From 322c3ca6737ea8a40802f4b73cfea9cc04216b22 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 24 Jun 2019 10:00:43 -0400 Subject: [PATCH 001/377] Update readme.txt Update readme.txt text --- readme.txt | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/readme.txt b/readme.txt index c8da89e..b545c26 100644 --- a/readme.txt +++ b/readme.txt @@ -1,19 +1,26 @@ === Radio Station === -Contributors: kionae -Donate link: http://www.nlb-creations.com/donate +Contributors: kionae, tonyzeoli +Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 -Tested up to: 4.1 +Tested up to: 4.3.1 Stable tag: trunk Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin -includes the ability to associate users with "shows" (schedulable blocks of time that contain a description, and other meta information), and generate playlists -associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's show and current playlist. A schedule of -all show can also be generated. +Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. + +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli. + +If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. + +You may also submit feature requests here: https://github.com/netmix/radio-station/issues + +We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate + +Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin. == Installation == @@ -43,8 +50,9 @@ The following attributes are available for the shortcode: 'show_image' => If set to a value of 1, the show's avatar will be displayed. Default value is 0. 'show_djs' => If set to a value of 1, the names of the show's DJs will be displayed. Default value is 0. 'divheight' => Set the height, in pixels, of the individual divs in the 'divs' layout. Default is 45. + 'single_day' => Display schedule for only a single day of the week. Only works if you are using the 'list' format. Valid values are sunday, monday, tuesday, wednesday, thursday, friday, saturday. -For example, if you wish to display the schedule in 24-hour time format, use `[master-schedule time="24"]`. +For example, if you wish to display the schedule in 24-hour time format, use `[master-schedule time="24"]`. If you want to only show Sunday's schedule, use `[master-schedule list="list" single_day="sunday"]`. = How do I schedule a show? = @@ -220,6 +228,7 @@ Italian (it_IT) Russion (ru_RU) Serbian (sr_RS) Spanish (es_ES) +Catalan (ca) = Can you translate the plugin into my language? = @@ -229,6 +238,13 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.1.3 = +* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). + += 2.1.2 = +* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. +* Catalan translation added (Thank you to Victor Riera for the file!) + = 2.1.1 = * Bug fix - Fixed day of the week language translation issue in master schedule shortcode * Bug fix - Added some error checking in the sidebar widgets @@ -434,6 +450,13 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.1.3 = +* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). + += 2.1.2 = +* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. +* Catalan translation added (Thank you to Victor Riera for the file!) + = 2.1.1 = * Bug fix - Fixed day of the week language translation issue in master schedule shortcode * Bug fix - Added some error checking in the sidebar widgets From d3fab8d22a18b38969ebd0e173d3b3f65cfcd086 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 26 Jun 2019 10:06:56 -0400 Subject: [PATCH 002/377] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 98eacf7e30a71cc112249c21b14a676dcc637dce Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 15 Jul 2019 01:47:19 +1000 Subject: [PATCH 003/377] coding standard refactoring and minor updates --- {templates => css}/djonair.css | 102 +- css/jquery-ui.css | 489 ++++++++ {templates => css}/program-schedule.css | 254 ++-- .../images => images}/playlist-menu-icon.png | Bin images/radio-station-icon.png | Bin 0 -> 720 bytes .../images => images}/show-menu-icon.png | Bin includes/master_schedule.php | 704 ++++++----- includes/post_types.php | 1041 ++++++++++------- includes/shortcodes.php | 376 +++--- includes/support_functions.php | 580 ++++----- includes/widget_djcomingup.php | 267 ++--- includes/widget_djonair.php | 338 +++--- includes/widget_nowplaying.php | 161 +-- radio-station.php | 969 +++++---------- readme.txt | 2 +- templates/admin-export.php | 114 ++ templates/archive-playlist.php | 39 +- templates/author.php | 46 +- templates/help.php | 249 ++++ templates/playlist-archive-template.php | 38 +- templates/show-blog-archive-template.php | 57 +- templates/single-playlist.php | 42 +- templates/single-show.php | 115 +- 23 files changed, 3309 insertions(+), 2674 deletions(-) rename {templates => css}/djonair.css (94%) create mode 100644 css/jquery-ui.css rename {templates => css}/program-schedule.css (94%) rename {includes/images => images}/playlist-menu-icon.png (100%) create mode 100644 images/radio-station-icon.png rename {includes/images => images}/show-menu-icon.png (100%) create mode 100644 templates/admin-export.php create mode 100644 templates/help.php diff --git a/templates/djonair.css b/css/djonair.css similarity index 94% rename from templates/djonair.css rename to css/djonair.css index b1f9e07..0049cc7 100644 --- a/templates/djonair.css +++ b/css/djonair.css @@ -1,51 +1,51 @@ -/* Widget layout */ -.on-air-dj-avatar { - float: left; - padding-right: 15px; - padding-bottom: 5px; - display: block; - width: 50px; -} - -.on-air-dj-avatar img { - width: 50px; - height: 50px; -} - -.on-air-list, -.widget .on-air-list, -.on-air-upcoming-list, -.widget .on-air-upcoming-list { - list-style-type: none; -} - -.on-air-list li, -.widget .on-air-list li, -.on-air-upcoming-list li, -.widget .on-air-upcoming-list li { - font-size: 2em; -} - -.on-air-dj-sched { - font-size: .5em; - font-style: italics; -} -.radio-clear { - display: block; - clear: both; -} - -/* Schedule layout */ -.on-air-dj-schedule-day-block { - float: left; - margin-right: 25px; -} - -.on-air-dj-schedule-day-title {} -.on-air-dj-schedule-time-list {} -.on-air-dj-schedule-time-item {} -.on-air-dj-schedule-dj-list {} -.on-air-dj-schedule-dj-item {} -.on-air-no-dj { font-style: italics; } -.on-air-dj-schedule-dj-item {} -.scheduled-dj {} +/* Widget layout */ +.on-air-dj-avatar { + float: left; + padding-right: 15px; + padding-bottom: 5px; + display: block; + width: 50px; +} + +.on-air-dj-avatar img { + width: 50px; + height: 50px; +} + +.on-air-list, +.widget .on-air-list, +.on-air-upcoming-list, +.widget .on-air-upcoming-list { + list-style-type: none; +} + +.on-air-list li, +.widget .on-air-list li, +.on-air-upcoming-list li, +.widget .on-air-upcoming-list li { + font-size: 2em; +} + +.on-air-dj-sched { + font-size: .5em; + font-style: italics; +} +.radio-clear { + display: block; + clear: both; +} + +/* Schedule layout */ +.on-air-dj-schedule-day-block { + float: left; + margin-right: 25px; +} + +.on-air-dj-schedule-day-title {} +.on-air-dj-schedule-time-list {} +.on-air-dj-schedule-time-item {} +.on-air-dj-schedule-dj-list {} +.on-air-dj-schedule-dj-item {} +.on-air-no-dj { font-style: italics; } +.on-air-dj-schedule-dj-item {} +.scheduled-dj {} diff --git a/css/jquery-ui.css b/css/jquery-ui.css new file mode 100644 index 0000000..3c4a611 --- /dev/null +++ b/css/jquery-ui.css @@ -0,0 +1,489 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.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:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* 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: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; } +.ui-widget-header a { color: #222222; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; 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: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.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; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.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-off { background-position: -96px -144px; } +.ui-icon-radio-on { 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-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; 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;}/* Selectable +----------------------------------*/ +.ui-selectable-helper { border:1px dotted black } +/* Accordion +----------------------------------*/ +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +/* IE7-/Win - Fix extra vertical space in lists */ +.ui-accordion a { zoom: 1; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* Autocomplete +----------------------------------*/ +.ui-autocomplete { position: absolute; cursor: default; } +.ui-autocomplete-loading { background: white url('images/ui-anim_basic_16x16.gif') right center no-repeat; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* Menu +----------------------------------*/ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* Button +----------------------------------*/ + +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.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: 1.4; } +.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 .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.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 .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 .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-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 */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ + + + + + +/* Dialog +----------------------------------*/ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* Slider +----------------------------------*/ +.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; } + +.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; }/* Tabs +----------------------------------*/ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.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: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; 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-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.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 { border-left-width:0; } +.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%; } + +/* 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 { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/templates/program-schedule.css b/css/program-schedule.css similarity index 94% rename from templates/program-schedule.css rename to css/program-schedule.css index d250aa5..4890650 100644 --- a/templates/program-schedule.css +++ b/css/program-schedule.css @@ -1,127 +1,127 @@ -/* Table Style Gird */ - -#master-program-schedule { - width: 100%; -} - -#master-program-schedule th { - width: 12%; - text-align: center; -} - -#master-program-schedule td { - vertical-align: top; - font-size: 12px; - text-align: center; - padding: 0px; -} - -#master-program-schedule td { - border: 1px solid black; -} - -#master-program-schedule td div { - border-top: 1px solid #dddddd; -} - -#master-program-schedule span.show-title, -#master-program-schedule span.show-file, -#master-program-schedule span.show-time, -#master-program-schedule span.show-encore { - display: block; -} - -#master-program-schedule span.show-time { - font-size: 10px; -} - -#master-program-schedule span.show-encore { - font-size: 10px; - color: #EE8E66; -} - -#master-program-schedule span.show-file { - margin-bottom: 5px; - margin-top: 5px; -} - -#master-program-schedule span.show-file a { - width: 95%; - height: 20px; - background-color: #931B25; - padding: 3px; - text-decoration: none; - color: #ffffff; - margin-bottom: 3px; -} - -#master-program-schedule span.show-file a:hover { - background-color: #C51D2E; - color: #ffffff; -} - -#master-genre-list { - font-size: 10px; -} - -#master-genre-list span.heading { - font-weight: bold; -} - -/* Div Style Grid */ -#master-schedule-divs { - width: 100%; -} - -#master-schedule-divs .master-schedule-hour { - width: 100%; - clear: both; -} - -#master-schedule-divs .master-schedule-hour-header { - width: 12%; - float: left; - text-align: center; - font-weight: bold; - font-size: 0.7em; -} - -#master-schedule-divs .master-schedule-weekday { - width: 10%; - float: left; - border: 1px solid #dddddd; -} - -#master-schedule-divs .master-schedule-weekday-header { - text-align: center; - font-weight: bold; - font-size: 0.7em; - display: block; -} - -#master-schedule-divs .master-show-entry { - padding: 8px 5px 5px 5px; - position: relative; -} - -#master-schedule-divs .show-dj-names, -#master-schedule-divs .show-time, -#master-schedule-divs .show-title { - display: block; - font-size: 0.6em; - line-height: 1em; -} - -#master-schedule-divs .show-image img { - width: 100%; - height: auto; -} - -#master-schedule-divs .rowspan { - background-color: #dddddd; - margin-left: -1px; - position: absolute; - width: 50px; - z-index: 5; -} - +/* Table Style Gird */ + +#master-program-schedule { + width: 100%; +} + +#master-program-schedule th { + width: 12%; + text-align: center; +} + +#master-program-schedule td { + vertical-align: top; + font-size: 12px; + text-align: center; + padding: 0px; +} + +#master-program-schedule td { + border: 1px solid black; +} + +#master-program-schedule td div { + border-top: 1px solid #dddddd; +} + +#master-program-schedule span.show-title, +#master-program-schedule span.show-file, +#master-program-schedule span.show-time, +#master-program-schedule span.show-encore { + display: block; +} + +#master-program-schedule span.show-time { + font-size: 10px; +} + +#master-program-schedule span.show-encore { + font-size: 10px; + color: #EE8E66; +} + +#master-program-schedule span.show-file { + margin-bottom: 5px; + margin-top: 5px; +} + +#master-program-schedule span.show-file a { + width: 95%; + height: 20px; + background-color: #931B25; + padding: 3px; + text-decoration: none; + color: #ffffff; + margin-bottom: 3px; +} + +#master-program-schedule span.show-file a:hover { + background-color: #C51D2E; + color: #ffffff; +} + +#master-genre-list { + font-size: 10px; +} + +#master-genre-list span.heading { + font-weight: bold; +} + +/* Div Style Grid */ +#master-schedule-divs { + width: 100%; +} + +#master-schedule-divs .master-schedule-hour { + width: 100%; + clear: both; +} + +#master-schedule-divs .master-schedule-hour-header { + width: 12%; + float: left; + text-align: center; + font-weight: bold; + font-size: 0.7em; +} + +#master-schedule-divs .master-schedule-weekday { + width: 10%; + float: left; + border: 1px solid #dddddd; +} + +#master-schedule-divs .master-schedule-weekday-header { + text-align: center; + font-weight: bold; + font-size: 0.7em; + display: block; +} + +#master-schedule-divs .master-show-entry { + padding: 8px 5px 5px 5px; + position: relative; +} + +#master-schedule-divs .show-dj-names, +#master-schedule-divs .show-time, +#master-schedule-divs .show-title { + display: block; + font-size: 0.6em; + line-height: 1em; +} + +#master-schedule-divs .show-image img { + width: 100%; + height: auto; +} + +#master-schedule-divs .rowspan { + background-color: #dddddd; + margin-left: -1px; + position: absolute; + width: 50px; + z-index: 5; +} + diff --git a/includes/images/playlist-menu-icon.png b/images/playlist-menu-icon.png similarity index 100% rename from includes/images/playlist-menu-icon.png rename to images/playlist-menu-icon.png diff --git a/images/radio-station-icon.png b/images/radio-station-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb1471162c7d73d0bfc0b7fbf29c325bea73476 GIT binary patch literal 720 zcmV;>0x$iEP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;(#7eog=Y0zpYcK~y+Tm6Od&RACgx$0Tc^rBMr4wF)E< zk^);~gq5rQf)+0N1447{j4s5LxDoslV#=9;YQDd2GmHjfi*O?@M8pV0{K6;^rr&cp zH4ArH-t#`sdGCGixuGDW)9Gu4LLrjR=U0foM9U+UN~OzW%^{b|Jt!87-!bpN zc^bVGl~do-Ua3^-H~IO%*o*Oi_z}}}*q+H`o{7qn$>a_8Q#9Qt-Z5J+XtacIxn8ed z2{#06GpV|Kg zr;i}s4Tr-Qh%x9s0rmT0qtR&58^CwbXtb66L-JRy*$)V&;i~0d5zi8R?y;NJS}ULFz$G2|$#xkl)lG^**2 z`RW7G>fTik1kLK0YTQwq<2c '12', 'show_link' => 1, @@ -26,622 +18,604 @@ function master_schedule($atts) { 'show_djs' => 0, 'divheight' => 45 ), $atts ) ); - + $timeformat = $time; - - //$overrides = master_get_overrides(true); - - //set up the structure of the master schedule - $default_dj = get_option('dj_default_name'); - - //check to see what day of the week we need to start on - $start_of_week = get_option('start_of_week'); - $days_of_the_week = array('Sunday' => array(), 'Monday' => array(), 'Tuesday' => array(), 'Wednesday' => array(), 'Thursday' => array(), 'Friday' => array(), 'Saturday' => array()); - $week_start = array_slice($days_of_the_week, $start_of_week); - - foreach($days_of_the_week as $i => $weekday) { - if($start_of_week > 0) { + + // $overrides = radio_station_master_get_overrides(true); + + // set up the structure of the master schedule + $default_dj = get_option( 'dj_default_name' ); + + // check to see what day of the week we need to start on + $start_of_week = get_option( 'start_of_week' ); + $days_of_the_week = array( 'Sunday' => array(), 'Monday' => array(), 'Tuesday' => array(), 'Wednesday' => array(), 'Thursday' => array(), 'Friday' => array(), 'Saturday' => array() ); + $week_start = array_slice( $days_of_the_week, $start_of_week ); + + foreach ( $days_of_the_week as $i => $weekday ) { + if ( $start_of_week > 0 ) { $add = $days_of_the_week[$i]; - unset($days_of_the_week[$i]); - + unset( $days_of_the_week[$i] ); $days_of_the_week[$i] = $add; } $start_of_week--; } - - //create the master_list array based on the start of the week + + // create the master_list array based on the start of the week $master_list = array(); - for($i=0; $i<24; $i++) { - $master_list[$i] = $days_of_the_week; - } - - //get the show schedules, excluding shows marked as inactive - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` + for ( $i=0; $i<24; $i++ ) {$master_list[$i] = $days_of_the_week;} + + + // get the show schedules, excluding shows marked as inactive + $show_shifts = $wpdb->get_results( "SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` JOIN ".$wpdb->prefix."postmeta AS `active` ON `meta`.`post_id` = `active`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` + JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' + AND `posts`.`post_status` = 'publish' AND ( `active`.`meta_key` = 'show_active' - AND `active`.`meta_value` = 'on');"); - - //insert schedules into the master list - foreach($show_shifts as $shift) { - $shift->meta_value = unserialize($shift->meta_value); - - //if a show is not scheduled yet, unserialize will return false... fix that. - if(!is_array($shift->meta_value)) { - $shift->meta_value = array(); - } - - foreach($shift->meta_value as $time) { - //switch to 24-hour time + AND `active`.`meta_value` = 'on');" ); + + // insert schedules into the master list + foreach ( $show_shifts as $shift ) { + $shift->meta_value = unserialize( $shift->meta_value ); + + // if a show is not scheduled yet, unserialize will return false... fix that. + if ( !is_array($shift->meta_value ) ) {$shift->meta_value = array();} + + foreach ( $shift->meta_value as $time ) { + + // switch to 24-hour time if($time['start_meridian'] == 'pm' && $time['start_hour'] != 12) { $time['start_hour'] += 12; } if($time['start_meridian'] == 'am' && $time['start_hour'] == 12) { $time['start_hour'] = 0; } - + if($time['end_meridian'] == 'pm' && $time['end_hour'] != 12) { $time['end_hour'] += 12; } if($time['end_meridian'] == 'am' && $time['end_hour'] == 12) { $time['end_hour'] = 0; } - - //check if we're spanning multiple days + + // check if we're spanning multiple days $time['multi-day'] = 0; if( ($time['start_hour'] > $time['end_hour']) || ($time['start_hour'] == $time['end_hour']) ) { $time['multi-day'] = 1; } - - $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array('id'=> $shift->post_id, 'time' => $time); + + $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array( 'id'=> $shift->post_id, 'time' => $time ); } } - - //sort the array by time - foreach($master_list as $hour => $days) { - foreach($days as $day => $min) { + + // sort the array by time + foreach ( $master_list as $hour => $days ) { + foreach ( $days as $day => $min ) { ksort($min); $master_list[$hour][$day] = $min; - - //we need to take into account shows that start late at night and end the following day + + // we need to take into account shows that start late at night and end the following day foreach($min as $i => $time) { - //if it ends at midnight, we don't need to worry about carry-over + + // if it ends at midnight, we don't need to worry about carry-over if($time['time']['end_hour'] == "0" && $time['time']['end_min'] == "00") { continue; } - - //if it ends after midnight, fix it + + // if it ends after midnight, fix it if( ($time['time']['start_meridian'] == 'pm' && $time['time']['end_meridian'] == 'am') || //if it starts at night and ends in the morning, end hour is on the following day ($time['time']['start_hour'].$time['time']['start_min'].$time['time']['start_meridian'] == $time['time']['end_hour'].$time['time']['end_min'].$time['time']['end_meridian']) || //if the start and end times are identical, assume the end time is the following day ($time['time']['start_meridian'] == 'am' && $time['time']['start_hour'] > $time['time']['end_hour']) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day ) { - + if($timeformat == 12) { $time['time']['real_start'] = ($time['time']['start_hour']-12).':'.$time['time']['start_min']; - } - else { + } else { $pad_hour = ""; - if($time['time']['start_hour'] < 10) { + if ( $time['time']['start_hour'] < 10) { $pad_hour = "0"; } $time['time']['real_start'] = $pad_hour.$time['time']['start_hour'].':'.$time['time']['start_min']; } - //$time['time']['start_hour'] = "0"; - //$time['time']['start_min'] = "00"; - //$time['time']['start_meridian'] = "am"; + // $time['time']['start_hour'] = "0"; + // $time['time']['start_min'] = "00"; + // $time['time']['start_meridian'] = "am"; $time['time']['rollover'] = 1; - - if($day == 'Sunday') { $nextday = 'Monday'; } - if($day == 'Monday') { $nextday = 'Tuesday'; } - if($day == 'Tuesday') { $nextday = 'Wednesday'; } - if($day == 'Wednesday') { $nextday = 'Thursday'; } - if($day == 'Thursday') { $nextday = 'Friday'; } - if($day == 'Friday') { $nextday = 'Saturday'; } - if($day == 'Saturday') { $nextday = 'Sunday'; } - + + if ( $day == 'Sunday' ) {$nextday = 'Monday';} + if ( $day == 'Monday' ) {$nextday = 'Tuesday';} + if ( $day == 'Tuesday' ) {$nextday = 'Wednesday';} + if ( $day == 'Wednesday' ) {$nextday = 'Thursday';} + if ( $day == 'Thursday' ) {$nextday = 'Friday';} + if ( $day == 'Friday' ) {$nextday = 'Saturday';} + if ( $day == 'Saturday' ) {$nextday = 'Sunday';} + $master_list[0][$nextday]['00'] = $time; - + } } } } - + $output = ''; - if($list == 1 || $list == 'list') { - //output as a list + if ( ($list == 1) || ($list == 'list') ) { + + // output as a list $flip = $days_of_the_week; - foreach($master_list as $hour => $days) { - foreach($days as $day => $mins) { + foreach ( $master_list as $hour => $days ) { + foreach ( $days as $day => $mins ) { foreach($mins as $fmin => $fshow) { $flip[$day][$hour][$fmin] = $fshow; } } } - + $output .= '
    '; - - foreach($flip as $day => $hours) { + + foreach ( $flip as $day => $hours ) { $output .= '
  • '; $output .= ''.__($day, 'radio-station').''; $output .= '
      '; - foreach($hours as $hour => $mins) { - - foreach($mins as $min => $show) { + foreach ($hours as $hour => $mins) { + + foreach ($mins as $min => $show ) { $output .= '
    • '; - + if($show_image) { $output .= ''; - if(has_post_thumbnail($show['id'])) { + if ( has_post_thumbnail( $show['id'] ) ) { $output .= get_the_post_thumbnail($show['id'], 'thumbnail'); } $output .= ''; } - + $output .= ''; - if($show_link) { + if ( $show_link ) { $output .= ''.get_the_title($show['id']).''; - } - else { - $output .= get_the_title($show['id']); + } else { + $output .= get_the_title( $show['id'] ); } $output .= ''; - - if($show_djs) { + + if ( $show_djs ) { $output .= ''; - - $names = get_post_meta($show['id'], 'show_user_list', true); + + $names = get_post_meta( $show['id'], 'show_user_list', true ); $count = 0; - - if($names) { + + if ( $names ) { $output .= ' with '; - foreach($names as $name) { + foreach ( $names as $name ) { $count ++; - $user_info = get_userdata($name); - + $user_info = get_userdata( $name ); + $output .= $user_info->display_name; - - if( ($count == 1 && count($names) == 2) || (count($names) > 2 && $count == count($names)-1) ) { + + if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) + || ( (count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { $output .= ' and '; - } - elseif($count < count($names) && count($names) > 2) { + } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { $output .= ', '; } - else { - //do nothing - } } } - + $output .= ' '; } - - if($display_show_time) { + + if ( $display_show_time ) { + $output .= ''; - if($timeformat == 12) { - //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } - else { - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - } - $output .= ''; + + if ( $timeformat == 12 ) { + + //$output .= $weekday.' '; + $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); + $output .= ' - '; + $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); + + } else { + + $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); + $output .= ' - '; + $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); + + } + + $output .= ''; } - - if(isset($show['time']['encore']) && $show['time']['encore'] == 'on') { + + if ( isset( $show['time']['encore'] ) && ( $show['time']['encore'] == 'on' ) ) { $output .= ' '.__('encore airing', 'radio-station').''; } - - $link = get_post_meta($show['id'], 'show_file', true); - if($link != '') { + + $link = get_post_meta( $show['id'], 'show_file', true ); + if ( $link && ( $link != '' ) ) { $output .= ' '.__('Audio File', 'radio-station').''; } - + $output .= '
    • '; } } $output .= '
    '; $output .= '
  • '; } - + $output .= '
'; - } - elseif($list == 'divs') { - - //output some dynamic styles + + } elseif ( $list == 'divs' ) { + + // output some dynamic styles $output .= ''; - - //output the schedule - $output .= master_fetch_js_filter(); + + // output the schedule + $output .= radio_station_master_fetch_js_filter(); $output .= '
'; $weekdays = array_keys($days_of_the_week); - + $output .= '
'; $output .= '
 
'; - foreach($weekdays as $weekday) { - $output .= '
'.__($weekday, 'radio-station').'
'; + foreach( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
'.$translated.'
'; } $output .= '
'; - + foreach($master_list as $hour => $days) { - + $output .= '
'; - - //output the hour labels + + // output the hour labels $output .= '
'; - if($timeformat == 12) { + if ( $timeformat == 12 ) { $output .= date('ga', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 12-hour format - } - else { + } else { $output .= date('H:i', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 24-hour format } $output .= '
'; - - - foreach($weekdays as $weekday) { + + foreach ( $weekdays as $weekday ) { $output .= '
'; - if(isset($days[$weekday])) { - foreach($days[$weekday] as $min => $showdata) { - + if ( isset( $days[$weekday] ) ) { + foreach ( $days[$weekday] as $min => $showdata ) { + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); $classes = ' show-id-'.$showdata['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($showdata['id']))).' '; - foreach($terms as $term) { + foreach ( $terms as $term ) { $classes .= sanitize_title_with_dashes($term->name).' '; } - + $output .= '
'; - - //featured image - if($show_image) { + + // featured image + if ( $show_image ) { $output .= ''; - if(has_post_thumbnail($showdata['id'])) { - $output .= get_the_post_thumbnail($showdata['id'], 'thumbnail'); + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); } $output .= ''; } - - //title + link to page if requested + + // title + link to page if requested $output .= ''; - if($show_link) { + if ( $show_link ) { $output .= ''.get_the_title($showdata['id']).''; - } - else { + } else { $output .= get_the_title($showdata['id']); } $output .= ''; - - //list of DJs - if($show_djs) { + + // list of DJs + if ( $show_djs ) { + $output .= ''; - - $names = get_post_meta($showdata['id'], 'show_user_list', true); + + $names = get_post_meta( $showdata['id'], 'show_user_list', true ); $count = 0; - - if($names) { + + if ( $names ) { + $output .= ' with '; - foreach($names as $name) { - $count ++; - $user_info = get_userdata($name); - + + foreach ( $names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + $output .= $user_info->display_name; - - if( ($count == 1 && count($names) == 2) || (count($names) > 2 && $count == count($names)-1) ) { + + if ( ( ($count == 1) && ( count($names) == 2) ) + || ( (count($names) > 2 ) && ( $count == ( count($names) -1 ) ) ) ) { $output .= ' and '; - } - elseif($count < count($names) && count($names) > 2) { + } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { $output .= ', '; } - else { - //do nothing - } } } - + $output .= ''; } - - //show's schedule - if($display_show_time) { + + // show's schedule + if ( $display_show_time ) { + $output .= ''; - if($timeformat == 12) { - //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ')); + + if ( $timeformat == 12 ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ')); - } - else { - $output .= date('H:i', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ')); + $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); + } else { + $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ')); + $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); } $output .= ''; } - - //designate as encore - if(isset($showdata['time']['encore']) && $showdata['time']['encore'] == 'on') { + + // designate as encore + if ( isset($showdata['time']['encore']) && ( $showdata['time']['encore'] == 'on' ) ) { $output .= ''.__('encore airing', 'radio-station').''; } - - //link to media file - $link = get_post_meta($showdata['id'], 'show_file', true); - if($link != '') { + + // link to media file + $link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $link && ( $link != '' ) ) { $output .= ''.__('Audio File', 'radio-station').''; } - - //calculate duration of show for rowspanning - if(isset($showdata['time']['rollover'])) { //show started on the previous day + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day $duration = $showdata['time']['end_hour']; - } - else { - if($showdata['time']['end_hour'] >= $showdata['time']['start_hour']) { + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } - else { + } else { $duration = 23 - $showdata['time']['start_hour']; } } - - if($duration >= 1) { + + if ( $duration >= 1 ) { $output .= '
'; - - if($showdata['time']['end_min'] != '00') { + + if ( $showdata['time']['end_min'] != '00' ) { $output .= '
'; } } - - - - $output .= '
'; //end master-show-entry + + $output .= '
'; // end master-show-entry } } - $output .= '
'; //end master-schedule-weekday + $output .= '
'; // end master-schedule-weekday } - $output .= ''; //end master-schedule-hour + $output .= ''; // end master-schedule-hour } - $output .= ''; //end master-schedule-divs - } - else { - //create the output in a table - $output .= master_fetch_js_filter(); - + $output .= ''; // end master-schedule-divs + + } else { + + // create the output in a table + $output .= radio_station_master_fetch_js_filter(); + $output .= ''; - - //output the headings in the correct order + + // output the headings in the correct order $output .= ''; - foreach($days_of_the_week as $heading => $info) { - $output .= ''; + foreach($days_of_the_week as $weekday => $info) { + $heading = substr( $heading, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= ''; } $output .= ''; - //$output .= ''; - - if(!isset($nextskip)) { - $nextskip = array(); - } - - foreach($master_list as $hour => $days) { + // $output .= ''; + + if ( !isset( $nextskip ) ) {$nextskip = array();} + + foreach ( $master_list as $hour => $days ) { + $output .= ''; $output .= ''; - + $curskip = $nextskip; $nextskip = array(); - - foreach($days as $day => $min) { - - //overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours $continue = 0; - foreach($curskip as $x => $skip) { - - if($skip['day'] == $day) { - if($skip['span'] > 1 ) { + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] == $day ) { + if ( $skip['span'] > 1 ) { $continue = 1; $skip['span'] = $skip['span']-1; $curskip[$x]['span'] = $skip['span']; $nextskip = $curskip; } - } + } } - + $rowspan = 0; - foreach($min as $shift) { - - if($shift['time']['start_hour'] == 0 && $shift['time']['end_hour'] == 0) { ///midnight to midnight shows - if($shift['time']['start_min'] == $shift['time']['end_min']) { //accomodate shows that end midway through the 12am hour + foreach ( $min as $shift ) { + + if ( $shift['time']['start_hour'] == 0 && $shift['time']['end_hour'] == 0 ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] == $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour $rowspan = 24; } } - - if($shift['time']['end_hour'] == 0 && $shift['time']['start_hour'] != 0) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + + if ( $shift['time']['end_hour'] == 0 && $shift['time']['start_hour'] != 0 ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span $rowspan = $rowspan + (24 - $shift['time']['start_hour']); - } - elseif($shift['time']['start_hour'] > $shift['time']['end_hour']) { // show runs from before midnight night until the next morning - //print_r($shift);die('moo'); - if(isset($shift['time']['real_start'])) { //if we're on the second day of a show that spans two days - + } elseif($shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning + // print_r($shift);die('moo'); + if ( isset($shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); } - else { //if we're on the first day of a show that spans two days - $rowspan = $rowspan + (24 - $shift['time']['start_hour']); - } - } - else { //all other shows + } else { + // all other shows $rowspan = $rowspan + ($shift['time']['end_hour'] - $shift['time']['start_hour']); } } - + $span = ''; - if($rowspan > 1) { + if ( $rowspan > 1 ) { $span = ' rowspan="'.$rowspan.'"'; - //add to both arrays - $curskip[] = array('day' => $day, 'span' => $rowspan, 'show' => get_the_title($shift['id'])); - $nextskip[] = array('day' => $day, 'span' => $rowspan, 'show' => get_the_title($shift['id'])); + // add to both arrays + $curskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); + $nextskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); } - - //if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if($continue) { - continue; - } - + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ($continue) {continue;} + $output .= ''; - - foreach($min as $shift) { + + foreach ( $min as $shift ) { + //print_r($shift); - + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); $classes = ' show-id-'.$shift['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($shift['id']))).' '; - foreach($terms as $term) { - $classes .= sanitize_title_with_dashes($term->name).' '; + foreach ( $terms as $term ) { + $classes .= sanitize_title_with_dashes( $term->name ).' '; } - + $output .= '
'; - - if($show_image) { + + if ( $show_image ) { $output .= ''; - if(has_post_thumbnail($shift['id'])) { + if ( has_post_thumbnail($shift['id'] ) ) { $output .= get_the_post_thumbnail($shift['id'], 'thumbnail'); } $output .= ''; } - + $output .= ''; - if($show_link) { + if ( $show_link ) { $output .= ''.get_the_title($shift['id']).''; - } - else { + } else { $output .= get_the_title($shift['id']); } $output .= ''; - - if($show_djs) { + + if ( $show_djs ) { + $output .= ''; - - $names = get_post_meta($shift['id'], 'show_user_list', true); + + $names = get_post_meta( $shift['id'], 'show_user_list', true ); $count = 0; - - if($names) { - $output .= ' with '; - foreach($names as $name) { + + if ( $names ) { + + $output .= ' '.__( 'with','radio-station' ).' '; + foreach( $names as $name ) { $count ++; $user_info = get_userdata($name); - + $output .= $user_info->display_name; - - if( ($count == 1 && count($names) == 2) || (count($names) > 2 && $count == count($names)-1) ) { + + if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) + || ( ( count($names) > 2) && ( $count == ( count($names) -1 ) ) ) ) { $output .= ' and '; - } - elseif($count < count($names) && count($names) > 2) { + } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { $output .= ', '; } - else { - //do nothing - } } } - + $output .= ''; } - - if($display_show_time) { + + if ( $display_show_time ) { + $output .= ''; - if($timeformat == 12) { + + if ( $timeformat == 12 ) { //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ')); + $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].' ')); - } - else { - $output .= date('H:i', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ')); + $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].' ') ); + } else { + $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].':00 ')); + $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].':00 ') ); } $output .= ''; } - - if(isset($shift['time']['encore']) && $shift['time']['encore'] == 'on') { + + if ( isset($shift['time']['encore']) && $shift['time']['encore'] == 'on' ) { $output .= ''.__('encore airing', 'radio-station').''; } - + $link = get_post_meta($shift['id'], 'show_file', true); - if($link != '') { + if ( $link && ( $link != '' ) ) { $output .= ''.__('Audio File', 'radio-station').''; } - + $output .= '
'; } $output .= ''; } - + $output .= '
'; } $output .= '
'.__(substr($heading, 0, 3), 'radio-station').''.$heading.'
'.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
'.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
'; - - if($timeformat == 12) { - if($hour == 0) { - $output .= '12am'; - } - elseif($hour < 12) { - $output .= $hour.'am'; - } - elseif($hour == 12) { - $output .= '12pm'; - } - else { - $output .= ($hour-12).'pm'; - } - } - else { - if($hour < 10) { - $output .= "0"; - } + + if ( $timeformat == 12 ) { + if ( $hour == 0 ) {$output .= '12am';} + elseif ( $hour < 12 ) {$output .= $hour.'am';} + elseif ( $hour == 12 ) {$output .= '12pm';} + else {$output .= ($hour-12).'pm';} + } else { + if ( $hour < 10 ) {$output .= "0";} $output .= $hour.":00"; } - + $output .= '
'; } - + return $output; - + } -add_shortcode( 'master-schedule', 'master_schedule'); +add_shortcode( 'master-schedule', 'radio_station_master_schedule'); + +// --- add javascript for highlighting shows based on genre --- +function radio_station_master_fetch_js_filter(){ -//add javascript for highlighting shows based on genre -function master_fetch_js_filter(){ $js = '
'.__('Genres', 'radio-station').': '; - - $taxes = get_terms('genres', array('hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC')); - foreach($taxes as $i => $tax) { + + $taxes = get_terms( 'genres', array('hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC') ); + foreach ( $taxes as $i => $tax ) { $js .= ''.$tax->name.''; if($i < count($taxes)) { $js .= ' | '; } } - + $js .= '
'; - + $js .= ''; - + return $js; } - -?> \ No newline at end of file diff --git a/includes/post_types.php b/includes/post_types.php index 04ef5d5..8d7b11b 100644 --- a/includes/post_types.php +++ b/includes/post_types.php @@ -1,129 +1,232 @@ array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ) + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ) ), - 'show_ui' => true, - 'description' => __('Post type for Playlist descriptions', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png', - 'public' => true, - 'hierarchical' => false, - 'supports' => array('title', 'editor', 'comments'), - 'can_export' => true, - 'has_archive' => 'playlists-archive', - 'rewrite' => array('slug' => 'playlists'), - 'capability_type' => 'playlist', - 'map_meta_cap' => true + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __('Post type for Playlist descriptions', 'radio-station'), + 'menu_position' => 5, + 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'comments' ), + 'can_export' => true, + 'has_archive' => 'playlists-archive', + 'rewrite' => array('slug' => 'playlists'), + 'capability_type' => 'playlist', + 'map_meta_cap' => true ) ); - + + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'show', array( 'labels' => array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ) + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ) ), - 'show_ui' => true, - 'description' => __('Post type for Show descriptions', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png', - 'public' => true, - 'taxonomies' => array('genres'), - 'hierarchical' => false, - 'supports' => array('title', 'editor', 'thumbnail', 'comments'), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __('Post type for Show descriptions', 'radio-station'), + 'menu_position' => 5, + 'menu_icon' => $icon, + 'public' => true, + 'taxonomies' => array( 'genres' ), + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), + 'can_export' => true, + 'capability_type' => 'show', + 'map_meta_cap' => true ) ); - + + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'override', - array( - 'labels' => array( - 'name' => __( 'Schedule Override', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ) - ), - 'show_ui' => true, - 'description' => __('Post type for Schedule Override', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png', - 'public' => true, - 'hierarchical' => false, - 'supports' => array('title', 'thumbnail'), - 'can_export' => true, - 'rewrite' => array('slug' => 'show-override'), - 'capability_type' => 'show', - 'map_meta_cap' => true - ) + array( + 'labels' => array( + 'name' => __( 'Schedule Override', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ) + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __('Post type for Schedule Override', 'radio-station'), + 'menu_position' => 5, + 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'thumbnail' ), + 'can_export' => true, + 'rewrite' => array('slug' => 'show-override'), + 'capability_type' => 'show', + 'map_meta_cap' => true + ) ); } -add_action( 'init', 'station_create_post_types' ); +add_action( 'init', 'radio_station_create_post_types' ); + +// -------------------------- +// Add Show Thumbnail Support +// -------------------------- +// add featured image support to "show" post type +// (this is probably no longer necessary as declared in register_post_type for show) +function radio_station_add_featured_image_support() { + $supportedTypes = get_theme_support( 'post-thumbnails' ); + + if ( $supportedTypes === false ) { + add_theme_support( 'post-thumbnails', array( 'show' ) ); + } elseif ( is_array( $supportedTypes ) ) { + $supportedTypes[0][] = 'show'; + add_theme_support( 'post-thumbnails', $supportedTypes[0] ); + } +} +add_action( 'init', 'radio_station_add_featured_image_support' ); + +// ----------------------- +// Register Genre Taxonomy +// ----------------------- +// create custom taxonomy for the Show post type +function radio_station_myplaylist_create_show_taxonomy() { -/* Playlists */ + // add taxonomy labels + $labels = array( + 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), + 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), + 'search_items' => __( 'Search Genres', 'radio-station' ), + 'all_items' => __( 'All Genres', 'radio-station' ), + 'parent_item' => __( 'Parent Genre', 'radio-station' ), + 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), + 'edit_item' => __( 'Edit Genre', 'radio-station' ), + 'update_item' => __( 'Update Genre', 'radio-station' ), + 'add_new_item' => __( 'Add New Genre', 'radio-station' ), + 'new_item_name' => __( 'New Genre Name', 'radio-station' ), + 'menu_name' => __( 'Genre', 'radio-station' ), + ); + + // register the genre taxonomy + register_taxonomy( 'genres', array( 'show' ), + array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => false, + 'query_var' => true, + 'rewrite' => array('slug' => 'genre'), + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows' + ), + ) + ); + +} +add_action('init', 'radio_station_myplaylist_create_show_taxonomy'); -//Add custom repeating meta field for the playlist edit form... Stores multiple associated values as a serialized string -//Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -function myplaylist_add_custom_box() { + +// ----------------- +// === Playlists === +// ----------------- + +// --------------------- +// Playlist Data Metabox +// --------------------- + +// Add custom repeating meta field for the playlist edit form... Stores multiple associated values as a serialized string +// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 +function radio_station_myplaylist_add_custom_box() { add_meta_box( 'dynamic_sectionid', - __( 'Playlist Entries', 'radio-station' ), - 'myplaylist_inner_custom_box', - 'playlist'); + __( 'Playlist Entries', 'radio-station' ), + 'radio_station_myplaylist_inner_custom_box', + 'playlist' + ); } -add_action( 'add_meta_boxes', 'myplaylist_add_custom_box', 1 ); +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); + +// Prints the playlist entry box to the main column on the edit screen +function radio_station_myplaylist_inner_custom_box() { -//Prints the playlist entry box to the main column on the edit screen -function myplaylist_inner_custom_box() { global $post; + // Use nonce for verification wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); ?>
ID,'playlist',false); + // get the saved meta as an arry + $entries = get_post_meta( $post->ID, 'playlist', false ); //print_r($prices); $c = 1; - + echo ''; echo ""; echo ""; echo ""; - - if (isset($entries[0]) && !empty($entries[0])){ - - foreach($entries[0] as $track ){ - if (isset($track['playlist_entry_artist']) || isset($track['playlist_entry_song']) || isset($track['playlist_entry_album']) || isset($track['playlist_entry_label']) || isset($track['playlist_entry_comments']) || isset($track['playlist_entry_new']) || isset($track['playlist_entry_status'])){ + + if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { + + foreach( $entries[0] as $track ){ + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + echo ''; echo ''; echo ''; @@ -131,29 +234,29 @@ function myplaylist_inner_custom_box() { echo ''; echo ''; echo ''; - + echo ''; - + echo ''; - + echo ''; echo ''; - $c = $c +1; + $c++; } } } @@ -167,7 +270,7 @@ function myplaylist_inner_custom_box() { shiftadda(document).ready(function() { var count = ; shiftadda(".add").click(function() { - + shiftadda('#here').append('' ); count = count + 1; return false; @@ -208,96 +311,210 @@ function myplaylist_inner_custom_box() { $song) { - if($song['playlist_entry_status'] == 'queued') { + // move songs that are still queued to the end of the list so that order is maintained + foreach ( $playlist as $i => $song ) { + if ( $song['playlist_entry_status'] == 'queued' ) { $playlist[] = $song; unset($playlist[$i]); } } - + update_post_meta($post_id, 'playlist', $playlist); + + // sanitize and save show ID $show = $_POST['playlist_show_id']; - - update_post_meta($post_id,'playlist',$playlist); - update_post_meta($post_id,'playlist_show_id',$show); + if ( $show == '') { + delete_post_meta( $post_id, 'playlist_show_id' ); + } else { + $show = absint( $show ); + if ( $show > 0 ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + } + } } } -add_action( 'save_post', 'myplaylist_save_postdata' ); +add_action( 'save_post', 'radio_station_myplaylist_save_postdata' ); + + +// ------------- +// === Shows === +// ------------- + +// -------------------- +// Related Show Metabox +// -------------------- + +// --- Add custom meta box for show assigment on blog posts --- +function radio_station_add_showblog_box() { + + // make sure a show exists before adding metabox + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish' + ); + $shows = get_posts( $args ); + + if ( count( $shows ) > 0 ) { + + // add a filter for which post types to show metabox on + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + + add_meta_box( + 'dynamicShowBlog_sectionid', + __( 'Related to Show', 'radio-station' ), + 'radio_station_inner_showblog_custom_box', + $post_types, + 'side' + ); + } +} +add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -/* Shows*/ +// --- Prints the box content for the Show field --- +function radio_station_inner_showblog_custom_box() { + global $post; + + // Use nonce for verification + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); -//Add custom meta box for show assigment -function myplaylist_add_show_box() { + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish' + ); + $shows = get_posts( $args ); + $current = get_post_meta( $post->ID, 'post_showblog_id', true); + + ?> +
+ + +
+ 0 ) {update_post_meta( $post_id, 'post_showblog_id', $show );} + } + } + +} +add_action( 'save_post', 'radio_station_save_postdata' ); + + +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- + +// --- Add custom meta box for show assigment --- +function radio_station_myplaylist_add_show_box() { add_meta_box( 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'myplaylist_inner_show_custom_box', + __( 'Show', 'radio-station' ), + 'radio_station_myplaylist_inner_show_custom_box', 'playlist', - 'side'); + 'side' + ); } -add_action( 'add_meta_boxes', 'myplaylist_add_show_box' ); +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); + +// --- Prints the box content for the Show field --- +function radio_station_myplaylist_inner_show_custom_box() { + + global $post, $wpdb; -//Prints the box content for the Show field -function myplaylist_inner_show_custom_box() { - global $post; - global $wpdb; - // Use nonce for verification wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); $user = wp_get_current_user(); - - //exclude administrators... they should be able to do whatever they want - if(!in_array('administrator', $user->roles)) { - //get the user lists for all shows + + // exclude administrators... they should be able to do whatever they want + if ( !in_array('administrator', $user->roles ) ) { + + // get the user lists for all shows $allowed_shows = array(); - - $show_user_lists = $wpdb->get_results("SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'"); - - //check each list for the current user + + $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + + // check each list for the current user foreach($show_user_lists as $list) { - - $list->meta_value = unserialize($list->meta_value); - - //if a list has no users, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. - if(!is_array($list->meta_value)) { - $list->meta_value = array(); - } - - //only include shows the user is assigned to - foreach($list->meta_value as $user_id) { - if($user->ID == $user_id) { + + $list->meta_value = unserialize( $list->meta_value ); + + // if a list has no users, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. + if ( !is_array( $list->meta_value ) ) {$list->meta_value = array();} + + // only include shows the user is assigned to + foreach ( $list->meta_value as $user_id ) { + if ( $user->ID == $user_id ) { $allowed_shows[] = $list->post_id; - } + } } } - + $args = array( 'numberposts' => -1, 'offset' => 0, @@ -305,12 +522,13 @@ function myplaylist_inner_show_custom_box() { 'order' => 'aSC', 'post_type' => 'show', 'post_status' => 'publish', - 'include' => implode(',', $allowed_shows) + 'include' => implode( ',', $allowed_shows ) ); - $shows = get_posts($args); - } - else { //you're an administrator + $shows = get_posts( $args ); + + } else { + // you are an administrator $args = array( 'numberposts' => -1, 'offset' => 0, @@ -319,24 +537,22 @@ function myplaylist_inner_show_custom_box() { 'post_type' => 'show', 'post_status' => 'publish' ); - - $shows = get_posts($args); + + $shows = get_posts( $args ); } - $current = get_post_meta($post->ID,'playlist_show_id',true); - + $current = get_post_meta( $post->ID, 'playlist_show_id', true ); + ?>
- + @@ -344,147 +560,126 @@ function myplaylist_inner_show_custom_box() { true, - 'label' => __('Genres', 'radio-station'), - 'singular_label' => __('Genre', 'radio-station'), - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array('slug' => 'genre'), - 'capabilities' => array( - 'manage_terms' => 'edit_shows', - 'edit_terms' => 'edit_shows', - 'delete_terms' => 'edit_shows', - 'assign_terms' => 'edit_shows' - ), - ) - ); - -} -add_action('init', 'myplaylist_create_show_taxonomy'); - -//Adds a box to the side column of the show edit screens -function myplaylist_add_metainfo_box() { +// ---------------------------- +// Playlist Information Metabox +// ---------------------------- +// Adds a box to the side column of the show edit screens +function radio_station_myplaylist_add_metainfo_box() { add_meta_box( 'dynamicShowMeta_sectionid', - __( 'Information', 'radio-station' ), - 'myplaylist_inner_metainfo_custom_box', + __( 'Information', 'radio-station' ), + 'radio_station_myplaylist_inner_metainfo_custom_box', 'show'); } -add_action( 'add_meta_boxes', 'myplaylist_add_metainfo_box' ); +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_metainfo_box' ); + +// --- Prints the box for additional meta data for the Show post type --- +function radio_station_myplaylist_inner_metainfo_custom_box() { -//Prints the box for additional meta data for the Show post type -function myplaylist_inner_metainfo_custom_box() { global $post; - $file = get_post_meta($post->ID,'show_file',true); - $email = get_post_meta($post->ID,'show_email',true); - $active = get_post_meta($post->ID,'show_active',true); - $link = get_post_meta($post->ID,'show_link',true); + $file = get_post_meta( $post->ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + // added max-width to prevent metabox overflows ?>
- +

- />
+ />

- +


-

- +

+


-

- +

+


-

- +

+
roles as $name => $role) { + + // check for roles that have the edit_shows capability enabled + $add_roles = array( 'dj' ); + foreach ( $wp_roles->roles as $name => $role ) { foreach($role['capabilities'] as $capname => $capstatus) { - if($capname == "edit_shows" && ($capstatus == 1 || $capstatus == true)) { + if ( ( $capname == 'edit_shows' ) && ( ( $capstatus == 1 ) || ( $capstatus == true ) ) ) { $add_roles[] = $name; } } } - - $add_roles = array_unique($add_roles); - - //create the meta query for get_users() - $meta_query = array('relation' => 'OR'); - foreach($add_roles as $role) { + $add_roles = array_unique( $add_roles ); + + // create the meta query for get_users() + $meta_query = array( 'relation' => 'OR' ); + foreach ( $add_roles as $role ) { $meta_query[] = array( - 'key' => $table_prefix.'capabilities', - 'value' => $role, - 'compare' => 'like' - ); + 'key' => $wpdb->prefix.'capabilities', + 'value' => $role, + 'compare' => 'like' + ); } - - //get all eligible users + + // get all eligible users $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //'fields' => array('ID, display_name'), + 'meta_query' => $meta_query, + 'orderby' => 'display_name', + 'order' => 'ASC', + //'fields' => array('ID, display_name'), ); - - $users = get_users($args); + $users = get_users( $args ); - //get the DJs currently assigned to the show - $current = get_post_meta($post->ID,'show_user_list',true); - if(!$current) { - $current = array(); - } - - //move any selected DJs to the top of the list - foreach($users as $i => $dj) { - if(in_array($dj->ID, $current)) { - unset($users[$i]); //unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift($users, $dj); //prepend the user to the beginning of the array + // get the DJs currently assigned to the show + $current = get_post_meta( $post->ID, 'show_user_list', true); + if ( !$current ) {$current = array();} + + // move any selected DJs to the top of the list + foreach ( $users as $i => $dj ) { + if ( in_array($dj->ID, $current ) ) { + unset( $users[$i] ); //unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $users, $dj ); //prepend the user to the beginning of the array } } ?>
- + @@ -492,36 +687,39 @@ function myplaylist_inner_user_custom_box() {
ID,'show_sched',false); - + + // get the saved meta as an array + $shifts = get_post_meta( $post->ID, 'show_sched', false); + //print_r($shifts); $c = 0; - if (isset($shifts[0]) && is_array($shifts[0])){ - foreach($shifts[0] as $track ){ - if (isset($track['day']) || isset($track['time'])){ + if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { + foreach ( $shifts[0] as $track ){ + if ( isset( $track['day'] ) || isset( $track['time'] ) ){ ?>

- : + : - - - : + - + : - - - - : + + - + : - /> - + /> +

- @@ -601,33 +795,30 @@ function myplaylist_inner_sched_custom_box() { var count = ; shiftaddb(".add").click(function() { count = count + 1; - output = '

: '; + output = '

: '; output += ''; output += ' - : '; - + output += ' '; output += ' '; - output += ' - : '; + output += ' - : '; output += ' '; output += ' '; - output += ' '; + output += ' '; - output += '

'; + output += '

'; shiftaddb('#here').append( output ); return false; @@ -679,70 +867,76 @@ function myplaylist_inner_sched_custom_box() {
ID,'show_override_sched',false); - if($track) { - $track = $track[0]; - } - + if ($track) {$track = $track[0];} + ?> - +

- : - - - - : + : + + - + : - - - - : + + - + : - +

\ No newline at end of file diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 1b67919..4eab023 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -3,7 +3,8 @@ /* Shortcode for displaying the current song * Since 2.0.0 */ -function station_shortcode_now_playing($atts) { +function radio_station_shortcode_now_playing( $atts ) { + extract( shortcode_atts( array( 'title' => '', 'artist' => 1, @@ -13,86 +14,81 @@ function station_shortcode_now_playing($atts) { 'comments' => 0 ), $atts ) ); - $most_recent = myplaylist_get_now_playing(); + $most_recent = radio_station_myplaylist_get_now_playing(); $output = ''; - if($most_recent) { + if ( $most_recent ) { $class = ''; - if(isset($most_recent['playlist_entry_new']) && $most_recent['playlist_entry_new'] == 'on') { + if ( isset( $most_recent['playlist_entry_new'] ) && ( $most_recent['playlist_entry_new'] == 'on') ) { $class = ' class="new"'; } $output .= '
'; - if($title != '') { - $output .= '

'.$title.'

'; - } + if ($title != '') {$output .= '

'.$title.'

';} - if($song == 1) { + if ( $song == 1 ) { $output .= ''.$most_recent['playlist_entry_song'].' '; } - if($artist == 1) { + if ( $artist == 1 ) { $output .= ''.$most_recent['playlist_entry_artist'].' '; } - if($album == 1) { + if ( $album == 1 ) { $output .= ''.$most_recent['playlist_entry_album'].' '; } - if($label == 1) { + if ( $label == 1 ) { $output .= ''.$most_recent['playlist_entry_label'].' '; } - if($comments == 1) { + if ( $comments == 1 ) { $output .= ''.$most_recent['playlist_entry_comments'].' '; } $output .= ''.__('View Playlist', 'radio-station').' '; $output .= '
'; - } - else { + } else { echo 'No playlists available.'; } + return $output; } -add_shortcode('now-playing', 'station_shortcode_now_playing'); +add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); /* Shortcode to fetch all playlists for a given show id * Since 2.0.0 */ -function station_shortcode_get_playlists_for_show($atts) { +function radio_station_shortcode_get_playlists_for_show($atts) { + extract( shortcode_atts( array( 'show' => '', 'limit' => -1 ), $atts ) ); - //don't return anything if we don't have a show - if($show == '') { - return false; - } + // don't return anything if we do not have a show + if ( $show == '' ) {return false;} $args = array( - 'numberposts' => $limit, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $show + 'numberposts' => $limit, + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + 'meta_key' => 'playlist_show_id', + 'meta_value' => $show ); - $playlists = get_posts($args); + $playlists = get_posts( $args ); $output = ''; $output .= '
".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
'.$c.''; echo ''.__('Remove', 'radio-station').'
'+count+'
- - - - - - - - - - - - - - - - -
- - - - - - - -
- - - - - - - - -
  - -
- -
- -

'.__('Right-click and download this file to save your export', 'radio-station').'

'; + } -//Add custom meta box for show assigment on blog posts -function station_add_showblog_box() { - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'station_inner_showblog_custom_box', - 'post', - 'side'); -} -add_action( 'add_meta_boxes', 'station_add_showblog_box' ); - -//Prints the box content for the Show field -function station_inner_showblog_custom_box() { - global $post; - // Use nonce for verification - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - - $shows = get_posts($args); - $current = get_post_meta($post->ID,'post_showblog_id',true); - - ?> -
- - -
- -

Radio Station Help/FAQs

- - I've scheduled all my shows, but they're not showing up on the programming grid! -

Did you remember to check the "Active" checkbox for each show? If a show is not marked active, the plugin assumes that it's not currently in production and hides it on the grid.

- -
- - I'm seeing 404 Not Found errors when I click on the link for a show! -

Try re-saving your site's permalink settings. Wordpress sometimes gets confused with a new custom post type is added.

- -
- - How do I display a full schedule of my station's shows? -

Use the shortcode [master-schedule] on any page. This will generate a full-page schedule in one of three formats. -

- The following attributes are available for the shortcode: -

    -
  • 'list' => If set to a value of 'list', the schedule will display in list format rather than table or div format. Valid values are 'list', 'divs', 'table'. Default value is 'table'.
  • -
  • 'time' => The time format you with to use. Valid values are 12 and 24. Default is 12.
  • -
  • 'show_link' => Display the title of the show as a link to its profile page. Valid values are 0 for hide, 1 for show. Default is 1.
  • -
  • 'display_show_time' => Display start and end times of each show after the title in the grid. Valid values are 0 for hide, 1 for show. Default is 1.
  • -
  • 'show_image' => If set to a value of 1, the show's avatar will be displayed. Default value is 0.
  • -
  • 'show_djs' => If set to a value of 1, the names of the show's DJs will be displayed. Default value is 0.
  • -
  • 'divheight' => Set the height, in pixels, of the individual divs in the 'divs' layout. Default is 45.
  • -
-

- For example, if you wish to display the schedule in 24-hour time format, use [master-schedule time="24"].

- -
- - How do I schedule a show? - -

Simply create a new show. You will be able to assign it to any timeslot you wish on the edit page.

- -
- - What if I have a special event? - -

If you have a one-off event that you need to show up in the On-Air or Coming Up Next widgets, you can create a Schedule Override by clicking the Schedule Override tab in the Dashboard menu. This will allow you to set aside a block of time on a specific date, and will display the title you give it in the widgets. Please note that this will only override the widgets and their corresponding shortcodes. If you are using the weekly master schedule shortcode on a page, its output will not be altered.

- -
- - How do I get the last song played to show up? - -

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [now-playing] in your page/post, or use do_shortcode('[now-playing]'); in your template files. -

- The following attributes are available for the shortcode: -

    -
  • 'title' => The title you would like to appear over the now playing block
  • -
  • 'artist' => Display artist name. Valid values are 0 for hide, 1 for show. Default is 1.
  • -
  • 'song' => Display song name. Valid values are 0 for hide, 1 for show. Default is 1.
  • -
  • 'album' => Display album name. Valid values are 0 for hide, 1 for show. Default is 0.
  • -
  • 'label' => Display label name. Valid values are 0 for hide, 1 for show. Default is 0.
  • -
  • 'comments' => Display DJ comments. Valid values are 0 for hide, 1 for show. Default is 0.
  • -
-

- Example:
- [now-playing title="Current Song" artist="1" song="1" album="1" label="1" comments="0"]

- -
- - What about displaying the current DJ on air? - -

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [dj-widget] in your page/post, or you can use do_shortcode('[dj-widget]'); in your template files. -

- The following attributes are available for the shortcode: -

    -
  • 'title' > The title you would like to appear over the on-air block
  • -
  • 'display_djs' > Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0.
  • -
  • 'show_avatar' > Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0.
  • -
  • 'show_link' > Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0.
  • -
  • 'default_name' > The text you would like to display when no show is schedule for the current time.
  • -
  • 'time' > The time format used for displaying schedules. Valid values are 12 and 24. Default is 12.
  • -
  • 'show_sched' > Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1.
  • -
  • 'show_playlist' > Display a link to the show's current playlist. Valid values are 0 for hide link, 1 for show link. Default is 1.
  • -
  • 'show_all_sched' > Displays all schedules for a show if it airs on multiple days. Valid values are 0 for current schedule, 1 for all schedules. Default is 0.
  • -
  • 'show_desc' > Displays the first 20 words of the show's description. Valid values are 0 for hide descripion, 1 for show description. Default is 0.
  • -
-

- Example:
- [dj-widget title"Now On-Air" display_djs"1" show_avatar"1" show_link"1" default_name"RadioBot" time"12" show_sched"1" show_playlist"1"]

- -
- - Can I display upcoming shows, too? - -

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [dj-coming-up-widget] in your page/post, or you can use do_shortcode('[dj-coming-up-widget]'); in your template files. -

- The following attributes are available for the shortcode: -

    -
  • 'title' => The title you would like to appear over the on-air block
  • -
  • 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0.
  • -
  • 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0.
  • -
  • 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0.
  • -
  • 'limit' => The number of upcoming shows to display. Default is 1.
  • -
  • 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12.
  • -
  • 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1.
  • -
-

- Example:
- [dj-coming-up-widget title"Coming Up On-Air" display_djs"1" show_avatar"1" show_link"1" limit"3" time"12" schow_sched"1"]`

- -
- - Can I change how show pages are laid out/displayed? - -

Yes. Copy the radio-station/templates/single-show.php file into your theme directory, and alter as you wish. This template, and all of the other templates in this plugin, are based on the TwentyEleven theme. If you're using a different theme, you may have to rework them to reflect your theme's layout.

- -
- - What about playlist pages? - -

Same deal. Grab the radio-station/templates/single-playlist.php file, copy it to your theme directory, and go to town.

- -
- - And playlist archive pages? - -

Same deal. Grab the radio-station/templates/archive-playlist.php file, copy it to your theme directory, and go to town.

- -
- - And the program schedule, too? - -

Because of the complexity of outputting the data, you can't directly alter the template, but you can copy the radio-station/templates/program-schedule.css file into your theme directory and change the CSS rules for the page.

- -
- - What if I want to style the DJ on air sidebar widget? - -

Copy the radio-station/templates/djonair.css file to your theme directory.

- -
- - How do I get an archive page that lists ALL of the playlists instead of just the archives of individual shows? - -

First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory. -

- Then, create a Page in wordpress to hold the playlist archive. -

- Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear.

- -
- - Can show pages link to an archive of related blog posts? - -

Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory. -

- Then, create a Page in wordpress to hold the blog archive. -

- Under Page Attributes, set the template to Show Blog Archive.

- -
- - How can I list all of my shows? - -

Use the shortcode [list-shows] in your page/posts or use do_shortcode(['list-shows']); in your template files. This will output an unordered list element containing the titles of and links to all shows marked as "Active". -

- The following attributes are available for the shortcode: -

    -
  • 'genre' => Displays shows only from the specified genre(s). Separate multiple genres with a comma, e.g. genre="pop,rock".
  • -
-

- Example: - `[list-shows genre="pop"]` - [list-shows genre="pop,rock,metal"]

- -
- - I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? - -

Since I'm stongly opposed to reinventing the wheel, I recommend Justin Tadlock's excellent "Members" plugin for that purpose. You can find it on Wordpress.org, here: http://wordpress.org/extend/plugins/members/ -

- Add the following capabilities to any role you want to give access to Shows and Playlist: -

    -
  • edit_shows
  • -
  • edit_published_shows
  • -
  • edit_others_shows
  • -
  • read_shows
  • -
  • edit_playlists
  • -
  • edit_published_playlists
  • -
  • read_playlists
  • -
  • publish_playlists
  • -
  • read
  • -
  • upload_files
  • -
  • edit_posts
  • -
  • edit_published_posts
  • -
  • publish_posts
  • -
-

- If you want the new role to be able to create or approve new shows, you should also give them the following capabilities: -
    -
  • publish_shows
  • -
  • edit_others_shows
  • -
-

- -
- - How do I change the DJ's avatar in the sidebar widget? - -

The avatar is whatever image is assigned as the DJ/Show's featured image. All you have to do is set a new featured image.

- -
- - Why don't any users show up in the DJs list on the Show edit page? - -

You did remember to assign the DJ role to the users you want to be DJs, right?

- -
- - My DJs can't edit a show page. What do I do? - -

The only DJs that can edit a show are the ones listed as being ON that show in the DJs select menu. This is to prevent DJs from editing other DJs shows without permission.

- -
- - How can I export a list of songs played on a given date? - -

Under the Playlists menu in the dashboard is an Export link. Simply specify the a date range, and a text file will be generated for you.

- -
- - Can my DJ's have customized user pages in addition to Show pages? - -

Yes. These pages are the same as any other author page (edit or create the author.php template file in your theme directory). A sample can be found in the radio-station/templates/author.php file (please note that this file doesn't actually do anything unless you copy it over to your theme's directory). Like the other theme templates included with this plugin, this file is based on the TwentyEleven theme and may need to be modified in order to work with your theme.

- -
- - I don't want to use Gravatar for my DJ's image on their profile page. - -

Then you'll need to install a plugin that lets you add a different image to your DJ's user account and edit your author.php theme file accordingly. That's a little out of the scope of this plugin. I recommend Cimy User Extra Fields: http://wordpress.org/extend/plugins/cimy-user-extra-fields/

- -
- - What languages other than English is the plugin available in, and can you translate the plugin into my language? - -

Right now: -

    -
  • Albanian (sq_AL)
  • -
  • French (fr_FR)
  • -
  • German (de_DE)
  • -
  • Italian (it_IT)
  • -
  • Russion (ru_RU)
  • -
  • Serbian (sr_RS)
  • -
  • Spanish (es_ES)
  • -
- - My foreign language skills are rather lacking. I managed a Spanish translation, sheerly due to the fact that I still remember at least some of what I learned in high school Spanish class. But I've included the .pot file in the /languages directory. If you want to give it a shot, be my guest. If you send me your finished translation, I'd love to include it.

- - \ No newline at end of file diff --git a/readme.txt b/readme.txt index b545c26..4e8127a 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: kionae, tonyzeoli Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 -Tested up to: 4.3.1 +Tested up to: 4.9.10 Stable tag: trunk Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin. diff --git a/templates/admin-export.php b/templates/admin-export.php new file mode 100644 index 0000000..be0fbbe --- /dev/null +++ b/templates/admin-export.php @@ -0,0 +1,114 @@ +
+

+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + +
  + +
+
+
\ No newline at end of file diff --git a/templates/archive-playlist.php b/templates/archive-playlist.php index 4a6ae0a..b70f87c 100644 --- a/templates/archive-playlist.php +++ b/templates/archive-playlist.php @@ -10,30 +10,33 @@ + - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'playlist_show_id', - 'value' => $_GET['show_id'] - ) - ), - 'paged' => $paged - ); + 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'playlist_show_id', + 'value' => $show_id + ) + ), + 'paged' => $paged + ); $loop = new WP_Query( $args ); + // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); ?> - have_posts() ) : $loop->the_post(); ?> - - + + diff --git a/templates/author.php b/templates/author.php index e2b0c66..d42aa0b 100644 --- a/templates/author.php +++ b/templates/author.php @@ -6,23 +6,23 @@ get_header(); ?>
- - roles)): ?> - + + roles ) ): ?> + - +
ID, 50 ); ?>
- + description; ?> - +
URL: user_url; ?>
Email: user_email; ?>
@@ -30,13 +30,13 @@ Jabber: jabber; ?>
YIM: yim; ?>
- +
- - - + + + - + - + - + - + @@ -72,10 +72,10 @@
- + - + - + - + - +

- +

- + - +
diff --git a/templates/help.php b/templates/help.php new file mode 100644 index 0000000..6e39550 --- /dev/null +++ b/templates/help.php @@ -0,0 +1,249 @@ +

Radio Station Help/FAQs

+ +I've scheduled all my shows, but they're not showing up on the programming grid! +

Did you remember to check the "Active" checkbox for each show? If a show is not marked active, the plugin assumes that it's not currently in production and hides it on the grid.

+ +
+ +I'm seeing 404 Not Found errors when I click on the link for a show! +

Try re-saving your site's permalink settings. Wordpress sometimes gets confused with a new custom post type is added.

+ +
+ +How do I display a full schedule of my station's shows? +

Use the shortcode [master-schedule] on any page. This will generate a full-page schedule in one of three formats. +

+The following attributes are available for the shortcode: +

    +
  • 'list' => If set to a value of 'list', the schedule will display in list format rather than table or div format. Valid values are 'list', 'divs', 'table'. Default value is 'table'.
  • +
  • 'time' => The time format you with to use. Valid values are 12 and 24. Default is 12.
  • +
  • 'show_link' => Display the title of the show as a link to its profile page. Valid values are 0 for hide, 1 for show. Default is 1.
  • +
  • 'display_show_time' => Display start and end times of each show after the title in the grid. Valid values are 0 for hide, 1 for show. Default is 1.
  • +
  • 'show_image' => If set to a value of 1, the show's avatar will be displayed. Default value is 0.
  • +
  • 'show_djs' => If set to a value of 1, the names of the show's DJs will be displayed. Default value is 0.
  • +
  • 'divheight' => Set the height, in pixels, of the individual divs in the 'divs' layout. Default is 45.
  • +
+

+For example, if you wish to display the schedule in 24-hour time format, use [master-schedule time="24"].

+ +
+ +How do I schedule a show? + +

Simply create a new show. You will be able to assign it to any timeslot you wish on the edit page.

+ +
+ +What if I have a special event? + +

If you have a one-off event that you need to show up in the On-Air or Coming Up Next widgets, you can create a Schedule Override by clicking the Schedule Override tab in the Dashboard menu. This will allow you to set aside a block of time on a specific date, and will display the title you give it in the widgets. Please note that this will only override the widgets and their corresponding shortcodes. If you are using the weekly master schedule shortcode on a page, its output will not be altered.

+ +
+ +How do I get the last song played to show up? + +

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [now-playing] in your page/post, or use do_shortcode('[now-playing]'); in your template files. +

+The following attributes are available for the shortcode: +

    +
  • 'title' => The title you would like to appear over the now playing block
  • +
  • 'artist' => Display artist name. Valid values are 0 for hide, 1 for show. Default is 1.
  • +
  • 'song' => Display song name. Valid values are 0 for hide, 1 for show. Default is 1.
  • +
  • 'album' => Display album name. Valid values are 0 for hide, 1 for show. Default is 0.
  • +
  • 'label' => Display label name. Valid values are 0 for hide, 1 for show. Default is 0.
  • +
  • 'comments' => Display DJ comments. Valid values are 0 for hide, 1 for show. Default is 0.
  • +
+

+Example:
+[now-playing title="Current Song" artist="1" song="1" album="1" label="1" comments="0"]

+ +
+ +What about displaying the current DJ on air? + +

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [dj-widget] in your page/post, or you can use do_shortcode('[dj-widget]'); in your template files. +

+The following attributes are available for the shortcode: +

    +
  • 'title' > The title you would like to appear over the on-air block
  • +
  • 'display_djs' > Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0.
  • +
  • 'show_avatar' > Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0.
  • +
  • 'show_link' > Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0.
  • +
  • 'default_name' > The text you would like to display when no show is schedule for the current time.
  • +
  • 'time' > The time format used for displaying schedules. Valid values are 12 and 24. Default is 12.
  • +
  • 'show_sched' > Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1.
  • +
  • 'show_playlist' > Display a link to the show's current playlist. Valid values are 0 for hide link, 1 for show link. Default is 1.
  • +
  • 'show_all_sched' > Displays all schedules for a show if it airs on multiple days. Valid values are 0 for current schedule, 1 for all schedules. Default is 0.
  • +
  • 'show_desc' > Displays the first 20 words of the show's description. Valid values are 0 for hide descripion, 1 for show description. Default is 0.
  • +
+

+Example:
+[dj-widget title"Now On-Air" display_djs"1" show_avatar"1" show_link"1" default_name"RadioBot" time"12" show_sched"1" show_playlist"1"]

+ +
+ +Can I display upcoming shows, too? + +

You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode [dj-coming-up-widget] in your page/post, or you can use do_shortcode('[dj-coming-up-widget]'); in your template files. +

+The following attributes are available for the shortcode: +

    +
  • 'title' => The title you would like to appear over the on-air block
  • +
  • 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0.
  • +
  • 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0.
  • +
  • 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0.
  • +
  • 'limit' => The number of upcoming shows to display. Default is 1.
  • +
  • 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12.
  • +
  • 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1.
  • +
+

+ Example:
+ [dj-coming-up-widget title"Coming Up On-Air" display_djs"1" show_avatar"1" show_link"1" limit"3" time"12" schow_sched"1"]`

+ +
+ +Can I change how show pages are laid out/displayed? + +

Yes. Copy the radio-station/templates/single-show.php file into your theme directory, and alter as you wish. This template, and all of the other templates in this plugin, are based on the TwentyEleven theme. If you're using a different theme, you may have to rework them to reflect your theme's layout.

+ +
+ +What about playlist pages? + +

Same deal. Grab the radio-station/templates/single-playlist.php file, copy it to your theme directory, and go to town.

+ +
+ +And playlist archive pages? + +

Same deal. Grab the radio-station/templates/archive-playlist.php file, copy it to your theme directory, and go to town.

+ +
+ +And the program schedule, too? + +

Because of the complexity of outputting the data, you can't directly alter the template, but you can copy the radio-station/css/program-schedule.css file into your theme directory and change the CSS rules for the page.

+ +
+ +What if I want to style the DJ on air sidebar widget? + +

Copy the radio-station/css/djonair.css file to your theme directory.

+ +
+ +How do I get an archive page that lists ALL of the playlists instead of just the archives of individual shows? + +

First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory. +

+Then, create a Page in wordpress to hold the playlist archive. +

+Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear.

+ +
+ +Can show pages link to an archive of related blog posts? + +

Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory. +

+Then, create a Page in wordpress to hold the blog archive. +

+Under Page Attributes, set the template to Show Blog Archive.

+ +
+ +How can I list all of my shows? + +

Use the shortcode [list-shows] in your page/posts or use do_shortcode(['list-shows']); in your template files. This will output an unordered list element containing the titles of and links to all shows marked as "Active". +

+The following attributes are available for the shortcode: +

    +
  • 'genre' => Displays shows only from the specified genre(s). Separate multiple genres with a comma, e.g. genre="pop,rock".
  • +
+

+Example: +`[list-shows genre="pop"]` +[list-shows genre="pop,rock,metal"]

+ +
+ +I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? + +

Since I'm stongly opposed to reinventing the wheel, I recommend Justin Tadlock's excellent "Members" plugin for that purpose. You can find it on Wordpress.org, here: http://wordpress.org/extend/plugins/members/ +

+Add the following capabilities to any role you want to give access to Shows and Playlist: +

    +
  • edit_shows
  • +
  • edit_published_shows
  • +
  • edit_others_shows
  • +
  • read_shows
  • +
  • edit_playlists
  • +
  • edit_published_playlists
  • +
  • read_playlists
  • +
  • publish_playlists
  • +
  • read
  • +
  • upload_files
  • +
  • edit_posts
  • +
  • edit_published_posts
  • +
  • publish_posts
  • +
+

+If you want the new role to be able to create or approve new shows, you should also give them the following capabilities: +
    +
  • publish_shows
  • +
  • edit_others_shows
  • +
+

+ +
+ +How do I change the DJ's avatar in the sidebar widget? + +

The avatar is whatever image is assigned as the DJ/Show's featured image. All you have to do is set a new featured image.

+ +
+ +Why don't any users show up in the DJs list on the Show edit page? + +

You did remember to assign the DJ role to the users you want to be DJs, right?

+ +
+ +My DJs can't edit a show page. What do I do? + +

The only DJs that can edit a show are the ones listed as being ON that show in the DJs select menu. This is to prevent DJs from editing other DJs shows without permission.

+ +
+ +How can I export a list of songs played on a given date? + +

Under the Playlists menu in the dashboard is an Export link. Simply specify the a date range, and a text file will be generated for you.

+ +
+ +Can my DJ's have customized user pages in addition to Show pages? + +

Yes. These pages are the same as any other author page (edit or create the author.php template file in your theme directory). A sample can be found in the radio-station/templates/author.php file (please note that this file doesn't actually do anything unless you copy it over to your theme's directory). Like the other theme templates included with this plugin, this file is based on the TwentyEleven theme and may need to be modified in order to work with your theme.

+ +
+ +I don't want to use Gravatar for my DJ's image on their profile page. + +

Then you'll need to install a plugin that lets you add a different image to your DJ's user account and edit your author.php theme file accordingly. That's a little out of the scope of this plugin. I recommend Cimy User Extra Fields: http://wordpress.org/extend/plugins/cimy-user-extra-fields/

+ +
+ +What languages other than English is the plugin available in, and can you translate the plugin into my language? + +

Right now: +

    +
  • Albanian (sq_AL)
  • +
  • French (fr_FR)
  • +
  • German (de_DE)
  • +
  • Italian (it_IT)
  • +
  • Russion (ru_RU)
  • +
  • Serbian (sr_RS)
  • +
  • Spanish (es_ES)
  • +
+ +My foreign language skills are rather lacking. I managed a Spanish translation, sheerly due to the fact that I still remember at least some of what I learned in high school Spanish class. But I've included the .pot file in the /languages directory. If you want to give it a shot, be my guest. If you send me your finished translation, I'd love to include it.

diff --git a/templates/playlist-archive-template.php b/templates/playlist-archive-template.php index 2d505d3..34849cd 100644 --- a/templates/playlist-archive-template.php +++ b/templates/playlist-archive-template.php @@ -5,7 +5,7 @@ */ get_header(); ?> - +
@@ -13,47 +13,45 @@ 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'paged' => $paged, - 'post_status' => 'publish' + 'post_type' => 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'paged' => $paged, + 'post_status' => 'publish' ); $loop = new WP_Query( $args ); ?> have_posts()) : $loop->the_post(); ?> - +
>

- - - + - post_date)); ?>
- + - - + + diff --git a/templates/show-blog-archive-template.php b/templates/show-blog-archive-template.php index 01e5490..4023a08 100644 --- a/templates/show-blog-archive-template.php +++ b/templates/show-blog-archive-template.php @@ -5,60 +5,67 @@ */ get_header(); ?> - +
+ 'post', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $_GET['show_id'] - ) - ), - 'paged' => $paged - ); + + // filter to allow for custom show-related post types + $post_types = apply_filters( 'radio_station_show_related_post_types', array('post') ); + + // since this is a custom query, we have to do a little trickery to get pagination to work + $args = array( + 'post_type' => $post_types, + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'post_showblog_id', + 'value' => $show_id + ) + ), + 'paged' => $paged + ); $temp = $wp_query; $wp_query = null; $wp_query = new WP_Query( $args ); ?> have_posts()) : $wp_query->the_post(); ?> - +
>

- - - + - +
- +
- + - - diff --git a/templates/single-playlist.php b/templates/single-playlist.php index b6a0cbf..998baa5 100644 --- a/templates/single-playlist.php +++ b/templates/single-playlist.php @@ -11,33 +11,33 @@
>
- ID, 'playlist_show_id', true); ?> + ID, 'playlist_show_id', true ); ?>

-

+

- +
'' ) ); ?> - - + + - - ID, 'playlist', true); ?> - - + + ID, 'playlist', true ); ?> + +
- - - - - + + + + + - - - + + + > @@ -51,13 +51,13 @@
- +
- + - - + + diff --git a/templates/single-show.php b/templates/single-show.php index b1d7d4d..04053ec 100644 --- a/templates/single-show.php +++ b/templates/single-show.php @@ -13,101 +13,94 @@

- +
- +
-

:

- : + '.$user_info->display_name.''; - - if( ($count == 1 && count($djs) == 2) || (count($djs) > 2 && $count == count($djs)-1) ) { + + echo ''.$user_info->display_name.''; + + if ( ( ( $count == 1 ) && ( count($djs) == 2 ) ) + || ( ( count($djs) > 2 ) && ( $count == ( count($djs) - 1 ) ) ) ) { echo ' and '; - } - elseif($count < count($djs) && count($djs) > 2) { + } elseif ( ( $count < count($djs) ) && ( count($djs) > 2 ) ) { echo ', '; } - else { - //do nothing - } } } ?>
- +
-

:

+

:

'genres', 'title_li' => '') ); + // use this function instead if you would like the genres to link to an archive page + // wp_list_categories( array('taxonomy' => 'genres', 'title_li' => '') ); ?>
    - '.$genre->name.''; } ?>
- +

- + - + - +
- +
- +
-

+

    - '; - echo __($shift['day'], 'radio-station').' - '.$shift['start_hour'].':'.$shift['start_min'].' '.$shift['start_meridian'].' - '.$shift['end_hour'].':'.$shift['end_min'].' '.$shift['end_meridian']; + echo $weekday.' - '.$shift['start_hour'].':'.$shift['start_min'].' '.$shift['start_meridian'].' - '.$shift['end_hour'].':'.$shift['end_min'].' '.$shift['end_meridian']; echo ''; } } - - //24-hour time + + // 24-hour time /* - $shifts = get_post_meta(get_the_ID(), 'show_sched', true); - if($shifts) { - foreach($shifts as $shift) { + $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); + if ( $shifts ) { + foreach ( $shifts as $shift ) { $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); - + + $weekday = radio_station_translate_weekday( $shift['day'] ); echo '
  • '; - echo __($shift['day'], 'radio-station').' - '.$start.' - '.$end; + echo $weekday.' - '.$start.' - '.$end; echo '
  • '; } } @@ -115,16 +108,16 @@ ?>
- +
-

+

- - - + + + - + '' ) ); ?>
From 0f6096d03277cd663b2453fafe243a5a58728017 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 17 Jul 2019 17:36:00 -0400 Subject: [PATCH 004/377] Read Me Changes read me changes --- radio-station.php | 8 ++++---- readme.txt | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/radio-station.php b/radio-station.php index 53629b2..6f39bf5 100644 --- a/radio-station.php +++ b/radio-station.php @@ -5,13 +5,13 @@ */ /* Plugin Name: Radio Station -Plugin URI: http://nlb-creations.com/2013/02/25/wordpress-plugin-radio-station/ -Description: Adds playlist and on-air programming functionality to your site. -Author: Nikki Blight +Plugin URI: https://netmix.com/radio-station +Description: Adds show page, show schedule, DJ member role, playlist and on-air programming functionality to your site. +Author: Nikki Blight , Tony Zeoli Version: 2.1.1 Text Domain: radio-station Domain Path: /languages -Author URI: http://www.nlb-creations.com +Author URI: https://netmix.com/radio-station Copyright 2013 Nikki Blight (email : nblight@nlb-creations.com) diff --git a/readme.txt b/readme.txt index b545c26..f0abe55 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ === Radio Station === -Contributors: kionae, tonyzeoli -Donate link: https://netmix.co/donate +Contributors: tonyzeoli, kionae +Donate link: https://netmix.com/radio-station Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 Tested up to: 4.3.1 @@ -14,13 +14,15 @@ Radio Station is a plugin to run a radio station's website. It's functionality i We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. +If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github here: https://github.com/netmix/radio-station. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +You may also submit feature requests and bugs on Github here: https://github.com/netmix/radio-station/issues -We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate +Join our Slack at netmix.slack.com and channel: #radio-station -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin. +We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.com/radio-station. + +Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ == Installation == From fade10f3993562ef09300464764a225f1ceca990 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 17 Jul 2019 23:38:32 -0400 Subject: [PATCH 005/377] Remove Nikki Blight Removing Nikki Blight --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index 6f39bf5..a1a0387 100644 --- a/radio-station.php +++ b/radio-station.php @@ -7,7 +7,7 @@ Plugin Name: Radio Station Plugin URI: https://netmix.com/radio-station Description: Adds show page, show schedule, DJ member role, playlist and on-air programming functionality to your site. -Author: Nikki Blight , Tony Zeoli +Author: Tony Zeoli Version: 2.1.1 Text Domain: radio-station Domain Path: /languages From aaaa12378fb990d784d78731eb5b22ba9c77b6fc Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 17 Jul 2019 23:50:15 -0400 Subject: [PATCH 006/377] Revert "Remove Nikki Blight" This reverts commit fade10f3993562ef09300464764a225f1ceca990. --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index a1a0387..6f39bf5 100644 --- a/radio-station.php +++ b/radio-station.php @@ -7,7 +7,7 @@ Plugin Name: Radio Station Plugin URI: https://netmix.com/radio-station Description: Adds show page, show schedule, DJ member role, playlist and on-air programming functionality to your site. -Author: Tony Zeoli +Author: Nikki Blight , Tony Zeoli Version: 2.1.1 Text Domain: radio-station Domain Path: /languages From bdfda574d92f765e64d03bf0ac19102d3cbfc3e8 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 17 Jul 2019 23:50:22 -0400 Subject: [PATCH 007/377] Revert "Read Me Changes" This reverts commit 0f6096d03277cd663b2453fafe243a5a58728017. --- radio-station.php | 8 ++++---- readme.txt | 14 ++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/radio-station.php b/radio-station.php index 6f39bf5..53629b2 100644 --- a/radio-station.php +++ b/radio-station.php @@ -5,13 +5,13 @@ */ /* Plugin Name: Radio Station -Plugin URI: https://netmix.com/radio-station -Description: Adds show page, show schedule, DJ member role, playlist and on-air programming functionality to your site. -Author: Nikki Blight , Tony Zeoli +Plugin URI: http://nlb-creations.com/2013/02/25/wordpress-plugin-radio-station/ +Description: Adds playlist and on-air programming functionality to your site. +Author: Nikki Blight Version: 2.1.1 Text Domain: radio-station Domain Path: /languages -Author URI: https://netmix.com/radio-station +Author URI: http://www.nlb-creations.com Copyright 2013 Nikki Blight (email : nblight@nlb-creations.com) diff --git a/readme.txt b/readme.txt index f0abe55..b545c26 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ === Radio Station === -Contributors: tonyzeoli, kionae -Donate link: https://netmix.com/radio-station +Contributors: kionae, tonyzeoli +Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 Tested up to: 4.3.1 @@ -14,15 +14,13 @@ Radio Station is a plugin to run a radio station's website. It's functionality i We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github here: https://github.com/netmix/radio-station. +If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. -You may also submit feature requests and bugs on Github here: https://github.com/netmix/radio-station/issues +You may also submit feature requests here: https://github.com/netmix/radio-station/issues -Join our Slack at netmix.slack.com and channel: #radio-station +We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate -We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.com/radio-station. - -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin. == Installation == From 640b19c2571e02240cf5313692f92a5524827292 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 17 Jul 2019 23:58:15 -0400 Subject: [PATCH 008/377] Plugin Header and Read Me Changes, Update version to 2.2.0 Plugin Header and Read Me text changes. --- radio-station.php | 14 +++++++------- readme.txt | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/radio-station.php b/radio-station.php index 6b2df57..7c087a3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,19 +1,19 @@ -Version: 2.1.1 +Plugin URI: https://netmix.com/radio-station +Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. +Author: Tony Zeoli +Version: 2.2.0 Text Domain: radio-station Domain Path: /languages -Author URI: http://www.nlb-creations.com +Author URI: https://netmix.com/radio-station -Copyright 2013 Nikki Blight (email : nblight@nlb-creations.com) +Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/readme.txt b/readme.txt index 4e8127a..926c8e6 100644 --- a/readme.txt +++ b/readme.txt @@ -1,18 +1,18 @@ === Radio Station === -Contributors: kionae, tonyzeoli +Contributors: tonyzeoli Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 Tested up to: 4.9.10 Stable tag: trunk -Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin. +Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. +Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli. +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. From 9c2043bdd501969d4e73bbd451482f8a1e9cbe64 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 18 Jul 2019 00:07:13 -0400 Subject: [PATCH 009/377] Changelog and Upgrade sections to 2.2.0 Add changes to Changelog and change version number to 2.2.0. Add versoin upgrade description to Upgrade Notification to 2.2.0. --- readme.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/readme.txt b/readme.txt index 926c8e6..8abc22e 100644 --- a/readme.txt +++ b/readme.txt @@ -238,6 +238,22 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.0 = +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) +* fixed the protocol in jQuery UI style Google URL +* reprefixed all functions for consistency (radio_station_) +* updated all the widget constructor methods +* merged the menu items into a single main menu +* updated the capability checks for the menu items +* moved the help and export pages to /templates/ +* moved all the css files to /css/ +* enqeued the djonair css from within the widget +* use plugins_url for all resource URLs +* added $wpdb->prepare to sanitize a query +* added some sanization for metabox save values +* added a week and month translation helper +* added a radio station antenna icon + = 2.1.3 = * Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). @@ -450,6 +466,9 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.0 = +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) + = 2.1.3 = * Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). From 10199a58466ff19b47ea085dbf2b10098a906d59 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 18 Jul 2019 00:27:34 -0400 Subject: [PATCH 010/377] Update test version to 5.2.2 Update to 5.2.2 --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 8abc22e..e5045a2 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 -Tested up to: 4.9.10 +Tested up to: 5.2.2 Stable tag: trunk Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. From f355caa8097c644cffddc5c77e968292d8b06312 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 18 Jul 2019 16:46:30 -0400 Subject: [PATCH 011/377] Update readme.txt Update Tested up to Version to 4.1.10 Added links, updated plugin descrioption text. --- readme.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.txt b/readme.txt index e5045a2..635b647 100644 --- a/readme.txt +++ b/readme.txt @@ -3,24 +3,24 @@ Contributors: tonyzeoli Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 -Tested up to: 5.2.2 +Tested up to: 4.9.10 Stable tag: trunk Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. +Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. +If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +You may also submit feature requests here: https://github.com/netmix/radio-station/issues -We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate. -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin. +Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ == Installation == From 0487a879df81064ea4044b7c6275b05512a83aa9 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 18 Jul 2019 23:24:52 -0400 Subject: [PATCH 012/377] 2.2.1 change I didn't upload all files and folders originally, so had to change versions to 2.2.1 --- .DS_Store | Bin 0 -> 6148 bytes radio-station.php | 4 ++-- readme.txt | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9ab15797523356a8e1a45eb290f58d75d90b9dbd GIT binary patch literal 6148 zcmeHKJxc>Y5Pf5UB(aH=mRH&su(mluEUf(l=8Gs?a=|2Ed5wQX@b~!x^v&!N?jz|$ zP-bBF?at1;n|%kjw*X|go816?039~PlPtT9N&%kPYMrxy-n#;>fGe<9K)(+O zn_?QV2xy-Uc6J3Iw&^zG*na+ri8_dB#3CR)GzqE1kSg(tA@S}YA)V`Kj4uL)bcj8f zJn_jAZzvM49+Fr%Bn{}jE8q%L6=;QNU(f%$aK$D+2CQIIyFbv3e9=vYFAJ=zy3;ECSL)^B)0|!8=#rR~7gM2>E?Y literal 0 HcmV?d00001 diff --git a/radio-station.php b/radio-station.php index 7c087a3..458cf57 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,14 +1,14 @@ -Version: 2.2.0 +Version: 2.2.1 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station diff --git a/readme.txt b/readme.txt index 635b647..89b2633 100644 --- a/readme.txt +++ b/readme.txt @@ -238,6 +238,10 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.1 = + +* Re-commit all missing files via SVN + = 2.2.0 = * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) * fixed the protocol in jQuery UI style Google URL From 3207216acf346ed1db77b7b392c9f691094694f0 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sat, 20 Jul 2019 20:16:39 -0400 Subject: [PATCH 013/377] Update donate link Change donate link --- .DS_Store | Bin 6148 -> 6148 bytes readme.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index 9ab15797523356a8e1a45eb290f58d75d90b9dbd..69c641161dc1004267ad80b0aa89353275eee9a5 100644 GIT binary patch delta 541 zcmZoMXfc=|#>B!ku~2NHo+2aP#(>?7iw`g}G4f63VLDWwR9;+=l#`#tz`$@asURn_ zxWvHVIwKP^3o9Et2MF+T#0F>Nmj{<5mXsDdB^E`4cmate8A(ufNPd0}oSm2ymYG@} zFCgNapI4HYnU`7w)|{D=3RDsko|%`DU+$D&nwL@xHYYd?BEty+@d6Un)y8HPItoSx zX0wRJOrt_1={MhMNo52az$tjW8W6ejmF z3G$Q$7v<&T=cR*GPrlA{cXAW6@No5WJ+l~!enB)qu~2NHo+2aH#(>?7j9im>SPpTf6es5-<>%)xOn$(6ce5CWAj`xC ir_Jmf{2V~_n*}+(Gf(ChapYhC0!9V~mdz0&YnTC)!4$Iq diff --git a/readme.txt b/readme.txt index 89b2633..e7add86 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ === Radio Station === Contributors: tonyzeoli -Donate link: https://netmix.co/donate +Donate link: https://www.patreon.com/radiostation Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 Tested up to: 4.9.10 From cdfd00d49a494420b46dad3dda2b426a555bc1f1 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sat, 20 Jul 2019 20:34:27 -0400 Subject: [PATCH 014/377] Add Upgrade Notice in read me Add Upgrade Notice in readme file --- .DS_Store | Bin 6148 -> 10244 bytes readme.txt | 4 ++++ 2 files changed, 4 insertions(+) diff --git a/.DS_Store b/.DS_Store index 69c641161dc1004267ad80b0aa89353275eee9a5..58f68e87980534b8cf74a3dac01a60eca7a3802a 100644 GIT binary patch literal 10244 zcmeHMTWl0n82X=!1(lx`@EMVHzO7Nk_R7lMr-4K2NJE3-ReJ2E@7?#%82 zOSLZ=QCsGtZXDMHatG0@K)P=u2`>if?NDZ&Byn`s>X&FE(+=%*eS=I($| zA%i+VAV8oL0hYU0LL6Lhhpk2VdoNjD`->MI=6mUG+=@Al#r9z}n2-PsX22*3zg`r+ z$hUA1i3YK01{S12FEbFEM_a)>O8jG4H|gIyd=^Vurt5Wde2o>ARm-ZEORTJvj*red z*-=mP((ZtkI?8$@nl~Hs+heqzg|-$OH;wGH+A!$Yo@UxciU*szK`zH8Oj~!d15Vo3 zT|UmJFvzS-tD#)3yJu%4+||)D7YXNfc6UX>JzWpa&B@Z**3Q1+lZi8DbLZxszQQ2| z^iMOW75#w7brA>!N*9DY^Uhc;1$I@TNERcmq8p$^5w905l!*8 zJ4X}{RbSWsoWSTxHCbn;8`Qq#}`r;Z}a1 z$jXY)-Uemgq#1YYQS+=Zt=77I<`A*q+F6M(GI>`kZ;s9u5WH> z+t#(CH+pmBs#;a4R~jZVrfbG5<8ap0z1bOu2~xjO{Qb!Q)$yXqqazr7;9r_ldQfg9n)zh2OOP6>f93A7}_jJN11*wWl$~D zN^9soS=FR5m)0eQxL=9Lsw`$e2 zuosbL(ft6$yFxT&Qr z(%I7+UHF1UA8iq-3Yo;oBiO-NlV^osD=~H5`VC6cVd9f$;ClIFF+tbAV45MAVA=rK%i2rGR*G(_b&ba|9b+9V37d=_bLKdH8eUj zK&#JZaK%8dyY?_WC+T5@`OOQ-L#X1{@g(_mJayQ0JkMB3=co5PE=qb9KQAP;sQl~y S3<&c7ApgJfb=3bl|Nk3`q$e=| delta 150 zcmZn(XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jG-bU^g=(-((&E3r?Uo0~ gBwRs8Z!G-IJegm`6J!ws6U0)GI~X>{^UPre0QEQ>{{R30 diff --git a/readme.txt b/readme.txt index e7add86..2e047f6 100644 --- a/readme.txt +++ b/readme.txt @@ -470,6 +470,10 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.1 = + +* Re-commit all missing files via SVN + = 2.2.0 = * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) From bfec5f2418bedd52adc5050aa25407ef12868b7e Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 24 Jul 2019 19:32:04 +1000 Subject: [PATCH 015/377] 2.2.2 updates --- images/patreon-button.jpg | Bin 0 -> 4786 bytes images/playlist-menu-icon.png | Bin 1273 -> 505 bytes images/radio-station.png | Bin 0 -> 18619 bytes images/show-menu-icon.png | Bin 1191 -> 429 bytes includes/post_types.php | 118 +++++-- includes/widget_djonair.php | 3 +- languages/radio-station-ca.mo | Bin 0 -> 8128 bytes languages/radio-station-ca.po | 596 ++++++++++++++++++++++++++++++++++ radio-station.php | 175 +++++++++- readme.txt | 54 ++- templates/author.php | 5 +- 11 files changed, 897 insertions(+), 54 deletions(-) create mode 100644 images/patreon-button.jpg create mode 100644 images/radio-station.png create mode 100644 languages/radio-station-ca.mo create mode 100644 languages/radio-station-ca.po diff --git a/images/patreon-button.jpg b/images/patreon-button.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df3953e817d81f4a5c5f674d2e9575712a237138 GIT binary patch literal 4786 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 diff --git a/images/radio-station.png b/images/radio-station.png new file mode 100644 index 0000000000000000000000000000000000000000..b6cc021667a804023ee212cdb8b768ba1d49ddcc GIT binary patch literal 18619 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 N@O1TaS?83{1OR^QsE_~v literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1|*Ak?@s|zk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%uvD1M9IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr4|esh*)icxGNo zet9uiy|1s8XI^nhVqS8pr;Du;&;-5A%oHmN7c*B!Geb8=M`vS0Lsw%bH&+)I6IUlU zH)kVrAb{z0$xklLP0cHT=}m#_H8Q}d7nBro3xGDeq!wkCrKY$Q<>xAZy=;|<+bu3Q z&4cPq!RZ!5SDbqFfsWA!MJ!T8!-RmT2gHOYTObFX@Kf`Esl5o8thY{ze#gMTDDCOu z7*cU7>CgZF_RO}7+{!cu9G`rX|2 array( @@ -47,21 +50,21 @@ function radio_station_create_post_types() { 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'description' => __('Post type for Playlist descriptions', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => $icon, + // 'menu_position' => 5, + // 'menu_icon' => $icon, 'public' => true, 'hierarchical' => false, 'supports' => array( 'title', 'editor', 'comments' ), 'can_export' => true, 'has_archive' => 'playlists-archive', - 'rewrite' => array('slug' => 'playlists'), + 'rewrite' => array( 'slug' => 'playlists' ), 'capability_type' => 'playlist', 'map_meta_cap' => true ) ); // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'show', array( 'labels' => array( @@ -76,8 +79,8 @@ function radio_station_create_post_types() { 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'description' => __('Post type for Show descriptions', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => $icon, + // 'menu_position' => 5, + // 'menu_icon' => $icon, 'public' => true, 'taxonomies' => array( 'genres' ), 'hierarchical' => false, @@ -89,7 +92,7 @@ function radio_station_create_post_types() { ); // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'override', array( 'labels' => array( @@ -104,8 +107,8 @@ function radio_station_create_post_types() { 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'description' => __('Post type for Schedule Override', 'radio-station'), - 'menu_position' => 5, - 'menu_icon' => $icon, + // 'menu_position' => 5, + // 'menu_icon' => $icon, 'public' => true, 'hierarchical' => false, 'supports' => array( 'title', 'thumbnail' ), @@ -118,23 +121,49 @@ function radio_station_create_post_types() { } add_action( 'init', 'radio_station_create_post_types' ); +// --------------------------------------- +// Set Post Type Editing to Classic Editor +// --------------------------------------- +// 2.2.2: so metabox displays can have wide widths +function radio_station_post_type_editor( $can_edit, $post_type ) { + $post_types = array( 'show', 'playlist', 'override' ); + if ( in_array( $post_type, $post_types ) ) {return false;} + return $can_edit; +} +add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); +add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); + // -------------------------- // Add Show Thumbnail Support // -------------------------- // add featured image support to "show" post type // (this is probably no longer necessary as declared in register_post_type for show) function radio_station_add_featured_image_support() { - $supportedTypes = get_theme_support( 'post-thumbnails' ); + $supported_types = get_theme_support( 'post-thumbnails' ); - if ( $supportedTypes === false ) { + if ( $supported_types === false ) { add_theme_support( 'post-thumbnails', array( 'show' ) ); - } elseif ( is_array( $supportedTypes ) ) { - $supportedTypes[0][] = 'show'; - add_theme_support( 'post-thumbnails', $supportedTypes[0] ); + } elseif ( is_array( $supported_types ) ) { + $supported_types[0][] = 'show'; + add_theme_support( 'post-thumbnails', $supported_types[0] ); } } add_action( 'init', 'radio_station_add_featured_image_support' ); +// ---------------------------- +// Metaboxes Above Content Area +// ---------------------------- +function radio_station_top_meta_boxes() { + global $post; + do_meta_boxes( get_current_screen(), 'top', $post ); +} +add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); + + +// ------------------ +// === Taxonomies === +// ------------------ + // ----------------------- // Register Genre Taxonomy // ----------------------- @@ -175,7 +204,18 @@ function radio_station_myplaylist_create_show_taxonomy() { ); } -add_action('init', 'radio_station_myplaylist_create_show_taxonomy'); +add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); + +// ---------------------------- +// Shift Genre Metabox on Shows +// ---------------------------- +function radio_station_genre_meta_box_order() { + global $wp_meta_boxes; + $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; + unset($wp_meta_boxes['show']['side']['core']['genresdiv']); + $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; +} +add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); // ----------------- @@ -189,11 +229,14 @@ function radio_station_myplaylist_create_show_taxonomy() { // Add custom repeating meta field for the playlist edit form... Stores multiple associated values as a serialized string // Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 function radio_station_myplaylist_add_custom_box() { + // 2.2.2: change context to show at top of edit screen add_meta_box( 'dynamic_sectionid', __( 'Playlist Entries', 'radio-station' ), 'radio_station_myplaylist_inner_custom_box', - 'playlist' + 'playlist', + 'top', // shift to top + 'high' ); } add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); @@ -368,7 +411,7 @@ function radio_station_myplaylist_save_postdata( $post_id ) { // Related Show Metabox // -------------------- -// --- Add custom meta box for show assigment on blog posts --- +// --- Add custom meta box for show assignment on blog posts --- function radio_station_add_showblog_box() { // make sure a show exists before adding metabox @@ -432,7 +475,7 @@ function radio_station_inner_showblog_custom_box() { display_name; + if ( $dj->display_name != $dj->user_login ) {$display_name .= '('.$dj->user_login.')';} + + // 2.2.2: fix to make DJ multi-select input full metabox width ?>
- ID, $current ) ) {$selected = ' selected="selected"';} else {$selected = '';} - echo ''; + echo ''; } ?> @@ -687,13 +743,16 @@ function radio_station_myplaylist_inner_user_custom_box() { hiAFAuW4!(@{cfk>yfNKA1kgDc0@JHbnp!#__ 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 aUfnH6!x|Qx>3M}5p9h5*1=H+&nEoG>!CDaj literal 0 HcmV?d00001 diff --git a/languages/radio-station-ca.po b/languages/radio-station-ca.po new file mode 100644 index 0000000..5e553cc --- /dev/null +++ b/languages/radio-station-ca.po @@ -0,0 +1,596 @@ +msgid "" +msgstr "" +"Project-Id-Version: radio-station\n" +"POT-Creation-Date: 2013-11-08 08:28-0600\n" +"PO-Revision-Date: 2015-07-13 13:51+0100\n" +"Last-Translator: Nikki Blight \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.7.6\n" +"X-Poedit-KeywordsList: __;_e;esc_attr_e\n" +"X-Poedit-Basepath: ..\n" +"Language: ca_ES\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SearchPath-0: c:\\xampp\\htdocs\\wp352test\\wp-content\\plugins" +"\\radio-station\n" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:244 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:330 +msgid "Export Playlists" +msgstr "Exportar Llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:244 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:451 +msgid "Export" +msgstr "Exportar" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:323 +msgid "Right-click and download this file to save your export" +msgstr "" +"Clica amb el botó dret i descarrega aquest arxiu per desar l'exportació" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:340 +msgid "Start Date" +msgstr "Data d'Inici" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:394 +msgid "End Date" +msgstr "Data d'Acabament" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/radio-station.php:466 +msgid "Related to Show" +msgstr "Relacionat amb el Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:90 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:664 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:22 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:359 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:546 +msgid "View Playlist" +msgstr "Veure Llista de Reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:480 +msgid "None Upcoming" +msgstr "Res Properament" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:495 +msgid "The current on-air DJ." +msgstr "DJ actual en antena" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:496 +msgid "Radio Station: Show/DJ On-Air" +msgstr "Radio Station: Mostra Programa/DJ en antena" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:511 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:738 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:436 +msgid "Title" +msgstr "Títol" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:519 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:746 +msgid "Show Avatars" +msgstr "Mostrar avatars" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:526 +msgid "Link to the Show/DJ's profile" +msgstr "Enllaç al Programa o perfil de DJ" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:533 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:767 +msgid "Display schedule info for this show" +msgstr "Mostrar información de la programació d'aquest programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:540 +msgid "Display link to show's playlist" +msgstr "Mostrar enllaç a la llista de reproducció del programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:545 +msgid "Default DJ Name" +msgstr "Nom DJ per defecte" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:548 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:761 +msgid "" +"If no Show/DJ is scheduled for the current hour, display this name/text." +msgstr "Si no hi ha programa/DJ per l'hora actual, mostra aquest nom/text." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:552 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:779 +msgid "Time Format" +msgstr "Format de l'hora" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:554 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:781 +msgid "12-hour" +msgstr "12 hores" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:555 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:782 +msgid "24-hour" +msgstr "24 hores" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:558 +msgid "Choose time format for displayed schedules" +msgstr "Escull el format horari per els horaris mostrats" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:722 +msgid "The upcoming DJs/Shows." +msgstr "Els propers Programes/DJs" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:723 +msgid "Radio Station: Upcoming DJ On-Air" +msgstr "Radio Station: Proper DJ en antena" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:753 +msgid "Link to Show/DJ's user profile" +msgstr "Enllaç al perfil d'usuari de DJ/Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:758 +msgid "No Additional Schedules" +msgstr "No hi ha horaris adicionals" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:772 +msgid "Limit" +msgstr "Límit" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:775 +msgid "Number of upcoming DJs/Shows to display." +msgstr "Número de propers DJs/Programes a mostrar." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/dj-on-air.php:785 +msgid "Choose time format for displayed schedules." +msgstr "Escull el format pels horaris mostrats." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:21 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:22 +msgid "Schedule Override" +msgstr "Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:23 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:24 +msgid "Add Schedule Override" +msgstr "Afegir Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:25 +msgid "Edit Schedule Override" +msgstr "Editar Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:26 +msgid "New Schedule Override" +msgstr "Nova Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:27 +msgid "View Schedule Override" +msgstr "Veure Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:30 +msgid "Post type for Schedule Override" +msgstr "Tipus de Post per Anul.lació horària" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:49 +msgid "Override Schedule" +msgstr "Anul.lar Horari" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:79 +msgid "Date" +msgstr "Data" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:82 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:756 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:835 +msgid "Start Time" +msgstr "Data d'Inici" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:108 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:782 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:861 +msgid "End Time" +msgstr "Data d'Acabament" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:432 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:595 +msgid "encore airing" +msgstr "presentació de la repetició" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:437 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:600 +msgid "Audio File" +msgstr "Arxiu d'àudio" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Sun" +msgstr "Dg." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Mon" +msgstr "Dl." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Tue" +msgstr "Dt." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Wed" +msgstr "Dc." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Thu" +msgstr "Dj." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Fri" +msgstr "Dv." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:454 +msgid "Sat" +msgstr "Ds." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/master_schedule.php:619 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:572 +msgid "Genres" +msgstr "Gèneres" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:16 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:136 +msgid "Playlists" +msgstr "Llistes de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:17 +msgid "Playlist" +msgstr "Llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:18 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:19 +msgid "Add Playlist" +msgstr "Afegir Llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:20 +msgid "Edit Playlist" +msgstr "Editar Llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:21 +msgid "New Playlist" +msgstr "Nova Llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:25 +msgid "Post type for Playlist descriptions" +msgstr "Tipus de Post per descripcions de llistes de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:42 +msgid "Shows" +msgstr "Programes" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:43 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:191 +msgid "Show" +msgstr "Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:44 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:45 +msgid "Add Show" +msgstr "Afegir nou Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:46 +msgid "Edit Show" +msgstr "Editar Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:47 +msgid "New Show" +msgstr "Nou Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:48 +msgid "View Show" +msgstr "Veure Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:51 +msgid "Post type for Show descriptions" +msgstr "Tipus de Post per descripcions de Programa" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:71 +msgid "Playlist Entries" +msgstr "Entrades de llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:43 +msgid "Artist" +msgstr "Artista" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:44 +msgid "Song" +msgstr "Cançó" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:45 +msgid "Album" +msgstr "Album" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:46 +msgid "Record Label" +msgstr "Segell discogràfic" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:47 +msgid "DJ Comments" +msgstr "Comentaris de DJ" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +msgid "New" +msgstr "Nou" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +msgid "Status" +msgstr "Estatus" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:127 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:144 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:807 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:888 +msgid "Remove" +msgstr "Eliminar" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:119 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:144 +msgid "Queued" +msgstr "en cua" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:123 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:144 +msgid "Played" +msgstr "Reproduït" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:136 +msgid "Add Entry" +msgstr "Afegir entrada" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:163 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:164 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:99 +msgid "Schedule" +msgstr "Horari" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:166 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:167 +msgid "Publish" +msgstr "Publicar" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:170 +msgid "Submit for Review" +msgstr "Presentar per la seva revisió" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:171 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:176 +msgid "Update Playlist" +msgstr "Actualitzar Llista de Reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:175 +msgid "Update" +msgstr "Actualitzar" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:420 +msgid "Display the current song." +msgstr "Mostrar cançó actual." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:421 +msgid "Radio Station: Now Playing" +msgstr "Radio Station: Ara Sona" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:444 +msgid "Show Artist Name" +msgstr "Mostrar el nom de l'artista" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:451 +msgid "Show Song Title" +msgstr "Mostrar títol de la cançó" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:458 +msgid "Show Album Name" +msgstr "Mostrar nom de l'àlbum" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:465 +msgid "Show Record Label Name" +msgstr "Mostrar segell discogràfic" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:472 +msgid "Show DJ Comments" +msgstr "Mostrar comentaris de DJs" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:573 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:60 +msgid "Genre" +msgstr "Gènere" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:594 +msgid "Information" +msgstr "Informació" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:612 +msgid "Active" +msgstr "Actiu" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:614 +msgid "" +"Check this box if show is currently active (Show will not appear on " +"programming schedule if unchecked)" +msgstr "" +"Marca aquesta casella si el programa està actiu (El Programa no apareixerà " +"a la graella si està desmarcat)" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:616 +msgid "Current Audio File" +msgstr "Arxiu d'àudio actual" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:619 +msgid "DJ Email" +msgstr "Direcció de correu electrònic" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:622 +msgid "Website Link" +msgstr "Enllaç al web" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:633 +msgid "DJs" +msgstr "DJs" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:719 +msgid "Schedules" +msgstr "Horaris" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:744 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:824 +msgid "Day" +msgstr "Dia" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:747 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:827 +msgid "Monday" +msgstr "Dilluns" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:748 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:828 +msgid "Tuesday" +msgstr "Dimarts" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:749 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:829 +msgid "Wednesday" +msgstr "Dimecres" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:750 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:830 +msgid "Thursday" +msgstr "Dijous" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:751 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:831 +msgid "Friday" +msgstr "Divendres" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:752 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:832 +msgid "Saturday" +msgstr "Dissabte" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:753 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:833 +msgid "Sunday" +msgstr "Diumenge" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:806 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:886 +msgid "Encore Presentation" +msgstr "Presentació de la repetició" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:817 +msgid "Add Shift" +msgstr "Afegir nova franja" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:980 +msgid "More Playlists" +msgstr "Més llistes de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/includes/playlist.php:1041 +msgid "More Blog Posts" +msgstr "Més entrades del blog" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:15 +msgid "Playlist Archive for" +msgstr "Arxiu de Llistes de Reproducció per" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:50 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:13 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:13 +msgid "Post navigation" +msgstr "Navegador d'articles" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:51 +msgid "Older posts" +msgstr "Entrades més antigues" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:52 +msgid "Newer posts" +msgstr "Entrades recents" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:61 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/author.php:93 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/playlist-archive-template.php:63 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/show-blog-archive-template.php:71 +msgid "Nothing Found" +msgstr "No s'han trobat resultats" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/archive-playlist.php:65 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/author.php:97 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/playlist-archive-template.php:67 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/show-blog-archive-template.php:75 +msgid "" +"Apologies, but no results were found for the requested archive. Perhaps " +"searching will help find a related post." +msgstr "" +"Disculpes, però no s'han trobat resultats de l'arxiu sol.licitat. Potser la " +"cerca t'ajudarà a trobar l'entrada relacionada." + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/author.php:51 +#, php-format +msgid "Author Archives: %s" +msgstr "Arxiu de l'Autor: %s" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/author.php:70 +#, php-format +msgid "About %s" +msgstr "Sobre %s" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "Arxiu de Llista de Reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/show-blog-archive-template.php:16 +msgid "Blog Archive" +msgstr "Arxiu de Blog" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/show-blog-archive-template.php:46 +msgid "Posted by" +msgstr "Publicat per" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:14 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:14 +msgid "Previous" +msgstr "Anterior" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:15 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:15 +msgid "Next" +msgstr "Següent" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:32 +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:144 +msgid "Pages:" +msgstr "Pàgines:" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-playlist.php:65 +msgid "No entries for this playlist" +msgstr "No hi ha entrades per aquesta llista de reproducció" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:32 +msgid "Hosted by" +msgstr "Allotjada per" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:84 +msgid "Email the DJ" +msgstr "Contactar amb el DJ" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:88 +msgid "Show Website" +msgstr "Mostrar Pàgina Web" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:95 +msgid "Most recent broadcast" +msgstr "Emissió més recent" + +#: c:\xampp\htdocs\wp352test\wp-content\plugins\radio-station/templates/single-show.php:140 +msgid "Blog Posts" +msgstr "Entrades del Blog" diff --git a/radio-station.php b/radio-station.php index 6b2df57..ad10efe 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,19 +1,20 @@ -Version: 2.1.1 +Plugin URI: https://netmix.com/radio-station +Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. +Author: Tony Zeoli +Version: 2.2.2 Text Domain: radio-station Domain Path: /languages -Author URI: http://www.nlb-creations.com +Author URI: https://netmix.com/radio-station +GitHub Plugin URI: netmix/radio-station -Copyright 2013 Nikki Blight (email : nblight@nlb-creations.com) +Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as @@ -302,28 +303,92 @@ function radio_station_add_admin_menus() { } elseif ( $item[2] == 'shows' ) {$submenu[$i][$j][2] = 'edit.php?post_type=show';} elseif ( $item[2] == 'playlists' ) {$submenu[$i][$j][2] = 'edit.php?post_type=playlist';} elseif ( $item[2] == 'add-playlist' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=playlist';} - elseif ( $item[2] == 'genres' ) {$submenu[$i][$j][2] = 'edit-tags.php?taxonomy=genres&post_type=show';} + elseif ( $item[2] == 'genres' ) {$submenu[$i][$j][2] = 'edit-tags.php?taxonomy=genres';} elseif ( $item[2] == 'schedule-overrides' ) {$submenu[$i][$j][2] = 'edit.php?post_type=override';} elseif ( $item[2] == 'add-override' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=override';} } } } - // echo ""; } add_action( 'admin_menu', 'radio_station_add_admin_menus' ); +// --- expand main menu fix for plugin submenu items --- +// 2.2.2: added fix for genre taxonomy page and post type editing +function radio_station_fix_genre_parent($parent_file = '') { + global $pagenow, $post; + $post_types = array( 'show', 'playlist', 'override' ); + if ( ( $pagenow == 'edit-tags.php') && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { + $parent_file = 'radio-station'; + } elseif ( ( $pagenow == 'post.php' ) && ( in_array( $post->post_type, $post_types ) ) ) { + $parent_file = 'radio-station'; + } + return $parent_file; +} +add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); + +// --- genre taxonomy submenu item fix --- +// 2.2.2: so genre submenu item link is set to current (bold) +function radio_station_genre_submenu_fix() { + global $pagenow; + if ( ( $pagenow == 'edit-tags.php' ) && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { + echo ""; + } +} +add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); + // --- remove the Add Show link for DJs from the wp admin bar --- -function station_radio_modify_admin_bar_menu($wp_admin_bar) { - // $user = wp_get_current_user(); - // if ( in_array('dj', $user->roles ) ) { - if ( !current_user_can( 'publish_shows' ) ) { - $wp_admin_bar->remove_node( 'new-show' ); +// 2.2.2: re-add new post type items to admin bar +// (as no longer automatically added by register_post_type) +function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { + + // --- new show --- + if ( current_user_can( 'publish_shows' ) ) { + + $args = array( + 'id' => 'new-show', + 'title' => __( 'Show', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url('post-new.php?post_type=show') + ); + $wp_admin_bar->add_node($args); } + + // --- new playlist --- + if ( current_user_can( 'publish_playlists' ) ) { + $args = array( + 'id' => 'new-playlist', + 'title' => __( 'Playlist', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url('post-new.php?post_type=playlist') + ); + $wp_admin_bar->add_node($args); + } + + // --- new schedule override --- + if ( current_user_can( 'publish_shows' ) ) { + $args = array( + 'id' => 'new-override', + 'title' => __( 'Override', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url('post-new.php?post_type=override') + ); + $wp_admin_bar->add_node($args); + } + } add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); // --- output help page --- function radio_station_plugin_help() { + + // 2.2.2: include patreon button link + echo radio_station_patreon_blurb(false); + // include help template include( dirname(__FILE__).'/templates/help.php' ); } @@ -416,3 +481,85 @@ function radio_station_admin_export() { } + +// -------------------- +// === Admin Notice === +// -------------------- + +// --- plugin announcement notice --- +// 2.2.2: added plugin announcement notice +function radio_station_announcement_notice() { + + // --- bug out if already dismissed --- + if ( get_option( 'radio_station_announcement_dismissed' ) ) {return;} + + // --- bug out on certain plugin pages --- + $pages = array( 'radio-station', 'radio-station-help' ); + if ( isset( $_REQUEST['page'] ) && ( in_array( $_REQUEST['page'], $pages ) ) ) {return;} + + // --- display plugin announcement --- + echo '
'; + echo radio_station_patreon_blurb(); + echo ''; + echo '
'; +} +add_action( 'admin_notices', 'radio_station_announcement_notice' ); + +// --- dismiss plugin announcement notice --- +// 2.2.2: AJAX for announcement notice dismissal +function radio_station_announcement_dismiss() { + if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { + update_option( 'radio_station_announcement_dismissed', true ); + echo ""; + exit; + } +} +add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); + +// --- Patreon supporter blurb --- +// 2.2.2: added simple patreon supporter blurb +function radio_station_patreon_blurb( $dismissable = true ) { + + $blurb = '
    '; + $blurb .= '
  • '; + $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); + $blurb .= ''; + $blurb .= '
  • '; + $blurb .= '
  • '; + $blurb .= ''.__( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ).'
    '; + $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ).',
    '; + $blurb .= __( 'since June 2019', 'radio-station' ).', '; + $blurb .= ''.__( 'Radio Station', 'radio-station' ).' '; + $blurb .= __(' plugin development has been actively taken over by', 'radio-station' ); + $blurb .= ' Netmix.
    '; + $blurb .= __( 'We invite you to', 'radio-station'); + $blurb .= ' '; + $blurb .= __('Become a Radio Station Patreon Supporter', 'radio-station'); + $blurb .= ' '.__('to make it better for everyone', 'radio-station').'!'; + $blurb .= '
  • '; + $blurb .= '
  • '; + $blurb .= radio_station_patreon_button(); + $blurb .= '
  • '; + if ($dismissable) { + $blurb .= '
  • '; + $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); + $blurb .= ''; + $blurb .= ''; + $blurb .= '
  • '; + } + $blurb .= '
'; + return $blurb; +} + +// --- Patreon supporter button --- +// 2.2.2: added simple patreon supporter image button +function radio_station_patreon_button() { + $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); + $button = ''; + $button .= ''; + $button .= ''; + $button .= ''; + return $button; +} + diff --git a/readme.txt b/readme.txt index 4e8127a..dbd780a 100644 --- a/readme.txt +++ b/readme.txt @@ -1,26 +1,26 @@ === Radio Station === -Contributors: kionae, tonyzeoli +Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 -Tested up to: 4.9.10 +Tested up to: 5.2.2 Stable tag: trunk -Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin. +Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to run a radio station's website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. +Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli. +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. +If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +You may also submit feature requests here: https://github.com/netmix/radio-station/issues -We are actively seeking donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate. -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin. +Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ == Installation == @@ -238,6 +238,39 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.2 = +* shift main playlist and show metaboxes above editor +* set plugin custom post types editor to Classic Editor +* add high priority to side metaboxes for plugin post types +* added dismissable development changeover admin notice +* added simple Patreon supporter image button and blurb +* added filter for DJ Avatar size on Author page template +* fix to file_exists check for DJ on Air stylesheet path +* fix to make DJ multi-select input full metabox width +* fix to expand admin menu when on genre taxonomy page +* fix to expand admin menu when editing plugin post types +* fix to genre submenu item link for current page +* added GitHub URI to plugin header for GitHub updater + += 2.2.1 = +* Re-commit all missing files via SVN + += 2.2.0 = +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) +* fixed the protocol in jQuery UI style Google URL +* reprefixed all functions for consistency (radio_station_) +* updated all the widget constructor methods +* merged the menu items into a single main menu +* updated the capability checks for the menu items +* moved the help and export pages to /templates/ +* moved all the css files to /css/ +* enqeued the djonair css from within the widget +* use plugins_url for all resource URLs +* added $wpdb->prepare to sanitize a query +* added some sanization for metabox save values +* added a week and month translation helper +* added a radio station antenna icon + = 2.1.3 = * Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). @@ -450,6 +483,9 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.0 = +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) + = 2.1.3 = * Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). diff --git a/templates/author.php b/templates/author.php index d42aa0b..884870e 100644 --- a/templates/author.php +++ b/templates/author.php @@ -18,7 +18,8 @@

display_name; ?>

-
ID, 50 ); ?>
+ ID, $avatar_size ); ?>
description; ?> @@ -64,7 +65,7 @@ if ( get_the_author_meta( 'description' ) ) : ?>
- +

From 3c333a9735685fc23579aa6ccd5783ff15931dfc Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 24 Jul 2019 23:01:15 +1000 Subject: [PATCH 016/377] schedule override metabox fix --- includes/master_schedule.php | 8 +- includes/post_types.php | 331 +++++++++++++++++++++-------------- radio-station.php | 11 -- readme.txt | 16 +- 4 files changed, 206 insertions(+), 160 deletions(-) diff --git a/includes/master_schedule.php b/includes/master_schedule.php index 5eaa163..c692398 100644 --- a/includes/master_schedule.php +++ b/includes/master_schedule.php @@ -154,8 +154,11 @@ function radio_station_master_schedule( $atts ) { $output .= '
    '; foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); $output .= '
  • '; - $output .= ''.__($day, 'radio-station').''; + $output .= ''.$display_day.''; $output .= '
      '; foreach ($hours as $hour => $mins) { @@ -414,7 +417,8 @@ function radio_station_master_schedule( $atts ) { // output the headings in the correct order $output .= '
'; foreach($days_of_the_week as $weekday => $info) { - $heading = substr( $heading, 0, 3 ); + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); $heading = radio_station_translate_weekday( $heading, true ); $output .= ''; } diff --git a/includes/post_types.php b/includes/post_types.php index b92fde7..c7ad002 100644 --- a/includes/post_types.php +++ b/includes/post_types.php @@ -10,6 +10,7 @@ // - Register Post Types // - Set CPTs to Classic Editor // - Add Show Thumbnail Support +// - Metaboxes Above Content Area // === Taxonomies === // - Add Genre Taxonomy // - Shift Genre Metabox @@ -777,68 +778,74 @@ function radio_station_myplaylist_inner_sched_custom_box() { foreach ( $shifts[0] as $track ){ if ( isset( $track['day'] ) || isset( $track['time'] ) ){ ?> -

- : - - - - : - - + + + + + + + + + + + +

  • + : + + + +
  • + +
  • + : + + - - - - - : - - - - /> - -

    + if ( $i < 10 ) {$min = '0'.$i;} + if ( $track['end_min'] == $min ) {$selected = ' selected="selected"';} else {$selected = '';} + echo ''; + } ?> + + + /> + +
  • + ; shiftaddb(".add").click(function() { count = count + 1; - output = '

    : '; + output = '

      '; + output += '
    • '; + output += ': '; output += ''; - output += ' - : '; + output += '
    • '; - output += ''; output += ''; output += ''; output += ' '; - output += ''; output += ''; ">'; output += ' '; - output += ''; output += ''; output += ''; output += ''; output += ' '; + output += ''; - output += ' - : '; - output += ''; output += ''; output += ''; output += ' '; - output += ''; output += ''; ">'; output += ' '; - output += ''; output += ''; output += ''; output += ''; output += ' '; + output += ''; + + output += '
    • '; + output += '
    • '; - output += ' '; + output += '
    • '; + output += '
    • '; - output += '

      '; + output += '
    '; shiftaddb('#here').append( output ); return false; }); shiftaddb(".remove").live('click', function() { - shiftaddb(this).parent().remove(); + shiftaddb(this).parent().parent().remove(); }); }); @@ -947,18 +966,20 @@ function radio_station_myplaylist_save_showpostdata( $post_id ) { // OK, we are authenticated: we need to find and save the data $djs = $_POST['show_user_list']; - $sched = $_POST['show_sched']; $file = $_POST['show_file']; $email = $_POST['show_email']; $active = $_POST['show_active']; $link = $_POST['show_link']; update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_sched', $sched ); update_post_meta( $post_id, 'show_file', $file ); update_post_meta( $post_id, 'show_email', $email ); update_post_meta( $post_id, 'show_active', $active ); update_post_meta( $post_id, 'show_link', $link ); + + $sched = $_POST['show_sched']; + update_post_meta( $post_id, 'show_sched', $sched ); + } add_action( 'save_post', 'radio_station_myplaylist_save_showpostdata' ); @@ -996,7 +1017,7 @@ function radio_station_master_override_inner_sched_custom_box() { ID,'show_override_sched',false); + $track = get_post_meta( $post->ID, 'show_override_sched', false); if ($track) {$track = $track[0];} ?> @@ -1008,58 +1029,62 @@ function radio_station_master_override_inner_sched_custom_box() { }); -

    - : - - - - : - - - - - - - : - - - - -

    +
      +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • +
    '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + ); + } + + // sanitize values before saving + // 2.2.2: loop and validate schedule override values + $changed = false; + foreach ( $sched as $key => $value ) { + $isvalid = false; + + // validate according to key + if ( $key == 'date' ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) {$isvalid = true;} + } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { + if ( $value == '' ) {$isvalid = true;} + elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} + } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { + if ( $value == '' ) {$isvalid = true;} + elseif ( ( absint($value) > 0 ) && ( absint($value) < 61 ) ) {$isvalid = true;} + } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { + $valid = array( '', 'am', 'pm' ); + if ( in_array( $value, $valid ) ) {$isvalid = true;} + } + + // if value override current schedule setting + if ( $isvalid && ( $value != $current_sched[$key] ) ) { + $current_sched[$key] = $value; $changed = true; + } + } - // sanitize value before saving - $valid = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); - if ( !in_array( $sched, $valid ) ) {return;} - if ( $sched == '' ) {delete_post_meta( $post_id, 'show_override_sched' );} - else {update_post_meta( $post_id, 'show_override_sched', $sched );} + // save schedule setting if changed + if ($changed) {update_post_meta( $post_id, 'show_override_sched', $current_sched );} } add_action( 'save_post', 'radio_station_master_override_save_showpostdata' ); diff --git a/radio-station.php b/radio-station.php index 875e211..ad10efe 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,29 +1,18 @@ >>>>>> release/2.2.2 */ /* Plugin Name: Radio Station Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli -<<<<<<< HEAD -Version: 2.2.1 -Text Domain: radio-station -Domain Path: /languages -Author URI: https://netmix.com/radio-station -======= Version: 2.2.2 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station GitHub Plugin URI: netmix/radio-station ->>>>>>> release/2.2.2 Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) diff --git a/readme.txt b/readme.txt index 7e30d28..528ca5e 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,6 @@ === Radio Station === -<<<<<<< HEAD -Contributors: tonyzeoli -Donate link: https://www.patreon.com/radiostation -======= Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate ->>>>>>> release/2.2.2 Tags: dj, music, playlist, radio, scheduling Requires at least: 3.3.1 Tested up to: 5.2.2 @@ -243,10 +238,6 @@ you send me your finished translation, I'd love to include it. == Changelog == -<<<<<<< HEAD -= 2.2.1 = - -======= = 2.2.2 = * shift main playlist and show metaboxes above editor * set plugin custom post types editor to Classic Editor @@ -254,6 +245,8 @@ you send me your finished translation, I'd love to include it. * added dismissable development changeover admin notice * added simple Patreon supporter image button and blurb * added filter for DJ Avatar size on Author page template +* fix to Schedule Override metabox value saving +* fix to missing day of week headings in master schedule * fix to file_exists check for DJ on Air stylesheet path * fix to make DJ multi-select input full metabox width * fix to expand admin menu when on genre taxonomy page @@ -262,7 +255,6 @@ you send me your finished translation, I'd love to include it. * added GitHub URI to plugin header for GitHub updater = 2.2.1 = ->>>>>>> release/2.2.2 * Re-commit all missing files via SVN = 2.2.0 = @@ -493,10 +485,6 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == -= 2.2.1 = - -* Re-commit all missing files via SVN - = 2.2.0 = * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) From 14dfb2ac511d682cd09bf4fda87eee03658a83ec Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 24 Jul 2019 23:05:28 +1000 Subject: [PATCH 017/377] remove DS store --- .DS_Store | Bin 10244 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 58f68e87980534b8cf74a3dac01a60eca7a3802a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMTWl0n82X=!1(lx`@EMVHzO7Nk_R7lMr-4K2NJE3-ReJ2E@7?#%82 zOSLZ=QCsGtZXDMHatG0@K)P=u2`>if?NDZ&Byn`s>X&FE(+=%*eS=I($| zA%i+VAV8oL0hYU0LL6Lhhpk2VdoNjD`->MI=6mUG+=@Al#r9z}n2-PsX22*3zg`r+ z$hUA1i3YK01{S12FEbFEM_a)>O8jG4H|gIyd=^Vurt5Wde2o>ARm-ZEORTJvj*red z*-=mP((ZtkI?8$@nl~Hs+heqzg|-$OH;wGH+A!$Yo@UxciU*szK`zH8Oj~!d15Vo3 zT|UmJFvzS-tD#)3yJu%4+||)D7YXNfc6UX>JzWpa&B@Z**3Q1+lZi8DbLZxszQQ2| z^iMOW75#w7brA>!N*9DY^Uhc;1$I@TNERcmq8p$^5w905l!*8 zJ4X}{RbSWsoWSTxHCbn;8`Qq#}`r;Z}a1 z$jXY)-Uemgq#1YYQS+=Zt=77I<`A*q+F6M(GI>`kZ;s9u5WH> z+t#(CH+pmBs#;a4R~jZVrfbG5<8ap0z1bOu2~xjO{Qb!Q)$yXqqazr7;9r_ldQfg9n)zh2OOP6>f93A7}_jJN11*wWl$~D zN^9soS=FR5m)0eQxL=9Lsw`$e2 zuosbL(ft6$yFxT&Qr z(%I7+UHF1UA8iq-3Yo;oBiO-NlV^osD=~H5`VC6cVd9f$;ClIFF+tbAV45MAVA=rK%i2rGR*G(_b&ba|9b+9V37d=_bLKdH8eUj zK&#JZaK%8dyY?_WC+T5@`OOQ-L#X1{@g(_mJayQ0JkMB3=co5PE=qb9KQAP;sQl~y S3<&c7ApgJfb=3bl|Nk3`q$e=| From fe159aabb8e465936e2dd4a15b6b704547ec7cdc Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 25 Jul 2019 01:09:57 +1000 Subject: [PATCH 018/377] playlist tracks metabox fix --- includes/post_types.php | 56 ++++++++++++++++++++++++++++------------- readme.txt | 1 + 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/includes/post_types.php b/includes/post_types.php index c7ad002..a8d1c30 100644 --- a/includes/post_types.php +++ b/includes/post_types.php @@ -260,7 +260,14 @@ function radio_station_myplaylist_inner_custom_box() { echo '
    '.$heading.'
    '; echo ""; - echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + // echo ""; + // echo ""; + // echo ""; + // echo ""; echo ""; if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { @@ -277,28 +284,29 @@ function radio_station_myplaylist_inner_custom_box() { echo ''; echo ''; echo ''; - echo ''; - echo ''; + echo ''; - echo ''; + + if ( isset( $track['playlist_entry_new'] ) && $track['playlist_entry_new'] ) {$checked = ' checked="checked"';} else {$checked = '';} + + echo ''; - echo ''; + echo ''; echo ''; $c++; } @@ -310,12 +318,26 @@ function radio_station_myplaylist_inner_custom_box() {
    From 558690e55f91fc9a0e734bbfc69c0aac5a2e3828 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 25 Jul 2019 16:27:39 +1000 Subject: [PATCH 022/377] hour row schedule table display fix --- css/program-schedule.css | 7 ++++++- includes/master_schedule.php | 6 +++--- readme.txt | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/css/program-schedule.css b/css/program-schedule.css index 4890650..8942572 100644 --- a/css/program-schedule.css +++ b/css/program-schedule.css @@ -1,4 +1,4 @@ -/* Table Style Gird */ +/* Table Style Grid */ #master-program-schedule { width: 100%; @@ -24,6 +24,11 @@ border-top: 1px solid #dddddd; } +#master-program-schedule .master-program-hour div { + margin-top: -1em; + padding-bottom: 1em; +} + #master-program-schedule span.show-title, #master-program-schedule span.show-file, #master-program-schedule span.show-time, diff --git a/includes/master_schedule.php b/includes/master_schedule.php index 2087201..a103a2b 100644 --- a/includes/master_schedule.php +++ b/includes/master_schedule.php @@ -275,7 +275,7 @@ function radio_station_master_schedule( $atts ) { } $output .= ''; - foreach($master_list as $hour => $days) { + foreach ($master_list as $hour => $days) { $output .= '
    '; @@ -430,7 +430,7 @@ function radio_station_master_schedule( $atts ) { foreach ( $master_list as $hour => $days ) { $output .= '
    '; - $output .= ''; + $output .= ''; $curskip = $nextskip; $nextskip = array(); diff --git a/readme.txt b/readme.txt index a0f9183..d174df2 100644 --- a/readme.txt +++ b/readme.txt @@ -246,8 +246,9 @@ you send me your finished translation, I'd love to include it. * added simple Patreon supporter image button and blurb * added filter for DJ Avatar size on Author page template * fix to Schedule Override metabox value saving -* fix to Playlist track list overflowing metabox -* fix to missing day of week headings in Master Schedule +* fix to Playlist track list items overflowing metabox +* fix to shift up time row on Master Schedule table view +* fix to missing weekday headings in Master Schedule table * fix to weekday display for Upcoming DJ Widget * fix to user display labels on select DJ metabox * fix to file_exists check for DJ on Air stylesheet path From 0a9c78854530fa8922af52dbfc10c059de8b8012 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 26 Jul 2019 10:02:18 +1000 Subject: [PATCH 023/377] author template fix --- templates/author.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/author.php b/templates/author.php index 884870e..323799f 100644 --- a/templates/author.php +++ b/templates/author.php @@ -18,7 +18,7 @@

    display_name; ?>

    -
    ID, $avatar_size ); ?>
    From 96ad2a1750d7ffff9b504f8bef83e418852c2a07 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 29 Jul 2019 22:32:41 +1000 Subject: [PATCH 024/377] 2.2.3 updates --- includes/post_types.php | 686 +++++++++++++++++++-------------- includes/support_functions.php | 25 +- includes/widget_djcomingup.php | 175 +++++---- includes/widget_djonair.php | 181 +++++---- includes/widget_nowplaying.php | 49 +-- radio-station.php | 18 +- readme.txt | 7 + 7 files changed, 643 insertions(+), 498 deletions(-) diff --git a/includes/post_types.php b/includes/post_types.php index 3705918..4b8d913 100644 --- a/includes/post_types.php +++ b/includes/post_types.php @@ -15,14 +15,26 @@ // - Add Genre Taxonomy // - Shift Genre Metabox // === Playlists === +// - Add Playlist Data Metabox // - Playlist Data Metabox +// - Update Playlist Data // === Shows === -// - Related Show Metabox +// - Add Related Show Metabox +// - Related Shows Metabox +// - Update Related Show +// - Add Assign Playlist to Show Metabox // - Assign Playlist to Show Metabox -// - Playlist Information Metabox -// - Assign DJ to Show Metabox +// - Add Playlist Info Metabox +// - Playlist Info Metabox +// - Add Assign DJs to Show Metabox +// - Assign DJs to Show Metabox +// - Add Show Shifts Metabox +// - Show Shifts Metabox +// - Update Show Metadata // === Schedule Overrides === +// - Add Schedule Override Metabox // - Schedule Override Metabox +// - Update Schedule Override // ------------------ @@ -32,9 +44,44 @@ // ------------------- // Register Post Types // ------------------- -// create post types for playlists and shows +// --- create post types for playlists and shows --- +add_action( 'init', 'radio_station_create_post_types' ); function radio_station_create_post_types() { + // ---- + // Show + // ---- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( 'show', + array( + 'labels' => array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ) + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __('Post type for Show descriptions', 'radio-station'), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'taxonomies' => array( 'genres' ), + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), + 'can_export' => true, + 'capability_type' => 'show', + 'map_meta_cap' => true + ) + ); + + // -------- + // Playlist + // -------- // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'playlist', @@ -64,34 +111,9 @@ function radio_station_create_post_types() { ) ); - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'show', - array( - 'labels' => array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Show descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'taxonomies' => array( 'genres' ), - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - + // ----------------- + // Schedule Override + // ----------------- // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); register_post_type( 'override', @@ -119,26 +141,32 @@ function radio_station_create_post_types() { 'map_meta_cap' => true ) ); + + // --- maybe trigger flush of rewrite rules --- + if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { + add_action( 'init', 'flush_rewrite_rules', 20 ); + delete_option( 'radio_station_flush_rewrite_rules' ); + } } -add_action( 'init', 'radio_station_create_post_types' ); // --------------------------------------- // Set Post Type Editing to Classic Editor // --------------------------------------- -// 2.2.2: so metabox displays can have wide widths +// 2.2.2: added so metabox displays can continue to use wide widths +add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); +add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); function radio_station_post_type_editor( $can_edit, $post_type ) { $post_types = array( 'show', 'playlist', 'override' ); if ( in_array( $post_type, $post_types ) ) {return false;} return $can_edit; } -add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); -add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); // -------------------------- // Add Show Thumbnail Support // -------------------------- -// add featured image support to "show" post type +// --- add featured image support to "show" post type --- // (this is probably no longer necessary as declared in register_post_type for show) +add_action( 'init', 'radio_station_add_featured_image_support' ); function radio_station_add_featured_image_support() { $supported_types = get_theme_support( 'post-thumbnails' ); @@ -149,16 +177,15 @@ function radio_station_add_featured_image_support() { add_theme_support( 'post-thumbnails', $supported_types[0] ); } } -add_action( 'init', 'radio_station_add_featured_image_support' ); // ---------------------------- // Metaboxes Above Content Area // ---------------------------- +// --- shows plugin metaboxes above editor box for plugin CPTs --- +add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); function radio_station_top_meta_boxes() { - global $post; - do_meta_boxes( get_current_screen(), 'top', $post ); + global $post; do_meta_boxes( get_current_screen(), 'top', $post ); } -add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); // ------------------ @@ -168,10 +195,11 @@ function radio_station_top_meta_boxes() { // ----------------------- // Register Genre Taxonomy // ----------------------- -// create custom taxonomy for the Show post type +// --- create custom taxonomy for the Show post type --- +add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); function radio_station_myplaylist_create_show_taxonomy() { - // add taxonomy labels + // --- add taxonomy labels --- $labels = array( 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), @@ -186,16 +214,19 @@ function radio_station_myplaylist_create_show_taxonomy() { 'menu_name' => __( 'Genre', 'radio-station' ), ); - // register the genre taxonomy + // --- register the genre taxonomy --- + // 2.2.3: added show_admin_column and show_in_quick_edit arguments register_taxonomy( 'genres', array( 'show' ), array( - 'hierarchical' => true, - 'labels' => $labels, - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array('slug' => 'genre'), - 'capabilities' => array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => false, + 'query_var' => true, + 'rewrite' => array('slug' => 'genre'), + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( 'manage_terms' => 'edit_shows', 'edit_terms' => 'edit_shows', 'delete_terms' => 'edit_shows', @@ -205,30 +236,31 @@ function radio_station_myplaylist_create_show_taxonomy() { ); } -add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); // ---------------------------- // Shift Genre Metabox on Shows // ---------------------------- +// --- moves genre metabox above publish box --- +add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); function radio_station_genre_meta_box_order() { global $wp_meta_boxes; $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; unset($wp_meta_boxes['show']['side']['core']['genresdiv']); $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; } -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); // ----------------- // === Playlists === // ----------------- -// --------------------- -// Playlist Data Metabox -// --------------------- - -// Add custom repeating meta field for the playlist edit form... Stores multiple associated values as a serialized string +// ------------------------- +// Add Playlist Data Metabox +// ------------------------- +// --- Add custom repeating meta field for the playlist edit form --- +// (Stores multiple associated values as a serialized string) // Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); function radio_station_myplaylist_add_custom_box() { // 2.2.2: change context to show at top of edit screen add_meta_box( @@ -240,34 +272,36 @@ function radio_station_myplaylist_add_custom_box() { 'high' ); } -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -// Prints the playlist entry box to the main column on the edit screen +// --------------------- +// Playlist Data Metabox +// --------------------- +// -- prints the playlist entry box to the main column on the edit screen --- function radio_station_myplaylist_inner_custom_box() { global $post; - // Use nonce for verification + // --- add nonce field for verification --- wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); ?>
    ID, 'playlist', false ); - //print_r($prices); + // print_r($entries); $c = 1; echo '
    ".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    '; + echo ''.__('Comments', 'radio-station').' '; + echo ''.__( 'New', 'radio-station' ).' '; + echo ''; + + echo ' '.__( 'Status', 'radio-station').' '; echo ''.__('Remove', 'radio-station').''.__('Remove', 'radio-station').'
    '; + $output .= '
    '; if ( $timeformat == 12 ) { if ( $hour == 0 ) {$output .= '12am';} @@ -442,7 +442,7 @@ function radio_station_master_schedule( $atts ) { $output .= $hour.":00"; } - $output .= '
    '; echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - // echo ""; - // echo ""; - // echo ""; - // echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + // echo ""; + // echo ""; + // echo ""; + // echo ""; echo ""; if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { @@ -314,83 +348,85 @@ function radio_station_myplaylist_inner_custom_box() { } echo '
    ".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    '; - ?> - -
    - -
    - -
    -

    - post_status, array('publish', 'future', 'private') ) || 0 == $post->ID ) { - if ( $can_publish ) : - if ( !empty($post->post_date_gmt) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : ?> - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - - -
    + ?> + +
    + +
    + +
    +

    + post_status, array('publish', 'future', 'private') ) || 0 == $post->ID ) { + if ( $can_publish ) : + if ( !empty($post->post_date_gmt) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : ?> + + '50', 'accesskey' => 'o' ) ); ?> + + + '50', 'accesskey' => 'o' ) ); ?> + + + '50', 'accesskey' => 'o' ) ); ?> + + + + +
    -1, 'offset' => 0, @@ -453,9 +488,11 @@ function radio_station_add_showblog_box() { if ( count( $shows ) > 0 ) { - // add a filter for which post types to show metabox on + // ---- add a filter for which post types to show metabox on --- + // TODO: add this filter to plugin documentation $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + // --- add the metabox to post types --- add_meta_box( 'dynamicShowBlog_sectionid', __( 'Related to Show', 'radio-station' ), @@ -465,13 +502,15 @@ function radio_station_add_showblog_box() { ); } } -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); +// -------------------- +// Related Show Metabox +// -------------------- // --- Prints the box content for the Show field --- function radio_station_inner_showblog_custom_box() { global $post; - // Use nonce for verification + // --- add nonce field for verification --- wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); $args = array( @@ -491,6 +530,7 @@ function radio_station_inner_showblog_custom_box() { ID, 'playlist_show_id', true ); if ( !$current ) {$selected = ' selected="selected"';} else {$selected = '';} echo ''; foreach ( $shows as $show ) { @@ -631,10 +678,11 @@ function radio_station_myplaylist_inner_show_custom_box() {

    - />
    + />


    @@ -679,12 +729,11 @@ function radio_station_myplaylist_inner_metainfo_custom_box() { roles as $name => $role ) { foreach( $role['capabilities'] as $capname => $capstatus ) { @@ -717,7 +768,7 @@ function radio_station_myplaylist_inner_user_custom_box() { } $add_roles = array_unique( $add_roles ); - // create the meta query for get_users() + // ---- create the meta query for get_users() --- $meta_query = array( 'relation' => 'OR' ); foreach ( $add_roles as $role ) { $meta_query[] = array( @@ -727,7 +778,7 @@ function radio_station_myplaylist_inner_user_custom_box() { ); } - // get all eligible users + // --- get all eligible users --- $args = array( 'meta_query' => $meta_query, 'orderby' => 'display_name', @@ -736,11 +787,11 @@ function radio_station_myplaylist_inner_user_custom_box() { ); $users = get_users( $args ); - // get the DJs currently assigned to the show + // --- get the DJs currently assigned to the show --- $current = get_post_meta( $post->ID, 'show_user_list', true ); if ( !$current ) {$current = array();} - // move any selected DJs to the top of the list + // --- move any selected DJs to the top of the list --- foreach ( $users as $i => $dj ) { if ( in_array( $dj->ID, $current ) ) { unset( $users[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item @@ -748,7 +799,7 @@ function radio_station_myplaylist_inner_user_custom_box() { } } - // 2.2.2: fix to make DJ multi-select input full metabox width + // 2.2.2: add fix to make DJ multi-select input full metabox width ?>

    @@ -768,7 +819,11 @@ function radio_station_myplaylist_inner_user_custom_box() {
    ID, 'show_sched', false); - - //print_r($shifts); + // print_r($shifts); $c = 0; if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { @@ -866,9 +922,12 @@ function radio_station_myplaylist_inner_sched_custom_box() { - /> - + +
  • />
  • + +
  • + - +
    - -
      -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • -
    -
    +
    + + ID, 'show_override_sched', false); + if ($override) {$override = $override[0];} + ?> + + +
      +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • +
    +
    $value ) { $isvalid = false; - // validate according to key + // --- validate according to key --- if ( $key == 'date' ) { // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) $parts = explode( '-', $value ); @@ -1160,21 +1262,21 @@ function radio_station_master_override_save_showpostdata( $post_id ) { if ( $value == '' ) {$isvalid = true;} elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { + // 2.2.3: fix to validate 00 minute value if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 61 ) ) {$isvalid = true;} + elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { $valid = array( '', 'am', 'pm' ); if ( in_array( $value, $valid ) ) {$isvalid = true;} } - // if value override current schedule setting + // --- if valid add to current schedule setting --- if ( $isvalid && ( $value != $current_sched[$key] ) ) { $current_sched[$key] = $value; $changed = true; } } - // save schedule setting if changed + // --- save schedule setting if changed --- if ($changed) {update_post_meta( $post_id, 'show_override_sched', $current_sched );} } -add_action( 'save_post', 'radio_station_master_override_save_showpostdata' ); diff --git a/includes/support_functions.php b/includes/support_functions.php index f0895fe..826d12a 100644 --- a/includes/support_functions.php +++ b/includes/support_functions.php @@ -96,6 +96,19 @@ function radio_station_convert_schedule_to_24hour( $sched = array() ) { // --- fetch the current DJ(s) on-air -- function radio_station_dj_get_current() { + // first check to see if there are any shift overrides + $check = radio_station_master_get_overrides(true); + + if ( $check ) { + $shows = array( + 'all' => $check, + 'type' => 'override' + ); + + // at this point, we are done. Return the info. + return $shows; + } + // load the info for the DJ global $wpdb; @@ -107,18 +120,6 @@ function radio_station_dj_get_current() { $curDay = date( 'l', $now ); $curDate = date( 'Y-m-d', $now ); - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(true); - - if ( $check ) { - $shows = array(); - $shows['all'] = $check; - $shows['type'] = 'override'; - - // at this point, we are done. Return the info. - return $shows; - } - // then check to see if a show is scheduled // $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` // WHERE `meta_key` = 'show_sched';"); diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php index 65337d2..702dbe3 100644 --- a/includes/widget_djcomingup.php +++ b/includes/widget_djcomingup.php @@ -7,9 +7,9 @@ class DJ_Upcoming_Widget extends WP_Widget { // use __construct instead of DJ_Upcoming_Widget function __construct() { - $widget_ops = array('classname' => 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station')); - // $this->WP_Widget('DJ_Upcoming_Widget', __('Radio Station: Upcoming DJ On-Air', 'radio-station'), $widget_ops); - parent::__construct('DJ_Upcoming_Widget', __('Radio Station: Upcoming DJ On-Air', 'radio-station' ), $widget_ops ); + $widget_ops = array( 'classname' => 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); + $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); + parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); } // --- widget instance form --- @@ -116,110 +116,121 @@ function widget( $args, $instance ) { $time = empty( $instance['time'] ) ? '' : $instance['time']; $show_sched = $instance['show_sched']; - // find out which DJ(s) are coming up today + // --- find out which DJ(s) are coming up today --- $djs = radio_station_dj_get_next($limit); - // print_r($djs); + // 2.2.3: convert all span tags to div tags + ?>
      + 0 ) ) { //print_r($djs['all']); foreach ( $djs['all'] as $showtime => $dj ) { if ( is_array($dj) && $dj['type'] == 'override' ) { - echo '
    • '; - - if ( $djavatar ) { - if ( has_post_thumbnail($dj['post_id'] ) ) { - echo ''.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).''; - } - } - echo $dj['title']; - - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $dj['sched']['start_hour']; - if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { - $start_hour = substr($dj['sched']['start_hour'], 1); - } - - $end_hour = $dj['sched']['end_hour']; - if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($dj['sched']['end_hour'], 1); - } + echo '
    • '; - echo ' '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
      '; - } else { - echo ' '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
      '; - } - } + // --- show thumbnail --- + if ( $djavatar ) { + if ( has_post_thumbnail($dj['post_id'] ) ) { + echo '
      '.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
      '; + } + } + + // --- show title --- + echo '
      '.$dj['title'].'
      '; + + // --- show schedule --- + if ( $show_sched ) { + + if ( $time == 12 ) { + $start_hour = $dj['sched']['start_hour']; + if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { + $start_hour = substr($dj['sched']['start_hour'], 1); + } + + $end_hour = $dj['sched']['end_hour']; + if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { + $end_hour = substr($dj['sched']['end_hour'], 1); + } + + echo '
      '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
      '; + } else { + echo '
      '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
      '; + } + } echo '
    • '; } else { - // print as normal - echo '
    • '; - if ( $djavatar ) { - echo ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - - if ( $link ) { - echo ''.$dj->post_title.''; - } else { - echo $dj->post_title; - } - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if($names) { - echo '
      '.__( 'With', 'radio-station' ).' '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - echo $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - echo ', '; - } - } - echo '
      '; - } - } - - echo ''; - - if ( $show_sched ) { - - $showtimes = explode( "|", $showtime ); - // 2.2.2: fix to weekday value to be translated - $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); - - if ( $time == 12 ) { - echo ''.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
      '; - } else { - echo ''.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
      '; - } + echo '
    • '; - } + // --- show thumbnail --- + if ( $djavatar ) { + echo '
      '.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; + } + + // --- show title --- + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; + + // --- DJ names --- + if ( $display_djs ) { + + $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; + + if($names) { + echo '
      '.__( 'With', 'radio-station' ).' '; + foreach ( $names as $name ) { + $count++; + $user_info = get_userdata( $name ); + + echo $user_info->display_name; + + if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) + || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { + echo ' and '; + } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { + echo ', '; + } + } + echo '
      '; + } + } + + echo ''; + + // --- show schedule --- + if ( $show_sched ) { + + $showtimes = explode( "|", $showtime ); + // 2.2.2: fix to weekday value to be translated + $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); + + if ( $time == 12 ) { + echo '
      '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
      '; + } else { + echo '
      '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
      '; + } + + } echo '
    • '; } diff --git a/includes/widget_djonair.php b/includes/widget_djonair.php index b82bce2..56c0096 100644 --- a/includes/widget_djonair.php +++ b/includes/widget_djonair.php @@ -5,11 +5,11 @@ */ class DJ_Widget extends WP_Widget { - // use __contruct instead of DJ_Widget + // --- use __contruct instead of DJ_Widget --- function __construct() { $widget_ops = array( 'classname' => 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); - // $this->WP_Widget( 'DJ_Widget', __('Radio Station: Show/DJ On-Air', 'radio-station'), $widget_ops ); - parent::__construct( 'DJ_Widget', __('Radio Station: Show/DJ On-Air', 'radio-station' ), $widget_ops ); + $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); } // --- widget instance form --- @@ -137,10 +137,12 @@ function widget( $args, $instance ) { $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; // keep the default settings for people updating from 1.6.2 or earlier $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; // keep the default settings for people updating from 2.0.12 or earlier - // fetch the current DJs + // --- fetch the current DJs and playlist --- $djs = radio_station_dj_get_current(); $playlist = radio_station_myplaylist_get_now_playing(); + // 2.2.3: convert all span tags to div tags + ?>
      '; - if ( $djavatar ) { - if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo ''.get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).''; + if ( $djavatar ) { + if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { + echo '
      '.get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
      '; + } } - } - echo $djs['all'][0]['title']; + // --- show title --- + echo '
      '.$djs['all'][0]['title'].'
      '; - // display the schedule override if requested - if ( $show_sched ) { + // --- display the schedule override if requested --- + if ( $show_sched ) { - if ( $time == 12 ) { - $start_hour = $djs['all'][0]['sched']['start_hour']; - if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { - $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); - } + if ( $time == 12 ) { + $start_hour = $djs['all'][0]['sched']['start_hour']; + if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { + $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); + } - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); - } + $end_hour = $djs['all'][0]['sched']['end_hour']; + if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { + $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); + } - echo ' '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].'-'.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
      '; + echo '
      '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
      '; - } else { + } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - echo ' '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
      '; + $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); + echo '
      '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
      '; + } } - echo ''; - } + echo ''; } else { @@ -198,84 +202,92 @@ function widget( $args, $instance ) { $scheds = get_post_meta( $dj->ID, 'show_sched', true ); echo '
    • '; - if ( $djavatar ) { - echo ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - if ( $link ) { - echo ''.$dj->post_title.''; - } else { - echo $dj->post_title; - } + // --- show thumbnail --- + if ( $djavatar ) { + echo '
      '.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'

      '; + } + + // --- show title --- + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; - if ( $display_djs ) { + // --- DJ names --- + if ( $display_djs ) { - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; + $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; - if ( $names ) { + if ( $names ) { - echo '
      '.__( 'With', 'radio-station' ).' '; - foreach( $names as $name ) { - $count ++; - $user_info = get_userdata($name); + echo '
      '.__( 'With', 'radio-station' ).' '; + foreach( $names as $name ) { + $count ++; + $user_info = get_userdata($name); - echo $user_info->display_name; + echo $user_info->display_name; - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - echo ', '; + if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) + || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { + echo ' and '; + } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { + echo ', '; + } } + echo '
      '; } - echo '
      '; } - } - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - echo ''.$desc_string.''; - } + // --- show description --- + if ( $show_desc ) { + $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); + echo '
      '.$desc_string.'
      '; + } - if ( $show_playlist ) { - echo ''.__('View Playlist', 'radio-station').''; - } - echo ''; + // --- playlist link --- + if ( $show_playlist ) { + echo ''; + } + + echo ''; - if ( $show_sched ) { + // --- show schedule --- + if ( $show_sched ) { - // if we only want the schedule that's relevant now to display... - if ( !$show_all_sched ) { + // --- if we only want the schedule that's relevant now to display --- + if ( !$show_all_sched ) { - $current_sched = radio_station_current_schedule( $scheds ); + $current_sched = radio_station_current_schedule( $scheds ); - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - echo ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
      '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - echo ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
      '; + if ( $current_sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $current_sched['day'] ); + if ( $time == 12 ) { + echo '
      '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
      '; + } else { + $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); + echo '
      '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
      '; + } } - } - } else { + } else { - foreach ( $scheds as $sched ) { + foreach ( $scheds as $sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - echo ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
      '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - echo ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
      '; + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $sched['day'] ); + if ( $time == 12 ) { + echo '
      '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
      '; + } else { + $sched = radio_station_convert_schedule_to_24hour( $sched ); + echo '
      '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
      '; + } } } } - } + echo '
    • '; } @@ -297,7 +309,8 @@ function widget( $args, $instance ) { $version = filemtime( $dj_widget_css ); $url = get_stylesheet_directory_uri().'/djonair.css'; } else { - $version = filemtime( dirname(__FILE__).'/css/djonair.css' ); + // 2.2.3: fix to version path check also + $version = filemtime( dirname(dirname(__FILE__)).'/css/djonair.css' ); $url = plugins_url('css/djonair.css', dirname(dirname(__FILE__)).'/radio-station.php' ); } wp_enqueue_style( 'dj-widget', $url, array(), $version, true ); diff --git a/includes/widget_nowplaying.php b/includes/widget_nowplaying.php index c57407c..4a58930 100644 --- a/includes/widget_nowplaying.php +++ b/includes/widget_nowplaying.php @@ -5,11 +5,11 @@ */ class Playlist_Widget extends WP_Widget { - // use __constuct instead of Playlist_Widget + // --- use __constuct instead of Playlist_Widget --- function __construct() { - $widget_ops = array('classname' => 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station')); - // $this->WP_Widget('Playlist_Widget', __('Radio Station: Now Playing', 'radio-station'), $widget_ops); - parent::__construct('Playlist_Widget', __('Radio Station: Now Playing', 'radio-station' ), $widget_ops ); + $widget_ops = array( 'classname' => 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); + $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); + parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); } // --- widget instance form --- @@ -113,33 +113,36 @@ function widget( $args, $instance ) { echo '
      '; - if ( $song ) { - echo ''.$most_recent['playlist_entry_song'].' '; - } + // 2.2.3: convert span tags to div tags + if ( $song ) { + echo '
      '.$most_recent['playlist_entry_song'].'
      '; + } - if ( $artist ) { - echo ''.$most_recent['playlist_entry_artist'].' '; - } + if ( $artist ) { + echo '
      '.$most_recent['playlist_entry_artist'].'
      '; + } - if ( $album ) { - echo ''.$most_recent['playlist_entry_album'].' '; - } + if ( $album ) { + echo '
      '.$most_recent['playlist_entry_album'].'
      '; + } - if ( $label ) { - echo ''.$most_recent['playlist_entry_label'].' '; - } + if ( $label ) { + echo '
      '.$most_recent['playlist_entry_label'].'
      '; + } - if ( $comments ) { - echo ''.$most_recent['playlist_entry_comments'].' '; - } + if ( $comments ) { + echo '
      '.$most_recent['playlist_entry_comments'].'
      '; + } + + echo ''; - echo ''; - echo ''.__('View Playlist', 'radio-station').''; - echo ''; echo '
      '; } else { - echo 'No playlists available.'; + // 2.2.3: added missing translation wrapper + echo '
      '.__('No playlists available.','radio-station').'
      '; } ?> diff --git a/radio-station.php b/radio-station.php index 7d00777..6e1e62f 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,14 +1,14 @@ -Version: 2.2.2 +Version: 2.2.3 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -67,6 +67,14 @@ function radio_station_init() { } add_action( 'plugins_loaded', 'radio_station_init' ); +// --- flush rewrite rules on activate / deactivation --- +// 2.2.3: added this for custom post types rewrite flushing +register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); +register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); +function radio_station_flush_rewrite_flag() { + add_option( 'radio_station_flush_rewrite_rules', true ); +} + // --- enqueue necessary stylesheets --- function radio_station_load_styles() { @@ -93,7 +101,7 @@ function radio_station_master_scripts() { // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); if (is_ssl()) {$protocol = 'https';} else {$protocol = 'http';} $url = $protocol.'://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; - wp_enqueue_style( 'jquery-ui-style', $url ); + wp_enqueue_style( 'jquery-ui-style', $url, '1.8.2' ); } add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); @@ -141,7 +149,7 @@ function radio_station_load_template( $single_template ) { return $single_template; } -add_filter( 'single_template', 'radio_station_load_template' ) ; +add_filter( 'single_template', 'radio_station_load_template' ); // --- load the theme file for the playlist archive pages --- function radio_station_load_custom_post_type_template( $archive_template ) { @@ -156,7 +164,7 @@ function radio_station_load_custom_post_type_template( $archive_template ) { return $archive_template; } -add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ) ; +add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ); diff --git a/readme.txt b/readme.txt index d174df2..c6aa398 100644 --- a/readme.txt +++ b/readme.txt @@ -238,6 +238,13 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.3 = +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + = 2.2.2 = * shift main playlist and show metaboxes above editor * set plugin custom post types editor to Classic Editor From d96d7ae80e188c5c7310eaa0c518fa1aebc01e95 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Tue, 30 Jul 2019 20:57:53 -0400 Subject: [PATCH 025/377] Create .DS_Store --- .DS_Store | Bin 0 -> 8196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d462bde636ee06778d060bab22d103933b745532 GIT binary patch literal 8196 zcmeHLPiz!b82`R)DKi75Q-rnyEZtD61xx=?{waT!ZZR~VRJNrp1>4!3u^pYAS$Af; zg<5Q4NYtPQ6F@x~y+}A1F?!6GoJsHHCO4Ld>v zLIgqtLIgqtLImy)1ZdA@A}z7+3u{=02!seckO;8%LyRt_ipxMbc}V7jt6>1XzC$B5lUKwVsOO3;Ou}Rob2(ye@18#4#-EQJi*8qoKP@0dSHyZ z1IC38%MgJOfrSXLy}u07;DOg;&pW?AL6)X}xbUzFupk3EjDfkp_i~A^M7|DXwIL72 zLVv6+@t4U{r-(7~UvvaKR}xVTIr21U92d6I?0stayyZX(WMLe#^VfsFm1IyTgUNGb zNVib*B>3}VB6>-TOkO6Tkmd7b^%B$~U;lqZs^DdV|0Fk3S=;jbwzk_CDJx%Av0P$O zDV<7=yM?5$`+2WR&keIyukMehf_k6p+H-Y1b=)!wBkG!j>-f6mm^mJ7878@$9Izb2 zEp)kg$MATbq%g=#M%0?AskY`=N9V5T=GfG(Z5_?A9UZ%;r)8jVDRpRD$$-R-=^9k(AeHYLM$}$R&k+siVKyM722U5$IYBax*^^srY>Juxi-4#k;mG$KfW(9Te+%AtzNAtCkP?Ox1O`i zTz|nb{P7{(OPh{iIb#D8mS?4Gv(M2pW;Hg;@@!3QU8S~8+fX!>hDd6IG?-6WFXk=( zG#_JQy{4QrXm(Gz1{=jtKb2%|X{gtOezgYnpIa-RVq!|7FpG$K9ANbdbTR9 zvg*lwFVOAfJz9s-DXE6sZ|f=37F|1)-I97(9+(iz7M-h=y^=hbb~71Tq{oNdAJ?>J z*|0}+&p(n}V{0e`-pYRv_jTKmh8*n4mV+=RrcHDtGF^&f? zf!)}H$1sWgcpQh(KoiGs933p+B%Z-3dBSp?05$$4$-&1XI&IS)>WN0zYMh`xzB#p5otsvTpRLXi~zbvc3 zhfx}%30YFHl!QxC@G@M5&*4kBMb!HNeu7`%cLbu{N~|H;#jpV%#zt(xR&2)(?8Lpe zFCgF%d>VW486x2@p286vMIE0=FDoXmMF0Q* literal 0 HcmV?d00001 From 9efe21ce8f1cf79284adaf53759eeed54e0eee9e Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Tue, 30 Jul 2019 21:09:31 -0400 Subject: [PATCH 026/377] Update readme.txt file 2.2.3 Needed to change the dontate link and add 2.2.3 changelog to Upgrade Notice. --- readme.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/readme.txt b/readme.txt index de392de..da33da5 100644 --- a/readme.txt +++ b/readme.txt @@ -16,11 +16,11 @@ We are grateful to Nikki Blight for her contribution to creating and developing If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +You may also submit bugs and feature requests here: https://github.com/netmix/radio-station/issues -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate. +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ == Installation == @@ -496,6 +496,13 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.3 = +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + = 2.2.2 = * shift main playlist and show metaboxes above editor * set plugin custom post types editor to Classic Editor From 4d72da9c9a7b06219314178a3a80a220fb07ae96 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 4 Aug 2019 19:45:43 +1000 Subject: [PATCH 027/377] 2.2.4 updates --- css/djonair.css | 51 ----------- css/widgets.css | 59 ++++++++++++ includes/post_types.php | 4 + includes/support_functions.php | 7 ++ includes/widget_djcomingup.php | 160 ++++++++++++++++++++++++++------- includes/widget_djonair.php | 156 +++++++++++++++++++++++++------- includes/widget_nowplaying.php | 35 ++++++-- radio-station.php | 4 +- readme.txt | 7 ++ 9 files changed, 357 insertions(+), 126 deletions(-) delete mode 100644 css/djonair.css create mode 100644 css/widgets.css diff --git a/css/djonair.css b/css/djonair.css deleted file mode 100644 index 0049cc7..0000000 --- a/css/djonair.css +++ /dev/null @@ -1,51 +0,0 @@ -/* Widget layout */ -.on-air-dj-avatar { - float: left; - padding-right: 15px; - padding-bottom: 5px; - display: block; - width: 50px; -} - -.on-air-dj-avatar img { - width: 50px; - height: 50px; -} - -.on-air-list, -.widget .on-air-list, -.on-air-upcoming-list, -.widget .on-air-upcoming-list { - list-style-type: none; -} - -.on-air-list li, -.widget .on-air-list li, -.on-air-upcoming-list li, -.widget .on-air-upcoming-list li { - font-size: 2em; -} - -.on-air-dj-sched { - font-size: .5em; - font-style: italics; -} -.radio-clear { - display: block; - clear: both; -} - -/* Schedule layout */ -.on-air-dj-schedule-day-block { - float: left; - margin-right: 25px; -} - -.on-air-dj-schedule-day-title {} -.on-air-dj-schedule-time-list {} -.on-air-dj-schedule-time-item {} -.on-air-dj-schedule-dj-list {} -.on-air-dj-schedule-dj-item {} -.on-air-no-dj { font-style: italics; } -.on-air-dj-schedule-dj-item {} -.scheduled-dj {} diff --git a/css/widgets.css b/css/widgets.css new file mode 100644 index 0000000..8714cd3 --- /dev/null +++ b/css/widgets.css @@ -0,0 +1,59 @@ +/* Widget Layout */ +.on-air-dj-avatar { + padding-bottom: 5px; + display: block; + max-width: 90%; +} + +.on-air-dj-avatar img { + width: 100%; + height: auto; +} + +.on-air-dj-avatar.float-left { + float: left; + padding-right: 15px; +} + +.on-air-dj-avatar.float-right { + float: right; + padding-left: 15px; +} + +.on-air-list .on-air-dj-title { + font-size: 1.25em; +} + +.on-air-upcoming-list .on-air-dj-title { + font-size: 1.1em; +} + +.on-air-dj-encore { + font-style: italic; +} + +.on-air-dj-encore, .on-air-dj-names { + font-size: 1em; +} + +.on-air-upcoming-list .on-air-dj-encore, .on-air-upcoming-list .on-air-dj-names { + font-size: 0.9em; +} + +.on-air-list, +.widget .on-air-list, +.on-air-upcoming-list, +.widget .on-air-upcoming-list { + list-style: none; + list-style-type: none; +} + +.on-air-dj-desc, .on-air-dj-sched { + font-size: .8em; +} + +.radio-clear { + display: block; + clear: both; +} + diff --git a/includes/post_types.php b/includes/post_types.php index 4b8d913..91022b1 100644 --- a/includes/post_types.php +++ b/includes/post_types.php @@ -1096,6 +1096,10 @@ function radio_station_myplaylist_save_showpostdata( $post_id ) { } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { $valid = array( '', 'am', 'pm' ); if ( in_array( $value, $valid ) ) {$isvalid = true;} + } elseif ($key == 'encore') { + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on'); + if ( in_array( $value, $valid ) ) {$isvalid = true;} } // --- if valid add to new schedule --- diff --git a/includes/support_functions.php b/includes/support_functions.php index 826d12a..6792144 100644 --- a/includes/support_functions.php +++ b/includes/support_functions.php @@ -246,6 +246,7 @@ function radio_station_dj_get_next($limit = 1) { // fix that to prevent errors in the foreach loop. if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} + $encore_ids = array(); $days = array('Sunday' => 7, 'Monday' => 1, 'Tuesday' => 2, 'Wednesday' => 3, 'Thursday' => 4, 'Friday' => 5, 'Saturday' => 6); foreach ( $shift->meta_value as $time ) { @@ -267,6 +268,10 @@ function radio_station_dj_get_next($limit = 1) { // if the shift occurs later than the current time, we want it if ( $curShift >= $now ) { $show_ids[$curShift.'|'.$endShift] = $shift->post_id; + // 2.2.4: set encore ID array to pass back + if ( isset( $time['encore'] ) && ( $time['encore'] == 'on' ) ) { + $encore_ids[$curShift.'|'.$endShift] = $shift->post_id; + } } } @@ -320,6 +325,8 @@ function array_replace() { } } $shows['type'] = 'shows'; + // 2.2.4: set encore IDs to pass back + $shows['encore'] = $encore_ids; // return the information return $shows; diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php index 702dbe3..92d14af 100644 --- a/includes/widget_djcomingup.php +++ b/includes/widget_djcomingup.php @@ -25,6 +25,11 @@ function form( $instance ) { $time = isset( $instance['time'] ) ? $instance['time']: 12; $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + ?>

      -

      + +

      +

      -

      + +

      + +

      + +

      +

      @@ -98,6 +137,12 @@ function update( $new_instance, $old_instance ) { $instance['limit'] = $new_instance['limit']; $instance['time'] = $new_instance['time']; $instance['show_sched'] = $new_instance['show_sched']; + + // 2.2.4: added title position, avatar width and DJ link settings + $instance['title_position'] = $new_instance['title_position']; + $instance['avatar_width'] = $new_instance['avatar_width']; + $instance['link_djs'] = ( isset( $new_instance['link_djs'] ) ? 1 : 0 ); + return $instance; } @@ -116,11 +161,21 @@ function widget( $args, $instance ) { $time = empty( $instance['time'] ) ? '' : $instance['time']; $show_sched = $instance['show_sched']; + // 2.2.4: added title position, avatar width and DJ link settings + $position = empty( $instance['title_position'] ) ? 'right' : $instance['title_position']; + $width = empty( $instance['avatar_width'] ) ? '75' : $instance['avatar_width']; + $link_djs = $instance['link_djs']; + // --- find out which DJ(s) are coming up today --- $djs = radio_station_dj_get_next($limit); // print_r($djs); // 2.2.3: convert all span tags to div tags + // 2.2.4: maybe set float class and avatar width style + $floatclass = $widthstyle = ''; + if ( $width != '' ) {$widthstyle = 'style="width:'.$width.'px;"';} + if ( $position == 'right' ) {$floatclass = ' float-left';} + elseif ( $position == 'left' ) {$floatclass = ' float-right';} ?> @@ -135,8 +190,7 @@ function widget( $args, $instance ) { 0 ) ) { - //print_r($djs['all']); + if ( isset($djs['all']) && ( count( $djs['all'] ) > 0 ) ) { foreach ( $djs['all'] as $showtime => $dj ) { @@ -144,15 +198,25 @@ function widget( $args, $instance ) { echo '
    • '; - // --- show thumbnail --- + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
      '.$dj['title'].'
      '; + } + + // --- show avatar --- if ( $djavatar ) { if ( has_post_thumbnail($dj['post_id'] ) ) { - echo '
      '.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
      '; + echo '
      '; + echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
      '; } } // --- show title --- - echo '
      '.$dj['title'].'
      '; + if ( $position != 'above' ) { + echo '
      '.$dj['title'].'
      '; + } + + echo ''; // --- show schedule --- if ( $show_sched ) { @@ -179,35 +243,58 @@ function widget( $args, $instance ) { echo '
    • '; - // --- show thumbnail --- + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; + } + + // --- show avatar --- if ( $djavatar ) { - echo '
      '.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; + echo '
      '; + echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; } // --- show title --- - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; + if ( $position != 'above' ) { + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; + } + + echo ''; + + // --- encore presentation --- + // 2.2.4: added encore presentation display + if ( array_key_exists( $showtime, $djs['encore'] ) ) { + echo '
      '.__('Encore Presentation','radio-station').'
      '; + } // --- DJ names --- if ( $display_djs ) { - $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $ids = get_post_meta( $dj->ID, 'show_user_list', true ); $count = 0; - if($names) { - echo '
      '.__( 'With', 'radio-station' ).' '; - foreach ( $names as $name ) { + if ( $ids && is_array( $ids ) ) { + echo '
      '.__( 'with', 'radio-station' ).' '; + foreach ( $ids as $id ) { $count++; - $user_info = get_userdata( $name ); - - echo $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { + $user_info = get_userdata( $id ); + + if ( $link_djs ) { + $dj_link = get_author_posts_url( $user_info->ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + echo ''.$user_info->display_name.''; + } else {echo $user_info->display_name;} + + if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) + || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { + echo ' '.__( 'and', 'radio-station' ).' '; + } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { echo ', '; } } @@ -245,6 +332,19 @@ function widget( $args, $instance ) {

      -

      + +

      +

      -

      + + +

      + +

      + +

      +

      @@ -116,6 +156,12 @@ function update( $new_instance, $old_instance ) { $instance['show_playlist'] = $new_instance['show_playlist']; $instance['show_all_sched'] = $new_instance['show_all_sched']; $instance['show_desc'] = $new_instance['show_desc']; + + // 2.2.4: added title position and avatar width settings + $instance['title_position'] = $new_instance['title_position']; + $instance['avatar_width'] = $new_instance['avatar_width']; + $instance['link_djs'] = ( isset( $new_instance['link_djs'] ) ? 1 : 0 ); + return $instance; } @@ -137,11 +183,21 @@ function widget( $args, $instance ) { $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; // keep the default settings for people updating from 1.6.2 or earlier $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; // keep the default settings for people updating from 2.0.12 or earlier + // 2.2.4: added title position, avatar width and DJ link settings + $position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; + $width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; + $link_djs = $instance['link_djs']; + // --- fetch the current DJs and playlist --- $djs = radio_station_dj_get_current(); $playlist = radio_station_myplaylist_get_now_playing(); // 2.2.3: convert all span tags to div tags + // 2.2.4: maybe set float class and avatar width style + $floatclass = $widthstyle = ''; + if ( $width != '' ) {$widthstyle = 'style="width:'.$width.'px;"';} + if ( $position == 'right' ) {$floatclass = ' float-left';} + elseif ( $position == 'left' ) {$floatclass = ' float-right';} ?>
      @@ -156,17 +212,26 @@ function widget( $args, $instance ) { // --- find out which DJ/show is currently scheduled to be on-air and display them --- if ( $djs['type'] == 'override' ) { - // print_r($djs); echo '
    • '; + // --- show title *for above only) --- + if ( $position == 'above' ) { + echo '
      '.$djs['all'][0]['title'].'
      '; + } + if ( $djavatar ) { if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo '
      '.get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
      '; + echo '
      '; + echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
      '; } } // --- show title --- - echo '
      '.$djs['all'][0]['title'].'
      '; + if ( $position != 'above' ) { + echo '
      '.$djs['all'][0]['title'].'
      '; + } + + echo ''; // --- display the schedule override if requested --- if ( $show_sched ) { @@ -200,39 +265,61 @@ function widget( $args, $instance ) { foreach( $djs['all'] as $dj ) { $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + $current_sched = radio_station_current_schedule( $scheds ); echo '
    • '; - // --- show thumbnail --- + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; + } + + // --- show avatar --- if ( $djavatar ) { - echo '
      '.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'

      '; + echo '
      '; + echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; } // --- show title --- - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; + if ( $position != 'above' ) { + echo '
      '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
      '; + } + + echo ''; + + // --- encore presentation --- + // 2.2.4: added encore presentation display + if ( $current_sched['encore'] == 'on' ) { + echo '
      '.__('Encore Presentation','radio-station').'
      '; + } // --- DJ names --- if ( $display_djs ) { - $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $ids = get_post_meta( $dj->ID, 'show_user_list', true ); $count = 0; - if ( $names ) { + if ( $ids && is_array( $ids ) ) { - echo '
      '.__( 'With', 'radio-station' ).' '; - foreach( $names as $name ) { - $count ++; - $user_info = get_userdata($name); + echo '
      '.__( 'with', 'radio-station' ).' '; + foreach( $ids as $id ) { + $count++; + $user_info = get_userdata($id); - echo $user_info->display_name; + $dj_link = get_author_posts_url( $user_info->ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + echo ''.$user_info->display_name.''; - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { + if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) + || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { + echo ' '.__( 'and', 'radio-station').' '; + } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { echo ', '; } } @@ -243,6 +330,7 @@ function widget( $args, $instance ) { // --- show description --- if ( $show_desc ) { $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); + $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); echo '
      '.$desc_string.'
      '; } @@ -259,8 +347,6 @@ function widget( $args, $instance ) { // --- if we only want the schedule that's relevant now to display --- if ( !$show_all_sched ) { - $current_sched = radio_station_current_schedule( $scheds ); - if ( $current_sched ) { // 2.2.2: translate weekday for display $display_day = radio_station_translate_weekday( $current_sched['day'] ); @@ -303,17 +389,19 @@ function widget( $args, $instance ) { // --- enqueue widget stylesheet in footer --- // (this means it will only load if widget is on page) - $dj_widget_css = get_stylesheet_directory().'/djonair.css'; + // 2.2.4: renamed djonair.css and load for all widgets + $dj_widget_css = get_stylesheet_directory().'/widgets.css'; // 2.2.2: fix to file check logic (file_exists not !file_exists) if ( file_exists( $dj_widget_css ) ) { $version = filemtime( $dj_widget_css ); - $url = get_stylesheet_directory_uri().'/djonair.css'; + $url = get_stylesheet_directory_uri().'/widgets.css'; } else { // 2.2.3: fix to version path check also - $version = filemtime( dirname(dirname(__FILE__)).'/css/djonair.css' ); - $url = plugins_url('css/djonair.css', dirname(dirname(__FILE__)).'/radio-station.php' ); + $version = filemtime( dirname(dirname(__FILE__)).'/css/widgets.css' ); + $url = plugins_url('css/widgets.css', dirname(dirname(__FILE__)).'/radio-station.php' ); } - wp_enqueue_style( 'dj-widget', $url, array(), $version, true ); + // 2.2.4: fix to media argument + wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); echo $after_widget; } diff --git a/includes/widget_nowplaying.php b/includes/widget_nowplaying.php index 4a58930..3b08773 100644 --- a/includes/widget_nowplaying.php +++ b/includes/widget_nowplaying.php @@ -95,8 +95,9 @@ function widget( $args, $instance ) { $comments = $instance['comments']; - // fetch the current song + // --- fetch the current song --- $most_recent = radio_station_myplaylist_get_now_playing(); + echo ""; ?>
      @@ -114,29 +115,32 @@ function widget( $args, $instance ) { echo '
      '; // 2.2.3: convert span tags to div tags - if ( $song ) { + // 2.2.4: check value keys are set before outputting + if ( $song && isset( $most_recent['playlist_entry_song']) ) { echo '
      '.$most_recent['playlist_entry_song'].'
      '; } - if ( $artist ) { + if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { echo '
      '.$most_recent['playlist_entry_artist'].'
      '; } - if ( $album ) { + if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { echo '
      '.$most_recent['playlist_entry_album'].'
      '; } - if ( $label ) { + if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { echo '
      '.$most_recent['playlist_entry_label'].'
      '; } - if ( $comments ) { + if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { echo '
      '.$most_recent['playlist_entry_comments'].'
      '; } - echo ''; + if ( isset( $most_recent['playlist_permalink'] ) ) { + echo ''; + } echo '
      '; @@ -151,6 +155,19 @@ function widget( $args, $instance ) {
      -Version: 2.2.3 +Version: 2.2.4 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station diff --git a/readme.txt b/readme.txt index c6aa398..ea05893 100644 --- a/readme.txt +++ b/readme.txt @@ -238,6 +238,13 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.4 = +* added title position and avatar width options to widgets +* added missing DJ author links as new option to widgets +* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) +* improved to show Encore Presentation in show widget displays +* fix to Show shift Encore Presentation checkbox saving + = 2.2.3 = * added flush rewrite rules on plugin activation/deactivation * added show_admin_column and show_in_quick_edit for Genres From 4a7e66233129112079d91694ed4ee3128cac03f9 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sun, 4 Aug 2019 20:59:53 -0400 Subject: [PATCH 028/377] Update .DS_Store --- .DS_Store | Bin 8196 -> 10244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index d462bde636ee06778d060bab22d103933b745532..1d082bd2db27cd1ef1c9d56896a2c55c6fb662e4 100644 GIT binary patch delta 206 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50$jG}fU^hP_?_?eUO<`t+WQJmf z;-vE8f~1`MB%rdBle+{PG$pF5jg3up6pT#_YjqT=&CLyT6ikfGYHK+;#8nM#Jri;( ztEy{i>t;?i6p|O81=I&MsVulCFDE}Q9Vo)M*+}3j`^E-tc0pz!7YG!%frKl_DH{vF gGf(E12^3+1I8Xy5&B(xD0HP-s$n$ v1;sYI3T|f?WCp4O0ts#);R@2SvG6%E$}=%_0*o From 8360048e046c82c6a88095963cbd10a974c409b8 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sun, 4 Aug 2019 21:02:47 -0400 Subject: [PATCH 029/377] Add 2.2.4 notes to the upgrade notice Add changelog notes to upgrade notice in readme file. --- .DS_Store | Bin 10244 -> 10244 bytes readme.txt | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index 1d082bd2db27cd1ef1c9d56896a2c55c6fb662e4..97e8ecf5595ab74c3286ae192c41ac52fb7cb1a6 100644 GIT binary patch delta 20 bcmZn(XbISGMu6SOOh>`k$b9o<0Yh;BN>K)C delta 20 bcmZn(XbISGMu6SeR7b(s#BlRv0Yh;BN<0Q; diff --git a/readme.txt b/readme.txt index b8cf850..8bea178 100644 --- a/readme.txt +++ b/readme.txt @@ -501,7 +501,14 @@ you send me your finished translation, I'd love to include it. = 1.0 = * Initial release -== Upgrade Notice == +== Upgrade Notice == + += 2.2.4 = +* added title position and avatar width options to widgets +* added missing DJ author links as new option to widgets +* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) +* improved to show Encore Presentation in show widget displays +* fix to Show shift Encore Presentation checkbox saving = 2.2.3 = * added flush rewrite rules on plugin activation/deactivation From 244026ffbe7fc7e622239f82125ef36d4237c372 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 12 Aug 2019 10:44:53 -0400 Subject: [PATCH 030/377] Update readme.txt Update the Readm.txt file --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 8bea178..a458c12 100644 --- a/readme.txt +++ b/readme.txt @@ -12,7 +12,7 @@ Radio Station is a plugin to build and manage a Show Calendar in a radio station Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by https://github.com/netmix/radio-station. From 181eac629c1fa6fbd7bd55a7d5ade682c1acfe41 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 12 Aug 2019 11:21:59 -0400 Subject: [PATCH 031/377] Fix type on readme.txt fix typo --- readme.txt | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/readme.txt b/readme.txt index a458c12..2deffcf 100644 --- a/readme.txt +++ b/readme.txt @@ -6,22 +6,28 @@ Requires at least: 3.3.1 Tested up to: 5.2.2 Stable tag: trunk -Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. Functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by https://github.com/netmix/radio-station. +The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -You may also submit bugs and feature requests here: https://github.com/netmix/radio-station/issues +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. + +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. + +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues">https://github.com/netmix/radio-station/issues">https://github.com/netmix/radio-station/issues We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +You can find a demo version of the plugin on our demo site here: https://netmix.co/radio-station-demo + == Installation == 1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. @@ -501,13 +507,13 @@ you send me your finished translation, I'd love to include it. = 1.0 = * Initial release -== Upgrade Notice == - -= 2.2.4 = -* added title position and avatar width options to widgets -* added missing DJ author links as new option to widgets -* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) -* improved to show Encore Presentation in show widget displays +== Upgrade Notice == + += 2.2.4 = +* added title position and avatar width options to widgets +* added missing DJ author links as new option to widgets +* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) +* improved to show Encore Presentation in show widget displays * fix to Show shift Encore Presentation checkbox saving = 2.2.3 = From 3b4d860a0d5d88fc86971d4f78bd891557eaf972 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 12 Aug 2019 11:27:04 -0400 Subject: [PATCH 032/377] Fix Link in read me fix link error --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 2deffcf..844132a 100644 --- a/readme.txt +++ b/readme.txt @@ -20,7 +20,7 @@ We are grateful to Nikki Blight for her contribution to creating and developing If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. -Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues">https://github.com/netmix/radio-station/issues">https://github.com/netmix/radio-station/issues +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. From c430d32ebdecaffc1dd46fc78e3f66ff82b10a14 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 12 Aug 2019 11:35:15 -0400 Subject: [PATCH 033/377] Update readme.txt Add License text Fix Updgrade Notice --- readme.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/readme.txt b/readme.txt index 844132a..9beede2 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,12 @@ === Radio Station === Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate -Tags: dj, music, playlist, radio, scheduling +Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.2.2 Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. Functionality is based on Drupal 6's Station plugin. @@ -510,11 +512,7 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == = 2.2.4 = -* added title position and avatar width options to widgets -* added missing DJ author links as new option to widgets -* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) -* improved to show Encore Presentation in show widget displays -* fix to Show shift Encore Presentation checkbox saving +Adds title position and avatar width options to widgets; missing DJ author links as new option to widgets; cleanup, improve and fix enqueued Widget CSS (on air/upcoming); show Encore Presentation in show widget displays; fix to Show shift Encore Presentation checkbox saving = 2.2.3 = * added flush rewrite rules on plugin activation/deactivation From f3b30806832e385b597e91e43ef8fc04584d13fd Mon Sep 17 00:00:00 2001 From: Mike Garrett Date: Tue, 20 Aug 2019 11:50:33 -0400 Subject: [PATCH 034/377] WordPress coding standards and review --- includes/class-dj-upcoming-widget.php | 455 +++++++ includes/class-dj-widget.php | 528 ++++++++ includes/class-playlist-widget.php | 210 +++ includes/master-schedule.php | 691 ++++++++++ includes/master_schedule.php | 624 --------- includes/post-types.php | 1488 ++++++++++++++++++++++ includes/post_types.php | 1286 ------------------- includes/shortcodes.php | 917 +++++++------ includes/support-functions.php | 557 ++++++++ includes/support_functions.php | 547 -------- includes/widget_djcomingup.php | 354 ----- includes/widget_djonair.php | 412 ------ includes/widget_nowplaying.php | 177 --- phpcs.xml | 5 + radio-station.php | 1179 ++++++++--------- templates/admin-export.php | 169 ++- templates/archive-playlist.php | 162 +-- templates/author.php | 225 ++-- templates/help.php | 6 +- templates/playlist-archive-template.php | 155 +-- templates/show-blog-archive-template.php | 182 ++- templates/single-playlist.php | 155 ++- templates/single-show.php | 297 +++-- 23 files changed, 5763 insertions(+), 5018 deletions(-) create mode 100644 includes/class-dj-upcoming-widget.php create mode 100644 includes/class-dj-widget.php create mode 100644 includes/class-playlist-widget.php create mode 100644 includes/master-schedule.php delete mode 100644 includes/master_schedule.php create mode 100644 includes/post-types.php delete mode 100644 includes/post_types.php create mode 100644 includes/support-functions.php delete mode 100644 includes/support_functions.php delete mode 100644 includes/widget_djcomingup.php delete mode 100644 includes/widget_djonair.php delete mode 100644 includes/widget_nowplaying.php create mode 100644 phpcs.xml diff --git a/includes/class-dj-upcoming-widget.php b/includes/class-dj-upcoming-widget.php new file mode 100644 index 0000000..44d632e --- /dev/null +++ b/includes/class-dj-upcoming-widget.php @@ -0,0 +1,455 @@ + 'DJ_Upcoming_Widget', + 'description' => __( 'The upcoming DJs/Shows.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +

      + +

      + + +

      + +

      + +
      + +

      + + +
      + +
        + + 0 ) ) { + + foreach ( $djs['all'] as $showtime => $dj ) { + + if ( is_array( $dj ) && isset( $dj['type'] ) && 'override' === $dj['type'] ) { + ?> +
      • + +
        + +
        + +
        > + +
        + +
        + +
        + + + +
        + +
        + +
        + +
        + +
      • + +
      • + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + +
        > + ID, 'thumbnail' ); ?> +
        + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + + + +
        + +
        + ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + ?> +
        + ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + ?> + + display_name ); ?> + + display_name ); + } + + $id_count = count( $ids ); + if ( ( 1 === $count && 2 === $id_count ) + || ( $id_count > 2 ) && $count === $id_count - 1 ) { + echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; + } elseif ( $count < $id_count && $id_count > 2 ) { + echo ', '; + } + } + ?> +
        + + + +
        + , - +
        + +
        + , + - +
        + +
      • + +
      • + +
      • + +
      +
      + 'DJ_Widget', + 'description' => __( 'The current on-air DJ.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Show/DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +
      + +

      + +
      + + +
        + +
      • + +
        + +
        + +
        > + +
        + +
        + +
        + + + +
        + +
        + +
        + +
        + +
      • + 0 ) ) { + foreach ( $djs['all'] as $dj ) { + + $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + $current_sched = radio_station_current_schedule( $scheds ); + ?> +
      • + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + +
        > + ID, 'thumbnail' ); ?> +
        + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + + + +
        + +
        + ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + ?> +
        + ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + if ( $link_djs ) { + ?> + + display_name ); ?> + + display_name ); + } + + $id_count = count( $ids ); + if ( ( 1 === $count && 2 === $id_count ) || ( $id_count > 2 && $count === $id_count - 1 ) ) { + echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; + } elseif ( $count < $id_count && $id_count > 2 ) { + echo ', '; + } + } + ?> +
        + post_content ), 20 ); + $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); + ?> +
        + +
        + +
        + + + +
        + + + +
        + +
        + +
        + +
        + +
        + +
        + +
        + +
        + +
      • + +
      • + +
      • + +
      +
      + 'Playlist_Widget', + 'description' => __( 'Display the current song.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Now Playing', 'radio-station' ); + parent::__construct( 'Playlist_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; + $song = isset( $instance['song'] ) ? $instance['song'] : true; + $album = isset( $instance['album'] ) ? $instance['album'] : false; + $label = isset( $instance['label'] ) ? $instance['label'] : false; + $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + + +
      + +
      + +
      + +
      + '12', + 'show_link' => 1, + 'display_show_time' => 1, + 'list' => 'table', + 'show_image' => 0, + 'show_djs' => 0, + 'divheight' => 45, + ), + $atts, + 'master-schedule' + ); + + $timeformat = $atts['time']; + + // $overrides = radio_station_master_get_overrides(true); + + // set up the structure of the master schedule + $default_dj = get_option( 'dj_default_name' ); + + // check to see what day of the week we need to start on + $start_of_week = get_option( 'start_of_week' ); + $days_of_the_week = array( + 'Sunday' => array(), + 'Monday' => array(), + 'Tuesday' => array(), + 'Wednesday' => array(), + 'Thursday' => array(), + 'Friday' => array(), + 'Saturday' => array(), + ); + $week_start = array_slice( $days_of_the_week, $start_of_week ); + + foreach ( $days_of_the_week as $i => $weekday ) { + if ( $start_of_week > 0 ) { + $add = $days_of_the_week[ $i ]; + unset( $days_of_the_week[ $i ] ); + $days_of_the_week[ $i ] = $add; + } + $start_of_week--; + } + + // create the master_list array based on the start of the week + $master_list = array(); + for ( $i = 0; $i < 24; $i++ ) { + $master_list[ $i ] = $days_of_the_week; + } + + // get the show schedules, excluding shows marked as inactive + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value + FROM {$wpdb->prefix}postmeta AS meta + JOIN {$wpdb->prefix}postmeta AS active + ON meta.post_id = active.post_id + JOIN {$wpdb->prefix}posts as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + active.meta_key = 'show_active' AND + active.meta_value = 'on' + )" + ); + + // insert schedules into the master list + foreach ( $show_shifts as $shift ) { + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show is not scheduled yet, unserialize will return false... fix that. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array();} + + foreach ( $shift->meta_value as $time ) { + + // switch to 24-hour time + if ( 'pm' === $time['start_meridian'] && 12 !== $time['start_hour'] ) { + $time['start_hour'] += 12; + } + if ( 'am' === $time['start_meridian'] && 12 === $time['start_hour'] ) { + $time['start_hour'] = 0; + } + + if ( 'pm' === $time['end_meridian'] && 12 !== $time['end_hour'] ) { + $time['end_hour'] += 12; + } + if ( 'am' === $time['end_meridian'] && 12 === $time['end_hour'] ) { + $time['end_hour'] = 0; + } + + // check if we're spanning multiple days + $time['multi-day'] = 0; + if ( $time['start_hour'] > $time['end_hour'] || $time['start_hour'] === $time['end_hour'] ) { + $time['multi-day'] = 1; + } + + $master_list[ $time['start_hour'] ][ $time['day'] ][ $time['start_min'] ] = array( + 'id' => $shift->post_id, + 'time' => $time, + ); + } + } + + // sort the array by time + foreach ( $master_list as $hour => $days ) { + foreach ( $days as $day => $min ) { + ksort( $min ); + $master_list[ $hour ][ $day ] = $min; + + // we need to take into account shows that start late at night and end the following day + foreach ( $min as $i => $time ) { + + // if it ends at midnight, we don't need to worry about carry-over + if ( 0 === (int) $time['time']['end_hour'] && 0 === (int) $time['time']['end_min'] ) { + continue; + } + + // if it ends after midnight, fix it + if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || + //if it starts at night and ends in the morning, end hour is on the following day + ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || + //if the start and end times are identical, assume the end time is the following day + ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day + ) { + + if ( 12 === (int) $timeformat ) { + $time['time']['real_start'] = ( $time['time']['start_hour'] - 12 ) . ':' . $time['time']['start_min']; + } else { + $pad_hour = ''; + if ( $time['time']['start_hour'] < 10 ) { + $pad_hour = '0'; + } + $time['time']['real_start'] = $pad_hour . $time['time']['start_hour'] . ':' . $time['time']['start_min']; + } + // $time['time']['start_hour'] = "0"; + // $time['time']['start_min'] = "00"; + // $time['time']['start_meridian'] = "am"; + $time['time']['rollover'] = 1; + + $nextday = ''; + switch ( $day ) { + case 'Sunday': + $nextday = 'Monday'; + break; + case 'Monday': + $nextday = 'Tuesday'; + break; + case 'Tuesday': + $nextday = 'Wednesday'; + break; + case 'Wednesday': + $nextday = 'Thursday'; + break; + case 'Thursday': + $nextday = 'Friday'; + break; + case 'Friday': + $nextday = 'Saturday'; + break; + case 'Saturday': + $nextday = 'Sunday'; + break; + } + + $master_list[0][ $nextday ]['00'] = $time; + + } + } + } + } + + $output = ''; + + if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { + + // output as a list + $flip = $days_of_the_week; + foreach ( $master_list as $hour => $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } + } + + $output .= '
        '; + + foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
      • '; + $output .= '' . $display_day . ''; + $output .= '
          '; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + $output .= '
        • '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $show['id'] ) ) { + $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + $output .= ''; + + $names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $names ) { + $output .= ' with '; + foreach ( $names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ' '; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } else { + + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $output .= ''; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; + } + + $link = get_post_meta( $show['id'], 'show_file', true ); + if ( $link && ! empty( $link ) ) { + $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
        • '; + } + } + $output .= '
        '; + $output .= '
      • '; + } + + $output .= '
      '; + + } elseif ( 'divs' === $atts['list'] ) { + + // output some dynamic styles + $output .= ''; + + // output the schedule + $output .= radio_station_master_fetch_js_filter(); + $output .= '
      '; + $weekdays = array_keys( $days_of_the_week ); + + $output .= '
      '; + $output .= '
       
      '; + foreach ( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
      ' . $translated . '
      '; + } + $output .= '
      '; + + foreach ( $master_list as $hour => $days ) { + + $output .= '
      '; + + // output the hour labels + $output .= '
      '; + if ( 12 === (int) $timeformat ) { + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format + } + $output .= '
      '; + + foreach ( $weekdays as $weekday ) { + $output .= '
      '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $showdata ) { + + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); + $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; + foreach ( $terms as $term ) { + $classes .= sanitize_title_with_dashes( $term->name ) . ' '; + } + + $output .= '
      '; + + // featured image + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); + } + $output .= ''; + } + + // title + link to page if requested + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $showdata['id'] ) . ''; + } else { + $output .= get_the_title( $showdata['id'] ); + } + $output .= ''; + + // list of DJs + if ( $atts['show_djs'] ) { + + $output .= ''; + + $names = get_post_meta( $showdata['id'], 'show_user_list', true ); + $count = 0; + + if ( $names ) { + + $output .= ' with '; + + foreach ( $names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // show's schedule + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + // designate as encore + if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + // link to media file + $link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $link && ! empty( $link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day + $duration = $showdata['time']['end_hour']; + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { + $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; + } else { + $duration = 23 - $showdata['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
      '; + + if ( '00' !== $showdata['time']['end_min'] ) { + $output .= '
      '; + } + } + + $output .= '
      '; // end master-show-entry + } + } + $output .= '
      '; // end master-schedule-weekday + } + $output .= '
      '; // end master-schedule-hour + } + $output .= '
      '; // end master-schedule-divs + + } else { + + // create the output in a table + $output .= radio_station_master_fetch_js_filter(); + + $output .= ''; + + // output the headings in the correct order + $output .= ''; + foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= ''; + } + $output .= ''; + // $output .= ''; + + if ( ! isset( $nextskip ) ) { + $nextskip = array();} + + foreach ( $master_list as $hour => $days ) { + + $output .= ''; + $output .= ''; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] == $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[ $x ]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $shift ) { + + if ( 0 === $shift['time']['start_hour'] && 0 === $shift['time']['end_hour'] ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === $shift['time']['end_hour'] && 0 !== $shift['time']['start_hour'] ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning + // print_r($shift);die('moo'); + if ( isset( $shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue;} + + $output .= ''; + + foreach ( $min as $shift ) { + + //print_r($shift); + + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); + $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; + foreach ( $terms as $term ) { + $classes .= sanitize_title_with_dashes( $term->name ) . ' '; + } + + $output .= '
      '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $shift['id'] ) ) { + $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $shift['id'] ) . ''; + } else { + $output .= get_the_title( $shift['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $names = get_post_meta( $shift['id'], 'show_user_list', true ); + $count = 0; + + if ( $names ) { + + $output .= ' ' . __( 'with', 'radio-station' ) . ' '; + foreach ( $names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + $link = get_post_meta( $shift['id'], 'show_file', true ); + if ( $link && ! empty( $link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
      '; + } + $output .= ''; + } + + $output .= '
      '; + } + $output .= '
      ' . $heading . '
      '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
      '; + + if ( 12 === (int) $timeformat ) { + if ( 0 === $hour ) { + $output .= '12am'; + } elseif ( $hour < 12 ) { + $output .= $hour . 'am'; + } elseif ( 12 === $hour ) { + $output .= '12pm'; + } else { + $output .= ( $hour - 12 ) . 'pm'; + } + } else { + if ( $hour < 10 ) { + $output .= '0';} + $output .= $hour . ':00'; + } + + $output .= '
      '; + } + + return $output; + +} +add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); + +// --- add javascript for highlighting shows based on genre --- +function radio_station_master_fetch_js_filter() { + + $js = '
      ' . __( 'Genres', 'radio-station' ) . ': '; + + $taxes = get_terms( + 'genres', + array( + 'hide_empty' => true, + 'orderby' => 'name', + 'order' => 'ASC', + ) + ); + foreach ( $taxes as $i => $tax ) { + $js .= '' . $tax->name . ''; + // 2.2.2: fix to not add pipe suffix for last genre + if ( count( $taxes ) - 1 !== $i ) { + $js .= ' | ';} + } + + $js .= '
      '; + + $js .= ''; + + return $js; +} diff --git a/includes/master_schedule.php b/includes/master_schedule.php deleted file mode 100644 index a103a2b..0000000 --- a/includes/master_schedule.php +++ /dev/null @@ -1,624 +0,0 @@ - '12', - 'show_link' => 1, - 'display_show_time' => 1, - 'list' => 'table', - 'show_image' => 0, - 'show_djs' => 0, - 'divheight' => 45 - ), $atts ) ); - - $timeformat = $time; - - // $overrides = radio_station_master_get_overrides(true); - - // set up the structure of the master schedule - $default_dj = get_option( 'dj_default_name' ); - - // check to see what day of the week we need to start on - $start_of_week = get_option( 'start_of_week' ); - $days_of_the_week = array( 'Sunday' => array(), 'Monday' => array(), 'Tuesday' => array(), 'Wednesday' => array(), 'Thursday' => array(), 'Friday' => array(), 'Saturday' => array() ); - $week_start = array_slice( $days_of_the_week, $start_of_week ); - - foreach ( $days_of_the_week as $i => $weekday ) { - if ( $start_of_week > 0 ) { - $add = $days_of_the_week[$i]; - unset( $days_of_the_week[$i] ); - $days_of_the_week[$i] = $add; - } - $start_of_week--; - } - - // create the master_list array based on the start of the week - $master_list = array(); - for ( $i=0; $i<24; $i++ ) {$master_list[$i] = $days_of_the_week;} - - - // get the show schedules, excluding shows marked as inactive - $show_shifts = $wpdb->get_results( "SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `active` ON `meta`.`post_id` = `active`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `active`.`meta_key` = 'show_active' - AND `active`.`meta_value` = 'on');" ); - - // insert schedules into the master list - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show is not scheduled yet, unserialize will return false... fix that. - if ( !is_array($shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // switch to 24-hour time - if($time['start_meridian'] == 'pm' && $time['start_hour'] != 12) { - $time['start_hour'] += 12; - } - if($time['start_meridian'] == 'am' && $time['start_hour'] == 12) { - $time['start_hour'] = 0; - } - - if($time['end_meridian'] == 'pm' && $time['end_hour'] != 12) { - $time['end_hour'] += 12; - } - if($time['end_meridian'] == 'am' && $time['end_hour'] == 12) { - $time['end_hour'] = 0; - } - - // check if we're spanning multiple days - $time['multi-day'] = 0; - if( ($time['start_hour'] > $time['end_hour']) || ($time['start_hour'] == $time['end_hour']) ) { - $time['multi-day'] = 1; - } - - $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array( 'id'=> $shift->post_id, 'time' => $time ); - } - } - - // sort the array by time - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $min ) { - ksort($min); - $master_list[$hour][$day] = $min; - - // we need to take into account shows that start late at night and end the following day - foreach($min as $i => $time) { - - // if it ends at midnight, we don't need to worry about carry-over - if($time['time']['end_hour'] == "0" && $time['time']['end_min'] == "00") { - continue; - } - - // if it ends after midnight, fix it - if( ($time['time']['start_meridian'] == 'pm' && $time['time']['end_meridian'] == 'am') || //if it starts at night and ends in the morning, end hour is on the following day - ($time['time']['start_hour'].$time['time']['start_min'].$time['time']['start_meridian'] == $time['time']['end_hour'].$time['time']['end_min'].$time['time']['end_meridian']) || //if the start and end times are identical, assume the end time is the following day - ($time['time']['start_meridian'] == 'am' && $time['time']['start_hour'] > $time['time']['end_hour']) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day - ) { - - if($timeformat == 12) { - $time['time']['real_start'] = ($time['time']['start_hour']-12).':'.$time['time']['start_min']; - } else { - $pad_hour = ""; - if ( $time['time']['start_hour'] < 10) { - $pad_hour = "0"; - } - $time['time']['real_start'] = $pad_hour.$time['time']['start_hour'].':'.$time['time']['start_min']; - } - // $time['time']['start_hour'] = "0"; - // $time['time']['start_min'] = "00"; - // $time['time']['start_meridian'] = "am"; - $time['time']['rollover'] = 1; - - if ( $day == 'Sunday' ) {$nextday = 'Monday';} - if ( $day == 'Monday' ) {$nextday = 'Tuesday';} - if ( $day == 'Tuesday' ) {$nextday = 'Wednesday';} - if ( $day == 'Wednesday' ) {$nextday = 'Thursday';} - if ( $day == 'Thursday' ) {$nextday = 'Friday';} - if ( $day == 'Friday' ) {$nextday = 'Saturday';} - if ( $day == 'Saturday' ) {$nextday = 'Sunday';} - - $master_list[0][$nextday]['00'] = $time; - - } - } - } - } - - $output = ''; - - if ( ($list == 1) || ($list == 'list') ) { - - // output as a list - $flip = $days_of_the_week; - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $mins ) { - foreach($mins as $fmin => $fshow) { - $flip[$day][$hour][$fmin] = $fshow; - } - } - } - - $output .= '
        '; - - foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
      • '; - $output .= ''.$display_day.''; - $output .= '
          '; - foreach ($hours as $hour => $mins) { - - foreach ($mins as $min => $show ) { - $output .= '
        • '; - - if($show_image) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail($show['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($show['id']).''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $show_djs ) { - $output .= ''; - - $names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $output .= ' with '; - foreach ( $names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - - //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } else { - - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && ( $show['time']['encore'] == 'on' ) ) { - $output .= ' '.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta( $show['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ' '.__('Audio File', 'radio-station').''; - } - - $output .= '
        • '; - } - } - $output .= '
        '; - $output .= '
      • '; - } - - $output .= '
      '; - - } elseif ( $list == 'divs' ) { - - // output some dynamic styles - $output .= ''; - - // output the schedule - $output .= radio_station_master_fetch_js_filter(); - $output .= '
      '; - $weekdays = array_keys($days_of_the_week); - - $output .= '
      '; - $output .= '
       
      '; - foreach( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
      '.$translated.'
      '; - } - $output .= '
      '; - - foreach ($master_list as $hour => $days) { - - $output .= '
      '; - - // output the hour labels - $output .= '
      '; - if ( $timeformat == 12 ) { - $output .= date('ga', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 12-hour format - } else { - $output .= date('H:i', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 24-hour format - } - $output .= '
      '; - - foreach ( $weekdays as $weekday ) { - $output .= '
      '; - if ( isset( $days[$weekday] ) ) { - foreach ( $days[$weekday] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-'.$showdata['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($showdata['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes($term->name).' '; - } - - $output .= '
      '; - - // featured image - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($showdata['id']).''; - } else { - $output .= get_the_title($showdata['id']); - } - $output .= ''; - - // list of DJs - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' with '; - - foreach ( $names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ($count == 1) && ( count($names) == 2) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - // designate as encore - if ( isset($showdata['time']['encore']) && ( $showdata['time']['encore'] == 'on' ) ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - // link to media file - $link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
      '; - - if ( $showdata['time']['end_min'] != '00' ) { - $output .= '
      '; - } - } - - $output .= '
      '; // end master-show-entry - } - } - $output .= '
      '; // end master-schedule-weekday - } - $output .= '
      '; // end master-schedule-hour - } - $output .= '
      '; // end master-schedule-divs - - } else { - - // create the output in a table - $output .= radio_station_master_fetch_js_filter(); - - $output .= ''; - - // output the headings in the correct order - $output .= ''; - foreach($days_of_the_week as $weekday => $info) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= ''; - } - $output .= ''; - // $output .= ''; - - if ( !isset( $nextskip ) ) {$nextskip = array();} - - foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= ''; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] == $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span']-1; - $curskip[$x]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( $shift['time']['start_hour'] == 0 && $shift['time']['end_hour'] == 0 ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] == $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( $shift['time']['end_hour'] == 0 && $shift['time']['start_hour'] != 0 ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + (24 - $shift['time']['start_hour']); - } elseif($shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning - // print_r($shift);die('moo'); - if ( isset($shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ($shift['time']['end_hour'] - $shift['time']['start_hour']); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="'.$rowspan.'"'; - // add to both arrays - $curskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - $nextskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ($continue) {continue;} - - $output .= ''; - - foreach ( $min as $shift ) { - - //print_r($shift); - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-'.$shift['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($shift['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes( $term->name ).' '; - } - - $output .= '
      '; - - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail($shift['id'] ) ) { - $output .= get_the_post_thumbnail($shift['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($shift['id']).''; - } else { - $output .= get_the_title($shift['id']); - } - $output .= ''; - - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' '.__( 'with','radio-station' ).' '; - foreach( $names as $name ) { - $count ++; - $user_info = get_userdata($name); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].' ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - if ( isset($shift['time']['encore']) && $shift['time']['encore'] == 'on' ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta($shift['id'], 'show_file', true); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - $output .= '
      '; - } - $output .= ''; - } - - $output .= '
      '; - } - $output .= '
      '.$heading.'
      '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
      '; - - if ( $timeformat == 12 ) { - if ( $hour == 0 ) {$output .= '12am';} - elseif ( $hour < 12 ) {$output .= $hour.'am';} - elseif ( $hour == 12 ) {$output .= '12pm';} - else {$output .= ($hour-12).'pm';} - } else { - if ( $hour < 10 ) {$output .= "0";} - $output .= $hour.":00"; - } - - $output .= '
      '; - } - - return $output; - -} -add_shortcode( 'master-schedule', 'radio_station_master_schedule'); - -// --- add javascript for highlighting shows based on genre --- -function radio_station_master_fetch_js_filter(){ - - $js = '
      '.__('Genres', 'radio-station').': '; - - $taxes = get_terms( 'genres', array('hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC') ); - foreach ( $taxes as $i => $tax ) { - $js .= ''.$tax->name.''; - // 2.2.2: fix to not add pipe suffix for last genre - if ( $i != ( count($taxes) - 1 ) ) {$js .= ' | ';} - } - - $js .= '
      '; - - $js .= ''; - - return $js; -} diff --git a/includes/post-types.php b/includes/post-types.php new file mode 100644 index 0000000..e0750de --- /dev/null +++ b/includes/post-types.php @@ -0,0 +1,1488 @@ + array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'taxonomies' => array( 'genres' ), + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), + 'can_export' => true, + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // -------- + // Playlist + // -------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; + // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'playlist', + array( + 'labels' => array( + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'comments' ), + 'can_export' => true, + 'has_archive' => 'playlists-archive', + 'rewrite' => array( 'slug' => 'playlists' ), + 'capability_type' => 'playlist', + 'map_meta_cap' => true, + ) + ); + + // ----------------- + // Schedule Override + // ----------------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'override', + array( + 'labels' => array( + 'name' => __( 'Schedule Override', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'thumbnail' ), + 'can_export' => true, + 'rewrite' => array( 'slug' => 'show-override' ), + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // --- maybe trigger flush of rewrite rules --- + if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { + add_action( 'init', 'flush_rewrite_rules', 20 ); + delete_option( 'radio_station_flush_rewrite_rules' ); + } +} + +// --------------------------------------- +// Set Post Type Editing to Classic Editor +// --------------------------------------- +// 2.2.2: added so metabox displays can continue to use wide widths +add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); +add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); +function radio_station_post_type_editor( $can_edit, $post_type ) { + $post_types = array( 'show', 'playlist', 'override' ); + if ( in_array( $post_type, $post_types, true ) ) { + return false;} + return $can_edit; +} + +// -------------------------- +// Add Show Thumbnail Support +// -------------------------- +// --- add featured image support to "show" post type --- +// (this is probably no longer necessary as declared in register_post_type for show) +add_action( 'init', 'radio_station_add_featured_image_support' ); +function radio_station_add_featured_image_support() { + $supported_types = get_theme_support( 'post-thumbnails' ); + + if ( false === $supported_types ) { + add_theme_support( 'post-thumbnails', array( 'show' ) ); + } elseif ( is_array( $supported_types ) ) { + $supported_types[0][] = 'show'; + add_theme_support( 'post-thumbnails', $supported_types[0] ); + } +} + +// ---------------------------- +// Metaboxes Above Content Area +// ---------------------------- +// --- shows plugin metaboxes above editor box for plugin CPTs --- +add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); +function radio_station_top_meta_boxes() { + global $post; + do_meta_boxes( get_current_screen(), 'top', $post ); +} + + +// ------------------ +// === Taxonomies === +// ------------------ + +// ----------------------- +// Register Genre Taxonomy +// ----------------------- +// --- create custom taxonomy for the Show post type --- +add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); +function radio_station_myplaylist_create_show_taxonomy() { + + // --- add taxonomy labels --- + $labels = array( + 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), + 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), + 'search_items' => __( 'Search Genres', 'radio-station' ), + 'all_items' => __( 'All Genres', 'radio-station' ), + 'parent_item' => __( 'Parent Genre', 'radio-station' ), + 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), + 'edit_item' => __( 'Edit Genre', 'radio-station' ), + 'update_item' => __( 'Update Genre', 'radio-station' ), + 'add_new_item' => __( 'Add New Genre', 'radio-station' ), + 'new_item_name' => __( 'New Genre Name', 'radio-station' ), + 'menu_name' => __( 'Genre', 'radio-station' ), + ); + + // --- register the genre taxonomy --- + // 2.2.3: added show_admin_column and show_in_quick_edit arguments + register_taxonomy( + 'genres', + array( 'show' ), + array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => false, + 'query_var' => true, + 'rewrite' => array( 'slug' => 'genre' ), + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows', + ), + ) + ); + +} + +// ---------------------------- +// Shift Genre Metabox on Shows +// ---------------------------- +// --- moves genre metabox above publish box --- +add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); +function radio_station_genre_meta_box_order() { + global $wp_meta_boxes; + $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; + unset( $wp_meta_boxes['show']['side']['core']['genresdiv'] ); + $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; +} + + +// ----------------- +// === Playlists === +// ----------------- + +// ------------------------- +// Add Playlist Data Metabox +// ------------------------- +// --- Add custom repeating meta field for the playlist edit form --- +// (Stores multiple associated values as a serialized string) +// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); +function radio_station_myplaylist_add_custom_box() { + // 2.2.2: change context to show at top of edit screen + add_meta_box( + 'dynamic_sectionid', + __( 'Playlist Entries', 'radio-station' ), + 'radio_station_myplaylist_inner_custom_box', + 'playlist', + 'top', // shift to top + 'high' + ); +} + +// --------------------- +// Playlist Data Metabox +// --------------------- +// -- prints the playlist entry box to the main column on the edit screen --- +function radio_station_myplaylist_inner_custom_box() { + + global $post; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); + ?> +
      + ID, 'playlist', false ); + // print_r($entries); + $c = 1; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + // echo ""; + // echo ""; + // echo ""; + // echo ""; + echo ''; + + if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { + + foreach ( $entries[0] as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + + echo ''; + + echo ''; + + echo ''; + echo ''; + $c++; + } + } + } + echo '
      ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
      ' . esc_html( $c ) . '
      ' . esc_html__( 'Comments', 'radio-station' ) . ' '; + echo '' . esc_html__( 'New', 'radio-station' ) . ' '; + echo ''; + + echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; + echo '' . esc_html__( 'Remove', 'radio-station' ) . '
      '; + + ?> + +
      + +
      + +
      +

      + post_status, array( 'publish', 'future', 'private' ), true ) || 0 === $post->ID ) { + if ( $can_publish ) : + if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : + ?> + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + + +
      + + $song ) { + if ( 'queued' === $song['playlist_entry_status'] ) { + $playlist[] = $song; + unset( $playlist[ $i ] ); + } + } + update_post_meta( $post_id, 'playlist', $playlist ); + + // sanitize and save show ID + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + } else { + $show = absint( $show ); + if ( $show > 0 ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + } + } + } + +} + + +// ------------- +// === Shows === +// ------------- + +// ------------------------ +// Add Related Show Metabox +// ------------------------ +// --- Add custom meta box for show assignment on blog posts --- +add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); +function radio_station_add_showblog_box() { + + // --- make sure a show exists before adding metabox --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + + if ( count( $shows ) > 0 ) { + + // ---- add a filter for which post types to show metabox on --- + // TODO: add this filter to plugin documentation + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + + // --- add the metabox to post types --- + add_meta_box( + 'dynamicShowBlog_sectionid', + __( 'Related to Show', 'radio-station' ), + 'radio_station_inner_showblog_custom_box', + $post_types, + 'side' + ); + } +} + +// -------------------- +// Related Show Metabox +// -------------------- +// --- Prints the box content for the Show field --- +function radio_station_inner_showblog_custom_box() { + global $post; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + $current = get_post_meta( $post->ID, 'post_showblog_id', true ); + + ?> +
      + + +
      + 0 ) { + update_post_meta( $post_id, 'post_showblog_id', $show );} + } + } + +} + + +// ----------------------------------- +// Add Assign Playlist to Show Metabox +// ----------------------------------- +// --- Add custom meta box for show assigment --- +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); +function radio_station_myplaylist_add_show_box() { + // 2.2.2: add high priority to shift above publish box + add_meta_box( + 'dynamicShow_sectionid', + __( 'Show', 'radio-station' ), + 'radio_station_myplaylist_inner_show_custom_box', + 'playlist', + 'side', + 'high' + ); +} + +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- +// --- Prints the box content for the Show field --- +function radio_station_myplaylist_inner_show_custom_box() { + + global $post, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); + + $user = wp_get_current_user(); + + // --- allow administrators to do whatever they want --- + if ( ! in_array( 'administrator', $user->roles, true ) ) { + + // --- get the user lists for all shows --- + $allowed_shows = array(); + + $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + + // ---- check each list for the current user --- + foreach ( $show_user_lists as $list ) { + + $list->meta_value = maybe_unserialize( $list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( ! is_array( $list->meta_value ) ) { + $list->meta_value = array();} + + // --- only include shows the user is assigned to --- + foreach ( $list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $list->post_id; + } + } + } + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); + + $shows = get_posts( $args ); + + } else { + + // --- for if you are an administrator --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + + $shows = get_posts( $args ); + } + + ?> +
      + + +
      + ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + + // added max-width to prevent metabox overflows + ?> +
      + +

      + /> +

      + +


      +

      + +


      +

      + +


      +

      + +
      + roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'edit_shows' === $capname && $capstatus ) { + $add_roles[] = $name; + } + } + } + $add_roles = array_unique( $add_roles ); + + // ---- create the meta query for get_users() --- + $meta_query = array( 'relation' => 'OR' ); + foreach ( $add_roles as $role ) { + $meta_query[] = array( + 'key' => $wpdb->prefix . 'capabilities', + 'value' => $role, + 'compare' => 'like', + ); + } + + // --- get all eligible users --- + $args = array( + 'meta_query' => $meta_query, + 'orderby' => 'display_name', + 'order' => 'ASC', + //' fields' => array( 'ID, display_name' ), + ); + $users = get_users( $args ); + + // --- get the DJs currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( ! $current ) { + $current = array();} + + // --- move any selected DJs to the top of the list --- + foreach ( $users as $i => $dj ) { + if ( in_array( $dj->ID, $current, true ) ) { + unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $users, $dj ); // prepend the user to the beginning of the array + } + } + + // 2.2.2: add fix to make DJ multi-select input full metabox width + ?> +
      + + +
      + +
      + ID, 'show_sched', false ); + // print_r($shifts); + + $c = 0; + if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { + foreach ( $shifts[0] as $track ) { + if ( isset( $track['day'] ) || isset( $track['time'] ) ) { + ?> +
        + +
      • + : + +
      • + +
      • + : + + + +
      • + +
      • + : + + + +
      • + +
      • />
      • + +
      • + +
      + + + + +
      + $dj ) { + if ( ! empty( $dj ) ) { + $userid = get_user_by( 'id', $dj ); + if ( ! $userid ) { + unset( $djs[ $i ] );} + } + } + } + $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); + $email = sanitize_email( trim( $_POST['show_email'] ) ); + $active = $_POST['show_active']; + if ( ! in_array( $active, array( '', 'on' ), true ) ) { + $active = '';} + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + + // --- update the show metadata --- + update_post_meta( $post_id, 'show_user_list', $djs ); + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + + // --- update the show shift metadata + $scheds = $_POST['show_sched']; + + // --- sanitize the show shift times --- + $new_scheds = array(); + $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + foreach ( $scheds as $i => $sched ) { + foreach ( $sched as $key => $value ) { + + // --- validate according to key --- + $isvalid = false; + if ( 'day' === $key ) { + if ( in_array( $value, $days, true ) ) { + $isvalid = true;} + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } elseif ( 'encore' === $key ) { + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } + + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_scheds[ $i ][ $key ] = $value; + } else { + $new_scheds[ $i ][ $key ] = ''; + } + } + } + + update_post_meta( $post_id, 'show_sched', $new_scheds ); + +} + + +// -------------------------- +// === Schedule Overrides === +// -------------------------- + +// ----------------------------- +// Add Schedule Override Metabox +// ----------------------------- +// --- Add schedule override box to override edit screens --- +add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); +function radio_station_master_override_add_sched_box() { + // 2.2.2: add high priority to show at top of edit screen + add_meta_box( + 'dynamicSchedOver_sectionid', + __( 'Override Schedule', 'radio-station' ), + 'radio_station_master_override_inner_sched_custom_box', + 'override', + 'normal', + 'high' + ); +} + +// ------------------------- +// Schedule Override Metabox +// ------------------------- +function radio_station_master_override_inner_sched_custom_box() { + + global $post; + + // --- add nonce field for update verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); + ?> +
      + + ID, 'show_override_sched', false ); + if ( $override ) { + $override = $override[0];} + ?> + + +
        +
      • + : + +
      • + +
      • + : + + + +
      • + +
      • + : + + + +
      • +
      +
      + '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + ); + } + + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $changed = false; + foreach ( $sched as $key => $value ) { + $isvalid = false; + + // --- validate according to key --- + if ( 'date' === $key ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { + $isvalid = true;} + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + // 2.2.3: fix to validate 00 minute value + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } + + // --- if valid add to current schedule setting --- + if ( $isvalid && $value !== $current_sched[ $key ] ) { + $current_sched[ $key ] = $value; + $changed = true; + } + } + + // --- save schedule setting if changed --- + if ( $changed ) { + update_post_meta( $post_id, 'show_override_sched', $current_sched );} +} diff --git a/includes/post_types.php b/includes/post_types.php deleted file mode 100644 index 91022b1..0000000 --- a/includes/post_types.php +++ /dev/null @@ -1,1286 +0,0 @@ - array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Show descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'taxonomies' => array( 'genres' ), - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // -------- - // Playlist - // -------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; - // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'playlist', - array( - 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Playlist descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'comments' ), - 'can_export' => true, - 'has_archive' => 'playlists-archive', - 'rewrite' => array( 'slug' => 'playlists' ), - 'capability_type' => 'playlist', - 'map_meta_cap' => true - ) - ); - - // ----------------- - // Schedule Override - // ----------------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'override', - array( - 'labels' => array( - 'name' => __( 'Schedule Override', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Schedule Override', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'thumbnail' ), - 'can_export' => true, - 'rewrite' => array('slug' => 'show-override'), - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // --- maybe trigger flush of rewrite rules --- - if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { - add_action( 'init', 'flush_rewrite_rules', 20 ); - delete_option( 'radio_station_flush_rewrite_rules' ); - } -} - -// --------------------------------------- -// Set Post Type Editing to Classic Editor -// --------------------------------------- -// 2.2.2: added so metabox displays can continue to use wide widths -add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); -add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); -function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( 'show', 'playlist', 'override' ); - if ( in_array( $post_type, $post_types ) ) {return false;} - return $can_edit; -} - -// -------------------------- -// Add Show Thumbnail Support -// -------------------------- -// --- add featured image support to "show" post type --- -// (this is probably no longer necessary as declared in register_post_type for show) -add_action( 'init', 'radio_station_add_featured_image_support' ); -function radio_station_add_featured_image_support() { - $supported_types = get_theme_support( 'post-thumbnails' ); - - if ( $supported_types === false ) { - add_theme_support( 'post-thumbnails', array( 'show' ) ); - } elseif ( is_array( $supported_types ) ) { - $supported_types[0][] = 'show'; - add_theme_support( 'post-thumbnails', $supported_types[0] ); - } -} - -// ---------------------------- -// Metaboxes Above Content Area -// ---------------------------- -// --- shows plugin metaboxes above editor box for plugin CPTs --- -add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); -function radio_station_top_meta_boxes() { - global $post; do_meta_boxes( get_current_screen(), 'top', $post ); -} - - -// ------------------ -// === Taxonomies === -// ------------------ - -// ----------------------- -// Register Genre Taxonomy -// ----------------------- -// --- create custom taxonomy for the Show post type --- -add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); -function radio_station_myplaylist_create_show_taxonomy() { - - // --- add taxonomy labels --- - $labels = array( - 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), - 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), - 'search_items' => __( 'Search Genres', 'radio-station' ), - 'all_items' => __( 'All Genres', 'radio-station' ), - 'parent_item' => __( 'Parent Genre', 'radio-station' ), - 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), - 'edit_item' => __( 'Edit Genre', 'radio-station' ), - 'update_item' => __( 'Update Genre', 'radio-station' ), - 'add_new_item' => __( 'Add New Genre', 'radio-station' ), - 'new_item_name' => __( 'New Genre Name', 'radio-station' ), - 'menu_name' => __( 'Genre', 'radio-station' ), - ); - - // --- register the genre taxonomy --- - // 2.2.3: added show_admin_column and show_in_quick_edit arguments - register_taxonomy( 'genres', array( 'show' ), - array( - 'hierarchical' => true, - 'labels' => $labels, - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array('slug' => 'genre'), - 'show_admin_column' => true, - 'show_in_quick_edit' => true, - 'capabilities' => array( - 'manage_terms' => 'edit_shows', - 'edit_terms' => 'edit_shows', - 'delete_terms' => 'edit_shows', - 'assign_terms' => 'edit_shows' - ), - ) - ); - -} - -// ---------------------------- -// Shift Genre Metabox on Shows -// ---------------------------- -// --- moves genre metabox above publish box --- -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); -function radio_station_genre_meta_box_order() { - global $wp_meta_boxes; - $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; - unset($wp_meta_boxes['show']['side']['core']['genresdiv']); - $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; -} - - -// ----------------- -// === Playlists === -// ----------------- - -// ------------------------- -// Add Playlist Data Metabox -// ------------------------- -// --- Add custom repeating meta field for the playlist edit form --- -// (Stores multiple associated values as a serialized string) -// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -function radio_station_myplaylist_add_custom_box() { - // 2.2.2: change context to show at top of edit screen - add_meta_box( - 'dynamic_sectionid', - __( 'Playlist Entries', 'radio-station' ), - 'radio_station_myplaylist_inner_custom_box', - 'playlist', - 'top', // shift to top - 'high' - ); -} - -// --------------------- -// Playlist Data Metabox -// --------------------- -// -- prints the playlist entry box to the main column on the edit screen --- -function radio_station_myplaylist_inner_custom_box() { - - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); - ?> -
      - ID, 'playlist', false ); - // print_r($entries); - $c = 1; - - echo ''; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - // echo ""; - // echo ""; - // echo ""; - // echo ""; - echo ""; - - if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { - - foreach( $entries[0] as $track ){ - if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - - echo ''; - - echo ''; - - if ( isset( $track['playlist_entry_new'] ) && $track['playlist_entry_new'] ) {$checked = ' checked="checked"';} else {$checked = '';} - - echo ''; - - echo ''; - echo ''; - $c++; - } - } - } - echo '
      ".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
      '.$c.'
      '.__('Comments', 'radio-station').' '; - echo ''.__( 'New', 'radio-station' ).' '; - echo ''; - - echo ' '.__( 'Status', 'radio-station').' '; - echo ''.__('Remove', 'radio-station').'
      '; - - ?> - -
      - -
      - -
      -

      - post_status, array('publish', 'future', 'private') ) || 0 == $post->ID ) { - if ( $can_publish ) : - if ( !empty($post->post_date_gmt) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : ?> - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - - -
      - - $song ) { - if ( $song['playlist_entry_status'] == 'queued' ) { - $playlist[] = $song; - unset($playlist[$i]); - } - } - update_post_meta($post_id, 'playlist', $playlist); - - // sanitize and save show ID - $show = $_POST['playlist_show_id']; - if ( $show == '') { - delete_post_meta( $post_id, 'playlist_show_id' ); - } else { - $show = absint( $show ); - if ( $show > 0 ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); - } - } - } - -} - - -// ------------- -// === Shows === -// ------------- - -// ------------------------ -// Add Related Show Metabox -// ------------------------ -// --- Add custom meta box for show assignment on blog posts --- -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -function radio_station_add_showblog_box() { - - // --- make sure a show exists before adding metabox --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - - if ( count( $shows ) > 0 ) { - - // ---- add a filter for which post types to show metabox on --- - // TODO: add this filter to plugin documentation - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - - // --- add the metabox to post types --- - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'radio_station_inner_showblog_custom_box', - $post_types, - 'side' - ); - } -} - -// -------------------- -// Related Show Metabox -// -------------------- -// --- Prints the box content for the Show field --- -function radio_station_inner_showblog_custom_box() { - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - $current = get_post_meta( $post->ID, 'post_showblog_id', true); - - ?> -
      - - -
      - 0 ) {update_post_meta( $post_id, 'post_showblog_id', $show );} - } - } - -} - - -// ----------------------------------- -// Add Assign Playlist to Show Metabox -// ----------------------------------- -// --- Add custom meta box for show assigment --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); -function radio_station_myplaylist_add_show_box() { - // 2.2.2: add high priority to shift above publish box - add_meta_box( - 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'radio_station_myplaylist_inner_show_custom_box', - 'playlist', - 'side', - 'high' - ); -} - -// ------------------------------- -// Assign Playlist to Show Metabox -// ------------------------------- -// --- Prints the box content for the Show field --- -function radio_station_myplaylist_inner_show_custom_box() { - - global $post, $wpdb; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); - - $user = wp_get_current_user(); - - // --- allow administrators to do whatever they want --- - if ( !in_array('administrator', $user->roles ) ) { - - // --- get the user lists for all shows --- - $allowed_shows = array(); - - $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); - - // ---- check each list for the current user --- - foreach($show_user_lists as $list) { - - $list->meta_value = unserialize( $list->meta_value ); - - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( !is_array( $list->meta_value ) ) {$list->meta_value = array();} - - // --- only include shows the user is assigned to --- - foreach ( $list->meta_value as $user_id ) { - if ( $user->ID == $user_id ) { - $allowed_shows[] = $list->post_id; - } - } - } - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ) - ); - - $shows = get_posts( $args ); - - } else { - - // --- for if you are an administrator --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - - $shows = get_posts( $args ); - } - - ?> -
      - - -
      - ID, 'show_file', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); - - // added max-width to prevent metabox overflows - ?> -
      - -

      - /> -

      - -


      -

      - -


      -

      - -


      -

      - -
      - roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( ( $capname == 'edit_shows' ) && ( ( $capstatus == 1 ) || ( $capstatus == true ) ) ) { - $add_roles[] = $name; - } - } - } - $add_roles = array_unique( $add_roles ); - - // ---- create the meta query for get_users() --- - $meta_query = array( 'relation' => 'OR' ); - foreach ( $add_roles as $role ) { - $meta_query[] = array( - 'key' => $wpdb->prefix.'capabilities', - 'value' => $role, - 'compare' => 'like' - ); - } - - // --- get all eligible users --- - $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //' fields' => array( 'ID, display_name' ), - ); - $users = get_users( $args ); - - // --- get the DJs currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$current ) {$current = array();} - - // --- move any selected DJs to the top of the list --- - foreach ( $users as $i => $dj ) { - if ( in_array( $dj->ID, $current ) ) { - unset( $users[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $users, $dj ); // prepend the user to the beginning of the array - } - } - - // 2.2.2: add fix to make DJ multi-select input full metabox width - ?> -
      - - -
      - -
      - ID, 'show_sched', false); - // print_r($shifts); - - $c = 0; - if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { - foreach ( $shifts[0] as $track ){ - if ( isset( $track['day'] ) || isset( $track['time'] ) ){ - ?> -
        - -
      • - : - -
      • - -
      • - : - - - -
      • - -
      • - : - - - -
      • - -
      • />
      • - -
      • - -
      - - -
      - -
      - $dj ) { - if ( $dj != '' ) { - $userid = get_user_by( 'id', $dj ); - if ( !$userid ) {unset( $djs[$i] );} - } - } - } - $file = strip_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; - if ( !in_array( $active, array( '', 'on') ) ) {$active = '';} - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - - // --- update the show metadata --- - update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - - // --- update the show shift metadata - $scheds = $_POST['show_sched']; - - // --- sanitize the show shift times --- - $new_scheds = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'); - foreach ( $scheds as $i => $sched ) { - foreach ( $sched as $key => $value ) { - - // --- validate according to key --- - $isvalid = false; - if ( $key == 'day' ) { - if ( in_array( $value, $days ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } elseif ($key == 'encore') { - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on'); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to new schedule --- - if ( $isvalid ) {$new_scheds[$i][$key] = $value;} else {$new_scheds[$i][$key] = '';} - } - } - - update_post_meta( $post_id, 'show_sched', $new_scheds ); - -} - - -// -------------------------- -// === Schedule Overrides === -// -------------------------- - -// ----------------------------- -// Add Schedule Override Metabox -// ----------------------------- -// --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); -function radio_station_master_override_add_sched_box() { - // 2.2.2: add high priority to show at top of edit screen - add_meta_box( - 'dynamicSchedOver_sectionid', - __( 'Override Schedule', 'radio-station' ), - 'radio_station_master_override_inner_sched_custom_box', - 'override', - 'normal', - 'high' - ); -} - -// ------------------------- -// Schedule Override Metabox -// ------------------------- -function radio_station_master_override_inner_sched_custom_box() { - - global $post; - - // --- add nonce field for update verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); - ?> -
      - - ID, 'show_override_sched', false); - if ($override) {$override = $override[0];} - ?> - - -
        -
      • - : - -
      • - -
      • - : - - - -
      • - -
      • - : - - - -
      • -
      -
      - '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - ); - } - - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $changed = false; - foreach ( $sched as $key => $value ) { - $isvalid = false; - - // --- validate according to key --- - if ( $key == 'date' ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - // 2.2.3: fix to validate 00 minute value - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to current schedule setting --- - if ( $isvalid && ( $value != $current_sched[$key] ) ) { - $current_sched[$key] = $value; $changed = true; - } - } - - // --- save schedule setting if changed --- - if ($changed) {update_post_meta( $post_id, 'show_override_sched', $current_sched );} -} - diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 93fec06..19e298d 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1,424 +1,493 @@ - '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0 - ), $atts ) ); - - $most_recent = radio_station_myplaylist_get_now_playing(); - $output = ''; - - if ( $most_recent ) { - $class = ''; - if ( isset( $most_recent['playlist_entry_new'] ) && ( $most_recent['playlist_entry_new'] == 'on') ) { - $class = ' class="new"'; - } - - $output .= '
      '; - if ($title != '') {$output .= '

      '.$title.'

      ';} - - if ( $song == 1 ) { - $output .= ''.$most_recent['playlist_entry_song'].' '; - } - if ( $artist == 1 ) { - $output .= ''.$most_recent['playlist_entry_artist'].' '; - } - if ( $album == 1 ) { - $output .= ''.$most_recent['playlist_entry_album'].' '; - } - if ( $label == 1 ) { - $output .= ''.$most_recent['playlist_entry_label'].' '; - } - if ( $comments == 1 ) { - $output .= ''.$most_recent['playlist_entry_comments'].' '; - } - $output .= ''.__('View Playlist', 'radio-station').' '; - $output .= '
      '; - - } else { - echo 'No playlists available.'; - } - - return $output; -} -add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); - - -/* Shortcode to fetch all playlists for a given show id - * Since 2.0.0 - */ -function radio_station_shortcode_get_playlists_for_show($atts) { - - extract( shortcode_atts( array( - 'show' => '', - 'limit' => -1 - ), $atts ) ); - - // don't return anything if we do not have a show - if ( $show == '' ) {return false;} - - $args = array( - 'numberposts' => $limit, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $show - ); - - $playlists = get_posts( $args ); - - $output = ''; - - $output .= ''; - - return $output; -} -add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); - -/* Shortcode for displaying a list of all shows - * Since 2.0.0 - */ -function radio_station_shortcode_list_shows($atts) { - - extract( shortcode_atts( array( - 'genre' => '' - ), $atts ) ); - - // grab the published shows - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'meta_query' => array( - array( - 'key' => 'show_active', - 'value' => 'on', - ) - ) - ); - - if ( $genre != '' ) {$args['genres'] = $genre;} - - $shows = get_posts( $args ); - - // if there are no shows saved, return nothing - if ( !$shows ) {return false;} - - $output = ''; - - $output .= '
      '; - $output .= ''; - $output .= '
      '; - return $output; -} -add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); - -/* Shortcode function for current DJ on-air - * Since 2.0.9 - */ -function radio_station_shortcode_dj_on_air($atts) { - extract( shortcode_atts( array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'default_name' => '', - 'time' => '12', - 'show_sched' => 1, - 'show_playlist' => 1, - 'show_all_sched' => 0, - 'show_desc' => 0 - ), $atts ) ); - - // find out which DJ(s) are currently scheduled to be on-air and display them - $djs = radio_station_dj_get_current(); - $playlist = radio_station_myplaylist_get_now_playing(); - - $dj_str = ''; - - $dj_str .= '
      '; - if ( $title != '' ) {$dj_str .= '

      '.$title.'

      ';} - $dj_str .= '
        '; - - // echo the show/dj currently on-air - if ( $djs['type'] == 'override' ) { - - $dj_str .= '
      • '; - if ( $show_avatar ) { - if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail($djs['all'][0]['post_id'], 'thumbnail').''; - } - } - - $dj_str .= $djs['all'][0]['title']; - - // display the override's schedule if requested - if ( $show_sched ) { - - if ( $time == 12 ) { - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
        '; - } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
        '; - } - - $dj_str .= '
      • '; - } - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach ( $djs['all'] as $dj ) { - - $dj_str .= '
      • '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - - $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $dj_str .= '
        '.__( 'With','radio-station' ).' '; - foreach( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2) ) { - $dj_str .= ', '; - } - } - $dj_str .= '
        '; - } - } - - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $dj_str .= ''.$desc_string.''; - } - - if ( $show_playlist ) { - $dj_str .= ''.__('View Playlist', 'radio-station').''; - } - - $dj_str .= ''; - - if ( $show_sched ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - - // if we only want the schedule that's relevant now to display... - if ( !$show_all_sched ) { - - $current_sched = radio_station_current_schedule( $scheds ); - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
        '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour($current_sched); - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
        '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
        '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
        '; - } - } - } - } - - $dj_str .= '
      • '; - } - } else { - $dj_str .= '
      • '.$default_name.'
      • '; - } - } - - $dj_str .= '
      '; - $dj_str .= '
      '; - - return $dj_str; - -} -add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air'); - -/* Shortcode for displaying upcoming DJs/shows - * Since 2.0.9 -*/ -function radio_station_shortcode_coming_up( $atts ) { - - extract( shortcode_atts( array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'limit' => 1, - 'time' => '12', - 'show_sched' => 1 - ), $atts ) ); - - // find out which DJ(s) are coming up today - $djs = radio_station_dj_get_next($limit); - // print_r($djs); - - $now = strtotime( current_time( 'mysql' )); - $curDate = date( 'Y-m-d', $now ); - - $dj_str = ''; - - $dj_str .= '
      '; - if ( $title != '' ) {$dj_str .= '

      '.$title.'

      ';} - $dj_str .= '
        '; - - // echo the show/dj coming up - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && ( $dj['type'] == 'override') ) { - - echo '
      • '; - - if ( $show_avatar ) { - if ( has_post_thumbnail( $dj['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).''; - } - } - - echo $dj['title']; - if( $show_sched ) { - - if ( $time == 12 ) { - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
        '; - } else { - $dj['sched'] = radio_station_convert_schedule_to_24hour($dj['sched']); - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
        '; - - } - } - echo '
      • '; - - } else { - - $dj_str .= '
      • '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - - $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $dj_str .= '
        With '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - if( ( ( $count == 1 ) && ( count($names) == 2) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $dj_str .= ', '; - } - } - $dj_str .= '
        '; - } - } - - $dj_str .= ''; - - if ( $show_sched ) { - $showtimes = explode( '|', $showtime ); - if ( $time == 12 ) { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('g:i a', $showtimes[0]).'-'.date('g:i a', $showtimes[1]).'
        '; - } else { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('H:i', $showtimes[0]).'-'.date('H:i', $showtimes[1]).'
        '; - } - } - - $dj_str .= '
      • '; - } - } - } else { - $dj_str .= '
      • '.__('None Upcoming', 'radio-station').'
      • '; - } - - $dj_str .= '
      '; - $dj_str .= '
      '; - - return $dj_str; - -} -add_shortcode( 'dj-coming-up-widget', 'radio_station_shortcode_coming_up' ); + '', + 'artist' => 1, + 'song' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, + ), + $atts, + 'now-playing' + ); + + $most_recent = radio_station_myplaylist_get_now_playing(); + $output = ''; + + if ( $most_recent ) { + $class = ''; + if ( isset( $most_recent['playlist_entry_new'] ) && 'on' === $most_recent['playlist_entry_new'] ) { + $class = 'new'; + } + + $output .= '
      '; + if ( ! empty( $atts['title'] ) ) { + $output .= '

      ' . $atts['title'] . '

      ';} + + if ( 1 === $atts['song'] ) { + $output .= '' . $most_recent['playlist_entry_song'] . ' '; + } + if ( 1 === $atts['artist'] ) { + $output .= '' . $most_recent['playlist_entry_artist'] . ' '; + } + if ( 1 === $atts['album'] ) { + $output .= '' . $most_recent['playlist_entry_album'] . ' '; + } + if ( 1 === $atts['label'] ) { + $output .= '' . $most_recent['playlist_entry_label'] . ' '; + } + if ( 1 === $atts['comments'] ) { + $output .= '' . $most_recent['playlist_entry_comments'] . ' '; + } + $output .= '' . __( 'View Playlist', 'radio-station' ) . ' '; + $output .= '
      '; + + } else { + echo 'No playlists available.'; + } + + return $output; +} +add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); + + +/* Shortcode to fetch all playlists for a given show id + * Since 2.0.0 + */ +function radio_station_shortcode_get_playlists_for_show( $atts ) { + + $atts = shortcode_atts( + array( + 'show' => '', + 'limit' => -1, + ), + $atts, + 'get-playlists' + ); + + // don't return anything if we do not have a show + if ( empty( $atts['show'] ) ) { + return false; + } + + $args = array( + 'numberposts' => $atts['limit'], + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + 'meta_key' => 'playlist_show_id', + 'meta_value' => $atts['show'], + ); + + $query = new WP_Query( $args ); + $playlists = $query->posts; + + $output = ''; + + $output .= ''; + + return $output; +} +add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); + +/* Shortcode for displaying a list of all shows + * Since 2.0.0 + */ +function radio_station_shortcode_list_shows( $atts ) { + + $atts = shortcode_atts( + array( + 'genre' => '', + ), + $atts, + 'list-shows' + ); + + // grab the published shows + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'meta_query' => array( + array( + 'key' => 'show_active', + 'value' => 'on', + ), + ), + ); + + if ( ! empty( $atts['genre'] ) ) { + $args['genres'] = $atts['genre'];} + + $query = new WP_Query( $args ); + + // if there are no shows saved, return nothing + if ( $query->found_posts <= 0 ) { + return false; + } + + $output = ''; + + $output .= '
      '; + $output .= ''; + $output .= '
      '; + return $output; +} +add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); + +/* Shortcode function for current DJ on-air + * Since 2.0.9 + */ +function radio_station_shortcode_dj_on_air( $atts ) { + $atts = shortcode_atts( + array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'default_name' => '', + 'time' => '12', + 'show_sched' => 1, + 'show_playlist' => 1, + 'show_all_sched' => 0, + 'show_desc' => 0, + ), + $atts, + 'dj-widget' + ); + + // find out which DJ(s) are currently scheduled to be on-air and display them + $djs = radio_station_dj_get_current(); + $playlist = radio_station_myplaylist_get_now_playing(); + + $dj_str = ''; + + $dj_str .= '
      '; + if ( ! empty( $atts['title'] ) ) { + $dj_str .= '

      ' . $atts['title'] . '

      '; + } + $dj_str .= '
        '; + + // echo the show/dj currently on-air + if ( 'override' === $djs['type'] ) { + + $dj_str .= '
      • '; + if ( $atts['show_avatar'] ) { + if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { + $dj_str .= '' . get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ) . ''; + } + } + + $dj_str .= $djs['all'][0]['title']; + + // display the override's schedule if requested + if ( $atts['show_sched'] ) { + + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' ' . $djs['all'][0]['sched']['start_meridian'] . '-' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . ' ' . $djs['all'][0]['sched']['end_meridian'] . '
        '; + } else { + $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour( $djs['all'][0]['sched'] ); + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' -' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . '
        '; + } + + $dj_str .= '
      • '; + } + } else { + + if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { + foreach ( $djs['all'] as $dj ) { + + $dj_str .= '
      • '; + if ( $atts['show_avatar'] ) { + $dj_str .= '' . get_the_post_thumbnail( $dj->ID, 'thumbnail' ) . ''; + } + + $dj_str .= ''; + if ( $atts['show_link'] ) { + $dj_str .= '' . $dj->post_title . ''; + } else { + $dj_str .= $dj->post_title; + } + $dj_str .= ''; + + if ( $atts['display_djs'] ) { + + $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; + + if ( $names ) { + + $dj_str .= '
        ' . __( 'With', 'radio-station' ) . ' '; + foreach ( $names as $name ) { + $count++; + $user_info = get_userdata( $name ); + + $dj_str .= $user_info->display_name; + + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { + $dj_str .= ' and '; + } elseif ( $count < $count_names && $count_names > 2 ) { + $dj_str .= ', '; + } + } + $dj_str .= '
        '; + } + } + + if ( $atts['show_desc'] ) { + $desc_string = radio_station_shorten_string( wp_strip_all_tags( $dj->post_content ), 20 ); + $dj_str .= '' . $desc_string . ''; + } + + if ( $atts['show_playlist'] ) { + $dj_str .= '' . __( 'View Playlist', 'radio-station' ) . ''; + } + + $dj_str .= ''; + + if ( $atts['show_sched'] ) { + + $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + + // if we only want the schedule that's relevant now to display... + if ( ! $atts['show_all_sched'] ) { + + $current_sched = radio_station_current_schedule( $scheds ); + + if ( $current_sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $current_sched['day'] ); + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' ' . $current_sched['start_meridian'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . ' ' . $current_sched['end_meridian'] . '
        '; + } else { + $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . '
        '; + } + } + } else { + + foreach ( $scheds as $sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $sched['day'] ); + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] . '
        '; + } else { + $sched = radio_station_convert_schedule_to_24hour( $sched ); + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . '
        '; + } + } + } + } + + $dj_str .= '
      • '; + } + } else { + $dj_str .= '
      • ' . $atts['default_name'] . '
      • '; + } + } + + $dj_str .= '
      '; + $dj_str .= '
      '; + + return $dj_str; + +} +add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air' ); + +/* Shortcode for displaying upcoming DJs/shows + * Since 2.0.9 +*/ +function radio_station_shortcode_coming_up( $atts ) { + + $atts = shortcode_atts( + array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'limit' => 1, + 'time' => '12', + 'show_sched' => 1, + ), + $atts, + 'dj-coming-up-widget' + ); + + // find out which DJ(s) are coming up today + $djs = radio_station_dj_get_next( $atts['limit'] ); + if ( ! isset( $djs['all'] ) || count( $djs['all'] ) <= 0 ) { + $output = '
    • ' . __( 'None Upcoming', 'radio-station' ) . '
    • '; + return $output; + } + + ob_start(); + ?> +
      + +

      + +
        + $dj ) { + + if ( is_array( $dj ) && 'override' === $dj['type'] ) { + ?> +
      • + + + + + +
        + + + +
        + +
      • '; + +
      • + + + ID, 'thumbnail' ); ?> + + + + + + post_title ); ?> + + post_title ); + } + ?> + + ID, 'show_user_list', true ); + $count = 0; + + if ( $names ) { + ?> +
        With + display_name ); + + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { + echo ' and '; + } elseif ( $count < $count_names && $count_names > 2 ) { + echo ', '; + } + } + ?> +
        + + + + + + , + + +
        + + + + , + + +
        + +
      • + +
      +
      + = $now ) ) { + $current = $sched; + } else { + continue; + } + } + + return $current; +} + +// --- convert shift times to 24-hour and timestamp formats for comparisons --- +function radio_station_convert_time( $time = array() ) { + + if ( empty( $time ) ) { + return false; + } + + $now = strtotime( current_time( 'mysql' ) ); + $cur_date = date( 'Y-m-d', $now ); + $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow + + // convert to 24 hour time + $time = radio_station_convert_schedule_to_24hour( $time ); + + // get a timestamp for the schedule start and end + $time['start_timestamp'] = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] ); + + if ( 'pm' === $time['start_meridian'] && 'am' === $time['end_meridian'] ) { + // check for shows that run overnight into the next morning + $time['end_timestamp'] = strtotime( $tom_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } else { + $time['end_timestamp'] = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } + + // a show cannot end before it begins... if it does, it ends the following day. + if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { + $time['end_timestamp'] = $time['end_timestamp'] + 86400; + } + + return $time; +} + +// --- convert a shift to 24 hour time for display --- +function radio_station_convert_schedule_to_24hour( $sched = array() ) { + + if ( empty( $sched ) ) { + return false; + } + + if ( 'pm' === $sched['start_meridian'] && 12 !== (int) $sched['start_hour'] ) { + $sched['start_hour'] = $sched['start_hour'] + 12; + } + if ( 'am' === $sched['start_meridian'] && $sched['start_hour'] < 10 ) { + $sched['start_hour'] = '0' . $sched['start_hour']; + } + if ( 'am' === $sched['start_meridian'] && 12 === (int) $sched['start_hour'] ) { + $sched['start_hour'] = '00'; + } + + if ( 'pm' === $sched['end_meridian'] && 12 !== (int) $sched['end_hour'] ) { + $sched['end_hour'] = $sched['end_hour'] + 12; + } + if ( 'am' === $sched['end_meridian'] && $sched['end_hour'] < 10 ) { + $sched['end_hour'] = '0' . $sched['end_hour']; + } + if ( 'am' === $sched['end_meridian'] && 12 === (int) $sched['end_hour'] ) { + $sched['end_hour'] = '00'; + } + + return $sched; +} + +// --- fetch the current DJ(s) on-air -- +function radio_station_dj_get_current() { + + // first check to see if there are any shift overrides + $check = radio_station_master_get_overrides( true ); + + if ( $check ) { + $shows = array( + 'all' => $check, + 'type' => 'override', + ); + + // at this point, we are done. Return the info. + return $shows; + } + + // load the info for the DJ + global $wpdb; + + // get the current time + $now = strtotime( current_time( 'mysql' ) ); + + $cur_day = date( 'l', $now ); + + // we only want active shows + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + foreach ( $show_shifts as $shift ) { + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + foreach ( $shift->meta_value as $time ) { + + // check if the shift is for the current day. If it's not, skip it + if ( $time['day'] === $cur_day ) { + // format the time so that it is more easily compared + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + + // we need to make a special allowance for shows that run from one day into the next + if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { + + $time = radio_station_convert_time( $time ); + // because station_convert_time assumes that the show STARTS on the current day, + // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps + $time['start_timestamp'] = $time['start_timestamp'] - 86400; + $time['end_timestamp'] = $time['end_timestamp'] - 86400; + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + } + } + + $shows = array(); + foreach ( $show_ids as $id ) { + $shows['all'][] = get_post( $id ); + } + $shows['type'] = 'shows'; + + return $shows; +} + +// --- get the next DJ or DJs scheduled to be on air based on the current time --- +function radio_station_dj_get_next( $limit = 1 ) { + + // load the info for the DJ + global $wpdb; + + // get the various times/dates we need + $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); + $cur_day_num = date( 'N', strtotime( current_time( 'mysql' ) ) ); + $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); + $now = strtotime( current_time( 'mysql' ) ); + $shows = array(); + + // first check to see if there are any shift overrides + $check = radio_station_master_get_overrides(); + $overrides = array(); + + if ( $check ) { + + foreach ( $check as $i => $p ) { + + $p['sched'] = radio_station_convert_time( $p['sched'] ); + + // compare to the current timestamp + if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { + //show is on now, so we don't need it listed under upcoming + //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; + unset( $check[ $i ] ); + } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { + // show is on later today + $overrides[ $p['sched']['start_timestamp'] . '|' . $p['sched']['end_timestamp'] ] = $p; + } else { + // show is already over and we don't need it + unset( $check[ $i ] ); + } + } + + // sort the overrides by start time + ksort( $overrides ); + } + + // Fetch all schedules... we only want active shows + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value + FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + + foreach ( $show_shifts as $shift ) { + + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... + // fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + $encore_ids = array(); + $days = array( + 'Sunday' => 7, + 'Monday' => 1, + 'Tuesday' => 2, + 'Wednesday' => 3, + 'Thursday' => 4, + 'Friday' => 5, + 'Saturday' => 6, + ); + foreach ( $shift->meta_value as $time ) { + + if ( $time['day'] === $cur_day ) { + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ); + } else { + if ( $cur_day_num < $days[ $time['day'] ] ) { + $day_diff = $days[ $time['day'] ] - $cur_day_num; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } else { + $day_diff = $cur_day_num + $days[ $time['day'] ] + 1; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } + } + + // if the shift occurs later than the current time, we want it + if ( $cur_shift >= $now ) { + $show_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + // 2.2.4: set encore ID array to pass back + if ( isset( $time['encore'] ) && 'on' === $time['encore'] ) { + $encore_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + } + } + } + } + + // sort the shows by start time + ksort( $show_ids ); + + // merge in the overrides array + foreach ( $show_ids as $s => $id ) { + foreach ( $overrides as $o => $info ) { + $stime = explode( '|', $s ); + $otime = explode( '|', $o ); + + if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends + if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) + unset( $show_ids[ $s ] ); // this show is overriden... drop it + } + } + } + } + + // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) + if ( ! function_exists( 'array_replace' ) ) { + + function array_replace() { + $array = array(); + $n = func_num_args(); + + while ( $n-- > 0 ) { + $array += func_get_arg( $n ); + } + return $array; + } + } + + $combined = array_replace( $show_ids, $overrides ); + ksort( $combined ); + + // grab the number of shows from the list the user wants to display + $combined = array_slice( $combined, 0, $limit, true ); + + // fetch detailed show information + foreach ( $combined as $timestamp => $id ) { + if ( ! is_array( $id ) ) { + $shows['all'][ $timestamp ] = get_post( $id ); + } else { + $id['type'] = 'override'; + $shows['all'][ $timestamp ] = $id; + } + } + $shows['type'] = 'shows'; + // 2.2.4: set encore IDs to pass back + $shows['encore'] = $encore_ids; + + // return the information + return $shows; +} + +// --- get the most recently entered song --- +function radio_station_myplaylist_get_now_playing() { + + // grab the most recent playlist + $args = array( + 'numberposts' => 1, + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + ); + + $playlist = get_posts( $args ); + + // if there are no playlists saved, return nothing + if ( ! $playlist ) { + return false; + } + + // fetch the tracks for each playlist from the wp_postmeta table + $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); + + if ( ! empty( $songs[0] ) ) { + // removed any entries that are marked as 'queued' + foreach ( $songs[0] as $i => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[0][ $i ] ); + } + } + + // pop the last track off the list for display + $most_recent = array_pop( $songs[0] ); + + // get the permalink for the playlist so it can be displayed + $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); + + return $most_recent; + } else { + return false; + } +} + +// --- fetch all blog posts for a show's DJs --- +function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { + + global $wpdb; + + // do not return anything if we don't have a show + if ( ! $show_id ) { + return false; + } + + $fetch_posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = 'post_showblog_id' AND + meta.meta_value = %d", + $show_id + ) + ); + + $blog_array = array(); + $blogposts = array(); + foreach ( $fetch_posts as $f ) { + $blog_array[] = $f->post_id; + } + + if ( $blog_array ) { + + $blogposts = $wpdb->get_results( + $wpdb->prepare( + "SELECT posts.ID, posts.post_title + FROM {$wpdb->posts} AS posts + WHERE posts.ID IN(%s) AND + posts.post_status = 'publish' + ORDER BY posts.post_date DESC + LIMIT %d", + $blog_array, + $limit + ) + ); + } + + $output = ''; + + $output .= '
      '; + $output .= '

      ' . $title . '

      '; + $output .= ''; + $output .= '
      '; + + // if the blog archive page has been created, add a link to the archive for this show + $page = $wpdb->get_results( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = '_wp_page_template' AND + meta.meta_value = 'show-blog-archive-template.php' + LIMIT 1" + ); + + if ( $page ) { + $blog_archive = get_permalink( $page[0]->post_id ); + $params = array( 'show_id' => $show_id ); + $blog_archive = add_query_arg( $params, $blog_archive ); + + $output .= '' . __( 'More Blog Posts', 'radio-station' ) . ''; + } + + return $output; +} + +// --- get any schedule overrides for today's date --- +// If currenthour is true, only overrides that are in effect NOW will be returned +function radio_station_master_get_overrides( $currenthour = false ) { + + global $wpdb; + + $now = strtotime( current_time( 'mysql' ) ); + $date = date( 'Y-m-d', $now ); + $sql_date = $wpdb->esc_like( $date ); + $sql_date = '%' . $sql_date . '%'; + $show_shifts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta_key = 'show_override_sched' AND + meta_value LIKE %s", + $sql_date + ) + ); + + $scheds = array(); + if ( $show_shifts ) { + foreach ( $show_shifts as $shift ) { + + $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $time = $next_sched[0]; + + if ( $currenthour ) { + + // convert to 24 hour time + $check = array(); + $check = $time; + + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $title = get_the_title( $shift->post_id ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $time, + ); + } else { + continue; + } + } else { + $title = get_the_title( $shift->post_id ); + $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $sched[0], + ); + } + } + } + + return $scheds; +} + +// --- shorten a string to a set number of words --- +function radio_station_shorten_string( $string, $limit ) { + + $shortened = $string; // just in case of a problem + + $array = explode( ' ', $string ); + if ( count( $array ) <= $limit ) { + // already at or under the limit + $shortened = $string; + } else { + array_splice( $array, $limit ); + $shortened = implode( ' ', $array ) . ' ...'; + } + return $shortened; +} + +// --- translate weekday --- +// translated individually as cannot translate a variable +function radio_station_translate_weekday( $weekday, $short = false ) { + return __( $weekday, 'radio-station' ); +} + +// --- translate month --- +function radio_station_translate_month( $month, $short = false ) { + return __( $month, 'radio-station' ); +} diff --git a/includes/support_functions.php b/includes/support_functions.php deleted file mode 100644 index 6792144..0000000 --- a/includes/support_functions.php +++ /dev/null @@ -1,547 +0,0 @@ -= $now ) ) {$current = $sched;} - else {continue;} - } - - return $current; -} - -// --- convert shift times to 24-hour and timestamp formats for comparisons --- -function radio_station_convert_time( $time = array() ) { - - if ( empty( $time ) ) {return false;} - - $now = strtotime( current_time( 'mysql' ) ); - $curDate = date( 'Y-m-d', $now ); - $tomDate = date( 'Y-m-d', ( $now + 86400) ); // get the date for tomorrow - - // convert to 24 hour time - $time = radio_station_convert_schedule_to_24hour( $time ); - - // get a timestamp for the schedule start and end - $time['start_timestamp'] = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'] ); - - if ( ( $time['start_meridian'] == 'pm' ) && ( $time['end_meridian'] == 'am' ) ) { - // check for shows that run overnight into the next morning - $time['end_timestamp'] = strtotime( $tomDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } else { - $time['end_timestamp'] = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } - - // a show cannot end before it begins... if it does, it ends the following day. - if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { - $time['end_timestamp'] = $time['end_timestamp'] + 86400; - } - - return $time; -} - -// --- convert a shift to 24 hour time for display --- -function radio_station_convert_schedule_to_24hour( $sched = array() ) { - - if ( empty( $sched ) ) {return false;} - - if ( ( $sched['start_meridian'] == 'pm' ) && ( $sched['start_hour'] != 12 ) ) { - $sched['start_hour'] = $sched['start_hour'] + 12; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] < 10 ) ) { - $sched['start_hour'] = "0".$sched['start_hour']; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] == 12 ) ) { - $sched['start_hour'] = '00'; - } - - if ( ( $sched['end_meridian'] == 'pm' ) && ( $sched['end_hour'] != 12 ) ) { - $sched['end_hour'] = $sched['end_hour'] + 12; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] < 10 ) ) { - $sched['end_hour'] = "0".$sched['end_hour']; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] == 12 ) ) { - $sched['end_hour'] = '00'; - } - - return $sched; -} - -// --- fetch the current DJ(s) on-air -- -function radio_station_dj_get_current() { - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(true); - - if ( $check ) { - $shows = array( - 'all' => $check, - 'type' => 'override' - ); - - // at this point, we are done. Return the info. - return $shows; - } - - // load the info for the DJ - global $wpdb; - - // get the current time - $now = strtotime( current_time( 'mysql' ) ); - - $hour = date ('H', $now ); - $min = date( 'i', $now ); - $curDay = date( 'l', $now ); - $curDate = date( 'Y-m-d', $now ); - - // then check to see if a show is scheduled - // $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - // WHERE `meta_key` = 'show_sched';"); - - // we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // check if the shift is for the current day. If it's not, skip it - if ( $time['day'] == $curDay ) { - // format the time so that it is more easily compared - $time = radio_station_convert_time( $time ); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - // we need to make a special allowance for shows that run from one day into the next - if( date('w', strtotime($time['day']))+1 == date('w', strtotime($curDay)) ) { - - $time = radio_station_convert_time($time); - // because station_convert_time assumes that the show STARTS on the current day, - // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps - $time['start_timestamp'] = $time['start_timestamp'] - 86400; - $time['end_timestamp'] = $time['end_timestamp'] - 86400; - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - } - } - - $shows = array(); - foreach( $show_ids as $id ) { - $shows['all'][] = get_post($id); - } - $shows['type'] = 'shows'; - - return $shows; -} - -// --- get the next DJ or DJs scheduled to be on air based on the current time --- -function radio_station_dj_get_next($limit = 1) { - - // load the info for the DJ - global $wpdb; - - // get the various times/dates we need - $curDay = date( 'l', strtotime( current_time( 'mysql' ) ) ); - $curDayNum = date( 'N', strtotime(current_time( 'mysql') ) ); - $curDate = date( 'Y-m-d', strtotime(current_time( 'mysql') ) ); - $now = strtotime( current_time( 'mysql' ) ); - $tomorrow = date( "Y-m-d", (strtotime( $curDate ) + 86400) ); - $tomorrowDay = date( "l", (strtotime( $curDate ) + 86400) ); - $shows = array(); - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(); - $overrides = array(); - - if ( $check ) { - - foreach ( $check as $i => $p ) { - - $x = array(); - $x = $p['sched']; - - $p['sched'] = radio_station_convert_time( $p['sched'] ); - - - // compare to the current timestamp - if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { - //show is on now, so we don't need it listed under upcoming - //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - unset($check[$i]); - } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { - // show is on later today - $overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - } else { - // show is already over and we don't need it - unset($check[$i]); - } - - } - - // sort the overrides by start time - ksort( $overrides ); - } - - // Fetch all schedules... we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - - foreach ( $show_shifts as $shift ) { - - $shift->meta_value = unserialize( $shift->meta_value ); - //print_r($shift); - - // if a show has no shifts, unserialize() will return false instead of an empty array... - // fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - $encore_ids = array(); - $days = array('Sunday' => 7, 'Monday' => 1, 'Tuesday' => 2, 'Wednesday' => 3, 'Thursday' => 4, 'Friday' => 5, 'Saturday' => 6); - foreach ( $shift->meta_value as $time ) { - - if ( $time['day'] == $curDay ) { - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ); - } else { - if ( $curDayNum < $days[$time['day']] ) { - $day_diff = $days[$time['day']] - $curDayNum; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } else { - $day_diff = $curDayNum+$days[$time['day']] + 1; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } - } - - // if the shift occurs later than the current time, we want it - if ( $curShift >= $now ) { - $show_ids[$curShift.'|'.$endShift] = $shift->post_id; - // 2.2.4: set encore ID array to pass back - if ( isset( $time['encore'] ) && ( $time['encore'] == 'on' ) ) { - $encore_ids[$curShift.'|'.$endShift] = $shift->post_id; - } - } - - } - } - - // sort the shows by start time - ksort( $show_ids ); - - // merge in the overrides array - foreach ( $show_ids as $s => $id ) { - foreach ( $overrides as $o => $info ) { - $stime = explode( "|", $s ); - $otime = explode( "|", $o ); - - if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends - if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) - unset($show_ids[$s]); // this show is overriden... drop it - } - } - - } - } - - // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) - if ( !function_exists( 'array_replace' ) ) { - - function array_replace() { - $array = array(); - $n = func_num_args(); - - while ( $n-- >0 ) { - $array+=func_get_arg($n); - } - return $array; - } - } - - $combined = array_replace( $show_ids, $overrides ); - ksort( $combined ); - - // grab the number of shows from the list the user wants to display - $combined = array_slice( $combined, 0, $limit, true ); - - // fetch detailed show information - foreach ( $combined as $timestamp => $id ) { - if ( !is_array( $id ) ) { - $shows['all'][$timestamp] = get_post($id); - } else { - $id['type'] = 'override'; - $shows['all'][$timestamp] = $id; - } - } - $shows['type'] = 'shows'; - // 2.2.4: set encore IDs to pass back - $shows['encore'] = $encore_ids; - - // return the information - return $shows; -} - -// --- get the most recently entered song --- -function radio_station_myplaylist_get_now_playing() { - - // grab the most recent playlist - $args = array( - 'numberposts' => 1, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish' - ); - - $playlist = get_posts( $args ); - - // if there are no playlists saved, return nothing - if ( !$playlist ) {return false;} - - // fetch the tracks for each playlist from the wp_postmeta table - $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); - - //print_r($songs);die(); - - if ( !empty( $songs[0] ) ) { - // removed any entries that are marked as 'queued' - foreach ( $songs[0] as $i => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) { - unset( $songs[0][$i] ); - } - } - - // pop the last track off the list for display - $most_recent = array_pop( $songs[0] ); - - // get the permalink for the playlist so it can be displayed - $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); - - return $most_recent; - } else { - return false; - } -} - -// --- fetch all blog posts for a show's DJs --- -function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { - - global $wpdb; - - // do not return anything if we don't have a show - if ( !$show_id ) {return false;} - - $fetch_posts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = 'post_showblog_id' AND `meta`.`meta_value` = ".$show_id.";"); - - $blog_array = array(); - $blogposts = array(); - foreach ( $fetch_posts as $f ) {$blog_array[] = $f->post_id;} - - if ( $blog_array ) { - $blog_array = implode( ',', $blog_array ); - - $blogposts = $wpdb->get_results("SELECT `posts`.`ID`, `posts`.`post_title` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`ID` IN(".$blog_array.") - AND `posts`.`post_status` = 'publish' - ORDER BY `posts`.`post_date` DESC - LIMIT ".$limit.";"); - } - - $output = ''; - - $output .= '
      '; - $output .= '

      '.$title.'

      '; - $output .= ''; - $output .= '
      '; - - // if the blog archive page has been created, add a link to the archive for this show - $page = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = '_wp_page_template' - AND `meta`.`meta_value` = 'show-blog-archive-template.php' - LIMIT 1;"); - - if ( $page ) { - $blog_archive = get_permalink($page[0]->post_id); - $params = array( 'show_id' => $show_id ); - $blog_archive = add_query_arg( $params, $blog_archive ); - - $output .= ''.__('More Blog Posts', 'radio-station').''; - } - - return $output; -} - -// --- get any schedule overrides for today's date --- -// If currenthour is true, only overrides that are in effect NOW will be returned -function radio_station_master_get_overrides( $currenthour = false ) { - - global $wpdb; - - $now = strtotime( current_time( 'mysql' ) ); - $date = date( 'Y-m-d', $now ); - - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta_key` = 'show_override_sched' - AND `meta_value` LIKE '%".$date."%';"); - - $scheds = array(); - if ( $show_shifts ) { - foreach ( $show_shifts as $shift ) { - - $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false); - $time = $next_sched[0]; - - if ( $currenthour ) { - - // convert to 24 hour time - $check = array(); - $check = $time; - - $time = radio_station_convert_time($time); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $title = get_the_title( $shift->post_id ); - $scheds[] = array('post_id' => $shift->post_id, 'title' => $title, 'sched' => $time); - } else {continue;} - - } else { - $title = get_the_title( $shift->post_id ); - $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); - $scheds[] = array( 'post_id' => $shift->post_id, 'title' => $title, 'sched' => $sched[0] ); - } - } - } - - return $scheds; -} - -// --- shorten a string to a set number of words --- -function radio_station_shorten_string($string, $limit) { - - $shortened = $string; // just in case of a problem - - $array = explode( ' ', $string ); - if ( count( $array ) <= $limit ) { - // already at or under the limit - $shortened = $string; - } else { - array_splice( $array, $limit ); - $shortened = implode( ' ', $array )." ..."; - } - return $shortened; -} - -// --- translate weekday --- -// translated individually as cannot translate a variable -function radio_station_translate_weekday( $weekday, $short = false ) { - if ( $short ) { - if ( $weekday == 'Sun' ) {$translated = __( 'Sun', 'radio-station' );} - elseif ( $weekday == 'Mon' ) {$translated = __( 'Mon', 'radio-station' );} - elseif ( $weekday == 'Tue' ) {$translated = __( 'Tue', 'radio-station' );} - elseif ( $weekday == 'Wed' ) {$translated = __( 'Wed', 'radio-station' );} - elseif ( $weekday == 'Thu' ) {$translated = __( 'Thu', 'radio-station' );} - elseif ( $weekday == 'Fri' ) {$translated = __( 'Fri', 'radio-station' );} - elseif ( $weekday == 'Sat' ) {$translated = __( 'Sat', 'radio-station' );} - } else { - if ( $weekday == 'Sunday' ) {$translated = __( 'Sunday', 'radio-station' );} - elseif ( $weekday == 'Monday' ) {$translated = __( 'Monday', 'radio-station' );} - elseif ( $weekday == 'Tueday' ) {$translated = __( 'Tueday', 'radio-station' );} - elseif ( $weekday == 'Wednesday' ) {$translated = __( 'Wednesday', 'radio-station' );} - elseif ( $weekday == 'Thurday' ) {$translated = __( 'Thurday', 'radio-station' );} - elseif ( $weekday == 'Friday' ) {$translated = __( 'Friday', 'radio-station' );} - elseif ( $weekday == 'Saturday' ) {$translated = __( 'Saturday', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $weekday;} -} - -// --- translate month --- -function radio_station_translate_month( $month, $short = false ) { - if ( $short ) { - if ( $month == 'Jan' ) {$translated = __( 'Jan', 'radio-station' );} - elseif ( $month == 'Feb' ) {$translated = __( 'Feb', 'radio-station' );} - elseif ( $month == 'Mar' ) {$translated = __( 'Mar', 'radio-station' );} - elseif ( $month == 'Apr' ) {$translated = __( 'Apr', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'Jun' ) {$translated = __( 'Jun', 'radio-station' );} - elseif ( $month == 'Jul' ) {$translated = __( 'Jul', 'radio-station' );} - elseif ( $month == 'Aug' ) {$translated = __( 'Aug', 'radio-station' );} - elseif ( $month == 'Sep' ) {$translated = __( 'Sep', 'radio-station' );} - elseif ( $month == 'Oct' ) {$translated = __( 'Oct', 'radio-station' );} - elseif ( $month == 'Nov' ) {$translated = __( 'Nov', 'radio-station' );} - elseif ( $month == 'Dec' ) {$translated = __( 'Dec', 'radio-station' );} - } else { - if ( $month == 'January' ) {$translated = __( 'January', 'radio-station' );} - elseif ( $month == 'February' ) {$translated = __( 'February', 'radio-station' );} - elseif ( $month == 'March' ) {$translated = __( 'March', 'radio-station' );} - elseif ( $month == 'April' ) {$translated = __( 'April', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'June' ) {$translated = __( 'June', 'radio-station' );} - elseif ( $month == 'July' ) {$translated = __( 'July', 'radio-station' );} - elseif ( $month == 'August' ) {$translated = __( 'August', 'radio-station' );} - elseif ( $month == 'September' ) {$translated = __( 'September', 'radio-station' );} - elseif ( $month == 'October' ) {$translated = __( 'October', 'radio-station' );} - elseif ( $month == 'November' ) {$translated = __( 'November', 'radio-station' );} - elseif ( $month == 'December' ) {$translated = __( 'December', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $month;} -} \ No newline at end of file diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php deleted file mode 100644 index 92d14af..0000000 --- a/includes/widget_djcomingup.php +++ /dev/null @@ -1,354 +0,0 @@ - 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); - $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); - parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time']: 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - - -

      - -

      - -

      - -

      - -

      - -

      - - -

      - -

      - -

      - -

      - - -

      - -

      -
      - -

      - - -
      - -
        - - 0 ) ) { - - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && $dj['type'] == 'override' ) { - - echo '
      • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
        '.$dj['title'].'
        '; - } - - // --- show avatar --- - if ( $djavatar ) { - if ( has_post_thumbnail($dj['post_id'] ) ) { - echo '
        '; - echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
        '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
        '.$dj['title'].'
        '; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $dj['sched']['start_hour']; - if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { - $start_hour = substr($dj['sched']['start_hour'], 1); - } - - $end_hour = $dj['sched']['end_hour']; - if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($dj['sched']['end_hour'], 1); - } - - echo '
        '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
        '; - } else { - echo '
        '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
        '; - } - } - echo '
      • '; - - } else { - - echo '
      • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
        '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
        '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
        '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
        '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
        '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
        '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( array_key_exists( $showtime, $djs['encore'] ) ) { - echo '
        '.__('Encore Presentation','radio-station').'
        '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - echo '
        '.__( 'with', 'radio-station' ).' '; - foreach ( $ids as $id ) { - $count++; - $user_info = get_userdata( $id ); - - if ( $link_djs ) { - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - } else {echo $user_info->display_name;} - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station' ).' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
        '; - } - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - $showtimes = explode( "|", $showtime ); - // 2.2.2: fix to weekday value to be translated - $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); - - if ( $time == 12 ) { - echo '
        '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
        '; - } else { - echo '
        '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
        '; - } - - } - - echo '
      • '; - } - } - } else { - if ( $default != '' ) { - echo '
      • '.$default.'
      • '; - } - } - ?> -
      -
      - 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist']: false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $links_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - - -

      - - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - - -

      - -

      -
      - -

      - -
      - - -
        - '; - - // --- show title *for above only) --- - if ( $position == 'above' ) { - echo '
        '.$djs['all'][0]['title'].'
        '; - } - - if ( $djavatar ) { - if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo '
        '; - echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
        '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
        '.$djs['all'][0]['title'].'
        '; - } - - echo ''; - - // --- display the schedule override if requested --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $djs['all'][0]['sched']['start_hour']; - if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { - $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); - } - - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); - } - - echo '
        '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
        '; - - } else { - - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - echo '
        '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
        '; - - } - } - - echo ''; - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach( $djs['all'] as $dj ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - $current_sched = radio_station_current_schedule( $scheds ); - - echo '
      • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
        '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
        '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
        '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
        '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
        '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
        '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( $current_sched['encore'] == 'on' ) { - echo '
        '.__('Encore Presentation','radio-station').'
        '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - - echo '
        '.__( 'with', 'radio-station' ).' '; - foreach( $ids as $id ) { - $count++; - $user_info = get_userdata($id); - - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station').' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
        '; - } - } - - // --- show description --- - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); - echo '
        '.$desc_string.'
        '; - } - - // --- playlist link --- - if ( $show_playlist ) { - echo ''; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - // --- if we only want the schedule that's relevant now to display --- - if ( !$show_all_sched ) { - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - echo '
        '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
        '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - echo '
        '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
        '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - echo '
        '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
        '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - echo '
        '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
        '; - } - } - } - } - - echo '
      • '; - - } - } else { - echo '
      • '.$default.'
      • '; - } - } - - ?> -
      -
      - 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); - parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; - $song = isset( $instance['song'] ) ? $instance['song'] : true; - $album = isset( $instance['album'] ) ? $instance['album'] : false; - $label = isset( $instance['label'] ) ? $instance['label'] : false; - $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - - ?> -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - -

      - "; - - ?> -
      - '; - - // 2.2.3: convert span tags to div tags - // 2.2.4: check value keys are set before outputting - if ( $song && isset( $most_recent['playlist_entry_song']) ) { - echo '
      '.$most_recent['playlist_entry_song'].'
      '; - } - - if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { - echo '
      '.$most_recent['playlist_entry_artist'].'
      '; - } - - if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { - echo '
      '.$most_recent['playlist_entry_album'].'
      '; - } - - if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { - echo '
      '.$most_recent['playlist_entry_label'].'
      '; - } - - if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { - echo '
      '.$most_recent['playlist_entry_comments'].'
      '; - } - - if ( isset( $most_recent['playlist_permalink'] ) ) { - echo ''; - } - - echo '
      '; - - } else { - // 2.2.3: added missing translation wrapper - echo '
      '.__('No playlists available.','radio-station').'
      '; - } - - ?> - - -
      - + + WordPress Coding Standards + + diff --git a/radio-station.php b/radio-station.php index 7a8bdfd..73177a9 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,573 +1,606 @@ - -Version: 2.2.4 -Text Domain: radio-station -Domain Path: /languages -Author URI: https://netmix.com/radio-station -GitHub Plugin URI: netmix/radio-station - -Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License, version 2, as -published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// === Setup === -// - Include Necessary Files -// - Load Text Domain -// - Enqueue Stylesheets -// - Enqueue Admin Scripts -// - Enqueue Admin Styles -// === Template Filters === -// - Single Show/Playlist Template -// - Playlist Archive Template -// === Roles === -// - Add DJ Role and Capabilities -// - maybe Revoke Edit Show Capability -// === Menus === -// - Add Menus -// - maybe Remove Add Show Bar Link -// - Output Help Page -// - Output Export Page - -// ------------- -// === Setup === -// ------------- - -// --- include necessary files --- -include('includes/post_types.php'); -include('includes/master_schedule.php'); -include('includes/shortcodes.php'); -include('includes/widget_nowplaying.php'); -include('includes/widget_djonair.php'); -include('includes/widget_djcomingup.php'); -include('includes/support_functions.php'); - -// --- load the text domain --- -function radio_station_init() { - load_plugin_textdomain( 'radio-station', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); -} -add_action( 'plugins_loaded', 'radio_station_init' ); - -// --- flush rewrite rules on activate / deactivation --- -// 2.2.3: added this for custom post types rewrite flushing -register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); -register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); -function radio_station_flush_rewrite_flag() { - add_option( 'radio_station_flush_rewrite_rules', true ); -} - -// --- enqueue necessary stylesheets --- -function radio_station_load_styles() { - - $program_css = get_stylesheet_directory().'/program-schedule.css'; - if ( file_exists( $program_css ) ) { - $version = filemtime( $program_css ); - $url = get_stylesheet_directory_uri().'/program-schedule.css'; - } else { - $version = filemtime( dirname(__FILE__).'/css/program-schedule.css' ); - $url = plugins_url( 'css/program-schedule.css', __FILE__ ); - } - wp_enqueue_style( 'program-schedule', $url, array(), $version ); - - // note: djonair.css style enqueueing moved to /includes/widget_djonair.php -} -add_action( 'wp_enqueue_scripts', 'radio_station_load_styles' ); - -// --- enqueue admin scripts --- -// jQuery is needed by the output of this code, so let us make sure we have it available -function radio_station_master_scripts() { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'jquery-ui-datepicker' ); - // $url = plugins_url( 'css/jquery-ui.css', dirname(__FILE__).'/radio-station.php' ); - // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); - if (is_ssl()) {$protocol = 'https';} else {$protocol = 'http';} - $url = $protocol.'://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; - wp_enqueue_style( 'jquery-ui-style', $url, '1.8.2' ); -} -add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); -add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); - -// --- add some style rules to certain parts of the admin area --- -function radio_station_load_admin_styles() { - global $post; - - // hide the first submenu item to prevent duplicate of main menu item - $styles = ' #toplevel_page_radio-station .wp-first-item { display: none; }'."\n"; - - if ( isset( $post->post_type ) && ( $post->post_type == 'playlist' ) ) { - $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }'."\n"; - } - - echo ''; - -} -add_action( 'admin_print_styles', 'radio_station_load_admin_styles' ); - - -// ------------------------ -// === Template Filters === -// ------------------------ - -// --- load the theme file for the playlist and show post types --- -function radio_station_load_template( $single_template ) { - global $post; - - if ($post->post_type == 'playlist') { - // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-playlist.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-playlist.php'; - } - } - - if ($post->post_type == 'show') { - // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-show.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-show.php'; - } - } - - return $single_template; -} -add_filter( 'single_template', 'radio_station_load_template' ); - -// --- load the theme file for the playlist archive pages --- -function radio_station_load_custom_post_type_template( $archive_template ) { - global $post; - - if ( is_post_type_archive('playlist') ) { - $playlist_archive_theme = get_stylesheet_directory().'/archive-playlist.php'; - if ( !file_exists( $playlist_archive_theme ) ) { - $archive_template = dirname(__FILE__).'/templates/archive-playlist.php'; - } - } - - return $archive_template; -} -add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ); - - - -// ------------- -// === Roles === -// ------------- - -// --- set up the DJ role and user capabilities --- -function radio_station_set_roles() { - - global $wp_roles; - - // set only the necessary capabilities for DJs - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, - // 'edit_others_playlists' => true, // uncomment to allow DJs to edit all playlists - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true - ); - // $wp_roles->remove_role('dj'); // we need this here in case we ever update the capabilities list - // TODO: translate role name ? - $wp_roles->add_role( 'dj', 'DJ', $caps ); - - // grant all new capabilities to admin users - $wp_roles->add_cap( 'administrator', 'edit_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_published_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_others_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_private_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_published_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_others_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_private_shows', true ); - $wp_roles->add_cap( 'administrator', 'read_shows', true ); - $wp_roles->add_cap( 'administrator', 'publish_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_published_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_others_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_private_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_published_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_others_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_private_playlists', true ); - $wp_roles->add_cap( 'administrator', 'read_playlists', true ); - $wp_roles->add_cap( 'administrator', 'publish_playlists', true ); -} -if ( is_multisite() ) { - add_action( 'init', 'radio_station_set_roles', 10, 0 ); -} else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} - -// --- revoke the ability to edit a show if the user is not listed as a DJ on that show --- -function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $args ) { - - global $post, $wp_roles; - - $user = wp_get_current_user(); - - // determine which roles should have full access aside from administrator - $add_roles = array( 'administrator' ); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach( $wp_roles->roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( $capname == 'publish_shows' && ($capstatus == 1 || $capstatus == true) ) { - $add_roles[] = $name; - } - } - } - } - - // exclude administrators and custom roles with appropriate capabilities... - // they should be able to do whatever they want - $found = false; - foreach ( $add_roles as $role ) { - if ( in_array( $role, $user->roles ) ) {$found = true;} - } - - if ( !$found ) { - - // limit this to published shows - if ( isset( $post->post_type ) ) { - if ( is_admin() && ( $post->post_type == 'show' ) && ( $post->post_status == 'publish' ) ) { - - $djs = get_post_meta( $post->ID, 'show_user_list', true ); - - if ( !$djs || ( $djs == '' ) ) {$djs = array();} - - // if they are not listed, temporarily revoke editing ability for this post - if ( !in_array( $user->ID, $djs ) ) { - $allcaps['edit_shows'] = false; - $allcaps['edit_published_shows'] = false; - } - } - } - } - return $allcaps; -} -add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); - - -// ------------- -// === Menus === -// ------------- - -function radio_station_add_admin_menus() { - - $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); - $position = apply_filters( 'radio_station_menu_position', 5 ); - $capability = 'publish_playlists'; - add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); - - add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); - add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); - add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); - add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); - add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); - add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); - add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station'), __('Export Playlists', 'radio-station'), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); - add_submenu_page( 'radio-station', __( 'Help', 'radio-station'), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); - - // hack the submenu global to post type add/edit URLs - global $submenu; - foreach ( $submenu as $i => $menu ) { - if ( $i == 'radio-station' ) { - foreach ( $menu as $j => $item ) { - if ( $item[2] == 'add-show' ) { - // maybe remove the Add Show link for DJs - // $user = wp_get_current_user(); - // if ( in_array( 'dj', $user->roles ) ) { - if ( !current_user_can( 'publish_shows' ) ) {unset($submenu[$i][$j]);} - else {$submenu[$i][$j][2] = 'post-new.php?post_type=show';} - } elseif ( $item[2] == 'shows' ) {$submenu[$i][$j][2] = 'edit.php?post_type=show';} - elseif ( $item[2] == 'playlists' ) {$submenu[$i][$j][2] = 'edit.php?post_type=playlist';} - elseif ( $item[2] == 'add-playlist' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=playlist';} - elseif ( $item[2] == 'genres' ) {$submenu[$i][$j][2] = 'edit-tags.php?taxonomy=genres';} - elseif ( $item[2] == 'schedule-overrides' ) {$submenu[$i][$j][2] = 'edit.php?post_type=override';} - elseif ( $item[2] == 'add-override' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=override';} - } - } - } -} -add_action( 'admin_menu', 'radio_station_add_admin_menus' ); - -// --- expand main menu fix for plugin submenu items --- -// 2.2.2: added fix for genre taxonomy page and post type editing -function radio_station_fix_genre_parent($parent_file = '') { - global $pagenow, $post; - $post_types = array( 'show', 'playlist', 'override' ); - if ( ( $pagenow == 'edit-tags.php') && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - $parent_file = 'radio-station'; - } elseif ( ( $pagenow == 'post.php' ) && ( in_array( $post->post_type, $post_types ) ) ) { - $parent_file = 'radio-station'; - } - return $parent_file; -} -add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); - -// --- genre taxonomy submenu item fix --- -// 2.2.2: so genre submenu item link is set to current (bold) -function radio_station_genre_submenu_fix() { - global $pagenow; - if ( ( $pagenow == 'edit-tags.php' ) && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - echo ""; - } -} -add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); - -// --- remove the Add Show link for DJs from the wp admin bar --- -// 2.2.2: re-add new post type items to admin bar -// (as no longer automatically added by register_post_type) -function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { - - // --- new show --- - if ( current_user_can( 'publish_shows' ) ) { - - $args = array( - 'id' => 'new-show', - 'title' => __( 'Show', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=show') - ); - $wp_admin_bar->add_node($args); - } - - // --- new playlist --- - if ( current_user_can( 'publish_playlists' ) ) { - $args = array( - 'id' => 'new-playlist', - 'title' => __( 'Playlist', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=playlist') - ); - $wp_admin_bar->add_node($args); - } - - // --- new schedule override --- - if ( current_user_can( 'publish_shows' ) ) { - $args = array( - 'id' => 'new-override', - 'title' => __( 'Override', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=override') - ); - $wp_admin_bar->add_node($args); - } - -} -add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); - -// --- output help page --- -function radio_station_plugin_help() { - - // 2.2.2: include patreon button link - echo radio_station_patreon_blurb(false); - - // include help template - include( dirname(__FILE__).'/templates/help.php' ); -} - -// --- output playlist export page --- -function radio_station_admin_export() { - global $wpdb; - - // first, delete any old exports from the export directory - $dir = dirname(__FILE__).'/export/'; - if ( is_dir ( $dir ) ) { - $get_contents = opendir($dir); - while ( $file = readdir( $get_contents ) ) { - if ( ( $file != '.' ) && ( $file != '..' ) ) { - unlink( $dir.$file ); - } - } - closedir($get_contents); - } - - // watch for form submission - if ( isset( $_POST['export_action']) && ( $_POST['export_action'] == 'station_playlist_export' ) ) { - - // validate referrer and nonce field - check_admin_referer( 'station_export_valid' ); - - $start = $_POST['station_export_start_year'].'-'.$_POST['station_export_start_month'].'-'.$_POST['station_export_start_day']; - $start .= ' 00:00:00'; - $end = $_POST['station_export_end_year'].'-'.$_POST['station_export_end_month'].'-'.$_POST['station_export_end_day']; - $end .= ' 23:59:59'; - - // fetch all records that were created between the start and end dates - $query = - "SELECT `posts`.`ID`, `posts`.`post_date` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`post_type` = 'playlist' - AND `posts`.`post_status` = 'publish' - AND TO_DAYS(`posts`.`post_date`) >= TO_DAYS(%s) - AND TO_DAYS(`posts`.`post_date`) <= TO_DAYS(%s) - ORDER BY `posts`.`post_date` ASC;"; - //" prepare query before executing - $query = $wpdb->prepare( $query, array( $start, $end ) ); - $playlists = $wpdb->get_results( $query ); - - if ( !$playlists ) {$list = 'No playlists found for this period.';} - - // fetch the tracks for each playlist from the wp_postmeta table - foreach ( $playlists as $i => $playlist ) { - - $songs = get_post_meta( $playlist->ID, 'playlist', true ); - - // remove any entries that are marked as 'queued' - foreach ( $songs as $j => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) {unset($songs[$j]);} - } - - $playlists[$i]->songs = $songs; - } - - $output = ''; - - $date = ''; - foreach ( $playlists as $playlist ) { - - if ( ( $date == '' ) || ( $date != array_shift( explode( " ", $playlist->post_date ) ) ) ) { - $date = array_shift( explode( " ", $playlist->post_date ) ); - $output .= $date."\n\n"; - } - - foreach ( $playlist->songs as $song ) { - $output .= $song['playlist_entry_artist'].' || '.$song['playlist_entry_song'].' || '.$song['playlist_entry_album'].' || '.$song['playlist_entry_label']."\n"; - } - } - - // save as file - $dir = dirname(__FILE__).'/export/'; - $file = $date.'-export.txt'; - if ( !file_exists( $dir ) ) {wp_mkdir_p( $dir );} - - $f = fopen( $dir.$file, 'w' ); - fwrite( $f, $output ); - fclose( $f ); - - // display link to file - $url = get_bloginfo('url').'/wp-content/plugins/radio-station/tmp/'.$file; - echo ''; - } - - // display the export page - include( dirname(__FILE__).'/templates/admin-export.php' ); - -} - - -// -------------------- -// === Admin Notice === -// -------------------- - -// --- plugin announcement notice --- -// 2.2.2: added plugin announcement notice -function radio_station_announcement_notice() { - - // --- bug out if already dismissed --- - if ( get_option( 'radio_station_announcement_dismissed' ) ) {return;} - - // --- bug out on certain plugin pages --- - $pages = array( 'radio-station', 'radio-station-help' ); - if ( isset( $_REQUEST['page'] ) && ( in_array( $_REQUEST['page'], $pages ) ) ) {return;} - - // --- display plugin announcement --- - echo '
      '; - echo radio_station_patreon_blurb(); - echo ''; - echo '
      '; -} -add_action( 'admin_notices', 'radio_station_announcement_notice' ); - -// --- dismiss plugin announcement notice --- -// 2.2.2: AJAX for announcement notice dismissal -function radio_station_announcement_dismiss() { - if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { - update_option( 'radio_station_announcement_dismissed', true ); - echo ""; - exit; - } -} -add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); - -// --- Patreon supporter blurb --- -// 2.2.2: added simple patreon supporter blurb -function radio_station_patreon_blurb( $dismissable = true ) { - - $blurb = '
        '; - $blurb .= '
      • '; - $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); - $blurb .= ''; - $blurb .= '
      • '; - $blurb .= '
      • '; - $blurb .= ''.__( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ).'
        '; - $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ).',
        '; - $blurb .= __( 'since June 2019', 'radio-station' ).', '; - $blurb .= ''.__( 'Radio Station', 'radio-station' ).' '; - $blurb .= __(' plugin development has been actively taken over by', 'radio-station' ); - $blurb .= ' Netmix.
        '; - $blurb .= __( 'We invite you to', 'radio-station'); - $blurb .= ' '; - $blurb .= __('Become a Radio Station Patreon Supporter', 'radio-station'); - $blurb .= ' '.__('to make it better for everyone', 'radio-station').'!'; - $blurb .= '
      • '; - $blurb .= '
      • '; - $blurb .= radio_station_patreon_button(); - $blurb .= '
      • '; - if ($dismissable) { - $blurb .= '
      • '; - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - $blurb .= ''; - $blurb .= ''; - $blurb .= '
      • '; - } - $blurb .= '
      '; - return $blurb; -} - -// --- Patreon supporter button --- -// 2.2.2: added simple patreon supporter image button -function radio_station_patreon_button() { - $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); - $button = ''; - $button .= ''; - $button .= ''; - $button .= ''; - return $button; -} - + +Version: 2.2.4 +Text Domain: radio-station +Domain Path: /languages +Author URI: https://netmix.com/radio-station +GitHub Plugin URI: netmix/radio-station + +Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 2, as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// === Setup === +// - Include Necessary Files +// - Load Text Domain +// - Enqueue Stylesheets +// - Enqueue Admin Scripts +// - Enqueue Admin Styles +// === Template Filters === +// - Single Show/Playlist Template +// - Playlist Archive Template +// === Roles === +// - Add DJ Role and Capabilities +// - maybe Revoke Edit Show Capability +// === Menus === +// - Add Menus +// - maybe Remove Add Show Bar Link +// - Output Help Page +// - Output Export Page + +// ------------- +// === Setup === +// ------------- + +// --- include necessary files --- +require 'includes/post-types.php'; +require 'includes/master-schedule.php'; +require 'includes/shortcodes.php'; +require 'includes/class-dj-upcoming-widget.php'; +require 'includes/class-dj-widget.php'; +require 'includes/class-playlist-widget.php'; +require 'includes/support-functions.php'; + +// --- load the text domain --- +function radio_station_init() { + load_plugin_textdomain( 'radio-station', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); +} +add_action( 'plugins_loaded', 'radio_station_init' ); + +// --- flush rewrite rules on activate / deactivation --- +// 2.2.3: added this for custom post types rewrite flushing +register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); +register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); +function radio_station_flush_rewrite_flag() { + add_option( 'radio_station_flush_rewrite_rules', true ); +} + +// --- enqueue necessary stylesheets --- +function radio_station_load_styles() { + + $program_css = get_stylesheet_directory() . '/program-schedule.css'; + if ( file_exists( $program_css ) ) { + $version = filemtime( $program_css ); + $url = get_stylesheet_directory_uri() . '/program-schedule.css'; + } else { + $version = filemtime( dirname( __FILE__ ) . '/css/program-schedule.css' ); + $url = plugins_url( 'css/program-schedule.css', __FILE__ ); + } + wp_enqueue_style( 'program-schedule', $url, array(), $version ); + + // note: djonair.css style enqueueing moved to /includes/widget_djonair.php +} +add_action( 'wp_enqueue_scripts', 'radio_station_load_styles' ); + +// --- enqueue admin scripts --- +// jQuery is needed by the output of this code, so let us make sure we have it available +function radio_station_master_scripts() { + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'jquery-ui-datepicker' ); + // $url = plugins_url( 'css/jquery-ui.css', dirname(__FILE__).'/radio-station.php' ); + // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); + if ( is_ssl() ) { + $protocol = 'https'; + } else { + $protocol = 'http';} + $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; + wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.8.2' ); +} +add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); +add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); + +// --- add some style rules to certain parts of the admin area --- +function radio_station_load_admin_styles() { + global $post; + + // hide the first submenu item to prevent duplicate of main menu item + $styles = ' #toplevel_page_radio-station .wp-first-item { display: none; }' . "\n"; + + if ( isset( $post->post_type ) && ( 'playlist' === $post->post_type ) ) { + $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }' . "\n"; + } + + echo ''; + +} +add_action( 'admin_print_styles', 'radio_station_load_admin_styles' ); + + +// ------------------------ +// === Template Filters === +// ------------------------ + +// --- load the theme file for the playlist and show post types --- +function radio_station_load_template( $single_template ) { + global $post; + + if ( 'playlist' === $post->post_type ) { + // first check to see if there's a template in the active theme's directory + $user_theme = get_stylesheet_directory() . '/single-playlist.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = dirname( __FILE__ ) . '/templates/single-playlist.php'; + } + } + + if ( 'show' === $post->post_type ) { + // first check to see if there's a template in the active theme's directory + $user_theme = get_stylesheet_directory() . '/single-show.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = dirname( __FILE__ ) . '/templates/single-show.php'; + } + } + + return $single_template; +} +add_filter( 'single_template', 'radio_station_load_template' ); + +// --- load the theme file for the playlist archive pages --- +function radio_station_load_custom_post_type_template( $archive_template ) { + global $post; + + if ( is_post_type_archive( 'playlist' ) ) { + $playlist_archive_theme = get_stylesheet_directory() . '/archive-playlist.php'; + if ( ! file_exists( $playlist_archive_theme ) ) { + $archive_template = dirname( __FILE__ ) . '/templates/archive-playlist.php'; + } + } + + return $archive_template; +} +add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ); + + + +// ------------- +// === Roles === +// ------------- + +// --- set up the DJ role and user capabilities --- +function radio_station_set_roles() { + + global $wp_roles; + + // set only the necessary capabilities for DJs + $caps = array( + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'read_shows' => true, + 'edit_playlists' => true, + 'edit_published_playlists' => true, + // 'edit_others_playlists' => true, // uncomment to allow DJs to edit all playlists + 'read_playlists' => true, + 'publish_playlists' => true, + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, + ); + // $wp_roles->remove_role('dj'); // we need this here in case we ever update the capabilities list + // TODO: translate role name ? + $wp_roles->add_role( 'dj', 'DJ', $caps ); + + // grant all new capabilities to admin users + $wp_roles->add_cap( 'administrator', 'edit_shows', true ); + $wp_roles->add_cap( 'administrator', 'edit_published_shows', true ); + $wp_roles->add_cap( 'administrator', 'edit_others_shows', true ); + $wp_roles->add_cap( 'administrator', 'edit_private_shows', true ); + $wp_roles->add_cap( 'administrator', 'delete_shows', true ); + $wp_roles->add_cap( 'administrator', 'delete_published_shows', true ); + $wp_roles->add_cap( 'administrator', 'delete_others_shows', true ); + $wp_roles->add_cap( 'administrator', 'delete_private_shows', true ); + $wp_roles->add_cap( 'administrator', 'read_shows', true ); + $wp_roles->add_cap( 'administrator', 'publish_shows', true ); + $wp_roles->add_cap( 'administrator', 'edit_playlists', true ); + $wp_roles->add_cap( 'administrator', 'edit_published_playlists', true ); + $wp_roles->add_cap( 'administrator', 'edit_others_playlists', true ); + $wp_roles->add_cap( 'administrator', 'edit_private_playlists', true ); + $wp_roles->add_cap( 'administrator', 'delete_playlists', true ); + $wp_roles->add_cap( 'administrator', 'delete_published_playlists', true ); + $wp_roles->add_cap( 'administrator', 'delete_others_playlists', true ); + $wp_roles->add_cap( 'administrator', 'delete_private_playlists', true ); + $wp_roles->add_cap( 'administrator', 'read_playlists', true ); + $wp_roles->add_cap( 'administrator', 'publish_playlists', true ); +} +if ( is_multisite() ) { + add_action( 'init', 'radio_station_set_roles', 10, 0 ); +} else { + add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); +} + +// --- revoke the ability to edit a show if the user is not listed as a DJ on that show --- +function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $args ) { + + global $post, $wp_roles; + + $user = wp_get_current_user(); + + // determine which roles should have full access aside from administrator + $add_roles = array( 'administrator' ); + if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { + foreach ( $wp_roles->roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'publish_shows' === $capname && (bool) $capstatus ) { + $add_roles[] = $name; + } + } + } + } + + // exclude administrators and custom roles with appropriate capabilities... + // they should be able to do whatever they want + $found = false; + foreach ( $add_roles as $role ) { + if ( in_array( $role, $user->roles, true ) ) { + $found = true;} + } + + if ( ! $found ) { + + // limit this to published shows + if ( isset( $post->post_type ) ) { + if ( is_admin() && ( 'show' === $post->post_type ) && ( 'publish' === $post->post_status ) ) { + + $djs = get_post_meta( $post->ID, 'show_user_list', true ); + + if ( ! isset( $djs ) || empty( $djs ) ) { + $djs = array();} + + // if they are not listed, temporarily revoke editing ability for this post + if ( ! in_array( $user->ID, $djs, true ) ) { + $allcaps['edit_shows'] = false; + $allcaps['edit_published_shows'] = false; + } + } + } + } + return $allcaps; +} +add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); + + +// ------------- +// === Menus === +// ------------- + +function radio_station_add_admin_menus() { + + $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); + $position = apply_filters( 'radio_station_menu_position', 5 ); + $capability = 'publish_playlists'; + add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); + + add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); + add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); + add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); + add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); + add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); + add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); + add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); + add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); + add_submenu_page( 'radio-station', __( 'Help', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); + + // hack the submenu global to post type add/edit URLs + global $submenu; + foreach ( $submenu as $i => $menu ) { + if ( 'radio-station' === $i ) { + foreach ( $menu as $j => $item ) { + switch ( $item[2] ) { + case 'add-show': + // maybe remove the Add Show link for DJs + // $user = wp_get_current_user(); + // if ( in_array( 'dj', $user->roles ) ) { + if ( ! current_user_can( 'publish_shows' ) ) { + unset( $submenu[ $i ][ $j ] ); + } else { + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=show'; + } + break; + case 'shows': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=show'; + break; + case 'playlists': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=playlist'; + break; + case 'add-playlist': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=playlist'; + break; + case 'genres': + $submenu[ $i ][ $j ][2] = 'edit-tags.php?taxonomy=genres'; + break; + case 'schedule-overrides': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=override'; + break; + case 'add-override': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=override'; + break; + } + } + } + } +} +add_action( 'admin_menu', 'radio_station_add_admin_menus' ); + +// --- expand main menu fix for plugin submenu items --- +// 2.2.2: added fix for genre taxonomy page and post type editing +function radio_station_fix_genre_parent( $parent_file = '' ) { + global $pagenow, $post; + $post_types = array( 'show', 'playlist', 'override' ); + if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + $parent_file = 'radio-station'; + } elseif ( 'post.php' === $pagenow && in_array( $post->post_type, $post_types, true ) ) { + $parent_file = 'radio-station'; + } + return $parent_file; +} +add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); + +// --- genre taxonomy submenu item fix --- +// 2.2.2: so genre submenu item link is set to current (bold) +function radio_station_genre_submenu_fix() { + global $pagenow; + if ( 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + echo ""; + } +} +add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); + +// --- remove the Add Show link for DJs from the wp admin bar --- +// 2.2.2: re-add new post type items to admin bar +// (as no longer automatically added by register_post_type) +function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { + + // --- new show --- + if ( current_user_can( 'publish_shows' ) ) { + + $args = array( + 'id' => 'new-show', + 'title' => __( 'Show', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=show' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new playlist --- + if ( current_user_can( 'publish_playlists' ) ) { + $args = array( + 'id' => 'new-playlist', + 'title' => __( 'Playlist', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=playlist' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new schedule override --- + if ( current_user_can( 'publish_shows' ) ) { + $args = array( + 'id' => 'new-override', + 'title' => __( 'Override', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=override' ), + ); + $wp_admin_bar->add_node( $args ); + } + +} +add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); + +// --- output help page --- +function radio_station_plugin_help() { + + // 2.2.2: include patreon button link + echo radio_station_patreon_blurb( false ); + + // include help template + include dirname( __FILE__ ) . '/templates/help.php'; +} + +// --- output playlist export page --- +function radio_station_admin_export() { + global $wpdb; + + // first, delete any old exports from the export directory + $dir = dirname( __FILE__ ) . '/export/'; + if ( is_dir( $dir ) ) { + $get_contents = opendir( $dir ); + while ( $file = readdir( $get_contents ) ) { + if ( '.' !== $file && '..' !== $file ) { + unlink( $dir . $file ); + } + } + closedir( $get_contents ); + } + + // watch for form submission + if ( isset( $_POST['export_action'] ) && ( 'station_playlist_export' === $_POST['export_action'] ) ) { + + // validate referrer and nonce field + check_admin_referer( 'station_export_valid' ); + + $start = $_POST['station_export_start_year'] . '-' . $_POST['station_export_start_month'] . '-' . $_POST['station_export_start_day']; + $start .= ' 00:00:00'; + $end = $_POST['station_export_end_year'] . '-' . $_POST['station_export_end_month'] . '-' . $_POST['station_export_end_day']; + $end .= ' 23:59:59'; + + // fetch all records that were created between the start and end dates + $sql = + 'SELECT `posts`.`ID`, `posts`.`post_date` FROM ' . $wpdb->prefix . "posts AS `posts` + WHERE `posts`.`post_type` = 'playlist' + AND `posts`.`post_status` = 'publish' + AND TO_DAYS(`posts`.`post_date`) >= TO_DAYS(%s) + AND TO_DAYS(`posts`.`post_date`) <= TO_DAYS(%s) + ORDER BY `posts`.`post_date` ASC;"; + // prepare query before executing + $query = $wpdb->prepare( $sql, array( $start, $end ) ); + $playlists = $wpdb->get_results( $query ); + + if ( ! $playlists ) { + $list = 'No playlists found for this period.';} + + // fetch the tracks for each playlist from the wp_postmeta table + foreach ( $playlists as $i => $playlist ) { + + $songs = get_post_meta( $playlist->ID, 'playlist', true ); + + // remove any entries that are marked as 'queued' + foreach ( $songs as $j => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[ $j ] );} + } + + $playlists[ $i ]->songs = $songs; + } + + $output = ''; + + $date = ''; + foreach ( $playlists as $playlist ) { + if ( ! isset( $playlist->post_date ) ) { + continue; + } + $playlist_datetime = explode( ' ', $playlist->post_date ); + $playlist_post_date = array_shift( $playlist_datetime ); + if ( empty( $date ) || $date !== $playlist_post_date ) { + $date = $playlist_post_date; + $output .= $date . "\n\n"; + } + + foreach ( $playlist->songs as $song ) { + $output .= $song['playlist_entry_artist'] . ' || ' . $song['playlist_entry_song'] . ' || ' . $song['playlist_entry_album'] . ' || ' . $song['playlist_entry_label'] . "\n"; + } + } + + // save as file + $dir = dirname( __FILE__ ) . '/export/'; + $file = $date . '-export.txt'; + if ( ! file_exists( $dir ) ) { + wp_mkdir_p( $dir );} + + $f = fopen( $dir . $file, 'w' ); + fwrite( $f, $output ); + fclose( $f ); + + // display link to file + $url = get_bloginfo( 'url' ) . '/wp-content/plugins/radio-station/tmp/' . $file; + echo wp_kses_post( '' ); + } + + // display the export page + include dirname( __FILE__ ) . '/templates/admin-export.php'; + +} + + +// -------------------- +// === Admin Notice === +// -------------------- + +// --- plugin announcement notice --- +// 2.2.2: added plugin announcement notice +function radio_station_announcement_notice() { + + // --- bug out if already dismissed --- + if ( get_option( 'radio_station_announcement_dismissed' ) ) { + return; + } + + // --- bug out on certain plugin pages --- + $pages = array( 'radio-station', 'radio-station-help' ); + if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages, true ) ) { + return; + } + + // --- display plugin announcement --- + echo '
      '; + echo radio_station_patreon_blurb(); + echo ''; + echo '
      '; +} +add_action( 'admin_notices', 'radio_station_announcement_notice' ); + +// --- dismiss plugin announcement notice --- +// 2.2.2: AJAX for announcement notice dismissal +function radio_station_announcement_dismiss() { + if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { + update_option( 'radio_station_announcement_dismissed', true ); + echo ""; + exit; + } +} +add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); + +// --- Patreon supporter blurb --- +// 2.2.2: added simple patreon supporter blurb +function radio_station_patreon_blurb( $dismissable = true ) { + + $blurb = '
        '; + $blurb .= '
      • '; + $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); + $blurb .= ''; + $blurb .= '
      • '; + $blurb .= '
      • '; + $blurb .= '' . __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) . '
        '; + $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) . ',
        '; + $blurb .= __( 'since June 2019', 'radio-station' ) . ', '; + $blurb .= '' . __( 'Radio Station', 'radio-station' ) . ' '; + $blurb .= __( ' plugin development has been actively taken over by', 'radio-station' ); + $blurb .= ' Netmix.
        '; + $blurb .= __( 'We invite you to', 'radio-station' ); + $blurb .= ' '; + $blurb .= __( 'Become a Radio Station Patreon Supporter', 'radio-station' ); + $blurb .= ' ' . __( 'to make it better for everyone', 'radio-station' ) . '!'; + $blurb .= '
      • '; + $blurb .= '
      • '; + $blurb .= radio_station_patreon_button(); + $blurb .= '
      • '; + $blurb .= '
      '; + if ( $dismissable ) { + ?> + '; + $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); + $blurb .= ''; + $blurb .= ''; + $blurb .= '
    '; + } + return $blurb; +} + +// --- Patreon supporter button --- +// 2.2.2: added simple patreon supporter image button +function radio_station_patreon_button() { + $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); + $button = ''; + $button .= ''; + $button .= ''; + return $button; +} diff --git a/templates/admin-export.php b/templates/admin-export.php index be0fbbe..a44c8df 100644 --- a/templates/admin-export.php +++ b/templates/admin-export.php @@ -1,5 +1,5 @@
    -

    +

    @@ -8,96 +8,143 @@ - - + + - + @@ -106,9 +153,9 @@
    - + - +
    - + - + - +
      - +
    -
    \ No newline at end of file +
    diff --git a/templates/archive-playlist.php b/templates/archive-playlist.php index b70f87c..8d50001 100644 --- a/templates/archive-playlist.php +++ b/templates/archive-playlist.php @@ -1,79 +1,83 @@ - - -
    -
    - - - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'playlist_show_id', - 'value' => $show_id - ) - ), - 'paged' => $paged - ); - $loop = new WP_Query( $args ); - // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); - ?> - have_posts() ) : $loop->the_post(); ?> - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - - \ No newline at end of file + + +
    +
    + + + + + + + + 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'playlist_show_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, + ); + $loop = new WP_Query( $args ); + // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); + ?> + have_posts() ) : + $loop->the_post(); + ?> + + + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + + diff --git a/templates/author.php b/templates/author.php index 323799f..07b5e32 100644 --- a/templates/author.php +++ b/templates/author.php @@ -1,110 +1,115 @@ - - - - -
    -
    - - roles ) ): ?> - - - - -
    ID, $avatar_size ); ?>
    -
    - - description; ?> - -
    - URL: user_url; ?>
    - Email: user_email; ?>
    - AIM: aim; ?>
    - Jabber: jabber; ?>
    - YIM: yim; ?>
    -
    - -
    - - - - - - - - - - - - -
    -
    - -
    -
    -

    - -
    -
    - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - - -
    -
    - - - \ No newline at end of file + + + + +
    +
    + + roles, true ) ) : ?> + + + + +
    ID, $avatar_size ); ?>
    +
    + + description ); ?> + +
    + URL: user_url ); ?>
    + Email: user_email ); ?>
    + AIM: aim ); ?>
    + Jabber: jabber ); ?>
    + YIM: yim ); ?>
    +
    + +
    + + + + + + + + + + + + +
    +
    + +
    +
    +

    + +
    +
    + + + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + + +
    +
    + + + diff --git a/templates/help.php b/templates/help.php index 6e39550..acc7522 100644 --- a/templates/help.php +++ b/templates/help.php @@ -6,7 +6,7 @@
    I'm seeing 404 Not Found errors when I click on the link for a show! -

    Try re-saving your site's permalink settings. Wordpress sometimes gets confused with a new custom post type is added.

    +

    Try re-saving your site's permalink settings. WordPress sometimes gets confused with a new custom post type is added.


    @@ -136,7 +136,7 @@

    First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the playlist archive. +Then, create a Page in WordPress to hold the playlist archive.

    Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear.

    @@ -146,7 +146,7 @@

    Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the blog archive. +Then, create a Page in WordPress to hold the blog archive.

    Under Page Attributes, set the template to Show Blog Archive.

    diff --git a/templates/playlist-archive-template.php b/templates/playlist-archive-template.php index 34849cd..3be15d8 100644 --- a/templates/playlist-archive-template.php +++ b/templates/playlist-archive-template.php @@ -1,75 +1,80 @@ - - - -
    -
    - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'paged' => $paged, - 'post_status' => 'publish' - ); - $loop = new WP_Query( $args ); - ?> - have_posts()) : $loop->the_post(); ?> - -
    > -
    -

    - -
    - - - - post_date)); ?> -
    -
    -
    - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - \ No newline at end of file + + + +
    +
    + + + + + + 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'paged' => $paged, + 'post_status' => 'publish', + ); + $loop = new WP_Query( $args ); + ?> + have_posts() ) : + $loop->the_post(); + ?> + +
    > +
    +

    + +
    + + - + post_date ) ) ); ?> +
    +
    +
    + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + diff --git a/templates/show-blog-archive-template.php b/templates/show-blog-archive-template.php index 4023a08..f9456de 100644 --- a/templates/show-blog-archive-template.php +++ b/templates/show-blog-archive-template.php @@ -1,92 +1,90 @@ - - - -
    -
    - - - - - - - $post_types, - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $show_id - ) - ), - 'paged' => $paged - ); - $temp = $wp_query; - $wp_query = null; - $wp_query = new WP_Query( $args ); - ?> - have_posts()) : $wp_query->the_post(); ?> - -
    > -
    -

    - -
    - - - -
    -
    - -
    - -
    -
    - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - \ No newline at end of file + + + +
    +
    + + + + + + + $post_types, + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'post_showblog_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, + ); + $archive_query = new WP_Query( $args ); + while ( $archive_query->have_posts() ) : + $archive_query->the_post(); + ?> + +
    > +
    +

    + +
    + - + +
    +
    + +
    + +
    +
    + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + diff --git a/templates/single-playlist.php b/templates/single-playlist.php index 998baa5..cf42a47 100644 --- a/templates/single-playlist.php +++ b/templates/single-playlist.php @@ -1,69 +1,86 @@ - -
    -
    - - - -
    > -
    - ID, 'playlist_show_id', true ); ?> -

    -

    -
    - -
    - - '' ) ); ?> - - - - - ID, 'playlist', true ); ?> - - -
    - - - - - - - - - - - - > - - - - - - - - -
    -
    - -
    - -
    - - - - - -
    -
    - - - -
    -
    - - \ No newline at end of file + +
    +
    + + + +
    > +
    + ID, 'playlist_show_id', true ); + ?> +

    +

    +
    + +
    + + '', + ) + ); + ?> + + + + + ID, 'playlist', true ); ?> + + +
    + + + + + + + + + + + + > + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    +
    + + + +
    +
    + + diff --git a/templates/single-show.php b/templates/single-show.php index 04053ec..75d1023 100644 --- a/templates/single-show.php +++ b/templates/single-show.php @@ -1,132 +1,165 @@ - - -
    -
    - - -
    > -
    -

    -
    - -
    - - -
    -

    :

    - '.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($djs) == 2 ) ) - || ( ( count($djs) > 2 ) && ( $count == ( count($djs) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($djs) ) && ( count($djs) > 2 ) ) { - echo ', '; - } - } - } - ?> -
    - -
    -

    :

    - 'genres', 'title_li' => '') ); - ?> -
      - '.$genre->name.''; - } - ?> -
    -
    - -

    - - - - - -
    - -
    - -
    -

    -
      - '; - echo $weekday.' - '.$shift['start_hour'].':'.$shift['start_min'].' '.$shift['start_meridian'].' - '.$shift['end_hour'].':'.$shift['end_min'].' '.$shift['end_meridian']; - echo ''; - } - } - - // 24-hour time - /* - $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); - if ( $shifts ) { - foreach ( $shifts as $shift ) { - $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); - $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); - - $weekday = radio_station_translate_weekday( $shift['day'] ); - echo '
    • '; - echo $weekday.' - '.$start.' - '.$end; - echo '
    • '; - } - } - */ - ?> -
    -
    - -
    -

    - -
    - - - - - - '' ) ); ?> -
    -
    - - - - - -
    -
    - - \ No newline at end of file + + +
    +
    + + +
    > +
    +

    +
    + +
    + + +
    +

    :

    + ' . esc_html( $user_info->display_name ) . ''; + + $dj_count = count( $djs ); + if ( ( 1 === $count && 2 === $dj_count ) || ( $dj_count > 2 && $count === $dj_count - 1 ) ) { + echo ' and '; + } elseif ( ( $count < count( $djs ) ) && ( count( $djs ) > 2 ) ) { + echo ', '; + } + } + } + ?> +
    + +
    +

    :

    + 'genres', 'title_li' => '') ); + ?> +
      + ' . esc_html( $genre->name ) . ''; + } + ?> +
    +
    + +

    + + + + + +
    + + + +
    + +
    +

    +
      + '; + echo esc_html( $weekday . ' - ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] . ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian'] ); + echo ''; + } + } + + // 24-hour time + /* + $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); + if ( $shifts ) { + foreach ( $shifts as $shift ) { + $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); + $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); + + $weekday = radio_station_translate_weekday( $shift['day'] ); + echo '
    • '; + echo $weekday.' - '.$start.' - '.$end; + echo '
    • '; + } + } + */ + ?> +
    +
    + +
    +

    + +
    + + + + + + '', + ) + ); + ?> +
    +
    + + + + + +
    +
    + + From e338a79d98d52fbe7c1e78d2684b0c976d452cf9 Mon Sep 17 00:00:00 2001 From: Mike Garrett Date: Tue, 20 Aug 2019 11:52:25 -0400 Subject: [PATCH 035/377] Version bump and readme update. --- radio-station.php | 4 ++-- readme.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/radio-station.php b/radio-station.php index 73177a9..6b94e9d 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,14 +1,14 @@ -Version: 2.2.4 +Version: 2.2.5 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station diff --git a/readme.txt b/readme.txt index 9beede2..411a908 100644 --- a/readme.txt +++ b/readme.txt @@ -246,6 +246,9 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + = 2.2.4 = * added title position and avatar width options to widgets * added missing DJ author links as new option to widgets From 3aab6898ce792829e21512a1f6544b152d083674 Mon Sep 17 00:00:00 2001 From: Mike Garrett Date: Tue, 20 Aug 2019 16:25:09 -0400 Subject: [PATCH 036/377] Hotfix: overly ambitious escaping. --- includes/class-dj-upcoming-widget.php | 9 ++++----- includes/class-dj-widget.php | 8 ++++---- includes/class-playlist-widget.php | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/includes/class-dj-upcoming-widget.php b/includes/class-dj-upcoming-widget.php index 44d632e..1d07d40 100644 --- a/includes/class-dj-upcoming-widget.php +++ b/includes/class-dj-upcoming-widget.php @@ -162,7 +162,7 @@ public function update( $new_instance, $old_instance ) { // --- output widget display --- public function widget( $args, $instance ) { - echo wp_kses_post( $args['before_widget'] ); + echo $args['before_widget']; $title = empty( $instance['title'] ) ? '' : apply_filters( 'widget_title', $instance['title'] ); $display_djs = $instance['display_djs']; $djavatar = $instance['djavatar']; @@ -199,11 +199,11 @@ public function widget( $args, $instance ) {
      @@ -213,7 +213,6 @@ public function widget( $args, $instance ) { if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { foreach ( $djs['all'] as $showtime => $dj ) { - if ( is_array( $dj ) && isset( $dj['type'] ) && 'override' === $dj['type'] ) { ?>
    • @@ -441,7 +440,7 @@ public function widget( $args, $instance ) { } wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); - echo wp_kses_post( $args['after_widget'] ); + echo $args['after_widget']; } } diff --git a/includes/class-dj-widget.php b/includes/class-dj-widget.php index 8cf752f..fc7d202 100644 --- a/includes/class-dj-widget.php +++ b/includes/class-dj-widget.php @@ -183,7 +183,7 @@ public function update( $new_instance, $old_instance ) { // --- widget output --- public function widget( $args, $instance ) { - echo wp_kses_post( $args['before_title'] ); + echo $args['before_widget']; $title = empty( $instance['title'] ) ? '' : apply_filters( 'widget_title', $instance['title'] ); $display_djs = $instance['display_djs']; $djavatar = $instance['djavatar']; @@ -222,11 +222,11 @@ public function widget( $args, $instance ) { ?>
        @@ -515,7 +515,7 @@ public function widget( $args, $instance ) { // 2.2.4: fix to media argument wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); - echo wp_kses_post( $args['after_widget'] ); + echo $args['after_widget']; } } diff --git a/includes/class-playlist-widget.php b/includes/class-playlist-widget.php index f26eb0e..a094a2d 100644 --- a/includes/class-playlist-widget.php +++ b/includes/class-playlist-widget.php @@ -88,7 +88,7 @@ public function update( $new_instance, $old_instance ) { // --- output widget display --- public function widget( $args, $instance ) { - echo wp_kses_post( $args['before_widget'] ); + echo $args['before_widget']; $title = empty( $instance['title'] ) ? '' : apply_filters( 'widget_title', $instance['title'] ); $artist = $instance['artist']; $song = $instance['song']; @@ -102,11 +102,11 @@ public function widget( $args, $instance ) { ?>
        Date: Tue, 20 Aug 2019 16:33:19 -0400 Subject: [PATCH 037/377] Fixes for common problems v2.2.6 Reorganize master-list shortcode into templates, Add constant for plugin directory, WP_Query instead of get_posts, new posts_per_page and tax_query, fixes for undefined indexes, various fixes for raw mysql queries, typecasting to support strict comparisons. --- includes/master-schedule.php | 483 +----------------------------- includes/post-types.php | 7 +- includes/shortcodes.php | 50 ++-- radio-station.php | 50 ++-- readme.txt | 9 + templates/master-list-default.php | 206 +++++++++++++ templates/master-list-div.php | 162 ++++++++++ templates/master-list-list.php | 109 +++++++ 8 files changed, 554 insertions(+), 522 deletions(-) create mode 100644 templates/master-list-default.php create mode 100644 templates/master-list-div.php create mode 100644 templates/master-list-list.php diff --git a/includes/master-schedule.php b/includes/master-schedule.php index c860917..59569d6 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -61,10 +61,10 @@ function radio_station_master_schedule( $atts ) { // get the show schedules, excluding shows marked as inactive $show_shifts = $wpdb->get_results( "SELECT meta.post_id, meta.meta_value - FROM {$wpdb->prefix}postmeta AS meta - JOIN {$wpdb->prefix}postmeta AS active + FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS active ON meta.post_id = active.post_id - JOIN {$wpdb->prefix}posts as posts + JOIN {$wpdb->posts} as posts ON posts.ID = meta.post_id WHERE meta.meta_key = 'show_sched' AND posts.post_status = 'publish' AND @@ -85,17 +85,17 @@ function radio_station_master_schedule( $atts ) { foreach ( $shift->meta_value as $time ) { // switch to 24-hour time - if ( 'pm' === $time['start_meridian'] && 12 !== $time['start_hour'] ) { + if ( 'pm' === $time['start_meridian'] && 12 !== (int) $time['start_hour'] ) { $time['start_hour'] += 12; } - if ( 'am' === $time['start_meridian'] && 12 === $time['start_hour'] ) { + if ( 'am' === $time['start_meridian'] && 12 === (int) $time['start_hour'] ) { $time['start_hour'] = 0; } - if ( 'pm' === $time['end_meridian'] && 12 !== $time['end_hour'] ) { + if ( 'pm' === $time['end_meridian'] && 12 !== (int) $time['end_hour'] ) { $time['end_hour'] += 12; } - if ( 'am' === $time['end_meridian'] && 12 === $time['end_hour'] ) { + if ( 'am' === $time['end_meridian'] && 12 === (int) $time['end_hour'] ) { $time['end_hour'] = 0; } @@ -183,474 +183,11 @@ function radio_station_master_schedule( $atts ) { $output = ''; if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { - - // output as a list - $flip = $days_of_the_week; - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $mins ) { - foreach ( $mins as $fmin => $fshow ) { - $flip[ $day ][ $hour ][ $fmin ] = $fshow; - } - } - } - - $output .= '
          '; - - foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
        • '; - $output .= '' . $display_day . ''; - $output .= '
            '; - foreach ( $hours as $hour => $mins ) { - - foreach ( $mins as $min => $show ) { - $output .= '
          • '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $show['id'] ) . ''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - $output .= ''; - - $names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $output .= ' with '; - foreach ( $names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } else { - - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { - $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; - } - - $link = get_post_meta( $show['id'], 'show_file', true ); - if ( $link && ! empty( $link ) ) { - $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
          • '; - } - } - $output .= '
          '; - $output .= '
        • '; - } - - $output .= '
        '; - + require RADIO_STATION_DIR . '/templates/master-list-list.php'; } elseif ( 'divs' === $atts['list'] ) { - - // output some dynamic styles - $output .= ''; - - // output the schedule - $output .= radio_station_master_fetch_js_filter(); - $output .= '
        '; - $weekdays = array_keys( $days_of_the_week ); - - $output .= '
        '; - $output .= '
         
        '; - foreach ( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
        ' . $translated . '
        '; - } - $output .= '
        '; - - foreach ( $master_list as $hour => $days ) { - - $output .= '
        '; - - // output the hour labels - $output .= '
        '; - if ( 12 === (int) $timeformat ) { - $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format - } - $output .= '
        '; - - foreach ( $weekdays as $weekday ) { - $output .= '
        '; - if ( isset( $days[ $weekday ] ) ) { - foreach ( $days[ $weekday ] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes( $term->name ) . ' '; - } - - $output .= '
        '; - - // featured image - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $showdata['id'] ) . ''; - } else { - $output .= get_the_title( $showdata['id'] ); - } - $output .= ''; - - // list of DJs - if ( $atts['show_djs'] ) { - - $output .= ''; - - $names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' with '; - - foreach ( $names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - // designate as encore - if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - // link to media file - $link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $link && ! empty( $link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
        '; - - if ( '00' !== $showdata['time']['end_min'] ) { - $output .= '
        '; - } - } - - $output .= '
        '; // end master-show-entry - } - } - $output .= '
        '; // end master-schedule-weekday - } - $output .= '
        '; // end master-schedule-hour - } - $output .= '
        '; // end master-schedule-divs - + require RADIO_STATION_DIR . '/templates/master-list-div.php'; } else { - - // create the output in a table - $output .= radio_station_master_fetch_js_filter(); - - $output .= ''; - - // output the headings in the correct order - $output .= ''; - foreach ( $days_of_the_week as $weekday => $info ) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= ''; - } - $output .= ''; - // $output .= ''; - - if ( ! isset( $nextskip ) ) { - $nextskip = array();} - - foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= ''; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] == $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span'] - 1; - $curskip[ $x ]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( 0 === $shift['time']['start_hour'] && 0 === $shift['time']['end_hour'] ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( 0 === $shift['time']['end_hour'] && 0 !== $shift['time']['start_hour'] ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning - // print_r($shift);die('moo'); - if ( isset( $shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="' . $rowspan . '"'; - // add to both arrays - $curskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - $nextskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ( $continue ) { - continue;} - - $output .= ''; - - foreach ( $min as $shift ) { - - //print_r($shift); - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes( $term->name ) . ' '; - } - - $output .= '
        '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $shift['id'] ) ) { - $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $shift['id'] ) . ''; - } else { - $output .= get_the_title( $shift['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - - $output .= ''; - - $names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' ' . __( 'with', 'radio-station' ) . ' '; - foreach ( $names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - $link = get_post_meta( $shift['id'], 'show_file', true ); - if ( $link && ! empty( $link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
        '; - } - $output .= ''; - } - - $output .= '
        '; - } - $output .= '
        ' . $heading . '
        '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
        '; - - if ( 12 === (int) $timeformat ) { - if ( 0 === $hour ) { - $output .= '12am'; - } elseif ( $hour < 12 ) { - $output .= $hour . 'am'; - } elseif ( 12 === $hour ) { - $output .= '12pm'; - } else { - $output .= ( $hour - 12 ) . 'pm'; - } - } else { - if ( $hour < 10 ) { - $output .= '0';} - $output .= $hour . ':00'; - } - - $output .= '
        '; + require RADIO_STATION_DIR . '/templates/master-list-default.php'; } return $output; diff --git a/includes/post-types.php b/includes/post-types.php index e0750de..447f0e9 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -296,8 +296,7 @@ function radio_station_myplaylist_inner_custom_box() { // --- get the saved meta as an arry --- $entries = get_post_meta( $post->ID, 'playlist', false ); - // print_r($entries); - $c = 1; + $c = 1; echo ''; echo ''; @@ -332,6 +331,7 @@ function radio_station_myplaylist_inner_custom_box() { echo ''; echo ''; +foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= ''; +} +$output .= ''; +// $output .= ''; + +if ( ! isset( $nextskip ) ) { + $nextskip = array(); +} + +foreach ( $master_list as $hour => $days ) { + + $output .= ''; + $output .= ''; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] === $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[ $x ]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $shift ) { + + if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { + //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { + // show runs from before midnight night until the next morning + if ( isset( $shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue; + } + + $output .= ''; + + foreach ( $min as $shift ) { + + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); + $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; + foreach ( $terms as $shift_term ) { + $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; + } + + $output .= '
        '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $shift['id'] ) ) { + $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $shift['id'] ) . ''; + } else { + $output .= get_the_title( $shift['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); + $count = 0; + + if ( $dj_names ) { + + $output .= ' ' . __( 'with', 'radio-station' ) . ' '; + foreach ( $dj_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $dj_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $shift['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
        '; + } + $output .= ''; + } + + $output .= '
        '; +} +$output .= '
        ' . esc_html__( 'New', 'radio-station' ) . ' '; + $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; echo ''; echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; @@ -901,7 +901,6 @@ function radio_station_myplaylist_inner_sched_custom_box() { // --- get the saved meta as an array --- $shifts = get_post_meta( $post->ID, 'show_sched', false ); - // print_r($shifts); $c = 0; if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { @@ -1046,7 +1045,7 @@ function radio_station_myplaylist_inner_sched_custom_box() { >pm - +
      • />
      • diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 19e298d..55878df 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -78,14 +78,14 @@ function radio_station_shortcode_get_playlists_for_show( $atts ) { } $args = array( - 'numberposts' => $atts['limit'], - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $atts['show'], + 'posts_per_page' => $atts['limit'], + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + 'meta_key' => 'playlist_show_id', + 'meta_value' => $atts['show'], ); $query = new WP_Query( $args ); @@ -127,27 +127,33 @@ function radio_station_shortcode_list_shows( $atts ) { // grab the published shows $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'meta_query' => array( + 'posts_per_page' => 1000, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'meta_query' => array( array( 'key' => 'show_active', 'value' => 'on', ), ), ); - if ( ! empty( $atts['genre'] ) ) { - $args['genres'] = $atts['genre'];} + $args['tax_query'] = array( + array( + 'taxonomy' => 'genres', + 'field' => 'slug', + 'terms' => $atts['genre'], + ), + ); + } $query = new WP_Query( $args ); // if there are no shows saved, return nothing - if ( $query->found_posts <= 0 ) { + if ( ! $query->have_posts() ) { return false; } @@ -155,13 +161,15 @@ function radio_station_shortcode_list_shows( $atts ) { $output .= '
        '; $output .= ''; $output .= '
        '; + wp_reset_postdata(); return $output; } add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); diff --git a/radio-station.php b/radio-station.php index 6b94e9d..2f0ca3f 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,14 +1,14 @@ -Version: 2.2.5 +Version: 2.2.6 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -52,14 +52,15 @@ // === Setup === // ------------- +define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); // --- include necessary files --- -require 'includes/post-types.php'; -require 'includes/master-schedule.php'; -require 'includes/shortcodes.php'; -require 'includes/class-dj-upcoming-widget.php'; -require 'includes/class-dj-widget.php'; -require 'includes/class-playlist-widget.php'; -require 'includes/support-functions.php'; +require RADIO_STATION_DIR . '/includes/post-types.php'; +require RADIO_STATION_DIR . '/includes/master-schedule.php'; +require RADIO_STATION_DIR . '/includes/shortcodes.php'; +require RADIO_STATION_DIR . '/includes/class-dj-upcoming-widget.php'; +require RADIO_STATION_DIR . '/includes/class-dj-widget.php'; +require RADIO_STATION_DIR . '/includes/class-playlist-widget.php'; +require RADIO_STATION_DIR . '/includes/support-functions.php'; // --- load the text domain --- function radio_station_init() { @@ -83,7 +84,7 @@ function radio_station_load_styles() { $version = filemtime( $program_css ); $url = get_stylesheet_directory_uri() . '/program-schedule.css'; } else { - $version = filemtime( dirname( __FILE__ ) . '/css/program-schedule.css' ); + $version = filemtime( RADIO_STATION_DIR . '/css/program-schedule.css' ); $url = plugins_url( 'css/program-schedule.css', __FILE__ ); } wp_enqueue_style( 'program-schedule', $url, array(), $version ); @@ -97,7 +98,7 @@ function radio_station_load_styles() { function radio_station_master_scripts() { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'jquery-ui-datepicker' ); - // $url = plugins_url( 'css/jquery-ui.css', dirname(__FILE__).'/radio-station.php' ); + // $url = plugins_url( 'css/jquery-ui.css', RADIO_STATION_DIR.'/radio-station.php' ); // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); if ( is_ssl() ) { $protocol = 'https'; @@ -138,7 +139,7 @@ function radio_station_load_template( $single_template ) { // first check to see if there's a template in the active theme's directory $user_theme = get_stylesheet_directory() . '/single-playlist.php'; if ( ! file_exists( $user_theme ) ) { - $single_template = dirname( __FILE__ ) . '/templates/single-playlist.php'; + $single_template = RADIO_STATION_DIR . '/templates/single-playlist.php'; } } @@ -146,7 +147,7 @@ function radio_station_load_template( $single_template ) { // first check to see if there's a template in the active theme's directory $user_theme = get_stylesheet_directory() . '/single-show.php'; if ( ! file_exists( $user_theme ) ) { - $single_template = dirname( __FILE__ ) . '/templates/single-show.php'; + $single_template = RADIO_STATION_DIR . '/templates/single-show.php'; } } @@ -161,7 +162,7 @@ function radio_station_load_custom_post_type_template( $archive_template ) { if ( is_post_type_archive( 'playlist' ) ) { $playlist_archive_theme = get_stylesheet_directory() . '/archive-playlist.php'; if ( ! file_exists( $playlist_archive_theme ) ) { - $archive_template = dirname( __FILE__ ) . '/templates/archive-playlist.php'; + $archive_template = RADIO_STATION_DIR . '/templates/archive-playlist.php'; } } @@ -421,7 +422,7 @@ function radio_station_plugin_help() { echo radio_station_patreon_blurb( false ); // include help template - include dirname( __FILE__ ) . '/templates/help.php'; + include RADIO_STATION_DIR . '/templates/help.php'; } // --- output playlist export page --- @@ -429,7 +430,7 @@ function radio_station_admin_export() { global $wpdb; // first, delete any old exports from the export directory - $dir = dirname( __FILE__ ) . '/export/'; + $dir = RADIO_STATION_DIR . '/export/'; if ( is_dir( $dir ) ) { $get_contents = opendir( $dir ); while ( $file = readdir( $get_contents ) ) { @@ -453,12 +454,13 @@ function radio_station_admin_export() { // fetch all records that were created between the start and end dates $sql = - 'SELECT `posts`.`ID`, `posts`.`post_date` FROM ' . $wpdb->prefix . "posts AS `posts` - WHERE `posts`.`post_type` = 'playlist' - AND `posts`.`post_status` = 'publish' - AND TO_DAYS(`posts`.`post_date`) >= TO_DAYS(%s) - AND TO_DAYS(`posts`.`post_date`) <= TO_DAYS(%s) - ORDER BY `posts`.`post_date` ASC;"; + "SELECT posts.ID, posts.post_date + FROM {$wpdb->posts} AS posts + WHERE posts.post_type = 'playlist' AND + posts.post_status = 'publish' AND + TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND + TO_DAYS(posts.post_date) <= TO_DAYS(%s) + ORDER BY posts.post_date ASC"; // prepare query before executing $query = $wpdb->prepare( $sql, array( $start, $end ) ); $playlists = $wpdb->get_results( $query ); @@ -500,7 +502,7 @@ function radio_station_admin_export() { } // save as file - $dir = dirname( __FILE__ ) . '/export/'; + $dir = RADIO_STATION_DIR . '/export/'; $file = $date . '-export.txt'; if ( ! file_exists( $dir ) ) { wp_mkdir_p( $dir );} @@ -515,7 +517,7 @@ function radio_station_admin_export() { } // display the export page - include dirname( __FILE__ ) . '/templates/admin-export.php'; + include RADIO_STATION_DIR . '/templates/admin-export.php'; } diff --git a/readme.txt b/readme.txt index 411a908..403f541 100644 --- a/readme.txt +++ b/readme.txt @@ -246,6 +246,15 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.6 = +* Reorganize master-list shortcode into templates, +* Add constant for plugin directory, +* WP_Query instead of get_posts, +* new posts_per_page and tax_query, +* fixes for undefined indexes, +* fixes for raw mysql queries, +* typecasting to support strict comparisons. + = 2.2.5 = * WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) diff --git a/templates/master-list-default.php b/templates/master-list-default.php new file mode 100644 index 0000000..86be524 --- /dev/null +++ b/templates/master-list-default.php @@ -0,0 +1,206 @@ +'; + +// output the headings in the correct order +$output .= '
        ' . $heading . '
        '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
        '; + + if ( 12 === (int) $timeformat ) { + if ( 0 === $hour ) { + $output .= '12am'; + } elseif ( (int) $hour < 12 ) { + $output .= $hour . 'am'; + } elseif ( 12 === (int) $hour ) { + $output .= '12pm'; + } else { + $output .= ( $hour - 12 ) . 'pm'; + } + } else { + if ( $hour < 10 ) { + $output .= '0'; + } + $output .= $hour . ':00'; + } + + $output .= '
        '; diff --git a/templates/master-list-div.php b/templates/master-list-div.php new file mode 100644 index 0000000..a5059ab --- /dev/null +++ b/templates/master-list-div.php @@ -0,0 +1,162 @@ +'; +for ( $i = 2; $i < 24; $i++ ) { + $rowheight = $atts['divheight'] * $i; + + $output .= '#master-schedule-divs .rowspan' . $i . ' { '; + $output .= 'height: ' . ( $rowheight ) . 'px; }'; +} + +$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; +$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; +$output .= ''; + +// output the schedule +$output .= radio_station_master_fetch_js_filter(); +$output .= '
        '; +$weekdays = array_keys( $days_of_the_week ); + +$output .= '
        '; +$output .= '
         
        '; +foreach ( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
        ' . $translated . '
        '; +} + $output .= '
        '; + +foreach ( $master_list as $hour => $days ) { + + $output .= '
        '; + + // output the hour labels + $output .= '
        '; + if ( 12 === (int) $timeformat ) { + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format + } + $output .= '
        '; + + foreach ( $weekdays as $weekday ) { + $output .= '
        '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $showdata ) { + + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); + $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; + foreach ( $terms as $show_term ) { + $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + } + + $output .= '
        '; + + // featured image + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); + } + $output .= ''; + } + + // title + link to page if requested + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $showdata['id'] ) . ''; + } else { + $output .= get_the_title( $showdata['id'] ); + } + $output .= ''; + + // list of DJs + if ( $atts['show_djs'] ) { + + $output .= ''; + + $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + + $output .= ' with '; + + foreach ( $show_names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // show's schedule + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + // designate as encore + if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + // link to media file + $show_link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day + $duration = $showdata['time']['end_hour']; + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { + $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; + } else { + $duration = 23 - $showdata['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
        '; + + if ( '00' !== $showdata['time']['end_min'] ) { + $output .= '
        '; + } + } + + $output .= '
        '; // end master-show-entry + } + } + $output .= '
        '; // end master-schedule-weekday + } + $output .= '
        '; // end master-schedule-hour +} +$output .= '
        '; // end master-schedule-divs diff --git a/templates/master-list-list.php b/templates/master-list-list.php new file mode 100644 index 0000000..46b4a28 --- /dev/null +++ b/templates/master-list-list.php @@ -0,0 +1,109 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
          '; + +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
        • '; + $output .= '' . $display_day . ''; + $output .= '
            '; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + $output .= '
          • '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $show['id'] ) ) { + $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + $output .= ''; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $output .= ' with '; + foreach ( $show_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ' '; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } else { + + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $output .= ''; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
          • '; + } + } + $output .= '
          '; + $output .= '
        • '; +} + +$output .= '
        '; From f1c4d13180f711f55e6d4b72f6abb7e7c2dc4c22 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 23 Aug 2019 19:26:31 -0400 Subject: [PATCH 038/377] Update .DS_Store --- .DS_Store | Bin 10244 -> 10244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index 97e8ecf5595ab74c3286ae192c41ac52fb7cb1a6..d13b5d1fa526d8cc469ffaf6b0b75cea60c0e551 100644 GIT binary patch delta 1569 zcmeIwPfQ$D90%~i5~2&9!zXg4pkF97~6xFUfaaz+g&82Ha&Up;3O~a z&+ld4FTc-kacpsHc@*9+#=fqEccV<4fP2j2aiij-66uVD>pCJ`?kzD8Q^+8TQe@H` zX{3{*IVm$~+a}5HA3@WZA55BdlBj4lnKlh4)7EwyMSF@%_LeG)KcOD+@L~5CZ45N7d|Ju{6#MiZ_3q-h=uyw3M*N_8hVcUX_%&EZPT!g0*vJuJe-r}zw~a2^-1iYr*hm$+I;tO(l)j%?4{ z6Ryd{qtw&e>XSR_Vi$Yxu#XS@&3fnrhV_YzQ_hiYVHWEmIS24RWzZ`m_ADy@LluiKJHY+wKVGqFDAq zA=(?2(Y;c=@FPCJ2bl-wn4rh2WglW&r8W9WlHH=)^t0spT~ghbR3)gCOf?7~h*~tF zNkX-tH4k+FJs89xsWpT#yn=B|;22&<0x9XGORu!_a*)CM$l?Qhhy^U+BP?SDr7k|f z8Ju+|)P41rA4M}Co3%{adQ-;WE_k{cne4K+mC%x{)JxnzZL$2tq^={EBpl+Ay^Lp delta 42 ycmZn(XbG6$&&azmU^hP_?`9r>D#poM#f>&gO1xv=*pSG!nO)%*%jP6$W@Z2}O%2Ha From a3f91b22795df232a82ed333b50ae421f5a5c3da Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 23 Aug 2019 19:33:01 -0400 Subject: [PATCH 039/377] Adding back widgets php files so that I can then delete them --- .DS_Store | Bin 10244 -> 10244 bytes includes/widget_djcomingup.php | 354 ++++++++++++++++++++++++++++ includes/widget_djonair.php | 412 +++++++++++++++++++++++++++++++++ includes/widget_nowplaying.php | 177 ++++++++++++++ 4 files changed, 943 insertions(+) create mode 100644 includes/widget_djcomingup.php create mode 100644 includes/widget_djonair.php create mode 100644 includes/widget_nowplaying.php diff --git a/.DS_Store b/.DS_Store index d13b5d1fa526d8cc469ffaf6b0b75cea60c0e551..04db7c54000131cee900c54ab92e9e9e3046ef47 100644 GIT binary patch delta 265 zcmZn(XbIS`NRaWw@~ delta 240 zcmZn(XbIS`NN}>dKnsVtvAK?dk-6z)T_M@Y9|Tk;PZ1ZKTrcFn#N{#hpiuba2QtE( z5t+&PdBK@gshdrNwHReMl^qNiAb^8Gl0lEbfx&|zh9Q-qfT4MEl}J6~_Q}6Q!kM_- mu^4bnRFHrHpT%k!=WQ+&pUSbBUEvqYB+)>MOqyIMJ_!KI2|up@ diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php new file mode 100644 index 0000000..92d14af --- /dev/null +++ b/includes/widget_djcomingup.php @@ -0,0 +1,354 @@ + 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); + $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); + parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + function form( $instance ) { + + $instance = wp_parse_args((array) $instance, array( 'title' => '' )); + $title = $instance['title']; + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; + $time = isset( $instance['time'] ) ? $instance['time']: 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + + +

        + +

        + +

        + +

        + +

        + +

        + + +

        + +

        + +

        + +

        + + +

        + +

        +
        + +

        + + +
        + +
          + + 0 ) ) { + + foreach ( $djs['all'] as $showtime => $dj ) { + + if ( is_array($dj) && $dj['type'] == 'override' ) { + + echo '
        • '; + + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
          '.$dj['title'].'
          '; + } + + // --- show avatar --- + if ( $djavatar ) { + if ( has_post_thumbnail($dj['post_id'] ) ) { + echo '
          '; + echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
          '; + } + } + + // --- show title --- + if ( $position != 'above' ) { + echo '
          '.$dj['title'].'
          '; + } + + echo ''; + + // --- show schedule --- + if ( $show_sched ) { + + if ( $time == 12 ) { + $start_hour = $dj['sched']['start_hour']; + if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { + $start_hour = substr($dj['sched']['start_hour'], 1); + } + + $end_hour = $dj['sched']['end_hour']; + if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { + $end_hour = substr($dj['sched']['end_hour'], 1); + } + + echo '
          '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
          '; + } else { + echo '
          '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
          '; + } + } + echo '
        • '; + + } else { + + echo '
        • '; + + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
          '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
          '; + } + + // --- show avatar --- + if ( $djavatar ) { + echo '
          '; + echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; + } + + // --- show title --- + if ( $position != 'above' ) { + echo '
          '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
          '; + } + + echo ''; + + // --- encore presentation --- + // 2.2.4: added encore presentation display + if ( array_key_exists( $showtime, $djs['encore'] ) ) { + echo '
          '.__('Encore Presentation','radio-station').'
          '; + } + + // --- DJ names --- + if ( $display_djs ) { + + $ids = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + echo '
          '.__( 'with', 'radio-station' ).' '; + foreach ( $ids as $id ) { + $count++; + $user_info = get_userdata( $id ); + + if ( $link_djs ) { + $dj_link = get_author_posts_url( $user_info->ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + echo ''.$user_info->display_name.''; + } else {echo $user_info->display_name;} + + if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) + || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { + echo ' '.__( 'and', 'radio-station' ).' '; + } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { + echo ', '; + } + } + echo '
          '; + } + } + + echo ''; + + // --- show schedule --- + if ( $show_sched ) { + + $showtimes = explode( "|", $showtime ); + // 2.2.2: fix to weekday value to be translated + $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); + + if ( $time == 12 ) { + echo '
          '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
          '; + } else { + echo '
          '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
          '; + } + + } + + echo '
        • '; + } + } + } else { + if ( $default != '' ) { + echo '
        • '.$default.'
        • '; + } + } + ?> +
        +
        + 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); + $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + function form( $instance ) { + + $instance = wp_parse_args((array) $instance, array( 'title' => '' )); + $title = $instance['title']; + $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist']: false; + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; + $links_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + + +

        + + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + + +

        + +

        +
        + +

        + +
        + + +
          + '; + + // --- show title *for above only) --- + if ( $position == 'above' ) { + echo '
          '.$djs['all'][0]['title'].'
          '; + } + + if ( $djavatar ) { + if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { + echo '
          '; + echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
          '; + } + } + + // --- show title --- + if ( $position != 'above' ) { + echo '
          '.$djs['all'][0]['title'].'
          '; + } + + echo ''; + + // --- display the schedule override if requested --- + if ( $show_sched ) { + + if ( $time == 12 ) { + $start_hour = $djs['all'][0]['sched']['start_hour']; + if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { + $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); + } + + $end_hour = $djs['all'][0]['sched']['end_hour']; + if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { + $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); + } + + echo '
          '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
          '; + + } else { + + $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); + echo '
          '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
          '; + + } + } + + echo ''; + + } else { + + if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { + foreach( $djs['all'] as $dj ) { + + $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + $current_sched = radio_station_current_schedule( $scheds ); + + echo '
        • '; + + // --- show title (for above only) --- + if ( $position == 'above' ) { + echo '
          '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
          '; + } + + // --- show avatar --- + if ( $djavatar ) { + echo '
          '; + echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; + } + + // --- show title --- + if ( $position != 'above' ) { + echo '
          '; + if ( $link ) {echo ''.$dj->post_title.'';} + else {echo $dj->post_title;} + echo '
          '; + } + + echo ''; + + // --- encore presentation --- + // 2.2.4: added encore presentation display + if ( $current_sched['encore'] == 'on' ) { + echo '
          '.__('Encore Presentation','radio-station').'
          '; + } + + // --- DJ names --- + if ( $display_djs ) { + + $ids = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + + echo '
          '.__( 'with', 'radio-station' ).' '; + foreach( $ids as $id ) { + $count++; + $user_info = get_userdata($id); + + $dj_link = get_author_posts_url( $user_info->ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + echo ''.$user_info->display_name.''; + + if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) + || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { + echo ' '.__( 'and', 'radio-station').' '; + } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { + echo ', '; + } + } + echo '
          '; + } + } + + // --- show description --- + if ( $show_desc ) { + $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); + $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); + echo '
          '.$desc_string.'
          '; + } + + // --- playlist link --- + if ( $show_playlist ) { + echo ''; + } + + echo ''; + + // --- show schedule --- + if ( $show_sched ) { + + // --- if we only want the schedule that's relevant now to display --- + if ( !$show_all_sched ) { + + if ( $current_sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $current_sched['day'] ); + if ( $time == 12 ) { + echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
          '; + } else { + $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); + echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
          '; + } + } + + } else { + + foreach ( $scheds as $sched ) { + + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $sched['day'] ); + if ( $time == 12 ) { + echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
          '; + } else { + $sched = radio_station_convert_schedule_to_24hour( $sched ); + echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
          '; + } + } + } + } + + echo '
        • '; + + } + } else { + echo '
        • '.$default.'
        • '; + } + } + + ?> +
        +
        + 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); + $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); + parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + function form( $instance ) { + + $instance = wp_parse_args((array) $instance, array( 'title' => '' )); + $title = $instance['title']; + $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; + $song = isset( $instance['song'] ) ? $instance['song'] : true; + $album = isset( $instance['album'] ) ? $instance['album'] : false; + $label = isset( $instance['label'] ) ? $instance['label'] : false; + $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; + + ?> +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + +

        + "; + + ?> +
        + '; + + // 2.2.3: convert span tags to div tags + // 2.2.4: check value keys are set before outputting + if ( $song && isset( $most_recent['playlist_entry_song']) ) { + echo '
        '.$most_recent['playlist_entry_song'].'
        '; + } + + if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { + echo '
        '.$most_recent['playlist_entry_artist'].'
        '; + } + + if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { + echo '
        '.$most_recent['playlist_entry_album'].'
        '; + } + + if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { + echo '
        '.$most_recent['playlist_entry_label'].'
        '; + } + + if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { + echo '
        '.$most_recent['playlist_entry_comments'].'
        '; + } + + if ( isset( $most_recent['playlist_permalink'] ) ) { + echo ''; + } + + echo '
        '; + + } else { + // 2.2.3: added missing translation wrapper + echo '
        '.__('No playlists available.','radio-station').'
        '; + } + + ?> + + +
        + Date: Fri, 23 Aug 2019 19:33:55 -0400 Subject: [PATCH 040/377] delele widget .php files --- includes/.DS_Store | Bin 0 -> 6148 bytes includes/widget_djcomingup.php | 354 ---------------------------- includes/widget_djonair.php | 412 --------------------------------- includes/widget_nowplaying.php | 177 -------------- 4 files changed, 943 deletions(-) create mode 100644 includes/.DS_Store delete mode 100644 includes/widget_djcomingup.php delete mode 100644 includes/widget_djonair.php delete mode 100644 includes/widget_nowplaying.php diff --git a/includes/.DS_Store b/includes/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..663d6b83a827325a08242f512d6e4bcb9a3eaa05 GIT binary patch literal 6148 zcmeHK!AiqG5Pcg9Rs<;&QNcrwUMhI@5=uRI_6Ka6YAdk`CPjM7#gFl8{1`t#-|Q|e zO;YqEqBCLV%}#b^c3(m^3&8Xj@deNU&}I`1_NW>}?n|pl;61xUV{_!_A;tssqBdF` zzmWlXcBdF4!4NsG_4CW`a%`GtRd7&b-V~C1aPXlGPSm%M&x?tT3e=^Q}#YjjJ;+l`IG3 zAoX)7uw-w-UUTewEjDMxl+_E%a=bk@7*=8rS!Ka$QeEPExMP3LJOw#PX>FNB@K(vh zu8K0C4E$3DxMz#B_Z_NM29yD1V8wuZA3`?4$Ybfyembb^5rEjD+X`*@6_l9JW8|@P z$U79{LWwTa_#=jK;q-@oT;#EI=)z(A;ludK#-C7(t+Tvud4cTtlL}Xm)a1}zsZp9ocTk$cQ75YOl Vh>^$AAw3lR5wJ9 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); - $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); - parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time']: 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        - -

        - -

        - - -

        - -

        -
        - -

        - - -
        - -
          - - 0 ) ) { - - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && $dj['type'] == 'override' ) { - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '.$dj['title'].'
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - if ( has_post_thumbnail($dj['post_id'] ) ) { - echo '
          '; - echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
          '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '.$dj['title'].'
          '; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $dj['sched']['start_hour']; - if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { - $start_hour = substr($dj['sched']['start_hour'], 1); - } - - $end_hour = $dj['sched']['end_hour']; - if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($dj['sched']['end_hour'], 1); - } - - echo '
          '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
          '; - } else { - echo '
          '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
          '; - } - } - echo '
        • '; - - } else { - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
          '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( array_key_exists( $showtime, $djs['encore'] ) ) { - echo '
          '.__('Encore Presentation','radio-station').'
          '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - echo '
          '.__( 'with', 'radio-station' ).' '; - foreach ( $ids as $id ) { - $count++; - $user_info = get_userdata( $id ); - - if ( $link_djs ) { - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - } else {echo $user_info->display_name;} - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station' ).' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
          '; - } - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - $showtimes = explode( "|", $showtime ); - // 2.2.2: fix to weekday value to be translated - $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); - - if ( $time == 12 ) { - echo '
          '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
          '; - } else { - echo '
          '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
          '; - } - - } - - echo '
        • '; - } - } - } else { - if ( $default != '' ) { - echo '
        • '.$default.'
        • '; - } - } - ?> -
        -
        - 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist']: false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $links_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        -
        - -

        - -
        - - -
          - '; - - // --- show title *for above only) --- - if ( $position == 'above' ) { - echo '
          '.$djs['all'][0]['title'].'
          '; - } - - if ( $djavatar ) { - if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo '
          '; - echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
          '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '.$djs['all'][0]['title'].'
          '; - } - - echo ''; - - // --- display the schedule override if requested --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $djs['all'][0]['sched']['start_hour']; - if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { - $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); - } - - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); - } - - echo '
          '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
          '; - - } else { - - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - echo '
          '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
          '; - - } - } - - echo ''; - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach( $djs['all'] as $dj ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - $current_sched = radio_station_current_schedule( $scheds ); - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
          '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( $current_sched['encore'] == 'on' ) { - echo '
          '.__('Encore Presentation','radio-station').'
          '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - - echo '
          '.__( 'with', 'radio-station' ).' '; - foreach( $ids as $id ) { - $count++; - $user_info = get_userdata($id); - - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station').' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
          '; - } - } - - // --- show description --- - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); - echo '
          '.$desc_string.'
          '; - } - - // --- playlist link --- - if ( $show_playlist ) { - echo ''; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - // --- if we only want the schedule that's relevant now to display --- - if ( !$show_all_sched ) { - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
          '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
          '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
          '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
          '; - } - } - } - } - - echo '
        • '; - - } - } else { - echo '
        • '.$default.'
        • '; - } - } - - ?> -
        -
        - 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); - parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; - $song = isset( $instance['song'] ) ? $instance['song'] : true; - $album = isset( $instance['album'] ) ? $instance['album'] : false; - $label = isset( $instance['label'] ) ? $instance['label'] : false; - $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - "; - - ?> -
        - '; - - // 2.2.3: convert span tags to div tags - // 2.2.4: check value keys are set before outputting - if ( $song && isset( $most_recent['playlist_entry_song']) ) { - echo '
        '.$most_recent['playlist_entry_song'].'
        '; - } - - if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { - echo '
        '.$most_recent['playlist_entry_artist'].'
        '; - } - - if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { - echo '
        '.$most_recent['playlist_entry_album'].'
        '; - } - - if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { - echo '
        '.$most_recent['playlist_entry_label'].'
        '; - } - - if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { - echo '
        '.$most_recent['playlist_entry_comments'].'
        '; - } - - if ( isset( $most_recent['playlist_permalink'] ) ) { - echo ''; - } - - echo '
        '; - - } else { - // 2.2.3: added missing translation wrapper - echo '
        '.__('No playlists available.','radio-station').'
        '; - } - - ?> - - -
      - Date: Fri, 23 Aug 2019 21:03:58 -0400 Subject: [PATCH 041/377] Update .DS_Store --- .DS_Store | Bin 10244 -> 10244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index 04db7c54000131cee900c54ab92e9e9e3046ef47..dfddf6b25260e09f4f6f5fb843e8963b7c186443 100644 GIT binary patch delta 69 zcmZn(XbG6$&nU7nU^hRb$YvgaY9>a;$rHszHqR0AVV%59T#zHBI5{UNKR;*lBXKs4 R&Fl&f*+IhGFkuD;1^@)k72yB? delta 46 zcmZn(XbG6$&nUbxU^hRb@Ma!?Y9>a8$rHszHqR0AVO`8F!7;HxelxqmL-xt+5;6cu C9Su_e From a5dd199d0d06c0f4c2798116d33a28e2feeae659 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 23 Aug 2019 21:22:49 -0400 Subject: [PATCH 042/377] Update .DS_Store --- .DS_Store | Bin 10244 -> 10244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index dfddf6b25260e09f4f6f5fb843e8963b7c186443..493c54ebc6ada52ace5f24d2e2421117fddefaec 100644 GIT binary patch delta 18 ZcmZn(XbITRF2u+%d7`+;<~c$cq5wY51|R?c delta 18 ZcmZn(XbITRF2u+ Date: Mon, 26 Aug 2019 13:22:17 -0400 Subject: [PATCH 043/377] Update readme.txt Upgrade Notice Add 2.2.5 and 2.2.6 notes to Upgrade Notice at the bottom of the readme file. We should remember to always add to the upgrade notice too. --- readme.txt | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/readme.txt b/readme.txt index 403f541..415da0b 100644 --- a/readme.txt +++ b/readme.txt @@ -247,14 +247,14 @@ you send me your finished translation, I'd love to include it. == Changelog == = 2.2.6 = -* Reorganize master-list shortcode into templates, -* Add constant for plugin directory, -* WP_Query instead of get_posts, -* new posts_per_page and tax_query, -* fixes for undefined indexes, -* fixes for raw mysql queries, -* typecasting to support strict comparisons. - +* Reorganize master-list shortcode into templates, +* Add constant for plugin directory, +* WP_Query instead of get_posts, +* new posts_per_page and tax_query, +* fixes for undefined indexes, +* fixes for raw mysql queries, +* typecasting to support strict comparisons. + = 2.2.5 = * WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) @@ -523,6 +523,18 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.6 = +* Reorganize master-list shortcode into templates, +* Add constant for plugin directory, +* WP_Query instead of get_posts, +* new posts_per_page and tax_query, +* fixes for undefined indexes, +* fixes for raw mysql queries, +* typecasting to support strict comparisons. + += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + = 2.2.4 = Adds title position and avatar width options to widgets; missing DJ author links as new option to widgets; cleanup, improve and fix enqueued Widget CSS (on air/upcoming); show Encore Presentation in show widget displays; fix to Show shift Encore Presentation checkbox saving From acc62f277e3b5adec96351b05324bf145105632a Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Tue, 27 Aug 2019 20:33:35 -0400 Subject: [PATCH 044/377] Add Dutch Translation files Add Dutch translation files to Languages folder. --- .DS_Store | Bin 10244 -> 12292 bytes languages/.DS_Store | Bin 0 -> 8196 bytes languages/radio-station-nl_NL.mo | Bin 0 -> 7610 bytes languages/radio-station-nl_NL.po | 548 +++++++++++++++++++++++++++++++ 4 files changed, 548 insertions(+) create mode 100644 languages/.DS_Store create mode 100644 languages/radio-station-nl_NL.mo create mode 100644 languages/radio-station-nl_NL.po diff --git a/.DS_Store b/.DS_Store index 493c54ebc6ada52ace5f24d2e2421117fddefaec..8c83ab3bd560a692956fcee73bb8f5d00c69495c 100644 GIT binary patch delta 1335 zcmbu9Urbw79LLZ1($aJ3k)C!y3%4Cqa0P@>{wO1E!hoRybC^K}x=dQ`-MCs>;C4v1 znG!RfbS~t+Oc&i0eK6ApM43L$KgpuL*~GZ$%hafQa3nJ^B*u8oEt)0ji=O11-=Fil zzw^8Keeyd!a(c3c5Q5(0dMhFILZxC$$(e8qqsF^j6N?ndAhAPY3!M{^CdbHek`Sk{ zJhx)U>s)Rb;IdbP74~j^mUhPDO*L&@_t?#Wh`qt<>~|^yt4qJS*F4lk!JN3wV5z+x z^f#oeg}!7elO#mRCudWpm5s-*K`C;%i#=2=@v=L#!J*;(4>S8pps)FH?z5)mGpW=_ z#xk>JCOt8pH5Ri6%@fAouEy;P<&8s^N`uvP_04UayL{ zsb{Ar64sQF*3I;>M@`*GXU(IgF|Yd3q>iQ&Ge$mCSryR2+IIVD)sCE#BC6G@{5^VD zPh^e#8C`y$JIS@E7Nhhr>F=92#Hr^4-0y2>)YzycisT8ZI-Z>^jEz2Nrgd(q1y6ei z)5cPXq>|?@B+@_94_KDtl)LLjdfhc`?!J+@i9I@9^c>x zwET#la09pSo8neVlx>AV70I7mBL(s2sgz09BxAE%jGg~y#=-*+JowPye`DzY#Yk5HJ?0gpGUhnrK(_&TyE^UQ);0 z5fi2&V55>ljWvaLs)8b3_D>u9Key1)Jn|6vK-UqG-_3f-ZEU2Yu+r01n_09Kr9n5g8mgxh^{UbEAXx{{-wwMzA_pXJh%%?)O&^8cl(T6ox53N`|7&ne3OGt*^;g; QN$u&n7uPn+ZYVOqpG88@?Yuy8PnZ01q8&OTXDSd25JI5{UNKR<_IlNc+9AT!7Spt;;Y q!WCpTQ0I5%$^1H&lkIsVIG7;TYD`YhnYo!!^$`=u2yVC$3=9Bb`X4L+ diff --git a/languages/.DS_Store b/languages/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..87eba5bbbfe6d21797f169cf49f67bccc130ba11 GIT binary patch literal 8196 zcmeI1KTE?<6vfZe4h0vBpdB+fxL9y@iKP_^POZ+>{t*h*YX9t6{2~s5pU)4V=f3xl z+Lx9zgNV5o-aBcMdvfz5O?p!RroI@Q0aXBH4$=KRPAMVVWdyO~*Hoy6`M?CHm|>1P z4htOS%mAqmL+A>t5?y(UiWt%H+o0jMRew!vlg z0X8S{8hcHg+EjYF?!ov}<68{l(z!j7-KnwH#HmY%ap^Gr$i{al#vYw{k=-38cB-=y zPy!(V>D>=7Wye0i=tuu9pB~xI_qnZx2TWSyNxwIq^q3)EvU84;Pa9my=JFJ{ysx+% zZmea1A-ZUwnO2cG%Iy`@WnQ-O8uG12msNFA>-xy;71U*^zb#SU%682ko&G5{+ zE^w7rm$1+271Sl{xAKzftG)iZ&*>G^W!b+jv9D~nPt?GC%vbJLLVVeQ7lwusC@_It zckWf`|C{UI{}-5$p1KnF{{&2_(P`9qp1!vZ&RBZwh~t(+LiS6X+7wQ<9jDrMocjHT eA(tcWDmk&|P>e*HxNe?-^=>!bt literal 0 HcmV?d00001 diff --git a/languages/radio-station-nl_NL.mo b/languages/radio-station-nl_NL.mo new file mode 100644 index 0000000000000000000000000000000000000000..551491cae31344a8120011550dfcccbe6ff1d14a GIT binary patch literal 7610 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 Zstl%#cqVlTWw!nm4SD-wV)d`l{{WX3Uoijx literal 0 HcmV?d00001 diff --git a/languages/radio-station-nl_NL.po b/languages/radio-station-nl_NL.po new file mode 100644 index 0000000..15879ee --- /dev/null +++ b/languages/radio-station-nl_NL.po @@ -0,0 +1,548 @@ +msgid "" +msgstr "" +"Project-Id-Version: radio station\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 08:27-0600\n" +"PO-Revision-Date: 2018-05-10 10:18+0200\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-KeywordsList: _e;__;esc_attr_e\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Generator: Poedit 2.0.7\n" +"X-Poedit-Basepath: c:/xampp/htdocs/wp352test/wp-content/plugins/radio-" +"station\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: nl\n" +"X-Poedit-SearchPath-0: includes\n" +"X-Poedit-SearchPath-1: templates\n" +"X-Poedit-SearchPath-2: .\n" + +#: includes/dj-on-air.php:90 includes/dj-on-air.php:664 +#: includes/playlist.php:22 includes/playlist.php:359 includes/playlist.php:546 +msgid "View Playlist" +msgstr "Bekijk de playlist" + +#: includes/dj-on-air.php:480 +msgid "None Upcoming" +msgstr "Geen Volgende" + +#: includes/dj-on-air.php:495 +msgid "The current on-air DJ." +msgstr "De huidige on-air DJ." + +#: includes/dj-on-air.php:496 +msgid "Radio Station: Show/DJ On-Air" +msgstr "" + +#: includes/dj-on-air.php:511 includes/dj-on-air.php:738 +#: includes/playlist.php:436 +msgid "Title" +msgstr "Titel" + +#: includes/dj-on-air.php:519 includes/dj-on-air.php:746 +msgid "Show Avatars" +msgstr "Laat avatar zien" + +#: includes/dj-on-air.php:526 +msgid "Link to the Show/DJ's profile" +msgstr "Link naar de Show/DJ's profiel" + +#: includes/dj-on-air.php:533 includes/dj-on-air.php:767 +msgid "Display schedule info for this show" +msgstr "Laat de geschedulde informatie van de show zien" + +#: includes/dj-on-air.php:540 +msgid "Display link to show's playlist" +msgstr "Laat de link van de playlist van de show zien" + +#: includes/dj-on-air.php:545 +msgid "Default DJ Name" +msgstr "Standaard DJ naam" + +#: includes/dj-on-air.php:548 includes/dj-on-air.php:761 +msgid "" +"If no Show/DJ is scheduled for the current hour, display this name/text." +msgstr "" +"ALs er geen show is gescheduled voor dit uur, laat deze naam/tekst zien." + +#: includes/dj-on-air.php:552 includes/dj-on-air.php:779 +msgid "Time Format" +msgstr "Tijdsformaat" + +#: includes/dj-on-air.php:554 includes/dj-on-air.php:781 +msgid "12-hour" +msgstr "12-uur" + +#: includes/dj-on-air.php:555 includes/dj-on-air.php:782 +msgid "24-hour" +msgstr "24-uur" + +#: includes/dj-on-air.php:558 +msgid "Choose time format for displayed schedules" +msgstr "Kies het tijdsformaat voor de getoonde schedules" + +#: includes/dj-on-air.php:722 +msgid "The upcoming DJs/Shows." +msgstr "De volgende DJ's/Shows" + +#: includes/dj-on-air.php:723 +msgid "Radio Station: Upcoming DJ On-Air" +msgstr "Radio Station: De volgende DJ On-Air" + +#: includes/dj-on-air.php:753 +msgid "Link to Show/DJ's user profile" +msgstr "Link naar Show/DJ's gebruikers profiel" + +#: includes/dj-on-air.php:758 +msgid "No Additional Schedules" +msgstr "Geen Extra Schedules" + +#: includes/dj-on-air.php:772 +msgid "Limit" +msgstr "Limiet" + +#: includes/dj-on-air.php:775 +msgid "Number of upcoming DJs/Shows to display." +msgstr "Aantal volgende DJ's/Shows om te tonen" + +#: includes/dj-on-air.php:785 +msgid "Choose time format for displayed schedules." +msgstr "Kies het tijdsformaat voor de getoonde schedules" + +#: includes/master_schedule.php:21 includes/master_schedule.php:22 +msgid "Schedule Override" +msgstr "Schedule Override" + +#: includes/master_schedule.php:23 includes/master_schedule.php:24 +msgid "Add Schedule Override" +msgstr "Maak Schedule Override" + +#: includes/master_schedule.php:25 +msgid "Edit Schedule Override" +msgstr "Edit Schedule Override" + +#: includes/master_schedule.php:26 +msgid "New Schedule Override" +msgstr "Nieuwe Schedule Override" + +#: includes/master_schedule.php:27 +msgid "View Schedule Override" +msgstr "Bekijk Schedule Override" + +#: includes/master_schedule.php:30 +msgid "Post type for Schedule Override" +msgstr "Post het type voorde Schedule Override" + +#: includes/master_schedule.php:49 +msgid "Override Schedule" +msgstr "Override Schedule" + +#: includes/master_schedule.php:79 +msgid "Date" +msgstr "Datum" + +#: includes/master_schedule.php:82 includes/playlist.php:756 +#: includes/playlist.php:835 +msgid "Start Time" +msgstr "Start tijd" + +#: includes/master_schedule.php:108 includes/playlist.php:782 +#: includes/playlist.php:861 +msgid "End Time" +msgstr "Eind tijd" + +#: includes/master_schedule.php:432 includes/master_schedule.php:595 +msgid "encore airing" +msgstr "Nogmaals On Air" + +#: includes/master_schedule.php:437 includes/master_schedule.php:600 +msgid "Audio File" +msgstr "Audio File" + +#: includes/master_schedule.php:454 +msgid "Sun" +msgstr "Zon" + +#: includes/master_schedule.php:454 +msgid "Mon" +msgstr "Maan" + +#: includes/master_schedule.php:454 +msgid "Tue" +msgstr "Dins" + +#: includes/master_schedule.php:454 +msgid "Wed" +msgstr "Woe" + +#: includes/master_schedule.php:454 +msgid "Thu" +msgstr "Don" + +#: includes/master_schedule.php:454 +msgid "Fri" +msgstr "Vrij" + +#: includes/master_schedule.php:454 +msgid "Sat" +msgstr "Zat" + +#: includes/master_schedule.php:619 includes/playlist.php:572 +msgid "Genres" +msgstr "Genres" + +#: includes/playlist.php:16 templates/single-show.php:136 +msgid "Playlists" +msgstr "Playlists" + +#: includes/playlist.php:17 +msgid "Playlist" +msgstr "Playlist" + +#: includes/playlist.php:18 includes/playlist.php:19 +msgid "Add Playlist" +msgstr "Maak Playlist" + +#: includes/playlist.php:20 +msgid "Edit Playlist" +msgstr "Edit Playlist" + +#: includes/playlist.php:21 +msgid "New Playlist" +msgstr "Nieuwe Playlist" + +#: includes/playlist.php:25 +msgid "Post type for Playlist descriptions" +msgstr "Post type voor Playlist omschrijving" + +#: includes/playlist.php:42 +msgid "Shows" +msgstr "Shows" + +#: includes/playlist.php:43 includes/playlist.php:191 +msgid "Show" +msgstr "Show" + +#: includes/playlist.php:44 includes/playlist.php:45 +msgid "Add Show" +msgstr "Maak Show Aan" + +#: includes/playlist.php:46 +msgid "Edit Show" +msgstr "Edit Show" + +#: includes/playlist.php:47 +msgid "New Show" +msgstr "Nieuwe Show" + +#: includes/playlist.php:48 +msgid "View Show" +msgstr "Bekijk Show" + +#: includes/playlist.php:51 +msgid "Post type for Show descriptions" +msgstr "Post type voor Show omschrijving" + +#: includes/playlist.php:71 +msgid "Playlist Entries" +msgstr "Playlist Entries" + +#: includes/playlist.php:93 templates/single-playlist.php:43 +msgid "Artist" +msgstr "Artiest" + +#: includes/playlist.php:93 templates/single-playlist.php:44 +msgid "Song" +msgstr "Nummer" + +#: includes/playlist.php:93 templates/single-playlist.php:45 +msgid "Album" +msgstr "Album" + +#: includes/playlist.php:93 templates/single-playlist.php:46 +msgid "Record Label" +msgstr "Platenlabel" + +#: includes/playlist.php:93 templates/single-playlist.php:47 +msgid "DJ Comments" +msgstr "DJ commentaar" + +#: includes/playlist.php:93 +msgid "New" +msgstr "Nieuw" + +#: includes/playlist.php:93 +msgid "Status" +msgstr "Status" + +#: includes/playlist.php:93 includes/playlist.php:127 includes/playlist.php:144 +#: includes/playlist.php:807 includes/playlist.php:888 +msgid "Remove" +msgstr "Verwijder" + +#: includes/playlist.php:119 includes/playlist.php:144 +msgid "Queued" +msgstr "Queued" + +#: includes/playlist.php:123 includes/playlist.php:144 +msgid "Played" +msgstr "Gespeeld" + +#: includes/playlist.php:136 +msgid "Add Entry" +msgstr "Maak Entry Aan" + +#: includes/playlist.php:163 includes/playlist.php:164 +#: templates/single-show.php:99 +msgid "Schedule" +msgstr "Uitzenddag" + +#: includes/playlist.php:166 includes/playlist.php:167 +msgid "Publish" +msgstr "Publish" + +#: includes/playlist.php:170 +msgid "Submit for Review" +msgstr "Submit voor Review" + +#: includes/playlist.php:171 includes/playlist.php:176 +msgid "Update Playlist" +msgstr "Update Playlist" + +#: includes/playlist.php:175 +msgid "Update" +msgstr "Update" + +#: includes/playlist.php:420 +msgid "Display the current song." +msgstr "Laat de huidige song zien" + +#: includes/playlist.php:421 +msgid "Radio Station: Now Playing" +msgstr "Radio Station: Now Playing" + +#: includes/playlist.php:444 +msgid "Show Artist Name" +msgstr "Laat Artiest Naam Zien" + +#: includes/playlist.php:451 +msgid "Show Song Title" +msgstr "Laat Titel Zien" + +#: includes/playlist.php:458 +msgid "Show Album Name" +msgstr "Laat Album Zien" + +#: includes/playlist.php:465 +msgid "Show Record Label Name" +msgstr "Laat Record Label Zien" + +#: includes/playlist.php:472 +msgid "Show DJ Comments" +msgstr "Show DJ Commentaar" + +#: includes/playlist.php:573 templates/single-show.php:60 +msgid "Genre" +msgstr "Genre" + +#: includes/playlist.php:594 +msgid "Information" +msgstr "Informatie" + +#: includes/playlist.php:612 +msgid "Active" +msgstr "Actief" + +#: includes/playlist.php:614 +msgid "" +"Check this box if show is currently active (Show will not appear on " +"programming schedule if unchecked)" +msgstr "" +"Vink deze box aan als de huidige show actief is (De show zal niet " +"verschijnenin de programmering indien niet aangevinkt)" + +#: includes/playlist.php:616 +msgid "Current Audio File" +msgstr "Huidige Audio File" + +#: includes/playlist.php:619 +msgid "DJ Email" +msgstr "DJ Email" + +#: includes/playlist.php:622 +msgid "Website Link" +msgstr "Website Link" + +#: includes/playlist.php:633 +msgid "DJs" +msgstr "DJs" + +#: includes/playlist.php:719 +msgid "Schedules" +msgstr "Schedules" + +#: includes/playlist.php:744 includes/playlist.php:824 +msgid "Day" +msgstr "Dag" + +#: includes/playlist.php:747 includes/playlist.php:827 +msgid "Monday" +msgstr "Maandag" + +#: includes/playlist.php:748 includes/playlist.php:828 +msgid "Tuesday" +msgstr "Dinsdag" + +#: includes/playlist.php:749 includes/playlist.php:829 +msgid "Wednesday" +msgstr "Woensdag" + +#: includes/playlist.php:750 includes/playlist.php:830 +msgid "Thursday" +msgstr "Donderdag" + +#: includes/playlist.php:751 includes/playlist.php:831 +msgid "Friday" +msgstr "Vrijdag" + +#: includes/playlist.php:752 includes/playlist.php:832 +msgid "Saturday" +msgstr "Zaterdag" + +#: includes/playlist.php:753 includes/playlist.php:833 +msgid "Sunday" +msgstr "Zondag" + +#: includes/playlist.php:806 includes/playlist.php:886 +msgid "Encore Presentation" +msgstr "Nogmaals Gepresenteerd" + +#: includes/playlist.php:817 +msgid "Add Shift" +msgstr "Voeg Shift Toe" + +#: includes/playlist.php:980 +msgid "More Playlists" +msgstr "Meer Playlists" + +#: includes/playlist.php:1041 +msgid "More Blog Posts" +msgstr "Meer Blog Posts" + +#: templates/archive-playlist.php:15 +msgid "Playlist Archive for" +msgstr "Playlist Archief voor" + +#: templates/archive-playlist.php:50 templates/single-playlist.php:13 +#: templates/single-show.php:13 +msgid "Post navigation" +msgstr "Post Navigatie" + +#: templates/archive-playlist.php:51 +msgid "Older posts" +msgstr "Oudere posts" + +#: templates/archive-playlist.php:52 +msgid "Newer posts" +msgstr "Nieuwe posts" + +#: templates/archive-playlist.php:61 templates/author.php:93 +#: templates/playlist-archive-template.php:63 +#: templates/show-blog-archive-template.php:71 +msgid "Nothing Found" +msgstr "Niets Gevonden" + +#: templates/archive-playlist.php:65 templates/author.php:97 +#: templates/playlist-archive-template.php:67 +#: templates/show-blog-archive-template.php:75 +msgid "" +"Apologies, but no results were found for the requested archive. Perhaps " +"searching will help find a related post." +msgstr "" +"Excuses, maar er is niets gevonden in het archief. Misschien helpt de search " +"functie om een post te vinden." + +#: templates/author.php:51 +#, php-format +msgid "Author Archives: %s" +msgstr "Auteur Archieven: %s" + +#: templates/author.php:70 +#, php-format +msgid "About %s" +msgstr "Over: %s" + +#: templates/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "Playlist Archief" + +#: templates/show-blog-archive-template.php:16 +msgid "Blog Archive" +msgstr "Blog Archief" + +#: templates/show-blog-archive-template.php:46 +msgid "Posted by" +msgstr "Gepost door" + +#: templates/single-playlist.php:14 templates/single-show.php:14 +msgid "Previous" +msgstr "Vorige" + +#: templates/single-playlist.php:15 templates/single-show.php:15 +msgid "Next" +msgstr "Volgende" + +#: templates/single-playlist.php:32 templates/single-show.php:144 +msgid "Pages:" +msgstr "Paginas:" + +#: templates/single-playlist.php:65 +msgid "No entries for this playlist" +msgstr "Niets gevonden voor deze playlist" + +#: templates/single-show.php:32 +msgid "Hosted by" +msgstr "Gepresenteerd door" + +#: templates/single-show.php:84 +msgid "Email the DJ" +msgstr "Email de DJ" + +#: templates/single-show.php:88 +msgid "Show Website" +msgstr "Laat De Website Zien" + +#: templates/single-show.php:95 +msgid "Most recent broadcast" +msgstr "Laatste Uitzending" + +#: templates/single-show.php:140 +msgid "Blog Posts" +msgstr "Blog Posts" + +#: radio-station.php:244 radio-station.php:330 +msgid "Export Playlists" +msgstr "Exporteer Playlist" + +#: radio-station.php:244 radio-station.php:451 +msgid "Export" +msgstr "Exporteer" + +#: radio-station.php:323 +msgid "Right-click and download this file to save your export" +msgstr "Klik met rechts en download deze file om deze export op te slaan" + +#: radio-station.php:340 +msgid "Start Date" +msgstr "Start Datum" + +#: radio-station.php:394 +msgid "End Date" +msgstr "Eind Datum" + +#: radio-station.php:466 +msgid "Related to Show" +msgstr "Gerelateerd aan de Show" From cb6289fe735cc0ddf500a4d75965aa79a243d8a9 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Tue, 27 Aug 2019 20:38:10 -0400 Subject: [PATCH 045/377] Updated readme.txt to add 2.2.7 changelog and update note Added 2.2.7 changelog and upgrade notices about Dutch translation to readme.txt --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 6 ++++++ 2 files changed, 6 insertions(+) diff --git a/.DS_Store b/.DS_Store index 8c83ab3bd560a692956fcee73bb8f5d00c69495c..1e6bb4617777ab99a321e748f85d692de588e6e3 100644 GIT binary patch delta 217 zcmZokXi3=MBfxlavaf(7iJ!k70MSFZ=%`ZjcNzuEubA@lb5OsZe|r@SB|1gt_HrCl%ym7MB Date: Tue, 27 Aug 2019 20:45:01 -0400 Subject: [PATCH 046/377] Updated readme.txt include language in FAQ Forgot to add the Dutch language to the FAQ. Added to the FAQ in the read me file. --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 1 + 2 files changed, 1 insertion(+) diff --git a/.DS_Store b/.DS_Store index 1e6bb4617777ab99a321e748f85d692de588e6e3..21e660adaa3f9e7a9291edce188acde8081a608b 100644 GIT binary patch delta 16 XcmZokXi3Cfy43uH&g~1 delta 18 ZcmZokXi3 Date: Tue, 27 Aug 2019 20:56:04 -0400 Subject: [PATCH 047/377] more update to language faq in readme Added: = Can the plugin be translated into my language? = You may translate the plugin into another language. Please visit our WordPress Translate project page for this plugin for further instruction: https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/ The radio-station.pot file is located in the /languages directory of the plugin. Please send the finished translation to info@netmix.com. We'd love to include it. --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.DS_Store b/.DS_Store index 21e660adaa3f9e7a9291edce188acde8081a608b..dd04cc4b9bcdba9369ed60df988258845e5d03c6 100644 GIT binary patch delta 914 zcmZvaTS!zv9Ea!U^&A~_*6Z1IU2o~Ct-5Nut7K_nh9(whYDu`;9_!>aca@TAGd3a! z%1-no75 zAos{q@|wIOAIUOVAwS4Z@(TnaqCgRYWTeA{A{4`nO4MUB8n6v*Xh#P+VaHx5*pFVg zFowf8f}=QzGnl|RsF=hRT*Woa;65JW5gy|?Uf?Ab@iqdVu#B(j^`vxsQ)i9IswpAb zaO(d;^?srGtAv(mVttoSNK8sj$;c@vDzjFrNYtv|b1dri#3Hw()urmQ^lJppi3wic zkQ(**B3H@aAL@qTyog$2@$v{vB0ScErK ztCQ-|C}l(xSA%!|CI1ii)!1K=CGv%=AOw*dbUbt%bsooDff_&NMl_)rJ2>i2>_iu2 z^k5&y?SnmnQ5?iLj>Cf!9Qj#}T*Y}@z!WZ`dK#BF`&+n;S=_-q7Px^2eh1Is4ZMmF m1m8(xunjhN_JJ4i7|0GC4q0U=H z(CeK;w9MnlcNTk>>IK;jea4wP_6uthGaq;L_xOW@9?vvPF|l#+2~;v$*gW3U+|;s$ z>66(t`nKe9{rZ9|Hkz20m77;kT(+WewVq}zqxSR+8&`Oy?zGzVv-ZSLl5Djx7tfL8 zJ9M+^8`ReHss3)iriP6OcX2nRn+(5ZKoh+hw#n9{MT$;a91urcz3C$Xv^*%!|(20=`}?r_vKu5~0LF zib*YLA)RD^>?6Zugj^)I$v6p3kSX$vgvm5{Pri~FGE2UbA27iT85!VkAQNtQP=pec zq6$rDMhiONLpQd=j~xhN5B6do_Tw;)U>L`693!}ZOSp_HxQTI0;4UU{9}n;lVZ6Zy ze8eYw#w>p3nqV?VuW7>EGg{}yxxr-7zu8=wC;m-csjIJRX#AVH)0SwIFnNATYMOJw z;=)qz%IZ_R)j-b`2g@?hhuQ@yZ4b2~Uy>8TT$I(7tthfla~hEg-9nngr=VeDBghttps://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/ The radio-station.pot file is located in the /languages directory of the plugin. Please send the finished translation to info@netmix.com. We'd love to include it. == Changelog == From d319c219a98cd047cbf073b1d8555981950517b0 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 29 Aug 2019 00:14:22 +1000 Subject: [PATCH 048/377] 2.2.7 updates --- css/mailchimp.css | 30 + css/program-schedule.css | 65 +- includes/class-dj-upcoming-widget.php | 455 ++++++ includes/class-dj-widget.php | 532 +++++++ includes/class-playlist-widget.php | 210 +++ includes/master-schedule.php | 262 ++++ includes/master_schedule.php | 624 -------- includes/post-types-admin.php | 1776 ++++++++++++++++++++++ includes/post-types.php | 213 +++ includes/post_types.php | 1286 ---------------- includes/shortcodes.php | 517 ++++--- includes/support-functions.php | 606 ++++++++ includes/support_functions.php | 547 ------- includes/widget_djcomingup.php | 354 ----- includes/widget_djonair.php | 412 ----- includes/widget_nowplaying.php | 177 --- phpcs.xml | 5 + radio-station-admin.php | 458 ++++++ radio-station.php | 521 ++----- readme.txt | 84 +- templates/admin-export.php | 169 +- templates/archive-playlist.php | 48 +- templates/author.php | 37 +- templates/help.php | 6 +- templates/master-schedule-default.php | 211 +++ templates/master-schedule-div.php | 162 ++ templates/master-schedule-list.php | 110 ++ templates/master-schedule-tabs.php | 146 ++ templates/playlist-archive-template.php | 37 +- templates/show-blog-archive-template.php | 86 +- templates/single-playlist.php | 59 +- templates/single-show.php | 267 ++-- 32 files changed, 6133 insertions(+), 4339 deletions(-) create mode 100644 css/mailchimp.css create mode 100644 includes/class-dj-upcoming-widget.php create mode 100644 includes/class-dj-widget.php create mode 100644 includes/class-playlist-widget.php create mode 100644 includes/master-schedule.php delete mode 100644 includes/master_schedule.php create mode 100644 includes/post-types-admin.php create mode 100644 includes/post-types.php delete mode 100644 includes/post_types.php create mode 100644 includes/support-functions.php delete mode 100644 includes/support_functions.php delete mode 100644 includes/widget_djcomingup.php delete mode 100644 includes/widget_djonair.php delete mode 100644 includes/widget_nowplaying.php create mode 100644 phpcs.xml create mode 100644 radio-station-admin.php create mode 100644 templates/master-schedule-default.php create mode 100644 templates/master-schedule-div.php create mode 100644 templates/master-schedule-list.php create mode 100644 templates/master-schedule-tabs.php diff --git a/css/mailchimp.css b/css/mailchimp.css new file mode 100644 index 0000000..d4de4b7 --- /dev/null +++ b/css/mailchimp.css @@ -0,0 +1,30 @@ +/* MailChimp Form Embed Code - Slim - 12/15/2015 v10.7 */ +#mc_embed_signup {background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif;} +#mc_embed_signup form {display:block; position:relative; text-align:left; padding:10px 0 10px 3%} +#mc_embed_signup h2 {font-weight:bold; padding:0; margin:15px 0; font-size:1.4em;} +#mc_embed_signup input {border:1px solid #999; -webkit-appearance:none;} +#mc_embed_signup input[type=checkbox]{-webkit-appearance:checkbox;} +#mc_embed_signup input[type=radio]{-webkit-appearance:radio;} +#mc_embed_signup input:focus {border-color:#333;} +#mc_embed_signup .button {clear:both; background-color: #00bb33; border: 0 none; border-radius:4px; letter-spacing:.03em; color: #FFFFFF; cursor: pointer; display: inline-block; font-size:15px; height: 32px; line-height: 32px; margin: 0 5px 10px 0; padding:0; text-align: center; text-decoration: none; vertical-align: top; white-space: nowrap; width: auto; transition: all 0.23s ease-in-out 0s;} +#mc_embed_signup .button:hover {background-color:#00cc33;} +#mc_embed_signup .small-meta {font-size: 11px;} +#mc_embed_signup .nowrap {white-space:nowrap;} +#mc_embed_signup .clear {clear:none; display:inline;} + +#mc_embed_signup label {display:block; font-size:16px; padding-bottom:10px; font-weight:bold;} +#mc_embed_signup input.email {font-family:"Open Sans","Helvetica Neue",Arial,Helvetica,Verdana,sans-serif; font-size: 15px; display:block; padding:0 0.4em; margin:0 4% 10px 0; min-height:32px; width:58%; min-width:130px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;} +#mc_embed_signup input.button {display:block; width:35%; margin:0 0 10px 0; min-width:90px;} + +#mc_embed_signup div#mce-responses {float:left; top:-1.4em; padding:0em .5em 0em .5em; overflow:hidden; width:90%;margin: 0 5%; clear: both;} +#mc_embed_signup div.response {margin:1em 0; padding:1em .5em .5em 0; font-weight:bold; float:left; top:-1.5em; z-index:1; width:80%;} +#mc_embed_signup #mce-error-response {display:none;} +#mc_embed_signup #mce-success-response {color:#529214; display:none;} +#mc_embed_signup label.error {display:block; float:none; width:auto; margin-left:1.05em; text-align:left; padding:.5em 0;} + +/* Custom */ +#mc_embed_signup #plugin-icon {display:inline-block; vertical-align:middle; margin-right:40px;} +#mc_embed_signup #plugin-icon img {width:40px; height:40px;} +#mc_embed_signup #signup-label {display:inline-block; vertical-align:middle; margin-right:40px; font-size:16px; line-height:24px;} +#mc_embed_signup input[type=email] {display:inline-block; vertical-align:middle; width:300px;} +#mc_embed_signup .subscribe {display:inline-block; vertical-align:middle;} diff --git a/css/program-schedule.css b/css/program-schedule.css index 8942572..768e1f3 100644 --- a/css/program-schedule.css +++ b/css/program-schedule.css @@ -1,5 +1,5 @@ /* Table Style Grid */ - +/* ---------------- */ #master-program-schedule { width: 100%; } @@ -74,6 +74,7 @@ } /* Div Style Grid */ +/* -------------- */ #master-schedule-divs { width: 100%; } @@ -130,3 +131,65 @@ z-index: 5; } +/* Tabbed Styled List */ +/* ------------------ */ +#master-schedule-tabs { + border: none; + padding: 0; + margin: 0; +} +.master-schedule-tabs-day { + float: left; + display: block; + width: 100px; + margin: 0px; + cursor: pointer; + text-align: center; + background-color: #eeeeee; + color: #444444; + border-radius: 7px 7px 0 0; + border: 1px solid #000000; + } +.master-schedule-tabs-panel { + display: none; + float: left; + margin: 0; + position: relative; + text-align: left; + width: 702px; + list-style: none; +} +.master-schedule-tabs-day-name { + padding: 5px 0 5px 0; +} +.master-schedule-tabs-show { + padding: 20px 10px 10px 10px; + clear: both; + list-style: none; +} +.master-schedule-tabs-day.active-day-tab { + font-weight: bold; + background-color: #ffffff; + color: #000000; + border-bottom: 0; +} +.master-schedule-tabs-panel.active-day-panel { + display: block; + border: 1px solid #333333; + background-color: #ffffff; + border-top: 0; + border-radius: 0 0 7px 7px; +} +.master-schedule-tabs-show .show-info, .master-schedule-tabs-show .show-genres, +.master-schedule-tabs-show .show-image { + display: inline-block; + vertical-align: top; +} +.master-schedule-tabs-show .show-image { + margin-right: 30px; + min-width: 200px; +} +.master-schedule-tabs-show .show-genres { + float: right; + margin-right: 10px; +} \ No newline at end of file diff --git a/includes/class-dj-upcoming-widget.php b/includes/class-dj-upcoming-widget.php new file mode 100644 index 0000000..46c783a --- /dev/null +++ b/includes/class-dj-upcoming-widget.php @@ -0,0 +1,455 @@ + 'DJ_Upcoming_Widget', + 'description' => __( 'The upcoming DJs/Shows.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +

      + +

      + + +

      + +

      + +
      + +

      + + +
      + +
        + + 0 ) ) { + + foreach ( $djs['all'] as $showtime => $dj ) { + if ( is_array( $dj ) && isset( $dj['type'] ) && 'override' === $dj['type'] ) { + ?> +
      • + +
        + +
        + +
        > + +
        + +
        + +
        + + + +
        + +
        + +
        + +
        + +
      • + +
      • + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + +
        > + ID, 'thumbnail' ); ?> +
        + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + + + +
        + +
        + ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + ?> +
        + ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + ?> + + display_name ); ?> + + display_name ); + } + + $id_count = count( $ids ); + if ( ( 1 === $count && 2 === $id_count ) + || ( $id_count > 2 ) && $count === $id_count - 1 ) { + echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; + } elseif ( $count < $id_count && $id_count > 2 ) { + echo ', '; + } + } + ?> +
        + + + +
        + , - +
        + +
        + , + - +
        + +
      • + +
      • + +
      • + +
      +
      + 'DJ_Widget', + 'description' => __( 'The current on-air DJ.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Show/DJ On-Air', 'radio-station' ); + parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + + +

      + +

      + +
      + +

      + +
      + + +
        + +
      • + +
        + +
        + +
        > + +
        + +
        + +
        + + + 12 ) {$start_hour = $start_hour - 12;} + + // 2.2.7: add fix to convert back from 24 hour time if past midday + $end_hour = $djs['all'][0]['sched']['end_hour']; + if ( substr( $end_hour, 0, 1 ) === '0' ) { + $end_hour = substr( $end_hour, 1 ); + } elseif ( (int) $end_hour > 12 ) {$end_hour = $end_hour - 12;} + + ?> +
        + +
        + +
        + +
        + +
      • + 0 ) ) { + foreach ( $djs['all'] as $dj ) { + + $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + $current_sched = radio_station_current_schedule( $scheds ); + ?> +
      • + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + +
        > + ID, 'thumbnail' ); ?> +
        + +
        + + + post_title ); ?> + + post_title ); + } + ?> +
        + + + +
        + +
        + ID, 'show_user_list', true ); + $count = 0; + + if ( $ids && is_array( $ids ) ) { + ?> +
        + ID ); + $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); + if ( $link_djs ) { + ?> + + display_name ); ?> + + display_name ); + } + + $id_count = count( $ids ); + if ( ( 1 === $count && 2 === $id_count ) || ( $id_count > 2 && $count === $id_count - 1 ) ) { + echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; + } elseif ( $count < $id_count && $id_count > 2 ) { + echo ', '; + } + } + ?> +
        + post_content ), 20 ); + $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); + ?> +
        + +
        + +
        + + + +
        + + + +
        + +
        + +
        + +
        + +
        + +
        + +
        + +
        + +
      • + +
      • + +
      • + +
      +
      + 'Playlist_Widget', + 'description' => __( 'Display the current song.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Now Playing', 'radio-station' ); + parent::__construct( 'Playlist_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; + $song = isset( $instance['song'] ) ? $instance['song'] : true; + $album = isset( $instance['album'] ) ? $instance['album'] : false; + $label = isset( $instance['label'] ) ? $instance['label'] : false; + $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; + + ?> +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +

      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + + +
      + +
      + +
      + +
      + '12', + 'show_link' => 1, + 'display_show_time' => 1, + 'list' => 'table', + 'show_image' => 0, + 'show_djs' => 0, + 'show_genres' => 0, + 'divheight' => 45, + ), + $atts, + 'master-schedule' + ); + + $timeformat = $atts['time']; + + // $overrides = radio_station_master_get_overrides(true); + + // set up the structure of the master schedule + $default_dj = get_option( 'dj_default_name' ); + + // check to see what day of the week we need to start on + $start_of_week = get_option( 'start_of_week' ); + $days_of_the_week = array( + 'Sunday' => array(), + 'Monday' => array(), + 'Tuesday' => array(), + 'Wednesday' => array(), + 'Thursday' => array(), + 'Friday' => array(), + 'Saturday' => array(), + ); + $week_start = array_slice( $days_of_the_week, $start_of_week ); + + foreach ( $days_of_the_week as $i => $weekday ) { + if ( $start_of_week > 0 ) { + $add = $days_of_the_week[ $i ]; + unset( $days_of_the_week[ $i ] ); + $days_of_the_week[ $i ] = $add; + } + $start_of_week--; + } + + // create the master_list array based on the start of the week + $master_list = array(); + for ( $i = 0; $i < 24; $i++ ) { + $master_list[ $i ] = $days_of_the_week; + } + + // get the show schedules, excluding shows marked as inactive + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value + FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS active + ON meta.post_id = active.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + active.meta_key = 'show_active' AND + active.meta_value = 'on' + )" + ); + + // insert schedules into the master list + foreach ( $show_shifts as $shift ) { + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show is not scheduled yet, unserialize will return false... fix that. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array();} + + foreach ( $shift->meta_value as $time ) { + + // switch to 24-hour time + if ( 'pm' === $time['start_meridian'] && 12 !== (int) $time['start_hour'] ) { + $time['start_hour'] += 12; + } + if ( 'am' === $time['start_meridian'] && 12 === (int) $time['start_hour'] ) { + $time['start_hour'] = 0; + } + + if ( 'pm' === $time['end_meridian'] && 12 !== (int) $time['end_hour'] ) { + $time['end_hour'] += 12; + } + if ( 'am' === $time['end_meridian'] && 12 === (int) $time['end_hour'] ) { + $time['end_hour'] = 0; + } + + // check if we're spanning multiple days + $time['multi-day'] = 0; + if ( $time['start_hour'] > $time['end_hour'] || $time['start_hour'] === $time['end_hour'] ) { + $time['multi-day'] = 1; + } + + $master_list[ $time['start_hour'] ][ $time['day'] ][ $time['start_min'] ] = array( + 'id' => $shift->post_id, + 'time' => $time, + ); + } + } + + // sort the array by time + foreach ( $master_list as $hour => $days ) { + foreach ( $days as $day => $min ) { + ksort( $min ); + $master_list[ $hour ][ $day ] = $min; + + // we need to take into account shows that start late at night and end the following day + foreach ( $min as $i => $time ) { + + // if it ends at midnight, we don't need to worry about carry-over + if ( 0 === (int) $time['time']['end_hour'] && 0 === (int) $time['time']['end_min'] ) { + continue; + } + + // if it ends after midnight, fix it + // if it starts at night and ends in the morning, end hour is on the following day + if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || + // if the start and end times are identical, assume the end time is the following day + ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || + // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day + ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) + ) { + + if ( 12 === (int) $timeformat ) { + $time['time']['real_start'] = ( $time['time']['start_hour'] - 12 ) . ':' . $time['time']['start_min']; + } else { + $pad_hour = ''; + if ( $time['time']['start_hour'] < 10 ) { + $pad_hour = '0'; + } + $time['time']['real_start'] = $pad_hour . $time['time']['start_hour'] . ':' . $time['time']['start_min']; + } + // $time['time']['start_hour'] = "0"; + // $time['time']['start_min'] = "00"; + // $time['time']['start_meridian'] = "am"; + $time['time']['rollover'] = 1; + + $nextday = ''; + switch ( $day ) { + case 'Sunday': + $nextday = 'Monday'; + break; + case 'Monday': + $nextday = 'Tuesday'; + break; + case 'Tuesday': + $nextday = 'Wednesday'; + break; + case 'Wednesday': + $nextday = 'Thursday'; + break; + case 'Thursday': + $nextday = 'Friday'; + break; + case 'Friday': + $nextday = 'Saturday'; + break; + case 'Saturday': + $nextday = 'Sunday'; + break; + } + + $master_list[0][ $nextday ]['00'] = $time; + + } + } + } + } + + $output = ''; + + // 2.2.7: added tabbed master schedule template + if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { + require( RADIO_STATION_DIR . '/templates/master-schedule-list.php' ); + } elseif ( 'divs' === $atts['list'] ) { + require( RADIO_STATION_DIR . '/templates/master-schedule-div.php' ); + } elseif ( 'tabs' === $atts['list'] ) { + require( RADIO_STATION_DIR . '/templates/master-schedule-tabs.php' ); + // 2.2.7: add tab switching javascript to footer + add_action( 'wp_footer', 'radio_station_master_schedule_tabs_js' ); + } else { + require( RADIO_STATION_DIR . '/templates/master-schedule-default.php' ); + } + + return $output; + +} +add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); + +// --- add javascript for highlighting shows based on genre --- +function radio_station_master_fetch_js_filter() { + + $js = '
      ' . __( 'Genres', 'radio-station' ) . ': '; + + $taxes = get_terms( + 'genres', + array( + 'hide_empty' => true, + 'orderby' => 'name', + 'order' => 'ASC', + ) + ); + foreach ( $taxes as $i => $tax ) { + $js .= '' . $tax->name . ''; + // 2.2.2: fix to not add pipe suffix for last genre + if ( count( $taxes ) - 1 !== $i ) { + $js .= ' | '; + } + } + + $js .= '
      '; + + $js .= ''; + + return $js; +} + +// --- javascript for tab switching --- +// 2.2.7: added for tabbed display type +function radio_station_master_schedule_tabs_js() { + echo ''; +} diff --git a/includes/master_schedule.php b/includes/master_schedule.php deleted file mode 100644 index a103a2b..0000000 --- a/includes/master_schedule.php +++ /dev/null @@ -1,624 +0,0 @@ - '12', - 'show_link' => 1, - 'display_show_time' => 1, - 'list' => 'table', - 'show_image' => 0, - 'show_djs' => 0, - 'divheight' => 45 - ), $atts ) ); - - $timeformat = $time; - - // $overrides = radio_station_master_get_overrides(true); - - // set up the structure of the master schedule - $default_dj = get_option( 'dj_default_name' ); - - // check to see what day of the week we need to start on - $start_of_week = get_option( 'start_of_week' ); - $days_of_the_week = array( 'Sunday' => array(), 'Monday' => array(), 'Tuesday' => array(), 'Wednesday' => array(), 'Thursday' => array(), 'Friday' => array(), 'Saturday' => array() ); - $week_start = array_slice( $days_of_the_week, $start_of_week ); - - foreach ( $days_of_the_week as $i => $weekday ) { - if ( $start_of_week > 0 ) { - $add = $days_of_the_week[$i]; - unset( $days_of_the_week[$i] ); - $days_of_the_week[$i] = $add; - } - $start_of_week--; - } - - // create the master_list array based on the start of the week - $master_list = array(); - for ( $i=0; $i<24; $i++ ) {$master_list[$i] = $days_of_the_week;} - - - // get the show schedules, excluding shows marked as inactive - $show_shifts = $wpdb->get_results( "SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `active` ON `meta`.`post_id` = `active`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `active`.`meta_key` = 'show_active' - AND `active`.`meta_value` = 'on');" ); - - // insert schedules into the master list - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show is not scheduled yet, unserialize will return false... fix that. - if ( !is_array($shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // switch to 24-hour time - if($time['start_meridian'] == 'pm' && $time['start_hour'] != 12) { - $time['start_hour'] += 12; - } - if($time['start_meridian'] == 'am' && $time['start_hour'] == 12) { - $time['start_hour'] = 0; - } - - if($time['end_meridian'] == 'pm' && $time['end_hour'] != 12) { - $time['end_hour'] += 12; - } - if($time['end_meridian'] == 'am' && $time['end_hour'] == 12) { - $time['end_hour'] = 0; - } - - // check if we're spanning multiple days - $time['multi-day'] = 0; - if( ($time['start_hour'] > $time['end_hour']) || ($time['start_hour'] == $time['end_hour']) ) { - $time['multi-day'] = 1; - } - - $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array( 'id'=> $shift->post_id, 'time' => $time ); - } - } - - // sort the array by time - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $min ) { - ksort($min); - $master_list[$hour][$day] = $min; - - // we need to take into account shows that start late at night and end the following day - foreach($min as $i => $time) { - - // if it ends at midnight, we don't need to worry about carry-over - if($time['time']['end_hour'] == "0" && $time['time']['end_min'] == "00") { - continue; - } - - // if it ends after midnight, fix it - if( ($time['time']['start_meridian'] == 'pm' && $time['time']['end_meridian'] == 'am') || //if it starts at night and ends in the morning, end hour is on the following day - ($time['time']['start_hour'].$time['time']['start_min'].$time['time']['start_meridian'] == $time['time']['end_hour'].$time['time']['end_min'].$time['time']['end_meridian']) || //if the start and end times are identical, assume the end time is the following day - ($time['time']['start_meridian'] == 'am' && $time['time']['start_hour'] > $time['time']['end_hour']) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day - ) { - - if($timeformat == 12) { - $time['time']['real_start'] = ($time['time']['start_hour']-12).':'.$time['time']['start_min']; - } else { - $pad_hour = ""; - if ( $time['time']['start_hour'] < 10) { - $pad_hour = "0"; - } - $time['time']['real_start'] = $pad_hour.$time['time']['start_hour'].':'.$time['time']['start_min']; - } - // $time['time']['start_hour'] = "0"; - // $time['time']['start_min'] = "00"; - // $time['time']['start_meridian'] = "am"; - $time['time']['rollover'] = 1; - - if ( $day == 'Sunday' ) {$nextday = 'Monday';} - if ( $day == 'Monday' ) {$nextday = 'Tuesday';} - if ( $day == 'Tuesday' ) {$nextday = 'Wednesday';} - if ( $day == 'Wednesday' ) {$nextday = 'Thursday';} - if ( $day == 'Thursday' ) {$nextday = 'Friday';} - if ( $day == 'Friday' ) {$nextday = 'Saturday';} - if ( $day == 'Saturday' ) {$nextday = 'Sunday';} - - $master_list[0][$nextday]['00'] = $time; - - } - } - } - } - - $output = ''; - - if ( ($list == 1) || ($list == 'list') ) { - - // output as a list - $flip = $days_of_the_week; - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $mins ) { - foreach($mins as $fmin => $fshow) { - $flip[$day][$hour][$fmin] = $fshow; - } - } - } - - $output .= '
        '; - - foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
      • '; - $output .= ''.$display_day.''; - $output .= '
          '; - foreach ($hours as $hour => $mins) { - - foreach ($mins as $min => $show ) { - $output .= '
        • '; - - if($show_image) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail($show['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($show['id']).''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $show_djs ) { - $output .= ''; - - $names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $output .= ' with '; - foreach ( $names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - - //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } else { - - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && ( $show['time']['encore'] == 'on' ) ) { - $output .= ' '.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta( $show['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ' '.__('Audio File', 'radio-station').''; - } - - $output .= '
        • '; - } - } - $output .= '
        '; - $output .= '
      • '; - } - - $output .= '
      '; - - } elseif ( $list == 'divs' ) { - - // output some dynamic styles - $output .= ''; - - // output the schedule - $output .= radio_station_master_fetch_js_filter(); - $output .= '
      '; - $weekdays = array_keys($days_of_the_week); - - $output .= '
      '; - $output .= '
       
      '; - foreach( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
      '.$translated.'
      '; - } - $output .= '
      '; - - foreach ($master_list as $hour => $days) { - - $output .= '
      '; - - // output the hour labels - $output .= '
      '; - if ( $timeformat == 12 ) { - $output .= date('ga', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 12-hour format - } else { - $output .= date('H:i', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 24-hour format - } - $output .= '
      '; - - foreach ( $weekdays as $weekday ) { - $output .= '
      '; - if ( isset( $days[$weekday] ) ) { - foreach ( $days[$weekday] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-'.$showdata['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($showdata['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes($term->name).' '; - } - - $output .= '
      '; - - // featured image - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($showdata['id']).''; - } else { - $output .= get_the_title($showdata['id']); - } - $output .= ''; - - // list of DJs - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' with '; - - foreach ( $names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ($count == 1) && ( count($names) == 2) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - // designate as encore - if ( isset($showdata['time']['encore']) && ( $showdata['time']['encore'] == 'on' ) ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - // link to media file - $link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
      '; - - if ( $showdata['time']['end_min'] != '00' ) { - $output .= '
      '; - } - } - - $output .= '
      '; // end master-show-entry - } - } - $output .= '
      '; // end master-schedule-weekday - } - $output .= '
      '; // end master-schedule-hour - } - $output .= '
      '; // end master-schedule-divs - - } else { - - // create the output in a table - $output .= radio_station_master_fetch_js_filter(); - - $output .= ''; - - // output the headings in the correct order - $output .= ''; - foreach($days_of_the_week as $weekday => $info) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= ''; - } - $output .= ''; - // $output .= ''; - - if ( !isset( $nextskip ) ) {$nextskip = array();} - - foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= ''; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] == $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span']-1; - $curskip[$x]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( $shift['time']['start_hour'] == 0 && $shift['time']['end_hour'] == 0 ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] == $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( $shift['time']['end_hour'] == 0 && $shift['time']['start_hour'] != 0 ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + (24 - $shift['time']['start_hour']); - } elseif($shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning - // print_r($shift);die('moo'); - if ( isset($shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ($shift['time']['end_hour'] - $shift['time']['start_hour']); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="'.$rowspan.'"'; - // add to both arrays - $curskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - $nextskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ($continue) {continue;} - - $output .= ''; - - foreach ( $min as $shift ) { - - //print_r($shift); - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-'.$shift['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($shift['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes( $term->name ).' '; - } - - $output .= '
      '; - - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail($shift['id'] ) ) { - $output .= get_the_post_thumbnail($shift['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($shift['id']).''; - } else { - $output .= get_the_title($shift['id']); - } - $output .= ''; - - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' '.__( 'with','radio-station' ).' '; - foreach( $names as $name ) { - $count ++; - $user_info = get_userdata($name); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].' ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - if ( isset($shift['time']['encore']) && $shift['time']['encore'] == 'on' ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta($shift['id'], 'show_file', true); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - $output .= '
      '; - } - $output .= ''; - } - - $output .= '
      '; - } - $output .= '
      '.$heading.'
      '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
      '; - - if ( $timeformat == 12 ) { - if ( $hour == 0 ) {$output .= '12am';} - elseif ( $hour < 12 ) {$output .= $hour.'am';} - elseif ( $hour == 12 ) {$output .= '12pm';} - else {$output .= ($hour-12).'pm';} - } else { - if ( $hour < 10 ) {$output .= "0";} - $output .= $hour.":00"; - } - - $output .= '
      '; - } - - return $output; - -} -add_shortcode( 'master-schedule', 'radio_station_master_schedule'); - -// --- add javascript for highlighting shows based on genre --- -function radio_station_master_fetch_js_filter(){ - - $js = '
      '.__('Genres', 'radio-station').': '; - - $taxes = get_terms( 'genres', array('hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC') ); - foreach ( $taxes as $i => $tax ) { - $js .= ''.$tax->name.''; - // 2.2.2: fix to not add pipe suffix for last genre - if ( $i != ( count($taxes) - 1 ) ) {$js .= ' | ';} - } - - $js .= '
      '; - - $js .= ''; - - return $js; -} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php new file mode 100644 index 0000000..b561496 --- /dev/null +++ b/includes/post-types-admin.php @@ -0,0 +1,1776 @@ + +
      + ID, 'playlist', false ); + $c = 1; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + // echo ""; + // echo ""; + // echo ""; + // echo ""; + echo ''; + + if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { + + foreach ( $entries[0] as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + + echo ''; + + echo ''; + + echo ''; + echo ''; + $c++; + } + } + } + echo '
      ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
      ' . esc_html( $c ) . '
      ' . esc_html__( 'Comments', 'radio-station' ) . ' '; + echo '' . esc_html__( 'New', 'radio-station' ) . ' '; + $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + echo ''; + + echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; + echo '' . esc_html__( 'Remove', 'radio-station' ) . '
      '; + + ?> + +
      + +
      + +
      +

      + post_status, array( 'publish', 'future', 'private' ), true ) || 0 === $post->ID ) { + if ( $can_publish ) : + if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : + ?> + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + + +
      + + $song ) { + if ( 'queued' === $song['playlist_entry_status'] ) { + $playlist[] = $song; + unset( $playlist[ $i ] ); + } + } + update_post_meta( $post_id, 'playlist', $playlist ); + + // sanitize and save show ID + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + } else { + $show = absint( $show ); + if ( $show > 0 ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + } + } + } + +} + +// ------------------------- +// Add Playlist List Columns +// ------------------------- +// 2.2.7: added data columns to playlist list display +add_filter( 'manage_edit-playlist_columns', 'radio_station_playlist_columns', 6 ); +function radio_station_playlist_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb'] ) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset( $columns['date'] ); + $comments = $columns['comments']; unset( $columns['comments'] ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// ------------------------- +// Playlist List Column Data +// ------------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_playlist_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); +function radio_station_playlist_column_data( $column, $post_id ) { + if ( $column == 'show' ) { + $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); + $post = get_post( $show_id ); + echo "" . $post->post_title . ""; + } elseif ( $column == 'trackcount' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + echo count( $tracks ); + } elseif ( $column == 'tracklist' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + $tracklist = ''; + $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . "
      "; + $tracklist .= ''; + echo $tracklist; + } +} + +// --------------------------- +// Playlist List Column Styles +// --------------------------- +add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); +function radio_station_playlist_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-playlist' ) {return;} + echo ""; + + // --- expand/collapse tracklist data --- + echo ""; +} + + +// ------------- +// === Shows === +// ------------- + +// ------------------------ +// Add Related Show Metabox +// ------------------------ +// --- Add custom meta box for show assignment on blog posts --- +add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); +function radio_station_add_showblog_box() { + + // --- make sure a show exists before adding metabox --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + + if ( count( $shows ) > 0 ) { + + // ---- add a filter for which post types to show metabox on --- + // TODO: add this filter to plugin documentation + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + + // --- add the metabox to post types --- + add_meta_box( + 'dynamicShowBlog_sectionid', + __( 'Related to Show', 'radio-station' ), + 'radio_station_inner_showblog_custom_box', + $post_types, + 'side' + ); + } +} + +// -------------------- +// Related Show Metabox +// -------------------- +// --- Prints the box content for the Show field --- +function radio_station_inner_showblog_custom_box() { + global $post; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + $current = get_post_meta( $post->ID, 'post_showblog_id', true ); + + ?> +
      + + +
      + 0 ) { + update_post_meta( $post_id, 'post_showblog_id', $show ); + } + } + } + +} + + +// ----------------------------------- +// Add Assign Playlist to Show Metabox +// ----------------------------------- +// --- Add custom meta box for show assigment --- +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); +function radio_station_myplaylist_add_show_box() { + // 2.2.2: add high priority to shift above publish box + add_meta_box( + 'dynamicShow_sectionid', + __( 'Show', 'radio-station' ), + 'radio_station_myplaylist_inner_show_custom_box', + 'playlist', + 'side', + 'high' + ); +} + +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- +// --- Prints the box content for the Show field --- +function radio_station_myplaylist_inner_show_custom_box() { + + global $post, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); + + $user = wp_get_current_user(); + + // --- allow administrators to do whatever they want --- + if ( ! in_array( 'administrator', $user->roles, true ) ) { + + // --- get the user lists for all shows --- + $allowed_shows = array(); + + $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + + // ---- check each list for the current user --- + foreach ( $show_user_lists as $list ) { + + $list->meta_value = maybe_unserialize( $list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( ! is_array( $list->meta_value ) ) { + $list->meta_value = array(); + } + + // --- only include shows the user is assigned to --- + foreach ( $list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $list->post_id; + } + } + } + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); + + $shows = get_posts( $args ); + + } else { + + // --- for if you are an administrator --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + + $shows = get_posts( $args ); + } + + ?> +
      + + +
      + ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + + // added max-width to prevent metabox overflows + ?> +
      + +

      + /> +

      + +


      +

      + +


      +

      + +


      +

      + +
      + roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'edit_shows' === $capname && $capstatus ) { + $add_roles[] = $name; + } + } + } + $add_roles = array_unique( $add_roles ); + + // ---- create the meta query for get_users() --- + $meta_query = array( 'relation' => 'OR' ); + foreach ( $add_roles as $role ) { + $meta_query[] = array( + 'key' => $wpdb->prefix . 'capabilities', + 'value' => $role, + 'compare' => 'like', + ); + } + + // --- get all eligible users --- + $args = array( + 'meta_query' => $meta_query, + 'orderby' => 'display_name', + 'order' => 'ASC', + //' fields' => array( 'ID, display_name' ), + ); + $users = get_users( $args ); + + // --- get the DJs currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( ! $current ) {$current = array();} + + // --- move any selected DJs to the top of the list --- + foreach ( $users as $i => $dj ) { + if ( in_array( $dj->ID, $current, true ) ) { + unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $users, $dj ); // prepend the user to the beginning of the array + } + } + + // 2.2.2: add fix to make DJ multi-select input full metabox width + ?> +
      + + +
      + +
      + ID, 'show_sched', false ); + + $c = 0; + if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { + + // 2.2.7: soft shifts by start day and time for ordered display + foreach ( $shifts[0] as $shift ) { + if ( isset( $shift['day'] ) ) { + // --- group shifts by days of week --- + $starttime = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + if ( $shift['day'] == 'Sunday' ) {$i = 0;} + elseif ( $shift['day'] == 'Monday' ) {$i = 1;} + elseif ( $shift['day'] == 'Tuesday' ) {$i = 2;} + elseif ( $shift['day'] == 'Wednesday' ) {$i = 3;} + elseif ( $shift['day'] == 'Thursday' ) {$i = 4;} + elseif ( $shift['day'] == 'Friday' ) {$i = 5;} + elseif ( $shift['day'] == 'Saturdday' ) {$i = 6;} + $day_shifts[$i][$starttime] = $shift; + } else { + // --- preserve shift times even if day is not set --- + $starttime = strtotime( '1981-04-28 ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + $day_shifts[7][$starttime] = $shift; + } + } + + // --- sort day shifts and loop --- + ksort( $day_shifts ); + $show_shifts = array(); + foreach ( $day_shifts as $i => $day_shift ) { + // --- sort shifts by start time for each day --- + ksort( $day_shift ); + foreach ( $day_shift as $shift ) { + $show_shifts[] = $shift; + } + } + + // --- loop ordered show shifts --- + foreach ( $show_shifts as $shift ) { + ?> +
        + +
      • + : + +
      • + +
      • + : + + + +
      • + +
      • + : + + + +
      • + +
      • />
      • + +
      • + +
      + + + + +
      + $dj ) { + if ( ! empty( $dj ) ) { + $userid = get_user_by( 'id', $dj ); + if ( ! $userid ) {unset( $djs[ $i ] );} + } + } + } + + $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); + $email = sanitize_email( trim( $_POST['show_email'] ) ); + $active = $_POST['show_active']; + if ( ! in_array( $active, array( '', 'on' ), true ) ) { + $active = '';} + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + + // --- update the show metadata --- + update_post_meta( $post_id, 'show_user_list', $djs ); + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + + // --- update the show shift metadata + $scheds = $_POST['show_sched']; + + // --- sanitize the show shift times --- + $new_scheds = array(); + $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + foreach ( $scheds as $i => $sched ) { + foreach ( $sched as $key => $value ) { + + // --- validate according to key --- + $isvalid = false; + if ( 'day' === $key ) { + if ( in_array( $value, $days, true ) ) { + $isvalid = true;} + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } elseif ( 'encore' === $key ) { + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } + + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_scheds[ $i ][ $key ] = $value; + } else { + $new_scheds[ $i ][ $key ] = ''; + } + } + } + + update_post_meta( $post_id, 'show_sched', $new_scheds ); + +} + +// --------------------- +// Add Show List Columns +// --------------------- +// 2.2.7: added data columns to show list display +add_filter( 'manage_edit-show_columns', 'radio_station_show_columns', 6 ); +function radio_station_show_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb']) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset( $columns['date'] ); + $comments = $columns['comments']; unset( $columns['comments'] ); + $genres = $columns['taxonomy-genres']; unset( $columns['taxonomy-genres'] ); + $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); + $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); + $columns['djs'] = esc_attr( __( 'DJs', 'radio-station' ) ); + $columns['show_image'] = esc_attr( __( 'Show Image', 'radio-station' ) ); + $columns['taxonomy-genres'] = $genres; + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// --------------------- +// Show List Column Data +// --------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_show_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); +function radio_station_show_column_data( $column, $post_id ) { + if ( $column == 'active' ) { + $active = get_post_meta( $post_id, 'show_active', true ); + if ( $active == 'on') {echo __( 'Yes', 'radio-station' );} + else {echo __( 'No', 'radio-station' );} + } elseif ( $column == 'shifts' ) { + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $shift ) { + $timestamp = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ":" . $shift['start_min'] . " " . $shift['start_meridian'] ); + $sortedshifts[$timestamp] = $shift; + } + ksort( $sortedshifts ); + foreach ( $sortedshifts as $shift ) { + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo ""; + } + echo radio_station_translate_weekday( $shift['day'] ); + echo " " . $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + echo " - " . $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']."
      "; + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo "
      "; + } + } + } + } elseif ( $column == 'djs') { + $djs = get_post_meta( $post_id, 'show_user_list', true ); + if ( $djs && ( count( $djs ) > 0 ) ) { + foreach ( $djs as $dj ) { + $user_info = get_userdata( $dj ); + echo esc_html( $user_info->display_name )."
      "; + } + } + } elseif ( $column == 'show_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
      "; + } + } +} + +// ----------------------- +// Show List Column Styles +// ----------------------- +// 2.2.7: added show column styles +add_action( 'admin_footer', 'radio_station_show_column_styles' ); +function radio_station_show_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-show' ) {return;} + echo ""; +} + +// ------------------------- +// Add Show Shift Day Filter +// ------------------------- +// 2.2.7: added show day selection filtering +add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); +function radio_station_show_day_filter( $post_type, $which ) { + if ( 'show' !== $post_type ) {return;} + + // -- maybe get specified day --- + $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; + + // --- show day selector --- + $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + ?> + + + +
      + + ID, 'show_override_sched', false ); + if ( $override ) {$override = $override[0];} + ?> + + +
        +
      • + : + +
      • + +
      • + : + + + +
      • + +
      • + : + + + +
      • +
      +
      + '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + ); + } + + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $changed = false; + foreach ( $sched as $key => $value ) { + $isvalid = false; + + // --- validate according to key --- + if ( 'date' === $key ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { + $isvalid = true; + } + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + // 2.2.3: fix to validate 00 minute value + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + if ( in_array( $value, $valid, true ) ) { + $isvalid = true; + } + } + + // --- if valid add to current schedule setting --- + if ( $isvalid && ( $value !== $current_sched[ $key ] ) ) { + $current_sched[ $key ] = $value; + $changed = true; + + // 2.2.7: sync separate meta key for override date + // (could be used to improve column sorting efficiency) + if ( $key == 'date' ) { + update_post_meta( $post_id, 'show_override_date', $value ); + } + } + } + + // --- save schedule setting if changed --- + if ( $changed ) { + update_post_meta( $post_id, 'show_override_sched', $current_sched ); + } +} + +// ---------------------------------- +// Add Schedule Override List Columns +// ---------------------------------- +// 2.2.7: added data columns to override list display +add_filter( 'manage_edit-override_columns', 'radio_station_override_columns', 6 ); +function radio_station_override_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb'] ) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset($columns['date']); + $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); + $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); + $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); + $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station') ); + $columns['override_image'] = esc_attr( __( 'Override Image' ) ); + $columns['date'] = $date; + return $columns; +} + +// ----------------------------- +// Schedule Override Column Data +// ----------------------------- +// 2.2.7: added data columns for override list display +add_action( 'manage_override_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); +function radio_station_override_column_data( $column, $post_id ) { + + global $show_shifts; + + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $column == 'override_date' ) { + $datetime = strtotime( $override['date'] ); + $month = date( 'F', $datetime ); + $month = radio_station_translate_month( $month ); + $weekday = date ( 'l', $datetime ); + $weekday = radio_station_translate_weekday( $weekday ); + echo $weekday . ' ' . date( 'j', $datetime ) . ' '. $month . ' ' . date( 'Y', $datetime ); + } + elseif ( $column == 'start_time' ) {echo $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'];} + elseif ( $column == 'end_time' ) {echo $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'];} + elseif ( $column == 'shows_affected' ) { + + // --- maybe get all show shifts --- + if ( !isset( $show_shifts ) ) { + global $wpdb; + $show_shifts = $wpdb->get_results( + "SELECT posts.post_title, meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish'" + ); + } + if ( ! $show_shifts || ( count( $show_shifts ) == 0 ) ) {return;} + + // --- get the override weekday and convert to 24 hour time --- + $datetime = strtotime( $override['date'] ); + $weekday = date( 'l', $datetime ); + + // --- get start and end override times --- + $startoverride = strtotime( $override['date']. ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); + $endoverride = strtotime( $override['date']. ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); + if ( $endoverride <= $startoverride ) {$endoverride = $endoverride + 86400;} + + // --- loop show shifts --- + foreach ( $show_shifts as $show_shift ) { + $shift = maybe_unserialize( $show_shift->meta_value ); + if ( ! is_array( $shift ) ) {$shift = array();} + + foreach ( $shift as $time ) { + if ( isset( $time['day'] ) && ( $time['day'] == $weekday ) ) { + + // --- get start and end shift times --- + $startshift = strtotime( $override['date']. ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' .$time['start_meridian'] ); + $endshift = strtotime( $override['date']. ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); + if ( $endshift <= $startshift ) {$endshift = $endshift + 86400;} + + // --- compare override time overlaps to get affected shows --- + if ( ( ( $startoverride < $startshift ) && ( $endoverride > $startshift ) ) + || ( ( $startoverride >= $startshift ) && ( $startoverride < $endshift ) ) ) { + $active = get_post_meta( $show_shift->post_id, 'show_active', true ); + if ( $active != 'on' ) {echo "[" . __( 'Inactive', 'radio-station' ) . "] ";} + echo $show_shift->post_title; + echo " (" . $time['start_hour'] . ":" . $time['start_min'] . $time['start_meridian']; + echo " - " . $time['end_hour'] . ":" . $time['end_min'] . $time['end_meridian'] . ")"; + if ( $active != 'on' ) {echo "";} + echo "
      "; + } + } + } + } + } elseif ( $column == 'override_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
      "; + } + } +} + +// ----------------------------- +// Sortable Override Date Column +// ----------------------------- +// 2.2.7: added to allow override date column sorting +add_filter( 'manage_edit-override_sortable_columns', 'radio_station_override_sortable_columns' ); +function radio_station_override_sortable_columns( $columns ) { + $columns['override_date'] = 'show_override_date'; + return $columns; +} + +// ------------------------------- +// Schedule Override Column Styles +// ------------------------------- +add_action( 'admin_footer', 'radio_station_override_column_styles' ); +function radio_station_override_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-override' ) {return;} + echo ""; +} + +// ---------------------------------- +// Add Schedule Override Month Filter +// ---------------------------------- +// 2.2.7: added month selection filtering +add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); +function radio_station_override_date_filter( $post_type, $which ) { + global $wp_locale; + if ( 'override' !== $post_type ) {return;} + + // --- get all show override months/years --- + global $wpdb; + $overridequery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count($results) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_date', true ); + $datetime = strtotime( $override ); + $month = date( 'm', $datetime ); + $year = date( 'Y', $datetime ); + $months[$year.$month]['year'] = $year; + $months[$year.$month]['month'] = $month; + } + } else {return;} + + // --- maybe get specified month --- + $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + + // --- month override selector --- + ?> + + + is_main_query() ) {return;} + + // --- Shows by Shift Days Filtering --- + if ( 'show' === $query->get( 'post_type' ) ) { + + // --- check if day filter is seta --- + if ( isset( $_GET['weekday'] ) && ( '0' != $_GET['weekday'] ) ) { + + $weekday = $_GET['weekday']; + + // need to loop and sync a separate meta key to enable filtering + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $showquery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'show'"; + $results = $wpdb->get_results( $showquery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + $shiftdays = array(); $shiftstart = false; + foreach ( $shifts as $shift ) { + if ( $shift['day'] == $weekday ) { + $shifttime = radio_station_convert_schedule_to_24hour( $shift ); + $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ":00"; + } + } + if ( $shiftstart ) { + update_post_meta( $post_id, 'show_shift_time', $shiftstart ); + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } + } + + // --- set the meta query for filtering --- + // this is not working?! but does not need to as using orderby fixes it + $meta_query = array( + 'key' => 'show_shift_time', + 'compare' => 'EXISTS', + ); + // $query->set( 'meta_query', $meta_query ); + + // --- order by show time start --- + // only need to set the orderby query and exists check is automatically done! + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_shift_time' ); + $query->set( 'meta_type', 'TIME' ); + } + } + + // --- Order Show Overrides by Override Date --- + // also making this the default sort order + // if ( 'show_override_date' === $query->get( 'orderby' ) ) { + if ( 'override' === $query->get( 'post_type' ) ) { + + // unless order by published date is explicitly chosen + if ( 'date' !== $query->get( 'orderby') ) { + + // need to loop and sync a separate meta key to enable orderby sorting + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $overridequery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $override ) { + update_post_meta( $post_id, 'show_override_date', $override['date'] ); + } else { + delete_post_meta( $post_id, 'show_override_data' ); + } + } + } + + // --- now we can set the orderby meta query to the synced key --- + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_override_date' ); + $query->set( 'meta_type', 'date' ); + + // --- apply override year/month filtering --- + if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { + $yearmonth = $_GET['month']; + $start_date = date($yearmonth.'01'); + $end_date = date($yearmonth.'t'); + $meta_query = array( + 'key' => 'show_override_date', + 'value' => array($start_date, $end_date), + 'compare' => 'BETWEEN', + 'type' => 'DATE' + ); + $query->set( 'meta_query', $meta_query ); + } + + } + } +} + diff --git a/includes/post-types.php b/includes/post-types.php new file mode 100644 index 0000000..c4a1926 --- /dev/null +++ b/includes/post-types.php @@ -0,0 +1,213 @@ + array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'taxonomies' => array( 'genres' ), + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), + 'can_export' => true, + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // -------- + // Playlist + // -------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; + // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'playlist', + array( + 'labels' => array( + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'comments' ), + 'can_export' => true, + 'has_archive' => 'playlists-archive', + 'rewrite' => array( 'slug' => 'playlists' ), + 'capability_type' => 'playlist', + 'map_meta_cap' => true, + ) + ); + + // ----------------- + // Schedule Override + // ----------------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'override', + array( + 'labels' => array( + 'name' => __( 'Schedule Override', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'thumbnail' ), + 'can_export' => true, + 'rewrite' => array( 'slug' => 'show-override' ), + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // --- maybe trigger flush of rewrite rules --- + if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { + add_action( 'init', 'flush_rewrite_rules', 20 ); + delete_option( 'radio_station_flush_rewrite_rules' ); + } +} + +// --------------------------------------- +// Set Post Type Editing to Classic Editor +// --------------------------------------- +// 2.2.2: added so metabox displays can continue to use wide widths +add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); +add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); +function radio_station_post_type_editor( $can_edit, $post_type ) { + $post_types = array( 'show', 'playlist', 'override' ); + if ( in_array( $post_type, $post_types, true ) ) { + return false;} + return $can_edit; +} + +// -------------------------- +// Add Show Thumbnail Support +// -------------------------- +// --- add featured image support to "show" post type --- +// (this is probably no longer necessary as declared in register_post_type for show) +add_action( 'init', 'radio_station_add_featured_image_support' ); +function radio_station_add_featured_image_support() { + $supported_types = get_theme_support( 'post-thumbnails' ); + + if ( false === $supported_types ) { + add_theme_support( 'post-thumbnails', array( 'show' ) ); + } elseif ( is_array( $supported_types ) ) { + $supported_types[0][] = 'show'; + add_theme_support( 'post-thumbnails', $supported_types[0] ); + } +} + + +// ------------------ +// === Taxonomies === +// ------------------ + +// ----------------------- +// Register Genre Taxonomy +// ----------------------- +// --- create custom taxonomy for the Show post type --- +add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); +function radio_station_myplaylist_create_show_taxonomy() { + + // --- add taxonomy labels --- + $labels = array( + 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), + 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), + 'search_items' => __( 'Search Genres', 'radio-station' ), + 'all_items' => __( 'All Genres', 'radio-station' ), + 'parent_item' => __( 'Parent Genre', 'radio-station' ), + 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), + 'edit_item' => __( 'Edit Genre', 'radio-station' ), + 'update_item' => __( 'Update Genre', 'radio-station' ), + 'add_new_item' => __( 'Add New Genre', 'radio-station' ), + 'new_item_name' => __( 'New Genre Name', 'radio-station' ), + 'menu_name' => __( 'Genre', 'radio-station' ), + ); + + // --- register the genre taxonomy --- + // 2.2.3: added show_admin_column and show_in_quick_edit arguments + register_taxonomy( + 'genres', + array( 'show' ), + array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => false, + 'query_var' => true, + 'rewrite' => array( 'slug' => 'genre' ), + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows', + ), + ) + ); + +} + diff --git a/includes/post_types.php b/includes/post_types.php deleted file mode 100644 index 91022b1..0000000 --- a/includes/post_types.php +++ /dev/null @@ -1,1286 +0,0 @@ - array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Show descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'taxonomies' => array( 'genres' ), - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // -------- - // Playlist - // -------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; - // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'playlist', - array( - 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Playlist descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'comments' ), - 'can_export' => true, - 'has_archive' => 'playlists-archive', - 'rewrite' => array( 'slug' => 'playlists' ), - 'capability_type' => 'playlist', - 'map_meta_cap' => true - ) - ); - - // ----------------- - // Schedule Override - // ----------------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'override', - array( - 'labels' => array( - 'name' => __( 'Schedule Override', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Schedule Override', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'thumbnail' ), - 'can_export' => true, - 'rewrite' => array('slug' => 'show-override'), - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // --- maybe trigger flush of rewrite rules --- - if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { - add_action( 'init', 'flush_rewrite_rules', 20 ); - delete_option( 'radio_station_flush_rewrite_rules' ); - } -} - -// --------------------------------------- -// Set Post Type Editing to Classic Editor -// --------------------------------------- -// 2.2.2: added so metabox displays can continue to use wide widths -add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); -add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); -function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( 'show', 'playlist', 'override' ); - if ( in_array( $post_type, $post_types ) ) {return false;} - return $can_edit; -} - -// -------------------------- -// Add Show Thumbnail Support -// -------------------------- -// --- add featured image support to "show" post type --- -// (this is probably no longer necessary as declared in register_post_type for show) -add_action( 'init', 'radio_station_add_featured_image_support' ); -function radio_station_add_featured_image_support() { - $supported_types = get_theme_support( 'post-thumbnails' ); - - if ( $supported_types === false ) { - add_theme_support( 'post-thumbnails', array( 'show' ) ); - } elseif ( is_array( $supported_types ) ) { - $supported_types[0][] = 'show'; - add_theme_support( 'post-thumbnails', $supported_types[0] ); - } -} - -// ---------------------------- -// Metaboxes Above Content Area -// ---------------------------- -// --- shows plugin metaboxes above editor box for plugin CPTs --- -add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); -function radio_station_top_meta_boxes() { - global $post; do_meta_boxes( get_current_screen(), 'top', $post ); -} - - -// ------------------ -// === Taxonomies === -// ------------------ - -// ----------------------- -// Register Genre Taxonomy -// ----------------------- -// --- create custom taxonomy for the Show post type --- -add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); -function radio_station_myplaylist_create_show_taxonomy() { - - // --- add taxonomy labels --- - $labels = array( - 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), - 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), - 'search_items' => __( 'Search Genres', 'radio-station' ), - 'all_items' => __( 'All Genres', 'radio-station' ), - 'parent_item' => __( 'Parent Genre', 'radio-station' ), - 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), - 'edit_item' => __( 'Edit Genre', 'radio-station' ), - 'update_item' => __( 'Update Genre', 'radio-station' ), - 'add_new_item' => __( 'Add New Genre', 'radio-station' ), - 'new_item_name' => __( 'New Genre Name', 'radio-station' ), - 'menu_name' => __( 'Genre', 'radio-station' ), - ); - - // --- register the genre taxonomy --- - // 2.2.3: added show_admin_column and show_in_quick_edit arguments - register_taxonomy( 'genres', array( 'show' ), - array( - 'hierarchical' => true, - 'labels' => $labels, - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array('slug' => 'genre'), - 'show_admin_column' => true, - 'show_in_quick_edit' => true, - 'capabilities' => array( - 'manage_terms' => 'edit_shows', - 'edit_terms' => 'edit_shows', - 'delete_terms' => 'edit_shows', - 'assign_terms' => 'edit_shows' - ), - ) - ); - -} - -// ---------------------------- -// Shift Genre Metabox on Shows -// ---------------------------- -// --- moves genre metabox above publish box --- -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); -function radio_station_genre_meta_box_order() { - global $wp_meta_boxes; - $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; - unset($wp_meta_boxes['show']['side']['core']['genresdiv']); - $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; -} - - -// ----------------- -// === Playlists === -// ----------------- - -// ------------------------- -// Add Playlist Data Metabox -// ------------------------- -// --- Add custom repeating meta field for the playlist edit form --- -// (Stores multiple associated values as a serialized string) -// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -function radio_station_myplaylist_add_custom_box() { - // 2.2.2: change context to show at top of edit screen - add_meta_box( - 'dynamic_sectionid', - __( 'Playlist Entries', 'radio-station' ), - 'radio_station_myplaylist_inner_custom_box', - 'playlist', - 'top', // shift to top - 'high' - ); -} - -// --------------------- -// Playlist Data Metabox -// --------------------- -// -- prints the playlist entry box to the main column on the edit screen --- -function radio_station_myplaylist_inner_custom_box() { - - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); - ?> -
      - ID, 'playlist', false ); - // print_r($entries); - $c = 1; - - echo ''; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - // echo ""; - // echo ""; - // echo ""; - // echo ""; - echo ""; - - if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { - - foreach( $entries[0] as $track ){ - if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - - echo ''; - - echo ''; - - if ( isset( $track['playlist_entry_new'] ) && $track['playlist_entry_new'] ) {$checked = ' checked="checked"';} else {$checked = '';} - - echo ''; - - echo ''; - echo ''; - $c++; - } - } - } - echo '
      ".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
      '.$c.'
      '.__('Comments', 'radio-station').' '; - echo ''.__( 'New', 'radio-station' ).' '; - echo ''; - - echo ' '.__( 'Status', 'radio-station').' '; - echo ''.__('Remove', 'radio-station').'
      '; - - ?> - -
      - -
      - -
      -

      - post_status, array('publish', 'future', 'private') ) || 0 == $post->ID ) { - if ( $can_publish ) : - if ( !empty($post->post_date_gmt) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : ?> - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - - -
      - - $song ) { - if ( $song['playlist_entry_status'] == 'queued' ) { - $playlist[] = $song; - unset($playlist[$i]); - } - } - update_post_meta($post_id, 'playlist', $playlist); - - // sanitize and save show ID - $show = $_POST['playlist_show_id']; - if ( $show == '') { - delete_post_meta( $post_id, 'playlist_show_id' ); - } else { - $show = absint( $show ); - if ( $show > 0 ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); - } - } - } - -} - - -// ------------- -// === Shows === -// ------------- - -// ------------------------ -// Add Related Show Metabox -// ------------------------ -// --- Add custom meta box for show assignment on blog posts --- -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -function radio_station_add_showblog_box() { - - // --- make sure a show exists before adding metabox --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - - if ( count( $shows ) > 0 ) { - - // ---- add a filter for which post types to show metabox on --- - // TODO: add this filter to plugin documentation - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - - // --- add the metabox to post types --- - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'radio_station_inner_showblog_custom_box', - $post_types, - 'side' - ); - } -} - -// -------------------- -// Related Show Metabox -// -------------------- -// --- Prints the box content for the Show field --- -function radio_station_inner_showblog_custom_box() { - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - $current = get_post_meta( $post->ID, 'post_showblog_id', true); - - ?> -
      - - -
      - 0 ) {update_post_meta( $post_id, 'post_showblog_id', $show );} - } - } - -} - - -// ----------------------------------- -// Add Assign Playlist to Show Metabox -// ----------------------------------- -// --- Add custom meta box for show assigment --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); -function radio_station_myplaylist_add_show_box() { - // 2.2.2: add high priority to shift above publish box - add_meta_box( - 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'radio_station_myplaylist_inner_show_custom_box', - 'playlist', - 'side', - 'high' - ); -} - -// ------------------------------- -// Assign Playlist to Show Metabox -// ------------------------------- -// --- Prints the box content for the Show field --- -function radio_station_myplaylist_inner_show_custom_box() { - - global $post, $wpdb; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); - - $user = wp_get_current_user(); - - // --- allow administrators to do whatever they want --- - if ( !in_array('administrator', $user->roles ) ) { - - // --- get the user lists for all shows --- - $allowed_shows = array(); - - $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); - - // ---- check each list for the current user --- - foreach($show_user_lists as $list) { - - $list->meta_value = unserialize( $list->meta_value ); - - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( !is_array( $list->meta_value ) ) {$list->meta_value = array();} - - // --- only include shows the user is assigned to --- - foreach ( $list->meta_value as $user_id ) { - if ( $user->ID == $user_id ) { - $allowed_shows[] = $list->post_id; - } - } - } - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ) - ); - - $shows = get_posts( $args ); - - } else { - - // --- for if you are an administrator --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - - $shows = get_posts( $args ); - } - - ?> -
      - - -
      - ID, 'show_file', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); - - // added max-width to prevent metabox overflows - ?> -
      - -

      - /> -

      - -


      -

      - -


      -

      - -


      -

      - -
      - roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( ( $capname == 'edit_shows' ) && ( ( $capstatus == 1 ) || ( $capstatus == true ) ) ) { - $add_roles[] = $name; - } - } - } - $add_roles = array_unique( $add_roles ); - - // ---- create the meta query for get_users() --- - $meta_query = array( 'relation' => 'OR' ); - foreach ( $add_roles as $role ) { - $meta_query[] = array( - 'key' => $wpdb->prefix.'capabilities', - 'value' => $role, - 'compare' => 'like' - ); - } - - // --- get all eligible users --- - $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //' fields' => array( 'ID, display_name' ), - ); - $users = get_users( $args ); - - // --- get the DJs currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$current ) {$current = array();} - - // --- move any selected DJs to the top of the list --- - foreach ( $users as $i => $dj ) { - if ( in_array( $dj->ID, $current ) ) { - unset( $users[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $users, $dj ); // prepend the user to the beginning of the array - } - } - - // 2.2.2: add fix to make DJ multi-select input full metabox width - ?> -
      - - -
      - -
      - ID, 'show_sched', false); - // print_r($shifts); - - $c = 0; - if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { - foreach ( $shifts[0] as $track ){ - if ( isset( $track['day'] ) || isset( $track['time'] ) ){ - ?> -
        - -
      • - : - -
      • - -
      • - : - - - -
      • - -
      • - : - - - -
      • - -
      • />
      • - -
      • - -
      - - -
      - -
      - $dj ) { - if ( $dj != '' ) { - $userid = get_user_by( 'id', $dj ); - if ( !$userid ) {unset( $djs[$i] );} - } - } - } - $file = strip_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; - if ( !in_array( $active, array( '', 'on') ) ) {$active = '';} - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - - // --- update the show metadata --- - update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - - // --- update the show shift metadata - $scheds = $_POST['show_sched']; - - // --- sanitize the show shift times --- - $new_scheds = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'); - foreach ( $scheds as $i => $sched ) { - foreach ( $sched as $key => $value ) { - - // --- validate according to key --- - $isvalid = false; - if ( $key == 'day' ) { - if ( in_array( $value, $days ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } elseif ($key == 'encore') { - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on'); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to new schedule --- - if ( $isvalid ) {$new_scheds[$i][$key] = $value;} else {$new_scheds[$i][$key] = '';} - } - } - - update_post_meta( $post_id, 'show_sched', $new_scheds ); - -} - - -// -------------------------- -// === Schedule Overrides === -// -------------------------- - -// ----------------------------- -// Add Schedule Override Metabox -// ----------------------------- -// --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); -function radio_station_master_override_add_sched_box() { - // 2.2.2: add high priority to show at top of edit screen - add_meta_box( - 'dynamicSchedOver_sectionid', - __( 'Override Schedule', 'radio-station' ), - 'radio_station_master_override_inner_sched_custom_box', - 'override', - 'normal', - 'high' - ); -} - -// ------------------------- -// Schedule Override Metabox -// ------------------------- -function radio_station_master_override_inner_sched_custom_box() { - - global $post; - - // --- add nonce field for update verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); - ?> -
      - - ID, 'show_override_sched', false); - if ($override) {$override = $override[0];} - ?> - - -
        -
      • - : - -
      • - -
      • - : - - - -
      • - -
      • - : - - - -
      • -
      -
      - '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - ); - } - - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $changed = false; - foreach ( $sched as $key => $value ) { - $isvalid = false; - - // --- validate according to key --- - if ( $key == 'date' ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - // 2.2.3: fix to validate 00 minute value - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to current schedule setting --- - if ( $isvalid && ( $value != $current_sched[$key] ) ) { - $current_sched[$key] = $value; $changed = true; - } - } - - // --- save schedule setting if changed --- - if ($changed) {update_post_meta( $post_id, 'show_override_sched', $current_sched );} -} - diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 93fec06..9b62951 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -5,43 +5,48 @@ */ function radio_station_shortcode_now_playing( $atts ) { - extract( shortcode_atts( array( - 'title' => '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0 - ), $atts ) ); + $atts = shortcode_atts( + array( + 'title' => '', + 'artist' => 1, + 'song' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, + ), + $atts, + 'now-playing' + ); $most_recent = radio_station_myplaylist_get_now_playing(); - $output = ''; + $output = ''; if ( $most_recent ) { $class = ''; - if ( isset( $most_recent['playlist_entry_new'] ) && ( $most_recent['playlist_entry_new'] == 'on') ) { - $class = ' class="new"'; + if ( isset( $most_recent['playlist_entry_new'] ) && 'on' === $most_recent['playlist_entry_new'] ) { + $class = 'new'; } - $output .= '
      '; - if ($title != '') {$output .= '

      '.$title.'

      ';} + $output .= '
      '; + if ( ! empty( $atts['title'] ) ) { + $output .= '

      ' . $atts['title'] . '

      ';} - if ( $song == 1 ) { - $output .= ''.$most_recent['playlist_entry_song'].' '; + if ( 1 === $atts['song'] ) { + $output .= '' . $most_recent['playlist_entry_song'] . ' '; } - if ( $artist == 1 ) { - $output .= ''.$most_recent['playlist_entry_artist'].' '; + if ( 1 === $atts['artist'] ) { + $output .= '' . $most_recent['playlist_entry_artist'] . ' '; } - if ( $album == 1 ) { - $output .= ''.$most_recent['playlist_entry_album'].' '; + if ( 1 === $atts['album'] ) { + $output .= '' . $most_recent['playlist_entry_album'] . ' '; } - if ( $label == 1 ) { - $output .= ''.$most_recent['playlist_entry_label'].' '; + if ( 1 === $atts['label'] ) { + $output .= '' . $most_recent['playlist_entry_label'] . ' '; } - if ( $comments == 1 ) { - $output .= ''.$most_recent['playlist_entry_comments'].' '; + if ( 1 === $atts['comments'] ) { + $output .= '' . $most_recent['playlist_entry_comments'] . ' '; } - $output .= ''.__('View Playlist', 'radio-station').' '; + $output .= '' . __( 'View Playlist', 'radio-station' ) . ' '; $output .= '
      '; } else { @@ -56,43 +61,50 @@ function radio_station_shortcode_now_playing( $atts ) { /* Shortcode to fetch all playlists for a given show id * Since 2.0.0 */ -function radio_station_shortcode_get_playlists_for_show($atts) { - - extract( shortcode_atts( array( - 'show' => '', - 'limit' => -1 - ), $atts ) ); +function radio_station_shortcode_get_playlists_for_show( $atts ) { + + $atts = shortcode_atts( + array( + 'show' => '', + 'limit' => -1, + ), + $atts, + 'get-playlists' + ); // don't return anything if we do not have a show - if ( $show == '' ) {return false;} + if ( empty( $atts['show'] ) ) { + return false; + } $args = array( - 'numberposts' => $limit, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $show + 'posts_per_page' => $atts['limit'], + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + 'meta_key' => 'playlist_show_id', + 'meta_value' => $atts['show'], ); - $playlists = get_posts( $args ); + $query = new WP_Query( $args ); + $playlists = $query->posts; $output = ''; $output .= ''; @@ -103,46 +115,61 @@ function radio_station_shortcode_get_playlists_for_show($atts) { /* Shortcode for displaying a list of all shows * Since 2.0.0 */ -function radio_station_shortcode_list_shows($atts) { - - extract( shortcode_atts( array( - 'genre' => '' - ), $atts ) ); +function radio_station_shortcode_list_shows( $atts ) { + + $atts = shortcode_atts( + array( + 'genre' => '', + ), + $atts, + 'list-shows' + ); // grab the published shows $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'meta_query' => array( - array( - 'key' => 'show_active', - 'value' => 'on', - ) - ) + 'posts_per_page' => 1000, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'meta_query' => array( + array( + 'key' => 'show_active', + 'value' => 'on', + ), + ), ); + if ( ! empty( $atts['genre'] ) ) { + $args['tax_query'] = array( + array( + 'taxonomy' => 'genres', + 'field' => 'slug', + 'terms' => $atts['genre'], + ), + ); + } - if ( $genre != '' ) {$args['genres'] = $genre;} - - $shows = get_posts( $args ); + $query = new WP_Query( $args ); // if there are no shows saved, return nothing - if ( !$shows ) {return false;} + if ( ! $query->have_posts() ) { + return false; + } $output = ''; $output .= '
      '; $output .= '
        '; - foreach ( $shows as $show ) { - $output .= '
      • '; - $output .= ''.get_the_title( $show->ID ).''; - $output .= '
      • '; - } + while ( $query->have_posts() ) : + $query->the_post(); + $output .= '
      • '; + $output .= '' . get_the_title() . ''; + $output .= '
      • '; + endwhile; $output .= '
      '; $output .= '
      '; + wp_reset_postdata(); return $output; } add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); @@ -150,91 +177,96 @@ function radio_station_shortcode_list_shows($atts) { /* Shortcode function for current DJ on-air * Since 2.0.9 */ -function radio_station_shortcode_dj_on_air($atts) { - extract( shortcode_atts( array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'default_name' => '', - 'time' => '12', - 'show_sched' => 1, - 'show_playlist' => 1, +function radio_station_shortcode_dj_on_air( $atts ) { + $atts = shortcode_atts( + array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'default_name' => '', + 'time' => '12', + 'show_sched' => 1, + 'show_playlist' => 1, 'show_all_sched' => 0, - 'show_desc' => 0 - ), $atts ) ); + 'show_desc' => 0, + ), + $atts, + 'dj-widget' + ); // find out which DJ(s) are currently scheduled to be on-air and display them - $djs = radio_station_dj_get_current(); + $djs = radio_station_dj_get_current(); $playlist = radio_station_myplaylist_get_now_playing(); $dj_str = ''; $dj_str .= '
      '; - if ( $title != '' ) {$dj_str .= '

      '.$title.'

      ';} + if ( ! empty( $atts['title'] ) ) { + $dj_str .= '

      ' . $atts['title'] . '

      '; + } $dj_str .= '
        '; // echo the show/dj currently on-air - if ( $djs['type'] == 'override' ) { + if ( 'override' === $djs['type'] ) { $dj_str .= '
      • '; - if ( $show_avatar ) { + if ( $atts['show_avatar'] ) { if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail($djs['all'][0]['post_id'], 'thumbnail').''; + $dj_str .= '' . get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ) . ''; } } $dj_str .= $djs['all'][0]['title']; // display the override's schedule if requested - if ( $show_sched ) { + if ( $atts['show_sched'] ) { - if ( $time == 12 ) { - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
        '; + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' ' . $djs['all'][0]['sched']['start_meridian'] . '-' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . ' ' . $djs['all'][0]['sched']['end_meridian'] . '
        '; } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
        '; + $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour( $djs['all'][0]['sched'] ); + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' -' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . '
        '; } $dj_str .= '
      • '; } - } else { - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { + if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { foreach ( $djs['all'] as $dj ) { $dj_str .= '
      • '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; + if ( $atts['show_avatar'] ) { + $dj_str .= '' . get_the_post_thumbnail( $dj->ID, 'thumbnail' ) . ''; } $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; + if ( $atts['show_link'] ) { + $dj_str .= '' . $dj->post_title . ''; } else { $dj_str .= $dj->post_title; } $dj_str .= ''; - if ( $display_djs ) { + if ( $atts['display_djs'] ) { $names = get_post_meta( $dj->ID, 'show_user_list', true ); $count = 0; if ( $names ) { - $dj_str .= '
        '.__( 'With','radio-station' ).' '; - foreach( $names as $name ) { + $dj_str .= '
        ' . __( 'With', 'radio-station' ) . ' '; + foreach ( $names as $name ) { $count++; $user_info = get_userdata( $name ); $dj_str .= $user_info->display_name; - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2) ) { + } elseif ( $count < $count_names && $count_names > 2 ) { $dj_str .= ', '; } } @@ -242,47 +274,46 @@ function radio_station_shortcode_dj_on_air($atts) { } } - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $dj_str .= ''.$desc_string.''; + if ( $atts['show_desc'] ) { + $desc_string = radio_station_shorten_string( wp_strip_all_tags( $dj->post_content ), 20 ); + $dj_str .= '' . $desc_string . ''; } - if ( $show_playlist ) { - $dj_str .= ''.__('View Playlist', 'radio-station').''; + if ( $atts['show_playlist'] ) { + $dj_str .= '' . __( 'View Playlist', 'radio-station' ) . ''; } $dj_str .= ''; - if ( $show_sched ) { + if ( $atts['show_sched'] ) { $scheds = get_post_meta( $dj->ID, 'show_sched', true ); // if we only want the schedule that's relevant now to display... - if ( !$show_all_sched ) { + if ( ! $atts['show_all_sched'] ) { $current_sched = radio_station_current_schedule( $scheds ); if ( $current_sched ) { // 2.2.2: translate weekday for display $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
        '; + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' ' . $current_sched['start_meridian'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . ' ' . $current_sched['end_meridian'] . '
        '; } else { - $current_sched = radio_station_convert_schedule_to_24hour($current_sched); - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
        '; + $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . '
        '; } } - } else { foreach ( $scheds as $sched ) { // 2.2.2: translate weekday for display $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
        '; + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] . '
        '; } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
        '; + $sched = radio_station_convert_schedule_to_24hour( $sched ); + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . '
        '; } } } @@ -291,7 +322,7 @@ function radio_station_shortcode_dj_on_air($atts) { $dj_str .= '
      • '; } } else { - $dj_str .= '
      • '.$default_name.'
      • '; + $dj_str .= '
      • ' . $atts['default_name'] . '
      • '; } } @@ -301,124 +332,170 @@ function radio_station_shortcode_dj_on_air($atts) { return $dj_str; } -add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air'); +add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air' ); /* Shortcode for displaying upcoming DJs/shows * Since 2.0.9 */ function radio_station_shortcode_coming_up( $atts ) { - extract( shortcode_atts( array( - 'title' => '', + $atts = shortcode_atts( + array( + 'title' => '', 'display_djs' => 0, 'show_avatar' => 0, - 'show_link' => 0, - 'limit' => 1, - 'time' => '12', - 'show_sched' => 1 - ), $atts ) ); + 'show_link' => 0, + 'limit' => 1, + 'time' => '12', + 'show_sched' => 1, + ), + $atts, + 'dj-coming-up-widget' + ); // find out which DJ(s) are coming up today - $djs = radio_station_dj_get_next($limit); - // print_r($djs); - - $now = strtotime( current_time( 'mysql' )); - $curDate = date( 'Y-m-d', $now ); - - $dj_str = ''; - - $dj_str .= '
        '; - if ( $title != '' ) {$dj_str .= '

        '.$title.'

        ';} - $dj_str .= '
          '; - - // echo the show/dj coming up - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && ( $dj['type'] == 'override') ) { - - echo '
        • '; - - if ( $show_avatar ) { - if ( has_post_thumbnail( $dj['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).''; - } - } - - echo $dj['title']; - if( $show_sched ) { - - if ( $time == 12 ) { - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
          '; - } else { - $dj['sched'] = radio_station_convert_schedule_to_24hour($dj['sched']); - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
          '; - - } - } - echo '
        • '; + $djs = radio_station_dj_get_next( $atts['limit'] ); + if ( ! isset( $djs['all'] ) || count( $djs['all'] ) <= 0 ) { + $output = '
        • ' . __( 'None Upcoming', 'radio-station' ) . '
        • '; + return $output; + } - } else { + ob_start(); + ?> +
          + +

          + +
            + $dj ) { + + if ( is_array( $dj ) && 'override' === $dj['type'] ) { + ?> +
          • + + + '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } + echo esc_html( $dj['title'] ); + if ( $atts['show_sched'] ) { - $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; + if ( 12 === (int) $atts['time'] ) { + ?> + + +
            + + + +
            + +
          • '; + post_title; - } - $dj_str .= ''; - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $dj_str .= '
            With '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - if( ( ( $count == 1 ) && ( count($names) == 2) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $dj_str .= ', '; + ?> +
          • + + + ID, 'thumbnail' ); ?> + + + + + + post_title ); ?> + + post_title ); + } + ?> + + ID, 'show_user_list', true ); + $count = 0; + + if ( $names ) { + ?> +
            With + display_name ); + + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { + echo ' and '; + } elseif ( $count < $count_names && $count_names > 2 ) { + echo ', '; + } + } + ?> +
            + '; - } - } - - $dj_str .= ''; - - if ( $show_sched ) { - $showtimes = explode( '|', $showtime ); - if ( $time == 12 ) { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('g:i a', $showtimes[0]).'-'.date('g:i a', $showtimes[1]).'
            '; - } else { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('H:i', $showtimes[0]).'-'.date('H:i', $showtimes[1]).'
            '; - } + ?> + + + + + , + + +
            + + + + , + + +
            + +
          • + '; } - } - } else { - $dj_str .= '
          • '.__('None Upcoming', 'radio-station').'
          • '; - } - - $dj_str .= '
          '; - $dj_str .= '
          '; - - return $dj_str; + ?> +
        +
        + = $now ) ) { + $current = $sched; + } else { + continue; + } + } + + return $current; +} + +// --- convert shift times to 24-hour and timestamp formats for comparisons --- +function radio_station_convert_time( $time = array() ) { + + if ( empty( $time ) ) {return false;} + + $now = strtotime( current_time( 'mysql' ) ); + $cur_date = date( 'Y-m-d', $now ); + $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow + + // --- convert to 24 hour time --- + $time = radio_station_convert_schedule_to_24hour( $time ); + + // --- get a timestamp for the schedule start and end --- + $time['start_timestamp'] = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] ); + + if ( 'pm' === $time['start_meridian'] && 'am' === $time['end_meridian'] ) { + // check for shows that run overnight into the next morning + $time['end_timestamp'] = strtotime( $tom_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } else { + $time['end_timestamp'] = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } + + // a show cannot end before it begins... if it does, it ends the following day. + if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { + $time['end_timestamp'] = $time['end_timestamp'] + 86400; + } + + return $time; +} + +// --- convert a shift to 24 hour time for display --- +function radio_station_convert_schedule_to_24hour( $sched = array() ) { + + if ( empty( $sched ) ) {return false;} + + if ( 'pm' === $sched['start_meridian'] && 12 !== (int) $sched['start_hour'] ) { + $sched['start_hour'] = $sched['start_hour'] + 12; + } + if ( 'am' === $sched['start_meridian'] && $sched['start_hour'] < 10 ) { + $sched['start_hour'] = '0' . $sched['start_hour']; + } + if ( 'am' === $sched['start_meridian'] && 12 === (int) $sched['start_hour'] ) { + $sched['start_hour'] = '00'; + } + + if ( 'pm' === $sched['end_meridian'] && 12 !== (int) $sched['end_hour'] ) { + $sched['end_hour'] = $sched['end_hour'] + 12; + } + if ( 'am' === $sched['end_meridian'] && $sched['end_hour'] < 10 ) { + $sched['end_hour'] = '0' . $sched['end_hour']; + } + if ( 'am' === $sched['end_meridian'] && 12 === (int) $sched['end_hour'] ) { + $sched['end_hour'] = '00'; + } + + return $sched; +} + +// --- fetch the current DJ(s) on-air -- +function radio_station_dj_get_current() { + + // --- first check to see if there are any shift overrides --- + $check = radio_station_master_get_overrides( true ); + if ( $check ) { + $shows = array( + 'all' => $check, + 'type' => 'override', + ); + return $shows; + } + + global $wpdb; + + // --- get the current time and day --- + $now = strtotime( current_time( 'mysql' ) ); + $cur_day = date( 'l', $now ); + + // --- query for active shows only --- + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + foreach ( $show_shifts as $shift ) { + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + foreach ( $shift->meta_value as $time ) { + + // check if the shift is for the current day. If it's not, skip it + if ( $time['day'] === $cur_day ) { + // format the time so that it is more easily compared + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + + // we need to make a special allowance for shows that run from one day into the next + if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { + + $time = radio_station_convert_time( $time ); + // because station_convert_time assumes that the show STARTS on the current day, + // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps + $time['start_timestamp'] = $time['start_timestamp'] - 86400; + $time['end_timestamp'] = $time['end_timestamp'] - 86400; + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + } + } + + $shows = array(); + foreach ( $show_ids as $id ) { + $shows['all'][] = get_post( $id ); + } + $shows['type'] = 'shows'; + + return $shows; +} + +// --- get the next DJ or DJs scheduled to be on air based on the current time --- +function radio_station_dj_get_next( $limit = 1 ) { + + global $wpdb; + + // get the various times/dates we need + $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); + $cur_day_num = date( 'N', strtotime( current_time( 'mysql' ) ) ); + $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); + $now = strtotime( current_time( 'mysql' ) ); + $shows = array(); + + // first check to see if there are any shift overrides + $check = radio_station_master_get_overrides(); + $overrides = array(); + + if ( $check ) { + + foreach ( $check as $i => $p ) { + + $p['sched'] = radio_station_convert_time( $p['sched'] ); + + // compare to the current timestamp + if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { + //show is on now, so we don't need it listed under upcoming + //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; + unset( $check[ $i ] ); + } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { + // show is on later today + $overrides[ $p['sched']['start_timestamp'] . '|' . $p['sched']['end_timestamp'] ] = $p; + } else { + // show is already over and we don't need it + unset( $check[ $i ] ); + } + } + + // sort the overrides by start time + ksort( $overrides ); + } + + // Fetch all schedules... we only want active shows + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value + FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + + foreach ( $show_shifts as $shift ) { + + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... + // fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + $encore_ids = array(); + $days = array( + 'Sunday' => 7, + 'Monday' => 1, + 'Tuesday' => 2, + 'Wednesday' => 3, + 'Thursday' => 4, + 'Friday' => 5, + 'Saturday' => 6, + ); + foreach ( $shift->meta_value as $time ) { + + if ( $time['day'] === $cur_day ) { + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ); + } else { + if ( $cur_day_num < $days[ $time['day'] ] ) { + $day_diff = $days[ $time['day'] ] - $cur_day_num; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } else { + $day_diff = $cur_day_num + $days[ $time['day'] ] + 1; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } + } + + // if the shift occurs later than the current time, we want it + if ( $cur_shift >= $now ) { + $show_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + // 2.2.4: set encore ID array to pass back + if ( isset( $time['encore'] ) && 'on' === $time['encore'] ) { + $encore_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + } + } + } + } + + // sort the shows by start time + ksort( $show_ids ); + + // merge in the overrides array + foreach ( $show_ids as $s => $id ) { + foreach ( $overrides as $o => $info ) { + $stime = explode( '|', $s ); + $otime = explode( '|', $o ); + + if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends + if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) + unset( $show_ids[ $s ] ); // this show is overriden... drop it + } + } + } + } + + // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) + if ( ! function_exists( 'array_replace' ) ) { + + function array_replace() { + $array = array(); + $n = func_num_args(); + + while ( $n-- > 0 ) { + $array += func_get_arg( $n ); + } + return $array; + } + } + + $combined = array_replace( $show_ids, $overrides ); + ksort( $combined ); + + // grab the number of shows from the list the user wants to display + $combined = array_slice( $combined, 0, $limit, true ); + + // fetch detailed show information + foreach ( $combined as $timestamp => $id ) { + if ( ! is_array( $id ) ) { + $shows['all'][ $timestamp ] = get_post( $id ); + } else { + $id['type'] = 'override'; + $shows['all'][ $timestamp ] = $id; + } + } + $shows['type'] = 'shows'; + // 2.2.4: set encore IDs to pass back + $shows['encore'] = $encore_ids; + + // return the information + return $shows; +} + +// --- get the most recently entered song --- +function radio_station_myplaylist_get_now_playing() { + + // grab the most recent playlist + $args = array( + 'numberposts' => 1, + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + ); + + $playlist = get_posts( $args ); + + // if there are no playlists saved, return nothing + if ( ! $playlist ) { + return false; + } + + // fetch the tracks for each playlist from the wp_postmeta table + $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); + + if ( ! empty( $songs[0] ) ) { + // removed any entries that are marked as 'queued' + foreach ( $songs[0] as $i => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[0][ $i ] ); + } + } + + // pop the last track off the list for display + $most_recent = array_pop( $songs[0] ); + + // get the permalink for the playlist so it can be displayed + $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); + + return $most_recent; + } else { + return false; + } +} + +// --- fetch all blog posts for a show's DJs --- +function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { + + global $wpdb; + + // do not return anything if we don't have a show + if ( ! $show_id ) { + return false; + } + + $fetch_posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = 'post_showblog_id' AND + meta.meta_value = %d", + $show_id + ) + ); + + $blog_array = array(); + $blogposts = array(); + foreach ( $fetch_posts as $f ) { + $blog_array[] = $f->post_id; + } + + if ( $blog_array ) { + + $blogposts = $wpdb->get_results( + $wpdb->prepare( + "SELECT posts.ID, posts.post_title + FROM {$wpdb->posts} AS posts + WHERE posts.ID IN(%s) AND + posts.post_status = 'publish' + ORDER BY posts.post_date DESC + LIMIT %d", + $blog_array, + $limit + ) + ); + } + + $output = ''; + + $output .= '
        '; + $output .= '

        ' . $title . '

        '; + $output .= ''; + $output .= '
        '; + + // if the blog archive page has been created, add a link to the archive for this show + $page = $wpdb->get_results( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = '_wp_page_template' AND + meta.meta_value = 'show-blog-archive-template.php' + LIMIT 1" + ); + + if ( $page ) { + $blog_archive = get_permalink( $page[0]->post_id ); + $params = array( 'show_id' => $show_id ); + $blog_archive = add_query_arg( $params, $blog_archive ); + + $output .= '' . __( 'More Blog Posts', 'radio-station' ) . ''; + } + + return $output; +} + +// --- get any schedule overrides for today's date --- +// If currenthour is true, only overrides that are in effect NOW will be returned +function radio_station_master_get_overrides( $currenthour = false ) { + + global $wpdb; + + $now = strtotime( current_time( 'mysql' ) ); + $date = date( 'Y-m-d', $now ); + $sql_date = $wpdb->esc_like( $date ); + $sql_date = '%' . $sql_date . '%'; + $show_shifts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta_key = 'show_override_sched' AND + meta_value LIKE %s", + $sql_date + ) + ); + + $scheds = array(); + if ( $show_shifts ) { + foreach ( $show_shifts as $shift ) { + + $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $time = $next_sched[0]; + + if ( $currenthour ) { + + // convert to 24 hour time + $check = array(); + $check = $time; + + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $title = get_the_title( $shift->post_id ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $time, + ); + } else { + continue; + } + } else { + $title = get_the_title( $shift->post_id ); + $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $sched[0], + ); + } + } + } + + return $scheds; +} + +// --- shorten a string to a set number of words --- +function radio_station_shorten_string( $string, $limit ) { + + $shortened = $string; // just in case of a problem + + $array = explode( ' ', $string ); + if ( count( $array ) <= $limit ) { + // already at or under the limit + $shortened = $string; + } else { + array_splice( $array, $limit ); + $shortened = implode( ' ', $array ) . ' ...'; + } + return $shortened; +} + +// --- translate weekday --- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate weekdays +function radio_station_translate_weekday( $weekday, $short = false ) { + global $wp_locale; + if ( $short ) { + if ( $weekday == 'Sun' ) {$weekday = $wp_locale->get_weekday_abbrev( 0 );} + elseif ( $weekday == 'Mon' ) {$weekday = $wp_locale->get_weekday_abbrev( 1 );} + elseif ( $weekday == 'Tue' ) {$weekday = $wp_locale->get_weekday_abbrev( 2 );} + elseif ( $weekday == 'Wed' ) {$weekday = $wp_locale->get_weekday_abbrev( 3 );} + elseif ( $weekday == 'Thu' ) {$weekday = $wp_locale->get_weekday_abbrev( 4 );} + elseif ( $weekday == 'Fri' ) {$weekday = $wp_locale->get_weekday_abbrev( 5 );} + elseif ( $weekday == 'Sat' ) {$weekday = $wp_locale->get_weekday_abbrev( 6 );} + } else { + // 2.2.7: fix to typo for Tuesday + if ( $weekday == 'Sunday' ) {$weekday = $wp_locale->get_weekday( 0 );} + elseif ( $weekday == 'Monday' ) {$weekday = $wp_locale->get_weekday( 1 );} + elseif ( $weekday == 'Tuesday' ) {$weekday = $wp_locale->get_weekday( 2 );} + elseif ( $weekday == 'Wednesday' ) {$weekday = $wp_locale->get_weekday( 3 );} + elseif ( $weekday == 'Thurday' ) {$weekday = $wp_locale->get_weekday( 4 );} + elseif ( $weekday == 'Friday' ) {$weekday = $wp_locale->get_weekday( 5 );} + elseif ( $weekday == 'Saturday' ) {$weekday = $wp_locale->get_weekday( 6 );} + } + return $weekday; +} + +// --- translate month --- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate months +function radio_station_translate_month( $month, $short = false ) { + global $wp_locale; + if ( $short ) { + if ( $month == 'Jan' ) {$month = $wp_locale->get_month_abbrev( 1 );} + elseif ( $month == 'Feb' ) {$month = $wp_locale->get_month_abbrev( 2 );} + elseif ( $month == 'Mar' ) {$month = $wp_locale->get_month_abbrev( 3 );} + elseif ( $month == 'Apr' ) {$month = $wp_locale->get_month_abbrev( 4 );} + elseif ( $month == 'May' ) {$month = $wp_locale->get_month_abbrev( 5 );} + elseif ( $month == 'Jun' ) {$month = $wp_locale->get_month_abbrev( 6 );} + elseif ( $month == 'Jul' ) {$month = $wp_locale->get_month_abbrev( 7 );} + elseif ( $month == 'Aug' ) {$month = $wp_locale->get_month_abbrev( 8 );} + elseif ( $month == 'Sep' ) {$month = $wp_locale->get_month_abbrev( 9 );} + elseif ( $month == 'Oct' ) {$month = $wp_locale->get_month_abbrev( 10 );} + elseif ( $month == 'Nov' ) {$month = $wp_locale->get_month_abbrev( 11 );} + elseif ( $month == 'Dec' ) {$month = $wp_locale->get_month_abbrev( 12 );} + } else { + if ( $month == 'January' ) {$month = $wp_locale->get_month( 1 );} + elseif ( $month == 'February' ) {$month = $wp_locale->get_month( 2 );} + elseif ( $month == 'March' ) {$month = $wp_locale->get_month( 3 );} + elseif ( $month == 'April' ) {$month = $wp_locale->get_month( 4 );} + elseif ( $month == 'May' ) {$month = $wp_locale->get_month( 5 );} + elseif ( $month == 'June' ) {$month = $wp_locale->get_month( 6 );} + elseif ( $month == 'July' ) {$month = $wp_locale->get_month( 7 );} + elseif ( $month == 'August' ) {$month = $wp_locale->get_month( 8 );} + elseif ( $month == 'September' ) {$month = $wp_locale->get_month( 9 );} + elseif ( $month == 'October' ) {$month = $wp_locale->get_month( 10 );} + elseif ( $month == 'November' ) {$month = $wp_locale->get_month( 11 );} + elseif ( $month == 'December' ) {$month = $wp_locale->get_month( 12 );} + } + return $month; +} + +// ------------------ +// Translate Meridiem +// ------------------ +// 2.2.7: added meridiem translation function +function radio_station_translate_meridiem( $meridiem ) { + global $wp_locale; + return $wp_locale->get_meridiem( $meridiem ); +} \ No newline at end of file diff --git a/includes/support_functions.php b/includes/support_functions.php deleted file mode 100644 index 6792144..0000000 --- a/includes/support_functions.php +++ /dev/null @@ -1,547 +0,0 @@ -= $now ) ) {$current = $sched;} - else {continue;} - } - - return $current; -} - -// --- convert shift times to 24-hour and timestamp formats for comparisons --- -function radio_station_convert_time( $time = array() ) { - - if ( empty( $time ) ) {return false;} - - $now = strtotime( current_time( 'mysql' ) ); - $curDate = date( 'Y-m-d', $now ); - $tomDate = date( 'Y-m-d', ( $now + 86400) ); // get the date for tomorrow - - // convert to 24 hour time - $time = radio_station_convert_schedule_to_24hour( $time ); - - // get a timestamp for the schedule start and end - $time['start_timestamp'] = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'] ); - - if ( ( $time['start_meridian'] == 'pm' ) && ( $time['end_meridian'] == 'am' ) ) { - // check for shows that run overnight into the next morning - $time['end_timestamp'] = strtotime( $tomDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } else { - $time['end_timestamp'] = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } - - // a show cannot end before it begins... if it does, it ends the following day. - if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { - $time['end_timestamp'] = $time['end_timestamp'] + 86400; - } - - return $time; -} - -// --- convert a shift to 24 hour time for display --- -function radio_station_convert_schedule_to_24hour( $sched = array() ) { - - if ( empty( $sched ) ) {return false;} - - if ( ( $sched['start_meridian'] == 'pm' ) && ( $sched['start_hour'] != 12 ) ) { - $sched['start_hour'] = $sched['start_hour'] + 12; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] < 10 ) ) { - $sched['start_hour'] = "0".$sched['start_hour']; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] == 12 ) ) { - $sched['start_hour'] = '00'; - } - - if ( ( $sched['end_meridian'] == 'pm' ) && ( $sched['end_hour'] != 12 ) ) { - $sched['end_hour'] = $sched['end_hour'] + 12; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] < 10 ) ) { - $sched['end_hour'] = "0".$sched['end_hour']; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] == 12 ) ) { - $sched['end_hour'] = '00'; - } - - return $sched; -} - -// --- fetch the current DJ(s) on-air -- -function radio_station_dj_get_current() { - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(true); - - if ( $check ) { - $shows = array( - 'all' => $check, - 'type' => 'override' - ); - - // at this point, we are done. Return the info. - return $shows; - } - - // load the info for the DJ - global $wpdb; - - // get the current time - $now = strtotime( current_time( 'mysql' ) ); - - $hour = date ('H', $now ); - $min = date( 'i', $now ); - $curDay = date( 'l', $now ); - $curDate = date( 'Y-m-d', $now ); - - // then check to see if a show is scheduled - // $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - // WHERE `meta_key` = 'show_sched';"); - - // we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // check if the shift is for the current day. If it's not, skip it - if ( $time['day'] == $curDay ) { - // format the time so that it is more easily compared - $time = radio_station_convert_time( $time ); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - // we need to make a special allowance for shows that run from one day into the next - if( date('w', strtotime($time['day']))+1 == date('w', strtotime($curDay)) ) { - - $time = radio_station_convert_time($time); - // because station_convert_time assumes that the show STARTS on the current day, - // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps - $time['start_timestamp'] = $time['start_timestamp'] - 86400; - $time['end_timestamp'] = $time['end_timestamp'] - 86400; - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - } - } - - $shows = array(); - foreach( $show_ids as $id ) { - $shows['all'][] = get_post($id); - } - $shows['type'] = 'shows'; - - return $shows; -} - -// --- get the next DJ or DJs scheduled to be on air based on the current time --- -function radio_station_dj_get_next($limit = 1) { - - // load the info for the DJ - global $wpdb; - - // get the various times/dates we need - $curDay = date( 'l', strtotime( current_time( 'mysql' ) ) ); - $curDayNum = date( 'N', strtotime(current_time( 'mysql') ) ); - $curDate = date( 'Y-m-d', strtotime(current_time( 'mysql') ) ); - $now = strtotime( current_time( 'mysql' ) ); - $tomorrow = date( "Y-m-d", (strtotime( $curDate ) + 86400) ); - $tomorrowDay = date( "l", (strtotime( $curDate ) + 86400) ); - $shows = array(); - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(); - $overrides = array(); - - if ( $check ) { - - foreach ( $check as $i => $p ) { - - $x = array(); - $x = $p['sched']; - - $p['sched'] = radio_station_convert_time( $p['sched'] ); - - - // compare to the current timestamp - if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { - //show is on now, so we don't need it listed under upcoming - //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - unset($check[$i]); - } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { - // show is on later today - $overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - } else { - // show is already over and we don't need it - unset($check[$i]); - } - - } - - // sort the overrides by start time - ksort( $overrides ); - } - - // Fetch all schedules... we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - - foreach ( $show_shifts as $shift ) { - - $shift->meta_value = unserialize( $shift->meta_value ); - //print_r($shift); - - // if a show has no shifts, unserialize() will return false instead of an empty array... - // fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - $encore_ids = array(); - $days = array('Sunday' => 7, 'Monday' => 1, 'Tuesday' => 2, 'Wednesday' => 3, 'Thursday' => 4, 'Friday' => 5, 'Saturday' => 6); - foreach ( $shift->meta_value as $time ) { - - if ( $time['day'] == $curDay ) { - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ); - } else { - if ( $curDayNum < $days[$time['day']] ) { - $day_diff = $days[$time['day']] - $curDayNum; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } else { - $day_diff = $curDayNum+$days[$time['day']] + 1; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } - } - - // if the shift occurs later than the current time, we want it - if ( $curShift >= $now ) { - $show_ids[$curShift.'|'.$endShift] = $shift->post_id; - // 2.2.4: set encore ID array to pass back - if ( isset( $time['encore'] ) && ( $time['encore'] == 'on' ) ) { - $encore_ids[$curShift.'|'.$endShift] = $shift->post_id; - } - } - - } - } - - // sort the shows by start time - ksort( $show_ids ); - - // merge in the overrides array - foreach ( $show_ids as $s => $id ) { - foreach ( $overrides as $o => $info ) { - $stime = explode( "|", $s ); - $otime = explode( "|", $o ); - - if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends - if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) - unset($show_ids[$s]); // this show is overriden... drop it - } - } - - } - } - - // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) - if ( !function_exists( 'array_replace' ) ) { - - function array_replace() { - $array = array(); - $n = func_num_args(); - - while ( $n-- >0 ) { - $array+=func_get_arg($n); - } - return $array; - } - } - - $combined = array_replace( $show_ids, $overrides ); - ksort( $combined ); - - // grab the number of shows from the list the user wants to display - $combined = array_slice( $combined, 0, $limit, true ); - - // fetch detailed show information - foreach ( $combined as $timestamp => $id ) { - if ( !is_array( $id ) ) { - $shows['all'][$timestamp] = get_post($id); - } else { - $id['type'] = 'override'; - $shows['all'][$timestamp] = $id; - } - } - $shows['type'] = 'shows'; - // 2.2.4: set encore IDs to pass back - $shows['encore'] = $encore_ids; - - // return the information - return $shows; -} - -// --- get the most recently entered song --- -function radio_station_myplaylist_get_now_playing() { - - // grab the most recent playlist - $args = array( - 'numberposts' => 1, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish' - ); - - $playlist = get_posts( $args ); - - // if there are no playlists saved, return nothing - if ( !$playlist ) {return false;} - - // fetch the tracks for each playlist from the wp_postmeta table - $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); - - //print_r($songs);die(); - - if ( !empty( $songs[0] ) ) { - // removed any entries that are marked as 'queued' - foreach ( $songs[0] as $i => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) { - unset( $songs[0][$i] ); - } - } - - // pop the last track off the list for display - $most_recent = array_pop( $songs[0] ); - - // get the permalink for the playlist so it can be displayed - $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); - - return $most_recent; - } else { - return false; - } -} - -// --- fetch all blog posts for a show's DJs --- -function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { - - global $wpdb; - - // do not return anything if we don't have a show - if ( !$show_id ) {return false;} - - $fetch_posts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = 'post_showblog_id' AND `meta`.`meta_value` = ".$show_id.";"); - - $blog_array = array(); - $blogposts = array(); - foreach ( $fetch_posts as $f ) {$blog_array[] = $f->post_id;} - - if ( $blog_array ) { - $blog_array = implode( ',', $blog_array ); - - $blogposts = $wpdb->get_results("SELECT `posts`.`ID`, `posts`.`post_title` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`ID` IN(".$blog_array.") - AND `posts`.`post_status` = 'publish' - ORDER BY `posts`.`post_date` DESC - LIMIT ".$limit.";"); - } - - $output = ''; - - $output .= '
        '; - $output .= '

        '.$title.'

        '; - $output .= ''; - $output .= '
        '; - - // if the blog archive page has been created, add a link to the archive for this show - $page = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = '_wp_page_template' - AND `meta`.`meta_value` = 'show-blog-archive-template.php' - LIMIT 1;"); - - if ( $page ) { - $blog_archive = get_permalink($page[0]->post_id); - $params = array( 'show_id' => $show_id ); - $blog_archive = add_query_arg( $params, $blog_archive ); - - $output .= ''.__('More Blog Posts', 'radio-station').''; - } - - return $output; -} - -// --- get any schedule overrides for today's date --- -// If currenthour is true, only overrides that are in effect NOW will be returned -function radio_station_master_get_overrides( $currenthour = false ) { - - global $wpdb; - - $now = strtotime( current_time( 'mysql' ) ); - $date = date( 'Y-m-d', $now ); - - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta_key` = 'show_override_sched' - AND `meta_value` LIKE '%".$date."%';"); - - $scheds = array(); - if ( $show_shifts ) { - foreach ( $show_shifts as $shift ) { - - $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false); - $time = $next_sched[0]; - - if ( $currenthour ) { - - // convert to 24 hour time - $check = array(); - $check = $time; - - $time = radio_station_convert_time($time); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $title = get_the_title( $shift->post_id ); - $scheds[] = array('post_id' => $shift->post_id, 'title' => $title, 'sched' => $time); - } else {continue;} - - } else { - $title = get_the_title( $shift->post_id ); - $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); - $scheds[] = array( 'post_id' => $shift->post_id, 'title' => $title, 'sched' => $sched[0] ); - } - } - } - - return $scheds; -} - -// --- shorten a string to a set number of words --- -function radio_station_shorten_string($string, $limit) { - - $shortened = $string; // just in case of a problem - - $array = explode( ' ', $string ); - if ( count( $array ) <= $limit ) { - // already at or under the limit - $shortened = $string; - } else { - array_splice( $array, $limit ); - $shortened = implode( ' ', $array )." ..."; - } - return $shortened; -} - -// --- translate weekday --- -// translated individually as cannot translate a variable -function radio_station_translate_weekday( $weekday, $short = false ) { - if ( $short ) { - if ( $weekday == 'Sun' ) {$translated = __( 'Sun', 'radio-station' );} - elseif ( $weekday == 'Mon' ) {$translated = __( 'Mon', 'radio-station' );} - elseif ( $weekday == 'Tue' ) {$translated = __( 'Tue', 'radio-station' );} - elseif ( $weekday == 'Wed' ) {$translated = __( 'Wed', 'radio-station' );} - elseif ( $weekday == 'Thu' ) {$translated = __( 'Thu', 'radio-station' );} - elseif ( $weekday == 'Fri' ) {$translated = __( 'Fri', 'radio-station' );} - elseif ( $weekday == 'Sat' ) {$translated = __( 'Sat', 'radio-station' );} - } else { - if ( $weekday == 'Sunday' ) {$translated = __( 'Sunday', 'radio-station' );} - elseif ( $weekday == 'Monday' ) {$translated = __( 'Monday', 'radio-station' );} - elseif ( $weekday == 'Tueday' ) {$translated = __( 'Tueday', 'radio-station' );} - elseif ( $weekday == 'Wednesday' ) {$translated = __( 'Wednesday', 'radio-station' );} - elseif ( $weekday == 'Thurday' ) {$translated = __( 'Thurday', 'radio-station' );} - elseif ( $weekday == 'Friday' ) {$translated = __( 'Friday', 'radio-station' );} - elseif ( $weekday == 'Saturday' ) {$translated = __( 'Saturday', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $weekday;} -} - -// --- translate month --- -function radio_station_translate_month( $month, $short = false ) { - if ( $short ) { - if ( $month == 'Jan' ) {$translated = __( 'Jan', 'radio-station' );} - elseif ( $month == 'Feb' ) {$translated = __( 'Feb', 'radio-station' );} - elseif ( $month == 'Mar' ) {$translated = __( 'Mar', 'radio-station' );} - elseif ( $month == 'Apr' ) {$translated = __( 'Apr', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'Jun' ) {$translated = __( 'Jun', 'radio-station' );} - elseif ( $month == 'Jul' ) {$translated = __( 'Jul', 'radio-station' );} - elseif ( $month == 'Aug' ) {$translated = __( 'Aug', 'radio-station' );} - elseif ( $month == 'Sep' ) {$translated = __( 'Sep', 'radio-station' );} - elseif ( $month == 'Oct' ) {$translated = __( 'Oct', 'radio-station' );} - elseif ( $month == 'Nov' ) {$translated = __( 'Nov', 'radio-station' );} - elseif ( $month == 'Dec' ) {$translated = __( 'Dec', 'radio-station' );} - } else { - if ( $month == 'January' ) {$translated = __( 'January', 'radio-station' );} - elseif ( $month == 'February' ) {$translated = __( 'February', 'radio-station' );} - elseif ( $month == 'March' ) {$translated = __( 'March', 'radio-station' );} - elseif ( $month == 'April' ) {$translated = __( 'April', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'June' ) {$translated = __( 'June', 'radio-station' );} - elseif ( $month == 'July' ) {$translated = __( 'July', 'radio-station' );} - elseif ( $month == 'August' ) {$translated = __( 'August', 'radio-station' );} - elseif ( $month == 'September' ) {$translated = __( 'September', 'radio-station' );} - elseif ( $month == 'October' ) {$translated = __( 'October', 'radio-station' );} - elseif ( $month == 'November' ) {$translated = __( 'November', 'radio-station' );} - elseif ( $month == 'December' ) {$translated = __( 'December', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $month;} -} \ No newline at end of file diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php deleted file mode 100644 index 92d14af..0000000 --- a/includes/widget_djcomingup.php +++ /dev/null @@ -1,354 +0,0 @@ - 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); - $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); - parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time']: 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        - -

        - -

        - - -

        - -

        -
        - -

        - - -
        - -
          - - 0 ) ) { - - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && $dj['type'] == 'override' ) { - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '.$dj['title'].'
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - if ( has_post_thumbnail($dj['post_id'] ) ) { - echo '
          '; - echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
          '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '.$dj['title'].'
          '; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $dj['sched']['start_hour']; - if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { - $start_hour = substr($dj['sched']['start_hour'], 1); - } - - $end_hour = $dj['sched']['end_hour']; - if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($dj['sched']['end_hour'], 1); - } - - echo '
          '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
          '; - } else { - echo '
          '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
          '; - } - } - echo '
        • '; - - } else { - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
          '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( array_key_exists( $showtime, $djs['encore'] ) ) { - echo '
          '.__('Encore Presentation','radio-station').'
          '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - echo '
          '.__( 'with', 'radio-station' ).' '; - foreach ( $ids as $id ) { - $count++; - $user_info = get_userdata( $id ); - - if ( $link_djs ) { - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - } else {echo $user_info->display_name;} - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station' ).' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
          '; - } - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - $showtimes = explode( "|", $showtime ); - // 2.2.2: fix to weekday value to be translated - $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); - - if ( $time == 12 ) { - echo '
          '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
          '; - } else { - echo '
          '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
          '; - } - - } - - echo '
        • '; - } - } - } else { - if ( $default != '' ) { - echo '
        • '.$default.'
        • '; - } - } - ?> -
        -
        - 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist']: false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $links_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - - -

        - -

        -
        - -

        - -
        - - -
          - '; - - // --- show title *for above only) --- - if ( $position == 'above' ) { - echo '
          '.$djs['all'][0]['title'].'
          '; - } - - if ( $djavatar ) { - if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo '
          '; - echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
          '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '.$djs['all'][0]['title'].'
          '; - } - - echo ''; - - // --- display the schedule override if requested --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $djs['all'][0]['sched']['start_hour']; - if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { - $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); - } - - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); - } - - echo '
          '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
          '; - - } else { - - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - echo '
          '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
          '; - - } - } - - echo ''; - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach( $djs['all'] as $dj ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - $current_sched = radio_station_current_schedule( $scheds ); - - echo '
        • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
          '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
          '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
          '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
          '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( $current_sched['encore'] == 'on' ) { - echo '
          '.__('Encore Presentation','radio-station').'
          '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - - echo '
          '.__( 'with', 'radio-station' ).' '; - foreach( $ids as $id ) { - $count++; - $user_info = get_userdata($id); - - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station').' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
          '; - } - } - - // --- show description --- - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); - echo '
          '.$desc_string.'
          '; - } - - // --- playlist link --- - if ( $show_playlist ) { - echo ''; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - // --- if we only want the schedule that's relevant now to display --- - if ( !$show_all_sched ) { - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
          '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - echo '
          '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
          '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
          '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - echo '
          '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
          '; - } - } - } - } - - echo '
        • '; - - } - } else { - echo '
        • '.$default.'
        • '; - } - } - - ?> -
        -
        - 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); - parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; - $song = isset( $instance['song'] ) ? $instance['song'] : true; - $album = isset( $instance['album'] ) ? $instance['album'] : false; - $label = isset( $instance['label'] ) ? $instance['label'] : false; - $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - - ?> -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - -

        - "; - - ?> -
        - '; - - // 2.2.3: convert span tags to div tags - // 2.2.4: check value keys are set before outputting - if ( $song && isset( $most_recent['playlist_entry_song']) ) { - echo '
        '.$most_recent['playlist_entry_song'].'
        '; - } - - if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { - echo '
        '.$most_recent['playlist_entry_artist'].'
        '; - } - - if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { - echo '
        '.$most_recent['playlist_entry_album'].'
        '; - } - - if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { - echo '
        '.$most_recent['playlist_entry_label'].'
        '; - } - - if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { - echo '
        '.$most_recent['playlist_entry_comments'].'
        '; - } - - if ( isset( $most_recent['playlist_permalink'] ) ) { - echo ''; - } - - echo '
        '; - - } else { - // 2.2.3: added missing translation wrapper - echo '
        '.__('No playlists available.','radio-station').'
        '; - } - - ?> - - -
      - + + WordPress Coding Standards + + diff --git a/radio-station-admin.php b/radio-station-admin.php new file mode 100644 index 0000000..fb835c9 --- /dev/null +++ b/radio-station-admin.php @@ -0,0 +1,458 @@ +post_type ) && ( 'playlist' === $post->post_type ) ) { + $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }' . "\n"; + } + + echo ''; + +} +add_action( 'admin_print_styles', 'radio_station_admin_styles' ); + + +// ------------------ +// === Admin Menu === +// ------------------ + +// -------------------- +// Add Admin Menu Items +// -------------------- +function radio_station_add_admin_menus() { + + $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); + $position = apply_filters( 'radio_station_menu_position', 5 ); + $capability = 'publish_playlists'; + add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); + + add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); + add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); + add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); + add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); + add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); + add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); + add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); + add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); + add_submenu_page( 'radio-station', __( 'Help', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); + + // --- hack the submenu global to add post type add/edit URLs --- + global $submenu; + foreach ( $submenu as $i => $menu ) { + if ( 'radio-station' === $i ) { + foreach ( $menu as $j => $item ) { + switch ( $item[2] ) { + case 'add-show': + // maybe remove the Add Show link for DJs + if ( ! current_user_can( 'publish_shows' ) ) { + unset( $submenu[ $i ][ $j ] ); + } else { + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=show'; + } + break; + case 'shows': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=show'; + break; + case 'playlists': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=playlist'; + break; + case 'add-playlist': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=playlist'; + break; + case 'genres': + $submenu[ $i ][ $j ][2] = 'edit-tags.php?taxonomy=genres'; + break; + case 'schedule-overrides': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=override'; + break; + case 'add-override': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=override'; + break; + } + } + } + } +} +add_action( 'admin_menu', 'radio_station_add_admin_menus' ); + +// ----------------------------------------- +// Fix to Expand Main Menu for Submenu Items +// ----------------------------------------- +// 2.2.2: added fix for genre taxonomy page and post type editing +function radio_station_fix_genre_parent( $parent_file = '' ) { + global $pagenow, $post; + $post_types = array( 'show', 'playlist', 'override' ); + if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + $parent_file = 'radio-station'; + } elseif ( 'post.php' === $pagenow && in_array( $post->post_type, $post_types, true ) ) { + $parent_file = 'radio-station'; + } + return $parent_file; +} +add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); + +// ------------------------------- +// Genre Taxonomy Submenu Item Fix +// ------------------------------- +// 2.2.2: so genre submenu item link is set to current (bold) +function radio_station_genre_submenu_fix() { + global $pagenow; + if ( 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + echo ""; + } +} +add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); + +// -------------------------- +// Add New Links to Admin Bar +// -------------------------- +// 2.2.2: re-add new post type items to admin bar +// (as no longer automatically added by register_post_type) +function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { + + // --- new show --- + if ( current_user_can( 'publish_shows' ) ) { + + $args = array( + 'id' => 'new-show', + 'title' => __( 'Show', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=show' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new playlist --- + if ( current_user_can( 'publish_playlists' ) ) { + $args = array( + 'id' => 'new-playlist', + 'title' => __( 'Playlist', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=playlist' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new schedule override --- + if ( current_user_can( 'publish_shows' ) ) { + $args = array( + 'id' => 'new-override', + 'title' => __( 'Override', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=override' ), + ); + $wp_admin_bar->add_node( $args ); + } + +} +add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); + +// ---------------- +// Output Help Page +// ---------------- +function radio_station_plugin_help() { + + // 2.2.2: include patreon button link + echo radio_station_patreon_blurb( false ); + + // --- show MailChimp signup form --- + radio_station_mailchimp_form(); + + // --- include help file template --- + include RADIO_STATION_DIR . '/templates/help.php'; +} + +// --------------------------- +// Output Playlist Export Page +// --------------------------- +function radio_station_admin_export() { + global $wpdb; + + // first, delete any old exports from the export directory + $dir = RADIO_STATION_DIR . '/export/'; + if ( is_dir( $dir ) ) { + $get_contents = opendir( $dir ); + while ( $file = readdir( $get_contents ) ) { + if ( '.' !== $file && '..' !== $file ) { + unlink( $dir . $file ); + } + } + closedir( $get_contents ); + } + + // --- watch for form submission --- + if ( isset( $_POST['export_action'] ) && ( 'station_playlist_export' === $_POST['export_action'] ) ) { + + // validate referrer and nonce field + check_admin_referer( 'station_export_valid' ); + + $start = $_POST['station_export_start_year'] . '-' . $_POST['station_export_start_month'] . '-' . $_POST['station_export_start_day']; + $start .= ' 00:00:00'; + $end = $_POST['station_export_end_year'] . '-' . $_POST['station_export_end_month'] . '-' . $_POST['station_export_end_day']; + $end .= ' 23:59:59'; + + // fetch all records that were created between the start and end dates + $sql = + "SELECT posts.ID, posts.post_date + FROM {$wpdb->posts} AS posts + WHERE posts.post_type = 'playlist' AND + posts.post_status = 'publish' AND + TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND + TO_DAYS(posts.post_date) <= TO_DAYS(%s) + ORDER BY posts.post_date ASC"; + // prepare query before executing + $query = $wpdb->prepare( $sql, array( $start, $end ) ); + $playlists = $wpdb->get_results( $query ); + + if ( ! $playlists ) { + $list = 'No playlists found for this period.';} + + // fetch the tracks for each playlist from the wp_postmeta table + foreach ( $playlists as $i => $playlist ) { + + $songs = get_post_meta( $playlist->ID, 'playlist', true ); + + // remove any entries that are marked as 'queued' + foreach ( $songs as $j => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[ $j ] );} + } + + $playlists[ $i ]->songs = $songs; + } + + $output = ''; + + $date = ''; + foreach ( $playlists as $playlist ) { + if ( ! isset( $playlist->post_date ) ) { + continue; + } + $playlist_datetime = explode( ' ', $playlist->post_date ); + $playlist_post_date = array_shift( $playlist_datetime ); + if ( empty( $date ) || $date !== $playlist_post_date ) { + $date = $playlist_post_date; + $output .= $date . "\n\n"; + } + + foreach ( $playlist->songs as $song ) { + $output .= $song['playlist_entry_artist'] . ' || ' . $song['playlist_entry_song'] . ' || ' . $song['playlist_entry_album'] . ' || ' . $song['playlist_entry_label'] . "\n"; + } + } + + // save as file + $dir = RADIO_STATION_DIR . '/export/'; + $file = $date . '-export.txt'; + if ( ! file_exists( $dir ) ) { + wp_mkdir_p( $dir );} + + $f = fopen( $dir . $file, 'w' ); + fwrite( $f, $output ); + fclose( $f ); + + // display link to file + $url = get_bloginfo( 'url' ) . '/wp-content/plugins/radio-station/tmp/' . $file; + echo wp_kses_post( '' ); + } + + // display the export page + include RADIO_STATION_DIR . '/templates/admin-export.php'; + +} + + +// -------------------- +// === Admin Notice === +// -------------------- + +// -------------------------- +// Plugin Announcement Notice +// -------------------------- +// 2.2.2: added plugin announcement notice +function radio_station_announcement_notice() { + + // --- bug out if already dismissed --- + if ( get_option( 'radio_station_announcement_dismissed' ) ) { + return; + } + + // --- bug out on certain plugin pages --- + $pages = array( 'radio-station', 'radio-station-help' ); + if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages, true ) ) { + return; + } + + // --- display plugin announcement --- + echo '
      '; + echo radio_station_patreon_blurb(); + echo ''; + echo '
      '; +} +add_action( 'admin_notices', 'radio_station_announcement_notice' ); + +// ---------------------------------- +// Dismiss Plugin Announcement Notice +// ---------------------------------- +// 2.2.2: AJAX for announcement notice dismissal +function radio_station_announcement_dismiss() { + if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { + update_option( 'radio_station_announcement_dismissed', true ); + echo ""; + exit; + } +} +add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); + +// ----------------------- +// Patreon Supporter Blurb +// ----------------------- +// 2.2.2: added simple patreon supporter blurb +function radio_station_patreon_blurb( $dismissable = true ) { + + $blurb = '
        '; + $blurb .= '
      • '; + $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); + $blurb .= ''; + $blurb .= '
      • '; + $blurb .= '
      • '; + $blurb .= '' . __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) . '
        '; + $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) . ',
        '; + $blurb .= __( 'since June 2019', 'radio-station' ) . ', '; + $blurb .= '' . __( 'Radio Station', 'radio-station' ) . ' '; + $blurb .= __( ' plugin development has been actively taken over by', 'radio-station' ); + $blurb .= ' Netmix.
        '; + $blurb .= __( 'We invite you to', 'radio-station' ); + $blurb .= ' '; + $blurb .= __( 'Become a Radio Station Patreon Supporter', 'radio-station' ); + $blurb .= ' ' . __( 'to make it better for everyone', 'radio-station' ) . '!'; + $blurb .= '
      • '; + $blurb .= '
      • '; + $blurb .= radio_station_patreon_button(); + // 2.2.7: added WordPress.Org star rating link + $blurb .= '

        '; + $blurb .= '' . __( 'Rate on WordPress.Org', 'radio-station' ) . ''; + $blurb .= '
      • '; + $blurb .= '
      '; + if ( $dismissable ) { + $blurb .= '
      '; + $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); + $blurb .= ''; + $blurb .= ''; + $blurb .= '
      '; + } + return $blurb; +} + +// ------------------------ +// Patreon Supporter Button +// ------------------------ +// 2.2.2: added simple patreon supporter image button +function radio_station_patreon_button() { + // 2.2.7: added button hover opacity + $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); + $button = ''; + $button .= ''; + $button .= ''; + $button .= ''; + return $button; +} + +// ------------------------- +// MailChimp Subscriber Form +// ------------------------- +function radio_station_mailchimp_form() { + + // --- enqueue MailChimp form styles --- + $version = filemtime( RADIO_STATION_DIR . '/css/mailchimp.css' ); + $url = plugins_url( 'css/mailchimp.css', __FILE__ ); + wp_enqueue_style( 'program-schedule', $url, array(), $version ); + + // --- get current user email --- + $current_user = wp_get_current_user(); + $user_email = $current_user->user_email; + + // --- set radio station plugin icon URL --- + $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); + + // --- output MailChimp signup form --- +?> +
      +
      + +
      +
      + +
      + + + +
      +
      +
      + + -Version: 2.2.4 +Version: 2.2.7 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -31,43 +31,54 @@ */ // === Setup === -// - Include Necessary Files -// - Load Text Domain +// - Include Plugin Files +// - Load Plugin Text Domain +// - Flush Rewrite Rules // - Enqueue Stylesheets -// - Enqueue Admin Scripts -// - Enqueue Admin Styles // === Template Filters === -// - Single Show/Playlist Template -// - Playlist Archive Template -// === Roles === -// - Add DJ Role and Capabilities +// - Single Templates Loader +// - Archive Templates Loader +// === User Roles === +// - Set DJ Role and Capabilities // - maybe Revoke Edit Show Capability -// === Menus === -// - Add Menus -// - maybe Remove Add Show Bar Link -// - Output Help Page -// - Output Export Page + +// 2.2.7: moved all admin functions to radio-station-admin.php // ------------- // === Setup === // ------------- -// --- include necessary files --- -include('includes/post_types.php'); -include('includes/master_schedule.php'); -include('includes/shortcodes.php'); -include('includes/widget_nowplaying.php'); -include('includes/widget_djonair.php'); -include('includes/widget_djcomingup.php'); -include('includes/support_functions.php'); +define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); + +// -------------------- +// Include Plugin Files +// -------------------- +require RADIO_STATION_DIR . '/includes/post-types.php'; +require RADIO_STATION_DIR . '/includes/master-schedule.php'; +require RADIO_STATION_DIR . '/includes/shortcodes.php'; +require RADIO_STATION_DIR . '/includes/support-functions.php'; +require RADIO_STATION_DIR . '/includes/class-dj-upcoming-widget.php'; +require RADIO_STATION_DIR . '/includes/class-dj-widget.php'; +require RADIO_STATION_DIR . '/includes/class-playlist-widget.php'; + +// 2.2.7: added conditional load of admin includes +if ( is_admin() ) { + require RADIO_STATION_DIR . '/radio-station-admin.php'; + require RADIO_STATION_DIR . '/includes/post-types-admin.php'; +} -// --- load the text domain --- +// ----------------------- +// Load Plugin Text Domain +// ----------------------- function radio_station_init() { load_plugin_textdomain( 'radio-station', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); } add_action( 'plugins_loaded', 'radio_station_init' ); -// --- flush rewrite rules on activate / deactivation --- +// ------------------- +// Flush Rewrite Rules +// ------------------- +// (on plugin activation / deactivation) // 2.2.3: added this for custom post types rewrite flushing register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); @@ -75,75 +86,49 @@ function radio_station_flush_rewrite_flag() { add_option( 'radio_station_flush_rewrite_rules', true ); } -// --- enqueue necessary stylesheets --- -function radio_station_load_styles() { +// -------------------------- +// Enqueue Plugin Stylesheets +// -------------------------- +function radio_station_enqueue_styles() { - $program_css = get_stylesheet_directory().'/program-schedule.css'; + $program_css = get_stylesheet_directory() . '/program-schedule.css'; if ( file_exists( $program_css ) ) { $version = filemtime( $program_css ); - $url = get_stylesheet_directory_uri().'/program-schedule.css'; + $url = get_stylesheet_directory_uri() . '/program-schedule.css'; } else { - $version = filemtime( dirname(__FILE__).'/css/program-schedule.css' ); - $url = plugins_url( 'css/program-schedule.css', __FILE__ ); + $version = filemtime( RADIO_STATION_DIR . '/css/program-schedule.css' ); + $url = plugins_url( 'css/program-schedule.css', __FILE__ ); } wp_enqueue_style( 'program-schedule', $url, array(), $version ); - // note: djonair.css style enqueueing moved to /includes/widget_djonair.php -} -add_action( 'wp_enqueue_scripts', 'radio_station_load_styles' ); - -// --- enqueue admin scripts --- -// jQuery is needed by the output of this code, so let us make sure we have it available -function radio_station_master_scripts() { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'jquery-ui-datepicker' ); - // $url = plugins_url( 'css/jquery-ui.css', dirname(__FILE__).'/radio-station.php' ); - // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); - if (is_ssl()) {$protocol = 'https';} else {$protocol = 'http';} - $url = $protocol.'://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; - wp_enqueue_style( 'jquery-ui-style', $url, '1.8.2' ); + // note: widgets.css style enqueueing moved to within widgets } -add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); -add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); - -// --- add some style rules to certain parts of the admin area --- -function radio_station_load_admin_styles() { - global $post; - - // hide the first submenu item to prevent duplicate of main menu item - $styles = ' #toplevel_page_radio-station .wp-first-item { display: none; }'."\n"; - - if ( isset( $post->post_type ) && ( $post->post_type == 'playlist' ) ) { - $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }'."\n"; - } - - echo ''; - -} -add_action( 'admin_print_styles', 'radio_station_load_admin_styles' ); +add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_styles' ); // ------------------------ // === Template Filters === // ------------------------ -// --- load the theme file for the playlist and show post types --- +// ----------------------- +// Single Templates Loader +// ----------------------- function radio_station_load_template( $single_template ) { global $post; - if ($post->post_type == 'playlist') { + if ( 'playlist' === $post->post_type ) { // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-playlist.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-playlist.php'; + $user_theme = get_stylesheet_directory() . '/single-playlist.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = RADIO_STATION_DIR . '/templates/single-playlist.php'; } } - if ($post->post_type == 'show') { + if ( 'show' === $post->post_type ) { // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-show.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-show.php'; + $user_theme = get_stylesheet_directory() . '/single-show.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = RADIO_STATION_DIR . '/templates/single-show.php'; } } @@ -151,14 +136,16 @@ function radio_station_load_template( $single_template ) { } add_filter( 'single_template', 'radio_station_load_template' ); -// --- load the theme file for the playlist archive pages --- +// ------------------------ +// Archive Templates Loader +// ------------------------ function radio_station_load_custom_post_type_template( $archive_template ) { global $post; - if ( is_post_type_archive('playlist') ) { - $playlist_archive_theme = get_stylesheet_directory().'/archive-playlist.php'; - if ( !file_exists( $playlist_archive_theme ) ) { - $archive_template = dirname(__FILE__).'/templates/archive-playlist.php'; + if ( is_post_type_archive( 'playlist' ) ) { + $playlist_archive_theme = get_stylesheet_directory() . '/archive-playlist.php'; + if ( ! file_exists( $playlist_archive_theme ) ) { + $archive_template = RADIO_STATION_DIR . '/templates/archive-playlist.php'; } } @@ -168,38 +155,42 @@ function radio_station_load_custom_post_type_template( $archive_template ) { -// ------------- -// === Roles === -// ------------- +// ------------------ +// === User Roles === +// ------------------ -// --- set up the DJ role and user capabilities --- +// ---------------------------- +// Set DJ Role and Capabilities +// ---------------------------- function radio_station_set_roles() { global $wp_roles; - // set only the necessary capabilities for DJs + // --- set only necessary capabilities for DJs --- $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'read_shows' => true, + 'edit_playlists' => true, + 'edit_published_playlists' => true, // 'edit_others_playlists' => true, // uncomment to allow DJs to edit all playlists - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true + 'read_playlists' => true, + 'publish_playlists' => true, + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, ); // $wp_roles->remove_role('dj'); // we need this here in case we ever update the capabilities list - // TODO: translate role name ? + + // --- add the role --- + // TODO: maybe translate role name ? $wp_roles->add_role( 'dj', 'DJ', $caps ); - // grant all new capabilities to admin users + // --- grant all new capabilities to admin users --- $wp_roles->add_cap( 'administrator', 'edit_shows', true ); $wp_roles->add_cap( 'administrator', 'edit_published_shows', true ); $wp_roles->add_cap( 'administrator', 'edit_others_shows', true ); @@ -224,48 +215,54 @@ function radio_station_set_roles() { if ( is_multisite() ) { add_action( 'init', 'radio_station_set_roles', 10, 0 ); } else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); + add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); } -// --- revoke the ability to edit a show if the user is not listed as a DJ on that show --- +// --------------------------------- +// maybe Revoke Edit Show Capability +// --------------------------------- +// (revoke ability to edit show if user is not assigned as a DJ to it) function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $args ) { global $post, $wp_roles; $user = wp_get_current_user(); - // determine which roles should have full access aside from administrator + // --- get roles with publish shows capability --- $add_roles = array( 'administrator' ); if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach( $wp_roles->roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( $capname == 'publish_shows' && ($capstatus == 1 || $capstatus == true) ) { + foreach ( $wp_roles->roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'publish_shows' === $capname && (bool) $capstatus ) { $add_roles[] = $name; } } } } - // exclude administrators and custom roles with appropriate capabilities... - // they should be able to do whatever they want + // --- check if current user has any of these roles --- $found = false; foreach ( $add_roles as $role ) { - if ( in_array( $role, $user->roles ) ) {$found = true;} + if ( in_array( $role, $user->roles, true ) ) { + $found = true; + } } - if ( !$found ) { + if ( ! $found ) { - // limit this to published shows + // --- limit this to published shows --- if ( isset( $post->post_type ) ) { - if ( is_admin() && ( $post->post_type == 'show' ) && ( $post->post_status == 'publish' ) ) { + if ( is_admin() && ( 'show' === $post->post_type ) && ( 'publish' === $post->post_status ) ) { $djs = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$djs || ( $djs == '' ) ) {$djs = array();} + if ( ! isset( $djs ) || empty( $djs ) ) { + $djs = array(); + } - // if they are not listed, temporarily revoke editing ability for this post - if ( !in_array( $user->ID, $djs ) ) { - $allcaps['edit_shows'] = false; + // ---- revoke editing capability if not assigned to this show --- + if ( ! in_array( $user->ID, $djs, true ) ) { + $allcaps['edit_shows'] = false; $allcaps['edit_published_shows'] = false; } } @@ -275,299 +272,3 @@ function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $arg } add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); - -// ------------- -// === Menus === -// ------------- - -function radio_station_add_admin_menus() { - - $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); - $position = apply_filters( 'radio_station_menu_position', 5 ); - $capability = 'publish_playlists'; - add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); - - add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); - add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); - add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); - add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); - add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); - add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); - add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station'), __('Export Playlists', 'radio-station'), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); - add_submenu_page( 'radio-station', __( 'Help', 'radio-station'), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); - - // hack the submenu global to post type add/edit URLs - global $submenu; - foreach ( $submenu as $i => $menu ) { - if ( $i == 'radio-station' ) { - foreach ( $menu as $j => $item ) { - if ( $item[2] == 'add-show' ) { - // maybe remove the Add Show link for DJs - // $user = wp_get_current_user(); - // if ( in_array( 'dj', $user->roles ) ) { - if ( !current_user_can( 'publish_shows' ) ) {unset($submenu[$i][$j]);} - else {$submenu[$i][$j][2] = 'post-new.php?post_type=show';} - } elseif ( $item[2] == 'shows' ) {$submenu[$i][$j][2] = 'edit.php?post_type=show';} - elseif ( $item[2] == 'playlists' ) {$submenu[$i][$j][2] = 'edit.php?post_type=playlist';} - elseif ( $item[2] == 'add-playlist' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=playlist';} - elseif ( $item[2] == 'genres' ) {$submenu[$i][$j][2] = 'edit-tags.php?taxonomy=genres';} - elseif ( $item[2] == 'schedule-overrides' ) {$submenu[$i][$j][2] = 'edit.php?post_type=override';} - elseif ( $item[2] == 'add-override' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=override';} - } - } - } -} -add_action( 'admin_menu', 'radio_station_add_admin_menus' ); - -// --- expand main menu fix for plugin submenu items --- -// 2.2.2: added fix for genre taxonomy page and post type editing -function radio_station_fix_genre_parent($parent_file = '') { - global $pagenow, $post; - $post_types = array( 'show', 'playlist', 'override' ); - if ( ( $pagenow == 'edit-tags.php') && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - $parent_file = 'radio-station'; - } elseif ( ( $pagenow == 'post.php' ) && ( in_array( $post->post_type, $post_types ) ) ) { - $parent_file = 'radio-station'; - } - return $parent_file; -} -add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); - -// --- genre taxonomy submenu item fix --- -// 2.2.2: so genre submenu item link is set to current (bold) -function radio_station_genre_submenu_fix() { - global $pagenow; - if ( ( $pagenow == 'edit-tags.php' ) && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - echo ""; - } -} -add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); - -// --- remove the Add Show link for DJs from the wp admin bar --- -// 2.2.2: re-add new post type items to admin bar -// (as no longer automatically added by register_post_type) -function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { - - // --- new show --- - if ( current_user_can( 'publish_shows' ) ) { - - $args = array( - 'id' => 'new-show', - 'title' => __( 'Show', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=show') - ); - $wp_admin_bar->add_node($args); - } - - // --- new playlist --- - if ( current_user_can( 'publish_playlists' ) ) { - $args = array( - 'id' => 'new-playlist', - 'title' => __( 'Playlist', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=playlist') - ); - $wp_admin_bar->add_node($args); - } - - // --- new schedule override --- - if ( current_user_can( 'publish_shows' ) ) { - $args = array( - 'id' => 'new-override', - 'title' => __( 'Override', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=override') - ); - $wp_admin_bar->add_node($args); - } - -} -add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); - -// --- output help page --- -function radio_station_plugin_help() { - - // 2.2.2: include patreon button link - echo radio_station_patreon_blurb(false); - - // include help template - include( dirname(__FILE__).'/templates/help.php' ); -} - -// --- output playlist export page --- -function radio_station_admin_export() { - global $wpdb; - - // first, delete any old exports from the export directory - $dir = dirname(__FILE__).'/export/'; - if ( is_dir ( $dir ) ) { - $get_contents = opendir($dir); - while ( $file = readdir( $get_contents ) ) { - if ( ( $file != '.' ) && ( $file != '..' ) ) { - unlink( $dir.$file ); - } - } - closedir($get_contents); - } - - // watch for form submission - if ( isset( $_POST['export_action']) && ( $_POST['export_action'] == 'station_playlist_export' ) ) { - - // validate referrer and nonce field - check_admin_referer( 'station_export_valid' ); - - $start = $_POST['station_export_start_year'].'-'.$_POST['station_export_start_month'].'-'.$_POST['station_export_start_day']; - $start .= ' 00:00:00'; - $end = $_POST['station_export_end_year'].'-'.$_POST['station_export_end_month'].'-'.$_POST['station_export_end_day']; - $end .= ' 23:59:59'; - - // fetch all records that were created between the start and end dates - $query = - "SELECT `posts`.`ID`, `posts`.`post_date` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`post_type` = 'playlist' - AND `posts`.`post_status` = 'publish' - AND TO_DAYS(`posts`.`post_date`) >= TO_DAYS(%s) - AND TO_DAYS(`posts`.`post_date`) <= TO_DAYS(%s) - ORDER BY `posts`.`post_date` ASC;"; - //" prepare query before executing - $query = $wpdb->prepare( $query, array( $start, $end ) ); - $playlists = $wpdb->get_results( $query ); - - if ( !$playlists ) {$list = 'No playlists found for this period.';} - - // fetch the tracks for each playlist from the wp_postmeta table - foreach ( $playlists as $i => $playlist ) { - - $songs = get_post_meta( $playlist->ID, 'playlist', true ); - - // remove any entries that are marked as 'queued' - foreach ( $songs as $j => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) {unset($songs[$j]);} - } - - $playlists[$i]->songs = $songs; - } - - $output = ''; - - $date = ''; - foreach ( $playlists as $playlist ) { - - if ( ( $date == '' ) || ( $date != array_shift( explode( " ", $playlist->post_date ) ) ) ) { - $date = array_shift( explode( " ", $playlist->post_date ) ); - $output .= $date."\n\n"; - } - - foreach ( $playlist->songs as $song ) { - $output .= $song['playlist_entry_artist'].' || '.$song['playlist_entry_song'].' || '.$song['playlist_entry_album'].' || '.$song['playlist_entry_label']."\n"; - } - } - - // save as file - $dir = dirname(__FILE__).'/export/'; - $file = $date.'-export.txt'; - if ( !file_exists( $dir ) ) {wp_mkdir_p( $dir );} - - $f = fopen( $dir.$file, 'w' ); - fwrite( $f, $output ); - fclose( $f ); - - // display link to file - $url = get_bloginfo('url').'/wp-content/plugins/radio-station/tmp/'.$file; - echo ''; - } - - // display the export page - include( dirname(__FILE__).'/templates/admin-export.php' ); - -} - - -// -------------------- -// === Admin Notice === -// -------------------- - -// --- plugin announcement notice --- -// 2.2.2: added plugin announcement notice -function radio_station_announcement_notice() { - - // --- bug out if already dismissed --- - if ( get_option( 'radio_station_announcement_dismissed' ) ) {return;} - - // --- bug out on certain plugin pages --- - $pages = array( 'radio-station', 'radio-station-help' ); - if ( isset( $_REQUEST['page'] ) && ( in_array( $_REQUEST['page'], $pages ) ) ) {return;} - - // --- display plugin announcement --- - echo '
      '; - echo radio_station_patreon_blurb(); - echo ''; - echo '
      '; -} -add_action( 'admin_notices', 'radio_station_announcement_notice' ); - -// --- dismiss plugin announcement notice --- -// 2.2.2: AJAX for announcement notice dismissal -function radio_station_announcement_dismiss() { - if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { - update_option( 'radio_station_announcement_dismissed', true ); - echo ""; - exit; - } -} -add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); - -// --- Patreon supporter blurb --- -// 2.2.2: added simple patreon supporter blurb -function radio_station_patreon_blurb( $dismissable = true ) { - - $blurb = '
        '; - $blurb .= '
      • '; - $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); - $blurb .= ''; - $blurb .= '
      • '; - $blurb .= '
      • '; - $blurb .= ''.__( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ).'
        '; - $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ).',
        '; - $blurb .= __( 'since June 2019', 'radio-station' ).', '; - $blurb .= ''.__( 'Radio Station', 'radio-station' ).' '; - $blurb .= __(' plugin development has been actively taken over by', 'radio-station' ); - $blurb .= ' Netmix.
        '; - $blurb .= __( 'We invite you to', 'radio-station'); - $blurb .= ' '; - $blurb .= __('Become a Radio Station Patreon Supporter', 'radio-station'); - $blurb .= ' '.__('to make it better for everyone', 'radio-station').'!'; - $blurb .= '
      • '; - $blurb .= '
      • '; - $blurb .= radio_station_patreon_button(); - $blurb .= '
      • '; - if ($dismissable) { - $blurb .= '
      • '; - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - $blurb .= ''; - $blurb .= ''; - $blurb .= '
      • '; - } - $blurb .= '
      '; - return $blurb; -} - -// --- Patreon supporter button --- -// 2.2.2: added simple patreon supporter image button -function radio_station_patreon_button() { - $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); - $button = ''; - $button .= ''; - $button .= ''; - $button .= ''; - return $button; -} - diff --git a/readme.txt b/readme.txt index ea05893..cde0a8d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,26 +1,34 @@ === Radio Station === Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate -Tags: dj, music, playlist, radio, scheduling +Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.2.2 Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html -Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. Functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. +The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. +The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate. +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues + +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. + +For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ + +You can find a demo version of the plugin on our demo site here: https://netmix.co/radio-station-demo == Installation == @@ -238,6 +246,36 @@ you send me your finished translation, I'd love to include it. == Changelog == += 2.2.7 = +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations + += 2.2.6 = +* Reorganize master-list shortcode into templates +* Add constant for plugin directory +* Use WP_Query instead of get_posts +* New posts_per_page and tax_query +* Fixes for undefined indexes +* Fixes for raw mysql queries +* Typecasting to support strict comparisons + += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + = 2.2.4 = * added title position and avatar width options to widgets * added missing DJ author links as new option to widgets @@ -503,6 +541,36 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.4 = +Adds title position and avatar width options to widgets; missing DJ author links as new option to widgets; cleanup, improve and fix enqueued Widget CSS (on air/upcoming); show Encore Presentation in show widget displays; fix to Show shift Encore Presentation checkbox saving + += 2.2.3 = +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + += 2.2.2 = +* shift main playlist and show metaboxes above editor +* set plugin custom post types editor to Classic Editor +* add high priority to side metaboxes for plugin post types +* added dismissable development changeover admin notice +* added simple Patreon supporter image button and blurb +* added filter for DJ Avatar size on Author page template +* fix to Schedule Override metabox value saving +* fix to Playlist track list overflowing metabox +* fix to missing day of week headings in master schedule +* fix to file_exists check for DJ on Air stylesheet path +* fix to make DJ multi-select input full metabox width +* fix to expand admin menu when on genre taxonomy page +* fix to expand admin menu when editing plugin post types +* fix to genre submenu item link for current page +* added GitHub URI to plugin header for GitHub updater + += 2.2.1 = +* Re-commit all missing files via SVN + = 2.2.0 = * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) diff --git a/templates/admin-export.php b/templates/admin-export.php index be0fbbe..a44c8df 100644 --- a/templates/admin-export.php +++ b/templates/admin-export.php @@ -1,5 +1,5 @@
      -

      +

      @@ -8,96 +8,143 @@ - - + + - + @@ -106,9 +153,9 @@
      - + - +
      - + - + - +
        - +
      -
      \ No newline at end of file +
      diff --git a/templates/archive-playlist.php b/templates/archive-playlist.php index b70f87c..69a5a65 100644 --- a/templates/archive-playlist.php +++ b/templates/archive-playlist.php @@ -10,34 +10,38 @@ - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'playlist_show_id', - 'value' => $show_id - ) - ), - 'paged' => $paged + 'post_type' => 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'playlist_show_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, ); $loop = new WP_Query( $args ); // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); ?> - have_posts() ) : $loop->the_post(); ?> + have_posts() ) : + $loop->the_post(); + ?> -
    - \ No newline at end of file + diff --git a/templates/author.php b/templates/author.php index 323799f..7f1cca1 100644 --- a/templates/author.php +++ b/templates/author.php @@ -3,7 +3,8 @@ * The template for displaying DJ info in the Author Archive pages. Based on the TwentyEleven theme. */ -get_header(); ?> +get_header(); +?>
    - roles ) ): ?> + roles, true ) ) : ?>
    ID, $avatar_size ); ?>
    - description; ?> + description ); ?>
    - URL: user_url; ?>
    - Email: user_email; ?>
    - AIM: aim; ?>
    - Jabber: jabber; ?>
    - YIM: yim; ?>
    + URL: user_url ); ?>
    + Email: user_email ); ?>
    + AIM: aim ); ?>
    + Jabber: jabber ); ?>
    + YIM: yim ); ?>
    @@ -49,7 +50,7 @@ ?> + if ( get_the_author_meta( 'description' ) ) : + ?>
    -

    +

    - +
    -

    +

    -

    +

    @@ -107,4 +112,4 @@
    - \ No newline at end of file + diff --git a/templates/help.php b/templates/help.php index 6e39550..acc7522 100644 --- a/templates/help.php +++ b/templates/help.php @@ -6,7 +6,7 @@
    I'm seeing 404 Not Found errors when I click on the link for a show! -

    Try re-saving your site's permalink settings. Wordpress sometimes gets confused with a new custom post type is added.

    +

    Try re-saving your site's permalink settings. WordPress sometimes gets confused with a new custom post type is added.


    @@ -136,7 +136,7 @@

    First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the playlist archive. +Then, create a Page in WordPress to hold the playlist archive.

    Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear.

    @@ -146,7 +146,7 @@

    Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the blog archive. +Then, create a Page in WordPress to hold the blog archive.

    Under Page Attributes, set the template to Show Blog Archive.

    diff --git a/templates/master-schedule-default.php b/templates/master-schedule-default.php new file mode 100644 index 0000000..d3e2ae7 --- /dev/null +++ b/templates/master-schedule-default.php @@ -0,0 +1,211 @@ +'; + +// output the headings in the correct order +$output .= ' '; +foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= '' . $heading . ''; +} +$output .= ''; +// $output .= ' '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').' '; + +if ( ! isset( $nextskip ) ) { + $nextskip = array(); +} + +foreach ( $master_list as $hour => $days ) { + + $output .= ''; + $output .= '
    '; + + // 2.2.7: added meridiem translations + if ( 12 === (int) $timeformat ) { + if ( 0 === $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'am' ); + } elseif ( (int) $hour < 12 ) { + $output .= $hour . radio_station_translate_meridiem( 'am' ); + } elseif ( 12 === (int) $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'pm' ); + } else { + $output .= ( $hour - 12 ) . radio_station_translate_meridiem( 'pm' ); + } + } else { + if ( $hour < 10 ) { + $output .= '0'; + } + $output .= $hour . ':00'; + } + + $output .= '
    '; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] === $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[ $x ]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $shift ) { + + if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { + //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { + // show runs from before midnight night until the next morning + if ( isset( $shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue; + } + + $output .= ''; + + foreach ( $min as $shift ) { + + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); + $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; + foreach ( $terms as $shift_term ) { + $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; + } + + $output .= '
    '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $shift['id'] ) ) { + $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $shift['id'] ) . ''; + } else { + $output .= get_the_title( $shift['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); + $count = 0; + + if ( $dj_names ) { + + $output .= ' ' . __( 'with', 'radio-station' ) . ' '; + foreach ( $dj_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $dj_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ); + $output .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $output .= ' - '; + $output .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $shift['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
    '; + } + $output .= ''; + } + + $output .= ''; +} +$output .= ''; diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php new file mode 100644 index 0000000..a5059ab --- /dev/null +++ b/templates/master-schedule-div.php @@ -0,0 +1,162 @@ +'; +for ( $i = 2; $i < 24; $i++ ) { + $rowheight = $atts['divheight'] * $i; + + $output .= '#master-schedule-divs .rowspan' . $i . ' { '; + $output .= 'height: ' . ( $rowheight ) . 'px; }'; +} + +$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; +$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; +$output .= ''; + +// output the schedule +$output .= radio_station_master_fetch_js_filter(); +$output .= '
    '; +$weekdays = array_keys( $days_of_the_week ); + +$output .= '
    '; +$output .= '
     
    '; +foreach ( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
    ' . $translated . '
    '; +} + $output .= '
    '; + +foreach ( $master_list as $hour => $days ) { + + $output .= '
    '; + + // output the hour labels + $output .= '
    '; + if ( 12 === (int) $timeformat ) { + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format + } + $output .= '
    '; + + foreach ( $weekdays as $weekday ) { + $output .= '
    '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $showdata ) { + + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); + $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; + foreach ( $terms as $show_term ) { + $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + } + + $output .= '
    '; + + // featured image + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); + } + $output .= ''; + } + + // title + link to page if requested + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $showdata['id'] ) . ''; + } else { + $output .= get_the_title( $showdata['id'] ); + } + $output .= ''; + + // list of DJs + if ( $atts['show_djs'] ) { + + $output .= ''; + + $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + + $output .= ' with '; + + foreach ( $show_names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // show's schedule + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + // designate as encore + if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + // link to media file + $show_link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day + $duration = $showdata['time']['end_hour']; + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { + $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; + } else { + $duration = 23 - $showdata['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
    '; + + if ( '00' !== $showdata['time']['end_min'] ) { + $output .= '
    '; + } + } + + $output .= '
    '; // end master-show-entry + } + } + $output .= '
    '; // end master-schedule-weekday + } + $output .= '
    '; // end master-schedule-hour +} +$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php new file mode 100644 index 0000000..7d26aa6 --- /dev/null +++ b/templates/master-schedule-list.php @@ -0,0 +1,110 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
      '; + +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
    • '; + $output .= '' . $display_day . ''; + $output .= '
        '; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + $output .= '
      • '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $show['id'] ) ) { + $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + $output .= ''; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $output .= ' '.__( 'with', 'radio-station').' '; + foreach ( $show_names as $name ) { + $count++; + $user_info = get_userdata( $name ); + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' '.__( 'and', 'radio-station' ).' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ' '; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); + $output .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $output .= ' - '; + $output .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $output .= ''; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
      • '; + } + } + $output .= '
      '; + $output .= '
    • '; +} + +$output .= '
    '; diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php new file mode 100644 index 0000000..534204e --- /dev/null +++ b/templates/master-schedule-tabs.php @@ -0,0 +1,146 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
      '; + +$panels = ''; +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
    • '; + $output .= '
      ' . $display_day . '
      '; + $output .= '
    • '; + + // 2.2.7: separate headings from panels for tab view + $panels .= '
        '; + + $foundshows = false; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + + $foundshows = true; + $panels .= '
      • '; + + // --- Show Image --- + // (defaults to display on) + if ( $atts['show_image'] !== 'false' ) { + $panels .= '
        '; + if ( has_post_thumbnail( $show['id'] ) ) { + $panels .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $panels .= '
        '; + } + + // --- Show Information --- + $panels .= '
        '; + + $panels .= '
        '; + if ( $atts['show_link'] ) { + $panels .= '' . get_the_title( $show['id'] ) . ''; + } else { + $panels .= get_the_title( $show['id'] ); + } + $panels .= '
        '; + + if ( $atts['show_djs'] ) { + $panels .= '
        '; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $panels .= ' '.__( 'with', 'radio-station').' '; + foreach ( $show_names as $name ) { + $count++; + $user_info = get_userdata( $name ); + $panels .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $panels .= ' '.__( 'and', 'radio-station' ).' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $panels .= ', '; + } + } + } + + $panels .= '
        '; + } + + if ( $atts['display_show_time'] ) { + + $panels .= '
        '; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); + $panels .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $panels .= ' - '; + $panels .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + + $panels .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $panels .= ' - '; + $panels .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $panels .= '
        '; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $panels .= '
        ' . __( 'encore airing', 'radio-station' ) . '
        '; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $panels .= ' '; + } + + $panels .= '
        '; + + // --- Show Genres list --- + // (defaults to display on) + if ( $atts['show_genres'] !== 'false' ) { + $panels .= '
        '; + $terms = wp_get_post_terms( $show['id'], 'genres', array() ); + $genres = array(); + if ( count( $terms ) > 0 ) { + foreach ( $terms as $term ) {$genres[] = '' . $term->name . '';} + $genredisplay = implode( ', ', $genres ); + $panels .= __( 'Genres', 'radio-station' ) . ': ' . $genredisplay; + } + $panels .= '
        '; + } + + $panels .= '
      • '; + } + } + + if (!$foundshows) { + $panels .= '
      • '; + $panels .= __('No Shows found for this day.','radio-station'); + $panels .= '
      • '; + } + + $panels .= '
      '; +} + +$output .= '
    '.$panels; diff --git a/templates/playlist-archive-template.php b/templates/playlist-archive-template.php index 34849cd..514b739 100644 --- a/templates/playlist-archive-template.php +++ b/templates/playlist-archive-template.php @@ -13,22 +13,25 @@ - + 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'paged' => $paged, - 'post_status' => 'publish' + 'post_type' => 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'paged' => $paged, + 'post_status' => 'publish', ); $loop = new WP_Query( $args ); ?> - have_posts()) : $loop->the_post(); ?> + have_posts() ) : + $loop->the_post(); + ?>
    >
    @@ -37,18 +40,20 @@
    - - post_date)); ?> + post_date ) ) ); ?>
    @@ -58,11 +63,11 @@
    -

    +

    -

    +

    @@ -72,4 +77,4 @@ - \ No newline at end of file + diff --git a/templates/show-blog-archive-template.php b/templates/show-blog-archive-template.php index 4023a08..31e8516 100644 --- a/templates/show-blog-archive-template.php +++ b/templates/show-blog-archive-template.php @@ -12,74 +12,72 @@ - - + $post_types, - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $show_id - ) - ), - 'paged' => $paged + $args = array( + 'post_type' => $post_types, + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'post_showblog_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, ); - $temp = $wp_query; - $wp_query = null; - $wp_query = new WP_Query( $args ); - ?> - have_posts()) : $wp_query->the_post(); ?> + $archive_query = new WP_Query( $args ); + while ( $archive_query->have_posts() ) : + $archive_query->the_post(); + ?> -
    > -
    -

    +
    > +
    +

    - - - + - +
    -
    - -
    - -
    -
    - +
    + +
    + +
    +
    + - -
    -

    +

    -

    +

    @@ -89,4 +87,4 @@ - \ No newline at end of file + diff --git a/templates/single-playlist.php b/templates/single-playlist.php index 998baa5..fbc879f 100644 --- a/templates/single-playlist.php +++ b/templates/single-playlist.php @@ -3,47 +3,64 @@ * The Template for displaying all single playlist posts. Based on TwentyEleven. */ -get_header(); ?> +get_header(); +?>
    - +
    >
    - ID, 'playlist_show_id', true ); ?> + ID, 'playlist_show_id', true ); + ?>

    -

    +

    - '' ) ); ?> + '', + ) + ); + ?> ID, 'playlist', true ); ?> - +
    - - - - - + + + + + - - - > - - - - - + + + > + + + + + @@ -51,7 +68,7 @@
    - +
    @@ -66,4 +83,4 @@ - \ No newline at end of file + diff --git a/templates/single-show.php b/templates/single-show.php index 04053ec..97ac144 100644 --- a/templates/single-show.php +++ b/templates/single-show.php @@ -3,130 +3,163 @@ * The Template for displaying all single playlist posts. Based on TwentyEleven. */ -get_header(); ?> - -
    -
    - - -
    > -
    -

    -
    - -
    - - -
    -

    :

    +get_header(); +?> + +
    +
    + + +
    > +
    +

    +
    + +
    + + +
    +

    :

    '.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($djs) == 2 ) ) - || ( ( count($djs) > 2 ) && ( $count == ( count($djs) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($djs) ) && ( count($djs) > 2 ) ) { - echo ', '; - } + $djs = get_post_meta( get_the_ID(), 'show_user_list', true ); + $count = 0; + if ( $djs ) { + foreach ( $djs as $dj ) { + $count++; + $user_info = get_userdata( $dj ); + + echo '' . esc_html( $user_info->display_name ) . ''; + + $dj_count = count( $djs ); + if ( ( 1 === $count && 2 === $dj_count ) || ( $dj_count > 2 && $count === $dj_count - 1 ) ) { + echo ' and '; + } elseif ( ( $count < count( $djs ) ) && ( count( $djs ) > 2 ) ) { + echo ', '; } } + } ?> -
    +
    -
    -

    :

    +
    +

    :

    + 'genres', 'title_li' => '') ); + ?> +
      'genres', 'title_li' => '') ); + $terms = wp_get_post_terms( get_the_ID(), 'genres' ); + foreach ( $terms as $genre ) { + echo '
    • ' . esc_html( $genre->name ) . '
    • '; + } ?> -
        +
      +
    + +

    + + + + + +
    + + + +
    + +
    +

    +
      + '; + echo esc_html( $weekday . ' - ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] . ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian'] ); + echo ''; } + } + + // 24-hour time + /* + $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); + if ( $shifts ) { + foreach ( $shifts as $shift ) { + $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); + $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); + + $weekday = radio_station_translate_weekday( $shift['day'] ); + echo '
    • '; + echo $weekday.' - '.$start.' - '.$end; + echo '
    • '; + } + } + */ ?> -
    -
    - -

    - - - - - -
    - -
    - -
    -

    -
      - '; - echo $weekday.' - '.$shift['start_hour'].':'.$shift['start_min'].' '.$shift['start_meridian'].' - '.$shift['end_hour'].':'.$shift['end_min'].' '.$shift['end_meridian']; - echo ''; - } - } - - // 24-hour time - /* - $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); - if ( $shifts ) { - foreach ( $shifts as $shift ) { - $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); - $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); - - $weekday = radio_station_translate_weekday( $shift['day'] ); - echo '
    • '; - echo $weekday.' - '.$start.' - '.$end; - echo '
    • '; - } - } - */ - ?> -
    -
    - -
    -

    - -
    - - - - - - '' ) ); ?> -
    -
    - - - - - -
    -
    - - \ No newline at end of file + +
    + +
    +

    + +
    + + + + + + '', + ) + ); + ?> +
    +
    + + + + + +
    +
    + + From 8d3f34f782e12b8a92b5f456aa30ced0e1ea7246 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 28 Aug 2019 12:58:02 -0400 Subject: [PATCH 049/377] Update the Upgrade Notice for 2.2.7 Update the Upgrade Notice in the read me for 2.2.7 --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/.DS_Store b/.DS_Store index dd04cc4b9bcdba9369ed60df988258845e5d03c6..d17289c0438c1c252e15a35022f1610dcdb9984b 100644 GIT binary patch delta 846 zcmZvaO-$2Z9LC>gFnG&K)(sd?!Ujw?3<@)%0W^GMiC}<$0R@B@wzZhb*pL?dIJg+` zBYGhgJt>ALFi-}%L7DW?DG~PU!XuSHioza6mwD0r(KGD?W;esnsns9GrW5w@IMdtN@}3LJc)*x9wu z!*}bUqGjFT3a8wH%UM<~io>%}<=CvEofQ0(W}Je{&3nZvPIyemq=+W>CM9J;ktLIe zUU3tpqsBZuBeT0T=7ZuEPKeM!RXHVFuh`1hbArkZPAZA)*6sWbDoEUccqA&vGtLw< z5_j=UROsf0=HwYwNhU0hVtxS zIAY8@BuSPirT+E-VqX6C+U&x55W)!{lvI)cY0|^)x|AEmD1iq)RG|ibgwTu@>_#X0 zu^$5%#0a8@LB<3opI2Ji45>-c~#_=X?& zX_?!QJw(r+!w{l3h<5y9`@6*-^}RRmJ3v5M*%pHDF2pcz93f<9gV z>x(mGU64^*mNkSJ%VE`6tZ^sr6YQ)7YiDx{&kMP%CX2SVF`CEvuxPlFQNnTm23qhp zQT=mqOROG^XvJQ1p&LEu#X$_?5JoY^B0B;FN6pyg%-GK0EY9IP3vU5eu!yU;hMQQ% zE!@Tm?y(3Tnh`$5Gd#x&yu@o{@JWB`_F=>L-}RNCOJ6GqZ7wAf)xQByr@$Hj delta 789 zcmY+CO-vI(7>4&nlvx?mDPSF-RHVj2M6D)3L?Sc|)=&u?s0Ag4mR)qAbdfCtiCQ%^ z#*^S0PbM0FZweMq#`t$3@#j<%Jehdac=P1N+1+Y%4m-QwJ73;!o_BL2xsioF@a6vJ zE>Lqh;30}Jno8<=%3KO;p($fFoJ{FSNh-N-`YJtTJaRbVp*?0jZ!@PW$IWtgIO}5W z3SP}?3G!8;1}lYP}8K?L^#{5&M${yiKx*guzh*-d%*S5=RSq_sJp!w$9O7 zXd4l{*?LE9rya6Lgq1pKCk+w7ooy)8R@d-w9i>ci$@2dMe^#|;gJ?yUO{@n|^x~+X zHiTmshKezq667pyGnmC3F5(h$xGd-`2zn-N;3jV44({Tfp!gV1u#Bfz!3)9il}+;< s3J$No{CfW7C;no5viO;+n?w1yXGLDTpybQz*UHRaHQVx~jbH4S-xn^mt^fc4 diff --git a/readme.txt b/readme.txt index 35010c3..6a9d1a3 100644 --- a/readme.txt +++ b/readme.txt @@ -543,6 +543,22 @@ You may translate the plugin into another language. Please visit our WordPress T = 2.2.7 = * Dutch translation added (Thank you to André Dortmont for the file!) +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations = 2.2.6 = * Reorganize master-list shortcode into templates, From 4da1f85353009e66eccebe54d1145b6b5dae3b61 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 29 Aug 2019 10:53:45 +1000 Subject: [PATCH 050/377] 2.2.7 merge conflict fi --- includes/class-dj-upcoming-widget.php | 25 +- includes/class-dj-widget.php | 53 - includes/class-playlist-widget.php | 49 - includes/master-schedule.php | 41 - includes/post-types.php | 1290 ---------------------- includes/shortcodes.php | 510 +-------- includes/support-functions.php | 105 -- radio-station.php | 10 +- templates/archive-playlist.php | 86 -- templates/author.php | 118 -- templates/playlist-archive-template.php | 83 -- templates/show-blog-archive-template.php | 93 -- templates/single-playlist.php | 89 -- templates/single-show.php | 168 --- 14 files changed, 8 insertions(+), 2712 deletions(-) diff --git a/includes/class-dj-upcoming-widget.php b/includes/class-dj-upcoming-widget.php index 1dda31c..6b35b98 100644 --- a/includes/class-dj-upcoming-widget.php +++ b/includes/class-dj-upcoming-widget.php @@ -149,11 +149,7 @@ public function update( $new_instance, $old_instance ) { $instance['default'] = $new_instance['default']; $instance['limit'] = $new_instance['limit']; $instance['time'] = $new_instance['time']; -<<<<<<< HEAD - $instance['show_sched'] = $new_instance['show_sched']; -======= $instance['show_sched'] = ( isset( $new_instance['show_sched'] ) ? 1 : 0 ); ->>>>>>> release/2.2.7 // 2.2.4: added title position, avatar width and DJ link settings $instance['title_position'] = $new_instance['title_position']; @@ -397,12 +393,8 @@ public function widget( $args, $instance ) { // 2.2.2: fix to weekday value to be translated $weekday = radio_station_translate_weekday( date( 'l', $showtimes[0] ) ); -<<<<<<< HEAD - if ( 12 === $time ) { -======= // 2.2.7: fix to convert time to integer if ( 12 === (int) $time ) { ->>>>>>> release/2.2.7 ?>
    , - @@ -444,13 +436,8 @@ public function widget( $args, $instance ) { $version = filemtime( $dj_widget_css ); $url = get_stylesheet_directory_uri() . '/widgets.css'; } else { -<<<<<<< HEAD - $version = filemtime( dirname( dirname( __FILE__ ) ) . '/css/widgets.css' ); - $url = plugins_url( 'css/widgets.css', dirname( dirname( __FILE__ ) ) . '/radio-station.php' ); -======= $version = filemtime( RADIO_STATION_DIR . '/css/widgets.css' ); $url = plugins_url( 'css/widgets.css', RADIO_STATION_DIR . '/radio-station.php' ); ->>>>>>> release/2.2.7 } wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); @@ -460,18 +447,8 @@ public function widget( $args, $instance ) { // --- register the widget --- -<<<<<<< HEAD -add_action( - 'widgets_init', - function() { - return register_widget( 'DJ_Upcoming_Widget' ); - } -); -======= // 2.2.7: revert anonymous function usage for backwards compatibility add_action( 'widgets_init', 'radio_station_register_djcomingup_widget' ); function radio_station_register_djcomingup_widget() { register_widget('DJ_Upcoming_Widget'); -} - ->>>>>>> release/2.2.7 +} \ No newline at end of file diff --git a/includes/class-dj-widget.php b/includes/class-dj-widget.php index c3c6f69..c76ec95 100644 --- a/includes/class-dj-widget.php +++ b/includes/class-dj-widget.php @@ -156,11 +156,7 @@ public function form( $instance ) { >>>>>> release/2.2.7 public function update( $new_instance, $old_instance ) { $instance = $old_instance; @@ -170,18 +166,11 @@ public function update( $new_instance, $old_instance ) { $instance['link'] = ( isset( $new_instance['link'] ) ? 1 : 0 ); $instance['default'] = $new_instance['default']; $instance['time'] = $new_instance['time']; -<<<<<<< HEAD - $instance['show_sched'] = $new_instance['show_sched']; - $instance['show_playlist'] = $new_instance['show_playlist']; - $instance['show_all_sched'] = $new_instance['show_all_sched']; - $instance['show_desc'] = $new_instance['show_desc']; -======= // 2.2.7: fix checkbox value saving $instance['show_sched'] = ( isset( $new_instance['show_sched'] ) ? 1 : 0 ); $instance['show_playlist'] = ( isset( $new_instance['show_playlist'] ) ? 1 : 0 ); $instance['show_all_sched'] = ( isset( $new_instance['show_all_sched'] ) ? 1 : 0 ); $instance['show_desc'] = ( isset( $new_instance['show_desc'] ) ? 1 : 0 ); ->>>>>>> release/2.2.7 // 2.2.4: added title position and avatar width settings $instance['title_position'] = $new_instance['title_position']; @@ -272,11 +261,7 @@ public function widget( $args, $instance ) { if ( 'above' !== $position ) { ?>
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    12 ) {$end_hour = $end_hour - 12;} ->>>>>>> release/2.2.7 ?>
    @@ -421,11 +394,7 @@ public function widget( $args, $instance ) { if ( $link_djs ) { ?> -<<<<<<< HEAD - display_name ); ?> -======= display_name ); ?> ->>>>>>> release/2.2.7 >>>>>> release/2.2.7 if ( ! $show_all_sched ) { if ( $current_sched ) { @@ -506,11 +471,7 @@ public function widget( $args, $instance ) { if ( 12 === (int) $time ) { ?>
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    >>>>>> release/2.2.7 } // 2.2.4: fix to media argument wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); @@ -569,17 +525,8 @@ public function widget( $args, $instance ) { } // --- register the widget --- -<<<<<<< HEAD -add_action( - 'widgets_init', - function() { - return register_widget( 'DJ_Widget' ); - } -); -======= // 2.2.7: revert anonymous function usage for backwards compatibility add_action( 'widgets_init', 'radio_station_register_dj_widget' ); function radio_station_register_dj_widget() { register_widget('DJ_Widget'); } ->>>>>>> release/2.2.7 diff --git a/includes/class-playlist-widget.php b/includes/class-playlist-widget.php index 2c413ef..ca2f969 100644 --- a/includes/class-playlist-widget.php +++ b/includes/class-playlist-widget.php @@ -35,28 +35,16 @@ public function form( $instance ) {

    -<<<<<<< HEAD -

    -<<<<<<< HEAD -

    @@ -135,27 +123,16 @@ public function widget( $args, $instance ) { if ( $song && isset( $most_recent['playlist_entry_song'] ) ) { ?>
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    -
    - -======= // 2.2.7: add label prefixes to now playing data if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { ?>
    ->>>>>>> release/2.2.7
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    -<<<<<<< HEAD - -======= ->>>>>>> release/2.2.7
    >>>>>> release/2.2.7 } wp_enqueue_style( 'dj-widget', $url, array(), $version, 'all' ); @@ -243,17 +203,8 @@ public function widget( $args, $instance ) { } // --- register the widget --- -<<<<<<< HEAD -add_action( - 'widgets_init', - function() { - return register_widget( 'Playlist_Widget' ); - } -); -======= // 2.2.7: revert anonymous function usage for backwards compatibility add_action( 'widgets_init', 'radio_station_register_nowplaying_widget' ); function radio_station_register_nowplaying_widget() { register_widget('Playlist_Widget'); } ->>>>>>> release/2.2.7 diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 049e039..a333041 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -17,10 +17,7 @@ function radio_station_master_schedule( $atts ) { 'list' => 'table', 'show_image' => 0, 'show_djs' => 0, -<<<<<<< HEAD -======= 'show_genres' => 0, ->>>>>>> release/2.2.7 'divheight' => 45, ), $atts, @@ -64,19 +61,6 @@ function radio_station_master_schedule( $atts ) { // get the show schedules, excluding shows marked as inactive $show_shifts = $wpdb->get_results( -<<<<<<< HEAD - "SELECT meta.post_id, meta.meta_value - FROM {$wpdb->postmeta} AS meta - JOIN {$wpdb->postmeta} AS active - ON meta.post_id = active.post_id - JOIN {$wpdb->posts} as posts - ON posts.ID = meta.post_id - WHERE meta.meta_key = 'show_sched' AND - posts.post_status = 'publish' AND - ( - active.meta_key = 'show_active' AND - active.meta_value = 'on' -======= "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta JOIN {$wpdb->postmeta} AS active @@ -88,7 +72,6 @@ function radio_station_master_schedule( $atts ) { ( active.meta_key = 'show_active' AND active.meta_value = 'on' ->>>>>>> release/2.2.7 )" ); @@ -145,20 +128,12 @@ function radio_station_master_schedule( $atts ) { } // if it ends after midnight, fix it -<<<<<<< HEAD - if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || - //if it starts at night and ends in the morning, end hour is on the following day - ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || - //if the start and end times are identical, assume the end time is the following day - ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day -======= // if it starts at night and ends in the morning, end hour is on the following day if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || // if the start and end times are identical, assume the end time is the following day ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) ->>>>>>> release/2.2.7 ) { if ( 12 === (int) $timeformat ) { @@ -209,14 +184,6 @@ function radio_station_master_schedule( $atts ) { $output = ''; -<<<<<<< HEAD - if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { - require RADIO_STATION_DIR . '/templates/master-list-list.php'; - } elseif ( 'divs' === $atts['list'] ) { - require RADIO_STATION_DIR . '/templates/master-list-div.php'; - } else { - require RADIO_STATION_DIR . '/templates/master-list-default.php'; -======= // 2.2.7: added tabbed master schedule template if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { require( RADIO_STATION_DIR . '/templates/master-schedule-list.php' ); @@ -228,7 +195,6 @@ function radio_station_master_schedule( $atts ) { add_action( 'wp_footer', 'radio_station_master_schedule_tabs_js' ); } else { require( RADIO_STATION_DIR . '/templates/master-schedule-default.php' ); ->>>>>>> release/2.2.7 } return $output; @@ -253,12 +219,8 @@ function radio_station_master_fetch_js_filter() { $js .= '' . $tax->name . ''; // 2.2.2: fix to not add pipe suffix for last genre if ( count( $taxes ) - 1 !== $i ) { -<<<<<<< HEAD - $js .= ' | ';} -======= $js .= ' | '; } ->>>>>>> release/2.2.7 } $js .= '
    '; @@ -272,8 +234,6 @@ function radio_station_master_fetch_js_filter() { return $js; } -<<<<<<< HEAD -======= // --- javascript for tab switching --- // 2.2.7: added for tabbed display type @@ -300,4 +260,3 @@ function radio_station_master_schedule_tabs_js() { }); });'; } ->>>>>>> release/2.2.7 diff --git a/includes/post-types.php b/includes/post-types.php index 4d96dcb..573de23 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -1,10 +1,6 @@ >>>>>> release/2.2.7 * Author: Nikki Blight * Since: 2.0.0 */ @@ -14,36 +10,8 @@ // - Register Post Types // - Set CPTs to Classic Editor // - Add Show Thumbnail Support -<<<<<<< HEAD -// - Metaboxes Above Content Area // === Taxonomies === // - Add Genre Taxonomy -// - Shift Genre Metabox -// === Playlists === -// - Add Playlist Data Metabox -// - Playlist Data Metabox -// - Update Playlist Data -// === Shows === -// - Add Related Show Metabox -// - Related Shows Metabox -// - Update Related Show -// - Add Assign Playlist to Show Metabox -// - Assign Playlist to Show Metabox -// - Add Playlist Info Metabox -// - Playlist Info Metabox -// - Add Assign DJs to Show Metabox -// - Assign DJs to Show Metabox -// - Add Show Shifts Metabox -// - Show Shifts Metabox -// - Update Show Metadata -// === Schedule Overrides === -// - Add Schedule Override Metabox -// - Schedule Override Metabox -// - Update Schedule Override -======= -// === Taxonomies === -// - Add Genre Taxonomy ->>>>>>> release/2.2.7 // ------------------ @@ -191,20 +159,6 @@ function radio_station_add_featured_image_support() { } } -<<<<<<< HEAD -// ---------------------------- -// Metaboxes Above Content Area -// ---------------------------- -// --- shows plugin metaboxes above editor box for plugin CPTs --- -add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); -function radio_station_top_meta_boxes() { - global $post; - do_meta_boxes( get_current_screen(), 'top', $post ); -} - -======= ->>>>>>> release/2.2.7 - // ------------------ // === Taxonomies === // ------------------ @@ -256,1247 +210,3 @@ function radio_station_myplaylist_create_show_taxonomy() { } -<<<<<<< HEAD -// ---------------------------- -// Shift Genre Metabox on Shows -// ---------------------------- -// --- moves genre metabox above publish box --- -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); -function radio_station_genre_meta_box_order() { - global $wp_meta_boxes; - $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; - unset( $wp_meta_boxes['show']['side']['core']['genresdiv'] ); - $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; -} - - -// ----------------- -// === Playlists === -// ----------------- - -// ------------------------- -// Add Playlist Data Metabox -// ------------------------- -// --- Add custom repeating meta field for the playlist edit form --- -// (Stores multiple associated values as a serialized string) -// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -function radio_station_myplaylist_add_custom_box() { - // 2.2.2: change context to show at top of edit screen - add_meta_box( - 'dynamic_sectionid', - __( 'Playlist Entries', 'radio-station' ), - 'radio_station_myplaylist_inner_custom_box', - 'playlist', - 'top', // shift to top - 'high' - ); -} - -// --------------------- -// Playlist Data Metabox -// --------------------- -// -- prints the playlist entry box to the main column on the edit screen --- -function radio_station_myplaylist_inner_custom_box() { - - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); - ?> -
    - ID, 'playlist', false ); - $c = 1; - - echo '
    '; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - // echo ""; - // echo ""; - // echo ""; - // echo ""; - echo ''; - - if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { - - foreach ( $entries[0] as $track ) { - if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - - echo ''; - - echo ''; - - echo ''; - - echo ''; - echo ''; - $c++; - } - } - } - echo '
    ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    ' . esc_html( $c ) . '
    ' . esc_html__( 'Comments', 'radio-station' ) . ' '; - echo '' . esc_html__( 'New', 'radio-station' ) . ' '; - $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; - echo ''; - - echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; - echo '' . esc_html__( 'Remove', 'radio-station' ) . '
    '; - - ?> - -
    - -
    - -
    -

    - post_status, array( 'publish', 'future', 'private' ), true ) || 0 === $post->ID ) { - if ( $can_publish ) : - if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : - ?> - - '50', - 'accesskey' => 'o', - ) - ); - ?> - - - '50', - 'accesskey' => 'o', - ) - ); - ?> - - - '50', - 'accesskey' => 'o', - ) - ); - ?> - - - - -
    - - $song ) { - if ( 'queued' === $song['playlist_entry_status'] ) { - $playlist[] = $song; - unset( $playlist[ $i ] ); - } - } - update_post_meta( $post_id, 'playlist', $playlist ); - - // sanitize and save show ID - $show = $_POST['playlist_show_id']; - if ( empty( $show ) ) { - delete_post_meta( $post_id, 'playlist_show_id' ); - } else { - $show = absint( $show ); - if ( $show > 0 ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); - } - } - } - -} - - -// ------------- -// === Shows === -// ------------- - -// ------------------------ -// Add Related Show Metabox -// ------------------------ -// --- Add custom meta box for show assignment on blog posts --- -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -function radio_station_add_showblog_box() { - - // --- make sure a show exists before adding metabox --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - ); - $shows = get_posts( $args ); - - if ( count( $shows ) > 0 ) { - - // ---- add a filter for which post types to show metabox on --- - // TODO: add this filter to plugin documentation - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - - // --- add the metabox to post types --- - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'radio_station_inner_showblog_custom_box', - $post_types, - 'side' - ); - } -} - -// -------------------- -// Related Show Metabox -// -------------------- -// --- Prints the box content for the Show field --- -function radio_station_inner_showblog_custom_box() { - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - ); - $shows = get_posts( $args ); - $current = get_post_meta( $post->ID, 'post_showblog_id', true ); - - ?> -
    - - -
    - 0 ) { - update_post_meta( $post_id, 'post_showblog_id', $show );} - } - } - -} - - -// ----------------------------------- -// Add Assign Playlist to Show Metabox -// ----------------------------------- -// --- Add custom meta box for show assigment --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); -function radio_station_myplaylist_add_show_box() { - // 2.2.2: add high priority to shift above publish box - add_meta_box( - 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'radio_station_myplaylist_inner_show_custom_box', - 'playlist', - 'side', - 'high' - ); -} - -// ------------------------------- -// Assign Playlist to Show Metabox -// ------------------------------- -// --- Prints the box content for the Show field --- -function radio_station_myplaylist_inner_show_custom_box() { - - global $post, $wpdb; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); - - $user = wp_get_current_user(); - - // --- allow administrators to do whatever they want --- - if ( ! in_array( 'administrator', $user->roles, true ) ) { - - // --- get the user lists for all shows --- - $allowed_shows = array(); - - $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); - - // ---- check each list for the current user --- - foreach ( $show_user_lists as $list ) { - - $list->meta_value = maybe_unserialize( $list->meta_value ); - - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( ! is_array( $list->meta_value ) ) { - $list->meta_value = array();} - - // --- only include shows the user is assigned to --- - foreach ( $list->meta_value as $user_id ) { - if ( $user->ID === $user_id ) { - $allowed_shows[] = $list->post_id; - } - } - } - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ), - ); - - $shows = get_posts( $args ); - - } else { - - // --- for if you are an administrator --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - ); - - $shows = get_posts( $args ); - } - - ?> -
    - - -
    - ID, 'show_file', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); - - // added max-width to prevent metabox overflows - ?> -
    - -

    - /> -

    - -


    -

    - -


    -

    - -


    -

    - -
    - roles as $name => $role ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - if ( 'edit_shows' === $capname && $capstatus ) { - $add_roles[] = $name; - } - } - } - $add_roles = array_unique( $add_roles ); - - // ---- create the meta query for get_users() --- - $meta_query = array( 'relation' => 'OR' ); - foreach ( $add_roles as $role ) { - $meta_query[] = array( - 'key' => $wpdb->prefix . 'capabilities', - 'value' => $role, - 'compare' => 'like', - ); - } - - // --- get all eligible users --- - $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //' fields' => array( 'ID, display_name' ), - ); - $users = get_users( $args ); - - // --- get the DJs currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( ! $current ) { - $current = array();} - - // --- move any selected DJs to the top of the list --- - foreach ( $users as $i => $dj ) { - if ( in_array( $dj->ID, $current, true ) ) { - unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $users, $dj ); // prepend the user to the beginning of the array - } - } - - // 2.2.2: add fix to make DJ multi-select input full metabox width - ?> -
    - - -
    - -
    - ID, 'show_sched', false ); - - $c = 0; - if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { - foreach ( $shifts[0] as $track ) { - if ( isset( $track['day'] ) || isset( $track['time'] ) ) { - ?> -
      - -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • - -
    • />
    • - -
    • - -
    - - - - -
    - $dj ) { - if ( ! empty( $dj ) ) { - $userid = get_user_by( 'id', $dj ); - if ( ! $userid ) { - unset( $djs[ $i ] );} - } - } - } - $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; - if ( ! in_array( $active, array( '', 'on' ), true ) ) { - $active = '';} - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - - // --- update the show metadata --- - update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - - // --- update the show shift metadata - $scheds = $_POST['show_sched']; - - // --- sanitize the show shift times --- - $new_scheds = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); - foreach ( $scheds as $i => $sched ) { - foreach ( $sched as $key => $value ) { - - // --- validate according to key --- - $isvalid = false; - if ( 'day' === $key ) { - if ( in_array( $value, $days, true ) ) { - $isvalid = true;} - } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { - $isvalid = true; - } - } elseif ( 'start_min' === $key || 'end_min' === $key ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { - $isvalid = true; - } - } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid, true ) ) { - $isvalid = true; - } - } elseif ( 'encore' === $key ) { - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on' ); - if ( in_array( $value, $valid, true ) ) { - $isvalid = true; - } - } - - // --- if valid add to new schedule --- - if ( $isvalid ) { - $new_scheds[ $i ][ $key ] = $value; - } else { - $new_scheds[ $i ][ $key ] = ''; - } - } - } - - update_post_meta( $post_id, 'show_sched', $new_scheds ); - -} - - -// -------------------------- -// === Schedule Overrides === -// -------------------------- - -// ----------------------------- -// Add Schedule Override Metabox -// ----------------------------- -// --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); -function radio_station_master_override_add_sched_box() { - // 2.2.2: add high priority to show at top of edit screen - add_meta_box( - 'dynamicSchedOver_sectionid', - __( 'Override Schedule', 'radio-station' ), - 'radio_station_master_override_inner_sched_custom_box', - 'override', - 'normal', - 'high' - ); -} - -// ------------------------- -// Schedule Override Metabox -// ------------------------- -function radio_station_master_override_inner_sched_custom_box() { - - global $post; - - // --- add nonce field for update verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); - ?> -
    - - ID, 'show_override_sched', false ); - if ( $override ) { - $override = $override[0];} - ?> - - -
      -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • -
    -
    - '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - ); - } - - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $changed = false; - foreach ( $sched as $key => $value ) { - $isvalid = false; - - // --- validate according to key --- - if ( 'date' === $key ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { - $isvalid = true;} - } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { - $isvalid = true; - } - } elseif ( 'start_min' === $key || 'end_min' === $key ) { - // 2.2.3: fix to validate 00 minute value - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { - $isvalid = true; - } - } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid, true ) ) { - $isvalid = true; - } - } - - // --- if valid add to current schedule setting --- - if ( $isvalid && $value !== $current_sched[ $key ] ) { - $current_sched[ $key ] = $value; - $changed = true; - } - } - - // --- save schedule setting if changed --- - if ( $changed ) { - update_post_meta( $post_id, 'show_override_sched', $current_sched );} -} -======= ->>>>>>> release/2.2.7 diff --git a/includes/shortcodes.php b/includes/shortcodes.php index c432a4d..ec4ed19 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -
    With +
    With - , + ,
    @@ -480,7 +479,7 @@ function radio_station_shortcode_coming_up( $atts ) { ?> - , + ,
    @@ -500,506 +499,3 @@ function radio_station_shortcode_coming_up( $atts ) { } add_shortcode( 'dj-coming-up-widget', 'radio_station_shortcode_coming_up' ); -======= - '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0, - ), - $atts, - 'now-playing' - ); - - $most_recent = radio_station_myplaylist_get_now_playing(); - $output = ''; - - if ( $most_recent ) { - $class = ''; - if ( isset( $most_recent['playlist_entry_new'] ) && 'on' === $most_recent['playlist_entry_new'] ) { - $class = 'new'; - } - - $output .= '
    '; - if ( ! empty( $atts['title'] ) ) { - $output .= '

    ' . $atts['title'] . '

    ';} - - if ( 1 === $atts['song'] ) { - $output .= '' . $most_recent['playlist_entry_song'] . ' '; - } - if ( 1 === $atts['artist'] ) { - $output .= '' . $most_recent['playlist_entry_artist'] . ' '; - } - if ( 1 === $atts['album'] ) { - $output .= '' . $most_recent['playlist_entry_album'] . ' '; - } - if ( 1 === $atts['label'] ) { - $output .= '' . $most_recent['playlist_entry_label'] . ' '; - } - if ( 1 === $atts['comments'] ) { - $output .= '' . $most_recent['playlist_entry_comments'] . ' '; - } - $output .= '' . __( 'View Playlist', 'radio-station' ) . ' '; - $output .= '
    '; - - } else { - echo 'No playlists available.'; - } - - return $output; -} -add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); - - -/* Shortcode to fetch all playlists for a given show id - * Since 2.0.0 - */ -function radio_station_shortcode_get_playlists_for_show( $atts ) { - - $atts = shortcode_atts( - array( - 'show' => '', - 'limit' => -1, - ), - $atts, - 'get-playlists' - ); - - // don't return anything if we do not have a show - if ( empty( $atts['show'] ) ) { - return false; - } - - $args = array( - 'posts_per_page' => $atts['limit'], - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $atts['show'], - ); - - $query = new WP_Query( $args ); - $playlists = $query->posts; - - $output = ''; - - $output .= ''; - - return $output; -} -add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); - -/* Shortcode for displaying a list of all shows - * Since 2.0.0 - */ -function radio_station_shortcode_list_shows( $atts ) { - - $atts = shortcode_atts( - array( - 'genre' => '', - ), - $atts, - 'list-shows' - ); - - // grab the published shows - $args = array( - 'posts_per_page' => 1000, - 'offset' => 0, - 'orderby' => 'title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'meta_query' => array( - array( - 'key' => 'show_active', - 'value' => 'on', - ), - ), - ); - if ( ! empty( $atts['genre'] ) ) { - $args['tax_query'] = array( - array( - 'taxonomy' => 'genres', - 'field' => 'slug', - 'terms' => $atts['genre'], - ), - ); - } - - $query = new WP_Query( $args ); - - // if there are no shows saved, return nothing - if ( ! $query->have_posts() ) { - return false; - } - - $output = ''; - - $output .= '
    '; - $output .= '
      '; - while ( $query->have_posts() ) : - $query->the_post(); - $output .= '
    • '; - $output .= '' . get_the_title() . ''; - $output .= '
    • '; - endwhile; - $output .= '
    '; - $output .= '
    '; - wp_reset_postdata(); - return $output; -} -add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); - -/* Shortcode function for current DJ on-air - * Since 2.0.9 - */ -function radio_station_shortcode_dj_on_air( $atts ) { - $atts = shortcode_atts( - array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'default_name' => '', - 'time' => '12', - 'show_sched' => 1, - 'show_playlist' => 1, - 'show_all_sched' => 0, - 'show_desc' => 0, - ), - $atts, - 'dj-widget' - ); - - // find out which DJ(s) are currently scheduled to be on-air and display them - $djs = radio_station_dj_get_current(); - $playlist = radio_station_myplaylist_get_now_playing(); - - $dj_str = ''; - - $dj_str .= '
    '; - if ( ! empty( $atts['title'] ) ) { - $dj_str .= '

    ' . $atts['title'] . '

    '; - } - $dj_str .= '
      '; - - // echo the show/dj currently on-air - if ( 'override' === $djs['type'] ) { - - $dj_str .= '
    • '; - if ( $atts['show_avatar'] ) { - if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { - $dj_str .= '' . get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ) . ''; - } - } - - $dj_str .= $djs['all'][0]['title']; - - // display the override's schedule if requested - if ( $atts['show_sched'] ) { - - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' ' . $djs['all'][0]['sched']['start_meridian'] . '-' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . ' ' . $djs['all'][0]['sched']['end_meridian'] . '
      '; - } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour( $djs['all'][0]['sched'] ); - $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' -' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . '
      '; - } - - $dj_str .= '
    • '; - } - } else { - - if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { - foreach ( $djs['all'] as $dj ) { - - $dj_str .= '
    • '; - if ( $atts['show_avatar'] ) { - $dj_str .= '' . get_the_post_thumbnail( $dj->ID, 'thumbnail' ) . ''; - } - - $dj_str .= ''; - if ( $atts['show_link'] ) { - $dj_str .= '' . $dj->post_title . ''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $atts['display_djs'] ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $dj_str .= '
      ' . __( 'With', 'radio-station' ) . ' '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - $count_names = count( $names ); - if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { - $dj_str .= ' and '; - } elseif ( $count < $count_names && $count_names > 2 ) { - $dj_str .= ', '; - } - } - $dj_str .= '
      '; - } - } - - if ( $atts['show_desc'] ) { - $desc_string = radio_station_shorten_string( wp_strip_all_tags( $dj->post_content ), 20 ); - $dj_str .= '' . $desc_string . ''; - } - - if ( $atts['show_playlist'] ) { - $dj_str .= '' . __( 'View Playlist', 'radio-station' ) . ''; - } - - $dj_str .= ''; - - if ( $atts['show_sched'] ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - - // if we only want the schedule that's relevant now to display... - if ( ! $atts['show_all_sched'] ) { - - $current_sched = radio_station_current_schedule( $scheds ); - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' ' . $current_sched['start_meridian'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . ' ' . $current_sched['end_meridian'] . '
      '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . '
      '; - } - } - } else { - - foreach ( $scheds as $sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] . '
      '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . '
      '; - } - } - } - } - - $dj_str .= '
    • '; - } - } else { - $dj_str .= '
    • ' . $atts['default_name'] . '
    • '; - } - } - - $dj_str .= '
    '; - $dj_str .= '
    '; - - return $dj_str; - -} -add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air' ); - -/* Shortcode for displaying upcoming DJs/shows - * Since 2.0.9 -*/ -function radio_station_shortcode_coming_up( $atts ) { - - $atts = shortcode_atts( - array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'limit' => 1, - 'time' => '12', - 'show_sched' => 1, - ), - $atts, - 'dj-coming-up-widget' - ); - - // find out which DJ(s) are coming up today - $djs = radio_station_dj_get_next( $atts['limit'] ); - if ( ! isset( $djs['all'] ) || count( $djs['all'] ) <= 0 ) { - $output = '
  • ' . __( 'None Upcoming', 'radio-station' ) . '
  • '; - return $output; - } - - ob_start(); - ?> -
    - -

    - -
      - $dj ) { - - if ( is_array( $dj ) && 'override' === $dj['type'] ) { - ?> -
    • - - - - - -
      - - - -
      - -
    • '; - -
    • - - - ID, 'thumbnail' ); ?> - - - - - - post_title ); ?> - - post_title ); - } - ?> - - ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - ?> -
      With - display_name ); - - $count_names = count( $names ); - if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { - echo ' and '; - } elseif ( $count < $count_names && $count_names > 2 ) { - echo ', '; - } - } - ?> -
      - - - - - - , - - -
      - - - - , - - -
      - -
    • - -
    -
    - >>>>>> release/2.2.7 diff --git a/includes/support-functions.php b/includes/support-functions.php index 7cc12b0..890d7f1 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -45,29 +45,16 @@ function radio_station_current_schedule( $scheds = array() ) { // --- convert shift times to 24-hour and timestamp formats for comparisons --- function radio_station_convert_time( $time = array() ) { -<<<<<<< HEAD - if ( empty( $time ) ) { - return false; - } -======= if ( empty( $time ) ) {return false;} ->>>>>>> release/2.2.7 $now = strtotime( current_time( 'mysql' ) ); $cur_date = date( 'Y-m-d', $now ); $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow -<<<<<<< HEAD - // convert to 24 hour time - $time = radio_station_convert_schedule_to_24hour( $time ); - - // get a timestamp for the schedule start and end -======= // --- convert to 24 hour time --- $time = radio_station_convert_schedule_to_24hour( $time ); // --- get a timestamp for the schedule start and end --- ->>>>>>> release/2.2.7 $time['start_timestamp'] = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] ); if ( 'pm' === $time['start_meridian'] && 'am' === $time['end_meridian'] ) { @@ -88,13 +75,7 @@ function radio_station_convert_time( $time = array() ) { // --- convert a shift to 24 hour time for display --- function radio_station_convert_schedule_to_24hour( $sched = array() ) { -<<<<<<< HEAD - if ( empty( $sched ) ) { - return false; - } -======= if ( empty( $sched ) ) {return false;} ->>>>>>> release/2.2.7 if ( 'pm' === $sched['start_meridian'] && 12 !== (int) $sched['start_hour'] ) { $sched['start_hour'] = $sched['start_hour'] + 12; @@ -122,45 +103,13 @@ function radio_station_convert_schedule_to_24hour( $sched = array() ) { // --- fetch the current DJ(s) on-air -- function radio_station_dj_get_current() { -<<<<<<< HEAD - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides( true ); - -======= // --- first check to see if there are any shift overrides --- $check = radio_station_master_get_overrides( true ); ->>>>>>> release/2.2.7 if ( $check ) { $shows = array( 'all' => $check, 'type' => 'override', ); -<<<<<<< HEAD - - // at this point, we are done. Return the info. - return $shows; - } - - // load the info for the DJ - global $wpdb; - - // get the current time - $now = strtotime( current_time( 'mysql' ) ); - - $cur_day = date( 'l', $now ); - - // we only want active shows - $show_shifts = $wpdb->get_results( - "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta - JOIN {$wpdb->postmeta} AS metab - ON meta.post_id = metab.post_id - JOIN {$wpdb->posts} as posts - ON posts.ID = meta.post_id - WHERE meta.meta_key = 'show_sched' AND - posts.post_status = 'publish' AND - ( - metab.meta_key = 'show_active' AND -======= return $shows; } @@ -181,7 +130,6 @@ function radio_station_dj_get_current() { posts.post_status = 'publish' AND ( metab.meta_key = 'show_active' AND ->>>>>>> release/2.2.7 metab.meta_value = 'on' )" ); @@ -237,10 +185,6 @@ function radio_station_dj_get_current() { // --- get the next DJ or DJs scheduled to be on air based on the current time --- function radio_station_dj_get_next( $limit = 1 ) { -<<<<<<< HEAD - // load the info for the DJ -======= ->>>>>>> release/2.2.7 global $wpdb; // get the various times/dates we need @@ -280,18 +224,6 @@ function radio_station_dj_get_next( $limit = 1 ) { // Fetch all schedules... we only want active shows $show_shifts = $wpdb->get_results( -<<<<<<< HEAD - "SELECT meta.post_id, meta.meta_value - FROM {$wpdb->postmeta} AS meta - JOIN {$wpdb->postmeta} AS metab - ON meta.post_id = metab.post_id - JOIN {$wpdb->posts} as posts - ON posts.ID = meta.post_id - WHERE meta.meta_key = 'show_sched' AND - posts.post_status = 'publish' AND - ( - metab.meta_key = 'show_active' AND -======= "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta JOIN {$wpdb->postmeta} AS metab @@ -302,7 +234,6 @@ function radio_station_dj_get_next( $limit = 1 ) { posts.post_status = 'publish' AND ( metab.meta_key = 'show_active' AND ->>>>>>> release/2.2.7 metab.meta_value = 'on' )" ); @@ -466,15 +397,9 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = $fetch_posts = $wpdb->get_results( $wpdb->prepare( -<<<<<<< HEAD - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = 'post_showblog_id' AND -======= "SELECT meta.post_id FROM {$wpdb->postmeta} AS meta WHERE meta.meta_key = 'post_showblog_id' AND ->>>>>>> release/2.2.7 meta.meta_value = %d", $show_id ) @@ -490,15 +415,9 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = $blogposts = $wpdb->get_results( $wpdb->prepare( -<<<<<<< HEAD - "SELECT posts.ID, posts.post_title - FROM {$wpdb->posts} AS posts - WHERE posts.ID IN(%s) AND -======= "SELECT posts.ID, posts.post_title FROM {$wpdb->posts} AS posts WHERE posts.ID IN(%s) AND ->>>>>>> release/2.2.7 posts.post_status = 'publish' ORDER BY posts.post_date DESC LIMIT %d", @@ -521,15 +440,9 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = // if the blog archive page has been created, add a link to the archive for this show $page = $wpdb->get_results( -<<<<<<< HEAD - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = '_wp_page_template' AND -======= "SELECT meta.post_id FROM {$wpdb->postmeta} AS meta WHERE meta.meta_key = '_wp_page_template' AND ->>>>>>> release/2.2.7 meta.meta_value = 'show-blog-archive-template.php' LIMIT 1" ); @@ -557,15 +470,9 @@ function radio_station_master_get_overrides( $currenthour = false ) { $sql_date = '%' . $sql_date . '%'; $show_shifts = $wpdb->get_results( $wpdb->prepare( -<<<<<<< HEAD - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta_key = 'show_override_sched' AND -======= "SELECT meta.post_id FROM {$wpdb->postmeta} AS meta WHERE meta_key = 'show_override_sched' AND ->>>>>>> release/2.2.7 meta_value LIKE %s", $sql_date ) @@ -629,17 +536,6 @@ function radio_station_shorten_string( $string, $limit ) { } // --- translate weekday --- -<<<<<<< HEAD -// translated individually as cannot translate a variable -function radio_station_translate_weekday( $weekday, $short = false ) { - return __( $weekday, 'radio-station' ); -} - -// --- translate month --- -function radio_station_translate_month( $month, $short = false ) { - return __( $month, 'radio-station' ); -} -======= // important note: translated individually as cannot translate a variable // 2.2.7: use wp locale class to translate weekdays function radio_station_translate_weekday( $weekday, $short = false ) { @@ -708,4 +604,3 @@ function radio_station_translate_meridiem( $meridiem ) { global $wp_locale; return $wp_locale->get_meridiem( $meridiem ); } ->>>>>>> release/2.2.7 diff --git a/radio-station.php b/radio-station.php index b952868..fc55dc0 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD posts} AS posts - WHERE posts.post_type = 'playlist' AND - posts.post_status = 'publish' AND - TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND + WHERE posts.post_type = 'playlist' AND + posts.post_status = 'publish' AND + TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND TO_DAYS(posts.post_date) <= TO_DAYS(%s) ORDER BY posts.post_date ASC"; // prepare query before executing @@ -882,4 +881,3 @@ function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $arg } add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); ->>>>>>> release/2.2.7 diff --git a/templates/archive-playlist.php b/templates/archive-playlist.php index da49c06..8d50001 100644 --- a/templates/archive-playlist.php +++ b/templates/archive-playlist.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - - -
    -
    - - - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'playlist_show_id', - 'value' => $show_id, - ), - ), - 'paged' => $paged, - ); - $loop = new WP_Query( $args ); - // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); - ?> - have_posts() ) : - $loop->the_post(); - ?> - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - - ->>>>>>> release/2.2.7 diff --git a/templates/author.php b/templates/author.php index f7d1e66..07b5e32 100644 --- a/templates/author.php +++ b/templates/author.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - - - - -
    -
    - - roles, true ) ) : ?> - - - - -
    ID, $avatar_size ); ?>
    -
    - - description ); ?> - -
    - URL: user_url ); ?>
    - Email: user_email ); ?>
    - AIM: aim ); ?>
    - Jabber: jabber ); ?>
    - YIM: yim ); ?>
    -
    - -
    - - - - - - - - - - - - -
    -
    - -
    -
    -

    - -
    -
    - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - - -
    -
    - - - ->>>>>>> release/2.2.7 diff --git a/templates/playlist-archive-template.php b/templates/playlist-archive-template.php index 96b1561..3be15d8 100644 --- a/templates/playlist-archive-template.php +++ b/templates/playlist-archive-template.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - - - -
    -
    - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'paged' => $paged, - 'post_status' => 'publish', - ); - $loop = new WP_Query( $args ); - ?> - have_posts() ) : - $loop->the_post(); - ?> - -
    > -
    -

    - -
    - - - - post_date ) ) ); ?> -
    -
    -
    - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - ->>>>>>> release/2.2.7 diff --git a/templates/show-blog-archive-template.php b/templates/show-blog-archive-template.php index 942c652..f9456de 100644 --- a/templates/show-blog-archive-template.php +++ b/templates/show-blog-archive-template.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - - - -
    -
    - - - - - - - $post_types, - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $show_id, - ), - ), - 'paged' => $paged, - ); - $archive_query = new WP_Query( $args ); - while ( $archive_query->have_posts() ) : - $archive_query->the_post(); - ?> - -
    > -
    -

    - -
    - - - -
    -
    - -
    - -
    -
    - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - ->>>>>>> release/2.2.7 diff --git a/templates/single-playlist.php b/templates/single-playlist.php index c11b583..cf42a47 100644 --- a/templates/single-playlist.php +++ b/templates/single-playlist.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - -
    -
    - - - -
    > -
    - ID, 'playlist_show_id', true ); - ?> -

    -

    -
    - -
    - - '', - ) - ); - ?> - - - - - ID, 'playlist', true ); ?> - - -
    - - - - - - - - - - - - > - - - - - - - - -
    -
    - -
    - -
    - - - - - -
    -
    - - - -
    -
    - - ->>>>>>> release/2.2.7 diff --git a/templates/single-show.php b/templates/single-show.php index c52a349..75d1023 100644 --- a/templates/single-show.php +++ b/templates/single-show.php @@ -1,4 +1,3 @@ -<<<<<<< HEAD -======= - - -
    -
    - - -
    > -
    -

    -
    - -
    - - -
    -

    :

    - ' . esc_html( $user_info->display_name ) . ''; - - $dj_count = count( $djs ); - if ( ( 1 === $count && 2 === $dj_count ) || ( $dj_count > 2 && $count === $dj_count - 1 ) ) { - echo ' and '; - } elseif ( ( $count < count( $djs ) ) && ( count( $djs ) > 2 ) ) { - echo ', '; - } - } - } - ?> -
    - -
    -

    :

    - 'genres', 'title_li' => '') ); - ?> -
      - ' . esc_html( $genre->name ) . ''; - } - ?> -
    -
    - -

    - - - - - -
    - - - -
    - -
    -

    -
      - '; - echo esc_html( $weekday . ' - ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] . ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian'] ); - echo ''; - } - } - - // 24-hour time - /* - $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); - if ( $shifts ) { - foreach ( $shifts as $shift ) { - $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); - $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); - - $weekday = radio_station_translate_weekday( $shift['day'] ); - echo '
    • '; - echo $weekday.' - '.$start.' - '.$end; - echo '
    • '; - } - } - */ - ?> -
    -
    - -
    -

    - -
    - - - - - - '', - ) - ); - ?> -
    -
    - - - - - -
    -
    - - ->>>>>>> release/2.2.7 From 607db921974ac06ba3590566521d357bdff3d11f Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 30 Aug 2019 19:30:18 +1000 Subject: [PATCH 051/377] 2.2.7 merge fix 2 --- radio-station.php | 609 ---------------------------------------------- 1 file changed, 609 deletions(-) diff --git a/radio-station.php b/radio-station.php index fc55dc0..049235b 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,613 +1,4 @@ -Version: 2.2.6 -Text Domain: radio-station -Domain Path: /languages -Author URI: https://netmix.com/radio-station -GitHub Plugin URI: netmix/radio-station - -Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License, version 2, as -published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// === Setup === -// - Include Necessary Files -// - Load Text Domain -// - Enqueue Stylesheets -// - Enqueue Admin Scripts -// - Enqueue Admin Styles -// === Template Filters === -// - Single Show/Playlist Template -// - Playlist Archive Template -// === Roles === -// - Add DJ Role and Capabilities -// - maybe Revoke Edit Show Capability -// === Menus === -// - Add Menus -// - maybe Remove Add Show Bar Link -// - Output Help Page -// - Output Export Page - -// ------------- -// === Setup === -// ------------- - -define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); -// --- include necessary files --- -require RADIO_STATION_DIR . '/includes/post-types.php'; -require RADIO_STATION_DIR . '/includes/master-schedule.php'; -require RADIO_STATION_DIR . '/includes/shortcodes.php'; -require RADIO_STATION_DIR . '/includes/class-dj-upcoming-widget.php'; -require RADIO_STATION_DIR . '/includes/class-dj-widget.php'; -require RADIO_STATION_DIR . '/includes/class-playlist-widget.php'; -require RADIO_STATION_DIR . '/includes/support-functions.php'; - -// --- load the text domain --- -function radio_station_init() { - load_plugin_textdomain( 'radio-station', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); -} -add_action( 'plugins_loaded', 'radio_station_init' ); - -// --- flush rewrite rules on activate / deactivation --- -// 2.2.3: added this for custom post types rewrite flushing -register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); -register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); -function radio_station_flush_rewrite_flag() { - add_option( 'radio_station_flush_rewrite_rules', true ); -} - -// --- enqueue necessary stylesheets --- -function radio_station_load_styles() { - - $program_css = get_stylesheet_directory() . '/program-schedule.css'; - if ( file_exists( $program_css ) ) { - $version = filemtime( $program_css ); - $url = get_stylesheet_directory_uri() . '/program-schedule.css'; - } else { - $version = filemtime( RADIO_STATION_DIR . '/css/program-schedule.css' ); - $url = plugins_url( 'css/program-schedule.css', __FILE__ ); - } - wp_enqueue_style( 'program-schedule', $url, array(), $version ); - - // note: djonair.css style enqueueing moved to /includes/widget_djonair.php -} -add_action( 'wp_enqueue_scripts', 'radio_station_load_styles' ); - -// --- enqueue admin scripts --- -// jQuery is needed by the output of this code, so let us make sure we have it available -function radio_station_master_scripts() { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'jquery-ui-datepicker' ); - // $url = plugins_url( 'css/jquery-ui.css', RADIO_STATION_DIR.'/radio-station.php' ); - // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); - if ( is_ssl() ) { - $protocol = 'https'; - } else { - $protocol = 'http';} - $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; - wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.8.2' ); -} -add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); -add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); - -// --- add some style rules to certain parts of the admin area --- -function radio_station_load_admin_styles() { - global $post; - - // hide the first submenu item to prevent duplicate of main menu item - $styles = ' #toplevel_page_radio-station .wp-first-item { display: none; }' . "\n"; - - if ( isset( $post->post_type ) && ( 'playlist' === $post->post_type ) ) { - $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }' . "\n"; - } - - echo ''; - -} -add_action( 'admin_print_styles', 'radio_station_load_admin_styles' ); - - -// ------------------------ -// === Template Filters === -// ------------------------ - -// --- load the theme file for the playlist and show post types --- -function radio_station_load_template( $single_template ) { - global $post; - - if ( 'playlist' === $post->post_type ) { - // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory() . '/single-playlist.php'; - if ( ! file_exists( $user_theme ) ) { - $single_template = RADIO_STATION_DIR . '/templates/single-playlist.php'; - } - } - - if ( 'show' === $post->post_type ) { - // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory() . '/single-show.php'; - if ( ! file_exists( $user_theme ) ) { - $single_template = RADIO_STATION_DIR . '/templates/single-show.php'; - } - } - - return $single_template; -} -add_filter( 'single_template', 'radio_station_load_template' ); - -// --- load the theme file for the playlist archive pages --- -function radio_station_load_custom_post_type_template( $archive_template ) { - global $post; - - if ( is_post_type_archive( 'playlist' ) ) { - $playlist_archive_theme = get_stylesheet_directory() . '/archive-playlist.php'; - if ( ! file_exists( $playlist_archive_theme ) ) { - $archive_template = RADIO_STATION_DIR . '/templates/archive-playlist.php'; - } - } - - return $archive_template; -} -add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ); - - - -// ------------- -// === Roles === -// ------------- - -// --- set up the DJ role and user capabilities --- -function radio_station_set_roles() { - - global $wp_roles; - - // set only the necessary capabilities for DJs - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, - // 'edit_others_playlists' => true, // uncomment to allow DJs to edit all playlists - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true, - ); - // $wp_roles->remove_role('dj'); // we need this here in case we ever update the capabilities list - // TODO: translate role name ? - $wp_roles->add_role( 'dj', 'DJ', $caps ); - - // grant all new capabilities to admin users - $wp_roles->add_cap( 'administrator', 'edit_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_published_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_others_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_private_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_published_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_others_shows', true ); - $wp_roles->add_cap( 'administrator', 'delete_private_shows', true ); - $wp_roles->add_cap( 'administrator', 'read_shows', true ); - $wp_roles->add_cap( 'administrator', 'publish_shows', true ); - $wp_roles->add_cap( 'administrator', 'edit_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_published_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_others_playlists', true ); - $wp_roles->add_cap( 'administrator', 'edit_private_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_published_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_others_playlists', true ); - $wp_roles->add_cap( 'administrator', 'delete_private_playlists', true ); - $wp_roles->add_cap( 'administrator', 'read_playlists', true ); - $wp_roles->add_cap( 'administrator', 'publish_playlists', true ); -} -if ( is_multisite() ) { - add_action( 'init', 'radio_station_set_roles', 10, 0 ); -} else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} - -// --- revoke the ability to edit a show if the user is not listed as a DJ on that show --- -function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $args ) { - - global $post, $wp_roles; - - $user = wp_get_current_user(); - - // determine which roles should have full access aside from administrator - $add_roles = array( 'administrator' ); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach ( $wp_roles->roles as $name => $role ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - if ( 'publish_shows' === $capname && (bool) $capstatus ) { - $add_roles[] = $name; - } - } - } - } - - // exclude administrators and custom roles with appropriate capabilities... - // they should be able to do whatever they want - $found = false; - foreach ( $add_roles as $role ) { - if ( in_array( $role, $user->roles, true ) ) { - $found = true;} - } - - if ( ! $found ) { - - // limit this to published shows - if ( isset( $post->post_type ) ) { - if ( is_admin() && ( 'show' === $post->post_type ) && ( 'publish' === $post->post_status ) ) { - - $djs = get_post_meta( $post->ID, 'show_user_list', true ); - - if ( ! isset( $djs ) || empty( $djs ) ) { - $djs = array();} - - // if they are not listed, temporarily revoke editing ability for this post - if ( ! in_array( $user->ID, $djs, true ) ) { - $allcaps['edit_shows'] = false; - $allcaps['edit_published_shows'] = false; - } - } - } - } - return $allcaps; -} -add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); - - -// ------------- -// === Menus === -// ------------- - -function radio_station_add_admin_menus() { - - $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); - $position = apply_filters( 'radio_station_menu_position', 5 ); - $capability = 'publish_playlists'; - add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); - - add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); - add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); - add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); - add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); - add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); - add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); - add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); - add_submenu_page( 'radio-station', __( 'Help', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); - - // hack the submenu global to post type add/edit URLs - global $submenu; - foreach ( $submenu as $i => $menu ) { - if ( 'radio-station' === $i ) { - foreach ( $menu as $j => $item ) { - switch ( $item[2] ) { - case 'add-show': - // maybe remove the Add Show link for DJs - // $user = wp_get_current_user(); - // if ( in_array( 'dj', $user->roles ) ) { - if ( ! current_user_can( 'publish_shows' ) ) { - unset( $submenu[ $i ][ $j ] ); - } else { - $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=show'; - } - break; - case 'shows': - $submenu[ $i ][ $j ][2] = 'edit.php?post_type=show'; - break; - case 'playlists': - $submenu[ $i ][ $j ][2] = 'edit.php?post_type=playlist'; - break; - case 'add-playlist': - $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=playlist'; - break; - case 'genres': - $submenu[ $i ][ $j ][2] = 'edit-tags.php?taxonomy=genres'; - break; - case 'schedule-overrides': - $submenu[ $i ][ $j ][2] = 'edit.php?post_type=override'; - break; - case 'add-override': - $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=override'; - break; - } - } - } - } -} -add_action( 'admin_menu', 'radio_station_add_admin_menus' ); - -// --- expand main menu fix for plugin submenu items --- -// 2.2.2: added fix for genre taxonomy page and post type editing -function radio_station_fix_genre_parent( $parent_file = '' ) { - global $pagenow, $post; - $post_types = array( 'show', 'playlist', 'override' ); - if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { - $parent_file = 'radio-station'; - } elseif ( 'post.php' === $pagenow && in_array( $post->post_type, $post_types, true ) ) { - $parent_file = 'radio-station'; - } - return $parent_file; -} -add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); - -// --- genre taxonomy submenu item fix --- -// 2.2.2: so genre submenu item link is set to current (bold) -function radio_station_genre_submenu_fix() { - global $pagenow; - if ( 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { - echo ""; - } -} -add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); - -// --- remove the Add Show link for DJs from the wp admin bar --- -// 2.2.2: re-add new post type items to admin bar -// (as no longer automatically added by register_post_type) -function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { - - // --- new show --- - if ( current_user_can( 'publish_shows' ) ) { - - $args = array( - 'id' => 'new-show', - 'title' => __( 'Show', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url( 'post-new.php?post_type=show' ), - ); - $wp_admin_bar->add_node( $args ); - } - - // --- new playlist --- - if ( current_user_can( 'publish_playlists' ) ) { - $args = array( - 'id' => 'new-playlist', - 'title' => __( 'Playlist', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url( 'post-new.php?post_type=playlist' ), - ); - $wp_admin_bar->add_node( $args ); - } - - // --- new schedule override --- - if ( current_user_can( 'publish_shows' ) ) { - $args = array( - 'id' => 'new-override', - 'title' => __( 'Override', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url( 'post-new.php?post_type=override' ), - ); - $wp_admin_bar->add_node( $args ); - } - -} -add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); - -// --- output help page --- -function radio_station_plugin_help() { - - // 2.2.2: include patreon button link - echo radio_station_patreon_blurb( false ); - - // include help template - include RADIO_STATION_DIR . '/templates/help.php'; -} - -// --- output playlist export page --- -function radio_station_admin_export() { - global $wpdb; - - // first, delete any old exports from the export directory - $dir = RADIO_STATION_DIR . '/export/'; - if ( is_dir( $dir ) ) { - $get_contents = opendir( $dir ); - while ( $file = readdir( $get_contents ) ) { - if ( '.' !== $file && '..' !== $file ) { - unlink( $dir . $file ); - } - } - closedir( $get_contents ); - } - - // watch for form submission - if ( isset( $_POST['export_action'] ) && ( 'station_playlist_export' === $_POST['export_action'] ) ) { - - // validate referrer and nonce field - check_admin_referer( 'station_export_valid' ); - - $start = $_POST['station_export_start_year'] . '-' . $_POST['station_export_start_month'] . '-' . $_POST['station_export_start_day']; - $start .= ' 00:00:00'; - $end = $_POST['station_export_end_year'] . '-' . $_POST['station_export_end_month'] . '-' . $_POST['station_export_end_day']; - $end .= ' 23:59:59'; - - // fetch all records that were created between the start and end dates - $sql = - "SELECT posts.ID, posts.post_date - FROM {$wpdb->posts} AS posts - WHERE posts.post_type = 'playlist' AND - posts.post_status = 'publish' AND - TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND - TO_DAYS(posts.post_date) <= TO_DAYS(%s) - ORDER BY posts.post_date ASC"; - // prepare query before executing - $query = $wpdb->prepare( $sql, array( $start, $end ) ); - $playlists = $wpdb->get_results( $query ); - - if ( ! $playlists ) { - $list = 'No playlists found for this period.';} - - // fetch the tracks for each playlist from the wp_postmeta table - foreach ( $playlists as $i => $playlist ) { - - $songs = get_post_meta( $playlist->ID, 'playlist', true ); - - // remove any entries that are marked as 'queued' - foreach ( $songs as $j => $entry ) { - if ( 'queued' === $entry['playlist_entry_status'] ) { - unset( $songs[ $j ] );} - } - - $playlists[ $i ]->songs = $songs; - } - - $output = ''; - - $date = ''; - foreach ( $playlists as $playlist ) { - if ( ! isset( $playlist->post_date ) ) { - continue; - } - $playlist_datetime = explode( ' ', $playlist->post_date ); - $playlist_post_date = array_shift( $playlist_datetime ); - if ( empty( $date ) || $date !== $playlist_post_date ) { - $date = $playlist_post_date; - $output .= $date . "\n\n"; - } - - foreach ( $playlist->songs as $song ) { - $output .= $song['playlist_entry_artist'] . ' || ' . $song['playlist_entry_song'] . ' || ' . $song['playlist_entry_album'] . ' || ' . $song['playlist_entry_label'] . "\n"; - } - } - - // save as file - $dir = RADIO_STATION_DIR . '/export/'; - $file = $date . '-export.txt'; - if ( ! file_exists( $dir ) ) { - wp_mkdir_p( $dir );} - - $f = fopen( $dir . $file, 'w' ); - fwrite( $f, $output ); - fclose( $f ); - - // display link to file - $url = get_bloginfo( 'url' ) . '/wp-content/plugins/radio-station/tmp/' . $file; - echo wp_kses_post( '' ); - } - - // display the export page - include RADIO_STATION_DIR . '/templates/admin-export.php'; - -} - - -// -------------------- -// === Admin Notice === -// -------------------- - -// --- plugin announcement notice --- -// 2.2.2: added plugin announcement notice -function radio_station_announcement_notice() { - - // --- bug out if already dismissed --- - if ( get_option( 'radio_station_announcement_dismissed' ) ) { - return; - } - - // --- bug out on certain plugin pages --- - $pages = array( 'radio-station', 'radio-station-help' ); - if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages, true ) ) { - return; - } - - // --- display plugin announcement --- - echo '
    '; - echo radio_station_patreon_blurb(); - echo ''; - echo '
    '; -} -add_action( 'admin_notices', 'radio_station_announcement_notice' ); - -// --- dismiss plugin announcement notice --- -// 2.2.2: AJAX for announcement notice dismissal -function radio_station_announcement_dismiss() { - if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { - update_option( 'radio_station_announcement_dismissed', true ); - echo ""; - exit; - } -} -add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); - -// --- Patreon supporter blurb --- -// 2.2.2: added simple patreon supporter blurb -function radio_station_patreon_blurb( $dismissable = true ) { - - $blurb = '
      '; - $blurb .= '
    • '; - $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); - $blurb .= ''; - $blurb .= '
    • '; - $blurb .= '
    • '; - $blurb .= '' . __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) . '
      '; - $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) . ',
      '; - $blurb .= __( 'since June 2019', 'radio-station' ) . ', '; - $blurb .= '' . __( 'Radio Station', 'radio-station' ) . ' '; - $blurb .= __( ' plugin development has been actively taken over by', 'radio-station' ); - $blurb .= ' Netmix.
      '; - $blurb .= __( 'We invite you to', 'radio-station' ); - $blurb .= ' '; - $blurb .= __( 'Become a Radio Station Patreon Supporter', 'radio-station' ); - $blurb .= ' ' . __( 'to make it better for everyone', 'radio-station' ) . '!'; - $blurb .= '
    • '; - $blurb .= '
    • '; - $blurb .= radio_station_patreon_button(); - $blurb .= '
    • '; - $blurb .= '
    '; - if ( $dismissable ) { - ?> - '; - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - $blurb .= ''; - $blurb .= ''; - $blurb .= '
    '; - } - return $blurb; -} - -// --- Patreon supporter button --- -// 2.2.2: added simple patreon supporter image button -function radio_station_patreon_button() { - $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); - $button = ''; - $button .= ''; - $button .= ''; - return $button; -} -======= - Date: Sat, 31 Aug 2019 01:26:12 -0400 Subject: [PATCH 052/377] Change yplaylist-artist from 'song' to 'artist' Just a minor change - where yplaylist-artist had 'Song', updated to 'Artist' --- includes/class-playlist-widget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-playlist-widget.php b/includes/class-playlist-widget.php index ca2f969..ce202dd 100644 --- a/includes/class-playlist-widget.php +++ b/includes/class-playlist-widget.php @@ -132,7 +132,7 @@ public function widget( $args, $instance ) { if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { ?>
    - +
    Date: Tue, 17 Sep 2019 16:12:48 -0400 Subject: [PATCH 053/377] Update readme.txt Add new URL for demo site --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 6a9d1a3..c0a28a5 100644 --- a/readme.txt +++ b/readme.txt @@ -28,7 +28,7 @@ We are actively seeking radio station partners and donations to fund further dev For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ -You can find a demo version of the plugin on our demo site here: https://netmix.co/radio-station-demo +You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com == Installation == From 54072757ae3b613930bf8e4d5313a883b3025ddc Mon Sep 17 00:00:00 2001 From: Mike Garrett Date: Mon, 30 Sep 2019 15:03:41 -0400 Subject: [PATCH 054/377] Solves #63: Translations of months/weekdays do not display --- includes/support-functions.php | 128 ++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 41 deletions(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index 890d7f1..c080263 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -45,7 +45,9 @@ function radio_station_current_schedule( $scheds = array() ) { // --- convert shift times to 24-hour and timestamp formats for comparisons --- function radio_station_convert_time( $time = array() ) { - if ( empty( $time ) ) {return false;} + if ( empty( $time ) ) { + return false; + } $now = strtotime( current_time( 'mysql' ) ); $cur_date = date( 'Y-m-d', $now ); @@ -75,7 +77,9 @@ function radio_station_convert_time( $time = array() ) { // --- convert a shift to 24 hour time for display --- function radio_station_convert_schedule_to_24hour( $sched = array() ) { - if ( empty( $sched ) ) {return false;} + if ( empty( $sched ) ) { + return false; + } if ( 'pm' === $sched['start_meridian'] && 12 !== (int) $sched['start_hour'] ) { $sched['start_hour'] = $sched['start_hour'] + 12; @@ -116,7 +120,7 @@ function radio_station_dj_get_current() { global $wpdb; // --- get the current time and day --- - $now = strtotime( current_time( 'mysql' ) ); + $now = strtotime( current_time( 'mysql' ) ); $cur_day = date( 'l', $now ); // --- query for active shows only --- @@ -541,22 +545,38 @@ function radio_station_shorten_string( $string, $limit ) { function radio_station_translate_weekday( $weekday, $short = false ) { global $wp_locale; if ( $short ) { - if ( $weekday == 'Sun' ) {$weekday = $wp_locale->get_weekday_abbrev( 0 );} - elseif ( $weekday == 'Mon' ) {$weekday = $wp_locale->get_weekday_abbrev( 1 );} - elseif ( $weekday == 'Tue' ) {$weekday = $wp_locale->get_weekday_abbrev( 2 );} - elseif ( $weekday == 'Wed' ) {$weekday = $wp_locale->get_weekday_abbrev( 3 );} - elseif ( $weekday == 'Thu' ) {$weekday = $wp_locale->get_weekday_abbrev( 4 );} - elseif ( $weekday == 'Fri' ) {$weekday = $wp_locale->get_weekday_abbrev( 5 );} - elseif ( $weekday == 'Sat' ) {$weekday = $wp_locale->get_weekday_abbrev( 6 );} + if ( 'Sun' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); + } elseif ( 'Mon' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); + } elseif ( 'Tue' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); + } elseif ( 'Wed' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); + } elseif ( 'Thu' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); + } elseif ( 'Fri' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); + } elseif ( 'Sat' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); + } } else { // 2.2.7: fix to typo for Tuesday - if ( $weekday == 'Sunday' ) {$weekday = $wp_locale->get_weekday( 0 );} - elseif ( $weekday == 'Monday' ) {$weekday = $wp_locale->get_weekday( 1 );} - elseif ( $weekday == 'Tuesday' ) {$weekday = $wp_locale->get_weekday( 2 );} - elseif ( $weekday == 'Wednesday' ) {$weekday = $wp_locale->get_weekday( 3 );} - elseif ( $weekday == 'Thurday' ) {$weekday = $wp_locale->get_weekday( 4 );} - elseif ( $weekday == 'Friday' ) {$weekday = $wp_locale->get_weekday( 5 );} - elseif ( $weekday == 'Saturday' ) {$weekday = $wp_locale->get_weekday( 6 );} + if ( 'Sunday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 0 ); + } elseif ( 'Monday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 1 ); + } elseif ( 'Tuesday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 2 ); + } elseif ( 'Wednesday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 3 ); + } elseif ( 'Thurday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 4 ); + } elseif ( 'Friday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 5 ); + } elseif ( 'Saturday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 6 ); + } } return $weekday; } @@ -567,31 +587,57 @@ function radio_station_translate_weekday( $weekday, $short = false ) { function radio_station_translate_month( $month, $short = false ) { global $wp_locale; if ( $short ) { - if ( $month == 'Jan' ) {$month = $wp_locale->get_month_abbrev( 1 );} - elseif ( $month == 'Feb' ) {$month = $wp_locale->get_month_abbrev( 2 );} - elseif ( $month == 'Mar' ) {$month = $wp_locale->get_month_abbrev( 3 );} - elseif ( $month == 'Apr' ) {$month = $wp_locale->get_month_abbrev( 4 );} - elseif ( $month == 'May' ) {$month = $wp_locale->get_month_abbrev( 5 );} - elseif ( $month == 'Jun' ) {$month = $wp_locale->get_month_abbrev( 6 );} - elseif ( $month == 'Jul' ) {$month = $wp_locale->get_month_abbrev( 7 );} - elseif ( $month == 'Aug' ) {$month = $wp_locale->get_month_abbrev( 8 );} - elseif ( $month == 'Sep' ) {$month = $wp_locale->get_month_abbrev( 9 );} - elseif ( $month == 'Oct' ) {$month = $wp_locale->get_month_abbrev( 10 );} - elseif ( $month == 'Nov' ) {$month = $wp_locale->get_month_abbrev( 11 );} - elseif ( $month == 'Dec' ) {$month = $wp_locale->get_month_abbrev( 12 );} + if ( 'Jan' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); + } elseif ( 'Feb' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); + } elseif ( 'Mar' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); + } elseif ( 'Apr' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); + } elseif ( 'Jun' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); + } elseif ( 'Jul' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); + } elseif ( 'Aug' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); + } elseif ( 'Sep' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); + } elseif ( 'Oct' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); + } elseif ( 'Nov' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); + } elseif ( 'Dec' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); + } } else { - if ( $month == 'January' ) {$month = $wp_locale->get_month( 1 );} - elseif ( $month == 'February' ) {$month = $wp_locale->get_month( 2 );} - elseif ( $month == 'March' ) {$month = $wp_locale->get_month( 3 );} - elseif ( $month == 'April' ) {$month = $wp_locale->get_month( 4 );} - elseif ( $month == 'May' ) {$month = $wp_locale->get_month( 5 );} - elseif ( $month == 'June' ) {$month = $wp_locale->get_month( 6 );} - elseif ( $month == 'July' ) {$month = $wp_locale->get_month( 7 );} - elseif ( $month == 'August' ) {$month = $wp_locale->get_month( 8 );} - elseif ( $month == 'September' ) {$month = $wp_locale->get_month( 9 );} - elseif ( $month == 'October' ) {$month = $wp_locale->get_month( 10 );} - elseif ( $month == 'November' ) {$month = $wp_locale->get_month( 11 );} - elseif ( $month == 'December' ) {$month = $wp_locale->get_month( 12 );} + if ( 'January' === $month ) { + $month = $wp_locale->get_month( 1 ); + } elseif ( 'February' === $month ) { + $month = $wp_locale->get_month( 2 ); + } elseif ( 'March' === $month ) { + $month = $wp_locale->get_month( 3 ); + } elseif ( 'April' === $month ) { + $month = $wp_locale->get_month( 4 ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month( 5 ); + } elseif ( 'June' === $month ) { + $month = $wp_locale->get_month( 6 ); + } elseif ( 'July' === $month ) { + $month = $wp_locale->get_month( 7 ); + } elseif ( 'August' === $month ) { + $month = $wp_locale->get_month( 8 ); + } elseif ( 'September' === $month ) { + $month = $wp_locale->get_month( 9 ); + } elseif ( 'October' === $month ) { + $month = $wp_locale->get_month( 10 ); + } elseif ( 'November' === $month ) { + $month = $wp_locale->get_month( 11 ); + } elseif ( 'December' === $month ) { + $month = $wp_locale->get_month( 12 ); + } } return $month; } From 5eea6652e052a2d45ed9c45af346b272dd879ca2 Mon Sep 17 00:00:00 2001 From: Mike Garrett Date: Thu, 3 Oct 2019 10:50:40 -0400 Subject: [PATCH 055/377] Hotfix: string to integer comparison for in_array. --- includes/post-types-admin.php | 464 +++++++++++++++++++++------------- radio-station.php | 3 +- 2 files changed, 288 insertions(+), 179 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index b561496..4284826 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -277,17 +277,22 @@ function radio_station_myplaylist_save_postdata( $post_id ) { // --- verify if this is an auto save routine --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return;} + return; + } if ( isset( $_POST['playlist'] ) || isset( $_POST['playlist_show_id'] ) ) { // --- verify this came from the our screen and with proper authorization --- - if ( ! isset( $_POST['dynamicMeta_noncename'] ) ) {return;} + if ( ! isset( $_POST['dynamicMeta_noncename'] ) ) { + return; + } if ( ! wp_verify_nonce( $_POST['dynamicMeta_noncename'], plugin_basename( __FILE__ ) ) ) { return; } - if ( ! isset( $_POST['dynamicMetaShow_noncename'] ) ) {return;} + if ( ! isset( $_POST['dynamicMetaShow_noncename'] ) ) { + return; + } if ( ! wp_verify_nonce( $_POST['dynamicMetaShow_noncename'], plugin_basename( __FILE__ ) ) ) { return; } @@ -324,15 +329,21 @@ function radio_station_myplaylist_save_postdata( $post_id ) { // 2.2.7: added data columns to playlist list display add_filter( 'manage_edit-playlist_columns', 'radio_station_playlist_columns', 6 ); function radio_station_playlist_columns( $columns ) { - if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} - if ( isset( $columns['post_thumb'] ) ) {unset( $columns['post_thumb'] );} - $date = $columns['date']; unset( $columns['date'] ); - $comments = $columns['comments']; unset( $columns['comments'] ); - $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); + } + $date = $columns['date']; + unset( $columns['date'] ); + $comments = $columns['comments']; + unset( $columns['comments'] ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); - $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); - $columns['comments'] = $comments; - $columns['date'] = $date; + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; return $columns; } @@ -344,27 +355,27 @@ function radio_station_playlist_columns( $columns ) { function radio_station_playlist_column_data( $column, $post_id ) { if ( $column == 'show' ) { $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); - $post = get_post( $show_id ); - echo "" . $post->post_title . ""; + $post = get_post( $show_id ); + echo "" . $post->post_title . ''; } elseif ( $column == 'trackcount' ) { $tracks = get_post_meta( $post_id, 'playlist', true ); echo count( $tracks ); } elseif ( $column == 'tracklist' ) { - $tracks = get_post_meta( $post_id, 'playlist', true ); - $tracklist = ''; - $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . "
    "; + $tracks = get_post_meta( $post_id, 'playlist', true ); + $tracklist = ''; + $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . '
    '; $tracklist .= ''; @@ -378,9 +389,11 @@ function radio_station_playlist_column_data( $column, $post_id ) { add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); function radio_station_playlist_column_styles() { $currentscreen = get_current_screen(); - if ( $currentscreen->id !== 'edit-playlist' ) {return;} - echo ""; + if ( $currentscreen->id !== 'edit-playlist' ) { + return; + } + echo ''; // --- expand/collapse tracklist data --- echo "'; + + return $js; +} + +// --- javascript for tab switching --- +// 2.2.7: added for tabbed display type +function radio_station_master_schedule_tabs_js() { + echo ''; +} diff --git a/includes/master_schedule.php b/includes/master_schedule.php deleted file mode 100644 index a103a2b..0000000 --- a/includes/master_schedule.php +++ /dev/null @@ -1,624 +0,0 @@ - '12', - 'show_link' => 1, - 'display_show_time' => 1, - 'list' => 'table', - 'show_image' => 0, - 'show_djs' => 0, - 'divheight' => 45 - ), $atts ) ); - - $timeformat = $time; - - // $overrides = radio_station_master_get_overrides(true); - - // set up the structure of the master schedule - $default_dj = get_option( 'dj_default_name' ); - - // check to see what day of the week we need to start on - $start_of_week = get_option( 'start_of_week' ); - $days_of_the_week = array( 'Sunday' => array(), 'Monday' => array(), 'Tuesday' => array(), 'Wednesday' => array(), 'Thursday' => array(), 'Friday' => array(), 'Saturday' => array() ); - $week_start = array_slice( $days_of_the_week, $start_of_week ); - - foreach ( $days_of_the_week as $i => $weekday ) { - if ( $start_of_week > 0 ) { - $add = $days_of_the_week[$i]; - unset( $days_of_the_week[$i] ); - $days_of_the_week[$i] = $add; - } - $start_of_week--; - } - - // create the master_list array based on the start of the week - $master_list = array(); - for ( $i=0; $i<24; $i++ ) {$master_list[$i] = $days_of_the_week;} - - - // get the show schedules, excluding shows marked as inactive - $show_shifts = $wpdb->get_results( "SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `active` ON `meta`.`post_id` = `active`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `active`.`meta_key` = 'show_active' - AND `active`.`meta_value` = 'on');" ); - - // insert schedules into the master list - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show is not scheduled yet, unserialize will return false... fix that. - if ( !is_array($shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // switch to 24-hour time - if($time['start_meridian'] == 'pm' && $time['start_hour'] != 12) { - $time['start_hour'] += 12; - } - if($time['start_meridian'] == 'am' && $time['start_hour'] == 12) { - $time['start_hour'] = 0; - } - - if($time['end_meridian'] == 'pm' && $time['end_hour'] != 12) { - $time['end_hour'] += 12; - } - if($time['end_meridian'] == 'am' && $time['end_hour'] == 12) { - $time['end_hour'] = 0; - } - - // check if we're spanning multiple days - $time['multi-day'] = 0; - if( ($time['start_hour'] > $time['end_hour']) || ($time['start_hour'] == $time['end_hour']) ) { - $time['multi-day'] = 1; - } - - $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array( 'id'=> $shift->post_id, 'time' => $time ); - } - } - - // sort the array by time - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $min ) { - ksort($min); - $master_list[$hour][$day] = $min; - - // we need to take into account shows that start late at night and end the following day - foreach($min as $i => $time) { - - // if it ends at midnight, we don't need to worry about carry-over - if($time['time']['end_hour'] == "0" && $time['time']['end_min'] == "00") { - continue; - } - - // if it ends after midnight, fix it - if( ($time['time']['start_meridian'] == 'pm' && $time['time']['end_meridian'] == 'am') || //if it starts at night and ends in the morning, end hour is on the following day - ($time['time']['start_hour'].$time['time']['start_min'].$time['time']['start_meridian'] == $time['time']['end_hour'].$time['time']['end_min'].$time['time']['end_meridian']) || //if the start and end times are identical, assume the end time is the following day - ($time['time']['start_meridian'] == 'am' && $time['time']['start_hour'] > $time['time']['end_hour']) //if the start hour is in the morning, and greater than the end hour, assume end hour is the following day - ) { - - if($timeformat == 12) { - $time['time']['real_start'] = ($time['time']['start_hour']-12).':'.$time['time']['start_min']; - } else { - $pad_hour = ""; - if ( $time['time']['start_hour'] < 10) { - $pad_hour = "0"; - } - $time['time']['real_start'] = $pad_hour.$time['time']['start_hour'].':'.$time['time']['start_min']; - } - // $time['time']['start_hour'] = "0"; - // $time['time']['start_min'] = "00"; - // $time['time']['start_meridian'] = "am"; - $time['time']['rollover'] = 1; - - if ( $day == 'Sunday' ) {$nextday = 'Monday';} - if ( $day == 'Monday' ) {$nextday = 'Tuesday';} - if ( $day == 'Tuesday' ) {$nextday = 'Wednesday';} - if ( $day == 'Wednesday' ) {$nextday = 'Thursday';} - if ( $day == 'Thursday' ) {$nextday = 'Friday';} - if ( $day == 'Friday' ) {$nextday = 'Saturday';} - if ( $day == 'Saturday' ) {$nextday = 'Sunday';} - - $master_list[0][$nextday]['00'] = $time; - - } - } - } - } - - $output = ''; - - if ( ($list == 1) || ($list == 'list') ) { - - // output as a list - $flip = $days_of_the_week; - foreach ( $master_list as $hour => $days ) { - foreach ( $days as $day => $mins ) { - foreach($mins as $fmin => $fshow) { - $flip[$day][$hour][$fmin] = $fshow; - } - } - } - - $output .= '
      '; - - foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
    • '; - $output .= ''.$display_day.''; - $output .= '
        '; - foreach ($hours as $hour => $mins) { - - foreach ($mins as $min => $show ) { - $output .= '
      • '; - - if($show_image) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail($show['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($show['id']).''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $show_djs ) { - $output .= ''; - - $names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $output .= ' with '; - foreach ( $names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - - //$output .= $weekday.' '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('g:i a', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } else { - - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['start_hour'].':'.$show['time']['start_min'].':00 ')); - $output .= ' - '; - $output .= date('H:i', strtotime('1981-04-28 '.$show['time']['end_hour'].':'.$show['time']['end_min'].':00 ')); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && ( $show['time']['encore'] == 'on' ) ) { - $output .= ' '.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta( $show['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ' '.__('Audio File', 'radio-station').''; - } - - $output .= '
      • '; - } - } - $output .= '
      '; - $output .= '
    • '; - } - - $output .= '
    '; - - } elseif ( $list == 'divs' ) { - - // output some dynamic styles - $output .= ''; - - // output the schedule - $output .= radio_station_master_fetch_js_filter(); - $output .= '
    '; - $weekdays = array_keys($days_of_the_week); - - $output .= '
    '; - $output .= '
     
    '; - foreach( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
    '.$translated.'
    '; - } - $output .= '
    '; - - foreach ($master_list as $hour => $days) { - - $output .= '
    '; - - // output the hour labels - $output .= '
    '; - if ( $timeformat == 12 ) { - $output .= date('ga', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 12-hour format - } else { - $output .= date('H:i', strtotime('1981-04-28 '.$hour.':00:00')); //random date needed to convert time to 24-hour format - } - $output .= '
    '; - - foreach ( $weekdays as $weekday ) { - $output .= '
    '; - if ( isset( $days[$weekday] ) ) { - foreach ( $days[$weekday] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-'.$showdata['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($showdata['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes($term->name).' '; - } - - $output .= '
    '; - - // featured image - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($showdata['id']).''; - } else { - $output .= get_the_title($showdata['id']); - } - $output .= ''; - - // list of DJs - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' with '; - - foreach ( $names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - if ( ( ($count == 1) && ( count($names) == 2) ) - || ( (count($names) > 2 ) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['start_hour'].':'.$showdata['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$showdata['time']['end_hour'].':'.$showdata['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - // designate as encore - if ( isset($showdata['time']['encore']) && ( $showdata['time']['encore'] == 'on' ) ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - // link to media file - $link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
    '; - - if ( $showdata['time']['end_min'] != '00' ) { - $output .= '
    '; - } - } - - $output .= '
    '; // end master-show-entry - } - } - $output .= '
    '; // end master-schedule-weekday - } - $output .= '
    '; // end master-schedule-hour - } - $output .= '
    '; // end master-schedule-divs - - } else { - - // create the output in a table - $output .= radio_station_master_fetch_js_filter(); - - $output .= ''; - - // output the headings in the correct order - $output .= ''; - foreach($days_of_the_week as $weekday => $info) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= ''; - } - $output .= ''; - // $output .= ''; - - if ( !isset( $nextskip ) ) {$nextskip = array();} - - foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= ''; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] == $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span']-1; - $curskip[$x]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( $shift['time']['start_hour'] == 0 && $shift['time']['end_hour'] == 0 ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] == $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( $shift['time']['end_hour'] == 0 && $shift['time']['start_hour'] != 0 ) { //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + (24 - $shift['time']['start_hour']); - } elseif($shift['time']['start_hour'] > $shift['time']['end_hour'] ) { // show runs from before midnight night until the next morning - // print_r($shift);die('moo'); - if ( isset($shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ($shift['time']['end_hour'] - $shift['time']['start_hour']); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="'.$rowspan.'"'; - // add to both arrays - $curskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - $nextskip[] = array( 'day' => $day, 'span' => $rowspan, 'show' => get_the_title( $shift['id'] ) ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ($continue) {continue;} - - $output .= ''; - - foreach ( $min as $shift ) { - - //print_r($shift); - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-'.$shift['id'].' '.sanitize_title_with_dashes(str_replace("_", "-", get_the_title($shift['id']))).' '; - foreach ( $terms as $term ) { - $classes .= sanitize_title_with_dashes( $term->name ).' '; - } - - $output .= '
    '; - - if ( $show_image ) { - $output .= ''; - if ( has_post_thumbnail($shift['id'] ) ) { - $output .= get_the_post_thumbnail($shift['id'], 'thumbnail'); - } - $output .= ''; - } - - $output .= ''; - if ( $show_link ) { - $output .= ''.get_the_title($shift['id']).''; - } else { - $output .= get_the_title($shift['id']); - } - $output .= ''; - - if ( $show_djs ) { - - $output .= ''; - - $names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $output .= ' '.__( 'with','radio-station' ).' '; - foreach( $names as $name ) { - $count ++; - $user_info = get_userdata($name); - - $output .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2) && ( $count == ( count($names) -1 ) ) ) ) { - $output .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $display_show_time ) { - - $output .= ''; - - if ( $timeformat == 12 ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].' ') ); - } else { - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['start_hour'].':'.$shift['time']['start_min'].':00 ') ); - $output .= ' - '; - $output .= date( 'H:i', strtotime('1981-04-28 '.$shift['time']['end_hour'].':'.$shift['time']['end_min'].':00 ') ); - } - $output .= ''; - } - - if ( isset($shift['time']['encore']) && $shift['time']['encore'] == 'on' ) { - $output .= ''.__('encore airing', 'radio-station').''; - } - - $link = get_post_meta($shift['id'], 'show_file', true); - if ( $link && ( $link != '' ) ) { - $output .= ''.__('Audio File', 'radio-station').''; - } - - $output .= '
    '; - } - $output .= ''; - } - - $output .= '
    '; - } - $output .= '
    '.$heading.'
    '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').'
    '; - - if ( $timeformat == 12 ) { - if ( $hour == 0 ) {$output .= '12am';} - elseif ( $hour < 12 ) {$output .= $hour.'am';} - elseif ( $hour == 12 ) {$output .= '12pm';} - else {$output .= ($hour-12).'pm';} - } else { - if ( $hour < 10 ) {$output .= "0";} - $output .= $hour.":00"; - } - - $output .= '
    '; - } - - return $output; - -} -add_shortcode( 'master-schedule', 'radio_station_master_schedule'); - -// --- add javascript for highlighting shows based on genre --- -function radio_station_master_fetch_js_filter(){ - - $js = '
    '.__('Genres', 'radio-station').': '; - - $taxes = get_terms( 'genres', array('hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC') ); - foreach ( $taxes as $i => $tax ) { - $js .= ''.$tax->name.''; - // 2.2.2: fix to not add pipe suffix for last genre - if ( $i != ( count($taxes) - 1 ) ) {$js .= ' | ';} - } - - $js .= '
    '; - - $js .= ''; - - return $js; -} diff --git a/includes/post-types-admin.bak b/includes/post-types-admin.bak new file mode 100644 index 0000000..e85d6cf --- /dev/null +++ b/includes/post-types-admin.bak @@ -0,0 +1,1798 @@ + +
    + ID, 'playlist', false ); + $c = 1; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + // echo ""; + // echo ""; + // echo ""; + // echo ""; + echo ''; + + if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { + + foreach ( $entries[0] as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + + echo ''; + + echo ''; + + echo ''; + echo ''; + $c++; + } + } + } + echo '
    ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    ' . esc_html( $c ) . '
    ' . esc_html__( 'Comments', 'radio-station' ) . ' '; + echo '' . esc_html__( 'New', 'radio-station' ) . ' '; + $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + echo ''; + + echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; + echo '' . esc_html__( 'Remove', 'radio-station' ) . '
    '; + + ?> + +
    + +
    + +
    +

    + post_status, array( 'publish', 'future', 'private' ) ) || 0 === $post->ID ) { + if ( $can_publish ) : + if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : + ?> + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + + +
    + + $song ) { + if ( 'queued' === $song['playlist_entry_status'] ) { + $playlist[] = $song; + unset( $playlist[ $i ] ); + } + } + update_post_meta( $post_id, 'playlist', $playlist ); + + // sanitize and save show ID + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + } else { + $show = absint( $show ); + if ( $show > 0 ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + } + } + } + +} + +// ------------------------- +// Add Playlist List Columns +// ------------------------- +// 2.2.7: added data columns to playlist list display +add_filter( 'manage_edit-playlist_columns', 'radio_station_playlist_columns', 6 ); +function radio_station_playlist_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb'] ) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset( $columns['date'] ); + $comments = $columns['comments']; unset( $columns['comments'] ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// ------------------------- +// Playlist List Column Data +// ------------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_playlist_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); +function radio_station_playlist_column_data( $column, $post_id ) { + if ( $column == 'show' ) { + $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); + $post = get_post( $show_id ); + echo "" . $post->post_title . ""; + } elseif ( $column == 'trackcount' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + echo count( $tracks ); + } elseif ( $column == 'tracklist' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + $tracklist = ''; + $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . "
    "; + $tracklist .= ''; + echo $tracklist; + } +} + +// --------------------------- +// Playlist List Column Styles +// --------------------------- +add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); +function radio_station_playlist_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-playlist' ) {return;} + echo ""; + + // --- expand/collapse tracklist data --- + echo ""; +} + + +// ------------- +// === Shows === +// ------------- + +// ------------------------ +// Add Related Show Metabox +// ------------------------ +// --- Add custom meta box for show assignment on blog posts --- +add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); +function radio_station_add_showblog_box() { + + // --- make sure a show exists before adding metabox --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + + if ( count( $shows ) > 0 ) { + + // ---- add a filter for which post types to show metabox on --- + // TODO: add this filter to plugin documentation + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + + // --- add the metabox to post types --- + add_meta_box( + 'dynamicShowBlog_sectionid', + __( 'Related to Show', 'radio-station' ), + 'radio_station_inner_showblog_custom_box', + $post_types, + 'side' + ); + } +} + +// -------------------- +// Related Show Metabox +// -------------------- +// --- Prints the box content for the Show field --- +function radio_station_inner_showblog_custom_box() { + global $post; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + $current = get_post_meta( $post->ID, 'post_showblog_id', true ); + + ?> +
    + + +
    + 0 ) { + update_post_meta( $post_id, 'post_showblog_id', $show ); + } + } + } + +} + + +// ----------------------------------- +// Add Assign Playlist to Show Metabox +// ----------------------------------- +// --- Add custom meta box for show assigment --- +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); +function radio_station_myplaylist_add_show_box() { + // 2.2.2: add high priority to shift above publish box + add_meta_box( + 'dynamicShow_sectionid', + __( 'Show', 'radio-station' ), + 'radio_station_myplaylist_inner_show_custom_box', + 'playlist', + 'side', + 'high' + ); +} + +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- +// --- Prints the box content for the Show field --- +function radio_station_myplaylist_inner_show_custom_box() { + + global $post, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); + + $user = wp_get_current_user(); + + // --- allow administrators to do whatever they want --- + // 2.2.8: remove strict in_array checking + if ( ! in_array( 'administrator', $user->roles ) ) { + + // --- get the user lists for all shows --- + $allowed_shows = array(); + + $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + + // ---- check each list for the current user --- + foreach ( $show_user_lists as $list ) { + + $list->meta_value = maybe_unserialize( $list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( ! is_array( $list->meta_value ) ) { + $list->meta_value = array(); + } + + // --- only include shows the user is assigned to --- + foreach ( $list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $list->post_id; + } + } + } + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); + + $shows = get_posts( $args ); + + } else { + + // --- for if you are an administrator --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + + $shows = get_posts( $args ); + } + + ?> +
    + + +
    + ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + + // added max-width to prevent metabox overflows + ?> +
    + +

    + /> +

    + +


    +

    + +


    +

    + +


    +

    + +
    + roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'edit_shows' === $capname && $capstatus ) { + $add_roles[] = $name; + } + } + } + $add_roles = array_unique( $add_roles ); + + // ---- create the meta query for get_users() --- + $meta_query = array( 'relation' => 'OR' ); + foreach ( $add_roles as $role ) { + $meta_query[] = array( + 'key' => $wpdb->prefix . 'capabilities', + 'value' => $role, + 'compare' => 'like', + ); + } + + // --- get all eligible users --- + $args = array( + 'meta_query' => $meta_query, + 'orderby' => 'display_name', + 'order' => 'ASC', + //' fields' => array( 'ID, display_name' ), + ); + $users = get_users( $args ); + + // --- get the DJs currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( ! $current ) {$current = array();} + + // --- move any selected DJs to the top of the list --- + foreach ( $users as $i => $dj ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $dj->ID, $current ) ) { + unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $users, $dj ); // prepend the user to the beginning of the array + } + } + + // 2.2.2: add fix to make DJ multi-select input full metabox width + ?> +
    + + +
    + +
    + ID, 'show_sched', false ); + + $c = 0; + if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { + + // 2.2.7: soft shifts by start day and time for ordered display + foreach ( $shifts[0] as $shift ) { + if ( isset( $shift['day'] ) ) { + // --- group shifts by days of week --- + $starttime = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + if ( $shift['day'] == 'Sunday' ) {$i = 0;} + elseif ( $shift['day'] == 'Monday' ) {$i = 1;} + elseif ( $shift['day'] == 'Tuesday' ) {$i = 2;} + elseif ( $shift['day'] == 'Wednesday' ) {$i = 3;} + elseif ( $shift['day'] == 'Thursday' ) {$i = 4;} + elseif ( $shift['day'] == 'Friday' ) {$i = 5;} + elseif ( $shift['day'] == 'Saturdday' ) {$i = 6;} + $day_shifts[$i][$starttime] = $shift; + } else { + // --- preserve shift times even if day is not set --- + $starttime = strtotime( '1981-04-28 ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + $day_shifts[7][$starttime] = $shift; + } + } + + // --- sort day shifts and loop --- + ksort( $day_shifts ); + $show_shifts = array(); + foreach ( $day_shifts as $i => $day_shift ) { + // --- sort shifts by start time for each day --- + ksort( $day_shift ); + foreach ( $day_shift as $shift ) { + $show_shifts[] = $shift; + } + } + + // --- loop ordered show shifts --- + foreach ( $show_shifts as $shift ) { + ?> +
      + +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • + +
    • />
    • + +
    • + +
    + + + + +
    + $dj ) { + if ( ! empty( $dj ) ) { + $userid = get_user_by( 'id', $dj ); + if ( ! $userid ) {unset( $djs[ $i ] );} + } + } + } + + $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); + $email = sanitize_email( trim( $_POST['show_email'] ) ); + $active = $_POST['show_active']; + // 2.2.8: remove strict in_array checking + if ( ! in_array( $active, array( '', 'on' ) ) ) { + $active = ''; + } + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + + // --- update the show metadata --- + update_post_meta( $post_id, 'show_user_list', $djs ); + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + + // --- update the show shift metadata + $scheds = $_POST['show_sched']; + + // --- sanitize the show shift times --- + $new_scheds = array(); + $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + foreach ( $scheds as $i => $sched ) { + foreach ( $sched as $key => $value ) { + + // --- validate according to key --- + $isvalid = false; + if ( 'day' === $key ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $days ) ) { + $isvalid = true; + } + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } elseif ( 'encore' === $key ) { + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } + + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_scheds[$i][$key] = $value; + } else { + $new_scheds[$i][$key] = ''; + } + } + } + + update_post_meta( $post_id, 'show_sched', $new_scheds ); + +} + +// --------------------- +// Add Show List Columns +// --------------------- +// 2.2.7: added data columns to show list display +add_filter( 'manage_edit-show_columns', 'radio_station_show_columns', 6 ); +function radio_station_show_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb']) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset( $columns['date'] ); + $comments = $columns['comments']; unset( $columns['comments'] ); + $genres = $columns['taxonomy-genres']; unset( $columns['taxonomy-genres'] ); + $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); + $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); + $columns['djs'] = esc_attr( __( 'DJs', 'radio-station' ) ); + $columns['show_image'] = esc_attr( __( 'Show Image', 'radio-station' ) ); + $columns['taxonomy-genres'] = $genres; + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// --------------------- +// Show List Column Data +// --------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_show_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); +function radio_station_show_column_data( $column, $post_id ) { + if ( $column == 'active' ) { + $active = get_post_meta( $post_id, 'show_active', true ); + if ( $active == 'on') {echo __( 'Yes', 'radio-station' );} + else {echo __( 'No', 'radio-station' );} + } elseif ( $column == 'shifts' ) { + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $shift ) { + $timestamp = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ":" . $shift['start_min'] . " " . $shift['start_meridian'] ); + $sortedshifts[$timestamp] = $shift; + } + ksort( $sortedshifts ); + foreach ( $sortedshifts as $shift ) { + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo ""; + } + echo radio_station_translate_weekday( $shift['day'] ); + echo " " . $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + echo " - " . $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']."
    "; + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo "
    "; + } + } + } + } elseif ( $column == 'djs') { + $djs = get_post_meta( $post_id, 'show_user_list', true ); + if ( $djs && ( count( $djs ) > 0 ) ) { + foreach ( $djs as $dj ) { + $user_info = get_userdata( $dj ); + echo esc_html( $user_info->display_name )."
    "; + } + } + } elseif ( $column == 'show_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
    "; + } + } +} + +// ----------------------- +// Show List Column Styles +// ----------------------- +// 2.2.7: added show column styles +add_action( 'admin_footer', 'radio_station_show_column_styles' ); +function radio_station_show_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-show' ) {return;} + echo ""; +} + +// ------------------------- +// Add Show Shift Day Filter +// ------------------------- +// 2.2.7: added show day selection filtering +add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); +function radio_station_show_day_filter( $post_type, $which ) { + if ( 'show' !== $post_type ) {return;} + + // -- maybe get specified day --- + $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; + + // --- show day selector --- + $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + ?> + + + +
    + + ID, 'show_override_sched', false ); + if ( $override ) {$override = $override[0];} + else { + // 2.2.8: fix undefined index warnings for new schedule overrides + $override = array( + 'date' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '' + ); + } + ?> + + +
      +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • +
    +
    + '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + ); + } + + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $changed = false; + foreach ( $sched as $key => $value ) { + $isvalid = false; + + // --- validate according to key --- + if ( 'date' === $key ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { + $isvalid = true; + } + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + // 2.2.3: fix to validate 00 minute value + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } + + // --- if valid add to current schedule setting --- + if ( $isvalid && ( $value !== $current_sched[ $key ] ) ) { + $current_sched[ $key ] = $value; + $changed = true; + + // 2.2.7: sync separate meta key for override date + // (could be used to improve column sorting efficiency) + if ( $key == 'date' ) { + update_post_meta( $post_id, 'show_override_date', $value ); + } + } + } + + // --- save schedule setting if changed --- + if ( $changed ) { + update_post_meta( $post_id, 'show_override_sched', $current_sched ); + } +} + +// ---------------------------------- +// Add Schedule Override List Columns +// ---------------------------------- +// 2.2.7: added data columns to override list display +add_filter( 'manage_edit-override_columns', 'radio_station_override_columns', 6 ); +function radio_station_override_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) {unset( $columns['thumbnail'] );} + if ( isset( $columns['post_thumb'] ) ) {unset( $columns['post_thumb'] );} + $date = $columns['date']; unset($columns['date']); + $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); + $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); + $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); + $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station') ); + $columns['override_image'] = esc_attr( __( 'Override Image' ) ); + $columns['date'] = $date; + return $columns; +} + +// ----------------------------- +// Schedule Override Column Data +// ----------------------------- +// 2.2.7: added data columns for override list display +add_action( 'manage_override_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); +function radio_station_override_column_data( $column, $post_id ) { + + global $show_shifts; + + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $column == 'override_date' ) { + $datetime = strtotime( $override['date'] ); + $month = date( 'F', $datetime ); + $month = radio_station_translate_month( $month ); + $weekday = date ( 'l', $datetime ); + $weekday = radio_station_translate_weekday( $weekday ); + echo $weekday . ' ' . date( 'j', $datetime ) . ' '. $month . ' ' . date( 'Y', $datetime ); + } + elseif ( $column == 'start_time' ) {echo $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'];} + elseif ( $column == 'end_time' ) {echo $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'];} + elseif ( $column == 'shows_affected' ) { + + // --- maybe get all show shifts --- + if ( !isset( $show_shifts ) ) { + global $wpdb; + $show_shifts = $wpdb->get_results( + "SELECT posts.post_title, meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish'" + ); + } + if ( ! $show_shifts || ( count( $show_shifts ) == 0 ) ) {return;} + + // --- get the override weekday and convert to 24 hour time --- + $datetime = strtotime( $override['date'] ); + $weekday = date( 'l', $datetime ); + + // --- get start and end override times --- + $startoverride = strtotime( $override['date']. ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); + $endoverride = strtotime( $override['date']. ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); + if ( $endoverride <= $startoverride ) {$endoverride = $endoverride + 86400;} + + // --- loop show shifts --- + foreach ( $show_shifts as $show_shift ) { + $shift = maybe_unserialize( $show_shift->meta_value ); + if ( ! is_array( $shift ) ) {$shift = array();} + + foreach ( $shift as $time ) { + if ( isset( $time['day'] ) && ( $time['day'] == $weekday ) ) { + + // --- get start and end shift times --- + $startshift = strtotime( $override['date']. ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' .$time['start_meridian'] ); + $endshift = strtotime( $override['date']. ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); + if ( $endshift <= $startshift ) {$endshift = $endshift + 86400;} + + // --- compare override time overlaps to get affected shows --- + if ( ( ( $startoverride < $startshift ) && ( $endoverride > $startshift ) ) + || ( ( $startoverride >= $startshift ) && ( $startoverride < $endshift ) ) ) { + $active = get_post_meta( $show_shift->post_id, 'show_active', true ); + if ( $active != 'on' ) {echo "[" . __( 'Inactive', 'radio-station' ) . "] ";} + echo $show_shift->post_title; + echo " (" . $time['start_hour'] . ":" . $time['start_min'] . $time['start_meridian']; + echo " - " . $time['end_hour'] . ":" . $time['end_min'] . $time['end_meridian'] . ")"; + if ( $active != 'on' ) {echo "";} + echo "
    "; + } + } + } + } + } elseif ( $column == 'override_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
    "; + } + } +} + +// ----------------------------- +// Sortable Override Date Column +// ----------------------------- +// 2.2.7: added to allow override date column sorting +add_filter( 'manage_edit-override_sortable_columns', 'radio_station_override_sortable_columns' ); +function radio_station_override_sortable_columns( $columns ) { + $columns['override_date'] = 'show_override_date'; + return $columns; +} + +// ------------------------------- +// Schedule Override Column Styles +// ------------------------------- +add_action( 'admin_footer', 'radio_station_override_column_styles' ); +function radio_station_override_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-override' ) {return;} + echo ""; +} + +// ---------------------------------- +// Add Schedule Override Month Filter +// ---------------------------------- +// 2.2.7: added month selection filtering +add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); +function radio_station_override_date_filter( $post_type, $which ) { + global $wp_locale; + if ( 'override' !== $post_type ) {return;} + + // --- get all show override months/years --- + global $wpdb; + $overridequery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count($results) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_date', true ); + $datetime = strtotime( $override ); + $month = date( 'm', $datetime ); + $year = date( 'Y', $datetime ); + $months[$year.$month]['year'] = $year; + $months[$year.$month]['month'] = $month; + } + } else {return;} + + // --- maybe get specified month --- + $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + + // --- month override selector --- + ?> + + + is_main_query() ) {return;} + + // --- Shows by Shift Days Filtering --- + if ( 'show' === $query->get( 'post_type' ) ) { + + // --- check if day filter is seta --- + if ( isset( $_GET['weekday'] ) && ( '0' != $_GET['weekday'] ) ) { + + $weekday = $_GET['weekday']; + + // need to loop and sync a separate meta key to enable filtering + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $showquery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'show'"; + $results = $wpdb->get_results( $showquery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + $shiftdays = array(); $shiftstart = false; + foreach ( $shifts as $shift ) { + if ( $shift['day'] == $weekday ) { + $shifttime = radio_station_convert_schedule_to_24hour( $shift ); + $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ":00"; + } + } + if ( $shiftstart ) { + update_post_meta( $post_id, 'show_shift_time', $shiftstart ); + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } + } + + // --- set the meta query for filtering --- + // this is not working?! but does not need to as using orderby fixes it + $meta_query = array( + 'key' => 'show_shift_time', + 'compare' => 'EXISTS', + ); + // $query->set( 'meta_query', $meta_query ); + + // --- order by show time start --- + // only need to set the orderby query and exists check is automatically done! + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_shift_time' ); + $query->set( 'meta_type', 'TIME' ); + } + } + + // --- Order Show Overrides by Override Date --- + // also making this the default sort order + // if ( 'show_override_date' === $query->get( 'orderby' ) ) { + if ( 'override' === $query->get( 'post_type' ) ) { + + // unless order by published date is explicitly chosen + if ( 'date' !== $query->get( 'orderby') ) { + + // need to loop and sync a separate meta key to enable orderby sorting + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $overridequery = "SELECT ID FROM ".$wpdb->posts." WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $override ) { + update_post_meta( $post_id, 'show_override_date', $override['date'] ); + } else { + delete_post_meta( $post_id, 'show_override_data' ); + } + } + } + + // --- now we can set the orderby meta query to the synced key --- + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_override_date' ); + $query->set( 'meta_type', 'date' ); + + // --- apply override year/month filtering --- + if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { + $yearmonth = $_GET['month']; + $start_date = date($yearmonth.'01'); + $end_date = date($yearmonth.'t'); + $meta_query = array( + 'key' => 'show_override_date', + 'value' => array($start_date, $end_date), + 'compare' => 'BETWEEN', + 'type' => 'DATE' + ); + $query->set( 'meta_query', $meta_query ); + } + + } + } +} + diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php new file mode 100644 index 0000000..04c7cd5 --- /dev/null +++ b/includes/post-types-admin.php @@ -0,0 +1,1895 @@ + +
    + ID, 'playlist', false ); + $c = 1; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + // echo ""; + // echo ""; + // echo ""; + // echo ""; + echo ''; + + if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { + + foreach ( $entries[0] as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + + echo ''; + + echo ''; + + echo ''; + echo ''; + $c++; + } + } + } + echo '
    ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    ' . esc_html( $c ) . '
    ' . esc_html__( 'Comments', 'radio-station' ) . ' '; + echo '' . esc_html__( 'New', 'radio-station' ) . ' '; + $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + echo ''; + + echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; + echo '' . esc_html__( 'Remove', 'radio-station' ) . '
    '; + + ?> + +
    + +
    + +
    +

    + post_status, array( 'publish', 'future', 'private' ) ) || ( 0 === $post->ID ) ) { + if ( $can_publish ) : + if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : + ?> + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + '50', + 'accesskey' => 'o', + ) + ); + ?> + + + + +
    + + $song ) { + if ( 'queued' === $song['playlist_entry_status'] ) { + $playlist[] = $song; + unset( $playlist[ $i ] ); + } + } + update_post_meta( $post_id, 'playlist', $playlist ); + + // sanitize and save show ID + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + } else { + $show = absint( $show ); + if ( $show > 0 ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + } + } + } + +} + +// ------------------------- +// Add Playlist List Columns +// ------------------------- +// 2.2.7: added data columns to playlist list display +add_filter( 'manage_edit-playlist_columns', 'radio_station_playlist_columns', 6 ); +function radio_station_playlist_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); + } + $date = $columns['date']; + unset( $columns['date'] ); + $comments = $columns['comments']; + unset( $columns['comments'] ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// ------------------------- +// Playlist List Column Data +// ------------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_playlist_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); +function radio_station_playlist_column_data( $column, $post_id ) { + if ( $column == 'show' ) { + $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); + $post = get_post( $show_id ); + echo "" . $post->post_title . ''; + } elseif ( $column == 'trackcount' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + echo count( $tracks ); + } elseif ( $column == 'tracklist' ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + $tracklist = ''; + $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . '
    '; + $tracklist .= ''; + echo $tracklist; + } +} + +// --------------------------- +// Playlist List Column Styles +// --------------------------- +add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); +function radio_station_playlist_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-playlist' ) { + return; + } + echo ''; + + // --- expand/collapse tracklist data --- + echo ""; +} + + +// ------------- +// === Shows === +// ------------- + +// ------------------------ +// Add Related Show Metabox +// ------------------------ +// --- Add custom meta box for show assignment on blog posts --- +add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); +function radio_station_add_showblog_box() { + + // --- make sure a show exists before adding metabox --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + + if ( count( $shows ) > 0 ) { + + // ---- add a filter for which post types to show metabox on --- + // TODO: add this filter to plugin documentation + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + + // --- add the metabox to post types --- + add_meta_box( + 'dynamicShowBlog_sectionid', + __( 'Related to Show', 'radio-station' ), + 'radio_station_inner_showblog_custom_box', + $post_types, + 'side' + ); + } +} + +// -------------------- +// Related Show Metabox +// -------------------- +// --- Prints the box content for the Show field --- +function radio_station_inner_showblog_custom_box() { + global $post; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + $current = get_post_meta( $post->ID, 'post_showblog_id', true ); + + ?> +
    + + +
    + 0 ) { + update_post_meta( $post_id, 'post_showblog_id', $show ); + } + } + } + +} + + +// ----------------------------------- +// Add Assign Playlist to Show Metabox +// ----------------------------------- +// --- Add custom meta box for show assigment --- +add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); +function radio_station_myplaylist_add_show_box() { + // 2.2.2: add high priority to shift above publish box + add_meta_box( + 'dynamicShow_sectionid', + __( 'Show', 'radio-station' ), + 'radio_station_myplaylist_inner_show_custom_box', + 'playlist', + 'side', + 'high' + ); +} + +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- +// --- Prints the box content for the Show field --- +function radio_station_myplaylist_inner_show_custom_box() { + + global $post, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); + + $user = wp_get_current_user(); + + // --- allow administrators to do whatever they want --- + // 2.2.8: remove strict in_array checking + if ( ! in_array( 'administrator', $user->roles ) ) { + + // --- get the user lists for all shows --- + $allowed_shows = array(); + + $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + + // ---- check each list for the current user --- + foreach ( $show_user_lists as $list ) { + + $list->meta_value = maybe_unserialize( $list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( ! is_array( $list->meta_value ) ) { + $list->meta_value = array(); + } + + // --- only include shows the user is assigned to --- + foreach ( $list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $list->post_id; + } + } + } + + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); + + $shows = get_posts( $args ); + + } else { + + // --- for if you are an administrator --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => 'show', + 'post_status' => 'publish', + ); + + $shows = get_posts( $args ); + } + + ?> +
    + + +
    + ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + + // added max-width to prevent metabox overflows + ?> +
    + +

    + /> +

    + +


    +

    + +


    +

    + +


    +

    + +
    + roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'edit_shows' === $capname && $capstatus ) { + $add_roles[] = $name; + } + } + } + $add_roles = array_unique( $add_roles ); + + // ---- create the meta query for get_users() --- + $meta_query = array( 'relation' => 'OR' ); + foreach ( $add_roles as $role ) { + $meta_query[] = array( + 'key' => $wpdb->prefix . 'capabilities', + 'value' => $role, + 'compare' => 'like', + ); + } + + // --- get all eligible users --- + $args = array( + 'meta_query' => $meta_query, + 'orderby' => 'display_name', + 'order' => 'ASC', + //' fields' => array( 'ID, display_name' ), + ); + $users = get_users( $args ); + + // --- get the DJs currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( ! $current ) { + $current = array(); + } + + // --- move any selected DJs to the top of the list --- + foreach ( $users as $i => $dj ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $dj->ID, $current ) ) { + unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $users, $dj ); // prepend the user to the beginning of the array + } + } + + // 2.2.2: add fix to make DJ multi-select input full metabox width + ?> +
    + + +
    + +
    + ID, 'show_sched', false ); + + $c = 0; + if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { + + // 2.2.7: soft shifts by start day and time for ordered display + foreach ( $shifts[0] as $shift ) { + if ( isset( $shift['day'] ) ) { + // --- group shifts by days of week --- + $starttime = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + if ( $shift['day'] == 'Sunday' ) { + $i = 0; + } elseif ( $shift['day'] == 'Monday' ) { + $i = 1; + } elseif ( $shift['day'] == 'Tuesday' ) { + $i = 2; + } elseif ( $shift['day'] == 'Wednesday' ) { + $i = 3; + } elseif ( $shift['day'] == 'Thursday' ) { + $i = 4; + } elseif ( $shift['day'] == 'Friday' ) { + $i = 5; + } elseif ( $shift['day'] == 'Saturdday' ) { + $i = 6; + } + $day_shifts[ $i ][ $starttime ] = $shift; + } else { + // --- preserve shift times even if day is not set --- + $starttime = strtotime( '1981-04-28 ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + $day_shifts[7][ $starttime ] = $shift; + } + } + + // --- sort day shifts and loop --- + ksort( $day_shifts ); + $show_shifts = array(); + foreach ( $day_shifts as $i => $day_shift ) { + // --- sort shifts by start time for each day --- + ksort( $day_shift ); + foreach ( $day_shift as $shift ) { + $show_shifts[] = $shift; + } + } + + // --- loop ordered show shifts --- + foreach ( $show_shifts as $shift ) { + ?> +
      + +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • + +
    • />
    • + +
    • + +
    + + + + +
    + $dj ) { + if ( ! empty( $dj ) ) { + $userid = get_user_by( 'id', $dj ); + if ( ! $userid ) { + unset( $djs[ $i ] ); + } + } + } + } + + $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); + $email = sanitize_email( trim( $_POST['show_email'] ) ); + $active = $_POST['show_active']; + // 2.2.8: remove strict in_array checking + if ( ! in_array( $active, array( '', 'on' ) ) ) { + $active = ''; + } + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + + // --- update the show metadata --- + update_post_meta( $post_id, 'show_user_list', $djs ); + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + + // --- update the show shift metadata + $scheds = $_POST['show_sched']; + + // --- sanitize the show shift times --- + $new_scheds = array(); + $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + foreach ( $scheds as $i => $sched ) { + foreach ( $sched as $key => $value ) { + // --- validate according to key --- + $isvalid = false; + if ( 'day' === $key ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $days ) ) { + $isvalid = true; + } + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } elseif ( 'encore' === $key ) { + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } + + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_scheds[$i][$key] = $value; + } else { + $new_scheds[$i][$key] = ''; + } + } + } + + update_post_meta( $post_id, 'show_sched', $new_scheds ); + +} + +// --------------------- +// Add Show List Columns +// --------------------- +// 2.2.7: added data columns to show list display +add_filter( 'manage_edit-show_columns', 'radio_station_show_columns', 6 ); +function radio_station_show_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); + } + $date = $columns['date']; + unset( $columns['date'] ); + $comments = $columns['comments']; + unset( $columns['comments'] ); + $genres = $columns['taxonomy-genres']; + unset( $columns['taxonomy-genres'] ); + $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); + $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); + $columns['djs'] = esc_attr( __( 'DJs', 'radio-station' ) ); + $columns['show_image'] = esc_attr( __( 'Show Image', 'radio-station' ) ); + $columns['taxonomy-genres'] = $genres; + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; +} + +// --------------------- +// Show List Column Data +// --------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_show_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); +function radio_station_show_column_data( $column, $post_id ) { + if ( $column == 'active' ) { + $active = get_post_meta( $post_id, 'show_active', true ); + if ( $active == 'on' ) { + echo __( 'Yes', 'radio-station' ); + } else { + echo __( 'No', 'radio-station' ); + } + } elseif ( $column == 'shifts' ) { + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $shift ) { + $timestamp = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + $sortedshifts[ $timestamp ] = $shift; + } + ksort( $sortedshifts ); + foreach ( $sortedshifts as $shift ) { + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo ''; + } + echo radio_station_translate_weekday( $shift['day'] ); + echo ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + echo ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian'] . '
    '; + if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { + echo '
    '; + } + } + } + } elseif ( $column == 'djs' ) { + $djs = get_post_meta( $post_id, 'show_user_list', true ); + if ( $djs && ( count( $djs ) > 0 ) ) { + foreach ( $djs as $dj ) { + $user_info = get_userdata( $dj ); + echo esc_html( $user_info->display_name ) . '
    '; + } + } + } elseif ( $column == 'show_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
    "; + } + } +} + +// ----------------------- +// Show List Column Styles +// ----------------------- +// 2.2.7: added show column styles +add_action( 'admin_footer', 'radio_station_show_column_styles' ); +function radio_station_show_column_styles() { + $currentscreen = get_current_screen(); + if ( 'edit-show' !== $currentscreen->id ) { + return; + } + echo ''; +} + +// ------------------------- +// Add Show Shift Day Filter +// ------------------------- +// 2.2.7: added show day selection filtering +add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); +function radio_station_show_day_filter( $post_type, $which ) { + if ( 'show' !== $post_type ) { + return; + } + + // -- maybe get specified day --- + $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; + + // --- show day selector --- + $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + ?> + + + +
    + + ID, 'show_override_sched', false ); + if ( $override ) { + $override = $override[0]; + } + ?> + + +
      +
    • + : + +
    • + +
    • + : + + + +
    • + +
    • + : + + + +
    • +
    +
    + '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + ); + } + + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $changed = false; + foreach ( $sched as $key => $value ) { + $isvalid = false; + + // --- validate according to key --- + if ( 'date' === $key ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { + $isvalid = true; + } + } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( 'start_min' === $key || 'end_min' === $key ) { + // 2.2.3: fix to validate 00 minute value + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } + + // --- if valid add to current schedule setting --- + if ( $isvalid && ( $value !== $current_sched[ $key ] ) ) { + $current_sched[ $key ] = $value; + $changed = true; + + // 2.2.7: sync separate meta key for override date + // (could be used to improve column sorting efficiency) + if ( $key == 'date' ) { + update_post_meta( $post_id, 'show_override_date', $value ); + } + } + } + + // --- save schedule setting if changed --- + if ( $changed ) { + update_post_meta( $post_id, 'show_override_sched', $current_sched ); + } +} + +// ---------------------------------- +// Add Schedule Override List Columns +// ---------------------------------- +// 2.2.7: added data columns to override list display +add_filter( 'manage_edit-override_columns', 'radio_station_override_columns', 6 ); +function radio_station_override_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); + } + $date = $columns['date']; + unset( $columns['date'] ); + $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); + $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); + $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); + $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station' ) ); + $columns['override_image'] = esc_attr( __( 'Override Image' ) ); + $columns['date'] = $date; + return $columns; +} + +// ----------------------------- +// Schedule Override Column Data +// ----------------------------- +// 2.2.7: added data columns for override list display +add_action( 'manage_override_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); +function radio_station_override_column_data( $column, $post_id ) { + + global $show_shifts; + + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $column == 'override_date' ) { + $datetime = strtotime( $override['date'] ); + $month = date( 'F', $datetime ); + $month = radio_station_translate_month( $month ); + $weekday = date( 'l', $datetime ); + $weekday = radio_station_translate_weekday( $weekday ); + echo $weekday . ' ' . date( 'j', $datetime ) . ' ' . $month . ' ' . date( 'Y', $datetime ); + } elseif ( $column == 'start_time' ) { + echo $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; + } elseif ( $column == 'end_time' ) { + echo $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; + } elseif ( $column == 'shows_affected' ) { + + // --- maybe get all show shifts --- + if ( ! isset( $show_shifts ) ) { + global $wpdb; + $show_shifts = $wpdb->get_results( + "SELECT posts.post_title, meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish'" + ); + } + if ( ! $show_shifts || ( count( $show_shifts ) == 0 ) ) { + return; + } + + // --- get the override weekday and convert to 24 hour time --- + $datetime = strtotime( $override['date'] ); + $weekday = date( 'l', $datetime ); + + // --- get start and end override times --- + $startoverride = strtotime( $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); + $endoverride = strtotime( $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); + if ( $endoverride <= $startoverride ) { + $endoverride = $endoverride + 86400; + } + + // --- loop show shifts --- + foreach ( $show_shifts as $show_shift ) { + $shift = maybe_unserialize( $show_shift->meta_value ); + if ( ! is_array( $shift ) ) { + $shift = array(); + } + + foreach ( $shift as $time ) { + if ( isset( $time['day'] ) && ( $time['day'] == $weekday ) ) { + + // --- get start and end shift times --- + $startshift = strtotime( $override['date'] . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' . $time['start_meridian'] ); + $endshift = strtotime( $override['date'] . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); + if ( $endshift <= $startshift ) { + $endshift = $endshift + 86400; + } + + // --- compare override time overlaps to get affected shows --- + if ( ( ( $startoverride < $startshift ) && ( $endoverride > $startshift ) ) + || ( ( $startoverride >= $startshift ) && ( $startoverride < $endshift ) ) ) { + $active = get_post_meta( $show_shift->post_id, 'show_active', true ); + if ( $active != 'on' ) { + echo '[' . __( 'Inactive', 'radio-station' ) . '] '; + } + echo $show_shift->post_title; + echo ' (' . $time['start_hour'] . ':' . $time['start_min'] . $time['start_meridian']; + echo ' - ' . $time['end_hour'] . ':' . $time['end_min'] . $time['end_meridian'] . ')'; + if ( $active != 'on' ) { + echo ''; + } + echo '
    '; + } + } + } + } + } elseif ( $column == 'override_image' ) { + $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + if ( $thumbnail_url ) { + echo "
    "; + } + } +} + +// ----------------------------- +// Sortable Override Date Column +// ----------------------------- +// 2.2.7: added to allow override date column sorting +add_filter( 'manage_edit-override_sortable_columns', 'radio_station_override_sortable_columns' ); +function radio_station_override_sortable_columns( $columns ) { + $columns['override_date'] = 'show_override_date'; + return $columns; +} + +// ------------------------------- +// Schedule Override Column Styles +// ------------------------------- +add_action( 'admin_footer', 'radio_station_override_column_styles' ); +function radio_station_override_column_styles() { + $currentscreen = get_current_screen(); + if ( $currentscreen->id !== 'edit-override' ) { + return; + } + echo ''; +} + +// ---------------------------------- +// Add Schedule Override Month Filter +// ---------------------------------- +// 2.2.7: added month selection filtering +add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); +function radio_station_override_date_filter( $post_type, $which ) { + global $wp_locale; + if ( 'override' !== $post_type ) { + return; + } + + // --- get all show override months/years --- + global $wpdb; + $overridequery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_date', true ); + $datetime = strtotime( $override ); + $month = date( 'm', $datetime ); + $year = date( 'Y', $datetime ); + $months[ $year . $month ]['year'] = $year; + $months[ $year . $month ]['month'] = $month; + } + } else { + return; + } + + // --- maybe get specified month --- + $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + + // --- month override selector --- + ?> + + + is_main_query() ) { + return; + } + + // --- Shows by Shift Days Filtering --- + if ( 'show' === $query->get( 'post_type' ) ) { + + // --- check if day filter is seta --- + if ( isset( $_GET['weekday'] ) && ( '0' != $_GET['weekday'] ) ) { + + $weekday = $_GET['weekday']; + + // need to loop and sync a separate meta key to enable filtering + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $showquery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'show'"; + $results = $wpdb->get_results( $showquery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + $shiftdays = array(); + $shiftstart = false; + foreach ( $shifts as $shift ) { + if ( $shift['day'] == $weekday ) { + $shifttime = radio_station_convert_schedule_to_24hour( $shift ); + $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ':00'; + } + } + if ( $shiftstart ) { + update_post_meta( $post_id, 'show_shift_time', $shiftstart ); + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } else { + delete_post_meta( $post_id, 'show_shift_time' ); + } + } + } + + // --- set the meta query for filtering --- + // this is not working?! but does not need to as using orderby fixes it + $meta_query = array( + 'key' => 'show_shift_time', + 'compare' => 'EXISTS', + ); + // $query->set( 'meta_query', $meta_query ); + + // --- order by show time start --- + // only need to set the orderby query and exists check is automatically done! + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_shift_time' ); + $query->set( 'meta_type', 'TIME' ); + } + } + + // --- Order Show Overrides by Override Date --- + // also making this the default sort order + // if ( 'show_override_date' === $query->get( 'orderby' ) ) { + if ( 'override' === $query->get( 'post_type' ) ) { + + // unless order by published date is explicitly chosen + if ( 'date' !== $query->get( 'orderby' ) ) { + + // need to loop and sync a separate meta key to enable orderby sorting + // (not really efficient but at least it makes it possible!) + // ...but could be improved by checking against postmeta table + global $wpdb; + $overridequery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'override'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $override ) { + update_post_meta( $post_id, 'show_override_date', $override['date'] ); + } else { + delete_post_meta( $post_id, 'show_override_data' ); + } + } + } + + // --- now we can set the orderby meta query to the synced key --- + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_override_date' ); + $query->set( 'meta_type', 'date' ); + + // --- apply override year/month filtering --- + if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { + $yearmonth = $_GET['month']; + $start_date = date( $yearmonth . '01' ); + $end_date = date( $yearmonth . 't' ); + $meta_query = array( + 'key' => 'show_override_date', + 'value' => array( $start_date, $end_date ), + 'compare' => 'BETWEEN', + 'type' => 'DATE', + ); + $query->set( 'meta_query', $meta_query ); + } + } + } +} diff --git a/includes/post-types.php b/includes/post-types.php new file mode 100644 index 0000000..942c90f --- /dev/null +++ b/includes/post-types.php @@ -0,0 +1,214 @@ + array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'taxonomies' => array( 'genres' ), + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), + 'can_export' => true, + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // -------- + // Playlist + // -------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; + // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'playlist', + array( + 'labels' => array( + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'comments' ), + 'can_export' => true, + 'has_archive' => 'playlists-archive', + 'rewrite' => array( 'slug' => 'playlists' ), + 'capability_type' => 'playlist', + 'map_meta_cap' => true, + ) + ); + + // ----------------- + // Schedule Override + // ----------------- + // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; + // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); + register_post_type( + 'override', + array( + 'labels' => array( + 'name' => __( 'Schedule Override', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + // 'menu_position' => 5, + // 'menu_icon' => $icon, + 'public' => true, + 'hierarchical' => false, + 'supports' => array( 'title', 'thumbnail' ), + 'can_export' => true, + 'rewrite' => array( 'slug' => 'show-override' ), + 'capability_type' => 'show', + 'map_meta_cap' => true, + ) + ); + + // --- maybe trigger flush of rewrite rules --- + if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { + add_action( 'init', 'flush_rewrite_rules', 20 ); + delete_option( 'radio_station_flush_rewrite_rules' ); + } +} + +// --------------------------------------- +// Set Post Type Editing to Classic Editor +// --------------------------------------- +// 2.2.2: added so metabox displays can continue to use wide widths +add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); +add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); +function radio_station_post_type_editor( $can_edit, $post_type ) { + $post_types = array( 'show', 'playlist', 'override' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $post_type, $post_types ) ) { + return false; + } + return $can_edit; +} + +// -------------------------- +// Add Show Thumbnail Support +// -------------------------- +// --- add featured image support to "show" post type --- +// (this is probably no longer necessary as declared in register_post_type for show) +add_action( 'init', 'radio_station_add_featured_image_support' ); +function radio_station_add_featured_image_support() { + $supported_types = get_theme_support( 'post-thumbnails' ); + + if ( false === $supported_types ) { + add_theme_support( 'post-thumbnails', array( 'show' ) ); + } elseif ( is_array( $supported_types ) ) { + $supported_types[0][] = 'show'; + add_theme_support( 'post-thumbnails', $supported_types[0] ); + } +} + +// ------------------ +// === Taxonomies === +// ------------------ + +// ----------------------- +// Register Genre Taxonomy +// ----------------------- +// --- create custom taxonomy for the Show post type --- +add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); +function radio_station_myplaylist_create_show_taxonomy() { + + // --- add taxonomy labels --- + $labels = array( + 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), + 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), + 'search_items' => __( 'Search Genres', 'radio-station' ), + 'all_items' => __( 'All Genres', 'radio-station' ), + 'parent_item' => __( 'Parent Genre', 'radio-station' ), + 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), + 'edit_item' => __( 'Edit Genre', 'radio-station' ), + 'update_item' => __( 'Update Genre', 'radio-station' ), + 'add_new_item' => __( 'Add New Genre', 'radio-station' ), + 'new_item_name' => __( 'New Genre Name', 'radio-station' ), + 'menu_name' => __( 'Genre', 'radio-station' ), + ); + + // --- register the genre taxonomy --- + // 2.2.3: added show_admin_column and show_in_quick_edit arguments + register_taxonomy( + 'genres', + array( 'show' ), + array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => false, + 'query_var' => true, + 'rewrite' => array( 'slug' => 'genre' ), + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows', + ), + ) + ); + +} + diff --git a/includes/post_types.php b/includes/post_types.php deleted file mode 100644 index 91022b1..0000000 --- a/includes/post_types.php +++ /dev/null @@ -1,1286 +0,0 @@ - array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Show descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'taxonomies' => array( 'genres' ), - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // -------- - // Playlist - // -------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; - // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'playlist', - array( - 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Playlist descriptions', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'comments' ), - 'can_export' => true, - 'has_archive' => 'playlists-archive', - 'rewrite' => array( 'slug' => 'playlists' ), - 'capability_type' => 'playlist', - 'map_meta_cap' => true - ) - ); - - // ----------------- - // Schedule Override - // ----------------- - // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( 'override', - array( - 'labels' => array( - 'name' => __( 'Schedule Override', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ) - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __('Post type for Schedule Override', 'radio-station'), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'thumbnail' ), - 'can_export' => true, - 'rewrite' => array('slug' => 'show-override'), - 'capability_type' => 'show', - 'map_meta_cap' => true - ) - ); - - // --- maybe trigger flush of rewrite rules --- - if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { - add_action( 'init', 'flush_rewrite_rules', 20 ); - delete_option( 'radio_station_flush_rewrite_rules' ); - } -} - -// --------------------------------------- -// Set Post Type Editing to Classic Editor -// --------------------------------------- -// 2.2.2: added so metabox displays can continue to use wide widths -add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); -add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); -function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( 'show', 'playlist', 'override' ); - if ( in_array( $post_type, $post_types ) ) {return false;} - return $can_edit; -} - -// -------------------------- -// Add Show Thumbnail Support -// -------------------------- -// --- add featured image support to "show" post type --- -// (this is probably no longer necessary as declared in register_post_type for show) -add_action( 'init', 'radio_station_add_featured_image_support' ); -function radio_station_add_featured_image_support() { - $supported_types = get_theme_support( 'post-thumbnails' ); - - if ( $supported_types === false ) { - add_theme_support( 'post-thumbnails', array( 'show' ) ); - } elseif ( is_array( $supported_types ) ) { - $supported_types[0][] = 'show'; - add_theme_support( 'post-thumbnails', $supported_types[0] ); - } -} - -// ---------------------------- -// Metaboxes Above Content Area -// ---------------------------- -// --- shows plugin metaboxes above editor box for plugin CPTs --- -add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); -function radio_station_top_meta_boxes() { - global $post; do_meta_boxes( get_current_screen(), 'top', $post ); -} - - -// ------------------ -// === Taxonomies === -// ------------------ - -// ----------------------- -// Register Genre Taxonomy -// ----------------------- -// --- create custom taxonomy for the Show post type --- -add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); -function radio_station_myplaylist_create_show_taxonomy() { - - // --- add taxonomy labels --- - $labels = array( - 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), - 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), - 'search_items' => __( 'Search Genres', 'radio-station' ), - 'all_items' => __( 'All Genres', 'radio-station' ), - 'parent_item' => __( 'Parent Genre', 'radio-station' ), - 'parent_item_colon' => __( 'Parent Genre:', 'radio-station' ), - 'edit_item' => __( 'Edit Genre', 'radio-station' ), - 'update_item' => __( 'Update Genre', 'radio-station' ), - 'add_new_item' => __( 'Add New Genre', 'radio-station' ), - 'new_item_name' => __( 'New Genre Name', 'radio-station' ), - 'menu_name' => __( 'Genre', 'radio-station' ), - ); - - // --- register the genre taxonomy --- - // 2.2.3: added show_admin_column and show_in_quick_edit arguments - register_taxonomy( 'genres', array( 'show' ), - array( - 'hierarchical' => true, - 'labels' => $labels, - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array('slug' => 'genre'), - 'show_admin_column' => true, - 'show_in_quick_edit' => true, - 'capabilities' => array( - 'manage_terms' => 'edit_shows', - 'edit_terms' => 'edit_shows', - 'delete_terms' => 'edit_shows', - 'assign_terms' => 'edit_shows' - ), - ) - ); - -} - -// ---------------------------- -// Shift Genre Metabox on Shows -// ---------------------------- -// --- moves genre metabox above publish box --- -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); -function radio_station_genre_meta_box_order() { - global $wp_meta_boxes; - $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; - unset($wp_meta_boxes['show']['side']['core']['genresdiv']); - $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; -} - - -// ----------------- -// === Playlists === -// ----------------- - -// ------------------------- -// Add Playlist Data Metabox -// ------------------------- -// --- Add custom repeating meta field for the playlist edit form --- -// (Stores multiple associated values as a serialized string) -// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -function radio_station_myplaylist_add_custom_box() { - // 2.2.2: change context to show at top of edit screen - add_meta_box( - 'dynamic_sectionid', - __( 'Playlist Entries', 'radio-station' ), - 'radio_station_myplaylist_inner_custom_box', - 'playlist', - 'top', // shift to top - 'high' - ); -} - -// --------------------- -// Playlist Data Metabox -// --------------------- -// -- prints the playlist entry box to the main column on the edit screen --- -function radio_station_myplaylist_inner_custom_box() { - - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); - ?> -
    - ID, 'playlist', false ); - // print_r($entries); - $c = 1; - - echo ''; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - // echo ""; - // echo ""; - // echo ""; - // echo ""; - echo ""; - - if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { - - foreach( $entries[0] as $track ){ - if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - - echo ''; - - echo ''; - - if ( isset( $track['playlist_entry_new'] ) && $track['playlist_entry_new'] ) {$checked = ' checked="checked"';} else {$checked = '';} - - echo ''; - - echo ''; - echo ''; - $c++; - } - } - } - echo '
    ".__('Artist', 'radio-station')."".__('Song', 'radio-station')."".__('Album', 'radio-station')."".__('Record Label', 'radio-station')."".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."
    '.$c.'
    '.__('Comments', 'radio-station').' '; - echo ''.__( 'New', 'radio-station' ).' '; - echo ''; - - echo ' '.__( 'Status', 'radio-station').' '; - echo ''.__('Remove', 'radio-station').'
    '; - - ?> - -
    - -
    - -
    -

    - post_status, array('publish', 'future', 'private') ) || 0 == $post->ID ) { - if ( $can_publish ) : - if ( !empty($post->post_date_gmt) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : ?> - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - '50', 'accesskey' => 'o' ) ); ?> - - - - -
    - - $song ) { - if ( $song['playlist_entry_status'] == 'queued' ) { - $playlist[] = $song; - unset($playlist[$i]); - } - } - update_post_meta($post_id, 'playlist', $playlist); - - // sanitize and save show ID - $show = $_POST['playlist_show_id']; - if ( $show == '') { - delete_post_meta( $post_id, 'playlist_show_id' ); - } else { - $show = absint( $show ); - if ( $show > 0 ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); - } - } - } - -} - - -// ------------- -// === Shows === -// ------------- - -// ------------------------ -// Add Related Show Metabox -// ------------------------ -// --- Add custom meta box for show assignment on blog posts --- -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -function radio_station_add_showblog_box() { - - // --- make sure a show exists before adding metabox --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - - if ( count( $shows ) > 0 ) { - - // ---- add a filter for which post types to show metabox on --- - // TODO: add this filter to plugin documentation - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - - // --- add the metabox to post types --- - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'radio_station_inner_showblog_custom_box', - $post_types, - 'side' - ); - } -} - -// -------------------- -// Related Show Metabox -// -------------------- -// --- Prints the box content for the Show field --- -function radio_station_inner_showblog_custom_box() { - global $post; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - $shows = get_posts( $args ); - $current = get_post_meta( $post->ID, 'post_showblog_id', true); - - ?> -
    - - -
    - 0 ) {update_post_meta( $post_id, 'post_showblog_id', $show );} - } - } - -} - - -// ----------------------------------- -// Add Assign Playlist to Show Metabox -// ----------------------------------- -// --- Add custom meta box for show assigment --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); -function radio_station_myplaylist_add_show_box() { - // 2.2.2: add high priority to shift above publish box - add_meta_box( - 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'radio_station_myplaylist_inner_show_custom_box', - 'playlist', - 'side', - 'high' - ); -} - -// ------------------------------- -// Assign Playlist to Show Metabox -// ------------------------------- -// --- Prints the box content for the Show field --- -function radio_station_myplaylist_inner_show_custom_box() { - - global $post, $wpdb; - - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); - - $user = wp_get_current_user(); - - // --- allow administrators to do whatever they want --- - if ( !in_array('administrator', $user->roles ) ) { - - // --- get the user lists for all shows --- - $allowed_shows = array(); - - $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); - - // ---- check each list for the current user --- - foreach($show_user_lists as $list) { - - $list->meta_value = unserialize( $list->meta_value ); - - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( !is_array( $list->meta_value ) ) {$list->meta_value = array();} - - // --- only include shows the user is assigned to --- - foreach ( $list->meta_value as $user_id ) { - if ( $user->ID == $user_id ) { - $allowed_shows[] = $list->post_id; - } - } - } - - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ) - ); - - $shows = get_posts( $args ); - - } else { - - // --- for if you are an administrator --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish' - ); - - $shows = get_posts( $args ); - } - - ?> -
    - - -
    - ID, 'show_file', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); - - // added max-width to prevent metabox overflows - ?> -
    - -

    - /> -

    - -


    -

    - -


    -

    - -


    -

    - -
    - roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( ( $capname == 'edit_shows' ) && ( ( $capstatus == 1 ) || ( $capstatus == true ) ) ) { - $add_roles[] = $name; - } - } - } - $add_roles = array_unique( $add_roles ); - - // ---- create the meta query for get_users() --- - $meta_query = array( 'relation' => 'OR' ); - foreach ( $add_roles as $role ) { - $meta_query[] = array( - 'key' => $wpdb->prefix.'capabilities', - 'value' => $role, - 'compare' => 'like' - ); - } - - // --- get all eligible users --- - $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //' fields' => array( 'ID, display_name' ), - ); - $users = get_users( $args ); - - // --- get the DJs currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$current ) {$current = array();} - - // --- move any selected DJs to the top of the list --- - foreach ( $users as $i => $dj ) { - if ( in_array( $dj->ID, $current ) ) { - unset( $users[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $users, $dj ); // prepend the user to the beginning of the array - } - } - - // 2.2.2: add fix to make DJ multi-select input full metabox width - ?> -
    - - -
    - -
    - ID, 'show_sched', false); - // print_r($shifts); - - $c = 0; - if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { - foreach ( $shifts[0] as $track ){ - if ( isset( $track['day'] ) || isset( $track['time'] ) ){ - ?> -
      - -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • - -
    • />
    • - -
    • - -
    - - -
    - -
    - $dj ) { - if ( $dj != '' ) { - $userid = get_user_by( 'id', $dj ); - if ( !$userid ) {unset( $djs[$i] );} - } - } - } - $file = strip_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; - if ( !in_array( $active, array( '', 'on') ) ) {$active = '';} - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - - // --- update the show metadata --- - update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - - // --- update the show shift metadata - $scheds = $_POST['show_sched']; - - // --- sanitize the show shift times --- - $new_scheds = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'); - foreach ( $scheds as $i => $sched ) { - foreach ( $sched as $key => $value ) { - - // --- validate according to key --- - $isvalid = false; - if ( $key == 'day' ) { - if ( in_array( $value, $days ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } elseif ($key == 'encore') { - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on'); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to new schedule --- - if ( $isvalid ) {$new_scheds[$i][$key] = $value;} else {$new_scheds[$i][$key] = '';} - } - } - - update_post_meta( $post_id, 'show_sched', $new_scheds ); - -} - - -// -------------------------- -// === Schedule Overrides === -// -------------------------- - -// ----------------------------- -// Add Schedule Override Metabox -// ----------------------------- -// --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); -function radio_station_master_override_add_sched_box() { - // 2.2.2: add high priority to show at top of edit screen - add_meta_box( - 'dynamicSchedOver_sectionid', - __( 'Override Schedule', 'radio-station' ), - 'radio_station_master_override_inner_sched_custom_box', - 'override', - 'normal', - 'high' - ); -} - -// ------------------------- -// Schedule Override Metabox -// ------------------------- -function radio_station_master_override_inner_sched_custom_box() { - - global $post; - - // --- add nonce field for update verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); - ?> -
    - - ID, 'show_override_sched', false); - if ($override) {$override = $override[0];} - ?> - - -
      -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • -
    -
    - '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - ); - } - - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $changed = false; - foreach ( $sched as $key => $value ) { - $isvalid = false; - - // --- validate according to key --- - if ( $key == 'date' ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_hour' ) || ( $key == 'end_hour' ) ) { - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > 0 ) && ( absint($value) < 13 ) ) {$isvalid = true;} - } elseif ( ( $key == 'start_min' ) || ( $key == 'end_min' ) ) { - // 2.2.3: fix to validate 00 minute value - if ( $value == '' ) {$isvalid = true;} - elseif ( ( absint($value) > -1 ) && ( absint($value) < 61 ) ) {$isvalid = true;} - } elseif ( ($key == 'start_meridian') || ( $key == 'end_meridian' ) ) { - $valid = array( '', 'am', 'pm' ); - if ( in_array( $value, $valid ) ) {$isvalid = true;} - } - - // --- if valid add to current schedule setting --- - if ( $isvalid && ( $value != $current_sched[$key] ) ) { - $current_sched[$key] = $value; $changed = true; - } - } - - // --- save schedule setting if changed --- - if ($changed) {update_post_meta( $post_id, 'show_override_sched', $current_sched );} -} - diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 93fec06..ec4ed19 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1,424 +1,501 @@ - '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0 - ), $atts ) ); - - $most_recent = radio_station_myplaylist_get_now_playing(); - $output = ''; - - if ( $most_recent ) { - $class = ''; - if ( isset( $most_recent['playlist_entry_new'] ) && ( $most_recent['playlist_entry_new'] == 'on') ) { - $class = ' class="new"'; - } - - $output .= '
    '; - if ($title != '') {$output .= '

    '.$title.'

    ';} - - if ( $song == 1 ) { - $output .= ''.$most_recent['playlist_entry_song'].' '; - } - if ( $artist == 1 ) { - $output .= ''.$most_recent['playlist_entry_artist'].' '; - } - if ( $album == 1 ) { - $output .= ''.$most_recent['playlist_entry_album'].' '; - } - if ( $label == 1 ) { - $output .= ''.$most_recent['playlist_entry_label'].' '; - } - if ( $comments == 1 ) { - $output .= ''.$most_recent['playlist_entry_comments'].' '; - } - $output .= ''.__('View Playlist', 'radio-station').' '; - $output .= '
    '; - - } else { - echo 'No playlists available.'; - } - - return $output; -} -add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); - - -/* Shortcode to fetch all playlists for a given show id - * Since 2.0.0 - */ -function radio_station_shortcode_get_playlists_for_show($atts) { - - extract( shortcode_atts( array( - 'show' => '', - 'limit' => -1 - ), $atts ) ); - - // don't return anything if we do not have a show - if ( $show == '' ) {return false;} - - $args = array( - 'numberposts' => $limit, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish', - 'meta_key' => 'playlist_show_id', - 'meta_value' => $show - ); - - $playlists = get_posts( $args ); - - $output = ''; - - $output .= ''; - - return $output; -} -add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); - -/* Shortcode for displaying a list of all shows - * Since 2.0.0 - */ -function radio_station_shortcode_list_shows($atts) { - - extract( shortcode_atts( array( - 'genre' => '' - ), $atts ) ); - - // grab the published shows - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'meta_query' => array( - array( - 'key' => 'show_active', - 'value' => 'on', - ) - ) - ); - - if ( $genre != '' ) {$args['genres'] = $genre;} - - $shows = get_posts( $args ); - - // if there are no shows saved, return nothing - if ( !$shows ) {return false;} - - $output = ''; - - $output .= '
    '; - $output .= ''; - $output .= '
    '; - return $output; -} -add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); - -/* Shortcode function for current DJ on-air - * Since 2.0.9 - */ -function radio_station_shortcode_dj_on_air($atts) { - extract( shortcode_atts( array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'default_name' => '', - 'time' => '12', - 'show_sched' => 1, - 'show_playlist' => 1, - 'show_all_sched' => 0, - 'show_desc' => 0 - ), $atts ) ); - - // find out which DJ(s) are currently scheduled to be on-air and display them - $djs = radio_station_dj_get_current(); - $playlist = radio_station_myplaylist_get_now_playing(); - - $dj_str = ''; - - $dj_str .= '
    '; - if ( $title != '' ) {$dj_str .= '

    '.$title.'

    ';} - $dj_str .= '
      '; - - // echo the show/dj currently on-air - if ( $djs['type'] == 'override' ) { - - $dj_str .= '
    • '; - if ( $show_avatar ) { - if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail($djs['all'][0]['post_id'], 'thumbnail').''; - } - } - - $dj_str .= $djs['all'][0]['title']; - - // display the override's schedule if requested - if ( $show_sched ) { - - if ( $time == 12 ) { - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
      '; - } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - $dj_str .= ''.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
      '; - } - - $dj_str .= '
    • '; - } - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach ( $djs['all'] as $dj ) { - - $dj_str .= '
    • '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - - $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $dj_str .= '
      '.__( 'With','radio-station' ).' '; - foreach( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - if ( ( ( $count == 1 ) && ( count($names) == 2 ) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2) ) { - $dj_str .= ', '; - } - } - $dj_str .= '
      '; - } - } - - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $dj_str .= ''.$desc_string.''; - } - - if ( $show_playlist ) { - $dj_str .= ''.__('View Playlist', 'radio-station').''; - } - - $dj_str .= ''; - - if ( $show_sched ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - - // if we only want the schedule that's relevant now to display... - if ( !$show_all_sched ) { - - $current_sched = radio_station_current_schedule( $scheds ); - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
      '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour($current_sched); - $dj_str .= ''.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
      '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
      '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - $dj_str .= ''.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
      '; - } - } - } - } - - $dj_str .= '
    • '; - } - } else { - $dj_str .= '
    • '.$default_name.'
    • '; - } - } - - $dj_str .= '
    '; - $dj_str .= '
    '; - - return $dj_str; - -} -add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air'); - -/* Shortcode for displaying upcoming DJs/shows - * Since 2.0.9 -*/ -function radio_station_shortcode_coming_up( $atts ) { - - extract( shortcode_atts( array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'limit' => 1, - 'time' => '12', - 'show_sched' => 1 - ), $atts ) ); - - // find out which DJ(s) are coming up today - $djs = radio_station_dj_get_next($limit); - // print_r($djs); - - $now = strtotime( current_time( 'mysql' )); - $curDate = date( 'Y-m-d', $now ); - - $dj_str = ''; - - $dj_str .= '
    '; - if ( $title != '' ) {$dj_str .= '

    '.$title.'

    ';} - $dj_str .= '
      '; - - // echo the show/dj coming up - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && ( $dj['type'] == 'override') ) { - - echo '
    • '; - - if ( $show_avatar ) { - if ( has_post_thumbnail( $dj['post_id'] ) ) { - $dj_str .= ''.get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).''; - } - } - - echo $dj['title']; - if( $show_sched ) { - - if ( $time == 12 ) { - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
      '; - } else { - $dj['sched'] = radio_station_convert_schedule_to_24hour($dj['sched']); - $dj_str .= ''.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' '.'-'.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
      '; - - } - } - echo '
    • '; - - } else { - - $dj_str .= '
    • '; - if ( $show_avatar ) { - $dj_str .= ''.get_the_post_thumbnail( $dj->ID, 'thumbnail' ).''; - } - - $dj_str .= ''; - if ( $show_link ) { - $dj_str .= ''.$dj->post_title.''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $display_djs ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - $dj_str .= '
      With '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - if( ( ( $count == 1 ) && ( count($names) == 2) ) - || ( ( count($names) > 2 ) && ( $count == ( count($names) - 1 ) ) ) ) { - $dj_str .= ' and '; - } elseif ( ( $count < count($names) ) && ( count($names) > 2 ) ) { - $dj_str .= ', '; - } - } - $dj_str .= '
      '; - } - } - - $dj_str .= ''; - - if ( $show_sched ) { - $showtimes = explode( '|', $showtime ); - if ( $time == 12 ) { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('g:i a', $showtimes[0]).'-'.date('g:i a', $showtimes[1]).'
      '; - } else { - $dj_str .= ''.__(date('l', $showtimes[0]), 'radio-station').', '.date('H:i', $showtimes[0]).'-'.date('H:i', $showtimes[1]).'
      '; - } - } - - $dj_str .= '
    • '; - } - } - } else { - $dj_str .= '
    • '.__('None Upcoming', 'radio-station').'
    • '; - } - - $dj_str .= '
    '; - $dj_str .= '
    '; - - return $dj_str; - -} -add_shortcode( 'dj-coming-up-widget', 'radio_station_shortcode_coming_up' ); + '', + 'artist' => 1, + 'song' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, + ), + $atts, + 'now-playing' + ); + + $most_recent = radio_station_myplaylist_get_now_playing(); + $output = ''; + + if ( $most_recent ) { + $class = ''; + if ( isset( $most_recent['playlist_entry_new'] ) && 'on' === $most_recent['playlist_entry_new'] ) { + $class = 'new'; + } + + $output .= '
    '; + if ( ! empty( $atts['title'] ) ) { + $output .= '

    ' . $atts['title'] . '

    ';} + + if ( 1 === $atts['song'] ) { + $output .= '' . $most_recent['playlist_entry_song'] . ' '; + } + if ( 1 === $atts['artist'] ) { + $output .= '' . $most_recent['playlist_entry_artist'] . ' '; + } + if ( 1 === $atts['album'] ) { + $output .= '' . $most_recent['playlist_entry_album'] . ' '; + } + if ( 1 === $atts['label'] ) { + $output .= '' . $most_recent['playlist_entry_label'] . ' '; + } + if ( 1 === $atts['comments'] ) { + $output .= '' . $most_recent['playlist_entry_comments'] . ' '; + } + $output .= '' . __( 'View Playlist', 'radio-station' ) . ' '; + $output .= '
    '; + + } else { + echo 'No playlists available.'; + } + + return $output; +} +add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); + + +/* Shortcode to fetch all playlists for a given show id + * Since 2.0.0 + */ +function radio_station_shortcode_get_playlists_for_show( $atts ) { + + $atts = shortcode_atts( + array( + 'show' => '', + 'limit' => -1, + ), + $atts, + 'get-playlists' + ); + + // don't return anything if we do not have a show + if ( empty( $atts['show'] ) ) { + return false; + } + + $args = array( + 'posts_per_page' => $atts['limit'], + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + 'meta_key' => 'playlist_show_id', + 'meta_value' => $atts['show'], + ); + + $query = new WP_Query( $args ); + $playlists = $query->posts; + + $output = ''; + + $output .= ''; + + return $output; +} +add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); + +/* Shortcode for displaying a list of all shows + * Since 2.0.0 + */ +function radio_station_shortcode_list_shows( $atts ) { + + $atts = shortcode_atts( + array( + 'genre' => '', + ), + $atts, + 'list-shows' + ); + + // grab the published shows + $args = array( + 'posts_per_page' => 1000, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'show', + 'post_status' => 'publish', + 'meta_query' => array( + array( + 'key' => 'show_active', + 'value' => 'on', + ), + ), + ); + if ( ! empty( $atts['genre'] ) ) { + $args['tax_query'] = array( + array( + 'taxonomy' => 'genres', + 'field' => 'slug', + 'terms' => $atts['genre'], + ), + ); + } + + $query = new WP_Query( $args ); + + // if there are no shows saved, return nothing + if ( ! $query->have_posts() ) { + return false; + } + + $output = ''; + + $output .= '
    '; + $output .= '
      '; + while ( $query->have_posts() ) : + $query->the_post(); + $output .= '
    • '; + $output .= '' . get_the_title() . ''; + $output .= '
    • '; + endwhile; + $output .= '
    '; + $output .= '
    '; + wp_reset_postdata(); + return $output; +} +add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); + +/* Shortcode function for current DJ on-air + * Since 2.0.9 + */ +function radio_station_shortcode_dj_on_air( $atts ) { + $atts = shortcode_atts( + array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'default_name' => '', + 'time' => '12', + 'show_sched' => 1, + 'show_playlist' => 1, + 'show_all_sched' => 0, + 'show_desc' => 0, + ), + $atts, + 'dj-widget' + ); + + // find out which DJ(s) are currently scheduled to be on-air and display them + $djs = radio_station_dj_get_current(); + $playlist = radio_station_myplaylist_get_now_playing(); + + $dj_str = ''; + + $dj_str .= '
    '; + if ( ! empty( $atts['title'] ) ) { + $dj_str .= '

    ' . $atts['title'] . '

    '; + } + $dj_str .= '
      '; + + // echo the show/dj currently on-air + if ( 'override' === $djs['type'] ) { + + $dj_str .= '
    • '; + if ( $atts['show_avatar'] ) { + if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { + $dj_str .= '' . get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ) . ''; + } + } + + $dj_str .= $djs['all'][0]['title']; + + // display the override's schedule if requested + if ( $atts['show_sched'] ) { + + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' ' . $djs['all'][0]['sched']['start_meridian'] . '-' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . ' ' . $djs['all'][0]['sched']['end_meridian'] . '
      '; + } else { + $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour( $djs['all'][0]['sched'] ); + $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' -' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . '
      '; + } + + $dj_str .= '
    • '; + } + } else { + + if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { + foreach ( $djs['all'] as $dj ) { + + $dj_str .= '
    • '; + if ( $atts['show_avatar'] ) { + $dj_str .= '' . get_the_post_thumbnail( $dj->ID, 'thumbnail' ) . ''; + } + + $dj_str .= ''; + if ( $atts['show_link'] ) { + $dj_str .= '' . $dj->post_title . ''; + } else { + $dj_str .= $dj->post_title; + } + $dj_str .= ''; + + if ( $atts['display_djs'] ) { + + $names = get_post_meta( $dj->ID, 'show_user_list', true ); + $count = 0; + + if ( $names ) { + + $dj_str .= '
      ' . __( 'With', 'radio-station' ) . ' '; + foreach ( $names as $name ) { + $count++; + $user_info = get_userdata( $name ); + + $dj_str .= $user_info->display_name; + + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { + $dj_str .= ' and '; + } elseif ( $count < $count_names && $count_names > 2 ) { + $dj_str .= ', '; + } + } + $dj_str .= '
      '; + } + } + + if ( $atts['show_desc'] ) { + $desc_string = radio_station_shorten_string( wp_strip_all_tags( $dj->post_content ), 20 ); + $dj_str .= '' . $desc_string . ''; + } + + if ( $atts['show_playlist'] ) { + $dj_str .= '' . __( 'View Playlist', 'radio-station' ) . ''; + } + + $dj_str .= ''; + + if ( $atts['show_sched'] ) { + + $scheds = get_post_meta( $dj->ID, 'show_sched', true ); + + // if we only want the schedule that's relevant now to display... + if ( ! $atts['show_all_sched'] ) { + + $current_sched = radio_station_current_schedule( $scheds ); + + if ( $current_sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $current_sched['day'] ); + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' ' . $current_sched['start_meridian'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . ' ' . $current_sched['end_meridian'] . '
      '; + } else { + $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); + $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . '
      '; + } + } + } else { + + foreach ( $scheds as $sched ) { + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $sched['day'] ); + if ( 12 === (int) $atts['time'] ) { + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] . '
      '; + } else { + $sched = radio_station_convert_schedule_to_24hour( $sched ); + $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . '
      '; + } + } + } + } + + $dj_str .= '
    • '; + } + } else { + $dj_str .= '
    • ' . $atts['default_name'] . '
    • '; + } + } + + $dj_str .= '
    '; + $dj_str .= '
    '; + + return $dj_str; + +} +add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air' ); + +/* Shortcode for displaying upcoming DJs/shows + * Since 2.0.9 +*/ +function radio_station_shortcode_coming_up( $atts ) { + + $atts = shortcode_atts( + array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'limit' => 1, + 'time' => '12', + 'show_sched' => 1, + ), + $atts, + 'dj-coming-up-widget' + ); + + // find out which DJ(s) are coming up today + $djs = radio_station_dj_get_next( $atts['limit'] ); + if ( ! isset( $djs['all'] ) || count( $djs['all'] ) <= 0 ) { + $output = '
  • ' . __( 'None Upcoming', 'radio-station' ) . '
  • '; + return $output; + } + + ob_start(); + ?> +
    + +

    + +
      + $dj ) { + + if ( is_array( $dj ) && 'override' === $dj['type'] ) { + ?> +
    • + + + + + +
      + + + +
      + +
    • '; + +
    • + + + ID, 'thumbnail' ); ?> + + + + + + post_title ); ?> + + post_title ); + } + ?> + + ID, 'show_user_list', true ); + $count = 0; + + if ( $names ) { + ?> +
      With + display_name ); + + $count_names = count( $names ); + if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { + echo ' and '; + } elseif ( $count < $count_names && $count_names > 2 ) { + echo ', '; + } + } + ?> +
      + + + + + + , + + +
      + + + + , + + +
      + +
    • + +
    +
    + = $now ) ) { + $current = $sched; + } else { + continue; + } + } + + return $current; +} + +// --- convert shift times to 24-hour and timestamp formats for comparisons --- +function radio_station_convert_time( $time = array() ) { + + if ( empty( $time ) ) { + return false; + } + + $now = strtotime( current_time( 'mysql' ) ); + $cur_date = date( 'Y-m-d', $now ); + $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow + + // --- convert to 24 hour time --- + $time = radio_station_convert_schedule_to_24hour( $time ); + + // --- get a timestamp for the schedule start and end --- + $time['start_timestamp'] = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] ); + + if ( 'pm' === $time['start_meridian'] && 'am' === $time['end_meridian'] ) { + // check for shows that run overnight into the next morning + $time['end_timestamp'] = strtotime( $tom_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } else { + $time['end_timestamp'] = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] ); + } + + // a show cannot end before it begins... if it does, it ends the following day. + if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { + $time['end_timestamp'] = $time['end_timestamp'] + 86400; + } + + return $time; +} + +// --- convert a shift to 24 hour time for display --- +function radio_station_convert_schedule_to_24hour( $sched = array() ) { + + if ( empty( $sched ) ) { + return false; + } + + if ( 'pm' === $sched['start_meridian'] && 12 !== (int) $sched['start_hour'] ) { + $sched['start_hour'] = $sched['start_hour'] + 12; + } + if ( 'am' === $sched['start_meridian'] && $sched['start_hour'] < 10 ) { + $sched['start_hour'] = '0' . $sched['start_hour']; + } + if ( 'am' === $sched['start_meridian'] && 12 === (int) $sched['start_hour'] ) { + $sched['start_hour'] = '00'; + } + + if ( 'pm' === $sched['end_meridian'] && 12 !== (int) $sched['end_hour'] ) { + $sched['end_hour'] = $sched['end_hour'] + 12; + } + if ( 'am' === $sched['end_meridian'] && $sched['end_hour'] < 10 ) { + $sched['end_hour'] = '0' . $sched['end_hour']; + } + if ( 'am' === $sched['end_meridian'] && 12 === (int) $sched['end_hour'] ) { + $sched['end_hour'] = '00'; + } + + return $sched; +} + +// --- fetch the current DJ(s) on-air -- +function radio_station_dj_get_current() { + + // --- first check to see if there are any shift overrides --- + $check = radio_station_master_get_overrides( true ); + if ( $check ) { + $shows = array( + 'all' => $check, + 'type' => 'override', + ); + return $shows; + } + + global $wpdb; + + // --- get the current time and day --- + $now = strtotime( current_time( 'mysql' ) ); + $cur_day = date( 'l', $now ); + + // --- query for active shows only --- + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + foreach ( $show_shifts as $shift ) { + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + foreach ( $shift->meta_value as $time ) { + + // check if the shift is for the current day. If it's not, skip it + if ( $time['day'] === $cur_day ) { + // format the time so that it is more easily compared + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + + // we need to make a special allowance for shows that run from one day into the next + if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { + + $time = radio_station_convert_time( $time ); + // because station_convert_time assumes that the show STARTS on the current day, + // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps + $time['start_timestamp'] = $time['start_timestamp'] - 86400; + $time['end_timestamp'] = $time['end_timestamp'] - 86400; + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } + } + } + } + + $shows = array(); + foreach ( $show_ids as $id ) { + $shows['all'][] = get_post( $id ); + } + $shows['type'] = 'shows'; + + return $shows; +} + +// --- get the next DJ or DJs scheduled to be on air based on the current time --- +function radio_station_dj_get_next( $limit = 1 ) { + + global $wpdb; + + // get the various times/dates we need + $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); + $cur_day_num = date( 'N', strtotime( current_time( 'mysql' ) ) ); + $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); + $now = strtotime( current_time( 'mysql' ) ); + $shows = array(); + + // first check to see if there are any shift overrides + $check = radio_station_master_get_overrides(); + $overrides = array(); + + if ( $check ) { + + foreach ( $check as $i => $p ) { + + $p['sched'] = radio_station_convert_time( $p['sched'] ); + + // compare to the current timestamp + if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { + //show is on now, so we don't need it listed under upcoming + //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; + unset( $check[ $i ] ); + } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { + // show is on later today + $overrides[ $p['sched']['start_timestamp'] . '|' . $p['sched']['end_timestamp'] ] = $p; + } else { + // show is already over and we don't need it + unset( $check[ $i ] ); + } + } + + // sort the overrides by start time + ksort( $overrides ); + } + + // Fetch all schedules... we only want active shows + $show_shifts = $wpdb->get_results( + "SELECT meta.post_id, meta.meta_value + FROM {$wpdb->postmeta} AS meta + JOIN {$wpdb->postmeta} AS metab + ON meta.post_id = metab.post_id + JOIN {$wpdb->posts} as posts + ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND + posts.post_status = 'publish' AND + ( + metab.meta_key = 'show_active' AND + metab.meta_value = 'on' + )" + ); + + $show_ids = array(); + + foreach ( $show_shifts as $shift ) { + + $shift->meta_value = maybe_unserialize( $shift->meta_value ); + + // if a show has no shifts, unserialize() will return false instead of an empty array... + // fix that to prevent errors in the foreach loop. + if ( ! is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } + + $encore_ids = array(); + $days = array( + 'Sunday' => 7, + 'Monday' => 1, + 'Tuesday' => 2, + 'Wednesday' => 3, + 'Thursday' => 4, + 'Friday' => 5, + 'Saturday' => 6, + ); + foreach ( $shift->meta_value as $time ) { + + if ( $time['day'] === $cur_day ) { + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ); + } else { + if ( $cur_day_num < $days[ $time['day'] ] ) { + $day_diff = $days[ $time['day'] ] - $cur_day_num; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } else { + $day_diff = $cur_day_num + $days[ $time['day'] ] + 1; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } + } + + // if the shift occurs later than the current time, we want it + if ( $cur_shift >= $now ) { + $show_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + // 2.2.4: set encore ID array to pass back + if ( isset( $time['encore'] ) && 'on' === $time['encore'] ) { + $encore_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + } + } + } + } + + // sort the shows by start time + ksort( $show_ids ); + + // merge in the overrides array + foreach ( $show_ids as $s => $id ) { + foreach ( $overrides as $o => $info ) { + $stime = explode( '|', $s ); + $otime = explode( '|', $o ); + + if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends + if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) + unset( $show_ids[ $s ] ); // this show is overriden... drop it + } + } + } + } + + // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) + if ( ! function_exists( 'array_replace' ) ) { + + function array_replace() { + $array = array(); + $n = func_num_args(); + + while ( $n-- > 0 ) { + $array += func_get_arg( $n ); + } + return $array; + } + } + + $combined = array_replace( $show_ids, $overrides ); + ksort( $combined ); + + // grab the number of shows from the list the user wants to display + $combined = array_slice( $combined, 0, $limit, true ); + + // fetch detailed show information + foreach ( $combined as $timestamp => $id ) { + if ( ! is_array( $id ) ) { + $shows['all'][ $timestamp ] = get_post( $id ); + } else { + $id['type'] = 'override'; + $shows['all'][ $timestamp ] = $id; + } + } + $shows['type'] = 'shows'; + // 2.2.4: set encore IDs to pass back + $shows['encore'] = $encore_ids; + + // return the information + return $shows; +} + +// --- get the most recently entered song --- +function radio_station_myplaylist_get_now_playing() { + + // grab the most recent playlist + $args = array( + 'numberposts' => 1, + 'offset' => 0, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'playlist', + 'post_status' => 'publish', + ); + + $playlist = get_posts( $args ); + + // if there are no playlists saved, return nothing + if ( ! $playlist ) { + return false; + } + + // fetch the tracks for each playlist from the wp_postmeta table + $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); + + if ( ! empty( $songs[0] ) ) { + // removed any entries that are marked as 'queued' + foreach ( $songs[0] as $i => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[0][ $i ] ); + } + } + + // pop the last track off the list for display + $most_recent = array_pop( $songs[0] ); + + // get the permalink for the playlist so it can be displayed + $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); + + return $most_recent; + } else { + return false; + } +} + +// --- fetch all blog posts for a show's DJs --- +function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { + + global $wpdb; + + // do not return anything if we don't have a show + if ( ! $show_id ) { + return false; + } + + $fetch_posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = 'post_showblog_id' AND + meta.meta_value = %d", + $show_id + ) + ); + + $blog_array = array(); + $blogposts = array(); + foreach ( $fetch_posts as $f ) { + $blog_array[] = $f->post_id; + } + + if ( $blog_array ) { + + $blogposts = $wpdb->get_results( + $wpdb->prepare( + "SELECT posts.ID, posts.post_title + FROM {$wpdb->posts} AS posts + WHERE posts.ID IN(%s) AND + posts.post_status = 'publish' + ORDER BY posts.post_date DESC + LIMIT %d", + $blog_array, + $limit + ) + ); + } + + $output = ''; + + $output .= '
    '; + $output .= '

    ' . $title . '

    '; + $output .= ''; + $output .= '
    '; + + // if the blog archive page has been created, add a link to the archive for this show + $page = $wpdb->get_results( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = '_wp_page_template' AND + meta.meta_value = 'show-blog-archive-template.php' + LIMIT 1" + ); + + if ( $page ) { + $blog_archive = get_permalink( $page[0]->post_id ); + $params = array( 'show_id' => $show_id ); + $blog_archive = add_query_arg( $params, $blog_archive ); + + $output .= '' . __( 'More Blog Posts', 'radio-station' ) . ''; + } + + return $output; +} + +// --- get any schedule overrides for today's date --- +// If currenthour is true, only overrides that are in effect NOW will be returned +function radio_station_master_get_overrides( $currenthour = false ) { + + global $wpdb; + + $now = strtotime( current_time( 'mysql' ) ); + $date = date( 'Y-m-d', $now ); + $sql_date = $wpdb->esc_like( $date ); + $sql_date = '%' . $sql_date . '%'; + $show_shifts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta_key = 'show_override_sched' AND + meta_value LIKE %s", + $sql_date + ) + ); + + $scheds = array(); + if ( $show_shifts ) { + foreach ( $show_shifts as $shift ) { + + $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $time = $next_sched[0]; + + if ( $currenthour ) { + + // convert to 24 hour time + $check = array(); + $check = $time; + + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $title = get_the_title( $shift->post_id ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $time, + ); + } else { + continue; + } + } else { + $title = get_the_title( $shift->post_id ); + $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $scheds[] = array( + 'post_id' => $shift->post_id, + 'title' => $title, + 'sched' => $sched[0], + ); + } + } + } + + return $scheds; +} + +// --- shorten a string to a set number of words --- +function radio_station_shorten_string( $string, $limit ) { + + $shortened = $string; // just in case of a problem + + $array = explode( ' ', $string ); + if ( count( $array ) <= $limit ) { + // already at or under the limit + $shortened = $string; + } else { + array_splice( $array, $limit ); + $shortened = implode( ' ', $array ) . ' ...'; + } + return $shortened; +} + +// --- translate weekday --- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate weekdays +function radio_station_translate_weekday( $weekday, $short = false ) { + global $wp_locale; + if ( $short ) { + if ( 'Sun' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); + } elseif ( 'Mon' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); + } elseif ( 'Tue' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); + } elseif ( 'Wed' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); + } elseif ( 'Thu' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); + } elseif ( 'Fri' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); + } elseif ( 'Sat' === $weekday ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); + } + } else { + // 2.2.7: fix to typo for Tuesday + if ( 'Sunday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 0 ); + } elseif ( 'Monday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 1 ); + } elseif ( 'Tuesday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 2 ); + } elseif ( 'Wednesday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 3 ); + } elseif ( 'Thurday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 4 ); + } elseif ( 'Friday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 5 ); + } elseif ( 'Saturday' === $weekday ) { + $weekday = $wp_locale->get_weekday( 6 ); + } + } + return $weekday; +} + +// --- translate month --- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate months +function radio_station_translate_month( $month, $short = false ) { + global $wp_locale; + if ( $short ) { + if ( 'Jan' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); + } elseif ( 'Feb' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); + } elseif ( 'Mar' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); + } elseif ( 'Apr' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); + } elseif ( 'Jun' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); + } elseif ( 'Jul' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); + } elseif ( 'Aug' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); + } elseif ( 'Sep' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); + } elseif ( 'Oct' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); + } elseif ( 'Nov' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); + } elseif ( 'Dec' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); + } + } else { + if ( 'January' === $month ) { + $month = $wp_locale->get_month( 1 ); + } elseif ( 'February' === $month ) { + $month = $wp_locale->get_month( 2 ); + } elseif ( 'March' === $month ) { + $month = $wp_locale->get_month( 3 ); + } elseif ( 'April' === $month ) { + $month = $wp_locale->get_month( 4 ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month( 5 ); + } elseif ( 'June' === $month ) { + $month = $wp_locale->get_month( 6 ); + } elseif ( 'July' === $month ) { + $month = $wp_locale->get_month( 7 ); + } elseif ( 'August' === $month ) { + $month = $wp_locale->get_month( 8 ); + } elseif ( 'September' === $month ) { + $month = $wp_locale->get_month( 9 ); + } elseif ( 'October' === $month ) { + $month = $wp_locale->get_month( 10 ); + } elseif ( 'November' === $month ) { + $month = $wp_locale->get_month( 11 ); + } elseif ( 'December' === $month ) { + $month = $wp_locale->get_month( 12 ); + } + } + return $month; +} + +// ------------------ +// Translate Meridiem +// ------------------ +// 2.2.7: added meridiem translation function +function radio_station_translate_meridiem( $meridiem ) { + global $wp_locale; + return $wp_locale->get_meridiem( $meridiem ); +} diff --git a/includes/support_functions.php b/includes/support_functions.php deleted file mode 100644 index 6792144..0000000 --- a/includes/support_functions.php +++ /dev/null @@ -1,547 +0,0 @@ -= $now ) ) {$current = $sched;} - else {continue;} - } - - return $current; -} - -// --- convert shift times to 24-hour and timestamp formats for comparisons --- -function radio_station_convert_time( $time = array() ) { - - if ( empty( $time ) ) {return false;} - - $now = strtotime( current_time( 'mysql' ) ); - $curDate = date( 'Y-m-d', $now ); - $tomDate = date( 'Y-m-d', ( $now + 86400) ); // get the date for tomorrow - - // convert to 24 hour time - $time = radio_station_convert_schedule_to_24hour( $time ); - - // get a timestamp for the schedule start and end - $time['start_timestamp'] = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'] ); - - if ( ( $time['start_meridian'] == 'pm' ) && ( $time['end_meridian'] == 'am' ) ) { - // check for shows that run overnight into the next morning - $time['end_timestamp'] = strtotime( $tomDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } else { - $time['end_timestamp'] = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'] ); - } - - // a show cannot end before it begins... if it does, it ends the following day. - if ( $time['end_timestamp'] <= $time['start_timestamp'] ) { - $time['end_timestamp'] = $time['end_timestamp'] + 86400; - } - - return $time; -} - -// --- convert a shift to 24 hour time for display --- -function radio_station_convert_schedule_to_24hour( $sched = array() ) { - - if ( empty( $sched ) ) {return false;} - - if ( ( $sched['start_meridian'] == 'pm' ) && ( $sched['start_hour'] != 12 ) ) { - $sched['start_hour'] = $sched['start_hour'] + 12; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] < 10 ) ) { - $sched['start_hour'] = "0".$sched['start_hour']; - } - if ( ( $sched['start_meridian'] == 'am' ) && ( $sched['start_hour'] == 12 ) ) { - $sched['start_hour'] = '00'; - } - - if ( ( $sched['end_meridian'] == 'pm' ) && ( $sched['end_hour'] != 12 ) ) { - $sched['end_hour'] = $sched['end_hour'] + 12; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] < 10 ) ) { - $sched['end_hour'] = "0".$sched['end_hour']; - } - if ( ( $sched['end_meridian'] == 'am' ) && ( $sched['end_hour'] == 12 ) ) { - $sched['end_hour'] = '00'; - } - - return $sched; -} - -// --- fetch the current DJ(s) on-air -- -function radio_station_dj_get_current() { - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(true); - - if ( $check ) { - $shows = array( - 'all' => $check, - 'type' => 'override' - ); - - // at this point, we are done. Return the info. - return $shows; - } - - // load the info for the DJ - global $wpdb; - - // get the current time - $now = strtotime( current_time( 'mysql' ) ); - - $hour = date ('H', $now ); - $min = date( 'i', $now ); - $curDay = date( 'l', $now ); - $curDate = date( 'Y-m-d', $now ); - - // then check to see if a show is scheduled - // $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - // WHERE `meta_key` = 'show_sched';"); - - // we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - foreach ( $show_shifts as $shift ) { - $shift->meta_value = unserialize( $shift->meta_value ); - - // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - foreach ( $shift->meta_value as $time ) { - - // check if the shift is for the current day. If it's not, skip it - if ( $time['day'] == $curDay ) { - // format the time so that it is more easily compared - $time = radio_station_convert_time( $time ); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - // we need to make a special allowance for shows that run from one day into the next - if( date('w', strtotime($time['day']))+1 == date('w', strtotime($curDay)) ) { - - $time = radio_station_convert_time($time); - // because station_convert_time assumes that the show STARTS on the current day, - // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps - $time['start_timestamp'] = $time['start_timestamp'] - 86400; - $time['end_timestamp'] = $time['end_timestamp'] - 86400; - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; - } - } - - } - } - - $shows = array(); - foreach( $show_ids as $id ) { - $shows['all'][] = get_post($id); - } - $shows['type'] = 'shows'; - - return $shows; -} - -// --- get the next DJ or DJs scheduled to be on air based on the current time --- -function radio_station_dj_get_next($limit = 1) { - - // load the info for the DJ - global $wpdb; - - // get the various times/dates we need - $curDay = date( 'l', strtotime( current_time( 'mysql' ) ) ); - $curDayNum = date( 'N', strtotime(current_time( 'mysql') ) ); - $curDate = date( 'Y-m-d', strtotime(current_time( 'mysql') ) ); - $now = strtotime( current_time( 'mysql' ) ); - $tomorrow = date( "Y-m-d", (strtotime( $curDate ) + 86400) ); - $tomorrowDay = date( "l", (strtotime( $curDate ) + 86400) ); - $shows = array(); - - // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(); - $overrides = array(); - - if ( $check ) { - - foreach ( $check as $i => $p ) { - - $x = array(); - $x = $p['sched']; - - $p['sched'] = radio_station_convert_time( $p['sched'] ); - - - // compare to the current timestamp - if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { - //show is on now, so we don't need it listed under upcoming - //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - unset($check[$i]); - } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { - // show is on later today - $overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - } else { - // show is already over and we don't need it - unset($check[$i]); - } - - } - - // sort the overrides by start time - ksort( $overrides ); - } - - // Fetch all schedules... we only want active shows - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id`, `meta`.`meta_value` FROM ".$wpdb->prefix."postmeta AS `meta` - JOIN ".$wpdb->prefix."postmeta AS `metab` ON `meta`.`post_id` = `metab`.`post_id` - JOIN ".$wpdb->prefix."posts as `posts` ON `posts`.`ID` = `meta`.`post_id` - WHERE `meta`.`meta_key` = 'show_sched' - AND `posts`.`post_status` = 'publish' - AND ( `metab`.`meta_key` = 'show_active' AND `metab`.`meta_value` = 'on');"); - - $show_ids = array(); - - foreach ( $show_shifts as $shift ) { - - $shift->meta_value = unserialize( $shift->meta_value ); - //print_r($shift); - - // if a show has no shifts, unserialize() will return false instead of an empty array... - // fix that to prevent errors in the foreach loop. - if ( !is_array( $shift->meta_value ) ) {$shift->meta_value = array();} - - $encore_ids = array(); - $days = array('Sunday' => 7, 'Monday' => 1, 'Tuesday' => 2, 'Wednesday' => 3, 'Thursday' => 4, 'Friday' => 5, 'Saturday' => 6); - foreach ( $shift->meta_value as $time ) { - - if ( $time['day'] == $curDay ) { - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ); - } else { - if ( $curDayNum < $days[$time['day']] ) { - $day_diff = $days[$time['day']] - $curDayNum; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } else { - $day_diff = $curDayNum+$days[$time['day']] + 1; - $curShift = strtotime( $curDate.' '.$time['start_hour'].':'.$time['start_min'].':00 '.$time['start_meridian'] ) + ( $day_diff * 86400 ); - $endShift = strtotime( $curDate.' '.$time['end_hour'].':'.$time['end_min'].':00 '.$time['end_meridian'] ) + ( $day_diff * 86400 ); - } - } - - // if the shift occurs later than the current time, we want it - if ( $curShift >= $now ) { - $show_ids[$curShift.'|'.$endShift] = $shift->post_id; - // 2.2.4: set encore ID array to pass back - if ( isset( $time['encore'] ) && ( $time['encore'] == 'on' ) ) { - $encore_ids[$curShift.'|'.$endShift] = $shift->post_id; - } - } - - } - } - - // sort the shows by start time - ksort( $show_ids ); - - // merge in the overrides array - foreach ( $show_ids as $s => $id ) { - foreach ( $overrides as $o => $info ) { - $stime = explode( "|", $s ); - $otime = explode( "|", $o ); - - if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends - if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) - unset($show_ids[$s]); // this show is overriden... drop it - } - } - - } - } - - // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) - if ( !function_exists( 'array_replace' ) ) { - - function array_replace() { - $array = array(); - $n = func_num_args(); - - while ( $n-- >0 ) { - $array+=func_get_arg($n); - } - return $array; - } - } - - $combined = array_replace( $show_ids, $overrides ); - ksort( $combined ); - - // grab the number of shows from the list the user wants to display - $combined = array_slice( $combined, 0, $limit, true ); - - // fetch detailed show information - foreach ( $combined as $timestamp => $id ) { - if ( !is_array( $id ) ) { - $shows['all'][$timestamp] = get_post($id); - } else { - $id['type'] = 'override'; - $shows['all'][$timestamp] = $id; - } - } - $shows['type'] = 'shows'; - // 2.2.4: set encore IDs to pass back - $shows['encore'] = $encore_ids; - - // return the information - return $shows; -} - -// --- get the most recently entered song --- -function radio_station_myplaylist_get_now_playing() { - - // grab the most recent playlist - $args = array( - 'numberposts' => 1, - 'offset' => 0, - 'orderby' => 'post_date', - 'order' => 'DESC', - 'post_type' => 'playlist', - 'post_status' => 'publish' - ); - - $playlist = get_posts( $args ); - - // if there are no playlists saved, return nothing - if ( !$playlist ) {return false;} - - // fetch the tracks for each playlist from the wp_postmeta table - $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); - - //print_r($songs);die(); - - if ( !empty( $songs[0] ) ) { - // removed any entries that are marked as 'queued' - foreach ( $songs[0] as $i => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) { - unset( $songs[0][$i] ); - } - } - - // pop the last track off the list for display - $most_recent = array_pop( $songs[0] ); - - // get the permalink for the playlist so it can be displayed - $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); - - return $most_recent; - } else { - return false; - } -} - -// --- fetch all blog posts for a show's DJs --- -function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { - - global $wpdb; - - // do not return anything if we don't have a show - if ( !$show_id ) {return false;} - - $fetch_posts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = 'post_showblog_id' AND `meta`.`meta_value` = ".$show_id.";"); - - $blog_array = array(); - $blogposts = array(); - foreach ( $fetch_posts as $f ) {$blog_array[] = $f->post_id;} - - if ( $blog_array ) { - $blog_array = implode( ',', $blog_array ); - - $blogposts = $wpdb->get_results("SELECT `posts`.`ID`, `posts`.`post_title` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`ID` IN(".$blog_array.") - AND `posts`.`post_status` = 'publish' - ORDER BY `posts`.`post_date` DESC - LIMIT ".$limit.";"); - } - - $output = ''; - - $output .= '
    '; - $output .= '

    '.$title.'

    '; - $output .= ''; - $output .= '
    '; - - // if the blog archive page has been created, add a link to the archive for this show - $page = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta`.`meta_key` = '_wp_page_template' - AND `meta`.`meta_value` = 'show-blog-archive-template.php' - LIMIT 1;"); - - if ( $page ) { - $blog_archive = get_permalink($page[0]->post_id); - $params = array( 'show_id' => $show_id ); - $blog_archive = add_query_arg( $params, $blog_archive ); - - $output .= ''.__('More Blog Posts', 'radio-station').''; - } - - return $output; -} - -// --- get any schedule overrides for today's date --- -// If currenthour is true, only overrides that are in effect NOW will be returned -function radio_station_master_get_overrides( $currenthour = false ) { - - global $wpdb; - - $now = strtotime( current_time( 'mysql' ) ); - $date = date( 'Y-m-d', $now ); - - $show_shifts = $wpdb->get_results("SELECT `meta`.`post_id` FROM ".$wpdb->prefix."postmeta AS `meta` - WHERE `meta_key` = 'show_override_sched' - AND `meta_value` LIKE '%".$date."%';"); - - $scheds = array(); - if ( $show_shifts ) { - foreach ( $show_shifts as $shift ) { - - $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false); - $time = $next_sched[0]; - - if ( $currenthour ) { - - // convert to 24 hour time - $check = array(); - $check = $time; - - $time = radio_station_convert_time($time); - - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $title = get_the_title( $shift->post_id ); - $scheds[] = array('post_id' => $shift->post_id, 'title' => $title, 'sched' => $time); - } else {continue;} - - } else { - $title = get_the_title( $shift->post_id ); - $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); - $scheds[] = array( 'post_id' => $shift->post_id, 'title' => $title, 'sched' => $sched[0] ); - } - } - } - - return $scheds; -} - -// --- shorten a string to a set number of words --- -function radio_station_shorten_string($string, $limit) { - - $shortened = $string; // just in case of a problem - - $array = explode( ' ', $string ); - if ( count( $array ) <= $limit ) { - // already at or under the limit - $shortened = $string; - } else { - array_splice( $array, $limit ); - $shortened = implode( ' ', $array )." ..."; - } - return $shortened; -} - -// --- translate weekday --- -// translated individually as cannot translate a variable -function radio_station_translate_weekday( $weekday, $short = false ) { - if ( $short ) { - if ( $weekday == 'Sun' ) {$translated = __( 'Sun', 'radio-station' );} - elseif ( $weekday == 'Mon' ) {$translated = __( 'Mon', 'radio-station' );} - elseif ( $weekday == 'Tue' ) {$translated = __( 'Tue', 'radio-station' );} - elseif ( $weekday == 'Wed' ) {$translated = __( 'Wed', 'radio-station' );} - elseif ( $weekday == 'Thu' ) {$translated = __( 'Thu', 'radio-station' );} - elseif ( $weekday == 'Fri' ) {$translated = __( 'Fri', 'radio-station' );} - elseif ( $weekday == 'Sat' ) {$translated = __( 'Sat', 'radio-station' );} - } else { - if ( $weekday == 'Sunday' ) {$translated = __( 'Sunday', 'radio-station' );} - elseif ( $weekday == 'Monday' ) {$translated = __( 'Monday', 'radio-station' );} - elseif ( $weekday == 'Tueday' ) {$translated = __( 'Tueday', 'radio-station' );} - elseif ( $weekday == 'Wednesday' ) {$translated = __( 'Wednesday', 'radio-station' );} - elseif ( $weekday == 'Thurday' ) {$translated = __( 'Thurday', 'radio-station' );} - elseif ( $weekday == 'Friday' ) {$translated = __( 'Friday', 'radio-station' );} - elseif ( $weekday == 'Saturday' ) {$translated = __( 'Saturday', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $weekday;} -} - -// --- translate month --- -function radio_station_translate_month( $month, $short = false ) { - if ( $short ) { - if ( $month == 'Jan' ) {$translated = __( 'Jan', 'radio-station' );} - elseif ( $month == 'Feb' ) {$translated = __( 'Feb', 'radio-station' );} - elseif ( $month == 'Mar' ) {$translated = __( 'Mar', 'radio-station' );} - elseif ( $month == 'Apr' ) {$translated = __( 'Apr', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'Jun' ) {$translated = __( 'Jun', 'radio-station' );} - elseif ( $month == 'Jul' ) {$translated = __( 'Jul', 'radio-station' );} - elseif ( $month == 'Aug' ) {$translated = __( 'Aug', 'radio-station' );} - elseif ( $month == 'Sep' ) {$translated = __( 'Sep', 'radio-station' );} - elseif ( $month == 'Oct' ) {$translated = __( 'Oct', 'radio-station' );} - elseif ( $month == 'Nov' ) {$translated = __( 'Nov', 'radio-station' );} - elseif ( $month == 'Dec' ) {$translated = __( 'Dec', 'radio-station' );} - } else { - if ( $month == 'January' ) {$translated = __( 'January', 'radio-station' );} - elseif ( $month == 'February' ) {$translated = __( 'February', 'radio-station' );} - elseif ( $month == 'March' ) {$translated = __( 'March', 'radio-station' );} - elseif ( $month == 'April' ) {$translated = __( 'April', 'radio-station' );} - elseif ( $month == 'May' ) {$translated = __( 'May', 'radio-station' );} - elseif ( $month == 'June' ) {$translated = __( 'June', 'radio-station' );} - elseif ( $month == 'July' ) {$translated = __( 'July', 'radio-station' );} - elseif ( $month == 'August' ) {$translated = __( 'August', 'radio-station' );} - elseif ( $month == 'September' ) {$translated = __( 'September', 'radio-station' );} - elseif ( $month == 'October' ) {$translated = __( 'October', 'radio-station' );} - elseif ( $month == 'November' ) {$translated = __( 'November', 'radio-station' );} - elseif ( $month == 'December' ) {$translated = __( 'December', 'radio-station' );} - } - if ( isset( $translated ) ) {return $translated;} - else {return $month;} -} \ No newline at end of file diff --git a/includes/widget_djcomingup.php b/includes/widget_djcomingup.php deleted file mode 100644 index 92d14af..0000000 --- a/includes/widget_djcomingup.php +++ /dev/null @@ -1,354 +0,0 @@ - 'DJ_Upcoming_Widget', 'description' => __('The upcoming DJs/Shows.', 'radio-station') ); - $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); - parent::__construct('DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time']: 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    - -

    - -

    - - -

    - -

    -
    - -

    - - -
    - -
      - - 0 ) ) { - - foreach ( $djs['all'] as $showtime => $dj ) { - - if ( is_array($dj) && $dj['type'] == 'override' ) { - - echo '
    • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
      '.$dj['title'].'
      '; - } - - // --- show avatar --- - if ( $djavatar ) { - if ( has_post_thumbnail($dj['post_id'] ) ) { - echo '
      '; - echo get_the_post_thumbnail( $dj['post_id'], 'thumbnail' ).'
      '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
      '.$dj['title'].'
      '; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $dj['sched']['start_hour']; - if ( substr($dj['sched']['start_hour'], 0, 1) === '0' ) { - $start_hour = substr($dj['sched']['start_hour'], 1); - } - - $end_hour = $dj['sched']['end_hour']; - if ( substr($dj['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($dj['sched']['end_hour'], 1); - } - - echo '
      '.$start_hour.':'.$dj['sched']['start_min'].' '.$dj['sched']['start_meridian'].' - '.$end_hour.':'.$dj['sched']['end_min'].' '.$dj['sched']['end_meridian'].'
      '; - } else { - echo '
      '.$dj['sched']['start_hour'].':'.$dj['sched']['start_min'].' - '.$dj['sched']['end_hour'].':'.$dj['sched']['end_min'].'
      '; - } - } - echo '
    • '; - - } else { - - echo '
    • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
      '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( array_key_exists( $showtime, $djs['encore'] ) ) { - echo '
      '.__('Encore Presentation','radio-station').'
      '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - echo '
      '.__( 'with', 'radio-station' ).' '; - foreach ( $ids as $id ) { - $count++; - $user_info = get_userdata( $id ); - - if ( $link_djs ) { - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - } else {echo $user_info->display_name;} - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station' ).' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
      '; - } - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - $showtimes = explode( "|", $showtime ); - // 2.2.2: fix to weekday value to be translated - $weekday = radio_station_translate_weekday( date('l', $showtimes[0] ) ); - - if ( $time == 12 ) { - echo '
      '.$weekday.', '.date( 'g:i a', $showtimes[0] ).' - '.date( 'g:i a', $showtimes[1] ).'
      '; - } else { - echo '
      '.$weekday.', '.date( 'H:i', $showtimes[0] ).' - '.date( 'H:i', $showtimes[1] ).'
      '; - } - - } - - echo '
    • '; - } - } - } else { - if ( $default != '' ) { - echo '
    • '.$default.'
    • '; - } - } - ?> -
    -
    - 'DJ_Widget', 'description' => __('The current on-air DJ.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Show/DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist']: false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $links_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    -
    - -

    - -
    - - -
      - '; - - // --- show title *for above only) --- - if ( $position == 'above' ) { - echo '
      '.$djs['all'][0]['title'].'
      '; - } - - if ( $djavatar ) { - if ( has_post_thumbnail($djs['all'][0]['post_id']) ) { - echo '
      '; - echo get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ).'
      '; - } - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
      '.$djs['all'][0]['title'].'
      '; - } - - echo ''; - - // --- display the schedule override if requested --- - if ( $show_sched ) { - - if ( $time == 12 ) { - $start_hour = $djs['all'][0]['sched']['start_hour']; - if ( substr( $djs['all'][0]['sched']['start_hour'], 0, 1 ) === '0' ) { - $start_hour = substr($djs['all'][0]['sched']['start_hour'], 1); - } - - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $djs['all'][0]['sched']['end_hour'], 0, 1) === '0' ) { - $end_hour = substr($djs['all'][0]['sched']['end_hour'], 1); - } - - echo '
      '.$start_hour.':'.$djs['all'][0]['sched']['start_min'].' '.$djs['all'][0]['sched']['start_meridian'].' - '.$end_hour.':'.$djs['all'][0]['sched']['end_min'].' '.$djs['all'][0]['sched']['end_meridian'].'
      '; - - } else { - - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour($djs['all'][0]['sched']); - echo '
      '.$djs['all'][0]['sched']['start_hour'].':'.$djs['all'][0]['sched']['start_min'].' '.'-'.$djs['all'][0]['sched']['end_hour'].':'.$djs['all'][0]['sched']['end_min'].'
      '; - - } - } - - echo ''; - - } else { - - if ( isset( $djs['all'] ) && ( count($djs['all']) > 0 ) ) { - foreach( $djs['all'] as $dj ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - $current_sched = radio_station_current_schedule( $scheds ); - - echo '
    • '; - - // --- show title (for above only) --- - if ( $position == 'above' ) { - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; - } - - // --- show avatar --- - if ( $djavatar ) { - echo '
      '; - echo get_the_post_thumbnail( $dj->ID, 'thumbnail' ).'
      '; - } - - // --- show title --- - if ( $position != 'above' ) { - echo '
      '; - if ( $link ) {echo ''.$dj->post_title.'';} - else {echo $dj->post_title;} - echo '
      '; - } - - echo ''; - - // --- encore presentation --- - // 2.2.4: added encore presentation display - if ( $current_sched['encore'] == 'on' ) { - echo '
      '.__('Encore Presentation','radio-station').'
      '; - } - - // --- DJ names --- - if ( $display_djs ) { - - $ids = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - - echo '
      '.__( 'with', 'radio-station' ).' '; - foreach( $ids as $id ) { - $count++; - $user_info = get_userdata($id); - - $dj_link = get_author_posts_url( $user_info->ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - echo ''.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($ids) == 2 ) ) - || ( ( count($ids) > 2 ) && ( $count == ( count($ids) - 1 ) ) ) ) { - echo ' '.__( 'and', 'radio-station').' '; - } elseif ( ( $count < count($ids) ) && ( count($ids) > 2 ) ) { - echo ', '; - } - } - echo '
      '; - } - } - - // --- show description --- - if ( $show_desc ) { - $desc_string = radio_station_shorten_string( strip_tags( $dj->post_content ), 20 ); - $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); - echo '
      '.$desc_string.'
      '; - } - - // --- playlist link --- - if ( $show_playlist ) { - echo ''; - } - - echo ''; - - // --- show schedule --- - if ( $show_sched ) { - - // --- if we only want the schedule that's relevant now to display --- - if ( !$show_all_sched ) { - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( $time == 12 ) { - echo '
      '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' '.$current_sched['start_meridian'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].' '.$current_sched['end_meridian'].'
      '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - echo '
      '.$display_day.', '.$current_sched['start_hour'].':'.$current_sched['start_min'].' - '.$current_sched['end_hour'].':'.$current_sched['end_min'].'
      '; - } - } - - } else { - - foreach ( $scheds as $sched ) { - - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( $time == 12 ) { - echo '
      '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' '.$sched['start_meridian'].' - '.$sched['end_hour'].':'.$sched['end_min'].' '.$sched['end_meridian'].'
      '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - echo '
      '.$display_day.', '.$sched['start_hour'].':'.$sched['start_min'].' - '.$sched['end_hour'].':'.$sched['end_min'].'
      '; - } - } - } - } - - echo '
    • '; - - } - } else { - echo '
    • '.$default.'
    • '; - } - } - - ?> -
    -
    - 'Playlist_Widget', 'description' => __('Display the current song.', 'radio-station') ); - $widget_display_name = __('(Radio Station) Now Playing', 'radio-station' ); - parent::__construct('Playlist_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - function form( $instance ) { - - $instance = wp_parse_args((array) $instance, array( 'title' => '' )); - $title = $instance['title']; - $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; - $song = isset( $instance['song'] ) ? $instance['song'] : true; - $album = isset( $instance['album'] ) ? $instance['album'] : false; - $label = isset( $instance['label'] ) ? $instance['label'] : false; - $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - "; - - ?> -
    - '; - - // 2.2.3: convert span tags to div tags - // 2.2.4: check value keys are set before outputting - if ( $song && isset( $most_recent['playlist_entry_song']) ) { - echo '
    '.$most_recent['playlist_entry_song'].'
    '; - } - - if ( $artist && isset( $most_recent['playlist_entry_artist'] ) ) { - echo '
    '.$most_recent['playlist_entry_artist'].'
    '; - } - - if ( $album && isset( $most_recent['playlist_entry_album'] ) ) { - echo '
    '.$most_recent['playlist_entry_album'].'
    '; - } - - if ( $label && isset( $most_recent['playlist_entry_label'] ) ) { - echo '
    '.$most_recent['playlist_entry_label'].'
    '; - } - - if ( $comments && isset( $most_recent['playlist_entry_comments'] ) ) { - echo '
    '.$most_recent['playlist_entry_comments'].'
    '; - } - - if ( isset( $most_recent['playlist_permalink'] ) ) { - echo ''; - } - - echo '
    '; - - } else { - // 2.2.3: added missing translation wrapper - echo '
    '.__('No playlists available.','radio-station').'
    '; - } - - ?> - - -
    - {t*h*YX9t6{2~s5pU)4V=f3xl z+Lx9zgNV5o-aBcMdvfz5O?p!RroI@Q0aXBH4$=KRPAMVVWdyO~*Hoy6`M?CHm|>1P z4htOS%mAqmL+A>t5?y(UiWt%H+o0jMRew!vlg z0X8S{8hcHg+EjYF?!ov}<68{l(z!j7-KnwH#HmY%ap^Gr$i{al#vYw{k=-38cB-=y zPy!(V>D>=7Wye0i=tuu9pB~xI_qnZx2TWSyNxwIq^q3)EvU84;Pa9my=JFJ{ysx+% zZmea1A-ZUwnO2cG%Iy`@WnQ-O8uG12msNFA>-xy;71U*^zb#SU%682ko&G5{+ zE^w7rm$1+271Sl{xAKzftG)iZ&*>G^W!b+jv9D~nPt?GC%vbJLLVVeQ7lwusC@_It zckWf`|C{UI{}-5$p1KnF{{&2_(P`9qp1!vZ&RBZwh~t(+LiS6X+7wQ<9jDrMocjHT eA(tcWDmk&|P>e*HxNe?-^=>!bt literal 0 HcmV?d00001 diff --git a/languages/radio-station-nl_NL.mo b/languages/radio-station-nl_NL.mo new file mode 100644 index 0000000000000000000000000000000000000000..551491cae31344a8120011550dfcccbe6ff1d14a GIT binary patch literal 7610 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 Zstl%#cqVlTWw!nm4SD-wV)d`l{{WX3Uoijx literal 0 HcmV?d00001 diff --git a/languages/radio-station-nl_NL.po b/languages/radio-station-nl_NL.po new file mode 100644 index 0000000..1452620 --- /dev/null +++ b/languages/radio-station-nl_NL.po @@ -0,0 +1,548 @@ +msgid "" +msgstr "" +"Project-Id-Version: radio station\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 08:27-0600\n" +"PO-Revision-Date: 2018-05-10 10:18+0200\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-KeywordsList: _e;__;esc_attr_e\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Generator: Poedit 2.0.7\n" +"X-Poedit-Basepath: c:/xampp/htdocs/wp352test/wp-content/plugins/radio-" +"station\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: nl\n" +"X-Poedit-SearchPath-0: includes\n" +"X-Poedit-SearchPath-1: templates\n" +"X-Poedit-SearchPath-2: .\n" + +#: includes/dj-on-air.php:90 includes/dj-on-air.php:664 +#: includes/playlist.php:22 includes/playlist.php:359 includes/playlist.php:546 +msgid "View Playlist" +msgstr "Bekijk de playlist" + +#: includes/dj-on-air.php:480 +msgid "None Upcoming" +msgstr "Geen Volgende" + +#: includes/dj-on-air.php:495 +msgid "The current on-air DJ." +msgstr "De huidige on-air DJ." + +#: includes/dj-on-air.php:496 +msgid "Radio Station: Show/DJ On-Air" +msgstr "" + +#: includes/dj-on-air.php:511 includes/dj-on-air.php:738 +#: includes/playlist.php:436 +msgid "Title" +msgstr "Titel" + +#: includes/dj-on-air.php:519 includes/dj-on-air.php:746 +msgid "Show Avatars" +msgstr "Laat avatar zien" + +#: includes/dj-on-air.php:526 +msgid "Link to the Show/DJ's profile" +msgstr "Link naar de Show/DJ's profiel" + +#: includes/dj-on-air.php:533 includes/dj-on-air.php:767 +msgid "Display schedule info for this show" +msgstr "Laat de geschedulde informatie van de show zien" + +#: includes/dj-on-air.php:540 +msgid "Display link to show's playlist" +msgstr "Laat de link van de playlist van de show zien" + +#: includes/dj-on-air.php:545 +msgid "Default DJ Name" +msgstr "Standaard DJ naam" + +#: includes/dj-on-air.php:548 includes/dj-on-air.php:761 +msgid "" +"If no Show/DJ is scheduled for the current hour, display this name/text." +msgstr "" +"ALs er geen show is gescheduled voor dit uur, laat deze naam/tekst zien." + +#: includes/dj-on-air.php:552 includes/dj-on-air.php:779 +msgid "Time Format" +msgstr "Tijdsformaat" + +#: includes/dj-on-air.php:554 includes/dj-on-air.php:781 +msgid "12-hour" +msgstr "12-uur" + +#: includes/dj-on-air.php:555 includes/dj-on-air.php:782 +msgid "24-hour" +msgstr "24-uur" + +#: includes/dj-on-air.php:558 +msgid "Choose time format for displayed schedules" +msgstr "Kies het tijdsformaat voor de getoonde schedules" + +#: includes/dj-on-air.php:722 +msgid "The upcoming DJs/Shows." +msgstr "De volgende DJ's/Shows" + +#: includes/dj-on-air.php:723 +msgid "Radio Station: Upcoming DJ On-Air" +msgstr "Radio Station: De volgende DJ On-Air" + +#: includes/dj-on-air.php:753 +msgid "Link to Show/DJ's user profile" +msgstr "Link naar Show/DJ's gebruikers profiel" + +#: includes/dj-on-air.php:758 +msgid "No Additional Schedules" +msgstr "Geen Extra Schedules" + +#: includes/dj-on-air.php:772 +msgid "Limit" +msgstr "Limiet" + +#: includes/dj-on-air.php:775 +msgid "Number of upcoming DJs/Shows to display." +msgstr "Aantal volgende DJ's/Shows om te tonen" + +#: includes/dj-on-air.php:785 +msgid "Choose time format for displayed schedules." +msgstr "Kies het tijdsformaat voor de getoonde schedules" + +#: includes/master_schedule.php:21 includes/master_schedule.php:22 +msgid "Schedule Override" +msgstr "Schedule Override" + +#: includes/master_schedule.php:23 includes/master_schedule.php:24 +msgid "Add Schedule Override" +msgstr "Maak Schedule Override" + +#: includes/master_schedule.php:25 +msgid "Edit Schedule Override" +msgstr "Edit Schedule Override" + +#: includes/master_schedule.php:26 +msgid "New Schedule Override" +msgstr "Nieuwe Schedule Override" + +#: includes/master_schedule.php:27 +msgid "View Schedule Override" +msgstr "Bekijk Schedule Override" + +#: includes/master_schedule.php:30 +msgid "Post type for Schedule Override" +msgstr "Post het type voorde Schedule Override" + +#: includes/master_schedule.php:49 +msgid "Override Schedule" +msgstr "Override Schedule" + +#: includes/master_schedule.php:79 +msgid "Date" +msgstr "Datum" + +#: includes/master_schedule.php:82 includes/playlist.php:756 +#: includes/playlist.php:835 +msgid "Start Time" +msgstr "Start tijd" + +#: includes/master_schedule.php:108 includes/playlist.php:782 +#: includes/playlist.php:861 +msgid "End Time" +msgstr "Eind tijd" + +#: includes/master_schedule.php:432 includes/master_schedule.php:595 +msgid "encore airing" +msgstr "Nogmaals On Air" + +#: includes/master_schedule.php:437 includes/master_schedule.php:600 +msgid "Audio File" +msgstr "Audio File" + +#: includes/master_schedule.php:454 +msgid "Sun" +msgstr "Zon" + +#: includes/master_schedule.php:454 +msgid "Mon" +msgstr "Maan" + +#: includes/master_schedule.php:454 +msgid "Tue" +msgstr "Dins" + +#: includes/master_schedule.php:454 +msgid "Wed" +msgstr "Woe" + +#: includes/master_schedule.php:454 +msgid "Thu" +msgstr "Don" + +#: includes/master_schedule.php:454 +msgid "Fri" +msgstr "Vrij" + +#: includes/master_schedule.php:454 +msgid "Sat" +msgstr "Zat" + +#: includes/master_schedule.php:619 includes/playlist.php:572 +msgid "Genres" +msgstr "Genres" + +#: includes/playlist.php:16 templates/single-show.php:136 +msgid "Playlists" +msgstr "Playlists" + +#: includes/playlist.php:17 +msgid "Playlist" +msgstr "Playlist" + +#: includes/playlist.php:18 includes/playlist.php:19 +msgid "Add Playlist" +msgstr "Maak Playlist" + +#: includes/playlist.php:20 +msgid "Edit Playlist" +msgstr "Edit Playlist" + +#: includes/playlist.php:21 +msgid "New Playlist" +msgstr "Nieuwe Playlist" + +#: includes/playlist.php:25 +msgid "Post type for Playlist descriptions" +msgstr "Post type voor Playlist omschrijving" + +#: includes/playlist.php:42 +msgid "Shows" +msgstr "Shows" + +#: includes/playlist.php:43 includes/playlist.php:191 +msgid "Show" +msgstr "Show" + +#: includes/playlist.php:44 includes/playlist.php:45 +msgid "Add Show" +msgstr "Maak Show Aan" + +#: includes/playlist.php:46 +msgid "Edit Show" +msgstr "Edit Show" + +#: includes/playlist.php:47 +msgid "New Show" +msgstr "Nieuwe Show" + +#: includes/playlist.php:48 +msgid "View Show" +msgstr "Bekijk Show" + +#: includes/playlist.php:51 +msgid "Post type for Show descriptions" +msgstr "Post type voor Show omschrijving" + +#: includes/playlist.php:71 +msgid "Playlist Entries" +msgstr "Playlist Entries" + +#: includes/playlist.php:93 templates/single-playlist.php:43 +msgid "Artist" +msgstr "Artiest" + +#: includes/playlist.php:93 templates/single-playlist.php:44 +msgid "Song" +msgstr "Nummer" + +#: includes/playlist.php:93 templates/single-playlist.php:45 +msgid "Album" +msgstr "Album" + +#: includes/playlist.php:93 templates/single-playlist.php:46 +msgid "Record Label" +msgstr "Platenlabel" + +#: includes/playlist.php:93 templates/single-playlist.php:47 +msgid "DJ Comments" +msgstr "DJ commentaar" + +#: includes/playlist.php:93 +msgid "New" +msgstr "Nieuw" + +#: includes/playlist.php:93 +msgid "Status" +msgstr "Status" + +#: includes/playlist.php:93 includes/playlist.php:127 includes/playlist.php:144 +#: includes/playlist.php:807 includes/playlist.php:888 +msgid "Remove" +msgstr "Verwijder" + +#: includes/playlist.php:119 includes/playlist.php:144 +msgid "Queued" +msgstr "Queued" + +#: includes/playlist.php:123 includes/playlist.php:144 +msgid "Played" +msgstr "Gespeeld" + +#: includes/playlist.php:136 +msgid "Add Entry" +msgstr "Maak Entry Aan" + +#: includes/playlist.php:163 includes/playlist.php:164 +#: templates/single-show.php:99 +msgid "Schedule" +msgstr "Uitzenddag" + +#: includes/playlist.php:166 includes/playlist.php:167 +msgid "Publish" +msgstr "Publish" + +#: includes/playlist.php:170 +msgid "Submit for Review" +msgstr "Submit voor Review" + +#: includes/playlist.php:171 includes/playlist.php:176 +msgid "Update Playlist" +msgstr "Update Playlist" + +#: includes/playlist.php:175 +msgid "Update" +msgstr "Update" + +#: includes/playlist.php:420 +msgid "Display the current song." +msgstr "Laat de huidige song zien" + +#: includes/playlist.php:421 +msgid "Radio Station: Now Playing" +msgstr "Radio Station: Now Playing" + +#: includes/playlist.php:444 +msgid "Show Artist Name" +msgstr "Laat Artiest Naam Zien" + +#: includes/playlist.php:451 +msgid "Show Song Title" +msgstr "Laat Titel Zien" + +#: includes/playlist.php:458 +msgid "Show Album Name" +msgstr "Laat Album Zien" + +#: includes/playlist.php:465 +msgid "Show Record Label Name" +msgstr "Laat Record Label Zien" + +#: includes/playlist.php:472 +msgid "Show DJ Comments" +msgstr "Show DJ Commentaar" + +#: includes/playlist.php:573 templates/single-show.php:60 +msgid "Genre" +msgstr "Genre" + +#: includes/playlist.php:594 +msgid "Information" +msgstr "Informatie" + +#: includes/playlist.php:612 +msgid "Active" +msgstr "Actief" + +#: includes/playlist.php:614 +msgid "" +"Check this box if show is currently active (Show will not appear on " +"programming schedule if unchecked)" +msgstr "" +"Vink deze box aan als de huidige show actief is (De show zal niet " +"verschijnenin de programmering indien niet aangevinkt)" + +#: includes/playlist.php:616 +msgid "Current Audio File" +msgstr "Huidige Audio File" + +#: includes/playlist.php:619 +msgid "DJ Email" +msgstr "DJ Email" + +#: includes/playlist.php:622 +msgid "Website Link" +msgstr "Website Link" + +#: includes/playlist.php:633 +msgid "DJs" +msgstr "DJs" + +#: includes/playlist.php:719 +msgid "Schedules" +msgstr "Schedules" + +#: includes/playlist.php:744 includes/playlist.php:824 +msgid "Day" +msgstr "Dag" + +#: includes/playlist.php:747 includes/playlist.php:827 +msgid "Monday" +msgstr "Maandag" + +#: includes/playlist.php:748 includes/playlist.php:828 +msgid "Tuesday" +msgstr "Dinsdag" + +#: includes/playlist.php:749 includes/playlist.php:829 +msgid "Wednesday" +msgstr "Woensdag" + +#: includes/playlist.php:750 includes/playlist.php:830 +msgid "Thursday" +msgstr "Donderdag" + +#: includes/playlist.php:751 includes/playlist.php:831 +msgid "Friday" +msgstr "Vrijdag" + +#: includes/playlist.php:752 includes/playlist.php:832 +msgid "Saturday" +msgstr "Zaterdag" + +#: includes/playlist.php:753 includes/playlist.php:833 +msgid "Sunday" +msgstr "Zondag" + +#: includes/playlist.php:806 includes/playlist.php:886 +msgid "Encore Presentation" +msgstr "Nogmaals Gepresenteerd" + +#: includes/playlist.php:817 +msgid "Add Shift" +msgstr "Voeg Shift Toe" + +#: includes/playlist.php:980 +msgid "More Playlists" +msgstr "Meer Playlists" + +#: includes/playlist.php:1041 +msgid "More Blog Posts" +msgstr "Meer Blog Posts" + +#: templates/archive-playlist.php:15 +msgid "Playlist Archive for" +msgstr "Playlist Archief voor" + +#: templates/archive-playlist.php:50 templates/single-playlist.php:13 +#: templates/single-show.php:13 +msgid "Post navigation" +msgstr "Post Navigatie" + +#: templates/archive-playlist.php:51 +msgid "Older posts" +msgstr "Oudere posts" + +#: templates/archive-playlist.php:52 +msgid "Newer posts" +msgstr "Nieuwe posts" + +#: templates/archive-playlist.php:61 templates/author.php:93 +#: templates/playlist-archive-template.php:63 +#: templates/show-blog-archive-template.php:71 +msgid "Nothing Found" +msgstr "Niets Gevonden" + +#: templates/archive-playlist.php:65 templates/author.php:97 +#: templates/playlist-archive-template.php:67 +#: templates/show-blog-archive-template.php:75 +msgid "" +"Apologies, but no results were found for the requested archive. Perhaps " +"searching will help find a related post." +msgstr "" +"Excuses, maar er is niets gevonden in het archief. Misschien helpt de search " +"functie om een post te vinden." + +#: templates/author.php:51 +#, php-format +msgid "Author Archives: %s" +msgstr "Auteur Archieven: %s" + +#: templates/author.php:70 +#, php-format +msgid "About %s" +msgstr "Over: %s" + +#: templates/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "Playlist Archief" + +#: templates/show-blog-archive-template.php:16 +msgid "Blog Archive" +msgstr "Blog Archief" + +#: templates/show-blog-archive-template.php:46 +msgid "Posted by" +msgstr "Gepost door" + +#: templates/single-playlist.php:14 templates/single-show.php:14 +msgid "Previous" +msgstr "Vorige" + +#: templates/single-playlist.php:15 templates/single-show.php:15 +msgid "Next" +msgstr "Volgende" + +#: templates/single-playlist.php:32 templates/single-show.php:144 +msgid "Pages:" +msgstr "Paginas:" + +#: templates/single-playlist.php:65 +msgid "No entries for this playlist" +msgstr "Niets gevonden voor deze playlist" + +#: templates/single-show.php:32 +msgid "Hosted by" +msgstr "Gepresenteerd door" + +#: templates/single-show.php:84 +msgid "Email the DJ" +msgstr "Email de DJ" + +#: templates/single-show.php:88 +msgid "Show Website" +msgstr "Laat De Website Zien" + +#: templates/single-show.php:95 +msgid "Most recent broadcast" +msgstr "Laatste Uitzending" + +#: templates/single-show.php:140 +msgid "Blog Posts" +msgstr "Blog Posts" + +#: radio-station.php:244 radio-station.php:330 +msgid "Export Playlists" +msgstr "Exporteer Playlist" + +#: radio-station.php:244 radio-station.php:451 +msgid "Export" +msgstr "Exporteer" + +#: radio-station.php:323 +msgid "Right-click and download this file to save your export" +msgstr "Klik met rechts en download deze file om deze export op te slaan" + +#: radio-station.php:340 +msgid "Start Date" +msgstr "Start Datum" + +#: radio-station.php:394 +msgid "End Date" +msgstr "Eind Datum" + +#: radio-station.php:466 +msgid "Related to Show" +msgstr "Gerelateerd aan de Show" diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..8be3f53 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,5 @@ + + + WordPress Coding Standards + + diff --git a/radio-station-admin.php b/radio-station-admin.php new file mode 100644 index 0000000..1f24bd9 --- /dev/null +++ b/radio-station-admin.php @@ -0,0 +1,460 @@ +post_type ) && ( 'playlist' === $post->post_type ) ) { + $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }' . "\n"; + } + + echo ''; + +} +add_action( 'admin_print_styles', 'radio_station_admin_styles' ); + + +// ------------------ +// === Admin Menu === +// ------------------ + +// -------------------- +// Add Admin Menu Items +// -------------------- +function radio_station_add_admin_menus() { + + $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); + $position = apply_filters( 'radio_station_menu_position', 5 ); + $capability = 'publish_playlists'; + add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); + + add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); + add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); + add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); + add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); + add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); + add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); + add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); + add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); + add_submenu_page( 'radio-station', __( 'Help', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); + + // --- hack the submenu global to add post type add/edit URLs --- + global $submenu; + foreach ( $submenu as $i => $menu ) { + if ( 'radio-station' === $i ) { + foreach ( $menu as $j => $item ) { + switch ( $item[2] ) { + case 'add-show': + // maybe remove the Add Show link for DJs + if ( ! current_user_can( 'publish_shows' ) ) { + unset( $submenu[ $i ][ $j ] ); + } else { + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=show'; + } + break; + case 'shows': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=show'; + break; + case 'playlists': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=playlist'; + break; + case 'add-playlist': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=playlist'; + break; + case 'genres': + $submenu[ $i ][ $j ][2] = 'edit-tags.php?taxonomy=genres'; + break; + case 'schedule-overrides': + $submenu[ $i ][ $j ][2] = 'edit.php?post_type=override'; + break; + case 'add-override': + $submenu[ $i ][ $j ][2] = 'post-new.php?post_type=override'; + break; + } + } + } + } +} +add_action( 'admin_menu', 'radio_station_add_admin_menus' ); + +// ----------------------------------------- +// Fix to Expand Main Menu for Submenu Items +// ----------------------------------------- +// 2.2.2: added fix for genre taxonomy page and post type editing +// 2.2.8: remove strict in_array checking +function radio_station_fix_genre_parent( $parent_file = '' ) { + global $pagenow, $post; + $post_types = array( 'show', 'playlist', 'override' ); + if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + $parent_file = 'radio-station'; + } elseif ( 'post.php' === $pagenow && in_array( $post->post_type, $post_types ) ) { + $parent_file = 'radio-station'; + } + return $parent_file; +} +add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); + +// ------------------------------- +// Genre Taxonomy Submenu Item Fix +// ------------------------------- +// 2.2.2: so genre submenu item link is set to current (bold) +function radio_station_genre_submenu_fix() { + global $pagenow; + if ( 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'genres' === $_GET['taxonomy'] ) { + echo ""; + } +} +add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); + +// -------------------------- +// Add New Links to Admin Bar +// -------------------------- +// 2.2.2: re-add new post type items to admin bar +// (as no longer automatically added by register_post_type) +function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { + + // --- new show --- + if ( current_user_can( 'publish_shows' ) ) { + + $args = array( + 'id' => 'new-show', + 'title' => __( 'Show', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=show' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new playlist --- + if ( current_user_can( 'publish_playlists' ) ) { + $args = array( + 'id' => 'new-playlist', + 'title' => __( 'Playlist', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=playlist' ), + ); + $wp_admin_bar->add_node( $args ); + } + + // --- new schedule override --- + if ( current_user_can( 'publish_shows' ) ) { + $args = array( + 'id' => 'new-override', + 'title' => __( 'Override', 'radio-station' ), + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=override' ), + ); + $wp_admin_bar->add_node( $args ); + } + +} +add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); + +// ---------------- +// Output Help Page +// ---------------- +function radio_station_plugin_help() { + + // 2.2.2: include patreon button link + echo radio_station_patreon_blurb( false ); + + // --- show MailChimp signup form --- + radio_station_mailchimp_form(); + + // --- include help file template --- + include RADIO_STATION_DIR . '/templates/help.php'; +} + +// --------------------------- +// Output Playlist Export Page +// --------------------------- +function radio_station_admin_export() { + global $wpdb; + + // first, delete any old exports from the export directory + $dir = RADIO_STATION_DIR . '/export/'; + if ( is_dir( $dir ) ) { + $get_contents = opendir( $dir ); + while ( $file = readdir( $get_contents ) ) { + if ( '.' !== $file && '..' !== $file ) { + unlink( $dir . $file ); + } + } + closedir( $get_contents ); + } + + // --- watch for form submission --- + if ( isset( $_POST['export_action'] ) && ( 'station_playlist_export' === $_POST['export_action'] ) ) { + + // validate referrer and nonce field + check_admin_referer( 'station_export_valid' ); + + $start = $_POST['station_export_start_year'] . '-' . $_POST['station_export_start_month'] . '-' . $_POST['station_export_start_day']; + $start .= ' 00:00:00'; + $end = $_POST['station_export_end_year'] . '-' . $_POST['station_export_end_month'] . '-' . $_POST['station_export_end_day']; + $end .= ' 23:59:59'; + + // fetch all records that were created between the start and end dates + $sql = + "SELECT posts.ID, posts.post_date + FROM {$wpdb->posts} AS posts + WHERE posts.post_type = 'playlist' AND + posts.post_status = 'publish' AND + TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND + TO_DAYS(posts.post_date) <= TO_DAYS(%s) + ORDER BY posts.post_date ASC"; + // prepare query before executing + $query = $wpdb->prepare( $sql, array( $start, $end ) ); + $playlists = $wpdb->get_results( $query ); + + if ( ! $playlists ) { + $list = 'No playlists found for this period.';} + + // fetch the tracks for each playlist from the wp_postmeta table + foreach ( $playlists as $i => $playlist ) { + + $songs = get_post_meta( $playlist->ID, 'playlist', true ); + + // remove any entries that are marked as 'queued' + foreach ( $songs as $j => $entry ) { + if ( 'queued' === $entry['playlist_entry_status'] ) { + unset( $songs[ $j ] );} + } + + $playlists[ $i ]->songs = $songs; + } + + $output = ''; + + $date = ''; + foreach ( $playlists as $playlist ) { + if ( ! isset( $playlist->post_date ) ) { + continue; + } + $playlist_datetime = explode( ' ', $playlist->post_date ); + $playlist_post_date = array_shift( $playlist_datetime ); + if ( empty( $date ) || $date !== $playlist_post_date ) { + $date = $playlist_post_date; + $output .= $date . "\n\n"; + } + + foreach ( $playlist->songs as $song ) { + $output .= $song['playlist_entry_artist'] . ' || ' . $song['playlist_entry_song'] . ' || ' . $song['playlist_entry_album'] . ' || ' . $song['playlist_entry_label'] . "\n"; + } + } + + // save as file + $dir = RADIO_STATION_DIR . '/export/'; + $file = $date . '-export.txt'; + if ( ! file_exists( $dir ) ) { + wp_mkdir_p( $dir );} + + $f = fopen( $dir . $file, 'w' ); + fwrite( $f, $output ); + fclose( $f ); + + // display link to file + $url = get_bloginfo( 'url' ) . '/wp-content/plugins/radio-station/tmp/' . $file; + echo wp_kses_post( '' ); + } + + // display the export page + include RADIO_STATION_DIR . '/templates/admin-export.php'; + +} + + +// -------------------- +// === Admin Notice === +// -------------------- + +// -------------------------- +// Plugin Announcement Notice +// -------------------------- +// 2.2.2: added plugin announcement notice +function radio_station_announcement_notice() { + + // --- bug out if already dismissed --- + if ( get_option( 'radio_station_announcement_dismissed' ) ) { + return; + } + + // --- bug out on certain plugin pages --- + $pages = array( 'radio-station', 'radio-station-help' ); + // 2.2.8: remove strict in_array checking + if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages ) ) { + return; + } + + // --- display plugin announcement --- + echo '
    '; + echo radio_station_patreon_blurb(); + echo ''; + echo '
    '; +} +add_action( 'admin_notices', 'radio_station_announcement_notice' ); + +// ---------------------------------- +// Dismiss Plugin Announcement Notice +// ---------------------------------- +// 2.2.2: AJAX for announcement notice dismissal +function radio_station_announcement_dismiss() { + if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { + update_option( 'radio_station_announcement_dismissed', true ); + echo ""; + exit; + } +} +add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); + +// ----------------------- +// Patreon Supporter Blurb +// ----------------------- +// 2.2.2: added simple patreon supporter blurb +function radio_station_patreon_blurb( $dismissable = true ) { + + $blurb = '
      '; + $blurb .= '
    • '; + $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); + $blurb .= ''; + $blurb .= '
    • '; + $blurb .= '
    • '; + $blurb .= '' . __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) . '
      '; + $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) . ',
      '; + $blurb .= __( 'since June 2019', 'radio-station' ) . ', '; + $blurb .= '' . __( 'Radio Station', 'radio-station' ) . ' '; + $blurb .= __( ' plugin development has been actively taken over by', 'radio-station' ); + $blurb .= ' Netmix.
      '; + $blurb .= __( 'We invite you to', 'radio-station' ); + $blurb .= ' '; + $blurb .= __( 'Become a Radio Station Patreon Supporter', 'radio-station' ); + $blurb .= ' ' . __( 'to make it better for everyone', 'radio-station' ) . '!'; + $blurb .= '
    • '; + $blurb .= '
    • '; + $blurb .= radio_station_patreon_button(); + // 2.2.7: added WordPress.Org star rating link + $blurb .= '

      '; + $blurb .= '' . __( 'Rate on WordPress.Org', 'radio-station' ) . ''; + $blurb .= '
    • '; + $blurb .= '
    '; + if ( $dismissable ) { + $blurb .= '
    '; + $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); + $blurb .= ''; + $blurb .= ''; + $blurb .= '
    '; + } + return $blurb; +} + +// ------------------------ +// Patreon Supporter Button +// ------------------------ +// 2.2.2: added simple patreon supporter image button +function radio_station_patreon_button() { + // 2.2.7: added button hover opacity + $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); + $button = ''; + $button .= ''; + $button .= ''; + $button .= ''; + return $button; +} + +// ------------------------- +// MailChimp Subscriber Form +// ------------------------- +function radio_station_mailchimp_form() { + + // --- enqueue MailChimp form styles --- + $version = filemtime( RADIO_STATION_DIR . '/css/mailchimp.css' ); + $url = plugins_url( 'css/mailchimp.css', __FILE__ ); + wp_enqueue_style( 'program-schedule', $url, array(), $version ); + + // --- get current user email --- + $current_user = wp_get_current_user(); + $user_email = $current_user->user_email; + + // --- set radio station plugin icon URL --- + $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); + + // --- output MailChimp signup form --- +?> +
    +
    + +
    +
    + +
    + + + +
    +
    +
    + + -Version: 2.2.4 +Version: 2.2.8 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -31,119 +31,105 @@ */ // === Setup === -// - Include Necessary Files -// - Load Text Domain +// - Include Plugin Files +// - Load Plugin Text Domain +// - Flush Rewrite Rules // - Enqueue Stylesheets -// - Enqueue Admin Scripts -// - Enqueue Admin Styles // === Template Filters === -// - Single Show/Playlist Template -// - Playlist Archive Template -// === Roles === -// - Add DJ Role and Capabilities +// - Single Templates Loader +// - Archive Templates Loader +// === User Roles === +// - Set DJ Role and Capabilities // - maybe Revoke Edit Show Capability -// === Menus === -// - Add Menus -// - maybe Remove Add Show Bar Link -// - Output Help Page -// - Output Export Page + +// 2.2.7: moved all admin functions to radio-station-admin.php // ------------- // === Setup === // ------------- -// --- include necessary files --- -include('includes/post_types.php'); -include('includes/master_schedule.php'); -include('includes/shortcodes.php'); -include('includes/widget_nowplaying.php'); -include('includes/widget_djonair.php'); -include('includes/widget_djcomingup.php'); -include('includes/support_functions.php'); +define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); + +// -------------------- +// Include Plugin Files +// -------------------- +require RADIO_STATION_DIR . '/includes/post-types.php'; +require RADIO_STATION_DIR . '/includes/master-schedule.php'; +require RADIO_STATION_DIR . '/includes/shortcodes.php'; +require RADIO_STATION_DIR . '/includes/support-functions.php'; +require RADIO_STATION_DIR . '/includes/class-dj-upcoming-widget.php'; +require RADIO_STATION_DIR . '/includes/class-dj-widget.php'; +require RADIO_STATION_DIR . '/includes/class-playlist-widget.php'; + +// 2.2.7: added conditional load of admin includes +if ( is_admin() ) { + require RADIO_STATION_DIR . '/radio-station-admin.php'; + require RADIO_STATION_DIR . '/includes/post-types-admin.php'; +} -// --- load the text domain --- +// ----------------------- +// Load Plugin Text Domain +// ----------------------- function radio_station_init() { load_plugin_textdomain( 'radio-station', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); } add_action( 'plugins_loaded', 'radio_station_init' ); -// --- flush rewrite rules on activate / deactivation --- +// ------------------- +// Flush Rewrite Rules +// ------------------- +// (on plugin activation / deactivation) // 2.2.3: added this for custom post types rewrite flushing -register_activation_hook( __FILE__, 'radio_station_flush_rewrite_rules' ); +// 2.2.8: fix for mismatched flag function name +register_activation_hook( __FILE__, 'radio_station_flush_rewrite_flag' ); register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); function radio_station_flush_rewrite_flag() { add_option( 'radio_station_flush_rewrite_rules', true ); } -// --- enqueue necessary stylesheets --- -function radio_station_load_styles() { +// -------------------------- +// Enqueue Plugin Stylesheets +// -------------------------- +function radio_station_enqueue_styles() { - $program_css = get_stylesheet_directory().'/program-schedule.css'; + $program_css = get_stylesheet_directory() . '/program-schedule.css'; if ( file_exists( $program_css ) ) { $version = filemtime( $program_css ); - $url = get_stylesheet_directory_uri().'/program-schedule.css'; + $url = get_stylesheet_directory_uri() . '/program-schedule.css'; } else { - $version = filemtime( dirname(__FILE__).'/css/program-schedule.css' ); - $url = plugins_url( 'css/program-schedule.css', __FILE__ ); + $version = filemtime( RADIO_STATION_DIR . '/css/program-schedule.css' ); + $url = plugins_url( 'css/program-schedule.css', __FILE__ ); } wp_enqueue_style( 'program-schedule', $url, array(), $version ); - // note: djonair.css style enqueueing moved to /includes/widget_djonair.php -} -add_action( 'wp_enqueue_scripts', 'radio_station_load_styles' ); - -// --- enqueue admin scripts --- -// jQuery is needed by the output of this code, so let us make sure we have it available -function radio_station_master_scripts() { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'jquery-ui-datepicker' ); - // $url = plugins_url( 'css/jquery-ui.css', dirname(__FILE__).'/radio-station.php' ); - // wp_enqueue_style( 'jquery-style', $url, array(), '1.8.2' ); - if (is_ssl()) {$protocol = 'https';} else {$protocol = 'http';} - $url = $protocol.'://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css'; - wp_enqueue_style( 'jquery-ui-style', $url, '1.8.2' ); -} -add_action( 'wp_enqueue_scripts', 'radio_station_master_scripts' ); -add_action( 'admin_enqueue_scripts', 'radio_station_master_scripts' ); - -// --- add some style rules to certain parts of the admin area --- -function radio_station_load_admin_styles() { - global $post; - - // hide the first submenu item to prevent duplicate of main menu item - $styles = ' #toplevel_page_radio-station .wp-first-item { display: none; }'."\n"; - - if ( isset( $post->post_type ) && ( $post->post_type == 'playlist' ) ) { - $styles .= ' .wp-editor-container textarea.wp-editor-area { height: 100px; }'."\n"; - } - - echo ''; - + // note: widgets.css style enqueueing moved to within widgets } -add_action( 'admin_print_styles', 'radio_station_load_admin_styles' ); +add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_styles' ); // ------------------------ // === Template Filters === // ------------------------ -// --- load the theme file for the playlist and show post types --- +// ----------------------- +// Single Templates Loader +// ----------------------- function radio_station_load_template( $single_template ) { global $post; - if ($post->post_type == 'playlist') { + if ( 'playlist' === $post->post_type ) { // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-playlist.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-playlist.php'; + $user_theme = get_stylesheet_directory() . '/single-playlist.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = RADIO_STATION_DIR . '/templates/single-playlist.php'; } } - if ($post->post_type == 'show') { + if ( 'show' === $post->post_type ) { // first check to see if there's a template in the active theme's directory - $user_theme = get_stylesheet_directory().'/single-show.php'; - if ( !file_exists( $user_theme ) ) { - $single_template = dirname(__FILE__).'/templates/single-show.php'; + $user_theme = get_stylesheet_directory() . '/single-show.php'; + if ( ! file_exists( $user_theme ) ) { + $single_template = RADIO_STATION_DIR . '/templates/single-show.php'; } } @@ -151,14 +137,16 @@ function radio_station_load_template( $single_template ) { } add_filter( 'single_template', 'radio_station_load_template' ); -// --- load the theme file for the playlist archive pages --- +// ------------------------ +// Archive Templates Loader +// ------------------------ function radio_station_load_custom_post_type_template( $archive_template ) { global $post; - if ( is_post_type_archive('playlist') ) { - $playlist_archive_theme = get_stylesheet_directory().'/archive-playlist.php'; - if ( !file_exists( $playlist_archive_theme ) ) { - $archive_template = dirname(__FILE__).'/templates/archive-playlist.php'; + if ( is_post_type_archive( 'playlist' ) ) { + $playlist_archive_theme = get_stylesheet_directory() . '/archive-playlist.php'; + if ( ! file_exists( $playlist_archive_theme ) ) { + $archive_template = RADIO_STATION_DIR . '/templates/archive-playlist.php'; } } @@ -166,40 +154,63 @@ function radio_station_load_custom_post_type_template( $archive_template ) { } add_filter( 'archive_template', 'radio_station_load_custom_post_type_template' ); +// ---------------------- +// DJ Author Template Fix +// ---------------------- +// 2.2.8: fix to not 404 author pages for DJs without blog posts +// Ref: https://wordpress.org/plugins/show-authors-without-posts/ +function radio_station_fix_djs_without_posts( $template ) { + global $wp_query; + if ( ! is_author() && get_query_var( 'author' ) && ( 0 == $wp_query->posts->post ) ) { + + // --- check if author has DJ (or administrator) role --- + $author = get_query_var( 'author_name' ) ? get_user_by( 'slug', get_query_var( 'author_name' ) ) : get_userdata( get_query_var( 'author' ) ); + if ( in_array( 'dj', $author->roles ) || in_array( 'administrator', $author->roles ) ) { + return get_author_template(); + } + + } + return $template; +} +add_filter( '404_template', 'radio_station_fix_djs_without_posts' ); -// ------------- -// === Roles === -// ------------- +// ------------------ +// === User Roles === +// ------------------ -// --- set up the DJ role and user capabilities --- +// ---------------------------- +// Set DJ Role and Capabilities +// ---------------------------- function radio_station_set_roles() { global $wp_roles; - // set only the necessary capabilities for DJs + // --- set only necessary capabilities for DJs --- $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'read_shows' => true, + 'edit_playlists' => true, + 'edit_published_playlists' => true, // 'edit_others_playlists' => true, // uncomment to allow DJs to edit all playlists - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true + 'read_playlists' => true, + 'publish_playlists' => true, + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, ); // $wp_roles->remove_role('dj'); // we need this here in case we ever update the capabilities list - // TODO: translate role name ? + + // --- add the role --- + // TODO: maybe translate role name ? $wp_roles->add_role( 'dj', 'DJ', $caps ); - // grant all new capabilities to admin users + // --- grant all new capabilities to admin users --- $wp_roles->add_cap( 'administrator', 'edit_shows', true ); $wp_roles->add_cap( 'administrator', 'edit_published_shows', true ); $wp_roles->add_cap( 'administrator', 'edit_others_shows', true ); @@ -224,48 +235,56 @@ function radio_station_set_roles() { if ( is_multisite() ) { add_action( 'init', 'radio_station_set_roles', 10, 0 ); } else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); + add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); } -// --- revoke the ability to edit a show if the user is not listed as a DJ on that show --- +// --------------------------------- +// maybe Revoke Edit Show Capability +// --------------------------------- +// (revoke ability to edit show if user is not assigned as a DJ to it) function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $args ) { global $post, $wp_roles; $user = wp_get_current_user(); - // determine which roles should have full access aside from administrator + // --- get roles with publish shows capability --- $add_roles = array( 'administrator' ); if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach( $wp_roles->roles as $name => $role ) { - foreach( $role['capabilities'] as $capname => $capstatus ) { - if ( $capname == 'publish_shows' && ($capstatus == 1 || $capstatus == true) ) { + foreach ( $wp_roles->roles as $name => $role ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( 'publish_shows' === $capname && (bool) $capstatus ) { $add_roles[] = $name; } } } } - // exclude administrators and custom roles with appropriate capabilities... - // they should be able to do whatever they want + // --- check if current user has any of these roles --- $found = false; foreach ( $add_roles as $role ) { - if ( in_array( $role, $user->roles ) ) {$found = true;} + // 2.2.8: remove strict in_array checking + if ( in_array( $role, $user->roles ) ) { + $found = true; + } } - if ( !$found ) { + if ( ! $found ) { - // limit this to published shows + // --- limit this to published shows --- if ( isset( $post->post_type ) ) { - if ( is_admin() && ( $post->post_type == 'show' ) && ( $post->post_status == 'publish' ) ) { + if ( is_admin() && ( 'show' === $post->post_type ) && ( 'publish' === $post->post_status ) ) { $djs = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$djs || ( $djs == '' ) ) {$djs = array();} + if ( ! isset( $djs ) || empty( $djs ) ) { + $djs = array(); + } - // if they are not listed, temporarily revoke editing ability for this post - if ( !in_array( $user->ID, $djs ) ) { - $allcaps['edit_shows'] = false; + // ---- revoke editing capability if not assigned to this show --- + // 2.2.8: remove strict in_array checking + if ( ! in_array( $user->ID, $djs ) ) { + $allcaps['edit_shows'] = false; $allcaps['edit_published_shows'] = false; } } @@ -275,299 +294,3 @@ function radio_station_revoke_show_edit_cap( $allcaps, $cap = 'edit_shows', $arg } add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); - -// ------------- -// === Menus === -// ------------- - -function radio_station_add_admin_menus() { - - $icon = plugins_url( 'images/radio-station-icon.png', __FILE__ ); - $position = apply_filters( 'radio_station_menu_position', 5 ); - $capability = 'publish_playlists'; - add_menu_page( __( 'Radio Station', 'radio-station' ), __( 'Radio Station', 'radio-station' ), $capability, 'radio-station', 'radio_station_plugin_help', $icon, $position ); - - add_submenu_page( 'radio-station', __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); - add_submenu_page( 'radio-station', __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); - add_submenu_page( 'radio-station', __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); - add_submenu_page( 'radio-station', __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); - add_submenu_page( 'radio-station', __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); - add_submenu_page( 'radio-station', __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - add_submenu_page( 'radio-station', __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); - add_submenu_page( 'radio-station', __( 'Export Playlists', 'radio-station'), __('Export Playlists', 'radio-station'), 'manage_options', 'playlist-export', 'radio_station_admin_export' ); - add_submenu_page( 'radio-station', __( 'Help', 'radio-station'), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-help', 'radio_station_plugin_help' ); - - // hack the submenu global to post type add/edit URLs - global $submenu; - foreach ( $submenu as $i => $menu ) { - if ( $i == 'radio-station' ) { - foreach ( $menu as $j => $item ) { - if ( $item[2] == 'add-show' ) { - // maybe remove the Add Show link for DJs - // $user = wp_get_current_user(); - // if ( in_array( 'dj', $user->roles ) ) { - if ( !current_user_can( 'publish_shows' ) ) {unset($submenu[$i][$j]);} - else {$submenu[$i][$j][2] = 'post-new.php?post_type=show';} - } elseif ( $item[2] == 'shows' ) {$submenu[$i][$j][2] = 'edit.php?post_type=show';} - elseif ( $item[2] == 'playlists' ) {$submenu[$i][$j][2] = 'edit.php?post_type=playlist';} - elseif ( $item[2] == 'add-playlist' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=playlist';} - elseif ( $item[2] == 'genres' ) {$submenu[$i][$j][2] = 'edit-tags.php?taxonomy=genres';} - elseif ( $item[2] == 'schedule-overrides' ) {$submenu[$i][$j][2] = 'edit.php?post_type=override';} - elseif ( $item[2] == 'add-override' ) {$submenu[$i][$j][2] = 'post-new.php?post_type=override';} - } - } - } -} -add_action( 'admin_menu', 'radio_station_add_admin_menus' ); - -// --- expand main menu fix for plugin submenu items --- -// 2.2.2: added fix for genre taxonomy page and post type editing -function radio_station_fix_genre_parent($parent_file = '') { - global $pagenow, $post; - $post_types = array( 'show', 'playlist', 'override' ); - if ( ( $pagenow == 'edit-tags.php') && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - $parent_file = 'radio-station'; - } elseif ( ( $pagenow == 'post.php' ) && ( in_array( $post->post_type, $post_types ) ) ) { - $parent_file = 'radio-station'; - } - return $parent_file; -} -add_filter( 'parent_file', 'radio_station_fix_genre_parent', 11 ); - -// --- genre taxonomy submenu item fix --- -// 2.2.2: so genre submenu item link is set to current (bold) -function radio_station_genre_submenu_fix() { - global $pagenow; - if ( ( $pagenow == 'edit-tags.php' ) && isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] == 'genres' ) ) { - echo ""; - } -} -add_action( 'admin_footer', 'radio_station_genre_submenu_fix' ); - -// --- remove the Add Show link for DJs from the wp admin bar --- -// 2.2.2: re-add new post type items to admin bar -// (as no longer automatically added by register_post_type) -function station_radio_modify_admin_bar_menu( $wp_admin_bar ) { - - // --- new show --- - if ( current_user_can( 'publish_shows' ) ) { - - $args = array( - 'id' => 'new-show', - 'title' => __( 'Show', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=show') - ); - $wp_admin_bar->add_node($args); - } - - // --- new playlist --- - if ( current_user_can( 'publish_playlists' ) ) { - $args = array( - 'id' => 'new-playlist', - 'title' => __( 'Playlist', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=playlist') - ); - $wp_admin_bar->add_node($args); - } - - // --- new schedule override --- - if ( current_user_can( 'publish_shows' ) ) { - $args = array( - 'id' => 'new-override', - 'title' => __( 'Override', 'radio-station' ), - 'parent' => 'new-content', - 'href' => admin_url('post-new.php?post_type=override') - ); - $wp_admin_bar->add_node($args); - } - -} -add_action( 'admin_bar_menu', 'station_radio_modify_admin_bar_menu', 999 ); - -// --- output help page --- -function radio_station_plugin_help() { - - // 2.2.2: include patreon button link - echo radio_station_patreon_blurb(false); - - // include help template - include( dirname(__FILE__).'/templates/help.php' ); -} - -// --- output playlist export page --- -function radio_station_admin_export() { - global $wpdb; - - // first, delete any old exports from the export directory - $dir = dirname(__FILE__).'/export/'; - if ( is_dir ( $dir ) ) { - $get_contents = opendir($dir); - while ( $file = readdir( $get_contents ) ) { - if ( ( $file != '.' ) && ( $file != '..' ) ) { - unlink( $dir.$file ); - } - } - closedir($get_contents); - } - - // watch for form submission - if ( isset( $_POST['export_action']) && ( $_POST['export_action'] == 'station_playlist_export' ) ) { - - // validate referrer and nonce field - check_admin_referer( 'station_export_valid' ); - - $start = $_POST['station_export_start_year'].'-'.$_POST['station_export_start_month'].'-'.$_POST['station_export_start_day']; - $start .= ' 00:00:00'; - $end = $_POST['station_export_end_year'].'-'.$_POST['station_export_end_month'].'-'.$_POST['station_export_end_day']; - $end .= ' 23:59:59'; - - // fetch all records that were created between the start and end dates - $query = - "SELECT `posts`.`ID`, `posts`.`post_date` FROM ".$wpdb->prefix."posts AS `posts` - WHERE `posts`.`post_type` = 'playlist' - AND `posts`.`post_status` = 'publish' - AND TO_DAYS(`posts`.`post_date`) >= TO_DAYS(%s) - AND TO_DAYS(`posts`.`post_date`) <= TO_DAYS(%s) - ORDER BY `posts`.`post_date` ASC;"; - //" prepare query before executing - $query = $wpdb->prepare( $query, array( $start, $end ) ); - $playlists = $wpdb->get_results( $query ); - - if ( !$playlists ) {$list = 'No playlists found for this period.';} - - // fetch the tracks for each playlist from the wp_postmeta table - foreach ( $playlists as $i => $playlist ) { - - $songs = get_post_meta( $playlist->ID, 'playlist', true ); - - // remove any entries that are marked as 'queued' - foreach ( $songs as $j => $entry ) { - if ( $entry['playlist_entry_status'] == 'queued' ) {unset($songs[$j]);} - } - - $playlists[$i]->songs = $songs; - } - - $output = ''; - - $date = ''; - foreach ( $playlists as $playlist ) { - - if ( ( $date == '' ) || ( $date != array_shift( explode( " ", $playlist->post_date ) ) ) ) { - $date = array_shift( explode( " ", $playlist->post_date ) ); - $output .= $date."\n\n"; - } - - foreach ( $playlist->songs as $song ) { - $output .= $song['playlist_entry_artist'].' || '.$song['playlist_entry_song'].' || '.$song['playlist_entry_album'].' || '.$song['playlist_entry_label']."\n"; - } - } - - // save as file - $dir = dirname(__FILE__).'/export/'; - $file = $date.'-export.txt'; - if ( !file_exists( $dir ) ) {wp_mkdir_p( $dir );} - - $f = fopen( $dir.$file, 'w' ); - fwrite( $f, $output ); - fclose( $f ); - - // display link to file - $url = get_bloginfo('url').'/wp-content/plugins/radio-station/tmp/'.$file; - echo ''; - } - - // display the export page - include( dirname(__FILE__).'/templates/admin-export.php' ); - -} - - -// -------------------- -// === Admin Notice === -// -------------------- - -// --- plugin announcement notice --- -// 2.2.2: added plugin announcement notice -function radio_station_announcement_notice() { - - // --- bug out if already dismissed --- - if ( get_option( 'radio_station_announcement_dismissed' ) ) {return;} - - // --- bug out on certain plugin pages --- - $pages = array( 'radio-station', 'radio-station-help' ); - if ( isset( $_REQUEST['page'] ) && ( in_array( $_REQUEST['page'], $pages ) ) ) {return;} - - // --- display plugin announcement --- - echo '
    '; - echo radio_station_patreon_blurb(); - echo ''; - echo '
    '; -} -add_action( 'admin_notices', 'radio_station_announcement_notice' ); - -// --- dismiss plugin announcement notice --- -// 2.2.2: AJAX for announcement notice dismissal -function radio_station_announcement_dismiss() { - if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { - update_option( 'radio_station_announcement_dismissed', true ); - echo ""; - exit; - } -} -add_action( 'wp_ajax_radio_station_announcement_dismiss', 'radio_station_announcement_dismiss' ); - -// --- Patreon supporter blurb --- -// 2.2.2: added simple patreon supporter blurb -function radio_station_patreon_blurb( $dismissable = true ) { - - $blurb = '
      '; - $blurb .= '
    • '; - $plugin_image = plugins_url( 'images/radio-station.png', __FILE__ ); - $blurb .= ''; - $blurb .= '
    • '; - $blurb .= '
    • '; - $blurb .= ''.__( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ).'
      '; - $blurb .= __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ).',
      '; - $blurb .= __( 'since June 2019', 'radio-station' ).', '; - $blurb .= ''.__( 'Radio Station', 'radio-station' ).' '; - $blurb .= __(' plugin development has been actively taken over by', 'radio-station' ); - $blurb .= ' Netmix.
      '; - $blurb .= __( 'We invite you to', 'radio-station'); - $blurb .= ' '; - $blurb .= __('Become a Radio Station Patreon Supporter', 'radio-station'); - $blurb .= ' '.__('to make it better for everyone', 'radio-station').'!'; - $blurb .= '
    • '; - $blurb .= '
    • '; - $blurb .= radio_station_patreon_button(); - $blurb .= '
    • '; - if ($dismissable) { - $blurb .= '
    • '; - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - $blurb .= ''; - $blurb .= ''; - $blurb .= '
    • '; - } - $blurb .= '
    '; - return $blurb; -} - -// --- Patreon supporter button --- -// 2.2.2: added simple patreon supporter image button -function radio_station_patreon_button() { - $image_url = plugins_url( 'images/patreon-button.jpg', __FILE__ ); - $button = ''; - $button .= ''; - $button .= ''; - $button .= ''; - return $button; -} - diff --git a/readme.txt b/readme.txt index ea05893..dbd9436 100644 --- a/readme.txt +++ b/readme.txt @@ -1,26 +1,34 @@ === Radio Station === Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate -Tags: dj, music, playlist, radio, scheduling +Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.2.2 Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html -Radio Station is a plugin to build and manage a Show Calendar in a radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. Functionality is based on Drupal 6's Station plugin. == Description == -Radio Station is a plugin to build and manage a Show Calendar in radio station's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. +Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station will will managed by Tony Zeoli and developed by contributing committers to the project. +The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. -If you are a WordPress developer interested in contributing to this plugin, please follow plugin development on Github: https://github.com/netmix/radio-station. +The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -You may also submit feature requests here: https://github.com/netmix/radio-station/issues +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://netmix.co/donate. +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. -Please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues + +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. + +For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ + +You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com == Installation == @@ -222,6 +230,7 @@ little out of the scope of this plugin. I recommend Cimy User Extra Fields: ht Right now: Albanian (sq_AL) +Dutch (nl_NL) French (fr_FR) German (de_DE) Italian (it_IT) @@ -230,14 +239,49 @@ Serbian (sr_RS) Spanish (es_ES) Catalan (ca) -= Can you translate the plugin into my language? = += Can the plugin be translated into my language? = -My foreign language skills are rather lacking. I managed a Spanish translation, sheerly due to the fact that I still remember at least some of what -I learned in high school Spanish class. But I've included the .pot file in the /languages directory. If you want to give it a shot, be my guest. If -you send me your finished translation, I'd love to include it. +You may translate the plugin into another language. Please visit our WordPress Translate project page for this plugin for further instruction: https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/ The radio-station.pot file is located in the /languages directory of the plugin. Please send the finished translation to info@netmix.com. We'd love to include it. == Changelog == += 2.2.8 = +* Fix to remove strict type checking from in_array (introduced 2.2.6) +* Fix to mismatched flush rewrite rules flag function name +* Fix to undefined index warnings for new Schedule Overrides +* Fix to not 404 author pages for DJs without blog posts + += 2.2.7 = +* Dutch translation added (Thank you to André Dortmont for the file!) +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations + += 2.2.6 = +* Reorganize master-list shortcode into templates +* Add constant for plugin directory +* Use WP_Query instead of get_posts +* New posts_per_page and tax_query +* Fixes for undefined indexes +* Fixes for raw mysql queries +* Typecasting to support strict comparisons + += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + = 2.2.4 = * added title position and avatar width options to widgets * added missing DJ author links as new option to widgets @@ -503,6 +547,67 @@ you send me your finished translation, I'd love to include it. == Upgrade Notice == += 2.2.7 = +* Dutch translation added (Thank you to André Dortmont for the file!) +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations + += 2.2.6 = +* Reorganize master-list shortcode into templates, +* Add constant for plugin directory, +* WP_Query instead of get_posts, +* new posts_per_page and tax_query, +* fixes for undefined indexes, +* fixes for raw mysql queries, +* typecasting to support strict comparisons. + += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + += 2.2.4 = +Adds title position and avatar width options to widgets; missing DJ author links as new option to widgets; cleanup, improve and fix enqueued Widget CSS (on air/upcoming); show Encore Presentation in show widget displays; fix to Show shift Encore Presentation checkbox saving + += 2.2.3 = +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + += 2.2.2 = +* shift main playlist and show metaboxes above editor +* set plugin custom post types editor to Classic Editor +* add high priority to side metaboxes for plugin post types +* added dismissable development changeover admin notice +* added simple Patreon supporter image button and blurb +* added filter for DJ Avatar size on Author page template +* fix to Schedule Override metabox value saving +* fix to Playlist track list overflowing metabox +* fix to missing day of week headings in master schedule +* fix to file_exists check for DJ on Air stylesheet path +* fix to make DJ multi-select input full metabox width +* fix to expand admin menu when on genre taxonomy page +* fix to expand admin menu when editing plugin post types +* fix to genre submenu item link for current page +* added GitHub URI to plugin header for GitHub updater + += 2.2.1 = +* Re-commit all missing files via SVN + = 2.2.0 = * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) diff --git a/templates/admin-export.php b/templates/admin-export.php index be0fbbe..a44c8df 100644 --- a/templates/admin-export.php +++ b/templates/admin-export.php @@ -1,5 +1,5 @@
    -

    +

    @@ -8,96 +8,143 @@ - - + + - + @@ -106,9 +153,9 @@
    - + - +
    - + - + - +
      - +
    -
    \ No newline at end of file +
    diff --git a/templates/archive-playlist.php b/templates/archive-playlist.php index b70f87c..8d50001 100644 --- a/templates/archive-playlist.php +++ b/templates/archive-playlist.php @@ -1,79 +1,83 @@ - - -
    -
    - - - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'playlist_show_id', - 'value' => $show_id - ) - ), - 'paged' => $paged - ); - $loop = new WP_Query( $args ); - // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); - ?> - have_posts() ) : $loop->the_post(); ?> - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - - \ No newline at end of file + + +
    +
    + + + + + + + + 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'playlist_show_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, + ); + $loop = new WP_Query( $args ); + // query_posts( $query_string.'&post_type=playlist&orderby=post_date&order=desc&posts_per_page=5&meta_key=playlist_show_id&meta_value='.$show_id ); + ?> + have_posts() ) : + $loop->the_post(); + ?> + + + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + + diff --git a/templates/author.php b/templates/author.php index 323799f..fc2c40e 100644 --- a/templates/author.php +++ b/templates/author.php @@ -1,110 +1,116 @@ - - - - -
    -
    - - roles ) ): ?> - - - - -
    ID, $avatar_size ); ?>
    -
    - - description; ?> - -
    - URL: user_url; ?>
    - Email: user_email; ?>
    - AIM: aim; ?>
    - Jabber: jabber; ?>
    - YIM: yim; ?>
    -
    - -
    - - - - - - - - - - - - -
    -
    - -
    -
    -

    - -
    -
    - - - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - - -
    -
    - - - \ No newline at end of file + + + + +
    +
    + + roles ) ) : ?> + + + + +
    ID, $avatar_size ); ?>
    +
    + + description ); ?> + +
    + URL: user_url ); ?>
    + Email: user_email ); ?>
    + AIM: aim ); ?>
    + Jabber: jabber ); ?>
    + YIM: yim ); ?>
    +
    + +
    + + + + + + + + + + + + +
    +
    + +
    +
    +

    + +
    +
    + + + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + + +
    +
    + + + diff --git a/templates/help.php b/templates/help.php index 6e39550..acc7522 100644 --- a/templates/help.php +++ b/templates/help.php @@ -6,7 +6,7 @@
    I'm seeing 404 Not Found errors when I click on the link for a show! -

    Try re-saving your site's permalink settings. Wordpress sometimes gets confused with a new custom post type is added.

    +

    Try re-saving your site's permalink settings. WordPress sometimes gets confused with a new custom post type is added.


    @@ -136,7 +136,7 @@

    First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the playlist archive. +Then, create a Page in WordPress to hold the playlist archive.

    Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear.

    @@ -146,7 +146,7 @@

    Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory.

    -Then, create a Page in wordpress to hold the blog archive. +Then, create a Page in WordPress to hold the blog archive.

    Under Page Attributes, set the template to Show Blog Archive.

    diff --git a/templates/master-list-default.php b/templates/master-list-default.php new file mode 100644 index 0000000..86be524 --- /dev/null +++ b/templates/master-list-default.php @@ -0,0 +1,206 @@ +'; + +// output the headings in the correct order +$output .= ' '; +foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= '' . $heading . ''; +} +$output .= ''; +// $output .= ' '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').' '; + +if ( ! isset( $nextskip ) ) { + $nextskip = array(); +} + +foreach ( $master_list as $hour => $days ) { + + $output .= ''; + $output .= '
    '; + + if ( 12 === (int) $timeformat ) { + if ( 0 === $hour ) { + $output .= '12am'; + } elseif ( (int) $hour < 12 ) { + $output .= $hour . 'am'; + } elseif ( 12 === (int) $hour ) { + $output .= '12pm'; + } else { + $output .= ( $hour - 12 ) . 'pm'; + } + } else { + if ( $hour < 10 ) { + $output .= '0'; + } + $output .= $hour . ':00'; + } + + $output .= '
    '; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] === $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[ $x ]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $shift ) { + + if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { + //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { + // show runs from before midnight night until the next morning + if ( isset( $shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue; + } + + $output .= ''; + + foreach ( $min as $shift ) { + + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); + $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; + foreach ( $terms as $shift_term ) { + $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; + } + + $output .= '
    '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $shift['id'] ) ) { + $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $shift['id'] ) . ''; + } else { + $output .= get_the_title( $shift['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); + $count = 0; + + if ( $dj_names ) { + + $output .= ' ' . __( 'with', 'radio-station' ) . ' '; + foreach ( $dj_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $dj_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $shift['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
    '; + } + $output .= ''; + } + + $output .= ''; +} +$output .= ''; diff --git a/templates/master-list-div.php b/templates/master-list-div.php new file mode 100644 index 0000000..a5059ab --- /dev/null +++ b/templates/master-list-div.php @@ -0,0 +1,162 @@ +'; +for ( $i = 2; $i < 24; $i++ ) { + $rowheight = $atts['divheight'] * $i; + + $output .= '#master-schedule-divs .rowspan' . $i . ' { '; + $output .= 'height: ' . ( $rowheight ) . 'px; }'; +} + +$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; +$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; +$output .= ''; + +// output the schedule +$output .= radio_station_master_fetch_js_filter(); +$output .= '
    '; +$weekdays = array_keys( $days_of_the_week ); + +$output .= '
    '; +$output .= '
     
    '; +foreach ( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
    ' . $translated . '
    '; +} + $output .= '
    '; + +foreach ( $master_list as $hour => $days ) { + + $output .= '
    '; + + // output the hour labels + $output .= '
    '; + if ( 12 === (int) $timeformat ) { + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format + } + $output .= '
    '; + + foreach ( $weekdays as $weekday ) { + $output .= '
    '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $showdata ) { + + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); + $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; + foreach ( $terms as $show_term ) { + $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + } + + $output .= '
    '; + + // featured image + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); + } + $output .= ''; + } + + // title + link to page if requested + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $showdata['id'] ) . ''; + } else { + $output .= get_the_title( $showdata['id'] ); + } + $output .= ''; + + // list of DJs + if ( $atts['show_djs'] ) { + + $output .= ''; + + $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + + $output .= ' with '; + + foreach ( $show_names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // show's schedule + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + // designate as encore + if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + // link to media file + $show_link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day + $duration = $showdata['time']['end_hour']; + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { + $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; + } else { + $duration = 23 - $showdata['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
    '; + + if ( '00' !== $showdata['time']['end_min'] ) { + $output .= '
    '; + } + } + + $output .= '
    '; // end master-show-entry + } + } + $output .= '
    '; // end master-schedule-weekday + } + $output .= '
    '; // end master-schedule-hour +} +$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-list-list.php b/templates/master-list-list.php new file mode 100644 index 0000000..46b4a28 --- /dev/null +++ b/templates/master-list-list.php @@ -0,0 +1,109 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
      '; + +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
    • '; + $output .= '' . $display_day . ''; + $output .= '
        '; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + $output .= '
      • '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $show['id'] ) ) { + $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + $output .= ''; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $output .= ' with '; + foreach ( $show_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ' '; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + //$output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } else { + + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $output .= ''; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
      • '; + } + } + $output .= '
      '; + $output .= '
    • '; +} + +$output .= '
    '; diff --git a/templates/master-schedule-default.php b/templates/master-schedule-default.php new file mode 100644 index 0000000..d3e2ae7 --- /dev/null +++ b/templates/master-schedule-default.php @@ -0,0 +1,211 @@ +'; + +// output the headings in the correct order +$output .= ' '; +foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + $heading = substr( $weekday, 0, 3 ); + $heading = radio_station_translate_weekday( $heading, true ); + $output .= '' . $heading . ''; +} +$output .= ''; +// $output .= ' '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').' '; + +if ( ! isset( $nextskip ) ) { + $nextskip = array(); +} + +foreach ( $master_list as $hour => $days ) { + + $output .= ''; + $output .= '
    '; + + // 2.2.7: added meridiem translations + if ( 12 === (int) $timeformat ) { + if ( 0 === $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'am' ); + } elseif ( (int) $hour < 12 ) { + $output .= $hour . radio_station_translate_meridiem( 'am' ); + } elseif ( 12 === (int) $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'pm' ); + } else { + $output .= ( $hour - 12 ) . radio_station_translate_meridiem( 'pm' ); + } + } else { + if ( $hour < 10 ) { + $output .= '0'; + } + $output .= $hour . ':00'; + } + + $output .= '
    '; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] === $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[ $x ]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $shift ) { + + if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows + if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { + //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { + // show runs from before midnight night until the next morning + if ( isset( $shift['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $shift['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $shift['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue; + } + + $output .= ''; + + foreach ( $min as $shift ) { + + $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); + $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; + foreach ( $terms as $shift_term ) { + $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; + } + + $output .= '
    '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $shift['id'] ) ) { + $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $shift['id'] ) . ''; + } else { + $output .= get_the_title( $shift['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); + $count = 0; + + if ( $dj_names ) { + + $output .= ' ' . __( 'with', 'radio-station' ) . ' '; + foreach ( $dj_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $dj_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ); + $output .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $output .= ' - '; + $output .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $shift['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
    '; + } + $output .= ''; + } + + $output .= ''; +} +$output .= ''; diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php new file mode 100644 index 0000000..a5059ab --- /dev/null +++ b/templates/master-schedule-div.php @@ -0,0 +1,162 @@ +'; +for ( $i = 2; $i < 24; $i++ ) { + $rowheight = $atts['divheight'] * $i; + + $output .= '#master-schedule-divs .rowspan' . $i . ' { '; + $output .= 'height: ' . ( $rowheight ) . 'px; }'; +} + +$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; +$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; +$output .= ''; + +// output the schedule +$output .= radio_station_master_fetch_js_filter(); +$output .= '
    '; +$weekdays = array_keys( $days_of_the_week ); + +$output .= '
    '; +$output .= '
     
    '; +foreach ( $weekdays as $weekday ) { + $translated = radio_station_translate_weekday( $weekday ); + $output .= '
    ' . $translated . '
    '; +} + $output .= '
    '; + +foreach ( $master_list as $hour => $days ) { + + $output .= '
    '; + + // output the hour labels + $output .= '
    '; + if ( 12 === (int) $timeformat ) { + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format + } + $output .= '
    '; + + foreach ( $weekdays as $weekday ) { + $output .= '
    '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $showdata ) { + + $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); + $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; + foreach ( $terms as $show_term ) { + $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + } + + $output .= '
    '; + + // featured image + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $showdata['id'] ) ) { + $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); + } + $output .= ''; + } + + // title + link to page if requested + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $showdata['id'] ) . ''; + } else { + $output .= get_the_title( $showdata['id'] ); + } + $output .= ''; + + // list of DJs + if ( $atts['show_djs'] ) { + + $output .= ''; + + $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + + $output .= ' with '; + + foreach ( $show_names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' and '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // show's schedule + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + // $output .= $weekday.' '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } else { + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); + } + $output .= ''; + } + + // designate as encore + if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { + $output .= '' . __( 'encore airing', 'radio-station' ) . ''; + } + + // link to media file + $show_link = get_post_meta( $showdata['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= '' . __( 'Audio File', 'radio-station' ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day + $duration = $showdata['time']['end_hour']; + } else { + if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { + $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; + } else { + $duration = 23 - $showdata['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
    '; + + if ( '00' !== $showdata['time']['end_min'] ) { + $output .= '
    '; + } + } + + $output .= '
    '; // end master-show-entry + } + } + $output .= '
    '; // end master-schedule-weekday + } + $output .= '
    '; // end master-schedule-hour +} +$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php new file mode 100644 index 0000000..7d26aa6 --- /dev/null +++ b/templates/master-schedule-list.php @@ -0,0 +1,110 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
      '; + +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
    • '; + $output .= '' . $display_day . ''; + $output .= '
        '; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + $output .= '
      • '; + + if ( $atts['show_image'] ) { + $output .= ''; + if ( has_post_thumbnail( $show['id'] ) ) { + $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $output .= ''; + } + + $output .= ''; + if ( $atts['show_link'] ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + $output .= ''; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $output .= ' '.__( 'with', 'radio-station').' '; + foreach ( $show_names as $name ) { + $count++; + $user_info = get_userdata( $name ); + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' '.__( 'and', 'radio-station' ).' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ' '; + } + + if ( $atts['display_show_time'] ) { + + $output .= ''; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); + $output .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $output .= ' - '; + $output .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $output .= ' - '; + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $output .= ''; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; + } + + $output .= '
      • '; + } + } + $output .= '
      '; + $output .= '
    • '; +} + +$output .= '
    '; diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php new file mode 100644 index 0000000..534204e --- /dev/null +++ b/templates/master-schedule-tabs.php @@ -0,0 +1,146 @@ + $days ) { + foreach ( $days as $day => $mins ) { + foreach ( $mins as $fmin => $fshow ) { + $flip[ $day ][ $hour ][ $fmin ] = $fshow; + } + } +} + +$output .= '
      '; + +$panels = ''; +foreach ( $flip as $day => $hours ) { + + // 2.2.2: use translate function for weekday string + $display_day = radio_station_translate_weekday( $day ); + $output .= '
    • '; + $output .= '
      ' . $display_day . '
      '; + $output .= '
    • '; + + // 2.2.7: separate headings from panels for tab view + $panels .= '
        '; + + $foundshows = false; + foreach ( $hours as $hour => $mins ) { + + foreach ( $mins as $min => $show ) { + + $foundshows = true; + $panels .= '
      • '; + + // --- Show Image --- + // (defaults to display on) + if ( $atts['show_image'] !== 'false' ) { + $panels .= '
        '; + if ( has_post_thumbnail( $show['id'] ) ) { + $panels .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); + } + $panels .= '
        '; + } + + // --- Show Information --- + $panels .= '
        '; + + $panels .= '
        '; + if ( $atts['show_link'] ) { + $panels .= '' . get_the_title( $show['id'] ) . ''; + } else { + $panels .= get_the_title( $show['id'] ); + } + $panels .= '
        '; + + if ( $atts['show_djs'] ) { + $panels .= '
        '; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + $panels .= ' '.__( 'with', 'radio-station').' '; + foreach ( $show_names as $name ) { + $count++; + $user_info = get_userdata( $name ); + $panels .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $panels .= ' '.__( 'and', 'radio-station' ).' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $panels .= ', '; + } + } + } + + $panels .= '
        '; + } + + if ( $atts['display_show_time'] ) { + + $panels .= '
        '; + + if ( 12 === (int) $timeformat ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); + $panels .= date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $panels .= ' - '; + $panels .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + + $panels .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $panels .= ' - '; + $panels .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + + } + + $panels .= '
        '; + } + + if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { + $panels .= '
        ' . __( 'encore airing', 'radio-station' ) . '
        '; + } + + $show_link = get_post_meta( $show['id'], 'show_file', true ); + if ( $show_link && ! empty( $show_link ) ) { + $panels .= ' '; + } + + $panels .= '
        '; + + // --- Show Genres list --- + // (defaults to display on) + if ( $atts['show_genres'] !== 'false' ) { + $panels .= '
        '; + $terms = wp_get_post_terms( $show['id'], 'genres', array() ); + $genres = array(); + if ( count( $terms ) > 0 ) { + foreach ( $terms as $term ) {$genres[] = '' . $term->name . '';} + $genredisplay = implode( ', ', $genres ); + $panels .= __( 'Genres', 'radio-station' ) . ': ' . $genredisplay; + } + $panels .= '
        '; + } + + $panels .= '
      • '; + } + } + + if (!$foundshows) { + $panels .= '
      • '; + $panels .= __('No Shows found for this day.','radio-station'); + $panels .= '
      • '; + } + + $panels .= '
      '; +} + +$output .= '
    '.$panels; diff --git a/templates/playlist-archive-template.php b/templates/playlist-archive-template.php index 34849cd..3be15d8 100644 --- a/templates/playlist-archive-template.php +++ b/templates/playlist-archive-template.php @@ -1,75 +1,80 @@ - - - -
    -
    - - - - - - 'playlist', - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'paged' => $paged, - 'post_status' => 'publish' - ); - $loop = new WP_Query( $args ); - ?> - have_posts()) : $loop->the_post(); ?> - -
    > -
    -

    - -
    - - - - post_date)); ?> -
    -
    -
    - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - \ No newline at end of file + + + +
    +
    + + + + + + 'playlist', + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'paged' => $paged, + 'post_status' => 'publish', + ); + $loop = new WP_Query( $args ); + ?> + have_posts() ) : + $loop->the_post(); + ?> + +
    > +
    +

    + +
    + + - + post_date ) ) ); ?> +
    +
    +
    + + + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + diff --git a/templates/show-blog-archive-template.php b/templates/show-blog-archive-template.php index 4023a08..f9456de 100644 --- a/templates/show-blog-archive-template.php +++ b/templates/show-blog-archive-template.php @@ -1,92 +1,90 @@ - - - -
    -
    - - - - - - - $post_types, - 'posts_per_page' => 10, - 'orderby' => 'post_date', - 'order' => 'desc', - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $show_id - ) - ), - 'paged' => $paged - ); - $temp = $wp_query; - $wp_query = null; - $wp_query = new WP_Query( $args ); - ?> - have_posts()) : $wp_query->the_post(); ?> - -
    > -
    -

    - -
    - - - -
    -
    - -
    - -
    -
    - - - - - - - - - -
    -
    -

    -
    - -
    -

    - -
    -
    - - - -
    -
    - - \ No newline at end of file + + + +
    +
    + + + + + + + $post_types, + 'posts_per_page' => 10, + 'orderby' => 'post_date', + 'order' => 'desc', + 'meta_query' => array( + array( + 'key' => 'post_showblog_id', + 'value' => $show_id, + ), + ), + 'paged' => $paged, + ); + $archive_query = new WP_Query( $args ); + while ( $archive_query->have_posts() ) : + $archive_query->the_post(); + ?> + +
    > +
    +

    + +
    + - + +
    +
    + +
    + +
    +
    + + + + + + + +
    +
    +

    +
    + +
    +

    + +
    +
    + + + +
    +
    + + diff --git a/templates/single-playlist.php b/templates/single-playlist.php index 998baa5..b2eec2e 100644 --- a/templates/single-playlist.php +++ b/templates/single-playlist.php @@ -1,69 +1,86 @@ - -
    -
    - - - -
    > -
    - ID, 'playlist_show_id', true ); ?> -

    -

    -
    - -
    - - '' ) ); ?> - - - - - ID, 'playlist', true ); ?> - - -
    - - - - - - - - - - - - > - - - - - - - - -
    -
    - -
    - -
    - - - - - -
    -
    - - - -
    -
    - - \ No newline at end of file + +
    +
    + + + +
    > +
    + ID, 'playlist_show_id', true ); + ?> +

    +

    +
    + +
    + + '', + ) + ); + ?> + + + + + ID, 'playlist', true ); ?> + + +
    + + + + + + + + + + + + > + + + + + + + + +
    +
    + +
    + +
    + + + + + +
    +
    + + + +
    +
    + + diff --git a/templates/single-show.php b/templates/single-show.php index 04053ec..75d1023 100644 --- a/templates/single-show.php +++ b/templates/single-show.php @@ -1,132 +1,165 @@ - - -
    -
    - - -
    > -
    -

    -
    - -
    - - -
    -

    :

    - '.$user_info->display_name.''; - - if ( ( ( $count == 1 ) && ( count($djs) == 2 ) ) - || ( ( count($djs) > 2 ) && ( $count == ( count($djs) - 1 ) ) ) ) { - echo ' and '; - } elseif ( ( $count < count($djs) ) && ( count($djs) > 2 ) ) { - echo ', '; - } - } - } - ?> -
    - -
    -

    :

    - 'genres', 'title_li' => '') ); - ?> -
      - '.$genre->name.''; - } - ?> -
    -
    - -

    - - - - - -
    - -
    - -
    -

    -
      - '; - echo $weekday.' - '.$shift['start_hour'].':'.$shift['start_min'].' '.$shift['start_meridian'].' - '.$shift['end_hour'].':'.$shift['end_min'].' '.$shift['end_meridian']; - echo ''; - } - } - - // 24-hour time - /* - $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); - if ( $shifts ) { - foreach ( $shifts as $shift ) { - $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); - $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); - - $weekday = radio_station_translate_weekday( $shift['day'] ); - echo '
    • '; - echo $weekday.' - '.$start.' - '.$end; - echo '
    • '; - } - } - */ - ?> -
    -
    - -
    -

    - -
    - - - - - - '' ) ); ?> -
    -
    - - - - - -
    -
    - - \ No newline at end of file + + +
    +
    + + +
    > +
    +

    +
    + +
    + + +
    +

    :

    + ' . esc_html( $user_info->display_name ) . ''; + + $dj_count = count( $djs ); + if ( ( 1 === $count && 2 === $dj_count ) || ( $dj_count > 2 && $count === $dj_count - 1 ) ) { + echo ' and '; + } elseif ( ( $count < count( $djs ) ) && ( count( $djs ) > 2 ) ) { + echo ', '; + } + } + } + ?> +
    + +
    +

    :

    + 'genres', 'title_li' => '') ); + ?> +
      + ' . esc_html( $genre->name ) . ''; + } + ?> +
    +
    + +

    + + + + + +
    + + + +
    + +
    +

    +
      + '; + echo esc_html( $weekday . ' - ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] . ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian'] ); + echo ''; + } + } + + // 24-hour time + /* + $shifts = get_post_meta( get_the_ID(), 'show_sched', true ); + if ( $shifts ) { + foreach ( $shifts as $shift ) { + $start = date('H:i', strtotime('1981-04-28 '.$shift['start_hour'].':'.$shift['start_min'].':00 ')); + $end = date('H:i', strtotime('1981-04-28 '.$shift['end_hour'].':'.$shift['end_min'].':00 ')); + + $weekday = radio_station_translate_weekday( $shift['day'] ); + echo '
    • '; + echo $weekday.' - '.$start.' - '.$end; + echo '
    • '; + } + } + */ + ?> +
    +
    + +
    +

    + +
    + + + + + + '', + ) + ); + ?> +
    +
    + + + + + +
    +
    + + From 1a9ecc280882da8b9baef09cedb878c6c220fe58 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 4 Oct 2019 22:31:26 +1000 Subject: [PATCH 057/377] remove old master schedule templates --- templates/master-list-default.php | 206 ------------------------------ templates/master-list-div.php | 162 ----------------------- templates/master-list-list.php | 109 ---------------- 3 files changed, 477 deletions(-) delete mode 100644 templates/master-list-default.php delete mode 100644 templates/master-list-div.php delete mode 100644 templates/master-list-list.php diff --git a/templates/master-list-default.php b/templates/master-list-default.php deleted file mode 100644 index 86be524..0000000 --- a/templates/master-list-default.php +++ /dev/null @@ -1,206 +0,0 @@ -'; - -// output the headings in the correct order -$output .= ' '; -foreach ( $days_of_the_week as $weekday => $info ) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= '' . $heading . ''; -} -$output .= ''; -// $output .= ' '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').' '; - -if ( ! isset( $nextskip ) ) { - $nextskip = array(); -} - -foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= '
    '; - - if ( 12 === (int) $timeformat ) { - if ( 0 === $hour ) { - $output .= '12am'; - } elseif ( (int) $hour < 12 ) { - $output .= $hour . 'am'; - } elseif ( 12 === (int) $hour ) { - $output .= '12pm'; - } else { - $output .= ( $hour - 12 ) . 'pm'; - } - } else { - if ( $hour < 10 ) { - $output .= '0'; - } - $output .= $hour . ':00'; - } - - $output .= '
    '; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] === $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span'] - 1; - $curskip[ $x ]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { - //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { - // show runs from before midnight night until the next morning - if ( isset( $shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="' . $rowspan . '"'; - // add to both arrays - $curskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - $nextskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ( $continue ) { - continue; - } - - $output .= ''; - - foreach ( $min as $shift ) { - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; - foreach ( $terms as $shift_term ) { - $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; - } - - $output .= '
    '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $shift['id'] ) ) { - $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $shift['id'] ) . ''; - } else { - $output .= get_the_title( $shift['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - - $output .= ''; - - $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $dj_names ) { - - $output .= ' ' . __( 'with', 'radio-station' ) . ' '; - foreach ( $dj_names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $dj_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - $show_link = get_post_meta( $shift['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
    '; - } - $output .= ''; - } - - $output .= ''; -} -$output .= ''; diff --git a/templates/master-list-div.php b/templates/master-list-div.php deleted file mode 100644 index a5059ab..0000000 --- a/templates/master-list-div.php +++ /dev/null @@ -1,162 +0,0 @@ -'; -for ( $i = 2; $i < 24; $i++ ) { - $rowheight = $atts['divheight'] * $i; - - $output .= '#master-schedule-divs .rowspan' . $i . ' { '; - $output .= 'height: ' . ( $rowheight ) . 'px; }'; -} - -$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; -$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; -$output .= ''; - -// output the schedule -$output .= radio_station_master_fetch_js_filter(); -$output .= '
    '; -$weekdays = array_keys( $days_of_the_week ); - -$output .= '
    '; -$output .= '
     
    '; -foreach ( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
    ' . $translated . '
    '; -} - $output .= '
    '; - -foreach ( $master_list as $hour => $days ) { - - $output .= '
    '; - - // output the hour labels - $output .= '
    '; - if ( 12 === (int) $timeformat ) { - $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format - } - $output .= '
    '; - - foreach ( $weekdays as $weekday ) { - $output .= '
    '; - if ( isset( $days[ $weekday ] ) ) { - foreach ( $days[ $weekday ] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; - foreach ( $terms as $show_term ) { - $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; - } - - $output .= '
    '; - - // featured image - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $showdata['id'] ) . ''; - } else { - $output .= get_the_title( $showdata['id'] ); - } - $output .= ''; - - // list of DJs - if ( $atts['show_djs'] ) { - - $output .= ''; - - $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $show_names ) { - - $output .= ' with '; - - foreach ( $show_names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $show_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - // designate as encore - if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - // link to media file - $show_link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
    '; - - if ( '00' !== $showdata['time']['end_min'] ) { - $output .= '
    '; - } - } - - $output .= '
    '; // end master-show-entry - } - } - $output .= '
    '; // end master-schedule-weekday - } - $output .= '
    '; // end master-schedule-hour -} -$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-list-list.php b/templates/master-list-list.php deleted file mode 100644 index 46b4a28..0000000 --- a/templates/master-list-list.php +++ /dev/null @@ -1,109 +0,0 @@ - $days ) { - foreach ( $days as $day => $mins ) { - foreach ( $mins as $fmin => $fshow ) { - $flip[ $day ][ $hour ][ $fmin ] = $fshow; - } - } -} - -$output .= '
      '; - -foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
    • '; - $output .= '' . $display_day . ''; - $output .= '
        '; - foreach ( $hours as $hour => $mins ) { - - foreach ( $mins as $min => $show ) { - $output .= '
      • '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $show['id'] ) . ''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - $output .= ''; - - $show_names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $show_names ) { - $output .= ' with '; - foreach ( $show_names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $show_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } else { - - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { - $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; - } - - $show_link = get_post_meta( $show['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
      • '; - } - } - $output .= '
      '; - $output .= '
    • '; -} - -$output .= '
    '; From 1d420ff2ad1074ab027e661f14b37a74e78616bc Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 5 Oct 2019 01:28:04 +1000 Subject: [PATCH 058/377] fix to show blog post listing --- includes/support-functions.php | 3 ++- readme.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index c080263..25fedb5 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -417,6 +417,7 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = if ( $blog_array ) { + // 2.2.8: fix to implode blog array to string $blogposts = $wpdb->get_results( $wpdb->prepare( "SELECT posts.ID, posts.post_title @@ -425,7 +426,7 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = posts.post_status = 'publish' ORDER BY posts.post_date DESC LIMIT %d", - $blog_array, + implode( ',', $blog_array ), $limit ) ); diff --git a/readme.txt b/readme.txt index 4726cf2..4f299ef 100644 --- a/readme.txt +++ b/readme.txt @@ -250,6 +250,7 @@ You may translate the plugin into another language. Please visit our WordPress T * Fix to mismatched flush rewrite rules flag function name * Fix to undefined index warnings for new Schedule Overrides * Fix to not 404 author pages for DJs without blog posts +* Fix to implode blog array for Show blog post listing = 2.2.7 = * Dutch translation added (Thank you to André Dortmont for the file!) From c9a347a34b1c11dd65ef0ace61c6c9084b258d4e Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 4 Oct 2019 11:58:17 -0400 Subject: [PATCH 059/377] Commit new read me changes --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/.DS_Store b/.DS_Store index d17289c0438c1c252e15a35022f1610dcdb9984b..88465cf71b5e291e337b85b634223f88c15e90c3 100644 GIT binary patch delta 67 zcmZokXi1ph&nUGqU^hP_-{g(zVw;r&ZnATx6es5-<>%)xY Date: Sun, 20 Oct 2019 21:47:37 -0400 Subject: [PATCH 060/377] change readme to .md Change to .md --- readme.txt => readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.txt => readme.md (100%) diff --git a/readme.txt b/readme.md similarity index 100% rename from readme.txt rename to readme.md From 2acc855c7a3020da51a190b442139d832a3f6c21 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sun, 20 Oct 2019 22:11:24 -0400 Subject: [PATCH 061/377] update readme.md new show description --- .DS_Store | Bin 12292 -> 12292 bytes readme.md | 16 +++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.DS_Store b/.DS_Store index 88465cf71b5e291e337b85b634223f88c15e90c3..6a3014a8b0b6a3a1763b068443a9199686b1ae68 100644 GIT binary patch delta 58 zcmZokXi3;GQ%snXp@<=sA(0`4As0yN0m+oj>%?L?H*+f7W}p05Omy;Zah}br8uwuw FZU6*N66pW{ delta 52 zcmZokXi3;GQ%sbLp@<=sA(0`4As0yNF_bV=FqCXwBNofKnOor|`{YOB%A46W?t&N! E0FRju{Qv*} diff --git a/readme.md b/readme.md index d11d4ea..e1822aa 100644 --- a/readme.md +++ b/readme.md @@ -14,21 +14,27 @@ Radio Station is a plugin to build and manage a Show Calendar in a radio station Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. -The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. +The plugin includes the ability to associate users (as member role "DJ") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. +Posts can be assigned to Shows by creating a Post and using the meta-box to assign it to a specific Show. They will appear below the Show Description and Playlist on a Show page. Please use this function as your archive for each show, so that each Show archive has it's own URL and can be crawled for SEO purposes. Use your favorite SEO plugin to manage SEO on Show pages and Posts. We prefer [All in One SEO Pack] (https://wordpress.org/plugins/all-in-one-seo-pack/). + We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. -Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues +You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. +== Plugin Support == For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ -You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com +== Development == + +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. + +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues == Installation == From 7ca943fe447b9426c0b1885b2d28427d9967963f Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 25 Nov 2019 07:59:38 -0500 Subject: [PATCH 062/377] Create developer_task --- .github/ISSUE_TEMPLATE/developer_task | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/developer_task diff --git a/.github/ISSUE_TEMPLATE/developer_task b/.github/ISSUE_TEMPLATE/developer_task new file mode 100644 index 0000000..cf3411b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/developer_task @@ -0,0 +1,20 @@ +--- +name: Developer task +about: Suggest an specific task for a developer +title: '' +labels: '' +assignees: '' + +--- + +**Is your developer task related to a problem or a requirement? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 5889ccaf66d8b87f1eca5f7a34842f7cadbf2337 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 25 Nov 2019 08:00:19 -0500 Subject: [PATCH 063/377] update dev task --- .github/ISSUE_TEMPLATE/{developer_task => developer_task.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{developer_task => developer_task.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/developer_task b/.github/ISSUE_TEMPLATE/developer_task.md similarity index 100% rename from .github/ISSUE_TEMPLATE/developer_task rename to .github/ISSUE_TEMPLATE/developer_task.md From 46b2e918ecbf14e89a7579f8c0831ba686a4a92a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 4 Oct 2019 22:31:26 +1000 Subject: [PATCH 064/377] remove old master schedule templates --- templates/master-list-default.php | 206 ------------------------------ templates/master-list-div.php | 162 ----------------------- templates/master-list-list.php | 109 ---------------- 3 files changed, 477 deletions(-) delete mode 100644 templates/master-list-default.php delete mode 100644 templates/master-list-div.php delete mode 100644 templates/master-list-list.php diff --git a/templates/master-list-default.php b/templates/master-list-default.php deleted file mode 100644 index 86be524..0000000 --- a/templates/master-list-default.php +++ /dev/null @@ -1,206 +0,0 @@ -'; - -// output the headings in the correct order -$output .= ' '; -foreach ( $days_of_the_week as $weekday => $info ) { - // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading, true ); - $output .= '' . $heading . ''; -} -$output .= ''; -// $output .= ' '.__('Sun', 'radio-station').' '.__('Mon', 'radio-station').' '.__('Tue', 'radio-station').' '.__('Wed', 'radio-station').' '.__('Thu', 'radio-station').' '.__('Fri', 'radio-station').' '.__('Sat', 'radio-station').' '; - -if ( ! isset( $nextskip ) ) { - $nextskip = array(); -} - -foreach ( $master_list as $hour => $days ) { - - $output .= ''; - $output .= '
    '; - - if ( 12 === (int) $timeformat ) { - if ( 0 === $hour ) { - $output .= '12am'; - } elseif ( (int) $hour < 12 ) { - $output .= $hour . 'am'; - } elseif ( 12 === (int) $hour ) { - $output .= '12pm'; - } else { - $output .= ( $hour - 12 ) . 'pm'; - } - } else { - if ( $hour < 10 ) { - $output .= '0'; - } - $output .= $hour . ':00'; - } - - $output .= '
    '; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] === $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span'] - 1; - $curskip[ $x ]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $shift ) { - - if ( 0 === (int) $shift['time']['start_hour'] && 0 === (int) $shift['time']['end_hour'] ) { ///midnight to midnight shows - if ( $shift['time']['start_min'] === $shift['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( 0 === (int) $shift['time']['end_hour'] && 0 !== (int) $shift['time']['start_hour'] ) { - //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } elseif ( $shift['time']['start_hour'] > $shift['time']['end_hour'] ) { - // show runs from before midnight night until the next morning - if ( isset( $shift['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $shift['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $shift['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ( $shift['time']['end_hour'] - $shift['time']['start_hour'] ); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="' . $rowspan . '"'; - // add to both arrays - $curskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - $nextskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $shift['id'] ), - ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ( $continue ) { - continue; - } - - $output .= ''; - - foreach ( $min as $shift ) { - - $terms = wp_get_post_terms( $shift['id'], 'genres', array() ); - $classes = ' show-id-' . $shift['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $shift['id'] ) ) ) . ' '; - foreach ( $terms as $shift_term ) { - $classes .= sanitize_title_with_dashes( $shift_term->name ) . ' '; - } - - $output .= '
    '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $shift['id'] ) ) { - $output .= get_the_post_thumbnail( $shift['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $shift['id'] ) . ''; - } else { - $output .= get_the_title( $shift['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - - $output .= ''; - - $dj_names = get_post_meta( $shift['id'], 'show_user_list', true ); - $count = 0; - - if ( $dj_names ) { - - $output .= ' ' . __( 'with', 'radio-station' ) . ' '; - foreach ( $dj_names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $dj_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ' ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['start_hour'] . ':' . $shift['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $shift['time']['end_hour'] . ':' . $shift['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - if ( isset( $shift['time']['encore'] ) && 'on' === $shift['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - $show_link = get_post_meta( $shift['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
    '; - } - $output .= ''; - } - - $output .= ''; -} -$output .= ''; diff --git a/templates/master-list-div.php b/templates/master-list-div.php deleted file mode 100644 index a5059ab..0000000 --- a/templates/master-list-div.php +++ /dev/null @@ -1,162 +0,0 @@ -'; -for ( $i = 2; $i < 24; $i++ ) { - $rowheight = $atts['divheight'] * $i; - - $output .= '#master-schedule-divs .rowspan' . $i . ' { '; - $output .= 'height: ' . ( $rowheight ) . 'px; }'; -} - -$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; -$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; -$output .= ''; - -// output the schedule -$output .= radio_station_master_fetch_js_filter(); -$output .= '
    '; -$weekdays = array_keys( $days_of_the_week ); - -$output .= '
    '; -$output .= '
     
    '; -foreach ( $weekdays as $weekday ) { - $translated = radio_station_translate_weekday( $weekday ); - $output .= '
    ' . $translated . '
    '; -} - $output .= '
    '; - -foreach ( $master_list as $hour => $days ) { - - $output .= '
    '; - - // output the hour labels - $output .= '
    '; - if ( 12 === (int) $timeformat ) { - $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 12-hour format - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); //random date needed to convert time to 24-hour format - } - $output .= '
    '; - - foreach ( $weekdays as $weekday ) { - $output .= '
    '; - if ( isset( $days[ $weekday ] ) ) { - foreach ( $days[ $weekday ] as $min => $showdata ) { - - $terms = wp_get_post_terms( $showdata['id'], 'genres', array() ); - $classes = ' show-id-' . $showdata['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $showdata['id'] ) ) ) . ' '; - foreach ( $terms as $show_term ) { - $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; - } - - $output .= '
    '; - - // featured image - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $showdata['id'] ) ) { - $output .= get_the_post_thumbnail( $showdata['id'], 'thumbnail' ); - } - $output .= ''; - } - - // title + link to page if requested - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $showdata['id'] ) . ''; - } else { - $output .= get_the_title( $showdata['id'] ); - } - $output .= ''; - - // list of DJs - if ( $atts['show_djs'] ) { - - $output .= ''; - - $show_names = get_post_meta( $showdata['id'], 'show_user_list', true ); - $count = 0; - - if ( $show_names ) { - - $output .= ' with '; - - foreach ( $show_names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $show_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // show's schedule - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - // $output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } else { - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['start_hour'] . ':' . $showdata['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $showdata['time']['end_hour'] . ':' . $showdata['time']['end_min'] . ':00 ' ) ); - } - $output .= ''; - } - - // designate as encore - if ( isset( $showdata['time']['encore'] ) && 'on' === $showdata['time']['encore'] ) { - $output .= '' . __( 'encore airing', 'radio-station' ) . ''; - } - - // link to media file - $show_link = get_post_meta( $showdata['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= '' . __( 'Audio File', 'radio-station' ) . ''; - } - - // calculate duration of show for rowspanning - if ( isset( $showdata['time']['rollover'] ) ) { //show started on the previous day - $duration = $showdata['time']['end_hour']; - } else { - if ( $showdata['time']['end_hour'] >= $showdata['time']['start_hour'] ) { - $duration = $showdata['time']['end_hour'] - $showdata['time']['start_hour']; - } else { - $duration = 23 - $showdata['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
    '; - - if ( '00' !== $showdata['time']['end_min'] ) { - $output .= '
    '; - } - } - - $output .= '
    '; // end master-show-entry - } - } - $output .= '
    '; // end master-schedule-weekday - } - $output .= '
    '; // end master-schedule-hour -} -$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-list-list.php b/templates/master-list-list.php deleted file mode 100644 index 46b4a28..0000000 --- a/templates/master-list-list.php +++ /dev/null @@ -1,109 +0,0 @@ - $days ) { - foreach ( $days as $day => $mins ) { - foreach ( $mins as $fmin => $fshow ) { - $flip[ $day ][ $hour ][ $fmin ] = $fshow; - } - } -} - -$output .= '
      '; - -foreach ( $flip as $day => $hours ) { - - // 2.2.2: use translate function for weekday string - $display_day = radio_station_translate_weekday( $day ); - $output .= '
    • '; - $output .= '' . $display_day . ''; - $output .= '
        '; - foreach ( $hours as $hour => $mins ) { - - foreach ( $mins as $min => $show ) { - $output .= '
      • '; - - if ( $atts['show_image'] ) { - $output .= ''; - if ( has_post_thumbnail( $show['id'] ) ) { - $output .= get_the_post_thumbnail( $show['id'], 'thumbnail' ); - } - $output .= ''; - } - - $output .= ''; - if ( $atts['show_link'] ) { - $output .= '' . get_the_title( $show['id'] ) . ''; - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - $output .= ''; - - $show_names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $show_names ) { - $output .= ' with '; - foreach ( $show_names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $show_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' and '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ' '; - } - - if ( $atts['display_show_time'] ) { - - $output .= ''; - - if ( 12 === (int) $timeformat ) { - - //$output .= $weekday.' '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } else { - - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $output .= ' - '; - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - - } - - $output .= ''; - } - - if ( isset( $show['time']['encore'] ) && 'on' === $show['time']['encore'] ) { - $output .= ' ' . __( 'encore airing', 'radio-station' ) . ''; - } - - $show_link = get_post_meta( $show['id'], 'show_file', true ); - if ( $show_link && ! empty( $show_link ) ) { - $output .= ' ' . __( 'Audio File', 'radio-station' ) . ''; - } - - $output .= '
      • '; - } - } - $output .= '
      '; - $output .= '
    • '; -} - -$output .= '
    '; From 790029c4451135754a661d2668cdbaeeccfdc9d3 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 5 Oct 2019 01:28:04 +1000 Subject: [PATCH 065/377] fix to show blog post listing --- includes/support-functions.php | 3 ++- readme.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index c080263..25fedb5 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -417,6 +417,7 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = if ( $blog_array ) { + // 2.2.8: fix to implode blog array to string $blogposts = $wpdb->get_results( $wpdb->prepare( "SELECT posts.ID, posts.post_title @@ -425,7 +426,7 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = posts.post_status = 'publish' ORDER BY posts.post_date DESC LIMIT %d", - $blog_array, + implode( ',', $blog_array ), $limit ) ); diff --git a/readme.txt b/readme.txt index 4726cf2..4f299ef 100644 --- a/readme.txt +++ b/readme.txt @@ -250,6 +250,7 @@ You may translate the plugin into another language. Please visit our WordPress T * Fix to mismatched flush rewrite rules flag function name * Fix to undefined index warnings for new Schedule Overrides * Fix to not 404 author pages for DJs without blog posts +* Fix to implode blog array for Show blog post listing = 2.2.7 = * Dutch translation added (Thank you to André Dortmont for the file!) From b7aabbecc66787fbcade4420b94d473454d47e57 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 4 Oct 2019 11:58:17 -0400 Subject: [PATCH 066/377] Commit new read me changes --- .DS_Store | Bin 12292 -> 12292 bytes readme.txt | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/.DS_Store b/.DS_Store index d17289c0438c1c252e15a35022f1610dcdb9984b..88465cf71b5e291e337b85b634223f88c15e90c3 100644 GIT binary patch delta 67 zcmZokXi1ph&nUGqU^hP_-{g(zVw;r&ZnATx6es5-<>%)xY Date: Sun, 20 Oct 2019 21:47:37 -0400 Subject: [PATCH 067/377] change readme to .md Change to .md --- readme.txt => readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.txt => readme.md (100%) diff --git a/readme.txt b/readme.md similarity index 100% rename from readme.txt rename to readme.md From 8f1fb5d3968efab90446af9c33c5b2786a04a545 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sun, 20 Oct 2019 22:11:24 -0400 Subject: [PATCH 068/377] update readme.md new show description --- .DS_Store | Bin 12292 -> 12292 bytes readme.md | 16 +++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.DS_Store b/.DS_Store index 88465cf71b5e291e337b85b634223f88c15e90c3..6a3014a8b0b6a3a1763b068443a9199686b1ae68 100644 GIT binary patch delta 58 zcmZokXi3;GQ%snXp@<=sA(0`4As0yN0m+oj>%?L?H*+f7W}p05Omy;Zah}br8uwuw FZU6*N66pW{ delta 52 zcmZokXi3;GQ%sbLp@<=sA(0`4As0yNF_bV=FqCXwBNofKnOor|`{YOB%A46W?t&N! E0FRju{Qv*} diff --git a/readme.md b/readme.md index d11d4ea..e1822aa 100644 --- a/readme.md +++ b/readme.md @@ -14,21 +14,27 @@ Radio Station is a plugin to build and manage a Show Calendar in a radio station Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. -The plugin includes the ability to associate users (as member role "DJ"") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. +The plugin includes the ability to associate users (as member role "DJ") with the included custom post type of "Shows" (schedulable blocks of time that contain a Show description, and other meta information), and generate playlists associated with those shows. The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. +Posts can be assigned to Shows by creating a Post and using the meta-box to assign it to a specific Show. They will appear below the Show Description and Playlist on a Show page. Please use this function as your archive for each show, so that each Show archive has it's own URL and can be crawled for SEO purposes. Use your favorite SEO plugin to manage SEO on Show pages and Posts. We prefer [All in One SEO Pack] (https://wordpress.org/plugins/all-in-one-seo-pack/). + We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. -Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues +You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. +== Plugin Support == For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ -You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com +== Development == + +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. + +Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues == Installation == From 47ddcaac9570ce5b28bcd7c83682af6038fc2931 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 25 Nov 2019 07:59:38 -0500 Subject: [PATCH 069/377] Create developer_task --- .github/ISSUE_TEMPLATE/developer_task | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/developer_task diff --git a/.github/ISSUE_TEMPLATE/developer_task b/.github/ISSUE_TEMPLATE/developer_task new file mode 100644 index 0000000..cf3411b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/developer_task @@ -0,0 +1,20 @@ +--- +name: Developer task +about: Suggest an specific task for a developer +title: '' +labels: '' +assignees: '' + +--- + +**Is your developer task related to a problem or a requirement? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 21c870d11db1ecd5514a96349f194931df2fd676 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 25 Nov 2019 08:00:19 -0500 Subject: [PATCH 070/377] update dev task --- .github/ISSUE_TEMPLATE/{developer_task => developer_task.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{developer_task => developer_task.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/developer_task b/.github/ISSUE_TEMPLATE/developer_task.md similarity index 100% rename from .github/ISSUE_TEMPLATE/developer_task rename to .github/ISSUE_TEMPLATE/developer_task.md From aaddcbb5b89119cf6162fcf983ef5e602a8ae89e Mon Sep 17 00:00:00 2001 From: Mark Nyon Date: Sun, 1 Dec 2019 17:20:59 -0500 Subject: [PATCH 071/377] Added CHANGELOG.md and Upgrade Notices to README.md #1 - CHANGELOG.md - Migrated from the readme.txt file - readme.md - Added the Upgrade Notices section from readme.txt --- readme.md | 425 ++++++++---------------------------------------------- 1 file changed, 61 insertions(+), 364 deletions(-) diff --git a/readme.md b/readme.md index e1822aa..79ed5df 100644 --- a/readme.md +++ b/readme.md @@ -249,317 +249,14 @@ Catalan (ca) You may translate the plugin into another language. Please visit our WordPress Translate project page for this plugin for further instruction: https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/ The radio-station.pot file is located in the /languages directory of the plugin. Please send the finished translation to info@netmix.com. We'd love to include it. -== Changelog == +## [CHANGELOG.md](file:///CHANGELOG.md) -= 2.2.8 = -* Fix to remove strict type checking from in_array (introduced 2.2.6) -* Fix to mismatched flush rewrite rules flag function name -* Fix to undefined index warnings for new Schedule Overrides -* Fix to not 404 author pages for DJs without blog posts -* Fix to implode blog array for Show blog post listing +## Upgrade Notices -= 2.2.7 = -* Dutch translation added (Thank you to André Dortmont for the file!) -* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) -* Add Show list columns with active, shift, DJs and show image displays -* Add Schedule Override list columns with date sorting and filtering -* Add playlist track information labels to Now Playing Widget -* Added meridiem (am/pm) translations via WP Locale class -* Added star rating link to plugin announcement box -* Added update subscription form to plugin Help page -* Fix to checkbox value saving for On Air/Upcoming Widgets -* Fix 12 hour show time display in Upcoming Widget -* Fix PM 12 hour shot time display in On Air Widget -* Fix to schedule override date picker value visibility -* Fix to weekday and month translations to use WP Locale -* Fix to checkbox value saving in Upcoming Widget -* Split Plugin Admin Functions into separate file -* Split Post Type Admin Functions into separate include -* Revert anonymous function use in widget registrations - -= 2.2.6 = -* Reorganize master-list shortcode into templates -* Add constant for plugin directory -* Use WP_Query instead of get_posts -* New posts_per_page and tax_query -* Fixes for undefined indexes -* Fixes for raw mysql queries -* Typecasting to support strict comparisons - -= 2.2.5 = -* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) - -= 2.2.4 = -* added title position and avatar width options to widgets -* added missing DJ author links as new option to widgets -* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) -* improved to show Encore Presentation in show widget displays -* fix to Show shift Encore Presentation checkbox saving - -= 2.2.3 = -* added flush rewrite rules on plugin activation/deactivation -* added show_admin_column and show_in_quick_edit for Genres -* added show metadata and schedule value sanitization -* fix to 00 minute validation for Schedule Override -* convert span tags to div tags in Widgets to fix line breaks - -= 2.2.2 = -* shift main playlist and show metaboxes above editor -* set plugin custom post types editor to Classic Editor -* add high priority to side metaboxes for plugin post types -* added dismissable development changeover admin notice -* added simple Patreon supporter image button and blurb -* added filter for DJ Avatar size on Author page template -* fix to Schedule Override metabox value saving -* fix to Playlist track list items overflowing metabox -* fix to shift up time row on Master Schedule table view -* fix to missing weekday headings in Master Schedule table -* fix to weekday display for Upcoming DJ Widget -* fix to user display labels on select DJ metabox -* fix to file_exists check for DJ on Air stylesheet path -* fix to make DJ multi-select input full metabox width -* fix to expand admin menu when on genre taxonomy page -* fix to expand admin menu when editing plugin post types -* fix to genre submenu item link for current page -* added GitHub URI to plugin header for GitHub updater - -= 2.2.1 = -* Re-commit all missing files via SVN - -= 2.2.0 = -* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) -* fixed the protocol in jQuery UI style Google URL -* reprefixed all functions for consistency (radio_station_) -* updated all the widget constructor methods -* merged the menu items into a single main menu -* updated the capability checks for the menu items -* moved the help and export pages to /templates/ -* moved all the css files to /css/ -* enqeued the djonair css from within the widget -* use plugins_url for all resource URLs -* added $wpdb->prepare to sanitize a query -* added some sanization for metabox save values -* added a week and month translation helper -* added a radio station antenna icon - -= 2.1.3 = -* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). - -= 2.1.2 = -* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. -* Catalan translation added (Thank you to Victor Riera for the file!) - -= 2.1.1 = -* Bug fix - Fixed day of the week language translation issue in master schedule shortcode -* Bug fix - Added some error checking in the sidebar widgets -* New Feature - Added ability to give schedule overrides a featured image -* New Feature - Added built-in help page - -= 2.1 = -* General code cleanup, 4.1 compatibility testing, and changes for better efficiency. -* Bug fix - Fixed issue with early morning shows spanning entire column in the programming grid shortcode -* New Feature - Master programming grid can now be displayed in div format, as well as the original table and list formats. - -= 2.0.16 = -* Minor revisions to German translation. -* Fixed a bug that was resetting custom-sert role capabilities for the DJ role. - -= 2.0.15 = -* German translation added (Thank you to Ian Hook for the file!) - -= 2.0.14 = -* Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked -* Added ability to display DJ names on the master schedule. -* Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. -* Verified compatibility woth WordPress 4.0 - -= 2.0.13 = -* Added the ability to display show avatars on the program grid. -* Added the ability to display show description in the now on-air widget and short code. - -= 2.0.12 = -* Fixed a bug in the master schedule shortcode - -= 2.0.11 = -* Russian translation added (Thank you to Alexander Esin for the file!) - -= 2.0.10 = -* Fixed role/capability conflict with WP User Avatar plugin. -* Added the missing leading zero to 24-hour time format on the master schedule. -* Fixed dj_get_current function so that it no longer returns shows that have been moved to the trash. -* Fixed dj_get_next function so that it no longer ignores the "Active" checkbox on a show. -* Added some CSS ids and classes to the master program schedule list format to make it more useful - -= 2.0.9 = -* Fixed broken upcoming show shortcode. -* Added ability to display DJ names along with the show title in the widgets. - -= 2.0.8 = -* Fixed the display of schedules for upcoming shows in the widget and shortcode. -* Fixed a bug in the dj_get_next function that was causing it to ignore the beginning of the next week at the end of the current week. - -= 2.0.7 = -* Fixed scheduling bug in shortcode function - -= 2.0.6 = -* Master Schedule now displays days starting with the start_of_week option set in the WordPress General Settings panel. -* Fixed issue with shows that have been unplublished still showing up on the master schedule. -* Fixed missing am/pm text on shows that run overnight on the master schedule. -* Fixed an issue with shows that run overnight not spanning the correct number of hours on the second day on the master schedule. -* Fixed problem in Upcoming DJ Widget that wasn't displaying the correct upcoming shift. - -= 2.0.5 = -* Fixed an issue with some shows displaying in 24 hour time on master schedule grid even though 12-hour time is specified -* Fixed a bug in the On-Air widget that was preventing shows spanning two day from displaying -* Added code to enable theme support for post-thumbnails on the "show" post-type so users don't have to add it to their theme's functions.php file anymore. - -= 2.0.4 = -* Master Schedule bug for shows that start at midnight and end before the hour is up fixed. - -= 2.0.3 = -* Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes - -= 2.0.2 = -* Bug fix: Scheduling issue with overnight shows fixed - -= 2.0.1 = -* Bug fix: Fixed PHP error in Playlist save function that was triggered during preview -* Bug fix: Fixed PHP notice in playlist template file -* Bug fix: Fixed PHP error in dj-widget shortcode - -= 2.0.0 = -* Major code reorganization for better future development -* PHP warning fix -* Enabled option to add comments on Shows and Playlists -* Added option to show either single or multiple schedules in the On Air widget - -= 1.6.2 = -* Minor PHP warning fixes - -= 1.6.1 = -* Bug fix: Some of the code added in the previous update uses the array_replace() function that is only available in PHP 5.3+. Added a fallback for older PHP versions. - -= 1.6.0 = -* Added the ability to override the weekly schedule to allow one-off events to be scheduled -* Added a list format option to the master schedule shortcode -* Added Italian translation (it_IT) (thank you to Cristofaro Giuseppe!) - -= 1.5.4 = -* Fixed some PHP notices that were being generated when there were no playlist entries in the system. - -= 1.5.3 = -* Added Serbian translation (sr_RS) (thank you to Miodarag Zivkovic!) - -= 1.5.2.1 = -* Removed some debug code from one of the template files - -= 1.5.2 = -* Fixed some localization bugs. -* Added Albanian translation (sq_AL) (thank you to Lorenc!) - -= 1.5.1 = -* Fixed some localization bugs. -* Added French translation (fr_FR) (a big thank you to Dan over at BuddyPress France - http://bp-fr.net/) - -= 1.5.0 = -* Plugin modified to allow for internationalization. -* Spanish translation (es_ES) added. - -= 1.4.6 = -* Fixed a bug with shows that start at midnight not displaying in the on-air sidebar widget. -* Switched DJ/Show avatars in the widgets to use the featured image of the show instead of gravatar. -* Updated show template to get rid of a PHP warning that appeared if the show had no schedules. -* Fixed some other areas of the code that were generating PHP notices in WordPress 3.6 -* Added CSS classes to master program schedule output so CSS rules can be applied to specific shows -* Added new attribute to the list-shows shortcode to allow only specified genres to be displayed - -= 1.4.5 = -* Fixed master-schedule shortcode bug that was preventing display of 12 hour time - -= 1.4.4 = -* Compatibility fix for Wordpress 3.6 - fixed problem with giving alternative roles DJ capabilities -* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 - -= 1.4.3 = -* Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time -* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 -* Added example of how to display show schedule to single-show.php template -* Added more options to the plugin's widgets -* Added new options to the master-schedule shortcode - -= 1.4.2 = -* Fixed a bug in the CSS file override from theme directory - -= 1.4.1 = -* Fixed issue with templates copied to the theme directory not overriding the defaults correctly -* Fixed incorrectly implemented wp_enqueue_styles() -* Removed deprecated escape_attribute() function from the plugin widgets -* Fixed some areas of the code that were generating PHP notices - -= 1.4.0 = -* Compatibility fix for WordPress 3.6 - -= 1.3.9 = -* Fixed a bug that was preventing sites using a non-default table prefix from seeing the list of DJs on the add/edit show pages - -= 1.3.8 = -* Changes to fix the incorrect list of available shows on the Add Playlist page -* Removing Add Show links from admin menu for DJs, since they don't have permission to use them anyway. - -= 1.3.7 = -* Fixed a scheduling bug in the upcoming shows widget -* By popular request, switched the order of artist and song in the now playing widget - -= 1.3.6 = -* Fixed issue with shows that run overnight not showing up correctly in the sidebar widgets - -= 1.3.5 = -* Fixed a time display bug in the DJ On-Air sidebar widget -* Fixed a display bug on the master schedule with overnight shows - -= 1.3.4 = -* By request, added as 24-hour time format option to the master schedule and sidebar widgets. - -= 1.3.3 = -* Added the ability to assign any user with the edit_shows capability as a DJ, to accomodate custom and edited roles. - -= 1.3.2 = -* Fixed a bug in the DJ-on-air widget - -= 1.3.1 = -* Fixed a major bug in the master schedule output - -= 1.3 = -* Fixed some minor compatibility issues with WordPress 3.5 -* Fixed Shows icon in Dashboard - -= 1.2 = -* Fixed thumbnail bug in sidebar widgets -* Added new widget to display upcoming shows -* Added pagination options for playlists and show blogs - -= 1.1 = -* Fixed playlist edit screen so that queued songs fall to the bottom of the list to maintain play order -* Reduced the size of the content field in the playlist post type -* Some minor formatting changes to default templates -* Added genre highlighter to the master programming schedule page -* Added a second Update button on the bottom of the playlist edit page for convinience. -* Added sample template for DJ user pages -* Fixed a bug in the master schedule shortcode that messed up the table for shows that are more than two hours in duration -* Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. -* Added new field to associate blog posts with shows - -= 1.0 = -* Initial release - -== Upgrade Notice == - -= 2.2.8 = - -= 2.2.8 = +## 2.2.8 * Fix to remove strict type checking from in_array (introduced 2.2.6) which fixes DJ can't edit Show issue; mismatched flush rewrite rules flag function name; undefined index warnings for new Schedule Overrides; not 404 author pages for DJs without blog posts; implode blog array for Show blog post listing (introduced 2.2.6) which affected display of blog posts on Show page. -= 2.2.7 = +## 2.2.7 * Dutch translation added (Thank you to André Dortmont for the file!) * Added Tabbed Display for Master Schedule Shortcode (via Tutorial) * Add Show list columns with active, shift, DJs and show image displays @@ -578,7 +275,7 @@ You may translate the plugin into another language. Please visit our WordPress T * Split Post Type Admin Functions into separate include * Revert anonymous function use in widget registrations -= 2.2.6 = +## 2.2.6 * Reorganize master-list shortcode into templates, * Add constant for plugin directory, * WP_Query instead of get_posts, @@ -587,20 +284,20 @@ You may translate the plugin into another language. Please visit our WordPress T * fixes for raw mysql queries, * typecasting to support strict comparisons. -= 2.2.5 = +## 2.2.5 * WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) -= 2.2.4 = +## 2.2.4 Adds title position and avatar width options to widgets; missing DJ author links as new option to widgets; cleanup, improve and fix enqueued Widget CSS (on air/upcoming); show Encore Presentation in show widget displays; fix to Show shift Encore Presentation checkbox saving -= 2.2.3 = +## 2.2.3 * added flush rewrite rules on plugin activation/deactivation * added show_admin_column and show_in_quick_edit for Genres * added show metadata and schedule value sanitization * fix to 00 minute validation for Schedule Override * convert span tags to div tags in Widgets to fix line breaks -= 2.2.2 = +## 2.2.2 * shift main playlist and show metaboxes above editor * set plugin custom post types editor to Classic Editor * add high priority to side metaboxes for plugin post types @@ -617,136 +314,136 @@ Adds title position and avatar width options to widgets; missing DJ author links * fix to genre submenu item link for current page * added GitHub URI to plugin header for GitHub updater -= 2.2.1 = +## 2.2.1 * Re-commit all missing files via SVN -= 2.2.0 = +## 2.2.0 * WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) -= 2.1.3 = +## 2.1.3 * Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). -= 2.1.2 = +## 2.1.2 * Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. * Catalan translation added (Thank you to Victor Riera for the file!) -= 2.1.1 = +## 2.1.1 * Bug fix - Fixed day of the week language translation issue in master schedule shortcode * Bug fix - Added some error checking in the sidebar widgets * New Feature - Added ability to give schedule overrides a featured image * New Feature - Added built-in help page -= 2.1 = +## 2.1 * General code cleanup, 4.1 compatibility testing, and changes for better efficiency. * Bug fix - Fixed issue with early morning shows spanning entire column in the programming grid shortcode * New Feature - Master programming grid can now be displayed in div format, as well as the original table and list formats. -= 2.0.16 = +## 2.0.16 * Minor revisions to German translation. * Fixed a bug that was resetting custom-sert role capabilities for the DJ role. -= 2.0.15 = +## 2.0.15 * German translation added (Thank you to Ian Hook for the file!) -= 2.0.14 = +## 2.0.14 * Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked * Added ability to display DJ names on the master schedule. * Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. * Verified compatibility woth WordPress 4.0 -= 2.0.13 = +## 2.0.13 * Added the ability to display show avatars on the program grid. * Added the ability to display show description in the now on-air widget and short code. -= 2.0.12 = +## 2.0.12 * Fixed a bug in the master schedule shortcode -= 2.0.11 = +## 2.0.11 * Russian translation added (Thank you to Alexander Esin for the file!) -= 2.0.10 = +## 2.0.10 * Fixed role/capability conflict with WP User Avatar plugin. * Added the missing leading zero to 24-hour time format on the master schedule. * Fixed dj_get_current function so that it no longer returns shows that have been moved to the trash. * Fixed dj_get_next function so that it no longer ignores the "Active" checkbox on a show. * Added some CSS ids and classes to the master program schedule list format to make it more useful -= 2.0.9 = +## 2.0.9 * Fixed broken upcoming show shortcode. * Added ability to display DJ names along with the show title in the widgets. -= 2.0.8 = +## 2.0.8 * Fixed the display of schedules for upcoming shows in the widget and shortcode. * Fixed a bug in the dj_get_next function that was causing it to ignore the beginning of the next week at the end of the current week. -= 2.0.7 = +## 2.0.7 * Fixed scheduling bug in shortcode function -= 2.0.6 = +## 2.0.6 * Master Schedule now displays days starting with the start_of_week option set in the WordPress General Settings panel. * Fixed issue with shows that have been unplublished still showing up on the master schedule. * Fixed missing am/pm text on shows that run overnight on the master schedule. * Fixed an issue with shows that run overnight not spanning the correct number of hours on the second day on the master schedule. * Fixed problem in Upcoming DJ Widget that wasn't displaying the correct upcoming shift. -= 2.0.5 = +## 2.0.5 * Fixed an issue with some shows displaying in 24 hour time on master schedule grid even though 12-hour time is specified * Fixed a bug in the On-Air widget that was preventing shows spanning two day from displaying * Added code to enable theme support for post-thumbnails on the "show" post-type so users don't have to add it to their theme's functions.php file anymore. -= 2.0.4 = +## 2.0.4 * Master Schedule bug for shows that start at midnight and end before the hour is up fixed. -= 2.0.3 = +## 2.0.3 * Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes -= 2.0.2 = +## 2.0.2 * Bug fix: Scheduling issue with overnight shows fixed -= 2.0.1 = +## 2.0.1 * Bug fix: Fixed PHP error in Playlist save function that was triggered during preview * Bug fix: Fixed PHP notice in playlist template file * Bug fix: Fixed PHP error in dj-widget shortcode -= 2.0.0 = +## 2.0.0 * Major code reorganization for better future development * PHP warning fix * Enabled option to add comments on Shows and Playlists * Added option to show either single or multiple schedules in the On Air widget -= 1.6.2 = +## 1.6.2 * Minor PHP warning fixes -= 1.6.1 = +## 1.6.1 * Bug fix: Some of the code added in the previous update uses the array_replace() function that is only available in PHP 5.3+. Added a fallback for older PHP versions. -= 1.6.0 = +## 1.6.0 * Added the ability to override the weekly schedule to allow one-off events to be scheduled * Added a list format option to the master schedule shortcode * Added Italian translation (it_IT) (thank you to Cristofaro Giuseppe!) -= 1.5.4 = +## 1.5.4 * Fixed some PHP notices that were being generated when there were no playlist entries in the system. -= 1.5.3 = +## 1.5.3 * Added Serbian translation (sr_RS) (thank you to Miodarag Zivkovic!) -= 1.5.2.1 = +## 1.5.2.1 * Removed some debug code from one of the template files -= 1.5.2 = +## 1.5.2 * Fixed some localization bugs. * Added Albanian translation (sq_AL) (thank you to Lorenc!) -= 1.5.1 = +## 1.5.1 * Fixed some localization bugs. * Added French translation (fr_FR) (a big thank you to Dan over at BuddyPress France - http://bp-fr.net/) -= 1.5.0 = +## 1.5.0 * Plugin modified to allow for internationalization. * Spanish translation (es_ES) added. -= 1.4.6 = +## 1.4.6 * Fixed a bug with shows that start at midnight not displaying in the on-air sidebar widget. * Switched DJ/Show avatars in the widgets to use the featured image of the show instead of gravatar. * Updated show template to get rid of a PHP warning that appeared if the show had no schedules. @@ -754,72 +451,72 @@ Adds title position and avatar width options to widgets; missing DJ author links * Added CSS classes to master program schedule output so CSS rules can be applied to specific shows * Added new attribute to the list-shows shortcode to allow only specified genres to be displayed -= 1.4.5 = +## 1.4.5 * Fixed master-schedule shortcode bug that was preventing display of 12 hour time -= 1.4.4 = +## 1.4.4 * Compatibility fix for Wordpress 3.6 - fixed problem with giving alternative roles DJ capabilities * Fixed some areas of the code that were generating PHP notices in WordPress 3.6 -= 1.4.3 = +## 1.4.3 * Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time * Fixed some areas of the code that were generating PHP notices in WordPress 3.6 * Added example of how to display show schedule to single-show.php template * Added more options to the plugin's widgets * Added new options to the master-schedule shortcode -= 1.4.2 = +## 1.4.2 * Fixed a bug in the CSS file override from theme directory -= 1.4.1 = +## 1.4.1 * Fixed issue with templates copied to the theme directory not overriding the defaults correctly * Fixed incorrectly implemented wp_enqueue_styles() * Removed deprecated escape_attribute() function from the plugin widgets * Fixed some areas of the code that were generating PHP notices -= 1.4.0 = +## 1.4.0 * Compatibility fix for WordPress 3.6 -= 1.3.9 = +## 1.3.9 * Fixed a bug that was preventing sites using a non-default table prefix from seeing the list of DJs on the add/edit show pages -= 1.3.8 = +## 1.3.8 * Changes to fix the incorrect list of available shows on the Add Playlist page * Removing Add Show links from admin menu for DJs, since they don't have permission to use them anyway. -= 1.3.7 = +## 1.3.7 * Fixed a scheduling bug in the upcoming shows widget * By popular request, switched the order of artist and song in the now playing widget -= 1.3.6 = +## 1.3.6 * Fixed issue with shows that run overnight not showing up correctly in the sidebar widgets -= 1.3.5 = +## 1.3.5 * Fixed a time display bug in the DJ On-Air sidebar widget * Fixed a display bug on the master schedule with overnight shows -= 1.3.4 = +## 1.3.4 * By request, added as 24-hour time format option to the master schedule and sidebar widgets. -= 1.3.3 = +## 1.3.3 * Added the ability to assign any user with the edit_shows capability as a DJ, to accomodate custom and edited roles. -= 1.3.2 = +## 1.3.2 * Fixed a bug in the DJ-on-air widget -= 1.3.1 = +## 1.3.1 * Fixed a major bug in the master schedule output -= 1.3 = +## 1.3 * Fixed some minor compatibility issues with WordPress 3.5 * Fixed Shows icon in Dashboard -= 1.2 = +## 1.2 * Fixed thumbnail bug in sidebar widgets * Added new widget to display upcoming shows * Added pagination options for playlists and show blogs -= 1.1 = +## 1.1 * Fixed playlist edit screen so that queued songs fall to the bottom of the list to maintain play order * Reduced the size of the content field in the playlist post type * Some minor formatting changes to default templates @@ -830,5 +527,5 @@ Adds title position and avatar width options to widgets; missing DJ author links * Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. * Added new field to associate blog posts with shows -= 1.0 = +## 1.0 * Initial release \ No newline at end of file From 617a8d09a54f82a4362dd64f1293ea184ef6e8d7 Mon Sep 17 00:00:00 2001 From: Mark Nyon Date: Sun, 3 Nov 2019 13:11:55 -0500 Subject: [PATCH 072/377] Fixed headings & lists for README.md #1 --- readme.md | 153 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 79 insertions(+), 74 deletions(-) diff --git a/readme.md b/readme.md index 79ed5df..6c6c4a9 100644 --- a/readme.md +++ b/readme.md @@ -32,11 +32,17 @@ For plugin support, please give 24-48 hours to answer support questions, which w == Development == +<<<<<<< HEAD If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues == Installation == +======= +You can find a demo version of the plugin on our demo site [here](https://radiostationdemo.com). + +## Installation +>>>>>>> a35950f... Fixed headings & lists for README.md #1 1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. 2. Activate the plugin through the 'Plugins' menu in WordPress @@ -46,137 +52,136 @@ Submit bugs and feature requests here: If set to a value of 'list', the schedule will display in list format rather than table or div format. Valid values are 'list', 'divs', 'table'. Default value is 'table'. - 'time' => The time format you with to use. Valid values are 12 and 24. Default is 12. - 'show_link' => Display the title of the show as a link to its profile page. Valid values are 0 for hide, 1 for show. Default is 1. - 'display_show_time' => Display start and end times of each show after the title in the grid. Valid values are 0 for hide, 1 for show. Default is 1. - 'show_image' => If set to a value of 1, the show's avatar will be displayed. Default value is 0. - 'show_djs' => If set to a value of 1, the names of the show's DJs will be displayed. Default value is 0. - 'divheight' => Set the height, in pixels, of the individual divs in the 'divs' layout. Default is 45. - 'single_day' => Display schedule for only a single day of the week. Only works if you are using the 'list' format. Valid values are sunday, monday, tuesday, wednesday, thursday, friday, saturday. - +* 'list' => If set to a value of 'list', the schedule will display in list format rather than table or div format. Valid values are 'list', 'divs', 'table'. Default value is 'table'. +* 'time' => The time format you with to use. Valid values are 12 and 24. Default is 12. +* 'show_link' => Display the title of the show as a link to its profile page. Valid values are 0 for hide, 1 for show. Default is 1. +* 'display_show_time' => Display start and end times of each show after the title in the grid. Valid values are 0 for hide, 1 for show. Default is 1. +* 'show_image' => If set to a value of 1, the show's avatar will be displayed. Default value is 0. +* 'show_djs' => If set to a value of 1, the names of the show's DJs will be displayed. Default value is 0. +* 'divheight' => Set the height, in pixels, of the individual divs in the 'divs' layout. Default is 45. +* 'single_day' => Display schedule for only a single day of the week. Only works if you are using the 'list' format. Valid values are sunday, monday, tuesday, wednesday, thursday, friday, saturday. +* For example, if you wish to display the schedule in 24-hour time format, use `[master-schedule time="24"]`. If you want to only show Sunday's schedule, use `[master-schedule list="list" single_day="sunday"]`. -= How do I schedule a show? = +### How do I schedule a show? Simply create a new show. You will be able to assign it to any timeslot you wish on the edit page. -= What if I have a special event? = +### What if I have a special event? If you have a one-off event that you need to show up in the On-Air or Coming Up Next widgets, you can create a Schedule Override by clicking the Schedule Override tab in the Dashboard menu. This will allow you to set aside a block of time on a specific date, and will display the title you give it in the widgets. Please note that this will only override the widgets and their corresponding shortcodes. If you are using the weekly master schedule shortcode on a page, its output will not be altered. -= How do I get the last song played to show up? = +### How do I get the last song played to show up? You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode `[now-playing]` in your page/post, or use `do_shortcode('[now-playing]');` in your template files. The following attributes are available for the shortcode: - 'title' => The title you would like to appear over the now playing block - 'artist' => Display artist name. Valid values are 0 for hide, 1 for show. Default is 1. - 'song' => Display song name. Valid values are 0 for hide, 1 for show. Default is 1. - 'album' => Display album name. Valid values are 0 for hide, 1 for show. Default is 0. - 'label' => Display label name. Valid values are 0 for hide, 1 for show. Default is 0. - 'comments' => Display DJ comments. Valid values are 0 for hide, 1 for show. Default is 0. +* 'title' => The title you would like to appear over the now playing block +* 'artist' => Display artist name. Valid values are 0 for hide, 1 for show. Default is 1. +* 'song' => Display song name. Valid values are 0 for hide, 1 for show. Default is 1. +* 'album' => Display album name. Valid values are 0 for hide, 1 for show. Default is 0. +* 'label' => Display label name. Valid values are 0 for hide, 1 for show. Default is 0. +* 'comments' => Display DJ comments. Valid values are 0 for hide, 1 for show. Default is 0. Example: `[now-playing title="Current Song" artist="1" song="1" album="1" label="1" comments="0"]` -= What about displaying the current DJ on air? = +### What about displaying the current DJ on air? You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode `[dj-widget]` in your page/post, or you can use `do_shortcode('[dj-widget]');` in your template files. The following attributes are available for the shortcode: - 'title' => The title you would like to appear over the on-air block - 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0. - 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0. - 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0. - 'default_name' => The text you would like to display when no show is schedule for the current time. - 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12. - 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1. - 'show_playlist' => Display a link to the show's current playlist. Valid values are 0 for hide link, 1 for show link. Default is 1. - 'show_all_sched' => Displays all schedules for a show if it airs on multiple days. Valid values are 0 for current schedule, 1 for all schedules. Default is 0. - 'show_desc' => Displays the first 20 words of the show's description. Valid values are 0 for hide descripion, 1 for show description. Default is 0. - +* 'title' => The title you would like to appear over the on-air block +* 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0. +* 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0. +* 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0. +* 'default_name' => The text you would like to display when no show is schedule for the current time. +* 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12. +* 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1. +* 'show_playlist' => Display a link to the show's current playlist. Valid values are 0 for hide link, 1 for show link. Default is 1. +* 'show_all_sched' => Displays all schedules for a show if it airs on multiple days. Valid values are 0 for current schedule, 1 for all schedules. Default is 0. +* 'show_desc' => Displays the first 20 words of the show's description. Valid values are 0 for hide descripion, 1 for show description. Default is 0. +* Example: `[dj-widget title="Now On-Air" display_djs="1" show_avatar="1" show_link="1" default_name="RadioBot" time="12" show_sched="1" show_playlist="1"]` -= Can I display upcoming shows, too? = +### Can I display upcoming shows, too? You'll find a widget for just that purpose under the Widgets tab. You can also use the shortcode `[dj-coming-up-widget]` in your page/post, or you can use `do_shortcode('[dj-coming-up-widget]');` in your template files. The following attributes are available for the shortcode: - 'title' => The title you would like to appear over the on-air block - 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0. - 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0. - 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0. - 'limit' => The number of upcoming shows to display. Default is 1. - 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12. - 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1. - +* 'title' => The title you would like to appear over the on-air block +* 'display_djs' => Display the names of the DJs on the show. Valid values are 0 for hide names, 1 for show names. Default is 0. +* 'show_avatar' => Display a show's thumbnail. Valid values are 0 for hide avatar, 1 for show avatar. Default is 0. +* 'show_link' => Display a link to a show's page. Valid values are 0 for hide link, 1 for show link. Default is 0. +* 'limit' => The number of upcoming shows to display. Default is 1. +* 'time' => The time format used for displaying schedules. Valid values are 12 and 24. Default is 12. +* 'show_sched' => Display the show's schedules. Valid values are 0 for hide schedule, 1 for show schedule. Default is 1. +* Example: `[dj-coming-up-widget title="Coming Up On-Air" display_djs="1" show_avatar="1" show_link="1" limit="3" time="12" schow_sched="1"]` -= Can I change how show pages are laid out/displayed? = - -Yes. Copy the radio-station/templates/single-show.php file into your theme directory, and alter as you wish. This template, and all of the other templates +### Can I change how show pages are laid out/displayed? +Yes. Copy the `radio-station/templates/single-show.php` file into your theme directory, and alter as you wish. This template, and all of the other templates in this plugin, are based on the TwentyEleven theme. If you're using a different theme, you may have to rework them to reflect your theme's layout. -= What about playlist pages? = +### What about playlist pages? Same deal. Grab the radio-station/templates/single-playlist.php file, copy it to your theme directory, and go to town. -= And playlist archive pages? = +### And playlist archive pages? Same deal. Grab the radio-station/templates/archive-playlist.php file, copy it to your theme directory, and go to town. -= And the program schedule, too? = +### And the program schedule, too? Because of the complexity of outputting the data, you can't directly alter the template, but you can copy the radio-station/templates/program-schedule.css file into your theme directory and change the CSS rules for the page. -= What if I want to style the DJ on air sidebar widget? = +### What if I want to style the DJ on air sidebar widget? Copy the radio-station/templates/djonair.css file to your theme directory. -= How do I get an archive page that lists ALL of the playlists instead of just the archives of individual shows? = +### How do I get an archive page that lists ALL of the playlists instead of just the archives of individual shows? First, grab the radio-station/templates/playlist-archive-template.php file, and copy it to your active theme directory. Then, create a Page in wordpress to hold the playlist archive. Under Page Attributes, set the template to Playlist Archive. Please note: If you don't copy the template file to your theme first, the option to select it will not appear. -= Can show pages link to an archive of related blog posts? = +### Can show pages link to an archive of related blog posts? Yes, in much the same way as the full playlist archive described above. First, grab the radio-station/templates/show-blog-archive-template.php file, and copy it to your active theme directory. Then, create a Page in wordpress to hold the blog archive. Under Page Attributes, set the template to Show Blog Archive. -= How can I list all of my shows? = +### How can I list all of my shows? Use the shortcode `[list-shows]` in your page/posts or use `do_shortcode(['list-shows']);` in your template files. This will output an unordered list element containing the titles of and links to all shows marked as "Active". The following attributes are available for the shortcode: - 'genre' => Displays shows only from the specified genre(s). Separate multiple genres with a comma, e.g. genre="pop,rock". +* 'genre' => Displays shows only from the specified genre(s). Separate multiple genres with a comma, e.g. genre="pop,rock". Example: `[list-shows genre="pop"]` `[list-shows genre="pop,rock,metal"]` -= I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? = +### I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? Since I'm stongly opposed to reinventing the wheel, I recommend Justin Tadlock's excellent "Members" plugin for that purpose. You can find it on Wordpress.org, here: http://wordpress.org/extend/plugins/members/ @@ -202,50 +207,50 @@ If you want the new role to be able to create or approve new shows, you should a publish_shows edit_others_shows -= How do I change the DJ's avatar in the sidebar widget? = +### How do I change the DJ's avatar in the sidebar widget? The avatar is whatever image is assigned as the DJ/Show's featured image. All you have to do is set a new featured image. -= Why don't any users show up in the DJs list on the Show edit page? = +### Why don't any users show up in the DJs list on the Show edit page? You did remember to assign the DJ role to the users you want to be DJs, right? -= My DJs can't edit a show page. What do I do? = +### My DJs can't edit a show page. What do I do? The only DJs that can edit a show are the ones listed as being ON that show in the DJs select menu. This is to prevent DJs from editing other DJs shows without permission. -= How can I export a list of songs played on a given date? = +### How can I export a list of songs played on a given date? Under the Playlists menu in the dashboard is an Export link. Simply specify the a date range, and a text file will be generated for you. -= Can my DJ's have customized user pages in addition to Show pages? = +### Can my DJ's have customized user pages in addition to Show pages? Yes. These pages are the same as any other author page (edit or create the author.php template file in your theme directory). A sample can be found in the radio-station/templates/author.php file (please note that this file doesn't actually do anything unless you copy it over to your theme's directory). Like the other theme templates included with this plugin, this file is based on the TwentyEleven theme and may need to be modified in order to work with your theme. -= I don't want to use Gravatar for my DJ's image on their profile page. = +### I don't want to use Gravatar for my DJ's image on their profile page. Then you'll need to install a plugin that lets you add a different image to your DJ's user account and edit your author.php theme file accordingly. That's a little out of the scope of this plugin. I recommend Cimy User Extra Fields: http://wordpress.org/extend/plugins/cimy-user-extra-fields/ -= What languages other than English is the plugin available in? = +### What languages other than English is the plugin available in? Right now: -Albanian (sq_AL) -Dutch (nl_NL) -French (fr_FR) -German (de_DE) -Italian (it_IT) -Russion (ru_RU) -Serbian (sr_RS) -Spanish (es_ES) -Catalan (ca) - -= Can the plugin be translated into my language? = +* Albanian (sq_AL) +* Dutch (nl_NL) +* French (fr_FR) +* German (de_DE) +* Italian (it_IT) +* Russian (ru_RU) +* Serbian (sr_RS) +* Spanish (es_ES) +* Catalan (ca) + +### Can the plugin be translated into my language? You may translate the plugin into another language. Please visit our WordPress Translate project page for this plugin for further instruction: https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/ The radio-station.pot file is located in the /languages directory of the plugin. Please send the finished translation to info@netmix.com. We'd love to include it. From 86e774c6702b5531bcfa7261c9512b792e597482 Mon Sep 17 00:00:00 2001 From: Mark Nyon Date: Tue, 22 Oct 2019 00:19:35 -0400 Subject: [PATCH 073/377] Initial README.md file (incomplete) #1 --- readme.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/readme.md b/readme.md index 6c6c4a9..f9a38f8 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -=== Radio Station === +# Radio Station Contributors: tonyzeoli, majick, nourma Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting @@ -10,7 +10,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. Functionality is based on Drupal 6's Station plugin. -== Description == +## Description Radio Station is a plugin to build and manage a Show Calendar in a radio station or Internet broadcaster's WordPress website. It's functionality is based on Drupal 6's Station plugin, reworked for use in Wordpress. @@ -18,31 +18,23 @@ The plugin includes the ability to associate users (as member role "DJ") with th The plugin contains a widget to display the currently on-air DJ with a link to the DJ's Show page and current playlist. A schedule of all Shows can also be generated and added to a page with a short code. Shows can be categorized and a category filter appears when the Calendar is added using a short code to a WordPress page or post. -Posts can be assigned to Shows by creating a Post and using the meta-box to assign it to a specific Show. They will appear below the Show Description and Playlist on a Show page. Please use this function as your archive for each show, so that each Show archive has it's own URL and can be crawled for SEO purposes. Use your favorite SEO plugin to manage SEO on Show pages and Posts. We prefer [All in One SEO Pack] (https://wordpress.org/plugins/all-in-one-seo-pack/). +Posts can be assigned to Shows by creating a Post and using the meta-box to assign it to a specific Show. They will appear below the Show Description and Playlist on a Show page. Please use this function as your archive for each show, so that each Show archive has it's own URL and can be crawled for SEO purposes. Use your favorite SEO plugin to manage SEO on Show pages and Posts. We prefer [All in One SEO Pack](https://wordpress.org/plugins/all-in-one-seo-pack/). -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by Tony Zeoli and developed by contributing committers to the project. +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) and developed by contributing committers to the project. -We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: https://www.patreon.com/radiostation. +We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: [https://www.patreon.com/radiostation](https://www.patreon.com/radiostation). -You can find a demo version of the plugin on our demo site here: https://radiostationdemo.com +You can find a demo version of the plugin on our demo site here: [https://radiostationdemo.com](https://radiostationdemo.com). -== Plugin Support == +## Plugin Support -For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: https://wordpress.org/support/plugin/radio-station/ +For plugin support, please give 24-48 hours to answer support questions, which will be handled in the Wordpress Support Forums for this free version of the plugin here: [https://wordpress.org/support/plugin/radio-station](https://wordpress.org/support/plugin/radio-station/). -== Development == +## Development -<<<<<<< HEAD -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on Github: https://github.com/netmix/radio-station/. - -Submit bugs and feature requests here: https://github.com/netmix/radio-station/issues - -== Installation == -======= You can find a demo version of the plugin on our demo site [here](https://radiostationdemo.com). ## Installation ->>>>>>> a35950f... Fixed headings & lists for README.md #1 1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. 2. Activate the plugin through the 'Plugins' menu in WordPress @@ -50,7 +42,7 @@ You can find a demo version of the plugin on our demo site [here](https://radios 4. Create shows and set up shifts. 5. Add playlists to your shows. -== Frequently Asked Questions == +## Frequently Asked Questions ### I've scheduled all my shows, but they're not showing up on the programming grid! From bf385e98c506ff52549d234c9c4943f208561db5 Mon Sep 17 00:00:00 2001 From: Mark Nyon Date: Sun, 1 Dec 2019 18:10:36 -0500 Subject: [PATCH 074/377] Updated Changelog to 2.2.8 --- CHANGELOG.md | 318 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..eeb32d2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,318 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## 2.2.8 + +* Fix to remove strict type checking from in_array (introduced 2.2.6) +* Fix to mismatched flush rewrite rules flag function name +* Fix to undefined index warnings for new Schedule Overrides +* Fix to not 404 author pages for DJs without blog posts +* Fix to implode blog array for Show blog post listing + +## 2.2.7 + +* Dutch translation added (Thank you to André Dortmont for the file!) +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations +## 2.2.6 + +* Reorganize master-list shortcode into templates +* Add constant for plugin directory +* Use WP_Query instead of get_posts +* New posts_per_page and tax_query +* Fixes for undefined indexes +* Fixes for raw mysql queries +* Typecasting to support strict comparisons + +## 2.2.5 + +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + +## 2.2.4 + +* added title position and avatar width options to widgets +* added missing DJ author links as new option to widgets +* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) +* improved to show Encore Presentation in show widget displays +* fix to Show shift Encore Presentation checkbox saving + +## 2.2.3 + +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + +## 2.2.2 + +* shift main playlist and show metaboxes above editor +* set plugin custom post types editor to Classic Editor +* add high priority to side metaboxes for plugin post types +* added dismissable development changeover admin notice +* added simple Patreon supporter image button and blurb +* added filter for DJ Avatar size on Author page template +* fix to Schedule Override metabox value saving +* fix to Playlist track list items overflowing metabox +* fix to shift up time row on Master Schedule table view +* fix to missing weekday headings in Master Schedule table +* fix to weekday display for Upcoming DJ Widget +* fix to user display labels on select DJ metabox +* fix to file_exists check for DJ on Air stylesheet path +* fix to make DJ multi-select input full metabox width +* fix to expand admin menu when on genre taxonomy page +* fix to expand admin menu when editing plugin post types +* fix to genre submenu item link for current page +* added GitHub URI to plugin header for GitHub updater + +## 2.2.1 + +* Re-commit all missing files via SVN + +## 2.2.0 + +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) +* fixed the protocol in jQuery UI style Google URL +* reprefixed all functions for consistency (radio_station_) +* updated all the widget constructor methods +* merged the menu items into a single main menu +* updated the capability checks for the menu items +* moved the help and export pages to /templates/ +* moved all the css files to /css/ +* enqeued the djonair css from within the widget +* use plugins_url for all resource URLs +* added $wpdb->prepare to sanitize a query +* added some sanization for metabox save values +* added a week and month translation helper +* added a radio station antenna icon + +## 2.1.3 + +* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). + +## 2.1.2 + +* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. +* Catalan translation added (Thank you to Victor Riera for the file!) + +## 2.1.1 +* Bug fix - Fixed day of the week language translation issue in master schedule shortcode +* Bug fix - Added some error checking in the sidebar widgets +* New Feature - Added ability to give schedule overrides a featured image +* New Feature - Added built-in help page + +## 2.1 +* General code cleanup, 4.1 compatibility testing, and changes for better efficiency. +* Bug fix - Fixed issue with early morning shows spanning entire column in the programming grid shortcode +* New Feature - Master programming grid can now be displayed in div format, as well as the original table and list formats. + +## 2.0.16 +* Minor revisions to German translation. +* Fixed a bug that was resetting custom-sert role capabilities for the DJ role. + +## 2.0.15 +* German translation added (Thank you to Ian Hook for the file!) + +## 2.0.14 +* Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked +* Added ability to display DJ names on the master schedule. +* Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. +* Verified compatibility woth WordPress 4.0 + +## 2.0.13 +* Added the ability to display show avatars on the program grid. +* Added the ability to display show description in the now on-air widget and short code. + +## 2.0.12 +* Fixed a bug in the master schedule shortcode + +## 2.0.11 +* Russian translation added (Thank you to Alexander Esin for the file!) + +## 2.0.10 +* Fixed role/capability conflict with WP User Avatar plugin. +* Added the missing leading zero to 24-hour time format on the master schedule. +* Fixed dj_get_current function so that it no longer returns shows that have been moved to the trash. +* Fixed dj_get_next function so that it no longer ignores the "Active" checkbox on a show. +* Added some CSS ids and classes to the master program schedule list format to make it more useful + +## 2.0.9 +* Fixed broken upcoming show shortcode. +* Added ability to display DJ names along with the show title in the widgets. + +## 2.0.8 +* Fixed the display of schedules for upcoming shows in the widget and shortcode. +* Fixed a bug in the dj_get_next function that was causing it to ignore the beginning of the next week at the end of the current week. + +## 2.0.7 +* Fixed scheduling bug in shortcode function + +## 2.0.6 +* Master Schedule now displays days starting with the start_of_week option set in the WordPress General Settings panel. +* Fixed issue with shows that have been unplublished still showing up on the master schedule. +* Fixed missing am/pm text on shows that run overnight on the master schedule. +* Fixed an issue with shows that run overnight not spanning the correct number of hours on the second day on the master schedule. +* Fixed problem in Upcoming DJ Widget that wasn't displaying the correct upcoming shift. + +## 2.0.5 +* Fixed an issue with some shows displaying in 24 hour time on master schedule grid even though 12-hour time is specified +* Fixed a bug in the On-Air widget that was preventing shows spanning two day from displaying +* Added code to enable theme support for post-thumbnails on the "show" post-type so users don't have to add it to their theme's functions.php file anymore. + +## 2.0.4 +* Master Schedule bug for shows that start at midnight and end before the hour is up fixed. + +## 2.0.3 +* Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes + +## 2.0.2 +* Bug fix: Scheduling issue with overnight shows fixed + +## 2.0.1 +* Bug fix: Fixed PHP error in Playlist save function that was triggered during preview +* Bug fix: Fixed PHP notice in playlist template file +* Bug fix: Fixed PHP error in dj-widget shortcode + +## 2.0.0 +* Major code reorganization for better future development +* PHP warning fix +* Enabled option to add comments on Shows and Playlists +* Added option to show either single or multiple schedules in the On Air widget + +## 1.6.2 +* Minor PHP warning fixes + +## 1.6.1 +* Bug fix: Some of the code added in the previous update uses the array_replace() function that is only available in PHP 5.3+. Added a fallback for older PHP versions. + +## 1.6.0 +* Added the ability to override the weekly schedule to allow one-off events to be scheduled +* Added a list format option to the master schedule shortcode +* Added Italian translation (it_IT) (thank you to Cristofaro Giuseppe!) + +## 1.5.4 +* Fixed some PHP notices that were being generated when there were no playlist entries in the system. + +## 1.5.3 +* Added Serbian translation (sr_RS) (thank you to Miodarag Zivkovic!) + +## 1.5.2.1 +* Removed some debug code from one of the template files + +## 1.5.2 +* Fixed some localization bugs. +* Added Albanian translation (sq_AL) (thank you to Lorenc!) + +## 1.5.1 +* Fixed some localization bugs. +* Added French translation (fr_FR) (a big thank you to Dan over at [BuddyPress France](http://bp-fr.net/). + +## 1.5.0 +* Plugin modified to allow for internationalization. +* Spanish translation (es_ES) added. + +## 1.4.6 +* Fixed a bug with shows that start at midnight not displaying in the on-air sidebar widget. +* Switched DJ/Show avatars in the widgets to use the featured image of the show instead of gravatar. +* Updated show template to get rid of a PHP warning that appeared if the show had no schedules. +* Fixed some other areas of the code that were generating PHP notices in WordPress 3.6 +* Added CSS classes to master program schedule output so CSS rules can be applied to specific shows +* Added new attribute to the list-shows shortcode to allow only specified genres to be displayed + +## 1.4.5 +* Fixed master-schedule shortcode bug that was preventing display of 12 hour time + +## 1.4.4 +* Compatibility fix for Wordpress 3.6 - fixed problem with giving alternative roles DJ capabilities +* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 + +## 1.4.3 +* Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time +* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 +* Added example of how to display show schedule to single-show.php template +* Added more options to the plugin's widgets +* Added new options to the master-schedule shortcode + +## 1.4.2 +* Fixed a bug in the CSS file override from theme directory + +## 1.4.1 +* Fixed issue with templates copied to the theme directory not overriding the defaults correctly +* Fixed incorrectly implemented wp_enqueue_styles() +* Removed deprecated escape_attribute() function from the plugin widgets +* Fixed some areas of the code that were generating PHP notices + +## 1.4.0 +* Compatibility fix for WordPress 3.6 + +## 1.3.9 +* Fixed a bug that was preventing sites using a non-default table prefix from seeing the list of DJs on the add/edit show pages + +## 1.3.8 +* Changes to fix the incorrect list of available shows on the Add Playlist page +* Removing Add Show links from admin menu for DJs, since they don't have permission to use them anyway. + +## 1.3.7 +* Fixed a scheduling bug in the upcoming shows widget +* By popular request, switched the order of artist and song in the now playing widget + +## 1.3.6 +* Fixed issue with shows that run overnight not showing up correctly in the sidebar widgets + +## 1.3.5 +* Fixed a time display bug in the DJ On-Air sidebar widget +* Fixed a display bug on the master schedule with overnight shows + +## 1.3.4 +* By request, added as 24-hour time format option to the master schedule and sidebar widgets. + +## 1.3.3 +* Added the ability to assign any user with the edit_shows capability as a DJ, to accomodate custom and edited roles. + +## 1.3.2 +* Fixed a bug in the DJ-on-air widget + +## 1.3.1 +* Fixed a major bug in the master schedule output + +## 1.3 +* Fixed some minor compatibility issues with WordPress 3.5 +* Fixed Shows icon in Dashboard + +## 1.2 +* Fixed thumbnail bug in sidebar widgets +* Added new widget to display upcoming shows +* Added pagination options for playlists and show blogs + +## 1.1 +* Fixed playlist edit screen so that queued songs fall to the bottom of the list to maintain play order +* Reduced the size of the content field in the playlist post type +* Some minor formatting changes to default templates +* Added genre highlighter to the master programming schedule page +* Added a second Update button on the bottom of the playlist edit page for convinience. +* Added sample template for DJ user pages +* Fixed a bug in the master schedule shortcode that messed up the table for shows that are more than two hours in duration +* Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. +* Added new field to associate blog posts with shows + +## 1.0 +* Initial release From eafc6e43b2ad6203b5963c7e8e8a817984c6200f Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Sat, 21 Dec 2019 22:42:51 -0500 Subject: [PATCH 075/377] Update readme.md minor text changes, remove nourma from committers Add link to Nikki Blight's WordPress page. --- readme.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 546737f..4224d75 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,6 @@ - # Radio Station -Contributors: tonyzeoli, majick, nourma -Donate link: https://netmix.co/donate +Contributors: tonyzeoli, majick +Donate link: https://www.patreon.com/radiostation Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.2.2 @@ -21,7 +20,7 @@ The plugin contains a widget to display the currently on-air DJ with a link to t Posts can be assigned to Shows by creating a Post and using the meta-box to assign it to a specific Show. They will appear below the Show Description and Playlist on a Show page. Please use this function as your archive for each show, so that each Show archive has it's own URL and can be crawled for SEO purposes. Use your favorite SEO plugin to manage SEO on Show pages and Posts. We prefer [All in One SEO Pack](https://wordpress.org/plugins/all-in-one-seo-pack/). -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) and developed by contributing committers to the project. +We are grateful to [Nikki Blight] (https://profiles.wordpress.org/kionae/) for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) and developed by contributing committers to the project overseen by Lead Developer, [Tony Hayes] (https://profiles.wordpress.org/majick/) We are actively seeking radio station partners and donations to fund further development of the free, open source version of this plugin at: [https://www.patreon.com/radiostation](https://www.patreon.com/radiostation). From be05790c47a70093b8334f4a070b6b5351f05e54 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Mon, 30 Dec 2019 11:21:16 -0500 Subject: [PATCH 076/377] .po and .mo files added to languages for portugese translation Portugese language translation .po and .mo files added. Translation by Gabriel Lima Barros. --- .DS_Store | Bin 12292 -> 12292 bytes languages/radio-station-pt_BR.mo | Bin 0 -> 34261 bytes languages/radio-station-pt_BR.po | 1911 ++++++++++++++++++++++++++++++ 3 files changed, 1911 insertions(+) create mode 100644 languages/radio-station-pt_BR.mo create mode 100644 languages/radio-station-pt_BR.po diff --git a/.DS_Store b/.DS_Store index 6a3014a8b0b6a3a1763b068443a9199686b1ae68..5d3d1d91cec02d78755e5cb1aef3e1a6f0e2427b 100644 GIT binary patch delta 16 XcmZokXi3Cfy43uH&g~1 delta 18 ZcmZokXi3dXbMKj%OYXhrdLNUA zq5>5`q)IEGsAyD*L6KVPi^{*PqvER~TB%xUwN|ZF(Ark(qksGTt-bd-_s%4u|G%IA z|9;r{oxRU~uD$kJYp=b}@Qpe9+!^tE=vh&8Eck|lqG({RC_3;MrAE40`3of7CasNvd3S8qW9?wT>CEu_aQz8D*qNx{b+*+fUf`#18)UY?_0s6z=uH5 zsD-HwH>{kuWcvlE;RzTe{`pvM0haBuJn z;ML%l{QJdyF8zE^?XCdDhigF5*K_*Z+}2$Chb!pCm`MUOi`(fvM9{yV7t%wFR7`*={}+yW~9B&c!R2#W7t3#$AZK(%`hsP^6m z9s+(46#YI4ZUjFI9ta-2)Y0iwQ1zV!s-ETG3UCm-9lRM79~};HMBlf7s{a8{^Zb7B zHt%sPfCfBft?* z{l5-W`z;Xh9lZinyElR{cn_%je*sFaJr0VW{}r4I9{hY4KN-|`)_^C16;SK_wV>L4 z52*h96)67s5~zGX0-p=cImeCjG*IhCsQLOckDmh*;{Ob0;OYyas0n@utb+?KbbRwh za31jwgIX_N1J&Q3ff~;N%iZ{n1YyPKcu?co?9<0Uji(A~o_2sLe-kMB-|64K8`QWy z1Ztgq-lu;PRDDl_r-Q!+HNMkVxOqDdRJ&!6DUaR^o&|mdWXhu1D1$S>)gE68s+~Xe z_$g5RdJhCH6QKC!pFq|7Ls0qt+o#W7>E0jV@dQxi7l4|NGkts|sB#-X)$<}y z>!Ie;|G>xp$j5hq%J*SVbojJS|0<~ZzYA)e{2M4fJ9?Fyr&B@k_j%w}Z~$BZeiT%F zzX10EXAio1p5yTtkEei=14}^B_gqlztN_*j3aI(m3M${Np!nksQ1reJRR7)&(zWPg zpy(H^cJn+7lw6BJ_4^!9d@%@$9>qPsxV|20tge*kJ-{2UZr z4q5BycnYX?7K1FkXgRnLdaC^|g_o&e6GQN>RK6~7o%z2l(x<2q1s@m5fLem_`w z7Bm6H4|6Y#qUVGC;6iW=)Oh|FJQ@5DxIg$LD8BxVkN@1q_uAyr=Yry+(?I243~D^* zg5rnO{{4E7!#@2=@KD}g3yLqBpyu~BP~(3GI0yU)sBwM{Tmb$HDET!9A|DK%3W`t9 z2G##7z;W=U;Emw7!Rx`X5!cQ?gX-6Rd)#NU>&I+Rd~+Ol4A>7I0j>pW;I-gZ@C)F6 z;Ofg9A8!C}B7P;P{{90fI{X+^e}4}4fhS$=`1DdxtQ{MRJa9P0^SNL|6hO_$KQeK*LOjc{{bla&bh|DKME9I%m=l8F9b#Rb>LCp zi$Il|1U1gLg2#fpK=uDq9=`!zNc^YZx!_qZMpl3ssCwT6D*s18(eH7O|K!vE9h^n_ z>}%cl=YppaUj(YZmxJP~Ye3O`98`aHfLgCNgIX`I1=Y?&AfhFD6coSBOkmQI^Gi_Un?36C9R(`i3E=C%>pcDf z)V#l@M1coOk@!8^f^gKq$@C_6el1!~>=7pU>?mAd&j0MvY)1gf9sg6ii*p!#tY zsQeRPKbV2m4yb;A98|v_2Q}VrfiW13IX;>PoRjl20e4pco0K(*5ks-GJ`_2+s}eD@acZ16)q{V8xU@dGEE zzvTjO5%CG|RPc?U%Ka6n{(T)h8T=6_xphRv`LmXLyb3&>^bV-?vJ*TJe8l57LDjc6 z!bbEu4qO5Df%CySsPW$I-`@vn9e)ti`h67KAN;1r?}G;r{|Tu2eg$eB9Js~T4=UaV z&IT_8pAD`BRnMiM=zSHa`s<+R_i9k%y&u%N_!y{m9s|WEUjs$oAAu~f==iFe*O!AD z-|gT9;QgTTe+LxZehHoi&a1h3Tn9?NyaYT2d?z>?{4BUH_ynl&KM5WLJ`Jirzw&rU z-Hqc!Q14F%_XE!bHLd|r`8R{&p9znz1Vx88f*RM`K;_>Bs{A9M==n)72EPJ|?|urt z1Ux2levjM1TZkXgVDAau3u+!?GMx^t0X6OpcqsU0Q1p2aR6CD?%fQFLqrq8CmwqxR zx!nh-|EwE2Ir7|FR1x`Kd65EC3pn*n8)vc8vidr<(swD z$;l%|{`eGl9QZe& z#_5(dU6!F)Cs{a8{<9is? zcs>EDe_sN{7hea}&eNdsKj-C+4l$_tJQds2a4vW| zsQm8&mG93%weu)=DfoS#e%>n_T{eI!_d-zmunF!D-UX_jw}GhO=pk?gc-Si)e~*A) zCSC=VZ^aGhXmAKT3*6=LAHnm9A9178TbsZ#@moQS=V|a5@V~&L!NYHI<6ZOt z1vO7~kCUMI^X1?^;H$tB!PkH%fe(Ue?{QG=XZnZV{oqRp|H$*#EXDnEIB7rN@qM7i z7kLluB?Qfd z_))(u?8Y`ia*% zgjW)Ol~0>Zd;{Ur1j&Xuq>uV{n(No|TmugP%iv!V!cRQ7nt<4~UxRm;9%L ze?)ja;RJ$yA0xcTp2$a@H}I^ra4FA!=+plmJdAi9d^tFqa4paC!6(7LB~%GV6A!Q`UqDNwEpfR{G1@!_P+=%(z}1_iSJD~%_sf@yo&G% z!i|JQgz#HQzEvRtCd4-q{*&ixeEJCSfA-Idc|MHij}w*>?j$TBJe%~dgOW@7olg8V zf_{=6`fVdDBW)`o<@q%5d*D047O3C*2x|#1A>2y(BEqkE-j}e2=eL223Fq^CDB*sd z{{*~;a6ZqUC+Ihq_}zqe^L&S;MA2V?D@c1UVGZFI#GfEsMmU4;I@0ueF1Q1{iST_T z@Oz5zLjV5DJinReUxCXA-M^=aoaghM1YY2u8{mFE;}FkZ;Q3#D{F@%HA$^n2e==zg z@%&zJfqy@b_&W&y;^Tq~31|8;uj2W$gmVcG5$+&;fbe6U!|!Gu7W(`Tf@Q*;gsVwE zhj1j%KMI+T< zB=|1UeyRk1gFHWr@F34$BJ54*Ctd+xN9g|DOZtD1@JhlY&*9g{!wSN^A;IGb*w?2W>R${KU+vQp z(oXd071DMR#))49F8Arb1RwR!SCIZEJb#K%B7BDMV#39w|8MXuV2$uap05UPB;3k# z_`Q+%KM{F8;dr083H&7CEZ#kba0Ab`gW-2M&-?qx**^U-A0O}-cjf~RP@h;WII3q}bKkYB%yuz}|$VS?~5@qWGF_b$R)2p=VEBgTLPgKTSAr0S3x3hSl z)1Jth@yeu;j8>|Zb|qyfRLrZva;3eSS1mWU4f@ks7(?x5R!tW&zph(xQZL6@y)UUW z<3=+ZZzeUP`Bc;Bm(p_FX;Eu`yt$Reqgi_*PU2Fh z)y`^hJFU?`JBjA;rQol)oonVGVb*^XgfrY+lA zcT2=x1b%BVPu1$4YWB<$YPQ-E`rWgHy}gmDvKn)z;nv$p6V}jUBdccPu;9XY zw9}63Sxm1x)i&cun<=y0p`)5>Ewg6&l1|zZ9VtG6{OFH|(&j|cXvMA6l3uWTObwk&oo162kA`rnBA&0c<7T(cQcoHUrd$nli=0-FgVcodQSE7& zRljl~%aAbb3WI~2pp7wA*-;1P2nAceL)QLiWwk>0FA=B0CYu|GzH&OxC)2egU&MMZ zAzNvOQY5sq@o{G4lFIl*mC#1V_*H1q%lOUIg34sHG@|}{rL{7vjaKS(eSL~NFGs{B z7h4(a`Z@NhiwFQiO_JW;!eU|k}`y5yhdzpiOmWNGizE|Ds0D_ zhKJ+TX2@?c*U8At+VNi*CGTx8J$hR;?Dr8&Lxg?&fApfTaJ6}rq z^^Qa)xiVHE>WKnpu$qt}E8ER#pQC&$ts+-jagD`OX;jfGD5pgzb66{XiU(`(zT!C< zR;_LMY^})T@G6$bD!&N3h#Z!Wjntbnm;j9q3TI_fS2w*}E@Q+%^)x9>h=rT&QiiN% zpj#{D@svqlm1?jJ=|i3;BNnEXV>y3-Y|_@G*DA8n2}6tVgedv zsCg3zTho-w$DxaLOvIMjm3+{Abcu>*%+%A@uympSSY|V2wCl;EMmjyVpy)8!iZ83A zj5;epJ9JvGZWvq&M7}xYZYRr)+{Nu?l>WH=ku<_{^-kBrc2sTV|SLmXj|dLd3(!9TY3< z;%8$$LKmAAZQ?FM@v3j5+ry%F##mRncup1>N!#2Qw5DZK zE%GLeE%HEJtXh-CBKWY1$|`gHQ|X#n#Qo7~*s_f+i*#_0o1`rK^GMc+V9OL^KnR`f z-=LLSrgmLgE4o-Z$2DcUsY@`EBK?CfHqDL=sT?&nWmr0B*2&Zq$3hl#8F5^)X+wL^ z(F*$+<7(R`jipH&yQwuV>RB1xk(RweqO;LcdMjEpCX3I8j>-=UxiI*=95cPWFfM!P zX3M*sZbOz)fh@LQsF~DBlUBvdhLY^xFfjF4Y(JWyN1Z`QlTN0i$$al^U_JYmh1gSZ z2|31O)loKX6C+dG-Y_vIW4l7LlWjQ0qG7)gmosD>ima>a=x^6n+3DDYaq3pKjY_?w zZsVQZX=FHJTi_leTPm@HX3?IyQfILyaQ2$Ix0#fMSh^(3^NnU>hD$FBpp z+blxFBC^WW>x}_f@2GD2QbWll)mA3nV+ir^;6|&_w^h#D-ikrR{cVCITfCl)>IJ zGd7`Gx=wMe%iMxA(YYh5r(EA*I&#&Oo1ZS-iOzhG(}cT|6ziq9gYs%c7PUc}t?x#T zyCTY67S7qDX!lyT_o1aCOQ_gh&rpi0B&eFglVPc1WF8Q|4U-%jvS@=@)W*k5YADxI zx{1J^EhO~z;-i1J87wvQx4Wdh*yuOk-7mD}yQ>UCi<7X`vZ5jRHx=EK?`vmt-0CRa z@3DtP7EM-nV^)LBU@*N#5S=1|%Nbl`MN0Rk8|i>P%j+msyVEkoCVfv?y3!%ouo>)K zX+;Io>ABl+U^p~!78C+JkXOB*@v;$YFj)jE^9^NS6(l7u%-nJ2!iG+56b{2E4K|?m zx5o48oe?pSd(S-%u`sovMT`_Bd#%i#L2ayRXXW@eaFnxt@|fFp<qxeYjJtLj9ZKC zC}K~JUq-J2d4@n0{N?#haYmkL+lcA!1HTl)v$qZ7;2HHhi%dCPeciL+8Z?t_hPa-d zKxyY)3`oULIJ&fxcHplb!`;Ws>`oD9=bGo@nSuuIs4wbIm9O;<7?B;ESZBC5gF9Fo1MmceFS>g6(_HSRw z+=H^|%fY!|ma>+=Gy}57TiB`Fgl>|TegLsNUU&XFCA7GUEtTUpu~As$`9rbV!>c@n zvcV&2(lw9ScHeuviafd4oEHt7L)|GCYH_hZGbKps;JtKEN%9Of@(*-x9UB62i|s*?Dq#TM;mMkef__w zSkBwOb4ly$Z!V=BX@V6!t*g@{1ro2Pt2J5ffeP9Ln((y!SC8b(3)!nKQ$Oqlw7HdB z)qWuKL4~9tA$X7I%Ts&gUcqX?abip^rf}yIg%f3XLL)U&I|aHV)ppvL$cQVJlU~g zab2J+zl51~-o~&Ez4GFmDGsttM9e&dG%IMt9YXfBE5_Z5(*aL)I}W3EMk6kWs+o5* ztbUYmW{=BnNCi6Y$a6=cUW7wbr)wjLJ#tK9=L)3qXjYE*m?~qN%j*Emb~wPnKiPc} z(zDo4RmLtiXhc?=2{z14>7<<3{!A_)Lt)S6EZ7MD!zkT1C%5?`!Xxgz1ecy9x2DdY zTsi7-s?nC86P?6yZOSb5vkY9l9dn-c_xDG|@bjm5rI9FRQ!MGtGh&ys^B=xjv zN97|~eS3UWnpG?D`E8ze=y63oZPzN>`f-U~7@6R5TReMgDW%?GW#ClcybnT0n@3ji zrA}g&8(SSmED!N2{Ed}rD@4s?36Wfhc>1z-bmEuCwuZ-`Hlod}1|5N#0CdNo+W44_ zWiMy+PAl9y^NWcoCDjtlAPY^WbEez0)FZPDdt7;zlDF?-(>9{6R6gs$f#H*-_N`cc zzT~2|_oZ|4cuNN4`}AP8XjiyC={bV3h0z~fo;2$a9@#0I#xY*OYvxrdr)}G`&5JJA zt%pg}k?}j;UJ`PDx8Eq`o6o}ElW<;N$0ga2iZ*h)3Dzp%ev-e>qz=dyDfU}oKWbf* zv$@M@Z|m07mUr6Y?4~;NN;L4pXvA8CR@TV_uS(%SzHz&J!e2I^Q|9_~Ic0Chqt3bC zt&^=96D7z$-JpVU6BW*=+u?Rq7x}yF)iB#(ulrjTv*wDHbfrWR&XT>|IUh0DsBBBC zoSKB4?O2B^I~%xByrOT`tZ+9Gzj4Evt=<{Gp|WL5g;NqcV9Qw|%AZ(gW&wAlvS{gj5A$yx#$EU0QGFP?Tq?N{maM= z&1&>|9yc>hoNMIv=13tBI#TT{DM^Fi8Z2t319_KuHukrxRb1{w4jNmpOxo#q*4%Du zIcPoj*%X|tK|9-Vb(S-uk)7alHE5kK*} zEcR%k-EOp&Em~Aa@I}TAeV!X4ILY|Y$&6A~?bO(ap-ZPZstv zo1nfWOZ&LIyZEeSOV93Ge8%F%vjPG8Mz}+;ES_~mAG5_FIorWyaH|#%dyl34XOgo; zx6~ZkP;X~pJad}fx0>x9*XinvkZ9|IrRPal@OZ)edVFELWWjlPE6d_W`^6V+nia+C z(G^Xst_`|I2a`|TpbODaEU#`_pJZ0BN9s%|?`TK&qsOue-lWPTOaYA~7do{HubGQz z{vd2Sb^p{mDUimSrgkFN7MLxPC}D&>f1RuYEW2d)nZ@h6AYDfY?Ww+@=F|taRce_m zxcr)QGvVHY?Rc$iyE?5jyCgmBWuCUV#!fDq7GJxl&@1A>|+k2}zmPdCCy>3Oo-1WT$k!*pN+f_&4rTomw#~MD2StAE(m0*%RPy;ClP){E3F^wpl@b_Cv)Pqs<1K1KX6XiY zsZvg04UJ_$*X+6q%*-LXp(jgYYNxcA6<7V-$xa%nQD)WJt_#|jS6KKWXSHar(cODy ztq)o!Xw8%xuy#j#Gqwpru|9QwQqJ5J?W8sem!N5b`4IfuOi(;%*R#1>%3#b0n0i=j zoN$e%178Gsb8R}_Al|^bZY1rg2XU0^Ij^*3_qJ(r0ru?gu?xm>zf0>PsZ6_pF^sq} z1Ft9A*26^kwe6{gYtcxicl_r$j}1adaZG0k%#tRG;<}XLS*gr|$}@9KBcDlkK-cDK zHvVdC%g)r!W`!n1r+ki$W5hgJmvLY>yeU+RQ?n6xU`jdDVUahi4h^ft+b|&*C9Eji z3W+sFE4yuv16Jd|-sY5M=76UG_b{md4zPZdg7AVnW=gXD-Q5V+9=cO)B-n>F=uuDW z6MRvE-rHlFBxE82W7YmBu3(m)1$Uf`VF%z1-7! z9PeMSV32B~q{zjKpmvC~t z0*O<7YePfKX`La6v%TQdxnOLWYigmKEr4L)TEoNLgZ=!r{0ba zPP6fUTF&m0OF$%WxNBmd-9gCjNSf{%y2f48JUB_@@+3|NPVMZgGJp6GRE2dgafg>o z7KEWvY#2MRL|#bpkeJ5fPQP+5u^8he*dL-kTi>Y!#{R89aZV%n{J$QX3%EsX=cbm??YcAbIdD_>Ksi`OVtS|tVU3^QZ_2i zt%K1H_7f6AQCL;ZOmlX}@|9B>a!ftUx^mp&MoyZp2kLdPDisq$p-N3a@<9orCd+TJ zbK{PycQhepCq2|4z87~HO|}1$uS&NkDK0r#7zoJT>G#e|4 zu3-edHT58GB?qPUSx4czJ2x?VuDhG1aUH6d{>|MDT)nej#7jQ;IQIhMe!2LXjzCY6&^kN^40yS&+BP9@7Sco0M5Z zGWHB=#@n9m_#;=}1?HuzQ>-H`BDa+D)?h>#1zncEynnVZnOj|tkL+gr>SPCVpWlzC z>ZyBRPR7wAW@ViwO}qWhaDx@@QsZeRyHE8tdC23ZEL2gHK~9v-I`%oA$rKOSG+l#u z{^*apPtR1CYcnvCvwN_zoP2=ISHYlMWO2*A$#J3VwVCO?`FnNSp6}Y{)s8gbM^y{G zoxFCe$jWD|#ioLfd*D~RvaTdsz@Gm6rgn?__hKR}lt$V{Eb_%2k`bZdEi?A~N=HoS z=z|`OOzpxJtY=)&mQ3$TScB7>q$JjTe7IoPPNv)y{HYIMTewU3b_d_oVqvD)P5gNh z)IAo=hN+u2#zA9Y3;Rp?=tH{?A#U;yg|VtTdrXO4&yQ>x>;k?v-7*biV5`@?EYh0= zi$OWW9rOsnrVrq{gmRoN!bz8Pr+9`jq32Z;7If%3=9xA|{n5r&iO=ks((Hp+ooN%o zcBQT2>IjP@Pl6q}zn?`HyX*UYs-;jPQ;Y=%u5wrmg z8z6g)Fw`A?tNdgf7-l?@iBBTfB{&N(l~&cR!WsYFX3IhjAWii_<`(-|ZRfBSP@a`- z#)!{o*2rXPvp*5nrsYCwiL}s1%bP3z<~`A6f9!Qk=8!GLot>3n0}A^)3?^$L+k;Q- zLNZ|5$+l%$?Akx$(B|CRRI$0VZFfZ-kX`-(HOUsOr80FU7nQKnuKzbOc~Z4s)?N!u z>aP1s9eEqw%F42|U8!7oZN6?r?Mtv_1B$KUV~oo%zGrgmw2M#NPa+Xn%3x4VCd ztr6>q3`VRyT1KkoTV6c{M~OaLhihmtTR1&aGez*?0~_4P9N9+#E&E^q>1%FVF+g;2 z6PF0qWfFPi<#Zu?pRjAmcUt+rC1lY|Vd{Ii;5M0>7VijL$43JQXb$Q%*FI(+57?KO zjyrpefbOBmu;dsc7`{6DVgm55v^#KVF#$}ObwvM2BCB4SEi}JLlW27ZUPTAE^~nV4 zV*~9h9QXMB#1J0%Ql-H`3ORbrf!$>o4Ew*ROUCU^;jw3fk8%fvixyuX~Ze;-8rZhagkVXS4Yn>*3!HNmv(`D;G|g%%EPW(-9x=sX*o zBH=uT1F&XKQKY{~DR$EdyU*bDf$RCNI`yEw z)?pVOd_FQDJZX%%b=h-I_39%C+nxt^$1q~vCa=4O;2H0!Ht&a5W2R1U-t{()SVYV} z!xULJB|!6>(sy|5Ser;K%|lBk`{oJnt-qI0dzv#F(oCXyc0|9+weAbccNLagKBqIY zgq@X}ngMf*NDXB1dfSa^(p2eSm@e2KHz_W8$-1sZ%w08VNAlx%V{J&vk20MU?UD|n z5ET{`D8XDOtrXvnH-Ws}fTWg15)W`DHqP41S1j8X?85H2Q@Y(Ly^Gm@ae*rJsw00` z7KKS2O7sB+UtBuF4L_E}mqqIwYlejB@%-}(x$*Fp$uAJ)l+P7O*KF)2%_c2$O2qSd zOws3+4Z0b2R1N#9Zq4KWvP>wz*2m|9{V=Ef=%=MNye`Ks*}ea5h@xZ3B>&?1z^0PE z?{a#WGYJdByLxQPDb@2g6FsJq9f*u!F7^E?wE#L6SSoDJ#BbUjaM%H|`>h z87^MSromc{jbVf@d^I6g zBhQul9OU~``7j^K>QeH&MU6OLZ0kF-zScA7qnRb9_dsjOmJAH2>2}M<{o6bJQiMCg zspJd=NkM5^H-@lRw~LxMUftIq=KDgj$q&e8LN3p+1uf{En;S5ep8`BT)e2^BtvKSa z&qMZj@q(=)p~CI7XT#DRB-vkAy!2aGb>-;?hp4 zZ1230BgxC`V~=!X8`>ty?F0s#$DN3mYpXjP8JlVwN*YtUD(GQXczWZ`b||>l30g_U z9=kf{?xKm_3|>i@OzkP@_cWff&9dluuMWA>hUtyYi!SQ4bZX@1u@&OZsBT4Hs2?vYT&jX#glC2VGQ z+9w9L6Du)${w+_wxnI?LMq@pZXP;3}u(xB0zxLwZ=dJ0l!+6(p=#TSHhM3J0-#{0h zF%x3bs_$9MdF77X^5}XKwV~p5`U)sS1eC^9%b5 z-&(NdiV+8TFwWqf!~^at3Lg{M39I9p;$(~)F8+%mU&kUHwe#0Xnp&Yf9~Qwj@TdQ< zh?Cg5ygiLx261v#>NW8`7^XnVuaxxM$?Cbp(&cnM@TUJfwOYfDek4n%R5;qcf?kK_`}>Y_a7jI zqntI|*ueD7PjW&$?;al);dtkspWOi9)-#RG!%J+Z+KD^V=DUUN+*bf8B@3wFXpBOdNV&@bbd~DQylp+P3~(XxyQ%Yap}rf96n33w&Xr@ zCm41>&*AL@?A?<7cmvI$5dFOe)5H2e$uXn;9Ej6}`H@>r=qUeqH8(tpclu6A%7Y7P zyP}PwTo+f2QToG_{G+LP@nv>8;oQ^>{mCSqgNg{uYB+P@`iOkSY!9uY@|N%+4hKQg zk6>}wJ^&kb8zx~LDYv}zT^=u<%>-<4)*~004(SW`ln$i)jTactj#K1fk+Ff_->B-8 z+-@Xoe>?~`QD0EPj9!YAYZtN&-7QX7Jt%Kp>tsM8DbU{RF~8G+uu1nFk(4O zkpl;w3u@E*lQ_$8w-uDJ5PqCbinWUbUGC|ftRBmhieV!tShTF`Wxrb`GxP7U&Sa8H z6YlDg_H3Aem1g(d4>&22G;r#-*To-sIn1AYwLf4WO7Qn3f7`F5{1K#9FUMMl6bJf1 ziX&v*GI6cir&8wBO>Ezwt0dg{^_P~4CrH-jeh>b!{AIAkyHcDr&fhTAxMh`eOM$L@ z?t*l9i(4zWUN-A2(LSf*QjvYGZ=8}F+D-eUiYrFb$KjTbU*6&~2yU0Y^yL7Hp)|Eq zA6Pj@QTL{UI~FWb^OqD?m@O&vsrP-Z&6V7}719oE`c}#E$YRT-os+k@&k90y-#5^L z<_;tlA_qH?Z}K@%$KD{?XDyE(IlUZlRU~Cr-B*h))C+c$`TFf+E=EgYVVY{jeW!F?)h;bE4wQwv{$?9o zFK~Uve*XFjTPnnq85bI-7jf3R=Cs0|1!t6U46?_Pu2Z|Y3YzzKK5{aA_SJBXCeDW4 z#Ze?gg-cMyJyIO__D6|jF!}NBuDtsIj15J8x2mSU{T$Bjr7U-6iN$=0;IuikgU2V( z>G|hiwAXN-W$fWou`J`^*z4-1sf<#jQ~mZ5!2rstG90;BKn4AAY3wkPoiuvu~aB(V$ zoTSP+&-{rQv!n0I0G4mAZWYnm$13yFUtFs0r*^1i-<;`AtN-pScRo2i*9s?1;qHM+ uM;jYct#4(}_ 1);\n" +"Language: pt_BR\n" + +#: includes/class-dj-upcoming-widget.php:13 +msgid "Display the upcoming Shows." +msgstr "Mostrar os próximos Shows." + +#: includes/class-dj-upcoming-widget.php:15 +msgid "(Radio Station) Upcoming Shows" +msgstr "(Estação de Rádio) Próximos Shows" + +#: includes/class-dj-upcoming-widget.php:40 includes/class-dj-widget.php:41 +#: includes/class-playlist-widget.php:33 +msgid "Title" +msgstr "Título" + +#: includes/class-dj-upcoming-widget.php:49 includes/class-dj-widget.php:50 +msgid "Link the title to the Show page" +msgstr "Conecte o título a página do Show" + +#: includes/class-dj-upcoming-widget.php:58 includes/class-dj-widget.php:59 +msgid "Above" +msgstr "Acima" + +#: includes/class-dj-upcoming-widget.php:59 includes/class-dj-widget.php:60 +msgid "Left" +msgstr "Esquerda" + +#: includes/class-dj-upcoming-widget.php:60 includes/class-dj-widget.php:61 +msgid "Right" +msgstr "Direita" + +#: includes/class-dj-upcoming-widget.php:61 includes/class-dj-widget.php:62 +msgid "Below" +msgstr "Abaixo" + +#: includes/class-dj-upcoming-widget.php:68 includes/class-dj-widget.php:70 +msgid "Show Title Position (relative to Avatar)" +msgstr "Mostrar Posição do Título (relativo ao Avatar)" + +#: includes/class-dj-upcoming-widget.php:76 +msgid "Display Show Avatars" +msgstr "Mostrar Avatares do Show" + +#: includes/class-dj-upcoming-widget.php:82 includes/class-dj-widget.php:84 +msgid "Avatar Width" +msgstr "Largura do Avatar" + +#: includes/class-dj-upcoming-widget.php:85 +msgid "Width of Show Avatars (in pixels, default 75px)" +msgstr "Largura dos Avatares do Show (em pixels, padrão 75px)" + +#: includes/class-dj-upcoming-widget.php:92 +msgid "Display names of the DJs on the show" +msgstr "Mostrar nomes dos DJs no show" + +#: includes/class-dj-upcoming-widget.php:100 includes/class-dj-widget.php:102 +msgid "Link DJ names to author pages" +msgstr "Conectar nomes dos DJs as páginas dos autores" + +#: includes/class-dj-upcoming-widget.php:106 +msgid "No Additional Schedules" +msgstr "Nenhuma Programação Adicional" + +#: includes/class-dj-upcoming-widget.php:109 includes/class-dj-widget.php:143 +msgid "If no Show is scheduled for the current time, display this text." +msgstr "" +"Se nenhum Show está programado para o horário atual, mostrar esse texto." + +#: includes/class-dj-upcoming-widget.php:116 includes/class-dj-widget.php:110 +msgid "Display schedule info for this show" +msgstr "Mostrar informações da programação deste show" + +#: includes/class-dj-upcoming-widget.php:122 +msgid "Limit" +msgstr "Limite" + +#: includes/class-dj-upcoming-widget.php:125 +msgid "Number of upcoming Shows to display." +msgstr "Número de Shows à seguir para mostrar." + +#: includes/class-dj-upcoming-widget.php:129 includes/class-dj-widget.php:147 +msgid "Time Format" +msgstr "Formato de Hora" + +#: includes/class-dj-upcoming-widget.php:132 includes/class-dj-widget.php:150 +msgid "12 Hour" +msgstr "12 Horas" + +#: includes/class-dj-upcoming-widget.php:134 includes/class-dj-widget.php:152 +msgid "24 Hour" +msgstr "24 Horas" + +#: includes/class-dj-upcoming-widget.php:138 +msgid "Choose time format for displayed schedules." +msgstr "Escolher formato de tempo para programações à mostra." + +#: includes/class-dj-widget.php:12 +msgid "The current on-air DJ." +msgstr "DJ atual no ar." + +#: includes/class-dj-widget.php:14 +msgid "(Radio Station) Show/DJ On-Air" +msgstr "(Estação de Rádio)Show/DJ no ar" + +#: includes/class-dj-widget.php:78 +msgid "Display Show Avatar" +msgstr "Mostrar Avatar do Show" + +#: includes/class-dj-widget.php:87 +msgid "Width of Show Avatar (in pixels, default full width)" +msgstr "Largura do Avatar do Show (em pixels, padrão largura cheia)" + +#: includes/class-dj-widget.php:94 +msgid "Display names of the DJs on the Show" +msgstr "Mostrar nomes dos DJs no Show" + +#: includes/class-dj-widget.php:118 +msgid "Display multiple schedules (if show airs more than once per week)" +msgstr "" +"Mostrar múltiplas programações (se o show vai ao ar mais de uma vez por " +"semana)" + +#: includes/class-dj-widget.php:126 +msgid "Display description of show" +msgstr "Mostrar descrição do show" + +#: includes/class-dj-widget.php:134 +msgid "Display link to show's playlist" +msgstr "Mostrar o link para a playlist do show" + +#: includes/class-dj-widget.php:140 +msgid "No Show Display Text" +msgstr "Texto de Display Sem Show" + +#: includes/class-dj-widget.php:156 +msgid "Choose time format for displayed schedules" +msgstr "Escolher formato de tempo para programações à mostra" + +#: includes/class-playlist-widget.php:12 +msgid "Display currently playling playlist." +msgstr "Mostrar a playlist sendo tocada." + +#: includes/class-playlist-widget.php:14 +msgid "(Radio Station) Now Playing List" +msgstr "(Estação de Rádio) Lista de Tocando Agora" + +#: includes/class-playlist-widget.php:42 +msgid "Show Song Title" +msgstr "Mostrar Título da Música" + +#: includes/class-playlist-widget.php:50 +msgid "Show Artist Name" +msgstr "Mostrar Nome do Artisra" + +#: includes/class-playlist-widget.php:58 +msgid " Show Album Name" +msgstr " Mostrar Nome do Ãlbum" + +#: includes/class-playlist-widget.php:66 +msgid "Show Record Label Name" +msgstr "Mostrar Nome da Gravadora" + +#: includes/class-playlist-widget.php:74 +msgid "Show DJ Comments" +msgstr "Mostrar Comentários do DJ" + +#: includes/data-feeds.php:769 +msgid "Requested Show was not found." +msgstr "Show requisitado não foi encontrado." + +#: includes/data-feeds.php:770 +msgid "No Requested Shows were found." +msgstr "Nenhum Show Requisitado foi encontrado." + +#: includes/data-feeds.php:771 includes/post-types-admin.php:535 +msgid "No Shows were found." +msgstr "Nenhum Show foi encontrado." + +#: includes/data-feeds.php:813 +msgid "Requested Genre was not found." +msgstr "Gênero Requisitado não foi encontrado." + +#: includes/data-feeds.php:814 +msgid "No Requested Genres were found." +msgstr "Nenhum Gênero requisitado foi encontrado." + +#: includes/data-feeds.php:815 +msgid "No Genres were found." +msgstr "Nenhum Gênero foi encontrado." + +#: includes/data-feeds.php:858 +msgid "Requested Language was not found." +msgstr "A linguagem solicitada não foi encontrada." + +#: includes/data-feeds.php:859 +msgid "No Requested Languages were found." +msgstr "Não foram encontradas as línguas solicitadas." + +#: includes/data-feeds.php:860 +msgid "No Languages were found." +msgstr "Nenhum Idioma encontrado." + +#: includes/data-feeds.php:896 +msgid "Error 404 Not Found" +msgstr "Erro 404 não encontrado" + +#: includes/data-feeds.php:897 +msgid "The requested data could not be found." +msgstr "Os dados solicitados não puderam ser encontrados." + +#: includes/master-schedule.php:260 radio-station-admin.php:136 +#: templates/master-schedule-tabs.php:176 +msgid "Genres" +msgstr "Gêneros" + +#: includes/master-schedule.php:276 +msgid "Click to toggle Highlight of Shows with this Genre." +msgstr "Clique para alternar destaque de shows com este gênero." + +#: includes/post-types-admin.php:90 +msgid "Show Language" +msgstr "Mostrar idioma" + +#: includes/post-types-admin.php:126 +msgid "Main Radio Language" +msgstr "Idioma Principal da Estação" + +#: includes/post-types-admin.php:130 +msgid "Select below if Show language(s) differ." +msgstr "Selecione abaixo se o(s) idioma(s) do show diferem." + +#: includes/post-types-admin.php:158 +msgid "Remove Language" +msgstr "Remover Língua" + +#: includes/post-types-admin.php:166 +msgid "Select Language" +msgstr "Seleciona Língua" + +#: includes/post-types-admin.php:180 +msgid "Click on a Language to Add it." +msgstr "Clicar em um Idioma para adicioná-lo." + +#: includes/post-types-admin.php:258 +msgid "Playlist Entries" +msgstr "Entradas da Playlist" + +#: includes/post-types-admin.php:284 includes/post-types-admin.php:659 +#: includes/shortcodes.php:1304 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:11 +msgid "Artist" +msgstr "Artista" + +#: includes/post-types-admin.php:285 includes/post-types-admin.php:658 +#: includes/shortcodes.php:1296 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:12 +msgid "Song" +msgstr "Música" + +#: includes/post-types-admin.php:286 includes/shortcodes.php:1311 +#: templates/legacy/single-playlist.php:47 +#: templates/single-playlist-content.php:13 +msgid "Album" +msgstr "Albúm" + +#: includes/post-types-admin.php:287 templates/legacy/single-playlist.php:48 +#: templates/single-playlist-content.php:14 +msgid "Record Label" +msgstr "Gravadora" + +#: includes/post-types-admin.php:311 includes/post-types-admin.php:353 +#: includes/shortcodes.php:1325 templates/single-playlist-content.php:15 +msgid "Comments" +msgstr "Comentários" + +#: includes/post-types-admin.php:314 includes/post-types-admin.php:354 +msgid "New" +msgstr "Novo" + +#: includes/post-types-admin.php:318 includes/post-types-admin.php:355 +#: includes/post-types-admin.php:660 +msgid "Status" +msgstr "Status" + +#: includes/post-types-admin.php:321 includes/post-types-admin.php:356 +msgid "Queued" +msgstr "Em fila" + +#: includes/post-types-admin.php:323 includes/post-types-admin.php:357 +msgid "Played" +msgstr "Tocado" + +#: includes/post-types-admin.php:327 includes/post-types-admin.php:359 +#: includes/post-types-admin.php:1262 includes/post-types-admin.php:1389 +msgid "Remove" +msgstr "Remover" + +#: includes/post-types-admin.php:337 +msgid "Add Entry" +msgstr "Adicionar Entrada" + +#: includes/post-types-admin.php:392 includes/post-types-admin.php:395 +msgid "Schedule" +msgstr "Agenda" + +#: includes/post-types-admin.php:406 includes/post-types-admin.php:409 +msgid "Publish" +msgstr "Publicar" + +#: includes/post-types-admin.php:423 +msgid "Submit for Review" +msgstr "Enviar para Revisão" + +#: includes/post-types-admin.php:426 includes/post-types-admin.php:441 +msgid "Update Playlist" +msgstr "Atualizar Playlist" + +#: includes/post-types-admin.php:440 +msgid "Update" +msgstr "Atualizar" + +#: includes/post-types-admin.php:459 +msgid "Linked Show" +msgstr "Show Conectado" + +#: includes/post-types-admin.php:538 +msgid "You are not assigned to any Shows." +msgstr "Você não está designado para nenhum Show." + +#: includes/post-types-admin.php:546 +msgid "Unassigned" +msgstr "Não atribuído" + +#: includes/post-types-admin.php:630 includes/post-types.php:49 +msgid "Show" +msgstr "Show" + +#: includes/post-types-admin.php:631 +msgid "Tracks" +msgstr "Faixas" + +#: includes/post-types-admin.php:632 +msgid "Track List" +msgstr "Lista de faixas" + +#: includes/post-types-admin.php:654 +msgid "Show/Hide Tracklist" +msgstr "Mostrar/Esconder Tracklist" + +#: includes/post-types-admin.php:716 +msgid "Related to Show" +msgstr "Relacionado ao Show" + +#: includes/post-types-admin.php:758 +msgid "No Shows to Select." +msgstr "Sem shows para selecionar." + +#: includes/post-types-admin.php:819 +msgid "Show Information" +msgstr "Mostrar informações" + +#: includes/post-types-admin.php:848 +msgid "Active" +msgstr "Ativo" + +#: includes/post-types-admin.php:850 +msgid "" +"Check this box if show is currently active (Show will not appear on " +"programming schedule if unchecked.)" +msgstr "" +"Marcar esse campo se o show está ativo (O show não irá aparecer na agenda de " +"programas se não marcado)" + +#: includes/post-types-admin.php:852 +msgid "Website Link" +msgstr "Link do Website" + +#: includes/post-types-admin.php:855 +msgid "DJ / Host Email" +msgstr "Email do DJ/Host" + +#: includes/post-types-admin.php:858 +msgid "Latest Audio File" +msgstr "Último Arquivo de Ãudio" + +#: includes/post-types-admin.php:868 +msgid "Show DJ(s) / Host(s)" +msgstr "DJ(s) / Host(s) do Show" + +#: includes/post-types-admin.php:872 includes/post-types-admin.php:988 +msgid "Show Producer(s)" +msgstr "Produtor(es) do Show" + +#: includes/post-types-admin.php:885 +msgid "Toggle panel: %s" +msgstr "Alternar painel: %s" + +#: includes/post-types-admin.php:912 +msgid "DJs / Hosts" +msgstr "DJs / Hosts" + +#: includes/post-types-admin.php:975 includes/post-types-admin.php:1044 +msgid "Ctrl-Click selects multiple." +msgstr "Ctrl-Click seleciona múltiplos." + +#: includes/post-types-admin.php:1058 +msgid "Show Schedule" +msgstr "Agenda do Show" + +#: includes/post-types-admin.php:1162 includes/post-types-admin.php:1324 +msgid "Day" +msgstr "Dia" + +#: includes/post-types-admin.php:1178 includes/post-types-admin.php:1337 +#: includes/post-types-admin.php:2168 includes/post-types-admin.php:2343 +msgid "Start Time" +msgstr "Hora de Início" + +#: includes/post-types-admin.php:1210 includes/post-types-admin.php:1359 +#: includes/post-types-admin.php:2198 includes/post-types-admin.php:2344 +msgid "End Time" +msgstr "Hora de Término" + +#: includes/post-types-admin.php:1246 includes/post-types-admin.php:1381 +msgid "Encore" +msgstr "Show repetido" + +#: includes/post-types-admin.php:1256 includes/post-types-admin.php:1385 +msgid "Disabled" +msgstr "Desabilitado" + +#: includes/post-types-admin.php:1271 +msgid "Shift Conflicts" +msgstr "Conflitos de Expediente" + +#: includes/post-types-admin.php:1297 +msgid "Warning! Show Shift Conflicts were detected!" +msgstr "Atenção! Conflitos de Expediente entre Shows foram detectados!" + +#: includes/post-types-admin.php:1298 +msgid "" +"Please note that Shifts with conflicts are automatically disabled upon " +"saving." +msgstr "" +"Por favor notar que Expedientes com conflitos são automaticamente " +"desabilitados quando salvos." + +#: includes/post-types-admin.php:1299 +msgid "" +"Fix the Shift and/or the Shift on the conflicting Show and Update them both." +msgstr "" +"Consertar Expediente e/ou o Expediente do Show em conflito e Atualizar " +"ambos." + +#: includes/post-types-admin.php:1300 +msgid "" +"Then you can uncheck the shift Disable box and Update to re-enable the Shift." +msgstr "" +"Então você pode desmarcar a caixa para Desabilitar o expediente e Atualizar " +"para reabilitar o Expediente." + +#: includes/post-types-admin.php:1312 +msgid "Add Shift" +msgstr "Adicionar Expediente" + +#: includes/post-types-admin.php:1316 +msgid "Are you sure you want to remove this shift?" +msgstr "Tem certeza que quer remover esse expediente?" + +#: includes/post-types-admin.php:1434 +msgid "Show Description" +msgstr "Descrição do Show" + +#: includes/post-types-admin.php:1451 +msgid "" +"The text field below is for your Show Description. It will display in the " +"About section of your Show page." +msgstr "" +"O campo de texto abaixo é para a sua Descrição do Show. Ele será exibido na " +"seção Sobre da página do seu Show." + +#: includes/post-types-admin.php:1452 +msgid "" +"It is not recommended to include your past show content or archives in this " +"area, as it will affect the Show page layout your visitors see." +msgstr "" +"Não é recomendado incluir o conteúdo ou arquivos do seu show passado nesta " +"área, pois afetará o layout da página show que seus visitantes veem." + +#: includes/post-types-admin.php:1453 +msgid "" +"It may also impact SEO, as archived content won't have their own pages and " +"thus their own SEO and Social meta rules." +msgstr "" +"Isto pode também pode afetar SEO, como conteúdo arquivado não terá suas " +"próprias páginas e, portanto, suas próprias regras SEO e meta social." + +#: includes/post-types-admin.php:1454 +msgid "" +"We recommend using WordPress Posts to add new posts and assign them to your " +"Show(s) using the Related Show metabox on the Post Edit screen so they " +"display on the Show page." +msgstr "" +"Recomendamos o uso do WordPress Posts para adicionar novas postagens e " +"atribuí-las ao seu Show(s) usando a metacaixa do Show Relacionado na tela " +"Edição de Post para que elas sejam exibidas na página do Show." + +#: includes/post-types-admin.php:1455 +msgid "" +"You can then assign them to a relevent Post Category for display on your " +"site also." +msgstr "" +"Você pode também designa-las a uma Categoria de Post relevante no seu site." + +#: includes/post-types-admin.php:1476 +msgid "Show Logo" +msgstr "Mostrar Logo" + +#: includes/post-types-admin.php:1487 +msgid "Show Images" +msgstr "Mostrar Imagens" + +#: includes/post-types-admin.php:1532 +msgid "Set Show Avatar Image" +msgstr "Definir Imagem de Avatar do Show" + +#: includes/post-types-admin.php:1536 +msgid "Remove Show Avatar Image" +msgstr "Remover Imagem de Avatar do Show" + +#: includes/post-types-admin.php:1568 +msgid "Set Show Header Image" +msgstr "Definir Imagem de Cabeçalho do Show" + +#: includes/post-types-admin.php:1572 +msgid "Remove Show Header Image" +msgstr "Remover Imagem de Cabeçalho do Show" + +#: includes/post-types-admin.php:1589 +msgid "Are you sure you want to remove this image?" +msgstr "Tem certeza que quer remover essa imagem?" + +#: includes/post-types-admin.php:1948 +msgid "Active?" +msgstr "Ativo?" + +#: includes/post-types-admin.php:1950 +msgid "About?" +msgstr "Sobre?" + +#: includes/post-types-admin.php:1951 +msgid "Shifts" +msgstr "Expedientes" + +#: includes/post-types-admin.php:1953 +msgid "Hosts" +msgstr "Hosts" + +#: includes/post-types-admin.php:1954 +msgid "Show Avatar" +msgstr "Mostrar Avatar" + +#: includes/post-types-admin.php:1970 includes/post-types-admin.php:1981 +#: includes/post-types-admin.php:2443 +msgid "Yes" +msgstr "Sim" + +#: includes/post-types-admin.php:1971 includes/post-types-admin.php:1979 +#: includes/post-types-admin.php:2441 +msgid "No" +msgstr "Não" + +#: includes/post-types-admin.php:1999 +msgid "This Shift is Disabled." +msgstr "Esse Expediente está Desabilitado." + +#: includes/post-types-admin.php:2004 +msgid "This Shift has Schedule Conflicts and is Disabled." +msgstr "Esse Expediente tem conflitos de Programação e está Desabilitado." + +#: includes/post-types-admin.php:2005 +msgid "This Shift has Schedule Conflicts." +msgstr "Esse Expediente tem conflitos de Programação." + +#: includes/post-types-admin.php:2088 +msgid "Filter by show day" +msgstr "Filtrar por dia de Show" + +#: includes/post-types-admin.php:2090 +msgid "All show days" +msgstr "Todos os dias de Show" + +#: includes/post-types-admin.php:2117 +msgid "Override Schedule" +msgstr "Substituir Programação" + +#: includes/post-types-admin.php:2162 +msgid "Date" +msgstr "Data" + +#: includes/post-types-admin.php:2342 +msgid "Override Date" +msgstr "Substituir Data" + +#: includes/post-types-admin.php:2345 +msgid "Affected Show(s) on Date" +msgstr "Show(s) Afetados na Data" + +#: includes/post-types-admin.php:2347 +msgid "Description" +msgstr "Descrição" + +#: includes/post-types-admin.php:2348 +msgid "Override Image" +msgstr "Substituir Imagem" + +#: includes/post-types-admin.php:2423 +msgid "Inactive Show" +msgstr "Show Inativo" + +#: includes/post-types-admin.php:2425 +msgid "Disabled Shift" +msgstr "Expediente Desabilitado" + +#: includes/post-types-admin.php:2506 +msgid "Filter by override date" +msgstr "Filtrar por data de Substituição" + +#: includes/post-types-admin.php:2509 +msgid "All override dates" +msgstr "Todas as datas substituidas" + +#: includes/post-types.php:48 includes/post-types.php:56 +#: radio-station-admin.php:131 +msgid "Shows" +msgstr "Shows" + +#: includes/post-types.php:50 includes/post-types.php:51 +#: radio-station-admin.php:132 +msgid "Add Show" +msgstr "Adicionar Show" + +#: includes/post-types.php:52 +msgid "Edit Show" +msgstr "Editar Show" + +#: includes/post-types.php:53 +msgid "New Show" +msgstr "Novo Show" + +#: includes/post-types.php:54 +msgid "View Show" +msgstr "Ver Show" + +#: includes/post-types.php:61 +msgid "Post type for Show descriptions" +msgstr "Tipo de post para descrições de Show" + +#: includes/post-types.php:89 includes/post-types.php:97 +#: radio-station-admin.php:134 templates/single-show-content.php:520 +msgid "Playlists" +msgstr "Playlists" + +#: includes/post-types.php:90 +msgid "Playlist" +msgstr "Playlist" + +#: includes/post-types.php:91 includes/post-types.php:92 +msgid "Add Playlist" +msgstr "Adicionar Playlist" + +#: includes/post-types.php:93 +msgid "Edit Playlist" +msgstr "Editar Playlist" + +#: includes/post-types.php:94 +msgid "New Playlist" +msgstr "Nova Playlist" + +#: includes/post-types.php:95 includes/shortcodes.php:1337 +msgid "View Playlist" +msgstr "Ver Playlist" + +#: includes/post-types.php:102 +msgid "Post type for Playlist descriptions" +msgstr "Tipo de post para descrições de Playlist" + +#: includes/post-types.php:129 radio-station-admin.php:137 +msgid "Schedule Overrides" +msgstr "Substituições de Programação" + +#: includes/post-types.php:130 +msgid "Schedule Override" +msgstr "Substituição de Programação" + +#: includes/post-types.php:131 includes/post-types.php:132 +msgid "Add Schedule Override" +msgstr "Adicionar Substituição de Programação" + +#: includes/post-types.php:133 +msgid "Edit Schedule Override" +msgstr "Editar Substituição de Programação" + +#: includes/post-types.php:134 +msgid "New Schedule Override" +msgstr "Nova Substituição de Programação" + +#: includes/post-types.php:135 +msgid "View Schedule Override" +msgstr "Ver Substituição de Programação" + +#: includes/post-types.php:140 +msgid "Post type for Schedule Override" +msgstr "Tipo de post para substituição de Programação" + +#: includes/post-types.php:170 +msgid "Host Profiles" +msgstr "Perfis de Hosts" + +#: includes/post-types.php:171 +msgid "Host Profile" +msgstr "Perfil do Host" + +#: includes/post-types.php:172 includes/post-types.php:175 +msgid "New Host Profile" +msgstr "Novo perfil de Host" + +#: includes/post-types.php:173 +msgid "Add Host Profile" +msgstr "Adicionar Perfil de Host" + +#: includes/post-types.php:174 +msgid "Edit Host Profile" +msgstr "Editar Perfil de Host" + +#: includes/post-types.php:176 +msgid "View Host Profile" +msgstr "Ver Perfil de Host" + +#: includes/post-types.php:177 +msgid "Show Hosts" +msgstr "Mostrar Hosts" + +#: includes/post-types.php:183 +msgid "Post type for DJ / Host Profiles" +msgstr "Tipo de Post para Perfis de DJ / Host" + +#: includes/post-types.php:212 +msgid "Producer Profiles" +msgstr "Perfis de Produtor" + +#: includes/post-types.php:213 +msgid "Producer Profile" +msgstr "Perfil de Produtor" + +#: includes/post-types.php:214 includes/post-types.php:217 +msgid "New Producer Profile" +msgstr "Novo Perfil de Produtoe" + +#: includes/post-types.php:215 +msgid "Add Producer Profile" +msgstr "Adicionar Perfil de Produtor" + +#: includes/post-types.php:216 +msgid "Edit Producer Profile" +msgstr "Editar Perfil de Produtor" + +#: includes/post-types.php:218 +msgid "View Producer Profile" +msgstr "Ver Perfil de Produtor" + +#: includes/post-types.php:219 +msgid "Show Producers Profile" +msgstr "Mostrar Perfil do Produtor" + +#: includes/post-types.php:225 +msgid "Post type for Producer Profiles" +msgstr "Tipos de Post para Perfis de Podutor" + +#: includes/post-types.php:332 +msgid "Edit" +msgstr "Editar" + +#: includes/post-types.php:357 +msgctxt "taxonomy general name" +msgid "Genres" +msgstr "Gêneros" + +#: includes/post-types.php:358 +msgctxt "taxonomy singular name" +msgid "Genre" +msgstr "Gênero" + +#: includes/post-types.php:359 +msgid "Search Genres" +msgstr "Buscar Gêneros" + +#: includes/post-types.php:360 +msgid "All Genres" +msgstr "Todos os Gêneros" + +#: includes/post-types.php:361 +msgid "Parent Genre" +msgstr "Gênero Parente" + +#: includes/post-types.php:362 +msgid "Parent Genre:" +msgstr "Gênero Parente:" + +#: includes/post-types.php:363 +msgid "Edit Genre" +msgstr "Editar Gênero" + +#: includes/post-types.php:364 +msgid "Update Genre" +msgstr "Atualizar Gênero" + +#: includes/post-types.php:365 +msgid "Add New Genre" +msgstr "Adicionar Novo Gênero" + +#: includes/post-types.php:366 +msgid "New Genre Name" +msgstr "Novo Nome de Gênero" + +#: includes/post-types.php:367 +msgid "Genre" +msgstr "Gênero" + +#: includes/post-types.php:401 +msgctxt "taxonomy general name" +msgid "Languages" +msgstr "Idiomas" + +#: includes/post-types.php:402 +msgctxt "taxonomy singular name" +msgid "Language" +msgstr "Idioma" + +#: includes/post-types.php:403 +msgid "Search Languages" +msgstr "Buscar Idiomas" + +#: includes/post-types.php:404 +msgid "All Languages" +msgstr "Todos os Idiomas" + +#: includes/post-types.php:405 +msgid "Parent Language" +msgstr "Língua Parente" + +#: includes/post-types.php:406 +msgid "Parent Language:" +msgstr "Língua Parente:" + +#: includes/post-types.php:407 +msgid "Edit Language" +msgstr "Editar Idioma" + +#: includes/post-types.php:408 +msgid "Update Language" +msgstr "Atualizar Idioma" + +#: includes/post-types.php:409 +msgid "Add New Language" +msgstr "Adicionar novo Idioma" + +#: includes/post-types.php:410 +msgid "New Language Name" +msgstr "Nome do Novo Idioma" + +#: includes/post-types.php:411 +msgid "Language" +msgstr "Idioma" + +#: includes/shortcodes.php:51 +msgid "Radio Time" +msgstr "Horário na Rádio" + +#: includes/shortcodes.php:61 +msgid "Your Time" +msgstr "Seu Horário" + +#: includes/shortcodes.php:180 +msgid "No Shows in this Genre were found." +msgstr "Nenhum Show nesse Gênero foi encontrado." + +#: includes/shortcodes.php:182 +msgid "No Shows were found to display." +msgstr "Nenhum Show foi encontrado para exibição." + +#: includes/shortcodes.php:185 +msgid "No Playlists were found to display." +msgstr "Nenhuma Playlist foi encontrada para exibição." + +#: includes/shortcodes.php:187 +msgid "No Overrides were found to display." +msgstr "Nenhuma Substituição foi encontrada para exibição." + +#: includes/shortcodes.php:354 +msgid "No Genres were found to display." +msgstr "Nenhum Gênero foi encontrado para exibição." + +#: includes/shortcodes.php:443 +msgid "No Shows in this Genre." +msgstr "Nenhum Show neste Gênero." + +#: includes/shortcodes.php:599 +msgid "Published on " +msgstr "Publicado em " + +#: includes/shortcodes.php:863 includes/shortcodes.php:1138 +#: templates/single-show-content.php:422 +msgid "Encore Presentation" +msgstr "Apresentação Repetida" + +#: includes/shortcodes.php:876 includes/shortcodes.php:1150 +#: radio-station-admin.php:500 templates/master-schedule-legacy.php:168 +#: templates/master-schedule-list.php:75 +#: templates/master-schedule-table.php:189 +#: templates/master-schedule-tabs.php:89 +msgid "with" +msgstr "com" + +#: includes/shortcodes.php:907 includes/shortcodes.php:1181 +#: templates/master-schedule-legacy.php:179 +#: templates/master-schedule-list.php:83 +#: templates/master-schedule-table.php:200 +#: templates/master-schedule-tabs.php:99 templates/single-show-content.php:229 +#: templates/single-show-content.php:256 +msgid "and" +msgstr "e" + +#: includes/shortcodes.php:1084 +msgid "No Shows Upcoming" +msgstr "Nenhum Show Próximo" + +#: includes/shortcodes.php:1318 +msgid "Label" +msgstr "Gravadora" + +#: includes/shortcodes.php:1346 +msgid "No Playlist available." +msgstr "Nenhuma Playlist disponíveis." + +#: includes/shortcodes.php:1411 +msgid "More Playlists" +msgstr "Mais Playlists" + +#: includes/support-functions.php:1803 +msgid "Africa" +msgstr "Africa" + +#: includes/support-functions.php:1804 +msgid "America" +msgstr "America" + +#: includes/support-functions.php:1805 +msgid "Asia" +msgstr "Asia" + +#: includes/support-functions.php:1806 +msgid "Atlantic" +msgstr "Atlantic" + +#: includes/support-functions.php:1807 +msgid "Australia" +msgstr "Australia" + +#: includes/support-functions.php:1808 +msgid "Europe" +msgstr "Europa" + +#: includes/support-functions.php:1809 +msgid "Indian" +msgstr "Indico" + +#: includes/support-functions.php:1810 +msgid "Pacific" +msgstr "Pacifico" + +#: includes/support-functions.php:1811 +msgid "Antarctica" +msgstr "Antarctica" + +#: includes/support-functions.php:1854 +msgid "WordPress Timezone" +msgstr "Fuso Horário do WordPress" + +#: includes/support-functions.php:1920 +msgid "WordPress Setting" +msgstr "Configuração do WordPress" + +#: includes/support-functions.php:2853 +msgid "More Show Blog Posts" +msgstr "Mais Blog Posts do Show" + +#: loader.php:959 +msgid "Plugin Name" +msgstr "Nome do Plugin" + +#: loader.php:961 +msgid "Requires at least" +msgstr "Requer pelo menos" + +#: loader.php:961 loader.php:962 +msgid "WordPress" +msgstr "WordPress" + +#: loader.php:962 +msgid "Tested up to" +msgstr "Testado até" + +#: loader.php:963 +msgid "Stable Tag" +msgstr "Stable Tag" + +#: loader.php:964 +msgid "Contributors" +msgstr "Contribuidores" + +#: loader.php:984 +msgid "Extra Notes" +msgstr "Notas Extra" + +#: loader.php:1139 +msgid "" +"If you want to more easily access support and feedback for this plugins " +"features and functionality, %s can connect your user, %s at %s, to %s" +msgstr "" +"Se você quer acessar o suporte e feedback para as funcionalidades e " +"ferramentas desse plugin, %s pode conectar seu usuário, %s em %s, para %s" + +#: loader.php:1204 radio-station-admin.php:121 radio-station-admin.php:125 +#: radio-station-admin.php:143 +msgid "Settings" +msgstr "Configurações" + +#: loader.php:1323 +msgid "by" +msgstr "por" + +#: loader.php:1332 +msgid "Readme" +msgstr "Readme" + +#: loader.php:1334 +msgid "Docs" +msgstr "Docs" + +#: loader.php:1335 +msgid "Support" +msgstr "Suporte" + +#: loader.php:1336 +msgid "Dev" +msgstr "Dev" + +#: loader.php:1374 radio-station-admin.php:432 +msgid "Rate on WordPress.Org" +msgstr "Avalie em WordPress.Org" + +#: loader.php:1385 +msgid "Share the Plugin Love" +msgstr "Compartilhe o Amor pelo Plugin" + +#: loader.php:1396 radio-station.php:581 +msgid "Support this Plugin" +msgstr "Apoie esse Plugin" + +#: loader.php:1409 +msgid "Settings Updated." +msgstr "Configurações Atualizadas." + +#: loader.php:1411 +msgid "Error! Settings NOT Updated." +msgstr "Erro! Configurações NÃO Atualizadas." + +#: loader.php:1413 +msgid "Settings Reset!" +msgstr "Configurações Resetadas!" + +#: loader.php:1531 radio-station.php:542 +msgid "General" +msgstr "Geral" + +#: loader.php:1536 +msgid "Are you sure you want to reset to default settings?" +msgstr "Tem certeza que quer reiniciar as configurações para padrão?" + +#: loader.php:1628 +msgid "Reset Settings" +msgstr "Reiniciar Configurações" + +#: loader.php:1630 +msgid "Save Settings" +msgstr "Salvar Configurações" + +#: loader.php:1710 +msgid "Use Ctrl and Click to Select" +msgstr "Usar Ctrl e Click para Selecionar" + +#: loader.php:1737 +msgid "Available in Pro Version." +msgstr "Disponível na versão Pro." + +#: loader.php:1738 +msgid "Click Here to Upgrade!" +msgstr "Clique aqui para Atualizar!" + +#: loader.php:1740 +msgid "Coming soon in Pro version!" +msgstr "Em breve na Versão Pro!" + +#: radio-station-admin.php:103 +msgid "You do not have permissions to access that page." +msgstr "Você não tem permissões para acessar esta página." + +#. Plugin Name of the plugin/theme +#: radio-station-admin.php:116 radio-station-admin.php:121 +#: radio-station-admin.php:419 radio-station-admin.php:480 +#: radio-station-admin.php:594 radio-station-admin.php:661 +msgid "Radio Station" +msgstr "Estação de Rádio" + +#: radio-station-admin.php:142 templates/admin-export.php:2 +msgid "Export Playlists" +msgstr "Exportar Playlists" + +#: radio-station-admin.php:144 +msgid "Help" +msgstr "Ajuda" + +#: radio-station-admin.php:236 +msgid "Role Assignments" +msgstr "Atribuições de Funções" + +#: radio-station-admin.php:237 +msgid "" +"You can assign a Radio Station role to users through the WordPress User " +"editor." +msgstr "" +"Você pode designar funções na Estação de Rádio através do editor de Usuário " +"do WordPress." + +#: radio-station-admin.php:358 +msgid "Right-click and download this file to save your export" +msgstr "Clique com o botão direito para salvar a sua exportação" + +#: radio-station-admin.php:416 +msgid "" +"Help support us to make improvements, modifications and introduce new " +"features!" +msgstr "" +"Ajude-nos a fazer melhoras, modificações e introduzir novas ferramentas!" + +#: radio-station-admin.php:417 +msgid "" +"With over a thousand radio station users thanks to the original plugin " +"author Nikki Blight" +msgstr "" +"Com mais de mill usuários da Estação de Rádio obrigado ao author original do " +"plugin Nikki Blight" + +#: radio-station-admin.php:418 +msgid "since June 2019" +msgstr "des de Junho de 2019" + +#: radio-station-admin.php:420 +msgid " plugin development has been actively taken over by" +msgstr " desenvolvimento do Plugin foi ativamente assumido por" + +#: radio-station-admin.php:422 +msgid "We invite you to" +msgstr "Nós te convidamos para" + +#: radio-station-admin.php:424 +msgid "Become a Radio Station Patreon Supporter" +msgstr "Torne-se um Supporter da Estação de Rádio no Patreon" + +#: radio-station-admin.php:425 +msgid "to make it better for everyone" +msgstr "para tornar isto melhor para todos" + +#: radio-station-admin.php:441 radio-station-admin.php:619 +#: radio-station-admin.php:690 +msgid "Dismiss this Notice" +msgstr "Ignore esse Aviso" + +#: radio-station-admin.php:481 +msgid "has detected" +msgstr "detectou" + +#: radio-station-admin.php:482 +msgid "Schedule conflicts!" +msgstr "Conflitos de Programação!" + +#: radio-station-admin.php:486 +msgid "The following Shows have conflicting Shift times" +msgstr "Os próximos Shows têm conflitos de Horário" + +#: radio-station-admin.php:518 +msgid "Go to Show List" +msgstr "Ir para a Lista do Show" + +#: radio-station-admin.php:519 +msgid "Conflicts are highlighted" +msgstr "Conflitos estão em destaque" + +#: radio-station-admin.php:520 +msgid "in Show Shift column." +msgstr "na coluna de Horários de Shows." + +#: radio-station-admin.php:525 +msgid "This notice will persist" +msgstr "Esse aviso irá persistir" + +#: radio-station-admin.php:526 +msgid "until conflicts are resolved." +msgstr "até os conflitos serem resolvidos." + +#: radio-station-admin.php:593 +msgid "A new version of" +msgstr "Uma nova versão de" + +#: radio-station-admin.php:595 +msgid "is available." +msgstr "está disponível." + +#: radio-station-admin.php:599 +msgid "Take a moment to Upgrade for a better experience. In this update..." +msgstr "" +"Tire um momento e faça um Upgrade para uma melhor experiência. Nessa " +"atualização..." + +#: radio-station-admin.php:605 radio-station-admin.php:674 +msgid "Update Details" +msgstr "Atualizar Detalhes" + +#: radio-station-admin.php:610 +msgid "Update Now" +msgstr "Atualizar Agora" + +#: radio-station-admin.php:662 +msgid "Update Notice" +msgstr "Aviso de Atualização" + +#: radio-station-admin.php:667 +msgid "Thanks for Upgrading! You can enjoy these improvements now" +msgstr "" +"Obrigado por fazer o Upgrade! Você pode aproveitar essas melhoras agora" + +#: radio-station-admin.php:680 +msgid "Plugin Settings" +msgstr "Configurações do Plugin" + +#: radio-station-admin.php:856 +msgid "Stay tuned! Subscribe to Radio Station's" +msgstr "Fique ligado! Inscreva-se aos" + +#: radio-station-admin.php:857 +msgid "Plugin Updates and Announcements List" +msgstr "Updates do Plugin e Lista de Anúncios da Estação de Rádio" + +#: radio-station.php:115 +msgid "Streaming URL" +msgstr "URL da Stream" + +#: radio-station.php:117 +msgid "Enter the Streaming URL for your Radio Station." +msgstr "Insira a URL da Stream." + +#: radio-station.php:125 +msgid "Main Broadcast Language" +msgstr "Idioma Principal da Transmissão" + +#: radio-station.php:127 +msgid "Select the main language used on your Radio Station." +msgstr "Selecione o idioma principal usado na sua Estação de Rádio." + +#: radio-station.php:147 +msgid "Location Timezone" +msgstr "Fuso Horário da Localização" + +#: radio-station.php:149 +msgid "Select your Broadcast Location for Timezone display." +msgstr "" +"Selecione a Localização da sua Transmissão para exibição de Fuso Horário." + +#: radio-station.php:157 +msgid "12 Hour Format" +msgstr "Formato de 12 Horas" + +#: radio-station.php:158 +msgid "24 Hour Format" +msgstr "Formato de 24 Horas" + +#: radio-station.php:160 +msgid "Clock Time Format" +msgstr "Formato de Hora do Relógio" + +#: radio-station.php:162 +msgid "" +"Default Time Format Display for plugin output. Can be overridden in each " +"shortcode or widget." +msgstr "" +"Formato de Exibição de Tempo padrão para output do plugin. Pode ser " +"substituído em cada shortcode ou widget." + +#: radio-station.php:169 +msgid "Enable Data Routes" +msgstr "Permitir Rotas de Dados" + +#: radio-station.php:172 +msgid "Enables Station Data Routes via WordPress REST API." +msgstr "Permite Rotas de Dados da Estação via WordPress REST API." + +#: radio-station.php:179 +msgid "Enable Data Feeds" +msgstr "Permitir Feeds de Dados" + +#: radio-station.php:182 +msgid "Enable Station Data Feeds via WordPress feed links." +msgstr "Permitir Feeds de Dados da Estação via WordPress feed links." + +#: radio-station.php:189 +msgid "Show Shift Feeds" +msgstr "Exibir Feeds de Expediente" + +#: radio-station.php:192 +msgid "" +"Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor " +"to subscribe to a Show feed to be notified of Show shifts." +msgstr "" +"Converter Feeds RSS de um único Show para um feed de expediente de Show, " +"permitindo ao visitor inscrever-se ao feed de um Show para ser notificado " +"dos Expedientes." + +#: radio-station.php:200 +msgid "Transient Caching" +msgstr "Cache Temporário" + +#: radio-station.php:203 +msgid "Use Transient Caching to improve Schedule calculation performance." +msgstr "" +"Use o Cache Temporário para melhorar a performance do cálculo de Programação." + +#: radio-station.php:213 +msgid "Master Schedule Page" +msgstr "Página de Programação Mestre" + +#: radio-station.php:215 +msgid "Select the Page you are displaying the Master Schedule on." +msgstr "Selecione a página na qual você está exibindo a Programação Mestre." + +#: radio-station.php:222 radio-station.php:345 radio-station.php:376 +#: radio-station.php:407 +msgid "Automatic Display" +msgstr "Display Automático" + +#: radio-station.php:225 +msgid "" +"Replaces selected page content with Master Schedule. Alternatively customize " +"with the shortcode: " +msgstr "" +"Repõe o conteúdo selecionado da página com a Programação Mestre. " +"Alternativamente customize com o shortcode:" + +#: radio-station.php:232 +msgid "Schedule View Default" +msgstr "Visão Padrão da Programação" + +#: radio-station.php:235 +msgid "Table View" +msgstr "Visão em Tabela" + +#: radio-station.php:236 +msgid "List View" +msgstr "Visão em Lista" + +#: radio-station.php:237 +msgid "Divs View" +msgstr "Visão em Divs" + +#: radio-station.php:238 +msgid "Tabbed View" +msgstr "Visão em Guias" + +#: radio-station.php:239 +msgid "Legacy Table" +msgstr "Tabela Legado" + +#: radio-station.php:241 +msgid "View type to use for automatic display on Master Schedule Page." +msgstr "" +"Tipo de visualização a ser utilizado para display automático na Página da " +"Programação Mestre." + +#: radio-station.php:249 +msgid "View Switching" +msgstr "Troca de Visão" + +#: radio-station.php:252 +msgid "Enable View Switching on the Master Schedule." +msgstr "Permitir Troca de Visualização na Programação Mestre." + +#: radio-station.php:261 +msgid "Info Blocks Position" +msgstr "Posição de Blocos de Informação" + +#: radio-station.php:263 +msgid "Float Left" +msgstr "Oscilar à Esquerda" + +#: radio-station.php:264 +msgid "Float Right" +msgstr "Oscilar à Direita" + +#: radio-station.php:265 +msgid "Float Top" +msgstr "Oscilar ao Topo" + +#: radio-station.php:268 +msgid "Where to position Show info blocks relative to Show Page content." +msgstr "" +"Onde posicionar blocos de Informação do Show relativo ao conteúdo da Página " +"do Show." + +#: radio-station.php:275 +msgid "Content Header Image" +msgstr "Imagem de Cabeçalho do Conteúdo" + +#: radio-station.php:278 +msgid "" +"If your template does not display the Featured Image, enable this and use " +"the Content Header Image box on the Show edit screen instead." +msgstr "" +"Se o seu modelo não exibir a imagem em destaque, habilite isso e use a caixa " +"de Imagem de Cabeçalho do Conteúdo na tela de edição do Show." + +#: radio-station.php:285 templates/single-show-content.php:617 +msgid "Latest Show Posts" +msgstr "Posts do Último Show" + +#: radio-station.php:290 +msgid "Number of Latest Blog Posts to Show above Show Page tabs." +msgstr "Número de Blog Posts Recentes acima de abas da Página de Show." + +#: radio-station.php:297 +msgid "Posts per Page" +msgstr "Posts por Página" + +#: radio-station.php:302 +msgid "Blog Posts per page on the Show Page tab." +msgstr "Posts do Blog por página na guia Página do Show." + +#: radio-station.php:312 +msgid "Playlists per Page" +msgstr "Playlists por Página" + +#: radio-station.php:314 +msgid "Playlists per page on the Show Page tab." +msgstr "Playlists por página na guia Página do Show." + +#: radio-station.php:335 +msgid "Show Archives Page" +msgstr "Mostrar Página de Arquivos" + +#: radio-station.php:339 +msgid "Select the Page for displaying the Show archive list." +msgstr "Selecione a Página para display da lista de arquivos do Show." + +#: radio-station.php:349 +msgid "" +"Replaces selected page content with default Show Archive. Alternatively " +"customize display using the shortcode:" +msgstr "" +"Substitui o conteúdo da página selecionada pelo Arquivo Show Padrão. " +"Alternativamente personalizar display usando o shortcode:" + +#: radio-station.php:366 +msgid "Playlist Archives Page" +msgstr "Página de Arquivos de Playlist" + +#: radio-station.php:370 +msgid "Select the Page for displaying the Playlist archive list." +msgstr "Selecione a Página para exibição da lista de arquivos de Playlist" + +#: radio-station.php:380 +msgid "" +"Replaces selected page content with default Playlist Archive. Alternatively " +"customize display using the shortcode:" +msgstr "" +"Substitui o conteúdo da página selecionada pelo Arquivo de Playlist Padrão. " +"Alternativamente personalizar display usando o shortcode:" + +#: radio-station.php:397 +msgid "Genre Archives Page" +msgstr "Página de Arquivos de Gênero" + +#: radio-station.php:401 +msgid "Select the Page for displaying the Genre archive list." +msgstr "Seleciona a Página para exibição da lista de arquivos de Gênero." + +#: radio-station.php:411 +msgid "" +"Replaces selected page content with default Genre Archive. Alternatively " +"customize display using the shortcode:" +msgstr "" +"Substitui o conteúdo da página selecionada pelo Arquivo de Gênero Padrão. " +"Alternativamente personalizar display usando o shortcode:" + +#: radio-station.php:430 +msgid "Templates Change Note" +msgstr "Nota de Troca de Templates" + +#: radio-station.php:431 +msgid "Since 2.3.0, the way that Templates are implemented has changed." +msgstr "Des de 2.3.0, a maneira que as Templates são implementados mudou." + +#: radio-station.php:432 +msgid "See the Documentation for more information:" +msgstr "Veja a documentação para mais informações:" + +#: radio-station.php:433 +msgid "Templates Documentation" +msgstr "Documentação de Templates" + +#: radio-station.php:438 +msgid "Show Template" +msgstr "Modelo do Show" + +#: radio-station.php:441 radio-station.php:464 +msgid "Theme Page Template (page.php)" +msgstr "Modelo de Página Tema (page.php)" + +#: radio-station.php:442 radio-station.php:465 +msgid "Theme Post Template (single.php)" +msgstr "Modelo de Postagem Tema (single.php)" + +#: radio-station.php:443 +msgid "Theme Singular Template (singular.php)" +msgstr "Modelo de tema singular (singular.php)" + +#: radio-station.php:444 radio-station.php:466 +msgid "Legacy Plugin Template" +msgstr "Modelo de Plugin Legado" + +#: radio-station.php:447 +msgid "Which template to use for displaying Show content." +msgstr "Qual template utilizar para exibir conteúdo do Show." + +#: radio-station.php:452 radio-station.php:474 +msgid "Combined Method" +msgstr "Método Combinado" + +#: radio-station.php:456 +msgid "" +"Advanced usage. Use both a custom template AND content filtering for a Show. " +"(Not compatible with Legacy templates.)" +msgstr "" +"Uso avançado. Use ambos a template customizada E filtro de conteúdo para um " +"Show. ( Não compatível com templates Legado.)" + +#: radio-station.php:461 +msgid "Playlist Template" +msgstr "Modelo de Playlist" + +#: radio-station.php:469 +msgid "Which template to use for displaying Playlist content." +msgstr "Qual template usar para exibir conteúdo da Playlist." + +#: radio-station.php:478 +msgid "" +"Advanced usage. Use both a custom template AND content filtering for a " +"Playlist. (Not compatible with Legacy templates.)" +msgstr "" +"Uso avançado. Use ambos a template customizada E filtro de conteúdo para uma " +"Playlist. ( Não compatível com templates Legado.)" + +#: radio-station.php:488 +msgid "Show Editor Role" +msgstr "Mostrar Papel de Editor" + +#: radio-station.php:489 +msgid "" +"Since 2.3.0, a new Show Editor role has been added with Publish and Edit " +"capabilities for all Radio Station Post Types." +msgstr "" +"Desde 2.3.0, um novo papel de Editor show foi adicionado com capacidades de " +"publicação e edição para todos os tipos de post da Estação de Rádio." + +#: radio-station.php:490 +msgid "" +"You can assign this Role to any user to give them full Station Schedule " +"updating permissions." +msgstr "" +"Você pode designar este Papel à qualquer usuário para dá-los permissões " +"completas para updates da Programação da Estação." + +#: radio-station.php:507 +msgid "Add to Author Capabilities" +msgstr "Adicionar as Capacidades de Autor" + +#: radio-station.php:510 +msgid "" +"Allow users with WordPress Author role to publish and edit their own Shows " +"and Playlists." +msgstr "" +"Permitir que usuários com papel de Autores do WordPress publiquem e editem " +"seus próprios Shows e Playlists." + +#: radio-station.php:516 +msgid "Add to Editor Capabilities" +msgstr "Adicionar as Capacidades de Editor" + +#: radio-station.php:519 +msgid "" +"Allow users with WordPress Editor role to edit all Radio Station post types." +msgstr "" +"Permita que os usuários com papel do editor do WordPress editem todos os " +"tipos de publicação da Estação de Rádio." + +#: radio-station.php:543 +msgid "Pages" +msgstr "Páginas" + +#: radio-station.php:544 +msgid "Templates" +msgstr "Modelos" + +#: radio-station.php:545 +msgid "Roles" +msgstr "Papéis" + +#: radio-station.php:548 +msgid "Station" +msgstr "Estação" + +#: radio-station.php:549 +msgid "Broadcast" +msgstr "Transmissão" + +#: radio-station.php:550 +msgid "Times" +msgstr "Horários" + +#: radio-station.php:551 +msgid "Feeds" +msgstr "Feeds" + +#: radio-station.php:552 +msgid "Single Templates" +msgstr "Modelos Individuais" + +#: radio-station.php:553 +msgid "Archive Templates" +msgstr "Modelos de Arquivo" + +#: radio-station.php:554 +msgid "Schedule Page" +msgstr "Página de Programação" + +#: radio-station.php:555 +msgid "Show Pages" +msgstr "Páginas de Show" + +#: radio-station.php:556 +msgid "Archives" +msgstr "Arquivos" + +#: radio-station.php:557 +msgid "Permissions" +msgstr "Permissões" + +#: radio-station.php:577 +msgid "Rate on WordPress.org" +msgstr "Avalie em WordPress.Org" + +#: radio-station.php:1406 +msgid "DJ / Host" +msgstr "DJ / Host" + +#: radio-station.php:1418 +msgid "Show Producer" +msgstr "Produtor do Show" + +#: radio-station.php:1491 +msgid "Show Editor" +msgstr "Editor do Show" + +#: templates/admin-export.php:14 +msgid "Start Date" +msgstr "Data de Início" + +#: templates/admin-export.php:84 +msgid "End Date" +msgstr "Data de Término" + +#: templates/admin-export.php:156 +msgid "Export" +msgstr "Exportar" + +#: templates/legacy/archive-playlist.php:19 +msgid "Playlist Archive for" +msgstr "Arquivo da Playlist para" + +#: templates/legacy/archive-playlist.php:57 +msgid "Post navigation" +msgstr "Navegação nos Posts" + +#: templates/legacy/archive-playlist.php:58 +msgid "Older posts" +msgstr "Posts Antigos" + +#: templates/legacy/archive-playlist.php:59 +msgid "Newer posts" +msgstr "Posts Novos" + +#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:100 +#: templates/legacy/playlist-archive-template.php:66 +#: templates/legacy/show-blog-archive-template.php:76 +msgid "Nothing Found" +msgstr "Nada Encontrado" + +#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:104 +#: templates/legacy/playlist-archive-template.php:70 +#: templates/legacy/show-blog-archive-template.php:80 +msgid "" +"Apologies, but no results were found for the requested archive. Perhaps " +"searching will help find a related post." +msgstr "" +"Desculpas, mas nenhum resultado foi encontrado para o arquivo solicitado. " +"Talvez pesquisar irá ajudar a achar um post relacionado." + +#: templates/legacy/author.php:54 +msgid "Author Archives: %s" +msgstr "Arquivos do Author: %s" + +#: templates/legacy/author.php:74 +msgid "About %s" +msgstr "Sobre %s" + +#: templates/legacy/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "Arquivo da Playlist" + +#: templates/legacy/show-blog-archive-template.php:20 +msgid "Blog Archive" +msgstr "Arquivo do Blog" + +#: templates/legacy/show-blog-archive-template.php:53 +msgid "Posted by" +msgstr "Postado por" + +#: templates/legacy/single-playlist.php:30 templates/legacy/single-show.php:35 +msgid "Pages:" +msgstr "Páginas:" + +#: templates/legacy/single-playlist.php:49 +msgid "DJ Comments" +msgstr "Comentários do DJ" + +#: templates/legacy/single-playlist.php:71 +msgid "No entries for this playlist" +msgstr "Nenhuma entrada para essa playlist" + +#: templates/master-schedule-div.php:143 +#: templates/master-schedule-legacy.php:219 +#: templates/master-schedule-list.php:131 +#: templates/master-schedule-table.php:242 +#: templates/master-schedule-tabs.php:146 +msgid "encore airing" +msgstr "transmissão repetida" + +#: templates/master-schedule-div.php:151 +#: templates/master-schedule-legacy.php:232 +#: templates/master-schedule-list.php:144 +#: templates/master-schedule-table.php:254 +#: templates/master-schedule-tabs.php:159 +msgid "Audio File" +msgstr "Arquivo de Ãudio" + +#: templates/master-schedule-list.php:117 +#: templates/master-schedule-tabs.php:133 +msgid "to" +msgstr "para" + +#: templates/master-schedule-tabs.php:187 +msgid "No Shows found for this day." +msgstr "Nenhum Show encontrado para esse dia." + +#: templates/single-playlist-content.php:38 +msgid "No entries for this Playlist" +msgstr "Nenhuma entrada para essa Playlist" + +#: templates/single-show-content.php:63 +msgid "Show Website" +msgstr "Mostrar o Website" + +#: templates/single-show-content.php:75 +msgid "Email Show Host" +msgstr "Enviar Email ao Host do Show" + +#: templates/single-show-content.php:88 +msgid "Show RSS Feed" +msgstr "Feed RSS do Show" + +#: templates/single-show-content.php:191 +msgid "Download Latest Broadcast" +msgstr "Fazer Download da Última Transmissão" + +#: templates/single-show-content.php:209 +msgid "Show Info" +msgstr "Informações do Show" + +#: templates/single-show-content.php:214 +msgid "Hosted by" +msgstr "Hospedado por" + +#: templates/single-show-content.php:241 +msgid "Produced by" +msgstr "Produzido por" + +#: templates/single-show-content.php:325 +msgid "Show Times" +msgstr "Horários de Show" + +#: templates/single-show-content.php:330 +msgid "Not Currently Scheduled." +msgstr "Não Agendado no Momento." + +#: templates/single-show-content.php:360 +msgid "Timezone" +msgstr "Fuso Horário" + +#: templates/single-show-content.php:362 templates/single-show-content.php:366 +msgid "UTC" +msgstr "UTC" + +#: templates/single-show-content.php:444 +msgid "Show More" +msgstr "Mostrar Mais" + +#: templates/single-show-content.php:445 +msgid "Show Less" +msgstr "Mostrar Menos" + +#: templates/single-show-content.php:459 templates/single-show-content.php:716 +msgid "About the Show" +msgstr "Sobre o Show" + +#: templates/single-show-content.php:460 +msgid "About" +msgstr "Sobre" + +#: templates/single-show-content.php:478 +msgid "Show Episodes" +msgstr "Mostrar Episódios" + +#: templates/single-show-content.php:479 +msgid "Episodes" +msgstr "Episódios" + +#: templates/single-show-content.php:499 +msgid "Show Posts" +msgstr "Posts do Show" + +#: templates/single-show-content.php:500 +msgid "Posts" +msgstr "Posts" + +#: templates/single-show-content.php:519 +msgid "Show Playlists" +msgstr "Playlists do Show" + +#: templates/single-show-content.php:681 +msgid "Jump to" +msgstr "Pular para" + +#. Plugin URI of the plugin/theme +#. Author URI of the plugin/theme +msgid "https://netmix.com/radio-station" +msgstr "https://netmix.com/radio-station" + +#. Description of the plugin/theme +msgid "" +"Adds Show pages, DJ role, playlist and on-air programming functionality to " +"your site." +msgstr "" +"Adicionar páginas de Show, papel de DJ, playlist e funcionalidades de " +"programação no-ar ao seu site." + +#. Author of the plugin/theme +msgid "Tony Zeoli " +msgstr "Tony Zeoli " From d8b530d358689f29fe63a1e824b643276706901e Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 1 Jan 2020 20:14:21 -0500 Subject: [PATCH 077/377] Update .DS_Store --- .DS_Store | Bin 12292 -> 12292 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index 5d3d1d91cec02d78755e5cb1aef3e1a6f0e2427b..6a3014a8b0b6a3a1763b068443a9199686b1ae68 100644 GIT binary patch delta 18 ZcmZokXi3Cfy43uH&g~1 From b5ac60e00d28c958096892be8a56fe273ae37389 Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 15 Jan 2020 15:03:30 +1100 Subject: [PATCH 078/377] Merge development fork into develop branch. --- .DS_Store | Bin 12292 -> 0 bytes .github/ISSUE_TEMPLATE/developer_task.md | 2 +- .gitignore | 8 + CHANGELOG.md | 2 +- css/program-schedule.css | 195 - css/{mailchimp.css => rs-mailchimp.css} | 0 css/rs-schedule.css | 292 + css/rs-shortcodes.css | 64 + css/rs-templates.css | 308 + css/{widgets.css => rs-widgets.css} | 7 +- freemius/LICENSE.txt | 674 + freemius/README.md | 253 + freemius/assets/css/admin/account.css | 1 + freemius/assets/css/admin/add-ons.css | 2 + freemius/assets/css/admin/affiliation.css | 1 + freemius/assets/css/admin/checkout.css | 1 + freemius/assets/css/admin/common.css | 2 + freemius/assets/css/admin/connect.css | 1 + freemius/assets/css/admin/debug.css | 1 + freemius/assets/css/admin/dialog-boxes.css | 2 + .../assets/css/admin/gdpr-optin-notice.css | 1 + freemius/assets/css/admin/index.php | 3 + freemius/assets/css/customizer.css | 1 + freemius/assets/css/index.php | 3 + freemius/assets/img/index.php | 3 + freemius/assets/img/plugin-icon.png | Bin 0 -> 9380 bytes freemius/assets/img/theme-icon.png | Bin 0 -> 11237 bytes freemius/assets/index.php | 3 + freemius/assets/js/index.php | 3 + freemius/assets/js/nojquery.ba-postmessage.js | 140 + .../assets/js/nojquery.ba-postmessage.min.js | 12 + freemius/assets/js/postmessage.js | 135 + freemius/config.php | 388 + freemius/includes/class-freemius-abstract.php | 597 + freemius/includes/class-freemius.php | 23283 ++++++++++++++++ freemius/includes/class-fs-admin-notices.php | 321 + freemius/includes/class-fs-api.php | 664 + freemius/includes/class-fs-logger.php | 691 + freemius/includes/class-fs-options.php | 431 + freemius/includes/class-fs-plugin-updater.php | 1488 + freemius/includes/class-fs-security.php | 85 + freemius/includes/class-fs-storage.php | 526 + freemius/includes/class-fs-user-lock.php | 126 + .../class-fs-customizer-support-section.php | 102 + .../class-fs-customizer-upsell-control.php | 157 + freemius/includes/customizer/index.php | 3 + .../debug/class-fs-debug-bar-panel.php | 64 + freemius/includes/debug/debug-bar-start.php | 52 + freemius/includes/debug/index.php | 3 + .../entities/class-fs-affiliate-terms.php | 128 + .../includes/entities/class-fs-affiliate.php | 84 + .../includes/entities/class-fs-billing.php | 95 + .../includes/entities/class-fs-entity.php | 149 + .../includes/entities/class-fs-payment.php | 168 + .../entities/class-fs-plugin-info.php | 34 + .../entities/class-fs-plugin-license.php | 290 + .../entities/class-fs-plugin-plan.php | 145 + .../includes/entities/class-fs-plugin-tag.php | 60 + .../includes/entities/class-fs-plugin.php | 154 + .../includes/entities/class-fs-pricing.php | 141 + .../entities/class-fs-scope-entity.php | 29 + freemius/includes/entities/class-fs-site.php | 233 + .../entities/class-fs-subscription.php | 125 + freemius/includes/entities/class-fs-user.php | 79 + freemius/includes/entities/index.php | 3 + freemius/includes/fs-core-functions.php | 1351 + freemius/includes/fs-essential-functions.php | 479 + freemius/includes/fs-plugin-info-dialog.php | 1610 ++ freemius/includes/i18n.php | 603 + freemius/includes/index.php | 3 + freemius/includes/l10n.php | 48 + .../managers/class-fs-admin-menu-manager.php | 980 + .../class-fs-admin-notice-manager.php | 472 + .../managers/class-fs-cache-manager.php | 326 + .../managers/class-fs-gdpr-manager.php | 202 + .../managers/class-fs-key-value-storage.php | 392 + .../managers/class-fs-license-manager.php | 104 + .../managers/class-fs-option-manager.php | 497 + .../managers/class-fs-plan-manager.php | 162 + .../managers/class-fs-plugin-manager.php | 220 + freemius/includes/managers/index.php | 3 + .../Exceptions/ArgumentNotExistException.php | 9 + .../sdk/Exceptions/EmptyArgumentException.php | 9 + .../includes/sdk/Exceptions/Exception.php | 74 + .../Exceptions/InvalidArgumentException.php | 8 + .../sdk/Exceptions/OAuthException.php | 12 + freemius/includes/sdk/Exceptions/index.php | 3 + freemius/includes/sdk/FreemiusBase.php | 215 + freemius/includes/sdk/FreemiusWordPress.php | 704 + freemius/includes/sdk/LICENSE.txt | 340 + freemius/includes/sdk/index.php | 3 + .../fs-essential-functions-1.1.7.1.php | 43 + .../fs-essential-functions-2.2.1.php | 45 + freemius/includes/supplements/index.php | 3 + freemius/index.php | 3 + freemius/languages/freemius-da_DK.mo | Bin 0 -> 26080 bytes freemius/languages/freemius-da_DK.po | 2722 ++ freemius/languages/freemius-en.mo | Bin 0 -> 53012 bytes freemius/languages/freemius-en.po | 2386 ++ freemius/languages/freemius-es_ES.mo | Bin 0 -> 56182 bytes freemius/languages/freemius-es_ES.po | 2508 ++ freemius/languages/freemius-fr_FR.mo | Bin 0 -> 57448 bytes freemius/languages/freemius-fr_FR.po | 2508 ++ freemius/languages/freemius-he_IL.mo | Bin 0 -> 56093 bytes freemius/languages/freemius-he_IL.po | 2509 ++ freemius/languages/freemius-hu_HU.mo | Bin 0 -> 54168 bytes freemius/languages/freemius-hu_HU.po | 2508 ++ freemius/languages/freemius-it_IT.mo | Bin 0 -> 55345 bytes freemius/languages/freemius-it_IT.po | 2512 ++ freemius/languages/freemius-ja_JP.mo | Bin 0 -> 61357 bytes freemius/languages/freemius-ja_JP.po | 2511 ++ freemius/languages/freemius-nl_NL.mo | Bin 0 -> 55531 bytes freemius/languages/freemius-nl_NL.po | 2509 ++ freemius/languages/freemius-ru_RU.mo | Bin 0 -> 69773 bytes freemius/languages/freemius-ru_RU.po | 2508 ++ freemius/languages/freemius.pot | 2383 ++ freemius/languages/index.php | 3 + freemius/package.json | 27 + freemius/require.php | 49 + freemius/start.php | 522 + freemius/templates/account.php | 932 + freemius/templates/account/billing.php | 423 + freemius/templates/account/index.php | 3 + .../partials/activate-license-button.php | 54 + freemius/templates/account/partials/addon.php | 407 + .../partials/deactivate-license-button.php | 36 + freemius/templates/account/partials/index.php | 3 + freemius/templates/account/partials/site.php | 336 + freemius/templates/account/payments.php | 59 + freemius/templates/add-ons.php | 441 + freemius/templates/add-trial-to-pricing.php | 31 + freemius/templates/admin-notice.php | 76 + freemius/templates/ajax-loader.php | 1 + freemius/templates/auto-installation.php | 249 + freemius/templates/checkout.php | 337 + freemius/templates/connect.php | 971 + freemius/templates/contact.php | 128 + freemius/templates/debug.php | 726 + freemius/templates/debug/api-calls.php | 155 + freemius/templates/debug/index.php | 3 + freemius/templates/debug/logger.php | 66 + .../templates/debug/plugins-themes-sync.php | 76 + freemius/templates/debug/scheduled-crons.php | 136 + freemius/templates/email.php | 49 + freemius/templates/firewall-issues-js.php | 59 + freemius/templates/forms/affiliation.php | 486 + .../templates/forms/deactivation/contact.php | 23 + .../templates/forms/deactivation/form.php | 539 + .../templates/forms/deactivation/index.php | 3 + .../forms/deactivation/retry-skip.php | 24 + freemius/templates/forms/index.php | 3 + .../templates/forms/license-activation.php | 701 + freemius/templates/forms/optout.php | 267 + .../premium-versions-upgrade-handler.php | 205 + .../premium-versions-upgrade-metadata.php | 47 + freemius/templates/forms/resend-key.php | 247 + .../forms/subscription-cancellation.php | 277 + freemius/templates/forms/trial-start.php | 181 + freemius/templates/gdpr-optin-js.php | 66 + freemius/templates/index.php | 3 + freemius/templates/js/index.php | 3 + .../templates/js/jquery.content-change.php | 58 + .../templates/js/open-license-activation.php | 37 + freemius/templates/js/style-premium-theme.php | 53 + .../templates/partials/network-activation.php | 89 + freemius/templates/plugin-icon.php | 20 + .../templates/plugin-info/description.php | 78 + freemius/templates/plugin-info/features.php | 114 + freemius/templates/plugin-info/index.php | 3 + .../templates/plugin-info/screenshots.php | 34 + freemius/templates/powered-by.php | 58 + freemius/templates/pricing.php | 176 + freemius/templates/secure-https-header.php | 39 + freemius/templates/sticky-admin-notice-js.php | 39 + freemius/templates/tabs-capture-js.php | 63 + freemius/templates/tabs.php | 190 + includes/.DS_Store | Bin 6148 -> 0 bytes includes/class-current-playlist-widget.php | 172 + includes/class-current-show-widget.php | 263 + includes/class-dj-upcoming-widget.php | 454 - includes/class-dj-widget.php | 532 - includes/class-playlist-widget.php | 210 - includes/class-upcoming-shows-widget.php | 243 + includes/data-feeds.php | 1105 + includes/master-schedule.php | 418 +- includes/post-types-admin.php | 3324 ++- includes/post-types.php | 493 +- includes/shortcodes.php | 1909 +- includes/support-functions.php | 3152 ++- js/radio-station-admin.js | 5 + js/radio-station.js | 26 + languages/.DS_Store | Bin 8196 -> 0 bytes languages/languages.json | 1 + languages/radio-station-pt_BR.mo | Bin 34261 -> 0 bytes languages/radio-station-pt_BR.po | 1911 -- languages/radio-station.pot | 1789 +- loader.php | 2669 ++ phpcs.xml | 98 +- radio-station-admin.php | 885 +- radio-station.php | 1755 +- reader.php | 3557 +++ readme.md | 7 + readme.txt | 588 + templates/admin-export.php | 54 +- templates/{ => legacy}/archive-playlist.php | 4 +- templates/{ => legacy}/author.php | 1 + templates/legacy/legacy-note.txt | 6 + .../playlist-archive-template.php | 0 .../show-blog-archive-template.php | 0 templates/{ => legacy}/single-playlist.php | 2 +- templates/legacy/single-show.php | 50 + templates/master-schedule-default.php | 211 - templates/master-schedule-div.php | 100 +- templates/master-schedule-legacy.php | 248 + templates/master-schedule-list.php | 196 +- templates/master-schedule-table.php | 355 + templates/master-schedule-tabs.php | 273 +- templates/single-playlist-content.php | 45 + templates/single-show-content.php | 916 + templates/single-show.php | 165 - 220 files changed, 101571 insertions(+), 6610 deletions(-) delete mode 100644 .DS_Store create mode 100644 .gitignore delete mode 100644 css/program-schedule.css rename css/{mailchimp.css => rs-mailchimp.css} (100%) create mode 100644 css/rs-schedule.css create mode 100644 css/rs-shortcodes.css create mode 100644 css/rs-templates.css rename css/{widgets.css => rs-widgets.css} (87%) create mode 100644 freemius/LICENSE.txt create mode 100644 freemius/README.md create mode 100644 freemius/assets/css/admin/account.css create mode 100644 freemius/assets/css/admin/add-ons.css create mode 100644 freemius/assets/css/admin/affiliation.css create mode 100644 freemius/assets/css/admin/checkout.css create mode 100644 freemius/assets/css/admin/common.css create mode 100644 freemius/assets/css/admin/connect.css create mode 100644 freemius/assets/css/admin/debug.css create mode 100644 freemius/assets/css/admin/dialog-boxes.css create mode 100644 freemius/assets/css/admin/gdpr-optin-notice.css create mode 100644 freemius/assets/css/admin/index.php create mode 100644 freemius/assets/css/customizer.css create mode 100644 freemius/assets/css/index.php create mode 100644 freemius/assets/img/index.php create mode 100644 freemius/assets/img/plugin-icon.png create mode 100644 freemius/assets/img/theme-icon.png create mode 100644 freemius/assets/index.php create mode 100644 freemius/assets/js/index.php create mode 100644 freemius/assets/js/nojquery.ba-postmessage.js create mode 100644 freemius/assets/js/nojquery.ba-postmessage.min.js create mode 100644 freemius/assets/js/postmessage.js create mode 100644 freemius/config.php create mode 100644 freemius/includes/class-freemius-abstract.php create mode 100644 freemius/includes/class-freemius.php create mode 100644 freemius/includes/class-fs-admin-notices.php create mode 100644 freemius/includes/class-fs-api.php create mode 100644 freemius/includes/class-fs-logger.php create mode 100644 freemius/includes/class-fs-options.php create mode 100644 freemius/includes/class-fs-plugin-updater.php create mode 100644 freemius/includes/class-fs-security.php create mode 100644 freemius/includes/class-fs-storage.php create mode 100644 freemius/includes/class-fs-user-lock.php create mode 100644 freemius/includes/customizer/class-fs-customizer-support-section.php create mode 100644 freemius/includes/customizer/class-fs-customizer-upsell-control.php create mode 100644 freemius/includes/customizer/index.php create mode 100644 freemius/includes/debug/class-fs-debug-bar-panel.php create mode 100644 freemius/includes/debug/debug-bar-start.php create mode 100644 freemius/includes/debug/index.php create mode 100644 freemius/includes/entities/class-fs-affiliate-terms.php create mode 100644 freemius/includes/entities/class-fs-affiliate.php create mode 100644 freemius/includes/entities/class-fs-billing.php create mode 100644 freemius/includes/entities/class-fs-entity.php create mode 100644 freemius/includes/entities/class-fs-payment.php create mode 100644 freemius/includes/entities/class-fs-plugin-info.php create mode 100644 freemius/includes/entities/class-fs-plugin-license.php create mode 100644 freemius/includes/entities/class-fs-plugin-plan.php create mode 100644 freemius/includes/entities/class-fs-plugin-tag.php create mode 100644 freemius/includes/entities/class-fs-plugin.php create mode 100644 freemius/includes/entities/class-fs-pricing.php create mode 100644 freemius/includes/entities/class-fs-scope-entity.php create mode 100644 freemius/includes/entities/class-fs-site.php create mode 100644 freemius/includes/entities/class-fs-subscription.php create mode 100644 freemius/includes/entities/class-fs-user.php create mode 100644 freemius/includes/entities/index.php create mode 100644 freemius/includes/fs-core-functions.php create mode 100644 freemius/includes/fs-essential-functions.php create mode 100644 freemius/includes/fs-plugin-info-dialog.php create mode 100644 freemius/includes/i18n.php create mode 100644 freemius/includes/index.php create mode 100644 freemius/includes/l10n.php create mode 100644 freemius/includes/managers/class-fs-admin-menu-manager.php create mode 100644 freemius/includes/managers/class-fs-admin-notice-manager.php create mode 100644 freemius/includes/managers/class-fs-cache-manager.php create mode 100644 freemius/includes/managers/class-fs-gdpr-manager.php create mode 100644 freemius/includes/managers/class-fs-key-value-storage.php create mode 100644 freemius/includes/managers/class-fs-license-manager.php create mode 100644 freemius/includes/managers/class-fs-option-manager.php create mode 100644 freemius/includes/managers/class-fs-plan-manager.php create mode 100644 freemius/includes/managers/class-fs-plugin-manager.php create mode 100644 freemius/includes/managers/index.php create mode 100644 freemius/includes/sdk/Exceptions/ArgumentNotExistException.php create mode 100644 freemius/includes/sdk/Exceptions/EmptyArgumentException.php create mode 100644 freemius/includes/sdk/Exceptions/Exception.php create mode 100644 freemius/includes/sdk/Exceptions/InvalidArgumentException.php create mode 100644 freemius/includes/sdk/Exceptions/OAuthException.php create mode 100644 freemius/includes/sdk/Exceptions/index.php create mode 100644 freemius/includes/sdk/FreemiusBase.php create mode 100644 freemius/includes/sdk/FreemiusWordPress.php create mode 100644 freemius/includes/sdk/LICENSE.txt create mode 100644 freemius/includes/sdk/index.php create mode 100644 freemius/includes/supplements/fs-essential-functions-1.1.7.1.php create mode 100644 freemius/includes/supplements/fs-essential-functions-2.2.1.php create mode 100644 freemius/includes/supplements/index.php create mode 100644 freemius/index.php create mode 100644 freemius/languages/freemius-da_DK.mo create mode 100644 freemius/languages/freemius-da_DK.po create mode 100644 freemius/languages/freemius-en.mo create mode 100644 freemius/languages/freemius-en.po create mode 100644 freemius/languages/freemius-es_ES.mo create mode 100644 freemius/languages/freemius-es_ES.po create mode 100644 freemius/languages/freemius-fr_FR.mo create mode 100644 freemius/languages/freemius-fr_FR.po create mode 100644 freemius/languages/freemius-he_IL.mo create mode 100644 freemius/languages/freemius-he_IL.po create mode 100644 freemius/languages/freemius-hu_HU.mo create mode 100644 freemius/languages/freemius-hu_HU.po create mode 100644 freemius/languages/freemius-it_IT.mo create mode 100644 freemius/languages/freemius-it_IT.po create mode 100644 freemius/languages/freemius-ja_JP.mo create mode 100644 freemius/languages/freemius-ja_JP.po create mode 100644 freemius/languages/freemius-nl_NL.mo create mode 100644 freemius/languages/freemius-nl_NL.po create mode 100644 freemius/languages/freemius-ru_RU.mo create mode 100644 freemius/languages/freemius-ru_RU.po create mode 100644 freemius/languages/freemius.pot create mode 100644 freemius/languages/index.php create mode 100644 freemius/package.json create mode 100644 freemius/require.php create mode 100644 freemius/start.php create mode 100644 freemius/templates/account.php create mode 100644 freemius/templates/account/billing.php create mode 100644 freemius/templates/account/index.php create mode 100644 freemius/templates/account/partials/activate-license-button.php create mode 100644 freemius/templates/account/partials/addon.php create mode 100644 freemius/templates/account/partials/deactivate-license-button.php create mode 100644 freemius/templates/account/partials/index.php create mode 100644 freemius/templates/account/partials/site.php create mode 100644 freemius/templates/account/payments.php create mode 100644 freemius/templates/add-ons.php create mode 100644 freemius/templates/add-trial-to-pricing.php create mode 100644 freemius/templates/admin-notice.php create mode 100644 freemius/templates/ajax-loader.php create mode 100644 freemius/templates/auto-installation.php create mode 100644 freemius/templates/checkout.php create mode 100644 freemius/templates/connect.php create mode 100644 freemius/templates/contact.php create mode 100644 freemius/templates/debug.php create mode 100644 freemius/templates/debug/api-calls.php create mode 100644 freemius/templates/debug/index.php create mode 100644 freemius/templates/debug/logger.php create mode 100644 freemius/templates/debug/plugins-themes-sync.php create mode 100644 freemius/templates/debug/scheduled-crons.php create mode 100644 freemius/templates/email.php create mode 100644 freemius/templates/firewall-issues-js.php create mode 100644 freemius/templates/forms/affiliation.php create mode 100644 freemius/templates/forms/deactivation/contact.php create mode 100644 freemius/templates/forms/deactivation/form.php create mode 100644 freemius/templates/forms/deactivation/index.php create mode 100644 freemius/templates/forms/deactivation/retry-skip.php create mode 100644 freemius/templates/forms/index.php create mode 100644 freemius/templates/forms/license-activation.php create mode 100644 freemius/templates/forms/optout.php create mode 100644 freemius/templates/forms/premium-versions-upgrade-handler.php create mode 100644 freemius/templates/forms/premium-versions-upgrade-metadata.php create mode 100644 freemius/templates/forms/resend-key.php create mode 100644 freemius/templates/forms/subscription-cancellation.php create mode 100644 freemius/templates/forms/trial-start.php create mode 100644 freemius/templates/gdpr-optin-js.php create mode 100644 freemius/templates/index.php create mode 100644 freemius/templates/js/index.php create mode 100644 freemius/templates/js/jquery.content-change.php create mode 100644 freemius/templates/js/open-license-activation.php create mode 100644 freemius/templates/js/style-premium-theme.php create mode 100644 freemius/templates/partials/network-activation.php create mode 100644 freemius/templates/plugin-icon.php create mode 100644 freemius/templates/plugin-info/description.php create mode 100644 freemius/templates/plugin-info/features.php create mode 100644 freemius/templates/plugin-info/index.php create mode 100644 freemius/templates/plugin-info/screenshots.php create mode 100644 freemius/templates/powered-by.php create mode 100644 freemius/templates/pricing.php create mode 100644 freemius/templates/secure-https-header.php create mode 100644 freemius/templates/sticky-admin-notice-js.php create mode 100644 freemius/templates/tabs-capture-js.php create mode 100644 freemius/templates/tabs.php delete mode 100644 includes/.DS_Store create mode 100644 includes/class-current-playlist-widget.php create mode 100644 includes/class-current-show-widget.php delete mode 100644 includes/class-dj-upcoming-widget.php delete mode 100644 includes/class-dj-widget.php delete mode 100644 includes/class-playlist-widget.php create mode 100644 includes/class-upcoming-shows-widget.php create mode 100644 includes/data-feeds.php create mode 100644 js/radio-station-admin.js create mode 100644 js/radio-station.js delete mode 100644 languages/.DS_Store create mode 100644 languages/languages.json delete mode 100644 languages/radio-station-pt_BR.mo delete mode 100644 languages/radio-station-pt_BR.po create mode 100644 loader.php create mode 100644 reader.php create mode 100644 readme.txt rename templates/{ => legacy}/archive-playlist.php (90%) rename templates/{ => legacy}/author.php (96%) create mode 100644 templates/legacy/legacy-note.txt rename templates/{ => legacy}/playlist-archive-template.php (100%) rename templates/{ => legacy}/show-blog-archive-template.php (100%) rename templates/{ => legacy}/single-playlist.php (96%) create mode 100644 templates/legacy/single-show.php delete mode 100644 templates/master-schedule-default.php create mode 100644 templates/master-schedule-legacy.php create mode 100644 templates/master-schedule-table.php create mode 100644 templates/single-playlist-content.php create mode 100644 templates/single-show-content.php delete mode 100644 templates/single-show.php diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6a3014a8b0b6a3a1763b068443a9199686b1ae68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12292 zcmeHNdvH`&8UMcUy7zWNZZIUfOEy_ZLqZ67lLrvkWWyr^6(j)?poV>1VC_EAy_*Dx zsi4*;)>>(?^?|LmwS$V*(m$M5TAe9WtJOAB3Qjwtjyjb;bVe;xwNt-y&fVqi%_bo2 z2+E$Bd(S=R+;i@^-~P_|{l4!m0AS}(upXci03=QeVWkslN}?@E1m zSIR|KG+Yc6-KpNqZl~O17$JuW*fXP9$C9^GVlOR&KK8I@hl2F0`%h3k71)p3nhpK+V(1 zJMO09rnojtp76FnEIbs6>H}(iz!we-g?$OFBOGQoQ(c}Oh{wX=O)))`2*slPeF<$} zA~{f6Q{Q$y7EgHjU<0Z;q-&cs8Y?ucb_wAAgJ@m3idtt9@I|ZFMXeyqQzCat-2MvA!yE7>=HM*M zCA>NbtCd)TO}G-*;5uB78}Js~ihZ~pci>J!>K+W?hd5e?Ia&|l$M8=41Y!1Wya$iq zy?7rU!w2yj_%MDGze~t{oFn&XJcZBTv-o5D8II#?7Syh{p!W65s2%Q!N5iq`9%^NH zmthe0(h&e1YG5BkAlw~}1&kOb6GqGFc;p?P=n9|+;uQ4^LWtJy66%$pQi7MFRAwy^ zDn)5DKC)%B$1J1ePcd4?5%8}xLZxXwGL4`a&(-Lu5eTL~GsicfM}m4pXnrhy%dT}x zj~6fB!xZT~om~CbD$-jxI!eLahNe~)=xuFmsCG9uv`q-~9;KI}G-;rh1%hR6jx^Jx z)A|Im{J(eh-w5#PDk zh9r*{7TI$hPDgQaqq269h`v))&Yw|I>QK`5O1^cmrMr!7qrvUxBmmDslK5@Mn07IQ(DmHqOL6 zEXESf;EQl6He(BVumio=h26NBID8B3jyo{OcgDRK<$K}*9Hm|HFn$j2!lU?gJVETu zcEP9cIXsPL@OgZRaX3dbOv>EnGeep%n`G|(Oqg}8UZZ%glC|F(%$~Cepb{sSlvOTi zXj$QzjFHV)nv#Um;K(3NEUUP#jFBCL5|`VxfDy7Pjq_r;t3o1Ft2unEMO7}vAYnFi zO|9ZGNSGBHn-s+$VODHyqgFw}Y$#=(vPza!;NvRgF0U-9NQ|5YLL%YJ5PS?ChiBpE z@Df4qHTXUJ2`-X!oQXD)jU^--%kX9_Cqk~pdTbzCZYN?E0NjA@=Q46L!S7Zaz+LDg zVh$4oV>pQW34&uJB|kz^^3x?O z!34XQRp^99lLn0^@nzPa_BHuym5f21|189miQ7#wVL}{9MftdGuCr7yc*j+U&toJ$ znGztBdN#E0+ zkx7NQEfXSLs|(g#BSfZ2%L?WetxElZmLD!nF`D5trOtf!(vl<~$-$E;omnL%Rc7^) zG?iK3kft)5ThdhK@)hYSvqSMx2=*8W%}d(y3V_F4oF5YCUM79{Isxtif$c2<+YEAS ziqJvMjf-?<1qFSoa4|07P-8(#=HRU6fy&Jkresde0O`vBcXXnpFXO0Fz;cZAlzcSquDMS^%ea^>lU8t0h00Sl^O;Yj@MJiw>69eFxbECAwzKEa#u%DVL10 k@r{kQd5pS)B4;D8dft`t|I(iTvivW;L1y{?hUfo(15)0Dr~m)} diff --git a/.github/ISSUE_TEMPLATE/developer_task.md b/.github/ISSUE_TEMPLATE/developer_task.md index cf3411b..b4cafa0 100644 --- a/.github/ISSUE_TEMPLATE/developer_task.md +++ b/.github/ISSUE_TEMPLATE/developer_task.md @@ -17,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Additional context** -Add any other context or screenshots about the feature request here. +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..416a3c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +.idea/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb32d2..a33a7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -315,4 +315,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new field to associate blog posts with shows ## 1.0 -* Initial release +* Initial release \ No newline at end of file diff --git a/css/program-schedule.css b/css/program-schedule.css deleted file mode 100644 index 768e1f3..0000000 --- a/css/program-schedule.css +++ /dev/null @@ -1,195 +0,0 @@ -/* Table Style Grid */ -/* ---------------- */ -#master-program-schedule { - width: 100%; -} - -#master-program-schedule th { - width: 12%; - text-align: center; -} - -#master-program-schedule td { - vertical-align: top; - font-size: 12px; - text-align: center; - padding: 0px; -} - -#master-program-schedule td { - border: 1px solid black; -} - -#master-program-schedule td div { - border-top: 1px solid #dddddd; -} - -#master-program-schedule .master-program-hour div { - margin-top: -1em; - padding-bottom: 1em; -} - -#master-program-schedule span.show-title, -#master-program-schedule span.show-file, -#master-program-schedule span.show-time, -#master-program-schedule span.show-encore { - display: block; -} - -#master-program-schedule span.show-time { - font-size: 10px; -} - -#master-program-schedule span.show-encore { - font-size: 10px; - color: #EE8E66; -} - -#master-program-schedule span.show-file { - margin-bottom: 5px; - margin-top: 5px; -} - -#master-program-schedule span.show-file a { - width: 95%; - height: 20px; - background-color: #931B25; - padding: 3px; - text-decoration: none; - color: #ffffff; - margin-bottom: 3px; -} - -#master-program-schedule span.show-file a:hover { - background-color: #C51D2E; - color: #ffffff; -} - -#master-genre-list { - font-size: 10px; -} - -#master-genre-list span.heading { - font-weight: bold; -} - -/* Div Style Grid */ -/* -------------- */ -#master-schedule-divs { - width: 100%; -} - -#master-schedule-divs .master-schedule-hour { - width: 100%; - clear: both; -} - -#master-schedule-divs .master-schedule-hour-header { - width: 12%; - float: left; - text-align: center; - font-weight: bold; - font-size: 0.7em; -} - -#master-schedule-divs .master-schedule-weekday { - width: 10%; - float: left; - border: 1px solid #dddddd; -} - -#master-schedule-divs .master-schedule-weekday-header { - text-align: center; - font-weight: bold; - font-size: 0.7em; - display: block; -} - -#master-schedule-divs .master-show-entry { - padding: 8px 5px 5px 5px; - position: relative; -} - -#master-schedule-divs .show-dj-names, -#master-schedule-divs .show-time, -#master-schedule-divs .show-title { - display: block; - font-size: 0.6em; - line-height: 1em; -} - -#master-schedule-divs .show-image img { - width: 100%; - height: auto; -} - -#master-schedule-divs .rowspan { - background-color: #dddddd; - margin-left: -1px; - position: absolute; - width: 50px; - z-index: 5; -} - -/* Tabbed Styled List */ -/* ------------------ */ -#master-schedule-tabs { - border: none; - padding: 0; - margin: 0; -} -.master-schedule-tabs-day { - float: left; - display: block; - width: 100px; - margin: 0px; - cursor: pointer; - text-align: center; - background-color: #eeeeee; - color: #444444; - border-radius: 7px 7px 0 0; - border: 1px solid #000000; - } -.master-schedule-tabs-panel { - display: none; - float: left; - margin: 0; - position: relative; - text-align: left; - width: 702px; - list-style: none; -} -.master-schedule-tabs-day-name { - padding: 5px 0 5px 0; -} -.master-schedule-tabs-show { - padding: 20px 10px 10px 10px; - clear: both; - list-style: none; -} -.master-schedule-tabs-day.active-day-tab { - font-weight: bold; - background-color: #ffffff; - color: #000000; - border-bottom: 0; -} -.master-schedule-tabs-panel.active-day-panel { - display: block; - border: 1px solid #333333; - background-color: #ffffff; - border-top: 0; - border-radius: 0 0 7px 7px; -} -.master-schedule-tabs-show .show-info, .master-schedule-tabs-show .show-genres, -.master-schedule-tabs-show .show-image { - display: inline-block; - vertical-align: top; -} -.master-schedule-tabs-show .show-image { - margin-right: 30px; - min-width: 200px; -} -.master-schedule-tabs-show .show-genres { - float: right; - margin-right: 10px; -} \ No newline at end of file diff --git a/css/mailchimp.css b/css/rs-mailchimp.css similarity index 100% rename from css/mailchimp.css rename to css/rs-mailchimp.css diff --git a/css/rs-schedule.css b/css/rs-schedule.css new file mode 100644 index 0000000..99522d6 --- /dev/null +++ b/css/rs-schedule.css @@ -0,0 +1,292 @@ +/* ----------------------------- */ +/* Radio Station Schedule Styles */ +/* ----------------------------- */ + +/* Schedule Controls */ +/* ----------------- */ +#master-schedule-controls-wrapper { + width: 100%; +} + +#master-schedule-clock-wrapper, #master-schedule-selector-wrapper { + font-size: 13px; + vertical-align: top; + text-align: left; +} + +#master-schedule-clock-wrapper .radio-clock-title, #master-schedule-clock-wrapper .radio-timezone-title { + font-weight: bold; +} + +/* Table Style Grid */ +/* ---------------- */ +#master-genre-list { + font-size: 12px; + float: left; +} + +#master-genre-list .genre-highlight.highlighted { + font-weight: bold; + background-color: rgba( 235, 235, 0, 0.4 ); + border: 1px dashed orange; + padding-left: 4px; + padding-right: 2px; +} + +#master-program-schedule { + width: 100%; + height: 100%; +} + +#master-program-schedule tr { + height: 100%; +} + +#master-program-schedule th { + width: 12%; + text-align: center; + vertical-align: top; +} + +#master-program-schedule .master-program-hour-row th { + padding-top: 0; +} + +#master-program-schedule td { + vertical-align: top; + font-size: 12px; + text-align: center; + padding: 0px; + height: 100%; +} + +#master-program-schedule td { + border: 1px solid #222222; +} + +#master-program-schedule td.show-info { + padding: 0; +} + +#master-program-schedule td div { + border-top: 1px solid #eeeeee; +} + +#master-program-schedule .master-program-hour div { + padding-bottom: 1em; +} + +#master-program-schedule .show-wrap { + position: relative; + display: -ms-grid; + display: grid; + min-height: 50px; + height: 100%; +} + +#master-program-schedule .show-info.overflow { + border-bottom: transparent !important; + position: relative; +} + +#master-program-schedule .show-info.continued { + border-top: transparent !important; +} + +#master-program-schedule .show-info .master-show-entry { + position: relative; + display: block; + height: 100%; +} + +#master-program-schedule .show-info .master-show-entry.finished { + height: 99%; +} + +#master-program-schedule .show-info .master-show-entry.nowplaying { + border: 3px solid #F00000; +} + +#master-program-schedule .show-info .master-show-entry.overflow, +#master-program-schedule .show-info .master-show-entry.nowplaying.overflow { + border-bottom: 1px solid transparent; +} + +#master-program-schedule .show-info .master-show-entry.continued, +#master-program-schedule .show-info .master-show-entry.nowplaying.continued { + border-top: 1px solid transparent; +} + +#master-program-schedule .show-info .master-show-entry.continued.overflow, +#master-program-schedule .show-info .master-show-entry.nowplaying.continued.overflow { + border-bottom: 1px solid transparent; +} + +#master-program-schedule .master-show-entry.highlighted { + border: 1px dashed orange; + background-color: rgba(235,235,0,0.4); +} + +#master-program-schedule span.show-title, +#master-program-schedule span.show-file, +#master-program-schedule span.show-time, +#master-program-schedule span.show-encore { + display: block; +} + +#master-program-schedule span.show-time { + font-size: 10px; +} + +#master-program-schedule span.show-encore { + font-size: 10px; + color: #EE8E66; +} + +#master-program-schedule span.show-file { + margin-bottom: 5px; + margin-top: 5px; +} + +#master-program-schedule span.show-file a { + width: 95%; + height: 20px; + background-color: #931B25; + padding: 3px; + text-decoration: none; + color: #ffffff; + margin-bottom: 3px; +} + +#master-program-schedule span.show-file a:hover { + background-color: #C51D2E; + color: #ffffff; +} + +#master-genre-list span.heading { + font-weight: bold; +} + +/* Div Style Grid */ +/* -------------- */ +#master-schedule-divs { + width: 100%; +} + +#master-schedule-divs .master-schedule-hour { + width: 100%; + clear: both; +} + +#master-schedule-divs .master-schedule-hour-header { + width: 12%; + float: left; + text-align: center; + font-weight: bold; + font-size: 0.7em; +} + +#master-schedule-divs .master-schedule-weekday { + width: 10%; + float: left; + border: 1px solid #dddddd; +} + +#master-schedule-divs .master-schedule-weekday-header { + text-align: center; + font-weight: bold; + font-size: 0.7em; + display: block; +} + +#master-schedule-divs .master-show-entry { + padding: 8px 5px 5px 5px; + position: relative; +} + +#master-schedule-divs .show-dj-names, +#master-schedule-divs .show-time, +#master-schedule-divs .show-title { + display: block; + font-size: 0.6em; + line-height: 1em; +} + +#master-schedule-divs .show-image img { + width: 100%; + height: auto; +} + +#master-schedule-divs .rowspan { + background-color: #dddddd; + margin-left: -1px; + position: absolute; + width: 50px; + z-index: 5; +} + +/* Tabbed Styled List */ +/* ------------------ */ +#master-schedule-tabs { + border: none; + padding: 0; + margin: 0; +} +#master-schedule-tabs .master-schedule-tabs-day { + display: inline-block; + width: 98px; + margin: 0px; + cursor: pointer; + text-align: center; + background-color: #eeeeee; + color: #444444; + font-size: 16px; + font-weight: bold; + border-radius: 7px 7px 0 0; + border: 1px solid #000000; +} +#master-schedule-tabs .master-schedule-tabs-day-name { + padding: 5px 0 5px 0; +} +#master-schedule-tabs .master-schedule-tabs-day.active-day-tab { + font-weight: bold; + background-color: #ffffff; + color: #000000; + border-bottom: 0; +} +#master-schedule-tab-panels .master-schedule-tabs-panel { + display: none; + float: left; + margin: 0; + padding: 0; + position: relative; + text-align: left; + width: 698px; + list-style: none; +} +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show { + padding: 20px 10px 10px 10px; + clear: both; + list-style: none; +} +#master-schedule-tab-panels .master-schedule-tabs-panel.active-day-panel { + display: block; + border: 1px solid #333333; + background-color: #ffffff; + border-top: 0; + border-radius: 0 0 7px 7px; +} +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show .show-info, +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show .show-genres, +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show .show-image { + display: inline-block; + vertical-align: top; +} +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show .show-image { + margin-right: 30px; + min-width: 200px; +} +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show .show-genres { + float: right; + margin-right: 10px; +} diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css new file mode 100644 index 0000000..dcaec5a --- /dev/null +++ b/css/rs-shortcodes.css @@ -0,0 +1,64 @@ +/* ------------------------------ */ +/* Radio Station Shortcode Styles */ +/* ------------------------------ */ + +/* Archive Shortcodes */ +/* ------------------ */ +.show-archive-list-item, .playlist-archive-list-item, .override-archive-list-item { + list-style: none; +} + +/* Show Subarchive Shortcodes */ +/* -------------------------- */ +#show-posts, #show-playlists, #show-episodes { + overflow: hidden; +} + +.show-post { + padding-top: 5px; + padding-bottom: 5px; +} + +.show-post-thumbnail, .show-post-info, +.show-playlist-thumbnail, .show-playlist-info, +.show-episode-thumbnail, .show-episode-info { + display: table-cell; + vertical-align: top; +} + +.show-post-thumbnail, .show-playlist-thumbnail, .show-episode-thumbnail { + padding-right: 30px; + margin-top: 10px; +} + +.show-post-info, .show-playlist-info, .show-episode-info { + max-width: 600px; +} + +.show-post-title, .show-playlist-title, .show-episode-title { + margin-bottom: 10px; +} + +.show-pagination-button { + float: left; + display: inline-block; + width: 30px; + margin-right: 7px; + cursor: pointer; + text-align: center; + background-color: #eeeeee; + border-radius: 7px; + border: 1px solid #777777; + font-size: 16px; + color: #444444; + padding: 5px; +} + +.show-pagination-button.active { + background-color: transparent; + font-weight: bold; +} + +.show-pagination-button:hover { + background-color: #f5f5f5; +} diff --git a/css/rs-templates.css b/css/rs-templates.css new file mode 100644 index 0000000..43444a7 --- /dev/null +++ b/css/rs-templates.css @@ -0,0 +1,308 @@ +/* ============================= */ +/* Radio Station Template Styles */ +/* ============================= */ + +/* ---------------- */ +/* Show Page Styles */ +/* ---------------- */ + +#show-content .show-sections { + min-width: 250px; +} + +#show-content .show-sections h3, #show-content .show-sections h4, #show-content .show-sections h5 { + clear: none; +} + +/* Show Info */ +/* --------- */ +#show-content .show-info { + float: left; +} + +#show-content .show-info .show-block { + vertical-align: top; + line-height: 1.8em; + max-width: 250px; + margin-top: 20px; +} + +#show-content .show-info .show-block.first-block { + margin-top: 0px; +} + +#show-content .show-info .show-block.last-block { + margin-bottom: 20px; +} + +#show-content .show-info .show-avatar { + display: inline-block; + vertical-align: top; + margin-right: 10px; + text-align: center; +} + +#show-content .show-avatar img { + width: 200px; + height: 200px; +} + +#show-content .show-embed { + display: inline-block; + vertical-align: middle; + width: 100%; + min-width: 150px; + max-width: 200px; +} + +#show-content .show-download { + display: inline-block; + vertical-align: middle; + margin-left: 10px; + margin-top: 5px; +} + +#show-content .show-embed .mejs-container { + border-radius: 15px; + overflow: hidden; +} + +#show-content .show-embed .mejs-container, +#show-content .show-embed .mejs-controls, +#show-content .show-embed .mejs-embed, +#show-content .show-embed .mejs-embed body { + background-color: #AAAAAA; +} + +#show-content .show-embed .mejs-horizontal-volume-total { + width: 100%; +} + +#show-content .show-icons { + vertical-align: top; + margin-top: 10px; +} + +#show-content .show-icons .show-icon { + display: inline-block; + margin-right: 10px; + margin-bottom: 10px; +} + +#show-content .show-icons .show-icon span, +#show-content .show-download span { + font-size: 28px; +} + +#show-content .show-icon a { + text-decoration: none; +} + +/* Show Schedule */ +/* ------------- */ +#show-content .show-info .show-block h3, #show-content .show-info .show-block h4 { + margin-top: 0; +} + +#show-content .show-times .show-offset { + font-size: 16px; +} + +#show-content .show-times .show-encore { + color: #CC0000; +} + +#show-content .show-times .show-encore-label { + font-size: 14px; +} + +#show-content .show-times table { + padding: 0; + margin: 0; +} + +#show-content .show-times table td { + vertical-align: top; +} + +#show-content .show-times .show-schedule-link { + width: 100%; + text-align: right; +} + +#show-content .show-times .show-day-time, #show-content .show-times .show-time { + font-size: 16px; +} + +#show-content .show-latest-title { + margin-bottom: 10px; +} + +/* Show Description */ +/* ---------------- */ +#show-content .show-sections .show-jump-links { + font-size: 12px; +} + +#show-content .show-description { + position: relative; + max-height: 800px; + overflow: hidden; +} + +#show-content .show-description.expanded, +#show-content .show-description.narrow.expanded { + max-height: none; +} + +#show-content #show-desc-buttons { + width: 100%; + text-align: right; + padding-bottom: 20px; +} + +#show-content #show-desc-buttons #show-desc-less { + display: none; +} + +#show-content #show-more-overlay { + position: absolute; + bottom: 0; + width: 100%; + height: 6em; + background: -webkit-linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + background-image: -moz-linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + background-image: -o-linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + background-image: linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + background-image: -ms-linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); +} + +#show-content .show-description.expanded #show-more-overlay { + display: none; +} + +#show-content .show-description.expanded { + max-height: none; + overflow: visible; +} + +/* Show Tabs */ +/* --------- */ +#show-content .show-tabs .show-tab { + display: inline-block; + width: 100px; + line-height: 40px; + font-size: 18px; + color: #444444; + margin: 0px; + cursor: pointer; + text-align: center; + background-color: #eeeeee; + border-radius: 7px 7px 0 0; + border: 1px solid #000000; + padding: 6px 14px; +} + +#show-content .show-tabs .show-tab-spacer { + display: inline-block; + width: 50px; + line-height: 40px; + font-size: 18px; + border-bottom: 1px solid #000000; + border-top: 1px solid transparent; + padding: 6px 0; +} + +#show-content .show-tabs .show-tab.tab-active { + font-weight: bold; + background-color: transparent; + color: #000000; + border-bottom: 0; +} + +#show-content .show-tabs .show-tab.tab-inactive { + +} + +#show-content .show-tabs .show-tab.tab-inactive:hover { + background-color: #f5f5f5; +} + +#show-content .show-section .show-tab.tab-inactive { + display: none; +} + +/* Alternative Layouts */ +/* ------------------- */ +#show-content.left-blocks .show-info { + margin-right: 30px; +} + +#show-content.right-blocks .show-info { + float: right; + margin-left: 30px; +} + +#show-content.top-blocks .show-info { + float: none; +} + +#show-content.top-blocks .show-info .show-block { + display: inline-block; + border-left: 1px solid #AAAAAA; + padding-left: 20px; + margin-left: 20px; + margin-top: 0; +} + +#show-content.top-blocks .show-info .show-block.first-block { + border-left: none; + padding-left: 0; + margin-left: 0; +} + +#show-content.top-blocks .show-info .show-block.last-block { + margin-bottom: 0; +} + +/* Mobile Screens */ +/* -------------- */ +#show-content.narrow .show-sections { + overflow: hidden; +} + +#show-content.narrow .show-description { + max-height:600px; +} + +#show-content.narrow .show-tabs .show-tab-spacer { + width: 20px; +} + +#show-content.right-blocks.narrow .show-info { + margin-left: 0; +} + +#show-content.narrow .show-info .show-block { + max-width: 95%; + margin-left: auto; + margin-right: auto; +} + +#show-content.narrow .show-info .show-avatar, #show-content.narrow .show-info .show-controls { + display: inline-block; +} + +#show-content.narrow .show-info .show-images { + margin-right: 20px; + margin-left: 0; +} + +#show-content.narrow .show-info .show-avatar img { + width: 150px; + height: 150px; +} + +#show-content.narrow .show-info .show-download { + margin-left: 0; +} diff --git a/css/widgets.css b/css/rs-widgets.css similarity index 87% rename from css/widgets.css rename to css/rs-widgets.css index 8714cd3..7766146 100644 --- a/css/widgets.css +++ b/css/rs-widgets.css @@ -1,3 +1,6 @@ +/* Radio Station Widget Styles */ +/* --------------------------- */ + /* Widget Layout */ .on-air-dj-avatar { padding-bottom: 5px; @@ -45,7 +48,6 @@ .on-air-upcoming-list, .widget .on-air-upcoming-list { list-style: none; - list-style-type: none; } .on-air-dj-desc, .on-air-dj-sched { @@ -57,3 +59,6 @@ clear: both; } +#myplaylist-nowplaying { + line-height: 1.6em; +} diff --git a/freemius/LICENSE.txt b/freemius/LICENSE.txt new file mode 100644 index 0000000..30ace6a --- /dev/null +++ b/freemius/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/freemius/README.md b/freemius/README.md new file mode 100644 index 0000000..1c1f8fd --- /dev/null +++ b/freemius/README.md @@ -0,0 +1,253 @@ +Freemius WordPress SDK +====================== + +[Monetization](https://freemius.com/wordpress/), [analytics](https://freemius.com/wordpress/insights/), and marketing automation platform for plugin & theme developers. Freemius empower developers to create prosperous subscription based businesses. + +You can see some of the WordPress.org plugins & themes that are utilizing the power of Freemius here: + +https://includewp.com/freemius/#focus + +If you are a WordPress plugin or theme developer and you are interested to monetize with Freemius you can [sign-up here for free](https://dashboard.freemius.com/register/): + +https://dashboard.freemius.com/register/ + +**Below you'll find the integration instructions for our WordPress SDK.** + +## Code Documentation + +You can find the SDK's documentation here: +https://freemius.com/help/documentation/wordpress-sdk/ + +## Initializing the SDK + +Copy the code below and paste it into the top of your main plugin's PHP file, right after the plugin's header comment: + +```php + '1234', + 'slug' => 'my-plugin-slug', + 'menu_slug' => 'my_menu_slug', // You can also use __FILE__ + 'public_key' => 'pk_MY_PUBLIC_KEY', + 'is_live' => true, + 'is_premium' => true, + 'has_addons' => false, + 'has_paid_plans' => false, + // Set the SDK to work in a sandbox mode (for development & testing). + // IMPORTANT: MAKE SURE TO REMOVE SECRET KEY BEFORE DEPLOYMENT. + 'secret_key' => 'sk_MY_SECRET_KEY', + ) ); + } + + return $my_prefix_fs; + } + + // Init Freemius. + my_prefix_fs(); +?> +``` + +- **1234** - Replace with your plugin's ID. +- **pk_MY_PUBLIC_KEY** - Replace with your plugin's public key. +- **sk_MY_SECRET_KEY** - Replace with your plugin's secret key. +- **my-plugin-slug** - Replace with your plugin's WordPress.org slug. +- **my_menu_slug** - Replace with your admin dashboard settings menu slug. + + +## Usage example + +You can call the SDK by using the shortcode function: + +```php +get_upgrade_url(); ?> +``` + +Or when calling Freemius multiple times in a scope, it's recommended to use it with the global variable: + +```php +get_account_url(); +?> +``` + +## Adding license based logic examples + +Add marketing content to encourage your users to upgrade for your paid version: + +```php +is_not_paying() ) { + echo '

    ' . esc_html__('Awesome Premium Features', 'my-plugin-slug') . '

    '; + echo '' . + esc_html__('Upgrade Now!', 'my-plugin-slug') . + ''; + echo '
    '; + } +?> +``` + +Add logic which will only be available in your premium plugin version: + +```php +is__premium_only() ) { + + // ... premium only logic ... + + } +?> +``` + +To add a function which will only be available in your premium plugin version, simply add __premium_only as the suffix of the function name. Just make sure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: + +```php +is__premium_only() ) { + // Init premium version. + $this->admin_init__premium_only(); + + add_action( 'admin_init', array( &$this, 'admin_init_hook__premium_only' ); + } + + ... + } + + // This method will be only included in the premium version. + function admin_init__premium_only() { + ... + } + + // This method will be only included in the premium version. + function admin_init_hook__premium_only() { + ... + } + } +?> +``` + +Add logic which will only be executed for customers in your 'professional' plan: + +```php +is_plan('professional', true) ) { + // .. logic related to Professional plan only ... + } +?> +``` + +Add logic which will only be executed for customers in your 'professional' plan or higher plans: + +```php +is_plan('professional') ) { + // ... logic related to Professional plan and higher plans ... + } +?> +``` + +Add logic which will only be available in your premium plugin version AND will only be executed for customers in your 'professional' plan (and higher plans): + +```php +is_plan__premium_only('professional') ) { + // ... logic related to Professional plan and higher plans ... + } +?> +``` + +Add logic only for users in trial: + +```php +is_trial() ) { + // ... logic for users in trial ... + } +?> +``` + +Add logic for specified paid plan: + +```php +is__premium_only() ) { + if ( my_prefix_fs()->is_plan( 'professional', true ) ) { + + // ... logic related to Professional plan only ... + + } else if ( my_prefix_fs()->is_plan( 'business' ) ) { + + // ... logic related to Business plan and higher plans ... + + } + } +?> +``` + +## Excluding files and folders from the free plugin version +There are two ways to exclude files from your free version. + +1. Add `__premium_only` just before the file extension. For example, functions__premium_only.php will be only included in the premium plugin version. This works for all type of files, not only PHP. +2. Add `@fs_premium_only` a sepcial meta tag to the plugin's main PHP file header. Example: +```php + +``` +The file `/lib/functions.php` and the directory `/premium-files/` will be removed from the free plugin version. + +# WordPress.org Compliance +Based on [WordPress.org Guidelines](https://wordpress.org/plugins/about/guidelines/) you are not allowed to submit a plugin that has premium code in it: +> All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers. + +Therefore, if you want to deploy your free plugin's version to WordPress.org, make sure you wrap all your premium code with `if ( my_prefix_fs()->{{ method }}__premium_only() )` or the other methods provided to exclude premium features & files from the free version. + +## Deployment +Zip your plugin's root folder and upload it in the Deployment section in the *Freemius Developer's Dashboard*. +The plugin will be scanned and processed by a custom developed *PHP Processor* which will auto-generate two versions of your plugin: + +1. **Premium version**: Identical to your uploaded version, including all code (except your `secret_key`). Will be enabled for download ONLY for your paying or in trial customers. +2. **Free version**: The code stripped from all your paid features (based on the logic added wrapped in `{ method }__premium_only()`). + +The free version is the one that you should give your users to download. Therefore, download the free generated version and upload to your site. Or, if your plugin was WordPress.org complaint and you made sure to exclude all your premium code with the different provided techniques, you can deploy the downloaded free version to the .org repo. + +## Reporting Bugs +Email dev [at] freemius [dot] com + +## FAQ + +## Copyright +Freemius, Inc. diff --git a/freemius/assets/css/admin/account.css b/freemius/assets/css/admin/account.css new file mode 100644 index 0000000..24e84f7 --- /dev/null +++ b/freemius/assets/css/admin/account.css @@ -0,0 +1 @@ +label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}#fs_account .postbox,#fs_account .widefat{max-width:700px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #F1F1F1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:0.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type="text"]{color:#0073AA;font-size:16px;background:none}.fs-key-value-table input[type="text"]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} diff --git a/freemius/assets/css/admin/add-ons.css b/freemius/assets/css/admin/add-ons.css new file mode 100644 index 0000000..1a41fe1 --- /dev/null +++ b/freemius/assets/css/admin/add-ons.css @@ -0,0 +1,2 @@ +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:greenyellow;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.3);box-shadow:1px 1px 1px rgba(0,0,0,0.3);text-transform:uppercase;font-size:0.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} +#TB_window,#TB_window iframe{width:821px !important}#plugin-information .fyi{width:266px !important}#plugin-information #section-holder{margin-right:299px}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:greenyellow;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid darkgreen;padding:2px;text-align:center;font-size:0.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#F3F3F3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:greenyellow}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;width:auto;top:0;right:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-top:6px solid white;border-right:4px solid transparent;border-left:4px solid transparent;top:12px;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{position:absolute;right:-1px;top:100%;margin-left:auto;padding:3px 0;border:1px solid #bfbfbf;background-color:#fff;z-index:1;width:230px;text-align:left;-moz-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);-webkit-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{-moz-border-radius:3px 0 3px 3px;-webkit-border-radius:3px 0 3px 3px;border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{-moz-border-radius:3px 3px 0 3px;-webkit-border-radius:3px 3px 0 3px;border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:normal;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{top:auto;bottom:100%;margin-bottom:2px}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{text-align:center;display:table}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer>.button,#plugin-information-footer .fs-dropdown{position:relative;top:3px}#plugin-information-footer>.button.left,#plugin-information-footer .fs-dropdown.left{float:left}#plugin-information-footer>.right,#plugin-information-footer .fs-dropdown{float:right}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} diff --git a/freemius/assets/css/admin/affiliation.css b/freemius/assets/css/admin/affiliation.css new file mode 100644 index 0000000..003ca37 --- /dev/null +++ b/freemius/assets/css/admin/affiliation.css @@ -0,0 +1 @@ +@charset "UTF-8";#fs_affiliation_content_wrapper #messages{margin-top:25px}#fs_affiliation_content_wrapper h3{font-size:24px;padding:0;margin-left:0}#fs_affiliation_content_wrapper ul li{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;list-style-type:none}#fs_affiliation_content_wrapper ul li:before{content:'✓';margin-right:10px;font-weight:bold}#fs_affiliation_content_wrapper p:not(.description),#fs_affiliation_content_wrapper li,#fs_affiliation_content_wrapper label{font-size:16px !important;line-height:26px !important}#fs_affiliation_content_wrapper .button{margin-top:20px;margin-bottom:7px;line-height:35px;height:40px;font-size:16px}#fs_affiliation_content_wrapper .button#cancel_button{margin-right:5px}#fs_affiliation_content_wrapper form .input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form .input-container .input-label{font-weight:bold;display:block;width:100%}#fs_affiliation_content_wrapper form .input-container.input-container-text label,#fs_affiliation_content_wrapper form .input-container.input-container-text input,#fs_affiliation_content_wrapper form .input-container.input-container-text textarea{display:block}#fs_affiliation_content_wrapper form .input-container #add_domain,#fs_affiliation_content_wrapper form .input-container .remove-domain{text-decoration:none;display:inline-block;margin-top:3px}#fs_affiliation_content_wrapper form .input-container #add_domain:focus,#fs_affiliation_content_wrapper form .input-container .remove-domain:focus{box-shadow:none}#fs_affiliation_content_wrapper form .input-container #add_domain.disabled,#fs_affiliation_content_wrapper form .input-container .remove-domain.disabled{color:#aaa;cursor:default}#fs_affiliation_content_wrapper form #extra_domains_container .description{margin-top:0;position:relative;top:-4px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain{display:inline-block;margin-right:5px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain:last-of-type{margin-bottom:0} diff --git a/freemius/assets/css/admin/checkout.css b/freemius/assets/css/admin/checkout.css new file mode 100644 index 0000000..56515d2 --- /dev/null +++ b/freemius/assets/css/admin/checkout.css @@ -0,0 +1 @@ +@media screen and (max-width: 782px){#wpbody-content{padding-bottom:0 !important}} diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css new file mode 100644 index 0000000..846a68d --- /dev/null +++ b/freemius/assets/css/admin/common.css @@ -0,0 +1,2 @@ +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media (max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}} +.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 500px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/assets/css/admin/connect.css b/freemius/assets/css/admin/connect.css new file mode 100644 index 0000000..176489d --- /dev/null +++ b/freemius/assets/css/admin/connect.css @@ -0,0 +1 @@ +#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions .fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li div{margin-left:55px}#fs_connect .fs-permissions ul li div span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li div p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:0;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li div{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa} diff --git a/freemius/assets/css/admin/debug.css b/freemius/assets/css/admin/debug.css new file mode 100644 index 0000000..808068d --- /dev/null +++ b/freemius/assets/css/admin/debug.css @@ -0,0 +1 @@ +.switch{position:relative;display:inline-block;font-size:1.6em;font-weight:bold;color:#ccc;text-shadow:0px 1px 1px rgba(255,255,255,0.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:4px;background:#ececec;box-shadow:0px 0px 4px rgba(0,0,0,0.1),inset 0px 1px 3px 0px rgba(0,0,0,0.1);cursor:pointer}.switch span{display:inline-block;width:35px;text-transform:uppercase}.switch span.on{color:#6bc406}.switch .toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.3);border-radius:4px;background:#fff;background:-moz-linear-gradient(top, #ececec 0%, #fff 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ececec), color-stop(100%, #fff));background:-webkit-linear-gradient(top, #ececec 0%, #fff 100%);background:-o-linear-gradient(top, #ececec 0%, #fff 100%);background:-ms-linear-gradient(top, #ececec 0%, #fff 100%);background:linear-gradient(top, #ececec 0%, #fff 100%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.5);z-index:999;-moz-transition:all 0.15s ease-in-out;-o-transition:all 0.15s ease-in-out;-ms-transition:all 0.15s ease-in-out;-webkit-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}.switch.on .toggle{left:2%}.switch.off .toggle{left:54%}.switch.round{padding:0px 20px;border-radius:40px}.switch.round .toggle{border-radius:40px;width:14px;height:14px}.switch.round.on .toggle{left:3%;background:#6bc406}.switch.round.off .toggle{left:58%}.switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:bold}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac !important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be} diff --git a/freemius/assets/css/admin/dialog-boxes.css b/freemius/assets/css/admin/dialog-boxes.css new file mode 100644 index 0000000..00f7555 --- /dev/null +++ b/freemius/assets/css/admin/dialog-boxes.css @@ -0,0 +1,2 @@ +.fs-modal{position:fixed;overflow:auto;height:100%;width:100%;top:0;z-index:100000;display:none;background:rgba(0,0,0,0.6)}.fs-modal .fs-modal-dialog{background:transparent;position:absolute;left:50%;margin-left:-298px;padding-bottom:30px;top:-100%;z-index:100001;width:596px}@media (max-width: 650px){.fs-modal .fs-modal-dialog{margin-left:-50%;box-sizing:border-box;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active{display:block}.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{border:0;background:#fefefe;padding:20px}.fs-modal .fs-modal-header{border-bottom:#eeeeee solid 1px;background:#fbfbfb;padding:15px 20px;position:relative;margin-bottom:-10px}.fs-modal .fs-modal-header h4{margin:0;padding:0;text-transform:uppercase;font-size:1.2em;font-weight:bold;color:#cacaca;text-shadow:1px 1px 1px #fff;letter-spacing:0.6px;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{position:absolute;right:10px;top:12px;cursor:pointer;color:#bbb;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;padding:3px;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;-ms-transition:all 0.2s ease-in-out;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{color:#fff;background:#aaa}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-weight:bold;font-size:20px;margin-top:0}.fs-modal .fs-modal-footer{border-top:#eeeeee solid 1px;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:first-child{margin:0}.fs-modal .fs-modal-panel>.notice.inline{margin:0;display:none}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{right:auto;left:20px}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .reason-input,.fs-modal.fs-modal-deactivation-feedback .internal-message{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea,.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;padding:7px;display:none}@media (max-width: 650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label{float:left}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0 !important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{margin-top:0;line-height:1.5em}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:bold;padding:0 25px;margin-bottom:0}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;top:5px;position:relative}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table,.fs-license-options-container table select,.fs-license-options-container table .fs-available-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{position:relative;top:6px;float:left;margin-right:5px}.fs-license-options-container table .fs-other-license-key-container div{overflow:hidden;width:auto;height:30px;display:block;top:2px;position:relative}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media (max-width: 650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}} +.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-right:7px;margin-left:0}a.show-license-resend-modal{margin-top:4px;display:inline-block}.fs-ajax-loader{position:relative;width:170px;height:20px;margin:auto}.fs-ajax-loader .fs-ajax-loader-bar{position:absolute;top:0;background-color:#0074a3;width:20px;height:20px;-webkit-animation-name:bounce_ajaxLoader;-moz-animation-name:bounce_ajaxLoader;-ms-animation-name:bounce_ajaxLoader;-o-animation-name:bounce_ajaxLoader;animation-name:bounce_ajaxLoader;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s;animation-duration:1.5s;animation-iteration-count:infinite;-o-animation-iteration-count:infinite;-ms-animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;-webkit-animation-direction:normal;-moz-animation-direction:normal;-ms-animation-direction:normal;-o-animation-direction:normal;animation-direction:normal;-moz-transform:0.3;-o-transform:0.3;-ms-transform:0.3;-webkit-transform:0.3;transform:0.3}.fs-ajax-loader .fs-ajax-loader-bar-1{left:0px;animation-delay:0.6s;-o-animation-delay:0.6s;-ms-animation-delay:0.6s;-webkit-animation-delay:0.6s;-moz-animation-delay:0.6s}.fs-ajax-loader .fs-ajax-loader-bar-2{left:19px;animation-delay:0.75s;-o-animation-delay:0.75s;-ms-animation-delay:0.75s;-webkit-animation-delay:0.75s;-moz-animation-delay:0.75s}.fs-ajax-loader .fs-ajax-loader-bar-3{left:38px;animation-delay:0.9s;-o-animation-delay:0.9s;-ms-animation-delay:0.9s;-webkit-animation-delay:0.9s;-moz-animation-delay:0.9s}.fs-ajax-loader .fs-ajax-loader-bar-4{left:57px;animation-delay:1.05s;-o-animation-delay:1.05s;-ms-animation-delay:1.05s;-webkit-animation-delay:1.05s;-moz-animation-delay:1.05s}.fs-ajax-loader .fs-ajax-loader-bar-5{left:76px;animation-delay:1.2s;-o-animation-delay:1.2s;-ms-animation-delay:1.2s;-webkit-animation-delay:1.2s;-moz-animation-delay:1.2s}.fs-ajax-loader .fs-ajax-loader-bar-6{left:95px;animation-delay:1.35s;-o-animation-delay:1.35s;-ms-animation-delay:1.35s;-webkit-animation-delay:1.35s;-moz-animation-delay:1.35s}.fs-ajax-loader .fs-ajax-loader-bar-7{left:114px;animation-delay:1.5s;-o-animation-delay:1.5s;-ms-animation-delay:1.5s;-webkit-animation-delay:1.5s;-moz-animation-delay:1.5s}.fs-ajax-loader .fs-ajax-loader-bar-8{left:133px;animation-delay:1.65s;-o-animation-delay:1.65s;-ms-animation-delay:1.65s;-webkit-animation-delay:1.65s;-moz-animation-delay:1.65s}@-moz-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-ms-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-o-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-webkit-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}.fs-modal-auto-install #request-filesystem-credentials-form h2,.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;padding:10px 10px 5px 10px;width:300px;max-width:100%}.fs-modal-auto-install #request-filesystem-credentials-form>div,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form fieldset{width:300px;max-width:100%;margin:0 auto;display:block}.button-primary.warn{box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c;background:#f56a48;border-color:#ec6544 #d2593c #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{color:#f5b3a1 !important;background:#e76444 !important;border-color:#d85e40 !important;text-shadow:0 -1px 0 rgba(0,0,0,0.1) !important} diff --git a/freemius/assets/css/admin/gdpr-optin-notice.css b/freemius/assets/css/admin/gdpr-optin-notice.css new file mode 100644 index 0000000..0da5146 --- /dev/null +++ b/freemius/assets/css/admin/gdpr-optin-notice.css @@ -0,0 +1 @@ +.fs-notice[data-id^="gdpr_optin_actions"] .underlined{text-decoration:underline}.fs-notice[data-id^="gdpr_optin_actions"] ul .button,.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{vertical-align:middle}.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{display:inline-block;margin-left:3px} diff --git a/freemius/assets/css/admin/index.php b/freemius/assets/css/admin/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/assets/css/admin/index.php @@ -0,0 +1,3 @@ +4QUc+`00KlZEA}{+QaB(jz&calwp&7Oa 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r literal 0 HcmV?d00001 diff --git a/freemius/assets/img/theme-icon.png b/freemius/assets/img/theme-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..045d4b25f9727fada65dcfd66cf0a0a3d167ee84 GIT binary patch literal 11237 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

    {^6Retfv z6e)+Y86 z&-OAhn&%B~c$`zOH~YnViV(=PdD(}Fcr6gb z@qCJTX)KfxzL`hqdq$pyR{#-Ot7VpvXTX&Lf;o10Uz!M^YMI*SFN%7P`VNK(D>QTdyCP-Z<%CK(t*T4j;W{3$D2mM!AexlA- zUb<}1Dx;~3vaWm)z9em_tc|P?cssQ$Hj)Y6^M;Y^!#S7DAf(wTVsi4od$#co(U{KZ4KkEm$NX_fw^6flAOA1wX{X>{{THMY+L{U literal 0 HcmV?d00001 diff --git a/freemius/assets/index.php b/freemius/assets/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/assets/index.php @@ -0,0 +1,3 @@ + 0) { + $window.on('scroll', function () { + for (var i = 0; i < iframes.length; i++) { + FS.PostMessage.postScroll(iframes[i]); + } + }); + } + }, + init_child : function () + { + this.init(_parent_subdomain); + + _is_child = true; + + // Post height of a child right after window is loaded. + $(window).bind('load', function () { + FS.PostMessage.postHeight(); + + // Post message that window was loaded. + FS.PostMessage.post('loaded'); + }); + }, + hasParent : function () + { + return _hasParent; + }, + postHeight : function (diff, wrapper) { + diff = diff || 0; + wrapper = wrapper || '#wrap_section'; + this.post('height', { + height: diff + $(wrapper).outerHeight(true) + }); + }, + postScroll : function (iframe) { + this.post('scroll', { + top: $window.scrollTop(), + height: ($window.height() - parseFloat($html.css('paddingTop')) - parseFloat($html.css('marginTop'))) + }, iframe); + }, + post : function (type, data, iframe) + { + console.debug('PostMessage.post', type); + + if (iframe) + { + // Post to iframe. + _postman.postMessage(JSON.stringify({ + type: type, + data: data + }), iframe.src, iframe.contentWindow); + } + else { + // Post to parent. + _postman.postMessage(JSON.stringify({ + type: type, + data: data + }), _parent_url, window.parent); + } + }, + receive: function (type, callback) + { + console.debug('PostMessage.receive', type); + + if (undef === _callbacks[type]) + _callbacks[type] = []; + + _callbacks[type].push(callback); + }, + receiveOnce: function (type, callback) + { + if (this.is_set(type)) + return; + + this.receive(type, callback); + }, + // Check if any callbacks assigned to a specified message type. + is_set: function (type) + { + return (undef != _callbacks[type]); + }, + parent_url: function () + { + return _parent_url; + }, + parent_subdomain: function () + { + return _parent_subdomain; + } + }; + }(); +})(jQuery); \ No newline at end of file diff --git a/freemius/config.php b/freemius/config.php new file mode 100644 index 0000000..1c6011b --- /dev/null +++ b/freemius/config.php @@ -0,0 +1,388 @@ +is_registered() && $fs->is_tracking_allowed()` + * + * @since 1.0.1 + * @return bool + */ + abstract function is_registered(); + + /** + * Check if the user skipped connecting the account with Freemius. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_anonymous(); + + /** + * Check if the user currently in activation mode. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_activation_mode(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Usage Tracking + #---------------------------------------------------------------------------------- + + /** + * Returns TRUE if the user opted-in and didn't disconnect (opt-out). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool + */ + abstract function is_tracking_allowed(); + + /** + * Returns TRUE if the user never opted-in or manually opted-out. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + function is_tracking_prohibited() { + return ! $this->is_registered() || ! $this->is_tracking_allowed(); + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API Result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + abstract function stop_tracking(); + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + abstract function allow_tracking(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Module Type + #---------------------------------------------------------------------------------- + + /** + * Checks if the plugin's type is "plugin". The other type is "theme". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + abstract function is_plugin(); + + /** + * Checks if the module type is "theme". The other type is "plugin". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_theme() { + return ( ! $this->is_plugin() ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Permissions + #---------------------------------------------------------------------------------- + + /** + * Check if plugin must be WordPress.org compliant. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_org_repo_compliant(); + + /** + * Check if plugin is allowed to install executable files. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_allowed_to_install() { + return ( $this->is_premium() || ! $this->is_org_repo_compliant() ); + } + + #endregion + + /** + * Check if user in trial or in free plan (not paying). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_not_paying() { + return ( $this->is_trial() || $this->is_free_plan() ); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + */ + abstract function is_paying(); + + /** + * Check if the user is paying or in trial. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying_or_trial() { + return ( $this->is_paying() || $this->is_trial() ); + } + + /** + * Check if user in a trial or have feature enabled license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + abstract function can_use_premium_code(); + + #---------------------------------------------------------------------------------- + #region Premium Only + #---------------------------------------------------------------------------------- + + /** + * All logic wrapped in methods with "__premium_only()" suffix will be only + * included in the premium code. + * + * Example: + * if ( freemius()->is__premium_only() ) { + * ... + * } + */ + + /** + * Returns true when running premium plugin code. + * + * @since 1.0.9 + * + * @return bool + */ + function is__premium_only() { + return $this->is_premium(); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + * + */ + function is_paying__premium_only() { + return ( $this->is__premium_only() && $this->is_paying() ); + } + + /** + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan__premium_only( $plan, $exact = false ) { + return ( $this->is_premium() && $this->is_plan( $plan, $exact ) ); + } + + /** + * Check if plan matches active license' plan or active trial license' plan. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan_or_trial__premium_only( $plan, $exact = false ) { + return ( $this->is_premium() && $this->is_plan_or_trial( $plan, $exact ) ); + } + + /** + * Check if the user is paying or in trial. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying_or_trial__premium_only() { + return $this->is_premium() && $this->is_paying_or_trial(); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.4 + * + * @return bool + * + * @deprecated Method name is confusing since it's not clear from the name the code will be removed. + * @using Alias to is_paying__premium_only() + */ + function is_paying__fs__() { + return $this->is_paying__premium_only(); + } + + /** + * Check if user in a trial or have feature enabled license. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function can_use_premium_code__premium_only() { + return $this->is_premium() && $this->can_use_premium_code(); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Trial + #---------------------------------------------------------------------------------- + + /** + * Check if the user in a trial. + * + * @since 1.0.3 + * + * @return bool + */ + abstract function is_trial(); + + /** + * Check if trial already utilized. + * + * @since 1.0.9 + * + * @return bool + */ + abstract function is_trial_utilized(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Plans + #---------------------------------------------------------------------------------- + + /** + * Check if the user is on the free plan of the product. + * + * @since 1.0.4 + * + * @return bool + */ + abstract function is_free_plan(); + + /** + * @since 1.0.2 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + abstract function is_plan( $plan, $exact = false ); + + /** + * Check if plan based on trial. If not in trial mode, should return false. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + abstract function is_trial_plan( $plan, $exact = false ); + + /** + * Check if plan matches active license' plan or active trial license' plan. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan_or_trial( $plan, $exact = false ) { + return $this->is_plan( $plan, $exact ) || + $this->is_trial_plan( $plan, $exact ); + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + abstract function has_paid_plan(); + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + abstract function has_free_plan(); + + /** + * Check if plugin is premium only (no free plans). + * + * NOTE: is__premium_only() is very different method, don't get confused. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + abstract function is_only_premium(); + + /** + * Check if module has a premium code version. + * + * Serviceware module might be freemium without any + * premium code version, where the paid features + * are all part of the service. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + abstract function has_premium_version(); + + /** + * Check if module has any release on Freemius, + * or all plugin's code is on WordPress.org (Serviceware). + * + * @return bool + */ + function has_release_on_freemius() { + return ! $this->is_org_repo_compliant() || + $this->has_premium_version(); + } + + /** + * Checks if it's a freemium plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_freemium() { + return $this->has_paid_plan() && + $this->has_free_plan(); + } + + /** + * Check if module has only one plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return bool + */ + abstract function is_single_plan(); + + #endregion + + /** + * Check if running payments in sandbox mode. + * + * @since 1.0.4 + * + * @return bool + */ + abstract function is_payments_sandbox(); + + /** + * Check if running test vs. live plugin. + * + * @since 1.0.5 + * + * @return bool + */ + abstract function is_live(); + + /** + * Check if running premium plugin code. + * + * @since 1.0.5 + * + * @return bool + */ + abstract function is_premium(); + + /** + * Get upgrade URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param string $period Billing cycle. + * + * @return string + */ + abstract function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ); + + /** + * Check if Freemius was first added in a plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + function is_plugin_update() { + return ! $this->is_plugin_new_install(); + } + + /** + * Check if Freemius was part of the plugin when the user installed it first. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + abstract function is_plugin_new_install(); + + #---------------------------------------------------------------------------------- + #region Marketing + #---------------------------------------------------------------------------------- + + /** + * Check if current user purchased any other plugins before. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function has_purchased_before(); + + /** + * Check if current user classified as an agency. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_agency(); + + /** + * Check if current user classified as a developer. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_developer(); + + /** + * Check if current user classified as a business. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_business(); + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php new file mode 100644 index 0000000..79a9a49 --- /dev/null +++ b/freemius/includes/class-freemius.php @@ -0,0 +1,23283 @@ +store_id_slug_type_path_map( $module_id, $slug ); + } + + $this->_module_id = $module_id; + $this->_slug = $this->get_slug(); + $this->_module_type = $this->get_module_type(); + + $this->_blog_id = is_multisite() ? get_current_blog_id() : null; + + $this->_storage = FS_Storage::instance( $this->_module_type, $this->_slug ); + + $this->_cache = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" ); + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init ); + $this->_plugin_dir_path = plugin_dir_path( $this->_plugin_main_file_path ); + $this->_plugin_basename = $this->get_plugin_basename(); + $this->_free_plugin_basename = str_replace( '-premium/', '/', $this->_plugin_basename ); + + $this->_is_multisite_integrated = ( + defined( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) && + ( true === constant( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) ) + ); + + $this->_is_network_active = ( + is_multisite() && + $this->_is_multisite_integrated && + // Themes are always network activated, but the ACTUAL activation is per site. + $this->is_plugin() && + ( + is_plugin_active_for_network( $this->_plugin_basename ) || + // Plugin network level activation or uninstall. + ( fs_is_network_admin() && is_plugin_inactive( $this->_plugin_basename ) ) + ) + ); + + $this->_storage->set_network_active( + $this->_is_network_active, + $this->is_delegated_connection() + ); + + if ( ! isset( $this->_storage->is_network_activated ) ) { + $this->_storage->is_network_activated = $this->_is_network_active; + } + + if ( $this->_storage->is_network_activated != $this->_is_network_active ) { + // Update last activation level. + $this->_storage->is_network_activated = $this->_is_network_active; + + $this->maybe_adjust_storage(); + } + + #region Migration + + if ( is_multisite() ) { + /** + * If the install_timestamp exists on the site level but doesn't exist on the + * network level storage, it means that we need to process the storage with migration. + * + * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, install_timestamp will be already set in the network level storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( false === $this->_storage->get( 'install_timestamp', false, true ) && + false !== $this->_storage->get( 'install_timestamp', false, false ) + ) { + // Initiate storage migration. + $this->_storage->migrate_to_network(); + + // Migrate module cache to network level storage. + $this->_cache->migrate_to_network(); + } + } + + #endregion + + $base_name_split = explode( '/', $this->_plugin_basename ); + $this->_plugin_dir_name = $base_name_split[0]; + + if ( $this->_logger->is_on() ) { + $this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path ); + $this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path ); + $this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename ); + $this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename ); + $this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name ); + } + + // Remember link between file to slug. + $this->store_file_slug_map(); + + // Store plugin's initial install timestamp. + if ( ! isset( $this->_storage->install_timestamp ) ) { + $this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME; + } + + if ( ! is_object( $this->_plugin ) ) { + $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get(); + } + + $this->_admin_notices = FS_Admin_Notices::instance( + $this->_slug . ( $this->is_theme() ? ':theme' : '' ), + /** + * Ensure that the admin notice will always have a title by using the stored plugin title if available and + * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + ( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ), + $this->get_unique_affix() + ); + + if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) || + 'true' === fs_request_is_action( 'restart_freemius' ) + ) { + FS_Api::clear_cache(); + $this->_cache->clear(); + } + + $this->_register_hooks(); + + /** + * Starting from version 2.0.0, `FS_Site` entities no longer have the `plan` property and have `plan_id` + * instead. This should be called before calling `_load_account()`, otherwise, `$this->_site` will not be + * loaded in `_load_account` for versions of SDK starting from 2.0.0. + * + * @author Leo Fajardo (@leorw) + */ + self::migrate_install_plan_to_plan_id( $this->_storage ); + + $this->_load_account(); + + $this->_version_updates_handler(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + private function maybe_adjust_storage() { + $install_timestamp = null; + $prev_is_premium = null; + + $options_to_update = array(); + + $is_network_admin = fs_is_network_admin(); + + $network_install_timestamp = $this->_storage->get( 'install_timestamp', null, true ); + + if ( ! $is_network_admin ) { + if ( is_null( $network_install_timestamp ) ) { + // Plugin was not network-activated before. + return; + } + + if ( is_null( $this->_storage->get( 'install_timestamp', null, false ) ) ) { + // Set the `install_timestamp` only if it's not yet set. + $install_timestamp = $network_install_timestamp; + } + + $prev_is_premium = $this->_storage->get( 'prev_is_premium', null, true ); + } else { + $current_wp_user = self::_get_current_wp_user(); + $current_fs_user = self::_get_user_by_email( $current_wp_user->user_email ); + $network_user_info = array(); + + $skips_count = 0; + + $sites = self::get_sites(); + $sites_count = count( $sites ); + + $blog_id_2_install_map = array(); + + $is_first_non_ignored_blog = true; + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $blog_install_timestamp = $this->_storage->get( 'install_timestamp', null, $blog_id ); + + if ( is_null( $blog_install_timestamp ) ) { + // Plugin has not been installed on this blog. + continue; + } + + $is_earlier_install = ( + ! is_null( $install_timestamp ) && + $blog_install_timestamp < $install_timestamp + ); + + $install = $this->get_install_by_blog_id( $blog_id ); + + $update_network_user_info = false; + + if ( ! is_object( $install ) ) { + if ( ! $this->_storage->get( 'is_anonymous', false, $blog_id ) ) { + // The opt-in decision (whether to skip or opt in) is yet to be made. + continue; + } + + $skips_count ++; + } else { + $blog_id_2_install_map[ $blog_id ] = $install; + + if ( empty( $network_user_info ) ) { + // Set the network user info for the 1st time. Choose any user information whether or not it is for the current WP user. + $update_network_user_info = true; + } + + if ( ! $update_network_user_info && + is_object( $current_fs_user ) && + $network_user_info['user_id'] != $current_fs_user->id && + $install->user_id == $current_fs_user->id + ) { + // If an install that is owned by the current WP user is found, use its user information instead. + $update_network_user_info = true; + } + + if ( ! $update_network_user_info && + $is_earlier_install && + ( ! is_object( $current_fs_user ) || $current_fs_user->id == $install->user_id ) + ) { + // Update to the earliest install info if there's no install found so far that is owned by the current WP user; OR only if the found install is owned by the current WP user. + $update_network_user_info = true; + } + } + + if ( $update_network_user_info ) { + $network_user_info = array( + 'user_id' => $install->user_id, + 'blog_id' => $blog_id + ); + } + + $site_prev_is_premium = $this->_storage->get( 'prev_is_premium', null, $blog_id ); + + if ( $is_first_non_ignored_blog ) { + $prev_is_premium = $site_prev_is_premium; + + if ( is_null( $network_install_timestamp ) ) { + $install_timestamp = $blog_install_timestamp; + } + + $is_first_non_ignored_blog = false; + + continue; + } + + if ( ! is_null( $prev_is_premium ) && $prev_is_premium !== $site_prev_is_premium ) { + // If a different `$site_prev_is_premium` value is found, do not include the option in the collection of options to update. + $prev_is_premium = null; + } + + if ( $is_earlier_install ) { + // If an earlier install timestamp is found. + $install_timestamp = $blog_install_timestamp; + } + } + + $installs_count = count( $blog_id_2_install_map ); + + if ( $sites_count === ( $installs_count + $skips_count ) ) { + if ( ! empty( $network_user_info ) ) { + $options_to_update['network_user_id'] = $network_user_info['user_id']; + $options_to_update['network_install_blog_id'] = $network_user_info['blog_id']; + + foreach ( $blog_id_2_install_map as $blog_id => $install ) { + if ( $install->user_id == $network_user_info['user_id'] ) { + continue; + } + + $this->_storage->store( 'is_delegated_connection', true, $blog_id ); + } + } + + if ( $sites_count === $skips_count ) { + /** + * Assume network-level skipping as the intended action if all actions identified were only + * skipping of the connection (i.e., no opt-ins and delegated connections so far). + */ + $options_to_update['is_anonymous_ms'] = true; + } else if ( $sites_count === $installs_count ) { + /** + * Assume network-level opt-in as the intended action if all actions identified were only opt-ins + * (i.e., no delegation and skipping of the connections so far). + */ + $options_to_update['is_network_connected'] = true; + } + } + } + + if ( ! is_null( $install_timestamp ) ) { + $options_to_update['install_timestamp'] = $install_timestamp; + } + + if ( ! is_null( $prev_is_premium ) ) { + $options_to_update['prev_is_premium'] = $prev_is_premium; + } + + if ( ! empty( $options_to_update ) ) { + $this->adjust_storage( $options_to_update, $is_network_admin ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param array $options + * @param bool $is_network_admin + */ + private function adjust_storage( $options, $is_network_admin ) { + foreach ( $options as $name => $value ) { + $this->_storage->store( $name, $value, $is_network_admin ? true : null ); + } + } + + /** + * Checks whether this module has a settings menu. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function has_settings_menu() { + return ( $this->_is_network_active && fs_is_network_admin() ) ? + $this->_menu->has_network_menu() : + $this->_menu->has_menu(); + } + + /** + * Check if the context module is free wp.org theme. + * + * This method is helpful because: + * 1. wp.org themes are limited to a single submenu item, + * and sub-submenu items are most likely not allowed (never verified). + * 2. wp.org themes are not allowed to redirect the user + * after the theme activation, therefore, the agreed UX + * is showing the opt-in as a modal dialog box after + * activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_free_wp_org_theme() { + return ( + $this->is_theme() && + $this->is_org_repo_compliant() && + ! $this->is_premium() + ); + } + + /** + * Checks whether this a submenu item is visible. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.6 + * @since 1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page. + * + * @param string $slug + * @param bool $ignore_free_wp_org_theme_context This is used to decide if the associated tab should be shown + * or hidden. + * + * @return bool + */ + function is_submenu_item_visible( $slug, $ignore_free_wp_org_theme_context = false ) { + if ( $this->is_admin_page( $slug ) ) { + /** + * It is the current context page, so show the submenu item + * so the user will have the right context page, even if it + * was set to hidden. + */ + return true; + } + + if ( ! $this->has_settings_menu() ) { + // No menu settings at all. + return false; + } + + if ( ! $ignore_free_wp_org_theme_context && $this->is_free_wp_org_theme() ) { + /** + * wp.org themes are limited to a single submenu item, and + * sub-submenu items are most likely not allowed (never verified). + */ + return false; + } + + return $this->_menu->is_submenu_item_visible( $slug ); + } + + /** + * Check if a Freemius page should be accessible via the UI. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $slug + * + * @return bool + */ + function is_page_visible( $slug ) { + if ( $this->is_admin_page( $slug ) ) { + return true; + } + + return $this->_menu->is_submenu_item_visible( $slug, true, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _version_updates_handler() { + if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) { + // Freemius version upgrade mode. + $this->_storage->sdk_last_version = $this->_storage->sdk_version; + $this->_storage->sdk_version = $this->version; + + if ( empty( $this->_storage->sdk_last_version ) || + version_compare( $this->_storage->sdk_last_version, $this->version, '<' ) + ) { + $this->_storage->sdk_upgrade_mode = true; + $this->_storage->sdk_downgrade_mode = false; + } else { + $this->_storage->sdk_downgrade_mode = true; + $this->_storage->sdk_upgrade_mode = false; + + } + + $this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version ); + } + + $plugin_version = $this->get_plugin_version(); + if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) { + // Plugin version upgrade mode. + $this->_storage->plugin_last_version = $this->_storage->plugin_version; + $this->_storage->plugin_version = $plugin_version; + + if ( empty( $this->_storage->plugin_last_version ) || + version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' ) + ) { + $this->_storage->plugin_upgrade_mode = true; + $this->_storage->plugin_downgrade_mode = false; + } else { + $this->_storage->plugin_downgrade_mode = true; + $this->_storage->plugin_upgrade_mode = false; + } + + if ( ! empty( $this->_storage->plugin_last_version ) ) { + // Different version of the plugin was installed before, therefore it's an update. + $this->_storage->is_plugin_new_install = false; + } + + $this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version ); + } + } + + #-------------------------------------------------------------------------------- + #region Data Migration on SDK Update + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @param string $sdk_prev_version + * @param string $sdk_version + */ + function _sdk_version_update( $sdk_prev_version, $sdk_version ) { + /** + * @since 1.1.7.3 Fixed unwanted connectivity test cleanup. + */ + if ( empty( $sdk_prev_version ) ) { + return; + } + + if ( version_compare( $sdk_prev_version, '2.1.0', '<' ) && + version_compare( $sdk_version, '2.1.0', '>=' ) + ) { + $this->_storage->handle_gdpr_admin_notice = true; + } + + if ( version_compare( $sdk_prev_version, '2.0.0', '<' ) && + version_compare( $sdk_version, '2.0.0', '>=' ) + ) { + $this->migrate_to_subscriptions_collection(); + + $this->consolidate_licenses(); + + // Clear trial_plan since it's now loaded from the plans collection when needed. + $this->_storage->remove( 'trial_plan', true, false ); + } + + if ( version_compare( $sdk_prev_version, '1.2.3', '<' ) && + version_compare( $sdk_version, '1.2.3', '>=' ) + ) { + /** + * Starting from version 1.2.3, paths are stored as relative instead of absolute and some of them can be + * invalid. + * + * @author Leo Fajardo (@leorw) + */ + $this->remove_invalid_paths(); + } + + if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) && + version_compare( $sdk_version, '1.1.5', '>=' ) + ) { + // On version 1.1.5 merged connectivity and is_on data. + if ( isset( $this->_storage->connectivity_test ) ) { + if ( ! isset( $this->_storage->is_on ) ) { + unset( $this->_storage->connectivity_test ); + } else { + $connectivity_data = $this->_storage->connectivity_test; + $connectivity_data['is_active'] = $this->_storage->is_on['is_active']; + $connectivity_data['timestamp'] = $this->_storage->is_on['timestamp']; + + // Override. + $this->_storage->connectivity_test = $connectivity_data; + + // Remove previous structure. + unset( $this->_storage->is_on ); + } + + } + } + + if ( + version_compare( $sdk_prev_version, '2.2.1', '<' ) && + version_compare( $sdk_version, '2.2.1', '>=' ) + ) { + /** + * Clear the file cache without storing the previous path since it could be a wrong path. For example, + * in the versions of the SDK lower than 2.2.1, it's possible for the path of an add-on to be the same + * as the parent plugin's when the add-on was auto-installed since the relevant method names were not + * skipped in the logic that determines the right path in the `get_caller_main_file_and_type` method + * (e.g. `try_activate_plugin`). Since it was an auto-installation, the caller was the parent plugin + * and so its path was used. In case the stored path is wrong, clearing the cache will resolve issues + * related to data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $this->clear_module_main_file_cache( false ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param \FS_Storage $storage + * @param bool|int|null $blog_id + */ + private static function migrate_install_plan_to_plan_id( FS_Storage $storage, $blog_id = null ) { + if ( empty( $storage->sdk_version ) ) { + // New installation of the plugin, no need to upgrade. + return; + } + + if ( ! version_compare( $storage->sdk_version, '2.0.0', '<' ) ) { + // Previous version is >= 2.0.0, so no need to migrate. + return; + } + + // Alias. + $module_type = $storage->get_module_type(); + $module_slug = $storage->get_module_slug(); + + $installs = self::get_all_sites( $module_type, $blog_id ); + $install = isset( $installs[ $module_slug ] ) ? $installs[ $module_slug ] : null; + + if ( ! is_object( $install ) ) { + return; + } + + if ( isset( $install->plan ) && is_object( $install->plan ) ) { + if ( isset( $install->plan->id ) && ! empty( $install->plan->id ) ) { + $install->plan_id = self::_decrypt( $install->plan->id ); + } + + unset( $install->plan ); + + $installs[ $module_slug ] = clone $install; + + self::set_account_option_by_module( + $module_type, + 'sites', + $installs, + true, + $blog_id + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function migrate_to_subscriptions_collection() { + if ( ! is_object( $this->_site ) ) { + return; + } + + if ( isset( $this->_storage->subscription ) && is_object( $this->_storage->subscription ) ) { + $this->_storage->subscriptions = array( $this->_storage->subscription ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function consolidate_licenses() { + $plugin_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_PLUGIN ); + if ( isset( $plugin_licenses[ $this->_slug ] ) ) { + $plugin_licenses = $plugin_licenses[ $this->_slug ]; + } else { + $plugin_licenses = array(); + } + + $theme_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_THEME ); + if ( isset( $theme_licenses[ $this->_slug ] ) ) { + $theme_licenses = $theme_licenses[ $this->_slug ]; + } else { + $theme_licenses = array(); + } + + if ( empty( $plugin_licenses ) && empty( $theme_licenses ) ) { + return; + } + + $all_licenses = array(); + $user_id_license_ids_map = array(); + + foreach ( $plugin_licenses as $user_id => $user_licenses ) { + if ( is_array( $user_licenses ) ) { + if ( ! isset( $user_license_ids[ $user_id ] ) ) { + $user_id_license_ids_map[ $user_id ] = array(); + } + + foreach ( $user_licenses as $user_license ) { + $all_licenses[] = $user_license; + $user_id_license_ids_map[ $user_id ][] = $user_license->id; + } + } + } + + foreach ( $theme_licenses as $user_id => $user_licenses ) { + if ( is_array( $user_licenses ) ) { + if ( ! isset( $user_license_ids[ $user_id ] ) ) { + $user_id_license_ids_map[ $user_id ] = array(); + } + + foreach ( $user_licenses as $user_license ) { + $all_licenses[] = $user_license; + $user_id_license_ids_map[ $user_id ][] = $user_license->id; + } + } + } + + self::store_user_id_license_ids_map( + $user_id_license_ids_map, + $this->_module_id + ); + + $this->_store_licenses( true, $this->_module_id, $all_licenses ); + } + + /** + * Remove invalid paths. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + private function remove_invalid_paths() { + // Remove invalid path that is still associated with the current slug if there's any. + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + foreach ( $file_slug_map as $plugin_basename => $slug ) { + if ( $slug === $this->_slug && + $plugin_basename !== $this->_plugin_basename && + ! file_exists( $this->get_absolute_path( $plugin_basename ) ) + ) { + unset( $file_slug_map[ $plugin_basename ] ); + self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); + + break; + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $plugin_prev_version + * @param string $plugin_version + */ + function _after_version_update( $plugin_prev_version, $plugin_version ) { + if ( $this->is_theme() ) { + // Expire the cache of the previous tabs since the theme may + // have setting updates. + $this->_cache->expire( 'tabs' ); + $this->_cache->expire( 'tabs_stylesheets' ); + } + } + + /** + * A special migration logic for the $_accounts, executed for all the plugins in the system: + * - Moves some data to the network level storage. + * - If the plugin's connection was skipped for all sites, set the plugin as if it was network skipped. + * - If the plugin's connection was ignored for all sites, don't do anything in terms of the network connection. + * - If the plugin was connected to all sites by the same super-admin, set the plugin as if was network opted-in for all sites. + * - If there's at least one site that was connected by a super-admin, find the "main super-admin" (the one that installed the majority of the plugin installs) and set the plugin as if was network activated with the main super-admin, set all the sites that were skipped or opted-in with a different user to delegated mode. Then, prompt the currently logged super-admin to choose what to do with the ignored sites. + * - If there are any sites in the network which the connection decision was not yet taken for, set this plugin into network activation mode so a super-admin can choose what to do with the rest of the sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function migrate_accounts_to_network() { + $sites = self::get_sites(); + $sites_count = count( $sites ); + $connection_status = array(); + $plugin_slugs = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + self::$_accounts->migrate_to_network( $blog_id ); + + /** + * Build a list of all Freemius powered plugins slugs. + */ + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array(), $blog_id ); + foreach ( $id_slug_type_path_map as $module_id => $data ) { + if ( WP_FS__MODULE_TYPE_PLUGIN === $data['type'] ) { + $plugin_slugs[ $data['slug'] ] = true; + } + } + + $installs = self::get_account_option( 'sites', WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); + + if ( is_array( $installs ) ) { + foreach ( $installs as $slug => $install ) { + if ( ! isset( $connection_status[ $slug ] ) ) { + $connection_status[ $slug ] = array(); + } + + if ( is_object( $install ) && + FS_Site::is_valid_id( $install->id ) && + FS_User::is_valid_id( $install->user_id ) + ) { + $connection_status[ $slug ][ $blog_id ] = $install->user_id; + } + } + } + } + + foreach ( $plugin_slugs as $slug => $true ) { + if ( ! isset( $connection_status[ $slug ] ) ) { + $connection_status[ $slug ] = array(); + } + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( isset( $connection_status[ $slug ][ $blog_id ] ) ) { + continue; + } + + $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + $is_anonymous = $storage->get( 'is_anonymous', null, $blog_id ); + + if ( ! is_null( $is_anonymous ) ) { + // Since 1.1.3 is_anonymous is an array. + if ( is_array( $is_anonymous ) && isset( $is_anonymous['is'] ) ) { + $is_anonymous = $is_anonymous['is']; + } + + if ( is_bool( $is_anonymous ) && true === $is_anonymous ) { + $connection_status[ $slug ][ $blog_id ] = 'skipped'; + } + } + + if ( ! isset( $connection_status[ $slug ][ $blog_id ] ) ) { + $connection_status[ $slug ][ $blog_id ] = 'ignored'; + } + } + } + + $super_admins = array(); + + foreach ( $connection_status as $slug => $blogs_status ) { + $skips = 0; + $ignores = 0; + $connections = 0; + $opted_in_users = array(); + $opted_in_super_admins = array(); + + $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + foreach ( $blogs_status as $blog_id => $status_or_user_id ) { + if ( 'skipped' === $status_or_user_id ) { + $skips ++; + } else if ( 'ignored' === $status_or_user_id ) { + $ignores ++; + } else if ( FS_User::is_valid_id( $status_or_user_id ) ) { + $connections ++; + + if ( ! isset( $opted_in_users[ $status_or_user_id ] ) ) { + $opted_in_users[ $status_or_user_id ] = array(); + } + + $opted_in_users[ $status_or_user_id ][] = $blog_id; + + if ( isset( $super_admins[ $status_or_user_id ] ) || + self::is_super_admin( $status_or_user_id ) + ) { + // Cache super-admin data. + $super_admins[ $status_or_user_id ] = true; + + // Remember opted-in super-admins for the plugin. + $opted_in_super_admins[ $status_or_user_id ] = true; + } + } + } + + $main_super_admin_user_id = null; + $all_migrated = false; + if ( $sites_count == $skips ) { + // All sites were skipped -> network skip by copying the anonymous mode from any of the sites. + $storage->is_anonymous_ms = $storage->is_anonymous; + + $all_migrated = true; + } else if ( $sites_count == $ignores ) { + // Don't do anything, still in activation mode. + + $all_migrated = true; + } else if ( 0 < count( $opted_in_super_admins ) ) { + // Find the super-admin with the majority of installs. + $max_installs_by_super_admin = 0; + foreach ( $opted_in_super_admins as $user_id => $true ) { + $installs_count = count( $opted_in_users[ $user_id ] ); + + if ( $installs_count > $max_installs_by_super_admin ) { + $max_installs_by_super_admin = $installs_count; + $main_super_admin_user_id = $user_id; + } + } + + if ( $sites_count == $connections && 1 == count( $opted_in_super_admins ) ) { + // Super-admin opted-in for all sites in the network. + $storage->is_network_connected = true; + + $all_migrated = true; + } + + // Store network user. + $storage->network_user_id = $main_super_admin_user_id; + + $storage->network_install_blog_id = ( $sites_count == $connections ) ? + // Since all sites are opted-in, associating with the main site. + get_current_blog_id() : + // Associating with the 1st found opted-in site. + $opted_in_users[ $main_super_admin_user_id ][0]; + + /** + * Make sure we migrate the plan ID of the network install, otherwise, if after the migration + * the 1st page that will be loaded is the network level WP Admin and $storage->network_install_blog_id + * is different than the main site of the network, the $this->_site will not be set since the plan_id + * will be empty. + */ + $storage->migrate_to_network(); + self::migrate_install_plan_to_plan_id( $storage, $storage->network_install_blog_id ); + } else { + // At least one opt-in. All the opt-in were created by a non-super-admin. + if ( 0 == $ignores ) { + // All sites were opted-in or skipped, all by non-super-admin. So delegate all. + $storage->store( 'is_delegated_connection', true, true ); + + $all_migrated = true; + } + } + + if ( ! $all_migrated ) { + /** + * Delegate all sites that were: + * 1) Opted-in by a user that is NOT the main-super-admin. + * 2) Skipped and non of the sites was opted-in by a super-admin. If any site was opted-in by a super-admin, there will be a main-super-admin, and we consider the skip as if it was done by that user. + */ + foreach ( $blogs_status as $blog_id => $status_or_user_id ) { + if ( $status_or_user_id == $main_super_admin_user_id ) { + continue; + } + + if ( FS_User::is_valid_id( $status_or_user_id ) || + ( 'skipped' === $status_or_user_id && is_null( $main_super_admin_user_id ) ) + ) { + $storage->store( 'is_delegated_connection', true, $blog_id ); + } + } + } + + + if ( ( $connections + $skips > 0 ) ) { + if ( $ignores > 0 ) { + /** + * If admin already opted-in or skipped in any of the network sites, and also + * have sites which the connection decision was not yet taken, set this plugin + * into network activation mode so the super-admin can choose what to do with + * the rest of the sites. + */ + self::set_network_upgrade_mode( $storage ); + } + } + } + } + + /** + * Set a module into network upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Storage $storage + * + * @return bool + */ + private static function set_network_upgrade_mode( FS_Storage $storage ) { + return $storage->is_network_activation = true; + } + + /** + * Will return true after upgrading to the SDK with the network level integration, + * when the super-admin involvement is required regarding the rest of the sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_upgrade_mode() { + return $this->_storage->get( 'is_network_activation' ); + } + + /** + * Clear flag after the upgrade mode completion. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool True if network activation was on and now completed. + */ + private function network_upgrade_mode_completed() { + if ( fs_is_network_admin() && $this->is_network_upgrade_mode() ) { + $this->_storage->remove( 'is_network_activation' ); + + return true; + } + + return false; + } + + #endregion + + /** + * This action is connected to the 'plugins_loaded' hook and helps to determine + * if this is a new plugin installation or a plugin update. + * + * There are 3 different use-cases: + * 1) New plugin installation right with Freemius: + * 1.1 _activate_plugin_event_hook() will be executed first + * 1.2 Since $this->_storage->is_plugin_new_install is not set, + * and $this->_storage->plugin_last_version is not set, + * $this->_storage->is_plugin_new_install will be set to TRUE. + * 1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will + * be already set to TRUE. + * + * 2) Plugin update, didn't have Freemius before, and now have the SDK: + * 2.1 _activate_plugin_event_hook() will not be executed, because + * the activation hook do NOT fires on updates since WP 3.1. + * 2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will + * be empty, therefore, it will be set to FALSE. + * + * 3) Plugin update, had Freemius in prev version as well: + * 3.1 _version_updates_handler() will be executed 1st, since FS was installed + * before, $this->_storage->plugin_last_version will NOT be empty, + * therefore, $this->_storage->is_plugin_new_install will be set to FALSE. + * 3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is + * already set, therefore, it will not be modified. + * + * Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9. + * + * NOTE: + * The only fallback of this mechanism is if an admin updates a plugin based on use-case #2, + * and then, the next immediate PageView is the plugin's main settings page, it will not + * show the opt-in right away. The reason it will happen is because Freemius execution + * will be turned off till the plugin is fully loaded at least once + * (till $this->_storage->was_plugin_loaded is TRUE). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + */ + function _plugins_loaded() { + // Update flag that plugin was loaded with Freemius at least once. + $this->_storage->was_plugin_loaded = true; + + /** + * Bug fix - only set to false when it's a plugin, due to the + * execution sequence of the theme hooks and our methods, if + * this will be set for themes, Freemius will always assume + * it's a theme update. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.2 + */ + if ( $this->is_plugin() && + ! isset( $this->_storage->is_plugin_new_install ) + ) { + $this->_storage->is_plugin_new_install = false; + } + } + + /** + * Add special parameter to WP admin AJAX calls so when we + * process AJAX calls we can identify its source properly. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + static function _enrich_ajax_url() { + $admin_param = is_network_admin() ? + '_fs_network_admin' : + '_fs_blog_admin'; + ?> + + + + _logger->entrance(); + + if ( is_admin() ) { + add_action( 'plugins_loaded', array( &$this, '_hook_action_links_and_register_account_hooks' ) ); + + if ( $this->is_plugin() ) { + if ( self::is_plugin_install_page() && true !== fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) { + /** + * Unless the `fs_allow_updater_and_dialog` URL param exists and its value is `true`, make + * Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) + * so that they won't interfere with the .org plugins' functionalities on that page (e.g. + * updating of a .org plugin). + */ + add_filter( 'site_transient_update_plugins', array( 'Freemius', '_remove_fs_updates_from_plugin_install_page' ), 10, 2 ); + } else if ( self::is_plugins_page() || self::is_updates_page() ) { + /** + * On the "Plugins" and "Updates" admin pages, if there are premium or non–org-compliant plugins, modify their details dialog URLs (add a Freemius-specific param) so that the SDK can determine if the plugin information dialog should show information from Freemius. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + add_action( 'admin_footer', array( 'Freemius', '_prepend_fs_allow_updater_and_dialog_flag_url_param' ) ); + } + + $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; + + /** + * @since 1.2.2 + * + * Hook to both free and premium version activations to support + * auto deactivation on the other version activation. + */ + register_activation_hook( + $plugin_dir . $this->_free_plugin_basename, + array( &$this, '_activate_plugin_event_hook' ) + ); + + register_activation_hook( + $plugin_dir . $this->premium_plugin_basename(), + array( &$this, '_activate_plugin_event_hook' ) + ); + } else { + add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 ); + + /** + * Include the required hooks to capture the theme settings' page tabs + * and cache them. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + if ( ! $this->_cache->has_valid( 'tabs' ) ) { + add_action( 'admin_footer', array( &$this, '_tabs_capture' ) ); + // Add license activation AJAX callback. + $this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) ); + + add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 ); + } + + add_action( + 'admin_footer', + array( &$this, '_add_freemius_tabs' ), + /** + * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()). + * That's why the priority is 11 while the tabs capture logic is added + * with priority 10. + * + * @author Vova Feldman (@svovaf) + */ + 11 + ); + + add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) ); + } + + /** + * Part of the mechanism to identify new plugin install vs. plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + if ( empty( $this->_storage->was_plugin_loaded ) ) { + /** + * During the plugin activation (not theme), 'plugins_loaded' will be already executed + * when the logic gets here since the activation logic first add the activate plugins, + * then triggers 'plugins_loaded', and only then include the code of the plugin that + * is activated. Which means that _plugins_loaded() will NOT be executed during the + * plugin activation, and that IS intentional. + * + * @author Vova Feldman (@svovaf) + */ + if ( $this->is_plugin() && $this->is_activation_mode( false ) ) { + add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) ); + } else { + // If was activated before, then it was already loaded before. + $this->_plugins_loaded(); + } + } + + if ( ! self::is_ajax() ) { + if ( ! $this->is_addon() ) { + add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY ); + } + } + + if ( $this->_storage->handle_gdpr_admin_notice ) { + add_action( 'init', array( &$this, '_maybe_show_gdpr_admin_notice' ) ); + } + + add_action( 'init', array( &$this, '_maybe_add_gdpr_optin_ajax_handler') ); + } + + if ( $this->is_plugin() ) { + if ( $this->_is_network_active ) { + add_action( 'wpmu_new_blog', array( $this, '_after_new_blog_callback' ), 10, 6 ); + } + + register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) ); + } + + if ( is_multisite() ) { + add_action( 'deactivate_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'archive_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'make_spam_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'deleted_blog', array( &$this, '_after_site_deleted_callback' ), 10, 2 ); + + add_action( 'activate_blog', array( &$this, '_after_site_reactivated_callback' ) ); + add_action( 'unarchive_blog', array( &$this, '_after_site_reactivated_callback' ) ); + add_action( 'make_ham_blog', array( &$this, '_after_site_reactivated_callback' ) ); + } + + if ( $this->is_theme() && + self::is_customizer() && + $this->apply_filters( 'show_customizer_upsell', true ) + ) { + // Register customizer upsell. + add_action( 'customize_register', array( &$this, '_customizer_register' ) ); + } + + add_action( 'admin_init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY ); + + if ( $this->is_theme() ) { + add_action( 'admin_init', array( &$this, '_add_tracking_links' ) ); + } + + add_action( 'admin_init', array( &$this, '_add_license_activation' ) ); + add_action( 'admin_init', array( &$this, '_add_premium_version_upgrade_selection' ) ); + add_action( 'admin_init', array( &$this, '_add_beta_mode_update_handler' ) ); + + $this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) ); + $this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) ); + + if ( $this->_is_network_active && fs_is_network_admin() ) { + $this->add_ajax_action( 'network_activate', array( &$this, '_network_activate_ajax_action' ) ); + } + + $this->add_ajax_action( 'install_premium_version', array( + &$this, + '_install_premium_version_ajax_action' + ) ); + + $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) ); + + $this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) ); + + $this->add_action( 'sdk_version_update', array( &$this, '_sdk_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 ); + + $this->add_action( + 'plugin_version_update', + array( &$this, '_after_version_update' ), + WP_FS__DEFAULT_PRIORITY, + 2 + ); + $this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) ); + + add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); + add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); + add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); + + /** + * Handle request to reset anonymous mode for `get_reconnect_url()`. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + if ( fs_request_is_action( 'reset_anonymous_mode' ) && + $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) + ) { + add_action( 'admin_init', array( &$this, 'connect_again' ) ); + } + } + + /** + * Makes Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) so that + * they won't interfere with the .org plugins' functionalities on that page (e.g. updating of a .org plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @param object $updates + * @param string|null $transient + * + * @return object + */ + static function _remove_fs_updates_from_plugin_install_page( $updates, $transient = null ) { + if ( is_object( $updates ) && isset( $updates->response ) ) { + foreach ( $updates->response as $file => $plugin ) { + if ( isset( $plugin->package ) && false !== strpos( $plugin->package, 'api.freemius' ) ) { + unset( $updates->response[ $file ] ); + } + } + } + + return $updates; + } + + /** + * Prepends the `fs_allow_updater_and_dialog` param to the plugin information URLs to tell the SDK to handle + * the information that is shown on the plugin details dialog that is shown when the relevant link is clicked. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return string + */ + static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { + $slug_basename_map = array(); + foreach ( self::$_instances as $instance ) { + if ( ! $instance->is_plugin() ) { + continue; + } + + $slug_basename_map[ $instance->get_slug() ] = $instance->premium_plugin_basename(); + } + ?> + + is_beta() ) { + $has_any_beta_version = true; + break; + } + } + + if ( $has_any_beta_version ) { + fs_enqueue_local_style( 'fs_plugins', '/admin/plugins.css' ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + static function _maybe_add_beta_label_to_plugins_and_handle_confirmation() { + $beta_data = array(); + + foreach ( self::$_instances as $instance ) { + if ( ! $instance->is_premium() ) { + continue; + } + + /** + * If there's an available beta version update, a confirmation message will be shown when the + * "Update now" link on the "Plugins" or "Themes" page is clicked. + */ + $has_beta_update = $instance->has_beta_update(); + + $is_beta = ( + // The "Beta" label is added separately for themes. + $instance->is_plugin() && + $instance->is_beta() + ); + + if ( ! $is_beta && ! $has_beta_update ) { + continue; + } + + $beta_data[ $instance->get_plugin_basename() ] = array( 'is_installed_version_beta' => $is_beta ); + + if ( ! $has_beta_update ) { + continue; + } + + $beta_data[ $instance->get_plugin_basename() ]['beta_version_update_confirmation_message'] = sprintf( + '%s %s', + sprintf( + fs_esc_attr_inline( + 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', + 'beta-version-update-caution', + $instance->get_slug() + ), + $instance->get_plugin_title() + ), + fs_esc_attr_inline( 'Would you like to proceed with the update?', 'update-confirmation', $instance->get_slug() ) + ); + } + + if ( empty( $beta_data ) ) { + return; + } + ?> + + _free_plugin_basename ] ); + unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] ); + + update_option( 'uninstall_plugins', $uninstallable_plugins ); + } + + /** + * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates. + * + * @param bool $store_prev_path + */ + private function clear_module_main_file_cache( $store_prev_path = true ) { + if ( ! isset( $this->_storage->plugin_main_file ) || + empty( $this->_storage->plugin_main_file->path ) + ) { + return; + } + + if ( ! $store_prev_path ) { + /** + * Storing the previous path is not needed when clearing the cache after an SDK version update since + * the main purpose of the cache clearing in that event is to correct a wrong plugin main file path + * which causes data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + unset( $this->_storage->plugin_main_file->path ); + } else { + $plugin_main_file = clone $this->_storage->plugin_main_file; + + // Store cached path (2nd layer cache). + $plugin_main_file->prev_path = $plugin_main_file->path; + + // Clear cached path. + unset( $plugin_main_file->path ); + + $this->_storage->plugin_main_file = $plugin_main_file; + } + + /** + * Clear global cached path. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' ); + unset( $id_slug_type_path_map[ $this->_module_id ]['path'] ); + self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + function _hook_action_links_and_register_account_hooks() { + add_action( 'admin_init', array( &$this, '_add_tracking_links' ) ); + + if ( self::is_plugins_page() && $this->is_plugin() ) { + $this->hook_plugin_action_links(); + } + + $this->_register_account_hooks(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _register_account_hooks() { + if ( ! is_admin() ) { + return; + } + + /** + * Always show the deactivation feedback form since we added + * automatic free version deactivation upon premium code activation. + * + * @since 1.2.1.6 + */ + $this->add_ajax_action( + 'submit_uninstall_reason', + array( &$this, '_submit_uninstall_reason_action' ) + ); + + $this->add_ajax_action( + 'cancel_subscription_or_trial', + array( &$this, 'cancel_subscription_or_trial_ajax_action' ) + ); + + if ( ! $this->is_addon() || $this->is_parent_plugin_installed() ) { + if ( ( $this->is_plugin() && self::is_plugins_page() ) || + ( $this->is_theme() && self::is_themes_page() ) + ) { + add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) ); + } + } + } + + /** + * Leverage backtrace to find caller plugin file path. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool $is_init Is initiation sequence. + * + * @return string + */ + private function _find_caller_plugin_file( $is_init = false ) { + // Try to load the cached value of the file path. + if ( isset( $this->_storage->plugin_main_file ) ) { + $plugin_main_file = $this->_storage->plugin_main_file; + if ( ! empty( $plugin_main_file->path ) ) { + $absolute_path = $this->get_absolute_path( $plugin_main_file->path ); + if ( file_exists( $absolute_path ) ) { + return $absolute_path; + } + } + } + + /** + * @since 1.2.1 + * + * `clear_module_main_file_cache()` is clearing the plugin's cached path on + * deactivation. Therefore, if any plugin/theme was initiating `Freemius` + * with that plugin's slug, it was overriding the empty plugin path with a wrong path. + * + * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path` + * when the class instantiator isn't the module. + */ + if ( ! $is_init ) { + // Fetch prev path cache. + if ( isset( $this->_storage->plugin_main_file ) && + ! empty( $this->_storage->plugin_main_file->prev_path ) + ) { + $absolute_path = $this->get_absolute_path( $this->_storage->plugin_main_file->prev_path ); + if ( file_exists( $absolute_path ) ) { + return $absolute_path; + } + } + + wp_die( + $this->get_text_inline( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', 'failed-finding-main-path' ) . + " Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";", + $this->get_text_inline( 'Error', 'error' ), + array( 'back_link' => true ) + ); + } + + /** + * @since 1.2.1 + * + * Only the original instantiator that calls dynamic_init can modify the module's path. + */ + // Find caller module. + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_storage->plugin_main_file = (object) array( + 'path' => $id_slug_type_path_map[ $this->_module_id ]['path'], + ); + + return $this->get_absolute_path( $id_slug_type_path_map[ $this->_module_id ]['path'] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string $path + * + * @return string + */ + private function get_relative_path( $path ) { + $module_root_dir = $this->get_module_root_dir_path(); + if ( 0 === strpos( $path, $module_root_dir ) ) { + $path = substr( $path, strlen( $module_root_dir ) ); + } + + return $path; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string $path + * @param string|bool $module_type + * + * @return string + */ + private function get_absolute_path( $path, $module_type = false ) { + $module_root_dir = $this->get_module_root_dir_path( $module_type ); + if ( 0 !== strpos( $path, $module_root_dir ) ) { + $path = fs_normalize_path( $module_root_dir . $path ); + } + + return $path; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string|bool $module_type + * + * @return string + */ + private function get_module_root_dir_path( $module_type = false ) { + $is_plugin = empty( $module_type ) ? + $this->is_plugin() : + ( WP_FS__MODULE_TYPE_PLUGIN === $module_type ); + + return fs_normalize_path( trailingslashit( $is_plugin ? + WP_PLUGIN_DIR : + get_theme_root( get_stylesheet() ) ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param number $module_id + * @param string $slug + * + * @since 1.2.2 + */ + private function store_id_slug_type_path_map( $module_id, $slug ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + + $store_option = false; + + if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) { + $id_slug_type_path_map[ $module_id ] = array( + 'slug' => $slug + ); + + $store_option = true; + } + + if ( empty( $id_slug_type_path_map[ $module_id ]['path'] ) || + /** + * This verification is for cases when suddenly the same module + * is installed but with a different folder name. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + */ + ! file_exists( $this->get_absolute_path( + $id_slug_type_path_map[ $module_id ]['path'], + $id_slug_type_path_map[ $module_id ]['type'] + ) ) + ) { + $caller_main_file_and_type = $this->get_caller_main_file_and_type(); + + $id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type; + $id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path; + + $store_option = true; + } + + if ( $store_option ) { + self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); + } + } + + /** + * Identifies the caller type: plugin or theme. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when + * add-ons are relying on loading the SDK from the parent module, and also allows themes including the + * SDK an internal file instead of directly from functions.php. + * @since 1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic. + */ + private function get_caller_main_file_and_type() { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins( true ); + $all_plugins_paths = array(); + + // Get active plugin's main files real full names (might be symlinks). + foreach ( $all_plugins as $relative_path => $data ) { + if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) { + /** + * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they + * can't really include the SDK. + * + * @author Vova Feldman + * @since 1.2.1.7 + */ + continue; + } + + $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); + } + + $caller_file_candidate = false; + $caller_map = array(); + $module_type = WP_FS__MODULE_TYPE_PLUGIN; + $themes_dir = fs_normalize_path( get_theme_root( get_stylesheet() ) ); + $plugin_dir_to_skip = false; + + for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { + if ( empty( $bt[ $i ]['file'] ) ) { + continue; + } + + if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) { + // If file same as the prev file in the stack, skip it. + continue; + } + + if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array( + 'do_action', + 'apply_filter', + // The string split is stupid, but otherwise, theme check + // throws info notices. + 'requir' . 'e_once', + 'requir' . 'e', + 'includ' . 'e_once', + 'includ' . 'e', + 'install_and_activate_plugin', + 'try_activate_plugin', + 'activate_plugin' + ) ) + ) { + if ( 'activate_plugin' === $bt[ $i ]['function'] ) { + /** + * Store the directory of the activator plugin so that any other file that starts with it + * cannot be mistakenly chosen as a candidate caller file. + * + * @author Leo Fajardo + * + * @since 2.3.0 + */ + $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); + + foreach ( $all_plugins_paths as $plugin_path ) { + $plugin_dir = fs_normalize_path( dirname( $plugin_path ) . '/' ); + if ( false !== strpos( $caller_file_path, $plugin_dir ) ) { + $plugin_dir_to_skip = $plugin_dir; + + break; + } + } + } + + // Ignore call stack hooks and files inclusion. + continue; + } + + $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); + + if ( ! empty( $plugin_dir_to_skip ) ) { + /** + * Skip if it's an activator plugin file to avoid mistakenly choosing it as a candidate caller file. + * + * @author Leo Fajardo + * + * @since 2.3.0 + */ + if ( 0 === strpos( $caller_file_path, $plugin_dir_to_skip ) ) { + continue; + } + } + + if ( 'functions.php' === basename( $caller_file_path ) ) { + /** + * 1. Assumes that theme's starting execution file is functions.php. + * 2. This complex logic fixes symlink issues (e.g. with Vargant). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + */ + + if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) { + $module_type = WP_FS__MODULE_TYPE_THEME; + + /** + * Relative path of the theme, e.g.: + * `my-theme/functions.php` + * + * @author Leo Fajardo (@leorw) + */ + $caller_file_candidate = basename( dirname( $caller_file_path ) ) . + '/' . + basename( $caller_file_path ); + + continue; + } + } + + $caller_file_hash = md5( $caller_file_path ); + + if ( ! isset( $caller_map[ $caller_file_hash ] ) ) { + foreach ( $all_plugins_paths as $plugin_path ) { + if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) { + $caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path ); + break; + } + } + } + + if ( isset( $caller_map[ $caller_file_hash ] ) ) { + $module_type = WP_FS__MODULE_TYPE_PLUGIN; + $caller_file_candidate = plugin_basename( $caller_map[ $caller_file_hash ] ); + } + } + + return (object) array( + 'module_type' => $module_type, + 'path' => $caller_file_candidate + ); + } + + #---------------------------------------------------------------------------------- + #region Deactivation Feedback Form + #---------------------------------------------------------------------------------- + + /** + * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins + * page. + * + * @author Vova Feldman (@svovaf) + * @author Leo Fajardo (@leorw) + * + * @since 1.1.2 + */ + function _add_deactivation_feedback_dialog_box() { + $subscription_cancellation_dialog_box_template_params = $this->apply_filters( 'show_deactivation_subscription_cancellation', true ) ? + $this->_get_subscription_cancellation_dialog_box_template_params() : + array(); + + /** + * @since 2.3.0 Developers can optionally hide the deactivation feedback form using the 'show_deactivation_feedback_form' filter. + */ + $show_deactivation_feedback_form = true; + if ( $this->has_filter( 'show_deactivation_feedback_form' ) ) { + $show_deactivation_feedback_form = $this->apply_filters( 'show_deactivation_feedback_form', true ); + } else if ( $this->is_addon() ) { + /** + * If the add-on's 'show_deactivation_feedback_form' is not set, try to inherit the value from the parent. + */ + $show_deactivation_feedback_form = $this->get_parent_instance()->apply_filters( 'show_deactivation_feedback_form', true ); + } + + $uninstall_confirmation_message = $this->apply_filters( 'uninstall_confirmation_message', '' ); + + if ( + empty( $subscription_cancellation_dialog_box_template_params ) && + ! $show_deactivation_feedback_form && + empty( $uninstall_confirmation_message ) + ) { + return; + } + + $vars = array( 'id' => $this->_module_id ); + + if ( $show_deactivation_feedback_form ) { + /* Check the type of user: + * 1. Long-term (long-term) + * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term). + * 3. Short-term (short-term) + */ + $is_long_term_user = true; + + // Check if the site is at least 2 days old. + $time_installed = $this->_storage->install_timestamp; + + // Difference in seconds. + $date_diff = time() - $time_installed; + + // Convert seconds to days. + $date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) ); + + if ( $date_diff_days < 2 ) { + $is_long_term_user = false; + } + + $is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user ); + + if ( $is_long_term_user ) { + $user_type = 'long-term'; + } else { + if ( ! $this->is_registered() && ! $this->is_anonymous() ) { + $user_type = 'non-registered-and-non-anonymous-short-term'; + } else { + $user_type = 'short-term'; + } + } + + $uninstall_reasons = $this->_get_uninstall_reasons( $user_type ); + + $vars['reasons'] = $uninstall_reasons; + } + + $vars['subscription_cancellation_dialog_box_template_params'] = &$subscription_cancellation_dialog_box_template_params; + $vars['show_deactivation_feedback_form'] = $show_deactivation_feedback_form; + $vars['uninstall_confirmation_message'] = $uninstall_confirmation_message; + + /** + * Load the HTML template for the deactivation feedback dialog box. + * + * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin. + */ + fs_require_template( 'forms/deactivation/form.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @param string $user_type + * + * @return array The uninstall reasons for the specified user type. + */ + function _get_uninstall_reasons( $user_type = 'long-term' ) { + $module_type = $this->_module_type; + + $internal_message_template_var = array( + 'id' => $this->_module_id + ); + + $plan = $this->get_plan(); + + if ( $this->is_registered() && is_object( $plan ) && $plan->has_technical_support() ) { + $contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var ); + } else { + $contact_support_template = ''; + } + + $reason_found_better_plugin = array( + 'id' => self::REASON_FOUND_A_BETTER_PLUGIN, + 'text' => sprintf( $this->get_text_inline( 'I found a better %s', 'reason-found-a-better-plugin' ), $module_type ), + 'input_type' => 'textfield', + 'input_placeholder' => sprintf( $this->get_text_inline( "What's the %s's name?", 'placeholder-plugin-name' ), $module_type ), + ); + + $reason_temporary_deactivation = array( + 'id' => self::REASON_TEMPORARY_DEACTIVATION, + 'text' => sprintf( + $this->get_text_inline( "It's a temporary %s. I'm just debugging an issue.", 'reason-temporary-x' ), + strtolower( $this->is_plugin() ? + $this->get_text_inline( 'Deactivation', 'deactivation' ) : + $this->get_text_inline( 'Theme Switch', 'theme-switch' ) + ) + ), + 'input_type' => '', + 'input_placeholder' => '' + ); + + $reason_other = array( + 'id' => self::REASON_OTHER, + 'text' => $this->get_text_inline( 'Other', 'reason-other' ), + 'input_type' => 'textfield', + 'input_placeholder' => '' + ); + + $long_term_user_reasons = array( + array( + 'id' => self::REASON_NO_LONGER_NEEDED, + 'text' => sprintf( $this->get_text_inline( 'I no longer need the %s', 'reason-no-longer-needed' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + $reason_found_better_plugin, + array( + 'id' => self::REASON_NEEDED_FOR_A_SHORT_PERIOD, + 'text' => sprintf( $this->get_text_inline( 'I only needed the %s for a short period', 'reason-needed-for-a-short-period' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + array( + 'id' => self::REASON_BROKE_MY_SITE, + 'text' => sprintf( $this->get_text_inline( 'The %s broke my site', 'reason-broke-my-site' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ), + array( + 'id' => self::REASON_SUDDENLY_STOPPED_WORKING, + 'text' => sprintf( $this->get_text_inline( 'The %s suddenly stopped working', 'reason-suddenly-stopped-working' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ) + ); + + if ( $this->is_paying() ) { + $long_term_user_reasons[] = array( + 'id' => self::REASON_CANT_PAY_ANYMORE, + 'text' => $this->get_text_inline( "I can't pay for it anymore", 'reason-cant-pay-anymore' ), + 'input_type' => 'textfield', + 'input_placeholder' => $this->get_text_inline( 'What price would you feel comfortable paying?', 'placeholder-comfortable-price' ) + ); + } + + $reason_dont_share_info = array( + 'id' => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION, + 'text' => $this->get_text_inline( "I don't like to share my information with you", 'reason-dont-like-to-share-my-information' ), + 'input_type' => '', + 'input_placeholder' => '' + ); + + /** + * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the + * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in + * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is + * enabled and the user's account is currently not in pending activation state (similar to the way the Skip + * button in the opt-in form is shown/hidden). + */ + if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) { + $reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var ); + } + + $uninstall_reasons = array( + 'long-term' => $long_term_user_reasons, + 'non-registered-and-non-anonymous-short-term' => array( + array( + 'id' => self::REASON_DIDNT_WORK, + 'text' => sprintf( $this->get_text_inline( "The %s didn't work", 'reason-didnt-work' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + $reason_dont_share_info, + $reason_found_better_plugin + ), + 'short-term' => array( + array( + 'id' => self::REASON_COULDNT_MAKE_IT_WORK, + 'text' => $this->get_text_inline( "I couldn't understand how to make it work", 'reason-couldnt-make-it-work' ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ), + $reason_found_better_plugin, + array( + 'id' => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE, + 'text' => sprintf( $this->get_text_inline( "The %s is great, but I need specific feature that you don't support", 'reason-great-but-need-specific-feature' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( 'What feature?', 'placeholder-feature' ) + ), + array( + 'id' => self::REASON_NOT_WORKING, + 'text' => sprintf( $this->get_text_inline( 'The %s is not working', 'reason-not-working' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( "Kindly share what didn't work so we can fix it for future users...", 'placeholder-share-what-didnt-work' ) + ), + array( + 'id' => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR, + 'text' => $this->get_text_inline( "It's not what I was looking for", 'reason-not-what-i-was-looking-for' ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( "What you've been looking for?", 'placeholder-what-youve-been-looking-for' ) + ), + array( + 'id' => self::REASON_DIDNT_WORK_AS_EXPECTED, + 'text' => sprintf( $this->get_text_inline( "The %s didn't work as expected", 'reason-didnt-work-as-expected' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( 'What did you expect?', 'placeholder-what-did-you-expect' ) + ) + ) + ); + + // Randomize the reasons for the current user type. + shuffle( $uninstall_reasons[ $user_type ] ); + + // Keep the following reasons as the last items in the list. + $uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation; + $uninstall_reasons[ $user_type ][] = $reason_other; + + $uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons ); + + return $uninstall_reasons[ $user_type ]; + } + + /** + * Called after the user has submitted his reason for deactivating the plugin. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + */ + function _submit_uninstall_reason_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'submit_uninstall_reason' ); + + $reason_id = fs_request_get( 'reason_id' ); + + // Check if the given reason ID is an unsigned integer. + if ( ! ctype_digit( $reason_id ) ) { + exit; + } + + $reason_info = trim( fs_request_get( 'reason_info', '' ) ); + if ( ! empty( $reason_info ) ) { + $reason_info = substr( $reason_info, 0, 128 ); + } + + $reason = (object) array( + 'id' => $reason_id, + 'info' => $reason_info, + 'is_anonymous' => fs_request_get_bool( 'is_anonymous' ) + ); + + $this->_storage->store( 'uninstall_reason', $reason ); + + /** + * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do + * not support uninstall hook. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( $this->is_theme() ) { + if ( $this->is_premium() && ! $this->has_active_valid_license() ) { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + $this->_uninstall_plugin_event( false ); + $this->remove_sdk_reference(); + } + + // Print '1' for successful operation. + echo 1; + exit; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + */ + function cancel_subscription_or_trial_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'cancel_subscription_or_trial' ); + + $result = $this->cancel_subscription_or_trial( fs_request_get( 'plugin_id', $this->get_id() ), false ); + + if ( $this->is_api_error( $result ) ) { + $this->shoot_ajax_failure( $result->error->message ); + } + + $this->shoot_ajax_success(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param number $plugin_id + * + * @return object + */ + private function cancel_subscription_or_trial( $plugin_id ) { + $fs = null; + if ( $plugin_id == $this->get_id() ) { + $fs = $this; + } else if ( $this->is_addon_activated( $plugin_id ) ) { + $fs = self::get_instance_by_id( $plugin_id ); + } + + $result = null; + + if ( ! is_null( $fs ) ) { + $result = $fs->is_paid_trial() ? + $fs->_cancel_trial() : + $fs->_downgrade_site(); + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _delete_theme_update_data_action() { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Instance + #---------------------------------------------------------------------------------- + + /** + * Main singleton instance. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param number $module_id + * @param string|bool $slug + * @param bool $is_init Is initiation sequence. + * + * @return Freemius|false + */ + static function instance( $module_id, $slug = false, $is_init = false ) { + if ( empty( $module_id ) ) { + return false; + } + + /** + * Load the essential static data prior to initiating FS_Plugin_Manager since there's an essential MS network migration logic that needs to be executed prior to the initiation. + */ + self::_load_required_static(); + + if ( ! is_numeric( $module_id ) ) { + if ( ! $is_init && true === $slug ) { + $is_init = true; + } + + $slug = $module_id; + + $module = FS_Plugin_Manager::instance( $slug )->get(); + + if ( is_object( $module ) ) { + $module_id = $module->id; + } + } + + $key = 'm_' . $module_id; + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init ); + } + + return self::$_instances[ $key ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $addon_id + * + * @return bool + */ + private static function has_instance( $addon_id ) { + return isset( self::$_instances[ 'm_' . $addon_id ] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string|number $id_or_slug + * @param string $module_type + * + * @return number|false + */ + private static function get_module_id( $id_or_slug, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { + if ( is_numeric( $id_or_slug ) ) { + return $id_or_slug; + } + + foreach ( self::$_instances as $instance ) { + // Also check the module type since there can be a plugin and a theme with the same slug. + if ( ( $module_type === $instance->get_module_type() ) && ( $id_or_slug === $instance->get_slug() ) ) { + return $instance->get_id(); + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $id + * + * @return false|Freemius + */ + static function get_instance_by_id( $id ) { + return isset ( self::$_instances[ 'm_' . $id ] ) ? + self::$_instances[ 'm_' . $id ] : + false; + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $plugin_file + * @param string $module_type + * + * @return false|Freemius + */ + static function get_instance_by_file( $plugin_file, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { + $slug = self::find_slug_by_basename( $plugin_file ); + + return ( false !== $slug ) ? + self::instance( self::get_module_id( $slug, $module_type ) ) : + false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return false|Freemius + */ + function get_parent_instance() { + return self::get_instance_by_id( $this->_plugin->parent_plugin_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return false|Freemius + */ + function get_addon_instance( $id_or_slug ) { + $addon_id = self::get_module_id( $id_or_slug ); + + return self::instance( $addon_id ); + } + + #endregion ------------------------------------------------------------------ + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_parent_plugin_installed() { + $is_active = self::has_instance( $this->_plugin->parent_plugin_id ); + + if ( $is_active ) { + return true; + } + + /** + * Parent module might be a theme. If that's the case, the add-on's FS + * instance will be loaded prior to the theme's FS instance, therefore, + * we need to check if it's active with a "look ahead". + * + * @author Vova Feldman + * @since 1.2.2.3 + */ + global $fs_active_plugins; + if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) { + $active_theme = wp_get_theme(); + + foreach ( $fs_active_plugins->plugins as $sdk => $module ) { + if ( WP_FS__MODULE_TYPE_THEME === $module->type ) { + if ( $module->plugin_path == $active_theme->get_stylesheet() ) { + // Parent module is a theme and it's currently active. + return true; + } + } + } + } + + return false; + } + + /** + * Check if add-on parent plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_parent_in_activation() { + $parent_fs = $this->get_parent_instance(); + if ( ! is_object( $parent_fs ) ) { + return false; + } + + return ( $parent_fs->is_activation_mode() ); + } + + /** + * Is plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $and_on + * + * @return bool + */ + function is_activation_mode( $and_on = true ) { + return fs_is_network_admin() ? + $this->is_network_activation_mode( $and_on ) : + $this->is_site_activation_mode( $and_on ); + } + + /** + * Is plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $and_on + * + * @return bool + */ + function is_site_activation_mode( $and_on = true ) { + return ( + ( $this->is_on() || ! $and_on ) && + ( + ( $this->is_premium() && true === $this->_storage->require_license_activation ) || + ( + ( ! $this->is_registered() || + ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) && + ( ! $this->is_enable_anonymous() || + ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) ) + ) + ) + ); + } + + /** + * Checks if the SDK in network activation mode. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $and_on + * + * @return bool + */ + private function is_network_activation_mode( $and_on = true ) { + if ( ! $this->_is_network_active ) { + // Not network activated. + return false; + } + + if ( $this->is_network_upgrade_mode() ) { + // Special flag to enforce network activation mode to decide what to do with the sites that are not yet opted-in nor skipped. + return true; + } + + if ( ! $this->is_site_activation_mode( $and_on ) ) { + // Whether the context is single site or the network, if the plugin is no longer in activation mode then it is not in network activation mode as well. + return false; + } + + if ( $this->is_network_delegated_connection() ) { + // Super-admin delegated the connection to the site admins -> not activation mode. + return false; + } + + if ( $this->is_network_anonymous() && true !== $this->_storage->require_license_activation ) { + // Super-admin skipped the connection network wide -> not activation mode. + return false; + } + + if ( $this->is_network_registered() ) { + // Super-admin connected at least one site -> not activation mode. + return false; + } + + return true; + } + + /** + * Check if current page is the opt-in/pending-activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return bool + */ + function is_activation_page() { + if ( $this->_menu->is_main_settings_page() ) { + return true; + } + + if ( ! $this->is_activation_mode() ) { + return false; + } + + // Check if current page is matching the activation page. + return $this->is_matching_url( $this->get_activation_url() ); + } + + /** + * Check if URL path's are matching and that all querystring + * arguments of the $sub_url exist in the $url with the same values. + * + * WARNING: + * 1. This method doesn't check if the sub/domain are matching. + * 2. Ignore case sensitivity. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $sub_url + * @param string $url If argument is not set, check if the sub_url matching the current's page URL. + * + * @return bool + */ + private function is_matching_url( $sub_url, $url = '' ) { + if ( empty( $url ) ) { + $url = $_SERVER['REQUEST_URI']; + } + + $url = strtolower( $url ); + $sub_url = strtolower( $sub_url ); + + if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) { + // Different path - DO NOT OVERRIDE PAGE. + return false; + } + + $url_params = array(); + parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params ); + + $sub_url_params = array(); + parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params ); + + foreach ( $sub_url_params as $key => $val ) { + if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) { + // Not matching query string - DO NOT OVERRIDE PAGE. + return false; + } + } + + return true; + } + + /** + * Get the basenames of all active plugins for specific blog. Including network activated plugins. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return string[] + */ + private static function get_active_plugins_basenames( $blog_id = 0 ) { + if ( is_multisite() && $blog_id > 0 ) { + $active_basenames = get_blog_option( $blog_id, 'active_plugins' ); + } else { + $active_basenames = get_option( 'active_plugins' ); + } + + if ( ! is_array( $active_basenames ) ) { + $active_basenames = array(); + } + + if ( is_multisite() ) { + $network_active_basenames = get_site_option( 'active_sitewide_plugins' ); + + if ( is_array( $network_active_basenames ) && ! empty( $network_active_basenames ) ) { + $active_basenames = array_merge( $active_basenames, array_keys( $network_active_basenames ) ); + } + } + + return $active_basenames; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param int $blog_id + * + * @return array + */ + static function get_active_plugins_directories_map( $blog_id = 0 ) { + $active_basenames = self::get_active_plugins_basenames( $blog_id ); + + $map = array(); + + foreach ( $active_basenames as $active_basename ) { + $active_basename = fs_normalize_path( $active_basename ); + + if ( false === strpos( $active_basename, '/' ) ) { + continue; + } + + $map[ dirname( $active_basename ) ] = true; + } + + return $map; + } + + /** + * Get collection of all active plugins. Including network activated plugins. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param int $blog_id Since 2.0.0 + * + * @return array[string]array + */ + private static function get_active_plugins( $blog_id = 0 ) { + self::require_plugin_essentials(); + + $active_plugin = array(); + $all_plugins = fs_get_plugins(); + $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); + + foreach ( $active_plugins_basenames as $plugin_basename ) { + $active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ]; + } + + return $active_plugin; + } + + /** + * Get collection of all site active plugins for a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return array[string]array + */ + private static function get_site_active_plugins( $blog_id = 0 ) { + $active_basenames = ( is_multisite() && $blog_id > 0 ) ? + get_blog_option( $blog_id, 'active_plugins' ) : + get_option( 'active_plugins' ); + + $active = array(); + + if ( ! is_array( $active_basenames ) ) { + return $active; + } + + foreach ( $active_basenames as $basename ) { + $active[ $basename ] = array( + 'is_active' => true, + 'Version' => '1.0', // Dummy version. + 'slug' => self::get_plugin_slug( $basename ), + ); + } + + return $active; + } + + /** + * Get collection of all plugins with their activation status for a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @param int $blog_id Since 2.0.0 + * + * @return array Key is the plugin file path and the value is an array of the plugin data. + */ + private static function get_all_plugins( $blog_id = 0 ) { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins(); + + $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); + + foreach ( $all_plugins as $basename => &$data ) { + // By default set to inactive (next foreach update the active plugins). + $data['is_active'] = false; + // Enrich with plugin slug. + $data['slug'] = self::get_plugin_slug( $basename ); + } + + // Flag active plugins. + foreach ( $active_plugins_basenames as $basename ) { + if ( isset( $all_plugins[ $basename ] ) ) { + $all_plugins[ $basename ]['is_active'] = true; + } + } + + return $all_plugins; + } + + /** + * Get collection of all plugins and if they are network level activated. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array Key is the plugin basename and the value is an array of the plugin data. + */ + private static function get_network_plugins() { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins(); + + $network_active_basenames = is_multisite() ? + get_site_option( 'active_sitewide_plugins' ) : + array(); + + foreach ( $all_plugins as $basename => &$data ) { + // By default set to inactive (next foreach update the active plugins). + $data['is_active'] = false; + // Enrich with plugin slug. + $data['slug'] = self::get_plugin_slug( $basename ); + } + + // Flag active plugins. + foreach ( $network_active_basenames as $basename ) { + if ( isset( $all_plugins[ $basename ] ) ) { + $all_plugins[ $basename ]['is_active'] = true; + } + } + + return $all_plugins; + } + + /** + * Cached result of get_site_transient( 'update_plugins' ) + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @var object + */ + private static $_plugins_info; + + /** + * Helper function to get specified plugin's slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @param $basename + * + * @return string + */ + private static function get_plugin_slug( $basename ) { + if ( ! isset( self::$_plugins_info ) ) { + self::$_plugins_info = get_site_transient( 'update_plugins' ); + } + + $slug = ''; + + if ( is_object( self::$_plugins_info ) ) { + if ( isset( self::$_plugins_info->no_update ) && + isset( self::$_plugins_info->no_update[ $basename ] ) && + ! empty( self::$_plugins_info->no_update[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->no_update[ $basename ]->slug; + } else if ( isset( self::$_plugins_info->response ) && + isset( self::$_plugins_info->response[ $basename ] ) && + ! empty( self::$_plugins_info->response[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->response[ $basename ]->slug; + } + } + + if ( empty( $slug ) ) { + // Try to find slug from FS data. + $slug = self::find_slug_by_basename( $basename ); + } + + if ( empty( $slug ) ) { + // Fallback to plugin's folder name. + $slug = dirname( $basename ); + } + + return $slug; + } + + private static $_statics_loaded = false; + + /** + * Load static resources. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private static function _load_required_static() { + if ( self::$_statics_loaded ) { + return; + } + + self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + self::$_static_logger->entrance(); + + self::$_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); + + if ( is_multisite() ) { + $has_skipped_migration = ( + // 'id_slug_type_path_map' - was never stored on older versions, therefore, not exists on the site level. + null === self::$_accounts->get_option( 'id_slug_type_path_map', null, false ) && + // 'file_slug_map' stored on the site level, so it was running an SDK version before it was integrated with MS-network. + null !== self::$_accounts->get_option( 'file_slug_map', null, false ) + ); + + /** + * If the file_slug_map exists on the site level but doesn't exist on the + * network level storage, it means that we need to process the storage with migration. + * + * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, file_slug_map will be already set in the network level storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( + ( $has_skipped_migration && true !== self::$_accounts->get_option( 'ms_migration_complete', false, true ) ) || + ( null === self::$_accounts->get_option( 'file_slug_map', null, true ) && + null !== self::$_accounts->get_option( 'file_slug_map', null, false ) ) + ) { + self::migrate_options_to_network(); + } + } + + self::$_global_admin_notices = FS_Admin_Notices::instance( 'global' ); + + if ( ! WP_FS__DEMO_MODE ) { + add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( + 'Freemius', + '_add_debug_section' + ) ); + } + + add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) ); + + self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) ); + + self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) ); + + self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) ); + + if ( 0 == did_action( 'plugins_loaded' ) ) { + add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 ); + } + + add_action( 'admin_footer', array( 'Freemius', '_enrich_ajax_url' ) ); + add_action( 'admin_footer', array( 'Freemius', '_open_support_forum_in_new_page' ) ); + + if ( self::is_plugins_page() || self::is_themes_page() ) { + add_action( 'admin_print_footer_scripts', array( 'Freemius', '_maybe_add_beta_label_styles' ), 9 ); + + /** + * Specifically use this hook so that the JS event handlers will work properly on the "Themes" + * page. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + add_action( 'admin_footer-' . self::get_current_page(), array( 'Freemius', '_maybe_add_beta_label_to_plugins_and_handle_confirmation') ); + } + + self::$_statics_loaded = true; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 2.1.3 + */ + private static function migrate_options_to_network() { + self::migrate_accounts_to_network(); + + // Migrate API options from site level to network level. + $api_network_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true ); + $api_network_options->migrate_to_network(); + + // Migrate API cache to network level storage. + FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME )->migrate_to_network(); + + self::$_accounts->set_option( 'ms_migration_complete', true, true ); + } + + #---------------------------------------------------------------------------------- + #region Localization + #---------------------------------------------------------------------------------- + + /** + * Load framework's text domain. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + */ + static function _load_textdomain() { + if ( ! is_admin() ) { + return; + } + + global $fs_active_plugins; + + // Works both for plugins and themes. + load_plugin_textdomain( + 'freemius', + false, + $fs_active_plugins->newest->sdk_path . '/languages/' + ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Debugging + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _add_debug_section() { + if ( ! is_super_admin() ) { + // Add debug page only for super-admins. + return; + } + + self::$_static_logger->entrance(); + + $title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION ); + + if ( WP_FS__DEV_MODE ) { + // Add top-level debug menu item. + $hook = FS_Admin_Menu_Manager::add_page( + $title, + $title, + 'manage_options', + 'freemius', + array( 'Freemius', '_debug_page_render' ) + ); + } else { + // Add hidden debug page. + $hook = FS_Admin_Menu_Manager::add_subpage( + null, + $title, + $title, + 'manage_options', + 'freemius', + array( 'Freemius', '_debug_page_render' ) + ); + } + + if ( ! empty( $hook ) ) { + add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + static function _toggle_debug_mode() { + if ( ! is_super_admin() ) { + return; + } + + $is_on = fs_request_get( 'is_on', false, 'post' ); + + if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) { + update_option( 'fs_debug_mode', $is_on ); + + // Turn on/off storage logging. + FS_Logger::_set_storage_logging( ( 1 == $is_on ) ); + } + + exit; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + static function _get_debug_log() { + $logs = FS_Logger::load_db_logs( + fs_request_get( 'filters', false, 'post' ), + ! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200, + ! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0 + ); + + self::shoot_ajax_success( $logs ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + static function _get_db_option() { + check_admin_referer( 'fs_get_db_option' ); + + $option_name = fs_request_get( 'option_name' ); + + if ( ! is_super_admin() || + ! fs_starts_with( $option_name, 'fs_' ) + ) { + self::shoot_ajax_failure(); + } + + $value = get_option( $option_name ); + + $result = array( + 'name' => $option_name, + ); + + if ( false !== $value ) { + if ( ! is_string( $value ) ) { + $value = json_encode( $value ); + } + + $result['value'] = $value; + } + + self::shoot_ajax_success( $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + static function _set_db_option() { + check_admin_referer( 'fs_set_db_option' ); + + $option_name = fs_request_get( 'option_name' ); + + if ( ! is_super_admin() || + ! fs_starts_with( $option_name, 'fs_' ) + ) { + self::shoot_ajax_failure(); + } + + $option_value = fs_request_get( 'option_value' ); + + if ( ! empty( $option_value ) ) { + update_option( $option_name, $option_value ); + } + + self::shoot_ajax_success(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _debug_page_actions() { + self::_clean_admin_content_section(); + + if ( fs_request_is_action( 'restart_freemius' ) ) { + check_admin_referer( 'restart_freemius' ); + + if ( ! is_multisite() ) { + // Clear accounts data. + self::$_accounts->clear( null, true ); + } else { + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + self::$_accounts->clear( $blog_id, true ); + } + + // Clear network level storage. + self::$_accounts->clear( true, true ); + } + + // Clear SDK reference cache. + delete_option( 'fs_active_plugins' ); + } else if ( fs_request_is_action( 'clear_updates_data' ) ) { + check_admin_referer( 'clear_updates_data' ); + + if ( ! is_multisite() ) { + set_site_transient( 'update_plugins', null ); + set_site_transient( 'update_themes', null ); + } else { + $current_blog_id = get_current_blog_id(); + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + switch_to_blog( self::get_site_blog_id( $site ) ); + + set_site_transient( 'update_plugins', null ); + set_site_transient( 'update_themes', null ); + } + + switch_to_blog( $current_blog_id ); + } + } else if ( fs_request_is_action( 'simulate_trial' ) ) { + check_admin_referer( 'simulate_trial' ); + + $fs = freemius( fs_request_get( 'module_id' ) ); + + // Update SDK install to at least 24 hours before. + $fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC ); + // Unset the trial shown timestamp. + unset( $fs->_storage->trial_promotion_shown ); + } else if ( fs_request_is_action( 'simulate_network_upgrade' ) ) { + check_admin_referer( 'simulate_network_upgrade' ); + + $fs = freemius( fs_request_get( 'module_id' ) ); + + self::set_network_upgrade_mode( $fs->_storage ); + } else if ( fs_request_is_action( 'delete_install' ) ) { + check_admin_referer( 'delete_install' ); + + self::_delete_site_by_slug( + fs_request_get( 'slug' ), + fs_request_get( 'module_type' ), + true, + fs_request_get( 'blog_id', null ) + ); + } else if ( fs_request_is_action( 'delete_user' ) ) { + check_admin_referer( 'delete_user' ); + + self::delete_user( fs_request_get( 'user_id' ) ); + } else if ( fs_request_is_action( 'download_logs' ) ) { + check_admin_referer( 'download_logs' ); + + $download_url = FS_Logger::download_db_logs( + fs_request_get( 'filters', false, 'post' ) + ); + + if ( false === $download_url ) { + wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact support@freemius.com.' ); + } + + fs_redirect( $download_url ); + } else if ( fs_request_is_action( 'migrate_options_to_network' ) ) { + check_admin_referer( 'migrate_options_to_network' ); + + self::migrate_options_to_network(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _debug_page_render() { + self::$_static_logger->entrance(); + + if ( ! is_multisite() ) { + $all_plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); + $all_themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME ); + } else { + $sites = self::get_sites(); + + $all_plugins_installs = array(); + $all_themes_installs = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); + + foreach ( $plugins_installs as $slug => $install ) { + if ( ! isset( $all_plugins_installs[ $slug ] ) ) { + $all_plugins_installs[ $slug ] = array(); + } + + $install->blog_id = $blog_id; + + $all_plugins_installs[ $slug ][] = $install; + } + + $themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ); + + foreach ( $themes_installs as $slug => $install ) { + if ( ! isset( $all_themes_installs[ $slug ] ) ) { + $all_themes_installs[ $slug ] = array(); + } + + $install->blog_id = $blog_id; + + $all_themes_installs[ $slug ][] = $install; + } + } + } + + $licenses_by_module_type = self::get_all_licenses_by_module_type(); + + $vars = array( + 'plugin_sites' => $all_plugins_installs, + 'theme_sites' => $all_themes_installs, + 'users' => self::get_all_users(), + 'addons' => self::get_all_addons(), + 'account_addons' => self::get_all_account_addons(), + 'plugin_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_PLUGIN ], + 'theme_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_THEME ] + ); + + fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' ); + fs_require_once_template( 'debug.php', $vars ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Issues + #---------------------------------------------------------------------------------- + + /** + * Check if Freemius should be turned on for the current plugin install. + * + * Note: + * $this->_is_on is updated in has_api_connectivity() + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_on() { + self::$_static_logger->entrance(); + + if ( isset( $this->_is_on ) ) { + return $this->_is_on; + } + + // If already installed or pending then sure it's on :) + if ( $this->is_registered() || $this->is_pending_activation() ) { + $this->_is_on = true; + + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $flush_if_no_connectivity + * + * @return bool + */ + private function should_run_connectivity_test( $flush_if_no_connectivity = false ) { + if ( ! isset( $this->_storage->connectivity_test ) ) { + // Connectivity test was never executed, or cache was cleared. + return true; + } + + if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) { + if ( WP_FS__IS_HTTP_REQUEST ) { + if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) { + // Domain changed. + return true; + } + + if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) { + // Server IP changed. + return true; + } + } + } + + if ( $this->_storage->connectivity_test['is_connected'] && + $this->_storage->connectivity_test['is_active'] + ) { + // API connected and Freemius is active - no need to run connectivity check. + return false; + } + + if ( $flush_if_no_connectivity ) { + /** + * If explicitly asked to flush when no connectivity - do it only + * if at least 10 sec passed from the last API connectivity test. + */ + return ( isset( $this->_storage->connectivity_test['timestamp'] ) && + ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 ); + } + + /** + * @since 1.1.7 Don't check for connectivity on plugin downgrade. + */ + $version = $this->get_plugin_version(); + if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) { + // If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test. + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param int|null $blog_id Since 2.0.0. + * @param bool $is_gdpr_test Since 2.0.2. Perform only the GDPR test. + * + * @return object|false + */ + private function ping( $blog_id = null, $is_gdpr_test = false ) { + if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) { + return false; + } + + $version = $this->get_plugin_version(); + + $is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ); + + return $this->get_api_plugin_scope()->ping( + $this->get_anonymous_id( $blog_id ), + array( + 'is_update' => json_encode( $is_update ), + 'version' => $version, + 'sdk' => $this->version, + 'is_admin' => json_encode( is_admin() ), + 'is_ajax' => json_encode( self::is_ajax() ), + 'is_cron' => json_encode( self::is_cron() ), + 'is_gdpr_test' => $is_gdpr_test, + 'is_http' => json_encode( WP_FS__IS_HTTP_REQUEST ), + ) + ); + } + + /** + * Check if there's any connectivity issue to Freemius API. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $flush_if_no_connectivity + * + * @return bool + */ + function has_api_connectivity( $flush_if_no_connectivity = false ) { + $this->_logger->entrance(); + + if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) { + return $this->_has_api_connection; + } + + if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY && + isset( $this->_storage->connectivity_test ) && + true === $this->_storage->connectivity_test['is_connected'] + ) { + unset( $this->_storage->connectivity_test ); + } + + if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { + $this->_has_api_connection = $this->_storage->connectivity_test['is_connected']; + /** + * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration. + * + * @since 1.2.1.5 If the user running the premium version then ignore the 'is_active' flag and turn Freemius on to enable license key activation. + */ + $this->_is_on = $this->_storage->connectivity_test['is_active'] || + $this->is_premium() || + ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF ); + + return $this->_has_api_connection; + } + + $pong = $this->ping(); + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( ! $is_connected ) { + // API failure. + $this->_add_connectivity_issue_message( $pong ); + } + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + } + + $this->store_connectivity_info( $pong, $is_connected ); + + return $this->_has_api_connection; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param object $pong + * @param bool $is_connected + */ + private function store_connectivity_info( $pong, $is_connected ) { + $this->_logger->entrance(); + + $version = $this->get_plugin_version(); + + if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { + $is_active = false; + } else { + $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); + } + + $is_active = $this->apply_filters( + 'is_on', + $is_active, + $this->is_plugin_update(), + $version + ); + + $this->_storage->connectivity_test = array( + 'is_connected' => $is_connected, + 'host' => $_SERVER['HTTP_HOST'], + 'server_ip' => WP_FS__REMOTE_ADDR, + 'is_active' => $is_active, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + // Last version with connectivity attempt. + 'version' => $version, + ); + + $this->_has_api_connection = $is_connected; + $this->_is_on = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF ); + } + + /** + * Force turning Freemius on. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @return bool TRUE if successfully turned on. + */ + private function turn_on() { + $this->_logger->entrance(); + + if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) { + return false; + } + + $updated_connectivity = $this->_storage->connectivity_test; + $updated_connectivity['is_active'] = true; + $updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME; + $this->_storage->connectivity_test = $updated_connectivity; + + $this->_is_on = true; + + return true; + } + + /** + * Anonymous and unique site identifier (Hash). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param null|int $blog_id Since 2.0.0 + * + * @return string + */ + function get_anonymous_id( $blog_id = null ) { + $unique_id = self::$_accounts->get_option( 'unique_id', null, $blog_id ); + + if ( empty( $unique_id ) || ! is_string( $unique_id ) ) { + $key = fs_strip_url_protocol( get_site_url( $blog_id ) ); + + $secure_auth = SECURE_AUTH_KEY; + if ( empty( $secure_auth ) || + false !== strpos( $secure_auth, ' ' ) || + 'put your unique phrase here' === $secure_auth + ) { + // Protect against default auth key. + $secure_auth = md5( microtime() ); + } + + /** + * Base the unique identifier on the WP secure authentication key. Which + * turns the key into a secret anonymous identifier. This will help us + * to avoid duplicate installs generation on the backend upon opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + */ + $unique_id = md5( $key . $secure_auth ); + + self::$_accounts->set_option( 'unique_id', $unique_id, true, $blog_id ); + } + + $this->_logger->departure( $unique_id ); + + return $unique_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @return \WP_User + */ + static function _get_current_wp_user() { + self::require_pluggable_essentials(); + self::wp_cookie_constants(); + + return wp_get_current_user(); + } + + /** + * Define cookie constants which are required by Freemius::_get_current_wp_user() since + * it uses wp_get_current_user() which needs the cookie constants set. When a plugin + * is network activated the cookie constants are only configured after the network + * plugins activation, therefore, if we don't define those constants WP will throw + * PHP warnings/notices. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.1 + */ + private static function wp_cookie_constants() { + if ( defined( 'LOGGED_IN_COOKIE' ) && + ( defined( 'AUTH_COOKIE' ) || defined( 'SECURE_AUTH_COOKIE' ) ) + ) { + return; + } + + /** + * Used to guarantee unique hash cookies + * + * @since 1.5.0 + */ + if ( ! defined( 'COOKIEHASH' ) ) { + $siteurl = get_site_option( 'siteurl' ); + if ( $siteurl ) { + define( 'COOKIEHASH', md5( $siteurl ) ); + } else { + define( 'COOKIEHASH', '' ); + } + } + + if ( ! defined( 'LOGGED_IN_COOKIE' ) ) { + define( 'LOGGED_IN_COOKIE', 'wordpress_logged_in_' . COOKIEHASH ); + } + + /** + * @since 2.5.0 + */ + if ( ! defined( 'AUTH_COOKIE' ) ) { + define( 'AUTH_COOKIE', 'wordpress_' . COOKIEHASH ); + } + + /** + * @since 2.6.0 + */ + if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) { + define( 'SECURE_AUTH_COOKIE', 'wordpress_sec_' . COOKIEHASH ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return int + */ + static function get_current_wp_user_id() { + $wp_user = self::_get_current_wp_user(); + + return $wp_user->ID; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $email + * + * @return bool + */ + static function is_valid_email( $email ) { + if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { + return false; + } + + $parts = explode( '@', $email ); + + if ( 2 !== count( $parts ) || empty( $parts[1] ) ) { + return false; + } + + $blacklist = array( + 'admin.', + 'webmaster.', + 'localhost.', + 'dev.', + 'development.', + 'test.', + 'stage.', + 'staging.', + ); + + // Make sure domain is not one of the blacklisted. + foreach ( $blacklist as $invalid ) { + if ( 0 === strpos( $parts[1], $invalid ) ) { + return false; + } + } + + // Get the UTF encoded domain name. + $domain = idn_to_ascii( $parts[1] ) . '.'; + + return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); + } + + /** + * Generate API connectivity issue message. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param mixed $api_result + * @param bool $is_first_failure + */ + function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) { + if ( ! $this->is_premium() && $this->_enable_anonymous ) { + // Don't add message if it's the free version and can run anonymously. + return; + } + + if ( ! function_exists( 'wp_nonce_url' ) ) { + require_once ABSPATH . 'wp-includes/functions.php'; + } + + $current_user = self::_get_current_wp_user(); +// $admin_email = get_option( 'admin_email' ); + $admin_email = $current_user->user_email; + + // Aliases. + $deactivate_plugin_title = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' ); + $deactivate_plugin_desc = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' ); + $install_previous_title = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' ); + $install_previous_desc = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' ); + $fix_issue_title = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' ); + $fix_issue_desc = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' ); + /* translators: %s: product title (e.g. "Awesome Plugin" requires an access to...) */ + $x_requires_access_to_api = $this->esc_html_inline( '%s requires an access to our API.', 'x-requires-access-to-api' ); + $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' ); + $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' ); + + $message = false; + if ( is_object( $api_result ) && + isset( $api_result->error ) && + isset( $api_result->error->code ) + ) { + switch ( $api_result->error->code ) { + case 'curl_missing': + $missing_methods = ''; + if ( is_array( $api_result->missing_methods ) && + ! empty( $api_result->missing_methods ) + ) { + foreach ( $api_result->missing_methods as $m ) { + if ( 'curl_version' === $m ) { + continue; + } + + if ( ! empty( $missing_methods ) ) { + $missing_methods .= ', '; + } + + $missing_methods .= sprintf( '%s', $m ); + } + + if ( ! empty( $missing_methods ) ) { + $missing_methods = sprintf( + '

    %s %s', + $this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ), + $missing_methods + ); + } + } + + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' . + $missing_methods . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '

    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ), + ' - ' . sprintf( + $this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ), + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + $sysadmin_title, + esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) ) + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; + case 'cloudflare_ddos_protection': + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' . + $happy_to_resolve_issue_asap . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $fix_issue_title, + ' - ' . sprintf( + $fix_issue_desc, + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), + $install_previous_title, + $install_previous_desc + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; + case 'squid_cache_block': + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s - %s', + $this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ), + sprintf( + $this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ), + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + $sysadmin_title, + sprintf( + $this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ), + // We use a filter since the plugin might require additional API connectivity. + '' . implode( ', ', $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com' + ) ) ) . '', + $this->_module_type + ) + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; +// default: +// $message = $this->get_text_inline( 'connectivity-test-fails-message' ); +// break; + } + } + + $message_id = 'failed_connect_api'; + $type = 'error'; + + $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' ); + + if ( false === $message ) { + if ( $is_first_failure ) { + // First attempt failed. + $message = sprintf( + $x_requires_access_to_api . ' ' . + $connectivity_test_fails_message . ' ' . + $this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '

    ' . + '%s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    %s %s
    ', + sprintf( + '%s', + $this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' ) + ), + sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $this->get_text_inline( 'No - just deactivate', 'no-deactivate' ) + ) + ) + ); + + $message_id = 'failed_connect_api_first'; + $type = 'promotion'; + } else { + // Second connectivity attempt failed. + $message = sprintf( + $x_requires_access_to_api . ' ' . + $connectivity_test_fails_message . ' ' . + $happy_to_resolve_issue_asap . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $fix_issue_title, + ' - ' . sprintf( + $fix_issue_desc, + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), + $install_previous_title, + $install_previous_desc + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + } + } + + $this->_admin_notices->add_sticky( + $message, + $message_id, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + $type + ); + } + + /** + * Handle user request to resolve connectivity issue. + * This method will send an email to Freemius API technical staff for resolution. + * The email will contain server's info and installed plugins (might be caching issue). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _email_about_firewall_issue() { + $this->_admin_notices->remove_sticky( 'failed_connect_api' ); + + $pong = $this->ping(); + + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + $this->store_connectivity_info( $pong, $is_connected ); + + echo $this->get_after_plugin_activation_redirect_url(); + exit; + } + + $current_user = self::_get_current_wp_user(); + $admin_email = $current_user->user_email; + + $error_type = fs_request_get( 'error_type', 'general' ); + + switch ( $error_type ) { + case 'squid': + $title = 'Squid ACL Blocking Issue'; + break; + case 'cloudflare': + $title = 'CloudFlare Blocking Issue'; + break; + default: + $title = 'API Connectivity Issue'; + break; + } + + $custom_email_sections = array(); + + // Add 'API Error' custom email section. + $custom_email_sections['api_error'] = array( + 'title' => 'API Error', + 'rows' => array( + 'ping' => array( + 'API Error', + is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong ) + ), + ) + ); + + // Send email with technical details to resolve API connectivity issues. + $this->send_email( + 'api@freemius.com', // recipient + $title . ' [' . $this->get_plugin_name() . ']', // subject + $custom_email_sections, + array( "Reply-To: $admin_email <$admin_email>" ) // headers + ); + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ), + '' . $admin_email . '' + ), + 'server_details_sent' + ); + + // Action was taken, tell that API connectivity troubleshooting should be off now. + + echo "1"; + exit; + } + + /** + * Handle connectivity test retry approved by the user. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + */ + function _retry_connectivity_test() { + $this->_admin_notices->remove_sticky( 'failed_connect_api_first' ); + + $pong = $this->ping(); + + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + $this->store_connectivity_info( $pong, $is_connected ); + + echo $this->get_after_plugin_activation_redirect_url(); + } else { + // Add connectivity issue message after 2nd failed attempt. + $this->_add_connectivity_issue_message( $pong, false ); + + echo "1"; + } + + exit; + } + + static function _add_firewall_issues_javascript() { + $params = array(); + fs_require_once_template( 'firewall-issues-js.php', $params ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Email + #---------------------------------------------------------------------------------- + + /** + * Generates and sends an HTML email with customizable sections. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @param string $to_address + * @param string $subject + * @param array $sections + * @param array $headers + * + * @return bool Whether the email contents were sent successfully. + */ + private function send_email( + $to_address, + $subject, + $sections = array(), + $headers = array() + ) { + $default_sections = $this->get_email_sections(); + + // Insert new sections or replace the default email sections. + if ( is_array( $sections ) && ! empty( $sections ) ) { + foreach ( $sections as $section_id => $custom_section ) { + if ( ! isset( $default_sections[ $section_id ] ) ) { + // If the section does not exist, add it. + $default_sections[ $section_id ] = $custom_section; + } else { + // If the section already exists, override it. + $current_section = $default_sections[ $section_id ]; + + // Replace the current section's title if a custom section title exists. + if ( isset( $custom_section['title'] ) ) { + $current_section['title'] = $custom_section['title']; + } + + // Insert new rows under the current section or replace the default rows. + if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) { + foreach ( $custom_section['rows'] as $row_id => $row ) { + $current_section['rows'][ $row_id ] = $row; + } + } + + $default_sections[ $section_id ] = $current_section; + } + } + } + + $vars = array( 'sections' => $default_sections ); + $message = fs_get_template( 'email.php', $vars ); + + // Set the type of email to HTML. + $headers[] = 'Content-type: text/html; charset=UTF-8'; + + $header_string = implode( "\r\n", $headers ); + + return wp_mail( + $to_address, + $subject, + $message, + $header_string + ); + } + + /** + * Generates the data for the sections of the email content. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @return array + */ + private function get_email_sections() { + // Retrieve the current user's information so that we can get the user's email, first name, and last name below. + $current_user = self::_get_current_wp_user(); + + // Retrieve the cURL version information so that we can get the version number below. + $curl_version_information = curl_version(); + + $active_plugin = self::get_active_plugins(); + + // Generate the list of active plugins separated by new line. + $active_plugin_string = ''; + foreach ( $active_plugin as $plugin ) { + $active_plugin_string .= sprintf( + '%s [v%s]
    ', + $plugin['PluginURI'], + $plugin['Name'], + $plugin['Version'] + ); + } + + $server_ip = WP_FS__REMOTE_ADDR; + + // Add PHP info for deeper investigation. + ob_start(); + phpinfo(); + $php_info = ob_get_clean(); + + $api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 ); + + // Generate the default email sections. + $sections = array( + 'sdk' => array( + 'title' => 'SDK', + 'rows' => array( + 'fs_version' => array( 'FS Version', $this->version ), + 'curl_version' => array( 'cURL Version', $curl_version_information['version'] ) + ) + ), + 'plugin' => array( + 'title' => ucfirst( $this->get_module_type() ), + 'rows' => array( + 'name' => array( 'Name', $this->get_plugin_name() ), + 'version' => array( 'Version', $this->get_plugin_version() ) + ) + ), + 'api' => array( + 'title' => 'API Subdomain', + 'rows' => array( + 'dns' => array( + 'DNS_CNAME', + function_exists( 'dns_get_record' ) ? + var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) : + 'dns_get_record() disabled/blocked' + ), + 'ip' => array( + 'IP', + function_exists( 'gethostbyname' ) ? + gethostbyname( $api_domain ) : + 'gethostbyname() disabled/blocked' + ), + ), + ), + 'site' => array( + 'title' => 'Site', + 'rows' => array( + 'unique_id' => array( 'Unique ID', $this->get_anonymous_id() ), + 'address' => array( 'Address', site_url() ), + 'host' => array( + 'HTTP_HOST', + ( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) + ), + 'hosting' => array( + 'Hosting Company' => fs_request_has( 'hosting_company' ) ? + fs_request_get( 'hosting_company' ) : + 'Unknown', + ), + 'server_addr' => array( + 'SERVER_ADDR', + '' . $server_ip . '' + ) + ) + ), + 'user' => array( + 'title' => 'User', + 'rows' => array( + 'email' => array( 'Email', $current_user->user_email ), + 'first' => array( 'First', $current_user->user_firstname ), + 'last' => array( 'Last', $current_user->user_lastname ) + ) + ), + 'plugins' => array( + 'title' => 'Plugins', + 'rows' => array( + 'active_plugins' => array( 'Active Plugins', $active_plugin_string ) + ) + ), + 'php_info' => array( + 'title' => 'PHP Info', + 'rows' => array( + 'info' => array( $php_info ) + ), + ) + ); + + // Allow the sections to be modified by other code. + $sections = $this->apply_filters( 'email_template_sections', $sections ); + + return $sections; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Initialization + #---------------------------------------------------------------------------------- + + /** + * Init plugin's Freemius instance. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param number $id + * @param string $public_key + * @param bool $is_live + * @param bool $is_premium + */ + function init( $id, $public_key, $is_live = true, $is_premium = true ) { + $this->_logger->entrance(); + + $this->dynamic_init( array( + 'id' => $id, + 'public_key' => $public_key, + 'is_live' => $is_live, + 'is_premium' => $is_premium, + ) ); + } + + /** + * Dynamic initiator, originally created to support initiation + * with parent_id for add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param array $plugin_info + * + * @throws Freemius_Exception + */ + function dynamic_init( array $plugin_info ) { + $this->_logger->entrance(); + + $this->parse_settings( $plugin_info ); + + if ( is_admin() && $this->is_theme() && $this->is_premium() && ! $this->has_active_valid_license() ) { + $this->add_ajax_action( + 'delete_theme_update_data', + array( &$this, '_delete_theme_update_data_action' ) + ); + } + + if ( ! self::is_ajax() ) { + if ( ! $this->is_addon() || $this->is_only_premium() ) { + add_action( + ( $this->_is_network_active && fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', + array( &$this, '_prepare_admin_menu' ), + WP_FS__LOWEST_PRIORITY + ); + } + } + + if ( $this->should_stop_execution() ) { + return; + } + + if ( ! $this->is_registered() ) { + if ( $this->is_anonymous() ) { + // If user skipped, no need to test connectivity. + $this->_has_api_connection = true; + $this->_is_on = true; + } else { + if ( ! $this->has_api_connectivity() ) { + if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) || + $this->_admin_notices->has_sticky( 'failed_connect_api' ) + ) { + if ( ! $this->_enable_anonymous || $this->is_premium() ) { + // If anonymous mode is disabled, add firewall admin-notice message. + add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) ); + + $ajax_action_suffix = $this->_slug . ( $this->is_theme() ? ':theme' : '' ); + add_action( "wp_ajax_fs_resolve_firewall_issues_{$ajax_action_suffix}", array( + &$this, + '_email_about_firewall_issue' + ) ); + + add_action( "wp_ajax_fs_retry_connectivity_test_{$ajax_action_suffix}", array( + &$this, + '_retry_connectivity_test' + ) ); + + /** + * Currently the admin notice manager relies on the module's type and slug. The new AJAX actions manager uses module IDs, hence, consider to replace the if block above with the commented code below after adjusting the admin notices manager to work with module IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + /*$this->add_ajax_action( 'resolve_firewall_issues', array( + &$this, + '_email_about_firewall_issue' + ) ); + + $this->add_ajax_action( 'retry_connectivity_test', array( + &$this, + '_retry_connectivity_test' + ) );*/ + } + } + + return; + } else { + $this->_admin_notices->remove_sticky( array( + 'failed_connect_api_first', + 'failed_connect_api', + ) ); + + if ( $this->_anonymous_mode ) { + // Simulate anonymous mode. + $this->_is_anonymous = true; + } + } + } + } + + /** + * This should be executed even if Freemius is off for the core module, + * otherwise, the add-ons dialogbox won't work properly. This is esepcially + * relevant when the developer decided to turn FS off for existing users. + * + * @author Vova Feldman (@svovaf) + */ + if ( $this->is_user_in_admin() && + 'plugin-information' === fs_request_get( 'tab', false ) && + $this->should_use_freemius_updater_and_dialog() && + ( + ( $this->is_addon() && $this->get_slug() == fs_request_get( 'plugin', false ) ) || + ( $this->has_addons() && $this->get_id() == fs_request_get( 'parent_plugin_id', false ) ) + ) + ) { + require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php'; + + new FS_Plugin_Info_Dialog( $this->is_addon() ? $this->get_parent_instance() : $this ); + } + + // Check if Freemius is on for the current plugin. + // This MUST be executed after all the plugin variables has been loaded. + if ( ! $this->is_registered() && ! $this->is_on() ) { + return; + } + + if ( $this->has_api_connectivity() ) { + if ( self::is_cron() ) { + $this->hook_callback_to_sync_cron(); + } else if ( $this->is_user_in_admin() ) { + /** + * Schedule daily data sync cron if: + * + * 1. User opted-in (for tracking). + * 2. If skipped, but later upgraded (opted-in via upgrade). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + */ + if ( $this->is_registered() ) { + if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) { + $this->schedule_sync_cron(); + } + } + + /** + * Check if requested for manual blocking background sync. + */ + if ( fs_request_has( 'background_sync' ) ) { + $this->run_manual_sync(); + } + } + } + + if ( $this->is_registered() ) { + $this->hook_callback_to_install_sync(); + } + + if ( $this->is_addon() ) { + if ( $this->is_parent_plugin_installed() ) { + // Link to parent FS. + $this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id ); + + // Get parent plugin reference. + $this->_parent_plugin = $this->_parent->get_plugin(); + } + } + + if ( $this->is_user_in_admin() ) { + if ( $this->is_addon() ) { + if ( ! $this->is_parent_plugin_installed() ) { + $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); + + if ( isset( $plugin_info['parent'] ) ) { + $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); + } + + $this->_admin_notices->add( + ( ! empty( $parent_name ) ? + sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) : + sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() ) + ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + + return; + } else { + $is_network_admin = fs_is_network_admin(); + + if ( + $this->_parent->is_registered() && + ! $this->is_registered() && + /** + * If not registered for add-on and the following conditions for the add-on are met, activate add-on account. + * * Network active and in network admin - network activate add-on account. + * * Network active and not in network admin - activate add-on account for the current blog. + * * Not network active and not in network admin - activate add-on account for the current blog. + * + * If not registered for add-on, not network active, and in network admin, do not handle the add-on activation. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + ( $this->is_network_active() || ! $is_network_admin ) + ) { + // If parent plugin activated, automatically install add-on for the user. + $this->_activate_addon_account( + $this->_parent, + ( $this->is_network_active() && $is_network_admin ) ? + true : + get_current_blog_id() + ); + } else if ( ! $this->_parent->is_registered() && $this->is_registered() ) { + // If add-on activated and parent not, automatically install parent for the user. + $this->activate_parent_account( $this->_parent ); + } + + // @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic. + if ( $this->is_premium() ) { + // Remove add-on download admin-notice. + $this->_parent->_admin_notices->remove_sticky( array( + 'addon_plan_upgraded_' . $this->_slug, + 'no_addon_license_' . $this->_slug, + ) ); + } + +// $this->deactivate_premium_only_addon_without_license(); + } + } + + add_action( 'admin_init', array( &$this, '_admin_init_action' ) ); + +// if ( $this->is_registered() || +// $this->is_anonymous() || +// $this->is_pending_activation() +// ) { +// $this->_init_admin(); +// } + } + + /** + * Should be called outside `$this->is_user_in_admin()` scope + * because the updater has some logic that needs to be executed + * during AJAX calls. + * + * Currently we need to hook to the `http_request_host_is_external` filter. + * In the future, there might be additional logic added. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + if ( + $this->should_use_freemius_updater_and_dialog() && + ( + $this->is_premium() || + /** + * If not premium but the premium version is installed, also instantiate the updater so that the + * plugin information dialog of the premium version will have the information from the server. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) ) + ) && + $this->has_release_on_freemius() + ) { + FS_Plugin_Updater::instance( $this ); + } + + $this->do_action( 'initiated' ); + + if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) { + if ( isset( $this->_storage->prev_is_premium ) ) { + $this->apply_filters( + 'after_code_type_change', + // New code type. + $this->_plugin->is_premium + ); + } else { + // Set for code type for the first time. + $this->_storage->prev_is_premium = $this->_plugin->is_premium; + } + } + + if ( ! $this->is_addon() ) { + if ( $this->is_registered() ) { + // Fix for upgrade from versions < 1.0.9. + if ( ! isset( $this->_storage->activation_timestamp ) ) { + $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; + } + + $this->do_action( 'after_init_plugin_registered' ); + } else if ( $this->is_anonymous() ) { + $this->do_action( 'after_init_plugin_anonymous' ); + } else if ( $this->is_pending_activation() ) { + $this->do_action( 'after_init_plugin_pending_activations' ); + } + } else { + if ( $this->is_registered() ) { + $this->do_action( 'after_init_addon_registered' ); + } else if ( $this->is_anonymous() ) { + $this->do_action( 'after_init_addon_anonymous' ); + } else if ( $this->is_pending_activation() ) { + $this->do_action( 'after_init_addon_pending_activations' ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return bool + */ + private function should_use_freemius_updater_and_dialog() { + return ( + /** + * Allow updater and dialog when the `fs_allow_updater_and_dialog` URL query param exists and has `true` + * value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action` + * URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts + * with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing + * plugin details from .org). + */ + ( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) || + ( + ! self::is_plugin_install_page() && + // Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected. + ( 'install-plugin' !== fs_request_get( 'action' ) ) + ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.1.5 + */ + function _stop_tracking_callback() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'stop_tracking' ); + + $result = $this->stop_tracking( fs_is_network_admin() ); + + if ( true === $result ) { + self::shoot_ajax_success(); + } + + $this->_logger->api_error( $result ); + + self::shoot_ajax_failure( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . + ( $this->is_api_error( $result ) && isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _allow_tracking_callback() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'allow_tracking' ); + + $result = $this->allow_tracking( fs_is_network_admin() ); + + if ( true === $result ) { + self::shoot_ajax_success(); + } + + $this->_logger->api_error( $result ); + + self::shoot_ajax_failure( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . + ( $this->is_api_error( $result ) && isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) ) + ); + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function stop_site_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + if ( $this->is_tracking_prohibited() ) { + // Already disconnected. + return true; + } + + // Send update to FS. + $result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array( + 'is_disconnected' => true + ) ); + + if ( ! $this->is_api_result_entity( $result ) || + ! isset( $result->is_disconnected ) || + ! $result->is_disconnected + ) { + $this->_logger->api_error( $result ); + + return $result; + } + + $this->_site->is_disconnected = $result->is_disconnected; + $this->_store_site(); + + $this->clear_sync_cron(); + + // Successfully disconnected. + return true; + } + + /** + * Opt-out network from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function stop_network_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + $install_id_2_blog_id = array(); + $installs_map = $this->get_blog_install_map(); + + $opt_out_all = true; + + $params = array(); + foreach ( $installs_map as $blog_id => $install ) { + if ( $install->is_tracking_prohibited() ) { + // Already opted-out. + continue; + } + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + // Opt-out only from non-delegated installs. + $opt_out_all = false; + continue; + } + + $params[] = array( 'id' => $install->id ); + + $install_id_2_blog_id[ $install->id ] = $blog_id; + } + + if ( empty( $install_id_2_blog_id ) ) { + return true; + } + + $params[] = array( 'is_disconnected' => true ); + + // Send update to FS. + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + $this->_logger->api_error( $result ); + + return $result; + } + + foreach ( $result->installs as $r_install ) { + $blog_id = $install_id_2_blog_id[ $r_install->id ]; + $install = $installs_map[ $blog_id ]; + $install->is_disconnected = $r_install->is_disconnected; + $this->_store_site( true, $blog_id, $install ); + } + + $this->clear_sync_cron( $opt_out_all ); + + // Successfully disconnected. + return true; + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_network_action + * + * @return bool|object + */ + function stop_tracking( $is_network_action = false ) { + $this->_logger->entrance(); + + return $is_network_action ? + $this->stop_network_tracking() : + $this->stop_site_tracking(); + } + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function allow_site_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + if ( $this->is_tracking_allowed() ) { + // Tracking already allowed. + return true; + } + + $result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array( + 'is_disconnected' => false + ) ); + + if ( ! $this->is_api_result_entity( $result ) || + ! isset( $result->is_disconnected ) || + $result->is_disconnected + ) { + $this->_logger->api_error( $result ); + + return $result; + } + + $this->_site->is_disconnected = $result->is_disconnected; + $this->_store_site(); + + $this->schedule_sync_cron(); + + // Successfully reconnected. + return true; + } + + /** + * Opt-in network back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function allow_network_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + $install_id_2_blog_id = array(); + $installs_map = $this->get_blog_install_map(); + + $params = array(); + foreach ( $installs_map as $blog_id => $install ) { + if ( $install->is_tracking_allowed() ) { + continue; + } + + $params[] = array( 'id' => $install->id ); + + $install_id_2_blog_id[ $install->id ] = $blog_id; + } + + if ( empty( $install_id_2_blog_id ) ) { + return true; + } + + $params[] = array( 'is_disconnected' => false ); + + // Send update to FS. + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + $this->_logger->api_error( $result ); + + return $result; + } + + foreach ( $result->installs as $r_install ) { + $blog_id = $install_id_2_blog_id[ $r_install->id ]; + $install = $installs_map[ $blog_id ]; + $install->is_disconnected = $r_install->is_disconnected; + $this->_store_site( true, $blog_id, $install ); + } + + $this->schedule_sync_cron(); + + // Successfully reconnected. + return true; + } + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_network_action + * + * @return bool|object + */ + function allow_tracking( $is_network_action = false ) { + $this->_logger->entrance(); + + return $is_network_action ? + $this->allow_network_tracking() : + $this->allow_site_tracking(); + } + + /** + * If user opted-in and later disabled usage-tracking, + * re-allow tracking for licensing and updates. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_context_single_site + */ + private function reconnect_locally( $is_context_single_site = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return; + } + + if ( ! fs_is_network_admin() || $is_context_single_site ) { + if ( $this->is_tracking_prohibited() ) { + $this->_site->is_disconnected = false; + $this->_store_site(); + } + } else { + $installs_map = $this->get_blog_install_map(); + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_tracking_prohibited() ) { + $install->is_disconnected = false; + $this->_store_site( true, $blog_id, $install ); + } + } + } + } + + /** + * Parse plugin's settings (as defined by the plugin dev). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param array $plugin_info + * + * @throws \Freemius_Exception + */ + private function parse_settings( &$plugin_info ) { + $this->_logger->entrance(); + + $id = $this->get_numeric_option( $plugin_info, 'id', false ); + $public_key = $this->get_option( $plugin_info, 'public_key', false ); + $secret_key = $this->get_option( $plugin_info, 'secret_key', null ); + $parent_id = $this->get_numeric_option( $plugin_info, 'parent_id', null ); + $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 Try to pull secret key from external config. + */ + if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) { + $secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" ); + } + + if ( isset( $plugin_info['parent'] ) ) { + $parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null ); +// $parent_slug = $this->get_option( $plugin_info['parent'], 'slug', null ); +// $parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null ); +// $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); + } + + if ( false === $id ) { + throw new Freemius_Exception( array( + 'error' => array( + 'type' => 'ParameterNotSet', + 'message' => 'Plugin id parameter is not set.', + 'code' => 'plugin_id_not_set', + 'http' => 500, + ) + ) ); + } + if ( false === $public_key ) { + throw new Freemius_Exception( array( + 'error' => array( + 'type' => 'ParameterNotSet', + 'message' => 'Plugin public_key parameter is not set.', + 'code' => 'plugin_public_key_not_set', + 'http' => 500, + ) + ) ); + } + + $plugin = ( $this->_plugin instanceof FS_Plugin ) ? + $this->_plugin : + new FS_Plugin(); + + $premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' ); + + $plugin->update( array( + 'id' => $id, + 'type' => $this->get_option( $plugin_info, 'type', $this->_module_type ), + 'public_key' => $public_key, + 'slug' => $this->_slug, + 'premium_slug' => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ), + 'parent_plugin_id' => $parent_id, + 'version' => $this->get_plugin_version(), + 'title' => $this->get_plugin_name( $premium_suffix ), + 'file' => $this->_plugin_basename, + 'is_premium' => $this->get_bool_option( $plugin_info, 'is_premium', true ), + 'premium_suffix' => $premium_suffix, + 'is_live' => $this->get_bool_option( $plugin_info, 'is_live', true ), + 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ), + 'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ), + ) ); + + if ( $plugin->is_updated() ) { + // Update plugin details. + $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin ); + } + // Set the secret key after storing the plugin, we don't want to store the key in the storage. + $this->_plugin->secret_key = $secret_key; + + /** + * If the product is network integrated and activated and the current view is in the network level Admin dashboard, if the product's network-level menu located differently from the sub-site level, then use the network menu details (when set). + * + * @author Vova Feldman + * @since 2.4.5 + */ + if ( $this->is_network_active() && fs_is_network_admin() ) { + if ( isset( $plugin_info['menu_network'] ) && + is_array( $plugin_info['menu_network'] ) && + ! empty( $plugin_info['menu_network'] ) + ) { + $plugin_info['menu'] = $plugin_info['menu_network']; + } + } + + if ( ! isset( $plugin_info['menu'] ) ) { + $plugin_info['menu'] = array(); + + if ( ! empty( $this->_storage->sdk_last_version ) && + version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' ) + ) { + // Backward compatibility to 1.1.2 + $plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ? + $plugin_info['menu_slug'] : + $this->_slug; + } + } + + $this->_menu = FS_Admin_Menu_Manager::instance( + $this->_module_id, + $this->_module_type, + $this->get_unique_affix() + ); + + $this->_menu->init( $plugin_info['menu'], $this->is_addon() ); + + $this->_has_addons = $this->get_bool_option( $plugin_info, 'has_addons', false ); + $this->_has_paid_plans = $this->get_bool_option( $plugin_info, 'has_paid_plans', true ); + $this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans ); + $this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false ); + $this->_is_org_compliant = $this->get_bool_option( $plugin_info, 'is_org_compliant', true ); + $this->_is_premium_only = $this->get_bool_option( $plugin_info, 'is_premium_only', false ); + if ( $this->_is_premium_only ) { + // If premium only plugin, disable anonymous mode. + $this->_enable_anonymous = false; + $this->_anonymous_mode = false; + } else { + $this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true ); + $this->_anonymous_mode = $this->get_bool_option( $plugin_info, 'anonymous_mode', false ); + } + $this->_permissions = $this->get_option( $plugin_info, 'permissions', array() ); + + if ( ! empty( $plugin_info['trial'] ) ) { + $this->_trial_days = $this->get_numeric_option( + $plugin_info['trial'], + 'days', + // Default to 0 - trial without days specification. + 0 + ); + + $this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false ); + } + } + + /** + * @param string[] $options + * @param string $key + * @param mixed $default + * + * @return bool + */ + private function get_option( &$options, $key, $default = false ) { + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_bool_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_numeric_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default; + } + + /** + * Gate keeper. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function should_stop_execution() { + if ( empty( $this->_storage->was_plugin_loaded ) ) { + /** + * Don't execute Freemius until plugin was fully loaded at least once, + * to give the opportunity for the activation hook to run before pinging + * the API for connectivity test. This logic is relevant for the + * identification of new plugin install vs. plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + return true; + } + + if ( $this->is_activation_mode() ) { + if ( ! is_admin() ) { + /** + * If in activation mode, don't execute Freemius outside of the + * admin dashboard. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + + if ( ! WP_FS__IS_HTTP_REQUEST ) { + /** + * If in activation and executed without HTTP context (e.g. CLI, Cronjob), + * then don't start Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6.3 + * + * @link https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli + */ + return true; + } + + if ( self::is_cron() ) { + /** + * If in activation mode, don't execute Freemius during wp crons + * (wp crons have HTTP context - called as HTTP request). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + + if ( self::is_ajax() && + ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) && + ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) + ) { + /** + * During activation, if running in AJAX mode, unless there's a sticky + * connectivity issue notice, don't run Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + } + + return false; + } + + /** + * Triggered after code type has changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9.1 + */ + function _after_code_type_change() { + $this->_logger->entrance(); + + if ( $this->is_theme() ) { + // Expire the cache of the previous tabs since the theme may + // have setting updates after code type has changed. + $this->_cache->expire( 'tabs' ); + $this->_cache->expire( 'tabs_stylesheets' ); + } + + if ( $this->is_registered() ) { + if ( ! $this->is_addon() ) { + add_action( + is_admin() ? 'admin_init' : 'init', + array( &$this, '_plugin_code_type_changed' ) + ); + } + + if ( $this->is_premium() ) { + // Purge cached payments after switching to the premium version. + // @todo This logic doesn't handle purging the cache for serviceware module upgrade. + $this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" ); + } + } + } + + /** + * Handles plugin's code type change (free <--> premium). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _plugin_code_type_changed() { + $this->_logger->entrance(); + + if ( $this->is_premium() ) { + $this->reconnect_locally(); + + // Activated premium code. + $this->do_action( 'after_premium_version_activation' ); + + // Remove all sticky messages related to download of the premium version. + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'plan_upgraded', + 'plan_changed', + 'license_activated', + ) ); + + $notice = ''; + if ( ! $this->is_only_premium() ) { + $notice = sprintf( $this->get_text_inline( 'Premium %s version was successfully activated.', 'premium-activated-message' ), $this->_module_type ); + } + + $license_notice = $this->get_license_network_activation_notice(); + if ( ! empty( $license_notice ) ) { + $notice .= ' ' . $license_notice; + } + + if ( ! empty( $notice ) ) { + $this->_admin_notices->add_sticky( + trim( $notice ), + 'premium_activated', + $this->get_text_x_inline( 'W00t', + 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' + ); + } + } else { + // Remove sticky message related to premium code activation. + $this->_admin_notices->remove_sticky( 'premium_activated' ); + + // Activated free code (after had the premium before). + $this->do_action( 'after_free_version_reactivation' ); + + if ( $this->is_paying() && ! $this->is_premium() ) { + $this->_admin_notices->add_sticky( + sprintf( + /* translators: %s: License type (e.g. you have a professional license) */ + $this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ), + $this->get_plan_title() + ) . $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + + // Schedule code type changes event. + $this->schedule_install_sync(); + + /** + * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid + * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version + * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been + * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since + * the main file of the "free" version will be loaded first before calling the hooked callback. Since the + * free and premium versions are almost identical (same class or have same functions), a fatal error like + * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur. + */ + $this->unregister_uninstall_hook(); + + $this->clear_module_main_file_cache(); + + // Update is_premium of latest version. + $this->_storage->prev_is_premium = $this->_plugin->is_premium; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Add-ons + #---------------------------------------------------------------------------------- + + /** + * Check if add-on installed and activated on site. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * @param bool|null $is_premium Since 1.2.1.7 can check for specified add-on version. + * + * @return bool + */ + function is_addon_activated( $id_or_slug, $is_premium = null ) { + $this->_logger->entrance(); + + $addon_id = self::get_module_id( $id_or_slug ); + $is_activated = self::has_instance( $addon_id ); + + if ( ! $is_activated ) { + return false; + } + + if ( is_bool( $is_premium ) ) { + // Check if the specified code version is activate. + $addon = $this->get_addon_instance( $addon_id ); + $is_activated = ( $is_premium === $addon->is_premium() ); + } + + return $is_activated; + } + + /** + * Check if add-on was connected to install + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param string|number $id_or_slug + * + * @return bool + */ + function is_addon_connected( $id_or_slug ) { + $this->_logger->entrance(); + + $sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); + + $addon_id = self::get_module_id( $id_or_slug ); + $addon = $this->get_addon( $addon_id ); + $slug = $addon->slug; + if ( ! isset( $sites[ $slug ] ) ) { + return false; + } + + $site = $sites[ $slug ]; + + $plugin = FS_Plugin_Manager::instance( $addon_id )->get(); + + if ( $plugin->parent_plugin_id != $this->_plugin->id ) { + // The given slug do NOT belong to any of the plugin's add-ons. + return false; + } + + return ( is_object( $site ) && + is_numeric( $site->id ) && + is_numeric( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ); + } + + /** + * Determines if add-on installed. + * + * NOTE: This is a heuristic and only works if the folder/file named as the slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return bool + */ + function is_addon_installed( $id_or_slug ) { + $this->_logger->entrance(); + + $addon_id = self::get_module_id( $id_or_slug ); + + return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) ); + } + + /** + * Get add-on basename. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return string + */ + function get_addon_basename( $id_or_slug ) { + $addon_id = self::get_module_id( $id_or_slug ); + + if ( $this->is_addon_activated( $addon_id ) ) { + return self::instance( $addon_id )->get_plugin_basename(); + } + + $addon = $this->get_addon( $addon_id ); + $premium_basename = "{$addon->premium_slug}/{$addon->slug}.php"; + + if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) { + return $premium_basename; + } + + $all_plugins = $this->get_all_plugins(); + + foreach ( $all_plugins as $basename => $data ) { + if ( $addon->slug === $data['slug'] || + $addon->premium_slug === $data['slug'] + ) { + return $basename; + } + } + + $free_basename = "{$addon->slug}/{$addon->slug}.php"; + + return $free_basename; + } + + /** + * Get installed add-ons instances. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return Freemius[] + */ + function get_installed_addons() { + $installed_addons = array(); + foreach ( self::$_instances as $instance ) { + if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) { + if ( $this->_plugin->id == $instance->_parent_plugin->id ) { + $installed_addons[] = $instance; + } + } + } + + return $installed_addons; + } + + /** + * Check if any add-ons of the plugin are installed. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.1 + * + * @return bool + */ + function has_installed_addons() { + if ( ! $this->has_addons() ) { + return false; + } + + foreach ( self::$_instances as $instance ) { + if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) { + if ( $this->_plugin->id == $instance->_parent_plugin->id ) { + return true; + } + } + } + + return false; + } + + /** + * Tell Freemius that the current plugin is an add-on. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $parent_plugin_id The parent plugin ID + */ + function init_addon( $parent_plugin_id ) { + $this->_plugin->parent_plugin_id = $parent_plugin_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_addon() { + return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id ); + } + + /** + * Deactivate add-on if it's premium only and the user does't have a valid license. + * + * @param bool $is_after_trial_cancel + * + * @return bool If add-on was deactivated. + */ + private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) { + if ( ! $this->has_free_plan() && + ! $this->has_features_enabled_license() && + ! $this->_has_premium_license() + ) { + if ( $this->is_registered() ) { + // IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons). + // if (empty($this->_storage->activation_timestamp) || + // (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30 + // ) { + /** + * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation. + * + * Retry syncing the user add-on licenses. + */ + // Sync licenses. + $this->_sync_licenses(); + // } + + // Try to activate premium license. + $this->_activate_license( true ); + } + + if ( ! $this->has_free_plan() && + ! $this->has_features_enabled_license() && + ! $this->_has_premium_license() + ) { + // @todo Check if deactivate plugins also call the deactivation hook. + + $this->_parent->_admin_notices->add_sticky( + sprintf( + ( $is_after_trial_cancel ? + $this->_parent->get_text_inline( + '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.', + 'addon-trial-cancelled-message' + ) : + $this->_parent->get_text_inline( + '%s is a premium only add-on. You have to purchase a license first before activating the plugin.', + 'addon-no-license-message' + ) + ), + '' . $this->_plugin->title . '' + ) . ' ' . sprintf( + '%s  ➜', + $this->_parent->addon_url( $this->_slug ), + esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ), + $this->_parent->get_text_inline( 'Purchase License', 'purchase-license' ) + ), + 'no_addon_license_' . $this->_slug, + ( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ), + ( $is_after_trial_cancel ? 'success' : 'error' ) + ); + + deactivate_plugins( array( $this->_plugin_basename ), true ); + + return true; + } + } + + return false; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Sandbox + #---------------------------------------------------------------------------------- + + /** + * Set Freemius into sandbox mode for debugging. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $secret_key + */ + function init_sandbox( $secret_key ) { + $this->_plugin->secret_key = $secret_key; + + // Update plugin details. + FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true ); + } + + /** + * Check if running payments in sandbox mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_payments_sandbox() { + return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key ); + } + + #endregion + + /** + * Check if running test vs. live plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_live() { + return $this->_plugin->is_live; + } + + /** + * Check if super-admin skipped connection for all sites in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function is_network_anonymous() { + if ( ! $this->_is_network_active ) { + return false; + } + + $is_anonymous_ms = $this->_storage->get( 'is_anonymous_ms' ); + + if ( empty( $is_anonymous_ms ) ) { + return false; + } + + return $is_anonymous_ms['is']; + } + + /** + * Check if super-admin opted-in for all sites in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function is_network_connected() { + if ( ! $this->_is_network_active ) { + return false; + } + + return $this->_storage->get( 'is_network_connected' ); + } + + /** + * Check if the user skipped connecting the account with Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_anonymous() { + if ( ! isset( $this->_is_anonymous ) ) { + if ( $this->is_network_anonymous() ) { + $this->_is_anonymous = true; + } else if ( ! fs_is_network_admin() ) { + if ( ! isset( $this->_storage->is_anonymous ) ) { + // Not skipped. + $this->_is_anonymous = false; + } else if ( is_bool( $this->_storage->is_anonymous ) ) { + // For back compatibility, since the variable was boolean before. + $this->_is_anonymous = $this->_storage->is_anonymous; + + // Upgrade stored data format to 1.1.3 format. + $this->set_anonymous_mode( $this->_storage->is_anonymous ); + } else { + // Version 1.1.3 and later. + $this->_is_anonymous = $this->_storage->is_anonymous['is']; + } + } + } + + return $this->_is_anonymous; + } + + /** + * Check if the user skipped the connection of a specified site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_anonymous_site( $blog_id = 0 ) { + if ( $this->is_network_anonymous() ) { + return true; + } + + $is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id ); + + if ( empty( $is_anonymous ) ) { + return false; + } + + return $is_anonymous['is']; + } + + /** + * Check if user connected his account and install pending email activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_pending_activation() { + return $this->_storage->get( 'is_pending_activation', false ); + } + + /** + * Check if plugin must be WordPress.org compliant. + * + * @since 1.0.7 + * + * @return bool + */ + function is_org_repo_compliant() { + return $this->_is_org_compliant; + } + + #-------------------------------------------------------------------------------- + #region WP Cron Common + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return object + */ + private function get_cron_data( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + return $this->_storage->get( "{$name}_cron", null ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + */ + private function clear_cron_data( $name ) { + $this->_logger->entrance( $name ); + + $this->_storage->remove( "{$name}_cron" ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param int $cron_blog_id The cron executing blog ID. + */ + private function set_cron_data( $name, $cron_blog_id = 0 ) { + $this->_logger->entrance( $name ); + + $this->_storage->store( "{$name}_cron", (object) array( + 'version' => $this->get_plugin_version(), + 'blog_id' => $cron_blog_id, + 'sdk_version' => $this->version, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + 'on' => true, + ) ); + } + + /** + * Get the cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return int + */ + private function get_cron_blog_id( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + return ( is_object( $cron_data ) && is_numeric( $cron_data->blog_id ) ) ? + $cron_data->blog_id : + 0; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return bool + */ + private function is_cron_on( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + return ( ! is_null( $cron_data ) && true === $cron_data->on ); + } + + /** + * Unix timestamp for previous cron execution or false if never executed. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return int|false + */ + private function cron_last_execution( $name ) { + $this->_logger->entrance( $name ); + + return $this->_storage->get( "{$name}_timestamp" ); + } + + /** + * Set cron execution time to now. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + */ + private function set_cron_execution_timestamp( $name ) { + $this->_logger->entrance( $name ); + + $this->_storage->store( "{$name}_timestamp", time() ); + } + + /** + * Sets the keepalive time to now. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @param bool|null $use_network_level_storage + */ + private function set_keepalive_timestamp( $use_network_level_storage = null ) { + $this->_logger->entrance(); + + $this->_storage->store( 'keepalive_timestamp', time(), $use_network_level_storage ); + } + + /** + * Check if cron was executed in the last $period of seconds. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param int $period In seconds + * + * @return bool + */ + private function is_cron_executed( $name, $period = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( $name ); + + $last_execution = $this->cron_last_execution( $name ); + + if ( ! is_numeric( $last_execution ) ) { + return false; + } + + return ( $last_execution > ( WP_FS__SCRIPT_START_TIME - $period ) ); + } + + /** + * WP Cron is executed on a site level. When running in a multisite network environment + * with the network integration activated, for optimization reasons, we are consolidating + * the installs data sync cron to be executed only from a single site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $except_blog_id Target any except the excluded blog ID. + * + * @return int + */ + private function get_cron_target_blog_id( $except_blog_id = 0 ) { + if ( ! is_multisite() ) { + return 0; + } + + if ( $this->_is_network_active && + is_numeric( $this->_storage->network_install_blog_id ) && + $except_blog_id != $this->_storage->network_install_blog_id && + self::is_site_active( $this->_storage->network_install_blog_id ) + ) { + // Try to run cron from the main network blog. + $install = $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ); + + if ( is_object( $install ) && + ( $this->is_premium() || $install->is_tracking_allowed() ) + ) { + return $this->_storage->network_install_blog_id; + } + } + + // Get first opted-in blog ID with active tracking. + $installs = $this->get_blog_install_map(); + foreach ( $installs as $blog_id => $install ) { + if ( $except_blog_id != $blog_id && + self::is_site_active( $blog_id ) && + ( $this->is_premium() || $install->is_tracking_allowed() ) + ) { + return $blog_id; + } + } + + return 0; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * @param bool $is_network_clear If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_cron( $name, $action_tag = '', $is_network_clear = false ) { + $this->_logger->entrance( $name ); + + if ( ! $this->is_cron_on( $name ) ) { + return; + } + + $clear_cron = true; + if ( ! $is_network_clear && $this->_is_network_active ) { + $installs = $this->get_blog_install_map(); + + foreach ( $installs as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_tracking_allowed() ) { + $clear_cron = false; + break; + } + } + } + + if ( ! $clear_cron ) { + return; + } + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? + $cron_data->blog_id : + 0; + + $this->clear_cron_data( $name ); + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( empty( $action_tag ) ) { + $action_tag = $name; + } + + wp_clear_scheduled_hook( $this->get_action_tag( $action_tag ) ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + } + + /** + * Unix timestamp for next cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * + * @return int|false + */ + private function get_next_scheduled_cron( $name, $action_tag = '' ) { + $this->_logger->entrance( $name ); + + if ( ! $this->is_cron_on( $name ) ) { + return false; + } + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? + $cron_data->blog_id : + 0; + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( empty( $action_tag ) ) { + $action_tag = $name; + } + + $next_scheduled = wp_next_scheduled( $this->get_action_tag( $action_tag ) ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + + return $next_scheduled; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * @param string $recurrence 'single' or 'daily'. + * @param int $start_at Defaults to now. + * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. + * @param int $except_blog_id Target any except the excluded blog ID. + */ + private function schedule_cron( + $name, + $action_tag = '', + $recurrence = 'single', + $start_at = WP_FS__SCRIPT_START_TIME, + $randomize_start = true, + $except_blog_id = 0 + ) { + $this->_logger->entrance( $name ); + + $this->clear_cron( $name, $action_tag, true ); + + $cron_blog_id = $this->get_cron_target_blog_id( $except_blog_id ); + + if ( is_multisite() && 0 == $cron_blog_id ) { + // Don't schedule cron since couldn't find a target blog. + return; + } + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( 'daily' === $recurrence ) { + if ( $randomize_start ) { + // Schedule first sync with a random 12 hour time range from now. + $start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) ); + } + + // Schedule daily WP cron. + wp_schedule_event( + $start_at, + 'daily', + $this->get_action_tag( $action_tag ) + ); + } else if ( 'single' === $recurrence ) { + // Schedule single cron. + wp_schedule_single_event( + $start_at, + $this->get_action_tag( $action_tag ) + ); + } + + $this->set_cron_data( $name, $cron_blog_id ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + } + + /** + * Consolidated cron execution for performance optimization. The max number of API requests is based on the number of unique opted-in users. + * that doesn't halt page loading. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param callable $callable The function that should be executed. + */ + private function execute_cron( $name, $callable ) { + $this->_logger->entrance( $name ); + + // Store the last time data sync was executed. + $this->set_cron_execution_timestamp( $name ); + + // Check if API is temporary down. + if ( FS_Api::is_temporary_down() ) { + return; + } + + // @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours. + + $users_2_blog_ids = array(); + + if ( ! is_multisite() ) { + // Add dummy blog. + $users_2_blog_ids[0] = array( 0 ); + } else { + $installs = $this->get_blog_install_map(); + foreach ( $installs as $blog_id => $install ) { + if ( $this->is_premium() || $install->is_tracking_allowed() ) { + if ( ! isset( $users_2_blog_ids[ $install->user_id ] ) ) { + $users_2_blog_ids[ $install->user_id ] = array(); + } + + $users_2_blog_ids[ $install->user_id ][] = $blog_id; + } + } + } + + $current_blog_id = get_current_blog_id(); + + foreach ( $users_2_blog_ids as $user_id => $blog_ids ) { + if ( 0 < $blog_ids[0] ) { + $this->switch_to_blog( $blog_ids[0] ); + } + + call_user_func_array( $callable, array( $blog_ids, ( is_multisite() ? $current_blog_id : null ) ) ); + + foreach ( $blog_ids as $blog_id ) { + $this->do_action( "after_{$name}_cron", $blog_id ); + } + } + + if ( is_multisite() ) { + $this->switch_to_blog( $current_blog_id, fs_is_network_admin() ? $this->get_network_install() : null ); + + $this->do_action( "after_{$name}_cron_multisite" ); + } + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Daily Sync Cron + #---------------------------------------------------------------------------------- + + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function is_sync_cron_scheduled() { + return $this->is_cron_on( 'sync' ); + } + + /** + * Get the sync cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return int + */ + private function get_sync_cron_blog_id() { + return $this->get_cron_blog_id( 'sync' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function run_manual_sync() { + self::require_pluggable_essentials(); + + if ( ! $this->is_user_admin() ) { + return; + } + + // Run manual sync. + $this->_sync_cron(); + + // Reschedule next cron to run 24 hours from now (performance optimization). + $this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false ); + } + + /** + * Data sync cron job. Replaces the background sync non blocking HTTP request + * that doesn't halt page loading. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. + */ + function _sync_cron() { + $this->_logger->entrance(); + + $this->execute_cron( 'sync', array( &$this, '_sync_cron_method' ) ); + } + + /** + * The actual data sync cron logic. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int[] $blog_ids + * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the + * `_sync_plugin_license` method in order to switch to the previous blog when sending + * updates for a single site in case `execute_cron` has switched to a different blog. + */ + function _sync_cron_method( array $blog_ids, $current_blog_id = null ) { + if ( $this->is_registered() ) { + $this->sync_user_beta_mode(); + + if ( $this->has_paid_plan() ) { + // Initiate background plan sync. + $this->_sync_license( true, false, $current_blog_id ); + + if ( $this->is_paying() ) { + // Check for premium plugin updates. + $this->check_updates( true ); + } + } else { + // Sync install(s) (only if something changed locally). + if ( 1 < count( $blog_ids ) ) { + $this->sync_installs(); + } else { + $this->sync_install(); + } + } + } + } + + /** + * Check if sync was executed in the last $period of seconds. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $period In seconds + * + * @return bool + */ + private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) { + return $this->is_cron_executed( 'sync', $period ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function is_sync_cron_on() { + return $this->is_cron_on( 'sync' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $start_at Defaults to now. + * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. + * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. + */ + private function schedule_sync_cron( + $start_at = WP_FS__SCRIPT_START_TIME, + $randomize_start = true, + $except_blog_id = 0 + ) { + $this->schedule_cron( + 'sync', + 'data_sync', + 'daily', + $start_at, + $randomize_start, + $except_blog_id + ); + } + + /** + * Add the actual sync function to the cron job hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function hook_callback_to_sync_cron() { + $this->add_action( 'data_sync', array( &$this, '_sync_cron' ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_sync_cron( $is_network_clear = false ) { + $this->_logger->entrance(); + + $this->clear_cron( 'sync', 'data_sync', $is_network_clear ); + } + + /** + * Unix timestamp for next sync cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function next_sync_cron() { + return $this->get_next_scheduled_cron( 'sync', 'data_sync' ); + } + + /** + * Unix timestamp for previous sync cron execution or false if never executed. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function last_sync_cron() { + return $this->cron_last_execution( 'sync' ); + } + + #endregion Daily Sync Cron ------------------------------------------------------------------ + + #---------------------------------------------------------------------------------- + #region Async Install Sync + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function is_install_sync_scheduled() { + return $this->is_cron_on( 'install_sync' ); + } + + /** + * Get the sync cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return int + */ + private function get_install_sync_cron_blog_id() { + return $this->get_cron_blog_id( 'install_sync' ); + } + + /** + * Instead of running blocking install sync event, execute non blocking scheduled wp-cron. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. + */ + private function schedule_install_sync( $except_blog_id = 0 ) { + $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id ); + } + + /** + * Unix timestamp for previous install sync cron execution or false if never executed. + * + * @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function last_install_sync() { + return $this->cron_last_execution( 'install_sync' ); + } + + /** + * Unix timestamp for next install sync cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function next_install_sync() { + return $this->get_next_scheduled_cron( 'install_sync', 'install_sync' ); + } + + /** + * Add the actual install sync function to the cron job hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function hook_callback_to_install_sync() { + $this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_install_sync_cron( $is_network_clear = false ) { + $this->_logger->entrance(); + + $this->clear_cron( 'install_sync', 'install_sync', $is_network_clear ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. + */ + public function _run_sync_install() { + $this->_logger->entrance(); + + $this->execute_cron( 'sync', array( &$this, '_sync_install_cron_method' ) ); + } + + /** + * The actual install(s) sync cron logic. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int[] $blog_ids + * @param int|null $current_blog_id + */ + function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { + if ( $this->is_registered() ) { + if ( 1 < count( $blog_ids ) ) { + $this->sync_installs( array(), true ); + } else { + $this->sync_install( array(), true ); + } + } + } + + #endregion Async Install Sync ------------------------------------------------------------------ + + /** + * Show a notice that activation is currently pending. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool|string $email + * @param bool $is_pending_trial Since 1.2.1.5 + */ + function _add_pending_activation_notice( $email = false, $is_pending_trial = false ) { + if ( ! is_string( $email ) ) { + $current_user = self::_get_current_wp_user(); + $email = $current_user->user_email; + } + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message' ), + '' . $this->get_plugin_name() . '', + '' . $email . '', + ( $is_pending_trial ? + $this->get_text_inline( 'start the trial', 'start-the-trial' ) : + $this->get_text_inline( 'complete the install', 'complete-the-install' ) ) + ), + 'activation_pending', + 'Thanks!' + ); + } + + /** + * Check if currently in plugin activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @return bool + */ + function is_plugin_activation() { + $result = get_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); + + return !empty($result); + } + + /** + * + * NOTE: admin_menu action executed before admin_init. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _admin_init_action() { + /** + * Automatically redirect to connect/activation page after plugin activation. + * + * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode. + */ + if ( $this->is_plugin_activation() ) { + delete_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); + + if ( isset( $_GET['activate-multi'] ) ) { + /** + * Don't redirect if activating multiple plugins at once (bulk activation). + */ + } else { + $this->_redirect_on_activation_hook(); + return; + } + } + + if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) { + check_admin_referer( $this->get_unique_affix() . '_skip_activation' ); + + $this->skip_connection( null, fs_is_network_admin() ); + + fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ); + } + + if ( $this->is_network_activation_mode() && + fs_request_is_action( $this->get_unique_affix() . '_delegate_activation' ) + ) { + check_admin_referer( $this->get_unique_affix() . '_delegate_activation' ); + + $this->delegate_connection(); + + fs_redirect( $this->get_after_activation_url( 'after_delegation_url' ) ); + } + + $this->_add_upgrade_action_link(); + + if ( ! $this->is_addon() && + ! ( ! $this->_is_network_active && fs_is_network_admin() ) && + ( + ( true === $this->_storage->require_license_activation ) || + // Not registered nor anonymous. + ( ! $this->is_registered() && ! $this->is_anonymous() ) || + // OR, network level and in network upgrade mode. + ( fs_is_network_admin() && $this->_is_network_active && $this->is_network_upgrade_mode() ) + ) + ) { + if ( ! $this->is_pending_activation() ) { + if ( ! $this->_menu->is_main_settings_page() ) { + /** + * If a user visits any other admin page before activating the premium-only theme with a valid + * license, reactivate the previous theme. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( $this->is_theme() && + ! $this->has_settings_menu() && + ! isset( $_REQUEST['fs_action'] ) && + $this->can_activate_previous_theme() + ) { + if ( $this->is_only_premium() ) { + $this->activate_previous_theme(); + return; + } + + if ( true === $this->_storage->require_license_activation ) { + $this->_storage->require_license_activation = false; + } + } + + if ( ! fs_is_network_admin() && + $this->is_network_activation_mode() && + ! $this->is_delegated_connection() + ) { + return; + } + + if ( $this->is_plugin_new_install() || $this->is_only_premium() ) { + if ( ! $this->_anonymous_mode ) { + // Show notice for new plugin installations. + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'You are just one step away - %s', 'you-are-step-away' ), + sprintf( '%s', + $this->get_activation_url( array(), ! $this->is_delegated_connection() ), + sprintf( $this->get_text_x_inline( 'Complete "%s" Activation Now', + '%s - plugin name. As complete "PluginX" activation now', 'activate-x-now' ), $this->get_plugin_name() ) + ) + ), + '', + 'update-nag' + ); + } + } else { + if ( $this->should_add_sticky_optin_notice() ) { + $this->add_sticky_optin_admin_notice(); + } + + if ( $this->has_filter( 'optin_pointer_element' ) ) { + // Don't show admin nag if plugin update. + wp_enqueue_script( 'wp-pointer' ); + wp_enqueue_style( 'wp-pointer' ); + + $this->_enqueue_connect_essentials(); + + add_action( 'admin_print_footer_scripts', array( + $this, + '_add_connect_pointer_script' + ) ); + } + } + } + } + + if ( $this->is_theme() && + $this->_menu->is_main_settings_page() + ) { + $this->_show_theme_activation_optin_dialog(); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function should_add_sticky_optin_notice() { + if ( fs_is_network_admin() ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( ! $this->is_network_activation_mode() ) { + return false; + } + + return ! isset( $this->_storage->sticky_optin_added_ms ); + } + + if ( ! $this->is_activation_mode() ) { + return false; + } + + // If running from a blog admin and delegated the connection. + return ! isset( $this->_storage->sticky_optin_added ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function add_sticky_optin_admin_notice() { + if ( ! $this->_is_network_active || ! fs_is_network_admin() ) { + $this->_storage->sticky_optin_added = true; + } else { + $this->_storage->sticky_optin_added_ms = true; + } + + // Show notice for new plugin installations. + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'We made a few tweaks to the %s, %s', 'few-plugin-tweaks' ), + $this->_module_type, + sprintf( '%s', + $this->get_activation_url(), + sprintf( $this->get_text_inline( 'Opt in to make "%s" better!', 'optin-x-now' ), $this->get_plugin_name() ) + ) + ), + 'connect_account', + '', + 'update-nag' + ); + } + + /** + * Enqueue connect requires scripts and styles. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + function _enqueue_connect_essentials() { + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'json2' ); + + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); + + fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); + } + + /** + * Add connect / opt-in pointer. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + function _add_connect_pointer_script() { + $vars = array( 'id' => $this->_module_id ); + $pointer_content = fs_get_template( 'connect.php', $vars ); + ?> + + _menu->get_raw_slug() ) || + fs_is_plugin_page( $this->_slug ); + } + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Delete site install from Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store + * @param int|null $blog_id Since 2.0.0 + * + * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). + */ + function _delete_site( $store = true, $blog_id = null ) { + return self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store, $blog_id ); + } + + /** + * Delete site install from Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $slug + * @param string $module_type + * @param bool $store + * @param int|null $blog_id Since 2.0.0 + * + * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). + */ + static function _delete_site_by_slug( $slug, $module_type, $store = true, $blog_id = null ) { + $sites = self::get_all_sites( $module_type, $blog_id ); + + $install_id = false; + + if ( isset( $sites[ $slug ] ) ) { + if ( is_object( $sites[ $slug ] ) ) { + $install_id = $sites[ $slug ]->id; + } + + unset( $sites[ $slug ] ); + + self::set_account_option_by_module( $module_type, 'sites', $sites, $store, $blog_id ); + } + + return $install_id; + } + + /** + * Delete user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param bool $store + * + * @return false|int The user ID if deleted. Otherwise, FALSE (when install not exist). + */ + private static function delete_user( $user_id, $store = true ) { + $users = self::get_all_users(); + + if ( ! is_array( $users ) || ! isset( $users[ $user_id ] ) ) { + return false; + } + + unset( $users[ $user_id ] ); + + self::$_accounts->set_option( 'users', $users, $store ); + + return $user_id; + } + + /** + * Delete plugin's plans information. + * + * @param bool $store Flush to Database if true. + * @param bool $keep_associated_plans If set to false, delete all plans, even if a plan is associated with an install. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _delete_plans( $store = true, $keep_associated_plans = true ) { + $this->_logger->entrance(); + + $plans = self::get_all_plans( $this->_module_type ); + + $plans_to_keep = array(); + + if ( $keep_associated_plans ) { + $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); + foreach ( $plans_ids_to_keep as $plan_id ) { + $plan = self::_get_plan_by_id( $plan_id ); + if ( is_object( $plan ) ) { + $plans_to_keep[] = self::_encrypt_entity( $plan ); + } + } + } + + if ( ! empty( $plans_to_keep ) ) { + $plans[ $this->_slug ] = $plans_to_keep; + } else { + unset( $plans[ $this->_slug ] ); + } + + $this->set_account_option( 'plans', $plans, $store ); + } + + /** + * Delete all plugin licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $store + */ + private function _delete_licenses( $store = true ) { + $this->_logger->entrance(); + + $all_licenses = self::get_all_licenses(); + + unset( $all_licenses[ $this->_module_id ] ); + + self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); + } + + /** + * Check if Freemius was added on new plugin installation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + function is_plugin_new_install() { + return isset( $this->_storage->is_plugin_new_install ) && + $this->_storage->is_plugin_new_install; + } + + /** + * Check if it's the first plugin release that is running Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + function is_first_freemius_powered_version() { + return empty( $this->_storage->plugin_last_version ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool|string + */ + private function get_previous_theme_slug() { + return isset( $this->_storage->previous_theme ) ? + $this->_storage->previous_theme : + false; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + private function can_activate_previous_theme() { + $slug = $this->get_previous_theme_slug(); + if ( false !== $slug && current_user_can( 'switch_themes' ) ) { + $theme_instance = wp_get_theme( $slug ); + + return $theme_instance->exists(); + } + + return false; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + private function activate_previous_theme() { + switch_theme( $this->get_previous_theme_slug() ); + unset( $this->_storage->previous_theme ); + + global $pagenow; + if ( 'themes.php' === $pagenow ) { + /** + * Refresh the active theme information. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + fs_redirect( $this->admin_url( $pagenow ) ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_previous_theme_activation_url() { + if ( ! $this->can_activate_previous_theme() ) { + return ''; + } + + /** + * Activation URL + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + return wp_nonce_url( + $this->admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ), + 'switch-theme_' . $this->get_previous_theme_slug() + ); + } + + /** + * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in + * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is + * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous + * theme doesn't exist, then there will be no close button. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string $slug_or_name Old theme's slug or name. + * @param bool|WP_Theme $old_theme WP_Theme instance of the old theme if it still exists. + */ + function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) { + $this->_storage->previous_theme = ( false !== $old_theme ) ? + $old_theme->get_stylesheet() : + $slug_or_name; + + $this->_activate_plugin_event_hook(); + } + + /** + * Plugin activated hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @uses FS_Api + */ + function _activate_plugin_event_hook() { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( ! $this->is_user_admin() ) { + return; + } + + $this->unregister_uninstall_hook(); + + // Clear API cache on activation. + FS_Api::clear_cache(); + + $is_premium_version_activation = $this->is_plugin() ? + ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) ) : + $this->is_premium(); + + $this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' ); + + if ( $this->is_plugin() ) { + // This logic is relevant only to plugins since both the free and premium versions of a plugin can be active at the same time. + // 1. If running in the activation of the FREE module, get the basename of the PREMIUM. + // 2. If running in the activation of the PREMIUM module, get the basename of the FREE. + $other_version_basename = $is_premium_version_activation ? + $this->_free_plugin_basename : + $this->premium_plugin_basename(); + + if ( ! $this->_is_network_active ) { + /** + * Themes are always network activated, but the ACTUAL activation is per site. + * + * During the activation, the plugin isn't yet active, therefore, + * _is_network_active will be set to false even if it's a network level + * activation. So we need to fix that by looking at the is_network_admin() value. + * + * @author Vova Feldman + */ + $this->_is_network_active = ( + $this->_is_multisite_integrated && + fs_is_network_admin() + ); + } + + /** + * If the other module version is active, deactivate it. + * + * is_plugin_active() checks if the plugin is active on the site or the network level and + * deactivate_plugins() deactivates the plugin whether it's activated on the site or network level. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( is_plugin_active( $other_version_basename ) ) { + deactivate_plugins( $other_version_basename ); + } + } + + if ( $this->is_registered() ) { + if ( $is_premium_version_activation ) { + $this->reconnect_locally(); + } + + + // Schedule re-activation event and sync. +// $this->sync_install( array(), true ); + $this->schedule_install_sync(); + + // If activating the premium module version, add an admin notice to congratulate for an upgrade completion. + if ( $is_premium_version_activation ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'The upgrade of %s was successfully completed.', 'successful-version-upgrade-message' ), sprintf( '%s', $this->_plugin->title ) ), + $this->get_text_x_inline( 'W00t', + 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' + ); + } + } else if ( $this->is_anonymous() ) { + if ( isset( $this->_storage->is_anonymous_ms ) && $this->_storage->is_anonymous_ms['is'] ) { + $plugin_version = $this->_storage->is_anonymous_ms['version']; + $network = true; + } else { + $plugin_version = $this->_storage->is_anonymous['version']; + $network = false; + } + + /** + * Reset "skipped" click cache on the following: + * 1. Freemius DEV mode. + * 2. WordPress DEBUG mode. + * 3. If a plugin and the user skipped the exact same version before. + * + * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE). + * + * @todo 4. If explicitly asked to retry after every activation. + */ + if ( WP_FS__DEV_MODE || + ( + ( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) && + $this->get_plugin_version() == $plugin_version + ) + ) { + $this->reset_anonymous_mode( $network ); + } + } + + $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); + + if ( $this->is_addon() && ! $is_trial_or_has_features_enabled_license ) { + /** + * When activating an add-on, try to also activate a license. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $this->_is_network_active ) { + $this->maybe_activate_addon_license(); + } else { + $this->maybe_network_activate_addon_license(); + } + + /** + * Avoid redirecting to the license activation screen after automatically activating an add-on license. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); + + if ( $is_trial_or_has_features_enabled_license && true === $this->_storage->require_license_activation ) { + $this->_storage->require_license_activation = false; + } + } + + if ( + $is_premium_version_activation && + ( + ( ! $this->is_registered() && $this->is_anonymous() ) || + ( + $this->is_registered() && + ! $is_trial_or_has_features_enabled_license + ) + ) + ) { + $this->_storage->require_license_activation = true; + } + + if ( ! isset( $this->_storage->is_plugin_new_install ) ) { + /** + * If no previous version of plugin's version exist, it means that it's either + * the first time that the plugin installed on the site, or the plugin was installed + * before but didn't have Freemius integrated. + * + * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires + * on manual activation via the dashboard, is_plugin_activation() is TRUE + * only after immediate activation. + * + * @since 1.1.4 + * @link https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/ + */ + $this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version ); + } + + if ( ! $this->_anonymous_mode && + $this->has_api_connectivity( WP_FS__DEV_MODE ) && + ! $this->_isAutoInstall + ) { + // Store hint that the plugin was just activated to enable auto-redirection to settings. + set_transient( "fs_{$this->_module_type}_{$this->_slug}_activated", true, 60 ); + } + + /** + * Activation hook is executed after the plugin's main file is loaded, therefore, + * after the plugin was loaded. The logic is located at activate_plugin() + * ./wp-admin/includes/plugin.php. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + $this->_storage->was_plugin_loaded = true; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + private function maybe_activate_addon_license() { + $parent_fs = $this->get_parent_instance(); + + if ( + ! is_object( $parent_fs ) || + ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) + ) { + // Try to activate a license only if the parent plugin is active and has a valid `install`. + return; + } + + $license = $this->get_addon_active_parent_license(); + if ( ! is_object( $license ) ) { + return; + } + + if ( ! $this->is_registered() ) { + // Opt in with a license key. + $this->opt_in( + $parent_fs->get_current_or_network_user()->email, + false, + false, + $license->secret_key + ); + } else { + // Activate the license. + $install = $this->get_api_site_scope()->call( + '/', + 'put', + array( 'license_key' => $this->apply_filters( 'license_key', $license->secret_key ) ) + ); + + if ( ! FS_Api::is_api_error( $install ) ) { + $this->_sync_addon_license( $this->get_id(), true ); + } + } + } + + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Plugin_License $license + */ + private function maybe_network_activate_addon_license( $license = null ) { + $parent_fs = $this->get_parent_instance(); + if ( ! is_object( $parent_fs ) || ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) ) { + // Try to activate a license only if the parent plugin is active and has a valid `install`. + return; + } + + $license = ( ! is_null( $license ) ) ? + $license : + $this->get_addon_active_parent_license(); + + if ( ! is_object( $license ) ) { + return; + } + + if ( ! $this->is_network_registered() ) { + $sites = $this->get_sites_for_network_level_optin(); + + if ( count( $sites ) > $license->left() ) { + // If the add-on is network active, try to activate the license only if it can be activated on all sites. + return; + } + + // Opt in with a license key. + $this->opt_in( + $parent_fs->get_user()->email, + false, + false, + $license->secret_key, + false, + false, + false, + null, + $sites + ); + } else { + $blog_2_install_map = array(); + $site_ids = array(); + + $all_sites = Freemius::get_sites(); + + foreach ( $all_sites as $site ) { + $blog_id = Freemius::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { + // Skip license activation for installs that are already associated with a license. + continue; + } + + if ( is_object( $install ) ) { + $blog_2_install_map[ $blog_id ] = $install; + } else { + $site_ids[] = $blog_id; + } + } + + if ( ( count( $blog_2_install_map ) + count( $site_ids ) ) > $license->left() ) { + return; + } + + $user = $this->get_current_or_network_user(); + + if ( ! empty( $blog_2_install_map ) ) { + $result = $this->activate_license_on_many_installs( $user, $license->secret_key, $blog_2_install_map ); + + if ( true !== $result ) { + return; + } + } + + if ( ! empty( $site_ids ) ) { + $this->activate_license_on_many_sites( $user, $license->secret_key, $site_ids ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return FS_Plugin_License + */ + private function get_addon_active_parent_license() { + $parent_licenses_endpoint = "/plugins/{$this->get_id()}/parent_licenses.json?filter=activatable"; + $parent_instance = $this->get_parent_instance(); + + $foreign_licenses = $parent_instance->get_foreign_licenses_info( + self::get_all_licenses( $this->get_parent_id() ) + ); + + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $parent_licenses_endpoint = add_query_arg( $foreign_licenses, $parent_licenses_endpoint ); + } + + $result = $parent_instance->get_current_or_network_user_api_scope()->get( $parent_licenses_endpoint, true ); + + if ( + ! $this->is_api_result_object( $result, 'licenses' ) || + ! is_array( $result->licenses ) || + empty( $result->licenses ) + ) { + return null; + } + + $license = new FS_Plugin_License( $result->licenses[ 0 ] ); + + return $license; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return array + */ + private function get_sites_for_network_level_optin() { + $sites = array(); + $all_sites = self::get_sites(); + + foreach ( $all_sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( ! $this->is_site_delegated_connection( $blog_id ) && + ! $this->is_installed_on_site( $blog_id ) + ) { + $sites[] = $this->get_site_info( $site ); + } + } + + return $sites; + } + + /** + * Delete account. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function delete_account_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! $this->is_user_admin() ) { + return; + } + + $this->do_action( 'before_account_delete' ); + + // Clear all admin notices. + $this->_admin_notices->clear_all_sticky( false ); + + $this->_delete_site( false ); + + $delete_network_common_data = true; + + if ( $this->_is_network_active ) { + $installs = $this->get_blog_install_map(); + + // Don't delete common network data unless no other installs left. + $delete_network_common_data = empty( $installs ); + } + + if ( $delete_network_common_data ) { + $this->_delete_plans( false ); + + $this->_delete_licenses( false ); + + // Delete add-ons related to plugin's account. + $this->_delete_account_addons( false ); + } + + // @todo Delete plans and licenses of add-ons. + + self::$_accounts->store(); + + /** + * IMPORTANT: + * Clear crons must be executed before clearing all storage. + * Otherwise, the cron will not be cleared. + */ + if ( $delete_network_common_data ) { + $this->clear_sync_cron(); + } + + $this->clear_install_sync_cron(); + + // Clear all storage data. + $this->_storage->clear_all( true, array( + 'is_delegated_connection', + 'connectivity_test', + 'is_on', + ), false ); + + // Send delete event. + $this->get_api_site_scope()->call( '/', 'delete' ); + + $this->do_action( 'after_account_delete' ); + } + + /** + * Delete network level account. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function delete_network_account_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! $this->is_user_admin() ) { + return; + } + + $this->do_action( 'before_network_account_delete' ); + + // Clear all admin notices. + $this->_admin_notices->clear_all_sticky(); + + $this->_delete_plans( false, false ); + + $this->_delete_licenses( false ); + + // Delete add-ons related to plugin's account. + $this->_delete_account_addons( false ); + + // @todo Delete plans and licenses of add-ons. + + self::$_accounts->store( true ); + + /** + * IMPORTANT: + * Clear crons must be executed before clearing all storage. + * Otherwise, the cron will not be cleared. + */ + $this->clear_sync_cron( true ); + $this->clear_install_sync_cron( true ); + + $sites = self::get_sites(); + + $install_ids = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $install_id = $this->_delete_site( true, $blog_id ); + + // Clear all storage data. + $this->_storage->clear_all( true, array( 'connectivity_test' ), $blog_id ); + + if ( FS_Site::is_valid_id( $install_id ) ) { + $install_ids[] = $install_id; + } + + switch_to_blog( $blog_id ); + + $this->do_action( 'after_account_delete' ); + + restore_current_blog(); + } + + $this->_storage->clear_all( true, array( + 'connectivity_test', + 'is_on', + ), true ); + + // Send delete event. + if ( ! empty( $install_ids ) ) { + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', $install_ids ), 'delete' ); + } + + $this->do_action( 'after_network_account_delete' ); + } + + /** + * Plugin deactivation hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + function _deactivate_plugin_hook() { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( ! $this->is_user_admin() ) { + return; + } + + $is_network_deactivation = fs_is_network_admin(); + $storage_keys_for_removal = array(); + + $this->_admin_notices->clear_all_sticky(); + + $storage_keys_for_removal[] = 'sticky_optin_added'; + if ( isset( $this->_storage->sticky_optin_added ) ) { + unset( $this->_storage->sticky_optin_added ); + } + + if ( ! isset( $this->_storage->is_plugin_new_install ) ) { + // Remember that plugin was already installed. + $this->_storage->is_plugin_new_install = false; + } + + // Hook to plugin uninstall. + register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) ); + + $this->clear_module_main_file_cache(); + $this->clear_sync_cron( $this->_is_network_active ); + $this->clear_install_sync_cron(); + + if ( $this->is_registered() ) { + if ( $this->is_premium() && ! $this->has_active_valid_license() ) { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + if ( $is_network_deactivation ) { + // Send deactivation event. + $this->sync_installs( array( + 'is_active' => false, + ) ); + } else { + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + } + } else { + if ( ! $this->has_api_connectivity() ) { + // Reset connectivity test cache. + unset( $this->_storage->connectivity_test ); + + $storage_keys_for_removal[] = 'connectivity_test'; + } + } + + if ( $is_network_deactivation ) { + if ( isset( $this->_storage->sticky_optin_added_ms ) ) { + unset( $this->_storage->sticky_optin_added_ms ); + } + + if ( ! empty( $storage_keys_for_removal ) ) { + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + foreach ( $storage_keys_for_removal as $key ) { + $this->_storage->remove( $key, false, $blog_id ); + } + + $this->_storage->save( $blog_id ); + } + } + } + + // Clear API cache on deactivation. + FS_Api::clear_cache(); + + $this->remove_sdk_reference(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + private function remove_sdk_reference() { + global $fs_active_plugins; + + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( $this->_plugin_basename == $data->plugin_path ) { + unset( $fs_active_plugins->plugins[ $sdk_path ] ); + break; + } + } + + fs_fallback_to_newest_active_sdk(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool $is_anonymous + * @param bool|int $network_or_blog_id Since 2.0.0 + */ + private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = 0 ) { + // Store information regarding skip to try and opt-in the user + // again in the future. + $skip_info = array( + 'is' => $is_anonymous, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + 'version' => $this->get_plugin_version(), + ); + + if ( true === $network_or_blog_id ) { + $this->_storage->is_anonymous_ms = $skip_info; + } else { + $this->_storage->store( 'is_anonymous', $skip_info, $network_or_blog_id ); + } + + $this->network_upgrade_mode_completed(); + + // Update anonymous mode cache. + $this->_is_anonymous = $is_anonymous; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id Site ID. + * @param int $user_id User ID. + * @param string $domain Site domain. + * @param string $path Site path. + * @param int $network_id Network ID. Only relevant on multi-network installations. + * @param array $meta Metadata. Used to set initial site options. + * + * @uses Freemius::is_license_network_active() to check if the context license was network activated by the super-admin. + * @uses Freemius::is_network_connected() to check if the super-admin network opted-in. + * @uses Freemius::is_network_anonymous() to check if the super-admin network skipped. + * @uses Freemius::is_network_delegated_connection() to check if the super-admin network delegated the connection to the site admins. + */ + function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_id, $meta ) { + $this->_logger->entrance(); + + if ( $this->is_premium() && + $this->is_network_connected() && + is_object( $this->_license ) && + $this->_license->can_activate( FS_Site::is_localhost_by_address( $domain ) ) && + $this->is_license_network_active( $blog_id ) + ) { + /** + * Running the premium version, the license was network activated, and the license can also be activated on the current site -> so try to opt-in with the license key. + */ + $current_blog_id = get_current_blog_id(); + $license = clone $this->_license; + + $this->switch_to_blog( $blog_id ); + + // Opt-in with network user. + $this->install_with_user( + $this->get_network_user(), + $license->secret_key, + false, + false, + false + ); + + if ( is_object( $this->_site ) ) { + if ( $this->_site->license_id == $license->id ) { + /** + * If the license was activated successfully, sync the license data from the remote server. + */ + $this->_license = $license; + $this->sync_site_license(); + } + } + + $this->switch_to_blog( $current_blog_id ); + + if ( is_object( $this->_site ) ) { + // Already connected (with or without a license), so no need to continue. + return; + } + } + + if ( $this->is_network_anonymous() ) { + /** + * Opt-in was network skipped so automatically skip the opt-in for the new site. + */ + $this->skip_site_connection( $blog_id ); + } else if ( $this->is_network_delegated_connection() ) { + /** + * Opt-in was network delegated so automatically delegate the opt-in for the new site's admin. + */ + $this->delegate_site_connection( $blog_id ); + } else if ( $this->is_network_connected() ) { + /** + * Opt-in was network activated so automatically opt-in with the network user and new site admin. + */ + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $blog_id ); + + // Opt-in with network user. + $this->install_with_user( + $this->get_network_user(), + false, + false, + false, + false + ); + + $this->switch_to_blog( $current_blog_id ); + } else { + /** + * If the super-admin mixed different options (connect, skip, delegated): + * a) If at least one site connection was delegated, then automatically delegate connection. + * b) Otherwise, it means that at least one site was skipped and at least one site was connected. For a simplified UX in the initial release of the multisite network integration, skip the connection for the newly created site. If the super-admin will want to opt-in they can still do that from the network level Account page. + */ + $has_delegated_site = false; + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + $has_delegated_site = true; + break; + } + } + + if ( $has_delegated_site ) { + $this->delegate_site_connection( $blog_id ); + } else { + $this->skip_site_connection( $blog_id ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool|int $network_or_blog_id Since 2.0.0. + */ + private function reset_anonymous_mode( $network_or_blog_id = 0 ) { + if ( true === $network_or_blog_id ) { + unset( $this->_storage->is_anonymous_ms ); + } else { + $this->_storage->remove( 'is_anonymous', true, $network_or_blog_id ); + } + + /** + * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module + * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click + * on the admin notice that contains the opt-in link in order to trigger the opt-in popup). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( ! $this->_is_network_active || + 0 === $network_or_blog_id || + get_current_blog_id() == $network_or_blog_id || + ( true === $network_or_blog_id && fs_is_network_admin() ) + ) { + $this->_is_anonymous = null; + } + } + + /** + * This is used to ensure that before redirecting to the opt-in page after resetting the anonymous mode or + * deleting the account in the network level, the URL of the page to redirect to is correct. + * + * @author Leo Fajardo (@leorw) + * + * @since 2.1.3 + */ + private function maybe_set_slug_and_network_menu_exists_flag() { + if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + $this->_menu->set_slug_and_network_menu_exists_flag( $this->_menu->has_menu() ? + $this->_menu->get_slug() : + $this->_slug + ); + } + } + + /** + * Clears the anonymous mode and redirects to the opt-in screen. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + */ + function connect_again() { + if ( ! $this->is_anonymous() ) { + return; + } + + $this->reset_anonymous_mode( fs_is_network_admin() ); + + $this->maybe_set_slug_and_network_menu_exists_flag(); + + fs_redirect( $this->get_activation_url() ); + } + + /** + * Skip account connect, and set anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param array|null $sites Since 2.0.0. Specific sites. + * @param bool $skip_all_network Since 2.0.0. If true, skip connection for all sites. + */ + function skip_connection( $sites = null, $skip_all_network = false ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $skip_all_network ) { + $this->set_anonymous_mode( true, true ); + } + + if ( ! $skip_all_network && empty( $sites ) ) { + $this->skip_site_connection(); + } else { + $uids = array(); + + if ( $skip_all_network ) { + $this->set_anonymous_mode( true, true ); + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $this->skip_site_connection( $blog_id, false ); + $uids[] = $this->get_anonymous_id( $blog_id ); + } + } else if ( ! empty( $sites ) ) { + foreach ( $sites as $site ) { + $uids[] = $site['uid']; + $this->skip_site_connection( $site['blog_id'], false ); + } + } + + // Send anonymous skip event. + // No user identified info nor any tracking will be sent after the user skips the opt-in. + $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( + 'uids' => $uids, + ) ); + } + + $this->network_upgrade_mode_completed(); + } + + /** + * Skip connection for specific site in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * @param bool $send_skip + */ + private function skip_site_connection( $blog_id = null, $send_skip = true ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account', $blog_id ); + + $this->set_anonymous_mode( true, $blog_id ); + + if ( $send_skip ) { + $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( + 'uids' => array( $this->get_anonymous_id( $blog_id ) ), + ) ); + } + } + + /** + * Plugin version update hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + */ + private function update_plugin_version_event() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return; + } + + $this->schedule_install_sync(); +// $this->sync_install( array(), true ); + } + + /** + * Generate an MD5 signature of a plugins collection. + * This helper methods used to identify changes in a plugins collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array [string]array $plugins + * + * @return string + */ + private function get_plugins_thumbprint( $plugins ) { + ksort( $plugins ); + + $thumbprint = ''; + foreach ( $plugins as $basename => $data ) { + $thumbprint .= $data['slug'] . ',' . + $data['Version'] . ',' . + ( $data['is_active'] ? '1' : '0' ) . ';'; + } + + return md5( $thumbprint ); + } + + /** + * Return a list of modified plugins since the last sync. + * + * Note: + * There's no point to store a plugins counter since even if the number of + * plugins didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_plugins_data_for_api() { + // Alias. + $site_active_plugins_option_name = 'active_plugins'; + $network_plugins_option_name = 'all_plugins'; + + /** + * Collection of all site level active plugins. + */ + $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); + + if ( ! is_object( $site_active_plugins_cache ) ) { + $site_active_plugins_cache = (object) array( + 'timestamp' => '', + 'md5' => '', + 'plugins' => array(), + ); + } + + $time = time(); + + if ( ! empty( $site_active_plugins_cache->timestamp ) && + ( $time - $site_active_plugins_cache->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC + ) { + // Don't send plugin updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $site_active_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); + + // Reload options from DB. + self::$_accounts->load( true ); + $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); + + if ( $time != $site_active_plugins_cache->timestamp ) { + // If timestamp is different, then another thread captured the lock. + return false; + } + + /** + * Collection of all plugins (network level). + */ + $network_plugins_cache = self::$_accounts->get_option( $network_plugins_option_name ); + + if ( ! is_object( $network_plugins_cache ) ) { + $network_plugins_cache = (object) array( + 'timestamp' => '', + 'md5' => '', + 'plugins' => array(), + ); + } + + // Check if there's a change in plugins. + $network_plugins = self::get_network_plugins(); + $site_active_plugins = self::get_site_active_plugins(); + + $network_plugins_thumbprint = $this->get_plugins_thumbprint( $network_plugins ); + $site_active_plugins_thumbprint = $this->get_plugins_thumbprint( $site_active_plugins ); + + // Check if plugins status changed (version or active/inactive). + $network_plugins_changed = ( $network_plugins_cache->md5 !== $network_plugins_thumbprint ); + $site_active_plugins_changed = ( $site_active_plugins_cache->md5 !== $site_active_plugins_thumbprint ); + + if ( ! $network_plugins_changed && + ! $site_active_plugins_changed + ) { + // No changes. + return array(); + } + + $plugins_update_data = array(); + + foreach ( $network_plugins_cache->plugins as $basename => $data ) { + if ( ! isset( $network_plugins[ $basename ] ) ) { + // Plugin uninstalled. + $uninstalled_plugin_data = $data; + $uninstalled_plugin_data['is_active'] = false; + $uninstalled_plugin_data['is_uninstalled'] = true; + $plugins_update_data[] = $uninstalled_plugin_data; + + unset( $network_plugins[ $basename ] ); + + unset( $network_plugins_cache->plugins[ $basename ] ); + unset( $site_active_plugins_cache->plugins[ $basename ] ); + + continue; + } + + $was_active = $data['is_active'] || + ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && + true === $site_active_plugins_cache->plugins[ $basename ]['is_active'] ); + $is_active = $network_plugins[ $basename ]['is_active'] || + ( isset( $site_active_plugins[ $basename ] ) && + $site_active_plugins[ $basename ]['is_active'] ); + + if ( ! isset( $site_active_plugins_cache->plugins[ $basename ] ) && + isset( $site_active_plugins[ $basename ] ) + ) { + // Plugin was site level activated. + $site_active_plugins_cache->plugins[ $basename ] = $network_plugins[ $basename ]; + $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; + } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && + ! isset( $site_active_plugins[ $basename ] ) + ) { + // Plugin was site level deactivated. + unset( $site_active_plugins_cache->plugins[ $basename ] ); + } + + $prev_version = $data['version']; + $current_version = $network_plugins[ $basename ]['Version']; + + if ( $was_active !== $is_active || $prev_version !== $current_version ) { + // Plugin activated or deactivated, or version changed. + + if ( $was_active !== $is_active ) { + if ( $data['is_active'] != $network_plugins[ $basename ]['is_active'] ) { + $network_plugins_cache->plugins[ $basename ]['is_active'] = $data['is_active']; + } + } + + if ( $prev_version !== $current_version ) { + $network_plugins_cache->plugins[ $basename ]['Version'] = $current_version; + } + + $updated_plugin_data = $data; + $updated_plugin_data['is_active'] = $is_active; + $updated_plugin_data['version'] = $current_version; + $updated_plugin_data['title'] = $network_plugins[ $basename ]['Name']; + $plugins_update_data[] = $updated_plugin_data; + } + } + + // Find new plugins that weren't yet seen before. + foreach ( $network_plugins as $basename => $data ) { + if ( ! isset( $network_plugins_cache->plugins[ $basename ] ) ) { + // New plugin. + $new_plugin = array( + 'slug' => $data['slug'], + 'version' => $data['Version'], + 'title' => $data['Name'], + 'is_active' => $data['is_active'], + 'is_uninstalled' => false, + ); + + $plugins_update_data[] = $new_plugin; + $network_plugins_cache->plugins[ $basename ] = $new_plugin; + + if ( isset( $site_active_plugins[ $basename ] ) ) { + $site_active_plugins_cache->plugins[ $basename ] = $new_plugin; + $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; + } + } + } + + $site_active_plugins_cache->md5 = $site_active_plugins_thumbprint; + $site_active_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); + + $network_plugins_cache->md5 = $network_plugins_thumbprint; + $network_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $network_plugins_option_name, $network_plugins_cache, true ); + + return $plugins_update_data; + } + + /** + * Return a list of modified themes since the last sync. + * + * Note: + * There's no point to store a themes counter since even if the number of + * themes didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_themes_data_for_api() { + // Alias. + $option_name = 'all_themes'; + + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ( ! is_object( $all_cached_themes ) ) { + $all_cached_themes = (object) array( + 'timestamp' => '', + 'md5' => '', + 'themes' => array(), + ); + } + + $time = time(); + + if ( ! empty( $all_cached_themes->timestamp ) && + ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC + ) { + // Don't send theme updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $all_cached_themes->timestamp = $time; + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + + // Reload options from DB. + self::$_accounts->load( true ); + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ( $time != $all_cached_themes->timestamp ) { + // If timestamp is different, then another thread captured the lock. + return false; + } + + // Get active theme. + $active_theme = wp_get_theme(); + $active_theme_stylesheet = $active_theme->get_stylesheet(); + + // Check if there's a change in themes. + $all_themes = wp_get_themes(); + + // Check if themes changed. + ksort( $all_themes ); + + $themes_signature = ''; + foreach ( $all_themes as $slug => $data ) { + $is_active = ( $slug === $active_theme_stylesheet ); + $themes_signature .= $slug . ',' . + $data->version . ',' . + ( $is_active ? '1' : '0' ) . ';'; + } + + // Check if themes status changed (version or active/inactive). + $themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) ); + + $themes_update_data = array(); + + if ( $themes_changed ) { + // Change in themes, report changes. + + // Update existing themes info. + foreach ( $all_cached_themes->themes as $slug => $data ) { + $is_active = ( $slug === $active_theme_stylesheet ); + + if ( ! isset( $all_themes[ $slug ] ) ) { + // Plugin uninstalled. + $uninstalled_theme_data = $data; + $uninstalled_theme_data['is_active'] = false; + $uninstalled_theme_data['is_uninstalled'] = true; + $themes_update_data[] = $uninstalled_theme_data; + + unset( $all_themes[ $slug ] ); + unset( $all_cached_themes->themes[ $slug ] ); + } else if ( $data['is_active'] !== $is_active || + $data['version'] !== $all_themes[ $slug ]->version + ) { + // Plugin activated or deactivated, or version changed. + + $all_cached_themes->themes[ $slug ]['is_active'] = $is_active; + $all_cached_themes->themes[ $slug ]['version'] = $all_themes[ $slug ]->version; + + $themes_update_data[] = $all_cached_themes->themes[ $slug ]; + } + } + + // Find new themes that weren't yet seen before. + foreach ( $all_themes as $slug => $data ) { + if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) { + $is_active = ( $slug === $active_theme_stylesheet ); + + // New plugin. + $new_plugin = array( + 'slug' => $slug, + 'version' => $data->version, + 'title' => $data->name, + 'is_active' => $is_active, + 'is_uninstalled' => false, + ); + + $themes_update_data[] = $new_plugin; + $all_cached_themes->themes[ $slug ] = $new_plugin; + } + } + + $all_cached_themes->md5 = md5( $themes_signature ); + $all_cached_themes->timestamp = time(); + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + } + + return $themes_update_data; + } + + /** + * Get site data for API install request. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @param string[] $override + * @param bool $include_plugins Since 1.1.8 by default include plugin changes. + * @param bool $include_themes Since 1.1.8 by default include plugin changes. + * @param bool $include_blog_data Since 2.3.0 by default include the current blog's data (language, charset, title, and URL). + * + * @return array + */ + private function get_install_data_for_api( + array $override, + $include_plugins = true, + $include_themes = true, + $include_blog_data = true + ) { + if ( ! defined( 'WP_FS__TRACK_PLUGINS' ) || false !== WP_FS__TRACK_PLUGINS ) { + /** + * @since 1.1.8 Also send plugin updates. + */ + if ( $include_plugins && ! isset( $override['plugins'] ) ) { + $plugins = $this->get_plugins_data_for_api(); + if ( ! empty( $plugins ) ) { + $override['plugins'] = $plugins; + } + } + } + + if ( ! defined( 'WP_FS__TRACK_THEMES' ) || false !== WP_FS__TRACK_THEMES ) { + /** + * @since 1.1.8 Also send themes updates. + */ + if ( $include_themes && ! isset( $override['themes'] ) ) { + $themes = $this->get_themes_data_for_api(); + if ( ! empty( $themes ) ) { + $override['themes'] = $themes; + } + } + } + + $versions = $this->get_versions(); + + $blog_data = $include_blog_data ? + array( + 'language' => get_bloginfo( 'language' ), + 'charset' => get_bloginfo( 'charset' ), + 'title' => get_bloginfo( 'name' ), + 'url' => get_site_url(), + ) : + array(); + + return array_merge( $versions, $blog_data, array( + 'version' => $this->get_plugin_version(), + 'is_premium' => $this->is_premium(), + // Special params. + 'is_active' => true, + 'is_disconnected' => $this->is_tracking_prohibited(), + 'is_uninstalled' => false, + ), $override ); + } + + /** + * Update installs details. + * + * @todo V1 of multiste network support doesn't support plugin and theme data sending. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string[] string $override + * @param bool $only_diff + * @param bool $include_plugins Since 1.1.8 by default include plugin changes. + * @param bool $include_themes Since 1.1.8 by default include plugin changes. + * + * @return array + */ + private function get_installs_data_for_api( + array $override, + $only_diff = false, + $include_plugins = true, + $include_themes = true + ) { + /** + * @since 1.1.8 Also send plugin updates. + */ +// if ( $include_plugins && ! isset( $override['plugins'] ) ) { +// $plugins = $this->get_plugins_data_for_api(); +// if ( ! empty( $plugins ) ) { +// $override['plugins'] = $plugins; +// } +// } + /** + * @since 1.1.8 Also send themes updates. + */ +// if ( $include_themes && ! isset( $override['themes'] ) ) { +// $themes = $this->get_themes_data_for_api(); +// if ( ! empty( $themes ) ) { +// $override['themes'] = $themes; +// } +// } + + // Common properties. + $versions = $this->get_versions(); + $common = array_merge( $versions, array( + 'version' => $this->get_plugin_version(), + 'is_premium' => $this->is_premium(), + ), $override ); + + + $is_common_diff_for_any_site = false; + $common_diff_union = array(); + + $installs_data = array(); + + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + if ( $install->user_id != $this->_user->id ) { + // Install belongs to a different owner. + continue; + } + + if ( ! $this->is_premium() && $install->is_tracking_prohibited() ) { + // Don't send updates regarding opted-out installs. + continue; + } + + $install_data = $this->get_site_info( $site ); + + $uid = $install_data['uid']; + + unset( $install_data['blog_id'] ); + unset( $install_data['uid'] ); + + $install_data['is_disconnected'] = $install->is_disconnected; + $install_data['is_active'] = $this->is_active_for_site( $blog_id ); + $install_data['is_uninstalled'] = $install->is_uninstalled; + + $common_diff = null; + $is_common_diff = false; + if ( $only_diff ) { + $install_data = $this->get_install_diff_for_api( $install_data, $install, $override ); + $common_diff = $this->get_install_diff_for_api( $common, $install, $override ); + + $is_common_diff = ! empty( $common_diff ); + + if ( $is_common_diff ) { + foreach ( $common_diff as $k => $v ) { + if ( ! isset( $common_diff_union[ $k ] ) ) { + $common_diff_union[ $k ] = $v; + } + } + } + + $is_common_diff_for_any_site = $is_common_diff_for_any_site || $is_common_diff; + } + + if ( ! empty( $install_data ) || $is_common_diff ) { + // Add install ID and site unique ID. + $install_data['id'] = $install->id; + $install_data['uid'] = $uid; + + $installs_data[] = $install_data; + } + } + } + + restore_current_blog(); + + if ( 0 < count( $installs_data ) && ( $is_common_diff_for_any_site || ! $only_diff ) ) { + if ( ! $only_diff ) { + $installs_data[] = $common; + } else if ( ! empty( $common_diff_union ) ) { + $installs_data[] = $common_diff_union; + } + } + + foreach ( $installs_data as &$data ) { + $data = (object) $data; + } + + return $installs_data; + } + + /** + * Compare site actual data to the stored install data and return the differences for an API data sync. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array $site + * @param FS_Site $install + * @param string[] string $override + * + * @return array + */ + private function get_install_diff_for_api( $site, $install, $override = array() ) { + $diff = array(); + $special = array(); + $special_override = false; + + foreach ( $site as $p => $v ) { + if ( property_exists( $install, $p ) ) { + if ( ( is_bool( $install->{$p} ) || ! empty( $install->{$p} ) ) && + $install->{$p} != $v + ) { + $install->{$p} = $v; + $diff[ $p ] = $v; + } + } else { + $special[ $p ] = $v; + + if ( isset( $override[ $p ] ) || + 'plugins' === $p || + 'themes' === $p + ) { + $special_override = true; + } + } + } + + if ( $special_override || 0 < count( $diff ) ) { + // Add special params only if has at least one + // standard param, or if explicitly requested to + // override a special param or a param which is not exist + // in the install object. + $diff = array_merge( $diff, $special ); + } + + return $diff; + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + * + * @return false|object|string + */ + private function send_install_update( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $check_properties = $this->get_install_data_for_api( $override ); + + if ( $flush ) { + $params = $check_properties; + } else { + $params = $this->get_install_diff_for_api( $check_properties, $this->_site, $override ); + } + + $keepalive_only_update = false; + if ( empty( $params ) ) { + $keepalive_only_update = $this->should_send_keepalive_update(); + + if ( ! $keepalive_only_update ) { + /** + * There are no updates to send including keepalive. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return false; + } + } + + if ( ! $keepalive_only_update ) { + /** + * Do not update the last install sync timestamp after a keepalive-only call since there were no actual + * updates sent. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( ! is_multisite() ) { + // Update last install sync timestamp. + $this->set_cron_execution_timestamp( 'install_sync' ); + } + + $params['uid'] = $this->get_anonymous_id(); + } + + $this->set_keepalive_timestamp(); + + // Send updated values to FS. + $site = $this->get_api_site_scope()->call( '/', 'put', $params ); + + if ( ! $keepalive_only_update && $this->is_api_result_entity( $site ) ) { + /** + * Do not clear scheduled sync after a keepalive-only call since there were no actual updates sent. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( ! is_multisite() ) { + // I successfully sent install update, clear scheduled sync if exist. + $this->clear_install_sync_cron(); + } + } + + return $site; + } + + /** + * Update installs only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string[] string $override + * @param bool $flush + * + * @return false|object|string + */ + private function send_installs_update( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $installs_data = $this->get_installs_data_for_api( $override, ! $flush ); + + $keepalive_only_update = false; + if ( empty( $installs_data ) ) { + /** + * Pass `true` to use the network level storage since the update is for many installs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + $keepalive_only_update = $this->should_send_keepalive_update( true ); + + if ( ! $keepalive_only_update ) { + /** + * There are no updates to send including keepalive. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return false; + } + } + + if ( ! $keepalive_only_update ) { + // Update last install sync timestamp if there were actual updates sent (i.e., not a keepalive-only call). + $this->set_cron_execution_timestamp( 'install_sync' ); + } + + /** + * Pass `true` to use the network level storage since the update is for many installs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + $this->set_keepalive_timestamp( true ); + + // Send updated values to FS. + $result = $this->get_api_user_scope()->call( "/plugins/{$this->_plugin->id}/installs.json", 'put', $installs_data ); + + if ( ! $keepalive_only_update && $this->is_api_result_object( $result, 'installs' ) ) { + // I successfully sent installs update (there was an actual update sent and it's not just a keepalive-only call), clear scheduled sync if exist. + $this->clear_install_sync_cron(); + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param bool|null $use_network_level_storage + * + * @return bool + */ + private function should_send_keepalive_update( $use_network_level_storage = null ) { + $keepalive_timestamp = $this->_storage->get( 'keepalive_timestamp', 0, $use_network_level_storage ); + + if ( $keepalive_timestamp < ( time() - WP_FS__TIME_WEEK_IN_SEC ) ) { + // If updated more than 7 days ago, trigger a keepalive and update the time it was triggered. + return true; + } else { + // If updated 7 days ago or less, "flip a coin", if the value is 7 trigger a keepalive and update the last time it was triggered. + return ( 7 == rand( 1, 7 ) ); + } + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + */ + private function sync_install( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $site = $this->send_install_update( $override, $flush ); + + if ( false === $site ) { + // No sync required. + return; + } + + if ( ! $this->is_api_result_entity( $site ) ) { + // Failed to sync, don't update locally. + return; + } + + $this->_site = new FS_Site( $site ); + + $this->_store_site( true ); + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + */ + private function sync_installs( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $result = $this->send_installs_update( $override, $flush ); + + if ( false === $result ) { + // No sync required. + return; + } + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + // Failed to sync, don't update locally. + return; + } + + $address_to_blog_map = $this->get_address_to_blog_map(); + + foreach ( $result->installs as $install ) { + $this->_site = new FS_Site( $install ); + + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id ); + } + } + + /** + * Track install's custom event. + * + * IMPORTANT: + * Custom event tracking is currently only supported for specific clients. + * If you are not one of them, please don't use this method. If you will, + * the API will simply ignore your request based on the plugin ID. + * + * Need custom tracking for your plugin or theme? + * If you are interested in custom event tracking please contact yo@freemius.com + * for further details. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $name Event name. + * @param array $properties Associative key/value array with primitive values only + * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. + * @param bool $once If true, event will be tracked only once. IMPORTANT: Still trigger the API call. + * + * @return object|false Event data or FALSE on failure. + * + * @throws \Freemius_InvalidArgumentException + */ + public function track_event( $name, $properties = array(), $process_at = false, $once = false ) { + $this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) ); + + if ( ! $this->is_registered() ) { + return false; + } + + $event = array( 'type' => $name ); + + if ( is_numeric( $process_at ) && $process_at > time() ) { + $event['process_at'] = $process_at; + } + + if ( $once ) { + $event['once'] = true; + } + + if ( ! empty( $properties ) ) { + // Verify associative array values are primitive. + foreach ( $properties as $k => $v ) { + if ( ! is_scalar( $v ) ) { + throw new Freemius_InvalidArgumentException( 'The $properties argument must be an associative key/value array with primitive values only.' ); + } + } + + $event['properties'] = $properties; + } + + $result = $this->get_api_site_scope()->call( 'events.json', 'post', $event ); + + return $this->is_api_error( $result ) ? + false : + $result; + } + + /** + * Track install's custom event only once, but it still triggers the API call. + * + * IMPORTANT: + * Custom event tracking is currently only supported for specific clients. + * If you are not one of them, please don't use this method. If you will, + * the API will simply ignore your request based on the plugin ID. + * + * Need custom tracking for your plugin or theme? + * If you are interested in custom event tracking please contact yo@freemius.com + * for further details. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $name Event name. + * @param array $properties Associative key/value array with primitive values only + * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. + * + * @return object|false Event data or FALSE on failure. + * + * @throws \Freemius_InvalidArgumentException + * + * @user Freemius::track_event() + */ + public function track_event_once( $name, $properties = array(), $process_at = false ) { + return $this->track_event( $name, $properties, $process_at, true ); + } + + /** + * Plugin uninstall hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function _uninstall_plugin_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! current_user_can( 'activate_plugins' ) ) { + return; + } + + $params = array(); + $uninstall_reason = null; + if ( isset( $this->_storage->uninstall_reason ) ) { + $uninstall_reason = $this->_storage->uninstall_reason; + $params['reason_id'] = $uninstall_reason->id; + $params['reason_info'] = $uninstall_reason->info; + } + + if ( ! $this->is_registered() ) { + // Send anonymous uninstall event only if user submitted a feedback. + if ( isset( $uninstall_reason ) ) { + if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) { + $this->opt_in( false, false, false, false, true ); + } else { + $params['uid'] = $this->get_anonymous_id(); + $this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params ); + } + } + } else { + $params = array_merge( $params, array( + 'is_active' => false, + 'is_uninstalled' => true, + ) ); + + if ( $this->_is_network_active ) { + // Send uninstall event. + $this->send_installs_update( $params ); + } else { + // Send uninstall event. + $this->send_install_update( $params ); + } + } + + // @todo Decide if we want to delete plugin information from db. + } + + /** + * Set the basename of the current product and hook _activate_plugin_event_hook() to the activation action. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @param string $is_premium + * @param string $caller + * + * @return string + */ + function set_basename( $is_premium, $caller ) { + $basename = plugin_basename( $caller ); + + $current_basename = $is_premium ? + $this->_premium_plugin_basename : + $this->_free_plugin_basename; + + if ( $current_basename == $basename ) { + // Basename value set correctly. + return; + } + + if ( $is_premium ) { + $this->_premium_plugin_basename = $basename; + } else { + $this->_free_plugin_basename = $basename; + } + + $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; + + register_activation_hook( + $plugin_dir . $basename, + array( &$this, '_activate_plugin_event_hook' ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @since 2.2.1 If the context product is in its premium version, use the current module's basename, even if it was renamed. + * + * @return string + */ + function premium_plugin_basename() { + if ( ! isset( $this->_premium_plugin_basename ) ) { + $this->_premium_plugin_basename = $this->is_premium() ? + // The product is premium, so use the current basename. + $this->_plugin_basename : + $this->get_premium_slug() . '/' . basename( $this->_free_plugin_basename ); + } + + return $this->_premium_plugin_basename; + } + + /** + * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + */ + public static function _uninstall_plugin_hook() { + self::_load_required_static(); + + self::$_static_logger->entrance(); + + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + $plugin_file = substr( current_filter(), strlen( 'uninstall_' ) ); + + self::$_static_logger->info( 'plugin = ' . $plugin_file ); + + define( 'WP_FS__UNINSTALL_MODE', true ); + + $fs = self::get_instance_by_file( $plugin_file ); + + if ( is_object( $fs ) ) { + self::require_plugin_essentials(); + + if ( is_plugin_active( $fs->_free_plugin_basename ) || + is_plugin_active( $fs->premium_plugin_basename() ) + ) { + // Deleting Free or Premium plugin version while the other version still installed. + return; + } + + $fs->_uninstall_plugin_event(); + + $fs->do_action( 'after_uninstall' ); + } + } + + #---------------------------------------------------------------------------------- + #region Plugin Information + #---------------------------------------------------------------------------------- + + /** + * Load WordPress core plugin.php essential module. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + */ + private static function require_plugin_essentials() { + if ( ! function_exists( 'get_plugins' ) ) { + self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' ); + + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + } + + /** + * Load WordPress core pluggable.php module. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + */ + private static function require_pluggable_essentials() { + if ( ! function_exists( 'wp_get_current_user' ) ) { + require_once ABSPATH . 'wp-includes/pluggable.php'; + } + } + + /** + * Return plugin data. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return array + */ + function get_plugin_data() { + if ( ! isset( $this->_plugin_data ) ) { + self::require_plugin_essentials(); + + if ( $this->is_plugin() ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 When using get_plugin_data() do NOT translate plugin data. + * + * @link https://github.com/Freemius/wordpress-sdk/issues/77 + */ + $plugin_data = get_plugin_data( + $this->_plugin_main_file_path, + false, + false + ); + } else { + $theme_data = wp_get_theme(); + + if ( $this->_plugin_basename !== $theme_data->get_stylesheet() && is_child_theme() ) { + $parent_theme = $theme_data->parent(); + + if ( ( $parent_theme instanceof WP_Theme ) && $this->_plugin_basename === $parent_theme->get_stylesheet() ) { + $theme_data = $parent_theme; + } + } + + $plugin_data = array( + 'Name' => $theme_data->get( 'Name' ), + 'Version' => $theme_data->get( 'Version' ), + 'Author' => $theme_data->get( 'Author' ), + 'Description' => $theme_data->get( 'Description' ), + 'PluginURI' => $theme_data->get( 'ThemeURI' ), + ); + } + + $this->_plugin_data = $plugin_data; + } + + return $this->_plugin_data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * @since 1.2.2.5 If slug not set load slug by module ID. + * + * @return string Plugin slug. + */ + function get_slug() { + if ( ! isset( $this->_slug ) ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_slug = $id_slug_type_path_map[ $this->_module_id ]['slug']; + } + + return $this->_slug; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @return string + */ + function get_premium_slug() { + return is_object( $this->_plugin ) ? + $this->_plugin->premium_slug : + "{$this->_slug}-premium"; + } + + /** + * Retrieve the desired folder name for the product. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return string Plugin slug. + */ + function get_target_folder_name() { + return $this->can_use_premium_code() ? + $this->_plugin->premium_slug : + $this->_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return number Plugin ID. + */ + function get_id() { + return $this->_plugin->id; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return number|null Bundle ID. + */ + function get_bundle_id() { + return ( isset( $this->_plugin->bundle_id ) && FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) ) ? + $this->_plugin->bundle_id : + null; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return string Freemius SDK version + */ + function get_sdk_version() { + return $this->version; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return number Parent plugin ID (if parent exist). + */ + function get_parent_id() { + return $this->is_addon() ? + $this->get_parent_instance()->get_id() : + $this->_plugin->id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return string Plugin public key. + */ + function get_public_key() { + return $this->_plugin->public_key; + } + + /** + * Will be available only on sandbox mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return mixed Plugin secret key. + */ + function get_secret_key() { + return $this->_plugin->secret_key; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @return bool + */ + function has_secret_key() { + return ! empty( $this->_plugin->secret_key ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string|bool $premium_suffix + * + * @return string + */ + function get_plugin_name( $premium_suffix = false ) { + $this->_logger->entrance(); + + /** + * This `if-else` can be squeezed into a single `if` but I intentionally split it for code readability. + * + * @author Vova Feldman + */ + if ( ! isset( $this->_plugin_name ) ) { + // Name is not yet set. + $this->set_name( $premium_suffix ); + } else if ( + ! empty( $premium_suffix ) && + ( ! is_object( $this->_plugin ) || $this->_plugin->premium_suffix !== $premium_suffix ) + ) { + // Name is already set, but there's a change in the premium suffix. + $this->set_name( $premium_suffix ); + } + + return $this->_plugin_name; + } + + /** + * Calculates and stores the product's name. This helper function was created specifically for get_plugin_name() just to make the code clearer. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @param string $premium_suffix + */ + private function set_name( $premium_suffix = '' ) { + $plugin_data = $this->get_plugin_data(); + + // Get name. + $this->_plugin_name = $plugin_data['Name']; + + if ( is_string( $premium_suffix ) ) { + $premium_suffix = trim( $premium_suffix ); + + if ( ! empty( $premium_suffix ) ) { + // Check if plugin name contains " (premium)" or a custom suffix and remove it. + $suffix = ( ' ' . strtolower( $premium_suffix ) ); + $suffix_len = strlen( $suffix ); + + if ( strlen( $plugin_data['Name'] ) > $suffix_len && + $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len ) + ) { + $this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len ); + } + } + } + + $this->_logger->departure( 'Name = ' . $this->_plugin_name ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @return string + */ + function get_plugin_version() { + $this->_logger->entrance(); + + $plugin_data = $this->get_plugin_data(); + + $this->_logger->departure( 'Version = ' . $plugin_data['Version'] ); + + return $this->apply_filters( 'plugin_version', $plugin_data['Version'] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return string + */ + function get_plugin_title() { + $this->_logger->entrance(); + + $title = $this->_plugin->title; + + return $this->apply_filters( 'plugin_title', $title ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param bool $lowercase + * + * @return string + */ + function get_module_label( $lowercase = false ) { + $label = $this->is_addon() ? + $this->get_text_inline( 'Add-On', 'addon' ) : + ( $this->is_plugin() ? + $this->get_text_inline( 'Plugin', 'plugin' ) : + $this->get_text_inline( 'Theme', 'theme' ) ); + + if ( $lowercase ) { + $label = strtolower( $label ); + } + + return $label; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return string + */ + function get_plugin_basename() { + if ( ! isset( $this->_plugin_basename ) ) { + if ( $this->is_plugin() ) { + $this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path ); + } else { + $this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) ); + } + } + + return $this->_plugin_basename; + } + + function get_plugin_folder_name() { + $this->_logger->entrance(); + + $plugin_folder = $this->_plugin_basename; + + while ( '.' !== dirname( $plugin_folder ) ) { + $plugin_folder = dirname( $plugin_folder ); + } + + $this->_logger->departure( 'Folder Name = ' . $plugin_folder ); + + return $plugin_folder; + } + + #endregion ------------------------------------------------------------------ + + /* Account + ------------------------------------------------------------------------------------------------------------------*/ + + /** + * Find plugin's slug by plugin's basename. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $plugin_base_name + * + * @return false|string + */ + private static function find_slug_by_basename( $plugin_base_name ) { + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + + if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) { + return false; + } + + return $file_slug_map[ $plugin_base_name ]; + } + + /** + * Store the map between the plugin's basename to the slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function store_file_slug_map() { + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + + if ( ! array( $file_slug_map ) ) { + $file_slug_map = array(); + } + + if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) || + $file_slug_map[ $this->_plugin_basename ] !== $this->_slug + ) { + $file_slug_map[ $this->_plugin_basename ] = $this->_slug; + self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); + } + } + + /** + * @return array[number]FS_User + */ + static function get_all_users() { + $users = self::$_accounts->get_option( 'users', array() ); + + if ( ! is_array( $users ) ) { + $users = array(); + } + + return $users; + } + + /** + * @param string $module_type + * @param null|int $blog_id Since 2.0.0 + * + * @return array[string]FS_Site + */ + private static function get_all_sites( + $module_type = WP_FS__MODULE_TYPE_PLUGIN, + $blog_id = null + ) { + $sites = self::get_account_option( 'sites', $module_type, $blog_id ); + + if ( ! is_array( $sites ) ) { + $sites = array(); + } + + return $sites; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.2 + * + * @param string $option_name + * @param string $module_type + * @param null|int $network_level_or_blog_id Since 2.0.0 + * + * @return mixed + */ + private static function get_account_option( $option_name, $module_type = null, $network_level_or_blog_id = null ) { + if ( ! is_null( $module_type ) && WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + return self::$_accounts->get_option( $option_name, array(), $network_level_or_blog_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.2 + * + * @param string $option_name + * @param mixed $option_value + * @param bool $store + * @param null|int $network_level_or_blog_id Since 2.0.0 + */ + private function set_account_option( $option_name, $option_value, $store, $network_level_or_blog_id = null ) { + self::set_account_option_by_module( + $this->_module_type, + $option_name, + $option_value, + $store, + $network_level_or_blog_id + ); + } + + /** + * @author Vova Feldman (@svovaf) + * + * @since 1.2.2.7 + * + * @param string $module_type + * @param string $option_name + * @param mixed $option_value + * @param bool $store + * @param null|int $network_level_or_blog_id Since 2.0.0 + */ + private static function set_account_option_by_module( + $module_type, + $option_name, + $option_value, + $store, + $network_level_or_blog_id = null + ) { + if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + self::$_accounts->set_option( $option_name, $option_value, $store, $network_level_or_blog_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number|null $module_id + * + * @return FS_Plugin_License[] + */ + private static function get_all_licenses( $module_id = null ) { + $licenses = self::get_account_option( 'all_licenses' ); + + if ( ! is_array( $licenses ) ) { + $licenses = array(); + } + + if ( is_null( $module_id ) ) { + return $licenses; + } + + $licenses = isset( $licenses[ $module_id ] ) ? + $licenses[ $module_id ] : + array(); + + return $licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return array + */ + private static function get_all_licenses_by_module_type() { + $licenses = self::get_account_option( 'all_licenses' ); + + $licenses_by_module_type = array( + WP_FS__MODULE_TYPE_PLUGIN => array(), + WP_FS__MODULE_TYPE_THEME => array() + ); + + if ( ! is_array( $licenses ) ) { + return $licenses_by_module_type; + } + + foreach ( $licenses as $module_id => $module_licenses ) { + $fs = self::get_instance_by_id( $module_id ); + if ( false === $fs ) { + continue; + } + + $licenses_by_module_type[ $fs->_module_type ] = array_merge( $licenses_by_module_type[ $fs->_module_type ], $module_licenses ); + } + + return $licenses_by_module_type; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param number $module_id + * @param number|null $user_id + * + * @return array + */ + private static function get_user_id_license_ids_map( $module_id, $user_id = null ) { + $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); + + if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { + $all_modules_user_id_license_ids_map = array(); + } + + $user_id_license_ids_map = isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ? + $all_modules_user_id_license_ids_map[ $module_id ] : + array(); + + if ( FS_User::is_valid_id( $user_id ) ) { + $user_id_license_ids_map = isset( $user_id_license_ids_map[ $user_id ] ) ? + $user_id_license_ids_map[ $user_id ] : + array(); + } + + return $user_id_license_ids_map; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array $new_user_id_license_ids_map + * @param number $module_id + * @param number|null $user_id + */ + private static function store_user_id_license_ids_map( $new_user_id_license_ids_map, $module_id, $user_id = null ) { + $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); + if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { + $all_modules_user_id_license_ids_map = array(); + } + + if ( ! isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ) { + $all_modules_user_id_license_ids_map[ $module_id ] = array(); + } + + if ( FS_User::is_valid_id( $user_id ) ) { + $all_modules_user_id_license_ids_map[ $module_id ][ $user_id ] = $new_user_id_license_ids_map; + } else { + $all_modules_user_id_license_ids_map[ $module_id ] = $new_user_id_license_ids_map; + } + + self::$_accounts->set_option( 'user_id_license_ids_map', $all_modules_user_id_license_ids_map, true ); + } + + /** + * Get a collection of the user's linked license IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return number[] + */ + private function get_user_linked_license_ids( $user_id ) { + return self::get_user_id_license_ids_map( $this->_module_id, $user_id ); + } + + /** + * Override the user's linked license IDs with a new IDs collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param number[] $license_ids + */ + private function set_user_linked_license_ids( $user_id, array $license_ids ) { + self::store_user_id_license_ids_map( $license_ids, $this->_module_id, $user_id ); + } + + /** + * Link a specified license ID to a given user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param number $user_id + */ + private function link_license_2_user( $license_id, $user_id ) { + $license_ids = $this->get_user_linked_license_ids( $user_id ); + + if ( in_array( $license_id, $license_ids ) ) { + // License already linked. + return; + } + + $license_ids[] = $license_id; + + $this->set_user_linked_license_ids( $user_id, $license_ids ); + } + + /** + * @param string|bool $module_type + * + * @return FS_Plugin_Plan[] + */ + private static function get_all_plans( $module_type = false ) { + $plans = self::get_account_option( 'plans', $module_type ); + + if ( ! is_array( $plans ) ) { + $plans = array(); + } + + return $plans; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return FS_Plugin_Tag[] + */ + private static function get_all_updates() { + $updates = self::$_accounts->get_option( 'updates', array() ); + + if ( ! is_array( $updates ) ) { + $updates = array(); + } + + return $updates; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return array|false + */ + private static function get_all_addons() { + $addons = self::$_accounts->get_option( 'addons', array() ); + + if ( ! is_array( $addons ) ) { + $addons = array(); + } + + return $addons; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return FS_Plugin[]|false + */ + private static function get_all_account_addons() { + $addons = self::$_accounts->get_option( 'account_addons', array() ); + + if ( ! is_array( $addons ) ) { + $addons = array(); + } + + return $addons; + } + + /** + * Check if user has connected his account (opted-in). + * + * Note: + * If the user opted-in and opted-out on a later stage, + * this will still return true. If you want to check if the + * user is currently opted-in, use: + * `$fs->is_registered() && $fs->is_tracking_allowed()` + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * @return bool + */ + function is_registered() { + return is_object( $this->_user ); + } + + /** + * Returns TRUE if the user opted-in and didn't disconnect (opt-out). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool + */ + function is_tracking_allowed() { + return ( is_object( $this->_site ) && $this->_site->is_tracking_allowed() ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return FS_Plugin + */ + function get_plugin() { + return $this->_plugin; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return FS_User + */ + function get_user() { + return $this->_user; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return FS_Site + */ + function get_site() { + return $this->_site; + } + + /** + * Get plugin add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @since 1.1.7.3 If not yet loaded, fetch data from the API. + * + * @param bool $flush + * + * @return FS_Plugin[]|false + */ + function get_addons( $flush = false ) { + $this->_logger->entrance(); + + if ( ! $this->_has_addons ) { + return false; + } + + $addons = $this->sync_addons( $flush ); + + return ( ! is_array( $addons ) || empty( $addons ) ) ? + false : + $addons; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return FS_Plugin[]|false + */ + function get_account_addons() { + $this->_logger->entrance(); + + $addons = self::get_all_account_addons(); + + if ( ! is_array( $addons ) || + ! isset( $addons[ $this->_plugin->id ] ) || + ! is_array( $addons[ $this->_plugin->id ] ) || + 0 === count( $addons[ $this->_plugin->id ] ) + ) { + return false; + } + + return $addons[ $this->_plugin->id ]; + } + + /** + * Check if user has any + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + function has_account_addons() { + $addons = $this->get_account_addons(); + + return is_array( $addons ) && ( 0 < count( $addons ) ); + } + + + /** + * Get add-on by ID (from local data). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $id + * + * @return FS_Plugin|false + */ + function get_addon( $id ) { + $this->_logger->entrance(); + + $addons = $this->get_addons(); + + if ( is_array( $addons ) ) { + foreach ( $addons as $addon ) { + if ( $id == $addon->id ) { + return $addon; + } + } + } + + return false; + } + + /** + * Get add-on by slug (from local data). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $slug + * + * @param bool $flush + * + * @return FS_Plugin|false + */ + function get_addon_by_slug( $slug, $flush = false ) { + $this->_logger->entrance(); + + $addons = $this->get_addons( $flush ); + + if ( is_array( $addons ) ) { + foreach ( $addons as $addon ) { + if ( $slug === $addon->slug ) { + return $addon; + } + } + } + + return false; + } + + /** + * @var array { + * @key number Add-on ID. + * @val object[] The add-on's plans and prices object. + * } + */ + private $plans_and_pricing_by_addon_id; + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return array { + * @key number Add-on ID. + * @val object[] The add-on's plans and prices object. + * } + */ + function _get_addons_plans_and_pricing_map_by_id() { + if ( ! isset( $this->plans_and_pricing_by_addon_id ) ) { + $result = $this->get_api_plugin_scope()->get( $this->add_show_pending( "/addons/pricing.json?type=visible" ) ); + + $plans_and_pricing_by_addon_id = array(); + if ( $this->is_api_result_object( $result, 'addons' ) ) { + foreach ( $result->addons as $addon ) { + $plans_and_pricing_by_addon_id[ $addon->id ] = $addon->plans; + } + } + + $this->plans_and_pricing_by_addon_id = $plans_and_pricing_by_addon_id; + } + + return $this->plans_and_pricing_by_addon_id; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param number $addon_id + * @param bool $is_installed + * + * @return array + */ + function _get_addon_info( $addon_id, $is_installed ) { + $addon = $this->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + // Unexpected call. + return array(); + } + + $slug = $addon->slug; + + $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + if ( ! fs_is_network_admin() ) { + // Get blog-level activated installations. + $sites = self::$_accounts->get_option( 'sites', array() ); + } else { + $sites = null; + + if ( $this->is_addon_activated( $addon_id ) && + $this->get_addon_instance( $addon_id )->is_network_active() + ) { + if ( FS_Site::is_valid_id( $addon_storage->network_install_blog_id ) ) { + // Get network-level activated installations. + $sites = self::$_accounts->get_option( + 'sites', + array(), + $addon_storage->network_install_blog_id + ); + } + } + } + + $addon_info = array( + 'is_connected' => false, + 'slug' => $slug, + 'title' => $addon->title + ); + + if ( ! $is_installed ) { + $plans_and_pricing_by_addon_id = $this->_get_addons_plans_and_pricing_map_by_id(); + + if ( isset( $plans_and_pricing_by_addon_id[ $addon_id ] ) ) { + $has_paid_plan = false; + $plans = $plans_and_pricing_by_addon_id[ $addon_id ]; + + if ( is_array( $plans ) && count( $plans ) > 0 ) { + foreach ( $plans as $plan ) { + if ( isset( $plan->pricing ) && + is_array( $plan->pricing ) && + count( $plan->pricing ) > 0 + ) { + $has_paid_plan = true; + break; + } + } + } + + $addon_info['has_paid_plan'] = $has_paid_plan; + } + } + + if ( ! is_array( $sites ) || ! isset( $sites[ $slug ] ) ) { + return $addon_info; + } + + $site = $sites[ $slug ]; + + $addon_info['is_connected'] = ( + ( $addon->parent_plugin_id == $this->get_id() ) && + is_object( $site ) && + FS_Site::is_valid_id( $site->id ) && + FS_User::is_valid_id( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ); + + if ( $addon_info['is_connected'] && $is_installed ) { + return $addon_info; + } + + $addon_info['site'] = $site; + + $plugins_data = self::$_accounts->get_option( WP_FS__MODULE_TYPE_PLUGIN . 's', array() ); + if ( isset( $plugins_data[ $slug ] ) ) { + $plugin_data = $plugins_data[ $slug ]; + + $addon_info['version'] = $plugin_data->version; + } + + $all_plans = self::$_accounts->get_option( 'plans', array() ); + if ( isset( $all_plans[ $slug ] ) ) { + $plans = $all_plans[ $slug ]; + + foreach ( $plans as $plan ) { + if ( $site->plan_id == Freemius::_decrypt( $plan->id ) ) { + $addon_info['plan_name'] = Freemius::_decrypt( $plan->name ); + $addon_info['plan_title'] = Freemius::_decrypt( $plan->title ); + break; + } + } + } + + $licenses = self::$_accounts->get_option( 'all_licenses', array() ); + if ( is_array( $licenses ) && isset( $licenses[ $addon_id ] ) ) { + foreach ( $licenses[ $addon_id ] as $license ) { + if ( $license->id == $site->license_id ) { + $addon_info['license'] = $license; + break; + } + } + } + + if ( isset( $addon_info['license'] ) ) { + if ( isset( $addon_storage->subscriptions ) && + ! empty( $addon_storage->subscriptions ) + ) { + foreach ( $addon_storage->subscriptions as $subscription ) { + if ( $subscription->license_id == $site->license_id ) { + $addon_info['subscription'] = $subscription; + break; + } + } + } + } + + return $addon_info; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return FS_User + */ + static function _get_user_by_id( $user_id ) { + self::$_static_logger->entrance( "user_id = {$user_id}" ); + + $users = self::get_all_users(); + + if ( is_array( $users ) ) { + if ( isset( $users[ $user_id ] ) && + $users[ $user_id ] instanceof FS_User && + $user_id == $users[ $user_id ]->id + ) { + return $users[ $user_id ]; + } + + // If user wasn't found by the key, iterate over all the users collection. + foreach ( $users as $user ) { + /** + * @var FS_User $user + */ + if ( $user_id == $user->id ) { + return $user; + } + } + } + + return null; + } + + /** + * Checks if a Freemius user_id is associated with a super-admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return bool + */ + private static function is_super_admin( $user_id ) { + $is_super_admin = false; + + $user = self::_get_user_by_id( $user_id ); + + if ( $user instanceof FS_User && ! empty( $user->email ) ) { + self::require_pluggable_essentials(); + + $wp_user = get_user_by( 'email', $user->email ); + + if ( $wp_user instanceof WP_User ) { + $super_admins = get_super_admins(); + $is_super_admin = ( is_array( $super_admins ) && in_array( $wp_user->user_login, $super_admins ) ); + } + } + + return $is_super_admin; + } + + #---------------------------------------------------------------------------------- + #region Plans & Licensing + #---------------------------------------------------------------------------------- + + /** + * Check if running premium plugin code. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_premium() { + /** + * `$this->_plugin` will be `false` when `is_activation_mode` calls this method directly from the + * `_register_hooks` method. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return is_object( $this->_plugin ) ? + $this->_plugin->is_premium : + false; + } + + /** + * Get site's plan ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return number + */ + function get_plan_id() { + return $this->_site->plan_id; + } + + /** + * Get site's plan title. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return string + */ + function get_plan_title() { + $plan = $this->get_plan(); + + return is_object( $plan ) ? $plan->title : 'PLAN_TITLE'; + } + + /** + * Get site's plan name. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_plan_name() { + $plan = $this->get_plan(); + + return is_object( $plan ) ? $plan->name : 'PLAN_NAME'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return FS_Plugin_Plan|false + */ + function get_plan() { + if ( ! is_object( $this->_site ) ) { + return false; + } + + return FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) ? + $this->_get_plan_by_id( $this->_site->plan_id ) : + false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() || ! is_object( $this->_site ) ) { + return false; + } + + return $this->_site->is_trial(); + } + + /** + * Check if currently in a trial with payment method (credit card or paypal). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function is_paid_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return false; + } + + return $this->has_active_valid_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id ); + } + + /** + * Check if trial already utilized. + * + * @since 1.0.9 + * + * @return bool + */ + function is_trial_utilized() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + return $this->_site->is_trial_utilized(); + } + + /** + * Get trial plan information (if in trial). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool|FS_Plugin_Plan + */ + function get_trial_plan() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return false; + } + + // Try to load plan from local cache. + $trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id ); + + if ( ! is_object( $trial_plan ) ) { + $trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id ); + + /** + * If managed to fetch the plan, add it to the plans collection. + */ + if ( $trial_plan instanceof FS_Plugin_Plan ) { + if ( ! is_array( $this->_plans ) ) { + $this->_plans = array(); + } + + $this->_plans[] = $trial_plan; + $this->_store_plans(); + } + } + + if ( $trial_plan instanceof FS_Plugin_Plan ) { + return $trial_plan; + } + + /** + * If for some reason failed to get the trial plan, fallback to a dummy name and title. + */ + $trial_plan = new FS_Plugin_Plan(); + $trial_plan->id = $this->_site->trial_plan_id; + $trial_plan->name = 'pro'; + $trial_plan->title = 'Pro'; + + return $trial_plan; + } + + /** + * Check if the user has an activate, non-expired license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + if ( ! $this->has_paid_plan() ) { + return false; + } + + return ( + ! $this->is_trial() && + 'free' !== $this->get_plan_name() && + $this->has_active_valid_license() + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_free_plan() { + if ( ! $this->is_registered() ) { + return true; + } + + if ( ! $this->has_paid_plan() ) { + return true; + } + + return ( + 'free' === $this->get_plan_name() || + ! $this->has_features_enabled_license() + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function _has_premium_license() { + $this->_logger->entrance(); + + $premium_license = $this->_get_available_premium_license(); + + return ( false !== $premium_license ); + } + + /** + * Check if user has any licenses associated with the plugin (including expired or blocking). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + function has_any_license() { + return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|null $is_localhost + * + * @return FS_Plugin_License|false + */ + function _get_available_premium_license( $is_localhost = null ) { + $this->_logger->entrance(); + + $licenses = $this->get_available_premium_licenses( $is_localhost ); + if ( ! empty( $licenses ) ) { + return $licenses[0]; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|null $is_localhost + * + * @return FS_Plugin_License[] + */ + function get_available_premium_licenses( $is_localhost = null ) { + $this->_logger->entrance(); + + $licenses = array(); + if ( ! $this->has_paid_plan() ) { + return $licenses; + } + + if ( is_array( $this->_licenses ) ) { + foreach ( $this->_licenses as $license ) { + if ( ! $license->can_activate( $is_localhost ) ) { + continue; + } + + $licenses[] = $license; + } + } + + return $licenses; + } + + /** + * Sync local plugin plans with remote server. + * + * IMPORTANT: If for some reason a site is associated with deleted plan, we'll preserve the plan's information and append it as the last plan. This means that if plan is deleted, the is_plan() method will ALWAYS return true for any given argument (it becomes the most inclusive plan). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return FS_Plugin_Plan[]|object + */ + function _sync_plans() { + $plans = $this->_fetch_plugin_plans(); + + if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { + $plans_map = array(); + foreach ( $plans as $plan ) { + $plans_map[ $plan->id ] = true; + } + + $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); + + foreach ( $plans_ids_to_keep as $plan_id ) { + if ( isset( $plans_map[ $plan_id ] ) ) { + continue; + } + + $missing_plan = self::_get_plan_by_id( $plan_id ); + + if ( is_object( $missing_plan ) ) { + $plans[] = $missing_plan; + } + } + + $this->_plans = $plans; + $this->_store_plans(); + } + + $this->do_action( 'after_plans_sync', $plans ); + + return $this->_plans; + } + + /** + * Check if specified plan exists locally. If not, fetch it and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $plan_id + * + * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. + */ + private function sync_plan_if_not_exist( $plan_id ) { + $plan = self::_get_plan_by_id( $plan_id ); + + if ( is_object( $plan ) ) { + // Plan already exists. + return $plan; + } + + $plan = $this->fetch_plan_by_id( $plan_id ); + + if ( $plan instanceof FS_Plugin_Plan ) { + $this->_plans[] = $plan; + $this->_store_plans(); + + return $plan; + } + + return $plan; + } + + /** + * Check if specified license exists locally. If not, fetch it and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param string $license_key + * + * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. + */ + private function sync_license_if_not_exist( $license_id, $license_key ) { + $license = $this->_get_license_by_id( $license_id ); + + if ( is_object( $license ) ) { + // License already exists. + return $license; + } + + $license = $this->fetch_license_by_key( $license_id, $license_key ); + + if ( $license instanceof FS_Plugin_License ) { + $this->_licenses[] = $license; + $this->_license = $license; + $this->_store_licenses(); + + return $license; + } + + return $license; + } + + /** + * Get a collection of unique plan IDs that are associated with any installs in the network. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return number[] + */ + private function get_plans_ids_associated_with_installs() { + if ( ! is_multisite() ) { + if ( ! is_object( $this->_site ) || + ! FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) + ) { + return array(); + } + + return array( $this->_site->plan_id ); + } + + $plan_ids = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ) { + continue; + } + + $plan_ids[ $install->plan_id ] = true; + } + + return array_keys( $plan_ids ); + } + + /** + * Get a collection of unique license IDs that are associated with any installs in the network. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return number[] + */ + private function get_license_ids_associated_with_installs() { + if ( ! $this->_is_network_active ) { + if ( ! is_object( $this->_site ) || + ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) + ) { + return array(); + } + + return array( $this->_site->license_id ); + } + + $license_ids = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + continue; + } + + $license_ids[ $install->license_id ] = true; + } + + return array_keys( $license_ids ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param number $id + * + * @return FS_Plugin_Plan|false + */ + function _get_plan_by_id( $id ) { + $this->_logger->entrance(); + + if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { + $this->_sync_plans(); + } + + foreach ( $this->_plans as $plan ) { + if ( $id == $plan->id ) { + return $plan; + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @param string $name + * + * @return FS_Plugin_Plan|false + */ + private function get_plan_by_name( $name ) { + $this->_logger->entrance(); + + if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { + $this->_sync_plans(); + } + + foreach ( $this->_plans as $plan ) { + if ( $name == $plan->name ) { + return $plan; + } + } + + return false; + } + + /** + * Sync local licenses with remote server. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number|bool $site_license_id + * @param number|null $blog_id + * + * @return FS_Plugin_License[]|object + */ + function _sync_licenses( $site_license_id = false, $blog_id = null ) { + $this->_logger->entrance(); + + $is_network_admin = fs_is_network_admin(); + + if ( $is_network_admin && is_null( $blog_id ) ) { + $all_licenses = self::get_all_licenses( $this->_module_id ); + } else { + $all_licenses = $this->get_user_licenses( $this->_user->id ); + } + + $foreign_licenses = $this->get_foreign_licenses_info( $all_licenses, $site_license_id ); + + $all_licenses_map = array(); + foreach ( $all_licenses as $license ) { + $all_licenses_map[ $license->id ] = true; + } + + $licenses = $this->_fetch_licenses( false, $site_license_id, $foreign_licenses, $blog_id ); + + if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { + $licenses_map = array(); + foreach ( $licenses as $license ) { + $licenses_map[ $license->id ] = true; + } + +// $license_ids_to_keep = $this->get_license_ids_associated_with_installs(); +// foreach ( $license_ids_to_keep as $license_id ) { +// if ( isset( $licenses_map[ $license_id ] ) ) { +// continue; +// } +// +// $missing_license = self::_get_license_by_id( $license_id, false ); +// if ( is_object( $missing_license ) ) { +// $licenses[] = $missing_license; +// $licenses_map[ $missing_license->id ] = true; +// } +// } + + $user_license_ids = $this->get_user_linked_license_ids( $this->_user->id ); + + foreach ( $user_license_ids as $key => $license_id ) { + if ( ! isset( $licenses_map[ $license_id ] ) ) { + // Remove access to licenses that no longer exist. + unset( $user_license_ids[ $key ] ); + } + } + + if ( ! empty( $user_license_ids ) ) { + foreach ( $licenses_map as $license_id => $value ) { + if ( ! isset( $all_licenses_map[ $license_id ] ) ) { + // Associate new licenses with the user who triggered the license syncing. + $user_license_ids[] = $license_id; + } + } + + $user_license_ids = array_unique( $user_license_ids ); + } else { + $user_license_ids = array_keys( $licenses_map ); + } + + if ( ! $is_network_admin || ! is_null( $blog_id ) ) { + $user_licenses = array(); + foreach ( $licenses as $license ) { + if ( ! in_array( $license->id, $user_license_ids ) ) { + continue; + } + + $user_licenses[] = $license; + } + + $this->_licenses = $user_licenses; + } else { + $this->_licenses = $licenses; + } + + $this->set_user_linked_license_ids( $this->_user->id, $user_license_ids ); + + $this->_store_licenses( true, $this->_module_id, $licenses ); + } + + // Update current license. + if ( is_object( $this->_license ) ) { + $this->_license = $this->_get_license_by_id( $this->_license->id ); + } + + return $this->_licenses; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param number $id + * @param bool $sync_licenses + * + * @return FS_Plugin_License|false + */ + function _get_license_by_id( $id, $sync_licenses = true ) { + $this->_logger->entrance(); + + if ( ! FS_Plugin_License::is_valid_id( $id ) ) { + return false; + } + + /** + * When running from the network level admin and opted-in from the network, + * check if the license exists in the network user licenses collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( fs_is_network_admin() && + $this->is_network_registered() && + ( ! is_object( $this->_user ) || $this->_storage->network_user_id != $this->_user->id ) + ) { + $licenses = $this->get_user_licenses( $this->_storage->network_user_id ); + + foreach ( $licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + if ( ! $this->has_any_license() && $sync_licenses ) { + $this->_sync_licenses( $id ); + } + + if ( is_array( $this->_licenses ) ) { + foreach ( $this->_licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + return false; + } + + /** + * Get license by ID. Unlike _get_license_by_id(), this method only checks the local storage and return any license, whether it's associated with the current context user/install or not. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $id + * + * @return FS_Plugin_License + */ + private function get_license_by_id( $id ) { + $licenses = self::get_all_licenses( $this->_module_id ); + + if ( is_array( $licenses ) && ! empty( $licenses ) ) { + foreach ( $licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + return null; + } + + /** + * Synchronize the site's context license by fetching the license form the API and updating the local data with it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return \FS_Plugin_License|mixed + */ + private function sync_site_license() { + $api = $this->get_api_user_scope(); + + $result = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . urlencode( $this->_license->secret_key ), true ); + + if ( ! $this->is_api_result_entity( $result ) ) { + return $result; + } + + $license = $this->_update_site_license( new FS_Plugin_License( $result ) ); + $this->_store_licenses(); + + return $license; + } + + /** + * Get all user's available licenses for the current module. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return FS_Plugin_License[] + */ + private function get_user_licenses( $user_id ) { + $all_licenses = self::get_all_licenses( $this->_module_id ); + if ( empty( $all_licenses ) ) { + return array(); + } + + $user_license_ids = $this->get_user_linked_license_ids( $user_id ); + if ( empty( $user_license_ids ) ) { + return array(); + } + + $licenses = array(); + foreach ( $all_licenses as $license ) { + if ( in_array( $license->id, $user_license_ids ) ) { + $licenses[] = $license; + } + } + + return $licenses; + } + + /** + * Checks if the context license is network activated except on the given blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $except_blog_id + * + * @return bool + */ + private function is_license_network_active( $except_blog_id = 0 ) { + $this->_logger->entrance(); + + if ( ! is_object( $this->_license ) ) { + return false; + } + + $sites = self::get_sites(); + + if ( $this->_license->total_activations() < ( count( $sites ) - 1 ) ) { + // There are more sites than the number of activations, so license cannot be network activated. + return false; + } + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( $except_blog_id == $blog_id ) { + // Skip excluded blog. + continue; + } + + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) && $install->license_id != $this->_license->id ) { + return false; + } + } + + return true; + } + + /** + * Checks if license can be activated on all the network sites (opted-in or skipped) that are not yet associated with a license. If possible, try to make the activation, if not return false. + * + * Notice: On success, this method will also update the license activations counters (without updating the license in the storage). + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param \FS_Plugin_License $license + * + * @return bool + */ + private function try_activate_license_on_network( FS_User $user, FS_Plugin_License $license ) { + $this->_logger->entrance(); + + $result = $this->can_activate_license_on_network( $license ); + + if ( false === $result ) { + return false; + } + + $installs_without_license = $result['installs']; + if ( ! empty( $installs_without_license ) ) { + $this->activate_license_on_many_installs( $user, $license->secret_key, $installs_without_license ); + } + + $disconnected_site_ids = $result['sites']; + if ( ! empty( $disconnected_site_ids ) ) { + $this->activate_license_on_many_sites( $user, $license->secret_key, $disconnected_site_ids ); + } + + $this->link_license_2_user( $license->id, $user->id ); + + // Sync license after activations. + $license->activated += $result['production_count']; + $license->activated_local += $result['localhost_count']; + +// $this->_store_licenses() + + return true; + } + + /** + * Checks if the given license can be activated on the whole network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_License $license + * + * @return false|array { + * @type array[int]FS_Site $installs Blog ID to install map. + * @type int[] $sites Non-connected blog IDs. + * @type int $production_count Production sites count. + * @type int $localhost_count Production sites count. + * } + */ + private function can_activate_license_on_network( FS_Plugin_License $license ) { + $sites = self::get_sites(); + + $production_count = 0; + $localhost_count = 0; + + $installs_without_license = array(); + $disconnected_site_ids = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { + // License already activated on the install. + continue; + } + + $url = $install->url; + + $installs_without_license[ $blog_id ] = $install; + } else { + $url = is_object( $site ) ? + $site->siteurl : + get_site_url( $blog_id ); + + $disconnected_site_ids[] = $blog_id; + } + + if ( FS_Site::is_localhost_by_address( $url ) ) { + $localhost_count ++; + } else { + $production_count ++; + } + } + + if ( ! $license->can_activate_bulk( $production_count, $localhost_count ) ) { + return false; + } + + return array( + 'installs' => $installs_without_license, + 'sites' => $disconnected_site_ids, + 'production_count' => $production_count, + 'localhost_count' => $localhost_count, + ); + } + + /** + * Activate a given license on a collection of installs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string $license_key + * @param array $blog_2_install_map { + * @key int Blog ID. + * @value FS_Site Blog's associated install. + * } + * + * @return mixed|true + */ + private function activate_license_on_many_installs( + FS_User $user, + $license_key, + array $blog_2_install_map + ) { + $params = array( + array( 'license_key' => $this->apply_filters( 'license_key', $license_key ) ) + ); + + $install_2_blog_map = array(); + foreach ( $blog_2_install_map as $blog_id => $install ) { + $params[] = array( 'id' => $install->id ); + + $install_2_blog_map[ $install->id ] = $blog_id; + } + + $result = $this->get_api_user_scope_by_user( $user )->call( + "plugins/{$this->_plugin->id}/installs.json", + 'PUT', + $params + ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + return $result; + } + + foreach ( $result->installs as $r_install ) { + $install = new FS_Site( $r_install ); + $install->is_disconnected = false; + + // Update install. + $this->_store_site( + true, + $install_2_blog_map[ $r_install->id ], + $install + ); + } + + return true; + } + + /** + * Activate a given license on a collection of blogs/sites that are not yet opted-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string $license_key + * @param int[] $site_ids + * + * @return true|mixed True if successful, otherwise, the API result. + */ + private function activate_license_on_many_sites( + FS_User $user, + $license_key, + array $site_ids + ) { + $sites = array(); + foreach ( $site_ids as $site_id ) { + $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); + } + + // Install the plugin. + $result = $this->create_installs_with_user( + $user, + $license_key, + false, + $sites, + false, + true + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + return $result; + } + + $installs = array(); + foreach ( $result->installs as $install ) { + $installs[] = new FS_Site( $install ); + } + + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + $first_blog_id = null; + + foreach ( $installs as $install ) { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, $install ); + + $this->reset_anonymous_mode( $blog_id ); + + if ( is_null( $first_blog_id ) ) { + $first_blog_id = $blog_id; + } + } + + if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { + $this->_storage->network_install_blog_id = $first_blog_id; + } + + return true; + } + + /** + * Sync site's license with user licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin_License|null $new_license + * + * @return FS_Plugin_License|null + */ + function _update_site_license( $new_license ) { + $this->_logger->entrance(); + + $this->_license = $new_license; + + if ( ! is_object( $new_license ) ) { + $this->_site->license_id = null; + $this->_sync_site_subscription( null ); + + return $this->_license; + } + + $this->_site->license_id = $this->_license->id; + + if ( ! is_array( $this->_licenses ) ) { + $this->_licenses = array(); + } + + $is_license_found = false; + for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { + if ( $new_license->id == $this->_licenses[ $i ]->id ) { + $this->_licenses[ $i ] = $new_license; + + $is_license_found = true; + break; + } + } + + // If new license just append. + if ( ! $is_license_found ) { + $this->_licenses[] = $new_license; + } + + $this->_sync_site_subscription( $new_license ); + + return $this->_license; + } + + /** + * Sync site's subscription. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_License|null $license + * + * @return bool|\FS_Subscription + */ + private function _sync_site_subscription( $license ) { + if ( ! is_object( $license ) ) { + $this->delete_unused_subscriptions(); + + return false; + } + + // Load subscription details if not lifetime. + $subscription = $license->is_lifetime() ? + false : + $this->_fetch_site_license_subscription(); + + if ( is_object( $subscription ) && ! isset( $subscription->error ) ) { + $this->store_subscription( $subscription ); + } else { + $this->delete_unused_subscriptions(); + } + + return $subscription; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool|\FS_Plugin_License + */ + function _get_license() { + if ( ! fs_is_network_admin() || is_object( $this->_license ) ) { + return $this->_license; + } + + return $this->_get_available_premium_license(); + } + + /** + * @param number $license_id + * + * @return null|\FS_Subscription + */ + function _get_subscription( $license_id ) { + if ( ! isset( $this->_storage->subscriptions ) || + empty( $this->_storage->subscriptions ) + ) { + return null; + } + + foreach ( $this->_storage->subscriptions as $subscription ) { + if ( $subscription->license_id == $license_id ) { + return $subscription; + } + } + + return null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param FS_Subscription $subscription + */ + function store_subscription( FS_Subscription $subscription ) { + if ( ! isset( $this->_storage->subscriptions ) ) { + $this->_storage->subscriptions = array(); + } + + if ( empty( $this->_storage->subscriptions ) || ! is_multisite() ) { + $this->_storage->subscriptions = array( $subscription ); + + return; + } + + $subscriptions = $this->_storage->subscriptions; + + $updated_subscription = false; + foreach ( $subscriptions as $key => $existing_subscription ) { + if ( $existing_subscription->id == $subscription->id ) { + $subscriptions[ $key ] = $subscription; + $updated_subscription = true; + break; + } + } + + if ( ! $updated_subscription ) { + $subscriptions[] = $subscription; + } + + $this->_storage->subscriptions = $subscriptions; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + function delete_unused_subscriptions() { + if ( ! isset( $this->_storage->subscriptions ) || + empty( $this->_storage->subscriptions ) || + // Clean up only if there are already at least 3 subscriptions. + ( count( $this->_storage->subscriptions ) < 3 ) + ) { + return; + } + + if ( ! is_multisite() ) { + // If not multisite, there should only be 1 subscription, so just clear the array. + $this->_storage->subscriptions = array(); + + return; + } + + $subscriptions_to_keep_by_license_id_map = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + continue; + } + + $subscriptions_to_keep_by_license_id_map[ $install->license_id ] = true; + } + + if ( empty( $subscriptions_to_keep_by_license_id_map ) ) { + $this->_storage->subscriptions = array(); + + return; + } + + foreach ( $this->_storage->subscriptions as $key => $subscription ) { + if ( ! isset( $subscriptions_to_keep_by_license_id_map[ $subscription->license_id ] ) ) { + unset( $this->_storage->subscriptions[ $key ] ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param string $plan Plan name + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan( $plan, $exact = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + $plan = strtolower( $plan ); + + $current_plan_name = $this->get_plan_name(); + + if ( $current_plan_name === $plan ) { + // Exact plan. + return true; + } else if ( $exact ) { + // Required exact, but plans are different. + return false; + } + + $current_plan_order = - 1; + $required_plan_order = - 1; + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $plan === $this->_plans[ $i ]->name ) { + $required_plan_order = $i; + } else if ( $current_plan_name === $this->_plans[ $i ]->name ) { + $current_plan_order = $i; + } + } + + return ( $current_plan_order > $required_plan_order ); + } + + /** + * Check if module has only one plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return bool + */ + function is_single_plan() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() || + ! is_array( $this->_plans ) || + 0 === count( $this->_plans ) + ) { + return true; + } + + return ( 1 === ( count( $this->_plans ) - ( $this->has_free_plan() ? 1 : 0 ) ) ); + } + + /** + * Check if plan based on trial. If not in trial mode, should return false. + * + * @since 1.0.9 + * + * @param string $plan Plan name + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_trial_plan( $plan, $exact = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + if ( ! $this->is_trial() ) { + return false; + } + + $trial_plan = $this->get_trial_plan(); + + if ( $trial_plan->name === $plan ) { + // Exact plan. + return true; + } else if ( $exact ) { + // Required exact, but plans are different. + return false; + } + + $current_plan_order = - 1; + $required_plan_order = - 1; + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $plan === $this->_plans[ $i ]->name ) { + $required_plan_order = $i; + } else if ( $trial_plan->name === $this->_plans[ $i ]->name ) { + $current_plan_order = $i; + } + } + + return ( $current_plan_order > $required_plan_order ); + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function has_paid_plan() { + return $this->_has_paid_plans || + FS_Plan_Manager::instance()->has_paid_plan( $this->_plans ); + } + + /** + * Check if plugin has any plan with a trail. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_trial_plan() { + /** + * @author Vova Feldman(@svovaf) + * @since 1.2.1.5 + * + * Allow setting a trial from the SDK without calling the API. + * But, if the user did opt-in, continue using the real data from the API. + */ + if ( $this->_trial_days >= 0 ) { + return true; + } + + return $this->_storage->get( 'has_trial_plan', false ); + } + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function has_free_plan() { + return ! $this->is_only_premium(); + } + + /** + * Displays a license activation dialog box when the user clicks on the "Activate License" + * or "Change License" link on the plugins + * page. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + */ + function _add_license_activation_dialog_box() { + $vars = array( + 'id' => $this->_module_id, + ); + + fs_require_template( 'forms/license-activation.php', $vars ); + fs_require_template( 'forms/resend-key.php', $vars ); + } + + /** + * Displays a subscription cancellation dialog box when the user clicks on the "Deactivate License" + * link on the "Account" page or deactivates a plugin and there's an active subscription that is + * either associated with a non-lifetime single-site license or non-lifetime multisite license that + * is only activated on a single production site. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param bool $is_license_deactivation + * + * @return array + */ + function _get_subscription_cancellation_dialog_box_template_params( $is_license_deactivation = false ) { + if ( fs_is_network_admin() ) { + // Subscription cancellation dialog box is currently not supported for multisite networks. + return array(); + } + + $license = $this->_get_license(); + + /** + * If the installation is associated with a non-lifetime license, which is either a single-site or only activated on a single production site (or zero), and connected to an active subscription, suggest the customer to cancel the subscription upon deactivation. + * + * @author Leo Fajardo (@leorw) (Comment added by Vova Feldman @svovaf) + * @since 2.2.1 + */ + if ( ! is_object( $license ) || + $license->is_lifetime() || + ( ! $license->is_single_site() && $license->activated > 1 ) + ) { + return array(); + } + + /** + * @var FS_Subscription $subscription + */ + $subscription = $this->_get_subscription( $license->id ); + if ( ! is_object( $subscription ) || ! $subscription->is_active() ) { + return array(); + } + + return array( + 'id' => $this->_module_id, + 'license' => $license, + 'has_trial' => $this->is_paid_trial(), + 'is_license_deactivation' => $is_license_deactivation, + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection_dialog_box() { + $modules_update = get_site_transient( $this->is_theme() ? 'update_themes' : 'update_plugins' ); + if ( ! isset( $modules_update->response[ $this->_plugin_basename ] ) ) { + return; + } + + $vars = array( + 'id' => $this->_module_id, + 'new_version' => is_object( $modules_update->response[ $this->_plugin_basename ] ) ? + $modules_update->response[ $this->_plugin_basename ]->new_version : + $modules_update->response[ $this->_plugin_basename ]['new_version'] + ); + + fs_require_template( 'forms/premium-versions-upgrade-metadata.php', $vars ); + fs_require_once_template( 'forms/premium-versions-upgrade-handler.php', $vars ); + } + + /** + * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins" + * page. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _add_optout_dialog() { + if ( $this->is_theme() ) { + $vars = null; + fs_require_once_template( '/js/jquery.content-change.php', $vars ); + } + + $vars = array( 'id' => $this->_module_id ); + fs_require_template( 'forms/optout.php', $vars ); + } + + /** + * Prepare page to include all required UI and logic for the license activation dialog. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + */ + function _add_license_activation() { + if ( ! $this->is_user_admin() ) { + // Only admins can activate a license. + return; + } + + if ( ! $this->has_paid_plan() ) { + // Module doesn't have any paid plans. + return; + } + + if ( ! $this->is_premium() ) { + // Only add license activation logic to the premium version. + return; + } + + // Add license activation link and AJAX request handler. + if ( self::is_plugins_page() ) { + $is_network_admin = fs_is_network_admin(); + + if ( + ( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) || + ( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) ) + ) { + /** + * @since 1.2.0 Add license action link only on plugins page. + */ + $this->_add_license_action_link(); + } + } + + // Add license activation AJAX callback. + $this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) ); + + // Add resend license AJAX callback. + $this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection() { + if ( ! $this->is_user_admin() ) { + return; + } + + if ( ! $this->is_premium() || $this->has_any_active_valid_license() ) { + // This is relevant only to the free versions and premium versions without an active license. + return; + } + + if ( self::is_updates_page() || ( $this->is_plugin() && self::is_plugins_page() ) ) { + $this->_add_premium_version_upgrade_selection_action(); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + function _add_beta_mode_update_handler() { + if ( ! $this->is_user_admin() ) { + return; + } + + if ( ! $this->is_premium() ) { + return; + } + + $this->add_ajax_action( 'set_beta_mode', array( &$this, '_set_beta_mode_ajax_handler' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + function _set_beta_mode_ajax_handler() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'set_beta_mode' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $is_beta = trim( fs_request_get( 'is_beta', '', 'post' ) ); + + if ( empty( $is_beta ) || ! in_array( $is_beta, array( 'true', 'false' ) ) ) { + self::shoot_ajax_failure(); + } + + $user = $this->get_api_user_scope()->call( + '', + 'put', + array( + 'plugin_id' => $this->get_id(), + 'is_beta' => ( 'true' == $is_beta ), + 'fields' => 'is_beta' + ) + ); + + if ( ! $this->is_api_result_entity( $user ) ) { + self::shoot_ajax_failure( + FS_Api::is_api_error_object( $user ) ? + $user->error->message : + fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() ) + ); + } + + $this->_user->is_beta = $user->is_beta; + $this->_store_user(); + + self::shoot_ajax_response( array( 'success' => true ) ); + } + + /** + * License activation WP AJAX handler. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + * + * @uses Freemius::activate_license() + */ + function _activate_license_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'activate_license' ); + + $license_key = trim( fs_request_get( 'license_key' ) ); + + if ( empty( $license_key ) ) { + exit; + } + + $result = $this->activate_license( + $license_key, + fs_is_network_admin() ? + fs_request_get( 'sites', array(), 'post' ) : + array(), + fs_request_get_bool( 'is_marketing_allowed', null ), + fs_request_get( 'blog_id', null ), + fs_request_get( 'module_id', null, 'post' ) + ); + + echo json_encode( $result ); + + exit; + } + + /** + * A helper method to activate migrated licenses. If the product is network activated and integrated, the method will network activate the license. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.0 + * + * @param string $license_key + * @param null|bool $is_marketing_allowed + * @param null|number $plugin_id + * + * @return array { + * @var bool $success + * @var string $error + * @var string $next_page + * } + * + * @uses Freemius::activate_license() + */ + function activate_migrated_license( + $license_key, + $is_marketing_allowed = null, + $plugin_id = null + ) { + return $this->activate_license( + $license_key, + $this->is_network_active() ? + $this->get_sites_for_network_level_optin() : + array(), + $is_marketing_allowed, + null, + $plugin_id + ); + } + + /** + * The implementation of this method was previously in `_activate_license_ajax_action()`. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.4 + * @since 2.0.0 When a super-admin that hasn't connected before is network activating a license and excluding some of the sites for the license activation, go over the unselected sites in the network and if a site is not connected, skipped, nor delegated, if it's a freemium product then just skip the connection for the site, if it's a premium only product, delegate the connection and license activation to the site admin (Vova Feldman @svovaf). + * @param string $license_key + * @param array $sites + * @param null|bool $is_marketing_allowed + * @param null|int $blog_id + * @param null|number $plugin_id + * + * @return array { + * @var bool $success + * @var string $error + * @var string $next_page + * } + */ + private function activate_license( + $license_key, + $sites = array(), + $is_marketing_allowed = null, + $blog_id = null, + $plugin_id = null + ) { + $this->_logger->entrance(); + + $license_key = trim( $license_key ); + + if ( ! fs_is_network_admin() ) { + // If the license activation is executed outside the context of a network admin, ignore the sites collection. + $sites = array(); + } + + $fs = ( empty($plugin_id) || $plugin_id == $this->_module_id ) ? + $this : + $this->get_addon_instance( $plugin_id ); + + $error = false; + $next_page = false; + + $has_valid_blog_id = is_numeric( $blog_id ); + + if ( $fs->is_registered() ) { + if ( fs_is_network_admin() && ! $has_valid_blog_id ) { + // If no specific blog ID was provided, activate the license for all sites in the network. + $blog_2_install_map = array(); + $site_ids = array(); + + foreach ( $sites as $site ) { + if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { + continue; + } + + $install = $this->get_install_by_blog_id( $site['blog_id'] ); + + if ( is_object( $install ) ) { + $blog_2_install_map[ $site['blog_id'] ] = $install; + } else { + $site_ids[] = $site['blog_id']; + } + } + + $user = $this->get_current_or_network_user(); + + if ( ! empty( $blog_2_install_map ) ) { + $result = $this->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); + + if ( true !== $result ) { + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } + } + + if ( empty( $error ) && ! empty( $site_ids ) ) { + $result = $this->activate_license_on_many_sites( $user, $license_key, $site_ids ); + + if ( true !== $result ) { + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } + } + } else { + if ( $has_valid_blog_id ) { + /** + * If a specific blog ID was provided, activate the license only for the install that is + * associated with the given blog ID. + * + * @author Leo Fajardo (@leorw) + */ + $this->switch_to_blog( $blog_id ); + } + + $api = $fs->get_api_site_scope(); + + $params = array( + 'license_key' => $fs->apply_filters( 'license_key', $license_key ) + ); + + $install = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + + if ( FS_Api::is_api_error( $install ) ) { + $error = FS_Api::is_api_error_object( $install ) ? + $install->error->message : + var_export( $install->error, true ); + } else { + $fs->reconnect_locally( $has_valid_blog_id ); + } + } + + if ( empty( $error ) ) { + $this->network_upgrade_mode_completed(); + + $fs->_sync_license( true, $has_valid_blog_id ); + + $next_page = $fs->is_addon() ? + $fs->get_parent_instance()->get_account_url() : + $fs->get_account_url(); + } + } else { + $next_page = $fs->opt_in( + false, + false, + false, + $license_key, + false, + false, + false, + $is_marketing_allowed, + $sites + ); + + if ( isset( $next_page->error ) ) { + $error = $next_page->error; + } else { + if ( fs_is_network_admin() ) { + /** + * Get the list of sites that were just opted-in (and license activated). + * This is an optimization for the next part below saving some DB queries. + */ + $connected_sites = array(); + foreach ( $sites as $site ) { + if ( isset( $site['blog_id'] ) && is_numeric( $site['blog_id'] ) ) { + $connected_sites[ $site['blog_id'] ] = true; + } + } + + $all_sites = self::get_sites(); + $pending_sites = array(); + + /** + * Check if there are any sites that are not connected, skipped, nor delegated. For every site that falls into that category, if the product is freemium, skip the connection. If the product is premium only, delegate the connection to the site administrator. + * + * @author Vova Feldman (@svovaf) + */ + foreach ( $all_sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( isset( $connected_sites[ $blog_id ] ) ) { + // Site was just connected. + continue; + } + + if ( $this->is_installed_on_site( $blog_id ) ) { + // Site was already connected before. + continue; + } + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + // Site's connection was delegated. + continue; + } + + if ( $this->is_anonymous_site( $blog_id ) ) { + // Site connection was already skipped. + continue; + } + + $pending_sites[] = self::get_site_info( $site ); + } + + if ( ! empty( $pending_sites ) ) { + if ( $this->is_freemium() && $this->is_enable_anonymous() ) { + $this->skip_connection( $pending_sites ); + } else { + $this->delegate_connection( $pending_sites ); + } + } + } + } + } + + if ( false === $error && true === $this->_storage->require_license_activation ) { + $this->_storage->require_license_activation = false; + } + + $result = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $result['error'] = $this->apply_filters( 'opt_in_error_message', $error ); + } else { + if ( $this->is_addon() || $this->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $this->purge_valid_user_licenses_cache(); + } + + $result['next_page'] = $next_page; + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3.1 + */ + function _network_activate_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'network_activate' ); + + $plugin_id = fs_request_get( 'module_id', '', 'post' ); + $fs = ( $plugin_id == $this->_module_id ) ? + $this : + $this->get_addon_instance( $plugin_id ); + + $error = false; + + $sites = fs_request_get( 'sites', array(), 'post' ); + if ( is_array( $sites ) && ! empty( $sites ) ) { + $sites_by_action = array( + 'allow' => array(), + 'delegate' => array(), + 'skip' => array() + ); + + foreach ( $sites as $site ) { + $sites_by_action[ $site['action'] ][] = $site; + } + + $total_sites = count( $sites ); + $total_sites_to_delegate = count( $sites_by_action['delegate'] ); + + $next_page = ''; + + $has_any_install = fs_request_get_bool( 'has_any_install' ); + + if ( $total_sites === $total_sites_to_delegate && + ! $this->is_network_upgrade_mode() && + ! $has_any_install + ) { + $this->delegate_connection(); + } else { + if ( ! empty( $sites_by_action['delegate'] ) ) { + $this->delegate_connection( $sites_by_action['delegate'] ); + } + + if ( ! empty( $sites_by_action['skip'] ) ) { + $this->skip_connection( $sites_by_action['skip'] ); + } + + if ( empty( $sites_by_action['allow'] ) ) { + if ( $has_any_install ) { + $first_install = $fs->find_first_install(); + + if ( ! is_null( $first_install ) ) { + $fs->_site = $first_install['install']; + $fs->_storage->network_install_blog_id = $first_install['blog_id']; + + $fs->_user = self::_get_user_by_id( $fs->_site->user_id ); + $fs->_storage->network_user_id = $fs->_user->id; + } + } + } else { + if ( ! $fs->is_registered() || ! $this->_is_network_active ) { + $next_page = $fs->opt_in( + false, + false, + false, + false, + false, + false, + false, + fs_request_get_bool( 'is_marketing_allowed', null ), + $sites_by_action['allow'] + ); + } else { + $next_page = $fs->install_with_user( + $this->get_network_user(), + false, + false, + false, + true, + $sites_by_action['allow'] + ); + } + + if ( is_object( $next_page ) && isset( $next_page->error ) ) { + $error = $next_page->error; + } + } + } + + if ( empty( $next_page ) ) { + $next_page = $this->get_after_activation_url( 'after_network_activation_url' ); + } + } else { + $error = $this->get_text_inline( 'Invalid site details collection.', 'invalid_site_details_collection' ); + } + + $result = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $result['error'] = $error; + } else { + $result['next_page'] = $next_page; + } + + echo json_encode( $result ); + + exit; + } + + /** + * Billing update AJAX callback. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _update_billing_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'update_billing' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $billing = fs_request_get( 'billing' ); + + $api = $this->get_api_user_scope(); + $result = $api->call( '/billing.json', 'put', array_merge( $billing, array( + 'plugin_id' => $this->get_parent_id(), + ) ) ); + + if ( ! $this->is_api_result_entity( $result ) ) { + self::shoot_ajax_failure(); + } + + // Purge cached billing. + $this->get_api_user_scope()->purge_cache( 'billing.json' ); + + self::shoot_ajax_success(); + } + + /** + * Trial start for anonymous users (AJAX callback). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _start_trial_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'start_trial' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $trial_data = fs_request_get( 'trial' ); + + $next_page = $this->opt_in( + false, + false, + false, + false, + false, + $trial_data['plan_id'] + ); + + if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { + self::shoot_ajax_failure( + isset( $next_page->error ) ? + $next_page->error->message : + var_export( $next_page, true ) + ); + } + + $this->shoot_ajax_success( array( + 'next_page' => $next_page, + ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.0 + */ + function _resend_license_key_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'resend_license_key' ); + + $email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) ); + + if ( empty( $email_address ) ) { + exit; + } + + $error = false; + + $api = $this->get_api_plugin_scope(); + $result = $api->call( '/licenses/resend.json', 'post', + array( + 'email' => $email_address, + 'url' => home_url(), + ) + ); + + if ( is_object( $result ) && isset( $result->error ) ) { + $error = $result->error; + + if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) { + $error = $this->get_text_inline( "We couldn't find your email address in the system, are you sure it's the right address?", 'email-not-found' ); + } else if ( 'no_license' === $error->code ) { + $error = $this->get_text_inline( "We can't see any active licenses associated with that email address, are you sure it's the right address?", 'no-active-licenses' ); + } else { + $error = $error->message; + } + } + + $licenses = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $licenses['error'] = sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), strtolower( $error ) ); + } + + echo json_encode( $licenses ); + + exit; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @var string + */ + private static $_pagenow; + + /** + * Get current page or the referer if executing a WP AJAX request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @return string + */ + static function get_current_page() { + if ( ! isset( self::$_pagenow ) ) { + global $pagenow; + if ( empty( $pagenow ) && is_admin() && is_multisite() ) { + /** + * It appears that `$pagenow` is not yet initialized in some network admin pages when this method + * is called, so initialize it here using some pieces of code from `wp-includes/vars.php`. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( is_network_admin() ) { + preg_match( '#/wp-admin/network/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } else if ( is_user_admin() ) { + preg_match( '#/wp-admin/user/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } else { + preg_match( '#/wp-admin/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } + + $pagenow = $self_matches[1]; + $pagenow = trim( $pagenow, '/' ); + $pagenow = preg_replace( '#\?.*?$#', '', $pagenow ); + if ( '' === $pagenow || 'index' === $pagenow || 'index.php' === $pagenow ) { + $pagenow = 'index.php'; + } else { + preg_match( '#(.*?)(/|$)#', $pagenow, $self_matches ); + $pagenow = strtolower( $self_matches[1] ); + if ( '.php' !== substr($pagenow, -4, 4) ) + $pagenow .= '.php'; // for Options +Multiviews: /wp-admin/themes/index.php (themes.php is queried) + } + } + + self::$_pagenow = $pagenow; + + if ( self::is_ajax() && + 'admin-ajax.php' === $pagenow + ) { + $referer = fs_get_raw_referer(); + + if ( is_string( $referer ) ) { + $parts = explode( '?', $referer ); + + self::$_pagenow = basename( $parts[0] ); + } + } + } + + return self::$_pagenow; + } + + /** + * Helper method to check if user in the plugins page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + static function is_plugins_page() { + return ( 'plugins.php' === self::get_current_page() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return bool + */ + static function is_plugin_install_page() { + return ( 'plugin-install.php' === self::get_current_page() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + * + * @return bool + */ + static function is_updates_page() { + return ( 'update-core.php' === self::get_current_page() ); + } + + /** + * Helper method to check if user in the themes page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.6 + * + * @return bool + */ + static function is_themes_page() { + return ( 'themes.php' === self::get_current_page() ); + } + + #---------------------------------------------------------------------------------- + #region Affiliation + #---------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return bool + */ + function has_affiliate_program() { + if ( ! is_object( $this->_plugin ) ) { + return false; + } + + return $this->_plugin->has_affiliate_program(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.4 + */ + private function fetch_affiliate_terms() { + if ( ! is_object( $this->plugin_affiliate_terms ) ) { + $plugins_api = $this->get_api_plugin_scope(); + $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', false ); + + if ( ! $this->is_api_result_entity( $affiliate_terms ) ) { + return; + } + + $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.4 + */ + private function fetch_affiliate_and_custom_terms() { + if ( ! empty( $this->_storage->affiliate_application_data ) ) { + $application_data = $this->_storage->affiliate_application_data; + $flush = ( ! isset( $application_data['status'] ) || 'pending' === $application_data['status'] ); + + $users_api = $this->get_api_user_scope(); + $result = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush ); + if ( $this->is_api_result_object( $result, 'affiliates' ) ) { + if ( ! empty( $result->affiliates ) ) { + $affiliate = new FS_Affiliate( $result->affiliates[0] ); + + if ( ! isset( $application_data['status'] ) || $application_data['status'] !== $affiliate->status ) { + $application_data['status'] = $affiliate->status; + $this->_storage->affiliate_application_data = $application_data; + } + + if ( $affiliate->is_using_custom_terms ) { + $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", $flush ); + if ( $this->is_api_result_entity( $affiliate_terms ) ) { + $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); + } + } + + $this->affiliate = $affiliate; + } + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + private function fetch_affiliate_and_terms() { + $this->_logger->entrance(); + + $this->fetch_affiliate_terms(); + $this->fetch_affiliate_and_custom_terms(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return FS_Affiliate + */ + function get_affiliate() { + return $this->affiliate; + } + + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return FS_AffiliateTerms + */ + function get_affiliate_terms() { + return is_object( $this->custom_affiliate_terms ) ? + $this->custom_affiliate_terms : + $this->plugin_affiliate_terms; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + function _submit_affiliate_application() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'submit_affiliate_application' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $affiliate = fs_request_get( 'affiliate' ); + + if ( empty( $affiliate['promotion_methods'] ) ) { + unset( $affiliate['promotion_methods'] ); + } + + if ( ! empty( $affiliate['additional_domains'] ) ) { + $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] ); + } + + if ( ! $this->is_registered() ) { + // Opt in but don't track usage. + $next_page = $this->opt_in( + false, + false, + false, + false, + false, + false, + true + ); + + if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { + self::shoot_ajax_failure( + isset( $next_page->error ) ? + $next_page->error->message : + var_export( $next_page, true ) + ); + } else if ( $this->is_pending_activation() ) { + self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation.', 'account-is-pending-activation' ) ); + } + } + + $this->fetch_affiliate_terms(); + + $api = $this->get_api_user_scope(); + $result = $api->call( + ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), + 'post', + $affiliate + ); + + if ( $this->is_api_error( $result ) ) { + self::shoot_ajax_failure( + isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) + ); + } else { + if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { + $this->_admin_notices->remove_sticky( 'affiliate_program' ); + } + + $affiliate_application_data = array( + 'status' => 'pending', + 'stats_description' => $affiliate['stats_description'], + 'promotion_method_description' => $affiliate['promotion_method_description'], + ); + + if ( ! empty( $affiliate['promotion_methods'] ) ) { + $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods']; + } + + if ( ! empty( $affiliate['domain'] ) ) { + $affiliate_application_data['domain'] = $affiliate['domain']; + } + + if ( ! empty( $affiliate['additional_domains'] ) ) { + $affiliate_application_data['additional_domains'] = $affiliate['additional_domains']; + } + + $this->_storage->affiliate_application_data = $affiliate_application_data; + } + + // Purge cached affiliate. + $api->purge_cache( 'affiliate.json' ); + + self::shoot_ajax_success( $result ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return array|null + */ + function get_affiliate_application_data() { + if ( empty( $this->_storage->affiliate_application_data ) ) { + return null; + } + + return $this->_storage->affiliate_application_data; + } + + #endregion Affiliation ------------------------------------------------------------ + + #---------------------------------------------------------------------------------- + #region URL Generators + #---------------------------------------------------------------------------------- + + /** + * Alias to pricing_url(). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @uses pricing_url() + * + * @param string $period Billing cycle + * @param bool $is_trial + * + * @return string + */ + function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { + return $this->pricing_url( $period, $is_trial ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @uses get_upgrade_url() + * + * @return string + */ + function get_trial_url() { + return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param string $new_version + * + * @return string + */ + function version_upgrade_checkout_link( $new_version ) { + if ( ! is_object( $this->_license ) ) { + $url = $this->pricing_url(); + + $purchase_license_text = $this->get_text_inline( 'Buy a license now', 'buy-license-now' ); + } else { + $subscription = $this->_get_subscription( $this->_license->id ); + + $url = $this->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $this->_license->quota ) + ); + + $purchase_license_text = $this->get_text_inline( 'Renew your license now', 'renew-license-now' ); + } + + return sprintf( + $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), + sprintf( '%s', $url, $purchase_license_text ), + $new_version + ); + } + + /** + * Plugin's pricing URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $billing_cycle Billing cycle + * + * @param bool $is_trial + * + * @return string + */ + function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { + $this->_logger->entrance(); + + $params = array( + 'billing_cycle' => $billing_cycle + ); + + if ( $is_trial ) { + $params['trial'] = 'true'; + } + + $url = $this->is_addon() ? + $this->_parent->addon_url( $this->_slug ) : + $this->_get_admin_page_url( 'pricing', $params ); + + return $this->apply_filters( 'pricing_url', $url ); + } + + /** + * Checkout page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $billing_cycle Billing cycle + * @param bool $is_trial + * @param array $extra (optional) Extra parameters, override other query params. + * @param bool|null $network + * + * @return string + */ + function checkout_url( + $billing_cycle = WP_FS__PERIOD_ANNUALLY, + $is_trial = false, + $extra = array(), + $network = null + ) { + $this->_logger->entrance(); + + $params = array( + 'checkout' => 'true', + 'billing_cycle' => $billing_cycle, + ); + + if ( $is_trial ) { + $params['trial'] = 'true'; + } + + /** + * Params in extra override other params. + */ + $params = array_merge( $params, $extra ); + + return $this->_get_admin_page_url( 'pricing', $params, $network ); + } + + /** + * Add-on checkout URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param number $addon_id + * @param number $pricing_id + * @param string $billing_cycle + * @param bool $is_trial + * @param bool|null $network + * + * @return string + */ + function addon_checkout_url( + $addon_id, + $pricing_id, + $billing_cycle = WP_FS__PERIOD_ANNUALLY, + $is_trial = false, + $network = null + ) { + return $this->checkout_url( $billing_cycle, $is_trial, array( + 'plugin_id' => $addon_id, + 'pricing_id' => $pricing_id, + ), $network ); + } + + #endregion + + #endregion ------------------------------------------------------------------ + + /** + * Check if plugin has any add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @since 1.1.7.3 Base logic only on the parameter provided by the developer in the init function. + * + * @return bool + */ + function has_addons() { + $this->_logger->entrance(); + + return $this->_has_addons; + } + + /** + * Check if plugin can work in anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + * + * @deprecated Please use is_enable_anonymous() instead. + */ + function enable_anonymous() { + return $this->_enable_anonymous; + } + + /** + * Check if plugin can work in anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_enable_anonymous() { + return $this->_enable_anonymous; + } + + /** + * Check if plugin is premium only (no free plans). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_only_premium() { + return $this->_is_premium_only; + } + + /** + * Checks if the plugin's type is "plugin". The other type is "theme". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_plugin() { + return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_module_type() { + if ( ! isset( $this->_module_type ) ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_module_type = $id_slug_type_path_map[ $this->_module_id ]['type']; + } + + return $this->_module_type; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_plugin_main_file_path() { + return $this->_plugin_main_file_path; + } + + /** + * Check if module has a premium code version. + * + * Serviceware module might be freemium without any + * premium code version, where the paid features + * are all part of the service. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + function has_premium_version() { + return $this->_has_premium_version; + } + + /** + * Check if feature supported with current site's plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @todo IMPLEMENT + * + * @param number $feature_id + * + * @throws Exception + */ + function is_feature_supported( $feature_id ) { + throw new Exception( 'not implemented' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return bool Is running in SSL/HTTPS + */ + function is_ssl() { + return WP_FS__IS_HTTPS; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool Is running in AJAX call. + * + * @link http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax + */ + static function is_ajax() { + return ( defined( 'DOING_AJAX' ) && DOING_AJAX ); + } + + /** + * Check if it's an AJAX call targeted for the current module. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param array|string $actions Collection of AJAX actions. + * + * @return bool + */ + function is_ajax_action( $actions ) { + // Verify it's an ajax call. + if ( ! self::is_ajax() ) { + return false; + } + + // Verify the call is relevant for the plugin. + if ( $this->_module_id != fs_request_get( 'module_id' ) ) { + return false; + } + + // Verify it's one of the specified actions. + if ( is_string( $actions ) ) { + $actions = explode( ',', $actions ); + } + + if ( is_array( $actions ) && 0 < count( $actions ) ) { + $ajax_action = fs_request_get( 'action' ); + + foreach ( $actions as $action ) { + if ( $ajax_action === $this->get_action_tag( $action ) ) { + return true; + } + } + } + + return false; + } + + /** + * Check if it's an AJAX call targeted for current request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param array|string $actions Collection of AJAX actions. + * @param number|null $module_id + * + * @return bool + */ + static function is_ajax_action_static( $actions, $module_id = null ) { + // Verify it's an ajax call. + if ( ! self::is_ajax() ) { + return false; + } + + + if ( ! empty( $module_id ) ) { + // Verify the call is relevant for the plugin. + if ( $module_id != fs_request_get( 'module_id' ) ) { + return false; + } + } + + // Verify it's one of the specified actions. + if ( is_string( $actions ) ) { + $actions = explode( ',', $actions ); + } + + if ( is_array( $actions ) && 0 < count( $actions ) ) { + $ajax_action = fs_request_get( 'action' ); + + foreach ( $actions as $action ) { + if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) { + return true; + } + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + static function is_cron() { + return ( defined( 'DOING_CRON' ) && DOING_CRON ); + } + + /** + * Check if a real user is visiting the admin dashboard. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function is_user_in_admin() { + return is_admin() && ! self::is_ajax() && ! self::is_cron(); + } + + /** + * Check if a real user is in the customizer view. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + static function is_customizer() { + return is_customize_preview(); + } + + /** + * Check if running in HTTPS and if site's plan matching the specified plan. + * + * @param string $plan + * @param bool $exact + * + * @return bool + */ + function is_ssl_and_plan( $plan, $exact = false ) { + return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) ); + } + + /** + * Construct plugin's settings page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $page + * @param array $params + * @param bool|null $network + * + * @return string + */ + function _get_admin_page_url( $page = '', $params = array(), $network = null ) { + if ( is_null( $network ) ) { + $network = ( + $this->_is_network_active && + ( fs_is_network_admin() || ! $this->is_delegated_connection() ) + ); + } + + if ( 0 < count( $params ) ) { + foreach ( $params as $k => $v ) { + $params[ $k ] = urlencode( $v ); + } + } + + $page_param = $this->_menu->get_slug( $page ); + + if ( empty( $page ) && + $this->is_theme() && + // Show the opt-in as an overlay for free wp.org themes or themes without any settings page. + ( $this->is_free_wp_org_theme() || ! $this->has_settings_menu() ) + ) { + $params[ $this->get_unique_affix() . '_show_optin' ] = 'true'; + + return add_query_arg( + $params, + $this->admin_url( 'themes.php', 'admin', $network ) + ); + } + + if ( ! $this->has_settings_menu() ) { + if ( ! empty( $page ) ) { + // Module doesn't have a setting page, but since the request is for + // a specific Freemius page, use the admin.php path. + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + if ( $this->is_activation_mode() ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * If plugin doesn't have a settings page, create one for the opt-in screen. + */ + return add_query_arg( array_merge( $params, array( + 'page' => $this->_slug, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + // Plugin without a settings page. + return add_query_arg( + $params, + $this->admin_url( 'plugins.php', 'admin', $network ) + ); + } + } + } + + // Module has a submenu settings page. + if ( ! $this->_menu->is_top_level() ) { + $parent_slug = $this->_menu->get_parent_slug(); + $menu_file = ( false !== strpos( $parent_slug, '.php' ) ) ? + $parent_slug : + 'admin.php'; + + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( $menu_file, 'admin', $network ) ); + } + + // Module has a top level CPT settings page. + if ( $this->_menu->is_cpt() ) { + if ( empty( $page ) && $this->is_activation_mode() ) { + return add_query_arg( array_merge( $params, array( + 'page' => $page_param + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + if ( ! empty( $page ) ) { + $params['page'] = $page_param; + } + + return add_query_arg( + $params, + $this->admin_url( $this->_menu->get_raw_slug(), 'admin', $network ) + ); + } + } + + // Module has a custom top level settings page. + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } + + #-------------------------------------------------------------------------------- + #region Multisite + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return bool + */ + function is_network_active() { + return $this->_is_network_active; + } + + /** + * Delegate activation for the given sites in the network (or all sites if `null`) to site admins. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array|null $sites + */ + private function delegate_connection( $sites = null ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( is_null( $sites ) ) { + // All sites delegation. + $this->_storage->store( 'is_delegated_connection', true, true, true ); + } else { + // Specified sites delegation. + foreach ( $sites as $site ) { + $this->delegate_site_connection( $site['blog_id'] ); + } + } + + $this->network_upgrade_mode_completed(); + } + + /** + * Delegate specific network site conncetion to the site admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + private function delegate_site_connection( $blog_id ) { + $this->_storage->store( 'is_delegated_connection', true, $blog_id, true ); + } + + /** + * Check if super-admin delegated the connection of ALL sites to the site admins. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_delegated_connection() { + if ( ! $this->_is_network_active ) { + return false; + } + + return $this->_storage->get( 'is_delegated_connection', false, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_site_delegated_connection( $blog_id = 0 ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( 0 == $blog_id ) { + $blog_id = get_current_blog_id(); + } + + return $this->_storage->get( 'is_delegated_connection', false, $blog_id ); + } + + /** + * Check if delegated the connection. When running within the the network admin, + * and haven't specified the blog ID, checks if network level delegated. If running + * within a site admin or specified a blog ID, check if delegated the connection for + * the current context site. + * + * If executed outside the the admin, check if delegated the connection + * for the current context site OR the whole network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id If set, checks if network delegated or blog specific delegated. + * + * @return bool + */ + function is_delegated_connection( $blog_id = 0 ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( fs_is_network_admin() && 0 == $blog_id ) { + return $this->is_network_delegated_connection(); + } + + return ( + $this->is_network_delegated_connection() || + $this->is_site_delegated_connection( $blog_id ) + ); + } + + /** + * Check if the current module is active for the site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_active_for_site( $blog_id ) { + if ( ! is_multisite() ) { + // Not a multisite and this code is executed, means that the plugin is active. + return true; + } + + if ( $this->is_theme() ) { + // All themes are site level activated. + return true; + } + + if ( $this->_is_network_active ) { + // Plugin was network activated so it's active. + return true; + } + + return in_array( $this->_plugin_basename, (array) get_blog_option( $blog_id, 'active_plugins', array() ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return array Active & public sites collection. + */ + static function get_sites() { + if ( ! is_multisite() ) { + return array(); + } + + /** + * For consistency with get_blog_list() which only return active public sites. + * + * @author Vova Feldman (@svovaf) + */ + $args = array( + /** + * Commented out in order to handle the migration of site options whether the site is public or not. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + // 'public' => 1, + 'archived' => 0, + 'mature' => 0, + 'spam' => 0, + 'deleted' => 0, + ); + + if ( function_exists( 'get_sites' ) ) { + // For WP 4.6 and above. + return get_sites( $args ); + } else if ( function_exists( 'wp_get_sites' ) ) { + // For WP 3.7 to WP 4.5. + return wp_get_sites( $args ); + } else { + // For WP 3.6 and below. + return get_blog_list( 0, 'all' ); + } + } + + /** + * Checks if a given blog is active. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param $blog_id + * + * @return bool + */ + private static function is_site_active( $blog_id ) { + global $wpdb; + + $blog_info = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d", $blog_id ) ); + + if ( ! is_object( $blog_info ) ) { + return false; + } + + return ( + true == $blog_info->public && + false == $blog_info->archived && + false == $blog_info->mature && + false == $blog_info->spam && + false == $blog_info->deleted + ); + } + + /** + * Get a mapping between the site addresses to their blog IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array { + * @key string Site address without protocol with a trailing slash. + * @value int Site's blog ID. + * } + */ + private function get_address_to_blog_map() { + $sites = self::get_sites(); + + // Map site addresses to their blog IDs. + $address_to_blog_map = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $address = trailingslashit( fs_strip_url_protocol( get_site_url( $blog_id ) ) ); + $address_to_blog_map[ $address ] = $blog_id; + } + + return $address_to_blog_map; + } + + /** + * Get a mapping between the site addresses to their blog IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array { + * @key int Site's blog ID. + * @value FS_Site Associated install. + * } + */ + function get_blog_install_map() { + $sites = self::get_sites(); + + // Map site blog ID to its install. + $install_map = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + $install_map[ $blog_id ] = $install; + } + } + + return $install_map; + } + + /** + * Gets a map of module IDs that the given user has opted-in to. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param number $fs_user_id + * + * @return array { + * @key number $plugin_id + * @value bool Always true. + * } + */ + private static function get_user_opted_in_module_ids_map( $fs_user_id ) { + self::$_static_logger->entrance(); + + if ( ! is_multisite() ) { + $installs = array_merge( + self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ), + self::get_all_sites( WP_FS__MODULE_TYPE_THEME ) + ); + } else { + $sites = self::get_sites(); + + $installs = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $installs = array_merge( + $installs, + self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ), + self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ) + ); + } + } + + $module_ids_map = array(); + foreach ( $installs as $install ) { + if ( is_object( $install ) && + FS_Site::is_valid_id( $install->id ) && + FS_User::is_valid_id( $install->user_id ) && + ( $install->user_id == $fs_user_id ) + ) { + $module_ids_map[ $install->plugin_id ] = true; + } + } + + return $module_ids_map; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return null|array { + * 'install' => FS_Site Module's install, + * 'blog_id' => string The associated blog ID. + * } + */ + function find_first_install() { + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + return array( + 'install' => $install, + 'blog_id' => $blog_id + ); + } + } + + return null; + } + + /** + * Switches the Freemius site level context to a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * @param FS_Site $install + */ + function switch_to_blog( $blog_id, FS_Site $install = null ) { + if ( $blog_id == $this->_context_is_network_or_blog_id ) { + return; + } + + switch_to_blog( $blog_id ); + $this->_context_is_network_or_blog_id = $blog_id; + + self::$_accounts->set_site_blog_context( $blog_id ); + $this->_storage->set_site_blog_context( $blog_id ); + $this->_storage->set_network_active( $this->_is_network_active, $this->is_delegated_connection( $blog_id ) ); + + $this->_site = is_object( $install ) ? + $install : + $this->get_install_by_blog_id( $blog_id ); + + $this->_user = false; + $this->_licenses = false; + $this->_license = null; + + if ( is_object( $this->_site ) ) { + // Try to fetch user from install. + $this->_user = self::_get_user_by_id( $this->_site->user_id ); + + if ( ! is_object( $this->_user ) && + FS_User::is_valid_id( $this->_storage->prev_user_id ) + ) { + // Try to fetch previously saved user. + $this->_user = self::_get_user_by_id( $this->_storage->prev_user_id ); + + if ( ! is_object( $this->_user ) ) { + // Fallback to network's user. + $this->_user = $this->get_network_user(); + } + } + + $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); + + if ( ! empty( $all_plugin_licenses ) ) { + if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { + $this->_license = null; + } else { + $license_found = false; + foreach ( $all_plugin_licenses as $license ) { + if ( $license->id == $this->_site->license_id ) { + // License found. + $this->_license = $license; + $license_found = true; + break; + } + } + + if ( $license_found ) { + $this->link_license_2_user( $this->_license->id, $this->_user->id ); + } + } + + $this->_licenses = $this->get_user_licenses( $this->_user->id ); + } + } + + unset( $this->_site_api ); + unset( $this->_user_api ); + } + + /** + * Restore the blog context to the blog that originally loaded the module. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function restore_current_blog() { + $this->switch_to_blog( $this->_blog_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array|WP_Site $site + * + * @return int + */ + static function get_site_blog_id( &$site ) { + return ( $site instanceof WP_Site ) ? + $site->blog_id : + ( is_object( $site ) && isset( $site->userblog_id ) ? + $site->userblog_id : + $site['blog_id'] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array|WP_Site|null $site + * + * @return array + */ + function get_site_info( $site = null ) { + $this->_logger->entrance(); + + $switched = false; + + if ( is_null( $site ) ) { + $url = get_site_url(); + $name = get_bloginfo( 'name' ); + $blog_id = null; + } else { + $blog_id = self::get_site_blog_id( $site ); + + if ( get_current_blog_id() != $blog_id ) { + switch_to_blog( $blog_id ); + $switched = true; + } + + if ( $site instanceof WP_Site ) { + $url = $site->siteurl; + $name = $site->blogname; + } else { + $url = get_site_url( $blog_id ); + $name = get_bloginfo( 'name' ); + } + } + + $info = array( + 'uid' => $this->get_anonymous_id( $blog_id ), + 'url' => $url, + 'title' => $name, + 'language' => get_bloginfo( 'language' ), + 'charset' => get_bloginfo( 'charset' ), + ); + + if ( is_numeric( $blog_id ) ) { + $info['blog_id'] = $blog_id; + } + + if ( $switched ) { + restore_current_blog(); + } + + return $info; + } + + /** + * Load the module's install based on the blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * + * @return FS_Site + */ + function get_install_by_blog_id( $blog_id = null ) { + $installs = self::get_all_sites( $this->_module_type, $blog_id ); + $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; + + if ( is_object( $install ) && + is_numeric( $install->id ) && + is_numeric( $install->user_id ) && + FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ) { + // Load site. + $install = clone $install; + } + + return $install; + } + + /** + * Check if module is installed on a specified site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * + * @return bool + */ + function is_installed_on_site( $blog_id = null ) { + $installs = self::get_all_sites( $this->_module_type, $blog_id ); + $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; + + return ( + is_object( $install ) && + is_numeric( $install->id ) && + is_numeric( $install->user_id ) && + FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ); + } + + /** + * Check if super-admin connected at least one site via the network opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_registered() { + if ( ! $this->_is_network_active ) { + return false; + } + + return FS_User::is_valid_id( $this->_storage->network_user_id ); + } + + /** + * Returns the main user associated with the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_User + */ + function get_network_user() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_User::is_valid_id( $this->_storage->network_user_id ) ? + self::_get_user_by_id( $this->_storage->network_user_id ) : + null; + } + + /** + * Returns the current context user or the network's main user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_User + */ + function get_current_or_network_user() { + return ( $this->_user instanceof FS_User ) ? + $this->_user : + $this->get_network_user(); + } + + /** + * Returns the main install associated with the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_Site + */ + function get_network_install() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? + $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ) : + null; + } + + /** + * Returns the blog ID that is associated with the main install. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return int|null + */ + function get_network_install_blog_id() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? + $this->_storage->network_install_blog_id : + null; + } + + /** + * Returns the current context install or the network's main install. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_Site + */ + function get_current_or_network_install() { + return ( $this->_site instanceof FS_Site ) ? + $this->_site : + $this->get_network_install(); + } + + /** + * Check if executing a site level action from the network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return false|int If yes, return the requested blog ID. + */ + private function is_network_level_site_specific_action() { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( ! fs_is_network_admin() ) { + return false; + } + + $blog_id = fs_request_get( 'blog_id', '' ); + + return is_numeric( $blog_id ) ? $blog_id : false; + } + + /** + * Check if executing an action from the network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function is_network_level_action() { + return ( $this->_is_network_active && fs_is_network_admin() ); + } + + /** + * Needs to be executed after site deactivation, archive, deletion, or flag as spam. + * The logic updates the network level user and blog, and reschedule the crons if the cron executing site matching the site that is no longer publicly active. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + private function update_multisite_data_after_site_deactivation( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + if ( $this->_is_network_active ) { + if ( $context_blog_id == $this->_storage->network_install_blog_id ) { + $installs_map = $this->get_blog_install_map(); + + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $context_blog_id == $blog_id ) { + continue; + } + + if ( $install->user_id != $this->_storage->network_user_id ) { + continue; + } + + // Switch reference to a blog that is opted-in and belong to the same super-admin. + $this->_storage->network_install_blog_id = $blog_id; + break; + } + } + } + + if ( $this->is_sync_cron_scheduled() && + $context_blog_id == $this->get_sync_cron_blog_id() + ) { + $this->schedule_sync_cron( WP_FS__SCRIPT_START_TIME, true, $context_blog_id ); + } + + if ( $this->is_install_sync_scheduled() && + $context_blog_id == $this->get_install_sync_cron_blog_id() + ) { + $this->schedule_install_sync( $context_blog_id ); + } + } + + /** + * Executed after site deactivation, archive, or flag as spam. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + public function _after_site_deactivated_callback( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + + $this->switch_to_blog( $current_blog_id ); + } + + /** + * Executed after site deletion. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + * @param bool $drop True if site's database tables should be dropped. Default is false. + */ + public function _after_site_deleted_callback( $context_blog_id = 0, $drop = false ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + if ( $drop ) { + // Delete install if dropping site DB. + $this->delete_account_event(); + } else { + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + } + + $this->switch_to_blog( $current_blog_id ); + } + + /** + * Executed after site re-activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + public function _after_site_reactivated_callback( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + if ( ! self::is_site_active( $context_blog_id ) ) { + // Site not yet active (can be in spam mode, archived, deleted...). + return; + } + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + // Send re-activation event. + $this->sync_install( array( + 'is_active' => true, + ) ); + + $this->switch_to_blog( $current_blog_id ); + } + + #endregion Multisite + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $path + * @param string $scheme + * @param bool $network + * + * @return string + */ + private function admin_url( $path = '', $scheme = 'admin', $network = true ) { + return ( $this->_is_network_active && $network ) ? + network_admin_url( $path, $scheme ) : + admin_url( $path, $scheme ); + } + + /** + * Check if currently in a specified admin page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $page + * + * @return bool + */ + function is_admin_page( $page ) { + return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) ); + } + + /** + * Get module's main admin setting page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function main_menu_url() { + return $this->_menu->main_menu_url(); + } + + /** + * Check if currently on the theme's setting page or + * on any of the Freemius added pages (via tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_theme_settings_page() { + return fs_starts_with( + fs_request_get( 'page', '', 'get' ), + $this->_menu->get_slug() + ); + } + + /** + * Plugin's account page + sync license URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9.1 + * + * @param bool|number $plugin_id + * @param bool $add_action_nonce + * @param array $params + * + * @return string + */ + function _get_sync_license_url( $plugin_id = false, $add_action_nonce = true, $params = array() ) { + if ( is_numeric( $plugin_id ) ) { + $params['plugin_id'] = $plugin_id; + } + + return $this->get_account_url( + $this->get_unique_affix() . '_sync_license', + $params, + $add_action_nonce + ); + } + + /** + * Plugin's account URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|string $action + * @param array $params + * + * @param bool $add_action_nonce + * + * @return string + */ + function get_account_url( $action = false, $params = array(), $add_action_nonce = true ) { + if ( is_string( $action ) ) { + $params['fs_action'] = $action; + } + + self::require_pluggable_essentials(); + + return ( $add_action_nonce && is_string( $action ) ) ? + fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) : + $this->_get_admin_page_url( 'account', $params ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param string $tab + * @param bool $action + * @param array $params + * @param bool $add_action_nonce + * + * @return string + * + * @uses get_account_url() + */ + function get_account_tab_url( $tab, $action = false, $params = array(), $add_action_nonce = true ) { + $params['tab'] = $tab; + + return $this->get_account_url( $action, $params, $add_action_nonce ); + } + + /** + * Plugin's account URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|string $topic + * @param bool|string $message + * + * @return string + */ + function contact_url( $topic = false, $message = false ) { + $params = array(); + if ( is_string( $topic ) ) { + $params['topic'] = $topic; + } + if ( is_string( $message ) ) { + $params['message'] = $message; + } + + if ( $this->is_addon() ) { + $params['addon_id'] = $this->get_id(); + + return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params ); + } else { + return $this->_get_admin_page_url( 'contact', $params ); + } + } + + /** + * Add-on direct info URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $slug + * + * @return string + */ + function addon_url( $slug ) { + return $this->_get_admin_page_url( 'addons', array( + 'slug' => $slug + ) ); + } + + /** + * Add-ons URL. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.5 + * + * @return string + */ + function get_addons_url() { + return $this->_get_admin_page_url( 'addons' ); + } + + /* Logger + ------------------------------------------------------------------------------------------------------------------*/ + /** + * @param string $id + * @param bool $prefix_slug + * + * @return FS_Logger + */ + function get_logger( $id = '', $prefix_slug = true ) { + return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id ); + } + + /** + * Note: This method is used externally so don't delete it. + * + * @param $id + * @param bool $load_options + * @param bool $prefix_slug + * + * @return FS_Option_Manager + */ + function get_options_manager( $id, $load_options = false, $prefix_slug = true ) { + return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options ); + } + + /* Security + ------------------------------------------------------------------------------------------------------------------*/ + private static function _encrypt( $str ) { + if ( is_null( $str ) ) { + return null; + } + + /** + * The encrypt/decrypt functions are used to protect + * the user from messing up with some of the sensitive + * data stored for the module as a JSON in the database. + * + * I used the same suggested hack by the theme review team. + * For more details, look at the function `Base64UrlDecode()` + * in `./sdk/FreemiusBase.php`. + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + $fn = 'base64' . '_encode'; + + return $fn( $str ); + } + + static function _decrypt( $str ) { + if ( is_null( $str ) ) { + return null; + } + + /** + * The encrypt/decrypt functions are used to protect + * the user from messing up with some of the sensitive + * data stored for the module as a JSON in the database. + * + * I used the same suggested hack by the theme review team. + * For more details, look at the function `Base64UrlDecode()` + * in `./sdk/FreemiusBase.php`. + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + $fn = 'base64' . '_decode'; + + return $fn( $str ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Entity $entity + * + * @return FS_Entity Return an encrypted clone entity. + */ + private static function _encrypt_entity( FS_Entity $entity ) { + $clone = clone $entity; + $props = get_object_vars( $entity ); + + foreach ( $props as $key => $val ) { + $clone->{$key} = self::_encrypt( $val ); + } + + return $clone; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Entity $entity + * + * @return FS_Entity Return an decrypted clone entity. + */ + private static function decrypt_entity( FS_Entity $entity ) { + $clone = clone $entity; + $props = get_object_vars( $entity ); + + foreach ( $props as $key => $val ) { + $clone->{$key} = self::_decrypt( $val ); + } + + return $clone; + } + + /** + * Tries to activate account based on POST params. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @deprecated Not in use, outdated. + */ + function _activate_account() { + if ( $this->is_registered() ) { + // Already activated. + return; + } + + self::_clean_admin_content_section(); + + if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) { +// check_admin_referer( 'activate_' . $this->_plugin->public_key ); + + // Verify matching plugin details. + if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) { + return; + } + + $user = new FS_User(); + $user->id = fs_request_get( 'user_id' ); + $user->public_key = fs_request_get( 'user_public_key' ); + $user->secret_key = fs_request_get( 'user_secret_key' ); + $user->email = fs_request_get( 'user_email' ); + $user->first = fs_request_get( 'user_first' ); + $user->last = fs_request_get( 'user_last' ); + $user->is_verified = fs_request_get_bool( 'user_is_verified' ); + + $site = new FS_Site(); + $site->id = fs_request_get( 'install_id' ); + $site->public_key = fs_request_get( 'install_public_key' ); + $site->secret_key = fs_request_get( 'install_secret_key' ); + $site->plan_id = fs_request_get( 'plan_id' ); + + $plans = array(); + $plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) ); + foreach ( $plans_data as $p ) { + $plan = new FS_Plugin_Plan( $p ); + if ( $site->plan_id == $plan->id ) { + $plan->title = fs_request_get( 'plan_title' ); + $plan->name = fs_request_get( 'plan_name' ); + } + + $plans[] = $plan; + } + + $this->_set_account( $user, $site, $plans ); + + // Reload the page with the keys. + fs_redirect( $this->_get_admin_page_url() ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $email + * + * @return FS_User|false + */ + static function _get_user_by_email( $email ) { + self::$_static_logger->entrance(); + + $email = trim( strtolower( $email ) ); + + $users = self::get_all_users(); + + if ( is_array( $users ) ) { + foreach ( $users as $user ) { + if ( $email === trim( strtolower( $user->email ) ) ) { + return $user; + } + } + } + + return false; + } + + #---------------------------------------------------------------------------------- + #region Account (Loading, Updates & Activation) + #---------------------------------------------------------------------------------- + + /*** + * Load account information (user + site). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private function _load_account() { + $this->_logger->entrance(); + + $this->do_action( 'before_account_load' ); + + $users = self::get_all_users(); + $plans = self::get_all_plans( $this->_module_type ); + + if ( $this->_logger->is_on() && is_admin() ) { + $this->_logger->log( 'users = ' . var_export( $users, true ) ); + $this->_logger->log( 'plans = ' . var_export( $plans, true ) ); + } + + $site = fs_is_network_admin() ? + $this->get_network_install() : + $this->get_install_by_blog_id(); + + if ( fs_is_network_admin() && + $this->is_network_active() && + ! is_object( $site ) && + FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) + ) { + $first_install = $this->find_first_install(); + + if ( is_null( $first_install ) ) { + unset( $this->_storage->network_install_blog_id ); + } else { + $site = $first_install['install']; + $this->_storage->network_install_blog_id = $first_install['blog_id']; + } + } + + if ( is_object( $site ) && + is_numeric( $site->id ) && + is_numeric( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ) { + // Load site. + $this->_site = $site; + + // Load plans. + $this->_plans = $plans[ $this->_slug ]; + if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) { + $this->_sync_plans(); + } else { + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) { + $this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] ); + } else { + unset( $this->_plans[ $i ] ); + } + } + } + } + + $user = null; + if ( fs_is_network_admin() && $this->_is_network_active ) { + $user = $this->get_network_user(); + } + + if ( is_object( $user ) ) { + $this->_user = clone $user; + } else if ( $this->_site ) { + $user = self::_get_user_by_id( $this->_site->user_id ); + + if ( ! is_object( $user ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) { + /** + * Try to load the previous owner. This recovery is used for the following use-case: + * 1. Opt-in + * 2. Cloning site1 to site2 + * 3. Ownership switch in site1 (same applies for site2) + * 4. Install data sync on site2 + * 5. Now site2's install is associated with the new owner which does not exists locally. + */ + $user = self::_get_user_by_id( $this->_storage->prev_user_id ); + } + + if ( ! is_object( $user ) ) { + /** + * This is a special fault tolerance mechanism to handle a scenario that the user data is missing. + */ + $user = $this->fetch_user_by_install(); + } + + $this->_user = ( $user instanceof FS_User ) ? + clone $user : + null; + } + + if ( is_object( $this->_user ) ) { + // Load licenses. + $this->_licenses = $this->get_user_licenses( $this->_user->id ); + } + + if ( is_object( $this->_site ) ) { + $this->_license = $this->_get_license_by_id( $this->_site->license_id ); + + if ( $this->_site->version != $this->get_plugin_version() ) { + // If stored install version is different than current installed plugin version, + // then update plugin version event. + $this->update_plugin_version_event(); + } + } + + if ( true === $this->_storage->require_license_activation && + ! fs_request_get_bool( 'require_license', true ) + ) { + $this->_storage->require_license_activation = false; + } + + if ( $this->is_theme() ) { + $this->_register_account_hooks(); + } + } + + /** + * Special user recovery mechanism. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return \FS_User|mixed + */ + private function fetch_user_by_install() { + $api = $this->get_api_site_scope(); + + $uid = $this->get_anonymous_id(); + $request_path = "/users/{$this->_site->user_id}.json?uid={$uid}"; + + $result = $api->get( $request_path, false, WP_FS__TIME_10_MIN_IN_SEC ); + + if ( $this->is_api_result_entity( $result ) ) { + $user = new FS_User( $result ); + $this->_user = $user; + $this->_store_user(); + + return $user; + } + + $error_code = FS_Api::get_error_code( $result ); + + if ( in_array( $error_code, array( 'invalid_unique_id', 'user_cannot_be_recovered' ) ) ) { + /** + * Those API errors will continue coming and are not recoverable with the + * current site's data. Therefore, extend the API call's cached result to 7 days. + */ + $api->update_cache_expiration( $request_path, WP_FS__TIME_WEEK_IN_SEC ); + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param FS_User $user + * @param FS_Site $site + * @param bool|array $plans + */ + private function _set_account( FS_User $user, FS_Site $site, $plans = false ) { + $site->user_id = $user->id; + + $this->_site = $site; + $this->_user = $user; + if ( false !== $plans ) { + $this->_plans = $plans; + } + + $this->send_install_update(); + + $this->_store_account(); + + } + + /** + * Get a sanitized array with the WordPress version, SDK version, and PHP version. + * Each version is trimmed after the 16th char. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @return array + */ + private function get_versions() { + $versions = array(); + $versions['platform_version'] = get_bloginfo( 'version' ); + $versions['sdk_version'] = $this->version; + $versions['programming_language_version'] = phpversion(); + + foreach ( $versions as $k => $version ) { + if ( is_string( $versions[ $k ] ) && ! empty( $versions[ $k ] ) ) { + $versions[ $k ] = substr( $versions[ $k ], 0, 16 ); + } + } + + return $versions; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return bool + */ + function has_beta_update() { + return ( + ! empty( $this->_storage->beta_data ) && + ( true === $this->_storage->beta_data['is_beta'] ) && + version_compare( $this->_storage->beta_data['version'], $this->get_plugin_version(), '>' ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return bool + */ + function is_beta() { + return ( + ! empty( $this->_storage->beta_data ) && + ( true === $this->_storage->beta_data['is_beta'] ) && + ( $this->get_plugin_version() === $this->_storage->beta_data['version'] ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + private function sync_user_beta_mode() { + $user = $this->get_api_user_scope()->get( '/?plugin_id=' . $this->get_id() . '&fields=is_beta' ); + + if ( $this->is_api_result_entity( $user ) ) { + $this->_user->is_beta = $user->is_beta; + $this->_store_user(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param array $override_with + * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network. + * + * @return array + */ + function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) { + $this->_logger->entrance(); + + $current_user = self::_get_current_wp_user(); + + $activation_action = $this->get_unique_affix() . '_activate_new'; + $return_url = $this->is_anonymous() ? + // If skipped already, then return to the account page. + $this->get_account_url( $activation_action, array(), false ) : + // Return to the module's main page. + $this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) ); + + $versions = $this->get_versions(); + + $params = array_merge( $versions, array( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_nickname' => $current_user->user_nicename, + 'user_email' => $current_user->user_email, + 'user_ip' => WP_FS__REMOTE_ADDR, + 'plugin_slug' => $this->_slug, + 'plugin_id' => $this->get_id(), + 'plugin_public_key' => $this->get_public_key(), + 'plugin_version' => $this->get_plugin_version(), + 'return_url' => fs_nonce_url( $return_url, $activation_action ), + 'account_url' => fs_nonce_url( $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ), 'sync_user' ), + 'is_premium' => $this->is_premium(), + 'is_active' => true, + 'is_uninstalled' => false, + ) ); + + if ( true === $network_level_or_blog_id ) { + if ( ! isset( $override_with['sites'] ) ) { + $params['sites'] = $this->get_sites_for_network_level_optin(); + } + } else { + $site = is_numeric( $network_level_or_blog_id ) ? + array( 'blog_id' => $network_level_or_blog_id ) : + null; + + $site = $this->get_site_info( $site ); + + $params = array_merge( $params, array( + 'site_uid' => $site['uid'], + 'site_url' => $site['url'], + 'site_name' => $site['title'], + 'language' => $site['language'], + 'charset' => $site['charset'], + ) ); + } + + if ( $this->is_pending_activation() && + ! empty( $this->_storage->pending_license_key ) + ) { + $params['license_key'] = $this->_storage->pending_license_key; + } + + if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) { + // Even though rand() is known for its security issues, + // the timestamp adds another layer of protection. + // It would be very hard for an attacker to get the secret key form here. + // Plus, this should never run in production since the secret should never + // be included in the production version. + $params['ts'] = WP_FS__SCRIPT_START_TIME; + $params['salt'] = md5( uniqid( rand() ) ); + $params['secure'] = md5( + $params['ts'] . + $params['salt'] . + $this->get_secret_key() + ); + } + + return array_merge( $params, $override_with ); + } + + /** + * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to. + * 2. If there was an API error, return the API result. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $email + * @param string|bool $first + * @param string|bool $last + * @param string|bool $license_key + * @param bool $is_uninstall If "true", this means that the module is currently being uninstalled. + * In this case, the user and site info will be sent to the server but no + * data will be saved to the WP installation's database. + * @param number|bool $trial_plan_id + * @param bool $is_disconnected Whether or not to opt in without tracking. + * @param null|bool $is_marketing_allowed + * @param array $sites If network-level opt-in, an array of containing details of sites. + * + * @return string|object + * @use WP_Error + */ + function opt_in( + $email = false, + $first = false, + $last = false, + $license_key = false, + $is_uninstall = false, + $trial_plan_id = false, + $is_disconnected = false, + $is_marketing_allowed = null, + $sites = array() + ) { + $this->_logger->entrance(); + + if ( false === $email ) { + $current_user = self::_get_current_wp_user(); + $email = $current_user->user_email; + } + + /** + * @since 1.2.1 If activating with license key, ignore the context-user + * since the user will be automatically loaded from the license. + */ + if ( empty( $license_key ) ) { + // Clean up pending license if opt-ing in again. + $this->_storage->remove( 'pending_license_key' ); + + if ( ! $is_uninstall ) { + $fs_user = Freemius::_get_user_by_email( $email ); + if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) { + return $this->install_with_current_user( + false, + $trial_plan_id, + $sites + ); + } + } + } + + $user_info = array(); + if ( ! empty( $email ) ) { + $user_info['user_email'] = $email; + } + if ( ! empty( $first ) ) { + $user_info['user_firstname'] = $first; + } + if ( ! empty( $last ) ) { + $user_info['user_lastname'] = $last; + } + + if ( ! empty( $sites ) ) { + $is_network = true; + + $user_info['sites'] = $sites; + } else { + $is_network = false; + } + + $params = $this->get_opt_in_params( $user_info, $is_network ); + + $filtered_license_key = false; + if ( is_string( $license_key ) ) { + $filtered_license_key = $this->apply_filters( 'license_key', $license_key ); + $params['license_key'] = $filtered_license_key; + } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { + $params['trial_plan_id'] = $trial_plan_id; + } + + if ( $is_uninstall ) { + $params['uninstall_params'] = array( + 'reason_id' => $this->_storage->uninstall_reason->id, + 'reason_info' => $this->_storage->uninstall_reason->info + ); + } + + if ( isset( $params['license_key'] ) ) { + $fs_user = Freemius::_get_user_by_email( $email ); + + if ( is_object( $fs_user ) ) { + /** + * If opting in with a context license and the context WP Admin user already opted in + * before from the current site, add the user context security params to avoid the + * unnecessary email activation when the context license is owned by the same context user. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + $params = array_merge( $params, FS_Security::instance()->get_context_params( + $fs_user, + false, + 'install_with_existing_user' + ) ); + } + } + + if ( is_bool( $is_marketing_allowed ) ) { + $params['is_marketing_allowed'] = $is_marketing_allowed; + } + + $params['is_disconnected'] = $is_disconnected; + $params['format'] = 'json'; + + $request = array( + 'method' => 'POST', + 'body' => $params, + 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, + ); + + $url = $this->add_show_pending( WP_FS__ADDRESS . '/action/service/user/install/' ); + $response = self::safe_remote_post( $url, $request ); + + if ( is_wp_error( $response ) ) { + /** + * @var WP_Error $response + */ + $result = new stdClass(); + + $error_code = $response->get_error_code(); + $error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) ); + + $result->error = (object) array( + 'type' => $error_type, + 'message' => $response->get_error_message(), + 'code' => $error_code, + 'http' => 402 + ); + + return $result; + } + + // Module is being uninstalled, don't handle the returned data. + if ( $is_uninstall ) { + return true; + } + + /** + * When json_decode() executed on PHP 5.2 with an invalid JSON, it will throw a PHP warning. Unfortunately, the new Theme Check doesn't allow PHP silencing and the theme review team isn't open to change that, therefore, instead of using `@json_decode()` we had to use the method without the `@` directive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:5 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:9 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:12 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:14 + */ + $decoded = is_string( $response['body'] ) ? + json_decode( $response['body'] ) : + null; + + if ( empty( $decoded ) ) { + return false; + } + + if ( ! $this->is_api_result_object( $decoded ) ) { + if ( ! empty( $params['license_key'] ) ) { + // Pass the fully entered license key to the failure handler. + $params['license_key'] = $license_key; + } + + return $is_uninstall ? + $decoded : + $this->apply_filters( 'after_install_failure', $decoded, $params ); + } else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) { + if ( $is_network ) { + $site_ids = array(); + foreach ( $sites as $site ) { + $site_ids[] = $site['blog_id']; + } + + /** + * Store the sites so that they can be installed once the user has clicked on the activation link + * in the email. + * + * @author Leo Fajardo (@leorw) + */ + $this->_storage->pending_sites_info = array( + 'blog_ids' => $site_ids, + 'license_key' => $license_key, + 'trial_plan_id' => $trial_plan_id + ); + } + + // Pending activation, add message. + return $this->set_pending_confirmation( + ( isset( $decoded->email ) ? + $decoded->email : + true ), + false, + $filtered_license_key, + ! empty( $params['trial_plan_id'] ) + ); + } else if ( isset( $decoded->install_secret_key ) ) { + return $this->install_with_new_user( + $decoded->user_id, + $decoded->user_public_key, + $decoded->user_secret_key, + ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? + $decoded->is_marketing_allowed : + null ), + $decoded->install_id, + $decoded->install_public_key, + $decoded->install_secret_key, + false + ); + } else if ( is_array( $decoded->installs ) ) { + return $this->install_many_with_new_user( + $decoded->user_id, + $decoded->user_public_key, + $decoded->user_secret_key, + ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? + $decoded->is_marketing_allowed : + null ), + $decoded->installs, + false + ); + } + + return $decoded; + } + + /** + * Set user and site identities. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_User $user + * @param FS_Site $site + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will + * redirect (or return a URL) to the account page with a special parameter to + * trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + function setup_account( + FS_User $user, + FS_Site $site, + $redirect = true, + $auto_install = false + ) { + return $this->setup_network_account( + $user, + array( $site ), + $redirect, + $auto_install, + false + ); + } + + /** + * Set user and site identities. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param FS_User $user + * @param FS_Site[] $installs + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. + * @param bool $is_network_level_opt_in + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + function setup_network_account( + FS_User $user, + array $installs, + $redirect = true, + $auto_install = false, + $is_network_level_opt_in = true + ) { + $first_install = $installs[0]; + + $this->_user = $user; + $this->_site = $first_install; + + $this->_sync_plans(); + + if ( $this->_storage->handle_gdpr_admin_notice && + $this->should_handle_gdpr_admin_notice() && + FS_GDPR_Manager::instance()->should_show_opt_in_notice() + ) { + /** + * Clear user lock after an opt-in. + */ + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + FS_User_Lock::instance()->unlock(); + } + + if ( 1 < count( $installs ) ) { + // Only network level opt-in can have more than one install. + $is_network_level_opt_in = true; + } +// $is_network_level_opt_in = self::is_ajax_action_static( 'network_activate', $this->_module_id ); + // If Freemius was OFF before, turn it on. + $this->turn_on(); + + $this->handle_account_connection( + $installs, + ( ! $this->_is_network_active || ! $is_network_level_opt_in ) + ); + + if ( is_numeric( $first_install->license_id ) ) { + $this->_license = $this->_get_license_by_id( $first_install->license_id ); + } + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) { + // Remove pending activation sticky notice (if still exist). + $this->_admin_notices->remove_sticky( 'activation_pending' ); + + // Remove plugin from pending activation mode. + unset( $this->_storage->is_pending_activation ); + + if ( ! $this->is_paying_or_trial() ) { + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( '%s activation was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), + 'activation_complete' + ); + } + } + + if ( $this->is_paying_or_trial() ) { + if ( ! $this->is_premium() || + ! $this->has_premium_version() || + ! $this->has_settings_menu() + ) { + if ( $this->is_paying() ) { + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ), + $this->get_plan_title() + ) . $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } else { + $trial_plan = $this->get_trial_plan(); + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), + '' . $this->get_plugin_name() . '' + ) . $this->get_complete_upgrade_instructions( $trial_plan->title ), + 'trial_started', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + ) ); + } + + $plugin_id = fs_request_get( 'plugin_id', false ); + + // Store activation time ONLY for plugins & themes (not add-ons). + if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) { + if ( empty( $this->_storage->activation_timestamp ) ) { + $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; + } + } + + $next_page = ''; + + $extra = array(); + if ( $auto_install ) { + $extra['auto_install'] = 'true'; + } + + if ( is_numeric( $plugin_id ) ) { + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1.6 + * + * Also sync the license after an anonymous user subscribes. + */ + if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) { + // Add-on was installed - sync license right after install. + $next_page = $this->_get_sync_license_url( $plugin_id, true, $extra ); + } + } else { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 If site installed with a valid license, sync license. + */ + if ( $this->is_paying() ) { + $this->_sync_plugin_license( + true, + // Installs data is already synced in the beginning of this method directly or via _set_account(). + false + ); + } + + // Reload the page with the keys. + $next_page = $this->is_anonymous() ? + // If user previously skipped, redirect to account page. + $this->get_account_url( false, $extra ) : + $this->get_after_activation_url( 'after_connect_url', array(), $is_network_level_opt_in ); + } + + if ( ! empty( $next_page ) && $redirect ) { + fs_redirect( $next_page ); + } + + return $next_page; + } + + /** + * Install plugin with new user information after approval. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _install_with_new_user() { + $this->_logger->entrance(); + + if ( $this->is_registered() ) { + return; + } + + if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) || + // @todo This logic should be improved because it's executed on every load of a theme. + $this->is_theme() + ) { +// check_admin_referer( $this->_slug . '_activate_new' ); + + if ( fs_request_has( 'user_secret_key' ) ) { + if ( fs_is_network_admin() && isset( $this->_storage->pending_sites_info ) ) { + $pending_sites_info = $this->_storage->pending_sites_info; + + $this->install_many_pending_with_user( + fs_request_get( 'user_id' ), + fs_request_get( 'user_public_key' ), + fs_request_get( 'user_secret_key' ), + fs_request_get_bool( 'is_marketing_allowed', null ), + $pending_sites_info['blog_ids'], + $pending_sites_info['license_key'], + $pending_sites_info['trial_plan_id'] + ); + } else { + $this->install_with_new_user( + fs_request_get( 'user_id' ), + fs_request_get( 'user_public_key' ), + fs_request_get( 'user_secret_key' ), + fs_request_get_bool( 'is_marketing_allowed', null ), + fs_request_get( 'install_id' ), + fs_request_get( 'install_public_key' ), + fs_request_get( 'install_secret_key' ), + true, + fs_request_get_bool( 'auto_install' ) + ); + } + } else if ( fs_request_has( 'pending_activation' ) ) { + $this->set_pending_confirmation( fs_request_get( 'user_email' ), true ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $id + * @param string $public_key + * @param string $secret_key + * + * @return \FS_User + */ + private function setup_user( $id, $public_key, $secret_key ) { + $user = self::_get_user_by_id( $id ); + + if ( is_object( $user ) ) { + $this->_user = $user; + } else { + $user = new FS_User(); + $user->id = $id; + $user->public_key = $public_key; + $user->secret_key = $secret_key; + + $this->_user = $user; + $user_result = $this->get_api_user_scope()->get(); + $user = new FS_User( $user_result ); + + $this->_user = $user; + $this->_store_user(); + } + + return $user; + } + + /** + * Install plugin with new user. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param number $install_id + * @param string $install_public_key + * @param string $install_secret_key + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will + * redirect (or return a URL) to the account page with a special parameter to + * trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_with_new_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + $install_id, + $install_public_key, + $install_secret_key, + $redirect = true, + $auto_install = false + ) { + /** + * This method is also executed after opting in with a license key since the + * license can be potentially associated with a different owner. + * + * @since 2.0.0 + */ + $user = self::_get_user_by_id( $user_id ); + + if ( ! is_object( $user ) ) { + $user = new FS_User(); + $user->id = $user_id; + $user->public_key = $user_public_key; + $user->secret_key = $user_secret_key; + + $this->_user = $user; + $user_result = $this->get_api_user_scope()->get(); + $user = new FS_User( $user_result ); + } + + $this->_user = $user; + + $site = new FS_Site(); + $site->id = $install_id; + $site->public_key = $install_public_key; + $site->secret_key = $install_secret_key; + + $this->_site = $site; + $site_result = $this->get_api_site_scope()->get(); + $site = new FS_Site( $site_result ); + $this->_site = $site; + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + return $this->setup_account( + $this->_user, + $this->_site, + $redirect, + $auto_install + ); + } + + /** + * Install plugin with user. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param array $site_ids + * @param bool $license_key + * @param bool $trial_plan_id + * @param bool $redirect + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_many_pending_with_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + $site_ids, + $license_key = false, + $trial_plan_id = false, + $redirect = true + ) { + $user = $this->setup_user( $user_id, $user_public_key, $user_secret_key ); + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + $sites = array(); + foreach ( $site_ids as $site_id ) { + $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); + } + + $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); + } + + /** + * Multi-site install with a new user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param object[] $installs + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will + * redirect (or return a URL) to the account page with a special parameter to + * trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_many_with_new_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + array $installs, + $redirect = true, + $auto_install = false + ) { + $this->setup_user( $user_id, $user_public_key, $user_secret_key ); + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + $install_ids = array(); + + foreach ( $installs as $install ) { + $install_ids[] = $install->id; + } + + $left = count( $install_ids ); + $offset = 0; + + $installs = array(); + while ( $left > 0 ) { + $result = $this->get_api_user_scope()->get( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', array_slice( $install_ids, $offset, 25 ) ) ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + // @todo Handle API error. + } + + $installs = array_merge( $installs, $result->installs ); + + $left -= 25; + } + + foreach ( $installs as &$install ) { + $install = new FS_Site( $install ); + } + + return $this->setup_network_account( + $this->_user, + $installs, + $redirect, + $auto_install + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $email + * @param bool $redirect + * @param string|bool $license_key Since 1.2.1.5 + * @param bool $is_pending_trial Since 1.2.1.5 + * + * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page. + */ + private function set_pending_confirmation( + $email = false, + $redirect = true, + $license_key = false, + $is_pending_trial = false + ) { + if ( $this->_ignore_pending_mode ) { + /** + * If explicitly asked to ignore pending mode, set to anonymous mode + * if require confirmation before finalizing the opt-in. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + $this->skip_connection( null, fs_is_network_admin() ); + } else { + // Install must be activated via email since + // user with the same email already exist. + $this->_storage->is_pending_activation = true; + $this->_add_pending_activation_notice( $email, $is_pending_trial ); + } + + if ( ! empty( $license_key ) ) { + $this->_storage->pending_license_key = $license_key; + } + + // Remove the opt-in sticky notice. + $this->_admin_notices->remove_sticky( array( + 'connect_account', + 'trial_promotion', + ) ); + + $next_page = $this->get_after_activation_url( 'after_pending_connect_url' ); + + // Reload the page with with pending activation message. + if ( $redirect ) { + fs_redirect( $next_page ); + } + + return $next_page; + } + + /** + * Install plugin with current logged WP user info. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _install_with_current_user() { + $this->_logger->entrance(); + + if ( $this->is_registered() ) { + return; + } + + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) { +// check_admin_referer( 'activate_existing_' . $this->_plugin->public_key ); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 Add license key if given. + */ + $license_key = fs_request_get( 'license_secret_key' ); + + $this->install_with_current_user( $license_key ); + } + } + + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $license_key + * @param number|bool $trial_plan_id + * @param array $sites Since 2.0.0 + * @param bool $redirect + * + * @return object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. + */ + private function install_with_current_user( + $license_key = false, + $trial_plan_id = false, + $sites = array(), + $redirect = true + ) { + // Get current logged WP user. + $current_user = self::_get_current_wp_user(); + + // Find the relevant FS user by the email. + $user = self::_get_user_by_email( $current_user->user_email ); + + return $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string|bool $license_key + * @param number|bool $trial_plan_id + * @param bool $redirect + * @param bool $setup_account Since 2.0.0. When set to FALSE, executes a light installation without setting up the account as if it's the first opt-in. + * @param array $sites Since 2.0.0. If not empty, should be a collection of site details for the bulk install API request. + * + * @return \FS_Site|object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. If $setup_account is set to `false`, return the newly created install. + */ + private function install_with_user( + FS_User $user, + $license_key = false, + $trial_plan_id = false, + $redirect = true, + $setup_account = true, + $sites = array() + ) { + // We have to set the user before getting user scope API handler. + $this->_user = $user; + + // Install the plugin. + $result = $this->create_installs_with_user( + $user, + $license_key, + $trial_plan_id, + $sites, + $redirect + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + // @todo Handler potential API error of the $result + } + + if ( empty( $sites ) ) { + $site = new FS_Site( $result ); + $this->_site = $site; + + if ( ! $setup_account ) { + $this->_store_site(); + + $this->sync_plan_if_not_exist( $site->plan_id ); + + if ( ! empty( $license_key ) && FS_Plugin_License::is_valid_id( $site->license_id ) ) { + $this->sync_license_if_not_exist( $site->license_id, $license_key ); + } + + $this->_admin_notices->remove_sticky( 'connect_account', false ); + + return $site; + } + + return $this->setup_account( $this->_user, $this->_site, $redirect ); + } else { + $installs = array(); + foreach ( $result->installs as $install ) { + $installs[] = new FS_Site( $install ); + } + + return $this->setup_network_account( + $user, + $installs, + $redirect + ); + } + } + + /** + * Initiate an API request to create a collection of installs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param bool $license_key + * @param bool $trial_plan_id + * @param array $sites + * @param bool $redirect + * @param bool $silent + * + * @return object|mixed + */ + private function create_installs_with_user( + FS_User $user, + $license_key = false, + $trial_plan_id = false, + $sites = array(), + $redirect = false, + $silent = false + ) { + $extra_install_params = array( + 'uid' => $this->get_anonymous_id(), + 'is_disconnected' => false, + ); + + if ( ! empty( $license_key ) ) { + $extra_install_params['license_key'] = $this->apply_filters( 'license_key', $license_key ); + } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { + $extra_install_params['trial_plan_id'] = $trial_plan_id; + } + + if ( ! empty( $sites ) ) { + $extra_install_params['sites'] = $sites; + } + + $args = $this->get_install_data_for_api( $extra_install_params, false, false ); + + // Install the plugin. + $result = $this->get_api_user_scope_by_user( $user )->call( + "/plugins/{$this->get_id()}/installs.json", + 'post', + $args + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + if ( ! empty( $args['license_key'] ) ) { + // Pass full the fully entered license key to the failure handler. + $args['license_key'] = $license_key; + } + + $result = $this->apply_filters( 'after_install_failure', $result, $args ); + + if ( ! $silent ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $result->error->message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + if ( $redirect ) { + /** + * We set the user before getting the user scope API handler, so the user became temporarily + * registered (`is_registered() = true`). Since the API returned an error and we will redirect, + * we have to set the user to `null`, otherwise, the user will be redirected to the wrong + * activation page based on the return value of `is_registered()`. In addition, in case the + * context plugin doesn't have a settings menu and the default page is the `Plugins` page, + * misleading plugin activation errors will be shown on the `Plugins` page. + * + * @author Leo Fajardo (@leorw) + */ + $this->_user = null; + + fs_redirect( $this->get_activation_url( array( 'error' => $result->error->message ) ) ); + } + } + + return $result; + } + + /** + * Tries to activate add-on account based on parent plugin info. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param Freemius $parent_fs + * @param bool|int|null $network_level_or_blog_id True for network level opt-in and integer for opt-in for specified blog in the network. + */ + private function _activate_addon_account( Freemius $parent_fs, $network_level_or_blog_id = null ) { + if ( $this->is_registered() ) { + // Already activated. + return; + } + + /** + * Do not override the `uid` if network-level opt-in since the call to `get_sites_for_network_level_optin()` + * already returns the data for the current blog. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $uid_param_to_override = ( true === $network_level_or_blog_id ) ? + array() : + array( 'uid' => $this->get_anonymous_id() ); + + $params = $this->get_install_data_for_api( + $uid_param_to_override, + false, + false, + /** + * Do not include the data for the current blog if network-level opt-in since the call to `get_sites_for_network_level_optin` + * already includes the data for it. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + ( true !== $network_level_or_blog_id ) + ); + + if ( true === $network_level_or_blog_id ) { + $params['sites'] = $this->get_sites_for_network_level_optin(); + + if ( empty( $params['sites'] ) ) { + return; + } + } + + // Activate add-on with parent plugin credentials. + $result = $parent_fs->get_api_site_scope()->call( + "/addons/{$this->_plugin->id}/installs.json", + 'post', + $params + ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + $error_message = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + $this->get_text_inline( 'An unknown error has occurred.', 'unknown-error' ); + + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $error_message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + + return; + } + + $addon_installs = $result->installs; + foreach ( $addon_installs as $key => $addon_install ) { + $addon_installs[ $key ] = new FS_Site( $addon_install ); + } + + $first_install = $addon_installs[0]; + + // Get user information based on parent's plugin. + $user = $parent_fs->get_user(); + + // First of all, set site and user info - otherwise we won't + // be able to invoke API calls. + $this->_site = $first_install; + $this->_user = $user; + + // Sync add-on plans. + $this->_sync_plans(); + + $this->handle_account_connection( $addon_installs, ! fs_is_network_admin() ); + + // Get site's current plan. + //$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id ); + + // Sync licenses. + $this->_sync_licenses(); + + if ( ! fs_is_network_admin() ) { + // Try to activate premium license. + $this->_activate_license( true ); + } else { + $license_id = fs_request_get( 'license_id' ); + + if ( is_object( $this->_site ) && + FS_Plugin_License::is_valid_id( $license_id ) && + $license_id == $this->_site->license_id + ) { + // License is already activated. + return; + } + + $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? + $this->_get_license_by_id( $license_id ) : + $this->_get_available_premium_license(); + + if ( is_object( $premium_license ) ) { + $this->maybe_network_activate_addon_license( $premium_license ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Site[] $installs + * @param bool $is_site_level + */ + private function handle_account_connection( $installs, $is_site_level ) { + $first_install = $installs[0]; + + if ( $is_site_level ) { + $this->_set_account( $this->_user, $first_install ); + + $this->do_action( 'after_account_connection', $this->_user, $first_install ); + } else { + $this->_store_user(); + + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + $first_blog_id = null; + $blog_2_install_map = array(); + foreach ( $installs as $install ) { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, $install ); + + if ( is_null( $first_blog_id ) ) { + $first_blog_id = $blog_id; + } + + $blog_2_install_map[ $blog_id ] = $install; + } + + if ( ! FS_User::is_valid_id( $this->_storage->network_user_id ) || + ! is_object( self::_get_user_by_id( $this->_storage->network_user_id ) ) + ) { + // Store network user. + $this->_storage->network_user_id = $this->_user->id; + } + + if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { + $this->_storage->network_install_blog_id = $first_blog_id; + } + + if ( count( $installs ) === count( $address_to_blog_map ) ) { + // Super admin opted in for all sites in the network. + $this->_storage->is_network_connected = true; + } + + $this->_store_licenses( false ); + + self::$_accounts->store(); + + // Don't sync the installs data on network upgrade + if ( ! $this->network_upgrade_mode_completed() ) { + $this->send_installs_update(); + } + + // Switch install context back to the first install. + $this->_site = $first_install; + + $current_blog = get_current_blog_id(); + + foreach ( $blog_2_install_map as $blog_id => $install ) { + $this->switch_to_blog( $blog_id ); + + $this->do_action( 'after_account_connection', $this->_user, $install ); + } + + $this->switch_to_blog( $current_blog ); + + $this->do_action( 'after_network_account_connection', $this->_user, $blog_2_install_map ); + } + } + + /** + * Tries to activate parent account based on add-on's info. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param Freemius $parent_fs + */ + private function activate_parent_account( Freemius $parent_fs ) { + if ( ! $this->is_addon() ) { + // This is not an add-on. + return; + } + + if ( $parent_fs->is_registered() ) { + // Already activated. + return; + } + + // Activate parent with add-on's user credentials. + $parent_install = $this->get_api_user_scope()->call( + "/plugins/{$parent_fs->_plugin->id}/installs.json", + 'post', + $parent_fs->get_install_data_for_api( array( + 'uid' => $parent_fs->get_anonymous_id(), + ), false, false ) + ); + + if ( isset( $parent_install->error ) ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $parent_install->error->message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + + return; + } + + $parent_fs->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $parent_fs->is_pending_activation() ) { + $parent_fs->_admin_notices->remove_sticky( 'activation_pending' ); + + unset( $parent_fs->_storage->is_pending_activation ); + } + + // Get user information based on parent's plugin. + $user = $this->get_user(); + + // First of all, set site info - otherwise we won't + // be able to invoke API calls. + $parent_fs->_site = new FS_Site( $parent_install ); + $parent_fs->_user = $user; + + // Sync add-on plans. + $parent_fs->_sync_plans(); + + $parent_fs->_set_account( $user, $parent_fs->_site ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Admin Menu Items + #---------------------------------------------------------------------------------- + + private $_menu_items = array(); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @return array + */ + function get_menu_items() { + return $this->_menu_items; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return string + */ + function get_menu_slug() { + return $this->_menu->get_slug(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _prepare_admin_menu() { +// if ( ! $this->is_on() ) { +// return; +// } + + /** + * When running from a site admin with a network activated module and the connection + * was NOT delegated and the user still haven't skipped or opted-in, then hide the + * site level settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $should_hide_site_admin_settings = ( + $this->_is_network_active && + ! fs_is_network_admin() && + ! $this->is_delegated_connection() && + ! $this->is_anonymous() && + ! $this->is_registered() + ); + + $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); + + if ( ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || + $should_hide_site_admin_settings + ) { + $this->_menu->remove_menu_item( $should_hide_site_admin_settings ); + } else { + $this->do_action( fs_is_network_admin() ? + 'before_network_admin_menu_init' : + 'before_admin_menu_init' + ); + + $this->add_menu_action(); + + $this->add_network_menu_when_missing(); + + $this->add_submenu_items(); + } + } + + /** + * Admin dashboard menu items modifications. + * + * NOTE: admin_menu action executed before admin_init. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + */ + private function add_menu_action() { + if ( $this->is_activation_mode() ) { + if ( $this->is_plugin() || ( $this->has_settings_menu() && ! $this->is_free_wp_org_theme() ) ) { + $this->override_plugin_menu_with_activation(); + } else { + /** + * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page. + */ + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { + add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) ); + } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) || + fs_request_get_bool( 'pending_activation' ) + ) { + add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) ); + } + } + } else { + if ( ! $this->is_registered() ) { + // If not registered try to install user. + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { + $this->_install_with_new_user(); + } + } else if ( + fs_request_is_action( 'sync_user' ) && + ( ! $this->has_settings_menu() || $this->is_free_wp_org_theme() ) + ) { + $this->_handle_account_user_sync(); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + function _redirect_on_clicked_menu_link() { + $this->_logger->entrance(); + + $page = fs_request_get('page'); + $page = is_string($page) ? strtolower($page) : ''; + + $this->_logger->log( 'page = ' . $page ); + + foreach ( $this->_menu_items as $priority => $items ) { + foreach ( $items as $item ) { + if ( isset( $item['url'] ) ) { + if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) { + $this->_logger->log( 'Redirecting to ' . $item['url'] ); + + fs_redirect( $item['url'] ); + } + } + } + } + } + + /** + * Remove plugin's all admin menu items & pages, and replace with activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private function override_plugin_menu_with_activation() { + $this->_logger->entrance(); + + $hook = false; + + if ( ! $this->has_settings_menu() ) { + // Add the opt-in page without a menu item. + $hook = FS_Admin_Menu_Manager::add_subpage( + null, + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_slug, + array( &$this, '_connect_page_render' ) + ); + } else if ( $this->_menu->is_top_level() ) { + if ( $this->_menu->is_override_exact() ) { + // Make sure the current page is matching the activation page. + if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { + return; + } + } + + $hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) ); + + if ( false === $hook ) { + // Create new menu item just for the opt-in. + $hook = FS_Admin_Menu_Manager::add_page( + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->get_slug(), + array( &$this, '_connect_page_render' ) + ); + } + } else { + $menus = array( $this->_menu->get_parent_slug() ); + + if ( $this->_menu->is_override_exact() ) { + // Make sure the current page is matching the activation page. + if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { + return; + } + } + + foreach ( $menus as $parent_slug ) { + $hook = $this->_menu->override_submenu_action( + $parent_slug, + $this->_menu->get_raw_slug(), + array( &$this, '_connect_page_render' ) + ); + + if ( false !== $hook ) { + // Found plugin's submenu item. + break; + } + } + } + + if ( $this->is_activation_page() ) { + // Clean admin page from distracting content. + self::_clean_admin_content_section(); + } + + if ( false !== $hook ) { + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { + $this->_install_with_current_user(); + } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { + $this->_install_with_new_user(); + } + } + } + + /** + * If a plugin was network activated and connected but don't have a network + * level settings, then add an artificial menu item for the Account and other + * Freemius settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function add_network_menu_when_missing() { + $this->_logger->entrance(); + + if ( ! $this->_is_network_active ) { + // Plugin wasn't activated on the network level. + return; + } + + if ( ! fs_is_network_admin() ) { + // The context is not the network admin. + return; + } + + if ( $this->_menu->has_network_menu() ) { + // Plugin already has a network level menu. + return; + } + + if ( $this->is_network_activation_mode() ) { + /** + * Do not add during activation mode, otherwise, there will be duplicate menus while the opt-in + * screen is being shown. + * + * @author Leo Fajardo (@leorw) + */ + return; + } + + if ( ! WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED ) { + if ( $this->is_network_delegated_connection() ) { + // Super-admin delegated the connection to the site admins. + return; + } + } + + if ( ! $this->_menu->has_menu() || $this->_menu->is_top_level() ) { + + if ( $this->_menu->has_menu() || + ! $this->is_addon() || + $this->is_activation_mode() + ) { + $this->_dynamically_added_top_level_page_hook_name = $this->_menu->add_page_and_update( + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->has_menu() ? $this->_menu->get_slug() : $this->_slug + ); + } + } else { + $this->_menu->add_subpage_and_update( + $this->_menu->get_parent_slug(), + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->get_slug() + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1 + * + * return string + */ + function get_top_level_menu_capability() { + global $menu; + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + + foreach ( $menu as $menu_info ) { + /** + * The second element in the menu info array is the capability/role that has access to the menu and the + * third element is the menu slug. + */ + if ( $menu_info[2] === $top_level_menu_slug ) { + return $menu_info[1]; + } + } + + return 'read'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @return string + */ + private function get_top_level_menu_slug() { + return ( $this->is_addon() ? + $this->get_parent_instance()->_menu->get_top_level_menu_slug() : + $this->_menu->get_top_level_menu_slug() ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function get_pricing_cta_label() { + $label = $this->get_text_inline( 'Upgrade', 'upgrade' ); + + if ( $this->is_in_trial_promotion() && + ! $this->is_paying_or_trial() + ) { + // If running a trial promotion, modify the pricing to load the trial. + $label = $this->get_text_inline( 'Start Trial', 'start-trial' ); + } else if ( $this->is_paying() ) { + $label = $this->get_text_inline( 'Pricing', 'pricing' ); + } + + return $label; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_pricing_page_visible() { + return ( + // Has at least one paid plan. + $this->has_paid_plan() && + // Didn't ask to hide the pricing page. + $this->is_page_visible( 'pricing' ) && + // Don't have a valid active license or has more than one plan. + ( ! $this->is_paying() || ! $this->is_single_plan() ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param bool $is_activation_mode + * + * @return bool + */ + private function should_add_submenu_or_action_links( $is_activation_mode ) { + if ( $this->is_addon() ) { + // No submenu items or action links for add-ons. + return false; + } + + if ( $this->is_free_wp_org_theme() && ! fs_is_network_admin() ) { + // Also add action links or submenu items when running in a free .org theme so the tabs will be visible. + return true; + } + + if ( $is_activation_mode && ! $this->is_free_wp_org_theme() ) { + return false; + } + + if ( fs_is_network_admin() ) { + /** + * Add submenu items or action links to network level when plugin was network activated and the super + * admin did NOT delegate the connection of all sites to site admins. + */ + return ( + $this->_is_network_active && + ( WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED || + ! $this->is_network_delegated_connection() ) + ); + } + + return ( ! $this->_is_network_active || $this->is_delegated_connection() ); + } + + /** + * Add default Freemius menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible. + */ + private function add_submenu_items() { + $this->_logger->entrance(); + + $is_activation_mode = $this->is_activation_mode(); + + $add_submenu_items = $this->should_add_submenu_or_action_links( $is_activation_mode ); + + if ( $add_submenu_items ) { + if ( $this->has_affiliate_program() ) { + // Add affiliation page. + $this->add_submenu_item( + $this->get_text_inline( 'Affiliation', 'affiliation' ), + array( &$this, '_affiliation_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Affiliation', 'affiliation' ), + 'manage_options', + 'affiliation', + 'Freemius::_clean_admin_content_section', + WP_FS__DEFAULT_PRIORITY, + $this->is_submenu_item_visible( 'affiliation' ) + ); + } + } + + if ( $add_submenu_items || + ( $is_activation_mode && + $this->is_only_premium() && + $this->is_admin_page( 'account' ) && + fs_request_is_action( $this->get_unique_affix() . '_sync_license' ) + ) + ) { + if ( ! WP_FS__DEMO_MODE && $this->is_registered() ) { + $show_account = ( + $this->is_submenu_item_visible( 'account' ) && + /** + * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans. + */ + ( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() ) + ); + + // Add user account page. + $this->add_submenu_item( + $this->get_text_inline( 'Account', 'account' ), + array( &$this, '_account_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Account', 'account' ), + 'manage_options', + 'account', + array( &$this, '_account_page_load' ), + WP_FS__DEFAULT_PRIORITY, + ( $add_submenu_items && $show_account ) + ); + } + } + + if ( $add_submenu_items ) { + // Add contact page. + $this->add_submenu_item( + $this->get_text_inline( 'Contact Us', 'contact-us' ), + array( &$this, '_contact_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Contact Us', 'contact-us' ), + 'manage_options', + 'contact', + 'Freemius::_clean_admin_content_section', + WP_FS__DEFAULT_PRIORITY, + $this->is_submenu_item_visible( 'contact' ) + ); + + if ( $this->has_addons() ) { + $this->add_submenu_item( + $this->get_text_inline( 'Add-Ons', 'add-ons' ), + array( &$this, '_addons_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Add-Ons', 'add-ons' ), + 'manage_options', + 'addons', + array( &$this, '_addons_page_load' ), + WP_FS__LOWEST_PRIORITY - 1, + $this->is_submenu_item_visible( 'addons' ) + ); + } + } + + if ( $add_submenu_items || + ( $is_activation_mode && $this->is_only_premium() && $this->is_admin_page( 'pricing' ) ) + ) { + if ( ! WP_FS__DEMO_MODE ) { + $show_pricing = ( + $this->is_submenu_item_visible( 'pricing' ) && + $this->is_pricing_page_visible() + ); + + $pricing_cta_text = $this->get_pricing_cta_label(); + $pricing_class = 'upgrade-mode'; + if ( $show_pricing ) { + if ( $this->is_in_trial_promotion() && + ! $this->is_paying_or_trial() + ) { + // If running a trial promotion, modify the pricing to load the trial. + $pricing_class = 'trial-mode'; + } else if ( $this->is_paying() ) { + $pricing_class = ''; + } + } + + // Add upgrade/pricing page. + $this->add_submenu_item( + $pricing_cta_text . '  ' . ( is_rtl() ? $this->get_text_x_inline( '←', 'ASCII arrow left icon', 'symbol_arrow-left' ) : $this->get_text_x_inline( '➤', 'ASCII arrow right icon', 'symbol_arrow-right' ) ), + array( &$this, '_pricing_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_x_inline( 'Pricing', 'noun', 'pricing' ), + 'manage_options', + 'pricing', + 'Freemius::_clean_admin_content_section', + WP_FS__LOWEST_PRIORITY, + ( $add_submenu_items && $show_pricing ), + $pricing_class + ); + } + } + + if ( ! $is_activation_mode || ( true !== $this->_storage->require_license_activation ) ) { + /** + * Add the other menu items if there are any when not in activation mode or license activation is not + * required (license activation is required for registered or anonymous users after activating the + * premium version when the site is not in trial mode or there's no active valid license). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + if ( 0 < count( $this->_menu_items ) ) { + if ( ! $this->_menu->is_top_level() ) { + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + // Append submenu items right after the plugin's submenu item. + $this->order_sub_submenu_items(); + } else { + // Append submenu items. + $this->embed_submenu_items(); + } + } + } + } + + /** + * Moved the actual submenu item additions to a separated function, + * in order to support sub-submenu items when the plugin's settings + * only have a submenu and not top-level menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + private function embed_submenu_items() { + $item_template = $this->_menu->is_top_level() ? + '%s' : + '%s'; + + $top_level_menu_capability = $this->get_top_level_menu_capability(); + + ksort( $this->_menu_items ); + + $is_first_submenu_item = true; + + foreach ( $this->_menu_items as $priority => $items ) { + foreach ( $items as $item ) { + $capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability ); + + $menu_item = sprintf( + $item_template, + $this->get_unique_affix(), + $item['menu_slug'], + ! empty( $item['class'] ) ? $item['class'] : '', + $item['menu_title'] + ); + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + $menu_slug = $this->_menu->get_slug( $item['menu_slug'] ); + + if ( ! isset( $item['url'] ) ) { + $hook = FS_Admin_Menu_Manager::add_subpage( + $item['show_submenu'] ? + $top_level_menu_slug : + null, + $item['page_title'], + $menu_item, + $capability, + $menu_slug, + $item['render_function'] + ); + + if ( false !== $item['before_render_function'] ) { + add_action( "load-$hook", $item['before_render_function'] ); + } + } else { + FS_Admin_Menu_Manager::add_subpage( + $item['show_submenu'] ? + $top_level_menu_slug : + null, + $item['page_title'], + $menu_item, + $capability, + $menu_slug, + array( $this, '' ) + ); + } + + if ( $item['show_submenu'] && $is_first_submenu_item ) { + if ( $this->_is_network_active && ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + /** + * If the top-level menu has been dynamically created, remove the first submenu item that + * WordPress automatically creates when there's no submenu item whose slug matches the + * parent's. In the following example, the `Awesome Plugin` submenu item will be removed. + * + * Awesome Plugin + * - Awesome Plugin <-- we want to remove this since there's no real setting page for the top-level + * + * @author Leo Fajardo (@leorw) + */ + remove_submenu_page( $top_level_menu_slug, $top_level_menu_slug ); + } + + $is_first_submenu_item = false; + } + } + } + } + + /** + * Re-order the submenu items so all Freemius added new submenu items + * are added right after the plugin's settings submenu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + private function order_sub_submenu_items() { + global $submenu; + + $menu_slug = $this->_menu->get_top_level_menu_slug(); + + /** + * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user + * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any + * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us, + * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission. + * + * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu + * of another plugin, only users that have the right role can access its sub-submenus since we will use the + * capability needed to access the parent menu as the capability for the submenus that we will add. + */ + if ( empty( $submenu[ $menu_slug ] ) ) { + return; + } + + $top_level_menu = &$submenu[ $menu_slug ]; + + $all_submenu_items_after = array(); + + $found_submenu_item = false; + + foreach ( $top_level_menu as $submenu_id => $meta ) { + if ( $found_submenu_item ) { + // Remove all submenu items after the plugin's submenu item. + $all_submenu_items_after[] = $meta; + unset( $top_level_menu[ $submenu_id ] ); + } + + if ( $this->_menu->get_raw_slug() === $meta[2] ) { + // Found the submenu item, put all below. + $found_submenu_item = true; + continue; + } + } + + // Embed all plugin's new submenu items. + $this->embed_submenu_items(); + + // Start with specially high number to make sure it's appended. + $i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 ); + foreach ( $all_submenu_items_after as $meta ) { + $top_level_menu[ $i ] = $meta; + $i ++; + } + + // Sort submenu items. + ksort( $top_level_menu ); + } + + /** + * Helper method to return the module's support forum URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function get_support_forum_url() { + return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" ); + } + + /** + * Displays the Support Forum link when enabled. + * + * Can be filtered like so: + * + * function _fs_show_support_menu( $is_visible, $menu_id ) { + * if ( 'support' === $menu_id ) { + * return _fs->is_registered(); + * } + * return $is_visible; + * } + * _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2); + * + */ + function _add_default_submenu_items() { + if ( ! $this->is_on() ) { + return; + } + + if ( ! $this->is_activation_mode() && + ( ( $this->_is_network_active && fs_is_network_admin() ) || + ( ! $this->_is_network_active && is_admin() ) ) + ) { + $this->add_submenu_link_item( + $this->apply_filters( 'support_forum_submenu', $this->get_text_inline( 'Support Forum', 'support-forum' ) ), + $this->get_support_forum_url(), + 'wp-support-forum', + null, + 50, + $this->is_submenu_item_visible( 'support' ) + ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $menu_title + * @param callable $render_function + * @param bool|string $page_title + * @param string $capability + * @param bool|string $menu_slug + * @param bool|callable $before_render_function + * @param int $priority + * @param bool $show_submenu + * @param string $class Since 1.2.1.5 can add custom classes to menu items. + */ + function add_submenu_item( + $menu_title, + $render_function, + $page_title = false, + $capability = 'manage_options', + $menu_slug = false, + $before_render_function = false, + $priority = WP_FS__DEFAULT_PRIORITY, + $show_submenu = true, + $class = '' + ) { + $this->_logger->entrance( 'Title = ' . $menu_title ); + + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + if ( is_object( $parent_fs ) ) { + $parent_fs->add_submenu_item( + $menu_title, + $render_function, + $page_title, + $capability, + $menu_slug, + $before_render_function, + $priority, + $show_submenu, + $class + ); + + return; + } + } + + if ( ! isset( $this->_menu_items[ $priority ] ) ) { + $this->_menu_items[ $priority ] = array(); + } + + $this->_menu_items[ $priority ][] = array( + 'page_title' => is_string( $page_title ) ? $page_title : $menu_title, + 'menu_title' => $menu_title, + 'capability' => $capability, + 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), + 'render_function' => $render_function, + 'before_render_function' => $before_render_function, + 'show_submenu' => $show_submenu, + 'class' => $class, + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $menu_title + * @param string $url + * @param bool $menu_slug + * @param string $capability + * @param int $priority + * @param bool $show_submenu + */ + function add_submenu_link_item( + $menu_title, + $url, + $menu_slug = false, + $capability = 'read', + $priority = WP_FS__DEFAULT_PRIORITY, + $show_submenu = true + ) { + $this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url ); + + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + if ( is_object( $parent_fs ) ) { + $parent_fs->add_submenu_link_item( + $menu_title, + $url, + $menu_slug, + $capability, + $priority, + $show_submenu + ); + + return; + } + } + + if ( ! isset( $this->_menu_items[ $priority ] ) ) { + $this->_menu_items[ $priority ] = array(); + } + + $this->_menu_items[ $priority ][] = array( + 'menu_title' => $menu_title, + 'capability' => $capability, + 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), + 'url' => $url, + 'page_title' => $menu_title, + 'render_function' => 'fs_dummy', + 'before_render_function' => '', + 'show_submenu' => $show_submenu, + ); + } + + #endregion ------------------------------------------------------------------ + + + #-------------------------------------------------------------------------------- + #region Actions / Hooks / Filters + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param string $tag + * + * @return string + */ + public function get_action_tag( $tag ) { + return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $tag + * @param string $slug + * @param bool $is_plugin + * + * @return string + */ + static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) { + $action = "fs_{$tag}"; + + if ( ! empty( $slug ) ) { + $action .= '_' . self::get_module_unique_affix( $slug, $is_plugin ); + } + + return $action; + } + + /** + * Returns a string that can be used to generate a unique action name, + * option name, HTML element ID, or HTML element class. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + public function get_unique_affix() { + return self::get_module_unique_affix( $this->_slug, $this->is_plugin() ); + } + + /** + * Returns a string that can be used to generate a unique action name, + * option name, HTML element ID, or HTML element class. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + * + * @param string $slug + * @param bool $is_plugin + * + * @return string + */ + static function get_module_unique_affix( $slug, $is_plugin = true ) { + $affix = $slug; + + if ( ! $is_plugin ) { + $affix .= '-' . WP_FS__MODULE_TYPE_THEME; + } + + return $affix; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are + * based on the slug for backward compatibility. + * + * @param string $tag + * + * @return string + */ + function get_ajax_action( $tag ) { + return self::get_ajax_action_static( $tag, $this->_module_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $tag + * + * @return string + */ + function get_ajax_security( $tag ) { + return wp_create_nonce( $this->get_ajax_action( $tag ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $tag + */ + function check_ajax_referer( $tag ) { + check_ajax_referer( $this->get_ajax_action( $tag ), 'security' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are + * based on the slug for backward compatibility. + * + * @param string $tag + * @param number|null $module_id + * + * @return string + */ + private static function get_ajax_action_static( $tag, $module_id = null ) { + $action = "fs_{$tag}"; + + if ( ! empty( $module_id ) ) { + $action .= "_{$module_id}"; + } + + return $action; + } + + /** + * Do action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $tag The name of the action to be executed. + * @param mixed $arg,... Optional. Additional arguments which are passed on to the + * functions hooked to the action. Default empty. + * + * @uses do_action() + */ + function do_action( $tag, $arg = '' ) { + $this->_logger->entrance( $tag ); + + $args = func_get_args(); + + call_user_func_array( 'do_action', array_merge( + array( $this->get_action_tag( $tag ) ), + array_slice( $args, 1 ) ) + ); + } + + /** + * Add action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param int $accepted_args + * + * @uses add_action() + */ + function add_action( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY, + $accepted_args = 1 + ) { + $this->_logger->entrance( $tag ); + + add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); + } + + /** + * Add AJAX action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * + * @uses add_action() + * + * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. + */ + function add_ajax_action( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY + ) { + $this->_logger->entrance( $tag ); + + return self::add_ajax_action_static( + $tag, + $function_to_add, + $priority, + $this->_module_id + ); + } + + /** + * Add AJAX action. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param number|null $module_id + * + * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. + * @uses add_action() + * + */ + static function add_ajax_action_static( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY, + $module_id = null + ) { + self::$_static_logger->entrance( $tag ); + + if ( ! self::is_ajax_action_static( $tag, $module_id ) ) { + return false; + } + + add_action( + 'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ), + $function_to_add, + $priority, + 0 + ); + + self::$_static_logger->info( "$tag AJAX callback action added." ); + + return true; + } + + /** + * Send a JSON response back to an Ajax request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $response + */ + static function shoot_ajax_response( $response ) { + wp_send_json( $response ); + } + + /** + * Send a JSON response back to an Ajax request, indicating success. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $data Data to encode as JSON, then print and exit. + */ + static function shoot_ajax_success( $data = null ) { + wp_send_json_success( $data ); + } + + /** + * Send a JSON response back to an Ajax request, indicating failure. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $error Optional error message. + */ + static function shoot_ajax_failure( $error = '' ) { + $result = array( 'success' => false ); + if ( ! empty( $error ) ) { + $result['error'] = $error; + } + + wp_send_json( $result ); + } + + /** + * Apply filter, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $tag The name of the filter hook. + * @param mixed $value The value on which the filters hooked to `$tag` are applied on. + * + * @return mixed The filtered value after all hooked functions are applied to it. + * + * @uses apply_filters() + */ + function apply_filters( $tag, $value ) { + $this->_logger->entrance( $tag ); + + $args = func_get_args(); + array_unshift( $args, $this->get_unique_affix() ); + + return call_user_func_array( 'fs_apply_filter', $args ); + } + + /** + * Add filter, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param int $accepted_args + * + * @uses add_filter() + */ + function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) { + $this->_logger->entrance( $tag ); + + add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); + } + + /** + * Check if has filter. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $tag + * @param callable|bool $function_to_check Optional. The callback to check for. Default false. + * + * @return false|int + * + * @uses has_filter() + */ + function has_filter( $tag, $function_to_check = false ) { + $this->_logger->entrance( $tag ); + + return has_filter( $this->get_action_tag( $tag ), $function_to_check ); + } + + #endregion + + /** + * Override default i18n text phrases. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string[] string $key_value + * + * @uses fs_override_i18n() + */ + function override_i18n( $key_value ) { + fs_override_i18n( $key_value, $this->_slug ); + } + + /* Account Page + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Update site information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store Flush to Database if true. + * @param null|int $network_level_or_blog_id Since 2.0.0 + * @param \FS_Site $site Since 2.0.0 + */ + private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null ) { + $this->_logger->entrance(); + + if ( empty( $this->_site->id ) ) { + $this->_logger->error( "Empty install ID, can't store site." ); + + return; + } + + $site_clone = is_object( $site ) ? $site : $this->_site; + $encrypted_site = clone $site_clone; + + $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id ); + + $prev_stored_user_id = $this->_storage->get( 'prev_user_id', false, $network_level_or_blog_id ); + + if ( empty( $prev_stored_user_id ) && + $this->_user->id != $this->_site->user_id + ) { + /** + * Store the current user ID as the previous user ID so that the previous user can be used + * as the install's owner while the new owner's details are not yet available. + * + * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original` + * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica` + * will be using the previous user until it is updated again (e.g.: until the next clone of `original` + * into `replica`. + * + * @author Leo Fajardo (@leorw) + */ + $this->_storage->store( 'prev_user_id', $sites[ $this->_slug ]->user_id, $network_level_or_blog_id ); + } + + $sites[ $this->_slug ] = $encrypted_site; + + $this->set_account_option( 'sites', $sites, $store, $network_level_or_blog_id ); + } + + /** + * Update plugin's plans information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $store Flush to Database if true. + */ + private function _store_plans( $store = true ) { + $this->_logger->entrance(); + + $plans = self::get_all_plans( $this->_module_type ); + + // Copy plans. + $encrypted_plans = array(); + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + $encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] ); + } + + $plans[ $this->_slug ] = $encrypted_plans; + + $this->set_account_option( 'plans', $plans, $store ); + } + + /** + * Update user's plugin licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $store + * @param number|bool $module_id + * @param FS_Plugin_License[] $licenses + */ + private function _store_licenses( $store = true, $module_id = false, $licenses = array() ) { + $this->_logger->entrance(); + + $all_licenses = self::get_all_licenses(); + + if ( ! FS_Plugin::is_valid_id( $module_id ) ) { + $module_id = $this->_module_id; + + $user_licenses = is_array( $this->_licenses ) ? + $this->_licenses : + array(); + + if ( empty( $user_licenses ) ) { + // If the context user doesn't have any license, don't update the licenses collection. + return; + } + + $new_user_licenses_map = array(); + foreach ( $user_licenses as $user_license ) { + $new_user_licenses_map[ $user_license->id ] = $user_license; + } + + self::store_user_id_license_ids_map( array_keys( $new_user_licenses_map ), $this->_module_id, $this->_user->id ); + + // Update user licenses. + $licenses_to_update_count = count( $new_user_licenses_map ); + foreach ( $all_licenses[ $module_id ] as $key => $license ) { + if ( 0 === $licenses_to_update_count ) { + break; + } + + if ( isset( $new_user_licenses_map[ $license->id ] ) ) { + // Update license. + $all_licenses[ $module_id ][ $key ] = $new_user_licenses_map[ $license->id ]; + unset( $new_user_licenses_map[ $license->id ] ); + + $licenses_to_update_count --; + } + } + + if ( ! empty( $new_user_licenses_map ) ) { + // Add new licenses. + $all_licenses[ $module_id ] = array_merge( array_values( $new_user_licenses_map ), $all_licenses[ $module_id ] ); + } + + $licenses = $all_licenses[ $module_id ]; + } + + if ( ! isset( $all_licenses[ $module_id ] ) ) { + $all_licenses[ $module_id ] = array(); + } + + $all_licenses[ $module_id ] = $licenses; + + self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); + } + + /** + * Update user information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store Flush to Database if true. + */ + private function _store_user( $store = true ) { + $this->_logger->entrance(); + + if ( empty( $this->_user->id ) ) { + $this->_logger->error( "Empty user ID, can't store user." ); + + return; + } + + $users = self::get_all_users(); + $users[ $this->_user->id ] = $this->_user; + self::$_accounts->set_option( 'users', $users, $store ); + } + + /** + * Update new updates information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param FS_Plugin_Tag|null $update + * @param bool $store Flush to Database if true. + * @param bool|number $plugin_id + */ + private function _store_update( $update, $store = true, $plugin_id = false ) { + $this->_logger->entrance(); + + if ( $update instanceof FS_Plugin_Tag ) { + $update->updated = time(); + } + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $updates = self::get_all_updates(); + $updates[ $plugin_id ] = $update; + self::$_accounts->set_option( 'updates', $updates, $store ); + } + + /** + * Update new updates information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin[] $plugin_addons + * @param bool $store Flush to Database if true. + */ + private function _store_addons( $plugin_addons, $store = true ) { + $this->_logger->entrance(); + + $addons = self::get_all_addons(); + $addons[ $this->_plugin->id ] = $plugin_addons; + self::$_accounts->set_option( 'addons', $addons, $store ); + } + + /** + * Delete plugin's associated add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + * + * @param bool $store + * + * @return bool + */ + private function _delete_account_addons( $store = true ) { + $all_addons = self::get_all_account_addons(); + + if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) { + return false; + } + + unset( $all_addons[ $this->_plugin->id ] ); + + self::$_accounts->set_option( 'account_addons', $all_addons, $store ); + + return true; + } + + /** + * Update account add-ons list. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin[] $addons + * @param bool $store Flush to Database if true. + */ + private function _store_account_addons( $addons, $store = true ) { + $this->_logger->entrance(); + + $all_addons = self::get_all_account_addons(); + $all_addons[ $this->_plugin->id ] = $addons; + self::$_accounts->set_option( 'account_addons', $all_addons, $store ); + } + + /** + * Purges the cache for the valid user licenses API call so that when the `Account` or `Add-Ons` page is loaded, + * the valid user licenses will be fetched again and the account add-ons may be updated. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + private function purge_valid_user_licenses_cache() { + $this->get_api_user_scope()->purge_cache( $this->get_valid_user_licenses_endpoint() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param array $all_licenses + * @param number|null $site_license_id + * @param bool $include_parent_licenses + * + * @return array + */ + private function get_foreign_licenses_info( $all_licenses, $site_license_id = null, $include_parent_licenses = false ) { + $foreign_licenses = array( + 'ids' => array(), + 'license_keys' => array() + ); + + $parent_license_ids_map = array(); + + foreach ( $all_licenses as $license ) { + if ( $license->user_id == $this->_user->id || $license->id == $site_license_id ) { + continue; + } + + $foreign_licenses['ids'][] = $license->id; + $foreign_licenses['license_keys'][] = $license->secret_key; + + if ( + $include_parent_licenses && + is_object( $this->_license ) && + FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) && + ! isset( $parent_license_ids_map[ $this->_license->parent_license_id ] ) + ) { + /** + * Include the parent license's info only if it has not been included before since child licenses + * can have the same parent license. + */ + $foreign_licenses['ids'][] = $this->_license->parent_license_id; + $foreign_licenses['license_keys'][] = $license->secret_key; + + $parent_license_ids_map[ $this->_license->parent_license_id ] = true; + } + } + + if ( empty( $foreign_licenses['ids'] ) ) { + $foreign_licenses = array(); + } + + return $foreign_licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + private function get_valid_user_licenses_endpoint() { + $user_licenses_endpoint = '/licenses.json?type=active' . + ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? '&is_enriched=true' : '' ); + + $foreign_licenses = $this->get_foreign_licenses_info( self::get_all_licenses( $this->_module_id ), null, true ); + + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); + } + + return $user_licenses_endpoint; + } + + /** + * Fetches active licenses that are enriched with product type if there's a context `bundle_id` and bundle + * licenses enriched with product IDs if there are any. From the licenses, the `get_updated_account_addons` + * method filters out non–add-on product IDs and stores the add-on IDs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return stdClass[] array + */ + private function fetch_valid_user_licenses() { + $this->_logger->entrance(); + + $result = $this->get_api_user_scope()->get( $this->get_valid_user_licenses_endpoint() ); + + if ( ! $this->is_api_result_object( $result, 'licenses' ) || + ! is_array( $result->licenses ) + ) { + return array(); + } + + return $result->licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return number[] Account add-on IDs. + */ + function get_updated_account_addons() { + $addons = $this->get_addons(); + if ( empty( $addons ) ) { + return array(); + } + + $account_addons = $this->get_account_addons(); + if ( ! is_array( $account_addons ) ) { + $account_addons = array(); + } + + $user_licenses = $this->is_registered() ? + $this->fetch_valid_user_licenses() : + array(); + + if ( empty( $user_licenses ) ) { + return $account_addons; + } + + $addon_ids = array(); + foreach ( $addons as $addon ) { + $addon_ids[] = $addon->id; + } + + $license_product_ids = array(); + + foreach ( $user_licenses as $license ) { + if ( isset( $license->plugin_type ) && 'bundle' === $license->plugin_type ) { + $license_product_ids = array_merge( $license_product_ids, $license->products ); + } else { + $license_product_ids[] = $license->plugin_id; + } + } + + // Filter out non–add-on IDs. + $new_account_addons = array_intersect( $addon_ids, $license_product_ids ); + if ( count( $new_account_addons ) !== count( $account_addons ) ) { + $this->_store_account_addons( array_unique( $new_account_addons ) ); + } + + return $new_account_addons; + } + + /** + * Store account params in the Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param null|int $blog_id Since 2.0.0 + */ + private function _store_account( $blog_id = null ) { + $this->_logger->entrance(); + + $this->_store_site( false, $blog_id ); + $this->_store_user( false ); + $this->_store_plans( false ); + $this->_store_licenses( false ); + + self::$_accounts->store( $blog_id ); + } + + /** + * Sync user's information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + */ + private function _handle_account_user_sync() { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + // Get user's information. + $user = $api->get( '/', true ); + + if ( isset( $user->id ) ) { + $this->_user->first = $user->first; + $this->_user->last = $user->last; + $this->_user->email = $user->email; + + $is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' ); + + if ( $user->is_verified && + ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) + ) { + $this->_user->is_verified = true; + + $this->do_action( 'account_email_verified', $user->email ); + + $this->_admin_notices->add( + $this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ), + $this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!', + 'success', + // Make admin sticky if account menu item is invisible, + // since the page will be auto redirected to the plugin's + // main settings page, and the non-sticky message + // will disappear. + ! $is_menu_item_account_visible, + 'email_verified' + ); + } + + // Flush user details to DB. + $this->_store_user(); + + $this->do_action( 'after_account_user_sync', $user ); + + /** + * If account menu item is hidden, redirect to plugin's main settings page. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @link https://github.com/Freemius/wordpress-sdk/issues/6 + */ + if ( ! $is_menu_item_account_visible ) { + fs_redirect( $this->_get_admin_page_url() ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * @uses FS_Api + * + * @param number|bool $license_id + * + * @return FS_Subscription|object|bool + */ + private function _fetch_site_license_subscription( $license_id = false ) { + $this->_logger->entrance(); + $api = $this->get_api_site_scope(); + + if ( ! is_numeric( $license_id ) ) { + $license_id = FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) ? + $this->_license->parent_license_id : + $this->_license->id; + } + + $result = $api->get( "/licenses/{$license_id}/subscriptions.json", true ); + + return ! isset( $result->error ) ? + ( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ? + new FS_Subscription( $result->subscriptions[0] ) : + false + ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * @uses FS_Api + * + * @param number|bool $plan_id + * + * @return FS_Plugin_Plan|object + */ + private function _fetch_site_plan( $plan_id = false ) { + $this->_logger->entrance(); + $api = $this->get_api_site_scope(); + + if ( ! is_numeric( $plan_id ) ) { + $plan_id = $this->_site->plan_id; + } + + $plan = $api->get( "/plans/{$plan_id}.json", true ); + + return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * @uses FS_Api + * + * @return FS_Plugin_Plan[]|object + */ + private function _fetch_plugin_plans() { + $this->_logger->entrance(); + $api = $this->get_current_or_network_user_api_scope(); + + /** + * @since 1.2.3 When running in DEV mode, retrieve pending plans as well. + */ + $result = $api->get( $this->add_show_pending( "/plugins/{$this->_module_id}/plans.json" ), true ); + + if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) { + for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) { + $result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] ); + } + + $result = $result->plans; + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $plan_id + * + * @return \FS_Plugin_Plan|object + */ + private function fetch_plan_by_id( $plan_id ) { + $this->_logger->entrance(); + $api = $this->get_current_or_network_user_api_scope(); + + $result = $api->get( "/plugins/{$this->_module_id}/plans/{$plan_id}.json", true ); + + return $this->is_api_result_entity( $result ) ? + new FS_Plugin_Plan( $result ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * @uses FS_Api + * + * @param number|bool $plugin_id + * @param number|bool $site_license_id + * @param array $foreign_licenses @since 2.0.0. This is used by network-activated plugins. + * @param number|null $blog_id + * + * @return FS_Plugin_License[]|object + */ + private function _fetch_licenses( + $plugin_id = false, + $site_license_id = false, + $foreign_licenses = array(), + $blog_id = null + ) { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $user_licenses_endpoint = "/plugins/{$plugin_id}/licenses.json?is_enriched=true"; + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); + } + + $result = $api->get( $user_licenses_endpoint, true ); + + $is_site_license_synced = false; + + $api_errors = array(); + + if ( $this->is_api_result_object( $result, 'licenses' ) && + is_array( $result->licenses ) + ) { + for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { + $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); + + if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) { + $is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id ); + } + } + + $result = $result->licenses; + } else { + $api_errors[] = $result; + $result = array(); + } + + if ( ! $is_site_license_synced ) { + if ( ! is_null( $blog_id ) ) { + /** + * If blog ID is not null, the request is for syncing of the license of a single site via the + * network-level "Account" page. + * + * @author Leo Fajardo (@leorw) + */ + $this->switch_to_blog( $blog_id ); + } + + $api = $this->get_api_site_scope(); + + if ( is_numeric( $site_license_id ) ) { + // Try to retrieve a foreign license that is linked to the install. + $api_result = $api->call( '/licenses.json?is_enriched=true' ); + + if ( $this->is_api_result_object( $api_result, 'licenses' ) && + is_array( $api_result->licenses ) + ) { + $licenses = $api_result->licenses; + + if ( ! empty( $licenses ) ) { + $result[] = new FS_Plugin_License( $licenses[0] ); + } + } else { + $api_errors[] = $api_result; + } + } else if ( + is_object( $this->_license ) && + /** + * Sync only if the license belongs to the context plugin. `$plugin_id` can be an add-on ID while + * the FS instance that does the syncing is the parent FS instance. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->_license->plugin_id == $plugin_id + ) { + $is_license_in_result = false; + if ( ! empty( $result ) ) { + foreach ( $result as $license ) { + if ( $license->id == $this->_license->id ) { + $is_license_in_result = true; + break; + } + } + } + + if ( ! $is_license_in_result ) { + // Fetch foreign license by ID and license key. + $license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . + urlencode( $this->_license->secret_key ) . '&is_enriched=true' ); + + if ( $this->is_api_result_entity( $license ) ) { + $result[] = new FS_Plugin_License( $license ); + } else { + $api_errors[] = $license; + } + } + } + + if ( ! is_null( $blog_id ) ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + } + + if ( is_array( $result ) && 0 < count( $result ) ) { + // If found at least one license, return license collection even if there are errors. + return $result; + } + + if ( ! empty( $api_errors ) ) { + // If found any errors and no licenses, return first error. + return $api_errors[0]; + } + + // Fallback to empty licenses list. + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param string $license_key + * + * @return \FS_Plugin_License|object + */ + private function fetch_license_by_key( $license_id, $license_key ) { + $this->_logger->entrance(); + + $api = $this->get_current_or_network_user_api_scope(); + + $result = $api->get( "/licenses/{$license_id}.json?license_key=" . urlencode( $license_key ) ); + + return $this->is_api_result_entity( $result ) ? + new FS_Plugin_License( $result ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * @uses FS_Api + * + * @param number|bool $plugin_id + * @param bool $flush + * + * @return FS_Payment[]|object + */ + function _fetch_payments( $plugin_id = false, $flush = false ) { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $include_bundles = ( + is_object( $this->_plugin ) && + FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) + ); + + $result = $api->get( + "/plugins/{$plugin_id}/payments.json?include_addons=true" . ($include_bundles ? '&include_bundles=true' : ''), + $flush + ); + + if ( ! isset( $result->error ) ) { + for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) { + $result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] ); + } + $result = $result->payments; + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * @uses FS_Api + * + * @param bool $flush + * + * @return \FS_Billing|mixed + */ + function _fetch_billing( $flush = false ) { + require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php'; + + $billing = $this->get_api_user_scope()->get( 'billing.json', $flush ); + + if ( $this->is_api_result_entity( $billing ) ) { + $billing = new FS_Billing( $billing ); + } + + return $billing; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Plugin_License[] $licenses + * @param number $module_id + */ + private function _update_licenses( $licenses, $module_id ) { + $this->_logger->entrance(); + + if ( is_array( $licenses ) ) { + for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) { + $licenses[ $i ]->updated = time(); + } + } + + $this->_store_licenses( true, $module_id, $licenses ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * + * @return object|false New plugin tag info if exist. + */ + private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { + $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than ); + + if ( ! is_object( $latest_tag ) ) { + return false; + } + + $plugin_version = $this->get_plugin_version(); + + // Check if version is actually newer. + $has_new_version = + // If it's an non-installed add-on then always return latest. + ( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) || + // Compare versions. + version_compare( $plugin_version, $latest_tag->version, '<' ); + + $this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' ); + + $is_latest_version_beta = ( 'beta' === $latest_tag->release_mode ); + + $this->_storage->beta_data = array( + 'is_beta' => $is_latest_version_beta, + 'version' => $latest_tag->version + ); + + return $has_new_version ? $latest_tag : false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * + * @return bool|FS_Plugin_Tag + */ + function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { + $this->_logger->entrance(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than ); + $updates = $this->get_all_updates(); + + return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false; + } + + /** + * Check if site assigned with active license. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @deprecated Please use has_active_valid_license() instead because license can be cancelled. + */ + function has_active_license() { + return ( + is_object( $this->_license ) && + is_numeric( $this->_license->id ) && + ! $this->_license->is_expired() + ); + } + + /** + * Check if site assigned with active & valid (not expired) license. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + */ + function has_active_valid_license() { + return self::is_active_valid_license( $this->_license ); + } + + /** + * Check if a given license is active & valid (not expired). + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + * + * @param FS_Plugin_License $license + * + * @return bool + */ + private static function is_active_valid_license( $license ) { + return ( + is_object( $license ) && + FS_Plugin_License::is_valid_id( $license->id ) && + $license->is_active() && + $license->is_valid() + ); + } + + /** + * Checks if there's any site that is associated with an active & valid license. + * This logic is used to determine if the admin can download the premium code base from a network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + * + * @return bool + */ + function has_any_active_valid_license() { + if ( ! fs_is_network_admin() ) { + return $this->has_active_valid_license(); + } + + $installs = $this->get_blog_install_map(); + $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); + + foreach ( $installs as $blog_id => $install ) { + if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { + continue; + } + + foreach ( $all_plugin_licenses as $license ) { + if ( $license->id == $install->license_id ) { + if ( self::is_active_valid_license( $license ) ) { + return true; + } + } + } + } + + return false; + } + + /** + * Check if site assigned with license with enabled features. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function has_features_enabled_license() { + return ( + is_object( $this->_license ) && + is_numeric( $this->_license->id ) && + $this->_license->is_features_enabled() + ); + } + + /** + * Check if user is a trial or have feature enabled license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function can_use_premium_code() { + return $this->is_trial() || $this->has_features_enabled_license(); + } + + /** + * Checks if the current user can activate plugins or switch themes. Note that this method should only be used + * after the `init` action is triggered because it is using `current_user_can()` which is only functional after + * the context user is authenticated. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_user_admin() { + /** + * Require a super-admin when network activated, running from the network level OR if + * running from the site level but not delegated the opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( $this->_is_network_active && + ( fs_is_network_admin() || ! $this->is_delegated_connection() ) + ) { + return is_super_admin(); + } + + return ( $this->is_plugin() && current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) + || ( $this->is_theme() && current_user_can( 'switch_themes' ) ); + } + + /** + * Sync site's plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by + * the admin. + * @param bool $is_context_single_site @since 2.0.0. This is used when syncing a license for a single install from the + * network-level "Account" page. + * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the + * `_sync_plugin_license` method in order to switch to the previous blog when sending + * updates for a single site in case `execute_cron` has switched to a different blog. + */ + private function _sync_license( $background = false, $is_context_single_site = false, $current_blog_id = null ) { + $this->_logger->entrance(); + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + + $is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() ); + + if ( $is_addon_sync ) { + $this->_sync_addon_license( $plugin_id, $background ); + } else { + $this->_sync_plugin_license( $background, true, $is_context_single_site, $current_blog_id ); + } + + $this->do_action( 'after_account_plan_sync', $this->get_plan_name() ); + } + + /** + * Sync plugin's add-on license. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * @uses FS_Api + * + * @param number $addon_id + * @param bool $background + */ + private function _sync_addon_license( $addon_id, $background ) { + $this->_logger->entrance(); + + if ( $this->is_addon_activated( $addon_id ) ) { + // If already installed, use add-on sync. + $fs_addon = self::get_instance_by_id( $addon_id ); + + if ( + // Add-on is network activated and network integrated. + $fs_addon->is_network_active() || + // Background sync cron. + self::is_cron() || + // Add-on is not network activated or not network integrated. + ! fs_is_network_admin() + ) { + $fs_addon->_sync_license( $background ); + + return; + } + } + + // Validate add-on exists. + $addon = $this->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + return; + } + + // Add add-on into account add-ons. + $account_addons = $this->get_account_addons(); + if ( ! is_array( $account_addons ) ) { + $account_addons = array(); + } + $account_addons[] = $addon->id; + $account_addons = array_unique( $account_addons ); + $this->_store_account_addons( $account_addons ); + + // Load add-on licenses. + $licenses = $this->_fetch_licenses( $addon->id ); + + // Sync add-on licenses. + if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { + $this->_update_licenses( $licenses, $addon->id ); + + if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) { + $plans_result = $this->get_api_site_or_plugin_scope()->get( $this->add_show_pending( "/addons/{$addon_id}/plans.json" ) ); + + if ( ! isset( $plans_result->error ) ) { + $plans = array(); + foreach ( $plans_result->plans as $plan ) { + $plans[] = new FS_Plugin_Plan( $plan ); + } + + $this->_admin_notices->add_sticky( + sprintf( + ( FS_Plan_Manager::instance()->has_free_plan( $plans ) ? + $this->get_text_inline( 'Your %s Add-on plan was successfully upgraded.', 'addon-successfully-upgraded-message' ) : + /* translators: %s:product name, e.g. Facebook add-on was successfully... */ + $this->get_text_inline( '%s Add-on was successfully purchased.', 'addon-successfully-purchased-message' ) ), + $addon->title + ) . ' ' . $this->get_latest_download_link( + $this->get_text_inline( 'Download the latest version', 'download-latest-version' ), + $addon_id + ), + 'addon_plan_upgraded_' . $addon->slug, + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + } + } + + /** + * Sync site's plugin plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by the admin. + * @param bool $send_installs_update Since 2.0.0 + * @param bool $is_context_single_site Since 2.0.0. This is used when sending an update for a single install and + * syncing its license from the network-level "Account" page (e.g.: after + * activating a license only for the single install). + * @param int|null $current_blog_id Since 2.2.3. This is passed from the `execute_cron` method so that it + * can be used here to switch to the previous blog in case `execute_cron` + * has switched to a different blog. + */ + private function _sync_plugin_license( + $background = false, + $send_installs_update = true, + $is_context_single_site = false, + $current_blog_id = null + ) { + $this->_logger->entrance(); + + $plan_change = 'none'; + + $is_site_level_sync = ( $is_context_single_site || fs_is_blog_admin() || ! $this->_is_network_active ); + + if ( ! $send_installs_update ) { + $site = $this->_site; + } else { + /** + * Sync site info. + * + * @todo This line will execute install sync on a daily basis, even if running the free version (for opted-in users). The reason we want to keep it that way is for cases when the user was a paying customer, then there was a failure in subscription payment, and then after some time the payment was successful. This could be heavily optimized. For example, we can skip the $flush if the current install was never associated with a paid version. + */ + if ( $is_site_level_sync ) { + /** + * Switch to the previous blog since `execute_cron` may have switched to a different blog. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( is_numeric( $current_blog_id ) ) { + $this->switch_to_blog( $current_blog_id ); + } + + $result = $this->send_install_update( array(), true ); + $is_valid = $this->is_api_result_entity( $result ); + } else { + $result = $this->send_installs_update( array(), true ); + $is_valid = $this->is_api_result_object( $result, 'installs' ); + } + + if ( ! $is_valid ) { + if ( $is_context_single_site ) { + // Switch back to the main blog so that the following logic will have the right entities. + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + // Show API messages only if not background sync or if paying customer. + if ( ! $background || $this->is_paying() ) { + // Try to ping API to see if not blocked. + if ( ! FS_Api::test() ) { + /** + * Failed to ping API - blocked! + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages. + */ + $api = $this->get_api_site_scope(); + + if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { + self::$_global_admin_notices->add( + sprintf( + $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', 'server-blocking-access' ), + $this->get_plugin_name(), + '' . implode( ', ', $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com' + ) ) ) . '' + ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error', + $background, + 'api_blocked' + ); + } + } else { + // Authentication params are broken. + $this->_admin_notices->add( + $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + } + + // No reason to continue with license sync while there are API issues. + return; + } + + if ( $is_site_level_sync ) { + $site = new FS_Site( $result ); + } else { + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + // Find the current context install. + $site = null; + foreach ( $result->installs as $install ) { + if ( $install->id == $this->_site->id ) { + $site = new FS_Site( $install ); + } else { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, new FS_Site( $install ) ); + } + } + } + + // Sync plans. + $this->_sync_plans(); + } + + // Remove sticky API connectivity message. + self::$_global_admin_notices->remove_sticky( 'api_blocked' ); + + if ( ! $this->has_paid_plan() ) { + $this->_site = $site; + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } else { + $context_blog_id = 0; + + if ( $is_context_single_site ) { + $context_blog_id = get_current_blog_id(); + + // Switch back to the main blog in order to properly sync the license. + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + /** + * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license + * associated with that ID is not included in the user's licenses collection. + */ + $this->_sync_licenses( + $site->license_id, + ( $is_context_single_site ? + $context_blog_id : + null + ) + ); + + if ( $is_context_single_site ) { + $this->switch_to_blog( $context_blog_id ); + } + + // Check if plan / license changed. + if ( $site->plan_id != $this->_site->plan_id || + // Check if trial started. + $site->trial_plan_id != $this->_site->trial_plan_id || + $site->trial_ends != $this->_site->trial_ends || + // Check if license changed. + $site->license_id != $this->_site->license_id + ) { + if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) { + // New trial started. + $this->_site = $site; + $plan_change = 'trial_started'; + + // For trial with subscription use-case. + $new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id ); + + if ( is_object( $new_license ) && $new_license->is_valid() ) { + $this->_site = $site; + $this->_update_site_license( $new_license ); + $this->_store_licenses(); + + $this->_sync_site_subscription( $this->_license ); + } + } else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) { + // Was in trial, but now trial expired and no license ID. + // New trial started. + $this->_site = $site; + $plan_change = 'trial_expired'; + } else { + $is_free = $this->is_free_plan(); + + // Make sure license exist and not expired. + $new_license = is_null( $site->license_id ) ? + null : + $this->_get_license_by_id( $site->license_id ); + + if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) { + // License cancelled. + $this->_site = $site; + $this->_update_site_license( $new_license ); + $this->_store_licenses(); + + $plan_change = 'cancelled'; + } else if ( $is_free && ( ( ! is_object( $new_license ) || $new_license->is_expired() ) ) ) { + // The license is expired, so ignore upgrade method. + $this->_site = $site; + } else { + // License changed. + $this->_site = $site; + + /** + * IMPORTANT: + * The line below should be executed before trying to activate the license on the rest of the network, otherwise, the license' activation counters may be out of sync + there's no need to activate the license on the context site since it's already activated on it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $this->_update_site_license( $new_license ); + + if ( ! $is_context_single_site && + fs_is_network_admin() && + $this->_is_network_active && + $new_license->quota > 1 && + get_blog_count() > 1 + ) { + // See if license can activated on all sites. + if ( ! $this->try_activate_license_on_network( $this->_user, $new_license ) ) { + if ( ! fs_request_get_bool( 'auto_install' ) ) { + // Open the license activation dialog box on the account page. + add_action( 'admin_footer', array( + &$this, + '_open_license_activation_dialog_box' + ) ); + } + } + } + + $this->_store_licenses(); + + $plan_change = $is_free ? + ( $this->is_only_premium() ? 'activated' : 'upgraded' ) : + ( is_object( $new_license ) ? + 'changed' : + 'downgraded' ); + } + } + + // Store updated site info. + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } else { + if ( is_object( $this->_license ) && $this->_license->is_expired() ) { + if ( ! $this->has_features_enabled_license() ) { + $this->_deactivate_license(); + $plan_change = 'downgraded'; + } else { + $plan_change = 'expired'; + } + } + + if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) { + $this->_sync_site_subscription( $this->_license ); + } + } + + if ( $this->is_addon() || $this->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $this->purge_valid_user_licenses_cache(); + } + } + + $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; + + if ( $this->has_paid_plan() ) { + switch ( $plan_change ) { + case 'none': + if ( ! $background && is_admin() ) { + $plan = $this->is_trial() ? + $this->get_trial_plan() : + $this->get_plan(); + + if ( $plan->is_free() ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.', 'plan-did-not-change-message' ), + '' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text_x_inline( 'Trial', 'trial period', 'trial' ) : '' ) . '' + ) . ' ' . sprintf( + '%s', + $this->contact_url( + 'bug', + sprintf( $this->get_text_inline( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.', 'plan-did-not-change-email-message' ), + strtoupper( $plan->name ) + ) + ), + $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) + ), + $hmm_text + ); + } + } + break; + case 'upgraded': + case 'activated': + $this->_admin_notices->add_sticky( + ( 'activated' === $plan_change ) ? + $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ) : + $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) . + $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'trial_expired', + 'activation_complete', + 'license_expired', + ) ); + break; + case 'changed': + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your plan was successfully changed to %s.', 'plan-changed-to-x-message' ), + $this->get_plan_title() + ), + 'plan_changed' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'trial_expired', + 'activation_complete', + ) ); + break; + case 'downgraded': + $this->_admin_notices->add_sticky( + ($this->has_free_plan() ? + sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using the free %s forever.', 'license-expired-blocking-message' ), $this->_module_type ) : + /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ + sprintf( $this->get_text_inline( 'Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'license-expired-blocking-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true) ) ), + 'license_expired', + $hmm_text + ); + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'cancelled': + $this->_admin_notices->add( + $this->get_text_inline( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.', 'license-cancelled' ) . ' ' . + sprintf( + '%s', + $this->contact_url( 'bug' ), + $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) + ), + $hmm_text, + 'error' + ); + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'expired': + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.', 'license-expired-non-blocking-message' ), $this->get_plan()->title ), + 'license_expired', + $hmm_text + ); + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'trial_started': + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), + '' . $this->get_plugin_name() . '' + ) . $this->get_complete_upgrade_instructions( $this->get_trial_plan()->title ), + 'trial_started', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + ) ); + break; + case 'trial_expired': + $this->_admin_notices->add_sticky( + ($this->has_free_plan() ? + $this->get_text_inline( 'Your free trial has expired. You can still continue using all our free features.', 'trial-expired-message' ) : + /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ + sprintf( $this->get_text_inline( 'Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'trial-expired-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true))), + 'trial_expired', + $hmm_text + ); + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'plan_upgraded', + ) ); + break; + } + } + + if ( 'none' !== $plan_change ) { + $this->do_action( 'after_license_change', $plan_change, $this->get_plan() ); + } + } + + /** + * Include the required JS at the footer of the admin to trigger the license activation dialog box. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + public function _open_license_activation_dialog_box() { + $vars = array( 'license_id' => $this->_site->license_id ); + fs_require_once_template( 'js/open-license-activation.php', $vars ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $background + */ + protected function _activate_license( $background = false, $premium_license = null ) { + $this->_logger->entrance(); + + if ( is_null( $premium_license ) ) { + $license_id = fs_request_get( 'license_id' ); + + if ( is_object( $this->_site ) && + FS_Plugin_License::is_valid_id( $license_id ) && + $license_id == $this->_site->license_id + ) { + // License is already activated. + return; + } + + $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? + $this->_get_license_by_id( $license_id ) : + $this->_get_available_premium_license(); + } + + if ( ! is_object( $premium_license ) ) { + return; + } + + if ( ! is_object( $this->_site ) ) { + // Not yet opted-in. + $user = $this->get_current_or_network_user(); + if ( ! is_object( $user ) ) { + $user = self::_get_user_by_id( $premium_license->user_id ); + } + + if ( is_object( $user ) ) { + $this->install_with_user( $user, $premium_license->secret_key, false, false, false ); + } else { + $this->opt_in( + false, + false, + false, + $premium_license->secret_key + ); + + return; + } + } + + + /** + * If the premium license is already associated with the install, just + * update the license reference (activation is not required). + * + * @since 1.1.9 + */ + if ( $premium_license->id == $this->_site->license_id ) { + // License is already activated. + $this->_update_site_license( $premium_license ); + $this->_store_account(); + + return; + } + + if ( $this->_site->user_id != $premium_license->user_id ) { + $api_request_params = array( 'license_key' => $premium_license->secret_key ); + } else { + $api_request_params = array(); + } + + $api = $this->get_api_site_scope(); + $license = $api->call( "/licenses/{$premium_license->id}.json?is_enriched=true", 'put', $api_request_params ); + + if ( ! $this->is_api_result_entity( $license ) ) { + if ( ! $background ) { + $this->_admin_notices->add( sprintf( + '%s %s', + $this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ), + ( is_object( $license ) && isset( $license->error ) ? + $license->error->message : + sprintf( '%s
    %s', + $this->get_text_inline( 'Error received from the server:', 'server-error-message' ), + var_export( $license, true ) + ) + ) + ), + $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...', + 'error' + ); + } + + return; + } + + $premium_license = new FS_Plugin_License( $license ); + + // Updated site plan. + $site = $this->get_api_site_scope()->get( '/', true ); + if ( $this->is_api_result_entity( $site ) ) { + $this->_site = new FS_Site( $site ); + } + $this->_update_site_license( $premium_license ); + + $this->_store_account(); + + if ( $this->is_addon() || $this->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $this->purge_valid_user_licenses_cache(); + } + + if ( ! $background ) { + $this->_admin_notices->add_sticky( + $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) . + $this->get_complete_upgrade_instructions(), + 'license_activated', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + 'license_expired', + ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $show_notice + */ + protected function _deactivate_license( $show_notice = true ) { + $this->_logger->entrance(); + + $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; + + if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'It looks like your site currently doesn\'t have an active license.', 'no-active-license-message' ), $this->get_plan_title() ), + $hmm_text + ); + + return; + } + + $api = $this->get_api_site_scope(); + $license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' ); + + $this->handle_license_deactivation_result( $license, $hmm_text, $show_notice ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param FS_Plugin_License $license + * @param bool|string $hmm_text + * @param bool $show_notice + */ + private function handle_license_deactivation_result( $license, $hmm_text = false, $show_notice = true ) { + if ( isset( $license->error ) ) { + $this->_admin_notices->add( + $this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '
    ' . + $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ), + $hmm_text, + 'error' + ); + + return; + } + + // Update license cache. + if ( is_array( $this->_licenses ) ) { + for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { + if ( $license->id == $this->_licenses[ $i ]->id ) { + $this->_licenses[ $i ] = new FS_Plugin_License( $license ); + } + } + } + + // Update site plan to default. + $this->_sync_plans(); + $this->_site->plan_id = $this->_plans[0]->id; + // Unlink license from site. + $this->_update_site_license( null ); + + $this->_store_account(); + + if ( $show_notice ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Your license was successfully deactivated, you are back to the %s plan.', 'license-deactivation-message' ), $this->get_plan_title() ), + $this->get_text_inline( 'O.K', 'ok' ) + ); + } + + $this->_admin_notices->remove_sticky( array( + 'plan_upgraded', + 'license_activated', + ) ); + } + + /** + * Site plan downgrade. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return object + * + * @uses FS_Api + */ + private function _downgrade_site() { + $this->_logger->entrance(); + + $deactivate_license = fs_request_get_bool( 'deactivate_license' ); + + $api = $this->get_api_site_scope(); + $site = $api->call( 'downgrade.json', 'put', array( 'deactivate_license' => $deactivate_license ) ); + + $plan_downgraded = false; + $plan = false; + if ( $this->is_api_result_entity( $site ) ) { + $prev_plan_id = $this->_site->plan_id; + + // Update new site plan id. + $this->_site->plan_id = $site->plan_id; + + $plan = $this->get_plan(); + $subscription = $this->_sync_site_subscription( $this->_license ); + + // Plan downgraded if plan was changed or subscription was cancelled. + $plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) || + ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() ); + } else { + // handle different error cases. + $this->handle_license_deactivation_result( + $site, + $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...' + ); + } + + if ( ! $plan_downgraded ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.', 'subscription-cancellation-failure-message' ) + ) + ); + } + + // Remove previous sticky message about upgrade (if exist). + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Your subscription was successfully cancelled. Your %s plan license will expire in %s.', 'plan-x-downgraded-message' ), + $plan->title, + human_time_diff( time(), strtotime( $this->_license->expiration ) ) + ) + ); + + // Store site updates. + $this->_store_site(); + + if ( $deactivate_license && + ! FS_Plugin_License::is_valid_id( $site->license_id ) + ) { + if ( $this->_site->is_localhost() ) { + $this->_license->activated_local = max( 0, $this->_license->activated_local - 1 ); + } else { + $this->_license->activated = max( 0, $this->_license->activated - 1 ); + } + + // Handle successful license deactivation result. + $this->handle_license_deactivation_result( $this->_license ); + } + + return $site; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @param bool|string $plan_name + * + * @return bool If trial was successfully started. + */ + function start_trial( $plan_name = false ) { + $this->_logger->entrance(); + + // Alias. + $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; + + if ( $this->is_trial() ) { + // Already in trial mode. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'You are already running the %s in a trial mode.', 'in-trial-mode' ), $this->_module_type ), + $oops_text, + 'error' + ); + + return false; + } + + if ( $this->_site->is_trial_utilized() ) { + // Trial was already utilized. + $this->_admin_notices->add( + $this->get_text_inline( 'You already utilized a trial before.', 'trial-utilized' ), + $oops_text, + 'error' + ); + + return false; + } + + if ( false !== $plan_name ) { + $plan = $this->get_plan_by_name( $plan_name ); + + if ( false === $plan ) { + // Plan doesn't exist. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ), + $oops_text, + 'error' + ); + + return false; + } + + if ( ! $plan->has_trial() ) { + // Plan doesn't exist. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ), + $oops_text, + 'error' + ); + + return false; + } + } else { + if ( ! $this->has_trial_plan() ) { + // None of the plans have a trial. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ), + $oops_text, + 'error' + ); + + return false; + } + + $plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); + + $plan = $plans_with_trial[0]; + } + + $api = $this->get_api_site_scope(); + $plan = $api->call( "plans/{$plan->id}/trials.json", 'post' ); + + if ( ! $this->is_api_result_entity( $plan ) ) { + // Some API error while trying to start the trial. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) + . ' ' . var_export( $plan, true ), + $oops_text, + 'error' + ); + + return false; + } + + // Sync license. + $this->_sync_license(); + + return $this->is_trial(); + } + + /** + * Cancel site trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return object + * + * @uses FS_Api + */ + private function _cancel_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' ) + ) + ); + } + + $trial_plan = $this->get_trial_plan(); + + $api = $this->get_api_site_scope(); + $site = $api->call( 'trials.json', 'delete' ); + + $trial_cancelled = false; + + if ( $this->is_api_result_entity( $site ) ) { + $prev_trial_ends = $this->_site->trial_ends; + + if ( $this->is_paid_trial() ) { + $this->_license->expiration = $site->trial_ends; + $this->_license->is_cancelled = true; + $this->_update_site_license( $this->_license ); + $this->_store_licenses(); + + // Clear subscription reference. + $this->_sync_site_subscription( null ); + } + + // Update site info. + $this->_site = new FS_Site( $site ); + + $trial_cancelled = ( $prev_trial_ends != $site->trial_ends ); + } else { + // @todo handle different error cases. + } + + if ( ! $trial_cancelled ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' ) + ) + ); + } + + // Remove previous sticky messages about upgrade or trial (if exist). + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'plan_upgraded', + ) ); + + // Store site updates. + $this->_store_site(); + + if ( ! $this->is_addon() || + ! $this->deactivate_premium_only_addon_without_license( true ) + ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $trial_plan->title ) + ); + } + + return $site; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|number $plugin_id + * + * @return bool + */ + private function _is_addon_id( $plugin_id ) { + return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id ); + } + + /** + * Check if user eligible to download premium version updates. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + private function _can_download_premium() { + return $this->has_any_active_valid_license() || + ( $this->is_trial() && ! $this->get_trial_plan()->is_free() ); + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|number $addon_id + * @param string $type "json" or "zip" + * + * @return string + */ + private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) { + + $is_addon = $this->_is_addon_id( $addon_id ); + + $is_premium = null; + if ( ! $is_addon ) { + $is_premium = ( $this->is_premium() || $this->_can_download_premium() ); + } else if ( $this->is_addon_activated( $addon_id ) ) { + $fs_addon = self::get_instance_by_id( $addon_id ); + $is_premium = ( $fs_addon->is_premium() || $fs_addon->_can_download_premium() ); + } + + // If add-on, then append add-on ID. + $endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) . + '/updates/latest.' . $type; + + // If add-on and not yet activated, try to fetch based on server licensing. + if ( is_bool( $is_premium ) ) { + $endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint ); + } + + if ( $this->has_secret_key() ) { + $endpoint = add_query_arg( 'type', 'all', $endpoint ); + } else if ( $this->is_registered() && $this->_user->is_beta() ) { + $endpoint = add_query_arg( 'type', 'beta', $endpoint ); + } + + return $endpoint; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|number $addon_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * @param bool|string $fetch_readme Since 2.2.1 + * + * @return object|false Plugin latest tag info. + */ + function _fetch_latest_version( + $addon_id = false, + $flush = true, + $expiration = WP_FS__TIME_24_HOURS_IN_SEC, + $newer_than = false, + $fetch_readme = true + ) { + $this->_logger->entrance(); + + $switch_to_blog_id = null; + + /** + * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in. + * @since 1.1.7.4 Also check updates for add-ons. + */ + if ( ! $this->is_registered() && + ! $this->_is_addon_id( $addon_id ) + ) { + if ( ! is_multisite() ) { + return false; + } + + $installs_map = $this->get_blog_install_map(); + + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_trial() ) { + $switch_to_blog_id = $blog_id; + break; + } + + if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $license = $this->get_license_by_id( $install->license_id ); + + if ( is_object( $license ) && $license->is_features_enabled() ) { + $switch_to_blog_id = $blog_id; + break; + } + } + } + + if ( is_null( $switch_to_blog_id ) ) { + return false; + } + } + + $current_blog_id = is_numeric( $switch_to_blog_id ) ? + get_current_blog_id() : + 0; + + if ( is_numeric( $switch_to_blog_id ) ) { + $this->switch_to_blog( $switch_to_blog_id ); + } + + $latest_version_endpoint = $this->_get_latest_version_endpoint( $addon_id, 'json' ); + + if ( ! empty( $newer_than ) ) { + $latest_version_endpoint = add_query_arg( 'newer_than', $newer_than, $latest_version_endpoint ); + } + + if ( true === $fetch_readme ) { + $latest_version_endpoint = add_query_arg( 'readme', 'true', $latest_version_endpoint ); + } + + $tag = $this->get_api_site_or_plugin_scope()->get( + $latest_version_endpoint, + $flush, + $expiration + ); + + if ( is_numeric( $switch_to_blog_id ) ) { + $this->switch_to_blog( $current_blog_id ); + } + + $latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get'; + + $this->_logger->departure( 'Latest version ' . $latest_version ); + + return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false; + } + + #---------------------------------------------------------------------------------- + #region Download Plugin + #---------------------------------------------------------------------------------- + + /** + * Download latest plugin version, based on plan. + * + * Not like _download_latest(), this will redirect the page + * to secure download url to prevent dual download (from FS to WP server, + * and then from WP server to the client / browser). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @uses FS_Api + * @uses wp_redirect() + */ + private function download_latest_directly( $plugin_id = false ) { + $this->_logger->entrance(); + + wp_redirect( $this->get_latest_download_api_url( $plugin_id ) ); + } + + /** + * Get latest plugin FS API download URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @return string + */ + private function get_latest_download_api_url( $plugin_id = false ) { + $this->_logger->entrance(); + + return $this->get_api_site_scope()->get_signed_url( + $this->_get_latest_version_endpoint( $plugin_id, 'zip' ) + ); + } + + /** + * Get payment invoice URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param bool|number $payment_id + * + * @return string + */ + function _get_invoice_api_url( $payment_id = false ) { + $this->_logger->entrance(); + + $url = $this->get_api_user_scope()->get_signed_url( + "/payments/{$payment_id}/invoice.pdf" + ); + + if ( ! fs_starts_with( $url, 'https://' ) ) { + // Always use HTTPS for invoices. + $url = 'https' . substr( $url, 4 ); + } + + return $url; + } + + /** + * Get latest plugin download link. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $label + * @param bool|number $plugin_id + * + * @return string + */ + private function get_latest_download_link( $label, $plugin_id = false ) { + return sprintf( + '%s', + $this->_get_latest_download_local_url( $plugin_id ), + $label + ); + } + + /** + * Get latest plugin download local URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @return string + */ + function _get_latest_download_local_url( $plugin_id = false ) { + // Add timestamp to protect from caching. + $params = array( 'ts' => WP_FS__SCRIPT_START_TIME ); + + if ( ! empty( $plugin_id ) ) { + $params['plugin_id'] = $plugin_id; + } else if ( $this->is_addon() ) { + $params['plugin_id'] = $this->get_id(); + } + + $fs = $this->is_addon() ? + $this->get_parent_instance() : + $this; + + return $this->apply_filters( 'download_latest_url', $fs->get_account_url( 'download_latest', $params ) ); + } + + #endregion Download Plugin ------------------------------------------------------------------ + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background updates check. If false, it means that + * was initiated by the admin. + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + */ + private function check_updates( + $background = false, + $plugin_id = false, + $flush = true, + $expiration = WP_FS__TIME_24_HOURS_IN_SEC, + $newer_than = false + ) { + $this->_logger->entrance(); + + // Check if there's a newer version for download. + $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than ); + + $update = null; + if ( is_object( $new_version ) ) { + $update = new FS_Plugin_Tag( $new_version ); + + if ( ! $background ) { + $this->_admin_notices->add( + sprintf( + /* translators: %s: Numeric version number (e.g. '2.1.9' */ + $this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ), + $update->version, + sprintf( + '%s', + $this->get_account_url( 'download_latest' ), + sprintf( + /* translators: %s: plan name (e.g. latest "Professional" version) */ + $this->get_text_inline( 'the latest %s version here', 'latest-x-version' ), + $this->get_plan_title() + ) + ) + ), + $this->get_text_inline( 'New', 'new' ) . '!' + ); + } + } else if ( false === $new_version && ! $background ) { + $this->_admin_notices->add( + $this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ), + $this->get_text_inline( 'You are all good!', 'you-are-good' ) + ); + } + + $this->_store_update( $update, true, $plugin_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool $flush Since 1.1.7.3 add 24 hour cache by default. + * + * @return FS_Plugin[] + * + * @uses FS_Api + */ + private function sync_addons( $flush = false ) { + $this->_logger->entrance(); + + $api = $this->get_api_site_or_plugin_scope(); + + $path = $this->add_show_pending( '/addons.json?enriched=true' ); + + /** + * @since 1.2.1 + * + * If there's a cached version of the add-ons and not asking + * for a flush, just use the currently stored add-ons. + */ + if ( ! $flush && $api->is_cached( $path ) ) { + $addons = self::get_all_addons(); + + return isset( $addons[ $this->_plugin->id ] ) ? + $addons[ $this->_plugin->id ] : + array(); + } + + $result = $api->get( $path, $flush ); + + $addons = array(); + if ( $this->is_api_result_object( $result, 'plugins' ) && + is_array( $result->plugins ) + ) { + for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) { + $addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] ); + } + + $this->_store_addons( $addons, true ); + } + + return $addons; + } + + /** + * Handle user email update. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + * + * @param string $new_email + * + * @return object + */ + private function update_email( $new_email ) { + $this->_logger->entrance(); + + + $api = $this->get_api_user_scope(); + $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array( + 'email' => $new_email, + 'after_email_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ), + ) ); + + if ( ! isset( $user->error ) ) { + $this->_user->email = $user->email; + $this->_user->is_verified = $user->is_verified; + $this->_store_user(); + } else { + // handle different error cases. + + } + + return $user; + } + + #---------------------------------------------------------------------------------- + #region API Error Handling + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + private function is_api_error( $result ) { + return FS_Api::is_api_error( $result ); + } + + /** + * Checks if given API result is a non-empty and not an error object. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * @param string|null $required_property Optional property we want to verify that is set. + * + * @return bool + */ + function is_api_result_object( $result, $required_property = null ) { + return FS_Api::is_api_result_object( $result, $required_property ); + } + + /** + * Checks if given API result is a non-empty entity object with non-empty ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool + */ + private function is_api_result_entity( $result ) { + return FS_Api::is_api_result_entity( $result ); + } + + #endregion + + /** + * Make sure a given argument is an array of a specific type. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $array + * @param string $class + * + * @return bool + */ + private function is_array_instanceof( $array, $class ) { + return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) ); + } + + /** + * Start install ownership change. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @uses FS_Api + * + * @param string $candidate_email + * + * @return bool Is ownership change successfully initiated. + */ + private function init_change_owner( $candidate_email ) { + $this->_logger->entrance(); + + $api = $this->get_api_site_scope(); + $result = $api->call( "/users/{$this->_user->id}.json", 'put', array( + 'email' => $candidate_email, + 'after_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'change_owner' ) + ), + ) ); + + return ! $this->is_api_error( $result ); + } + + /** + * Handle install ownership change. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @uses FS_Api + * + * @return bool Was ownership change successfully complete. + */ + private function complete_change_owner() { + $this->_logger->entrance(); + + $site_result = $this->get_api_site_scope( true )->get(); + $site = new FS_Site( $site_result ); + $this->_site = $site; + + $user = new FS_User(); + $user->id = fs_request_get( 'user_id' ); + + // Validate install's user and given user. + if ( $user->id != $this->_site->user_id ) { + return false; + } + + $user->public_key = fs_request_get( 'user_public_key' ); + $user->secret_key = fs_request_get( 'user_secret_key' ); + + // Fetch new user information. + $this->_user = $user; + $user_result = $this->get_api_user_scope( true )->get(); + $user = new FS_User( $user_result ); + $this->_user = $user; + + $this->_set_account( $user, $site ); + + return true; + } + + /** + * Handle user name update. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * @uses FS_Api + * + * @return object + */ + private function update_user_name() { + $this->_logger->entrance(); + $name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' ); + + $api = $this->get_api_user_scope(); + $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array( + 'name' => $name, + ) ); + + if ( ! isset( $user->error ) ) { + $this->_user->first = $user->first; + $this->_user->last = $user->last; + $this->_store_user(); + } else { + // handle different error cases. + + } + + return $user; + } + + /** + * Verify user email. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + */ + private function verify_email() { + $this->_handle_account_user_sync(); + + if ( $this->_user->is_verified() ) { + return; + } + + $api = $this->get_api_site_scope(); + $result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array( + 'after_email_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ) + ) ); + + if ( ! isset( $result->error ) ) { + $this->_admin_notices->add( sprintf( + $this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ), + sprintf( '%2$s', esc_url( $this->_user->email ), $this->_user->email ) + ) ); + } else { + // handle different error cases. + + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @param array $params + * @param bool|null $network + * + * @return string + */ + function get_activation_url( $params = array(), $network = null ) { + if ( $this->is_addon() && $this->has_free_plan() ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 Add-on's activation is the parent's module activation. + */ + return $this->get_parent_instance()->get_activation_url( $params ); + } + + return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params, $network ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param array $params + * + * @return string + */ + function get_reconnect_url( $params = array() ) { + $params['fs_action'] = 'reset_anonymous_mode'; + $params['fs_unique_affix'] = $this->get_unique_affix(); + + return $this->get_activation_url( $params ); + } + + /** + * Get the URL of the page that should be loaded after the user connect + * or skip in the opt-in screen. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $filter Filter name. + * @param array $params Since 1.2.2.7 + * @param bool|null $network + * + * @return string + */ + function get_after_activation_url( $filter, $params = array(), $network = null ) { + if ( $this->is_free_wp_org_theme() && + ( fs_request_has( 'pending_activation' ) || + // For cases when the first time path is set, even though it's a WP.org theme. + fs_request_get_bool( $this->get_unique_affix() . '_show_optin' ) ) + ) { + $first_time_path = ''; + } else { + $first_time_path = $this->_menu->get_first_time_path( + fs_is_network_admin() && $this->_is_network_active + ); + } + + if ( $this->_is_network_active && + fs_is_network_admin() && + ! $this->_menu->has_network_menu() && + $this->is_network_registered() + ) { + $target_url = $this->get_account_url(); + } else { + // Default plugin's page. + $target_url = $this->_get_admin_page_url( '', array(), $network ); + } + + return add_query_arg( $params, $this->apply_filters( + $filter, + empty( $first_time_path ) ? + $target_url : + $first_time_path + ) ); + } + + /** + * Handle account page updates / edits / actions. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + */ + private function _handle_account_edits() { + if ( ! $this->is_user_admin() ) { + return; + } + + $action = fs_get_action(); + + if ( empty( $action ) ) { + return; + } + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + $install_id = fs_request_get( 'install_id', '' ); + + // Alias. + $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; + + $is_network_action = $this->is_network_level_action(); + $blog_id = $this->is_network_level_site_specific_action(); + + if ( is_numeric( $blog_id ) ) { + $this->switch_to_blog( $blog_id ); + } else { + $blog_id = ''; + } + + switch ( $action ) { + case 'opt_in': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $plugin_id == $this->get_id() ) { + if ( $is_network_action && ! empty( $blog_id ) ) { + if ( ! $this->is_registered() ) { + $this->install_with_user( + $this->get_network_user(), + false, + false, + false, + false + ); + + $this->_admin_notices->add( + $this->get_text_inline( 'Site successfully opted in.', 'successful-opt-in' ), + $this->get_text_inline( 'Awesome', 'awesome' ) + ); + } + } + } + break; + + case 'toggle_tracking': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $plugin_id == $this->get_id() ) { + if ( $is_network_action && ! empty( $blog_id ) ) { + if ( $this->is_registered() ) { + if ( $this->is_tracking_prohibited() ) { + if ( $this->allow_site_tracking() ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation' ), $this->_module_type ), + $this->get_text_inline( 'Thank you!', 'thank-you' ) + ); + } + } else { + if ( $this->stop_site_tracking() ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'We will no longer be sending any usage data of %s on %s to %s.', 'opted-out-successfully' ), + $this->get_plugin_title(), + fs_strip_url_protocol( get_site_url( $blog_id ) ), + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ) + ); + } + } + } + } + } + + break; + + case 'delete_account': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $plugin_id == $this->get_id() ) { + if ( $is_network_action && empty( $blog_id ) ) { + $this->delete_network_account_event(); + } else { + $this->delete_account_event(); + } + + // Clear user and site. + $this->_site = null; + $this->_user = null; + + $this->maybe_set_slug_and_network_menu_exists_flag(); + + fs_redirect( $this->get_activation_url() ); + } else { + if ( $this->is_addon_activated( $plugin_id ) ) { + $fs_addon = self::get_instance_by_id( $plugin_id ); + $fs_addon->delete_account_event(); + + fs_redirect( $this->_get_admin_page_url( 'account' ) ); + } + } + + return; + + case 'downgrade_account': + if ( is_numeric( $blog_id ) ) { + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + } else { + check_admin_referer( $action ); + } + + $switch_to_network_install_blog_after_cancellation = ( + is_numeric( $blog_id ) && + $plugin_id == $this->get_id() && + ! $this->is_trial() + ); + + $result = $this->cancel_subscription_or_trial( $plugin_id ); + if ( $this->is_api_error( $result ) ) { + $this->_admin_notices->add( + $result->error->message, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + if ( $switch_to_network_install_blog_after_cancellation ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + return; + + case 'activate_license': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + $fs = $this; + if ( $plugin_id != $this->get_id() ) { + $fs = $this->is_addon_activated( $plugin_id ) ? + self::get_instance_by_id( $plugin_id ) : + null; + } + + if ( is_object( $fs ) ) { + $fs->_activate_license(); + } + + return; + + case 'deactivate_license': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $plugin_id == $this->get_id() ) { + $this->_deactivate_license(); + + if ( $this->is_only_premium() ) { + // Clear user and site. + $this->_site = null; + $this->_user = null; + + if ( ! $is_network_action ) { + fs_redirect( $this->get_activation_url() ); + } else if ( is_numeric( $blog_id ) ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + } + } else { + if ( $this->is_addon_activated( $plugin_id ) ) { + $fs_addon = self::get_instance_by_id( $plugin_id ); + $fs_addon->_deactivate_license(); + } + } + + return; + + case 'check_updates': + check_admin_referer( $action ); + $this->check_updates(); + + return; + + case 'change_owner': + $state = fs_request_get( 'state', 'init' ); + switch ( $state ) { + case 'init': + $candidate_email = fs_request_get( 'candidate_email', '' ); + + if ( $this->init_change_owner( $candidate_email ) ) { + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '' . $this->_user->email . '' ) ); + } + break; + case 'owner_confirmed': + $candidate_email = fs_request_get( 'candidate_email', '' ); + + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '' . $candidate_email . '' ) ); + break; + case 'candidate_confirmed': + if ( $this->complete_change_owner() ) { + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '' . $this->_user->email . '' ), + 'ownership_changed', + $this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!' + ); + } else { + // @todo Handle failed ownership change message. + } + break; + } + + return; + + case 'update_email': + check_admin_referer( 'update_email' ); + + $new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' ); + $result = $this->update_email( $new_email ); + + if ( isset( $result->error ) ) { + switch ( $result->error->code ) { + case 'user_exist': + $this->_admin_notices->add( + $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . + sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email . '' ) . + sprintf( + '', + $this->get_account_url( 'change_owner', array( + 'state' => 'init', + 'candidate_email' => $new_email + ) ), + $this->get_text_inline( 'Change Ownership', 'change-ownership' ) + ), + $oops_text, + 'error' + ); + break; + } + } else { + $this->_admin_notices->add( $this->get_text_inline( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.', 'email-updated-message' ) ); + } + + return; + + case 'update_user_name': + check_admin_referer( 'update_user_name' ); + + $result = $this->update_user_name(); + + if ( isset( $result->error ) ) { + $this->_admin_notices->add( + $this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ), + $oops_text, + 'error' + ); + } else { + $this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) ); + } + + return; + + #region Actions that might be called from external links (e.g. email) + + case 'cancel_trial': + $result = $this->cancel_subscription_or_trial( $plugin_id ); + if ( $this->is_api_error( $result ) ) { + $this->_admin_notices->add( + $result->error->message, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + return; + + case 'verify_email': + $this->verify_email(); + + return; + + case 'sync_user': + $this->_handle_account_user_sync(); + + return; + + case $this->get_unique_affix() . '_sync_license': + $this->_sync_license(); + + return; + + case 'download_latest': + $this->download_latest_directly( $plugin_id ); + + return; + + #endregion + } + + if ( WP_FS__IS_POST_REQUEST ) { + $properties = array( 'site_secret_key', 'site_id', 'site_public_key' ); + foreach ( $properties as $p ) { + if ( 'update_' . $p === $action ) { + check_admin_referer( $action ); + + $this->_logger->log( $action ); + + $site_property = substr( $p, strlen( 'site_' ) ); + $site_property_value = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' ); + $this->get_site()->{$site_property} = $site_property_value; + + // Store account after modification. + $this->_store_site(); + + $this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value ); + + $this->_admin_notices->add( sprintf( + /* translators: %s: User's account property (e.g. email address, name) */ + $this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ), + '' . str_replace( '_', ' ', $p ) . '' + ) ); + + return; + } + } + } + } + + /** + * Account page resources load. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _account_page_load() { + $this->_logger->entrance(); + + $this->_logger->info( var_export( $_REQUEST, true ) ); + + fs_enqueue_local_style( 'fs_account', '/admin/account.css' ); + + if ( $this->has_addons() ) { + wp_enqueue_script( 'plugin-install' ); + add_thickbox(); + + function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + + return $classes; + } + + add_filter( 'admin_body_class', 'fs_addons_body_class' ); + } + + if ( $this->has_paid_plan() && + ! $this->has_any_license() && + ! $this->is_sync_executed() && + $this->is_tracking_allowed() + ) { + /** + * If no licenses found and no sync job was executed during the last 24 hours, + * just execute the sync job right away (blocking execution). + * + * @since 1.1.7.3 + */ + $this->run_manual_sync(); + } + + $this->_handle_account_edits(); + + $this->do_action( 'account_page_load_before_departure' ); + } + + /** + * Renders the "Affiliation" page. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + function _affiliation_page_render() { + $this->_logger->entrance(); + + $this->fetch_affiliate_and_terms(); + + fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' ); + + $vars = array( 'id' => $this->_module_id ); + echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) ); + } + + + /** + * Render account page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _account_page_render() { + $this->_logger->entrance(); + + $template = 'account.php'; + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) ); + } + + /** + * Render account connect page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _connect_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) ); + } + + /** + * Load required resources before add-ons page render. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _addons_page_load() { + $this->_logger->entrance(); + + fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); + + wp_enqueue_script( 'plugin-install' ); + add_thickbox(); + + function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + + return $classes; + } + + add_filter( 'admin_body_class', 'fs_addons_body_class' ); + + if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '' . $this->get_plugin_name() . '' ), + $this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ), + 'update-nag' + ); + } + } + + /** + * Render add-ons page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _addons_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) ); + } + + /* Pricing & Upgrade + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Render pricing page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _pricing_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + if ( 'true' === fs_request_get( 'checkout', false ) ) { + echo $this->apply_filters( 'templates/checkout.php', fs_get_template( 'checkout.php', $vars ) ); + } else { + echo $this->apply_filters( 'templates/pricing.php', fs_get_template( 'pricing.php', $vars ) ); + } + } + + #---------------------------------------------------------------------------------- + #region Contact Us + #---------------------------------------------------------------------------------- + + /** + * Render contact-us page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function _contact_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + */ + echo $this->apply_filters( 'templates/contact.php', fs_get_template( 'contact.php', $vars ) ); + } + + #endregion ------------------------------------------------------------------------ + + /** + * Hide all admin notices to prevent distractions. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @uses remove_all_actions() + */ + private static function _hide_admin_notices() { + remove_all_actions( 'admin_notices' ); + remove_all_actions( 'network_admin_notices' ); + remove_all_actions( 'all_admin_notices' ); + remove_all_actions( 'user_admin_notices' ); + } + + static function _clean_admin_content_section_hook() { + self::_hide_admin_notices(); + + // Hide footer. + echo ''; + } + + /** + * Attach to admin_head hook to hide all admin notices. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + static function _clean_admin_content_section() { + add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' ); + } + + /* CSS & JavaScript + ------------------------------------------------------------------------------------------------------------------*/ + /* function _enqueue_script($handle, $src) { + $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); + + $this->_logger->entrance( 'script = ' . $url ); + + wp_enqueue_script( $handle, $url ); + }*/ + + /* SDK + ------------------------------------------------------------------------------------------------------------------*/ + private $_user_api; + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_api_user_scope( $flush = false ) { + if ( ! isset( $this->_user_api ) || $flush ) { + $this->_user_api = $this->get_api_user_scope_by_user( $this->_user ); + } + + return $this->_user_api; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * + * @return \FS_Api + */ + private function get_api_user_scope_by_user( FS_User $user ) { + return FS_Api::instance( + $this->_module_id, + 'user', + $user->id, + $user->public_key, + ! $this->is_live(), + $user->secret_key, + $this->get_sdk_version() + ); + } + + /** + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_current_or_network_user_api_scope( $flush = false ) { + if ( ! $this->_is_network_active || + ( isset( $this->_user ) && $this->_user instanceof FS_User ) + ) { + return $this->get_api_user_scope( $flush ); + } + + $user = $this->get_current_or_network_user(); + + $this->_user_api = FS_Api::instance( + $this->_module_id, + 'user', + $user->id, + $user->public_key, + ! $this->is_live(), + $user->secret_key, + $this->get_sdk_version() + ); + + return $this->_user_api; + } + + private $_site_api; + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_api_site_scope( $flush = false ) { + if ( ! isset( $this->_site_api ) || $flush ) { + $this->_site_api = FS_Api::instance( + $this->_module_id, + 'install', + $this->_site->id, + $this->_site->public_key, + ! $this->is_live(), + $this->_site->secret_key, + $this->get_sdk_version() + ); + } + + return $this->_site_api; + } + + private $_plugin_api; + + /** + * Get plugin public API scope. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return FS_Api + */ + function get_api_plugin_scope() { + if ( ! isset( $this->_plugin_api ) ) { + $this->_plugin_api = FS_Api::instance( + $this->_module_id, + 'plugin', + $this->_plugin->id, + $this->_plugin->public_key, + ! $this->is_live(), + false, + $this->get_sdk_version() + ); + } + + return $this->_plugin_api; + } + + /** + * Get site API scope object (fallback to public plugin scope when not registered). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return FS_Api + */ + function get_api_site_or_plugin_scope() { + return $this->is_registered() ? + $this->get_api_site_scope() : + $this->get_api_plugin_scope(); + } + + /** + * Show trial promotional notice (if any trial exist). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + */ + function _check_for_trial_plans( $plans ) { + /** + * For some reason core's do_action() flattens arrays when it has a single object item. Therefore, we need to restructure the array as expected. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.2 + */ + if ( ! is_array( $plans ) && is_object( $plans ) ) { + $plans = array( $plans ); + } + + if ( ! $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { + $plans = array(); + } + + $this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans ); + } + + /** + * During trial promotion the "upgrade" submenu item turns to + * "start trial" to encourage the trial. Since we want to keep + * the same menu item handler and there's no robust way to + * add new arguments to the menu item link's querystring, + * use JavaScript to find the menu item and update the href of + * the link. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _fix_start_trial_menu_item_url() { + $template_args = array( 'id' => $this->_module_id ); + fs_require_template( 'add-trial-to-pricing.php', $template_args ); + } + + /** + * Check if module is currently in a trial promotion mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_in_trial_promotion() { + return $this->_admin_notices->has_sticky( 'trial_promotion' ); + } + + /** + * Show trial promotional notice (if any trial exist). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool If trial notice added. + */ + function _add_trial_notice() { + if ( ! $this->is_user_admin() ) { + return false; + } + + if ( ! $this->is_user_in_admin() ) { + return false; + } + + if ( $this->_is_network_active ) { + if ( fs_is_network_admin() ) { + // Network level trial is disabled at the moment. + return false; + } + + if ( ! $this->is_delegated_connection() ) { + // Only delegated sites should support trials. + return false; + } + } + + // Check if trial message is already shown. + if ( $this->is_in_trial_promotion() ) { + add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) ); + + $this->_menu->add_counter_to_menu_item( 1, 'fs-trial' ); + + return false; + } + + if ( $this->is_premium() && ! WP_FS__DEV_MODE ) { + // Don't show trial if running the premium code, unless running in DEV mode. + return false; + } + + if ( ! $this->has_trial_plan() ) { + // No plans with trial. + return false; + } + + if ( ! $this->apply_filters( 'show_trial', true ) ) { + // Developer explicitly asked not to show the trial promo. + return false; + } + + if ( $this->is_registered() ) { + // Check if trial already utilized. + if ( $this->_site->is_trial_utilized() ) { + return false; + } + + if ( $this->is_paying_or_trial() ) { + // Don't show trial if paying or already in trial. + return false; + } + } + + if ( $this->is_activation_mode() || $this->is_pending_activation() ) { + // If not yet opted-in/skipped, or pending activation, don't show trial. + return false; + } + + $last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false ); + $was_promotion_shown_before = ( false !== $last_time_trial_promotion_shown ); + + // Show promotion if never shown before and 24 hours after initial activation with FS. + if ( ! $was_promotion_shown_before && + $this->_storage->install_timestamp > ( time() - $this->apply_filters( 'show_first_trial_after_n_sec', WP_FS__TIME_24_HOURS_IN_SEC ) ) + ) { + return false; + } + + // OR if promotion was shown before, try showing it every 30 days. + if ( $was_promotion_shown_before && + $this->apply_filters( 'reshow_trial_after_every_n_sec', 30 * WP_FS__TIME_24_HOURS_IN_SEC ) > time() - $last_time_trial_promotion_shown + ) { + return false; + } + + $trial_period = $this->_trial_days; + $require_payment = $this->_is_trial_require_payment; + $trial_url = $this->get_trial_url(); + $plans_string = strtolower( $this->get_text_inline( 'Awesome', 'awesome' ) ); + + if ( $this->is_registered() ) { + // If opted-in, override trial with up to date data from API. + $trial_plans = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); + $trial_plans_count = count( $trial_plans ); + + if ( 0 === $trial_plans_count ) { + // If there's no plans with a trial just exit. + return false; + } + + /** + * @var FS_Plugin_Plan $paid_plan + */ + $paid_plan = $trial_plans[0]; + $require_payment = $paid_plan->is_require_subscription; + $trial_period = $paid_plan->trial_period; + + $total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 ); + + if ( $total_paid_plans !== $trial_plans_count ) { + // Not all paid plans have a trial - generate a string of those that have it. + for ( $i = 0; $i < $trial_plans_count; $i ++ ) { + $plans_string .= sprintf( + ' %s', + $trial_url, + $trial_plans[ $i ]->title + ); + + if ( $i < $trial_plans_count - 2 ) { + $plans_string .= ', '; + } else if ( $i == $trial_plans_count - 2 ) { + $plans_string .= ' and '; + } + } + } + } + + $message = sprintf( + $this->get_text_x_inline( 'Hey', 'exclamation', 'hey' ) . '! ' . $this->get_text_inline( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.', 'trial-x-promotion-message' ), + sprintf( '%s', $this->get_plugin_name() ), + $plans_string, + $trial_period + ); + + // "No Credit-Card Required" or "No Commitment for N Days". + $cc_string = $require_payment ? + sprintf( $this->get_text_inline( 'No commitment for %s days - cancel anytime!', 'no-commitment-for-x-days' ), $trial_period ) : + $this->get_text_inline( 'No credit card required', 'no-cc-required' ) . '!'; + + + // Start trial button. + $button = ' ' . sprintf( + '', + $trial_url, + $this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' ) + ); + + $this->_admin_notices->add_sticky( + $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ), + 'trial_promotion', + '', + 'promotion' + ); + + $this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME; + + return true; + } + + /** + * Lets users/customers know that the product has an affiliate program. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2.11 + * + * @return bool Returns true if the notice has been added. + */ + function _add_affiliate_program_notice() { + if ( ! $this->is_user_admin() ) { + return false; + } + + if ( ! $this->is_user_in_admin() ) { + return false; + } + + // Check if the notice is already shown. + if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { + return false; + } + + if ( + // Product has no affiliate program. + ! $this->has_affiliate_program() || + // User has applied for an affiliate account. + ! empty( $this->_storage->affiliate_application_data ) + ) { + return false; + } + + if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) { + // Developer explicitly asked not to show the notice about the affiliate program. + return false; + } + + if ( $this->is_activation_mode() || $this->is_pending_activation() ) { + // If not yet opted in/skipped, or pending activation, don't show the notice. + return false; + } + + $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false ); + $was_notice_shown_before = ( false !== $last_time_notice_was_shown ); + + /** + * Do not show the notice if it was already shown before or less than 30 days have passed since the initial + * activation with FS. + */ + if ( $was_notice_shown_before || + $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) ) + ) { + return false; + } + + if ( ! $this->is_paying() && + FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation + ) { + // If the user is not a customer and the affiliate program is only for customers, don't show the notice. + return false; + } + + $message = sprintf( + $this->get_text_inline( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!', 'become-an-ambassador-admin-notice' ), + sprintf( '%s', $this->get_plugin_name() ), + $this->get_module_label( true ) + ); + + // HTML code for the "Learn more..." button. + $button = ' ' . sprintf( + '', + $this->_get_admin_page_url( 'affiliation' ), + $this->get_text_inline( 'Learn more', 'learn-more' ) . '...' + ); + + $this->_admin_notices->add_sticky( + $this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ), + 'affiliate_program', + '', + 'promotion' + ); + + $this->_storage->affiliate_program_notice_shown = WP_FS__SCRIPT_START_TIME; + + return true; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _enqueue_common_css() { + if ( $this->has_paid_plan() && ! $this->is_paying() ) { + // Add basic CSS for admin-notices and menu-item colors. + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + function _show_theme_activation_optin_dialog() { + fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); + + add_action( 'admin_footer-themes.php', array( &$this, '_add_fs_theme_activation_dialog' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + function _add_fs_theme_activation_dialog() { + $vars = array( 'id' => $this->_module_id ); + fs_require_once_template( 'connect.php', $vars ); + } + + /* Action Links + ------------------------------------------------------------------------------------------------------------------*/ + private $_action_links_hooked = false; + private $_action_links = array(); + + /** + * Hook to plugin action links filter. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + private function hook_plugin_action_links() { + $this->_logger->entrance(); + + $this->_action_links_hooked = true; + + $this->_logger->log( 'Adding action links hooks.' ); + + // Add action link to settings page. + add_filter( 'plugin_action_links_' . $this->_plugin_basename, array( + &$this, + '_modify_plugin_action_links_hook' + ), WP_FS__DEFAULT_PRIORITY, 2 ); + add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array( + &$this, + '_modify_plugin_action_links_hook' + ), WP_FS__DEFAULT_PRIORITY, 2 ); + } + + /** + * Add plugin action link. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param $label + * @param $url + * @param bool $external + * @param int $priority + * @param bool $key + */ + function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) { + $this->_logger->entrance(); + + if ( ! isset( $this->_action_links[ $priority ] ) ) { + $this->_action_links[ $priority ] = array(); + } + + if ( false === $key ) { + $key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) ); + } + + $this->_action_links[ $priority ][] = array( + 'label' => $label, + 'href' => $url, + 'key' => $key, + 'external' => $external + ); + } + + /** + * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _add_upgrade_action_link() { + $this->_logger->entrance(); + + $is_activation_mode = $this->is_activation_mode(); + + $add_action_links = $this->should_add_submenu_or_action_links( $is_activation_mode ); + + /** + * The following logic is based on the logic in `add_submenu_items()` method that decides when the "Upgrade" + * and "Add-Ons" menus should be added. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $add_upgrade_link = ( + $add_action_links || + ( $is_activation_mode && $this->is_only_premium() ) + ) && ! WP_FS__DEMO_MODE; + + $add_addons_link = ( $add_action_links && $this->has_addons() ); + + if ( ! $add_upgrade_link && ! $add_addons_link ) { + return; + } + + if ( + $add_upgrade_link && + $this->is_pricing_page_visible() && + $this->is_submenu_item_visible( 'pricing' ) + ) { + $this->add_plugin_action_link( + $this->get_text_inline( 'Upgrade', 'upgrade' ), + $this->get_upgrade_url(), + false, + 7, + 'upgrade' + ); + } + + if ( + $add_addons_link && + $this->has_addons() && + $this->is_submenu_item_visible( 'addons' ) + ) { + $this->add_plugin_action_link( + $this->get_text_inline( 'Add-Ons', 'add-ons' ), + $this->_get_admin_page_url( 'addons' ), + false, + 9, + 'addons' + ); + } + } + + /** + * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + */ + function _add_license_action_link() { + $this->_logger->entrance(); + + if ( ! self::is_ajax() ) { + // Inject license activation dialog UI and client side code. + add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) ); + } + + $link_text = $this->is_free_plan() ? + $this->get_text_inline( 'Activate License', 'activate-license' ) : + $this->get_text_inline( 'Change License', 'change-license' ); + + $this->add_plugin_action_link( + $link_text, + '#', + false, + 11, + ( 'activate-license ' . $this->get_unique_affix() ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection_action() { + $this->_logger->entrance(); + + if ( ! self::is_ajax() ) { + add_action( 'admin_footer', array( &$this, '_add_premium_version_upgrade_selection_dialog_box' ) ); + } + } + + /** + * Adds "Opt In" or "Opt Out" link to the main "Plugins" page link actions collection. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _add_tracking_links() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + $this->_logger->entrance(); + + if ( fs_is_network_admin() ) { + if ( ! $this->_is_network_active ) { + // Don't add tracking links when browsing the network WP Admin and the plugin is not network active. + return; + } else if ( $this->is_network_delegated_connection() ) { + // Don't add tracking links when browsing the network WP Admin and the activation has been delegated to site admins. + return; + } + } else { + if ( $this->_is_network_active && ! $this->is_delegated_connection() ) { + // Don't add tracking links when browsing the sub-site WP Admin, the plugin is network active, and the connection was not delegated. + return; + } + } + + if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) { + if ( ! $this->is_registered() && $this->is_anonymous() ) { + $this->connect_again(); + + return; + } + } + + if ( ( $this->is_plugin() && ! self::is_plugins_page() ) || + ( $this->is_theme() && ! self::is_themes_page() ) + ) { + // Only show tracking links on the plugins and themes pages. + return; + } + + if ( $this->is_registered() && $this->is_tracking_allowed() ) { + if ( ! $this->is_enable_anonymous() ) { + // If opted in and tracking is allowed, don't allow to opt out if anonymous mode is disabled. + return; + } + + if ( ! $this->is_free_plan() ) { + // Don't allow to opt out if running in paid plan. + return; + } + } + + if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) { + return; + } + + if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) { + return; + } + + $link_text_id = ''; + $url = '#'; + + if ( $this->is_registered() ) { + if ( $this->is_tracking_allowed() ) { + $link_text_id = $this->get_text_inline( 'Opt Out', 'opt-out' ); + } else { + $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); + } + + add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) ); + } else if ( $this->is_anonymous() || $this->is_activation_mode() ) { + /** + * Show opt-in link only if skipped or in activation mode. + */ + $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); + + $params = ! $this->is_anonymous() ? + array() : + array( + 'nonce' => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ), + 'fs_action' => ( $this->get_unique_affix() . '_reconnect' ), + ); + + $url = $this->get_activation_url( $params ); + } + + if ( ! empty( $link_text_id ) && $this->is_plugin() && self::is_plugins_page() ) { + $this->add_plugin_action_link( + $link_text_id, + $url, + false, + 13, + "opt-in-or-opt-out {$this->_slug}" + ); + } + } + + /** + * Get the URL of the page that should be loaded right after the plugin activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @return string + */ + function get_after_plugin_activation_redirect_url() { + $url = false; + + if ( ! $this->is_addon() || ! $this->has_free_plan() ) { + $first_time_path = $this->_menu->get_first_time_path( + fs_is_network_admin() && $this->_is_network_active + ); + + if ( $this->is_activation_mode() ) { + $url = $this->get_activation_url(); + } else if ( ! empty( $first_time_path ) ) { + $url = $first_time_path; + } else { + $page = ''; + if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + if ( $this->is_network_registered() ) { + $page = 'account'; + } else if ( $this->is_network_anonymous() ) { + $this->maybe_set_slug_and_network_menu_exists_flag(); + } + } + + $url = $this->_get_admin_page_url( $page ); + } + } else { + $plugin_fs = false; + + if ( $this->is_parent_plugin_installed() ) { + $plugin_fs = self::get_parent_instance(); + } + + if ( is_object( $plugin_fs ) ) { + if ( ! $plugin_fs->is_registered() ) { + // Forward to parent plugin connect when parent not registered. + $url = $plugin_fs->get_activation_url(); + } else { + // Forward to account page. + $url = $plugin_fs->_get_admin_page_url( 'account' ); + } + } + } + + return $url; + } + + /** + * Forward page to activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function _redirect_on_activation_hook() { + $url = $this->get_after_plugin_activation_redirect_url(); + + if ( is_string( $url ) ) { + fs_redirect( $url ); + } + } + + /** + * Modify plugin's page action links collection. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param array $links + * @param $file + * + * @return array + */ + function _modify_plugin_action_links_hook( $links, $file ) { + $this->_logger->entrance(); + + $passed_deactivate = false; + $deactivate_link = ''; + $before_deactivate = array(); + $after_deactivate = array(); + foreach ( $links as $key => $link ) { + if ( 'deactivate' === $key ) { + $deactivate_link = $link; + $passed_deactivate = true; + continue; + } + + if ( ! $passed_deactivate ) { + $before_deactivate[ $key ] = $link; + } else { + $after_deactivate[ $key ] = $link; + } + } + + ksort( $this->_action_links ); + + foreach ( $this->_action_links as $new_links ) { + foreach ( $new_links as $link ) { + $before_deactivate[ $link['key'] ] = '' . $link['label'] . ''; + } + } + + if ( ! empty( $deactivate_link ) ) { + /** + * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link. + * + * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation. + */ + $deactivate_link .= ''; + + // Append deactivation link. + $before_deactivate['deactivate'] = $deactivate_link; + } + + return array_merge( $before_deactivate, $after_deactivate ); + } + + /** + * Adds admin message. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + */ + function add_admin_message( $message, $title = '', $type = 'success' ) { + $this->_admin_notices->add( $message, $title, $type ); + } + + /** + * Adds sticky admin message. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $message + * @param string $id + * @param string $title + * @param string $type + */ + function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' ) { + $this->_admin_notices->add_sticky( $message, $id, $title, $type ); + } + + /** + * Check if the paid version of the module is installed. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.0 + * + * @return bool + */ + private function is_premium_version_installed() { + $premium_plugin_basename = $this->premium_plugin_basename(); + $premium_plugin = get_plugins( '/' . dirname( $premium_plugin_basename ) ); + + return ! empty( $premium_plugin ); + } + + /** + * Helper function that returns the final steps for the upgrade completion. + * + * If the module is already running the premium code, returns an empty string. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $plan_title + * + * @return string + */ + private function get_complete_upgrade_instructions( $plan_title = '' ) { + $this->_logger->entrance(); + + $activate_license_string = $this->get_license_network_activation_notice(); + + if ( ! $this->has_premium_version() || $this->is_premium() ) { + return '' . $activate_license_string; + } + + if ( empty( $plan_title ) ) { + $plan_title = $this->get_plan_title(); + } + + if ( $this->is_premium_version_installed() ) { + /** + * If the premium version is already installed, instead of showing the installation instructions, + * tell the current user to activate it. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $premium_plugin_basename = $this->premium_plugin_basename(); + + return sprintf( + /* translators: %1$s: Product title; %2$s: Plan title */ + $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ), + sprintf( '%s', esc_html( $this->get_plugin_title() ) ), + $plan_title, + sprintf( + '', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_plugin_basename, 'activate-plugin_' . $premium_plugin_basename ), + esc_html( sprintf( + /* translators: %s: Plan title */ + $this->get_text_inline( 'Activate %s features', 'activate-x-features' ), + $plan_title + ) ) + ) + ); + } else { + // @since 1.2.1.5 The free version is auto deactivated. + $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ? + ( '
  • ' . $this->esc_html_inline( 'Deactivate the free version', 'deactivate-free-version' ) . '.
  • ' ) : + ''; + + return sprintf( + ' %s:
    1. %s.
    2. %s
    3. %s (%s).
    ', + $this->get_text_inline( 'Please follow these steps to complete the upgrade', 'follow-steps-to-complete-upgrade' ), + ( empty( $activate_license_string ) ? '' : $activate_license_string . '
  • ' ) . + $this->get_latest_download_link( sprintf( + /* translators: %s: Plan title */ + $this->get_text_inline( 'Download the latest %s version', 'download-latest-x-version' ), + $plan_title + ) ), + $deactivation_step, + $this->get_text_inline( 'Upload and activate the downloaded version', 'upload-and-activate' ), + $this->apply_filters( 'upload_and_install_video_url', '//bit.ly/upload-wp-' . $this->_module_type . 's' ), + $this->get_text_inline( 'How to upload and activate?', 'howto-upload-activate' ) + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $url + * @param array $request + */ + private static function enrich_request_for_debug( &$url, &$request ) { + if ( WP_FS__DEBUG_SDK || isset( $_COOKIE['XDEBUG_SESSION'] ) ) { + $url = add_query_arg( 'XDEBUG_SESSION_START', rand( 0, 9999999 ), $url ); + $url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url ); + + $request['cookies'] = array( + new WP_Http_Cookie( array( + 'name' => 'XDEBUG_SESSION', + 'value' => 'PHPSTORM', + ) ) + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $url + * @param array $request + * @param int $success_cache_expiration + * @param int $failure_cache_expiration + * @param bool $maybe_enrich_request_for_debug + * + * @return WP_Error|array + */ + static function safe_remote_post( + &$url, + $request, + $success_cache_expiration = 0, + $failure_cache_expiration = 0, + $maybe_enrich_request_for_debug = true + ) { + $should_cache = ($success_cache_expiration + $failure_cache_expiration > 0); + + $cache_key = $should_cache ? md5( fs_strip_url_protocol($url) . json_encode( $request ) ) : false; + + $response = (!WP_FS__DEBUG_SDK && ( false !== $cache_key )) ? + get_transient( $cache_key ) : + false; + + if ( false === $response ) { + if ( $maybe_enrich_request_for_debug ) { + self::enrich_request_for_debug( $url, $request ); + } + + $response = wp_remote_post( $url, $request ); + + if ( $response instanceof WP_Error ) { + if ( 'https://' === substr( $url, 0, 8 ) && + isset( $response->errors ) && + isset( $response->errors['http_request_failed'] ) + ) { + $http_error = strtolower( $response->errors['http_request_failed'][0] ); + + if ( false !== strpos( $http_error, 'ssl' ) || + false !== strpos( $http_error, 'curl error 35' ) + ) { + // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). + $url = 'http://' . substr( $url, 8 ); + + $request['timeout'] = 15; + $response = wp_remote_post( $url, $request ); + } + } + } + + if ( false !== $cache_key ) { + set_transient( + $cache_key, + $response, + ( ( $response instanceof WP_Error ) ? + $failure_cache_expiration : + $success_cache_expiration ) + ); + } + } + + return $response; + } + + /** + * This method is used to enrich the after upgrade notice instructions when the upgraded + * license cannot be activated network wide (license quota isn't large enough). + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_license_network_activation_notice() { + if ( ! $this->_is_network_active ) { + // Module isn't network level activated. + return ''; + } + + if ( ! fs_is_network_admin() ) { + // Not network level admin. + return ''; + } + + if ( get_blog_count() == 1 ) { + // There's only a single site in the network so if there's a context license it was already activated. + return ''; + } + + if ( ! is_object( $this->_license ) ) { + // No context license. + return ''; + } + + if ( $this->_license->is_single_site() && 0 < $this->_license->activated ) { + // License was already utilized (this is not 100% the case if all the network is localhost sites and the license can be utilized on unlimited localhost sites). + return ''; + } + + if ( $this->can_activate_license_on_network( $this->_license ) ) { + // License can be activated on all the network, so probably, the license is already activate on all the network (that's how the after upgrade sync works). + return ''; + } + + return sprintf( + $this->get_text_inline( '%sClick here%s to choose the sites where you\'d like to activate the license on.', 'network-choose-sites-for-license' ), + '', + '' + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * + * @return string + */ + function get_text( $key ) { + return fs_text( $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * + * @return string + */ + function get_text_inline( $text, $key = '' ) { + return _fs_text_inline( $text, $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * + * @return string + */ + function get_text_x_inline( $text, $context, $key ) { + return _fs_text_x_inline( $text, $context, $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * + * @return string + */ + function esc_html_inline( $text, $key ) { + return esc_html( _fs_text_inline( $text, $key, $this->_slug ) ); + } + + #---------------------------------------------------------------------------------- + #region Versioning + #---------------------------------------------------------------------------------- + + /** + * Check if Freemius in SDK upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_sdk_upgrade_mode() { + return isset( $this->_storage->sdk_upgrade_mode ) ? + $this->_storage->sdk_upgrade_mode : + false; + } + + /** + * Turn SDK upgrade mode off. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function set_sdk_upgrade_complete() { + $this->_storage->sdk_upgrade_mode = false; + } + + /** + * Check if plugin upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_plugin_upgrade_mode() { + return isset( $this->_storage->plugin_upgrade_mode ) ? + $this->_storage->plugin_upgrade_mode : + false; + } + + /** + * Turn plugin upgrade mode off. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function set_plugin_upgrade_complete() { + $this->_storage->plugin_upgrade_mode = false; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Permissions + #---------------------------------------------------------------------------------- + + /** + * Check if specific permission requested. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $permission + * + * @return bool + */ + function is_permission_requested( $permission ) { + return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Auto Activation + #---------------------------------------------------------------------------------- + + /** + * Hints the SDK if running an auto-installation. + * + * @var bool + */ + private $_isAutoInstall = false; + + /** + * After upgrade callback to install and auto activate a plugin. + * This code will only be executed on explicit request from the user, + * following the practice Jetpack are using with their theme installations. + * + * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + function _install_premium_version_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'install_premium_version' ); + + if ( ! $this->is_registered() ) { + // Not registered. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Auto installation only works for opted-in users.', 'auto-install-error-not-opted-in' ), + 'code' => 'premium_installed', + ) ); + } + + $plugin_id = fs_request_get( 'target_module_id', $this->get_id() ); + + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid ID. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ) ); + } + + if ( $plugin_id == $this->get_id() ) { + if ( $this->is_premium() ) { + // Already using the premium code version. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), + 'code' => 'premium_installed', + ) ); + } + if ( ! $this->can_use_premium_code() ) { + // Don't have access to the premium code. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'You do not have a valid license to access the premium version.', 'auto-install-error-invalid-license' ), + 'code' => 'invalid_license', + ) ); + } + if ( ! $this->has_release_on_freemius() ) { + // Plugin is a serviceware, no premium code version. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Plugin is a "Serviceware" which means it does not have a premium code version.', 'auto-install-error-serviceware' ), + 'code' => 'premium_version_missing', + ) ); + } + } else { + $addon = $this->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ) ); + } + + if ( $this->is_addon_activated( $plugin_id, true ) ) { + // Premium add-on version is already activated. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ), + 'code' => 'premium_installed', + ) ); + } + } + + $this->_isAutoInstall = true; + + // Try to install and activate. + $updater = FS_Plugin_Updater::instance( $this ); + $result = $updater->install_and_activate_plugin( $plugin_id ); + + if ( is_array( $result ) && ! empty( $result['message'] ) ) { + self::shoot_ajax_failure( array( + 'message' => $result['message'], + 'code' => $result['code'], + ) ); + } + + self::shoot_ajax_success( $result ); + } + + /** + * Displays module activation dialog box after a successful upgrade + * where the user explicitly requested to auto download and install + * the premium version. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + function _add_auto_installation_dialog_box() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // Not registered. + return; + } + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid module ID. + return; + } + + if ( $plugin_id == $this->get_id() ) { + if ( $this->is_premium() ) { + // Already using the premium code version. + return; + } + if ( ! $this->can_use_premium_code() ) { + // Don't have access to the premium code. + return; + } + if ( ! $this->has_release_on_freemius() ) { + // Plugin is a serviceware, no premium code version. + return; + } + } else { + $addon = $this->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + return; + } + + if ( $this->is_addon_activated( $plugin_id, true ) ) { + // Premium add-on version is already activated. + return; + } + } + + $vars = array( + 'id' => $this->_module_id, + 'target_module_id' => $plugin_id, + 'slug' => $this->_slug, + ); + + fs_require_template( 'auto-installation.php', $vars ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Tabs Integration + #-------------------------------------------------------------------------------- + + #region Module's Original Tabs + + /** + * Inject a JavaScript logic to capture the theme tabs HTML. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _tabs_capture() { + $this->_logger->entrance(); + + if ( ! $this->is_theme_settings_page() || + ! $this->is_matching_url( $this->main_menu_url() ) + ) { + return; + } + + $params = array( + 'id' => $this->_module_id, + ); + + fs_require_once_template( 'tabs-capture-js.php', $params ); + } + + /** + * Cache theme's tabs HTML for a week. The cache will also be set as expired + * after version and type (free/premium) changes, in addition to the week period. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _store_tabs_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'store_tabs' ); + + // Init filesystem if not yet initiated. + WP_Filesystem(); + + // Get POST body HTML data. + global $wp_filesystem; + $tabs_html = $wp_filesystem->get_contents( "php://input" ); + + if ( is_string( $tabs_html ) ) { + $tabs_html = trim( $tabs_html ); + } + + if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) { + self::shoot_ajax_failure(); + } + + $this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); + + self::shoot_ajax_success(); + } + + /** + * Cache theme's settings page custom styles. The cache will also be set as expired + * after version and type (free/premium) changes, in addition to the week period. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _store_tabs_styles() { + $this->_logger->entrance(); + + if ( ! $this->is_theme_settings_page() || + ! $this->is_matching_url( $this->main_menu_url() ) + ) { + return; + } + + $wp_styles = wp_styles(); + + $theme_styles_url = get_template_directory_uri(); + + $stylesheets = array(); + foreach ( $wp_styles->queue as $handler ) { + if ( fs_starts_with( $handler, 'fs_' ) ) { + // Assume that stylesheets that their handler starts with "fs_" belong to the SDK. + continue; + } + + /** + * @var _WP_Dependency $stylesheet + */ + $stylesheet = $wp_styles->registered[ $handler ]; + + if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) { + $stylesheets[] = $stylesheet->src; + } + } + + if ( ! empty( $stylesheets ) ) { + $this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + } + + /** + * Check if module's original settings page has any tabs. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + private function has_tabs() { + return $this->_cache->has( 'tabs' ); + } + + /** + * Get module's settings page HTML content, starting + * from the beginning of the
    element, + * until the tabs HTML (including). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + private function get_tabs_html() { + $this->_logger->entrance(); + + return $this->_cache->get( 'tabs' ); + } + + /** + * Check if page should include tabs. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + private function should_page_include_tabs() { + if ( ! $this->has_settings_menu() ) { + // Don't add tabs if no settings at all. + return false; + } + + if ( ! $this->is_theme() ) { + // Only add tabs to themes for now. + return false; + } + + if ( ! $this->has_paid_plan() && ! $this->has_addons() ) { + // Only add tabs to monetizing themes. + return false; + } + + if ( ! $this->is_theme_settings_page() ) { + // Only add tabs if browsing one of the theme's setting pages. + return false; + } + + if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) { + // Don't add tabs on checkout page, we want to reduce distractions + // as much as possible. + return false; + } + + return true; + } + + /** + * Add the tabs HTML before the setting's page content and + * enqueue any required stylesheets. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool If tabs were included. + */ + function _add_tabs_before_content() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return false; + } + + /** + * Enqueue the original stylesheets that are included in the + * theme settings page. That way, if the theme settings has + * some custom _styled_ content above the tabs UI, this + * will make sure that the styling is preserved. + */ + $stylesheets = $this->_cache->get( 'tabs_stylesheets', array() ); + if ( is_array( $stylesheets ) ) { + for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) { + wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] ); + } + } + + // Cut closing
    tag. + echo substr( trim( $this->get_tabs_html() ), 0, - 6 ); + + return true; + } + + /** + * Add the tabs closing HTML after the setting's page content. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool If tabs closing HTML was included. + */ + function _add_tabs_after_content() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return false; + } + + echo '
  • '; + + return true; + } + + #endregion + + /** + * Add in-page JavaScript to inject the Freemius tabs into + * the module's setting tabs section. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _add_freemius_tabs() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return; + } + + $params = array( 'id' => $this->_module_id ); + fs_require_once_template( 'tabs.php', $params ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Customizer Integration for Themes + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param WP_Customize_Manager $customizer + */ + function _customizer_register( $customizer ) { + $this->_logger->entrance(); + + if ( $this->is_pricing_page_visible() ) { + require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php'; + + $customizer->add_section( 'freemius_upsell', array( + 'title' => '★ ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ), + 'priority' => 1, + ) ); + $customizer->add_setting( 'freemius_upsell', array( + 'sanitize_callback' => 'esc_html', + ) ); + + $customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array( + 'fs' => $this, + 'section' => 'freemius_upsell', + 'priority' => 100, + ) ) ); + } + + if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) { + require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php'; + + // Main Documentation Link In Customizer Root. + $customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array( + 'fs' => $this, + 'priority' => 1000, + ) ) ); + } + } + + #endregion + + /** + * If the theme has a paid version, add some custom + * styling to the theme's premium version (if exists) + * to highlight that it's the premium version of the + * same theme, making it easier for identification + * after the user upgrades and upload it to the site. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _style_premium_theme() { + $this->_logger->entrance(); + + if ( ! self::is_themes_page() ) { + // Only include in the themes page. + return; + } + + if ( ! $this->has_paid_plan() ) { + // Only include if has any paid plans. + return; + } + + $params = null; + fs_require_once_template( '/js/jquery.content-change.php', $params ); + + $params = array( + 'slug' => $this->_slug, + 'id' => $this->_module_id, + ); + + fs_require_template( '/js/style-premium-theme.php', $params ); + } + + /** + * This method will return the absolute URL of the module's local icon. + * + * When you are running your plugin or theme on a **localhost** environment, if the icon + * is not found in the local assets folder, try to fetch the icon URL from Freemius. If not set and + * it's a plugin hosted on WordPress.org, try fetching the icon URL from wordpress.org. + * If an icon is found, this method will automatically attempt to download the icon and store it + * in /freemius/assets/img/{slug}.{png|jpg|gif|svg}. + * + * It's important to mention that this method is NOT phoning home since the developer will deploy + * the product with the local icon in the assets folder. The download process just simplifies + * the process for the developer. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_local_icon_url() { + global $fs_active_plugins; + + /** + * @since 1.1.7.5 + */ + $local_path = $this->apply_filters( 'plugin_icon', false ); + + if ( is_string( $local_path ) ) { + $icons = array( $local_path ); + } else { + $img_dir = WP_FS__DIR_IMG; + + // Locate the main assets folder. + if ( 1 < count( $fs_active_plugins->plugins ) ) { + $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); + + foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) { + if ( $data->plugin_path == $this->get_plugin_basename() ) { + $img_dir = $plugin_or_theme_img_dir + . '/' + /** + * The basename will be `themes` or the basename of a custom themes directory. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + . str_replace( '../' . basename( $plugin_or_theme_img_dir ) . '/', '', $sdk_path ) + . '/assets/img'; + + break; + } + } + } + + // Try to locate the icon in the assets folder. + $icons = glob( fs_normalize_path( $img_dir . "/{$this->_slug}.*" ) ); + + if ( ! is_array( $icons ) || 0 === count( $icons ) ) { + if ( ! WP_FS__IS_LOCALHOST && $this->is_theme() ) { + $icons = array( + fs_normalize_path( $img_dir . '/theme-icon.png' ) + ); + } else { + $icon_found = false; + $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.png" ); + + if ( ! function_exists( 'get_filesystem_method' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + $have_write_permissions = ( 'direct' === get_filesystem_method( array(), fs_normalize_path( $img_dir ) ) ); + + /** + * IMPORTANT: THIS CODE WILL NEVER RUN AFTER THE PLUGIN IS IN THE REPO. + * + * This code will only be executed once during the testing + * of the plugin in a local environment. The plugin icon file WILL + * already exist in the assets folder when the plugin is deployed to + * the repository. + */ + if ( WP_FS__IS_LOCALHOST && $have_write_permissions ) { + // Fetch icon from Freemius. + $icon = $this->fetch_remote_icon_url(); + + // Fetch icon from WordPress.org. + if ( empty( $icon ) && $this->is_plugin() && $this->is_org_repo_compliant() ) { + if ( ! function_exists( 'plugins_api' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + } + + $plugin_information = plugins_api( 'plugin_information', array( + 'slug' => $this->_slug, + 'fields' => array( + 'sections' => false, + 'tags' => false, + 'icons' => true + ) + ) ); + + if ( + ! is_wp_error( $plugin_information ) + && isset( $plugin_information->icons ) + && ! empty( $plugin_information->icons ) + ) { + /** + * Get the smallest icon. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $icon = end( $plugin_information->icons ); + } + } + + if ( ! empty( $icon ) ) { + if ( 0 !== strpos( $icon, 'http' ) ) { + $icon = 'http:' . $icon; + } + + /** + * Get a clean file extension, e.g.: "jpg" and not "jpg?rev=1305765". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $ext = pathinfo( strtok( $icon, '?' ), PATHINFO_EXTENSION ); + + $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.{$ext}" ); + + // Try to download the icon. + $icon_found = fs_download_image( $icon, $local_path ); + } + } + + if ( ! $icon_found ) { + // No icons found, fallback to default icon. + if ( $have_write_permissions ) { + // If have write permissions, copy default icon. + copy( fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ), $local_path ); + } else { + // If doesn't have write permissions, use default icon path. + $local_path = fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ); + } + } + + $icons = array( $local_path ); + } + } + } + + $icon_dir = dirname( $icons[0] ); + + return fs_img_url( substr( $icons[0], strlen( $icon_dir ) ), $icon_dir ); + } + + /** + * Fetch module's extended info. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return object|mixed + */ + private function fetch_module_info() { + return $this->get_api_plugin_scope()->get( 'info.json', false, WP_FS__TIME_WEEK_IN_SEC ); + } + + /** + * Fetch module's remote icon URL. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function fetch_remote_icon_url() { + $info = $this->fetch_module_info(); + + return ( $this->is_api_result_object( $info, 'icon' ) && is_string( $info->icon ) ) ? + $info->icon : + ''; + } + + #-------------------------------------------------------------------------------- + #region GDPR + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @return bool + */ + function fetch_and_store_current_user_gdpr_anonymously() { + $pong = $this->ping( null, true ); + + if ( ! $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) { + return false; + } else { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + return $pong->is_gdpr_required; + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param array $user_plugins + * + * @return string + */ + private function get_gdpr_admin_notice_string( $user_plugins ) { + $this->_logger->entrance(); + + $addons = self::get_all_addons(); + + foreach ( $user_plugins as $user_plugin ) { + $has_addons = isset( $addons[ $user_plugin->id ] ); + + if ( WP_FS__MODULE_TYPE_PLUGIN === $user_plugin->type && ! $has_addons ) { + if ( $this->_module_id == $user_plugin->id ) { + $addons = $this->get_addons(); + $has_addons = ( ! empty( $addons ) ); + } else { + $plugin_api = FS_Api::instance( + $user_plugin->id, + 'plugin', + $user_plugin->id, + $user_plugin->public_key, + ! $user_plugin->is_live, + false, + $this->get_sdk_version() + ); + + $addons_result = $plugin_api->get( '/addons.json?enriched=true', true ); + + if ( $this->is_api_result_object( $addons_result, 'plugins' ) && + is_array( $addons_result->plugins ) && + ! empty( $addons_result->plugins ) + ) { + $has_addons = true; + } + } + } + + $user_plugin->has_addons = $has_addons; + } + + $is_single_parent_product = ( 1 === count( $user_plugins ) ); + + $multiple_products_text = ''; + + if ( $is_single_parent_product ) { + $single_parent_product = reset( $user_plugins ); + + $thank_you = sprintf( + "%s", + $single_parent_product->id, + sprintf( + $single_parent_product->has_addons ? + $this->get_text_inline( 'Thank you so much for using %s and its add-ons!', 'thank-you-for-using-product-and-its-addons' ) : + $this->get_text_inline( 'Thank you so much for using %s!', 'thank-you-for-using-product' ), + sprintf('%s', $single_parent_product->title) + ) + ); + + $already_opted_in = sprintf( + $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving the %s.", 'already-opted-in-to-product-usage-tracking' ), + ( WP_FS__MODULE_TYPE_THEME === $single_parent_product->type ) ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN + ); + } else { + $thank_you = $this->get_text_inline( 'Thank you so much for using our products!', 'thank-you-for-using-products' ); + $already_opted_in = $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving them.", 'already-opted-in-to-products-usage-tracking' ); + + $products_and_add_ons = ''; + foreach ( $user_plugins as $user_plugin ) { + if ( ! empty( $products_and_add_ons ) ) { + $products_and_add_ons .= ', '; + } + + if ( ! $user_plugin->has_addons ) { + $products_and_add_ons .= sprintf( + "%s", + $user_plugin->id, + $user_plugin->title + ); + } else { + $products_and_add_ons .= sprintf( + "%s", + $user_plugin->id, + sprintf( + $this->get_text_inline( '%s and its add-ons', 'product-and-its-addons' ), + $user_plugin->title + ) + ); + } + } + + $multiple_products_text = sprintf( + "%s: %s", + $this->get_text_inline( 'Products', 'products' ), + $products_and_add_ons + ); + } + + $actions = sprintf( + '
    • %s - %s
    • %s - %s
    ', + sprintf('', $this->get_text_inline( 'Yes', 'yes' ) ), + $this->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ), + sprintf('', $this->get_text_inline( 'No', 'no' ) ), + sprintf( + $this->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), + '', + '' + ) + ); + + return sprintf( + '%s %s %s', + $thank_you, + $already_opted_in, + sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . + '

    ' . + '' . $this->get_text_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) . '' . + $actions . + ( $is_single_parent_product ? '' : $multiple_products_text ) + ); + } + + /** + * This method is called for opted-in users to fetch the is_marketing_allowed flag of the user for all the + * plugins and themes they've opted in to. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $user_email + * @param string $license_key + * @param array $plugin_ids + * @param string|null $license_key + * + * @return array|false + */ + private function fetch_user_marketing_flag_status_by_plugins( $user_email, $license_key, $plugin_ids ) { + $request = array( + 'method' => 'POST', + 'body' => array(), + 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, + ); + + if ( is_string( $user_email ) ) { + $request['body']['email'] = $user_email; + } else { + $request['body']['license_key'] = $license_key; + } + + $result = array(); + + $url = WP_FS__ADDRESS . '/action/service/user_plugin/'; + $total_plugin_ids = count( $plugin_ids ); + + $plugin_ids_count_per_request = 10; + for ( $i = 1; $i <= $total_plugin_ids; $i += $plugin_ids_count_per_request ) { + $plugin_ids_set = array_slice( $plugin_ids, $i - 1, $plugin_ids_count_per_request ); + + $request['body']['plugin_ids'] = $plugin_ids_set; + + $response = self::safe_remote_post( + $url, + $request, + WP_FS__TIME_24_HOURS_IN_SEC, + WP_FS__TIME_12_HOURS_IN_SEC + ); + + if ( ! is_wp_error( $response ) ) { + $decoded = is_string( $response['body'] ) ? + json_decode( $response['body'] ) : + null; + + if ( + !is_object($decoded) || + !isset($decoded->success) || + true !== $decoded->success || + !isset( $decoded->data ) || + !is_array( $decoded->data ) + ) { + return false; + } + + $result = array_merge( $result, $decoded->data ); + } + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _maybe_show_gdpr_admin_notice() { + if ( ! $this->is_user_in_admin() ) { + return; + } + + if ( ! $this->should_handle_gdpr_admin_notice() ) { + return; + } + + if ( ! $this->is_user_admin() ) { + return; + } + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + $lock = FS_User_Lock::instance(); + + /** + * Try to acquire a 60-sec lock based on the WP user and thread/process ID. + */ + if ( ! $lock->try_lock( 60 ) ) { + return; + } + + /** + * @var $current_wp_user WP_User + */ + $current_wp_user = self::_get_current_wp_user(); + + /** + * @var FS_User $current_fs_user + */ + $current_fs_user = Freemius::_get_user_by_email( $current_wp_user->user_email ); + + $ten_years_in_sec = 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC; + + if ( ! is_object( $current_fs_user ) ) { + // 10-year lock. + $lock->lock( $ten_years_in_sec ); + + return; + } + + $gdpr = FS_GDPR_Manager::instance(); + + if ( $gdpr->is_opt_in_notice_shown() ) { + // 30-day lock. + $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); + + return; + } + + if ( ! $gdpr->should_show_opt_in_notice() ) { + // 10-year lock. + $lock->lock( $ten_years_in_sec ); + + return; + } + + $last_time_notice_shown = $gdpr->last_time_notice_was_shown(); + $was_notice_shown_before = ( false !== $last_time_notice_shown ); + + if ( $was_notice_shown_before && + 30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_notice_shown + ) { + // If the notice was shown before, show it again after 30 days from the last time it was shown. + return; + } + + /** + * Find all plugin IDs that were installed by the current admin. + */ + $plugin_ids_map = self::get_user_opted_in_module_ids_map( $current_fs_user->id ); + + if ( empty( $plugin_ids_map )) { + $lock->lock( $ten_years_in_sec ); + + return; + } + + $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( + $current_fs_user->email, + null, + array_keys( $plugin_ids_map ) + ); + + if ( empty( $user_plugins ) ) { + $lock->lock( + is_array($user_plugins) ? + $ten_years_in_sec : + // Lock for 24-hours on errors. + WP_FS__TIME_24_HOURS_IN_SEC + ); + + return; + } + + $has_unset_marketing_optin = false; + + foreach ( $user_plugins as $user_plugin ) { + if ( true == $user_plugin->is_marketing_allowed ) { + unset( $plugin_ids_map[ $user_plugin->plugin_id ] ); + } + + if ( ! $has_unset_marketing_optin && is_null( $user_plugin->is_marketing_allowed ) ) { + $has_unset_marketing_optin = true; + } + } + + if ( empty( $plugin_ids_map ) || + ( $was_notice_shown_before && ! $has_unset_marketing_optin ) + ) { + $lock->lock( $ten_years_in_sec ); + + return; + } + + $modules = array_merge( + array_values( self::$_accounts->get_option( 'plugins', array() ) ), + array_values( self::$_accounts->get_option( 'themes', array() ) ) + ); + + foreach ( $modules as $module ) { + if ( ! FS_Plugin::is_valid_id( $module->parent_plugin_id ) && isset( $plugin_ids_map[ $module->id ] ) ) { + $plugin_ids_map[ $module->id ] = $module; + } + } + + $plugin_title = null; + if ( 1 === count( $plugin_ids_map ) ) { + $module = reset( $plugin_ids_map ); + $plugin_title = $module->title; + } + + $gdpr->add_opt_in_sticky_notice( + $this->get_gdpr_admin_notice_string( $plugin_ids_map ), + $plugin_title + ); + + $this->add_gdpr_optin_ajax_handler_and_style(); + + $gdpr->notice_was_just_shown(); + + // 30-day lock. + $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + + /** + * Prevents the GDPR opt-in admin notice from being added if the user has already chosen to allow or not allow + * marketing. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + private function disable_opt_in_notice_and_lock_user() { + FS_GDPR_Manager::instance()->disable_opt_in_notice(); + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + // 10-year lock. + FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _add_gdpr_optin_js() { + $vars = array( 'id' => $this->_module_id ); + + fs_require_once_template( 'gdpr-optin-js.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function enqueue_gdpr_optin_notice_style() { + fs_enqueue_local_style( 'fs_gdpr_optin_notice', '/admin/gdpr-optin-notice.css' ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _maybe_add_gdpr_optin_ajax_handler() { + $this->add_ajax_action( 'fetch_is_marketing_required_flag_value', array( &$this, '_fetch_is_marketing_required_flag_value_ajax_action' ) ); + + if ( FS_GDPR_Manager::instance()->is_opt_in_notice_shown() ) { + $this->add_gdpr_optin_ajax_handler_and_style(); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _fetch_is_marketing_required_flag_value_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); + + $license_key = fs_request_get( 'license_key' ); + + if ( empty($license_key) ) { + self::shoot_ajax_failure( $this->get_text_inline( 'License key is empty.', 'empty-license-key' ) ); + } + + $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( + null, + $license_key, + array( $this->_module_id ) + ); + + if ( ! is_array( $user_plugins ) || + empty($user_plugins) || + !isset($user_plugins[0]->plugin_id) || + $user_plugins[0]->plugin_id != $this->_module_id + ) { + /** + * If faced an error or if the module ID do not match to the current module, ask for GDPR opt-in. + * + * @author Vova Feldman (@svovaf) + */ + self::shoot_ajax_success( array( 'is_marketing_allowed' => null ) ); + } + + self::shoot_ajax_success( array( 'is_marketing_allowed' => $user_plugins[0]->is_marketing_allowed ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + private function add_gdpr_optin_ajax_handler_and_style() { + // Add GDPR action AJAX callback. + $this->add_ajax_action( 'gdpr_optin_action', array( &$this, '_gdpr_optin_ajax_action' ) ); + + add_action( 'admin_footer', array( &$this, '_add_gdpr_optin_js' ) ); + add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_gdpr_optin_notice_style' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _gdpr_optin_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'gdpr_optin_action' ); + + if ( ! fs_request_has( 'is_marketing_allowed' ) || ! fs_request_has( 'plugin_ids' ) ) { + self::shoot_ajax_failure(); + } + + $current_wp_user = self::_get_current_wp_user(); + + $plugin_ids = fs_request_get( 'plugin_ids', array() ); + if ( ! is_array( $plugin_ids ) || empty( $plugin_ids ) ) { + self::shoot_ajax_failure(); + } + + $modules = array_merge( + array_values( self::$_accounts->get_option( 'plugins', array() ) ), + array_values( self::$_accounts->get_option( 'themes', array() ) ) + ); + + foreach ( $modules as $key => $module ) { + if ( ! in_array( $module->id, $plugin_ids ) ) { + unset( $modules[ $key ] ); + } + } + + if ( empty( $modules ) ) { + self::shoot_ajax_failure(); + } + + $user_api = $this->get_api_user_scope_by_user( Freemius::_get_user_by_email( $current_wp_user->user_email ) ); + + foreach ( $modules as $module ) { + $user_api->call( "?plugin_id={$module->id}", 'put', array( + 'is_marketing_allowed' => ( true == fs_request_get_bool( 'is_marketing_allowed' ) ) + ) ); + } + + FS_GDPR_Manager::instance()->remove_opt_in_notice(); + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + // 10-year lock. + FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); + + self::shoot_ajax_success(); + } + + /** + * Checks if the GDPR admin notice should be handled. By default, this logic is off, unless the integrator adds the special 'handle_gdpr_admin_notice' filter. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + private function should_handle_gdpr_admin_notice() { + return $this->apply_filters( + 'handle_gdpr_admin_notice', + // Default to false. + false + ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Marketing + #---------------------------------------------------------------------------------- + + /** + * Check if current user purchased any other plugins before. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_purchased_before() { + // TODO: Implement has_purchased_before() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as an agency. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_agency() { + // TODO: Implement is_agency() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as a developer. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_developer() { + // TODO: Implement is_developer() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as a business. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_business() { + // TODO: Implement is_business() method. + throw new Exception( 'not implemented' ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Helper + #---------------------------------------------------------------------------------- + + /** + * If running with a secret key, assume it's the developer and show pending plans as well. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.2 + * + * @param string $path + * + * @return string + */ + function add_show_pending( $path ) { + if ( ! $this->has_secret_key() ) { + return $path; + } + + return $path . ( false !== strpos( $path, '?' ) ? '&' : '?' ) . 'show_pending=true'; + } + + #endregion + } diff --git a/freemius/includes/class-fs-admin-notices.php b/freemius/includes/class-fs-admin-notices.php new file mode 100644 index 0000000..01b197a --- /dev/null +++ b/freemius/includes/class-fs-admin-notices.php @@ -0,0 +1,321 @@ +_id = $id; + $this->_title = $title; + $this->_module_unique_affix = $module_unique_affix; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + + $this->_network_notices = FS_Admin_Notice_Manager::instance( + $id, + $title, + $module_unique_affix, + $is_network_and_blog_admins, + true + ); + } + + $this->_notices = FS_Admin_Notice_Manager::instance( + $id, + $title, + $module_unique_affix, + false, + $this->_blog_id + ); + } + + /** + * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + * @param bool $store_if_sticky + * @param int|null $network_level_or_blog_id + * + * @uses add_action() + */ + function add( + $message, + $title = '', + $type = 'success', + $is_sticky = false, + $id = '', + $store_if_sticky = true, + $network_level_or_blog_id = null + ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + $notices->add( + $message, + $title, + $type, + $is_sticky, + $id, + $store_if_sticky + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string|string[] $ids + * @param int|null $network_level_or_blog_id + */ + function remove_sticky( $ids, $network_level_or_blog_id = null ) { + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + if ( $this->should_use_network_notices( $ids[0], $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + return $notices->remove_sticky( $ids ); + } + + /** + * Check if sticky message exists by id. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $id + * @param int|null $network_level_or_blog_id + * + * @return bool + */ + function has_sticky( $id, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + return $notices->has_sticky( $id ); + } + + /** + * Adds sticky admin notification. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $message + * @param string $id Message ID + * @param string $title + * @param string $type + * @param int|null $network_level_or_blog_id + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and + * blog admin pages. + */ + function add_sticky( + $message, + $id, + $title = '', + $type = 'success', + $network_level_or_blog_id = null, + $wp_user_id = null, + $plugin_title = null, + $is_network_and_blog_admins = false + ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + $notices->add_sticky( $message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + } + + /** + * Clear all sticky messages. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $network_level_or_blog_id + */ + function clear_all_sticky( $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + 0 == $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) + ) { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + $notices->clear_all_sticky(); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_notices->clear_all_sticky(); + } + } + + /** + * Add admin message to all admin messages queue, and hook to all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + */ + function add_all( $message, $title = '', $type = 'success', $is_sticky = false, $id = '' ) { + $this->add( $message, $title, $type, $is_sticky, true, $id ); + } + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return FS_Admin_Notice_Manager + */ + private function get_site_notices( $blog_id = 0 ) { + if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { + return $this->_notices; + } + + return FS_Admin_Notice_Manager::instance( + $this->_id, + $this->_title, + $this->_module_unique_affix, + false, + $blog_id + ); + } + + /** + * Check if the network notices should be used. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $id + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite notices (if there's a network). When `false`, use the current context blog notices. When `null`, the decision which notices manager to use (MS vs. Current S) will be handled internally and determined based on the $id and the context admin (blog admin vs. network level admin). + * + * @return bool + */ + private function should_use_network_notices( $id = '', $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + return fs_is_network_admin(); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php new file mode 100644 index 0000000..659a9d5 --- /dev/null +++ b/freemius/includes/class-fs-api.php @@ -0,0 +1,664 @@ +get_option( 'api_clock_diff', 0 ); + Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); + + if ( self::$_options->get_option( 'api_force_http', false ) ) { + Freemius_Api_WordPress::SetHttp(); + } + } + + /** + * @param string $slug + * @param string $scope 'app', 'developer', 'user' or 'install'. + * @param number $id Element's id. + * @param string $public_key Public key. + * @param bool|string $secret_key Element's secret key. + * @param bool $is_sandbox + * @param null|string $sdk_version + */ + private function __construct( + $slug, + $scope, + $id, + $public_key, + $secret_key, + $is_sandbox, + $sdk_version + ) { + $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox ); + + $this->_slug = $slug; + $this->_sdk_version = $sdk_version; + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + } + + /** + * Find clock diff between server and API server, and store the diff locally. + * + * @param bool|int $diff + * + * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. + */ + private function _sync_clock_diff( $diff = false ) { + $this->_logger->entrance(); + + // Sync clock and store. + $new_clock_diff = ( false === $diff ) ? + Freemius_Api_WordPress::FindClockDiff() : + $diff; + + if ( $new_clock_diff === self::$_clock_diff ) { + return false; + } + + self::$_clock_diff = $new_clock_diff; + + // Update API clock's diff. + Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); + + // Store new clock diff in storage. + self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); + + return $new_clock_diff; + } + + /** + * Override API call to enable retry with servers' clock auto sync method. + * + * @param string $path + * @param string $method + * @param array $params + * @param bool $retry Is in retry or first call attempt. + * + * @return array|mixed|string|void + */ + private function _call( $path, $method = 'GET', $params = array(), $retry = false ) { + $this->_logger->entrance( $method . ':' . $path ); + + if ( self::is_temporary_down() ) { + $result = $this->get_temporary_unavailable_error(); + } else { + /** + * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet. + */ + if ( ! empty( $this->_sdk_version ) ) { + if ( false === strpos( $path, 'sdk_version=' ) && + ! isset( $params['sdk_version'] ) + ) { + // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY. + $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path ); + } + } + + $result = $this->_api->Api( $path, $method, $params ); + + if ( null !== $result && + isset( $result->error ) && + isset( $result->error->code ) && + 'request_expired' === $result->error->code + ) { + if ( ! $retry ) { + $diff = isset( $result->error->timestamp ) ? + ( time() - strtotime( $result->error->timestamp ) ) : + false; + + // Try to sync clock diff. + if ( false !== $this->_sync_clock_diff( $diff ) ) { + // Retry call with new synced clock. + return $this->_call( $path, $method, $params, true ); + } + } + } + } + + if ( $this->_logger->is_on() && self::is_api_error( $result ) ) { + // Log API errors. + $this->_logger->api_error( $result ); + } + + return $result; + } + + /** + * Override API call to wrap it in servers' clock sync method. + * + * @param string $path + * @param string $method + * @param array $params + * + * @return array|mixed|string|void + * @throws Freemius_Exception + */ + function call( $path, $method = 'GET', $params = array() ) { + return $this->_call( $path, $method, $params ); + } + + /** + * Get API request URL signed via query string. + * + * @param string $path + * + * @return string + */ + function get_signed_url( $path ) { + return $this->_api->GetSignedUrl( $path ); + } + + /** + * @param string $path + * @param bool $flush + * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours + * + * @return stdClass|mixed + */ + function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( $path ); + + $cache_key = $this->get_cache_key( $path ); + + // Always flush during development. + if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { + $flush = true; + } + + $cached_result = self::$_cache->get( $cache_key ); + + if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) { + $result = $this->call( $path ); + + if ( ! is_object( $result ) || isset( $result->error ) ) { + // Api returned an error. + if ( is_object( $cached_result ) && + ! isset( $cached_result->error ) + ) { + // If there was an error during a newer data fetch, + // fallback to older data version. + $result = $cached_result; + + if ( $this->_logger->is_on() ) { + $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) ); + } + } else { + if ( is_object( $result ) && 404 == $result->error->http ) { + /** + * If the response code is 404, cache the result for half of the `$expiration`. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $expiration /= 2; + } else { + // If no older data version and the response code is not 404, return result without + // caching the error. + return $result; + } + } + } + + self::$_cache->set( $cache_key, $result, $expiration ); + + $cached_result = $result; + } else { + $this->_logger->log( 'Using cached API result.' ); + } + + return $cached_result; + } + + /** + * Check if there's a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $path + * @param string $method + * @param array $params + * + * @return bool + */ + function is_cached( $path, $method = 'GET', $params = array() ) { + $cache_key = $this->get_cache_key( $path, $method, $params ); + + return self::$_cache->has_valid( $cache_key ); + } + + /** + * Invalidate a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param string $path + * @param string $method + * @param array $params + */ + function purge_cache( $path, $method = 'GET', $params = array() ) { + $this->_logger->entrance( "{$method}:{$path}" ); + + $cache_key = $this->get_cache_key( $path, $method, $params ); + + self::$_cache->purge( $cache_key ); + } + + /** + * Invalidate a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $path + * @param int $expiration + * @param string $method + * @param array $params + */ + function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) { + $this->_logger->entrance( "{$method}:{$path}:{$expiration}" ); + + $cache_key = $this->get_cache_key( $path, $method, $params ); + + self::$_cache->update_expiration( $cache_key, $expiration ); + } + + /** + * @param string $path + * @param string $method + * @param array $params + * + * @return string + * @throws \Freemius_Exception + */ + private function get_cache_key( $path, $method = 'GET', $params = array() ) { + $canonized = $this->_api->CanonizePath( $path ); +// $exploded = explode('/', $canonized); +// return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); + return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); + } + + /** + * Test API connectivity. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 If fails, try to fallback to HTTP. + * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if + * temporary down. + * + * @return bool True if successful connectivity to the API. + */ + static function test() { + self::_init(); + + $cache_key = 'ping_test'; + + $test = self::$_cache->get_valid( $cache_key, null ); + + if ( is_null( $test ) ) { + $test = Freemius_Api_WordPress::Test(); + + if ( false === $test && Freemius_Api_WordPress::IsHttps() ) { + // Fallback to HTTP, since HTTPS fails. + Freemius_Api_WordPress::SetHttp(); + + self::$_options->set_option( 'api_force_http', true, true ); + + $test = Freemius_Api_WordPress::Test(); + + if ( false === $test ) { + /** + * API connectivity test fail also in HTTP request, therefore, + * fallback to HTTPS to keep connection secure. + * + * @since 1.1.6 + */ + self::$_options->set_option( 'api_force_http', false, true ); + } + } + + self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); + } + + return $test; + } + + /** + * Check if API is temporary down. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + static function is_temporary_down() { + self::_init(); + + $test = self::$_cache->get_valid( 'ping_test', null ); + + return ( false === $test ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return object + */ + private function get_temporary_unavailable_error() { + return (object) array( + 'error' => (object) array( + 'type' => 'TemporaryUnavailable', + 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.', + 'code' => 'temporary_unavailable', + 'http' => 503 + ) + ); + } + + /** + * Ping API for connectivity test, and return result object. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param null|string $unique_anonymous_id + * @param array $params + * + * @return object + */ + function ping( $unique_anonymous_id = null, $params = array() ) { + $this->_logger->entrance(); + + if ( self::is_temporary_down() ) { + return $this->get_temporary_unavailable_error(); + } + + $pong = is_null( $unique_anonymous_id ) ? + Freemius_Api_WordPress::Ping() : + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); + + if ( $this->is_valid_ping( $pong ) ) { + return $pong; + } + + if ( self::should_try_with_http( $pong ) ) { + // Fallback to HTTP, since HTTPS fails. + Freemius_Api_WordPress::SetHttp(); + + self::$_options->set_option( 'api_force_http', true, true ); + + $pong = is_null( $unique_anonymous_id ) ? + Freemius_Api_WordPress::Ping() : + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); + + if ( ! $this->is_valid_ping( $pong ) ) { + self::$_options->set_option( 'api_force_http', false, true ); + } + } + + return $pong; + } + + /** + * Check if based on the API result we should try + * to re-run the same request with HTTP instead of HTTPS. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param $result + * + * @return bool + */ + private static function should_try_with_http( $result ) { + if ( ! Freemius_Api_WordPress::IsHttps() ) { + return false; + } + + return ( ! is_object( $result ) || + ! isset( $result->error ) || + ! isset( $result->error->code ) || + ! in_array( $result->error->code, array( + 'curl_missing', + 'cloudflare_ddos_protection', + 'maintenance_mode', + 'squid_cache_block', + 'too_many_requests', + ) ) ); + + } + + /** + * Check if valid ping request result. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param mixed $pong + * + * @return bool + */ + function is_valid_ping( $pong ) { + return Freemius_Api_WordPress::Test( $pong ); + } + + function get_url( $path = '' ) { + return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); + } + + /** + * Clear API cache. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + static function clear_cache() { + self::_init(); + + self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); + self::$_cache->clear(); + } + + #---------------------------------------------------------------------------------- + #region Error Handling + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + static function is_api_error( $result ) { + return ( is_object( $result ) && isset( $result->error ) ) || + is_string( $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + static function is_api_error_object( $result ) { + return ( + is_object( $result ) && + isset( $result->error ) && + isset( $result->error->message ) + ); + } + + /** + * Checks if given API result is a non-empty and not an error object. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * @param string|null $required_property Optional property we want to verify that is set. + * + * @return bool + */ + static function is_api_result_object( $result, $required_property = null ) { + return ( + is_object( $result ) && + ! isset( $result->error ) && + ( empty( $required_property ) || isset( $result->{$required_property} ) ) + ); + } + + /** + * Checks if given API result is a non-empty entity object with non-empty ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool + */ + static function is_api_result_entity( $result ) { + return self::is_api_result_object( $result, 'id' ) && + FS_Entity::is_valid_id( $result->id ); + } + + /** + * Get API result error code. If failed to get code, returns an empty string. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param mixed $result + * + * @return string + */ + static function get_error_code( $result ) { + if ( is_object( $result ) && + isset( $result->error ) && + is_object( $result->error ) && + ! empty( $result->error->code ) + ) { + return $result->error->code; + } + + return ''; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-logger.php b/freemius/includes/class-fs-logger.php new file mode 100644 index 0000000..90e918f --- /dev/null +++ b/freemius/includes/class-fs-logger.php @@ -0,0 +1,691 @@ +_id = $id; + + $bt = debug_backtrace(); + $caller = $bt[2]; + + if ( false !== strpos( $caller['file'], 'plugins' ) ) { + $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' ); + } else { + $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' ); + } + + if ( $on ) { + $this->on(); + } + if ( $echo ) { + $this->echo_on(); + } + } + + /** + * @param string $id + * @param bool $on + * @param bool $echo + * + * @return FS_Logger + */ + public static function get_logger( $id, $on = false, $echo = false ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_processID ) ) { + self::init(); + } + + if ( ! isset( self::$LOGGERS[ $id ] ) ) { + self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo ); + } + + return self::$LOGGERS[ $id ]; + } + + /** + * Initialize logging global info. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + private static function init() { + self::$_ownerName = function_exists( 'get_current_user' ) ? + get_current_user() : + 'unknown'; + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); + self::$_abspathLength = strlen( ABSPATH ); + self::$_processID = mt_rand( 0, 32000 ); + + // Process ID may be `false` on errors. + if ( ! is_numeric( self::$_processID ) ) { + self::$_processID = 0; + } + } + + private static function hook_footer() { + if ( self::$_HOOKED_FOOTER ) { + return; + } + + if ( is_admin() ) { + add_action( 'admin_footer', 'FS_Logger::dump', 100 ); + } else { + add_action( 'wp_footer', 'FS_Logger::dump', 100 ); + } + } + + function is_on() { + return $this->_on; + } + + function on() { + $this->_on = true; + + if ( ! function_exists( 'dbDelta' ) ) { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + } + + self::hook_footer(); + } + + function echo_on() { + $this->on(); + + $this->_echo = true; + } + + function is_echo_on() { + return $this->_echo; + } + + function get_id() { + return $this->_id; + } + + function get_file() { + return $this->_file_start; + } + + private function _log( &$message, $type = 'log', $wrapper ) { + if ( ! $this->is_on() ) { + return; + } + + $bt = debug_backtrace(); + $depth = $wrapper ? 3 : 2; + while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) { + $depth ++; + } + + $caller = $bt[ $depth ]; + + /** + * Retrieve the correct call file & line number from backtrace + * when logging from a wrapper method. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + if ( empty( $caller['line'] ) ) { + $depth --; + + while ( $depth >= 0 ) { + if ( ! empty( $bt[ $depth ]['line'] ) ) { + $caller['line'] = $bt[ $depth ]['line']; + $caller['file'] = $bt[ $depth ]['file']; + break; + } + } + } + + $log = array_merge( $caller, array( + 'cnt' => self::$CNT ++, + 'logger' => $this, + 'timestamp' => microtime( true ), + 'log_type' => $type, + 'msg' => $message, + ) ); + + if ( self::$_isStorageLoggingOn ) { + $this->db_log( $type, $message, self::$CNT, $caller ); + } + + self::$LOG[] = $log; + + if ( $this->is_echo_on() && ! Freemius::is_ajax() ) { + echo self::format_html( $log ) . "\n"; + } + } + + function log( $message, $wrapper = false ) { + $this->_log( $message, 'log', $wrapper ); + } + + function info( $message, $wrapper = false ) { + $this->_log( $message, 'info', $wrapper ); + } + + function warn( $message, $wrapper = false ) { + $this->_log( $message, 'warn', $wrapper ); + } + + function error( $message, $wrapper = false ) { + $this->_log( $message, 'error', $wrapper ); + } + + /** + * Log API error. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $api_result + * @param bool $wrapper + */ + function api_error( $api_result, $wrapper = false ) { + $message = ''; + if ( is_object( $api_result ) && + ! empty( $api_result->error ) && + ! empty( $api_result->error->message ) + ) { + $message = $api_result->error->message; + } else if ( is_object( $api_result ) ) { + $message = var_export( $api_result, true ); + } else if ( is_string( $api_result ) ) { + $message = $api_result; + } else if ( empty( $api_result ) ) { + $message = 'Empty API result.'; + } + + $message = 'API Error: ' . $message; + + $this->_log( $message, 'error', $wrapper ); + } + + function entrance( $message = '', $wrapper = false ) { + $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + function departure( $message = '', $wrapper = false ) { + $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + #-------------------------------------------------------------------------------- + #region Log Formatting + #-------------------------------------------------------------------------------- + + private static function format( $log, $show_type = true ) { + return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']'; + } + + private static function format_html( $log ) { + return '
    [' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . esc_html( $log['msg'] ) . '' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']
    '; + } + + #endregion + + static function dump() { + ?> + + + + prefix}fs_logger"; + + if ( $is_on ) { + /** + * Create logging table. + * + * NOTE: + * dbDelta must use KEY and not INDEX for indexes. + * + * @link https://core.trac.wordpress.org/ticket/2695 + */ + $result = $wpdb->query( "CREATE TABLE {$table} ( +`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, +`process_id` INT UNSIGNED NOT NULL, +`user_name` VARCHAR(64) NOT NULL, +`logger` VARCHAR(128) NOT NULL, +`log_order` INT UNSIGNED NOT NULL, +`type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log', +`message` TEXT NOT NULL, +`file` VARCHAR(256) NOT NULL, +`line` INT UNSIGNED NOT NULL, +`function` VARCHAR(256) NOT NULL, +`request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call', +`request_url` VARCHAR(1024) NOT NULL, +`created` DECIMAL(16, 6) NOT NULL, +PRIMARY KEY (`id`), +KEY `process_id` (`process_id` ASC), +KEY `process_logger` (`process_id` ASC, `logger` ASC), +KEY `function` (`function` ASC), +KEY `type` (`type` ASC))" ); + } else { + /** + * Drop logging table. + */ + $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" ); + } + + if ( false !== $result ) { + update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) ); + } + + return ( false !== $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $type + * @param string $message + * @param int $log_order + * @param array $caller + * + * @return false|int + */ + private function db_log( + &$type, + &$message, + &$log_order, + &$caller + ) { + global $wpdb; + + $request_type = 'call'; + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + $request_type = 'cron'; + } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + $request_type = 'ajax'; + } + + $request_url = WP_FS__IS_HTTP_REQUEST ? + $_SERVER['REQUEST_URI'] : + ''; + + return $wpdb->insert( + "{$wpdb->prefix}fs_logger", + array( + 'process_id' => self::$_processID, + 'user_name' => self::$_ownerName, + 'logger' => $this->_id, + 'log_order' => $log_order, + 'type' => $type, + 'request_type' => $request_type, + 'request_url' => $request_url, + 'message' => $message, + 'file' => isset( $caller['file'] ) ? + substr( $caller['file'], self::$_abspathLength ) : + '', + 'line' => $caller['line'], + 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'], + 'created' => microtime( true ), + ) + ); + } + + /** + * Persistent DB logger columns. + * + * @var array + */ + private static $_log_columns = array( + 'id', + 'process_id', + 'user_name', + 'logger', + 'log_order', + 'type', + 'message', + 'file', + 'line', + 'function', + 'request_type', + 'request_url', + 'created', + ); + + /** + * Create DB logs query. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * @param bool $escape_eol + * + * @return string + */ + private static function build_db_logs_query( + $filters = false, + $limit = 200, + $offset = 0, + $order = false, + $escape_eol = false + ) { + global $wpdb; + + $select = '*'; + + if ( $escape_eol ) { + $select = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $select .= ', '; + } + + if ( 'message' !== self::$_log_columns[ $i ] ) { + $select .= self::$_log_columns[ $i ]; + } else { + $select .= 'REPLACE(message , \'\n\', \' \') AS message'; + } + } + } + + $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; + if ( is_array( $filters ) ) { + $criteria = array(); + + if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) { + $filters['type'] = strtolower( $filters['type'] ); + + switch ( $filters['type'] ) { + case 'warn_error': + $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); + break; + case 'error': + case 'warn': + $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); + break; + case 'info': + default: + $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); + break; + } + } + + if ( ! empty( $filters['request_type'] ) ) { + $filters['request_type'] = strtolower( $filters['request_type'] ); + + if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) { + $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); + } + } + + if ( ! empty( $filters['file'] ) ) { + $criteria[] = array( + 'col' => 'file', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['file'] ), + ); + } + + if ( ! empty( $filters['function'] ) ) { + $criteria[] = array( + 'col' => 'function', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['function'] ), + ); + } + + if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) { + $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); + } + + if ( ! empty( $filters['logger'] ) ) { + $criteria[] = array( + 'col' => 'logger', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['logger'] ) . '%', + ); + } + + if ( ! empty( $filters['message'] ) ) { + $criteria[] = array( + 'col' => 'message', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['message'] ) . '%', + ); + } + + if ( 0 < count( $criteria ) ) { + $query .= "\nWHERE\n"; + + $first = true; + foreach ( $criteria as $c ) { + if ( ! $first ) { + $query .= "AND\n"; + } + + if ( is_array( $c['val'] ) ) { + $operator = 'IN'; + + for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) { + $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'"; + } + + $val = '(' . implode( ',', $c['val'] ) . ')'; + } else { + $operator = ! empty( $c['op'] ) ? $c['op'] : '='; + $val = "'" . esc_sql( $c['val'] ) . "'"; + } + + $query .= "`{$c['col']}` {$operator} {$val}\n"; + + $first = false; + } + } + } + + if ( ! is_array( $order ) ) { + $order = array( + 'col' => 'id', + 'order' => 'desc' + ); + } + + $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; + + return $query; + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return object[]|null + */ + public static function load_db_logs( + $filters = false, + $limit = 200, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order + ); + + return $wpdb->get_results( $query ); + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param string $filename + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return false|string File download URL or false on failure. + */ + public static function download_db_logs( + $filters = false, + $filename = '', + $limit = 10000, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order, + true + ); + + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}"; + + $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; + + $columns = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $columns .= ', '; + } + + $columns .= "'" . self::$_log_columns[ $i ] . "'"; + } + + $query = "SELECT {$columns} UNION ALL " . $query; + + $result = $wpdb->query( $query ); + + if ( false === $result ) { + return false; + } + + return rtrim( $upload_dir['url'], '/' ) . '/' . $filename; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $filename + * + * @return string + */ + public static function get_logs_download_url( $filename = '' ) { + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + + return rtrim( $upload_dir['url'], '/' ) . $filename; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-options.php b/freemius/includes/class-fs-options.php new file mode 100644 index 0000000..dcb9409 --- /dev/null +++ b/freemius/includes/class-fs-options.php @@ -0,0 +1,431 @@ +_id = $id; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); + } + + $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); + } + + /** + * Switch the context of the site level options manager. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param $blog_id + */ + function set_site_blog_context( $blog_id ) { + $this->_blog_id = $blog_id; + + $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $option + * @param mixed $default + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + * + * @return mixed + */ + function get_option( $option, $default = null, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + return $this->_network_options->get_option( $option, $default ); + } + + $site_options = $this->get_site_options( $network_level_or_blog_id ); + + return $site_options->get_option( $option, $default ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param string $option + * @param mixed $value + * @param bool $flush + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + */ + function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + $this->_network_options->set_option( $option, $value, $flush ); + } else { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->set_option( $option, $value, $flush ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * @param bool $flush + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + */ + function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + $this->_network_options->unset_option( $option, $flush ); + } else { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->unset_option( $option, $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $flush + * @param bool $network_level + */ + function load( $flush = false, $network_level = true ) { + if ( $this->_is_multisite && $network_level ) { + $this->_network_options->load( $flush ); + } else { + $this->_options->load( $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage. + */ + function store( $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + 0 == $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) + ) { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->store(); + } + + if ( $this->_is_multisite && + ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) + ) { + $this->_network_options->store(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null|bool $network_level_or_blog_id + * @param bool $flush + */ + function clear( $network_level_or_blog_id = null, $flush = false ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) || + is_numeric( $network_level_or_blog_id ) + ) { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->clear( $flush ); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_options->clear( $flush ); + } + } + + /** + * Migration script to the new storage data structure that is network compatible. + * + * IMPORTANT: + * This method should be executed only after it is determined if this is a network + * level compatible product activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + function migrate_to_network( $blog_id = 0 ) { + if ( ! $this->_is_multisite ) { + return; + } + + $updated = false; + + $site_options = $this->get_site_options( $blog_id ); + + $keys = $site_options->get_options_keys(); + + foreach ( $keys as $option ) { + if ( $this->is_site_option( $option ) || + // Don't move admin notices to the network storage. + in_array($option, array( + // Don't move admin notices to the network storage. + 'admin_notices', + // Don't migrate the module specific data, it will be migrated by the FS_Storage. + 'plugin_data', + 'theme_data', + )) + ) { + continue; + } + + $option_updated = false; + + // Migrate option to the network storage. + $site_option = $site_options->get_option( $option ); + + if ( ! $this->_network_options->has_option( $option ) ) { + // Option not set on the network level, so just set it. + $this->_network_options->set_option( $option, $site_option, false ); + + $option_updated = true; + } else { + // Option already set on the network level, so we need to merge it inelegantly. + $network_option = $this->_network_options->get_option( $option ); + + if ( is_array( $network_option ) && is_array( $site_option ) ) { + // Option is an array. + foreach ( $site_option as $key => $value ) { + if ( ! isset( $network_option[ $key ] ) ) { + $network_option[ $key ] = $value; + + $option_updated = true; + } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { + if ( empty( $network_option[ $key ] ) ) { + $network_option[ $key ] = $value; + + $option_updated = true; + } else if ( empty( $value ) ) { + // Do nothing. + } else { + reset($value); + $first_key = key($value); + if ( $value[$first_key] instanceof FS_Entity ) { + // Merge entities by IDs. + $network_entities_ids = array(); + foreach ( $network_option[ $key ] as $entity ) { + $network_entities_ids[ $entity->id ] = true; + } + + foreach ( $value as $entity ) { + if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { + $network_option[ $key ][] = $entity; + + $option_updated = true; + } + } + } + } + } + } + } + + if ( $option_updated ) { + $this->_network_options->set_option( $option, $network_option, false ); + } + } + + /** + * Remove the option from site level storage. + * + * IMPORTANT: + * The line below is intentionally commented since we want to preserve the option + * on the site storage level for "downgrade compatibility". Basically, if the user + * will downgrade to an older version of the plugin with the prev storage structure, + * it will continue working. + * + * @todo After a few releases we can remove this. + */ +// $site_options->unset_option($option, false); + + if ( $option_updated ) { + $updated = true; + } + } + + if ( ! $updated ) { + return; + } + + // Update network level storage. + $this->_network_options->store(); +// $site_options->store(); + } + + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * We don't want to load the map right away since it's not even needed in a non-MS environment. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function load_site_options_map() { + self::$_SITE_OPTIONS_MAP = array( + 'sites' => true, + 'theme_sites' => true, + 'unique_id' => true, + 'active_plugins' => true, + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * + * @return bool + */ + private function is_site_option( $option ) { + if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { + return false; + } + + if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { + self::load_site_options_map(); + } + + return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return FS_Option_Manager + */ + private function get_site_options( $blog_id = 0 ) { + if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { + return $this->_options; + } + + return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); + } + + /** + * Check if an option should be stored on the MS network storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + * + * @return bool + */ + private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + // Determine which storage to use based on the option. + return ! $this->is_site_option( $option ); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php new file mode 100644 index 0000000..4415310 --- /dev/null +++ b/freemius/includes/class-fs-plugin-updater.php @@ -0,0 +1,1488 @@ +get_id(); + + if ( ! isset( self::$_INSTANCES[ $key ] ) ) { + self::$_INSTANCES[ $key ] = new self( $freemius ); + } + + return self::$_INSTANCES[ $key ]; + } + + #endregion + + private function __construct( Freemius $freemius ) { + $this->_fs = $freemius; + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->filters(); + } + + /** + * Initiate required filters. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + */ + private function filters() { + // Override request for plugin information + add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 ); + + $this->add_transient_filters(); + + /** + * If user has the premium plugin's code but do NOT have an active license, + * encourage him to upgrade by showing that there's a new release, but instead + * of showing an update link, show upgrade link to the pricing page. + * + * @since 1.1.6 + * + */ + // WP 2.9+ + add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( + &$this, + 'catch_plugin_update_row' + ), 9 ); + add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( + &$this, + 'edit_and_echo_plugin_update_row' + ), 11, 2 ); + + add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) ); + + if ( ! WP_FS__IS_PRODUCTION_MODE ) { + add_filter( 'http_request_host_is_external', array( + $this, + 'http_request_host_is_external_filter' + ), 10, 3 ); + } + + if ( $this->_fs->is_premium() ) { + if ( ! $this->is_correct_folder_name() ) { + add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 ); + } + + add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 ); + add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); + + if ( ! $this->_fs->has_any_active_valid_license() ) { + add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + */ + function catch_plugin_information_dialog_contents() { + if ( + 'plugin-information' !== fs_request_get( 'tab', false ) || + $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) + ) { + return; + } + + add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 ); + + ob_start(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param string $hook_suffix + */ + function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { + if ( + 'plugin-information' !== fs_request_get( 'tab', false ) || + $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) + ) { + return; + } + + $license = $this->_fs->_get_license(); + + $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ? + $this->_fs->_get_subscription( $license->id ) : + null; + + $contents = ob_get_clean(); + + $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); + + if ( false !== $update_button_id_attribute_pos ) { + $update_button_start_pos = strrpos( + substr( $contents, 0, $update_button_id_attribute_pos ), + '', $update_button_id_attribute_pos ) + strlen( '' ) ); + + /** + * The part of the contents without the update button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents = substr( $contents, 0, $update_button_start_pos ); + + $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) ); + + /** + * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, + * the text will be "Renew license" and will link to the checkout page with the license's billing cycle + * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. + */ + $update_button = preg_replace( + '/(\)(.+)(\<\/a>)/is', + is_object( $license ) ? + sprintf( + '$1$3%s$5%s$7', + $this->_fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ), + fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) + ) : + sprintf( + '$1$3%s$5%s$7', + $this->_fs->pricing_url(), + fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) + ), + $update_button + ); + + /** + * Append the modified button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents .= $update_button; + + /** + * Append the remaining part of the contents after the update button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents .= substr( $contents, $update_button_end_pos ); + + $contents = $modified_contents; + } + + echo $contents; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function add_transient_filters() { + add_filter( 'pre_set_site_transient_update_plugins', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + + add_filter( 'pre_set_site_transient_update_themes', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function remove_transient_filters() { + remove_filter( 'pre_set_site_transient_update_plugins', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + + remove_filter( 'pre_set_site_transient_update_themes', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + } + + /** + * Capture plugin update row by turning output buffering. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + function catch_plugin_update_row() { + ob_start(); + } + + /** + * Overrides default update message format with "renew your license" message. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $file + * @param array $plugin_data + */ + function edit_and_echo_plugin_update_row( $file, $plugin_data ) { + $plugin_update_row = ob_get_clean(); + + $current = get_site_transient( 'update_plugins' ); + if ( ! isset( $current->response[ $file ] ) ) { + echo $plugin_update_row; + + return; + } + + $r = $current->response[ $file ]; + + $has_beta_update = $this->_fs->has_beta_update(); + + if ( $this->_fs->has_any_active_valid_license() ) { + if ( $has_beta_update ) { + /** + * Turn the "new version" text into "new Beta version". + * + * Sample input: + * There is a new version of Awesome Plugin available. update now. + * Output: + * There is a new Beta version of Awesome Plugin available. update now. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $plugin_update_row = preg_replace( + '/(\)(.+)(\.+\)/is', + ( + '$1' . + sprintf( + fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), + $has_beta_update ? + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ), + $this->_fs->get_plugin_title() + ) . + ' ' . + '$3' . + '$6' + ), + $plugin_update_row + ); + } + } else { + /** + * Turn the "new version" text into a link that opens the plugin information dialog when clicked and + * make the "View version x details" text link to the checkout page instead of opening the plugin + * information dialog when clicked. + * + * Sample input: + * There is a new version of Awesome Plugin available. update now. + * Output: + * There is a Buy a license now to access version x.y.z security & feature updates, and support. + * OR + * There is a Buy a license now to access version x.y.z security & feature updates, and support. + * + * @author Leo Fajardo (@leorw) + */ + $plugin_update_row = preg_replace( + '/(\)(.+)(\.+\)/is', + ( + '$1' . + sprintf( + fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), + sprintf( + '%s', + '$5', + $has_beta_update ? + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ) + ), + $this->_fs->get_plugin_title() + ) . + ' ' . + $this->_fs->version_upgrade_checkout_link( $r->new_version ) . + '$6' + ), + $plugin_update_row + ); + } + + if ( + $this->_fs->is_plugin() && + isset( $r->upgrade_notice ) && + strlen( trim( $r->upgrade_notice ) ) > 0 + ) { + $slug = $this->_fs->get_slug(); + + $upgrade_notice_html = sprintf( + '

    %3$s %4$s

    ', + $slug, + $this->_fs->get_module_type(), + fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ), + esc_html( $r->upgrade_notice ) + ); + + $plugin_update_row = str_replace( '
    ', '' . $upgrade_notice_html, $plugin_update_row ); + } + + echo $plugin_update_row; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + * + * @param array $prepared_themes + * + * @return array + */ + function change_theme_update_info_html( $prepared_themes ) { + $theme_basename = $this->_fs->get_plugin_basename(); + + if ( ! isset( $prepared_themes[ $theme_basename ] ) ) { + return $prepared_themes; + } + + $themes_update = get_site_transient( 'update_themes' ); + if ( ! isset( $themes_update->response[ $theme_basename ] ) || + empty( $themes_update->response[ $theme_basename ]['package'] ) + ) { + return $prepared_themes; + } + + $prepared_themes[ $theme_basename ]['update'] = preg_replace( + '/(\)(.+)(\)/is', + '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) . + '$4', + $prepared_themes[ $theme_basename ]['update'] + ); + + // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page. + $prepared_themes[ $theme_basename ]['hasPackage'] = false; + + return $prepared_themes; + } + + /** + * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip. + * During development mode we want to be able updating plugin versions via our localhost repository. This + * filter white-list all domains including "api.freemius". + * + * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/ + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool $allow + * @param string $host + * @param string $url + * + * @return bool + */ + function http_request_host_is_external_filter( $allow, $host, $url ) { + return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow; + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update api just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param object $transient_data Update array build by WordPress. + * + * @return object Modified update array with custom plugin data. + */ + function pre_set_site_transient_update_plugins_filter( $transient_data ) { + $this->_logger->entrance(); + + /** + * "plugins" or "themes". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $module_type = $this->_fs->get_module_type() . 's'; + + /** + * Ensure that we don't mix plugins update info with themes update info. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) { + return $transient_data; + } + + if ( empty( $transient_data ) || + defined( 'WP_FS__UNINSTALL_MODE' ) + ) { + return $transient_data; + } + + if ( ! isset( $this->_update_details ) ) { + // Get plugin's newest update. + $new_version = $this->_fs->get_update( + false, + fs_request_get_bool( 'force-check' ), + WP_FS__TIME_24_HOURS_IN_SEC / 24, + $this->_fs->get_plugin_version() + ); + + $this->_update_details = false; + + if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) { + $this->_logger->log( 'Found newer plugin version ' . $new_version->version ); + + /** + * Cache plugin details locally since set_site_transient( 'update_plugins' ) + * called multiple times and the non wp.org plugins are filtered after the + * call to .org. + * + * @since 1.1.8.1 + */ + $this->_update_details = $this->get_update_details( $new_version ); + } + } + + if ( is_object( $this->_update_details ) ) { + if ( ! isset( $transient_data->response ) ) { + $transient_data->response = array(); + } + + // Add plugin to transient data. + $transient_data->response[ $this->_fs->premium_plugin_basename() ] = $this->_fs->is_plugin() ? + $this->_update_details : + (array) $this->_update_details; + } else if ( isset( $transient_data->response ) ) { + /** + * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + unset( $transient_data->response[ $this->_fs->premium_plugin_basename() ] ); + } + + $slug = $this->_fs->get_slug(); + + if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) { + if ( ! isset( $this->_translation_updates ) ) { + $this->_translation_updates = array(); + + if ( current_user_can( 'update_languages' ) ) { + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug ); + if ( ! empty( $translation_updates ) ) { + $this->_translation_updates = $translation_updates; + } + } + } + + if ( ! empty( $this->_translation_updates ) ) { + $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ? + $transient_data->translations : + array(); + + $current_plugin_translation_updates_map = array(); + foreach ( $all_translation_updates as $key => $translation_update ) { + if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) { + $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update; + unset( $all_translation_updates[ $key ] ); + } + } + + foreach ( $this->_translation_updates as $translation_update ) { + $lang = $translation_update['language']; + if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) || + version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' ) + ) { + $current_plugin_translation_updates_map[ $lang ] = $translation_update; + } + } + + $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) ); + } + } + + return $transient_data; + } + + /** + * Get module's required data for the updates mechanism. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_Tag $new_version + * + * @return object + */ + function get_update_details( FS_Plugin_Tag $new_version ) { + $update = new stdClass(); + $update->slug = $this->_fs->get_slug(); + $update->new_version = $new_version->version; + $update->url = WP_FS__ADDRESS; + $update->package = $new_version->url; + $update->tested = $new_version->tested_up_to_version; + $update->requires = $new_version->requires_platform_version; + + $icon = $this->_fs->get_local_icon_url(); + + if ( ! empty( $icon ) ) { + $update->icons = array( +// '1x' => $icon, +// '2x' => $icon, + 'default' => $icon, + ); + } + + if ( $this->_fs->is_premium() ) { + $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false ); + + if ( + isset( $latest_tag->readme ) && + isset( $latest_tag->readme->upgrade_notice ) && + ! empty( $latest_tag->readme->upgrade_notice ) + ) { + $update->upgrade_notice = $latest_tag->readme->upgrade_notice; + } + } + + $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename(); + + return $update; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Plugin_Tag $new_version + * + * @return bool + */ + private function is_new_version_premium( FS_Plugin_Tag $new_version ) { + $query_str = parse_url( $new_version->url, PHP_URL_QUERY ); + if ( empty( $query_str ) ) { + return false; + } + + parse_str( $query_str, $params ); + + return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] ); + } + + /** + * Update the updates transient with the module's update information. + * + * This method is required for multisite environment. + * If a module is site activated (not network) and not on the main site, + * the module will NOT be executed on the network level, therefore, the + * custom updates logic will not be executed as well, so unless we force + * the injection of the update into the updates transient, premium updates + * will not work. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_Tag $new_version + */ + function set_update_data( FS_Plugin_Tag $new_version ) { + $this->_logger->entrance(); + + if ( ! $this->is_new_version_premium( $new_version ) ) { + return; + } + + $transient_key = "update_{$this->_fs->get_module_type()}s"; + + $transient_data = get_site_transient( $transient_key ); + + $transient_data = is_object( $transient_data ) ? + $transient_data : + new stdClass(); + + // Alias. + $basename = $this->_fs->get_plugin_basename(); + $is_plugin = $this->_fs->is_plugin(); + + if ( ! isset( $transient_data->response ) || + ! is_array( $transient_data->response ) + ) { + $transient_data->response = array(); + } else if ( ! empty( $transient_data->response[ $basename ] ) ) { + $version = $is_plugin ? + ( ! empty( $transient_data->response[ $basename ]->new_version ) ? + $transient_data->response[ $basename ]->new_version : + null + ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ? + $transient_data->response[ $basename ]['new_version'] : + null + ); + + if ( $version == $new_version->version ) { + // The update data is already set. + return; + } + } + + // Remove the added filters. + $this->remove_transient_filters(); + + $this->_update_details = $this->get_update_details( $new_version ); + + // Set update data in transient. + $transient_data->response[ $basename ] = $is_plugin ? + $this->_update_details : + (array) $this->_update_details; + + if ( ! isset( $transient_data->checked ) || + ! is_array( $transient_data->checked ) + ) { + $transient_data->checked = array(); + } + + // Flag the module as if it was already checked. + $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version(); + $transient_data->last_checked = time(); + + set_site_transient( $transient_key, $transient_data ); + + $this->add_transient_filters(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function delete_update_data() { + $this->_logger->entrance(); + + $transient_key = "update_{$this->_fs->get_module_type()}s"; + + $transient_data = get_site_transient( $transient_key ); + + // Alias + $basename = $this->_fs->get_plugin_basename(); + + if ( ! is_object( $transient_data ) || + ! isset( $transient_data->response ) || + ! is_array( $transient_data->response ) || + empty( $transient_data->response[ $basename ] ) + ) { + return; + } + + unset( $transient_data->response[ $basename ] ); + + // Remove the added filters. + $this->remove_transient_filters(); + + set_site_transient( $transient_key, $transient_data ); + + $this->add_transient_filters(); + } + + /** + * Try to fetch plugin's info from .org repository. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param string $action + * @param object $args + * + * @return bool|mixed + */ + static function _fetch_plugin_info_from_repository( $action, $args ) { + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/'; + if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { + $url = set_url_scheme( $url, 'https' ); + } + + $args = array( + 'timeout' => 15, + 'body' => array( + 'action' => $action, + 'request' => serialize( $args ) + ) + ); + + $request = wp_remote_post( $url, $args ); + + if ( is_wp_error( $request ) ) { + return false; + } + + $res = maybe_unserialize( wp_remote_retrieve_body( $request ) ); + + if ( ! is_object( $res ) && ! is_array( $res ) ) { + return false; + } + + return $res; + } + + /** + * Fetches module translation updates from wordpress.org. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.2 + * + * @param string $module_type + * @param string $slug + * + * @return array|null + */ + private function fetch_wp_org_module_translation_updates( $module_type, $slug ) { + $plugin_data = $this->_fs->get_plugin_data(); + + $locales = array_values( get_available_languages() ); + $locales = apply_filters( "{$module_type}_update_check_locales", $locales ); + $locales = array_unique( $locales ); + + $plugin_basename = $this->_fs->get_plugin_basename(); + if ( 'themes' === $module_type ) { + $plugin_basename = $slug; + } + + global $wp_version; + + $request_args = array( + 'timeout' => 15, + 'body' => array( + "{$module_type}" => json_encode( + array( + "{$module_type}" => array( + $plugin_basename => array( + 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ), + 'Author' => $plugin_data['Author'], + ) + ) + ) + ), + 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ), + 'locale' => json_encode( $locales ) + ), + 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) ) + ); + + $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/"; + if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { + $url = set_url_scheme( $url, 'https' ); + } + + $raw_response = Freemius::safe_remote_post( + $url, + $request_args, + WP_FS__TIME_24_HOURS_IN_SEC, + WP_FS__TIME_12_HOURS_IN_SEC, + false + ); + + if ( is_wp_error( $raw_response ) ) { + return null; + } + + $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); + + if ( ! is_array( $response ) ) { + return null; + } + + if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) { + return null; + } + + return $response['translations']; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.2 + * + * @param string $module_type + * @param string $slug + * + * @return array + */ + private function get_installed_translations( $module_type, $slug ) { + if ( function_exists( 'wp_get_installed_translations' ) ) { + return wp_get_installed_translations( $module_type ); + } + + $dir = "/{$module_type}"; + + if ( ! is_dir( WP_LANG_DIR . $dir ) ) + return array(); + + $files = scandir( WP_LANG_DIR . $dir ); + if ( ! $files ) + return array(); + + $language_data = array(); + + foreach ( $files as $file ) { + if ( 0 !== strpos( $file, $slug ) ) { + continue; + } + + if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) { + continue; + } + + if ( substr( $file, -3 ) !== '.po' ) { + continue; + } + + if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { + continue; + } + + if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { + continue; + } + + list( , $textdomain, $language ) = $match; + + if ( '' === $textdomain ) { + $textdomain = 'default'; + } + + $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" ); + } + + return $language_data; + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param object $data + * @param string $action + * @param mixed $args + * + * @return object + */ + function plugins_api_filter( $data, $action = '', $args = null ) { + $this->_logger->entrance(); + + if ( ( 'plugin_information' !== $action ) || + ! isset( $args->slug ) + ) { + return $data; + } + + $addon = false; + $is_addon = false; + $addon_version = false; + + if ( $this->_fs->get_slug() !== $args->slug ) { + $addon = $this->_fs->get_addon_by_slug( $args->slug ); + + if ( ! is_object( $addon ) ) { + return $data; + } + + if ( $this->_fs->is_addon_activated( $addon->id ) ) { + $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version(); + } else if ( $this->_fs->is_addon_installed( $addon->id ) ) { + $addon_plugin_data = get_plugin_data( + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ), + false, + false + ); + + if ( ! empty( $addon_plugin_data ) ) { + $addon_version = $addon_plugin_data['Version']; + } + } + + $is_addon = true; + } + + $plugin_in_repo = false; + if ( ! $is_addon ) { + // Try to fetch info from .org repository. + $data = self::_fetch_plugin_info_from_repository( $action, $args ); + + $plugin_in_repo = ( false !== $data ); + } + + if ( ! $plugin_in_repo ) { + $data = $args; + + // Fetch as much as possible info from local files. + $plugin_local_data = $this->_fs->get_plugin_data(); + $data->name = $plugin_local_data['Name']; + $data->author = $plugin_local_data['Author']; + $data->sections = array( + 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.', + ); + + // @todo Store extra plugin info on Freemius or parse readme.txt markup. + /*$info = $this->_fs->get_api_site_scope()->call('/information.json'); + +if ( !isset($info->error) ) { + $data = $info; +}*/ + } + + $plugin_version = $is_addon ? + $addon_version : + $this->_fs->get_plugin_version(); + + // Get plugin's newest update. + $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version ); + + if ( ! is_object( $new_version ) || empty( $new_version->version ) ) { + $data->version = $plugin_version; + } else { + if ( $is_addon ) { + $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); + $data->slug = $addon->slug; + $data->url = WP_FS__ADDRESS; + $data->package = $new_version->url; + } + + if ( ! $plugin_in_repo ) { + $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created; + $data->requires = $new_version->requires_platform_version; + $data->tested = $new_version->tested_up_to_version; + } + + $data->version = $new_version->version; + $data->download_link = $new_version->url; + + if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) { + $new_version_readme_data = $new_version->readme; + if ( isset( $new_version_readme_data->sections ) ) { + $new_version_readme_data->sections = (array) $new_version_readme_data->sections; + } else { + $new_version_readme_data->sections = array(); + } + + if ( isset( $data->sections ) ) { + if ( isset( $data->sections['screenshots'] ) ) { + $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots']; + } + + if ( isset( $data->sections['reviews'] ) ) { + $new_version_readme_data->sections['reviews'] = $data->sections['reviews']; + } + } + + if ( isset( $new_version_readme_data->banners ) ) { + $new_version_readme_data->banners = (array) $new_version_readme_data->banners; + } else if ( isset( $data->banners ) ) { + $new_version_readme_data->banners = $data->banners; + } + + $wp_org_sections = array( + 'author', + 'author_profile', + 'rating', + 'ratings', + 'num_ratings', + 'support_threads', + 'support_threads_resolved', + 'active_installs', + 'added', + 'homepage' + ); + + foreach ( $wp_org_sections as $wp_org_section ) { + if ( isset( $data->{$wp_org_section} ) ) { + $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section}; + } + } + + $data = $new_version_readme_data; + } + } + + return $data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param number|bool $addon_id + * @param bool|string $newer_than Since 2.2.1 + * @param bool|string $fetch_readme Since 2.2.1 + * + * @return object + */ + private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) { + return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme ); + } + + /** + * Checks if a given basename has a matching folder name + * with the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + private function is_correct_folder_name() { + return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) ); + } + + /** + * This is a special after upgrade handler for migrating modules + * that didn't use the '-premium' suffix folder structure before + * the migration. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $response Install response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * @param array $result Installation result data. + * + * @return bool + */ + function _maybe_update_folder_name( $response, $hook_extra, $result ) { + $basename = $this->_fs->get_plugin_basename(); + + if ( true !== $response || + empty( $hook_extra ) || + empty( $hook_extra['plugin'] ) || + $basename !== $hook_extra['plugin'] + ) { + return $response; + } + + $active_plugins_basenames = get_option( 'active_plugins' ); + + foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) { + if ( $basename === $active_plugin_basename ) { + // Get filename including extension. + $filename = basename( $basename ); + + $new_basename = plugin_basename( + trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) . + $filename + ); + + // Verify that the expected correct path exists. + if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) { + // Override active plugin name. + $active_plugins_basenames[ $key ] = $new_basename; + update_option( 'active_plugins', $active_plugins_basenames ); + } + + break; + } + } + + return $response; + } + + #---------------------------------------------------------------------------------- + #region Auto Activation + #---------------------------------------------------------------------------------- + + /** + * Installs and active a plugin when explicitly requested that from a 3rd party service. + * + * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin. + * + * @link http://tgmpluginactivation.com/ + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ + * + * @uses WP_Filesystem + * @uses WP_Error + * @uses WP_Upgrader + * @uses Plugin_Upgrader + * @uses Plugin_Installer_Skin + * @uses Plugin_Upgrader_Skin + * + * @param number|bool $plugin_id + * + * @return array + */ + function install_and_activate_plugin( $plugin_id = false ) { + if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid plugin ID. + return array( + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ); + } + + $is_addon = false; + if ( FS_Plugin::is_valid_id( $plugin_id ) && + $plugin_id != $this->_fs->get_id() + ) { + $addon = $this->_fs->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + return array( + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ); + } + + $slug = $addon->slug; + $premium_slug = $addon->premium_slug; + $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); + + $is_addon = true; + } else { + $slug = $this->_fs->get_slug(); + $premium_slug = $this->_fs->get_premium_slug(); + $title = $this->_fs->get_plugin_title() . + ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' ); + } + + if ( $this->is_premium_plugin_active( $plugin_id ) ) { + // Premium version already activated. + return array( + 'message' => $is_addon ? + $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) : + $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), + 'code' => 'premium_installed', + ); + } + + $latest_version = $this->get_latest_download_details( $plugin_id, false, false ); + $target_folder = $premium_slug; + + // Prep variables for Plugin_Installer_Skin class. + $extra = array(); + $extra['slug'] = $target_folder; + $source = $latest_version->url; + $api = null; + + $install_url = add_query_arg( + array( + 'action' => 'install-plugin', + 'plugin' => urlencode( $slug ), + ), + 'update.php' + ); + + if ( ! class_exists( 'Plugin_Upgrader', false ) ) { + // Include required resources for the installation. + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + } + + $skin_args = array( + 'type' => 'web', + 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ), + 'url' => esc_url_raw( $install_url ), + 'nonce' => 'install-plugin_' . $slug, + 'plugin' => '', + 'api' => $api, + 'extra' => $extra, + ); + +// $skin = new Automatic_Upgrader_Skin( $skin_args ); +// $skin = new Plugin_Installer_Skin( $skin_args ); + $skin = new WP_Ajax_Upgrader_Skin( $skin_args ); + + // Create a new instance of Plugin_Upgrader. + $upgrader = new Plugin_Upgrader( $skin ); + + // Perform the action and install the plugin from the $source urldecode(). + add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); + + $install_result = $upgrader->install( $source ); + + remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 ); + + if ( is_wp_error( $install_result ) ) { + return array( + 'message' => $install_result->get_error_message(), + 'code' => $install_result->get_error_code(), + ); + } elseif ( is_wp_error( $skin->result ) ) { + return array( + 'message' => $skin->result->get_error_message(), + 'code' => $skin->result->get_error_code(), + ); + } elseif ( $skin->get_errors()->get_error_code() ) { + return array( + 'message' => $skin->get_error_messages(), + 'code' => 'unknown', + ); + } elseif ( is_null( $install_result ) ) { + global $wp_filesystem; + + $error_code = 'unable_to_connect_to_filesystem'; + $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && + is_wp_error( $wp_filesystem->errors ) && + $wp_filesystem->errors->get_error_code() + ) { + $error_message = $wp_filesystem->errors->get_error_message(); + } + + return array( + 'message' => $error_message, + 'code' => $error_code, + ); + } + + // Grab the full path to the main plugin's file. + $plugin_activate = $upgrader->plugin_info(); + + // Try to activate the plugin. + $activation_result = $this->try_activate_plugin( $plugin_activate ); + + if ( is_wp_error( $activation_result ) ) { + return array( + 'message' => $activation_result->get_error_message(), + 'code' => $activation_result->get_error_code(), + ); + } + + return $skin->get_upgrade_messages(); + } + + /** + * Tries to activate a plugin. If fails, returns the error. + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @param string $file_path Path within wp-plugins/ to main plugin file. + * This determines the styling of the output messages. + * + * @return bool|WP_Error + */ + protected function try_activate_plugin( $file_path ) { + $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() ); + + return is_wp_error( $activate ) ? + $activate : + true; + } + + /** + * Check if a premium module version is already active. + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @param number|bool $plugin_id + * + * @return bool + */ + private function is_premium_plugin_active( $plugin_id = false ) { + if ( $plugin_id != $this->_fs->get_id() ) { + return $this->_fs->is_addon_activated( $plugin_id, true ); + } + + return is_plugin_active( $this->_fs->premium_plugin_basename() ); + } + + /** + * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param bool|WP_Error $response Response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * + * @return bool|WP_Error + */ + static function _store_basename_for_source_adjustment( $response, $hook_extra ) { + if ( isset( $hook_extra['plugin'] ) ) { + self::$_upgrade_basename = $hook_extra['plugin']; + } else if ( isset( $hook_extra['theme'] ) ) { + self::$_upgrade_basename = $hook_extra['theme']; + } else { + self::$_upgrade_basename = null; + } + + return $response; + } + + /** + * Adjust the plugin directory name if necessary. + * Assumes plugin has a folder (not a single file plugin). + * + * The final destination directory of a plugin is based on the subdirectory name found in the + * (un)zipped source. In some cases this subdirectory name is not the same as the expected + * slug and the plugin will not be recognized as installed. This is fixed by adjusting + * the temporary unzipped source subdirectory name to the expected plugin slug. + * + * @author Vova Feldman + * @since 1.2.1.7 + * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated. + * + * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/. + * @param string $remote_source Path to upgrade/zip-file-name.tmp. + * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin. + * + * @return string|WP_Error + */ + static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) { + if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) { + return $source; + } + + $basename = self::$_upgrade_basename; + $is_theme = false; + + // Figure out what the slug is supposed to be. + if ( isset( $upgrader->skin->options['extra'] ) ) { + // Set by the auto-install logic. + $desired_slug = $upgrader->skin->options['extra']['slug']; + } else if ( ! empty( $basename ) ) { + /** + * If it doesn't end with ".php", it's a theme. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $is_theme = ( ! fs_ends_with( $basename, '.php' ) ); + + $desired_slug = ( ! $is_theme ) ? + dirname( $basename ) : + // Theme slug + $basename; + } else { + // Can't figure out the desired slug, stop the execution. + return $source; + } + + if ( is_multisite() ) { + /** + * If we are running in a multisite environment and the product is not network activated, + * the instance will not exist anyway. Therefore, try to update the source if necessary + * regardless if the Freemius instance of the product exists or not. + * + * @author Vova Feldman + */ + } else if ( ! empty( $basename ) ) { + $fs = Freemius::get_instance_by_file( + $basename, + $is_theme ? + WP_FS__MODULE_TYPE_THEME : + WP_FS__MODULE_TYPE_PLUGIN + ); + + if ( ! is_object( $fs ) ) { + /** + * If the Freemius instance does not exist on a non-multisite network environment, it means that: + * 1. The product is not powered by Freemius; OR + * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + return $source; + } + } + + $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) ); + + if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) { + $from_path = untrailingslashit( $source ); + $to_path = trailingslashit( $remote_source ) . $desired_slug; + + if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) { + return trailingslashit( $to_path ); + } + + return new WP_Error( + 'rename_failed', + fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ), + array( + 'found' => $subdir_name, + 'expected' => $desired_slug + ) + ); + } + + return $source; + } + + #endregion + } diff --git a/freemius/includes/class-fs-security.php b/freemius/includes/class-fs-security.php new file mode 100644 index 0000000..4535aa2 --- /dev/null +++ b/freemius/includes/class-fs-security.php @@ -0,0 +1,85 @@ +id . + $entity->secret_key . + $entity->public_key . + $action + ); + } + + /** + * @param \FS_Scope_Entity $entity + * @param int|bool $timestamp + * @param string $action + * + * @return array + */ + function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) { + if ( false === $timestamp ) { + $timestamp = time(); + } + + return array( + 's_ctx_type' => $entity->get_type(), + 's_ctx_id' => $entity->id, + 's_ctx_ts' => $timestamp, + 's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ), + ); + } + } diff --git a/freemius/includes/class-fs-storage.php b/freemius/includes/class-fs-storage.php new file mode 100644 index 0000000..22dff1d --- /dev/null +++ b/freemius/includes/class-fs-storage.php @@ -0,0 +1,526 @@ +_module_type = $module_type; + $this->_module_slug = $slug; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true ); + } + + $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id ); + } + + /** + * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values + * are retrieved/stored from/into the storage. + * + * @author Leo Fajardo (@leorw) + * + * @param bool $is_network_active + * @param bool $is_delegated_connection + */ + function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { + $this->_is_network_active = $is_network_active; + $this->_is_delegated_connection = $is_delegated_connection; + } + + /** + * Switch the context of the site level storage manager. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + function set_site_blog_context( $blog_id ) { + $this->_storage = $this->get_site_storage( $blog_id ); + $this->_blog_id = $blog_id; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param mixed $value + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * @param bool $flush + */ + function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + $this->_network_storage->store( $key, $value, $flush ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->store( $key, $value, $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param bool $store + * @param string[] $exceptions Set of keys to keep and not clear. + * @param int|null|bool $network_level_or_blog_id + */ + function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) || + is_numeric( $network_level_or_blog_id ) + ) { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->clear_all( $store, $exceptions ); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_storage->clear_all( $store, $exceptions ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param bool $store + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + */ + function remove( $key, $store = true, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + $this->_network_storage->remove( $key, $store ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->remove( $key, $store ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param mixed $default + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * + * @return mixed + */ + function get( $key, $default = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + return $this->_network_storage->get( $key, $default ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + + return $storage->get( $key, $default ); + } + } + + /** + * Multisite activated: + * true: Save network storage. + * int: Save site specific storage. + * false|0: Save current site storage. + * null: Save network and current site storage. + * Site level activated: + * Save site storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool|int|null $network_level_or_blog_id + */ + function save( $network_level_or_blog_id = null ) { + if ( $this->_is_network_active && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_storage->save(); + } + + if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->save(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_module_slug() { + return $this->_module_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_module_type() { + return $this->_module_type; + } + + /** + * Migration script to the new storage data structure that is network compatible. + * + * IMPORTANT: + * This method should be executed only after it is determined if this is a network + * level compatible product activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + if ( ! $this->_is_multisite ) { + return; + } + + $updated = false; + + if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { + self::load_network_options_map(); + } + + foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { + if ( ! $this->is_multisite_option( $option ) ) { + continue; + } + + if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { + // Migrate option to the network storage. + $this->_network_storage->store( $option, $this->_storage->{$option}, false ); + + /** + * Remove the option from site level storage. + * + * IMPORTANT: + * The line below is intentionally commented since we want to preserve the option + * on the site storage level for "downgrade compatibility". Basically, if the user + * will downgrade to an older version of the plugin with the prev storage structure, + * it will continue working. + * + * @todo After a few releases we can remove this. + */ +// $this->_storage->remove($option, false); + + $updated = true; + } + } + + if ( ! $updated ) { + return; + } + + // Update network level storage. + $this->_network_storage->save(); +// $this->_storage->save(); + } + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * We don't want to load the map right away since it's not even needed in a non-MS environment. + * + * Example: + * array( + * 'option1' => 0, // Means that the option should always be stored on the network level. + * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. + * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. + * 'option3' => 3, // Means that the option should always be stored on the site level. + * ) + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function load_network_options_map() { + self::$_NETWORK_OPTIONS_MAP = array( + // Network level options. + 'affiliate_application_data' => 0, + 'beta_data' => 0, + 'connectivity_test' => 0, + 'handle_gdpr_admin_notice' => 0, + 'has_trial_plan' => 0, + 'install_sync_timestamp' => 0, + 'install_sync_cron' => 0, + 'is_anonymous_ms' => 0, + 'is_network_activated' => 0, + 'is_on' => 0, + 'is_plugin_new_install' => 0, + 'network_install_blog_id' => 0, + 'pending_sites_info' => 0, + 'plugin_last_version' => 0, + 'plugin_main_file' => 0, + 'plugin_version' => 0, + 'sdk_downgrade_mode' => 0, + 'sdk_last_version' => 0, + 'sdk_upgrade_mode' => 0, + 'sdk_version' => 0, + 'sticky_optin_added_ms' => 0, + 'subscriptions' => 0, + 'sync_timestamp' => 0, + 'sync_cron' => 0, + 'was_plugin_loaded' => 0, + 'network_user_id' => 0, + 'plugin_upgrade_mode' => 0, + 'plugin_downgrade_mode' => 0, + 'is_network_connected' => 0, + /** + * Special flag that is used when a super-admin upgrades to the new version of the SDK that + * supports network level integration, when the connection decision wasn't made for all of the + * sites in the network. + */ + 'is_network_activation' => 0, + + // When network activated, then network level. + 'install_timestamp' => 1, + 'prev_is_premium' => 1, + 'require_license_activation' => 1, + + // If not network activated OR delegated, then site level. + 'activation_timestamp' => 2, + 'prev_user_id' => 2, + 'sticky_optin_added' => 2, + 'uninstall_reason' => 2, + 'is_pending_activation' => 2, + 'pending_license_key' => 2, + + // Site level options. + 'is_anonymous' => 3, + ); + } + + /** + * This method will and should only be executed when is_multisite() is true. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $key + * + * @return bool|mixed + */ + private function is_multisite_option( $key ) { + if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { + self::load_network_options_map(); + } + + if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) { + // Option not found -> use site level storage. + return false; + } + + if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Option found and set to always use the network level storage on a multisite. + return true; + } + + if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Option found and set to always use the site level storage on a multisite. + return false; + } + + if ( ! $this->_is_network_active ) { + return false; + } + + if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Network activated. + return true; + } + + if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) { + // Network activated and not delegated. + return true; + } + + return false; + } + + /** + * @author Leo Fajardo + * + * @param string $key + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * + * @return bool + */ + private function should_use_network_storage( $key, $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + // Determine which storage to use based on the option. + return $this->is_multisite_option( $key ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return \FS_Key_Value_Storage + */ + private function get_site_storage( $blog_id = 0 ) { + if ( ! is_numeric( $blog_id ) || + $blog_id == $this->_blog_id || + 0 == $blog_id + ) { + return $this->_storage; + } + + return FS_Key_Value_Storage::instance( + $this->_module_type . '_data', + $this->_storage->get_secondary_id(), + $blog_id + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Magic methods + #-------------------------------------------------------------------------------- + + function __set( $k, $v ) { + if ( $this->should_use_network_storage( $k ) ) { + $this->_network_storage->{$k} = $v; + } else { + $this->_storage->{$k} = $v; + } + } + + function __isset( $k ) { + return $this->should_use_network_storage( $k ) ? + isset( $this->_network_storage->{$k} ) : + isset( $this->_storage->{$k} ); + } + + function __unset( $k ) { + if ( $this->should_use_network_storage( $k ) ) { + unset( $this->_network_storage->{$k} ); + } else { + unset( $this->_storage->{$k} ); + } + } + + function __get( $k ) { + return $this->should_use_network_storage( $k ) ? + $this->_network_storage->{$k} : + $this->_storage->{$k}; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-user-lock.php b/freemius/includes/class-fs-user-lock.php new file mode 100644 index 0000000..842cbba --- /dev/null +++ b/freemius/includes/class-fs-user-lock.php @@ -0,0 +1,126 @@ +_wp_user_id = Freemius::get_current_wp_user_id(); + $this->_thread_id = mt_rand( 0, 32000 ); + } + + + /** + * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param int $expiration + * + * @return bool TRUE if successfully acquired lock. + */ + function try_lock( $expiration = 0 ) { + if ( $this->is_locked() ) { + // Already locked. + return false; + } + + set_site_transient( "locked_{$this->_wp_user_id}", $this->_thread_id, $expiration ); + + if ( $this->has_lock() ) { + set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); + + return true; + } + + return false; + } + + /** + * Acquire lock regardless if it's already acquired by another locker or not. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param int $expiration + */ + function lock( $expiration = 0 ) { + set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); + } + + /** + * Checks if lock is currently acquired. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + function is_locked() { + return ( false !== get_site_transient( "locked_{$this->_wp_user_id}" ) ); + } + + /** + * Unlock the lock. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + function unlock() { + delete_site_transient( "locked_{$this->_wp_user_id}" ); + } + + /** + * Checks if lock is currently acquired by the current locker. + * + * @return bool + */ + private function has_lock() { + return ( $this->_thread_id == get_site_transient( "locked_{$this->_wp_user_id}" ) ); + } + } \ No newline at end of file diff --git a/freemius/includes/customizer/class-fs-customizer-support-section.php b/freemius/includes/customizer/class-fs-customizer-support-section.php new file mode 100644 index 0000000..e84b535 --- /dev/null +++ b/freemius/includes/customizer/class-fs-customizer-support-section.php @@ -0,0 +1,102 @@ +register_section_type( 'FS_Customizer_Support_Section' ); + + parent::__construct( $manager, $id, $args ); + } + + /** + * The type of customize section being rendered. + * + * @since 1.0.0 + * @access public + * @var string + */ + public $type = 'freemius-support-section'; + + /** + * @var Freemius + */ + public $fs = null; + + /** + * Add custom parameters to pass to the JS via JSON. + * + * @since 1.0.0 + */ + public function json() { + $json = parent::json(); + + $is_contact_visible = $this->fs->is_page_visible( 'contact' ); + $is_support_visible = $this->fs->is_page_visible( 'support' ); + + $json['theme_title'] = $this->fs->get_plugin_name(); + + if ( $is_contact_visible && $is_support_visible ) { + $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); + } + + if ( $is_contact_visible ) { + $json['contact'] = array( + 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), + 'url' => $this->fs->contact_url(), + ); + } + + if ( $is_support_visible ) { + $json['support'] = array( + 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), + 'url' => $this->fs->get_support_forum_url() + ); + } + + return $json; + } + + /** + * Outputs the Underscore.js template. + * + * @since 1.0.0 + */ + protected function render_template() { + ?> +
  • +

    + {{ data.theme_title }} + <# if ( data.contact && data.support ) { #> +
    + <# } #> + <# if ( data.contact ) { #> + {{ data.contact.label }} + <# } #> + <# if ( data.support ) { #> + {{ data.support.label }} + <# } #> + <# if ( data.contact && data.support ) { #> +
    + <# } #> +

    +
  • + register_control_type( 'FS_Customizer_Upsell_Control' ); + + parent::__construct( $manager, $id, $args ); + } + + /** + * Enqueue resources for the control. + */ + public function enqueue() { + fs_enqueue_local_style( 'fs_customizer', 'customizer.css' ); + } + + /** + * Json conversion + */ + public function to_json() { + $pricing_cta = esc_html( $this->fs->get_pricing_cta_label() ) . '  ' . ( is_rtl() ? '←' : '➤' ); + + parent::to_json(); + + $this->json['button_text'] = $pricing_cta; + $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? + $this->fs->get_trial_url() : + $this->fs->get_upgrade_url(); + + // Load features. + $pricing = $this->fs->get_api_plugin_scope()->get( $this->fs->add_show_pending( "pricing.json" ) ); + + if ( $this->fs->is_api_result_object( $pricing, 'plans' ) ) { + // Add support features. + if ( is_array( $pricing->plans ) && 0 < count( $pricing->plans ) ) { + $support_features = array( + 'kb' => 'Help Center', + 'forum' => 'Support Forum', + 'email' => 'Priority Email Support', + 'phone' => 'Phone Support', + 'skype' => 'Skype Support', + 'is_success_manager' => 'Personal Success Manager', + ); + + for ( $i = 0, $len = count( $pricing->plans ); $i < $len; $i ++ ) { + if ( 'free' == $pricing->plans[$i]->name ) { + continue; + } + + if ( ! isset( $pricing->plans[ $i ]->features ) || + ! is_array( $pricing->plans[ $i ]->features ) ) { + $pricing->plans[$i]->features = array(); + } + + foreach ( $support_features as $key => $label ) { + $key = ( 'is_success_manager' !== $key ) ? + "support_{$key}" : + $key; + + if ( ! empty( $pricing->plans[ $i ]->{$key} ) ) { + + $support_feature = new stdClass(); + $support_feature->title = $label; + + $pricing->plans[ $i ]->features[] = $support_feature; + } + } + } + } + } + + $this->json['plans'] = $pricing->plans; + + $this->json['strings'] = array( + 'plan' => $this->fs->get_text_x_inline( 'Plan', 'as product pricing plan', 'plan' ), + ); + } + + /** + * Control content + */ + public function content_template() { + ?> +
    + <# if ( data.plans ) { #> +
      + <# for (i in data.plans) { #> + <# if ( 'free' != data.plans[i].name && (null != data.plans[i].features && 0 < data.plans[i].features.length) ) { #> +
    • +
      + +
      + <# if ( data.plans[i].description ) { #> +

      {{ data.plans[i].description }}

      + <# } #> + <# if ( data.plans[i].features ) { #> +
        + <# for ( j in data.plans[i].features ) { #> +
      • + <# if ( data.plans[i].features[j].value ) { #>{{ data.plans[i].features[j].value }} <# } #>{{ data.plans[i].features[j].title }} + <# if ( data.plans[i].features[j].description ) { #> + {{ data.plans[i].features[j].description }} + <# } #> +
      • + <# } #> +
      + <# } #> + <# if ( 'free' != data.plans[i].name ) { #> + {{{ data.button_text }}} + <# } #> +
      +
      +
    • + <# } #> + <# } #> +
    + <# } #> +
    + title( 'Freemius' ); + } + + static function requests_count() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } + + return number_format( count( $logger ) ); + } + + static function total_time() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } + + $total_time = .0; + foreach ( $logger as $l ) { + $total_time += $l['total']; + } + + return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); + } + + function render() { + ?> +
    + +
    + +
    + +
    + +
    + commission_type ) ? + ( '$' . $this->commission ) : + ( $this->commission . '%' ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function has_lifetime_commission() { + return ( 0 !== $this->future_payments_days ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function is_session_cookie() { + return ( 0 == $this->cookie_days ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function has_renewals_commission() { + return ( is_null( $this->commission_renewals_days ) || $this->commission_renewals_days > 0 ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-affiliate.php b/freemius/includes/entities/class-fs-affiliate.php new file mode 100644 index 0000000..cdae366 --- /dev/null +++ b/freemius/includes/entities/class-fs-affiliate.php @@ -0,0 +1,84 @@ +status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_pending() { + return ( 'pending' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_suspended() { + return ( 'suspended' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_rejected() { + return ( 'rejected' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_blocked() { + return ( 'blocked' === $this->status ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-billing.php b/freemius/includes/entities/class-fs-billing.php new file mode 100644 index 0000000..bf68401 --- /dev/null +++ b/freemius/includes/entities/class-fs-billing.php @@ -0,0 +1,95 @@ + $def_value ) { + $this->{$key} = isset( $entity->{$key} ) ? + $entity->{$key} : + $def_value; + } + } + + static function get_type() { + return 'type'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Entity $entity1 + * @param FS_Entity $entity2 + * + * @return bool + */ + static function equals( $entity1, $entity2 ) { + if ( is_null( $entity1 ) && is_null( $entity2 ) ) { + return true; + } else if ( is_object( $entity1 ) && is_object( $entity2 ) ) { + return ( $entity1->id == $entity2->id ); + } else if ( is_object( $entity1 ) ) { + return is_null( $entity1->id ); + } else { + return is_null( $entity2->id ); + } + } + + private $_is_updated = false; + + /** + * Update object property. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string|array[string]mixed $key + * @param string|bool $val + * + * @return bool + */ + function update( $key, $val = false ) { + if ( ! is_array( $key ) ) { + $key = array( $key => $val ); + } + + $is_updated = false; + + foreach ( $key as $k => $v ) { + if ( $this->{$k} === $v ) { + continue; + } + + if ( ( is_string( $this->{$k} ) && is_numeric( $v ) || + ( is_numeric( $this->{$k} ) && is_string( $v ) ) ) && + $this->{$k} == $v + ) { + continue; + } + + // Update value. + $this->{$k} = $v; + + $is_updated = true; + } + + $this->_is_updated = $is_updated; + + return $is_updated; + } + + /** + * Checks if entity was updated. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_updated() { + return $this->_is_updated; + } + + /** + * @param $id + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @return bool + */ + static function is_valid_id($id){ + return is_numeric($id); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-payment.php b/freemius/includes/entities/class-fs-payment.php new file mode 100644 index 0000000..475267e --- /dev/null +++ b/freemius/includes/entities/class-fs-payment.php @@ -0,0 +1,168 @@ +bound_payment_id ) && 0 > $this->gross ); + } + + /** + * Checks if the payment was migrated from another platform. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.2 + * + * @return bool + */ + function is_migrated() { + return ( 0 != $this->source ); + } + + /** + * Returns the gross in this format: + * `{symbol}{amount | 2 decimal digits} {currency | uppercase}` + * + * Examples: £9.99 GBP, -£9.99 GBP. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + function formatted_gross() + { + return ( + ( $this->gross < 0 ? '-' : '' ) . + $this->get_symbol() . + number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' . + strtoupper( $this->currency ) + ); + } + + /** + * A map between supported currencies with their symbols. + * + * @var array + */ + static $CURRENCY_2_SYMBOL; + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + private function get_symbol() { + if ( ! isset( self::$CURRENCY_2_SYMBOL ) ) { + // Lazy load. + self::$CURRENCY_2_SYMBOL = array( + self::CURRENCY_USD => '$', + self::CURRENCY_GBP => '£', + self::CURRENCY_EUR => '€', + ); + } + + return self::$CURRENCY_2_SYMBOL[ $this->currency ]; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-info.php b/freemius/includes/entities/class-fs-plugin-info.php new file mode 100644 index 0000000..f546534 --- /dev/null +++ b/freemius/includes/entities/class-fs-plugin-info.php @@ -0,0 +1,34 @@ +is_features_enabled() ) { + return 0; + } + + if ( $this->is_unlimited() ) { + return 999; + } + + return ( $this->quota - $this->activated - ( $this->is_free_localhost ? 0 : $this->activated_local ) ); + } + + /** + * Check if single site license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @return bool + */ + function is_single_site() { + return ( is_numeric( $this->quota ) && 1 == $this->quota ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_expired() { + return ! $this->is_lifetime() && ( strtotime( $this->expiration ) < WP_FS__SCRIPT_START_TIME ); + } + + /** + * Check if license is not expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @return bool + */ + function is_valid() { + return ! $this->is_expired(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_lifetime() { + return is_null( $this->expiration ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @return bool + */ + function is_unlimited() { + return is_null( $this->quota ); + } + + /** + * Check if license is fully utilized. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|null $is_localhost + * + * @return bool + */ + function is_utilized( $is_localhost = null ) { + if ( is_null( $is_localhost ) ) { + $is_localhost = WP_FS__IS_LOCALHOST_FOR_SERVER; + } + + if ( $this->is_unlimited() ) { + return false; + } + + return ! ( $this->is_free_localhost && $is_localhost ) && + ( $this->quota <= $this->activated + ( $this->is_free_localhost ? 0 : $this->activated_local ) ); + } + + /** + * Check if license can be activated. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool|null $is_localhost + * + * @return bool + */ + function can_activate( $is_localhost = null ) { + return ! $this->is_utilized( $is_localhost ) && $this->is_features_enabled(); + } + + /** + * Check if license can be activated on a given number of production and localhost sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $production_count + * @param int $localhost_count + * + * @return bool + */ + function can_activate_bulk( $production_count, $localhost_count ) { + if ( $this->is_unlimited() ) { + return true; + } + + /** + * For simplicity, the logic will work as following: when given X sites to activate the license on, if it's + * possible to activate on ALL of them, do the activation. If it's not possible to activate on ALL of them, + * do NOT activate on any of them. + */ + return ( $this->quota >= $this->activated + $production_count + ( $this->is_free_localhost ? 0 : $this->activated_local + $localhost_count ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @return bool + */ + function is_active() { + return ( ! $this->is_cancelled ); + } + + /** + * Check if license's plan features are enabled. + * + * - Either if plan not expired + * - If expired, based on the configuration to block features or not. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_features_enabled() { + return $this->is_active() && ( ! $this->is_block_features || ! $this->is_expired() ); + } + + /** + * Subscription considered to be new without any payments + * if the license expires in less than 24 hours + * from the license creation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_first_payment_pending() { + return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->expiration ) - strtotime( $this->created ) ); + } + + /** + * @return int + */ + function total_activations() { + return ( $this->activated + $this->activated_local ); + } + } diff --git a/freemius/includes/entities/class-fs-plugin-plan.php b/freemius/includes/entities/class-fs-plugin-plan.php new file mode 100644 index 0000000..00a0d74 --- /dev/null +++ b/freemius/includes/entities/class-fs-plugin-plan.php @@ -0,0 +1,145 @@ +name = strtolower( $plan->name ); + } + } + + static function get_type() { + return 'plan'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_free() { + return ( 'free' === $this->name ); + } + + /** + * Checks if this plan supports "Technical Support". + * + * @author Leo Fajardo (leorw) + * @since 1.2.0 + * + * @return bool + */ + function has_technical_support() { + return ( ! empty( $this->support_email ) || + ! empty( $this->support_skype ) || + ! empty( $this->support_phone ) || + ! empty( $this->is_success_manager ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_trial() { + return ! $this->is_free() && + is_numeric( $this->trial_period ) && ( $this->trial_period > 0 ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-tag.php b/freemius/includes/entities/class-fs-plugin-tag.php new file mode 100644 index 0000000..739e9c8 --- /dev/null +++ b/freemius/includes/entities/class-fs-plugin-tag.php @@ -0,0 +1,60 @@ +release_mode ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin.php b/freemius/includes/entities/class-fs-plugin.php new file mode 100644 index 0000000..8fdb33c --- /dev/null +++ b/freemius/includes/entities/class-fs-plugin.php @@ -0,0 +1,154 @@ +is_premium = false; + $this->is_live = true; + + if ( empty( $this->premium_slug ) && ! empty( $plugin->slug ) ) { + $this->premium_slug = "{$this->slug}-premium"; + } + + if ( empty( $this->premium_suffix ) ) { + $this->premium_suffix = '(Premium)'; + } + + if ( isset( $plugin->info ) && is_object( $plugin->info ) ) { + $this->info = new FS_Plugin_Info( $plugin->info ); + } + } + + /** + * Check if plugin is an add-on (has parent). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_addon() { + return isset( $this->parent_plugin_id ) && is_numeric( $this->parent_plugin_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return bool + */ + function has_affiliate_program() { + return ( ! empty( $this->affiliate_moderation ) ); + } + + static function get_type() { + return 'plugin'; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-pricing.php b/freemius/includes/entities/class-fs-pricing.php new file mode 100644 index 0000000..f08b85d --- /dev/null +++ b/freemius/includes/entities/class-fs-pricing.php @@ -0,0 +1,141 @@ +monthly_price ) && $this->monthly_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_annual() { + return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_lifetime() { + return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); + } + + /** + * Check if unlimited licenses pricing. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_unlimited() { + return is_null( $this->licenses ); + } + + + /** + * Check if pricing has more than one billing cycle. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_multi_cycle() { + $cycles = 0; + if ( $this->has_monthly() ) { + $cycles ++; + } + if ( $this->has_annual() ) { + $cycles ++; + } + if ( $this->has_lifetime() ) { + $cycles ++; + } + + return $cycles > 1; + } + + /** + * Get annual over monthly discount. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return int + */ + function annual_discount_percentage() { + return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); + } + + /** + * Get annual over monthly savings. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return float + */ + function annual_savings() { + return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); + } + + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-scope-entity.php b/freemius/includes/entities/class-fs-scope-entity.php new file mode 100644 index 0000000..6b83107 --- /dev/null +++ b/freemius/includes/entities/class-fs-scope-entity.php @@ -0,0 +1,29 @@ +plan_id = $site->plan_id; + } + + if ( ! is_bool( $this->is_disconnected ) ) { + $this->is_disconnected = false; + } + } + + static function get_type() { + return 'install'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $url + * + * @return bool + */ + static function is_localhost_by_address( $url ) { + if ( false !== strpos( $url, '127.0.0.1' ) || + false !== strpos( $url, 'localhost' ) + ) { + return true; + } + + if ( ! fs_starts_with( $url, 'http' ) ) { + $url = 'http://' . $url; + } + + $url_parts = parse_url( $url ); + + $subdomain = $url_parts['host']; + + return ( + // Starts with. + fs_starts_with( $subdomain, 'local.' ) || + fs_starts_with( $subdomain, 'dev.' ) || + fs_starts_with( $subdomain, 'test.' ) || + fs_starts_with( $subdomain, 'stage.' ) || + fs_starts_with( $subdomain, 'staging.' ) || + + // Ends with. + fs_ends_with( $subdomain, '.dev' ) || + fs_ends_with( $subdomain, '.test' ) || + fs_ends_with( $subdomain, '.staging' ) || + fs_ends_with( $subdomain, '.local' ) || + fs_ends_with( $subdomain, '.example' ) || + fs_ends_with( $subdomain, '.invalid' ) || + // GoDaddy test/dev. + fs_ends_with( $subdomain, '.myftpupload.com' ) || + // ngrok tunneling. + fs_ends_with( $subdomain, '.ngrok.io' ) || + // wpsandbox. + fs_ends_with( $subdomain, '.wpsandbox.pro' ) || + // SiteGround staging. + fs_starts_with( $subdomain, 'staging' ) || + // WPEngine staging. + fs_ends_with( $subdomain, '.staging.wpengine.com' ) || + fs_ends_with( $subdomain, '.dev.wpengine.com' ) || + // Pantheon + ( fs_ends_with($subdomain, 'pantheonsite.io') && + (fs_starts_with($subdomain, 'test-') || fs_starts_with($subdomain, 'dev-'))) || + // Cloudways + fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || + // Kinsta + (fs_ends_with($subdomain, '.kinsta.com') && fs_starts_with($subdomain, 'staging-')) || + // DesktopServer + fs_ends_with( $subdomain, '.dev.cc' ) + ); + } + + function is_localhost() { + return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) ); + } + + /** + * Check if site in trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_trial() { + return is_numeric( $this->trial_plan_id ) && ( strtotime( $this->trial_ends ) > WP_FS__SCRIPT_START_TIME ); + } + + /** + * Check if user already utilized the trial with the current install. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_trial_utilized() { + return is_numeric( $this->trial_plan_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_tracking_allowed() { + return ( true !== $this->is_disconnected ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_tracking_prohibited() { + return ! $this->is_tracking_allowed(); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-subscription.php b/freemius/includes/entities/class-fs-subscription.php new file mode 100644 index 0000000..8a01402 --- /dev/null +++ b/freemius/includes/entities/class-fs-subscription.php @@ -0,0 +1,125 @@ +next_payment ) && + ( strtotime( $this->next_payment ) > WP_FS__SCRIPT_START_TIME ); + } + + /** + * Subscription considered to be new without any payments + * if the next payment should be made within less than 24 hours + * from the subscription creation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_first_payment_pending() { + return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->next_payment ) - strtotime( $this->created ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + */ + function has_trial() { + return ! is_null( $this->trial_ends ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-user.php b/freemius/includes/entities/class-fs-user.php new file mode 100644 index 0000000..6ad4e0e --- /dev/null +++ b/freemius/includes/entities/class-fs-user.php @@ -0,0 +1,79 @@ +first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) ); + } + + function is_verified() { + return ( isset( $this->is_verified ) && true === $this->is_verified ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return bool + */ + function is_beta() { + return ( isset( $this->is_beta ) && true === $this->is_beta ); + } + + static function get_type() { + return 'user'; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/index.php b/freemius/includes/entities/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/entities/index.php @@ -0,0 +1,3 @@ +
    ' : '' ) . $title; + + if ( is_string( $confirmation ) ) { + return sprintf( '
    %s%s
    ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), + $method, + $action, + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $confirmation, + $title + ); + } else if ( 'GET' !== strtoupper( $method ) ) { + return sprintf( '
    %s%s
    ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), + $method, + $action, + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $title + ); + } else { + return sprintf( '%s', + wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $title + ); + } + } + + function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { + ?> 5 ) { + return $url; + } + + return substr( $url, $protocol_pos + 3 ); + } + } + + #region Url Canonization ------------------------------------------------------------------ + + if ( ! function_exists( 'fs_canonize_url' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $url + * @param bool $omit_host + * @param array $ignore_params + * + * @return string + */ + function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { + $parsed_url = parse_url( strtolower( $url ) ); + +// if ( ! isset( $parsed_url['host'] ) ) { +// return $url; +// } + + $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path']; + + if ( isset( $parsed_url['query'] ) ) { + parse_str( $parsed_url['query'], $queryString ); + $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params ); + } + + return $canonical; + } + } + + if ( ! function_exists( 'fs_canonize_query_string' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param array $params + * @param array $ignore_params + * @param bool $params_prefix + * + * @return string + */ + function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) { + if ( ! is_array( $params ) || 0 === count( $params ) ) { + return ''; + } + + // Url encode both keys and values + $keys = fs_urlencode_rfc3986( array_keys( $params ) ); + $values = fs_urlencode_rfc3986( array_values( $params ) ); + $params = array_combine( $keys, $values ); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort( $params, 'strcmp' ); + + $pairs = array(); + foreach ( $params as $parameter => $value ) { + $lower_param = strtolower( $parameter ); + + // Skip ignore params. + if ( in_array( $lower_param, $ignore_params ) || + ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) ) + ) { + continue; + } + + if ( is_array( $value ) ) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + natsort( $value ); + foreach ( $value as $duplicate_value ) { + $pairs[] = $lower_param . '=' . $duplicate_value; + } + } else { + $pairs[] = $lower_param . '=' . $value; + } + } + + if ( 0 === count( $pairs ) ) { + return ''; + } + + return implode( "&", $pairs ); + } + } + + if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string|string[] $input + * + * @return array|mixed|string + */ + function fs_urlencode_rfc3986( $input ) { + if ( is_array( $input ) ) { + return array_map( 'fs_urlencode_rfc3986', $input ); + } else if ( is_scalar( $input ) ) { + return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) ); + } + + return ''; + } + } + + #endregion Url Canonization ------------------------------------------------------------------ + + /** + * @author Vova Feldman (@svovaf) + * + * @since 1.2.2 Changed to usage of WP_Filesystem_Direct. + * + * @param string $from URL + * @param string $to File path. + * + * @return bool Is successfully downloaded. + */ + function fs_download_image( $from, $to ) { + $dir = dirname( $to ); + + if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { + return false; + } + + if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; + } + + $fs = new WP_Filesystem_Direct( '' ); + $tmpfile = download_url( $from ); + + if ( $tmpfile instanceof WP_Error ) { + // Issue downloading the file. + return false; + } + + $fs->copy( $tmpfile, $to ); + $fs->delete( $tmpfile ); + + return true; + } + + /* General Utilities + --------------------------------------------------------------------------------------------*/ + + /** + * Sorts an array by the value of the priority key. + * + * @author Daniel Iser (@danieliser) + * @since 1.1.7 + * + * @param $a + * @param $b + * + * @return int + */ + function fs_sort_by_priority( $a, $b ) { + + // If b has a priority and a does not, b wins. + if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) { + return 1; + } // If b has a priority and a does not, b wins. + elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { + return - 1; + } // If neither has a priority or both priorities are equal its a tie. + elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) { + return 0; + } + + // If both have priority return the winner. + return ( $a['priority'] < $b['priority'] ) ? - 1 : 1; + } + + #-------------------------------------------------------------------------------- + #region Localization + #-------------------------------------------------------------------------------- + + if ( ! function_exists( 'fs_text' ) ) { + /** + * Retrieve a translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * @param string $slug + * + * @return string + * + * @global $fs_text , $fs_text_overrides + */ + function fs_text( $key, $slug = 'freemius' ) { + global $fs_text, + $fs_module_info_text, + $fs_text_overrides; + + if ( isset( $fs_text_overrides[ $slug ] ) ) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + } + + if ( ! isset( $fs_text ) ) { + $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? + WP_FS__DIR_INCLUDES : + dirname( __FILE__ ); + + require_once $dir . '/i18n.php'; + } + + if ( isset( $fs_text[ $key ] ) ) { + return $fs_text[ $key ]; + } + + if ( isset( $fs_module_info_text[ $key ] ) ) { + return $fs_module_info_text[ $key ]; + } + + return $key; + } + + #region Private + + /** + * Retrieve an inline translated text by key with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); + + // Avoid misleading Theme Check warning. + $fn = 'translate_with_gettext_context'; + + return $fn( $text, $context, $text_domain ); + } + + #endregion + + /** + * Retrieve an inline translated text by key with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return _fs_text_x_inline( $text, $context, $key, $slug ); + } + + /** + * Output a translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * @param string $slug + */ + function fs_echo( $key, $slug = 'freemius' ) { + echo fs_text( $key, $slug ); + } + + /** + * Output an inline translated text. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo _fs_text_inline( $text, $key, $slug ); + } + + /** + * Output an inline translated text with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo _fs_text_x_inline( $text, $context, $key, $slug ); + } + } + + if ( ! function_exists( 'fs_text_override' ) ) { + /** + * Get a translatable text override if exists, or `false`. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string|false + */ + function fs_text_override( $text, $key, $slug ) { + global $fs_text_overrides; + + /** + * Check if string is overridden. + */ + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { + return false; + } + + if ( empty( $key ) ) { + $key = strtolower( str_replace( ' ', '-', $text ) ); + } + + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + + return false; + } + } + + if ( ! function_exists( 'fs_text_and_domain' ) ) { + /** + * Get a translatable text and its text domain. + * + * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string[] + */ + function fs_text_and_domain( $text, $key, $slug ) { + $override = fs_text_override( $text, $key, $slug ); + + if ( false === $override ) { + // No override, use FS text domain. + $text_domain = 'freemius'; + } else { + // Found an override. + $text = $override; + // Use the module's text domain. + $text_domain = $slug; + } + + return array( $text, $text_domain ); + } + } + + if ( ! function_exists( '_fs_text_inline' ) ) { + /** + * Retrieve an inline translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); + + // Avoid misleading Theme Check warning. + $fn = 'translate'; + + return $fn( $text, $text_domain ); + } + } + + if ( ! function_exists( 'fs_text_inline' ) ) { + /** + * Retrieve an inline translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + return _fs_text_inline( $text, $key, $slug ); + } + } + + if ( ! function_exists( 'fs_esc_attr' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_attr( $key, $slug ) { + return esc_attr( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_attr_echo( $key, $slug ) { + echo esc_attr( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_attr( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_js( $key, $slug ) { + return esc_js( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_js_echo( $key, $slug ) { + echo esc_js( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_json_encode_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_json_encode_echo( $key, $slug ) { + echo json_encode( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo json_encode( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_html( $key, $slug ) { + return esc_html( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_html_echo( $key, $slug ) { + echo esc_html( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_override_i18n' ) ) { + /** + * Override default i18n text phrases. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param array[string]string $key_value + * @param string $slug + * + * @global $fs_text_overrides + */ + function fs_override_i18n( array $key_value, $slug = 'freemius' ) { + global $fs_text_overrides; + + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { + $fs_text_overrides[ $slug ] = array(); + } + + foreach ( $key_value as $key => $value ) { + $fs_text_overrides[ $slug ][ $key ] = $value; + } + } + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Multisite Network + #-------------------------------------------------------------------------------- + + if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_plugin_uninstall() { + return ( + defined( 'WP_UNINSTALL_PLUGIN' ) || + ( 0 < did_action( 'update_option_uninstall_plugins' ) ) + ); + } + } + + if ( ! function_exists( 'fs_is_network_admin' ) ) { + /** + * Unlike is_network_admin(), this one will also work properly when + * the context execution is WP AJAX handler, and during plugin + * uninstall. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_network_admin() { + return ( + WP_FS__IS_NETWORK_ADMIN || + ( is_multisite() && fs_is_plugin_uninstall() ) + ); + } + } + + if ( ! function_exists( 'fs_is_blog_admin' ) ) { + /** + * Unlike is_blog_admin(), this one will also work properly when + * the context execution is WP AJAX handler, and during plugin + * uninstall. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_blog_admin() { + return ( + WP_FS__IS_BLOG_ADMIN || + ( ! is_multisite() && fs_is_plugin_uninstall() ) + ); + } + } + + #endregion + + if ( ! function_exists( 'fs_apply_filter' ) ) { + /** + * Apply filter for specific plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $module_unique_affix Module's unique affix. + * @param string $tag The name of the filter hook. + * @param mixed $value The value on which the filters hooked to `$tag` are applied on. + * + * @return mixed The filtered value after all hooked functions are applied to it. + * + * @uses apply_filters() + */ + function fs_apply_filter( $module_unique_affix, $tag, $value ) { + $args = func_get_args(); + + return call_user_func_array( 'apply_filters', array_merge( + array( "fs_{$tag}_{$module_unique_affix}" ), + array_slice( $args, 2 ) ) + ); + } + } \ No newline at end of file diff --git a/freemius/includes/fs-essential-functions.php b/freemius/includes/fs-essential-functions.php new file mode 100644 index 0000000..05f68c6 --- /dev/null +++ b/freemius/includes/fs-essential-functions.php @@ -0,0 +1,479 @@ +add( "Freemius failed to redirect the page because the headers have been already sent from line {$line} in file {$file}. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' ); + } + + return false; + } + + if ( defined( 'DOING_AJAX' ) ) { + // Don't redirect on AJAX calls. + return false; + } + + if ( ! $location ) // allows the wp_redirect filter to cancel a redirect + { + return false; + } + + $location = fs_sanitize_redirect( $location ); + + if ( $is_IIS ) { + header( "Refresh: 0;url=$location" ); + } else { + if ( php_sapi_name() != 'cgi-fcgi' ) { + status_header( $status ); + } // This causes problems on IIS and some FastCGI setups + header( "Location: $location" ); + } + + if ( $exit ) { + exit(); + } + + return true; + } + + if ( ! function_exists( 'fs_sanitize_redirect' ) ) { + /** + * Sanitizes a URL for use in a redirect. + * + * @since 2.3 + * + * @param string $location + * + * @return string redirect-sanitized URL + */ + function fs_sanitize_redirect( $location ) { + $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location ); + $location = fs_kses_no_null( $location ); + + // remove %0d and %0a from location + $strip = array( '%0d', '%0a' ); + $found = true; + while ( $found ) { + $found = false; + foreach ( (array) $strip as $val ) { + while ( strpos( $location, $val ) !== false ) { + $found = true; + $location = str_replace( $val, '', $location ); + } + } + } + + return $location; + } + } + + if ( ! function_exists( 'fs_kses_no_null' ) ) { + /** + * Removes any NULL characters in $string. + * + * @since 1.0.0 + * + * @param string $string + * + * @return string + */ + function fs_kses_no_null( $string ) { + $string = preg_replace( '/\0+/', '', $string ); + $string = preg_replace( '/(\\\\0)+/', '', $string ); + + return $string; + } + } + } + + #endregion Core Redirect (copied from BuddyPress) ----------------------------------------- + + if ( ! function_exists( '__fs' ) ) { + global $fs_text_overrides; + + if ( ! isset( $fs_text_overrides ) ) { + $fs_text_overrides = array(); + } + + /** + * Retrieve a translated text by key. + * + * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7. + * @todo Remove this method in the future. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $key + * @param string $slug + * + * @return string + * + * @global $fs_text, $fs_text_overrides + */ + function __fs( $key, $slug = 'freemius' ) { + _deprecated_function( __FUNCTION__, '2.0.0', 'fs_text()' ); + + global $fs_text, + $fs_module_info_text, + $fs_text_overrides; + + if ( isset( $fs_text_overrides[ $slug ] ) ) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + } + + if ( ! isset( $fs_text ) ) { + $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? + WP_FS__DIR_INCLUDES : + dirname( __FILE__ ); + + require_once $dir . '/i18n.php'; + } + + if ( isset( $fs_text[ $key ] ) ) { + return $fs_text[ $key ]; + } + + if ( isset( $fs_module_info_text[ $key ] ) ) { + return $fs_module_info_text[ $key ]; + } + + return $key; + } + + /** + * Output a translated text by key. + * + * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`. + * + * @todo Remove this method in the future. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $key + * @param string $slug + */ + function _efs( $key, $slug = 'freemius' ) { + fs_echo( $key, $slug ); + } + } + + if ( ! function_exists( 'fs_get_ip' ) ) { + /** + * Get client IP. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @return string|null + */ + function fs_get_ip() { + $fields = array( + 'HTTP_CF_CONNECTING_IP', + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR', + ); + + foreach ( $fields as $ip_field ) { + if ( ! empty( $_SERVER[ $ip_field ] ) ) { + return $_SERVER[ $ip_field ]; + } + } + + return null; + } + } + + /** + * Leverage backtrace to find caller plugin main file path. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return string + */ + function fs_find_caller_plugin_file() { + /** + * All the code below will be executed once on activation. + * If the user changes the main plugin's file name, the file_exists() + * will catch it. + */ + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $all_plugins = fs_get_plugins( true ); + $all_plugins_paths = array(); + + // Get active plugin's main files real full names (might be symlinks). + foreach ( $all_plugins as $relative_path => $data ) { + $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); + } + + $plugin_file = null; + for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { + if ( empty( $bt[ $i ]['file'] ) ) { + continue; + } + + if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) { + $plugin_file = $bt[ $i ]['file']; + break; + } + } + + if ( is_null( $plugin_file ) ) { + // Throw an error to the developer in case of some edge case dev environment. + wp_die( + 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', + 'Error', + array( 'back_link' => true ) + ); + } + + return $plugin_file; + } + + require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php'; + + /** + * Update SDK newest version reference. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $sdk_relative_path + * @param string|bool $plugin_file + * + * @global $fs_active_plugins + */ + function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) { + /** + * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()` + * function in the older version will be used instead of this one. But since the older version is using + * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the + * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be + * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine. + * + * Future versions that will call this function will use the proper logic here instead of just relying on the + * `is_plugin_active` function to fail for themes. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + + global $fs_active_plugins; + + $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ]; + + if ( ! is_string( $plugin_file ) ) { + $plugin_file = plugin_basename( fs_find_caller_plugin_file() ); + } + + if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) { + $in_activation = ( ! is_plugin_active( $plugin_file ) ); + } else { + $theme = wp_get_theme(); + $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet ); + } + + $fs_active_plugins->newest = (object) array( + 'plugin_path' => $plugin_file, + 'sdk_path' => $sdk_relative_path, + 'version' => $newest_sdk->version, + 'in_activation' => $in_activation, + 'timestamp' => time(), + ); + + // Update DB with latest SDK version and path. + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + /** + * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool Was plugin order changed. Return false if plugin was loaded first anyways. + * + * @global $fs_active_plugins + */ + function fs_newest_sdk_plugin_first() { + global $fs_active_plugins; + + /** + * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins). + * + * @link https://github.com/Freemius/wordpress-sdk/issues/26 + */ + + $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path; + + $active_plugins = get_option( 'active_plugins', array() ); + $newest_sdk_plugin_key = array_search( $newest_sdk_plugin_path, $active_plugins ); + if ( 0 === $newest_sdk_plugin_key ) { + // if it's 0 it's the first plugin already, no need to continue + return false; + } else if ( is_numeric( $newest_sdk_plugin_key ) ) { + // Remove plugin from its current position. + array_splice( $active_plugins, $newest_sdk_plugin_key, 1 ); + + // Set it to be included first. + array_unshift( $active_plugins, $newest_sdk_plugin_path ); + + update_option( 'active_plugins', $active_plugins ); + + return true; + } else if ( is_multisite() && false === $newest_sdk_plugin_key ) { + // Plugin is network active. + $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() ); + + if (isset($network_active_plugins[$newest_sdk_plugin_path])) { + reset($network_active_plugins); + if ( $newest_sdk_plugin_path === key($network_active_plugins) ) { + // Plugin is already activated first on the network level. + return false; + } else if ( is_numeric( $newest_sdk_plugin_key ) ) { + $time = $network_active_plugins[$newest_sdk_plugin_path]; + + // Remove plugin from its current position. + unset($network_active_plugins[$newest_sdk_plugin_path]); + + // Set it to be included first. + $network_active_plugins = array($newest_sdk_plugin_path => $time) + $network_active_plugins; + + update_site_option( 'active_sitewide_plugins', $network_active_plugins ); + + return true; + } + } + } + + return false; + } + + /** + * Go over all Freemius SDKs in the system and find and "remember" + * the newest SDK which is associated with an active plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @global $fs_active_plugins + */ + function fs_fallback_to_newest_active_sdk() { + global $fs_active_plugins; + + /** + * @var object $newest_sdk_data + */ + $newest_sdk_data = null; + $newest_sdk_path = null; + + foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) { + if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' ) + ) { + // If plugin inactive or SDK starter file doesn't exist, remove SDK reference. + if ( 'plugin' === $data->type ) { + $is_module_active = is_plugin_active( $data->plugin_path ); + } else { + $active_theme = wp_get_theme(); + $is_module_active = ( $data->plugin_path === $active_theme->get_template() ); + } + + $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) ); + + if ( ! $is_module_active || ! $is_sdk_exists ) { + unset( $fs_active_plugins->plugins[ $sdk_relative_path ] ); + + // No need to store the data since it will be stored in fs_update_sdk_newest_version() + // or explicitly with update_option(). + } else { + $newest_sdk_data = $data; + $newest_sdk_path = $sdk_relative_path; + } + } + } + + if ( is_null( $newest_sdk_data ) ) { + // Couldn't find any SDK reference. + $fs_active_plugins = new stdClass(); + update_option( 'fs_active_plugins', $fs_active_plugins ); + } else { + fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path ); + } + } \ No newline at end of file diff --git a/freemius/includes/fs-plugin-info-dialog.php b/freemius/includes/fs-plugin-info-dialog.php new file mode 100644 index 0000000..cd3f4d4 --- /dev/null +++ b/freemius/includes/fs-plugin-info-dialog.php @@ -0,0 +1,1610 @@ +_fs = $fs; + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + // Remove default plugin information action. + remove_all_actions( 'install_plugins_pre_plugin-information' ); + + // Override action with custom plugins function for add-ons. + add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) ); + + // Override request for plugin information for Add-ons. + add_filter( + 'fs_plugins_api', + array( &$this, '_get_addon_info_filter' ), + WP_FS__DEFAULT_PRIORITY, 3 ); + } + + /** + * Generate add-on plugin information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param array $data + * @param string $action + * @param object|null $args + * + * @return array|null + */ + function _get_addon_info_filter( $data, $action = '', $args = null ) { + $this->_logger->entrance(); + + $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() ); + + if ( $this->_fs->get_id() != $parent_plugin_id || + ( 'plugin_information' !== $action ) || + ! isset( $args->slug ) + ) { + return $data; + } + + // Find add-on by slug. + $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE ); + + if ( false === $selected_addon ) { + return $data; + } + + if ( ! isset( $selected_addon->info ) ) { + // Setup some default info. + $selected_addon->info = new stdClass(); + $selected_addon->info->selling_point_0 = 'Selling Point 1'; + $selected_addon->info->selling_point_1 = 'Selling Point 2'; + $selected_addon->info->selling_point_2 = 'Selling Point 3'; + $selected_addon->info->description = '

    Tell your users all about your add-on

    '; + } + + fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); + + $data = $args; + + $has_free_plan = false; + $has_paid_plan = false; + + // Load add-on pricing. + $has_pricing = false; + $has_features = false; + $plans = false; + + $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) ); + + if ( ! isset( $result->error ) ) { + $plans = $result->plans; + + if ( is_array( $plans ) ) { + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null; + $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null; + + $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); + $plan = $plans[ $i ]; + + if ( 'free' == $plans[ $i ]->name || + ! is_array( $pricing ) || + 0 == count( $pricing ) + ) { + $has_free_plan = true; + } + + if ( is_array( $pricing ) && 0 < count( $pricing ) ) { + $has_paid_plan = true; + + foreach ( $pricing as &$prices ) { + $prices = new FS_Pricing( $prices ); + } + + $plan->pricing = $pricing; + + $has_pricing = true; + } + + if ( is_array( $features ) && 0 < count( $features ) ) { + $plan->features = $features; + + $has_features = true; + } + } + } + } + + $latest = null; + + if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { + $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( + 'plugin_information', (object) array( + 'slug' => $selected_addon->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => true, + 'reviews' => true, + 'downloaded' => false, + 'active_installs' => true + ) + ) ); + + if ( ! empty( $repo_data ) ) { + $data = $repo_data; + $data->wp_org_missing = false; + } else { + // Couldn't find plugin on .org. + $selected_addon->is_wp_org_compliant = false; + + // Plugin is missing, not on Freemius nor WP.org. + $data->wp_org_missing = true; + } + + $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing ); + } else { + $data->has_purchased_license = false; + $data->wp_org_missing = false; + + $fs_addon = null; + $current_addon_version = false; + if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) { + $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id ); + $current_addon_version = $fs_addon->get_plugin_version(); + } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) { + $addon_plugin_data = get_plugin_data( + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ), + false, + false + ); + + if ( ! empty( $addon_plugin_data ) ) { + $current_addon_version = $addon_plugin_data['Version']; + } + } + + // Fetch latest version from Freemius. + $latest = $this->_fs->_fetch_latest_version( + $selected_addon->id, + true, + WP_FS__TIME_24_HOURS_IN_SEC, + $current_addon_version + ); + + if ( $has_paid_plan ) { + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); + + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); + } + + $data->checkout_link = $this->_fs->checkout_url( + WP_FS__PERIOD_ANNUALLY, + false, + array(), + ( $has_valid_blog_id ? false : null ) + ); + + if ( $has_valid_blog_id ) { + restore_current_blog(); + } + + if ( is_object( $fs_addon ) ) { + $data->has_purchased_license = $fs_addon->has_active_valid_license(); + } else { + $account_addons = $this->_fs->get_account_addons(); + if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { + $data->has_purchased_license = true; + } + } + } + + if ( $has_free_plan || $data->has_purchased_license ) { + $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id ); + } + + $data->fs_missing = ( + false === $latest && + ( + empty( $selected_addon->premium_releases_count ) || + ! ( $selected_addon->premium_releases_count > 0 ) + ) + ); + + // Fetch as much as possible info from local files. + $plugin_local_data = $this->_fs->get_plugin_data(); + $data->author = $plugin_local_data['Author']; + + if ( ! empty( $selected_addon->info->banner_url ) ) { + $data->banners = array( + 'low' => $selected_addon->info->banner_url, + ); + } + + if ( ! empty( $selected_addon->info->screenshots ) ) { + $view_vars = array( + 'screenshots' => $selected_addon->info->screenshots, + 'plugin' => $selected_addon, + ); + $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars ); + } + + if ( is_object( $latest ) ) { + $data->version = $latest->version; + $data->last_updated = $latest->created; + $data->requires = $latest->requires_platform_version; + $data->tested = $latest->tested_up_to_version; + } else if ( ! empty( $current_addon_version ) ) { + $data->version = $current_addon_version; + } else { + // Add dummy version. + $data->version = '1.0.0'; + + // Add message to developer to deploy the plugin through Freemius. + } + } + + $data->name = $selected_addon->title; + $view_vars = array( 'plugin' => $selected_addon ); + + if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { + $latest_version_readme_data = $latest->readme; + if ( isset( $latest_version_readme_data->sections ) ) { + $data->sections = (array) $latest_version_readme_data->sections; + } else { + $data->sections = array(); + } + } + + $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars ); + + if ( $has_pricing ) { + // Add plans to data. + $data->plans = $plans; + + if ( $has_features ) { + $view_vars = array( + 'plans' => $plans, + 'plugin' => $selected_addon, + ); + $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); + } + } + + $data->has_free_plan = $has_free_plan; + $data->has_paid_plan = $has_paid_plan; + $data->is_paid = $has_paid_plan; + $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant; + $data->premium_slug = $selected_addon->premium_slug; + $data->addon_id = $selected_addon->id; + + if ( ! isset( $data->has_purchased_license ) ) { + $data->has_purchased_license = false; + } + + return $data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_billing_cycle( FS_Plugin_Plan $plan ) { + $billing_cycle = null; + + if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { + $pricing = $plan->pricing[0]; + if ( isset( $pricing->annual_price ) ) { + $billing_cycle = 'annual'; + } else if ( isset( $pricing->monthly_price ) ) { + $billing_cycle = 'monthly'; + } else if ( isset( $pricing->lifetime_price ) ) { + $billing_cycle = 'lifetime'; + } + } else { + foreach ( $plan->pricing as $pricing ) { + if ( isset( $pricing->annual_price ) ) { + $billing_cycle = 'annual'; + } else if ( isset( $pricing->monthly_price ) ) { + $billing_cycle = 'monthly'; + } else if ( isset( $pricing->lifetime_price ) ) { + $billing_cycle = 'lifetime'; + } + + if ( ! is_null( $billing_cycle ) ) { + break; + } + } + } + + return $billing_cycle; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param FS_Plugin_Plan $plan + * @param FS_Pricing $pricing + * + * @return float|null|string + */ + private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) { + $price_tag = ''; + if ( isset( $pricing->annual_price ) ) { + $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' ); + } else if ( isset( $pricing->monthly_price ) ) { + $price_tag = $pricing->monthly_price . ' / mo'; + } else if ( isset( $pricing->lifetime_price ) ) { + $price_tag = $pricing->lifetime_price; + } + + return '$' . $price_tag; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param object $api + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_actions_dropdown( $api, $plan = null ) { + $this->actions = isset( $this->actions ) ? + $this->actions : + $this->get_plugin_actions( $api ); + + $actions = $this->actions; + + $checkout_cta = $this->get_checkout_cta( $api, $plan ); + if ( ! empty( $checkout_cta ) ) { + /** + * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in + * the actions dropdown. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $api->has_purchased_license ) { + array_unshift( $actions, $checkout_cta ); + } else { + $actions[] = $checkout_cta; + } + } + + if ( empty( $actions ) ) { + return ''; + } + + $total_actions = count( $actions ); + if ( 1 === $total_actions ) { + return $actions[0]; + } + + ob_start(); + + ?> +
    +
    + +
    + + +
    +
    +
    + checkout_link ) || + ! isset( $api->plans ) || + ! is_array( $api->plans ) || + 0 == count( $api->plans ) + ) { + return ''; + } + + if ( is_null( $plan ) ) { + foreach ( $api->plans as $p ) { + if ( ! empty( $p->pricing ) ) { + $plan = $p; + break; + } + } + } + + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); + + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); + } + + $addon_checkout_url = $this->_fs->addon_checkout_url( + $plan->plugin_id, + $plan->pricing[0]->id, + $this->get_billing_cycle( $plan ), + $plan->has_trial(), + ( $has_valid_blog_id ? false : null ) + ); + + if ( $has_valid_blog_id ) { + restore_current_blog(); + } + + return '' . + esc_html( ! $plan->has_trial() ? + ( + $api->has_purchased_license ? + fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) + ) : + sprintf( + /* translators: %s: N-days trial */ + fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), + $this->get_trial_period( $plan ) + ) + ) . + ''; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param object $api + * + * @return string[] + */ + private function get_plugin_actions( $api ) { + $this->status = isset( $this->status ) ? + $this->status : + install_plugin_install_status( $api ); + + $is_update_available = ( 'update_available' === $this->status['status'] ); + + if ( $is_update_available && empty( $this->status['url'] ) ) { + return array(); + } + + $blog_id = fs_request_get( 'fs_blog_id' ); + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id ); + + $actions = array(); + + $is_addon_activated = $this->_fs->is_addon_activated( $api->slug ); + $fs_addon = null; + + $is_free_installed = null; + $is_premium_installed = null; + + $has_installed_version = ( 'install' !== $this->status['status'] ); + + if ( ! $api->has_paid_plan ) { + /** + * Free-only add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_free_installed = $has_installed_version; + $is_premium_installed = false; + } else if ( ! $api->has_free_plan ) { + /** + * Premium-only add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_free_installed = false; + $is_premium_installed = $has_installed_version; + } else { + /** + * Freemium add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $has_installed_version ) { + $is_free_installed = false; + $is_premium_installed = false; + } else { + $fs_addon = $is_addon_activated ? + $this->_fs->get_addon_instance( $api->slug ) : + null; + + if ( is_object( $fs_addon ) ) { + if ( $fs_addon->is_premium() ) { + $is_premium_installed = true; + } else { + $is_free_installed = true; + } + } + + if ( is_null( $is_free_installed ) ) { + $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) ); + if ( ! $is_free_installed ) { + /** + * Check if there's a plugin installed in a directory named `$api->slug`. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $installed_plugins = get_plugins( '/' . $api->slug ); + $is_free_installed = ( ! empty( $installed_plugins ) ); + } + } + + if ( is_null( $is_premium_installed ) ) { + $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) ); + if ( ! $is_premium_installed ) { + /** + * Check if there's a plugin installed in a directory named `$api->premium_slug`. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $installed_plugins = get_plugins( '/' . $api->premium_slug ); + $is_premium_installed = ( ! empty( $installed_plugins ) ); + } + } + } + + $has_installed_version = ( $is_free_installed || $is_premium_installed ); + } + + $this->status['is_free_installed'] = $is_free_installed; + $this->status['is_premium_installed'] = $is_premium_installed; + + $can_install_free_version = false; + $can_install_free_version_update = false; + $can_download_free_version = false; + $can_activate_free_version = false; + $can_install_premium_version = false; + $can_install_premium_version_update = false; + $can_download_premium_version = false; + $can_activate_premium_version = false; + + if ( ! $api->has_purchased_license ) { + if ( $api->has_free_plan ) { + if ( $has_installed_version ) { + if ( $is_update_available ) { + $can_install_free_version_update = true; + } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { + $can_activate_free_version = true; + } + } else { + if ( + $this->_fs->is_premium() || + ! $this->_fs->is_org_repo_compliant() || + $api->is_wp_org_compliant + ) { + $can_install_free_version = true; + } else { + $can_download_free_version = true; + } + } + } + } else { + if ( ! is_object( $fs_addon ) && $is_addon_activated ) { + $fs_addon = $this->_fs->get_addon_instance( $api->slug ); + } + + $can_download_premium_version = true; + + if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { + if ( $is_premium_installed ) { + $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() ); + } else if ( $is_free_installed ) { + $can_activate_free_version = ( ! $is_addon_activated ); + } + } + + if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) { + if ( $is_update_available ) { + $can_install_premium_version_update = true; + } else if ( ! $is_premium_installed ) { + $can_install_premium_version = true; + } + } + } + + if ( + $can_install_premium_version || + $can_install_premium_version_update + ) { + if ( is_numeric( $blog_id ) ) { + /** + * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded + * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update + * to work. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); + } + + /** + * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be + * installed/updated. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&', $this->status['url'] ); + } + + if ( $can_install_free_version_update || $can_install_premium_version_update ) { + $actions[] = $this->get_cta( + ( $can_install_free_version_update ? + fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) : + fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ), + true, + false, + $this->status['url'], + '_parent' + ); + } else if ( $can_install_free_version || $can_install_premium_version ) { + $actions[] = $this->get_cta( + ( $can_install_free_version ? + fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) : + fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ), + true, + false, + $this->status['url'], + '_parent' + ); + } + + $download_latest_action = ''; + + if ( + ! empty( $api->download_link ) && + ( $can_download_free_version || $can_download_premium_version ) + ) { + $download_latest_action = $this->get_cta( + ( $can_download_free_version ? + fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) : + fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ), + true, + false, + esc_url( $api->download_link ) + ); + } + + if ( ! $can_activate_free_version && ! $can_activate_premium_version ) { + if ( ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + } + } else { + $activate_action = sprintf( + '%s', + wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ), + $can_activate_free_version ? + fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) : + fs_text_inline( 'Activate', 'activate', $api->slug ) + ); + + if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + + $download_latest_action = ''; + } + + if ( $can_install_premium_version || $can_install_premium_version_update ) { + if ( $can_download_premium_version && ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + + $download_latest_action = ''; + } + + $actions[] = $activate_action; + } else { + array_unshift( $actions, $activate_action ); + } + + if ( ! empty ($download_latest_action ) ) { + $actions[] = $download_latest_action; + } + } + + return $actions; + } + + /** + * Rebuilds the status URL based on the admin URL. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param int $blog_id + * @param string $network_status_url + * @param string $status + * + * @return string + */ + private static function get_blog_status_url( $blog_id, $network_status_url, $status ) { + if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) { + return $network_status_url; + } + + $action = ( 'install' === $status ) ? + 'install-plugin' : + 'upgrade-plugin'; + + $query = parse_url( $network_status_url, PHP_URL_QUERY ); + if ( empty( $query ) ) { + return $network_status_url; + } + + parse_str( html_entity_decode( $query ), $url_params ); + if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) { + return $network_status_url; + } + + $plugin = $url_params['plugin']; + + return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); + } + + /** + * Helper method to get a CTA button HTML. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $label + * @param bool $is_primary + * @param bool $is_disabled + * @param string $href + * @param string $target + * + * @return string + */ + private function get_cta( + $label, + $is_primary = true, + $is_disabled = false, + $href = '', + $target = '_blank' + ) { + $classes = array(); + + if ( ! $is_primary ) { + $classes[] = 'left'; + } else { + $classes[] = 'button-primary'; + $classes[] = 'right'; + } + + if ( $is_disabled ) { + $classes[] = 'disabled'; + } + + return sprintf( + '%s', + empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"', + implode( ' ', $classes ), + $label + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_trial_period( $plan ) { + $trial_period = (int) $plan->trial_period; + + switch ( $trial_period ) { + case 30: + return 'month'; + case 60: + return '2 months'; + default: + return "{$plan->trial_period} days"; + } + } + + /** + * Display plugin information in dialog box form. + * + * Based on core install_plugin_information() function. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function install_plugin_information() { + global $tab; + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + $args = array( + 'slug' => wp_unslash( $_REQUEST['plugin'] ), + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => true, + 'reviews' => true, + 'downloaded' => false, + 'active_installs' => true + ) + ); + + if ( is_array( $args ) ) { + $args = (object) $args; + } + + if ( ! isset( $args->per_page ) ) { + $args->per_page = 24; + } + + if ( ! isset( $args->locale ) ) { + $args->locale = get_locale(); + } + + $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); + + if ( is_wp_error( $api ) ) { + wp_die( $api ); + } + + $plugins_allowedtags = array( + 'a' => array( + 'href' => array(), + 'title' => array(), + 'target' => array(), + // Add image style for screenshots. + 'class' => array() + ), + 'style' => array(), + 'abbr' => array( 'title' => array() ), + 'acronym' => array( 'title' => array() ), + 'code' => array(), + 'pre' => array(), + 'em' => array(), + 'strong' => array(), + 'div' => array( 'class' => array() ), + 'span' => array( 'class' => array() ), + 'p' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array( 'class' => array() ), + 'i' => array( 'class' => array() ), + 'h1' => array(), + 'h2' => array(), + 'h3' => array(), + 'h4' => array(), + 'h5' => array(), + 'h6' => array(), + 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ), +// 'table' => array(), +// 'td' => array(), +// 'tr' => array(), +// 'th' => array(), +// 'thead' => array(), +// 'tbody' => array(), + ); + + $plugins_section_titles = array( + 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ), + 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ), + 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ), + 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ), + 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ), + 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ), + 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ), + ); + + // Sanitize HTML +// foreach ( (array) $api->sections as $section_name => $content ) { +// $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); +// } + + foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { + if ( isset( $api->$key ) ) { + $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); + } + } + + // Add after $api->slug is ready. + $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug ); + + $_tab = esc_attr( $tab ); + + $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English. + if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) { + $section_titles = array_keys( (array) $api->sections ); + $section = array_shift( $section_titles ); + } + + iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) ); + + $_with_banner = ''; + +// var_dump($api->banners); + if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) { + $_with_banner = 'with-banner'; + $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low']; + $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high']; + ?> + + '; + echo "

    {$api->name}

    "; + echo "
    \n"; + + foreach ( (array) $api->sections as $section_name => $content ) { + if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { + continue; + } + + if ( isset( $plugins_section_titles[ $section_name ] ) ) { + $title = $plugins_section_titles[ $section_name ]; + } else { + $title = ucwords( str_replace( '_', ' ', $section_name ) ); + } + + $class = ( $section_name === $section ) ? ' class="current"' : ''; + $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); + $href = esc_url( $href ); + $san_section = esc_attr( $section_name ); + echo "\t$title\n"; + } + + echo "
    \n"; + + ?> +
    +
    + is_paid ) : ?> + plans ) ) : ?> +
    + plans as $plan ) : ?> + pricing ) ) { + continue; + } + + /** + * @var FS_Plugin_Plan $plan + */ + ?> + pricing[0] ?> + is_multi_cycle() ?> +
    +

    slug ), $plan->title ) ) ?>

    + has_annual() ?> + has_monthly() ?> + +
    + + pricing[0]->annual_discount_percentage() : 0 ?> + 0 ) : ?> + slug ), $annual_discount . '%' ) ?> + +
      +
    + get_actions_dropdown( $api, $plan ) ?> +
    + has_trial() ) : ?> + get_trial_period( $plan ) ?> +
      +
    • + slug ), $trial_period ) ) ?> +
    • +
    • + slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?> +
    • +
    + +
    +
    +
    + + + +
    +

    slug ) ?>

    +
      + version ) ) { ?> +
    • + slug ); ?> + : version; ?>
    • + author ) ) { + ?> +
    • + slug ); ?> + : author, '_blank' ); ?> +
    • + last_updated ) ) { + ?> +
    • slug ); ?> + : + slug ), + human_time_diff( strtotime( $api->last_updated ) ) + ) ) ?> +
    • + requires ) ) { + ?> +
    • + slug ) ?> + : slug ), $api->requires ) ) ?> +
    • + tested ) ) { + ?> +
    • + slug ); ?> + : tested; ?> +
    • + downloaded ) ) { + ?> +
    • + slug ) ?> + : downloaded ) ? + /* translators: %s: 1 or One (Number of times downloaded) */ + fs_text_inline( '%s time', 'x-time', $api->slug ) : + /* translators: %s: Number of times downloaded */ + fs_text_inline( '%s times', 'x-times', $api->slug ) + ), + number_format_i18n( $api->downloaded ) + ) ); ?> +
    • + slug ) && true == $api->is_wp_org_compliant ) { + ?> +
    • slug ) ?> + » +
    • + homepage ) ) { + ?> +
    • slug ) ?> + » +
    • + donate_link ) && empty( $api->contributors ) ) { + ?> +
    • slug ) ?> + » +
    • + +
    +
    + rating ) ) { ?> +

    slug ); ?>

    + $api->rating, + 'type' => 'percent', + 'number' => $api->num_ratings + ) ); ?> + (slug ), + sprintf( + ( ( 1 == $api->num_ratings ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s rating', 'x-rating', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) + ), + number_format_i18n( $api->num_ratings ) + ) ) ) ?>) + + ratings ) && array_sum( (array) $api->ratings ) > 0 ) { + foreach ( $api->ratings as $key => $ratecount ) { + // Avoid div-by-zero. + $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; + $stars_label = sprintf( + ( ( 1 == $key ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s star', 'x-star', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s stars', 'x-stars', $api->slug ) + ), + number_format_i18n( $key ) + ); + ?> +
    + + + + + +
    + contributors ) ) { + ?> +

    slug ); ?>

    +
      + contributors as $contrib_username => $contrib_profile ) { + if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { + continue; + } + if ( empty( $contrib_username ) ) { + $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); + } + $contrib_username = sanitize_user( $contrib_username ); + if ( empty( $contrib_profile ) ) { + echo "
    • {$contrib_username}
    • "; + } else { + echo "
    • {$contrib_username}
    • "; + } + } + ?> +
    + donate_link ) ) { ?> + slug ) ?> + » + + +
    +
    + tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; + } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; + } + + foreach ( (array) $api->sections as $section_name => $content ) { + $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); + $content = links_add_target( $content, '_blank' ); + + $san_section = esc_attr( $section_name ); + + $display = ( $section_name === $section ) ? 'block' : 'none'; + + if ( 'description' === $section_name && + ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || + ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) + ) { + $missing_notice = array( + 'type' => 'error', + 'id' => md5( microtime() ), + 'message' => $api->is_paid ? + fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : + fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), + ); + fs_require_template( 'admin-notice.php', $missing_notice ); + } + echo "\t
    \n"; + echo $content; + echo "\t
    \n"; + } + echo "
    \n"; + echo "
    \n"; + echo "\n"; // #plugin-information-scrollable + echo "\n"; + ?> + + 'You are just one step away - %s', + * + * We can use the filter: + * fs_override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ), '{plugin_slug}' ); + * + * Or with the Freemius instance: + * + * my_freemius->override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ) ); + */ + global $fs_text; + + $fs_text = array( + 'account' => _fs_text( 'Account' ), + 'addon' => _fs_text( 'Add-On' ), + 'contact-us' => _fs_text( 'Contact Us' ), + 'contact-support' => _fs_text( 'Contact Support' ), + 'change-ownership' => _fs_text( 'Change Ownership' ), + 'support' => _fs_text( 'Support' ), + 'support-forum' => _fs_text( 'Support Forum' ), + 'add-ons' => _fs_text( 'Add-Ons' ), + 'upgrade' => _fs_x( 'Upgrade', 'verb' ), + 'awesome' => _fs_text( 'Awesome' ), + 'pricing' => _fs_x( 'Pricing', 'noun' ), + 'price' => _fs_x( 'Price', 'noun' ), + 'unlimited-updates' => _fs_text( 'Unlimited Updates' ), + 'downgrade' => _fs_x( 'Downgrade', 'verb' ), + 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ), + 'cancel-trial' => _fs_text( 'Cancel Trial' ), + 'free-trial' => _fs_text( 'Free Trial' ), + 'start-free-x' => _fs_text( 'Start my free %s' ), + 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ), + 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ), + 'details' => _fs_text( 'Details' ), + 'account-details' => _fs_text( 'Account Details' ), + 'delete' => _fs_x( 'Delete', 'verb' ), + 'show' => _fs_x( 'Show', 'verb' ), + 'hide' => _fs_x( 'Hide', 'verb' ), + 'edit' => _fs_x( 'Edit', 'verb' ), + 'update' => _fs_x( 'Update', 'verb' ), + 'date' => _fs_text( 'Date' ), + 'amount' => _fs_text( 'Amount' ), + 'invoice' => _fs_text( 'Invoice' ), + 'billing' => _fs_text( 'Billing' ), + 'payments' => _fs_text( 'Payments' ), + 'delete-account' => _fs_text( 'Delete Account' ), + 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ), + 'plan' => _fs_x( 'Plan', 'as product pricing plan' ), + 'change-plan' => _fs_text( 'Change Plan' ), + 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ), + 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ), + 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ), + 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ), + 'new' => _fs_text( 'New' ), + 'free' => _fs_text( 'Free' ), + 'trial' => _fs_x( 'Trial', 'as trial plan' ), + 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ), + 'purchase' => _fs_x( 'Purchase', 'verb' ), + 'purchase-license' => _fs_text( 'Purchase License' ), + 'buy' => _fs_x( 'Buy', 'verb' ), + 'buy-license' => _fs_text( 'Buy License' ), + 'license-single-site' => _fs_text( 'Single Site License' ), + 'license-unlimited' => _fs_text( 'Unlimited Licenses' ), + 'license-x-sites' => _fs_text( 'Up to %s Sites' ), + 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ), + 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ), + 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ), + 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ), + 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now', + '%s - plugin name. As complete "Jetpack" activation now' ), + 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ), + 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ), + 'error' => _fs_text( 'Error' ), + 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), + 'learn-more' => _fs_text( 'Learn more' ), + + #region Affiliation + 'affiliation' => _fs_text( 'Affiliation' ), + 'affiliate' => _fs_text( 'Affiliate' ), + 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ), + 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ), + 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ), + 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ), + 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ), + 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ), + 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ), + 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ), + 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ), + 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ), + 'program-summary' => _fs_text( 'Program Summary' ), + 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ), + 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ), + 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ), + 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ), + 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ), + 'become-an-affiliate' => _fs_text( 'Become an affiliate' ), + 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ), + 'full-name' => _fs_text( 'Full name' ), + 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ), + 'promotion-methods' => _fs_text( 'Promotion methods' ), + 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ), + 'mobile-apps' => _fs_text( 'Mobile apps' ), + 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ), + 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ), + 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ), + 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ), + 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ), + 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ), + 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ), + 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ), + 'add-another-domain' => _fs_text( 'Add another domain' ), + 'remove' => _fs_x( 'Remove', 'Remove domain' ), + 'email-address-is-required' => _fs_text( 'Email address is required.' ), + 'domain-is-required' => _fs_text( 'Domain is required.' ), + 'invalid-domain' => _fs_text( 'Invalid domain' ), + 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ), + 'processing' => _fs_text( 'Processing...' ), + 'non-expiring' => _fs_text( 'Non-expiring' ), + 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ), + #endregion Affiliation + + #region Account + 'expiration' => _fs_x( 'Expiration', 'as expiration date' ), + 'license' => _fs_x( 'License', 'as software license' ), + 'not-verified' => _fs_text( 'not verified' ), + 'verify-email' => _fs_text( 'Verify Email' ), + 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ), + 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ), + 'no-expiration' => _fs_text( 'No expiration' ), + 'expired' => _fs_text( 'Expired' ), + 'cancelled' => _fs_text( 'Cancelled' ), + 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ), + 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ), + /* translators: %s: Version number (e.g. 4.6 or higher) */ + 'x-or-higher' => _fs_text( '%s or higher' ), + 'version' => _fs_x( 'Version', 'as plugin version' ), + 'name' => _fs_text( 'Name' ), + 'email' => _fs_text( 'Email' ), + 'email-address' => _fs_text( 'Email address' ), + 'verified' => _fs_text( 'Verified' ), + 'module' => _fs_text( 'Module' ), + 'module-type' => _fs_text( 'Module Type' ), + 'plugin' => _fs_text( 'Plugin' ), + 'plugins' => _fs_text( 'Plugins' ), + 'theme' => _fs_text( 'Theme' ), + 'themes' => _fs_text( 'Themes' ), + 'path' => _fs_x( 'Path', 'as file/folder path' ), + 'title' => _fs_text( 'Title' ), + 'free-version' => _fs_text( 'Free version' ), + 'premium-version' => _fs_text( 'Premium version' ), + 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ), + 'id' => _fs_text( 'ID' ), + 'users' => _fs_text( 'Users' ), + 'module-installs' => _fs_text( '%s Installs' ), + 'sites' => _fs_x( 'Sites', 'like websites' ), + 'user-id' => _fs_text( 'User ID' ), + 'site-id' => _fs_text( 'Site ID' ), + 'public-key' => _fs_text( 'Public Key' ), + 'secret-key' => _fs_text( 'Secret Key' ), + 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ), + 'no-id' => _fs_text( 'No ID' ), + 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ), + 'sync' => _fs_x( 'Sync', 'as synchronize' ), + 'activate-license' => _fs_text( 'Activate License' ), + 'activate-free-version' => _fs_text( 'Activate Free Version' ), + 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ), + 'activating-license' => _fs_text( 'Activating license...' ), + 'change-license' => _fs_text( 'Change License' ), + 'update-license' => _fs_text( 'Update License' ), + 'deactivate-license' => _fs_text( 'Deactivate License' ), + 'activate' => _fs_text( 'Activate' ), + 'deactivate' => _fs_text( 'Deactivate' ), + 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ), + 'skip-and-x' => _fs_text( 'Skip & %s' ), + 'no-deactivate' => _fs_text( 'No - just deactivate' ), + 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ), + 'active' => _fs_x( 'Active', 'active mode' ), + 'is-active' => _fs_x( 'Is Active', 'is active mode?' ), + 'install-now' => _fs_text( 'Install Now' ), + 'install-update-now' => _fs_text( 'Install Update Now' ), + 'more-information-about-x' => _fs_text( 'More information about %s' ), + 'localhost' => _fs_text( 'Localhost' ), + 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ), + 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ), + 'last-license' => _fs_text( 'Last license' ), + 'what-is-your-x' => _fs_text( 'What is your %s?' ), + 'activate-this-addon' => _fs_text( 'Activate this add-on' ), + 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ), + 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ), + 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ), + 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ), + 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ), + 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ), + 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ), + 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ), + #endregion Account + + 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ), + 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ), + #region Plugin Deactivation + 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ), + 'quick-feedback' => _fs_text( 'Quick feedback' ), + 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ), + 'deactivating' => _fs_text( 'deactivating' ), + 'deactivation' => _fs_text( 'Deactivation' ), + 'theme-switch' => _fs_text( 'Theme Switch' ), + 'switching' => _fs_text( 'switching' ), + 'switch' => _fs_text( 'Switch' ), + 'activate-x' => _fs_text( 'Activate %s' ), + 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ), + 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ), + 'cancel' => _fs_text( 'Cancel' ), + 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ), + 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ), + 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ), + 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ), + 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ), + 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ), + 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ), + 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ), + 'reason-other' => _fs_x( 'Other', + 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ), + 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ), + 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ), + 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ), + 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ), + 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ), + 'reason-not-working' => _fs_text( 'The %s is not working' ), + 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ), + 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ), + 'placeholder-feature' => _fs_text( 'What feature?' ), + 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ), + 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ), + 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ), + 'reason-didnt-work' => _fs_text( "The %s didn't work" ), + 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ), + 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ), + #endregion Plugin Deactivation + + #region Connect + 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ), + 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ), + 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ), + 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ), + 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ), + 'complete-the-install' => _fs_text( 'complete the install' ), + 'start-the-trial' => _fs_text( 'start the trial' ), + 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ), + 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ), + 'what-permissions' => _fs_text( 'What permissions are being granted?' ), + 'permissions-profile' => _fs_text( 'Your Profile Overview' ), + 'permissions-profile_desc' => _fs_text( 'Name and email address' ), + 'permissions-site' => _fs_text( 'Your Site Overview' ), + 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ), + 'permissions-events' => _fs_text( 'Current %s Events' ), + 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ), + 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ), + 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ), + 'permissions-admin-notices' => _fs_text( 'Admin Notices' ), + 'permissions-newsletter' => _fs_text( 'Newsletter' ), + 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ), + 'privacy-policy' => _fs_text( 'Privacy Policy' ), + 'tos' => _fs_text( 'Terms of Service' ), + 'activating' => _fs_x( 'Activating', 'as activating plugin' ), + 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ), + 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ), + 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ), + 'skip' => _fs_x( 'Skip', 'verb' ), + 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ), + 'resend-activation-email' => _fs_text( 'Re-send activation email' ), + 'license-key' => _fs_text( 'License key' ), + 'send-license-key' => _fs_text( 'Send License Key' ), + 'sending-license-key' => _fs_text( 'Sending license key' ), + 'have-license-key' => _fs_text( 'Have a license key?' ), + 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ), + 'cant-find-license-key' => _fs_text( "Can't find your license key?" ), + 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ), + 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ), + 'opt-in' => _fs_text( 'Opt In' ), + 'opt-out' => _fs_text( 'Opt Out' ), + 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ), + 'opting-out' => _fs_text( 'Opting out...' ), + 'opting-in' => _fs_text( 'Opting in...' ), + 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ), + 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ), + 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ), + 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ), + 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ), + 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ), + 'continue' => _fs_text( 'Continue' ), + 'allow' => _fs_text( 'allow' ), + 'delegate' => _fs_text( 'delegate' ), + #endregion Connect + + #region Screenshots + 'screenshots' => _fs_text( 'Screenshots' ), + 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ), + #endregion Screenshots + + #region Debug + 'freemius-debug' => _fs_text( 'Freemius Debug' ), + 'on' => _fs_x( 'On', 'as turned on' ), + 'off' => _fs_x( 'Off', 'as turned off' ), + 'debugging' => _fs_x( 'Debugging', 'as code debugging' ), + 'freemius-state' => _fs_text( 'Freemius State' ), + 'connected' => _fs_x( 'Connected', 'as connection was successful' ), + 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ), + 'api' => _fs_x( 'API', 'as application program interface' ), + 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ), + 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ), + 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ), + 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ), + 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ), + 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ), + 'actions' => _fs_text( 'Actions' ), + 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ), + 'start-fresh' => _fs_text( 'Start Fresh' ), + 'clear-api-cache' => _fs_text( 'Clear API Cache' ), + 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ), + 'scheduled-crons' => _fs_text( 'Scheduled Crons' ), + 'cron-type' => _fs_text( 'Cron Type' ), + 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ), + 'module-licenses' => _fs_text( '%s Licenses' ), + 'debug-log' => _fs_text( 'Debug Log' ), + 'all' => _fs_text( 'All' ), + 'file' => _fs_text( 'File' ), + 'function' => _fs_text( 'Function' ), + 'process-id' => _fs_text( 'Process ID' ), + 'logger' => _fs_text( 'Logger' ), + 'message' => _fs_text( 'Message' ), + 'download' => _fs_text( 'Download' ), + 'filter' => _fs_text( 'Filter' ), + 'type' => _fs_text( 'Type' ), + 'all-types' => _fs_text( 'All Types' ), + 'all-requests' => _fs_text( 'All Requests' ), + #endregion Debug + + #region Expressions + 'congrats' => _fs_x( 'Congrats', 'as congratulations' ), + 'oops' => _fs_x( 'Oops', 'exclamation' ), + 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ), + 'woot' => _fs_x( 'W00t', + '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ), + 'right-on' => _fs_x( 'Right on', 'a positive response' ), + 'hmm' => _fs_x( 'Hmm', + 'something somebody says when they are thinking about what you have just said. ' ), + 'ok' => _fs_text( 'O.K' ), + 'hey' => _fs_x( 'Hey', 'exclamation' ), + 'heads-up' => _fs_x( 'Heads up', + 'advance notice of something that will need attention.' ), + #endregion Expressions + + #region Admin Notices + 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ), + 'you-are-good' => _fs_text( 'You are all good!' ), + 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ), + 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ), + 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ), + 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ), + 'x-updated' => _fs_text( 'You have successfully updated your %s.' ), + 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ), + 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ), + 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ), + 'no-cc-required' => _fs_text( 'No credit card required' ), + 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ), + 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ), + 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ), + 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ), + 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ), + 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ), + 'download-latest-version' => _fs_text( 'Download the latest version' ), + 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ), + 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ), + 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ), + 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.', + '%s - product name, e.g. Facebook add-on was successfully...' ), + 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ), + 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ), + 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ), + 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ), + 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ), + 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ), + 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ), + 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ), + 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ), + 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ), + 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ), + 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ), + 'server-error-message' => _fs_text( 'Error received from the server:' ), + 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ), + 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ), + 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ), + 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ), + 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ), + 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ), + 'please-download-x' => _fs_text( 'Please download %s.' ), + 'latest-x-version' => _fs_x( 'the latest %s version here', + '%s - plan name, as the latest professional version here' ), + 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ), + 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ), + 'starting-trial' => _fs_text( 'Starting trial' ), + 'please-wait' => _fs_text( 'Please wait' ), + 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ), + 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ), + 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ), + 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ), + 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ), + 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ), + 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ), + 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ), + 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ), + 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ), + 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ), + 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ), + 'contact-us-here' => _fs_text( 'Please contact us here' ), + 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ), + #endregion Admin Notices + #region Connectivity Issues + 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ), + 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ), + 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ), + 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ), + 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ), + 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.', + 'as pluginX requires an access to our API' ), + 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ), + 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ), + 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ), + 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ), + 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ), + 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ), + 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ), + 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ), + 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ), + 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ), + 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ), + 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ), + 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ), + 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ), + 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ), + 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', + '%1$s - plugin title, %2$s - API domain' ), + 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ), + #endregion Connectivity Issues + #region Change Owner + 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ), + 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ), + 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ), + #endregion Change Owner + 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.', + 'addonX cannot run without pluginY' ), + 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ), + 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.', + 'pluginX activation was successfully...' ), + 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ), + 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ), + 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ), + #-------------------------------------------------------------------------------- + #region Add-On Licensing + #-------------------------------------------------------------------------------- + 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ), + 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ), + #endregion + #-------------------------------------------------------------------------------- + #region Billing Cycles + #-------------------------------------------------------------------------------- + 'monthly' => _fs_x( 'Monthly', 'as every month' ), + 'mo' => _fs_x( 'mo', 'as monthly period' ), + 'annual' => _fs_x( 'Annual', 'as once a year' ), + 'annually' => _fs_x( 'Annually', 'as once a year' ), + 'once' => _fs_x( 'Once', 'as once a year' ), + 'year' => _fs_x( 'year', 'as annual period' ), + 'lifetime' => _fs_text( 'Lifetime' ), + 'best' => _fs_x( 'Best', 'e.g. the best product' ), + 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ), + 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ), + #endregion Billing Cycles + 'view-details' => _fs_text( 'View details' ), + #-------------------------------------------------------------------------------- + #region Trial + #-------------------------------------------------------------------------------- + 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ), + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ), + /* translators: %s: Link to freemius.com */ + 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ), + + #endregion + #-------------------------------------------------------------------------------- + #region Billing Details + #-------------------------------------------------------------------------------- + 'business-name' => _fs_text( 'Business name' ), + 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ), + 'address-line-n' => _fs_text( 'Address Line %d' ), + 'country' => _fs_text( 'Country' ), + 'select-country' => _fs_text( 'Select Country' ), + 'city' => _fs_text( 'City' ), + 'town' => _fs_text( 'Town' ), + 'state' => _fs_text( 'State' ), + 'province' => _fs_text( 'Province' ), + 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ), + #endregion + #-------------------------------------------------------------------------------- + #region Module Installation + #-------------------------------------------------------------------------------- + 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ), + 'auto-installation' => _fs_text( 'Automatic Installation' ), + /* translators: %s: Number of seconds */ + 'x-sec' => _fs_text( '%s sec' ), + 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ), + 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ), + 'cancel-installation' => _fs_text( 'Cancel Installation' ), + 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ), + 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ), + 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ), + 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ), + 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ), + 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ), + 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ), + #endregion + + /* translators: %s: Page name */ + 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ), + 'pci-compliant' => _fs_text( 'PCI compliant' ), + 'view-paid-features' => _fs_text( 'View paid features' ), + ); + + /** + * Localization of the strings in the plugin/theme info dialog box. + * + * $fs_module_info_text should ONLY include strings that are not located in $fs_text. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + global $fs_module_info_text; + + $fs_module_info_text = array( + 'description' => _fs_x( 'Description', 'Plugin installer section title' ), + 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ), + 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ), + 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ), + 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ), + 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ), + /* translators: %s: 1 or One */ + 'x-star' => _fs_text( '%s star' ), + /* translators: %s: Number larger than 1 */ + 'x-stars' => _fs_text( '%s stars' ), + /* translators: %s: 1 or One */ + 'x-rating' => _fs_text( '%s rating' ), + /* translators: %s: Number larger than 1 */ + 'x-ratings' => _fs_text( '%s ratings' ), + /* translators: %s: 1 or One (Number of times downloaded) */ + 'x-time' => _fs_text( '%s time' ), + /* translators: %s: Number of times downloaded */ + 'x-times' => _fs_text( '%s times' ), + /* translators: %s: # of stars (e.g. 5 stars) */ + 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ), + 'last-updated:' => _fs_text( 'Last Updated' ), + 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ), + 'author:' => _fs_x( 'Author:', 'as the plugin author' ), + 'compatible-up-to:' => _fs_text( 'Compatible up to:' ), + 'downloaded:' => _fs_text( 'Downloaded:' ), + 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ), + 'plugin-homepage' => _fs_text( 'Plugin Homepage' ), + 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ), + 'average-rating' => _fs_text( 'Average Rating' ), + 'based-on-x' => _fs_text( 'based on %s' ), + 'warning:' => _fs_text( 'Warning:' ), + 'contributors' => _fs_text( 'Contributors' ), + 'plugin-install' => _fs_text( 'Plugin Install' ), + 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ), + 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ), + 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ), + 'latest-installed' => _fs_text( 'Latest Version Installed' ), + ); diff --git a/freemius/includes/index.php b/freemius/includes/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/index.php @@ -0,0 +1,3 @@ + + */ + private $_default_submenu_items; + /** + * @since 1.1.3 + * + * @var string + */ + private $_first_time_path; + /** + * @since 1.2.2 + * + * @var bool + */ + private $_menu_exists; + /** + * @since 2.0.0 + * + * @var bool + */ + private $_network_menu_exists; + + #endregion Properties + + /** + * @var FS_Logger + */ + protected $_logger; + + #region Singleton + + /** + * @var FS_Admin_Menu_Manager[] + */ + private static $_instances = array(); + + /** + * @param number $module_id + * @param string $module_type + * @param string $module_unique_affix + * + * @return FS_Admin_Menu_Manager + */ + static function instance( $module_id, $module_type, $module_unique_affix ) { + $key = 'm_' . $module_id; + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $module_id, $module_type, $module_unique_affix ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_module_id = $module_id; + $this->_module_type = $module_type; + $this->_module_unique_affix = $module_unique_affix; + } + + #endregion Singleton + + #region Helpers + + private function get_option( &$options, $key, $default = false ) { + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_bool_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; + } + + #endregion Helpers + + /** + * @param array $menu + * @param bool $is_addon + */ + function init( $menu, $is_addon = false ) { + $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); + $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); + + $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); + + $this->_default_submenu_items = array(); + // @deprecated + $this->_type = 'page'; + $this->_is_top_level = true; + $this->_is_override_exact = false; + $this->_parent_slug = false; + // @deprecated + $this->_parent_type = 'page'; + + if ( isset( $menu ) ) { + if ( ! $is_addon ) { + $this->_default_submenu_items = array( + 'contact' => $this->get_bool_option( $menu, 'contact', true ), + 'support' => $this->get_bool_option( $menu, 'support', true ), + 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), + 'account' => $this->get_bool_option( $menu, 'account', true ), + 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), + 'addons' => $this->get_bool_option( $menu, 'addons', true ), + ); + + // @deprecated + $this->_type = $this->get_option( $menu, 'type', 'page' ); + } + + $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); + + if ( isset( $menu['parent'] ) ) { + $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); + // @deprecated + $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); + + // If parent's slug is different, then it's NOT a top level menu item. + $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); + } else { + /** + * If no parent then top level if: + * - Has custom admin menu ('page') + * - CPT menu type ('cpt') + */ +// $this->_is_top_level = in_array( $this->_type, array( +// 'cpt', +// 'page' +// ) ); + } + + $first_path = $this->get_option( $menu, 'first-path', false ); + + if ( ! empty( $first_path ) && is_string( $first_path ) ) { + $this->_first_time_path = $first_path; + } + } + } + + /** + * Check if top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_top_level() { + return $this->_is_top_level; + } + + /** + * Check if the page should be override on exact URL match. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_override_exact() { + return $this->_is_override_exact; + } + + + /** + * Get the path of the page the user should be forwarded to after first activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool $is_network Since 2.4.5 + * + * @return string + */ + function get_first_time_path( $is_network = false ) { + if ( empty ( $this->_first_time_path ) ) { + return $this->_first_time_path; + } + + if ( $is_network ) { + return network_admin_url( $this->_first_time_path ); + } else { + return admin_url( $this->_first_time_path ); + } + } + + /** + * Check if plugin's menu item is part of a custom top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function has_custom_parent() { + return ! $this->_is_top_level && is_string( $this->_parent_slug ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function has_menu() { + return $this->_menu_exists; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function has_network_menu() { + return $this->_network_menu_exists; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $menu_slug + * + * @since 2.1.3 + */ + function set_slug_and_network_menu_exists_flag($menu_slug ) { + $this->_menu_slug = $menu_slug; + $this->_network_menu_exists = false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $id + * @param bool $default + * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. + * + * @return bool + */ + function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { + if ( ! $ignore_menu_existence && ! $this->has_menu() ) { + return false; + } + + return fs_apply_filter( + $this->_module_unique_affix, + 'is_submenu_visible', + $this->get_bool_option( $this->_default_submenu_items, $id, $default ), + $id + ); + } + + /** + * Calculates admin settings menu slug. + * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $page + * + * @return string + */ + function get_slug( $page = '' ) { + return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? + $this->_menu_slug : + $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_slug() { + return $this->_parent_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_type() { + return $this->_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function is_cpt() { + return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || + // Back compatibility. + 'cpt' === $this->_type + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_type() { + return $this->_parent_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_raw_slug() { + return $this->_menu_slug; + } + + /** + * Get plugin's original menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_original_menu_slug() { + if ( 'cpt' === $this->_type ) { + return add_query_arg( array( + 'post_type' => $this->_menu_slug + ), 'edit.php' ); + } + + if ( false === strpos( $this->_menu_slug, '.php?' ) ) { + return $this->_menu_slug; + } else { + return $this->_module_unique_affix; + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_top_level_menu_slug() { + return $this->has_custom_parent() ? + $this->get_parent_slug() : + $this->get_raw_slug(); + } + + /** + * Is user on plugin's admin activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + * + * @return bool + */ + function is_main_settings_page() { + if ( $this->_menu_exists && + ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) + ) { + /** + * Module has a settings menu and the context page is the main settings page, so assume it's in + * activation (doesn't really check if already opted-in/skipped or not). + * + * @since 1.2.2 + */ + return true; + } + + global $pagenow; + if ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() ) { + /** + * In activation only when show_optin query string param is given. + * + * @since 1.2.2 + */ + return fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ); + } + + return false; + } + + #region Submenu Override + + /** + * Override submenu's action. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $parent_slug + * @param string $menu_slug + * @param callable $function + * + * @return false|string If submenu exist, will return the hook name. + */ + function override_submenu_action( $parent_slug, $menu_slug, $function ) { + global $submenu; + + $menu_slug = plugin_basename( $menu_slug ); + $parent_slug = plugin_basename( $parent_slug ); + + if ( ! isset( $submenu[ $parent_slug ] ) ) { + // Parent menu not exist. + return false; + } + + $found_submenu_item = false; + foreach ( $submenu[ $parent_slug ] as $submenu_item ) { + if ( $menu_slug === $submenu_item[2] ) { + $found_submenu_item = $submenu_item; + break; + } + } + + if ( false === $found_submenu_item ) { + // Submenu item not found. + return false; + } + + // Remove current function. + $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); + remove_all_actions( $hookname ); + + // Attach new action. + add_action( $hookname, $function ); + + return $hookname; + } + + #endregion Submenu Override + + #region Top level menu Override + + /** + * Find plugin's admin dashboard main menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return string[]|false + */ + private function find_top_level_menu() { + global $menu; + + $position = - 1; + $found_menu = false; + + $menu_slug = $this->get_raw_slug(); + + $hook_name = get_plugin_page_hookname( $menu_slug, '' ); + foreach ( $menu as $pos => $m ) { + if ( $menu_slug === $m[2] ) { + $position = $pos; + $found_menu = $m; + break; + } + } + + if ( false === $found_menu ) { + return false; + } + + return array( + 'menu' => $found_menu, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Find plugin's admin dashboard main submenu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return array|false + */ + private function find_main_submenu() { + global $submenu; + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + + if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { + return false; + } + + $submenu_slug = $this->get_raw_slug(); + + $position = - 1; + $found_submenu = false; + + $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); + + foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { + if ( $submenu_slug === $sub[2] ) { + $position = $pos; + $found_submenu = $sub; + } + } + + if ( false === $found_submenu ) { + return false; + } + + return array( + 'menu' => $found_submenu, + 'parent_slug' => $top_level_menu_slug, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Remove all sub-menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool If submenu with plugin's menu slug was found. + */ + private function remove_all_submenu_items() { + global $submenu; + + $menu_slug = $this->get_raw_slug(); + + if ( ! isset( $submenu[ $menu_slug ] ) ) { + return false; + } + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $submenu_ref = &$submenu; + $submenu_ref[ $menu_slug ] = array(); + + return true; + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $remove_top_level_menu + * + * @return false|array[string]mixed + */ + function remove_menu_item( $remove_top_level_menu = false ) { + $this->_logger->entrance(); + + // Find main menu item. + $top_level_menu = $this->find_top_level_menu(); + + if ( false === $top_level_menu ) { + return false; + } + + // Remove it with its actions. + remove_all_actions( $top_level_menu['hook_name'] ); + + // Remove all submenu items. + $this->remove_all_submenu_items(); + + if ( $remove_top_level_menu ) { + global $menu; + unset( $menu[ $top_level_menu['position'] ] ); + } + + return $top_level_menu; + } + + /** + * Get module's main admin setting page URL. + * + * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function main_menu_url() { + $this->_logger->entrance(); + + if ( $this->_is_top_level ) { + $menu = $this->find_top_level_menu(); + } else { + $menu = $this->find_main_submenu(); + } + + $parent_slug = isset( $menu['parent_slug'] ) ? + $menu['parent_slug'] : + 'admin.php'; + + return admin_url( $parent_slug . '?page=' . $menu['menu'][2] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param callable $function + * + * @return false|array[string]mixed + */ + function override_menu_item( $function ) { + $found_menu = $this->remove_menu_item(); + + if ( false === $found_menu ) { + return false; + } + + if ( ! $this->is_top_level() || ! $this->is_cpt() ) { + $menu_slug = plugin_basename( $this->get_slug() ); + + $hookname = get_plugin_page_hookname( $menu_slug, '' ); + + // Override menu action. + add_action( $hookname, $function ); + } else { + global $menu; + + // Remove original CPT menu. + unset( $menu[ $found_menu['position'] ] ); + + // Create new top-level menu action. + $hookname = self::add_page( + $found_menu['menu'][3], + $found_menu['menu'][0], + 'manage_options', + $this->get_slug(), + $function, + $found_menu['menu'][6], + $found_menu['position'] + ); + } + + return $hookname; + } + + /** + * Adds a counter to the module's top level menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param int $counter + * @param string $class + */ + function add_counter_to_menu_item( $counter = 1, $class = '' ) { + global $menu, $submenu; + + $mask = '%s '; + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $menu_ref = &$menu; + $submenu_ref = &$submenu; + + if ( $this->_is_top_level ) { + // Find main menu item. + $found_menu = $this->find_top_level_menu(); + + if ( false !== $found_menu ) { + // Override menu label. + $menu_ref[ $found_menu['position'] ][0] = sprintf( + $mask, + $found_menu['menu'][0], + $class, + $counter + ); + } + } else { + $found_submenu = $this->find_main_submenu(); + + if ( false !== $found_submenu ) { + // Override menu label. + $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( + $mask, + $found_submenu['menu'][0], + $class, + $counter + ); + } + } + } + + #endregion Top level menu Override + + /** + * Add a top-level menu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_menu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * @param string $icon_url The URL to the icon to be used for this menu. + * * Pass a base64-encoded SVG using a data URI, which will be colored to + * match the color scheme. This should begin with + * 'data:image/svg+xml;base64,'. + * * Pass the name of a Dashicons helper class to use a font icon, + * e.g. 'dashicons-chart-pie'. + * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added + * via CSS. + * @param int $position The position in the menu order this one should appear. + * + * @return string The resulting page's hook_suffix. + */ + static function add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $fn = 'add_menu' . '_page'; + + return $fn( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * @param string $icon_url + * @param int|null $position + * + * @return string + */ + function add_page_and_update( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $this->_menu_slug = $menu_slug; + $this->_is_top_level = true; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add a submenu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_submenu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $parent_slug The slug name for the parent menu (or the file name of a standard + * WordPress admin page). + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * + * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability + * required. + */ + static function add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $fn = 'add_submenu' . '_page'; + + return $fn( $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + + /** + * Add sub page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $parent_slug + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * + * @return string + */ + function add_subpage_and_update( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $this->_menu_slug = $menu_slug; + $this->_parent_slug = $parent_slug; + $this->_is_top_level = false; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-admin-notice-manager.php b/freemius/includes/managers/class-fs-admin-notice-manager.php new file mode 100644 index 0000000..cc9d7f9 --- /dev/null +++ b/freemius/includes/managers/class-fs-admin-notice-manager.php @@ -0,0 +1,472 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Notice_Manager( + $id, + $title, + $module_unique_affix, + $is_network_and_blog_admins, + $network_level_or_blog_id + ); + } + + return self::$_instances[ $key ]; + } + + /** + * @param string $id + * @param string $title + * @param string $module_unique_affix + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and + * blog admin pages. + * @param bool|int $network_level_or_blog_id + */ + protected function __construct( + $id, + $title = '', + $module_unique_affix = '', + $is_network_and_blog_admins = false, + $network_level_or_blog_id = false + ) { + $this->_id = $id; + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_title = ! empty( $title ) ? $title : ''; + $this->_module_unique_affix = $module_unique_affix; + $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); + + if ( is_multisite() ) { + $this->_is_network_notices = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_notices = false; + } + + $is_network_admin = fs_is_network_admin(); + $is_blog_admin = fs_is_blog_admin(); + + if ( ( $this->_is_network_notices && $is_network_admin ) || + ( ! $this->_is_network_notices && $is_blog_admin ) || + ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) + ) { + if ( 0 < count( $this->_sticky_storage ) ) { + $ajax_action_suffix = str_replace( ':', '-', $this->_id ); + + // If there are sticky notices for the current slug, add a callback + // to the AJAX action that handles message dismiss. + add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( + &$this, + 'dismiss_notice_ajax_callback' + ) ); + + foreach ( $this->_sticky_storage as $msg ) { + // Add admin notice. + $this->add( + $msg['message'], + $msg['title'], + $msg['type'], + true, + $msg['id'], + false, + isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, + ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, + $is_network_and_blog_admins + ); + } + } + } + } + + /** + * Remove sticky message by ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + */ + function dismiss_notice_ajax_callback() { + $this->_sticky_storage->remove( $_POST['message_id'] ); + wp_die(); + } + + /** + * Rendered sticky message dismiss JavaScript. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + static function _add_sticky_dismiss_javascript() { + $params = array(); + fs_require_once_template( 'sticky-admin-notice-js.php', $params ); + } + + private static $_added_sticky_javascript = false; + + /** + * Hook to the admin_footer to add sticky message dismiss JavaScript handler. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + private static function has_sticky_messages() { + if ( ! self::$_added_sticky_javascript ) { + add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); + } + } + + /** + * Handle admin_notices by printing the admin messages stacked in the queue. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + */ + function _admin_notices_hook() { + if ( function_exists( 'current_user_can' ) && + ! current_user_can( 'manage_options' ) + ) { + // Only show messages to admins. + return; + } + + + $show_admin_notices = ( ! $this->is_gutenberg_page() ); + + foreach ( $this->_notices as $id => $msg ) { + if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { + if ( get_current_user_id() != $msg['wp_user_id'] ) { + continue; + } + } + + /** + * Added a filter to control the visibility of admin notices. + * + * Usage example: + * + * /** + * * @param bool $show + * * @param array $msg { + * * @var string $message The actual message. + * * @var string $title An optional message title. + * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). + * * @var string $id The unique identifier of the message. + * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. + * * @var string $plugin The product's title. + * * @var string $wp_user_id An optional WP user ID that this admin notice is for. + * * } + * * + * * @return bool + * *\/ + * function my_custom_show_admin_notice( $show, $msg ) { + * if ('trial_promotion' != $msg['id']) { + * return false; + * } + * + * return $show; + * } + * + * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); + * + * @author Vova Feldman + * @since 2.2.0 + */ + $show_notice = call_user_func_array( 'fs_apply_filter', array( + $this->_module_unique_affix, + 'show_admin_notice', + $show_admin_notices, + $msg + ) ); + + if ( true !== $show_notice ) { + continue; + } + + fs_require_template( 'admin-notice.php', $msg ); + + if ( $msg['sticky'] ) { + self::has_sticky_messages(); + } + } + } + + /** + * Enqueue common stylesheet to style admin notice. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _enqueue_styles() { + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + } + + /** + * Check if the current page is the Gutenberg block editor. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.3 + * + * @return bool + */ + function is_gutenberg_page() { + if ( function_exists( 'is_gutenberg_page' ) && + is_gutenberg_page() + ) { + // The Gutenberg plugin is on. + return true; + } + + $current_screen = get_current_screen(); + + if ( method_exists( $current_screen, 'is_block_editor' ) && + $current_screen->is_block_editor() + ) { + // Gutenberg page on 5+. + return true; + } + + return false; + } + + /** + * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + * @param bool $store_if_sticky + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + * + * @uses add_action() + */ + function add( + $message, + $title = '', + $type = 'success', + $is_sticky = false, + $id = '', + $store_if_sticky = true, + $wp_user_id = null, + $plugin_title = null, + $is_network_and_blog_admins = false + ) { + $notices_type = $this->get_notices_type(); + + if ( empty( $this->_notices ) ) { + if ( ! $is_network_and_blog_admins ) { + add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); + } else { + add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); + add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); + } + + add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); + } + + if ( '' === $id ) { + $id = md5( $title . ' ' . $message . ' ' . $type ); + } + + $message_object = array( + 'message' => $message, + 'title' => $title, + 'type' => $type, + 'sticky' => $is_sticky, + 'id' => $id, + 'manager_id' => $this->_id, + 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), + 'wp_user_id' => $wp_user_id, + ); + + if ( $is_sticky && $store_if_sticky ) { + $this->_sticky_storage->{$id} = $message_object; + } + + $this->_notices[ $id ] = $message_object; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string|string[] $ids + */ + function remove_sticky( $ids ) { + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + foreach ( $ids as $id ) { + // Remove from sticky storage. + $this->_sticky_storage->remove( $id ); + + if ( isset( $this->_notices[ $id ] ) ) { + unset( $this->_notices[ $id ] ); + } + } + } + + /** + * Check if sticky message exists by id. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param $id + * + * @return bool + */ + function has_sticky( $id ) { + return isset( $this->_sticky_storage[ $id ] ); + } + + /** + * Adds sticky admin notification. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $message + * @param string $id Message ID + * @param string $title + * @param string $type + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + */ + function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false ) { + if ( ! empty( $this->_module_unique_affix ) ) { + $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); + $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); + } + + $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + } + + /** + * Clear all sticky messages. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + function clear_all_sticky() { + $this->_sticky_storage->clear_all(); + } + + #-------------------------------------------------------------------------------- + #region Helper Method + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_notices_type() { + return $this->_is_network_notices ? + 'network_admin_notices' : + 'admin_notices'; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-cache-manager.php b/freemius/includes/managers/class-fs-cache-manager.php new file mode 100644 index 0000000..c6cb282 --- /dev/null +++ b/freemius/includes/managers/class-fs-cache-manager.php @@ -0,0 +1,326 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_options = FS_Option_Manager::get_manager( $id, true, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param $id + * + * @return FS_Cache_Manager + */ + static function get_manager( $id ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_MANAGERS[ $id ] ) ) { + self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); + } + + return self::$_MANAGERS[ $id ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + function is_empty() { + $this->_logger->entrance(); + + return $this->_options->is_empty(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + function clear() { + $this->_logger->entrance(); + + $this->_options->clear( true ); + } + + /** + * Delete cache manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_options->delete(); + } + + /** + * Check if there's a cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * + * @return bool + */ + function has( $key ) { + $cache_entry = $this->_options->get_option( $key, false ); + + return ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ); + } + + /** + * Check if there's a valid cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param null|int $expiration Since 1.2.2.7 + * + * @return bool + */ + function has_valid( $key, $expiration = null ) { + $cache_entry = $this->_options->get_option( $key, false ); + + $is_valid = ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ); + + if ( $is_valid && + is_numeric( $expiration ) && + isset( $cache_entry->created ) && + is_numeric( $cache_entry->created ) && + $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME + ) { + /** + * Even if the cache is still valid, since we are checking for validity + * with an explicit expiration period, if the period has past, return + * `false` as if the cache is invalid. + * + * @since 1.2.2.7 + */ + $is_valid = false; + } + + return $is_valid; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get_valid( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $value + * @param int $expiration + * @param int $created Since 2.0.0 Cache creation date. + */ + function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = new stdClass(); + + $cache_entry->result = $value; + $cache_entry->created = $created; + $cache_entry->timestamp = $created + $expiration; + $this->_options->set_option( $key, $cache_entry, true ); + } + + /** + * Get cached record expiration, or false if not cached or expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param string $key + * + * @return bool|int + */ + function get_record_expiration( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->timestamp; + } + + return false; + } + + /** + * Purge cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + */ + function purge( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $this->_options->unset_option( $key, true ); + } + + /** + * Extend cached item caching period. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $key + * @param int $expiration + * + * @return bool + */ + function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( ! is_object( $cache_entry ) || + ! isset( $cache_entry->timestamp ) || + ! is_numeric( $cache_entry->timestamp ) + ) { + return false; + } + + $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); + + return true; + } + + /** + * Set cached item as expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $key + */ + function expire( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + // Set to expired. + $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; + $this->_options->set_option( $key, $cache_entry, true ); + } + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $this->_options->migrate_to_network(); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-gdpr-manager.php b/freemius/includes/managers/class-fs-gdpr-manager.php new file mode 100644 index 0000000..a64abb0 --- /dev/null +++ b/freemius/includes/managers/class-fs-gdpr-manager.php @@ -0,0 +1,202 @@ +_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); + $this->_wp_user_id = Freemius::get_current_wp_user_id(); + $this->_option_name = "u{$this->_wp_user_id}"; + $this->_data = $this->_storage->get_option( $this->_option_name, array() ); + $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); + + if ( ! is_array( $this->_data ) ) { + $this->_data = array(); + } + } + + /** + * Update a GDPR option for the current admin and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param string $name + * @param mixed $value + */ + private function update_option( $name, $value ) { + $this->_data[ $name ] = $value; + + $this->_storage->set_option( $this->_option_name, $this->_data, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @return bool|null + */ + public function is_required() { + return isset( $this->_data['required'] ) ? + $this->_data['required'] : + null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param bool $is_required + */ + public function store_is_required( $is_required ) { + $this->update_option( 'required', $is_required ); + } + + /** + * Checks if the GDPR opt-in sticky notice is currently shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function is_opt_in_notice_shown() { + return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + } + + /** + * Remove the GDPR opt-in sticky notice. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function remove_opt_in_notice() { + $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + + $this->disable_opt_in_notice(); + } + + /** + * Prevents the opt-in message from being added/shown. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + public function disable_opt_in_notice() { + $this->update_option( 'show_opt_in_notice', false ); + } + + /** + * Checks if a GDPR opt-in message needs to be shown to the current admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function should_show_opt_in_notice() { + return ( + ! isset( $this->_data['show_opt_in_notice'] ) || + true === $this->_data['show_opt_in_notice'] + ); + } + + /** + * Get the last time the GDPR opt-in notice was shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return false|int + */ + public function last_time_notice_was_shown() { + return isset( $this->_data['notice_shown_at'] ) ? + $this->_data['notice_shown_at'] : + false; + } + + /** + * Update the timestamp of the last time the GDPR opt-in message was shown to now. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function notice_was_just_shown() { + $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); + } + + /** + * @param string $message + * @param string|null $plugin_title + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { + $this->_notices->add_sticky( + $message, + "gdpr_optin_actions_{$this->_wp_user_id}", + '', + 'promotion', + true, + $this->_wp_user_id, + $plugin_title, + true + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-key-value-storage.php b/freemius/includes/managers/class-fs-key-value-storage.php new file mode 100644 index 0000000..713df6f --- /dev/null +++ b/freemius/includes/managers/class-fs-key-value-storage.php @@ -0,0 +1,392 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_id = $id; + $this->_secondary_id = $secondary_id; + + if ( is_multisite() ) { + $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_multisite_storage = false; + } + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( + WP_FS__ACCOUNTS_OPTION_NAME, + true, + $this->_is_multisite_storage ? + true : + ( $this->_blog_id > 0 ? $this->_blog_id : false ) + ); + } + + protected function get_all_data() { + return $this->get_option_manager()->get_option( $this->_id, array() ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function load() { + $all_plugins_data = $this->get_all_data(); + $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? + $all_plugins_data[ $this->_secondary_id ] : + array(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $value + * @param bool $flush + */ + function store( $key, $value, $flush = true ) { + if ( $this->_logger->is_on() ) { + $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); + } + + if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { + // No need to store data if the value wasn't changed. + return; + } + + $all_data = $this->get_all_data(); + + $this->_data[ $key ] = $value; + + $all_data[ $this->_secondary_id ] = $this->_data; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, $flush ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function save() { + $this->get_option_manager()->store(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $store + * @param string[] $exceptions Set of keys to keep and not clear. + */ + function clear_all( $store = true, $exceptions = array() ) { + $new_data = array(); + foreach ( $exceptions as $key ) { + if ( isset( $this->_data[ $key ] ) ) { + $new_data[ $key ] = $this->_data[ $key ]; + } + } + + $this->_data = $new_data; + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * Delete key-value storage. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_data = array(); + + $all_data = $this->get_all_data(); + unset( $all_data[ $this->_secondary_id ] ); + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param bool $store + */ + function remove( $key, $store = true ) { + if ( ! array_key_exists( $key, $this->_data ) ) { + return; + } + + unset( $this->_data[ $key ] ); + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $default + * + * @return bool|\FS_Plugin + */ + function get( $key, $default = false ) { + return array_key_exists( $key, $this->_data ) ? + $this->_data[ $key ] : + $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_secondary_id() { + return $this->_secondary_id; + } + + + /* ArrayAccess + Magic Access (better for refactoring) + -----------------------------------------------------------------------------------*/ + function __set( $k, $v ) { + $this->store( $k, $v ); + } + + function __isset( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function __unset( $k ) { + $this->remove( $k ); + } + + function __get( $k ) { + return $this->get( $k, null ); + } + + function offsetSet( $k, $v ) { + if ( is_null( $k ) ) { + throw new Exception( 'Can\'t append value to request params.' ); + } else { + $this->{$k} = $v; + } + } + + function offsetExists( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function offsetUnset( $k ) { + unset( $this->$k ); + } + + function offsetGet( $k ) { + return $this->get( $k, null ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the current element + * + * @link http://php.net/manual/en/iterator.current.php + * @return mixed Can return any type. + */ + public function current() { + return current( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Move forward to next element + * + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + */ + public function next() { + next( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the key of the current element + * + * @link http://php.net/manual/en/iterator.key.php + * @return mixed scalar on success, or null on failure. + */ + public function key() { + return key( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Checks if current position is valid + * + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() { + $key = key( $this->_data ); + + return ( $key !== null && $key !== false ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Rewind the Iterator to the first element + * + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + */ + public function rewind() { + reset( $this->_data ); + } + + /** + * (PHP 5 >= 5.1.0)
    + * Count elements of an object + * + * @link http://php.net/manual/en/countable.count.php + * @return int The custom count as an integer. + *

    + *

    + * The return value is cast to an integer. + */ + public function count() { + return count( $this->_data ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-license-manager.php b/freemius/includes/managers/class-fs-license-manager.php new file mode 100644 index 0000000..891ecd8 --- /dev/null +++ b/freemius/includes/managers/class-fs-license-manager.php @@ -0,0 +1,104 @@ +get_slug() ); +// +// if ( ! isset( self::$_instances[ $slug ] ) ) { +// self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); +// } +// +// return self::$_instances[ $slug ]; +// } +// +//// private function __construct($slug) { +//// parent::__construct($slug); +//// } +// +// function entry_id() { +// return 'licenses'; +// } +// +// function sync( $id ) { +// +// } +// +// /** +// * @author Vova Feldman (@svovaf) +// * @since 1.0.5 +// * @uses FS_Api +// * +// * @param number|bool $plugin_id +// * +// * @return FS_Plugin_License[]|stdClass Licenses or API error. +// */ +// function api_get_user_plugin_licenses( $plugin_id = false ) { +// $api = $this->_fs->get_api_user_scope(); +// +// if ( ! is_numeric( $plugin_id ) ) { +// $plugin_id = $this->_fs->get_id(); +// } +// +// $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); +// +// if ( ! isset( $result->error ) ) { +// for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { +// $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); +// } +// +// $result = $result->licenses; +// } +// +// return $result; +// } +// +// function api_get_many() { +// +// } +// +// function api_activate( $id ) { +// +// } +// +// function api_deactivate( $id ) { +// +// } + + /** + * @param FS_Plugin_License[] $licenses + * + * @return bool + */ + static function has_premium_license( $licenses ) { + if ( is_array( $licenses ) ) { + foreach ( $licenses as $license ) { + /** + * @var FS_Plugin_License $license + */ + if ( ! $license->is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-option-manager.php b/freemius/includes/managers/class-fs-option-manager.php new file mode 100644 index 0000000..4e5494c --- /dev/null +++ b/freemius/includes/managers/class-fs-option-manager.php @@ -0,0 +1,497 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_id = $id; + + if ( is_multisite() ) { + $this->_is_network_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_storage = false; + } + + if ( $load ) { + $this->load(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $id + * @param bool $load + * @param bool|int $network_level_or_blog_id Since 2.0.0 + * + * @return FS_Option_Manager + */ + static function get_manager( $id, $load = false, $network_level_or_blog_id = false ) { + $key = strtolower( $id ); + + if ( is_multisite() ) { + if ( true === $network_level_or_blog_id ) { + $key .= ':ms'; + } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_MANAGERS[ $key ] ) ) { + self::$_MANAGERS[ $key ] = new FS_Option_Manager( $id, $load, $network_level_or_blog_id ); + } // If load required but not yet loaded, load. + else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { + self::$_MANAGERS[ $key ]->load(); + } + + return self::$_MANAGERS[ $key ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param bool $flush + */ + function load( $flush = false ) { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $flush || ! isset( $this->_options ) ) { + if ( isset( $this->_options ) ) { + // Clear prev options. + $this->clear(); + } + + $cache_group = $this->get_cache_group(); + + if ( WP_FS__DEBUG_SDK ) { + + // Don't use cache layer in DEBUG mode. + $load_options = empty( $this->_options ); + + } else { + + $this->_options = wp_cache_get( + $option_name, + $cache_group + ); + + $load_options = ( false === $this->_options ); + } + + $cached = true; + + if ( $load_options ) { + if ( $this->_is_network_storage ) { + $this->_options = get_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + $this->_options = get_blog_option( $this->_blog_id, $option_name ); + } else { + $this->_options = get_option( $option_name ); + } + + if ( is_string( $this->_options ) ) { + $this->_options = json_decode( $this->_options ); + } + +// $this->_logger->info('get_option = ' . var_export($this->_options, true)); + + if ( false === $this->_options ) { + $this->clear(); + } + + $cached = false; + } + + if ( ! WP_FS__DEBUG_SDK && ! $cached ) { + // Set non encoded cache. + wp_cache_set( $option_name, $this->_options, $cache_group ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_loaded() { + return isset( $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_empty() { + return ( $this->is_loaded() && false === $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool $flush + */ + function clear( $flush = false ) { + $this->_logger->entrance(); + + $this->_options = array(); + + if ( $flush ) { + $this->store(); + } + } + + /** + * Delete options manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $option_name = $this->get_option_manager_name(); + + if ( $this->_is_network_storage ) { + delete_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + delete_blog_option( $this->_blog_id, $option_name ); + } else { + delete_option( $option_name ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $option + * + * @return bool + */ + function has_option( $option ) { + return array_key_exists( $option, $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $default + * + * @return mixed + */ + function get_option( $option, $default = null ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->load(); + } + + if ( is_array( $this->_options ) ) { + $value = isset( $this->_options[ $option ] ) ? + $this->_options[ $option ] : + $default; + } else if ( is_object( $this->_options ) ) { + $value = isset( $this->_options->{$option} ) ? + $this->_options->{$option} : + $default; + } else { + $value = $default; + } + + /** + * If it's an object, return a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = $options->get_option( 'object1' ); + * $object1->x = 123; + * + * $object2 = $options->get_option( 'object2' ); + * $object2->y = 'dummy'; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't return a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' which is quite not + * an expected behaviour. + * + * @author Vova Feldman + */ + return is_object( $value ) ? clone $value : $value; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $value + * @param bool $flush + */ + function set_option( $option, $value, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->clear(); + } + + /** + * If it's an object, store a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = new stdClass(); + * $object1->x = 123; + * + * $options->set_option( 'object1', $object1 ); + * + * $object1->x = 456; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't set the option as a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' ($object1->x = 456 instead of + * $object1->x = 123) which is quite not an expected behaviour. + * + * @author Vova Feldman + */ + $copy = is_object( $value ) ? clone $value : $value; + + if ( is_array( $this->_options ) ) { + $this->_options[ $option ] = $copy; + } else if ( is_object( $this->_options ) ) { + $this->_options->{$option} = $copy; + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Unset option. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param bool $flush + */ + function unset_option( $option, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( is_array( $this->_options ) ) { + if ( ! isset( $this->_options[ $option ] ) ) { + return; + } + + unset( $this->_options[ $option ] ); + + } else if ( is_object( $this->_options ) ) { + if ( ! isset( $this->_options->{$option} ) ) { + return; + } + + unset( $this->_options->{$option} ); + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Dump options to database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function store() { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $this->_logger->is_on() ) { + $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); + } + + // Update DB. + if ( $this->_is_network_storage ) { + update_site_option( $option_name, $this->_options ); + } else if ( $this->_blog_id > 0 ) { + update_blog_option( $this->_blog_id, $option_name, $this->_options ); + } else { + update_option( $option_name, $this->_options ); + } + + if ( ! WP_FS__DEBUG_SDK ) { + wp_cache_set( $option_name, $this->_options, $this->get_cache_group() ); + } + } + + /** + * Get options keys. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return string[] + */ + function get_options_keys() { + if ( is_array( $this->_options ) ) { + return array_keys( $this->_options ); + } else if ( is_object( $this->_options ) ) { + return array_keys( get_object_vars( $this->_options ) ); + } + + return array(); + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $site_options = FS_Option_Manager::get_manager($this->_id, true, false); + + $options = is_object( $site_options->_options ) ? + get_object_vars( $site_options->_options ) : + $site_options->_options; + + if ( ! empty( $options ) ) { + foreach ( $options as $key => $val ) { + $this->set_option( $key, $val, false ); + } + + $this->store(); + } + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @return string + */ + private function get_option_manager_name() { + return $this->_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_cache_group() { + $group = WP_FS__SLUG; + + if ( $this->_is_network_storage ) { + $group .= '_ms'; + } else if ( $this->_blog_id > 0 ) { + $group .= "_s{$this->_blog_id}"; + } + + return $group; + } + + #endregion + } diff --git a/freemius/includes/managers/class-fs-plan-manager.php b/freemius/includes/managers/class-fs-plan-manager.php new file mode 100644 index 0000000..639de43 --- /dev/null +++ b/freemius/includes/managers/class-fs-plan-manager.php @@ -0,0 +1,162 @@ +is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_paid_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return false; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( ! $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_free_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Find all plans that have trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return FS_Plugin_Plan[] + */ + function get_trial_plans( $plans ) { + $trial_plans = array(); + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + $trial_plans[] = $plans[ $i ]; + } + } + } + + return $trial_plans; + } + + /** + * Check if plugin has any trial plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_trial_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + return true; + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-plugin-manager.php b/freemius/includes/managers/class-fs-plugin-manager.php new file mode 100644 index 0000000..d8ea082 --- /dev/null +++ b/freemius/includes/managers/class-fs-plugin-manager.php @@ -0,0 +1,220 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_module_id = $module_id; + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string|bool $module_type "plugin", "theme", or "false" for all modules. + * + * @return array + */ + protected function get_all_modules( $module_type = false ) { + $option_manager = $this->get_option_manager(); + + if ( false !== $module_type ) { + return $option_manager->get_option( $module_type . 's', array() ); + } + + return array( + self::OPTION_NAME_PLUGINS => $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), + self::OPTION_NAME_THEMES => $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), + ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function load() { + $all_modules = $this->get_all_modules(); + + if ( ! is_numeric( $this->_module_id ) ) { + unset( $all_modules[ self::OPTION_NAME_THEMES ] ); + } + + foreach ( $all_modules as $modules ) { + /** + * @since 1.2.2 + * + * @var $modules FS_Plugin[] + */ + foreach ( $modules as $module ) { + $found_module = false; + + /** + * If module ID is not numeric, it must be a plugin's slug. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( ! is_numeric( $this->_module_id ) ) { + if ( $this->_module_id === $module->slug ) { + $this->_module_id = $module->id; + $found_module = true; + } + } else if ( $this->_module_id == $module->id ) { + $found_module = true; + } + + if ( $found_module ) { + $this->_module = $module; + break; + } + } + } + } + + /** + * Store plugin on local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|FS_Plugin $module + * @param bool $flush + * + * @return bool|\FS_Plugin + */ + function store( $module = false, $flush = true ) { + if ( false !== $module ) { + $this->_module = $module; + } + + $all_modules = $this->get_all_modules( $this->_module->type ); + $all_modules[ $this->_module->slug ] = $this->_module; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); + + return $this->_module; + } + + /** + * Update local plugin data if different. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param \FS_Plugin $plugin + * @param bool $store + * + * @return bool True if plugin was updated. + */ + function update( FS_Plugin $plugin, $store = true ) { + if ( ! ($this->_module instanceof FS_Plugin ) || + $this->_module->slug != $plugin->slug || + $this->_module->public_key != $plugin->public_key || + $this->_module->secret_key != $plugin->secret_key || + $this->_module->parent_plugin_id != $plugin->parent_plugin_id || + $this->_module->title != $plugin->title + ) { + $this->store( $plugin, $store ); + + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin $plugin + * @param bool $store + */ + function set( FS_Plugin $plugin, $store = false ) { + $this->_module = $plugin; + + if ( $store ) { + $this->store(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool|\FS_Plugin + */ + function get() { + return isset( $this->_module ) ? + $this->_module : + false; + } + + + } \ No newline at end of file diff --git a/freemius/includes/managers/index.php b/freemius/includes/managers/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/managers/index.php @@ -0,0 +1,3 @@ +_result = $result; + + $code = 0; + $message = 'Unknown error, please check GetResult().'; + $type = ''; + + if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { + if ( isset( $result['error']['code'] ) ) { + $code = $result['error']['code']; + } + if ( isset( $result['error']['message'] ) ) { + $message = $result['error']['message']; + } + if ( isset( $result['error']['type'] ) ) { + $type = $result['error']['type']; + } + } + + $this->_type = $type; + $this->_code = $code; + + parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); + } + + /** + * Return the associated result object returned by the API server. + * + * @return array The result from the API server + */ + public function getResult() { + return $this->_result; + } + + public function getStringCode() { + return $this->_code; + } + + public function getType() { + return $this->_type; + } + + /** + * To make debugging easier. + * + * @return string The string representation of the error + */ + public function __toString() { + $str = $this->getType() . ': '; + + if ( $this->code != 0 ) { + $str .= $this->getStringCode() . ': '; + } + + return $str . $this->getMessage(); + } + } + } \ No newline at end of file diff --git a/freemius/includes/sdk/Exceptions/InvalidArgumentException.php b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..685752a --- /dev/null +++ b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php @@ -0,0 +1,8 @@ +_id = $pID; + $this->_public = $pPublic; + $this->_secret = $pSecret; + $this->_scope = $pScope; + $this->_isSandbox = $pIsSandbox; + } + + public function IsSandbox() { + return $this->_isSandbox; + } + + function CanonizePath( $pPath ) { + $pPath = trim( $pPath, '/' ); + $query_pos = strpos( $pPath, '?' ); + $query = ''; + + if ( false !== $query_pos ) { + $query = substr( $pPath, $query_pos ); + $pPath = substr( $pPath, 0, $query_pos ); + } + + // Trim '.json' suffix. + $format_length = strlen( '.' . self::FORMAT ); + $start = $format_length * ( - 1 ); //negative + if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { + $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); + } + + switch ( $this->_scope ) { + case 'app': + $base = '/apps/' . $this->_id; + break; + case 'developer': + $base = '/developers/' . $this->_id; + break; + case 'user': + $base = '/users/' . $this->_id; + break; + case 'plugin': + $base = '/plugins/' . $this->_id; + break; + case 'install': + $base = '/installs/' . $this->_id; + break; + default: + throw new Freemius_Exception( 'Scope not implemented.' ); + } + + return '/v' . FS_API__VERSION . $base . + ( ! empty( $pPath ) ? '/' : '' ) . $pPath . + ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; + } + + abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); + + /** + * @param string $pPath + * @param string $pMethod + * @param array $pParams + * + * @return object[]|object|null + */ + private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + $pMethod = strtoupper( $pMethod ); + + try { + $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); + } + + /** + * Base64 decoding that does not need to be urldecode()-ed. + * + * Exactly the same as PHP base64 encode except it uses + * `-` instead of `+` + * `_` instead of `/` + * No padded = + * + * @param string $input Base64UrlEncoded() string + * + * @return string + */ + protected static function Base64UrlDecode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_decode'; + return $fn( strtr( $input, '-_', '+/' ) ); + } + + /** + * Base64 encoding that does not need to be urlencode()ed. + * + * Exactly the same as base64 encode except it uses + * `-` instead of `+ + * `_` instead of `/` + * + * @param string $input string + * + * @return string Base64 encoded string + */ + protected static function Base64UrlEncode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_encode'; + $str = strtr( $fn( $input ), '+/', '-_' ); + $str = str_replace( '=', '', $str ); + + return $str; + } + } diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php new file mode 100644 index 0000000..34e1c2e --- /dev/null +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -0,0 +1,704 @@ + '7.37' ); + + if ( ! defined( 'FS_API__PROTOCOL' ) ) { + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); + } + + if ( ! defined( 'FS_API__LOGGER_ON' ) ) { + define( 'FS_API__LOGGER_ON', false ); + } + + if ( ! defined( 'FS_API__ADDRESS' ) ) { + define( 'FS_API__ADDRESS', '://api.freemius.com' ); + } + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); + } + + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + return; + } + + class Freemius_Api_WordPress extends Freemius_Api_Base { + private static $_logger = array(); + + /** + * @param string $pScope 'app', 'developer', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string|bool $pSecret Element's secret key. + * @param bool $pSandbox Whether or not to run API in sandbox mode. + */ + public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { + // If secret key not provided, use public key encryption. + if ( is_bool( $pSecret ) ) { + $pSecret = $pPublic; + } + + parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); + } + + public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { + $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); + + if ( ':' === $address[0] ) { + $address = self::$_protocol . $address; + } + + return $address . $pCanonizedPath; + } + + #---------------------------------------------------------------------------------- + #region Servers Clock Diff + #---------------------------------------------------------------------------------- + + /** + * @var int Clock diff in seconds between current server to API server. + */ + private static $_clock_diff = 0; + + /** + * Set clock diff for all API calls. + * + * @since 1.0.3 + * + * @param $pSeconds + */ + public static function SetClockDiff( $pSeconds ) { + self::$_clock_diff = $pSeconds; + } + + /** + * Find clock diff between current server to API server. + * + * @since 1.0.2 + * @return int Clock diff in seconds. + */ + public static function FindClockDiff() { + $time = time(); + $pong = self::Ping(); + + return ( $time - strtotime( $pong->timestamp ) ); + } + + #endregion + + /** + * @var string http or https + */ + private static $_protocol = FS_API__PROTOCOL; + + /** + * Set API connection protocol. + * + * @since 1.0.4 + */ + public static function SetHttp() { + self::$_protocol = 'http'; + } + + /** + * @since 1.0.4 + * + * @return bool + */ + public static function IsHttps() { + return ( 'https' === self::$_protocol ); + } + + /** + * Sign request with the following HTTP headers: + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @param string $pResourceUrl + * @param array $pWPRemoteArgs + * + * @return array + */ + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { + $auth = $this->GenerateAuthorizationParams( + $pResourceUrl, + $pWPRemoteArgs['method'], + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' + ); + + $pWPRemoteArgs['headers']['Date'] = $auth['date']; + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; + + if ( ! empty( $auth['content_md5'] ) ) { + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; + } + + return $pWPRemoteArgs; + } + + /** + * Generate Authorization request headers: + * + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @author Vova Feldman + * + * @param string $pResourceUrl + * @param string $pMethod + * @param string $pPostParams + * + * @return array + * @throws Freemius_Exception + */ + function GenerateAuthorizationParams( + $pResourceUrl, + $pMethod = 'GET', + $pPostParams = '' + ) { + $pMethod = strtoupper( $pMethod ); + + $eol = "\n"; + $content_md5 = ''; + $content_type = ''; + $now = ( time() - self::$_clock_diff ); + $date = date( 'r', $now ); + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) && ! empty( $pPostParams ) ) { + $content_md5 = md5( $pPostParams ); + $content_type = 'application/json'; + } + + $string_to_sign = implode( $eol, array( + $pMethod, + $content_md5, + $content_type, + $date, + $pResourceUrl + ) ); + + // If secret and public keys are identical, it means that + // the signature uses public key hash encoding. + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; + + $auth = array( + 'date' => $date, + 'authorization' => $auth_type . ' ' . $this->_id . ':' . + $this->_public . ':' . + self::Base64UrlEncode( hash_hmac( + 'sha256', $string_to_sign, $this->_secret + ) ) + ); + + if ( ! empty( $content_md5 ) ) { + $auth['content_md5'] = $content_md5; + } + + return $auth; + } + + /** + * Get API request URL signed via query string. + * + * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). + * + * @param string $pPath + * + * @throws Freemius_Exception + * + * @return string + */ + function GetSignedUrl( $pPath ) { + $resource = explode( '?', $this->CanonizePath( $pPath ) ); + $pResourceUrl = $resource[0]; + + $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); + + return Freemius_Api_WordPress::GetUrl( + $pResourceUrl . '?' . + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . + 'authorization=' . urlencode( $auth['authorization'] ) . + '&auth_date=' . urlencode( $auth['date'] ) + , $this->_isSandbox ); + } + + /** + * @author Vova Feldman + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { + $start = microtime( true ); + + $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); + + if ( FS_API__LOGGER_ON ) { + $end = microtime( true ); + + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); + $is_http_error = is_wp_error( $response ); + + self::$_logger[] = array( + 'id' => count( self::$_logger ), + 'start' => $start, + 'end' => $end, + 'total' => ( $end - $start ), + 'method' => $pWPRemoteArgs['method'], + 'path' => $pUrl, + 'body' => $has_body ? $pWPRemoteArgs['body'] : null, + 'result' => ! $is_http_error ? + $response['body'] : + json_encode( $response->get_error_messages() ), + 'code' => ! $is_http_error ? $response['response']['code'] : null, + 'backtrace' => debug_backtrace(), + ); + } + + return $response; + } + + /** + * @return array + */ + static function GetLogger() { + return self::$_logger; + } + + /** + * @param string $pCanonizedPath + * @param string $pMethod + * @param array $pParams + * @param null|array $pWPRemoteArgs + * @param bool $pIsSandbox + * @param null|callable $pBeforeExecutionFunction + * + * @return object[]|object|null + * + * @throws \Freemius_Exception + */ + private static function MakeStaticRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null, + $pIsSandbox = false, + $pBeforeExecutionFunction = null + ) { + // Connectivity errors simulation. + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { + self::ThrowCloudFlareDDoSException(); + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { + self::ThrowSquidAclException(); + } + + if ( empty( $pWPRemoteArgs ) ) { + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . + home_url(); + + $pWPRemoteArgs = array( + 'method' => strtoupper( $pMethod ), + 'connect_timeout' => 10, + 'timeout' => 60, + 'follow_redirects' => true, + 'redirection' => 5, + 'user-agent' => $user_agent, + 'blocking' => true, + ); + } + + if ( ! isset( $pWPRemoteArgs['headers'] ) || + ! is_array( $pWPRemoteArgs['headers'] ) + ) { + $pWPRemoteArgs['headers'] = array(); + } + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + if ( is_array( $pParams ) && 0 < count( $pParams ) ) { + $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; + $pWPRemoteArgs['body'] = json_encode( $pParams ); + } + } + + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); + + $resource = explode( '?', $pCanonizedPath ); + + if ( FS_SDK__HAS_CURL ) { + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait + // for 2 seconds if the server does not support this header. + $pWPRemoteArgs['headers']['Expect'] = ''; + } + + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { + $pWPRemoteArgs['sslverify'] = false; + } + + if ( false !== $pBeforeExecutionFunction && + is_callable( $pBeforeExecutionFunction ) + ) { + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); + } + + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + + if ( is_wp_error( $result ) ) { + /** + * @var WP_Error $result + */ + if ( self::IsCurlError( $result ) ) { + /** + * With dual stacked DNS responses, it's possible for a server to + * have IPv6 enabled but not have IPv6 connectivity. If this is + * the case, cURL will try IPv4 first and if that fails, then it will + * fall back to IPv6 and the error EHOSTUNREACH is returned by the + * operating system. + */ + $matches = array(); + $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { + /** + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. + * @author Vova Feldman (@svovaf) + */ + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { +// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); + // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); + + // Re-run request. + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + } + } + } + } + + if ( is_wp_error( $result ) ) { + self::ThrowWPRemoteException( $result ); + } + } + + $response_body = $result['body']; + + if ( empty( $response_body ) ) { + return null; + } + + $decoded = json_decode( $response_body ); + + if ( is_null( $decoded ) ) { + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && + preg_match( '/text\/javascript/', $response_body ) + ) { + self::ThrowCloudFlareDDoSException( $response_body ); + } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && + preg_match( '/squid/', $response_body ) + ) { + self::ThrowSquidAclException( $response_body ); + } else { + $decoded = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $response_body, + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + } + + return $decoded; + } + + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than wp_remote_request() + * to make the request. + * + * @param string $pCanonizedPath The URL to make the request to + * @param string $pMethod HTTP method + * @param array $pParams The parameters to use for the POST body + * @param null|array $pWPRemoteArgs wp_remote_request options. + * + * @return object[]|object|null + * + * @throws Freemius_Exception + */ + public function MakeRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null + ) { + $resource = explode( '?', $pCanonizedPath ); + + // Only sign request if not ping.json connectivity test. + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); + + return self::MakeStaticRequest( + $pCanonizedPath, + $pMethod, + $pParams, + $pWPRemoteArgs, + $this->_isSandbox, + $sign_request ? array( &$this, 'SignRequest' ) : null + ); + } + + /** + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter + * + * @param resource $handle A cURL handle returned by curl_init() + * + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to + * CURL_IPRESOLVE_V4 + * + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e + */ + static function CurlResolveToIPv4( $handle ) { + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + + return $handle; + } + + #---------------------------------------------------------------------------------- + #region Connectivity Test + #---------------------------------------------------------------------------------- + + /** + * If successful connectivity to the API endpoint using ping.json endpoint. + * + * - OR - + * + * Validate if ping result object is valid. + * + * @param mixed $pPong + * + * @return bool + */ + public static function Test( $pPong = null ) { + $pong = is_null( $pPong ) ? + self::Ping() : + $pPong; + + return ( + is_object( $pong ) && + isset( $pong->api ) && + 'pong' === $pong->api + ); + } + + /** + * Ping API to test connectivity. + * + * @return object + */ + public static function Ping() { + try { + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Exceptions + #---------------------------------------------------------------------------------- + + /** + * @param \WP_Error $pError + * + * @return bool + */ + private static function IsCurlError( WP_Error $pError ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + return ( 0 === strpos( $message, 'cURL' ) ); + } + + /** + * @param WP_Error $pError + * + * @throws Freemius_Exception + */ + private static function ThrowWPRemoteException( WP_Error $pError ) { + if ( self::IsCurlError( $pError ) ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + #region Check if there are any missing cURL methods. + + $curl_required_methods = array( + 'curl_version', + 'curl_exec', + 'curl_init', + 'curl_close', + 'curl_setopt', + 'curl_setopt_array', + 'curl_error', + ); + + // Find all missing methods. + $missing_methods = array(); + foreach ( $curl_required_methods as $m ) { + if ( ! function_exists( $m ) ) { + $missing_methods[] = $m; + } + } + + if ( ! empty( $missing_methods ) ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'cUrlMissing', + 'message' => $message, + 'code' => 'curl_missing', + 'http' => 402 + ), + 'missing_methods' => $missing_methods, + ) ); + } + + #endregion + + // cURL error - "cURL error {{errno}}: {{error}}". + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); + + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; + + $e = new Freemius_Exception( array( + 'error' => array( + 'code' => $code, + 'message' => $message, + 'type' => 'CurlException', + ), + ) ); + } else { + $e = new Freemius_Exception( array( + 'error' => array( + 'code' => $pError->get_error_code(), + 'message' => $pError->get_error_message(), + 'type' => 'WPRemoteException', + ), + ) ); + } + + throw $e; + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowCloudFlareDDoSException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'CloudFlareDDoSProtection', + 'message' => $pResult, + 'code' => 'cloudflare_ddos_protection', + 'http' => 402 + ) + ) ); + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowSquidAclException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'SquidCacheBlock', + 'message' => $pResult, + 'code' => 'squid_cache_block', + 'http' => 402 + ) + ) ); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/sdk/LICENSE.txt b/freemius/includes/sdk/LICENSE.txt new file mode 100644 index 0000000..d6a9326 --- /dev/null +++ b/freemius/includes/sdk/LICENSE.txt @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/freemius/includes/sdk/index.php b/freemius/includes/sdk/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/sdk/index.php @@ -0,0 +1,3 @@ + $data ) { + if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) ) ) ) { + if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { + return $relative_path; + } + } + } + + return null; + } diff --git a/freemius/includes/supplements/fs-essential-functions-2.2.1.php b/freemius/includes/supplements/fs-essential-functions-2.2.1.php new file mode 100644 index 0000000..946a34d --- /dev/null +++ b/freemius/includes/supplements/fs-essential-functions-2.2.1.php @@ -0,0 +1,45 @@ +{vT9ZyK}8JW3&lqgh#Di97#9T}*t#FwXh4lDW)&ewOx6Yb1YaQm_xt;wQ}^DU z2V++Ev!A^m=wF?xQ|IwN|MxjH4;|C@&Vb)dzZC>0fOj9RQV?8nd>03~d@bcE@Pl9l zJmkee@Y`VB;~aPp<*UJCz_)_G1AY)xQxAY@`p4j*;4|Q1;K46(?;QgkO!;{55OA?C zpX$qJfE&1fHmLTm0uKP+4IT-;57hgA4(k2C0yVz7!K1)?!A0PAz@^}mUNgJ12t6C@%wF1a1X2|0(~x z4QgC(05kCIpy(Hz6a*gt4hGfVZ6HGpJ_9ZRzYWq=@C)z^aM8)o3furj;4biJ@N3|4 z;6tG1@l)^xU|-1p;EOvHTNtG7zsuvtK+)s#py>WLpxXa7D7rifYTiEqF9MHZ@*2l3P~)5d zHNH21qW@K(=zJ5n9Q+G#HTVG74<7PTSAPIh`)7ih=P;=EH-Q?@CH{T_YFw9rqUY;C z^>;m}`P>MKZnuD!gLi;8fh$gN?|mM8A?15P(f?sk^M4XN;kV#j26Y4F5N6Xj?f_fh zo#63cX_@2K^F6*6)Vy<0?OX|J9B&6d1HKP@H@Kb2zXp69oCHT!1i^ve9iZs@Y48B> z%b@6bFBpMKPIcqB3RJzfdb}AtoAO7%KJW+NLEt}w&w@Yp_n$n?jprwz==e*Ihp%+= zKOQ`k`^!Pi;|%aj@N7_goPnCxAA?%QPlL|`?*li3-vnO)hNU2AfUgEcr>}yV-=p9h z_|Kr;FAoF(EFWA6if`TzYP~)JN)Oxx9t?g1)VRLm?>`EPKK}~RW$+BBd7rw<&1;p% zb3jBk7y;E^6%<|G2&&y{LGjHEp!)x?$G-s8|7Sqa;Xd$X;Dg}v!6Qz0<2eD;IF^EH z_f$~ruJ+}#KvZaO9;o(ThDFkNKL8?%!KXmU`vc%%;8UR5e-_kw9l&I?9tVN_;NjpA;2EI$JrAsdo59zE zcYzxJnwL9ztOwQZR#5L<48o$p6sUf01Vy*oL0%2+1l9lJpy=~2;CAq7Q1cqa2&tdr zVIIlddqBy@U;nO?`=ift^j-{#{-=SQD3Tl2&fU5sj+2B&} zJa7|O1y%1;p!)f&FaHgw@qGgne|-;B`~MTvI{b^jf5@xcdyBw#a(^lKF7R#;(F?}T zb^78>pw{b8K+W^(;0EwP@EUOOdB_O(5wHRd3_3c#13ZcH&EPWdGvH?M5%7F))sUOt zEU0<>fyZk>&G&j=zR{Q81Fqx#Eui$)qoC;jGf;AT$XZADlRXZAr*i+5;CI0(P;^)~ z?E2g6aSRk6Tm*_<6CN{A{agv^y&J$3d^bp!!9ffLQ3^&t(f^I$OTj+^wN8Hx9uIyO zJOKO|sD7RXMTY~{J3r?_@M6mMfaBl^5N8{B8L0L8GN^I`|ZU$vvz5;;aG&j2-!jiCB}1E_v(0$&Jz3>1HV2|NLO06Z0Z3S14Iw9)lH3aVZW6#d@l@Bb;N z`F#vbT)a0qw|xCML(D8AbTYP{EhqQ{?sTBk4h^0&Z)DL(C=P(vW^EXfy1EYx79zdg0NPQgMHwC0agEQ@KErppw{JEpxXNm zDE;u5fBqAX&-&+wFbTbXEO-RC7*so}Je~!rowcCqZS{Bw_!Y_tsNbrs4o~;E8WcUx z0QKH5D7sw;ioY%f^{abKLD3-tRsW6P^TF#u(e3@9=<%nZ#&f&Be;25B=Ry6x=JD&G z-hUWWdq48!r@$jAKMQJ|pFisSj(%_zYmclq*{ zK(#v$_JiL7mx2H4pD#wJS5saBs-JOCzd7(2@Cs0Tb-l-1!J{dE4%F{HP;|ZDm%ro7 ze-Da&kNW#R^7y|%&FgVr{yB(>34Q@e&dwck?Uh0OY98wzQ&9BFz!!j5gIbUGf}+PA zpni9GyxZgFJboTjy)StDZ{YJNe;d^LKM3mgkKhXMhrWC?N>J;33@CXT0!4@OLCy0b zP;`z!@#STp>b)M+eBT7>cfG&=Hh=#e;8EPa#XtXq$J;@*cZV;35!ATv@%O*x?|;|h z_du<~L%#e&Q2OnkK&{__+xf%qL{Rlk0*?ikfok_0|NMN98$6D9yZ{tmZ3IQ9ZJ_3N zsec}WDdkB}?SC7TTs{P99*=|Kvu8lb+o~O|y)!}m*7)+-pvE->>iwOd=s5|B-Z`l8 zUJa_Bw}bk<*W=Bg=KDTS^ZzSQ>wCX{{w?qol>g3`58p|F-w~kn;q!g@Snws37lEgO zt3i$967Yp!0v-qc0jTwV52*2c2-NSRpy>56@OQvZ`tm=4TJI-8(It4b^IMMs)!&(* z+S>~1{hgqGzXz)S-}hJp7g3%ARsRN1zjuQ#0N(?O?>-KS-k%25-e*1DY?huJU-g$JO8qd42{cJ#+ymd)NTg z?+u{FcQdH*ei#%zZv{^WKLM)0?|Xb4)bHOs{yV7kcm@Rd__)m~?!f&a^WuW?9 z4yxbt{rzpA<~IiF{a1sp0x$K?KL}n;`9r?E^feUtEd#};%R%+`3Q+VP1hrnWDW_5X43rQoN0`9W|c<%dD>%@LP4c{mZ&ZxyKiPX|S}HNLzS zRQ=7M#`RiI?Oz6p59dJr-t6%$pw|Db;0fT3{{HQt=5vQHe-1pJ^5;SI_aLbGJp!uz z$2>j`s^6c2qSK+Tb#~=Npw{y=P`|T4t;ZTra=s4Kdpp4sz-dswKlFGVD7wBGd^z|o zQ1ky9sChjAYFs}Amw`V9N5L2Uo||6+s{IVqy1xn3`+o>(95;fR$1UJXz*|9$_a0F5 zxDV9tA&=kp_%JBCJOZu&{|!_-{lD+xqY|j!6&_#b@k&tq|DSyMD)1D_SNrnEz>Spu z9Mrsj4r-kaxYXgHp!D34pvHL&sCk8;e*Kp^xm^lseCL4r?YPv_8PvR{LH(|{)F5~r zD7st;Y97~s8t21_({PCBd`1=c>=>K=1e&4&)Ab1FT6_?)!j{#4L7$dkE z)VQ{Jtbv-(HK6*v4%`a<5vboI;6>m+fMeh}6E049J19Ny7$|-7@1TCimK`nvHQwVu z(Qg^3`IJDd`^!Mh_gwG*aKz(gQ1veaHSe9E=2r$)zXqz^8SrHAT2Sr%8K`=H2_696 z1!{f106q`=I;i>G@9{zZ{9#b*_XH?DeahcIxZ=jU2-G-E07b{;pvG}FcmTN0;|QpA z-3)5{mw!1HuQ1kv zX*^rZ&^oFoX;N?3MUkxC3Zr_~PDRi>%$ng;Qk{yMDm0@Obn9MOmn@I&FjA`{wEL~3 z9_xY7-DpPTY08>}_qulrW`G~XNR2qHXdVnVW}(!TwY*z3dn~5W6w|mhn>D92m%+oj} ztiu!GL`(;bS~eF~MD(3mvohL5^aiqKRm(0?)RVR*SgGhifv{pq2aL4>--KJT7OctB z$;qT9nd3?eYXUVXdiEoza8?-QFkP#~=(y6Tt9rUL>=4lNcrXZ&x5byWT}&mNGx_SXTj}d8Q(C8GU873JggxEt-N#tRPLG33Imsc84`h%7xxF(guU9 z9jhO1qloew+6xJWP&&a7hQ}&6gilA(nao6EsFs=L3%IC;Bg4T^R+$Ti7(KFJ3TUXE zBUISEG(u|*wdV@;91Jlht+^D=lC6zaxV7C{VskbYO0%$*rB$YfJm+39Md_STy2EWu zwI4xV8>Pz-iv&(JhA-q`I-WaM58)>dV$r4Hs9TPnd!=v$CM&v3Su2=4H#Dq#&lWq+T55Su2V_F4U-&i5 z&}6VF-CFP1uz|2wCYTev3eOpDteuL>)0pL8ElyCdwuV&@^%O77 z_*J!9DVM#5)-hhw6ieC`EZ&q$5=&=mO3i%maQat zqycNay@()iQ%QMBVzUf=hOD_7j$fr%y`gp7*%_8PS|DoHo#zPY;@$XQr8K#BI#cVS zZ~Od z@$976T5+AoBx=qL;H+VwyhTi$rSIy|!eTlh3wMJR3>hO zG;iLrP$`A7I9ffhT7LfU=(ZI!iQmw`dxb*q)@5zNJr^3nn5>7tg`+H&LQalXjgEJS5784; zg$UT-7K~QK-G&vj6n2AeCA|Jkb4b#OENWK5HKi5B$Y+wwj|sm~v1s98iJ!#$&e5sF zWz)z=Zoyp%oaHo%X`0^mI!x`IOSc-BQuD28S)Mrt1qs}9{Rq0f(sALo=4`P!{#FIg zt}LGEC;mo*EYDZC8ESTpv5F|kYtX_2r&D<2S8JQ7(zgw3x}~)!YF}Ns`L`@qR5$Ag zdTQ>kf@BL~){$&kC!V(kU0fuyX7A3%6Y5d6!s#M^1J_f2J0Z3ATuqm;29TrbvMwtuI3m5OCsFTGwRS4bwjek-{2<4-nQ=sr+8&qglm*f(Q>iST2G&7#6*vZFXx#`Zf*!`XVlL4#KaTL>Dc64<6sVzHj=9;4g~9yTC6`%C|J)(djzAS z@fOC4)ixZ76Qq31M9l1%g_uCGjoSBa5o#b;{7_=XBPiqYUJE7%+eHta>+Zh*UNwrDR+VNfyEq1VI-#O;p;s^j zgYbVit%!VfYR-ri){7F&sPkt^gB6Ce{@pNR52s@RfNEFCGnmHEmS>o+_Kndo_g;WKvKKE_GpgzW-%p&x=HtW$7e0QaVwK)LS+k%`FbQ zS$yY5C~X?#Vzp#b9-iKugWKGPj z%Q`um>-NqNVQ*GEi80O3-J3O&cJg{XVO`O?a`^4ErWBz&XL0*iEyOw zGF*Dg>wCJNB@_#tKklv}p}R13HoA7C<7u{nQEl(wu(PYR*S!jn!fj2AMsuTxYmtap zs0?{%K^*NdW^I&V$}w--$A(R7VXG9+;F8x7_bH6qOhTY&O%4Q>d($7S_NWVzoYx`Z z`kBePnEEoRkd=rrq83T3k{eEw{WJ2=o!zctBW_L6DrtIlC*@qSax$R!(+qG9`^qka zyp*1}B+tsqGTQ#8oa`3}2=SapV*6@TNZa)Z76DJujmvwT+~eV7 zCa>Bq<5qbfTwcT?({W7RPVP;Q6E6KY$tZ14ZiQ%NF=Vfc=aL+SPbY^#0^@Q7;vjjz z`!+I6XK&hAY9lLSf-yZ1!71ks9WhLb#0W5o!7B;V!^~LUv@(f36&czsb4(yOp*wDo z22}Xot8l>=qG z?^pTy;@4TQ7S&*$${fpy zTBxzL!PV0w8v?r$4+iZy-w`K;}mI(6*pIR)=USZr^OuG_nue&MvEoNPi z@yolL;DsYWoxELZ1!Kb-^}ln*#}s7u=fN0h1$pX~Q2P`rqr+|cgKI>Tu}rY*?GsO9 zF^^kD8ZNb2x?y~LbWG8oWM^gAY^SyfW~nC34(^6_w`;}b{TSAOG)=KLGA7Dxa-ooK zmE#Yccw~$N9cU?HR~KOvk#5j=U`dUT#dV9xgR}~{#*_f_?Q)L-Y+<;4+oqM_&e0<2 zureIoFltsoQMtrj3=7liyoX-kuXf^>8m*`hjP=^C0Lf?C~Q8dWc09q25MhL^PLQK{C{0&k?7^`>Frj&qc6?S8Bed zwpu!u>e)MIS}>l3FSs$u*Nmz&gSGr2{b>}1Cw z(!6(zk6a82`z4cdImtxI)z-NopNoV#A<|;~~u2GWVSSa*0`Vmb$2OVxkps z)Fh&}S+WCZ8?kk+8v7w6`8!-S8zCs{%97M}dm@#LQ$Qu1P?48~f<=L9Lkv&RotDuk zus?FjoOE4~uFhgtWmzF z!ESDxLG7b8rMH+{_kEp6R2aFBc65TS71bNTxV)uc9NSsr?`#^l#I)^@Z+H8};-@T4 zr6IT!DLIxE=X3N}?<^)Yv@hk@2D9WndE&(FX^pdY8l5)*dCx7E%j><~2O7p>PJF5!Hk3N}6?ZM^Mq2&HK*q0qHqTx4Sa9xfr#l`Q?9}cZr^c4SL3R{P9lG#YC+_kVafDw__6>^;h1fbNz-rucF;JYf^6VwX&-D)@>m z)nXeX3s2H1$WZnZCr?e-J{tyKyJU{|`v`2qF~DXOgqb}F`m8%*YO^v7*+BqYLVX8?(wx$-GfAp^XsDhe{&@cRtuIuN5Hq_SY zlG|X2W>KN$v6+of<8E|Vff9$-d|YoQgWy@~tvF;nV6AmCJ#o4#V{x#b@N~hM3sXH( zNV}zasM|3cdpRN9bNtHnSGRP8H6pUHgxdGfu@lh;LF1@^J!NHc!b)gID#C1*OooPQ z*;xA3Y*?LA!jeK5c`Zc{!tJ=i1M!DgtSyl)6;eYue4&g6@{xaOzy5ZxeIRuH&b!t`Y!%sP;MiZ$QGA$fm!T};}zpyzdz zNEo%W>*B{yJX6$D)ibb1x&CM16Xm>bR-*UHX)4u)943KzQW2O7i38 zBz}CKc0qZyTvHw-Lhq#A(7B+I;q89vFd`XvEI`b%ACuw z^=)=3L%Xf5c++eWlelkPr>v)9v{l}{;V9fxpKa*yz?(B@X}%fSLBX)tU%{UgW1nI? z-EdJc?-Yqj=U4XCGxgAu73v$bwcGn$&id_ETUlY|U~)th$X0 z;tT|q%$1fRKW)a3;EpAsbz2pC%!Z+I51u9ZbpDtkHp-rg2osV6zqWlt6at;Nmzei$ zAa6}hKj|yKJ&<5G{GtakT3kC1 zN4{Slc_l0HujX`fZHpp?k%X0AoJL(ZIf(cZT0v>;jGC{*} zfYlzYBhGSH8*r7})m9F$jHKOb&Skqok@#|@F|w@wO2e?`oGV;hyM8V&@l}r&SRePW z@`~|GBbAaT%9`-7ZL#)jOPlj*QYj6!t9fZWsX9X4@N$ChQxO_uN$ z;wE1qxNi2SEpf9dVM3hJnwh^%C$_rvp&8M4550<((8In>=3$=@xgIEq!V!DumKSg| z9Pc5xTGKT5xMyD!^*oWH!X9wEZ2mT=%x6GE{yAUcID~+^aH;+0K_xb-q$F)g2Rk2) zth0R&_(p!ZsK>fMH0%!oA5OPT0S`L2s!4t>T0kd#2}E2=w3avK?`&4tp=G6Xj~x>( znL>@j8B0T0v6au?*5aQsh->qA@)_!&9el^TF20OWYLUH?{VdaNwf402z?UG%}2MtS3PbrO`~v_sQ55 zBClyI0j6NddR)J_R#{>bn@K{uTZ0*pZ<#&uxWx;ToI+$_9b*jy8}Vx(hgH`{DAa-h zoH9t~?}}l zv!6y(>&`Sw3XoqmBFJOeee@0HW265jf?Z!eO@%t9hG6jm@p0Wp5kpCZZ;$dhbOooB zkh`8QA_8=M#*uR`8r>^TQ6{`T-c=K!nP#rdwO|Y`45y{@+@)zIl@n?Y7ithaXl!f? z1;;F0Ax>}4f0Q$lFg0qRLl)lm4tVDxlujE8_G$yN(*BZ z##Y5YOIPAt*2O-4HQ*lYv}f-8o*5VF;M>JYrc2ksnUcJ!zT*|M^-e<@kBmR5l8>oK zGcYWFQOMW2X&r1V)ExRHWt5#_A=h@4wvuDEcxVpIbPF28`F51HI7u6vZ)(m16?@s# zJ?l`aINh-DWGUlCal-)|E@d(%n4B&}FJg1}C%@d1?Xg-LX2@$GccKKuV}x}M=rPvJ z9RLkBz_xlqqz_FepkSPCN@PSKP|OXdEYF1PAKMlF{&D1|EbE6#a? zo@be5|J;dkEy@C$xv=WK-s`=Oa5@j)jxnxB25>#3eN7sIV2h~}9bj(F--ej=%w_)8 zrg9NtC}s+iOhX>#H4)qT%F2!<6|8SC#%>{#_d|z6+>L^hiVCC*7tW@mj(c7yh|>FP zlT;@9V3Td)@hQHc(}epj5F4^mI;y=Xnc?h5g~eCyczB3(3*(k&j#}I6dBmYOS5VDy zMCl<;6{4t0-7|;om|=-F+?A{;Ck-(H`XiZIAd{Ska-K;})NaQO%}>cEnr(S{`fcG% zjq{5na8ijh**1|%GK{=x!cQMcbjzYsROFA_7 zu-VlrdflmILQJSRn9474zNYJnjo!I*S~&zl3aaD%c*WrY4iYQ8xBI~gW>Ul<5;j>$ z?U8i-5Gjeho~^*=n#miSu4pCG(7{qWTG>I=%Py?AZdKYWfL+1=rj7HY%Aq%}nneoE zRUn{NhjSHqK&k=}mp|vV1D?kWQx0(UWZXPk$TRfrfJh_tN9xvb6K^!zlqQp z?hdpt3?uu664gO}{qq`rGGln?yQ?odrNCf|;3w}f^l;ct7a!%KZRh$|IG+F$kzduC z`?t6ngRK-Di8gi;U?Wz;B-x!;;CmT9yWruTCl)X)GaOjhk>Hg~LnJD39>hz9Obq=Y zgxv0NC#QBnj0PJnQ{MFI&;tBTrqj+WkW3%zJ+#mpB-yzIM?xFwVAlukIkHeNeAf$y z`{@I%&QXQV$%Qd@U;)=)8bv5|Js#OtGL=L|-aA@ig2lmR`yLtHvAa`@QxN+EqS%_v zbn4iqdbh4ADBV;*W|0J6!XQo-im&Xt8R!=C{B{ouq*H=%w{Co56~fe>btTXJ7CQEt zmjQETJFcndZ@w&>wlmlU+kWL=RY3izr?f4zWiDb*tG)ZUxC7cgZSZ+KJFxm|JFr9! zqYE54+lLLdPq5L2r6ZHraTS^i@Aj%!94QL|G!L&a<0ki%N-oj}On=CXs`%Yqv1!Pt zD~aDbETf@X-D|H9;7pvm!mn2WM>pczu0qk1)u_zy54JF;j!>~HXQs}$7vIiKm|8%K z6~J8T{ApJvIE7H1WKVSk+80LdE~Ylsa4u{5hb$}}H@!@P-?&{o-%YejT>%jZC6`lDxX`t1@-5O*wW5oRFF%uKF&v5;CVZ15T%$?{mVaM8|Mhu z=WoSPGL?|SFi?JI79ZGIR&2l=z3Z z)+Rr4nQ{z=G?6I=$i_CD(-fu~#I5wk5;#Xy`$)g(HY*$v2($slBJnR4OqzHQGLSIX z?Y29TOoj@!>F!K%!kB+)$R(fxx99;}&D8{JP$Z_PA$4Ql%_&jW8>SYCh?Z>E1phz= zOkG5d$}HfI^QC{W)8lUxYwM*q*gStLaUj;R>(Ze&6H}Wkoa64GUJ2%}Zd$DZ*QLr0 z=6sN;T#>nEBj!Y6A$qJCRH!{e)YnS;aN0qx_AeOhlQ p@{qP+niXt}P48+;JDbRf#uB|?qt+3%*K6Yo)e8DH`im^|{{}4*{DJ@g literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-da_DK.po b/freemius/languages/freemius-da_DK.po new file mode 100644 index 0000000..5c89f56 --- /dev/null +++ b/freemius/languages/freemius-da_DK.po @@ -0,0 +1,2722 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" +o: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Joachim Jensen\n" +"Language-Team: Danish (Denmark) (http://www.transifex.com/freemius/wordpress-sdk/language/da_DK/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: da_DK\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "" +"An update to a Beta version will replace your installed version of %s with " +"the latest Beta release - use with caution, and not on production sites. You" +" have been warned." +msgstr "" + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "" + +#: includes/class-freemius.php:2053 +msgid "" +"Freemius SDK couldn't find the plugin's main file. Please contact " +"sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Fejl" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Jeg fandt et bedre %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Hvad er navnet pÃ¥ %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Det er en midlertidig %s. Jeg er i gang med fejlrettelser." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deaktivering" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Temaskift" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Andet" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Jeg har ikke længere brug for %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Jeg behøvede kun %s i en kort periode" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ødelagde min webside" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s stoppede pludseligt med at virke" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Jeg kan ikke længere betale for det" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Hvilken pris ville du foretrække at betale?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Jeg har ikke lyst til at dele mine informationer med jer" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s virkede ikke" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Jeg forstod ikke, hvordan jeg skulle fÃ¥ det til at fungere." + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s er godt, men jeg har brug for en specifik feature, som ikke understøttes" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Hvilken feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s virker ikke" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Det er ikke, hvad jeg søgte" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Hvad har du ledt efter?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s virkede ikke som forventet" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Hvad forventede du?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Jeg ved ikke hvad cURL er, eller hvordan jeg installerer det. Hjælp mig!" + +#: includes/class-freemius.php:4179 +msgid "" +"We'll make sure to contact your hosting company and resolve the issue. You " +"will get a follow-up email to %s once we have an update." +msgstr "Vi vil kontakte din udbyder og løse problemet. NÃ¥r vi har opdatinger i sagen, vil vi følge op med en email til dig pÃ¥ %s." + +#: includes/class-freemius.php:4186 +msgid "" +"Great, please install cURL and enable it in your php.ini file. In addition, " +"search for the 'disable_functions' directive in your php.ini file and remove" +" any disabled methods starting with 'curl_'. To make sure it was " +"successfully activated, use 'phpinfo()'. Once activated, deactivate the %s " +"and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Ja - fortsæt bare" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nej - bare deaktiver" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Ups" + +#: includes/class-freemius.php:4410 +msgid "" +"Thank for giving us the chance to fix it! A message was just sent to our " +"technical staff. We will get back to you as soon as we have an update to %s." +" Appreciate your patience." +msgstr "Tak fordi du giver os en chance for at fixe det! En besked er lige blevet sendt til vores tekniske personale. Vi vil vende tilbage, sÃ¥ snart der er nyt om %s. Vi sætter pris pÃ¥ din tÃ¥lmodighed." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s virker ikke uden %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s virker ikke uden pluginnet." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "" +"Unexpected API error. Please contact the %s's author with the following " +"error." +msgstr "" + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium-versionen af %s blev aktiveret." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +"Used to express elation, enthusiasm, or triumph (especially in electronic " +"communication)." +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Du har en %s licens." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Yee-haw" + +#: includes/class-freemius.php:5982 +msgid "" +"%s free trial was successfully cancelled. Since the add-on is premium only " +"it was automatically deactivated. If you like to use it in the future, " +"you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:5986 +msgid "" +"%s is a premium only add-on. You have to purchase a license first before " +"activating the plugin." +msgstr "" + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Mere information om %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Køb licens" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "" +"You should receive an activation email for %s to your mailbox at %s. Please " +"make sure you click the activation button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start prøveperioden" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "færdiggør installeringen" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Du mangler kun ét skridt - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Færdiggør aktivering af \"%s\" nu" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Vi har foretaget nogle rettelser til %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "Opgraderingen af %s blev fuldendt." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Tilføjelse" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "" +"An unknown error has occurred while trying to set the user's beta mode." +msgstr "" + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:12669 +msgid "" +"We couldn't find your email address in the system, are you sure it's the " +"right address?" +msgstr "Vi kunne ikke finde din e-mailadresse i systemet, er du sikker pÃ¥, det er den rigtige adresse?" + +#: includes/class-freemius.php:12671 +msgid "" +"We can't see any active licenses associated with that email address, are you" +" sure it's the right address?" +msgstr "Vi kan ikke finde nogen aktive licenser knyttet til den e-mailadresse, er du sikker pÃ¥, det er den rigtige adresse?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Konto afventer aktivering." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "Aktivering af %s blev gennemført." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Din konto blev aktiveret med planen %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Din prøveperiode er begyndt." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Kunne ikke aktivere %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Kontakt os venligst med følgende besked:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Opgrader" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start prøveperiode" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Priser" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Konto" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Kontakt os" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Tilføjelser" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Priser" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Supportforum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Din e-mailadresse er blevet verificeret - du er FOR SEJ!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "SÃ¥dan" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "Betalingen for tilføjelsen %s blev gennemført." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download den seneste version" + +#: includes/class-freemius.php:18751 +msgid "" +"Your server is blocking the access to Freemius' API, which is crucial for " +"%1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Fejl modtager fra serveren:" + +#: includes/class-freemius.php:18767 +msgid "" +"It seems like one of the authentication parameters is wrong. Update your " +"Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +"something somebody says when they are thinking about what you have just " +"said." +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "" +"It looks like you are still on the %s plan. If you did upgrade or change " +"your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Prøveperiode" + +#: includes/class-freemius.php:18980 +msgid "" +"I have upgraded my account but when I try to Sync the License, the plan " +"remains %s." +msgstr "Jeg har opgraderet min konto, men nÃ¥r jeg forsøger at synkronisere licensen, forbliver planen %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Kontakt os her" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Din plan er blevet opgraderet." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Din plan er blevet ændret til %s." + +#: includes/class-freemius.php:19029 +msgid "" +"Your license has expired. You can still continue using the free %s forever." +msgstr "Din licens er udløbet. Du kan stadig fortsætte med at benytte den gratis udgave af %s." + +#: includes/class-freemius.php:19031 +msgid "" +"Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s " +"without interruptions." +msgstr "Din licens er udløbet. %1$sOpgrader nu%2$s for at fortsætte med at benytte %3$s uden forstyrrelser." + +#: includes/class-freemius.php:19039 +msgid "" +"Your license has been cancelled. If you think it's a mistake, please contact" +" support." +msgstr "Din licens er blevet annulleret. Hvis du mener, dette er en fejl, sÃ¥ kontakt venligst support." + +#: includes/class-freemius.php:19052 +msgid "" +"Your license has expired. You can still continue using all the %s features, " +"but you'll need to renew your license to continue getting updates and " +"support." +msgstr "Din licens er udløbet. Du kan stadig benytte alle funktionerne i %s, men du bliver nødt til at fornye din licens for at fÃ¥ opdateringer og support." + +#: includes/class-freemius.php:19075 +msgid "" +"Your free trial has expired. You can still continue using all our free " +"features." +msgstr "Din gratis prøveperiode er udløbet. Du kan stadig benytte alle de gratis features." + +#: includes/class-freemius.php:19077 +msgid "" +"Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s " +"without interruptions." +msgstr "" + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Det ser ud til, at licensen ikke kunne aktiveres." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Din licens er blevet aktiveret." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Det ser ud til, at dit websted endnu ikke har en aktiv licens." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Det ser ud til, at licens-deaktiveringen mislykkedes." + +#: includes/class-freemius.php:19304 +msgid "" +"Your license was successfully deactivated, you are back to the %s plan." +msgstr "Din licens blev deaktiveret, du er tilbage pÃ¥ planen %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "" +"Seems like we are having some temporary issue with your subscription " +"cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19367 +msgid "" +"Your subscription was successfully cancelled. Your %s plan license will " +"expire in %s." +msgstr "" + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Du benytter allerede %s under en prøveperiode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Du har allerede brugt din prøveperiode." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s eksisterer ikke og kan derfor ikke starte prøveperiode." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s understøtter ikke en prøveperiode." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:19506 +msgid "" +"It looks like you are not in trial mode anymore so there's nothing to cancel" +" :)" +msgstr "Det lader ikke til du er i en prøveperiode længere, sÃ¥ der er ikke noget at annullere :-)" + +#: includes/class-freemius.php:19542 +msgid "" +"Seems like we are having some temporary issue with your trial cancellation. " +"Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Din gratis prøveperiode for %s er blevet annulleret." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s er blevet udgivet." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Download venligst %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "den seneste version af %s her" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Ny" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Det ser ud til, at du har den seneste udgivelse." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Det var det!" + +#: includes/class-freemius.php:20165 +msgid "" +"Verification mail was just sent to %s. If you can't find it after 5 min, " +"please check your spam box." +msgstr "" + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Websted er tilmeldt." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Sejt" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "" +"We appreciate your help in making the %s better by letting us track some " +"usage data." +msgstr "Vi sætter pris pÃ¥ din hjælp med at forbedre %s ved at lade os indsamle brugsdata." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Mange tak!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Vi vil ikke længere indsende brugsdata af %s pÃ¥ %s til %s." + +#: includes/class-freemius.php:20458 +msgid "" +"Please check your mailbox, you should receive an email via %s to confirm the" +" ownership change. From security reasons, you must confirm the change within" +" the next 15 min. If you cannot find the email, please check your spam " +"folder." +msgstr "" + +#: includes/class-freemius.php:20464 +msgid "" +"Thanks for confirming the ownership change. An email was just sent to %s for" +" final approval." +msgstr "" + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s er den nye ejer af kontoen." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Tillykke" + +#: includes/class-freemius.php:20491 +msgid "" +"Sorry, we could not complete the email update. Another user with the same " +"email is already registered." +msgstr "" + +#: includes/class-freemius.php:20492 +msgid "" +"If you would like to give up the ownership of the %s's account to %s click " +"the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Skift ejerskab" + +#: includes/class-freemius.php:20507 +msgid "" +"Your email was successfully updated. You should receive an email with " +"confirmation instructions in few moments." +msgstr "" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Indtast venligst dit fulde navn." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Dit navn er blevet opdateret." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Opdatering af %s blev gennemført." + +#: includes/class-freemius.php:20725 +msgid "" +"Just letting you know that the add-ons information of %s is being pulled " +"from an external server." +msgstr "" + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Se her" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "" +"How do you like %s so far? Test all our %s premium features with a %d-day " +"free trial." +msgstr "Hvad syntes du om %s indtil videre? Test alle %s premium funktioner med en %d-dags gratis prøveperiode." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Ingen bindinger i %s dage - annuller nÃ¥r som helst!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Betalingskort ikke pÃ¥krævet" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start gratis prøveperiode" + +#: includes/class-freemius.php:21258 +msgid "" +"Hey there, did you know that %s has an affiliate program? If you like the %s" +" you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Læs mere" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Aktiver licens" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Skift licens" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Frameld" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Tilmeld" + +#: includes/class-freemius.php:21775 +msgid "" +" The paid version of %1$s is already installed. Please activate it to start " +"benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Følg venligst disse trin for at færdiggøre opgraderingen" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download den seneste version af %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload og aktiver den downloadede version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Upload og aktivering, hvordan?" + +#: includes/class-freemius.php:21940 +msgid "" +"%sClick here%s to choose the sites where you'd like to activate the license " +"on." +msgstr "" + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto-installation fungerer kun for tilmeldte brugere." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ugyldigt modul-ID." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version allerede aktiv." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Du har ikke en gyldig licens til at benytte premium-versionen." + +#: includes/class-freemius.php:22134 +msgid "" +"Plugin is a \"Serviceware\" which means it does not have a premium code " +"version." +msgstr "" + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium tilføjelse er allerede installeret." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Vis betalte features" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Tak fordi du benytter %s!" + +#: includes/class-freemius.php:22826 +msgid "" +"You've already opted-in to our usage-tracking, which helps us keep improving" +" the %s." +msgstr "Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Mange tak for at benytte vores produkter!" + +#: includes/class-freemius.php:22831 +msgid "" +"You've already opted-in to our usage-tracking, which helps us keep improving" +" them." +msgstr "Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre dem." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s og tilføjelser" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Produkter" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Ja" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send mig sikkerheds- og feature-opdateringer, informativt indhold og tilbud." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nej" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "" +"do %sNOT%s send me security & feature updates, educational content and " +"offers." +msgstr "send %sIKKE%s sikkerheds- og feature-opdateringer, informativt indhold og tilbud." + +#: includes/class-freemius.php:22880 +msgid "" +"Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance " +"requirements it is required that you provide your explicit consent, again, " +"confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "" +"Please let us know if you'd like us to contact you for security & feature " +"updates, educational content, and occasional offers:" +msgstr "Lad os vide, om vi har lov til at kontakte dig med sikkerheds- og feature-opdateringer, informativt indhold og lejlighedsvise tilbud:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "Licensnøglen er tom." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Forny licens" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "" + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installerer plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1437 +msgid "" +"The remote plugin package does not contain a folder with the desired slug " +"and renaming did not work." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Køb" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start min gratis %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installer opdatering til gratis version nu" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installer opdatering nu" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer gratis version nu" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installer nu" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download seneste gratis version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download seneste" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Aktiver denne tilføjelse" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Aktiver gratis version" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Aktiver" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Beskrivelse" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installering" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Skærmbilleder" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Ændringslog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Anmeldelser" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Andre noter" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Funktioner og priser" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plugin-installering" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Bedste" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "MÃ¥nedligt" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Ã…rligt" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Livstid" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Faktureret %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Ã…rligt" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Engangsbeløb" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Ubegrænsede licenser" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Op til %s websteder" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "md" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "Ã¥r" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Pris" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Spar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Ingen bindinger ved %s - annuller nÃ¥r som helst" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Efter din gratis %s er prisen kun %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Detaljer" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Forfatter" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Senest opdateret" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s siden" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Kræver WordPress-version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s eller højere" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Kompatibel op til" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloadet" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s gang" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s gange" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin-side" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plugin-websted" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donér til dette plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Gennemsnitlig vurdering" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "baseret pÃ¥ %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s vurdering" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s vurderinger" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s stjerne" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stjerner" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Bidragsydere" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Advarsel" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "" +"This plugin has not been tested with your current version of WordPress." +msgstr "Dette plugin er ikke blevet testet med din nuværende version af WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "" +"This plugin has not been marked as compatible with your version of " +"WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nyere version (%s) installeret" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nyere gratis version (%s) installeret" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Seneste version installeret" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Seneste gratis version installeret" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "" +"%1$s will immediately stop all future recurring payments and your %2$s plan " +"license will expire in %3$s." +msgstr "" + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "" +"Please note that we will not be able to grandfather outdated pricing for " +"renewals/new subscriptions after a cancellation. If you choose to renew the " +"subscription manually in the future, after a price increase, which typically" +" occurs once a year, you will be charged the updated price." +msgstr "" + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "" +"Cancelling the trial will immediately block access to all premium features. " +"Are you sure?" +msgstr "" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "" +"You can still enjoy all %s features but you will not have access to %s " +"security & feature updates, nor support." +msgstr "" + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "" +"Once your license expires you can still use the Free version but you will " +"NOT have access to the %s features." +msgstr "" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Aktiver %s plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto-fornyer om %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Udløber om %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Synkroniser licens" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annuller prøveperiode" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Skift plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Opgrader" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Nedgrader" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Gratis prøveperiode" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Kontodetaljer" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "" + +#: templates/account.php:210 +msgid "" +"Deleting the account will automatically deactivate your %s plan license so " +"you can use it on other sites. If you want to terminate the recurring " +"payments as well, click the \"Cancel\" button, and first \"Downgrade\" your " +"account. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:212 +msgid "" +"Deletion is not temporary. Only delete if you no longer want to use this %s " +"anymore. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Slet konto" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deaktiver licens" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Er du sikker pÃ¥, du vil fortsætte?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annuller abonnement" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Synkroniser" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Navn" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "E-mail" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Bruger-ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Websteds-ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Intet ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Offentlig nøgle" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Privat nøgle" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Ingen privat nøgle" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Prøveperiode" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licensnøgle" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "" + +#: templates/account.php:435 +msgid "not verified" +msgstr "ikke verificeret" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Udløbet" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium version" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Gratis version" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verificer e-mail" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download 1%s version" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Vis" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Angiv venligst %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Rediger" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Websteder" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Søg efter adresse" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adresse" + +#: templates/account.php:610 +msgid "License" +msgstr "Licens" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licens" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Skjul" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Arbejder" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "" + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "" + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "" + +#: templates/account.php:858 +msgid "" +"Deactivating your license will block all premium features, but will enable " +"activating the license on another site. Are you sure you want to proceed?" +msgstr "" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Vis detaljer" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Tilføjelser til %s" + +#: templates/add-ons.php:55 +msgid "" +"We could'nt load the add-ons list. It's probably an issue on our side, " +"please try to come back in few minutes." +msgstr "" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Aktiv" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Fjern" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "1%s sek" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatisk installering" + +#: templates/auto-installation.php:93 +msgid "" +"An automated download and installation of %s (paid version) from %s will " +"start in %s. If you would like to do it manually - click the cancellation " +"button now." +msgstr "" + +#: templates/auto-installation.php:104 +msgid "" +"The installation process has started and may take a few minutes to complete." +" Please wait until it is done - do not refresh this page." +msgstr "" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuller installering" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Udtjekning" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Tillad & Fortsæt" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Gensend e-mail om aktivering" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Tak %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Accepter & aktiver licens" + +#: templates/connect.php:181 +msgid "" +"Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Tak for at købe %s! For at komme i gang, venligst indtast din licensnøgle:" + +#: templates/connect.php:188 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, educational content, offers, and non-sensitive diagnostic " +"tracking with %4$s." +msgstr "" + +#: templates/connect.php:189 +msgid "" +"Never miss an important update - opt in to our security and feature updates " +"notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:195 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, educational content, offers, and non-sensitive diagnostic " +"tracking with %4$s. If you skip this, that's okay! %1$s will still work just" +" fine." +msgstr "" + +#: templates/connect.php:196 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, and non-sensitive diagnostic tracking with %4$s. If you skip " +"this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:233 +msgid "" +"During the update process we detected %d site(s) that are still pending " +"license activation." +msgstr "" + +#: templates/connect.php:235 +msgid "" +"If you'd like to use the %s on those sites, please enter your license key " +"below and click the activation button." +msgstr "" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "" + +#: templates/connect.php:242 +msgid "" +"Alternatively, you can skip it for now and activate the license later, in " +"your %s's network-level Account page." +msgstr "" + +#: templates/connect.php:244 +msgid "" +"During the update process we detected %s site(s) in the network that are " +"still pending your attention." +msgstr "" + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licensnøgle" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Kan du ikke finde din licensnøgle?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Spring over" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "" + +#: templates/connect.php:318 +msgid "" +"If you click it, this decision will be delegated to the sites " +"administrators." +msgstr "" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Overblik af din profil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Navn og e-mailadresse" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Overblik af dit websted" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Websteds-URL, WP version, PHP info, plugins og temaer" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin-meddelelser" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Aktivering, deaktivering og afinstallering" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Nyhedsbrev" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "" +"The %1$s will be periodically sending data to %2$s to check for security and" +" feature updates, and verify the validity of your license." +msgstr "" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Hvilke tilladelser bliver givet?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Har du ikke en licensnøgle?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Har du en licensnøgle?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privatlivspolitik" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "ServicevilkÃ¥r" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Sender e-mail" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Aktiverer" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Kontakt" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Fra" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Til" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Fejlfinding" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Handlinger" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Er du sikker pÃ¥, du vil slette al Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Slet alle konti" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Ryd API-cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synkroniser data fra server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Hent DB-indstilling" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Sæt DB-indstilling" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Nøgle" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Værdi" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK-versioner" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK-sti" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Modul-sti" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Er aktiv" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temaer" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Kortnavn" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titel" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius tilstand" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Netværksblog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Netværksbruger" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Forbundet" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blokeret" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuler netværksopgradering" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s installeringer" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Websteder" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog-ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Slet" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Tilføjelser til modul %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Brugere" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificeret" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "1%s licenser" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin-ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan-ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Kvote" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Aktiveret" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blokerer" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Udløber" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Fejlfindingslog" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Alle typer" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Alle forespørgsler" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Fil" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Funktion" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Proces-ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Besked" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Tidsstempel" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Opdater" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Betaling" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Firmanavn" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Moms / VAT ID" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresselinje %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "By" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "By" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / Postnummer" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Land" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Vælg land" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Stat" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provins" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Betalinger" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Dato" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Beløb" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Faktura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Metode" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Kode" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Længde" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Sti" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Slut" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Om %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s siden" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sek" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synkronisering af plugins og temaer" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Sidste" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Planlagte cron jobs" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Modul" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Modultype" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Næste" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Udløber ikke" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "" + +#: templates/forms/affiliation.php:104 +msgid "" +"Your affiliate application for %s has been accepted! Log in to your " +"affiliate area at: %s." +msgstr "" + +#: templates/forms/affiliation.php:119 +msgid "" +"Thank you for applying for our affiliate program, we'll review your details " +"during the next 14 days and will get back to you with further information." +msgstr "" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "" + +#: templates/forms/affiliation.php:125 +msgid "" +"Thank you for applying for our affiliate program, unfortunately, we've " +"decided at this point to reject your application. Please try again in 30 " +"days." +msgstr "" + +#: templates/forms/affiliation.php:128 +msgid "" +"Due to violation of our affiliation terms, we decided to temporarily block " +"your affiliation account. If you have any questions, please contact support." +msgstr "" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "" + +#: templates/forms/affiliation.php:142 +msgid "" +"Refer new customers to our %s and earn %s commission on each successful sale" +" you refer!" +msgstr "" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Programoversigt" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "" + +#: templates/forms/affiliation.php:152 +msgid "" +"%s tracking cookie after the first visit to maximize earnings potential." +msgstr "" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "" + +#: templates/forms/affiliation.php:159 +msgid "" +"As we reserve 30 days for potential refunds, we only pay commissions that " +"are older than 30 days." +msgstr "" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "E-mailadresse" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Fulde navn" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "E-mailadresse til PayPal-konto" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Hvor vil du promovere %s?" + +#: templates/forms/affiliation.php:179 +msgid "" +"Enter the domain of your website or other websites from where you plan to " +"promote the %s." +msgstr "" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Tilføj andet domæne" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Ekstra domæner" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Andre domæner du vil markedsføre produktet fra." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promoveringsmetoder" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Sociale medier (Facebook, Twitter osv.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobil-apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Websted, e-mail, og statistikker for sociale medier (valgfrit)" + +#: templates/forms/affiliation.php:210 +msgid "" +"Please feel free to provide any relevant website or social media statistics," +" e.g. monthly unique site visits, number of email subscribers, followers, " +"etc. (we will keep this information confidential)." +msgstr "" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Hvordan vil du promovere os?" + +#: templates/forms/affiliation.php:217 +msgid "" +"Please provide details on how you intend to promote %s (please be as " +"specific as possible)." +msgstr "" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuller" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Bliv en affiliate" + +#: templates/forms/license-activation.php:21 +msgid "" +"Please enter the license key that you received in the email right after the " +"purchase:" +msgstr "Indtast licensnøglen, du modtog i e-mailen lige efter købet:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Opdater licens" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Frameld" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Tilmeld" + +#: templates/forms/optout.php:33 +msgid "" +"Usage tracking is done in the name of making %s better. Making a better user" +" experience, prioritizing new features, and more good things. We'd really " +"appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:35 +msgid "" +"By clicking \"Opt Out\", we will no longer be sending any data from %s to " +"%s." +msgstr "Ved at klikke \"Frameld\" vil vi ikke længere sende data fra %s til %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "En ny version af %s er tilgængelig." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Ny version tilgængelig" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Fjern" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Send licensnøgle" + +#: templates/forms/resend-key.php:57 +msgid "" +"Enter the email address you've used for the upgrade below and we will resend" +" you the license key." +msgstr "Indtast e-mailadressen, som du benyttede ved opgraderingen, nedenfor og vi vil gensende licensnøglen til dig." + +#: templates/forms/subscription-cancellation.php:37 +msgid "" +"Deactivating or uninstalling the %s will automatically disable the license, " +"which you'll be able to use on another site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:47 +msgid "" +"In case you are NOT planning on using this %s on this site (or any other " +"site) - would you like to cancel the %s as well?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: templates/forms/subscription-cancellation.php:57 +msgid "" +"Cancel %s - I no longer need any security & feature updates, nor support for" +" %s because I'm not planning to use the %s on this, or any other site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:68 +msgid "" +"Don't cancel %s - I'm still interested in getting security & feature " +"updates, as well as be able to contact support." +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "" +"%1$s will immediately stop all future recurring payments and your %s plan " +"license will expire in %s." +msgstr "" + +#: templates/forms/subscription-cancellation.php:103 +msgid "" +"Once your license expires you will no longer be able to use the %s, unless " +"you activate it again with a valid premium license." +msgstr "" + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "" + +#: templates/forms/trial-start.php:22 +msgid "" +"You are 1-click away from starting your %1$s-day free trial of the %2$s " +"plan." +msgstr "Du er 1 klik fra at begynde din %1$s dages gratis prøveperiode af planen %2$s." + +#: templates/forms/trial-start.php:28 +msgid "" +"For compliance with the WordPress.org guidelines, before we start the trial " +"we ask that you opt in with your user and non-sensitive site information, " +"allowing the %s to periodically send data to %s to check for version updates" +" and to validate your trial." +msgstr "" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Aktiver licens pÃ¥ alle websteder i netværket." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Anvend pÃ¥ alle websteder i netværket." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Akiver licens pÃ¥ alle afventende websteder." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Anvend pÃ¥ alle afventende websteder." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "tillad" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegér" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "spring over" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Klik for at vise skærmbillede %d i fuld skærm" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Ubegrænsede opdateringer" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s tilbage" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Seneste license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annulleret" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Udløber ikke" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Ejer-navn" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "E-mailadresse for ejer" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Ejer-ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Abonnement" + +#: templates/forms/deactivation/contact.php:19 +msgid "" +"Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Vi beklager ulejligheden, og vi er her for at hjælpe, hvis du giver os chancen." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Kontakt support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonym feedback" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deaktiver" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Aktiver %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Hvis du har tid, sÃ¥ lad os venligst vide hvorfor du %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deaktiverer" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "skifter" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Send & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Fortæl os venligst Ã¥rsagen, sÃ¥ vi kan forbedre det." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Ja - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Spring over & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Klik her for at benytte pluginnet anonymt" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "" +"You might have missed it, but you don't have to share any data and can just " +"%s the opt-in." +msgstr "Du har mÃ¥ske overset det, men du behøver ikke at dele data og kan blot %s tilmeldingen." diff --git a/freemius/languages/freemius-en.mo b/freemius/languages/freemius-en.mo new file mode 100644 index 0000000000000000000000000000000000000000..03eb52bbc95b7396d275b0583bc59a3c49bbcfdd GIT binary patch literal 53012 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

    WE3Ys}zZ`3Tu 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 It|RUK4~-(Dh5!Hn literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-en.po b/freemius/languages/freemius-en.po new file mode 100644 index 0000000..158f1b6 --- /dev/null +++ b/freemius/languages/freemius-en.po @@ -0,0 +1,2386 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +msgid "" +msgstr "" +"Project-Id-Version: freemius\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Vova Feldman \n" +"Language: \n" +"Language-Team: Freemius Team \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php:1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Error" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "I found a better %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "What's the %s's name?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "It's a temporary %s. I'm just debugging an issue." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deactivation" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Theme Switch" + +#: includes/class-freemius.php:2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Other" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "The %s broke my site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "The %s suddenly stopped working" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "I can't pay for it anymore" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "What price would you feel comfortable paying?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "I don't like to share my information with you" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "The %s didn't work" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "I couldn't understand how to make it work" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "What feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "The %s is not working" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Kindly share what didn't work so we can fix it for future users..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "It's not what I was looking for" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "What you've been looking for?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "The %s didn't work as expected" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "What did you expect?" + +#: includes/class-freemius.php:3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "I don't know what is cURL or how to install it, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Yes - do your thing" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - just deactivate" + +#: includes/class-freemius.php:4341, includes/class-freemius.php:4850, includes/class-freemius.php:5999, includes/class-freemius.php:12682, includes/class-freemius.php:16045, includes/class-freemius.php:16133, includes/class-freemius.php:16299, includes/class-freemius.php:18758, includes/class-freemius.php:18768, includes/class-freemius.php:19404, includes/class-freemius.php:20277, includes/class-freemius.php:20392, includes/class-freemius.php:20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s cannot run without %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s cannot run without the plugin." + +#: includes/class-freemius.php:5020, includes/class-freemius.php:5045, includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php:5699, includes/class-freemius.php:7567 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "You have a %s license." + +#: includes/class-freemius.php:5718, includes/class-freemius.php:15466, includes/class-freemius.php:15477, includes/class-freemius.php:18669, includes/class-freemius.php:18999, includes/class-freemius.php:19065, includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Yee-haw" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php:5995, templates/add-ons.php:130, templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "More information about %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Purchase License" + +#: includes/class-freemius.php:6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start the trial" + +#: includes/class-freemius.php:6936, templates/connect.php:167 +msgid "complete the install" +msgstr "complete the install" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "You are just one step away - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Complete \"%s\" Activation Now" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php:9728, includes/class-fs-plugin-updater.php:975, includes/class-fs-plugin-updater.php:1170, includes/class-fs-plugin-updater.php:1177, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php:9730, templates/account.php:313, templates/account.php:321, templates/debug.php:361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php:9731, templates/account.php:314, templates/account.php:322, templates/debug.php:361, templates/debug.php:522, templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Theme" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account is pending activation." + +#: includes/class-freemius.php:13057, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php:13069, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activation was successfully completed." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Your account was successfully activated with the %s plan." + +#: includes/class-freemius.php:15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Your trial has been successfully started." + +#: includes/class-freemius.php:16043, includes/class-freemius.php:16131, includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Couldn't activate %s." + +#: includes/class-freemius.php:16044, includes/class-freemius.php:16132, includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Please contact us with the following message:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php:16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Upgrade" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start Trial" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Pricing" + +#: includes/class-freemius.php:16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php:16772, includes/class-freemius.php:16774, templates/account.php:177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php:16787, includes/class-freemius.php:16789, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contact Us" + +#: includes/class-freemius.php:16799, includes/class-freemius.php:16801, includes/class-freemius.php:21423, templates/account.php:105, templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php:16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Pricing" + +#: includes/class-freemius.php:17050, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Support Forum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Your email has been successfully verified - you are AWESOME!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Right on" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Your %s Add-on plan was successfully upgraded." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on was successfully purchased." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download the latest version" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php:18757, includes/class-freemius.php:19188, includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error received from the server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php:18961, includes/class-freemius.php:19193, includes/class-freemius.php:19248, includes/class-freemius.php:19351 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php:18975, templates/account.php:107, templates/add-ons.php:191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Trial" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "I have upgraded my account but when I try to Sync the License, the plan remains %s." + +#: includes/class-freemius.php:18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Please contact us here" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Your plan was successfully upgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Your plan was successfully changed to %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Your license has been cancelled. If you think it's a mistake, please contact support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "It looks like the license could not be activated." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Your license was successfully activated." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "It looks like your site currently doesn't have an active license." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "It looks like the license deactivation failed." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Your license was successfully deactivated, you are back to the %s plan." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "You already utilized a trial before." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s do not exist, therefore, can't start a trial." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s does not support a trial period." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Your %s free trial was successfully cancelled." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s was released." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Please download %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "the latest %s version here" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "New" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Seems like you got the latest release." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "You are all good!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php:20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Awesome" + +#: includes/class-freemius.php:20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Thank you!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Thanks for confirming the ownership change. An email was just sent to %s for final approval." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is the new owner of the account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Congrats" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Change Ownership" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Please provide your full name." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Your name was successfully updated." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "You have successfully updated your %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Heads up" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "No commitment for %s days - cancel anytime!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "No credit card required" + +#: includes/class-freemius.php:21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start free trial" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Learn more" + +#: includes/class-freemius.php:21447, templates/account.php:474, templates/account.php:595, templates/connect.php:171, templates/connect.php:421, templates/forms/license-activation.php:25, templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activate License" + +#: includes/class-freemius.php:21448, templates/account.php:543, templates/account.php:594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Change License" + +#: includes/class-freemius.php:21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php:21541, includes/class-freemius.php:21547, templates/account/partials/site.php:43, templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Please follow these steps to complete the upgrade" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download the latest %s version" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload and activate the downloaded version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "How to upload and activate?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php:22111, includes/class-freemius.php:22144, includes/class-fs-plugin-updater.php:1149, includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Invalid module ID." + +#: includes/class-freemius.php:22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version already active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "You do not have a valid license to access the premium version." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php:22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "View paid features" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Products" + +#: includes/class-freemius.php:22866, templates/connect.php:272 +msgid "Yes" +msgstr "Yes" + +#: includes/class-freemius.php:22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php:22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php:22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "License key is empty." + +#: includes/class-fs-plugin-updater.php:184, templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renew license" + +#: includes/class-fs-plugin-updater.php:189, templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php:280, includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php:282, includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php:283, includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installing plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php:510, templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Purchase" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start my free %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php:713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Install Update Now" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php:723, templates/add-ons.php:262, templates/auto-installation.php:111, templates/account/partials/addon.php:327, templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Install Now" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php:740, templates/account.php:85, templates/add-ons.php:34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Latest" + +#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:268, templates/account/partials/addon.php:318, templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activate this add-on" + +#: includes/fs-plugin-info-dialog.php:757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activate Free Version" + +#: includes/fs-plugin-info-dialog.php:758, templates/account.php:109, templates/add-ons.php:269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activate" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Description" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installation" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php:971, templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Screenshots" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Reviews" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Other Notes" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Features & Pricing" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plugin Install" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Best" + +#: includes/fs-plugin-info-dialog.php:1103, includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Monthly" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annual" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Lifetime" + +#: includes/fs-plugin-info-dialog.php:1123, includes/fs-plugin-info-dialog.php:1125, includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Billed %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annually" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Once" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Single Site License" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Unlimited Licenses" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Up to %s Sites" + +#: includes/fs-plugin-info-dialog.php:1147, templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mo" + +#: includes/fs-plugin-info-dialog.php:1154, templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "year" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Price" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Save %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Details" + +#: includes/fs-plugin-info-dialog.php:1284, templates/account.php:96, templates/debug.php:203, templates/debug.php:240, templates/debug.php:454, templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Author" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Last Updated" + +#: includes/fs-plugin-info-dialog.php:1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s ago" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Requires WordPress Version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s or higher" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloaded" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s time" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s times" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin Page" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plugin Homepage" + +#: includes/fs-plugin-info-dialog.php:1360, includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donate to this plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Average Rating" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "based on %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s rating" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s ratings" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s star" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stars" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributors" + +#: includes/fs-plugin-info-dialog.php:1450, includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Warning" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "This plugin has not been tested with your current version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "This plugin has not been marked as compatible with your version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Newer Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Latest Version Installed" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Latest Free Version Installed" + +#: templates/account.php:86, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php:87, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php:90, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php:91, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancelling the trial will immediately block access to all premium features. Are you sure?" + +#: templates/account.php:92, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php:93, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php:95, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activate %s Plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php:98, templates/account/partials/addon.php:38, templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renews in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php:100, templates/account/partials/addon.php:40, templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Expires in %s" + +#: templates/account.php:101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sync License" + +#: templates/account.php:102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Cancel Trial" + +#: templates/account.php:103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Change Plan" + +#: templates/account.php:104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Upgrade" + +#: templates/account.php:106, templates/account/partials/addon.php:46, templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php:108, templates/add-ons.php:187, templates/plugin-info/features.php:72, templates/account/partials/addon.php:48, templates/account/partials/site.php:31 +msgid "Free" +msgstr "Free" + +#: templates/account.php:110, templates/debug.php:373, includes/customizer/class-fs-customizer-upsell-control.php:106, templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Free Trial" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Account Details" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Delete Account" + +#: templates/account.php:227, templates/account/partials/addon.php:211, templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deactivate License" + +#: templates/account.php:250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Are you sure you want to proceed?" + +#: templates/account.php:250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Cancel Subscription" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sync" + +#: templates/account.php:292, templates/debug.php:489 +msgid "Name" +msgstr "Name" + +#: templates/account.php:298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php:305, templates/debug.php:372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID" + +#: templates/account.php:322, templates/account.php:608, templates/account.php:653, templates/debug.php:238, templates/debug.php:366, templates/debug.php:451, templates/debug.php:488, templates/debug.php:526, templates/debug.php:599, templates/account/payments.php:35, templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "No ID" + +#: templates/account.php:337, templates/debug.php:245, templates/debug.php:374, templates/debug.php:455, templates/debug.php:492, templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Public Key" + +#: templates/account.php:343, templates/debug.php:375, templates/debug.php:456, templates/debug.php:493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Secret Key" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "No Secret" + +#: templates/account.php:373, templates/account/partials/site.php:112, templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Trial" + +#: templates/account.php:400, templates/debug.php:533, templates/account/partials/site.php:248 +msgid "License Key" +msgstr "License Key" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "not verified" + +#: templates/account.php:444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Expired" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium version" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Free version" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verify Email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download %s Version" + +#: templates/account.php:541, templates/account.php:749, templates/account/partials/site.php:237, templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Show" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "What is your %s?" + +#: templates/account.php:563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Edit" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Search by address" + +#: templates/account.php:609, templates/debug.php:369 +msgid "Address" +msgstr "Address" + +#: templates/account.php:610 +msgid "License" +msgstr "License" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "License" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Hide" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php:826, templates/account.php:843, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php:841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php:844, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "View details" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons for %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php:13, templates/forms/license-activation.php:209, templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Dismiss" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatic Installation" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Cancel Installation" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Checkout" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Allow & Continue" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Re-send activation email" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Thanks %s!" + +#: templates/connect.php:172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Agree & Activate License" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Thanks for purchasing %s! To get started, please enter your license key:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php:253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "License key" + +#: templates/connect.php:256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Can't find your license key?" + +#: templates/connect.php:315, templates/connect.php:652, templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Skip" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Your Profile Overview" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Name and email address" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Your Site Overview" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP version, PHP info, plugins & themes" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin Notices" + +#: templates/connect.php:359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Updates, announcements, marketing, no spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Current %s Events" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activation, deactivation and uninstall" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php:391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "What permissions are being granted?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Don't have a license key?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Have a license key?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privacy Policy" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Terms of Service" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Sending email" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activating" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php:54, templates/debug.php:250, templates/debug.php:376, templates/debug.php:494 +msgid "Actions" +msgstr "Actions" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Delete All Accounts" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Clear API Cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sync Data From Server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Key" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Value" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK Versions" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK Path" + +#: templates/debug.php:205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Is Active" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Themes" + +#: templates/debug.php:239, templates/debug.php:371, templates/debug.php:453, templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php:241, templates/debug.php:452 +msgid "Title" +msgstr "Title" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius State" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connected" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blocked" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installs" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php:368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php:433, templates/debug.php:511, templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Delete" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Users" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verified" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activated" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "All Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "All Requests" + +#: templates/debug.php:573, templates/debug.php:602, templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php:574, templates/debug.php:600, templates/debug/logger.php:23 +msgid "Function" +msgstr "Function" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Process ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php:577, templates/debug.php:601, templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php:598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php:603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Update" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Billing" + +#: templates/account/billing.php:38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Business name" + +#: templates/account/billing.php:39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Tax / VAT ID" + +#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Address Line %d" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "City" +msgstr "City" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "Town" +msgstr "Town" + +#: templates/account/billing.php:47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / Postal Code" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Country" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Select Country" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "State" +msgstr "State" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "Province" +msgstr "Province" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Payments" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Date" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Amount" + +#: templates/account/payments.php:38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Invoice" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Length" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Path" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s ago" + +#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Plugins & Themes Sync" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Module Type" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php:165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Email address" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Full name" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account email address" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Add another domain" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domains" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, email, and social media statistics (optional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php:223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Cancel" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Please enter the license key that you received in the email right after the purchase:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Update License" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "There is a new version of %s available." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "New Version Available" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Dismiss" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Send License Key" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Enter the email address you've used for the upgrade below and we will resend you the license key." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:29, templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +msgid "skip" +msgstr "skip" + +#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Unlimited Updates" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s left" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Last license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Cancelled" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "No expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Owner Name" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Owner Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Owner ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Subscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry for the inconvenience and we are here to help if you give us a chance." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contact Support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonymous feedback" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deactivate" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activate %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "If you have a moment, please let us know why you are %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactivating" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "switching" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Submit & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Kindly tell us the reason so we can improve." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Skip & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Click here to use the plugin anonymously" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "You might have missed it, but you don't have to share any data and can just %s the opt-in." diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo new file mode 100644 index 0000000000000000000000000000000000000000..b2145c34636eb589f720bc1d9126174e200f5338 GIT binary patch literal 56182 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

    j;UP?YVNY36p9h-5AvTEH0 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 IUY7R%0B0z;(*OVf literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-es_ES.po b/freemius/languages/freemius-es_ES.po new file mode 100644 index 0000000..daef057 --- /dev/null +++ b/freemius/languages/freemius-es_ES.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Carlos Longarela , 2017-2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Carlos Longarela \n" +"Language: es_ES\n" +"Language-Team: Spanish (Spain) (http://www.transifex.com/freemius/wordpress-sdk/language/es_ES/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK no pudo encontrar el archivo principal del plugin. Por favor contacta a sdk@freemius.com con el error actual." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Error" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "He encontrado un %s mejor" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "¿Cuál es el nombre de %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Es una %stemporal . Sólo estoy depurando un problema" + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Desactivación" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Cambiar tema" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Otra" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Ya no necesito el %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Sólo necesitaba la %s por un corto período" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ha roto mi sitio" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s de repente ha dejado de funcionar" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "No puedo pagarlo durante más tiempo" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "¿Con qué precio te sentirías cómodo pagando?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "No me gusta compartir mi información contigo" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "El %s no funcionaba" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "No entiendo cómo hacerlo funcionar" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s es genial, pero necesito una característica que no soportáis" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "¿Qué característica?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr " El %s no funciona" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Por favor, comparte lo que no funcionó para que podamos arreglarlo para los futuros usuarios..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "No es lo que estaba buscando" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "¿Que has estado buscando?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr " El %s no funciona como esperaba" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "¿Qué esperas?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Debug Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "No sé qué es cURL o cómo instalarlo, ¡ayúdame!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Nos aseguraremos de ponernos en contacto con tu empresa de alojamiento web y resolver el problema. Recibirás un correo electrónico de seguimiento a %s tan pronto tengamos una actualización." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Genial, por favor instala cURL y habilítalo en el archivo php.ini. Además, busca la directiva 'disable_functions' en el archivo php.ini y quita cualquier método que comienza con 'curl_'. Para asegurarte de que se activó con éxito, utiliza 'phpinfo()'. Una vez activado, desactiva el %s y reactívalo de nuevo." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Vamos, adelante" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - sólo desactivar" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "¡Gracias por darnos la oportunidad de arreglarlo! Acabamos de enviar un mensaje a nuestro personal técnico. Nos pondremos en contacto contigo tan pronto como tengamos una actualización de %s. Apreciamos tu paciencia." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s no se puede ejecutar sin %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s no se puede ejecutar sin el plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Error inesperado del API. Pónte en contacto con el autor de %s indicándole el siguiente error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La versión Premium %s ha sido activada con éxito." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Tienes una licencia %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Vaya" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "la prueba gratuita de %s fue cancelada con éxito. Puesto que el complemento es sólo premium se desactivó automáticamente. Si quieres utilizarlo en el futuro, deberás comprar una licencia." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s es un complemento único de premium. Tienes que comprar una licencia primero antes de activar el plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Más información sobre %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Comprar licencia" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Recibirás un correo de activación para %s en tu buzón en %s. Por favor, asegúrate de hacer clic en el botón de activación en ese correo electrónico para %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "comenzar el período de prueba" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "completar la instalación" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Estás a sólo un paso - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Completar la activación de \"%s\" ahora" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Hemos realizado algunas optimizaciones al %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "¡Inscríbite para hacer \"%s\" Mejor!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "La actualización de %s se completó con éxito." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Complemento" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Colección de detalles del sitio no válida." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "No podemos encontrar tu dirección de correo electrónico en el sistema, ¿estás seguro de que es la dirección de correo electrónico correcta?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "No vemos ninguna licencia activa asociada a esa dirección de correo electrónico, ¿estás seguro de que es la dirección de correo electrónico correcta?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "La cuenta está pendiente de activación" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Compra una licencia ahora" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renueva tu licencia ahora" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activación se completó con éxito." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Tu cuenta se ha activado correctamente con el plan %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Tu versión de prueba se ha iniciado con éxito." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "No se puede activar %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Por favor contáctanos con el siguiente mensaje:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Actualizar" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Comenzar el período de prueba" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Precio" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Afiliación" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Cuenta" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contáctanos" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Complementos" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Precio" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Foro de soporte" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Tu email ha sido verificado correctamente - ¡Eres IMPRESIONANTE!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Bien hecho" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Tu complemento %s del plan se actualizó con éxito." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "El complemento %s ha sido comprado correctamente." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Descargar la última versión" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error recibido del servidor:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Parece que uno de los parámetros de autenticación es incorrecto. Actualiza tu clave pública, clave secreta e ID de usuario e inténtelo de nuevo." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Parece que todavía estás en el plan %s. Si actualizaste o cambiaste tu plan, probablemente sea un problema de nuestra parte - lo sentimos." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Período de Prueba Gratuito" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "He actualizado mi cuenta, pero cuando intento sincronizar la licencia, el plan sigue siendo %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Contacta aquí con nosotros" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Tu plan se actualizó con éxito." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Tu plan se cambió correctamente a %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Tu licencia ha caducado. Puedes seguir usando el plan gratuito %s para siempre." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Tu licencia ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Tu licencia ha sido cancelada. Si crees que es un error, ponte en contacto con el servicio de asistencia." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Tu licencia ha caducado. Todavía puedes seguir usando todas las funciones de %s, pero tendrás que renovar tu licencia para seguir recibiendo actualizaciones y soporte." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Tu período de prueba ha caducado. Todavía puedes seguir usando todas nuestras funciones gratuitas." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Tu período de prueba ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Parece que la licencia no se pudo activar." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Tu licencia fue activada correctamente." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Parece que tu sitio actualmente no tiene una licencia activa." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Parece que la desactivación de licencia ha fallado." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Tu licencia fue desactivada correctamente, has vuelto al plan %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Parece que estamos teniendo algún problema temporal con tu cancelación de la suscripción. Vuelve a intentarlo en unos minutos." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Tu suscripción ha sido cancelada correctamente. Tu %s licencia del plan caducará en %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Estás ejecutando %s en modo de prueba." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Ya utilizaste un período de prueba antes." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "El plan %s no existe, por lo tanto, no puedes comenzar un período de prueba." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "El plan %s no admite un período de prueba." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Ninguno de los planes de %s soportan un período de prueba." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Parece que ya no estás en modo de prueba, así que no hay nada que cancelar :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Parece que estamos teniendo algún problema temporal con tu cancelación de prueba. Vuelve a intentarlo en unos minutos." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Tu prueba gratuita de %s fue cancelada con éxito." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La versión %s se ha lanzado." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Por favor descarga %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "la última versión %s aquí" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nuevo" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Parece que tienes la última versión." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "¡Está todo listo!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "El correo de verificación se acaba de enviar a %s. Si no puedes encontrarlo después de 5 min, comprueba tu carpeta de spam." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Sitio dado de alta correctamente." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Increíble" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Agradecemos tu ayuda para mejorar %s y por permitirnos rastrear algunos datos de uso." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "¡Gracias!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "No continuaremos enviando datos de uso de %s en %s a %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Comprueba tu buzón de correo, debes recibir un correo electrónico a través de %s para confirmar el cambio de propiedad. Por razones de seguridad, debes confirmar el cambio dentro de los próximos 15 min. Si no puedes encontrar el correo electrónico, comprueba tu carpeta de correo no deseado." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Gracias por confirmar el cambio de propiedad. Se envió un correo electrónico a %s para su aprobación final." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s es el nuevo dueño de la cuenta." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Felicidades" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Lo sentimos, no podemos completar la actualización de correo electrónico. Ya hay registrado otro usuario con esa dirección de correo electrónico." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Si deseas renunciar a la titularidad de la cuenta de %s a %s haz clic en el botón de cambio de titularidad." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Cambiar propietario" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Se actualizó correctamente tu correo electrónico. Recibirás un correo electrónico con las instrucciones de confirmación en unos momentos." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Por favor, dinos tu nombre completo." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Tu nombre fue actualizado correctamente." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Has actualizado correctamente tu %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Sólo déjanos informarte que la información de complementos de %s se está extrayendo de un servidor externo." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Atención" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "¿Qué te pareció %s hasta ahora? Prueba todas nuestras funciones premium de %s con una prueba gratuita de % d-días." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Sin compromiso por %s días - ¡cancelar en cualquier momento!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "No se necesita tarjeta de crédito" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Comenzar el período de prueba gratuito" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey, ¿sabías que %s tiene un programa de afiliados? ¡Si te gusta %s puedes convertirte en nuestro embajador y ganar dinero!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Saber más" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activar licencia" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Cambiar licencia" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Darse de baja" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Inscribirse" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activar características %s" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Por favor, sigue estos pasos para completar la actualización" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Descargar la última versión %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Cargar y activar la versión descargada" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "¿Cómo subirlo y activarlo?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick aquí %s para elegir los sitios sobre los que te gustaría activar la licencia." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "La instalación automática sólo funciona para usuarios que aceptaron." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Id de módulo no válido." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Versión premium ya activa." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "No tienes una licencia válida para acceder a la versión premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "El plugin es un \"Serviceware\" lo que significa que no tiene una versión de código premium." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Versión del complemento premium ya instalada." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Ver las funciones de pago" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "¡Muchas gracias por utilizar %s y sus complementos!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "¡Muchas gracias por utilizar %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "¡Muchas gracias por utilizar nuestros productos!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s y sus complementos" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Productos" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Si" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "envíame actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%sNO%s me envíes actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Indica si deseas que te contactemos para actualizaciones de seguridad y nuevas funciones, contenido educativo y ofertas ocasionales:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La clave de licencia está vacía." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renovar la licencia" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Comprar licencia" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Hay una %s de %s disponible." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "nueva versión" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Aviso importante de actualización:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Instalando plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "No es posible conectarse al sistema de archivos. Por favor, confirma tus credenciales." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "El paquete de plugin remoto no contiene una carpeta con el Slug deseado y el cambio de nombre no funcionó." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Comprar" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Comenzar mi período gratuito de %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Instalar la actualización gratuita ahora" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Instalar actualización ahora" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Instalar la versión gratuita ahora" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Instalar ahora" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Descargar la última versión gratuita" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Descargar la última" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activar este complemento" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activar versión gratuita" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activar" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Descripción" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Instalación" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Capturas de pantalla" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Registro de cambios" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Valoraciones" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Otras notas" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Características y precios" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Instalar plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Plan %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "El mejor" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensual" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Anual" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Permanente" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Facturado %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Anualmente" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Una vez" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licencia para un único sitio" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licencias ilimitadas" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Hasta %s sitios" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "me" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "año" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Precio" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Guardar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Sin compromiso para %s - cancelar en cualquier momento" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Después de su período gratuito %s, pague sólo %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Detalles" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versión" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Autor" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Última actualización" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "hace %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Necesita la versión de WordPress" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s o mayor" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible hasta" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Descargado" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "% vez" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s veces" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Página del plugin en WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Página web del plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donar a este plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Calificación media" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "basado en %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s calificación" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s calificaciones" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s estrella" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s estrellas" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Haz clic para ver los comentarios con una valoración de %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Colaboradores" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Atencion" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Este plugin no ha sido probado con tu versión actual de WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Este puglin no ha sido marcado como compatible con tu versión de WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "El complemento de pago se debe implementar en Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "El complemento debe implementarse en WordPress.org o en Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Versión más reciente (%s) instalada" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Versión gratuita más reciente (%s) instalada" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Última versión instalada" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Última versión gratuita instalada" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Bajando tu plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelando la suscripción" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Ten en cuenta que no podremos abaratar los precios desactualizados para renovaciones/nuevas suscripciones después de una cancelación. Si eliges renovar la suscripción manualmente en el futuro, después de un aumento de precio, que generalmente ocurre una vez al año, se te cobrará el precio actualizado." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "La cancelación del período de prueba bloqueará inmediatamente el acceso a todas las funciones premium. ¿Estás seguro?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Todavía puedes disfrutar de todas las funciones de %s pero no tendrás acceso a soporte y actualizaciones de %s." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Una vez que caduque tu licencia todavía puedes utilizar la versión gratuita pero NO tendrás acceso a las funciones de %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activar plan %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renovaciones en %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Caduca en %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sincronizar licencia" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Cancelar período de prueba" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Cambiar Plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Actualizar" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Degradar" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Período de prueba gratuito" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Detalles de la cuenta" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "La eliminación de la cuenta desactivará automáticamente su licencia de plan %s para que pueda utilizarla en otros sitios. Si también desea cancelar los pagos periódicos, haga clic en el botón \"Cancelar\" y, en primer lugar, \"Degradar\" su cuenta. ¿Seguro que deseas continuar con la eliminación?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La eliminación no es temporal. Sólo elimínalo si ya no deseas utilizar este %s más. ¿Estás seguro que desea continuar con la eliminación?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Borrar cuenta" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Desactivar licencia" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "¿Estás seguro que quieres proceder?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Cancelar suscripción" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sincronizar" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nombre" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Correo electrónico" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ID de usuario" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "ID del sitio" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Sin ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Clave pública" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Clave secreta" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Sin clave secreta" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Período de prueba gratuito" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Clave de licencia" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "no verificado" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Caducado" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Versión premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Versión gratuita" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verificar correo electrónico" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Descargar versión %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mostrar" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "¿Cual es tú %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Editar" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sitios" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Buscar por dirección" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Dirección" + +#: templates/account.php:610 +msgid "License" +msgstr "Licencia" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licencia" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Ocultar" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Procesando" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelando %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "período de prueba" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelando %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "suscripción" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Al desactivar tu licencia todas las características premium se bloquearán, pero posibilitará poder activar tu licencia en otro sitio. ¿Estás seguro que quieres continuar?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Ver detalles" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Complementos para %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "No podemos cargar la lista de complementos. Probablemente es un problema por nuestro parte, por favor inténtalo de nuevo en unos minutos." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Activo" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Descartar" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s seg" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Instalación automática" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Una descarga automatizada y la instalación de %s (versión de pago) de %s comenzará en %s. Si quieres hacerlo manualmente - haz clic en el botón de cancelación." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "El proceso de instalación ha comenzado y puede tardar unos minutos en completarse. Por favor, espera hasta que se finalice - no actualices esta página." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Cancelar instalación" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Pagar" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Compatible con PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Permitir y continuar" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Reenviar correo electrónico de activación" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "¡Gracias %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "De acuerdo y activar licencia" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "¡Gracias por comprar %s! Para empezar, escribe tu clave de licencia:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "No te pierdas ninguna actualización importante - acepta para notificaciones de seguridad y de actualizaciones, ofertas y seguimiento de diagnóstico con datos no sensibles con %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "No te pierdas ninguna actualización importante - acepta para notificaciones de seguridad y de actualizaciones y seguimiento de diagnóstico con datos no sensibles con %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "No te pierdas ninguna actualización importante - acepta las notificaciones de seguridad y de actualizaciones, contenido educacional, ofertas y seguimiento de diagnóstico con datos no sensibles con %4$s. ¡Si te saltas esto, no pasa nada! %1$s seguirá funcionando bien." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "No te pierdas ninguna actualización importante - acepta las notificaciones de seguridad y de actualizaciones y seguimiento de diagnóstico con datos no sensibles con %4$s. ¡Si te saltas esto, no pasa nada! %1$s seguirá funcionando bien." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Estamos emocionados de introducir la integración de Freemius a nivel de red." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durante el proceso de actualización hemos detectado%d sitio(s) que aún están pendientes de la activación de licencia." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Si quieres utilizar %s en estos sitios, introduce por favor tu clave de licencia abajo y haz click en el botón de activación." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s características de pago" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternativamente, puedes saltarlo ahora y activar la licencia después, en tu %s página de cuenta a nivel de red." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durante el proceso de actualización detectamos %s sitio(s) en la red que todavía están pendientes de tu atención." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Clave de licencia" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "¿No puedes encontrar tu clave de licencia?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Saltar" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegar a administradores del sitio" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Si haces click, esta decisión será delegada a los administradores de los sitios." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Resumen del perfil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nombre y dirección de correo electrónico" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Resumen del sitio" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL del sitio web, versión de WP, PHP info, plugins y temas" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Avisos de administración" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Actualizaciones, anuncios, marketing, sin spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Eventos de %s actuales" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activación, desactivación y desinstalación" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Boletín" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$s periódicamente enviará datos a %2$s para comprobar las actualizaciones de seguridad, nuevas funcionalidades y verificar la validez de tu licencia." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "¿Qué permisos se otorgan?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "¿No tienes una clave de licencia?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "¿Tienes una clave de licencia?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Política de privacidad" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Acuerdo de licencia" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Términos de servicio" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Enviando correo electrónico" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activando" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contacto" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Apagado" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Encendido" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Depurando" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Acciones" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "¿Está seguro que desea eliminar todos los datos de Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Borrar todas las cuentas" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Borrar caché de la API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Borrar transients de actualizaciones" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sincronizar datos desde el servidor" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrar opciones a la red" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Cargar opción de BD" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Guardar opción en BD" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Clave" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valor" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versiones SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Ruta del SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Ruta del módulo" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Está activo" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temas" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Ruta" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Título" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Estado Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Blog de red" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Usuario de red" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Conectado" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simular período de prueba" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simular actualización de red" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Instalaciones" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sitios" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "ID del blog" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Borrar" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Complementos del módulo %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Usuarios" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificado" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licencias" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID del plugin" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID del plan" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Cuota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activado" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloqueando" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Caducidad" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Log de Debug" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Todos los Tipos" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Todas las peticiones" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Archivo" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Función" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID del proceso" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Mensaje" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filtro" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Descarga" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Tipo" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Página segura HTTPS %s, desde un dominio externo" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Soporte" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "API Freemius" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Peticiones" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Actualizar" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturación" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Nombre de la empresa" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Tax / Núm IVA" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Línea de la dirección %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Ciudad" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Municipio" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Código postal" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "País" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Seleccionar país" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Estado" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincia" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Pagos" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Fecha" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Cantidad" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Factura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Método" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Código" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Longitud" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Ruta" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Cuerpo" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultado" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Inicio" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fin" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "En %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "hace %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "seg" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Sincronizar plugins y temas" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Último" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Crons programados" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Módulo" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Tipo de módulo" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Tipo de cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Siguiente" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Sin caducidad" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Aceptar para hacerse afiliado" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "¡Tu aplicación al programa de afiliación para %s ha sido aceptada! Entra en tu área de afiliado desde: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Gracias por aplicar a nuestro programa de afiliados, revisaremos tu petición durante los próximos 14 días y te volveremos a contactar con información adicional." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Tu cuenta de afiliado ha sido suspendida temporalmente." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Gracias por aplicar a nuestro programa de asociados, infortunadamente, de momento hemos decidido rechazar tu petición. Por favor, prueba de nuevo en 30 días." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Debido a la violación de nuestros términos de afiliados, hemos decidido bloquear temporalmente tu cuenta de afiliación. Si tienes alguna pregunta, por favor contacta nuestro soporte." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "¿Te gusta %s? Conviértete en nuestro embajador y gana dinero ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "¡Envíanos nuevos usuarios a nuestro %s y gana %s de comisión en cada venta satisfactoria que nos hayas referido!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sumario del programa" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s comisión cuando un cliente compra una nueva licencia." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Obtén comisiones por renovaciones automatizadas de las suscripciones." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie después de la primera visita para maximizar las ganancias potenciales." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Comisiones Ilimitadas" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s cantidad mínima a pagar." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Los pagos son en USD y se procesan mensualmente por medio de PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Como aplazamos 30 días para posible devoluciones, sólo pagamos comisiones que son de más de 30 días." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Afiliado" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Dirección de correo electrónico" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nombre completo" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Dirección de correo electrónico de PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "¿Dónde vas a promocionar %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Introduce el dominio de tu sitio web o de otros sitios web donde planeas promocionar %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Añadir otro dominio" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Dominios extra" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Dominios extra desde donde promocionarás el producto." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Métodos de promoción" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Apps móviles " + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Sitio web, correo electrónico y estadísticas de social media (opcional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Siéntete libre de proporcionarnos estadísticas de tu sitio web o social media, p.ej. visitas únicas mensuales, número de suscriptores de correo electrónico, seguidores, etc. (mantendremos esta información confidencial)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "¿Como nos promocionarás?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Por favor, danos detalles de como pretendes promocionar %s (por favor, se lo más específico que puedas)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Cancelar" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Hacerse afiliado" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Por favor, introduce la clave de licencia que recibiste en el correo electrónico al realizar la compra:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Activar licencia" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Darse de baja" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Inscribirse" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "El uso del seguimiento se hace con la intención de mejorar %s. Crear una mejor experiencia de usuario, priorizando nuevas características y cosas mejores. Realmente apreciaríamos que considerases permitirnos continuar con el seguimiento." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Haciendo clic en \"Desistir\", ya no enviaremos los datos de %s a %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Hay una nueva versión de %s disponible." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nueva versión disponible" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Descartar" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Enviar clave de licencia" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Escribe abajo la dirección de correo electrónico que has usado para la actualización y te reenviaremos la clave de licencia." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Desactivar o desinstalar %s deshabilitará automáticamente la licencia, que podrás usar en otro sitio." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "En caso de que NO estés planeando utilizar este %s en este sitio (o en cualquier otro sitio), ¿te gustaría cancelar también %s?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licencia" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancelar %s - No necesito más actualizaciones de características y seguridad, ni soporte para %s porque no pretendo utilizar%s en este, u otro sitio." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "No cancelar %s - Todavía estoy interesado en obtener actualizaciones de características y seguridad, así como poder contactar con soporte." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Una vez que tu licencia caduque no podrás seguir utilizando %s, a no ser que lo actives de nuevo con una licencia premium válida." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "¿Cancelar %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceder" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancelar %s y proceder" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Estás a sólo 1-click de comenzar tu %1$s días de prueba gratuita del plan %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Para el cumplimiento de las directrices de WordPress.org, antes de empezar el período de prueba te pedimos que aceptes con tu usuario e información no sensible del sitio web, permitiendo a %s enviar datos periódicamente a %s para comprobar si hay actualizaciones de versión y para validar la versión de prueba." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activar licencia en todos los sitios de la red" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Aplicar en todos los sitios de la red" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Aplicar licencia en todos los sitios pendientes" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Aplicar en todos los sitios pendientes" + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "permitir" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegar" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "saltar" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click para ver la captura de pantalla a tamaño completo %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Actualizaciones Ilimitadas" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "quedan %s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Última licencia" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Cancelado" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Sin caducidad" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nombre del propietario" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Correo electrónico del propietario" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID del propietario" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Suscripción" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Disculpa las molestias y estamos aquí para ayudarte si nos das una oportunidad." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contactar soporte" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Comentarios anónimos" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Desactivar" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activar %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Comentarios rápidos" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Si tienes un momento, por favor, dinos por qué estás %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "desactivando" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "cambiando" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Enviar y %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Por favor, dínos la razón para que podamos mejorar." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Si - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Saltar y %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Haz click aquí para utilizar el plugin de forma anónima" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Es posible que te lo hayas perdido, pero no tienes que compartir ningún dato y puedes solo aceptar %s." diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo new file mode 100644 index 0000000000000000000000000000000000000000..7c9b950264c0eee0c99ed78ec616efb7fa0a6394 GIT binary patch literal 57448 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-fr_FR.po b/freemius/languages/freemius-fr_FR.po new file mode 100644 index 0000000..697bab1 --- /dev/null +++ b/freemius/languages/freemius-fr_FR.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Boris Colombier , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Boris Colombier \n" +"Language: fr_FR\n" +"Language-Team: French (France) (http://www.transifex.com/freemius/wordpress-sdk/language/fr_FR/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Le SDK Freemius ne trouve pas le fichier principal du plugin. Merci de contacter sdk@freemius.com en indiquant l'erreur." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Erreur" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "J'ai trouvé un meilleur %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Quel est le nom du %s ?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "C'est une %s temporaire. Je corrige un problème." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Désactivation" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Changement de Thème" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Autre" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Je n'ai plus besoin du %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Je n'ai besoin de %s que pour une courte période" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "Le %s a cassé mon site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "Le %s a soudainement arrêté de fonctionner" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Je ne peux plus payer pour ça" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Quel prix seriez-vous prêt à payer ?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Je ne veux pas partager mes informations avec vous" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "Le %s n'a pas fonctionné" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Je ne comprends pas comment le faire fonctionner" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "Le %s est bien mais j'ai besoin de fonctionnalités spécifiques que vous ne proposez pas" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Quelle fonctionnalité ?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "Le %s ne fonctionne pas" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Merci de nous indiquer ce qui ne fonctionne pas afin que nous puissions le corriger pour les futurs utilisateurs..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Ce n'est pas ce que je recherche" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Que recherchez-vous ?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "Le %s n'a pas fonctionné comme prévu" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "À quoi vous attendiez-vous ?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Débuggage Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Je ne sais pas ce qu'est cURL ou comment l'installer, aidez moi !" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Nous allons contacter votre hébergeur afin de résoudre le problème. Vous recevrez un email à propos de %s dès que nous aurons des nouvelles." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Parfait, merci d'installer cURL et de l'activer dans votre fichier php.ini. De plus, recherchez l'instruction 'disable_functions' de votre fichier php.ini et désactivez les commandes commençant par 'curl_'. Pour vérifier la bonne activation, utilisez la fonction 'phpinfo()'. Une fois activé, désactivez le %s et réactivez le à nouveau." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Oui - allez-y" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Non - désactivation seulement" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oups" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Merci de nous permettre de corriger ça. Un message vient d'être envoyé à notre service technique. Nous reviendrons vers vous dès que nous aurons des nouvelles à propos de %s." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ne peut pas fonctionner sans %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s ne peut pas fonctionner sans le plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Une erreur est survenue dans l'API. Merci de contacter l'auteur du %s en lui indiquant l'erreur." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La version premium de %s a été activée avec succès." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Génial" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Vous avez une license pour %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Youpi" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "La période d'essai du %s a bien été annulé. L'add-on a été désactivé car il ne fonctionne qu'avec la version premium. Si vous souhaitez l'utiliser ultérieurement, vous devrez acheter une licence." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%sest un add-on pour la version premium. Vous devez acheter une licence avant d'activer le plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Plus d'informations à propos de %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Acheter une licence" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Vous devriez recevoir un email d'activation pour %s sur votre boîte %s. Merci de cliquer sur le bouton d'activation dans l'email pour %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "commencer la période d'essai" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "compléter l'installation" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Il ne reste qu'une étape - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Compléter \"%s\" Activer Maintenant" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Nous avons fait quelques modifications au %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Inscrivez-vous pour améliorer \"%s\" !" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "La mise à jour du %s s'est terminée avec succès " + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Thème" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Récupération des détails du site non valide." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "Nous ne trouvons pas votre adresse mail dans notre système, êtes-vous qu'il s'agit de la bonne adresse ?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Nous ne trouvons aucune licence active associée avec cette adresse email, êtes-vous qu'il s'agit de la bonne adresse ?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Compte en cours d'activation." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Acheter une licence maintenant" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renouvelez votre licence maintenant" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s pour permettre les mises à jour de sécurité et de fonctionnalités de la version %s, et le support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "L'activation de %s s'est terminée avec succès." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Votre compte a été activé avec succès avec la formule %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Votre période d'essai a bien démarré." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Impossible d'activer %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Merci de nous contacter avec le message suivant :" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Mise à jour" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Essai gratuit" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Tarifs" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Compte" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contactez Nous" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Tarifs" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Forum de Support" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Votre email a été vérifié avec succès - vous êtes FORMIDABLE !" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Directement" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Votre Add-on %s a bien été mis à jour." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "L'Add-on %s a bien été acheté." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Télécharger la dernière version" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Une erreur a été reçu depuis le serveur :" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Il semble que l'un des paramètres d'authentification soit faux. Veuillez mettre à jour votre Public Key, votre Secret Key ainsi que vote User ID et essayez à nouveau." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Il semble que vous soyez encore sur la formule %s. Si vous avez mis à jour ou changer votre formule, le problème est probablement de votre côté - désolé." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Période d'essai" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "J'ai mis à jour mon compte mais quand j'essaie de synchroniser la licence, la formule est toujours %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Merci de nous contacter ici" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Votre formule a bien été mise à jour." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Votre formule a bien été modifié vers %s. " + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Votre licence a expiré. Vous pouvez toujours utiliser la version gratuite indéfiniment." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Votre licence a expiré.%1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Votre licence a été annulé. Si vous pensez qu'il s'agit d'une erreur, merci de contacter le support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Votre licence a expiré. Vous pouvez toujours utiliser les fonctionnalités %s mais vous devrez renouveler votre licence pour recevoir les mises à jour et une assistance." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Votre période d'essai gratuite est terminée. Vous pouvez continuer à utiliser toutes nos fonctionnalités gratuites." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Votre période d'essai gratuite est terminée. %1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Il semble que la licence ne puisse être activée." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Votre licence a bien été activée." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Il semble que votre site n'ait pas de licence active." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Il semble que la désactivation de la licence a échoué." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Votre licence a bien été désactivé, vous utilisez à présent la formule %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Il semble que nous ayons un problème temporaire avec l'annulation de votre abonnement. Merci de réessayer dans quelques minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Votre abonnement a bien été annulé. Votre licence de la formule %s expirera dans %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Vous utilisez déjà le %s en période d'essai. " + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Vous avez déjà utilisé la période d'essai." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "La formule %s n'existe pas, il n'est pas possible de commencer une période d'essai." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "La formule %s ne propose pas de période d'essai." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Aucune formule du %s ne propose de période d'essai." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Il semble que vous ne soyez plus en période d'essai donc il n'y a rien à annuler :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Il semble que nous ayons un problème temporaire pour annuler votre période d'essai. Merci de réessayer dans quelques minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Votre période d'essai %s a bien été annulé." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La version %s vient d'être publiée." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Merci de télécharger %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "la dernière version de %s ici" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nouveau" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Il semble que vous ayez la dernière version." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Vous êtes tout bon !" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Un email de vérification vient d'être envoyé sur %s. Si vous ne le recevez pas d'ici 5 minutes, merci de vérifier dans vos spams." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site ajouté avec succès." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Formidable" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Nous vous remercions de votre aide pour améliorer le %s en nous permettant de recevoir des informations concernant son usage." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Merci !" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Nous n'enverrons plus d'information d'utilisation de %s sur %s à %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Merci de vérifier votre messagerie, vous devriez recevoir un email via %s pour confirmer le changement de propriétaire. Pour des raisons de sécurité, vous devez confirmer le changement dans les prochaines 15 minutes. Vérifiez vos spams si vous ne recevez pas le message." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Merci pour la confirmation du changement de propriétaire. Un email vient d'être envoyé à %s pour la validation finale." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s est le nouveau propriétaire du compte." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Félicitations" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Désolé, nous ne pouvons pas mettre à jour l'email. Il existe déjà un autre utilisateur avec cette adresse." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Si vous voulez transférer la propriété du compte de %s à %s cliquez sur le bouton Changement De Propriétaire" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Changement De Propriétaire" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Votre email a été mis à jour. Vous allez recevoir un message avec les instructions de confirmation." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Merci d'indiquer vos prénom et nom." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Votre nom a été mis à jour." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Votre %s a bien été mis à jour." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Sachez que les informations de l'add-ons de %s sont issus d'un serveur externe." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Avertissement" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Que pensez-vous de %s ? Testez nos %s fonctionnalités premium avec %d jours d'essai gratuit." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Pas d'engagement durant %s jours - annuler quand vous voulez !" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Pas besoin de carte bancaire" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Commencer l'essai gratuit" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Dites, savez-vous que %s propose un système de affiliation ? Si vous aimez le %s vous pouvez devenir notre ambassadeur et gagner de l'argent !" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "En savoir plus" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activer la licence" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Changer la licence" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Désinscription" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Inscription" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activer les fonctionnalités %s" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Merci de suivre ces étapes pour finaliser la mise à jour" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Télécharger la dernière version %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Téléverser et activer la version téléchargée" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Comment téléverser et activer ?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sCliquez ici %s pour choisir les sites sur lesquels vous souhaitez activer la licence." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "L'installation automatique ne fonctionne que pour les utilisateurs qui se sont inscrits." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "ID du module non valide." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Version premium déjà active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Vous n'avez pas de licence valide pour accéder à la version premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Le plugin est un \"Serviceware\" ce qui veut dire qu'il n'a pas de version premium de code." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "La version premium de l'add-on est déjà installée." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Voir les fonctionnalités payantes" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Merci beaucoup d'utiliser %s et ses add-ons !" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Merci beaucoup d'utiliser %s !" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à améliorer le %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Merci beaucoup d'utiliser nos produits !" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à les améliorer." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s et ses add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Produits" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Oui" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "envoyez moi des mises à jour de sécurité et des fonctionnalités, du contenu instructif et des offres." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Non" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "ne %sPAS%s m'envoyer de mises à jour de sécurité ou de fonctionnalités, ni de contenu instructif, ni d'offre." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Merci de nous indiquer si vous souhaitez que nous vous contactions pour les mises à jour de sécurité et de fonctionnalités, du contenu instructif et des offres spéciales :" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La clé de licence est vide." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renouvelez votre licence" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Acheter une licence" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Il y a une %s de %s disponible." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "Nouvelle version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Information importante de mise à jour :" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installation du plugin : %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Impossible de se connecter au système de fichiers. Merci de confirmer vos autorisations." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Le package du plugin à télécharger ne contient pas de dossier avec le bon slug et iln'a pas été possible de le renommer." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Acheter" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Commencer ma %s gratuite" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installer la dernière mise à jour gratuite maintenant" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installer la mise à jour maintenant" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer la version gratuite maintenant" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installer maintenant" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Télécharger la dernière version gratuite" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Télécharger la dernière version" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activer cet add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activez la version gratuite" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activer" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Description" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installation" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Captures d'écran" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Commentaires" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Autres Informations" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Fonctionnalités & Tarifs" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Installation du Plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Formule %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Best" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensuel" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annuel" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "À vie" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s Facturé" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annuel" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Une fois" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licence 1 site" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licences sites illimités" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Jusqu'à %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mois" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "année" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Tarif" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Économisez %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Pas d'engagement durant %s - annuler quand vous voulez" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Après vos %s gratuits, payez seulement %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Détails" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Auteur" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Dernière mise à jour" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "Il y a %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Version de WordPress requise" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s ou plus" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible jusqu'à" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Téléchargé" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s fois" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s fois" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Page WordPress.org du plugin" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Site Web du plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Faire une donation pour ce plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Note moyenne" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "Basé sur %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s notation" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%snotations " + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s étoile" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s étoiles" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Cliquez pour voir les avis avec une notation de %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributeurs" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Attention" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Ce plugin n'a pas été testé avec votre actuelle version de WordPress" + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Ce plugin n'a pas été indiqué comme étant compatible avec votre version actuelle de WordPress" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Les add-ons payant doivent être déposés sur Freemius" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Les add-ons doivent être déposés sur WordPress.org ou Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nouvelle Version (%s) Installée" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "La nouvelle version gratuite ( %s ) a été installé" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Dernière Version Installée" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "La dernière version gratuite a été installé" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Rétrograder votre formule" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Annuler votre abonnement" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Veuillez noter que nous ne serons pas en mesure de garantir le maintien des prix actuels pour les renouvellements/nouveaux abonnements après une annulation. Si vous choisissez de renouveler l'abonnement manuellement à l'avenir, après une augmentation de prix, qui se produit généralement une fois par an, le prix mis à jour vous sera facturé." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Annuler la période d'essai va immédiatement bloquer les fonctionnalités premium. Souhaitez-vous continuer ?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Vous pouvez toujours profiter de toutes les fonctionnalités de %s mais vous n'aurez plus accès aux mises à jour de sécurité ou de fonctionnalités de %s, ni au support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Une fois la licence expirée vous pourrez toujours utiliser la version gratuite mais vous n'aurez PAS accès aux fonctionnalités de %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activer la formule %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Renouvellements automatique dans %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Expire dans %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Synchroniser la licence" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annuler la période d'essai" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Changer de formule" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Mise à jour" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Rétrograder" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratuit" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Formule" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Essai gratuit" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Détails du compte" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Supprimer le compte désactivera automatiquement la licence de votre formule %s afin que vous puissiez l'utiliser sur d'autres sites. Si vous voulez aussi annuler le paiement récurrent, cliquez sur le bouton \"Annuler\" et commencez par \"Rétrograder\" votre compte. Êtes-vous sûr de vouloir poursuivre la suppression ? " + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La suppression est permanente. Ne faites cette suppression que si vous ne souhaitez plus utiliser le %s. Êtes-vous sûr de vouloir poursuivre la suppression ?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Supprimer le compte" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Désactiver la licence" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Êtes-vous de vouloir continuer ?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annuler l'abonnement" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Synchroniser" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nom" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "ID manquant" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Clef publique" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Clef secrête" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Clef secrète manquante" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Période d'essai" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Clef de licence" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "Non vérifié" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Expiré" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Version premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Version gratuite" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Vérifier l'email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Télécharger la version %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Afficher" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Quel est votre %s ?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Éditer" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Recherche par adresse" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adresse" + +#: templates/account.php:610 +msgid "License" +msgstr "Licence" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Formule" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licence" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Cacher" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Traitement en cours" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Annulation de %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "essai" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Annulation de %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "abonnement" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Désactiver la licence bloquera toutes les fonctionnalités premium mais vous permettra d'activer la licence sur un autre site. Êtes-vous sûr de vouloir continuer ?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Voir les détails" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons pour %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Nous n'avons pas pu charger la liste des add-ons. C'est probablement une difficulté de notre côté, merci de d'essayer à nouveau dans quelques minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Fermer" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Installation automatique" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Un téléchargement et une installation automatique de %s (version premium) de %s va commencer dans %s. Si vous voulez le faire manuellement, cliquez sur le bouton d'annulation maintenant." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "L'installation a commencé et peut prendre quelques minutes pour se finir. Merci de patienter jusqu'à ce qu'elle soit terminée - veuillez ne pas rafraichir cette page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuler l'installation" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Paiement" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Compatible PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Autoriser & Continuer" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Renvoyer l'email d'activation" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Merci %s !" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Valider & Activer la licence" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Merci d'avoir acheté %s ! Pour commencer, veuillez indiquer votre clef de licence :" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités, de contenu instructif, d'offres ainsi que le suivi d'activité non sensible avec %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ne manquez jamais une mise à jour importante - optez pour nos notifications de mises à jour de sécurité et de fonctionnalités, et un suivi diagnostique non sensible avec %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités, de contenu instructif, d'offres ainsi que le suivi d'activité non sensible avec %4$s. Dans le cas contraire, pas de problème ! %1$s fonctionnera parfaitement aussi." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités ainsi que le suivi d'activité non sensible avec %4$s. Dans le cas contraire, pas de problème ! %1$s fonctionnera parfaitement aussi." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Nous sommes impatient de vous présenter l'intégration Freemius au niveau réseau." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durant le processus de mise à jour nous avons détecté %d site(s) toujours en attente d'activation de la licence." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Si vous voulez utiliser le %s sur ces sites, merci d'indiquer votre clé de licence ci-dessous et de cliquer sur le bouton d'activation." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "Fonctionnalités payantes de %s" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Éventuellement, vous pouvez l'ignorer pour l'instant et activer la licence plus tard, sur votre page de compte du réseau %s." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durant le processus de mise à jour nous avons détecté %s site(s) dans le réseau que vous devez vérifier." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Clef de licence" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Vous ne trouvez pas votre clef de licence ?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Passer" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Déléguer aux administrateurs du site" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Si vous cliquez, cette décision sera déléguée aux administrateurs des sites." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Résumé de votre profil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nom et adresse email" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Résumé de votre site" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP version, PHP info, plugins & themes" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Notifications Administrateur" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Mises à jour, annonces, marketing, pas de spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Évènements du %s actuel" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activation, désactivation et désintallation" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "Le %1$s va régulièrement envoyer des données à %2$s pour vérifier les mises à jour de sécurité et de fonctionnalités ainsi que pour vérifier la validité de votre licence." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Quelles autorisations sont accordées ?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Vous n'avez pas de clef de licence ?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Vous avez une clef de licence ?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Politique de confidentialité" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Contrat de licence" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Conditions générales de service" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Email en cours d'envoi" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activation en cours" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debuggage" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Actions" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Êtes-vous sûr de vouloir supprimer toutes les données de Freemius ?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Supprimer tous les comptes" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Vider le cache API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Vider les transients de mise à jour" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synchronisation des données depuis le serveur" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrer les options vers le réseau" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Chargement des options de la base de données" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Mise en place des options de la base de données" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Clef" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valeur" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versions du SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Chemin d'accès du SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Chemin d'accès du module" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Est actif" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Thèmes" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titre" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "État de Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Réseau de Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Réseau d'Utilisateur" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connecté" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloqué" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simuler la promotion d'essai" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuler la mise à jour du réseau" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installations" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Supprimer" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons du module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Utilisateurs" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Vérifié" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licences" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID du plugin" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID de la formule" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activé" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloquant" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Tous les types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Toutes les demandes" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Fichier" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Fonction" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID du processus" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Téléchargement" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Page %s sécurisée HTTPS, s'exécutant sur un domaine externe" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "API Freemius" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Demandes" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Mise à jour" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturation" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Raison sociale" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Code TVA" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresse ligne %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Ville" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Ville" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Code postal" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Pays" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Choisir le pays" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "État" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Région" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Paiements" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Date" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Montant" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Facture" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Méthode" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Longueur" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Chemin" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Résultat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Début" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fin" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Dans %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "Il y a %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synchronisation des plugin et des thèmes" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Dernier" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Crons programmés" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Type de module" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Type de Cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Suivant" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Sans expiration" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Postuler pour devenir un affilié" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Votre dossier d'affiliation pour %s a été accepté ! Identifiez-vous dans votre espace affilié sur : %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Merci d'avoir postulé à notre programme d'affiliation, nous regarderons votre dossier durant les 14 prochains jours et nous reviendrons vers vous avec d'autres informations." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Votre compte affilié a été suspendu temporairement." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Merci d'avoir postulé à notre programme d'affiliation, malheureusement, nous avons décidé pour le moment de décliner votre dossier. Merci d'essayer à nouveau d'ici 30 jours." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Suite à une violation de nos conditions d'affiliation, nous avons décidé de bloquer temporairement votre compte d'affilié. Si vous avez la moindre question, merci de contacter le support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Vous aimez %s ? Devenez notre ambassadeur et gagnez du cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Parrainez des nouveaux clients pour notre %s et gagnez une commission de %s sur chaque vente réussie que vous affiliez." + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sommaire du programme" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "Commission de %s quand un client achète une nouvelle licence." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Obtenez des commissions pour les renouvellements automatiques d'abonnement." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "Cookie de tracking de %s après la première visite pour maximiser les potentiels de gain." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Commissions illimitées." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "Montant de paiement minimum %s." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Les paiements se font en Dollars US et sont effectués mensuellement via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Comme nous bloquons sur 30 jours pour les remboursements éventuels, seules sont payées les commissions de plus de 30 jours." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliation" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Adresse email" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nom complet" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Adresse email du compte PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Où allez-vous faire la promotion du %s ? " + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Indiquez l'adresse de votre site ou d'autres sites sur lesquels vous pensez faire la promotion du %s" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Ajouter une autre adresse" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Adresses supplémentaires" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Adresses supplémentaires depuis lesquelles vous ferez la promotion du produit." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Méthodes de promotion" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Réseaux sociaux (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Applications mobiles" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Statistiques du site web, de l'adresse email et des réseaux sociaux (optionnel)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "N'hésitez pas à indiquer des statistiques pertinentes concernant votre site ou vos réseaux sociaux telles que le nombre de visiteurs mensuel, le nombre d'abonnés, de followers, etc... (C'est informations resteront confidentielles)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Comment allez-vous faire de la promotion ?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Merci d'indiquer en détail comment vous allez faire la promotion du %s (en étant aussi précis que possible)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuler" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Devenir un affilié" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Merci d'indiquer le code de licence que vous avez reçu par email juste après l'achat :" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Mettre à jour la licence" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Désinscription" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Inscription" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Le suivi d'utilisation de %s nous permet de l'améliorer. Apporter une meilleure expérience pour l'utilisateur, définir quelles seront les nouvelles fonctionnalités, ce genre de choses. Aussi nous vous serions reconnaissant si vous acceptiez de nous permettre de continuer à récupérer des informations." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "En cliquant \"Désincription\", nous n'enverrons plus d'informations de %s à %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Il y a une nouvelle version disponible de %s. " + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%s pour accéder aux mises à jour de sécurité et de fonctionnalités de la version %s, et au support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Une nouvelle version est disponible" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Fermer" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Envoyer le code de la licence" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Indiquez ci-dessous l'adresse email que vous avez utilisez pour la mise à jour et nous allons vous renvoyer le code de la licence." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Désactiver ou désinstaller le %s désactivera automatiquement la licence, que vous pourrez utiliser sur un autre site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "Dans le cas où vous n'avez PAS l'intention d'utiliser ce %s sur ce site (ou tout autre site) - voulez-vous aussi annuler le %s ?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licence" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Annuler %s - Je n'ai plus besoin de mises à jour de sécurité et de fonctionnalités, ni de support pour %s parce que je n'ai pas l'intention d'utiliser le %s sur ce site, ou tout autre site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Ne pas annuler %s - Je veux toujours recevoir les mises à jour de sécurité et de fonctionnalités, ainsi que d'être en mesure de contacter le support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Une fois votre licence expirée, vous ne pourrez plus utiliser le %s, sauf si vous l'activez à nouveau avec une licence premium valide." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Annuler %s ?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Poursuivre" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Annuler %s et poursuivre" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Vous êtes à 1 clic de commencer votre période d'essai gratuite de %1$s jours de la formule %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Pour être en accord avec les directives de WordPress.org, avant que nous commencions la période d'essai, nous vous demandons de nous permettre de récupérer votre nom d'utilisateur et des informations non sensibles du site afin de permettre au %s de communiquer avec %s pour vérifier les mises à jour et valider votre période d'essai." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activer la licence sur tous les sites du réseau." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Effectuer sur tous les sites dans le réseau." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activer la licence sur tous les sites en attente." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Activer sur tous les sites en attente." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "autoriser" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "déléguer" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "passer" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Cliquez pour voir la capture d'écran %d en pleine taille" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Mises à jour illimitées" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s restante(s)" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Dernière licence" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annulé" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Pas d'expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nom du propriétaire" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Email du propriétaire" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID du propriétaire" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Inscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Désolé pour le dérangement et nous sommes là pour vous aider si vous nous le permettez." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contacter l'Assistance" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Commentaire anonyme" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Désactiver" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activer %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Commentaires rapides" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Si vous avez un instant, merci de nous indiquer pourquoi %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "Désactivation" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "Changement" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Envoyer & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "S'il vous plait, dites nous pourquoi afin que nous puissions nous améliorer." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Oui - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Passer & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Cliquer ici pour utiliser le plugin anonymement" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Peut-être que cela vous a échappé mais vous n'êtes pas obligé de partager la moindre information et vous pouvez juste %s l'enregistrement." diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo new file mode 100644 index 0000000000000000000000000000000000000000..7e3b6ef13b3ccc7bc6b0a157871877fd87643d05 GIT binary patch literal 56093 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 XBC7g&w_0VJEFb= literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-he_IL.po b/freemius/languages/freemius-he_IL.po new file mode 100644 index 0000000..ea2e7e0 --- /dev/null +++ b/freemius/languages/freemius-he_IL.po @@ -0,0 +1,2509 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Rami Yushuvaev , 2017 +# Vova Feldman , 2017-2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: he_IL\n" +"Language-Team: Hebrew (Israel) (http://www.transifex.com/freemius/wordpress-sdk/language/he_IL/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "שגי××”" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "מצ×תי %s יותר טוב" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "What's the %s's name?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "It's a temporary %s. I'm just debugging an issue." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "די×קטיבציה" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "החלפת תֵמָה" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "×חר" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "×”%s הרס לי ×ת ×”×תר" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "×”%s הפסיק פת××•× ×œ×¢×‘×•×“" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "×× ×™ ×œ× ×™×›×•×œ/×” להמשיך ×œ×©×œ× ×¢×œ ×–×”" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "מה המחיר שכן תרגיש\\×™ בנוח לשל×?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "×× ×™ ×œ× ×והב ×ת הרעיון של שיתוף מידע ×יתכ×" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "×”%s ×œ× ×¢×‘×“" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "×œ× ×”×¦×œ×—×ª×™ להבין ×יך ×œ×’×¨×•× ×œ×–×” לעבוד" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "××™×–×” פיטצ'ר?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "×”%s ×œ× ×¢×•×‘×“" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "×× × ×©×ª×¤\\×™ מה ×œ× ×¢×‘×“ כדי שנוכל לתקן ×–×ת עבור ×ž×©×ª×ž×©×™× ×¢×ª×™×“×™×™×..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "חיפשתי משהו ×חר" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "מה חיפשת?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "×”%s ×œ× ×¢×‘×“ כמצופה" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "למה ציפית?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "ניפוי תקלות פרימיוס" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "×ין לי מושג מה ×–×” cURL ×ו ×יך להתקין ×ותו - ×שמח לעזרה!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "כן - בצעו ×ת מה שצריך" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "×œ× - פשוט כבה" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "×ופס" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ×œ× ×™×›×•×œ לעבוד ×œ×œ× %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "ההרחבה %s ××™× ×” יכולה לפעול ×œ×œ× ×”×ª×•×¡×£." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "יש" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "יש לך רישיון %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "יששש" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "מידע נוסף ×ודות %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "קניית רישיון" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "התחל תקופת ניסיון" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "×”×©×œ× ×”×ª×§× ×”" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "You are just one step away - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "×”×©×œ× ×”×¤×¢×œ×ª \"%s\" עכשיו" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "תוסף" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "תבנית" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account is pending activation." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "הפעלת %s הושלמה בהצלחה." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "חשבונך הופעל בהצלחה ×¢× ×—×‘×™×œ×ª %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "הניסיון שלך הופעל בהצלחה." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "×œ× × ×™×ª×Ÿ להפעיל ×ת %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "×× × ×¦×•×¨ ×יתנו קשר יחד ×¢× ×”×”×•×“×¢×” הב××”:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "שדרג" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "התחל תקופת ניסיון" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "מחירון" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "×פילי×ציה" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "חשבון" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "יצירת קשר" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "מחירון" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "×¤×•×¨×•× ×ª×ž×™×›×”" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Your email has been successfully verified - you are AWESOME!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "מעולה" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "חבילת ההרחבה %s שודרגה בהצלחה." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "ההרחבה %s נרכשה בהצלחה." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "הורד ×ת הגרסה ×”×חרונה" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "הוחזרה שגי××” מהשרת:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "×ממ" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "ניסיון" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "שידרגתי ×ת החשבון שלי ×בל כש×× ×™ מנסה לבצע סנכרון לרישיון החבילה נש×רת %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "×× × ×¦×•×¨ ×יתנו קשר ×›×ן" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "החבילה שודרגה בהצלחה." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "החבילה עודכנה בהצלחה ×ל %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "רשיונך בוטל. ×× ×œ×“×¢×ª×š זו טעות, × × ×œ×™×¦×•×¨ קשר ×¢× ×”×ª×ž×™×›×”." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "תקופת הניסיון שלך הסתיימה. הפיטצ'×¨×™× ×”×—×™× ××ž×™×™× ×¢×“×™×™×Ÿ × ×™×ª× ×™× ×œ×©×™×ž×•×©." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "נר××” ×©×œ× × ×™×ª×Ÿ להפעיל ×ת הרישיון." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "הרישיון הופעל בהצלחה." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "נר××” ל×תר עדיין ×ין רישיון פעיל." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "נר××” שניתוק הרישיון נכשל." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "רישיונך נותק בהצלחה, חזרת לחבילת %s" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "×וקיי" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "הניסיון כבר נוצל בעבר." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "החבילה %s ××™× ×” קיימת, לכן, ×œ× × ×™×ª×Ÿ להתחיל תקופת ניסיון." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "תוכנית %s ××™× ×” תומכת בתקופת ניסיון." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "נר××” שיש תקלה זמנית המונעת ×ת ביטול הניסיון. ×× × × ×¡×• שוב בעוד כמה דקות." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "תקופת הניסיון החינמית של %s בוטלה בהצלחה." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "גרסה %s הושקה." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "× × ×œ×”×•×¨×™×“ ×ת %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "גרסת ×”-%s ×”×חרונה ×›×ן" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "חדש" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "נר××” שיש לך ×ת הגרסה ×”×חרונה." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "×ת\\×” מסודר!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "×דיר" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "תודה רבה!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "תודה על ×ישור ביצוע החלפת הבעלות. הרגע נשלח מייל ל-%s כדי לקבל ×ישור סופי." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s הינו ×”×‘×¢×œ×™× ×”×—×“ של חשבון ×–×”." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "מזל טוב" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "עדכון בעלות" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "כתובת הדו×ל שלך עודכנה בהצלחה. הודעת ×ישור ×מורה להתקבל בדו×ל שלך ×‘×¨×’×¢×™× ×”×§×¨×•×‘×™×." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "× × ×œ×ž×œ× ×ת שמך המל×." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "שמך עודכן בהצלחה." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "עידכנת בהצלחה ×ת ×”%s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "לתשמות לבך" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "×”×™×™" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "×œ×œ× ×”×ª×—×™×™×‘×•×ª ל-%s ימין - בטלו בכל רגע!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "×œ× × ×“×¨×© כרטיס ×שר××™" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "התחלת ניסיון ×—×™× ×" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Learn more" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "הפעלת רישיון" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "שינוי רישיון" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "× × ×œ×‘×¦×¢ ×ת ×”×¦×¢×“×™× ×”×‘××™× ×œ×”×©×œ×ž×ª השידרוג" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "הורד\\×™ ×ת גרסת ×”-%s העדכנית" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "העלה\\×™ והפעיל\\×™ ×ת הגרסה שהורדת" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "×יך להעלות ולהפעיל?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "מזהה המודול ×œ× ×ª×§× ×™." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "הגרסה ×‘×ª×©×œ×•× ×›×‘×¨ פעילה." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "×ין ברשותך רישיון בר תוקף לשימוש בגרסת הפרימיו×." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "צפה בפיטצ'×¨×™× ×©×‘×ª×©×œ×•×" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "×נו ×ž×•×“×™× ×œ×š על היותך כמשתמש של %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "×נו ×ž×•×“×™× ×œ×š על השימוש ×‘×ž×•×¦×¨×™× ×©×œ× ×•!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "מוצרי×" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "כן" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "תשלחו לי עדכוני ×בטחה ופיטצ'רי×, תוכן חינוכי, ומידע ×ודות מבצעי×." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "ל×" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%s×ל%2$s תשלחו לי עדכוני ×בטחה, פיטצ'רי×, תוכן חינוכי, ומידע על מבצעי×." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "מפתח הרישיון ריק." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "חידוש רישיון" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installing plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "רכישה" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "התחל ×ת %s הניסיון שלי" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "התקן עדכון גרסה ×—×™× ×מית עכשיו" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "התקן עדכון במיידי" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "התקן גרסה ×—×™× ×מית עכשיו" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "התקן עכשיו" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "הורד גרסה ×חרונה" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "הפעל ×ת ההרחבה" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "הפעלת גירסה ×—×™× ×מית" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "הפעלה" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "תי×ור" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "התקנה" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "ש×לות נפוצות" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "צילומי מסך" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "לוג שינויי×" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "ביקורות" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "היערות נוספות" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "פיטצ'×¨×™× ×•×ž×—×™×¨×™×" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "התקנת תוסף" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "חבילה %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "×”×›×™ טוב" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "חודשי" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "שנתי" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "לכל ×”×—×™×™×" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "מחוייב על בסיס %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "שנתי" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "×¤×¢× ×חת" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "רשיון ל×תר ×חד" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "רשיונות ×œ×œ× ×”×’×‘×œ×”" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "עד %s ×תרי×" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "חודשי×" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "שנה" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "מחיר" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "שמירת %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "פרטי×" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "גרסה" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Author" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "עודכן ל×חרונה" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "לפני %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Requires WordPress Version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s ומעלה" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloaded" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "×¤×¢× %s" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s פעמי×" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin Page" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "עמוד התוסף" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "×ª×¨×•× ×œ×ª×•×¡×£" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "דירוג ממוצע" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "מבוסס על %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "דרוג %s" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s דרוגי×" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "כוכב %s" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s כוכבי×" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "תורמי×" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Warning" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "תוסף ×–×” ×œ× × ×‘×“×§ ×¢× ×’×¨×¡×ª הוורדפרס שלך." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "התוסף ×œ× ×¡×•×ž×Ÿ כתו×× ×œ×’×¨×¡×ª הוורדפרס שלך." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "גרסה חדשה (%s) הותקנה" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "הגרסה ×”×חרונה הותקנה" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "גרסה ×—×™× ×מית עדכנית הותקנה" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "ביטול הניסיון ×™×—×¡×•× ×ž×™×™×“ ×ת הפיטצ'×¨×™× ×©×”×™× × ×‘×ª×©×œ×•×. ×”×× ×‘×¨×¦×•× ×š בכל ×–×ת להמשיך?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "הפעל חבילה %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "עדכן ×וטומטית בעוד %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "פג תוקף בעוד %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "סינכרן רישיון" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "ביט" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "שינוי חבילה" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "שדרג" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "שנמך" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "×—×™× ×" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "חבילה" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "ניסיון ×—×™× ×" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "פרטי חשבון" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "מחיקת חשבון" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "שיחרור רישיון" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "×”×× ×ת/×” בטוח רוצה להמשיך?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "בטל מנוי" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "סינכרון" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "ש×" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "דו×\"ל" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "מזהה משתמש" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "מזהה" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "מזהה ×תר" + +#: templates/account.php:332 +msgid "No ID" +msgstr "×ין מזהה" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "מפתח פומבי" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "מפתח סודי" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "×ין מפתח סודי" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "ניסיון" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "License Key" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "×œ× ×ž×ומת" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "פג תוקף" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "גירסת פרימיו×" + +#: templates/account.php:504 +msgid "Free version" +msgstr "גירסה ×—×™× ×מית" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "×מת כתובת דו×\"ל" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "הורד גרסת %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "הצג" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "מה ×”%s שלך?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "ערוך" + +#: templates/account.php:588 +msgid "Sites" +msgstr "×תרי×" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "חפש לפי כתובת" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "כתובת" + +#: templates/account.php:610 +msgid "License" +msgstr "רישיון" + +#: templates/account.php:611 +msgid "Plan" +msgstr "חבילה" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "רישיון" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "הסתר" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "ביטול הרישיון ×™×—×¡×•× ×ת כל הפיטצ'×¨×™× ×©×‘×ª×©×œ×•× ×ך ×™×פשר להפעיל ×ת הרישיון על ×תר ×חר. ×”×× ×ª×¨×¦×• להמשיך בכל ×–×ת?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "×¤×¨×˜×™× × ×•×¡×¤×™×" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "הרחבות עבור %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "סגירה" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s שניות" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "התקנה ×וטומטית" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "הורדה והתקנה ×וטומטית של %s (גרסה בתשלו×) מ-%2$s תתחיל בעוד %3$s. ×× ×‘×¨×¦×•× ×š לבצע ×ת ההתקנה ידנית - לחץ על כפתור הביטול עכשיו." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "תהליך ההתקנה התחיל ויכול לקחת מספר דקות לסיו×. ×× × ×”×ž×ª×™× ×• בסבלנות עד ×œ×¡×™×•× ×ž×‘×œ×™ לרענן ×ת הדפדפן." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "בטל התקנה" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Checkout" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "עומד בתקן PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "×”×™×™ %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "×פשר\\×™ והמשכ\\×™" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "שליחה חוזרת של מייל ×”×קטיבציה" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "תודה %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "הסכמה והפעלת רישיון" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Thanks for purchasing %s! To get started, please enter your license key:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "מפתח רישיון" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "×”×× ×ינך ×ž×•×¦× ×ת מפתח הרישיון?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "דלג" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "×”×צלה למנהלי ×”×תרי×" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "×¤×¨×˜×™× ×›×œ×œ×™×™× ×¢×œ הפרופיל" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "×©× ×•×›×ª×•×‘×ª דו\"×ל" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "×¤×¨×˜×™× ×›×œ×œ×™×™× ×¢×œ ×”×תר" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "כתובת ×תר, גרסת וורדפרס, פרטי PHP, ×ª×•×¡×¤×™× ×•×ª×‘× ×™×•×ª" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "התר×ות מנהל" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "עדכוני×, הכרזות, הודעות שיווקיות, ×œ×œ× ×“×•×ר זבל" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Current %s Events" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "הפעלה, כיבוי והסרה" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "ניוסלטר" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "מהן ההרש×ות המוענקות?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "×”×× ×ין ברשותך מפתח רישיון?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "×”×× ×‘×¨×©×•×ª×š רישיון?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "מדיניות פרטיות" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "תנ××™ השירות" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "שולח דו×\"ל" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "מפעיל" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "כבוי" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "דלוק" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "דיבוג" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "פעולות" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "מחיקת כל החשבונות" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "ניקוי מטמון ×”-API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "סנכרון מידע מהשרת" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Key" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Value" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "גרס×ות SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "×ž×™×§×•× SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "×”×× ×¤×¢×™×œ" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "תוספי×" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "תבניות" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "מזהה כתובת" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "כותרת" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "מצב פרימיוס" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "משתמש רשת" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "מחובר" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "חסו×" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "סמלוץ עדכון לרשת" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s התקנות" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "×תרי×" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "מזהה בלוג" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "מחק" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "משתמשי×" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "מ×ומת" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activated" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "תפוגה" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "כל הסוגי×" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "כל הבקשות" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "קובץ" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "פונקציה" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Process ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "הודעה" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "פילטר" + +#: templates/debug.php:587 +msgid "Download" +msgstr "הורדה" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "סוג" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "תמיכה" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "עדכן" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "בילינג" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "×©× ×¢×¡×§" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "×—.פ." + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "כתובת %s" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "עיר" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "כפר" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "מיקוד / ×ª× ×“×•×ר" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "מדינה" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "בחר מדינה" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "מחוז/מדינה" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "פרובינציה" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "תשלומי×" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "ת×ריך" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "סכו×" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "חשבונית" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Length" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "נתיב" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "בעוד %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "לפני %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Plugins & Themes Sync" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "מודול" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "סוג מודול" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "כתובת דו×\"ל" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Full name" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account email address" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Add another domain" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domains" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, email, and social media statistics (optional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "בטל" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "×× × ×”×–×Ÿ ×ת הרישיון שקיבלת לתיבת הדו×ל שלך ל×חר השלמת הרכישה." + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "עדכון רישיון" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "יש גרסה חדשה עבור ×”%s." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "יש גרסה חדשה" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "סגירה" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "שליחת מפתח רישיון" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "הזן ×ת כתובת הדו×ל ש×יתה שידרגת כדי לקבל ×ת הרישיון שוב." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "הפעלת רישיון על כל ×”××ª×¨×™× ×‘×¨×©×ª." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "×™×™×©×•× ×¢×œ כל ×”××ª×¨×™× ×‘×¨×©×ª." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "הפעלת רישיון על כל ×”××ª×¨×™× ×”×ª×œ×•×™×™× ×•×”×¢×•×ž×“×™×." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "×™×™×©×•× ×¢×œ כל ×”××ª×¨×™× ×”×ª×œ×•×™×™× ×•×”×¢×•×ž×“×™×." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "×פשר" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "×”×צל" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "דלג" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "×¢×“×›×•× ×™× ×œ×œ× ×”×’×‘×œ×”" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "שרת לוק×לי" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "נש×רו %s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "רישיון ×חרון" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "בוטל" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "×œ×œ× ×ª×¤×•×’×”" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "×©× ×”×‘×¢×œ×™×" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "מייל הבעלי×" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "מזהה הבעלי×" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "מנוי" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "×ž×¦×˜×¢×¨×™× ×¢×œ חוסר הנעימות, ×נחנו ×›×ן כדי לעזור ×× ×ª×פשר\\×™ ×–×ת." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "צור קשר" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "פידבק ×נונימי" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "כיבוי" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activate %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "If you have a moment, please let us know why you are %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactivating" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "switching" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Submit & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "×× × ×©×ª×£ ×ת הסיבה כדי שנוכל להשתפר." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "דלג ו%s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Click here to use the plugin anonymously" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "×ולי פספסת ×ת ×–×” ×בל ×ינך חייב\\ת לשתף כל מידע ×יתנו, ביכולתך %s על שיתוף המידע." diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo new file mode 100644 index 0000000000000000000000000000000000000000..c8812867950b79ce6ac81cc2484bc797cb5e84a2 GIT binary patch literal 54168 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

    -Dbzm2Vd)dhH3%Uk5&c^ey1A;9a2P-e*De?~C9Q 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 F{|DK*5qSUr literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-hu_HU.po b/freemius/languages/freemius-hu_HU.po new file mode 100644 index 0000000..59d73fc --- /dev/null +++ b/freemius/languages/freemius-hu_HU.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Peter Ambrus, 2018-2019 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: hu_HU\n" +"Language-Team: Hungarian (Hungary) (http://www.transifex.com/freemius/wordpress-sdk/language/hu_HU/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Hiba" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Jobb %st találtam" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Mi a %s neve?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Ez csak egy ideiglenes %s. Egy hibát kell megoldanom." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deaktiválás" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Sablon váltás" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Egyéb" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "The %s broke my site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "The %s suddenly stopped working" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Nem tudom tovább fizetni" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Mi lenne az elfogadható ár, amit tudnál fizetni?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Nem szeretném megosztani veletek az információt" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "A %s nem működött" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Nem értettem, hogy kell használni" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Melyik funkcióra van szükséged?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "A(z) %s nem működik" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Ha elmondod mi nem működött, ki tudjuk javítani a leendÅ‘ felhasználók számára..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Nem ezt kerestem" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Pontosan mit kerestél?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "A %s nem az elvárásoknak megfelelÅ‘en működött" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Mire számítottál?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "I don't know what is cURL or how to install it, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Igen - tedd a dolgod" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nem - csak deaktiválom" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Hoppá" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s cannot run without %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s cannot run without the plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Fantasztikus" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "You have a %s license." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Juhuuu" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "More information about %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Licensz vásárlása" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Küldtünk egy aktivációs emailt a(z) %s szoftverünkhöz a következÅ‘ email címre: %s. Kérlek kattints a levélben található aktivációs linkre, hogy %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "próbaidÅ‘ indítása" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "befejezd a telepítést" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Már csak egy lépés van hátra - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "\"%s\" aktiválásának a befejezése most" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "KiegészítÅ‘" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "BÅ‘vítmény" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Sablon" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "A fiók aktiválása függÅ‘ben." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Vásárolj licenszet most" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Licensz kulcs megújítása" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activation was successfully completed." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "A fiókodat sikeresen aktiváltuk a következÅ‘ csomaggal: %s" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "A próbaidÅ‘szakodat sikeresen aktiváltuk." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Couldn't activate %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Please contact us with the following message:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "ElÅ‘fizetés frissítése" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "PróbaidÅ‘ indítása" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Ãrak" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Fiók" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Kapcsolat" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "KiegészítÅ‘k" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Ãrak" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Támogató fórum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Az email címedet sikerült ellenÅ‘rizni - ez nagyszerű!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Right on" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Your %s Add-on plan was successfully upgraded." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on was successfully purchased." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Töltsd le a legfrissebb verziót" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error received from the server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "PróbaidÅ‘" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "I have upgraded my account but when I try to Sync the License, the plan remains %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Please contact us here" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Your plan was successfully upgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Your plan was successfully changed to %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Your license has been cancelled. If you think it's a mistake, please contact support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "It looks like the license could not be activated." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Your license was successfully activated." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "It looks like your site currently doesn't have an active license." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Úgy tűnik a licensz deaktiválása nem sikerült." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "A licenszedet sikeresen deaktiváltuk, az aktuális csomagod: %s" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "Rendben" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "You already utilized a trial before." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s do not exist, therefore, can't start a trial." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s does not support a trial period." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Your %s free trial was successfully cancelled." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s was released." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Please download %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "the latest %s version here" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Új" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Seems like you got the latest release." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Minden rendben!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Nagyszerű" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Köszönjük!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Thanks for confirming the ownership change. An email was just sent to %s for final approval." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is the new owner of the account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Gratulálunk" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Tulajdonos módosítása" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Kérlek add meg a teljes neved!" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "A neved sikeresen frissítettük." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "You have successfully updated your %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Figyelem" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Üdv" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "No commitment for %s days - cancel anytime!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Bankkártya megadása nem kötelezÅ‘" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start free trial" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "BÅ‘vebben" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Licensz aktiválása" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Licensz módosítása" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Leiratkozás" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Feliratkozás" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Please follow these steps to complete the upgrade" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download the latest %s version" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload and activate the downloaded version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "How to upload and activate?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Invalid module ID." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version already active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "You do not have a valid license to access the premium version." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "FizetÅ‘s funkciók megtekintése" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Termékek" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Igen" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "kérek biztonsági és funkcionális frissítéseket, használati ismertetÅ‘ket és ajánlatokat." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nem" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "A licensz kulcs üres." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Licensz megújítása" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Licensz vásárlása" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "új verzió" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "BÅ‘vítmény telepítése: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Vásárlás" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start my free %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Frissítés telepítése most" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Telepítés most" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Latest" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activate this add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Ingyenes verzió aktiválása" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Aktiválás" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Leírás" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Telepítés" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "GYIK" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "KépernyÅ‘fotók" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Változtatások" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Vélemények" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Egyéb megjegyzések" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Funkciók & Ãrak" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "BÅ‘vítmény telepítése" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s csomag" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Legjobb" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Havi" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Éves" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Örök" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s számlázás" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Éves" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Egyszeri" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Egy weboldalas licensz" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Korlátlan licensz" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Up to %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "hó" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "év" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Ãr" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "%s mentése" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Részletek" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Verzió" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "SzerzÅ‘" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Utolsó frissítés" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s ago" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "A következÅ‘ WordPress verzió szükséges:" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s or higher" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Letöltések száma:" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org bÅ‘vítmény oldal" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "BÅ‘vítmény oldala" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "BÅ‘vítmény támogatása" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Ãtlagos értékelés" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "based on %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s rating" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s ratings" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s star" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stars" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "KözreműködÅ‘k" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Figyelmeztetés" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "This plugin has not been tested with your current version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "This plugin has not been marked as compatible with your version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Newer Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Legfrissebb verzió telepítve" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Legfrissebb ingyenes verzió telepítve" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancelling the trial will immediately block access to all premium features. Are you sure?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "%s csomag aktiválása" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renews in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "HátralévÅ‘ idÅ‘: %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Licensz szinkronizálása" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "PróbaidÅ‘ törlése" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Csomag módosítása" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Váltás nagyobb csomagra" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Váltás kisebb csomagra" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Ingyenes" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Csomag" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Ingyenes próbaidÅ‘" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Fiók információk" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Fiók törlése" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Licensz deaktiválása" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Are you sure you want to proceed?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "ElÅ‘fizetés törlése" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Szinkronizálás" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Név" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Felhasználó ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Weboldal ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Nincs ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Publikus kulcs" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Titkos kulcs" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Nincs titkos kulcs" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "PróbaidÅ‘" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licensz kulcs" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "nem ellenÅ‘rzött" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Lejárt" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Prémium verzió" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Ingyenes verzió" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Email ellenÅ‘rzése" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "%s verzió letöltése" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mutasd" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Mi a te %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Szerkesztés" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Weboldalak" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Keresés cím alapján" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Cím" + +#: templates/account.php:610 +msgid "License" +msgstr "Licensz" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Csomag" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licensz" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Elrejt" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "próbaidÅ‘" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "elÅ‘fizetés" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "A licensz deaktiválása után a prémium funkciók használata nem elérhetÅ‘, de így tudod másik weboldalon aktiválni ugyanezt a licenszt. Folytatod a deaktiválást?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Részletek megtekintése" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons for %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Mégsem" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatikus telepítés" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Telepítés törlése" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Pénztár" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Üdv %s!" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Engedélyezés és folytatás" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Aktivációs email újraküldése" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Köszönjük %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Licensz elfogadása és aktiválása" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Köszönjük, hogy megvásároltad a %s szoftverünket! A folytatáshoz most meg kell adnod a licensz kulcsot, amit a vásárlás után kaptál emailben:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licensz kulcs" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Nem találod a licensz kulcsod?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Ugrás" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Fiókod áttekintése" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Név és email cím" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Weboldalad adatainak áttekintése" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Weboldal címe, WP verzió, PHP információk, bÅ‘vítmények és sablonok" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin értesítések" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Frissítések, közlemények, marketing, de semmi SPAM!" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Aktuális %s események" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Aktiválás, deaktiválás és kikapcsolás" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Hírlevél" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "A %1$s idÅ‘közönként adatot küld a %2$s weboldalnak, hogy ellenÅ‘rizze a biztonsági és funkcionális frissítéseket, valamint ellenÅ‘rzi az érvényes licensz kulcsot." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Milyen jogosultágok lesznek engedélyezve?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Nincs még licensz kulcsod?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Van licensz kulcsod?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Adatkezelési tájékoztató" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Licensz szerzÅ‘dés" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Szolgáltatási feltételek" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Email küldése" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Aktiválás" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Kapcsolat" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Események" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Minden fiók törlése" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Clear API Cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Adatok szinkronizálása a szerverrÅ‘l" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Kulcs" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Érték" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK verziók" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK útvonal" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Aktív" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "BÅ‘vítmények" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Sablonok" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Cím" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius State" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connected" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blocked" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installs" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Weboldalak" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Törlés" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Felhasználók" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "EllenÅ‘rzött" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "BÅ‘vítmény ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Csomag ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Sikeres aktiválás" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "All Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "All Requests" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Function" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Művelet ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Frissítés" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Számlázás" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Cégnév" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Közösségi adószám" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Cím %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Város" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Town" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Irányítószám" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Ország" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Válaszz országot" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Megye" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Province" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Fizetési módok" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Dátum" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Mennyiség" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Számla" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Kód" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Hossz" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Path" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s ago" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "BÅ‘vítmények és sablonok szinkronizálása" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Összesen" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Module Type" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Email cím" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Teljes név" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal fiók email címe" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Másik domain hozzáadása" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "További domainek" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Weboldal, email és közösségi média statisztikák (opcionális)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Mégsem" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Kérlek add meg a licensz kulcsot, amit emailben kaptál a vásárlásod után:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Licensz frissítése" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Leiratkozás" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Feliratkozás" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "A(z) %s új verziója érhetÅ‘ el." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Új verzió érhetÅ‘ el" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Mégsem" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Licensz kulcs küldése" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Add meg az email címet, amit a vásárlás során használtál és újraküldjük a licensz kulcsot." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "A(z) %s deaktiválása vagy törlése automatikusan törli az oldalhoz tartozó licenszed is, amit így másik weboldalon tudsz újra aktiválni." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licensz" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Prémium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "ugrás" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Korlátlan frissítés" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s left" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Last license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Törölve" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "No expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Tulajdonos neve" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Tulajdonos email címe" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Tulajdonos ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "ElÅ‘fizetés" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry for the inconvenience and we are here to help if you give us a chance." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Ãrás az ügyfélszolgálatra" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Névtelen visszajelzés" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deaktiválás" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "%s aktiválása" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Gyors visszajelzés" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Kérlek mondd el, miért %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deaktiválod" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "váltasz" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Küldés & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Ha elmondod az okát, tudunk fejlÅ‘dni." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Kihagyás & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Kattints ide, ha névtelenül szeretnéd használni a bÅ‘vítményt" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "You might have missed it, but you don't have to share any data and can just %s the opt-in." diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo new file mode 100644 index 0000000000000000000000000000000000000000..5edddb795223916bf43737ad0470aaf13633b009 GIT binary patch literal 55345 zcmeI537BP7b?=W*GBlG6Dk8$^hNiortGb&(q#2s(u5P-Zhk~kZ6g7CO?y0Iv-=Vxi zRTpuLV>D_Iqt7WS(L@|#FeXNEd`J{EByrZK#tg(nqZytLdC} zf^;$Y3fK?+6x4hD=X$@kf_lCSJRWR;dj3nG+WQ(%{e1_hcHIG91b!e~|2?R7{t!G4 zJn}p*_mQC5cP0oalJme*!BJ3jt%65`^Pt-QJg@=23RHc4>yzZy!DGOyz;}S6!#{zy zgD27%weL>wvEaSnD)2j?`02O-#uz*oTnRoEEP>AkH-di-iseq)kR*=+&jybNF9p@l ztHG1O>%cy6GTc7^s@@i;ad=_4{|Zp=y#_o6d;@qK_;yhB{C>FqDexMuzW}P8DUDHo zF9%iM5co>)iQo$GyW#%*0at8t`keuae&>Oz=OXYC;5Jb77z3XK&VcIIr^EBFf@FaK7lV5Lw?WnSyP)Xx2~gjE7F4^x4jv1B z8x%kO0MvUcF7S4r1fIzC>7dFR0QKDk;7Q;W;6dieDZ8e**qA-2VwLseOlE=5#tf;A272XDxUWcyYMC4HW;} z0g4aq1R-(q36P;m9smcS<9U}mJ!)4V8@c~nQ2j}^B*{LY4-|cF0L3@6;A!A1z;)m| zKuD8(0qh5lyVA?M6ddJxFZgKiH^4REoggY9`Acv&_}3t$PA(iwk}0qX&VXM49|LaR z>U6vjoaZ_N)s8;{&jcR;#qXzXbG|wo)VN<9u6Khe*G~gQ|L20D%P)iC`&+>Q@NRG~ z_yh3S;Ls5C0Y3q%-XpgA`_BPY?|VVj^Et2t{tb9Ic=e7Xp-IUe5K)%g0%}}80*b%y z3iv1B?kr=0NeoyFm5x zPEhT-8$1HM2Rst|lW_eNP~-V$pvwQ}aQ}#%etlfP$AB7-)u8&b1$-1Z1gc+84mb^d zit7&eo8T40=uq%J@TK5quJ-m^e~q{Q$>2)vKOH!i4?c=RnD+WW?v;DWK?j6DYpD;975g21*|O75HTCKa5Ft2KZD^{dxv?0{A>o z{BSFHJorXX?RXb>F8D=o6_|{9`DcNm>poD=p9`J?z6+$O$>%|}f7L!8$8$klUkMI? zJ3-N94pjYb0X1*#0QKIVf@;TC!}Z^QqU%2d{1GU6AFoY;!-vsLY&EO-z z?V!eEKd5rcpvJESYJSaw*MqMF_1<^DM}ZH3D(?gyY95>lif$Kx;+M^!o?ii~-B*F? z&t6b`aU-a99RSsyXMz`jF9uuSN5Cq0{u7;kZvxf6KLpk8FM?{%UxTNDKL~i-lYIQo z0afp2a3i=C6o0lr_3ss+#`V`g@!6X|jni8}SUmX*I0>G5gTMc5Q0;yJsQPaS_iqdL z-wqzj{r7>Y_v4`2cMteT@XO)(_dtFB&)|E&V_?2t1MdW14PN^cpLc%)YTQqKs`JAW zK+$PDU=8GzuT>(_wW zx&H=m5BL|L+P!Jq`C|wazwHM#{x*upy>E3;3eRjK$Y`V@B;9{8on+#4XXaP zfec;pNQgQD9t8J+_kgfwawadm4tzFv4S3p&%i~G#Dz0Avihf@PSA#zS*MTda?sT{Y zRQqQ@mHQ)5&#!GdU49Wfo$Hr@8lQK7mx3P!9}oTr6n)M|NIe{UI(Qm52a1lb1l8~N zf<|{xlRS-eJXe^_k6p) zo&`P{d=+>y_%2ZW_!y{izX5InkDB%RH-ma_KPYy6;C;O*cF@b|;@$H7N)eJ`l@{usj12zY0BemAJ{{uDe8{APImcLDDQRZeoV zzjtiF$AEV;fzJU4dHi?Jbh$F{ERXNJ5k%A^M?Bl*%K>nJ>z9L~$A>}n|K9NYtDxHd z%5?l+e1g`*9@6!TKfvUd_ zivJDMEu*SCRc|DE7^@YA5^^g~ePp7VUK_cCxD*V{pjQv(#AJrjHkcnhd{-wBG2 z?*%2N{s2_{{|nSS`6;M!Pke#jUju6VH-YDYSAd%LH-X}#7l!Ar1y$c~f=un?gP{7q z;f3@8ycoO+{2HivwCY73-x&lo-rogRfjKfHCn)~;Fetiy3hV>F0jfWL0jm9f4;}>`_7ZRJF`(W%85Cci3hMi_LCJ|r z!u=8Odaj=Ws=e<6j|9I0iVoibUkUyITmio5rB2sZf-2|j0Y3^J&GnZ+joY_C(c}A| z==@U{9=zoh{@$BGjq5u=$-{d<_4}*fB@cryL8e%;fsfU`_k!ZDJ3)Q75h80|?FGfR zPY3nhi$Kx&S3#Svpyt6RLACGC!4H7n1%C^C+pXw!aQCawec&fR(e<)l^LA|o)xYb& ztHJBR=YY3?qT^Yw_I95OiatBQW57M&iQo;O__zwHoVSAd?so8O`uRJc+Wp1XIDNhe z>bviO$AUi%_mBN==v1yB3+nls!Q;U9g8Ker;1=*6@O<#-*D{YN|1$72u5WxDHXwL2 zcsO`JI1T(OxiUhq7wKMNiP zJ^+gEegx{f?Qg_302^Qx{3Gx&;O1XMk@HfHBz%PT7;3;o*|Ku0J%eg-3E$&B;fP-BBF_?lU-sa_> z4{F?Zf*QA5!6$>S12=&8hx_OJhSRSf6rXPe)&6V26zqWN$E!iL?>9iT_uZhz^Y=i_ zzfXqiuY-@}`um{9dBt13eW!xz_YI)RnE?+6ZwA%AXNCJO2-mLwPvicpLCKFh!u`9! z6S)2?sP=vxJRSTl*asf$C#12rDs0YgW{7F z?{c}b6IA=Az+v#|pvt)ud?NU9upd0_-99gOf?wtOT2S;m@3)-}o55qaei|r#s)Br) zd?7YY4`#V9=cjNE4-rIeL+re)EHQt{E)vwQkYS*_x&5wT!*DDwl)pH6cK1o46 zKN~y_yc}EsUK4OXxR2`+sPf+ro&kOY6u*80ycoO>><1tByFMP*fRE$)DWJZ)8Ps?_ z7u0wE4OBbc4643&fk%Pw2TuY&2A&Ll9<+Xd8jpVjrDq=YK5y?y;PG6ipy<8<6n|X= zijGeO_1yUTg z1%4Q;gPVTe>G5XpM6T}z9}RvBd=&TqsQGom`5?YS*zJ^6|b1RDG9&H-kGtjn7@+ z>%cQV?DY8{sPXz7cn;;4s&(2gMinf#TP{4|qSQ`~L!JypR02m;Wg6O0FLZUIjh{6ra2m z6usULJ{tTJP~ZIxDEj{pRQ*T&cjw;=z|~w2f-4?|9065d^Db}4GXg#jyoLKO0Y%5t zKjG!B0ng(4@u2wf22lO1gBt&rf$Gn#U16=!B>L^?{+)-gg^9l%z>A2|BYZ9 z{2I6$-1|vi*Lfo-{{0dte)t}!ejoNJAFoG(XLEfSsQ#Bgy+03%F24${0&fRJhtGnC zfjIfcwDxpLad?F7R-!|2*J*;1OK^EtrD;0E%8Ge!=-Js^l4PYNQ z397sUpvq~0M}xDV=>Hs0-~VdB*Mgeww}Beh4}mK0&%*QX2K+&||1Y5GIr@t}-p7M_ z|52doSp|x3*MrA{=YwkJAb2FW2h{ufLGjf%sCLeSE5PT3>z9K1?v3CMpCM)eqeJX8J>gw|;CDh&;NLOEQb}O-x=PK1|TB+5ta)lzQ^L*Zzp|WZ^ z*|DZJ)%vE&nJLZJvU;1Q(CGO_r|G56R7>@=TA9e|w8e_c4$f5QWQE>S&%nP$MXmIZ z)u@tI+Rsa!DXOiPYS}X)tNVILG|+DdmhDeQL`8kM)GgxEGOp)}Rd z$0BV-B(`d;4Bo3%TEPAzTJ=_pL4rk6TYS!!1%V2$~-oJFRC4TmSi4OD)Bf*NTD zHsB+ML5$~Iv|ewlfvKiTv-VOX9}N$zV~rM{_g3$%8c3f+4a5fs{NHJS!xj3km z4pwTFo3k`4HS2u9@HW~^V;Do-UBk5`PLvJY+Qx_+Na<##dK&4(bfdwTs%*;4S}AM@ zl{KQ}eAFX9*lST5H0lG%;K*ou2;`HysU_a?uQqosQBVV4;3 z({zVs(|(uf-B-g@@80Nk+(c+>m5~inL_xjaHK%HrF!iiG*JvInyxpGGv@n5?7g3JC z=;8YAbd;cx2UOw%;*U=x*|N)dQn zhkeJfI1pzllH@XMT#k z)Z0Uq@7fliP9R-K7VH%jAaN$ zK_yr^TBw16WkX1yc}0zxc2@3(Tg5685&EEstvZL1D;~BxW@x0rsIqdBLD+)%lU~Oq zYIkrhYeBSR>k_on@F!cbYxQD4X8dq{wqXKiYqepjGvUWnIy{tYZItJet#livHeIl_ z(?V2X#mkW731TOo@5~9i6Xt)i74m34X)3JRJJU}0cG|0qe8#}_Mp|vur=S#avlX>e z9TSQ{%V#n~VEQK8O7%5}oJzE>ql`I_&2P~|=4t><|8vq2AKAja{&X15j#btfcK8aT z{tzxmo!K2+kf0^RktLaN!)t0bPh@+`cF-)5t%+u3M$a*H(JaE7C`>Q<1lz-nyWG}~mwRPXwx%mH@rH5Jw&`r*0H$QJ4JR;H77qk-)%8jD3%!epUaj`GWFj&a$h_7xq7gN{4GGRawHc<|I8&oDLqoVxZ=(n! zGKA*6=GXgZIj=M;sFOxh7w96fb*?>)(`8gQX?qhUl4ee}Nwpy3;H&MkMt~vN1Vg!} zQTUnDJ%1{GfD3cG$C%Z*)TUw3wb6a4v`XqxL60+Irz;cFX4N8FnXLYXGrWX+Fp!+R zdnz{N6z8f{7g$PqVF4qpPoYb2lj~MRpi69VEN5YrJq)O3qMBSKgfy8tFEB01h_o7}5IS!^U^0_t`uYfGCR zQaxbziO@2)3zI^0fpMea7)ZQQ^U%Ax()1Qj0`r+8;{uHLeLz&b%^&F)yDLLD{~_8$ zw@aUxm2A_NGBNdd9PGvJCxyogpP^2MIyMpXlEMJ1Tif@gS7CrO36vmlN>Zxr%q_y} zvZ+omj@Mo_G;-ZKs>2qV!Cg_*q=|b$pv!||@H9giZs8fpOwue3GT1vwWQ_UEtI!W) zpbd{s;cmgjjXInP%PU+LzBD7NKHexb%jxF+b+P5Ml}2b2*021dXf=uzHE-N_D`yiX z`kC~RF)J{oOD3au@wMJDUHvJ>)jklnRnD6zs>)Z0L^f4DN?P| ztL6hVHz(UIR!dI}qY=LSAn~Pi$YKF4p6SXdfHa8FA8!Iep;l@h$V`x^WO^!(%BmVj zcHk-MALy9upg)D_lZ({e5W-XkzQtx!csvC3=&rlDDnD3p3INE74U+}OLK4l&v=M@W zvP$LX)%&3m!vzJ5Pk0g1%E>S-p@=|aU?#W*D{EF73PSB1{-DF~N z6}~NU7Q$?>eWjFtB#M8At zwbrC%Y8-!1!+sM6KSqW*{AFFeXIB&5Nh4 zi`D?5OLfOj*s_B#S~sBcWo^70!|cnLngIbyha6zOSnoINOmgJzCai6 z%;rUcW=71Apf3|8RglN05hW_hQ;b7sG9!CxTViC%gZy5-zLrH7;uofhYU8C=t5gNKg-OBXNpgqnneV)uLgA>CEzLIQ)C_kKD6E`p|PP!RPWtt0~gD*%PW+Z0Xw$SeZem9Nc!dkp{I`5HQP+}Se$Op-Z8U6 zEPz8fifzftbjL$Bqkft-v)1NqF_5zk@4-o!Pgp1ud59~)lQgy zZs`Xe^5}V9e0T9Vjnw+;66a!3#(X%G* zli@bOvIfz~T(U(o)C7PzcJh0IkR=kl{1>@|)^JTyxdO+%uZxOeQ2C2!8nDvy#! z!VtPPGk8%E2OF!L;+xkkQ=VJWHkihFn%DVqfJA+?AsJ!Ttrfd6{?mIbUbLwV$v93x z0vRHYwuPHSapKVy_84r?YBX_e7uT;B&_2PCkl)+LS`w?lhj7~E3no?qk*ZoCd4n~f zFafb65LsGdmfl7zqj_*2nwI}CN2F+K0G{vKRU@5o1}nWLW8G#nBX-r+Z{ni+Wcv*d z;awpyCI8IJQq7v-xj!^7iD%C7O6X1Z|mIt>TbPW)f(~yGmB?Vwtd-CR@8J=*{er>KMQ< z;%)DeAu+TyC8Q5Ca!KaHbVC$qOzA&D`pIr0#w<#JG6UIeiLJy`@K4N>*yF3Y$?nFu zmdr{sGcA5=;Wa=yQliU#%LAdimBLVDjncUC5RmM9v>Ks*jIVqA#w1jfYE2`*&^+J* zCxxsAg4^QjTzj}d&cqI`6LQ^V`?5;~l1G)vFgtP1G`V`6D{_dY>d=TuiT?IA5wnO_ zUq~KMv>|P2RZ2&eK}l1xaiBE6(ledRP;+T!Y-}?VR|o$4ck4)oziJ|f2_}!pER4qF zB$MP{-^>3b&HRrV3^7fx=X00v=j1CWKBxa;pfDYrMfWPUuDkGlk4Rd(ng~=^{P-JD zEG>T6k{GvHbg-T5Y4Cq$Oi&lmDA3K}A^mHPaa*#=#O-S3qy=LM&%%V!l5g2U?7~-9 zT8XT;BfKXR1dPE&-M1V&V_L52{dS4s&_%9KJ-Y`Dk9p^-G^YJ5C(O_v?hE$wQT-lng_Vd*==@jS?eK=~AU%PB=R!lu# z!7PNFW9`ZW>C~%QtW&*l5X^g1Fg1FKZVJZrm(Vk+Cr~t8H(;gKP%1ol>Z_Cm=pNR} zWG`)RCT8-d9wGGKjJ5wTfjqKJ&Y}l}+sTNMsa(M?nJy#Rd3MZ5X?_HO6CI9Tan3^v zD^nhwR6?LBg>>KOkjY(CvLdXQ2sH%OG-kkTh0wJXIgpGn=Jt;$kFzZUCOe2zWlkSU z{HPdW&gT4jU=5>4Y~i2!R}8RQx&^w$eDkDyw5SrJF8N zPG7Em=(hTmhtI4%xo_s7J?lqnrc`4ZE6qXOwOrd6P{UY~k-o%j(|2Nez6^mya{go{ zY>sc|={|C}L&1m)B$d94a}dME^rQ7P58#H5IT1ZsuABN7vnunN$W8$>W8HKJup9*-T)aklri0 z1rH|@v=|zwSS%p==rVMT?Q8{0oFvmY=dmTSw3jpxq{+yLDoF}LHn$gY%EMv`_7e_@ z-4)Xn#2Kbyy*!EWC&G2w@@mTXV5|~bi?iP1&J-%zAXLs60WWg#J6vK14g#M&93eIg&aY=%%0cBp>OG{}rb z(|0)~mTgm5sSTbDPSr{c50LJ}2T(pb-7 z_%b2S6x~^iCtC% zc_6oYL!M)DZAaNaJBZU-lH#?U2H9}eWm*EmF!3;!sxV2-1H2qt2#=czOgPFBgt!uM z=AQGru;!z$$z4LK#0?Q?tAux}`HOLpHCIxuq*hR6;mT%Y)NV?n%hPl9g9VsYH<>uv z^1RdPRNDzFU;59zV}v3^t&{x0m^mb{x-&ly>QivjsG^FI$L)e?WXK}m`t`b+XpT+>=s-AaS%X5@l`Js*jr?sV_oEV9H>f zK#W*nmNVK7I-5(Oge;ZsW|c>5$PvPnklCOxo_jlbr=`G$I&7JO`Fp2p*A z=^^G&C_~PZ;=xcL-feZtxQSW-VaMtWr52wDYuv0}t!!C3nz9?hU>Fb9Sod$2=p~8x zU8QS84ZA|~={HwG9+Lb@rWQZw6b;p)SO~6Q^Gp&2j#4VJp`f9NY+%G!IEVMYAr7k!^bLL*|V)ei|pX2N09$Ci3J4v zHyx#}iD|!R2^3$FPJ+u}3C+?NMYRv(W0`f)fL`J~b>CDl0@lra+eKp;Vwj(vKFO#k z``UVIyjLjj`g}VXjasHOnIjr+rR3W8F^OrJaC_V{+Y+F)ZRL|IXDD*MgRR zzRxp?G@}~zx@0jw+YF)-s#^2ZSj)K~2$4`Z1Lffg?6rM}PM-jl;_q2iX)yC_yj8Hx zFfB#0;E6=ZhrJsz3h7w;>Qy#xsaTj zTa21K8W)uz;yRG-_B(l!i&;3LXw%d%B9z2D_R?2w)`#N77*}=yr)5O!)`o%)yn#l) zJ|pad0Mf2y$?TxrLqy0_A;Y32Zjv#=iY=yyhDzH5yJHg>d^uHhlWRN?vnQosm$OW35lEzo$6YK)nd^v!VMyYs1ECURtJO9iV$xh#AfA!d zWZ;7t=gdvk@Q3w2 zYXdcB2T6oSX7DaHuvu_kMO-3bep?cE*hZ_!pw`z3UZ@Dmzs4KWWb?_tO#C`si^pd% z+I7kG3dl-@ilJOm!#lbeP;Eea#*tfkMVBOiSZEOG7bSx)9&7xM??A%7X9E zQEq_k^qDEiX9#aoz8nHmkA;j9e;SzFHxnZ*bypfX&V?!52HTB?X=5gaPflLT)u(o2 zdo+!p34zY*Z5>AajtPfm7DYX(0^Aqck~|4@!GD-H@SFeej7=?2P;v4kN{J38TWYK9 z9YeYM5)wV4!+1>g5^dhp@9Au%Im_A9a*hmQb%53oXTs!Nkk2W`wI0dFwN$3OG^K}1 zz^^Ra@t9l;-#95W{bGxJD{^d)+4QpBw2Y8itGuIh3Tee{IXhYXrUvqgU(B7TWXy)8 z(P#x8$P8Ps*vY%O6~4qeLSd_=(Nk` zM}^xxF%KWvcxnsAfrwGFqZX_T@is%KWX-sq>z24Zr6rzZB!OW5hdd0?%4kXTY9X_% zUr`kmy?E7cVdT+VDSc_qIr%BRXqYk;r6_~N)Rj}B`XSOHUE1<-LH<}%G{+*VTI7c| zs#0;DNy`4H*yBQn)Lu*B2wR`>>?fOT^pU2ifB&Y1)~ra2w+>ojm~p)6@&#jPW*CZ{ z7TD{)uxHV;#b&=R4Jqtnua7hvGGx=em}uJd>31zU)_W_iM=vTdj9^hflzAs~vmn zoRHVsLraK&xkgv|O%q704qm@~bnou%CRdt)jTX@$%p=!-i<#W3G;~wF_GMQrs3qz?EqnhOZ4VZ8Y_BZloE_eE)c^Z>>VNH%&GHX@IV$zk(X2j;JWa@+W~`Sl%La-rk;-phdJQOdkf_B)pL#GLsn{ z&ax6Bxp4Hgo_rSe2)YpN8c1#!9w8b(LgVO>?d<2Etp>3@?TEm@pTP;v`mQsRfOJB_L42{!h(Nt4PDxB94xm!aLOlxXC%*7n zy}JxCmad-Hb#P9|d+JlVgWc`M=oMc?HP>9~PO@Uur%il(I#Jc;j}ncn6C~*ys7B>^gjBu_@_60V{0OYdpRP2Zjd zt6zG-UTsXal_voca7I*P#&Sjv5Df~0(dyH3_kJH48~RrKBFKZjU19xS73Y#E>$6OW z-g}Yx?O6;ZsukU-7GjZogBFssXC0k<#FU%t+dDZ~yi@PnTTj^Nr>C~%NQVMS=4mRy zN29jF*fnXAW#)lm36A+AolpA~Sw9b(a)XY0|>vp0LP-h9%@HFqolb zo~r5Jxv~sGVNAYL!dv4=U~yvjjT|&z%P+T+fbG9_5+?Rvdp(QTKGkof65B=0&he{J z(%Ql=b;91*XMyl&-tHXKOF-KUux9*hs*u@eg1DQB*q{XR$P~93t<25)>Nh3 zzqK>f>K|)tPN&=LnbzhF8>Uzn>5LCByEg0y*~S}K8!He^^{Eb#sm=HYHx7^G*ZpHz zskWJLZAr&$q-*tKeqFj&-J^GNb8`c@?1bzmvxC-22^!gN3p&Y!w(>Vv@f+5yKnH4T zN57VPHj_7Su)P8Ms#3qyG8Q?*?($B1vi}L)_f!knO#R!*?vQfXoIYW^(q2JSjl)9v zcd$vfwK=V0G;rCv{QOI0Kk)PNwe@u6<>{t%m#)}7ynB0h5H}5MT=B&Ik%lt*`?n%L zrQ|lJ0|VW=we!Xbi$3-2(#h7sOOhWNg{R~i-gr}M!3_qYxzw#C zzMXE@staDVTg6xNMjZ@qZr-&1{E576i|*RDi|^7>YuJ)c=)-dNmoLs*y5wiwKJ+~9 zlHIe#G#vdK8IYwONVIo|F6f1G2X1N=-oaR(pjo9RJhVC8H?{*N`mf`l|6`AX-sZLi z=O3(r&B)HY8Ht9qRY`4&2j?^;yVeAqiy}_%db`5XX2Xz+=s(ewc<2^`Z44y56m+wd zRnZMI!EUyreY*u%b(FN|^T`t5u!KUPjf(2V(OTU?7@CEs<~_YbRDfd)SSyjJ!grYP z)^Lj>o;zfmq}2)5LT_euZ8$n54Rs4`%tLb=DpcOnB`jSX^0}Z%EDUp60G1Ra^1vf~ z2yy21U4rPai`b=b(gzEjvR650WR%=pGT}-U1yh?$o(*`F@G-maHDrPtpi{%Bn zU>1rR@9m?bq3TfYKsto>S@Ij2R;h6e4P7Ovf^efXigH z#?Y?PQ9mES-W0J(=DzFAO1D(eSOr!XyM@&~A9G^g9Wximt)^wQuHaI@Lpv*;lETT)5JVwtbgnfTvd=KkW(Nh5 z*)|RDUX2x39S4202BbpNy@}UZ5mC{VW$oS^NICVTuGxbA6><155=8G|0Q(Wq6D8QS zLOVpZ)?AL zb{(Ca*h$-*J6F;476I~B(kyEac4_>4(aZ2=T zl1;J=_W;QS>)9=xyWEcm)A*>rDn=B=r6FtDX~2H>PJN2m3r(vm!ZL1}4Af%Z`bS7g z+MuFvR@dN#Xq>4HTY4Pg@KLF2p!zY;c~A{8g=5}?;~ELkr4n=rmv$2Ltay`+*s3&~ z`B69rI-Usa-5C;NGq>9vwLK0Ef(HJahUy%PW>U!qcS1arB0(1tKN(|~7&qk*;56X2 zkrEU}u3ed_==e(97KSiCCAvf>2yBs@I3tcG*&UHWJOaY9T2Br`UJrMBhS`({+Q%DK zmsp_vHl;`-)O_;x`51-;Z{1eRY}oc{H%R$mSVCwaNou zK{e=nGxL;r4TDmNBsj7cGq-}-u5)^m@o>aqi7mB z@_C9>EOZAQu&DvlqM*k)w5JlLq~2qibqyTag`jL8e`hLpeIT99CNu%Xws~%IX&Lx*#yS zk4u);74*FCh)j0C_c*=$>4Id)3ExY01ygvb&0kFk@^_iDr#Wk&k?5QTC6O&UdwP#7 zG9PhSPN?Kc36Ufliu$@LL>8=0rj9Y6ZTukij`fHp3P+*BHOy*+uWVlqrG^z5E|!0l znCfGoW3E+f$lrE3Rgx2E~9;7DN|ARzkNkXu+7{U~v}&;c#Y&ng1+jQ-_w* z{Q@7v{=jA&B%2d3zujYMV;V&By7zGE-m8arRJ9GD3|i!IgRz9lJ+*OKJmG#=U6ytH zvpqq(n00H~NIE9yxHx6pmdmZ|{nBI14`PuWq=F9Vu8p(pHwX+GXF(Nz`ov)+WTEgT&ZdjWu%$J~CFePn5%Z(_Q z^g(=j54&$`>T{f>>=5a3PPZf;gq$r10aoqmvQ5O?1*2UQW!~>lEvD71s7i|^o?sOt zSs3zo4mQ#a8xWg?4^fC-%W69uTLDo`Oa!SN?m{9U5=Q0&j%3^H2>oDgu{XufUkJx% zb92I_BhZQ@C|GFQZq^44wpvlzwG@NnzIzRJ$SI5$VcEIe#%Z7E>F z&?gqLX{R5sO^fMb@b9F+C-COQP_p}YxY+Pp6~x^t4)07e4#^SQshDb7JY`!AmZ2UU z?gtqP$s*zqMM`bq%qXoUE2YF4-}D!~4p~3-tTd2r3ojJRe{2jXI&(hF&d6zF(`f0I zgktE3pLi@IqM|dt-4o?>X{Bs?b2JA3R}R&d?wy7QCBv3byAqyygkKj3B$phA2HkF( zFzifhtuSb&TXq5g@4_kD#OfJwCQ$xYS41K9(T3Y=*m|qLNbXR$@LcwJ{E`{+pZA#U zK}H!Z6l^d%Z9C4~3XCPMH_RThpM%})uaw47qL@~8*rmy6Q`edwU8eO39vs;)KXUWE zGHYVXhF$xPNoX@Yy-<M(HF-w}u??;* z%^c62kkD(FuCd$lK7t^6Xw}k)#no!ognA_$qOU7qw6F~pkC60`tZwQYm3TyV9!v-e z{X19|kr5xl|~y`ab~#1ZImi+N3+k~_g2Kl^GQc zI>Am(x1#Sx4=0MB15OHRok+WAkTp?p8fOikpD9XcWT{iYRg%O`u^<=#jz&aO(gwb` zj^=R3ELd*zOya=eYRqU@GC%%X15w~DGrKI%*uvBD>A~01H9ZG^?~})(<%0a3La;a8 z1Y@g7{yp7?t|m});`g$q!nR6@KxvB?)C!82Hn)VSk9ol9BPyk+$+lo6Np4|tl6J@Y z7WnK5IGHMhEp><{iw`6Lu%{<3BiNi{&uX)=GTAp>lb?i`SC{H#GxS9$9mhFh%T^C! z_R~0;pigA((CETEQ_In49*pA8L8&5g@FrOCVK~f?QNt`h&^!iwU1Mr9?Opblu=HeW z$(**h$Op|P#+J6*6Qvo#DAGn&woL5kRj1FVoxv8kq> z+wQ}YoTyynDE5^tmtf8ka_FjF$)2xrxR;vx=x;9RmN2eoDI_>YHit{ zIl-~vg*H-+DIl_%6jSy(^2yJvPs0?8-n~nmkXDZ^&FVjHj*ABT&0bf(pd(4?1YtB6suPm@aqc+jd%o^SFi}BN z@JI|k^QD1_;2o<$t#L^g8qZ-b!Auu@H8X=XKGPZ3vErqr&lES^YZ_=Ex$DKIj}RSQ z=MLgQ7+aq(!$adoOISJ47Fx!lKoJkFBdd@Y_&7#4rV8s6@zZ6_7vHtOL~eCVVum$Z zQK!?TL>o)**PmcyqrEY@U~(yJr3 z2x}{MSFH#%GyTzRiW5>}9k|rOSH4yf`Id=ladA%6zz%egspNvOuA#Lh9SVy}km}GT z77dVrWv7*I^N3AjMncE6<lX{b%f6l}c^SBY0`>1BZ`dR4FdWg9MF>a@270D&j1n^5iT&(n6NSK6LwXrq) z?i7{#wuf2!ucnSkjzYj<5n-002e|@)9#}bDdPs`@Y$W;Fn*aaT;Qkwr?L#Au_EvFr z2}+we&@pv}YbK+f zw8>P3srbJRYRXqybfgE$0Xd_UZ>_$*s%_R4<&Pd;mgc7z3lEM z-)_*1fjo z2crkK&F1ZDx@3Qj*S&j5V7EVl#j^Jr&IgS)Wsf&PHADs1$K^F#-A)~uBw~a$V*Dd) zMJyNyvPj{YtGQ?I9!`z2)j3a^MW}JL<2c9-V<0W$#7n_g4vV|2MrNV z$681SYp@5tZiQJ+OO=VLpehzlt5TJnVHQKAFj@#KuZnQ1ml&Y=^yD9G|+S&1}}ZHv#6>+Iwk`VM z?7Ir3X|V!9WG5HVB8i85RRd9kB!5a=Gx*Ba zP27ieMhk1GqJ*8=uDnh6|6@prJqIR2yHSI~)x2Q z;rrb`+nNFnoCdC}(aYxvRd@P2*_{1j0!_1hz5F_9pI%FrV} z+fnSroysAB2o}b)z|8PJYteJIbKxXfbQhsu^c<#*;)wDjFsK!fhnbXJiw}8I|H`J!my1D zGI2aSh0sCB5T5cA;1@)2LS9ICh!Hxr;CT359+xu(Zf1w(DEZ*B!Bw#CCHgzj@nU^1 z_l;;#reH8|csvHIOU)q4Ms2-4&v@vSQ#?cxkWW%td}@45YFYB!_=yRf8*c;JB5Y1{ z5uTJ8q68D_V30kEkL_e{AuH^;Ef{iQo(asLjrdN6p9ZbBM9e9PRl6Bh84v=R=vu>H z!ql<99A%ake_bz3ufT*W+PteMaye^mmG3LDcgP@M;@Fw?K^Mh5 zFVC>1pGKKUt#Es!Kn=jLuzgmfYmS)gDJV0}2`Fy<`1xCCEph6F{^!-FN|6(tOkdkZ z$aK?%F?M754#Bkd?=?4TPVL?>wk7>UKIvKiRoUI$y(^3S)brkQPx6QzW1wXfvILwY z5gFtBr`l&;FReD$7W%`ChjwlE)z^e*LDwKFvMOp{wt>kdGtsPM9ae1VVTA#*s^bP_ zs@9SBBQz;``bbr-mhwRht8MU0SRL7%q{E!HsRQseX)L}?0R###YC=4M1wNbKG|`R* zrF>>-Ox`L^3|?d;a&5n4Y_sqwtYVI;&u;U|3*oVW1d6a8*0hB#NUS6a(=*)H9E~oX z-qUsO+L^f|HKZ^H6yi1#mE<+3ukmF{vouPIMInts26-&e_ie-j^D*uu+(V2H!)A&4 zmDyu|vKMbV(5+>vLKKO`lD7h}VKOsz)AE8nS2WH?kzqv|l5)?AGQ3^0r95V~Eyxlq zlHuS=DpxQq!XAT`hDUWMwrL8BG@;P8GGLf{*kKApN?42O`787cv;7xRu%mrOxQmt& zahQg9I%SUtlfs_dbfvA2HE$7eiY>sj5^S>Xxohf+_xQl`->pYHBNt5>l0o>z28OjC zX|i3t5dk&Rht1`C>@Vf1nyw!+hAzd6V~L!k9sTA;g~RNTDLRDCW)~+|QC8uE{cT#r zSKCcA_82U@=+2r2%?AkJ7Bakza(wD-!rbN0ji^B(YY$!MEAk?QYl9^%@k5rX~)1JthHLWvn4)M zl2Y;F>HFJ)nA4u0wqnzE@;-(J*`G0Emd!`7ZkTCW<>w<>kLJfZ#>iz5d>}Pw%;Y?b z7IProykw|2E{UV}i6JF}JDC3 zEv`NyI)=hY8orWT(Y{W2K$~(b!fBO+7;>0K(Pnc+Xdzq+fX1M1oN_E3D1WBZR1l#} zx=2CWsaeGcuPMAz%Y+hDE*0?IAM_Z`OZ*vf+;4!vSFF&OHK)VotS=!pEB2?ip_e>b+3 z^Pxk)Z-(hLV;Ng&E~JSl9=%6Ga-mnExVQ|(CG&fvCiKU+n7b6K5ZnR>1alOFz*Gq` zPa$V~j$*bxhZ_*D_T0`zb?4d!!KmYw@cVs(qD7n-ck8bt2WrwXn}QKyyAyOTWHsdh zmXP<{!wKYBFY9g}k|fBcCAw_M&Url#vHOEzGsYxRLqkL^6@S?$CXq@`tzWIA3{QY{ z22haHd>73+H@X|~Jjt@M@vLiB*rWXZq^nAPGf>0o-7I+@4wMeuFmr>jkNX8$dBC7}@dWs3X zi0Sq!O|sa<0<47Ok0Ds)m4(%e_$7Z+z(OC@$~uC4Cd*K-T@#OlVisB#EPlSlZ_4xw zB+}m((0hC+-J~6;Dpr!u)3*>Lntj@!Yk?m^S91A0`<_30WZoF_l@&kR#T%PK^AU~; zj3LW=sko8;8k8 z?|K(MlnKn=$|A3s9~MFgjOCuP&A39kBaBE7wLFPgrY)V8$!4jQ5g*03O(SJWwyog zq*#^~c0(;Y&d$%C=a!pq`E=4uaz zriN3Xx(&585aU0#E~BrVBUE2^K12Uq&L3zM*e1Ar8nr@v%O*m%`H(HdfJ^)tO3e>m z*IX8#A#Z8>g6~}!E@?pF4ggsK}^Vd3#;qbyl? zY_aIHh)fbI3wq&3xa{dek*e7ISOZaOVg{b^)%C-R#YfXmr7uG$$gaOpML5g%^Kjre zrVAdcEEaA2)3?l?*novS;H@B`7w4oZPq=H*9g0>Le4F@K*^^(OXKF4 zE?|dkgCjC=!x?=E%q7cargPVOb4Q#x;4(}$!69FO;obPQBLNA%;2C5I+^Sv9cHE?I z)I(B+3jGbaN{J5PVG+DjW+!+y-obGqkzt$vB?11jfJsgrLd9-3A`U#LYl)9#b&oJD z+N!nulW0+El|4@#)a#zNI)W3TeFC;POc<(Gs#Fu=(S#nb4RDbHn%3s1JIJoRuOZ_Odp8Tf*=|ShWl^ zjnQ8VH=yva3Y_@)#J5Yi2i|+E95%r);9>}do#lXhl_O3vk-%-t;uZw_=X@Ol+YsfU zqznwvn10U-A$yEG7k6KDz>yt9M&DMqw8O(Tj3zphelN$+$J6ISP7!|=V*4zA?lm#d z%+SaeP9FS_c(&Q)mIzT@Ij0T^KDh&t=txuxBxnJo=)apW{WEve_D#e!WX+W2{Jf-? zf039Q$stXP_*-Lgmv&+?dr7)g7Oll?qN0dL{NPW-!{K+cGNr%ynd~79Cub!&bYgKx b{$byZ4iSNGl|zsRhw~re0$U;CxZwW-A|tXl literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-it_IT.po b/freemius/languages/freemius-it_IT.po new file mode 100644 index 0000000..0ca2650 --- /dev/null +++ b/freemius/languages/freemius-it_IT.po @@ -0,0 +1,2512 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Alessandro Pelly Benassi , 2016 +# Daniele Scasciafratte Mte90 , 2015-2018 +# Dario Curvino , 2018 +# Alessandro Pelly Benassi , 2016-2017 +# Vova Feldman , 2015-2016 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Daniele Scasciafratte Mte90 \n" +"Language: it_IT\n" +"Language-Team: Italian (Italy) (http://www.transifex.com/freemius/wordpress-sdk/language/it_IT/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "L'SDK di Freemius non è riuscito a trovare il file principale del plugin. Per favore contatta sdk@freemius.com riportando l'errore." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Errore" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Ho trovato un migliore %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Qual è il nome di %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "È una %s temporanea. Sto solo cercando di risolvere un problema." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Disattivazione" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Cambio tema" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Altro" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Non ho più bisogno di %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Ho avuto bisogno di %s per un breve periodo" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ha rotto il mio sito" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s ha improvvisamente smesso di funzionare" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Non posso piú pagarlo" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Che prezzo ritieni opportuno pagare?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Non voglio condividere i miei dati con te" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s non funziona" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Non capisco come farlo funzionare" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s è ottimo ma ho bisogno di una funzionalità specifica non supportata" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Quale funzionalitá?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s non funziona" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Condividi cosa non ha funzionato in modo da migliorare il prodotto per gli utenti futuri..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Non é quello che stavo cercando" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Che cosa stai cercando?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s non ha funzionato come mi aspettavo" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Che cosa ti aspettavi?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Debug Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Non ho idea di cosa sia cURL o come installarlo, aiutami!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Contatteremo il tuo hosting e risolveremo il problema. Riceverai un' email a %s non appena ci saranno aggiornamenti." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Installa cURL e abilitalo nel file file php.ini. Inoltre cerca per il parametro 'disable_functions' nel tuo file php.ini e rimuovi ogni metodo disattivato che inizia con 'curl_'. Per verificare che tutti sia attivato usa 'phpinfo()'. Una volta attivato, disattiva 1%s e riattivalo di nuovo." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Sì - fai pure" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - disattiva e basta" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Ops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Grazie per averci dato la possibilità di risolvere il problema! È stato appena inviato un messaggio al nostro staff tecnico. Ti risponderemo non appena avremo un aggiornamento riguardante %s. Grazie per la tua pazienza." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s non può funzionare senza %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s non può funzionare senza il plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Errore API inaspettato. Contatta l'autore di %s con il seguente errore." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La versione 1%s Permium è stata attivata con successo." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Forte" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Hai la licenza %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Evvai" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "Il periodo di prova gratuito %s è stato annullato con successo. Siccome l'add-on è premium, è stato disattivato automaticamente. Se vorrai usarlo in futuro, dovrai comprare una licenza." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s è un add-on premium. Devi comprare una licenza prima di poter attivare il plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Ulteriori informazioni su %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Acquista licenza" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Dovresti ricevere un'email di attivazione di %s all'indirizzo %s. Assicurati di fare clic sul pulsante di attivazione nell'email per %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "completa l'installazione" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Sei a un passo dalla fine - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Completa l'attivazione di \"%s\" ora" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Abbiamo fatto alcune migliore a %s,%s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "L'aggiornamento di %s è stato completato con successo." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-on" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "Non siamo riusciti a trovare il tuo indirizzo email nel sistema, sei sicuro che sia l'indirizzo giusto?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Non siamo riusciti a trovare alcuna licenza attiva associata al tuo indirizzo email, sei sicuro che sia l'indirizzo giusto?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account in attesa di attivazione." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Compra una licenza ora" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Rinnova la tua licenza ora" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s è stato attivato con successo." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Il tuo account è stato attivato correttamente con il piano %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "La versione di prova è stata avviata correttamente." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Non é stato possibile attivare %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Contattaci con il seguente messaggio:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Aggiornamento" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Prezzi" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliazione" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contattaci" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Addon" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Prezzi" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Forum di supporto" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Il tuo indirizzo email è stato verificato con successo - SEI UN GRANDE!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Sì" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Il piano del tuo add-on %s è stato aggiornato con successo." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "L' add-on %s è stato acquistato con successo." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Scarica l'ultima versione" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Errore ricevuto dal server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Sembra che uno dei parametri di autenticazione sia sbagliato. Aggiorna la tua chiave pubblica, Secret Key & User ID e riprova." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Uhm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Sembra che tu sia ancora usando il piano %s. Se hai effettuato un upgrade o cambiato il piano, è probabile che ci sia un problema nei nostri sistemi." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Prova gratuita" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Ho aggiornato il mio account, ma quando cerco di sincronizzare la licenza, il piano rimane %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Contattaci qui" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Il piano è stato aggiornato con successo." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Il piano è stato cambiato con successo a %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "La tua licenza è scaduta. Puoi continuare ad usare la versione gratuita %s per sempre." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "La tua licenza è scaduta. %1$saggiorna ora %2$sper continuare ad utilizzare %3$s senza interruzioni." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "La tua licenza è stata cancellata. Se credi sia un errore, per favore contatta il supporto." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "La licenza è scaduta. È comunque possibile continuare a utilizzare tutte le funzionalità di %s, ma sarà necessario rinnovare la licenza per continuare a ricevere gli aggiornamenti ed il supporto." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "La tua versione di prova gratuita è scaduta. Puoi continuare ad usare tutte le funzionalità gratuite." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "La tua versione prova è scaduta.%1$s aggiorna ora %2$s per continuare ad usare %3$s senza interruzioni." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Sembra che la licenza non possa essere attivata." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "La tua licenza è stata attivata correttamente." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Sembra che il tuo sito non disponga di alcuna licenza attiva." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Sembra che la disattivazione della licenza non sia riuscita." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "La tua licenza é stata disattivata con successo, sei tornato al piano %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "OK" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Stai già usando %s in modalità prova." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Hai già utilizzato una prova gratuita in passato." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Il piano %s non esiste, per questo motivo non è possibile iniziare il periodo di prova." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Il piano %s non supporta il periodo di prova." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Nessuno dei piani di %ssupporta il periodo di prova." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Sembra che tu non stia più usando la prova gratuita, quindi non c'è niente che tu debba annullare :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Stiamo avendo qualche problema temporaneo con l'annullamento del periodo di prova. Riprova tra qualche minuto." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Il tuo periodo di prova gratuito %s è stato annullato con successo." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La versione %s é stata rilasciata." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Scarica %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "l'ultima versione %s é quì" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nuovo" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Sembra che tu abbia la versione più recente." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Sei fantastico!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "L'email di verifica è stata inviata a %s. Se dopo 5 minuti non è ancora arrivata, per favore controlla nella tua casella di posta indesiderata." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Sito accettato con successo." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Fantastico" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Ti ringraziamo per averci concesso di tracciare alcuni dati di utilizzo al fine di migliorare %s." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Grazie!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Non possiamo più inviare i dati di utilizzo di %ssu %sa %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Verifica di aver ricevuto l'email da %s per confermare il cambiamento del proprietario. Per ragioni di sicurezza devi confermare il cambiamento entro 15 minuti. Se non trovi l'email controlla nella posta indesiderata." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Grazie per aver confermato il cambiamento del proprietario. Un' email è stata appena inviata a %s per la conferma finale." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s è il nuovo proprietario dell'account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Congratulazioni" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Siamo spiacenti, non siamo riusciti a completare l'aggiornamento via email. Un altro utente con lo stesso indirizzo email è già registrato." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Puoi abbandonare la proprietà dell'account %s a %scliccando il pulsante Cambia proprietario." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Cambia Proprietario" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Il tuo indirizzo email è stato aggiornato correttamente. Riceverai un'email con le istruzioni di conferma in pochi istanti." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Per favore inserisci il tuo nome completo." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Il tuo nome è stato aggiornato correttamente." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Hai aggiornato con successo il tuo %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Le informazioni sugli add-on di %s vengono scaricate da un server esterno." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Attenzione" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Come sta andando con %s? Prova tutte le funzionalità premium di %s con una prova gratuita di %d giorni." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Nessun impegno per %s giorni - puoi annullare in qualsiasi momento!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Nessuna carta di credito richiesta" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Ciao, sai che %s ha il programma di affiliazione? Se ti piace %s puoi diventare un nostro ambasciatore e guadagnare denaro!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Scopri altro" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Attiva licenza" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Cambia licenza" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Cancella iscrizione" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Iscriviti" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Segui i passi seguenti per completare l'aggiornamento" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Scarica l'ultima versione di %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Carica e attiva la versione scaricata" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Come faccio a caricare ed attivare?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClicca qui%s per scegliere i siti dove vuoi attivare la licenza." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "L'installazione automatica funziona solo per gli utenti che hanno dato il consenso." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "ID modulo non valida." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Versione Premium già attiva." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Non disponi di una licenza valida per accedere alla versione Premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Il plugin è un \"Serviceware\", quindi non dispone di una versione del codice Premium." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Versione Premium dell'add-on già installata." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Vedi funzionalità a pagamento" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Grazie per utilizzare %se i suoi addon!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Grazie per utilizzare %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Hai già accettato il tracciamento d'uso, ci aiuterà a migliorare %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Grazie per utilizzare i nostri prodotti!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Hai già accettato il tracciamento d'uso che ci aiuta a migliorare." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%se i suoi addon" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Prodotti" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Si" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "inviami aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%snon %s mi invierà aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Facci sapere se vuoi essere contattato per aggiornamenti di sicurezza e di funzionalità, contenuti formativi e offerte occasionali:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La chiave licenza è vuota." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Rinnova licenza" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installazione plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Impossibile accedere al filesystem. Conferma le tue credenziali." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Il pacchetto remoto del plugin non contiene una cartella con lo slug desiderato e la rinominazione non ha funzionato." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Acquisto" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Inizia la mia %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installa l'ultima versione gratuita" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installa l'aggiornamento ora" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installa la versione gratuita ora" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installa ora" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Scarica l'ultima versione gratuita" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Scarica l'ultima versione" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Attivare questo addon" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Attiva versione gratuita" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Attiva" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Descrizione" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installazione" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Screenshot" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Recensioni" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Altre note" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Caratteristiche & prezzi" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Installazione del plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Piano %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Migliore" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensilmente" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annuale" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Tutta la vita" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Fatturato %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annualmente" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Una volta" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licenza per sito singolo" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licenze illimitate" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Fino a %s siti" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mese" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "anno" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Prezzo" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Risparmia %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Nessun impegno con %s - cancella quando vuoi" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Dopo il tuo %s gratuito, paghi solamente %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Dettagli" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versione" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Autore" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Ultimo aggiornamento" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s fa" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Richiede la versione di WordPress" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s o superiore" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatibile fino a" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Scaricato" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "% volta" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s volte" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Pagina dei plugin di WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Homepage del plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Fai una donazione a questo plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Valutazione media" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "basato su %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s valutazione" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s valutazioni" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s stella" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stelle" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Fai clic per vedere le recensioni che hanno fornito una valutazione di %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributori" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Avviso" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Questo plugin non è stato testato con la versione corrente di WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Questo plugin non è stato segnato come compatibile con la tua versione di WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Gli add-on a pagamento devono essere distribuiti da Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "L'add-on dev'essere distribuito da WordPress.org o Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Versione più recente (%s) installata" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nuova versione gratuita (%s) installata" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Versione più recente installata" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Ultima versione gratuita installata" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancellando il periodo di prova gratuito bloccherai immediatamente l'accesso a tutte le funzionalità premium. Vuoi continuare?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Quando la tua licenza scadrà, potrai comunque continuare a usare la versione gratuita, ma NON avrai accesso alle funzionalità %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Attivare il piano %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Rinnovo automatico in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Scade in %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sincronizza la licenza" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annulla prova gratuita" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Cambia piano" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Aggiornamento" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratuito" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Piano" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Prova gratuita" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Dettagli dell'account" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "L'eliminazione dell'account disattiva automaticamente la tua licenza del piano %s quindi è possibile utilizzarlo su altri siti. Se si desidera anche terminare i pagamenti ricorrenti, fare clic sul pulsante \"Annulla\" ed effettuare il \"Downgrade\" del tuo account. Sei sicuro di voler continuare con l'eliminazione?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La cancellazione non è temporanea. Cancella solamente se non vuoi più utilizzare %s. Sei sicuro di voler cancellare questi dati?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Elimina Account" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Disattiva licenza" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Sei sicuro di voler procedere?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annulla sottoscrizione" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sincronizza" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nome" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ID utente" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "ID del sito" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Nessun ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Chiave pubblica" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Chiave segreta" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Nessuna chiave" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Prova gratuita" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Chiave della licenza" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "non verificato" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Scaduto" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Versione premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Versione gratuita" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verifica email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Scarica la versione %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mostra" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Qual è il tuo %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Modifica" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Siti" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Cerca per indirizzo" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Indirizzo" + +#: templates/account.php:610 +msgid "License" +msgstr "Licenza" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Piano" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licenza" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Nascondi" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Disattiva la tua licenza bloccando tutte le funzionalità premium ma potrai attivare la licenza su un altro sito. Sei sicuro di voler continuare?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Visualizza dettagli" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add-on per %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Non siamo riusciti a caricare la lista degli add-on. Si tratta probabilmente di un problema nel nostro sistema, per favore riprova tra qualche minuto." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Attiva" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Chiudi" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Installazione automatica" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Un download con installazione automatica di %s (versione a pagamento) da %s inizierà in %s. Se preferisci farlo manualmente, fai clic sul pulsante per annullare." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "Il processo d'installazione è iniziato e potrebbe impiegare alcuni minuti per completarsi. Attendi finchè non ha finito, assicurandoti di non ricaricare questa pagina." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annulla installazione" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Cassa" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Consenti & Continua" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Invia nuovamente l'email di attivazione" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Grazie %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Accetta e attiva la licenza" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Grazie per aver acquistato %s! Per iniziare, per favore inserisci la tua chiave di licenza:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Non perdere nessun aggiornamento importante, accetta gli aggiornamenti di sicurezza e funzionalità, contenuti formativi, offerte e il tracciamento diagnostico senza dati sensibili con %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e notifiche di funzionalità e il tracciamento diagnostico senza dati sensibili con %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e di nuove funzionalità, contenuto formativo, offerte e tracciamento diagnostico senza dati sensibili con %4$s. Se vuoi saltare questo passaggio non è un problema! %1$scontinuerà a funzionare." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e di nuove funzionalità, contenuto formativo, offerte e tracciamento diagnostico senza dati sensibili con %4$s. Se vuoi saltare questo passaggio non è un problema! %1$s continuerà a funzionare." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Siamo felici di presentarvi il supporto al sistema multi network di Freemius." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durante la procedura di aggiornamento abbiamo individuato%d sito/i che sono in attesa della attivazione della licenza." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Se vuoi utilizzare %s su questi siti, inserisci la tua licenza sotto e fai clic sul pulsante di attivazione." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "Funzionalità a pagamento di %s" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "In caso puoi saltare per adesso e attivare la licenza successivamente nella tua pagina di attivazione network di %s." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durante la procedura di aggiornamenti abbiamo individuato %s sito/i del network che sono in attesa di un tuo controllo." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Chiave di licenza" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Non trovi la tua chiave di licenza?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Salta" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delega ai proprietari del sito" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Se fai clic questa decisione sarà delegata agli amministratori del sito." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Panoramica del tuo profilo" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nome ed indirizzo email" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Panoramica del tuo sito" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL del sito, versione di WP, informazioni PHP, plugin e temi" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Avvisi amministratore" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Aggiornamenti, annunci, marketing, no spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Eventi %sattuali" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Attiva, disattivazione e disinstallazione" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr " Il %1$s invierà periodicamente dei dati a %2$s per verificare aggiornamenti di sicurezza e di funzionalità e verificare la validità della tua licenza." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Quali autorizzazioni vengono concesse?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Non hai una chiave di licenza?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Hai una chiave di licenza?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Politica sulla privacy" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Termini del Servizio" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Invio email" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Attivazione" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contatti" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Non attivo" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Attivo" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Azioni" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Sei sicuro di voler eliminare tutti i dati di Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Eliminare tutti gli account" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Elimina cache API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Svuota le Transient degli aggiornamenti" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sincronizza i dati dal server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Carica opzioni del DB" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Imposta opzione del DB" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Chiave" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valore" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versioni SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Percorso SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Percorso modulo" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "è attiva" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugin" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temi" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titolo" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Stato di Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Utente Network" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connesso" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloccato" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simula aggiornamento network" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installazioni" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Siti" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Elimina" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Addon del modulo %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Utenti" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificato" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenze" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID Piano" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Attivato" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloccato" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Scadenza" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Tutti i tipi" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Tutte le richieste" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Funzione" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID processo" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Messaggio" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filtro" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Tipo" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Metti in sicurezza la pagina HTTPS %s gestita da un dominio esterno" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Supporto" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Richieste" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Aggiorna" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Fatturazione" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Nome della compagnia" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Numero Partita Iva o VAT" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Riga indirizzo %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Città" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Cittadina" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "CAP" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Nazione" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Seleziona Nazione" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Stato" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincia" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Pagamenti" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Data" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Importo" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Fattura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Metodo" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Codice" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Lunghezza" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Percorso" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Risultato" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Avvia" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fine" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s fa" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Sincronizzazione plugin e temi" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Totale" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Ultimo" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Azioni programmate" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Modulo" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Tipo di modulo" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Tipo di Cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Successivo" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non in scadenza" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Applica per diventare un affiliato" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "La tua applicazione di affiliazione per %s è stata accettata! Accedi alla tua area di affiliazione a %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Grazie per la partecipazione al nostro programma di affiliazione, valuteremo la tua richiesta durante i prossimi 14 giorni e ti contatteremo per maggiori informazioni." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Il tuo account di affiliazione è stato sospeso temporaneamente." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Grazie per la partecipazione al nostro programma di affiliazione, sfortunatamente abbiamo valutato di rifiutare la tua richiesta. Prova nuovamente fra 30 giorni." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "A causa della violazione dei nostri termini di affiliazione abbiamo deciso di bloccare temporaneamente il tuo account affiliativo. Se hai domande contatta il supporto." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Ti piace %s? Diventa il nostro ambasciatore e guadagna denaro ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Comunica nuovi clienti al nostro %s e guadagna %s di commissione per ogni vendita avvenuta!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sommario programma" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%scommissione quando un utente acquista una nuova lcienza." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Ottieni delle commissioni dal sistema automatizzato di rinnovo." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s cookie di tracciamento dopo che la prima visita per massimizzare i margini di guadagno. " + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Commissioni illimitate." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s quantità minima per il pagamento." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "I pagamenti sono in Dollari Americani e processati mensilmente da PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Ci riserviamo 30 giorni in caso di rimborsi, paghiamo le commissioni se sono più vecchie di 30 giorni." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliati" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Indirizzo email" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nome completo" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Indirizzo account email Paypal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Dove vuoi promuovere %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Inserisci il dominio del tuo sito o altri siti da dove vuoi promuovere %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Aggiungi un altro dominio" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Domini aggiuntivi" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Domini aggiuntivi dove ci sarà il modulo promozionale." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Metodi promozionali" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social network (Facebook, Twitter, ecc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Applicazioni mobile" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Siti, email e statistiche dei social network (opzionali)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Facci sapere ogni sito o statistiche social valide, es: visite uniche mensili, numero di sottoscrizioni email, follower ecc (tratteremo queste informazioni come riservate)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Come ci promuoverai?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Fornisci i dettagli su come intendi promuovere %s. (sii più esplicativo possibile)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annulla" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Diventa un affiliato" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Per favore inserisci la chiave di licenza che hai ricevuto via mail subito dopo l'acquisto:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Aggiorna licenza" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Cancella iscrizione" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Iscriviti" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Tracciamo l'utilizzo esclusivamente per rendere %s migliore, creando una migliore esperienza utente e dando priorità a nuove funzionalità, oltre a molte altre buone cose. Saremmo veramente felici di vederti cambiare idea e lasciarci continuare con il tracciamento." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Cliccando su \"Cancella iscrizione\", non invieremo più nessuna informazione da %s a %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "C'è una nuova versione di %s disponibile." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nuova versione disponibile" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Chiudi" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Invia chiave di licenza" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Inserisci qui sotto l'indirizzo email che hai usato per registrare l'aggiornamento e ti invieremo di nuovo la chiave di licenza." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Sei a un clic di distanza dall'iniziare il tuo periodo di prova gratuito di %1$s giorni per il piano %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Per essere accettato del regolamento WordPress.org, prima di attivare il periodo di prova devi accettare di condividere informazioni come il tuo utente e dati non sensibili. Permettendo a %s di inviare dati periodicamente a %s per verificare gli aggiornamenti e approvare il periodo di prova." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Attiva la licenza su tutti i siti del network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Applica su tutti i siti della rete." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Attiva le licenze su tutti i siti in attesa." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Applica su tutti i siti in attesa." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "permetti" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delega" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "salta" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Fare clic per visualizzare lo screenshot in grandi dimensioni %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Aggiornamenti Illimitati" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s rimanenti" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Ultima licenza" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annullato" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Nessuna scadenza" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nome proprietario" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Email proprietario" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID proprietario" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Sottoscrivi" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Siamo spiacenti per l'inconveniente e siamo qui per aiutarti con il tuo permesso." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contatta il supporto" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Feedback anonimo" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Disattiva" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Attiva %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Se hai un attimo, facci sapere perché %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "disattivazione in corso" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "passa a" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Invia e %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Spiegandoci il motivo ci aiuterai a migliorare." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "SI - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Salta & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Fai clic qui per usare il plugin anonimamente" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Potresti non averci fatto caso, ma non sei obbligato a condividere i tuoi dati e puoi semplicemente %s la tua partecipazione." diff --git a/freemius/languages/freemius-ja_JP.mo b/freemius/languages/freemius-ja_JP.mo new file mode 100644 index 0000000000000000000000000000000000000000..9de8a9d1f9db1bd4a88f2572fb1ecdf767e7d2e3 GIT binary patch literal 61357 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

    B}%I*x|KJGBhG;w#N>aU}?!B{FWnxZfF~?oEH> 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 Zno*KHkeg#zr5ntmihN(lg1$5Ge*iB^y9NLN literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-ja_JP.po b/freemius/languages/freemius-ja_JP.po new file mode 100644 index 0000000..2d877b2 --- /dev/null +++ b/freemius/languages/freemius-ja_JP.po @@ -0,0 +1,2511 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Odyssey <8bitodyssey+github@gmail.com>, 2016 +# Tomohyco Tsunoda, 2018 +# Takayuki Miyauchi , 2016 +# Tomohyco Tsunoda, 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Odyssey <8bitodyssey+github@gmail.com>\n" +"Language: ja_JP\n" +"Language-Team: Japanese (Japan) (http://www.transifex.com/freemius/wordpress-sdk/language/ja_JP/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK ãŒãƒ—ラグインã®ãƒ¡ã‚¤ãƒ³ãƒ•ァイルを見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ç¾åœ¨ã®ã‚¨ãƒ©ãƒ¼ã‚’æ·»ãˆã¦ sdk@freemius.com ã«é€£çµ¡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "エラー" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "より良ㄠ%sを見ã¤ã‘ã¾ã—ãŸ" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "%sã®åå‰ã¯ä½•ã§ã™ã‹ï¼Ÿ" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "%sã¯ä¸€æ™‚çš„ãªã‚‚ã®ã§ã™ã€‚ç¾åœ¨ã“ã®å•題をデãƒãƒƒã‚°ä¸­ã§ã™ã€‚" + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "無効化" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "テーマ変更" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "ãã®ä»–" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "%sã¯ã‚‚ã†ä¸è¦ã§ã™" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "短期間ã ã‘ %s㌠必è¦ã§ã™ã€‚" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ã®å½±éŸ¿ã§ã‚µã‚¤ãƒˆã‚’崩れã¾ã—ãŸ" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s ã®å‹•作ãŒçªç„¶åœæ­¢ã—ã¾ã—ãŸ" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "ã‚‚ã†æ‰•ã†ã“ã¨ãŒã§ãã¾ã›ã‚“" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr " 支払ã£ã¦ã‚‚よã„ã¨æ€ã†ä¾¡æ ¼ã¯ã„ãらã§ã™ã‹?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "è‡ªåˆ†ã®æƒ…報を共有ã—ãŸãã‚りã¾ã›ã‚“" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s ãŒå‹•作ã—ã¾ã›ã‚“ã§ã—ãŸ" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "ã©ã†ã—ãŸã‚‰å‹•作ã™ã‚‹ã‹åˆ†ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s ã¯ç´ æ™´ã‚‰ã—ã„ã®ã§ã™ãŒã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ãªã„ã‚る機能ãŒå¿…è¦ã§ã™" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "ä½•ã®æ©Ÿèƒ½ã§ã™ã‹?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s ãŒå‹•作ã—ã¦ã„ã¾ã›ã‚“" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "å°†æ¥ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãŸã‚ã«ä¿®æ­£ã§ãるよã†ã€ä½•ãŒå‹•作ã—ãªã‹ã£ãŸã®ã‹ã©ã†ã‹å…±æœ‰ã—ã¦ãã ã•ã„…" + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "探ã—ã¦ã„ãŸã‚‚ã®ã§ã¯ã‚りã¾ã›ã‚“" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "探ã—ã¦ã„ãŸã®ã¯ä½•ã§ã™ã‹?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%sãŒæœŸå¾…通りã«å‹•ãã¾ã›ã‚“ã§ã—㟠" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "何を期待ã—ã¦ã„ã¾ã—ãŸã‹?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius デãƒãƒƒã‚°" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "cURL ãŒãªã«ã‹ã€ãã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«æ–¹æ³•を知りã¾ã›ã‚“。助ã‘ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "ホスティング会社ã«é€£çµ¡ã—ã¦å•題を解決ã—ã¦ãã ã•ã„。 æ›´æ–°ãŒå®Œäº†ã—ãŸã‚‰ã€ ï¼…s ã¸ã®ãƒ•ォローアップメールãŒå±Šãã¾ã™ã€‚" + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "ã™ã°ã‚‰ã—ã„。cURL をインストールã—〠php.ini ãƒ•ã‚¡ã‚¤ãƒ«ã§æœ‰åŠ¹åŒ–ã—ã¦ãã ã•ã„。加ãˆã¦ã€php.ini 内㧠'disable_functions' ディレクティブを検索ã—ã¦ã€'curl_' ã§å§‹ã¾ã‚‹ç„¡åŠ¹åŒ–ã•れãŸãƒ¡ã‚½ãƒƒãƒ‰ã‚’削除ã—ã¦ãã ã•ã„。'phpinfo()' を使ã£ã¦æ­£å¸¸ã«èµ·å‹•ã•れãŸã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。有効化ã•れã¦ã„ã‚‹å ´åˆã¯ %s を一度無効化ã—ã€å†åº¦æœ‰åŠ¹åŒ–ã—ç›´ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "ã¯ã„ - ãŠæ§‹ã„ãªã" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "ã„ã„㈠- ã™ãã«ç„¡åŠ¹åŒ–" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "ãŠã£ã¨" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "修正ã™ã‚‹ãƒãƒ£ãƒ³ã‚¹ã‚’ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™! テクニカルスタッフã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒé€ä¿¡ã•れã¾ã—ãŸã€‚ ï¼…s ã¸ã®æ›´æ–°ãŒè¡Œã‚れるã¨ã™ãã«ã‚ãªãŸã«é€£çµ¡ã—ã¾ã™ã€‚ ã‚ãªãŸã®å¿è€ã«æ„Ÿè¬ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ã¯ã€%s ãŒç„¡ã„ã¨å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s ã¯ã€ãƒ—ラグインãŒç„¡ã„ã¨å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "予期ã—ãªã„ API エラーã§ã™ã€‚%sã®ä½œè€…ã«æ¬¡ã®ã‚¨ãƒ©ãƒ¼ã‚’連絡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã® %sã¯æœ‰åŠ¹åŒ–ã«æˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "ã‚„ã£ãŸãƒ¼" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "%s ライセンスをæŒã£ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "ヤッホー" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s ã®ç„¡æ–™è©¦ç”¨ãŒæ­£å¸¸ã«ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚ アドオンã¯ãƒ—レミアムãªã®ã§ã€è‡ªå‹•çš„ã«ç„¡åŠ¹åŒ–ã•れã¾ã—ãŸã€‚ å°†æ¥ä½¿ç”¨ã—ãŸã„å ´åˆã¯ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s ã¯ãƒ—レミアムã®ã¿ã®ã‚¢ãƒ‰ã‚ªãƒ³ã§ã™ã€‚ãã®ãƒ—ラグインを有効化ã™ã‚‹å‰ã«ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "%s ã«é–¢ã™ã‚‹è©³ç´°æƒ…å ±" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "ライセンスを購入" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "%s ã®ãƒ¡ãƒ¼ãƒ«ãƒœãƒƒã‚¯ã‚¹ã« %s ã®æœ‰åŠ¹åŒ–ã®ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã£ã¦ã„ã‚‹ã¯ãšã§ã™ã€‚%s ã®ãƒ¡ãƒ¼ãƒ«ã«è¨˜è¼‰ã•ã‚ŒãŸæœ‰åŠ¹åŒ–ãƒœã‚¿ãƒ³ã‚’ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "トライアルを開始" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "インストールを完了" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "ã‚‚ã†ã‚ã¨ã‚ãšã‹ã§ã™ - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "ã™ãã« \"%s\" 有効化を完了ã—ã¦ãã ã•ã„" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "プラグインを微調整ã—ã¾ã™ã€ %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "%s ã®ã‚¢ãƒƒãƒ—グレードãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "アドオン" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "プラグイン" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "テーマ" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "システムã§ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒæ­£ã—ã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "メールアドレスã«é–¢é€£ä»˜ã‘ã‚‰ã‚ŒãŸæœ‰åйãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã€‚ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒæ­£ã—ã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯æœ‰åŠ¹åŒ–å¾…ã¡ã§ã™ã€‚" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s ã®æœ‰åŠ¹åŒ–ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "アカウント㌠%s ãƒ—ãƒ©ãƒ³ã§æœ‰åŠ¹åŒ–ã§ãã¾ã—ãŸã€‚" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "トライアル版ã®åˆ©ç”¨ã‚’é–‹å§‹ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "%s を有効化ã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "以下ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¨ã¨ã‚‚ã«ç§ãŸã¡ã«é€£çµ¡ã‚’ãã ã•ã„。" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "アップグレード" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "トライアルを開始" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "料金表" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "アフィリエイト" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "アカウント" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "連絡" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "アドオン" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "料金表" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "サãƒãƒ¼ãƒˆãƒ•ォーラム" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "ã‚ãªãŸã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã®æ‰¿èªãŒå®Œäº†ã—ã¾ã—ãŸã€‚ã™ã”ã„ï¼" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "ãã†ã " + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã®ãƒ—ランã®ã‚¢ãƒƒãƒ—グレードãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã®æ”¯æ‰•ã„ãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "最新版をダウンロード" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "サーãƒãƒ¼ã‹ã‚‰ã‚¨ãƒ©ãƒ¼ã‚’å—ä¿¡ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "èªè¨¼ãƒ‘ラメータã®1ã¤ãŒé–“é•ã£ã¦ã„るよã†ã§ã™ã€‚ 公開éµã€ç§˜å¯†éµã€ãƒ¦ãƒ¼ã‚¶ãƒ¼IDã‚’æ›´æ–°ã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "ãµã‚€" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "ã¾ã  %s プランã®ã‚ˆã†ã§ã™ã€‚ã‚‚ã—アップグレードやプランã®å¤‰æ›´ã‚’ã—ãŸã®ãªã‚‰ã€ã“ã¡ã‚‰ã§ä½•らã‹ã®å•題ãŒç™ºç”Ÿã—ã¦ã„るよã†ã§ã™ã€‚申ã—訳ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "トライアル" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "アカウントをアップグレードã—ã¾ã—ãŸãŒã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’åŒæœŸã—よã†ã¨ã™ã‚‹ã¨ãƒ—ラン㌠%s ã®ã¾ã¾ã§ã™ã€‚" + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "ã“ã¡ã‚‰ã§ç§ãŸã¡ã«é€£çµ¡ã‚’ã¨ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "プランã®ã‚¢ãƒƒãƒ—ã‚°ãƒ¬ãƒ¼ãƒ‰ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "プラン㮠%s ã¸ã®å¤‰æ›´ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ç„¡æ–™ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®%s ã¯å¼•ãç¶šã利用ã§ãã¾ã™ã€‚" + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ %1$s %3$sã«é‚ªé­”ã•れãšã«åˆ©ç”¨ã‚’継続ã™ã‚‹ã«ã¯ï¼Œä»Šã™ã%2$sアップグレードを行ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "ライセンスã¯ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚ã‚‚ã—ãれãŒé–“é•ã„ã ã¨æ€ã†ãªã‚‰ã‚µãƒãƒ¼ãƒˆã«é€£çµ¡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã¯æœ‰åŠ¹æœŸé™ãŒãれã¾ã—ãŸã€‚%s ã®æ©Ÿèƒ½ã‚’引ãç¶šã利用ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ãŸã ã—ã€ã‚¢ãƒƒãƒ—デートやサãƒãƒ¼ãƒˆã‚’ã†ã‘ã‚‹ã«ã¯ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’アップデートã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "フリートライアル期間ãŒçµ‚了ã—ã¾ã—ãŸã€‚ç„¡æ–™ã§ä½¿ãˆã‚‹æ©Ÿèƒ½ã¯å¼•ãç¶šã利用å¯èƒ½ã§ã™ã€‚" + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "フリートライアル期間ãŒçµ‚了ã—ã¾ã—ãŸã€‚%1$s %3$sã«é‚ªé­”ã•れãšã«åˆ©ç”¨ã‚’継続ã™ã‚‹ã«ã¯ï¼Œä»Šã™ã %2$s ã®ã‚¢ãƒƒãƒ—グレードを行ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "ã‚µã‚¤ãƒˆã¯æœ‰åйãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’æŒã£ã¦ã„ãªã„よã†ã§ã™ã€‚" + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "ライセンスã®ç„¡åŠ¹åŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "ライセンスã®ç„¡åŠ¹åŒ–ãŒå®Œäº†ã—ã¾ã—ãŸã€‚%s ãƒ—ãƒ©ãƒ³ã«æˆ»ã‚Šã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "ã™ã§ã«%sをトライアルモードã§åˆ©ç”¨ä¸­ã§ã™ã€‚" + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "以å‰ã™ã§ã«è©¦ç”¨ç‰ˆã‚’利用ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "%s プランã¯å­˜åœ¨ã—ãªã„ãŸã‚ã€è©¦ç”¨ã‚’é–‹å§‹ã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "%s プランã«ã¯ãƒˆãƒ©ã‚¤ã‚¢ãƒ«æœŸé–“ã¯ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "%sã®ãƒ—ランã«ã¯ãƒˆãƒ©ã‚¤ã‚¢ãƒ«æœŸé–“ã¯ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "ã™ã§ã«ãƒˆãƒ©ã‚¤ã‚¢ãƒ«ãƒ¢ãƒ¼ãƒ‰ã§ã¯ãªã„よã†ãªã®ã§ã€ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“ :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "トライアルã®ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã«ä¸€æ™‚çš„ãªå•題ãŒã‚りã¾ã—ãŸã€‚数分後ã«å†åº¦ãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "%s ã®ãƒ•リートライアルã¯ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ %s をリリースã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "%s をダウンロードã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "最新㮠%s ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã“ã¡ã‚‰ã§ã™ã€‚" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "æ–°è¦" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "最新版をå–å¾—ã§ãã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "ã™ã¹ã¦å®Œç’§ã§ã™!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "%s ã«ç¢ºèªãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ã‚‚ã—5分以内ã«ãれãŒå±Šã‹ãªã„å ´åˆã€è¿·æƒ‘メールボックスを確èªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "サイトã®ã‚ªãƒ—ãƒˆã‚¤ãƒ³ã«æˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "ã™ã”ã„!" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "使用データを追跡ã§ãるよã†è¨±å¯ã—ã¦ãれãŸã“ã¨ã§ã€%s をより良ãã™ã‚‹ãŸã‚ã®æ‰‹åŠ©ã‘ã«æ„Ÿè¬è‡´ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "ã‚‚ã†%s上ã®%sã‹ã‚‰%sã¸ã®ãƒ‡ãƒ¼ã‚¿é€ä¿¡ã¯è¡Œã„ã¾ã›ã‚“。" + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "メールボックスを確èªã—ã¦ãã ã•ã„。所有権ã®å¤‰æ›´ã‚’確èªã™ã‚‹ã«ã¯ã€%s ã§ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ セキュリティ上ã®ç†ç”±ã‹ã‚‰ã€æ¬¡ã®15分以内ã«å¤‰æ›´ã‚’確èªã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ é›»å­ãƒ¡ãƒ¼ãƒ«ãŒè¦‹ã¤ã‹ã‚‰ãªã„å ´åˆã¯ã€è¿·æƒ‘メールフォルダを確èªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "所有権ã®å¤‰æ›´ã‚’確èªã—ã¦ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚ %s ã«æ‰¿èªãƒ¡ãƒ¼ãƒ«ãŒé€ä¿¡ã•れã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s ã¯æ–°ã—ã„オーナーã§ã™ã€‚" + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "ãŠã‚ã§ã¨ã†" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "メールアドレスã®ã‚¢ãƒƒãƒ—デートを完了ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã™ã§ã«åŒã˜ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ç™»éŒ²ã—ã¦ã„るよã†ã§ã™ã€‚" + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "%sã®æ‰€æœ‰æ¨©ã‚’%sã¸è­²ã‚ŠãŸã„å ´åˆã¯ã€æ‰€æœ‰æ¨©ã®å¤‰æ›´ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "オーナーを変更" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "メールアドレスã®ã‚¢ãƒƒãƒ—デートãŒå®Œäº†ã—ã¾ã—ãŸã€‚ã¾ã‚‚ãªã確èªãƒ¡ãƒ¼ãƒ«ãŒå±Šãã¾ã™ã€‚" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "フルãƒãƒ¼ãƒ ã‚’入力ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "åå‰ã®ã‚¢ãƒƒãƒ—ãƒ‡ãƒ¼ãƒˆãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "%s ã®ã‚¢ãƒƒãƒ—ãƒ‡ãƒ¼ãƒˆãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã«é–¢ã™ã‚‹æƒ…å ±ã¯ã€å¤–部サーãƒãƒ¼ã‹ã‚‰å–å¾—ã•れã¾ã™ã€‚" + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "警告" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "ヘイ" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "%s ã¯ã©ã†ã§ã™ã‹? ç§ãŸã¡ã®å…¨ã¦ã® %s ã®ãƒ—レミアム機能をãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "%s 日以内ã§ã‚れã°ã„ã¤ã§ã‚‚キャンセルã§ãã¾ã™ã€‚" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "クレジットカードã¯å¿…è¦ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "フリートライアルを開始" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "ã“ã‚“ã«ã¡ã¯ã€‚%sã«ã‚¢ãƒ•ィリエイトプログラムãŒã‚ã‚‹ã®ã¯ã”存知ã§ã—ãŸã‹ï¼Ÿã€€%sãŒãŠå¥½ããªã‚‰ã€ç§ãŸã¡ã®ã‚¢ãƒ³ãƒã‚µãƒ€ãƒ¼ã«ãªã£ã¦å ±é…¬ã‚’å¾—ã¾ã—ょã†ï¼" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "詳細ã¯ã“ã¡ã‚‰" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "ライセンスを有効化" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "ライセンスを変更" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "オプトアウト" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "オプトイン" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "アップグレードを完了ã™ã‚‹ã«ã¯ä»¥ä¸‹ã®æ‰‹é †ã‚’完了ã•ã›ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "最新㮠%s をダウンロード" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "ダウンロードã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’アップロードã—ã¦æœ‰åŠ¹åŒ–" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "ã‚¢ãƒƒãƒ—ãƒ­ãƒ¼ãƒ‰ã¨æœ‰åŠ¹åŒ–ã®æ–¹æ³•" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sã“ã“をクリックã—ã¦%s ライセンスを有効化ã—ãŸã„ã‚µã‚¤ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "自動インストールã¯ã‚ªãƒ—トインã—ãŸãƒ¦ãƒ¼ã‚¶ã®ã¿ã§å‹•作ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "モジュール ID ãŒä¸æ­£ã§ã™" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã™ã§ã«æœ‰åйã«ãªã£ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãる有効ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹æŒã£ã¦ã„ã¾ã›ã‚“。" + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "プラグインã¯ãƒ—レミアムコードãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ãªã„「サービスウェアã€ã§ã™ã€‚" + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "プレミアムアドオンãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã™ã§ã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«æ¸ˆã¿ã§ã™ã€‚" + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "æœ‰æ–™ã®æ©Ÿèƒ½ã‚’表示ã™ã‚‹" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "%sã¨ã‚¢ãƒ‰ã‚ªãƒ³ã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "%sã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "%sã®æ”¹å–„ã«å½¹ç«‹ã¤ä½¿ç”¨çжæ³ã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã«ã™ã§ã«ã‚ªãƒ—トインã—ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "プロダクトã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "ãƒ—ãƒ­ãƒ€ã‚¯ãƒˆã®æ”¹å–„ã«å½¹ç«‹ã¤ä½¿ç”¨çжæ³ã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã«ã™ã§ã«ã‚ªãƒ—トインã—ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%sã¨ãã®ã‚¢ãƒ‰ã‚ªãƒ³" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "プロダクト" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "ã¯ã„" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ©Ÿèƒ½ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用コンテンツやオファーをé€ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "ã„ã„ãˆ" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ©Ÿèƒ½ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用コンテンツやオファーを%sé€ã‚‰ãªã„ã§ãã ã•ã„%s。" + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "セキュリティや機能ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用用コンテンツã€ãŠã‚ˆã³ã‚ªãƒ•ァーã«ã¤ã„ã¦ãŠå•ã„åˆã‚ã›ã‚’希望ã•れる場åˆã¯ã€ãŠçŸ¥ã‚‰ã›ãã ã•ã„。" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "ライセンスキーãŒç©ºã§ã™ã€‚" + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "ライセンスを更新" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "インストール中プラグイン: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ ã«æŽ¥ç¶šã§ãã¾ã›ã‚“。視覚情報を確èªã—ã¦ãã ã•ã„。" + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "リモートプラグインパッケージã«ã¯ã€ç›®çš„ã®ã‚¹ãƒ©ãƒƒã‚°ã‚’å«ã‚€ãƒ•ォルダãŒå«ã¾ã‚Œã¦ã„ãªã„ãŸã‚ã€ãƒªã­ãƒ¼ãƒ ãŒæ©Ÿèƒ½ã—ã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "購入" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "無料㮠%s ã‚’é–‹å§‹" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®æ›´æ–°ã‚’今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "今ã™ã更新をインストール" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "最新ã®ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’ダウンロード" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "最新版をダウンロード" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "ã“ã®ã‚¢ãƒ‰ã‚ªãƒ³ã‚’有効化" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’有効化" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "有効化" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "説明" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "インストール" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "スクリーンショット" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "変更履歴" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "レビュー" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "ãã®ä»–ã®è¨˜è¿°" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "機能 & 料金" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "プラグインã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s プラン" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "ベスト" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "月" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "年次" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "ライフタイム" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s ã¸ã®è«‹æ±‚" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "毎年" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "一度" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "シングルサイトライセンス" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "無制é™ãƒ©ã‚¤ã‚»ãƒ³ã‚¹" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "%sサイトã¾ã§" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "月" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "å¹´" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "料金" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "%s ã‚’ä¿å­˜" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "%s ã®æ‹˜æŸã¯ã‚りã¾ã›ã‚“。ã„ã¤ã§ã‚‚キャンセルã§ãã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "無料㮠%s ã®å¾Œã¯ã€ã‚ãšã‹ %s ã ã‘ãŠæ”¯æ‰•ãã ã•ã„。" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "詳細" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "作者" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "最終更新" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s å‰" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "å¿…è¦ãª WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%sã¾ãŸã¯ãれ以上" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "äº’æ›æ€§ã®ã‚る最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "ダウンロード済ã¿" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s回" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s回" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org ã®ãƒ—ラグインページ" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "プラグインã®ãƒ›ãƒ¼ãƒ ãƒšãƒ¼ã‚¸" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "ã“ã®ãƒ—ラグインã«å¯„付ã™ã‚‹" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "レーティングã®å¹³å‡" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "%sを基ã«" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s評価" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s評価" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%sスター" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%sスター" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "クリックã—ã¦%sã®è©•価をã—ã¦ã„るレビューを観る" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "コントリビューター" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "警告" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "ã“ã®ãƒ—ラグインã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れ㟠WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã¯æ¤œè¨¼ã•れã¦ã„ã¾ã›ã‚“。" + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "ã“ã®ãƒ—ラグインã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れ㟠WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«äº’æ›æ€§ãŒã‚りã¾ã›ã‚“。" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "有料アドオン㯠Freemius ã«ãƒ‡ãƒ—ロイã•れã¦ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "アドオン㌠WordPress.org ã‹ Freemius ã«ãƒ‡ãƒ—ロイã•れã¦ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (%s) ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "æ–°ã—ã„フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ (%s) ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "最新版ãŒã‚¤ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "最新ã®ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "トライアルをキャンセルã™ã‚‹ã¨ã™ãã«ã™ã¹ã¦ã®ãƒ—レミアム機能ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ãŒã§ããªããªã‚Šã¾ã™ã€‚本当ã«å®Ÿè¡Œã—ã¾ã™ã‹?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "ä¸€åº¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœŸé™ãŒåˆ‡ã‚Œã‚‹ã¨ã€ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®åˆ©ç”¨ã¯å¯èƒ½ã§ã™ãŒã€%sã®æ©Ÿèƒ½ã‚’使ã†ã“ã¨ãŒã§ããªããªã‚Šã¾ã™ã€‚" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "%s プランを有効化" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "%s ã«è‡ªå‹•æ›´æ–°" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "%s ã§æœŸé–“終了" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’åŒæœŸ" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "トライアルをキャンセル" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "プラン変更" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "アップグレード" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "ダウングレード" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "ç„¡æ–™" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "プラン" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "フリートライアル" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "アカウント詳細" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "アカウントを削除ã™ã‚‹ã¨è‡ªå‹•的㫠%s プランライセンスãŒç„¡åйã«ãªã‚Šã€ä»–ã®ã‚µã‚¤ãƒˆã§ä½¿ã†ã“ã¨ãŒã§ãã¾ã™ã€‚å®šæœŸã®æ”¯æ‰•ã„も終了ã—ãŸã„å ´åˆã¯ã€\"キャンセル\"ボタンをクリックã—ã€ã¾ãšã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’\"ダウングレード\"ã—ã¦ãã ã•ã„。本当ã«å‰Šé™¤ã‚’続行ã—ã¦ã‚‚ã„ã„ã§ã™ã‹?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "削除ã¯ä¸€æ™‚çš„ãªã‚‚ã®ã§ã¯ã‚りã¾ã›ã‚“。本当ã«%sãŒå¿…è¦ãªããªã£ãŸæ™‚ã«è¡Œã£ã¦ãã ã•ã„。" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "アカウントを削除" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "ライセンスを無効化" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "本当ã«ç¶šè¡Œã—ã¦ã„ã„ã§ã™ã‹?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "サブスクリプションをキャンセルã™ã‚‹" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "åŒæœŸ" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "åå‰" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ユーザー ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "サイト ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "ID ãŒã‚りã¾ã›ã‚“" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "公開éµ" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "秘密éµ" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "秘密éµãŒã‚りã¾ã›ã‚“" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "トライアル" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "ライセンスキー" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "未èªè¨¼" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "期é™åˆ‡ã‚Œ" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/account.php:504 +msgid "Free version" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "èªè¨¼ãƒ¡ãƒ¼ãƒ«" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "%s ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’ダウンロード" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "表示" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "自分㮠%s ã¯ãªã‚“ã§ã™ã‹?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "編集" + +#: templates/account.php:588 +msgid "Sites" +msgstr "サイト数" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "使‰€ã§æ¤œç´¢ã™ã‚‹" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "使‰€" + +#: templates/account.php:610 +msgid "License" +msgstr "ライセンス" + +#: templates/account.php:611 +msgid "Plan" +msgstr "プラン" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "ライセンス" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "éžè¡¨ç¤º" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "ライセンスを無効化ã™ã‚‹ã¨ã™ã¹ã¦ã®ãƒ—レミアム機能ãŒä½¿ãˆãªããªã‚Šã¾ã™ãŒã€ä»–ã®ã‚µã‚¤ãƒˆã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚本当ã«å®Ÿè¡Œã—ã¾ã™ã‹ï¼Ÿ" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "詳細を表示" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "アドオンリストを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãŠãらãé‹å–¶å´ã®å•題ã«ãªã‚Šã¾ã™ã®ã§ã€ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ãŠè©¦ã—ãã ã•ã„。" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "有効" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "å´ä¸‹" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%sç§’" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "自動インストール" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "%sã‹ã‚‰ %s (有料版) ã®è‡ªå‹•ダウンロードã¨è‡ªå‹•インストールãŒ%sã§é–‹å§‹ã—ã¾ã™ã€‚手動ã§è¡Œã†å ´åˆã¯ä»Šã™ãã«ã‚­ãƒ£ãƒ³ã‚»ãƒ«ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "インストールプロセスãŒé–‹å§‹ã•ã‚Œã€æ•°åˆ†ã§å®Œäº†ã—ã¾ã™ã€‚完了ã¾ã§ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„。ページã®ãƒªãƒ•レッシュãªã©ã¯è¡Œã‚ãªã„ã§ãã ã•ã„。" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "インストールをキャンセルã™ã‚‹" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI コンプライアント" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "ãŠãŠã„ %s ã•ã‚“ã€" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "許å¯ã—ã¦ç¶šã‘ã‚‹" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "有効化メールをå†é€ä¿¡" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "ã‚りãŒã¨ã† $s ã•ã‚“!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "åŒæ„ã—ã¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効化" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "%s を購入ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚ã¯ã˜ã‚ã«ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã¦ãã ã•ã„:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。" + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。" + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。ã“れをスキップã—ã¦ã‚‚%1$sã¯ã‚‚ã¡ã‚ん動作ã—ã¾ã™ã€‚" + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。ã“れをスキップã—ã¦ã‚‚%1$sã¯ã‚‚ã¡ã‚ん動作ã—ã¾ã™ã€‚" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Freeminus ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¬ãƒ™ãƒ«ã®ã‚¤ãƒ³ãƒ†ã‚°ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’ã”紹介ã§ãã‚‹ã“ã¨ã«èˆˆå¥®ã—ã¦ã„ã¾ã™ã€‚" + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "アップデートã®å‡¦ç†ä¸­ã«%dサイトãŒãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒä¿ç•™ä¸­ã§ã‚ã‚‹ã“ã¨ã‚’検知ã—ã¾ã—ãŸã€‚" + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "ã“れらã®ã‚µã‚¤ãƒˆã§%sを使ã†å ´åˆã¯ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã€ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ã‚·ãƒ§ãƒ³ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%sã®æœ‰æ–™æ©Ÿèƒ½" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "ã¾ãŸã¯ã€ä»Šã™ãスキップã—ã¦ã€%sã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¬ãƒ™ãƒ«ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãƒšãƒ¼ã‚¸ã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚" + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "アップデートã®å‡¦ç†ä¸­ã«ã€ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯å†…ã®%dサイトãŒå¯¾å¿œå¾…ã¡ã«ãªã£ã¦ã„ã‚‹ã“ã¨ã‚’検知ã—ã¾ã—ãŸã€‚" + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "ライセンスキー" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "ライセンスキーã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã‹?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "スキップ" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "サイト管ç†è€…ã«å§”ä»»ã™ã‚‹" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "決定をサイトã®ç®¡ç†è€…ã«å§”ä»»ã™ã‚‹ã«ã¯ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãã ã•ã„。" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "プロフィール概è¦" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "åå‰ã¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "サイト概è¦" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "サイト URLã€WP ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã€PHP infoã€ãƒ—ラグインã¨ãƒ†ãƒ¼ãƒž" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "管ç†è€…通知" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "æ›´æ–°ã€ç™ºè¡¨ã€ãƒžãƒ¼ã‚±ãƒ†ã‚£ãƒ³ã‚°ã€ã‚¹ãƒ‘ムãªã—" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "ç¾åœ¨%sイベント" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "有効化ã€ç„¡åŠ¹åŒ–ã€ã‚¢ãƒ³ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "ニュースレター" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$sã¯ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨ã‚¢ãƒ—デートã€ãã—ã¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®çŠ¶æ…‹ã‚’ç¢ºèªã™ã‚‹ãŸã‚ã€å®šæœŸçš„ã«%2$sã¸ãƒ‡ãƒ¼ã‚¿ã‚’é€ä¿¡ã—ã¾ã™ã€‚" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "付与ã•れã¦ã„るパーミッションã¯ä½•ã§ã™ã‹?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’ãŠæŒã¡ã§ã¯ã‚りã¾ã›ã‚“ã‹?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "ライセンスキーã¯ãŠæŒã¡ã§ã™ã‹?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "プライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "利用è¦ç´„" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "メールé€ä¿¡ä¸­" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "有効化中" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "連絡" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "オフ" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "オン" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "デãƒãƒƒã‚°" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "アクション" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "ã»ã‚“ã¨ã†ã«å…¨ã¦ã® Freemius データを削除ã—ã¾ã™ã‹?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "å…¨ã¦ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’削除" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "API キャッシュをクリア" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "アップデートã®ãƒˆãƒ©ãƒ³ã‚¸ã‚¨ãƒ³ãƒˆã‚’クリアーã«ã™ã‚‹" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "サーãƒãƒ¼ã‹ã‚‰ã®ãƒ‡ãƒ¼ã‚¿ã‚’åŒæœŸ" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "DB オプションを読ã¿è¾¼ã‚€" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "DB オプションを設定ã™ã‚‹" + +#: templates/debug.php:182 +msgid "Key" +msgstr "キー" + +#: templates/debug.php:183 +msgid "Value" +msgstr "値" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK ã®ãƒ‘ス" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "モジュールã®ãƒ‘ス" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "有効" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "プラグイン" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "テーマ" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "スラッグ" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "タイトル" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius ステータス" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ–ログ" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¦ãƒ¼ã‚¶" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "接続" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "ブロック" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¢ãƒƒãƒ—グレードをシミュレートã™ã‚‹" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%sインストール" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "サイト数" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "ブログ ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "削除" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "モジュールã®ã‚¢ãƒ‰ã‚ªãƒ³%s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "ユーザー" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "èªè¨¼æ¸ˆã¿" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%sラインセス" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "プラグイン ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "プラン ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "クォータ" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "有効化済ã¿" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "ブロッキング" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "期é™åˆ‡ã‚Œ" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "デãƒãƒƒã‚°ãƒ­ã‚°" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "ã™ã¹ã¦ã®ã‚¿ã‚¤ãƒ—" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "ã™ã¹ã¦ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆ" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "ファイル" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "機能" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "プロセス ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "ロガー" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "メッセージ" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "フィルター" + +#: templates/debug.php:587 +msgid "Download" +msgstr "ダウンロード" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "タイプ" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "タイムスタンプ" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "外部ドメインã§å®Ÿè¡Œä¸­ã®ã‚»ã‚­ãƒ¥ã‚¢ãª HTTPS %sページ" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "サãƒãƒ¼ãƒˆ" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "リクエスト数" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "æ›´æ–°" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "請求書" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "商å·" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "税金 / VAT ID" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "使‰€æ¬„ %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "市" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "町" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / 郵便番å·" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "国" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "å›½ã‚’é¸æŠž" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "å·ž" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "県・州・çœ" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "支払ã„" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "日付" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "ç·é¡" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "請求書" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "メソッド" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "コード" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "é•·ã•" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "パス" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "本文" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "çµæžœ" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "é–‹å§‹" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "終了" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "ã‚°" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "%s 内" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s å‰" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "ç§’" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "プラグインã¨ãƒ†ãƒ¼ãƒžã‚’åŒæœŸ" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "トータル" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "最終" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "スケジュール Cron" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "モジュール" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "モジュールタイプ" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron タイプ" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "次" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "期é™ã®ãªã„" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "アフィリエイトã«å¿œå‹Ÿã™ã‚‹" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "%sã®ã‚¢ãƒ•ィリエイト申請ã¯å—ç†ã•れã¾ã—ãŸï¼ã€€æ¬¡ã®ãƒªãƒ³ã‚¯ã‹ã‚‰ã‚¢ãƒ•ィリエイトエリアã«ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„:%s" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "アフィリエイトプログラムã«å¿œå‹Ÿã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚14日以内ã«ãŠç”³ã—è¾¼ã¿è©³ç´°ã‚’レビューã—ã€æ”¹ã‚ã¦ã”連絡ã„ãŸã—ã¾ã™ã€‚" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "アフィリエイトアカウントã¯ä¸€æ™‚çš„ã«åœæ­¢ã•れã¾ã—ãŸã€‚" + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "アフィリエイトアカウントã«å¿œå‹Ÿã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚残念ãªãŒã‚‰ç¾æ™‚点ã§ã¯ç”³è«‹ã‚’å—ç†ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚30æ—¥å¾Œã«æ”¹ã‚ã¦ãŠç”³è¾¼ã¿ãã ã•ã„。" + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "アフィリエイトè¦ç´„é•åã«ã‚ˆã‚Šã€ã‚¢ãƒ•ィリエイトアカウントを一時的ã«å‡çµã•ã›ã¦ã„ãŸã ãã¾ã—ãŸã€‚ã”質å•ç­‰ãŒã‚りã¾ã—ãŸã‚‰ã€ã‚µãƒãƒ¼ãƒˆã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "%sã¯æ°—ã«å…¥ã‚Šã¾ã—ãŸã‹ï¼Ÿã€€ã‚¢ãƒ³ãƒã‚µãƒ€ãƒ¼ã«ãªã£ã¦å ±é…¬ã‚’å¾—ã¾ã—ょㆠ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "æ–°è¦ã‚«ã‚¹ã‚¿ãƒžãƒ¼ã«ç§ãŸã¡ã®%sを紹介ã—ã¦ã€å£²ã‚Šä¸Šã’ã”ã¨ã«%sã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã‚’å¾—ã¾ã—ょã†" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "プログラム概è¦" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "ã‚«ã‚¹ã‚¿ãƒžãƒ¼ãŒæ–°è¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹ã”ã¨ã«%sã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ãŒç™ºç”Ÿã—ã¾ã™ã€‚" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "サブスクリプションã®è‡ªå‹•æ›´æ–°ã§ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã‚’å¾—ã¾ã—ょã†ã€‚" + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%såˆå›žã®è¨ªå•後ã€ã‚¯ãƒƒã‚­ãƒ¼ã‚’トラッキングã—ã¦åŽç›Šã®å¯èƒ½æ€§ã‚’最大化ã—ã¾ã—ょã†ã€‚" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "無制é™ã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã€‚" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%sãŠæ”¯æ‰•ã„ã®æœ€ä½Žé‡‘é¡" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "ãŠæ”¯æ‰•ã„㯠USD ã‹ã¤ PayPal çµŒç”±ã§æ¯Žæœˆè¡Œã‚れã¾ã™ã€‚" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "30日間ã®è¿”金期間ãŒã‚ã‚‹ãŸã‚ã€ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã®ãŠæ”¯æ‰•ã„ã¯30日以é™ã«ãªã‚Šã¾ã™ã€‚" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "アフィリエイト" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "メールアドレス" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "フルãƒãƒ¼ãƒ " + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal アカウントã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "%sã®ãƒ—ロモーションを行ã†ã‚µã‚¤ãƒˆã¯ã©ã“ã§ã™ã‹ï¼Ÿ" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "%sã®ãƒ—ロモーションを行ã†äºˆå®šã®ã‚ãªãŸã®ã‚µã‚¤ãƒˆã‚„ä»–ã®ã‚µã‚¤ãƒˆã®ãƒ‰ãƒ¡ã‚¤ãƒ³åを入力ã—ã¦ãã ã•ã„。" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "ドメインåを追加ã™ã‚‹" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "追加ã®ãƒ‰ãƒ¡ã‚¤ãƒ³å" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "プロダクトフォームã®ãƒžãƒ¼ã‚±ãƒ†ã‚£ãƒ³ã‚°ã‚’行ã†è¿½åŠ ãƒ‰ãƒ¡ã‚¤ãƒ³å。" + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "プロモーション方法" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "ソーシャルメディア(Facebookã€Twitterã€ãã®ä»–)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "モãƒã‚¤ãƒ«ã‚¢ãƒ—リケーション" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "ウェブサイトã€Email ã¾ãŸã¯ã‚½ãƒ¼ã‚·ãƒ£ãƒ«ãƒ¡ãƒ‡ã‚£ã‚¢ã®çµ±è¨ˆ (オプション)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "関係ã®ã‚るウェブサイトやソーシャルメディアã®çµ±è¨ˆã‚’æä¾›ã—ã¦ãã ã•ã„。例: ã‚µã‚¤ãƒˆã®æœˆé–“訪å•者数ã€Emailã®è³¼èª­è€…æ•°ã€ãƒ•ォロワー数等 (機密情報ã¨ã—ã¦å–り扱ã„ã¾ã™)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "ã©ã®ã‚ˆã†ã«æˆ‘々をプロモートã—ã¾ã™ã‹ï¼Ÿ" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "ã©ã®ã‚ˆã†ã«%sをプロモートã™ã‚‹ã¤ã‚‚りãªã®ã‹ã€è©³ç´°ã‚’ãŠçŸ¥ã‚‰ã›ãã ã•ã„ (ã§ãã‚‹ã ã‘具体的ã«ãŠé¡˜ã„ã—ã¾ã™)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "キャンセル" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "アフィリエイトã«ãªã‚‹" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "購入後ã™ãã«ãƒ¡ãƒ¼ãƒ«ã§å—ã‘å–ã£ãŸãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã¦ãã ã•ã„:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "ライセンスを更新" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "オプトアウト" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "オプトイン" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "使用ã®è¿½è·¡ã¯ %s をより良ãã™ã‚‹åç›®ã®ä¸‹ã«è¡Œã‚れã¦ã„ã¾ã™ã€‚ユーザー体験をより良ãã—ã€æ–°æ©Ÿèƒ½ã«å„ªå…ˆé †ä½ã‚’ã¤ã‘ã‚‹ãŸã‚ãªã©ã«ä½¿ã„ã¾ã™ã€‚追跡を続ã‘ã¦ã‚‚よã„ã¨å†è€ƒã—ã¦ãれるãªã‚‰æœ¬å½“ã«æ„Ÿè¬è‡´ã—ã¾ã™ã€‚" + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "\"オプトアウト\"をクリックã™ã‚‹ã“ã¨ã§ã€ã‚‚ㆠ%s ã‹ã‚‰ %s ã¸ã®ãƒ‡ãƒ¼ã‚¿ã®é€ä¿¡ã¯è¡Œã„ã¾ã›ã‚“。" + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "%sã®å…¥æ‰‹å¯èƒ½ãªæ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚りã¾ã™" + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚りã¾ã™" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "å´ä¸‹" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "ライセンスキーをé€ä¿¡" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "アップグレードã«ä½¿ç”¨ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’下ã«å…¥åŠ›ã—ã¦ãã ã•ã„。ãã†ã™ã‚Œã°ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’ãŠé€ã‚Šã—ã¾ã™ã€‚" + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "%2$s プランã®%1$s日間ã®ãƒ•リートライアルを開始ã™ã‚‹ã¾ã§ã‚ã¨ãƒ¯ãƒ³ã‚¯ãƒªãƒƒã‚¯ã§ã™ã€‚" + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "WordPress.orgã®ã‚¬ã‚¤ãƒ‰ãƒ©ã‚¤ãƒ³ã«æº–æ‹ ã™ã‚‹ãŸã‚ã€ãƒˆãƒ©ã‚¤ã‚¢ãƒ«ã‚’é–‹å§‹ã™ã‚‹å‰ã«ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨é‡è¦ã§ãªã„サイト情報ã®ã‚ªãƒ—ãƒˆã‚¤ãƒ³ã€æ›´æ–°ã®ç¢ºèªã‚„トライアルã®çŠ¶æ…‹ç¢ºèªã®ãŸã‚ã«%sãŒ%sã«å¯¾ã—ã¦å®šæœŸçš„ã«ãƒ‡ãƒ¼ã‚¿ã‚’é€ä¿¡ã™ã‚‹è¨±å¯ã‚’得るよã†ã«è¨­å®šã—ã¦ãã ã•ã„。" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "プレミアム" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ä¸Šã«ã‚ã‚‹ã™ã¹ã¦ã®ã‚µã‚¤ãƒˆã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã€‚" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ä¸Šã«ã‚ã‚‹ã™ã¹ã¦ã®ã‚µã‚¤ãƒˆã«å¯¾ã—ã¦å映ã•ã›ã‚‹ã€‚" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "ä¿ç•™ä¸­ã®ã‚µã‚¤ãƒˆã™ã¹ã¦ã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã€‚" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "ä¿ç•™ä¸­ã®ã‚µã‚¤ãƒˆã™ã¹ã¦ã«å映ã•ã›ã‚‹ã€‚" + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "許å¯" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "代表" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "スキップ" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "クリックã—ã¦ãƒ•ルサイズã®ã‚¹ã‚¯ãƒªãƒ¼ãƒ³ã‚·ãƒ§ãƒƒãƒˆã‚’見る %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "無制é™ã®ã‚¢ãƒƒãƒ—デート" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "ã‚㨠%s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "最新ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "キャンセル" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "有効期é™ãªã—" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "所有者å" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "所有者㮠Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "オーナー ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "サブスクリプション" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "ã”迷惑をãŠã‹ã‘ã—ã¦ã™ã„ã¾ã›ã‚“ã€‚ã‚‚ã—æ©Ÿä¼šã‚’ã„ãŸã ã‘ãŸã‚‰ãŠæ‰‹ä¼ã„ã‚’ã—ã¾ã™ã€‚" + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "サãƒãƒ¼ãƒˆã«é€£çµ¡" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "匿åã®ãƒ•ィードãƒãƒƒã‚¯" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "無効化" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "%sを有効化ã™ã‚‹" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "ãŠæ™‚é–“ãŒã‚れã°ã€ãªãœ%sã™ã‚‹ã®ã‹ç†ç”±ã‚’æ•™ãˆã¦ãã ã•ã„。" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "無効化中" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "変更中" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "é€ä¿¡ã¨%s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "改善ã§ãるよã†ã€ã©ã†ã‹ç†ç”±ã‚’æ•™ãˆã¦ãã ã•ã„。" + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "ã¯ã„" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "スキップã¨%s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "匿åã§ãƒ—ラグインを使用ã™ã‚‹ã«ã¯ã“ã¡ã‚‰ã‚’クリック" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "見逃ã—ã¦ã„ãŸã‹ã‚‚ã—れã¾ã›ã‚“ãŒã€ã©ã‚“ãªæƒ…報も共有ã™ã‚‹å¿…è¦ã¯ãªãã€ã‚ªãƒ—トインを $s ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ " diff --git a/freemius/languages/freemius-nl_NL.mo b/freemius/languages/freemius-nl_NL.mo new file mode 100644 index 0000000000000000000000000000000000000000..d8f863fb21e87ceee00b0410896528b63476f70a GIT binary patch literal 55531 zcmeIb37Dl-b?^PEWrHA~%qYs+4b=m5RX3Ap5oxQtdMN0jpn3q*kW+Qusyba~D9_N< z)zLU$j64T(x{z=2#ZUMFxi-o&6{KBpwt;nSFS6Jz*dV(#z1*52=VPgOOb zCii>p^IRY4f4$Ql)?RDvwbovHpRb?X_u7R2{n8_o9{|q*&!ExAfR}+Mftx@*-xKcF!DG2T1ggK!2OkH%0z4Ib zBX}BkXSn}KkS-?og9G5dgL-e^eDBw0P|tUQCxR_d&tC+py|;ks@9m)4btiZ!_C z4?GdP98^EA1Wy640sFwIaDNt5y)97V@RD%>fbBDQ@~rnvmXJUg0JHG^DwLG{bhu{4NifQ@Bar>yZ$NQ@s~M0R)Q+$d{F#$ z1t@wx8&vxizz={g25-^xrzXiW!7qVR;6+bMl1GED2Q@Bl0ww3~0Ivh@1jUEP5BYmF zP;%b_)y|tiefPTo-ws~R^`C&E(?5dZmj}R~g8v@w|CE>1zDNC%)9J*3PXtAuwctVU zvT*$tQ2cWzC_cCwgv80cAVZZr01iXP3!d)us9k|>R9qQ1rPT6yF>I zp8&oJTnF9`LYm}0Z~#2v8D8Gy;5gTNz%#%<0M~$bgG>p@-+;ToZ-bCJxnwv=rok#W z2i^ys1#aE!bi4sv;5q}>H3h>k5G2owt>-#~C=Qlu=|1aVGiXDD^Lcp^?jYmJI{%itI2S-5l z>zM&(z)x}A0sjcRVw5=){1*5M@UvHXdk$RX?SCe?lKamA&jxP>MTd8Ts^@O-k>E$b zQ^9+{M}zl+{ovQZ3&6ge&Sx7z$=420?b;7+04G7w;byP~-U5o>9}N?VE-wa;0bc=% z-){z0&%40m!S{p5f*%FdzR!RsfL{dHfqw(4{U_}9_s$139v6e^#|(HWSOeFBe*mh! z&w<|t?*m28yJ5ls@C%^yR5Iprcq%Bm9t6d=7hmn|&p_$J{{cRe`;S2Ao(w)4RKK1J zo&??qiXUzUPXuoT)s8#B^T98ItH5NhmwzrOy6yw@{Dt6Z@D7lsCVv5{{j2u*IGzvc z`WfILxC0bj=0Vl}W>9i-C#d)S98^2L60W}simpEl_!Cg{Ua{Z5KM8z1*XMw`e<7&% zhrq{yTS1M-eo*C>L5*Jvlzc6K2f&*^z4txvbnpRC<(!OfufvjwVuuL3o$uLZ?tZvr(=w}G&D@>y^SJo9>g|M{TW{p+CWe|5P3 zmT>=V;A6S}K2Y_36jb{@4W0&mIXwRXsPF#;d>42;%=cRGZt!*B)z5Od`!1+)Kl9nn z4^IU}r-^_ykXMq!;12MM;7hsN>C zTfnW{e4qTuwd>uHt$n z%yR`e0^SJT2yO(@Q=Y$!1saYfu903PCo!8cbC)n4d5>D9PoKy1H2VfyH7aibl(Z8 zzDW?4O1MrF9F>S|lL5=GsQ1pE^cs}@I@G|gSp!ohfpx*l_DE>UV<8-?eJeBJT zsQP{lJOg|UcnWw2sD69|RJmURF9eT0)XMTzz>4bd!GZ< zzx%<{!3RLqf6~0m&w1dnTwe(828TfDiI;(D|Jy*(=@X#J{VVWs;QtKx$ix2p6j1FP z1djoK1ynm{Kz;XIP~ZI~sCL~7J{EjCcpP|Fxc(@32G^el_1?F^E#NR=6SAH&i^%!@4OyF)g+I6zVp*GDE@jOD7yR+sCw@LHJlL8d_Zm>``hD;;@J>*C`AJax`*l$L{UNvkT=Qb**8`xwuYzmA!=UPWGpPFR1jRRh z3hKRYff~pE2@Zk(4vH^^e%Tfx=fKY^<6>{s~s^@H!@`f^b3{}9yoKLsWKr~IbBw+1|(>!*XN zXA~4Y_JitA9Yob7zX6I5{teXkC;gUxcP{uCt~Y>c_tQW{yaPl; zl8=J#0!Lou<^2fM_sMU&K0OXp|JH)4X9($r z9#DGWGoZ%dd*I37KZ56hD=1_ocmb&P?E*glUJL#=@Z{IH|Ndv-FLQm-Yn^{z4XXdY z1FCp|83A@FhFC&3fJ`$6&XcRJr)1g`*}3SI=xgV#|0?V#lL-`?Q% z+gZQsdh1qjiu-Q{HLo4}MyKa$a1Ga2fER;Pp!ntu0sj&dKR@ACk9TbV_5I7h3&FR7 zj|9I0UJrg16x}ZSJ=gzl0iVnD@o(~RdnqV>cpJ!+k-P_d0yyydu6K5TMh{T+E`aLK zE5TF1+d;MWL!jvJZBXU?9e6qT04Ta#@@D2>um#eEo-rxTYs@yMuPXfOluKyKOIeqW&cAW}}-_meB2ucoy zK=EZAJPv#rxD|X2sCxer)cAY@Tn(;zr`I@A7!*Z-C;jkAT;KUjn}k zZhW^tKjTi1KaYV=;`{G{H}HJLd%c_~Q2lv1D1N&MRJ;BFJOz9wcoO(A@Uh?*!u@Z6 zrXRuMxxa!z)O#m`DsL6I8$2J>_$+`c!54++w}BUMeFr!Q-VdG#9`kep?c%6~to^6mvs1HS-18vHJJ4)}Lq z4LtEKFXy?S`1zHf>ir||Eb!go`ZJ*T?mkfMy&qJ&z6q+n?}MV#zksUugg3&A1qbg&Al ze>Z}v|7D=~>=sb`^+s?UyazlCp7CG({oe-F?$?2;=k4MChd`qrsD6GSTz?G|e|#Sl z9UlnK&%E2qIT!qM?mq>D6v?q4#3u(o4DJAD|C`JA`@pAi{S8p#eddRpzgB`T=6V2p z7Wf`ebUW$8uHWAbiaz&(DflJudhnk?jsGHdFa_@d#jjrm$G}H@%=P|W@OZ9Y2kQH`fGfba1$-B%^6mmf_j|x=!B2rx;L3ZP zzAptu?^{8Q+q*%1_eoH4bssnY{tr;?I{992_Y=SskHD7$H7-|v+{fiwQ07R5v=MCUfxc>jZ5pd(D zd|vE;+qgdY(>|Wpf#Rc^K*_~hLACcTa1D5Gz`q66j#EG5dU7z}D0nva%i#Io3&BT% zcYw!$?*dispMYz@PkYqxf-Ug-;4FB< zpSfM}S@0m&>;K%ZZvkJ=^#kBF;7wm}d*EB(R<4i#3+KHK;7af#;LE|k z2KR%t`~1Co!DG4p9=HPh$8h~G;F(-M>Wg0P+2CWjembc4uLMs7$HB*d6QKB|3O)*a zPPpF*_mRN0@a@nfvWH0;7;(wGMqM{s*<`0#X)mO!)2Xb~?liNsGgmIPv)1~wR4=Ek&fHw1*&a;Ny)#)lSE`ir zuNzZo|AptZ(n>2WRhwCm`k>XQl2$svOPy(| zt(R)qU^?7NCmXf7Y6fXmjoFiHS4C=RZ0e2qzAgD1{jF8$u#-{`cQ@u$=5V<@z}0+- zaqQU8PIan~X|B_poPqjft9jH11)8?folXk-I}DA=TT()7o0m|UZs=o?wjvT+wN?i2 z)haDxrumtyo|e+d4ue$7nt3C5Kdoo;aTKgcQ_U<(+s#U;x=huj`ef!jG+yCPV-c@X ztTxxoYL!kcZPe)~Or@rmI#gL|S0-VNg|wVSrh^Sfr^F3ZK1)H3v;!OP5yK$Hb1qu1 zH`c&ZGo?fJQY0S@53FO27N7T4@2wh4ucL;ef1FNLnk_N?R0GC~R1duGOcBdhqeQF5 zd>slRV7h_OHac}gkRhm5>XjP3hus^Uc3P_CPa4g1rZPQ~HT7B(V$`R*f7w_e>bfHl zjA-}Um0G47Lcj3~DGHU;EKSZzDkmF_*$O?FYSSH~xRK&erRD0NRytg%RgPq7R%+Jy zfZ=Vl5n~ub-Ce`AG)~kF+}gs3%%*fRQ$3Aza;DK>OjR~zW~~%9gvuJxaz5&jAMCZL z4I1^qWO!^e86MvbR7i)B zwrL_6E|=3ib#y<>t8Rx&_`cRCcdE<`ezR0>Fu={UjFeZP2G#9}U~fnZ)=)eM8THLo z8w*)k+;O1MERQvr@&+5t>6BVTD%1xmnL_j+BAT&IeKfs{jG!s%s{zHg^XpCQMFMjyZZN$qeW0$20$xaTQmyRL6AmG zw{5cZ>7MHQnb(6Vo^N!jUC}Hz)R9`L-Z9-hkWSL}Srb%xs)@ksn&`CKR7r1XWgQ-x z<*m%?Ce+gaJ1Yts$DKRNhSEE`+LbpS2)bvUwTW zY50@P*tL2wAQC@XKh!XVv$@)^sWah^>2!1?+1w~EB%A3rOl@<)=1vP$g%vMDk|(I0 zT;7ohyA$L;*$jCkPZEVyd*<5do=$s}ko-bkyB`ZSb6Z?@trRmX&4&~izJ2#9a8 zrBq*o%Be*AI_j9&Y+;igBC7!;{;Sh5AKAjafpiqkj#btfcK8aT{y|)jI?^3nkT6S# zBTF*lM%UC#PGoz_c94|nzGR&S!1|h-)=TTF7N0@H?ba1q%UiMGy~F99Nyj@At;uF( zPGT8*u~#;0xWLVcf2n%d!#~u}ay2*@E3zAER%>aA`LkdRFZOsQ#1Aq7HRANp#5tJ2 z(JaE7D2Nw*g6-kPoo?&L%RRCwTQilpc*D49%S<*oiz%6G!3j*Zpq82`R#Cd8G&z%n z>wWGJrhA*EdaEKYiwDsRE0R5UI84{Bj;bor)Vb8Kr29}s!enBKwNY!PM1;Lr0q?ZNHcS?MNb8U2ADy@=wRG7z+*qO@Yj9InlR)p2xaE6zX4+fI6cTdGOIYn8u>Hr?0w+~m4d5$F1L^%t?~wjzVwQ< zQ?pue?sU%DLwq)DIzjZ@O3+Roe}ye6CO1mk$^_K~q` z)=?d{&>ZfHq9#q;3j$pp6oaQ3%5V$MNM@3xH0WUOB#|-Xn^&P9#y}e$oyOgQiyL(~ z6_!`HE_`W5R(+yTYL?TXfpxLvhboQGCahoiN6~5&D@tzMcq?a9{6dSUPPMH+D^%Lks`K)cN^B$^q z&Xdtgj+!qjMEUR|53g)OE}?9=+K1M>=%ZET{cl?wMAdA?>8<*|^0dus-Ab15R*8!h z=zg*0Py2R0n^29Kb6kyu8(6t=&hiL}t3k#9lGNZUD@zk#x8zK9$68hT8jN#v12Gv& zS7$nNrn-*Iha4Ax))2rqKG>4L{7OSPF zhS3P$dYJf9I%2Va7SD9$6hIrq=#Mu6rBEw1XEReIDw&?jqq3?7lWlm4`Ug5D+vrb0 zd~%iA8$y`wz_-|J3Xg|?9^G{}SLFvQP5}Tpu|ZgHETqw_OdBC6D63SCUVQ*6FQQ4Yp}QP`|?p7?X@HY`RBPf##1pl=oO+M!al!Y9FR zwH8APj4S&GD^Dh_u44gqp{R*0!|sJm;@!*fk*gqJu&2yTFoIiX8^2qT8@z`q(@0nv z$Yr%W`wWFqEH1+kbf+`(=$X{b%l9@~GsfFAKiP(9=uXhKPTh23vK?Qa5@JYNL{YbBVLB9SsS2~}o3P|zRKxaSy#bw(l`*1=UO!HSA|8CgY6t2u=$zpBr<41x*=aP+ z+;)=E4S|=rnYlqCx1s-|7;NP-L#`2SVR2*Hsi~sZnzEaJH{j`7pIU3uGJ2f9VUXp@ zvno;a4W7%<2?dnXTGpOvls({U0$}{N2BKDPSTmUJZ7?Q8#m$SStxMJbqD@?0Z+6id zIw9U%yN-|dXo@MkQ104T>IIxA?uJZiw|I>T6qoQRWiZ*HQ2U~}ct^G%5;QYnh6H^P zlvF_;7b8kkl&2VnkT9crYMWwY$%Fh}y}p)37vdK-71bt6tyZZlrl4^OIj8c|d9O9I zGTBk9B|E5&h?G9I!l)JNaH`bYl|Eisi05V=6XAU{F2R4Q`U zAubuhXptUNyf{kuYhE|J46{a)Gf1y5U?7!0|iS>3Ns|y~I=8zpaNSR$j z)xrnPYuvYJkZvL8fdNhUQ~X6199?H7qa#T$Aw0^cE}Pvad&C#9WQQ_q$dHy>Vo_uj zs0mepZmgh6iy24mBMox%Iaro8m`P?+&F;9 zP2%+4tu}D6OuM{7c^R-{JKY!T0*|B*B`7_0ye?_8smJ1UyZ7uhE5rggl%v>|tW0-2 zWHahdvu4(s+!g~l>+l|>@>%nc!PE(c~G(LqMK9a-G`57OZx{{PW8{@Q_E(^WwWp zAFJ8!Jq&q2ZI+Ge7^5SMMcr>%XRtiAN`xS@(rJ3$UQi4$8D*X|b)Ss336?d8PUe~| znxUou%(0W-6O=5G;G=($YiJGE6qPG*-21wyC1SHTQ@@QMQNfaj@ zZDEhW2CYUD*LG?BdV$#|7!vY(8(B+gHTV!tyL`dKN+42I3nXu_CKLn^I|7xZHD=A* zsAXmz+=r&+Kg<&;njVDbyLQ!BXM({>ugX}r8PAAawe>e~(S5T0Mn~|j(3sMH=4Gj7 zNqFuLElA^;b9_(>TV+K|NFQJoF;F0Up}{iEK58?#_(gA_{gNvi8b$Pj88KnDOs-UM z=q57>H00e$R_|h&u$m@YyQ}EU?9tRQkr|(3c%d21T&FeJVaQO&Lv^!#FrlBxRm6!Q zx6d2aJ_cHZ*SsV?!fe+3JXJZYsZGi{5pcW1)2I~+56nnV@FUro)u-DtNtmD_rnbZBEd%lKC*x5Ct03`j3!)vWti@ixQyBK(mI)`2^FPU)5tF*2VCH!kkvqNTYR0* z9&V5$*uix|ubZ?Vb*Vu5s4^9#6X#6A)$3f5Lo{87MhGSIx37tqMcjW0c|g&Iw53%| zIy7+RWJ4BokK${`*hsNQS>^DhC0R$7B{pV`>T^`PqBC6dsIL0)H=t1GQU*4t6ulL`XH z;G*tZj-4?rSM>qAWa7|8KA(Da4>LUGov+f^>}NS)Zmtg|dj_vc%+oY;An0;0J!_dg zttrF>GFrAqPn3$8^YSz_#hKvB*7RvVUrp1T0v)0cM~(7pk6N1*Q_oj03nAxNyD~vK z^{N)@RBs#v^WGFpjb5Ugf^q#N^o;5W6phvmSgAGC6dpYFRmuW%4{K$zhqgBpGx<}G z5c=PYwf|uXd2EZEMGp$MlQAVz`2@dgx{PS&*)e0Kg)tOPbU1p|IS(zYOnG!t34t~# zr2EE4Oz$!!E5eG2P(xwOUk1TllB`6~nLZ;?i(a zoCyzooAJ@e-77az2a#Q(Rr&I@8z(S;M&7cKieCrARx?hp%IX+S>4ldnr!Sv==(hTm zhtI4%xo_s7J?lqnu2e&emFA%CTCQyjs9`M0NMGi*&39sYz6^mya{go{48^zebRRj~ zpJ4S(SNB8$>W8HKJup9*<4_qklri01rH|@v=|zw zSS%p==rVMT?Q8{0oFvmY=doq7w3jsyG?S4LRgxBjY;G^)l!wI>>?a%)yDO$Es54B( zdU*=tPlW5V<<*q&!B`z~*McFcTljPX&=Xii8CY^0iNJ;QtuB9ubj^@gBbxPm-pvFH zycAP(W2tI{EKnRO;s|kJyuC0N0^*hirtA;JC({M&cb7*~-SiMKY)&hzV}sJ|7$#g` zb~zN{#OR{qZzxixp{U6GvXBrj*@_8TV(pwupUB5tHb*E4J5+zqHOP!+rtfk}EZam_ zsSTbDPSr{c50LJ_2T(pu{+cX{RpTU< z!8_#4vCi)K6tzs|2NsN?0yd$9NM4+nCl&k%#)KF|gAi+1H}}r4z}ia33124UnKF0Q zB5DGOX2{V)>lEsr#c4TyvUEp9{$_Y(@ioC|WP(i?W^{<3U0#KUN|OueSOe|7z@=r} zIbx@XyL`fyiN>E=AfuBWTqlL5#uA!+2FEM(5!My-#vvu>C*4p7fBMdYbf*V$yEo)H zCRcZq4YZ9ottBa5-D!{wcTJ`xFboq9W2p+$)I7k;v4!xsslb$@96^XH6=&`_e;3w# z^fkFlXqC7jB5jrMZY94M7g=*9@sduKr*FrqxX*jw78j-G<=`4MTziBs3oM5cNdTuvD#6zN0S-NBI-m!7@ zQ<`9X+U$6e+mfJ5$cY~B2CdNPB?^F*rLAp+BQCNNzqjC-*#Xw&o<*@11 zVJXkcUg;o&gS0dcl>JO~dSLPAuS`QZ&4eW#PPTy-W(j9v zV`f9hiZlIz-k{~$YhfRB85KjBa0eWNL1@o-Lz&*Iji2~9882EvYqybnJVCr~eY%$c z#YDpz!iZVNYeYlMa1e=*(MFxDH8S@!DMFAqS{RA4Hf+_$&5+a=A*3*6uuh^ztT4+N z?FOCArBFha%6GHMBR1p+VM@qsP#Dj>9lg_1V8~y@q>7rVnN4HH(F#J*tm!rpCEFin zL=S(nZe%v1M9P+Z$2$`>RJIePm{1v${Q{=j8co!(bhGEqn6<81AZsRjONY}9>Hgup zQh$4umm&i(OcBXmy6#KpkV8Tg!jDc8k<7!EE7M`Lq1n$Cd^Oh_U1rJ9Qd6Xt%+zI> z$YhMaAqvJjiUpI>jis&42>OH#R!o~3(!tPP zl_O)3I9}C~-*urEV=j%QKWN2NJ}M8}9VI_$W5Il;ypEWagI#Er?;P=C>q;{_D50`i zReA`sBx(O76jkzKddP9>+v1D^r#M;2A2~l!{Cio1*?$z-FS^ zAaeoL&_WN#n}r32Wo}8d2t>ih1pGrWs3LagD3wEjB374`lAc@ev6XomkFTYNm_wlq zIZuiQLxFg=)hXj9Y5{~Dt8*WEvwF3%W$9?jZU}>6JXmAhzg?o2B;t1`T_bAP z6`G6Rd

  • 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-nl_NL.po b/freemius/languages/freemius-nl_NL.po new file mode 100644 index 0000000..d8e1822 --- /dev/null +++ b/freemius/languages/freemius-nl_NL.po @@ -0,0 +1,2509 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Benny Vluggen , 2017-2018 +# Patrick Buntsma , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Benny Vluggen \n" +"Language: nl_NL\n" +"Language-Team: Dutch (Netherlands) (http://www.transifex.com/freemius/wordpress-sdk/language/nl_NL/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK kon het hoofdbestand van de plug-in niet vinden. Neem a.j.b. contact op met sdk@freemius.com m.b.t. deze fout." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Fout" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Ik vond een beter %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Wat is de naam van het %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Het betreft een tijdelijke %s. Ik ben een probleem aan het debuggen." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deactivatie" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Thema Wissel" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Overige" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Ik heb de %s niet meer nodig " + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Ik had de %s alleen nodig voor een korte periode." + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "De %s maakte mijn site onbruikbaar" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "De %s werkte opeens niet meer" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Ik kan er niet langer meer voor betalen" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Welke bedrag zou je ervoor over hebben?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Ik vind het niet prettig om mijn informatie met jullie te delen" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "De %s werkte niet" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Ik snapte niet hoe ik het aan het werk kon krijgen." + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "De %s is uitstekend, maar ik heb een specifieke feature nodig die jullie niet ondersteunen" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Welke feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "De %s werkt niet" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Wil je alsjeblieft zo vriendelijk zijn om te delen wat niet werkte, zodat we dat kunnen verbeteren voor toekomstige gebruikers ..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Het is niet waarna ik opzoek was" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Waar was je naar op zoek?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "De %s werkte niet zoals verwacht" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Wat had je verwacht?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Ik weet niet wat cURL is of hoe dat te installeren is, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We doen onze best om contact op te nemen met uw hostingbedrijf om het probleem op te lossen. We sturen een vervolgmail naar %s, zodra we een update hebben. " + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Mooi, installeer alsjeblieft cURL en activeer het in je php.ini bestand. Tevens, zoek naar de 'disable_functions' directive in je php.ini bestand en verwijder iedere methode die start met 'curl_'. Gebruik 'phpinfo()' om je ervan te vergewissen dat het nu succesvol geactiveerd is. Als actief, deactiveer de %s en heractiveer deze opnieuw." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Ja, ga je gang" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nee - alleen deactiveren" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oeps" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Bedankt dat je ons in de gelegenheid stelt dit op te lossen. Zojuist is er een bericht verstuurd naar onze technische staf. We laten wat van ons horen, aan %s, als we een update hebben. Bedankt voor je geduld." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s werkt niet zonder %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s werkt niet zonder de plug-in." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Onverwachte API fout. Neem alsjeblieft contact op met de auteur van de %s met de volgende foutmelding." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s versie is succesvol geactiveerd." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Je hebt een %s licentie" + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Hoera" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s gratis proefperiode werd succesvol stop gezet. Daar de add-on alleen als premium versie beschikbaar is werd deze automatisch gedeactiveerd. Als u de add-on in de toekomst wilt gebruiken dient u een licentie aan te schaffen." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is uitsluitend beschikbaar als een premium add-on. Je moet een licentie kopen voordat je de plug-in activeert." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Meer informatie over %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Licentie Kopen" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Als het goed is ontvang je een activatie e-mail voor %s in je %s mailbox. Zorg er alsjeblieft voor dat je op de activatie knop klikt in die e-mail aan %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start de proefperiode" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "voltooi de installatie" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Je bent slechts een stap verwijderd - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Voltooi \"%s\" Activatie Nu" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We hebben een aantal aanpassingen gedaan op de %s, %s " + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt-in om \"%s\" te verbeteren!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "De upgrade van %s is succesvol voltooid." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Uitbreiding" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plug-in" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Thema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Ongeldige verzameling van Site Details." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We konden je e-mailadres niet vinden in het systeem, ben je zeker dat dat het juiste adres is?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Er is geen actieve licentie gekoppeld aan dat e-mailadres, ben je zeker dat dat het juiste adres is?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account wacht op activatie." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Koop nu een licentie" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Vernieuw je licentie nu" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%svoor toegang tot versie %s beveiliging en feature updates en support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activatie is succesvol voltooid." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Je account is succesvol geactiveerd met het %s plan." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "U proefperiode is met succes gestart." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Kon %s niet activeren." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Neem a.u.b. contact met ons op met het volgende bericht:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Upgrade" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start Proefperiode" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Prijzen" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliatie" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contacteer Ons" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Uitbreidingen" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Prijzen" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Supportforum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Je e-mail werd succesvol geverifieerd - je bent GEWELDIG!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Toppie" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Uw %sAdd-on plan werd succesvol geüpgraded. " + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on werd succesvol aangekocht." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download de meeste recente versie" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Foutmelding ontvangen van de server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Het lijkt erop dat een van de authenticatie parameters niet klopt. Update je Publieke Sleutel, Geheime Sleutel & Gebruikers ID en probeer het nogmaals. " + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Het lijkt erop dat u nog steeds op het %s plan zit. Als u uw plan geüpgraded of veranderd heeft, dan is het waarschijnlijk een fout aan onze kant - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Proefperiode" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Ik heb mijn account geüpgraded maar als ik probeer te Synchroniseren blijft het plan %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Neem hier a.u.b. contact met ons op" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Je plan is succesvol geüpgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Je plan is succesvol veranderd naar %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Je licentie is verlopen. Je kan echter de gratis %s voor altijd blijven gebruiken." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Je licentie is verlopen. %1$sUpgrade nu%2$s om de %3$s zonder interrupties te blijven gebruiken." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Je licentie is geannuleerd. Als je denkt dat dat een fout is, neem dan alsjeblieft contact op met support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Je licentie is verlopen. Je kan nog steeds alle %s features gebruiken, maar je zal je licentie moeten vernieuwen om weer updates en support te ontvangen." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Je gratis proefperiode is verlopen. Je kan nog steeds al onze gratis features blijven gebruiken." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Je gratis proefperiode is verlopen. %1$sUpgrade nu%2$som de %3$s zonder interrupties te blijven gebruiken. " + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Het lijkt erop dat de licentie niet geactiveerd kon worden." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Je licentie is succesvol geactiveerd." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Het lijkt erop dat je site momenteel geen actieve licentie heeft." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Het lijkt erop dat het deactiveren van je licentie mislukt is." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Je licentie is succesvol gedeactiveerd, je bent terug op het %s plan." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "Oké" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Het lijkt erop, dat we een tijdelijk probleem hebben met het annuleren van je abonnement. Probeer het alsjeblieft over een paar minuten nog eens." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Je abonnement is succesvol geannuleerd. De licentie van je %s-plan al over %s aflopen." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Je draait de %s al in proefmodus." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "U heeft reeds een proefperiode gebruikt." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s bestaat niet, daarom kan proefperiode niet gestart worden." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s ondersteunt geen proefperiode." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Geen van de %s plannen ondersteunt een proefperiode." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Het lijkt er op dat u niet langer meer in de proefperiode zit, dus er valt niets stop te zetten." + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Het lijkt er op dat we een tijdelijk probleem hebben met het opzeggen van uw proefperiode. Probeer het a.u.b. over enkele minuten nog eens." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Uw gratis %s proefperiode is succesvol opgezegd. " + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Versie %s is vrijgegeven." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "A.u.b. %s downloaden." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "de meest recente %s versie hier" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nieuw" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Het lijkt erop dat je de meest recente versie hebt." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Alles is goed!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verificatiemail zojuist verstuurd naar %s. Als je deze niet binnen 5 min. hebt ontvangen, kijk dan alsjeblieft in je spambox." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site opt-in geslaagd. " + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Geweldig" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We waarderen je hulp om %s beter te maken door ons gebruiksdata te laten verzamelen. " + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Bedankt!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We zullen geen gebruiksdata meer verzenden van %s m.b.t. %s naar %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Hou alsjeblieft je mailbox in de gaten, je zult een e-mail ontvangen via %s om de overdracht te bevestigen. Vanwege veiligheidsredenen moet je de overdracht binnen de volgende 15 min. bevestigen. Kijk eventueel in je spambox, mocht je de e-mail niet aantreffen in je inbox." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Bedankt voor het bevestigen van de eigendomsoverdracht. Zojuist is er een e-mail verstuurd naar %s voor de definitieve goedkeuring. " + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is de nieuwe eigenaar van het account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Gefeliciteerd" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we konden de e-mail update niet voltooien. Een andere gebruiker met hetzelfde e-mailadres is reeds geregistreerd." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Als je het eigendom van het %s account wilt overdragen aan %s, klik dan op de Eigendom Overdragen knop. " + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Eigendom Overdragen" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Je e-mailadres is succesvol verwerkt. Als het goed is ontvang je zometeen een e-mail met bevestigingsinstructies. " + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Geef alsjeblieft je volledige naam." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Je naam is succesvol bijgewerkt." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Je hebt je %s succesvol geüpdatet." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Voor alle duidelijkheid, de add-ons informatie van %s wordt opgehaald van een externe server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Aankondiging" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hoi" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Hoe bevalt %s tot dusver? Test al onze %s premium features gedurende een%d-daagse gratis proefperiode." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Geen verplichting voor %s dagen - elk moment opzeggen!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Geen creditcard nodig" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start gratis proefperidoe" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey, wist je dat %s een samenwerkingsprogramma heeft? Als je de %s goedvindt, kun je onze ambassadeur worden en wat geld verdienen!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Lees meer" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activeer Licentie" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Verander Licentie" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activeer %s features." + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Volg alsjeblieft deze stappen om de upgrade te voltooien" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download de meeste recente %s versie" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload en activeer de gedownloade versie" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Hoe te uploaden en activeren?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sKlik hier%s om de sites te kiezen waar op je de licentie wilt activeren." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Automatische installatie werkt alleen voor opted-in gebruikers." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ongeldige Module-ID" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium versie reeds actief." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Je hebt geen geldige licentie voor de premium versie." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plug-in is 'Serviceware' wat betekent dat het geen premium code versie bevat. " + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on versie is reeds geïnstalleerd." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Bekijk betaalde kenmerken" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Hartelijk bedankt voor het gebruik van %s en bijbehorende uitbreidingen!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Hartelijk bedankt voor het gebruik van %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om %s te blijven verbeteren." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Hartelijk bedankt voor het gebruiken van onze producten!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om deze te blijven verbeteren." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%sen bijbehorende uitbreidingen" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Producten" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Ja" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "stuur mij beveiliging & feature updates, educatieve content en aanbiedingen." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nee" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "stuur mij %sGEEN%s beveiliging & feature updates, educatieve content of aanbiedingen." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Laat ons alsjeblieft weten als je op de hoogte gehouden wilt worden van beveiliging & feature updates, educatieve content en zo nu en dan aanbiedingen:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "Licentiesleutel is leeg." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Vernieuw licentie" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Koop licentie" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Er is een %s van %s beschikbaar." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "nieuwe versie" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Belangrijke Upgrade Mededeling:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installeren van plug-in: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Toegang tot het bestandssysteem is niet mogelijk. Bevestig alsjeblieft je inloggegevens." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Het remote plug-in pakket bevat geen folder met de verwachte slug en hernoemen werkte niet. " + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Koop" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start mijn gratis %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installeer Gratis Versie Update Nu" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installeer Update Nu" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer Gratis Versie Nu" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installeer Nu" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Nieuwste Gratis Versie" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Nieuwste" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activeer deze add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activeer Gratis Versie" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activeer" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Beschrijving" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installatie" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "Veelgestelde Vragen" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Schermafbeeldingen" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Wijzigingen Log" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Reviews" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Andere Notities" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Features & Prijzen" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plug-in Installatie" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Beste" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Maandelijks" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Jaarlijks" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Levenslang" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s gefactureerd " + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Jaarlijks" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Eenmalig" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Enkele Site Licentie" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Onbeperkte Licenties" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Tot %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mnd" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "jaar" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Prijs" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Bespaar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Geen verplichting voor %s - opzeggen kan altijd" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Na uw gratis %s, betaal slechts %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Details" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versie" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Auteur" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Laatst Geüpdatet" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s geleden" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Vereiste WordPress-versie" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s of hoger" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible tot" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Gedownload" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s tijd" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s tijden" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plug-in Pagina" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plug-in Homepage" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Doneer aan deze plug-in" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Gemiddelde Beoordeling" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "gebaseerd op %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s beoordeling" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s beoordelingen" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s ster" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s sterren" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Klik om reviews te bekijken met een beoordeling van%s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Medewerkers" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Waarschuwing" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Deze plug-in is nog niet getest met je huidige WordPress versie. " + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Deze plug-in is niet als compatibel aangemerkt voor je huidige WordPress versie." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Betaalde add-on moet op Freemius geplaatst worden." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on moet op WordPress.org of Freemius geplaatst worden." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nieuwere Versie (%s) Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nieuwere Gratis Versie (%s) Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Meest Recente Versie Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Nieuwste Gratis Versie Geïnstalleerd" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Je plan naar beneden bijstellen" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Het abonnement annuleren" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Onthou alsjeblieft dat we geen oude prijzen voor verlengingen/nieuwe abonnementen na een annulering kunnen aanhouden. Als je in de toekomst besluit om een abonnement handmatig te vernieuwen, zal de nieuwe prijs (na een prijsverhoging die meestal jaarlijks plaatsvindt) worden berekend." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Het stopzetten van de proefperiode zal de toegang tot de premium features onmiddellijk blokkeren. Weet je dat zeker?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Je kunt nog steeds van alle %s-mogelijkheden genieten, maar je zult geen toegang hebben tot %s veiligheids- en uitbreidingsupdates, noch ondersteuning." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Als je licentie verloopt kan je nog steeds gebruik maken van de Gratis versie, maar je zal GEEN toegang meer hebben tot de %sfeatures." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activeer %s Plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto hernieuwd over %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Verloopt over %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sync Licentie" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Proefperiode Opzeggen" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Wijzig Plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Upgrade" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Gratis Proefperiode" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Accountgegevens" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Verwijdering van het account zal automatisch je %s licentie deactiveren zodat je die op andere sites kan gebruiken. Als je tevens je terugkerende betalingen wilt stopzetten, klik dan op de 'Annuleer' knop en 'Downgrade' je account eerst. Weet je zeker dat je wilt doorgaan met de verwijdering?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Verwijdering is niet tijdelijk. Verwijder alleen als je deze %s niet langer wilt gebruiken. Weet je zeker dat je wilt doorgaan met de verwijdering?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Verwijder Account" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deactiveer Licentie" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Weet je zeker dat je wilt doorgaan?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Abonnement Opzeggen" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sync" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Naam" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "E-mail" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Gebruikers ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Geen ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Publieke Sleutel" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Geheime Sleutel" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Geen Geheim" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Proefperiode" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licentiesleutel" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "niet geverifieerd" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Verlopen" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium versie" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Gratis versie" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verifieer E-mail" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download %s Versie" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Toon" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Wat is je %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Bewerk" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Zoek op adres" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adres" + +#: templates/account.php:610 +msgid "License" +msgstr "Licentie" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licentie" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Verberg" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Annuleren %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "proefperiode" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "%s wordt geannuleerd..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "abonnement" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactiveren van je licentie zal alle premium features blokkeren, maar geeft je de mogelijkheid de licentie op een andere site te activeren. Weet je zeker dat je wilt doorgaan?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Bekijk details" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add-ons voor %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We konden de add-ons lijst niet laden. Dat is waarschijnlijk een probleem aan onze kant, kom alsjeblieft over enkele minuten terug." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Activeer" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Afsluiten" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatische Installatie" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Een geautomatiseerde download en installatie van %s (betaalde versie) van %s zal starten binnen %s. Als je dit handmatig wil doen, klik dan nu op de annuleer knop." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "Het installatieproces is gestart en kan enkele minuten duren om te voltooien. Wacht alsjeblieft totdat dat gebeurt is - deze pagina niet verversen." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuleer Installatie" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Afrekenen" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI-comform" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hoi %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Toestaan & Ga Verder" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Activatiemail opnieuw versturen" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Bedankt %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Akkoord & Activeer Licentie" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Bedankt voor het aanschaffen van %s! Om te beginnen, voer alsjeblieft je licentiesleutel in:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging en feature update notificaties, educatieve content, aanbiedingen, en niet-gevoelige diagnostische tracking met %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging en feature update notificaties, en niet-gevoelige diagnostische tracking met %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging & feature update notificaties, educatieve content, aanbiedingen, en niet-gevoelige diagnostische tracking met %4$s. Als je deze stap overslaat, geen probleem! %1$szal ook dan gewoon 100% werken. " + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging & feature updates notificaties, en niet-gevoelige diagnostische tracking met %4$s. Als je deze stap overslaat, geen probleem! %1$szal ook dan gewoon 100% werken. " + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We zijn verheugd om Freemius network-level integratie te introduceren." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Tijdens het update proces detecteerden we %dsite(s) waarvoor de licentie nog niet geactiveerd is." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Als je de %s op deze sites wil gebruiken, voer dan alsjeblieft de licentiesleutel hieronder in en klik op de activatie-knop." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s betaalde mogelijkheden" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Je kunt dat eventueel ook nu overslaan en de licentie later in je %s netwerk-niveau Account pagina activeren. " + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Tijdens het update proces detecteerden we %dsite(s) in het netwerk die jouw aandacht vereisen." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licentiesleutel" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Kan je je licentiesleutel niet vinden?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Sla Over" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegeren aan Site Beheerders" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Al je er op klikt, zal deze beslissing gedelegeerd worden aan de beheerders van de sites. " + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Je Profiel Overzicht" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Naam en e-mailadres" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Je Site Overzicht" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP versie, PHP info, plug-ins & thema's" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin Mededelingen" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Updates, aankondigingen, marketing, geen spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Huidige %s Gebeurtenissen" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activatie, deactivatie en deïnstallatie" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Nieuwsbrief" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "De %1$s zal periodiek data verzenden naar %2$s om te controleren op beveiliging en feature updates en om te verifiëren of je licentie geldig is." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Welke toestemmingen worden er verleend?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Heb je geen licentiesleutel?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Heb je een licentiesleutel?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privacybeleid" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Licentieovereenkomst" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Servicevoorwaarden" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "E-mail versturen" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activeren" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Uit" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Aan" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Acties" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Weet u zeker dat u alle Freemius data wilt verwijderen?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Verwijder All Accounts" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "API-Cache Leegmaken" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Updates Transients Opschonen" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synchroniseer Data Vanaf Server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Zet Opties over naar Netwerk" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Laad DB-optie" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Activeer DB-Optie" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Sleutel" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Waarde" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK Versies" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK Pad" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Pad" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Is Actief" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plug-ins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Thema's" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titel" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius Status" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Netwerk Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Netwerk Gebruiker" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Verbonden" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Geblokkeerd" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simuleer Trial Actie" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuleer Netwerk Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installaties" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Verwijder" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Uitbreidingen van module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Gebruikers" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Geverifieerd" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenties" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plug-in ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Geactiveerd" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Geblokkeerd" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Verloopdatum" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Alle Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Alle Requests" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Bestand" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Functie" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Proces-ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Bericht" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Tijdstempel" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Beveiligde HTTPS %s pagina, loopt via een extern domein" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Ondersteuning" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Aanvragen" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Bijwerken" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturering" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Bedrijfsnaam" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Btw-nummer" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresregel %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Stad" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Plaats" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Postcode" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Land" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Selecteer Land" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Staat" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincie" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Betalingen" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Datum" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Bedrag" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Factuur" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Methodes" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Lengte" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Pad" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultaat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Einde" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Binnen %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s geleden" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synchronisatie Plug-ins & Thema's" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Totaal" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Laatste" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Geplande Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Moduletype" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Volgende" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Niet-verlopende" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Meld je aan om een affiliate partner te worden" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Je samenwerkingsaanvraag voor %s is geaccepteerd! Log in op je samenwerkingsomgeving op: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Bedankt voor je aanvraag voor deelname aan ons samenwerkingsprogramma. We zullen binnen 14 dagen je gegevens doornemen, waarna we je aanvullende informatie zullen sturen." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Je affiliate account is tijdelijk geschorst." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Bedankt voor je aanvraag voor deelname aan ons affiliate programma, helaas, op dit moment hebben we besloten je aanvraag af te wijzen. Probeer het alsjeblieft over 30 dagen nog eens." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Als gevolg van het overtreden van onze affiliate voorwaarden, hebben we besloten je affiliate account tijdelijk te blokkeren. Neem voor eventuele vragen alsjeblieft contact op met support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Vind je de %s goed? Word dan onze ambassadeur en verdien cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Verwijs nieuwe klanten naar onze %s en krijg %s commissie op iedere door jou doorverwezen, geslaagde verkoop!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Programma Samenvatting" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commissie als een klant een nieuwe licentie koopt. " + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Krijg een commissie voor automatische abonnementsverlengingen." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie na eerste bezoek om je verdienpotentieel te maximaliseren." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Onbeperkte commissies." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum uitbetalingsbedrag." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Uitbetalingen zijn in USD en worden maandelijks uitgevoerd via PayPal" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Omdat wij 30 dagen reserveren voor eventuele terugstortingen, betalen we alleen commissies uit die ouder dan 30 dagen zijn." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "E-mailadres" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Volledige naam" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account e-mailadres" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Waar ga je de %s promoten?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Voer de domeinnaam in van je website of andere websites waar vanaf je van plan bent de %ste gaan promoten." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Voeg nog een domein toe" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domeinen" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domeinen vanaf waar je het product gaat promoten." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotie methodes" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobiele apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, mail, and social media statistieken (optioneel)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Voel je alsjeblieft vrij om elke relevante website of social media statistieken met ons te delen, bijvoorbeeld maandelijkse unieke bezoekers, aantal e-mail abonnees , volgers, etc. (we zullen deze informatie vertrouwelijk houden)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Hoe ga je ons promoten?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Geef alsjeblieft zo gedetailleerd als mogelijk aan hoe je van plan bent om %s te gaan promoten." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuleer" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Wordt een affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Voer aalsjeblieft de licentiesleutel in die je ontving in de e-mail direct na de aankoop:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Update Licentie" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Het bijhouden van het gebruik wordt gedaan om %s te verbeteren. De gebruikerservaring te verbeteren, de prioriteit van nieuwe features te bepalen, en meer goede zaken. We zouden het heel erg op prijs stellen als je ons toch weer toestaat het gebruik te volgen. " + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Door op \"Opt Out\" te klikken, zullen wij niet langer gegevens van %s verzenden naar %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Er is een nieuwe versie van %s beschikbaar." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%svoor toegang to versie %s beveiliging & features updates en support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nieuwe Versie Beschikbaar" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Afsluiten" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Verzend Licentiesleutel" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Voer hieronder het e-mailadres in dat je gebruikt hebt voor de upgrade en we zullen je jouw licentiesleutel opnieuw toesturen." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Het deactiveren en deïnstalleren van de %s zal de licentie automatisch uitschakelen, die je dan kan gebruiken op een andere site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "Mocht je NIET van plan zijn om deze %s te gebruiken op deze site (of op een andere site) - wil je dan het %s ook opzeggen?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licentie" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Annuleer %s - Ik heb niet meer enige beveiligings- en uitbreidingsupdates of ondersteuning voor %s nodig, omdat ik niet van plan ben de %sop deze of enige andere site te gebruiken." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Annuleer %s niet - Ik wil nog steeds zowel beveiligings- en uitbreidingsupdates ontvangen als contact kunnen opnemen met Support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Als je licentie afloopt, zul je %s niet meer kunnen gebruiken, tenzij je het opnieuw activeert met een geldige Premium-licentie." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "%s annuleren?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Doorgaan" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Annuleer %s & Ga Door" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "U bent 1-klik verwijderd van het starten van uw %1$s-daagse gratis proefperiode van het %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Voordat we de proefperiode kunnen starten, vragen we je, in overeenstemming met de Wordpress.org-richtlijnen, in te stemmen je gebruikers- en niet-sensitieve site informatie door de %s periodiek te laten verzenden naar %s om te controleren op nieuwe versies en je proefversie te valideren." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activeer licentie op alle sites in het netwerk." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Pas toe op alle sites in het netwerk." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activeer licentie op alle in behandeling zijnde sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Pas toe op alle in behandeling zijnde sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "toestaan" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "deligeren" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "overslaan" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Klik voor het op volle-grootte bekijken van schermafbeelding %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Onbeperkte Updates" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s beschikbaar" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Laatste licentie" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Geannuleerd" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Geen verloopdatum" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Naam Eigenaar" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "E-mail Eigenaar" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID Eigenaar" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Abonnement" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry voor het ongemak en we zijn er om je te helpen als je daartoe de kans geeft.." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contacteer Support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonieme terugkoppeling" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deactiveer" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activeer %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Snelle terugkoppeling" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "We zouden het zeer op prijs stellen, als je even hebt, om ons alsjeblieft te laten weten waarom je gaat %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactiveren" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "overschakelen" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Verstuur & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Wilt je alsjeblieft zo vriendelijk zijn om de reden te vermelden, zodat wij verbeteringen kunnen doorvoeren." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Ja - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Sla over & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Klik hier om de plug-in anoniem te gebruiken" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Misschien heb je het gemist, maar je hoeft geen gegevens te delen en kunt de opt-in %s." diff --git a/freemius/languages/freemius-ru_RU.mo b/freemius/languages/freemius-ru_RU.mo new file mode 100644 index 0000000000000000000000000000000000000000..9f408a8856b569aa75e848575de78dd5cd656cfd GIT binary patch literal 69773 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 ZL5V_ppxrwOTZ_aDmN`c8sdibJ{})p&OIrW{ literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-ru_RU.po b/freemius/languages/freemius-ru_RU.po new file mode 100644 index 0000000..092a3c1 --- /dev/null +++ b/freemius/languages/freemius-ru_RU.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Robert Premmerce , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: ru_RU\n" +"Language-Team: Russian (Russia) (http://www.transifex.com/freemius/wordpress-sdk/language/ru_RU/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK не удалоÑÑŒ найти оÑновной файл плагина. ПожалуйÑта, ÑвÑжитеÑÑŒ Ñ sdk@freemius.com Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ ошибкой." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Ошибка" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Я нашел лучший %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Какое название %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Это Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ %s. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¸Ñ‚ проверка на наличие ошибок. " + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "ДеактивациÑ" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Переключатель шаблона " + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Другие" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "%s больше не понадобитÑÑ." + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "%s требовалаÑÑŒ на короткое времÑ" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s повредила мой Ñайт" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s внезапно переÑтала работать " + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Я больше не могу оплачивать Ñто. " + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "ÐšÐ°ÐºÐ°Ñ ÑтоимоÑть была бы Ð´Ð»Ñ Ð’Ð°Ñ Ð¿Ñ€Ð¸ÐµÐ¼Ð»ÐµÐ¼Ð¾Ð¹? " + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Я не хочу делитьÑÑ Ð»Ð¸Ñ‡Ð½Ð¾Ð¹ информацией Ñ Ð’Ð°Ð¼Ð¸" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s не Ñработала" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Я не могу понÑть как Ñделать так, чтобы оно работало" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s Ð¾Ñ‚Ð»Ð¸Ñ‡Ð½Ð°Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñть, но мне нужен определенный функционал, который вы не поддерживаете. " + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Какой функционал?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s не работает" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "ПожалуйÑта, Ñообщите о функционале, который не работает, чтобы мы Ñмогли иÑправить его Ð´Ð»Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ¹ÑˆÐµÐ³Ð¾ иÑпользованиÑ. " + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Это не то, что Ñ Ð¸Ñкал. " + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Что именно Ð’Ñ‹ ищите? " + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s не Ñработала как ожидалоÑÑŒ" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Каковы были Ваши ожиданиÑ? " + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "ИÑправление ошибок Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Я не знаю, что такое ÑURL и как его уÑтановить. ПожалуйÑта, помогите мне." + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Мы обÑзательно ÑвÑжемÑÑ Ñ Ð’Ð°ÑˆÐ¸Ð¼ хоÑтинг провайдером и найдем решение. Как только у Ð½Ð°Ñ Ð¿Ð¾ÑвитÑÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ, Вам будет отправлено пиÑьмо на почту. " + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Отлично! ПожалуйÑта, уÑтановите ÑURL и активируйте его в Вашем файле php.ini .Также, найдите директиву 'disable_functions' в файле php.ini и удалите вÑе неактивные методы которые начинаютÑÑ Ð½Ð° 'curl_'. Чтобы убедитÑÑ, что Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÑˆÐ»Ð° уÑпешно, иÑпользуйте 'phpinfo()'. ПоÑле активации, деактивируйте %s и Ñнова активируйте ее. " + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Да, делайте то, что Вам нужно. " + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Ðет. Ðужно деактивировать. " + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "УпÑ!" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "СпаÑибо, что предоÑтавили нам возможноÑть иÑправить ошибку. Сообщение уже отправлено нашим техничеÑким ÑпециалиÑтам. Мы Ñ Ð’Ð°Ð¼Ð¸ ÑвÑжемÑÑ, как только будет Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ %s. Благодарны за понимание. " + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s не работает без %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s не может работать без плагина. " + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "ÐÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° API. ПожалуйÑта, ÑвÑжитеÑÑŒ Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ %s в котором была обнаружена ошибка. " + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Премиум верÑÐ¸Ñ %s была уÑпешно активирована. " + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Вау!" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "У Ð’Ð°Ñ ÐµÑть Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Ура!" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "БеÑплатный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s закончилÑÑ. Этот плагин ÑвлÑетÑÑ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ продуктом и он был деактивирован автоматичеÑки. ЕÑли Ð’Ñ‹ планируете дальнейшее его иÑпользование, пожалуйÑта купите лицензию. " + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s ÑвлÑетÑÑ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ продуктом. Ðеобходимо купить лицензию перед активацией плагина. " + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Больше информации о %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Купите лицензию " + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Мы отправили Вам пиÑьмо Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ %s на Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ %s. ПожалуйÑта, нажмите на кнопку активации в Ñтом пиÑьме %s. " + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "Ðачать теÑтовый период" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "Закончить уÑтановку" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Вам оÑталоÑÑŒ ÑовÑем немножко %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Закончить активацию %s ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Мы уÑовершенÑтвовали в %s, %s Ð´Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ работы " + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "Обновление %s было уÑпешно завершено" + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Функционал плагина " + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Плагин " + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Шаблон " + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "К Ñожалению, Ваш почтовый Ð°Ð´Ñ€ÐµÑ Ð½Ðµ найден в ÑиÑтеме. Ð’Ñ‹ уверены, что предоÑтавили правильный адреÑ? " + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð²Ñ‹Ð´Ð°Ð½Ð½Ð°Ñ Ð½Ð° Ñтот Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð½Ðµ была найдена. Ð’Ñ‹ уверены, что предоÑтавили правильный Ñлектронный адреÑ?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ в процеÑÑе активации" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ %s была уÑпешно завершена" + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ была уÑпешно активирована ÑоглаÑно плану %s" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Ваш теÑтовый период уÑпешно начат" + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Ðевозможно активировать %s" + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "ПожалуйÑта, напишите нам Ñообщение Ñледующего ÑодержаниÑ:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Сделать апгрейд " + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Ðачать теÑтовый период" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Цены " + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "ПартнерÑтво " + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Личный кабинет" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Контакты " + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "ÐаÑтройки плагина " + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Цены" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Форум поддержки " + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±Ñ‹Ð» уÑпешно подтвержден и Ð’Ñ‹ проÑто молодец!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Ð’Ñе верно!" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Ваш %s план был уÑпешно обновлен" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "Покупка %s плагина уÑпешно ÑоÑтоÑлаÑÑŒ" + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Скачай поÑледнюю верÑию" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Ошибка Ñервера" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "ВероÑтно один из параметров ÑвлÑетÑÑ Ð½ÐµÐ²ÐµÑ€Ð½Ñ‹Ð¼. Обновите Ñвой Public Key, Secret Key&User ID и повторите попытку." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Хм..." + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "ВероÑтно Ð’Ñ‹ вÑе еще пользуетеÑÑŒ ÑервиÑом ÑоглаÑно плану %s. ЕÑли Ð’Ñ‹ обновлÑли или менÑли Ñвой тарифный план, то вероÑтно ÑущеÑтвуют какие-то трудноÑти ÑвÑзанные Ñ Ð’Ð°ÑˆÐ¸Ð¼ программным обеÑпечением. Извините. " + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "ТеÑтовый период" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Я провел апгрейд аккаунта, но при попытке Ñинхронизировать лицензию, мой тарифный план не менÑетÑÑ. " + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "ПожалуйÑта, напишите нам Ñообщение здеÑÑŒ. " + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Ваш тарифный план был уÑпешно изменен. " + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Ваш тарифный план был уÑпешно изменен на %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии закончилÑÑ. Ð’Ñ‹ можете продолжать пользоватьÑÑ Ð±ÐµÑплатной верÑией %s на беÑÑрочной оÑнове." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° аннулирована. ЕÑли Ð’Ñ‹ Ñчитаете, что Ñто ошибка, пожалуйÑта ÑвÑжитеÑÑŒ Ñ Ð½Ð°ÑˆÐµÐ¹ Ñлужбой поддержки. " + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии закончен. Ð’Ñ‹ можете продолжать пользоватьÑÑ Ð²Ñеми возможноÑÑ‚Ñми %s продлив Вашу лицензию. Ð’Ñ‹ также будете получать доÑтуп к обновлениÑм и поддержке. " + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "ВероÑтно возникли трудноÑти Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹ лицензии. " + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° уÑпешно активирована. " + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "ВероÑтно Ваш Ñайт не иÑпользует активную лицензию ÑейчаÑ. " + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "ВероÑтно Ð´ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸ не ÑоÑтоÑлаÑÑŒ. " + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° уÑпешно деактивирована и Ð’Ñ‹ Ñнова пользуетеÑÑŒ планом %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K." + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Ð’Ñ‹ уже пользуетеÑÑŒ теÑтовой верÑией %s " + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Ð’Ñ‹ уже иÑпользовали Ваш теÑтовый период" + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Тарифного плана % не ÑущеÑтвует, поÑтому Ð’Ñ‹ не можете начать теÑтовый период. " + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Тарифный план % не предуÑматривает теÑтового периода. " + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Тарифные планы %s не предуÑматривают теÑтовый период. " + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Возможно, Ваш теÑтовый период уже закончилÑÑ. " + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "К Ñожалению у Ð½Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð¸ трудноÑти Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¾Ð¹ Вашего теÑтового периода. ПожалуйÑта, повторите попытку через неÑколько минут." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Ваш беÑплатный теÑтовый период был уÑпешно отменен. " + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Релиз верÑии %s ÑоÑтоÑлÑÑ. " + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "ПожалуйÑта, Ñкачайте %s" + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "ПоÑледнÑÑ Ð²ÐµÑ€ÑÐ¸Ñ %s здеÑÑŒ" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Ðовое " + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "ВероÑтно, Ð’Ñ‹ пользуетеÑÑŒ поÑледней верÑией" + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Ð’Ñе прошло хорошо!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "ПиÑьмо подтверждение было только что отправлено на %s. ЕÑли Ð’Ñ‹ не получите его через 5 минут, пожалуйÑта, проверьте папку Ñпам." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Отлично!" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Ð’Ñ‹ очень помогаете нам ÑовершенÑтвовать %s Ñ€Ð°Ð·Ñ€ÐµÑˆÐ°Ñ Ñледить за некоторыми данными о пользовании. " + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Thank you!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "ПожалуйÑта, проверьте Ñвою Ñлектронную почту. Ð’Ñ‹ должны были получить пиÑьмо от %s Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ñмены прав иÑпользованиÑ. По причинам безопаÑноÑти, Ð’Ñ‹ должны подтвердить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð° протÑжении 15 минут. ЕÑли пиÑьмо не пришло, пожалуйÑта проверьте папку Ñпам. " + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "СпаÑибо, что подтвердили изменение прав иÑпользованиÑ. Вам отправлено пиÑьмо на %s Ð´Ð»Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ подтверждениÑ. " + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%Ñ ÑвлÑетÑÑ Ð½Ð¾Ð²Ñ‹Ð¼ владельцем аккаунта" + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "ПоздравлениÑ! " + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Извините, нам не удалоÑÑŒ обновить Ñлектронный адреÑ. Другой пользователь Ñ Ñ‚Ð°ÐºÐ¸Ð¼ же адреÑом уже был зарегиÑтрирован. " + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "ЕÑли Ð’Ñ‹ передаете права Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ %s %s нажмите кнопку \" Сменить права иÑпользованиÑ\"" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Сменить владельца лицензии " + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±Ñ‹Ð» уÑпешно обновлен. Через неÑколько минут Ð’Ñ‹ получите пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "ПожалуйÑта, введите Ваше полное имÑ" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Ваше Ð¸Ð¼Ñ Ð±Ñ‹Ð»Ð¾ уÑпешно обновлено" + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Ð’Ñ‹ уÑпешно обновили Ваш %s" + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Сообщаем, что Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ дополнительных наÑтройках %s предоÑтавлÑетÑÑ Ñо Ñтороннего Ñервера. " + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Внимание!" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Привет!" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Тебе нравитÑÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÑŒÑÑ %s? ВоÑпользуйÑÑ Ð²Ñеми нашими премиум возможноÑÑ‚Ñми на протÑжении %d - дневного теÑтового периода. " + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "БеÑплатное пользование на протÑжении %s дней. Отмена в любое времÑ. " + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Ðе требуютÑÑ Ð´Ð°Ð½Ð½Ñ‹Ðµ платежной карты" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Ðачни теÑтовый период!" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Привет! Знали ли Ð’Ñ‹, что %s предоÑтавлÑет реферальную программу? ЕÑли Вам нравитÑÑ %s, Ð’Ñ‹ можете Ñтать нашим предÑтавителем и зарабатывать!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Узнать больше" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Ðктивировать лицензию" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Изменить лицензию " + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "ОтказатьÑÑ Ð¾Ñ‚ иÑпользованиÑ" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "ПриÑоединитьÑÑ" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "ПожалуйÑта, пройдите Ñти шаги Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы произвеÑти апгрейд" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Скачайте поÑледнюю верÑию %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Загрузите и активируйте Ñкачанную верÑию" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Как загрузить и активировать?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Ðвто уÑтановка работает только Ð´Ð»Ñ Ð·Ð°Ñ€ÐµÐ³Ð¸Ñтрированных пользователей." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ðеверный ID модулÑ" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Премиум верÑÐ¸Ñ ÑƒÐ¶Ðµ активирована" + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ необходимых лицензионных прав Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ верÑией" + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Плагин ÑвлÑетÑÑ 'ServiÑeware'. Это означает, что он не имеет премиум верÑию кода. " + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Премиум верÑÐ¸Ñ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð° была уÑтановлена" + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "ПроÑмотр платных возможноÑтей" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Products" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Yes" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "License key is empty." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renew license" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "УÑтановка плагина: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Ðевозможно приÑоединитьÑÑ Ðº ÑиÑтеме файлов. ПожалуйÑта, подтвердите Ñвои данные. " + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Удаленный пакет плагинов не Ñодержит папку Ñ Ð½ÑƒÐ¶Ð½Ñ‹Ð¼ опиÑанием URL и Ñмена имени не Ñрабатывает. " + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Купить" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Ðачать мой беÑплатный %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "ПровеÑти Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "УÑтановить ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Скачать поÑледнюю верÑию" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Ðктивируйте Ñтот функционал " + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Ðктивировать беÑплатную верÑию?" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Ðктивировать " + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "ОпиÑание " + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "УÑтановка " + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "ЧаÑто задаваемые вопроÑÑ‹ " + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Снимки Ñкрана " + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Журнал изменений " + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Отзывы " + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Другие заметки " + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Функционал&тарифные планы " + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "УÑтановка плагина " + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s план " + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Лучший " + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "ПомеÑÑчно " + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Ежегодно " + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Ðа беÑÑрочный период " + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Оплачивать %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Один раз в год " + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Один раз " + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð½Ð° один Ñайт " + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "ÐÐµÐ¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "до % Ñайтов " + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "на один меÑÑц" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "на один год " + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "СтоимоÑть " + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Ð­ÐºÐ¾Ð½Ð¾Ð¼Ð¸Ñ %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Без обÑзательÑтв платить %s - аннулируй пользование в любое Ð²Ñ€ÐµÐ¼Ñ " + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "ПоÑле Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ беÑплатного %s, платите вÑего лиш %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Детальней" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "ВерÑÐ¸Ñ " + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Ðвтор" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "ПоÑледнее обновление " + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "% тому назад " + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Ðеобходима верÑÐ¸Ñ WordPress " + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s или выше" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "СовмеÑтима Ñ " + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Загружен " + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s Ð²Ñ€ÐµÐ¼Ñ " + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s раз " + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Страница плагинов WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Ð“Ð»Ð°Ð²Ð½Ð°Ñ Ñтраница плагина " + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "ИнвеÑтировать в разработку плагина " + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Средний рейтинг " + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "ОÑнован на %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "% оценка " + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "% оценки " + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%звездочка " + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "% звездочки " + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Ðажмите, чтобы поÑмотреть отзывы, которые Ñформировали рейтинг %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Контрибьюторы " + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Предупреждение " + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Этот плагин не был теÑтирован Ñ Ð’Ð°ÑˆÐµÐ¹ текущей верÑией WordPress. " + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Этот плагин не отмечен как ÑовмеÑтимый з Вашей верÑией WordPress " + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Платный функционал должен быть заÑвлен в Freemius" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Функционал должен быть заÑвлен на WordPress.org или Freemius " + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Более Ð½Ð¾Ð²Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ %s уÑтановлена " + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "ПоÑледнÑÑ Ð²ÐµÑ€ÑÐ¸Ñ ÑƒÑтановлена" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Latest Free Version Installed" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Отказ от Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚ÐµÑтовым периодом автоматичеÑки блокирует доÑтуп ко вÑем премиум возможноÑÑ‚Ñм. Ð’Ñ‹ уверены, что хотите отказатьÑÑ?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "По окончанию Ñрока дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии, Ð’Ñ‹ Ñможете пользоватьÑÑ Ð±ÐµÑплатной верÑией, но у Ð’Ð°Ñ Ð½Ðµ будет доÑтупа к возможноÑÑ‚Ñм %s. " + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Ðктивируйте план %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "ÐвтоматичеÑкое продление в %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Окончание Ñрока Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‡ÐµÑ€ÐµÐ· %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸ " + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Отменить теÑтовый период " + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Изменить план " + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Сделать апгрейд" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Понизить план " + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "БеÑÐ¿Ð»Ð°Ñ‚Ð½Ð°Ñ " + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Тарифный план " + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "БеÑплатный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/account.php:196 +msgid "Account Details" +msgstr " Детали" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Удалив личный кабинет, Ð’Ñ‹ автоматичеÑки деактивируете лицензию на Ваш тарифный план %s, которую Ð’Ñ‹ можете иÑпользовать на других Ñайтах. ЕÑли Ð’Ñ‹ хотите также приоÑтановить регулÑрные платежи, нажмите на кнопку \"Отмена\" и Ñначала измените Ñвой тарифный план на беÑплатный. Ð’Ñ‹ уверены, что хотите продолжить удаление?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Удаление личного кабинете не может быть произведено временно. Удалите только в Ñлучае еÑли Ð’Ñ‹ больше не хотите пользоватьÑÑ %s. Ð’Ñ‹ уверены, что хотите продолжить удаление? " + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Удалить личный кабинет" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Деактивировать лицензию " + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Ð’Ñ‹ уверены, что хотите продолжить?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Отменить подпиÑку " + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Синхронизировать " + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "ИмÑ" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID " + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "No ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Public Key " + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Secret Key " + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Ðет Ñекрета " + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "ТеÑтовый период " + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Лицензионный ключ " + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "не подтвержден " + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð·Ð°ÐºÐ¾Ð½Ñ‡Ð¸Ð»ÑÑ " + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Премиум верÑÐ¸Ñ " + +#: templates/account.php:504 +msgid "Free version" +msgstr "БеÑÐ¿Ð»Ð°Ñ‚Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ " + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Подтвердите Ñлектронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Скачайте верÑию %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Показать " + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Какой Ваш %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Редактировать " + +#: templates/account.php:588 +msgid "Sites" +msgstr "Сайтов " + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Search by address" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Address" + +#: templates/account.php:610 +msgid "License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account.php:611 +msgid "Plan" +msgstr "Тарифный план " + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "СпрÑтать " + +#: templates/account.php:765 +msgid "Processing" +msgstr "Обработка данных " + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Смотреть детальней " + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Функционал Ð´Ð»Ñ %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Мы не можем загрузить ÑпиÑок плагинов. ВероÑтно, произошла какаÑ-то ошибка Ñ Ð½Ð°ÑˆÐµÐ¹ Ñтороны. ПожалуйÑта, вернитеÑÑŒ на Ñтраницу через неÑколько минут. " + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Закрыть " + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s Ñекунд " + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "ÐвтоматичеÑÐºÐ°Ñ ÑƒÑтановка " + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "ÐвтоматичеÑкое Ñкачивание и уÑтановка %s ( платной верÑии) %s начнетÑÑ Ñ‡ÐµÑ€ÐµÐ· %s. ЕÑли Ð’Ñ‹ хотите вÑе Ñделать в ручном режиме, нажмите на кнопку \"Отменить\" ÑейчаÑ. " + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "ПроцеÑÑ ÑƒÑтановки уже начат и может занÑть неÑколько минут. ПожалуйÑта, подождите Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑÑа и не обновлÑйте Ñту Ñтраницу. " + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Отменить уÑтановку " + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Оплата " + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Жалоба PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "ЗдравÑтвуйте %s" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Разрешить и продолжить" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Отправить пиÑьмо активации еще раз " + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "СпаÑибо %s" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "СоглаÑитьÑÑ Ð¸ активировать лицензию " + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "СпаÑибо за покупку %s! Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы начать введите Ñвой лицензионный ключ:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ðикогда не пропуÑкайте важных оповещений - подпишитеÑÑŒ на наши ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ безопаÑноÑти и новом функционале." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Лицензионный ключ" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Ðе можете найти лицензионный ключ? " + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "ПропуÑтить" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "ПроÑмотр Вашего Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ " + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Ð˜Ð¼Ñ Ð¸ Ñлектронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "ПроÑмотр Вашего Ñайта " + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL Ñайта, верÑÐ¸Ñ WP, Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ PHP, плагинах и шаблонах " + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Ðдмин заметки" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "ÐовоÑти, объÑвлениÑ, маркетинг, без Ñпама" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Текущие ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ %s" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "ÐктивациÑ, Ð´ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¸ деинÑталлÑÑ†Ð¸Ñ " + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "РаÑÑылка " + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$s будет периодичеÑки приÑылать информацию %2$s Ñ Ñ†ÐµÐ»ÑŒÑŽ проверки безопаÑноÑти, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± обновлении функционала и Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии. " + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Какие предоÑтавлÑÑŽÑ‚ÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ лицензионного ключа?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "У Ð’Ð°Ñ ÐµÑть лицензионный ключ?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Политика КонфиденциальноÑти" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "ПользовательÑкое Ñоглашение" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Электронное пиÑьмо отправлÑетÑÑ Ð’Ð°Ð¼ на почту " + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ " + +#: templates/contact.php:78 +msgid "Contact" +msgstr "СвÑжитеÑÑŒ Ñ Ð½Ð°Ð¼Ð¸" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Выключить " + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Включить " + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "УÑтранение ошибок" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "ДейÑÑ‚Ð²Ð¸Ñ " + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Ð’Ñ‹ уверенны, что хотите удалить вÑе данные Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Удалить вÑе аккаунты" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "ОчиÑтить кÑш API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… Ñ Ñервера " + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Загрузить опцию базы данных " + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "УÑтановить опцию базы данных " + +#: templates/debug.php:182 +msgid "Key" +msgstr "Ключ " + +#: templates/debug.php:183 +msgid "Value" +msgstr "Значение " + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "ВерÑии SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "путь SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Путь Ð¼Ð¾Ð´ÑƒÐ»Ñ " + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "активный " + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Плагины " + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Шаблоны " + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "ОпиÑÐ°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть URL " + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Ðазвание " + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "CоÑтоÑние Freemius " + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Соединено " + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Заблокировано " + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s уÑтановок " + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Сайтов " + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Удалить" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Функционал Ð¼Ð¾Ð´ÑƒÐ»Ñ %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Пользователи " + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Подтвержден " + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s лицензий " + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID плагина " + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID тарифного плана " + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Выделенный объем памÑти" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Ðктивирован " + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Блокирование " + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Срок Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Журнал уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº " + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Ð’Ñе типы" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Ð’Ñе запроÑÑ‹ " + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Файл" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ " + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID процеÑÑа " + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Программа ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ " + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Сообщение " + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Фильтр " + +#: templates/debug.php:587 +msgid "Download" +msgstr "Скачать " + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Тип" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Маркер времени " + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "БезопаÑÐ½Ð°Ñ Ñтраница HTTPS %s воÑпроизводитÑÑ Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ реÑурÑа " + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Поддержка " + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "Ð¼Ñ " + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "ЗапроÑÑ‹ " + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Обновить " + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "СиÑтема оплаты " + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Ðазвание бизнеÑа " + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "ID налога/ÐДС " + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Поле Ð´Ð»Ñ Ð°Ð´Ñ€ÐµÑа %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Город " + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "ÐаÑеленный пункт " + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Ð˜Ð½Ð´ÐµÐºÑ " + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Страна " + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Выбрать Ñтрану " + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Штат " + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "ÐŸÑ€Ð¾Ð²Ð¸Ð½Ñ†Ð¸Ñ " + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Платежи" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Дата " + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "КоличеÑтво " + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Счет " + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Метод " + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Код" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Длинна " + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Путь " + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ‡Ð°Ñть " + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Результат" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Ðачало" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Конец " + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Журнал изменений " + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Ð’ %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s тому назад " + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "Ñек" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð¾Ð² и шаблонов " + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Итого" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "ПоÑледний " + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Спланированные задачи" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Модуль" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Тип Ð¼Ð¾Ð´ÑƒÐ»Ñ " + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Тип задачи" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Следующий " + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "БеÑÑрочный " + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Подать заÑвку на партнерÑтво " + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Ваша заÑвка на партнерÑтво Ñ %s принÑта! Войдите в Ваш кабинет партнера на %s" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "СпаÑибо за подачу заÑвки на партнерÑтво. Мы раÑÑмотрим Ваши данные на протÑжении Ñледующих 14 дней и ÑвÑжемÑÑ Ñ Ð’Ð°Ð¼Ð¸. " + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Ваш партнерÑкий аккаунт временно недоÑтупен. " + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "СпаÑибо за подачу заÑвки на партнерÑтво. К Ñожалению, мы принÑли решение отказать Вам в Ñтой возможноÑти. ПожалуйÑта, повторите попытку через 30 дней. " + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Из-за Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ ÑƒÑловий партнерÑтва мы вынуждены временно заблокировать Ваш аккаунт. ЕÑли у Ð’Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð¸ вопроÑÑ‹, пожалуйÑта, обратитеÑÑŒ в Ñлужбу поддержки. " + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Вам нравитÑÑ %s? Стань нашим партнером и зарабатывай ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Порекомендуй % новым пользователÑм и зарабатывай %s c каждой уÑпешной продажи. " + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Краткое опиÑание программы " + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s вознаграждениÑ, еÑли клиент купит новую лицензию." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Получай вознаграждение за автоматичеÑкие Ð¿Ñ€Ð¾Ð´Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ. " + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s данные cookies предоÑтавлÑÑŽÑ‚ÑÑ Ð¿Ð¾Ñле первого поÑещениÑ, чтобы макÑимально увеличить вероÑтноÑть заработка. " + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Ðеограниченное вознаграждение " + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "% Ð¼Ð¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ñумма выплаты " + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Выплаты производÑÑ‚ÑÑ Ð² долларах СШРчерез PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Мы выделÑем 30 дней Ð´Ð»Ñ Ð¿Ð¾ÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‚Ð¾Ð² и поÑтому Ð²Ð¾Ð·Ð½Ð°Ð³Ñ€Ð°Ð¶Ð´ÐµÐ½Ð¸Ñ Ð²Ñ‹Ð¿Ð»Ð°Ñ‡Ð¸Ð²Ð°ÑŽÑ‚ÑÑ Ð·Ð° покупки, которые были Ñовершены более чем 30 дней назад." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Партнер" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Полное имÑ" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð° PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Где Ð’Ñ‹ намерены продвигать %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Введите домен Вашего Ñайта или других Ñайтов на которых Ð’Ñ‹ намерены продвигать %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Добавьте другое доменное Ð¸Ð¼Ñ " + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Дополнительные доменные имена " + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Дополнительные доменные имена, где Ð’Ñ‹ будете продвигать продукт. " + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Методы Ð¿Ñ€Ð¾Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ " + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Социальные Ñети ( Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Мобильные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ " + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "ВебÑайт, Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð¸ ÑтатиÑтика Ñоциальных Ñетей (не обÑзательно)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "ПожалуйÑта, предоÑтавьте ÑоответÑтвенную ÑтатиÑтику вебÑайта или Ñтраницы Ñоциальных Ñетей, например, количеÑтво уникальных поÑетителей, количеÑтво подпиÑчиков, читателей, Ñ‚. д. ( Ñта Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ÑтанетÑÑ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð¾Ð¹). " + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Как Ð’Ñ‹ намерены продвигать наÑ?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "ПожалуйÑта, предоÑтавьте макÑимально детальную информацию о том, как Ð’Ñ‹ планируете продвигать %s." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Отмена " + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Стать партнером" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "ПожалуйÑта введите лицензионный ключ, который Ð’Ñ‹ получили на Ñлектронный Ð°Ð´Ñ€ÐµÑ Ñразу поÑле покупки. " + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Обновить лицензию" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "ОтпиÑатьÑÑ " + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "ПриÑоединитÑÑ " + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Данные о пользовании иÑÑледуютÑÑ Ñ Ñ†ÐµÐ»ÑŒÑŽ уÑовершенÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ %s и чтобы предоÑтавить Вам лучший опыт, поÑтавить приоритет на новых возможноÑÑ‚ÑÑ… и важном функционале. Мы будем очень благодарны, еÑли Ð’Ñ‹ переÑмотрите Ваше решение и позволите нам обрабатывать информацию о пользовании нашим продуктом." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "ЕÑли Ð’Ñ‹ нажмете \"ОтпиÑатьÑÑ\", Ð’Ñ‹ больше не будете получать информацию от %s на %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "There is a new version of %s available." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "New Version Available" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Закрыть " + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Отправить лицензионный ключ" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Введите ниже Ð°Ð´Ñ€ÐµÑ Ñвоей Ñлектронной почты, которую Ð’Ñ‹ иÑпользовали Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ð¹ и мы Вам отправим повторно Ваш лицензионный ключ. " + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Ð’Ñ‹ уже на раÑÑтоÑнии одного клика от начала Вашего беÑплатного %1$s - дневного теÑтового периода по тарифному плану %2$s. " + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Ð’ ÑоответÑтвии Ñ Ñ€ÑƒÐºÐ¾Ð²Ð¾Ð´Ñтвом WordPress.org, перед началом теÑтового периода мы проÑим, чтобы Ð’Ñ‹ приÑоединилиÑÑŒ к нашему ÑообщеÑтву предоÑтавив информацию о Вашем Ñайте и также Ваши личные данные, тем Ñамым разрешив %s периодичеÑки отправлÑть ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð½Ð° %s Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹ об обновлениÑÑ… и Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ теÑтового периода. " + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Премиум " + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "skip" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Кликните, чтобы поÑмотреть Ñнимок %d на широком Ñкране. " + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Ðеограниченные Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ " + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Локальный хоÑтинг " + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "ОÑталоÑÑŒ %s " + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "ПоÑледнÑÑ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Ðннулирована " + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "БеÑÑрочный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Owner Name" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Owner Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Owner ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Subscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Извините за неудобÑтво. Мы будем рады помочь, еÑли Ð’Ñ‹ нам предоÑтавите Ñту возможноÑть. " + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "СвÑзатьÑÑ Ñо Ñлужбой поддержки" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Ðнонимный отзыв " + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Деактивировать " + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Ðктивировать %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "ЕÑли у Ð’Ð°Ñ ÐµÑть времÑ, пожалуйÑта, Ñообщите причину почему Ð’Ñ‹ %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "Ð”ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ " + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "Переключение " + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Отправить&%s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "ПожалуйÑта, укажите причину, чтобы мы могли иÑправитьÑÑ. " + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Да - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "ПропуÑтить & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Ðажмите здеÑÑŒ, чтобы пользоватьÑÑ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð¾Ð¼ анонимно. " + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Возможно, Ð’Ñ‹ не обратили внимание, но Ð’Ñ‹ не обÑзаны делитьÑÑ Ð½Ð¸ÐºÐ°ÐºÐ¸Ð¼Ð¸ данными и можете проÑто % кнопку \"ПриÑоединитьÑÑ\". " diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot new file mode 100644 index 0000000..5707fe3 --- /dev/null +++ b/freemius/languages/freemius.pot @@ -0,0 +1,2383 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +msgid "" +msgstr "" +"Project-Id-Version: freemius\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language-Team: Freemius Team \n" +"Last-Translator: Vova Feldman \n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: includes/class-freemius.php:1783, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "" + +#: includes/class-freemius.php:1790 +msgid "Would you like to proceed with the update?" +msgstr "" + +#: includes/class-freemius.php:1998 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:2000 +msgid "Error" +msgstr "" + +#: includes/class-freemius.php:2390 +msgid "I found a better %s" +msgstr "" + +#: includes/class-freemius.php:2392 +msgid "What's the %s's name?" +msgstr "" + +#: includes/class-freemius.php:2398 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "" + +#: includes/class-freemius.php:2400 +msgid "Deactivation" +msgstr "" + +#: includes/class-freemius.php:2401 +msgid "Theme Switch" +msgstr "" + +#: includes/class-freemius.php:2410, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "" + +#: includes/class-freemius.php:2418 +msgid "I no longer need the %s" +msgstr "" + +#: includes/class-freemius.php:2425 +msgid "I only needed the %s for a short period" +msgstr "" + +#: includes/class-freemius.php:2431 +msgid "The %s broke my site" +msgstr "" + +#: includes/class-freemius.php:2438 +msgid "The %s suddenly stopped working" +msgstr "" + +#: includes/class-freemius.php:2448 +msgid "I can't pay for it anymore" +msgstr "" + +#: includes/class-freemius.php:2450 +msgid "What price would you feel comfortable paying?" +msgstr "" + +#: includes/class-freemius.php:2456 +msgid "I don't like to share my information with you" +msgstr "" + +#: includes/class-freemius.php:2477 +msgid "The %s didn't work" +msgstr "" + +#: includes/class-freemius.php:2487 +msgid "I couldn't understand how to make it work" +msgstr "" + +#: includes/class-freemius.php:2495 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "" + +#: includes/class-freemius.php:2497 +msgid "What feature?" +msgstr "" + +#: includes/class-freemius.php:2501 +msgid "The %s is not working" +msgstr "" + +#: includes/class-freemius.php:2503 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2507 +msgid "It's not what I was looking for" +msgstr "" + +#: includes/class-freemius.php:2509 +msgid "What you've been looking for?" +msgstr "" + +#: includes/class-freemius.php:2513 +msgid "The %s didn't work as expected" +msgstr "" + +#: includes/class-freemius.php:2515 +msgid "What did you expect?" +msgstr "" + +#: includes/class-freemius.php:3370, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "" + +#: includes/class-freemius.php:4122 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "" + +#: includes/class-freemius.php:4124 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "" + +#: includes/class-freemius.php:4131 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4236 +msgid "Yes - do your thing" +msgstr "" + +#: includes/class-freemius.php:4241 +msgid "No - just deactivate" +msgstr "" + +#: includes/class-freemius.php:4286, includes/class-freemius.php:4795, includes/class-freemius.php:5936, includes/class-freemius.php:12614, includes/class-freemius.php:15975, includes/class-freemius.php:16063, includes/class-freemius.php:16229, includes/class-freemius.php:18688, includes/class-freemius.php:18698, includes/class-freemius.php:19334, includes/class-freemius.php:20207, includes/class-freemius.php:20322, includes/class-freemius.php:20466, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "" + +#: includes/class-freemius.php:4355 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "" + +#: includes/class-freemius.php:4792 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "" + +#: includes/class-freemius.php:4793 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "" + +#: includes/class-freemius.php:4965, includes/class-freemius.php:4990, includes/class-freemius.php:19405 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "" + +#: includes/class-freemius.php:5624 +msgid "Premium %s version was successfully activated." +msgstr "" + +#: includes/class-freemius.php:5636, includes/class-freemius.php:7504 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "" + +#: includes/class-freemius.php:5651 +msgid "You have a %s license." +msgstr "" + +#: includes/class-freemius.php:5655, includes/class-freemius.php:15396, includes/class-freemius.php:15407, includes/class-freemius.php:18599, includes/class-freemius.php:18929, includes/class-freemius.php:18995, includes/class-freemius.php:19159 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "" + +#: includes/class-freemius.php:5919 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:5923 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "" + +#: includes/class-freemius.php:5932, templates/add-ons.php:130, templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "" + +#: includes/class-freemius.php:5933 +msgid "Purchase License" +msgstr "" + +#: includes/class-freemius.php:6868, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:6872 +msgid "start the trial" +msgstr "" + +#: includes/class-freemius.php:6873, templates/connect.php:167 +msgid "complete the install" +msgstr "" + +#: includes/class-freemius.php:6986 +msgid "You are just one step away - %s" +msgstr "" + +#: includes/class-freemius.php:6989 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "" + +#: includes/class-freemius.php:7067 +msgid "We made a few tweaks to the %s, %s" +msgstr "" + +#: includes/class-freemius.php:7071 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7503 +msgid "The upgrade of %s was successfully completed." +msgstr "" + +#: includes/class-freemius.php:9665, includes/class-fs-plugin-updater.php:1014, includes/class-fs-plugin-updater.php:1209, includes/class-fs-plugin-updater.php:1216, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "" + +#: includes/class-freemius.php:9667, templates/account.php:313, templates/account.php:321, templates/debug.php:361, templates/debug.php:522 +msgid "Plugin" +msgstr "" + +#: includes/class-freemius.php:9668, templates/account.php:314, templates/account.php:322, templates/debug.php:361, templates/debug.php:522, templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "" + +#: includes/class-freemius.php:12085 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "" + +#: includes/class-freemius.php:12481 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:12601 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:12603 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:12877 +msgid "Account is pending activation." +msgstr "" + +#: includes/class-freemius.php:12989, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php:13001, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:13005 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:15378 +msgid "%s activation was successfully completed." +msgstr "" + +#: includes/class-freemius.php:15392 +msgid "Your account was successfully activated with the %s plan." +msgstr "" + +#: includes/class-freemius.php:15403, includes/class-freemius.php:18991 +msgid "Your trial has been successfully started." +msgstr "" + +#: includes/class-freemius.php:15973, includes/class-freemius.php:16061, includes/class-freemius.php:16227 +msgid "Couldn't activate %s." +msgstr "" + +#: includes/class-freemius.php:15974, includes/class-freemius.php:16062, includes/class-freemius.php:16228 +msgid "Please contact us with the following message:" +msgstr "" + +#: includes/class-freemius.php:16058 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php:16585, includes/class-freemius.php:21339 +msgid "Upgrade" +msgstr "" + +#: includes/class-freemius.php:16591 +msgid "Start Trial" +msgstr "" + +#: includes/class-freemius.php:16593 +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:16672, includes/class-freemius.php:16674 +msgid "Affiliation" +msgstr "" + +#: includes/class-freemius.php:16702, includes/class-freemius.php:16704, templates/account.php:177, templates/debug.php:326 +msgid "Account" +msgstr "" + +#: includes/class-freemius.php:16717, includes/class-freemius.php:16719, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "" + +#: includes/class-freemius.php:16729, includes/class-freemius.php:16731, includes/class-freemius.php:21353, templates/account.php:105, templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "" + +#: includes/class-freemius.php:16765 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:16765 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php:16767, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:16980, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "" + +#: includes/class-freemius.php:17925 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "" + +#: includes/class-freemius.php:17926 +msgctxt "a positive response" +msgid "Right on" +msgstr "" + +#: includes/class-freemius.php:18590 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18592 +msgid "%s Add-on was successfully purchased." +msgstr "" + +#: includes/class-freemius.php:18595 +msgid "Download the latest version" +msgstr "" + +#: includes/class-freemius.php:18681 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php:18687, includes/class-freemius.php:19118, includes/class-freemius.php:19207 +msgid "Error received from the server:" +msgstr "" + +#: includes/class-freemius.php:18697 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php:18891, includes/class-freemius.php:19123, includes/class-freemius.php:19178, includes/class-freemius.php:19281 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "" + +#: includes/class-freemius.php:18904 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php:18905, templates/account.php:107, templates/add-ons.php:191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "" + +#: includes/class-freemius.php:18910 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "" + +#: includes/class-freemius.php:18914, includes/class-freemius.php:18973 +msgid "Please contact us here" +msgstr "" + +#: includes/class-freemius.php:18925 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:18926 +msgid "Your plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18943 +msgid "Your plan was successfully changed to %s." +msgstr "" + +#: includes/class-freemius.php:18959 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "" + +#: includes/class-freemius.php:18961 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:18969 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "" + +#: includes/class-freemius.php:18982 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "" + +#: includes/class-freemius.php:19005 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "" + +#: includes/class-freemius.php:19007 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:19114 +msgid "It looks like the license could not be activated." +msgstr "" + +#: includes/class-freemius.php:19156 +msgid "Your license was successfully activated." +msgstr "" + +#: includes/class-freemius.php:19182 +msgid "It looks like your site currently doesn't have an active license." +msgstr "" + +#: includes/class-freemius.php:19206 +msgid "It looks like the license deactivation failed." +msgstr "" + +#: includes/class-freemius.php:19234 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "" + +#: includes/class-freemius.php:19235 +msgid "O.K" +msgstr "" + +#: includes/class-freemius.php:19288 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19297 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "" + +#: includes/class-freemius.php:19339 +msgid "You are already running the %s in a trial mode." +msgstr "" + +#: includes/class-freemius.php:19350 +msgid "You already utilized a trial before." +msgstr "" + +#: includes/class-freemius.php:19364 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "" + +#: includes/class-freemius.php:19375 +msgid "Plan %s does not support a trial period." +msgstr "" + +#: includes/class-freemius.php:19386 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:19436 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "" + +#: includes/class-freemius.php:19472 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19491 +msgid "Your %s free trial was successfully cancelled." +msgstr "" + +#: includes/class-freemius.php:19807 +msgid "Version %s was released." +msgstr "" + +#: includes/class-freemius.php:19807 +msgid "Please download %s." +msgstr "" + +#: includes/class-freemius.php:19814 +msgid "the latest %s version here" +msgstr "" + +#: includes/class-freemius.php:19819 +msgid "New" +msgstr "" + +#: includes/class-freemius.php:19824 +msgid "Seems like you got the latest release." +msgstr "" + +#: includes/class-freemius.php:19825 +msgid "You are all good!" +msgstr "" + +#: includes/class-freemius.php:20095 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "" + +#: includes/class-freemius.php:20234 +msgid "Site successfully opted in." +msgstr "" + +#: includes/class-freemius.php:20235, includes/class-freemius.php:21055 +msgid "Awesome" +msgstr "" + +#: includes/class-freemius.php:20251, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "" + +#: includes/class-freemius.php:20252 +msgid "Thank you!" +msgstr "" + +#: includes/class-freemius.php:20259 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "" + +#: includes/class-freemius.php:20388 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "" + +#: includes/class-freemius.php:20394 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "" + +#: includes/class-freemius.php:20399 +msgid "%s is the new owner of the account." +msgstr "" + +#: includes/class-freemius.php:20401 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "" + +#: includes/class-freemius.php:20421 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "" + +#: includes/class-freemius.php:20422 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:20429 +msgid "Change Ownership" +msgstr "" + +#: includes/class-freemius.php:20437 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "" + +#: includes/class-freemius.php:20449 +msgid "Please provide your full name." +msgstr "" + +#: includes/class-freemius.php:20454 +msgid "Your name was successfully updated." +msgstr "" + +#: includes/class-freemius.php:20515 +msgid "You have successfully updated your %s." +msgstr "" + +#: includes/class-freemius.php:20655 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "" + +#: includes/class-freemius.php:20656 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "" + +#: includes/class-freemius.php:21095 +msgctxt "exclamation" +msgid "Hey" +msgstr "" + +#: includes/class-freemius.php:21095 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "" + +#: includes/class-freemius.php:21103 +msgid "No commitment for %s days - cancel anytime!" +msgstr "" + +#: includes/class-freemius.php:21104 +msgid "No credit card required" +msgstr "" + +#: includes/class-freemius.php:21111, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "" + +#: includes/class-freemius.php:21188 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:21197 +msgid "Learn more" +msgstr "" + +#: includes/class-freemius.php:21377, templates/account.php:474, templates/account.php:595, templates/connect.php:171, templates/connect.php:421, templates/forms/license-activation.php:25, templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "" + +#: includes/class-freemius.php:21378, templates/account.php:543, templates/account.php:594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "" + +#: includes/class-freemius.php:21469, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "" + +#: includes/class-freemius.php:21471, includes/class-freemius.php:21479, templates/account/partials/site.php:43, templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "" + +#: includes/class-freemius.php:21705 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:21713 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:21726 +msgid "Please follow these steps to complete the upgrade" +msgstr "" + +#: includes/class-freemius.php:21730 +msgid "Download the latest %s version" +msgstr "" + +#: includes/class-freemius.php:21734 +msgid "Upload and activate the downloaded version" +msgstr "" + +#: includes/class-freemius.php:21736 +msgid "How to upload and activate?" +msgstr "" + +#: includes/class-freemius.php:21870 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "" + +#: includes/class-freemius.php:22031 +msgid "Auto installation only works for opted-in users." +msgstr "" + +#: includes/class-freemius.php:22041, includes/class-freemius.php:22074, includes/class-fs-plugin-updater.php:1188, includes/class-fs-plugin-updater.php:1202 +msgid "Invalid module ID." +msgstr "" + +#: includes/class-freemius.php:22050, includes/class-fs-plugin-updater.php:1224 +msgid "Premium version already active." +msgstr "" + +#: includes/class-freemius.php:22057 +msgid "You do not have a valid license to access the premium version." +msgstr "" + +#: includes/class-freemius.php:22064 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "" + +#: includes/class-freemius.php:22082, includes/class-fs-plugin-updater.php:1223 +msgid "Premium add-on version already installed." +msgstr "" + +#: includes/class-freemius.php:22427 +msgid "View paid features" +msgstr "" + +#: includes/class-freemius.php:22749 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:22750 +msgid "Thank you so much for using %s!" +msgstr "" + +#: includes/class-freemius.php:22756 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "" + +#: includes/class-freemius.php:22760 +msgid "Thank you so much for using our products!" +msgstr "" + +#: includes/class-freemius.php:22761 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "" + +#: includes/class-freemius.php:22780 +msgid "%s and its add-ons" +msgstr "" + +#: includes/class-freemius.php:22789 +msgid "Products" +msgstr "" + +#: includes/class-freemius.php:22796, templates/connect.php:272 +msgid "Yes" +msgstr "" + +#: includes/class-freemius.php:22797, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:22798, templates/connect.php:278 +msgid "No" +msgstr "" + +#: includes/class-freemius.php:22800, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:22810 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php:22812, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "" + +#: includes/class-freemius.php:23094 +msgid "License key is empty." +msgstr "" + +#: includes/class-fs-plugin-updater.php:204, templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:209, templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:319, includes/class-fs-plugin-updater.php:352 +msgid "There is a %s of %s available." +msgstr "" + +#: includes/class-fs-plugin-updater.php:321, includes/class-fs-plugin-updater.php:357 +msgid "new Beta version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:322, includes/class-fs-plugin-updater.php:358 +msgid "new version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:381 +msgid "Important Upgrade Notice:" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1253 +msgid "Installing plugin: %s" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1294 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1476 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Purchase More" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:515, templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:519 +msgid "Start my free %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:717 +msgid "Install Free Version Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:718, templates/account.php:534 +msgid "Install Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:727 +msgid "Install Free Version Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:728, templates/add-ons.php:262, templates/auto-installation.php:111, templates/account/partials/addon.php:327, templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:744 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:745, templates/account.php:85, templates/add-ons.php:34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:760, templates/add-ons.php:268, templates/account/partials/addon.php:318, templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:762, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:763, templates/account.php:109, templates/add-ons.php:269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:975 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:976, templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:977 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:978 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:979 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:994 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1004 +msgid "Plugin Install" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1076 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1102 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1108, includes/fs-plugin-info-dialog.php:1128 +msgctxt "as every month" +msgid "Monthly" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1111 +msgctxt "as once a year" +msgid "Annual" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1114 +msgid "Lifetime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1128, includes/fs-plugin-info-dialog.php:1130, includes/fs-plugin-info-dialog.php:1132 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1130 +msgctxt "as once a year" +msgid "Annually" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1132 +msgctxt "as once a year" +msgid "Once" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1138 +msgid "Single Site License" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1140 +msgid "Unlimited Licenses" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1142 +msgid "Up to %s Sites" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1152, templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1159, templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1213 +msgctxt "noun" +msgid "Price" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1261 +msgid "Save %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1271 +msgid "No commitment for %s - cancel anytime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1274 +msgid "After your free %s, pay as little as %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1285 +msgid "Details" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1289, templates/account.php:96, templates/debug.php:203, templates/debug.php:240, templates/debug.php:454, templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1296 +msgctxt "as the plugin author" +msgid "Author" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1303 +msgid "Last Updated" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1308, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1317 +msgid "Requires WordPress Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1318 +msgid "%s or higher" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1325 +msgid "Compatible up to" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1333 +msgid "Downloaded" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1337 +msgid "%s time" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1339 +msgid "%s times" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1349 +msgid "WordPress.org Plugin Page" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1357 +msgid "Plugin Homepage" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1365, includes/fs-plugin-info-dialog.php:1447 +msgid "Donate to this plugin" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1372 +msgid "Average Rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1379 +msgid "based on %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1383 +msgid "%s rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1385 +msgid "%s ratings" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1400 +msgid "%s star" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1402 +msgid "%s stars" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1413 +msgid "Click to see reviews that provided a rating of %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1426 +msgid "Contributors" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1455, includes/fs-plugin-info-dialog.php:1457 +msgid "Warning" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1455 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1457 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1476 +msgid "Paid add-on must be deployed to Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1477 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1498 +msgid "Newer Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1499 +msgid "Newer Free Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1506 +msgid "Latest Version Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1507 +msgid "Latest Free Version Installed" +msgstr "" + +#: templates/account.php:86, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "" + +#: templates/account.php:87, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "" + +#: templates/account.php:90, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "" + +#: templates/account.php:91, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "" + +#: templates/account.php:92, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "" + +#: templates/account.php:93, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php:95, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php:98, templates/account/partials/addon.php:38, templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php:100, templates/account/partials/addon.php:40, templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "" + +#: templates/account.php:101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "" + +#: templates/account.php:102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "" + +#: templates/account.php:103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "" + +#: templates/account.php:104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "" + +#: templates/account.php:106, templates/account/partials/addon.php:46, templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "" + +#: templates/account.php:108, templates/add-ons.php:187, templates/plugin-info/features.php:72, templates/account/partials/addon.php:48, templates/account/partials/site.php:31 +msgid "Free" +msgstr "" + +#: templates/account.php:110, templates/debug.php:373, includes/customizer/class-fs-customizer-upsell-control.php:106, templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "" + +#: templates/account.php:227, templates/account/partials/addon.php:211, templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "" + +#: templates/account.php:250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "" + +#: templates/account.php:250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "" + +#: templates/account.php:292, templates/debug.php:489 +msgid "Name" +msgstr "" + +#: templates/account.php:298, templates/debug.php:490 +msgid "Email" +msgstr "" + +#: templates/account.php:305, templates/debug.php:372, templates/debug.php:528 +msgid "User ID" +msgstr "" + +#: templates/account.php:322, templates/account.php:608, templates/account.php:653, templates/debug.php:238, templates/debug.php:366, templates/debug.php:451, templates/debug.php:488, templates/debug.php:526, templates/debug.php:599, templates/account/payments.php:35, templates/debug/logger.php:21 +msgid "ID" +msgstr "" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "" + +#: templates/account.php:332 +msgid "No ID" +msgstr "" + +#: templates/account.php:337, templates/debug.php:245, templates/debug.php:374, templates/debug.php:455, templates/debug.php:492, templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "" + +#: templates/account.php:343, templates/debug.php:375, templates/debug.php:456, templates/debug.php:493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "" + +#: templates/account.php:373, templates/account/partials/site.php:112, templates/account/partials/site.php:114 +msgid "Trial" +msgstr "" + +#: templates/account.php:400, templates/debug.php:533, templates/account/partials/site.php:248 +msgid "License Key" +msgstr "" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "" + +#: templates/account.php:435 +msgid "not verified" +msgstr "" + +#: templates/account.php:444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "" + +#: templates/account.php:504 +msgid "Free version" +msgstr "" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "" + +#: templates/account.php:541, templates/account.php:749, templates/account/partials/site.php:237, templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "" + +#: templates/account.php:563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "" + +#: templates/account.php:588 +msgid "Sites" +msgstr "" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "" + +#: templates/account.php:609, templates/debug.php:369 +msgid "Address" +msgstr "" + +#: templates/account.php:610 +msgid "License" +msgstr "" + +#: templates/account.php:611 +msgid "Plan" +msgstr "" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "" + +#: templates/account.php:765 +msgid "Processing" +msgstr "" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "" + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "" + +#: templates/account.php:826, templates/account.php:843, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "" + +#: templates/account.php:841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "" + +#: templates/account.php:844, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "" + +#: templates/admin-notice.php:13, templates/forms/license-activation.php:209, templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "" + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "" + +#: templates/connect.php:172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "" + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "" + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "" + +#: templates/connect.php:253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "" + +#: templates/connect.php:256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "" + +#: templates/connect.php:315, templates/connect.php:652, templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "" + +#: templates/connect.php:359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "" + +#: templates/connect.php:391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "" + +#: templates/debug.php:54, templates/debug.php:250, templates/debug.php:376, templates/debug.php:494 +msgid "Actions" +msgstr "" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "" + +#: templates/debug.php:182 +msgid "Key" +msgstr "" + +#: templates/debug.php:183 +msgid "Value" +msgstr "" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "" + +#: templates/debug.php:205, templates/debug.php:244 +msgid "Module Path" +msgstr "" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "" + +#: templates/debug.php:239, templates/debug.php:371, templates/debug.php:453, templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "" + +#: templates/debug.php:241, templates/debug.php:452 +msgid "Title" +msgstr "" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "" + +#: templates/debug.php:368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "" + +#: templates/debug.php:433, templates/debug.php:511, templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "" + +#: templates/debug.php:484 +msgid "Users" +msgstr "" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "" + +#: templates/debug.php:573, templates/debug.php:602, templates/debug/logger.php:25 +msgid "File" +msgstr "" + +#: templates/debug.php:574, templates/debug.php:600, templates/debug/logger.php:23 +msgid "Function" +msgstr "" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "" + +#: templates/debug.php:577, templates/debug.php:601, templates/debug/logger.php:24 +msgid "Message" +msgstr "" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "" + +#: templates/debug.php:587 +msgid "Download" +msgstr "" + +#: templates/debug.php:598, templates/debug/logger.php:22 +msgid "Type" +msgstr "" + +#: templates/debug.php:603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "" + +#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "" + +#: templates/account/billing.php:38, templates/account/billing.php:38 +msgid "Business name" +msgstr "" + +#: templates/account/billing.php:39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "" + +#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "City" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "Town" +msgstr "" + +#: templates/account/billing.php:47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "State" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "Province" +msgstr "" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "" + +#: templates/account/payments.php:38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "" + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "" + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "" + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "" + +#: templates/forms/affiliation.php:165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "" + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "" + +#: templates/forms/affiliation.php:223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "" + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:29, templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "" + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "" + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "" + +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "" + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +msgid "allow" +msgstr "" + +#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "" + +#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +msgid "skip" +msgstr "" + +#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "" + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "" + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "" diff --git a/freemius/languages/index.php b/freemius/languages/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/languages/index.php @@ -0,0 +1,3 @@ +plugins ) ) { + $fs_active_plugins->plugins = array(); + } + } + + if ( empty( $fs_active_plugins->abspath ) ) { + /** + * Store the WP install absolute path reference to identify environment change + * while replicating the storage. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $fs_active_plugins->abspath = ABSPATH; + } else { + if ( ABSPATH !== $fs_active_plugins->abspath ) { + /** + * WordPress path has changed, cleanup the SDK references cache. + * This resolves issues triggered when spinning a staging environments + * while replicating the database. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $fs_active_plugins->abspath = ABSPATH; + $fs_active_plugins->plugins = array(); + unset( $fs_active_plugins->newest ); + } else { + /** + * Make sure SDK references are still valid. This resolves + * issues when users hard delete modules via FTP. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $has_changes = false; + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( ! file_exists( ( isset( $data->type ) && 'theme' === $data->type ? $themes_directory : WP_PLUGIN_DIR ) . '/' . $sdk_path ) ) { + unset( $fs_active_plugins->plugins[ $sdk_path ] ); + $has_changes = true; + } + } + + if ( $has_changes ) { + if ( empty( $fs_active_plugins->plugins ) ) { + unset( $fs_active_plugins->newest ); + } + + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + } + } + + if ( ! function_exists( 'fs_find_direct_caller_plugin_file' ) ) { + require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-1.1.7.1.php'; + } + + if ( ! function_exists( 'fs_get_plugins' ) ) { + require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-2.2.1.php'; + } + + // Update current SDK info based on the SDK path. + if ( ! isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) || + $this_sdk_version != $fs_active_plugins->plugins[ $this_sdk_relative_path ]->version + ) { + if ( $is_theme ) { + $plugin_path = basename( dirname( $this_sdk_relative_path ) ); + } else { + $plugin_path = plugin_basename( fs_find_direct_caller_plugin_file( $file_path ) ); + } + + $fs_active_plugins->plugins[ $this_sdk_relative_path ] = (object) array( + 'version' => $this_sdk_version, + 'type' => ( $is_theme ? 'theme' : 'plugin' ), + 'timestamp' => time(), + 'plugin_path' => $plugin_path, + ); + } + + $is_current_sdk_newest = isset( $fs_active_plugins->newest ) && ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path ); + + if ( ! isset( $fs_active_plugins->newest ) ) { + /** + * This will be executed only once, for the first time a Freemius powered plugin is activated. + */ + fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); + + $is_current_sdk_newest = true; + } else if ( version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '<' ) ) { + /** + * Current SDK is newer than the newest stored SDK. + */ + fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); + + if ( class_exists( 'Freemius' ) ) { + // Older SDK version was already loaded. + + if ( ! $fs_active_plugins->newest->in_activation ) { + // Re-order plugins to load this plugin first. + fs_newest_sdk_plugin_first(); + } + + // Refresh page. + fs_redirect( $_SERVER['REQUEST_URI'] ); + } + } else { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $fs_newest_sdk = $fs_active_plugins->newest; + $fs_newest_sdk = $fs_active_plugins->plugins[ $fs_newest_sdk->sdk_path ]; + + $is_newest_sdk_type_theme = ( isset( $fs_newest_sdk->type ) && 'theme' === $fs_newest_sdk->type ); + + if ( ! $is_newest_sdk_type_theme ) { + $is_newest_sdk_plugin_active = is_plugin_active( $fs_newest_sdk->plugin_path ); + } else { + $current_theme = wp_get_theme(); + $is_newest_sdk_plugin_active = ( $current_theme->stylesheet === $fs_newest_sdk->plugin_path ); + + $current_theme_parent = $current_theme->parent(); + + /** + * If the current theme is a child of the theme that has the newest SDK, this prevents a redirects loop + * from happening by keeping the SDK info stored in the `fs_active_plugins` option. + */ + if ( ! $is_newest_sdk_plugin_active && $current_theme_parent instanceof WP_Theme ) { + $is_newest_sdk_plugin_active = ( $fs_newest_sdk->plugin_path === $current_theme_parent->stylesheet ); + } + } + + if ( $is_current_sdk_newest && + ! $is_newest_sdk_plugin_active && + ! $fs_active_plugins->newest->in_activation + ) { + // If current SDK is the newest and the plugin is NOT active, it means + // that the current plugin in activation mode. + $fs_active_plugins->newest->in_activation = true; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + if ( ! $is_theme ) { + $sdk_starter_path = fs_normalize_path( WP_PLUGIN_DIR . '/' . $this_sdk_relative_path . '/start.php' ); + } else { + $sdk_starter_path = fs_normalize_path( + $themes_directory + . '/' + . str_replace( "../{$themes_directory_name}/", '', $this_sdk_relative_path ) + . '/start.php' ); + } + + $is_newest_sdk_path_valid = ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) && file_exists( $sdk_starter_path ); + + if ( ! $is_newest_sdk_path_valid && ! $is_current_sdk_newest ) { + // Plugin with newest SDK is no longer active, or SDK was moved to a different location. + unset( $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ] ); + } + + if ( ! ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) || + ! $is_newest_sdk_path_valid || + // Is newest SDK downgraded. + ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && + version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '>' ) ) + ) { + /** + * Plugin with newest SDK is no longer active. + * OR + * The newest SDK was in the current plugin. BUT, seems like the version of + * the SDK was downgraded to a lower SDK. + */ + // Find the active plugin with the newest SDK version and update the newest reference. + fs_fallback_to_newest_active_sdk(); + } else { + if ( $is_newest_sdk_plugin_active && + $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && + ( $fs_active_plugins->newest->in_activation || + ( class_exists( 'Freemius' ) && ( ! defined( 'WP_FS__SDK_VERSION' ) || version_compare( WP_FS__SDK_VERSION, $this_sdk_version, '<' ) ) ) + ) + + ) { + if ( $fs_active_plugins->newest->in_activation && ! $is_newest_sdk_type_theme ) { + // Plugin no more in activation. + $fs_active_plugins->newest->in_activation = false; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + // Reorder plugins to load plugin with newest SDK first. + if ( fs_newest_sdk_plugin_first() ) { + // Refresh page after re-order to make sure activated plugin loads newest SDK. + if ( class_exists( 'Freemius' ) ) { + fs_redirect( $_SERVER['REQUEST_URI'] ); + } + } + } + } + } + + if ( class_exists( 'Freemius' ) ) { + // SDK was already loaded. + return; + } + + if ( version_compare( $this_sdk_version, $fs_active_plugins->newest->version, '<' ) ) { + $newest_sdk = $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ]; + + $plugins_or_theme_dir_path = ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) ? + WP_PLUGIN_DIR : + $themes_directory; + + $newest_sdk_starter = fs_normalize_path( + $plugins_or_theme_dir_path + . '/' + . str_replace( "../{$themes_directory_name}/", '', $fs_active_plugins->newest->sdk_path ) + . '/start.php' ); + + if ( file_exists( $newest_sdk_starter ) ) { + // Reorder plugins to load plugin with newest SDK first. + fs_newest_sdk_plugin_first(); + + // There's a newer SDK version, load it instead of the current one! + require_once $newest_sdk_starter; + + return; + } + } + + #endregion SDK Selection Logic -------------------------------------------------------------------- + + #region Hooks & Filters Collection -------------------------------------------------------------------- + + /** + * Freemius hooks (actions & filters) tags structure: + * + * fs_{filter/action_name}_{plugin_slug} + * + * -------------------------------------------------------- + * + * Usage with WordPress' add_action() / add_filter(): + * + * add_action('fs_{filter/action_name}_{plugin_slug}', $callable); + * + * -------------------------------------------------------- + * + * Usage with Freemius' instance add_action() / add_filter(): + * + * // No need to add 'fs_' prefix nor '_{plugin_slug}' suffix. + * my_freemius()->add_action('{action_name}', $callable); + * + * -------------------------------------------------------- + * + * Freemius filters collection: + * + * fs_connect_url_{plugin_slug} + * fs_trial_promotion_message_{plugin_slug} + * fs_is_long_term_user_{plugin_slug} + * fs_uninstall_reasons_{plugin_slug} + * fs_is_plugin_update_{plugin_slug} + * fs_api_domains_{plugin_slug} + * fs_email_template_sections_{plugin_slug} + * fs_support_forum_submenu_{plugin_slug} + * fs_support_forum_url_{plugin_slug} + * fs_connect_message_{plugin_slug} + * fs_connect_message_on_update_{plugin_slug} + * fs_uninstall_confirmation_message_{plugin_slug} + * fs_pending_activation_message_{plugin_slug} + * fs_is_submenu_visible_{plugin_slug} + * fs_plugin_icon_{plugin_slug} + * fs_show_trial_{plugin_slug} + * + * -------------------------------------------------------- + * + * Freemius actions collection: + * + * fs_after_license_loaded_{plugin_slug} + * fs_after_license_change_{plugin_slug} + * fs_after_plans_sync_{plugin_slug} + * + * fs_after_account_details_{plugin_slug} + * fs_after_account_user_sync_{plugin_slug} + * fs_after_account_plan_sync_{plugin_slug} + * fs_before_account_load_{plugin_slug} + * fs_after_account_connection_{plugin_slug} + * fs_account_property_edit_{plugin_slug} + * fs_account_email_verified_{plugin_slug} + * fs_account_page_load_before_departure_{plugin_slug} + * fs_before_account_delete_{plugin_slug} + * fs_after_account_delete_{plugin_slug} + * + * fs_sdk_version_update_{plugin_slug} + * fs_plugin_version_update_{plugin_slug} + * + * fs_initiated_{plugin_slug} + * fs_after_init_plugin_registered_{plugin_slug} + * fs_after_init_plugin_anonymous_{plugin_slug} + * fs_after_init_plugin_pending_activations_{plugin_slug} + * fs_after_init_addon_registered_{plugin_slug} + * fs_after_init_addon_anonymous_{plugin_slug} + * fs_after_init_addon_pending_activations_{plugin_slug} + * + * fs_after_premium_version_activation_{plugin_slug} + * fs_after_free_version_reactivation_{plugin_slug} + * + * fs_after_uninstall_{plugin_slug} + * fs_before_admin_menu_init_{plugin_slug} + */ + + #endregion Hooks & Filters Collection -------------------------------------------------------------------- + + if ( ! class_exists( 'Freemius' ) ) { + + if ( ! defined( 'WP_FS__SDK_VERSION' ) ) { + define( 'WP_FS__SDK_VERSION', $this_sdk_version ); + } + + $plugins_or_theme_dir_path = fs_normalize_path( trailingslashit( $is_theme ? + $themes_directory : + WP_PLUGIN_DIR ) ); + + if ( 0 === strpos( $file_path, $plugins_or_theme_dir_path ) ) { + // No symlinks + } else { + /** + * This logic finds the SDK symlink and set WP_FS__DIR to use it. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + */ + $sdk_symlink = null; + + // Try to load SDK's symlink from cache. + if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + ! empty( $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink ) + ) { + $sdk_symlink = $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink; + if ( 0 === strpos( $sdk_symlink, $plugins_or_theme_dir_path ) ) { + /** + * Make the symlink path relative. + * + * @author Leo Fajardo (@leorw) + */ + $sdk_symlink = substr( $sdk_symlink, strlen( $plugins_or_theme_dir_path ) ); + + $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + $realpath = realpath( $plugins_or_theme_dir_path . $sdk_symlink ); + if ( ! is_string( $realpath ) || ! file_exists( $realpath ) ) { + $sdk_symlink = null; + } + } + + if ( empty( $sdk_symlink ) ) // Has symlinks, therefore, we need to configure WP_FS__DIR based on the symlink. + { + $partial_path_right = basename( $file_path ); + $partial_path_left = dirname( $file_path ); + $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); + + while ( '/' !== $partial_path_left && + ( false === $realpath || $file_path !== fs_normalize_path( $realpath ) ) + ) { + $partial_path_right = trailingslashit( basename( $partial_path_left ) ) . $partial_path_right; + $partial_path_left_prev = $partial_path_left; + $partial_path_left = dirname( $partial_path_left_prev ); + + /** + * Avoid infinite loop if for example `$partial_path_left_prev` is `C:/`, in this case, + * `dirname( 'C:/' )` will return `C:/`. + * + * @author Leo Fajardo (@leorw) + */ + if ( $partial_path_left === $partial_path_left_prev ) { + $partial_path_left = ''; + break; + } + + $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); + } + + if ( ! empty( $partial_path_left ) && '/' !== $partial_path_left ) { + $sdk_symlink = fs_normalize_path( dirname( $partial_path_right ) ); + + // Cache value. + if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) + ) { + $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + } + } + + if ( ! empty( $sdk_symlink ) ) { + // Set SDK dir to the symlink path. + define( 'WP_FS__DIR', $plugins_or_theme_dir_path . $sdk_symlink ); + } + } + + // Load SDK files. + require_once dirname( __FILE__ ) . '/require.php'; + + /** + * Quick shortcut to get Freemius for specified plugin. + * Used by various templates. + * + * @param number $module_id + * + * @return Freemius + */ + function freemius( $module_id ) { + return Freemius::instance( $module_id ); + } + + /** + * @param string $slug + * @param number $plugin_id + * @param string $public_key + * @param bool $is_live Is live or test plugin. + * @param bool $is_premium Hints freemius if running the premium plugin or not. + * + * @return Freemius + * + * @deprecated Please use fs_dynamic_init(). + */ + function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = true ) { + $fs = Freemius::instance( $plugin_id, $slug, true ); + $fs->init( $plugin_id, $public_key, $is_live, $is_premium ); + + return $fs; + } + + /** + * @param array $module Plugin or Theme details. + * + * @return Freemius + * @throws Freemius_Exception + */ + function fs_dynamic_init( $module ) { + $fs = Freemius::instance( $module['id'], $module['slug'], true ); + $fs->dynamic_init( $module ); + + return $fs; + } + + function fs_dump_log() { + FS_Logger::dump(); + } + } \ No newline at end of file diff --git a/freemius/templates/account.php b/freemius/templates/account.php new file mode 100644 index 0000000..0fdfe64 --- /dev/null +++ b/freemius/templates/account.php @@ -0,0 +1,932 @@ +get_slug(); + + /** + * @var FS_Plugin_Tag $update + */ + $update = $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ); + + if ( is_object($update) ) { + /** + * This logic is particularly required for multisite environment. + * If a module is site activated (not network) and not on the main site, + * the module will NOT be executed on the network level, therefore, the + * custom updates logic will not be executed as well, so unless we force + * the injection of the update into the updates transient, premium updates + * will not work. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $updater = FS_Plugin_Updater::instance( $fs ); + $updater->set_update_data( $update ); + } + + $is_paying = $fs->is_paying(); + $user = $fs->get_user(); + $site = $fs->get_site(); + $name = $user->get_name(); + $license = $fs->_get_license(); + $subscription = ( is_object( $license ) ? + $fs->_get_subscription( $license->id ) : + null ); + $plan = $fs->get_plan(); + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); + $is_paid_trial = $fs->is_paid_trial(); + $has_paid_plan = $fs->has_paid_plan(); + $show_upgrade = ( $has_paid_plan && ! $is_paying && ! $is_paid_trial ); + $trial_plan = $fs->get_trial_plan(); + + if ( $has_paid_plan ) { + $fs->_add_license_activation_dialog_box(); + } + + if ( fs_request_get_bool( 'auto_install' ) ) { + $fs->_add_auto_installation_dialog_box(); + } + + if ( fs_request_get_bool( 'activate_license' ) ) { + // Open the license activation dialog box on the account page. + add_action( 'admin_footer', array( + &$fs, + '_open_license_activation_dialog_box' + ) ); + } + + $payments = $fs->_fetch_payments(); + + $show_billing = ( is_array( $payments ) && 0 < count( $payments ) ); + + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); + + $show_plan_row = true; + $show_license_row = is_object( $license ); + + $site_view_params = array(); + + if ( fs_is_network_admin() ) { + $sites = Freemius::get_sites(); + $all_installs_plan_id = null; + $all_installs_license_id = ( $show_license_row ? $license->id : null ); + foreach ( $sites as $s ) { + $site_info = $fs->get_site_info( $s ); + $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); + $view_params = array( + 'freemius' => $fs, + 'license' => $license, + 'site' => $site_info, + 'install' => $install, + ); + + $site_view_params[] = $view_params; + + if ( empty( $install ) ) { + continue; + } + + if ( $show_plan_row ) { + if ( is_null( $all_installs_plan_id ) ) { + $all_installs_plan_id = $install->plan_id; + } else if ( $all_installs_plan_id != $install->plan_id ) { + $show_plan_row = false; + } + } + + if ( $show_license_row && $all_installs_license_id != $install->license_id ) { + $show_license_row = false; + } + } + } + + $is_child_license = ( is_object( $license ) && FS_Plugin_License::is_valid_id( $license->parent_license_id ) ); + $bundle_subscription = null; + + if ( + $show_plan_row && + is_object( $license ) && + FS_Plugin_License::is_valid_id( $license->parent_license_id ) + ) { + $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); + } + + $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + + $is_premium = $fs->is_premium(); +?> + + _get_subscription_cancellation_dialog_box_template_params( true ); + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } + ?> + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'account', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/account/billing.php b/freemius/templates/account/billing.php new file mode 100644 index 0000000..a4de409 --- /dev/null +++ b/freemius/templates/account/billing.php @@ -0,0 +1,423 @@ +get_slug(); + + $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); + $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); + + $billing = $fs->_fetch_billing(); + $has_billing = ( $billing instanceof FS_Billing ); + if ( ! $has_billing ) { + $billing = new FS_Billing(); + } +?> + +
    +
    +

    + > + + + + + + + + + + + + + + 'Afghanistan', + 'AX' => 'Aland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire, Saint Eustatius and Saba', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'VG' => 'British Virgin Islands', + 'BN' => 'Brunei', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CA' => 'Canada', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CW' => 'Curacao', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'CD' => 'Democratic Republic of the Congo', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'TL' => 'East Timor', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'CI' => 'Ivory Coast', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'XK' => 'Kosovo', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Laos', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macao', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MD' => 'Moldova', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territory', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'CG' => 'Republic of the Congo', + 'RE' => 'Reunion', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthelemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'ST' => 'Sao Tome and Principe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SX' => 'Sint Maarten', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'KR' => 'South Korea', + 'SS' => 'South Sudan', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syria', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'VI' => 'U.S. Virgin Islands', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'GB' => 'United Kingdom', + 'US' => 'United States', + 'UM' => 'United States Minor Outlying Islands', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'WF' => 'Wallis and Futuna', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + ) ?> + + + + + + +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/freemius/templates/account/index.php b/freemius/templates/account/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/account/index.php @@ -0,0 +1,3 @@ + +
    + + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/addon.php b/freemius/templates/account/partials/addon.php new file mode 100644 index 0000000..9441696 --- /dev/null +++ b/freemius/templates/account/partials/addon.php @@ -0,0 +1,407 @@ +get_slug(); + + $fs_blog_id = $VARS['fs_blog_id']; + + $active_plugins_directories_map = $VARS['active_plugins_directories_map']; + + $addon_info = $VARS['addon_info']; + $is_addon_activated = $fs->is_addon_activated( $addon_id ); + $is_addon_connected = $addon_info['is_connected']; + $is_addon_installed = $VARS['is_addon_installed']; + + $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? + freemius( $addon_id ) : + false; + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + + // Defaults. + $plan = null; + $is_paid_trial = false; + /** + * @var FS_Plugin_License $license + */ + $license = null; + $site = null; + $is_active_subscription = false; + $subscription = null; + $is_paying = false; + $show_upgrade = false; + + if ( is_object( $fs_addon ) ) { + $is_paying = $fs_addon->is_paying(); + $user = $fs_addon->get_user(); + $site = $fs_addon->get_site(); + $license = $fs_addon->_get_license(); + $subscription = ( is_object( $license ) ? + $fs_addon->_get_subscription( $license->id ) : + null ); + $plan = $fs_addon->get_plan(); + $plan_name = $plan->name; + $plan_title = $plan->title; + $is_paid_trial = $fs_addon->is_paid_trial(); + $show_upgrade = ( $fs_addon->has_paid_plan() && ! $is_paying && ! $is_paid_trial && ! $fs_addon->_has_premium_license() ); + $version = $fs_addon->get_plugin_version(); + } else if ( $is_addon_connected ) { + if ( + empty( $addon_info ) || + ! isset( $addon_info['site'] ) + ) { + $is_addon_connected = false; + } else { + /** + * @var FS_Site $site + */ + $site = $addon_info['site']; + $version = $addon_info['version']; + + $plan_name = isset( $addon_info['plan_name'] ) ? + $addon_info['plan_name'] : + ''; + + $plan_title = isset( $addon_info['plan_title'] ) ? + $addon_info['plan_title'] : + ''; + + if ( isset( $addon_info['license'] ) ) { + $license = $addon_info['license']; + } + + if ( isset( $addon_info['subscription'] ) ) { + $subscription = $addon_info['subscription']; + } + + $has_valid_and_active_license = ( + is_object( $license ) && + $license->is_active() && + $license->is_valid() + ); + + $is_paid_trial = ( + $site->is_trial() && + $has_valid_and_active_license && + ( $site->trial_plan_id == $license->plan_id ) + ); + } + } + + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); +?> +> + + + + + + + id ?> + + + + + + + + + + + is_trial() || is_object( $license ) ) : ?> + + + + is_trial() ) { + $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); + + $tags[] = array( + 'label' => sprintf( + ( $is_paid_trial ? + $renews_in_text : + $expires_in_text ), + human_time_diff( time(), strtotime( $site->trial_ends ) ) + ), + 'type' => ( $is_paid_trial ? 'success' : 'warn' ) + ); + } else { + if ( is_object( $license ) ) { + if ( $license->is_cancelled ) { + $tags[] = array( + 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_expired() ) { + $tags[] = array( + 'label' => fs_text_inline( 'Expired', 'expired', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_lifetime() ) { + $tags[] = array( + 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), + 'type' => 'success' + ); + } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), + 'type' => 'warn' + ); + } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), + 'type' => 'success' + ); + } + } + } + + foreach ( $tags as $t ) { + printf( '' . "\n", $t['type'], $t['label'] ); + } + ?> + + + + + + get_id(), + 'account', + 'deactivate_license', + fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false + ); + + $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + + if ( ! $license->is_lifetime() && $is_active_subscription ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'downgrade_account', + esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + false, + ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), + 'POST' + ); + } + } else if ( $is_paid_trial ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'cancel_trial', + esc_html( $cancel_trial_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + 'dashicons dashicons-download', + $cancel_trial_confirm_text, + 'POST' + ); + } else if ( ! is_object( $license ) || ! $license->is_features_enabled() ) { + $premium_license = $fs_addon->_get_available_premium_license(); + + if ( is_object( $premium_license ) ) { + $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); + $site = $fs_addon->get_site(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), + '', + array( + 'plugin_id' => $addon_id, + 'license_id' => $premium_license->id, + ) + ); + } + } + + if ( 0 == count( $buttons ) ) { + if ( $show_upgrade && $fs_addon->is_premium() ) { + $fs_addon->_add_license_activation_dialog_box(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + fs_esc_html_inline( 'Activate License', 'activate-license', $slug ), + 'activate-license-trigger ' . $fs_addon->get_unique_affix(), + array( + 'plugin_id' => $addon_id, + ), + false, + true + ); + } + + // Add sync license only if non of the other CTAs are visible. + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + $fs->get_unique_affix() . '_sync_license', + esc_html( $sync_license_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + true + ); + + } + } else if ( ! $show_upgrade ) { + if ( $fs->is_addon_installed( $addon_id ) ) { + $addon_file = $fs->get_addon_basename( $addon_id ); + + if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), + $activate_text + ); + } + } else { + if ( $fs->is_allowed_to_install() ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), + fs_text_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + $buttons[] = sprintf( + '%s', + $fs->_get_latest_download_local_url( $addon_id ), + esc_html( $download_latest_text ) + ); + } + } + } + + if ( $show_upgrade ) { + $buttons[] = sprintf( ' %s', + esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), + esc_attr( $addon_info['title'] ), + ( $fs_addon->has_free_plan() ? + $upgrade_text : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) + ); + } + + $buttons_count = count( $buttons ); + ?> + + + 1 ) : ?> +
    + + 1 ) : ?>
    + + + + + + + is_addon_installed( $addon_id ) ) : ?> + get_addon_basename( $addon_id ) ?> + + + + + is_allowed_to_install() ) : ?> + + + + + + + + + + + + get_id(), 'account', + 'delete_account', + fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false, + $show_upgrade + ); + } + ?> + + + + \ No newline at end of file diff --git a/freemius/templates/account/partials/deactivate-license-button.php b/freemius/templates/account/partials/deactivate-license-button.php new file mode 100644 index 0000000..123b092 --- /dev/null +++ b/freemius/templates/account/partials/deactivate-license-button.php @@ -0,0 +1,36 @@ + +
    + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/index.php b/freemius/templates/account/partials/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/account/partials/index.php @@ -0,0 +1,3 @@ +get_slug(); + $site = $VARS['site']; + $main_license = $VARS['license']; + $has_paid_plan = $fs->has_paid_plan(); + $is_premium = $fs->is_premium(); + $main_user = $fs->get_user(); + $blog_id = $site['blog_id']; + + $install = $VARS['install']; + $is_registered = ! empty( $install ); + $license = null; + $trial_plan = $fs->get_trial_plan(); + $free_text = fs_text_inline( 'Free', 'free', $slug ); +?> + data-install-id="id ?>"> + + + id ?> + + +
    + + + + +
    + + + + + + + + + + $fs, + 'slug' => $slug, + 'blog_id' => $blog_id, + 'class' => 'button-small', + ); + + $license = null; + if ( $is_registered ) { + $view_params['install_id'] = $install->id; + $view_params['is_localhost'] = $install->is_localhost(); + + $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); + $license = $has_license ? + $fs->_get_license_by_id( $install->license_id ) : + null; + } else { + $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); + } + + if ( is_object( $license ) ) { + $view_params['license'] = $license; + + // Show license deactivation button. + fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); + } else { + if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { + // Main license is available for activation. + $available_license = $main_license; + } else { + // Try to find any available license for activation. + $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); + } + + if ( is_object( $available_license ) ) { + $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); + + $view_params['license'] = $available_license; + $view_params['class'] .= ' button-primary'; + $view_params['plan'] = $premium_plan; + + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); + } + } + } ?> + + + + + is_trial() ) { + if ( $trial_plan->id == $install->trial_plan_id ) { + $plan_title = is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ); + } else { + $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); + } + } else { + $plan = $fs->_get_plan_by_id( $install->plan_id ); + $plan_title = strtoupper( is_string( $plan->title ) ? + $plan->title : + strtoupper( $free_text ) + ); + } + } + ?> + + + + + + + + + + + + + + + + + + > + + + + + + + + user_id != $main_user->id ) : ?> + user_id ) ?> + + + > + + + + + + + + > + + + + + + + + > + + + + + + + + + + > + + + + + + + + > + + + + + + + + + + > + + + + + + + + id != $license->id ) : ?> + _get_subscription( $license->id ) ?> + is_lifetime() && is_object( $subscription ) ) : ?> + + > + + is_active(); + + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + ?> + + + + + + + + + + +
    + : + license_id ) ) : ?> + + +
    + + id}", ':' ) ) ?> + + + +
    + +
    + : + get_name() ) ?>
    + : + email ) ?>
    + : + id ?>
    + : + public_key ) ?>
    + : + + secret_key, 0, 6 ) ) . str_pad( '', 23 * 6, '•' ) . htmlspecialchars( substr( $install->secret_key, - 3 ) ) ?> +
    + : + + secret_key, 0, 6 ) ) . str_pad( '', 23 * 6, '•' ) . htmlspecialchars( substr( $license->secret_key, - 3 ) ) ?> + + + +
    + : + + id ?> - billing_cycle ? + _fs_text_inline( 'Annual', 'annual', $slug ) : + _fs_text_inline( 'Monthly', 'monthly', $slug ) + ); + ?> + + is_first_payment_pending() ) : ?> + + is_first_payment_pending() ) : ?> + + + + expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + ?> + +
    + + + + +
    +
    + + + + \ No newline at end of file diff --git a/freemius/templates/account/payments.php b/freemius/templates/account/payments.php new file mode 100644 index 0000000..c9dea91 --- /dev/null +++ b/freemius/templates/account/payments.php @@ -0,0 +1,59 @@ +get_slug(); + + $payments = $fs->_fetch_payments(); + + $show_payments = ( is_array( $payments ) && 0 < count( $payments ) ); + + if ( $show_payments ) : +?> +
    +
    +

    + +
    + + + + + + + + + + + + + > + + + + + + + +
    id ?>created ) ) ?>formatted_gross() ?>is_migrated() ) : ?>
    +
    +
    +
    +get_slug(); + + $open_addon_slug = fs_request_get( 'slug' ); + + $open_addon = false; + + /** + * @var FS_Plugin[] + */ + $addons = $fs->get_addons(); + + $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); + + $account_addon_ids = $fs->get_updated_account_addons(); + + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); + + $has_tabs = $fs->_add_tabs_before_content(); + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; +?> +
    + +

    get_plugin_name() ) ) ?>

    + + + do_action( 'addons/after_title' ) ?> + +
    + +

    + +
      + + _get_addons_plans_and_pricing_map_by_id(); + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + ?> + + get_addon_basename( $addon->id ); + + $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); + $is_addon_activated = $is_addon_installed ? + $fs->is_addon_activated( $addon->id ) : + false; + + $is_plugin_active = ( + $is_addon_activated || + isset( $active_plugins_directories_map[ dirname( $basename ) ] ) + ); + + $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); + + $price = 0; + $has_trial = false; + $has_free_plan = false; + $has_paid_plan = false; + + if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { + $plans = $plans_and_pricing_by_addon_id[$addon->id]; + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + foreach ( $plans as $plan ) { + if ( ! isset( $plan->pricing ) || + ! is_array( $plan->pricing ) || + 0 == count( $plan->pricing ) + ) { + // No pricing means a free plan. + $has_free_plan = true; + continue; + } + + + $has_paid_plan = true; + $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); + + $min_price = 999999; + foreach ( $plan->pricing as $pricing ) { + if ( ! is_null( $pricing->annual_price ) && $pricing->annual_price > 0 ) { + $min_price = min( $min_price, $pricing->annual_price ); + } else if ( ! is_null( $pricing->monthly_price ) && $pricing->monthly_price > 0 ) { + $min_price = min( $min_price, 12 * $pricing->monthly_price ); + } + } + + if ( $min_price < 999999 ) { + $price = $min_price; + } + + } + } + + if ( ! $has_paid_plan && ! $has_free_plan ) { + continue; + } + } + ?> +
    • + get_id() . '&plugin=' . $addon->slug . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), + esc_attr( $addon->title ) + ) . ' class="thickbox%s">%s'; + + echo sprintf( + $view_details_link, + /** + * Additional class. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + ' fs-overlay', + /** + * Set the view details link text to an empty string since it is an overlay that + * doesn't really need a text and whose purpose is to open the details dialog when + * the card is clicked. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + '' + ); + ?> + info ) ) { + $addon->info = new stdClass(); + } + if ( ! isset( $addon->info->card_banner_url ) ) { + $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; + } + if ( ! isset( $addon->info->short_description ) ) { + $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; + } + ?> +
      +
        +
      • %s', + esc_html( $is_plugin_active ? + fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : + fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) + ) + ); + } + ?>
      • + +
      • title ?>
      • +
      • + 0) + $descriptors[] = '$' . number_format( $price, 2 ); + if ($has_trial) + $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + + echo implode(' - ', $descriptors) ?> +
      • +
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • + is_wp_org_compliant ); + + $is_allowed_to_install = ( + $fs->is_allowed_to_install() || + $is_free_only_wp_org_compliant + ); + + $show_premium_activation_or_installation_action = true; + + if ( ! in_array( $addon->id, $account_addon_ids ) ) { + $show_premium_activation_or_installation_action = false; + } else if ( $is_addon_installed ) { + /** + * If any add-on's version (free or premium) is installed, check if the + * premium version can be activated and show the relevant action. Otherwise, + * show the relevant action for the free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + $fs_addon = $is_addon_activated ? + $fs->get_addon_instance( $addon->id ) : + null; + + $premium_plugin_basename = is_object( $fs_addon ) ? + $fs_addon->premium_plugin_basename() : + "{$addon->premium_slug}/{$addon->slug}.php"; + + if ( + ( $is_addon_activated && $fs_addon->is_premium() ) || + file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) + ) { + $basename = $premium_plugin_basename; + } + + $show_premium_activation_or_installation_action = ( + ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && + /** + * This check is needed for cases when an active add-on doesn't have an + * associated Freemius instance. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + ( ! $is_plugin_active ) + ); + } + ?> + +
      • + + _get_latest_download_local_url( $addon->id ); + ?> + +
      • +
        + + %s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), + fs_esc_html_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + echo sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), + fs_text_inline( 'Activate', 'activate', $addon->slug ) + ); + } + ?> + + + +
        +
        +
      • + +
      +
      +
    • + + +
    +
    + + do_action( 'addons/after_addons' ) ?> +
    + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'addons', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/add-trial-to-pricing.php b/freemius/templates/add-trial-to-pricing.php new file mode 100644 index 0000000..24fc885 --- /dev/null +++ b/freemius/templates/add-trial-to-pricing.php @@ -0,0 +1,31 @@ + + \ No newline at end of file diff --git a/freemius/templates/admin-notice.php b/freemius/templates/admin-notice.php new file mode 100644 index 0000000..6079e71 --- /dev/null +++ b/freemius/templates/admin-notice.php @@ -0,0 +1,76 @@ + + data-id="" data-manager-id="" data-slug="" data-type="" + class=" fs-notice"> + + + +
    +
    + +
    + + +
    + diff --git a/freemius/templates/ajax-loader.php b/freemius/templates/ajax-loader.php new file mode 100644 index 0000000..bc116f8 --- /dev/null +++ b/freemius/templates/ajax-loader.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/freemius/templates/auto-installation.php b/freemius/templates/auto-installation.php new file mode 100644 index 0000000..1fc1278 --- /dev/null +++ b/freemius/templates/auto-installation.php @@ -0,0 +1,249 @@ +is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $title = $fs->get_plugin_title(); + + if ( $plugin_id != $fs->get_id() ) { + $addon = $fs->get_addon( $plugin_id ); + + if ( is_object( $addon ) ) { + $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); + } + } + + $plugin_title = sprintf( + '%s', + esc_html( $title ) + ); + + $sec_countdown = 30; + $countdown_html = sprintf( + esc_js( + /* translators: %s: Number of seconds */ + fs_text_inline( '%s sec', 'x-sec', $slug ) + ), + sprintf( '%s', $sec_countdown ) + ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + $params = array(); + $loader_html = fs_get_template( 'ajax-loader.php', $params ); + + // Pass unique auto installation URL if WP_Filesystem is needed. + $install_url = $fs->_get_sync_license_url( + $plugin_id, + true, + array( 'auto_install' => 'true' ) + ); + + + ob_start(); + + $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. + + $credentials = request_filesystem_credentials( + esc_url_raw( $install_url ), + $method, + false, + WP_PLUGIN_DIR, + array() + ); + + $credentials_form = ob_get_clean(); + + $require_credentials = ! empty( $credentials_form ); +?> +
    +
    +
    +

    +
    +
    + + +
    + +
    + +

    %s', + 'https://freemius.com', + 'freemius.com' + ), + $countdown_html + ) ?>

    + + +
    + +
    +
    ' + + diff --git a/freemius/templates/checkout.php b/freemius/templates/checkout.php new file mode 100644 index 0000000..a0969cc --- /dev/null +++ b/freemius/templates/checkout.php @@ -0,0 +1,337 @@ +get_slug(); + + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + 'mode' => 'dashboard', + 'trial' => fs_request_get_bool( 'trial' ), + ); + + $plan_id = fs_request_get( 'plan_id' ); + if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { + $context_params['plan_id'] = $plan_id; + } + + $licenses = fs_request_get( 'licenses' ); + if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { + $context_params['licenses'] = $licenses; + } + + $plugin_id = fs_request_get( 'plugin_id' ); + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + $plugin_id = $fs->get_id(); + } + + if ( $plugin_id == $fs->get_id() ) { + $is_premium = $fs->is_premium(); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + } else { + // Identify the module code version of the checkout context module. + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $is_premium = $fs_addon->is_premium(); + } else { + // If add-on isn't activated assume the premium version isn't installed. + $is_premium = false; + } + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $site = $fs->get_site(); + + if ( $plugin_id != $fs->get_id() ) { + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $addon_site = $fs_addon->get_site(); + if ( is_object( $addon_site ) ) { + $site = $addon_site; + } + } + } + + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $site, + $timestamp, + 'checkout' + ) ); + } else { + $current_user = Freemius::_get_current_wp_user(); + + // Add site and user info to the request, this information + // is NOT being stored unless the user complete the purchase + // and agrees to the TOS. + $context_params = array_merge( $context_params, array( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_email' => $current_user->user_email, + 'home_url' => home_url(), + ) ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + if ( is_object( $fs_user ) && $fs_user->is_verified() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs_user, + $timestamp, + 'checkout' + ) ); + } + } + + if ( $fs->is_payments_sandbox() ) { + // Append plugin secure token for sandbox mode authentication. + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + + /** + * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. + */ + if ( empty( $context_params['s_ctx_ts'] ) ) { + $context_params['s_ctx_ts'] = $timestamp; + } + } + + $return_url = $fs->_get_sync_license_url( $plugin_id ); + + $can_user_install = ( + ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || + ( $fs->is_theme() && current_user_can( 'install_themes' ) ) + ); + + $query_params = array_merge( $context_params, $_GET, array( + // Current plugin version. + 'plugin_version' => $fs->get_plugin_version(), + 'sdk_version' => WP_FS__SDK_VERSION, + 'is_premium' => $is_premium ? 'true' : 'false', + 'can_install' => $can_user_install ? 'true' : 'false', + 'return_url' => $return_url, + ) ); + + $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); + if ( false !== $xdebug_session ) { + $query_params['XDEBUG_SESSION'] = $xdebug_session; + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), + ); + fs_require_once_template('secure-https-header.php', $view_params); +?> +
    +
    + +
    \ No newline at end of file diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php new file mode 100644 index 0000000..34a1132 --- /dev/null +++ b/freemius/templates/connect.php @@ -0,0 +1,971 @@ +get_slug(); + + $is_pending_activation = $fs->is_pending_activation(); + $is_premium_only = $fs->is_only_premium(); + $has_paid_plans = $fs->has_paid_plan(); + $is_premium_code = $fs->is_premium(); + $is_freemium = $fs->is_freemium(); + + $fs->_enqueue_connect_essentials(); + + $current_user = Freemius::_get_current_wp_user(); + + $first_name = $current_user->user_firstname; + if ( empty( $first_name ) ) { + $first_name = $current_user->nickname; + } + + $site_url = get_site_url(); + $protocol_pos = strpos( $site_url, '://' ); + if ( false !== $protocol_pos ) { + $site_url = substr( $site_url, $protocol_pos + 3 ); + } + + $freemius_site_www = 'https://freemius.com'; + + $freemius_usage_tracking_url = $freemius_site_www . '/wordpress/usage-tracking/' . $fs->get_id() . "/{$slug}/"; + $freemius_plugin_terms_url = $freemius_site_www . '/terms/' . $fs->get_id() . "/{$slug}/"; + + $freemius_site_url = $fs->is_premium() ? + $freemius_site_www : + $freemius_usage_tracking_url; + + if ( $fs->is_premium() ) { + $freemius_site_url .= '?' . http_build_query( array( + 'id' => $fs->get_id(), + 'slug' => $slug, + ) ); + } + + $freemius_link = 'freemius.com'; + + $error = fs_request_get( 'error' ); + + $require_license_key = $is_premium_only || + ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) ); + + if ( $is_pending_activation ) { + $require_license_key = false; + } + + if ( $require_license_key ) { + $fs->_add_license_activation_dialog_box(); + } + + $is_optin_dialog = ( + $fs->is_theme() && + $fs->is_themes_page() && + ( ! $fs->has_settings_menu() || $fs->is_free_wp_org_theme() ) + ); + + if ( $is_optin_dialog ) { + $show_close_button = false; + $previous_theme_activation_url = ''; + + if ( ! $is_premium_code ) { + $show_close_button = true; + } else if ( $is_premium_only ) { + $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); + $show_close_button = ( ! empty( $previous_theme_activation_url ) ); + } + } + + $is_network_level_activation = ( + fs_is_network_admin() && + $fs->is_network_active() && + ! $fs->is_network_delegated_connection() + ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + $activate_with_current_user = ( + is_object( $fs_user ) && + ! $is_pending_activation && + // If requires a license for activation, use the user associated with the license for the opt-in. + ! $require_license_key && + ! $is_network_level_activation + ); + + $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); + $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); + + $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); + + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); + + $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ? + FS_GDPR_Manager::instance()->is_required() : + false; + + if ( is_null( $is_gdpr_required ) ) { + $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); + } +?> + +
    + + + + +
    +
    + + + $fs->get_id() ); + fs_require_once_template( 'plugin-icon.php', $vars ); + ?> + + +
    +
    + +

    + +

    apply_filters( 'pending_activation_message', sprintf( + /* translators: %s: name (e.g. Thanks John!) */ + fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . + fs_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message', $slug ), + $first_name, + '' . $fs->get_plugin_name() . '', + '' . $current_user->user_email . '', + fs_text_inline( 'complete the install', 'complete-the-install', $slug ) + ) ); + } else if ( $require_license_key ) { + $button_label = $is_network_upgrade_mode ? + fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) : + fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + + $message = $fs->apply_filters( + 'connect-message_on-premium', + ($is_network_upgrade_mode ? + '' : + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text . '
    ' + ) . + sprintf( fs_text_inline( 'Thanks for purchasing %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), + $first_name, + $fs->get_plugin_name() + ); + } else { + $filter = 'connect_message'; + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) : + fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug); + + if ( $fs->is_plugin_update() ) { + // If Freemius was added on a plugin update, set different + // opt-in message. + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ) : + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ); + + // If user customized the opt-in message on update, use + // that message. Otherwise, fallback to regular opt-in + // custom message if exist. + if ( $fs->has_filter( 'connect_message_on_update' ) ) { + $filter = 'connect_message_on_update'; + } + } + + $message = $fs->apply_filters( + $filter, + ($is_network_upgrade_mode ? + '' : + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text . '
    ' + ) . + sprintf( + esc_html( $default_optin_message ), + '' . esc_html( $fs->get_plugin_name() ) . '', + '' . $current_user->user_login . '', + '' . $site_url . '', + $freemius_link + ), + $first_name, + $fs->get_plugin_name(), + $current_user->user_login, + '' . $site_url . '', + $freemius_link, + $is_gdpr_required + ); + } + + if ( $is_network_upgrade_mode ) { + $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); + + if ($is_premium_code){ + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); + + $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), + $fs->get_module_label( true ) + ) ); + + /* translators: %s: module type (plugin, theme, or add-on) */ + $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); + }else { + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); + } + } + + echo $message; + ?>

    + +
    + + + +
    + + do_action( 'connect/after_license_input' ); + ?> + + - %s', + $fs->get_text_inline( 'Yes', 'yes' ), + $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) + ); + + $do_not_send_updates_text = sprintf( + '%s - %s', + $fs->get_text_inline( 'No', 'no' ), + sprintf( + $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), + '', + '' + ) + ); + ?> +
    + +
    + + +
    +
    + + + $fs->get_id(), + 'sites' => $sites, + 'require_license_key' => $require_license_key + ); + + echo fs_get_template( 'partials/network-activation.php', $vars ); + ?> + +
    +
    + is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> + + + apply_filters( 'show_delegation_option', true ) ) : ?> + + + +
    + + get_public_key() ) ?> + +
    + +
    + + $value ) : ?> + + + +
    + +
    array( + 'icon-class' => 'dashicons dashicons-admin-users', + 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ), + 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ), + 'priority' => 5, + ), + 'site' => array( + 'icon-class' => 'dashicons dashicons-admin-settings', + 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), + 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info, plugins & themes', 'permissions-site_desc' ), + 'priority' => 10, + ), + 'notices' => array( + 'icon-class' => 'dashicons dashicons-testimonial', + 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 13, + ), + 'events' => array( + 'icon-class' => 'dashicons dashicons-admin-plugins', + 'label' => sprintf( $fs->get_text_inline( 'Current %s Events', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), + 'desc' => $fs->get_text_inline( 'Activation, deactivation and uninstall', 'permissions-events_desc' ), + 'priority' => 20, + ), + ); + + // Add newsletter permissions if enabled. + if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { + $permissions['newsletter'] = array( + 'icon-class' => 'dashicons dashicons-email-alt', + 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 15, + ); + } + + // Allow filtering of the permissions list. + $permissions = $fs->apply_filters( 'permission_list', $permissions ); + + // Sort by priority. + uasort( $permissions, 'fs_sort_by_priority' ); + + if ( ! empty( $permissions ) ) : ?> +
    + +

    get_module_label( true ), + $freemius_link + ) ?>

    + + +
      $permission ) : ?> +
    • + + +
      + + +

      +
      +
    • + +
    +
    + + +
    +

    + + + + + + + +

    +
    + +
    + +   -   + +
    +
    + +
    + + \ No newline at end of file diff --git a/freemius/templates/contact.php b/freemius/templates/contact.php new file mode 100644 index 0000000..bba1018 --- /dev/null +++ b/freemius/templates/contact.php @@ -0,0 +1,128 @@ +get_slug(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + time(), + 'contact' + ) ); + } + + $query_params = array_merge( $_GET, array_merge( $context_params, array( + 'plugin_version' => $fs->get_plugin_version(), + 'wp_login_url' => wp_login_url(), + 'site_url' => get_site_url(), +// 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", + ) ) ); + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    +
    + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'contact', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/debug.php b/freemius/templates/debug.php new file mode 100644 index 0000000..bafff16 --- /dev/null +++ b/freemius/templates/debug.php @@ -0,0 +1,726 @@ + +

    newest->version ?>

    +
    + + + + +
    +
    + + +
    + +
    +

    + + + + + + + + get_option( 'ms_migration_complete', false, true ) ) : ?> + + + + + + +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + + + +
    + + 'WP_FS__REMOTE_ADDR', + 'val' => WP_FS__REMOTE_ADDR, + ), + array( + 'key' => 'WP_FS__ADDRESS_PRODUCTION', + 'val' => WP_FS__ADDRESS_PRODUCTION, + ), + array( + 'key' => 'FS_API__ADDRESS', + 'val' => FS_API__ADDRESS, + ), + array( + 'key' => 'FS_API__SANDBOX_ADDRESS', + 'val' => FS_API__SANDBOX_ADDRESS, + ), + array( + 'key' => 'WP_FS__DIR', + 'val' => WP_FS__DIR, + ), + ) +?> +
    + + + + + + + + + + > + + + + + + +
    +

    + + + + + + + + + + + plugins as $sdk_path => $data ) : ?> + version ) ?> + > + + + + + + + +
    version ?>plugin_path ?>
    + + + + + get_option( $module_type . 's' ) ?> + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + $data ) : ?> + file ); + } else { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + + if ( ! $is_active && is_child_theme() ) { + $parent_theme = $current_theme->parent(); + + $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); + } + } + ?> + id ) : null ?> + has_api_connectivity() && $fs->is_on() ) { + echo ' style="background: #E6FFE6; font-weight: bold"'; + } else { + echo ' style="background: #ffd0d0; font-weight: bold"'; + } + } ?>> + + + + + has_api_connectivity() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>has_api_connectivity() ? + fs_text_x_inline( 'Connected', 'as connection was successful' ) : + fs_text_x_inline( 'Blocked', 'as connection blocked' ) + ); + } ?> + is_on() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>is_on() ? + $on_text : + $off_text + ); + } ?> + + + + get_network_install_blog_id(); + $network_user = $fs->get_network_user(); + } + ?> + + + + + + + +
    id ?>version ?>title ?>file ?>public_key ?>email; + } ?> + + has_trial_plan() ) : ?> +
    + + + + + +
    + + is_registered() ) : ?> + + + is_network_upgrade_mode() ) : ?> +
    + + + + + +
    + + +
    + + + + + 0 ) : ?> +

    /

    + + + + + + + + + + + + + + + + + + $sites ) : ?> + + + + + + + + + + + + + + + + + + +
    id ?>blog_id ?>url ) ?>user_id ?>plan_id ) ) { + if ( false === $all_plans ) { + $option_name = 'plans'; + if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + $all_plans = $fs_options->get_option( $option_name, array() ); + } + + if ( false === $plans ) { + $plans = $all_plans[ $slug ]; + } + + foreach ( $plans as $plan ) { + $plan_id = Freemius::_decrypt( $plan->id ); + + if ( $site->plan_id == $plan_id ) { + $plan_name = Freemius::_decrypt( $plan->name ); + break; + } + } + } + + echo $plan_name; + ?>public_key ?>secret_key ) ?> +
    + + + + + + + + + +
    +
    + + + + $plugin_addons ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>title ?>slug ?>version ?>public_key ?>secret_key ) ?>
    + + + +

    + + + + + + + + + + + + + + $user ) : ?> + + + + + + + + + + + +
    id ?>get_name() ?>email ?>is_verified ) ?>public_key ?>secret_key ) ?> +
    + + + + +
    +
    + + + + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>plugin_id ?>user_id ?>plan_id ?>is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?>activated ?>is_block_features ? 'Blocking' : 'Flexible' ?>secret_key ) ?>expiration ?>
    + + + + +

    + +
    + + + + + + + +
    + + +
    + + +
    + +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    {$log.log_order}.{$log.type}{$log.logger}{$log.function} + + {$log.message_short} + +
    {$log.message}
    +
    {$log.file}:{$log.line}{$log.created}
    +
    + + diff --git a/freemius/templates/debug/api-calls.php b/freemius/templates/debug/api-calls.php new file mode 100644 index 0000000..ea4e823 --- /dev/null +++ b/freemius/templates/debug/api-calls.php @@ -0,0 +1,155 @@ + 0, + 'POST' => 0, + 'PUT' => 0, + 'DELETE' => 0 + ); + + $show_body = false; + foreach ( $logger as $log ) { + $counters[ $log['method'] ] ++; + + if ( ! is_null( $log['body'] ) ) { + $show_body = true; + } + } + + $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); + + /** + * This template is used for debugging, therefore, when possible + * we'd like to prettify the output of a JSON encoded variable. + * This will only be executed when $pretty_print is `true`, and + * the var is `true` only for PHP 5.3 and higher. Due to the + * limitations of the current Theme Check, it throws an error + * that using the "options" parameter (the 2nd param) is not + * supported in PHP 5.2 and lower. Thus, we added this alias + * variable to work around that false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $encode = 'json_encode'; + + $root_path_len = strlen( ABSPATH ); + + $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); +?> +

    + +

    Total Time:

    + +

    Total Requests:

    + $count ) : ?> +

    :

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    . + %s', + $log['path'] + ); + ?> + + + + + + + + + + + + + + %s', + substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) + ); + if ( $pretty_print ) { + $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); + } + ?> +
    + +
    + %s', + substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) + ); + } + + if ( $is_not_empty_result && $pretty_print ) { + $decoded = json_decode( $result ); + if ( ! is_null( $decoded ) ) { + $result = $encode( $decoded, JSON_PRETTY_PRINT ); + } + } else { + $result = is_string( $result ) ? $result : json_encode( $result ); + } + ?> + style="display: none"> +
    \ No newline at end of file diff --git a/freemius/templates/debug/index.php b/freemius/templates/debug/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/debug/index.php @@ -0,0 +1,3 @@ + +

    + + + + + + + + + + + + + + + + + + > + + + + + + + + + + +
    #
    .get_id() ?> + %s', + esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) + ); + ?> +
    + +
    +
    get_file() ) . ':' . $log['line']; + } + ?>
    \ No newline at end of file diff --git a/freemius/templates/debug/plugins-themes-sync.php b/freemius/templates/debug/plugins-themes-sync.php new file mode 100644 index 0000000..8508cd1 --- /dev/null +++ b/freemius/templates/debug/plugins-themes-sync.php @@ -0,0 +1,76 @@ +get_option( 'all_plugins' ); + $all_themes = $fs_options->get_option( 'all_themes' ); + + /* translators: %s: time period (e.g. In "2 hours") */ + $in_x_text = fs_text_inline( 'In %s', 'in-x' ); + /* translators: %s: time period (e.g. "2 hours" ago) */ + $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + + + + + + + + + + + +
    plugins ) ?>timestamp ) && is_numeric( $all_plugins->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    themes ) ?>timestamp ) && is_numeric( $all_themes->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    diff --git a/freemius/templates/debug/scheduled-crons.php b/freemius/templates/debug/scheduled-crons.php new file mode 100644 index 0000000..1751e60 --- /dev/null +++ b/freemius/templates/debug/scheduled-crons.php @@ -0,0 +1,136 @@ +get_option( $module_type . 's' ); + if ( is_array( $modules ) && count( $modules ) > 0 ) { + foreach ( $modules as $slug => $data ) { + if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + } else { + $is_active = is_plugin_active( $data->file ); + } + + /** + * @author Vova Feldman + * + * @since 1.2.1 Don't load data from inactive modules. + */ + if ( $is_active ) { + $fs = freemius( $data->id ); + + $next_execution = $fs->next_sync_cron(); + $last_execution = $fs->last_sync_cron(); + + if ( false !== $next_execution ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'sync_cron', + 'last' => $last_execution, + 'next' => $next_execution, + ); + } + + $next_install_execution = $fs->next_install_sync(); + $last_install_execution = $fs->last_install_sync(); + + if (false !== $next_install_execution || + false !== $last_install_execution + ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'install_sync', + 'last' => $last_install_execution, + 'next' => $next_install_execution, + ); + } + } + } + } + } + + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + $crons ) : ?> + + + + + + + + + + + + +
    diff --git a/freemius/templates/email.php b/freemius/templates/email.php new file mode 100644 index 0000000..598c783 --- /dev/null +++ b/freemius/templates/email.php @@ -0,0 +1,49 @@ + + + $section ) { + ?> + + + + + $row ) { + $col_count = count( $row ); + ?> + + + + + + + + + + + +
    :
    \ No newline at end of file diff --git a/freemius/templates/firewall-issues-js.php b/freemius/templates/firewall-issues-js.php new file mode 100644 index 0000000..2abfbc0 --- /dev/null +++ b/freemius/templates/firewall-issues-js.php @@ -0,0 +1,59 @@ + + \ No newline at end of file diff --git a/freemius/templates/forms/affiliation.php b/freemius/templates/forms/affiliation.php new file mode 100644 index 0000000..14edd64 --- /dev/null +++ b/freemius/templates/forms/affiliation.php @@ -0,0 +1,486 @@ +get_slug(); + + $user = $fs->get_user(); + $affiliate = $fs->get_affiliate(); + $affiliate_terms = $fs->get_affiliate_terms(); + + $plugin_title = $fs->get_plugin_title(); + $module_type = $fs->is_plugin() ? + WP_FS__MODULE_TYPE_PLUGIN : + WP_FS__MODULE_TYPE_THEME; + + $commission = $affiliate_terms->get_formatted_commission(); + + $readonly = false; + $is_affiliate = is_object( $affiliate ); + $is_pending_affiliate = false; + $email_address = ( is_object( $user ) ? + $user->email : + '' ); + $full_name = ( is_object( $user ) ? + $user->get_name() : + '' ); + $paypal_email_address = ''; + $domain = ''; + $extra_domains = array(); + $promotion_method_social_media = false; + $promotion_method_mobile_apps = false; + $statistics_information = false; + $promotion_method_description = false; + $members_dashboard_login_url = 'https://members.freemius.com/login/'; + + $affiliate_application_data = $fs->get_affiliate_application_data(); + + if ( $is_affiliate && $affiliate->is_pending() ) { + $readonly = 'readonly'; + $is_pending_affiliate = true; + + $paypal_email_address = $affiliate->paypal_email; + $domain = $affiliate->domain; + $statistics_information = $affiliate_application_data['stats_description']; + $promotion_method_description = $affiliate_application_data['promotion_method_description']; + + if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { + $extra_domains = $affiliate_application_data['additional_domains']; + } + + if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { + $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); + $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); + $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); + } + } else { + $current_user = Freemius::_get_current_wp_user(); + $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); + $email_address = $current_user->user_email; + $domain = fs_strip_url_protocol( get_site_url() ); + } + + $affiliate_tracking = 30; + + if ( is_object( $affiliate_terms ) ) { + $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? + ( $affiliate_terms->cookie_days . '-day' ) : + fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); + } + + $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); +?> +
    +
    +
    +
    +
    +
    + + + + is_active() ) : ?> +
    +

    %s', + $members_dashboard_login_url, + $members_dashboard_login_url + ) + ); + ?>

    +
    + + is_suspended() ) { + $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); + $message_container_class = 'notice notice-warning'; + } else if ( $affiliate->is_rejected() ) { + $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); + $message_container_class = 'error'; + } else if ( $affiliate->is_blocked() ) { + $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); + $message_container_class = 'error'; + } + ?> +
    +

    +
    + + +
    +
    + +
    +

    +

    +
    + +

    +
      +
    • + has_renewals_commission() ) : ?> +
    • + + is_session_cookie() ) ) : ?> +
    • + + has_lifetime_commission() ) : ?> +
    • + +
    • +
    • +
    • +
    +
    > +

    + +
    + + > +
    +
    + + > +
    +
    + + > +
    +
    + + > +

    + + + ... + +
    +
    > + +

    + + +
    + > +
    + + +
    +
    + +
    + /> + +
    +
    + /> + +
    +
    +
    + + + +

    + +
    +
    + + + +

    + +
    + +
    + + + + + +
    +
    +
    +
    + + +
    + 'affiliation', + 'module_id' => $fs->get_id(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); +?> \ No newline at end of file diff --git a/freemius/templates/forms/deactivation/contact.php b/freemius/templates/forms/deactivation/contact.php new file mode 100644 index 0000000..24d67e7 --- /dev/null +++ b/freemius/templates/forms/deactivation/contact.php @@ -0,0 +1,23 @@ +get_slug(); + + echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) + . sprintf(" %s", + $fs->contact_url( 'technical_support' ), + fs_text_inline( 'Contact Support', 'contact-support', $slug ) + ); diff --git a/freemius/templates/forms/deactivation/form.php b/freemius/templates/forms/deactivation/form.php new file mode 100644 index 0000000..f15f6b6 --- /dev/null +++ b/freemius/templates/forms/deactivation/form.php @@ -0,0 +1,539 @@ +get_slug(); + + $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; + $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; + $confirmation_message = $VARS['uninstall_confirmation_message']; + + $is_anonymous = ( ! $fs->is_registered() ); + $anonymous_feedback_checkbox_html = ''; + + $reasons_list_items_html = ''; + + if ( $show_deactivation_feedback_form ) { + $reasons = $VARS['reasons']; + + foreach ( $reasons as $reason ) { + $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); + + if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { + $list_item_classes .= ' has-internal-message'; + $reason_internal_message = $reason['internal_message']; + } else { + $reason_internal_message = ''; + } + + $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); + $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); + + $reason_list_item_html = <<< HTML +
    + apply_filters( 'hide_account_tabs', false ) ) : ?> + + + +
    +
    +
    +
    +
    +

    + +
    + + apply_filters( 'hide_license_key', false ) ); + + $profile = array(); + $profile[] = array( + 'id' => 'user_name', + 'title' => fs_text_inline( 'Name', 'name', $slug ), + 'value' => $name + ); + // if (isset($user->email) && false !== strpos($user->email, '@')) + $profile[] = array( + 'id' => 'email', + 'title' => fs_text_inline( 'Email', 'email', $slug ), + 'value' => $user->email + ); + + if ( is_numeric( $user->id ) ) { + $profile[] = array( + 'id' => 'user_id', + 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), + 'value' => $user->id + ); + } + + $profile[] = array( + 'id' => 'product', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ), + 'value' => $fs->get_plugin_title() + ); + + $profile[] = array( + 'id' => 'product_id', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), + 'value' => $fs->get_id() + ); + + if ( ! fs_is_network_admin()) { + $profile[] = array( + 'id' => 'site_id', + 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), + 'value' => is_string( $site->id ) ? + $site->id : + fs_text_inline( 'No ID', 'no-id', $slug ) + ); + + $profile[] = array( + 'id' => 'site_public_key', + 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), + 'value' => $site->public_key + ); + + $profile[] = array( + 'id' => 'site_secret_key', + 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), + 'value' => ( ( is_string( $site->secret_key ) ) ? + $site->secret_key : + fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) + ) + ); + } + + $profile[] = array( + 'id' => 'version', + 'title' => $version_text, + 'value' => $fs->get_plugin_version() + ); + + if ( $is_premium ) { + $profile[] = array( + 'id' => 'beta_program', + 'title' => '', + 'value' => $user->is_beta + ); + } + + if ( $has_paid_plan ) { + if ( $fs->is_trial() ) { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => $plan_text, + 'value' => ( is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ) ) + ); + } + } else { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => ( $is_child_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, + 'value' => strtoupper( is_string( $plan->name ) ? + $plan->title : + strtoupper( $free_text ) + ) + ); + + if ( $is_child_license ) { + $profile[] = array( + 'id' => 'bundle_plan', + 'title' => $bundle_plan_text, + 'value' => strtoupper( $license->parent_plan_title ) + ); + } + } + + if ( is_object( $license ) ) { + if ( ! $hide_license_key ) { + $profile[] = array( + 'id' => 'license_key', + 'title' => fs_text_inline( 'License Key', $slug ), + 'value' => $license->secret_key, + ); + } + } + } + } + ?> + + + + + > + + + + + + + + + is_verified() ) : ?> + + + + is_trial() ) : ?> + + + is_lifetime() ) : ?> + is_first_payment_pending() ) : ?> + is_expired() ?> + + + is_first_payment_pending() ) : ?> + + + is_trial() ) : ?> + + +
    + is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?> + + _get_plan_by_id( $available_license->plan_id ) ?> + $fs, + 'slug' => $slug, + 'license' => $available_license, + 'plan' => $premium_plan, + 'is_localhost' => $site->is_localhost(), + 'install_id' => $site->id, + 'class' => 'button-primary', + ); + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?> + +
    + + + + + + get_unique_affix() . '_sync_license' ) ?> + is_single_plan() ) : ?> + + + + +
    + + + is_first_payment_pending() ) : ?> + + + + + has_premium_version() ) : ?> + + + can_use_premium_code() ) : ?> + + + + + + +
    + + + +
    + + + is_verified() ) : ?> +
    + + + +
    + + + has_release_on_freemius() ) : ?> + + + + + + + + secret_key ) && in_array( $p['id'], array( + 'email', + 'user_name' + ) ) ) + ) : ?> +
    + + + + +
    + +
    +
    +
    + +
    +

    +
    + + + + + + +
    + +
    +
    +
    + + + + + + + + + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + get_updated_account_addons(); + + $installed_addons = $fs->get_installed_addons(); + $installed_addons_ids = array(); + foreach ( $installed_addons as $fs_addon ) { + $installed_addons_ids[] = $fs_addon->get_id(); + } + + $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); + ?> + + +
    +
    + + + + + + + + + + + + + + + + + $fs, + 'addon_id' => $addon_id, + 'odd' => $odd, + 'fs_blog_id' => $fs_blog_id, + 'active_plugins_directories_map' => &$active_plugins_directories_map, + 'is_addon_installed' => $is_addon_installed, + 'addon_info' => $fs->_get_addon_info( $addon_id, $is_addon_installed ) + ); + + fs_require_template( + 'account/partials/addon.php', + $addon_view_params + ); + + $odd = ! $odd; + } ?> + +

    +
    +
    + + + do_action( 'after_account_details' ) ?> + + $VARS['id'] ); + fs_require_once_template( 'account/billing.php', $view_params ); + fs_require_once_template( 'account/payments.php', $view_params ); + } + ?> +
    +
    +
    +
    +
  • + +
    {$reason_internal_message}
    +
  • +HTML; + + $reasons_list_items_html .= $reason_list_item_html; + } + + if ( $is_anonymous ) { + $anonymous_feedback_checkbox_html = sprintf( + '', + fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) + ); + } + } + + // Aliases. + $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); + $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); + $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } +?> + diff --git a/freemius/templates/forms/deactivation/index.php b/freemius/templates/forms/deactivation/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/forms/deactivation/index.php @@ -0,0 +1,3 @@ +get_slug(); + + $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); + $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); + $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); + + echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) + . " {$use_plugin_anonymously_text}"; \ No newline at end of file diff --git a/freemius/templates/forms/index.php b/freemius/templates/forms/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/forms/index.php @@ -0,0 +1,3 @@ +get_slug(); + $unique_affix = $fs->get_unique_affix(); + + $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); + $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); + $message_below_input_field = ''; + + $header_title = $fs->is_free_plan() ? + fs_text_inline( 'Activate License', 'activate-license', $slug ) : + fs_text_inline( 'Update License', 'update-license', $slug ); + + if ( $fs->is_registered() ) { + $activate_button_text = $header_title; + } else { + $freemius_site_url = $fs->has_paid_plan() ? + 'https://freemius.com/wordpress/' : + // Insights platform information. + 'https://freemius.com/wordpress/usage-tracking/'; + + $freemius_link = 'freemius.com'; + + $message_below_input_field = sprintf( + fs_text_inline( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), + $fs->get_module_label( true ), + $freemius_link + ); + + $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + } + + $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); + + $is_network_activation = ( + $fs->is_network_active() && + fs_is_network_admin() && + ! $fs->is_delegated_connection() + ); + $network_activation_html = ''; + + $sites_details = array(); + if ( $is_network_activation ) { + $all_sites = Freemius::get_sites(); + + foreach ( $all_sites as $site ) { + $site_details = $fs->get_site_info( $site ); + + $blog_id = Freemius::get_site_blog_id( $site ); + $install = $fs->get_install_by_blog_id($blog_id); + + if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $site_details['license_id'] = $install->license_id; + } + + $sites_details[] = $site_details; + } + + if ( $is_network_activation ) { + $vars = array( + 'id' => $fs->get_id(), + 'sites' => $sites_details, + 'require_license_key' => true + ); + + $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); + } + } + + $premium_licenses = $fs->get_available_premium_licenses(); + $available_licenses = array(); + foreach ( $premium_licenses as $premium_license ) { + $activations_left = $premium_license->left(); + if ( ! ( $activations_left > 0 ) ) { + continue; + } + + $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; + } + + $total_available_licenses = count( $available_licenses ); + if ( $total_available_licenses > 0 ) { + $license_input_html = <<< HTML +
    + + + + + + + + + + + +
    +HTML; + + if ( $total_available_licenses > 1 ) { + // Sort the licenses by number of activations left in descending order. + krsort( $available_licenses ); + + $license_input_html .= ''; + } else { + $available_licenses = array_values( $available_licenses ); + + /** + * @var FS_Plugin_License $available_license + */ + $available_license = $available_licenses[0]; + $value = sprintf( + "%s-Site %s License - %s", + ( 1 == $available_license->quota ? + 'Single' : + $available_license->quota + ), + $fs->_get_plan_by_id( $available_license->plan_id )->title, + ( htmlspecialchars( substr( $available_license->secret_key, 0, 6 ) ) . + str_pad( '', 23 * 6, '•' ) . + htmlspecialchars( substr( $available_license->secret_key, - 3 ) ) ) + ); + + $license_input_html .= <<< HTML + +HTML; + } + + $license_input_html .= <<< HTML +
    + +
    + +
    +
    +
    +HTML; + } else { + $license_input_html = ""; + } + + /** + * IMPORTANT: + * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE + * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) + * FOR MIGRATED MODULES. + */ + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    + {$license_input_html} + {$cant_find_license_key_text} + {$network_activation_html} +

    {$message_below_input_field}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/optout.php b/freemius/templates/forms/optout.php new file mode 100644 index 0000000..a12e0f9 --- /dev/null +++ b/freemius/templates/forms/optout.php @@ -0,0 +1,267 @@ +get_slug(); + + $action = $fs->is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $reconnect_url = $fs->get_activation_url( array( + 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), + 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), + ) ); + + $plugin_title = "{$fs->get_plugin()->title}"; + $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); + $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug ); + $opt_out_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation', $slug ), $fs->get_module_type() ); + $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title ); + $opt_out_message_clicking_opt_out = sprintf( + fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ), + $plugin_title, + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $admin_notice_params = array( + 'id' => '', + 'slug' => $fs->get_id(), + 'type' => 'success', + 'sticky' => false, + 'plugin' => $fs->get_plugin()->title, + 'message' => $opt_out_message_appreciation + ); + + $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params ); + + $modal_content_html = <<< HTML +

    {$opt_out_message_appreciation}

    +

    +

    {$opt_out_message_usage_tracking}

    +

    {$opt_out_message_clicking_opt_out}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); +?> + diff --git a/freemius/templates/forms/premium-versions-upgrade-handler.php b/freemius/templates/forms/premium-versions-upgrade-handler.php new file mode 100644 index 0000000..f30639b --- /dev/null +++ b/freemius/templates/forms/premium-versions-upgrade-handler.php @@ -0,0 +1,205 @@ +get_slug(); + + $plugin_data = $fs->get_plugin_data(); + $plugin_name = $plugin_data['Name']; + $plugin_basename = $fs->get_plugin_basename(); + + $license = $fs->_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $message = sprintf( + fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . + fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), + '', + sprintf( + '%s', + is_object( $license ) ? + fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : + fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) + ), + '' + ); + + $modal_content_html = "

    {$message}

    "; + + $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); + + $renew_license_button_text = is_object( $license ) ? + fs_text_inline( 'Renew license', 'renew-license', $slug ) : + fs_text_inline( 'Buy license', 'buy-license', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/premium-versions-upgrade-metadata.php b/freemius/templates/forms/premium-versions-upgrade-metadata.php new file mode 100644 index 0000000..5f9fddd --- /dev/null +++ b/freemius/templates/forms/premium-versions-upgrade-metadata.php @@ -0,0 +1,47 @@ +_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $plugin_data = $fs->get_plugin_data(); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/resend-key.php b/freemius/templates/forms/resend-key.php new file mode 100644 index 0000000..f8cafb9 --- /dev/null +++ b/freemius/templates/forms/resend-key.php @@ -0,0 +1,247 @@ +get_slug(); + + $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); + $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); + $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); + $other_text = fs_text_inline( 'Other', 'other', $slug ); + + $is_freemium = $fs->is_freemium(); + + $send_button_text_html = esc_html($send_button_text); + + $button_html = <<< HTML + +HTML; + + if ( $is_freemium ) { + $current_user = Freemius::_get_current_wp_user(); + $email = $current_user->user_email; + $esc_email = esc_attr( $email ); + $form_html = <<< HTML + +{$button_html} +HTML; + } else { + $email = ''; + $form_html = <<< HTML +{$button_html} + +HTML; + } + + $message_above_input_field = fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    +
    + {$form_html} +
    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/forms/subscription-cancellation.php b/freemius/templates/forms/subscription-cancellation.php new file mode 100644 index 0000000..1198fe7 --- /dev/null +++ b/freemius/templates/forms/subscription-cancellation.php @@ -0,0 +1,277 @@ +get_slug(); + +/** + * @var FS_Plugin_License $license + */ +$license = $VARS['license']; + +$has_trial = $VARS['has_trial']; + +$subscription_cancellation_context = $has_trial ? + fs_text_inline( 'trial', 'trial', $slug ) : + fs_text_inline( 'subscription', 'subscription', $slug ); + +$plan = $fs->get_plan(); +$module_label = $fs->get_module_label( true ); + +if ( $VARS['is_license_deactivation'] ) { + $subscription_cancellation_text = ''; +} else { + $subscription_cancellation_text = sprintf( + fs_text_inline( + "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", + 'deactivation-or-uninstall-message', + $slug + ), + $module_label + ) . ' '; +} + + $subscription_cancellation_text .= sprintf( + fs_text_inline( + 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', + 'cancel-subscription-message', + $slug + ), + ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), + $subscription_cancellation_context +); + +$cancel_subscription_action_label = sprintf( + fs_esc_html_inline( + "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", + 'cancel-x', + $slug + ), + esc_html( $subscription_cancellation_context ), + sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), + esc_html( $module_label ) +); + +$keep_subscription_active_action_label = esc_html( sprintf( + fs_text_inline( + "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", + 'dont-cancel-x', + $slug + ), + $subscription_cancellation_context +) ); + +$subscription_cancellation_text = esc_html( $subscription_cancellation_text ); + +$subscription_cancellation_html = <<< HTML +

    {$subscription_cancellation_text}

    +
      +
    • + +
    • +
    • + +
    • +
    +HTML; + +$downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); +$cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); +/* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ +$downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); +$prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); +$after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); +$after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); +$after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); + +$subscription_cancellation_confirmation_message = $has_trial ? + fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : + sprintf( + '%s %s %s %s', + sprintf( + $downgrade_x_confirm_text, + ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + human_time_diff( time(), strtotime( $license->expiration ) ) + ), + ( + $license->is_block_features ? + ( + $fs->is_only_premium() ? + sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : + sprintf( $after_downgrade_blocking_text, $plan->title ) + ) : + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) + ), + $prices_increase_text, + fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) + ); + +fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/trial-start.php b/freemius/templates/forms/trial-start.php new file mode 100644 index 0000000..cea597e --- /dev/null +++ b/freemius/templates/forms/trial-start.php @@ -0,0 +1,181 @@ +get_slug(); + + $message_header = sprintf( + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), + '', + '' + ); + $message_content = sprintf( + /* translators: %s: Link to freemius.com */ + fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), + $fs->get_module_type(), + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $modal_content_html = <<< HTML +

    +

    {$message_header}

    +

    {$message_content}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/gdpr-optin-js.php b/freemius/templates/gdpr-optin-js.php new file mode 100644 index 0000000..4fdc5e3 --- /dev/null +++ b/freemius/templates/gdpr-optin-js.php @@ -0,0 +1,66 @@ + + \ No newline at end of file diff --git a/freemius/templates/index.php b/freemius/templates/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/index.php @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/open-license-activation.php b/freemius/templates/js/open-license-activation.php new file mode 100644 index 0000000..a88e6f9 --- /dev/null +++ b/freemius/templates/js/open-license-activation.php @@ -0,0 +1,37 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/style-premium-theme.php b/freemius/templates/js/style-premium-theme.php new file mode 100644 index 0000000..942da64 --- /dev/null +++ b/freemius/templates/js/style-premium-theme.php @@ -0,0 +1,53 @@ +get_slug(); + +?> + \ No newline at end of file diff --git a/freemius/templates/partials/network-activation.php b/freemius/templates/partials/network-activation.php new file mode 100644 index 0000000..06cbff2 --- /dev/null +++ b/freemius/templates/partials/network-activation.php @@ -0,0 +1,89 @@ +get_slug(); + + $sites = $VARS['sites']; + $require_license_key = $VARS['require_license_key']; + + $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); + $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); +?> +|' ?> + \ No newline at end of file diff --git a/freemius/templates/plugin-icon.php b/freemius/templates/plugin-icon.php new file mode 100644 index 0000000..ab0fb54 --- /dev/null +++ b/freemius/templates/plugin-icon.php @@ -0,0 +1,20 @@ + +
    + +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/description.php b/freemius/templates/plugin-info/description.php new file mode 100644 index 0000000..26bc67b --- /dev/null +++ b/freemius/templates/plugin-info/description.php @@ -0,0 +1,78 @@ +info->selling_point_0 ) || + ! empty( $plugin->info->selling_point_1 ) || + ! empty( $plugin->info->selling_point_2 ) + ) : ?> +
    +
      + + info->{'selling_point_' . $i} ) ) : ?> +
    • + +

      info->{'selling_point_' . $i} ) ?>

    • + + +
    +
    + +
    + info->description, array( + 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), + 'b' => array(), + 'i' => array(), + 'p' => array(), + 'blockquote' => array(), + 'h2' => array(), + 'h3' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array() + ) ); + ?> +
    +info->screenshots ) ) : ?> + info->screenshots ?> +
    +

    slug ) ?>

    +
      + $url ) : ?> + +
    • + + +
    • + +
    +
    + \ No newline at end of file diff --git a/freemius/templates/plugin-info/features.php b/freemius/templates/plugin-info/features.php new file mode 100644 index 0000000..b3d0fc8 --- /dev/null +++ b/freemius/templates/plugin-info/features.php @@ -0,0 +1,114 @@ +features) && is_array($plan->features)) { + foreach ( $plan->features as $feature ) { + if ( ! isset( $features_plan_map[ $feature->id ] ) ) { + $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); + } + + $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; + } + } + + // Add support as a feature. + if ( ! empty( $plan->support_email ) || + ! empty( $plan->support_skype ) || + ! empty( $plan->support_phone ) || + true === $plan->is_success_manager + ) { + if ( ! isset( $features_plan_map['support'] ) ) { + $support_feature = new stdClass(); + $support_feature->id = 'support'; + $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); + $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); + } else { + $support_feature = $features_plan_map['support']; + } + + $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; + } + } + + // Add updates as a feature for all plans. + $updates_feature = new stdClass(); + $updates_feature->id = 'updates'; + $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); + $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); + foreach ( $plans as $plan ) { + $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; + } +?> +
    + + + + + + + + + + + $data ) : ?> + + + + + + + + +
    + title ?> + pricing ) ) { + fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); + } else { + foreach ( $plan->pricing as $pricing ) { + /** + * @var FS_Pricing $pricing + */ + if ( 1 == $pricing->licenses ) { + if ( $pricing->has_annual() ) { + echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); + } else if ( $pricing->has_monthly() ) { + echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); + } else { + echo "\${$pricing->lifetime_price}"; + } + } + } + } + ?> +
    title ) ) ?> + id ] ) ) : ?> + id ]->value ) ) : ?> + id ]->value ) ?> + + + + +
    +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/index.php b/freemius/templates/plugin-info/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/plugin-info/index.php @@ -0,0 +1,3 @@ + +
      + $url ) : ?> + +
    1. + +
    2. + +
    diff --git a/freemius/templates/powered-by.php b/freemius/templates/powered-by.php new file mode 100644 index 0000000..b717216 --- /dev/null +++ b/freemius/templates/powered-by.php @@ -0,0 +1,58 @@ + + +
    + \ No newline at end of file diff --git a/freemius/templates/pricing.php b/freemius/templates/pricing.php new file mode 100644 index 0000000..6cf11b7 --- /dev/null +++ b/freemius/templates/pricing.php @@ -0,0 +1,176 @@ +get_slug(); + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + $timestamp, + 'upgrade' + ) ); + } else { + $context_params['home_url'] = home_url(); + } + + if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) + { + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + } + + $query_params = array_merge( $context_params, $_GET, array( + 'next' => $fs->_get_sync_license_url( false, false ), + 'plugin_version' => $fs->get_plugin_version(), + // Billing cycle. + 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), + 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', + ) ); + + if ( ! $fs->is_registered() ) { + $template_data = array( + 'id' => $fs->get_id(), + ); + fs_require_template( 'forms/trial-start.php', $template_data); + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    +
    +
    + + + + + + +
    + + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'pricing', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/secure-https-header.php b/freemius/templates/secure-https-header.php new file mode 100644 index 0000000..fd12bc2 --- /dev/null +++ b/freemius/templates/secure-https-header.php @@ -0,0 +1,39 @@ + +
    + + get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), + $VARS['page'] + ) ) . + ' - ' . + sprintf( + '%s', + 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, + 'Freemius Inc. [US]' + ); + } + ?> +
    \ No newline at end of file diff --git a/freemius/templates/sticky-admin-notice-js.php b/freemius/templates/sticky-admin-notice-js.php new file mode 100644 index 0000000..028a966 --- /dev/null +++ b/freemius/templates/sticky-admin-notice-js.php @@ -0,0 +1,39 @@ + + \ No newline at end of file diff --git a/freemius/templates/tabs-capture-js.php b/freemius/templates/tabs-capture-js.php new file mode 100644 index 0000000..236be3b --- /dev/null +++ b/freemius/templates/tabs-capture-js.php @@ -0,0 +1,63 @@ +get_slug(); +?> + \ No newline at end of file diff --git a/freemius/templates/tabs.php b/freemius/templates/tabs.php new file mode 100644 index 0000000..a0391a8 --- /dev/null +++ b/freemius/templates/tabs.php @@ -0,0 +1,190 @@ +get_slug(); + + $menu_items = $fs->get_menu_items(); + + $is_free_wp_org_theme = $fs->is_free_wp_org_theme(); + + $tabs = array(); + foreach ( $menu_items as $priority => $items ) { + foreach ( $items as $item ) { + if ( ! $item['show_submenu'] ) { + $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? + 'support' : + $item['menu_slug']; + + if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { + continue; + } + + if ( ! $is_free_wp_org_theme || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { + continue; + } + } + + $url = $fs->_get_admin_page_url( $item['menu_slug'] ); + $title = $item['menu_title']; + + $tab = array( + 'label' => $title, + 'href' => $url, + 'slug' => $item['menu_slug'], + ); + + if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { + $tab['href'] .= '&trial=true'; + } + + $tabs[] = $tab; + } + } +?> + \ No newline at end of file diff --git a/includes/.DS_Store b/includes/.DS_Store deleted file mode 100644 index 663d6b83a827325a08242f512d6e4bcb9a3eaa05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Pcg9Rs<;&QNcrwUMhI@5=uRI_6Ka6YAdk`CPjM7#gFl8{1`t#-|Q|e zO;YqEqBCLV%}#b^c3(m^3&8Xj@deNU&}I`1_NW>}?n|pl;61xUV{_!_A;tssqBdF` zzmWlXcBdF4!4NsG_4CW`a%`GtRd7&b-V~C1aPXlGPSm%M&x?tT3e=^Q}#YjjJ;+l`IG3 zAoX)7uw-w-UUTewEjDMxl+_E%a=bk@7*=8rS!Ka$QeEPExMP3LJOw#PX>FNB@K(vh zu8K0C4E$3DxMz#B_Z_NM29yD1V8wuZA3`?4$Ybfyembb^5rEjD+X`*@6_l9JW8|@P z$U79{LWwTa_#=jK;q-@oT;#EI=)z(A;ludK#-C7(t+Tvud4cTtlL}Xm)a1}zsZp9ocTk$cQ75YOl Vh>^$AAw3lR5wJ9 'Playlist_Widget', + 'description' => __( 'Display currently playing playlist.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Now Playing List', 'radio-station' ); + parent::__construct( 'Playlist_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + // 2.3.0: added hide widget if empty option + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; + $song = isset( $instance['song'] ) ? $instance['song'] : true; + $album = isset( $instance['album'] ) ? $instance['album'] : false; + $label = isset( $instance['label'] ) ? $instance['label'] : false; + $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; + $hide_empty = isset( $instnace['hide_empty'] ) ? $instance['hide_empty'] : true; + + // 2.3.0: convert template style code to straight php echo + echo ' +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    '; + } + + // --- update widget instance --- + public function update( $new_instance, $old_instance ) { + + // 2.3.0: added hide widget if empty option + $instance = $old_instance; + $instance['title'] = $new_instance['title']; + $instance['artist'] = ( isset( $new_instance['artist'] ) ? 1 : 0 ); + $instance['song'] = ( isset( $new_instance['song'] ) ? 1 : 0 ); + $instance['album'] = ( isset( $new_instance['album'] ) ? 1 : 0 ); + $instance['label'] = ( isset( $new_instance['label'] ) ? 1 : 0 ); + $instance['comments'] = ( isset( $new_instance['comments'] ) ? 1 : 0 ); + $instance['hide_empty'] = ( isset( $new_instance['comments'] ) ? 1 : 0 ); + + return $instance; + } + + // --- output widget display --- + public function widget( $args, $instance ) { + + // 2.3.0: added hide widget if empty option + // 2.3.0: filter widget_title whether empty or not + $title = empty( $instance['title'] ) ? '' : $instance['title']; + $title = apply_filters( 'widget_title', $title ); + $artist = $instance['artist']; + $song = $instance['song']; + $album = $instance['album']; + $label = $instance['label']; + $comments = $instance['comments']; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : true; + + // --- set shortcode attributes for display --- + $atts = array( + 'title' => $title, + 'artist' => $artist, + 'song' => $song, + 'album' => $album, + 'label' => $label, + 'comments' => $comments, + 'widget' => 1, + ); + + // --- get default display output --- + // 2.3.0: use shortcode to generate default widget output + $output = radio_station_now_playing_shortcode( $atts ); + + // --- check for widget output override --- + // 2.3.0: added this override filter + $output = apply_filters( 'radio_station_now_playing_widget_override', $output, $args, $atts ); + + // --- open widget container --- + // 2.3.0: added hide widget if empty option + if ( ! $hide_empty || ( $hide_empty && $output ) ) { + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + echo '
    '; + + // --- output widget title --- + echo $args['before_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + if ( !empty( $title ) ) { + echo esc_html( $title ); + } + echo $args['after_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- output widget display --- + echo $output; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- close widget container --- + echo '
    '; + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + // 2.2.4: renamed djonair.css to widgets.css and load for all widgets + // 2.3.0: widgets.css prefixed to rs-widgets.css + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'widgets' ); + } + } +} + +// --- register the widget --- +// 2.2.7: revert anonymous function usage for backwards compatibility +add_action( 'widgets_init', 'radio_station_register_playlist_widget' ); +function radio_station_register_playlist_widget() { + register_widget( 'Playlist_Widget' ); +} diff --git a/includes/class-current-show-widget.php b/includes/class-current-show-widget.php new file mode 100644 index 0000000..a2a79f1 --- /dev/null +++ b/includes/class-current-show-widget.php @@ -0,0 +1,263 @@ + 'DJ_Widget', + 'description' => __( 'The currently playing on-air Show.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Current Show On-Air', 'radio-station' ); + parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + // 2.3.0: convert template style code to straight php echo + echo ' +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + + ' . esc_html( __( 'Width of Show Avatar (in pixels, default full width)', 'radio-station' ) ) . ' +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + + ' . esc_html( __( 'If no Show is scheduled for the current time, display this text.', 'radio-station' ) ) . ' +

    + +

    + +
    + ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' +

    '; + } + + // --- update widget instance values --- + public function update( $new_instance, $old_instance ) { + + $instance = $old_instance; + $instance['title'] = $new_instance['title']; + $instance['display_djs'] = ( isset( $new_instance['display_djs'] ) ? 1 : 0 ); + $instance['djavatar'] = ( isset( $new_instance['djavatar'] ) ? 1 : 0 ); + $instance['link'] = ( isset( $new_instance['link'] ) ? 1 : 0 ); + $instance['default'] = $new_instance['default']; + $instance['time'] = $new_instance['time']; + // 2.2.7: fix checkbox value saving + $instance['show_sched'] = ( isset( $new_instance['show_sched'] ) ? 1 : 0 ); + $instance['show_playlist'] = ( isset( $new_instance['show_playlist'] ) ? 1 : 0 ); + $instance['show_all_sched'] = ( isset( $new_instance['show_all_sched'] ) ? 1 : 0 ); + $instance['show_desc'] = ( isset( $new_instance['show_desc'] ) ? 1 : 0 ); + + // 2.2.4: added title position and avatar width settings + $instance['title_position'] = $new_instance['title_position']; + $instance['avatar_width'] = $new_instance['avatar_width']; + $instance['link_djs'] = ( isset( $new_instance['link_djs'] ) ? 1 : 0 ); + + return $instance; + + } + + // --- widget output --- + public function widget( $args, $instance ) { + + // 2.3.0: filter widget_title whether empty or not + $title = empty( $instance['title'] ) ? '' : $instance['title']; + $title = apply_filters( 'widget_title', $title ); + $display_djs = $instance['display_djs']; + $djavatar = $instance['djavatar']; + $link = $instance['link']; + $default = empty( $instance['default'] ) ? '' : $instance['default']; + $time = empty( $instance['time'] ) ? '' : $instance['time']; + $show_sched = $instance['show_sched']; + $show_playlist = $instance['show_playlist']; + // keep the default settings for people updating from 1.6.2 or earlier + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + // keep the default settings for people updating from 2.0.12 or earlier + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + + // 2.2.4: added title position, avatar width and DJ link settings + $position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; + $width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + // --- set shortcode attributes --- + // 2.3.0: map widget options to shortcode attributes + $atts = array( + 'title' => $title, // + 'display_djs' => $display_djs, + 'show_avatar' => $djavatar, + 'show_link' => $link, + // 'default_name' => '', + 'time' => $time, + 'show_sched' => $show_sched, + 'show_playlist' => $show_playlist, + 'show_all_sched' => $show_all_sched, + 'show_desc' => $show_desc, + // new widget options + 'title_position' => $position, + 'avatar_width' => $width, + 'link_djs' => $link_djs, + 'widget' => 1, + ); + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- open widget container --- + echo '
    '; + + // --- widget title --- + echo $args['before_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + if ( !empty( $title ) ) { + echo esc_html( $title ); + } + echo $args['after_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- get default display output --- + // 2.3.0: use shortcode to generate default widget output + $output = radio_station_current_show_shortcode( $atts ); + + // --- check for widget output override --- + // 2.3.0: added this override filter + $output = apply_filters( 'radio_station_current_show_widget_override', $output, $args, $atts ); + + // --- output widget display --- + if ( $output ) { + echo $output; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + echo '
    '; + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + // 2.2.4: renamed djonair.css to widgets.css and load for all widgets + // 2.3.0: widgets.css prefixed to rs-widgets.css + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'widgets' ); + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + } +} + +// --- register the widget --- +// 2.2.7: revert anonymous function usage for backwards compatibility +add_action( 'widgets_init', 'radio_station_register_dj_widget' ); +function radio_station_register_dj_widget() { + register_widget( 'DJ_Widget' ); +} diff --git a/includes/class-dj-upcoming-widget.php b/includes/class-dj-upcoming-widget.php deleted file mode 100644 index 6b35b98..0000000 --- a/includes/class-dj-upcoming-widget.php +++ /dev/null @@ -1,454 +0,0 @@ - 'DJ_Upcoming_Widget', - 'description' => __( 'The upcoming DJs/Shows.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Upcoming DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    - -

    - -

    - - -

    - -

    - -
    - -

    - - -
    - -
      - - 0 ) ) { - - foreach ( $djs['all'] as $showtime => $dj ) { - if ( is_array( $dj ) && isset( $dj['type'] ) && 'override' === $dj['type'] ) { - ?> -
    • - -
      - -
      - -
      > - -
      - -
      - -
      - - - -
      - -
      - -
      - -
      - -
    • - -
    • - -
      - - - post_title ); ?> - - post_title ); - } - ?> -
      - -
      > - ID, 'thumbnail' ); ?> -
      - -
      - - - post_title ); ?> - - post_title ); - } - ?> -
      - - - -
      - -
      - ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - ?> -
      - ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - ?> - - display_name ); ?> - - display_name ); - } - - $id_count = count( $ids ); - if ( ( 1 === $count && 2 === $id_count ) - || ( $id_count > 2 ) && $count === $id_count - 1 ) { - echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; - } elseif ( $count < $id_count && $id_count > 2 ) { - echo ', '; - } - } - ?> -
      - - - -
      - , - -
      - -
      - , - - -
      - -
    • - -
    • - -
    • - -
    -
    - 'DJ_Widget', - 'description' => __( 'The current on-air DJ.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Show/DJ On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : 12; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - -

    - -

    - -
    - -

    - -
    - - -
      - -
    • - -
      - -
      - -
      > - -
      - -
      - -
      - - - 12 ) {$start_hour = $start_hour - 12;} - - // 2.2.7: add fix to convert back from 24 hour time if past midday - $end_hour = $djs['all'][0]['sched']['end_hour']; - if ( substr( $end_hour, 0, 1 ) === '0' ) { - $end_hour = substr( $end_hour, 1 ); - } elseif ( (int) $end_hour > 12 ) {$end_hour = $end_hour - 12;} - - ?> -
      - -
      - -
      - -
      - -
    • - 0 ) ) { - foreach ( $djs['all'] as $dj ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - $current_sched = radio_station_current_schedule( $scheds ); - ?> -
    • - -
      - - - post_title ); ?> - - post_title ); - } - ?> -
      - -
      > - ID, 'thumbnail' ); ?> -
      - -
      - - - post_title ); ?> - - post_title ); - } - ?> -
      - - - -
      - -
      - ID, 'show_user_list', true ); - $count = 0; - - if ( $ids && is_array( $ids ) ) { - ?> -
      - ID ); - $dj_link = apply_filters( 'radio_station_dj_link', $dj_link, $user_info->ID ); - if ( $link_djs ) { - ?> - - display_name ); ?> - - display_name ); - } - - $id_count = count( $ids ); - if ( ( 1 === $count && 2 === $id_count ) || ( $id_count > 2 && $count === $id_count - 1 ) ) { - echo ' ' . esc_html__( 'and', 'radio-station' ) . ' '; - } elseif ( $count < $id_count && $id_count > 2 ) { - echo ', '; - } - } - ?> -
      - post_content ), 20 ); - $desc_string = apply_filters( 'radio_station_show_description', $desc_string, $dj->ID ); - ?> -
      - -
      - -
      - - - -
      - - - -
      - -
      - -
      - -
      - -
      - -
      - -
      - -
      - -
    • - -
    • - -
    • - -
    -
    - 'Playlist_Widget', - 'description' => __( 'Display the current song.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Now Playing', 'radio-station' ); - parent::__construct( 'Playlist_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - $title = $instance['title']; - $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; - $song = isset( $instance['song'] ) ? $instance['song'] : true; - $album = isset( $instance['album'] ) ? $instance['album'] : false; - $label = isset( $instance['label'] ) ? $instance['label'] : false; - $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - - ?> -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - - - -
    - -
    - -
    - -
    - 'DJ_Upcoming_Widget', + 'description' => __( 'Display the upcoming Shows.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Upcoming Shows', 'radio-station' ); + parent::__construct( 'DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + $title = $instance['title']; + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $default = isset( $instance['default'] ) ? $instance['default'] : ''; + $link = isset( $instance['link'] ) ? $instance['link'] : false; + $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + + // 2.2.4: added title position, avatar width and DJ link options + $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + // 2.3.0: convert template style code to straight php echo + echo ' +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + + ' . esc_html( __( 'Width of Show Avatars (in pixels, default 75px)', 'radio-station' ) ) . ' +

    + +

    + +

    + +

    + +

    + +

    + + ' . esc_html_e( 'If no Show is scheduled for the current time, display this text.', 'radio-station' ) . ' +

    + +

    + +

    + +

    + + ' . esc_html( __( 'Number of upcoming Shows to display.', 'radio-station' ) ) . ' +

    + +

    + +
    + ' . esc_html( __( 'Choose time format for displayed schedules.', 'radio-station' ) ) . ' +

    '; + + } + + // --- update widget instance --- + public function update( $new_instance, $old_instance ) { + + $instance = $old_instance; + $instance['title'] = $new_instance['title']; + $instance['display_djs'] = ( isset( $new_instance['display_djs'] ) ? 1 : 0 ); + $instance['djavatar'] = ( isset( $new_instance['djavatar'] ) ? 1 : 0 ); + $instance['link'] = ( isset( $new_instance['link'] ) ? 1 : 0 ); + $instance['default'] = $new_instance['default']; + $instance['limit'] = $new_instance['limit']; + $instance['time'] = $new_instance['time']; + $instance['show_sched'] = ( isset( $new_instance['show_sched'] ) ? 1 : 0 ); + + // 2.2.4: added title position, avatar width and DJ link settings + $instance['title_position'] = $new_instance['title_position']; + $instance['avatar_width'] = $new_instance['avatar_width']; + $instance['link_djs'] = ( isset( $new_instance['link_djs'] ) ? 1 : 0 ); + + return $instance; + } + + // --- output widget display --- + public function widget( $args, $instance ) { + + // 2.3.0: filter widget_title whether empty or not + $title = empty( $instance['title'] ) ? '' : $instance['title']; + $title = apply_filters( 'widget_title', $title ); + $display_djs = $instance['display_djs']; + $djavatar = $instance['djavatar']; + $link = $instance['link']; + $default = empty( $instance['default'] ) ? '' : $instance['default']; + $limit = empty( $instance['limit'] ) ? '1' : $instance['limit']; + $time = empty( $instance['time'] ) ? '' : $instance['time']; + $show_sched = $instance['show_sched']; + + // 2.2.4: added title position, avatar width and DJ link settings + $position = empty( $instance['title_position'] ) ? 'right' : $instance['title_position']; + $width = empty( $instance['avatar_width'] ) ? '75' : $instance['avatar_width']; + $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + + // --- set shortcode attributes --- + // 2.3.0: map widget options to shortcode attributes + $atts = array( + 'title' => $title, // + 'display_djs' => $display_djs, + 'show_avatar' => $djavatar, + 'show_link' => $link, + // 'default_name' => #default, + 'limit' => $limit, + 'time' => $time, + 'show_sched' => $show_sched, + // 'show_playlist' => $show_playlist, + // 'show_desc' => $show_desc, + // new widget options + 'title_position' => $position, + 'avatar_width' => $width, + 'link_djs' => $link_djs, + 'widget' => 1, + ); + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- open widget container --- + echo '
    '; + + // --- output widget title --- + echo $args['before_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + if ( !empty( $title ) ) { + echo esc_html( $title ); + } + echo $args['after_title']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- get default display output --- + // 2.3.0: use shortcode to generate default widget output + $output = radio_station_upcoming_shows_shortcode( $atts ); + + // --- check for widget output override --- + // 2.3.0: added this override filter + $output = apply_filters( 'radio_station_upcoming_shows_widget_override', $output, $args, $atts ); + + // --- output widget display --- + if ( $output ) { + echo $output; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + // --- close widget container --- + echo '
    '; + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + // 2.2.4: renamed djonair.css to widgets.css and load for all widgets + // 2.3.0: widgets.css prefixed to rs-widgets.css + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'widgets' ); + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.OutputNotEscaped + + } +} + + +// --- register the widget --- +// 2.2.7: revert anonymous function usage for backwards compatibility +add_action( 'widgets_init', 'radio_station_register_djcomingup_widget' ); +function radio_station_register_djcomingup_widget() { + register_widget( 'DJ_Upcoming_Widget' ); +} diff --git a/includes/data-feeds.php b/includes/data-feeds.php new file mode 100644 index 0000000..3608397 --- /dev/null +++ b/includes/data-feeds.php @@ -0,0 +1,1105 @@ +; rel="' . RADIO_STATION_API_DOCS_URL . '"'; + $header = apply_filters( 'radio_station_api_discovery_header', $header ); + if ( $header ) { + header( $header, false ); + } +} + +// --------------------------- +// Add Data API Discovery Link +// --------------------------- +add_action( 'wp_head', 'radio_station_api_discovery_link' ); +function radio_station_api_discovery_link() { + $api_url = radio_station_get_api_url(); + $link = ""; + $link = apply_filters( 'radio_station_api_discovery_link', $link ); + if ( $link ) { + echo $link; // phpcs:ignore WordPress.Security.OutputNotEscaped + } +} + +// ------------------------ +// Add Data API to RSD List +// ------------------------ +add_action( 'xmlrpc_rsd_apis', 'radio_station_api_discovery_rsd' ); +function radio_station_api_discovery_rsd() { + $api_url = radio_station_get_api_url(); + $link = ''; + $link = apply_filters( 'radio_station_api_discovery_rsd', $link ); + if ( $link ) { + echo $link; // phpcs:ignore WordPress.Security.OutputNotEscaped + } +} + + +// ---------------------- +// === Data Functions === +// ---------------------- + +// ---------------- +// Get Station Data +// ---------------- +function radio_station_get_station_data() { + + // --- get station data --- + $stream_url = radio_station_get_stream_url(); + $station_url = radio_station_get_station_url(); + $schedule_url = radio_station_get_schedule_url(); + $language = radio_station_get_language(); + + $now = strtotime( current_time( 'mysql' ) ); + $date_time = date( 'Y-m-d H:i:s', $now ); + + // --- set station data array --- + $station_data = array( + 'stream_url' => $stream_url, + 'station_url' => $station_url, + 'schedule_url' => $schedule_url, + 'language' => $language['slug'], + 'timestamp' => $now, + 'date_time' => $date_time, + 'success' => true, + ); + $station_data = apply_filters( 'radio_station_station_data', $station_data ); + + return $station_data; +} + +// ---------------- +// Add Station Data +// ---------------- +function radio_station_add_station_data( $data ) { + $station_data = radio_station_get_station_data(); + $data = array_merge( $data, $station_data ); + + return $data; +} + +// ------------------ +// Get Broadcast Data +// ------------------ +function radio_station_get_broadcast_data() { + + // --- get broadcast info --- + $current_show = radio_station_get_current_show(); + $next_show = radio_station_get_next_show(); + + // TODO: maybe get now playing playlist ? + // $playlist = radio_station_current_playlist(); + + // --- return current and next show info --- + $broadcast = array( + 'current_show' => $current_show, + 'next_show' => $next_show, + ); + $broadcast = apply_filters( 'radio_station_broadcast_data', $broadcast ); + + return $broadcast; +} + +// -------------- +// Get Shows Data +// -------------- +function radio_station_get_shows_data( $show = false ) { + + $shows = array(); + if ( $show ) { + if ( strstr( $show, ',' ) ) { + $show_ids = explode( ',', $show ); + foreach ( $show_ids as $show ) { + $show = sanitize_title( $show ); + $show = radio_station_get_show( $show ); + $show = radio_station_get_show_data_meta( $show, true ); + $shows[] = $show; + } + } else { + $show = sanitize_title( $show ); + $show = radio_station_get_show( $show ); + $show = radio_station_get_show_data_meta( $show, true ); + $shows = array( $show ); + } + } else { + $shows = radio_station_get_shows(); + if ( count( $shows ) > 0 ) { + foreach ( $shows as $i => $show ) { + $shows[$i] = radio_station_get_show_data_meta( $show ); + } + } + } + $shows = apply_filters( 'radio_station_shows_data', $shows ); + + return $shows; +} + +// --------------- +// Get Genres Data +// --------------- +function radio_station_get_genres_data( $genre = false ) { + + // -- get genre or genres --- + $genres = array(); + if ( $genre ) { + if ( strstr( $genre, ',' ) ) { + $genre_ids = explode( ',', $genre ); + foreach ( $genre_ids as $genre ) { + $genre = sanitize_title( $genre ); + $genre = radio_station_get_genre( $genre ); + $genres[] = $genre; + } + } else { + $genre = sanitize_title( $genre ); + $genres = radio_station_get_genre( $genre ); + } + } else { + $genres = radio_station_get_genres(); + } + + // --- loop genres to get shows --- + if ( count( $genres ) > 0 ) { + foreach ( $genres as $name => $genre ) { + $shows = radio_station_get_genre_shows( $genre['slug'] ); + $genres[$name]['shows'] = array(); + $genres[$name]['show_count'] = 0; + if ( is_object( $shows ) && property_exists( $shows, 'posts' ) + && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { + $genres[$name]['show_count'] = count( $shows->posts ); + foreach ( $shows->posts as $show ) { + $genres[$name]['shows'][] = radio_station_get_show_data_meta( $show ); + } + } + } + } + $genres = apply_filters( 'radio_station_genres_data', $genres ); + + return $genres; +} + +// ------------------ +// Get Languages Data +// ------------------ +function radio_station_get_languages_data( $language = false ) { + + // -- get language or languages --- + $languages_data = array(); + if ( $language ) { + if ( strstr( $language, ',' ) ) { + $language_ids = explode( ',', $language ); + foreach ( $language_ids as $language ) { + $language = sanitize_title( $language ); + $language_data = radio_station_get_language( $language ); + if ( $language_data ) { + $languages_data[$language] = $language_data; + } + } + } else { + $language = sanitize_title( $language ); + $language_data = radio_station_get_language( $language ); + $languages_data[$language] = $language_data; + } + } else { + // --- get main site language --- + $main_language = radio_station_get_language(); + $languages_data = array( $main_language['slug'] => $main_language ); + + // --- get all assigned language terms --- + $args = array( 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, 'hide_empty' => true ); + $terms = get_terms( $args ); + if ( count( $terms ) > 0 ) { + $all_langs = radio_station_get_languages(); + foreach ( $terms as $term ) { + $languages_data[$term->slug] = array( + 'id' => $term->id, + 'slug' => $term->slug, + 'name' => $term->name, + 'description' => $term->description, + 'url' => get_term_link( $term->id, RADIO_STATION_LANGUAGES_SLUG ), + ); + } + } + } + + // --- loop languages to get shows --- + if ( count( $languages_data ) > 0 ) { + foreach ( $languages_data as $slug => $lang ) { + $shows = radio_station_get_language_shows( $slug ); + $languages[$slug]['shows'] = array(); + $languages[$slug]['show_count'] = 0; + if ( is_object( $shows ) && property_exists( $shows, 'posts' ) + && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { + $languages[$slug]['show_count'] = count( $shows->posts ); + foreach ( $shows->posts as $show ) { + $languages[$slug]['shows'][] = radio_station_get_show_data_meta( $show ); + } + } + } + } + + $languages_data = apply_filters( 'radio_station_languages_data', $languages_data, $language ); + + return $languages_data; +} + + +// ------------------- +// === REST Routes === +// ------------------- + +// -------------------- +// Register Rest Routes +// -------------------- +add_action( 'rest_api_init', 'radio_station_register_rest_routes' ); +function radio_station_register_rest_routes() { + + // --- check rest routes are enabled --- + $enabled = radio_station_get_setting( 'enable_data_routes' ); + if ( 'yes' != $enabled ) { + return; + } + + // --- filter route slugs --- + // (can disable individual routes by returning false from filters) + $base = apply_filters( 'radio_station_route_slug_base', 'radio' ); + $station = apply_filters( 'radio_station_route_slug_station', 'station' ); + $broadcast = apply_filters( 'radio_station_route_slug_broadcast', 'broadcast' ); + $schedule = apply_filters( 'radio_station_route_slug_schedule', 'schedule' ); + $shows = apply_filters( 'radio_station_route_slug_shows', 'shows' ); + $show = apply_filters( 'radio_station_route_slug_show', 'show' ); + $genres = apply_filters( 'radio_station_route_slug_genres', 'genres' ); + $genre = apply_filters( 'radio_station_route_slug_genre', 'genre' ); + $languages = apply_filters( 'radio_station_route_slug_languages', 'languages' ); + $language = apply_filters( 'radio_station_route_slug_language', 'language' ); + + // TODO: maybe add endpoint parameters (eg. weekday for schedule) ? + // ref: https://stackoverflow.com/q/53126137/5240159 + + $args = array( 'methods' => 'GET' ); + + // --- Station Route --- + // default URL: /wp-json/radio/station/ + if ( $station ) { + $args['callback'] = 'radio_station_route_station'; + register_rest_route( $base, '/' . $station . '/', $args ); + } + + // --- Show Broadcast Route --- + // default URL: /wp-json/radio/broadcast/ + if ( $broadcast ) { + $args['callback'] = 'radio_station_route_broadcast'; + register_rest_route( $base, '/' . $broadcast . '/', $args ); + } + + // --- Master Schedule Route --- + // default URL: /wp-json/radio/schedule/ + // (?P\d+) + if ( $schedule ) { + $args['callback'] = 'radio_station_route_schedule'; + // $args['args'] => array( + // 'weekday' => array( + // 'validate_callback' => function($param, $request, $key) { + // return is_numeric( $param ); + // } + // ), + // ), + register_rest_route( $base, '/' . $schedule . '/', $args ); + // unset( $args['args'] ); + } + + // --- Show Genre List Route --- + // default URL: /wp-json/radio/genres/ + $args['callback'] = 'radio_station_route_genres'; + if ( $genres ) { + register_rest_route( $base, '/' . $genres . '/', $args ); + } + if ( $genre ) { + register_rest_route( $base, '/' . $genre . '/', $args ); + } + + // --- Show List Route --- + // default URL: /wp-json/radio/shows/ + $args['callback'] = 'radio_station_route_shows'; + if ( $shows ) { + register_rest_route( $base, '/' . $shows . '/', $args ); + } + if ( $show ) { + register_rest_route( $base, '/' . $show . '/', $args ); + } + + // --- Language List Route --- + // default URL: /wp-json/radio/languages/ + $args['callback'] = 'radio_station_route_languages'; + if ( $languages ) { + register_rest_route( $base, '/' . $languages . '/', $args ); + } + if ( $language ) { + register_rest_route( $base, '/' . $language . '/', $args ); + } + +} + +// -------------- +// Get Route URLs +// -------------- +function radio_station_get_route_urls() { + + // --- get and add route links --- + $routes = array(); + $station = radio_station_get_route_url( 'station' ); + if ( $station ) { + $routes['station'] = $station; + } + $broadcast = radio_station_get_route_url( 'broadcast' ); + if ( $broadcast ) { + $routes['broadcast'] = $broadcast; + } + $schedule = radio_station_get_route_url( 'schedule' ); + if ( $schedule ) { + $routes['schedule'] = $schedule; + } + $shows = radio_station_get_route_url( 'shows' ); + if ( $shows ) { + $routes['shows'] = $shows; + } + $genres = radio_station_get_route_url( 'genres' ); + if ( $genres ) { + $routes['genres'] = $genres; + } + $languages = radio_station_get_route_url( 'languages' ); + if ( $languages ) { + $routes['languages'] = $languages; + } + + // --- maybe get and add pro route links --- + $routes = apply_filters( 'radio_station_route_urls', $routes ); + + return $routes; +} + +// ------------- +// Station Route +// ------------- +// (combined data from all routes) +function radio_station_route_station( $request ) { + + $data = array(); + $broadcast = radio_station_get_broadcast_data(); + $data['broadcast'] = $broadcast; + $schedule = radio_station_get_current_schedule(); + $data['schedule'] = $schedule; + $shows_data = radio_station_get_shows_data(); + $data['shows'] = $shows_data; + $data['show_count'] = count( $shows_data ); + $genres_data = radio_station_get_genres_data(); + $data['genres'] = $genres_data; + $data['genre_count'] = count( $genres_data ); + $languages_data = radio_station_get_languages_data(); + $data['languages'] = $languages_data; + $data['language_count'] = count( $languages_data ); + $data = radio_station_add_station_data( $data ); + $data['routes'] = radio_station_get_route_urls(); + + return $data; +} + +// ----------------------- +// Current Broadcast Route +// ----------------------- +function radio_station_route_broadcast( $request ) { + $broadcast = radio_station_get_broadcast_data(); + $broadcast = array( 'broadcast' => $broadcast ); + $broadcast = radio_station_add_station_data( $broadcast ); + $broadcast['routes'] = radio_station_get_route_urls(); + + return $broadcast; +} + +// ------------------- +// Show Schedule Route +// ------------------- +function radio_station_route_schedule( $request ) { + + // --- get current schedule --- + $schedule = radio_station_get_current_schedule(); + + // TODO: validate weekday + // if ( isset($request['weekday']) ) { + // if ( ( $request['weekday'] < 0 ) || ( $request( $weekday ) > 6 ) ) { + // return new WP_Error( 'invalid_weekday', 'Invalid Weekday (valid: 0-6)', array( 'status' => 404 ) ); + // } + // } + + $schedule = array( 'schedule' => $schedule ); + $schedule = radio_station_add_station_data( $schedule ); + $schedule['routes'] = radio_station_get_route_urls(); + + return $schedule; +} + +// --------------- +// Show List Route +// --------------- +function radio_station_route_shows( $request ) { + + // TODO: maybe validate show query parameter ? + $singular = $multiple = false; + if ( isset( $request['show'] ) ) { + if ( !strstr( $request['show'], ',' ) ) { + $singular = true; + } else { + $multiple = true; + } + $show = $request['show']; + } else { + $show = false; + } + + // --- get show list data --- + $shows_data = radio_station_get_shows_data( $show ); + + // --- maybe return route error --- + if ( count( $shows_data ) === 0 ) { + if ( $singular ) { + $code = 'show_not_found'; + $message = 'Requested Show was not found.'; + } elseif ( $multiple ) { + $code = 'shows_not_found'; + $message = 'No Requested Shows were found.'; + } else { + $code = 'no_shows'; + $message = 'No Shows were found.'; + } + + return new WP_Error( $code, $message, array( 'status' => 404 ) ); + } + + if ( $singular ) { + $show_list = array( 'show' => $shows_data[0] ); + } else { + $show_list = array( 'shows' => $shows_data, 'show_count' => count( $shows_data ) ); + } + + // --- return show list --- + $show_list = radio_station_add_station_data( $show_list ); + $show_list['routes'] = radio_station_get_route_urls(); + + return $show_list; +} + +// ---------------- +// Genre List Route +// ---------------- +function radio_station_route_genres( $request ) { + + // TODO: validate genre query parameter ? + $singular = $multiple = false; + if ( isset( $request['genre'] ) ) { + if ( !strstr( $request['genre'], ',' ) ) { + $singular = true; + } else { + $multiple = true; + } + $genre = $request['genre']; + } else { + $genre = false; + } + + // --- get genre list data --- + $genres_data = radio_station_get_genres_data( $genre ); + + // --- maybe return route error --- + if ( count( $genres_data ) === 0 ) { + if ( $singular ) { + $code = 'genre_not_found'; + $message = 'Requested Genre was not found.'; + } elseif ( $multiple ) { + $code = 'genres_not_found'; + $message = 'No Requested Genres were found.'; + } else { + $code = 'no_genres'; + $message = 'No Genres were found.'; + } + + return new WP_Error( $code, $message, array( 'status' => 404 ) ); + } + + if ( $singular ) { + $genre_list = array( 'genre' => $genres_data[0] ); + } else { + $genre_list = array( 'genres' => $genres_data, 'genre_count' => count( $genres_data ) ); + } + + // --- return genre list --- + $genre_list = radio_station_add_station_data( $genre_list ); + $genre_list['routes'] = radio_station_get_route_urls(); + + return $genre_list; +} + +// ------------------- +// Language List Route +// ------------------- +function radio_station_route_languages( $request ) { + + // TODO: validate language query parameter ? + $singular = $multiple = false; + if ( isset( $request['language'] ) ) { + if ( !strstr( $request['language'], ',' ) ) { + $singular = true; + } else { + $multiple = true; + } + $language = $request['language']; + } else { + $language = false; + } + + // --- get language list data --- + $languages_data = radio_station_get_languages_data( $language ); + + // --- maybe return route error --- + if ( count( $languages_data ) === 0 ) { + if ( $singular ) { + $code = 'language_not_found'; + $message = 'Requested Language was not found.'; + } elseif ( $multiple ) { + $code = 'languages_not_found'; + $message = 'No Requested Languages were found.'; + } else { + $code = 'no_languages'; + $message = 'No Languages were found.'; + } + + return new WP_Error( $code, $message, array( 'status' => 404 ) ); + } + + if ( $singular ) { + $languages_list = array( 'language' => $languages_data[0] ); + } else { + $languages_list = array( 'languages' => $languages_data, 'language_count' => count( $languages_data ) ); + } + + // --- return language list --- + $languages_list = radio_station_add_station_data( $languages_list ); + $languages_list['routes'] = radio_station_get_route_urls(); + + return $languages_list; +} + +// --------------- +// Check for Genre +// --------------- +// function radio_station_check_genre( $genre ) { +// $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); +// if ( $term ) {return true;} +// $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); +// if ( $term ) {return true;} +// return false; +// } + + +// ============= +// --- Feeds --- +// ============= + +// -------- +// Add Feed +// -------- +// (modified version of WordPress add_feed function) +function radio_station_add_feed( $feedname, $function ) { + + // note: removed as this is overwriting normal page slugs... + // so /feed/schedule/ overwrites /schedule/ - which is no good! + // global $wp_rewrite; + // if ( ! in_array( $feedname, $wp_rewrite->feeds ) ) { + // $wp_rewrite->feeds[] = $feedname; + // } + + $hook = 'do_feed_' . $feedname; + remove_action( $hook, $hook ); + add_action( $hook, $function, 10, 2 ); + + return $hook; +} + +// --------- +// Add Feeds +// --------- +add_action( 'init', 'radio_station_add_feeds', 11 ); +function radio_station_add_feeds() { + + // --- check feeds are enabled --- + $enabled = radio_station_get_setting( 'enable_data_feeds' ); + if ( 'yes' != $enabled ) { + return; + } + + // --- filter feed slugs --- + $radio = apply_filters( 'radio_station_feed_slug_base', 'radio' ); + $station = apply_filters( 'radio_station_feed_slug_station', 'station' ); + $broadcast = apply_filters( 'radio_station_feed_slug_broadcast', 'broadcast' ); + $schedule = apply_filters( 'radio_station_feed_slug_schedule', 'schedule' ); + $show = apply_filters( 'radio_station_feed_slug_show', 'show' ); + $shows = apply_filters( 'radio_station_feed_slug_shows', 'shows' ); + $genre = apply_filters( 'radio_station_feed_slug_genre', 'genre' ); + $genres = apply_filters( 'radio_station_feed_slug_genres', 'genres' ); + $language = apply_filters( 'radio_station_feed_slug_language', 'language' ); + $languages = apply_filters( 'radio_station_feed_slug_languages', 'languages' ); + + // --- add feeds --- + if ( $radio ) { + radio_station_add_feed( $radio, 'radio_station_feed_radio' ); + } + if ( $station ) { + radio_station_add_feed( $station, 'radio_station_feed_station' ); + } + if ( $broadcast ) { + radio_station_add_feed( $broadcast, 'radio_station_feed_broadcast' ); + } + if ( $schedule ) { + radio_station_add_feed( $schedule, 'radio_station_feed_schedule' ); + } + if ( $shows ) { + radio_station_add_feed( $shows, 'radio_station_feed_shows' ); + } + if ( $show ) { + radio_station_add_feed( $show, 'radio_station_feed_shows' ); + } + if ( $genres ) { + radio_station_add_feed( $genres, 'radio_station_feed_genres' ); + } + if ( $genre ) { + radio_station_add_feed( $genre, 'radio_station_feed_genres' ); + } + if ( $languages ) { + radio_station_add_feed( $languages, 'radio_station_feed_languages' ); + } + if ( $language ) { + radio_station_add_feed( $language, 'radio_station_feed_language' ); + } + + // --- add single feed rewrite rule --- + // (without risking overriding standard permalink slugs) + // https://wordpress.stackexchange.com/questions/351576/add-feed-rewrite-overwriting-standard-permalinks/351603#351603 + $feeds = array( $station, $broadcast, $schedule, $shows, $show, $genres, $genre ); + $feeds = apply_filters( 'radio_station_feed_slugs', $feeds ); + foreach ( $feeds as $i => $feed ) { + if ( !$feed ) { + unset( $feeds[$i] ); + } + } + $feedstring = implode( '|', $feeds ); + $feedrule = '^feed/(' . $feedstring . ')/?$'; + add_rewrite_rule( $feedrule, 'index.php?feed=$matches[1]', 'top' ); + + // --- check if feeds are registered --- + $rewrite_rules = get_option( 'rewrite_rules' ); + if ( !array_key_exists( $feedrule, $rewrite_rules ) ) { + flush_rewrite_rules( false ); + } +} + +// ------------- +// Get Feed URLs +// ------------- +function radio_station_get_feed_urls() { + + // --- get all feed URLs --- + $feeds = array(); + $station = radio_station_get_feed_url( 'station' ); + if ( $station ) { + $feeds['station'] = $station; + } + $broadcast = radio_station_get_feed_url( 'broadcast' ); + if ( $broadcast ) { + $feeds['broadcast'] = $broadcast; + } + $schedule = radio_station_get_feed_url( 'schedule' ); + if ( $schedule ) { + $feeds['schedule'] = $schedule; + } + $shows = radio_station_get_feed_url( 'shows' ); + if ( $shows ) { + $feeds['shows'] = $shows; + } + $genres = radio_station_get_feed_url( 'genres' ); + if ( $genres ) { + $feeds['genres'] = $genres; + } + $languages = radio_station_get_feed_url( 'languages' ); + if ( $languages ) { + $feeds['languages'] = $languages; + } + + // --- filter and return --- + $feeds = apply_filters( 'radio_station_feed_urls', $feeds ); + + return $feeds; +} + +// ------------------------- +// Radio Data Discovery Feed +// ------------------------- +function radio_station_feed_radio() { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + $radio = array( 'success' => true ); + $radio['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $radio ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $radio ); + } +} + +// ---------------------- +// Current Broadcast Feed +// ---------------------- +function radio_station_feed_broadcast( $comment_feed, $feed_name ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + $broadcast = radio_station_get_broadcast_data(); + $broadcast = array( 'broadcast', $broadcast ); + $broadcast = radio_station_add_station_data( $broadcast ); + $broadcast['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $broadcast ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $broadcast ); + } +} + +// ------------------ +// Show Schedule Feed +// ------------------ +function radio_station_feed_schedule( $comment_feed, $feed_name ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + // --- get current schedule --- + $schedule = radio_station_get_current_schedule(); + + // TODO: check for a specified weekday ? + + $schedule = array( 'schedule' => $schedule ); + $schedule = radio_station_add_station_data( $schedule ); + $schedule['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $schedule ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $schedule ); + } +} + +// -------------- +// Show List Feed +// -------------- +function radio_station_feed_shows( $comment_feed, $feed_name ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + // --- check for single show query --- + $singular = $multiple = false; + if ( isset( $_GET['show'] ) ) { + if ( !strstr( $_GET['show'], ',' ) ) { + $singular = true; + } else { + $singular = false; + } + $show = $_GET['show']; + } else { + $show = false; + } + + // --- get show list data --- + $shows_data = radio_station_get_shows_data( $show ); + + // --- maybe output feed error message --- + if ( count( $shows_data ) === 0 ) { + if ( $singular ) { + $details = __( 'Requested Show was not found.', 'radio-station' ); + } elseif ( $multiple ) { + $details = __( 'No Requested Shows were found.', 'radio-station' ); + } else { + $details = __( 'No Shows were found.', 'radio-station' ); + } + radio_station_feed_not_found( $details ); + + return; + } + + if ( $singular ) { + $show_list = array( 'show' => $shows_data[0] ); + } else { + $show_list = array( 'shows' => $shows_data, 'show_count' => count( $shows_data ) ); + } + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + print_r( $show_list ); + } + + // --- output encoded show list --- + $show_list = radio_station_add_station_data( $show_list ); + $show_list['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $show_list ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $show_list ); + } +} + +// --------------- +// Genre List Feed +// --------------- +function radio_station_feed_genres( $comment_feed, $feed_name ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + // --- check for single genre query --- + $singular = $multiple = false; + if ( isset( $_GET['genre'] ) ) { + if ( !strstr( $_GET['genre'], ',' ) ) { + $singular = true; + } else { + $multiple = true; + } + $genre = $_GET['genre']; + } else { + $genre = false; + } + + // --- get genre list data --- + $genres_data = radio_station_get_genres_data( $genre ); + + // --- maybe output feed error message --- + if ( count( $genres_data ) === 0 ) { + if ( $singular ) { + $details = __( 'Requested Genre was not found.', 'radio-station' ); + } elseif ( $multiple ) { + $details = __( 'No Requested Genres were found.', 'radio-station' ); + } else { + $details = __( 'No Genres were found.', 'radio-station' ); + } + radio_station_feed_not_found( $details ); + + return; + } + + // --- output encoded genre list --- + if ( $singular ) { + $genre_list = array( 'genre' => $genres_data[0] ); + } else { + $genre_list = array( 'genres' => $genres_data, 'genre_count' => count( $genres_data ) ); + } + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + print_r( $genre_list ); + } + + $genre_list = radio_station_add_station_data( $genre_list ); + $genre_list['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $genre_list ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $genre_list ); + } +} + +// ------------------- +// Languages List Feed +// ------------------- +function radio_station_feed_languages( $comment_feed, $feed_name ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + // --- check for single language query --- + $singular = $multiple = false; + if ( isset( $_GET['language'] ) ) { + if ( !strstr( $_GET['language'], ',' ) ) { + $singular = true; + } else { + $multiple = true; + } + $language = $_GET['language']; + } else { + $language = false; + } + + // --- get genre list data --- + $languages_data = radio_station_get_languages_data( $language ); + + // --- maybe output feed error message --- + if ( count( $languages_data ) === 0 ) { + if ( $singular ) { + $details = __( 'Requested Language was not found.', 'radio-station' ); + } elseif ( $multiple ) { + $details = __( 'No Requested Languages were found.', 'radio-station' ); + } else { + $details = __( 'No Languages were found.', 'radio-station' ); + } + radio_station_feed_not_found( $details ); + + return; + } + + // --- output encoded language list --- + if ( $singular ) { + $language_list = array( 'language' => $languages_data[0] ); + } else { + $language_list = array( 'languages' => $languages_data, 'language_count' => count( $languages_data ) ); + } + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + print_r( $language_list ); + } + + $language_list = radio_station_add_station_data( $language_list ); + $language_list['feeds'] = radio_station_get_feed_urls(); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $language_list ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $language_list ); + } +} + +// -------------------- +// Not Found Feed Error +// -------------------- +function radio_station_feed_not_found( $details ) { + + if ( isset( $_GET['debug'] ) && ( '1' == $_GET['debug'] ) ) { + header( 'Content-Type: text/plain' ); + } + + $error = array( + 'success' => false, + 'errors' => array( + 'status' => 404, + 'code' => 404, + 'title' => __( 'Error 404 Not Found', 'radio-station' ), + 'message' => __( 'The requested data could not be found.', 'radio-station' ), + 'detail' => $details, + ), + ); + + if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { + header( 'Content-Type: application/rss+xml' ); + echo radio_station_format_xml( $error ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + header( 'Content-Type: application/json' ); + echo json_encode( $error ); + } +} + +// ------------------ +// Format Data to XML +// ------------------ +function radio_station_format_xml( $data ) { + + $xml = new SimpleXMLElement( '' ); + radio_station_array_to_xml( $xml, $data ); + $output = $xml->asXML(); + $dom = new DOMDocument(); + // $dom->formatOutput = true; + $dom->loadXML( $output ); + $output = $dom->saveXML(); + + return $output; +} + +// -------------------- +// Convert Array to XML +// -------------------- +function radio_station_array_to_xml( SimpleXMLElement $object, array $data ) { + + foreach ( $data as $key => $value ) { + if ( is_array( $value ) ) { + $newobject = $object->addChild( $key ); + radio_station_array_to_xml( $newobject, $value ); + } else { + $object->addChild( $key, htmlspecialchars( $value ) ); + } + } +} + diff --git a/includes/master-schedule.php b/includes/master-schedule.php index a333041..91c9c30 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -1,38 +1,130 @@ '12', - 'show_link' => 1, - 'display_show_time' => 1, - 'list' => 'table', - 'show_image' => 0, - 'show_djs' => 0, - 'show_genres' => 0, - 'divheight' => 45, - ), - $atts, - 'master-schedule' + // --- make list attribute backward compatible --- + // 2.3.0: convert old list attribute to view + if ( !isset( $atts['view'] ) && isset( $atts['list'] ) ) { + if ( 1 === (int) $atts['list'] ) { + $atts['list'] = 'list'; + } + $atts['view'] = $atts['list']; + } + + // 2.3.0: added show_hosts (alias of show_djs) + // 2.3.0: added show_file attribute (default off) + // 2.3.0: added show_encore attribute (default on) + // 2.3.0: added display clock attribute (default on) + // 2.3.0: added display selector attribute (default on) + // 2.3.0: set default time format according to plugin setting + // 2.3.0: set default table display to new table formatting + $time_format = (int) radio_station_get_setting( 'clock_time_format' ); + $defaults = array( + 'time' => $time_format, + 'show_link' => 1, + 'display_show_time' => 1, + 'view' => 'table', + // 'list' => 0, + 'divheight' => 45, + 'show_djs' => 0, + // new display options + 'selector' => 1, + 'clock' => 1, + 'show_image' => 0, + 'show_hosts' => 0, + 'show_genres' => 0, + 'show_file' => 0, + 'show_encore' => 1, ); + $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); - $timeformat = $atts['time']; + // --- enqueue schedule stylesheet --- + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'schedule' ); - // $overrides = radio_station_master_get_overrides(true); + // --- set initial empty output string --- + $output = ''; - // set up the structure of the master schedule - $default_dj = get_option( 'dj_default_name' ); + // --- disable clock if feature is not present --- + // (temporarily while clock is in development) + if ( !function_exists( 'radio_station_clock_shortcode' ) ) { + $atts['clock'] = 0; + } + + // --- get show selection links and clock display --- + // 2.3.0: added server/user clock display + if ( $atts['selector'] ) { + $selector = radio_station_master_schedule_selector(); + } + if ( $atts['clock'] ) { + $clock = radio_station_clock_shortcode(); + } + + // --- table for selector and clock --- + // 2.3.0: moved out from templates to apply to all views + $output .= '
    '; + + $output .= '
    '; + if ( $atts['clock'] ) { + $output .= $clock; + } else { + // --- display radio timezone --- + $output .= radio_station_timezone_shortcode(); + } + $output .= '
    '; - // check to see what day of the week we need to start on - $start_of_week = get_option( 'start_of_week' ); + $output .= '
    '; + if ( $atts['selector'] ) { + $output .= $selector; + } + $output .= '
    '; + + $output .= '

    '; + + // ------------------------- + // New Master Schedule Views + // ------------------------- + + // --- load master schedule template --- + // 2.2.7: added tabbed master schedule template + // 2.3.0: use new data model for table and tabs view + // 2.3.0: check for user theme templates + if ( 'table' == $atts['view'] ) { + $template = radio_station_get_template( 'file', 'master-schedule-table.php' ); + require $template; + + return $output; + } elseif ( 'tabs' == $atts['view'] ) { + // 2.2.7: add tab switching javascript to footer + add_action( 'wp_footer', 'radio_station_master_schedule_tabs_js' ); + $template = radio_station_get_template( 'file', 'master-schedule-tabs.php' ); + require $template; + + return $output; + } elseif ( 'list' == $atts['view'] ) { + $template = radio_station_get_template( 'file', 'master-schedule-list.php' ); + require $template; + + return $output; + } + + // ---------------------- + // Legacy Master Schedule + // ---------------------- + // 2.3.0: remove usused default DJ name option + // $default_dj = get_option( 'dj_default_name' ); + + // --- check to see what day of the week we need to start on --- + $start_of_week = get_option( 'start_of_week' ); $days_of_the_week = array( 'Sunday' => array(), 'Monday' => array(), @@ -42,24 +134,23 @@ function radio_station_master_schedule( $atts ) { 'Friday' => array(), 'Saturday' => array(), ); - $week_start = array_slice( $days_of_the_week, $start_of_week ); - + $week_start = array_slice( $days_of_the_week, $start_of_week ); foreach ( $days_of_the_week as $i => $weekday ) { if ( $start_of_week > 0 ) { - $add = $days_of_the_week[ $i ]; - unset( $days_of_the_week[ $i ] ); - $days_of_the_week[ $i ] = $add; + $add = $days_of_the_week[$i]; + unset( $days_of_the_week[$i] ); + $days_of_the_week[$i] = $add; } - $start_of_week--; + $start_of_week --; } - // create the master_list array based on the start of the week + // --- create the master_list array based on the start of the week --- $master_list = array(); - for ( $i = 0; $i < 24; $i++ ) { - $master_list[ $i ] = $days_of_the_week; + for ( $i = 0; $i < 24; $i ++ ) { + $master_list[$i] = $days_of_the_week; } - // get the show schedules, excluding shows marked as inactive + // --- get the show schedules, excluding shows marked as inactive --- $show_shifts = $wpdb->get_results( "SELECT meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta @@ -75,49 +166,54 @@ function radio_station_master_schedule( $atts ) { )" ); - // insert schedules into the master list + // --- insert scheduled shifts into the master list --- foreach ( $show_shifts as $shift ) { $shift->meta_value = maybe_unserialize( $shift->meta_value ); // if a show is not scheduled yet, unserialize will return false... fix that. - if ( ! is_array( $shift->meta_value ) ) { - $shift->meta_value = array();} + if ( !is_array( $shift->meta_value ) ) { + $shift->meta_value = array(); + } foreach ( $shift->meta_value as $time ) { - // switch to 24-hour time - if ( 'pm' === $time['start_meridian'] && 12 !== (int) $time['start_hour'] ) { - $time['start_hour'] += 12; - } - if ( 'am' === $time['start_meridian'] && 12 === (int) $time['start_hour'] ) { - $time['start_hour'] = 0; - } + // 2.3.0: added check for show disabled switch + if ( !isset( $time['disabled'] ) || ( 'yes' == $time['disabled'] ) ) { - if ( 'pm' === $time['end_meridian'] && 12 !== (int) $time['end_hour'] ) { - $time['end_hour'] += 12; - } - if ( 'am' === $time['end_meridian'] && 12 === (int) $time['end_hour'] ) { - $time['end_hour'] = 0; - } + // --- switch to 24-hour time --- + if ( 'pm' === $time['start_meridian'] && 12 !== (int) $time['start_hour'] ) { + $time['start_hour'] += 12; + } + if ( 'am' === $time['start_meridian'] && 12 === (int) $time['start_hour'] ) { + $time['start_hour'] = 0; + } - // check if we're spanning multiple days - $time['multi-day'] = 0; - if ( $time['start_hour'] > $time['end_hour'] || $time['start_hour'] === $time['end_hour'] ) { - $time['multi-day'] = 1; - } + if ( 'pm' === $time['end_meridian'] && 12 !== (int) $time['end_hour'] ) { + $time['end_hour'] += 12; + } + if ( 'am' === $time['end_meridian'] && 12 === (int) $time['end_hour'] ) { + $time['end_hour'] = 0; + } + + // --- check if we are spanning multiple days --- + $time['multi-day'] = 0; + if ( $time['start_hour'] > $time['end_hour'] || $time['start_hour'] === $time['end_hour'] ) { + $time['multi-day'] = 1; + } - $master_list[ $time['start_hour'] ][ $time['day'] ][ $time['start_min'] ] = array( - 'id' => $shift->post_id, - 'time' => $time, - ); + $master_list[$time['start_hour']][$time['day']][$time['start_min']] = array( + 'id' => $shift->post_id, + 'time' => $time, + ); + } } } - // sort the array by time + // --- sort the array by time --- foreach ( $master_list as $hour => $days ) { foreach ( $days as $day => $min ) { ksort( $min ); - $master_list[ $hour ][ $day ] = $min; + $master_list[$hour][$day] = $min; // we need to take into account shows that start late at night and end the following day foreach ( $min as $i => $time ) { @@ -130,13 +226,13 @@ function radio_station_master_schedule( $atts ) { // if it ends after midnight, fix it // if it starts at night and ends in the morning, end hour is on the following day if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || - // if the start and end times are identical, assume the end time is the following day - ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || - // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day - ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) - ) { + // if the start and end times are identical, assume the end time is the following day + ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || + // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day + ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) + ) { - if ( 12 === (int) $timeformat ) { + if ( 12 === (int) $atts['time'] ) { $time['time']['real_start'] = ( $time['time']['start_hour'] - 12 ) . ':' . $time['time']['start_min']; } else { $pad_hour = ''; @@ -145,118 +241,134 @@ function radio_station_master_schedule( $atts ) { } $time['time']['real_start'] = $pad_hour . $time['time']['start_hour'] . ':' . $time['time']['start_min']; } - // $time['time']['start_hour'] = "0"; - // $time['time']['start_min'] = "00"; - // $time['time']['start_meridian'] = "am"; $time['time']['rollover'] = 1; - $nextday = ''; - switch ( $day ) { - case 'Sunday': - $nextday = 'Monday'; - break; - case 'Monday': - $nextday = 'Tuesday'; - break; - case 'Tuesday': - $nextday = 'Wednesday'; - break; - case 'Wednesday': - $nextday = 'Thursday'; - break; - case 'Thursday': - $nextday = 'Friday'; - break; - case 'Friday': - $nextday = 'Saturday'; - break; - case 'Saturday': - $nextday = 'Sunday'; - break; - } + // 2.3.0: use new get next day function + $nextday = radio_station_get_next_day( $day ); - $master_list[0][ $nextday ]['00'] = $time; + $master_list[0][$nextday]['00'] = $time; } } } } - $output = ''; - - // 2.2.7: added tabbed master schedule template - if ( 1 === (int) $atts['list'] || 'list' === $atts['list'] ) { - require( RADIO_STATION_DIR . '/templates/master-schedule-list.php' ); - } elseif ( 'divs' === $atts['list'] ) { - require( RADIO_STATION_DIR . '/templates/master-schedule-div.php' ); - } elseif ( 'tabs' === $atts['list'] ) { - require( RADIO_STATION_DIR . '/templates/master-schedule-tabs.php' ); - // 2.2.7: add tab switching javascript to footer - add_action( 'wp_footer', 'radio_station_master_schedule_tabs_js' ); - } else { - require( RADIO_STATION_DIR . '/templates/master-schedule-default.php' ); + // --- check for schedule overrides --- + // ? TODO - check/include schedule overrides in legacy template views + // $overrides = radio_station_master_get_overrides( true ); + + // --- include the specified master schedule output template --- + // 2.3.0: check for user theme templates + if ( 'divs' == $atts['view'] ) { + $output = ''; // no selector / clock support yet + $template = radio_station_get_template( 'file', 'master-schedule-div.php' ); + require $template; + } elseif ( 'legacy' == $atts['view'] ) { + $template = radio_station_get_template( 'file', 'master-schedule-legacy.php' ); + require $template; } return $output; - } -add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); -// --- add javascript for highlighting shows based on genre --- -function radio_station_master_fetch_js_filter() { +// ---------------------- +// Show / Genre Selector +// ---------------------- +function radio_station_master_schedule_selector() { - $js = '
    ' . __( 'Genres', 'radio-station' ) . ': '; + // --- open genre highlighter div --- + $html = '
    '; + $html .= '' . esc_html( __( 'Genres', 'radio-station' ) ) . ': '; - $taxes = get_terms( - 'genres', - array( - 'hide_empty' => true, - 'orderby' => 'name', - 'order' => 'ASC', - ) + // --- get genres --- + $args = array( + 'hide_empty' => true, + 'orderby' => 'name', + 'order' => 'ASC', ); - foreach ( $taxes as $i => $tax ) { - $js .= '' . $tax->name . ''; - // 2.2.2: fix to not add pipe suffix for last genre - if ( count( $taxes ) - 1 !== $i ) { - $js .= ' | '; - } + $genres = get_terms( RADIO_STATION_GENRES_SLUG, $args ); + + // --- genre highlight links --- + // 2.3.0: fix by imploding with genre link spacer + $genre_links = array(); + foreach ( $genres as $i => $genre ) { + $slug = sanitize_title_with_dashes( $genre->name ); + $javascript = 'javascript:show_highlight(\'' . $slug . '\')'; + $title = __( 'Click to toggle Highlight of Shows with this Genre.', 'radio-station' ); + $genre_link = ''; + $genre_link .= esc_html( $genre->name ) . ''; + $genre_links[] = $genre_link; } - - $js .= '
    '; - - $js .= ''; - - return $js; + $html .= implode( ' | ', $genre_links ); + + $html .= '
    '; + + // --- genre highlighter script --- + // 2.3.0: improved to highlight / unhighlight multiple genres + $js = "var highlighted_genres = new Array(); + function show_highlight(genre) { + if (jQuery('#genre-highlight-'+genre).hasClass('highlighted')) { + jQuery('#genre-highlight-'+genre).removeClass('highlighted'); + + jQuery('.master-show-entry').each(function() { + jQuery(this).removeClass('highlighted'); + }); + + j = 0; new_genre_highlights = new Array(); + for (i = 0; i < highlighted_genres.length; i++) { + console.log(i+':'+highlighted_genres[i]); + if (highlighted_genres[i] != genre) { + jQuery('.'+genre).addClass('highlighted'); + new_genre_highlights[j] = highlighted_genres[i]; j++; + } + } + highlighted_genres = new_genre_highlights; + + } else { + jQuery('#genre-highlight-'+genre).addClass('highlighted'); + highlighted_genres[highlighted_genres.length] = genre; + jQuery('.'+genre).each(function () { + jQuery(this).addClass('highlighted'); + }); + } + }"; + + // --- enqueue script --- + // 2.3.0: add script code to existing handle + wp_add_inline_script( 'radio-station', $js ); + + return $html; } -// --- javascript for tab switching --- -// 2.2.7: added for tabbed display type +// ------------------------ +// Tab Switching Javascript +// ------------------------ +// 2.2.7: added for tabbed schedule view function radio_station_master_schedule_tabs_js() { - echo ''; + });"; + + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station', $js ); } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 04c7cd5..45b9373 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -8,26 +8,37 @@ // === Metabox Positions === // - Metaboxes Above Content Area -// - Shift Genre Metabox +// - Modify Taxonomy Metaxboxes +// === Language Selection === +// - Add Language Metabox +// - Language Selection Metabox +// - Update Language Term on Save // === Playlists === // - Add Playlist Data Metabox // - Playlist Data Metabox +// - Add Assign Playlist to Show Metabox +// - Assign Playlist to Show Metabox // - Update Playlist Data // - Add Playlist List Columns // - Playlist List Column Data // - Playlist List Column Styles // === Shows === // - Add Related Show Metabox -// - Related Shows Metabox +// - Related Show Metabox // - Update Related Show -// - Add Assign Playlist to Show Metabox -// - Assign Playlist to Show Metabox -// - Add Playlist Info Metabox -// - Playlist Info Metabox -// - Add Assign DJs to Show Metabox -// - Assign DJs to Show Metabox +// - Add Show Info Metabox +// - Show Info Metabox +// - Add Assign Hosts to Show Metabox +// - Assign Hosts to Show Metabox +// - Add Assign Producers to Show Metabox +// - Assign Producers to Show Metabox // - Add Show Shifts Metabox // - Show Shifts Metabox +// - Add Show Description Helper Metabox +// - Show Description Helper Metabox +// - Rename Show Featured Image Metabox +// - Add Show Images Metabox +// - Show Images Metabox // - Update Show Metadata // - Add Show List Columns // - Show List Column Data @@ -43,6 +54,7 @@ // - Add Schedule Override Month Filter // === Post Type List Query Filter === + // ------------------------- // === Metabox Positions === // ------------------------- @@ -50,23 +62,272 @@ // ---------------------------- // Metaboxes Above Content Area // ---------------------------- -// --- shows plugin metaboxes above editor box for plugin CPTs --- +// (shows metaboxes above Editor area for Radio Station CPTs) add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); function radio_station_top_meta_boxes() { global $post; do_meta_boxes( get_current_screen(), 'top', $post ); } +// ------------------------- +// Modify Taxonomy Metaboxes +// ------------------------- +// 2.3.0: also apply to override post type +// 2.3.0: remove default languages metabox from shows +add_action( 'add_meta_boxes', 'radio_station_modify_taxonomy_meta_boxes' ); +function radio_station_modify_taxonomy_meta_boxes() { + global $wp_meta_boxes; + + // --- move genre selection metabox --- + $id = RADIO_STATION_GENRES_SLUG . 'div'; + if ( isset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core'][$id] ) ) { + $genres = $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core'][$id]; + unset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core'][$id] ); + $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['high'][$id] = $genres; + } + // 2.3.0: do similar for overrides post type + if ( isset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core'][$id] ) ) { + $genres = $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core'][$id]; + unset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core'][$id] ); + $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['high'][$id] = $genres; + } + + // --- remove default language metabox from shows --- + if ( isset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core'][RADIO_STATION_LANGUAGES_SLUG . 'div'] ) ) { + unset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core'][RADIO_STATION_LANGUAGES_SLUG . 'div'] ); + } + if ( isset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ) ) { + unset( $wp_meta_boxes[RADIO_STATION_SHOW_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ); + } + if ( isset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ) ) { + unset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ); + } + + // echo ""; +} + + +// -------------------------- +// === Language Selection === +// -------------------------- + +// -------------------- +// Add Language Metabox +// -------------------- +// 2.3.0: add language selection metabox +add_action( 'add_meta_boxes', 'radio_station_add_show_language_metabox' ); +function radio_station_add_show_language_metabox() { + // note: only added to overrides as moved into show info metabox for shows + add_meta_box( + RADIO_STATION_LANGUAGES_SLUG . 'div', + __( 'Show Language', 'radio-station' ), + 'radio_station_show_language_metabox', + array( RADIO_STATION_OVERRIDE_SLUG ), + 'side', + 'high' + ); +} + +// -------------------------- +// Language Selection Metabox +// -------------------------- +// 2.3.0: added language selection metabox +function radio_station_show_language_metabox() { + + // --- use same noncename as default box so no save_post hook needed --- + wp_nonce_field( 'taxonomy_' . RADIO_STATION_LANGUAGES_SLUG, 'taxonomy_noncename' ); + + // --- get terms associated with this post --- + $terms = wp_get_object_terms( get_the_ID(), RADIO_STATION_LANGUAGES_SLUG ); + + // --- get all language options --- + $languages = radio_station_get_languages(); + + echo '
    '; + + // --- get main language --- + $main_language = radio_station_get_language(); + foreach ( $languages as $i => $language ) { + if ( strtolower( $main_language['slug'] ) == strtolower( $language['language'] ) ) { + $label = $language['native_name']; + if ( $language['native_name'] != $language['english_name'] ) { + $label .= ' (' . $language['english_name'] . ')'; + } + } + } + + if ( isset( $label ) ) { + echo '' . esc_html( __( 'Main Radio Language', 'radio-station' ) ) . ':
    '; + echo esc_html( $label ) . '
    '; + } + + echo '
    ' . esc_html( __( 'Select below if Show language(s) differ.', 'radio-station' ) ) . '
    '; + + echo '
      '; + + // --- loop existing terms --- + $term_slugs = array(); + foreach ( $terms as $term ) { + + $slug = $term->slug; + $term_slugs[] = $slug; + $label = $term->name; + if ( !empty( $term->description ) ) { + $label .= ' (' . $term->description . ')'; + } + + echo '
    • '; + + // --- hidden input for term saving --- + // echo ''; + echo ''; + + // --- language term label --- + echo ''; + + // --- remove term button --- + echo ''; + + echo '
    • '; + } + echo '
    '; + + // --- new language selection list --- + echo '
    '; + + // --- add language term button --- + echo '
    ' . esc_html( __( 'Click on a Language to Add it.', 'radio-station' ) ) . '
    '; + + echo '
    '; + + // --- language selection javascript --- + $js = "function radio_add_language() { + /* get and disable selected language item */ + select = document.getElementById('rs-add-language-selection'); + options = select.options; + for (i = 0; i < options.length; i++) { + if (options[i].selected) { + optionvalue = options[i].value; + optionlabel = options[i].innerHTML; + options[i].setAttribute('disabled', 'disabled'); + } + } + select.selectedIndex = 0; + + /* add item to term list */ + listitem = document.createElement('li'); + listitem.setAttribute('id', '" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "_tax-'+optionvalue); + input = document.createElement('input'); + input.value = optionvalue; + input.setAttribute('type', 'hidden'); + input.setAttribute('name', '" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "[]'); + input.setAttribute('id', 'in-" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "_tax-'+optionvalue); + listitem.appendChild(input); + label = document.createElement('label'); + label.innerHTML = optionlabel; + listitem.appendChild(label); + button = document.createElement('input'); + button.setAttribute('type', 'button'); + button.setAttribute('class', 'button button-secondary'); + button.setAttribute('onclick', 'radio_remove_language(\"'+optionvalue+'\");'); + button.setAttribute('value', 'x'); + listitem.appendChild(button); + document.getElementById('" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "_taxradiolist').appendChild(listitem); + } + function radio_remove_language(term) { + /* remove item from term list */ + listitem = document.getElementById('" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "_tax-'+term); + listitem.parentNode.removeChild(listitem); + + /* re-enable language select option */ + select = document.getElementById('rs-add-language-selection'); + options = select.options; + for (i = 0; i < options.length; i++) { + if (options[i].value == term) { + options[i].removeAttribute('disabled'); + } + } + }"; + + // --- add script inline --- + wp_add_inline_script( 'radio-station-admin', $js ); + + // --- language input style fixes --- + echo ""; +} + // ---------------------------- -// Shift Genre Metabox on Shows +// Update Language Term on Save // ---------------------------- -// --- moves genre metabox above publish box --- -add_action( 'add_meta_boxes_show', 'radio_station_genre_meta_box_order' ); -function radio_station_genre_meta_box_order() { - global $wp_meta_boxes; - $genres = $wp_meta_boxes['show']['side']['core']['genresdiv']; - unset( $wp_meta_boxes['show']['side']['core']['genresdiv'] ); - $wp_meta_boxes['show']['side']['high']['genresdiv'] = $genres; +// 2.3.0: added to sync language names to language term +add_action( 'save_post', 'radio_station_language_term_filter', 11 ); +function radio_station_language_term_filter( $post_id ) { + + // ---- check permissions --- + if ( !isset( $_POST[RADIO_STATION_LANGUAGES_SLUG] ) ) {return;} + $check = wp_verify_nonce( $_POST['taxonomy_noncename'], 'taxonomy_' . RADIO_STATION_LANGUAGES_SLUG ); + if ( !$check ) {return;} + $taxonomy_obj = get_taxonomy( RADIO_STATION_LANGUAGES_SLUG ); + if ( !current_user_can( $taxonomy_obj->cap->assign_terms ) ) {return;} + + // --- loop and set posted terms --- + $terms = $_POST[RADIO_STATION_LANGUAGES_SLUG]; + + $term_ids = array(); + if ( is_array( $terms ) && ( count( $terms ) > 0 ) ) { + $languages = radio_station_get_languages(); + foreach ( $terms as $i => $term_slug ) { + + foreach ( $languages as $j => $language ) { + + if ( strtolower( $language['language'] ) == strtolower( $term_slug ) ) { + + // --- get existing term --- + $term = get_term_by( 'slug', $term_slug, RADIO_STATION_LANGUAGES_SLUG ); + + // --- set language name and description to the term --- + if ( $term ) { + $args = array( + 'slug' => $term_slug, + 'name' => $language['native_name'], + 'description ' => $language['english_name'], + ); + wp_update_term( $term->term_id, RADIO_STATION_LANGUAGES_SLUG, $args ); + $term_ids[] = $term->term_id; + } else { + $args = array( + 'slug' => $term_slug, + 'description' => $language['english_name'], + ); + $term = wp_insert_term( $language['native_name'], RADIO_STATION_LANGUAGES_SLUG, $args ); + if ( !is_wp_error( $term ) ) { + $term_ids[] = $term['term_id']; + } + } + } + } + } + } + + // --- set the language terms --- + error_log( print_r( $term_ids, true ) , 3, WP_CONTENT_DIR.'/tax-debug.log' ); + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); } @@ -80,14 +341,14 @@ function radio_station_genre_meta_box_order() { // --- Add custom repeating meta field for the playlist edit form --- // (Stores multiple associated values as a serialized string) // Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_custom_box', 1 ); -function radio_station_myplaylist_add_custom_box() { +add_action( 'add_meta_boxes', 'radio_station_add_playlist_metabox' ); +function radio_station_add_playlist_metabox() { // 2.2.2: change context to show at top of edit screen add_meta_box( - 'dynamic_sectionid', + 'radio-station-playlist-metabox', __( 'Playlist Entries', 'radio-station' ), - 'radio_station_myplaylist_inner_custom_box', - 'playlist', + 'radio_station_playlist_metabox', + RADIO_STATION_PLAYLIST_SLUG, 'top', // shift to top 'high' ); @@ -96,40 +357,38 @@ function radio_station_myplaylist_add_custom_box() { // --------------------- // Playlist Data Metabox // --------------------- -// -- prints the playlist entry box to the main column on the edit screen --- -function radio_station_myplaylist_inner_custom_box() { +function radio_station_playlist_metabox() { global $post; // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMeta_noncename' ); - ?> -
    - ID, 'playlist', false ); - $c = 1; + $c = 1; + + echo '
    '; echo ''; echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - // echo ""; - // echo ""; - // echo ""; - // echo ""; + echo ''; + echo ''; + echo ''; + echo ''; + // echo ""; + // echo ""; + // echo ""; + // echo ""; echo ''; - if ( isset( $entries[0] ) && ! empty( $entries[0] ) ) { + if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { foreach ( $entries[0] as $track ) { if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { echo ''; echo ''; @@ -143,49 +402,52 @@ function radio_station_myplaylist_inner_custom_box() { echo ''; - echo ''; - echo ''; + echo ''; echo ''; - $c++; + $c ++; } } } echo '
    ' . esc_html__( 'Artist', 'radio-station' ) . '' . esc_html__( 'Song', 'radio-station' ) . '' . esc_html__( 'Album', 'radio-station' ) . '' . esc_html__( 'Record Label', 'radio-station' ) . '".__('DJ Comments', 'radio-station')."".__('New', 'radio-station')."".__('Status', 'radio-station')."".__('Remove', 'radio-station')."' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '" . esc_html( __( 'DJ Comments', 'radio-station' ) ) . "" . esc_html( __( 'New', 'radio-station' ) ) . "" . esc_html( __( 'Status', 'radio-station') ) . "" . esc_html( __( 'Remove', 'radio-station') ) . "
    ' . esc_html( $c ) . '' . esc_html__( 'Comments', 'radio-station' ) . ' '; echo '' . esc_html__( 'New', 'radio-station' ) . ' '; + echo '' . esc_html( __( 'New', 'radio-station' ) ) . ' '; $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; echo ''; - echo ' ' . esc_html__( 'Status', 'radio-station' ) . ' '; + echo ' ' . esc_html( __( 'Status', 'radio-station' ) ) . ' '; echo '' . esc_html__( 'Remove', 'radio-station' ) . '' . esc_html( __( 'Remove', 'radio-station' ) ) . '
    '; ?> - -
    - -
    + "; + + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + + echo '
    '; + + echo '
    '; + echo '

    '; -
    -

    - post_status, array( 'publish', 'future', 'private' ) ) || ( 0 === $post->ID ) ) { - if ( $can_publish ) : - if ( ! empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) : - ?> - - post_status, array( 'publish', 'future', 'private' ) ) || 0 === $post->ID ) { + if ( $can_publish ) { + if ( !empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) { + echo ''; submit_button( - __( 'Schedule' ), + __( 'Schedule', 'radio-station' ), 'primary', 'publish', false, @@ -223,27 +488,21 @@ function radio_station_myplaylist_inner_custom_box() { 'accesskey' => 'o', ) ); - ?> - - - '50', - 'accesskey' => 'o', - ) - ); - ?> - - - '; + submit_button( + __( 'Publish' ), + 'primary', + 'publish', + false, + array( + 'tabindex' => '50', + 'accesskey' => 'o', + ) + ); + } + } else { + echo ''; submit_button( __( 'Update Playlist' ), 'primary', @@ -254,70 +513,189 @@ function radio_station_myplaylist_inner_custom_box() { 'accesskey' => 'o', ) ); - ?> - - - - '; + echo ''; } - ?> -
    + echo '
    '; +} - - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + if ( count( $shows ) > 0 ) { + $have_shows = true; + } else { + $have_shows = false; + } + + // --- maybe restrict show selection to user-assigned shows --- + // 2.2.8: remove strict argument from in_array checking + // 2.3.0: added check for new Show Editor role + // 2.3.0: added check for edit_others_shows capability + if ( !in_array( 'administrator', $user->roles ) + && !in_array( 'show-editor', $user->roles ) + && !current_user_can( 'edit_others_shows' ) ) { + + // --- get the user lists for all shows --- + $allowed_shows = array(); + $query = "SELECT pm.meta_value, pm.post_id FROM " . $wpdb->prefix . "postmeta pm + WHERE pm.meta_key = 'show_user_list'"; + $show_user_lists = $wpdb->get_results( $query ); + + // ---- check each list for the current user --- + foreach ( $show_user_lists as $user_list ) { + + $user_list->meta_value = maybe_unserialize( $user_list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( !is_array( $user_list->meta_value ) ) { + $user_list->meta_value = array(); + } + + // --- only include shows the user is assigned to --- + foreach ( $user_list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $user_list->post_id; + } + } + } + + $args = array( + 'numberposts' => - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); + + $shows = get_posts( $args ); + } + + echo '
    '; + if ( !$have_shows ) { + echo esc_html( __( 'No Shows were found.', 'radio-station' ) ); + } else { + if ( count( $shows ) < 1 ) { + echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ); + } else { + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); + + // --- select show to assign playlist to --- + $current = get_post_meta( $post->ID, 'playlist_show_id', true ); + echo ''; + } + } + echo '
    '; } // -------------------- // Update Playlist Data // -------------------- // --- When a playlist is saved, saves our custom data --- -add_action( 'save_post', 'radio_station_myplaylist_save_postdata' ); -function radio_station_myplaylist_save_postdata( $post_id ) { +add_action( 'save_post', 'radio_station_playlist_save_data' ); +function radio_station_playlist_save_data( $post_id ) { // --- verify if this is an auto save routine --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } - if ( isset( $_POST['playlist'] ) || isset( $_POST['playlist_show_id'] ) ) { - - // --- verify this came from the our screen and with proper authorization --- - if ( ! isset( $_POST['dynamicMeta_noncename'] ) ) { - return; - } - if ( ! wp_verify_nonce( $_POST['dynamicMeta_noncename'], plugin_basename( __FILE__ ) ) ) { - return; - } + // --- save playlist tracks --- + if ( isset( $_POST['playlist'] ) ) { - if ( ! isset( $_POST['dynamicMetaShow_noncename'] ) ) { - return; - } - if ( ! wp_verify_nonce( $_POST['dynamicMetaShow_noncename'], plugin_basename( __FILE__ ) ) ) { - return; - } + // --- verify playlist nonce --- + if ( isset( $_POST['playlist_tracks_nonce'] ) + || wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { - // OK, we are authenticated: we need to find and save the data - $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); + $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); - // move songs that are still queued to the end of the list so that order is maintained - foreach ( $playlist as $i => $song ) { - if ( 'queued' === $song['playlist_entry_status'] ) { - $playlist[] = $song; - unset( $playlist[ $i ] ); + // move songs that are still queued to the end of the list so that order is maintained + foreach ( $playlist as $i => $song ) { + if ( 'queued' === $song['playlist_entry_status'] ) { + $playlist[] = $song; + unset( $playlist[$i] ); + } } + update_post_meta( $post_id, 'playlist', $playlist ); } - update_post_meta( $post_id, 'playlist', $playlist ); + } - // sanitize and save show ID - $show = $_POST['playlist_show_id']; - if ( empty( $show ) ) { - delete_post_meta( $post_id, 'playlist_show_id' ); - } else { - $show = absint( $show ); - if ( $show > 0 ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); + // --- sanitize and save related show ID --- + // 2.3.0: check for changes in related show ID + if ( isset( $_POST['playlist_show_id'] ) ) { + + // --- verify playlist related to show nonce --- + if ( isset( $_POST['playlist_show_nonce'] ) + && wp_verify_nonce( $_POST['playlist_show_nonce'], 'radio-station' ) ) { + + $changed = false; + $prev_show = get_post_meta( $post_id, 'playlist_show_id', true ); + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + if ( $prev_show ) { + $show = $prev_show; + $changed = true; + } + } else { + $show = absint( $show ); + if ( ( $show > 0 ) && ( $show != $prev_show ) ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + $changed = true; + } + } + + // 2.3.0: maybe clear cached data to be safe + if ( $changed ) { + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_next_show' ); + do_action( 'radio_station_clear_data', 'show_meta', $show ); } } } @@ -328,7 +706,7 @@ function radio_station_myplaylist_save_postdata( $post_id ) { // Add Playlist List Columns // ------------------------- // 2.2.7: added data columns to playlist list display -add_filter( 'manage_edit-playlist_columns', 'radio_station_playlist_columns', 6 ); +add_filter( 'manage_edit-' . RADIO_STATION_PLAYLIST_SLUG . '_columns', 'radio_station_playlist_columns', 6 ); function radio_station_playlist_columns( $columns ) { if ( isset( $columns['thumbnail'] ) ) { unset( $columns['thumbnail'] ); @@ -340,11 +718,12 @@ function radio_station_playlist_columns( $columns ) { unset( $columns['date'] ); $comments = $columns['comments']; unset( $columns['comments'] ); - $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); - $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); - $columns['comments'] = $comments; - $columns['date'] = $date; + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; + return $columns; } @@ -352,35 +731,34 @@ function radio_station_playlist_columns( $columns ) { // Playlist List Column Data // ------------------------- // 2.2.7: added data columns for show list display -add_action( 'manage_playlist_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); +add_action( 'manage_' . RADIO_STATION_PLAYLIST_SLUG . '_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); function radio_station_playlist_column_data( $column, $post_id ) { - if ( $column == 'show' ) { + if ( 'show' == $column ) { $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); - $post = get_post( $show_id ); - echo "" . $post->post_title . ''; - } elseif ( $column == 'trackcount' ) { + $post = get_post( $show_id ); + echo "" . esc_html( $post->post_title ) . ""; + } elseif ( 'trackcount' == $column ) { $tracks = get_post_meta( $post_id, 'playlist', true ); echo count( $tracks ); - } elseif ( $column == 'tracklist' ) { - $tracks = get_post_meta( $post_id, 'playlist', true ); - $tracklist = ''; - $tracklist .= __( 'Show/Hide Tracklist', 'radio-station' ) . '
    '; - $tracklist .= ''; - echo $tracklist; + echo ''; } } @@ -390,18 +768,22 @@ function radio_station_playlist_column_data( $column, $post_id ) { add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); function radio_station_playlist_column_styles() { $currentscreen = get_current_screen(); - if ( $currentscreen->id !== 'edit-playlist' ) { + if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { return; } - echo ''; + echo ""; // --- expand/collapse tracklist data --- - echo ""; + }"; + + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echo + wp_add_inline_script( 'radio-station-admin', $js ); } @@ -412,812 +794,1369 @@ function radio_station_playlist_column_styles() { // ------------------------ // Add Related Show Metabox // ------------------------ -// --- Add custom meta box for show assignment on blog posts --- -add_action( 'add_meta_boxes', 'radio_station_add_showblog_box' ); -function radio_station_add_showblog_box() { +// (add metabox for show assignment on blog posts) +add_action( 'add_meta_boxes', 'radio_station_add_post_show_metabox' ); +function radio_station_add_post_show_metabox() { - // --- make sure a show exists before adding metabox --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => 'show', - 'post_status' => 'publish', - ); - $shows = get_posts( $args ); + // 2.3.0: moved check for shows inside metabox - if ( count( $shows ) > 0 ) { + // ---- add a filter for which post types to show metabox on --- + $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - // ---- add a filter for which post types to show metabox on --- - // TODO: add this filter to plugin documentation - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - - // --- add the metabox to post types --- - add_meta_box( - 'dynamicShowBlog_sectionid', - __( 'Related to Show', 'radio-station' ), - 'radio_station_inner_showblog_custom_box', - $post_types, - 'side' - ); - } + // --- add the metabox to post types --- + add_meta_box( + 'radio-station-post-show-metabox', + __( 'Related to Show', 'radio-station' ), + 'radio_station_post_show_metabox', + $post_types, + 'side' + ); } // -------------------- // Related Show Metabox // -------------------- -// --- Prints the box content for the Show field --- -function radio_station_inner_showblog_custom_box() { +function radio_station_post_show_metabox() { + global $post; // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShowBlog_noncename' ); + wp_nonce_field( 'radio-station', 'post_show_nonce' ); - $args = array( - 'numberposts' => -1, + $args = array( + 'numberposts' => - 1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', - 'post_type' => 'show', + 'post_type' => RADIO_STATION_SHOW_SLUG, 'post_status' => 'publish', ); - $shows = get_posts( $args ); + $shows = get_posts( $args ); $current = get_post_meta( $post->ID, 'post_showblog_id', true ); - ?> -
    + echo '
    '; - '; + echo ''; + // --- loop shows for selection options --- + foreach ( $shows as $show ) { + echo ''; + } + echo ''; + } else { + // --- no shows message --- + echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); } - ?> - -
    - '; } // ------------------- // Update Related Show // ------------------- -// --- When a post is saved, saves our custom data --- -add_action( 'save_post', 'radio_station_save_postdata' ); -function radio_station_save_postdata( $post_id ) { +add_action( 'save_post', 'radio_station_post_save_data' ); +function radio_station_post_save_data( $post_id ) { - // --- verify if this is an auto save routine --- - // If it is our form has not been submitted, so we dont want to do anything + // --- do not save when doing autosaves --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } + // --- check related show field is set --- + // 2.3.0: added check if changed if ( isset( $_POST['post_showblog_id'] ) ) { - // verify this came from the our screen and with proper authorization, - // because save_post can be triggered at other times - if ( ! isset( $_POST['dynamicMetaShowBlog_noncename'] ) ) { - return; - } - if ( ! wp_verify_nonce( $_POST['dynamicMetaShowBlog_noncename'], plugin_basename( __FILE__ ) ) ) { + // --- verify field save nonce --- + if ( !isset( $_POST['post_show_nonce'] ) + || !wp_verify_nonce( $_POST['post_show_nonce'], 'radio-station' ) ) { return; } - // OK, we are authenticated: we need to find and save the data - $show = $_POST['post_showblog_id']; + // --- get the related show ID --- + $changed = false; + $prev_show = get_post_meta( $post_id, 'post_showblog_id', true ); + $show = trim( $_POST['post_showblog_id'] ); if ( empty( $show ) ) { - // remove show from post + // --- remove show from post --- delete_post_meta( $post_id, 'post_showblog_id' ); + if ( $prev_show ) { + $changed = true; + } } else { - // sanitize to numeric before updating + // --- sanitize to numeric before updating --- $show = absint( $show ); - if ( $show > 0 ) { + if ( ( $show > 0 ) && ( $show != $prev_show ) ) { update_post_meta( $post_id, 'post_showblog_id', $show ); + $changed = true; } } + + // 2.3.0: clear cached data to be safe + if ( $changed ) { + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_next_show' ); + do_action( 'radio_station_clear_data', 'show_meta', $show ); + } } } +// --------------------- +// Add Show Info Metabox +// --------------------- +add_action( 'add_meta_boxes', 'radio_station_add_show_info_metabox' ); +function radio_station_add_show_info_metabox() { + // 2.2.2: change context to show at top of edit screen + add_meta_box( + 'radio-station-show-info-metabox', + __( 'Show Information', 'radio-station' ), + 'radio_station_show_info_metabox', + RADIO_STATION_SHOW_SLUG, + 'top', // shift to top + 'high' + ); +} -// ----------------------------------- -// Add Assign Playlist to Show Metabox -// ----------------------------------- -// --- Add custom meta box for show assigment --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_show_box' ); -function radio_station_myplaylist_add_show_box() { - // 2.2.2: add high priority to shift above publish box +// ----------------- +// Show Info Metabox +// ----------------- +function radio_station_show_info_metabox() { + + global $post; + + // 2.3.0: added missing nonce field + wp_nonce_field( 'radio-station', 'show_meta_nonce' ); + + // --- get show meta --- + $file = get_post_meta( $post->ID, 'show_file', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post->ID, 'show_link', true ); + $patreon_id = get_post_meta( $post->ID, 'show_patreon', true ); + + // added max-width to prevent metabox overflows + // 2.3.0: removed new lines between labels and fields and changed widths + echo '
    '; + echo '

    + + ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

    + +

    +

    + +

    +

    + +

    +

    '; + + // 2.3.0: added patreon page field + echo '

    + https://patreon.com/

    + +
    '; + + // --- inside show metaboxes --- + // 2.3.0: move metaboxes together inside meta + $inside_metaboxes = array( + 'hosts' => array( + 'title' => __( 'Show DJ(s) / Host(s)', 'radio-station' ), + 'callback' => 'radio_station_show_hosts_metabox', + ), + 'producers' => array( + 'title' => __( 'Show Producer(s)', 'radio-station' ), + 'callback' => 'radio_station_show_producers_metabox', + ), + 'languages' => array( + 'title' => __( 'Show Language(s)', 'radio-station' ), + 'callback' => 'radio_station_show_language_metabox', + ) + ); + + // --- display inside metaboxes --- + echo '
    '; + $i = 1; + foreach ( $inside_metaboxes as $key => $metabox ) { + + $classes = array( 'postbox' ); + if ( 1 == $i ) { + $classes[] = 'first'; + } elseif ( count( $inside_metaboxes ) == $i ) { + $classes[] = 'last'; + } + $class = implode( ' ', $classes ); + + echo '
    ' . "\n"; + $widget_title = $metabox['title']; + + // echo ''; + + echo '

    ' . esc_html( $metabox['title'] ) . '

    '; + echo '
    '; + call_user_func( $metabox['callback'] ); + echo "
    "; + echo "
    "; + + $i ++; + } + echo '
    '; + + // --- output inside metabox styles --- + echo ""; +} + +// ------------------------------ +// Add Assign DJs to Show Metabox +// ------------------------------ +// 2.3.0: move inside show meta selection metabox to reduce clutter +// add_action( 'add_meta_boxes', 'radio_station_add_show_hosts_metabox' ); +function radio_station_add_show_hosts_metabox() { + // 2.2.2: add high priority to show at top of edit sidebar + // 2.3.0: change metabox title from DJs to DJs / Hosts add_meta_box( - 'dynamicShow_sectionid', - __( 'Show', 'radio-station' ), - 'radio_station_myplaylist_inner_show_custom_box', - 'playlist', + 'radio-station-show-hosts-metabox', + __( 'DJs / Hosts', 'radio-station' ), + 'radio_station_show_hosts_metabox', + RADIO_STATION_SHOW_SLUG, 'side', 'high' ); } -// ------------------------------- -// Assign Playlist to Show Metabox -// ------------------------------- -// --- Prints the box content for the Show field --- -function radio_station_myplaylist_inner_show_custom_box() { +// ---------------------------- +// Assign Hosts to Show Metabox +// ---------------------------- +function radio_station_show_hosts_metabox() { - global $post, $wpdb; + global $post, $wp_roles, $wpdb; // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaShow_noncename' ); + wp_nonce_field( 'radio-station', 'show_hosts_nonce' ); - $user = wp_get_current_user(); + // --- check for DJ / Host roles --- + // 2.3.0: simplified by using role__in argument + $args = array( + 'role__in' => array( 'dj', 'administrator' ), + 'orderby' => 'display_name', + 'order' => 'ASC' + ); + $hosts = get_users( $args ); + + // --- get the Hosts currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( !$current ) { + $current = array(); + } - // --- allow administrators to do whatever they want --- - // 2.2.8: remove strict in_array checking - if ( ! in_array( 'administrator', $user->roles ) ) { + // --- move any selected Hosts to the top of the list --- + foreach ( $hosts as $i => $host ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $host->ID, $current ) ) { + unset( $hosts[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $hosts, $host ); // prepend the user to the beginning of the array + } + } - // --- get the user lists for all shows --- - $allowed_shows = array(); + // --- Host Selection Input --- + // 2.2.2: add fix to make DJ multi-select input full metabox width + echo '
    '; + echo ''; - $show_user_lists = $wpdb->get_results( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = 'show_user_list'" ); + // --- multiple selection helper text --- + // 2.3.0: added multiple selection helper text + echo '
    ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
    '; + echo '
    '; +} - // ---- check each list for the current user --- - foreach ( $show_user_lists as $list ) { +// ------------------------------------ +// Add Assign Producers to Show Metabox +// ------------------------------------ +// 2.3.0: move inside show meta selection metabox to reduce clutter +// add_action( 'add_meta_boxes', 'radio_station_add_show_producers_metabox' ); +function radio_station_add_show_producers_metabox() { + add_meta_box( + 'radio-station-show-producers-metabox', + __( 'Show Producer(s)', 'radio-station' ), + 'radio_station_show_producers_metabox', + RADIO_STATION_SHOW_SLUG, + 'side', + 'high' + ); +} - $list->meta_value = maybe_unserialize( $list->meta_value ); +// -------------------------------- +// Assign Producers to Show Metabox +// -------------------------------- +function radio_station_show_producers_metabox() { - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( ! is_array( $list->meta_value ) ) { - $list->meta_value = array(); - } + global $post, $wp_roles, $wpdb; - // --- only include shows the user is assigned to --- - foreach ( $list->meta_value as $user_id ) { - if ( $user->ID === $user_id ) { - $allowed_shows[] = $list->post_id; + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'show_producers_nonce' ); + + // --- check for Producer roles --- + $args = array( + 'role__in' => array( 'producer', 'administrator', 'show-editor' ), + 'orderby' => 'display_name', + 'order' => 'ASC' + ); + $producers = get_users( $args ); + + // --- get Producers currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_producer_list', true ); + if ( !$current ) { + $current = array(); + } + + // --- move any selected DJs to the top of the list --- + foreach ( $producers as $i => $producer ) { + if ( in_array( $producer->ID, $current ) ) { + unset( $producers[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $producers, $producer ); // prepend the user to the beginning of the array + } + } + + // --- Producer Selection Input --- + echo '
    '; + echo ''; + + // --- multiple selection helper text --- + echo '
    ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
    '; + echo '
    '; +} + +// ----------------------- +// Add Show Shifts Metabox +// ----------------------- +// --- Adds schedule box to show edit screens --- +add_action( 'add_meta_boxes', 'radio_station_add_show_shifts_metabox' ); +function radio_station_add_show_shifts_metabox() { + // 2.2.2: change context to show at top of edit screen + add_meta_box( + 'radio-station-show-shifts-metabox', + __( 'Show Schedule', 'radio-station' ), + 'radio_station_show_shifts_metabox', + RADIO_STATION_SHOW_SLUG, + 'top', // shift to top + 'low' + ); +} + +// ------------------- +// Show Shifts Metabox +// ------------------- +function radio_station_show_shifts_metabox() { + + global $post; + + // --- edit show link --- + $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); + + // 2.2.7: added meridiem translations + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); + + echo '
    '; + + // --- set days, hours and minutes arrays --- + $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $hours = $mins = array(); + for ( $i = 1; $i <= 12; $i ++ ) { + $hours[$i] = $i; + } + for ( $i = 0; $i < 60; $i ++ ) { + if ( $i < 10 ) { + $min = '0' . $i; + } else { + $min = $i; } + $mins[$i] = $min; + } - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ), - ); + // --- get the saved meta as an array --- + $shifts = get_post_meta( $post->ID, 'show_sched', true ); + + $c = 0; + $has_conflicts = false; + $list = ''; + if ( isset( $shifts ) && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + + // 2.2.7: soft shifts by start day and time for ordered display + foreach ( $shifts as $shift ) { + // 2.3.0: add shift index to prevent start time overwriting + $j = 1; + if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { + // --- group shifts by days of week --- + $starttime = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + // 2.3.0: simplify by getting day index + $i = array_search( $shift['day'], $days ); + $day_shifts[$i][$starttime . '.' . $j] = $shift; + } else { + // --- to still allow shift time sorting if day is not set --- + $starttime = strtotime( '1981-04-28 ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); + $day_shifts[7][$starttime . '.' . $j] = $shift; + } + $j ++; + } - $shows = get_posts( $args ); + // --- sort day shifts by day and time --- + ksort( $day_shifts ); + // 2.3.0: resort order using start of week + $sorted_shifts = array(); + $weekdays = radio_station_get_schedule_weekdays(); + foreach ( $weekdays as $i => $weekday ) { + if ( isset( $day_shifts[$i] ) ) { + $sorted_shifts[$i] = $day_shifts[$i]; + } + } + if ( isset( $day_shifts[7] ) ) { + $sorted_shifts[7] = $day_shifts[7]; + } + $show_shifts = array(); + foreach ( $sorted_shifts as $shift_day => $day_shift ) { + // --- sort shifts by (unique) start time for each day --- + ksort( $day_shift ); + foreach ( $day_shift as $shift ) { + $show_shifts[] = $shift; + } + } - } else { + // --- loop ordered show shifts --- + foreach ( $show_shifts as $i => $shift ) { - // --- for if you are an administrator --- - $args = array( - 'numberposts' => -1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => 'show', - 'post_status' => 'publish', - ); + $classes = array( 'show-shift' ); - $shows = get_posts( $args ); + // --- check conflicts with other show shifts --- + // 2.3.0: added shift conflict checking + $conflicts = radio_station_check_shift( $post->ID, $shift ); + if ( $conflicts && is_array( $conflicts ) ) { + $has_conflicts = true; + $classes[] = 'conflicts'; + } + + // --- check if shift disabled --- + // 2.3.0: added shift disabled switch + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $classes[] = 'disabled'; + } + $classlist = implode( " ", $classes ); + + $list .= '
      '; + + // --- shift day selection --- + $list .= '
    • '; + $list .= esc_html( __( 'Day', 'radio-station' ) ) . ': '; + + $class = ''; + if ( '' == $shift['day'] ) { + $class = 'incomplete'; + } + $list .= ''; + $list .= '
    • '; + + // --- shift start time --- + $list .= '
    • '; + $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ': '; + + // --- start hour selection --- + $class = ''; + if ( '' == $shift['start_hour'] ) { + $class = 'incomplete'; + } + $list .= ''; + + // --- start minute selection --- + $list .= ''; + + // --- start meridiem selection --- + $class = ''; + if ( '' == $shift['start_meridian'] ) { + $class = 'incomplete'; + } + $list .= ''; + $list .= '
    • '; + + // --- shift end time --- + $list .= '
    • '; + $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ': '; + + // --- end hour selection --- + $class = ''; + if ( '' == $shift['end_hour'] ) { + $class = 'incomplete'; + } + $list .= ''; + + // --- end minute selection --- + $list .= ''; + + // --- end meridiem selection --- + $class = ''; + if ( '' == $shift['end_meridian'] ) { + $class = 'incomplete'; + } + $list .= ''; + $list .= '
    • '; + + // --- encore presentation --- + if ( !isset( $shift['encore'] ) ) {$shift['encore'] = '';} + $list .= '
    • '; + $list .= ''; + $list .= esc_html( __( 'Encore', 'radio-station' ) ); + $list .= '
    • '; + + // --- shift disabled --- + // 2.3.0: added disabled checkbox to shift row + if ( !isset( $shift['disabled'] ) ) {$shift['disabled'] = '';} + $list .= '
    • '; + $list .= ''; + $list .= esc_html( __( 'Disabled', 'radio-station' ) ); + $list .= '
    • '; + + // --- remove shift button --- + $list .= '
    • '; + $list .= ''; + $list .= esc_html( __( 'Remove', 'radio-station' ) ); + $list .= ''; + $list .= '
    • '; + + $list .= '
    '; + + // --- output any shift conflicts found --- + if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { + $list .= '
    '; + $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': '; + foreach ( $conflicts as $j => $conflict ) { + if ( $j > 0 ) { + $list .= ', '; + } + if ( $conflict['show'] == $post->ID ) { + $list .= '' . esc_html( __('This Show', 'radio-station' ) ) . ''; + } else { + $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); + $show_title = get_the_title( $conflict['show'] ); + $list .= '' . esc_html( $show_title ) . ''; + } + $conflict_start = esc_html( $conflict['shift']['start_hour'] ) . ':' . esc_html( $conflict['shift']['start_min'] ) . ' ' . esc_html( $conflict['shift']['start_meridian'] ); + $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); + $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; + } + $list .= '

    '; + } + + // --- increment shift counter --- + $c ++; + } } - ?> -
    + // --- shift conflicts message --- + // 2.3.0: added instructions for fixing shift conflicts + if ( $has_conflicts ) { + echo '
    '; + echo '' . esc_html( __( 'Warning! Show Shift Conflicts were detected!', 'radio-station' ) ) . '
    '; + echo esc_html( __( 'Please note that Shifts with conflicts are automatically disabled upon saving.', 'radio-station' ) ) . '
    '; + echo esc_html( __( 'Fix the Shift and/or the Shift on the conflicting Show and Update them both.', 'radio-station' ) ) . '
    '; + echo esc_html( __( 'Then you can uncheck the shift Disable box and Update to re-enable the Shift.', 'radio-station' ) ) . '
    '; + // TODO: add more information blog post / documentation link ? + // echo '' . esc_html( __( 'Show Documentation', 'radio-station' ) ) . ''; + echo '

    '; + } - -
    + + + + '; + output += '
  • '; + output += '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; + output += ''; + output += '
  • '; + + output += '
  • '; + output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": '; + output += ' '; + output += ' '; + output += ' '; + output += '
  • '; + + output += '
  • '; + output += '" . esc_js( __( 'End Time', 'radio-station' ) ) . ": '; + output += ' '; + output += ' '; + output += ' '; + output += '
  • '; + + output += '
  • '; + output += ' " . esc_js( __( 'Encore', 'radio-station' ) ) . "'; + output += '
  • '; + + output += '
  • '; + output += ' " . esc_js( __( 'Disabled', 'radio-station' ) ) . "'; + output += '
  • '; + + output += '
  • '; + output += '" . esc_js( __( 'Remove', 'radio-station' ) ) . "'; + output += '
  • '; + + output += ''; + shiftaddb('#here').append( output ); + + return false; + }); + shiftaddb('.remove').live('click', function() { + /* ? maybe recheck shift count ? */ + agree = confirm('" . esc_js( $confirm_remove ) . "'); + if (!agree) {return;} + shiftaddb(this).parent().parent().remove(); + }); + });"; + + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + + echo ''; + + echo '
    '; } -// ------------------------- -// Add Playlist Info Metabox -// ------------------------- -// --- Adds a box to the side column of the show edit screens --- -add_action( 'add_meta_boxes', 'radio_station_myplaylist_add_metainfo_box' ); -function radio_station_myplaylist_add_metainfo_box() { - // 2.2.2: change context to show at top of edit screen +// ----------------------------------- +// Add Show Description Helper Metabox +// ----------------------------------- +// 2.3.0: added metabox for show description helper text +add_action( 'add_meta_boxes', 'radio_station_add_show_helper_box' ); +function radio_station_add_show_helper_box() { add_meta_box( - 'dynamicShowMeta_sectionid', - __( 'Information', 'radio-station' ), - 'radio_station_myplaylist_inner_metainfo_custom_box', - 'show', - 'top', // shift to top - 'high' + 'radio-station-show-helper-box', + __( 'Show Description', 'radio-station' ), + 'radio_station_show_helper_box', + RADIO_STATION_SHOW_SLUG, + 'top', + 'low' ); } -// --------------------- -// Playlist Info Metabox -// --------------------- -// --- Prints the box for additional meta data for the Show post type --- -function radio_station_myplaylist_inner_metainfo_custom_box() { +// ------------------------------- +// Show Description Helper Metabox +// ------------------------------- +// 2.3.0: added metabox for show description helper text +function radio_station_show_helper_box() { + + echo "

    "; + + // --- show description helper text --- + echo esc_html( __( "The text field below is for your Show Description. It will display in the About section of your Show page.", 'radio-station' ) ); + echo ' ' . esc_html( __( "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.", 'radio-station' ) ); + echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . "
    "; + echo esc_html( __( "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.", 'radio-station' ) ); + echo ' ' . esc_html( __( "You can then assign them to a relevent Post Category for display on your site also.", 'radio-station' ) ); + + // TODO: upgrade to Pro for upcoming Show Episodes blurb + // $upgrade_url = radio_station_get_upgrade_url(); + // echo ''; + // echo esc_html( __( "Upgrade to Radio Station Pro', 'radio-station' ) ); + // echo ''; + + echo "

    "; + +} + +// ---------------------------------- +// Rename Show Featured Image Metabox +// ---------------------------------- +// 2.3.0: renamed from "Feature Image" to be clearer +// 2.3.0: removed this as now implementing show images separately +// (note this is the Show Logo for backwards compatibility reasons) +// add_action( 'do_meta_boxes', 'radio_station_rename_featured_image_metabox' ); +function radio_station_rename_featured_image_metabox() { + remove_meta_box( 'postimagediv', RADIO_STATION_SHOW_SLUG, 'side' ); + add_meta_box( + 'postimagediv', + __( 'Show Logo' ), + 'post_thumbnail_meta_box', + RADIO_STATION_SHOW_SLUG, + 'side', + 'low' + ); +} + +// ----------------------- +// Add Show Images Metabox +// ----------------------- +// 2.3.0: added show images metabox +add_action( 'add_meta_boxes', 'radio_station_add_show_images_metabox' ); +function radio_station_add_show_images_metabox() { + add_meta_box( + 'radio-station-show-images-metabox', + __( 'Show Images', 'radio-station' ), + 'radio_station_show_images_metabox', + array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ), + 'side', + 'low' + ); +} + +// ------------------- +// Show Images Metabox +// ------------------- +// 2.3.0: added show header and avatar image metabox +// ref: https://codex.wordpress.org/Javascript_Reference/wp.media +function radio_station_show_images_metabox() { global $post; - $file = get_post_meta( $post->ID, 'show_file', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); + if ( isset( $_GET['avatar_refix'] ) && ( 'yes' == $_GET['avatar_refix'] ) ) { + delete_post_meta( $post->ID, '_rs_image_updated', true ); + $show_avatar = radio_station_get_show_avatar_id( $post->ID ); + echo "Transferred ID: " . $show_avatar; + } - // added max-width to prevent metabox overflows - ?> -
    + wp_nonce_field( 'radio-station', 'show_images_nonce' ); + $upload_link = get_upload_iframe_src( 'image', $post->ID ); + + // --- get show avatar image info --- + $show_avatar = get_post_meta( $post->ID, 'show_avatar', true ); + $show_avatar_src = wp_get_attachment_image_src( $show_avatar, 'full' ); + $has_show_avatar = is_array( $show_avatar_src ); + + // --- show avatar image --- + echo '
    '; + + // --- image container --- + echo '
    '; + if ( $has_show_avatar ) { + echo ''; + } + echo '
    '; + + // --- add and remove links --- + echo '

    '; + $hidden = ''; + if ( $has_show_avatar ) { + $hidden = ' hidden'; + } + echo ''; + echo esc_html( __( 'Set Show Avatar Image' ) ); + echo ''; + $hidden = ''; + if ( !$has_show_avatar ) { + $hidden = ' hidden'; + } + echo ''; + echo esc_html( __( 'Remove Show Avatar Image' ) ); + echo ''; + echo '

    '; + + // --- hidden input for image ID --- + echo ''; + + echo '
    '; + + // --- check if show content header image is enabled --- + $header_image = radio_station_get_setting( 'show_header_image' ); + if ( $header_image ) { + + // --- get show header image info + $show_header = get_post_meta( $post->ID, 'show_header', true ); + $show_header_src = wp_get_attachment_image_src( $show_header, 'full' ); + $has_show_header = is_array( $show_header_src ); + + // --- show header image --- + echo '
    '; + + // --- image container --- + echo '
    '; + if ( $has_show_header ) { + echo ''; + } + echo '
    '; + + // --- add and remove links --- + echo '

    '; + $hidden = ''; + if ( $has_show_header ) { + $hidden = ' hidden'; + } + echo ''; + echo esc_html( __( 'Set Show Header Image' ) ); + echo ''; + $hidden = ''; + if ( !$has_show_header ) { + $hidden = ' hidden'; + } + echo ''; + echo esc_html( __( 'Remove Show Header Image' ) ); + echo ''; + echo '

    '; + + // --- hidden input for image ID --- + echo ''; -

    - /> -

    + echo '
    '; + + } + + // --- set images autosave nonce and iframe --- + $images_autosave_nonce = wp_create_nonce( 'show-images-autosave' ); + echo ''; + echo ''; + + // --- image selection script --- + $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); + $js = "jQuery(function(){ + + var mediaframe, parentdiv, + imagesmetabox = jQuery('#radio-station-show-images-metabox'), + addimagelink = imagesmetabox.find('.upload-custom-image'), + deleteimagelink = imagesmetabox.find('.delete-custom-image'); + + /* Add Image on Click */ + addimagelink.on( 'click', function( event ) { + + event.preventDefault(); + parentdiv = jQuery(this).parent().parent(); + + if (mediaframe) {mediaframe.open(); return;} + mediaframe = wp.media({ + title: 'Select or Upload Image', + button: {text: 'Use this Image'}, + multiple: false + }); + + mediaframe.on( 'select', function() { + var attachment = mediaframe.state().get('selection').first().toJSON(); + image = '\"\"'; + parentdiv.find('.custom-image-container').append(image); + parentdiv.find('.custom-image-id').val(attachment.id); + parentdiv.find('.upload-custom-image').addClass('hidden'); + parentdiv.find('.delete-custom-image').removeClass('hidden'); + + /* auto-save image via AJAX */ + postid = '" . $post->ID . "'; imgid = attachment.id; + if (parentdiv.attr('id') == 'show-avatar-image') {imagetype = 'avatar';} + if (parentdiv.attr('id') == 'show-header-image') {imagetype = 'header';} + imagessavenonce = jQuery('#show-images-save-nonce').val(); + framesrc = ajaxurl+'?action=radio_station_show_images_save'; + framesrc += '&post_id='+postid+'&image_type='+imagetype; + framesrc += '&image_id='+imgid+'&_wpnonce='+imagessavenonce; + jQuery('#show-images-save-frame').attr('src', framesrc); + }); + + mediaframe.open(); + }); + + /* Delete Image on Click */ + deleteimagelink.on( 'click', function( event ) { + event.preventDefault(); + agree = confirm('Are you sure?'); + if (!agree) {return;} + parentdiv = jQuery(this).parent().parent(); + parentdiv.find('.custom-image-container').html(''); + parentdiv.find('.custom-image-id').val(''); + parentdiv.find('.upload-custom-image').removeClass('hidden'); + parentdiv.find('.delete-custom-image').addClass('hidden'); + }); + + });"; + + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + +} + +// --------------------------------- +// AJAX to AutoSave Images on Change +// --------------------------------- +add_action( 'wp_ajax_radio_station_show_images_save', 'radio_station_show_images_save' ); +function radio_station_show_images_save() { + + if ( !current_user_can( 'edit_shows' ) ) { + exit; + } + + // --- verify nonce value --- + if ( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( $_GET['_wpnonce'], 'show-images-autosave' ) ) { + exit; + } + + // --- sanitize posted values --- + if ( isset( $_GET['post_id'] ) ) { + $post_id = absint( $_GET['post_id'] ); + if ( $post_id < 1 ) { + unset( $post_id ); + } + } + // if ( !current_user_can( 'edit_show', $post_id ) ) {return;} + + if ( isset( $_GET['image_id'] ) ) { + $image_id = absint( $_GET['image_id'] ); + if ( $image_id < 1 ) { + unset( $image_id ); + } + } + if ( isset( $_GET['image_type'] ) ) { + if ( in_array( $_GET['image_type'], array( 'header', 'avatar' ) ) ) { + $image_type = $_GET['image_type']; + } + } -


    -

    + if ( isset( $post_id ) && isset( $image_id ) && isset( $image_type ) ) { + update_post_meta( $post_id, 'show_' . $image_type, $image_id ); + } else { + exit; + } -


    -

    + // --- add image updated flag --- + // (help prevent duplication on new posts) + $updated = get_post_meta( $post_id, '_rs_image_updated', true ); + if ( !$updated ) { + add_post_meta( $post_id, '_rs_image_updated', true ); + } -


    -

    + // --- refresh parent frame nonce --- + $images_save_nonce = wp_create_nonce( 'show-images-autosave' ); + echo ""; -
    - roles as $name => $role ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - if ( 'edit_shows' === $capname && $capstatus ) { - $add_roles[] = $name; + // --- get posted DJ / host list --- + // 2.2.7: check DJ post value is set + if ( isset( $_POST['show_hosts_nonce'] ) && wp_verify_nonce( $_POST['show_hosts_nonce'], 'radio-station' ) ) { + + if ( isset( $_POST['show_user_list'] ) ) { + $hosts = $_POST['show_user_list']; + } + if ( !isset( $hosts ) || !is_array( $hosts ) ) { + $hosts = array(); + } else { + foreach ( $hosts as $i => $host ) { + if ( !empty( $host ) ) { + $userid = get_user_by( 'ID', $host ); + if ( !$userid ) { + unset( $hosts[$i] ); + } + } } } + update_post_meta( $post_id, 'show_user_list', $hosts ); + $prev_hosts = get_post_meta( $post_id, 'show_user_list', true ); + if ( $prev_hosts != $hosts ) { + $show_meta_changed = true; + } } - $add_roles = array_unique( $add_roles ); - // ---- create the meta query for get_users() --- - $meta_query = array( 'relation' => 'OR' ); - foreach ( $add_roles as $role ) { - $meta_query[] = array( - 'key' => $wpdb->prefix . 'capabilities', - 'value' => $role, - 'compare' => 'like', - ); + // --- get posted show producers --- + // 2.3.0: added show producer sanitization + if ( isset( $_POST['show_producers_nonce'] ) && wp_verify_nonce( $_POST['show_producers_nonce'], 'radio-station' ) ) { + + if ( isset( $_POST['show_producer_list'] ) ) { + $producers = $_POST['show_producer_list']; + } + if ( !isset( $producers ) || !is_array( $producers ) ) { + $producers = array(); + } else { + foreach ( $producers as $i => $producer ) { + if ( !empty( $producer ) ) { + $userid = get_user_by( 'ID', $producer ); + if ( !$userid ) { + unset( $producers[$i] ); + } + } + } + } + // 2.3.0: added save of show producers + update_post_meta( $post_id, 'show_producer_list', $producers ); + $prev_producers = get_post_meta( $post_id, 'show_producer_list', true ); + if ( $prev_producers != $producers ) { + $show_meta_changed = true; + } } - // --- get all eligible users --- - $args = array( - 'meta_query' => $meta_query, - 'orderby' => 'display_name', - 'order' => 'ASC', - //' fields' => array( 'ID, display_name' ), - ); - $users = get_users( $args ); + // --- save show meta data --- + // 2.3.0: added separate nonce check for show meta + if ( isset( $_POST['show_meta_nonce'] ) && wp_verify_nonce( $_POST['show_meta_nonce'], 'radio-station' ) ) { + + // --- get the meta data to be saved --- + // 2.2.3: added show metadata value sanitization + $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); + $email = sanitize_email( trim( $_POST['show_email'] ) ); + $active = $_POST['show_active']; + // 2.2.8: removed strict in_array checking + if ( !in_array( $active, array( '', 'on' ) ) ) { + $active = ''; + } + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + $patreon_id = sanitize_title( $_POST['show_patreon'] ); + + // --- get existing values and check if changed --- + // 2.3.0: added check against previous values + $prev_file = get_post_meta( $post_id, 'show_file', true ); + $prev_email = get_post_meta( $post_id, 'show_email', true ); + $prev_active = get_post_meta( $post_id, 'show_active', true ); + $prev_link = get_post_meta( $post_id, 'show_link', true ); + $prev_patreon_id = get_post_meta( $post_id, 'show_patreont', true ); + if ( ( $prev_file != $file ) || ( $prev_email != $email ) + || ( $prev_active != $active ) || ( $prev_link != $link ) + || ( $prev_patreon_id != $patreon_id ) ) { + $show_meta_changed = true; + } - // --- get the DJs currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( ! $current ) { - $current = array(); + // --- update the show metadata --- + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + update_post_meta( $post_id, 'show_patreon', $patreon_id ); } - // --- move any selected DJs to the top of the list --- - foreach ( $users as $i => $dj ) { - // 2.2.8: remove strict in_array checking - if ( in_array( $dj->ID, $current ) ) { - unset( $users[ $i ] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $users, $dj ); // prepend the user to the beginning of the array + + // --- update the show images --- + if ( isset( $_POST['show_images_nonce'] ) && wp_verify_nonce( $_POST['show_images_nonce'], 'radio-station' ) ) { + + // --- show header image --- + if ( isset( $_POST['show_header'] ) ) { + $header = absint( $_POST['show_header'] ); + if ( $header > 0 ) { + // $prev_header = get_post_meta( $post_id, 'show_header', true ); + // if ( $header != $prev_header ) {$show_meta_changed = true;} + update_post_meta( $post_id, 'show_header', $header ); + } } - } - // 2.2.2: add fix to make DJ multi-select input full metabox width - ?> -
    + // --- show avatar image --- + $avatar = absint( $_POST['show_avatar'] ); + if ( $avatar > 0 ) { + // $prev_avatar = get_post_meta( $post_id, 'show_avatar', true ); + // if ( $avatar != $prev_avatar ) {$show_meta_changed = true;} + update_post_meta( $post_id, 'show_avatar', $avatar ); + } - -
    - 0 ) ) { + foreach ( $shifts as $i => $shift ) { - // 2.2.7: added meridiem translations - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); + // --- reset shift disabled flag --- + // 2.3.0: added shift disabling logic + $disabled = false; - // --- add nonce field for verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSched_noncename' ); - ?> -
    - ID, 'show_sched', false ); - - $c = 0; - if ( isset( $shifts[0] ) && is_array( $shifts[0] ) ) { - - // 2.2.7: soft shifts by start day and time for ordered display - foreach ( $shifts[0] as $shift ) { - if ( isset( $shift['day'] ) ) { - // --- group shifts by days of week --- - $starttime = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); - if ( $shift['day'] == 'Sunday' ) { - $i = 0; - } elseif ( $shift['day'] == 'Monday' ) { - $i = 1; - } elseif ( $shift['day'] == 'Tuesday' ) { - $i = 2; - } elseif ( $shift['day'] == 'Wednesday' ) { - $i = 3; - } elseif ( $shift['day'] == 'Thursday' ) { - $i = 4; - } elseif ( $shift['day'] == 'Friday' ) { - $i = 5; - } elseif ( $shift['day'] == 'Saturdday' ) { - $i = 6; - } - $day_shifts[ $i ][ $starttime ] = $shift; - } else { - // --- preserve shift times even if day is not set --- - $starttime = strtotime( '1981-04-28 ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); - $day_shifts[7][ $starttime ] = $shift; - } - } + // --- loop shift keys --- + foreach ( $shift as $key => $value ) { - // --- sort day shifts and loop --- - ksort( $day_shifts ); - $show_shifts = array(); - foreach ( $day_shifts as $i => $day_shift ) { - // --- sort shifts by start time for each day --- - ksort( $day_shift ); - foreach ( $day_shift as $shift ) { - $show_shifts[] = $shift; - } - } + // --- validate according to key --- + $isvalid = false; + if ( 'day' === $key ) { - // --- loop ordered show shifts --- - foreach ( $show_shifts as $shift ) { - ?> -
      - -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • - -
    • />
    • - -
    • - -
    - - - - -
    - $dj ) { - if ( ! empty( $dj ) ) { - $userid = get_user_by( 'id', $dj ); - if ( ! $userid ) { - unset( $djs[ $i ] ); + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_shifts[$i][$key] = $value; + } else { + $new_shifts[$i][$key] = ''; + } } - } - } - } - $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; - // 2.2.8: remove strict in_array checking - if ( ! in_array( $active, array( '', 'on' ) ) ) { - $active = ''; - } - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - - // --- update the show metadata --- - update_post_meta( $post_id, 'show_user_list', $djs ); - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - - // --- update the show shift metadata - $scheds = $_POST['show_sched']; - - // --- sanitize the show shift times --- - $new_scheds = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); - foreach ( $scheds as $i => $sched ) { - foreach ( $sched as $key => $value ) { - // --- validate according to key --- - $isvalid = false; - if ( 'day' === $key ) { - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $days ) ) { - $isvalid = true; - } - } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > 0 && absint( $value ) < 13 ) { - $isvalid = true; - } - } elseif ( 'start_min' === $key || 'end_min' === $key ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { - $isvalid = true; - } - } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { - $valid = array( '', 'am', 'pm' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; + // --- check for shift conflicts with other shows --- + // 2.3.0: added show shift conflict checking + if ( !$disabled ) { + $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'shows' ); + if ( $conflicts ) { + $disabled = true; + } } - } elseif ( 'encore' === $key ) { - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; + + // --- disable if incomplete data or shift conflicts --- + if ( $disabled ) { + $new_shifts[$i]['disabled'] = 'yes'; } } - // --- if valid add to new schedule --- - if ( $isvalid ) { - $new_scheds[$i][$key] = $value; - } else { - $new_scheds[$i][$key] = ''; + // --- recheck for conflicts with other shifts for this show --- + // 2.3.0: added new shift conflict checking + $new_shifts = radio_station_check_new_shifts( $new_shifts ); + + // --- update the schedule meta entry --- + // 2.3.0: check if shift times have changed before saving + if ( $new_shifts != $prev_shifts ) { + $show_shifts_changed = true; + update_post_meta( $post_id, 'show_sched', $new_shifts ); } + } else { + // 2.3.0: fix to clear data if all shifts removed + delete_post_meta( $post_id, 'show_sched' ); + $show_shifts_changed = true; } } - update_post_meta( $post_id, 'show_sched', $new_scheds ); + // --- maybe clear transient data --- + // 2.3.0: added to clear transients if any meta has changed + if ( $show_meta_changed || $show_shifts_changed ) { + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_next_show' ); + do_action( 'radio_station_clear_data', 'show', $post_id ); + do_action( 'radio_station_clear_data', 'show_meta', $post_id ); + } } @@ -1225,27 +2164,37 @@ function radio_station_save_show_data( $post_id ) { // Add Show List Columns // --------------------- // 2.2.7: added data columns to show list display -add_filter( 'manage_edit-show_columns', 'radio_station_show_columns', 6 ); +add_filter( 'manage_edit-' . RADIO_STATION_SHOW_SLUG . '_columns', 'radio_station_show_columns', 6 ); function radio_station_show_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { unset( $columns['thumbnail'] ); } if ( isset( $columns['post_thumb'] ) ) { unset( $columns['post_thumb'] ); } + $date = $columns['date']; unset( $columns['date'] ); $comments = $columns['comments']; unset( $columns['comments'] ); - $genres = $columns['taxonomy-genres']; - unset( $columns['taxonomy-genres'] ); - $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); - $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); - $columns['djs'] = esc_attr( __( 'DJs', 'radio-station' ) ); - $columns['show_image'] = esc_attr( __( 'Show Image', 'radio-station' ) ); - $columns['taxonomy-genres'] = $genres; - $columns['comments'] = $comments; - $columns['date'] = $date; + $genres = $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] ); + $languages = $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] ); + + $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); + // 2.3.0: added show description indicator column + $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); + $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); + // 2.3.0: change DJs column label to Hosts + $columns['hosts'] = esc_attr( __( 'Hosts', 'radio-station' ) ); + $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] = $genres; + $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] = $languages; + $columns['comments'] = $comments; + $columns['date'] = $date; + $columns['show_image'] = esc_attr( __( 'Show Avatar', 'radio-station' ) ); + return $columns; } @@ -1253,47 +2202,123 @@ function radio_station_show_columns( $columns ) { // Show List Column Data // --------------------- // 2.2.7: added data columns for show list display -add_action( 'manage_show_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); +add_action( 'manage_' . RADIO_STATION_SHOW_SLUG . '_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); function radio_station_show_column_data( $column, $post_id ) { - if ( $column == 'active' ) { + + if ( 'active' == $column ) { $active = get_post_meta( $post_id, 'show_active', true ); - if ( $active == 'on' ) { - echo __( 'Yes', 'radio-station' ); + if ( 'on' == $active ) { + echo esc_html( __( 'Yes', 'radio-station' ) ); + } else { + echo esc_html( __( 'No', 'radio-station' ) ); + } + } elseif ( 'description' == $column ) { + // 2.3.0: added show description indicator + global $wpdb; + $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $post_id ); + $content = $wpdb->get_var( $query ); + if ( !$content || ( trim( $content ) == '' ) ) { + echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; } else { - echo __( 'No', 'radio-station' ); + echo esc_html( __( 'Yes', 'radio-station' ) ); + } + } elseif ( 'shifts' == $column ) { + $active = get_post_meta( $post_id, 'show_active', true ); + if ( 'on' == $active ) { + $active = true; } - } elseif ( $column == 'shifts' ) { $shifts = get_post_meta( $post_id, 'show_sched', true ); if ( $shifts && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $shift ) { - $timestamp = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian'] ); - $sortedshifts[ $timestamp ] = $shift; + $timestamp = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ":" . $shift['start_min'] . " " . $shift['start_meridian'] ); + $sortedshifts[$timestamp] = $shift; } ksort( $sortedshifts ); + foreach ( $sortedshifts as $shift ) { - if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { - echo ''; + + // 2.3.0: highlight disabled shifts + $classes = array( 'show-shift' ); + $disabled = false; + $title = ''; + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $disabled = true; + $classes[] = 'disabled'; + $title = __( 'This Shift is Disabled.', 'radio-station' ); + } + + // --- check and highlight conflicts --- + // 2.3.0: added shift conflict checking + $conflicts = radio_station_check_shift( $post_id, $shift ); + if ( $conflicts ) { + $classes[] = 'conflict'; + if ( $disabled ) { + $title = __( 'This Shift has Schedule Conflicts and is Disabled.', 'radio-station' ); + } else { + $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); + } + } + // 2.3.0: also highlight if the show is not active + if ( !$active ) { + if ( !in_array( 'disabled', $classes ) ) { + $classes[] = 'disabled'; + } + $title = __( 'This Show is not currently active.', 'radio-station' ); + } + $classlist = implode( ' ', $classes ); + + echo "
    "; + + // --- get shift start and end times --- + $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + $start_time = strtotime( 'next ' . $shift['day'] . ' ' . $start ); + $end_time = strtotime( 'next' . $shift['day'] . ' ' . $end ); + + // --- make weekday filter selections bold --- + if ( isset( $_GET['weekday'] ) ) { + $weekday = trim( $_GET['weekday'] ); + } + $nextday = radio_station_get_next_day( $weekday ); + // 2.3.0: handle shifts that go overnight for weekday filter + if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { + echo ""; + $bold = true; + } else { + $bold = false; } - echo radio_station_translate_weekday( $shift['day'] ); - echo ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; - echo ' - ' . $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian'] . '
    '; - if ( isset( $_GET['weekday'] ) && ( $_GET['weekday'] == $shift['day'] ) ) { - echo '
    '; + + echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); + echo " " . esc_html( $start ) . " - " . esc_html( $end ); + if ( $bold ) { + echo "
    "; } + echo "
    "; } } - } elseif ( $column == 'djs' ) { - $djs = get_post_meta( $post_id, 'show_user_list', true ); - if ( $djs && ( count( $djs ) > 0 ) ) { - foreach ( $djs as $dj ) { - $user_info = get_userdata( $dj ); - echo esc_html( $user_info->display_name ) . '
    '; + } elseif ( 'hosts' == $column ) { + $hosts = get_post_meta( $post_id, 'show_user_list', true ); + if ( $hosts && ( count( $hosts ) > 0 ) ) { + foreach ( $hosts as $host ) { + $user_info = get_userdata( $host ); + echo esc_html( $user_info->display_name ) . "
    "; } } - } elseif ( $column == 'show_image' ) { - $thumbnail_url = get_the_post_thumbnail_url( $post_id ); - if ( $thumbnail_url ) { - echo "
    "; + } elseif ( 'producers' == $column ) { + // 2.3.0: added column for Producers + $producers = get_post_meta( $post_id, 'show_producer_list', true ); + if ( $producers && ( count( $producers ) > 0 ) ) { + foreach ( $producers as $producer ) { + $user_info = get_userdata( $producer ); + echo esc_html( $user_info->display_name ) . "
    "; + } + } + } elseif ( 'show_image' == $column ) { + // 2.3.0: get show avatar (with fallback to thumbnail) + $image_url = radio_station_get_show_avatar_url( $post_id ); + if ( $image_url ) { + echo "
    " . esc_html( __(
    "; } } } @@ -1305,11 +2330,15 @@ function radio_station_show_column_data( $column, $post_id ) { add_action( 'admin_footer', 'radio_station_show_column_styles' ); function radio_station_show_column_styles() { $currentscreen = get_current_screen(); - if ( 'edit-show' !== $currentscreen->id ) { + if ( 'edit-' . RADIO_STATION_SHOW_SLUG !== $currentscreen->id ) { return; } - echo ''; + + echo ""; } // ------------------------- @@ -1318,7 +2347,8 @@ function radio_station_show_column_styles() { // 2.2.7: added show day selection filtering add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); function radio_station_show_day_filter( $post_type, $which ) { - if ( 'show' !== $post_type ) { + + if ( RADIO_STATION_SHOW_SLUG !== $post_type ) { return; } @@ -1327,23 +2357,16 @@ function radio_station_show_day_filter( $post_type, $which ) { // --- show day selector --- $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); - ?> - - - ' . esc_html( __( 'Filter by show day', 'radio-station' ) ) . ''; + echo ''; } @@ -1355,15 +2378,16 @@ function radio_station_show_day_filter( $post_type, $which ) { // Add Schedule Override Metabox // ----------------------------- // --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_master_override_add_sched_box' ); -function radio_station_master_override_add_sched_box() { +add_action( 'add_meta_boxes', 'radio_station_add_override_schedule_box' ); +function radio_station_add_override_schedule_box() { // 2.2.2: add high priority to show at top of edit screen + // 2.3.0: set position to top to be above editor box add_meta_box( 'dynamicSchedOver_sectionid', __( 'Override Schedule', 'radio-station' ), - 'radio_station_master_override_inner_sched_custom_box', - 'override', - 'normal', + 'radio_station_master_override_schedule_metabox', + RADIO_STATION_OVERRIDE_SLUG, + 'top', // shift to top 'high' ); } @@ -1371,7 +2395,7 @@ function radio_station_master_override_add_sched_box() { // ------------------------- // Schedule Override Metabox // ------------------------- -function radio_station_master_override_inner_sched_custom_box() { +function radio_station_master_override_schedule_metabox() { global $post; @@ -1380,127 +2404,110 @@ function radio_station_master_override_inner_sched_custom_box() { $pm = radio_station_translate_meridiem( 'pm' ); // --- add nonce field for update verification --- - wp_nonce_field( plugin_basename( __FILE__ ), 'dynamicMetaSchedOver_noncename' ); + wp_nonce_field( 'radio-station', 'show_override_nonce' ); // 2.2.7: add explicit width to date picker field to ensure date is visible - ?> -
    - - ID, 'show_override_sched', false ); - if ( $override ) { - $override = $override[0]; - } - ?> - - -
      -
    • - : - -
    • - -
    • - : - - - -
    • - -
    • - : - - - -
    • -
    -
    - '; + + // --- get the saved meta as an array --- + $override = get_post_meta( $post->ID, 'show_override_sched', false ); + if ( $override ) { + $override = $override[0]; + } else { + // 2.2.8: fix undefined index warnings for new schedule overrides + $override = array( + 'date' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '' + ); + } + + echo '
      '; + + echo '
    • '; + echo esc_html( __( 'Date', 'radio-station' ) ) . ':'; + if ( !empty( $override['date'] ) ) { + $date = trim( $override['date'] ); + } else { + $date = ''; + } + echo ''; + echo '
    • '; + + echo '
    • '; + echo esc_html( __( 'Start Time', 'radio-station' ) ) . ':'; + echo ''; + echo ''; + echo ''; + echo '
    • '; + + echo '
    • '; + echo esc_html( __( 'End Time', 'radio-station' ) ) . ':'; + echo ''; + echo ''; + echo ''; + echo '
    • '; + echo '
    '; + echo '
    '; + + // --- datepicker z-index style fix --- + // 2.3.0: added for display conflict with editor buttons + echo ""; + + // --- enqueue inline script --- + // 2.3.0: enqeue instead of echoing + $js = "jQuery(document).ready(function() { + jQuery('#OverrideDate').datepicker({dateFormat : 'yy-mm-dd'}); + });"; + wp_add_inline_script( 'radio-station-admin', $js ); + } // ------------------------ // Update Schedule Override // ------------------------ -// --- save the custom fields when a show override is saved --- add_action( 'save_post', 'radio_station_master_override_save_showpostdata' ); function radio_station_master_override_save_showpostdata( $post_id ) { @@ -1510,23 +2517,20 @@ function radio_station_master_override_save_showpostdata( $post_id ) { } // --- verify this came from the our screen and with proper authorization --- - if ( ! isset( $_POST['dynamicMetaSchedOver_noncename'] ) ) { - return; - } - if ( ! wp_verify_nonce( $_POST['dynamicMetaSchedOver_noncename'], plugin_basename( __FILE__ ) ) ) { + if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { return; } // --- get the show override data --- $sched = $_POST['show_sched']; - if ( ! is_array( $sched ) ) { + if ( !is_array( $sched ) ) { return; } // --- get/set current schedule for merging --- // 2.2.2: added to set default keys $current_sched = get_post_meta( $post_id, 'show_override_sched', true ); - if ( ! $current_sched || ! is_array( $current_sched ) ) { + if ( !$current_sched || !is_array( $current_sched ) ) { $current_sched = array( 'date' => '', 'start_hour' => '', @@ -1551,20 +2555,20 @@ function radio_station_master_override_save_showpostdata( $post_id ) { if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { $isvalid = true; } - } elseif ( 'start_hour' === $key || 'end_hour' === $key ) { + } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { if ( empty( $value ) ) { $isvalid = true; } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { $isvalid = true; } - } elseif ( 'start_min' === $key || 'end_min' === $key ) { + } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { // 2.2.3: fix to validate 00 minute value if ( empty( $value ) ) { $isvalid = true; - } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + } elseif ( absint( $value ) > - 1 && absint( $value ) < 61 ) { $isvalid = true; } - } elseif ( 'start_meridian' === $key || 'end_meridian' === $key ) { + } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { $valid = array( '', 'am', 'pm' ); // 2.2.8: remove strict in_array checking if ( in_array( $value, $valid ) ) { @@ -1573,21 +2577,27 @@ function radio_station_master_override_save_showpostdata( $post_id ) { } // --- if valid add to current schedule setting --- - if ( $isvalid && ( $value !== $current_sched[ $key ] ) ) { - $current_sched[ $key ] = $value; - $changed = true; + if ( $isvalid && ( $value !== $current_sched[$key] ) ) { + $current_sched[$key] = $value; + $changed = true; // 2.2.7: sync separate meta key for override date // (could be used to improve column sorting efficiency) - if ( $key == 'date' ) { + if ( 'date' == $key ) { update_post_meta( $post_id, 'show_override_date', $value ); } } } // --- save schedule setting if changed --- + // 2.3.0: check if changed before saving if ( $changed ) { update_post_meta( $post_id, 'show_override_sched', $current_sched ); + + // --- clear cached schedule data if changed --- + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_next_show' ); } } @@ -1595,8 +2605,9 @@ function radio_station_master_override_save_showpostdata( $post_id ) { // Add Schedule Override List Columns // ---------------------------------- // 2.2.7: added data columns to override list display -add_filter( 'manage_edit-override_columns', 'radio_station_override_columns', 6 ); +add_filter( 'manage_edit-' . RADIO_STATION_OVERRIDE_SLUG . '_columns', 'radio_station_override_columns', 6 ); function radio_station_override_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { unset( $columns['thumbnail'] ); } @@ -1605,12 +2616,16 @@ function radio_station_override_columns( $columns ) { } $date = $columns['date']; unset( $columns['date'] ); - $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); - $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); - $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); + + $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); + $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); + $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station' ) ); + // 2.3.0: added description indicator column + $columns['description'] = esc_attr( __( 'Description', 'radio-station' ) ); $columns['override_image'] = esc_attr( __( 'Override Image' ) ); - $columns['date'] = $date; + $columns['date'] = $date; + return $columns; } @@ -1618,55 +2633,57 @@ function radio_station_override_columns( $columns ) { // Schedule Override Column Data // ----------------------------- // 2.2.7: added data columns for override list display -add_action( 'manage_override_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); +add_action( 'manage_' . RADIO_STATION_OVERRIDE_SLUG . '_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); function radio_station_override_column_data( $column, $post_id ) { - global $show_shifts; + global $radio_station_show_shifts; $override = get_post_meta( $post_id, 'show_override_sched', true ); - if ( $column == 'override_date' ) { + if ( 'override_date' == $column ) { $datetime = strtotime( $override['date'] ); - $month = date( 'F', $datetime ); - $month = radio_station_translate_month( $month ); - $weekday = date( 'l', $datetime ); - $weekday = radio_station_translate_weekday( $weekday ); - echo $weekday . ' ' . date( 'j', $datetime ) . ' ' . $month . ' ' . date( 'Y', $datetime ); - } elseif ( $column == 'start_time' ) { - echo $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; - } elseif ( $column == 'end_time' ) { - echo $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; - } elseif ( $column == 'shows_affected' ) { - - // --- maybe get all show shifts --- - if ( ! isset( $show_shifts ) ) { + $month = date( 'F', $datetime ); + $month = radio_station_translate_month( $month ); + $weekday = date( 'l', $datetime ); + $weekday = radio_station_translate_weekday( $weekday ); + echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); + } elseif ( 'start_time' == $column ) { + echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . ' ' . esc_html( $override['start_meridian'] ); + } elseif ( 'end_time' == $column ) { + echo esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . ' ' . esc_html( $override['end_meridian'] ); + } elseif ( 'shows_affected' == $column ) { + + // --- maybe get all show shifts --- + if ( isset( $radio_station_show_shifts ) ) { + $show_shifts = $radio_station_show_shifts; + } else { global $wpdb; - $show_shifts = $wpdb->get_results( - "SELECT posts.post_title, meta.post_id, meta.meta_value FROM {$wpdb->postmeta} AS meta - JOIN {$wpdb->posts} as posts - ON posts.ID = meta.post_id - WHERE meta.meta_key = 'show_sched' AND - posts.post_status = 'publish'" - ); - } - if ( ! $show_shifts || ( count( $show_shifts ) == 0 ) ) { + $query = "SELECT posts.post_title, meta.post_id, meta.meta_value FROM " . $wpdb->prefix . "postmeta} AS meta + JOIN " . $wpdb->prefix . "posts as posts ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND posts.post_status = 'publish'"; + // 2.3.0: get results as an array + $show_shifts = $wpdb->get_results( $query, ARRAY_A ); + $radio_station_show_shifts = $show_shifts; + } + if ( !$show_shifts || ( count( $show_shifts ) == 0 ) ) { return; } - // --- get the override weekday and convert to 24 hour time --- - $datetime = strtotime( $override['date'] ); - $weekday = date( 'l', $datetime ); - - // --- get start and end override times --- - $startoverride = strtotime( $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); - $endoverride = strtotime( $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); - if ( $endoverride <= $startoverride ) { - $endoverride = $endoverride + 86400; + // --- get the override weekday and convert to 24 hour time --- + $datetime = strtotime( $override['date'] ); + $weekday = date( 'l', $datetime ); + + // --- get start and end override times --- + $override_start = strtotime( $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); + $override_end = strtotime( $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); + // (if the end time is less than start time, adjust end to next day) + if ( $override_end <= $override_start ) { + $override_end = $override_end + 86400; } - // --- loop show shifts --- + // --- loop show shifts --- foreach ( $show_shifts as $show_shift ) { - $shift = maybe_unserialize( $show_shift->meta_value ); - if ( ! is_array( $shift ) ) { + $shift = maybe_unserialize( $show_shift['meta_value'] ); + if ( !is_array( $shift ) ) { $shift = array(); } @@ -1674,34 +2691,49 @@ function radio_station_override_column_data( $column, $post_id ) { if ( isset( $time['day'] ) && ( $time['day'] == $weekday ) ) { // --- get start and end shift times --- - $startshift = strtotime( $override['date'] . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' . $time['start_meridian'] ); - $endshift = strtotime( $override['date'] . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); - if ( $endshift <= $startshift ) { - $endshift = $endshift + 86400; + // 2.3.0: validate shift time to check if complete + $time = radio_station_validate_shift( $time ); + $shift_start = strtotime( $override['date'] . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' . $time['start_meridian'] ); + $shift_end = strtotime( $override['date'] . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); + if ( ( $shift_start == $shift_end ) || ( $shift_start > $shift_end ) ) { + $shift_end = $shift_end + 86400; } // --- compare override time overlaps to get affected shows --- - if ( ( ( $startoverride < $startshift ) && ( $endoverride > $startshift ) ) - || ( ( $startoverride >= $startshift ) && ( $startoverride < $endshift ) ) ) { - $active = get_post_meta( $show_shift->post_id, 'show_active', true ); - if ( $active != 'on' ) { - echo '[' . __( 'Inactive', 'radio-station' ) . '] '; + if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) + || ( ( $override_start >= $shift_start ) && ( $override_end < $shift_end ) ) ) { + // 2.3.0: adjust cell display to two line (to allow for long show titles) + $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); + if ( 'on' != $active ) { + echo "[" . esc_html( __( 'Inactive Show', 'radio-station' ) ) . "] "; } - echo $show_shift->post_title; - echo ' (' . $time['start_hour'] . ':' . $time['start_min'] . $time['start_meridian']; - echo ' - ' . $time['end_hour'] . ':' . $time['end_min'] . $time['end_meridian'] . ')'; - if ( $active != 'on' ) { - echo ''; + echo $show_shift['post_title'] . "
    "; + if ( $time['disabled'] ) { + echo "[" . esc_html( __( 'Disabled Shift', 'radio-station' ) ) . "] "; } - echo '
    '; + echo radio_station_translate_weekday( $time['day'] ); + echo " " . esc_html( $time['start_hour'] ) . ":" . esc_html( $time['start_min'] ) . esc_html( $time['start_meridian'] ); + echo " - " . esc_html( $time['end_hour'] ) . ":" . esc_html( $time['end_min'] ) . esc_html( $time['end_meridian'] ); + echo "
    "; } } } } - } elseif ( $column == 'override_image' ) { - $thumbnail_url = get_the_post_thumbnail_url( $post_id ); + } elseif ( 'description' == $column ) { + // 2.3.0: added override description indicator + global $wpdb; + $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $post_id ); + $content = $wpdb->get_var( $query ); + if ( !$content || ( trim( $content ) == '' ) ) { + echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + } else { + echo esc_html( __( 'Yes', 'radio-station' ) ); + } + } elseif ( 'override_image' == $column ) { + $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); if ( $thumbnail_url ) { - echo "
    "; + echo "
    " . esc_attr( __(
    "; } } } @@ -1722,11 +2754,11 @@ function radio_station_override_sortable_columns( $columns ) { add_action( 'admin_footer', 'radio_station_override_column_styles' ); function radio_station_override_column_styles() { $currentscreen = get_current_screen(); - if ( $currentscreen->id !== 'edit-override' ) { + if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { return; } - echo ''; + echo ""; } // ---------------------------------- @@ -1735,50 +2767,47 @@ function radio_station_override_column_styles() { // 2.2.7: added month selection filtering add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); function radio_station_override_date_filter( $post_type, $which ) { + global $wp_locale; - if ( 'override' !== $post_type ) { + if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { return; } - // --- get all show override months/years --- + // --- get all show override months / years --- global $wpdb; - $overridequery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'override'"; - $results = $wpdb->get_results( $overridequery, ARRAY_A ); + $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + $months = array(); if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { - $post_id = $result['ID']; - $override = get_post_meta( $post_id, 'show_override_date', true ); - $datetime = strtotime( $override ); - $month = date( 'm', $datetime ); - $year = date( 'Y', $datetime ); - $months[ $year . $month ]['year'] = $year; - $months[ $year . $month ]['month'] = $month; + $post_id = $result['ID']; + $override = get_post_meta( $post_id, 'show_override_date', true ); + $datetime = strtotime( $override ); + $month = date( 'm', $datetime ); + $year = date( 'Y', $datetime ); + $months[$year . $month]['year'] = $year; + $months[$year . $month]['month'] = $month; } } else { return; } // --- maybe get specified month --- + // TODO: maybe use get_query_var for month ? $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; // --- month override selector --- - ?> - - '; + echo ''; + if ( count( $months ) > 0 ) { foreach ( $months as $key => $data ) { - if ( $m == $key ) { - $selected = ' selected="selected"'; - } else { - $selected = ''; - } - $label = esc_attr( $wp_locale->get_month( $data['month'] ) . ' ' . $data['year'] ); - echo "\n"; + $label = $wp_locale->get_month( $data['month'] ) . ' ' . $data['year']; + echo "\n"; } - ?> - - '; + } @@ -1788,14 +2817,15 @@ function radio_station_override_date_filter( $post_type, $which ) { // 2.2.7: added filter for custom column sorting add_action( 'pre_get_posts', 'radio_station_columns_query_filter' ); function radio_station_columns_query_filter( $query ) { - if ( ! is_admin() || ! $query->is_main_query() ) { + if ( !is_admin() || !$query->is_main_query() ) { return; } // --- Shows by Shift Days Filtering --- - if ( 'show' === $query->get( 'post_type' ) ) { + if ( RADIO_STATION_SHOW_SLUG === $query->get( 'post_type' ) ) { // --- check if day filter is seta --- + // TODO: maybe use get_query_var for weekday ? if ( isset( $_GET['weekday'] ) && ( '0' != $_GET['weekday'] ) ) { $weekday = $_GET['weekday']; @@ -1803,20 +2833,36 @@ function radio_station_columns_query_filter( $query ) { // need to loop and sync a separate meta key to enable filtering // (not really efficient but at least it makes it possible!) // ...but could be improved by checking against postmeta table - global $wpdb; - $showquery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'show'"; - $results = $wpdb->get_results( $showquery, ARRAY_A ); + // 2.3.0: cache all show posts query result for efficiency + global $radio_station_data; + if ( isset( $radio_station_data['all-shows'] ) ) { + $results = $radio_station_data['all-shows']; + } else { + global $wpdb; + $showquery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "'"; + $results = $wpdb->get_results( $showquery, ARRAY_A ); + $radio_station_data['all-shows'] = $results; + } if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { $post_id = $result['ID']; - $shifts = get_post_meta( $post_id, 'show_sched', true ); - if ( $shifts && is_array( $shifts ) ) { - $shiftdays = array(); - $shiftstart = false; + $shifts = get_post_meta( $post_id, 'show_sched', true ); + + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + $shiftdays = array(); + $shiftstart = $prevtime = false; foreach ( $shifts as $shift ) { if ( $shift['day'] == $weekday ) { - $shifttime = radio_station_convert_schedule_to_24hour( $shift ); - $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ':00'; + // 2.3.0: replace old with new 24 hour conversion + // $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ":00"; + // $shiftstart = radio_station_convert_schedule_to_24hour( $shift ); + $shiftstart = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + $shifttime = strtotime( $weekday . ' ' . $shiftstart ); + // 2.3.0: check for earliest shift for that day + if ( !$prevtime || ( $shifttime < $prevtime ) ) { + $shiftstart = radio_station_convert_shift_time( $shiftstart, 24 ) . ':00'; + $prevtime = $shifttime; + } } } if ( $shiftstart ) { @@ -1832,10 +2878,10 @@ function radio_station_columns_query_filter( $query ) { // --- set the meta query for filtering --- // this is not working?! but does not need to as using orderby fixes it - $meta_query = array( - 'key' => 'show_shift_time', - 'compare' => 'EXISTS', - ); + // $meta_query = array( + // 'key' => 'show_shift_time', + // 'compare' => 'EXISTS', + // ); // $query->set( 'meta_query', $meta_query ); // --- order by show time start --- @@ -1849,7 +2895,7 @@ function radio_station_columns_query_filter( $query ) { // --- Order Show Overrides by Override Date --- // also making this the default sort order // if ( 'show_override_date' === $query->get( 'orderby' ) ) { - if ( 'override' === $query->get( 'post_type' ) ) { + if ( RADIO_STATION_OVERRIDE_SLUG === $query->get( 'post_type' ) ) { // unless order by published date is explicitly chosen if ( 'date' !== $query->get( 'orderby' ) ) { @@ -1858,11 +2904,11 @@ function radio_station_columns_query_filter( $query ) { // (not really efficient but at least it makes it possible!) // ...but could be improved by checking against postmeta table global $wpdb; - $overridequery = 'SELECT ID FROM ' . $wpdb->posts . " WHERE post_type = 'override'"; - $results = $wpdb->get_results( $overridequery, ARRAY_A ); + $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { - $post_id = $result['ID']; + $post_id = $result['ID']; $override = get_post_meta( $post_id, 'show_override_sched', true ); if ( $override ) { update_post_meta( $post_id, 'show_override_date', $override['date'] ); @@ -1879,9 +2925,9 @@ function radio_station_columns_query_filter( $query ) { // --- apply override year/month filtering --- if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { - $yearmonth = $_GET['month']; + $yearmonth = $_GET['month']; $start_date = date( $yearmonth . '01' ); - $end_date = date( $yearmonth . 't' ); + $end_date = date( $yearmonth . 't' ); $meta_query = array( 'key' => 'show_override_date', 'value' => array( $start_date, $end_date ), @@ -1890,6 +2936,8 @@ function radio_station_columns_query_filter( $query ) { ); $query->set( 'meta_query', $meta_query ); } + } } } + diff --git a/includes/post-types.php b/includes/post-types.php index 942c90f..aed4f76 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -5,13 +5,26 @@ * Since: 2.0.0 */ - // === Post Types === // - Register Post Types +// -- Show +// -- Playlist +// -- Override +// -* DJ / Host +// -* Producer // - Set CPTs to Classic Editor -// - Add Show Thumbnail Support +// - Add Theme Thumbnail Support +// - Add Admin Bar Add New Links +// - Add Admin Bar View/Edit Link // === Taxonomies === -// - Add Genre Taxonomy +// - Register Show Taxonomies +// -- Genre Taxonomy +// -- Language Taxonomy + +// Development TODOs +// ----------------- +// - change Host post type slug to rs-host +// - change Producer post type slug to rs-producer // ------------------ @@ -29,98 +42,202 @@ function radio_station_create_post_types() { // Show // ---- // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( - 'show', - array( - 'labels' => array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ), - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __( 'Post type for Show descriptions', 'radio-station' ), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'taxonomies' => array( 'genres' ), - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments' ), - 'can_export' => true, - 'capability_type' => 'show', - 'map_meta_cap' => true, - ) + // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); + $post_type = array( + 'labels' => array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), + // 2.3.0: added archive title label + 'archive_title' => __( 'Shows', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'show_in_admin_bar' => false, // this is done manually + 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + 'public' => true, + 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), + 'hierarchical' => false, + // 2.3.0: added custom field and revision support + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'revisions' ), + 'can_export' => true, + // 2.3.0: added show archives support + 'has_archive' => 'shows', + 'rewrite' => array( + 'slug' => 'show', + 'with_front' => false, + 'feeds' => true, + ), + 'capability_type' => 'show', + 'map_meta_cap' => true, ); + // 2.3.0: add filter for show post type array + $post_type = apply_filters( 'radio_station_post_type_show', $post_type ); + register_post_type( RADIO_STATION_SHOW_SLUG, $post_type ); // -------- // Playlist // -------- // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/playlist-menu-icon.png'; - // $icon = plugins_url( 'images/playlist-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( - 'playlist', - array( - 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ), - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'comments' ), - 'can_export' => true, - 'has_archive' => 'playlists-archive', - 'rewrite' => array( 'slug' => 'playlists' ), - 'capability_type' => 'playlist', - 'map_meta_cap' => true, - ) + // $icon = plugins_url( 'images/playlist-menu-icon.png', RADIO_STATION_FILE ); + $post_type = array( + 'labels' => array( + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), + // 2.3.0: added archive title label + 'archive_title' => __( 'Playlists', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'show_in_admin_bar' => false, // this is done manually + 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + 'public' => true, + 'hierarchical' => false, + // 2.3.0: added custom field and revision support + 'supports' => array( 'title', 'editor', 'comments', 'custom-fields', 'revisions' ), + 'can_export' => true, + // 2.3.0: changed from playlists-archive + 'has_archive' => 'playlists', + 'rewrite' => array( + 'slug' => 'playlist', + 'with_front' => true, + 'feeds' => false, + ), + 'capability_type' => 'playlist', + 'map_meta_cap' => true, ); + // 2.3.0: add filter for playlist post type array + $post_type = apply_filters( 'radio_station_post_type_playlist', $post_type ); + register_post_type( RADIO_STATION_PLAYLIST_SLUG, $post_type ); // ----------------- // Schedule Override // ----------------- // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; - // $icon = plugins_url( 'images/show-menu-icon.png', dirname(dirname(__FILE__)).'/radio-station.php' ); - register_post_type( - 'override', - array( - 'labels' => array( - 'name' => __( 'Schedule Override', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ), - ), - 'show_ui' => true, - 'show_in_menu' => false, // now added to main menu - 'description' => __( 'Post type for Schedule Override', 'radio-station' ), - // 'menu_position' => 5, - // 'menu_icon' => $icon, - 'public' => true, - 'hierarchical' => false, - 'supports' => array( 'title', 'thumbnail' ), - 'can_export' => true, - 'rewrite' => array( 'slug' => 'show-override' ), - 'capability_type' => 'show', - 'map_meta_cap' => true, - ) + // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); + $post_type = array( + 'labels' => array( + 'name' => __( 'Schedule Overrides', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + ), + 'show_ui' => true, + 'show_in_menu' => false, // now added to main menu + 'show_in_admin_bar' => false, // this is done manually + 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + 'public' => true, + // 2.3.0: added taxonomies to overrides + 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), + 'hierarchical' => false, + // 2.3.0: added editor support for override description + // 2.3.0: added custom field and revision support + 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields', 'revisions' ), + 'can_export' => true, + 'has_archive' => false, + 'rewrite' => array( + 'slug' => 'override', + 'with_front' => false, + 'feeds' => false, + ), + 'capability_type' => 'override', + 'map_meta_cap' => true, ); + // 2.3.0: add filter for override post type array + $post_type = apply_filters( 'radio_station_post_type_override', $post_type ); + register_post_type( RADIO_STATION_OVERRIDE_SLUG, $post_type ); + + // --------- + // DJ / Host + // --------- + // 2.3.0: added (dummy) post type for DJ / Host profiles + // (so that rewrite rules and query vars are added for it) + $ui = apply_filters( 'radio_station_host_interface', false ); + $post_type = array( + 'labels' => array( + 'name' => __( 'Host Profiles', 'radio-station' ), + 'singular_name' => __( 'Host Profile', 'radio-station' ), + 'add_new' => __( 'New Host Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), + 'new_item' => __( 'New Host Profile', 'radio-station' ), + 'view_item' => __( 'View Host Profile', 'radio-station' ), + 'archive_title' => __( 'Show Hosts', 'radio-station' ), + ), + 'show_ui' => $ui, + 'show_in_menu' => false, + 'show_in_admin_bar' => false, + 'show_in_nav_menus' => false, + 'description' => __( 'Post type for DJ / Host Profiles', 'radio-station' ), + 'exclude_from_search' => false, + 'public' => true, + 'hierarchical' => false, + 'can_export' => false, + 'has_archive' => 'hosts', + 'rewrite' => array( + 'slug' => 'host', + 'with_front' => true, + 'feeds' => false, + ), + 'query_var' => true, + 'capability_type' => 'host', + 'map_meta_cap' => false, + ); + $post_type = apply_filters( 'radio_station_post_type_host', $post_type ); + // TODO: change Host post type slug to rs-host + register_post_type( RADIO_STATION_HOST_SLUG, $post_type ); + + // -------- + // Producer + // -------- + // 2.3.0: added (dummy) post type for Producer profiles + // (so that rewrite rules and query vars are added for it) + $ui = apply_filters( 'radio_station_producer_interface', false ); + $post_type = array( + 'labels' => array( + 'name' => __( 'Producer Profiles', 'radio-station' ), + 'singular_name' => __( 'Producer Profile', 'radio-station' ), + 'add_new' => __( 'New Producer Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), + 'new_item' => __( 'New Producer Profile', 'radio-station' ), + 'view_item' => __( 'View Producer Profile', 'radio-station' ), + 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), + ), + 'show_ui' => $ui, + 'show_in_menu' => false, + 'show_in_admin_bar' => false, + 'show_in_nav_menus' => false, + 'description' => __( 'Post type for Producer Profiles', 'radio-station' ), + 'exclude_from_search' => false, + 'public' => true, + 'hierarchical' => false, + 'can_export' => false, + 'has_archive' => 'producers', + 'rewrite' => array( + 'slug' => 'producer', + 'with_front' => true, + 'feeds' => false, + ), + 'query_var' => true, + 'capability_type' => 'producer', + 'map_meta_cap' => false, + ); + $post_type = apply_filters( 'radio_station_post_type_producer', $post_type ); + // TODO: change Producer post type slug to rs-producer + register_post_type( RADIO_STATION_PRODUCER_SLUG, $post_type ); // --- maybe trigger flush of rewrite rules --- if ( get_option( 'radio_station_flush_rewrite_rules' ) ) { @@ -136,43 +253,139 @@ function radio_station_create_post_types() { add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( 'show', 'playlist', 'override' ); - // 2.2.8: remove strict in_array checking + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + // 2.2.8: removed strict in_array checking if ( in_array( $post_type, $post_types ) ) { return false; } + return $can_edit; } -// -------------------------- -// Add Show Thumbnail Support -// -------------------------- -// --- add featured image support to "show" post type --- -// (this is probably no longer necessary as declared in register_post_type for show) +// --------------------------- +// Add Theme Thumbnail Support +// --------------------------- +// --- declare featured image support for theme --- +// (probably no longer necessary as declared in register_post_type(s)) add_action( 'init', 'radio_station_add_featured_image_support' ); function radio_station_add_featured_image_support() { - $supported_types = get_theme_support( 'post-thumbnails' ); + // 2.3.0: add override thumbnail to theme support declaration + $supported_types = get_theme_support( 'post-thumbnails' ); if ( false === $supported_types ) { - add_theme_support( 'post-thumbnails', array( 'show' ) ); + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + add_theme_support( 'post-thumbnails', $post_types ); } elseif ( is_array( $supported_types ) ) { - $supported_types[0][] = 'show'; + $supported_types[0][] = RADIO_STATION_SHOW_SLUG; + $supported_types[0][] = RADIO_STATION_OVERRIDE_SLUG; add_theme_support( 'post-thumbnails', $supported_types[0] ); } } +// --------------------------- +// Add Admin Bar Add New Links +// --------------------------- +// 2.2.2: re-add new post type items to admin bar +// (as no longer automatically added by register_post_type) +// 2.3.0: fix to function prefix (was station_radio_) +// 2.3.0: change priority to be after main new content iteme +add_action( 'admin_bar_menu', 'radio_station_modify_admin_bar_menu', 71 ); +function radio_station_modify_admin_bar_menu( $wp_admin_bar ) { + + // 2.3.0: loop post types to add post type items + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + foreach ( $post_types as $post_type ) { + if ( current_user_can( 'publish_' . $post_type . 's' ) ) { + $post_type_object = get_post_type_object( $post_type ); + $args = array( + 'id' => 'new-' . $post_type, + 'title' => $post_type_object->labels->singular_name, + 'parent' => 'new-content', + 'href' => admin_url( 'post-new.php?post_type=' . $post_type ), + ); + $wp_admin_bar->add_node( $args ); + } + } +} + +// ---------------------------- +// Add Admin Bar View/Edit Link +// ---------------------------- +// 2.3.0: added (frontend) edit link to admin bar +// 2.3.0: changed priority to match bat edit link position +// 2.3.0: include view post type link for when editing +add_action( 'admin_bar_menu', 'radio_station_admin_bar_view_edit_links', 81 ); +function radio_station_admin_bar_view_edit_links( $wp_admin_bar ) { + + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + + // --- loop to check for plugin post types --- + if ( ! is_admin() && is_singular() ) { + foreach ( $post_types as $post_type ) { + if ( is_singular( $post_type ) ) { + // --- add post type edit link --- + if ( current_user_can( 'edit_' . $post_type . 's' ) ) { + $post_type_object = get_post_type_object( $post_type ); + $post_id = get_the_ID(); + $args = array( + 'id' => 'edit', + 'title' => __( 'Edit', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name, + 'href' => admin_url( 'post.php?post=' . $post_id . '&action=edit' ), + ); + $wp_admin_bar->add_node( $args ); + } + } + } + } + + // --- check edit post match for view link --- + // 2.3.0: add view links for admin + global $pagenow; + if ( is_admin() && ( 'post.php' == $pagenow ) ) { + global $post; + foreach ( $post_types as $post_type ) { + if ( $post->post_type == $post_type ) { + $post_type_object = get_post_type_object( $post_type ); + if ( 'draft' == $post->post_status ) { + // --- add post type preview link --- + $preview_link = get_preview_post_link( $post ); + $args = array( + 'id' => 'preview', + 'title' => $post_type_object->labels->view_item, + 'href' => esc_url( $preview_link ), + 'meta' => array( 'target' => 'wp-preview-' . $post->ID ), + ); + } else { + // --- add post type view link --- + $args = array( + 'id' => 'view', + 'title' => $post_type_object->labels->view_item, + 'href' => get_permalink( $post->ID ), + ); + } + $wp_admin_bar->add_node( $args ); + } + } + } + +} + + // ------------------ // === Taxonomies === // ------------------ -// ----------------------- -// Register Genre Taxonomy -// ----------------------- -// --- create custom taxonomy for the Show post type --- -add_action( 'init', 'radio_station_myplaylist_create_show_taxonomy' ); -function radio_station_myplaylist_create_show_taxonomy() { +// ------------------------ +// Register Show Taxonomies +// ------------------------ +add_action( 'init', 'radio_station_create_show_taxonomies' ); +function radio_station_create_show_taxonomies() { + + // -------------- + // Genre Taxonomy + // -------------- - // --- add taxonomy labels --- + // --- Genre taxonomy labels --- $labels = array( 'name' => _x( 'Genres', 'taxonomy general name', 'radio-station' ), 'singular_name' => _x( 'Genre', 'taxonomy singular name', 'radio-station' ), @@ -189,26 +402,70 @@ function radio_station_myplaylist_create_show_taxonomy() { // --- register the genre taxonomy --- // 2.2.3: added show_admin_column and show_in_quick_edit arguments - register_taxonomy( - 'genres', - array( 'show' ), - array( - 'hierarchical' => true, - 'labels' => $labels, - 'public' => true, - 'show_tagcloud' => false, - 'query_var' => true, - 'rewrite' => array( 'slug' => 'genre' ), - 'show_admin_column' => true, - 'show_in_quick_edit' => true, - 'capabilities' => array( - 'manage_terms' => 'edit_shows', - 'edit_terms' => 'edit_shows', - 'delete_terms' => 'edit_shows', - 'assign_terms' => 'edit_shows', - ), - ) + // 2.3.0: added show_in_rest argument + $args = array( + 'hierarchical' => true, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => true, + 'query_var' => true, + 'rewrite' => array( 'slug' => 'genre' ), + 'show_ui' => true, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows', + ), + ); + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + register_taxonomy( RADIO_STATION_GENRES_SLUG, $post_types, $args ); + + // ----------------- + // Language Taxonomy + // ----------------- + + // --- Language taxonomy labels --- + $labels = array( + 'name' => _x( 'Languages', 'taxonomy general name', 'radio-station' ), + 'singular_name' => _x( 'Language', 'taxonomy singular name', 'radio-station' ), + 'search_items' => __( 'Search Languages', 'radio-station' ), + 'all_items' => __( 'All Languages', 'radio-station' ), + 'parent_item' => __( 'Parent Language', 'radio-station' ), + 'parent_item_colon' => __( 'Parent Language:', 'radio-station' ), + 'edit_item' => __( 'Edit Language', 'radio-station' ), + 'update_item' => __( 'Update Language', 'radio-station' ), + 'add_new_item' => __( 'Add New Language', 'radio-station' ), + 'new_item_name' => __( 'New Language Name', 'radio-station' ), + 'menu_name' => __( 'Language', 'radio-station' ), + ); + + // --- register the language taxonomy --- + $args = array( + 'hierarchical' => false, + 'labels' => $labels, + 'public' => true, + 'show_tagcloud' => true, + 'query_var' => true, + 'rewrite' => array( 'slug' => 'language' ), + 'show_ui' => true, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'show_in_quick_edit' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_shows', + 'edit_terms' => 'edit_shows', + 'delete_terms' => 'edit_shows', + 'assign_terms' => 'edit_shows', + ), ); + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + register_taxonomy( RADIO_STATION_LANGUAGES_SLUG, $post_types, $args ); } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index ec4ed19..0927746 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -3,70 +3,1511 @@ /* Shortcode for displaying the current song * Since 2.0.0 */ -function radio_station_shortcode_now_playing( $atts ) { - $atts = shortcode_atts( - array( - 'title' => '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0, - ), - $atts, - 'now-playing' +// note: Master Schedule Shortcode in /includes/master-schedule.php + +// === Time Shortcodes === +// - Radio Timezone Shortcode +// === Archive Shortcodes === +// - Archive List Shortcode Abstract +// - Show Archive Shortcode +// - Playlist Archive Shortcode +// - Override Archive Shortcode +// - Genre Archive Shortcode +// === Show Shortcodes === +// - Show List Shortcode Abstract +// - Show Posts List Shortcode +// - Show Playlists List Shortcode +// - Show Lists Pagination Javascript +// === Legacy Shortcodes === +// - Current Show Shortcode +// - Upcoming Shows Shortcode +// - Now Playing Shortcode +// - Show Playlist Shortcode +// - Show List Shortcode + +// Development TODOs +// ----------------- +// - add pagination to Archive list shortcode +// - add pagination to Genre list shortcode +// - add basio styling to archive and genre list shortcodes + + +// ----------------------- +// === Time Shortcodes === +// ----------------------- + +// ------------------------ +// Radio Timezone Shortcode +// ------------------------ +add_shortcode( 'radio-timezone', 'radio_station_timezone_shortcode' ); +function radio_station_timezone_shortcode( $atts = array() ) { + + // --- get radio timezone values --- + $timezone = radio_station_get_setting( 'timezone_location' ); + if ( !$timezone || ( '' == $timezone ) ) { + // --- fallback to WordPress timezone --- + $timezone = get_option( 'timezone_string' ); + if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { + $timezone = ''; + } + if ( '' == $timezone ) { + $offset = get_option( 'gmt_offset' ); + } + } + if ( isset( $offset ) ) { + if ( !$offset || ( 0 == $offset ) ) { + $offset = ''; + } elseif ( $offset > 0 ) { + $offset = '+' . $offset; + } + $timezone_display = __( 'UTC', 'radio-station' ) . ' ' . $offset; + } else { + // --- get offset and code from timezone location --- + $datetimezone = new DateTimeZone( $timezone ); + $offset = $datetimezone->getOffset( new DateTime() ); + if ( 0 == $offset ) { + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ']'; + } else { + $offset = round( $offset / 60 / 60 ); + if ( strstr( (string) $offset, '.' ) ) { + if ( substr( $offset, - 2, 2 ) == '.5' ) { + $offset = str_replace( '.5', ':30', $offset ); + } elseif ( substr( $offset, - 3, 3 ) == '.75' ) { + $offset = str_replace( '.75', ':45', $offset ); + } elseif ( substr( $offset, - 3, 3 ) == '.25' ) { + $offset = str_replace( '.25', ':15', $offset ); + } + } + if ( $offset > 0 ) { + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' +' . $offset . ']'; + } else { + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' ' . $offset . ']'; + } + } + $code = radio_station_get_timezone_code( $timezone ); + $timezone_display = $code . ' ' . $utc_offset; + } + + // --- set shortcode output --- + $output = '
    '; + $output .= ''; + $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); + $output .= ': '; + $output .= '' . esc_html( $timezone_display ) . ''; + $output .= '
    '; + + // --- filter and return --- + $output = apply_filters( 'radio_station_timezone_shortcode', $output, $atts ); + return $output; +} + + +// -------------------------- +// === Archive Shortcodes === +// -------------------------- + +// ------------------------------- +// Archive List Shortcode Abstract +// ------------------------------- +function radio_station_archive_list_shortcode( $type, $atts ) { + + // TODO: add pagination to Archive list shortcode + + // --- merge defaults with passed attributes --- + $defaults = array( + 'genre' => '', + 'thumbnails' => 1, + 'hide_empty' => 0, + 'content' => 'excerpt', + 'paginate' => 1, + // query args + 'orderby' => 'title', + 'order' => 'ASC', + 'status' => 'publish', + 'perpage' => - 1, + 'offset' => 0, + // note: for shows only + 'show_avatars' => 1, + 'with_shifts' => 1, + // 'pagination' => 1, + ); + + // --- handle possible pagination offset --- + if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > - 1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } + $atts = shortcode_atts( $defaults, $atts, $type . '-archive' ); + + // --- get published shows --- + $args = array( + 'post_type' => $type, + 'numberposts' => $atts['perpage'], + 'offset' => $atts['offset'], + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'post_status' => $atts['status'], + ); + + // --- extra queries for shows --- + if ( RADIO_STATION_SHOW_SLUG == $type ) { + + if ( $atts['with_shifts'] ) { + + // --- active shows with shifts --- + $args['meta_query'] = array( + 'relation' => 'AND', + array( + 'meta_key ' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'meta_key' => 'show_active', + 'mata_value' => 'on', + 'compare' => '=', + ), + ); + } else { + + // --- just active shows --- + $args['meta_query'] = array( + array( + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', + ), + ); + } + + // --- check for a specified show genre --- + if ( !empty( $atts['genre'] ) ) { + // --- check for provided genre(s) as slug or ID --- + if ( strstr( $atts['genre'], ',' ) ) { + $atts['genre'] = explode( ',', $atts['genre'] ); + } + $args['tax_query'] = array( + 'relation' => 'OR', + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'slug', + 'terms' => $atts['genre'], + ), + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'ID', + 'terms' => $atts['genre'], + ), + ); + } + } + + // --- get posts via query --- + $archive_posts = get_posts( $args ); + + // --- check for results --- + $list = '
    '; + if ( !$archive_posts || ( count( $archive_posts ) == 0 ) ) { + + if ( $atts['hide_empty'] ) { + return ''; + } + + // --- no shows messages ---- + if ( RADIO_STATION_SHOW_SLUG == $type ) { + if ( !empty( $atts['genre'] ) ) { + $list .= esc_html( __( 'No Shows in this Genre were found.', 'radio-station' ) ); + } else { + $list .= esc_html( __( 'No Shows were found to display.', 'radio-station' ) ); + } + } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + $list .= esc_html( __( 'No Playlists were found to display.', 'radio-station' ) ); + } elseif ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + $list .= esc_html( __( 'No Overrides were found to display.', 'radio-station' ) ); + } + + } else { + + // --- archive list --- + $list .= '
      '; + + foreach ( $archive_posts as $archive_post ) { + + $list .= '
    • '; + + // --- show avatar or thumbnail --- + $list .= '
      '; + if ( $atts['show_avatars'] && ( RADIO_STATION_SHOW_SLUG == $type ) ) { + + // --- show avatar for shows --- + $attr = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); + $show_avatar = radio_station_get_show_avatar( $archive_post->ID, 'thumbnail', $attr ); + if ( $show_avatar ) { + $list .= $show_avatar; + } + } elseif ( $atts['thumbnails'] ) { + // --- post thumbnail --- + if ( has_post_thumbnail( $archive_post->ID ) ) { + $atts = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $archive_post->ID, 'thumbnail', $atts ); + $list .= $thumbnail; + } + } + $list .= '
      '; + + // --- title ---- + $list .= ''; + + // --- meta data --- + // if ( RADIO_STATION_SHOW_SLUG == $type ) { + // TODO: show shifts, genres + // } + // if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + // TODO: playlist track / count + // } + // if ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + // TODO: override date + // } + + // --- content --- + if ( 'none' == $atts['content'] ) { + $list .= ''; + } elseif ( 'full' == $atts['content'] ) { + $list .= '
      '; + $content = apply_filters( 'radio_station_' . $type . '_archive_content', $archive_post->post_content, $archive_post->ID ); + $list .= $content; + $list .= '
      '; + } else { + $list .= '
      '; + if ( !empty( $archive_post->post_excerpt ) ) { + $excerpt = $archive_post->post_excerpt; + } else { + $excerpt = radio_station_trim_excerpt( $archive_post['post_content'] ); + } + $excerpt = apply_filters( 'radio_station_' . $type . '_archive_excerpt', $excerpt, $archive_post->ID ); + $list .= $excerpt; + $list .= '
      '; + } + + $list .= '
    • '; + } + $list .= '
    '; + } + $list .= '
    '; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + // --- filter and return --- + $list = apply_filters( 'radio_station_' . $type . '_archive_list', $list, $atts ); + + return $list; +} + +// ---------------------- +// Show Archive Shortcode +// ---------------------- +add_shortcode( 'show-archive', 'radio_station_show_archive_list' ); +add_shortcode( 'shows-archive', 'radio_station_show_archive_list' ); +function radio_station_show_archive_list( $atts ) { + return radio_station_archive_list_shortcode( RADIO_STATION_SHOW_SLUG, $atts ); +} + +// -------------------------- +// Playlist Archive Shortcode +// -------------------------- +add_shortcode( 'playlist-archive', 'radio_station_playlist_archive_list' ); +add_shortcode( 'playlists-archive', 'radio_station_playlist_archive_list' ); +function radio_station_playlist_archive_list( $atts ) { + return radio_station_archive_list_shortcode( RADIO_STATION_PLAYLIST_SLUG, $atts ); +} + +// -------------------------- +// Override Archive Shortcode +// -------------------------- +add_shortcode( 'override-archive', 'radio_station_override_archive_list' ); +add_shortcode( 'overrides-archive', 'radio_station_override_archive_list' ); +function radio_station_override_archive_list( $atts ) { + return radio_station_archive_list_shortcode( RADIO_STATION_OVERRIDE_SLUG, $atts ); +} + +// ----------------------- +// Genre Archive Shortcode +// ----------------------- +add_shortcode( 'genre-archive', 'radio_station_genre_archive_list' ); +add_shortcode( 'genres-archive', 'radio_station_genre_archive_list' ); +function radio_station_genre_archive_list( $atts ) { + + // TODO: add pagination to Genre list shortcode + + $defaults = array( + // genre display options + 'genres' => '', + 'description' => 1, + 'genre_images' => 1, + 'hide_empty' => 1, + 'paginate' => 1, + // show query args + 'perpage' => - 1, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'status' => 'publish', + // show display options + 'with_shifts' => 1, + 'show_avatars' => 0, + 'thumbnails' => 0, + // 'pagination' => 1, + ); + + // --- handle possible pagination offset --- + if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > - 1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } + $atts = shortcode_atts( $defaults, $atts, 'genre-archive' ); + + // --- maybe get specified genre(s) --- + if ( !empty( $atts['genres'] ) ) { + $genres = explode( ',', $atts['genres'] ); + foreach ( $genres as $i => $genre ) { + $genre = trim( $genre ); + $genre = radio_station_get_genre( $genre ); + if ( $genre ) { + $genres[$i] = $genre; + } else { + unset( $genres[$i] ); + } + } + } else { + // --- get all genres --- + $args = array(); + if ( !$atts['hide_empty'] ) { + $args['hide_empty'] = false; + } + $genres = radio_station_get_genres( $args ); + } + + // --- check if we have genres --- + if ( !$genres || ( count( $genres ) == 0 ) ) { + if ( $atts['hide_empty'] ) { + return ''; + } else { + $list = '
    '; + $list .= esc_html( __( 'No Genres were found to display.', 'radio-station' ) ); + $list .= '
    '; + + return $list; + } + } + + $list = '
    '; + + // --- loop genres --- + foreach ( $genres as $name => $genre ) { + + // --- get published shows --- + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'numberposts' => $atts['perpage'], + 'offset' => $atts['offset'], + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'post_status' => $atts['status'], + ); + + if ( $atts['with_shifts'] ) { + + // --- active shows with shifts --- + $args['meta_query'] = array( + 'relation' => 'AND', + array( + 'meta_key ' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'meta_key' => 'show_active', + 'mata_value' => 'on', + 'compare' => '=', + ), + ); + } else { + + // --- just active shows --- + $args['meta_query'] = array( + array( + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', + ), + ); + } + + // --- set genre taxonomy query --- + $args['tax_query'] = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'slug', + 'terms' => $genre['slug'], + ), + ); + + $posts = get_posts( $args ); + + $list .= '
    '; + + if ( $posts || ( count( $posts ) > 0 ) ) { + $has_posts = true; + } else { + $has_posts = false; + } + if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { + + // --- Genre image --- + $list .= '
    '; + $genre_image = apply_filters( 'radio_station_genre_image', false, $genre['id'] ); + if ( $genre_image ) { + $list .= $genre_image; + } + $list .= '
    '; + + // --- genre title --- + $list .= '
    '; + $list .= '

    ' . $genre['name'] . '

    '; + $list .= '
    '; + + // --- genre description --- + if ( $atts['description'] && !empty( $genre['description'] ) ) { + $list .= '
    '; + $list .= $genre['description']; + $list .= '
    '; + } + + } + + if ( !$has_posts ) { + + // --- no shows messages ---- + if ( !$atts['hide_empty'] ) { + $list .= esc_html( __( 'No Shows in this Genre.', 'radio-station' ) ); + } + + } else { + + // --- show archive list --- + $list .= '
      '; + + foreach ( $posts as $post ) { + $list .= '
    • '; + + // --- avatar or thumbnail --- + $list .= '
      '; + if ( $atts['show_avatars'] ) { + // --- get show avatar --- + $attr = array( 'class' => 'show-thumbnail-image' ); + $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); + if ( $show_avatar ) { + $list .= $show_avatar; + } + } elseif ( $atts['thumbnails'] ) { + if ( has_post_thumbnail( $post->ID ) ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); + $list .= $thumbnail; + } + } + $list .= '
      '; + + // --- show title ---- + $list .= ''; + + // --- show excerpt --- + // n/a + + $list .= '
    • '; + } + $list .= '
    '; + } + + } + + $list .= '
    '; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + // --- filter and return --- + $list = apply_filters( 'radio_station_genre_archive_list', $list, $atts ); + + return $list; +} + + +// ----------------------- +// === Show Shortcodes === +// ----------------------- + +// ---------------------------- +// Show List Shortcode Abstract +// ---------------------------- +function radio_station_show_list_shortcode( $type, $atts ) { + + global $radio_station_data; + + // --- get time and date formats --- + $timeformat = get_option( 'time_format' ); + $dateformat = get_option( 'date_format' ); + + // --- get shortcode attributes --- + $defaults = array( + 'per_page' => 15, + 'limit' => - 1, + 'content' => 'excerpt', + 'thumbnails' => 1, + 'pagination' => 1, + ); + $atts = shortcode_atts( $defaults, $atts, 'show-' . $type . '-list' ); + + // --- maybe get stored post data --- + if ( isset( $radio_station_data['show-' . $type . 's'] ) ) { + + // --- use data stored from template --- + $posts = $radio_station_data['show-' . $type . 's']; + unset( $radio_station_data['show-' . $type . 's'] ); + $show_id = $radio_station_data['show-id']; + + } else { + // --- check for show ID (required at minimum) --- + if ( !isset( $atts['show'] ) ) { + return ''; + } + $show_id = $atts['show']; + + // --- attempt to get show ID via slug --- + if ( intval( $show_id ) != $show_id ) { + global $wpdb; + $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_name = %s"; + $query = $wpdb->prepare( $query, $show_id ); + $show_id = $wpdb->get_var( $query ); + if ( !$show_id ) { + return ''; + } + } + + // --- get related to show posts --- + $args = array(); + if ( isset( $atts['limit'] ) ) { + $args['limit'] = $atts['limit']; + } + if ( 'post' == $type ) { + $posts = radio_station_get_show_posts( $show_id, $args ); + } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + $posts = radio_station_get_show_playlists( $show_id, $args ); + } elseif ( defined( 'RADIO_STATION_EPISODE_SLUG' ) && ( RADIO_STATION_EPISODE_SLUG == $type ) ) { + $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args ); + } + } + if ( !isset( $posts ) || !$posts || !is_array( $posts ) || ( count( $posts ) == 0 ) ) {return '';} + + // --- show list div --- + $list = '
    '; + + // --- loop show posts --- + $post_pages = 1; + $j = 0; + foreach ( $posts as $post ) { + $newpage = $firstpage = false; + if ( 0 == $j ) { + $newpage = $firstpage = true; + } elseif ( $j == $atts['per_page'] ) { + // --- close page div --- + $list .= '
    '; + $newpage = true; + $post_pages ++; + $j = 0; + } + if ( $newpage ) { + // --- new page div --- + if ( !$firstpage ) { + $hide = ' style="display:none;"'; + } else { + $hide = ''; + } + $list .= '
    '; + } + + // --- new item div --- + $classes = array( 'show-' . $type ); + if ( $newpage ) {$classes[] = 'first-item';} + $class = implode( ' ', $classes ); + $list .= '
    '; + + // --- post thumbnail --- + if ( $atts['thumbnails'] ) { + $has_thumbnail = has_post_thumbnail( $post['ID'] ); + if ( $has_thumbnail ) { + $attr = array( 'class' => 'show-' . esc_attr( $type ) . '-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post['ID'], 'thumbnail', $attr ); + if ( $thumbnail ) { + $list .= '
    ' . $thumbnail . '
    '; + } + } + } + + $list .= '
    '; + + // --- link to post --- + $list .= '
    '; + $permalink = get_permalink( $post['ID'] ); + $timestamp = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); + $title = __( 'Published on ', 'radio-station' ) . $timestamp; + $list .= ''; + $list .= esc_attr( $post['post_title'] ); + $list .= ''; + $list .= '
    '; + + // --- post excerpt --- + if ( 'none' == $atts['content'] ) { + $list .= ''; + } elseif ( 'full' == $atts['content'] ) { + $list .= '
    '; + $content = apply_filters( 'radio_station_show_' . $type . '_content', $post['post_content'], $post['ID'] ); + // $list .= $content; + $list .= '
    '; + } else { + $list .= '
    '; + if ( !empty( $post['post_excerpt'] ) ) { + $excerpt = $post['post_excerpt']; + } else { + $excerpt = radio_station_trim_excerpt( $post['post_content'] ); + } + $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post['ID'] ); + $list .= $excerpt; + $list .= '
    '; + } + + $list .= '
    '; + + // --- close item div --- + $list .= '
    '; + $j ++; + } + + // --- close last page div --- + $list .= '
    '; + + // --- list pagination --- + if ( $atts['pagination'] && ( $post_pages > 1 ) ) { + $list .= '

    '; + $list .= '
    '; + $list .= '
    '; + $list .= ''; + $list .= '
    '; + for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { + if ( 1 == $pagenum ) { + $active = ' active'; + } else { + $active = ''; + } + $onclick = 'radio_show_page(' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; + $list .= ''; + } + $list .= '
    '; + $list .= ''; + $list .= '
    '; + $list .= ''; + $list .= ''; + $list .= '
    '; + } + + // --- close list div --- + $list .= '
    '; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + // --- enqueue pagination javascript --- + add_action( 'wp_footer', 'radio_station_pagination_javascript' ); + + // --- filter and return --- + $list = apply_filters( 'radio_station_show_' . $type . '_list', $list, $atts ); + + return $list; +} + +// ------------------------- +// Show Posts List Shortcode +// ------------------------- +// requires: show shortcode attribute, eg. [show-posts-list show="1"] +add_shortcode( 'show-posts-archive', 'radio_station_show_posts_list' ); +add_shortcode( 'show-posts-list', 'radio_station_show_posts_list' ); +function radio_station_show_posts_list( $atts ) { + $output = radio_station_show_list_shortcode( 'post', $atts ); + return $output; +} + +// --------------------------- +// Show Latest Posts Shortcode +// --------------------------- +add_shortcode( 'show-latest-archive', 'radio_station_show_latest_list' ); +add_shortcode( 'show-latest-list', 'radio_station_show_latest_list' ); +function radio_station_show_latest_list( $atts ) { + $output = radio_station_show_list_shortcode( 'latest', $atts ); + return $output; +} + +// ----------------------------- +// Show Playlists List Shortcode +// ----------------------------- +// requires: show shortcode attribute, eg. [show-playlists-list show="1"] +add_shortcode( 'show-playlists-archive', 'radio_station_show_playlists_list' ); +add_shortcode( 'show-playlists-list', 'radio_station_show_playlists_list' ); +function radio_station_show_playlists_list( $atts ) { + $output = radio_station_show_list_shortcode( RADIO_STATION_PLAYLIST_SLUG, $atts ); + return $output; +} + +// -------------------------------- +// Show Lists Pagination Javascript +// -------------------------------- +function radio_station_pagination_javascript() { + + // --- fade out current page and fade in selected page --- + $js = "function radio_show_page(id, types, pagenum) { + currentpage = document.getElementById('show-'+id+'-'+types+'-current-page').value; + if (pagenum == 'next') { + pagenum = parseInt(currentpage) + 1; + pagecount = document.getElementById('show-'+id+'-'+types+'-page-count').value; + if (pagenum > pagecount) {return;} + } + if (pagenum == 'prev') { + pagenum = parseInt(currentpage) - 1; + if (pagenum < 1) {return;} + } + console.log(pagenum); + if (typeof jQuery == 'function') { + console.log('.show-'+id+'-'+types+'-page'); + jQuery('.show-'+id+'-'+types+'-page').fadeOut(500); + jQuery('#show-'+id+'-'+types+'-page-'+pagenum).fadeIn(1000); + jQuery('.show-'+id+'-'+types+'-page-button').removeClass('active'); + jQuery('#show-'+id+'-'+types+'-page-button-'+pagenum).addClass('active'); + jQuery('#show-'+id+'-'+types+'-current-page').val(pagenum); + } else { + pages = document.getElementsByClassName('show-'+id+'-'+types+'-page'); + for (i = 0; i < pages.length; i++) {pages[i].style.display = 'none';} + document.getElementById('show-'+id+'-'+types+'-page-'+pagenum).style.display = ''; + buttons = document.getElementsByClassName('show-'+id+'-'+types+'-page-button'); + for (i = 0; i < buttons.length; i++) {buttons[i].classList.remove('active');} + document.getElementById('show-'+id+'-'+types+'-page-button-'+pagenum).classList.add('active'); + document.getElementById('show-'+id+'-'+types+'-current-page').value = pagenum; + } + }"; + + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station', $js ); +} + + +// ------------------------- +// === Legacy Shortcodes === +// ------------------------- + +// ---------------------- +// Current Show Shortcode +// ---------------------- +// [current-show] / [dj-widget] +// 2.0.9: shortcode function for current DJ on-air +// 2.3.0: added missing output sanitization +// 2.3.0: added current-show shortcode alias +add_shortcode( 'dj-widget', 'radio_station_current_show_shortcode' ); +add_shortcode( 'current-show', 'radio_station_current_show_shortcode' ); +function radio_station_current_show_shortcode( $atts ) { + + global $radio_station_data; + + $output = ''; + + // --- get shortcode attributes --- + $defaults = array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'default_name' => '', + 'time' => '12', + 'show_sched' => 1, + 'show_playlist' => 1, + 'show_all_sched' => 0, + 'show_desc' => 0, + // new display options + 'avatar_width' => '', + 'title_position' => 'below', + 'link_djs' => 0, + 'widget' => 0, ); + $atts = shortcode_atts( $defaults, $atts, 'dj-widget' ); - $most_recent = radio_station_myplaylist_get_now_playing(); - $output = ''; + // 2.3.0: maybe set float class and avatar width style + $widthstyle = $floatclass = ''; + if ( !empty( $atts['avatar_width'] ) ) { + $widthstyle = 'style="width:' . esc_attr( $atts['avatar_width'] ) . 'px;"'; + } + if ( 'right' == $atts['title_position'] ) { + $floatclass = ' float-left'; + } elseif ( 'left' == $atts['title_position'] ) { + $floatclass = ' float-right'; + } - if ( $most_recent ) { - $class = ''; - if ( isset( $most_recent['playlist_entry_new'] ) && 'on' === $most_recent['playlist_entry_new'] ) { - $class = 'new'; + // --- get meridiem conversions --- + // 2.3.0: added once-off pre-conversions + if ( 12 == (int) $atts['time'] ) { + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + } + + // --- get current show --- + // 2.3.0: use new get current show function + $shift = radio_station_get_current_show(); + + // --- open shortcode div wrapper --- + if ( !$atts['widget'] ) { + $output .= '
    '; + if ( !empty( $atts['title'] ) ) { + $output .= '

    '; + $output .= esc_html( $atts['title'] ); + $output .= '

    '; } + } + + // --- open current show list --- + $output .= '
      '; + + if ( !$shift ) { + + // TODO: output no current show text / default DJ ? + $output .= '
    • '; + $output .= esc_html( $atts['default_name'] ); + $output .= '
    • '; + + } else { - $output .= '
      '; - if ( ! empty( $atts['title'] ) ) { - $output .= '

      ' . $atts['title'] . '

      ';} + // --- set current show data --- + $show = $shift['show']; - if ( 1 === $atts['song'] ) { - $output .= '' . $most_recent['playlist_entry_song'] . ' '; + $output .= '
    • '; + + // --- set show title output --- + $title = '
      '; + if ( $atts['show_link'] ) { + $title .= ''; + } + $title .= esc_html( $show['name'] ); + if ( $atts['show_link'] ) { + $title .= ''; } - if ( 1 === $atts['artist'] ) { - $output .= '' . $most_recent['playlist_entry_artist'] . ' '; + $title .= '
      '; + + // --- show title (above only) --- + if ( 'above' == $atts['title_position'] ) { + $output .= $title; // already escaped } - if ( 1 === $atts['album'] ) { - $output .= '' . $most_recent['playlist_entry_album'] . ' '; + + // --- show avatar --- + if ( $atts['show_avatar'] ) { + + // 2.3.0: get show avatar (with thumbnail fallback) + $show_avatar = radio_station_get_show_avatar( $show['id'] ); + if ( $show_avatar ) { + $output .= '
      '; + $output .= $show_avatar; + $output .= '
      '; + } } - if ( 1 === $atts['label'] ) { - $output .= '' . $most_recent['playlist_entry_label'] . ' '; + + // --- show title (all other positions) --- + if ( 'above' != $atts['title_position'] ) { + $output .= $title; // already escaped } - if ( 1 === $atts['comments'] ) { - $output .= '' . $most_recent['playlist_entry_comments'] . ' '; + + $output .= ''; + + // --- encore presentation --- + // 2.3.0: added encore presentation display + if ( isset( $show['encore'] ) && ( $show['encore'] ) ) { + $output .= '
      '; + $output .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); + $output .= '
      '; + } + + // --- show DJs / hosts --- + if ( $atts['display_djs'] ) { + + $hosts = get_post_meta( $show['id'], 'show_user_list', true ); + + if ( $hosts && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + + $output .= '
      '; + + $output .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + + $count = 0; + $host_count = count( $hosts ); + foreach ( $hosts as $host ) { + + $count ++; + + // 2.3.0: maybe get stored user data + // $user = get_userdata( $host ); + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } + + if ( $atts['link_djs'] ) { + // 2.3.0: use new get host URL function + $host_link = radio_station_get_host_url( $host ); + $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); + + $output .= ''; + $output .= esc_html( $user->display_name ); + $output .= ''; + } else { + $output .= esc_html( $user->display_name ); + } + + if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) + || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) == $count ) ) ) { + $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { + $output .= ', '; + } + } + $output .= '
      '; + } } - $output .= '' . __( 'View Playlist', 'radio-station' ) . ' '; + + // --- show description --- + // 2.3.0: convert span to div tags for consistency + if ( $atts['show_desc'] ) { + $show_post = get_post( $show['id'] ); + $excerpt = radio_station_trim_excerpt( $show_post->post_content ); + $excerpt = apply_filters( 'radio_station_current_show_excerpt', $excerpt, $show['id'] ); + if ( $atts['widget'] ) { + $excerpt = apply_filters( 'radio_station_current_show_excerpt_widget', $excerpt, $show['id'] ); + } + $output .= '
      '; + $output .= $excerpt; + $output .= '
      '; + } + + // --- current show playlist --- + // 2.3.0: convert span to div tags for consistency + if ( $atts['show_playlist'] ) { + // TODO: fix to get now playing playlist + $output .= ''; + } + + $output .= ''; + + // --- show schedule --- + if ( $atts['show_sched'] ) { + + $output .= '
      '; + + // --- maybe show all shifts --- + // (only if not a schedule override) + if ( !isset( $show['override'] ) && $atts['show_all_sched'] ) { + $shifts = get_post_meta( $show['id'], 'show_sched', true ); + } else { + $shifts = array( $show['shifts'] ); + } + + foreach ( $shifts as $shift ) { + + // --- convert shift info --- + // 2.2.2: translate weekday for display + $display_day = radio_station_translate_weekday( $shift['day'] ); + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $shift_start_time = strtotime( $start ); + $shift_end_time = strtotime( $end ); + if ( $start > $end ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + // --- add class to highlight now playing shift --- + $classes = array( 'current-show-shifts', 'on-air-dj-sched' ); + $now = strtotime( current_time( 'mysql' ) ); + if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + $current_shift = $shift; + $current_shift_start = $shift_start_time; + $current_shift_end = $shift_end_time; + $classes[] = 'current-shift'; + } + $class = implode( ' ', $classes ); + + // --- shift display output --- + if ( 24 == (int) $atts['time'] ) { + $start = radio_station_convert_time( $start, 24 ); + $end = radio_station_convert_time( $end, 24 ); + $data_format = 'j, g:i'; + $data_format2 = 'g:i'; + } else { + $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $start ); + $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $end ); + $data_format = 'j, H:i a'; + $data_format2 = 'H:i a'; + } + + $output .= '
      '; + $output .= ''; + $output .= esc_html( $display_day ) . ', ' . $start . ' - '; + $output .= '' . $end . ''; + $output .= '
      '; + } + + $output .= '
      '; + } + + $output .= '
    • '; + } + + // --- close show list --- + $output .= '
    '; + + // --- hidden inputs for current shift times --- + // TODO: calculate and countdown remaining show time + if ( isset( $current_shift ) ) { + $output .= ''; + $output .= ''; + } + + // --- close shortcode div wrapper --- + if ( !$atts['widget'] ) { $output .= '
    '; + } + + return $output; +} + +// ------------------------ +// Upcoming Shows Shortcode +// ------------------------ +// [upcoming-shows] / [dj-coming-up-widget] +// 2.0.9: shortcode for displaying upcoming DJs/shows +// 2.3.0: added missing output sanitization +// 2.3.0: added new upcoming-shows shortcode alias +add_shortcode( 'dj-coming-up-widget', 'radio_station_upcoming_shows_shortcode' ); +add_shortcode( 'upcoming-shows', 'radio_station_upcoming_shows_shortcode' ); +function radio_station_upcoming_shows_shortcode( $atts ) { + + global $radio_station_data; + + $output = ''; + + $defaults = array( + 'title' => '', + 'display_djs' => 0, + 'show_avatar' => 0, + 'show_link' => 0, + 'limit' => 1, + 'time' => '12', + 'show_sched' => 1, + // new display options + 'display_hosts' => 0, + 'display_producers' => 0, + 'avatar_width' => '', + 'title_position' => 'below', + 'link_djs' => 0, + 'widget' => 0, + ); + $atts = shortcode_atts( $defaults, $atts, 'dj-coming-up-widget' ); + + // 2.2.4: maybe set float class and avatar width style + // 2.3.0: moved here from upcoming widget class + $width_style = $float_class = ''; + if ( !empty( $atts['avatar_width'] ) ) { + $width_style = 'style="width:' . esc_attr( $atts['avatar_width'] ) . 'px;"'; + } + if ( 'right' == $atts['title_position'] ) { + $float_class = ' float-left'; + } elseif ( 'left' == $atts['title_position'] ) { + $float_class = ' float-right'; + } + + // --- get meridiem conversions --- + // 2.3.0: added once-off pre-conversions + if ( 12 == (int) $atts['time'] ) { + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + } + + // --- get the upcoming shows --- + // 2.3.0: use new get next shows function + $shows = radio_station_get_next_shows( $atts['limit'] ); + + // --- open shortcode container --- + if ( !$atts['widget'] ) { + $output .= '
    '; + + // --- maybe output shortcode title --- + if ( !empty( $atts['title'] ) ) { + $output .= '

    '; + $output .= esc_html( $atts['title'] ); + $output .= '

    '; + } + } + + // --- open upcoming show list --- + $output .= '
      '; + + // --- no shows upcoming output --- + if ( !$shows ) { + + $output .= '
    • '; + $output .= esc_html( __( 'No Shows Upcoming', 'radio-station' ) ); + $output .= '
    • '; + + // TODO: where to output this?! + // if ( ! empty( $default ) ) { + // $output .= '
    • '; + // $output .= esc_html( $default ); + // $output .= '
    • '; + // } } else { - echo 'No playlists available.'; + + // --- loop upcoming shows --- + foreach ( $shows as $shift ) { + + $show = $shift['show']; + + $output .= '
    • '; + + // --- set show title output --- + $title = '
      '; + if ( $atts['show_link'] ) { + $title .= ''; + } + $title .= esc_html( $show['name'] ); + if ( $atts['show_link'] ) { + $title .= ''; + } + $title .= '
      '; + + // --- show title (above position only) --- + // (for above position only) + if ( 'above' == $atts['title_position'] ) { + $output .= $title; // already escaped + } + + // --- show avatar --- + if ( $atts['show_avatar'] ) { + + // 2.3.0: get show avatar (with thumbnail fallback) + $show_avatar = radio_station_get_show_avatar( $show['id'] ); + if ( $show_avatar ) { + $output .= '
      '; + $output .= $show_avatar; + $output .= '
      '; + } + } + + // --- show title (all other positions) --- + // (for all positions except above) + if ( 'above' != $atts['title_position'] ) { + $output .= $title; // already escaped + } + + echo ''; + + // --- encore presentation --- + // 2.2.4: added encore presentation display + // if ( array_key_exists( $showtime, $djs['encore'] ) ) { + if ( isset( $show['encore'] ) && ( $show['encore'] ) ) { + $output .= '
      '; + $output .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); + $output .= '
      '; + } + + // --- DJ / Host names --- + if ( ( $atts['display_djs'] ) || ( $atts['display_hosts'] ) ) { + + $hosts = get_post_meta( $show['id'], 'show_user_list', true ); + if ( isset( $hosts ) && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + + $output .= '
      '; + + $output .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + + $count = 0; + $host_count = count( $hosts ); + foreach ( $hosts as $host ) { + + $count ++; + + // 2.3.0: maybe get stored user data + // $user = get_userdata( $host ); + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } + + if ( $atts['link_djs'] ) { + // 2.3.0: use new get host URL function + $host_link = radio_station_get_host_url( $host ); + $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); + + $output .= ''; + $output .= esc_html( $user->display_name ); + $output .= ''; + } else { + $output .= esc_html( $user->display_name ); + } + + if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) + || ( ( $host_count > 2 ) && ( $count == ( $host_count - 1 ) ) ) ) { + $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { + $output .= ', '; + } + } + $output .= '
      '; + } + } + + $output .= ''; + + // --- show schedule --- + if ( $atts['show_sched'] ) { + + $output .= '
      '; + + // --- convert shift info --- + // 2.2.2: fix to weekday value to be translated + $display_day = radio_station_translate_weekday( $shift['day'] ); + $shift_start_time = strtotime ( $shift['start'] ); + $shift_end_time = strtotime( $shift['end'] ); + if ( $shift_end_time < $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 7 * 60 * 60 ); + } + + // --- maybe set next show shift times --- + if ( !isset( $next_start_time ) ) { + $next_start_time = $shift_start_time; + $next_end_time = $shift_end_time; + } + + $classes = array( 'upcoming-show-shift', 'on-air-dj-sched' ); + $now = strtotime( current_time( 'mysql' ) ); + if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + $classes[] = 'current-shift'; + } + $class = implode( ' ', $classes ); + + // 2.2.7: fix to convert time to integer + if ( 24 == (int) $atts['time'] ) { + + // --- convert start/end time to 24 hours --- + $start = radio_station_convert_time( $shift['start'], 24 ); + $end = radio_station_convert_time( $shift['end'], 24 ); + $data_format = 'j, G:i'; + $data_format2 = 'G:i'; + + } else { + + $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['start'] ); + $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['end'] ); + $data_format = 'j, H:i a'; + $data_format2 = 'H:i a'; + + } + + $output .= '
      '; + $output .= ''; + $output .= esc_html( $display_day ) . ', ' . $start . ' - '; + $output .= '' . $end . ''; + $output .= '
      '; + $output .= '
      '; + + $output .= '
      '; + } + + $output .= '
    • '; + } + + } + + // --- close upcoming shows list --- + $output .= '
    '; + + // --- hidden input for next show times --- + // TODO: calculate and countdown next upcoming show + $output .= ''; + $output .= ''; + + // --- close shortcode container --- + if ( !$atts['widget'] ) { + $output .= '
    '; } return $output; } -add_shortcode( 'now-playing', 'radio_station_shortcode_now_playing' ); +// --------------------- +// Now Playing Shortcode +// --------------------- +// [now-playing] +// 2.3.0: added missing output sanitization +add_shortcode( 'current-playlist', 'radio_station_now_playing_shortcode' ); +add_shortcode( 'now-playing', 'radio_station_now_playing_shortcode' ); +function radio_station_now_playing_shortcode( $atts ) { + + $output = ''; + + // --- get shortcode attributes --- + $defaults = array( + 'title' => '', + 'artist' => 1, + 'song' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, + 'widget' => 0, + ); + $atts = shortcode_atts( $defaults, $atts, 'now-playing' ); + + // --- fetch the current playlist --- + $playlist = radio_station_get_now_playing(); + + // --- shortcode title (not for widget) --- + // 2.3.0: added title class for shortcode + if ( !$atts['widget'] && !empty( $atts['title'] ) ) { + $output .= '

    ' . esc_attr( $atts['title'] ) . '

    '; + } + + // 2.3.0: use updated code from now playing widget + if ( $playlist ) { + + // 2.3.0: split div wrapper from track wrapper + $output .= '
    '; + + // --- loop playlist tracks --- + // 2.3.0: loop all instead of just latest + foreach ( $playlist['tracks'] as $track ) { + $class = ''; + if ( isset( $track['playlist_entry_new'] ) && ( 'on' === $track['playlist_entry_new'] ) ) { + $class .= ' new'; + } + // 2.3.0: added check for latest track since looping + if ( $track == $playlist['latest'] ) { + $class .= ' latest'; + } else { + $class .= ' played'; + } + + $output .= '
    '; + + // 2.2.3: convert span tags to div tags + // 2.2.4: check value keys are set before outputting + if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { + $output .= '
    '; + $output .= esc_html( __( 'Song', 'radio-station' ) ); + $output .= ': ' . esc_html( $track['playlist_entry_song'] ); + $output .= '
    '; + } + + // 2.2.7: add label prefixes to now playing data + if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { + $output .= '
    '; + $output .= esc_html( __( 'Artist', 'radio-station' ) ); + $output .= ': ' . esc_html( $track['playlist_entry_artist'] ); + $output .= '
    '; + } + + if ( $atts['album'] && isset( $track['playlist_entry_album'] ) ) { + $output .= '
    '; + $output .= esc_html( __( 'Album', 'radio-station' ) ); + $output .= ': ' . esc_html( $track['playlist_entry_album'] ); + $output .= '
    '; + } + + if ( $atts['label'] && isset( $track['playlist_entry_label'] ) ) { + $output .= '
    '; + $output .= esc_html( __( 'Label', 'radio-station' ) ); + $output .= ': ' . esc_html( $track['playlist_entry_label'] ); + $output .= '
    '; + } + + if ( $atts['comments'] && isset( $track['playlist_entry_comments'] ) ) { + $output .= '
    '; + $output .= esc_html( __( 'Comments', 'radio-station' ) ); + $output .= ': ' . esc_html( $track['playlist_entry_comments'] ); + $output .= '
    '; + } + + echo '
    '; + } + + // --- playlist permalink --- + if ( isset( $playlist['playlist_permalink'] ) ) { + $output .= ''; + } + + } else { + // 2.2.3: added missing translation wrapper + // 2.3.0: added no playlist class + $output .= '
    '; $output .= ''; - $playlist_archive = get_post_type_archive_link( 'playlist' ); - $params = array( 'show_id' => $atts['show'] ); + $playlist_archive = get_post_type_archive_link( RADIO_STATION_PLAYLIST_SLUG ); + $params = array( 'show_id' => $atts['show'] ); $playlist_archive = add_query_arg( $params, $playlist_archive ); - $output .= '' . __( 'More Playlists', 'radio-station' ) . ''; + $output .= '' . esc_html( __( 'More Playlists', 'radio-station' ) ) . ''; $output .= '
    '; return $output; } -add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); +// ------------------- +// Show List Shortcode +// ------------------- +// [list-shows] /* Shortcode for displaying a list of all shows * Since 2.0.0 */ +add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); function radio_station_shortcode_list_shows( $atts ) { - $atts = shortcode_atts( - array( - 'genre' => '', - ), - $atts, - 'list-shows' + $defaults = array( + 'title' => false, + 'genre' => '', ); + $atts = shortcode_atts( $defaults, $atts, 'list-shows' ); // grab the published shows $args = array( @@ -131,371 +1582,63 @@ function radio_station_shortcode_list_shows( $atts ) { 'offset' => 0, 'orderby' => 'title', 'order' => 'ASC', - 'post_type' => 'show', + 'post_type' => RADIO_STATION_SHOW_SLUG, 'post_status' => 'publish', 'meta_query' => array( array( - 'key' => 'show_active', - 'value' => 'on', + // 2.3.0: fix key/value to meta_key/meta_value + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', ), ), ); - if ( ! empty( $atts['genre'] ) ) { + if ( !empty( $atts['genre'] ) ) { $args['tax_query'] = array( array( - 'taxonomy' => 'genres', + 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'field' => 'slug', 'terms' => $atts['genre'], ), ); } - $query = new WP_Query( $args ); + // 2.3.0: use get_posts instead of WP_Query + $posts = get_posts( $args ); // if there are no shows saved, return nothing - if ( ! $query->have_posts() ) { - return false; + if ( !$posts || ( count( $posts ) == 0 ) ) { + return ''; } $output = ''; $output .= '
    '; - $output .= '
      '; - while ( $query->have_posts() ) : - $query->the_post(); - $output .= '
    • '; - $output .= '' . get_the_title() . ''; - $output .= '
    • '; - endwhile; - $output .= '
    '; - $output .= '
    '; - wp_reset_postdata(); - return $output; -} -add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); - -/* Shortcode function for current DJ on-air - * Since 2.0.9 - */ -function radio_station_shortcode_dj_on_air( $atts ) { - $atts = shortcode_atts( - array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'default_name' => '', - 'time' => '12', - 'show_sched' => 1, - 'show_playlist' => 1, - 'show_all_sched' => 0, - 'show_desc' => 0, - ), - $atts, - 'dj-widget' - ); - // find out which DJ(s) are currently scheduled to be on-air and display them - $djs = radio_station_dj_get_current(); - $playlist = radio_station_myplaylist_get_now_playing(); - - $dj_str = ''; - - $dj_str .= '
    '; - if ( ! empty( $atts['title'] ) ) { - $dj_str .= '

    ' . $atts['title'] . '

    '; + if ( $atts['title'] ) { + $output .= '
    '; + $output .= '

    ' . esc_html( $atts['title'] ) . '

    '; + $output .= '
    '; } - $dj_str .= '
      '; - // echo the show/dj currently on-air - if ( 'override' === $djs['type'] ) { + $output .= '
        '; - $dj_str .= '
      • '; - if ( $atts['show_avatar'] ) { - if ( has_post_thumbnail( $djs['all'][0]['post_id'] ) ) { - $dj_str .= '' . get_the_post_thumbnail( $djs['all'][0]['post_id'], 'thumbnail' ) . ''; - } - } + // 2.3.0: use posts loop instead of query loop + foreach ( $posts as $post ) { - $dj_str .= $djs['all'][0]['title']; - - // display the override's schedule if requested - if ( $atts['show_sched'] ) { - - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' ' . $djs['all'][0]['sched']['start_meridian'] . '-' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . ' ' . $djs['all'][0]['sched']['end_meridian'] . '
        '; - } else { - $djs['all'][0]['sched'] = radio_station_convert_schedule_to_24hour( $djs['all'][0]['sched'] ); - $dj_str .= '' . $djs['all'][0]['sched']['start_hour'] . ':' . $djs['all'][0]['sched']['start_min'] . ' -' . $djs['all'][0]['sched']['end_hour'] . ':' . $djs['all'][0]['sched']['end_min'] . '
        '; - } + $output .= '
      • '; - $dj_str .= '
      • '; - } - } else { - - if ( isset( $djs['all'] ) && ( count( $djs['all'] ) > 0 ) ) { - foreach ( $djs['all'] as $dj ) { - - $dj_str .= '
      • '; - if ( $atts['show_avatar'] ) { - $dj_str .= '' . get_the_post_thumbnail( $dj->ID, 'thumbnail' ) . ''; - } - - $dj_str .= ''; - if ( $atts['show_link'] ) { - $dj_str .= '' . $dj->post_title . ''; - } else { - $dj_str .= $dj->post_title; - } - $dj_str .= ''; - - if ( $atts['display_djs'] ) { - - $names = get_post_meta( $dj->ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - - $dj_str .= '
        ' . __( 'With', 'radio-station' ) . ' '; - foreach ( $names as $name ) { - $count++; - $user_info = get_userdata( $name ); - - $dj_str .= $user_info->display_name; - - $count_names = count( $names ); - if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { - $dj_str .= ' and '; - } elseif ( $count < $count_names && $count_names > 2 ) { - $dj_str .= ', '; - } - } - $dj_str .= '
        '; - } - } - - if ( $atts['show_desc'] ) { - $desc_string = radio_station_shorten_string( wp_strip_all_tags( $dj->post_content ), 20 ); - $dj_str .= '' . $desc_string . ''; - } - - if ( $atts['show_playlist'] ) { - $dj_str .= '' . __( 'View Playlist', 'radio-station' ) . ''; - } - - $dj_str .= ''; - - if ( $atts['show_sched'] ) { - - $scheds = get_post_meta( $dj->ID, 'show_sched', true ); - - // if we only want the schedule that's relevant now to display... - if ( ! $atts['show_all_sched'] ) { - - $current_sched = radio_station_current_schedule( $scheds ); - - if ( $current_sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $current_sched['day'] ); - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' ' . $current_sched['start_meridian'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . ' ' . $current_sched['end_meridian'] . '
        '; - } else { - $current_sched = radio_station_convert_schedule_to_24hour( $current_sched ); - $dj_str .= '' . $display_day . ', ' . $current_sched['start_hour'] . ':' . $current_sched['start_min'] . ' - ' . $current_sched['end_hour'] . ':' . $current_sched['end_min'] . '
        '; - } - } - } else { - - foreach ( $scheds as $sched ) { - // 2.2.2: translate weekday for display - $display_day = radio_station_translate_weekday( $sched['day'] ); - if ( 12 === (int) $atts['time'] ) { - $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] . '
        '; - } else { - $sched = radio_station_convert_schedule_to_24hour( $sched ); - $dj_str .= '' . $display_day . ', ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' - ' . $sched['end_hour'] . ':' . $sched['end_min'] . '
        '; - } - } - } - } + $output .= ''; - $dj_str .= '
      • '; - } - } else { - $dj_str .= '
      • ' . $atts['default_name'] . '
      • '; - } + $output .= ''; } - $dj_str .= '
      '; - $dj_str .= '
    '; - - return $dj_str; + $output .= ''; + $output .= '
    '; + return $output; } -add_shortcode( 'dj-widget', 'radio_station_shortcode_dj_on_air' ); - -/* Shortcode for displaying upcoming DJs/shows - * Since 2.0.9 -*/ -function radio_station_shortcode_coming_up( $atts ) { - - $atts = shortcode_atts( - array( - 'title' => '', - 'display_djs' => 0, - 'show_avatar' => 0, - 'show_link' => 0, - 'limit' => 1, - 'time' => '12', - 'show_sched' => 1, - ), - $atts, - 'dj-coming-up-widget' - ); - // find out which DJ(s) are coming up today - $djs = radio_station_dj_get_next( $atts['limit'] ); - if ( ! isset( $djs['all'] ) || count( $djs['all'] ) <= 0 ) { - $output = '
  • ' . __( 'None Upcoming', 'radio-station' ) . '
  • '; - return $output; - } - - ob_start(); - ?> -
    - -

    - -
      - $dj ) { - - if ( is_array( $dj ) && 'override' === $dj['type'] ) { - ?> -
    • - - - - - -
      - - - -
      - -
    • '; - -
    • - - - ID, 'thumbnail' ); ?> - - - - - - post_title ); ?> - - post_title ); - } - ?> - - ID, 'show_user_list', true ); - $count = 0; - - if ( $names ) { - ?> -
      With - display_name ); - - $count_names = count( $names ); - if ( ( 1 === $count && 2 === $count_names ) || ( $count_names > 2 && $count === $count_names - 1 ) ) { - echo ' and '; - } elseif ( $count < $count_names && $count_names > 2 ) { - echo ', '; - } - } - ?> -
      - - - - - - , - - -
      - - - - , - - -
      - -
    • - -
    -
    - posts . " WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; + $query = $wpdb->prepare( $query, $show ); + $show_id = $wpdb->get_var( $query ); + $show = get_post( $show_id ); + } elseif ( is_int( $show ) ) { + $show = get_post( $show ); + } + } + + return $show; +} + +// --------- +// Get Shows +// --------- +// 2.3.0: added get shows data grabber +function radio_station_get_shows( $args = false ) { + + // --- set default args --- + $defaults = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'numberposts' => - 1, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'meta_key' => 'show_sched', + 'compare' => 'EXISTS', + // 'meta_value' => 's:', + // 'compare' => 'LIKE', + ), + array( + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', + ), + ), + 'orderby' => 'post_name', + 'order' => 'ASC', + ); + + // --- overwrite defaults with any arguments passed --- + if ( $args && is_array( $args ) && ( count( $args ) > 0 ) ) { + foreach ( $args as $key => $value ) { + $defaults[$key] = $value; + } + } + + // -- get and return shows --- + $shows = get_posts( $defaults ); + $shows = apply_filters( 'radio_station_get_shows', $shows, $defaults ); + + return $shows; +} + +// --------------- +// Get Show Shifts +// --------------- +// 2.3.0: added get show shifts data grabber +function radio_station_get_show_shifts( $check_conflicts = true ) { + + // --- get all shows --- + $errors = array(); + $shows = radio_station_get_shows(); + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + foreach ( $shows as $i => $show ) { + $shows[$i]->post_content = ''; + } + $debug = "Shows" . PHP_EOL . PHP_EOL . print_r( $shows, true ); + radio_station_debug( $debug ); + } + + // --- loop shows to get shifts --- + $all_shifts = array(); + if ( count( $shows ) > 0 ) { + foreach ( $shows as $show ) { + $shifts = get_post_meta( $show->ID, 'show_sched', true ); + if ( $shifts ) { + foreach ( $shifts as $shift ) { + + // --- make sure shift has sufficient info --- + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $isdisabled = true; + } else { + $isdisabled = false; + } + $shift = radio_station_validate_shift( $shift ); + + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + + // --- if it was not already disabled, add to shift errors --- + if ( !$isdisabled ) { + $errors[$show->ID][] = $shift; + } + + } else { + + // --- shift is valid so continue checking --- + $day = $shift['day']; + $thisday = date( 'Y-m-d', strtotime( $day ) ); + $nextday = date( 'Y-m-d', ( strtotime( $thisday ) + ( 24 * 60 * 60 ) ) ); + $midnight = strtotime( $nextday . ' 12:00 am' ); + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + if ( '12:00 am' == $end ) { + $end_time = strtotime( $nextday . ' 12:00 am' ); + } else { + $end_time = strtotime( $thisday . ' ' . $end ); + } + $start_time = strtotime( $thisday . ' ' . $start ); + if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { + $encore = true; + } else { + $encore = false; + } + $updated = $show->post_modified_gmt; + + // --- check if show goes over midnight --- + if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { + + // --- set the shift time as is --- + $all_shifts[$day][$start_time . '.' . $show->ID] = array( + 'day' => $day, + 'start' => $start, + 'end' => $end, + 'show' => $show->ID, + 'encore' => $encore, + 'split' => false, + 'updated' => $updated, + 'shift' => $shift, + ); + } else { + // --- split shift for this day --- + $all_shifts[$day][$start_time . '.' . $show->ID] = array( + 'day' => $day, + 'start' => $start, + 'end' => '11:59:59 pm', // midnight + 'show' => $show->ID, + 'split' => true, + 'encore' => $encore, + 'updated' => $updated, + 'shift' => $shift, + 'real_end' => $end, + ); + + // --- split shift for next day --- + $nextday = radio_station_get_next_day( $day ); + $all_shifts[$nextday][$midnight . '.' . $show->ID] = array( + 'day' => $nextday, + 'start' => '00:00 am', // midnight + 'end' => $end, + 'show' => $show->ID, + 'encore' => $encore, + 'split' => true, + 'updated' => $updated, + 'shift' => $shift, + 'real_start' => $start, + ); + } + } + } + } + } + } + + // --- maybe store any found shift errors --- + if ( count( $errors ) > 0 ) { + update_option( 'radio_station_shift_errors', $errors ); + } else { + delete_option( 'radio_station_shift_errors' ); + } + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Raw Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); + $debug .= "Shift Errors" . PHP_EOL . PHP_EOL . print_r( $errors, true ); + radio_station_debug( $debug ); + } + + // --- sort by start time for each day --- + // note: all_shifts keys are made unique by combining start time and show ID + // which allows them to be both be sorted and then checked for conflicts + if ( count( $all_shifts ) > 0 ) { + foreach ( $all_shifts as $day => $shifts ) { + ksort( $shifts ); + $all_shifts[$day] = $shifts; + } + } + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Sorted Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); + radio_station_debug( $debug ); + } + + // --- check shifts for conflicts --- + if ( $check_conflicts ) { + $all_shifts = radio_station_check_shifts( $all_shifts ); + } else { + // --- return raw data for other shift conflict checking --- + return $all_shifts; + } + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Conflict Checked Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); + radio_station_debug( $debug ); + } + + // --- shuffle shift days so today is first day --- + $today = date( 'l' ); + $day_shifts = array(); + for ( $i = 0; $i < 7; $i ++ ) { + if ( 0 == $i ) { + $day = $today; + } else { + $day = radio_station_get_next_day( $day ); + } + if ( isset( $all_shifts[$day] ) ) { + $day_shifts[$day] = $all_shifts[$day]; + } + } + + // --- filter and return --- + $day_shifts = apply_filters( 'radio_station_show_shifts', $day_shifts ); + + return $day_shifts; +} + +// ---------------------- +// Get Schedule Overrides +// ---------------------- +// 2.3.0: added get schedule overrides data grabber +function radio_station_get_overrides( $start_date = false, $end_date = false ) { + + // --- convert dates to times for checking + if ( $start_date ) { + $start_time = strtotime( $start_date ); + } + if ( $end_date ) { + $end_time = strtotime( $end_date ) + ( 24 * 60 * 60 ) - 1; + } + + // --- get all override IDs --- + global $wpdb; + $query = "SELECT ID,post_title,post_name FROM " . $wpdb->posts + . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "' AND post_status = 'publish'"; + $overrides = $wpdb->get_results( $query, ARRAY_A ); + if ( !$overrides && !is_array( $overrides ) && ( count( $overrides ) < 1 ) ) { + return false; + } + + // --- loop overrides and get data --- + $override_list = array(); + foreach ( $overrides as $i => $override ) { + $data = get_post_meta( $override['ID'], 'show_override_sched', true ); + if ( $data ) { + $date = $data['date']; + if ( '' != $date ) { + $date_time = strtotime( $date ); + $inrange = true; + + // --- check if in specified date range --- + if ( ( isset( $start_time ) && ( $date_time < $start_time ) ) + || ( isset( $end_time ) && ( $date_time > $end_time ) ) ) { + $inrange = false; + } + + // --- add the override data --- + if ( $inrange ) { + $day = date( 'l', $date_time ); + $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; + $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; + $override_start_time = strtotime( $day . ' ' . $start ); + $override_end_time = strtotime( $day . ' ' . $end ); + + if ( $override_start_time < $override_end_time ) { + + // --- add the override as is --- + $override_data = array( + 'override' => $override['ID'], + 'name' => $override['post_title'], + 'slug' => $override['post_name'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => $end, + 'url' => get_permalink( $override['ID'] ), + 'split' => false, + ); + $override_list[$date][] = $override_data; + + } else { + + // --- split the override overnight --- + $override_data = array( + 'override' => $override['ID'], + 'name' => $override['post_title'], + 'slug' => $override['post_name'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => '11:59 pm', + 'url' => get_permalink( $override['ID'] ), + 'split' => true, + ); + $override_list[$date][] = $override_data; + + $nextday = date( 'l', $date_time + ( 24 * 60 * 60 ) ); + $nextdate = date( 'Y-m-d', $date_time + ( 24 * 60 * 60 ) ); + $override_data = array( + 'override' => $override['ID'], + 'name' => $override['post_title'], + 'slug' => $override['post_name'], + 'date' => $nextdate, + 'day' => $nextday, + 'start' => '00:00 am', + 'end' => $end, + 'url' => get_permalink( $override['ID'] ), + 'split' => true, + ); + $override_list[$date][] = $override_data; + } + } + } + } + } + + // --- filter and return --- + $override_list = apply_filters( 'radio_station_get_overrides', $override_list, $start_date, $end_date ); + + return $override_list; +} + +// ------------- +// Get Show Data +// ------------- +// 2.3.0: added get show data grabber +function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { + + // --- we need a data type and show ID --- + if ( !$datatype ) { + return false; + } + if ( !$show_id ) { + return false; + } + + // --- get meta key for valid data types --- + if ( 'posts' == $datatype ) { + $metakey = 'post_showblog_id'; + } elseif ( 'playlists' == $datatype ) { + $metakey = 'playlist_show_id'; + } elseif ( 'episodes' == $datatype ) { + $metakey = 'episode_show_id'; + } else { + return false; + } + + // --- check for optional arguments --- + $default = true; + if ( !isset( $args['limit'] ) ) { + $args['limit'] = false; + } elseif ( false !== $args['limit'] ) { + $default = false; + } + if ( !isset( $args['data'] ) ) { + $args['data'] = true; + } elseif ( true !== $args['data'] ) { + $default = false; + } + if ( !isset( $args['columns'] ) || !is_array( $args['columns'] ) || ( count( $args['columns'] ) < 1 ) ) { + $columns = 'posts.ID, posts.post_title, posts.post_content, posts.post_excerpt, posts.post_date'; + } else { + $columns = array(); + $default = false; + $valid = array( + 'ID', + 'post_author', + 'post_date', + 'post_date_gmt', + 'post_content', + 'post_title', + 'post_excerpt', + 'post_status', + 'comment_status', + 'ping_status', + 'post_password', + 'post_name', + 'to_ping', + 'pinged', + 'post_modified', + 'post_modified_gmt', + 'post_content_filtered', + 'post_parent', + 'guid', + 'menu_order', + 'post_type', + 'post_mime_type', + 'comment_count', + ); + foreach ( $args['columns'] as $i => $column ) { + if ( in_array( $column, $valid ) ) { + if ( !isset( $columns ) ) { + $columns = 'posts.' . $column; + } else { + $columns .= ', posts.' . $column; + } + } + } + } + + // --- check for cached default show data --- + if ( $default ) { + $defaultdata = apply_filters( 'radio_station_cached_data', false, $datatype, $show_id ); + if ( $defaultdata ) { + return $defaultdata; + } + } + + // --- get episodes with associated show ID --- + global $wpdb; + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; + $query = $wpdb->prepare( $query, $show_id ); + $post_metas = $wpdb->get_results( $query, ARRAY_A ); + if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { + return false; + } + + // --- get post IDs from post meta --- + $post_ids = array(); + foreach ( $post_metas as $post_meta ) { + $post_ids[] = $post_meta['post_id']; + } + + // --- get posts from post IDs --- + $post_id_list = implode( ',', $post_ids ); + $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts AS posts + WHERE posts.ID IN(" . $post_id_list . ") AND posts.post_status = 'publish' + ORDER BY posts.post_date DESC"; + if ( $args['limit'] ) { + $query .= $wpdb->prepare( " LIMIT %d", $args['limit'] ); + } + $results = $wpdb->get_results( $query, ARRAY_A ); + + // --- maybe get additional data --- + // TODO: maybe get additional data for each data type ? + // if ( $args['data'] && $results && is_array( $results ) && ( count( $results ) > 0 ) ) { + // if ( 'posts' == $datatype ) { + // } elseif ( 'playlists' == $datatype ) { + // } elseif ( 'episodes' == $datatype ) { + // } + // } + + // --- maybe cache default show data --- + if ( $default ) { + do_action( 'radio_station_cache_data', $datatype, $show_id, $results ); + } + + // --- filter and return --- + $results = apply_filters( 'radio_station_show_' . $datatype, $results, $show_id, $args ); + + return $results; +} + +// ------------------ +// Get Show Data Meta +// ------------------ +function radio_station_get_show_data_meta( $show, $single = false ) { + + global $radio_station_data; + + // --- get show post --- + if ( !is_object( $show ) ) { + $show = get_post( $show ); + } + + // --- get show terms --- + $genre_list = $language_list = array(); + $genres = wp_get_post_terms( $show->ID, RADIO_STATION_GENRES_SLUG ); + if ( $genres ) { + foreach ( $genres as $genre ) { + $genre_list[] = $genre->name; + } + } + $languages = wp_get_post_terms( $show->ID, RADIO_STATION_LANGUAGES_SLUG ); + if ( $languages ) { + foreach ( $languages as $language ) { + $language_list[] = $language->name; + } + } + + // --- get show data --- + // $show_email = get_post_meta( $show->ID, 'show_email', true ); + $show_link = get_post_meta( $show->ID, 'show_link', true ); + $show_file = get_post_meta( $show->ID, 'show_file', true ); + $show_schedule = get_post_meta( $show->ID, 'show_sched', true ); + if ( $show_schedule && is_array( $show_schedule ) && ( count( $show_schedule ) > 0 ) ) { + $show_shifts = array(); + foreach ( $show_schedule as $i => $shift ) { + $shift = radio_station_validate_shift( $shift ); + if ( !isset( $shift['disabled'] ) || ( 'yes' != $shift['disabled'] ) ) { + $show_shifts[] = $shift; + } + } + } else { + $show_shifts = array(); + } + + // --- get show user data --- + $show_hosts = get_post_meta( $show->ID, 'show_user_list', true ); + $show_producers = get_post_meta( $show->ID, 'show_producer_list', true ); + $hosts = $producers = array(); + if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { + foreach ( $show_hosts as $host ) { + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } + $hosts[]['name'] = $user->display_name; + $hosts[]['url'] = radio_station_get_host_url( $host ); + } + } + if ( is_array( $show_producers ) && ( count( $show_producers ) > 0 ) ) { + foreach ( $show_producers as $producer ) { + if ( isset( $radio_station_data['user-' . $producer] ) ) { + $user = $radio_station_data['user-' . $producer]; + } else { + $user = get_user_by( 'ID', $producer ); + $radio_station_data['user-' . $producer] = $user; + } + $producers[]['name'] = $user->display_name; + $producers[]['url'] = radio_station_get_producer_url( $producer ); + } + } + + // --- create array and return --- + $show_data = array( + 'id' => $show->ID, + 'name' => $show->post_title, + 'slug' => $show->post_name, + 'url' => get_permalink( $show->ID ), + 'latest' => $show_file, + 'website' => $show_link, + // note: left out intentionally to avoid spam scrapers + // 'email' => $show_email, + 'hosts' => $hosts, + 'producers' => $producers, + 'genres' => $genre_list, + 'languages' => $language_list, + 'schedule' => $show_shifts, + ); + + // --- data route / feed for show --- + if ( radio_station_get_setting( 'enable_data_routes' ) == 'yes' ) { + $route_link = radio_station_get_route_url( 'show' ); + $show_route = add_query_arg( 'show', $show->post_name, $route_link ); + $show_data['route'] = $show_route; + } + if ( radio_station_get_setting( 'enable_data_feeds' ) == 'yes' ) { + $feed_link = radio_station_get_feed_url( 'show' ); + $show_feed = add_query_arg( 'show', $show->post_name, $feed_link ); + $show_data['feed'] = $show_feed; + } + + // --- add extra data for single show route/feed --- + if ( $single ) { + + // --- add show posts --- + $show_data['posts'] = radio_station_get_show_posts( $show->ID ); + + // --- add show playlists --- + $show_data['playlists'] = radio_station_get_show_playlists( $show->ID ); + + // --- filter to maybe add more data --- + $show_data = apply_filters( 'radio_station_show_data_meta', $show_data, $show->ID ); + } + + // --- maybe cache Show meta data --- + do_action( 'radio_station_cache_data', 'show_meta', $show->ID, $show_data ); + + return $show_data; +} + +// ---------------------- +// Get Override Data Meta +// ---------------------- +function radio_station_get_override_data_meta( $override ) { + + global $radio_station_data; + + // --- get override post --- + if ( !is_object( $override ) ) { + $override = get_post( $override ); + } + + // --- get override terms --- + $genre_list = $language_list = array(); + $genres = wp_get_post_terms( $override->ID, RADIO_STATION_GENRES_SLUG ); + if ( $genres ) { + foreach ( $genres as $genre ) { + $genre_list[] = $genre->name; + } + } + $languages = wp_get_post_terms( $override->ID, RADIO_STATION_LANGUAGES_SLUG ); + if ( $languages ) { + foreach ( $languages as $language ) { + $language_list[] = $language->name; + } + } + + // --- get override user data --- + $override_hosts = get_post_meta( $override->ID, 'override_user_list', true ); + $override_producers = get_post_meta( $override->ID, 'override_producer_list', true ); + $hosts = $producers = array(); + if ( is_array( $override_hosts ) && ( count( $override_hosts ) > 0 ) ) { + foreach ( $override_hosts as $host ) { + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } + $hosts[]['name'] = $user->display_name; + $hosts[]['url'] = radio_station_get_host_url( $host ); + } + } + if ( is_array( $override_producers ) && ( count( $override_producers ) > 0 ) ) { + foreach ( $override_producers as $producer ) { + if ( isset( $radio_station_data['user-' . $producer] ) ) { + $user = $radio_station_data['user-' . $producer]; + } else { + $user = get_user_by( 'ID', $producer ); + $radio_station_data['user-' . $producer] = $user; + } + $producers[]['name'] = $user->display_name; + $producers[]['url'] = radio_station_get_producer_url( $producer ); + } + } + + // --- create array and return --- + $override_data = array( + 'id' => $override->ID, + 'name' => $override->post_title, + 'slug' => $override->post_name, + 'url' => get_permalink( $override->ID ), + 'genres' => $genre_list, + 'languages' => $language_list, + 'hosts' => $hosts, + 'producers' => $producers, + ); + + // --- filter and return --- + $override_data = apply_filters( 'radio_station_override_data', $override_data, $override->ID ); + + return $override_data; +} + +// -------------------- +// Get Current Schedule +// -------------------- +function radio_station_get_current_schedule() { + + global $radio_station_data; + + // --- maybe get cached schedule --- + $schedule = get_transient( 'radio_station_current_schedule' ); + if ( $schedule ) { + $schedule = apply_filters( 'radio_station_current_schedule', $schedule ); + if ( $schedule ) { + return $schedule; + } + } + + // --- get all show shifts --- + $show_shifts = radio_station_get_show_shifts(); + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Show Shifts: " . print_r( $show_shifts, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + if ( count( $show_shifts ) > 0 ) { + + // --- get show overrides --- + // (from 12am this morning, for one week ahead and back) + $now = strtotime( current_time( 'mysql' ) ); + $date = date( 'd-m-Y', $now ); + $start_time = strtotime( '12am ' . $date ); + $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; + $start_time = $start_time - ( 7 * 24 * 60 * 60 ) - 1; + $start_date = date( 'd-m-Y', $start_time ); + $end_date = date( 'd-m-Y', $end_time ); + $override_list = radio_station_get_overrides( $start_date, $end_date ); + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Now: " . $now . " - Today: " . $date . PHP_EOL; + $debug .= "Start Date: " . $start_date . " - End Date: " . $end_date . PHP_EOL; + $debug .= "Schedule Overrides: " . print_r( $override_list, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- apply overrides to the schedule --- + if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { + foreach ( $show_shifts as $day => $shifts ) { + $date = date( 'Y-m-d', strtotime( $day ) ); + if ( isset( $override_list[$date] ) ) { + $overrides = $override_list[$date]; + foreach ( $shifts as $start => $shift ) { + $start_time = strtotime( $date . ' ' . $shift['start'] ); + $end_time = strtotime( $date . ' ' . $shift['end'] ); + foreach ( $overrides as $i => $override ) { + $override_start_time = strtotime( $date . ' ' . $override['start'] ); + $override_end_time = strtotime( $date . ' ' . $override['end'] ); + + // --- check if start time is the same --- + if ( $override_start_time == $start_time ) { + + // overwrite the existing shift + $show_shifts[$day][$start] = $override; + + // check if there is remainder of existing show + // ...this should hopefully not happen! + // echo $override_end_time.'<->'.$end_time; + if ( $override_end_time < $end_time ) { + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $show_shifts[$day][$override['end']] = $shift; + } + unset( $overrides[$i] ); + + } elseif ( ( $override_start_time > $start_time ) + && ( $override_start_time < $end_time ) ) { + + // cut the existing shift short to add the override + $show_shifts[$day][$start]['end'] = $override['start']; + $show_shifts[$day][$start]['trimmed'] = 'end'; + $show_shifts[$day][$override['start']] = $override; + unset( $overrides[$i] ); + + } + } + } + + // --- maybe reloop to insert overrides before shows --- + if ( count( $overrides ) > 0 ) { + foreach ( $overrides as $i => $override ) { + $show_shifts[$day][$override['start']] = $override; + $override_end_time = strtotime( $date . ' ' . $override['end'] ); + $found = false; + + // --- check for overlapped shift (if any) --- + foreach ( $shifts as $start => $shift ) { + // find the first show overlapped + if ( !$found ) { + $start_time = strtotime( $date . ' ' . $shift['start'] ); + // echo $override_end_time.'~~~'.$start_time; + if ( $override_end_time > $start_time ) { + // adjust the show start time to override end time + unset( $overrides[$day][$start] ); + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $show_shifts[$day][$override['end']] = $shift; + $found = true; + } + } + } + } + } + + } + } + } + + if ( RADIO_STATION_DEBUG ) { + $debug = "Combined Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- loop (remaining) shifts to add show data --- + $now = strtotime( current_time( 'mysql' ) ); + foreach ( $show_shifts as $day => $shifts ) { + foreach ( $shifts as $start => $shift ) { + + $show_id = $shift['show']; + + // --- check if shift is an override --- + if ( isset( $shift['override'] ) && $shift['override'] ) { + // ---- add the override data --- + $override = radio_station_get_override_data_meta( $shift['override'] ); + $shift['show'] = $show_shifts[$day][$start]['show'] = $override; + } else { + // --- get (or get stored) show data --- + if ( isset( $radio_station_data['show-' . $show_id] ) ) { + $show = $radio_station_data['show-' . $show_id]; + } else { + $show = radio_station_get_show_data_meta( $show_id ); + $radio_station_data['show-' . $show_id] = $show; + } + unset( $show['schedule'] ); + + // --- add show data back to shift --- + $shift['show'] = $show_shifts[$day][$start]['show'] = $show; + } + + if ( !isset( $current_show ) ) { + + // --- get this shift start and end times --- + $shift_start = $day . ' ' . $shift['start']; + $shift_end = $day . ' ' . $shift['end']; + $shift_start_time = strtotime( $shift_start ); + $shift_end_time = strtotime( $shift_end ); + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = '[[[' . date( 'm-d H:i:s', $now ) . ' - ' . $now . ']]]' . PHP_EOL; + $debug .= '[[[' . $shift_start . ' - ' . $shift_start_time . ']]]' . PHP_EOL; + $debug .= '[[[' . $shift_end . ' - ' . $shift_end_time . ']]]' . PHP_EOL . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- check if this is the currently scheduled show --- + if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { + $shift['day'] = $day; + $current_show = $shift; + $expires = $shift_end_time - $now - 1; + if ( $expires > 3600 ) { + $expires = 3600; + } // cache for one hour max + set_transient( 'radio_station_current_show', $current_show, $expires ); + } + + } elseif ( isset( $current_show['split'] ) && $current_show['split'] ) { + + // --- recombine split current shift --- + $current_show['end'] = $shift['end']; + unset( $current_show['split'] ); + set_transient( 'radio_station_current_show', $current_show, $expires ); + + } elseif ( !isset( $next_show ) ) { + + // --- set next show transient --- + $shift['day'] = $day; + $next_show = $shift; + $shift_end_time = strtotime( $day . ' ' . $shift['end'] ); + $next_expires = $shift_end_time - $now - 1; + if ( $next_expires > ( $expires + 3600 ) ) { + $next_expires = $expires + 3600; + } + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + + } elseif ( isset( $next_show['split'] ) && $next_show['split'] ) { + + // --- recombine split next shift --- + $next_show['end'] = $shift['end']; + unset( $next_show['split'] ); + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + + } + } + } + + // --- get next show if we did not find a current one --- + if ( !isset( $next_show ) ) { + // --- pass calculated shifts with limit of 1 --- + $next_shows = radio_station_get_next_shows( 1, $show_shifts ); + if ( count( $next_shows ) > 0 ) { + $next_show = $next_shows[0]; + $shift_end_time = strtotime( $day . ' ' . $next_show['end'] ); + $next_expires = $shift_end_time - $now - 1; + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } + } + } + + // TODO: edge case where current show or next show is split + // ...but actually unfinished due to end of schedule week ? + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Show Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; + if ( isset( $current_show ) ) { + $debug .= "Current Show: " . print_r( $current_show, true ) . PHP_EOL; + } + if ( isset( $next_show ) ) { + $debug .= "Next Show: " . print_r( $next_show, true ) . PHP_EOL; + } + + $next_shows = radio_station_get_next_shows( 5, $show_shifts ); + $debug .= "Next 5 Shows: " . print_r( $next_shows, true ) . PHP_EOL; + + radio_station_debug( $debug ); + } + + // --- cache current schedule data --- + if ( isset( $current_show ) ) { + set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); + } + + // --- filter and return --- + $show_shifts = apply_filters( 'radio_station_current_schedule', $show_shifts ); + + return $show_shifts; +} + +// ---------------- +// Get Current Show +// ---------------- +// 2.3.0: added new get current show function +function radio_station_get_current_show() { + + // --- get cached current show value --- + $current_show = get_transient( 'radio_station_current_show' ); + + // --- if not set it has expired so recheck schedule --- + if ( !$current_show ) { + $schedule = radio_station_get_current_schedule(); + $current_show = get_transient( 'radio_station_current_show ' ); + } + + // --- filter and return --- + $current_show = apply_filters( 'radio_station_current_show', $current_show ); + + return $current_show; +} + +// ------------- +// Get Next Show +// ------------- +// 2.3.0: added new get next show function +function radio_station_get_next_show() { + + // --- get cached current show value --- + $next_show = get_transient( 'radio_station_next_show' ); + + // --- if not set it has expired so recheck schedule --- + if ( !$next_show ) { + $schedule = radio_station_get_current_schedule(); + $next_show = get_transient( 'radio_station_next_show' ); + } + + // --- filter and return --- + $next_show = apply_filters( 'radio_station_next_show', $next_show ); + + return $next_show; +} + +// -------------- +// Get Next Shows +// -------------- +// 2.3.0: added new get next shows function +function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { + + // --- get all show shifts --- + // (this check is needed to prevent an endless loop!) + if ( !$show_shifts ) { + $show_shifts = radio_station_get_current_schedule(); + } + + // --- loop (remaining) shifts to add show data --- + $next_shows = array(); + $current_split = false; + $now = strtotime( current_time( 'mysql' ) ); + $weekdays = radio_station_get_schedule_weekdays(); + foreach ( $weekdays as $day ) { + if ( isset( $show_shifts[$day] ) ) { + $shifts = $show_shifts[$day]; + foreach ( $shifts as $start => $shift ) { + + // --- get this shift start and end times --- + $shift_start = $day . ' ' . $shift['start']; + $shift_end = $day . ' ' . $shift['end']; + $shift_start_time = strtotime( $shift_start ); + $shift_end_time = strtotime( $shift_end ); + + // --- check if show has started --- + if ( $now < $shift_start_time ) { + + // --- dedupe for shifts split overnight --- + $skip = false; + if ( $current_split ) { + $skip = true; + $current_split = false; + } + if ( isset( $split ) && $split ) { + // --- recombine split shift --- + $show_index = count( $next_shows ) - 1; + $next_shows[$show_index]['end'] = $shift['end']; + unset( $next_shows[$show_index]['split'] ); + $skip = true; + $split = false; + } elseif ( isset( $shift['split'] ) && $shift['split'] ) { + $split = true; + } else { + $split = false; + } + + if ( !$skip ) { + + // --- add to next shows data --- + $next_shows[] = $shift; + + // --- return if we have reached limit --- + if ( count( $next_shows ) == $limit ) { + return $next_shows; + } + } + + } else { + + // TODO: test this with a split current shift + + // -- set flag for possibly split current shift --- + if ( $current_split ) { + // --- reset as in second part of split current shift --- + $current_split = false; + } elseif ( isset( $shift['split'] ) && $shift['split'] ) { + // --- flag as in first part of split current shift --- + $current_split = true; + } + } + } + } + } + + // --- filter and return --- + $next_shows = apply_filters( 'radio_station_next_shows', $next_shows, $limit, $show_shifts ); + + return $next_shows; +} + +// ----------------------- +// Get Blog Posts for Show +// ----------------------- +// 2.3.0: added show blog post data grabber +function radio_station_get_show_posts( $show_id = false, $args = array() ) { + return radio_station_get_show_data( 'posts', $show_id, $args ); +} + +// ---------------------- +// Get Playlists for Show +// ---------------------- +// 2.3.0: added show playlist data grabber +function radio_station_get_show_playlists( $show_id = false, $args = array() ) { + return radio_station_get_show_data( 'playlists', $show_id, $args ); +} + +// --------- +// Get Genre +// --------- +// 2.3.0: added genre data grabber +function radio_station_get_genre( $genre ) { + $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); + if ( !$term ) { + $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); + } + if ( !$term ) { + $term = get_term_by( 'id', $genre, RADIO_STATION_GENRES_SLUG ); + } + if ( !$term ) { + return false; + } + $genre[$term->name]['id'] = $term->term_id; + $genre[$term->name]['name'] = $term->name; + $genre[$term->name]['slug'] = $term->slug; + $genre[$term->name]['description'] = $term->description; + $genre[$term->name]['url'] = get_term_link( $term, RADIO_STATION_GENRES_SLUG ); + + return $genre; +} + +// ---------- +// Get Genres +// ---------- +// 2.3.0: added genres data grabber +function radio_station_get_genres( $args = false ) { + + $defaults = array( 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'orderby' => 'name', 'hide_empty' => true ); + if ( $args && is_array( $args ) ) { + foreach ( $args as $key => $value ) { + $defaults[$key] = $value; + } + } + $terms = get_terms( $defaults ); + $genres = array(); + if ( $terms ) { + foreach ( $terms as $term ) { + $genres[$term->name]['id'] = $term->term_id; + $genres[$term->name]['name'] = $term->name; + $genres[$term->name]['slug'] = $term->slug; + $genres[$term->name]['description'] = $term->description; + $genres[$term->name]['url'] = get_term_link( $term, RADIO_STATION_GENRES_SLUG ); + } + } + + // --- filter and return --- + $genres = apply_filters( 'radio_station_get_genres', $genres, $args ); + + return $genres; +} + +// ------------------- +// Get Shows for Genre +// ------------------- +// 2.3.0: added get shows for genre data grabber +function radio_station_get_genre_shows( $genre = false ) { + + if ( !$genre ) { + // --- get shows without a genre assigned --- + // ref: https://core.trac.wordpress.org/ticket/29181 + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'operator' => 'NOT EXISTS', + ), + ); + } else { + // --- get shows with specific genre assigned --- + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'slug', + 'terms' => $genre, + ), + ); + } + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'tax_query' => $tax_query, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'meta_key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', + ), + ), + ); + $args = apply_filters( 'radio_station_show_genres_query_args', $args, $genre ); + $shows = new WP_Query( $args ); + + return $shows; +} + +// ---------------------- +// Get Shows for Language +// ---------------------- +// 2.3.0: added get shows for language data grabber +function radio_station_get_language_shows( $language = false ) { + + if ( !$language ) { + // --- get shows without a language assigned --- + // ref: https://core.trac.wordpress.org/ticket/29181 + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'operator' => 'NOT EXISTS', + ), + ); + } else { + // --- get shows with specific language assigned --- + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'slug', + 'terms' => $language, + ), + ); + } + + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'tax_query' => $tax_query, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'meta_key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'meta_key' => 'show_active', + 'meta_value' => 'on', + 'compare' => '=', + ), + ), + ); + $args = apply_filters( 'radio_station_show_languages_query_args', $args, $language ); + $shows = new WP_Query( $args ); + + return $shows; +} + + +// ------------------- +// === Show Avatar === +// ------------------- + +// ------------------ +// Update Show Avatar +// ------------------ +// 2.3.0: trigger show avatar update when editing +add_action( 'replace_editor', 'radio_station_update_show_avatar', 10, 2 ); +function radio_station_update_show_avatar( $replace_editor, $post ) { + $show_id = $post->ID; + radio_station_get_show_avatar_id( $show_id ); + + return $replace_editor; +} + +// ------------------ +// Get Show Avatar ID +// ------------------ +// 2.3.0: added get show avatar ID with thumbnail update +// note: existing thumbnail (featured image) ID is duplicated to the show avatar ID, +// allowing for handling of Show Avatars and Featured Images separately. +function radio_station_get_show_avatar_id( $show_id ) { + + // --- get thumbnail and avatar ID --- + $avatar_id = get_post_meta( $show_id, 'show_avatar', true ); + + // --- check thumbnail to avatar updated switch --- + $updated = get_post_meta( $show_id, '_rs_image_updated', true ); + if ( !$updated ) { + if ( !$avatar_id ) { + $thumbnail_id = get_post_meta( $show_id, '_thumbnail_id', true ); + if ( $thumbnail_id ) { + // --- duplicate the existing thumbnail to avatar meta --- + $avatar_id = $thumbnail_id; + add_post_meta( $show_id, 'show_avatar', $avatar_id ); + } + } + // --- add a flag indicating image has been updated --- + add_post_meta( $show_id, '_rs_image_updated', true ); + } + + // --- filter and return --- + $avatar_id = apply_filters( 'radio_station_show_avatar_id', $avatar_id, $show_id ); + + return $avatar_id; +} + +// ------------------- +// Get Show Avatar URL +// ------------------- +// 2.3.0: added to get the show avatar URL +function radio_station_get_show_avatar_url( $show_id, $size = 'thumbnail' ) { + + // --- get avatar ID --- + $avatar_id = radio_station_get_show_avatar_id( $show_id ); + + // --- get the attachment image source --- + $avatar_url = false; + if ( $avatar_id ) { + $avatar_src = wp_get_attachment_image_src( $avatar_id, $size ); + $avatar_url = $avatar_src[0]; + } + + // --- filter and return --- + $avatar_url = apply_filters( 'radio_stattion_show_avatar_url', $avatar_url, $show_id ); + + return $avatar_url; +} + +// --------------- +// Get Show Avatar +// --------------- +// 2.3.0: added this function for getting show avatar tag +function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = array() ) { + + // --- get avatar ID --- + $avatar_id = radio_station_get_show_avatar_id( $show_id ); + + // --- get the attachment image tag --- + $avatar = false; + if ( $avatar_id ) { + $avatar = wp_get_attachment_image( $avatar_id, $size, false, $attr ); + } + + $avatar = apply_filters( 'radio_station_show_avatar', $avatar, $show_id ); + + return $avatar; +} + +// ---------------------- +// === Shift Checking === +// ---------------------- + +// ------------------------- +// Schedule Conflict Checker +// ------------------------- +// (checks all existing show shifts for schedule) +// 2.3.0: added show shift conflict checker +function radio_station_check_shifts( $all_shifts ) { + + $conflicts = array(); + if ( count( $all_shifts ) > 0 ) { + foreach ( $all_shifts as $day => $shifts ) { + + // --- get previous and next days for comparisons --- + $thisday = date( 'Y-m-d' ); + $prevday = date( 'Y-m-d', strtotime( $thisday ) - ( 24 * 60 * 60 ) ); + $nextday = date( 'Y-m-d', strtotime( $thisday ) + ( 24 * 60 * 60 ) ); + + // --- check for conflicts (overlaps) --- + $checked_shifts = array(); + $prev_shift = false; + foreach ( $shifts as $key => $shift ) { + + // --- reset shift switches --- + $set_shift = true; + $conflict = $disabled = false; + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $disabled = true; + } + + // --- account for midnight times --- + if ( ( '11:59:59 pm' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { + $start_time = strtotime( $nextday . ' 12:00 am' ); + } else { + $start_time = strtotime( $thisday . ' ' . $shift['start'] ); + } + if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { + $end_time = strtotime( $nextday . ' 12:00 am' ); + } else { + $end_time = strtotime( $thisday . ' ' . $shift['end'] ); + } + + if ( false != $prev_shift ) { + + // note: previous shift start and end times set in previous loop iteration + + // --- detect shift conflicts --- + // (and maybe *attempt* to fix them up) + if ( isset( $prev_start_time ) && ( $start_time == $prev_start_time ) ) { + if ( $shift['split'] || $prev_shift['split'] ) { + $conflict = 'overlap'; + if ( $shift['split'] && $prev_shift['split'] ) { + // need to compare start times on previous day + $data = $shift['shift']; + $prevdata = $prev_shift['shift']; + $real_start_time = strtotime( $prevday . ' ' . $data['real_start'] ); + $prev_real_start_time = strtotime( $prevday . ' ' . $prevdata['real_start'] ); + if ( $real_start_time > $prev_real_start_time ) { + // current shift started later (overwrite from midnight) + $set_shift = true; + } elseif ( $real_start_time == $prev_real_start_time ) { + $conflict = false; // do not duplicate, already recorded + // total overlap, check last updated post time + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; + } + } + } elseif ( $shift['split'] ) { + // the current shift has been split overnight + // assume previous shift is correct (ignore new shift time) + $set_shift = false; + } elseif ( $prev_shift['split'] ) { + // the previous shift has been split overnight + // so we will assume the new shift start is correct + // (overwrites previous shift from midnight key) + $set_shift = true; + } + } else { + $conflict = 'same_start'; + // we do not know which of these is correct + // no solution here, so check most recent last updated time + // we will assume (without certainty) most recent is correct + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; + } + } + } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { + + // --- set the previous shift end time to current shift start --- + $conflict = 'overlap'; + + // --- modify only if this shift is not disabled --- + if ( !$disabled ) { + $checked_shift[$prev_shift['start']]['end'] = $shift['start']; + $checked_shift[$prev_shift['start']]['trimmed'] = true; + } + + // --- conflict debug output --- + if ( RADIO_STATION_DEBUG && current_user_can( 'edit_shows' ) ) { + $debug = "(Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")"; + $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")"; + $debug .= "Shift: " . print_r( $shift, true ); + $debug .= "Previous Shift: " . print_r( $prev_shift, true ); + radio_station_debug( $debug ); + } + } + } + + // --- maybe store shift conflict data --- + if ( $conflict ) { + + // ---- set short shift time data --- + $shift_start = $shift['shift']['start_hour'] . ':' . $shift['shift']['start_min'] . $shift['shift']['start_meridian']; + $shift_end = $shift['shift']['end_hour'] . ':' . $shift['shift']['end_min'] . $shift['shift']['end_meridian']; + $prev_shift_start = $prev_shift['shift']['start_hour'] . ':' . $prev_shift['shift']['start_min'] . $prev_shift['shift']['start_meridian']; + $prev_shift_end = $prev_shift['shift']['end_hour'] . ':' . $prev_shift['shift']['end_min'] . $prev_shift['shift']['end_meridian']; + + // --- store conflict for this shift --- + $conflicts[$shift['show']][] = array( + 'show' => $shift['show'], + 'day' => $shift['shift']['day'], + 'start' => $shift_start, + 'end' => $shift_end, + 'disabled' => $disabled, + 'with_show' => $prev_shift['show'], + 'with_day' => $prev_shift['shift']['day'], + 'with_start' => $prev_shift_start, + 'with_end' => $prev_shift_end, + 'with_disabled' => $prev_disabled, + 'conflict' => $conflict, + 'duplicate' => false, + ); + + // --- store for previous shift only if a different show --- + if ( $shift['show'] != $prev_shift['show'] ) { + $conflicts[$prev_shift['show']][] = array( + 'show' => $prev_shift['show'], + 'day' => $prev_shift['shift']['day'], + 'start' => $prev_shift_start, + 'end' => $prev_shift_end, + 'disabled' => $prev_disabled, + 'with_show' => $shift['show'], + 'with_day' => $shift['shift']['day'], + 'with_start' => $shift_start, + 'with_end' => $shift_end, + 'with_disabled' => $disabled, + 'conflict' => $conflict, + 'duplicate' => true, + ); + } + } + + // --- set current shift to previous for next iteration --- + $prev_start_time = $start_time; + $prev_end_time = $end_time; + $prev_shift = $shift; + $prev_disabled = $disabled; + + // --- set the now checked shift data --- + // (...but only if not disabled!) + if ( $set_shift && !$disabled ) { + // --- no longer need shift and post updated times --- + unset( $shift['shift'] ); + unset( $shift['updated'] ); + if ( '00:00 am' == $shift['start'] ) { + $shift['start'] = '12:00 am'; + } + $checked_shifts[$shift['start']] = $shift; + } + + } + + // --- set checked shifts for day --- + $all_shifts[$day] = $checked_shifts; + } + } + + // --- check if any conflicts found --- + if ( count( $conflicts ) > 0 ) { + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Shift Conflict Data: " . print_r( $conflicts, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- save any conflicts found --- + update_option( 'radio_station_schedule_conflicts', $conflicts ); + + } else { + // --- clear conflicts data --- + delete_option( 'radio_station_schedule_conflicts' ); + } + + return $all_shifts; +} + +// ------------------ +// Show Shift Checker +// ------------------ +// (checks shift being saved against other shows) +function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { + + global $radio_station_data; + + // --- get all show shift times --- + if ( isset( $radio_station_data['all-shifts'] ) ) { + // --- get stored data --- + $all_shifts = $radio_station_data['all-shifts']; + } else { + // (with conflict checking off as we are doing that now) + $all_shifts = radio_station_get_show_shifts( false ); + + // --- store this data for efficiency --- + $radio_station_data['all-shifts'] = $all_shifts; + } + + // --- get shows to check against via context --- + $check_shifts = array(); + if ( 'all' == $context ) { + $check_shifts = $all_shifts; + } elseif ( 'shows' == $context ) { + // --- check only against other show shifts --- + foreach ( $all_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $start => $day_shift ) { + // --- ...so remove any shifts for this show --- + if ( $day_shift['show'] != $show_id ) { + $check_shifts[$day][$start] = $day_shift; + } + } + } + } + + // --- get shift start and end time --- + $shift_start_time = strtotime( 'next ' . $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian'] ); + $shift_end_time = strtotime( 'next ' . $shift['day'] . ' ' . $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian'] ); + if ( $shift_end_time < $shift_start_time ) { + $shift_end_time = $shift_end_time + 86400; + } + + // --- check for conflicts with other show shifts --- + $conflicts = false; + foreach ( $check_shifts as $day => $day_shifts ) { + if ( $day == $shift['day'] ) { + foreach ( $day_shifts as $i => $day_shift ) { + + // note: no need to adjust times for midnight as shifts are already split + $day_shift_start_time = strtotime( 'next ' . $day . ' ' . $day_shift['start'] ); + $day_shift_end_time = strtotime( 'next ' . $day . ' ' . $day_shift['end'] ); + + // --- ignore if this is the same shift we are checking --- + if ( $day_shift['shift'] != $shift ) { + + // if the new shift starts before existing shift but finishes after existing shift starts + // or new shift starts at the same time as the existing shift + // of the existing shift starts before the new shift and finishes after new shift starts + // ...then there is a shift overlap conflict + if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) + || ( $shift_start_time == $day_shift_start_time ) + || ( ( $day_shift_start_time < $shift_start_time ) && ( $day_shift_end_time > $shift_start_time ) ) ) { + if ( !$conflicts ) { + $conflicts = array(); + } + $conflicts[] = $day_shift; + } + } + } + } + } + + return $conflicts; +} + +// ------------------ +// New Shifts Checker +// ------------------ +// (checks show shifts for conflicts with same show) +function radio_station_check_new_shifts( $new_shifts ) { + + // ---debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "New Shifts: " . print_r( $new_shifts, true ); + radio_station_debug( $debug, false, 'shift-save.log' ); + } + + // --- double loop shifts to check against others --- + foreach ( $new_shifts as $i => $shift_a ) { + + $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; + $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; + + // ---debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Shift: " . $a_start . ' - ' . $a_end . PHP_EOL; + radio_station_debug( $debug, false, 'shift-save.log' ); + } + + // --- get shift A start and end times --- + $shift_a_start_time = strtotime( 'next ' . $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] ); + $shift_a_end_time = strtotime( 'next ' . $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] ); + if ( $shift_a_end_time < $shift_a_start_time ) { + $shift_a_end_time = $shift_a_end_time + 86400; + } + + foreach ( $new_shifts as $j => $shift_b ) { + if ( $i != $j ) { + + $b_start = $shift_b['day'] . ' ' . $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; + $b_end = $shift_b['day'] . ' ' . $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; + + if ( RADIO_STATION_DEBUG ) { + $debug = "with Shift: " . $b_start . ' - ' . $b_end . PHP_EOL; + radio_station_debug( $debug, false, 'show-shift-save.log' ); + } + + // --- get shift B start and end times --- + $shift_b_start_time = strtotime( 'next ' . $shift_b['day'] . ' ' . $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian'] ); + $shift_b_end_time = strtotime( 'next ' . $shift_b['day'] . ' ' . $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian'] ); + if ( $shift_b_end_time < $shift_b_start_time ) { + $shift_b_end_time = $shift_b_end_time + 86400; + } + + // --- compare shift A and B times --- + if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) + || ( $shift_a_start_time == $shift_b_start_time ) + || ( ( $shift_b_start_time < $shift_a_start_time ) && ( $shift_b_end_time > $shift_a_start_time ) ) ) { + + // --- maybe disable shift B --- + if ( ( 'yes' != $new_shifts[$i]['disabled'] ) + && ( 'yes' != $new_shifts[$j]['disabled'] ) ) { + + if ( RADIO_STATION_DEBUG ) { + $debug = "!Disabled!" . PHP_EOL; + radio_station_debug( $debug, false, 'show-shift-save.log' ); + } + + $new_shifts[$j]['disabled'] = 'yes'; + } + } + } + } + } + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Checked New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + return $new_shifts; +} + +// ------------------- +// Validate Shift Time +// ------------------- +// 2.3.0: added check for incomplete shift times +function radio_station_validate_shift( $shift ) { + + if ( '' == $shift['day'] ) { + $shift['disabled'] = 'yes'; + } + if ( ( '' == $shift['start_meridian'] ) || ( '' == $shift['end_meridian'] ) ) { + $shift['disabled'] = 'yes'; + } + if ( ( '' == $shift['start_hour'] ) || ( '' == $shift['end_hour'] ) ) { + $shift['disabled'] = 'yes'; + } + if ( '' == $shift['start_min'] ) { + $shift['start_min'] = '00'; + } + if ( '' == $shift['end_min'] ) { + $shift['end_min'] = '00'; + } + + return $shift; +} + + +// --------------------- +// === URL Functions === +// --------------------- + +// ----------------- +// Get Streaming URL +// ----------------- +// 2.3.0: added get streaming URL helper +function radio_station_get_stream_url() { + $streaming_url = ''; + $stream = radio_station_get_setting( 'streaming_url' ); + if ( $stream && ( '' != $stream ) ) { + $streaming_url = $stream; + } + $streaming_url = apply_filters( 'radio_station_stream_url', $streaming_url ); + + return $streaming_url; +} + +// --------------- +// Get Station URL +// --------------- +function radio_station_get_station_url() { + $station_url = ''; + $page_id = radio_station_get_setting( 'station_page' ); + if ( $page_id && ( '' != $page_id ) ) { + $station_url = get_permalink( $page_id ); + } + $station_url = apply_filters( 'radio_station_station_url', $station_url ); + + return $station_url; +} + +// ---------------------------- +// Get Master Schedule Page URL +// ---------------------------- +// 2.3.0: added get master schedule URL permalink +function radio_station_get_schedule_url() { + $schedule_url = ''; + $page_id = radio_station_get_setting( 'schedule_page' ); + if ( $page_id && ( '' != $page_id ) ) { + $schedule_url = get_permalink( $page_id ); + } + $schedule_url = apply_filters( 'radio_station_schedule_url', $schedule_url ); + + return $schedule_url; +} + +// ------------------------- +// Get Radio Station API URL +// ------------------------- +function radio_station_get_api_url() { + $routes = radio_station_get_setting( 'enable_data_routes' ); + $feeds = radio_station_get_setting( 'enable_data_feeds' ); + $rest_url = get_rest_url(); + $api_url = false; + if ( ( 'yes' == $routes ) && !empty( $rest_url ) ) { + $api_url = radio_station_get_route_url( '' ); + } elseif ( 'yes' == $feeds ) { + $api_url = radio_station_get_feed_url( 'radio' ); + } + $api_url = apply_filters( 'radio_station_api_url', $api_url ); + + return $api_url; +} + +// ------------- +// Get Route URL +// ------------- +function radio_station_get_route_url( $route ) { + global $radio_station_routes; + if ( isset( $radio_station_feeds[$route] ) ) { + return $radio_station_routes[$route]; + } + $base = apply_filters( 'radio_station_route_slug_base', 'base' ); + $route = apply_filters( 'radio_station_route_slug_' . $route, $route ); + if ( '' == $route ) { + $path = '/' . $base . '/'; + } elseif ( !$route ) { + return false; + } else { + $path = '/' . $base . '/' . $route . '/'; + } + $radio_station_routes[$route] = $route_url = get_rest_url( $path ); + + return $route_url; +} + +// ------------ +// Get Feed URL +// ------------ +function radio_station_get_feed_url( $feedname ) { + global $radio_station_feeds; + if ( isset( $radio_station_feeds[$feedname] ) ) { + return $radio_station_feeds[$feedname]; + } + $feedname = apply_filters( 'radio_station_feed_slug_' . $feedname, $feedname ); + if ( !$feedname ) { + return false; + } + $radio_station_feeds[$feedname] = $feed_url = get_feed_link( $feedname ); + + return $feed_url; +} + +// ---------------- +// Get Show RSS URL +// ---------------- +function radio_station_get_show_rss_url( $show_id ) { + // TODO: combine comments and full show content + $rss_url = get_post_comments_feed_link( $show_id ); + $rss_url = add_query_arg( 'withoutcomments', '1', $rss_url ); + + return $rss_url; +} + +// ------------------------- +// Get DJ / Host Profile URL +// ------------------------- +// 2.3.0: added to get DJ / Host profile permalink +function radio_station_get_host_url( $host_id ) { + $post_id = radio_station_get_profile_id( RADIO_STATION_HOST_SLUG, $host_id ); + if ( $post_id ) { + $host_url = get_permalink( $post_id ); + } else { + $host_url = get_author_posts_url( $host_id ); + } + $host_url = apply_filters( 'radio_station_host_url', $host_url, $host_id ); + + return $host_url; +} + +// ------------------------ +// Get Producer Profile URL +// ------------------------ +// 2.3.0: added to get Producer profile permalink +function radio_station_get_producer_url( $producer_id ) { + $post_id = radio_station_get_profile_id( RADIO_STATION_PRODUCER_SLUG, $producer_id ); + if ( $post_id ) { + $producer_url = get_permalink( $post_id ); + } else { + $producer_url = get_author_posts_url( $producer_id ); + } + $producer_url = apply_filters( 'radio_station_producer_url', $producer_url, $producer_id ); + + return $producer_url; +} + +// --------------- +// Get Upgrade URL +// --------------- +// 2.3.0: added to get Upgrade to Pro link +function radio_station_get_upgrade_url() { + // TODO: test Freemius upgrade to Pro URL + // ...maybe it is -addons instead of -pricing ??? + $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); + + return $upgrade_url; +} + +// ------------------------ +// Patreon Supporter Button +// ------------------------ +// 2.2.2: added simple patreon supporter image button +// 2.3.0: added Patreon page argument +// 2.3.0: moved from radio-station-admin.php +function radio_station_patreon_button( $page, $title = '' ) { + $image_url = plugins_url( 'images/patreon-button.jpg', RADIO_STATION_FILE ); + $button = ''; + $button .= ''; + $button .= ''; + + // 2.3.0: add button styling to footer + if ( is_admin() ) { + add_action( 'admin_footer', 'radio_station_patreon_button_styles' ); + } else { + add_action( 'wp_footer', 'radio_station_patreon_button_styles' ); + } + + // --- filter and return --- + $button = apply_filters( 'radio_station_patreon_button', $button, $page ); + return $button; +} + +// --------------------- +// Patreon Button Styles +// --------------------- +// 2.3.0: added separately in footer +function radio_station_patreon_button_styles() { + // 2.2.7: added button hover opacity + echo ''; +} + +// ------------------------ +// === Helper Functions === +// ------------------------ + +// -------------- +// Get Profile ID +// -------------- +// 2.3.0: added to get host or producer profile post ID +function radio_station_get_profile_id( $type, $user_id ) { + + global $radio_station_data; + + if ( isset( $radio_station_data[$type . '-' . $user_id] ) ) { + $post_id = $radio_station_data[$type . '-' . $user_id]; + return $post_id; + } + + // --- get the post ID(s) for the profile --- + global $wpdb; + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta + WHERE meta_key = '" . $type . "_user_id' AND meta_value = %d"; + $query = $wpdb->prepare( $query, $user_id ); + $results = $wpdb->get_results( $query, ARRAY_A ); + + // --- check for and return published profile ID --- + foreach ( $results as $result ) { + $query = "SELECT ID FROM " . $wpdb->prefix . "posts + WHERE post_status = 'publish' AND post_id = %d"; + $query = $wpdb->prepare( $query, $result['ID'] ); + $post_id = $wpdb->get_var( $query ); + if ( $post_id ) { + $radio_station_data[$type . '-' . $user_id] = $post_id; + + return $post_id; + } + } + + return false; +} + +// -------------------- +// Get Timezone Options +// -------------------- +// ref: (based on) https://stackoverflow.com/a/17355238/5240159 +function radio_station_get_timezone_options( $include_wp_timezone = false ) { + + // --- maybe get stored timezone options --- + $options = get_transient( 'radio-station-timezone-options' ); + if ( !$options ) { + + // --- set regions --- + $regions = array( + DateTimeZone::AFRICA => __( 'Africa', 'radio-station' ), + DateTimeZone::AMERICA => __( 'America', 'radio-station' ), + DateTimeZone::ASIA => __( 'Asia', 'radio-station' ), + DateTimeZone::ATLANTIC => __( 'Atlantic', 'radio-station' ), + DateTimeZone::AUSTRALIA => __( 'Australia', 'radio-station' ), + DateTimeZone::EUROPE => __( 'Europe', 'radio-station' ), + DateTimeZone::INDIAN => __( 'Indian', 'radio-station' ), + DateTimeZone::PACIFIC => __( 'Pacific', 'radio-station' ), + DateTimeZone::ANTARCTICA => __( 'Antarctica', 'radio-station' ), + ); + + // --- loop regions --- + foreach ( $regions as $region => $label ) { + + // --- option group by region --- + $options['*OPTGROUP*' . $region] = $label; + + $timezones = DateTimeZone::listIdentifiers( $region ); + $timezone_offsets = array(); + foreach ( $timezones as $timezone ) { + $datetimezone = new DateTimeZone( $timezone ); + $offset = $datetimezone->getOffset( new DateTime() ); + $timezone_offsets[$offset][] = $timezone; + } + ksort( $timezone_offsets ); + + foreach ( $timezone_offsets as $offset => $timezones ) { + foreach ( $timezones as $timezone ) { + $prefix = $offset < 0 ? '-' : '+'; + $hour = gmdate( 'H', abs( $offset ) ); + $minutes = gmdate( 'i', abs( $offset ) ); + // TODO: check for 15/45 minute timezones ? + if ( 30 == $minutes ) { + $halfhour = '.5'; + } elseif ( 0 == $minutes ) { + $halfhour = ''; + } else { + $halfhour = ':' . $minutes; + } + $code = radio_station_get_timezone_code( $timezone ); + $label = $code . ' (GMT' . $prefix . $hour . $halfhour . ') - '; + $timezone_split = explode( '/', $timezone ); + unset( $timezone_split[0] ); + $timezone_joined = implode( '/', $timezone_split ); + $label .= str_replace( '_', ' ', $timezone_joined ); + $options[$timezone] = $label; + } + } + } + $expiry = 7 * 24 * 60 * 60; + set_transient( 'radio-station-timezone-options', $options, $expiry ); + } + + // --- maybe add WordPress timezone (default) option --- + if ( $include_wp_timezone ) { + $wp_timezone = array( '' => __( 'WordPress Timezone', 'radio-station' ) ); + $options = array_merge( $wp_timezone, $options ); + } + + $options = apply_filters( 'radio_station_get_timezone_options', $options, $include_wp_timezone ); + + return $options; +} + +// ----------------- +// Get Timezone Code +// ----------------- +// note: this should only be used for display in the "now" +// (as the actual code to be used is based on location) +function radio_station_get_timezone_code( $timezone ) { + $date_time = new DateTime(); + $date_time->setTimeZone( new DateTimeZone( $timezone ) ); + + return $date_time->format( 'T' ); +} + +// ------------- +// Get Languages +// ------------- +function radio_station_get_languages() { + + // --- get all language translations --- + $translations = get_site_transient( 'available_translations' ); + if ( ( false === $translations ) || is_wp_error( $translations ) + || !isset( $translations['translations'] ) || empty( $translations['translations'] ) ) { + // --- fallback to language selection data --- + // (note this file is a minified from translations API result) + // http://api.wordpress.org/translations/core/1.0/ + $language_file = RADIO_STATION_DIR . '/languages/languages.json'; + if ( file_exists( $language_file ) ) { + $contents = file_get_contents( $language_file ); + $translations = json_decode( $contents, true ); + } + } + if ( isset( $translations['translations'] ) ) { + $translations = $translations['translations']; + } + + // --- merge in default language (en_US) --- + if ( is_array( $translations ) && ( count( $translations ) > 0 ) ) { + $trans_before = $trans_after = array(); + $found = false; + foreach ( $translations as $i => $translation ) { + if ( '' == $translation['language'] ) { + $found = true; + } + if ( !$found ) { + $trans_before[] = $translation; + } else { + $trans_after[] = $translation; + } + } + $trans_before[] = array( + 'language' => 'en_US', + 'native_name' => 'English (United States)', + 'english_name' => 'English (United States)', + ); + $translations = array_merge( $trans_before, $trans_after ); + } + + // --- filter and return --- + $translations = apply_filters( 'radio_station_get_languages', $translations ); + + return $translations; +} + +// -------------------- +// Get Language Options +// -------------------- +function radio_station_get_language_options( $include_wp_default = false ) { + + // --- maybe get stored timezone options --- + $languages = get_transient( 'radio-station-language-options' ); + if ( !$languages ) { + $languages = array(); + $translations = radio_station_get_languages(); + if ( $translations && is_array( $translations ) && ( count( $translations ) > 0 ) ) { + foreach ( $translations as $translation ) { + $lang = $translation['language']; + $languages[$lang] = $translation['native_name']; + } + } + } + + // --- maybe include WordPress default language --- + if ( $include_wp_default ) { + $wp_language = array( '', __( 'WordPress Setting', 'radio-station' ) ); + $languages = array_merge( $wp_language, $languages ); + } + + // --- filter and return --- + $languages = apply_filters( 'radio_station_get_language_options', $languages, $include_wp_default ); + + return $languages; +} + +// ------------ +// Get Language +// ------------ +function radio_station_get_language( $lang = false ) { + + // --- maybe get the main language --- + $main = false; + if ( !$lang ) { + $main = true; + $lang = radio_station_get_setting( 'radio_language' ); + if ( !$lang || ( '' == $lang ) ) { + $lang = get_option( 'WPLANG' ); + if ( !$lang ) { + $lang = 'en_US'; + } + } + } + + // --- get the specified language term --- + $term = get_term_by( 'slug', $lang, RADIO_STATION_LANGUAGES_SLUG ); + if ( !$term ) { + $term = get_term_by( 'name', $lang, RADIO_STATION_LANGUAGES_SLUG ); + } + if ( !$term ) { + $term = get_term_by( 'id', $lang, RADIO_STATION_LANGUAGES_SLUG ); + } + if ( $term ) { + $language = array( + 'id' => $term->term_id, + 'slug' => $term->slug, + 'name' => $term->name, + 'description' => $term->description, + 'url' => get_term_link( $term, RADIO_STATION_LANGUAGES_SLUG ), + ); + } else { + // --- set main language info --- + if ( $main ) { + $languages = radio_station_get_languages(); + foreach ( $languages as $i => $lang_data ) { + if ( $lang_data['language'] == $lang ) { + $language = array( + 'id' => 0, + 'slug' => $lang, + 'name' => $lang_data['native_name'], + 'description' => $lang_data['english_name'], + // TODO: set URL for main language and filter archive page results ? + // 'url' => '', + ); + } + } + } else { + $language = false; + } + } + + return $language; +} + +// ------------ +// Get Next Day +// ------------ +// 2.3.0: added get next day helper +function radio_station_get_next_day( $day ) { + // note: for internal use so not translated + $day = trim( $day ); + if ( 'Sunday' == $day ) { + return 'Monday'; + } + if ( 'Monday' == $day ) { + return 'Tuesday'; + } + if ( 'Tuesday' == $day ) { + return 'Wednesday'; + } + if ( 'Wednesday' == $day ) { + return 'Thursday'; + } + if ( 'Thursday' == $day ) { + return 'Friday'; + } + if ( 'Friday' == $day ) { + return 'Saturday'; + } + if ( 'Saturday' == $day ) { + return 'Sunday'; + } + + return ''; +} + +// ---------------- +// Get Previous Day +// ---------------- +// 2.3.0: added get previous day helper +function radio_station_get_previous_day( $day ) { + // note: for internal use so not translated + $day = trim( $day ); + if ( 'Sunday' == $day ) { + return 'Saturday'; + } + if ( 'Monday' == $day ) { + return 'Sunday'; + } + if ( 'Tuesday' == $day ) { + return 'Monday'; + } + if ( 'Wednesday' == $day ) { + return 'Tuesday'; + } + if ( 'Thursday' == $day ) { + return 'Wednesday'; + } + if ( 'Friday' == $day ) { + return 'Thursday'; + } + if ( 'Saturday' == $day ) { + return 'Friday'; + } + + return ''; +} + +// ------------- +// Get All Hours +// ------------- +function radio_station_get_hours( $format = 24 ) { + $hours = array(); + if ( 24 === (int) $format ) { + $hours = array( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ); + } elseif ( 12 === (int) $format ) { + $hours = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ); + } + return $hours; +} + +// --------------------------- +// Convert Hour to Time Format +// --------------------------- +// (note: used with suffix for on-the-hour times) +// 2.3.0: standalone function via master-schedule-default.php +// 2.3.0: optionally add suffix for both time formats +function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { + + $hour = intval( $hour ); + + // 2.3.0: handle next and previous hours (over 24 or below 0) + if ( $hour < 0 ) { + while ( $hour < 0 ) { + $hour = $hour + 24; + } + } + if ( $hour > 24 ) { + while ( $hour > 24 ) { + $hour = $hour - 24; + } + } + + if ( 24 === (int) $timeformat ) { + // --- 24 hour time format --- + if ( 24 == $hour ) { + $hour = '00'; + } elseif ( $hour < 10 ) { + $hour = '0' . $hour; + } + if ( $suffix ) { + $hour .= ':00'; + } + } elseif ( 12 === (int) $timeformat ) { + // --- 12 hour time format --- + // 2.2.7: added meridiem translations + if ( ( $hour === 0 ) || ( 24 === $hour ) ) { + // midnight + $hour = '12'; + if ( $suffix ) { + $hour .= ' ' . radio_station_translate_meridiem( 'am' ); + } + } elseif ( $hour < 12 ) { + // morning + if ( $suffix ) { + $hour .= ' ' . radio_station_translate_meridiem( 'am' ); + } + } elseif ( 12 === $hour ) { + // noon + if ( $suffix ) { + $hour .= ' ' . radio_station_translate_meridiem( 'pm' ); + } + } elseif ( $hour > 12 ) { + // after-noon + $hour = $hour - 12; + if ( $suffix ) { + $hour .= ' ' . radio_station_translate_meridiem( 'pm' ); + } + } + } + + return $hour; +} + +// ---------------------------- +// Convert Shift to Time Format +// ---------------------------- +// 2.3.0: added to convert shift time to 24 hours (or back) +function radio_station_convert_shift_time( $time, $timeformat ) { + $timestamp = strtotime( date( 'l ' . $time ) ); + if ( 12 == (int) $timeformat ) { + $time = date( 'h:i a', $timestamp ); + str_replace( 'am', radio_station_translate_meridiem( 'am' ), $time ); + str_replace( 'pm', radio_station_translate_meridiem( 'pm' ), $time ); + } elseif ( 24 == (int) $timeformat ) { + $time = date( 'H:i', $timestamp ); + } + + return $time; +} + +// --------------------- +// Get Schedule Weekdays +// --------------------- +// note: no translations here because used internally for sorting +// 2.3.0: added to get schedule weekdays from start of week +function radio_station_get_schedule_weekdays() { + + // --- get start of the week --- + $weekstart = get_option( 'start_of_week' ); + $weekstart = apply_filters( 'radio_station_schedule_weekday_start', $weekstart ); + + // --- loop weekdays and reorder from start day --- + $weekdays = array( + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ); + $start = $before = $after = array(); + foreach ( $weekdays as $i => $weekday ) { + if ( $i == $weekstart ) { + $start[] = $weekday; + } elseif ( $i > $weekstart ) { + $after[] = $weekday; + } elseif ( $i < $weekstart ) { + $before[] = $weekday; + } + } + + // --- put the days before the start day at the end --- + $weekdays = array_merge( $start, $after ); + $weekdays = array_merge( $weekdays, $before ); + + return $weekdays; +} + +// ------------ +// Trim Excerpt +// ------------ +// (copy of wp_trim_excerpt) +function radio_station_trim_excerpt( $content ) { + + $raw_content = $content; + $content = strip_shortcodes( $content ); + if ( function_exists( 'excerpt_remove_blocks' ) ) { + $content = excerpt_remove_blocks( $content ); + } + $content = apply_filters( 'the_content', $content ); + $content = str_replace( ']]>', ']]>', $content ); + + $excerpt_length = (int) apply_filters( 'radio_station_excerpt_length', 35 ); + $excerpt_more = apply_filters( 'radio_station_excerpt_more', ' […]' ); + $excerpt = wp_trim_words( $content, $excerpt_length, $excerpt_more ); + + return apply_filters( 'radio_station_trim_excerpt', $excerpt, $raw_content ); +} + +// -------------- +// Shorten String +// -------------- +// (shorten a string to a set number of words) +function radio_station_shorten_string( $string, $limit ) { + + $shortened = $string; + $array = explode( ' ', $string ); + + if ( count( $array ) <= $limit ) { + // --- already at or under the limit --- + $shortened = $string; + } else { + // --- over the word limit so trim --- + array_splice( $array, $limit ); + $shortened = implode( ' ', $array ) . ' ...'; + } + + return $shortened; +} + + +// -------------------- +// === Translations === +// -------------------- + +// ----------------- +// Translate Weekday +// ----------------- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate weekdays +// 2.3.0: allow for abbreviated and long version changeovers +function radio_station_translate_weekday( $weekday, $short = false ) { + + // 2.3.0: return empty for empty select option + if ( empty( $weekday ) ) { + return ''; + } + + global $wp_locale; + + if ( $short ) { + + // --- translate abbreviated weekday --- + if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); + } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); + } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); + } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); + } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); + } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); + } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); + } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { + // 2.3.0: add support for numeric weekday value + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $weekday ) ); + } + + } else { + + // --- translate full weekday --- + // 2.2.7: fix to typo for Tuesday + // 2.3.0: fix to typo for Thursday (jeez!) + if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 0 ); + } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 1 ); + } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 2 ); + } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 3 ); + } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 4 ); + } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 5 ); + } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { + $weekday = $wp_locale->get_weekday( 6 ); + } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { + // 2.3.0: add support for numeric weekday value + $weekday = $wp_locale->get_weekday( $weekday ); + } + + } + + return $weekday; +} + +// --------------- +// Translate Month +// --------------- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate months +function radio_station_translate_month( $month, $short = false ) { + + // 2.3.0: return empty for empty select option + if ( empty( $month ) ) { + return ''; + } + + global $wp_locale; + if ( $short ) { + + // --- translate abbreviated month --- + if ( 'Jan' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); + } elseif ( 'Feb' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); + } elseif ( 'Mar' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); + } elseif ( 'Apr' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); + } elseif ( 'Jun' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); + } elseif ( 'Jul' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); + } elseif ( 'Aug' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); + } elseif ( 'Sep' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); + } elseif ( 'Oct' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); + } elseif ( 'Nov' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); + } elseif ( 'Dec' === $month ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); + } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { + // 2.3.0: add support for numeric month value + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( $month ) ); + } + } else { + + // --- translate full month --- + if ( 'January' === $month ) { + $month = $wp_locale->get_month( 1 ); + } elseif ( 'February' === $month ) { + $month = $wp_locale->get_month( 2 ); + } elseif ( 'March' === $month ) { + $month = $wp_locale->get_month( 3 ); + } elseif ( 'April' === $month ) { + $month = $wp_locale->get_month( 4 ); + } elseif ( 'May' === $month ) { + $month = $wp_locale->get_month( 5 ); + } elseif ( 'June' === $month ) { + $month = $wp_locale->get_month( 6 ); + } elseif ( 'July' === $month ) { + $month = $wp_locale->get_month( 7 ); + } elseif ( 'August' === $month ) { + $month = $wp_locale->get_month( 8 ); + } elseif ( 'September' === $month ) { + $month = $wp_locale->get_month( 9 ); + } elseif ( 'October' === $month ) { + $month = $wp_locale->get_month( 10 ); + } elseif ( 'November' === $month ) { + $month = $wp_locale->get_month( 11 ); + } elseif ( 'December' === $month ) { + $month = $wp_locale->get_month( 12 ); + } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { + // 2.3.0: add support for numeric month value + $month = $wp_locale->get_month( $month ); + } + + } + + return $month; +} + +// ------------------ +// Translate Meridiem +// ------------------ +// 2.2.7: added meridiem translation function +function radio_station_translate_meridiem( $meridiem ) { + global $wp_locale; + + return $wp_locale->get_meridiem( $meridiem ); +} + + +// ------------------------ +// === Legacy Functions === +// ------------------------ + /* * Support functions for shortcodes and widgets * Author: Nikki Blight * Since: 2.0.14 */ +// (still used in legacy shortcodes, templates and widgets) +// note: to be gradually deprecated as they are replaced + // --- get only the currently relevant schedule --- +// (used in DJ Widget) function radio_station_current_schedule( $scheds = array() ) { - $now = current_time( 'timestamp' ); + $now = current_time( 'timestamp' ); $current = array(); foreach ( $scheds as $sched ) { - if ( date( 'l', $now ) !== $sched['day'] ) { - continue; - } + // 2.3.0: added check is shift is disabled + if ( !isset( $sched['disabled'] ) || ( 'yes' != $sched['disabled'] ) ) { - $start = strtotime( date( 'Y-m-d', $now ) . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] ); + if ( date( 'l', $now ) !== $sched['day'] ) { + continue; + } - if ( 'pm' === $sched['start_meridian'] && 'am' === $sched['end_meridian'] ) { - // check for shows that run overnight into the next morning - $end = strtotime( date( 'Y-m-d', ( $now + 86400 ) ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); - } else { - $end = strtotime( date( 'Y-m-d', $now ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); - } + $start = strtotime( date( 'Y-m-d', $now ) . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] ); - // a show cannot end before it begins... if it does, it ends the following day. - if ( $end <= $start ) { - $end = $end + 86400; - } + if ( 'pm' === $sched['start_meridian'] && 'am' === $sched['end_meridian'] ) { + // check for shows that run overnight into the next morning + $end = strtotime( date( 'Y-m-d', ( $now + 86400 ) ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); + } else { + $end = strtotime( date( 'Y-m-d', $now ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); + } - // compare to the current timestamp - if ( ( $start <= $now ) && ( $end >= $now ) ) { - $current = $sched; - } else { - continue; + // a show cannot end before it begins... if it does, it ends the following day. + if ( $end <= $start ) { + $end = $end + 86400; + } + + // compare to the current timestamp + if ( ( $start <= $now ) && ( $end >= $now ) ) { + $current = $sched; + } else { + continue; + } } } @@ -49,7 +2675,7 @@ function radio_station_convert_time( $time = array() ) { return false; } - $now = strtotime( current_time( 'mysql' ) ); + $now = strtotime( current_time( 'mysql' ) ); $cur_date = date( 'Y-m-d', $now ); $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow @@ -75,6 +2701,7 @@ function radio_station_convert_time( $time = array() ) { } // --- convert a shift to 24 hour time for display --- +// TODO: replace all use of this function with new radio_station_convert_shift_time function radio_station_convert_schedule_to_24hour( $sched = array() ) { if ( empty( $sched ) ) { @@ -105,6 +2732,7 @@ function radio_station_convert_schedule_to_24hour( $sched = array() ) { } // --- fetch the current DJ(s) on-air -- +// used in DJ Widget, dj-widget shortcode function radio_station_dj_get_current() { // --- first check to see if there are any shift overrides --- @@ -120,7 +2748,7 @@ function radio_station_dj_get_current() { global $wpdb; // --- get the current time and day --- - $now = strtotime( current_time( 'mysql' ) ); + $now = strtotime( current_time( 'mysql' ) ); $cur_day = date( 'l', $now ); // --- query for active shows only --- @@ -143,35 +2771,39 @@ function radio_station_dj_get_current() { $shift->meta_value = maybe_unserialize( $shift->meta_value ); // if a show has no shifts, unserialize() will return false instead of an empty array... fix that to prevent errors in the foreach loop. - if ( ! is_array( $shift->meta_value ) ) { + if ( !is_array( $shift->meta_value ) ) { $shift->meta_value = array(); } foreach ( $shift->meta_value as $time ) { - // check if the shift is for the current day. If it's not, skip it - if ( $time['day'] === $cur_day ) { - // format the time so that it is more easily compared - $time = radio_station_convert_time( $time ); + // 2.3.0: added check if shift has been disabled + if ( !isset( $time['disabled'] ) || ( 'yes' != $time['disabled'] ) ) { - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; + // check if the shift is for the current day. If it's not, skip it + if ( $time['day'] === $cur_day ) { + // format the time so that it is more easily compared + $time = radio_station_convert_time( $time ); + + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } } - } - // we need to make a special allowance for shows that run from one day into the next - if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { + // we need to make a special allowance for shows that run from one day into the next + if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { - $time = radio_station_convert_time( $time ); - // because station_convert_time assumes that the show STARTS on the current day, - // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps - $time['start_timestamp'] = $time['start_timestamp'] - 86400; - $time['end_timestamp'] = $time['end_timestamp'] - 86400; + $time = radio_station_convert_time( $time ); + // because station_convert_time assumes that the show STARTS on the current day, + // when, in this case, it ends on the current day, we have to subtract 1 day from the timestamps + $time['start_timestamp'] = $time['start_timestamp'] - 86400; + $time['end_timestamp'] = $time['end_timestamp'] - 86400; - // compare to the current timestamp - if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $show_ids[] = $shift->post_id; + // compare to the current timestamp + if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { + $show_ids[] = $shift->post_id; + } } } } @@ -187,19 +2819,20 @@ function radio_station_dj_get_current() { } // --- get the next DJ or DJs scheduled to be on air based on the current time --- +// used in DJ Upcoming Widget, dj-coming-up-widget shortcode function radio_station_dj_get_next( $limit = 1 ) { global $wpdb; // get the various times/dates we need - $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); + $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); $cur_day_num = date( 'N', strtotime( current_time( 'mysql' ) ) ); - $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); - $now = strtotime( current_time( 'mysql' ) ); - $shows = array(); + $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); + $now = strtotime( current_time( 'mysql' ) ); + $shows = array(); // first check to see if there are any shift overrides - $check = radio_station_master_get_overrides(); + $check = radio_station_master_get_overrides(); $overrides = array(); if ( $check ) { @@ -212,13 +2845,13 @@ function radio_station_dj_get_next( $limit = 1 ) { if ( ( $p['sched']['start_timestamp'] <= $now ) && ( $p['sched']['end_timestamp'] >= $now ) ) { //show is on now, so we don't need it listed under upcoming //$overrides[$p['sched']['start_timestamp'].'|'.$p['sched']['end_timestamp']] = $p; - unset( $check[ $i ] ); + unset( $check[$i] ); } elseif ( ( $p['sched']['start_timestamp'] > $now ) && ( $p['sched']['end_timestamp'] > $now ) ) { // show is on later today - $overrides[ $p['sched']['start_timestamp'] . '|' . $p['sched']['end_timestamp'] ] = $p; + $overrides[$p['sched']['start_timestamp'] . '|' . $p['sched']['end_timestamp']] = $p; } else { // show is already over and we don't need it - unset( $check[ $i ] ); + unset( $check[$i] ); } } @@ -242,7 +2875,7 @@ function radio_station_dj_get_next( $limit = 1 ) { )" ); - $show_ids = array(); + $show_ids = $encore_ids = array(); foreach ( $show_shifts as $shift ) { @@ -250,12 +2883,12 @@ function radio_station_dj_get_next( $limit = 1 ) { // if a show has no shifts, unserialize() will return false instead of an empty array... // fix that to prevent errors in the foreach loop. - if ( ! is_array( $shift->meta_value ) ) { + if ( !is_array( $shift->meta_value ) ) { $shift->meta_value = array(); } $encore_ids = array(); - $days = array( + $days = array( 'Sunday' => 7, 'Monday' => 1, 'Tuesday' => 2, @@ -266,27 +2899,31 @@ function radio_station_dj_get_next( $limit = 1 ) { ); foreach ( $shift->meta_value as $time ) { - if ( $time['day'] === $cur_day ) { - $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ); - $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ); - } else { - if ( $cur_day_num < $days[ $time['day'] ] ) { - $day_diff = $days[ $time['day'] ] - $cur_day_num; - $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); - $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + // 2.3.0: added check is shift is disabled + if ( !isset( $time['disabled'] ) || ( 'yes' != $time['disabled'] ) ) { + + if ( $time['day'] === $cur_day ) { + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ); } else { - $day_diff = $cur_day_num + $days[ $time['day'] ] + 1; - $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); - $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + if ( $cur_day_num < $days[$time['day']] ) { + $day_diff = $days[$time['day']] - $cur_day_num; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } else { + $day_diff = $cur_day_num + $days[$time['day']] + 1; + $cur_shift = strtotime( $cur_date . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ':00 ' . $time['start_meridian'] ) + ( $day_diff * 86400 ); + $end_shift = strtotime( $cur_date . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ':00 ' . $time['end_meridian'] ) + ( $day_diff * 86400 ); + } } - } - // if the shift occurs later than the current time, we want it - if ( $cur_shift >= $now ) { - $show_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; - // 2.2.4: set encore ID array to pass back - if ( isset( $time['encore'] ) && 'on' === $time['encore'] ) { - $encore_ids[ $cur_shift . '|' . $end_shift ] = $shift->post_id; + // if the shift occurs later than the current time, we want it + if ( $cur_shift >= $now ) { + $show_ids[$cur_shift . '|' . $end_shift] = $shift->post_id; + // 2.2.4: set encore ID array to pass back + if ( isset( $time['encore'] ) && 'on' === $time['encore'] ) { + $encore_ids[$cur_shift . '|' . $end_shift] = $shift->post_id; + } } } } @@ -303,22 +2940,23 @@ function radio_station_dj_get_next( $limit = 1 ) { if ( $otime[0] <= $stime[1] ) { //check if an override starts before a show ends if ( $otime[1] > $stime[0] ) { //and it ends after the show begins (so we're not pulling overrides that are already over based on current time) - unset( $show_ids[ $s ] ); // this show is overriden... drop it + unset( $show_ids[$s] ); // this show is overriden... drop it } } } } // Fallback function if the PHP Server does not have the array_replace function (i.e. prior to PHP 5.3) - if ( ! function_exists( 'array_replace' ) ) { + if ( !function_exists( 'array_replace' ) ) { function array_replace() { $array = array(); - $n = func_num_args(); + $n = func_num_args(); - while ( $n-- > 0 ) { + while ( $n -- > 0 ) { $array += func_get_arg( $n ); } + return $array; } } @@ -331,11 +2969,11 @@ function array_replace() { // fetch detailed show information foreach ( $combined as $timestamp => $id ) { - if ( ! is_array( $id ) ) { - $shows['all'][ $timestamp ] = get_post( $id ); + if ( !is_array( $id ) ) { + $shows['all'][$timestamp] = get_post( $id ); } else { - $id['type'] = 'override'; - $shows['all'][ $timestamp ] = $id; + $id['type'] = 'override'; + $shows['all'][$timestamp] = $id; } } $shows['type'] = 'shows'; @@ -347,132 +2985,94 @@ function array_replace() { } // --- get the most recently entered song --- -function radio_station_myplaylist_get_now_playing() { +// (used in DJ Widget, dj-widget shortcode, Playlist Widget, now-playing shortcode) +function radio_station_get_now_playing() { + + // --- get the currently playing show --- + // 2.3.0: added to prevent playlist/show mismatch! + $current_show = radio_station_get_current_show(); + if ( !$current_show ) { + return false; + } + if ( isset( $current_show['override'] ) && $current_show['override'] ) { + $playlist = apply_filters( 'radio_station_override_now_playing', false, $current_show['override'] ); + return $playlist; + } + $show_id = $current_show['show']; - // grab the most recent playlist + // --- grab the most recent playlist for the current show --- $args = array( 'numberposts' => 1, 'offset' => 0, 'orderby' => 'post_date', 'order' => 'DESC', - 'post_type' => 'playlist', + 'post_type' => RADIO_STATION_PLAYLIST_SLUG, 'post_status' => 'publish', + 'meta_query' => array( + array( + 'meta_key' => 'playlist_show_id', + 'meta_value' => $show_id, + 'compare' => '=', + ), + ), ); + $playlist_post = get_posts( $args ); - $playlist = get_posts( $args ); + $playlist = false; + if ( $playlist_post ) { - // if there are no playlists saved, return nothing - if ( ! $playlist ) { - return false; - } + // --- fetch the tracks for the playlist --- + // 2.3.0: added singular argument to true + $songs = get_post_meta( $playlist_post[0]->ID, 'playlist', true ); - // fetch the tracks for each playlist from the wp_postmeta table - $songs = get_post_meta( $playlist[0]->ID, 'playlist' ); + if ( !empty( $songs ) ) { - if ( ! empty( $songs[0] ) ) { - // removed any entries that are marked as 'queued' - foreach ( $songs[0] as $i => $entry ) { - if ( 'queued' === $entry['playlist_entry_status'] ) { - unset( $songs[0][ $i ] ); + // --- aplit off tracks marked as queued --- + $queued = array(); + foreach ( $songs as $i => $song ) { + if ( 'queued' == $songs['playlist_entry_status'] ) { + $queued[] = $song; + unset( $songs[$i] ); + } } - } - - // pop the last track off the list for display - $most_recent = array_pop( $songs[0] ); - - // get the permalink for the playlist so it can be displayed - $most_recent['playlist_permalink'] = get_permalink( $playlist[0]->ID ); - - return $most_recent; - } else { - return false; - } -} -// --- fetch all blog posts for a show's DJs --- -function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { - - global $wpdb; - - // do not return anything if we don't have a show - if ( ! $show_id ) { - return false; - } - - $fetch_posts = $wpdb->get_results( - $wpdb->prepare( - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = 'post_showblog_id' AND - meta.meta_value = %d", - $show_id - ) - ); - - $blog_array = array(); - $blogposts = array(); - foreach ( $fetch_posts as $f ) { - $blog_array[] = $f->post_id; - } - - if ( $blog_array ) { - - // 2.2.8: fix to implode blog array to string - $blogposts = $wpdb->get_results( - $wpdb->prepare( - "SELECT posts.ID, posts.post_title - FROM {$wpdb->posts} AS posts - WHERE posts.ID IN(%s) AND - posts.post_status = 'publish' - ORDER BY posts.post_date DESC - LIMIT %d", - implode( ',', $blog_array ), - $limit - ) - ); - } + // --- get the track list for display --- + // 2.3.0: return full playlist instead of just last song + $playlist = array( 'tracks' => $songs ); + $playlist['queued'] = $queued; + $playlist['latest'] = array_pop( $songs ); - $output = ''; + // --- add show and playlist data --- + // 2.3.0: add IDs and URLs instead of just playlist URL + $playlist['show'] = $show_id; + $playlist['show_url'] = get_permalink( $show_id ); + $playlist['playlist'] = $playlist[0]->ID; + $playlist['playlist_url'] = get_permalink( $playlist[0]->ID ); - $output .= '
    '; - $output .= '

    ' . $title . '

    '; - $output .= ''; - $output .= '
    '; - - // if the blog archive page has been created, add a link to the archive for this show - $page = $wpdb->get_results( - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = '_wp_page_template' AND - meta.meta_value = 'show-blog-archive-template.php' - LIMIT 1" - ); - - if ( $page ) { - $blog_archive = get_permalink( $page[0]->post_id ); - $params = array( 'show_id' => $show_id ); - $blog_archive = add_query_arg( $params, $blog_archive ); - $output .= '' . __( 'More Blog Posts', 'radio-station' ) . ''; - } + // --- filter and teturn tracks --- + $playlist = apply_filters( 'radio_station_show_now_playing', $playlist, $show_id ); - return $output; + return $playlist; } // --- get any schedule overrides for today's date --- +// (used in radio_station_dj_get_current, radio_station_dj_get_next) // If currenthour is true, only overrides that are in effect NOW will be returned -function radio_station_master_get_overrides( $currenthour = false ) { +// 2.3.0: add date argument to allow getting specific data overrides +function radio_station_master_get_overrides( $currenthour = false, $date = false ) { global $wpdb; - $now = strtotime( current_time( 'mysql' ) ); - $date = date( 'Y-m-d', $now ); - $sql_date = $wpdb->esc_like( $date ); - $sql_date = '%' . $sql_date . '%'; + $now = strtotime( current_time( 'mysql' ) ); + // 2.3.0: check if date argument supplied + if ( !$date ) { + $date = date( 'Y-m-d', $now ); + } + $sql_date = $wpdb->esc_like( $date ); + $sql_date = '%' . $sql_date . '%'; $show_shifts = $wpdb->get_results( $wpdb->prepare( "SELECT meta.post_id @@ -488,19 +3088,17 @@ function radio_station_master_get_overrides( $currenthour = false ) { foreach ( $show_shifts as $shift ) { $next_sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); - $time = $next_sched[0]; + $time = $next_sched[0]; if ( $currenthour ) { - // convert to 24 hour time - $check = array(); + // --- convert to 24 hour time --- $check = $time; - $time = radio_station_convert_time( $time ); - // compare to the current timestamp + // --- compare to the current timestamp --- if ( ( $time['start_timestamp'] <= $now ) && ( $time['end_timestamp'] >= $now ) ) { - $title = get_the_title( $shift->post_id ); + $title = get_the_title( $shift->post_id ); $scheds[] = array( 'post_id' => $shift->post_id, 'title' => $title, @@ -510,8 +3108,8 @@ function radio_station_master_get_overrides( $currenthour = false ) { continue; } } else { - $title = get_the_title( $shift->post_id ); - $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); + $title = get_the_title( $shift->post_id ); + $sched = get_post_meta( $shift->post_id, 'show_override_sched', false ); $scheds[] = array( 'post_id' => $shift->post_id, 'title' => $title, @@ -524,130 +3122,78 @@ function radio_station_master_get_overrides( $currenthour = false ) { return $scheds; } -// --- shorten a string to a set number of words --- -function radio_station_shorten_string( $string, $limit ) { +// --- fetch all blog posts for a show's DJs --- +// [Deprecated] replaced and no longer used +function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = '', $limit = 10 ) { - $shortened = $string; // just in case of a problem + global $wpdb; - $array = explode( ' ', $string ); - if ( count( $array ) <= $limit ) { - // already at or under the limit - $shortened = $string; - } else { - array_splice( $array, $limit ); - $shortened = implode( ' ', $array ) . ' ...'; + // do not return anything if we don't have a show + if ( !$show_id ) { + return false; } - return $shortened; -} -// --- translate weekday --- -// important note: translated individually as cannot translate a variable -// 2.2.7: use wp locale class to translate weekdays -function radio_station_translate_weekday( $weekday, $short = false ) { - global $wp_locale; - if ( $short ) { - if ( 'Sun' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); - } elseif ( 'Mon' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); - } elseif ( 'Tue' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); - } elseif ( 'Wed' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); - } elseif ( 'Thu' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); - } elseif ( 'Fri' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); - } elseif ( 'Sat' === $weekday ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); - } - } else { - // 2.2.7: fix to typo for Tuesday - if ( 'Sunday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 0 ); - } elseif ( 'Monday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 1 ); - } elseif ( 'Tuesday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 2 ); - } elseif ( 'Wednesday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 3 ); - } elseif ( 'Thurday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 4 ); - } elseif ( 'Friday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 5 ); - } elseif ( 'Saturday' === $weekday ) { - $weekday = $wp_locale->get_weekday( 6 ); - } + $fetch_posts = $wpdb->get_results( + $wpdb->prepare( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = 'post_showblog_id' AND + meta.meta_value = %d", + $show_id + ) + ); + + $blog_array = array(); + $blogposts = array(); + foreach ( $fetch_posts as $f ) { + $blog_array[] = $f->post_id; } - return $weekday; -} -// --- translate month --- -// important note: translated individually as cannot translate a variable -// 2.2.7: use wp locale class to translate months -function radio_station_translate_month( $month, $short = false ) { - global $wp_locale; - if ( $short ) { - if ( 'Jan' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); - } elseif ( 'Feb' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); - } elseif ( 'Mar' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); - } elseif ( 'Apr' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); - } elseif ( 'May' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); - } elseif ( 'Jun' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); - } elseif ( 'Jul' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); - } elseif ( 'Aug' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); - } elseif ( 'Sep' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); - } elseif ( 'Oct' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); - } elseif ( 'Nov' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); - } elseif ( 'Dec' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); - } - } else { - if ( 'January' === $month ) { - $month = $wp_locale->get_month( 1 ); - } elseif ( 'February' === $month ) { - $month = $wp_locale->get_month( 2 ); - } elseif ( 'March' === $month ) { - $month = $wp_locale->get_month( 3 ); - } elseif ( 'April' === $month ) { - $month = $wp_locale->get_month( 4 ); - } elseif ( 'May' === $month ) { - $month = $wp_locale->get_month( 5 ); - } elseif ( 'June' === $month ) { - $month = $wp_locale->get_month( 6 ); - } elseif ( 'July' === $month ) { - $month = $wp_locale->get_month( 7 ); - } elseif ( 'August' === $month ) { - $month = $wp_locale->get_month( 8 ); - } elseif ( 'September' === $month ) { - $month = $wp_locale->get_month( 9 ); - } elseif ( 'October' === $month ) { - $month = $wp_locale->get_month( 10 ); - } elseif ( 'November' === $month ) { - $month = $wp_locale->get_month( 11 ); - } elseif ( 'December' === $month ) { - $month = $wp_locale->get_month( 12 ); + if ( $blog_array ) { + + // 2.2.8: fix to implode blog array to string + // 2.3.0: allow for getting without limit + $query = $wpdb->prepare( + "SELECT posts.ID, posts.post_title + FROM {$wpdb->posts} AS posts + WHERE posts.ID IN(%s) AND + posts.post_status = 'publish' + ORDER BY posts.post_date DESC", + implode( ',', $blog_array ) + ); + if ( $limit > 0 ) { + $query .= $wpdb->prepare( " LIMIT %d", $limit ); } + $blogposts = $wpdb->get_results( $query ); } - return $month; -} -// ------------------ -// Translate Meridiem -// ------------------ -// 2.2.7: added meridiem translation function -function radio_station_translate_meridiem( $meridiem ) { - global $wp_locale; - return $wp_locale->get_meridiem( $meridiem ); + $output = ''; + + $output .= '
    '; + $output .= '

    ' . $title . '

    '; + $output .= ''; + $output .= '
    '; + + // if the blog archive page has been created, add a link to the archive for this show + $page = $wpdb->get_results( + "SELECT meta.post_id + FROM {$wpdb->postmeta} AS meta + WHERE meta.meta_key = '_wp_page_template' AND + meta.meta_value = 'show-blog-archive-template.php' + LIMIT 1" + ); + + if ( $page ) { + $blog_archive = get_permalink( $page[0]->post_id ); + $params = array( 'show_id' => $show_id ); + $blog_archive = add_query_arg( $params, $blog_archive ); + + $output .= '' . esc_html( __( 'More Show Blog Posts', 'radio-station' ) ) . ''; + } + + return $output; } diff --git a/js/radio-station-admin.js b/js/radio-station-admin.js new file mode 100644 index 0000000..176f63d --- /dev/null +++ b/js/radio-station-admin.js @@ -0,0 +1,5 @@ +/* --------------------------- */ +/* Radio Station Admin Scripts */ +/* --------------------------- */ +/* note: admin scripts are currently enqueued using wp_add_inline_script */ +/* this file is necessary to ensure they are printed in the right place */ diff --git a/js/radio-station.js b/js/radio-station.js new file mode 100644 index 0000000..45c52fe --- /dev/null +++ b/js/radio-station.js @@ -0,0 +1,26 @@ +/* --------------------- */ +/* Radio Station ScriptS */ +/* --------------------- */ + +/* Scrolling Function */ +function radio_scroll_to(id) { + elem = document.getElementById(id); + var jump = parseInt((elem.getBoundingClientRect().top - 50) * .2); + document.body.scrollTop += jump; + document.documentElement.scrollTop += jump; + if (!elem.lastjump || elem.lastjump > Math.abs(jump)) { + elem.lastjump = Math.abs(jump); + setTimeout(function() { radio_scroll_to(id);}, 100); + } else {elem.lastjump = null;} +} + +/* Debounce Delay Callback */ +var radio_resize_debounce = (function () { + var debounce_timers = {}; + return function (callback, ms, uniqueId) { + if (!uniqueId) {uniqueId = "nonuniqueid";} + if (debounce_timers[uniqueId]) {clearTimeout (debounce_timers[uniqueId]);} + debounce_timers[uniqueId] = setTimeout(callback, ms); + }; +})(); + diff --git a/languages/.DS_Store b/languages/.DS_Store deleted file mode 100644 index 87eba5bbbfe6d21797f169cf49f67bccc130ba11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeI1KTE?<6vfZe4h0vBpdB+fxL9y@iKP_^POZ+>{t*h*YX9t6{2~s5pU)4V=f3xl z+Lx9zgNV5o-aBcMdvfz5O?p!RroI@Q0aXBH4$=KRPAMVVWdyO~*Hoy6`M?CHm|>1P z4htOS%mAqmL+A>t5?y(UiWt%H+o0jMRew!vlg z0X8S{8hcHg+EjYF?!ov}<68{l(z!j7-KnwH#HmY%ap^Gr$i{al#vYw{k=-38cB-=y zPy!(V>D>=7Wye0i=tuu9pB~xI_qnZx2TWSyNxwIq^q3)EvU84;Pa9my=JFJ{ysx+% zZmea1A-ZUwnO2cG%Iy`@WnQ-O8uG12msNFA>-xy;71U*^zb#SU%682ko&G5{+ zE^w7rm$1+271Sl{xAKzftG)iZ&*>G^W!b+jv9D~nPt?GC%vbJLLVVeQ7lwusC@_It zckWf`|C{UI{}-5$p1KnF{{&2_(P`9qp1!vZ&RBZwh~t(+LiS6X+7wQ<9jDrMocjHT eA(tcWDmk&|P>e*HxNe?-^=>!bt diff --git a/languages/languages.json b/languages/languages.json new file mode 100644 index 0000000..2aa13aa --- /dev/null +++ b/languages/languages.json @@ -0,0 +1 @@ +{"translations":[{"language":"af","english_name":"Afrikaans","native_name":"Afrikaans"},{"language":"ar","english_name":"Arabic","native_name":"\u0627\u0644\u0639\u0631\u0628\u064a\u0629"},{"language":"ary","english_name":"Moroccan Arabic","native_name":"\u0627\u0644\u0639\u0631\u0628\u064a\u0629 \u0627\u0644\u0645\u063a\u0631\u0628\u064a\u0629"},{"language":"as","english_name":"Assamese","native_name":"\u0985\u09b8\u09ae\u09c0\u09af\u09bc\u09be"},{"language":"azb","english_name":"South Azerbaijani","native_name":"\u06af\u0624\u0646\u0626\u06cc \u0622\u0630\u0631\u0628\u0627\u06cc\u062c\u0627\u0646"},{"language":"az","english_name":"Azerbaijani","native_name":"Az\u0259rbaycan dili"},{"language":"bel","english_name":"Belarusian","native_name":"\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f \u043c\u043e\u0432\u0430"},{"language":"bg_BG","english_name":"Bulgarian","native_name":"\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438"},{"language":"bn_BD","english_name":"Bengali (Bangladesh)","native_name":"\u09ac\u09be\u0982\u09b2\u09be"},{"language":"bo","english_name":"Tibetan","native_name":"\u0f56\u0f7c\u0f51\u0f0b\u0f61\u0f72\u0f42"},{"language":"bs_BA","english_name":"Bosnian","native_name":"Bosanski"},{"language":"ca","english_name":"Catalan","native_name":"Catal\u00e0"},{"language":"ceb","english_name":"Cebuano","native_name":"Cebuano"},{"language":"cs_CZ","english_name":"Czech","native_name":"\u010ce\u0161tina"},{"language":"cy","english_name":"Welsh","native_name":"Cymraeg"},{"language":"da_DK","english_name":"Danish","native_name":"Dansk"},{"language":"de_AT","english_name":"German (Austria)","native_name":"Deutsch (\u00d6sterreich)"},{"language":"de_DE_formal","english_name":"German (Formal)","native_name":"Deutsch (Sie)"},{"language":"de_DE","english_name":"German","native_name":"Deutsch"},{"language":"de_CH_informal","english_name":"German (Switzerland, Informal)","native_name":"Deutsch (Schweiz, Du)"},{"language":"de_CH","english_name":"German (Switzerland)","native_name":"Deutsch (Schweiz)"},{"language":"dzo","english_name":"Dzongkha","native_name":"\u0f62\u0fab\u0f7c\u0f44\u0f0b\u0f41"},{"language":"el","english_name":"Greek","native_name":"\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac"},{"language":"en_AU","english_name":"English (Australia)","native_name":"English (Australia)"},{"language":"en_NZ","english_name":"English (New Zealand)","native_name":"English (New Zealand)"},{"language":"en_GB","english_name":"English (UK)","native_name":"English (UK)"},{"language":"en_CA","english_name":"English (Canada)","native_name":"English (Canada)"},{"language":"en_ZA","english_name":"English (South Africa)","native_name":"English (South Africa)"},{"language":"eo","english_name":"Esperanto","native_name":"Esperanto"},{"language":"es_VE","english_name":"Spanish (Venezuela)","native_name":"Espa\u00f1ol de Venezuela"},{"language":"es_ES","english_name":"Spanish (Spain)","native_name":"Espa\u00f1ol"},{"language":"es_CR","english_name":"Spanish (Costa Rica)","native_name":"Espa\u00f1ol de Costa Rica"},{"language":"es_AR","english_name":"Spanish (Argentina)","native_name":"Espa\u00f1ol de Argentina"},{"language":"es_CO","english_name":"Spanish (Colombia)","native_name":"Espa\u00f1ol de Colombia"},{"language":"es_CL","english_name":"Spanish (Chile)","native_name":"Espa\u00f1ol de Chile"},{"language":"es_GT","english_name":"Spanish (Guatemala)","native_name":"Espa\u00f1ol de Guatemala"},{"language":"es_PE","english_name":"Spanish (Peru)","native_name":"Espa\u00f1ol de Per\u00fa"},{"language":"es_UY","english_name":"Spanish (Uruguay)","native_name":"Espa\u00f1ol de Uruguay"},{"language":"es_MX","english_name":"Spanish (Mexico)","native_name":"Espa\u00f1ol de M\u00e9xico"},{"language":"et","english_name":"Estonian","native_name":"Eesti"},{"language":"eu","english_name":"Basque","native_name":"Euskara"},{"language":"fa_IR","english_name":"Persian","native_name":"\u0641\u0627\u0631\u0633\u06cc"},{"language":"fi","english_name":"Finnish","native_name":"Suomi"},{"language":"fr_FR","english_name":"French (France)","native_name":"Fran\u00e7ais"},{"language":"fr_BE","english_name":"French (Belgium)","native_name":"Fran\u00e7ais de Belgique"},{"language":"fr_CA","english_name":"French (Canada)","native_name":"Fran\u00e7ais du Canada"},{"language":"fur","english_name":"Friulian","native_name":"Friulian"},{"language":"gd","english_name":"Scottish Gaelic","native_name":"G\u00e0idhlig"},{"language":"gl_ES","english_name":"Galician","native_name":"Galego"},{"language":"gu","english_name":"Gujarati","native_name":"\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0"},{"language":"haz","english_name":"Hazaragi","native_name":"\u0647\u0632\u0627\u0631\u0647 \u06af\u06cc"},{"language":"he_IL","english_name":"Hebrew","native_name":"\u05e2\u05b4\u05d1\u05b0\u05e8\u05b4\u05d9\u05ea"},{"language":"hi_IN","english_name":"Hindi","native_name":"\u0939\u093f\u0928\u094d\u0926\u0940"},{"language":"hr","english_name":"Croatian","native_name":"Hrvatski"},{"language":"hsb","english_name":"Upper Sorbian","native_name":"Hornjoserb\u0161\u0107ina"},{"language":"hu_HU","english_name":"Hungarian","native_name":"Magyar"},{"language":"hy","english_name":"Armenian","native_name":"\u0540\u0561\u0575\u0565\u0580\u0565\u0576"},{"language":"id_ID","english_name":"Indonesian","native_name":"Bahasa Indonesia"},{"language":"is_IS","english_name":"Icelandic","native_name":"\u00cdslenska"},{"language":"it_IT","english_name":"Italian","native_name":"Italiano"},{"language":"ja","english_name":"Japanese","native_name":"\u65e5\u672c\u8a9e"},{"language":"jv_ID","english_name":"Javanese","native_name":"Basa Jawa"},{"language":"ka_GE","english_name":"Georgian","native_name":"\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8"},{"language":"kab","english_name":"Kabyle","native_name":"Taqbaylit"},{"language":"kk","english_name":"Kazakh","native_name":"\u049a\u0430\u0437\u0430\u049b \u0442\u0456\u043b\u0456"},{"language":"km","english_name":"Khmer","native_name":"\u1797\u17b6\u179f\u17b6\u1781\u17d2\u1798\u17c2\u179a"},{"language":"kn","english_name":"Kannada","native_name":"\u0c95\u0ca8\u0ccd\u0ca8\u0ca1"},{"language":"ko_KR","english_name":"Korean","native_name":"\ud55c\uad6d\uc5b4"},{"language":"ckb","english_name":"Kurdish (Sorani)","native_name":"\u0643\u0648\u0631\u062f\u06cc\u200e"},{"language":"lo","english_name":"Lao","native_name":"\u0e9e\u0eb2\u0eaa\u0eb2\u0ea5\u0eb2\u0ea7"},{"language":"lt_LT","english_name":"Lithuanian","native_name":"Lietuvi\u0173 kalba"},{"language":"lv","english_name":"Latvian","native_name":"Latvie\u0161u valoda"},{"language":"mk_MK","english_name":"Macedonian","native_name":"\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438 \u0458\u0430\u0437\u0438\u043a"},{"language":"ml_IN","english_name":"Malayalam","native_name":"\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02"},{"language":"mn","english_name":"Mongolian","native_name":"\u041c\u043e\u043d\u0433\u043e\u043b"},{"language":"mr","english_name":"Marathi","native_name":"\u092e\u0930\u093e\u0920\u0940"},{"language":"ms_MY","english_name":"Malay","native_name":"Bahasa Melayu"},{"language":"my_MM","english_name":"Myanmar (Burmese)","native_name":"\u1017\u1019\u102c\u1005\u102c"},{"language":"nb_NO","english_name":"Norwegian (Bokm\u00e5l)","native_name":"Norsk bokm\u00e5l"},{"language":"ne_NP","english_name":"Nepali","native_name":"\u0928\u0947\u092a\u093e\u0932\u0940"},{"language":"nl_NL","english_name":"Dutch","native_name":"Nederlands"},{"language":"nl_BE","english_name":"Dutch (Belgium)","native_name":"Nederlands (Belgi\u00eb)"},{"language":"nl_NL_formal","english_name":"Dutch (Formal)","native_name":"Nederlands (Formeel)"},{"language":"nn_NO","english_name":"Norwegian (Nynorsk)","native_name":"Norsk nynorsk"},{"language":"oci","english_name":"Occitan","native_name":"Occitan"},{"language":"pa_IN","english_name":"Punjabi","native_name":"\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40"},{"language":"pl_PL","english_name":"Polish","native_name":"Polski"},{"language":"ps","english_name":"Pashto","native_name":"\u067e\u069a\u062a\u0648"},{"language":"pt_AO","english_name":"Portuguese (Angola)","native_name":"Portugu\u00eas de Angola"},{"language":"pt_PT","english_name":"Portuguese (Portugal)","native_name":"Portugu\u00eas"},{"language":"pt_PT_ao90","english_name":"Portuguese (Portugal, AO90)","native_name":"Portugu\u00eas (AO90)"},{"language":"pt_BR","english_name":"Portuguese (Brazil)","native_name":"Portugu\u00eas do Brasil"},{"language":"rhg","english_name":"Rohingya","native_name":"Ru\u00e1inga"},{"language":"ro_RO","english_name":"Romanian","native_name":"Rom\u00e2n\u0103"},{"language":"ru_RU","english_name":"Russian","native_name":"\u0420\u0443\u0441\u0441\u043a\u0438\u0439"},{"language":"sah","english_name":"Sakha","native_name":"\u0421\u0430\u0445\u0430\u043b\u044b\u044b"},{"language":"snd","english_name":"Sindhi","native_name":"\u0633\u0646\u068c\u064a"},{"language":"si_LK","english_name":"Sinhala","native_name":"\u0dc3\u0dd2\u0d82\u0dc4\u0dbd"},{"language":"sk_SK","english_name":"Slovak","native_name":"Sloven\u010dina"},{"language":"skr","english_name":"Saraiki","native_name":"\u0633\u0631\u0627\u0626\u06cc\u06a9\u06cc"},{"language":"sl_SI","english_name":"Slovenian","native_name":"Sloven\u0161\u010dina"},{"language":"sq","english_name":"Albanian","native_name":"Shqip"},{"language":"sr_RS","english_name":"Serbian","native_name":"\u0421\u0440\u043f\u0441\u043a\u0438 \u0458\u0435\u0437\u0438\u043a"},{"language":"sv_SE","english_name":"Swedish","native_name":"Svenska"},{"language":"sw","english_name":"Swahili","native_name":"Kiswahili"},{"language":"szl","english_name":"Silesian","native_name":"\u015al\u014dnsk\u014f g\u014fdka"},{"language":"ta_IN","english_name":"Tamil","native_name":"\u0ba4\u0bae\u0bbf\u0bb4\u0bcd"},{"language":"te","english_name":"Telugu","native_name":"\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41"},{"language":"th","english_name":"Thai","native_name":"\u0e44\u0e17\u0e22"},{"language":"tl","english_name":"Tagalog","native_name":"Tagalog"},{"language":"tr_TR","english_name":"Turkish","native_name":"T\u00fcrk\u00e7e"},{"language":"tt_RU","english_name":"Tatar","native_name":"\u0422\u0430\u0442\u0430\u0440 \u0442\u0435\u043b\u0435"},{"language":"tah","english_name":"Tahitian","native_name":"Reo Tahiti"},{"language":"ug_CN","english_name":"Uighur","native_name":"\u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5"},{"language":"uk","english_name":"Ukrainian","native_name":"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"},{"language":"ur","english_name":"Urdu","native_name":"\u0627\u0631\u062f\u0648"},{"language":"uz_UZ","english_name":"Uzbek","native_name":"O\u2018zbekcha"},{"language":"vi","english_name":"Vietnamese","native_name":"Ti\u1ebfng Vi\u1ec7t"},{"language":"zh_CN","english_name":"Chinese (China)","native_name":"\u7b80\u4f53\u4e2d\u6587"},{"language":"zh_TW","english_name":"Chinese (Taiwan)","native_name":"\u7e41\u9ad4\u4e2d\u6587"},{"language":"zh_HK","english_name":"Chinese (Hong Kong)","native_name":"\u9999\u6e2f\u4e2d\u6587\u7248\t"}]} \ No newline at end of file diff --git a/languages/radio-station-pt_BR.mo b/languages/radio-station-pt_BR.mo deleted file mode 100644 index 4e7df55523339c443a0200b0409119023b7b5fd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34261 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 uM;jYct#4(}_ 1);\n" -"Language: pt_BR\n" - -#: includes/class-dj-upcoming-widget.php:13 -msgid "Display the upcoming Shows." -msgstr "Mostrar os próximos Shows." - -#: includes/class-dj-upcoming-widget.php:15 -msgid "(Radio Station) Upcoming Shows" -msgstr "(Estação de Rádio) Próximos Shows" - -#: includes/class-dj-upcoming-widget.php:40 includes/class-dj-widget.php:41 -#: includes/class-playlist-widget.php:33 -msgid "Title" -msgstr "Título" - -#: includes/class-dj-upcoming-widget.php:49 includes/class-dj-widget.php:50 -msgid "Link the title to the Show page" -msgstr "Conecte o título a página do Show" - -#: includes/class-dj-upcoming-widget.php:58 includes/class-dj-widget.php:59 -msgid "Above" -msgstr "Acima" - -#: includes/class-dj-upcoming-widget.php:59 includes/class-dj-widget.php:60 -msgid "Left" -msgstr "Esquerda" - -#: includes/class-dj-upcoming-widget.php:60 includes/class-dj-widget.php:61 -msgid "Right" -msgstr "Direita" - -#: includes/class-dj-upcoming-widget.php:61 includes/class-dj-widget.php:62 -msgid "Below" -msgstr "Abaixo" - -#: includes/class-dj-upcoming-widget.php:68 includes/class-dj-widget.php:70 -msgid "Show Title Position (relative to Avatar)" -msgstr "Mostrar Posição do Título (relativo ao Avatar)" - -#: includes/class-dj-upcoming-widget.php:76 -msgid "Display Show Avatars" -msgstr "Mostrar Avatares do Show" - -#: includes/class-dj-upcoming-widget.php:82 includes/class-dj-widget.php:84 -msgid "Avatar Width" -msgstr "Largura do Avatar" - -#: includes/class-dj-upcoming-widget.php:85 -msgid "Width of Show Avatars (in pixels, default 75px)" -msgstr "Largura dos Avatares do Show (em pixels, padrão 75px)" - -#: includes/class-dj-upcoming-widget.php:92 -msgid "Display names of the DJs on the show" -msgstr "Mostrar nomes dos DJs no show" - -#: includes/class-dj-upcoming-widget.php:100 includes/class-dj-widget.php:102 -msgid "Link DJ names to author pages" -msgstr "Conectar nomes dos DJs as páginas dos autores" - -#: includes/class-dj-upcoming-widget.php:106 -msgid "No Additional Schedules" -msgstr "Nenhuma Programação Adicional" - -#: includes/class-dj-upcoming-widget.php:109 includes/class-dj-widget.php:143 -msgid "If no Show is scheduled for the current time, display this text." -msgstr "" -"Se nenhum Show está programado para o horário atual, mostrar esse texto." - -#: includes/class-dj-upcoming-widget.php:116 includes/class-dj-widget.php:110 -msgid "Display schedule info for this show" -msgstr "Mostrar informações da programação deste show" - -#: includes/class-dj-upcoming-widget.php:122 -msgid "Limit" -msgstr "Limite" - -#: includes/class-dj-upcoming-widget.php:125 -msgid "Number of upcoming Shows to display." -msgstr "Número de Shows à seguir para mostrar." - -#: includes/class-dj-upcoming-widget.php:129 includes/class-dj-widget.php:147 -msgid "Time Format" -msgstr "Formato de Hora" - -#: includes/class-dj-upcoming-widget.php:132 includes/class-dj-widget.php:150 -msgid "12 Hour" -msgstr "12 Horas" - -#: includes/class-dj-upcoming-widget.php:134 includes/class-dj-widget.php:152 -msgid "24 Hour" -msgstr "24 Horas" - -#: includes/class-dj-upcoming-widget.php:138 -msgid "Choose time format for displayed schedules." -msgstr "Escolher formato de tempo para programações à mostra." - -#: includes/class-dj-widget.php:12 -msgid "The current on-air DJ." -msgstr "DJ atual no ar." - -#: includes/class-dj-widget.php:14 -msgid "(Radio Station) Show/DJ On-Air" -msgstr "(Estação de Rádio)Show/DJ no ar" - -#: includes/class-dj-widget.php:78 -msgid "Display Show Avatar" -msgstr "Mostrar Avatar do Show" - -#: includes/class-dj-widget.php:87 -msgid "Width of Show Avatar (in pixels, default full width)" -msgstr "Largura do Avatar do Show (em pixels, padrão largura cheia)" - -#: includes/class-dj-widget.php:94 -msgid "Display names of the DJs on the Show" -msgstr "Mostrar nomes dos DJs no Show" - -#: includes/class-dj-widget.php:118 -msgid "Display multiple schedules (if show airs more than once per week)" -msgstr "" -"Mostrar múltiplas programações (se o show vai ao ar mais de uma vez por " -"semana)" - -#: includes/class-dj-widget.php:126 -msgid "Display description of show" -msgstr "Mostrar descrição do show" - -#: includes/class-dj-widget.php:134 -msgid "Display link to show's playlist" -msgstr "Mostrar o link para a playlist do show" - -#: includes/class-dj-widget.php:140 -msgid "No Show Display Text" -msgstr "Texto de Display Sem Show" - -#: includes/class-dj-widget.php:156 -msgid "Choose time format for displayed schedules" -msgstr "Escolher formato de tempo para programações à mostra" - -#: includes/class-playlist-widget.php:12 -msgid "Display currently playling playlist." -msgstr "Mostrar a playlist sendo tocada." - -#: includes/class-playlist-widget.php:14 -msgid "(Radio Station) Now Playing List" -msgstr "(Estação de Rádio) Lista de Tocando Agora" - -#: includes/class-playlist-widget.php:42 -msgid "Show Song Title" -msgstr "Mostrar Título da Música" - -#: includes/class-playlist-widget.php:50 -msgid "Show Artist Name" -msgstr "Mostrar Nome do Artisra" - -#: includes/class-playlist-widget.php:58 -msgid " Show Album Name" -msgstr " Mostrar Nome do Ãlbum" - -#: includes/class-playlist-widget.php:66 -msgid "Show Record Label Name" -msgstr "Mostrar Nome da Gravadora" - -#: includes/class-playlist-widget.php:74 -msgid "Show DJ Comments" -msgstr "Mostrar Comentários do DJ" - -#: includes/data-feeds.php:769 -msgid "Requested Show was not found." -msgstr "Show requisitado não foi encontrado." - -#: includes/data-feeds.php:770 -msgid "No Requested Shows were found." -msgstr "Nenhum Show Requisitado foi encontrado." - -#: includes/data-feeds.php:771 includes/post-types-admin.php:535 -msgid "No Shows were found." -msgstr "Nenhum Show foi encontrado." - -#: includes/data-feeds.php:813 -msgid "Requested Genre was not found." -msgstr "Gênero Requisitado não foi encontrado." - -#: includes/data-feeds.php:814 -msgid "No Requested Genres were found." -msgstr "Nenhum Gênero requisitado foi encontrado." - -#: includes/data-feeds.php:815 -msgid "No Genres were found." -msgstr "Nenhum Gênero foi encontrado." - -#: includes/data-feeds.php:858 -msgid "Requested Language was not found." -msgstr "A linguagem solicitada não foi encontrada." - -#: includes/data-feeds.php:859 -msgid "No Requested Languages were found." -msgstr "Não foram encontradas as línguas solicitadas." - -#: includes/data-feeds.php:860 -msgid "No Languages were found." -msgstr "Nenhum Idioma encontrado." - -#: includes/data-feeds.php:896 -msgid "Error 404 Not Found" -msgstr "Erro 404 não encontrado" - -#: includes/data-feeds.php:897 -msgid "The requested data could not be found." -msgstr "Os dados solicitados não puderam ser encontrados." - -#: includes/master-schedule.php:260 radio-station-admin.php:136 -#: templates/master-schedule-tabs.php:176 -msgid "Genres" -msgstr "Gêneros" - -#: includes/master-schedule.php:276 -msgid "Click to toggle Highlight of Shows with this Genre." -msgstr "Clique para alternar destaque de shows com este gênero." - -#: includes/post-types-admin.php:90 -msgid "Show Language" -msgstr "Mostrar idioma" - -#: includes/post-types-admin.php:126 -msgid "Main Radio Language" -msgstr "Idioma Principal da Estação" - -#: includes/post-types-admin.php:130 -msgid "Select below if Show language(s) differ." -msgstr "Selecione abaixo se o(s) idioma(s) do show diferem." - -#: includes/post-types-admin.php:158 -msgid "Remove Language" -msgstr "Remover Língua" - -#: includes/post-types-admin.php:166 -msgid "Select Language" -msgstr "Seleciona Língua" - -#: includes/post-types-admin.php:180 -msgid "Click on a Language to Add it." -msgstr "Clicar em um Idioma para adicioná-lo." - -#: includes/post-types-admin.php:258 -msgid "Playlist Entries" -msgstr "Entradas da Playlist" - -#: includes/post-types-admin.php:284 includes/post-types-admin.php:659 -#: includes/shortcodes.php:1304 templates/legacy/single-playlist.php:45 -#: templates/single-playlist-content.php:11 -msgid "Artist" -msgstr "Artista" - -#: includes/post-types-admin.php:285 includes/post-types-admin.php:658 -#: includes/shortcodes.php:1296 templates/legacy/single-playlist.php:46 -#: templates/single-playlist-content.php:12 -msgid "Song" -msgstr "Música" - -#: includes/post-types-admin.php:286 includes/shortcodes.php:1311 -#: templates/legacy/single-playlist.php:47 -#: templates/single-playlist-content.php:13 -msgid "Album" -msgstr "Albúm" - -#: includes/post-types-admin.php:287 templates/legacy/single-playlist.php:48 -#: templates/single-playlist-content.php:14 -msgid "Record Label" -msgstr "Gravadora" - -#: includes/post-types-admin.php:311 includes/post-types-admin.php:353 -#: includes/shortcodes.php:1325 templates/single-playlist-content.php:15 -msgid "Comments" -msgstr "Comentários" - -#: includes/post-types-admin.php:314 includes/post-types-admin.php:354 -msgid "New" -msgstr "Novo" - -#: includes/post-types-admin.php:318 includes/post-types-admin.php:355 -#: includes/post-types-admin.php:660 -msgid "Status" -msgstr "Status" - -#: includes/post-types-admin.php:321 includes/post-types-admin.php:356 -msgid "Queued" -msgstr "Em fila" - -#: includes/post-types-admin.php:323 includes/post-types-admin.php:357 -msgid "Played" -msgstr "Tocado" - -#: includes/post-types-admin.php:327 includes/post-types-admin.php:359 -#: includes/post-types-admin.php:1262 includes/post-types-admin.php:1389 -msgid "Remove" -msgstr "Remover" - -#: includes/post-types-admin.php:337 -msgid "Add Entry" -msgstr "Adicionar Entrada" - -#: includes/post-types-admin.php:392 includes/post-types-admin.php:395 -msgid "Schedule" -msgstr "Agenda" - -#: includes/post-types-admin.php:406 includes/post-types-admin.php:409 -msgid "Publish" -msgstr "Publicar" - -#: includes/post-types-admin.php:423 -msgid "Submit for Review" -msgstr "Enviar para Revisão" - -#: includes/post-types-admin.php:426 includes/post-types-admin.php:441 -msgid "Update Playlist" -msgstr "Atualizar Playlist" - -#: includes/post-types-admin.php:440 -msgid "Update" -msgstr "Atualizar" - -#: includes/post-types-admin.php:459 -msgid "Linked Show" -msgstr "Show Conectado" - -#: includes/post-types-admin.php:538 -msgid "You are not assigned to any Shows." -msgstr "Você não está designado para nenhum Show." - -#: includes/post-types-admin.php:546 -msgid "Unassigned" -msgstr "Não atribuído" - -#: includes/post-types-admin.php:630 includes/post-types.php:49 -msgid "Show" -msgstr "Show" - -#: includes/post-types-admin.php:631 -msgid "Tracks" -msgstr "Faixas" - -#: includes/post-types-admin.php:632 -msgid "Track List" -msgstr "Lista de faixas" - -#: includes/post-types-admin.php:654 -msgid "Show/Hide Tracklist" -msgstr "Mostrar/Esconder Tracklist" - -#: includes/post-types-admin.php:716 -msgid "Related to Show" -msgstr "Relacionado ao Show" - -#: includes/post-types-admin.php:758 -msgid "No Shows to Select." -msgstr "Sem shows para selecionar." - -#: includes/post-types-admin.php:819 -msgid "Show Information" -msgstr "Mostrar informações" - -#: includes/post-types-admin.php:848 -msgid "Active" -msgstr "Ativo" - -#: includes/post-types-admin.php:850 -msgid "" -"Check this box if show is currently active (Show will not appear on " -"programming schedule if unchecked.)" -msgstr "" -"Marcar esse campo se o show está ativo (O show não irá aparecer na agenda de " -"programas se não marcado)" - -#: includes/post-types-admin.php:852 -msgid "Website Link" -msgstr "Link do Website" - -#: includes/post-types-admin.php:855 -msgid "DJ / Host Email" -msgstr "Email do DJ/Host" - -#: includes/post-types-admin.php:858 -msgid "Latest Audio File" -msgstr "Último Arquivo de Ãudio" - -#: includes/post-types-admin.php:868 -msgid "Show DJ(s) / Host(s)" -msgstr "DJ(s) / Host(s) do Show" - -#: includes/post-types-admin.php:872 includes/post-types-admin.php:988 -msgid "Show Producer(s)" -msgstr "Produtor(es) do Show" - -#: includes/post-types-admin.php:885 -msgid "Toggle panel: %s" -msgstr "Alternar painel: %s" - -#: includes/post-types-admin.php:912 -msgid "DJs / Hosts" -msgstr "DJs / Hosts" - -#: includes/post-types-admin.php:975 includes/post-types-admin.php:1044 -msgid "Ctrl-Click selects multiple." -msgstr "Ctrl-Click seleciona múltiplos." - -#: includes/post-types-admin.php:1058 -msgid "Show Schedule" -msgstr "Agenda do Show" - -#: includes/post-types-admin.php:1162 includes/post-types-admin.php:1324 -msgid "Day" -msgstr "Dia" - -#: includes/post-types-admin.php:1178 includes/post-types-admin.php:1337 -#: includes/post-types-admin.php:2168 includes/post-types-admin.php:2343 -msgid "Start Time" -msgstr "Hora de Início" - -#: includes/post-types-admin.php:1210 includes/post-types-admin.php:1359 -#: includes/post-types-admin.php:2198 includes/post-types-admin.php:2344 -msgid "End Time" -msgstr "Hora de Término" - -#: includes/post-types-admin.php:1246 includes/post-types-admin.php:1381 -msgid "Encore" -msgstr "Show repetido" - -#: includes/post-types-admin.php:1256 includes/post-types-admin.php:1385 -msgid "Disabled" -msgstr "Desabilitado" - -#: includes/post-types-admin.php:1271 -msgid "Shift Conflicts" -msgstr "Conflitos de Expediente" - -#: includes/post-types-admin.php:1297 -msgid "Warning! Show Shift Conflicts were detected!" -msgstr "Atenção! Conflitos de Expediente entre Shows foram detectados!" - -#: includes/post-types-admin.php:1298 -msgid "" -"Please note that Shifts with conflicts are automatically disabled upon " -"saving." -msgstr "" -"Por favor notar que Expedientes com conflitos são automaticamente " -"desabilitados quando salvos." - -#: includes/post-types-admin.php:1299 -msgid "" -"Fix the Shift and/or the Shift on the conflicting Show and Update them both." -msgstr "" -"Consertar Expediente e/ou o Expediente do Show em conflito e Atualizar " -"ambos." - -#: includes/post-types-admin.php:1300 -msgid "" -"Then you can uncheck the shift Disable box and Update to re-enable the Shift." -msgstr "" -"Então você pode desmarcar a caixa para Desabilitar o expediente e Atualizar " -"para reabilitar o Expediente." - -#: includes/post-types-admin.php:1312 -msgid "Add Shift" -msgstr "Adicionar Expediente" - -#: includes/post-types-admin.php:1316 -msgid "Are you sure you want to remove this shift?" -msgstr "Tem certeza que quer remover esse expediente?" - -#: includes/post-types-admin.php:1434 -msgid "Show Description" -msgstr "Descrição do Show" - -#: includes/post-types-admin.php:1451 -msgid "" -"The text field below is for your Show Description. It will display in the " -"About section of your Show page." -msgstr "" -"O campo de texto abaixo é para a sua Descrição do Show. Ele será exibido na " -"seção Sobre da página do seu Show." - -#: includes/post-types-admin.php:1452 -msgid "" -"It is not recommended to include your past show content or archives in this " -"area, as it will affect the Show page layout your visitors see." -msgstr "" -"Não é recomendado incluir o conteúdo ou arquivos do seu show passado nesta " -"área, pois afetará o layout da página show que seus visitantes veem." - -#: includes/post-types-admin.php:1453 -msgid "" -"It may also impact SEO, as archived content won't have their own pages and " -"thus their own SEO and Social meta rules." -msgstr "" -"Isto pode também pode afetar SEO, como conteúdo arquivado não terá suas " -"próprias páginas e, portanto, suas próprias regras SEO e meta social." - -#: includes/post-types-admin.php:1454 -msgid "" -"We recommend using WordPress Posts to add new posts and assign them to your " -"Show(s) using the Related Show metabox on the Post Edit screen so they " -"display on the Show page." -msgstr "" -"Recomendamos o uso do WordPress Posts para adicionar novas postagens e " -"atribuí-las ao seu Show(s) usando a metacaixa do Show Relacionado na tela " -"Edição de Post para que elas sejam exibidas na página do Show." - -#: includes/post-types-admin.php:1455 -msgid "" -"You can then assign them to a relevent Post Category for display on your " -"site also." -msgstr "" -"Você pode também designa-las a uma Categoria de Post relevante no seu site." - -#: includes/post-types-admin.php:1476 -msgid "Show Logo" -msgstr "Mostrar Logo" - -#: includes/post-types-admin.php:1487 -msgid "Show Images" -msgstr "Mostrar Imagens" - -#: includes/post-types-admin.php:1532 -msgid "Set Show Avatar Image" -msgstr "Definir Imagem de Avatar do Show" - -#: includes/post-types-admin.php:1536 -msgid "Remove Show Avatar Image" -msgstr "Remover Imagem de Avatar do Show" - -#: includes/post-types-admin.php:1568 -msgid "Set Show Header Image" -msgstr "Definir Imagem de Cabeçalho do Show" - -#: includes/post-types-admin.php:1572 -msgid "Remove Show Header Image" -msgstr "Remover Imagem de Cabeçalho do Show" - -#: includes/post-types-admin.php:1589 -msgid "Are you sure you want to remove this image?" -msgstr "Tem certeza que quer remover essa imagem?" - -#: includes/post-types-admin.php:1948 -msgid "Active?" -msgstr "Ativo?" - -#: includes/post-types-admin.php:1950 -msgid "About?" -msgstr "Sobre?" - -#: includes/post-types-admin.php:1951 -msgid "Shifts" -msgstr "Expedientes" - -#: includes/post-types-admin.php:1953 -msgid "Hosts" -msgstr "Hosts" - -#: includes/post-types-admin.php:1954 -msgid "Show Avatar" -msgstr "Mostrar Avatar" - -#: includes/post-types-admin.php:1970 includes/post-types-admin.php:1981 -#: includes/post-types-admin.php:2443 -msgid "Yes" -msgstr "Sim" - -#: includes/post-types-admin.php:1971 includes/post-types-admin.php:1979 -#: includes/post-types-admin.php:2441 -msgid "No" -msgstr "Não" - -#: includes/post-types-admin.php:1999 -msgid "This Shift is Disabled." -msgstr "Esse Expediente está Desabilitado." - -#: includes/post-types-admin.php:2004 -msgid "This Shift has Schedule Conflicts and is Disabled." -msgstr "Esse Expediente tem conflitos de Programação e está Desabilitado." - -#: includes/post-types-admin.php:2005 -msgid "This Shift has Schedule Conflicts." -msgstr "Esse Expediente tem conflitos de Programação." - -#: includes/post-types-admin.php:2088 -msgid "Filter by show day" -msgstr "Filtrar por dia de Show" - -#: includes/post-types-admin.php:2090 -msgid "All show days" -msgstr "Todos os dias de Show" - -#: includes/post-types-admin.php:2117 -msgid "Override Schedule" -msgstr "Substituir Programação" - -#: includes/post-types-admin.php:2162 -msgid "Date" -msgstr "Data" - -#: includes/post-types-admin.php:2342 -msgid "Override Date" -msgstr "Substituir Data" - -#: includes/post-types-admin.php:2345 -msgid "Affected Show(s) on Date" -msgstr "Show(s) Afetados na Data" - -#: includes/post-types-admin.php:2347 -msgid "Description" -msgstr "Descrição" - -#: includes/post-types-admin.php:2348 -msgid "Override Image" -msgstr "Substituir Imagem" - -#: includes/post-types-admin.php:2423 -msgid "Inactive Show" -msgstr "Show Inativo" - -#: includes/post-types-admin.php:2425 -msgid "Disabled Shift" -msgstr "Expediente Desabilitado" - -#: includes/post-types-admin.php:2506 -msgid "Filter by override date" -msgstr "Filtrar por data de Substituição" - -#: includes/post-types-admin.php:2509 -msgid "All override dates" -msgstr "Todas as datas substituidas" - -#: includes/post-types.php:48 includes/post-types.php:56 -#: radio-station-admin.php:131 -msgid "Shows" -msgstr "Shows" - -#: includes/post-types.php:50 includes/post-types.php:51 -#: radio-station-admin.php:132 -msgid "Add Show" -msgstr "Adicionar Show" - -#: includes/post-types.php:52 -msgid "Edit Show" -msgstr "Editar Show" - -#: includes/post-types.php:53 -msgid "New Show" -msgstr "Novo Show" - -#: includes/post-types.php:54 -msgid "View Show" -msgstr "Ver Show" - -#: includes/post-types.php:61 -msgid "Post type for Show descriptions" -msgstr "Tipo de post para descrições de Show" - -#: includes/post-types.php:89 includes/post-types.php:97 -#: radio-station-admin.php:134 templates/single-show-content.php:520 -msgid "Playlists" -msgstr "Playlists" - -#: includes/post-types.php:90 -msgid "Playlist" -msgstr "Playlist" - -#: includes/post-types.php:91 includes/post-types.php:92 -msgid "Add Playlist" -msgstr "Adicionar Playlist" - -#: includes/post-types.php:93 -msgid "Edit Playlist" -msgstr "Editar Playlist" - -#: includes/post-types.php:94 -msgid "New Playlist" -msgstr "Nova Playlist" - -#: includes/post-types.php:95 includes/shortcodes.php:1337 -msgid "View Playlist" -msgstr "Ver Playlist" - -#: includes/post-types.php:102 -msgid "Post type for Playlist descriptions" -msgstr "Tipo de post para descrições de Playlist" - -#: includes/post-types.php:129 radio-station-admin.php:137 -msgid "Schedule Overrides" -msgstr "Substituições de Programação" - -#: includes/post-types.php:130 -msgid "Schedule Override" -msgstr "Substituição de Programação" - -#: includes/post-types.php:131 includes/post-types.php:132 -msgid "Add Schedule Override" -msgstr "Adicionar Substituição de Programação" - -#: includes/post-types.php:133 -msgid "Edit Schedule Override" -msgstr "Editar Substituição de Programação" - -#: includes/post-types.php:134 -msgid "New Schedule Override" -msgstr "Nova Substituição de Programação" - -#: includes/post-types.php:135 -msgid "View Schedule Override" -msgstr "Ver Substituição de Programação" - -#: includes/post-types.php:140 -msgid "Post type for Schedule Override" -msgstr "Tipo de post para substituição de Programação" - -#: includes/post-types.php:170 -msgid "Host Profiles" -msgstr "Perfis de Hosts" - -#: includes/post-types.php:171 -msgid "Host Profile" -msgstr "Perfil do Host" - -#: includes/post-types.php:172 includes/post-types.php:175 -msgid "New Host Profile" -msgstr "Novo perfil de Host" - -#: includes/post-types.php:173 -msgid "Add Host Profile" -msgstr "Adicionar Perfil de Host" - -#: includes/post-types.php:174 -msgid "Edit Host Profile" -msgstr "Editar Perfil de Host" - -#: includes/post-types.php:176 -msgid "View Host Profile" -msgstr "Ver Perfil de Host" - -#: includes/post-types.php:177 -msgid "Show Hosts" -msgstr "Mostrar Hosts" - -#: includes/post-types.php:183 -msgid "Post type for DJ / Host Profiles" -msgstr "Tipo de Post para Perfis de DJ / Host" - -#: includes/post-types.php:212 -msgid "Producer Profiles" -msgstr "Perfis de Produtor" - -#: includes/post-types.php:213 -msgid "Producer Profile" -msgstr "Perfil de Produtor" - -#: includes/post-types.php:214 includes/post-types.php:217 -msgid "New Producer Profile" -msgstr "Novo Perfil de Produtoe" - -#: includes/post-types.php:215 -msgid "Add Producer Profile" -msgstr "Adicionar Perfil de Produtor" - -#: includes/post-types.php:216 -msgid "Edit Producer Profile" -msgstr "Editar Perfil de Produtor" - -#: includes/post-types.php:218 -msgid "View Producer Profile" -msgstr "Ver Perfil de Produtor" - -#: includes/post-types.php:219 -msgid "Show Producers Profile" -msgstr "Mostrar Perfil do Produtor" - -#: includes/post-types.php:225 -msgid "Post type for Producer Profiles" -msgstr "Tipos de Post para Perfis de Podutor" - -#: includes/post-types.php:332 -msgid "Edit" -msgstr "Editar" - -#: includes/post-types.php:357 -msgctxt "taxonomy general name" -msgid "Genres" -msgstr "Gêneros" - -#: includes/post-types.php:358 -msgctxt "taxonomy singular name" -msgid "Genre" -msgstr "Gênero" - -#: includes/post-types.php:359 -msgid "Search Genres" -msgstr "Buscar Gêneros" - -#: includes/post-types.php:360 -msgid "All Genres" -msgstr "Todos os Gêneros" - -#: includes/post-types.php:361 -msgid "Parent Genre" -msgstr "Gênero Parente" - -#: includes/post-types.php:362 -msgid "Parent Genre:" -msgstr "Gênero Parente:" - -#: includes/post-types.php:363 -msgid "Edit Genre" -msgstr "Editar Gênero" - -#: includes/post-types.php:364 -msgid "Update Genre" -msgstr "Atualizar Gênero" - -#: includes/post-types.php:365 -msgid "Add New Genre" -msgstr "Adicionar Novo Gênero" - -#: includes/post-types.php:366 -msgid "New Genre Name" -msgstr "Novo Nome de Gênero" - -#: includes/post-types.php:367 -msgid "Genre" -msgstr "Gênero" - -#: includes/post-types.php:401 -msgctxt "taxonomy general name" -msgid "Languages" -msgstr "Idiomas" - -#: includes/post-types.php:402 -msgctxt "taxonomy singular name" -msgid "Language" -msgstr "Idioma" - -#: includes/post-types.php:403 -msgid "Search Languages" -msgstr "Buscar Idiomas" - -#: includes/post-types.php:404 -msgid "All Languages" -msgstr "Todos os Idiomas" - -#: includes/post-types.php:405 -msgid "Parent Language" -msgstr "Língua Parente" - -#: includes/post-types.php:406 -msgid "Parent Language:" -msgstr "Língua Parente:" - -#: includes/post-types.php:407 -msgid "Edit Language" -msgstr "Editar Idioma" - -#: includes/post-types.php:408 -msgid "Update Language" -msgstr "Atualizar Idioma" - -#: includes/post-types.php:409 -msgid "Add New Language" -msgstr "Adicionar novo Idioma" - -#: includes/post-types.php:410 -msgid "New Language Name" -msgstr "Nome do Novo Idioma" - -#: includes/post-types.php:411 -msgid "Language" -msgstr "Idioma" - -#: includes/shortcodes.php:51 -msgid "Radio Time" -msgstr "Horário na Rádio" - -#: includes/shortcodes.php:61 -msgid "Your Time" -msgstr "Seu Horário" - -#: includes/shortcodes.php:180 -msgid "No Shows in this Genre were found." -msgstr "Nenhum Show nesse Gênero foi encontrado." - -#: includes/shortcodes.php:182 -msgid "No Shows were found to display." -msgstr "Nenhum Show foi encontrado para exibição." - -#: includes/shortcodes.php:185 -msgid "No Playlists were found to display." -msgstr "Nenhuma Playlist foi encontrada para exibição." - -#: includes/shortcodes.php:187 -msgid "No Overrides were found to display." -msgstr "Nenhuma Substituição foi encontrada para exibição." - -#: includes/shortcodes.php:354 -msgid "No Genres were found to display." -msgstr "Nenhum Gênero foi encontrado para exibição." - -#: includes/shortcodes.php:443 -msgid "No Shows in this Genre." -msgstr "Nenhum Show neste Gênero." - -#: includes/shortcodes.php:599 -msgid "Published on " -msgstr "Publicado em " - -#: includes/shortcodes.php:863 includes/shortcodes.php:1138 -#: templates/single-show-content.php:422 -msgid "Encore Presentation" -msgstr "Apresentação Repetida" - -#: includes/shortcodes.php:876 includes/shortcodes.php:1150 -#: radio-station-admin.php:500 templates/master-schedule-legacy.php:168 -#: templates/master-schedule-list.php:75 -#: templates/master-schedule-table.php:189 -#: templates/master-schedule-tabs.php:89 -msgid "with" -msgstr "com" - -#: includes/shortcodes.php:907 includes/shortcodes.php:1181 -#: templates/master-schedule-legacy.php:179 -#: templates/master-schedule-list.php:83 -#: templates/master-schedule-table.php:200 -#: templates/master-schedule-tabs.php:99 templates/single-show-content.php:229 -#: templates/single-show-content.php:256 -msgid "and" -msgstr "e" - -#: includes/shortcodes.php:1084 -msgid "No Shows Upcoming" -msgstr "Nenhum Show Próximo" - -#: includes/shortcodes.php:1318 -msgid "Label" -msgstr "Gravadora" - -#: includes/shortcodes.php:1346 -msgid "No Playlist available." -msgstr "Nenhuma Playlist disponíveis." - -#: includes/shortcodes.php:1411 -msgid "More Playlists" -msgstr "Mais Playlists" - -#: includes/support-functions.php:1803 -msgid "Africa" -msgstr "Africa" - -#: includes/support-functions.php:1804 -msgid "America" -msgstr "America" - -#: includes/support-functions.php:1805 -msgid "Asia" -msgstr "Asia" - -#: includes/support-functions.php:1806 -msgid "Atlantic" -msgstr "Atlantic" - -#: includes/support-functions.php:1807 -msgid "Australia" -msgstr "Australia" - -#: includes/support-functions.php:1808 -msgid "Europe" -msgstr "Europa" - -#: includes/support-functions.php:1809 -msgid "Indian" -msgstr "Indico" - -#: includes/support-functions.php:1810 -msgid "Pacific" -msgstr "Pacifico" - -#: includes/support-functions.php:1811 -msgid "Antarctica" -msgstr "Antarctica" - -#: includes/support-functions.php:1854 -msgid "WordPress Timezone" -msgstr "Fuso Horário do WordPress" - -#: includes/support-functions.php:1920 -msgid "WordPress Setting" -msgstr "Configuração do WordPress" - -#: includes/support-functions.php:2853 -msgid "More Show Blog Posts" -msgstr "Mais Blog Posts do Show" - -#: loader.php:959 -msgid "Plugin Name" -msgstr "Nome do Plugin" - -#: loader.php:961 -msgid "Requires at least" -msgstr "Requer pelo menos" - -#: loader.php:961 loader.php:962 -msgid "WordPress" -msgstr "WordPress" - -#: loader.php:962 -msgid "Tested up to" -msgstr "Testado até" - -#: loader.php:963 -msgid "Stable Tag" -msgstr "Stable Tag" - -#: loader.php:964 -msgid "Contributors" -msgstr "Contribuidores" - -#: loader.php:984 -msgid "Extra Notes" -msgstr "Notas Extra" - -#: loader.php:1139 -msgid "" -"If you want to more easily access support and feedback for this plugins " -"features and functionality, %s can connect your user, %s at %s, to %s" -msgstr "" -"Se você quer acessar o suporte e feedback para as funcionalidades e " -"ferramentas desse plugin, %s pode conectar seu usuário, %s em %s, para %s" - -#: loader.php:1204 radio-station-admin.php:121 radio-station-admin.php:125 -#: radio-station-admin.php:143 -msgid "Settings" -msgstr "Configurações" - -#: loader.php:1323 -msgid "by" -msgstr "por" - -#: loader.php:1332 -msgid "Readme" -msgstr "Readme" - -#: loader.php:1334 -msgid "Docs" -msgstr "Docs" - -#: loader.php:1335 -msgid "Support" -msgstr "Suporte" - -#: loader.php:1336 -msgid "Dev" -msgstr "Dev" - -#: loader.php:1374 radio-station-admin.php:432 -msgid "Rate on WordPress.Org" -msgstr "Avalie em WordPress.Org" - -#: loader.php:1385 -msgid "Share the Plugin Love" -msgstr "Compartilhe o Amor pelo Plugin" - -#: loader.php:1396 radio-station.php:581 -msgid "Support this Plugin" -msgstr "Apoie esse Plugin" - -#: loader.php:1409 -msgid "Settings Updated." -msgstr "Configurações Atualizadas." - -#: loader.php:1411 -msgid "Error! Settings NOT Updated." -msgstr "Erro! Configurações NÃO Atualizadas." - -#: loader.php:1413 -msgid "Settings Reset!" -msgstr "Configurações Resetadas!" - -#: loader.php:1531 radio-station.php:542 -msgid "General" -msgstr "Geral" - -#: loader.php:1536 -msgid "Are you sure you want to reset to default settings?" -msgstr "Tem certeza que quer reiniciar as configurações para padrão?" - -#: loader.php:1628 -msgid "Reset Settings" -msgstr "Reiniciar Configurações" - -#: loader.php:1630 -msgid "Save Settings" -msgstr "Salvar Configurações" - -#: loader.php:1710 -msgid "Use Ctrl and Click to Select" -msgstr "Usar Ctrl e Click para Selecionar" - -#: loader.php:1737 -msgid "Available in Pro Version." -msgstr "Disponível na versão Pro." - -#: loader.php:1738 -msgid "Click Here to Upgrade!" -msgstr "Clique aqui para Atualizar!" - -#: loader.php:1740 -msgid "Coming soon in Pro version!" -msgstr "Em breve na Versão Pro!" - -#: radio-station-admin.php:103 -msgid "You do not have permissions to access that page." -msgstr "Você não tem permissões para acessar esta página." - -#. Plugin Name of the plugin/theme -#: radio-station-admin.php:116 radio-station-admin.php:121 -#: radio-station-admin.php:419 radio-station-admin.php:480 -#: radio-station-admin.php:594 radio-station-admin.php:661 -msgid "Radio Station" -msgstr "Estação de Rádio" - -#: radio-station-admin.php:142 templates/admin-export.php:2 -msgid "Export Playlists" -msgstr "Exportar Playlists" - -#: radio-station-admin.php:144 -msgid "Help" -msgstr "Ajuda" - -#: radio-station-admin.php:236 -msgid "Role Assignments" -msgstr "Atribuições de Funções" - -#: radio-station-admin.php:237 -msgid "" -"You can assign a Radio Station role to users through the WordPress User " -"editor." -msgstr "" -"Você pode designar funções na Estação de Rádio através do editor de Usuário " -"do WordPress." - -#: radio-station-admin.php:358 -msgid "Right-click and download this file to save your export" -msgstr "Clique com o botão direito para salvar a sua exportação" - -#: radio-station-admin.php:416 -msgid "" -"Help support us to make improvements, modifications and introduce new " -"features!" -msgstr "" -"Ajude-nos a fazer melhoras, modificações e introduzir novas ferramentas!" - -#: radio-station-admin.php:417 -msgid "" -"With over a thousand radio station users thanks to the original plugin " -"author Nikki Blight" -msgstr "" -"Com mais de mill usuários da Estação de Rádio obrigado ao author original do " -"plugin Nikki Blight" - -#: radio-station-admin.php:418 -msgid "since June 2019" -msgstr "des de Junho de 2019" - -#: radio-station-admin.php:420 -msgid " plugin development has been actively taken over by" -msgstr " desenvolvimento do Plugin foi ativamente assumido por" - -#: radio-station-admin.php:422 -msgid "We invite you to" -msgstr "Nós te convidamos para" - -#: radio-station-admin.php:424 -msgid "Become a Radio Station Patreon Supporter" -msgstr "Torne-se um Supporter da Estação de Rádio no Patreon" - -#: radio-station-admin.php:425 -msgid "to make it better for everyone" -msgstr "para tornar isto melhor para todos" - -#: radio-station-admin.php:441 radio-station-admin.php:619 -#: radio-station-admin.php:690 -msgid "Dismiss this Notice" -msgstr "Ignore esse Aviso" - -#: radio-station-admin.php:481 -msgid "has detected" -msgstr "detectou" - -#: radio-station-admin.php:482 -msgid "Schedule conflicts!" -msgstr "Conflitos de Programação!" - -#: radio-station-admin.php:486 -msgid "The following Shows have conflicting Shift times" -msgstr "Os próximos Shows têm conflitos de Horário" - -#: radio-station-admin.php:518 -msgid "Go to Show List" -msgstr "Ir para a Lista do Show" - -#: radio-station-admin.php:519 -msgid "Conflicts are highlighted" -msgstr "Conflitos estão em destaque" - -#: radio-station-admin.php:520 -msgid "in Show Shift column." -msgstr "na coluna de Horários de Shows." - -#: radio-station-admin.php:525 -msgid "This notice will persist" -msgstr "Esse aviso irá persistir" - -#: radio-station-admin.php:526 -msgid "until conflicts are resolved." -msgstr "até os conflitos serem resolvidos." - -#: radio-station-admin.php:593 -msgid "A new version of" -msgstr "Uma nova versão de" - -#: radio-station-admin.php:595 -msgid "is available." -msgstr "está disponível." - -#: radio-station-admin.php:599 -msgid "Take a moment to Upgrade for a better experience. In this update..." -msgstr "" -"Tire um momento e faça um Upgrade para uma melhor experiência. Nessa " -"atualização..." - -#: radio-station-admin.php:605 radio-station-admin.php:674 -msgid "Update Details" -msgstr "Atualizar Detalhes" - -#: radio-station-admin.php:610 -msgid "Update Now" -msgstr "Atualizar Agora" - -#: radio-station-admin.php:662 -msgid "Update Notice" -msgstr "Aviso de Atualização" - -#: radio-station-admin.php:667 -msgid "Thanks for Upgrading! You can enjoy these improvements now" -msgstr "" -"Obrigado por fazer o Upgrade! Você pode aproveitar essas melhoras agora" - -#: radio-station-admin.php:680 -msgid "Plugin Settings" -msgstr "Configurações do Plugin" - -#: radio-station-admin.php:856 -msgid "Stay tuned! Subscribe to Radio Station's" -msgstr "Fique ligado! Inscreva-se aos" - -#: radio-station-admin.php:857 -msgid "Plugin Updates and Announcements List" -msgstr "Updates do Plugin e Lista de Anúncios da Estação de Rádio" - -#: radio-station.php:115 -msgid "Streaming URL" -msgstr "URL da Stream" - -#: radio-station.php:117 -msgid "Enter the Streaming URL for your Radio Station." -msgstr "Insira a URL da Stream." - -#: radio-station.php:125 -msgid "Main Broadcast Language" -msgstr "Idioma Principal da Transmissão" - -#: radio-station.php:127 -msgid "Select the main language used on your Radio Station." -msgstr "Selecione o idioma principal usado na sua Estação de Rádio." - -#: radio-station.php:147 -msgid "Location Timezone" -msgstr "Fuso Horário da Localização" - -#: radio-station.php:149 -msgid "Select your Broadcast Location for Timezone display." -msgstr "" -"Selecione a Localização da sua Transmissão para exibição de Fuso Horário." - -#: radio-station.php:157 -msgid "12 Hour Format" -msgstr "Formato de 12 Horas" - -#: radio-station.php:158 -msgid "24 Hour Format" -msgstr "Formato de 24 Horas" - -#: radio-station.php:160 -msgid "Clock Time Format" -msgstr "Formato de Hora do Relógio" - -#: radio-station.php:162 -msgid "" -"Default Time Format Display for plugin output. Can be overridden in each " -"shortcode or widget." -msgstr "" -"Formato de Exibição de Tempo padrão para output do plugin. Pode ser " -"substituído em cada shortcode ou widget." - -#: radio-station.php:169 -msgid "Enable Data Routes" -msgstr "Permitir Rotas de Dados" - -#: radio-station.php:172 -msgid "Enables Station Data Routes via WordPress REST API." -msgstr "Permite Rotas de Dados da Estação via WordPress REST API." - -#: radio-station.php:179 -msgid "Enable Data Feeds" -msgstr "Permitir Feeds de Dados" - -#: radio-station.php:182 -msgid "Enable Station Data Feeds via WordPress feed links." -msgstr "Permitir Feeds de Dados da Estação via WordPress feed links." - -#: radio-station.php:189 -msgid "Show Shift Feeds" -msgstr "Exibir Feeds de Expediente" - -#: radio-station.php:192 -msgid "" -"Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor " -"to subscribe to a Show feed to be notified of Show shifts." -msgstr "" -"Converter Feeds RSS de um único Show para um feed de expediente de Show, " -"permitindo ao visitor inscrever-se ao feed de um Show para ser notificado " -"dos Expedientes." - -#: radio-station.php:200 -msgid "Transient Caching" -msgstr "Cache Temporário" - -#: radio-station.php:203 -msgid "Use Transient Caching to improve Schedule calculation performance." -msgstr "" -"Use o Cache Temporário para melhorar a performance do cálculo de Programação." - -#: radio-station.php:213 -msgid "Master Schedule Page" -msgstr "Página de Programação Mestre" - -#: radio-station.php:215 -msgid "Select the Page you are displaying the Master Schedule on." -msgstr "Selecione a página na qual você está exibindo a Programação Mestre." - -#: radio-station.php:222 radio-station.php:345 radio-station.php:376 -#: radio-station.php:407 -msgid "Automatic Display" -msgstr "Display Automático" - -#: radio-station.php:225 -msgid "" -"Replaces selected page content with Master Schedule. Alternatively customize " -"with the shortcode: " -msgstr "" -"Repõe o conteúdo selecionado da página com a Programação Mestre. " -"Alternativamente customize com o shortcode:" - -#: radio-station.php:232 -msgid "Schedule View Default" -msgstr "Visão Padrão da Programação" - -#: radio-station.php:235 -msgid "Table View" -msgstr "Visão em Tabela" - -#: radio-station.php:236 -msgid "List View" -msgstr "Visão em Lista" - -#: radio-station.php:237 -msgid "Divs View" -msgstr "Visão em Divs" - -#: radio-station.php:238 -msgid "Tabbed View" -msgstr "Visão em Guias" - -#: radio-station.php:239 -msgid "Legacy Table" -msgstr "Tabela Legado" - -#: radio-station.php:241 -msgid "View type to use for automatic display on Master Schedule Page." -msgstr "" -"Tipo de visualização a ser utilizado para display automático na Página da " -"Programação Mestre." - -#: radio-station.php:249 -msgid "View Switching" -msgstr "Troca de Visão" - -#: radio-station.php:252 -msgid "Enable View Switching on the Master Schedule." -msgstr "Permitir Troca de Visualização na Programação Mestre." - -#: radio-station.php:261 -msgid "Info Blocks Position" -msgstr "Posição de Blocos de Informação" - -#: radio-station.php:263 -msgid "Float Left" -msgstr "Oscilar à Esquerda" - -#: radio-station.php:264 -msgid "Float Right" -msgstr "Oscilar à Direita" - -#: radio-station.php:265 -msgid "Float Top" -msgstr "Oscilar ao Topo" - -#: radio-station.php:268 -msgid "Where to position Show info blocks relative to Show Page content." -msgstr "" -"Onde posicionar blocos de Informação do Show relativo ao conteúdo da Página " -"do Show." - -#: radio-station.php:275 -msgid "Content Header Image" -msgstr "Imagem de Cabeçalho do Conteúdo" - -#: radio-station.php:278 -msgid "" -"If your template does not display the Featured Image, enable this and use " -"the Content Header Image box on the Show edit screen instead." -msgstr "" -"Se o seu modelo não exibir a imagem em destaque, habilite isso e use a caixa " -"de Imagem de Cabeçalho do Conteúdo na tela de edição do Show." - -#: radio-station.php:285 templates/single-show-content.php:617 -msgid "Latest Show Posts" -msgstr "Posts do Último Show" - -#: radio-station.php:290 -msgid "Number of Latest Blog Posts to Show above Show Page tabs." -msgstr "Número de Blog Posts Recentes acima de abas da Página de Show." - -#: radio-station.php:297 -msgid "Posts per Page" -msgstr "Posts por Página" - -#: radio-station.php:302 -msgid "Blog Posts per page on the Show Page tab." -msgstr "Posts do Blog por página na guia Página do Show." - -#: radio-station.php:312 -msgid "Playlists per Page" -msgstr "Playlists por Página" - -#: radio-station.php:314 -msgid "Playlists per page on the Show Page tab." -msgstr "Playlists por página na guia Página do Show." - -#: radio-station.php:335 -msgid "Show Archives Page" -msgstr "Mostrar Página de Arquivos" - -#: radio-station.php:339 -msgid "Select the Page for displaying the Show archive list." -msgstr "Selecione a Página para display da lista de arquivos do Show." - -#: radio-station.php:349 -msgid "" -"Replaces selected page content with default Show Archive. Alternatively " -"customize display using the shortcode:" -msgstr "" -"Substitui o conteúdo da página selecionada pelo Arquivo Show Padrão. " -"Alternativamente personalizar display usando o shortcode:" - -#: radio-station.php:366 -msgid "Playlist Archives Page" -msgstr "Página de Arquivos de Playlist" - -#: radio-station.php:370 -msgid "Select the Page for displaying the Playlist archive list." -msgstr "Selecione a Página para exibição da lista de arquivos de Playlist" - -#: radio-station.php:380 -msgid "" -"Replaces selected page content with default Playlist Archive. Alternatively " -"customize display using the shortcode:" -msgstr "" -"Substitui o conteúdo da página selecionada pelo Arquivo de Playlist Padrão. " -"Alternativamente personalizar display usando o shortcode:" - -#: radio-station.php:397 -msgid "Genre Archives Page" -msgstr "Página de Arquivos de Gênero" - -#: radio-station.php:401 -msgid "Select the Page for displaying the Genre archive list." -msgstr "Seleciona a Página para exibição da lista de arquivos de Gênero." - -#: radio-station.php:411 -msgid "" -"Replaces selected page content with default Genre Archive. Alternatively " -"customize display using the shortcode:" -msgstr "" -"Substitui o conteúdo da página selecionada pelo Arquivo de Gênero Padrão. " -"Alternativamente personalizar display usando o shortcode:" - -#: radio-station.php:430 -msgid "Templates Change Note" -msgstr "Nota de Troca de Templates" - -#: radio-station.php:431 -msgid "Since 2.3.0, the way that Templates are implemented has changed." -msgstr "Des de 2.3.0, a maneira que as Templates são implementados mudou." - -#: radio-station.php:432 -msgid "See the Documentation for more information:" -msgstr "Veja a documentação para mais informações:" - -#: radio-station.php:433 -msgid "Templates Documentation" -msgstr "Documentação de Templates" - -#: radio-station.php:438 -msgid "Show Template" -msgstr "Modelo do Show" - -#: radio-station.php:441 radio-station.php:464 -msgid "Theme Page Template (page.php)" -msgstr "Modelo de Página Tema (page.php)" - -#: radio-station.php:442 radio-station.php:465 -msgid "Theme Post Template (single.php)" -msgstr "Modelo de Postagem Tema (single.php)" - -#: radio-station.php:443 -msgid "Theme Singular Template (singular.php)" -msgstr "Modelo de tema singular (singular.php)" - -#: radio-station.php:444 radio-station.php:466 -msgid "Legacy Plugin Template" -msgstr "Modelo de Plugin Legado" - -#: radio-station.php:447 -msgid "Which template to use for displaying Show content." -msgstr "Qual template utilizar para exibir conteúdo do Show." - -#: radio-station.php:452 radio-station.php:474 -msgid "Combined Method" -msgstr "Método Combinado" - -#: radio-station.php:456 -msgid "" -"Advanced usage. Use both a custom template AND content filtering for a Show. " -"(Not compatible with Legacy templates.)" -msgstr "" -"Uso avançado. Use ambos a template customizada E filtro de conteúdo para um " -"Show. ( Não compatível com templates Legado.)" - -#: radio-station.php:461 -msgid "Playlist Template" -msgstr "Modelo de Playlist" - -#: radio-station.php:469 -msgid "Which template to use for displaying Playlist content." -msgstr "Qual template usar para exibir conteúdo da Playlist." - -#: radio-station.php:478 -msgid "" -"Advanced usage. Use both a custom template AND content filtering for a " -"Playlist. (Not compatible with Legacy templates.)" -msgstr "" -"Uso avançado. Use ambos a template customizada E filtro de conteúdo para uma " -"Playlist. ( Não compatível com templates Legado.)" - -#: radio-station.php:488 -msgid "Show Editor Role" -msgstr "Mostrar Papel de Editor" - -#: radio-station.php:489 -msgid "" -"Since 2.3.0, a new Show Editor role has been added with Publish and Edit " -"capabilities for all Radio Station Post Types." -msgstr "" -"Desde 2.3.0, um novo papel de Editor show foi adicionado com capacidades de " -"publicação e edição para todos os tipos de post da Estação de Rádio." - -#: radio-station.php:490 -msgid "" -"You can assign this Role to any user to give them full Station Schedule " -"updating permissions." -msgstr "" -"Você pode designar este Papel à qualquer usuário para dá-los permissões " -"completas para updates da Programação da Estação." - -#: radio-station.php:507 -msgid "Add to Author Capabilities" -msgstr "Adicionar as Capacidades de Autor" - -#: radio-station.php:510 -msgid "" -"Allow users with WordPress Author role to publish and edit their own Shows " -"and Playlists." -msgstr "" -"Permitir que usuários com papel de Autores do WordPress publiquem e editem " -"seus próprios Shows e Playlists." - -#: radio-station.php:516 -msgid "Add to Editor Capabilities" -msgstr "Adicionar as Capacidades de Editor" - -#: radio-station.php:519 -msgid "" -"Allow users with WordPress Editor role to edit all Radio Station post types." -msgstr "" -"Permita que os usuários com papel do editor do WordPress editem todos os " -"tipos de publicação da Estação de Rádio." - -#: radio-station.php:543 -msgid "Pages" -msgstr "Páginas" - -#: radio-station.php:544 -msgid "Templates" -msgstr "Modelos" - -#: radio-station.php:545 -msgid "Roles" -msgstr "Papéis" - -#: radio-station.php:548 -msgid "Station" -msgstr "Estação" - -#: radio-station.php:549 -msgid "Broadcast" -msgstr "Transmissão" - -#: radio-station.php:550 -msgid "Times" -msgstr "Horários" - -#: radio-station.php:551 -msgid "Feeds" -msgstr "Feeds" - -#: radio-station.php:552 -msgid "Single Templates" -msgstr "Modelos Individuais" - -#: radio-station.php:553 -msgid "Archive Templates" -msgstr "Modelos de Arquivo" - -#: radio-station.php:554 -msgid "Schedule Page" -msgstr "Página de Programação" - -#: radio-station.php:555 -msgid "Show Pages" -msgstr "Páginas de Show" - -#: radio-station.php:556 -msgid "Archives" -msgstr "Arquivos" - -#: radio-station.php:557 -msgid "Permissions" -msgstr "Permissões" - -#: radio-station.php:577 -msgid "Rate on WordPress.org" -msgstr "Avalie em WordPress.Org" - -#: radio-station.php:1406 -msgid "DJ / Host" -msgstr "DJ / Host" - -#: radio-station.php:1418 -msgid "Show Producer" -msgstr "Produtor do Show" - -#: radio-station.php:1491 -msgid "Show Editor" -msgstr "Editor do Show" - -#: templates/admin-export.php:14 -msgid "Start Date" -msgstr "Data de Início" - -#: templates/admin-export.php:84 -msgid "End Date" -msgstr "Data de Término" - -#: templates/admin-export.php:156 -msgid "Export" -msgstr "Exportar" - -#: templates/legacy/archive-playlist.php:19 -msgid "Playlist Archive for" -msgstr "Arquivo da Playlist para" - -#: templates/legacy/archive-playlist.php:57 -msgid "Post navigation" -msgstr "Navegação nos Posts" - -#: templates/legacy/archive-playlist.php:58 -msgid "Older posts" -msgstr "Posts Antigos" - -#: templates/legacy/archive-playlist.php:59 -msgid "Newer posts" -msgstr "Posts Novos" - -#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:100 -#: templates/legacy/playlist-archive-template.php:66 -#: templates/legacy/show-blog-archive-template.php:76 -msgid "Nothing Found" -msgstr "Nada Encontrado" - -#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:104 -#: templates/legacy/playlist-archive-template.php:70 -#: templates/legacy/show-blog-archive-template.php:80 -msgid "" -"Apologies, but no results were found for the requested archive. Perhaps " -"searching will help find a related post." -msgstr "" -"Desculpas, mas nenhum resultado foi encontrado para o arquivo solicitado. " -"Talvez pesquisar irá ajudar a achar um post relacionado." - -#: templates/legacy/author.php:54 -msgid "Author Archives: %s" -msgstr "Arquivos do Author: %s" - -#: templates/legacy/author.php:74 -msgid "About %s" -msgstr "Sobre %s" - -#: templates/legacy/playlist-archive-template.php:16 -msgid "Playlist Archive" -msgstr "Arquivo da Playlist" - -#: templates/legacy/show-blog-archive-template.php:20 -msgid "Blog Archive" -msgstr "Arquivo do Blog" - -#: templates/legacy/show-blog-archive-template.php:53 -msgid "Posted by" -msgstr "Postado por" - -#: templates/legacy/single-playlist.php:30 templates/legacy/single-show.php:35 -msgid "Pages:" -msgstr "Páginas:" - -#: templates/legacy/single-playlist.php:49 -msgid "DJ Comments" -msgstr "Comentários do DJ" - -#: templates/legacy/single-playlist.php:71 -msgid "No entries for this playlist" -msgstr "Nenhuma entrada para essa playlist" - -#: templates/master-schedule-div.php:143 -#: templates/master-schedule-legacy.php:219 -#: templates/master-schedule-list.php:131 -#: templates/master-schedule-table.php:242 -#: templates/master-schedule-tabs.php:146 -msgid "encore airing" -msgstr "transmissão repetida" - -#: templates/master-schedule-div.php:151 -#: templates/master-schedule-legacy.php:232 -#: templates/master-schedule-list.php:144 -#: templates/master-schedule-table.php:254 -#: templates/master-schedule-tabs.php:159 -msgid "Audio File" -msgstr "Arquivo de Ãudio" - -#: templates/master-schedule-list.php:117 -#: templates/master-schedule-tabs.php:133 -msgid "to" -msgstr "para" - -#: templates/master-schedule-tabs.php:187 -msgid "No Shows found for this day." -msgstr "Nenhum Show encontrado para esse dia." - -#: templates/single-playlist-content.php:38 -msgid "No entries for this Playlist" -msgstr "Nenhuma entrada para essa Playlist" - -#: templates/single-show-content.php:63 -msgid "Show Website" -msgstr "Mostrar o Website" - -#: templates/single-show-content.php:75 -msgid "Email Show Host" -msgstr "Enviar Email ao Host do Show" - -#: templates/single-show-content.php:88 -msgid "Show RSS Feed" -msgstr "Feed RSS do Show" - -#: templates/single-show-content.php:191 -msgid "Download Latest Broadcast" -msgstr "Fazer Download da Última Transmissão" - -#: templates/single-show-content.php:209 -msgid "Show Info" -msgstr "Informações do Show" - -#: templates/single-show-content.php:214 -msgid "Hosted by" -msgstr "Hospedado por" - -#: templates/single-show-content.php:241 -msgid "Produced by" -msgstr "Produzido por" - -#: templates/single-show-content.php:325 -msgid "Show Times" -msgstr "Horários de Show" - -#: templates/single-show-content.php:330 -msgid "Not Currently Scheduled." -msgstr "Não Agendado no Momento." - -#: templates/single-show-content.php:360 -msgid "Timezone" -msgstr "Fuso Horário" - -#: templates/single-show-content.php:362 templates/single-show-content.php:366 -msgid "UTC" -msgstr "UTC" - -#: templates/single-show-content.php:444 -msgid "Show More" -msgstr "Mostrar Mais" - -#: templates/single-show-content.php:445 -msgid "Show Less" -msgstr "Mostrar Menos" - -#: templates/single-show-content.php:459 templates/single-show-content.php:716 -msgid "About the Show" -msgstr "Sobre o Show" - -#: templates/single-show-content.php:460 -msgid "About" -msgstr "Sobre" - -#: templates/single-show-content.php:478 -msgid "Show Episodes" -msgstr "Mostrar Episódios" - -#: templates/single-show-content.php:479 -msgid "Episodes" -msgstr "Episódios" - -#: templates/single-show-content.php:499 -msgid "Show Posts" -msgstr "Posts do Show" - -#: templates/single-show-content.php:500 -msgid "Posts" -msgstr "Posts" - -#: templates/single-show-content.php:519 -msgid "Show Playlists" -msgstr "Playlists do Show" - -#: templates/single-show-content.php:681 -msgid "Jump to" -msgstr "Pular para" - -#. Plugin URI of the plugin/theme -#. Author URI of the plugin/theme -msgid "https://netmix.com/radio-station" -msgstr "https://netmix.com/radio-station" - -#. Description of the plugin/theme -msgid "" -"Adds Show pages, DJ role, playlist and on-air programming functionality to " -"your site." -msgstr "" -"Adicionar páginas de Show, papel de DJ, playlist e funcionalidades de " -"programação no-ar ao seu site." - -#. Author of the plugin/theme -msgid "Tony Zeoli " -msgstr "Tony Zeoli " diff --git a/languages/radio-station.pot b/languages/radio-station.pot index 45a1709..a33c162 100644 --- a/languages/radio-station.pot +++ b/languages/radio-station.pot @@ -1,544 +1,1791 @@ +# Copyright (C) 2019 Radio Station +# This file is distributed under the same license as the Radio Station package. msgid "" msgstr "" -"Project-Id-Version: radio station\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-11-08 08:27-0600\n" -"PO-Revision-Date: 2013-11-08 08:27-0600\n" -"Last-Translator: Nikki \n" -"Language-Team: \n" -"Language: es_ES\n" +"Project-Id-Version: Radio Station 2.2.9\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/radio-station\n" +"POT-Creation-Date: 2019-12-12 02:24:58+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: _e;__;esc_attr_e\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Generator: Poedit 1.5.7\n" -"X-Poedit-Basepath: c:\\xampp\\htdocs\\wp352test\\wp-content\\plugins\\radio-" -"station\n" -"X-Poedit-SearchPath-0: includes\n" -"X-Poedit-SearchPath-1: templates\n" -"X-Poedit-SearchPath-2: .\n" - -#: includes/dj-on-air.php:90 includes/dj-on-air.php:664 -#: includes/playlist.php:22 includes/playlist.php:359 -#: includes/playlist.php:546 -msgid "View Playlist" +"PO-Revision-Date: 2019-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" + +#: includes/class-dj-upcoming-widget.php:13 +msgid "Display the upcoming Shows." msgstr "" -#: includes/dj-on-air.php:480 -msgid "None Upcoming" +#: includes/class-dj-upcoming-widget.php:15 +msgid "(Radio Station) Upcoming Shows" msgstr "" -#: includes/dj-on-air.php:495 -msgid "The current on-air DJ." +#: includes/class-dj-upcoming-widget.php:40 includes/class-dj-widget.php:41 +#: includes/class-playlist-widget.php:33 +msgid "Title" msgstr "" -#: includes/dj-on-air.php:496 -msgid "Radio Station: Show/DJ On-Air" +#: includes/class-dj-upcoming-widget.php:49 includes/class-dj-widget.php:50 +msgid "Link the title to the Show page" msgstr "" -#: includes/dj-on-air.php:511 includes/dj-on-air.php:738 -#: includes/playlist.php:436 -msgid "Title" +#: includes/class-dj-upcoming-widget.php:58 includes/class-dj-widget.php:59 +msgid "Above" msgstr "" -#: includes/dj-on-air.php:519 includes/dj-on-air.php:746 -msgid "Show Avatars" +#: includes/class-dj-upcoming-widget.php:59 includes/class-dj-widget.php:60 +msgid "Left" msgstr "" -#: includes/dj-on-air.php:526 -msgid "Link to the Show/DJ's profile" +#: includes/class-dj-upcoming-widget.php:60 includes/class-dj-widget.php:61 +msgid "Right" msgstr "" -#: includes/dj-on-air.php:533 includes/dj-on-air.php:767 -msgid "Display schedule info for this show" +#: includes/class-dj-upcoming-widget.php:61 includes/class-dj-widget.php:62 +msgid "Below" msgstr "" -#: includes/dj-on-air.php:540 -msgid "Display link to show's playlist" +#: includes/class-dj-upcoming-widget.php:68 includes/class-dj-widget.php:70 +msgid "Show Title Position (relative to Avatar)" msgstr "" -#: includes/dj-on-air.php:545 -msgid "Default DJ Name" +#: includes/class-dj-upcoming-widget.php:76 +msgid "Display Show Avatars" msgstr "" -#: includes/dj-on-air.php:548 includes/dj-on-air.php:761 -msgid "" -"If no Show/DJ is scheduled for the current hour, display this name/text." +#: includes/class-dj-upcoming-widget.php:82 includes/class-dj-widget.php:84 +msgid "Avatar Width" msgstr "" -#: includes/dj-on-air.php:552 includes/dj-on-air.php:779 -msgid "Time Format" +#: includes/class-dj-upcoming-widget.php:85 +msgid "Width of Show Avatars (in pixels, default 75px)" msgstr "" -#: includes/dj-on-air.php:554 includes/dj-on-air.php:781 -msgid "12-hour" +#: includes/class-dj-upcoming-widget.php:92 +msgid "Display names of the DJs on the show" msgstr "" -#: includes/dj-on-air.php:555 includes/dj-on-air.php:782 -msgid "24-hour" +#: includes/class-dj-upcoming-widget.php:100 includes/class-dj-widget.php:102 +msgid "Link DJ names to author pages" msgstr "" -#: includes/dj-on-air.php:558 -msgid "Choose time format for displayed schedules" +#: includes/class-dj-upcoming-widget.php:106 +msgid "No Additional Schedules" msgstr "" -#: includes/dj-on-air.php:722 -msgid "The upcoming DJs/Shows." +#: includes/class-dj-upcoming-widget.php:109 includes/class-dj-widget.php:143 +msgid "If no Show is scheduled for the current time, display this text." msgstr "" -#: includes/dj-on-air.php:723 -msgid "Radio Station: Upcoming DJ On-Air" +#: includes/class-dj-upcoming-widget.php:116 includes/class-dj-widget.php:110 +msgid "Display schedule info for this show" msgstr "" -#: includes/dj-on-air.php:753 -msgid "Link to Show/DJ's user profile" +#: includes/class-dj-upcoming-widget.php:122 +msgid "Limit" msgstr "" -#: includes/dj-on-air.php:758 -msgid "No Additional Schedules" +#: includes/class-dj-upcoming-widget.php:125 +msgid "Number of upcoming Shows to display." msgstr "" -#: includes/dj-on-air.php:772 -msgid "Limit" +#: includes/class-dj-upcoming-widget.php:129 includes/class-dj-widget.php:147 +msgid "Time Format" msgstr "" -#: includes/dj-on-air.php:775 -msgid "Number of upcoming DJs/Shows to display." +#: includes/class-dj-upcoming-widget.php:132 includes/class-dj-widget.php:150 +msgid "12 Hour" msgstr "" -#: includes/dj-on-air.php:785 +#: includes/class-dj-upcoming-widget.php:134 includes/class-dj-widget.php:152 +msgid "24 Hour" +msgstr "" + +#: includes/class-dj-upcoming-widget.php:138 msgid "Choose time format for displayed schedules." msgstr "" -#: includes/master_schedule.php:21 includes/master_schedule.php:22 -msgid "Schedule Override" +#: includes/class-dj-widget.php:12 +msgid "The current on-air DJ." msgstr "" -#: includes/master_schedule.php:23 includes/master_schedule.php:24 -msgid "Add Schedule Override" +#: includes/class-dj-widget.php:14 +msgid "(Radio Station) Show/DJ On-Air" msgstr "" -#: includes/master_schedule.php:25 -msgid "Edit Schedule Override" +#: includes/class-dj-widget.php:78 +msgid "Display Show Avatar" msgstr "" -#: includes/master_schedule.php:26 -msgid "New Schedule Override" +#: includes/class-dj-widget.php:87 +msgid "Width of Show Avatar (in pixels, default full width)" msgstr "" -#: includes/master_schedule.php:27 -msgid "View Schedule Override" +#: includes/class-dj-widget.php:94 +msgid "Display names of the DJs on the Show" msgstr "" -#: includes/master_schedule.php:30 -msgid "Post type for Schedule Override" +#: includes/class-dj-widget.php:118 +msgid "Display multiple schedules (if show airs more than once per week)" msgstr "" -#: includes/master_schedule.php:49 -msgid "Override Schedule" +#: includes/class-dj-widget.php:126 +msgid "Display description of show" msgstr "" -#: includes/master_schedule.php:79 -msgid "Date" +#: includes/class-dj-widget.php:134 +msgid "Display link to show's playlist" msgstr "" -#: includes/master_schedule.php:82 includes/playlist.php:756 -#: includes/playlist.php:835 -msgid "Start Time" +#: includes/class-dj-widget.php:140 +msgid "No Show Display Text" msgstr "" -#: includes/master_schedule.php:108 includes/playlist.php:782 -#: includes/playlist.php:861 -msgid "End Time" +#: includes/class-dj-widget.php:156 +msgid "Choose time format for displayed schedules" msgstr "" -#: includes/master_schedule.php:432 includes/master_schedule.php:595 -msgid "encore airing" +#: includes/class-playlist-widget.php:12 +msgid "Display currently playling playlist." msgstr "" -#: includes/master_schedule.php:437 includes/master_schedule.php:600 -msgid "Audio File" +#: includes/class-playlist-widget.php:14 +msgid "(Radio Station) Now Playing List" msgstr "" -#: includes/master_schedule.php:454 -msgid "Sun" +#: includes/class-playlist-widget.php:42 +msgid "Show Song Title" msgstr "" -#: includes/master_schedule.php:454 -msgid "Mon" +#: includes/class-playlist-widget.php:50 +msgid "Show Artist Name" msgstr "" -#: includes/master_schedule.php:454 -msgid "Tue" +#: includes/class-playlist-widget.php:58 +msgid " Show Album Name" msgstr "" -#: includes/master_schedule.php:454 -msgid "Wed" +#: includes/class-playlist-widget.php:66 +msgid "Show Record Label Name" msgstr "" -#: includes/master_schedule.php:454 -msgid "Thu" +#: includes/class-playlist-widget.php:74 +msgid "Show DJ Comments" msgstr "" -#: includes/master_schedule.php:454 -msgid "Fri" +#: includes/data-feeds.php:769 +msgid "Requested Show was not found." msgstr "" -#: includes/master_schedule.php:454 -msgid "Sat" +#: includes/data-feeds.php:770 +msgid "No Requested Shows were found." msgstr "" -#: includes/master_schedule.php:619 includes/playlist.php:572 -msgid "Genres" +#: includes/data-feeds.php:771 includes/post-types-admin.php:535 +msgid "No Shows were found." msgstr "" -#: includes/playlist.php:16 templates/single-show.php:136 -msgid "Playlists" +#: includes/data-feeds.php:813 +msgid "Requested Genre was not found." msgstr "" -#: includes/playlist.php:17 -msgid "Playlist" +#: includes/data-feeds.php:814 +msgid "No Requested Genres were found." msgstr "" -#: includes/playlist.php:18 includes/playlist.php:19 -msgid "Add Playlist" +#: includes/data-feeds.php:815 +msgid "No Genres were found." msgstr "" -#: includes/playlist.php:20 -msgid "Edit Playlist" +#: includes/data-feeds.php:858 +msgid "Requested Language was not found." msgstr "" -#: includes/playlist.php:21 -msgid "New Playlist" +#: includes/data-feeds.php:859 +msgid "No Requested Languages were found." msgstr "" -#: includes/playlist.php:25 -msgid "Post type for Playlist descriptions" +#: includes/data-feeds.php:860 +msgid "No Languages were found." msgstr "" -#: includes/playlist.php:42 -msgid "Shows" +#: includes/data-feeds.php:896 +msgid "Error 404 Not Found" msgstr "" -#: includes/playlist.php:43 includes/playlist.php:191 -msgid "Show" +#: includes/data-feeds.php:897 +msgid "The requested data could not be found." msgstr "" -#: includes/playlist.php:44 includes/playlist.php:45 -msgid "Add Show" +#: includes/master-schedule.php:260 radio-station-admin.php:136 +#: templates/master-schedule-tabs.php:176 +msgid "Genres" msgstr "" -#: includes/playlist.php:46 -msgid "Edit Show" +#: includes/master-schedule.php:276 +msgid "Click to toggle Highlight of Shows with this Genre." msgstr "" -#: includes/playlist.php:47 -msgid "New Show" +#: includes/post-types-admin.php:90 +msgid "Show Language" msgstr "" -#: includes/playlist.php:48 -msgid "View Show" +#: includes/post-types-admin.php:126 +msgid "Main Radio Language" msgstr "" -#: includes/playlist.php:51 -msgid "Post type for Show descriptions" +#: includes/post-types-admin.php:130 +msgid "Select below if Show language(s) differ." +msgstr "" + +#: includes/post-types-admin.php:158 +msgid "Remove Language" +msgstr "" + +#: includes/post-types-admin.php:166 +msgid "Select Language" msgstr "" -#: includes/playlist.php:71 +#: includes/post-types-admin.php:180 +msgid "Click on a Language to Add it." +msgstr "" + +#: includes/post-types-admin.php:258 msgid "Playlist Entries" msgstr "" -#: includes/playlist.php:93 templates/single-playlist.php:43 +#: includes/post-types-admin.php:284 includes/post-types-admin.php:659 +#: includes/shortcodes.php:1304 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:11 msgid "Artist" msgstr "" -#: includes/playlist.php:93 templates/single-playlist.php:44 +#: includes/post-types-admin.php:285 includes/post-types-admin.php:658 +#: includes/shortcodes.php:1296 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:12 msgid "Song" msgstr "" -#: includes/playlist.php:93 templates/single-playlist.php:45 +#: includes/post-types-admin.php:286 includes/shortcodes.php:1311 +#: templates/legacy/single-playlist.php:47 +#: templates/single-playlist-content.php:13 msgid "Album" msgstr "" -#: includes/playlist.php:93 templates/single-playlist.php:46 +#: includes/post-types-admin.php:287 templates/legacy/single-playlist.php:48 +#: templates/single-playlist-content.php:14 msgid "Record Label" msgstr "" -#: includes/playlist.php:93 templates/single-playlist.php:47 -msgid "DJ Comments" +#: includes/post-types-admin.php:311 includes/post-types-admin.php:353 +#: includes/shortcodes.php:1325 templates/single-playlist-content.php:15 +msgid "Comments" msgstr "" -#: includes/playlist.php:93 +#: includes/post-types-admin.php:314 includes/post-types-admin.php:354 msgid "New" msgstr "" -#: includes/playlist.php:93 +#: includes/post-types-admin.php:318 includes/post-types-admin.php:355 +#: includes/post-types-admin.php:660 msgid "Status" msgstr "" -#: includes/playlist.php:93 includes/playlist.php:127 -#: includes/playlist.php:144 includes/playlist.php:807 -#: includes/playlist.php:888 -msgid "Remove" -msgstr "" - -#: includes/playlist.php:119 includes/playlist.php:144 +#: includes/post-types-admin.php:321 includes/post-types-admin.php:356 msgid "Queued" msgstr "" -#: includes/playlist.php:123 includes/playlist.php:144 +#: includes/post-types-admin.php:323 includes/post-types-admin.php:357 msgid "Played" msgstr "" -#: includes/playlist.php:136 +#: includes/post-types-admin.php:327 includes/post-types-admin.php:359 +#: includes/post-types-admin.php:1262 includes/post-types-admin.php:1389 +msgid "Remove" +msgstr "" + +#: includes/post-types-admin.php:337 msgid "Add Entry" msgstr "" -#: includes/playlist.php:163 includes/playlist.php:164 -#: templates/single-show.php:99 +#: includes/post-types-admin.php:392 includes/post-types-admin.php:395 msgid "Schedule" msgstr "" -#: includes/playlist.php:166 includes/playlist.php:167 +#: includes/post-types-admin.php:406 includes/post-types-admin.php:409 msgid "Publish" msgstr "" -#: includes/playlist.php:170 +#: includes/post-types-admin.php:423 msgid "Submit for Review" msgstr "" -#: includes/playlist.php:171 includes/playlist.php:176 +#: includes/post-types-admin.php:426 includes/post-types-admin.php:441 msgid "Update Playlist" msgstr "" -#: includes/playlist.php:175 +#: includes/post-types-admin.php:440 msgid "Update" msgstr "" -#: includes/playlist.php:420 -msgid "Display the current song." +#: includes/post-types-admin.php:459 +msgid "Linked Show" msgstr "" -#: includes/playlist.php:421 -msgid "Radio Station: Now Playing" +#: includes/post-types-admin.php:538 +msgid "You are not assigned to any Shows." msgstr "" -#: includes/playlist.php:444 -msgid "Show Artist Name" +#: includes/post-types-admin.php:546 +msgid "Unassigned" msgstr "" -#: includes/playlist.php:451 -msgid "Show Song Title" +#: includes/post-types-admin.php:630 includes/post-types.php:49 +msgid "Show" msgstr "" -#: includes/playlist.php:458 -msgid "Show Album Name" +#: includes/post-types-admin.php:631 +msgid "Tracks" msgstr "" -#: includes/playlist.php:465 -msgid "Show Record Label Name" +#: includes/post-types-admin.php:632 +msgid "Track List" msgstr "" -#: includes/playlist.php:472 -msgid "Show DJ Comments" +#: includes/post-types-admin.php:654 +msgid "Show/Hide Tracklist" msgstr "" -#: includes/playlist.php:573 templates/single-show.php:60 -msgid "Genre" +#: includes/post-types-admin.php:716 +msgid "Related to Show" msgstr "" -#: includes/playlist.php:594 -msgid "Information" +#: includes/post-types-admin.php:758 +msgid "No Shows to Select." msgstr "" -#: includes/playlist.php:612 +#: includes/post-types-admin.php:819 +msgid "Show Information" +msgstr "" + +#: includes/post-types-admin.php:848 msgid "Active" msgstr "" -#: includes/playlist.php:614 -msgid "" -"Check this box if show is currently active (Show will not appear on " -"programming schedule if unchecked)" +#: includes/post-types-admin.php:850 +msgid "Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)" +msgstr "" + +#: includes/post-types-admin.php:852 +msgid "Website Link" msgstr "" -#: includes/playlist.php:616 -msgid "Current Audio File" +#: includes/post-types-admin.php:855 +msgid "DJ / Host Email" msgstr "" -#: includes/playlist.php:619 -msgid "DJ Email" +#: includes/post-types-admin.php:858 +msgid "Latest Audio File" msgstr "" -#: includes/playlist.php:622 -msgid "Website Link" +#: includes/post-types-admin.php:868 +msgid "Show DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:872 includes/post-types-admin.php:988 +msgid "Show Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:885 +msgid "Toggle panel: %s" +msgstr "" + +#: includes/post-types-admin.php:912 +msgid "DJs / Hosts" msgstr "" -#: includes/playlist.php:633 -msgid "DJs" +#: includes/post-types-admin.php:975 includes/post-types-admin.php:1044 +msgid "Ctrl-Click selects multiple." msgstr "" -#: includes/playlist.php:719 -msgid "Schedules" +#: includes/post-types-admin.php:1058 +msgid "Show Schedule" msgstr "" -#: includes/playlist.php:744 includes/playlist.php:824 +#: includes/post-types-admin.php:1162 includes/post-types-admin.php:1324 msgid "Day" msgstr "" -#: includes/playlist.php:747 includes/playlist.php:827 -msgid "Monday" +#: includes/post-types-admin.php:1178 includes/post-types-admin.php:1337 +#: includes/post-types-admin.php:2168 includes/post-types-admin.php:2343 +msgid "Start Time" msgstr "" -#: includes/playlist.php:748 includes/playlist.php:828 -msgid "Tuesday" +#: includes/post-types-admin.php:1210 includes/post-types-admin.php:1359 +#: includes/post-types-admin.php:2198 includes/post-types-admin.php:2344 +msgid "End Time" msgstr "" -#: includes/playlist.php:749 includes/playlist.php:829 -msgid "Wednesday" +#: includes/post-types-admin.php:1246 includes/post-types-admin.php:1381 +msgid "Encore" msgstr "" -#: includes/playlist.php:750 includes/playlist.php:830 -msgid "Thursday" +#: includes/post-types-admin.php:1256 includes/post-types-admin.php:1385 +msgid "Disabled" msgstr "" -#: includes/playlist.php:751 includes/playlist.php:831 -msgid "Friday" +#: includes/post-types-admin.php:1271 +msgid "Shift Conflicts" msgstr "" -#: includes/playlist.php:752 includes/playlist.php:832 -msgid "Saturday" +#: includes/post-types-admin.php:1297 +msgid "Warning! Show Shift Conflicts were detected!" msgstr "" -#: includes/playlist.php:753 includes/playlist.php:833 -msgid "Sunday" +#: includes/post-types-admin.php:1298 +msgid "Please note that Shifts with conflicts are automatically disabled upon saving." msgstr "" -#: includes/playlist.php:806 includes/playlist.php:886 -msgid "Encore Presentation" +#: includes/post-types-admin.php:1299 +msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." msgstr "" -#: includes/playlist.php:817 +#: includes/post-types-admin.php:1300 +msgid "Then you can uncheck the shift Disable box and Update to re-enable the Shift." +msgstr "" + +#: includes/post-types-admin.php:1312 msgid "Add Shift" msgstr "" -#: includes/playlist.php:980 -msgid "More Playlists" +#: includes/post-types-admin.php:1316 +msgid "Are you sure you want to remove this shift?" msgstr "" -#: includes/playlist.php:1041 -msgid "More Blog Posts" +#: includes/post-types-admin.php:1434 +msgid "Show Description" msgstr "" -#: templates/archive-playlist.php:15 -msgid "Playlist Archive for" +#: includes/post-types-admin.php:1451 +msgid "The text field below is for your Show Description. It will display in the About section of your Show page." msgstr "" -#: templates/archive-playlist.php:50 templates/single-playlist.php:13 -#: templates/single-show.php:13 -msgid "Post navigation" +#: includes/post-types-admin.php:1452 +msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." msgstr "" -#: templates/archive-playlist.php:51 -msgid "Older posts" +#: includes/post-types-admin.php:1453 +msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." msgstr "" -#: templates/archive-playlist.php:52 -msgid "Newer posts" +#: includes/post-types-admin.php:1454 +msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." msgstr "" -#: templates/archive-playlist.php:61 templates/author.php:93 -#: templates/playlist-archive-template.php:63 -#: templates/show-blog-archive-template.php:71 -msgid "Nothing Found" +#: includes/post-types-admin.php:1455 +msgid "You can then assign them to a relevent Post Category for display on your site also." msgstr "" -#: templates/archive-playlist.php:65 templates/author.php:97 -#: templates/playlist-archive-template.php:67 -#: templates/show-blog-archive-template.php:75 -msgid "" -"Apologies, but no results were found for the requested archive. Perhaps " -"searching will help find a related post." +#: includes/post-types-admin.php:1476 +msgid "Show Logo" msgstr "" -#: templates/author.php:51 -#, php-format -msgid "Author Archives: %s" +#: includes/post-types-admin.php:1487 +msgid "Show Images" msgstr "" -#: templates/author.php:70 -#, php-format -msgid "About %s" +#: includes/post-types-admin.php:1532 +msgid "Set Show Avatar Image" msgstr "" -#: templates/playlist-archive-template.php:16 -msgid "Playlist Archive" +#: includes/post-types-admin.php:1536 +msgid "Remove Show Avatar Image" msgstr "" -#: templates/show-blog-archive-template.php:16 -msgid "Blog Archive" +#: includes/post-types-admin.php:1568 +msgid "Set Show Header Image" msgstr "" -#: templates/show-blog-archive-template.php:46 -msgid "Posted by" +#: includes/post-types-admin.php:1572 +msgid "Remove Show Header Image" msgstr "" -#: templates/single-playlist.php:14 templates/single-show.php:14 -msgid "Previous" +#: includes/post-types-admin.php:1589 +msgid "Are you sure you want to remove this image?" msgstr "" -#: templates/single-playlist.php:15 templates/single-show.php:15 -msgid "Next" +#: includes/post-types-admin.php:1948 +msgid "Active?" msgstr "" -#: templates/single-playlist.php:32 templates/single-show.php:144 -msgid "Pages:" +#: includes/post-types-admin.php:1950 +msgid "About?" msgstr "" -#: templates/single-playlist.php:65 -msgid "No entries for this playlist" +#: includes/post-types-admin.php:1951 +msgid "Shifts" msgstr "" -#: templates/single-show.php:32 -msgid "Hosted by" +#: includes/post-types-admin.php:1953 +msgid "Hosts" msgstr "" -#: templates/single-show.php:84 -msgid "Email the DJ" +#: includes/post-types-admin.php:1954 +msgid "Show Avatar" msgstr "" -#: templates/single-show.php:88 -msgid "Show Website" +#: includes/post-types-admin.php:1970 includes/post-types-admin.php:1981 +#: includes/post-types-admin.php:2443 +msgid "Yes" msgstr "" -#: templates/single-show.php:95 -msgid "Most recent broadcast" +#: includes/post-types-admin.php:1971 includes/post-types-admin.php:1979 +#: includes/post-types-admin.php:2441 +msgid "No" msgstr "" -#: templates/single-show.php:140 -msgid "Blog Posts" +#: includes/post-types-admin.php:1999 +msgid "This Shift is Disabled." msgstr "" -#: radio-station.php:244 radio-station.php:330 -msgid "Export Playlists" +#: includes/post-types-admin.php:2004 +msgid "This Shift has Schedule Conflicts and is Disabled." msgstr "" -#: radio-station.php:244 radio-station.php:451 -msgid "Export" +#: includes/post-types-admin.php:2005 +msgid "This Shift has Schedule Conflicts." msgstr "" -#: radio-station.php:323 -msgid "Right-click and download this file to save your export" +#: includes/post-types-admin.php:2088 +msgid "Filter by show day" msgstr "" -#: radio-station.php:340 -msgid "Start Date" +#: includes/post-types-admin.php:2090 +msgid "All show days" msgstr "" -#: radio-station.php:394 -msgid "End Date" +#: includes/post-types-admin.php:2117 +msgid "Override Schedule" msgstr "" -#: radio-station.php:466 -msgid "Related to Show" +#: includes/post-types-admin.php:2162 +msgid "Date" +msgstr "" + +#: includes/post-types-admin.php:2342 +msgid "Override Date" +msgstr "" + +#: includes/post-types-admin.php:2345 +msgid "Affected Show(s) on Date" +msgstr "" + +#: includes/post-types-admin.php:2347 +msgid "Description" +msgstr "" + +#: includes/post-types-admin.php:2348 +msgid "Override Image" +msgstr "" + +#: includes/post-types-admin.php:2423 +msgid "Inactive Show" +msgstr "" + +#: includes/post-types-admin.php:2425 +msgid "Disabled Shift" +msgstr "" + +#: includes/post-types-admin.php:2506 +msgid "Filter by override date" +msgstr "" + +#: includes/post-types-admin.php:2509 +msgid "All override dates" +msgstr "" + +#: includes/post-types.php:48 includes/post-types.php:56 +#: radio-station-admin.php:131 +msgid "Shows" +msgstr "" + +#: includes/post-types.php:50 includes/post-types.php:51 +#: radio-station-admin.php:132 +msgid "Add Show" +msgstr "" + +#: includes/post-types.php:52 +msgid "Edit Show" +msgstr "" + +#: includes/post-types.php:53 +msgid "New Show" +msgstr "" + +#: includes/post-types.php:54 +msgid "View Show" +msgstr "" + +#: includes/post-types.php:61 +msgid "Post type for Show descriptions" +msgstr "" + +#: includes/post-types.php:89 includes/post-types.php:97 +#: radio-station-admin.php:134 templates/single-show-content.php:520 +msgid "Playlists" +msgstr "" + +#: includes/post-types.php:90 +msgid "Playlist" +msgstr "" + +#: includes/post-types.php:91 includes/post-types.php:92 +msgid "Add Playlist" +msgstr "" + +#: includes/post-types.php:93 +msgid "Edit Playlist" +msgstr "" + +#: includes/post-types.php:94 +msgid "New Playlist" +msgstr "" + +#: includes/post-types.php:95 includes/shortcodes.php:1337 +msgid "View Playlist" +msgstr "" + +#: includes/post-types.php:102 +msgid "Post type for Playlist descriptions" +msgstr "" + +#: includes/post-types.php:129 radio-station-admin.php:137 +msgid "Schedule Overrides" +msgstr "" + +#: includes/post-types.php:130 +msgid "Schedule Override" +msgstr "" + +#: includes/post-types.php:131 includes/post-types.php:132 +msgid "Add Schedule Override" +msgstr "" + +#: includes/post-types.php:133 +msgid "Edit Schedule Override" +msgstr "" + +#: includes/post-types.php:134 +msgid "New Schedule Override" +msgstr "" + +#: includes/post-types.php:135 +msgid "View Schedule Override" +msgstr "" + +#: includes/post-types.php:140 +msgid "Post type for Schedule Override" +msgstr "" + +#: includes/post-types.php:170 +msgid "Host Profiles" +msgstr "" + +#: includes/post-types.php:171 +msgid "Host Profile" +msgstr "" + +#: includes/post-types.php:172 includes/post-types.php:175 +msgid "New Host Profile" +msgstr "" + +#: includes/post-types.php:173 +msgid "Add Host Profile" +msgstr "" + +#: includes/post-types.php:174 +msgid "Edit Host Profile" +msgstr "" + +#: includes/post-types.php:176 +msgid "View Host Profile" +msgstr "" + +#: includes/post-types.php:177 +msgid "Show Hosts" +msgstr "" + +#: includes/post-types.php:183 +msgid "Post type for DJ / Host Profiles" +msgstr "" + +#: includes/post-types.php:212 +msgid "Producer Profiles" +msgstr "" + +#: includes/post-types.php:213 +msgid "Producer Profile" +msgstr "" + +#: includes/post-types.php:214 includes/post-types.php:217 +msgid "New Producer Profile" +msgstr "" + +#: includes/post-types.php:215 +msgid "Add Producer Profile" +msgstr "" + +#: includes/post-types.php:216 +msgid "Edit Producer Profile" +msgstr "" + +#: includes/post-types.php:218 +msgid "View Producer Profile" +msgstr "" + +#: includes/post-types.php:219 +msgid "Show Producers Profile" +msgstr "" + +#: includes/post-types.php:225 +msgid "Post type for Producer Profiles" +msgstr "" + +#: includes/post-types.php:332 +msgid "Edit" +msgstr "" + +#: includes/post-types.php:357 +msgctxt "taxonomy general name" +msgid "Genres" +msgstr "" + +#: includes/post-types.php:358 +msgctxt "taxonomy singular name" +msgid "Genre" +msgstr "" + +#: includes/post-types.php:359 +msgid "Search Genres" +msgstr "" + +#: includes/post-types.php:360 +msgid "All Genres" +msgstr "" + +#: includes/post-types.php:361 +msgid "Parent Genre" +msgstr "" + +#: includes/post-types.php:362 +msgid "Parent Genre:" +msgstr "" + +#: includes/post-types.php:363 +msgid "Edit Genre" +msgstr "" + +#: includes/post-types.php:364 +msgid "Update Genre" +msgstr "" + +#: includes/post-types.php:365 +msgid "Add New Genre" +msgstr "" + +#: includes/post-types.php:366 +msgid "New Genre Name" +msgstr "" + +#: includes/post-types.php:367 +msgid "Genre" +msgstr "" + +#: includes/post-types.php:401 +msgctxt "taxonomy general name" +msgid "Languages" +msgstr "" + +#: includes/post-types.php:402 +msgctxt "taxonomy singular name" +msgid "Language" +msgstr "" + +#: includes/post-types.php:403 +msgid "Search Languages" +msgstr "" + +#: includes/post-types.php:404 +msgid "All Languages" +msgstr "" + +#: includes/post-types.php:405 +msgid "Parent Language" +msgstr "" + +#: includes/post-types.php:406 +msgid "Parent Language:" +msgstr "" + +#: includes/post-types.php:407 +msgid "Edit Language" +msgstr "" + +#: includes/post-types.php:408 +msgid "Update Language" +msgstr "" + +#: includes/post-types.php:409 +msgid "Add New Language" +msgstr "" + +#: includes/post-types.php:410 +msgid "New Language Name" +msgstr "" + +#: includes/post-types.php:411 +msgid "Language" +msgstr "" + +#: includes/shortcodes.php:51 +msgid "Radio Time" +msgstr "" + +#: includes/shortcodes.php:61 +msgid "Your Time" +msgstr "" + +#: includes/shortcodes.php:180 +msgid "No Shows in this Genre were found." +msgstr "" + +#: includes/shortcodes.php:182 +msgid "No Shows were found to display." +msgstr "" + +#: includes/shortcodes.php:185 +msgid "No Playlists were found to display." +msgstr "" + +#: includes/shortcodes.php:187 +msgid "No Overrides were found to display." +msgstr "" + +#: includes/shortcodes.php:354 +msgid "No Genres were found to display." +msgstr "" + +#: includes/shortcodes.php:443 +msgid "No Shows in this Genre." +msgstr "" + +#: includes/shortcodes.php:599 +msgid "Published on " +msgstr "" + +#: includes/shortcodes.php:863 includes/shortcodes.php:1138 +#: templates/single-show-content.php:422 +msgid "Encore Presentation" +msgstr "" + +#: includes/shortcodes.php:876 includes/shortcodes.php:1150 +#: radio-station-admin.php:500 templates/master-schedule-legacy.php:168 +#: templates/master-schedule-list.php:75 +#: templates/master-schedule-table.php:189 +#: templates/master-schedule-tabs.php:89 +msgid "with" +msgstr "" + +#: includes/shortcodes.php:907 includes/shortcodes.php:1181 +#: templates/master-schedule-legacy.php:179 +#: templates/master-schedule-list.php:83 +#: templates/master-schedule-table.php:200 +#: templates/master-schedule-tabs.php:99 templates/single-show-content.php:229 +#: templates/single-show-content.php:256 +msgid "and" +msgstr "" + +#: includes/shortcodes.php:1084 +msgid "No Shows Upcoming" +msgstr "" + +#: includes/shortcodes.php:1318 +msgid "Label" +msgstr "" + +#: includes/shortcodes.php:1346 +msgid "No Playlist available." +msgstr "" + +#: includes/shortcodes.php:1411 +msgid "More Playlists" +msgstr "" + +#: includes/support-functions.php:1803 +msgid "Africa" +msgstr "" + +#: includes/support-functions.php:1804 +msgid "America" +msgstr "" + +#: includes/support-functions.php:1805 +msgid "Asia" +msgstr "" + +#: includes/support-functions.php:1806 +msgid "Atlantic" +msgstr "" + +#: includes/support-functions.php:1807 +msgid "Australia" +msgstr "" + +#: includes/support-functions.php:1808 +msgid "Europe" +msgstr "" + +#: includes/support-functions.php:1809 +msgid "Indian" +msgstr "" + +#: includes/support-functions.php:1810 +msgid "Pacific" +msgstr "" + +#: includes/support-functions.php:1811 +msgid "Antarctica" +msgstr "" + +#: includes/support-functions.php:1854 +msgid "WordPress Timezone" +msgstr "" + +#: includes/support-functions.php:1920 +msgid "WordPress Setting" +msgstr "" + +#: includes/support-functions.php:2853 +msgid "More Show Blog Posts" +msgstr "" + +#: loader.php:959 +msgid "Plugin Name" +msgstr "" + +#: loader.php:961 +msgid "Requires at least" +msgstr "" + +#: loader.php:961 loader.php:962 +msgid "WordPress" +msgstr "" + +#: loader.php:962 +msgid "Tested up to" +msgstr "" + +#: loader.php:963 +msgid "Stable Tag" +msgstr "" + +#: loader.php:964 +msgid "Contributors" +msgstr "" + +#: loader.php:984 +msgid "Extra Notes" +msgstr "" + +#: loader.php:1139 +msgid "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" +msgstr "" + +#: loader.php:1204 radio-station-admin.php:121 radio-station-admin.php:125 +#: radio-station-admin.php:143 +msgid "Settings" +msgstr "" + +#: loader.php:1323 +msgid "by" +msgstr "" + +#: loader.php:1332 +msgid "Readme" +msgstr "" + +#: loader.php:1334 +msgid "Docs" +msgstr "" + +#: loader.php:1335 +msgid "Support" +msgstr "" + +#: loader.php:1336 +msgid "Dev" +msgstr "" + +#: loader.php:1374 radio-station-admin.php:432 +msgid "Rate on WordPress.Org" +msgstr "" + +#: loader.php:1385 +msgid "Share the Plugin Love" +msgstr "" + +#: loader.php:1396 radio-station.php:581 +msgid "Support this Plugin" +msgstr "" + +#: loader.php:1409 +msgid "Settings Updated." +msgstr "" + +#: loader.php:1411 +msgid "Error! Settings NOT Updated." +msgstr "" + +#: loader.php:1413 +msgid "Settings Reset!" +msgstr "" + +#: loader.php:1531 radio-station.php:542 +msgid "General" +msgstr "" + +#: loader.php:1536 +msgid "Are you sure you want to reset to default settings?" +msgstr "" + +#: loader.php:1628 +msgid "Reset Settings" +msgstr "" + +#: loader.php:1630 +msgid "Save Settings" +msgstr "" + +#: loader.php:1710 +msgid "Use Ctrl and Click to Select" +msgstr "" + +#: loader.php:1737 +msgid "Available in Pro Version." +msgstr "" + +#: loader.php:1738 +msgid "Click Here to Upgrade!" +msgstr "" + +#: loader.php:1740 +msgid "Coming soon in Pro version!" +msgstr "" + +#: radio-station-admin.php:103 +msgid "You do not have permissions to access that page." +msgstr "" + +#: radio-station-admin.php:116 radio-station-admin.php:121 +#: radio-station-admin.php:419 radio-station-admin.php:480 +#: radio-station-admin.php:594 radio-station-admin.php:661 +msgid "Radio Station" +msgstr "" + +#: radio-station-admin.php:142 templates/admin-export.php:2 +msgid "Export Playlists" +msgstr "" + +#: radio-station-admin.php:144 +msgid "Help" +msgstr "" + +#: radio-station-admin.php:236 +msgid "Role Assignments" +msgstr "" + +#: radio-station-admin.php:237 +msgid "You can assign a Radio Station role to users through the WordPress User editor." +msgstr "" + +#: radio-station-admin.php:358 +msgid "Right-click and download this file to save your export" +msgstr "" + +#: radio-station-admin.php:416 +msgid "Help support us to make improvements, modifications and introduce new features!" +msgstr "" + +#: radio-station-admin.php:417 +msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" +msgstr "" + +#: radio-station-admin.php:418 +msgid "since June 2019" +msgstr "" + +#: radio-station-admin.php:420 +msgid " plugin development has been actively taken over by" +msgstr "" + +#: radio-station-admin.php:422 +msgid "We invite you to" +msgstr "" + +#: radio-station-admin.php:424 +msgid "Become a Radio Station Patreon Supporter" +msgstr "" + +#: radio-station-admin.php:425 +msgid "to make it better for everyone" +msgstr "" + +#: radio-station-admin.php:441 radio-station-admin.php:619 +#: radio-station-admin.php:690 +msgid "Dismiss this Notice" +msgstr "" + +#: radio-station-admin.php:481 +msgid "has detected" +msgstr "" + +#: radio-station-admin.php:482 +msgid "Schedule conflicts!" +msgstr "" + +#: radio-station-admin.php:486 +msgid "The following Shows have conflicting Shift times" +msgstr "" + +#: radio-station-admin.php:518 +msgid "Go to Show List" +msgstr "" + +#: radio-station-admin.php:519 +msgid "Conflicts are highlighted" +msgstr "" + +#: radio-station-admin.php:520 +msgid "in Show Shift column." +msgstr "" + +#: radio-station-admin.php:525 +msgid "This notice will persist" +msgstr "" + +#: radio-station-admin.php:526 +msgid "until conflicts are resolved." +msgstr "" + +#: radio-station-admin.php:593 +msgid "A new version of" +msgstr "" + +#: radio-station-admin.php:595 +msgid "is available." +msgstr "" + +#: radio-station-admin.php:599 +msgid "Take a moment to Upgrade for a better experience. In this update..." +msgstr "" + +#: radio-station-admin.php:605 radio-station-admin.php:674 +msgid "Update Details" +msgstr "" + +#: radio-station-admin.php:610 +msgid "Update Now" +msgstr "" + +#: radio-station-admin.php:662 +msgid "Update Notice" +msgstr "" + +#: radio-station-admin.php:667 +msgid "Thanks for Upgrading! You can enjoy these improvements now" +msgstr "" + +#: radio-station-admin.php:680 +msgid "Plugin Settings" +msgstr "" + +#: radio-station-admin.php:856 +msgid "Stay tuned! Subscribe to Radio Station's" +msgstr "" + +#: radio-station-admin.php:857 +msgid "Plugin Updates and Announcements List" +msgstr "" + +#: radio-station.php:115 +msgid "Streaming URL" +msgstr "" + +#: radio-station.php:117 +msgid "Enter the Streaming URL for your Radio Station." +msgstr "" + +#: radio-station.php:125 +msgid "Main Broadcast Language" +msgstr "" + +#: radio-station.php:127 +msgid "Select the main language used on your Radio Station." +msgstr "" + +#: radio-station.php:147 +msgid "Location Timezone" +msgstr "" + +#: radio-station.php:149 +msgid "Select your Broadcast Location for Timezone display." +msgstr "" + +#: radio-station.php:157 +msgid "12 Hour Format" +msgstr "" + +#: radio-station.php:158 +msgid "24 Hour Format" +msgstr "" + +#: radio-station.php:160 +msgid "Clock Time Format" +msgstr "" + +#: radio-station.php:162 +msgid "Default Time Format Display for plugin output. Can be overridden in each shortcode or widget." +msgstr "" + +#: radio-station.php:169 +msgid "Enable Data Routes" +msgstr "" + +#: radio-station.php:172 +msgid "Enables Station Data Routes via WordPress REST API." +msgstr "" + +#: radio-station.php:179 +msgid "Enable Data Feeds" +msgstr "" + +#: radio-station.php:182 +msgid "Enable Station Data Feeds via WordPress feed links." +msgstr "" + +#: radio-station.php:189 +msgid "Show Shift Feeds" +msgstr "" + +#: radio-station.php:192 +msgid "Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts." +msgstr "" + +#: radio-station.php:200 +msgid "Transient Caching" +msgstr "" + +#: radio-station.php:203 +msgid "Use Transient Caching to improve Schedule calculation performance." +msgstr "" + +#: radio-station.php:213 +msgid "Master Schedule Page" +msgstr "" + +#: radio-station.php:215 +msgid "Select the Page you are displaying the Master Schedule on." +msgstr "" + +#: radio-station.php:222 radio-station.php:345 radio-station.php:376 +#: radio-station.php:407 +msgid "Automatic Display" +msgstr "" + +#: radio-station.php:225 +msgid "Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: " +msgstr "" + +#: radio-station.php:232 +msgid "Schedule View Default" +msgstr "" + +#: radio-station.php:235 +msgid "Table View" +msgstr "" + +#: radio-station.php:236 +msgid "List View" +msgstr "" + +#: radio-station.php:237 +msgid "Divs View" +msgstr "" + +#: radio-station.php:238 +msgid "Tabbed View" +msgstr "" + +#: radio-station.php:239 +msgid "Legacy Table" +msgstr "" + +#: radio-station.php:241 +msgid "View type to use for automatic display on Master Schedule Page." +msgstr "" + +#: radio-station.php:249 +msgid "View Switching" +msgstr "" + +#: radio-station.php:252 +msgid "Enable View Switching on the Master Schedule." +msgstr "" + +#: radio-station.php:261 +msgid "Info Blocks Position" +msgstr "" + +#: radio-station.php:263 +msgid "Float Left" +msgstr "" + +#: radio-station.php:264 +msgid "Float Right" +msgstr "" + +#: radio-station.php:265 +msgid "Float Top" +msgstr "" + +#: radio-station.php:268 +msgid "Where to position Show info blocks relative to Show Page content." +msgstr "" + +#: radio-station.php:275 +msgid "Content Header Image" +msgstr "" + +#: radio-station.php:278 +msgid "If your template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." +msgstr "" + +#: radio-station.php:285 templates/single-show-content.php:617 +msgid "Latest Show Posts" +msgstr "" + +#: radio-station.php:290 +msgid "Number of Latest Blog Posts to Show above Show Page tabs." +msgstr "" + +#: radio-station.php:297 +msgid "Posts per Page" +msgstr "" + +#: radio-station.php:302 +msgid "Blog Posts per page on the Show Page tab." +msgstr "" + +#: radio-station.php:312 +msgid "Playlists per Page" +msgstr "" + +#: radio-station.php:314 +msgid "Playlists per page on the Show Page tab." +msgstr "" + +#: radio-station.php:335 +msgid "Show Archives Page" +msgstr "" + +#: radio-station.php:339 +msgid "Select the Page for displaying the Show archive list." +msgstr "" + +#: radio-station.php:349 +msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: radio-station.php:366 +msgid "Playlist Archives Page" +msgstr "" + +#: radio-station.php:370 +msgid "Select the Page for displaying the Playlist archive list." +msgstr "" + +#: radio-station.php:380 +msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: radio-station.php:397 +msgid "Genre Archives Page" +msgstr "" + +#: radio-station.php:401 +msgid "Select the Page for displaying the Genre archive list." +msgstr "" + +#: radio-station.php:411 +msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: radio-station.php:430 +msgid "Templates Change Note" +msgstr "" + +#: radio-station.php:431 +msgid "Since 2.3.0, the way that Templates are implemented has changed." +msgstr "" + +#: radio-station.php:432 +msgid "See the Documentation for more information:" +msgstr "" + +#: radio-station.php:433 +msgid "Templates Documentation" +msgstr "" + +#: radio-station.php:438 +msgid "Show Template" +msgstr "" + +#: radio-station.php:441 radio-station.php:464 +msgid "Theme Page Template (page.php)" +msgstr "" + +#: radio-station.php:442 radio-station.php:465 +msgid "Theme Post Template (single.php)" +msgstr "" + +#: radio-station.php:443 +msgid "Theme Singular Template (singular.php)" +msgstr "" + +#: radio-station.php:444 radio-station.php:466 +msgid "Legacy Plugin Template" +msgstr "" + +#: radio-station.php:447 +msgid "Which template to use for displaying Show content." +msgstr "" + +#: radio-station.php:452 radio-station.php:474 +msgid "Combined Method" +msgstr "" + +#: radio-station.php:456 +msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" +msgstr "" + +#: radio-station.php:461 +msgid "Playlist Template" +msgstr "" + +#: radio-station.php:469 +msgid "Which template to use for displaying Playlist content." +msgstr "" + +#: radio-station.php:478 +msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" +msgstr "" + +#: radio-station.php:488 +msgid "Show Editor Role" +msgstr "" + +#: radio-station.php:489 +msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." +msgstr "" + +#: radio-station.php:490 +msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." +msgstr "" + +#: radio-station.php:507 +msgid "Add to Author Capabilities" +msgstr "" + +#: radio-station.php:510 +msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." +msgstr "" + +#: radio-station.php:516 +msgid "Add to Editor Capabilities" +msgstr "" + +#: radio-station.php:519 +msgid "Allow users with WordPress Editor role to edit all Radio Station post types." +msgstr "" + +#: radio-station.php:543 +msgid "Pages" +msgstr "" + +#: radio-station.php:544 +msgid "Templates" +msgstr "" + +#: radio-station.php:545 +msgid "Roles" +msgstr "" + +#: radio-station.php:548 +msgid "Station" +msgstr "" + +#: radio-station.php:549 +msgid "Broadcast" +msgstr "" + +#: radio-station.php:550 +msgid "Times" +msgstr "" + +#: radio-station.php:551 +msgid "Feeds" +msgstr "" + +#: radio-station.php:552 +msgid "Single Templates" +msgstr "" + +#: radio-station.php:553 +msgid "Archive Templates" +msgstr "" + +#: radio-station.php:554 +msgid "Schedule Page" +msgstr "" + +#: radio-station.php:555 +msgid "Show Pages" +msgstr "" + +#: radio-station.php:556 +msgid "Archives" +msgstr "" + +#: radio-station.php:557 +msgid "Permissions" +msgstr "" + +#: radio-station.php:577 +msgid "Rate on WordPress.org" +msgstr "" + +#: radio-station.php:1406 +msgid "DJ / Host" +msgstr "" + +#: radio-station.php:1418 +msgid "Show Producer" +msgstr "" + +#: radio-station.php:1491 +msgid "Show Editor" +msgstr "" + +#: templates/admin-export.php:14 +msgid "Start Date" +msgstr "" + +#: templates/admin-export.php:84 +msgid "End Date" +msgstr "" + +#: templates/admin-export.php:156 +msgid "Export" +msgstr "" + +#: templates/legacy/archive-playlist.php:19 +msgid "Playlist Archive for" +msgstr "" + +#: templates/legacy/archive-playlist.php:57 +msgid "Post navigation" +msgstr "" + +#: templates/legacy/archive-playlist.php:58 +msgid "Older posts" +msgstr "" + +#: templates/legacy/archive-playlist.php:59 +msgid "Newer posts" +msgstr "" + +#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:100 +#: templates/legacy/playlist-archive-template.php:66 +#: templates/legacy/show-blog-archive-template.php:76 +msgid "Nothing Found" +msgstr "" + +#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:104 +#: templates/legacy/playlist-archive-template.php:70 +#: templates/legacy/show-blog-archive-template.php:80 +msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." +msgstr "" + +#: templates/legacy/author.php:54 +msgid "Author Archives: %s" +msgstr "" + +#: templates/legacy/author.php:74 +msgid "About %s" +msgstr "" + +#: templates/legacy/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "" + +#: templates/legacy/show-blog-archive-template.php:20 +msgid "Blog Archive" +msgstr "" + +#: templates/legacy/show-blog-archive-template.php:53 +msgid "Posted by" +msgstr "" + +#: templates/legacy/single-playlist.php:30 templates/legacy/single-show.php:35 +msgid "Pages:" +msgstr "" + +#: templates/legacy/single-playlist.php:49 +msgid "DJ Comments" +msgstr "" + +#: templates/legacy/single-playlist.php:71 +msgid "No entries for this playlist" +msgstr "" + +#: templates/master-schedule-div.php:143 +#: templates/master-schedule-legacy.php:219 +#: templates/master-schedule-list.php:131 +#: templates/master-schedule-table.php:242 +#: templates/master-schedule-tabs.php:146 +msgid "encore airing" +msgstr "" + +#: templates/master-schedule-div.php:151 +#: templates/master-schedule-legacy.php:232 +#: templates/master-schedule-list.php:144 +#: templates/master-schedule-table.php:254 +#: templates/master-schedule-tabs.php:159 +msgid "Audio File" +msgstr "" + +#: templates/master-schedule-list.php:117 +#: templates/master-schedule-tabs.php:133 +msgid "to" +msgstr "" + +#: templates/master-schedule-tabs.php:187 +msgid "No Shows found for this day." +msgstr "" + +#: templates/single-playlist-content.php:38 +msgid "No entries for this Playlist" +msgstr "" + +#: templates/single-show-content.php:63 +msgid "Show Website" +msgstr "" + +#: templates/single-show-content.php:75 +msgid "Email Show Host" +msgstr "" + +#: templates/single-show-content.php:88 +msgid "Show RSS Feed" +msgstr "" + +#: templates/single-show-content.php:191 +msgid "Download Latest Broadcast" +msgstr "" + +#: templates/single-show-content.php:209 +msgid "Show Info" +msgstr "" + +#: templates/single-show-content.php:214 +msgid "Hosted by" +msgstr "" + +#: templates/single-show-content.php:241 +msgid "Produced by" +msgstr "" + +#: templates/single-show-content.php:325 +msgid "Show Times" +msgstr "" + +#: templates/single-show-content.php:330 +msgid "Not Currently Scheduled." +msgstr "" + +#: templates/single-show-content.php:360 +msgid "Timezone" +msgstr "" + +#: templates/single-show-content.php:362 templates/single-show-content.php:366 +msgid "UTC" +msgstr "" + +#: templates/single-show-content.php:444 +msgid "Show More" +msgstr "" + +#: templates/single-show-content.php:445 +msgid "Show Less" +msgstr "" + +#: templates/single-show-content.php:459 templates/single-show-content.php:716 +msgid "About the Show" +msgstr "" + +#: templates/single-show-content.php:460 +msgid "About" +msgstr "" + +#: templates/single-show-content.php:478 +msgid "Show Episodes" +msgstr "" + +#: templates/single-show-content.php:479 +msgid "Episodes" +msgstr "" + +#: templates/single-show-content.php:499 +msgid "Show Posts" +msgstr "" + +#: templates/single-show-content.php:500 +msgid "Posts" +msgstr "" + +#: templates/single-show-content.php:519 +msgid "Show Playlists" +msgstr "" + +#: templates/single-show-content.php:681 +msgid "Jump to" +msgstr "" +#. Plugin Name of the plugin/theme +msgid "Radio Station" +msgstr "" + +#. Plugin URI of the plugin/theme +msgid "https://netmix.com/radio-station" +msgstr "" + +#. Description of the plugin/theme +msgid "Adds Show pages, DJ role, playlist and on-air programming functionality to your site." +msgstr "" + +#. Author of the plugin/theme +msgid "Tony Zeoli " +msgstr "" + +#. Author URI of the plugin/theme +msgid "https://netmix.com/radio-station" msgstr "" diff --git a/loader.php b/loader.php new file mode 100644 index 0000000..63cef2e --- /dev/null +++ b/loader.php @@ -0,0 +1,2669 @@ + array( +// 'type' => 'checkbox', +// 'default' => '1', +// 'value' => '1', +// ), +// 'optionkey2' => array( +// 'type' => 'radio', +// 'default' => 'on', +// 'options' => 'on/off', +// ), +// 'optionkey3' => array( +// 'type' => 'special', +// ), +// ); + +// ----------------------- +// Example Plugin Settings +// ----------------------- +// $slug = 'plugin-name'; // plugin slug (usually same as filename) +// $args = array( +// // --- Plugin Info --- +// 'slug' => $slug, // (uses slug above) +// 'file' => __FILE__, // path to main plugin file (required!) +// 'version' => '0.0.1', // * rechecked later (pulled from plugin header) * +// +// // --- Menus and Links --- +// 'title' => 'Plugin Name', // plugin title +// 'parentmenu' => 'wordquest', // parent menu slug +// 'home' => 'http://mysite.com/plugins/plugin/', +// 'support' => 'http://mysite.com/plugins/plugin/support/', +// 'ratetext' => __('Rate on WordPress.org'), // (overrides default rate text) +// 'share' => 'http://mysites.com/plugins/plugin/#share', // (set sharing URL) +// 'sharetext' => __('Share the Plugin Love'), // (overrides default sharing text) +// 'donate' => 'https://patreon.com/pagename', // (overrides plugin Donate URI) +// 'donatetext' => __('Support this Plugin'), // (overrides default donate text) +// 'readme' => false, // to not link to popup readme in settings page header +// 'settingsmenu' => false, // to not automatically add a settings menu [non-WQ] +// +// // --- Options --- +// 'namespace' => 'plugin_name', // plugin namespace (function prefix) +// 'settings' => 'pn', // input settings prefix +// 'option' => 'plugin_key', // plugin option key +// 'options' => $options, // plugin options array set above +// +// // --- WordPress.Org --- +// 'wporgslug' => 'plugin-slug', // WordPress.org plugin slug +// 'wporg' => false, // * rechecked later (via presence of updatechecker.php) * +// 'textdomain' => 'text-domain', // translation text domain (usually same as plugin slug) +// +// // --- Freemius --- +// 'freemius_id' => '', // Freemius plugin ID +// 'freemius_key' => '', // Freemius public key +// 'hasplans' => false, // if plugin has paid plans +// 'hasaddons' => false, // if plugin has add ons +// 'plan' => 'free', // * rechecked later (if premium version found) * +// ); +// +// ------------------------------------ +// Example Start Plugin Loader Instance +// ------------------------------------ +// (add this to your main plugin file to run this loader) +// require(dirname(__FILE__).'/loader.php'); // requires this file! +// $instance = new radio_station_loader($args); // instantiates loader class +// (ie. search and replace 'radio_station_' with 'my_plugin_' function namespace) + + +// =========================== +// --- Plugin Loader Class --- +// =========================== +// usage: change class prefix to the plugin function prefix +if ( !class_exists( 'radio_station_loader' ) ) { + class radio_station_loader { + + public $args = null; + public $namespace = null; + public $data = null; + public $version = null; + + public $options = null; + public $defaults = null; + public $tabs = array(); + public $sections = array(); + public $scripts = array(); + + // ----------------- + // Initialize Loader + // ----------------- + public function __construct( $args ) { + + // --- set plugin options --- + // 1.0.6: added options filter + $args['options'] = apply_filters( $args['namespace'] . '_options', $args['options'] ); + // 1.0.9: maybe get tabs and sections from options array + if ( isset( $args['options']['tabs'] ) ) { + $this->tabs = $args['options']['tabs']; + unset( $args['options']['tabs'] ); + } + if ( isset( $args['options']['sections'] ) ) { + $this->sections = $args['options']['sections']; + unset( $args['options']['sections'] ); + } + $this->options = $args['options']; + unset( $args['options'] ); + + // --- set plugin args and namespace --- + $this->args = $args; + $this->namespace = $args['namespace']; + + // --- setup plugin values --- + $this->setup_plugin(); + + // --- maybe transfer settings --- + $this->maybe_transfer_settings(); + + // --- load settings --- + $this->load_settings(); + + // --- load actions --- + $this->add_actions(); + + // --- load helper libraries --- + $this->load_helpers(); + + // --- autoset class instance global for accessibility --- + $GLOBALS[$args['namespace'] . '_instance'] = $this; + } + + // ------------ + // Setup Plugin + // ------------ + public function setup_plugin() { + + $args = $this->args; + $namespace = $this->namespace; + + // --- Read Plugin Header --- + if ( !isset( $args['dir'] ) ) { + $args['dir'] = dirname( $args['file'] ); + } + $fh = fopen( $args['file'], 'r' ); + $data = fread( $fh, 2048 ); + $this->data = str_replace( "\r", "\n", $data ); + fclose( $fh ); + + // --- Version --- + $this->version = $args['version'] = $this->plugin_data( 'Version:' ); + + // --- Title --- + if ( !isset( $args['title'] ) ) { + $args['title'] = $this->plugin_data( 'Plugin Name:' ); + } + + // --- Plugin Home --- + if ( !isset( $args['home'] ) ) { + $args['home'] = $this->plugin_data( 'Plugin URI:' ); + } + + // --- Author --- + if ( !isset( $args['author'] ) ) { + $args['author'] = $this->plugin_data( 'Author:' ); + } + + // --- Author URL --- + if ( !isset( $args['author_url'] ) ) { + $args['author_url'] = $this->plugin_data( 'Author URI:' ); + } + + // --- Pro Functions --- + if ( !isset( $args['proslug'] ) ) { + $proslug = $this->plugin_data( '@fs_premium_only' ); + // 1.0.1: if more than one file, extract pro slug based on the first filename + if ( !strstr( $proslug, ',' ) ) { + $profiles = array( $proslug ); + $proslug = trim( $proslug ); + } else { + $profiles = explode( ',', $proslug ); + $proslug = trim( $profiles[0] ); + } + $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension + $args['profiles'] = $profiles; + } + + // --- Update Loader Args --- + $this->args = $args; + } + + // --------------- + // Get Plugin Data + // --------------- + public function plugin_data( $key ) { + + $data = $this->data; + $value = null; + $pos = strpos( $data, $key ); + if ( false !== $pos ) { + $pos = $pos + strlen( $key ) + 1; + $tmp = substr( $data, $pos ); + $pos = strpos( $tmp, "\n" ); + $value = trim( substr( $tmp, 0, $pos ) ); + } + + return $value; + } + + // ----------------- + // Set Pro Namespace + // ----------------- + public function pro_namespace( $pronamespace ) { + $this->args['pronamespace'] = $pronamespace; + } + + + // ======================= + // --- Plugin Settings --- + // ======================= + + // -------------------- + // Get Default Settings + // -------------------- + public function default_settings( $dkey = false ) { + + // --- return defaults if already set --- + $defaults = $this->defaults; + if ( !is_null( $defaults ) ) { + if ( $dkey && isset( $defaults[$dkey] ) ) { + return $defaults[$dkey]; + } + + return $defaults; + } + + // --- filter and store the plugin default settings --- + $options = $this->options; + $defaults = array(); + foreach ( $options as $key => $values ) { + // 1.0.9: set default to null if default value not set + if ( isset( $values['default'] ) ) { + $defaults[$key] = $values['default']; + } else { + $defaults[$key] = null; + } + } + $namespace = $this->namespace; + $defaults = apply_filters( $namespace . '_default_settings', $defaults ); + $this->defaults = $defaults; + if ( $dkey && isset( $defaults[$dkey] ) ) { + return $defaults[$dkey]; + } + + return $defaults; + } + + // ------------ + // Add Settings + // ------------ + public function add_settings() { + + // --- add the default plugin settings --- + $args = $this->args; + $defaults = $this->default_settings(); + $added = add_option( $args['option'], $defaults ); + + // --- if newly added, make the defaults current settings --- + if ( $added ) { + $namespace = $this->namespace; + foreach ( $defaults as $key => $value ) { + $GLOBALS[$namespace][$key] = $value; + } + } + + // 1.0.9: trigger add settings action + do_action( $args['nsmespace'] . '_add_settings', $args ); + } + + // ----------------------- + // Maybe Transfer Settings + // ----------------------- + public function maybe_transfer_settings() { + $namespace = $this->namespace; + $funcname = $namespace . '_transfer_settings'; + + // --- check for either function prefixed or class extended method --- + if ( method_exists( $this, 'transfer_settings' ) ) { + $this->transfer_settings(); + } elseif ( function_exists( $funcname ) ) { + call_user_func( $funcname ); + } + } + + // ----------------------- + // Get All Plugin Settings + // ----------------------- + public function get_settings( $filter = true ) { + $namespace = $this->namespace; + $settings = $GLOBALS[$namespace]; + if ( $filter ) { + // 1.0.8: only apply all settings filter if filter is true + $settings = apply_filters( $namespace . '_settings', $settings ); + // 1.0.8: maybe apply all individual key value filters + foreach ( $settings as $key => $value ) { + $settings[$key] = apply_filters( $namespace . '_' . $key, $value ); + } + } + + return $settings; + } + + // ------------------ + // Get Plugin Setting + // ------------------ + public function get_setting( $key, $filter = true ) { + $args = $this->args; + $namespace = $this->namespace; + $settings = $GLOBALS[$namespace]; + $settings = apply_filters( $namespace . '_settings', $settings ); + + // --- maybe strip settings prefix --- + // 1.0.4: added for backwards compatibility + if ( substr( $key, 0, strlen( $args['settings'] ) ) == $args['settings'] ) { + $key = substr( $key, strlen( $args['settings'] ) + 1, strlen( $key ) ); + } + + // --- get plugin setting --- + if ( isset( $settings[$key] ) ) { + $value = $settings[$key]; + } else { + $defaults = $this->default_settings(); + if ( isset( $defaults[$key] ) ) { + $value = $defaults[$key]; + } else { + $value = null; + } + } + if ( $filter ) { + $value = apply_filters( $namespace . '_' . $key, $value ); + } + + return $value; + } + + // --------------------- + // Reset Plugin Settings + // --------------------- + public function reset_settings() { + + $args = $this->args; + $namespace = $this->namespace; + + // --- check reset triggers --- + // 1.0.2: fix to namespace key typo in isset check + // 1.0.3: only use namespace not settings key + // 1.0.9: check page is set and matches slug + if ( !isset( $_REQUEST['page'] ) ) { + return; + } + if ( $_REQUEST['page'] != $args['slug'] ) { + return; + } + if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { + return; + } + if ( 'reset' != $_POST[$args['namespace'] . '_update_settings'] ) { + return; + } + + // --- check reset permissions --- + $capability = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' ); + if ( !current_user_can( $capability ) ) { + return; + } + + // --- verify nonce --- + // $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' ); + check_admin_referer( $args['slug'] . '_update_settings' ); + + // --- reset plugin settings --- + $defaults = $this->default_settings(); + $defaults['savetime'] = time(); + update_option( $args['option'], $defaults ); + + // --- loop to remerge with settings global --- + foreach ( $defaults as $key => $value ) { + $GLOBALS[$namespace][$key] = $value; + } + + // --- set settings reset message flag --- + $_GET['updated'] = 'reset'; + } + + // ---------------------- + // Update Plugin Settings + // ---------------------- + public function update_settings() { + + $args = $this->args; + $namespace = $this->namespace; + $settings = $GLOBALS[$namespace]; + + // --- check update triggers --- + // 1.0.2: fix to namespace key typo in isset check + // 1.0.3: only use namespace not settings key + // 1.0.9: check page is set and matches slug + if ( !isset( $_REQUEST['page'] ) ) { + return; + } + if ( $_REQUEST['page'] != $args['slug'] ) { + return; + } + if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { + return; + } + if ( 'yes' != $_POST[$args['namespace'] . '_update_settings'] ) { + return; + } + + // --- check update permissions --- + $capability = apply_filters( $namespace . '_manage_options_capability', 'manage_options' ); + if ( !current_user_can( $capability ) ) { + return; + } + + // --- verify nonce --- + // $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' ); + check_admin_referer( $args['slug'] . '_update_settings' ); + + // --- get plugin options and default settings --- + // 1.0.9: allow filtering of plugin options (eg. for Pro/Add Ons) + $options = $this->options; + $options = apply_filters( $namespace . '_plugin_options', $options ); + $defaults = $this->default_settings(); + + // --- maybe use custom method or function --- + $funcname = $namespace . '_process_settings'; + if ( method_exists( $this, 'process_settings' ) ) { + + // --- use class extended method if found --- + $settings = $this->process_settings(); + + } elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) { + + // --- use namespace prefixed function if found --- + $settings = call_user_func( $funcname ); + + } else { + + // --- loop plugin options to get new settings --- + foreach ( $options as $key => $values ) { + + // --- get option type and options --- + $type = $values['type']; + $valid = $validate_args = array(); + if ( isset( $values['options'] ) ) { + $valid = $values['options']; + } + + // --- get posted value --- + // 1.0.6: set null value for unchecked checkbox fix + $posted = null; + $postkey = $args['settings'] . '_' . $key; + if ( isset( $_POST[$postkey] ) ) { + $posted = $_POST[$postkey]; + } + $newsettings = false; + + // --- maybe validate special options --- + // 1.0.9: check for special options to prepare + if ( is_string( $valid ) ) { + + // --- maybe get public post type slugs --- + if ( in_array( $valid, array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) { + $valid = array(); + if ( !isset( $public ) ) { + $cpts = array( 'page', 'post' ); + $cptargs = array( 'public' => true, '_builtin' => false ); + $cptlist = get_post_types( $cptargs, 'names', 'and' ); + $public = array_merge( $cpts, $cptlist ); + } + foreach ( $public as $cpt ) { + $valid[$cpt] = ''; + } + } + + // --- maybe get post type slugs --- + if ( in_array( $valid, array( 'POSTTYPE', 'POSTTYPES' ) ) ) { + $valid = array(); + if ( !isset( $cpts ) ) { + $cpts = array( 'page', 'post' ); + $cptargs = array( 'public' => true, '_builtin' => false ); + $cptlist = get_post_types( $cptargs, 'names', 'and' ); + $cpts = array_merge( $cpts, $cptlist ); + } + foreach ( $cpts as $cpt ) { + $valid[$cpt] = ''; + } + } + + // --- maybe get all post type slugs --- + if ( in_array( $valid, array( 'ALLTYPE', 'ALLTYPES' ) ) ) { + $valid = array(); + if ( !isset( $allcpts ) ) { + $cptargs = array( '_builtin' => false ); + $allcpts = get_post_types( $cptargs, 'names', 'and' ); + } + foreach ( $allcpts as $cpt ) { + $valid[$cpt] = ''; + } + } + } + + if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + echo 'Saving Setting Key ' . $key . ' (' . $postkey . ': ' . print_r( $posted, true ) . '
    '; + echo 'Type: ' . $type . ' - Valid Options ' . $key . ': ' . print_r( $valid, true ) . '
    '; + } + + // --- sanitize value according to type --- + if ( strstr( $type, '/' ) ) { + + // --- implicit radio / select --- + $valid = explode( '/', $type ); + if ( in_array( $posted, $valid ) ) { + $settings[$key] = $posted; + } + + } elseif ( ( 'checkbox' == $type ) || ( 'toggle' == $type ) ) { + + // --- checkbox / toggle --- + // 1.0.6: fix to new unchecked checkbox value + // 1.0.9: maybe validate to specified checkbox value + if ( isset( $values['value'] ) ) { + $valid = array( $values['value'] ); + } else { + $valid = array( 'yes', '1', 'checked', 'on' ); + } + if ( in_array( $posted, $valid ) ) { + $settings[$key] = $posted; + } elseif ( is_null( $posted ) ) { + $settings[$key] = ''; + } + + } elseif ( 'textarea' == $type ) { + + // --- text area --- + // TODO: maybe use sanitize text field ? + $posted = stripslashes( $posted ); + $settings[$key] = $posted; + + } elseif ( 'text' == $type ) { + + // --- text field (slug) --- + // 1.0.9: move text field sanitization to validation + if ( !is_string( $valid ) ) { + $valid = 'TEXT'; + } + $newsettings = $posted; + + } elseif ( ( 'number' == $type ) || ( 'numeric' == $type ) ) { + + // --- number field value --- + // 1.0.9: added support for number step, minimum and maximum + $newsettings = $posted; + $valid = 'NUMERIC'; + if ( isset( $values['step'] ) ) { + $validate_args['step'] = $values['step']; + } + if ( isset( $values['min'] ) ) { + $validate_args['min'] = $values['min']; + } + if ( isset( $values['max'] ) ) { + $validate_args['max'] = $values['max']; + } + + } elseif ( 'multicheck' == $type ) { + + // --- process multicheck boxes --- + // 1.0.9: added multicheck input type + // note: needs defined options (but works with post types) + $posted = array(); + foreach ( $valid as $option => $label ) { + $optionkey = $args['settings'] . '_' . $key . '-' . $option; + if ( isset( $_POST[$optionkey] ) && ( 'yes' == $_POST[$optionkey] ) ) { + if ( isset( $values['value'] ) ) { + $posted[$option] = $values['value']; + } else { + $posted[$option] = 'yes'; + } + } else { + $posted[$option] = ''; + } + } + $settings[$key] = $posted; + + } elseif ( 'csv' == $type ) { + + // -- comma separated values --- + // 1.0.4: added comma separated values option + $values = array(); + if ( strstr( $posted, ',' ) ) { + $posted = explode( ',', $posted ); + } else { + $posted[0] = $posted; + } + foreach ( $posted as $i => $value ) { + $posted[$i] = trim( $value ); + } + if ( is_string( $valid ) ) { + $newsettings = $posted; + } elseif ( is_array( $valid ) ) { + foreach ( $posted as $i => $value ) { + if ( !in_array( $value, $valid ) ) { + unset( $posted[$i] ); + } + } + $settings[$key] = implode( ',', $posted ); + } else { + $settings[$key] = implode( ',', $posted ); + } + + } elseif ( ( 'radio' == $type ) || ( 'select' == $type ) ) { + + // --- explicit radio or select value --- + if ( is_string( $valid ) ) { + $newsettings = $posted; + } elseif ( is_array( $valid ) && array_key_exists( $posted, $valid ) ) { + $settings[$key] = $posted; + } + + } elseif ( 'multiselect' == $type ) { + + // --- multiselect values --- + // 1.0.9: added multiselect value saving + $newsettings = array_values( $posted ); + + } + + if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + echo 'New Settings for Key ' . $key . ': '; + if ( $newsettings ) { + echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; + } else { + echo '(validated) ' . print_r( $settings[$key], true ) . '
    '; + } + } + + // --- maybe validate new settings --- + if ( $newsettings ) { + if ( is_array( $newsettings ) ) { + + // --- validate array of settings --- + foreach ( $newsettings as $newkey => $newvalue ) { + $newsetting = $this->validate_setting( $newvalue, $valid, $validate_args ); + if ( $newsetting && ( '' != $newsetting ) ) { + $newsettings[$newkey] = $newsetting; + } else { + unset( $newsettings[$newkey] ); + } + } + if ( 'csv' == $type ) { + $settings[$key] = implode( ',', $newsettings ); + } else { + $settings[$key] = $newsettings; + } + } else { + // --- validate single setting --- + $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args ); + if ( $newsetting ) { + $settings[$key] = $newsetting; + } + } + + if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + echo 'Valid Options for Key ' . $key . ': ' . print_r( $valid, true ) . '
    '; + echo 'Validated Settings for Key ' . $key . ': ' . print_r( $settings[$key], true ) . '
    '; + } + } + + } + } + + // --- process special settings --- + // 1.0.2: added for processing special settings separately + $funcname = $namespace . '_process_special'; + if ( method_exists( $this, 'process_special' ) ) { + + // --- use class extended method if found --- + $settings = $this->process_special( $settings ); + + } elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) { + + // --- use namespace prefixed function if found --- + $settings = call_user_func( $funcname, $settings ); + } + + if ( $settings && is_array( $settings ) ) { + + // --- loop default keys to remove others --- + $settings_keys = array_keys( $defaults ); + foreach ( $settings as $key => $value ) { + if ( !in_array( $key, $settings_keys ) ) { + unset( $settings[$key] ); + } + } + + // --- update the plugin settings --- + $settings['savetime'] = time(); + update_option( $args['option'], $settings ); + + // --- merge with existing settings for pageload --- + foreach ( $settings as $key => $value ) { + $GLOBALS[$namespace][$key] = $value; + } + + // --- set settings update message flag --- + $_GET['updated'] = 'yes'; + + } else { + $_GET['updated'] = 'no'; + } + + // --- maybe trigger update of Pro settings --- + if ( method_exists( $this, 'pro_update_settings' ) ) { + $this->pro_update_settings(); + } else { + if ( isset( $args['pronamespace'] ) ) { + $funcname = $args['pronamespace'] . '_update_settings'; + } else { + $funcname = $args['namespace'] . '_pro_update_settings'; + } + if ( function_exists( $funcname ) ) { + call_user_func( $funcname ); + } + } + + } + + // ----------------------- + // Validate Plugin Setting + // ----------------------- + public function validate_setting( $posted, $valid, $args ) { + + // --- allow for clearing of a field --- + // note: array values are cleared if empty, single values are set empty + if ( trim( $posted ) == '' ) { + return ''; + } + + // --- validate different data types --- + if ( 'TEXT' == $valid ) { + + // --- sanitize text field --- + $posted = sanitize_text_field( $posted ); + + return $posted; + + } elseif ( 'ALPHABETIC' == $valid ) { + + // --- alphabetic --- + // 1.0.9: added alphabetic-only for completeness + $posted = trim( $posted ); + $checkposted = preg_match( '/^[a-zA-Z]+$/', $posted ); + if ( $checkposted ) { + return $posted; + } + + } elseif ( 'NUMERIC' == $valid ) { + + // --- number (numeric text) --- + // note: step key is only used for controls, not for validation + // 1.0.9: cast to integer not absolute integer + // TODO: validate step value match ? + $posted = floatval( trim( $posted ) ); + if ( isset( $args['min'] ) && ( $posted < $args['min'] ) ) { + return $args['min']; + } elseif ( isset( $args['max'] ) && ( $posted > $args['max'] ) ) { + return $args['max']; + } elseif ( 0 === $posted ) { + return 0; + } + + return $posted; + + } elseif ( 'ALPHANUMERIC' == $valid ) { + + // --- alphanumeric text only --- + $posted = trim( $posted ); + // TODO: maybe remove underscore from validation regex ? + $checkposted = preg_match( '/^[a-zA-Z0-9_]+$/', $posted ); + if ( $checkposted ) { + return $posted; + } + + } elseif ( in_array( $valid, array( 'URL', 'URLS' ) ) ) { + + // --- URL address --- + // 1.0.6: fix to type variable typo (vtype) + // 1.0.4: added validated URL option + // 1.0.6: fix to posted variable type (vposted) + // 1.0.9: remove check for http prefix to allow other protocols + $posted = trim( $posted ); + $url = filter_var( $posted, FILTER_SANITIZE_STRING ); + if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) { + $posted = ''; + } + + return $posted; + + } elseif ( in_array( $valid, array( 'EMAIL', 'EMAILS' ) ) ) { + + // --- email address --- + // 1.0.3: added email option type checking + $email = sanitize_email( trim( $posted ) ); + if ( $email ) { + $posted = $email; + } else { + $posted = ''; + } + + return $posted; + + } elseif ( in_array( $valid, array( 'USERNAME', 'USERNAMES' ) ) ) { + + // --- username --- + $username = sanitize_user( trim( $posted ) ); + if ( !$username ) { + return ''; + } + $user = get_user_by( 'login', $username ); + if ( $user ) { + $posted = $username; + } else { + $posted = ''; + } + + return $posted; + + } elseif ( in_array( $valid, array( 'USERID', 'USERIDS' ) ) ) { + + // --- user ID --- + $userid = intval( trim( $posted ) ); + if ( 0 === $userid ) { + return ''; + } + $user = get_user_by( 'ID', $userid ); + if ( $user ) { + $posted = $userid; + } else { + $posted = ''; + } + + return $posted; + + } elseif ( in_array( $valid, array( 'SLUG', 'SLUGS' ) ) ) { + + // -- post slugs --- + $posted = sanitize_title( trim( $posted ) ); + + return $posted; + + } elseif ( in_array( $valid, array( 'PAGEID', 'PAGEIDS', 'POSTID', 'POSTIDS' ) ) ) { + + $posted = intval( trim( $posted ) ); + if ( 0 === $posted ) { + return ''; + } + $post = get_post( $posted ); + if ( $post ) { + return $posted; + } + } + + return false; + } + + // --------------- + // Delete Settings + // --------------- + public function delete_settings() { + // TODO: check for plugin settings flag to delete settings data? + // $delete_settings = $this->get_setting( 'delete_settings' ); + // if ( $delete_settings ) { + // $args = $this->args; + // delete_option( $args['option'] ); + // } + // $delete_data = $this->get_setting( 'delete_data' ); + // if ( $delete_data ) { + // do_action( $this->namespace.'_delete_data' ); + // } + } + + + // =============== + // --- Loading --- + // =============== + + // -------------------- + // Load Plugin Settings + // -------------------- + public function load_settings() { + $args = $this->args; + $namespace = $this->namespace; + $GLOBALS[$namespace] = $args; + $settings = get_option( $args['option'], false ); + if ( $settings && is_array( $settings ) ) { + foreach ( $settings as $key => $value ) { + $GLOBALS[$namespace][$key] = $value; + } + } else { + $defaults = $this->default_settings(); + foreach ( $defaults as $key => $value ) { + $GLOBALS[$namespace][$key] = $value; + } + } + } + + // ----------- + // Add Actions + // ----------- + public function add_actions() { + + $args = $this->args; + $namespace = $this->namespace; + + // --- add settings on activation --- + register_activation_hook( $args['file'], array( $this, 'add_settings' ) ); + + // --- always check for update and reset of settings --- + add_action( 'admin_init', array( $this, 'update_settings' ) ); + add_action( 'admin_init', array( $this, 'reset_settings' ) ); + + // --- add plugin submenu --- + add_action( 'admin_menu', array( $this, 'settings_menu' ), 1 ); + + // --- add plugin settings page link --- + add_filter( 'plugin_action_links', array( $this, 'plugin_links' ), 10, 2 ); + + // --- maybe delete settings on deactivation --- + register_deactivation_hook( $args['file'], array( $this, 'delete_settings' ) ); + + // --- maybe load thickbox --- + add_action( 'admin_enqueue_scripts', array( $this, 'maybe_load_thickbox' ) ); + + // --- AJAX readme viewer --- + add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) ); + } + + // --------------------- + // Load Helper Libraries + // --------------------- + public function load_helpers() { + + $args = $this->args; + $file = $args['file']; + $dir = $args['dir']; + + // --- Plugin Slug --- + if ( !isset( $args['slug'] ) ) { + $args['slug'] = substr( $file, 0, - 4 ); + $this->args = $args; + } + + // --- Pro Functions --- + $plan = 'free'; + // 1.0.2: added prototype auto-loading of Pro file(s) + // (to work with @fs_premium_only file list) + if ( count( $args['profiles'] ) > 0 ) { + $included = get_included_files(); + foreach ( $args['profiles'] as $profile ) { + // --- chech for php extension --- + if ( substr( '.php' == $profile, - 4, 4 ) ) { + $filepath = $dir . '/' . $profile; + if ( file_exists( $filepath ) ) { + $plan = 'premium'; + // 1.0.9: add check if file already included + if ( !in_array( $filepath, $included ) ) { + include $filepath; + } + } + } + } + } + $args['plan'] = $plan; + $this->args = $args; + + // --- Plugin Update Checker --- + // note: lack of updatechecker.php file indicates WordPress.Org SVN repo version + // presence of updatechecker.php indicates direct site download or GitHub version + $wporg = true; + $updatechecker = $dir . '/updatechecker.php'; + if ( file_exists( $updatechecker ) ) { + $wporg = false; + $slug = $args['slug']; + // note: requires $file and $slug to be defined + include $updatechecker; + } + $args['wporg'] = $wporg; + $this->args = $args; + + // --- Trigger Loader Helpers Action --- + // 1.0.9: added action for extra loader helpers (eg. WordQuest) + do_action( $args['namespace'] . '_loader_helpers', $args ); + + // --- Freemius (requires PHP 5.4+) --- + if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) { + $this->load_freemius(); + } + + } + + // ------------------- + // Maybe Load Thickbox + // ------------------- + public function maybe_load_thickbox() { + $args = $this->args; + if ( isset( $_REQUEST['page'] ) && ( $_REQUEST['page'] == $args['slug'] ) ) { + add_thickbox(); + } + } + + // ------------- + // Readme Viewer + // ------------- + public function readme_viewer() { + + $args = $this->args; + $dir = $args['dir']; + + echo ""; + + // 1.0.7: changed readme.php to reader.php (for Github) + $readme = $dir . '/readme.txt'; + $contents = file_get_contents( $readme ); + $parser = $dir . '/reader.php'; + + if ( file_exists( $parser ) ) { + + // --- include Markdown Readme Parser --- + include $parser; + + // --- remove license info as causes breakage! --- + // TODO: find line start and end to handle other possible licenses + $contents = str_replace( 'License: GPLv2 or later', '', $contents ); + $contents = str_replace( 'License URI: http://www.gnu.org/licenses/gpl-2.0.html', '', $contents ); + + // --- instantiate Parser class --- + $readme = new WordPress_Readme_Parser(); + $parsed = $readme->parse_readme_contents( $contents ); + + // --- output plugin info --- + echo "" . esc_html( __( 'Plugin Name' ) ) . ": " . esc_html( $parsed['name'] ) . "
    "; + // echo "" . esc_html( __( 'Tags' ) ) . ": " . esc_html( implode( ', ', $parsed['tags'] ) ) . "
    "; + echo "" . esc_html( __( 'Requires at least' ) ) . ": " . esc_html( __( 'WordPress' ) ) . " v" . esc_html( $parsed['requires_at_least'] ) . "
    "; + echo "" . esc_html( __( 'Tested up to' ) ) . ": " . esc_html( __( 'WordPress' ) ) . " v" . esc_html( $parsed['tested_up_to'] ) . "
    "; + if ( isset( $parsed['stable_tag'] ) ) { + echo "" . esc_html( __( 'Stable Tag' ) ) . ": " . esc_html( $parsed['stable_tag'] ) . "
    "; + } + echo "" . esc_html( __( 'Contributors' ) ) . ": " . esc_html( implode( ', ', $parsed['contributors'] ) ) . "
    "; + // echo "Donate Link: ".$parsed['donate_link']."
    "; + echo "
    " . $parsed['short_description'] . "

    "; // phpcs:ignore WordPress.Security.OutputNotEscaped + + // --- output sections --- + // possible sections: 'description', 'installation', 'frequently_asked_questions', + // 'screenshots', 'changelog', 'change_log', 'upgrade_notice' + $strip = array( 'installation', 'screenshots' ); + foreach ( $parsed['sections'] as $key => $section ) { + if ( !empty( $section ) && !in_array( $key, $strip ) ) { + if ( strstr( $key, '_' ) ) { + $parts = explode( '_', $key ); + } else { + $parts = array( $key ); + } + foreach ( $parts as $i => $part ) { + $parts[$i] = strtoupper( substr( $part, 0, 1 ) ) . substr( $part, 1 ); + } + $title = implode( ' ', $parts ); + echo "

    " . esc_html( $title ) . "

    "; + echo $section; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + } + if ( isset( $parsed['remaining_content'] ) && !empty( $remaining_content ) ) { + echo "

    " . esc_html( __( 'Extra Notes' ) ) . "

    "; + echo $parsed['remaining_content']; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + } else { + // --- fallback text-only display --- + $contents = str_replace( "\n", "
    ", $contents ); + echo $contents; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + echo ""; + exit; + } + + + // ======================= + // --- Freemius Loader --- + // ======================= + // + // required settings keys: + // ----------------------- + // freemius_id - plugin ID from Freemius plugin dashboard + // freemius_key - public key from Freemius plugin dashboard + // + // optional settings keys: + // ----------------------- + // plan - (string) curent plugin plan (value of 'free' or 'premium') + // hasplans - (boolean) switch for whether plugin has premium plans + // hasaddons - (boolean) switch for whether plugin has premium addons + // wporg - (boolean) switch for whether free plugin is WordPress.org compliant + // contact - (boolean) submenu switch for plugin Contact (defaults to on for premium only) + // support - (boolean) submenu switch for plugin Support (default on) + // account - (boolean) submenu switch for plugin Account (default on) + // parentmenu - (string) optional slug for plugin parent menu + // + // okay lets do this... + // ==================== + public function load_freemius() { + + $args = $this->args; + $namespace = $this->namespace; + + // --- check for required Freemius keys --- + if ( !isset( $args['freemius_id'] ) || !isset( $args['freemius_key'] ) ) { + return; + } + + // --- check for free / premium plan --- + // convert plan string value of 'free' or 'premium' to boolean premium switch + // TODO: check for active addons also ? + $premium = false; + if ( isset( $args['plan'] ) && ( 'premium' == $args['plan'] ) ) { + $premium = true; + } + + // --- maybe redirect link to plugin support forum --- + // TODO: change to use new Freemius 2.3.0 support link filter ? + if ( isset( $_REQUEST['page'] ) && ( $args['slug'] . '-wp-support-forum' == $_REQUEST['page'] ) && is_admin() ) { + if ( !function_exists( 'wp_redirect' ) ) { + include ABSPATH . WPINC . '/pluggable.php'; + } + if ( isset( $args['support'] ) ) { + // changes the support forum slug for premium based on the pro plugin file slug + // 1.0.7: fix support URL undefined variable warning + $support_url = $args['support']; + if ( $premium ) { + $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); + } + $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] ); + wp_redirect( $support_url ); + exit; + } + } + + // --- do the Freemius Loading boogie --- + if ( !isset( $args['freemius'] ) ) { + + // --- start the Freemius SDK --- + if ( !class_exists( 'Freemius' ) ) { + $freemiuspath = dirname( __FILE__ ) . '/freemius/start.php'; + if ( !file_exists( $freemiuspath ) ) { + return; + } + require_once $freemiuspath; + } + + // --- set defaults for optional key values --- + if ( !isset( $args['type'] ) ) { + $args['type'] = 'plugin'; + } + if ( !isset( $args['hasaddons'] ) ) { + $args['hasaddons'] = false; + } + if ( !isset( $args['hasplans'] ) ) { + $args['hasplans'] = false; + } + if ( !isset( $args['wporg'] ) ) { + $args['wporg'] = false; + } + + // --- set defaults for options submenu key values --- + // 1.0.2: fix to isset check keys + // 1.0.5: fix to set args subkeys for support and account + if ( !isset( $args['support'] ) ) { + $args['support'] = true; + } + if ( !isset( $args['account'] ) ) { + $args['account'] = true; + } + // by default, enable contact submenu item for premium plugins only + if ( !isset( $args['contact'] ) ) { + $args['contact'] = $premium; + } + + // --- set Freemius settings from plugin settings --- + $first_path = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); + $first_path = add_query_arg( 'welcome', 'true', $first_path ); + $settings = array( + 'type' => $args['type'], + 'slug' => $args['slug'], + 'id' => $args['freemius_id'], + 'public_key' => $args['freemius_key'], + 'has_addons' => $args['hasaddons'], + 'has_paid_plans' => $args['hasplans'], + 'is_org_compliant' => $args['wporg'], + 'is_premium' => $premium, + 'menu' => array( + 'slug' => $args['slug'], + 'first-path' => $first_path, + 'contact' => $args['contact'], + 'support' => $args['support'], + 'account' => $args['account'], + ), + ); + + // --- maybe add plugin submenu to parent menu --- + if ( isset( $args['parentmenu'] ) ) { + $settings['menu']['parent'] = array( 'slug' => $args['parentmenu'] ); + } + + // --- filter settings before initializing --- + $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings ); + if ( !$settings || !is_array( $settings ) ) { + return; + } + + // --- initialize Freemius now --- + $freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings ); + + // --- set plugin basename --- + // 1.0.1: set free / premium plugin basename + if ( method_exists( $freemius, 'set_basename' ) ) { + $freemius->set_basename( $premium, $args['file'] ); + } + + // --- add Freemius connect message filter --- + $this->freemius_connect(); + } + } + + // ----------------------- + // Filter Freemius Connect + // ----------------------- + public function freemius_connect() { + $namespace = $this->args['namespace']; + $freemius = $GLOBALS[$namespace . '_freemius']; + if ( isset( $settings['freemius'] ) && is_object( $freemius ) && method_exists( $freemius, 'add_filter' ) ) { + $freemius->add_filter( 'connect_message', array( $this, 'freemius_connect_message' ), WP_FS__DEFAULT_PRIORITY, 6 ); + } + } + + // ------------------------ + // Freemius Connect Message + // ------------------------ + public function freemius_connect_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) { + // default: 'Never miss an important update - opt-in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' + // 1.0.9: fix to remove incorrect first argument in string replacement + $message = __fs( 'hey-x' ) . '
    '; + $message .= sprintf( + __( "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" ), + '' . $plugin_title . '', + '' . $user_login . '', + $site_link, + $freemius_link + ); + + return $message; + } + + // ---------------------- + // Connect Update Message + // ---------------------- + // TODO: message for connect updates + public function freemius_update_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) { + // default: 'Please help us improve %1$s! If you opt-in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' + // $message = freemius_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ); + // TODO: check if message needs return here ? + return $message; + } + + + // ============= + // --- Admin --- + // ============= + + // ----------------- + // Add Settings Menu + // ----------------- + public function settings_menu() { + + $args = $this->args; + $namespace = $this->namespace; + $settings = $GLOBALS[$namespace]; + + // --- filter capability --- + $args['capability'] = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' ); + if ( !isset( $args['pagetitle'] ) ) { + $args['pagetitle'] = $args['title']; + } + if ( !isset( $args['menutitle'] ) ) { + $args['menutitle'] = $args['title']; + } + + // --- trigger filter plugin menu action --- + // (can hook into this to add an admin menu manually using the provided loader args) + // return true from filter function to not add a submenu item in admin Settings menu + // 1.0.8: change from function exists check + // 1.0.9: change to filter usage to check if menu is manually added + $menuadded = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args ); + + // --- maybe auto-add standalone options page --- + if ( !$menuadded ) { + // 1.0.8: check settingsmenu switch that disables automatic settings menu + if ( !isset( $args['settingsmenu'] ) || ( false !== $args['settingsmenu'] ) ) { + add_options_page( $args['pagetitle'], $args['menutitle'], $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' ); + } + } + } + + // ----------------- + // Plugin Page Links + // ----------------- + public function plugin_links( $links, $file ) { + + $args = $this->args; + if ( plugin_basename( $args['file'] ) == $file ) { + + // --- add settings link --- + $settings_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); + $settings_link = "" . esc_html( __( 'Settings' ) ) . ""; + array_unshift( $links, $settings_link ); + + // --- maybe add Pro upgrade link --- + // TODO: check for correct upgrade/addon URLs + // if ( isset( $args['hasplans'] && $args['hasplans'] ) { + // // TODO: check if premium is already installed + // $upgrade_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); + // $upgrade_link = "" . esc_html( __('Upgrade to Pro' ) ) . ""; + // array_unshift( $links, $upgrade_link ); + // } + + // --- maybe add Addons link --- + //if ( isset($args['hasaddons'] && $args['hasaddons' ) { + // $addons_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); + // $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; + // array_unshift( $links, $addons_link ); + // } + + } + + return $links; + } + + // ----------- + // Message Box + // ----------- + public function message_box( $message, $echo ) { + $box = ""; + $box .= ""; + $box .= "
    "; + $box .= "
    "; + $box .= $message; + $box .= "
    "; + $box .= "
    "; + if ( $echo ) { + echo $box; // phpcs:ignore WordPress.Security.OutputNotEscaped + } else { + return $box; + } + return ''; + } + + // ------------------ + // Plugin Page Header + // ------------------ + public function settings_header() { + + $args = $this->args; + $namespace = $this->namespace; + $settings = $GLOBALS[$namespace]; + + // --- output debug values --- + if ( isset( $_REQUEST['debug'] ) ) { + if ( ( 'yes' == $_REQUEST['debug'] ) || ( '1' == $_REQUEST['debug'] ) ) { + + echo "
    Current Settings:
    "; + print_r( $settings ); + echo "

    "; + + echo "
    Plugin Options:
    "; + print_r( $this->options ); + echo "

    "; + + if ( isset( $_POST ) ) { + echo "
    Posted Values:
    "; + foreach ( $_POST as $key => $value ) { + echo esc_attr( $key ) . ': ' . print_r( $value, true ) . '
    '; + } + } + } + } + + // --- check for animated gif icon with fallback to normal icon --- + // 1.0.9: fix to check if PNG file exists + $icon_url = false; + if ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.gif' ) ) { + $icon_url = plugins_url( 'images/' . $args['slug'] . '.gif', $args['file'] ); + } elseif ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.png' ) ) { + $icon_url = plugins_url( 'images/' . $args['slug'] . '.png', $args['file'] ); + } + $icon_url = apply_filters( $namespace . '_plugin_icon_url', $icon_url ); + + // --- check for author icon based on provided author name --- + // 1.0.2: check if author icon file exists and fallback + $author_icon_url = false; + $author_slug = strtolower( str_replace( ' ', '', $args['author'] ) ); + if ( file_exists( $this->args['dir'] . '/images/' . $author_slug . '.png' ) ) { + $author_icon_url = plugins_url( 'images/' . $author_slug . '.png', $args['file'] ); + } elseif ( file_exists( $this->args['dir'] . '/images/wordquest.png' ) ) { + $author_icon_url = plugins_url( 'images/wordquest.png', $args['file'] ); + } + $author_icon_url = apply_filters( $namespace . '_author_icon_url', $author_icon_url ); + + // --- plugin header styles --- + echo ""; + + // --- open header table --- + echo ""; + + // --- plugin icon --- + echo ""; + + echo ""; + + // --- output updated and reset messages --- + if ( isset( $_GET['updated'] ) ) { + if ( 'yes' == $_GET['updated'] ) { + $message = $settings['title'] . ' ' . __( 'Settings Updated.' ); + } elseif ( 'no' == $_GET['updated'] ) { + $message = __( 'Error! Settings NOT Updated.' ); + } elseif ( 'reset' == $_GET['updated'] ) { + $message = $settings['title'] . ' ' . __( 'Settings Reset!' ); + } + if ( isset( $message ) ) { + echo ""; + } + } else { + // --- maybe output welcome message --- + if ( isset( $_REQUEST['welcome'] ) && ( 'true' == $_REQUEST['welcome'] ) ) { + if ( isset( $args['welcome'] ) ) { + echo ""; + } + } + } + + echo "
    "; + if ( $icon_url ) { + echo ""; + } + echo ""; + + echo ""; + + // --- plugin version --- + echo ""; + + echo "
    "; + + // --- plugin title --- + echo "

    " . esc_html( $args['title'] ) . "

    "; + + echo "

    v" . esc_html( $args['version'] ) . "

    "; + + echo "
    "; + + // ---- plugin author --- + // 1.0.8: check if author URL is set + if ( isset( $args['author_url'] ) ) { + echo "" . esc_html( __( 'by' ) ) . " "; + echo "" . esc_html( $args['author'] ) . "

    "; + } + + // --- readme / docs / support links --- + // 1.0.8: use filtered links array with automatic separator + $links = array(); + if ( !isset( $args['readme'] ) || ( false !== $args['readme'] ) ) { + $readme_url = add_query_arg( 'action', $namespace . '_readme_viewer', admin_url( 'admin-ajax.php' ) ); + $links[] = "" . esc_html( __( 'Readme' ) ) . ""; + } + if ( isset( $args['docs'] ) ) { + $links[] = "" . esc_html( __( 'Docs' ) ) . ""; + } + if ( isset( $args['support'] ) ) { + $links[] = "" . esc_html( __( 'Support' ) ) . ""; + } + if ( isset( $args['development'] ) ) { + $links[] = "" . esc_html( __( 'Dev' ) ) . ""; + } + + // 1.0.9: change filter from _plugin_links to disambiguate + $links = apply_filters( $args['namespace'] . '_plugin_admin_links', $links ); + if ( count( $links ) > 0 ) { + echo implode( ' | ', $links ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + // --- author icon --- + if ( $author_icon_url ) { + echo "
    "; + + // 1.0.8: check if author URL is set for link + if ( isset( $args['author_url'] ) ) { + echo ""; + } + echo ""; + if ( isset( $args['author_url'] ) ) { + echo ""; + } + } + + echo "
    "; + + echo "
    "; + + echo "
    "; + + // --- plugin supporter links --- + // 1.0.1: set rate/share/donate links and texts + // 1.0.8: added filters for rate/share/donate links + echo "
    "; + + // --- Rate link --- + if ( isset( $args['wporgslug'] ) ) { + if ( isset( $args['rate'] ) ) { + $rate_url = $args['rate']; + } elseif ( isset( $args['type'] ) && ( 'theme' == $args['type'] ) ) { + $rate_url = 'https://wordpress.org/support/theme/' . $args['wporgslug'] . '/reviews/#new-post'; + } else { + $rate_url = 'https://wordpress.org/plugins/' . $args['wporgslug'] . '/reviews/#new-post'; + } + if ( isset( $args['ratetext'] ) ) { + $rate_text = $args['ratetext']; + } else { + $rate_text = __( 'Rate on WordPress.Org' ); + } + $rate_link = ""; + $rate_link .= " "; + $rate_link .= esc_html( $rate_text ) . "

    "; + $rate_link = apply_filters( $args['namespace'] . '_rate_link', $rate_link, $args ); + if ( $rate_link ) { + echo $rate_link; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + } + + // --- Share link --- + if ( isset( $args['share'] ) ) { + if ( isset( $args['sharetext'] ) ) { + $share_text = $args['sharetext']; + } else { + $share_text = __( 'Share the Plugin Love' ); + } + $share_link = ""; + $share_link .= " "; + $share_link .= esc_html( $share_text ) . "

    "; + $share_link = apply_filters( $args['namespace'] . '_share_link', $share_link, $args ); + if ( $share_link ) { + echo $share_link; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + } + + // --- Donate link --- + if ( isset( $args['donate'] ) ) { + if ( isset( $args['donatetext'] ) ) { + $donate_text = $args['donatetext']; + } else { + $donate_text = __( 'Support this Plugin' ); + } + $donate_link = ""; + $donate_link .= " "; + $donate_link .= "" . esc_html( $donate_text ) . "

    "; + $donate_link = apply_filters( $args['namespace'] . '_donate_link', $donate_link, $args ); + if ( $donate_link ) { + echo $donate_link; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + } + + echo "
    "; + echo $this->message_box( $message, false ); // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "
    "; + echo $this->message_box( $args['welcome'], false ); + echo "

    "; + } + + // ------------- + // Settings Page + // ------------- + public function settings_page() { + + $namespace = $this->namespace; + + // --- open page wrapper --- + echo "
    "; + + do_action( $namespace . '_admin_page_top' ); + + // --- output settings header --- + $this->settings_header(); + + do_action( $namespace . '_admin_page_middle' ); + + // --- output settings table --- + $this->settings_table(); + + do_action( $namespace . '_admin_page_bottom' ); + + // --- close page wrapper --- + echo "
    "; + } + + // -------------- + // Settings Table + // -------------- + // 1.0.9: created automatic Plugin Settings page + // (based on passed plugin options and default settings) + public function settings_table() { + + // --- get all options and settings (unfiltered) --- + $args = $this->args; + $namespace = $this->namespace; + $options = $this->options; + $defaults = $this->default_settings(); + $settings = $this->get_settings( false ); + + // --- get option tabs and sections --- + $tabs = $this->tabs; + $sections = $this->sections; + + $currenttab = ''; + if ( isset( $settings['settingstab'] ) ) { + $currenttab = $settings['settingstab']; + } + + // --- loop options to maybe get tabbed groupings --- + $taboptions = array(); + foreach ( $options as $key => $option ) { + if ( isset( $option['tab'] ) && array_key_exists( $option['tab'], $tabs ) ) { + if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) { + $taboptions[$option['tab']][$option['section']][$key] = $option; + } else { + $taboptions[$option['tab']]['general'][$key] = $option; + } + } else { + if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) { + $taboptions['general'][$option['section']][$key] = $option; + } else { + $taboptions['general']['general'][$key] = $option; + } + } + } + + // --- maybe push general section to top of tab --- + foreach ( $taboptions as $tab => $tabsections ) { + if ( isset( $tabsections['general'] ) && ( count( $tabsections ) > 1 ) ) { + $general = $tabsections['general']; + unset( $tabsections['general'] ); + $taboptions[$tab] = array_merge( $general, $tabsections ); + } + } + + // --- maybe output tab groupings --- + if ( count( $tabs ) > 0 ) { + + // --- output tab switcher script --- + // 1.0.9: add to settings scripts + $script = "function show_settings_tab(tab) {" . PHP_EOL; + foreach ( $tabs as $tab => $label ) { + $script .= " document.getElementById('" . esc_js( $tab ) . "-tab-button').className = 'settings-tab-button inactive';" . PHP_EOL; + $script .= " document.getElementById('" . esc_js( $tab ) . "-tab').className = 'settings-tab inactive'; " . PHP_EOL; + } + $script .= " document.getElementById(tab+'-tab-button').className = 'settings-tab-button active';" . PHP_EOL; + $script .= " document.getElementById(tab+'-tab').className = 'settings-tab active';" . PHP_EOL; + $script .= " document.getElementById('settings-tab').value = tab;" . PHP_EOL; + $script .= "}"; + $this->scripts[] = $script; + + $i = 0; + echo "
      "; + foreach ( $tabs as $tab => $tablabel ) { + $class = 'inactive'; + if ( ( $tab == $currenttab ) || ( ( '' == $currenttab ) && ( 0 == $i ) ) ) { + $class = 'active'; + } + echo "
    • " . esc_html( $tablabel ) . "
    • "; + $i ++; + } + echo "
    "; + } else { + $tabs = array( 'general' => __( 'General' ) ); + } + + // --- reset to default script --- + // 1.0.9: add to settings scripts + $confirmreset = __( 'Are you sure you want to reset to default settings?' ); + $script = "function resettodefaults() { + agree = confirm('" . esc_js( $confirmreset ) . "'); if (!agree) {return false;} + document.getElementById('settings-action').value = 'reset'; + document.getElementById('settings-form').submit(); + }"; + $this->scripts[] = $script; + + // --- start settings form --- + echo "
    "; + echo ""; + echo ""; + wp_nonce_field( $args['slug'] . '_update_settings' ); + + // --- maybe set hidden debug input --- + if ( isset( $_REQUEST['debug'] ) ) { + if ( ( 'yes' == $_REQUEST['debug'] ) || ( '1' == $_REQUEST['debug'] ) ) { + echo ""; + } + } + + // ---- open wrapbox --- + echo "
    "; + echo "
    "; + + // --- output tabbed sections --- + $i = 0; + foreach ( $tabs as $tab => $tablabel ) { + + // --- open tab table output --- + $class = 'inactive'; + if ( ( $currenttab == $tab ) || ( ( '' == $currenttab ) && ( 0 == $i ) ) ) { + $class = 'active'; + } + echo "
    "; + + do_action( $namespace . '_admin_page_tab_' . $tab . '_top' ); + + echo ""; + + if ( count( $sections ) > 0 ) { + + $sectionheadings = array(); + foreach ( $sections as $section => $sectionlabel ) { + + if ( array_key_exists( $section, $taboptions[$tab] ) ) { + + // --- section top --- + ob_start(); + do_action( $namespace . '_admin_page_section_' . $section ); + $output = ob_get_clean(); + if ( $output ) { + echo ""; + } + + // --- section heading --- + if ( !isset( $sectionheadings[$section] ) ) { + echo ""; + echo ""; + echo ""; + $sectionheadings[$section] = true; + } + + // --- section setting rows --- + foreach ( $taboptions[$tab][$section] as $key => $option ) { + $option['key'] = $key; + echo $this->setting_row( $option ); // phpcs:ignore WordPress.Security.OutputNotEscaped + } + echo ""; + + // --- section bottom hook --- + ob_start(); + do_action( $namespace . '_admin_page_section_' . $section ); + $output = ob_get_clean(); + if ( $output ) { + echo ""; + } + + } + + } + } else { + foreach ( $taboptions[$tab]['general'] as $key => $option ) { + $option['key'] = $key; + echo ""; + echo $this->setting_row( $option ); // phpcs:ignore WordPress.Security.OutputNotEscaped + echo ""; + } + } + + // --- reset/save settings buttons --- + // (filtered so removable from any specific tab) + $buttons = ""; + $buttons .= ""; + $buttons .= ""; + $buttons = apply_filters( $namespace . '_admin_save_buttons', $buttons, $tab ); + if ( $buttons ) { + echo $buttons; // phpcs:ignore WordPress.Security.OutputNotEscaped + } + + // --- close table --- + echo "
    "; + echo $output; // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "

    " . esc_html( $sectionlabel ) . "

    "; + echo $output; + echo "
    "; + $buttons .= ""; + $buttons .= ""; + $buttons .= ""; + $buttons .= "
    "; + + // --- do below tab action --- + do_action( $namespace . '_admin_page_tab_' . $tab . '_bottom' ); + + // --- close tab output --- + echo "
    "; + + $i ++; + } + + // --- close wrapbox --- + echo "
    "; + + // --- close settings form --- + echo "
    "; + + // --- number input step script --- + // 1.0.9: added to script array + $script = "function number_step(updown, id, min, max, step) { + if (updown == 'up') {multiplier = 1;} + if (updown == 'down') {multiplier = -1;} + current = parseInt(document.getElementById(id).value); + newvalue = current + (multiplier * parseInt(step)); + if (newvalue < parseInt(min)) {newvalue = min;} + if (newvalue > parseInt(max)) {newvalue = max;} + document.getElementById(id).value = newvalue; + }"; + $this->scripts[] = $script; + + // --- enqueue settings scripts --- + add_action( 'admin_footer', array( $this, 'setting_scripts' ) ); + + // --- enqueue settings styles --- + add_action( 'admin_footer', array( $this, 'setting_styles' ) ); + + } + + // ------------ + // Setting Row + // ------------ + // 1.0.9: added for automatic Settings table generation + public function setting_row( $option ) { + + // --- prepare setting keys --- + $args = $this->args; + $namespace = $this->namespace; + $postkey = $args['settings']; + $name = $postkey . '_' . $option['key']; + $type = $option['type']; + $setting = $this->get_setting( $option['key'], false ); + + // --- convert old option type names --- + if ( 'email' == $type ) { + $type = 'text'; + $option['options'] = 'EMAIL'; + } elseif ( 'emails' == $type ) { + $type = 'text'; + $option['options'] = 'EMAIL'; + } elseif ( 'url' == $type ) { + $type = 'text'; + $option['options'] = 'URL'; + } elseif ( 'urls' == $type ) { + $type = 'text'; + $option['options'] = 'URL'; + } elseif ( 'alpabetic' == $type ) { + $type = 'text'; + $option['options'] = 'ALPHABETIC'; + } elseif ( 'alphanumeric' == $type ) { + $type = 'text'; + $option['options'] = 'ALPHANUMERIC'; + } elseif ( 'numeric' == $type ) { + $option['options'] = 'NUMERIC'; + } elseif ( 'numeric' == $type ) { + $option['options'] = 'NUMERIC'; + } elseif ( 'usernames' == $type ) { + $type = 'text'; + $option['options'] = 'USERNAME'; + } elseif ( 'csvslugs' == $type ) { + $type = 'text'; + $option['options'] = 'SLUG'; + } elseif ( 'pageid' == $type ) { + $type = 'select'; + $option['options'] = 'PAGEID'; + } elseif ( 'postid' == $type ) { + $type = 'select'; + $option['options'] = 'POSTID'; + } elseif ( 'pageids' == $type ) { + $type = 'multiselect'; + $option['options'] = 'PAGEIDS'; + } elseif ( 'postids' == $type ) { + $type = 'multiselect'; + $option['options'] = 'POSTIDS'; + } + + // --- prepare row output --- + $row = ""; + + $row .= "" . $option['label']; + if ( 'multiselect' == $type ) { + $row .= "
    " . esc_html( __( 'Use Ctrl and Click to Select' ) ) . ""; + } + $row .= ""; + + // 1.0.9: added multiple cell spanning note type + if ( ( 'note' == $type ) || ( 'info' == $type ) || ( 'helper' == $type ) ) { + + $row .= ""; + if ( isset( $option['helper'] ) ) { + $row .= $option['helper']; + } + $row .= ""; + + } else { + + // TODO: add check if already Pro version ? + if ( isset( $option['pro'] ) && $option['pro'] ) { + + // --- Pro version setting (teaser) --- + $row .= ""; + $upgrade_link = false; + if ( $args['hasplans'] || $args['hasaddons'] ) { + $upgrade_link = add_query_arg( 'page=', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); + $target = ''; + } elseif ( isset( $args['upgrade_link'] ) ) { + $upgrade_link = $args['upgrade_link']; + $target = " target='_blank'"; + } + if ( $upgrade_link ) { + $row .= __( 'Available in Pro Version.' ) . '
    '; + $row .= "" . esc_html( __( 'Click Here to Upgrade!' ) ) . ""; + } else { + $row .= esc_html( __( 'Coming soon in Pro version!' ) ); + } + $row .= ""; + + } else { + + $row .= ""; + + // --- maybe prepare special options --- + if ( isset( $option['options'] ) && is_string( $option['options'] ) ) { + + // --- maybe prepare post/page options (once) --- + if ( in_array( $option['options'], array( 'POSTID', 'POSTIDS', 'PAGEID', 'PAGEIDS' ) ) ) { + + $posttype = strtolower( substr( $option['options'], 0, 4 ) ); + if ( ( ( 'page' == $posttype ) && !isset( $pageoptions ) ) + || ( ( 'post' == $posttype ) && !isset( $postoptions ) ) ) { + $pageoptions = $postoptions = array( '' => '' ); + global $wpdb; + $query = "SELECT ID,post_title,post_status FROM " . $wpdb->prefix . "posts "; + $query .= " WHERE post_type = %s AND post_status != 'auto-draft'"; + $results = $wpdb->get_results( + $wpdb->prepare( $query, $posttype ), ARRAY_A + ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + if ( strlen( $result['post_title'] ) > 35 ) { + $result['post_title'] = substr( $result['post_title'], 0, 35 ) . '...'; + } + $label = $result['ID'] . ': ' . $result['post_title']; + if ( 'publish' != $result['post_status'] ) { + $label .= ' (' . $result['post_status'] . ')'; + } + if ( 'page' == $posttype ) { + $pageoptions[$result['ID']] = $label; + } elseif ( 'post' == $posttype ) { + $postoptions[$result['ID']] = $label; + } + } + } + } + if ( 'page' == $posttype ) { + $option['options'] = $pageoptions; + } elseif ( 'post' == $posttype ) { + $option['options'] = $postoptions; + } + } + + // --- maybe prepare public post type options (once) --- + if ( in_array( $option['options'], array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) { + if ( !isset( $publicoptions ) ) { + $cpts = array( 'page', 'post' ); + $args = array( 'public' => true, '_builtin' => false ); + $cptlist = get_post_types( $args, 'names', 'and' ); + $cpts = array_merge( $cpts, $cptlist ); + foreach ( $cpts as $cpt ) { + $posttypeobject = get_post_type_object( $cpt ); + $label = $posttypeobject->labels->singular_name; + $publicoptions[$cpt] = $label; + } + } + $option['options'] = $publicoptions; + } + + // --- maybe prepare post type options (once) --- + if ( in_array( $option['options'], array( 'POSTTYPE', 'POSTTYPES' ) ) ) { + if ( !isset( $cptoptions ) ) { + $cpts = array( 'page', 'post' ); + $args = array( '_builtin' => false ); + $cptlist = get_post_types( $args, 'names', 'and' ); + $cpts = array_merge( $cpts, $cptlist ); + foreach ( $cpts as $cpt ) { + $posttypeobject = get_post_type_object( $cpt ); + $label = $posttypeobject->labels->singular_name; + $cptoptions[$cpt] = $label; + } + } + $option['options'] = $cptoptions; + } + + // --- maybe prepare all post type options (once) --- + if ( in_array( $option['options'], array( 'ALLTYPE', 'ALLTYPES' ) ) ) { + if ( !isset( $allcptoptions ) ) { + $args = array( '_builtin' => true ); + $cpts = get_post_types( $args, 'names', 'and' ); + foreach ( $cpts as $cpt ) { + $posttypeobject = get_post_type_object( $cpt ); + $label = $posttypeobject->labels->singular_name; + $allcptoptions[$cpt] = $label; + } + } + $option['options'] = $allcptoptions; + } + + // --- maybe prepare user options (once) --- + if ( in_array( $option['options'], array( 'USERID', 'USERIDS', 'USERNAME', 'USERNAMES' ) ) ) { + if ( in_array( $option['options'], array( 'USERID', 'USERIDS' ) ) ) { + $userkey = 'userid'; + } else { + $userkey = 'username'; + } + $option['options'] = array( '' => '' ); + global $wpdb; + $query = "SELECT ID,user_login,display_name FROM " . $wpdb->prefix . "users"; + $results = $wpdb->get_results( $query, ARRAY_A ); + if ( $results && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $label = $result['ID'] . ': ' . $result['display_name']; + if ( $result['display_name'] != $result['user_login'] ) { + $label .= ' (' . $result['user_login'] . ')'; + } + if ( 'userid' == $userkey ) { + $useroptions[$result['ID']] = $label; + } elseif ( 'username' == $userkey ) { + $useroptions[$result['user_login']] = $label; + } + } + } + $option['options'] = array_merge( $option['options'], $useroptions ); + } + + } + + // --- output option input types --- + if ( 'toggle' == $type ) { + + // --- toggle --- + // 1.0.9: add toggle input (styled checkbox) + if ( $setting == $option['value'] ) { + $checked = ' checked'; + } else { + $checked = ''; + } + $row .= ""; + if ( isset( $option['suffix'] ) ) { + $row .= " " . $option['suffix']; + } + + } elseif ( 'checkbox' == $type ) { + + // --- checkbox --- + if ( $setting == $option['value'] ) { + $checked = ' checked'; + } else { + $checked = ''; + } + $row .= ""; + if ( isset( $option['suffix'] ) ) { + $row .= " " . $option['suffix']; + } + + } elseif ( 'multicheck' == $type ) { + + // --- multicheck boxes --- + $checkboxes = array(); + foreach ( $option['options'] as $key => $label ) { + if ( is_array( $setting ) && in_array( $key, $setting ) ) { + $checked = ' checked'; + } else { + $checked = ''; + } + $checkboxes[] = " " . esc_html( $label ); + } + $row .= implode( "
    ", $checkboxes ); + + } elseif ( 'radio' == $type ) { + + // --- radio buttons --- + $radios = array(); + foreach ( $option['options'] as $value => $label ) { + if ( $setting === $value ) { + $checked = " checked"; + } else { + $checked = ''; + } + $radios[] = " " . esc_html( $label ); + } + $row .= implode( '
    ', $radios ); + + } elseif ( 'select' == $type ) { + + // --- select dropdown --- + $row .= ""; + if ( isset( $option['suffix'] ) ) { + $row .= " " . $option['suffix']; + } + + } elseif ( 'multiselect' == $type ) { + + // --- multiselect dropdown --- + $row .= ""; + if ( isset( $option['suffix'] ) ) { + $row .= " " . $option['suffix']; + } + + } elseif ( 'text' == $type ) { + + // --- text inputs --- + $class = 'setting-text'; + if ( 'text' != $type ) { + $class .= ' setting-' . $type; + } + if ( isset( $option['placeholder'] ) ) { + $placeholder = $option['placeholder']; + } else { + $placeholder = ''; + } + $row .= ""; + if ( isset( $option['suffix'] ) ) { + $row .= " " . $option['suffix']; + } + + } elseif ( 'textarea' == $type ) { + + // --- textarea input --- + if ( isset( $option['rows'] ) ) { + $rows = $option['rows']; + } else { + $rows = '6'; + } + if ( isset( $option['placeholder'] ) ) { + $placeholder = $option['placeholder']; + } else { + $placeholder = ''; + } + $row .= "'; + exit; + } } } diff --git a/includes/support-functions.php b/includes/support-functions.php index 4b8cdca..765efa4 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -334,6 +334,10 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true ) // 2.3.2: added date data for next day $nextday = radio_station_get_next_day( $day ); $nextdate = $weekdates[$nextday]; + // 2.3.2: fix midnight timestamp for sorting + if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { + $midnight = radio_station_to_time( $nextdate . ' 00:00:00' ); + } $all_shifts[$nextday][$midnight . '.' . $show->ID] = array( 'day' => $nextday, 'date' => $nextdate, @@ -1015,7 +1019,7 @@ function radio_station_get_current_schedule( $time = false ) { } // --- apply overrides to the schedule --- - $debugday = 'Tuesday'; + $debugday = 'Monday'; if ( isset( $_REQUEST['debug-day'] ) ) { $debugday = $_REQUEST['debug-day']; } @@ -1846,9 +1850,6 @@ function radio_station_check_shifts( $all_shifts ) { // --- get previous and next days for comparisons --- // 2.3.2: fix to use week date schedule - // $thisdate = date( 'Y-m-d', strtotime( $now ) ); - // $prevdate = date( 'Y-m-d', strtotime( $thisdate ) - ( 24 * 60 * 60 ) ); - // $nextdate = date( 'Y-m-d', strtotime( $thisdate ) + ( 24 * 60 * 60 ) ); $thisdate = $weekdates[$day]; $date_time = radio_station_to_time( $weekdates[$day] . ' 00:00' ); @@ -1868,7 +1869,7 @@ function radio_station_check_shifts( $all_shifts ) { $disabled = true; } - // --- account for midnight times --- + // --- account for split midnight times --- // 2.3.2: replace strtotime with to_time for timezones if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { $start_time = radio_station_to_time( $thisdate . ' 00:00' ); @@ -1956,42 +1957,45 @@ function radio_station_check_shifts( $all_shifts ) { } } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { - // --- set the previous shift end time to current shift start --- - $conflict = 'overlap'; + if ( ( $end_time > $prev_start_time ) || ( $end_time > prev_end_time ) ) { - // --- modify only if this shift is not disabled --- - if ( !$disabled ) { - // 2.3.2: variable type fix (from checked_shift) - // 2.3.2: fix for midnight starting aplit shifts - // 2.3.2: set checked shifts with day key directly - if ( '00:00 am' == $prev_shift['start'] ) { - $prev_shift['start'] = '12:00 am'; - } - $checked_shifts[$day][$prev_shift['start']]['end'] = $shift['start']; - $checked_shifts[$day][$prev_shift['start']]['trimmed'] = true; + // --- set the previous shift end time to current shift start --- + $conflict = 'overlap'; - if ( RADIO_STATION_DEBUG ) { - echo "Previous Previous Shift: " . print_r( $prev_prev_shift, true ); - } + // --- modify only if this shift is not disabled --- + if ( !$disabled ) { + // 2.3.2: variable type fix (from checked_shift) + // 2.3.2: fix for midnight starting aplit shifts + // 2.3.2: set checked shifts with day key directly + if ( '00:00 am' == $prev_shift['start'] ) { + $prev_shift['start'] = '12:00 am'; + } + $checked_shifts[$day][$prev_shift['start']]['end'] = $shift['start']; + $checked_shifts[$day][$prev_shift['start']]['trimmed'] = true; - // --- fix for real end of first part of previous split shift --- - if ( isset( $prev_shift['split'] ) && $prev_shift['split'] && isset( $prev_shift['real_start'] ) ) { - if ( isset( $prev_prev_shift ) && isset( $prev_prev_shift['split'] ) && $prev_prev_shift['split'] ) { - $checked_shifts[$prev_prev_shift['start']]['real_end'] = $shift['start']; - $checked_shifts[$prev_prev_shift['start']]['trimmed'] = true; + if ( RADIO_STATION_DEBUG ) { + echo "Previous Previous Shift: " . print_r( $prev_prev_shift, true ); + } + + // --- fix for real end of first part of previous split shift --- + if ( isset( $prev_shift['split'] ) && $prev_shift['split'] && isset( $prev_shift['real_start'] ) ) { + if ( isset( $prev_prev_shift ) && isset( $prev_prev_shift['split'] ) && $prev_prev_shift['split'] ) { + $checked_shifts[$prev_prev_shift['start']]['real_end'] = $shift['start']; + $checked_shifts[$prev_prev_shift['start']]['trimmed'] = true; + } } } - } - // --- conflict debug output --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; - $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $start_time ) . ' ]'; - $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; - $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $prev_end_time ) . ' ]'; - // $debug .= "Shift: " . print_r( $shift, true ); - // $debug .= "Previous Shift: " . print_r( $prev_shift, true ); - radio_station_debug( $debug ); + // --- conflict debug output --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; + $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $start_time ) . ' ]'; + $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; + $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $prev_end_time ) . ' ]'; + // $debug .= "Shift: " . print_r( $shift, true ); + // $debug .= "Previous Shift: " . print_r( $prev_shift, true ); + radio_station_debug( $debug ); + } } } } @@ -2221,10 +2225,10 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $end_time = radio_station_convert_shift_time( $end_time ); // 2.3.2: use next week day instead of date - // $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - // $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - $shift_start_time = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $start_time ); - $shift_end_time = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $end_time ); + $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + // $shift_start_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $start_time ); + // $shift_end_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $end_time ); if ( $shift_end_time < $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -2254,10 +2258,10 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $shift_end = radio_station_convert_shift_time( $day_shift['end'] ); // 2.3.2: use next week day instead of date - // $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_start ); - // $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_end ); - $day_shift_start_time = radio_station_to_time( 'next ' . $day_shift['day'] . ' ' . $shift_start ); - $day_shift_end_time = radio_station_to_time( 'next ' . $day_shift['day'] . ' ' . $shift_end ); + $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_start ); + $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_end ); + // $day_shift_start_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_start ); + // $day_shift_end_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_end ); // 2.3.2: adjust for midnight with change to use non-split shifts if ( $day_shift_end_time < $day_shift_start_time ) { $day_shift_end_time = $day_shift_end_time + ( 24 * 60 * 60 ); @@ -2285,7 +2289,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // [external] or starts before but ends after the existing shift end time // [equal] or new shift starts at the same time as the existing shift // [internal] or if the new shift starts after existing shift and ends before it ends - // [overlap] of the new shift starts after the existing shift and before it ends + // [overlap] of the new shift starts after the existing shift but before it ends // ...then there is a shift overlap conflict if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) @@ -2303,7 +2307,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } // --- recheck for first shift overlaps --- - // (for date based shift rechecking) + // (not implemented as not needed) /* if ( isset( $first_shift ) ) { // --- check for first shift overlap using next week --- $shift_start = radio_station_convert_shift_time( $first_shift['start'] ); @@ -2327,10 +2331,10 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { echo "^^^ CONFLICT ^^^" . PHP_EOL; } } - } + } */ // --- recheck for last shift --- - // (for date based shift rechecking) + // (for date based schedule overflow rechecking) if ( isset( $day_shift ) ) { // --- check for new shift overlap using next week --- @@ -2355,7 +2359,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { echo "^^^ CONFLICT ^^^" . PHP_EOL; } } - } */ + } if ( count( $conflicts ) == 0 ) { return false; @@ -2391,75 +2395,99 @@ function radio_station_check_new_shifts( $new_shifts ) { // --- double loop shifts to check against others --- foreach ( $new_shifts as $i => $shift_a ) { - // --- get shift A start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_a_start = $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; - $shift_a_end = $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; - $shift_a_start = radio_station_convert_shift_time( $shift_a_start ); - $shift_a_end = radio_station_convert_shift_time( $shift_a_end ); - - // 2.3.2: use next week day instead of date - // $shift_a_start_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_start ); - // $shift_a_end_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); - $shift_a_start_time = radio_station_to_time( 'next ' . $shift_a['day'] . ' ' . $shift_a_start ); - $shift_a_end_time = radio_station_to_time( 'next ' . $shift_a['day'] . ' ' . $shift_a_end ); - if ( $shift_a_end_time < $shift_a_start_time ) { - $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; - $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; - $debug = "Shift A Start: " . $a_start . PHP_EOL . 'Shift A End: ' . $a_end . PHP_EOL; - radio_station_debug( $debug ); - } + if ( '' != $shift_a['day'] ) { - foreach ( $new_shifts as $j => $shift_b ) { - if ( $i != $j ) { + // --- get shift A start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_a_start = $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; + $shift_a_end = $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; + $shift_a_start = radio_station_convert_shift_time( $shift_a_start ); + $shift_a_end = radio_station_convert_shift_time( $shift_a_end ); + + // 2.3.2: use next week day instead of date + $shift_a_start_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_start ); + $shift_a_end_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); + // $shift_a_start_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_start ); + // $shift_a_end_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_end ); + if ( $shift_a_end_time < $shift_a_start_time ) { + $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); + } - // --- get shift B start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - $shift_b_start = $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; - $shift_b_end = $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; - $shift_b_start = radio_station_convert_shift_time( $shift_b_start ); - $shift_b_end = radio_station_convert_shift_time( $shift_b_end ); - - // 2.3.2: use next week day instead of date - // $shift_b_start_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_start ); - // $shift_b_end_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end ); - $shift_b_start_time = radio_station_to_time( 'next ' . $shift_b['day'] . ' ' . $shift_b_start ); - $shift_b_end_time = radio_station_to_time( 'next ' . $shift_b['day'] . ' ' . $shift_b_end ); - if ( $shift_b_end_time < $shift_b_start_time ) { - $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); - } + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; + $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; + $debug = "Shift A Start: " . $a_start . PHP_EOL . 'Shift A End: ' . $a_end . PHP_EOL; + radio_station_debug( $debug ); + } - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $b_start = $shift_b['day'] . ' ' . $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian'] . ' (' . $shift_b_start_time . ')'; - $b_end = $shift_b['day'] . ' ' . $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian'] . ' (' . $shift_b_end_time . ')'; - $debug = "with Shift B Start: " . $b_start . PHP_EOL . 'Shift B End: ' . $b_end . PHP_EOL; - radio_station_debug( $debug, false, 'show-shift-save.log' ); - } + foreach ( $new_shifts as $j => $shift_b ) { - // --- compare shift A and B times --- - if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) - || ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) - || ( $shift_a_start_time == $shift_b_start_time ) - || ( ( $shift_b_start_time < $shift_a_start_time ) && ( $shift_b_end_time > $shift_a_start_time ) ) - || ( ( $shift_b_start_time < $shift_a_start_time ) && ( $shift_b_end_time < $shift_a_end_time ) ) ) { + if ( $i != $j ) { + + if ( RADIO_STATION_DEBUG ) { + echo $i . ' ::: ' . $j . PHP_EOL; + } - // --- maybe disable shift B --- - if ( ( 'yes' != $new_shifts[$i]['disabled'] ) && ( 'yes' != $new_shifts[$j]['disabled'] ) ) { + if ( '' != $shift_b['day'] ) { + // --- get shift B start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + $shift_b_start = $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; + $shift_b_end = $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; + $shift_b_start = radio_station_convert_shift_time( $shift_b_start ); + $shift_b_end = radio_station_convert_shift_time( $shift_b_end ); + + // 2.3.2: use next week day instead of date + $shift_b_start_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_start ); + $shift_b_end_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end ); + // $shift_b_start_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_start ); + // $shift_b_end_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_end ); + if ( $shift_b_end_time < $shift_b_start_time ) { + $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); + } // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = "!Conflict Found! New Shift (B) Disabled!" . PHP_EOL; + $b_start = $shift_b['day'] . ' ' . $shift_b_start . ' (' . $shift_b_start_time . ')'; + $b_end = $shift_b['day'] . ' ' . $shift_b_end . ' (' . $shift_b_end_time . ')'; + $debug = "with Shift B Start: " . $b_start . ' - Shift B End: ' . $b_end . PHP_EOL; + // radio_station_debug( $debug, false, 'show-shift-save.log' ); radio_station_debug( $debug ); } - $new_shifts[$j]['disabled'] = 'yes'; + // --- compare shift A and B times --- + if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) + || ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) + || ( $shift_a_start_time == $shift_b_start_time ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) ) { + + // --- maybe disable shift B --- + // 2.3.2: added check for isset on disabled key + if ( ( !isset( $new_shifts[$i]['disabled'] ) || ( 'yes' != $new_shifts[$i]['disabled'] ) ) + && ( !isset( $new_shifts[$j]['disabled'] ) || ( 'yes' != $new_shifts[$j]['disabled'] ) ) ) { + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = PHP_EOL . "* Conflict Found! New Shift (B) Disabled "; + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) {$debug .= "[A]";} + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) {$debug .= "[B]";} + if ( $shift_a_start_time == $shift_b_start_time ) {$debug .= "[C]";} + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) {$debug .= "[D]";} + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) {$debug .= "[E]";} + $debug .= "*" . PHP_EOL; + radio_station_debug( $debug ); + } + + $new_shifts[$j]['disabled'] = 'yes'; + + } else { + if ( RADIO_STATION_DEBUG ) { + echo "[Conflict with disabled shift.]" . PHP_EOL; + } + } + } } } } diff --git a/radio-station.php b/radio-station.php index 8f25a96..138f187 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.1.8 +Version: 2.3.1.9 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -1065,7 +1065,7 @@ function radio_station_localize_script() { } else { $utc_offset = $offset_hours; } - $utc_offset = 'UTC' . $utc_offset_hours; + $utc_offset = 'UTC' . $utc_offset; $code = radio_station_get_timezone_code( $timezone ); $js .= "radio.timezone_location = '" . esc_js( $timezone ) . "'; "; $js .= "radio.timezone_offset = " . esc_js( $offset ) . "; "; @@ -2313,6 +2313,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { // Set Debug Mode Constant // ----------------------- // 2.3.0: added debug mode constant +// 2.3.2: added saving debug mode constant if ( !defined( 'RADIO_STATION_DEBUG' ) ) { $rs_debug = false; if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { @@ -2320,6 +2321,13 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { } define( 'RADIO_STATION_DEBUG', $rs_debug ); } +if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { + $rs_save_debug = false; + if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { + $rs_save_debug = true; + } + define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); +} // -------------------------- // maybe Clear Transient Data diff --git a/readme.txt b/readme.txt index d8b1580..3f74eb4 100644 --- a/readme.txt +++ b/readme.txt @@ -565,6 +565,14 @@ You may translate the plugin into another language. Please visit our [WordPress == Upgrade Notice == += 2.3.1 = +* Bugfix Update and Extended Directory Listing Offer +* https://netmix.com/improved-netmix-directory/ +* AJAX Saving of Show Shifts and Playlist Tracks +* AJAX Widget Loading to work with page caches +* Automated current Show schedule highlighting +* Improved timezones, overrides, shift checking and more + = 2.3.1 = * Bugfix Update and Announcing New Netmix Station Directory! * https://netmix.com/announcing-new-netmix-directory/ diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 01922d2..d14aef6 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -137,10 +137,26 @@ // --- convert shift time data --- // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $weekdate . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $weekdate . ' ' . $shift_end ); + // 2.3.2: fix timestamps for midnight/split shifts + // $shift_start = radio_station_convert_shift_time( $shift['start'] ); + // $shift_end = radio_station_convert_shift_time( $shift['end'] ); + // $shift_start_time = radio_station_to_time( $shift['day'] . ' ' . $shift_start ); + // $shift_end_time = radio_station_to_time( $shift['day'] . ' ' . $shift_end ); + // if ( $shift_end_time < $shift_start_time ) { + // $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + // } + if ( '00:00 am' == $shift['start'] ) { + $shift_start_time = radio_station_to_time( $weekdate . ' 00:00' ); + } else { + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_start_time = radio_station_to_time( $weekdate . ' ' . $shift_start ); + } + if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { + $shift_end_time = radio_station_to_time( $weekdate . ' 23:59:59' ) + 1; + } else { + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $shift_end_time = radio_station_to_time( $weekdate . ' ' . $shift_end ); + } // 2.3.0: filter show link by show and context $show_link = false; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index f26f779..6da5d85 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -311,7 +311,7 @@ } } - if ( isset( $_GET['shiftdebug'] ) && ( '1' == $_GET['shiftdebug'] ) ) { + if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { if ( !isset( $shiftdebug ) ) {$shiftdebug = '';} $shiftdebug .= 'Now: ' . $now . ' (' . radio_station_get_time( 'datetime', $now ) . ') -- Today: ' . $today . '
    '; $shiftdebug .= 'Day: ' . $weekday . ' - Raw Hour: ' . $raw_hour . ' - Hour: ' . $hour . ' - Hour Display: ' . $hour_display . '
    '; @@ -464,12 +464,12 @@ } $data_format = 'H:i'; } else { - $start = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['start'] ); + $start = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['start'] ); // 2.3.2: display real end of split shift if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['real_end'] ); + $end = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['real_end'] ); } else { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['end'] ); + $end = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['end'] ); } $data_format = 'g:i a'; } @@ -579,6 +579,6 @@ $output .= ''; -if ( isset( $_GET['shiftdebug'] ) && ( '1' == $_GET['shiftdebug'] ) ) { +if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { $output .= $shiftdebug; } diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 20dca84..d022bf0 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -180,10 +180,35 @@ // --- convert shift time data --- // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $shift['day'] . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $shift['day'] . ' ' . $shift_end ); + // 2.3.2: fix timestamps for midnight/split shifts + // $shift_start = radio_station_convert_shift_time( $shift['start'] ); + // $shift_end = radio_station_convert_shift_time( $shift['end'] ); + // $shift_start_time = radio_station_to_time( $shift['day'] . ' ' . $shift_start ); + // $shift_end_time = radio_station_to_time( $shift['day'] . ' ' . $shift_end ); + // if ( $shift_end_time < $shift_start_time ) { + // $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + // } + if ( '00:00 am' == $shift['start'] ) { + $shift_start_time = radio_station_to_time( $weekdate . ' 00:00' ); + } else { + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_start_time = radio_station_to_time( $weekdate . ' ' . $shift_start ); + } + if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { + $shift_end_time = radio_station_to_time( $weekdate . ' 23:59:59' ) + 1; + } else { + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $shift_end_time = radio_station_to_time( $weekdate . ' ' . $shift_end ); + } + + // --- shift debug --- + // 2.3.2: added shift debugging + if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { + if ( !isset( $shiftdebug ) ) {$shiftdebug = '';} + $shiftdebug .= 'Now: ' . $now . ' (' . radio_station_get_time( 'datetime', $now ) . ') -- Today: ' . $today . '
    '; + $shiftdebug .= 'Shift Start: ' . $shift_start . ' (' . date( 'Y-m-d l H: i', $shift_start ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start ) . ')' . '
    '; + $shiftdebug .= 'Shift End: ' . $shift_end . ' (' . date( 'Y-m-d l H: i', $shift_end ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end ) . ')' . '
    '; + } // 2.3.0: add genre classes for highlighting $classes = array( 'master-schedule-tabs-show' ); @@ -424,3 +449,7 @@ $output .= '
    '; $output .= $panels; $output .= '
    '; + +if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { + $output .= $shiftdebug; +} From b716984ee94cf026ccccce379d8b85ef2d605fdb Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 9 Jul 2020 01:39:12 +1000 Subject: [PATCH 125/377] dev update 2.3.1.10 #237 --- CHANGELOG.md | 2 + css/rs-schedule.css | 12 +- css/rs-shortcodes.css | 18 + includes/master-schedule.php | 62 +- includes/post-types-admin.php | 52 +- includes/post-types.php | 130 ++- includes/shortcodes.php | 185 ++-- includes/support-functions.php | 539 ++++++---- js/jstz.js | 1462 ++++++++++++++++++++++++++ js/jstz.min.js | 2 + js/radio-station.js | 24 + radio-station-admin.php | 38 +- radio-station.php | 27 +- readme.txt | 2 + templates/master-schedule-div.php | 73 +- templates/master-schedule-legacy.php | 2 +- templates/master-schedule-list.php | 52 +- templates/master-schedule-table.php | 101 +- templates/master-schedule-tabs.php | 48 +- templates/single-show-content.php | 30 +- 20 files changed, 2356 insertions(+), 505 deletions(-) create mode 100644 js/jstz.js create mode 100644 js/jstz.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0646214..85918cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added: AJAX save of show shifts and playlist tracks * Added: post type editing metabox position filtering * Added: more display attributes to Master Schedule shortcode +* Added: time format filters for time output displays +* Added: javascript user timezone display on Master Schedule ### 2.3.1 * Update: Plugin Loader (1.1.1) with Freemius first path fix diff --git a/css/rs-schedule.css b/css/rs-schedule.css index b01d284..7509a14 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -19,21 +19,11 @@ text-align: left; } -#master-schedule-clock-wrapper .radio-clock-title, -#master-schedule-timezone-wrapper .radio-timezone-title, -#master-schedule-timezone-wrapper .user-timezone-title { - font-weight: bold; -} - -.radio-timezone-title, .radio-timezone, -.user-timezone-title, .user-timezone, .user-timezone-change, .user-timezone-select { - display: inline-block; -} /* Genre Selector */ /* -------------- */ #master-genre-list { - font-size: 0.8em; + font-size: 1em; float: left; } diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index fe9dd74..7e99aaa 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -11,6 +11,24 @@ font-size: 0.8em; } +/* Clock/Timezone Shortcodes */ +/* ------------------------- */ +.radio-timezone-title, .radio-timezone, .radio-user-timezone-title, .radio-user-timezone { + display: inline-block; +} + +.radio-timezone-title, .radio-user-timezone-title, .radio-clock-title { + font-weight: bold; +} + +.radio-user-timezone-title { + display: none; margin-left: 30px; +} + +.user-timezone-change, .user-timezone-select { + display: inline-block; +} + /* Archive Shortcodes */ /* ------------------ */ diff --git a/includes/master-schedule.php b/includes/master-schedule.php index b06a861..4c82bce 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -9,7 +9,7 @@ add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { - // --- make list attribute backward compatible --- + // --- make attributes backward compatible --- // 2.3.0: convert old list attribute to view if ( !isset( $atts['view'] ) && isset( $atts['list'] ) ) { if ( 1 === (int) $atts['list'] ) { @@ -28,12 +28,15 @@ function radio_station_master_schedule( $atts ) { $atts['show_times'] = $atts['display_show_time']; unset( $atts['display_show_time'] ); } - // 2.3.0: convert single_day to days + // 2.3.0: convert single_day attribute to days if ( !isset( $atts['days'] ) && isset( $atts['single_day'] ) ) { $atts['days'] = $atts['single_day']; unset( $atts['single_day'] ); } + // --- get default clock display setting --- + $clock = radio_station_get_setting( 'schedule_clock' ); + // --- merge shortcode attributes with defaults --- // 2.3.0: added show_desc (default off) // 2.3.0: added show_hosts (alias of show_djs) @@ -51,7 +54,8 @@ function radio_station_master_schedule( $atts ) { // --- control display options --- 'selector' => 1, - 'clock' => 1, + 'clock' => $clock, + 'timezone' => 1, // --- schedule display options --- 'time' => $time_format, @@ -64,9 +68,11 @@ function radio_station_master_schedule( $atts ) { 'display_date' => 'jS', 'display_month' => 'short', 'divheight' => 45, - // 'list' => 0, // converted above / deprecated - // 'show_djs' => 0, // converted above / deprecated - // 'display_show_time' => 1, // converted above / deprecated + + // --- converted and deprecated --- + // 'list' => 0, + // 'show_djs' => 0, + // 'display_show_time' => 1, // --- show display options --- 'show_image' => 0, @@ -84,17 +90,17 @@ function radio_station_master_schedule( $atts ) { $atts['view'] = strtolower( $atts['view'] ); $views = explode( ',', $atts['view'] ); if ( ( 'tabs' == $atts['view'] ) || in_array( 'tabs', $views ) ) { + // 2.3.2: add show descriptions default for tabbed view + // 2.3.2: add display_ and display_date attributes $defaults['show_image'] = 1; $defaults['show_hosts'] = 1; $defaults['show_genres'] = 1; - // 2.3.2: add show description for tabbed view $defaults['show_desc'] = 1; - // 2.3.2: display day/date attributes $defaults['display_day'] = 'full'; $defaults['display_date'] = false; } elseif ( ( 'list' == $atts['view'] ) || in_array( 'list', $views ) ) { + // 2.3.2: add display date attribute $defaults['show_genres'] = 1; - // 2.3.2: display date attribute $defaults['display_date'] = false; } } @@ -115,36 +121,36 @@ function radio_station_master_schedule( $atts ) { $atts['clock'] = 0; } - // --- get show selection links and clock display --- - // 2.3.0: added server/user clock display - if ( $atts['selector'] ) { - $selector = radio_station_master_schedule_selector(); - } - if ( $atts['clock'] ) { - $clock = radio_station_clock_shortcode(); - } - // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views + // 2.3.2: moved shortcode calls inside and added filters $output .= '
    '; $controls = array(); + + // --- display radio clock or timezone (or neither) if ( $atts['clock'] ) { - // --- display radio clock --- + + // --- radio clock --- $controls['clock'] = '
    '; - $controls['clock'] .= $clock; + $clock_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); + $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); $controls['clock'] .= '
    '; - } else { - // --- display radio timezone --- + + } elseif ( $atts['timezone'] ) { + + // --- radio timezone --- $controls['timezone'] = '
    '; - $controls['timezone'] .= radio_station_timezone_shortcode(); + $timezone_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); + $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); $controls['timezone'] .= '
    '; + } + // --- genre selector --- if ( $atts['selector'] ) { - // --- display genre selector --- $controls['selector'] = '
    '; - $controls['selector'] .= $selector; + $controls['selector'] .= radio_station_master_schedule_selector(); $controls['selector'] .= '
    '; } @@ -152,10 +158,10 @@ function radio_station_master_schedule( $atts ) { $control_order = array( 'clock', 'timezone', 'selector' ); $control_order = apply_filters( 'radio_station_schedule_control_order', $control_order, $atts ); - // 2.3.1: add filters for controls + // 2.3.1: add filter for controls HTML $controls = apply_filters( 'radio_station_schedule_controls', $controls, $atts ); - // print_r( $controls ); - + + // --- add ordered controls to output --- if ( is_array( $control_order ) && ( count( $control_order ) > 0 ) ) { foreach ( $control_order as $control ) { if ( isset( $controls[$control] ) && ( '' != $control ) ) { diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 08b7c4e..ed8b80a 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2473,12 +2473,6 @@ function radio_station_show_save_data( $post_id ) { } } - // 2.3.2: check for post type slug match early - $post = get_post( $post_id ); - if ( RADIO_STATION_SHOW_SLUG != $post->post_type ) { - return; - } - // --- set show meta changed flags --- $show_meta_changed = $show_shifts_changed = false; @@ -2780,7 +2774,8 @@ function radio_station_show_save_data( $post_id ) { // --- maybe send directory ping --- // 2.3.1: added directory update ping option - radio_station_send_directory_ping(); + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); } if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { @@ -2828,10 +2823,12 @@ function radio_station_show_save_data( $post_id ) { } -// ------------------ -// Save Outpout Debug -// ------------------ -add_action( 'save_post', 'radio_station_save_debug_start', 0 ); +// ----------------- +// Save Output Debug +// ----------------- +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 0 ); function radio_station_save_debug_start( $post_id ) { if ( !RADIO_STATION_SAVE_DEBUG ) { return; @@ -2839,13 +2836,11 @@ function radio_station_save_debug_start( $post_id ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } - $slugs = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - $post = get_post( $post_id ); - if ( in_array( $post->post_type, $slugs ) ) { - ob_start(); - } + ob_start(); } -add_action( 'save_post', 'radio_station_save_debug_end', 9999 ); +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 9999 ); function radio_station_save_debug_end( $post_id ) { if ( !RADIO_STATION_SAVE_DEBUG ) { return; @@ -2853,16 +2848,12 @@ function radio_station_save_debug_end( $post_id ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } - $slugs = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - $post = get_post( $post_id ); - if ( in_array( $post->post_type, $slugs ) ) { - $contents = ob_get_contents(); - ob_end_clean(); - if ( strlen( $contents ) > 0 ) { - echo "Output Detected During Save (preventing redirect):
    "; - echo ''; - exit; - } + $contents = ob_get_contents(); + ob_end_clean(); + if ( strlen( $contents ) > 0 ) { + echo "Output Detected During Save (preventing redirect):
    "; + echo ''; + exit; } } @@ -3267,8 +3258,8 @@ function radio_station_schedule_override_metabox() { // ------------------------ // Update Schedule Override // ------------------------ -add_action( 'save_post', 'radio_station_master_override_save_showpostdata' ); -function radio_station_master_override_save_showpostdata( $post_id ) { +add_action( 'save_post', 'radio_station_override_save_data' ); +function radio_station_override_save_data( $post_id ) { // --- verify if this is an auto save routine --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { @@ -3364,7 +3355,8 @@ function radio_station_master_override_save_showpostdata( $post_id ) { // --- maybe send directory ping --- // 2.3.1: added directory update ping option - radio_station_send_directory_ping(); + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); } } diff --git a/includes/post-types.php b/includes/post-types.php index 154b703..f0ced94 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -39,22 +39,27 @@ function radio_station_create_post_types() { // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( - 'labels' => array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ), + 'labels' => array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), // 2.3.0: added archive title label - 'archive_title' => __( 'Shows', 'radio-station' ), + 'archive_title' => __( 'Shows', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Shows', 'radio-station' ), + 'not_found' => __( 'No Shows found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Shows found in Trash', 'radio-station' ), + 'all_items' => __( 'All Shows', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + 'description' => __( 'Post type for Shows', 'radio-station' ), 'public' => true, 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), 'hierarchical' => false, @@ -82,21 +87,26 @@ function radio_station_create_post_types() { // $icon = plugins_url( 'images/playlist-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ), + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), // 2.3.0: added archive title label - 'archive_title' => __( 'Playlists', 'radio-station' ), + 'archive_title' => __( 'Playlists', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Playlists', 'radio-station' ), + 'not_found' => __( 'No Playlists found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Playlists found in Trash', 'radio-station' ), + 'all_items' => __( 'All Playlists', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + 'description' => __( 'Post type for Playlists', 'radio-station' ), 'public' => true, 'hierarchical' => false, // 2.3.0: added thumbnail, custom field and revision support @@ -123,19 +133,25 @@ function radio_station_create_post_types() { // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( 'labels' => array( - 'name' => __( 'Schedule Overrides', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ), + 'name' => __( 'Schedule Overrides', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Overrides', 'radio-station' ), + 'not_found' => __( 'No Overrides found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Overrides found in Trash', 'radio-station' ), + 'all_items' => __( 'All Overrides', 'radio-station' ), + 'archive_title' => __( 'Overrides', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + 'description' => __( 'Post type for Schedule Overrides', 'radio-station' ), 'public' => true, // 2.3.0: added taxonomies to overrides 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), @@ -165,14 +181,19 @@ function radio_station_create_post_types() { $ui = apply_filters( 'radio_station_host_interface', false ); $post_type = array( 'labels' => array( - 'name' => __( 'Host Profiles', 'radio-station' ), - 'singular_name' => __( 'Host Profile', 'radio-station' ), - 'add_new' => __( 'New Host Profile', 'radio-station' ), - 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), - 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), - 'new_item' => __( 'New Host Profile', 'radio-station' ), - 'view_item' => __( 'View Host Profile', 'radio-station' ), - 'archive_title' => __( 'Show Hosts', 'radio-station' ), + 'name' => __( 'Host Profiles', 'radio-station' ), + 'singular_name' => __( 'Host Profile', 'radio-station' ), + 'add_new' => __( 'New Host Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), + 'new_item' => __( 'New Host Profile', 'radio-station' ), + 'view_item' => __( 'View Host Profile', 'radio-station' ), + 'archive_title' => __( 'Show Hosts', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Hosts', 'radio-station' ), + 'not_found' => __( 'No Hosts found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Hosts found in Trash', 'radio-station' ), + 'all_items' => __( 'All Hosts', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -206,14 +227,19 @@ function radio_station_create_post_types() { $ui = apply_filters( 'radio_station_producer_interface', false ); $post_type = array( 'labels' => array( - 'name' => __( 'Producer Profiles', 'radio-station' ), - 'singular_name' => __( 'Producer Profile', 'radio-station' ), - 'add_new' => __( 'New Producer Profile', 'radio-station' ), - 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), - 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), - 'new_item' => __( 'New Producer Profile', 'radio-station' ), - 'view_item' => __( 'View Producer Profile', 'radio-station' ), - 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), + 'name' => __( 'Producer Profiles', 'radio-station' ), + 'singular_name' => __( 'Producer Profile', 'radio-station' ), + 'add_new' => __( 'New Producer Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), + 'new_item' => __( 'New Producer Profile', 'radio-station' ), + 'view_item' => __( 'View Producer Profile', 'radio-station' ), + 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Producers', 'radio-station' ), + 'not_found' => __( 'No Producers found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Producers found in Trash', 'radio-station' ), + 'all_items' => __( 'All Producers', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -253,7 +279,14 @@ function radio_station_create_post_types() { add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + // 2.3.2: added host and producer slugs + $post_types = array( + RADIO_STATION_SHOW_SLUG, + RADIO_STATION_PLAYLIST_SLUG, + RADIO_STATION_OVERRIDE_SLUG, + RADIO_STATION_HOST_SLUG. + RADIO_STATION_PRODUCER_SLUG, + ); // 2.2.8: removed strict in_array checking if ( in_array( $post_type, $post_types ) ) { return false; @@ -273,11 +306,18 @@ function radio_station_add_featured_image_support() { // 2.3.0: add override thumbnail to theme support declaration $supported_types = get_theme_support( 'post-thumbnails' ); if ( false === $supported_types ) { - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + $post_types = array( + RADIO_STATION_SHOW_SLUG, + RADIO_STATION_OVERRIDE_SLUG, + RADIO_STATION_HOST_SLUG, + RADIO_STATION_PRODUCER_SLUG, + ); add_theme_support( 'post-thumbnails', $post_types ); } elseif ( is_array( $supported_types ) ) { $supported_types[0][] = RADIO_STATION_SHOW_SLUG; $supported_types[0][] = RADIO_STATION_OVERRIDE_SLUG; + $supported_types[0][] = RADIO_STATION_HOST_SLUG; + $supported_types[0][] = RADIO_STATION_PRODUCER_SLUG; add_theme_support( 'post-thumbnails', $supported_types[0] ); } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index e185005..4102e99 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -86,31 +86,45 @@ function radio_station_timezone_shortcode( $atts = array() ) { } } if ( $offset > 0 ) { - $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' +' . $offset . ']'; + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . '+' . $offset . ']'; } else { - $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' ' . $offset . ']'; + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . $offset . ']'; } } $code = radio_station_get_timezone_code( $timezone ); - $timezone_display = $code . ' ' . $utc_offset; + // 2.3.2: display full timezone location as well + $location = str_replace( '/', ', ', $timezone ); + $location = str_replace( '_', ' ', $location ); + $timezone_display = $code . ' (' . $location . ') ' . $utc_offset; } // --- set shortcode output --- $output = '
    '; + // --- radio timezone --- $output .= '
    '; $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); $output .= '
    : '; $output .= '
    ' . esc_html( $timezone_display ) . '
    '; - // 2.3.2 allow for timezone selector --- - $select = apply_filters( 'radio_station_clock_timezone_select', '', $instance, $atts ); + // --- user timezone --- + $output .= ''; + $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ); + $output .= ': '; + $output .= ''; + + // 2.3.2 allow for timezone selector test + $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); if ( '' != $select ) { $output .= $select; } $output .= '
    '; + // --- enqueue shortcode styles --- + // 2.3.2: added for timezone shortcode styles + radio_station_enqueue_style( 'shortcodes' ); + // --- filter and return --- $output = apply_filters( 'radio_station_timezone_shortcode', $output, $atts ); return $output; @@ -322,12 +336,17 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } } - // --- get meridiem conversions --- - // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); + // --- set time data formats --- + // 2.3.0: added once-off meridiem pre-conversions + // 2.3.2: replaced meridiem conversions with data formats + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; } + $start_data_format = 'j, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $type . '-archive', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $type . '-archive', $atts ); // --- check for results --- $list = '
    '; @@ -398,8 +417,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first $date_time = radio_station_to_time( $datetime['date'] ); - $day = radio_station_get_time( 'day', $date_time ); - $display_day = radio_station_translate_weekday( $day ); + // $day = radio_station_get_time( 'day', $date_time ); + // $display_day = radio_station_translate_weekday( $day ); $start = $datetime['start_hour'] . ':' . $datetime['start_min'] . ' ' . $datetime['start_meridian']; $end = $datetime['end_hour'] . ':' . $datetime['end_min'] . ' ' . $datetime['end_meridian']; $start_time = radio_station_convert_shift_time( $start ); @@ -411,23 +430,16 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // --- convert shift times --- - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $start, 24 ); - $end = radio_station_convert_shift_time( $end, 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - } else { - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $start ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $end ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - } - + // 2.3.2: use time formats with translations + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); + // 2.3.1: fix to append not echo override date to archive list - $list .= ''; - $list .= esc_html( $display_day ) . ', ' . $start . ''; + $list .= '' . esc_html( $start ) . ''; $list .= ' - '; - $list .= '' . $end . ''; + $list .= '' . esc_html( $end ) . ''; $list .= "
    "; } @@ -1183,10 +1195,10 @@ function radio_station_current_show_shortcode( $atts ) { // --- get meridiem conversions --- // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); - } + // if ( 12 == (int) $atts['time'] ) { + // $am = radio_station_translate_meridiem( 'am' ); + // $pm = radio_station_translate_meridiem( 'pm' ); + // } // --- maybe filter excerpt values --- // 2.3.0: added context specific excerpt value filtering @@ -1201,6 +1213,7 @@ function radio_station_current_show_shortcode( $atts ) { } // --- get current show --- + // note: current show is not split shift // 2.3.0: use new get current show function // 2.3.2: added attribute to pass time argument if ( $atts['for_time'] ) { @@ -1250,6 +1263,17 @@ function radio_station_current_show_shortcode( $atts ) { } else { + // --- get time formats --- + // 2.3.2: moved out to get once + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; + } + $start_data_format = 'l, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'current-show', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'current-show', $atts ); + // --- set html output --- // 2.3.1: store all HTML to allow section re-ordering $html = array( 'title' => '' ); @@ -1301,7 +1325,6 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: use dates for reliability // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to conver to 24 hour format first - $display_day = radio_station_translate_weekday( $shift['day'] ); $weekdate = $weekdates[$shift['day']]; if ( isset( $shift['start'] ) ) { $start = $shift['start']; @@ -1318,18 +1341,13 @@ function radio_station_current_show_shortcode( $atts ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - // --- convert shift times --- - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $start, 24 ); - $end = radio_station_convert_shift_time( $end, 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - } else { - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $start ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $end ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - } + // --- get shift display times --- + // 2.3.2: use time formats with translations + // $display_day = radio_station_translate_weekday( $shift['day'] ); + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); // --- set shift classes --- $classes = array( 'current-show-shifts', 'on-air-dj-sched' ); @@ -1340,10 +1358,9 @@ function radio_station_current_show_shortcode( $atts ) { $class = implode( ' ', $classes ); $current_shift_display = '
    '; - $current_shift_display .= ''; - $current_shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $current_shift_display .= '' . esc_html( $start ) . ''; $current_shift_display .= ' - '; - $current_shift_display .= '' . $end . ''; + $current_shift_display .= '' . esc_html( $end ) . ''; $current_shift_display .= '
    '; } $class = implode( ' ', $classes ); @@ -1353,10 +1370,9 @@ function radio_station_current_show_shortcode( $atts ) { if ( in_array( 'current-shift', $classes ) ) { $shift_display .= '
    • '; } - $shift_display .= ''; - $shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; - $shift_display .= '' . $end . ''; + $shift_display .= '' . esc_html( $end ) . ''; if ( in_array( 'current-shift', $classes ) ) { $shift_display .= '
    '; } @@ -1608,7 +1624,7 @@ function radio_station_current_show() { if ( !isset( $atts['for_time'] ) || !$atts['for_time'] ) { $current_show = get_transient( 'radio_station_current_show' ); if ( !$current_show ) { - sleep( 2 ); + sleep( 1 ); } } @@ -1624,8 +1640,10 @@ function radio_station_current_show() { $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; $js .= "parent.document.getElementById('rs-current-show-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; - // --- restart countdowns --- - $js .= "parent.radio_countdown();" . PHP_EOL; + // --- maybe restart countdowns --- + if ( $atts['countdown'] ) { + $js .= "parent.radio_countdown();" . PHP_EOL; + } } @@ -1755,12 +1773,13 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- get meridiem conversions --- // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); - } + // if ( 12 == (int) $atts['time'] ) { + // $am = radio_station_translate_meridiem( 'am' ); + // $pm = radio_station_translate_meridiem( 'pm' ); + // } // --- get the upcoming shows --- + // note: upcoming shows are not split shift // 2.3.0: use new get next shows function if ( $atts['for_time'] ) { $shows = radio_station_get_next_shows( $atts['limit'], false, $atts['for_time'] ); @@ -1811,6 +1830,18 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } else { + // --- set shift display data formats --- + // 2.2.7: fix to convert time to integer + // 2.3.2: moved outside shift loop + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; + } + $start_data_format = 'l, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'upcoming-shows', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'upcoming-shows', $atts ); + // --- loop upcoming shows --- foreach ( $shows as $i => $shift ) { @@ -1856,7 +1887,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: use exact shift date in time calculations // 2.3.2: fix to convert to 24 hour format first - $display_day = radio_station_translate_weekday( $shift['day'] ); + // $display_day = radio_station_translate_weekday( $shift['day'] ); $shift_start = radio_station_convert_shift_time( $shift['start'] ); $shift_end = radio_station_convert_shift_time( $shift['end'] ); $shift_start_time = radio_station_to_time( $shift['date'] . ' ' . $shift_start ); @@ -1878,30 +1909,18 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } $class = implode( ' ', $classes ); - // --- maybe convert times --- - // 2.2.7: fix to convert time to integer - if ( 24 == (int) $atts['time'] ) { - - // --- convert start/end time to 24 hours --- - $start = radio_station_convert_shift_time( $shift['start'], 24 ); - $end = radio_station_convert_shift_time( $shift['end'], 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - - } else { - - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['start'] ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['end'] ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - - } + // --- get shift display times --- + // 2.3.2: use time formats with translations + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); + // --- set shift display output --- $shift_display .= '
    '; - $shift_display .= ''; - $shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; - $shift_display .= '' . $end . ''; + $shift_display .= '' . esc_html( $end ) . ''; $shift_display .= '
    '; $shift_display .= '
    '; @@ -2101,7 +2120,9 @@ function radio_station_upcoming_shows() { $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; // --- restart countdowns --- - $js .= "parent.radio_countdown();" . PHP_EOL; + if ( $atts['countdown'] ) { + $js .= "parent.radio_countdown();" . PHP_EOL; + } } @@ -2403,7 +2424,9 @@ function radio_station_current_playlist() { $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; // --- restart countdowns --- - $js .= "parent.radio_countdown();" . PHP_EOL; + if ( $atts['countdown'] ) { + $js .= "parent.radio_countdown();" . PHP_EOL; + } } diff --git a/includes/support-functions.php b/includes/support-functions.php index 765efa4..34cdfcc 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -47,15 +47,22 @@ // - Patreon Supporter Button // - Patreon Button Styles // - Send Directory Ping -// === Time Conversions -// -// -// -// - Get Timezones Options +// === Time Conversions === +// - Get Now +// - Get Timezone // - Get Timezone Code +// - Get Date Time Object +// - String To Time +// - Get Time +// - Get Timezone Options +// - Get Weekday(s) +// - Get Month(s) // - Get Schedule Weekdays +// - Get Schedule Weekdates // - Get Next Day // - Get Previous Day +// - Get Next Date +// - Get Previous Date // - Get All Hours // - Convert Hour to Time Format // - Convert Shift to Time Format @@ -73,9 +80,12 @@ // - Sanitize Shortcode Values // === Translations === // - Translate Weekday +// - Replace Weekdays // - Translate Month +// - Replace Months // - Translate Meridiem - +// - Replace Meridiems +// - Translate Time String // ---------------------- // === Data Functions === @@ -964,7 +974,17 @@ function radio_station_get_current_schedule( $time = false ) { if ( !$time ) { $schedule = get_transient( 'radio_station_current_schedule' ); if ( $schedule ) { - $schedule = apply_filters( 'radio_station_current_schedule', $schedule ); + $schedule = apply_filters( 'radio_station_current_schedule', $schedule, false ); + if ( $schedule ) { + return $schedule; + } + } + } else { + // --- get schedule for time --- + // 2.3.2: added transient for time schedule + $schedule = get_transient( 'radio_station_current_schedule_' . $time ); + if ( $schedule ) { + $schedule = apply_filters( 'radio_station_current_schedule', $schedule, $time ); if ( $schedule ) { return $schedule; } @@ -1281,7 +1301,7 @@ function radio_station_get_current_schedule( $time = false ) { if ( !$time ) { set_transient( 'radio_station_current_show', $current_show, $expires ); } else { - set_transient( 'radio_station_current_show_temp', $current_show, $expires ); + set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); } } elseif ( $now > $shift_end_time ) { @@ -1378,7 +1398,7 @@ function radio_station_get_current_schedule( $time = false ) { if ( !$time ) { set_transient( 'radio_station_next_show', $next_show, $next_expires ); } else { - set_transient( 'radio_station_next_show_temp', $next_show, $next_expires ); + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); } } @@ -1397,8 +1417,11 @@ function radio_station_get_current_schedule( $time = false ) { } // --- pass calculated shifts with limit of 1 --- - $next_shows = radio_station_get_next_shows( 1, $show_shifts ); - if ( count( $next_shows ) > 0 ) { + // 2.3.2: added time argument + $next_shows = radio_station_get_next_shows( 1, $show_shifts, $time ); + + // 2.3.2: set next show transient within next shows function + /* if ( count( $next_shows ) > 0 ) { // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first @@ -1419,9 +1442,9 @@ function radio_station_get_current_schedule( $time = false ) { if ( !$time ) { set_transient( 'radio_station_next_show', $next_show, $next_expires ); } else { - set_transient( 'radio_station_next_show_temp', $next_show, $next_expires ); + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); } - } + } */ } // TODO: handle possible edge case where current show or next show is split @@ -1449,7 +1472,7 @@ function radio_station_get_current_schedule( $time = false ) { if ( !$time ) { set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); } else { - set_transient( 'radio_station_current_schedule_temp', $show_shifts, $expires ); + set_transient( 'radio_station_current_schedule_' . $time, $show_shifts, $expires ); } } @@ -1472,21 +1495,20 @@ function radio_station_get_current_show( $time = false ) { // --- get cached current show value --- if ( !$time ) { $current_show = get_transient( 'radio_station_current_show' ); + } else { + $current_show = get_transient( 'radio_station_current_show_' . $time ); } // --- if not set it has expired so recheck schedule --- // 2.3.2: fix for trailing space in transient name string if ( !$current_show ) { - // 2.3.2: clear current schedule to force recalculation - delete_transient( 'radio_station_current_schedule' ); - if ( !$time ) { $schedule = radio_station_get_current_schedule(); $current_show = get_transient( 'radio_station_current_show' ); } else { $schedule = radio_station_get_current_schedule( $time ); - $current_show = get_transient( 'radio_station_current_show_temp' ); + $current_show = get_transient( 'radio_station_current_show_' . $time ); } } @@ -1509,20 +1531,19 @@ function radio_station_get_next_show( $time = false ) { // --- get cached current show value --- if ( !$time ) { $next_show = get_transient( 'radio_station_next_show' ); + } else { + $next_show = get_transient( 'radio_station_next_show_' . $time ); } // --- if not set it has expired so recheck schedule --- if ( !$next_show ) { - // 2.3.2: clear schedule transient to force recalculation - delete_transient( 'radio_station_current_schedule '); - if ( !$time ) { $schedule = radio_station_get_current_schedule(); $next_show = get_transient( 'radio_station_next_show' ); } else { $schedule = radio_station_get_current_schedule( $time ); - $next_show = get_transient( 'radio_station_next_show_temp' ); + $next_show = get_transient( 'radio_station_next_show_' . $time ); } } @@ -1552,11 +1573,16 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = // --- loop (remaining) shifts to add show data --- $next_shows = array(); - $now = radio_station_get_now(); + // 2.3.2: maybe set provided time as now + if ( $time ) { + $now = $time; + } else { + $now = radio_station_get_now(); + } // 2.3.2: use get time function with timezone // 2.3.2: fix to pass week day start as numerical (w) - $today = radio_station_get_time( 'w' ); + $today = radio_station_get_time( 'w', $now ); $weekdays = radio_station_get_schedule_weekdays( $today ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); @@ -1575,7 +1601,7 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = // --- get this shift start and end times --- // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first + // 2.3.2: fix to convert to 24 hour format first $shift_start = radio_station_convert_shift_time( $shift['start'] ); $shift_end = radio_station_convert_shift_time( $shift['end'] ); $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); @@ -1587,11 +1613,26 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = echo print_r( $shift, true ) . PHP_EOL; } + // --- current show --- + if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + if ( !isset( $current_show ) ) { + // TODO: recombine possible split shift to set current show ? + /* $current_show = $shift; + $expires = $shift_end_time - $now - 1; + if ( $expires > 3600 ) { + $expires = 3600; + } + if ( !$time ) { + set_transient( 'radio_station_current_show', $current_show, $expires ); + } else { + set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); + } */ + } + } + // --- check if show is upcoming --- if ( $now < $shift_start_time ) { - if ( RADIO_STATION_DEBUG ) {echo "YES";} - // --- reset skip flag --- $skip = false; @@ -1603,18 +1644,14 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = } elseif ( isset( $shift['split'] ) && $shift['split'] ) { // --- dedupe for shifts split overnight --- + if ( isset( $shift['real_end'] ) ) { + $shift['end'] = $shift['real_end']; + $current_split = true; + } + // (note: probably unnecessary) if ( isset( $shift['real_start'] ) ) { - - // --- get real start day and time --- $shift['day'] = radio_station_get_previous_day( $day ); $shift['start'] = $shift['real_start']; - - } elseif ( isset( $shift['real_end'] ) ) { - - // --- get real end time --- - $shift['end'] = $shift['real_end']; - $current_split = true; - } } else { @@ -1624,8 +1661,19 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = if ( !$skip ) { + // --- maybe set next show transient --- + if ( !isset( $next_show ) ) { + $next_show = $shift; + $next_expires = $shift_end_time - $now - 1; + if ( !$time ) { + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } else { + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); + } + } + // --- add to next shows data --- - // 2.3.2: set date for widget + // 2.3.2: set date for widget display $shift['date'] = $weekdates[$day]; $next_shows[] = $shift; @@ -2833,6 +2881,15 @@ function radio_station_patreon_button_styles() { #radio-station-patreon-button:hover {opacity:1 !important;}'; } +// -------------------- +// Queue Directory Ping +// -------------------- +// 2.3.2: queue directory ping on saving +function radio_station_queue_directory_ping() { + $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); + if ( 'yes' != $do_ping ) {return;} + update_option( 'radio_station_ping_directory', '1' ); +} // ------------------- // Send Directory Ping @@ -2843,16 +2900,42 @@ function radio_station_send_directory_ping() { if ( 'yes' != $do_ping ) {return;} // --- set the URL to ping --- + // 2.3.2: fix url_encode to urlencode $site_url = site_url(); $url = add_query_arg( 'ping', 'directory', RADIO_STATION_NETMIX_DIR ); - $url = add_query_arg( 'station-url', url_encode( $site_url ), $url ); + $url = add_query_arg( 'station-url', urlencode( $site_url ), $url ); $url = add_query_arg( 'timestamp', time(), $url ); - + // --- send the ping --- - $args = array( 'timeout' => 3 ); - wp_remote_get( $url, $args ); + $args = array( 'timeout' => 10 ); + if ( !function_exists( 'wp_remote_get' ) ) { + include_once ABSPATH . WPINC . '/http.php'; + } + $response = wp_remote_get( $url, $args ); + if ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + echo 'Directory Ping Response:'; + echo ''; + } + return $response; } +// ------------------- +// Check and Send Ping +// ------------------- +// 2.3.2: send queued directory ping +add_action( 'admin_footer', 'radio_station_check_directory_ping', 99 ); +function radio_station_check_directory_ping() { + $ping = get_option( 'radio_station_ping_directory' ); + if ( $ping ) { + $response = radio_station_send_directory_ping(); + if ( !is_wp_error( $response ) && isset( $response['response']['code'] ) && ( 200 == $response['response']['code'] ) ) { + delete_option( 'radio_station_ping_directory' ); + } + } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + $response = radio_station_send_directory_ping(); + } +} // ------------------------ // === Time Conversions === @@ -3105,28 +3188,53 @@ function radio_station_get_timezone_options( $include_wp_timezone = false ) { return $options; } -// ----------------- -// Timezone Selector -// ----------------- -// 2.3.2: added for user timezone selection -function radio_station_timezone_selector( $id ) { - - $onchange = "radio_change_timezone('" . esc_js( $id ) . "');"; - $select .= ''; - - return $select; + return $weekdays; } +// ------------ +// Get Month(s) +// ------------ +// 2.3.2: added get weekday from number helper +function radio_station_get_month( $month_number = null ) { + + $months = array( + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ); + if ( !is_null( $month_number ) ) { + return $months[$month_number]; + } + return $months; +} // --------------------- // Get Schedule Weekdays @@ -3180,28 +3288,6 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { return $weekdays; } -// ----------- -// Get Weekday -// ----------- -// 2.3.2: added get weekday from number helper -function radio_station_get_weekday( $day_number = null ) { - - $weekdays = array( - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ); - - if ( !is_null( $day_number ) ) { - return $weekdays[$day_number]; - } - return $weekdays; -} - // ---------------------- // Get Schedule Weekdates // ---------------------- @@ -3318,65 +3404,64 @@ function radio_station_get_previous_day( $day ) { return ''; } -// ----------------- -// Get Previous Date -// ----------------- +// ------------- +// Get Next Date +// ------------- // 2.3.2: added for more reliable calculations -function radio_station_get_previous_date( $date, $weekday = false ) { +function radio_station_get_next_date( $date, $weekday = false ) { // note: this is used internally so timezone not used $timedate = strtotime( $date ); - $timedate = $timedate - ( 24 * 60 * 60 ); + $timedate = $timedate + ( 24 * 60 * 60 ); if ( $weekday ) { $day = date( 'l', $timedate ); if ( $day != $weekday ) { $i = 0; while ( $day != $weekday ) { - $timedate = $timedate - ( 24 * 60 * 60 ); + $timedate = $timedate + ( 24 * 60 * 60 ); $day = strtotime( 'l', $timedate ); if ( 8 == $i ) { // - failback for while failure - $timedate = strtotime( $date ); - $previous_date = date( 'previous ' . $weekday, $timedate ); - return $previous_date; + $next_date = date( 'next ' . $weekday, $timedate ); + return $next_date; } $i++; } } } - $previous_date = date( 'Y-m-d', $timedate ); - return $previous_date; + $next_date = date( 'Y-m-d', $timedate ); + return $next_date; } -// ------------- -// Get Next Date -// ------------- +// ----------------- +// Get Previous Date +// ----------------- // 2.3.2: added for more reliable calculations -function radio_station_get_next_date( $date, $weekday = false ) { +function radio_station_get_previous_date( $date, $weekday = false ) { // note: this is used internally so timezone not used $timedate = strtotime( $date ); - $timedate = $timedate + ( 24 * 60 * 60 ); + $timedate = $timedate - ( 24 * 60 * 60 ); if ( $weekday ) { $day = date( 'l', $timedate ); if ( $day != $weekday ) { $i = 0; while ( $day != $weekday ) { - $timedate = $timedate + ( 24 * 60 * 60 ); + $timedate = $timedate - ( 24 * 60 * 60 ); $day = strtotime( 'l', $timedate ); if ( 8 == $i ) { // - failback for while failure - - echo "WEEKDAY FAIL: " . $weekday; $timedate = strtotime( $date ); - $next_date = date( 'next ' . $weekday, $timedate ); - return $next_date; + $previous_date = date( 'previous ' . $weekday, $timedate ); + return $previous_date; } $i++; } } } - $next_date = date( 'Y-m-d', $timedate ); - return $next_date; + $previous_date = date( 'Y-m-d', $timedate ); + return $previous_date; } // ------------- @@ -3899,7 +3984,8 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // important note: translated individually as cannot translate a variable // 2.2.7: use wp locale class to translate weekdays // 2.3.0: allow for abbreviated and long version changeovers -function radio_station_translate_weekday( $weekday, $short = false ) { +// 2.3.2: default short to null for more flexibility +function radio_station_translate_weekday( $weekday, $short = null ) { // 2.3.0: return empty for empty select option if ( empty( $weekday ) ) { @@ -3908,63 +3994,64 @@ function radio_station_translate_weekday( $weekday, $short = false ) { global $wp_locale; - if ( $short ) { - - // --- translate abbreviated weekday --- - if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); - } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); - } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); - } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); - } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); - } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); - } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); - } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { - // 2.3.0: add support for numeric weekday value - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $weekday ) ); + $days = radio_station_get_weekday(); + + // --- translate weekday --- + // 2.3.2: optimized weekday translations + foreach ( $days as $i => $day ) { + $abbr = substr( $day, 0, 3 ); + if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) + || ( is_null( $short ) && ( $weekday == $day ) ) ) { + return $wp_locale->get_weekday( $i ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); + } } + } - } else { - - // --- translate full weekday --- - // 2.2.7: fix to typo for Tuesday - // 2.3.0: fix to typo for Thursday (jeez!) - if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 0 ); - } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 1 ); - } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 2 ); - } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 3 ); - } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 4 ); - } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 5 ); - } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 6 ); - } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { - // 2.3.0: add support for numeric weekday value - $weekday = $wp_locale->get_weekday( $weekday ); + // --- fallback if day number supplied --- + // 2.3.2: optimized day number fallback + $daynum = intval( $weekday ); + if ( ( $daynum > -1 ) && ( $daynum < 7 ) ) { + if ( $short ) { + return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $daynum ) ); + } else { + return $wp_locale->get_weekday( $daynum ); } - } - + return $weekday; } +// ---------------- +// Replace Weekdays +// ---------------- +// 2.3.2: to replace with translated weekdays in a time string +function radio_station_replace_weekday( $string ) { + + $days = radio_station_get_weekday(); + foreach( $days as $day ) { + $abbr = substr( $day, 0, 3 ); + if ( strstr( $string, $day ) ) { + $translated = radio_station_translate_weekday( $day ); + $string = str_replace( $day, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = radio_station_translate_weekday( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } + } + + return $string; +} + // --------------- // Translate Month // --------------- // important note: translated individually as cannot translate a variable // 2.2.7: use wp locale class to translate months -function radio_station_translate_month( $month, $short = false ) { +// 2.3.2: default short to null for more flexibility +function radio_station_translate_month( $month, $short = null ) { // 2.3.0: return empty for empty select option if ( empty( $month ) ) { @@ -3972,74 +4059,56 @@ function radio_station_translate_month( $month, $short = false ) { } global $wp_locale; - if ( $short ) { - - // --- translate abbreviated month --- - // 2.3.2: allow for short or long input match - if ( ( 'Jan' == $month ) || ( 'January' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); - } elseif ( ( 'Feb' == $month ) || ( 'February' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); - } elseif ( ( 'Mar' == $month ) || ( 'March' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); - } elseif ( ( 'Apr' == $month ) || ( 'April' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); - } elseif ( ( 'May' == $month ) || ( 'May' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); - } elseif ( ( 'Jun' == $month ) || ( 'June' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); - } elseif ( ( 'Jul' == $month ) || ( 'July' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); - } elseif ( ( 'Aug' == $month ) || ( 'August' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); - } elseif ( ( 'Sep' == $month ) || ( 'September' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); - } elseif ( ( 'Oct' == $month ) || ( 'October' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); - } elseif ( ( 'Nov' == $month ) || ( 'November' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); - } elseif ( ( 'Dec' == $month ) || ( 'December' == $month ) ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); - } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { - // 2.3.0: add support for numeric month value - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( $month ) ); + + $months = radio_station_get_month(); + + // --- translate month --- + // 2.3.2: optimized month translations + foreach ( $months as $i => $fullmonth ) { + $abbr = substr( $fullmonth, 0, 3 ); + if ( ( $month == $fullmonth ) || ( $month == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) + || ( is_null( $short ) && ( $month == $fullmonth ) ) ) { + return $wp_locale->get_month( $i ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + return $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) ); + } } - } else { + } - // --- translate full month --- - // 2.3.2: allow for short or long input match - if ( ( 'Jan' == $month ) || ( 'January' == $month ) ) { - $month = $wp_locale->get_month( 1 ); - } elseif ( ( 'Feb' == $month ) || ( 'February' == $month ) ) { - $month = $wp_locale->get_month( 2 ); - } elseif ( ( 'Mar' == $month ) || ( 'March' == $month ) ) { - $month = $wp_locale->get_month( 3 ); - } elseif ( ( 'Apr' == $month ) || ( 'April' == $month ) ) { - $month = $wp_locale->get_month( 4 ); - } elseif ( ( 'May' == $month ) || ( 'May' == $month ) ) { - $month = $wp_locale->get_month( 5 ); - } elseif ( ( 'Jun' == $month ) || ( 'June' == $month ) ) { - $month = $wp_locale->get_month( 6 ); - } elseif ( ( 'Jul' == $month ) || ( 'July' == $month ) ) { - $month = $wp_locale->get_month( 7 ); - } elseif ( ( 'Aug' == $month ) || ( 'August' == $month ) ) { - $month = $wp_locale->get_month( 8 ); - } elseif ( ( 'Sep' == $month ) || ( 'September' == $month ) ) { - $month = $wp_locale->get_month( 9 ); - } elseif ( ( 'Oct' == $month ) || ( 'October' == $month ) ) { - $month = $wp_locale->get_month( 10 ); - } elseif ( ( 'Nov' == $month ) || ( 'November' == $month ) ) { - $month = $wp_locale->get_month( 11 ); - } elseif ( ( 'Dec' == $month ) || ( 'December' == $month ) ) { - $month = $wp_locale->get_month( 12 ); - } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { - // 2.3.0: add support for numeric month value - $month = $wp_locale->get_month( $month ); + // --- fallback if month number supplied --- + // 2.3.2: optimized month number fallback + $monthnum = intval( $month ); + if ( ( $monthnum > 0 ) && ( $monthnum < 13 ) ) { + if ( $short ) { + return $wp_locale->get_month_abbrev( $wp_locale->get_month( $monthnum ) ); + } else { + return $wp_locale->get_month( $monthnum ); } + } + + return $month; +} +// -------------- +// Replace Months +// -------------- +// 2.3.2: to replace with translated months in a time string +function radio_station_replace_month( $string ) { + + $months = radio_station_get_month(); + foreach( $months as $month ) { + $abbr = substr( $month, 0, 3 ); + if ( strstr( $string, $month ) ) { + $translated = radio_station_translate_month( $month ); + $string = str_replace( $month, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = radio_station_translate_month( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } } - return $month; + return $string; } // ------------------ @@ -4052,3 +4121,51 @@ function radio_station_translate_meridiem( $meridiem ) { return $wp_locale->get_meridiem( $meridiem ); } +// ---------------- +// Replace Meridiem +// ---------------- +// 2.3.2: added optimized meridiem replacement +function radio_station_replace_meridiem( $string ) { + + global $radio_station_data; + if ( isset( $radio_station_data['meridiems'] ) ) { + $meridiems = $radio_station_data['meridiems']; + } else { + $meridiems = array( + 'am' => radio_station_translate_meridiem( 'am' ), + 'pm' => radio_station_translate_meridiem( 'pm' ), + 'AM' => radio_station_translate_meridiem( 'AM' ), + 'PM' => radio_station_translate_meridiem( 'PM' ), + ); + $radio_station_data['meridiems'] = $meridiems; + } + + + if ( strstr( $string, 'am' ) ) { + $string = str_replace( 'am', $meridiems['am'], $string ); + } + if ( strstr( $string, 'pm' ) ) { + $string = str_replace( 'pm', $meridiems['pm'], $string ); + } + if ( strstr( $string, 'AM' ) ) { + $string = str_replace( 'AM', $meridiems['AM'], $string ); + } + if ( strstr( $string, 'PM' ) ) { + $string = str_replace( 'PM', $meridiems['PM'], $string ); + } + + return $string; +} + +// --------------------- +// Translate Time String +// --------------------- +// 2.3.2: replace with translated month, day and meridiem in a string +function radio_station_translate_time( $string ) { + + $string = radio_station_replace_meridiem( $string ); + $string = radio_station_replace_weekday( $string ); + $string = radio_station_replace_month( $string ); + + return $string; +} diff --git a/js/jstz.js b/js/jstz.js new file mode 100644 index 0000000..f849c57 --- /dev/null +++ b/js/jstz.js @@ -0,0 +1,1462 @@ +(function (root) {/*global exports, Intl*/ +/** + * This script gives you the zone info key representing your device's time zone setting. + * + * @name jsTimezoneDetect + * @version 1.0.6 + * @author Jon Nylander + * @license MIT License - https://bitbucket.org/pellepim/jstimezonedetect/src/default/LICENCE.txt + * + * For usage and examples, visit: + * http://pellepim.bitbucket.org/jstz/ + * + * Copyright (c) Jon Nylander + */ + + +/** + * Namespace to hold all the code for timezone detection. + */ +var jstz = (function () { + 'use strict'; + var HEMISPHERE_SOUTH = 's', + + consts = { + DAY: 86400000, + HOUR: 3600000, + MINUTE: 60000, + SECOND: 1000, + BASELINE_YEAR: 2014, + MAX_SCORE: 864000000, // 10 days + AMBIGUITIES: { + 'America/Denver': ['America/Mazatlan'], + 'America/Chicago': ['America/Mexico_City'], + 'America/Asuncion': ['America/Campo_Grande', 'America/Santiago'], + 'America/Montevideo': ['America/Sao_Paulo', 'America/Santiago'], + 'Asia/Beirut': ['Asia/Amman', 'Asia/Jerusalem', 'Europe/Helsinki', 'Asia/Damascus', 'Africa/Cairo', 'Asia/Gaza', 'Europe/Minsk', 'Africa/Windhoek'], + 'Pacific/Auckland': ['Pacific/Fiji'], + 'America/Los_Angeles': ['America/Santa_Isabel'], + 'America/New_York': ['America/Havana'], + 'America/Halifax': ['America/Goose_Bay'], + 'America/Godthab': ['America/Miquelon'], + 'Asia/Dubai': ['Asia/Yerevan'], + 'Asia/Jakarta': ['Asia/Krasnoyarsk'], + 'Asia/Shanghai': ['Asia/Irkutsk', 'Australia/Perth'], + 'Australia/Sydney': ['Australia/Lord_Howe'], + 'Asia/Tokyo': ['Asia/Yakutsk'], + 'Asia/Dhaka': ['Asia/Omsk'], + 'Asia/Baku': ['Asia/Yerevan'], + 'Australia/Brisbane': ['Asia/Vladivostok'], + 'Pacific/Noumea': ['Asia/Vladivostok'], + 'Pacific/Majuro': ['Asia/Kamchatka', 'Pacific/Fiji'], + 'Pacific/Tongatapu': ['Pacific/Apia'], + 'Asia/Baghdad': ['Europe/Minsk', 'Europe/Moscow'], + 'Asia/Karachi': ['Asia/Yekaterinburg'], + 'Africa/Johannesburg': ['Asia/Gaza', 'Africa/Cairo'] + } + }, + + /** + * Gets the offset in minutes from UTC for a certain date. + * @param {Date} date + * @returns {Number} + */ + get_date_offset = function get_date_offset(date) { + var offset = -date.getTimezoneOffset(); + return (offset !== null ? offset : 0); + }, + + + get_offsets = function get_offsets() { + var offsets = []; + + for (var month = 0; month <= 11; month++) { + for (var date = 1; date <= 28; date++) { + var currentOffset = get_date_offset(new Date(consts.BASELINE_YEAR, month, date)); + if (!offsets) { + offsets.push(); + } else if (offsets && offsets[offsets.length-1] !== currentOffset) { + offsets.push(currentOffset); + } + } + } + + return offsets; + }, + + /** + * This function does some basic calculations to create information about + * the user's timezone. It uses REFERENCE_YEAR as a solid year for which + * the script has been tested rather than depend on the year set by the + * client device. + * + * Returns a key that can be used to do lookups in jstz.olson.timezones. + * eg: "720,1,2". + * + * @returns {String} + */ + lookup_key = function lookup_key() { + var diff = 0; + var offsets = get_offsets(); + + if (offsets.length > 1) { + diff = offsets[0] - offsets[1]; + } + + if (offsets.length > 3) { + return offsets[0] + ",1,weird"; + } else if (diff < 0) { + return offsets[0] + ",1"; + } else if (diff > 0) { + return offsets[1] + ",1," + HEMISPHERE_SOUTH; + } + + return offsets[0] + ",0"; + }, + + + /** + * Tries to get the time zone key directly from the operating system for those + * environments that support the ECMAScript Internationalization API. + */ + get_from_internationalization_api = function get_from_internationalization_api() { + var format, timezone; + if (!Intl || typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") { + return; + } + + format = Intl.DateTimeFormat(); + + if (typeof format === "undefined" || typeof format.resolvedOptions === "undefined") { + return; + } + + timezone = format.resolvedOptions().timeZone; + + if (timezone && (timezone.indexOf("/") > -1 || timezone === 'UTC')) { + return timezone; + } + + }, + + /** + * Starting point for getting all the DST rules for a specific year + * for the current timezone (as described by the client system). + * + * Returns an object with start and end attributes, or false if no + * DST rules were found for the year. + * + * @param year + * @returns {Object} || {Boolean} + */ + dst_dates = function dst_dates(year) { + var yearstart = new Date(year, 0, 1, 0, 0, 1, 0).getTime(); + var yearend = new Date(year, 12, 31, 23, 59, 59).getTime(); + var current = yearstart; + var offset = (new Date(current)).getTimezoneOffset(); + var dst_start = null; + var dst_end = null; + + while (current < yearend - 86400000) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + if (dateToCheckOffset < offset) { + dst_start = dateToCheck; + } + if (dateToCheckOffset > offset) { + dst_end = dateToCheck; + } + offset = dateToCheckOffset; + } + + current += 86400000; + } + + if (dst_start && dst_end) { + return { + s: find_dst_fold(dst_start).getTime(), + e: find_dst_fold(dst_end).getTime() + }; + } + + return false; + }, + + /** + * Probably completely unnecessary function that recursively finds the + * exact (to the second) time when a DST rule was changed. + * + * @param a_date - The candidate Date. + * @param padding - integer specifying the padding to allow around the candidate + * date for finding the fold. + * @param iterator - integer specifying how many milliseconds to iterate while + * searching for the fold. + * + * @returns {Date} + */ + find_dst_fold = function find_dst_fold(a_date, padding, iterator) { + if (typeof padding === 'undefined') { + padding = consts.DAY; + iterator = consts.HOUR; + } + + var date_start = new Date(a_date.getTime() - padding).getTime(); + var date_end = a_date.getTime() + padding; + var offset = new Date(date_start).getTimezoneOffset(); + + var current = date_start; + + var dst_change = null; + while (current < date_end - iterator) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + dst_change = dateToCheck; + break; + } + current += iterator; + } + + if (padding === consts.DAY) { + return find_dst_fold(dst_change, consts.HOUR, consts.MINUTE); + } + + if (padding === consts.HOUR) { + return find_dst_fold(dst_change, consts.MINUTE, consts.SECOND); + } + + return dst_change; + }, + + windows7_adaptations = function windows7_adaptions(rule_list, preliminary_timezone, score, sample) { + if (score !== 'N/A') { + return score; + } + if (preliminary_timezone === 'Asia/Beirut') { + if (sample.name === 'Africa/Cairo') { + if (rule_list[6].s === 1398376800000 && rule_list[6].e === 1411678800000) { + return 0; + } + } + if (sample.name === 'Asia/Jerusalem') { + if (rule_list[6].s === 1395964800000 && rule_list[6].e === 1411858800000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Santiago') { + if (sample.name === 'America/Asuncion') { + if (rule_list[6].s === 1412481600000 && rule_list[6].e === 1397358000000) { + return 0; + } + } + if (sample.name === 'America/Campo_Grande') { + if (rule_list[6].s === 1413691200000 && rule_list[6].e === 1392519600000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Montevideo') { + if (sample.name === 'America/Sao_Paulo') { + if (rule_list[6].s === 1413687600000 && rule_list[6].e === 1392516000000) { + return 0; + } + } + } else if (preliminary_timezone === 'Pacific/Auckland') { + if (sample.name === 'Pacific/Fiji') { + if (rule_list[6].s === 1414245600000 && rule_list[6].e === 1396101600000) { + return 0; + } + } + } + + return score; + }, + + /** + * Takes the DST rules for the current timezone, and proceeds to find matches + * in the jstz.olson.dst_rules.zones array. + * + * Compares samples to the current timezone on a scoring basis. + * + * Candidates are ruled immediately if either the candidate or the current zone + * has a DST rule where the other does not. + * + * Candidates are ruled out immediately if the current zone has a rule that is + * outside the DST scope of the candidate. + * + * Candidates are included for scoring if the current zones rules fall within the + * span of the samples rules. + * + * Low score is best, the score is calculated by summing up the differences in DST + * rules and if the consts.MAX_SCORE is overreached the candidate is ruled out. + * + * Yah follow? :) + * + * @param rule_list + * @param preliminary_timezone + * @returns {*} + */ + best_dst_match = function best_dst_match(rule_list, preliminary_timezone) { + var score_sample = function score_sample(sample) { + var score = 0; + + for (var j = 0; j < rule_list.length; j++) { + + // Both sample and current time zone report DST during the year. + if (!!sample.rules[j] && !!rule_list[j]) { + + // The current time zone's DST rules are inside the sample's. Include. + if (rule_list[j].s >= sample.rules[j].s && rule_list[j].e <= sample.rules[j].e) { + score = 0; + score += Math.abs(rule_list[j].s - sample.rules[j].s); + score += Math.abs(sample.rules[j].e - rule_list[j].e); + + // The current time zone's DST rules are outside the sample's. Discard. + } else { + score = 'N/A'; + break; + } + + // The max score has been reached. Discard. + if (score > consts.MAX_SCORE) { + score = 'N/A'; + break; + } + } + } + + score = windows7_adaptations(rule_list, preliminary_timezone, score, sample); + + return score; + }; + var scoreboard = {}; + var dst_zones = jstz.olson.dst_rules.zones; + var dst_zones_length = dst_zones.length; + var ambiguities = consts.AMBIGUITIES[preliminary_timezone]; + + for (var i = 0; i < dst_zones_length; i++) { + var sample = dst_zones[i]; + var score = score_sample(dst_zones[i]); + + if (score !== 'N/A') { + scoreboard[sample.name] = score; + } + } + + for (var tz in scoreboard) { + if (scoreboard.hasOwnProperty(tz)) { + for (var j = 0; j < ambiguities.length; j++) { + if (ambiguities[j] === tz) { + return tz; + } + } + } + } + + return preliminary_timezone; + }, + + /** + * Takes the preliminary_timezone as detected by lookup_key(). + * + * Builds up the current timezones DST rules for the years defined + * in the jstz.olson.dst_rules.years array. + * + * If there are no DST occurences for those years, immediately returns + * the preliminary timezone. Otherwise proceeds and tries to solve + * ambiguities. + * + * @param preliminary_timezone + * @returns {String} timezone_name + */ + get_by_dst = function get_by_dst(preliminary_timezone) { + var get_rules = function get_rules() { + var rule_list = []; + for (var i = 0; i < jstz.olson.dst_rules.years.length; i++) { + var year_rules = dst_dates(jstz.olson.dst_rules.years[i]); + rule_list.push(year_rules); + } + return rule_list; + }; + var check_has_dst = function check_has_dst(rules) { + for (var i = 0; i < rules.length; i++) { + if (rules[i] !== false) { + return true; + } + } + return false; + }; + var rules = get_rules(); + var has_dst = check_has_dst(rules); + + if (has_dst) { + return best_dst_match(rules, preliminary_timezone); + } + + return preliminary_timezone; + }, + + /** + * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary. + * + * Returns an object with one function ".name()" + * + * @returns Object + */ + determine = function determine(using_intl) { + var preliminary_tz = false; + var needle = lookup_key(); + if (using_intl || typeof using_intl === 'undefined') { + preliminary_tz = get_from_internationalization_api(); + } + + if (!preliminary_tz) { + preliminary_tz = jstz.olson.timezones[needle]; + + if (typeof consts.AMBIGUITIES[preliminary_tz] !== 'undefined') { + preliminary_tz = get_by_dst(preliminary_tz); + } + } + + return { + name: function () { + return preliminary_tz; + }, + using_intl: using_intl || typeof using_intl === 'undefined', + needle: needle, + offsets: get_offsets() + }; + }; + + return { + determine: determine + }; +}()); + + +jstz.olson = jstz.olson || {}; + +/** + * The keys in this dictionary are comma separated as such: + * + * First the offset compared to UTC time in minutes. + * + * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it + * does. + * + * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere, + * only interesting for timezones with DST. + * + * The mapped arrays is used for constructing the jstz.TimeZone object from within + * jstz.determine(); + */ +jstz.olson.timezones = { + '-720,0': 'Etc/GMT+12', + '-660,0': 'Pacific/Pago_Pago', + '-660,1,s': 'Pacific/Apia', // Why? Because windows... cry! + '-600,1': 'America/Adak', + '-600,0': 'Pacific/Honolulu', + '-570,0': 'Pacific/Marquesas', + '-540,0': 'Pacific/Gambier', + '-540,1': 'America/Anchorage', + '-480,1': 'America/Los_Angeles', + '-480,0': 'Pacific/Pitcairn', + '-420,0': 'America/Phoenix', + '-420,1': 'America/Denver', + '-360,0': 'America/Guatemala', + '-360,1': 'America/Chicago', + '-360,1,s': 'Pacific/Easter', + '-300,0': 'America/Bogota', + '-300,1': 'America/New_York', + '-270,0': 'America/Caracas', + '-240,1': 'America/Halifax', + '-240,0': 'America/Santo_Domingo', + '-240,1,s': 'America/Asuncion', + '-210,1': 'America/St_Johns', + '-180,1': 'America/Godthab', + '-180,0': 'America/Buenos_Aires', + '-180,1,s': 'America/Montevideo', + '-120,0': 'America/Noronha', + '-120,1': 'America/Noronha', + '-60,1': 'Atlantic/Azores', + '-60,0': 'Atlantic/Cape_Verde', + '0,0': 'UTC', + '0,1': 'Europe/London', + '0,1,weird': 'Africa/Casablanca', + '60,1': 'Europe/Berlin', + '60,0': 'Africa/Lagos', + '60,1,weird': 'Africa/Casablanca', + '120,1': 'Asia/Beirut', + '120,1,weird': 'Africa/Cairo', + '120,0': 'Africa/Johannesburg', + '180,0': 'Asia/Baghdad', + '180,1': 'Europe/Moscow', + '210,1': 'Asia/Tehran', + '240,0': 'Asia/Dubai', + '240,1': 'Asia/Baku', + '270,0': 'Asia/Kabul', + '300,1': 'Asia/Yekaterinburg', + '300,0': 'Asia/Karachi', + '330,0': 'Asia/Calcutta', + '345,0': 'Asia/Katmandu', + '360,0': 'Asia/Dhaka', + '360,1': 'Asia/Omsk', + '390,0': 'Asia/Rangoon', + '420,1': 'Asia/Krasnoyarsk', + '420,0': 'Asia/Jakarta', + '480,0': 'Asia/Shanghai', + '480,1': 'Asia/Irkutsk', + '525,0': 'Australia/Eucla', + '525,1,s': 'Australia/Eucla', + '540,1': 'Asia/Yakutsk', + '540,0': 'Asia/Tokyo', + '570,0': 'Australia/Darwin', + '570,1,s': 'Australia/Adelaide', + '600,0': 'Australia/Brisbane', + '600,1': 'Asia/Vladivostok', + '600,1,s': 'Australia/Sydney', + '630,1,s': 'Australia/Lord_Howe', + '660,1': 'Asia/Kamchatka', + '660,0': 'Pacific/Noumea', + '690,0': 'Pacific/Norfolk', + '720,1,s': 'Pacific/Auckland', + '720,0': 'Pacific/Majuro', + '765,1,s': 'Pacific/Chatham', + '780,0': 'Pacific/Tongatapu', + '780,1,s': 'Pacific/Apia', + '840,0': 'Pacific/Kiritimati' +}; + +/* Build time: 2019-09-09 11:29:41Z Build by invoking python utilities/dst.py generate */ +jstz.olson.dst_rules = { + "years": [ + 2008, + 2009, + 2010, + 2011, + 2012, + 2013, + 2014 + ], + "zones": [ + { + "name": "Africa/Cairo", + "rules": [ + { + "e": 1219957200000, + "s": 1209074400000 + }, + { + "e": 1250802000000, + "s": 1240524000000 + }, + { + "e": 1285880400000, + "s": 1284069600000 + }, + false, + false, + false, + { + "e": 1411678800000, + "s": 1406844000000 + } + ] + }, + { + "name": "America/Asuncion", + "rules": [ + { + "e": 1205031600000, + "s": 1224388800000 + }, + { + "e": 1236481200000, + "s": 1255838400000 + }, + { + "e": 1270954800000, + "s": 1286078400000 + }, + { + "e": 1302404400000, + "s": 1317528000000 + }, + { + "e": 1333854000000, + "s": 1349582400000 + }, + { + "e": 1364094000000, + "s": 1381032000000 + }, + { + "e": 1395543600000, + "s": 1412481600000 + } + ] + }, + { + "name": "America/Campo_Grande", + "rules": [ + { + "e": 1203217200000, + "s": 1224388800000 + }, + { + "e": 1234666800000, + "s": 1255838400000 + }, + { + "e": 1266721200000, + "s": 1287288000000 + }, + { + "e": 1298170800000, + "s": 1318737600000 + }, + { + "e": 1330225200000, + "s": 1350792000000 + }, + { + "e": 1361070000000, + "s": 1382241600000 + }, + { + "e": 1392519600000, + "s": 1413691200000 + } + ] + }, + { + "name": "America/Goose_Bay", + "rules": [ + { + "e": 1225594860000, + "s": 1205035260000 + }, + { + "e": 1257044460000, + "s": 1236484860000 + }, + { + "e": 1289098860000, + "s": 1268539260000 + }, + { + "e": 1320555600000, + "s": 1299988860000 + }, + { + "e": 1352005200000, + "s": 1331445600000 + }, + { + "e": 1383454800000, + "s": 1362895200000 + }, + { + "e": 1414904400000, + "s": 1394344800000 + } + ] + }, + { + "name": "America/Havana", + "rules": [ + { + "e": 1224997200000, + "s": 1205643600000 + }, + { + "e": 1256446800000, + "s": 1236488400000 + }, + { + "e": 1288501200000, + "s": 1268542800000 + }, + { + "e": 1321160400000, + "s": 1300597200000 + }, + { + "e": 1352005200000, + "s": 1333256400000 + }, + { + "e": 1383454800000, + "s": 1362891600000 + }, + { + "e": 1414904400000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Mazatlan", + "rules": [ + { + "e": 1225008000000, + "s": 1207472400000 + }, + { + "e": 1256457600000, + "s": 1238922000000 + }, + { + "e": 1288512000000, + "s": 1270371600000 + }, + { + "e": 1319961600000, + "s": 1301821200000 + }, + { + "e": 1351411200000, + "s": 1333270800000 + }, + { + "e": 1382860800000, + "s": 1365325200000 + }, + { + "e": 1414310400000, + "s": 1396774800000 + } + ] + }, + { + "name": "America/Mexico_City", + "rules": [ + { + "e": 1225004400000, + "s": 1207468800000 + }, + { + "e": 1256454000000, + "s": 1238918400000 + }, + { + "e": 1288508400000, + "s": 1270368000000 + }, + { + "e": 1319958000000, + "s": 1301817600000 + }, + { + "e": 1351407600000, + "s": 1333267200000 + }, + { + "e": 1382857200000, + "s": 1365321600000 + }, + { + "e": 1414306800000, + "s": 1396771200000 + } + ] + }, + { + "name": "America/Miquelon", + "rules": [ + { + "e": 1225598400000, + "s": 1205038800000 + }, + { + "e": 1257048000000, + "s": 1236488400000 + }, + { + "e": 1289102400000, + "s": 1268542800000 + }, + { + "e": 1320552000000, + "s": 1299992400000 + }, + { + "e": 1352001600000, + "s": 1331442000000 + }, + { + "e": 1383451200000, + "s": 1362891600000 + }, + { + "e": 1414900800000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Santa_Isabel", + "rules": [ + { + "e": 1225011600000, + "s": 1207476000000 + }, + { + "e": 1256461200000, + "s": 1238925600000 + }, + { + "e": 1289120400000, + "s": 1268560800000 + }, + { + "e": 1320570000000, + "s": 1300010400000 + }, + { + "e": 1352019600000, + "s": 1331460000000 + }, + { + "e": 1383469200000, + "s": 1362909600000 + }, + { + "e": 1414918800000, + "s": 1394359200000 + } + ] + }, + { + "name": "America/Santiago", + "rules": [ + { + "e": 1206846000000, + "s": 1223784000000 + }, + { + "e": 1237086000000, + "s": 1255233600000 + }, + { + "e": 1270350000000, + "s": 1286683200000 + }, + { + "e": 1304823600000, + "s": 1313899200000 + }, + { + "e": 1335668400000, + "s": 1346558400000 + }, + { + "e": 1367118000000, + "s": 1378612800000 + }, + { + "e": 1398567600000, + "s": 1410062400000 + } + ] + }, + { + "name": "America/Sao_Paulo", + "rules": [ + { + "e": 1203213600000, + "s": 1224385200000 + }, + { + "e": 1234663200000, + "s": 1255834800000 + }, + { + "e": 1266717600000, + "s": 1287284400000 + }, + { + "e": 1298167200000, + "s": 1318734000000 + }, + { + "e": 1330221600000, + "s": 1350788400000 + }, + { + "e": 1361066400000, + "s": 1382238000000 + }, + { + "e": 1392516000000, + "s": 1413687600000 + } + ] + }, + { + "name": "Asia/Amman", + "rules": [ + { + "e": 1225404000000, + "s": 1206655200000 + }, + { + "e": 1256853600000, + "s": 1238104800000 + }, + { + "e": 1288303200000, + "s": 1269554400000 + }, + { + "e": 1319752800000, + "s": 1301608800000 + }, + false, + false, + { + "e": 1414706400000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Damascus", + "rules": [ + { + "e": 1225486800000, + "s": 1207260000000 + }, + { + "e": 1256850000000, + "s": 1238104800000 + }, + { + "e": 1288299600000, + "s": 1270159200000 + }, + { + "e": 1319749200000, + "s": 1301608800000 + }, + { + "e": 1351198800000, + "s": 1333058400000 + }, + { + "e": 1382648400000, + "s": 1364508000000 + }, + { + "e": 1414702800000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Dubai", + "rules": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Gaza", + "rules": [ + { + "e": 1219957200000, + "s": 1206655200000 + }, + { + "e": 1252015200000, + "s": 1238104800000 + }, + { + "e": 1281474000000, + "s": 1269640860000 + }, + { + "e": 1312146000000, + "s": 1301608860000 + }, + { + "e": 1348178400000, + "s": 1333058400000 + }, + { + "e": 1380229200000, + "s": 1364508000000 + }, + { + "e": 1414098000000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Irkutsk", + "rules": [ + { + "e": 1224957600000, + "s": 1206813600000 + }, + { + "e": 1256407200000, + "s": 1238263200000 + }, + { + "e": 1288461600000, + "s": 1269712800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Jerusalem", + "rules": [ + { + "e": 1223161200000, + "s": 1206662400000 + }, + { + "e": 1254006000000, + "s": 1238112000000 + }, + { + "e": 1284246000000, + "s": 1269561600000 + }, + { + "e": 1317510000000, + "s": 1301616000000 + }, + { + "e": 1348354800000, + "s": 1333065600000 + }, + { + "e": 1382828400000, + "s": 1364515200000 + }, + { + "e": 1414278000000, + "s": 1395964800000 + } + ] + }, + { + "name": "Asia/Kamchatka", + "rules": [ + { + "e": 1224943200000, + "s": 1206799200000 + }, + { + "e": 1256392800000, + "s": 1238248800000 + }, + { + "e": 1288450800000, + "s": 1269698400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Krasnoyarsk", + "rules": [ + { + "e": 1224961200000, + "s": 1206817200000 + }, + { + "e": 1256410800000, + "s": 1238266800000 + }, + { + "e": 1288465200000, + "s": 1269716400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Omsk", + "rules": [ + { + "e": 1224964800000, + "s": 1206820800000 + }, + { + "e": 1256414400000, + "s": 1238270400000 + }, + { + "e": 1288468800000, + "s": 1269720000000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Vladivostok", + "rules": [ + { + "e": 1224950400000, + "s": 1206806400000 + }, + { + "e": 1256400000000, + "s": 1238256000000 + }, + { + "e": 1288454400000, + "s": 1269705600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yakutsk", + "rules": [ + { + "e": 1224954000000, + "s": 1206810000000 + }, + { + "e": 1256403600000, + "s": 1238259600000 + }, + { + "e": 1288458000000, + "s": 1269709200000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yekaterinburg", + "rules": [ + { + "e": 1224968400000, + "s": 1206824400000 + }, + { + "e": 1256418000000, + "s": 1238274000000 + }, + { + "e": 1288472400000, + "s": 1269723600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yerevan", + "rules": [ + { + "e": 1224972000000, + "s": 1206828000000 + }, + { + "e": 1256421600000, + "s": 1238277600000 + }, + { + "e": 1288476000000, + "s": 1269727200000 + }, + { + "e": 1319925600000, + "s": 1301176800000 + }, + false, + false, + false + ] + }, + { + "name": "Australia/Lord_Howe", + "rules": [ + { + "e": 1207407600000, + "s": 1223134200000 + }, + { + "e": 1238857200000, + "s": 1254583800000 + }, + { + "e": 1270306800000, + "s": 1286033400000 + }, + { + "e": 1301756400000, + "s": 1317483000000 + }, + { + "e": 1333206000000, + "s": 1349537400000 + }, + { + "e": 1365260400000, + "s": 1380987000000 + }, + { + "e": 1396710000000, + "s": 1412436600000 + } + ] + }, + { + "name": "Australia/Perth", + "rules": [ + { + "e": 1206813600000, + "s": 1224957600000 + }, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Helsinki", + "rules": [ + { + "e": 1224982800000, + "s": 1206838800000 + }, + { + "e": 1256432400000, + "s": 1238288400000 + }, + { + "e": 1288486800000, + "s": 1269738000000 + }, + { + "e": 1319936400000, + "s": 1301187600000 + }, + { + "e": 1351386000000, + "s": 1332637200000 + }, + { + "e": 1382835600000, + "s": 1364691600000 + }, + { + "e": 1414285200000, + "s": 1396141200000 + } + ] + }, + { + "name": "Europe/Minsk", + "rules": [ + { + "e": 1224979200000, + "s": 1206835200000 + }, + { + "e": 1256428800000, + "s": 1238284800000 + }, + { + "e": 1288483200000, + "s": 1269734400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Moscow", + "rules": [ + { + "e": 1224975600000, + "s": 1206831600000 + }, + { + "e": 1256425200000, + "s": 1238281200000 + }, + { + "e": 1288479600000, + "s": 1269730800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Pacific/Apia", + "rules": [ + false, + false, + false, + { + "e": 1301752800000, + "s": 1316872800000 + }, + { + "e": 1333202400000, + "s": 1348927200000 + }, + { + "e": 1365256800000, + "s": 1380376800000 + }, + { + "e": 1396706400000, + "s": 1411826400000 + } + ] + }, + { + "name": "Pacific/Fiji", + "rules": [ + false, + false, + { + "e": 1269698400000, + "s": 1287842400000 + }, + { + "e": 1327154400000, + "s": 1319292000000 + }, + { + "e": 1358604000000, + "s": 1350741600000 + }, + { + "e": 1390050000000, + "s": 1382796000000 + }, + { + "e": 1421503200000, + "s": 1414850400000 + } + ] + }, + { + "name": "Europe/London", + "rules": [ + { + "e": 1224982800000, + "s": 1206838800000 + }, + { + "e": 1256432400000, + "s": 1238288400000 + }, + { + "e": 1288486800000, + "s": 1269738000000 + }, + { + "e": 1319936400000, + "s": 1301187600000 + }, + { + "e": 1351386000000, + "s": 1332637200000 + }, + { + "e": 1382835600000, + "s": 1364691600000 + }, + { + "e": 1414285200000, + "s": 1396141200000 + } + ] + }, + { + "name": "Africa/Windhoek", + "rules": [ + { + "e": 1220749200000, + "s": 1207440000000 + }, + { + "e": 1252198800000, + "s": 1238889600000 + }, + { + "e": 1283648400000, + "s": 1270339200000 + }, + { + "e": 1315098000000, + "s": 1301788800000 + }, + { + "e": 1346547600000, + "s": 1333238400000 + }, + { + "e": 1377997200000, + "s": 1365292800000 + }, + { + "e": 1410051600000, + "s": 1396742400000 + } + ] + } + ] +}; +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = jstz; +} else if ((typeof define !== 'undefined' && define !== null) && (define.amd != null)) { + define([], function() { + return jstz; + }); +} else { + if (typeof root === 'undefined') { + window.jstz = jstz; + } else { + root.jstz = jstz; + } +} +}()); diff --git a/js/jstz.min.js b/js/jstz.min.js new file mode 100644 index 0000000..a05b7e8 --- /dev/null +++ b/js/jstz.min.js @@ -0,0 +1,2 @@ +/* jstz.min.js Version: 1.0.6 Build date: 2019-09-09 */ +!function(e){var a=function(){"use strict";var e="s",s={DAY:864e5,HOUR:36e5,MINUTE:6e4,SECOND:1e3,BASELINE_YEAR:2014,MAX_SCORE:864e6,AMBIGUITIES:{"America/Denver":["America/Mazatlan"],"America/Chicago":["America/Mexico_City"],"America/Asuncion":["America/Campo_Grande","America/Santiago"],"America/Montevideo":["America/Sao_Paulo","America/Santiago"],"Asia/Beirut":["Asia/Amman","Asia/Jerusalem","Europe/Helsinki","Asia/Damascus","Africa/Cairo","Asia/Gaza","Europe/Minsk","Africa/Windhoek"],"Pacific/Auckland":["Pacific/Fiji"],"America/Los_Angeles":["America/Santa_Isabel"],"America/New_York":["America/Havana"],"America/Halifax":["America/Goose_Bay"],"America/Godthab":["America/Miquelon"],"Asia/Dubai":["Asia/Yerevan"],"Asia/Jakarta":["Asia/Krasnoyarsk"],"Asia/Shanghai":["Asia/Irkutsk","Australia/Perth"],"Australia/Sydney":["Australia/Lord_Howe"],"Asia/Tokyo":["Asia/Yakutsk"],"Asia/Dhaka":["Asia/Omsk"],"Asia/Baku":["Asia/Yerevan"],"Australia/Brisbane":["Asia/Vladivostok"],"Pacific/Noumea":["Asia/Vladivostok"],"Pacific/Majuro":["Asia/Kamchatka","Pacific/Fiji"],"Pacific/Tongatapu":["Pacific/Apia"],"Asia/Baghdad":["Europe/Minsk","Europe/Moscow"],"Asia/Karachi":["Asia/Yekaterinburg"],"Africa/Johannesburg":["Asia/Gaza","Africa/Cairo"]}},i=function(e){var a=-e.getTimezoneOffset();return null!==a?a:0},r=function(){for(var e=[],a=0;a<=11;a++)for(var r=1;r<=28;r++){var n=i(new Date(s.BASELINE_YEAR,a,r));e?e&&e[e.length-1]!==n&&e.push(n):e.push()}return e},n=function(){var a=0,s=r();return s.length>1&&(a=s[0]-s[1]),s.length>3?s[0]+",1,weird":a<0?s[0]+",1":a>0?s[1]+",1,"+e:s[0]+",0"},o=function(){var e,a;if(Intl&&"undefined"!=typeof Intl&&"undefined"!=typeof Intl.DateTimeFormat&&(e=Intl.DateTimeFormat(),"undefined"!=typeof e&&"undefined"!=typeof e.resolvedOptions))return a=e.resolvedOptions().timeZone,a&&(a.indexOf("/")>-1||"UTC"===a)?a:void 0},t=function(e){for(var a=new Date(e,0,1,0,0,1,0).getTime(),s=new Date(e,12,31,23,59,59).getTime(),i=a,r=new Date(i).getTimezoneOffset(),n=null,o=null;ir&&(o=t),r=A),i+=864e5}return!(!n||!o)&&{s:u(n).getTime(),e:u(o).getTime()}},u=function f(e,a,i){"undefined"==typeof a&&(a=s.DAY,i=s.HOUR);for(var r=new Date(e.getTime()-a).getTime(),n=e.getTime()+a,o=new Date(r).getTimezoneOffset(),t=r,u=null;t=a.rules[n].s&&e[n].e<=a.rules[n].e)){r="N/A";break}if(r=0,r+=Math.abs(e[n].s-a.rules[n].s),r+=Math.abs(a.rules[n].e-e[n].e),r>s.MAX_SCORE){r="N/A";break}}return r=A(e,i,r,a)},n={},o=a.olson.dst_rules.zones,t=o.length,u=s.AMBIGUITIES[i],c=0;c -1) { + tz = tz.replace('/', ', '); tz = tz.replace('_',' '); + houroffset = parseInt(useroffset); + if (houroffset == 0) {userzone = ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {tz += ' [UTC+'+houroffset+']';} + else {tz += ' [UTC'+houroffset+']';} + } + jQuery('.radio-user-timezone').html(tz); + jQuery('.radio-user-timezone-title').show(); + } + } + }); +} diff --git a/radio-station-admin.php b/radio-station-admin.php index 804e3c9..4ed3b6e 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -160,11 +160,17 @@ function radio_station_add_admin_menus() { do_action( 'radio_station_admin_submenu_middle' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Hosts', 'radio-station' ), __( 'Hosts', 'radio-station' ), 'edit_hosts', 'hosts' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Producers', 'radio-station' ), __( 'Producers', 'radio-station' ), 'edit_producers', 'producers' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settingscap, 'playlist-export', 'radio_station_playlist_export_page' ); + + // 2.3.2: as temporarily disabled, allow enabling export playlists via filter + $export_playlists = apply_filters( 'radio_station_export_playlists', false ); + if ( $export_playlists ) { + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settingscap, 'playlist-export', 'radio_station_playlist_export_page' ); + } - // --- import / export feature --- + // --- import / export shows feature --- + // note: not yet implemented in free version if ( file_exists( RADIO_STATION_DIR . '/includes/import-export.php' ) ) { - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Show Data', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Shows', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); } add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settingscap, 'radio-station', 'radio_station_settings_page' ); @@ -305,22 +311,6 @@ function radio_station_role_editor() { echo " →."; } -// ---------------- -// Enqueue Semantic -// ---------------- -function radio_station_enqueue_semantic() { - - // --- enqueue semantic/ui styles --- - $suffix = '.min'; - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $suffix = ''; - } - $semantic_css_url = plugins_url( 'vendor/semantic/ui/dist/semantic' . $suffix . '.css', RADIO_STATION_FILE ); - wp_enqueue_style( 'semantic-ui-style', $semantic_css_url, array(), '2.4.1', 'all' ); - $semantic_js_url = plugins_url( 'vendor/semantic/ui/dist/semantic' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'semantic-ui-script', $semantic_js_url, array(), '2.4.1', true ); -} - // ------------------------------- // Display Import/Export Show Page // ------------------------------- @@ -329,10 +319,14 @@ function radio_station_import_export_show_page() { $importexport = RADIO_STATION_DIR . '/templates/import-export-shows.php'; if ( file_exists( $importexport ) ) { - radio_station_enqueue_semantic(); - // --- display the import/export page --- include $importexport; + + // --- enqueue Semenatic UI --- + // 2.3.2: conditional enqueue to be safe + if ( function_exists( 'radio_station_enqueue_semantic' ) ) { + radio_station_enqueue_semantic(); + } } } @@ -378,7 +372,7 @@ function radio_station_plugin_docs_page() { document.getElementById('doc-page-'+id).style.display = 'block'; if (hash != '') { anchor = document.getElementById(hash); - atop = anchor.offsetTop; /* do not use 'top' */ + atop = anchor.offsetTop; /* do not use 'top'! */ window.scrollTo(0, (atop-20)); } }"; diff --git a/radio-station.php b/radio-station.php index 138f187..576fbda 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.1.9 +Version: 2.3.1.10 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -355,6 +355,21 @@ 'section' => 'schedule', ), + // --- Schedule Clock Display --- + /* 'schedule_clock' => array( + 'type' => 'select', + 'label' => __( 'Schedule Clock?', 'radio-station' ), + 'default' => 'clock', + 'options' => array( + '' => __( 'None', 'radio-station' ), + 'clock' => __( 'Clock', 'radio-station' ), + 'timezone' => __( 'Timezone', 'radio-station' ), + ), + 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), */ + // --- [Pro] Schedule Switcher --- 'schedule_switcher' => array( 'type' => 'checkbox', @@ -691,7 +706,7 @@ 'type' => 'checkbox', 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), 'default' => 'yes', - 'value' => 'yes', + 'value' => '', 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), 'tab' => 'widgets', 'section' => 'loading', @@ -939,6 +954,14 @@ function radio_station_enqueue_scripts() { // --- enqueue plugin script --- // 2.3.0: added jquery dependency for inline script fragments radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); + + // -- enqueue javascript timezone script --- + // $suffix = '.min'; + // if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + // $suffix = ''; + // } + // $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); + // wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); } // --------------------- diff --git a/readme.txt b/readme.txt index 3f74eb4..6f4efe2 100644 --- a/readme.txt +++ b/readme.txt @@ -207,6 +207,8 @@ You may translate the plugin into another language. Please visit our [WordPress * Added: AJAX save of show shifts and playlist tracks * Added: post type editing metabox position filtering * Added: more display attributes to Master Schedule shortcode +* Added: time format filters for time output displays +* Added: javascript user timezone display on Master Schedule = 2.3.1 = * Update: Plugin Loader (1.1.1) with Freemius first path fix diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php index 96ae2eb..42d2cc4 100644 --- a/templates/master-schedule-div.php +++ b/templates/master-schedule-div.php @@ -3,6 +3,28 @@ * Template for master schedule shortcode div style. */ +$now = radio_station_get_now(); +$date = radio_station_get_time( 'date', $now ); + +// --- set shift time formats --- +// 2.3.2: set time formats early +if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; +} else { + $start_data_format = $end_data_format = 'g:i a'; +} +$start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'schedule-div', $atts ); +$end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'schedule-div', $atts ); + +// --- get schedule days and dates --- +// 2.3.2: allow for start day attibute +if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { + $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); +} else { + $weekdays = radio_station_get_schedule_weekdays(); +} +$weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + // --- filter show avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'div' ); @@ -10,7 +32,6 @@ $output .= ' + '; + echo "

    {$api->name}

    "; + echo "
    \n"; + + foreach ( (array) $api->sections as $section_name => $content ) { + if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { + continue; + } + + if ( isset( $plugins_section_titles[ $section_name ] ) ) { + $title = $plugins_section_titles[ $section_name ]; + } else { + $title = ucwords( str_replace( '_', ' ', $section_name ) ); + } + + $class = ( $section_name === $section ) ? ' class="current"' : ''; + $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); + $href = esc_url( $href ); + $san_section = esc_attr( $section_name ); + echo "\t$title\n"; + } + + echo "
    \n"; + + ?> +
    +
    + is_paid ) : ?> + plans ) ) : ?> +
    + plans as $plan ) : ?> + pricing ) ) { + continue; + } + + /** + * @var FS_Plugin_Plan $plan + */ + ?> + pricing[0] ?> + is_multi_cycle() ?> +
    +

    slug ), $plan->title ) ) ?>

    + has_annual() ?> + has_monthly() ?> + +
    + + pricing[0]->annual_discount_percentage() : 0 ?> + 0 ) : ?> + slug ), $annual_discount . '%' ) ?> + +
      +
    + get_actions_dropdown( $api, $plan ) ?> +
    + has_trial() ) : ?> + get_trial_period( $plan ) ?> +
      +
    • + slug ), $trial_period ) ) ?> +
    • +
    • + slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?> +
    • +
    + +
    +
    +
    + + + +
    +

    slug ) ?>

    +
      + version ) ) { ?> +
    • + slug ); ?> + : version; ?>
    • + author ) ) { + ?> +
    • + slug ); ?> + : author, '_blank' ); ?> +
    • + last_updated ) ) { + ?> +
    • slug ); ?> + : + slug ), + human_time_diff( strtotime( $api->last_updated ) ) + ) ) ?> +
    • + requires ) ) { + ?> +
    • + slug ) ?> + : slug ), $api->requires ) ) ?> +
    • + tested ) ) { + ?> +
    • + slug ); ?> + : tested; ?> +
    • + downloaded ) ) { + ?> +
    • + slug ) ?> + : downloaded ) ? + /* translators: %s: 1 or One (Number of times downloaded) */ + fs_text_inline( '%s time', 'x-time', $api->slug ) : + /* translators: %s: Number of times downloaded */ + fs_text_inline( '%s times', 'x-times', $api->slug ) + ), + number_format_i18n( $api->downloaded ) + ) ); ?> +
    • + slug ) && true == $api->is_wp_org_compliant ) { + ?> +
    • slug ) ?> + » +
    • + homepage ) ) { + ?> +
    • slug ) ?> + » +
    • + donate_link ) && empty( $api->contributors ) ) { + ?> +
    • slug ) ?> + » +
    • + +
    +
    + rating ) ) { ?> +

    slug ); ?>

    + $api->rating, + 'type' => 'percent', + 'number' => $api->num_ratings + ) ); ?> + (slug ), + sprintf( + ( ( 1 == $api->num_ratings ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s rating', 'x-rating', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) + ), + number_format_i18n( $api->num_ratings ) + ) ) ) ?>) + + ratings ) && array_sum( (array) $api->ratings ) > 0 ) { + foreach ( $api->ratings as $key => $ratecount ) { + // Avoid div-by-zero. + $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; + $stars_label = sprintf( + ( ( 1 == $key ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s star', 'x-star', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s stars', 'x-stars', $api->slug ) + ), + number_format_i18n( $key ) + ); + ?> +
    + + + + + +
    + contributors ) ) { + ?> +

    slug ); ?>

    +
      + contributors as $contrib_username => $contrib_profile ) { + if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { + continue; + } + if ( empty( $contrib_username ) ) { + $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); + } + $contrib_username = sanitize_user( $contrib_username ); + if ( empty( $contrib_profile ) ) { + echo "
    • {$contrib_username}
    • "; + } else { + echo "
    • {$contrib_username}
    • "; + } + } + ?> +
    + donate_link ) ) { ?> + slug ) ?> + » + + +
    +
    + tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; + } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; + } + + foreach ( (array) $api->sections as $section_name => $content ) { + $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); + $content = links_add_target( $content, '_blank' ); + + $san_section = esc_attr( $section_name ); + + $display = ( $section_name === $section ) ? 'block' : 'none'; + + if ( 'description' === $section_name && + ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || + ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) + ) { + $missing_notice = array( + 'type' => 'error', + 'id' => md5( microtime() ), + 'message' => $api->is_paid ? + fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : + fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), + ); + fs_require_template( 'admin-notice.php', $missing_notice ); + } + echo "\t
    \n"; + echo $content; + echo "\t
    \n"; + } + echo "
    \n"; + echo "
    \n"; + echo "\n"; // #plugin-information-scrollable + echo "\n"; + ?> + + 'You are just one step away - %s', + * + * We can use the filter: + * fs_override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ), '{plugin_slug}' ); + * + * Or with the Freemius instance: + * + * my_freemius->override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ) ); + */ + global $fs_text; + + $fs_text = array( + 'account' => _fs_text( 'Account' ), + 'addon' => _fs_text( 'Add-On' ), + 'contact-us' => _fs_text( 'Contact Us' ), + 'contact-support' => _fs_text( 'Contact Support' ), + 'change-ownership' => _fs_text( 'Change Ownership' ), + 'support' => _fs_text( 'Support' ), + 'support-forum' => _fs_text( 'Support Forum' ), + 'add-ons' => _fs_text( 'Add-Ons' ), + 'upgrade' => _fs_x( 'Upgrade', 'verb' ), + 'awesome' => _fs_text( 'Awesome' ), + 'pricing' => _fs_x( 'Pricing', 'noun' ), + 'price' => _fs_x( 'Price', 'noun' ), + 'unlimited-updates' => _fs_text( 'Unlimited Updates' ), + 'downgrade' => _fs_x( 'Downgrade', 'verb' ), + 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ), + 'cancel-trial' => _fs_text( 'Cancel Trial' ), + 'free-trial' => _fs_text( 'Free Trial' ), + 'start-free-x' => _fs_text( 'Start my free %s' ), + 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ), + 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ), + 'details' => _fs_text( 'Details' ), + 'account-details' => _fs_text( 'Account Details' ), + 'delete' => _fs_x( 'Delete', 'verb' ), + 'show' => _fs_x( 'Show', 'verb' ), + 'hide' => _fs_x( 'Hide', 'verb' ), + 'edit' => _fs_x( 'Edit', 'verb' ), + 'update' => _fs_x( 'Update', 'verb' ), + 'date' => _fs_text( 'Date' ), + 'amount' => _fs_text( 'Amount' ), + 'invoice' => _fs_text( 'Invoice' ), + 'billing' => _fs_text( 'Billing' ), + 'payments' => _fs_text( 'Payments' ), + 'delete-account' => _fs_text( 'Delete Account' ), + 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ), + 'plan' => _fs_x( 'Plan', 'as product pricing plan' ), + 'change-plan' => _fs_text( 'Change Plan' ), + 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ), + 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ), + 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ), + 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ), + 'new' => _fs_text( 'New' ), + 'free' => _fs_text( 'Free' ), + 'trial' => _fs_x( 'Trial', 'as trial plan' ), + 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ), + 'purchase' => _fs_x( 'Purchase', 'verb' ), + 'purchase-license' => _fs_text( 'Purchase License' ), + 'buy' => _fs_x( 'Buy', 'verb' ), + 'buy-license' => _fs_text( 'Buy License' ), + 'license-single-site' => _fs_text( 'Single Site License' ), + 'license-unlimited' => _fs_text( 'Unlimited Licenses' ), + 'license-x-sites' => _fs_text( 'Up to %s Sites' ), + 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ), + 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ), + 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ), + 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ), + 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now', + '%s - plugin name. As complete "Jetpack" activation now' ), + 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ), + 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ), + 'error' => _fs_text( 'Error' ), + 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), + 'learn-more' => _fs_text( 'Learn more' ), + + #region Affiliation + 'affiliation' => _fs_text( 'Affiliation' ), + 'affiliate' => _fs_text( 'Affiliate' ), + 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ), + 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ), + 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ), + 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ), + 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ), + 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ), + 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ), + 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ), + 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ), + 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ), + 'program-summary' => _fs_text( 'Program Summary' ), + 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ), + 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ), + 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ), + 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ), + 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ), + 'become-an-affiliate' => _fs_text( 'Become an affiliate' ), + 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ), + 'full-name' => _fs_text( 'Full name' ), + 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ), + 'promotion-methods' => _fs_text( 'Promotion methods' ), + 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ), + 'mobile-apps' => _fs_text( 'Mobile apps' ), + 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ), + 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ), + 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ), + 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ), + 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ), + 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ), + 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ), + 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ), + 'add-another-domain' => _fs_text( 'Add another domain' ), + 'remove' => _fs_x( 'Remove', 'Remove domain' ), + 'email-address-is-required' => _fs_text( 'Email address is required.' ), + 'domain-is-required' => _fs_text( 'Domain is required.' ), + 'invalid-domain' => _fs_text( 'Invalid domain' ), + 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ), + 'processing' => _fs_text( 'Processing...' ), + 'non-expiring' => _fs_text( 'Non-expiring' ), + 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ), + #endregion Affiliation + + #region Account + 'expiration' => _fs_x( 'Expiration', 'as expiration date' ), + 'license' => _fs_x( 'License', 'as software license' ), + 'not-verified' => _fs_text( 'not verified' ), + 'verify-email' => _fs_text( 'Verify Email' ), + 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ), + 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ), + 'no-expiration' => _fs_text( 'No expiration' ), + 'expired' => _fs_text( 'Expired' ), + 'cancelled' => _fs_text( 'Cancelled' ), + 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ), + 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ), + /* translators: %s: Version number (e.g. 4.6 or higher) */ + 'x-or-higher' => _fs_text( '%s or higher' ), + 'version' => _fs_x( 'Version', 'as plugin version' ), + 'name' => _fs_text( 'Name' ), + 'email' => _fs_text( 'Email' ), + 'email-address' => _fs_text( 'Email address' ), + 'verified' => _fs_text( 'Verified' ), + 'module' => _fs_text( 'Module' ), + 'module-type' => _fs_text( 'Module Type' ), + 'plugin' => _fs_text( 'Plugin' ), + 'plugins' => _fs_text( 'Plugins' ), + 'theme' => _fs_text( 'Theme' ), + 'themes' => _fs_text( 'Themes' ), + 'path' => _fs_x( 'Path', 'as file/folder path' ), + 'title' => _fs_text( 'Title' ), + 'free-version' => _fs_text( 'Free version' ), + 'premium-version' => _fs_text( 'Premium version' ), + 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ), + 'id' => _fs_text( 'ID' ), + 'users' => _fs_text( 'Users' ), + 'module-installs' => _fs_text( '%s Installs' ), + 'sites' => _fs_x( 'Sites', 'like websites' ), + 'user-id' => _fs_text( 'User ID' ), + 'site-id' => _fs_text( 'Site ID' ), + 'public-key' => _fs_text( 'Public Key' ), + 'secret-key' => _fs_text( 'Secret Key' ), + 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ), + 'no-id' => _fs_text( 'No ID' ), + 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ), + 'sync' => _fs_x( 'Sync', 'as synchronize' ), + 'activate-license' => _fs_text( 'Activate License' ), + 'activate-free-version' => _fs_text( 'Activate Free Version' ), + 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ), + 'activating-license' => _fs_text( 'Activating license...' ), + 'change-license' => _fs_text( 'Change License' ), + 'update-license' => _fs_text( 'Update License' ), + 'deactivate-license' => _fs_text( 'Deactivate License' ), + 'activate' => _fs_text( 'Activate' ), + 'deactivate' => _fs_text( 'Deactivate' ), + 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ), + 'skip-and-x' => _fs_text( 'Skip & %s' ), + 'no-deactivate' => _fs_text( 'No - just deactivate' ), + 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ), + 'active' => _fs_x( 'Active', 'active mode' ), + 'is-active' => _fs_x( 'Is Active', 'is active mode?' ), + 'install-now' => _fs_text( 'Install Now' ), + 'install-update-now' => _fs_text( 'Install Update Now' ), + 'more-information-about-x' => _fs_text( 'More information about %s' ), + 'localhost' => _fs_text( 'Localhost' ), + 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ), + 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ), + 'last-license' => _fs_text( 'Last license' ), + 'what-is-your-x' => _fs_text( 'What is your %s?' ), + 'activate-this-addon' => _fs_text( 'Activate this add-on' ), + 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ), + 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ), + 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ), + 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ), + 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ), + 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ), + 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ), + 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ), + #endregion Account + + 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ), + 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ), + #region Plugin Deactivation + 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ), + 'quick-feedback' => _fs_text( 'Quick feedback' ), + 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ), + 'deactivating' => _fs_text( 'deactivating' ), + 'deactivation' => _fs_text( 'Deactivation' ), + 'theme-switch' => _fs_text( 'Theme Switch' ), + 'switching' => _fs_text( 'switching' ), + 'switch' => _fs_text( 'Switch' ), + 'activate-x' => _fs_text( 'Activate %s' ), + 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ), + 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ), + 'cancel' => _fs_text( 'Cancel' ), + 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ), + 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ), + 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ), + 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ), + 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ), + 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ), + 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ), + 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ), + 'reason-other' => _fs_x( 'Other', + 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ), + 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ), + 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ), + 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ), + 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ), + 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ), + 'reason-not-working' => _fs_text( 'The %s is not working' ), + 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ), + 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ), + 'placeholder-feature' => _fs_text( 'What feature?' ), + 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ), + 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ), + 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ), + 'reason-didnt-work' => _fs_text( "The %s didn't work" ), + 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ), + 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ), + #endregion Plugin Deactivation + + #region Connect + 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ), + 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ), + 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ), + 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ), + 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ), + 'complete-the-install' => _fs_text( 'complete the install' ), + 'start-the-trial' => _fs_text( 'start the trial' ), + 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ), + 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ), + 'what-permissions' => _fs_text( 'What permissions are being granted?' ), + 'permissions-profile' => _fs_text( 'Your Profile Overview' ), + 'permissions-profile_desc' => _fs_text( 'Name and email address' ), + 'permissions-site' => _fs_text( 'Your Site Overview' ), + 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ), + 'permissions-events' => _fs_text( 'Current %s Events' ), + 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ), + 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ), + 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ), + 'permissions-admin-notices' => _fs_text( 'Admin Notices' ), + 'permissions-newsletter' => _fs_text( 'Newsletter' ), + 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ), + 'privacy-policy' => _fs_text( 'Privacy Policy' ), + 'tos' => _fs_text( 'Terms of Service' ), + 'activating' => _fs_x( 'Activating', 'as activating plugin' ), + 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ), + 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ), + 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ), + 'skip' => _fs_x( 'Skip', 'verb' ), + 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ), + 'resend-activation-email' => _fs_text( 'Re-send activation email' ), + 'license-key' => _fs_text( 'License key' ), + 'send-license-key' => _fs_text( 'Send License Key' ), + 'sending-license-key' => _fs_text( 'Sending license key' ), + 'have-license-key' => _fs_text( 'Have a license key?' ), + 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ), + 'cant-find-license-key' => _fs_text( "Can't find your license key?" ), + 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ), + 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ), + 'opt-in' => _fs_text( 'Opt In' ), + 'opt-out' => _fs_text( 'Opt Out' ), + 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ), + 'opting-out' => _fs_text( 'Opting out...' ), + 'opting-in' => _fs_text( 'Opting in...' ), + 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ), + 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ), + 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ), + 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ), + 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ), + 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ), + 'continue' => _fs_text( 'Continue' ), + 'allow' => _fs_text( 'allow' ), + 'delegate' => _fs_text( 'delegate' ), + #endregion Connect + + #region Screenshots + 'screenshots' => _fs_text( 'Screenshots' ), + 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ), + #endregion Screenshots + + #region Debug + 'freemius-debug' => _fs_text( 'Freemius Debug' ), + 'on' => _fs_x( 'On', 'as turned on' ), + 'off' => _fs_x( 'Off', 'as turned off' ), + 'debugging' => _fs_x( 'Debugging', 'as code debugging' ), + 'freemius-state' => _fs_text( 'Freemius State' ), + 'connected' => _fs_x( 'Connected', 'as connection was successful' ), + 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ), + 'api' => _fs_x( 'API', 'as application program interface' ), + 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ), + 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ), + 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ), + 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ), + 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ), + 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ), + 'actions' => _fs_text( 'Actions' ), + 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ), + 'start-fresh' => _fs_text( 'Start Fresh' ), + 'clear-api-cache' => _fs_text( 'Clear API Cache' ), + 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ), + 'scheduled-crons' => _fs_text( 'Scheduled Crons' ), + 'cron-type' => _fs_text( 'Cron Type' ), + 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ), + 'module-licenses' => _fs_text( '%s Licenses' ), + 'debug-log' => _fs_text( 'Debug Log' ), + 'all' => _fs_text( 'All' ), + 'file' => _fs_text( 'File' ), + 'function' => _fs_text( 'Function' ), + 'process-id' => _fs_text( 'Process ID' ), + 'logger' => _fs_text( 'Logger' ), + 'message' => _fs_text( 'Message' ), + 'download' => _fs_text( 'Download' ), + 'filter' => _fs_text( 'Filter' ), + 'type' => _fs_text( 'Type' ), + 'all-types' => _fs_text( 'All Types' ), + 'all-requests' => _fs_text( 'All Requests' ), + #endregion Debug + + #region Expressions + 'congrats' => _fs_x( 'Congrats', 'as congratulations' ), + 'oops' => _fs_x( 'Oops', 'exclamation' ), + 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ), + 'woot' => _fs_x( 'W00t', + '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ), + 'right-on' => _fs_x( 'Right on', 'a positive response' ), + 'hmm' => _fs_x( 'Hmm', + 'something somebody says when they are thinking about what you have just said. ' ), + 'ok' => _fs_text( 'O.K' ), + 'hey' => _fs_x( 'Hey', 'exclamation' ), + 'heads-up' => _fs_x( 'Heads up', + 'advance notice of something that will need attention.' ), + #endregion Expressions + + #region Admin Notices + 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ), + 'you-are-good' => _fs_text( 'You are all good!' ), + 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ), + 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ), + 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ), + 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ), + 'x-updated' => _fs_text( 'You have successfully updated your %s.' ), + 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ), + 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ), + 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ), + 'no-cc-required' => _fs_text( 'No credit card required' ), + 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ), + 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ), + 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ), + 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ), + 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ), + 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ), + 'download-latest-version' => _fs_text( 'Download the latest version' ), + 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ), + 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ), + 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ), + 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.', + '%s - product name, e.g. Facebook add-on was successfully...' ), + 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ), + 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ), + 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ), + 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ), + 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ), + 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ), + 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ), + 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ), + 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ), + 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ), + 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ), + 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ), + 'server-error-message' => _fs_text( 'Error received from the server:' ), + 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ), + 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ), + 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ), + 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ), + 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ), + 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ), + 'please-download-x' => _fs_text( 'Please download %s.' ), + 'latest-x-version' => _fs_x( 'the latest %s version here', + '%s - plan name, as the latest professional version here' ), + 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ), + 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ), + 'starting-trial' => _fs_text( 'Starting trial' ), + 'please-wait' => _fs_text( 'Please wait' ), + 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ), + 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ), + 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ), + 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ), + 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ), + 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ), + 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ), + 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ), + 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ), + 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ), + 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ), + 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ), + 'contact-us-here' => _fs_text( 'Please contact us here' ), + 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ), + #endregion Admin Notices + #region Connectivity Issues + 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ), + 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ), + 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ), + 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ), + 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ), + 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.', + 'as pluginX requires an access to our API' ), + 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ), + 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ), + 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ), + 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ), + 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ), + 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ), + 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ), + 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ), + 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ), + 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ), + 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ), + 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ), + 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ), + 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ), + 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ), + 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', + '%1$s - plugin title, %2$s - API domain' ), + 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ), + #endregion Connectivity Issues + #region Change Owner + 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ), + 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ), + 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ), + #endregion Change Owner + 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.', + 'addonX cannot run without pluginY' ), + 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ), + 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.', + 'pluginX activation was successfully...' ), + 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ), + 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ), + 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ), + #-------------------------------------------------------------------------------- + #region Add-On Licensing + #-------------------------------------------------------------------------------- + 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ), + 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ), + #endregion + #-------------------------------------------------------------------------------- + #region Billing Cycles + #-------------------------------------------------------------------------------- + 'monthly' => _fs_x( 'Monthly', 'as every month' ), + 'mo' => _fs_x( 'mo', 'as monthly period' ), + 'annual' => _fs_x( 'Annual', 'as once a year' ), + 'annually' => _fs_x( 'Annually', 'as once a year' ), + 'once' => _fs_x( 'Once', 'as once a year' ), + 'year' => _fs_x( 'year', 'as annual period' ), + 'lifetime' => _fs_text( 'Lifetime' ), + 'best' => _fs_x( 'Best', 'e.g. the best product' ), + 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ), + 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ), + #endregion Billing Cycles + 'view-details' => _fs_text( 'View details' ), + #-------------------------------------------------------------------------------- + #region Trial + #-------------------------------------------------------------------------------- + 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ), + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ), + /* translators: %s: Link to freemius.com */ + 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ), + + #endregion + #-------------------------------------------------------------------------------- + #region Billing Details + #-------------------------------------------------------------------------------- + 'business-name' => _fs_text( 'Business name' ), + 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ), + 'address-line-n' => _fs_text( 'Address Line %d' ), + 'country' => _fs_text( 'Country' ), + 'select-country' => _fs_text( 'Select Country' ), + 'city' => _fs_text( 'City' ), + 'town' => _fs_text( 'Town' ), + 'state' => _fs_text( 'State' ), + 'province' => _fs_text( 'Province' ), + 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ), + #endregion + #-------------------------------------------------------------------------------- + #region Module Installation + #-------------------------------------------------------------------------------- + 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ), + 'auto-installation' => _fs_text( 'Automatic Installation' ), + /* translators: %s: Number of seconds */ + 'x-sec' => _fs_text( '%s sec' ), + 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ), + 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ), + 'cancel-installation' => _fs_text( 'Cancel Installation' ), + 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ), + 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ), + 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ), + 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ), + 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ), + 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ), + 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ), + #endregion + + /* translators: %s: Page name */ + 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ), + 'pci-compliant' => _fs_text( 'PCI compliant' ), + 'view-paid-features' => _fs_text( 'View paid features' ), + ); + + /** + * Localization of the strings in the plugin/theme info dialog box. + * + * $fs_module_info_text should ONLY include strings that are not located in $fs_text. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + global $fs_module_info_text; + + $fs_module_info_text = array( + 'description' => _fs_x( 'Description', 'Plugin installer section title' ), + 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ), + 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ), + 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ), + 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ), + 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ), + /* translators: %s: 1 or One */ + 'x-star' => _fs_text( '%s star' ), + /* translators: %s: Number larger than 1 */ + 'x-stars' => _fs_text( '%s stars' ), + /* translators: %s: 1 or One */ + 'x-rating' => _fs_text( '%s rating' ), + /* translators: %s: Number larger than 1 */ + 'x-ratings' => _fs_text( '%s ratings' ), + /* translators: %s: 1 or One (Number of times downloaded) */ + 'x-time' => _fs_text( '%s time' ), + /* translators: %s: Number of times downloaded */ + 'x-times' => _fs_text( '%s times' ), + /* translators: %s: # of stars (e.g. 5 stars) */ + 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ), + 'last-updated:' => _fs_text( 'Last Updated' ), + 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ), + 'author:' => _fs_x( 'Author:', 'as the plugin author' ), + 'compatible-up-to:' => _fs_text( 'Compatible up to:' ), + 'downloaded:' => _fs_text( 'Downloaded:' ), + 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ), + 'plugin-homepage' => _fs_text( 'Plugin Homepage' ), + 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ), + 'average-rating' => _fs_text( 'Average Rating' ), + 'based-on-x' => _fs_text( 'based on %s' ), + 'warning:' => _fs_text( 'Warning:' ), + 'contributors' => _fs_text( 'Contributors' ), + 'plugin-install' => _fs_text( 'Plugin Install' ), + 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ), + 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ), + 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ), + 'latest-installed' => _fs_text( 'Latest Version Installed' ), + ); diff --git a/freemius/includes/index 2.php b/freemius/includes/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/index 2.php @@ -0,0 +1,3 @@ + + */ + private $_default_submenu_items; + /** + * @since 1.1.3 + * + * @var string + */ + private $_first_time_path; + /** + * @since 1.2.2 + * + * @var bool + */ + private $_menu_exists; + /** + * @since 2.0.0 + * + * @var bool + */ + private $_network_menu_exists; + + #endregion Properties + + /** + * @var FS_Logger + */ + protected $_logger; + + #region Singleton + + /** + * @var FS_Admin_Menu_Manager[] + */ + private static $_instances = array(); + + /** + * @param number $module_id + * @param string $module_type + * @param string $module_unique_affix + * + * @return FS_Admin_Menu_Manager + */ + static function instance( $module_id, $module_type, $module_unique_affix ) { + $key = 'm_' . $module_id; + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $module_id, $module_type, $module_unique_affix ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_module_id = $module_id; + $this->_module_type = $module_type; + $this->_module_unique_affix = $module_unique_affix; + } + + #endregion Singleton + + #region Helpers + + private function get_option( &$options, $key, $default = false ) { + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_bool_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; + } + + #endregion Helpers + + /** + * @param array $menu + * @param bool $is_addon + */ + function init( $menu, $is_addon = false ) { + $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); + $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); + + $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); + + $this->_default_submenu_items = array(); + // @deprecated + $this->_type = 'page'; + $this->_is_top_level = true; + $this->_is_override_exact = false; + $this->_parent_slug = false; + // @deprecated + $this->_parent_type = 'page'; + + if ( isset( $menu ) ) { + if ( ! $is_addon ) { + $this->_default_submenu_items = array( + 'contact' => $this->get_bool_option( $menu, 'contact', true ), + 'support' => $this->get_bool_option( $menu, 'support', true ), + 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), + 'account' => $this->get_bool_option( $menu, 'account', true ), + 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), + 'addons' => $this->get_bool_option( $menu, 'addons', true ), + ); + + // @deprecated + $this->_type = $this->get_option( $menu, 'type', 'page' ); + } + + $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); + + if ( isset( $menu['parent'] ) ) { + $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); + // @deprecated + $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); + + // If parent's slug is different, then it's NOT a top level menu item. + $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); + } else { + /** + * If no parent then top level if: + * - Has custom admin menu ('page') + * - CPT menu type ('cpt') + */ +// $this->_is_top_level = in_array( $this->_type, array( +// 'cpt', +// 'page' +// ) ); + } + + $first_path = $this->get_option( $menu, 'first-path', false ); + + if ( ! empty( $first_path ) && is_string( $first_path ) ) { + $this->_first_time_path = $first_path; + } + } + } + + /** + * Check if top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_top_level() { + return $this->_is_top_level; + } + + /** + * Check if the page should be override on exact URL match. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_override_exact() { + return $this->_is_override_exact; + } + + + /** + * Get the path of the page the user should be forwarded to after first activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool $is_network Since 2.4.5 + * + * @return string + */ + function get_first_time_path( $is_network = false ) { + if ( empty ( $this->_first_time_path ) ) { + return $this->_first_time_path; + } + + if ( $is_network ) { + return network_admin_url( $this->_first_time_path ); + } else { + return admin_url( $this->_first_time_path ); + } + } + + /** + * Check if plugin's menu item is part of a custom top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function has_custom_parent() { + return ! $this->_is_top_level && is_string( $this->_parent_slug ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function has_menu() { + return $this->_menu_exists; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function has_network_menu() { + return $this->_network_menu_exists; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $menu_slug + * + * @since 2.1.3 + */ + function set_slug_and_network_menu_exists_flag($menu_slug ) { + $this->_menu_slug = $menu_slug; + $this->_network_menu_exists = false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $id + * @param bool $default + * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. + * + * @return bool + */ + function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { + if ( ! $ignore_menu_existence && ! $this->has_menu() ) { + return false; + } + + return fs_apply_filter( + $this->_module_unique_affix, + 'is_submenu_visible', + $this->get_bool_option( $this->_default_submenu_items, $id, $default ), + $id + ); + } + + /** + * Calculates admin settings menu slug. + * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $page + * + * @return string + */ + function get_slug( $page = '' ) { + return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? + $this->_menu_slug : + $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_slug() { + return $this->_parent_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_type() { + return $this->_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function is_cpt() { + return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || + // Back compatibility. + 'cpt' === $this->_type + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_type() { + return $this->_parent_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_raw_slug() { + return $this->_menu_slug; + } + + /** + * Get plugin's original menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_original_menu_slug() { + if ( 'cpt' === $this->_type ) { + return add_query_arg( array( + 'post_type' => $this->_menu_slug + ), 'edit.php' ); + } + + if ( false === strpos( $this->_menu_slug, '.php?' ) ) { + return $this->_menu_slug; + } else { + return $this->_module_unique_affix; + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_top_level_menu_slug() { + return $this->has_custom_parent() ? + $this->get_parent_slug() : + $this->get_raw_slug(); + } + + /** + * Is user on plugin's admin activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + * + * @return bool + */ + function is_main_settings_page() { + if ( $this->_menu_exists && + ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) + ) { + /** + * Module has a settings menu and the context page is the main settings page, so assume it's in + * activation (doesn't really check if already opted-in/skipped or not). + * + * @since 1.2.2 + */ + return true; + } + + global $pagenow; + if ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() ) { + /** + * In activation only when show_optin query string param is given. + * + * @since 1.2.2 + */ + return fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ); + } + + return false; + } + + #region Submenu Override + + /** + * Override submenu's action. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $parent_slug + * @param string $menu_slug + * @param callable $function + * + * @return false|string If submenu exist, will return the hook name. + */ + function override_submenu_action( $parent_slug, $menu_slug, $function ) { + global $submenu; + + $menu_slug = plugin_basename( $menu_slug ); + $parent_slug = plugin_basename( $parent_slug ); + + if ( ! isset( $submenu[ $parent_slug ] ) ) { + // Parent menu not exist. + return false; + } + + $found_submenu_item = false; + foreach ( $submenu[ $parent_slug ] as $submenu_item ) { + if ( $menu_slug === $submenu_item[2] ) { + $found_submenu_item = $submenu_item; + break; + } + } + + if ( false === $found_submenu_item ) { + // Submenu item not found. + return false; + } + + // Remove current function. + $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); + remove_all_actions( $hookname ); + + // Attach new action. + add_action( $hookname, $function ); + + return $hookname; + } + + #endregion Submenu Override + + #region Top level menu Override + + /** + * Find plugin's admin dashboard main menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return string[]|false + */ + private function find_top_level_menu() { + global $menu; + + $position = - 1; + $found_menu = false; + + $menu_slug = $this->get_raw_slug(); + + $hook_name = get_plugin_page_hookname( $menu_slug, '' ); + foreach ( $menu as $pos => $m ) { + if ( $menu_slug === $m[2] ) { + $position = $pos; + $found_menu = $m; + break; + } + } + + if ( false === $found_menu ) { + return false; + } + + return array( + 'menu' => $found_menu, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Find plugin's admin dashboard main submenu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return array|false + */ + private function find_main_submenu() { + global $submenu; + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + + if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { + return false; + } + + $submenu_slug = $this->get_raw_slug(); + + $position = - 1; + $found_submenu = false; + + $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); + + foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { + if ( $submenu_slug === $sub[2] ) { + $position = $pos; + $found_submenu = $sub; + } + } + + if ( false === $found_submenu ) { + return false; + } + + return array( + 'menu' => $found_submenu, + 'parent_slug' => $top_level_menu_slug, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Remove all sub-menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool If submenu with plugin's menu slug was found. + */ + private function remove_all_submenu_items() { + global $submenu; + + $menu_slug = $this->get_raw_slug(); + + if ( ! isset( $submenu[ $menu_slug ] ) ) { + return false; + } + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $submenu_ref = &$submenu; + $submenu_ref[ $menu_slug ] = array(); + + return true; + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $remove_top_level_menu + * + * @return false|array[string]mixed + */ + function remove_menu_item( $remove_top_level_menu = false ) { + $this->_logger->entrance(); + + // Find main menu item. + $top_level_menu = $this->find_top_level_menu(); + + if ( false === $top_level_menu ) { + return false; + } + + // Remove it with its actions. + remove_all_actions( $top_level_menu['hook_name'] ); + + // Remove all submenu items. + $this->remove_all_submenu_items(); + + if ( $remove_top_level_menu ) { + global $menu; + unset( $menu[ $top_level_menu['position'] ] ); + } + + return $top_level_menu; + } + + /** + * Get module's main admin setting page URL. + * + * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function main_menu_url() { + $this->_logger->entrance(); + + if ( $this->_is_top_level ) { + $menu = $this->find_top_level_menu(); + } else { + $menu = $this->find_main_submenu(); + } + + $parent_slug = isset( $menu['parent_slug'] ) ? + $menu['parent_slug'] : + 'admin.php'; + + return admin_url( $parent_slug . '?page=' . $menu['menu'][2] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param callable $function + * + * @return false|array[string]mixed + */ + function override_menu_item( $function ) { + $found_menu = $this->remove_menu_item(); + + if ( false === $found_menu ) { + return false; + } + + if ( ! $this->is_top_level() || ! $this->is_cpt() ) { + $menu_slug = plugin_basename( $this->get_slug() ); + + $hookname = get_plugin_page_hookname( $menu_slug, '' ); + + // Override menu action. + add_action( $hookname, $function ); + } else { + global $menu; + + // Remove original CPT menu. + unset( $menu[ $found_menu['position'] ] ); + + // Create new top-level menu action. + $hookname = self::add_page( + $found_menu['menu'][3], + $found_menu['menu'][0], + 'manage_options', + $this->get_slug(), + $function, + $found_menu['menu'][6], + $found_menu['position'] + ); + } + + return $hookname; + } + + /** + * Adds a counter to the module's top level menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param int $counter + * @param string $class + */ + function add_counter_to_menu_item( $counter = 1, $class = '' ) { + global $menu, $submenu; + + $mask = '%s '; + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $menu_ref = &$menu; + $submenu_ref = &$submenu; + + if ( $this->_is_top_level ) { + // Find main menu item. + $found_menu = $this->find_top_level_menu(); + + if ( false !== $found_menu ) { + // Override menu label. + $menu_ref[ $found_menu['position'] ][0] = sprintf( + $mask, + $found_menu['menu'][0], + $class, + $counter + ); + } + } else { + $found_submenu = $this->find_main_submenu(); + + if ( false !== $found_submenu ) { + // Override menu label. + $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( + $mask, + $found_submenu['menu'][0], + $class, + $counter + ); + } + } + } + + #endregion Top level menu Override + + /** + * Add a top-level menu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_menu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * @param string $icon_url The URL to the icon to be used for this menu. + * * Pass a base64-encoded SVG using a data URI, which will be colored to + * match the color scheme. This should begin with + * 'data:image/svg+xml;base64,'. + * * Pass the name of a Dashicons helper class to use a font icon, + * e.g. 'dashicons-chart-pie'. + * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added + * via CSS. + * @param int $position The position in the menu order this one should appear. + * + * @return string The resulting page's hook_suffix. + */ + static function add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $fn = 'add_menu' . '_page'; + + return $fn( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * @param string $icon_url + * @param int|null $position + * + * @return string + */ + function add_page_and_update( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $this->_menu_slug = $menu_slug; + $this->_is_top_level = true; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add a submenu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_submenu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $parent_slug The slug name for the parent menu (or the file name of a standard + * WordPress admin page). + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * + * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability + * required. + */ + static function add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $fn = 'add_submenu' . '_page'; + + return $fn( $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + + /** + * Add sub page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $parent_slug + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * + * @return string + */ + function add_subpage_and_update( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $this->_menu_slug = $menu_slug; + $this->_parent_slug = $parent_slug; + $this->_is_top_level = false; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-admin-notice-manager 2.php b/freemius/includes/managers/class-fs-admin-notice-manager 2.php new file mode 100644 index 0000000..cc9d7f9 --- /dev/null +++ b/freemius/includes/managers/class-fs-admin-notice-manager 2.php @@ -0,0 +1,472 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Notice_Manager( + $id, + $title, + $module_unique_affix, + $is_network_and_blog_admins, + $network_level_or_blog_id + ); + } + + return self::$_instances[ $key ]; + } + + /** + * @param string $id + * @param string $title + * @param string $module_unique_affix + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and + * blog admin pages. + * @param bool|int $network_level_or_blog_id + */ + protected function __construct( + $id, + $title = '', + $module_unique_affix = '', + $is_network_and_blog_admins = false, + $network_level_or_blog_id = false + ) { + $this->_id = $id; + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_title = ! empty( $title ) ? $title : ''; + $this->_module_unique_affix = $module_unique_affix; + $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); + + if ( is_multisite() ) { + $this->_is_network_notices = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_notices = false; + } + + $is_network_admin = fs_is_network_admin(); + $is_blog_admin = fs_is_blog_admin(); + + if ( ( $this->_is_network_notices && $is_network_admin ) || + ( ! $this->_is_network_notices && $is_blog_admin ) || + ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) + ) { + if ( 0 < count( $this->_sticky_storage ) ) { + $ajax_action_suffix = str_replace( ':', '-', $this->_id ); + + // If there are sticky notices for the current slug, add a callback + // to the AJAX action that handles message dismiss. + add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( + &$this, + 'dismiss_notice_ajax_callback' + ) ); + + foreach ( $this->_sticky_storage as $msg ) { + // Add admin notice. + $this->add( + $msg['message'], + $msg['title'], + $msg['type'], + true, + $msg['id'], + false, + isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, + ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, + $is_network_and_blog_admins + ); + } + } + } + } + + /** + * Remove sticky message by ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + */ + function dismiss_notice_ajax_callback() { + $this->_sticky_storage->remove( $_POST['message_id'] ); + wp_die(); + } + + /** + * Rendered sticky message dismiss JavaScript. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + static function _add_sticky_dismiss_javascript() { + $params = array(); + fs_require_once_template( 'sticky-admin-notice-js.php', $params ); + } + + private static $_added_sticky_javascript = false; + + /** + * Hook to the admin_footer to add sticky message dismiss JavaScript handler. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + private static function has_sticky_messages() { + if ( ! self::$_added_sticky_javascript ) { + add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); + } + } + + /** + * Handle admin_notices by printing the admin messages stacked in the queue. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + */ + function _admin_notices_hook() { + if ( function_exists( 'current_user_can' ) && + ! current_user_can( 'manage_options' ) + ) { + // Only show messages to admins. + return; + } + + + $show_admin_notices = ( ! $this->is_gutenberg_page() ); + + foreach ( $this->_notices as $id => $msg ) { + if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { + if ( get_current_user_id() != $msg['wp_user_id'] ) { + continue; + } + } + + /** + * Added a filter to control the visibility of admin notices. + * + * Usage example: + * + * /** + * * @param bool $show + * * @param array $msg { + * * @var string $message The actual message. + * * @var string $title An optional message title. + * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). + * * @var string $id The unique identifier of the message. + * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. + * * @var string $plugin The product's title. + * * @var string $wp_user_id An optional WP user ID that this admin notice is for. + * * } + * * + * * @return bool + * *\/ + * function my_custom_show_admin_notice( $show, $msg ) { + * if ('trial_promotion' != $msg['id']) { + * return false; + * } + * + * return $show; + * } + * + * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); + * + * @author Vova Feldman + * @since 2.2.0 + */ + $show_notice = call_user_func_array( 'fs_apply_filter', array( + $this->_module_unique_affix, + 'show_admin_notice', + $show_admin_notices, + $msg + ) ); + + if ( true !== $show_notice ) { + continue; + } + + fs_require_template( 'admin-notice.php', $msg ); + + if ( $msg['sticky'] ) { + self::has_sticky_messages(); + } + } + } + + /** + * Enqueue common stylesheet to style admin notice. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _enqueue_styles() { + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + } + + /** + * Check if the current page is the Gutenberg block editor. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.3 + * + * @return bool + */ + function is_gutenberg_page() { + if ( function_exists( 'is_gutenberg_page' ) && + is_gutenberg_page() + ) { + // The Gutenberg plugin is on. + return true; + } + + $current_screen = get_current_screen(); + + if ( method_exists( $current_screen, 'is_block_editor' ) && + $current_screen->is_block_editor() + ) { + // Gutenberg page on 5+. + return true; + } + + return false; + } + + /** + * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + * @param bool $store_if_sticky + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + * + * @uses add_action() + */ + function add( + $message, + $title = '', + $type = 'success', + $is_sticky = false, + $id = '', + $store_if_sticky = true, + $wp_user_id = null, + $plugin_title = null, + $is_network_and_blog_admins = false + ) { + $notices_type = $this->get_notices_type(); + + if ( empty( $this->_notices ) ) { + if ( ! $is_network_and_blog_admins ) { + add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); + } else { + add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); + add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); + } + + add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); + } + + if ( '' === $id ) { + $id = md5( $title . ' ' . $message . ' ' . $type ); + } + + $message_object = array( + 'message' => $message, + 'title' => $title, + 'type' => $type, + 'sticky' => $is_sticky, + 'id' => $id, + 'manager_id' => $this->_id, + 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), + 'wp_user_id' => $wp_user_id, + ); + + if ( $is_sticky && $store_if_sticky ) { + $this->_sticky_storage->{$id} = $message_object; + } + + $this->_notices[ $id ] = $message_object; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string|string[] $ids + */ + function remove_sticky( $ids ) { + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + foreach ( $ids as $id ) { + // Remove from sticky storage. + $this->_sticky_storage->remove( $id ); + + if ( isset( $this->_notices[ $id ] ) ) { + unset( $this->_notices[ $id ] ); + } + } + } + + /** + * Check if sticky message exists by id. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param $id + * + * @return bool + */ + function has_sticky( $id ) { + return isset( $this->_sticky_storage[ $id ] ); + } + + /** + * Adds sticky admin notification. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $message + * @param string $id Message ID + * @param string $title + * @param string $type + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + */ + function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false ) { + if ( ! empty( $this->_module_unique_affix ) ) { + $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); + $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); + } + + $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + } + + /** + * Clear all sticky messages. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + function clear_all_sticky() { + $this->_sticky_storage->clear_all(); + } + + #-------------------------------------------------------------------------------- + #region Helper Method + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_notices_type() { + return $this->_is_network_notices ? + 'network_admin_notices' : + 'admin_notices'; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-cache-manager 2.php b/freemius/includes/managers/class-fs-cache-manager 2.php new file mode 100644 index 0000000..c6cb282 --- /dev/null +++ b/freemius/includes/managers/class-fs-cache-manager 2.php @@ -0,0 +1,326 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_options = FS_Option_Manager::get_manager( $id, true, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param $id + * + * @return FS_Cache_Manager + */ + static function get_manager( $id ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_MANAGERS[ $id ] ) ) { + self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); + } + + return self::$_MANAGERS[ $id ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + function is_empty() { + $this->_logger->entrance(); + + return $this->_options->is_empty(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + function clear() { + $this->_logger->entrance(); + + $this->_options->clear( true ); + } + + /** + * Delete cache manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_options->delete(); + } + + /** + * Check if there's a cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * + * @return bool + */ + function has( $key ) { + $cache_entry = $this->_options->get_option( $key, false ); + + return ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ); + } + + /** + * Check if there's a valid cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param null|int $expiration Since 1.2.2.7 + * + * @return bool + */ + function has_valid( $key, $expiration = null ) { + $cache_entry = $this->_options->get_option( $key, false ); + + $is_valid = ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ); + + if ( $is_valid && + is_numeric( $expiration ) && + isset( $cache_entry->created ) && + is_numeric( $cache_entry->created ) && + $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME + ) { + /** + * Even if the cache is still valid, since we are checking for validity + * with an explicit expiration period, if the period has past, return + * `false` as if the cache is invalid. + * + * @since 1.2.2.7 + */ + $is_valid = false; + } + + return $is_valid; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get_valid( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $value + * @param int $expiration + * @param int $created Since 2.0.0 Cache creation date. + */ + function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = new stdClass(); + + $cache_entry->result = $value; + $cache_entry->created = $created; + $cache_entry->timestamp = $created + $expiration; + $this->_options->set_option( $key, $cache_entry, true ); + } + + /** + * Get cached record expiration, or false if not cached or expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param string $key + * + * @return bool|int + */ + function get_record_expiration( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->timestamp; + } + + return false; + } + + /** + * Purge cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + */ + function purge( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $this->_options->unset_option( $key, true ); + } + + /** + * Extend cached item caching period. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $key + * @param int $expiration + * + * @return bool + */ + function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( ! is_object( $cache_entry ) || + ! isset( $cache_entry->timestamp ) || + ! is_numeric( $cache_entry->timestamp ) + ) { + return false; + } + + $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); + + return true; + } + + /** + * Set cached item as expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $key + */ + function expire( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + // Set to expired. + $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; + $this->_options->set_option( $key, $cache_entry, true ); + } + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $this->_options->migrate_to_network(); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-gdpr-manager 2.php b/freemius/includes/managers/class-fs-gdpr-manager 2.php new file mode 100644 index 0000000..a64abb0 --- /dev/null +++ b/freemius/includes/managers/class-fs-gdpr-manager 2.php @@ -0,0 +1,202 @@ +_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); + $this->_wp_user_id = Freemius::get_current_wp_user_id(); + $this->_option_name = "u{$this->_wp_user_id}"; + $this->_data = $this->_storage->get_option( $this->_option_name, array() ); + $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); + + if ( ! is_array( $this->_data ) ) { + $this->_data = array(); + } + } + + /** + * Update a GDPR option for the current admin and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param string $name + * @param mixed $value + */ + private function update_option( $name, $value ) { + $this->_data[ $name ] = $value; + + $this->_storage->set_option( $this->_option_name, $this->_data, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @return bool|null + */ + public function is_required() { + return isset( $this->_data['required'] ) ? + $this->_data['required'] : + null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param bool $is_required + */ + public function store_is_required( $is_required ) { + $this->update_option( 'required', $is_required ); + } + + /** + * Checks if the GDPR opt-in sticky notice is currently shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function is_opt_in_notice_shown() { + return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + } + + /** + * Remove the GDPR opt-in sticky notice. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function remove_opt_in_notice() { + $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + + $this->disable_opt_in_notice(); + } + + /** + * Prevents the opt-in message from being added/shown. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + public function disable_opt_in_notice() { + $this->update_option( 'show_opt_in_notice', false ); + } + + /** + * Checks if a GDPR opt-in message needs to be shown to the current admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function should_show_opt_in_notice() { + return ( + ! isset( $this->_data['show_opt_in_notice'] ) || + true === $this->_data['show_opt_in_notice'] + ); + } + + /** + * Get the last time the GDPR opt-in notice was shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return false|int + */ + public function last_time_notice_was_shown() { + return isset( $this->_data['notice_shown_at'] ) ? + $this->_data['notice_shown_at'] : + false; + } + + /** + * Update the timestamp of the last time the GDPR opt-in message was shown to now. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function notice_was_just_shown() { + $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); + } + + /** + * @param string $message + * @param string|null $plugin_title + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { + $this->_notices->add_sticky( + $message, + "gdpr_optin_actions_{$this->_wp_user_id}", + '', + 'promotion', + true, + $this->_wp_user_id, + $plugin_title, + true + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-key-value-storage 2.php b/freemius/includes/managers/class-fs-key-value-storage 2.php new file mode 100644 index 0000000..713df6f --- /dev/null +++ b/freemius/includes/managers/class-fs-key-value-storage 2.php @@ -0,0 +1,392 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_id = $id; + $this->_secondary_id = $secondary_id; + + if ( is_multisite() ) { + $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_multisite_storage = false; + } + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( + WP_FS__ACCOUNTS_OPTION_NAME, + true, + $this->_is_multisite_storage ? + true : + ( $this->_blog_id > 0 ? $this->_blog_id : false ) + ); + } + + protected function get_all_data() { + return $this->get_option_manager()->get_option( $this->_id, array() ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function load() { + $all_plugins_data = $this->get_all_data(); + $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? + $all_plugins_data[ $this->_secondary_id ] : + array(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $value + * @param bool $flush + */ + function store( $key, $value, $flush = true ) { + if ( $this->_logger->is_on() ) { + $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); + } + + if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { + // No need to store data if the value wasn't changed. + return; + } + + $all_data = $this->get_all_data(); + + $this->_data[ $key ] = $value; + + $all_data[ $this->_secondary_id ] = $this->_data; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, $flush ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function save() { + $this->get_option_manager()->store(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $store + * @param string[] $exceptions Set of keys to keep and not clear. + */ + function clear_all( $store = true, $exceptions = array() ) { + $new_data = array(); + foreach ( $exceptions as $key ) { + if ( isset( $this->_data[ $key ] ) ) { + $new_data[ $key ] = $this->_data[ $key ]; + } + } + + $this->_data = $new_data; + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * Delete key-value storage. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_data = array(); + + $all_data = $this->get_all_data(); + unset( $all_data[ $this->_secondary_id ] ); + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param bool $store + */ + function remove( $key, $store = true ) { + if ( ! array_key_exists( $key, $this->_data ) ) { + return; + } + + unset( $this->_data[ $key ] ); + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $default + * + * @return bool|\FS_Plugin + */ + function get( $key, $default = false ) { + return array_key_exists( $key, $this->_data ) ? + $this->_data[ $key ] : + $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_secondary_id() { + return $this->_secondary_id; + } + + + /* ArrayAccess + Magic Access (better for refactoring) + -----------------------------------------------------------------------------------*/ + function __set( $k, $v ) { + $this->store( $k, $v ); + } + + function __isset( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function __unset( $k ) { + $this->remove( $k ); + } + + function __get( $k ) { + return $this->get( $k, null ); + } + + function offsetSet( $k, $v ) { + if ( is_null( $k ) ) { + throw new Exception( 'Can\'t append value to request params.' ); + } else { + $this->{$k} = $v; + } + } + + function offsetExists( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function offsetUnset( $k ) { + unset( $this->$k ); + } + + function offsetGet( $k ) { + return $this->get( $k, null ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the current element + * + * @link http://php.net/manual/en/iterator.current.php + * @return mixed Can return any type. + */ + public function current() { + return current( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Move forward to next element + * + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + */ + public function next() { + next( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the key of the current element + * + * @link http://php.net/manual/en/iterator.key.php + * @return mixed scalar on success, or null on failure. + */ + public function key() { + return key( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Checks if current position is valid + * + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() { + $key = key( $this->_data ); + + return ( $key !== null && $key !== false ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Rewind the Iterator to the first element + * + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + */ + public function rewind() { + reset( $this->_data ); + } + + /** + * (PHP 5 >= 5.1.0)
    + * Count elements of an object + * + * @link http://php.net/manual/en/countable.count.php + * @return int The custom count as an integer. + *

    + *

    + * The return value is cast to an integer. + */ + public function count() { + return count( $this->_data ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-license-manager 2.php b/freemius/includes/managers/class-fs-license-manager 2.php new file mode 100644 index 0000000..891ecd8 --- /dev/null +++ b/freemius/includes/managers/class-fs-license-manager 2.php @@ -0,0 +1,104 @@ +get_slug() ); +// +// if ( ! isset( self::$_instances[ $slug ] ) ) { +// self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); +// } +// +// return self::$_instances[ $slug ]; +// } +// +//// private function __construct($slug) { +//// parent::__construct($slug); +//// } +// +// function entry_id() { +// return 'licenses'; +// } +// +// function sync( $id ) { +// +// } +// +// /** +// * @author Vova Feldman (@svovaf) +// * @since 1.0.5 +// * @uses FS_Api +// * +// * @param number|bool $plugin_id +// * +// * @return FS_Plugin_License[]|stdClass Licenses or API error. +// */ +// function api_get_user_plugin_licenses( $plugin_id = false ) { +// $api = $this->_fs->get_api_user_scope(); +// +// if ( ! is_numeric( $plugin_id ) ) { +// $plugin_id = $this->_fs->get_id(); +// } +// +// $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); +// +// if ( ! isset( $result->error ) ) { +// for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { +// $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); +// } +// +// $result = $result->licenses; +// } +// +// return $result; +// } +// +// function api_get_many() { +// +// } +// +// function api_activate( $id ) { +// +// } +// +// function api_deactivate( $id ) { +// +// } + + /** + * @param FS_Plugin_License[] $licenses + * + * @return bool + */ + static function has_premium_license( $licenses ) { + if ( is_array( $licenses ) ) { + foreach ( $licenses as $license ) { + /** + * @var FS_Plugin_License $license + */ + if ( ! $license->is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-option-manager 2.php b/freemius/includes/managers/class-fs-option-manager 2.php new file mode 100644 index 0000000..4e5494c --- /dev/null +++ b/freemius/includes/managers/class-fs-option-manager 2.php @@ -0,0 +1,497 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_id = $id; + + if ( is_multisite() ) { + $this->_is_network_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_storage = false; + } + + if ( $load ) { + $this->load(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $id + * @param bool $load + * @param bool|int $network_level_or_blog_id Since 2.0.0 + * + * @return FS_Option_Manager + */ + static function get_manager( $id, $load = false, $network_level_or_blog_id = false ) { + $key = strtolower( $id ); + + if ( is_multisite() ) { + if ( true === $network_level_or_blog_id ) { + $key .= ':ms'; + } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_MANAGERS[ $key ] ) ) { + self::$_MANAGERS[ $key ] = new FS_Option_Manager( $id, $load, $network_level_or_blog_id ); + } // If load required but not yet loaded, load. + else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { + self::$_MANAGERS[ $key ]->load(); + } + + return self::$_MANAGERS[ $key ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param bool $flush + */ + function load( $flush = false ) { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $flush || ! isset( $this->_options ) ) { + if ( isset( $this->_options ) ) { + // Clear prev options. + $this->clear(); + } + + $cache_group = $this->get_cache_group(); + + if ( WP_FS__DEBUG_SDK ) { + + // Don't use cache layer in DEBUG mode. + $load_options = empty( $this->_options ); + + } else { + + $this->_options = wp_cache_get( + $option_name, + $cache_group + ); + + $load_options = ( false === $this->_options ); + } + + $cached = true; + + if ( $load_options ) { + if ( $this->_is_network_storage ) { + $this->_options = get_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + $this->_options = get_blog_option( $this->_blog_id, $option_name ); + } else { + $this->_options = get_option( $option_name ); + } + + if ( is_string( $this->_options ) ) { + $this->_options = json_decode( $this->_options ); + } + +// $this->_logger->info('get_option = ' . var_export($this->_options, true)); + + if ( false === $this->_options ) { + $this->clear(); + } + + $cached = false; + } + + if ( ! WP_FS__DEBUG_SDK && ! $cached ) { + // Set non encoded cache. + wp_cache_set( $option_name, $this->_options, $cache_group ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_loaded() { + return isset( $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_empty() { + return ( $this->is_loaded() && false === $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool $flush + */ + function clear( $flush = false ) { + $this->_logger->entrance(); + + $this->_options = array(); + + if ( $flush ) { + $this->store(); + } + } + + /** + * Delete options manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $option_name = $this->get_option_manager_name(); + + if ( $this->_is_network_storage ) { + delete_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + delete_blog_option( $this->_blog_id, $option_name ); + } else { + delete_option( $option_name ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $option + * + * @return bool + */ + function has_option( $option ) { + return array_key_exists( $option, $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $default + * + * @return mixed + */ + function get_option( $option, $default = null ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->load(); + } + + if ( is_array( $this->_options ) ) { + $value = isset( $this->_options[ $option ] ) ? + $this->_options[ $option ] : + $default; + } else if ( is_object( $this->_options ) ) { + $value = isset( $this->_options->{$option} ) ? + $this->_options->{$option} : + $default; + } else { + $value = $default; + } + + /** + * If it's an object, return a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = $options->get_option( 'object1' ); + * $object1->x = 123; + * + * $object2 = $options->get_option( 'object2' ); + * $object2->y = 'dummy'; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't return a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' which is quite not + * an expected behaviour. + * + * @author Vova Feldman + */ + return is_object( $value ) ? clone $value : $value; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $value + * @param bool $flush + */ + function set_option( $option, $value, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->clear(); + } + + /** + * If it's an object, store a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = new stdClass(); + * $object1->x = 123; + * + * $options->set_option( 'object1', $object1 ); + * + * $object1->x = 456; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't set the option as a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' ($object1->x = 456 instead of + * $object1->x = 123) which is quite not an expected behaviour. + * + * @author Vova Feldman + */ + $copy = is_object( $value ) ? clone $value : $value; + + if ( is_array( $this->_options ) ) { + $this->_options[ $option ] = $copy; + } else if ( is_object( $this->_options ) ) { + $this->_options->{$option} = $copy; + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Unset option. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param bool $flush + */ + function unset_option( $option, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( is_array( $this->_options ) ) { + if ( ! isset( $this->_options[ $option ] ) ) { + return; + } + + unset( $this->_options[ $option ] ); + + } else if ( is_object( $this->_options ) ) { + if ( ! isset( $this->_options->{$option} ) ) { + return; + } + + unset( $this->_options->{$option} ); + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Dump options to database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function store() { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $this->_logger->is_on() ) { + $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); + } + + // Update DB. + if ( $this->_is_network_storage ) { + update_site_option( $option_name, $this->_options ); + } else if ( $this->_blog_id > 0 ) { + update_blog_option( $this->_blog_id, $option_name, $this->_options ); + } else { + update_option( $option_name, $this->_options ); + } + + if ( ! WP_FS__DEBUG_SDK ) { + wp_cache_set( $option_name, $this->_options, $this->get_cache_group() ); + } + } + + /** + * Get options keys. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return string[] + */ + function get_options_keys() { + if ( is_array( $this->_options ) ) { + return array_keys( $this->_options ); + } else if ( is_object( $this->_options ) ) { + return array_keys( get_object_vars( $this->_options ) ); + } + + return array(); + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $site_options = FS_Option_Manager::get_manager($this->_id, true, false); + + $options = is_object( $site_options->_options ) ? + get_object_vars( $site_options->_options ) : + $site_options->_options; + + if ( ! empty( $options ) ) { + foreach ( $options as $key => $val ) { + $this->set_option( $key, $val, false ); + } + + $this->store(); + } + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @return string + */ + private function get_option_manager_name() { + return $this->_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_cache_group() { + $group = WP_FS__SLUG; + + if ( $this->_is_network_storage ) { + $group .= '_ms'; + } else if ( $this->_blog_id > 0 ) { + $group .= "_s{$this->_blog_id}"; + } + + return $group; + } + + #endregion + } diff --git a/freemius/includes/managers/class-fs-plan-manager 2.php b/freemius/includes/managers/class-fs-plan-manager 2.php new file mode 100644 index 0000000..639de43 --- /dev/null +++ b/freemius/includes/managers/class-fs-plan-manager 2.php @@ -0,0 +1,162 @@ +is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_paid_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return false; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( ! $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_free_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Find all plans that have trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return FS_Plugin_Plan[] + */ + function get_trial_plans( $plans ) { + $trial_plans = array(); + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + $trial_plans[] = $plans[ $i ]; + } + } + } + + return $trial_plans; + } + + /** + * Check if plugin has any trial plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_trial_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + return true; + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-plugin-manager 2.php b/freemius/includes/managers/class-fs-plugin-manager 2.php new file mode 100644 index 0000000..d8ea082 --- /dev/null +++ b/freemius/includes/managers/class-fs-plugin-manager 2.php @@ -0,0 +1,220 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_module_id = $module_id; + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string|bool $module_type "plugin", "theme", or "false" for all modules. + * + * @return array + */ + protected function get_all_modules( $module_type = false ) { + $option_manager = $this->get_option_manager(); + + if ( false !== $module_type ) { + return $option_manager->get_option( $module_type . 's', array() ); + } + + return array( + self::OPTION_NAME_PLUGINS => $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), + self::OPTION_NAME_THEMES => $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), + ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function load() { + $all_modules = $this->get_all_modules(); + + if ( ! is_numeric( $this->_module_id ) ) { + unset( $all_modules[ self::OPTION_NAME_THEMES ] ); + } + + foreach ( $all_modules as $modules ) { + /** + * @since 1.2.2 + * + * @var $modules FS_Plugin[] + */ + foreach ( $modules as $module ) { + $found_module = false; + + /** + * If module ID is not numeric, it must be a plugin's slug. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( ! is_numeric( $this->_module_id ) ) { + if ( $this->_module_id === $module->slug ) { + $this->_module_id = $module->id; + $found_module = true; + } + } else if ( $this->_module_id == $module->id ) { + $found_module = true; + } + + if ( $found_module ) { + $this->_module = $module; + break; + } + } + } + } + + /** + * Store plugin on local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|FS_Plugin $module + * @param bool $flush + * + * @return bool|\FS_Plugin + */ + function store( $module = false, $flush = true ) { + if ( false !== $module ) { + $this->_module = $module; + } + + $all_modules = $this->get_all_modules( $this->_module->type ); + $all_modules[ $this->_module->slug ] = $this->_module; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); + + return $this->_module; + } + + /** + * Update local plugin data if different. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param \FS_Plugin $plugin + * @param bool $store + * + * @return bool True if plugin was updated. + */ + function update( FS_Plugin $plugin, $store = true ) { + if ( ! ($this->_module instanceof FS_Plugin ) || + $this->_module->slug != $plugin->slug || + $this->_module->public_key != $plugin->public_key || + $this->_module->secret_key != $plugin->secret_key || + $this->_module->parent_plugin_id != $plugin->parent_plugin_id || + $this->_module->title != $plugin->title + ) { + $this->store( $plugin, $store ); + + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin $plugin + * @param bool $store + */ + function set( FS_Plugin $plugin, $store = false ) { + $this->_module = $plugin; + + if ( $store ) { + $this->store(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool|\FS_Plugin + */ + function get() { + return isset( $this->_module ) ? + $this->_module : + false; + } + + + } \ No newline at end of file diff --git a/freemius/includes/managers/index 2.php b/freemius/includes/managers/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/managers/index 2.php @@ -0,0 +1,3 @@ +_result = $result; + + $code = 0; + $message = 'Unknown error, please check GetResult().'; + $type = ''; + + if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { + if ( isset( $result['error']['code'] ) ) { + $code = $result['error']['code']; + } + if ( isset( $result['error']['message'] ) ) { + $message = $result['error']['message']; + } + if ( isset( $result['error']['type'] ) ) { + $type = $result['error']['type']; + } + } + + $this->_type = $type; + $this->_code = $code; + + parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); + } + + /** + * Return the associated result object returned by the API server. + * + * @return array The result from the API server + */ + public function getResult() { + return $this->_result; + } + + public function getStringCode() { + return $this->_code; + } + + public function getType() { + return $this->_type; + } + + /** + * To make debugging easier. + * + * @return string The string representation of the error + */ + public function __toString() { + $str = $this->getType() . ': '; + + if ( $this->code != 0 ) { + $str .= $this->getStringCode() . ': '; + } + + return $str . $this->getMessage(); + } + } + } \ No newline at end of file diff --git a/freemius/includes/sdk/Exceptions/InvalidArgumentException 2.php b/freemius/includes/sdk/Exceptions/InvalidArgumentException 2.php new file mode 100644 index 0000000..685752a --- /dev/null +++ b/freemius/includes/sdk/Exceptions/InvalidArgumentException 2.php @@ -0,0 +1,8 @@ +_id = $pID; + $this->_public = $pPublic; + $this->_secret = $pSecret; + $this->_scope = $pScope; + $this->_isSandbox = $pIsSandbox; + } + + public function IsSandbox() { + return $this->_isSandbox; + } + + function CanonizePath( $pPath ) { + $pPath = trim( $pPath, '/' ); + $query_pos = strpos( $pPath, '?' ); + $query = ''; + + if ( false !== $query_pos ) { + $query = substr( $pPath, $query_pos ); + $pPath = substr( $pPath, 0, $query_pos ); + } + + // Trim '.json' suffix. + $format_length = strlen( '.' . self::FORMAT ); + $start = $format_length * ( - 1 ); //negative + if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { + $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); + } + + switch ( $this->_scope ) { + case 'app': + $base = '/apps/' . $this->_id; + break; + case 'developer': + $base = '/developers/' . $this->_id; + break; + case 'user': + $base = '/users/' . $this->_id; + break; + case 'plugin': + $base = '/plugins/' . $this->_id; + break; + case 'install': + $base = '/installs/' . $this->_id; + break; + default: + throw new Freemius_Exception( 'Scope not implemented.' ); + } + + return '/v' . FS_API__VERSION . $base . + ( ! empty( $pPath ) ? '/' : '' ) . $pPath . + ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; + } + + abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); + + /** + * @param string $pPath + * @param string $pMethod + * @param array $pParams + * + * @return object[]|object|null + */ + private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + $pMethod = strtoupper( $pMethod ); + + try { + $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); + } + + /** + * Base64 decoding that does not need to be urldecode()-ed. + * + * Exactly the same as PHP base64 encode except it uses + * `-` instead of `+` + * `_` instead of `/` + * No padded = + * + * @param string $input Base64UrlEncoded() string + * + * @return string + */ + protected static function Base64UrlDecode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_decode'; + return $fn( strtr( $input, '-_', '+/' ) ); + } + + /** + * Base64 encoding that does not need to be urlencode()ed. + * + * Exactly the same as base64 encode except it uses + * `-` instead of `+ + * `_` instead of `/` + * + * @param string $input string + * + * @return string Base64 encoded string + */ + protected static function Base64UrlEncode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_encode'; + $str = strtr( $fn( $input ), '+/', '-_' ); + $str = str_replace( '=', '', $str ); + + return $str; + } + } diff --git a/freemius/includes/sdk/FreemiusWordPress 2.php b/freemius/includes/sdk/FreemiusWordPress 2.php new file mode 100644 index 0000000..34e1c2e --- /dev/null +++ b/freemius/includes/sdk/FreemiusWordPress 2.php @@ -0,0 +1,704 @@ + '7.37' ); + + if ( ! defined( 'FS_API__PROTOCOL' ) ) { + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); + } + + if ( ! defined( 'FS_API__LOGGER_ON' ) ) { + define( 'FS_API__LOGGER_ON', false ); + } + + if ( ! defined( 'FS_API__ADDRESS' ) ) { + define( 'FS_API__ADDRESS', '://api.freemius.com' ); + } + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); + } + + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + return; + } + + class Freemius_Api_WordPress extends Freemius_Api_Base { + private static $_logger = array(); + + /** + * @param string $pScope 'app', 'developer', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string|bool $pSecret Element's secret key. + * @param bool $pSandbox Whether or not to run API in sandbox mode. + */ + public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { + // If secret key not provided, use public key encryption. + if ( is_bool( $pSecret ) ) { + $pSecret = $pPublic; + } + + parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); + } + + public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { + $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); + + if ( ':' === $address[0] ) { + $address = self::$_protocol . $address; + } + + return $address . $pCanonizedPath; + } + + #---------------------------------------------------------------------------------- + #region Servers Clock Diff + #---------------------------------------------------------------------------------- + + /** + * @var int Clock diff in seconds between current server to API server. + */ + private static $_clock_diff = 0; + + /** + * Set clock diff for all API calls. + * + * @since 1.0.3 + * + * @param $pSeconds + */ + public static function SetClockDiff( $pSeconds ) { + self::$_clock_diff = $pSeconds; + } + + /** + * Find clock diff between current server to API server. + * + * @since 1.0.2 + * @return int Clock diff in seconds. + */ + public static function FindClockDiff() { + $time = time(); + $pong = self::Ping(); + + return ( $time - strtotime( $pong->timestamp ) ); + } + + #endregion + + /** + * @var string http or https + */ + private static $_protocol = FS_API__PROTOCOL; + + /** + * Set API connection protocol. + * + * @since 1.0.4 + */ + public static function SetHttp() { + self::$_protocol = 'http'; + } + + /** + * @since 1.0.4 + * + * @return bool + */ + public static function IsHttps() { + return ( 'https' === self::$_protocol ); + } + + /** + * Sign request with the following HTTP headers: + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @param string $pResourceUrl + * @param array $pWPRemoteArgs + * + * @return array + */ + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { + $auth = $this->GenerateAuthorizationParams( + $pResourceUrl, + $pWPRemoteArgs['method'], + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' + ); + + $pWPRemoteArgs['headers']['Date'] = $auth['date']; + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; + + if ( ! empty( $auth['content_md5'] ) ) { + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; + } + + return $pWPRemoteArgs; + } + + /** + * Generate Authorization request headers: + * + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @author Vova Feldman + * + * @param string $pResourceUrl + * @param string $pMethod + * @param string $pPostParams + * + * @return array + * @throws Freemius_Exception + */ + function GenerateAuthorizationParams( + $pResourceUrl, + $pMethod = 'GET', + $pPostParams = '' + ) { + $pMethod = strtoupper( $pMethod ); + + $eol = "\n"; + $content_md5 = ''; + $content_type = ''; + $now = ( time() - self::$_clock_diff ); + $date = date( 'r', $now ); + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) && ! empty( $pPostParams ) ) { + $content_md5 = md5( $pPostParams ); + $content_type = 'application/json'; + } + + $string_to_sign = implode( $eol, array( + $pMethod, + $content_md5, + $content_type, + $date, + $pResourceUrl + ) ); + + // If secret and public keys are identical, it means that + // the signature uses public key hash encoding. + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; + + $auth = array( + 'date' => $date, + 'authorization' => $auth_type . ' ' . $this->_id . ':' . + $this->_public . ':' . + self::Base64UrlEncode( hash_hmac( + 'sha256', $string_to_sign, $this->_secret + ) ) + ); + + if ( ! empty( $content_md5 ) ) { + $auth['content_md5'] = $content_md5; + } + + return $auth; + } + + /** + * Get API request URL signed via query string. + * + * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). + * + * @param string $pPath + * + * @throws Freemius_Exception + * + * @return string + */ + function GetSignedUrl( $pPath ) { + $resource = explode( '?', $this->CanonizePath( $pPath ) ); + $pResourceUrl = $resource[0]; + + $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); + + return Freemius_Api_WordPress::GetUrl( + $pResourceUrl . '?' . + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . + 'authorization=' . urlencode( $auth['authorization'] ) . + '&auth_date=' . urlencode( $auth['date'] ) + , $this->_isSandbox ); + } + + /** + * @author Vova Feldman + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { + $start = microtime( true ); + + $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); + + if ( FS_API__LOGGER_ON ) { + $end = microtime( true ); + + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); + $is_http_error = is_wp_error( $response ); + + self::$_logger[] = array( + 'id' => count( self::$_logger ), + 'start' => $start, + 'end' => $end, + 'total' => ( $end - $start ), + 'method' => $pWPRemoteArgs['method'], + 'path' => $pUrl, + 'body' => $has_body ? $pWPRemoteArgs['body'] : null, + 'result' => ! $is_http_error ? + $response['body'] : + json_encode( $response->get_error_messages() ), + 'code' => ! $is_http_error ? $response['response']['code'] : null, + 'backtrace' => debug_backtrace(), + ); + } + + return $response; + } + + /** + * @return array + */ + static function GetLogger() { + return self::$_logger; + } + + /** + * @param string $pCanonizedPath + * @param string $pMethod + * @param array $pParams + * @param null|array $pWPRemoteArgs + * @param bool $pIsSandbox + * @param null|callable $pBeforeExecutionFunction + * + * @return object[]|object|null + * + * @throws \Freemius_Exception + */ + private static function MakeStaticRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null, + $pIsSandbox = false, + $pBeforeExecutionFunction = null + ) { + // Connectivity errors simulation. + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { + self::ThrowCloudFlareDDoSException(); + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { + self::ThrowSquidAclException(); + } + + if ( empty( $pWPRemoteArgs ) ) { + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . + home_url(); + + $pWPRemoteArgs = array( + 'method' => strtoupper( $pMethod ), + 'connect_timeout' => 10, + 'timeout' => 60, + 'follow_redirects' => true, + 'redirection' => 5, + 'user-agent' => $user_agent, + 'blocking' => true, + ); + } + + if ( ! isset( $pWPRemoteArgs['headers'] ) || + ! is_array( $pWPRemoteArgs['headers'] ) + ) { + $pWPRemoteArgs['headers'] = array(); + } + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + if ( is_array( $pParams ) && 0 < count( $pParams ) ) { + $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; + $pWPRemoteArgs['body'] = json_encode( $pParams ); + } + } + + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); + + $resource = explode( '?', $pCanonizedPath ); + + if ( FS_SDK__HAS_CURL ) { + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait + // for 2 seconds if the server does not support this header. + $pWPRemoteArgs['headers']['Expect'] = ''; + } + + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { + $pWPRemoteArgs['sslverify'] = false; + } + + if ( false !== $pBeforeExecutionFunction && + is_callable( $pBeforeExecutionFunction ) + ) { + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); + } + + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + + if ( is_wp_error( $result ) ) { + /** + * @var WP_Error $result + */ + if ( self::IsCurlError( $result ) ) { + /** + * With dual stacked DNS responses, it's possible for a server to + * have IPv6 enabled but not have IPv6 connectivity. If this is + * the case, cURL will try IPv4 first and if that fails, then it will + * fall back to IPv6 and the error EHOSTUNREACH is returned by the + * operating system. + */ + $matches = array(); + $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { + /** + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. + * @author Vova Feldman (@svovaf) + */ + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { +// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); + // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); + + // Re-run request. + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + } + } + } + } + + if ( is_wp_error( $result ) ) { + self::ThrowWPRemoteException( $result ); + } + } + + $response_body = $result['body']; + + if ( empty( $response_body ) ) { + return null; + } + + $decoded = json_decode( $response_body ); + + if ( is_null( $decoded ) ) { + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && + preg_match( '/text\/javascript/', $response_body ) + ) { + self::ThrowCloudFlareDDoSException( $response_body ); + } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && + preg_match( '/squid/', $response_body ) + ) { + self::ThrowSquidAclException( $response_body ); + } else { + $decoded = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $response_body, + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + } + + return $decoded; + } + + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than wp_remote_request() + * to make the request. + * + * @param string $pCanonizedPath The URL to make the request to + * @param string $pMethod HTTP method + * @param array $pParams The parameters to use for the POST body + * @param null|array $pWPRemoteArgs wp_remote_request options. + * + * @return object[]|object|null + * + * @throws Freemius_Exception + */ + public function MakeRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null + ) { + $resource = explode( '?', $pCanonizedPath ); + + // Only sign request if not ping.json connectivity test. + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); + + return self::MakeStaticRequest( + $pCanonizedPath, + $pMethod, + $pParams, + $pWPRemoteArgs, + $this->_isSandbox, + $sign_request ? array( &$this, 'SignRequest' ) : null + ); + } + + /** + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter + * + * @param resource $handle A cURL handle returned by curl_init() + * + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to + * CURL_IPRESOLVE_V4 + * + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e + */ + static function CurlResolveToIPv4( $handle ) { + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + + return $handle; + } + + #---------------------------------------------------------------------------------- + #region Connectivity Test + #---------------------------------------------------------------------------------- + + /** + * If successful connectivity to the API endpoint using ping.json endpoint. + * + * - OR - + * + * Validate if ping result object is valid. + * + * @param mixed $pPong + * + * @return bool + */ + public static function Test( $pPong = null ) { + $pong = is_null( $pPong ) ? + self::Ping() : + $pPong; + + return ( + is_object( $pong ) && + isset( $pong->api ) && + 'pong' === $pong->api + ); + } + + /** + * Ping API to test connectivity. + * + * @return object + */ + public static function Ping() { + try { + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Exceptions + #---------------------------------------------------------------------------------- + + /** + * @param \WP_Error $pError + * + * @return bool + */ + private static function IsCurlError( WP_Error $pError ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + return ( 0 === strpos( $message, 'cURL' ) ); + } + + /** + * @param WP_Error $pError + * + * @throws Freemius_Exception + */ + private static function ThrowWPRemoteException( WP_Error $pError ) { + if ( self::IsCurlError( $pError ) ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + #region Check if there are any missing cURL methods. + + $curl_required_methods = array( + 'curl_version', + 'curl_exec', + 'curl_init', + 'curl_close', + 'curl_setopt', + 'curl_setopt_array', + 'curl_error', + ); + + // Find all missing methods. + $missing_methods = array(); + foreach ( $curl_required_methods as $m ) { + if ( ! function_exists( $m ) ) { + $missing_methods[] = $m; + } + } + + if ( ! empty( $missing_methods ) ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'cUrlMissing', + 'message' => $message, + 'code' => 'curl_missing', + 'http' => 402 + ), + 'missing_methods' => $missing_methods, + ) ); + } + + #endregion + + // cURL error - "cURL error {{errno}}: {{error}}". + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); + + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; + + $e = new Freemius_Exception( array( + 'error' => array( + 'code' => $code, + 'message' => $message, + 'type' => 'CurlException', + ), + ) ); + } else { + $e = new Freemius_Exception( array( + 'error' => array( + 'code' => $pError->get_error_code(), + 'message' => $pError->get_error_message(), + 'type' => 'WPRemoteException', + ), + ) ); + } + + throw $e; + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowCloudFlareDDoSException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'CloudFlareDDoSProtection', + 'message' => $pResult, + 'code' => 'cloudflare_ddos_protection', + 'http' => 402 + ) + ) ); + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowSquidAclException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'SquidCacheBlock', + 'message' => $pResult, + 'code' => 'squid_cache_block', + 'http' => 402 + ) + ) ); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/sdk/LICENSE 2.txt b/freemius/includes/sdk/LICENSE 2.txt new file mode 100644 index 0000000..d6a9326 --- /dev/null +++ b/freemius/includes/sdk/LICENSE 2.txt @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/freemius/includes/sdk/index 2.php b/freemius/includes/sdk/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/includes/sdk/index 2.php @@ -0,0 +1,3 @@ + $data ) { + if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) ) ) ) { + if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { + return $relative_path; + } + } + } + + return null; + } diff --git a/freemius/includes/supplements/fs-essential-functions-2.2.1 2.php b/freemius/includes/supplements/fs-essential-functions-2.2.1 2.php new file mode 100644 index 0000000..946a34d --- /dev/null +++ b/freemius/includes/supplements/fs-essential-functions-2.2.1 2.php @@ -0,0 +1,45 @@ +\n" +"Last-Translator: Vova Feldman \n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: includes/class-freemius.php:1783, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "" + +#: includes/class-freemius.php:1790 +msgid "Would you like to proceed with the update?" +msgstr "" + +#: includes/class-freemius.php:1998 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:2000 +msgid "Error" +msgstr "" + +#: includes/class-freemius.php:2390 +msgid "I found a better %s" +msgstr "" + +#: includes/class-freemius.php:2392 +msgid "What's the %s's name?" +msgstr "" + +#: includes/class-freemius.php:2398 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "" + +#: includes/class-freemius.php:2400 +msgid "Deactivation" +msgstr "" + +#: includes/class-freemius.php:2401 +msgid "Theme Switch" +msgstr "" + +#: includes/class-freemius.php:2410, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "" + +#: includes/class-freemius.php:2418 +msgid "I no longer need the %s" +msgstr "" + +#: includes/class-freemius.php:2425 +msgid "I only needed the %s for a short period" +msgstr "" + +#: includes/class-freemius.php:2431 +msgid "The %s broke my site" +msgstr "" + +#: includes/class-freemius.php:2438 +msgid "The %s suddenly stopped working" +msgstr "" + +#: includes/class-freemius.php:2448 +msgid "I can't pay for it anymore" +msgstr "" + +#: includes/class-freemius.php:2450 +msgid "What price would you feel comfortable paying?" +msgstr "" + +#: includes/class-freemius.php:2456 +msgid "I don't like to share my information with you" +msgstr "" + +#: includes/class-freemius.php:2477 +msgid "The %s didn't work" +msgstr "" + +#: includes/class-freemius.php:2487 +msgid "I couldn't understand how to make it work" +msgstr "" + +#: includes/class-freemius.php:2495 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "" + +#: includes/class-freemius.php:2497 +msgid "What feature?" +msgstr "" + +#: includes/class-freemius.php:2501 +msgid "The %s is not working" +msgstr "" + +#: includes/class-freemius.php:2503 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2507 +msgid "It's not what I was looking for" +msgstr "" + +#: includes/class-freemius.php:2509 +msgid "What you've been looking for?" +msgstr "" + +#: includes/class-freemius.php:2513 +msgid "The %s didn't work as expected" +msgstr "" + +#: includes/class-freemius.php:2515 +msgid "What did you expect?" +msgstr "" + +#: includes/class-freemius.php:3370, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "" + +#: includes/class-freemius.php:4122 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "" + +#: includes/class-freemius.php:4124 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "" + +#: includes/class-freemius.php:4131 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4236 +msgid "Yes - do your thing" +msgstr "" + +#: includes/class-freemius.php:4241 +msgid "No - just deactivate" +msgstr "" + +#: includes/class-freemius.php:4286, includes/class-freemius.php:4795, includes/class-freemius.php:5936, includes/class-freemius.php:12614, includes/class-freemius.php:15975, includes/class-freemius.php:16063, includes/class-freemius.php:16229, includes/class-freemius.php:18688, includes/class-freemius.php:18698, includes/class-freemius.php:19334, includes/class-freemius.php:20207, includes/class-freemius.php:20322, includes/class-freemius.php:20466, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "" + +#: includes/class-freemius.php:4355 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "" + +#: includes/class-freemius.php:4792 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "" + +#: includes/class-freemius.php:4793 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "" + +#: includes/class-freemius.php:4965, includes/class-freemius.php:4990, includes/class-freemius.php:19405 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "" + +#: includes/class-freemius.php:5624 +msgid "Premium %s version was successfully activated." +msgstr "" + +#: includes/class-freemius.php:5636, includes/class-freemius.php:7504 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "" + +#: includes/class-freemius.php:5651 +msgid "You have a %s license." +msgstr "" + +#: includes/class-freemius.php:5655, includes/class-freemius.php:15396, includes/class-freemius.php:15407, includes/class-freemius.php:18599, includes/class-freemius.php:18929, includes/class-freemius.php:18995, includes/class-freemius.php:19159 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "" + +#: includes/class-freemius.php:5919 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:5923 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "" + +#: includes/class-freemius.php:5932, templates/add-ons.php:130, templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "" + +#: includes/class-freemius.php:5933 +msgid "Purchase License" +msgstr "" + +#: includes/class-freemius.php:6868, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:6872 +msgid "start the trial" +msgstr "" + +#: includes/class-freemius.php:6873, templates/connect.php:167 +msgid "complete the install" +msgstr "" + +#: includes/class-freemius.php:6986 +msgid "You are just one step away - %s" +msgstr "" + +#: includes/class-freemius.php:6989 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "" + +#: includes/class-freemius.php:7067 +msgid "We made a few tweaks to the %s, %s" +msgstr "" + +#: includes/class-freemius.php:7071 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7503 +msgid "The upgrade of %s was successfully completed." +msgstr "" + +#: includes/class-freemius.php:9665, includes/class-fs-plugin-updater.php:1014, includes/class-fs-plugin-updater.php:1209, includes/class-fs-plugin-updater.php:1216, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "" + +#: includes/class-freemius.php:9667, templates/account.php:313, templates/account.php:321, templates/debug.php:361, templates/debug.php:522 +msgid "Plugin" +msgstr "" + +#: includes/class-freemius.php:9668, templates/account.php:314, templates/account.php:322, templates/debug.php:361, templates/debug.php:522, templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "" + +#: includes/class-freemius.php:12085 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "" + +#: includes/class-freemius.php:12481 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:12601 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:12603 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:12877 +msgid "Account is pending activation." +msgstr "" + +#: includes/class-freemius.php:12989, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php:13001, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:13005 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:15378 +msgid "%s activation was successfully completed." +msgstr "" + +#: includes/class-freemius.php:15392 +msgid "Your account was successfully activated with the %s plan." +msgstr "" + +#: includes/class-freemius.php:15403, includes/class-freemius.php:18991 +msgid "Your trial has been successfully started." +msgstr "" + +#: includes/class-freemius.php:15973, includes/class-freemius.php:16061, includes/class-freemius.php:16227 +msgid "Couldn't activate %s." +msgstr "" + +#: includes/class-freemius.php:15974, includes/class-freemius.php:16062, includes/class-freemius.php:16228 +msgid "Please contact us with the following message:" +msgstr "" + +#: includes/class-freemius.php:16058 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php:16585, includes/class-freemius.php:21339 +msgid "Upgrade" +msgstr "" + +#: includes/class-freemius.php:16591 +msgid "Start Trial" +msgstr "" + +#: includes/class-freemius.php:16593 +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:16672, includes/class-freemius.php:16674 +msgid "Affiliation" +msgstr "" + +#: includes/class-freemius.php:16702, includes/class-freemius.php:16704, templates/account.php:177, templates/debug.php:326 +msgid "Account" +msgstr "" + +#: includes/class-freemius.php:16717, includes/class-freemius.php:16719, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "" + +#: includes/class-freemius.php:16729, includes/class-freemius.php:16731, includes/class-freemius.php:21353, templates/account.php:105, templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "" + +#: includes/class-freemius.php:16765 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:16765 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php:16767, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:16980, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "" + +#: includes/class-freemius.php:17925 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "" + +#: includes/class-freemius.php:17926 +msgctxt "a positive response" +msgid "Right on" +msgstr "" + +#: includes/class-freemius.php:18590 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18592 +msgid "%s Add-on was successfully purchased." +msgstr "" + +#: includes/class-freemius.php:18595 +msgid "Download the latest version" +msgstr "" + +#: includes/class-freemius.php:18681 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php:18687, includes/class-freemius.php:19118, includes/class-freemius.php:19207 +msgid "Error received from the server:" +msgstr "" + +#: includes/class-freemius.php:18697 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php:18891, includes/class-freemius.php:19123, includes/class-freemius.php:19178, includes/class-freemius.php:19281 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "" + +#: includes/class-freemius.php:18904 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php:18905, templates/account.php:107, templates/add-ons.php:191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "" + +#: includes/class-freemius.php:18910 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "" + +#: includes/class-freemius.php:18914, includes/class-freemius.php:18973 +msgid "Please contact us here" +msgstr "" + +#: includes/class-freemius.php:18925 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:18926 +msgid "Your plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18943 +msgid "Your plan was successfully changed to %s." +msgstr "" + +#: includes/class-freemius.php:18959 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "" + +#: includes/class-freemius.php:18961 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:18969 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "" + +#: includes/class-freemius.php:18982 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "" + +#: includes/class-freemius.php:19005 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "" + +#: includes/class-freemius.php:19007 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:19114 +msgid "It looks like the license could not be activated." +msgstr "" + +#: includes/class-freemius.php:19156 +msgid "Your license was successfully activated." +msgstr "" + +#: includes/class-freemius.php:19182 +msgid "It looks like your site currently doesn't have an active license." +msgstr "" + +#: includes/class-freemius.php:19206 +msgid "It looks like the license deactivation failed." +msgstr "" + +#: includes/class-freemius.php:19234 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "" + +#: includes/class-freemius.php:19235 +msgid "O.K" +msgstr "" + +#: includes/class-freemius.php:19288 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19297 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "" + +#: includes/class-freemius.php:19339 +msgid "You are already running the %s in a trial mode." +msgstr "" + +#: includes/class-freemius.php:19350 +msgid "You already utilized a trial before." +msgstr "" + +#: includes/class-freemius.php:19364 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "" + +#: includes/class-freemius.php:19375 +msgid "Plan %s does not support a trial period." +msgstr "" + +#: includes/class-freemius.php:19386 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:19436 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "" + +#: includes/class-freemius.php:19472 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19491 +msgid "Your %s free trial was successfully cancelled." +msgstr "" + +#: includes/class-freemius.php:19807 +msgid "Version %s was released." +msgstr "" + +#: includes/class-freemius.php:19807 +msgid "Please download %s." +msgstr "" + +#: includes/class-freemius.php:19814 +msgid "the latest %s version here" +msgstr "" + +#: includes/class-freemius.php:19819 +msgid "New" +msgstr "" + +#: includes/class-freemius.php:19824 +msgid "Seems like you got the latest release." +msgstr "" + +#: includes/class-freemius.php:19825 +msgid "You are all good!" +msgstr "" + +#: includes/class-freemius.php:20095 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "" + +#: includes/class-freemius.php:20234 +msgid "Site successfully opted in." +msgstr "" + +#: includes/class-freemius.php:20235, includes/class-freemius.php:21055 +msgid "Awesome" +msgstr "" + +#: includes/class-freemius.php:20251, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "" + +#: includes/class-freemius.php:20252 +msgid "Thank you!" +msgstr "" + +#: includes/class-freemius.php:20259 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "" + +#: includes/class-freemius.php:20388 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "" + +#: includes/class-freemius.php:20394 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "" + +#: includes/class-freemius.php:20399 +msgid "%s is the new owner of the account." +msgstr "" + +#: includes/class-freemius.php:20401 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "" + +#: includes/class-freemius.php:20421 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "" + +#: includes/class-freemius.php:20422 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:20429 +msgid "Change Ownership" +msgstr "" + +#: includes/class-freemius.php:20437 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "" + +#: includes/class-freemius.php:20449 +msgid "Please provide your full name." +msgstr "" + +#: includes/class-freemius.php:20454 +msgid "Your name was successfully updated." +msgstr "" + +#: includes/class-freemius.php:20515 +msgid "You have successfully updated your %s." +msgstr "" + +#: includes/class-freemius.php:20655 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "" + +#: includes/class-freemius.php:20656 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "" + +#: includes/class-freemius.php:21095 +msgctxt "exclamation" +msgid "Hey" +msgstr "" + +#: includes/class-freemius.php:21095 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "" + +#: includes/class-freemius.php:21103 +msgid "No commitment for %s days - cancel anytime!" +msgstr "" + +#: includes/class-freemius.php:21104 +msgid "No credit card required" +msgstr "" + +#: includes/class-freemius.php:21111, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "" + +#: includes/class-freemius.php:21188 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:21197 +msgid "Learn more" +msgstr "" + +#: includes/class-freemius.php:21377, templates/account.php:474, templates/account.php:595, templates/connect.php:171, templates/connect.php:421, templates/forms/license-activation.php:25, templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "" + +#: includes/class-freemius.php:21378, templates/account.php:543, templates/account.php:594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "" + +#: includes/class-freemius.php:21469, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "" + +#: includes/class-freemius.php:21471, includes/class-freemius.php:21479, templates/account/partials/site.php:43, templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "" + +#: includes/class-freemius.php:21705 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:21713 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:21726 +msgid "Please follow these steps to complete the upgrade" +msgstr "" + +#: includes/class-freemius.php:21730 +msgid "Download the latest %s version" +msgstr "" + +#: includes/class-freemius.php:21734 +msgid "Upload and activate the downloaded version" +msgstr "" + +#: includes/class-freemius.php:21736 +msgid "How to upload and activate?" +msgstr "" + +#: includes/class-freemius.php:21870 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "" + +#: includes/class-freemius.php:22031 +msgid "Auto installation only works for opted-in users." +msgstr "" + +#: includes/class-freemius.php:22041, includes/class-freemius.php:22074, includes/class-fs-plugin-updater.php:1188, includes/class-fs-plugin-updater.php:1202 +msgid "Invalid module ID." +msgstr "" + +#: includes/class-freemius.php:22050, includes/class-fs-plugin-updater.php:1224 +msgid "Premium version already active." +msgstr "" + +#: includes/class-freemius.php:22057 +msgid "You do not have a valid license to access the premium version." +msgstr "" + +#: includes/class-freemius.php:22064 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "" + +#: includes/class-freemius.php:22082, includes/class-fs-plugin-updater.php:1223 +msgid "Premium add-on version already installed." +msgstr "" + +#: includes/class-freemius.php:22427 +msgid "View paid features" +msgstr "" + +#: includes/class-freemius.php:22749 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:22750 +msgid "Thank you so much for using %s!" +msgstr "" + +#: includes/class-freemius.php:22756 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "" + +#: includes/class-freemius.php:22760 +msgid "Thank you so much for using our products!" +msgstr "" + +#: includes/class-freemius.php:22761 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "" + +#: includes/class-freemius.php:22780 +msgid "%s and its add-ons" +msgstr "" + +#: includes/class-freemius.php:22789 +msgid "Products" +msgstr "" + +#: includes/class-freemius.php:22796, templates/connect.php:272 +msgid "Yes" +msgstr "" + +#: includes/class-freemius.php:22797, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:22798, templates/connect.php:278 +msgid "No" +msgstr "" + +#: includes/class-freemius.php:22800, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:22810 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php:22812, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "" + +#: includes/class-freemius.php:23094 +msgid "License key is empty." +msgstr "" + +#: includes/class-fs-plugin-updater.php:204, templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:209, templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:319, includes/class-fs-plugin-updater.php:352 +msgid "There is a %s of %s available." +msgstr "" + +#: includes/class-fs-plugin-updater.php:321, includes/class-fs-plugin-updater.php:357 +msgid "new Beta version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:322, includes/class-fs-plugin-updater.php:358 +msgid "new version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:381 +msgid "Important Upgrade Notice:" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1253 +msgid "Installing plugin: %s" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1294 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1476 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Purchase More" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:515, templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:519 +msgid "Start my free %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:717 +msgid "Install Free Version Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:718, templates/account.php:534 +msgid "Install Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:727 +msgid "Install Free Version Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:728, templates/add-ons.php:262, templates/auto-installation.php:111, templates/account/partials/addon.php:327, templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:744 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:745, templates/account.php:85, templates/add-ons.php:34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:760, templates/add-ons.php:268, templates/account/partials/addon.php:318, templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:762, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:763, templates/account.php:109, templates/add-ons.php:269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:975 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:976, templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:977 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:978 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:979 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:994 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1004 +msgid "Plugin Install" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1076 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1102 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1108, includes/fs-plugin-info-dialog.php:1128 +msgctxt "as every month" +msgid "Monthly" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1111 +msgctxt "as once a year" +msgid "Annual" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1114 +msgid "Lifetime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1128, includes/fs-plugin-info-dialog.php:1130, includes/fs-plugin-info-dialog.php:1132 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1130 +msgctxt "as once a year" +msgid "Annually" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1132 +msgctxt "as once a year" +msgid "Once" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1138 +msgid "Single Site License" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1140 +msgid "Unlimited Licenses" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1142 +msgid "Up to %s Sites" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1152, templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1159, templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1213 +msgctxt "noun" +msgid "Price" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1261 +msgid "Save %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1271 +msgid "No commitment for %s - cancel anytime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1274 +msgid "After your free %s, pay as little as %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1285 +msgid "Details" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1289, templates/account.php:96, templates/debug.php:203, templates/debug.php:240, templates/debug.php:454, templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1296 +msgctxt "as the plugin author" +msgid "Author" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1303 +msgid "Last Updated" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1308, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1317 +msgid "Requires WordPress Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1318 +msgid "%s or higher" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1325 +msgid "Compatible up to" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1333 +msgid "Downloaded" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1337 +msgid "%s time" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1339 +msgid "%s times" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1349 +msgid "WordPress.org Plugin Page" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1357 +msgid "Plugin Homepage" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1365, includes/fs-plugin-info-dialog.php:1447 +msgid "Donate to this plugin" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1372 +msgid "Average Rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1379 +msgid "based on %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1383 +msgid "%s rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1385 +msgid "%s ratings" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1400 +msgid "%s star" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1402 +msgid "%s stars" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1413 +msgid "Click to see reviews that provided a rating of %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1426 +msgid "Contributors" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1455, includes/fs-plugin-info-dialog.php:1457 +msgid "Warning" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1455 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1457 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1476 +msgid "Paid add-on must be deployed to Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1477 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1498 +msgid "Newer Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1499 +msgid "Newer Free Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1506 +msgid "Latest Version Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1507 +msgid "Latest Free Version Installed" +msgstr "" + +#: templates/account.php:86, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "" + +#: templates/account.php:87, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "" + +#: templates/account.php:90, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "" + +#: templates/account.php:91, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "" + +#: templates/account.php:92, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "" + +#: templates/account.php:93, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php:95, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php:98, templates/account/partials/addon.php:38, templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php:100, templates/account/partials/addon.php:40, templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "" + +#: templates/account.php:101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "" + +#: templates/account.php:102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "" + +#: templates/account.php:103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "" + +#: templates/account.php:104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "" + +#: templates/account.php:106, templates/account/partials/addon.php:46, templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "" + +#: templates/account.php:108, templates/add-ons.php:187, templates/plugin-info/features.php:72, templates/account/partials/addon.php:48, templates/account/partials/site.php:31 +msgid "Free" +msgstr "" + +#: templates/account.php:110, templates/debug.php:373, includes/customizer/class-fs-customizer-upsell-control.php:106, templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "" + +#: templates/account.php:227, templates/account/partials/addon.php:211, templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "" + +#: templates/account.php:250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "" + +#: templates/account.php:250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "" + +#: templates/account.php:292, templates/debug.php:489 +msgid "Name" +msgstr "" + +#: templates/account.php:298, templates/debug.php:490 +msgid "Email" +msgstr "" + +#: templates/account.php:305, templates/debug.php:372, templates/debug.php:528 +msgid "User ID" +msgstr "" + +#: templates/account.php:322, templates/account.php:608, templates/account.php:653, templates/debug.php:238, templates/debug.php:366, templates/debug.php:451, templates/debug.php:488, templates/debug.php:526, templates/debug.php:599, templates/account/payments.php:35, templates/debug/logger.php:21 +msgid "ID" +msgstr "" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "" + +#: templates/account.php:332 +msgid "No ID" +msgstr "" + +#: templates/account.php:337, templates/debug.php:245, templates/debug.php:374, templates/debug.php:455, templates/debug.php:492, templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "" + +#: templates/account.php:343, templates/debug.php:375, templates/debug.php:456, templates/debug.php:493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "" + +#: templates/account.php:373, templates/account/partials/site.php:112, templates/account/partials/site.php:114 +msgid "Trial" +msgstr "" + +#: templates/account.php:400, templates/debug.php:533, templates/account/partials/site.php:248 +msgid "License Key" +msgstr "" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "" + +#: templates/account.php:435 +msgid "not verified" +msgstr "" + +#: templates/account.php:444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "" + +#: templates/account.php:504 +msgid "Free version" +msgstr "" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "" + +#: templates/account.php:541, templates/account.php:749, templates/account/partials/site.php:237, templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "" + +#: templates/account.php:563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "" + +#: templates/account.php:588 +msgid "Sites" +msgstr "" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "" + +#: templates/account.php:609, templates/debug.php:369 +msgid "Address" +msgstr "" + +#: templates/account.php:610 +msgid "License" +msgstr "" + +#: templates/account.php:611 +msgid "Plan" +msgstr "" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "" + +#: templates/account.php:765 +msgid "Processing" +msgstr "" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "" + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "" + +#: templates/account.php:826, templates/account.php:843, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "" + +#: templates/account.php:841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "" + +#: templates/account.php:844, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "" + +#: templates/admin-notice.php:13, templates/forms/license-activation.php:209, templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "" + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "" + +#: templates/connect.php:172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "" + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "" + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "" + +#: templates/connect.php:253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "" + +#: templates/connect.php:256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "" + +#: templates/connect.php:315, templates/connect.php:652, templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "" + +#: templates/connect.php:359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "" + +#: templates/connect.php:391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "" + +#: templates/debug.php:54, templates/debug.php:250, templates/debug.php:376, templates/debug.php:494 +msgid "Actions" +msgstr "" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "" + +#: templates/debug.php:182 +msgid "Key" +msgstr "" + +#: templates/debug.php:183 +msgid "Value" +msgstr "" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "" + +#: templates/debug.php:205, templates/debug.php:244 +msgid "Module Path" +msgstr "" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "" + +#: templates/debug.php:239, templates/debug.php:371, templates/debug.php:453, templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "" + +#: templates/debug.php:241, templates/debug.php:452 +msgid "Title" +msgstr "" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "" + +#: templates/debug.php:368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "" + +#: templates/debug.php:433, templates/debug.php:511, templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "" + +#: templates/debug.php:484 +msgid "Users" +msgstr "" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "" + +#: templates/debug.php:573, templates/debug.php:602, templates/debug/logger.php:25 +msgid "File" +msgstr "" + +#: templates/debug.php:574, templates/debug.php:600, templates/debug/logger.php:23 +msgid "Function" +msgstr "" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "" + +#: templates/debug.php:577, templates/debug.php:601, templates/debug/logger.php:24 +msgid "Message" +msgstr "" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "" + +#: templates/debug.php:587 +msgid "Download" +msgstr "" + +#: templates/debug.php:598, templates/debug/logger.php:22 +msgid "Type" +msgstr "" + +#: templates/debug.php:603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "" + +#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "" + +#: templates/account/billing.php:38, templates/account/billing.php:38 +msgid "Business name" +msgstr "" + +#: templates/account/billing.php:39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "" + +#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "City" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "Town" +msgstr "" + +#: templates/account/billing.php:47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "State" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "Province" +msgstr "" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "" + +#: templates/account/payments.php:38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "" + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "" + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "" + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "" + +#: templates/forms/affiliation.php:165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "" + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "" + +#: templates/forms/affiliation.php:223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "" + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:29, templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "" + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "" + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "" + +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "" + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +msgid "allow" +msgstr "" + +#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "" + +#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +msgid "skip" +msgstr "" + +#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "" + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "" + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "" diff --git a/freemius/languages/freemius-da_DK 2.mo b/freemius/languages/freemius-da_DK 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..51fee802358d665361c1c0bae297c6df696b3fae GIT binary patch literal 26080 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-da_DK 2.po b/freemius/languages/freemius-da_DK 2.po new file mode 100644 index 0000000..5c89f56 --- /dev/null +++ b/freemius/languages/freemius-da_DK 2.po @@ -0,0 +1,2722 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" +o: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Joachim Jensen\n" +"Language-Team: Danish (Denmark) (http://www.transifex.com/freemius/wordpress-sdk/language/da_DK/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: da_DK\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "" +"An update to a Beta version will replace your installed version of %s with " +"the latest Beta release - use with caution, and not on production sites. You" +" have been warned." +msgstr "" + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "" + +#: includes/class-freemius.php:2053 +msgid "" +"Freemius SDK couldn't find the plugin's main file. Please contact " +"sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Fejl" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Jeg fandt et bedre %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Hvad er navnet pÃ¥ %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Det er en midlertidig %s. Jeg er i gang med fejlrettelser." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deaktivering" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Temaskift" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Andet" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Jeg har ikke længere brug for %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Jeg behøvede kun %s i en kort periode" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ødelagde min webside" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s stoppede pludseligt med at virke" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Jeg kan ikke længere betale for det" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Hvilken pris ville du foretrække at betale?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Jeg har ikke lyst til at dele mine informationer med jer" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s virkede ikke" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Jeg forstod ikke, hvordan jeg skulle fÃ¥ det til at fungere." + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s er godt, men jeg har brug for en specifik feature, som ikke understøttes" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Hvilken feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s virker ikke" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Det er ikke, hvad jeg søgte" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Hvad har du ledt efter?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s virkede ikke som forventet" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Hvad forventede du?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Jeg ved ikke hvad cURL er, eller hvordan jeg installerer det. Hjælp mig!" + +#: includes/class-freemius.php:4179 +msgid "" +"We'll make sure to contact your hosting company and resolve the issue. You " +"will get a follow-up email to %s once we have an update." +msgstr "Vi vil kontakte din udbyder og løse problemet. NÃ¥r vi har opdatinger i sagen, vil vi følge op med en email til dig pÃ¥ %s." + +#: includes/class-freemius.php:4186 +msgid "" +"Great, please install cURL and enable it in your php.ini file. In addition, " +"search for the 'disable_functions' directive in your php.ini file and remove" +" any disabled methods starting with 'curl_'. To make sure it was " +"successfully activated, use 'phpinfo()'. Once activated, deactivate the %s " +"and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Ja - fortsæt bare" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nej - bare deaktiver" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Ups" + +#: includes/class-freemius.php:4410 +msgid "" +"Thank for giving us the chance to fix it! A message was just sent to our " +"technical staff. We will get back to you as soon as we have an update to %s." +" Appreciate your patience." +msgstr "Tak fordi du giver os en chance for at fixe det! En besked er lige blevet sendt til vores tekniske personale. Vi vil vende tilbage, sÃ¥ snart der er nyt om %s. Vi sætter pris pÃ¥ din tÃ¥lmodighed." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s virker ikke uden %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s virker ikke uden pluginnet." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "" +"Unexpected API error. Please contact the %s's author with the following " +"error." +msgstr "" + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium-versionen af %s blev aktiveret." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +"Used to express elation, enthusiasm, or triumph (especially in electronic " +"communication)." +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Du har en %s licens." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Yee-haw" + +#: includes/class-freemius.php:5982 +msgid "" +"%s free trial was successfully cancelled. Since the add-on is premium only " +"it was automatically deactivated. If you like to use it in the future, " +"you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:5986 +msgid "" +"%s is a premium only add-on. You have to purchase a license first before " +"activating the plugin." +msgstr "" + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Mere information om %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Køb licens" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "" +"You should receive an activation email for %s to your mailbox at %s. Please " +"make sure you click the activation button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start prøveperioden" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "færdiggør installeringen" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Du mangler kun ét skridt - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Færdiggør aktivering af \"%s\" nu" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Vi har foretaget nogle rettelser til %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "Opgraderingen af %s blev fuldendt." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Tilføjelse" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "" +"An unknown error has occurred while trying to set the user's beta mode." +msgstr "" + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:12669 +msgid "" +"We couldn't find your email address in the system, are you sure it's the " +"right address?" +msgstr "Vi kunne ikke finde din e-mailadresse i systemet, er du sikker pÃ¥, det er den rigtige adresse?" + +#: includes/class-freemius.php:12671 +msgid "" +"We can't see any active licenses associated with that email address, are you" +" sure it's the right address?" +msgstr "Vi kan ikke finde nogen aktive licenser knyttet til den e-mailadresse, er du sikker pÃ¥, det er den rigtige adresse?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Konto afventer aktivering." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "Aktivering af %s blev gennemført." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Din konto blev aktiveret med planen %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Din prøveperiode er begyndt." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Kunne ikke aktivere %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Kontakt os venligst med følgende besked:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Opgrader" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start prøveperiode" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Priser" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Konto" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Kontakt os" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Tilføjelser" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Priser" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Supportforum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Din e-mailadresse er blevet verificeret - du er FOR SEJ!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "SÃ¥dan" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "Betalingen for tilføjelsen %s blev gennemført." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download den seneste version" + +#: includes/class-freemius.php:18751 +msgid "" +"Your server is blocking the access to Freemius' API, which is crucial for " +"%1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Fejl modtager fra serveren:" + +#: includes/class-freemius.php:18767 +msgid "" +"It seems like one of the authentication parameters is wrong. Update your " +"Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +"something somebody says when they are thinking about what you have just " +"said." +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "" +"It looks like you are still on the %s plan. If you did upgrade or change " +"your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Prøveperiode" + +#: includes/class-freemius.php:18980 +msgid "" +"I have upgraded my account but when I try to Sync the License, the plan " +"remains %s." +msgstr "Jeg har opgraderet min konto, men nÃ¥r jeg forsøger at synkronisere licensen, forbliver planen %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Kontakt os her" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Din plan er blevet opgraderet." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Din plan er blevet ændret til %s." + +#: includes/class-freemius.php:19029 +msgid "" +"Your license has expired. You can still continue using the free %s forever." +msgstr "Din licens er udløbet. Du kan stadig fortsætte med at benytte den gratis udgave af %s." + +#: includes/class-freemius.php:19031 +msgid "" +"Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s " +"without interruptions." +msgstr "Din licens er udløbet. %1$sOpgrader nu%2$s for at fortsætte med at benytte %3$s uden forstyrrelser." + +#: includes/class-freemius.php:19039 +msgid "" +"Your license has been cancelled. If you think it's a mistake, please contact" +" support." +msgstr "Din licens er blevet annulleret. Hvis du mener, dette er en fejl, sÃ¥ kontakt venligst support." + +#: includes/class-freemius.php:19052 +msgid "" +"Your license has expired. You can still continue using all the %s features, " +"but you'll need to renew your license to continue getting updates and " +"support." +msgstr "Din licens er udløbet. Du kan stadig benytte alle funktionerne i %s, men du bliver nødt til at fornye din licens for at fÃ¥ opdateringer og support." + +#: includes/class-freemius.php:19075 +msgid "" +"Your free trial has expired. You can still continue using all our free " +"features." +msgstr "Din gratis prøveperiode er udløbet. Du kan stadig benytte alle de gratis features." + +#: includes/class-freemius.php:19077 +msgid "" +"Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s " +"without interruptions." +msgstr "" + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Det ser ud til, at licensen ikke kunne aktiveres." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Din licens er blevet aktiveret." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Det ser ud til, at dit websted endnu ikke har en aktiv licens." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Det ser ud til, at licens-deaktiveringen mislykkedes." + +#: includes/class-freemius.php:19304 +msgid "" +"Your license was successfully deactivated, you are back to the %s plan." +msgstr "Din licens blev deaktiveret, du er tilbage pÃ¥ planen %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "" +"Seems like we are having some temporary issue with your subscription " +"cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19367 +msgid "" +"Your subscription was successfully cancelled. Your %s plan license will " +"expire in %s." +msgstr "" + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Du benytter allerede %s under en prøveperiode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Du har allerede brugt din prøveperiode." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s eksisterer ikke og kan derfor ikke starte prøveperiode." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s understøtter ikke en prøveperiode." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:19506 +msgid "" +"It looks like you are not in trial mode anymore so there's nothing to cancel" +" :)" +msgstr "Det lader ikke til du er i en prøveperiode længere, sÃ¥ der er ikke noget at annullere :-)" + +#: includes/class-freemius.php:19542 +msgid "" +"Seems like we are having some temporary issue with your trial cancellation. " +"Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Din gratis prøveperiode for %s er blevet annulleret." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s er blevet udgivet." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Download venligst %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "den seneste version af %s her" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Ny" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Det ser ud til, at du har den seneste udgivelse." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Det var det!" + +#: includes/class-freemius.php:20165 +msgid "" +"Verification mail was just sent to %s. If you can't find it after 5 min, " +"please check your spam box." +msgstr "" + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Websted er tilmeldt." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Sejt" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "" +"We appreciate your help in making the %s better by letting us track some " +"usage data." +msgstr "Vi sætter pris pÃ¥ din hjælp med at forbedre %s ved at lade os indsamle brugsdata." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Mange tak!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Vi vil ikke længere indsende brugsdata af %s pÃ¥ %s til %s." + +#: includes/class-freemius.php:20458 +msgid "" +"Please check your mailbox, you should receive an email via %s to confirm the" +" ownership change. From security reasons, you must confirm the change within" +" the next 15 min. If you cannot find the email, please check your spam " +"folder." +msgstr "" + +#: includes/class-freemius.php:20464 +msgid "" +"Thanks for confirming the ownership change. An email was just sent to %s for" +" final approval." +msgstr "" + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s er den nye ejer af kontoen." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Tillykke" + +#: includes/class-freemius.php:20491 +msgid "" +"Sorry, we could not complete the email update. Another user with the same " +"email is already registered." +msgstr "" + +#: includes/class-freemius.php:20492 +msgid "" +"If you would like to give up the ownership of the %s's account to %s click " +"the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Skift ejerskab" + +#: includes/class-freemius.php:20507 +msgid "" +"Your email was successfully updated. You should receive an email with " +"confirmation instructions in few moments." +msgstr "" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Indtast venligst dit fulde navn." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Dit navn er blevet opdateret." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Opdatering af %s blev gennemført." + +#: includes/class-freemius.php:20725 +msgid "" +"Just letting you know that the add-ons information of %s is being pulled " +"from an external server." +msgstr "" + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Se her" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "" +"How do you like %s so far? Test all our %s premium features with a %d-day " +"free trial." +msgstr "Hvad syntes du om %s indtil videre? Test alle %s premium funktioner med en %d-dags gratis prøveperiode." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Ingen bindinger i %s dage - annuller nÃ¥r som helst!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Betalingskort ikke pÃ¥krævet" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start gratis prøveperiode" + +#: includes/class-freemius.php:21258 +msgid "" +"Hey there, did you know that %s has an affiliate program? If you like the %s" +" you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Læs mere" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Aktiver licens" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Skift licens" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Frameld" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Tilmeld" + +#: includes/class-freemius.php:21775 +msgid "" +" The paid version of %1$s is already installed. Please activate it to start " +"benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Følg venligst disse trin for at færdiggøre opgraderingen" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download den seneste version af %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload og aktiver den downloadede version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Upload og aktivering, hvordan?" + +#: includes/class-freemius.php:21940 +msgid "" +"%sClick here%s to choose the sites where you'd like to activate the license " +"on." +msgstr "" + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto-installation fungerer kun for tilmeldte brugere." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ugyldigt modul-ID." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version allerede aktiv." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Du har ikke en gyldig licens til at benytte premium-versionen." + +#: includes/class-freemius.php:22134 +msgid "" +"Plugin is a \"Serviceware\" which means it does not have a premium code " +"version." +msgstr "" + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium tilføjelse er allerede installeret." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Vis betalte features" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Tak fordi du benytter %s!" + +#: includes/class-freemius.php:22826 +msgid "" +"You've already opted-in to our usage-tracking, which helps us keep improving" +" the %s." +msgstr "Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Mange tak for at benytte vores produkter!" + +#: includes/class-freemius.php:22831 +msgid "" +"You've already opted-in to our usage-tracking, which helps us keep improving" +" them." +msgstr "Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre dem." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s og tilføjelser" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Produkter" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Ja" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send mig sikkerheds- og feature-opdateringer, informativt indhold og tilbud." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nej" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "" +"do %sNOT%s send me security & feature updates, educational content and " +"offers." +msgstr "send %sIKKE%s sikkerheds- og feature-opdateringer, informativt indhold og tilbud." + +#: includes/class-freemius.php:22880 +msgid "" +"Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance " +"requirements it is required that you provide your explicit consent, again, " +"confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "" +"Please let us know if you'd like us to contact you for security & feature " +"updates, educational content, and occasional offers:" +msgstr "Lad os vide, om vi har lov til at kontakte dig med sikkerheds- og feature-opdateringer, informativt indhold og lejlighedsvise tilbud:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "Licensnøglen er tom." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Forny licens" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "" + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installerer plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1437 +msgid "" +"The remote plugin package does not contain a folder with the desired slug " +"and renaming did not work." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Køb" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start min gratis %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installer opdatering til gratis version nu" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installer opdatering nu" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer gratis version nu" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installer nu" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download seneste gratis version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download seneste" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Aktiver denne tilføjelse" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Aktiver gratis version" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Aktiver" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Beskrivelse" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installering" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Skærmbilleder" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Ændringslog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Anmeldelser" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Andre noter" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Funktioner og priser" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plugin-installering" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Bedste" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "MÃ¥nedligt" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Ã…rligt" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Livstid" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Faktureret %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Ã…rligt" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Engangsbeløb" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Ubegrænsede licenser" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Op til %s websteder" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "md" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "Ã¥r" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Pris" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Spar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Ingen bindinger ved %s - annuller nÃ¥r som helst" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Efter din gratis %s er prisen kun %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Detaljer" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Forfatter" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Senest opdateret" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s siden" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Kræver WordPress-version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s eller højere" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Kompatibel op til" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloadet" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s gang" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s gange" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin-side" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plugin-websted" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donér til dette plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Gennemsnitlig vurdering" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "baseret pÃ¥ %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s vurdering" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s vurderinger" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s stjerne" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stjerner" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Bidragsydere" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Advarsel" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "" +"This plugin has not been tested with your current version of WordPress." +msgstr "Dette plugin er ikke blevet testet med din nuværende version af WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "" +"This plugin has not been marked as compatible with your version of " +"WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nyere version (%s) installeret" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nyere gratis version (%s) installeret" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Seneste version installeret" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Seneste gratis version installeret" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "" +"%1$s will immediately stop all future recurring payments and your %2$s plan " +"license will expire in %3$s." +msgstr "" + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "" +"Please note that we will not be able to grandfather outdated pricing for " +"renewals/new subscriptions after a cancellation. If you choose to renew the " +"subscription manually in the future, after a price increase, which typically" +" occurs once a year, you will be charged the updated price." +msgstr "" + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "" +"Cancelling the trial will immediately block access to all premium features. " +"Are you sure?" +msgstr "" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "" +"You can still enjoy all %s features but you will not have access to %s " +"security & feature updates, nor support." +msgstr "" + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "" +"Once your license expires you can still use the Free version but you will " +"NOT have access to the %s features." +msgstr "" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Aktiver %s plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto-fornyer om %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Udløber om %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Synkroniser licens" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annuller prøveperiode" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Skift plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Opgrader" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Nedgrader" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Gratis prøveperiode" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Kontodetaljer" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "" + +#: templates/account.php:210 +msgid "" +"Deleting the account will automatically deactivate your %s plan license so " +"you can use it on other sites. If you want to terminate the recurring " +"payments as well, click the \"Cancel\" button, and first \"Downgrade\" your " +"account. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:212 +msgid "" +"Deletion is not temporary. Only delete if you no longer want to use this %s " +"anymore. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Slet konto" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deaktiver licens" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Er du sikker pÃ¥, du vil fortsætte?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annuller abonnement" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Synkroniser" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Navn" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "E-mail" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Bruger-ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Websteds-ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Intet ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Offentlig nøgle" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Privat nøgle" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Ingen privat nøgle" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Prøveperiode" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licensnøgle" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "" + +#: templates/account.php:435 +msgid "not verified" +msgstr "ikke verificeret" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Udløbet" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium version" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Gratis version" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verificer e-mail" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download 1%s version" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Vis" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Angiv venligst %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Rediger" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Websteder" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Søg efter adresse" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adresse" + +#: templates/account.php:610 +msgid "License" +msgstr "Licens" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licens" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Skjul" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Arbejder" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "" + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "" + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "" + +#: templates/account.php:858 +msgid "" +"Deactivating your license will block all premium features, but will enable " +"activating the license on another site. Are you sure you want to proceed?" +msgstr "" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Vis detaljer" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Tilføjelser til %s" + +#: templates/add-ons.php:55 +msgid "" +"We could'nt load the add-ons list. It's probably an issue on our side, " +"please try to come back in few minutes." +msgstr "" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Aktiv" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Fjern" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "1%s sek" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatisk installering" + +#: templates/auto-installation.php:93 +msgid "" +"An automated download and installation of %s (paid version) from %s will " +"start in %s. If you would like to do it manually - click the cancellation " +"button now." +msgstr "" + +#: templates/auto-installation.php:104 +msgid "" +"The installation process has started and may take a few minutes to complete." +" Please wait until it is done - do not refresh this page." +msgstr "" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuller installering" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Udtjekning" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Tillad & Fortsæt" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Gensend e-mail om aktivering" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Tak %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Accepter & aktiver licens" + +#: templates/connect.php:181 +msgid "" +"Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Tak for at købe %s! For at komme i gang, venligst indtast din licensnøgle:" + +#: templates/connect.php:188 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, educational content, offers, and non-sensitive diagnostic " +"tracking with %4$s." +msgstr "" + +#: templates/connect.php:189 +msgid "" +"Never miss an important update - opt in to our security and feature updates " +"notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:195 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, educational content, offers, and non-sensitive diagnostic " +"tracking with %4$s. If you skip this, that's okay! %1$s will still work just" +" fine." +msgstr "" + +#: templates/connect.php:196 +msgid "" +"Never miss an important update - opt in to our security & feature updates " +"notifications, and non-sensitive diagnostic tracking with %4$s. If you skip " +"this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:233 +msgid "" +"During the update process we detected %d site(s) that are still pending " +"license activation." +msgstr "" + +#: templates/connect.php:235 +msgid "" +"If you'd like to use the %s on those sites, please enter your license key " +"below and click the activation button." +msgstr "" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "" + +#: templates/connect.php:242 +msgid "" +"Alternatively, you can skip it for now and activate the license later, in " +"your %s's network-level Account page." +msgstr "" + +#: templates/connect.php:244 +msgid "" +"During the update process we detected %s site(s) in the network that are " +"still pending your attention." +msgstr "" + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licensnøgle" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Kan du ikke finde din licensnøgle?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Spring over" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "" + +#: templates/connect.php:318 +msgid "" +"If you click it, this decision will be delegated to the sites " +"administrators." +msgstr "" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Overblik af din profil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Navn og e-mailadresse" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Overblik af dit websted" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Websteds-URL, WP version, PHP info, plugins og temaer" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin-meddelelser" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Aktivering, deaktivering og afinstallering" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Nyhedsbrev" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "" +"The %1$s will be periodically sending data to %2$s to check for security and" +" feature updates, and verify the validity of your license." +msgstr "" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Hvilke tilladelser bliver givet?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Har du ikke en licensnøgle?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Har du en licensnøgle?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privatlivspolitik" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "ServicevilkÃ¥r" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Sender e-mail" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Aktiverer" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Kontakt" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Fra" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Til" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Fejlfinding" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Handlinger" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Er du sikker pÃ¥, du vil slette al Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Slet alle konti" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Ryd API-cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synkroniser data fra server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Hent DB-indstilling" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Sæt DB-indstilling" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Nøgle" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Værdi" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK-versioner" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK-sti" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Modul-sti" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Er aktiv" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temaer" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Kortnavn" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titel" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius tilstand" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Netværksblog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Netværksbruger" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Forbundet" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blokeret" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuler netværksopgradering" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s installeringer" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Websteder" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog-ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Slet" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Tilføjelser til modul %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Brugere" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificeret" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "1%s licenser" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin-ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan-ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Kvote" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Aktiveret" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blokerer" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Udløber" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Fejlfindingslog" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Alle typer" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Alle forespørgsler" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Fil" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Funktion" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Proces-ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Besked" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Tidsstempel" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Opdater" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Betaling" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Firmanavn" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Moms / VAT ID" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresselinje %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "By" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "By" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / Postnummer" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Land" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Vælg land" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Stat" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provins" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Betalinger" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Dato" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Beløb" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Faktura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Metode" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Kode" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Længde" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Sti" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Slut" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Om %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s siden" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sek" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synkronisering af plugins og temaer" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Sidste" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Planlagte cron jobs" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Modul" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Modultype" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Næste" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Udløber ikke" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "" + +#: templates/forms/affiliation.php:104 +msgid "" +"Your affiliate application for %s has been accepted! Log in to your " +"affiliate area at: %s." +msgstr "" + +#: templates/forms/affiliation.php:119 +msgid "" +"Thank you for applying for our affiliate program, we'll review your details " +"during the next 14 days and will get back to you with further information." +msgstr "" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "" + +#: templates/forms/affiliation.php:125 +msgid "" +"Thank you for applying for our affiliate program, unfortunately, we've " +"decided at this point to reject your application. Please try again in 30 " +"days." +msgstr "" + +#: templates/forms/affiliation.php:128 +msgid "" +"Due to violation of our affiliation terms, we decided to temporarily block " +"your affiliation account. If you have any questions, please contact support." +msgstr "" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "" + +#: templates/forms/affiliation.php:142 +msgid "" +"Refer new customers to our %s and earn %s commission on each successful sale" +" you refer!" +msgstr "" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Programoversigt" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "" + +#: templates/forms/affiliation.php:152 +msgid "" +"%s tracking cookie after the first visit to maximize earnings potential." +msgstr "" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "" + +#: templates/forms/affiliation.php:159 +msgid "" +"As we reserve 30 days for potential refunds, we only pay commissions that " +"are older than 30 days." +msgstr "" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "E-mailadresse" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Fulde navn" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "E-mailadresse til PayPal-konto" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Hvor vil du promovere %s?" + +#: templates/forms/affiliation.php:179 +msgid "" +"Enter the domain of your website or other websites from where you plan to " +"promote the %s." +msgstr "" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Tilføj andet domæne" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Ekstra domæner" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Andre domæner du vil markedsføre produktet fra." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promoveringsmetoder" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Sociale medier (Facebook, Twitter osv.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobil-apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Websted, e-mail, og statistikker for sociale medier (valgfrit)" + +#: templates/forms/affiliation.php:210 +msgid "" +"Please feel free to provide any relevant website or social media statistics," +" e.g. monthly unique site visits, number of email subscribers, followers, " +"etc. (we will keep this information confidential)." +msgstr "" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Hvordan vil du promovere os?" + +#: templates/forms/affiliation.php:217 +msgid "" +"Please provide details on how you intend to promote %s (please be as " +"specific as possible)." +msgstr "" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuller" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Bliv en affiliate" + +#: templates/forms/license-activation.php:21 +msgid "" +"Please enter the license key that you received in the email right after the " +"purchase:" +msgstr "Indtast licensnøglen, du modtog i e-mailen lige efter købet:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Opdater licens" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Frameld" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Tilmeld" + +#: templates/forms/optout.php:33 +msgid "" +"Usage tracking is done in the name of making %s better. Making a better user" +" experience, prioritizing new features, and more good things. We'd really " +"appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:35 +msgid "" +"By clicking \"Opt Out\", we will no longer be sending any data from %s to " +"%s." +msgstr "Ved at klikke \"Frameld\" vil vi ikke længere sende data fra %s til %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "En ny version af %s er tilgængelig." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Ny version tilgængelig" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Fjern" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Send licensnøgle" + +#: templates/forms/resend-key.php:57 +msgid "" +"Enter the email address you've used for the upgrade below and we will resend" +" you the license key." +msgstr "Indtast e-mailadressen, som du benyttede ved opgraderingen, nedenfor og vi vil gensende licensnøglen til dig." + +#: templates/forms/subscription-cancellation.php:37 +msgid "" +"Deactivating or uninstalling the %s will automatically disable the license, " +"which you'll be able to use on another site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:47 +msgid "" +"In case you are NOT planning on using this %s on this site (or any other " +"site) - would you like to cancel the %s as well?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: templates/forms/subscription-cancellation.php:57 +msgid "" +"Cancel %s - I no longer need any security & feature updates, nor support for" +" %s because I'm not planning to use the %s on this, or any other site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:68 +msgid "" +"Don't cancel %s - I'm still interested in getting security & feature " +"updates, as well as be able to contact support." +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "" +"%1$s will immediately stop all future recurring payments and your %s plan " +"license will expire in %s." +msgstr "" + +#: templates/forms/subscription-cancellation.php:103 +msgid "" +"Once your license expires you will no longer be able to use the %s, unless " +"you activate it again with a valid premium license." +msgstr "" + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "" + +#: templates/forms/trial-start.php:22 +msgid "" +"You are 1-click away from starting your %1$s-day free trial of the %2$s " +"plan." +msgstr "Du er 1 klik fra at begynde din %1$s dages gratis prøveperiode af planen %2$s." + +#: templates/forms/trial-start.php:28 +msgid "" +"For compliance with the WordPress.org guidelines, before we start the trial " +"we ask that you opt in with your user and non-sensitive site information, " +"allowing the %s to periodically send data to %s to check for version updates" +" and to validate your trial." +msgstr "" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Aktiver licens pÃ¥ alle websteder i netværket." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Anvend pÃ¥ alle websteder i netværket." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Akiver licens pÃ¥ alle afventende websteder." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Anvend pÃ¥ alle afventende websteder." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "tillad" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegér" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "spring over" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Klik for at vise skærmbillede %d i fuld skærm" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Ubegrænsede opdateringer" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s tilbage" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Seneste license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annulleret" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Udløber ikke" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Ejer-navn" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "E-mailadresse for ejer" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Ejer-ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Abonnement" + +#: templates/forms/deactivation/contact.php:19 +msgid "" +"Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Vi beklager ulejligheden, og vi er her for at hjælpe, hvis du giver os chancen." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Kontakt support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonym feedback" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deaktiver" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Aktiver %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Hvis du har tid, sÃ¥ lad os venligst vide hvorfor du %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deaktiverer" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "skifter" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Send & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Fortæl os venligst Ã¥rsagen, sÃ¥ vi kan forbedre det." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Ja - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Spring over & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Klik her for at benytte pluginnet anonymt" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "" +"You might have missed it, but you don't have to share any data and can just " +"%s the opt-in." +msgstr "Du har mÃ¥ske overset det, men du behøver ikke at dele data og kan blot %s tilmeldingen." diff --git a/freemius/languages/freemius-en 2.mo b/freemius/languages/freemius-en 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..03eb52bbc95b7396d275b0583bc59a3c49bbcfdd GIT binary patch literal 53012 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

    WE3Ys}zZ`3Tu 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 It|RUK4~-(Dh5!Hn literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-en 2.po b/freemius/languages/freemius-en 2.po new file mode 100644 index 0000000..158f1b6 --- /dev/null +++ b/freemius/languages/freemius-en 2.po @@ -0,0 +1,2386 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +msgid "" +msgstr "" +"Project-Id-Version: freemius\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Vova Feldman \n" +"Language: \n" +"Language-Team: Freemius Team \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php:1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Error" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "I found a better %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "What's the %s's name?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "It's a temporary %s. I'm just debugging an issue." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deactivation" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Theme Switch" + +#: includes/class-freemius.php:2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Other" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "The %s broke my site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "The %s suddenly stopped working" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "I can't pay for it anymore" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "What price would you feel comfortable paying?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "I don't like to share my information with you" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "The %s didn't work" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "I couldn't understand how to make it work" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "What feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "The %s is not working" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Kindly share what didn't work so we can fix it for future users..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "It's not what I was looking for" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "What you've been looking for?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "The %s didn't work as expected" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "What did you expect?" + +#: includes/class-freemius.php:3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "I don't know what is cURL or how to install it, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Yes - do your thing" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - just deactivate" + +#: includes/class-freemius.php:4341, includes/class-freemius.php:4850, includes/class-freemius.php:5999, includes/class-freemius.php:12682, includes/class-freemius.php:16045, includes/class-freemius.php:16133, includes/class-freemius.php:16299, includes/class-freemius.php:18758, includes/class-freemius.php:18768, includes/class-freemius.php:19404, includes/class-freemius.php:20277, includes/class-freemius.php:20392, includes/class-freemius.php:20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s cannot run without %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s cannot run without the plugin." + +#: includes/class-freemius.php:5020, includes/class-freemius.php:5045, includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php:5699, includes/class-freemius.php:7567 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "You have a %s license." + +#: includes/class-freemius.php:5718, includes/class-freemius.php:15466, includes/class-freemius.php:15477, includes/class-freemius.php:18669, includes/class-freemius.php:18999, includes/class-freemius.php:19065, includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Yee-haw" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php:5995, templates/add-ons.php:130, templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "More information about %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Purchase License" + +#: includes/class-freemius.php:6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start the trial" + +#: includes/class-freemius.php:6936, templates/connect.php:167 +msgid "complete the install" +msgstr "complete the install" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "You are just one step away - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Complete \"%s\" Activation Now" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php:9728, includes/class-fs-plugin-updater.php:975, includes/class-fs-plugin-updater.php:1170, includes/class-fs-plugin-updater.php:1177, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php:9730, templates/account.php:313, templates/account.php:321, templates/debug.php:361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php:9731, templates/account.php:314, templates/account.php:322, templates/debug.php:361, templates/debug.php:522, templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Theme" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account is pending activation." + +#: includes/class-freemius.php:13057, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php:13069, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activation was successfully completed." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Your account was successfully activated with the %s plan." + +#: includes/class-freemius.php:15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Your trial has been successfully started." + +#: includes/class-freemius.php:16043, includes/class-freemius.php:16131, includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Couldn't activate %s." + +#: includes/class-freemius.php:16044, includes/class-freemius.php:16132, includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Please contact us with the following message:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php:16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Upgrade" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start Trial" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Pricing" + +#: includes/class-freemius.php:16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php:16772, includes/class-freemius.php:16774, templates/account.php:177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php:16787, includes/class-freemius.php:16789, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contact Us" + +#: includes/class-freemius.php:16799, includes/class-freemius.php:16801, includes/class-freemius.php:21423, templates/account.php:105, templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php:16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Pricing" + +#: includes/class-freemius.php:17050, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Support Forum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Your email has been successfully verified - you are AWESOME!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Right on" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Your %s Add-on plan was successfully upgraded." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on was successfully purchased." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download the latest version" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php:18757, includes/class-freemius.php:19188, includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error received from the server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php:18961, includes/class-freemius.php:19193, includes/class-freemius.php:19248, includes/class-freemius.php:19351 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php:18975, templates/account.php:107, templates/add-ons.php:191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Trial" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "I have upgraded my account but when I try to Sync the License, the plan remains %s." + +#: includes/class-freemius.php:18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Please contact us here" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Your plan was successfully upgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Your plan was successfully changed to %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Your license has been cancelled. If you think it's a mistake, please contact support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "It looks like the license could not be activated." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Your license was successfully activated." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "It looks like your site currently doesn't have an active license." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "It looks like the license deactivation failed." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Your license was successfully deactivated, you are back to the %s plan." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "You already utilized a trial before." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s do not exist, therefore, can't start a trial." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s does not support a trial period." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Your %s free trial was successfully cancelled." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s was released." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Please download %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "the latest %s version here" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "New" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Seems like you got the latest release." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "You are all good!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php:20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Awesome" + +#: includes/class-freemius.php:20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Thank you!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Thanks for confirming the ownership change. An email was just sent to %s for final approval." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is the new owner of the account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Congrats" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Change Ownership" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Please provide your full name." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Your name was successfully updated." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "You have successfully updated your %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Heads up" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "No commitment for %s days - cancel anytime!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "No credit card required" + +#: includes/class-freemius.php:21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start free trial" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Learn more" + +#: includes/class-freemius.php:21447, templates/account.php:474, templates/account.php:595, templates/connect.php:171, templates/connect.php:421, templates/forms/license-activation.php:25, templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activate License" + +#: includes/class-freemius.php:21448, templates/account.php:543, templates/account.php:594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Change License" + +#: includes/class-freemius.php:21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php:21541, includes/class-freemius.php:21547, templates/account/partials/site.php:43, templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Please follow these steps to complete the upgrade" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download the latest %s version" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload and activate the downloaded version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "How to upload and activate?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php:22111, includes/class-freemius.php:22144, includes/class-fs-plugin-updater.php:1149, includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Invalid module ID." + +#: includes/class-freemius.php:22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version already active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "You do not have a valid license to access the premium version." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php:22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "View paid features" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Products" + +#: includes/class-freemius.php:22866, templates/connect.php:272 +msgid "Yes" +msgstr "Yes" + +#: includes/class-freemius.php:22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php:22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php:22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "License key is empty." + +#: includes/class-fs-plugin-updater.php:184, templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renew license" + +#: includes/class-fs-plugin-updater.php:189, templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php:280, includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php:282, includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php:283, includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installing plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php:510, templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Purchase" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start my free %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php:713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Install Update Now" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php:723, templates/add-ons.php:262, templates/auto-installation.php:111, templates/account/partials/addon.php:327, templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Install Now" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php:740, templates/account.php:85, templates/add-ons.php:34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Latest" + +#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:268, templates/account/partials/addon.php:318, templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activate this add-on" + +#: includes/fs-plugin-info-dialog.php:757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activate Free Version" + +#: includes/fs-plugin-info-dialog.php:758, templates/account.php:109, templates/add-ons.php:269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activate" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Description" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installation" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php:971, templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Screenshots" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Reviews" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Other Notes" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Features & Pricing" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plugin Install" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Best" + +#: includes/fs-plugin-info-dialog.php:1103, includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Monthly" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annual" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Lifetime" + +#: includes/fs-plugin-info-dialog.php:1123, includes/fs-plugin-info-dialog.php:1125, includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Billed %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annually" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Once" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Single Site License" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Unlimited Licenses" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Up to %s Sites" + +#: includes/fs-plugin-info-dialog.php:1147, templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mo" + +#: includes/fs-plugin-info-dialog.php:1154, templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "year" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Price" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Save %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Details" + +#: includes/fs-plugin-info-dialog.php:1284, templates/account.php:96, templates/debug.php:203, templates/debug.php:240, templates/debug.php:454, templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Author" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Last Updated" + +#: includes/fs-plugin-info-dialog.php:1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s ago" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Requires WordPress Version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s or higher" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloaded" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s time" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s times" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin Page" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plugin Homepage" + +#: includes/fs-plugin-info-dialog.php:1360, includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donate to this plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Average Rating" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "based on %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s rating" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s ratings" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s star" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stars" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributors" + +#: includes/fs-plugin-info-dialog.php:1450, includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Warning" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "This plugin has not been tested with your current version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "This plugin has not been marked as compatible with your version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Newer Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Latest Version Installed" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Latest Free Version Installed" + +#: templates/account.php:86, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php:87, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php:90, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php:91, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancelling the trial will immediately block access to all premium features. Are you sure?" + +#: templates/account.php:92, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php:93, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php:95, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activate %s Plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php:98, templates/account/partials/addon.php:38, templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renews in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php:100, templates/account/partials/addon.php:40, templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Expires in %s" + +#: templates/account.php:101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sync License" + +#: templates/account.php:102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Cancel Trial" + +#: templates/account.php:103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Change Plan" + +#: templates/account.php:104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Upgrade" + +#: templates/account.php:106, templates/account/partials/addon.php:46, templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php:108, templates/add-ons.php:187, templates/plugin-info/features.php:72, templates/account/partials/addon.php:48, templates/account/partials/site.php:31 +msgid "Free" +msgstr "Free" + +#: templates/account.php:110, templates/debug.php:373, includes/customizer/class-fs-customizer-upsell-control.php:106, templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Free Trial" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Account Details" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Delete Account" + +#: templates/account.php:227, templates/account/partials/addon.php:211, templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deactivate License" + +#: templates/account.php:250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Are you sure you want to proceed?" + +#: templates/account.php:250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Cancel Subscription" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sync" + +#: templates/account.php:292, templates/debug.php:489 +msgid "Name" +msgstr "Name" + +#: templates/account.php:298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php:305, templates/debug.php:372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID" + +#: templates/account.php:322, templates/account.php:608, templates/account.php:653, templates/debug.php:238, templates/debug.php:366, templates/debug.php:451, templates/debug.php:488, templates/debug.php:526, templates/debug.php:599, templates/account/payments.php:35, templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "No ID" + +#: templates/account.php:337, templates/debug.php:245, templates/debug.php:374, templates/debug.php:455, templates/debug.php:492, templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Public Key" + +#: templates/account.php:343, templates/debug.php:375, templates/debug.php:456, templates/debug.php:493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Secret Key" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "No Secret" + +#: templates/account.php:373, templates/account/partials/site.php:112, templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Trial" + +#: templates/account.php:400, templates/debug.php:533, templates/account/partials/site.php:248 +msgid "License Key" +msgstr "License Key" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "not verified" + +#: templates/account.php:444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Expired" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium version" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Free version" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verify Email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download %s Version" + +#: templates/account.php:541, templates/account.php:749, templates/account/partials/site.php:237, templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Show" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "What is your %s?" + +#: templates/account.php:563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Edit" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Search by address" + +#: templates/account.php:609, templates/debug.php:369 +msgid "Address" +msgstr "Address" + +#: templates/account.php:610 +msgid "License" +msgstr "License" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "License" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Hide" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php:826, templates/account.php:843, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php:841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php:844, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "View details" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons for %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php:13, templates/forms/license-activation.php:209, templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Dismiss" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatic Installation" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Cancel Installation" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Checkout" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Allow & Continue" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Re-send activation email" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Thanks %s!" + +#: templates/connect.php:172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Agree & Activate License" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Thanks for purchasing %s! To get started, please enter your license key:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php:253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "License key" + +#: templates/connect.php:256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Can't find your license key?" + +#: templates/connect.php:315, templates/connect.php:652, templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Skip" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Your Profile Overview" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Name and email address" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Your Site Overview" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP version, PHP info, plugins & themes" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin Notices" + +#: templates/connect.php:359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Updates, announcements, marketing, no spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Current %s Events" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activation, deactivation and uninstall" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php:391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "What permissions are being granted?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Don't have a license key?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Have a license key?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privacy Policy" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Terms of Service" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Sending email" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activating" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php:54, templates/debug.php:250, templates/debug.php:376, templates/debug.php:494 +msgid "Actions" +msgstr "Actions" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Delete All Accounts" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Clear API Cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sync Data From Server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Key" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Value" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK Versions" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK Path" + +#: templates/debug.php:205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Is Active" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php:234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Themes" + +#: templates/debug.php:239, templates/debug.php:371, templates/debug.php:453, templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php:241, templates/debug.php:452 +msgid "Title" +msgstr "Title" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius State" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connected" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blocked" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installs" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php:368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php:433, templates/debug.php:511, templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Delete" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Users" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verified" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activated" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "All Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "All Requests" + +#: templates/debug.php:573, templates/debug.php:602, templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php:574, templates/debug.php:600, templates/debug/logger.php:23 +msgid "Function" +msgstr "Function" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Process ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php:577, templates/debug.php:601, templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php:598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php:603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Update" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Billing" + +#: templates/account/billing.php:38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Business name" + +#: templates/account/billing.php:39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Tax / VAT ID" + +#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Address Line %d" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "City" +msgstr "City" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "Town" +msgstr "Town" + +#: templates/account/billing.php:47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / Postal Code" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Country" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Select Country" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "State" +msgstr "State" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "Province" +msgstr "Province" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Payments" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Date" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Amount" + +#: templates/account/payments.php:38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Invoice" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Length" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Path" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s ago" + +#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Plugins & Themes Sync" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Module Type" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php:165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Email address" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Full name" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account email address" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Add another domain" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domains" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, email, and social media statistics (optional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php:223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Cancel" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Please enter the license key that you received in the email right after the purchase:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Update License" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "There is a new version of %s available." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "New Version Available" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Dismiss" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Send License Key" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Enter the email address you've used for the upgrade below and we will resend you the license key." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:29, templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +msgid "skip" +msgstr "skip" + +#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Unlimited Updates" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s left" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Last license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Cancelled" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "No expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Owner Name" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Owner Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Owner ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Subscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry for the inconvenience and we are here to help if you give us a chance." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contact Support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonymous feedback" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deactivate" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activate %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "If you have a moment, please let us know why you are %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactivating" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "switching" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Submit & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Kindly tell us the reason so we can improve." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Skip & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Click here to use the plugin anonymously" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "You might have missed it, but you don't have to share any data and can just %s the opt-in." diff --git a/freemius/languages/freemius-es_ES 2.mo b/freemius/languages/freemius-es_ES 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..b2145c34636eb589f720bc1d9126174e200f5338 GIT binary patch literal 56182 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

    j;UP?YVNY36p9h-5AvTEH0 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 IUY7R%0B0z;(*OVf literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-es_ES 2.po b/freemius/languages/freemius-es_ES 2.po new file mode 100644 index 0000000..daef057 --- /dev/null +++ b/freemius/languages/freemius-es_ES 2.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Carlos Longarela , 2017-2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Carlos Longarela \n" +"Language: es_ES\n" +"Language-Team: Spanish (Spain) (http://www.transifex.com/freemius/wordpress-sdk/language/es_ES/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK no pudo encontrar el archivo principal del plugin. Por favor contacta a sdk@freemius.com con el error actual." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Error" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "He encontrado un %s mejor" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "¿Cuál es el nombre de %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Es una %stemporal . Sólo estoy depurando un problema" + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Desactivación" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Cambiar tema" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Otra" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Ya no necesito el %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Sólo necesitaba la %s por un corto período" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ha roto mi sitio" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s de repente ha dejado de funcionar" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "No puedo pagarlo durante más tiempo" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "¿Con qué precio te sentirías cómodo pagando?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "No me gusta compartir mi información contigo" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "El %s no funcionaba" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "No entiendo cómo hacerlo funcionar" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s es genial, pero necesito una característica que no soportáis" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "¿Qué característica?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr " El %s no funciona" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Por favor, comparte lo que no funcionó para que podamos arreglarlo para los futuros usuarios..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "No es lo que estaba buscando" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "¿Que has estado buscando?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr " El %s no funciona como esperaba" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "¿Qué esperas?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Debug Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "No sé qué es cURL o cómo instalarlo, ¡ayúdame!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Nos aseguraremos de ponernos en contacto con tu empresa de alojamiento web y resolver el problema. Recibirás un correo electrónico de seguimiento a %s tan pronto tengamos una actualización." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Genial, por favor instala cURL y habilítalo en el archivo php.ini. Además, busca la directiva 'disable_functions' en el archivo php.ini y quita cualquier método que comienza con 'curl_'. Para asegurarte de que se activó con éxito, utiliza 'phpinfo()'. Una vez activado, desactiva el %s y reactívalo de nuevo." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Vamos, adelante" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - sólo desactivar" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "¡Gracias por darnos la oportunidad de arreglarlo! Acabamos de enviar un mensaje a nuestro personal técnico. Nos pondremos en contacto contigo tan pronto como tengamos una actualización de %s. Apreciamos tu paciencia." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s no se puede ejecutar sin %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s no se puede ejecutar sin el plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Error inesperado del API. Pónte en contacto con el autor de %s indicándole el siguiente error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La versión Premium %s ha sido activada con éxito." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Tienes una licencia %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Vaya" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "la prueba gratuita de %s fue cancelada con éxito. Puesto que el complemento es sólo premium se desactivó automáticamente. Si quieres utilizarlo en el futuro, deberás comprar una licencia." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s es un complemento único de premium. Tienes que comprar una licencia primero antes de activar el plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Más información sobre %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Comprar licencia" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Recibirás un correo de activación para %s en tu buzón en %s. Por favor, asegúrate de hacer clic en el botón de activación en ese correo electrónico para %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "comenzar el período de prueba" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "completar la instalación" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Estás a sólo un paso - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Completar la activación de \"%s\" ahora" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Hemos realizado algunas optimizaciones al %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "¡Inscríbite para hacer \"%s\" Mejor!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "La actualización de %s se completó con éxito." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Complemento" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Colección de detalles del sitio no válida." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "No podemos encontrar tu dirección de correo electrónico en el sistema, ¿estás seguro de que es la dirección de correo electrónico correcta?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "No vemos ninguna licencia activa asociada a esa dirección de correo electrónico, ¿estás seguro de que es la dirección de correo electrónico correcta?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "La cuenta está pendiente de activación" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Compra una licencia ahora" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renueva tu licencia ahora" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activación se completó con éxito." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Tu cuenta se ha activado correctamente con el plan %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Tu versión de prueba se ha iniciado con éxito." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "No se puede activar %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Por favor contáctanos con el siguiente mensaje:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Actualizar" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Comenzar el período de prueba" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Precio" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Afiliación" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Cuenta" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contáctanos" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Complementos" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Precio" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Foro de soporte" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Tu email ha sido verificado correctamente - ¡Eres IMPRESIONANTE!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Bien hecho" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Tu complemento %s del plan se actualizó con éxito." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "El complemento %s ha sido comprado correctamente." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Descargar la última versión" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error recibido del servidor:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Parece que uno de los parámetros de autenticación es incorrecto. Actualiza tu clave pública, clave secreta e ID de usuario e inténtelo de nuevo." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Parece que todavía estás en el plan %s. Si actualizaste o cambiaste tu plan, probablemente sea un problema de nuestra parte - lo sentimos." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Período de Prueba Gratuito" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "He actualizado mi cuenta, pero cuando intento sincronizar la licencia, el plan sigue siendo %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Contacta aquí con nosotros" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Tu plan se actualizó con éxito." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Tu plan se cambió correctamente a %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Tu licencia ha caducado. Puedes seguir usando el plan gratuito %s para siempre." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Tu licencia ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Tu licencia ha sido cancelada. Si crees que es un error, ponte en contacto con el servicio de asistencia." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Tu licencia ha caducado. Todavía puedes seguir usando todas las funciones de %s, pero tendrás que renovar tu licencia para seguir recibiendo actualizaciones y soporte." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Tu período de prueba ha caducado. Todavía puedes seguir usando todas nuestras funciones gratuitas." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Tu período de prueba ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Parece que la licencia no se pudo activar." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Tu licencia fue activada correctamente." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Parece que tu sitio actualmente no tiene una licencia activa." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Parece que la desactivación de licencia ha fallado." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Tu licencia fue desactivada correctamente, has vuelto al plan %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Parece que estamos teniendo algún problema temporal con tu cancelación de la suscripción. Vuelve a intentarlo en unos minutos." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Tu suscripción ha sido cancelada correctamente. Tu %s licencia del plan caducará en %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Estás ejecutando %s en modo de prueba." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Ya utilizaste un período de prueba antes." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "El plan %s no existe, por lo tanto, no puedes comenzar un período de prueba." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "El plan %s no admite un período de prueba." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Ninguno de los planes de %s soportan un período de prueba." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Parece que ya no estás en modo de prueba, así que no hay nada que cancelar :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Parece que estamos teniendo algún problema temporal con tu cancelación de prueba. Vuelve a intentarlo en unos minutos." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Tu prueba gratuita de %s fue cancelada con éxito." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La versión %s se ha lanzado." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Por favor descarga %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "la última versión %s aquí" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nuevo" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Parece que tienes la última versión." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "¡Está todo listo!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "El correo de verificación se acaba de enviar a %s. Si no puedes encontrarlo después de 5 min, comprueba tu carpeta de spam." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Sitio dado de alta correctamente." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Increíble" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Agradecemos tu ayuda para mejorar %s y por permitirnos rastrear algunos datos de uso." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "¡Gracias!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "No continuaremos enviando datos de uso de %s en %s a %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Comprueba tu buzón de correo, debes recibir un correo electrónico a través de %s para confirmar el cambio de propiedad. Por razones de seguridad, debes confirmar el cambio dentro de los próximos 15 min. Si no puedes encontrar el correo electrónico, comprueba tu carpeta de correo no deseado." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Gracias por confirmar el cambio de propiedad. Se envió un correo electrónico a %s para su aprobación final." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s es el nuevo dueño de la cuenta." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Felicidades" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Lo sentimos, no podemos completar la actualización de correo electrónico. Ya hay registrado otro usuario con esa dirección de correo electrónico." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Si deseas renunciar a la titularidad de la cuenta de %s a %s haz clic en el botón de cambio de titularidad." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Cambiar propietario" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Se actualizó correctamente tu correo electrónico. Recibirás un correo electrónico con las instrucciones de confirmación en unos momentos." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Por favor, dinos tu nombre completo." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Tu nombre fue actualizado correctamente." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Has actualizado correctamente tu %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Sólo déjanos informarte que la información de complementos de %s se está extrayendo de un servidor externo." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Atención" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "¿Qué te pareció %s hasta ahora? Prueba todas nuestras funciones premium de %s con una prueba gratuita de % d-días." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Sin compromiso por %s días - ¡cancelar en cualquier momento!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "No se necesita tarjeta de crédito" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Comenzar el período de prueba gratuito" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey, ¿sabías que %s tiene un programa de afiliados? ¡Si te gusta %s puedes convertirte en nuestro embajador y ganar dinero!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Saber más" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activar licencia" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Cambiar licencia" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Darse de baja" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Inscribirse" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activar características %s" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Por favor, sigue estos pasos para completar la actualización" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Descargar la última versión %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Cargar y activar la versión descargada" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "¿Cómo subirlo y activarlo?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick aquí %s para elegir los sitios sobre los que te gustaría activar la licencia." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "La instalación automática sólo funciona para usuarios que aceptaron." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Id de módulo no válido." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Versión premium ya activa." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "No tienes una licencia válida para acceder a la versión premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "El plugin es un \"Serviceware\" lo que significa que no tiene una versión de código premium." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Versión del complemento premium ya instalada." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Ver las funciones de pago" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "¡Muchas gracias por utilizar %s y sus complementos!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "¡Muchas gracias por utilizar %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "¡Muchas gracias por utilizar nuestros productos!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s y sus complementos" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Productos" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Si" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "envíame actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%sNO%s me envíes actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Indica si deseas que te contactemos para actualizaciones de seguridad y nuevas funciones, contenido educativo y ofertas ocasionales:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La clave de licencia está vacía." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renovar la licencia" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Comprar licencia" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Hay una %s de %s disponible." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "nueva versión" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Aviso importante de actualización:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Instalando plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "No es posible conectarse al sistema de archivos. Por favor, confirma tus credenciales." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "El paquete de plugin remoto no contiene una carpeta con el Slug deseado y el cambio de nombre no funcionó." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Comprar" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Comenzar mi período gratuito de %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Instalar la actualización gratuita ahora" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Instalar actualización ahora" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Instalar la versión gratuita ahora" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Instalar ahora" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Descargar la última versión gratuita" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Descargar la última" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activar este complemento" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activar versión gratuita" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activar" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Descripción" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Instalación" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Capturas de pantalla" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Registro de cambios" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Valoraciones" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Otras notas" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Características y precios" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Instalar plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Plan %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "El mejor" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensual" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Anual" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Permanente" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Facturado %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Anualmente" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Una vez" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licencia para un único sitio" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licencias ilimitadas" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Hasta %s sitios" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "me" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "año" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Precio" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Guardar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Sin compromiso para %s - cancelar en cualquier momento" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Después de su período gratuito %s, pague sólo %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Detalles" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versión" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Autor" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Última actualización" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "hace %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Necesita la versión de WordPress" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s o mayor" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible hasta" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Descargado" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "% vez" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s veces" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Página del plugin en WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Página web del plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Donar a este plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Calificación media" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "basado en %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s calificación" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s calificaciones" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s estrella" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s estrellas" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Haz clic para ver los comentarios con una valoración de %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Colaboradores" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Atencion" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Este plugin no ha sido probado con tu versión actual de WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Este puglin no ha sido marcado como compatible con tu versión de WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "El complemento de pago se debe implementar en Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "El complemento debe implementarse en WordPress.org o en Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Versión más reciente (%s) instalada" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Versión gratuita más reciente (%s) instalada" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Última versión instalada" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Última versión gratuita instalada" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Bajando tu plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelando la suscripción" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Ten en cuenta que no podremos abaratar los precios desactualizados para renovaciones/nuevas suscripciones después de una cancelación. Si eliges renovar la suscripción manualmente en el futuro, después de un aumento de precio, que generalmente ocurre una vez al año, se te cobrará el precio actualizado." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "La cancelación del período de prueba bloqueará inmediatamente el acceso a todas las funciones premium. ¿Estás seguro?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Todavía puedes disfrutar de todas las funciones de %s pero no tendrás acceso a soporte y actualizaciones de %s." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Una vez que caduque tu licencia todavía puedes utilizar la versión gratuita pero NO tendrás acceso a las funciones de %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activar plan %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renovaciones en %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Caduca en %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sincronizar licencia" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Cancelar período de prueba" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Cambiar Plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Actualizar" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Degradar" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Período de prueba gratuito" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Detalles de la cuenta" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "La eliminación de la cuenta desactivará automáticamente su licencia de plan %s para que pueda utilizarla en otros sitios. Si también desea cancelar los pagos periódicos, haga clic en el botón \"Cancelar\" y, en primer lugar, \"Degradar\" su cuenta. ¿Seguro que deseas continuar con la eliminación?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La eliminación no es temporal. Sólo elimínalo si ya no deseas utilizar este %s más. ¿Estás seguro que desea continuar con la eliminación?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Borrar cuenta" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Desactivar licencia" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "¿Estás seguro que quieres proceder?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Cancelar suscripción" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sincronizar" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nombre" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Correo electrónico" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ID de usuario" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "ID del sitio" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Sin ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Clave pública" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Clave secreta" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Sin clave secreta" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Período de prueba gratuito" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Clave de licencia" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "no verificado" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Caducado" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Versión premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Versión gratuita" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verificar correo electrónico" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Descargar versión %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mostrar" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "¿Cual es tú %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Editar" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sitios" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Buscar por dirección" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Dirección" + +#: templates/account.php:610 +msgid "License" +msgstr "Licencia" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licencia" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Ocultar" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Procesando" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelando %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "período de prueba" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelando %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "suscripción" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Al desactivar tu licencia todas las características premium se bloquearán, pero posibilitará poder activar tu licencia en otro sitio. ¿Estás seguro que quieres continuar?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Ver detalles" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Complementos para %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "No podemos cargar la lista de complementos. Probablemente es un problema por nuestro parte, por favor inténtalo de nuevo en unos minutos." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Activo" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Descartar" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s seg" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Instalación automática" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Una descarga automatizada y la instalación de %s (versión de pago) de %s comenzará en %s. Si quieres hacerlo manualmente - haz clic en el botón de cancelación." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "El proceso de instalación ha comenzado y puede tardar unos minutos en completarse. Por favor, espera hasta que se finalice - no actualices esta página." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Cancelar instalación" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Pagar" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Compatible con PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Permitir y continuar" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Reenviar correo electrónico de activación" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "¡Gracias %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "De acuerdo y activar licencia" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "¡Gracias por comprar %s! Para empezar, escribe tu clave de licencia:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "No te pierdas ninguna actualización importante - acepta para notificaciones de seguridad y de actualizaciones, ofertas y seguimiento de diagnóstico con datos no sensibles con %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "No te pierdas ninguna actualización importante - acepta para notificaciones de seguridad y de actualizaciones y seguimiento de diagnóstico con datos no sensibles con %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "No te pierdas ninguna actualización importante - acepta las notificaciones de seguridad y de actualizaciones, contenido educacional, ofertas y seguimiento de diagnóstico con datos no sensibles con %4$s. ¡Si te saltas esto, no pasa nada! %1$s seguirá funcionando bien." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "No te pierdas ninguna actualización importante - acepta las notificaciones de seguridad y de actualizaciones y seguimiento de diagnóstico con datos no sensibles con %4$s. ¡Si te saltas esto, no pasa nada! %1$s seguirá funcionando bien." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Estamos emocionados de introducir la integración de Freemius a nivel de red." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durante el proceso de actualización hemos detectado%d sitio(s) que aún están pendientes de la activación de licencia." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Si quieres utilizar %s en estos sitios, introduce por favor tu clave de licencia abajo y haz click en el botón de activación." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s características de pago" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternativamente, puedes saltarlo ahora y activar la licencia después, en tu %s página de cuenta a nivel de red." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durante el proceso de actualización detectamos %s sitio(s) en la red que todavía están pendientes de tu atención." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Clave de licencia" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "¿No puedes encontrar tu clave de licencia?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Saltar" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegar a administradores del sitio" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Si haces click, esta decisión será delegada a los administradores de los sitios." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Resumen del perfil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nombre y dirección de correo electrónico" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Resumen del sitio" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL del sitio web, versión de WP, PHP info, plugins y temas" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Avisos de administración" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Actualizaciones, anuncios, marketing, sin spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Eventos de %s actuales" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activación, desactivación y desinstalación" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Boletín" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$s periódicamente enviará datos a %2$s para comprobar las actualizaciones de seguridad, nuevas funcionalidades y verificar la validez de tu licencia." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "¿Qué permisos se otorgan?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "¿No tienes una clave de licencia?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "¿Tienes una clave de licencia?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Política de privacidad" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Acuerdo de licencia" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Términos de servicio" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Enviando correo electrónico" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activando" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contacto" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Apagado" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Encendido" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Depurando" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Acciones" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "¿Está seguro que desea eliminar todos los datos de Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Borrar todas las cuentas" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Borrar caché de la API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Borrar transients de actualizaciones" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sincronizar datos desde el servidor" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrar opciones a la red" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Cargar opción de BD" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Guardar opción en BD" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Clave" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valor" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versiones SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Ruta del SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Ruta del módulo" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Está activo" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temas" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Ruta" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Título" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Estado Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Blog de red" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Usuario de red" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Conectado" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloqueado" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simular período de prueba" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simular actualización de red" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Instalaciones" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sitios" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "ID del blog" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Borrar" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Complementos del módulo %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Usuarios" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificado" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licencias" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID del plugin" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID del plan" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Cuota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activado" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloqueando" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Caducidad" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Log de Debug" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Todos los Tipos" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Todas las peticiones" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Archivo" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Función" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID del proceso" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Mensaje" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filtro" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Descarga" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Tipo" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Página segura HTTPS %s, desde un dominio externo" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Soporte" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "API Freemius" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Peticiones" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Actualizar" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturación" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Nombre de la empresa" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Tax / Núm IVA" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Línea de la dirección %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Ciudad" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Municipio" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Código postal" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "País" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Seleccionar país" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Estado" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincia" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Pagos" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Fecha" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Cantidad" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Factura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Método" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Código" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Longitud" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Ruta" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Cuerpo" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultado" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Inicio" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fin" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "En %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "hace %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "seg" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Sincronizar plugins y temas" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Último" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Crons programados" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Módulo" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Tipo de módulo" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Tipo de cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Siguiente" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Sin caducidad" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Aceptar para hacerse afiliado" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "¡Tu aplicación al programa de afiliación para %s ha sido aceptada! Entra en tu área de afiliado desde: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Gracias por aplicar a nuestro programa de afiliados, revisaremos tu petición durante los próximos 14 días y te volveremos a contactar con información adicional." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Tu cuenta de afiliado ha sido suspendida temporalmente." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Gracias por aplicar a nuestro programa de asociados, infortunadamente, de momento hemos decidido rechazar tu petición. Por favor, prueba de nuevo en 30 días." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Debido a la violación de nuestros términos de afiliados, hemos decidido bloquear temporalmente tu cuenta de afiliación. Si tienes alguna pregunta, por favor contacta nuestro soporte." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "¿Te gusta %s? Conviértete en nuestro embajador y gana dinero ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "¡Envíanos nuevos usuarios a nuestro %s y gana %s de comisión en cada venta satisfactoria que nos hayas referido!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sumario del programa" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s comisión cuando un cliente compra una nueva licencia." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Obtén comisiones por renovaciones automatizadas de las suscripciones." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie después de la primera visita para maximizar las ganancias potenciales." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Comisiones Ilimitadas" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s cantidad mínima a pagar." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Los pagos son en USD y se procesan mensualmente por medio de PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Como aplazamos 30 días para posible devoluciones, sólo pagamos comisiones que son de más de 30 días." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Afiliado" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Dirección de correo electrónico" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nombre completo" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Dirección de correo electrónico de PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "¿Dónde vas a promocionar %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Introduce el dominio de tu sitio web o de otros sitios web donde planeas promocionar %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Añadir otro dominio" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Dominios extra" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Dominios extra desde donde promocionarás el producto." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Métodos de promoción" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Apps móviles " + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Sitio web, correo electrónico y estadísticas de social media (opcional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Siéntete libre de proporcionarnos estadísticas de tu sitio web o social media, p.ej. visitas únicas mensuales, número de suscriptores de correo electrónico, seguidores, etc. (mantendremos esta información confidencial)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "¿Como nos promocionarás?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Por favor, danos detalles de como pretendes promocionar %s (por favor, se lo más específico que puedas)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Cancelar" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Hacerse afiliado" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Por favor, introduce la clave de licencia que recibiste en el correo electrónico al realizar la compra:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Activar licencia" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Darse de baja" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Inscribirse" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "El uso del seguimiento se hace con la intención de mejorar %s. Crear una mejor experiencia de usuario, priorizando nuevas características y cosas mejores. Realmente apreciaríamos que considerases permitirnos continuar con el seguimiento." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Haciendo clic en \"Desistir\", ya no enviaremos los datos de %s a %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Hay una nueva versión de %s disponible." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nueva versión disponible" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Descartar" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Enviar clave de licencia" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Escribe abajo la dirección de correo electrónico que has usado para la actualización y te reenviaremos la clave de licencia." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Desactivar o desinstalar %s deshabilitará automáticamente la licencia, que podrás usar en otro sitio." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "En caso de que NO estés planeando utilizar este %s en este sitio (o en cualquier otro sitio), ¿te gustaría cancelar también %s?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licencia" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancelar %s - No necesito más actualizaciones de características y seguridad, ni soporte para %s porque no pretendo utilizar%s en este, u otro sitio." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "No cancelar %s - Todavía estoy interesado en obtener actualizaciones de características y seguridad, así como poder contactar con soporte." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Una vez que tu licencia caduque no podrás seguir utilizando %s, a no ser que lo actives de nuevo con una licencia premium válida." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "¿Cancelar %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceder" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancelar %s y proceder" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Estás a sólo 1-click de comenzar tu %1$s días de prueba gratuita del plan %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Para el cumplimiento de las directrices de WordPress.org, antes de empezar el período de prueba te pedimos que aceptes con tu usuario e información no sensible del sitio web, permitiendo a %s enviar datos periódicamente a %s para comprobar si hay actualizaciones de versión y para validar la versión de prueba." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activar licencia en todos los sitios de la red" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Aplicar en todos los sitios de la red" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Aplicar licencia en todos los sitios pendientes" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Aplicar en todos los sitios pendientes" + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "permitir" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegar" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "saltar" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click para ver la captura de pantalla a tamaño completo %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Actualizaciones Ilimitadas" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "quedan %s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Última licencia" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Cancelado" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Sin caducidad" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nombre del propietario" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Correo electrónico del propietario" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID del propietario" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Suscripción" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Disculpa las molestias y estamos aquí para ayudarte si nos das una oportunidad." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contactar soporte" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Comentarios anónimos" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Desactivar" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activar %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Comentarios rápidos" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Si tienes un momento, por favor, dinos por qué estás %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "desactivando" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "cambiando" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Enviar y %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Por favor, dínos la razón para que podamos mejorar." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Si - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Saltar y %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Haz click aquí para utilizar el plugin de forma anónima" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Es posible que te lo hayas perdido, pero no tienes que compartir ningún dato y puedes solo aceptar %s." diff --git a/freemius/languages/freemius-fr_FR 2.mo b/freemius/languages/freemius-fr_FR 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..7c9b950264c0eee0c99ed78ec616efb7fa0a6394 GIT binary patch literal 57448 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-fr_FR 2.po b/freemius/languages/freemius-fr_FR 2.po new file mode 100644 index 0000000..697bab1 --- /dev/null +++ b/freemius/languages/freemius-fr_FR 2.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Boris Colombier , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Boris Colombier \n" +"Language: fr_FR\n" +"Language-Team: French (France) (http://www.transifex.com/freemius/wordpress-sdk/language/fr_FR/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Le SDK Freemius ne trouve pas le fichier principal du plugin. Merci de contacter sdk@freemius.com en indiquant l'erreur." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Erreur" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "J'ai trouvé un meilleur %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Quel est le nom du %s ?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "C'est une %s temporaire. Je corrige un problème." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Désactivation" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Changement de Thème" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Autre" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Je n'ai plus besoin du %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Je n'ai besoin de %s que pour une courte période" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "Le %s a cassé mon site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "Le %s a soudainement arrêté de fonctionner" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Je ne peux plus payer pour ça" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Quel prix seriez-vous prêt à payer ?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Je ne veux pas partager mes informations avec vous" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "Le %s n'a pas fonctionné" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Je ne comprends pas comment le faire fonctionner" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "Le %s est bien mais j'ai besoin de fonctionnalités spécifiques que vous ne proposez pas" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Quelle fonctionnalité ?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "Le %s ne fonctionne pas" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Merci de nous indiquer ce qui ne fonctionne pas afin que nous puissions le corriger pour les futurs utilisateurs..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Ce n'est pas ce que je recherche" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Que recherchez-vous ?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "Le %s n'a pas fonctionné comme prévu" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "À quoi vous attendiez-vous ?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Débuggage Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Je ne sais pas ce qu'est cURL ou comment l'installer, aidez moi !" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Nous allons contacter votre hébergeur afin de résoudre le problème. Vous recevrez un email à propos de %s dès que nous aurons des nouvelles." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Parfait, merci d'installer cURL et de l'activer dans votre fichier php.ini. De plus, recherchez l'instruction 'disable_functions' de votre fichier php.ini et désactivez les commandes commençant par 'curl_'. Pour vérifier la bonne activation, utilisez la fonction 'phpinfo()'. Une fois activé, désactivez le %s et réactivez le à nouveau." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Oui - allez-y" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Non - désactivation seulement" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oups" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Merci de nous permettre de corriger ça. Un message vient d'être envoyé à notre service technique. Nous reviendrons vers vous dès que nous aurons des nouvelles à propos de %s." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ne peut pas fonctionner sans %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s ne peut pas fonctionner sans le plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Une erreur est survenue dans l'API. Merci de contacter l'auteur du %s en lui indiquant l'erreur." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La version premium de %s a été activée avec succès." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Génial" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Vous avez une license pour %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Youpi" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "La période d'essai du %s a bien été annulé. L'add-on a été désactivé car il ne fonctionne qu'avec la version premium. Si vous souhaitez l'utiliser ultérieurement, vous devrez acheter une licence." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%sest un add-on pour la version premium. Vous devez acheter une licence avant d'activer le plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Plus d'informations à propos de %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Acheter une licence" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Vous devriez recevoir un email d'activation pour %s sur votre boîte %s. Merci de cliquer sur le bouton d'activation dans l'email pour %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "commencer la période d'essai" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "compléter l'installation" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Il ne reste qu'une étape - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Compléter \"%s\" Activer Maintenant" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Nous avons fait quelques modifications au %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Inscrivez-vous pour améliorer \"%s\" !" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "La mise à jour du %s s'est terminée avec succès " + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Thème" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Récupération des détails du site non valide." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "Nous ne trouvons pas votre adresse mail dans notre système, êtes-vous qu'il s'agit de la bonne adresse ?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Nous ne trouvons aucune licence active associée avec cette adresse email, êtes-vous qu'il s'agit de la bonne adresse ?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Compte en cours d'activation." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Acheter une licence maintenant" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renouvelez votre licence maintenant" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s pour permettre les mises à jour de sécurité et de fonctionnalités de la version %s, et le support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "L'activation de %s s'est terminée avec succès." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Votre compte a été activé avec succès avec la formule %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Votre période d'essai a bien démarré." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Impossible d'activer %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Merci de nous contacter avec le message suivant :" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Mise à jour" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Essai gratuit" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Tarifs" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Compte" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contactez Nous" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Tarifs" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Forum de Support" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Votre email a été vérifié avec succès - vous êtes FORMIDABLE !" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Directement" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Votre Add-on %s a bien été mis à jour." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "L'Add-on %s a bien été acheté." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Télécharger la dernière version" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Une erreur a été reçu depuis le serveur :" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Il semble que l'un des paramètres d'authentification soit faux. Veuillez mettre à jour votre Public Key, votre Secret Key ainsi que vote User ID et essayez à nouveau." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Il semble que vous soyez encore sur la formule %s. Si vous avez mis à jour ou changer votre formule, le problème est probablement de votre côté - désolé." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Période d'essai" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "J'ai mis à jour mon compte mais quand j'essaie de synchroniser la licence, la formule est toujours %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Merci de nous contacter ici" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Votre formule a bien été mise à jour." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Votre formule a bien été modifié vers %s. " + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Votre licence a expiré. Vous pouvez toujours utiliser la version gratuite indéfiniment." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Votre licence a expiré.%1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Votre licence a été annulé. Si vous pensez qu'il s'agit d'une erreur, merci de contacter le support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Votre licence a expiré. Vous pouvez toujours utiliser les fonctionnalités %s mais vous devrez renouveler votre licence pour recevoir les mises à jour et une assistance." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Votre période d'essai gratuite est terminée. Vous pouvez continuer à utiliser toutes nos fonctionnalités gratuites." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Votre période d'essai gratuite est terminée. %1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Il semble que la licence ne puisse être activée." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Votre licence a bien été activée." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Il semble que votre site n'ait pas de licence active." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Il semble que la désactivation de la licence a échoué." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Votre licence a bien été désactivé, vous utilisez à présent la formule %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Il semble que nous ayons un problème temporaire avec l'annulation de votre abonnement. Merci de réessayer dans quelques minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Votre abonnement a bien été annulé. Votre licence de la formule %s expirera dans %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Vous utilisez déjà le %s en période d'essai. " + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Vous avez déjà utilisé la période d'essai." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "La formule %s n'existe pas, il n'est pas possible de commencer une période d'essai." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "La formule %s ne propose pas de période d'essai." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Aucune formule du %s ne propose de période d'essai." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Il semble que vous ne soyez plus en période d'essai donc il n'y a rien à annuler :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Il semble que nous ayons un problème temporaire pour annuler votre période d'essai. Merci de réessayer dans quelques minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Votre période d'essai %s a bien été annulé." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La version %s vient d'être publiée." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Merci de télécharger %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "la dernière version de %s ici" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nouveau" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Il semble que vous ayez la dernière version." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Vous êtes tout bon !" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Un email de vérification vient d'être envoyé sur %s. Si vous ne le recevez pas d'ici 5 minutes, merci de vérifier dans vos spams." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site ajouté avec succès." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Formidable" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Nous vous remercions de votre aide pour améliorer le %s en nous permettant de recevoir des informations concernant son usage." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Merci !" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Nous n'enverrons plus d'information d'utilisation de %s sur %s à %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Merci de vérifier votre messagerie, vous devriez recevoir un email via %s pour confirmer le changement de propriétaire. Pour des raisons de sécurité, vous devez confirmer le changement dans les prochaines 15 minutes. Vérifiez vos spams si vous ne recevez pas le message." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Merci pour la confirmation du changement de propriétaire. Un email vient d'être envoyé à %s pour la validation finale." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s est le nouveau propriétaire du compte." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Félicitations" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Désolé, nous ne pouvons pas mettre à jour l'email. Il existe déjà un autre utilisateur avec cette adresse." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Si vous voulez transférer la propriété du compte de %s à %s cliquez sur le bouton Changement De Propriétaire" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Changement De Propriétaire" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Votre email a été mis à jour. Vous allez recevoir un message avec les instructions de confirmation." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Merci d'indiquer vos prénom et nom." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Votre nom a été mis à jour." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Votre %s a bien été mis à jour." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Sachez que les informations de l'add-ons de %s sont issus d'un serveur externe." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Avertissement" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Que pensez-vous de %s ? Testez nos %s fonctionnalités premium avec %d jours d'essai gratuit." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Pas d'engagement durant %s jours - annuler quand vous voulez !" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Pas besoin de carte bancaire" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Commencer l'essai gratuit" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Dites, savez-vous que %s propose un système de affiliation ? Si vous aimez le %s vous pouvez devenir notre ambassadeur et gagner de l'argent !" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "En savoir plus" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activer la licence" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Changer la licence" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Désinscription" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Inscription" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activer les fonctionnalités %s" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Merci de suivre ces étapes pour finaliser la mise à jour" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Télécharger la dernière version %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Téléverser et activer la version téléchargée" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Comment téléverser et activer ?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sCliquez ici %s pour choisir les sites sur lesquels vous souhaitez activer la licence." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "L'installation automatique ne fonctionne que pour les utilisateurs qui se sont inscrits." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "ID du module non valide." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Version premium déjà active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Vous n'avez pas de licence valide pour accéder à la version premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Le plugin est un \"Serviceware\" ce qui veut dire qu'il n'a pas de version premium de code." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "La version premium de l'add-on est déjà installée." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Voir les fonctionnalités payantes" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Merci beaucoup d'utiliser %s et ses add-ons !" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Merci beaucoup d'utiliser %s !" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à améliorer le %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Merci beaucoup d'utiliser nos produits !" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à les améliorer." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s et ses add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Produits" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Oui" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "envoyez moi des mises à jour de sécurité et des fonctionnalités, du contenu instructif et des offres." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Non" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "ne %sPAS%s m'envoyer de mises à jour de sécurité ou de fonctionnalités, ni de contenu instructif, ni d'offre." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Merci de nous indiquer si vous souhaitez que nous vous contactions pour les mises à jour de sécurité et de fonctionnalités, du contenu instructif et des offres spéciales :" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La clé de licence est vide." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renouvelez votre licence" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Acheter une licence" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Il y a une %s de %s disponible." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "Nouvelle version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Information importante de mise à jour :" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installation du plugin : %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Impossible de se connecter au système de fichiers. Merci de confirmer vos autorisations." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Le package du plugin à télécharger ne contient pas de dossier avec le bon slug et iln'a pas été possible de le renommer." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Acheter" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Commencer ma %s gratuite" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installer la dernière mise à jour gratuite maintenant" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installer la mise à jour maintenant" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer la version gratuite maintenant" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installer maintenant" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Télécharger la dernière version gratuite" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Télécharger la dernière version" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activer cet add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activez la version gratuite" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activer" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Description" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installation" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Captures d'écran" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Commentaires" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Autres Informations" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Fonctionnalités & Tarifs" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Installation du Plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Formule %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Best" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensuel" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annuel" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "À vie" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s Facturé" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annuel" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Une fois" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licence 1 site" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licences sites illimités" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Jusqu'à %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mois" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "année" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Tarif" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Économisez %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Pas d'engagement durant %s - annuler quand vous voulez" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Après vos %s gratuits, payez seulement %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Détails" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Version" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Auteur" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Dernière mise à jour" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "Il y a %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Version de WordPress requise" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s ou plus" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible jusqu'à" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Téléchargé" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s fois" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s fois" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Page WordPress.org du plugin" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Site Web du plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Faire une donation pour ce plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Note moyenne" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "Basé sur %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s notation" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%snotations " + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s étoile" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s étoiles" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Cliquez pour voir les avis avec une notation de %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributeurs" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Attention" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Ce plugin n'a pas été testé avec votre actuelle version de WordPress" + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Ce plugin n'a pas été indiqué comme étant compatible avec votre version actuelle de WordPress" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Les add-ons payant doivent être déposés sur Freemius" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Les add-ons doivent être déposés sur WordPress.org ou Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nouvelle Version (%s) Installée" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "La nouvelle version gratuite ( %s ) a été installé" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Dernière Version Installée" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "La dernière version gratuite a été installé" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Rétrograder votre formule" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Annuler votre abonnement" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Veuillez noter que nous ne serons pas en mesure de garantir le maintien des prix actuels pour les renouvellements/nouveaux abonnements après une annulation. Si vous choisissez de renouveler l'abonnement manuellement à l'avenir, après une augmentation de prix, qui se produit généralement une fois par an, le prix mis à jour vous sera facturé." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Annuler la période d'essai va immédiatement bloquer les fonctionnalités premium. Souhaitez-vous continuer ?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Vous pouvez toujours profiter de toutes les fonctionnalités de %s mais vous n'aurez plus accès aux mises à jour de sécurité ou de fonctionnalités de %s, ni au support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Une fois la licence expirée vous pourrez toujours utiliser la version gratuite mais vous n'aurez PAS accès aux fonctionnalités de %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activer la formule %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Renouvellements automatique dans %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Expire dans %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Synchroniser la licence" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annuler la période d'essai" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Changer de formule" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Mise à jour" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Rétrograder" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratuit" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Formule" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Essai gratuit" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Détails du compte" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Supprimer le compte désactivera automatiquement la licence de votre formule %s afin que vous puissiez l'utiliser sur d'autres sites. Si vous voulez aussi annuler le paiement récurrent, cliquez sur le bouton \"Annuler\" et commencez par \"Rétrograder\" votre compte. Êtes-vous sûr de vouloir poursuivre la suppression ? " + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La suppression est permanente. Ne faites cette suppression que si vous ne souhaitez plus utiliser le %s. Êtes-vous sûr de vouloir poursuivre la suppression ?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Supprimer le compte" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Désactiver la licence" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Êtes-vous de vouloir continuer ?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annuler l'abonnement" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Synchroniser" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nom" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "ID manquant" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Clef publique" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Clef secrête" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Clef secrète manquante" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Période d'essai" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Clef de licence" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "Non vérifié" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Expiré" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Version premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Version gratuite" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Vérifier l'email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Télécharger la version %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Afficher" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Quel est votre %s ?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Éditer" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Recherche par adresse" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adresse" + +#: templates/account.php:610 +msgid "License" +msgstr "Licence" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Formule" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licence" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Cacher" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Traitement en cours" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Annulation de %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "essai" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Annulation de %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "abonnement" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Désactiver la licence bloquera toutes les fonctionnalités premium mais vous permettra d'activer la licence sur un autre site. Êtes-vous sûr de vouloir continuer ?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Voir les détails" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons pour %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Nous n'avons pas pu charger la liste des add-ons. C'est probablement une difficulté de notre côté, merci de d'essayer à nouveau dans quelques minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Fermer" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Installation automatique" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Un téléchargement et une installation automatique de %s (version premium) de %s va commencer dans %s. Si vous voulez le faire manuellement, cliquez sur le bouton d'annulation maintenant." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "L'installation a commencé et peut prendre quelques minutes pour se finir. Merci de patienter jusqu'à ce qu'elle soit terminée - veuillez ne pas rafraichir cette page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuler l'installation" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Paiement" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Compatible PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Autoriser & Continuer" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Renvoyer l'email d'activation" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Merci %s !" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Valider & Activer la licence" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Merci d'avoir acheté %s ! Pour commencer, veuillez indiquer votre clef de licence :" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités, de contenu instructif, d'offres ainsi que le suivi d'activité non sensible avec %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ne manquez jamais une mise à jour importante - optez pour nos notifications de mises à jour de sécurité et de fonctionnalités, et un suivi diagnostique non sensible avec %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités, de contenu instructif, d'offres ainsi que le suivi d'activité non sensible avec %4$s. Dans le cas contraire, pas de problème ! %1$s fonctionnera parfaitement aussi." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Ne ratez jamais une mise à jour importante - acceptez nos notifications de mises à jour de sécurité et de fonctionnalités ainsi que le suivi d'activité non sensible avec %4$s. Dans le cas contraire, pas de problème ! %1$s fonctionnera parfaitement aussi." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Nous sommes impatient de vous présenter l'intégration Freemius au niveau réseau." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durant le processus de mise à jour nous avons détecté %d site(s) toujours en attente d'activation de la licence." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Si vous voulez utiliser le %s sur ces sites, merci d'indiquer votre clé de licence ci-dessous et de cliquer sur le bouton d'activation." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "Fonctionnalités payantes de %s" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Éventuellement, vous pouvez l'ignorer pour l'instant et activer la licence plus tard, sur votre page de compte du réseau %s." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durant le processus de mise à jour nous avons détecté %s site(s) dans le réseau que vous devez vérifier." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Clef de licence" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Vous ne trouvez pas votre clef de licence ?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Passer" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Déléguer aux administrateurs du site" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Si vous cliquez, cette décision sera déléguée aux administrateurs des sites." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Résumé de votre profil" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nom et adresse email" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Résumé de votre site" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP version, PHP info, plugins & themes" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Notifications Administrateur" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Mises à jour, annonces, marketing, pas de spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Évènements du %s actuel" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activation, désactivation et désintallation" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "Le %1$s va régulièrement envoyer des données à %2$s pour vérifier les mises à jour de sécurité et de fonctionnalités ainsi que pour vérifier la validité de votre licence." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Quelles autorisations sont accordées ?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Vous n'avez pas de clef de licence ?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Vous avez une clef de licence ?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Politique de confidentialité" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Contrat de licence" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Conditions générales de service" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Email en cours d'envoi" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activation en cours" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debuggage" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Actions" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Êtes-vous sûr de vouloir supprimer toutes les données de Freemius ?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Supprimer tous les comptes" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Vider le cache API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Vider les transients de mise à jour" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synchronisation des données depuis le serveur" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrer les options vers le réseau" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Chargement des options de la base de données" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Mise en place des options de la base de données" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Clef" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valeur" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versions du SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Chemin d'accès du SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Chemin d'accès du module" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Est actif" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Thèmes" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titre" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "État de Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Réseau de Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Réseau d'Utilisateur" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connecté" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloqué" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simuler la promotion d'essai" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuler la mise à jour du réseau" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installations" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Supprimer" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons du module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Utilisateurs" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Vérifié" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licences" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID du plugin" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID de la formule" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activé" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloquant" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Tous les types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Toutes les demandes" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Fichier" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Fonction" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID du processus" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Téléchargement" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Page %s sécurisée HTTPS, s'exécutant sur un domaine externe" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "API Freemius" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Demandes" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Mise à jour" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturation" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Raison sociale" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Code TVA" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresse ligne %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Ville" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Ville" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Code postal" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Pays" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Choisir le pays" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "État" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Région" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Paiements" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Date" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Montant" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Facture" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Méthode" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Longueur" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Chemin" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Résultat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Début" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fin" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Dans %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "Il y a %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synchronisation des plugin et des thèmes" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Dernier" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Crons programmés" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Type de module" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Type de Cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Suivant" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Sans expiration" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Postuler pour devenir un affilié" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Votre dossier d'affiliation pour %s a été accepté ! Identifiez-vous dans votre espace affilié sur : %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Merci d'avoir postulé à notre programme d'affiliation, nous regarderons votre dossier durant les 14 prochains jours et nous reviendrons vers vous avec d'autres informations." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Votre compte affilié a été suspendu temporairement." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Merci d'avoir postulé à notre programme d'affiliation, malheureusement, nous avons décidé pour le moment de décliner votre dossier. Merci d'essayer à nouveau d'ici 30 jours." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Suite à une violation de nos conditions d'affiliation, nous avons décidé de bloquer temporairement votre compte d'affilié. Si vous avez la moindre question, merci de contacter le support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Vous aimez %s ? Devenez notre ambassadeur et gagnez du cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Parrainez des nouveaux clients pour notre %s et gagnez une commission de %s sur chaque vente réussie que vous affiliez." + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sommaire du programme" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "Commission de %s quand un client achète une nouvelle licence." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Obtenez des commissions pour les renouvellements automatiques d'abonnement." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "Cookie de tracking de %s après la première visite pour maximiser les potentiels de gain." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Commissions illimitées." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "Montant de paiement minimum %s." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Les paiements se font en Dollars US et sont effectués mensuellement via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Comme nous bloquons sur 30 jours pour les remboursements éventuels, seules sont payées les commissions de plus de 30 jours." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliation" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Adresse email" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nom complet" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Adresse email du compte PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Où allez-vous faire la promotion du %s ? " + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Indiquez l'adresse de votre site ou d'autres sites sur lesquels vous pensez faire la promotion du %s" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Ajouter une autre adresse" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Adresses supplémentaires" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Adresses supplémentaires depuis lesquelles vous ferez la promotion du produit." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Méthodes de promotion" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Réseaux sociaux (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Applications mobiles" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Statistiques du site web, de l'adresse email et des réseaux sociaux (optionnel)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "N'hésitez pas à indiquer des statistiques pertinentes concernant votre site ou vos réseaux sociaux telles que le nombre de visiteurs mensuel, le nombre d'abonnés, de followers, etc... (C'est informations resteront confidentielles)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Comment allez-vous faire de la promotion ?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Merci d'indiquer en détail comment vous allez faire la promotion du %s (en étant aussi précis que possible)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuler" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Devenir un affilié" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Merci d'indiquer le code de licence que vous avez reçu par email juste après l'achat :" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Mettre à jour la licence" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Désinscription" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Inscription" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Le suivi d'utilisation de %s nous permet de l'améliorer. Apporter une meilleure expérience pour l'utilisateur, définir quelles seront les nouvelles fonctionnalités, ce genre de choses. Aussi nous vous serions reconnaissant si vous acceptiez de nous permettre de continuer à récupérer des informations." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "En cliquant \"Désincription\", nous n'enverrons plus d'informations de %s à %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Il y a une nouvelle version disponible de %s. " + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%s pour accéder aux mises à jour de sécurité et de fonctionnalités de la version %s, et au support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Une nouvelle version est disponible" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Fermer" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Envoyer le code de la licence" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Indiquez ci-dessous l'adresse email que vous avez utilisez pour la mise à jour et nous allons vous renvoyer le code de la licence." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Désactiver ou désinstaller le %s désactivera automatiquement la licence, que vous pourrez utiliser sur un autre site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "Dans le cas où vous n'avez PAS l'intention d'utiliser ce %s sur ce site (ou tout autre site) - voulez-vous aussi annuler le %s ?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licence" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Annuler %s - Je n'ai plus besoin de mises à jour de sécurité et de fonctionnalités, ni de support pour %s parce que je n'ai pas l'intention d'utiliser le %s sur ce site, ou tout autre site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Ne pas annuler %s - Je veux toujours recevoir les mises à jour de sécurité et de fonctionnalités, ainsi que d'être en mesure de contacter le support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Une fois votre licence expirée, vous ne pourrez plus utiliser le %s, sauf si vous l'activez à nouveau avec une licence premium valide." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Annuler %s ?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Poursuivre" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Annuler %s et poursuivre" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Vous êtes à 1 clic de commencer votre période d'essai gratuite de %1$s jours de la formule %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Pour être en accord avec les directives de WordPress.org, avant que nous commencions la période d'essai, nous vous demandons de nous permettre de récupérer votre nom d'utilisateur et des informations non sensibles du site afin de permettre au %s de communiquer avec %s pour vérifier les mises à jour et valider votre période d'essai." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activer la licence sur tous les sites du réseau." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Effectuer sur tous les sites dans le réseau." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activer la licence sur tous les sites en attente." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Activer sur tous les sites en attente." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "autoriser" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "déléguer" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "passer" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Cliquez pour voir la capture d'écran %d en pleine taille" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Mises à jour illimitées" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s restante(s)" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Dernière licence" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annulé" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Pas d'expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nom du propriétaire" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Email du propriétaire" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID du propriétaire" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Inscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Désolé pour le dérangement et nous sommes là pour vous aider si vous nous le permettez." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contacter l'Assistance" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Commentaire anonyme" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Désactiver" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activer %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Commentaires rapides" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Si vous avez un instant, merci de nous indiquer pourquoi %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "Désactivation" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "Changement" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Envoyer & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "S'il vous plait, dites nous pourquoi afin que nous puissions nous améliorer." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Oui - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Passer & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Cliquer ici pour utiliser le plugin anonymement" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Peut-être que cela vous a échappé mais vous n'êtes pas obligé de partager la moindre information et vous pouvez juste %s l'enregistrement." diff --git a/freemius/languages/freemius-he_IL 2.mo b/freemius/languages/freemius-he_IL 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..7e3b6ef13b3ccc7bc6b0a157871877fd87643d05 GIT binary patch literal 56093 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 XBC7g&w_0VJEFb= literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-he_IL 2.po b/freemius/languages/freemius-he_IL 2.po new file mode 100644 index 0000000..ea2e7e0 --- /dev/null +++ b/freemius/languages/freemius-he_IL 2.po @@ -0,0 +1,2509 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Rami Yushuvaev , 2017 +# Vova Feldman , 2017-2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: he_IL\n" +"Language-Team: Hebrew (Israel) (http://www.transifex.com/freemius/wordpress-sdk/language/he_IL/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "שגי××”" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "מצ×תי %s יותר טוב" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "What's the %s's name?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "It's a temporary %s. I'm just debugging an issue." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "די×קטיבציה" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "החלפת תֵמָה" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "×חר" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "×”%s הרס לי ×ת ×”×תר" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "×”%s הפסיק פת××•× ×œ×¢×‘×•×“" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "×× ×™ ×œ× ×™×›×•×œ/×” להמשיך ×œ×©×œ× ×¢×œ ×–×”" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "מה המחיר שכן תרגיש\\×™ בנוח לשל×?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "×× ×™ ×œ× ×והב ×ת הרעיון של שיתוף מידע ×יתכ×" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "×”%s ×œ× ×¢×‘×“" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "×œ× ×”×¦×œ×—×ª×™ להבין ×יך ×œ×’×¨×•× ×œ×–×” לעבוד" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "××™×–×” פיטצ'ר?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "×”%s ×œ× ×¢×•×‘×“" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "×× × ×©×ª×¤\\×™ מה ×œ× ×¢×‘×“ כדי שנוכל לתקן ×–×ת עבור ×ž×©×ª×ž×©×™× ×¢×ª×™×“×™×™×..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "חיפשתי משהו ×חר" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "מה חיפשת?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "×”%s ×œ× ×¢×‘×“ כמצופה" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "למה ציפית?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "ניפוי תקלות פרימיוס" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "×ין לי מושג מה ×–×” cURL ×ו ×יך להתקין ×ותו - ×שמח לעזרה!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "כן - בצעו ×ת מה שצריך" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "×œ× - פשוט כבה" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "×ופס" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ×œ× ×™×›×•×œ לעבוד ×œ×œ× %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "ההרחבה %s ××™× ×” יכולה לפעול ×œ×œ× ×”×ª×•×¡×£." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "יש" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "יש לך רישיון %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "יששש" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "מידע נוסף ×ודות %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "קניית רישיון" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "התחל תקופת ניסיון" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "×”×©×œ× ×”×ª×§× ×”" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "You are just one step away - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "×”×©×œ× ×”×¤×¢×œ×ª \"%s\" עכשיו" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-On" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "תוסף" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "תבנית" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account is pending activation." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "הפעלת %s הושלמה בהצלחה." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "חשבונך הופעל בהצלחה ×¢× ×—×‘×™×œ×ª %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "הניסיון שלך הופעל בהצלחה." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "×œ× × ×™×ª×Ÿ להפעיל ×ת %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "×× × ×¦×•×¨ ×יתנו קשר יחד ×¢× ×”×”×•×“×¢×” הב××”:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "שדרג" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "התחל תקופת ניסיון" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "מחירון" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "×פילי×ציה" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "חשבון" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "יצירת קשר" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Add-Ons" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "מחירון" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "×¤×•×¨×•× ×ª×ž×™×›×”" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Your email has been successfully verified - you are AWESOME!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "מעולה" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "חבילת ההרחבה %s שודרגה בהצלחה." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "ההרחבה %s נרכשה בהצלחה." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "הורד ×ת הגרסה ×”×חרונה" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "הוחזרה שגי××” מהשרת:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "×ממ" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "ניסיון" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "שידרגתי ×ת החשבון שלי ×בל כש×× ×™ מנסה לבצע סנכרון לרישיון החבילה נש×רת %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "×× × ×¦×•×¨ ×יתנו קשר ×›×ן" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "החבילה שודרגה בהצלחה." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "החבילה עודכנה בהצלחה ×ל %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "רשיונך בוטל. ×× ×œ×“×¢×ª×š זו טעות, × × ×œ×™×¦×•×¨ קשר ×¢× ×”×ª×ž×™×›×”." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "תקופת הניסיון שלך הסתיימה. הפיטצ'×¨×™× ×”×—×™× ××ž×™×™× ×¢×“×™×™×Ÿ × ×™×ª× ×™× ×œ×©×™×ž×•×©." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "נר××” ×©×œ× × ×™×ª×Ÿ להפעיל ×ת הרישיון." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "הרישיון הופעל בהצלחה." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "נר××” ל×תר עדיין ×ין רישיון פעיל." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "נר××” שניתוק הרישיון נכשל." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "רישיונך נותק בהצלחה, חזרת לחבילת %s" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "×וקיי" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "הניסיון כבר נוצל בעבר." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "החבילה %s ××™× ×” קיימת, לכן, ×œ× × ×™×ª×Ÿ להתחיל תקופת ניסיון." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "תוכנית %s ××™× ×” תומכת בתקופת ניסיון." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "נר××” שיש תקלה זמנית המונעת ×ת ביטול הניסיון. ×× × × ×¡×• שוב בעוד כמה דקות." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "תקופת הניסיון החינמית של %s בוטלה בהצלחה." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "גרסה %s הושקה." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "× × ×œ×”×•×¨×™×“ ×ת %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "גרסת ×”-%s ×”×חרונה ×›×ן" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "חדש" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "נר××” שיש לך ×ת הגרסה ×”×חרונה." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "×ת\\×” מסודר!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "×דיר" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "תודה רבה!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "תודה על ×ישור ביצוע החלפת הבעלות. הרגע נשלח מייל ל-%s כדי לקבל ×ישור סופי." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s הינו ×”×‘×¢×œ×™× ×”×—×“ של חשבון ×–×”." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "מזל טוב" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "עדכון בעלות" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "כתובת הדו×ל שלך עודכנה בהצלחה. הודעת ×ישור ×מורה להתקבל בדו×ל שלך ×‘×¨×’×¢×™× ×”×§×¨×•×‘×™×." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "× × ×œ×ž×œ× ×ת שמך המל×." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "שמך עודכן בהצלחה." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "עידכנת בהצלחה ×ת ×”%s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "לתשמות לבך" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "×”×™×™" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "×œ×œ× ×”×ª×—×™×™×‘×•×ª ל-%s ימין - בטלו בכל רגע!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "×œ× × ×“×¨×© כרטיס ×שר××™" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "התחלת ניסיון ×—×™× ×" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Learn more" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "הפעלת רישיון" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "שינוי רישיון" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "× × ×œ×‘×¦×¢ ×ת ×”×¦×¢×“×™× ×”×‘××™× ×œ×”×©×œ×ž×ª השידרוג" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "הורד\\×™ ×ת גרסת ×”-%s העדכנית" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "העלה\\×™ והפעיל\\×™ ×ת הגרסה שהורדת" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "×יך להעלות ולהפעיל?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "מזהה המודול ×œ× ×ª×§× ×™." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "הגרסה ×‘×ª×©×œ×•× ×›×‘×¨ פעילה." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "×ין ברשותך רישיון בר תוקף לשימוש בגרסת הפרימיו×." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "צפה בפיטצ'×¨×™× ×©×‘×ª×©×œ×•×" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "×נו ×ž×•×“×™× ×œ×š על היותך כמשתמש של %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "×נו ×ž×•×“×™× ×œ×š על השימוש ×‘×ž×•×¦×¨×™× ×©×œ× ×•!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "מוצרי×" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "כן" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "תשלחו לי עדכוני ×בטחה ופיטצ'רי×, תוכן חינוכי, ומידע ×ודות מבצעי×." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "ל×" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%s×ל%2$s תשלחו לי עדכוני ×בטחה, פיטצ'רי×, תוכן חינוכי, ומידע על מבצעי×." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "מפתח הרישיון ריק." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "חידוש רישיון" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installing plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "רכישה" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "התחל ×ת %s הניסיון שלי" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "התקן עדכון גרסה ×—×™× ×מית עכשיו" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "התקן עדכון במיידי" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "התקן גרסה ×—×™× ×מית עכשיו" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "התקן עכשיו" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "הורד גרסה ×חרונה" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "הפעל ×ת ההרחבה" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "הפעלת גירסה ×—×™× ×מית" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "הפעלה" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "תי×ור" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "התקנה" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "ש×לות נפוצות" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "צילומי מסך" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "לוג שינויי×" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "ביקורות" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "היערות נוספות" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "פיטצ'×¨×™× ×•×ž×—×™×¨×™×" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "התקנת תוסף" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "חבילה %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "×”×›×™ טוב" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "חודשי" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "שנתי" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "לכל ×”×—×™×™×" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "מחוייב על בסיס %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "שנתי" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "×¤×¢× ×חת" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "רשיון ל×תר ×חד" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "רשיונות ×œ×œ× ×”×’×‘×œ×”" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "עד %s ×תרי×" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "חודשי×" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "שנה" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "מחיר" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "שמירת %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "פרטי×" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "גרסה" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Author" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "עודכן ל×חרונה" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "לפני %s" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Requires WordPress Version" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s ומעלה" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Downloaded" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "×¤×¢× %s" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s פעמי×" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plugin Page" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "עמוד התוסף" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "×ª×¨×•× ×œ×ª×•×¡×£" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "דירוג ממוצע" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "מבוסס על %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "דרוג %s" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s דרוגי×" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "כוכב %s" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s כוכבי×" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "תורמי×" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Warning" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "תוסף ×–×” ×œ× × ×‘×“×§ ×¢× ×’×¨×¡×ª הוורדפרס שלך." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "התוסף ×œ× ×¡×•×ž×Ÿ כתו×× ×œ×’×¨×¡×ª הוורדפרס שלך." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "גרסה חדשה (%s) הותקנה" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "הגרסה ×”×חרונה הותקנה" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "גרסה ×—×™× ×מית עדכנית הותקנה" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "ביטול הניסיון ×™×—×¡×•× ×ž×™×™×“ ×ת הפיטצ'×¨×™× ×©×”×™× × ×‘×ª×©×œ×•×. ×”×× ×‘×¨×¦×•× ×š בכל ×–×ת להמשיך?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "הפעל חבילה %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "עדכן ×וטומטית בעוד %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "פג תוקף בעוד %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "סינכרן רישיון" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "ביט" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "שינוי חבילה" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "שדרג" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "שנמך" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "×—×™× ×" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "חבילה" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "ניסיון ×—×™× ×" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "פרטי חשבון" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "מחיקת חשבון" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "שיחרור רישיון" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "×”×× ×ת/×” בטוח רוצה להמשיך?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "בטל מנוי" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "סינכרון" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "ש×" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "דו×\"ל" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "מזהה משתמש" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "מזהה" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "מזהה ×תר" + +#: templates/account.php:332 +msgid "No ID" +msgstr "×ין מזהה" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "מפתח פומבי" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "מפתח סודי" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "×ין מפתח סודי" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "ניסיון" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "License Key" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "×œ× ×ž×ומת" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "פג תוקף" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "גירסת פרימיו×" + +#: templates/account.php:504 +msgid "Free version" +msgstr "גירסה ×—×™× ×מית" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "×מת כתובת דו×\"ל" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "הורד גרסת %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "הצג" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "מה ×”%s שלך?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "ערוך" + +#: templates/account.php:588 +msgid "Sites" +msgstr "×תרי×" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "חפש לפי כתובת" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "כתובת" + +#: templates/account.php:610 +msgid "License" +msgstr "רישיון" + +#: templates/account.php:611 +msgid "Plan" +msgstr "חבילה" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "רישיון" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "הסתר" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "ביטול הרישיון ×™×—×¡×•× ×ת כל הפיטצ'×¨×™× ×©×‘×ª×©×œ×•× ×ך ×™×פשר להפעיל ×ת הרישיון על ×תר ×חר. ×”×× ×ª×¨×¦×• להמשיך בכל ×–×ת?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "×¤×¨×˜×™× × ×•×¡×¤×™×" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "הרחבות עבור %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "סגירה" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s שניות" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "התקנה ×וטומטית" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "הורדה והתקנה ×וטומטית של %s (גרסה בתשלו×) מ-%2$s תתחיל בעוד %3$s. ×× ×‘×¨×¦×•× ×š לבצע ×ת ההתקנה ידנית - לחץ על כפתור הביטול עכשיו." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "תהליך ההתקנה התחיל ויכול לקחת מספר דקות לסיו×. ×× × ×”×ž×ª×™× ×• בסבלנות עד ×œ×¡×™×•× ×ž×‘×œ×™ לרענן ×ת הדפדפן." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "בטל התקנה" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Checkout" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "עומד בתקן PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "×”×™×™ %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "×פשר\\×™ והמשכ\\×™" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "שליחה חוזרת של מייל ×”×קטיבציה" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "תודה %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "הסכמה והפעלת רישיון" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Thanks for purchasing %s! To get started, please enter your license key:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "מפתח רישיון" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "×”×× ×ינך ×ž×•×¦× ×ת מפתח הרישיון?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "דלג" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "×”×צלה למנהלי ×”×תרי×" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "×¤×¨×˜×™× ×›×œ×œ×™×™× ×¢×œ הפרופיל" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "×©× ×•×›×ª×•×‘×ª דו\"×ל" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "×¤×¨×˜×™× ×›×œ×œ×™×™× ×¢×œ ×”×תר" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "כתובת ×תר, גרסת וורדפרס, פרטי PHP, ×ª×•×¡×¤×™× ×•×ª×‘× ×™×•×ª" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "התר×ות מנהל" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "עדכוני×, הכרזות, הודעות שיווקיות, ×œ×œ× ×“×•×ר זבל" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Current %s Events" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "הפעלה, כיבוי והסרה" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "ניוסלטר" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "מהן ההרש×ות המוענקות?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "×”×× ×ין ברשותך מפתח רישיון?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "×”×× ×‘×¨×©×•×ª×š רישיון?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "מדיניות פרטיות" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "תנ××™ השירות" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "שולח דו×\"ל" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "מפעיל" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "כבוי" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "דלוק" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "דיבוג" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "פעולות" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "מחיקת כל החשבונות" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "ניקוי מטמון ×”-API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "סנכרון מידע מהשרת" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Key" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Value" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "גרס×ות SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "×ž×™×§×•× SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "×”×× ×¤×¢×™×œ" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "תוספי×" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "תבניות" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "מזהה כתובת" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "כותרת" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "מצב פרימיוס" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "משתמש רשת" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "מחובר" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "חסו×" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "סמלוץ עדכון לרשת" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s התקנות" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "×תרי×" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "מזהה בלוג" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "מחק" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "משתמשי×" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "מ×ומת" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Activated" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "תפוגה" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "כל הסוגי×" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "כל הבקשות" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "קובץ" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "פונקציה" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Process ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "הודעה" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "פילטר" + +#: templates/debug.php:587 +msgid "Download" +msgstr "הורדה" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "סוג" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "תמיכה" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "עדכן" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "בילינג" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "×©× ×¢×¡×§" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "×—.פ." + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "כתובת %s" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "עיר" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "כפר" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "מיקוד / ×ª× ×“×•×ר" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "מדינה" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "בחר מדינה" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "מחוז/מדינה" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "פרובינציה" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "תשלומי×" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "ת×ריך" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "סכו×" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "חשבונית" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Length" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "נתיב" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "בעוד %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "לפני %s" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Plugins & Themes Sync" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Total" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "מודול" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "סוג מודול" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "כתובת דו×\"ל" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Full name" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account email address" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Add another domain" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domains" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, email, and social media statistics (optional)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "בטל" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "×× × ×”×–×Ÿ ×ת הרישיון שקיבלת לתיבת הדו×ל שלך ל×חר השלמת הרכישה." + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "עדכון רישיון" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "יש גרסה חדשה עבור ×”%s." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "יש גרסה חדשה" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "סגירה" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "שליחת מפתח רישיון" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "הזן ×ת כתובת הדו×ל ש×יתה שידרגת כדי לקבל ×ת הרישיון שוב." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "הפעלת רישיון על כל ×”××ª×¨×™× ×‘×¨×©×ª." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "×™×™×©×•× ×¢×œ כל ×”××ª×¨×™× ×‘×¨×©×ª." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "הפעלת רישיון על כל ×”××ª×¨×™× ×”×ª×œ×•×™×™× ×•×”×¢×•×ž×“×™×." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "×™×™×©×•× ×¢×œ כל ×”××ª×¨×™× ×”×ª×œ×•×™×™× ×•×”×¢×•×ž×“×™×." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "×פשר" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "×”×צל" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "דלג" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "×¢×“×›×•× ×™× ×œ×œ× ×”×’×‘×œ×”" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "שרת לוק×לי" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "נש×רו %s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "רישיון ×חרון" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "בוטל" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "×œ×œ× ×ª×¤×•×’×”" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "×©× ×”×‘×¢×œ×™×" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "מייל הבעלי×" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "מזהה הבעלי×" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "מנוי" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "×ž×¦×˜×¢×¨×™× ×¢×œ חוסר הנעימות, ×נחנו ×›×ן כדי לעזור ×× ×ª×פשר\\×™ ×–×ת." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "צור קשר" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "פידבק ×נונימי" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "כיבוי" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activate %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "If you have a moment, please let us know why you are %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactivating" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "switching" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Submit & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "×× × ×©×ª×£ ×ת הסיבה כדי שנוכל להשתפר." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "דלג ו%s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Click here to use the plugin anonymously" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "×ולי פספסת ×ת ×–×” ×בל ×ינך חייב\\ת לשתף כל מידע ×יתנו, ביכולתך %s על שיתוף המידע." diff --git a/freemius/languages/freemius-hu_HU 2.mo b/freemius/languages/freemius-hu_HU 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..c8812867950b79ce6ac81cc2484bc797cb5e84a2 GIT binary patch literal 54168 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

    -Dbzm2Vd)dhH3%Uk5&c^ey1A;9a2P-e*De?~C9Q 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 F{|DK*5qSUr literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-hu_HU 2.po b/freemius/languages/freemius-hu_HU 2.po new file mode 100644 index 0000000..59d73fc --- /dev/null +++ b/freemius/languages/freemius-hu_HU 2.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Peter Ambrus, 2018-2019 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: hu_HU\n" +"Language-Team: Hungarian (Hungary) (http://www.transifex.com/freemius/wordpress-sdk/language/hu_HU/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Hiba" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Jobb %st találtam" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Mi a %s neve?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Ez csak egy ideiglenes %s. Egy hibát kell megoldanom." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deaktiválás" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Sablon váltás" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Egyéb" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "I no longer need the %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "I only needed the %s for a short period" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "The %s broke my site" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "The %s suddenly stopped working" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Nem tudom tovább fizetni" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Mi lenne az elfogadható ár, amit tudnál fizetni?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Nem szeretném megosztani veletek az információt" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "A %s nem működött" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Nem értettem, hogy kell használni" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "The %s is great, but I need specific feature that you don't support" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Melyik funkcióra van szükséged?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "A(z) %s nem működik" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Ha elmondod mi nem működött, ki tudjuk javítani a leendÅ‘ felhasználók számára..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Nem ezt kerestem" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Pontosan mit kerestél?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "A %s nem az elvárásoknak megfelelÅ‘en működött" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Mire számítottál?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "I don't know what is cURL or how to install it, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Igen - tedd a dolgod" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nem - csak deaktiválom" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Hoppá" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s cannot run without %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s cannot run without the plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Unexpected API error. Please contact the %s's author with the following error." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s version was successfully activated." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Fantasztikus" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "You have a %s license." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Juhuuu" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is a premium only add-on. You have to purchase a license first before activating the plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "More information about %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Licensz vásárlása" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Küldtünk egy aktivációs emailt a(z) %s szoftverünkhöz a következÅ‘ email címre: %s. Kérlek kattints a levélben található aktivációs linkre, hogy %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "próbaidÅ‘ indítása" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "befejezd a telepítést" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Már csak egy lépés van hátra - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "\"%s\" aktiválásának a befejezése most" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We made a few tweaks to the %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "The upgrade of %s was successfully completed." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "KiegészítÅ‘" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "BÅ‘vítmény" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Sablon" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We couldn't find your email address in the system, are you sure it's the right address?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "We can't see any active licenses associated with that email address, are you sure it's the right address?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "A fiók aktiválása függÅ‘ben." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Vásárolj licenszet most" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Licensz kulcs megújítása" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activation was successfully completed." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "A fiókodat sikeresen aktiváltuk a következÅ‘ csomaggal: %s" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "A próbaidÅ‘szakodat sikeresen aktiváltuk." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Couldn't activate %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Please contact us with the following message:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "ElÅ‘fizetés frissítése" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "PróbaidÅ‘ indítása" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Ãrak" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliation" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Fiók" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Kapcsolat" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "KiegészítÅ‘k" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Ãrak" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Támogató fórum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Az email címedet sikerült ellenÅ‘rizni - ez nagyszerű!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Right on" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Your %s Add-on plan was successfully upgraded." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on was successfully purchased." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Töltsd le a legfrissebb verziót" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Error received from the server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "PróbaidÅ‘" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "I have upgraded my account but when I try to Sync the License, the plan remains %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Please contact us here" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Your plan was successfully upgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Your plan was successfully changed to %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Your license has expired. You can still continue using the free %s forever." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Your license has been cancelled. If you think it's a mistake, please contact support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "It looks like the license could not be activated." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Your license was successfully activated." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "It looks like your site currently doesn't have an active license." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Úgy tűnik a licensz deaktiválása nem sikerült." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "A licenszedet sikeresen deaktiváltuk, az aktuális csomagod: %s" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "Rendben" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "You are already running the %s in a trial mode." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "You already utilized a trial before." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s do not exist, therefore, can't start a trial." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s does not support a trial period." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "None of the %s's plans supports a trial period." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "It looks like you are not in trial mode anymore so there's nothing to cancel :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Your %s free trial was successfully cancelled." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Version %s was released." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Please download %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "the latest %s version here" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Új" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Seems like you got the latest release." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Minden rendben!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Nagyszerű" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We appreciate your help in making the %s better by letting us track some usage data." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Köszönjük!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Thanks for confirming the ownership change. An email was just sent to %s for final approval." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is the new owner of the account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Gratulálunk" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we could not complete the email update. Another user with the same email is already registered." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Tulajdonos módosítása" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Kérlek add meg a teljes neved!" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "A neved sikeresen frissítettük." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "You have successfully updated your %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Just letting you know that the add-ons information of %s is being pulled from an external server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Figyelem" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Üdv" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "How do you like %s so far? Test all our %s premium features with a %d-day free trial." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "No commitment for %s days - cancel anytime!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Bankkártya megadása nem kötelezÅ‘" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start free trial" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "BÅ‘vebben" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Licensz aktiválása" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Licensz módosítása" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Leiratkozás" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Feliratkozás" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Please follow these steps to complete the upgrade" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download the latest %s version" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload and activate the downloaded version" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "How to upload and activate?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Auto installation only works for opted-in users." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Invalid module ID." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium version already active." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "You do not have a valid license to access the premium version." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plugin is a \"Serviceware\" which means it does not have a premium code version." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on version already installed." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "FizetÅ‘s funkciók megtekintése" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Termékek" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Igen" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "kérek biztonsági és funkcionális frissítéseket, használati ismertetÅ‘ket és ajánlatokat." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nem" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "A licensz kulcs üres." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Licensz megújítása" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Licensz vásárlása" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "új verzió" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "BÅ‘vítmény telepítése: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Unable to connect to the filesystem. Please confirm your credentials." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "The remote plugin package does not contain a folder with the desired slug and renaming did not work." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Vásárlás" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start my free %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Frissítés telepítése most" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Telepítés most" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Latest" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activate this add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Ingyenes verzió aktiválása" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Aktiválás" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Leírás" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Telepítés" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "GYIK" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "KépernyÅ‘fotók" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Változtatások" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Vélemények" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Egyéb megjegyzések" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Funkciók & Ãrak" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "BÅ‘vítmény telepítése" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s csomag" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Legjobb" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Havi" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Éves" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Örök" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s számlázás" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Éves" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Egyszeri" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Egy weboldalas licensz" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Korlátlan licensz" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Up to %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "hó" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "év" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Ãr" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "%s mentése" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "No commitment for %s - cancel anytime" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "After your free %s, pay as little as %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Részletek" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Verzió" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "SzerzÅ‘" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Utolsó frissítés" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s ago" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "A következÅ‘ WordPress verzió szükséges:" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s or higher" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible up to" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Letöltések száma:" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org bÅ‘vítmény oldal" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "BÅ‘vítmény oldala" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "BÅ‘vítmény támogatása" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Ãtlagos értékelés" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "based on %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s rating" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s ratings" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s star" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stars" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Click to see reviews that provided a rating of %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "KözreműködÅ‘k" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Figyelmeztetés" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "This plugin has not been tested with your current version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "This plugin has not been marked as compatible with your version of WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Paid add-on must be deployed to Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on must be deployed to WordPress.org or Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Newer Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Legfrissebb verzió telepítve" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Legfrissebb ingyenes verzió telepítve" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancelling the trial will immediately block access to all premium features. Are you sure?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Once your license expires you can still use the Free version but you will NOT have access to the %s features." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "%s csomag aktiválása" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto renews in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "HátralévÅ‘ idÅ‘: %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Licensz szinkronizálása" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "PróbaidÅ‘ törlése" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Csomag módosítása" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Váltás nagyobb csomagra" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Váltás kisebb csomagra" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Ingyenes" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Csomag" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Ingyenes próbaidÅ‘" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Fiók információk" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Fiók törlése" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Licensz deaktiválása" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Are you sure you want to proceed?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "ElÅ‘fizetés törlése" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Szinkronizálás" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Név" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Felhasználó ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Weboldal ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Nincs ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Publikus kulcs" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Titkos kulcs" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Nincs titkos kulcs" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "PróbaidÅ‘" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licensz kulcs" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "nem ellenÅ‘rzött" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Lejárt" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Prémium verzió" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Ingyenes verzió" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Email ellenÅ‘rzése" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "%s verzió letöltése" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mutasd" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Mi a te %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Szerkesztés" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Weboldalak" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Keresés cím alapján" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Cím" + +#: templates/account.php:610 +msgid "License" +msgstr "Licensz" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Csomag" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licensz" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Elrejt" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "próbaidÅ‘" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "elÅ‘fizetés" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "A licensz deaktiválása után a prémium funkciók használata nem elérhetÅ‘, de így tudod másik weboldalon aktiválni ugyanezt a licenszt. Folytatod a deaktiválást?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Részletek megtekintése" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add Ons for %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Mégsem" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatikus telepítés" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Telepítés törlése" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Pénztár" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Üdv %s!" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Engedélyezés és folytatás" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Aktivációs email újraküldése" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Köszönjük %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Licensz elfogadása és aktiválása" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Köszönjük, hogy megvásároltad a %s szoftverünket! A folytatáshoz most meg kell adnod a licensz kulcsot, amit a vásárlás után kaptál emailben:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licensz kulcs" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Nem találod a licensz kulcsod?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Ugrás" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Fiókod áttekintése" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Név és email cím" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Weboldalad adatainak áttekintése" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Weboldal címe, WP verzió, PHP információk, bÅ‘vítmények és sablonok" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin értesítések" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Frissítések, közlemények, marketing, de semmi SPAM!" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Aktuális %s események" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Aktiválás, deaktiválás és kikapcsolás" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Hírlevél" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "A %1$s idÅ‘közönként adatot küld a %2$s weboldalnak, hogy ellenÅ‘rizze a biztonsági és funkcionális frissítéseket, valamint ellenÅ‘rzi az érvényes licensz kulcsot." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Milyen jogosultágok lesznek engedélyezve?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Nincs még licensz kulcsod?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Van licensz kulcsod?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Adatkezelési tájékoztató" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Licensz szerzÅ‘dés" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Szolgáltatási feltételek" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Email küldése" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Aktiválás" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Kapcsolat" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Off" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "On" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Események" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Are you sure you want to delete all Freemius data?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Minden fiók törlése" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Clear API Cache" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Adatok szinkronizálása a szerverrÅ‘l" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Load DB Option" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Set DB Option" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Kulcs" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Érték" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK verziók" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK útvonal" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Path" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Aktív" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "BÅ‘vítmények" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Sablonok" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Cím" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius State" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connected" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Blocked" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installs" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Weboldalak" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Törlés" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Add Ons of module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Felhasználók" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "EllenÅ‘rzött" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenses" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "BÅ‘vítmény ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Csomag ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Sikeres aktiválás" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Blocking" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Expiration" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "All Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "All Requests" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Function" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Művelet ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Message" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Secure HTTPS %s page, running from an external domain" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Support" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Requests" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Frissítés" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Számlázás" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Cégnév" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Közösségi adószám" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Cím %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Város" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Town" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Irányítószám" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Ország" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Válaszz országot" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Megye" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Province" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Fizetési módok" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Dátum" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Mennyiség" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Számla" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Method" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Kód" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Hossz" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Path" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Result" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "End" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s ago" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "BÅ‘vítmények és sablonok szinkronizálása" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Összesen" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Last" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Scheduled Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Module Type" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Next" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non-expiring" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Apply to become an affiliate" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Your affiliation account was temporarily suspended." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Like the %s? Become our ambassador and earn cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Refer new customers to our %s and earn %s commission on each successful sale you refer!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Program Summary" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commission when a customer purchases a new license." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Get commission for automated subscription renewals." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie after the first visit to maximize earnings potential." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Unlimited commissions." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum payout amount." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Payouts are in USD and processed monthly via PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Email cím" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Teljes név" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal fiók email címe" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Where are you going to promote the %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Enter the domain of your website or other websites from where you plan to promote the %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Másik domain hozzáadása" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "További domainek" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domains where you will be marketing the product from." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotion methods" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobile apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Weboldal, email és közösségi média statisztikák (opcionális)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "How will you promote us?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Please provide details on how you intend to promote %s (please be as specific as possible)." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Mégsem" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Become an affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Kérlek add meg a licensz kulcsot, amit emailben kaptál a vásárlásod után:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Licensz frissítése" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Leiratkozás" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Feliratkozás" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "A(z) %s új verziója érhetÅ‘ el." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Új verzió érhetÅ‘ el" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Mégsem" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Licensz kulcs küldése" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Add meg az email címet, amit a vásárlás során használtál és újraküldjük a licensz kulcsot." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "A(z) %s deaktiválása vagy törlése automatikusan törli az oldalhoz tartozó licenszed is, amit így másik weboldalon tudsz újra aktiválni." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licensz" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Prémium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "ugrás" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Click to view full-size screenshot %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Korlátlan frissítés" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s left" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Last license" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Törölve" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "No expiration" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Tulajdonos neve" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Tulajdonos email címe" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Tulajdonos ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "ElÅ‘fizetés" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry for the inconvenience and we are here to help if you give us a chance." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Ãrás az ügyfélszolgálatra" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Névtelen visszajelzés" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deaktiválás" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "%s aktiválása" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Gyors visszajelzés" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Kérlek mondd el, miért %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deaktiválod" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "váltasz" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Küldés & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Ha elmondod az okát, tudunk fejlÅ‘dni." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Yes - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Kihagyás & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Kattints ide, ha névtelenül szeretnéd használni a bÅ‘vítményt" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "You might have missed it, but you don't have to share any data and can just %s the opt-in." diff --git a/freemius/languages/freemius-it_IT 2.mo b/freemius/languages/freemius-it_IT 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..5edddb795223916bf43737ad0470aaf13633b009 GIT binary patch literal 55345 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 b{$byZ4iSNGl|zsRhw~re0$U;CxZwW-A|tXl literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-it_IT 2.po b/freemius/languages/freemius-it_IT 2.po new file mode 100644 index 0000000..0ca2650 --- /dev/null +++ b/freemius/languages/freemius-it_IT 2.po @@ -0,0 +1,2512 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Alessandro Pelly Benassi , 2016 +# Daniele Scasciafratte Mte90 , 2015-2018 +# Dario Curvino , 2018 +# Alessandro Pelly Benassi , 2016-2017 +# Vova Feldman , 2015-2016 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Daniele Scasciafratte Mte90 \n" +"Language: it_IT\n" +"Language-Team: Italian (Italy) (http://www.transifex.com/freemius/wordpress-sdk/language/it_IT/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "L'SDK di Freemius non è riuscito a trovare il file principale del plugin. Per favore contatta sdk@freemius.com riportando l'errore." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Errore" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Ho trovato un migliore %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Qual è il nome di %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "È una %s temporanea. Sto solo cercando di risolvere un problema." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Disattivazione" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Cambio tema" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Altro" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Non ho più bisogno di %s" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Ho avuto bisogno di %s per un breve periodo" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ha rotto il mio sito" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s ha improvvisamente smesso di funzionare" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Non posso piú pagarlo" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Che prezzo ritieni opportuno pagare?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Non voglio condividere i miei dati con te" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s non funziona" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Non capisco come farlo funzionare" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s è ottimo ma ho bisogno di una funzionalità specifica non supportata" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Quale funzionalitá?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s non funziona" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Condividi cosa non ha funzionato in modo da migliorare il prodotto per gli utenti futuri..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Non é quello che stavo cercando" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Che cosa stai cercando?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s non ha funzionato come mi aspettavo" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Che cosa ti aspettavi?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Debug Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Non ho idea di cosa sia cURL o come installarlo, aiutami!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Contatteremo il tuo hosting e risolveremo il problema. Riceverai un' email a %s non appena ci saranno aggiornamenti." + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Installa cURL e abilitalo nel file file php.ini. Inoltre cerca per il parametro 'disable_functions' nel tuo file php.ini e rimuovi ogni metodo disattivato che inizia con 'curl_'. Per verificare che tutti sia attivato usa 'phpinfo()'. Una volta attivato, disattiva 1%s e riattivalo di nuovo." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Sì - fai pure" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "No - disattiva e basta" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Ops" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Grazie per averci dato la possibilità di risolvere il problema! È stato appena inviato un messaggio al nostro staff tecnico. Ti risponderemo non appena avremo un aggiornamento riguardante %s. Grazie per la tua pazienza." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s non può funzionare senza %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s non può funzionare senza il plugin." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Errore API inaspettato. Contatta l'autore di %s con il seguente errore." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "La versione 1%s Permium è stata attivata con successo." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Forte" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Hai la licenza %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Evvai" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "Il periodo di prova gratuito %s è stato annullato con successo. Siccome l'add-on è premium, è stato disattivato automaticamente. Se vorrai usarlo in futuro, dovrai comprare una licenza." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s è un add-on premium. Devi comprare una licenza prima di poter attivare il plugin." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Ulteriori informazioni su %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Acquista licenza" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Dovresti ricevere un'email di attivazione di %s all'indirizzo %s. Assicurati di fare clic sul pulsante di attivazione nell'email per %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "completa l'installazione" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Sei a un passo dalla fine - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Completa l'attivazione di \"%s\" ora" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Abbiamo fatto alcune migliore a %s,%s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "L'aggiornamento di %s è stato completato con successo." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Add-on" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plugin" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Tema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "Non siamo riusciti a trovare il tuo indirizzo email nel sistema, sei sicuro che sia l'indirizzo giusto?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Non siamo riusciti a trovare alcuna licenza attiva associata al tuo indirizzo email, sei sicuro che sia l'indirizzo giusto?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account in attesa di attivazione." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Compra una licenza ora" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Rinnova la tua licenza ora" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s è stato attivato con successo." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Il tuo account è stato attivato correttamente con il piano %s." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "La versione di prova è stata avviata correttamente." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Non é stato possibile attivare %s." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Contattaci con il seguente messaggio:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Aggiornamento" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Prezzi" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliazione" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contattaci" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Addon" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Prezzi" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Forum di supporto" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Il tuo indirizzo email è stato verificato con successo - SEI UN GRANDE!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Sì" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Il piano del tuo add-on %s è stato aggiornato con successo." + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "L' add-on %s è stato acquistato con successo." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Scarica l'ultima versione" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Errore ricevuto dal server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Sembra che uno dei parametri di autenticazione sia sbagliato. Aggiorna la tua chiave pubblica, Secret Key & User ID e riprova." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Uhm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Sembra che tu sia ancora usando il piano %s. Se hai effettuato un upgrade o cambiato il piano, è probabile che ci sia un problema nei nostri sistemi." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Prova gratuita" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Ho aggiornato il mio account, ma quando cerco di sincronizzare la licenza, il piano rimane %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Contattaci qui" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Il piano è stato aggiornato con successo." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Il piano è stato cambiato con successo a %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "La tua licenza è scaduta. Puoi continuare ad usare la versione gratuita %s per sempre." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "La tua licenza è scaduta. %1$saggiorna ora %2$sper continuare ad utilizzare %3$s senza interruzioni." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "La tua licenza è stata cancellata. Se credi sia un errore, per favore contatta il supporto." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "La licenza è scaduta. È comunque possibile continuare a utilizzare tutte le funzionalità di %s, ma sarà necessario rinnovare la licenza per continuare a ricevere gli aggiornamenti ed il supporto." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "La tua versione di prova gratuita è scaduta. Puoi continuare ad usare tutte le funzionalità gratuite." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "La tua versione prova è scaduta.%1$s aggiorna ora %2$s per continuare ad usare %3$s senza interruzioni." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Sembra che la licenza non possa essere attivata." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "La tua licenza è stata attivata correttamente." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Sembra che il tuo sito non disponga di alcuna licenza attiva." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Sembra che la disattivazione della licenza non sia riuscita." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "La tua licenza é stata disattivata con successo, sei tornato al piano %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "OK" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Stai già usando %s in modalità prova." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Hai già utilizzato una prova gratuita in passato." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Il piano %s non esiste, per questo motivo non è possibile iniziare il periodo di prova." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Il piano %s non supporta il periodo di prova." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Nessuno dei piani di %ssupporta il periodo di prova." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Sembra che tu non stia più usando la prova gratuita, quindi non c'è niente che tu debba annullare :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Stiamo avendo qualche problema temporaneo con l'annullamento del periodo di prova. Riprova tra qualche minuto." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Il tuo periodo di prova gratuito %s è stato annullato con successo." + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "La versione %s é stata rilasciata." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "Scarica %s." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "l'ultima versione %s é quì" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nuovo" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Sembra che tu abbia la versione più recente." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Sei fantastico!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "L'email di verifica è stata inviata a %s. Se dopo 5 minuti non è ancora arrivata, per favore controlla nella tua casella di posta indesiderata." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Sito accettato con successo." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Fantastico" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Ti ringraziamo per averci concesso di tracciare alcuni dati di utilizzo al fine di migliorare %s." + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Grazie!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "Non possiamo più inviare i dati di utilizzo di %ssu %sa %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Verifica di aver ricevuto l'email da %s per confermare il cambiamento del proprietario. Per ragioni di sicurezza devi confermare il cambiamento entro 15 minuti. Se non trovi l'email controlla nella posta indesiderata." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Grazie per aver confermato il cambiamento del proprietario. Un' email è stata appena inviata a %s per la conferma finale." + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s è il nuovo proprietario dell'account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Congratulazioni" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Siamo spiacenti, non siamo riusciti a completare l'aggiornamento via email. Un altro utente con lo stesso indirizzo email è già registrato." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Puoi abbandonare la proprietà dell'account %s a %scliccando il pulsante Cambia proprietario." + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Cambia Proprietario" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Il tuo indirizzo email è stato aggiornato correttamente. Riceverai un'email con le istruzioni di conferma in pochi istanti." + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Per favore inserisci il tuo nome completo." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Il tuo nome è stato aggiornato correttamente." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Hai aggiornato con successo il tuo %s." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Le informazioni sugli add-on di %s vengono scaricate da un server esterno." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Attenzione" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hey" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Come sta andando con %s? Prova tutte le funzionalità premium di %s con una prova gratuita di %d giorni." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Nessun impegno per %s giorni - puoi annullare in qualsiasi momento!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Nessuna carta di credito richiesta" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Inizia il periodo di prova gratuito" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Ciao, sai che %s ha il programma di affiliazione? Se ti piace %s puoi diventare un nostro ambasciatore e guadagnare denaro!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Scopri altro" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Attiva licenza" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Cambia licenza" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Cancella iscrizione" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Iscriviti" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Segui i passi seguenti per completare l'aggiornamento" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Scarica l'ultima versione di %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Carica e attiva la versione scaricata" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Come faccio a caricare ed attivare?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClicca qui%s per scegliere i siti dove vuoi attivare la licenza." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "L'installazione automatica funziona solo per gli utenti che hanno dato il consenso." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "ID modulo non valida." + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Versione Premium già attiva." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Non disponi di una licenza valida per accedere alla versione Premium." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Il plugin è un \"Serviceware\", quindi non dispone di una versione del codice Premium." + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Versione Premium dell'add-on già installata." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Vedi funzionalità a pagamento" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Grazie per utilizzare %se i suoi addon!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Grazie per utilizzare %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Hai già accettato il tracciamento d'uso, ci aiuterà a migliorare %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Grazie per utilizzare i nostri prodotti!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Hai già accettato il tracciamento d'uso che ci aiuta a migliorare." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%se i suoi addon" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Prodotti" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Si" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "inviami aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "%snon %s mi invierà aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Facci sapere se vuoi essere contattato per aggiornamenti di sicurezza e di funzionalità, contenuti formativi e offerte occasionali:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "La chiave licenza è vuota." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Rinnova licenza" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installazione plugin: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Impossibile accedere al filesystem. Conferma le tue credenziali." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Il pacchetto remoto del plugin non contiene una cartella con lo slug desiderato e la rinominazione non ha funzionato." + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Acquisto" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Inizia la mia %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installa l'ultima versione gratuita" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installa l'aggiornamento ora" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installa la versione gratuita ora" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installa ora" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Scarica l'ultima versione gratuita" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Scarica l'ultima versione" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Attivare questo addon" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Attiva versione gratuita" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Attiva" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Descrizione" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installazione" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Screenshot" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Changelog" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Recensioni" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Altre note" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Caratteristiche & prezzi" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Installazione del plugin" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "Piano %s" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Migliore" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Mensilmente" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Annuale" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Tutta la vita" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Fatturato %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Annualmente" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Una volta" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Licenza per sito singolo" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Licenze illimitate" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Fino a %s siti" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mese" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "anno" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Prezzo" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Risparmia %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Nessun impegno con %s - cancella quando vuoi" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Dopo il tuo %s gratuito, paghi solamente %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Dettagli" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versione" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Autore" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Ultimo aggiornamento" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s fa" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Richiede la versione di WordPress" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s o superiore" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatibile fino a" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Scaricato" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "% volta" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s volte" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Pagina dei plugin di WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Homepage del plugin" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Fai una donazione a questo plugin" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Valutazione media" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "basato su %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s valutazione" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s valutazioni" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s stella" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s stelle" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Fai clic per vedere le recensioni che hanno fornito una valutazione di %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Contributori" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Avviso" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Questo plugin non è stato testato con la versione corrente di WordPress." + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Questo plugin non è stato segnato come compatibile con la tua versione di WordPress." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Gli add-on a pagamento devono essere distribuiti da Freemius." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "L'add-on dev'essere distribuito da WordPress.org o Freemius." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Versione più recente (%s) installata" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nuova versione gratuita (%s) installata" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Versione più recente installata" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Ultima versione gratuita installata" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Cancellando il periodo di prova gratuito bloccherai immediatamente l'accesso a tutte le funzionalità premium. Vuoi continuare?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Quando la tua licenza scadrà, potrai comunque continuare a usare la versione gratuita, ma NON avrai accesso alle funzionalità %s." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Attivare il piano %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Rinnovo automatico in %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Scade in %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sincronizza la licenza" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Annulla prova gratuita" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Cambia piano" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Aggiornamento" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratuito" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Piano" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Prova gratuita" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Dettagli dell'account" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "L'eliminazione dell'account disattiva automaticamente la tua licenza del piano %s quindi è possibile utilizzarlo su altri siti. Se si desidera anche terminare i pagamenti ricorrenti, fare clic sul pulsante \"Annulla\" ed effettuare il \"Downgrade\" del tuo account. Sei sicuro di voler continuare con l'eliminazione?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "La cancellazione non è temporanea. Cancella solamente se non vuoi più utilizzare %s. Sei sicuro di voler cancellare questi dati?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Elimina Account" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Disattiva licenza" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Sei sicuro di voler procedere?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Annulla sottoscrizione" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sincronizza" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Nome" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ID utente" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "ID del sito" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Nessun ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Chiave pubblica" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Chiave segreta" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Nessuna chiave" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Prova gratuita" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Chiave della licenza" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "non verificato" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Scaduto" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Versione premium" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Versione gratuita" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verifica email" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Scarica la versione %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Mostra" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Qual è il tuo %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Modifica" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Siti" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Cerca per indirizzo" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Indirizzo" + +#: templates/account.php:610 +msgid "License" +msgstr "Licenza" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Piano" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licenza" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Nascondi" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Disattiva la tua licenza bloccando tutte le funzionalità premium ma potrai attivare la licenza su un altro sito. Sei sicuro di voler continuare?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Visualizza dettagli" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add-on per %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Non siamo riusciti a caricare la lista degli add-on. Si tratta probabilmente di un problema nel nostro sistema, per favore riprova tra qualche minuto." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Attiva" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Chiudi" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Installazione automatica" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Un download con installazione automatica di %s (versione a pagamento) da %s inizierà in %s. Se preferisci farlo manualmente, fai clic sul pulsante per annullare." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "Il processo d'installazione è iniziato e potrebbe impiegare alcuni minuti per completarsi. Attendi finchè non ha finito, assicurandoti di non ricaricare questa pagina." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annulla installazione" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Cassa" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI compliant" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hey %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Consenti & Continua" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Invia nuovamente l'email di attivazione" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Grazie %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Accetta e attiva la licenza" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Grazie per aver acquistato %s! Per iniziare, per favore inserisci la tua chiave di licenza:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Non perdere nessun aggiornamento importante, accetta gli aggiornamenti di sicurezza e funzionalità, contenuti formativi, offerte e il tracciamento diagnostico senza dati sensibili con %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e notifiche di funzionalità e il tracciamento diagnostico senza dati sensibili con %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e di nuove funzionalità, contenuto formativo, offerte e tracciamento diagnostico senza dati sensibili con %4$s. Se vuoi saltare questo passaggio non è un problema! %1$scontinuerà a funzionare." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Non perdere nessun aggiornamento importante, accetta i nostri aggiornamenti di sicurezza e di nuove funzionalità, contenuto formativo, offerte e tracciamento diagnostico senza dati sensibili con %4$s. Se vuoi saltare questo passaggio non è un problema! %1$s continuerà a funzionare." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Siamo felici di presentarvi il supporto al sistema multi network di Freemius." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Durante la procedura di aggiornamento abbiamo individuato%d sito/i che sono in attesa della attivazione della licenza." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Se vuoi utilizzare %s su questi siti, inserisci la tua licenza sotto e fai clic sul pulsante di attivazione." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "Funzionalità a pagamento di %s" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "In caso puoi saltare per adesso e attivare la licenza successivamente nella tua pagina di attivazione network di %s." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Durante la procedura di aggiornamenti abbiamo individuato %s sito/i del network che sono in attesa di un tuo controllo." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Chiave di licenza" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Non trovi la tua chiave di licenza?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Salta" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delega ai proprietari del sito" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Se fai clic questa decisione sarà delegata agli amministratori del sito." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Panoramica del tuo profilo" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Nome ed indirizzo email" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Panoramica del tuo sito" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL del sito, versione di WP, informazioni PHP, plugin e temi" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Avvisi amministratore" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Aggiornamenti, annunci, marketing, no spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Eventi %sattuali" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Attiva, disattivazione e disinstallazione" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Newsletter" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr " Il %1$s invierà periodicamente dei dati a %2$s per verificare aggiornamenti di sicurezza e di funzionalità e verificare la validità della tua licenza." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Quali autorizzazioni vengono concesse?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Non hai una chiave di licenza?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Hai una chiave di licenza?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Politica sulla privacy" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Termini del Servizio" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Invio email" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Attivazione" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contatti" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Non attivo" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Attivo" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Azioni" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Sei sicuro di voler eliminare tutti i dati di Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Eliminare tutti gli account" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "Elimina cache API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Svuota le Transient degli aggiornamenti" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Sincronizza i dati dal server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Carica opzioni del DB" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Imposta opzione del DB" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Chiave" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Valore" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "Versioni SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "Percorso SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Percorso modulo" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "è attiva" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plugin" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Temi" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titolo" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Stato di Freemius" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Utente Network" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Connesso" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Bloccato" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simula aggiornamento network" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installazioni" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Siti" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Elimina" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Addon del modulo %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Utenti" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Verificato" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenze" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plugin ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID Piano" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Attivato" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Bloccato" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Scadenza" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Tutti i tipi" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Tutte le richieste" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "File" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Funzione" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID processo" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Messaggio" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filtro" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Tipo" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Timestamp" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Metti in sicurezza la pagina HTTPS %s gestita da un dominio esterno" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Supporto" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Richieste" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Aggiorna" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Fatturazione" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Nome della compagnia" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Numero Partita Iva o VAT" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Riga indirizzo %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Città" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Cittadina" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "CAP" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Nazione" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Seleziona Nazione" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Stato" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincia" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Pagamenti" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Data" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Importo" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Fattura" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Metodo" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Codice" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Lunghezza" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Percorso" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Risultato" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Avvia" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Fine" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "In %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s fa" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Sincronizzazione plugin e temi" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Totale" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Ultimo" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Azioni programmate" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Modulo" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Tipo di modulo" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Tipo di Cron" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Successivo" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Non in scadenza" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Applica per diventare un affiliato" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "La tua applicazione di affiliazione per %s è stata accettata! Accedi alla tua area di affiliazione a %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Grazie per la partecipazione al nostro programma di affiliazione, valuteremo la tua richiesta durante i prossimi 14 giorni e ti contatteremo per maggiori informazioni." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Il tuo account di affiliazione è stato sospeso temporaneamente." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Grazie per la partecipazione al nostro programma di affiliazione, sfortunatamente abbiamo valutato di rifiutare la tua richiesta. Prova nuovamente fra 30 giorni." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "A causa della violazione dei nostri termini di affiliazione abbiamo deciso di bloccare temporaneamente il tuo account affiliativo. Se hai domande contatta il supporto." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Ti piace %s? Diventa il nostro ambasciatore e guadagna denaro ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Comunica nuovi clienti al nostro %s e guadagna %s di commissione per ogni vendita avvenuta!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Sommario programma" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%scommissione quando un utente acquista una nuova lcienza." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Ottieni delle commissioni dal sistema automatizzato di rinnovo." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s cookie di tracciamento dopo che la prima visita per massimizzare i margini di guadagno. " + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Commissioni illimitate." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s quantità minima per il pagamento." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "I pagamenti sono in Dollari Americani e processati mensilmente da PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Ci riserviamo 30 giorni in caso di rimborsi, paghiamo le commissioni se sono più vecchie di 30 giorni." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliati" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Indirizzo email" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Nome completo" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Indirizzo account email Paypal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Dove vuoi promuovere %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Inserisci il dominio del tuo sito o altri siti da dove vuoi promuovere %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Aggiungi un altro dominio" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Domini aggiuntivi" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Domini aggiuntivi dove ci sarà il modulo promozionale." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Metodi promozionali" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social network (Facebook, Twitter, ecc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Applicazioni mobile" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Siti, email e statistiche dei social network (opzionali)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Facci sapere ogni sito o statistiche social valide, es: visite uniche mensili, numero di sottoscrizioni email, follower ecc (tratteremo queste informazioni come riservate)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Come ci promuoverai?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Fornisci i dettagli su come intendi promuovere %s. (sii più esplicativo possibile)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annulla" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Diventa un affiliato" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Per favore inserisci la chiave di licenza che hai ricevuto via mail subito dopo l'acquisto:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Aggiorna licenza" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Cancella iscrizione" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Iscriviti" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Tracciamo l'utilizzo esclusivamente per rendere %s migliore, creando una migliore esperienza utente e dando priorità a nuove funzionalità, oltre a molte altre buone cose. Saremmo veramente felici di vederti cambiare idea e lasciarci continuare con il tracciamento." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Cliccando su \"Cancella iscrizione\", non invieremo più nessuna informazione da %s a %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "C'è una nuova versione di %s disponibile." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nuova versione disponibile" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Chiudi" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Invia chiave di licenza" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Inserisci qui sotto l'indirizzo email che hai usato per registrare l'aggiornamento e ti invieremo di nuovo la chiave di licenza." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Sei a un clic di distanza dall'iniziare il tuo periodo di prova gratuito di %1$s giorni per il piano %2$s." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Per essere accettato del regolamento WordPress.org, prima di attivare il periodo di prova devi accettare di condividere informazioni come il tuo utente e dati non sensibili. Permettendo a %s di inviare dati periodicamente a %s per verificare gli aggiornamenti e approvare il periodo di prova." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Attiva la licenza su tutti i siti del network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Applica su tutti i siti della rete." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Attiva le licenze su tutti i siti in attesa." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Applica su tutti i siti in attesa." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "permetti" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delega" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "salta" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Fare clic per visualizzare lo screenshot in grandi dimensioni %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Aggiornamenti Illimitati" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s rimanenti" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Ultima licenza" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Annullato" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Nessuna scadenza" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Nome proprietario" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Email proprietario" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID proprietario" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Sottoscrivi" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Siamo spiacenti per l'inconveniente e siamo qui per aiutarti con il tuo permesso." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contatta il supporto" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Feedback anonimo" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Disattiva" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Attiva %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "Se hai un attimo, facci sapere perché %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "disattivazione in corso" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "passa a" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Invia e %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Spiegandoci il motivo ci aiuterai a migliorare." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "SI - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Salta & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Fai clic qui per usare il plugin anonimamente" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Potresti non averci fatto caso, ma non sei obbligato a condividere i tuoi dati e puoi semplicemente %s la tua partecipazione." diff --git a/freemius/languages/freemius-ja_JP 2.mo b/freemius/languages/freemius-ja_JP 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..9de8a9d1f9db1bd4a88f2572fb1ecdf767e7d2e3 GIT binary patch literal 61357 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

    B}%I*x|KJGBhG;w#N>aU}?!B{FWnxZfF~?oEH> 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 Zno*KHkeg#zr5ntmihN(lg1$5Ge*iB^y9NLN literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-ja_JP 2.po b/freemius/languages/freemius-ja_JP 2.po new file mode 100644 index 0000000..2d877b2 --- /dev/null +++ b/freemius/languages/freemius-ja_JP 2.po @@ -0,0 +1,2511 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Odyssey <8bitodyssey+github@gmail.com>, 2016 +# Tomohyco Tsunoda, 2018 +# Takayuki Miyauchi , 2016 +# Tomohyco Tsunoda, 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Odyssey <8bitodyssey+github@gmail.com>\n" +"Language: ja_JP\n" +"Language-Team: Japanese (Japan) (http://www.transifex.com/freemius/wordpress-sdk/language/ja_JP/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK ãŒãƒ—ラグインã®ãƒ¡ã‚¤ãƒ³ãƒ•ァイルを見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ç¾åœ¨ã®ã‚¨ãƒ©ãƒ¼ã‚’æ·»ãˆã¦ sdk@freemius.com ã«é€£çµ¡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "エラー" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "より良ㄠ%sを見ã¤ã‘ã¾ã—ãŸ" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "%sã®åå‰ã¯ä½•ã§ã™ã‹ï¼Ÿ" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "%sã¯ä¸€æ™‚çš„ãªã‚‚ã®ã§ã™ã€‚ç¾åœ¨ã“ã®å•題をデãƒãƒƒã‚°ä¸­ã§ã™ã€‚" + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "無効化" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "テーマ変更" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "ãã®ä»–" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "%sã¯ã‚‚ã†ä¸è¦ã§ã™" + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "短期間ã ã‘ %s㌠必è¦ã§ã™ã€‚" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s ã®å½±éŸ¿ã§ã‚µã‚¤ãƒˆã‚’崩れã¾ã—ãŸ" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s ã®å‹•作ãŒçªç„¶åœæ­¢ã—ã¾ã—ãŸ" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "ã‚‚ã†æ‰•ã†ã“ã¨ãŒã§ãã¾ã›ã‚“" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr " 支払ã£ã¦ã‚‚よã„ã¨æ€ã†ä¾¡æ ¼ã¯ã„ãらã§ã™ã‹?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "è‡ªåˆ†ã®æƒ…報を共有ã—ãŸãã‚りã¾ã›ã‚“" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s ãŒå‹•作ã—ã¾ã›ã‚“ã§ã—ãŸ" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "ã©ã†ã—ãŸã‚‰å‹•作ã™ã‚‹ã‹åˆ†ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s ã¯ç´ æ™´ã‚‰ã—ã„ã®ã§ã™ãŒã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ãªã„ã‚る機能ãŒå¿…è¦ã§ã™" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "ä½•ã®æ©Ÿèƒ½ã§ã™ã‹?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s ãŒå‹•作ã—ã¦ã„ã¾ã›ã‚“" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "å°†æ¥ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãŸã‚ã«ä¿®æ­£ã§ãるよã†ã€ä½•ãŒå‹•作ã—ãªã‹ã£ãŸã®ã‹ã©ã†ã‹å…±æœ‰ã—ã¦ãã ã•ã„…" + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "探ã—ã¦ã„ãŸã‚‚ã®ã§ã¯ã‚りã¾ã›ã‚“" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "探ã—ã¦ã„ãŸã®ã¯ä½•ã§ã™ã‹?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%sãŒæœŸå¾…通りã«å‹•ãã¾ã›ã‚“ã§ã—㟠" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "何を期待ã—ã¦ã„ã¾ã—ãŸã‹?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius デãƒãƒƒã‚°" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "cURL ãŒãªã«ã‹ã€ãã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«æ–¹æ³•を知りã¾ã›ã‚“。助ã‘ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "ホスティング会社ã«é€£çµ¡ã—ã¦å•題を解決ã—ã¦ãã ã•ã„。 æ›´æ–°ãŒå®Œäº†ã—ãŸã‚‰ã€ ï¼…s ã¸ã®ãƒ•ォローアップメールãŒå±Šãã¾ã™ã€‚" + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "ã™ã°ã‚‰ã—ã„。cURL をインストールã—〠php.ini ãƒ•ã‚¡ã‚¤ãƒ«ã§æœ‰åŠ¹åŒ–ã—ã¦ãã ã•ã„。加ãˆã¦ã€php.ini 内㧠'disable_functions' ディレクティブを検索ã—ã¦ã€'curl_' ã§å§‹ã¾ã‚‹ç„¡åŠ¹åŒ–ã•れãŸãƒ¡ã‚½ãƒƒãƒ‰ã‚’削除ã—ã¦ãã ã•ã„。'phpinfo()' を使ã£ã¦æ­£å¸¸ã«èµ·å‹•ã•れãŸã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。有効化ã•れã¦ã„ã‚‹å ´åˆã¯ %s を一度無効化ã—ã€å†åº¦æœ‰åŠ¹åŒ–ã—ç›´ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "ã¯ã„ - ãŠæ§‹ã„ãªã" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "ã„ã„㈠- ã™ãã«ç„¡åŠ¹åŒ–" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "ãŠã£ã¨" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "修正ã™ã‚‹ãƒãƒ£ãƒ³ã‚¹ã‚’ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™! テクニカルスタッフã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒé€ä¿¡ã•れã¾ã—ãŸã€‚ ï¼…s ã¸ã®æ›´æ–°ãŒè¡Œã‚れるã¨ã™ãã«ã‚ãªãŸã«é€£çµ¡ã—ã¾ã™ã€‚ ã‚ãªãŸã®å¿è€ã«æ„Ÿè¬ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s ã¯ã€%s ãŒç„¡ã„ã¨å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s ã¯ã€ãƒ—ラグインãŒç„¡ã„ã¨å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "予期ã—ãªã„ API エラーã§ã™ã€‚%sã®ä½œè€…ã«æ¬¡ã®ã‚¨ãƒ©ãƒ¼ã‚’連絡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã® %sã¯æœ‰åŠ¹åŒ–ã«æˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "ã‚„ã£ãŸãƒ¼" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "%s ライセンスをæŒã£ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "ヤッホー" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s ã®ç„¡æ–™è©¦ç”¨ãŒæ­£å¸¸ã«ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚ アドオンã¯ãƒ—レミアムãªã®ã§ã€è‡ªå‹•çš„ã«ç„¡åŠ¹åŒ–ã•れã¾ã—ãŸã€‚ å°†æ¥ä½¿ç”¨ã—ãŸã„å ´åˆã¯ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s ã¯ãƒ—レミアムã®ã¿ã®ã‚¢ãƒ‰ã‚ªãƒ³ã§ã™ã€‚ãã®ãƒ—ラグインを有効化ã™ã‚‹å‰ã«ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "%s ã«é–¢ã™ã‚‹è©³ç´°æƒ…å ±" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "ライセンスを購入" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "%s ã®ãƒ¡ãƒ¼ãƒ«ãƒœãƒƒã‚¯ã‚¹ã« %s ã®æœ‰åŠ¹åŒ–ã®ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã£ã¦ã„ã‚‹ã¯ãšã§ã™ã€‚%s ã®ãƒ¡ãƒ¼ãƒ«ã«è¨˜è¼‰ã•ã‚ŒãŸæœ‰åŠ¹åŒ–ãƒœã‚¿ãƒ³ã‚’ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "トライアルを開始" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "インストールを完了" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "ã‚‚ã†ã‚ã¨ã‚ãšã‹ã§ã™ - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "ã™ãã« \"%s\" 有効化を完了ã—ã¦ãã ã•ã„" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "プラグインを微調整ã—ã¾ã™ã€ %s, %s" + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "%s ã®ã‚¢ãƒƒãƒ—グレードãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "アドオン" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "プラグイン" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "テーマ" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "システムã§ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒæ­£ã—ã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "メールアドレスã«é–¢é€£ä»˜ã‘ã‚‰ã‚ŒãŸæœ‰åйãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã€‚ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒæ­£ã—ã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯æœ‰åŠ¹åŒ–å¾…ã¡ã§ã™ã€‚" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s ã®æœ‰åŠ¹åŒ–ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "アカウント㌠%s ãƒ—ãƒ©ãƒ³ã§æœ‰åŠ¹åŒ–ã§ãã¾ã—ãŸã€‚" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "トライアル版ã®åˆ©ç”¨ã‚’é–‹å§‹ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "%s を有効化ã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "以下ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¨ã¨ã‚‚ã«ç§ãŸã¡ã«é€£çµ¡ã‚’ãã ã•ã„。" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "アップグレード" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "トライアルを開始" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "料金表" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "アフィリエイト" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "アカウント" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "連絡" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "アドオン" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "料金表" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "サãƒãƒ¼ãƒˆãƒ•ォーラム" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "ã‚ãªãŸã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã®æ‰¿èªãŒå®Œäº†ã—ã¾ã—ãŸã€‚ã™ã”ã„ï¼" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "ãã†ã " + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã®ãƒ—ランã®ã‚¢ãƒƒãƒ—グレードãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã®æ”¯æ‰•ã„ãŒå®Œäº†ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "最新版をダウンロード" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "サーãƒãƒ¼ã‹ã‚‰ã‚¨ãƒ©ãƒ¼ã‚’å—ä¿¡ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "èªè¨¼ãƒ‘ラメータã®1ã¤ãŒé–“é•ã£ã¦ã„るよã†ã§ã™ã€‚ 公開éµã€ç§˜å¯†éµã€ãƒ¦ãƒ¼ã‚¶ãƒ¼IDã‚’æ›´æ–°ã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "ãµã‚€" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "ã¾ã  %s プランã®ã‚ˆã†ã§ã™ã€‚ã‚‚ã—アップグレードやプランã®å¤‰æ›´ã‚’ã—ãŸã®ãªã‚‰ã€ã“ã¡ã‚‰ã§ä½•らã‹ã®å•題ãŒç™ºç”Ÿã—ã¦ã„るよã†ã§ã™ã€‚申ã—訳ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "トライアル" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "アカウントをアップグレードã—ã¾ã—ãŸãŒã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’åŒæœŸã—よã†ã¨ã™ã‚‹ã¨ãƒ—ラン㌠%s ã®ã¾ã¾ã§ã™ã€‚" + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "ã“ã¡ã‚‰ã§ç§ãŸã¡ã«é€£çµ¡ã‚’ã¨ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "プランã®ã‚¢ãƒƒãƒ—ã‚°ãƒ¬ãƒ¼ãƒ‰ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "プラン㮠%s ã¸ã®å¤‰æ›´ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ç„¡æ–™ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®%s ã¯å¼•ãç¶šã利用ã§ãã¾ã™ã€‚" + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ %1$s %3$sã«é‚ªé­”ã•れãšã«åˆ©ç”¨ã‚’継続ã™ã‚‹ã«ã¯ï¼Œä»Šã™ã%2$sアップグレードを行ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "ライセンスã¯ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚ã‚‚ã—ãれãŒé–“é•ã„ã ã¨æ€ã†ãªã‚‰ã‚µãƒãƒ¼ãƒˆã«é€£çµ¡ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã¯æœ‰åŠ¹æœŸé™ãŒãれã¾ã—ãŸã€‚%s ã®æ©Ÿèƒ½ã‚’引ãç¶šã利用ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ãŸã ã—ã€ã‚¢ãƒƒãƒ—デートやサãƒãƒ¼ãƒˆã‚’ã†ã‘ã‚‹ã«ã¯ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’アップデートã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "フリートライアル期間ãŒçµ‚了ã—ã¾ã—ãŸã€‚ç„¡æ–™ã§ä½¿ãˆã‚‹æ©Ÿèƒ½ã¯å¼•ãç¶šã利用å¯èƒ½ã§ã™ã€‚" + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "フリートライアル期間ãŒçµ‚了ã—ã¾ã—ãŸã€‚%1$s %3$sã«é‚ªé­”ã•れãšã«åˆ©ç”¨ã‚’継続ã™ã‚‹ã«ã¯ï¼Œä»Šã™ã %2$s ã®ã‚¢ãƒƒãƒ—グレードを行ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "ã‚µã‚¤ãƒˆã¯æœ‰åйãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’æŒã£ã¦ã„ãªã„よã†ã§ã™ã€‚" + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "ライセンスã®ç„¡åŠ¹åŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "ライセンスã®ç„¡åŠ¹åŒ–ãŒå®Œäº†ã—ã¾ã—ãŸã€‚%s ãƒ—ãƒ©ãƒ³ã«æˆ»ã‚Šã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "ã™ã§ã«%sをトライアルモードã§åˆ©ç”¨ä¸­ã§ã™ã€‚" + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "以å‰ã™ã§ã«è©¦ç”¨ç‰ˆã‚’利用ã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "%s プランã¯å­˜åœ¨ã—ãªã„ãŸã‚ã€è©¦ç”¨ã‚’é–‹å§‹ã§ãã¾ã›ã‚“。" + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "%s プランã«ã¯ãƒˆãƒ©ã‚¤ã‚¢ãƒ«æœŸé–“ã¯ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "%sã®ãƒ—ランã«ã¯ãƒˆãƒ©ã‚¤ã‚¢ãƒ«æœŸé–“ã¯ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "ã™ã§ã«ãƒˆãƒ©ã‚¤ã‚¢ãƒ«ãƒ¢ãƒ¼ãƒ‰ã§ã¯ãªã„よã†ãªã®ã§ã€ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“ :)" + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "トライアルã®ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã«ä¸€æ™‚çš„ãªå•題ãŒã‚りã¾ã—ãŸã€‚数分後ã«å†åº¦ãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "%s ã®ãƒ•リートライアルã¯ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ %s をリリースã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "%s をダウンロードã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "最新㮠%s ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã“ã¡ã‚‰ã§ã™ã€‚" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "æ–°è¦" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "最新版をå–å¾—ã§ãã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "ã™ã¹ã¦å®Œç’§ã§ã™!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "%s ã«ç¢ºèªãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ã‚‚ã—5分以内ã«ãれãŒå±Šã‹ãªã„å ´åˆã€è¿·æƒ‘メールボックスを確èªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "サイトã®ã‚ªãƒ—ãƒˆã‚¤ãƒ³ã«æˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "ã™ã”ã„!" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "使用データを追跡ã§ãるよã†è¨±å¯ã—ã¦ãれãŸã“ã¨ã§ã€%s をより良ãã™ã‚‹ãŸã‚ã®æ‰‹åŠ©ã‘ã«æ„Ÿè¬è‡´ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "ã‚‚ã†%s上ã®%sã‹ã‚‰%sã¸ã®ãƒ‡ãƒ¼ã‚¿é€ä¿¡ã¯è¡Œã„ã¾ã›ã‚“。" + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "メールボックスを確èªã—ã¦ãã ã•ã„。所有権ã®å¤‰æ›´ã‚’確èªã™ã‚‹ã«ã¯ã€%s ã§ãƒ¡ãƒ¼ãƒ«ã‚’å—ã‘å–ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ セキュリティ上ã®ç†ç”±ã‹ã‚‰ã€æ¬¡ã®15分以内ã«å¤‰æ›´ã‚’確èªã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ é›»å­ãƒ¡ãƒ¼ãƒ«ãŒè¦‹ã¤ã‹ã‚‰ãªã„å ´åˆã¯ã€è¿·æƒ‘メールフォルダを確èªã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "所有権ã®å¤‰æ›´ã‚’確èªã—ã¦ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚ %s ã«æ‰¿èªãƒ¡ãƒ¼ãƒ«ãŒé€ä¿¡ã•れã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s ã¯æ–°ã—ã„オーナーã§ã™ã€‚" + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "ãŠã‚ã§ã¨ã†" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "メールアドレスã®ã‚¢ãƒƒãƒ—デートを完了ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã™ã§ã«åŒã˜ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ç™»éŒ²ã—ã¦ã„るよã†ã§ã™ã€‚" + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "%sã®æ‰€æœ‰æ¨©ã‚’%sã¸è­²ã‚ŠãŸã„å ´åˆã¯ã€æ‰€æœ‰æ¨©ã®å¤‰æ›´ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "オーナーを変更" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "メールアドレスã®ã‚¢ãƒƒãƒ—デートãŒå®Œäº†ã—ã¾ã—ãŸã€‚ã¾ã‚‚ãªã確èªãƒ¡ãƒ¼ãƒ«ãŒå±Šãã¾ã™ã€‚" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "フルãƒãƒ¼ãƒ ã‚’入力ã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "åå‰ã®ã‚¢ãƒƒãƒ—ãƒ‡ãƒ¼ãƒˆãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "%s ã®ã‚¢ãƒƒãƒ—ãƒ‡ãƒ¼ãƒˆãŒæˆåŠŸã—ã¾ã—ãŸã€‚" + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³ã«é–¢ã™ã‚‹æƒ…å ±ã¯ã€å¤–部サーãƒãƒ¼ã‹ã‚‰å–å¾—ã•れã¾ã™ã€‚" + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "警告" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "ヘイ" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "%s ã¯ã©ã†ã§ã™ã‹? ç§ãŸã¡ã®å…¨ã¦ã® %s ã®ãƒ—レミアム機能をãŠè©¦ã—ãã ã•ã„。" + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "%s 日以内ã§ã‚れã°ã„ã¤ã§ã‚‚キャンセルã§ãã¾ã™ã€‚" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "クレジットカードã¯å¿…è¦ã‚りã¾ã›ã‚“。" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "フリートライアルを開始" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "ã“ã‚“ã«ã¡ã¯ã€‚%sã«ã‚¢ãƒ•ィリエイトプログラムãŒã‚ã‚‹ã®ã¯ã”存知ã§ã—ãŸã‹ï¼Ÿã€€%sãŒãŠå¥½ããªã‚‰ã€ç§ãŸã¡ã®ã‚¢ãƒ³ãƒã‚µãƒ€ãƒ¼ã«ãªã£ã¦å ±é…¬ã‚’å¾—ã¾ã—ょã†ï¼" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "詳細ã¯ã“ã¡ã‚‰" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "ライセンスを有効化" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "ライセンスを変更" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "オプトアウト" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "オプトイン" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "アップグレードを完了ã™ã‚‹ã«ã¯ä»¥ä¸‹ã®æ‰‹é †ã‚’完了ã•ã›ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "最新㮠%s をダウンロード" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "ダウンロードã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’アップロードã—ã¦æœ‰åŠ¹åŒ–" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "ã‚¢ãƒƒãƒ—ãƒ­ãƒ¼ãƒ‰ã¨æœ‰åŠ¹åŒ–ã®æ–¹æ³•" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sã“ã“をクリックã—ã¦%s ライセンスを有効化ã—ãŸã„ã‚µã‚¤ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„。" + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "自動インストールã¯ã‚ªãƒ—トインã—ãŸãƒ¦ãƒ¼ã‚¶ã®ã¿ã§å‹•作ã—ã¾ã™ã€‚" + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "モジュール ID ãŒä¸æ­£ã§ã™" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã™ã§ã«æœ‰åйã«ãªã£ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãる有効ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹æŒã£ã¦ã„ã¾ã›ã‚“。" + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "プラグインã¯ãƒ—レミアムコードãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ãªã„「サービスウェアã€ã§ã™ã€‚" + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "プレミアムアドオンãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯ã™ã§ã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«æ¸ˆã¿ã§ã™ã€‚" + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "æœ‰æ–™ã®æ©Ÿèƒ½ã‚’表示ã™ã‚‹" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "%sã¨ã‚¢ãƒ‰ã‚ªãƒ³ã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "%sã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "%sã®æ”¹å–„ã«å½¹ç«‹ã¤ä½¿ç”¨çжæ³ã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã«ã™ã§ã«ã‚ªãƒ—トインã—ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "プロダクトã®ã”利用ã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ï¼" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "ãƒ—ãƒ­ãƒ€ã‚¯ãƒˆã®æ”¹å–„ã«å½¹ç«‹ã¤ä½¿ç”¨çжæ³ã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã«ã™ã§ã«ã‚ªãƒ—トインã—ã¦ã„ã¾ã™ã€‚" + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%sã¨ãã®ã‚¢ãƒ‰ã‚ªãƒ³" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "プロダクト" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "ã¯ã„" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ©Ÿèƒ½ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用コンテンツやオファーをé€ã£ã¦ãã ã•ã„。" + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "ã„ã„ãˆ" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ©Ÿèƒ½ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用コンテンツやオファーを%sé€ã‚‰ãªã„ã§ãã ã•ã„%s。" + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "セキュリティや機能ã®ã‚¢ãƒƒãƒ—デートã€å­¦ç¿’用用コンテンツã€ãŠã‚ˆã³ã‚ªãƒ•ァーã«ã¤ã„ã¦ãŠå•ã„åˆã‚ã›ã‚’希望ã•れる場åˆã¯ã€ãŠçŸ¥ã‚‰ã›ãã ã•ã„。" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "ライセンスキーãŒç©ºã§ã™ã€‚" + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "ライセンスを更新" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "インストール中プラグイン: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ ã«æŽ¥ç¶šã§ãã¾ã›ã‚“。視覚情報を確èªã—ã¦ãã ã•ã„。" + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "リモートプラグインパッケージã«ã¯ã€ç›®çš„ã®ã‚¹ãƒ©ãƒƒã‚°ã‚’å«ã‚€ãƒ•ォルダãŒå«ã¾ã‚Œã¦ã„ãªã„ãŸã‚ã€ãƒªã­ãƒ¼ãƒ ãŒæ©Ÿèƒ½ã—ã¾ã›ã‚“ã§ã—ãŸã€‚" + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "購入" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "無料㮠%s ã‚’é–‹å§‹" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®æ›´æ–°ã‚’今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "今ã™ã更新をインストール" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "今ã™ãインストール" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "最新ã®ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’ダウンロード" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "最新版をダウンロード" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "ã“ã®ã‚¢ãƒ‰ã‚ªãƒ³ã‚’有効化" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’有効化" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "有効化" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "説明" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "インストール" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "FAQ" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "スクリーンショット" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "変更履歴" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "レビュー" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "ãã®ä»–ã®è¨˜è¿°" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "機能 & 料金" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "プラグインã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s プラン" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "ベスト" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "月" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "年次" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "ライフタイム" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s ã¸ã®è«‹æ±‚" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "毎年" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "一度" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "シングルサイトライセンス" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "無制é™ãƒ©ã‚¤ã‚»ãƒ³ã‚¹" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "%sサイトã¾ã§" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "月" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "å¹´" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "料金" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "%s ã‚’ä¿å­˜" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "%s ã®æ‹˜æŸã¯ã‚りã¾ã›ã‚“。ã„ã¤ã§ã‚‚キャンセルã§ãã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "無料㮠%s ã®å¾Œã¯ã€ã‚ãšã‹ %s ã ã‘ãŠæ”¯æ‰•ãã ã•ã„。" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "詳細" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "作者" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "最終更新" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s å‰" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "å¿…è¦ãª WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%sã¾ãŸã¯ãれ以上" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "äº’æ›æ€§ã®ã‚る最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "ダウンロード済ã¿" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s回" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s回" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org ã®ãƒ—ラグインページ" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "プラグインã®ãƒ›ãƒ¼ãƒ ãƒšãƒ¼ã‚¸" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "ã“ã®ãƒ—ラグインã«å¯„付ã™ã‚‹" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "レーティングã®å¹³å‡" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "%sを基ã«" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s評価" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s評価" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%sスター" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%sスター" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "クリックã—ã¦%sã®è©•価をã—ã¦ã„るレビューを観る" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "コントリビューター" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "警告" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "ã“ã®ãƒ—ラグインã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れ㟠WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã¯æ¤œè¨¼ã•れã¦ã„ã¾ã›ã‚“。" + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "ã“ã®ãƒ—ラグインã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れ㟠WordPress ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«äº’æ›æ€§ãŒã‚りã¾ã›ã‚“。" + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "有料アドオン㯠Freemius ã«ãƒ‡ãƒ—ロイã•れã¦ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "アドオン㌠WordPress.org ã‹ Freemius ã«ãƒ‡ãƒ—ロイã•れã¦ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (%s) ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "æ–°ã—ã„フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³ (%s) ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "最新版ãŒã‚¤ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "最新ã®ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸ" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "トライアルをキャンセルã™ã‚‹ã¨ã™ãã«ã™ã¹ã¦ã®ãƒ—レミアム機能ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ãŒã§ããªããªã‚Šã¾ã™ã€‚本当ã«å®Ÿè¡Œã—ã¾ã™ã‹?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "ä¸€åº¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœŸé™ãŒåˆ‡ã‚Œã‚‹ã¨ã€ãƒ•リーãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®åˆ©ç”¨ã¯å¯èƒ½ã§ã™ãŒã€%sã®æ©Ÿèƒ½ã‚’使ã†ã“ã¨ãŒã§ããªããªã‚Šã¾ã™ã€‚" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "%s プランを有効化" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "%s ã«è‡ªå‹•æ›´æ–°" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "%s ã§æœŸé–“終了" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’åŒæœŸ" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "トライアルをキャンセル" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "プラン変更" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "アップグレード" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "ダウングレード" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "ç„¡æ–™" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "プラン" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "フリートライアル" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "アカウント詳細" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "アカウントを削除ã™ã‚‹ã¨è‡ªå‹•的㫠%s プランライセンスãŒç„¡åйã«ãªã‚Šã€ä»–ã®ã‚µã‚¤ãƒˆã§ä½¿ã†ã“ã¨ãŒã§ãã¾ã™ã€‚å®šæœŸã®æ”¯æ‰•ã„も終了ã—ãŸã„å ´åˆã¯ã€\"キャンセル\"ボタンをクリックã—ã€ã¾ãšã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’\"ダウングレード\"ã—ã¦ãã ã•ã„。本当ã«å‰Šé™¤ã‚’続行ã—ã¦ã‚‚ã„ã„ã§ã™ã‹?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "削除ã¯ä¸€æ™‚çš„ãªã‚‚ã®ã§ã¯ã‚りã¾ã›ã‚“。本当ã«%sãŒå¿…è¦ãªããªã£ãŸæ™‚ã«è¡Œã£ã¦ãã ã•ã„。" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "アカウントを削除" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "ライセンスを無効化" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "本当ã«ç¶šè¡Œã—ã¦ã„ã„ã§ã™ã‹?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "サブスクリプションをキャンセルã™ã‚‹" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "åŒæœŸ" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "åå‰" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Email" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "ユーザー ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "サイト ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "ID ãŒã‚りã¾ã›ã‚“" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "公開éµ" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "秘密éµ" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "秘密éµãŒã‚りã¾ã›ã‚“" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "トライアル" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "ライセンスキー" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "未èªè¨¼" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "期é™åˆ‡ã‚Œ" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "プレミアムãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/account.php:504 +msgid "Free version" +msgstr "フリーãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "èªè¨¼ãƒ¡ãƒ¼ãƒ«" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "%s ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’ダウンロード" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "表示" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "自分㮠%s ã¯ãªã‚“ã§ã™ã‹?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "編集" + +#: templates/account.php:588 +msgid "Sites" +msgstr "サイト数" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "使‰€ã§æ¤œç´¢ã™ã‚‹" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "使‰€" + +#: templates/account.php:610 +msgid "License" +msgstr "ライセンス" + +#: templates/account.php:611 +msgid "Plan" +msgstr "プラン" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "ライセンス" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "éžè¡¨ç¤º" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "ライセンスを無効化ã™ã‚‹ã¨ã™ã¹ã¦ã®ãƒ—レミアム機能ãŒä½¿ãˆãªããªã‚Šã¾ã™ãŒã€ä»–ã®ã‚µã‚¤ãƒˆã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚本当ã«å®Ÿè¡Œã—ã¾ã™ã‹ï¼Ÿ" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "詳細を表示" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "%s ã®ã‚¢ãƒ‰ã‚ªãƒ³" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "アドオンリストを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãŠãらãé‹å–¶å´ã®å•題ã«ãªã‚Šã¾ã™ã®ã§ã€ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ãŠè©¦ã—ãã ã•ã„。" + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "有効" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "å´ä¸‹" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%sç§’" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "自動インストール" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "%sã‹ã‚‰ %s (有料版) ã®è‡ªå‹•ダウンロードã¨è‡ªå‹•インストールãŒ%sã§é–‹å§‹ã—ã¾ã™ã€‚手動ã§è¡Œã†å ´åˆã¯ä»Šã™ãã«ã‚­ãƒ£ãƒ³ã‚»ãƒ«ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "インストールプロセスãŒé–‹å§‹ã•ã‚Œã€æ•°åˆ†ã§å®Œäº†ã—ã¾ã™ã€‚完了ã¾ã§ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„。ページã®ãƒªãƒ•レッシュãªã©ã¯è¡Œã‚ãªã„ã§ãã ã•ã„。" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "インストールをキャンセルã™ã‚‹" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI コンプライアント" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "ãŠãŠã„ %s ã•ã‚“ã€" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "許å¯ã—ã¦ç¶šã‘ã‚‹" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "有効化メールをå†é€ä¿¡" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "ã‚りãŒã¨ã† $s ã•ã‚“!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "åŒæ„ã—ã¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効化" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "%s を購入ã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚ã¯ã˜ã‚ã«ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã¦ãã ã•ã„:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。" + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。" + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。ã“れをスキップã—ã¦ã‚‚%1$sã¯ã‚‚ã¡ã‚ん動作ã—ã¾ã™ã€‚" + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "é‡è¦ãªæ›´æ–°ã‚’逃ã•ãªã„よã†ã«ã€ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨æ›´æ–°é€šçŸ¥ã€å­¦ç¿’用コンテンツã€ã‚ªãƒ•ァーã€ãã—ã¦%4$s ã¨ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã§ã¯ãªã„診断トラッキングをオプトインã—ã¦ãã ã•ã„。ã“れをスキップã—ã¦ã‚‚%1$sã¯ã‚‚ã¡ã‚ん動作ã—ã¾ã™ã€‚" + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "Freeminus ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¬ãƒ™ãƒ«ã®ã‚¤ãƒ³ãƒ†ã‚°ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’ã”紹介ã§ãã‚‹ã“ã¨ã«èˆˆå¥®ã—ã¦ã„ã¾ã™ã€‚" + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "アップデートã®å‡¦ç†ä¸­ã«%dサイトãŒãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®æœ‰åŠ¹åŒ–ãŒä¿ç•™ä¸­ã§ã‚ã‚‹ã“ã¨ã‚’検知ã—ã¾ã—ãŸã€‚" + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "ã“れらã®ã‚µã‚¤ãƒˆã§%sを使ã†å ´åˆã¯ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã€ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ã‚·ãƒ§ãƒ³ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。" + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%sã®æœ‰æ–™æ©Ÿèƒ½" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "ã¾ãŸã¯ã€ä»Šã™ãスキップã—ã¦ã€%sã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¬ãƒ™ãƒ«ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãƒšãƒ¼ã‚¸ã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚" + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "アップデートã®å‡¦ç†ä¸­ã«ã€ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯å†…ã®%dサイトãŒå¯¾å¿œå¾…ã¡ã«ãªã£ã¦ã„ã‚‹ã“ã¨ã‚’検知ã—ã¾ã—ãŸã€‚" + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "ライセンスキー" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "ライセンスキーã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã‹?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "スキップ" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "サイト管ç†è€…ã«å§”ä»»ã™ã‚‹" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "決定をサイトã®ç®¡ç†è€…ã«å§”ä»»ã™ã‚‹ã«ã¯ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãã ã•ã„。" + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "プロフィール概è¦" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "åå‰ã¨ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "サイト概è¦" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "サイト URLã€WP ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã€PHP infoã€ãƒ—ラグインã¨ãƒ†ãƒ¼ãƒž" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "管ç†è€…通知" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "æ›´æ–°ã€ç™ºè¡¨ã€ãƒžãƒ¼ã‚±ãƒ†ã‚£ãƒ³ã‚°ã€ã‚¹ãƒ‘ムãªã—" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "ç¾åœ¨%sイベント" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "有効化ã€ç„¡åŠ¹åŒ–ã€ã‚¢ãƒ³ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "ニュースレター" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$sã¯ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ã¨ã‚¢ãƒ—デートã€ãã—ã¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã®çŠ¶æ…‹ã‚’ç¢ºèªã™ã‚‹ãŸã‚ã€å®šæœŸçš„ã«%2$sã¸ãƒ‡ãƒ¼ã‚¿ã‚’é€ä¿¡ã—ã¾ã™ã€‚" + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "付与ã•れã¦ã„るパーミッションã¯ä½•ã§ã™ã‹?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’ãŠæŒã¡ã§ã¯ã‚りã¾ã›ã‚“ã‹?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "ライセンスキーã¯ãŠæŒã¡ã§ã™ã‹?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "プライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "利用è¦ç´„" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "メールé€ä¿¡ä¸­" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "有効化中" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "連絡" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "オフ" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "オン" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "デãƒãƒƒã‚°" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "アクション" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "ã»ã‚“ã¨ã†ã«å…¨ã¦ã® Freemius データを削除ã—ã¾ã™ã‹?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "å…¨ã¦ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’削除" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "API キャッシュをクリア" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "アップデートã®ãƒˆãƒ©ãƒ³ã‚¸ã‚¨ãƒ³ãƒˆã‚’クリアーã«ã™ã‚‹" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "サーãƒãƒ¼ã‹ã‚‰ã®ãƒ‡ãƒ¼ã‚¿ã‚’åŒæœŸ" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "DB オプションを読ã¿è¾¼ã‚€" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "DB オプションを設定ã™ã‚‹" + +#: templates/debug.php:182 +msgid "Key" +msgstr "キー" + +#: templates/debug.php:183 +msgid "Value" +msgstr "値" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK ãƒãƒ¼ã‚¸ãƒ§ãƒ³" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK ã®ãƒ‘ス" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "モジュールã®ãƒ‘ス" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "有効" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "プラグイン" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "テーマ" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "スラッグ" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "タイトル" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius ステータス" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ–ログ" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ¦ãƒ¼ã‚¶" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "接続" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "ブロック" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¢ãƒƒãƒ—グレードをシミュレートã™ã‚‹" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%sインストール" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "サイト数" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "ブログ ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "削除" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "モジュールã®ã‚¢ãƒ‰ã‚ªãƒ³%s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "ユーザー" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "èªè¨¼æ¸ˆã¿" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%sラインセス" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "プラグイン ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "プラン ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "クォータ" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "有効化済ã¿" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "ブロッキング" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "期é™åˆ‡ã‚Œ" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "デãƒãƒƒã‚°ãƒ­ã‚°" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "ã™ã¹ã¦ã®ã‚¿ã‚¤ãƒ—" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "ã™ã¹ã¦ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆ" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "ファイル" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "機能" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "プロセス ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "ロガー" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "メッセージ" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "フィルター" + +#: templates/debug.php:587 +msgid "Download" +msgstr "ダウンロード" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "タイプ" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "タイムスタンプ" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "外部ドメインã§å®Ÿè¡Œä¸­ã®ã‚»ã‚­ãƒ¥ã‚¢ãª HTTPS %sページ" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "サãƒãƒ¼ãƒˆ" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "リクエスト数" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "æ›´æ–°" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "請求書" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "商å·" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "税金 / VAT ID" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "使‰€æ¬„ %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "市" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "町" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "ZIP / 郵便番å·" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "国" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "å›½ã‚’é¸æŠž" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "å·ž" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "県・州・çœ" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "支払ã„" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "日付" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "ç·é¡" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "請求書" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "メソッド" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "コード" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "é•·ã•" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "パス" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "本文" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "çµæžœ" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "é–‹å§‹" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "終了" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "ã‚°" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "%s 内" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s å‰" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "ç§’" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "プラグインã¨ãƒ†ãƒ¼ãƒžã‚’åŒæœŸ" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "トータル" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "最終" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "スケジュール Cron" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "モジュール" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "モジュールタイプ" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron タイプ" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "次" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "期é™ã®ãªã„" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "アフィリエイトã«å¿œå‹Ÿã™ã‚‹" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "%sã®ã‚¢ãƒ•ィリエイト申請ã¯å—ç†ã•れã¾ã—ãŸï¼ã€€æ¬¡ã®ãƒªãƒ³ã‚¯ã‹ã‚‰ã‚¢ãƒ•ィリエイトエリアã«ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„:%s" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "アフィリエイトプログラムã«å¿œå‹Ÿã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚14日以内ã«ãŠç”³ã—è¾¼ã¿è©³ç´°ã‚’レビューã—ã€æ”¹ã‚ã¦ã”連絡ã„ãŸã—ã¾ã™ã€‚" + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "アフィリエイトアカウントã¯ä¸€æ™‚çš„ã«åœæ­¢ã•れã¾ã—ãŸã€‚" + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "アフィリエイトアカウントã«å¿œå‹Ÿã„ãŸã ãã‚りãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚残念ãªãŒã‚‰ç¾æ™‚点ã§ã¯ç”³è«‹ã‚’å—ç†ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚30æ—¥å¾Œã«æ”¹ã‚ã¦ãŠç”³è¾¼ã¿ãã ã•ã„。" + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "アフィリエイトè¦ç´„é•åã«ã‚ˆã‚Šã€ã‚¢ãƒ•ィリエイトアカウントを一時的ã«å‡çµã•ã›ã¦ã„ãŸã ãã¾ã—ãŸã€‚ã”質å•ç­‰ãŒã‚りã¾ã—ãŸã‚‰ã€ã‚µãƒãƒ¼ãƒˆã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。" + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "%sã¯æ°—ã«å…¥ã‚Šã¾ã—ãŸã‹ï¼Ÿã€€ã‚¢ãƒ³ãƒã‚µãƒ€ãƒ¼ã«ãªã£ã¦å ±é…¬ã‚’å¾—ã¾ã—ょㆠ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "æ–°è¦ã‚«ã‚¹ã‚¿ãƒžãƒ¼ã«ç§ãŸã¡ã®%sを紹介ã—ã¦ã€å£²ã‚Šä¸Šã’ã”ã¨ã«%sã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã‚’å¾—ã¾ã—ょã†" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "プログラム概è¦" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "ã‚«ã‚¹ã‚¿ãƒžãƒ¼ãŒæ–°è¦ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’購入ã™ã‚‹ã”ã¨ã«%sã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ãŒç™ºç”Ÿã—ã¾ã™ã€‚" + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "サブスクリプションã®è‡ªå‹•æ›´æ–°ã§ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã‚’å¾—ã¾ã—ょã†ã€‚" + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%såˆå›žã®è¨ªå•後ã€ã‚¯ãƒƒã‚­ãƒ¼ã‚’トラッキングã—ã¦åŽç›Šã®å¯èƒ½æ€§ã‚’最大化ã—ã¾ã—ょã†ã€‚" + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "無制é™ã®ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã€‚" + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%sãŠæ”¯æ‰•ã„ã®æœ€ä½Žé‡‘é¡" + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "ãŠæ”¯æ‰•ã„㯠USD ã‹ã¤ PayPal çµŒç”±ã§æ¯Žæœˆè¡Œã‚れã¾ã™ã€‚" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "30日間ã®è¿”金期間ãŒã‚ã‚‹ãŸã‚ã€ã‚³ãƒŸãƒƒã‚·ãƒ§ãƒ³ã®ãŠæ”¯æ‰•ã„ã¯30日以é™ã«ãªã‚Šã¾ã™ã€‚" + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "アフィリエイト" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "メールアドレス" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "フルãƒãƒ¼ãƒ " + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal アカウントã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "%sã®ãƒ—ロモーションを行ã†ã‚µã‚¤ãƒˆã¯ã©ã“ã§ã™ã‹ï¼Ÿ" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "%sã®ãƒ—ロモーションを行ã†äºˆå®šã®ã‚ãªãŸã®ã‚µã‚¤ãƒˆã‚„ä»–ã®ã‚µã‚¤ãƒˆã®ãƒ‰ãƒ¡ã‚¤ãƒ³åを入力ã—ã¦ãã ã•ã„。" + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "ドメインåを追加ã™ã‚‹" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "追加ã®ãƒ‰ãƒ¡ã‚¤ãƒ³å" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "プロダクトフォームã®ãƒžãƒ¼ã‚±ãƒ†ã‚£ãƒ³ã‚°ã‚’行ã†è¿½åŠ ãƒ‰ãƒ¡ã‚¤ãƒ³å。" + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "プロモーション方法" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "ソーシャルメディア(Facebookã€Twitterã€ãã®ä»–)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "モãƒã‚¤ãƒ«ã‚¢ãƒ—リケーション" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "ウェブサイトã€Email ã¾ãŸã¯ã‚½ãƒ¼ã‚·ãƒ£ãƒ«ãƒ¡ãƒ‡ã‚£ã‚¢ã®çµ±è¨ˆ (オプション)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "関係ã®ã‚るウェブサイトやソーシャルメディアã®çµ±è¨ˆã‚’æä¾›ã—ã¦ãã ã•ã„。例: ã‚µã‚¤ãƒˆã®æœˆé–“訪å•者数ã€Emailã®è³¼èª­è€…æ•°ã€ãƒ•ォロワー数等 (機密情報ã¨ã—ã¦å–り扱ã„ã¾ã™)" + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "ã©ã®ã‚ˆã†ã«æˆ‘々をプロモートã—ã¾ã™ã‹ï¼Ÿ" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "ã©ã®ã‚ˆã†ã«%sをプロモートã™ã‚‹ã¤ã‚‚りãªã®ã‹ã€è©³ç´°ã‚’ãŠçŸ¥ã‚‰ã›ãã ã•ã„ (ã§ãã‚‹ã ã‘具体的ã«ãŠé¡˜ã„ã—ã¾ã™)" + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "キャンセル" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "アフィリエイトã«ãªã‚‹" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "購入後ã™ãã«ãƒ¡ãƒ¼ãƒ«ã§å—ã‘å–ã£ãŸãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’入力ã—ã¦ãã ã•ã„:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "ライセンスを更新" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "オプトアウト" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "オプトイン" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "使用ã®è¿½è·¡ã¯ %s をより良ãã™ã‚‹åç›®ã®ä¸‹ã«è¡Œã‚れã¦ã„ã¾ã™ã€‚ユーザー体験をより良ãã—ã€æ–°æ©Ÿèƒ½ã«å„ªå…ˆé †ä½ã‚’ã¤ã‘ã‚‹ãŸã‚ãªã©ã«ä½¿ã„ã¾ã™ã€‚追跡を続ã‘ã¦ã‚‚よã„ã¨å†è€ƒã—ã¦ãれるãªã‚‰æœ¬å½“ã«æ„Ÿè¬è‡´ã—ã¾ã™ã€‚" + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "\"オプトアウト\"をクリックã™ã‚‹ã“ã¨ã§ã€ã‚‚ㆠ%s ã‹ã‚‰ %s ã¸ã®ãƒ‡ãƒ¼ã‚¿ã®é€ä¿¡ã¯è¡Œã„ã¾ã›ã‚“。" + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "%sã®å…¥æ‰‹å¯èƒ½ãªæ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚りã¾ã™" + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚りã¾ã™" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "å´ä¸‹" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "ライセンスキーをé€ä¿¡" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "アップグレードã«ä½¿ç”¨ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’下ã«å…¥åŠ›ã—ã¦ãã ã•ã„。ãã†ã™ã‚Œã°ã€ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚­ãƒ¼ã‚’ãŠé€ã‚Šã—ã¾ã™ã€‚" + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "%2$s プランã®%1$s日間ã®ãƒ•リートライアルを開始ã™ã‚‹ã¾ã§ã‚ã¨ãƒ¯ãƒ³ã‚¯ãƒªãƒƒã‚¯ã§ã™ã€‚" + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "WordPress.orgã®ã‚¬ã‚¤ãƒ‰ãƒ©ã‚¤ãƒ³ã«æº–æ‹ ã™ã‚‹ãŸã‚ã€ãƒˆãƒ©ã‚¤ã‚¢ãƒ«ã‚’é–‹å§‹ã™ã‚‹å‰ã«ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨é‡è¦ã§ãªã„サイト情報ã®ã‚ªãƒ—ãƒˆã‚¤ãƒ³ã€æ›´æ–°ã®ç¢ºèªã‚„トライアルã®çŠ¶æ…‹ç¢ºèªã®ãŸã‚ã«%sãŒ%sã«å¯¾ã—ã¦å®šæœŸçš„ã«ãƒ‡ãƒ¼ã‚¿ã‚’é€ä¿¡ã™ã‚‹è¨±å¯ã‚’得るよã†ã«è¨­å®šã—ã¦ãã ã•ã„。" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "プレミアム" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ä¸Šã«ã‚ã‚‹ã™ã¹ã¦ã®ã‚µã‚¤ãƒˆã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã€‚" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ä¸Šã«ã‚ã‚‹ã™ã¹ã¦ã®ã‚µã‚¤ãƒˆã«å¯¾ã—ã¦å映ã•ã›ã‚‹ã€‚" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "ä¿ç•™ä¸­ã®ã‚µã‚¤ãƒˆã™ã¹ã¦ã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã™ã‚‹ã€‚" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "ä¿ç•™ä¸­ã®ã‚µã‚¤ãƒˆã™ã¹ã¦ã«å映ã•ã›ã‚‹ã€‚" + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "許å¯" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "代表" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "スキップ" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "クリックã—ã¦ãƒ•ルサイズã®ã‚¹ã‚¯ãƒªãƒ¼ãƒ³ã‚·ãƒ§ãƒƒãƒˆã‚’見る %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "無制é™ã®ã‚¢ãƒƒãƒ—デート" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "ã‚㨠%s" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "最新ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "キャンセル" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "有効期é™ãªã—" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "所有者å" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "所有者㮠Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "オーナー ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "サブスクリプション" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "ã”迷惑をãŠã‹ã‘ã—ã¦ã™ã„ã¾ã›ã‚“ã€‚ã‚‚ã—æ©Ÿä¼šã‚’ã„ãŸã ã‘ãŸã‚‰ãŠæ‰‹ä¼ã„ã‚’ã—ã¾ã™ã€‚" + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "サãƒãƒ¼ãƒˆã«é€£çµ¡" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "匿åã®ãƒ•ィードãƒãƒƒã‚¯" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "無効化" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "%sを有効化ã™ã‚‹" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "ãŠæ™‚é–“ãŒã‚れã°ã€ãªãœ%sã™ã‚‹ã®ã‹ç†ç”±ã‚’æ•™ãˆã¦ãã ã•ã„。" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "無効化中" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "変更中" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "é€ä¿¡ã¨%s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "改善ã§ãるよã†ã€ã©ã†ã‹ç†ç”±ã‚’æ•™ãˆã¦ãã ã•ã„。" + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "ã¯ã„" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "スキップã¨%s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "匿åã§ãƒ—ラグインを使用ã™ã‚‹ã«ã¯ã“ã¡ã‚‰ã‚’クリック" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "見逃ã—ã¦ã„ãŸã‹ã‚‚ã—れã¾ã›ã‚“ãŒã€ã©ã‚“ãªæƒ…報も共有ã™ã‚‹å¿…è¦ã¯ãªãã€ã‚ªãƒ—トインを $s ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ " diff --git a/freemius/languages/freemius-nl_NL 2.mo b/freemius/languages/freemius-nl_NL 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..d8f863fb21e87ceee00b0410896528b63476f70a GIT binary patch literal 55531 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

  • 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-nl_NL 2.po b/freemius/languages/freemius-nl_NL 2.po new file mode 100644 index 0000000..d8e1822 --- /dev/null +++ b/freemius/languages/freemius-nl_NL 2.po @@ -0,0 +1,2509 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Benny Vluggen , 2017-2018 +# Patrick Buntsma , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Benny Vluggen \n" +"Language: nl_NL\n" +"Language-Team: Dutch (Netherlands) (http://www.transifex.com/freemius/wordpress-sdk/language/nl_NL/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK kon het hoofdbestand van de plug-in niet vinden. Neem a.j.b. contact op met sdk@freemius.com m.b.t. deze fout." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Fout" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Ik vond een beter %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Wat is de naam van het %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Het betreft een tijdelijke %s. Ik ben een probleem aan het debuggen." + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "Deactivatie" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Thema Wissel" + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Overige" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "Ik heb de %s niet meer nodig " + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "Ik had de %s alleen nodig voor een korte periode." + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "De %s maakte mijn site onbruikbaar" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "De %s werkte opeens niet meer" + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Ik kan er niet langer meer voor betalen" + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "Welke bedrag zou je ervoor over hebben?" + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Ik vind het niet prettig om mijn informatie met jullie te delen" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "De %s werkte niet" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Ik snapte niet hoe ik het aan het werk kon krijgen." + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "De %s is uitstekend, maar ik heb een specifieke feature nodig die jullie niet ondersteunen" + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Welke feature?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "De %s werkt niet" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "Wil je alsjeblieft zo vriendelijk zijn om te delen wat niet werkte, zodat we dat kunnen verbeteren voor toekomstige gebruikers ..." + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Het is niet waarna ik opzoek was" + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Waar was je naar op zoek?" + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "De %s werkte niet zoals verwacht" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Wat had je verwacht?" + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "Freemius Debug" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Ik weet niet wat cURL is of hoe dat te installeren is, help me!" + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "We doen onze best om contact op te nemen met uw hostingbedrijf om het probleem op te lossen. We sturen een vervolgmail naar %s, zodra we een update hebben. " + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Mooi, installeer alsjeblieft cURL en activeer het in je php.ini bestand. Tevens, zoek naar de 'disable_functions' directive in je php.ini bestand en verwijder iedere methode die start met 'curl_'. Gebruik 'phpinfo()' om je ervan te vergewissen dat het nu succesvol geactiveerd is. Als actief, deactiveer de %s en heractiveer deze opnieuw." + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Ja, ga je gang" + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Nee - alleen deactiveren" + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "Oeps" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "Bedankt dat je ons in de gelegenheid stelt dit op te lossen. Zojuist is er een bericht verstuurd naar onze technische staf. We laten wat van ons horen, aan %s, als we een update hebben. Bedankt voor je geduld." + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s werkt niet zonder %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s werkt niet zonder de plug-in." + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "Onverwachte API fout. Neem alsjeblieft contact op met de auteur van de %s met de volgende foutmelding." + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Premium %s versie is succesvol geactiveerd." + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "W00t" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "Je hebt een %s licentie" + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Hoera" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "%s gratis proefperiode werd succesvol stop gezet. Daar de add-on alleen als premium versie beschikbaar is werd deze automatisch gedeactiveerd. Als u de add-on in de toekomst wilt gebruiken dient u een licentie aan te schaffen." + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s is uitsluitend beschikbaar als een premium add-on. Je moet een licentie kopen voordat je de plug-in activeert." + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Meer informatie over %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Licentie Kopen" + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Als het goed is ontvang je een activatie e-mail voor %s in je %s mailbox. Zorg er alsjeblieft voor dat je op de activatie knop klikt in die e-mail aan %s." + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "start de proefperiode" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "voltooi de installatie" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Je bent slechts een stap verwijderd - %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Voltooi \"%s\" Activatie Nu" + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "We hebben een aantal aanpassingen gedaan op de %s, %s " + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt-in om \"%s\" te verbeteren!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "De upgrade van %s is succesvol voltooid." + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Uitbreiding" + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Plug-in" + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Thema" + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Ongeldige verzameling van Site Details." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "We konden je e-mailadres niet vinden in het systeem, ben je zeker dat dat het juiste adres is?" + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "Er is geen actieve licentie gekoppeld aan dat e-mailadres, ben je zeker dat dat het juiste adres is?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Account wacht op activatie." + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Koop nu een licentie" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Vernieuw je licentie nu" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%svoor toegang tot versie %s beveiliging en feature updates en support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "%s activatie is succesvol voltooid." + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Je account is succesvol geactiveerd met het %s plan." + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "U proefperiode is met succes gestart." + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Kon %s niet activeren." + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "Neem a.u.b. contact met ons op met het volgende bericht:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Upgrade" + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Start Proefperiode" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Prijzen" + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "Affiliatie" + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Account" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Contacteer Ons" + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "Uitbreidingen" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Prijzen" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Supportforum" + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Je e-mail werd succesvol geverifieerd - je bent GEWELDIG!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Toppie" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Uw %sAdd-on plan werd succesvol geüpgraded. " + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "%s Add-on werd succesvol aangekocht." + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Download de meeste recente versie" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Foutmelding ontvangen van de server:" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "Het lijkt erop dat een van de authenticatie parameters niet klopt. Update je Publieke Sleutel, Geheime Sleutel & Gebruikers ID en probeer het nogmaals. " + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Hmm" + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "Het lijkt erop dat u nog steeds op het %s plan zit. Als u uw plan geüpgraded of veranderd heeft, dan is het waarschijnlijk een fout aan onze kant - sorry." + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "Proefperiode" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Ik heb mijn account geüpgraded maar als ik probeer te Synchroniseren blijft het plan %s." + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "Neem hier a.u.b. contact met ons op" + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Je plan is succesvol geüpgraded." + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Je plan is succesvol veranderd naar %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Je licentie is verlopen. Je kan echter de gratis %s voor altijd blijven gebruiken." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Je licentie is verlopen. %1$sUpgrade nu%2$s om de %3$s zonder interrupties te blijven gebruiken." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Je licentie is geannuleerd. Als je denkt dat dat een fout is, neem dan alsjeblieft contact op met support." + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Je licentie is verlopen. Je kan nog steeds alle %s features gebruiken, maar je zal je licentie moeten vernieuwen om weer updates en support te ontvangen." + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Je gratis proefperiode is verlopen. Je kan nog steeds al onze gratis features blijven gebruiken." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Je gratis proefperiode is verlopen. %1$sUpgrade nu%2$som de %3$s zonder interrupties te blijven gebruiken. " + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "Het lijkt erop dat de licentie niet geactiveerd kon worden." + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Je licentie is succesvol geactiveerd." + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "Het lijkt erop dat je site momenteel geen actieve licentie heeft." + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "Het lijkt erop dat het deactiveren van je licentie mislukt is." + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Je licentie is succesvol gedeactiveerd, je bent terug op het %s plan." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "Oké" + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Het lijkt erop, dat we een tijdelijk probleem hebben met het annuleren van je abonnement. Probeer het alsjeblieft over een paar minuten nog eens." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Je abonnement is succesvol geannuleerd. De licentie van je %s-plan al over %s aflopen." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Je draait de %s al in proefmodus." + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "U heeft reeds een proefperiode gebruikt." + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Plan %s bestaat niet, daarom kan proefperiode niet gestart worden." + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Plan %s ondersteunt geen proefperiode." + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Geen van de %s plannen ondersteunt een proefperiode." + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Het lijkt er op dat u niet langer meer in de proefperiode zit, dus er valt niets stop te zetten." + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "Het lijkt er op dat we een tijdelijk probleem hebben met het opzeggen van uw proefperiode. Probeer het a.u.b. over enkele minuten nog eens." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Uw gratis %s proefperiode is succesvol opgezegd. " + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Versie %s is vrijgegeven." + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "A.u.b. %s downloaden." + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "de meest recente %s versie hier" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Nieuw" + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "Het lijkt erop dat je de meest recente versie hebt." + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Alles is goed!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "Verificatiemail zojuist verstuurd naar %s. Als je deze niet binnen 5 min. hebt ontvangen, kijk dan alsjeblieft in je spambox." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site opt-in geslaagd. " + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Geweldig" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "We waarderen je hulp om %s beter te maken door ons gebruiksdata te laten verzamelen. " + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Bedankt!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We zullen geen gebruiksdata meer verzenden van %s m.b.t. %s naar %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "Hou alsjeblieft je mailbox in de gaten, je zult een e-mail ontvangen via %s om de overdracht te bevestigen. Vanwege veiligheidsredenen moet je de overdracht binnen de volgende 15 min. bevestigen. Kijk eventueel in je spambox, mocht je de e-mail niet aantreffen in je inbox." + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "Bedankt voor het bevestigen van de eigendomsoverdracht. Zojuist is er een e-mail verstuurd naar %s voor de definitieve goedkeuring. " + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%s is de nieuwe eigenaar van het account." + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "Gefeliciteerd" + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Sorry, we konden de e-mail update niet voltooien. Een andere gebruiker met hetzelfde e-mailadres is reeds geregistreerd." + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "Als je het eigendom van het %s account wilt overdragen aan %s, klik dan op de Eigendom Overdragen knop. " + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Eigendom Overdragen" + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Je e-mailadres is succesvol verwerkt. Als het goed is ontvang je zometeen een e-mail met bevestigingsinstructies. " + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "Geef alsjeblieft je volledige naam." + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Je naam is succesvol bijgewerkt." + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Je hebt je %s succesvol geüpdatet." + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Voor alle duidelijkheid, de add-ons informatie van %s wordt opgehaald van een externe server." + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Aankondiging" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Hoi" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Hoe bevalt %s tot dusver? Test al onze %s premium features gedurende een%d-daagse gratis proefperiode." + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "Geen verplichting voor %s dagen - elk moment opzeggen!" + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Geen creditcard nodig" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Start gratis proefperidoe" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Hey, wist je dat %s een samenwerkingsprogramma heeft? Als je de %s goedvindt, kun je onze ambassadeur worden en wat geld verdienen!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Lees meer" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Activeer Licentie" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Verander Licentie" + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "Opt Out" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "Opt In" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activeer %s features." + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "Volg alsjeblieft deze stappen om de upgrade te voltooien" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Download de meeste recente %s versie" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Upload en activeer de gedownloade versie" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Hoe te uploaden en activeren?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sKlik hier%s om de sites te kiezen waar op je de licentie wilt activeren." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Automatische installatie werkt alleen voor opted-in gebruikers." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ongeldige Module-ID" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Premium versie reeds actief." + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "Je hebt geen geldige licentie voor de premium versie." + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Plug-in is 'Serviceware' wat betekent dat het geen premium code versie bevat. " + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Premium add-on versie is reeds geïnstalleerd." + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "Bekijk betaalde kenmerken" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Hartelijk bedankt voor het gebruik van %s en bijbehorende uitbreidingen!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Hartelijk bedankt voor het gebruik van %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om %s te blijven verbeteren." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Hartelijk bedankt voor het gebruiken van onze producten!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om deze te blijven verbeteren." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%sen bijbehorende uitbreidingen" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Producten" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Ja" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "stuur mij beveiliging & feature updates, educatieve content en aanbiedingen." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "Nee" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "stuur mij %sGEEN%s beveiliging & feature updates, educatieve content of aanbiedingen." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Laat ons alsjeblieft weten als je op de hoogte gehouden wilt worden van beveiliging & feature updates, educatieve content en zo nu en dan aanbiedingen:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "Licentiesleutel is leeg." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Vernieuw licentie" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Koop licentie" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "Er is een %s van %s beschikbaar." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "nieuwe versie" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Belangrijke Upgrade Mededeling:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "Installeren van plug-in: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Toegang tot het bestandssysteem is niet mogelijk. Bevestig alsjeblieft je inloggegevens." + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Het remote plug-in pakket bevat geen folder met de verwachte slug en hernoemen werkte niet. " + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Koop" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Start mijn gratis %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Installeer Gratis Versie Update Nu" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "Installeer Update Nu" + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Installer Gratis Versie Nu" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "Installeer Nu" + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Nieuwste Gratis Versie" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Download Nieuwste" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Activeer deze add-on" + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Activeer Gratis Versie" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Activeer" + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "Beschrijving" + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "Installatie" + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "Veelgestelde Vragen" + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Schermafbeeldingen" + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Wijzigingen Log" + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Reviews" + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Andere Notities" + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Features & Prijzen" + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "Plug-in Installatie" + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s Plan" + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Beste" + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "Maandelijks" + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Jaarlijks" + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Levenslang" + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "%s gefactureerd " + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Jaarlijks" + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Eenmalig" + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Enkele Site Licentie" + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "Onbeperkte Licenties" + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "Tot %s Sites" + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "mnd" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "jaar" + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "Prijs" + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Bespaar %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Geen verplichting voor %s - opzeggen kan altijd" + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "Na uw gratis %s, betaal slechts %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Details" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "Versie" + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Auteur" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "Laatst Geüpdatet" + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "%s geleden" + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Vereiste WordPress-versie" + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s of hoger" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "Compatible tot" + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Gedownload" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s tijd" + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s tijden" + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "WordPress.org Plug-in Pagina" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Plug-in Homepage" + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "Doneer aan deze plug-in" + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Gemiddelde Beoordeling" + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "gebaseerd op %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "%s beoordeling" + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "%s beoordelingen" + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%s ster" + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "%s sterren" + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Klik om reviews te bekijken met een beoordeling van%s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Medewerkers" + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Waarschuwing" + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Deze plug-in is nog niet getest met je huidige WordPress versie. " + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Deze plug-in is niet als compatibel aangemerkt voor je huidige WordPress versie." + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Betaalde add-on moet op Freemius geplaatst worden." + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Add-on moet op WordPress.org of Freemius geplaatst worden." + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Nieuwere Versie (%s) Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Nieuwere Gratis Versie (%s) Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "Meest Recente Versie Geïnstalleerd" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Nieuwste Gratis Versie Geïnstalleerd" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Je plan naar beneden bijstellen" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Het abonnement annuleren" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Onthou alsjeblieft dat we geen oude prijzen voor verlengingen/nieuwe abonnementen na een annulering kunnen aanhouden. Als je in de toekomst besluit om een abonnement handmatig te vernieuwen, zal de nieuwe prijs (na een prijsverhoging die meestal jaarlijks plaatsvindt) worden berekend." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Het stopzetten van de proefperiode zal de toegang tot de premium features onmiddellijk blokkeren. Weet je dat zeker?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "Je kunt nog steeds van alle %s-mogelijkheden genieten, maar je zult geen toegang hebben tot %s veiligheids- en uitbreidingsupdates, noch ondersteuning." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "Als je licentie verloopt kan je nog steeds gebruik maken van de Gratis versie, maar je zal GEEN toegang meer hebben tot de %sfeatures." + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Activeer %s Plan" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "Auto hernieuwd over %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Verloopt over %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Sync Licentie" + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Proefperiode Opzeggen" + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Wijzig Plan" + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Upgrade" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Downgrade" + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "Gratis" + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "Gratis Proefperiode" + +#: templates/account.php:196 +msgid "Account Details" +msgstr "Accountgegevens" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Verwijdering van het account zal automatisch je %s licentie deactiveren zodat je die op andere sites kan gebruiken. Als je tevens je terugkerende betalingen wilt stopzetten, klik dan op de 'Annuleer' knop en 'Downgrade' je account eerst. Weet je zeker dat je wilt doorgaan met de verwijdering?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Verwijdering is niet tijdelijk. Verwijder alleen als je deze %s niet langer wilt gebruiken. Weet je zeker dat je wilt doorgaan met de verwijdering?" + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Verwijder Account" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Deactiveer Licentie" + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Weet je zeker dat je wilt doorgaan?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Abonnement Opzeggen" + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Sync" + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "Naam" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "E-mail" + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "Gebruikers ID" + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "Geen ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Publieke Sleutel" + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Geheime Sleutel" + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Geen Geheim" + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "Proefperiode" + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Licentiesleutel" + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "niet geverifieerd" + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Verlopen" + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Premium versie" + +#: templates/account.php:504 +msgid "Free version" +msgstr "Gratis versie" + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Verifieer E-mail" + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Download %s Versie" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Toon" + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Wat is je %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Bewerk" + +#: templates/account.php:588 +msgid "Sites" +msgstr "Sites" + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Zoek op adres" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Adres" + +#: templates/account.php:610 +msgid "License" +msgstr "Licentie" + +#: templates/account.php:611 +msgid "Plan" +msgstr "Plan" + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Licentie" + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "Verberg" + +#: templates/account.php:765 +msgid "Processing" +msgstr "Processing" + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Annuleren %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "proefperiode" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "%s wordt geannuleerd..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "abonnement" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactiveren van je licentie zal alle premium features blokkeren, maar geeft je de mogelijkheid de licentie op een andere site te activeren. Weet je zeker dat je wilt doorgaan?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Bekijk details" + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Add-ons voor %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "We konden de add-ons lijst niet laden. Dat is waarschijnlijk een probleem aan onze kant, kom alsjeblieft over enkele minuten terug." + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Activeer" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Afsluiten" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s sec" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "Automatische Installatie" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "Een geautomatiseerde download en installatie van %s (betaalde versie) van %s zal starten binnen %s. Als je dit handmatig wil doen, klik dan nu op de annuleer knop." + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "Het installatieproces is gestart en kan enkele minuten duren om te voltooien. Wacht alsjeblieft totdat dat gebeurt is - deze pagina niet verversen." + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Annuleer Installatie" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Afrekenen" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "PCI-comform" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "Hoi %s," + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Toestaan & Ga Verder" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Activatiemail opnieuw versturen" + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "Bedankt %s!" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "Akkoord & Activeer Licentie" + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "Bedankt voor het aanschaffen van %s! Om te beginnen, voer alsjeblieft je licentiesleutel in:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging en feature update notificaties, educatieve content, aanbiedingen, en niet-gevoelige diagnostische tracking met %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging en feature update notificaties, en niet-gevoelige diagnostische tracking met %4$s." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging & feature update notificaties, educatieve content, aanbiedingen, en niet-gevoelige diagnostische tracking met %4$s. Als je deze stap overslaat, geen probleem! %1$szal ook dan gewoon 100% werken. " + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Mis nooit een belangrijke update - opt-in voor onze beveiliging & feature updates notificaties, en niet-gevoelige diagnostische tracking met %4$s. Als je deze stap overslaat, geen probleem! %1$szal ook dan gewoon 100% werken. " + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We zijn verheugd om Freemius network-level integratie te introduceren." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "Tijdens het update proces detecteerden we %dsite(s) waarvoor de licentie nog niet geactiveerd is." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "Als je de %s op deze sites wil gebruiken, voer dan alsjeblieft de licentiesleutel hieronder in en klik op de activatie-knop." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s betaalde mogelijkheden" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Je kunt dat eventueel ook nu overslaan en de licentie later in je %s netwerk-niveau Account pagina activeren. " + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "Tijdens het update proces detecteerden we %dsite(s) in het netwerk die jouw aandacht vereisen." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Licentiesleutel" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Kan je je licentiesleutel niet vinden?" + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "Sla Over" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegeren aan Site Beheerders" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "Al je er op klikt, zal deze beslissing gedelegeerd worden aan de beheerders van de sites. " + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "Je Profiel Overzicht" + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Naam en e-mailadres" + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "Je Site Overzicht" + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "Site URL, WP versie, PHP info, plug-ins & thema's" + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Admin Mededelingen" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "Updates, aankondigingen, marketing, geen spam" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Huidige %s Gebeurtenissen" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "Activatie, deactivatie en deïnstallatie" + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "Nieuwsbrief" + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "De %1$s zal periodiek data verzenden naar %2$s om te controleren op beveiliging en feature updates en om te verifiëren of je licentie geldig is." + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Welke toestemmingen worden er verleend?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "Heb je geen licentiesleutel?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "Heb je een licentiesleutel?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Privacybeleid" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "Licentieovereenkomst" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "Servicevoorwaarden" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "E-mail versturen" + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "Activeren" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "Contact" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Uit" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Aan" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "Debugging" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "Acties" + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Weet u zeker dat u alle Freemius data wilt verwijderen?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Verwijder All Accounts" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "API-Cache Leegmaken" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Updates Transients Opschonen" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Synchroniseer Data Vanaf Server" + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Zet Opties over naar Netwerk" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Laad DB-optie" + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "Activeer DB-Optie" + +#: templates/debug.php:182 +msgid "Key" +msgstr "Sleutel" + +#: templates/debug.php:183 +msgid "Value" +msgstr "Waarde" + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "SDK Versies" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "SDK Pad" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Module Pad" + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "Is Actief" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Plug-ins" + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Thema's" + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "Slug" + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Titel" + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "Freemius Status" + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Netwerk Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Netwerk Gebruiker" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Verbonden" + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Geblokkeerd" + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simuleer Trial Actie" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simuleer Netwerk Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s Installaties" + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Sites" + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Verwijder" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Uitbreidingen van module %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Gebruikers" + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Geverifieerd" + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s Licenties" + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "Plug-in ID" + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "Plan ID" + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Quota" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Geactiveerd" + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Geblokkeerd" + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Verloopdatum" + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Debug Log" + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Alle Types" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Alle Requests" + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Bestand" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Functie" + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "Proces-ID" + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Logger" + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Bericht" + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Filter" + +#: templates/debug.php:587 +msgid "Download" +msgstr "Download" + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Type" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Tijdstempel" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "Beveiligde HTTPS %s pagina, loopt via een extern domein" + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Ondersteuning" + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "ms" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "Aanvragen" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Bijwerken" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "Facturering" + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Bedrijfsnaam" + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "Btw-nummer" + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Adresregel %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Stad" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "Plaats" + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Postcode" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Land" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Selecteer Land" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Staat" + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "Provincie" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Betalingen" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Datum" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "Bedrag" + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Factuur" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Methodes" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Code" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Lengte" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Pad" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "Body" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Resultaat" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Start" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Einde" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Log" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Binnen %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s geleden" + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "sec" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Synchronisatie Plug-ins & Thema's" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Totaal" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "Laatste" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Geplande Crons" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Module" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Moduletype" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Cron Type" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Volgende" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "Niet-verlopende" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Meld je aan om een affiliate partner te worden" + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Je samenwerkingsaanvraag voor %s is geaccepteerd! Log in op je samenwerkingsomgeving op: %s." + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "Bedankt voor je aanvraag voor deelname aan ons samenwerkingsprogramma. We zullen binnen 14 dagen je gegevens doornemen, waarna we je aanvullende informatie zullen sturen." + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Je affiliate account is tijdelijk geschorst." + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "Bedankt voor je aanvraag voor deelname aan ons affiliate programma, helaas, op dit moment hebben we besloten je aanvraag af te wijzen. Probeer het alsjeblieft over 30 dagen nog eens." + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Als gevolg van het overtreden van onze affiliate voorwaarden, hebben we besloten je affiliate account tijdelijk te blokkeren. Neem voor eventuele vragen alsjeblieft contact op met support." + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Vind je de %s goed? Word dan onze ambassadeur en verdien cash ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Verwijs nieuwe klanten naar onze %s en krijg %s commissie op iedere door jou doorverwezen, geslaagde verkoop!" + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Programma Samenvatting" + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s commissie als een klant een nieuwe licentie koopt. " + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Krijg een commissie voor automatische abonnementsverlengingen." + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s tracking cookie na eerste bezoek om je verdienpotentieel te maximaliseren." + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Onbeperkte commissies." + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "%s minimum uitbetalingsbedrag." + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Uitbetalingen zijn in USD en worden maandelijks uitgevoerd via PayPal" + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Omdat wij 30 dagen reserveren voor eventuele terugstortingen, betalen we alleen commissies uit die ouder dan 30 dagen zijn." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Affiliate" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "E-mailadres" + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Volledige naam" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "PayPal account e-mailadres" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Waar ga je de %s promoten?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Voer de domeinnaam in van je website of andere websites waar vanaf je van plan bent de %ste gaan promoten." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Voeg nog een domein toe" + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Extra Domeinen" + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Extra domeinen vanaf waar je het product gaat promoten." + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Promotie methodes" + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Social media (Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Mobiele apps" + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "Website, mail, and social media statistieken (optioneel)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "Voel je alsjeblieft vrij om elke relevante website of social media statistieken met ons te delen, bijvoorbeeld maandelijkse unieke bezoekers, aantal e-mail abonnees , volgers, etc. (we zullen deze informatie vertrouwelijk houden)." + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Hoe ga je ons promoten?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "Geef alsjeblieft zo gedetailleerd als mogelijk aan hoe je van plan bent om %s te gaan promoten." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Annuleer" + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Wordt een affiliate" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "Voer aalsjeblieft de licentiesleutel in die je ontving in de e-mail direct na de aankoop:" + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Update Licentie" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "Opt Out" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "Opt In" + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Het bijhouden van het gebruik wordt gedaan om %s te verbeteren. De gebruikerservaring te verbeteren, de prioriteit van nieuwe features te bepalen, en meer goede zaken. We zouden het heel erg op prijs stellen als je ons toch weer toestaat het gebruik te volgen. " + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "Door op \"Opt Out\" te klikken, zullen wij niet langer gegevens van %s verzenden naar %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "Er is een nieuwe versie van %s beschikbaar." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "%svoor toegang to versie %s beveiliging & features updates en support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "Nieuwe Versie Beschikbaar" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Afsluiten" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Verzend Licentiesleutel" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Voer hieronder het e-mailadres in dat je gebruikt hebt voor de upgrade en we zullen je jouw licentiesleutel opnieuw toesturen." + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Het deactiveren en deïnstalleren van de %s zal de licentie automatisch uitschakelen, die je dan kan gebruiken op een andere site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "Mocht je NIET van plan zijn om deze %s te gebruiken op deze site (of op een andere site) - wil je dan het %s ook opzeggen?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "licentie" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Annuleer %s - Ik heb niet meer enige beveiligings- en uitbreidingsupdates of ondersteuning voor %s nodig, omdat ik niet van plan ben de %sop deze of enige andere site te gebruiken." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Annuleer %s niet - Ik wil nog steeds zowel beveiligings- en uitbreidingsupdates ontvangen als contact kunnen opnemen met Support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Als je licentie afloopt, zul je %s niet meer kunnen gebruiken, tenzij je het opnieuw activeert met een geldige Premium-licentie." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "%s annuleren?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Doorgaan" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Annuleer %s & Ga Door" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "U bent 1-klik verwijderd van het starten van uw %1$s-daagse gratis proefperiode van het %2$s plan." + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Voordat we de proefperiode kunnen starten, vragen we je, in overeenstemming met de Wordpress.org-richtlijnen, in te stemmen je gebruikers- en niet-sensitieve site informatie door de %s periodiek te laten verzenden naar %s om te controleren op nieuwe versies en je proefversie te valideren." + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Premium" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activeer licentie op alle sites in het netwerk." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Pas toe op alle sites in het netwerk." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activeer licentie op alle in behandeling zijnde sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Pas toe op alle in behandeling zijnde sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "toestaan" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "deligeren" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "overslaan" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Klik voor het op volle-grootte bekijken van schermafbeelding %d" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Onbeperkte Updates" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Localhost" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "%s beschikbaar" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "Laatste licentie" + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Geannuleerd" + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "Geen verloopdatum" + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Naam Eigenaar" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "E-mail Eigenaar" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "ID Eigenaar" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Abonnement" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Sorry voor het ongemak en we zijn er om je te helpen als je daartoe de kans geeft.." + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "Contacteer Support" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Anonieme terugkoppeling" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Deactiveer" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Activeer %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Snelle terugkoppeling" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "We zouden het zeer op prijs stellen, als je even hebt, om ons alsjeblieft te laten weten waarom je gaat %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "deactiveren" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "overschakelen" + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Verstuur & %s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "Wilt je alsjeblieft zo vriendelijk zijn om de reden te vermelden, zodat wij verbeteringen kunnen doorvoeren." + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Ja - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "Sla over & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Klik hier om de plug-in anoniem te gebruiken" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Misschien heb je het gemist, maar je hoeft geen gegevens te delen en kunt de opt-in %s." diff --git a/freemius/languages/freemius-ru_RU 2.mo b/freemius/languages/freemius-ru_RU 2.mo new file mode 100644 index 0000000000000000000000000000000000000000..9f408a8856b569aa75e848575de78dd5cd656cfd GIT binary patch literal 69773 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 ZL5V_ppxrwOTZ_aDmN`c8sdibJ{})p&OIrW{ literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-ru_RU 2.po b/freemius/languages/freemius-ru_RU 2.po new file mode 100644 index 0000000..092a3c1 --- /dev/null +++ b/freemius/languages/freemius-ru_RU 2.po @@ -0,0 +1,2508 @@ +# Copyright (C) 2019 freemius +# This file is distributed under the same license as the freemius package. +# Translators: +# Robert Premmerce , 2018 +msgid "" +msgstr "" +"Project-Id-Version: WordPress SDK\n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2019-06-05 13:40+0000\n" +"Last-Translator: Vova Feldman \n" +"Language: ru_RU\n" +"Language-Team: Russian (Russia) (http://www.transifex.com/freemius/wordpress-sdk/language/ru_RU/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" +"MIME-Version: 1.0\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: includes/class-freemius.php1838, templates/account.php:769 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." + +#: includes/class-freemius.php:1845 +msgid "Would you like to proceed with the update?" +msgstr "Would you like to proceed with the update?" + +#: includes/class-freemius.php:2053 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "Freemius SDK не удалоÑÑŒ найти оÑновной файл плагина. ПожалуйÑта, ÑвÑжитеÑÑŒ Ñ sdk@freemius.com Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ ошибкой." + +#: includes/class-freemius.php:2055 +msgid "Error" +msgstr "Ошибка" + +#: includes/class-freemius.php:2445 +msgid "I found a better %s" +msgstr "Я нашел лучший %s" + +#: includes/class-freemius.php:2447 +msgid "What's the %s's name?" +msgstr "Какое название %s?" + +#: includes/class-freemius.php:2453 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "Это Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ %s. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¸Ñ‚ проверка на наличие ошибок. " + +#: includes/class-freemius.php:2455 +msgid "Deactivation" +msgstr "ДеактивациÑ" + +#: includes/class-freemius.php:2456 +msgid "Theme Switch" +msgstr "Переключатель шаблона " + +#: includes/class-freemius.php2465, templates/forms/resend-key.php:24 +msgid "Other" +msgstr "Другие" + +#: includes/class-freemius.php:2473 +msgid "I no longer need the %s" +msgstr "%s больше не понадобитÑÑ." + +#: includes/class-freemius.php:2480 +msgid "I only needed the %s for a short period" +msgstr "%s требовалаÑÑŒ на короткое времÑ" + +#: includes/class-freemius.php:2486 +msgid "The %s broke my site" +msgstr "%s повредила мой Ñайт" + +#: includes/class-freemius.php:2493 +msgid "The %s suddenly stopped working" +msgstr "%s внезапно переÑтала работать " + +#: includes/class-freemius.php:2503 +msgid "I can't pay for it anymore" +msgstr "Я больше не могу оплачивать Ñто. " + +#: includes/class-freemius.php:2505 +msgid "What price would you feel comfortable paying?" +msgstr "ÐšÐ°ÐºÐ°Ñ ÑтоимоÑть была бы Ð´Ð»Ñ Ð’Ð°Ñ Ð¿Ñ€Ð¸ÐµÐ¼Ð»ÐµÐ¼Ð¾Ð¹? " + +#: includes/class-freemius.php:2511 +msgid "I don't like to share my information with you" +msgstr "Я не хочу делитьÑÑ Ð»Ð¸Ñ‡Ð½Ð¾Ð¹ информацией Ñ Ð’Ð°Ð¼Ð¸" + +#: includes/class-freemius.php:2532 +msgid "The %s didn't work" +msgstr "%s не Ñработала" + +#: includes/class-freemius.php:2542 +msgid "I couldn't understand how to make it work" +msgstr "Я не могу понÑть как Ñделать так, чтобы оно работало" + +#: includes/class-freemius.php:2550 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "%s Ð¾Ñ‚Ð»Ð¸Ñ‡Ð½Ð°Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñть, но мне нужен определенный функционал, который вы не поддерживаете. " + +#: includes/class-freemius.php:2552 +msgid "What feature?" +msgstr "Какой функционал?" + +#: includes/class-freemius.php:2556 +msgid "The %s is not working" +msgstr "%s не работает" + +#: includes/class-freemius.php:2558 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "ПожалуйÑта, Ñообщите о функционале, который не работает, чтобы мы Ñмогли иÑправить его Ð´Ð»Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ¹ÑˆÐµÐ³Ð¾ иÑпользованиÑ. " + +#: includes/class-freemius.php:2562 +msgid "It's not what I was looking for" +msgstr "Это не то, что Ñ Ð¸Ñкал. " + +#: includes/class-freemius.php:2564 +msgid "What you've been looking for?" +msgstr "Что именно Ð’Ñ‹ ищите? " + +#: includes/class-freemius.php:2568 +msgid "The %s didn't work as expected" +msgstr "%s не Ñработала как ожидалоÑÑŒ" + +#: includes/class-freemius.php:2570 +msgid "What did you expect?" +msgstr "Каковы были Ваши ожиданиÑ? " + +#: includes/class-freemius.php3425, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "ИÑправление ошибок Freemius" + +#: includes/class-freemius.php:4177 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "Я не знаю, что такое ÑURL и как его уÑтановить. ПожалуйÑта, помогите мне." + +#: includes/class-freemius.php:4179 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "Мы обÑзательно ÑвÑжемÑÑ Ñ Ð’Ð°ÑˆÐ¸Ð¼ хоÑтинг провайдером и найдем решение. Как только у Ð½Ð°Ñ Ð¿Ð¾ÑвитÑÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ, Вам будет отправлено пиÑьмо на почту. " + +#: includes/class-freemius.php:4186 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "Отлично! ПожалуйÑта, уÑтановите ÑURL и активируйте его в Вашем файле php.ini .Также, найдите директиву 'disable_functions' в файле php.ini и удалите вÑе неактивные методы которые начинаютÑÑ Ð½Ð° 'curl_'. Чтобы убедитÑÑ, что Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÑˆÐ»Ð° уÑпешно, иÑпользуйте 'phpinfo()'. ПоÑле активации, деактивируйте %s и Ñнова активируйте ее. " + +#: includes/class-freemius.php:4291 +msgid "Yes - do your thing" +msgstr "Да, делайте то, что Вам нужно. " + +#: includes/class-freemius.php:4296 +msgid "No - just deactivate" +msgstr "Ðет. Ðужно деактивировать. " + +#: includes/class-freemius.php4341, includes/class-freemius.php4850, +#: includes/class-freemius.php5999, includes/class-freemius.php12682, +#: includes/class-freemius.php16045, includes/class-freemius.php16133, +#: includes/class-freemius.php16299, includes/class-freemius.php18758, +#: includes/class-freemius.php18768, includes/class-freemius.php19404, +#: includes/class-freemius.php20277, includes/class-freemius.php20392, +#: includes/class-freemius.php20536, templates/add-ons.php:54 +msgctxt "exclamation" +msgid "Oops" +msgstr "УпÑ!" + +#: includes/class-freemius.php:4410 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "СпаÑибо, что предоÑтавили нам возможноÑть иÑправить ошибку. Сообщение уже отправлено нашим техничеÑким ÑпециалиÑтам. Мы Ñ Ð’Ð°Ð¼Ð¸ ÑвÑжемÑÑ, как только будет Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ %s. Благодарны за понимание. " + +#: includes/class-freemius.php:4847 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "%s не работает без %s." + +#: includes/class-freemius.php:4848 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "%s не может работать без плагина. " + +#: includes/class-freemius.php5020, includes/class-freemius.php5045, +#: includes/class-freemius.php:19475 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "ÐÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° API. ПожалуйÑта, ÑвÑжитеÑÑŒ Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ %s в котором была обнаружена ошибка. " + +#: includes/class-freemius.php:5687 +msgid "Premium %s version was successfully activated." +msgstr "Премиум верÑÐ¸Ñ %s была уÑпешно активирована. " + +#: includes/class-freemius.php5699, includes/class-freemius.php:7567 +msgctxt "" +msgid "W00t" +msgstr "Вау!" + +#: includes/class-freemius.php:5714 +msgid "You have a %s license." +msgstr "У Ð’Ð°Ñ ÐµÑть Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ %s." + +#: includes/class-freemius.php5718, includes/class-freemius.php15466, +#: includes/class-freemius.php15477, includes/class-freemius.php18669, +#: includes/class-freemius.php18999, includes/class-freemius.php19065, +#: includes/class-freemius.php:19229 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "Ура!" + +#: includes/class-freemius.php:5982 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "БеÑплатный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s закончилÑÑ. Этот плагин ÑвлÑетÑÑ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ продуктом и он был деактивирован автоматичеÑки. ЕÑли Ð’Ñ‹ планируете дальнейшее его иÑпользование, пожалуйÑта купите лицензию. " + +#: includes/class-freemius.php:5986 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "%s ÑвлÑетÑÑ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ продуктом. Ðеобходимо купить лицензию перед активацией плагина. " + +#: includes/class-freemius.php5995, templates/add-ons.php130, +#: templates/account/partials/addon.php:343 +msgid "More information about %s" +msgstr "Больше информации о %s" + +#: includes/class-freemius.php:5996 +msgid "Purchase License" +msgstr "Купите лицензию " + +#: includes/class-freemius.php6931, templates/connect.php:163 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "Мы отправили Вам пиÑьмо Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ %s на Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ %s. ПожалуйÑта, нажмите на кнопку активации в Ñтом пиÑьме %s. " + +#: includes/class-freemius.php:6935 +msgid "start the trial" +msgstr "Ðачать теÑтовый период" + +#: includes/class-freemius.php6936, templates/connect.php:167 +msgid "complete the install" +msgstr "Закончить уÑтановку" + +#: includes/class-freemius.php:7049 +msgid "You are just one step away - %s" +msgstr "Вам оÑталоÑÑŒ ÑовÑем немножко %s" + +#: includes/class-freemius.php:7052 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "Закончить активацию %s ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/class-freemius.php:7130 +msgid "We made a few tweaks to the %s, %s" +msgstr "Мы уÑовершенÑтвовали в %s, %s Ð´Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ работы " + +#: includes/class-freemius.php:7134 +msgid "Opt in to make \"%s\" better!" +msgstr "Opt in to make \"%s\" better!" + +#: includes/class-freemius.php:7566 +msgid "The upgrade of %s was successfully completed." +msgstr "Обновление %s было уÑпешно завершено" + +#: includes/class-freemius.php9728, includes/class-fs-plugin-updater.php975, +#: includes/class-fs-plugin-updater.php1170, +#: includes/class-fs-plugin-updater.php1177, +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "Функционал плагина " + +#: includes/class-freemius.php9730, templates/account.php313, +#: templates/account.php321, templates/debug.php361, templates/debug.php:522 +msgid "Plugin" +msgstr "Плагин " + +#: includes/class-freemius.php9731, templates/account.php314, +#: templates/account.php322, templates/debug.php361, templates/debug.php522, +#: templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "Шаблон " + +#: includes/class-freemius.php:12148 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "An unknown error has occurred while trying to set the user's beta mode." + +#: includes/class-freemius.php:12549 +msgid "Invalid site details collection." +msgstr "Invalid site details collection." + +#: includes/class-freemius.php:12669 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "К Ñожалению, Ваш почтовый Ð°Ð´Ñ€ÐµÑ Ð½Ðµ найден в ÑиÑтеме. Ð’Ñ‹ уверены, что предоÑтавили правильный адреÑ? " + +#: includes/class-freemius.php:12671 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð²Ñ‹Ð´Ð°Ð½Ð½Ð°Ñ Ð½Ð° Ñтот Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð½Ðµ была найдена. Ð’Ñ‹ уверены, что предоÑтавили правильный Ñлектронный адреÑ?" + +#: includes/class-freemius.php:12945 +msgid "Account is pending activation." +msgstr "Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ в процеÑÑе активации" + +#: includes/class-freemius.php13057, +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "Buy a license now" + +#: includes/class-freemius.php13069, +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "Renew your license now" + +#: includes/class-freemius.php:13073 +msgid "%s to access version %s security & feature updates, and support." +msgstr "%s to access version %s security & feature updates, and support." + +#: includes/class-freemius.php:15448 +msgid "%s activation was successfully completed." +msgstr "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ %s была уÑпешно завершена" + +#: includes/class-freemius.php:15462 +msgid "Your account was successfully activated with the %s plan." +msgstr "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ была уÑпешно активирована ÑоглаÑно плану %s" + +#: includes/class-freemius.php15473, includes/class-freemius.php:19061 +msgid "Your trial has been successfully started." +msgstr "Ваш теÑтовый период уÑпешно начат" + +#: includes/class-freemius.php16043, includes/class-freemius.php16131, +#: includes/class-freemius.php:16297 +msgid "Couldn't activate %s." +msgstr "Ðевозможно активировать %s" + +#: includes/class-freemius.php16044, includes/class-freemius.php16132, +#: includes/class-freemius.php:16298 +msgid "Please contact us with the following message:" +msgstr "ПожалуйÑта, напишите нам Ñообщение Ñледующего ÑодержаниÑ:" + +#: includes/class-freemius.php:16128 +msgid "An unknown error has occurred." +msgstr "An unknown error has occurred." + +#: includes/class-freemius.php16655, includes/class-freemius.php:21409 +msgid "Upgrade" +msgstr "Сделать апгрейд " + +#: includes/class-freemius.php:16661 +msgid "Start Trial" +msgstr "Ðачать теÑтовый период" + +#: includes/class-freemius.php:16663 +msgid "Pricing" +msgstr "Цены " + +#: includes/class-freemius.php16742, includes/class-freemius.php:16744 +msgid "Affiliation" +msgstr "ПартнерÑтво " + +#: includes/class-freemius.php16772, includes/class-freemius.php16774, +#: templates/account.php177, templates/debug.php:326 +msgid "Account" +msgstr "Личный кабинет" + +#: includes/class-freemius.php16787, includes/class-freemius.php16789, +#: includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "Контакты " + +#: includes/class-freemius.php16799, includes/class-freemius.php16801, +#: includes/class-freemius.php21423, templates/account.php105, +#: templates/account/partials/addon.php:45 +msgid "Add-Ons" +msgstr "ÐаÑтройки плагина " + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "←" + +#: includes/class-freemius.php:16835 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "➤" + +#: includes/class-freemius.php16837, templates/pricing.php:102 +msgctxt "noun" +msgid "Pricing" +msgstr "Цены" + +#: includes/class-freemius.php17050, +#: includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "Форум поддержки " + +#: includes/class-freemius.php:17995 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±Ñ‹Ð» уÑпешно подтвержден и Ð’Ñ‹ проÑто молодец!" + +#: includes/class-freemius.php:17996 +msgctxt "a positive response" +msgid "Right on" +msgstr "Ð’Ñе верно!" + +#: includes/class-freemius.php:18660 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "Ваш %s план был уÑпешно обновлен" + +#: includes/class-freemius.php:18662 +msgid "%s Add-on was successfully purchased." +msgstr "Покупка %s плагина уÑпешно ÑоÑтоÑлаÑÑŒ" + +#: includes/class-freemius.php:18665 +msgid "Download the latest version" +msgstr "Скачай поÑледнюю верÑию" + +#: includes/class-freemius.php:18751 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" + +#: includes/class-freemius.php18757, includes/class-freemius.php19188, +#: includes/class-freemius.php:19277 +msgid "Error received from the server:" +msgstr "Ошибка Ñервера" + +#: includes/class-freemius.php:18767 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "ВероÑтно один из параметров ÑвлÑетÑÑ Ð½ÐµÐ²ÐµÑ€Ð½Ñ‹Ð¼. Обновите Ñвой Public Key, Secret Key&User ID и повторите попытку." + +#: includes/class-freemius.php18961, includes/class-freemius.php19193, +#: includes/class-freemius.php19248, includes/class-freemius.php:19351 +msgctxt "" +msgid "Hmm" +msgstr "Хм..." + +#: includes/class-freemius.php:18974 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "ВероÑтно Ð’Ñ‹ вÑе еще пользуетеÑÑŒ ÑервиÑом ÑоглаÑно плану %s. ЕÑли Ð’Ñ‹ обновлÑли или менÑли Ñвой тарифный план, то вероÑтно ÑущеÑтвуют какие-то трудноÑти ÑвÑзанные Ñ Ð’Ð°ÑˆÐ¸Ð¼ программным обеÑпечением. Извините. " + +#: includes/class-freemius.php18975, templates/account.php107, +#: templates/add-ons.php191, templates/account/partials/addon.php:47 +msgctxt "trial period" +msgid "Trial" +msgstr "ТеÑтовый период" + +#: includes/class-freemius.php:18980 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "Я провел апгрейд аккаунта, но при попытке Ñинхронизировать лицензию, мой тарифный план не менÑетÑÑ. " + +#: includes/class-freemius.php18984, includes/class-freemius.php:19043 +msgid "Please contact us here" +msgstr "ПожалуйÑта, напишите нам Ñообщение здеÑÑŒ. " + +#: includes/class-freemius.php:18995 +msgid "Your plan was successfully activated." +msgstr "Your plan was successfully activated." + +#: includes/class-freemius.php:18996 +msgid "Your plan was successfully upgraded." +msgstr "Ваш тарифный план был уÑпешно изменен. " + +#: includes/class-freemius.php:19013 +msgid "Your plan was successfully changed to %s." +msgstr "Ваш тарифный план был уÑпешно изменен на %s." + +#: includes/class-freemius.php:19029 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии закончилÑÑ. Ð’Ñ‹ можете продолжать пользоватьÑÑ Ð±ÐµÑплатной верÑией %s на беÑÑрочной оÑнове." + +#: includes/class-freemius.php:19031 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19039 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° аннулирована. ЕÑли Ð’Ñ‹ Ñчитаете, что Ñто ошибка, пожалуйÑта ÑвÑжитеÑÑŒ Ñ Ð½Ð°ÑˆÐµÐ¹ Ñлужбой поддержки. " + +#: includes/class-freemius.php:19052 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии закончен. Ð’Ñ‹ можете продолжать пользоватьÑÑ Ð²Ñеми возможноÑÑ‚Ñми %s продлив Вашу лицензию. Ð’Ñ‹ также будете получать доÑтуп к обновлениÑм и поддержке. " + +#: includes/class-freemius.php:19075 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "Your free trial has expired. You can still continue using all our free features." + +#: includes/class-freemius.php:19077 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." + +#: includes/class-freemius.php:19184 +msgid "It looks like the license could not be activated." +msgstr "ВероÑтно возникли трудноÑти Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹ лицензии. " + +#: includes/class-freemius.php:19226 +msgid "Your license was successfully activated." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° уÑпешно активирована. " + +#: includes/class-freemius.php:19252 +msgid "It looks like your site currently doesn't have an active license." +msgstr "ВероÑтно Ваш Ñайт не иÑпользует активную лицензию ÑейчаÑ. " + +#: includes/class-freemius.php:19276 +msgid "It looks like the license deactivation failed." +msgstr "ВероÑтно Ð´ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸ не ÑоÑтоÑлаÑÑŒ. " + +#: includes/class-freemius.php:19304 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "Ваша Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð±Ñ‹Ð»Ð° уÑпешно деактивирована и Ð’Ñ‹ Ñнова пользуетеÑÑŒ планом %s." + +#: includes/class-freemius.php:19305 +msgid "O.K" +msgstr "O.K." + +#: includes/class-freemius.php:19358 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." + +#: includes/class-freemius.php:19367 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "Your subscription was successfully cancelled. Your %s plan license will expire in %s." + +#: includes/class-freemius.php:19409 +msgid "You are already running the %s in a trial mode." +msgstr "Ð’Ñ‹ уже пользуетеÑÑŒ теÑтовой верÑией %s " + +#: includes/class-freemius.php:19420 +msgid "You already utilized a trial before." +msgstr "Ð’Ñ‹ уже иÑпользовали Ваш теÑтовый период" + +#: includes/class-freemius.php:19434 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "Тарифного плана % не ÑущеÑтвует, поÑтому Ð’Ñ‹ не можете начать теÑтовый период. " + +#: includes/class-freemius.php:19445 +msgid "Plan %s does not support a trial period." +msgstr "Тарифный план % не предуÑматривает теÑтового периода. " + +#: includes/class-freemius.php:19456 +msgid "None of the %s's plans supports a trial period." +msgstr "Тарифные планы %s не предуÑматривают теÑтовый период. " + +#: includes/class-freemius.php:19506 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "Возможно, Ваш теÑтовый период уже закончилÑÑ. " + +#: includes/class-freemius.php:19542 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "К Ñожалению у Ð½Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð¸ трудноÑти Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¾Ð¹ Вашего теÑтового периода. ПожалуйÑта, повторите попытку через неÑколько минут." + +#: includes/class-freemius.php:19561 +msgid "Your %s free trial was successfully cancelled." +msgstr "Ваш беÑплатный теÑтовый период был уÑпешно отменен. " + +#: includes/class-freemius.php:19877 +msgid "Version %s was released." +msgstr "Релиз верÑии %s ÑоÑтоÑлÑÑ. " + +#: includes/class-freemius.php:19877 +msgid "Please download %s." +msgstr "ПожалуйÑта, Ñкачайте %s" + +#: includes/class-freemius.php:19884 +msgid "the latest %s version here" +msgstr "ПоÑледнÑÑ Ð²ÐµÑ€ÑÐ¸Ñ %s здеÑÑŒ" + +#: includes/class-freemius.php:19889 +msgid "New" +msgstr "Ðовое " + +#: includes/class-freemius.php:19894 +msgid "Seems like you got the latest release." +msgstr "ВероÑтно, Ð’Ñ‹ пользуетеÑÑŒ поÑледней верÑией" + +#: includes/class-freemius.php:19895 +msgid "You are all good!" +msgstr "Ð’Ñе прошло хорошо!" + +#: includes/class-freemius.php:20165 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "ПиÑьмо подтверждение было только что отправлено на %s. ЕÑли Ð’Ñ‹ не получите его через 5 минут, пожалуйÑта, проверьте папку Ñпам." + +#: includes/class-freemius.php:20304 +msgid "Site successfully opted in." +msgstr "Site successfully opted in." + +#: includes/class-freemius.php20305, includes/class-freemius.php:21125 +msgid "Awesome" +msgstr "Отлично!" + +#: includes/class-freemius.php20321, templates/forms/optout.php:32 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "Ð’Ñ‹ очень помогаете нам ÑовершенÑтвовать %s Ñ€Ð°Ð·Ñ€ÐµÑˆÐ°Ñ Ñледить за некоторыми данными о пользовании. " + +#: includes/class-freemius.php:20322 +msgid "Thank you!" +msgstr "Thank you!" + +#: includes/class-freemius.php:20329 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "We will no longer be sending any usage data of %s on %s to %s." + +#: includes/class-freemius.php:20458 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "ПожалуйÑта, проверьте Ñвою Ñлектронную почту. Ð’Ñ‹ должны были получить пиÑьмо от %s Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ñмены прав иÑпользованиÑ. По причинам безопаÑноÑти, Ð’Ñ‹ должны подтвердить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð° протÑжении 15 минут. ЕÑли пиÑьмо не пришло, пожалуйÑта проверьте папку Ñпам. " + +#: includes/class-freemius.php:20464 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "СпаÑибо, что подтвердили изменение прав иÑпользованиÑ. Вам отправлено пиÑьмо на %s Ð´Ð»Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ подтверждениÑ. " + +#: includes/class-freemius.php:20469 +msgid "%s is the new owner of the account." +msgstr "%Ñ ÑвлÑетÑÑ Ð½Ð¾Ð²Ñ‹Ð¼ владельцем аккаунта" + +#: includes/class-freemius.php:20471 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "ПоздравлениÑ! " + +#: includes/class-freemius.php:20491 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "Извините, нам не удалоÑÑŒ обновить Ñлектронный адреÑ. Другой пользователь Ñ Ñ‚Ð°ÐºÐ¸Ð¼ же адреÑом уже был зарегиÑтрирован. " + +#: includes/class-freemius.php:20492 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "ЕÑли Ð’Ñ‹ передаете права Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ %s %s нажмите кнопку \" Сменить права иÑпользованиÑ\"" + +#: includes/class-freemius.php:20499 +msgid "Change Ownership" +msgstr "Сменить владельца лицензии " + +#: includes/class-freemius.php:20507 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "Ваш Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð±Ñ‹Ð» уÑпешно обновлен. Через неÑколько минут Ð’Ñ‹ получите пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ" + +#: includes/class-freemius.php:20519 +msgid "Please provide your full name." +msgstr "ПожалуйÑта, введите Ваше полное имÑ" + +#: includes/class-freemius.php:20524 +msgid "Your name was successfully updated." +msgstr "Ваше Ð¸Ð¼Ñ Ð±Ñ‹Ð»Ð¾ уÑпешно обновлено" + +#: includes/class-freemius.php:20585 +msgid "You have successfully updated your %s." +msgstr "Ð’Ñ‹ уÑпешно обновили Ваш %s" + +#: includes/class-freemius.php:20725 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "Сообщаем, что Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ дополнительных наÑтройках %s предоÑтавлÑетÑÑ Ñо Ñтороннего Ñервера. " + +#: includes/class-freemius.php:20726 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "Внимание!" + +#: includes/class-freemius.php:21165 +msgctxt "exclamation" +msgid "Hey" +msgstr "Привет!" + +#: includes/class-freemius.php:21165 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "Тебе нравитÑÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÑŒÑÑ %s? ВоÑпользуйÑÑ Ð²Ñеми нашими премиум возможноÑÑ‚Ñми на протÑжении %d - дневного теÑтового периода. " + +#: includes/class-freemius.php:21173 +msgid "No commitment for %s days - cancel anytime!" +msgstr "БеÑплатное пользование на протÑжении %s дней. Отмена в любое времÑ. " + +#: includes/class-freemius.php:21174 +msgid "No credit card required" +msgstr "Ðе требуютÑÑ Ð´Ð°Ð½Ð½Ñ‹Ðµ платежной карты" + +#: includes/class-freemius.php21181, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "Ðачни теÑтовый период!" + +#: includes/class-freemius.php:21258 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "Привет! Знали ли Ð’Ñ‹, что %s предоÑтавлÑет реферальную программу? ЕÑли Вам нравитÑÑ %s, Ð’Ñ‹ можете Ñтать нашим предÑтавителем и зарабатывать!" + +#: includes/class-freemius.php:21267 +msgid "Learn more" +msgstr "Узнать больше" + +#: includes/class-freemius.php21447, templates/account.php474, +#: templates/account.php595, templates/connect.php171, +#: templates/connect.php421, templates/forms/license-activation.php25, +#: templates/account/partials/addon.php:287 +msgid "Activate License" +msgstr "Ðктивировать лицензию" + +#: includes/class-freemius.php21448, templates/account.php543, +#: templates/account.php594, templates/account/partials/site.php:256 +msgid "Change License" +msgstr "Изменить лицензию " + +#: includes/class-freemius.php21539, templates/account/partials/site.php:161 +msgid "Opt Out" +msgstr "ОтказатьÑÑ Ð¾Ñ‚ иÑпользованиÑ" + +#: includes/class-freemius.php21541, includes/class-freemius.php21547, +#: templates/account/partials/site.php43, +#: templates/account/partials/site.php:161 +msgid "Opt In" +msgstr "ПриÑоединитьÑÑ" + +#: includes/class-freemius.php:21775 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" + +#: includes/class-freemius.php:21783 +msgid "Activate %s features" +msgstr "Activate %s features" + +#: includes/class-freemius.php:21796 +msgid "Please follow these steps to complete the upgrade" +msgstr "ПожалуйÑта, пройдите Ñти шаги Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы произвеÑти апгрейд" + +#: includes/class-freemius.php:21800 +msgid "Download the latest %s version" +msgstr "Скачайте поÑледнюю верÑию %s" + +#: includes/class-freemius.php:21804 +msgid "Upload and activate the downloaded version" +msgstr "Загрузите и активируйте Ñкачанную верÑию" + +#: includes/class-freemius.php:21806 +msgid "How to upload and activate?" +msgstr "Как загрузить и активировать?" + +#: includes/class-freemius.php:21940 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "%sClick here%s to choose the sites where you'd like to activate the license on." + +#: includes/class-freemius.php:22101 +msgid "Auto installation only works for opted-in users." +msgstr "Ðвто уÑтановка работает только Ð´Ð»Ñ Ð·Ð°Ñ€ÐµÐ³Ð¸Ñтрированных пользователей." + +#: includes/class-freemius.php22111, includes/class-freemius.php22144, +#: includes/class-fs-plugin-updater.php1149, +#: includes/class-fs-plugin-updater.php:1163 +msgid "Invalid module ID." +msgstr "Ðеверный ID модулÑ" + +#: includes/class-freemius.php22120, includes/class-fs-plugin-updater.php:1185 +msgid "Premium version already active." +msgstr "Премиум верÑÐ¸Ñ ÑƒÐ¶Ðµ активирована" + +#: includes/class-freemius.php:22127 +msgid "You do not have a valid license to access the premium version." +msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ необходимых лицензионных прав Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ¼Ð¸ÑƒÐ¼ верÑией" + +#: includes/class-freemius.php:22134 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "Плагин ÑвлÑетÑÑ 'ServiÑeware'. Это означает, что он не имеет премиум верÑию кода. " + +#: includes/class-freemius.php22152, includes/class-fs-plugin-updater.php:1184 +msgid "Premium add-on version already installed." +msgstr "Премиум верÑÐ¸Ñ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð° была уÑтановлена" + +#: includes/class-freemius.php:22497 +msgid "View paid features" +msgstr "ПроÑмотр платных возможноÑтей" + +#: includes/class-freemius.php:22819 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "Thank you so much for using %s and its add-ons!" + +#: includes/class-freemius.php:22820 +msgid "Thank you so much for using %s!" +msgstr "Thank you so much for using %s!" + +#: includes/class-freemius.php:22826 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving the %s." + +#: includes/class-freemius.php:22830 +msgid "Thank you so much for using our products!" +msgstr "Thank you so much for using our products!" + +#: includes/class-freemius.php:22831 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "You've already opted-in to our usage-tracking, which helps us keep improving them." + +#: includes/class-freemius.php:22850 +msgid "%s and its add-ons" +msgstr "%s and its add-ons" + +#: includes/class-freemius.php:22859 +msgid "Products" +msgstr "Products" + +#: includes/class-freemius.php22866, templates/connect.php:272 +msgid "Yes" +msgstr "Yes" + +#: includes/class-freemius.php22867, templates/connect.php:273 +msgid "send me security & feature updates, educational content and offers." +msgstr "send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php22868, templates/connect.php:278 +msgid "No" +msgstr "No" + +#: includes/class-freemius.php22870, templates/connect.php:280 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "do %sNOT%s send me security & feature updates, educational content and offers." + +#: includes/class-freemius.php:22880 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" + +#: includes/class-freemius.php22882, templates/connect.php:287 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" + +#: includes/class-freemius.php:23164 +msgid "License key is empty." +msgstr "License key is empty." + +#: includes/class-fs-plugin-updater.php184, +#: templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "Renew license" + +#: includes/class-fs-plugin-updater.php189, +#: templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "Buy license" + +#: includes/class-fs-plugin-updater.php280, +#: includes/class-fs-plugin-updater.php:313 +msgid "There is a %s of %s available." +msgstr "There is a %s of %s available." + +#: includes/class-fs-plugin-updater.php282, +#: includes/class-fs-plugin-updater.php:318 +msgid "new Beta version" +msgstr "new Beta version" + +#: includes/class-fs-plugin-updater.php283, +#: includes/class-fs-plugin-updater.php:319 +msgid "new version" +msgstr "new version" + +#: includes/class-fs-plugin-updater.php:342 +msgid "Important Upgrade Notice:" +msgstr "Important Upgrade Notice:" + +#: includes/class-fs-plugin-updater.php:1214 +msgid "Installing plugin: %s" +msgstr "УÑтановка плагина: %s" + +#: includes/class-fs-plugin-updater.php:1255 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "Ðевозможно приÑоединитьÑÑ Ðº ÑиÑтеме файлов. ПожалуйÑта, подтвердите Ñвои данные. " + +#: includes/class-fs-plugin-updater.php:1437 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "Удаленный пакет плагинов не Ñодержит папку Ñ Ð½ÑƒÐ¶Ð½Ñ‹Ð¼ опиÑанием URL и Ñмена имени не Ñрабатывает. " + +#: includes/fs-plugin-info-dialog.php:509 +msgid "Purchase More" +msgstr "Purchase More" + +#: includes/fs-plugin-info-dialog.php510, +#: templates/account/partials/addon.php:347 +msgctxt "verb" +msgid "Purchase" +msgstr "Купить" + +#: includes/fs-plugin-info-dialog.php:514 +msgid "Start my free %s" +msgstr "Ðачать мой беÑплатный %s" + +#: includes/fs-plugin-info-dialog.php:712 +msgid "Install Free Version Update Now" +msgstr "Install Free Version Update Now" + +#: includes/fs-plugin-info-dialog.php713, templates/account.php:534 +msgid "Install Update Now" +msgstr "ПровеÑти Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/fs-plugin-info-dialog.php:722 +msgid "Install Free Version Now" +msgstr "Install Free Version Now" + +#: includes/fs-plugin-info-dialog.php723, templates/add-ons.php262, +#: templates/auto-installation.php111, +#: templates/account/partials/addon.php327, +#: templates/account/partials/addon.php:379 +msgid "Install Now" +msgstr "УÑтановить ÑÐµÐ¹Ñ‡Ð°Ñ " + +#: includes/fs-plugin-info-dialog.php:739 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "Download Latest Free Version" + +#: includes/fs-plugin-info-dialog.php740, templates/account.php85, +#: templates/add-ons.php34, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "Скачать поÑледнюю верÑию" + +#: includes/fs-plugin-info-dialog.php755, templates/add-ons.php268, +#: templates/account/partials/addon.php318, +#: templates/account/partials/addon.php:373 +msgid "Activate this add-on" +msgstr "Ðктивируйте Ñтот функционал " + +#: includes/fs-plugin-info-dialog.php757, templates/connect.php:418 +msgid "Activate Free Version" +msgstr "Ðктивировать беÑплатную верÑию?" + +#: includes/fs-plugin-info-dialog.php758, templates/account.php109, +#: templates/add-ons.php269, templates/account/partials/addon.php:49 +msgid "Activate" +msgstr "Ðктивировать " + +#: includes/fs-plugin-info-dialog.php:968 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "ОпиÑание " + +#: includes/fs-plugin-info-dialog.php:969 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "УÑтановка " + +#: includes/fs-plugin-info-dialog.php:970 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "ЧаÑто задаваемые вопроÑÑ‹ " + +#: includes/fs-plugin-info-dialog.php971, +#: templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "Снимки Ñкрана " + +#: includes/fs-plugin-info-dialog.php:972 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "Журнал изменений " + +#: includes/fs-plugin-info-dialog.php:973 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "Отзывы " + +#: includes/fs-plugin-info-dialog.php:974 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "Другие заметки " + +#: includes/fs-plugin-info-dialog.php:989 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "Функционал&тарифные планы " + +#: includes/fs-plugin-info-dialog.php:999 +msgid "Plugin Install" +msgstr "УÑтановка плагина " + +#: includes/fs-plugin-info-dialog.php:1071 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "%s план " + +#: includes/fs-plugin-info-dialog.php:1097 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "Лучший " + +#: includes/fs-plugin-info-dialog.php1103, +#: includes/fs-plugin-info-dialog.php:1123 +msgctxt "as every month" +msgid "Monthly" +msgstr "ПомеÑÑчно " + +#: includes/fs-plugin-info-dialog.php:1106 +msgctxt "as once a year" +msgid "Annual" +msgstr "Ежегодно " + +#: includes/fs-plugin-info-dialog.php:1109 +msgid "Lifetime" +msgstr "Ðа беÑÑрочный период " + +#: includes/fs-plugin-info-dialog.php1123, +#: includes/fs-plugin-info-dialog.php1125, +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "Оплачивать %s" + +#: includes/fs-plugin-info-dialog.php:1125 +msgctxt "as once a year" +msgid "Annually" +msgstr "Один раз в год " + +#: includes/fs-plugin-info-dialog.php:1127 +msgctxt "as once a year" +msgid "Once" +msgstr "Один раз " + +#: includes/fs-plugin-info-dialog.php:1133 +msgid "Single Site License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð½Ð° один Ñайт " + +#: includes/fs-plugin-info-dialog.php:1135 +msgid "Unlimited Licenses" +msgstr "ÐÐµÐ¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: includes/fs-plugin-info-dialog.php:1137 +msgid "Up to %s Sites" +msgstr "до % Ñайтов " + +#: includes/fs-plugin-info-dialog.php1147, +#: templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "на один меÑÑц" + +#: includes/fs-plugin-info-dialog.php1154, +#: templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "на один год " + +#: includes/fs-plugin-info-dialog.php:1208 +msgctxt "noun" +msgid "Price" +msgstr "СтоимоÑть " + +#: includes/fs-plugin-info-dialog.php:1256 +msgid "Save %s" +msgstr "Ð­ÐºÐ¾Ð½Ð¾Ð¼Ð¸Ñ %s" + +#: includes/fs-plugin-info-dialog.php:1266 +msgid "No commitment for %s - cancel anytime" +msgstr "Без обÑзательÑтв платить %s - аннулируй пользование в любое Ð²Ñ€ÐµÐ¼Ñ " + +#: includes/fs-plugin-info-dialog.php:1269 +msgid "After your free %s, pay as little as %s" +msgstr "ПоÑле Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ беÑплатного %s, платите вÑего лиш %s" + +#: includes/fs-plugin-info-dialog.php:1280 +msgid "Details" +msgstr "Детальней" + +#: includes/fs-plugin-info-dialog.php1284, templates/account.php96, +#: templates/debug.php203, templates/debug.php240, templates/debug.php454, +#: templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "ВерÑÐ¸Ñ " + +#: includes/fs-plugin-info-dialog.php:1291 +msgctxt "as the plugin author" +msgid "Author" +msgstr "Ðвтор" + +#: includes/fs-plugin-info-dialog.php:1298 +msgid "Last Updated" +msgstr "ПоÑледнее обновление " + +#: includes/fs-plugin-info-dialog.php1303, templates/account.php:444 +msgctxt "x-ago" +msgid "%s ago" +msgstr "% тому назад " + +#: includes/fs-plugin-info-dialog.php:1312 +msgid "Requires WordPress Version" +msgstr "Ðеобходима верÑÐ¸Ñ WordPress " + +#: includes/fs-plugin-info-dialog.php:1313 +msgid "%s or higher" +msgstr "%s или выше" + +#: includes/fs-plugin-info-dialog.php:1320 +msgid "Compatible up to" +msgstr "СовмеÑтима Ñ " + +#: includes/fs-plugin-info-dialog.php:1328 +msgid "Downloaded" +msgstr "Загружен " + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "%s time" +msgstr "%s Ð²Ñ€ÐµÐ¼Ñ " + +#: includes/fs-plugin-info-dialog.php:1334 +msgid "%s times" +msgstr "%s раз " + +#: includes/fs-plugin-info-dialog.php:1344 +msgid "WordPress.org Plugin Page" +msgstr "Страница плагинов WordPress.org" + +#: includes/fs-plugin-info-dialog.php:1352 +msgid "Plugin Homepage" +msgstr "Ð“Ð»Ð°Ð²Ð½Ð°Ñ Ñтраница плагина " + +#: includes/fs-plugin-info-dialog.php1360, +#: includes/fs-plugin-info-dialog.php:1442 +msgid "Donate to this plugin" +msgstr "ИнвеÑтировать в разработку плагина " + +#: includes/fs-plugin-info-dialog.php:1367 +msgid "Average Rating" +msgstr "Средний рейтинг " + +#: includes/fs-plugin-info-dialog.php:1374 +msgid "based on %s" +msgstr "ОÑнован на %s" + +#: includes/fs-plugin-info-dialog.php:1378 +msgid "%s rating" +msgstr "% оценка " + +#: includes/fs-plugin-info-dialog.php:1380 +msgid "%s ratings" +msgstr "% оценки " + +#: includes/fs-plugin-info-dialog.php:1395 +msgid "%s star" +msgstr "%звездочка " + +#: includes/fs-plugin-info-dialog.php:1397 +msgid "%s stars" +msgstr "% звездочки " + +#: includes/fs-plugin-info-dialog.php:1408 +msgid "Click to see reviews that provided a rating of %s" +msgstr "Ðажмите, чтобы поÑмотреть отзывы, которые Ñформировали рейтинг %s" + +#: includes/fs-plugin-info-dialog.php:1421 +msgid "Contributors" +msgstr "Контрибьюторы " + +#: includes/fs-plugin-info-dialog.php1450, +#: includes/fs-plugin-info-dialog.php:1452 +msgid "Warning" +msgstr "Предупреждение " + +#: includes/fs-plugin-info-dialog.php:1450 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "Этот плагин не был теÑтирован Ñ Ð’Ð°ÑˆÐµÐ¹ текущей верÑией WordPress. " + +#: includes/fs-plugin-info-dialog.php:1452 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "Этот плагин не отмечен как ÑовмеÑтимый з Вашей верÑией WordPress " + +#: includes/fs-plugin-info-dialog.php:1471 +msgid "Paid add-on must be deployed to Freemius." +msgstr "Платный функционал должен быть заÑвлен в Freemius" + +#: includes/fs-plugin-info-dialog.php:1472 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "Функционал должен быть заÑвлен на WordPress.org или Freemius " + +#: includes/fs-plugin-info-dialog.php:1493 +msgid "Newer Version (%s) Installed" +msgstr "Более Ð½Ð¾Ð²Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ %s уÑтановлена " + +#: includes/fs-plugin-info-dialog.php:1494 +msgid "Newer Free Version (%s) Installed" +msgstr "Newer Free Version (%s) Installed" + +#: includes/fs-plugin-info-dialog.php:1501 +msgid "Latest Version Installed" +msgstr "ПоÑледнÑÑ Ð²ÐµÑ€ÑÐ¸Ñ ÑƒÑтановлена" + +#: includes/fs-plugin-info-dialog.php:1502 +msgid "Latest Free Version Installed" +msgstr "Latest Free Version Installed" + +#: templates/account.php86, templates/forms/subscription-cancellation.php96, +#: templates/account/partials/addon.php26, +#: templates/account/partials/site.php:295 +msgid "Downgrading your plan" +msgstr "Downgrading your plan" + +#: templates/account.php87, templates/forms/subscription-cancellation.php97, +#: templates/account/partials/addon.php27, +#: templates/account/partials/site.php:296 +msgid "Cancelling the subscription" +msgstr "Cancelling the subscription" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/account.php:89 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." + +#: templates/account.php90, templates/forms/subscription-cancellation.php100, +#: templates/account/partials/addon.php30, +#: templates/account/partials/site.php:299 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." + +#: templates/account.php91, templates/forms/subscription-cancellation.php106, +#: templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "Отказ от Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚ÐµÑтовым периодом автоматичеÑки блокирует доÑтуп ко вÑем премиум возможноÑÑ‚Ñм. Ð’Ñ‹ уверены, что хотите отказатьÑÑ?" + +#: templates/account.php92, templates/forms/subscription-cancellation.php101, +#: templates/account/partials/addon.php32, +#: templates/account/partials/site.php:300 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." + +#: templates/account.php93, templates/forms/subscription-cancellation.php102, +#: templates/account/partials/addon.php33, +#: templates/account/partials/site.php:301 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "По окончанию Ñрока дейÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии, Ð’Ñ‹ Ñможете пользоватьÑÑ Ð±ÐµÑплатной верÑией, но у Ð’Ð°Ñ Ð½Ðµ будет доÑтупа к возможноÑÑ‚Ñм %s. " + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php95, +#: templates/account/partials/activate-license-button.php31, +#: templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "Ðктивируйте план %s" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php98, templates/account/partials/addon.php38, +#: templates/account/partials/site.php:275 +msgid "Auto renews in %s" +msgstr "ÐвтоматичеÑкое продление в %s" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php100, templates/account/partials/addon.php40, +#: templates/account/partials/site.php:277 +msgid "Expires in %s" +msgstr "Окончание Ñрока Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‡ÐµÑ€ÐµÐ· %s" + +#: templates/account.php101, templates/account/partials/addon.php:41 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸ " + +#: templates/account.php102, templates/account/partials/addon.php:42 +msgid "Cancel Trial" +msgstr "Отменить теÑтовый период " + +#: templates/account.php103, templates/account/partials/addon.php:43 +msgid "Change Plan" +msgstr "Изменить план " + +#: templates/account.php104, templates/account/partials/addon.php:44 +msgctxt "verb" +msgid "Upgrade" +msgstr "Сделать апгрейд" + +#: templates/account.php106, templates/account/partials/addon.php46, +#: templates/account/partials/site.php:302 +msgctxt "verb" +msgid "Downgrade" +msgstr "Понизить план " + +#: templates/account.php108, templates/add-ons.php187, +#: templates/plugin-info/features.php72, +#: templates/account/partials/addon.php48, +#: templates/account/partials/site.php:31 +msgid "Free" +msgstr "БеÑÐ¿Ð»Ð°Ñ‚Ð½Ð°Ñ " + +#: templates/account.php110, templates/debug.php373, +#: includes/customizer/class-fs-customizer-upsell-control.php106, +#: templates/account/partials/addon.php:50 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "Тарифный план " + +#: templates/account.php:111 +msgid "Bundle Plan" +msgstr "Bundle Plan" + +#: templates/account.php:185 +msgid "Free Trial" +msgstr "БеÑплатный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/account.php:196 +msgid "Account Details" +msgstr " Детали" + +#: templates/account.php:200 +msgid "Billing & Invoices" +msgstr "Billing & Invoices" + +#: templates/account.php:210 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "Удалив личный кабинет, Ð’Ñ‹ автоматичеÑки деактивируете лицензию на Ваш тарифный план %s, которую Ð’Ñ‹ можете иÑпользовать на других Ñайтах. ЕÑли Ð’Ñ‹ хотите также приоÑтановить регулÑрные платежи, нажмите на кнопку \"Отмена\" и Ñначала измените Ñвой тарифный план на беÑплатный. Ð’Ñ‹ уверены, что хотите продолжить удаление?" + +#: templates/account.php:212 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "Удаление личного кабинете не может быть произведено временно. Удалите только в Ñлучае еÑли Ð’Ñ‹ больше не хотите пользоватьÑÑ %s. Ð’Ñ‹ уверены, что хотите продолжить удаление? " + +#: templates/account.php:215 +msgid "Delete Account" +msgstr "Удалить личный кабинет" + +#: templates/account.php227, templates/account/partials/addon.php211, +#: templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "Деактивировать лицензию " + +#: templates/account.php250, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "Ð’Ñ‹ уверены, что хотите продолжить?" + +#: templates/account.php250, templates/account/partials/addon.php:234 +msgid "Cancel Subscription" +msgstr "Отменить подпиÑку " + +#: templates/account.php:278 +msgctxt "as synchronize" +msgid "Sync" +msgstr "Синхронизировать " + +#: templates/account.php292, templates/debug.php:489 +msgid "Name" +msgstr "ИмÑ" + +#: templates/account.php298, templates/debug.php:490 +msgid "Email" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/account.php305, templates/debug.php372, templates/debug.php:528 +msgid "User ID" +msgstr "User ID " + +#: templates/account.php322, templates/account.php608, +#: templates/account.php653, templates/debug.php238, templates/debug.php366, +#: templates/debug.php451, templates/debug.php488, templates/debug.php526, +#: templates/debug.php599, templates/account/payments.php35, +#: templates/debug/logger.php:21 +msgid "ID" +msgstr "ID" + +#: templates/account.php:329 +msgid "Site ID" +msgstr "Site ID" + +#: templates/account.php:332 +msgid "No ID" +msgstr "No ID" + +#: templates/account.php337, templates/debug.php245, templates/debug.php374, +#: templates/debug.php455, templates/debug.php492, +#: templates/account/partials/site.php:219 +msgid "Public Key" +msgstr "Public Key " + +#: templates/account.php343, templates/debug.php375, templates/debug.php456, +#: templates/debug.php493, templates/account/partials/site.php:231 +msgid "Secret Key" +msgstr "Secret Key " + +#: templates/account.php:346 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "Ðет Ñекрета " + +#: templates/account.php373, templates/account/partials/site.php112, +#: templates/account/partials/site.php:114 +msgid "Trial" +msgstr "ТеÑтовый период " + +#: templates/account.php400, templates/debug.php533, +#: templates/account/partials/site.php:248 +msgid "License Key" +msgstr "Лицензионный ключ " + +#: templates/account.php:429 +msgid "Join the Beta program" +msgstr "Join the Beta program" + +#: templates/account.php:435 +msgid "not verified" +msgstr "не подтвержден " + +#: templates/account.php444, templates/account/partials/addon.php:172 +msgid "Expired" +msgstr "Срок дейÑÑ‚Ð²Ð¸Ñ Ð·Ð°ÐºÐ¾Ð½Ñ‡Ð¸Ð»ÑÑ " + +#: templates/account.php:502 +msgid "Premium version" +msgstr "Премиум верÑÐ¸Ñ " + +#: templates/account.php:504 +msgid "Free version" +msgstr "БеÑÐ¿Ð»Ð°Ñ‚Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ " + +#: templates/account.php:516 +msgid "Verify Email" +msgstr "Подтвердите Ñлектронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/account.php:527 +msgid "Download %s Version" +msgstr "Скачайте верÑию %s" + +#: templates/account.php541, templates/account.php749, +#: templates/account/partials/site.php237, +#: templates/account/partials/site.php:255 +msgctxt "verb" +msgid "Show" +msgstr "Показать " + +#: templates/account.php:555 +msgid "What is your %s?" +msgstr "Какой Ваш %s?" + +#: templates/account.php563, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "Редактировать " + +#: templates/account.php:588 +msgid "Sites" +msgstr "Сайтов " + +#: templates/account.php:599 +msgid "Search by address" +msgstr "Search by address" + +#: templates/account.php609, templates/debug.php:369 +msgid "Address" +msgstr "Address" + +#: templates/account.php:610 +msgid "License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account.php:611 +msgid "Plan" +msgstr "Тарифный план " + +#: templates/account.php:656 +msgctxt "as software license" +msgid "License" +msgstr "Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account.php:743 +msgctxt "verb" +msgid "Hide" +msgstr "СпрÑтать " + +#: templates/account.php:765 +msgid "Processing" +msgstr "Обработка данных " + +#: templates/account.php:768 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "Get updates for bleeding edge Beta versions of %s." + +#: templates/account.php:826 +msgid "Cancelling %s" +msgstr "Cancelling %s" + +#: templates/account.php826, templates/account.php843, +#: templates/forms/subscription-cancellation.php27, +#: templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "trial" + +#: templates/account.php841, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "Cancelling %s..." + +#: templates/account.php844, templates/forms/subscription-cancellation.php28, +#: templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "subscription" + +#: templates/account.php:858 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" + +#: templates/add-ons.php:35 +msgid "View details" +msgstr "Смотреть детальней " + +#: templates/add-ons.php:45 +msgid "Add Ons for %s" +msgstr "Функционал Ð´Ð»Ñ %s" + +#: templates/add-ons.php:55 +msgid "We could'nt load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "Мы не можем загрузить ÑпиÑок плагинов. ВероÑтно, произошла какаÑ-то ошибка Ñ Ð½Ð°ÑˆÐµÐ¹ Ñтороны. ПожалуйÑта, вернитеÑÑŒ на Ñтраницу через неÑколько минут. " + +#: templates/add-ons.php:173 +msgctxt "active add-on" +msgid "Active" +msgstr "Active" + +#: templates/add-ons.php:174 +msgctxt "installed add-on" +msgid "Installed" +msgstr "Installed" + +#: templates/admin-notice.php13, templates/forms/license-activation.php209, +#: templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "Закрыть " + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "%s Ñекунд " + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "ÐвтоматичеÑÐºÐ°Ñ ÑƒÑтановка " + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "ÐвтоматичеÑкое Ñкачивание и уÑтановка %s ( платной верÑии) %s начнетÑÑ Ñ‡ÐµÑ€ÐµÐ· %s. ЕÑли Ð’Ñ‹ хотите вÑе Ñделать в ручном режиме, нажмите на кнопку \"Отменить\" ÑейчаÑ. " + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "ПроцеÑÑ ÑƒÑтановки уже начат и может занÑть неÑколько минут. ПожалуйÑта, подождите Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑÑа и не обновлÑйте Ñту Ñтраницу. " + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "Отменить уÑтановку " + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "Оплата " + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "Жалоба PCI" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "ЗдравÑтвуйте %s" + +#: templates/connect.php:154 +msgid "Allow & Continue" +msgstr "Разрешить и продолжить" + +#: templates/connect.php:158 +msgid "Re-send activation email" +msgstr "Отправить пиÑьмо активации еще раз " + +#: templates/connect.php:162 +msgid "Thanks %s!" +msgstr "СпаÑибо %s" + +#: templates/connect.php172, templates/forms/license-activation.php:44 +msgid "Agree & Activate License" +msgstr "СоглаÑитьÑÑ Ð¸ активировать лицензию " + +#: templates/connect.php:181 +msgid "Thanks for purchasing %s! To get started, please enter your license key:" +msgstr "СпаÑибо за покупку %s! Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы начать введите Ñвой лицензионный ключ:" + +#: templates/connect.php:188 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." + +#: templates/connect.php:189 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "Ðикогда не пропуÑкайте важных оповещений - подпишитеÑÑŒ на наши ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ безопаÑноÑти и новом функционале." + +#: templates/connect.php:195 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:196 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." + +#: templates/connect.php:230 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "We're excited to introduce the Freemius network-level integration." + +#: templates/connect.php:233 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "During the update process we detected %d site(s) that are still pending license activation." + +#: templates/connect.php:235 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." + +#: templates/connect.php:237 +msgid "%s's paid features" +msgstr "%s's paid features" + +#: templates/connect.php:242 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." + +#: templates/connect.php:244 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "During the update process we detected %s site(s) in the network that are still pending your attention." + +#: templates/connect.php253, templates/forms/license-activation.php:47 +msgid "License key" +msgstr "Лицензионный ключ" + +#: templates/connect.php256, templates/forms/license-activation.php:20 +msgid "Can't find your license key?" +msgstr "Ðе можете найти лицензионный ключ? " + +#: templates/connect.php315, templates/connect.php652, +#: templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "ПропуÑтить" + +#: templates/connect.php:318 +msgid "Delegate to Site Admins" +msgstr "Delegate to Site Admins" + +#: templates/connect.php:318 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "If you click it, this decision will be delegated to the sites administrators." + +#: templates/connect.php:346 +msgid "Your Profile Overview" +msgstr "ПроÑмотр Вашего Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ " + +#: templates/connect.php:347 +msgid "Name and email address" +msgstr "Ð˜Ð¼Ñ Ð¸ Ñлектронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/connect.php:352 +msgid "Your Site Overview" +msgstr "ПроÑмотр Вашего Ñайта " + +#: templates/connect.php:353 +msgid "Site URL, WP version, PHP info, plugins & themes" +msgstr "URL Ñайта, верÑÐ¸Ñ WP, Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ PHP, плагинах и шаблонах " + +#: templates/connect.php:358 +msgid "Admin Notices" +msgstr "Ðдмин заметки" + +#: templates/connect.php359, templates/connect.php:375 +msgid "Updates, announcements, marketing, no spam" +msgstr "ÐовоÑти, объÑвлениÑ, маркетинг, без Ñпама" + +#: templates/connect.php:364 +msgid "Current %s Events" +msgstr "Текущие ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ %s" + +#: templates/connect.php:365 +msgid "Activation, deactivation and uninstall" +msgstr "ÐктивациÑ, Ð´ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¸ деинÑталлÑÑ†Ð¸Ñ " + +#: templates/connect.php:374 +msgid "Newsletter" +msgstr "РаÑÑылка " + +#: templates/connect.php391, templates/forms/license-activation.php:39 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "%1$s будет периодичеÑки приÑылать информацию %2$s Ñ Ñ†ÐµÐ»ÑŒÑŽ проверки безопаÑноÑти, ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± обновлении функционала и Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ð’Ð°ÑˆÐµÐ¹ лицензии. " + +#: templates/connect.php:396 +msgid "What permissions are being granted?" +msgstr "Какие предоÑтавлÑÑŽÑ‚ÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ?" + +#: templates/connect.php:417 +msgid "Don't have a license key?" +msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ лицензионного ключа?" + +#: templates/connect.php:420 +msgid "Have a license key?" +msgstr "У Ð’Ð°Ñ ÐµÑть лицензионный ключ?" + +#: templates/connect.php:428 +msgid "Privacy Policy" +msgstr "Политика КонфиденциальноÑти" + +#: templates/connect.php:430 +msgid "License Agreement" +msgstr "License Agreement" + +#: templates/connect.php:430 +msgid "Terms of Service" +msgstr "ПользовательÑкое Ñоглашение" + +#: templates/connect.php:805 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "Электронное пиÑьмо отправлÑетÑÑ Ð’Ð°Ð¼ на почту " + +#: templates/connect.php:806 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ " + +#: templates/contact.php:78 +msgid "Contact" +msgstr "СвÑжитеÑÑŒ Ñ Ð½Ð°Ð¼Ð¸" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "Выключить " + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "Включить " + +#: templates/debug.php:20 +msgid "SDK" +msgstr "SDK" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "УÑтранение ошибок" + +#: templates/debug.php54, templates/debug.php250, templates/debug.php376, +#: templates/debug.php:494 +msgid "Actions" +msgstr "ДейÑÑ‚Ð²Ð¸Ñ " + +#: templates/debug.php:64 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "Ð’Ñ‹ уверенны, что хотите удалить вÑе данные Freemius?" + +#: templates/debug.php:64 +msgid "Delete All Accounts" +msgstr "Удалить вÑе аккаунты" + +#: templates/debug.php:71 +msgid "Clear API Cache" +msgstr "ОчиÑтить кÑш API" + +#: templates/debug.php:79 +msgid "Clear Updates Transients" +msgstr "Clear Updates Transients" + +#: templates/debug.php:86 +msgid "Sync Data From Server" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… Ñ Ñервера " + +#: templates/debug.php:95 +msgid "Migrate Options to Network" +msgstr "Migrate Options to Network" + +#: templates/debug.php:100 +msgid "Load DB Option" +msgstr "Загрузить опцию базы данных " + +#: templates/debug.php:103 +msgid "Set DB Option" +msgstr "УÑтановить опцию базы данных " + +#: templates/debug.php:182 +msgid "Key" +msgstr "Ключ " + +#: templates/debug.php:183 +msgid "Value" +msgstr "Значение " + +#: templates/debug.php:199 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "ВерÑии SDK" + +#: templates/debug.php:204 +msgid "SDK Path" +msgstr "путь SDK" + +#: templates/debug.php205, templates/debug.php:244 +msgid "Module Path" +msgstr "Путь Ð¼Ð¾Ð´ÑƒÐ»Ñ " + +#: templates/debug.php:206 +msgid "Is Active" +msgstr "активный " + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "Плагины " + +#: templates/debug.php234, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "Шаблоны " + +#: templates/debug.php239, templates/debug.php371, templates/debug.php453, +#: templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "ОпиÑÐ°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть URL " + +#: templates/debug.php241, templates/debug.php:452 +msgid "Title" +msgstr "Ðазвание " + +#: templates/debug.php:242 +msgctxt "as application program interface" +msgid "API" +msgstr "API" + +#: templates/debug.php:243 +msgid "Freemius State" +msgstr "CоÑтоÑние Freemius " + +#: templates/debug.php:247 +msgid "Network Blog" +msgstr "Network Blog" + +#: templates/debug.php:248 +msgid "Network User" +msgstr "Network User" + +#: templates/debug.php:285 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "Соединено " + +#: templates/debug.php:286 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "Заблокировано " + +#: templates/debug.php:322 +msgid "Simulate Trial Promotion" +msgstr "Simulate Trial Promotion" + +#: templates/debug.php:334 +msgid "Simulate Network Upgrade" +msgstr "Simulate Network Upgrade" + +#: templates/debug.php:360 +msgid "%s Installs" +msgstr "%s уÑтановок " + +#: templates/debug.php:362 +msgctxt "like websites" +msgid "Sites" +msgstr "Сайтов " + +#: templates/debug.php368, templates/account/partials/site.php:148 +msgid "Blog ID" +msgstr "Blog ID" + +#: templates/debug.php433, templates/debug.php511, +#: templates/account/partials/addon.php:396 +msgctxt "verb" +msgid "Delete" +msgstr "Удалить" + +#: templates/debug.php:447 +msgid "Add Ons of module %s" +msgstr "Функционал Ð¼Ð¾Ð´ÑƒÐ»Ñ %s" + +#: templates/debug.php:484 +msgid "Users" +msgstr "Пользователи " + +#: templates/debug.php:491 +msgid "Verified" +msgstr "Подтвержден " + +#: templates/debug.php:522 +msgid "%s Licenses" +msgstr "%s лицензий " + +#: templates/debug.php:527 +msgid "Plugin ID" +msgstr "ID плагина " + +#: templates/debug.php:529 +msgid "Plan ID" +msgstr "ID тарифного плана " + +#: templates/debug.php:530 +msgid "Quota" +msgstr "Выделенный объем памÑти" + +#: templates/debug.php:531 +msgid "Activated" +msgstr "Ðктивирован " + +#: templates/debug.php:532 +msgid "Blocking" +msgstr "Блокирование " + +#: templates/debug.php:534 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "Срок Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/debug.php:557 +msgid "Debug Log" +msgstr "Журнал уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº " + +#: templates/debug.php:561 +msgid "All Types" +msgstr "Ð’Ñе типы" + +#: templates/debug.php:568 +msgid "All Requests" +msgstr "Ð’Ñе запроÑÑ‹ " + +#: templates/debug.php573, templates/debug.php602, +#: templates/debug/logger.php:25 +msgid "File" +msgstr "Файл" + +#: templates/debug.php574, templates/debug.php600, +#: templates/debug/logger.php:23 +msgid "Function" +msgstr "Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ " + +#: templates/debug.php:575 +msgid "Process ID" +msgstr "ID процеÑÑа " + +#: templates/debug.php:576 +msgid "Logger" +msgstr "Программа ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ " + +#: templates/debug.php577, templates/debug.php601, +#: templates/debug/logger.php:24 +msgid "Message" +msgstr "Сообщение " + +#: templates/debug.php:579 +msgid "Filter" +msgstr "Фильтр " + +#: templates/debug.php:587 +msgid "Download" +msgstr "Скачать " + +#: templates/debug.php598, templates/debug/logger.php:22 +msgid "Type" +msgstr "Тип" + +#: templates/debug.php603, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "Маркер времени " + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "БезопаÑÐ½Ð°Ñ Ñтраница HTTPS %s воÑпроизводитÑÑ Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ реÑурÑа " + +#: includes/customizer/class-fs-customizer-support-section.php55, +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "Поддержка " + +#: includes/debug/class-fs-debug-bar-panel.php48, +#: templates/debug/api-calls.php54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "Ð¼Ñ " + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "Freemius API" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "ЗапроÑÑ‹ " + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "Обновить " + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "СиÑтема оплаты " + +#: templates/account/billing.php38, templates/account/billing.php:38 +msgid "Business name" +msgstr "Ðазвание бизнеÑа " + +#: templates/account/billing.php39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "ID налога/ÐДС " + +#: templates/account/billing.php42, templates/account/billing.php42, +#: templates/account/billing.php43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "Поле Ð´Ð»Ñ Ð°Ð´Ñ€ÐµÑа %d" + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "City" +msgstr "Город " + +#: templates/account/billing.php46, templates/account/billing.php:46 +msgid "Town" +msgstr "ÐаÑеленный пункт " + +#: templates/account/billing.php47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "Ð˜Ð½Ð´ÐµÐºÑ " + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "Страна " + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "Выбрать Ñтрану " + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "State" +msgstr "Штат " + +#: templates/account/billing.php311, templates/account/billing.php:312 +msgid "Province" +msgstr "ÐŸÑ€Ð¾Ð²Ð¸Ð½Ñ†Ð¸Ñ " + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "Платежи" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "Дата " + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "КоличеÑтво " + +#: templates/account/payments.php38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "Счет " + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "API" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "Метод " + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "Код" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "Длинна " + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "Путь " + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ‡Ð°Ñть " + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "Результат" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "Ðачало" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "Конец " + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "Журнал изменений " + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php18, +#: templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "Ð’ %s" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php20, +#: templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "%s тому назад " + +#: templates/debug/plugins-themes-sync.php21, +#: templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "Ñек" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð¾Ð² и шаблонов " + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "Итого" + +#: templates/debug/plugins-themes-sync.php29, +#: templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "ПоÑледний " + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "Спланированные задачи" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "Модуль" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "Тип Ð¼Ð¾Ð´ÑƒÐ»Ñ " + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "Тип задачи" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "Следующий " + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "БеÑÑрочный " + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "Подать заÑвку на партнерÑтво " + +#: templates/forms/affiliation.php:104 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "Ваша заÑвка на партнерÑтво Ñ %s принÑта! Войдите в Ваш кабинет партнера на %s" + +#: templates/forms/affiliation.php:119 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "СпаÑибо за подачу заÑвки на партнерÑтво. Мы раÑÑмотрим Ваши данные на протÑжении Ñледующих 14 дней и ÑвÑжемÑÑ Ñ Ð’Ð°Ð¼Ð¸. " + +#: templates/forms/affiliation.php:122 +msgid "Your affiliation account was temporarily suspended." +msgstr "Ваш партнерÑкий аккаунт временно недоÑтупен. " + +#: templates/forms/affiliation.php:125 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "СпаÑибо за подачу заÑвки на партнерÑтво. К Ñожалению, мы принÑли решение отказать Вам в Ñтой возможноÑти. ПожалуйÑта, повторите попытку через 30 дней. " + +#: templates/forms/affiliation.php:128 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "Из-за Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ ÑƒÑловий партнерÑтва мы вынуждены временно заблокировать Ваш аккаунт. ЕÑли у Ð’Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð¸ вопроÑÑ‹, пожалуйÑта, обратитеÑÑŒ в Ñлужбу поддержки. " + +#: templates/forms/affiliation.php:141 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "Вам нравитÑÑ %s? Стань нашим партнером и зарабатывай ;-)" + +#: templates/forms/affiliation.php:142 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "Порекомендуй % новым пользователÑм и зарабатывай %s c каждой уÑпешной продажи. " + +#: templates/forms/affiliation.php:145 +msgid "Program Summary" +msgstr "Краткое опиÑание программы " + +#: templates/forms/affiliation.php:147 +msgid "%s commission when a customer purchases a new license." +msgstr "%s вознаграждениÑ, еÑли клиент купит новую лицензию." + +#: templates/forms/affiliation.php:149 +msgid "Get commission for automated subscription renewals." +msgstr "Получай вознаграждение за автоматичеÑкие Ð¿Ñ€Ð¾Ð´Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ. " + +#: templates/forms/affiliation.php:152 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "%s данные cookies предоÑтавлÑÑŽÑ‚ÑÑ Ð¿Ð¾Ñле первого поÑещениÑ, чтобы макÑимально увеличить вероÑтноÑть заработка. " + +#: templates/forms/affiliation.php:155 +msgid "Unlimited commissions." +msgstr "Ðеограниченное вознаграждение " + +#: templates/forms/affiliation.php:157 +msgid "%s minimum payout amount." +msgstr "% Ð¼Ð¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ñумма выплаты " + +#: templates/forms/affiliation.php:158 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "Выплаты производÑÑ‚ÑÑ Ð² долларах СШРчерез PayPal." + +#: templates/forms/affiliation.php:159 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "Мы выделÑем 30 дней Ð´Ð»Ñ Ð¿Ð¾ÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‚Ð¾Ð² и поÑтому Ð²Ð¾Ð·Ð½Ð°Ð³Ñ€Ð°Ð¶Ð´ÐµÐ½Ð¸Ñ Ð²Ñ‹Ð¿Ð»Ð°Ñ‡Ð¸Ð²Ð°ÑŽÑ‚ÑÑ Ð·Ð° покупки, которые были Ñовершены более чем 30 дней назад." + +#: templates/forms/affiliation.php:162 +msgid "Affiliate" +msgstr "Партнер" + +#: templates/forms/affiliation.php165, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ " + +#: templates/forms/affiliation.php:169 +msgid "Full name" +msgstr "Полное имÑ" + +#: templates/forms/affiliation.php:173 +msgid "PayPal account email address" +msgstr "Электронный Ð°Ð´Ñ€ÐµÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð° PayPal" + +#: templates/forms/affiliation.php:177 +msgid "Where are you going to promote the %s?" +msgstr "Где Ð’Ñ‹ намерены продвигать %s?" + +#: templates/forms/affiliation.php:179 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "Введите домен Вашего Ñайта или других Ñайтов на которых Ð’Ñ‹ намерены продвигать %s." + +#: templates/forms/affiliation.php:181 +msgid "Add another domain" +msgstr "Добавьте другое доменное Ð¸Ð¼Ñ " + +#: templates/forms/affiliation.php:185 +msgid "Extra Domains" +msgstr "Дополнительные доменные имена " + +#: templates/forms/affiliation.php:186 +msgid "Extra domains where you will be marketing the product from." +msgstr "Дополнительные доменные имена, где Ð’Ñ‹ будете продвигать продукт. " + +#: templates/forms/affiliation.php:196 +msgid "Promotion methods" +msgstr "Методы Ð¿Ñ€Ð¾Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ " + +#: templates/forms/affiliation.php:199 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "Социальные Ñети ( Facebook, Twitter, etc.)" + +#: templates/forms/affiliation.php:203 +msgid "Mobile apps" +msgstr "Мобильные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ " + +#: templates/forms/affiliation.php:207 +msgid "Website, email, and social media statistics (optional)" +msgstr "ВебÑайт, Ñлектронный Ð°Ð´Ñ€ÐµÑ Ð¸ ÑтатиÑтика Ñоциальных Ñетей (не обÑзательно)" + +#: templates/forms/affiliation.php:210 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "ПожалуйÑта, предоÑтавьте ÑоответÑтвенную ÑтатиÑтику вебÑайта или Ñтраницы Ñоциальных Ñетей, например, количеÑтво уникальных поÑетителей, количеÑтво подпиÑчиков, читателей, Ñ‚. д. ( Ñта Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ÑтанетÑÑ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð¾Ð¹). " + +#: templates/forms/affiliation.php:214 +msgid "How will you promote us?" +msgstr "Как Ð’Ñ‹ намерены продвигать наÑ?" + +#: templates/forms/affiliation.php:217 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "ПожалуйÑта, предоÑтавьте макÑимально детальную информацию о том, как Ð’Ñ‹ планируете продвигать %s." + +#: templates/forms/affiliation.php223, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "Отмена " + +#: templates/forms/affiliation.php:225 +msgid "Become an affiliate" +msgstr "Стать партнером" + +#: templates/forms/license-activation.php:21 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "ПожалуйÑта введите лицензионный ключ, который Ð’Ñ‹ получили на Ñлектронный Ð°Ð´Ñ€ÐµÑ Ñразу поÑле покупки. " + +#: templates/forms/license-activation.php:26 +msgid "Update License" +msgstr "Обновить лицензию" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "ОтпиÑатьÑÑ " + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "ПриÑоединитÑÑ " + +#: templates/forms/optout.php:33 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "Данные о пользовании иÑÑледуютÑÑ Ñ Ñ†ÐµÐ»ÑŒÑŽ уÑовершенÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ %s и чтобы предоÑтавить Вам лучший опыт, поÑтавить приоритет на новых возможноÑÑ‚ÑÑ… и важном функционале. Мы будем очень благодарны, еÑли Ð’Ñ‹ переÑмотрите Ваше решение и позволите нам обрабатывать информацию о пользовании нашим продуктом." + +#: templates/forms/optout.php:35 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "ЕÑли Ð’Ñ‹ нажмете \"ОтпиÑатьÑÑ\", Ð’Ñ‹ больше не будете получать информацию от %s на %s." + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "There is a new version of %s available." + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr " %s to access version %s security & feature updates, and support." + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "New Version Available" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "Закрыть " + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "Отправить лицензионный ключ" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "Введите ниже Ð°Ð´Ñ€ÐµÑ Ñвоей Ñлектронной почты, которую Ð’Ñ‹ иÑпользовали Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ð¹ и мы Вам отправим повторно Ваш лицензионный ключ. " + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "license" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the +#. subscription' +#: templates/forms/subscription-cancellation.php99, +#: templates/account/partials/addon.php29, +#: templates/account/partials/site.php:298 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "Cancel %s?" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "Proceed" + +#: templates/forms/subscription-cancellation.php191, +#: templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "Cancel %s & Proceed" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "Ð’Ñ‹ уже на раÑÑтоÑнии одного клика от начала Вашего беÑплатного %1$s - дневного теÑтового периода по тарифному плану %2$s. " + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "Ð’ ÑоответÑтвии Ñ Ñ€ÑƒÐºÐ¾Ð²Ð¾Ð´Ñтвом WordPress.org, перед началом теÑтового периода мы проÑим, чтобы Ð’Ñ‹ приÑоединилиÑÑŒ к нашему ÑообщеÑтву предоÑтавив информацию о Вашем Ñайте и также Ваши личные данные, тем Ñамым разрешив %s периодичеÑки отправлÑть ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð½Ð° %s Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹ об обновлениÑÑ… и Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ теÑтового периода. " + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "Премиум " + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "Beta" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "Activate license on all sites in the network." + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "Apply on all sites in the network." + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "Activate license on all pending sites." + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "Apply on all pending sites." + +#: templates/partials/network-activation.php40, +#: templates/partials/network-activation.php:74 +msgid "allow" +msgstr "allow" + +#: templates/partials/network-activation.php43, +#: templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "delegate" + +#: templates/partials/network-activation.php47, +#: templates/partials/network-activation.php:81 +msgid "skip" +msgstr "skip" + +#: templates/plugin-info/description.php72, +#: templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "Кликните, чтобы поÑмотреть Ñнимок %d на широком Ñкране. " + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "Ðеограниченные Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ " + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "Локальный хоÑтинг " + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "ОÑталоÑÑŒ %s " + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "ПоÑледнÑÑ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ " + +#: templates/account/partials/addon.php:167 +msgid "Cancelled" +msgstr "Ðннулирована " + +#: templates/account/partials/addon.php:177 +msgid "No expiration" +msgstr "БеÑÑрочный период Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ " + +#: templates/account/partials/site.php:181 +msgid "Owner Name" +msgstr "Owner Name" + +#: templates/account/partials/site.php:193 +msgid "Owner Email" +msgstr "Owner Email" + +#: templates/account/partials/site.php:205 +msgid "Owner ID" +msgstr "Owner ID" + +#: templates/account/partials/site.php:270 +msgid "Subscription" +msgstr "Subscription" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "Извините за неудобÑтво. Мы будем рады помочь, еÑли Ð’Ñ‹ нам предоÑтавите Ñту возможноÑть. " + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "СвÑзатьÑÑ Ñо Ñлужбой поддержки" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "Ðнонимный отзыв " + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "Деактивировать " + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "Ðктивировать %s" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "Quick Feedback" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "ЕÑли у Ð’Ð°Ñ ÐµÑть времÑ, пожалуйÑта, Ñообщите причину почему Ð’Ñ‹ %s" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "Ð”ÐµÐ°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ " + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "Переключение " + +#: templates/forms/deactivation/form.php:365 +msgid "Submit & %s" +msgstr "Отправить&%s" + +#: templates/forms/deactivation/form.php:386 +msgid "Kindly tell us the reason so we can improve." +msgstr "ПожалуйÑта, укажите причину, чтобы мы могли иÑправитьÑÑ. " + +#: templates/forms/deactivation/form.php:511 +msgid "Yes - %s" +msgstr "Да - %s" + +#: templates/forms/deactivation/form.php:518 +msgid "Skip & %s" +msgstr "ПропуÑтить & %s" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "Ðажмите здеÑÑŒ, чтобы пользоватьÑÑ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð¾Ð¼ анонимно. " + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "Возможно, Ð’Ñ‹ не обратили внимание, но Ð’Ñ‹ не обÑзаны делитьÑÑ Ð½Ð¸ÐºÐ°ÐºÐ¸Ð¼Ð¸ данными и можете проÑто % кнопку \"ПриÑоединитьÑÑ\". " diff --git a/freemius/languages/index 2.php b/freemius/languages/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/languages/index 2.php @@ -0,0 +1,3 @@ +get_slug(); + + /** + * @var FS_Plugin_Tag $update + */ + $update = $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ); + + if ( is_object($update) ) { + /** + * This logic is particularly required for multisite environment. + * If a module is site activated (not network) and not on the main site, + * the module will NOT be executed on the network level, therefore, the + * custom updates logic will not be executed as well, so unless we force + * the injection of the update into the updates transient, premium updates + * will not work. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $updater = FS_Plugin_Updater::instance( $fs ); + $updater->set_update_data( $update ); + } + + $is_paying = $fs->is_paying(); + $user = $fs->get_user(); + $site = $fs->get_site(); + $name = $user->get_name(); + $license = $fs->_get_license(); + $subscription = ( is_object( $license ) ? + $fs->_get_subscription( $license->id ) : + null ); + $plan = $fs->get_plan(); + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); + $is_paid_trial = $fs->is_paid_trial(); + $has_paid_plan = $fs->has_paid_plan(); + $show_upgrade = ( $has_paid_plan && ! $is_paying && ! $is_paid_trial ); + $trial_plan = $fs->get_trial_plan(); + + if ( $has_paid_plan ) { + $fs->_add_license_activation_dialog_box(); + } + + if ( fs_request_get_bool( 'auto_install' ) ) { + $fs->_add_auto_installation_dialog_box(); + } + + if ( fs_request_get_bool( 'activate_license' ) ) { + // Open the license activation dialog box on the account page. + add_action( 'admin_footer', array( + &$fs, + '_open_license_activation_dialog_box' + ) ); + } + + $payments = $fs->_fetch_payments(); + + $show_billing = ( is_array( $payments ) && 0 < count( $payments ) ); + + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); + + $show_plan_row = true; + $show_license_row = is_object( $license ); + + $site_view_params = array(); + + if ( fs_is_network_admin() ) { + $sites = Freemius::get_sites(); + $all_installs_plan_id = null; + $all_installs_license_id = ( $show_license_row ? $license->id : null ); + foreach ( $sites as $s ) { + $site_info = $fs->get_site_info( $s ); + $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); + $view_params = array( + 'freemius' => $fs, + 'license' => $license, + 'site' => $site_info, + 'install' => $install, + ); + + $site_view_params[] = $view_params; + + if ( empty( $install ) ) { + continue; + } + + if ( $show_plan_row ) { + if ( is_null( $all_installs_plan_id ) ) { + $all_installs_plan_id = $install->plan_id; + } else if ( $all_installs_plan_id != $install->plan_id ) { + $show_plan_row = false; + } + } + + if ( $show_license_row && $all_installs_license_id != $install->license_id ) { + $show_license_row = false; + } + } + } + + $is_child_license = ( is_object( $license ) && FS_Plugin_License::is_valid_id( $license->parent_license_id ) ); + $bundle_subscription = null; + + if ( + $show_plan_row && + is_object( $license ) && + FS_Plugin_License::is_valid_id( $license->parent_license_id ) + ) { + $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); + } + + $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + + $is_premium = $fs->is_premium(); +?> + + _get_subscription_cancellation_dialog_box_template_params( true ); + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } + ?> + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'account', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/account/billing 2.php b/freemius/templates/account/billing 2.php new file mode 100644 index 0000000..a4de409 --- /dev/null +++ b/freemius/templates/account/billing 2.php @@ -0,0 +1,423 @@ +get_slug(); + + $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); + $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); + + $billing = $fs->_fetch_billing(); + $has_billing = ( $billing instanceof FS_Billing ); + if ( ! $has_billing ) { + $billing = new FS_Billing(); + } +?> + +
    +
    +

    + > + + + + + + + + + + + + + + 'Afghanistan', + 'AX' => 'Aland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire, Saint Eustatius and Saba', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'VG' => 'British Virgin Islands', + 'BN' => 'Brunei', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CA' => 'Canada', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CW' => 'Curacao', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'CD' => 'Democratic Republic of the Congo', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'TL' => 'East Timor', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'CI' => 'Ivory Coast', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'XK' => 'Kosovo', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Laos', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macao', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MD' => 'Moldova', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territory', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'CG' => 'Republic of the Congo', + 'RE' => 'Reunion', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthelemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'ST' => 'Sao Tome and Principe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SX' => 'Sint Maarten', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'KR' => 'South Korea', + 'SS' => 'South Sudan', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syria', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'VI' => 'U.S. Virgin Islands', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'GB' => 'United Kingdom', + 'US' => 'United States', + 'UM' => 'United States Minor Outlying Islands', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'WF' => 'Wallis and Futuna', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + ) ?> + + + + + + +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/freemius/templates/account/index 2.php b/freemius/templates/account/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/account/index 2.php @@ -0,0 +1,3 @@ + +
    + + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/addon 2.php b/freemius/templates/account/partials/addon 2.php new file mode 100644 index 0000000..9441696 --- /dev/null +++ b/freemius/templates/account/partials/addon 2.php @@ -0,0 +1,407 @@ +get_slug(); + + $fs_blog_id = $VARS['fs_blog_id']; + + $active_plugins_directories_map = $VARS['active_plugins_directories_map']; + + $addon_info = $VARS['addon_info']; + $is_addon_activated = $fs->is_addon_activated( $addon_id ); + $is_addon_connected = $addon_info['is_connected']; + $is_addon_installed = $VARS['is_addon_installed']; + + $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? + freemius( $addon_id ) : + false; + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + + // Defaults. + $plan = null; + $is_paid_trial = false; + /** + * @var FS_Plugin_License $license + */ + $license = null; + $site = null; + $is_active_subscription = false; + $subscription = null; + $is_paying = false; + $show_upgrade = false; + + if ( is_object( $fs_addon ) ) { + $is_paying = $fs_addon->is_paying(); + $user = $fs_addon->get_user(); + $site = $fs_addon->get_site(); + $license = $fs_addon->_get_license(); + $subscription = ( is_object( $license ) ? + $fs_addon->_get_subscription( $license->id ) : + null ); + $plan = $fs_addon->get_plan(); + $plan_name = $plan->name; + $plan_title = $plan->title; + $is_paid_trial = $fs_addon->is_paid_trial(); + $show_upgrade = ( $fs_addon->has_paid_plan() && ! $is_paying && ! $is_paid_trial && ! $fs_addon->_has_premium_license() ); + $version = $fs_addon->get_plugin_version(); + } else if ( $is_addon_connected ) { + if ( + empty( $addon_info ) || + ! isset( $addon_info['site'] ) + ) { + $is_addon_connected = false; + } else { + /** + * @var FS_Site $site + */ + $site = $addon_info['site']; + $version = $addon_info['version']; + + $plan_name = isset( $addon_info['plan_name'] ) ? + $addon_info['plan_name'] : + ''; + + $plan_title = isset( $addon_info['plan_title'] ) ? + $addon_info['plan_title'] : + ''; + + if ( isset( $addon_info['license'] ) ) { + $license = $addon_info['license']; + } + + if ( isset( $addon_info['subscription'] ) ) { + $subscription = $addon_info['subscription']; + } + + $has_valid_and_active_license = ( + is_object( $license ) && + $license->is_active() && + $license->is_valid() + ); + + $is_paid_trial = ( + $site->is_trial() && + $has_valid_and_active_license && + ( $site->trial_plan_id == $license->plan_id ) + ); + } + } + + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); +?> +> + + + + + + + id ?> + + + + + + + + + + + is_trial() || is_object( $license ) ) : ?> + + + + is_trial() ) { + $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); + + $tags[] = array( + 'label' => sprintf( + ( $is_paid_trial ? + $renews_in_text : + $expires_in_text ), + human_time_diff( time(), strtotime( $site->trial_ends ) ) + ), + 'type' => ( $is_paid_trial ? 'success' : 'warn' ) + ); + } else { + if ( is_object( $license ) ) { + if ( $license->is_cancelled ) { + $tags[] = array( + 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_expired() ) { + $tags[] = array( + 'label' => fs_text_inline( 'Expired', 'expired', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_lifetime() ) { + $tags[] = array( + 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), + 'type' => 'success' + ); + } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), + 'type' => 'warn' + ); + } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), + 'type' => 'success' + ); + } + } + } + + foreach ( $tags as $t ) { + printf( '' . "\n", $t['type'], $t['label'] ); + } + ?> + + + + + + get_id(), + 'account', + 'deactivate_license', + fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false + ); + + $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + + if ( ! $license->is_lifetime() && $is_active_subscription ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'downgrade_account', + esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + false, + ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), + 'POST' + ); + } + } else if ( $is_paid_trial ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'cancel_trial', + esc_html( $cancel_trial_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + 'dashicons dashicons-download', + $cancel_trial_confirm_text, + 'POST' + ); + } else if ( ! is_object( $license ) || ! $license->is_features_enabled() ) { + $premium_license = $fs_addon->_get_available_premium_license(); + + if ( is_object( $premium_license ) ) { + $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); + $site = $fs_addon->get_site(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), + '', + array( + 'plugin_id' => $addon_id, + 'license_id' => $premium_license->id, + ) + ); + } + } + + if ( 0 == count( $buttons ) ) { + if ( $show_upgrade && $fs_addon->is_premium() ) { + $fs_addon->_add_license_activation_dialog_box(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + fs_esc_html_inline( 'Activate License', 'activate-license', $slug ), + 'activate-license-trigger ' . $fs_addon->get_unique_affix(), + array( + 'plugin_id' => $addon_id, + ), + false, + true + ); + } + + // Add sync license only if non of the other CTAs are visible. + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + $fs->get_unique_affix() . '_sync_license', + esc_html( $sync_license_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + true + ); + + } + } else if ( ! $show_upgrade ) { + if ( $fs->is_addon_installed( $addon_id ) ) { + $addon_file = $fs->get_addon_basename( $addon_id ); + + if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), + $activate_text + ); + } + } else { + if ( $fs->is_allowed_to_install() ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), + fs_text_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + $buttons[] = sprintf( + '%s', + $fs->_get_latest_download_local_url( $addon_id ), + esc_html( $download_latest_text ) + ); + } + } + } + + if ( $show_upgrade ) { + $buttons[] = sprintf( ' %s', + esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), + esc_attr( $addon_info['title'] ), + ( $fs_addon->has_free_plan() ? + $upgrade_text : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) + ); + } + + $buttons_count = count( $buttons ); + ?> + + + 1 ) : ?> +
    + + 1 ) : ?>
    + + + + + + + is_addon_installed( $addon_id ) ) : ?> + get_addon_basename( $addon_id ) ?> + + + + + is_allowed_to_install() ) : ?> + + + + + + + + + + + + get_id(), 'account', + 'delete_account', + fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false, + $show_upgrade + ); + } + ?> + + + + \ No newline at end of file diff --git a/freemius/templates/account/partials/deactivate-license-button 2.php b/freemius/templates/account/partials/deactivate-license-button 2.php new file mode 100644 index 0000000..123b092 --- /dev/null +++ b/freemius/templates/account/partials/deactivate-license-button 2.php @@ -0,0 +1,36 @@ + +
    + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/index 2.php b/freemius/templates/account/partials/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/account/partials/index 2.php @@ -0,0 +1,3 @@ +get_slug(); + $site = $VARS['site']; + $main_license = $VARS['license']; + $has_paid_plan = $fs->has_paid_plan(); + $is_premium = $fs->is_premium(); + $main_user = $fs->get_user(); + $blog_id = $site['blog_id']; + + $install = $VARS['install']; + $is_registered = ! empty( $install ); + $license = null; + $trial_plan = $fs->get_trial_plan(); + $free_text = fs_text_inline( 'Free', 'free', $slug ); +?> + data-install-id="id ?>"> + + + id ?> + + +
    + + + + +
    + + + + + + + + + + $fs, + 'slug' => $slug, + 'blog_id' => $blog_id, + 'class' => 'button-small', + ); + + $license = null; + if ( $is_registered ) { + $view_params['install_id'] = $install->id; + $view_params['is_localhost'] = $install->is_localhost(); + + $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); + $license = $has_license ? + $fs->_get_license_by_id( $install->license_id ) : + null; + } else { + $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); + } + + if ( is_object( $license ) ) { + $view_params['license'] = $license; + + // Show license deactivation button. + fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); + } else { + if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { + // Main license is available for activation. + $available_license = $main_license; + } else { + // Try to find any available license for activation. + $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); + } + + if ( is_object( $available_license ) ) { + $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); + + $view_params['license'] = $available_license; + $view_params['class'] .= ' button-primary'; + $view_params['plan'] = $premium_plan; + + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); + } + } + } ?> + + + + + is_trial() ) { + if ( $trial_plan->id == $install->trial_plan_id ) { + $plan_title = is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ); + } else { + $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); + } + } else { + $plan = $fs->_get_plan_by_id( $install->plan_id ); + $plan_title = strtoupper( is_string( $plan->title ) ? + $plan->title : + strtoupper( $free_text ) + ); + } + } + ?> + + + + + + + + + + + + + + + + + + > + + + + + + + + user_id != $main_user->id ) : ?> + user_id ) ?> + + + > + + + + + + + + > + + + + + + + + > + + + + + + + + + + > + + + + + + + + > + + + + + + + + + + > + + + + + + + + id != $license->id ) : ?> + _get_subscription( $license->id ) ?> + is_lifetime() && is_object( $subscription ) ) : ?> + + > + + is_active(); + + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + ?> + + + + + + + + + + +
    + : + license_id ) ) : ?> + + +
    + + id}", ':' ) ) ?> + + + +
    + +
    + : + get_name() ) ?>
    + : + email ) ?>
    + : + id ?>
    + : + public_key ) ?>
    + : + + secret_key, 0, 6 ) ) . str_pad( '', 23 * 6, '•' ) . htmlspecialchars( substr( $install->secret_key, - 3 ) ) ?> +
    + : + + secret_key, 0, 6 ) ) . str_pad( '', 23 * 6, '•' ) . htmlspecialchars( substr( $license->secret_key, - 3 ) ) ?> + + + +
    + : + + id ?> - billing_cycle ? + _fs_text_inline( 'Annual', 'annual', $slug ) : + _fs_text_inline( 'Monthly', 'monthly', $slug ) + ); + ?> + + is_first_payment_pending() ) : ?> + + is_first_payment_pending() ) : ?> + + + + expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + ?> + +
    + + + + +
    +
    + + + + \ No newline at end of file diff --git a/freemius/templates/account/payments 2.php b/freemius/templates/account/payments 2.php new file mode 100644 index 0000000..c9dea91 --- /dev/null +++ b/freemius/templates/account/payments 2.php @@ -0,0 +1,59 @@ +get_slug(); + + $payments = $fs->_fetch_payments(); + + $show_payments = ( is_array( $payments ) && 0 < count( $payments ) ); + + if ( $show_payments ) : +?> +
    +
    +

    + +
    + + + + + + + + + + + + + > + + + + + + + +
    id ?>created ) ) ?>formatted_gross() ?>is_migrated() ) : ?>
    +
    +
    +
    +get_slug(); + + $open_addon_slug = fs_request_get( 'slug' ); + + $open_addon = false; + + /** + * @var FS_Plugin[] + */ + $addons = $fs->get_addons(); + + $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); + + $account_addon_ids = $fs->get_updated_account_addons(); + + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); + + $has_tabs = $fs->_add_tabs_before_content(); + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; +?> +
    + +

    get_plugin_name() ) ) ?>

    + + + do_action( 'addons/after_title' ) ?> + +
    + +

    + +
      + + _get_addons_plans_and_pricing_map_by_id(); + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + ?> + + get_addon_basename( $addon->id ); + + $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); + $is_addon_activated = $is_addon_installed ? + $fs->is_addon_activated( $addon->id ) : + false; + + $is_plugin_active = ( + $is_addon_activated || + isset( $active_plugins_directories_map[ dirname( $basename ) ] ) + ); + + $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); + + $price = 0; + $has_trial = false; + $has_free_plan = false; + $has_paid_plan = false; + + if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { + $plans = $plans_and_pricing_by_addon_id[$addon->id]; + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + foreach ( $plans as $plan ) { + if ( ! isset( $plan->pricing ) || + ! is_array( $plan->pricing ) || + 0 == count( $plan->pricing ) + ) { + // No pricing means a free plan. + $has_free_plan = true; + continue; + } + + + $has_paid_plan = true; + $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); + + $min_price = 999999; + foreach ( $plan->pricing as $pricing ) { + if ( ! is_null( $pricing->annual_price ) && $pricing->annual_price > 0 ) { + $min_price = min( $min_price, $pricing->annual_price ); + } else if ( ! is_null( $pricing->monthly_price ) && $pricing->monthly_price > 0 ) { + $min_price = min( $min_price, 12 * $pricing->monthly_price ); + } + } + + if ( $min_price < 999999 ) { + $price = $min_price; + } + + } + } + + if ( ! $has_paid_plan && ! $has_free_plan ) { + continue; + } + } + ?> +
    • + get_id() . '&plugin=' . $addon->slug . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), + esc_attr( $addon->title ) + ) . ' class="thickbox%s">%s'; + + echo sprintf( + $view_details_link, + /** + * Additional class. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + ' fs-overlay', + /** + * Set the view details link text to an empty string since it is an overlay that + * doesn't really need a text and whose purpose is to open the details dialog when + * the card is clicked. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + '' + ); + ?> + info ) ) { + $addon->info = new stdClass(); + } + if ( ! isset( $addon->info->card_banner_url ) ) { + $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; + } + if ( ! isset( $addon->info->short_description ) ) { + $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; + } + ?> +
      +
        +
      • %s', + esc_html( $is_plugin_active ? + fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : + fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) + ) + ); + } + ?>
      • + +
      • title ?>
      • +
      • + 0) + $descriptors[] = '$' . number_format( $price, 2 ); + if ($has_trial) + $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + + echo implode(' - ', $descriptors) ?> +
      • +
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • + is_wp_org_compliant ); + + $is_allowed_to_install = ( + $fs->is_allowed_to_install() || + $is_free_only_wp_org_compliant + ); + + $show_premium_activation_or_installation_action = true; + + if ( ! in_array( $addon->id, $account_addon_ids ) ) { + $show_premium_activation_or_installation_action = false; + } else if ( $is_addon_installed ) { + /** + * If any add-on's version (free or premium) is installed, check if the + * premium version can be activated and show the relevant action. Otherwise, + * show the relevant action for the free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + $fs_addon = $is_addon_activated ? + $fs->get_addon_instance( $addon->id ) : + null; + + $premium_plugin_basename = is_object( $fs_addon ) ? + $fs_addon->premium_plugin_basename() : + "{$addon->premium_slug}/{$addon->slug}.php"; + + if ( + ( $is_addon_activated && $fs_addon->is_premium() ) || + file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) + ) { + $basename = $premium_plugin_basename; + } + + $show_premium_activation_or_installation_action = ( + ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && + /** + * This check is needed for cases when an active add-on doesn't have an + * associated Freemius instance. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + ( ! $is_plugin_active ) + ); + } + ?> + +
      • + + _get_latest_download_local_url( $addon->id ); + ?> + +
      • +
        + + %s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), + fs_esc_html_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + echo sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), + fs_text_inline( 'Activate', 'activate', $addon->slug ) + ); + } + ?> + + + +
        +
        +
      • + +
      +
      +
    • + + +
    +
    + + do_action( 'addons/after_addons' ) ?> +
    + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'addons', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/add-trial-to-pricing 2.php b/freemius/templates/add-trial-to-pricing 2.php new file mode 100644 index 0000000..24fc885 --- /dev/null +++ b/freemius/templates/add-trial-to-pricing 2.php @@ -0,0 +1,31 @@ + + \ No newline at end of file diff --git a/freemius/templates/admin-notice 2.php b/freemius/templates/admin-notice 2.php new file mode 100644 index 0000000..6079e71 --- /dev/null +++ b/freemius/templates/admin-notice 2.php @@ -0,0 +1,76 @@ + + data-id="" data-manager-id="" data-slug="" data-type="" + class=" fs-notice"> + + + +
    +
    + +
    + + +
    + diff --git a/freemius/templates/ajax-loader 2.php b/freemius/templates/ajax-loader 2.php new file mode 100644 index 0000000..bc116f8 --- /dev/null +++ b/freemius/templates/ajax-loader 2.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/freemius/templates/auto-installation 2.php b/freemius/templates/auto-installation 2.php new file mode 100644 index 0000000..1fc1278 --- /dev/null +++ b/freemius/templates/auto-installation 2.php @@ -0,0 +1,249 @@ +is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $title = $fs->get_plugin_title(); + + if ( $plugin_id != $fs->get_id() ) { + $addon = $fs->get_addon( $plugin_id ); + + if ( is_object( $addon ) ) { + $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); + } + } + + $plugin_title = sprintf( + '%s', + esc_html( $title ) + ); + + $sec_countdown = 30; + $countdown_html = sprintf( + esc_js( + /* translators: %s: Number of seconds */ + fs_text_inline( '%s sec', 'x-sec', $slug ) + ), + sprintf( '%s', $sec_countdown ) + ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + $params = array(); + $loader_html = fs_get_template( 'ajax-loader.php', $params ); + + // Pass unique auto installation URL if WP_Filesystem is needed. + $install_url = $fs->_get_sync_license_url( + $plugin_id, + true, + array( 'auto_install' => 'true' ) + ); + + + ob_start(); + + $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. + + $credentials = request_filesystem_credentials( + esc_url_raw( $install_url ), + $method, + false, + WP_PLUGIN_DIR, + array() + ); + + $credentials_form = ob_get_clean(); + + $require_credentials = ! empty( $credentials_form ); +?> +
    +
    +
    +

    +
    +
    + + +
    + +
    + +

    %s', + 'https://freemius.com', + 'freemius.com' + ), + $countdown_html + ) ?>

    + + +
    + +
    +
    ' + + diff --git a/freemius/templates/checkout 2.php b/freemius/templates/checkout 2.php new file mode 100644 index 0000000..a0969cc --- /dev/null +++ b/freemius/templates/checkout 2.php @@ -0,0 +1,337 @@ +get_slug(); + + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + 'mode' => 'dashboard', + 'trial' => fs_request_get_bool( 'trial' ), + ); + + $plan_id = fs_request_get( 'plan_id' ); + if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { + $context_params['plan_id'] = $plan_id; + } + + $licenses = fs_request_get( 'licenses' ); + if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { + $context_params['licenses'] = $licenses; + } + + $plugin_id = fs_request_get( 'plugin_id' ); + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + $plugin_id = $fs->get_id(); + } + + if ( $plugin_id == $fs->get_id() ) { + $is_premium = $fs->is_premium(); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + } else { + // Identify the module code version of the checkout context module. + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $is_premium = $fs_addon->is_premium(); + } else { + // If add-on isn't activated assume the premium version isn't installed. + $is_premium = false; + } + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $site = $fs->get_site(); + + if ( $plugin_id != $fs->get_id() ) { + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $addon_site = $fs_addon->get_site(); + if ( is_object( $addon_site ) ) { + $site = $addon_site; + } + } + } + + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $site, + $timestamp, + 'checkout' + ) ); + } else { + $current_user = Freemius::_get_current_wp_user(); + + // Add site and user info to the request, this information + // is NOT being stored unless the user complete the purchase + // and agrees to the TOS. + $context_params = array_merge( $context_params, array( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_email' => $current_user->user_email, + 'home_url' => home_url(), + ) ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + if ( is_object( $fs_user ) && $fs_user->is_verified() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs_user, + $timestamp, + 'checkout' + ) ); + } + } + + if ( $fs->is_payments_sandbox() ) { + // Append plugin secure token for sandbox mode authentication. + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + + /** + * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. + */ + if ( empty( $context_params['s_ctx_ts'] ) ) { + $context_params['s_ctx_ts'] = $timestamp; + } + } + + $return_url = $fs->_get_sync_license_url( $plugin_id ); + + $can_user_install = ( + ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || + ( $fs->is_theme() && current_user_can( 'install_themes' ) ) + ); + + $query_params = array_merge( $context_params, $_GET, array( + // Current plugin version. + 'plugin_version' => $fs->get_plugin_version(), + 'sdk_version' => WP_FS__SDK_VERSION, + 'is_premium' => $is_premium ? 'true' : 'false', + 'can_install' => $can_user_install ? 'true' : 'false', + 'return_url' => $return_url, + ) ); + + $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); + if ( false !== $xdebug_session ) { + $query_params['XDEBUG_SESSION'] = $xdebug_session; + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), + ); + fs_require_once_template('secure-https-header.php', $view_params); +?> +
    +
    + +
    \ No newline at end of file diff --git a/freemius/templates/connect 2.php b/freemius/templates/connect 2.php new file mode 100644 index 0000000..34a1132 --- /dev/null +++ b/freemius/templates/connect 2.php @@ -0,0 +1,971 @@ +get_slug(); + + $is_pending_activation = $fs->is_pending_activation(); + $is_premium_only = $fs->is_only_premium(); + $has_paid_plans = $fs->has_paid_plan(); + $is_premium_code = $fs->is_premium(); + $is_freemium = $fs->is_freemium(); + + $fs->_enqueue_connect_essentials(); + + $current_user = Freemius::_get_current_wp_user(); + + $first_name = $current_user->user_firstname; + if ( empty( $first_name ) ) { + $first_name = $current_user->nickname; + } + + $site_url = get_site_url(); + $protocol_pos = strpos( $site_url, '://' ); + if ( false !== $protocol_pos ) { + $site_url = substr( $site_url, $protocol_pos + 3 ); + } + + $freemius_site_www = 'https://freemius.com'; + + $freemius_usage_tracking_url = $freemius_site_www . '/wordpress/usage-tracking/' . $fs->get_id() . "/{$slug}/"; + $freemius_plugin_terms_url = $freemius_site_www . '/terms/' . $fs->get_id() . "/{$slug}/"; + + $freemius_site_url = $fs->is_premium() ? + $freemius_site_www : + $freemius_usage_tracking_url; + + if ( $fs->is_premium() ) { + $freemius_site_url .= '?' . http_build_query( array( + 'id' => $fs->get_id(), + 'slug' => $slug, + ) ); + } + + $freemius_link = 'freemius.com'; + + $error = fs_request_get( 'error' ); + + $require_license_key = $is_premium_only || + ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) ); + + if ( $is_pending_activation ) { + $require_license_key = false; + } + + if ( $require_license_key ) { + $fs->_add_license_activation_dialog_box(); + } + + $is_optin_dialog = ( + $fs->is_theme() && + $fs->is_themes_page() && + ( ! $fs->has_settings_menu() || $fs->is_free_wp_org_theme() ) + ); + + if ( $is_optin_dialog ) { + $show_close_button = false; + $previous_theme_activation_url = ''; + + if ( ! $is_premium_code ) { + $show_close_button = true; + } else if ( $is_premium_only ) { + $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); + $show_close_button = ( ! empty( $previous_theme_activation_url ) ); + } + } + + $is_network_level_activation = ( + fs_is_network_admin() && + $fs->is_network_active() && + ! $fs->is_network_delegated_connection() + ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + $activate_with_current_user = ( + is_object( $fs_user ) && + ! $is_pending_activation && + // If requires a license for activation, use the user associated with the license for the opt-in. + ! $require_license_key && + ! $is_network_level_activation + ); + + $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); + $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); + + $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); + + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); + + $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ? + FS_GDPR_Manager::instance()->is_required() : + false; + + if ( is_null( $is_gdpr_required ) ) { + $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); + } +?> + +
    + + + + +
    +
    + + + $fs->get_id() ); + fs_require_once_template( 'plugin-icon.php', $vars ); + ?> + + +
    +
    + +

    + +

    apply_filters( 'pending_activation_message', sprintf( + /* translators: %s: name (e.g. Thanks John!) */ + fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . + fs_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message', $slug ), + $first_name, + '' . $fs->get_plugin_name() . '', + '' . $current_user->user_email . '', + fs_text_inline( 'complete the install', 'complete-the-install', $slug ) + ) ); + } else if ( $require_license_key ) { + $button_label = $is_network_upgrade_mode ? + fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) : + fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + + $message = $fs->apply_filters( + 'connect-message_on-premium', + ($is_network_upgrade_mode ? + '' : + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text . '
    ' + ) . + sprintf( fs_text_inline( 'Thanks for purchasing %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), + $first_name, + $fs->get_plugin_name() + ); + } else { + $filter = 'connect_message'; + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) : + fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug); + + if ( $fs->is_plugin_update() ) { + // If Freemius was added on a plugin update, set different + // opt-in message. + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ) : + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ); + + // If user customized the opt-in message on update, use + // that message. Otherwise, fallback to regular opt-in + // custom message if exist. + if ( $fs->has_filter( 'connect_message_on_update' ) ) { + $filter = 'connect_message_on_update'; + } + } + + $message = $fs->apply_filters( + $filter, + ($is_network_upgrade_mode ? + '' : + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text . '
    ' + ) . + sprintf( + esc_html( $default_optin_message ), + '' . esc_html( $fs->get_plugin_name() ) . '', + '' . $current_user->user_login . '', + '' . $site_url . '', + $freemius_link + ), + $first_name, + $fs->get_plugin_name(), + $current_user->user_login, + '' . $site_url . '', + $freemius_link, + $is_gdpr_required + ); + } + + if ( $is_network_upgrade_mode ) { + $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); + + if ($is_premium_code){ + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); + + $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), + $fs->get_module_label( true ) + ) ); + + /* translators: %s: module type (plugin, theme, or add-on) */ + $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); + }else { + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); + } + } + + echo $message; + ?>

    + +
    + + + +
    + + do_action( 'connect/after_license_input' ); + ?> + + - %s', + $fs->get_text_inline( 'Yes', 'yes' ), + $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) + ); + + $do_not_send_updates_text = sprintf( + '%s - %s', + $fs->get_text_inline( 'No', 'no' ), + sprintf( + $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), + '', + '' + ) + ); + ?> +
    + +
    + + +
    +
    + + + $fs->get_id(), + 'sites' => $sites, + 'require_license_key' => $require_license_key + ); + + echo fs_get_template( 'partials/network-activation.php', $vars ); + ?> + +
    +
    + is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> + + + apply_filters( 'show_delegation_option', true ) ) : ?> + + + +
    + + get_public_key() ) ?> + +
    + +
    + + $value ) : ?> + + + +
    + +
    array( + 'icon-class' => 'dashicons dashicons-admin-users', + 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ), + 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ), + 'priority' => 5, + ), + 'site' => array( + 'icon-class' => 'dashicons dashicons-admin-settings', + 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), + 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info, plugins & themes', 'permissions-site_desc' ), + 'priority' => 10, + ), + 'notices' => array( + 'icon-class' => 'dashicons dashicons-testimonial', + 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 13, + ), + 'events' => array( + 'icon-class' => 'dashicons dashicons-admin-plugins', + 'label' => sprintf( $fs->get_text_inline( 'Current %s Events', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), + 'desc' => $fs->get_text_inline( 'Activation, deactivation and uninstall', 'permissions-events_desc' ), + 'priority' => 20, + ), + ); + + // Add newsletter permissions if enabled. + if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { + $permissions['newsletter'] = array( + 'icon-class' => 'dashicons dashicons-email-alt', + 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 15, + ); + } + + // Allow filtering of the permissions list. + $permissions = $fs->apply_filters( 'permission_list', $permissions ); + + // Sort by priority. + uasort( $permissions, 'fs_sort_by_priority' ); + + if ( ! empty( $permissions ) ) : ?> +
    + +

    get_module_label( true ), + $freemius_link + ) ?>

    + + +
      $permission ) : ?> +
    • + + +
      + + +

      +
      +
    • + +
    +
    + + +
    +

    + + + + + + + +

    +
    + +
    + +   -   + +
    +
    + +
    + + \ No newline at end of file diff --git a/freemius/templates/contact 2.php b/freemius/templates/contact 2.php new file mode 100644 index 0000000..bba1018 --- /dev/null +++ b/freemius/templates/contact 2.php @@ -0,0 +1,128 @@ +get_slug(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + time(), + 'contact' + ) ); + } + + $query_params = array_merge( $_GET, array_merge( $context_params, array( + 'plugin_version' => $fs->get_plugin_version(), + 'wp_login_url' => wp_login_url(), + 'site_url' => get_site_url(), +// 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", + ) ) ); + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    +
    + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'contact', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/debug 2.php b/freemius/templates/debug 2.php new file mode 100644 index 0000000..bafff16 --- /dev/null +++ b/freemius/templates/debug 2.php @@ -0,0 +1,726 @@ + +

    newest->version ?>

    +
    + + + + +
    +
    + + +
    + +
    +

    + + + + + + + + get_option( 'ms_migration_complete', false, true ) ) : ?> + + + + + + +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + + + +
    + + 'WP_FS__REMOTE_ADDR', + 'val' => WP_FS__REMOTE_ADDR, + ), + array( + 'key' => 'WP_FS__ADDRESS_PRODUCTION', + 'val' => WP_FS__ADDRESS_PRODUCTION, + ), + array( + 'key' => 'FS_API__ADDRESS', + 'val' => FS_API__ADDRESS, + ), + array( + 'key' => 'FS_API__SANDBOX_ADDRESS', + 'val' => FS_API__SANDBOX_ADDRESS, + ), + array( + 'key' => 'WP_FS__DIR', + 'val' => WP_FS__DIR, + ), + ) +?> +
    + + + + + + + + + + > + + + + + + +
    +

    + + + + + + + + + + + plugins as $sdk_path => $data ) : ?> + version ) ?> + > + + + + + + + +
    version ?>plugin_path ?>
    + + + + + get_option( $module_type . 's' ) ?> + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + $data ) : ?> + file ); + } else { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + + if ( ! $is_active && is_child_theme() ) { + $parent_theme = $current_theme->parent(); + + $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); + } + } + ?> + id ) : null ?> + has_api_connectivity() && $fs->is_on() ) { + echo ' style="background: #E6FFE6; font-weight: bold"'; + } else { + echo ' style="background: #ffd0d0; font-weight: bold"'; + } + } ?>> + + + + + has_api_connectivity() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>has_api_connectivity() ? + fs_text_x_inline( 'Connected', 'as connection was successful' ) : + fs_text_x_inline( 'Blocked', 'as connection blocked' ) + ); + } ?> + is_on() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>is_on() ? + $on_text : + $off_text + ); + } ?> + + + + get_network_install_blog_id(); + $network_user = $fs->get_network_user(); + } + ?> + + + + + + + +
    id ?>version ?>title ?>file ?>public_key ?>email; + } ?> + + has_trial_plan() ) : ?> +
    + + + + + +
    + + is_registered() ) : ?> + + + is_network_upgrade_mode() ) : ?> +
    + + + + + +
    + + +
    + + + + + 0 ) : ?> +

    /

    + + + + + + + + + + + + + + + + + + $sites ) : ?> + + + + + + + + + + + + + + + + + + +
    id ?>blog_id ?>url ) ?>user_id ?>plan_id ) ) { + if ( false === $all_plans ) { + $option_name = 'plans'; + if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + $all_plans = $fs_options->get_option( $option_name, array() ); + } + + if ( false === $plans ) { + $plans = $all_plans[ $slug ]; + } + + foreach ( $plans as $plan ) { + $plan_id = Freemius::_decrypt( $plan->id ); + + if ( $site->plan_id == $plan_id ) { + $plan_name = Freemius::_decrypt( $plan->name ); + break; + } + } + } + + echo $plan_name; + ?>public_key ?>secret_key ) ?> +
    + + + + + + + + + +
    +
    + + + + $plugin_addons ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>title ?>slug ?>version ?>public_key ?>secret_key ) ?>
    + + + +

    + + + + + + + + + + + + + + $user ) : ?> + + + + + + + + + + + +
    id ?>get_name() ?>email ?>is_verified ) ?>public_key ?>secret_key ) ?> +
    + + + + +
    +
    + + + + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>plugin_id ?>user_id ?>plan_id ?>is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?>activated ?>is_block_features ? 'Blocking' : 'Flexible' ?>secret_key ) ?>expiration ?>
    + + + + +

    + +
    + + + + + + + +
    + + +
    + + +
    + +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    {$log.log_order}.{$log.type}{$log.logger}{$log.function} + + {$log.message_short} + +
    {$log.message}
    +
    {$log.file}:{$log.line}{$log.created}
    +
    + + diff --git a/freemius/templates/debug/api-calls 2.php b/freemius/templates/debug/api-calls 2.php new file mode 100644 index 0000000..ea4e823 --- /dev/null +++ b/freemius/templates/debug/api-calls 2.php @@ -0,0 +1,155 @@ + 0, + 'POST' => 0, + 'PUT' => 0, + 'DELETE' => 0 + ); + + $show_body = false; + foreach ( $logger as $log ) { + $counters[ $log['method'] ] ++; + + if ( ! is_null( $log['body'] ) ) { + $show_body = true; + } + } + + $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); + + /** + * This template is used for debugging, therefore, when possible + * we'd like to prettify the output of a JSON encoded variable. + * This will only be executed when $pretty_print is `true`, and + * the var is `true` only for PHP 5.3 and higher. Due to the + * limitations of the current Theme Check, it throws an error + * that using the "options" parameter (the 2nd param) is not + * supported in PHP 5.2 and lower. Thus, we added this alias + * variable to work around that false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $encode = 'json_encode'; + + $root_path_len = strlen( ABSPATH ); + + $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); +?> +

    + +

    Total Time:

    + +

    Total Requests:

    + $count ) : ?> +

    :

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    . + %s', + $log['path'] + ); + ?> + + + + + + + + + + + + + + %s', + substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) + ); + if ( $pretty_print ) { + $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); + } + ?> +
    + +
    + %s', + substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) + ); + } + + if ( $is_not_empty_result && $pretty_print ) { + $decoded = json_decode( $result ); + if ( ! is_null( $decoded ) ) { + $result = $encode( $decoded, JSON_PRETTY_PRINT ); + } + } else { + $result = is_string( $result ) ? $result : json_encode( $result ); + } + ?> + style="display: none"> +
    \ No newline at end of file diff --git a/freemius/templates/debug/index 2.php b/freemius/templates/debug/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/debug/index 2.php @@ -0,0 +1,3 @@ + +

    + + + + + + + + + + + + + + + + + + > + + + + + + + + + + +
    #
    .get_id() ?> + %s', + esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) + ); + ?> +
    + +
    +
    get_file() ) . ':' . $log['line']; + } + ?>
    \ No newline at end of file diff --git a/freemius/templates/debug/plugins-themes-sync 2.php b/freemius/templates/debug/plugins-themes-sync 2.php new file mode 100644 index 0000000..8508cd1 --- /dev/null +++ b/freemius/templates/debug/plugins-themes-sync 2.php @@ -0,0 +1,76 @@ +get_option( 'all_plugins' ); + $all_themes = $fs_options->get_option( 'all_themes' ); + + /* translators: %s: time period (e.g. In "2 hours") */ + $in_x_text = fs_text_inline( 'In %s', 'in-x' ); + /* translators: %s: time period (e.g. "2 hours" ago) */ + $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + + + + + + + + + + + +
    plugins ) ?>timestamp ) && is_numeric( $all_plugins->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    themes ) ?>timestamp ) && is_numeric( $all_themes->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    diff --git a/freemius/templates/debug/scheduled-crons 2.php b/freemius/templates/debug/scheduled-crons 2.php new file mode 100644 index 0000000..1751e60 --- /dev/null +++ b/freemius/templates/debug/scheduled-crons 2.php @@ -0,0 +1,136 @@ +get_option( $module_type . 's' ); + if ( is_array( $modules ) && count( $modules ) > 0 ) { + foreach ( $modules as $slug => $data ) { + if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + } else { + $is_active = is_plugin_active( $data->file ); + } + + /** + * @author Vova Feldman + * + * @since 1.2.1 Don't load data from inactive modules. + */ + if ( $is_active ) { + $fs = freemius( $data->id ); + + $next_execution = $fs->next_sync_cron(); + $last_execution = $fs->last_sync_cron(); + + if ( false !== $next_execution ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'sync_cron', + 'last' => $last_execution, + 'next' => $next_execution, + ); + } + + $next_install_execution = $fs->next_install_sync(); + $last_install_execution = $fs->last_install_sync(); + + if (false !== $next_install_execution || + false !== $last_install_execution + ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'install_sync', + 'last' => $last_install_execution, + 'next' => $next_install_execution, + ); + } + } + } + } + } + + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + $crons ) : ?> + + + + + + + + + + + + +
    diff --git a/freemius/templates/email 2.php b/freemius/templates/email 2.php new file mode 100644 index 0000000..598c783 --- /dev/null +++ b/freemius/templates/email 2.php @@ -0,0 +1,49 @@ + + + $section ) { + ?> + + + + + $row ) { + $col_count = count( $row ); + ?> + + + + + + + + + + + +
    :
    \ No newline at end of file diff --git a/freemius/templates/firewall-issues-js 2.php b/freemius/templates/firewall-issues-js 2.php new file mode 100644 index 0000000..2abfbc0 --- /dev/null +++ b/freemius/templates/firewall-issues-js 2.php @@ -0,0 +1,59 @@ + + \ No newline at end of file diff --git a/freemius/templates/forms/affiliation 2.php b/freemius/templates/forms/affiliation 2.php new file mode 100644 index 0000000..14edd64 --- /dev/null +++ b/freemius/templates/forms/affiliation 2.php @@ -0,0 +1,486 @@ +get_slug(); + + $user = $fs->get_user(); + $affiliate = $fs->get_affiliate(); + $affiliate_terms = $fs->get_affiliate_terms(); + + $plugin_title = $fs->get_plugin_title(); + $module_type = $fs->is_plugin() ? + WP_FS__MODULE_TYPE_PLUGIN : + WP_FS__MODULE_TYPE_THEME; + + $commission = $affiliate_terms->get_formatted_commission(); + + $readonly = false; + $is_affiliate = is_object( $affiliate ); + $is_pending_affiliate = false; + $email_address = ( is_object( $user ) ? + $user->email : + '' ); + $full_name = ( is_object( $user ) ? + $user->get_name() : + '' ); + $paypal_email_address = ''; + $domain = ''; + $extra_domains = array(); + $promotion_method_social_media = false; + $promotion_method_mobile_apps = false; + $statistics_information = false; + $promotion_method_description = false; + $members_dashboard_login_url = 'https://members.freemius.com/login/'; + + $affiliate_application_data = $fs->get_affiliate_application_data(); + + if ( $is_affiliate && $affiliate->is_pending() ) { + $readonly = 'readonly'; + $is_pending_affiliate = true; + + $paypal_email_address = $affiliate->paypal_email; + $domain = $affiliate->domain; + $statistics_information = $affiliate_application_data['stats_description']; + $promotion_method_description = $affiliate_application_data['promotion_method_description']; + + if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { + $extra_domains = $affiliate_application_data['additional_domains']; + } + + if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { + $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); + $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); + $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); + } + } else { + $current_user = Freemius::_get_current_wp_user(); + $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); + $email_address = $current_user->user_email; + $domain = fs_strip_url_protocol( get_site_url() ); + } + + $affiliate_tracking = 30; + + if ( is_object( $affiliate_terms ) ) { + $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? + ( $affiliate_terms->cookie_days . '-day' ) : + fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); + } + + $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); +?> +
    +
    +
    +
    +
    +
    + + + + is_active() ) : ?> +
    +

    %s', + $members_dashboard_login_url, + $members_dashboard_login_url + ) + ); + ?>

    +
    + + is_suspended() ) { + $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); + $message_container_class = 'notice notice-warning'; + } else if ( $affiliate->is_rejected() ) { + $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); + $message_container_class = 'error'; + } else if ( $affiliate->is_blocked() ) { + $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); + $message_container_class = 'error'; + } + ?> +
    +

    +
    + + +
    +
    + +
    +

    +

    +
    + +

    +
      +
    • + has_renewals_commission() ) : ?> +
    • + + is_session_cookie() ) ) : ?> +
    • + + has_lifetime_commission() ) : ?> +
    • + +
    • +
    • +
    • +
    +
    > +

    + +
    + + > +
    +
    + + > +
    +
    + + > +
    +
    + + > +

    + + + ... + +
    +
    > + +

    + + +
    + > +
    + + +
    +
    + +
    + /> + +
    +
    + /> + +
    +
    +
    + + + +

    + +
    +
    + + + +

    + +
    + +
    + + + + + +
    +
    +
    +
    + + +
    + 'affiliation', + 'module_id' => $fs->get_id(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); +?> \ No newline at end of file diff --git a/freemius/templates/forms/deactivation/contact 2.php b/freemius/templates/forms/deactivation/contact 2.php new file mode 100644 index 0000000..24d67e7 --- /dev/null +++ b/freemius/templates/forms/deactivation/contact 2.php @@ -0,0 +1,23 @@ +get_slug(); + + echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) + . sprintf(" %s", + $fs->contact_url( 'technical_support' ), + fs_text_inline( 'Contact Support', 'contact-support', $slug ) + ); diff --git a/freemius/templates/forms/deactivation/form 2.php b/freemius/templates/forms/deactivation/form 2.php new file mode 100644 index 0000000..f15f6b6 --- /dev/null +++ b/freemius/templates/forms/deactivation/form 2.php @@ -0,0 +1,539 @@ +get_slug(); + + $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; + $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; + $confirmation_message = $VARS['uninstall_confirmation_message']; + + $is_anonymous = ( ! $fs->is_registered() ); + $anonymous_feedback_checkbox_html = ''; + + $reasons_list_items_html = ''; + + if ( $show_deactivation_feedback_form ) { + $reasons = $VARS['reasons']; + + foreach ( $reasons as $reason ) { + $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); + + if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { + $list_item_classes .= ' has-internal-message'; + $reason_internal_message = $reason['internal_message']; + } else { + $reason_internal_message = ''; + } + + $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); + $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); + + $reason_list_item_html = <<< HTML +
    + apply_filters( 'hide_account_tabs', false ) ) : ?> + + + +
    +
    +
    +
    +
    +

    + +
    + + apply_filters( 'hide_license_key', false ) ); + + $profile = array(); + $profile[] = array( + 'id' => 'user_name', + 'title' => fs_text_inline( 'Name', 'name', $slug ), + 'value' => $name + ); + // if (isset($user->email) && false !== strpos($user->email, '@')) + $profile[] = array( + 'id' => 'email', + 'title' => fs_text_inline( 'Email', 'email', $slug ), + 'value' => $user->email + ); + + if ( is_numeric( $user->id ) ) { + $profile[] = array( + 'id' => 'user_id', + 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), + 'value' => $user->id + ); + } + + $profile[] = array( + 'id' => 'product', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ), + 'value' => $fs->get_plugin_title() + ); + + $profile[] = array( + 'id' => 'product_id', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), + 'value' => $fs->get_id() + ); + + if ( ! fs_is_network_admin()) { + $profile[] = array( + 'id' => 'site_id', + 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), + 'value' => is_string( $site->id ) ? + $site->id : + fs_text_inline( 'No ID', 'no-id', $slug ) + ); + + $profile[] = array( + 'id' => 'site_public_key', + 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), + 'value' => $site->public_key + ); + + $profile[] = array( + 'id' => 'site_secret_key', + 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), + 'value' => ( ( is_string( $site->secret_key ) ) ? + $site->secret_key : + fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) + ) + ); + } + + $profile[] = array( + 'id' => 'version', + 'title' => $version_text, + 'value' => $fs->get_plugin_version() + ); + + if ( $is_premium ) { + $profile[] = array( + 'id' => 'beta_program', + 'title' => '', + 'value' => $user->is_beta + ); + } + + if ( $has_paid_plan ) { + if ( $fs->is_trial() ) { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => $plan_text, + 'value' => ( is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ) ) + ); + } + } else { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => ( $is_child_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, + 'value' => strtoupper( is_string( $plan->name ) ? + $plan->title : + strtoupper( $free_text ) + ) + ); + + if ( $is_child_license ) { + $profile[] = array( + 'id' => 'bundle_plan', + 'title' => $bundle_plan_text, + 'value' => strtoupper( $license->parent_plan_title ) + ); + } + } + + if ( is_object( $license ) ) { + if ( ! $hide_license_key ) { + $profile[] = array( + 'id' => 'license_key', + 'title' => fs_text_inline( 'License Key', $slug ), + 'value' => $license->secret_key, + ); + } + } + } + } + ?> + + + + + > + + + + + + + + + is_verified() ) : ?> + + + + is_trial() ) : ?> + + + is_lifetime() ) : ?> + is_first_payment_pending() ) : ?> + is_expired() ?> + + + is_first_payment_pending() ) : ?> + + + is_trial() ) : ?> + + +
    + is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?> + + _get_plan_by_id( $available_license->plan_id ) ?> + $fs, + 'slug' => $slug, + 'license' => $available_license, + 'plan' => $premium_plan, + 'is_localhost' => $site->is_localhost(), + 'install_id' => $site->id, + 'class' => 'button-primary', + ); + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?> + +
    + + + + + + get_unique_affix() . '_sync_license' ) ?> + is_single_plan() ) : ?> + + + + +
    + + + is_first_payment_pending() ) : ?> + + + + + has_premium_version() ) : ?> + + + can_use_premium_code() ) : ?> + + + + + + +
    + + + +
    + + + is_verified() ) : ?> +
    + + + +
    + + + has_release_on_freemius() ) : ?> + + + + + + + + secret_key ) && in_array( $p['id'], array( + 'email', + 'user_name' + ) ) ) + ) : ?> +
    + + + + +
    + +
    +
    +
    + +
    +

    +
    + + + + + + +
    + +
    +
    +
    + + + + + + + + + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + get_updated_account_addons(); + + $installed_addons = $fs->get_installed_addons(); + $installed_addons_ids = array(); + foreach ( $installed_addons as $fs_addon ) { + $installed_addons_ids[] = $fs_addon->get_id(); + } + + $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); + ?> + + +
    +
    + + + + + + + + + + + + + + + + + $fs, + 'addon_id' => $addon_id, + 'odd' => $odd, + 'fs_blog_id' => $fs_blog_id, + 'active_plugins_directories_map' => &$active_plugins_directories_map, + 'is_addon_installed' => $is_addon_installed, + 'addon_info' => $fs->_get_addon_info( $addon_id, $is_addon_installed ) + ); + + fs_require_template( + 'account/partials/addon.php', + $addon_view_params + ); + + $odd = ! $odd; + } ?> + +

    +
    +
    + + + do_action( 'after_account_details' ) ?> + + $VARS['id'] ); + fs_require_once_template( 'account/billing.php', $view_params ); + fs_require_once_template( 'account/payments.php', $view_params ); + } + ?> +
    +
    +
    +
    +
  • + +
    {$reason_internal_message}
    +
  • +HTML; + + $reasons_list_items_html .= $reason_list_item_html; + } + + if ( $is_anonymous ) { + $anonymous_feedback_checkbox_html = sprintf( + '', + fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) + ); + } + } + + // Aliases. + $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); + $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); + $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } +?> + diff --git a/freemius/templates/forms/deactivation/index 2.php b/freemius/templates/forms/deactivation/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/forms/deactivation/index 2.php @@ -0,0 +1,3 @@ +get_slug(); + + $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); + $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); + $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); + + echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) + . " {$use_plugin_anonymously_text}"; \ No newline at end of file diff --git a/freemius/templates/forms/index 2.php b/freemius/templates/forms/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/forms/index 2.php @@ -0,0 +1,3 @@ +get_slug(); + $unique_affix = $fs->get_unique_affix(); + + $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); + $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); + $message_below_input_field = ''; + + $header_title = $fs->is_free_plan() ? + fs_text_inline( 'Activate License', 'activate-license', $slug ) : + fs_text_inline( 'Update License', 'update-license', $slug ); + + if ( $fs->is_registered() ) { + $activate_button_text = $header_title; + } else { + $freemius_site_url = $fs->has_paid_plan() ? + 'https://freemius.com/wordpress/' : + // Insights platform information. + 'https://freemius.com/wordpress/usage-tracking/'; + + $freemius_link = 'freemius.com'; + + $message_below_input_field = sprintf( + fs_text_inline( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), + $fs->get_module_label( true ), + $freemius_link + ); + + $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + } + + $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); + + $is_network_activation = ( + $fs->is_network_active() && + fs_is_network_admin() && + ! $fs->is_delegated_connection() + ); + $network_activation_html = ''; + + $sites_details = array(); + if ( $is_network_activation ) { + $all_sites = Freemius::get_sites(); + + foreach ( $all_sites as $site ) { + $site_details = $fs->get_site_info( $site ); + + $blog_id = Freemius::get_site_blog_id( $site ); + $install = $fs->get_install_by_blog_id($blog_id); + + if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $site_details['license_id'] = $install->license_id; + } + + $sites_details[] = $site_details; + } + + if ( $is_network_activation ) { + $vars = array( + 'id' => $fs->get_id(), + 'sites' => $sites_details, + 'require_license_key' => true + ); + + $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); + } + } + + $premium_licenses = $fs->get_available_premium_licenses(); + $available_licenses = array(); + foreach ( $premium_licenses as $premium_license ) { + $activations_left = $premium_license->left(); + if ( ! ( $activations_left > 0 ) ) { + continue; + } + + $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; + } + + $total_available_licenses = count( $available_licenses ); + if ( $total_available_licenses > 0 ) { + $license_input_html = <<< HTML +
    + + + + + + + + + + + +
    +HTML; + + if ( $total_available_licenses > 1 ) { + // Sort the licenses by number of activations left in descending order. + krsort( $available_licenses ); + + $license_input_html .= ''; + } else { + $available_licenses = array_values( $available_licenses ); + + /** + * @var FS_Plugin_License $available_license + */ + $available_license = $available_licenses[0]; + $value = sprintf( + "%s-Site %s License - %s", + ( 1 == $available_license->quota ? + 'Single' : + $available_license->quota + ), + $fs->_get_plan_by_id( $available_license->plan_id )->title, + ( htmlspecialchars( substr( $available_license->secret_key, 0, 6 ) ) . + str_pad( '', 23 * 6, '•' ) . + htmlspecialchars( substr( $available_license->secret_key, - 3 ) ) ) + ); + + $license_input_html .= <<< HTML + +HTML; + } + + $license_input_html .= <<< HTML +
    + +
    + +
    +
    +
    +HTML; + } else { + $license_input_html = ""; + } + + /** + * IMPORTANT: + * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE + * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) + * FOR MIGRATED MODULES. + */ + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    + {$license_input_html} + {$cant_find_license_key_text} + {$network_activation_html} +

    {$message_below_input_field}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/optout 2.php b/freemius/templates/forms/optout 2.php new file mode 100644 index 0000000..a12e0f9 --- /dev/null +++ b/freemius/templates/forms/optout 2.php @@ -0,0 +1,267 @@ +get_slug(); + + $action = $fs->is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $reconnect_url = $fs->get_activation_url( array( + 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), + 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), + ) ); + + $plugin_title = "{$fs->get_plugin()->title}"; + $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); + $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug ); + $opt_out_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation', $slug ), $fs->get_module_type() ); + $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title ); + $opt_out_message_clicking_opt_out = sprintf( + fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ), + $plugin_title, + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $admin_notice_params = array( + 'id' => '', + 'slug' => $fs->get_id(), + 'type' => 'success', + 'sticky' => false, + 'plugin' => $fs->get_plugin()->title, + 'message' => $opt_out_message_appreciation + ); + + $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params ); + + $modal_content_html = <<< HTML +

    {$opt_out_message_appreciation}

    +

    +

    {$opt_out_message_usage_tracking}

    +

    {$opt_out_message_clicking_opt_out}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); +?> + diff --git a/freemius/templates/forms/premium-versions-upgrade-handler 2.php b/freemius/templates/forms/premium-versions-upgrade-handler 2.php new file mode 100644 index 0000000..f30639b --- /dev/null +++ b/freemius/templates/forms/premium-versions-upgrade-handler 2.php @@ -0,0 +1,205 @@ +get_slug(); + + $plugin_data = $fs->get_plugin_data(); + $plugin_name = $plugin_data['Name']; + $plugin_basename = $fs->get_plugin_basename(); + + $license = $fs->_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $message = sprintf( + fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . + fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), + '', + sprintf( + '%s', + is_object( $license ) ? + fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : + fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) + ), + '' + ); + + $modal_content_html = "

    {$message}

    "; + + $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); + + $renew_license_button_text = is_object( $license ) ? + fs_text_inline( 'Renew license', 'renew-license', $slug ) : + fs_text_inline( 'Buy license', 'buy-license', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/premium-versions-upgrade-metadata 2.php b/freemius/templates/forms/premium-versions-upgrade-metadata 2.php new file mode 100644 index 0000000..5f9fddd --- /dev/null +++ b/freemius/templates/forms/premium-versions-upgrade-metadata 2.php @@ -0,0 +1,47 @@ +_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $plugin_data = $fs->get_plugin_data(); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/resend-key 2.php b/freemius/templates/forms/resend-key 2.php new file mode 100644 index 0000000..f8cafb9 --- /dev/null +++ b/freemius/templates/forms/resend-key 2.php @@ -0,0 +1,247 @@ +get_slug(); + + $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); + $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); + $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); + $other_text = fs_text_inline( 'Other', 'other', $slug ); + + $is_freemium = $fs->is_freemium(); + + $send_button_text_html = esc_html($send_button_text); + + $button_html = <<< HTML + +HTML; + + if ( $is_freemium ) { + $current_user = Freemius::_get_current_wp_user(); + $email = $current_user->user_email; + $esc_email = esc_attr( $email ); + $form_html = <<< HTML + +{$button_html} +HTML; + } else { + $email = ''; + $form_html = <<< HTML +{$button_html} + +HTML; + } + + $message_above_input_field = fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    +
    + {$form_html} +
    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/forms/subscription-cancellation 2.php b/freemius/templates/forms/subscription-cancellation 2.php new file mode 100644 index 0000000..1198fe7 --- /dev/null +++ b/freemius/templates/forms/subscription-cancellation 2.php @@ -0,0 +1,277 @@ +get_slug(); + +/** + * @var FS_Plugin_License $license + */ +$license = $VARS['license']; + +$has_trial = $VARS['has_trial']; + +$subscription_cancellation_context = $has_trial ? + fs_text_inline( 'trial', 'trial', $slug ) : + fs_text_inline( 'subscription', 'subscription', $slug ); + +$plan = $fs->get_plan(); +$module_label = $fs->get_module_label( true ); + +if ( $VARS['is_license_deactivation'] ) { + $subscription_cancellation_text = ''; +} else { + $subscription_cancellation_text = sprintf( + fs_text_inline( + "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", + 'deactivation-or-uninstall-message', + $slug + ), + $module_label + ) . ' '; +} + + $subscription_cancellation_text .= sprintf( + fs_text_inline( + 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', + 'cancel-subscription-message', + $slug + ), + ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), + $subscription_cancellation_context +); + +$cancel_subscription_action_label = sprintf( + fs_esc_html_inline( + "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", + 'cancel-x', + $slug + ), + esc_html( $subscription_cancellation_context ), + sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), + esc_html( $module_label ) +); + +$keep_subscription_active_action_label = esc_html( sprintf( + fs_text_inline( + "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", + 'dont-cancel-x', + $slug + ), + $subscription_cancellation_context +) ); + +$subscription_cancellation_text = esc_html( $subscription_cancellation_text ); + +$subscription_cancellation_html = <<< HTML +

    {$subscription_cancellation_text}

    +
      +
    • + +
    • +
    • + +
    • +
    +HTML; + +$downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); +$cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); +/* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ +$downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); +$prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); +$after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); +$after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); +$after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); + +$subscription_cancellation_confirmation_message = $has_trial ? + fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : + sprintf( + '%s %s %s %s', + sprintf( + $downgrade_x_confirm_text, + ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + human_time_diff( time(), strtotime( $license->expiration ) ) + ), + ( + $license->is_block_features ? + ( + $fs->is_only_premium() ? + sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : + sprintf( $after_downgrade_blocking_text, $plan->title ) + ) : + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) + ), + $prices_increase_text, + fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) + ); + +fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/trial-start 2.php b/freemius/templates/forms/trial-start 2.php new file mode 100644 index 0000000..cea597e --- /dev/null +++ b/freemius/templates/forms/trial-start 2.php @@ -0,0 +1,181 @@ +get_slug(); + + $message_header = sprintf( + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), + '', + '' + ); + $message_content = sprintf( + /* translators: %s: Link to freemius.com */ + fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), + $fs->get_module_type(), + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $modal_content_html = <<< HTML +

    +

    {$message_header}

    +

    {$message_content}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/gdpr-optin-js 2.php b/freemius/templates/gdpr-optin-js 2.php new file mode 100644 index 0000000..4fdc5e3 --- /dev/null +++ b/freemius/templates/gdpr-optin-js 2.php @@ -0,0 +1,66 @@ + + \ No newline at end of file diff --git a/freemius/templates/index 2.php b/freemius/templates/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/index 2.php @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/open-license-activation 2.php b/freemius/templates/js/open-license-activation 2.php new file mode 100644 index 0000000..a88e6f9 --- /dev/null +++ b/freemius/templates/js/open-license-activation 2.php @@ -0,0 +1,37 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/style-premium-theme 2.php b/freemius/templates/js/style-premium-theme 2.php new file mode 100644 index 0000000..942da64 --- /dev/null +++ b/freemius/templates/js/style-premium-theme 2.php @@ -0,0 +1,53 @@ +get_slug(); + +?> + \ No newline at end of file diff --git a/freemius/templates/partials/network-activation 2.php b/freemius/templates/partials/network-activation 2.php new file mode 100644 index 0000000..06cbff2 --- /dev/null +++ b/freemius/templates/partials/network-activation 2.php @@ -0,0 +1,89 @@ +get_slug(); + + $sites = $VARS['sites']; + $require_license_key = $VARS['require_license_key']; + + $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); + $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); +?> +|' ?> + \ No newline at end of file diff --git a/freemius/templates/plugin-icon 2.php b/freemius/templates/plugin-icon 2.php new file mode 100644 index 0000000..ab0fb54 --- /dev/null +++ b/freemius/templates/plugin-icon 2.php @@ -0,0 +1,20 @@ + +
    + +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/description 2.php b/freemius/templates/plugin-info/description 2.php new file mode 100644 index 0000000..26bc67b --- /dev/null +++ b/freemius/templates/plugin-info/description 2.php @@ -0,0 +1,78 @@ +info->selling_point_0 ) || + ! empty( $plugin->info->selling_point_1 ) || + ! empty( $plugin->info->selling_point_2 ) + ) : ?> +
    +
      + + info->{'selling_point_' . $i} ) ) : ?> +
    • + +

      info->{'selling_point_' . $i} ) ?>

    • + + +
    +
    + +
    + info->description, array( + 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), + 'b' => array(), + 'i' => array(), + 'p' => array(), + 'blockquote' => array(), + 'h2' => array(), + 'h3' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array() + ) ); + ?> +
    +info->screenshots ) ) : ?> + info->screenshots ?> +
    +

    slug ) ?>

    +
      + $url ) : ?> + +
    • + + +
    • + +
    +
    + \ No newline at end of file diff --git a/freemius/templates/plugin-info/features 2.php b/freemius/templates/plugin-info/features 2.php new file mode 100644 index 0000000..b3d0fc8 --- /dev/null +++ b/freemius/templates/plugin-info/features 2.php @@ -0,0 +1,114 @@ +features) && is_array($plan->features)) { + foreach ( $plan->features as $feature ) { + if ( ! isset( $features_plan_map[ $feature->id ] ) ) { + $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); + } + + $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; + } + } + + // Add support as a feature. + if ( ! empty( $plan->support_email ) || + ! empty( $plan->support_skype ) || + ! empty( $plan->support_phone ) || + true === $plan->is_success_manager + ) { + if ( ! isset( $features_plan_map['support'] ) ) { + $support_feature = new stdClass(); + $support_feature->id = 'support'; + $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); + $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); + } else { + $support_feature = $features_plan_map['support']; + } + + $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; + } + } + + // Add updates as a feature for all plans. + $updates_feature = new stdClass(); + $updates_feature->id = 'updates'; + $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); + $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); + foreach ( $plans as $plan ) { + $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; + } +?> +
    + + + + + + + + + + + $data ) : ?> + + + + + + + + +
    + title ?> + pricing ) ) { + fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); + } else { + foreach ( $plan->pricing as $pricing ) { + /** + * @var FS_Pricing $pricing + */ + if ( 1 == $pricing->licenses ) { + if ( $pricing->has_annual() ) { + echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); + } else if ( $pricing->has_monthly() ) { + echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); + } else { + echo "\${$pricing->lifetime_price}"; + } + } + } + } + ?> +
    title ) ) ?> + id ] ) ) : ?> + id ]->value ) ) : ?> + id ]->value ) ?> + + + + +
    +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/index 2.php b/freemius/templates/plugin-info/index 2.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/plugin-info/index 2.php @@ -0,0 +1,3 @@ + +
      + $url ) : ?> + +
    1. + +
    2. + +
    diff --git a/freemius/templates/powered-by 2.php b/freemius/templates/powered-by 2.php new file mode 100644 index 0000000..b717216 --- /dev/null +++ b/freemius/templates/powered-by 2.php @@ -0,0 +1,58 @@ + + +
    + \ No newline at end of file diff --git a/freemius/templates/pricing 2.php b/freemius/templates/pricing 2.php new file mode 100644 index 0000000..6cf11b7 --- /dev/null +++ b/freemius/templates/pricing 2.php @@ -0,0 +1,176 @@ +get_slug(); + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + $timestamp, + 'upgrade' + ) ); + } else { + $context_params['home_url'] = home_url(); + } + + if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) + { + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + } + + $query_params = array_merge( $context_params, $_GET, array( + 'next' => $fs->_get_sync_license_url( false, false ), + 'plugin_version' => $fs->get_plugin_version(), + // Billing cycle. + 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), + 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', + ) ); + + if ( ! $fs->is_registered() ) { + $template_data = array( + 'id' => $fs->get_id(), + ); + fs_require_template( 'forms/trial-start.php', $template_data); + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    +
    +
    + + + + + + +
    + + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'pricing', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/secure-https-header 2.php b/freemius/templates/secure-https-header 2.php new file mode 100644 index 0000000..fd12bc2 --- /dev/null +++ b/freemius/templates/secure-https-header 2.php @@ -0,0 +1,39 @@ + +
    + + get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), + $VARS['page'] + ) ) . + ' - ' . + sprintf( + '%s', + 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, + 'Freemius Inc. [US]' + ); + } + ?> +
    \ No newline at end of file diff --git a/freemius/templates/sticky-admin-notice-js 2.php b/freemius/templates/sticky-admin-notice-js 2.php new file mode 100644 index 0000000..028a966 --- /dev/null +++ b/freemius/templates/sticky-admin-notice-js 2.php @@ -0,0 +1,39 @@ + + \ No newline at end of file diff --git a/freemius/templates/tabs 2.php b/freemius/templates/tabs 2.php new file mode 100644 index 0000000..a0391a8 --- /dev/null +++ b/freemius/templates/tabs 2.php @@ -0,0 +1,190 @@ +get_slug(); + + $menu_items = $fs->get_menu_items(); + + $is_free_wp_org_theme = $fs->is_free_wp_org_theme(); + + $tabs = array(); + foreach ( $menu_items as $priority => $items ) { + foreach ( $items as $item ) { + if ( ! $item['show_submenu'] ) { + $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? + 'support' : + $item['menu_slug']; + + if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { + continue; + } + + if ( ! $is_free_wp_org_theme || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { + continue; + } + } + + $url = $fs->_get_admin_page_url( $item['menu_slug'] ); + $title = $item['menu_title']; + + $tab = array( + 'label' => $title, + 'href' => $url, + 'slug' => $item['menu_slug'], + ); + + if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { + $tab['href'] .= '&trial=true'; + } + + $tabs[] = $tab; + } + } +?> + \ No newline at end of file diff --git a/freemius/templates/tabs-capture-js 2.php b/freemius/templates/tabs-capture-js 2.php new file mode 100644 index 0000000..236be3b --- /dev/null +++ b/freemius/templates/tabs-capture-js 2.php @@ -0,0 +1,63 @@ +get_slug(); +?> + \ No newline at end of file diff --git a/vendor/composer/ClassLoader 2.php b/vendor/composer/ClassLoader 2.php new file mode 100644 index 0000000..fce8549 --- /dev/null +++ b/vendor/composer/ClassLoader 2.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE 2 b/vendor/composer/LICENSE 2 new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE 2 @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap 2.php b/vendor/composer/autoload_classmap 2.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/vendor/composer/autoload_classmap 2.php @@ -0,0 +1,9 @@ + $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces 2.php b/vendor/composer/autoload_namespaces 2.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces 2.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), +); diff --git a/vendor/composer/autoload_real 2.php b/vendor/composer/autoload_real 2.php new file mode 100644 index 0000000..6163c71 --- /dev/null +++ b/vendor/composer/autoload_real 2.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit868985b3f545f384cf3e2610d157218a::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit868985b3f545f384cf3e2610d157218a::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire868985b3f545f384cf3e2610d157218a($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire868985b3f545f384cf3e2610d157218a($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static 2.php b/vendor/composer/autoload_static 2.php new file mode 100644 index 0000000..471e330 --- /dev/null +++ b/vendor/composer/autoload_static 2.php @@ -0,0 +1,40 @@ + __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Yaml\\' => 23, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit868985b3f545f384cf3e2610d157218a::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit868985b3f545f384cf3e2610d157218a::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed 2.json b/vendor/composer/installed 2.json new file mode 100644 index 0000000..16a57ca --- /dev/null +++ b/vendor/composer/installed 2.json @@ -0,0 +1,162 @@ +[ + { + "name": "semantic/ui", + "version": "2.4.1", + "version_normalized": "2.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/Semantic-Org/Semantic-UI.git", + "reference": "a753d5b7841ab6adbe6d8a3b40086b452f0ba715" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Semantic-Org/Semantic-UI/zipball/a753d5b7841ab6adbe6d8a3b40086b452f0ba715", + "reference": "a753d5b7841ab6adbe6d8a3b40086b452f0ba715", + "shasum": "" + }, + "time": "2018-10-13T22:51:35+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jack Lukic", + "email": "jacklukic@gmail.com", + "homepage": "http://www.jacklukic.com", + "role": "Creator" + } + ], + "description": "Semantic empowers designers and developers by creating a shared vocabulary for UI.", + "homepage": "http://www.semantic-ui.com", + "keywords": [ + "css", + "framework", + "semantic", + "ui" + ] + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2019-11-27T13:56:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ] + }, + { + "name": "symfony/yaml", + "version": "v5.0.2", + "version_normalized": "5.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "847661e77afa48d99ecfa508e8b60f0b029a19c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/847661e77afa48d99ecfa508e8b60f0b029a19c0", + "reference": "847661e77afa48d99ecfa508e8b60f0b029a19c0", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "time": "2019-12-10T11:06:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com" + } +] diff --git a/vendor/symfony/polyfill-ctype/Ctype 2.php b/vendor/symfony/polyfill-ctype/Ctype 2.php new file mode 100644 index 0000000..58414dc --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype 2.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE 2 b/vendor/symfony/polyfill-ctype/LICENSE 2 new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE 2 @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README 2.md b/vendor/symfony/polyfill-ctype/README 2.md new file mode 100644 index 0000000..8add1ab --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README 2.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap 2.php b/vendor/symfony/polyfill-ctype/bootstrap 2.php new file mode 100644 index 0000000..14d1d0f --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap 2.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } + function ctype_print($text) { return p\Ctype::ctype_print($text); } + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } + function ctype_space($text) { return p\Ctype::ctype_space($text); } + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer 2.json b/vendor/symfony/polyfill-ctype/composer 2.json new file mode 100644 index 0000000..2a2ea04 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer 2.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + } +} diff --git a/vendor/symfony/yaml/.gitattributes 2 b/vendor/symfony/yaml/.gitattributes 2 new file mode 100644 index 0000000..ebb9287 --- /dev/null +++ b/vendor/symfony/yaml/.gitattributes 2 @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitignore export-ignore diff --git a/vendor/symfony/yaml/CHANGELOG 2.md b/vendor/symfony/yaml/CHANGELOG 2.md new file mode 100644 index 0000000..a3a7270 --- /dev/null +++ b/vendor/symfony/yaml/CHANGELOG 2.md @@ -0,0 +1,202 @@ +CHANGELOG +========= + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/vendor/symfony/yaml/Command/LintCommand 2.php b/vendor/symfony/yaml/Command/LintCommand 2.php new file mode 100644 index 0000000..2c0b60c --- /dev/null +++ b/vendor/symfony/yaml/Command/LintCommand 2.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + */ +class LintCommand extends Command +{ + protected static $defaultName = 'lint:yaml'; + + private $parser; + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription('Lints a file and outputs encountered errors') + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags') + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format'); + $this->displayCorrectFiles = $output->isVerbose(); + $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + + if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + if (!$this->parser) { + $this->parser = new Parser(); + } + + return $this->parser; + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } +} diff --git a/vendor/symfony/yaml/Dumper 2.php b/vendor/symfony/yaml/Dumper 2.php new file mode 100644 index 0000000..0b56779 --- /dev/null +++ b/vendor/symfony/yaml/Dumper 2.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var int + */ + protected $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { + $output .= $prefix.Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); + + foreach (explode("\n", $value) as $row) { + $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf(" |%s\n", $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/vendor/symfony/yaml/Escaper 2.php b/vendor/symfony/yaml/Escaper 2.php new file mode 100644 index 0000000..afff168 --- /dev/null +++ b/vendor/symfony/yaml/Escaper 2.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private static $escapees = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private static $escaped = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require double quotes + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require single quotes + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException 2.php b/vendor/symfony/yaml/Exception/DumpException 2.php new file mode 100644 index 0000000..cce972f --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException 2.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface 2.php b/vendor/symfony/yaml/Exception/ExceptionInterface 2.php new file mode 100644 index 0000000..9091316 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ExceptionInterface 2.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException 2.php b/vendor/symfony/yaml/Exception/ParseException 2.php new file mode 100644 index 0000000..eae8838 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException 2.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + * @param \Exception|null $previous The previous exception + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + * + * @return string The snippet of code + */ + public function getSnippet() + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + */ + public function setSnippet(string $snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + * + * @return string The filename + */ + public function getParsedFile() + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + */ + public function setParsedFile(string $parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + * + * @return int The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + */ + public function setParsedLine(int $parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException 2.php b/vendor/symfony/yaml/Exception/RuntimeException 2.php new file mode 100644 index 0000000..3f36b73 --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException 2.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline 2.php b/vendor/symfony/yaml/Inline 2.php new file mode 100644 index 0000000..fc662b5 --- /dev/null +++ b/vendor/symfony/yaml/Inline 2.php @@ -0,0 +1,753 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + * + * @internal + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static $parsedLineNumber = -1; + public static $parsedFilename; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + private static $objectForMap = false; + private static $constantSupport = false; + + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @return mixed A PHP value + * + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array $references = []) + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + try { + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } finally { + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + } + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML string representing the PHP value + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump($value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + $output = []; + + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return \is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + $locale = setlocale(LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + $repr = '!!float '.$repr; + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + * + * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check + * + * @return bool true if value is hash array, false otherwise + */ + public static function isHash($value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML string representing the PHP array + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // hash + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @return mixed + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array $references = []) + { + if (\in_array($scalar[$i], ['"', "'"])) { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, \strlen($match[0]) - 2); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array $references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $isQuoted = \in_array($sequence[$i], ['"', "'"]); + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @return array|\stdClass + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array $references = []) + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, []); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @return mixed The evaluated YAML string + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array $references = []) + { + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + + if (0 === strpos($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case 0 === strpos($scalar, '!!str '): + return (string) substr($scalar, 6); + case 0 === strpos($scalar, '! '): + return substr($scalar, 2); + case 0 === strpos($scalar, '!php/object'): + if (self::$objectSupport) { + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case 0 === strpos($scalar, '!php/const'): + if (self::$constantSupport) { + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case 0 === strpos($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + default: + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + } + + // Optimize for returning strings. + // no break + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', (string) $scalar); + } + + switch (true) { + case ctype_digit($scalar): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[1] ? -octdec(substr($scalar, 1)) : (($raw === (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + if (Yaml::PARSE_DATETIME & $flags) { + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + return new \DateTime($scalar, new \DateTimeZone('UTC')); + } + + $timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $time = strtotime($scalar); + date_default_timezone_set($timeZone); + + return $time; + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value): bool + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @return string The regular expression + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE 2 b/vendor/symfony/yaml/LICENSE 2 new file mode 100644 index 0000000..a677f43 --- /dev/null +++ b/vendor/symfony/yaml/LICENSE 2 @@ -0,0 +1,19 @@ +Copyright (c) 2004-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/yaml/Parser 2.php b/vendor/symfony/yaml/Parser 2.php new file mode 100644 index 0000000..3c4faee --- /dev/null +++ b/vendor/symfony/yaml/Parser 2.php @@ -0,0 +1,1245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + * + * @final + */ +class Parser +{ + const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + + private $filename; + private $offset = 0; + private $totalNumberOfLines; + private $lines = []; + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = []; + private $skippedLineNumbers = []; + private $locallySkippedLineNumbers = []; + private $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0) + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0) + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + $mbEncoding = null; + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + try { + $data = $this->doParse($value, $flags); + } finally { + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + $this->lines = []; + $this->currentLine = ''; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + } + + return $data; + } + + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->locallySkippedLineNumbers = []; + + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = \count($this->lines); + } + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if (isset($values['leadspaces']) + && self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P.+))?$#u', rtrim($this->currentLine), $values) + && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException(sprintf('%s keys are not supported. Quote your evaluable mapping keys instead.', is_numeric($key) ? 'Numeric' : 'Non-string'), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P[^ ]++) *+(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + if ('' !== ltrim($line) && '#' === ltrim($line)[0]) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (false !== strpos($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === trim($line)) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== trim($line) && '\\' === substr($line, -1)) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== trim($line)) { + $value .= trim($line); + } + + if ('' === trim($line)) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === substr($line, -1)) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException $e) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + * + * @return int The current line number + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + /** + * Returns the current line indentation. + * + * @return int The current line indentation + */ + private function getCurrentLineIndentation(): int + { + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + + while ($this->moveToNextLine()) { + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= \count($this->lines) - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context) + { + if (0 === strpos($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs); + } + + $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; + + // do not take following lines into account when the current line is a quoted single line value + if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { + return Inline::parse($value, $flags, $this->refs); + } + + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + + // quoted string values end with a line that is terminated with the quotation character + if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { + break; + } + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return bool Returns true if the next line is indented, false otherwise + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isCurrentLineBlank(): bool + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isCurrentLineComment(): bool + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection. + * + * @return bool Returns true if the next line starts unindented collection, false otherwise + */ + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item. + * + * @return bool Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @see preg_last_error() + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Error.'; + } + + throw new ParseException($error); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function parseQuotedString(string $yaml): ?string + { + if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) { + throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml)); + } + + $lines = [$yaml]; + + while ($this->moveToNextLine()) { + $lines[] = $this->currentLine; + + if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) { + break; + } + } + + $value = ''; + + for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) { + if ('' === trim($lines[$i])) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) { + $value .= ltrim(substr($lines[$i], 0, -1)); + } elseif ('' !== trim($lines[$i])) { + $value .= trim($lines[$i]); + } + + if ('' === trim($lines[$i])) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === substr($lines[$i], -1)) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + return $value; + + for ($i = 1; isset($yaml[$i]) && $quotation !== $yaml[$i]; ++$i) { + } + + // quoted single line string + if (isset($yaml[$i]) && $quotation === $yaml[$i]) { + return $yaml; + } + + $lines = [$yaml]; + + while ($this->moveToNextLine()) { + for ($i = 1; isset($this->currentLine[$i]) && $quotation !== $this->currentLine[$i]; ++$i) { + } + + $lines[] = trim($this->currentLine); + + if (isset($this->currentLine[$i]) && $quotation === $this->currentLine[$i]) { + break; + } + } + } + + private function lexInlineMapping(string $yaml): string + { + if ('' === $yaml || '{' !== $yaml[0]) { + throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); + } + + for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) { + } + + if (isset($yaml[$i]) && '}' === $yaml[$i]) { + return $yaml; + } + + $lines = [$yaml]; + + while ($this->moveToNextLine()) { + $lines[] = $this->currentLine; + } + + return implode("\n", $lines); + } + + private function lexInlineSequence(string $yaml): string + { + if ('' === $yaml || '[' !== $yaml[0]) { + throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); + } + + for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) { + } + + if (isset($yaml[$i]) && ']' === $yaml[$i]) { + return $yaml; + } + + $value = $yaml; + + while ($this->moveToNextLine()) { + for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) { + } + + $value .= trim($this->currentLine); + + if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) { + break; + } + } + + return $value; + } +} diff --git a/vendor/symfony/yaml/README 2.md b/vendor/symfony/yaml/README 2.md new file mode 100644 index 0000000..0d32488 --- /dev/null +++ b/vendor/symfony/yaml/README 2.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/yaml/Tag/TaggedValue 2.php b/vendor/symfony/yaml/Tag/TaggedValue 2.php new file mode 100644 index 0000000..4ea3406 --- /dev/null +++ b/vendor/symfony/yaml/Tag/TaggedValue 2.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tag; + +/** + * @author Nicolas Grekas + * @author Guilhem N. + */ +final class TaggedValue +{ + private $tag; + private $value; + + public function __construct(string $tag, $value) + { + $this->tag = $tag; + $this->value = $value; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/symfony/yaml/Unescaper 2.php b/vendor/symfony/yaml/Unescaper 2.php new file mode 100644 index 0000000..9c3a19e --- /dev/null +++ b/vendor/symfony/yaml/Unescaper 2.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + * + * @return string The unescaped string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + * + * @return string The unescaped string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + * + * @return string The unescaped character + */ + private function unescapeCharacter(string $value): string + { + switch ($value[1]) { + case '0': + return "\x0"; + case 'a': + return "\x7"; + case 'b': + return "\x8"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\xB"; + case 'f': + return "\xC"; + case 'r': + return "\r"; + case 'e': + return "\x1B"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return "\xC2\x85"; + case '_': + // U+00A0 NO-BREAK SPACE + return "\xC2\xA0"; + case 'L': + // U+2028 LINE SEPARATOR + return "\xE2\x80\xA8"; + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return "\xE2\x80\xA9"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + default: + throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); + } + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/vendor/symfony/yaml/Yaml 2.php b/vendor/symfony/yaml/Yaml 2.php new file mode 100644 index 0000000..4efceb3 --- /dev/null +++ b/vendor/symfony/yaml/Yaml 2.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @final + */ +class Yaml +{ + const DUMP_OBJECT = 1; + const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + const PARSE_OBJECT = 4; + const PARSE_OBJECT_FOR_MAP = 8; + const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + const PARSE_DATETIME = 32; + const DUMP_OBJECT_AS_MAP = 64; + const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + const PARSE_CONSTANT = 256; + const PARSE_CUSTOM_TAGS = 512; + const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + const DUMP_NULL_AS_TILDE = 2048; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0) + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0) + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + * + * @return string A YAML string representing the original PHP value + */ + public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/vendor/symfony/yaml/composer 2.json b/vendor/symfony/yaml/composer 2.json new file mode 100644 index 0000000..598833b --- /dev/null +++ b/vendor/symfony/yaml/composer 2.json @@ -0,0 +1,43 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Symfony Yaml Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} From 23e2668e0026733a1329fa0129dd6011d73aa986 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Tue, 14 Jul 2020 11:55:47 -0400 Subject: [PATCH 129/377] push ds store to master --- .DS_Store | Bin 0 -> 6148 bytes .DS_Store~Stashed changes | Bin 0 -> 6148 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 .DS_Store~Stashed changes diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..21dc07c97503793be3f93b56315ccc144e8a8696 GIT binary patch literal 6148 zcmeHKIS#@=4733uB$||z`vrcmLhu4Uz}X>D1o~9Gi>EO@goFYO5)B$h_Ut;IjWR_X zi-<0+hq*{2A``fw+${9X?wj{)kQoKSamGd3L%Toi&gWCF`hCK-Yu#{i)1S z0V+TRr~nn90-qGH-V2*d0~x6R6`%r71?>A!;D$A^3-nJ1f{y^e5z=m0`z!%0mH^ho zE)W@*1{D}o%@IR`j(o|wn%D&fT{MRe%_nP4DC$qg`NhjcYak;PpaQQ73}f3^|6jpB z%>S<>?x+A2_$vi;v|KM2c%|&Ey_d6ITi_eG)!gA`SUUy5+cD7FF*eqYXI>O_#n!lA V6T3jCBkyz|e+En!8Ws4p0uNok6|w*T literal 0 HcmV?d00001 diff --git a/.DS_Store~Stashed changes b/.DS_Store~Stashed changes new file mode 100644 index 0000000000000000000000000000000000000000..e9984ba50047e81e47c4174a5b995ddeac7dc2b0 GIT binary patch literal 6148 zcmeH~K~BR!3`M_bg(7v+CCj-2HyBmr1YDrdAXF*PMbv$ET$^u?K`jyLjs^ObJTLLs z8RakH7=Ud~{XMV-(9xav@MUhk?>@7ej4Vj=XY7aP<8kolZEjtK*DkQfh_N5f>koLt z6JGIhxzXW(cjgZ`q5rvg^)w|&0VyB_q<|EV0_@pt-KtTm6p#W^;9CLzJ`}pMCa1>u zba06gfLt;h#_O0R$l?LACZ|S5Xx2)}R%$t7SSx3|WL`~9jjbG(!-wU`mJ^D_>3n{P zbXaZFDg~s#r2;;C)7IYqALzf#|DGe00#e|gDqzd)<95TB%icPBP4BgZzN3E`b0eKg lv|=(^F>kyT-<{ Date: Fri, 17 Jul 2020 00:20:56 +1000 Subject: [PATCH 130/377] 2.3.2 release updates --- .gitignore | 2 +- docs/Roadmap.md | 8 ++++---- docs/Shortcodes.md | 19 ++++++++++++++++++- docs/Widgets.md | 3 ++- radio-station.php | 2 +- readme.txt | 6 +++--- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 416a3c3..5bfbc8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -.DS_Store? +.DS_Store* ._* .Spotlight-V100 .Trashes diff --git a/docs/Roadmap.md b/docs/Roadmap.md index 177b5c5..4428ede 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -16,19 +16,19 @@ Normal = Feature Completed | Free | Version | Pro | Version | | --- | --- | --- | --- | | Scheduler with Conflict Checker | 2.3.0 | *with Visual Schedule Editor* | 2.3.5 | -| Master Schedule Views | 2.2.8 | **with View Switching** | 2.3.2 | +| Master Schedule Views | 2.2.8 | with View Switching | 2.3.2 | | Show Page Display Template | 2.3.0 | *with Social Icons* | 2.3.4 | | Show Genre Assignments | 2.2.8 | with Genre Term Image Support | 2.3.0 | | Host / Producer / Show Editor Roles | 2.3.0 | with Role Assignment Interface | 2.3.0 | -| *Streaming Widget Player* | 2.3.2 | **with Sticky Sitewide Player** | 2.3.3 | +| **Streaming Widget Player** | 2.3.3 | **with Sticky Sitewide Player** | 2.3.3 | | Current and Upcoming Show Widgets | 2.2.8 | - | - | -| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.2 | +| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.3 | | Playlist Widget | 2.2.8 | *with Track Affiliate Links* | 2.3.4 | | Show Posts and Playlists | 2.2.8 | *with Show Episodes Post Type* | 2.3.4 | | Post Type Archive Shortcodes | 2.3.0 | *with Grid View* | 2.3.4 | | Show Hosts and Producers | 2.2.8 | *with Profile Page Templates* | 2.3.4 | | REST/Feed API Endpoints | 2.3.0 | *with Episodes, Hosts and Producers* | 2.3.4 | -| (*Radio Clock and Widget** | 2.3.2 | *with Timezone Switcher* | 2.3.3 | +| Radio Clock and Widget | 2.3.2 | *with Timezone Switcher* | 2.3.3 | | Schedule Caching | 2.3.0 | with Show Meta Caching | 2.3.0 | [comment]: # (Show and Override Feeds) diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 06045bf..a9e43a9 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -26,6 +26,7 @@ The following attributes are available for the shortcode: * *view* : Which View to use for display output. 'table', 'tabbed', 'list', 'divs', 'legacy'. Default 'table'. * *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. +* *clock* : Display Radio Clock above schedule. 0 or 1. Default is the Plugin Setting. * *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. * *show_times* : Whether to display the show shift's start and end times. 0 or 1. Default 1. * *show_image* : Whether the display the show's avatar. 0 or 1. Default 0 (1 for Tabbed View.) @@ -48,8 +49,24 @@ Example: Display the schedule in 24-hour time format, use `[master-schedule time `[radio-timezone]` -Displays the Radio Station Timezone selected via Plugin Settings. There are no attributes for this shortcode. +Displays the Radio Station Timezone selected via Plugin Settings. There are no attributes for this shortcode. This is the default display above the Master Schedule when the Radio Clock is turned off in the Plugin Settings. +#### Radio Clock Shortcode + +`[radio-clock]` + +Added in 2.3.2. Displays the current server time and user time. Also available as a Widget. + +The following attributes are available for this shortcode: + +* *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. +* *seconds* : Display seconds with current times. 0 or 1. Default 0. +* *day* : Display day after current times. 'full', 'short' or 'none'. Default 'full'. +* *date* : Display date after current times. 0 or 1. Default 1. +* *month* : Display months after current times. 'full', 'short' or 'none'. Default 'full'. +* *zone* : Display timezone after current times. 0 or 1. Default 1. + +The Radio Clock can also be displayed by default above the Master Schedule by enabling this in the Plugin Settings. (It's display attributes there can changed via the `radio_station_schedule_clock` filter.) ## Archive Shortcodes diff --git a/docs/Widgets.md b/docs/Widgets.md index f6a1039..c3dba82 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -83,8 +83,9 @@ Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" l [Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-playlist-widget/) ## Radio Clock Widget +`[radio-clock]` (see [Radio Clock Shortcode](./Shortcodes.md#radio-clock-widget) -A future version of Radio Station will include a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page.) +As of 2.3.2, Radio Station now includes a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page) alongside the site visitor's current time (via browser detection.) ### [Pro] Timezone Switcher A future version of Radio Station Pro will include a user Timezone Switcher in the which will allow your listener's to select a timezone and display adjusted Schedule and Show times. diff --git a/radio-station.php b/radio-station.php index cf3980b..a4cafa3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.1.12 +Version: 2.3.2 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station diff --git a/readme.txt b/readme.txt index c084899..ddea30b 100644 --- a/readme.txt +++ b/readme.txt @@ -571,10 +571,10 @@ You may translate the plugin into another language. Please visit our [WordPress == Upgrade Notice == = 2.3.1 = -* Bugfix Update and Extended Directory Listing Offer -* https://netmix.com/improved-netmix-directory/ -* AJAX Saving of Show Shifts and Playlist Tracks +* Improved Times, AJAX Loading and Bugfix Update +* https://netmix.com/radio-station-2-3-2-release/ * Radio Clock Widget and Widget AJAX Loading +* AJAX Saving of Show Shifts and Playlist Tracks * Automated current Show schedule highlighting * Improved timezones, overrides, shift checking and more From 74f1cad2f0694737961da8899b9e6f3386cdbe4e Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 17 Jul 2020 00:24:39 +1000 Subject: [PATCH 131/377] 2.3.2 release sync --- .gitignore | 2 +- CHANGELOG.md | 29 + LICENSE | 6 +- css/rs-schedule.css | 68 +- css/rs-shortcodes.css | 54 + docs/Data.md | 3 +- docs/FAQ.md | 12 + docs/Options.md | 4 +- docs/Roadmap.md | 40 +- docs/Shortcodes.md | 25 +- docs/Widgets.md | 3 +- includes/class-current-playlist-widget.php | 43 +- includes/class-current-show-widget.php | 48 +- includes/class-radio-clock-widget.php | 186 ++ includes/class-upcoming-shows-widget.php | 33 +- includes/data-feeds.php | 27 +- includes/legacy.php | 15 +- includes/master-schedule.php | 346 ++- includes/post-types-admin.php | 1544 ++++++++++---- includes/post-types.php | 130 +- includes/shortcodes.php | 744 +++++-- includes/support-functions.php | 2200 +++++++++++++++----- js/jstz.js | 1462 +++++++++++++ js/jstz.min.js | 2 + js/radio-station-clock.js | 150 ++ js/radio-station-countdown.js | 49 +- js/radio-station.js | 65 + languages/radio-station-nl_NL.po | 214 +- loader.php | 262 ++- radio-station-admin.php | 44 +- radio-station.php | 373 +++- readme.txt | 39 +- templates/master-schedule-div.php | 77 +- templates/master-schedule-legacy.php | 6 +- templates/master-schedule-list.php | 207 +- templates/master-schedule-table.php | 279 ++- templates/master-schedule-tabs.php | 240 ++- templates/single-show-content.php | 108 +- 38 files changed, 7321 insertions(+), 1818 deletions(-) create mode 100644 includes/class-radio-clock-widget.php create mode 100644 js/jstz.js create mode 100644 js/jstz.min.js create mode 100644 js/radio-station-clock.js diff --git a/.gitignore b/.gitignore index 416a3c3..5bfbc8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -.DS_Store? +.DS_Store* ._* .Spotlight-V100 .Trashes diff --git a/CHANGELOG.md b/CHANGELOG.md index 56e5c90..7ed3e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.2 +* Update: Plugin Loader (1.1.2) with settings link fix +* Improved: use plugin timezone setting for all times +* Improved: show shift conflict checker logic +* Added: Radio Clock Widget for user/server time display +* Added: AJAX widget load option (to bypass page caches) +* Added: automated show schedule highlighting (table/tabs/list) +* Added: playlist track arrows for re-ordering tracks +* Added: AJAX save of show shifts and playlist tracks +* Added: post type editing metabox position filtering +* Added: more display attributes to Master Schedule shortcode +* Added: time format filters for time output displays +* Added: javascript user timezone display on Master Schedule +* Fixed: handling of UTC only timezone settings +* Fixed: added check for empty role capabilities +* Fixed: added settings submenu redirection fix +* Fixed: show and override midnight end conflict +* Fixed: calculate next shows at end of schedule week +* Fixed: metaboxes disappearing on position sorting +* Fixed: move tracks marked New to end of Playlist on update +* Fixed: override shift array output showing above schedule +* Fixed: master schedule specify days attribute bug +* Fixed: display real end time of overnight split shifts +* Fixed: master schedule display with days attribute +* Fixed: logic for Affected Shifts in override list +* Fixed: removed auto-tab selection change on tab view resize +* Fixed: Current Show widget schedule/countdown for Overrides +* Fixed: multiple overrides in schedule range variable conflict + ### 2.3.1 * Update: Plugin Loader (1.1.1) with Freemius first path fix * Fixed: conditions for Schedule Override time calculations diff --git a/LICENSE b/LICENSE index edc6672..e72bfdd 100644 --- a/LICENSE +++ b/LICENSE @@ -671,8 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -<<<<<<< HEAD -. -======= -. ->>>>>>> release/2.3.0 +. \ No newline at end of file diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 54d86eb..729f622 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -17,16 +17,14 @@ font-size: 0.8em; vertical-align: top; text-align: left; + margin-top: 10px; } -#master-schedule-clock-wrapper .radio-clock-title, #master-schedule-timezone-wrapper .radio-timezone-title { - font-weight: bold; -} /* Genre Selector */ /* -------------- */ #master-genre-list { - font-size: 0.8em; + font-size: 1em; float: left; } @@ -53,6 +51,10 @@ height: 100%; } +#master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row { + width: 100px; +} + #master-program-schedule th { width: auto; text-align: center; @@ -153,7 +155,7 @@ #master-program-schedule .show-info .master-show-entry { position: relative; display: block; - height: 100%; + /* height: 100%; */ } #master-program-schedule .show-info .master-show-entry.finished { @@ -293,12 +295,28 @@ margin-left: 0; } -#master-schedule-tabs .master-schedule-tabs-day-name { +#master-schedule-tabs .master-schedule-tabs-day.current-day { + border: 2px solid #000000; + border-bottom: 0; +} + +#master-schedule-tabs .master-schedule-tabs-headings { display: inline-block; padding: 5px 10px 5px 10px; +} + +#master-schedule-tabs .master-schedule-tabs-day-name { + /* display: inline-block; + padding: 5px 10px 5px 10px; */ + display: block; font-size: 1em; } +#master-schedule-tabs .master-schedule-tabs-date { + display: block; + font-size: 0.75em; +} + #master-schedule-tabs .master-schedule-tabs-day.active-day-tab { font-weight: bold; background-color: #ffffff; @@ -321,6 +339,12 @@ z-index: 999; } +#master-schedule-tab-panels .master-schedule-tabs-selected { + display: none; + padding: 10px 10px 0 10px; + margin: 0; +} + #master-schedule-tab-panels .master-schedule-tabs-panel { display: none; float: left; @@ -333,12 +357,28 @@ } #master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show { - padding: 10px 20px; - margin: 0; + padding: 10px 10px; + margin: 0 10px; clear: both; list-style: none; } +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.nowplaying { + border: 1px solid #F00000; +} + +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.first-show { + margin-top: 20px; +} + +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.last-show { + margin-bottom: 20px; +} + +#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-no-shows { + padding-top: 30px; padding-bottom: 30px; +} + #master-schedule-tab-panels .master-schedule-tabs-panel.active-day-panel { display: block; border: 1px solid #333333; @@ -356,11 +396,19 @@ } #master-schedule-tab-panels .master-schedule-tabs-show .show-image { - margin-right: 20px; - min-width: 200px; + margin-right: 10px; + width: 180px; text-align: center; } +#master-schedule-tab-panels .master-schedule-tabs-show .show-info { + width: 260px; +} + +#master-schedule-tab-panels .master-schedule-tabs-show .show-desc { + max-width: 400px; +} + #master-schedule-tab-panels .master-schedule-tabs-show .show-time, #master-schedule-tab-panels .master-schedule-tabs-show .show-user-time, #master-schedule-tab-panels .master-schedule-tabs-show .show-host-names { diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index fe1fb23..65d30b4 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -11,6 +11,55 @@ font-size: 0.8em; } +/* Timezone Shortcode */ +/* ------------------ */ +.radio-timezone-title, .radio-timezone, .radio-user-timezone-title, .radio-user-timezone { + display: inline-block; +} + +.radio-timezone-title, .radio-user-timezone-title, .radio-clock-title { + font-weight: bold; +} + +.radio-user-timezone-title { + display: none; + margin-left: 30px; +} + +/* Clock Shortcode */ +/* --------------- */ +.radio-clock-title, .radio-server-time, .radio-server-date, .radio-server-zone, +.radio-user-time, .radio-user-date, .radio-user-zone { + display: inline-block; +} + +.radio-clock-title { + min-width: 100px; +} + +.radio-server-time, .radio-user-time, .radio-server-date, .radio-user-date { + margin-left: 10px; +} + +.radio-server-zone, .radio-user-zone { + margin-left: 20px; +} + +.radio-station-clock-widget .radio-station-server-clock, .radio-station-clock-widget .radio-station-user-clock { + font-size: 0.9em; + margin-top: 10px; +} + +.radio-station-clock-widget .radio-server-zone, .radio-station-clock-widget .radio-user-zone { + display: block; + margin-left: 110px; + font-size: 0.9em; +} + +.user-timezone-change, .user-timezone-select { + display: inline-block; +} + /* Archive Shortcodes */ /* ------------------ */ @@ -136,6 +185,10 @@ font-size: 0.85em; } +.current-show-hosts, .upcoming-show-hosts, .on-air-dj-names { + overflow: hidden; +} + .current-show-list, .upcoming-shows-list, .on-air-list, .widget .on-air-list, .on-air-upcoming-list, .widget .on-air-upcoming-list { list-style: none; @@ -168,6 +221,7 @@ font-size: 0.85em; margin-top: 10px; margin-bottom: 10px; + overflow: hidden; } .current-show-shifts, .upcoming-show-shifts, .on-air-dj-sched { diff --git a/docs/Data.md b/docs/Data.md index 03178d8..e1de5ff 100644 --- a/docs/Data.md +++ b/docs/Data.md @@ -61,5 +61,6 @@ There are filters throughout the plugin that allow you to override data values a You can add your own custom filters via a Code Snippets plugin (which has the advantage of checking syntax for you), or in your Child Theme's `functions.php`, or in a file with a PHP extension in your `/wp-content/mu-plugins/` directory. -Currently you can find these filters by searching the plugin code for `apply_filters`. We will be gradually adding to the list of available filters here in future documentation. +### Filter List +Currently you can find these filters by searching the plugin code for `apply_filters( 'radio_station'`. Af full list of available filters will be gradually added here in future plugin documentation. diff --git a/docs/FAQ.md b/docs/FAQ.md index 762024d..c4460ef 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -91,3 +91,15 @@ Right now: ### Can the plugin be translated into my language? You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. + +### How do I install the latest Development version for testing? + +If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: + +1. Download the `develop` branch zip from the Github repository at: +`https://github.com/netmix/radio-station/tree/develop/` +2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` +3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` +4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` + +You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. \ No newline at end of file diff --git a/docs/Options.md b/docs/Options.md index 5321464..1c02228 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -156,6 +156,6 @@ Default: On. Allow users with WordPress Editor role to edit all Radio Station po Allows you to assign any of the Radio Station plugin Roles directly to any user. -## Value Filters +## Setting Value Filters -For custom flexibility, all Plugin Settings can also be filtered via their respective option key. Use `add_filter` to add a filter to `radio_station_{settings_key}`, check your desired conditions to modify the value and return the value. \ No newline at end of file +For custom flexibility, all Plugin Settings can also be filtered via their respective option key. Use `add_filter` to add a filter to `radio_station_{settings_key}`, then check your desired conditions to modify the value before returning it. eg: diff --git a/docs/Roadmap.md b/docs/Roadmap.md index c0b4ca6..4428ede 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -3,33 +3,33 @@ *** -Current Release Version: 2.2.8 +Current Release Version: 2.3.1 (released 14th May 2020) -Upcoming Release Version: 2.3.0 +Upcoming Release Version: 2.3.2 -**bold** = Feature Completed +Normal = Feature Completed -\* = Under Current Development +*italics* = Upcoming Feature -*italics* = Next Major Feature(s) +**bold** = Next Major Feature(s) | Free | Version | Pro | Version | | --- | --- | --- | --- | -| **Scheduler with Conflict Checker** | 2.3.0 | with Visual Schedule Editor* | 2.3.5 | -| **Master Schedule Views** | 2.2.8 | with View Switching* | 2.3.1 | -| **Show Page Display Template** | 2.3.0 | with Social Icons | 2.3.4 | -| **Show Genre Assignments** | 2.2.8 | **with Genre Term Image Support** | 2.3.0 | -| **Host / Producer / Show Editor Roles** | 2.3.0 | **with Role Assignment Interface** | 2.3.0 | -| *Streaming Widget Player* * | 2.3.1 | *with Sticky Sitewide Player* * | 2.3.2 | -| **Current and Upcoming Show Widgets** | 2.2.8 | - | - | -| **Show Countdown Timer** | 2.3.0 | with Dynamic Reloading* | 2.3.1 | -| **Playlist Widget** | 2.2.8 | with Track Affiliate Links | 2.3.4 | -| **Show Posts and Playlists** | 2.2.8 | with Show Episodes Post Type* | 2.3.3 | -| **Post Type Archive Shortcodes** | 2.3.0 | with Grid View* | 2.3.4 | -| **Show Hosts and Producers** | 2.2.8 | with Profile Page Templates | 2.3.3 | -| **REST/Feed API Endpoints** | 2.3.0 | with Episodes, Hosts and Producers* | 2.3.4 | -| Radio Clock and Widget* | 2.3.1 | with Timezone Switcher* | 2.3.2 | -| **Schedule Caching** | 2.3.0 | **with Show Meta Caching** | 2.3.0 | +| Scheduler with Conflict Checker | 2.3.0 | *with Visual Schedule Editor* | 2.3.5 | +| Master Schedule Views | 2.2.8 | with View Switching | 2.3.2 | +| Show Page Display Template | 2.3.0 | *with Social Icons* | 2.3.4 | +| Show Genre Assignments | 2.2.8 | with Genre Term Image Support | 2.3.0 | +| Host / Producer / Show Editor Roles | 2.3.0 | with Role Assignment Interface | 2.3.0 | +| **Streaming Widget Player** | 2.3.3 | **with Sticky Sitewide Player** | 2.3.3 | +| Current and Upcoming Show Widgets | 2.2.8 | - | - | +| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.3 | +| Playlist Widget | 2.2.8 | *with Track Affiliate Links* | 2.3.4 | +| Show Posts and Playlists | 2.2.8 | *with Show Episodes Post Type* | 2.3.4 | +| Post Type Archive Shortcodes | 2.3.0 | *with Grid View* | 2.3.4 | +| Show Hosts and Producers | 2.2.8 | *with Profile Page Templates* | 2.3.4 | +| REST/Feed API Endpoints | 2.3.0 | *with Episodes, Hosts and Producers* | 2.3.4 | +| Radio Clock and Widget | 2.3.2 | *with Timezone Switcher* | 2.3.3 | +| Schedule Caching | 2.3.0 | with Show Meta Caching | 2.3.0 | [comment]: # (Show and Override Feeds) diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 013e0c8..a9e43a9 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -26,6 +26,7 @@ The following attributes are available for the shortcode: * *view* : Which View to use for display output. 'table', 'tabbed', 'list', 'divs', 'legacy'. Default 'table'. * *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. +* *clock* : Display Radio Clock above schedule. 0 or 1. Default is the Plugin Setting. * *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. * *show_times* : Whether to display the show shift's start and end times. 0 or 1. Default 1. * *show_image* : Whether the display the show's avatar. 0 or 1. Default 0 (1 for Tabbed View.) @@ -35,7 +36,11 @@ The following attributes are available for the shortcode: * *link_hosts* : Whether to link each show host to their profile page. 0 or 1. Default 0. * *show_encore* : Whether to display 'encore airing' for a show shift. 0 or 1. Default 1. * *show_file* : Whether to add a link to the latest audio file. 0 or 1. Default 0. -* *days* : Display schedule for single day or multiple days, comma separated. Default all. +* *days* : Display for single day or multiple days (string or 0-6, comma separated.) Default all. +* *start_day* : day of the week to start schedule (string or 0-6.) Default WordPress setting. +* *display_day* : Full or short day heading ('full' or 'short') Default short for Table, full for Tabs/List. +* *display_date* : Date format for date subheading. 0 for none. Default 'jS' for Table/List, 0 for Tabs. +* *display_month* : Full or short month subheading ('full', 'short') Default 'short'. * *divheight* : Set the height, in pixels, of the individual divs. For 'divs' view only. Default 45. Example: Display the schedule in 24-hour time format, use `[master-schedule time="24"]`. @@ -44,8 +49,24 @@ Example: Display the schedule in 24-hour time format, use `[master-schedule time `[radio-timezone]` -Displays the Radio Station Timezone selected via Plugin Settings. There are no attributes for this shortcode. +Displays the Radio Station Timezone selected via Plugin Settings. There are no attributes for this shortcode. This is the default display above the Master Schedule when the Radio Clock is turned off in the Plugin Settings. +#### Radio Clock Shortcode + +`[radio-clock]` + +Added in 2.3.2. Displays the current server time and user time. Also available as a Widget. + +The following attributes are available for this shortcode: + +* *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. +* *seconds* : Display seconds with current times. 0 or 1. Default 0. +* *day* : Display day after current times. 'full', 'short' or 'none'. Default 'full'. +* *date* : Display date after current times. 0 or 1. Default 1. +* *month* : Display months after current times. 'full', 'short' or 'none'. Default 'full'. +* *zone* : Display timezone after current times. 0 or 1. Default 1. + +The Radio Clock can also be displayed by default above the Master Schedule by enabling this in the Plugin Settings. (It's display attributes there can changed via the `radio_station_schedule_clock` filter.) ## Archive Shortcodes diff --git a/docs/Widgets.md b/docs/Widgets.md index f6a1039..c3dba82 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -83,8 +83,9 @@ Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" l [Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-playlist-widget/) ## Radio Clock Widget +`[radio-clock]` (see [Radio Clock Shortcode](./Shortcodes.md#radio-clock-widget) -A future version of Radio Station will include a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page.) +As of 2.3.2, Radio Station now includes a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page) alongside the site visitor's current time (via browser detection.) ### [Pro] Timezone Switcher A future version of Radio Station Pro will include a user Timezone Switcher in the which will allow your listener's to select a timezone and display adjusted Schedule and Show times. diff --git a/includes/class-current-playlist-widget.php b/includes/class-current-playlist-widget.php index f973846..1199ac9 100644 --- a/includes/class-current-playlist-widget.php +++ b/includes/class-current-playlist-widget.php @@ -1,6 +1,6 @@ '' ) ); + // 2.3.0: added hide widget if empty option // 2.3.0: added countdown display option - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + // 2.3.2: added AJAX load option $title = $instance['title']; $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; $song = isset( $instance['song'] ) ? $instance['song'] : true; @@ -32,9 +34,22 @@ public function form( $instance ) { $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : true; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; // 2.3.0: convert template style code to strings + // 2.3.2: added AJAX load option field $fields = ' +

    + +

    +

    diff --git a/freemius/templates/ajax-loader.php b/freemius/templates/ajax-loader.php index e69de29..97ff60b 100644 --- a/freemius/templates/ajax-loader.php +++ b/freemius/templates/ajax-loader.php @@ -0,0 +1,6 @@ + + diff --git a/freemius/templates/auto-installation.php b/freemius/templates/auto-installation.php index e69de29..6b8183c 100644 --- a/freemius/templates/auto-installation.php +++ b/freemius/templates/auto-installation.php @@ -0,0 +1,249 @@ +is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $title = $fs->get_plugin_title(); + + if ( $plugin_id != $fs->get_id() ) { + $addon = $fs->get_addon( $plugin_id ); + + if ( is_object( $addon ) ) { + $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); + } + } + + $plugin_title = sprintf( + '%s', + esc_html( $title ) + ); + + $sec_countdown = 30; + $countdown_html = sprintf( + esc_js( + /* translators: %s: Number of seconds */ + fs_text_inline( '%s sec', 'x-sec', $slug ) + ), + sprintf( '%s', $sec_countdown ) + ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + $params = array(); + $loader_html = fs_get_template( 'ajax-loader.php', $params ); + + // Pass unique auto installation URL if WP_Filesystem is needed. + $install_url = $fs->_get_sync_license_url( + $plugin_id, + true, + array( 'auto_install' => 'true' ) + ); + + + ob_start(); + + $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. + + $credentials = request_filesystem_credentials( + esc_url_raw( $install_url ), + $method, + false, + WP_PLUGIN_DIR, + array() + ); + + $credentials_form = ob_get_clean(); + + $require_credentials = ! empty( $credentials_form ); +?> +
    +
    +
    +

    +
    +
    + + +
    + +
    + +

    %s', + 'https://freemius.com', + 'freemius.com' + ), + $countdown_html + ) ?>

    + + +
    + +
    +
    ' + + diff --git a/freemius/templates/checkout.php b/freemius/templates/checkout.php index e69de29..a0969cc 100644 --- a/freemius/templates/checkout.php +++ b/freemius/templates/checkout.php @@ -0,0 +1,337 @@ +get_slug(); + + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + 'mode' => 'dashboard', + 'trial' => fs_request_get_bool( 'trial' ), + ); + + $plan_id = fs_request_get( 'plan_id' ); + if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { + $context_params['plan_id'] = $plan_id; + } + + $licenses = fs_request_get( 'licenses' ); + if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { + $context_params['licenses'] = $licenses; + } + + $plugin_id = fs_request_get( 'plugin_id' ); + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + $plugin_id = $fs->get_id(); + } + + if ( $plugin_id == $fs->get_id() ) { + $is_premium = $fs->is_premium(); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + } else { + // Identify the module code version of the checkout context module. + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $is_premium = $fs_addon->is_premium(); + } else { + // If add-on isn't activated assume the premium version isn't installed. + $is_premium = false; + } + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $site = $fs->get_site(); + + if ( $plugin_id != $fs->get_id() ) { + if ( $fs->is_addon_activated( $plugin_id ) ) { + $fs_addon = Freemius::get_instance_by_id( $plugin_id ); + $addon_site = $fs_addon->get_site(); + if ( is_object( $addon_site ) ) { + $site = $addon_site; + } + } + } + + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $site, + $timestamp, + 'checkout' + ) ); + } else { + $current_user = Freemius::_get_current_wp_user(); + + // Add site and user info to the request, this information + // is NOT being stored unless the user complete the purchase + // and agrees to the TOS. + $context_params = array_merge( $context_params, array( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_email' => $current_user->user_email, + 'home_url' => home_url(), + ) ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + if ( is_object( $fs_user ) && $fs_user->is_verified() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs_user, + $timestamp, + 'checkout' + ) ); + } + } + + if ( $fs->is_payments_sandbox() ) { + // Append plugin secure token for sandbox mode authentication. + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + + /** + * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. + */ + if ( empty( $context_params['s_ctx_ts'] ) ) { + $context_params['s_ctx_ts'] = $timestamp; + } + } + + $return_url = $fs->_get_sync_license_url( $plugin_id ); + + $can_user_install = ( + ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || + ( $fs->is_theme() && current_user_can( 'install_themes' ) ) + ); + + $query_params = array_merge( $context_params, $_GET, array( + // Current plugin version. + 'plugin_version' => $fs->get_plugin_version(), + 'sdk_version' => WP_FS__SDK_VERSION, + 'is_premium' => $is_premium ? 'true' : 'false', + 'can_install' => $can_user_install ? 'true' : 'false', + 'return_url' => $return_url, + ) ); + + $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); + if ( false !== $xdebug_session ) { + $query_params['XDEBUG_SESSION'] = $xdebug_session; + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), + ); + fs_require_once_template('secure-https-header.php', $view_params); +?> +
    +
    + +
    \ No newline at end of file diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index e69de29..9cc7cab 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -0,0 +1,1038 @@ +get_slug(); + + $is_pending_activation = $fs->is_pending_activation(); + $is_premium_only = $fs->is_only_premium(); + $has_paid_plans = $fs->has_paid_plan(); + $is_premium_code = $fs->is_premium(); + $is_freemium = $fs->is_freemium(); + + $fs->_enqueue_connect_essentials(); + + $current_user = Freemius::_get_current_wp_user(); + + $first_name = $current_user->user_firstname; + if ( empty( $first_name ) ) { + $first_name = $current_user->nickname; + } + + $site_url = get_site_url(); + $protocol_pos = strpos( $site_url, '://' ); + if ( false !== $protocol_pos ) { + $site_url = substr( $site_url, $protocol_pos + 3 ); + } + + $freemius_site_www = 'https://freemius.com'; + + $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url(); + $freemius_plugin_terms_url = $fs->get_eula_url(); + + $freemius_site_url = $fs->is_premium() ? + $freemius_site_www : + $freemius_usage_tracking_url; + + if ( $fs->is_premium() ) { + $freemius_site_url .= '?' . http_build_query( array( + 'id' => $fs->get_id(), + 'slug' => $slug, + ) ); + } + + $freemius_link = 'freemius.com'; + + $error = fs_request_get( 'error' ); + + $require_license_key = $is_premium_only || + ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) ); + + if ( $is_pending_activation ) { + $require_license_key = false; + } + + if ( $require_license_key ) { + $fs->_add_license_activation_dialog_box(); + } + + $is_optin_dialog = ( + $fs->is_theme() && + $fs->is_themes_page() && + $fs->show_opt_in_on_themes_page() + ); + + if ( $is_optin_dialog ) { + $show_close_button = false; + $previous_theme_activation_url = ''; + + if ( ! $is_premium_code ) { + $show_close_button = true; + } else if ( $is_premium_only ) { + $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); + $show_close_button = ( ! empty( $previous_theme_activation_url ) ); + } + } + + $is_network_level_activation = ( + fs_is_network_admin() && + $fs->is_network_active() && + ! $fs->is_network_delegated_connection() + ); + + $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); + + $activate_with_current_user = ( + is_object( $fs_user ) && + ! $is_pending_activation && + // If requires a license for activation, use the user associated with the license for the opt-in. + ! $require_license_key && + ! $is_network_level_activation + ); + + $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); + $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); + + $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); + + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); + + $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ? + FS_GDPR_Manager::instance()->is_required() : + false; + + if ( is_null( $is_gdpr_required ) ) { + $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); + } +?> + +
    + + + + do_action( 'connect/before' ); + ?> +
    +
    + + + $fs->get_id() ); + fs_require_once_template( 'plugin-icon.php', $vars ); + ?> + + +
    +
    + +

    + +

    apply_filters( 'pending_activation_message', sprintf( + /* translators: %s: name (e.g. Thanks John!) */ + fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . + fs_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message', $slug ), + $first_name, + '' . $fs->get_plugin_name() . '', + '' . $current_user->user_email . '', + fs_text_inline( 'complete the install', 'complete-the-install', $slug ) + ) ); + } else if ( $require_license_key ) { + $button_label = $is_network_upgrade_mode ? + fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) : + fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + + $message = $fs->apply_filters( + 'connect-message_on-premium', + sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), + $first_name, + $fs->get_plugin_name() + ); + } else { + $filter = 'connect_message'; + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) : + fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug); + + if ( $fs->is_plugin_update() ) { + // If Freemius was added on a plugin update, set different + // opt-in message. + $default_optin_message = $is_gdpr_required ? + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ) : + fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ); + + // If user customized the opt-in message on update, use + // that message. Otherwise, fallback to regular opt-in + // custom message if exist. + if ( $fs->has_filter( 'connect_message_on_update' ) ) { + $filter = 'connect_message_on_update'; + } + } + + $message = $fs->apply_filters( + $filter, + ($is_network_upgrade_mode ? + '' : + /* translators: %s: name (e.g. Hey John,) */ + $hey_x_text . '
    ' + ) . + sprintf( + esc_html( $default_optin_message ), + '' . esc_html( $fs->get_plugin_name() ) . '', + '' . $current_user->user_login . '', + '' . $site_url . '', + $freemius_link + ), + $first_name, + $fs->get_plugin_name(), + $current_user->user_login, + '' . $site_url . '', + $freemius_link, + $is_gdpr_required + ); + } + + if ( $is_network_upgrade_mode ) { + $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); + + if ($is_premium_code){ + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); + + $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), + $fs->get_module_label( true ) + ) ); + + /* translators: %s: module type (plugin, theme, or add-on) */ + $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); + }else { + $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); + } + } + + echo $message; + ?>

    + +
    + + + +
    + + do_action( 'connect/after_license_input' ); + ?> + + - %s', + $fs->get_text_inline( 'Yes', 'yes' ), + $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) + ); + + $do_not_send_updates_text = sprintf( + '%s - %s', + $fs->get_text_inline( 'No', 'no' ), + sprintf( + $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), + '', + '' + ) + ); + ?> +
    + +
    + + +
    +
    + + + $fs->get_id(), + 'sites' => $sites, + 'require_license_key' => $require_license_key + ); + + echo fs_get_template( 'partials/network-activation.php', $vars ); + ?> + +
    +
    + is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> + + + apply_filters( 'show_delegation_option', true ) ) : ?> + + + +
    + + get_public_key() ) ?> + + +
    + +
    + + $value ) : ?> + + + + +
    + + + + +
    'dashicons dashicons-admin-users', + 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ), + 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ), + 'priority' => 5, + ); + } + + $permissions['site'] = array( + 'icon-class' => 'dashicons dashicons-admin-settings', + 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ), + 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), + 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ), + 'priority' => 10, + ); + + if ( ! $require_license_key ) { + $permissions['notices'] = array( + 'icon-class' => 'dashicons dashicons-testimonial', + 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 13, + ); + } + + $permissions['events'] = array( + 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), + 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ), + 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), + 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ), + 'priority' => 20, + ); + + // Add newsletter permissions if enabled. + if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { + $permissions['newsletter'] = array( + 'icon-class' => 'dashicons dashicons-email-alt', + 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 15, + ); + } + + $permissions['extensions'] = array( + 'icon-class' => 'dashicons dashicons-menu', + 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ), + 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ), + 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ), + 'priority' => 25, + 'optional' => true, + 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key ) + ); + + // Allow filtering of the permissions list. + $permissions = $fs->apply_filters( 'permission_list', $permissions ); + + // Sort by priority. + uasort( $permissions, 'fs_sort_by_priority' ); + + if ( ! empty( $permissions ) ) : ?> +
    + +

    get_module_label( true ), + sprintf('%s', fs_esc_html_inline('diagnostic data', 'send-data')), + 'freemius.com ' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '' + ) ?>

    + + + +
      $permission ) : ?> +
    • + + +
      +
      +
      + + +
      + class="fs-tooltip-trigger"> + +

      +
      +
    • + +
    +
    + + +
    +

    + + + + + + + +

    +
    + +
    + +   -   + +
    +
    + do_action( 'connect/after' ); + + if ( $is_optin_dialog ) { ?> +
    + + \ No newline at end of file diff --git a/freemius/templates/contact.php b/freemius/templates/contact.php index e69de29..bba1018 100644 --- a/freemius/templates/contact.php +++ b/freemius/templates/contact.php @@ -0,0 +1,128 @@ +get_slug(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + time(), + 'contact' + ) ); + } + + $query_params = array_merge( $_GET, array_merge( $context_params, array( + 'plugin_version' => $fs->get_plugin_version(), + 'wp_login_url' => wp_login_url(), + 'site_url' => get_site_url(), +// 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", + ) ) ); + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    +
    + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'contact', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/debug.php b/freemius/templates/debug.php index e69de29..4dbf3a3 100644 --- a/freemius/templates/debug.php +++ b/freemius/templates/debug.php @@ -0,0 +1,765 @@ + +

    newest->version ?>

    +
    + + + + +
    +
    +
    + +
    +

    + + + + + + + + get_option( 'ms_migration_complete', false, true ) ) : ?> + + + + + + +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + +
    + + +
    +
    + +
    + + + +
    +
    + + + +
    + + 'WP_FS__REMOTE_ADDR', + 'val' => WP_FS__REMOTE_ADDR, + ), + array( + 'key' => 'WP_FS__ADDRESS_PRODUCTION', + 'val' => WP_FS__ADDRESS_PRODUCTION, + ), + array( + 'key' => 'FS_API__ADDRESS', + 'val' => FS_API__ADDRESS, + ), + array( + 'key' => 'FS_API__SANDBOX_ADDRESS', + 'val' => FS_API__SANDBOX_ADDRESS, + ), + array( + 'key' => 'WP_FS__DIR', + 'val' => WP_FS__DIR, + ), + ) +?> +
    + + + + + + + + + + > + + + + + + +
    +

    + + + + + + + + + + + plugins as $sdk_path => $data ) : ?> + version ) ?> + > + + + + + + + +
    version ?>plugin_path ?>
    + + + + + get_option( $module_type . 's' ), FS_Plugin::get_class_name() ) ?> + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + $data ) : ?> + file ); + } else { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + + if ( ! $is_active && is_child_theme() ) { + $parent_theme = $current_theme->parent(); + + $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); + } + } + ?> + id ) : null ?> + has_api_connectivity() && $fs->is_on() ) { + echo ' style="background: #E6FFE6; font-weight: bold"'; + } else { + echo ' style="background: #ffd0d0; font-weight: bold"'; + } + } ?>> + + + + + has_api_connectivity() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>has_api_connectivity() ? + fs_text_x_inline( 'Connected', 'as connection was successful' ) : + fs_text_x_inline( 'Blocked', 'as connection blocked' ) + ); + } ?> + is_on() ) { + echo ' style="color: red; text-transform: uppercase;"'; + } ?>>is_on() ? + $on_text : + $off_text + ); + } ?> + + + + get_network_install_blog_id(); + $network_user = $fs->get_network_user(); + } + ?> + + + + + + + +
    id ?>version ?>title ?>file ?>public_key ?>email; + } ?> + + has_trial_plan() ) : ?> +
    + + + + + +
    + + is_registered() ) : ?> + + + is_network_upgrade_mode() ) : ?> +
    + + + + + +
    + + +
    + + + + + 0 ) : ?> +

    /

    + + + + + + + + + + + + + + + + + + + $sites ) : ?> + + + + + + + + + + + + + + + + + + + +
    id ?>blog_id ?>url ) ?>user_id ?>license_id) ? $site->license_id : '' ?>plan_id ) ) { + if ( false === $all_plans ) { + $option_name = 'plans'; + if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + $all_plans = fs_get_entities( $fs_options->get_option( $option_name, array() ), FS_Plugin_Plan::get_class_name() ); + } + + foreach ( $all_plans[ $slug ] as $plan ) { + $plan_id = Freemius::_decrypt( $plan->id ); + + if ( $site->plan_id == $plan_id ) { + $plan_name = Freemius::_decrypt( $plan->name ); + break; + } + } + } + + echo $plan_name; + ?>public_key ?>is_whitelabeled ? + FS_Plugin_License::mask_secret_key_for_html( $site->secret_key ) : + esc_html( $site->secret_key ); + ?> +
    + + + + + + + + + +
    +
    + + + + $plugin_addons ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>title ?>slug ?>version ?>public_key ?>secret_key ) ?>
    + +is_whitelabeled ) { + $users_with_developer_license_by_id[ $license->user_id ] = true; + } + } + } + +?> + +

    + + + + + + + + + + + + + + $user ) : ?> + + + + + + + + + + + + +
    id ?>get_name() ?> + + email ?> + + is_verified ) ?>public_key ?>secret_key) : esc_html( $user->secret_key ) ?> + +
    + + + + +
    + +
    + + + + 0 ) : ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id ?>plugin_id ?>user_id ?>plan_id ?>is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?>activated ?>is_block_features ? 'Blocking' : 'Flexible' ?>is_whitelabeled ? 'Whitelabeled' : 'Normal' ?>is_whitelabeled ? + $license->get_html_escaped_masked_secret_key() : + esc_html( $license->secret_key ); + ?>expiration ?>
    + + + + +

    + +
    + + + + + + + +
    + + +
    + + +
    + +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    {$log.log_order}.{$log.type}{$log.logger}{$log.function} + + {$log.message_short} + +
    {$log.message}
    +
    {$log.file}:{$log.line}{$log.created}
    +
    + + diff --git a/freemius/templates/debug/api-calls.php b/freemius/templates/debug/api-calls.php index e69de29..ea4e823 100644 --- a/freemius/templates/debug/api-calls.php +++ b/freemius/templates/debug/api-calls.php @@ -0,0 +1,155 @@ + 0, + 'POST' => 0, + 'PUT' => 0, + 'DELETE' => 0 + ); + + $show_body = false; + foreach ( $logger as $log ) { + $counters[ $log['method'] ] ++; + + if ( ! is_null( $log['body'] ) ) { + $show_body = true; + } + } + + $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); + + /** + * This template is used for debugging, therefore, when possible + * we'd like to prettify the output of a JSON encoded variable. + * This will only be executed when $pretty_print is `true`, and + * the var is `true` only for PHP 5.3 and higher. Due to the + * limitations of the current Theme Check, it throws an error + * that using the "options" parameter (the 2nd param) is not + * supported in PHP 5.2 and lower. Thus, we added this alias + * variable to work around that false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $encode = 'json_encode'; + + $root_path_len = strlen( ABSPATH ); + + $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); +?> +

    + +

    Total Time:

    + +

    Total Requests:

    + $count ) : ?> +

    :

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #
    . + %s', + $log['path'] + ); + ?> + + + + + + + + + + + + + + %s', + substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) + ); + if ( $pretty_print ) { + $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); + } + ?> +
    + +
    + %s', + substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) + ); + } + + if ( $is_not_empty_result && $pretty_print ) { + $decoded = json_decode( $result ); + if ( ! is_null( $decoded ) ) { + $result = $encode( $decoded, JSON_PRETTY_PRINT ); + } + } else { + $result = is_string( $result ) ? $result : json_encode( $result ); + } + ?> + style="display: none"> +
    \ No newline at end of file diff --git a/freemius/templates/debug/index.php b/freemius/templates/debug/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/debug/index.php +++ b/freemius/templates/debug/index.php @@ -0,0 +1,3 @@ + +

    + + + + + + + + + + + + + + + + + + > + + + + + + + + + + +
    #
    .get_id() ?> + %s', + esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) + ); + ?> +
    + +
    +
    get_file() ) . ':' . $log['line']; + } + ?>
    \ No newline at end of file diff --git a/freemius/templates/debug/plugins-themes-sync.php b/freemius/templates/debug/plugins-themes-sync.php index e69de29..8508cd1 100644 --- a/freemius/templates/debug/plugins-themes-sync.php +++ b/freemius/templates/debug/plugins-themes-sync.php @@ -0,0 +1,76 @@ +get_option( 'all_plugins' ); + $all_themes = $fs_options->get_option( 'all_themes' ); + + /* translators: %s: time period (e.g. In "2 hours") */ + $in_x_text = fs_text_inline( 'In %s', 'in-x' ); + /* translators: %s: time period (e.g. "2 hours" ago) */ + $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + + + + + + + + + + + +
    plugins ) ?>timestamp ) && is_numeric( $all_plugins->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    themes ) ?>timestamp ) && is_numeric( $all_themes->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . $sec_text : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); + + echo esc_html( sprintf( + ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? + $in_x_text : + $x_ago_text ), + $human_diff + ) ); + } + ?>
    diff --git a/freemius/templates/debug/scheduled-crons.php b/freemius/templates/debug/scheduled-crons.php index e69de29..47a715e 100644 --- a/freemius/templates/debug/scheduled-crons.php +++ b/freemius/templates/debug/scheduled-crons.php @@ -0,0 +1,136 @@ +get_option( $module_type . 's' ), FS_Plugin::get_class_name() ); + if ( is_array( $modules ) && count( $modules ) > 0 ) { + foreach ( $modules as $slug => $data ) { + if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { + $current_theme = wp_get_theme(); + $is_active = ( $current_theme->stylesheet === $data->file ); + } else { + $is_active = is_plugin_active( $data->file ); + } + + /** + * @author Vova Feldman + * + * @since 1.2.1 Don't load data from inactive modules. + */ + if ( $is_active ) { + $fs = freemius( $data->id ); + + $next_execution = $fs->next_sync_cron(); + $last_execution = $fs->last_sync_cron(); + + if ( false !== $next_execution ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'sync_cron', + 'last' => $last_execution, + 'next' => $next_execution, + ); + } + + $next_install_execution = $fs->next_install_sync(); + $last_install_execution = $fs->last_install_sync(); + + if (false !== $next_install_execution || + false !== $last_install_execution + ) { + $scheduled_crons[ $slug ][] = array( + 'name' => $fs->get_plugin_name(), + 'slug' => $slug, + 'module_type' => $fs->get_module_type(), + 'type' => 'install_sync', + 'last' => $last_install_execution, + 'next' => $next_install_execution, + ); + } + } + } + } + } + + $sec_text = fs_text_x_inline( 'sec', 'seconds' ); +?> +

    + + + + + + + + + + + + + + $crons ) : ?> + + + + + + + + + + + + +
    diff --git a/freemius/templates/email.php b/freemius/templates/email.php index e69de29..598c783 100644 --- a/freemius/templates/email.php +++ b/freemius/templates/email.php @@ -0,0 +1,49 @@ + + + $section ) { + ?> + + + + + $row ) { + $col_count = count( $row ); + ?> + + + + + + + + + + + +
    :
    \ No newline at end of file diff --git a/freemius/templates/firewall-issues-js.php b/freemius/templates/firewall-issues-js.php index e69de29..6a3f2a5 100644 --- a/freemius/templates/firewall-issues-js.php +++ b/freemius/templates/firewall-issues-js.php @@ -0,0 +1,63 @@ + + diff --git a/freemius/templates/forms/affiliation.php b/freemius/templates/forms/affiliation.php index e69de29..fe6d694 100644 --- a/freemius/templates/forms/affiliation.php +++ b/freemius/templates/forms/affiliation.php @@ -0,0 +1,509 @@ +get_slug(); + + $user = $fs->get_user(); + $affiliate = $fs->get_affiliate(); + $affiliate_terms = $fs->get_affiliate_terms(); + + $plugin_title = $fs->get_plugin_title(); + $module_type = $fs->is_plugin() ? + WP_FS__MODULE_TYPE_PLUGIN : + WP_FS__MODULE_TYPE_THEME; + + $commission = $affiliate_terms->get_formatted_commission(); + + $readonly = false; + $is_affiliate = is_object( $affiliate ); + $is_pending_affiliate = false; + $email_address = ( is_object( $user ) ? + $user->email : + '' ); + $full_name = ( is_object( $user ) ? + $user->get_name() : + '' ); + $paypal_email_address = ''; + $domain = ''; + $extra_domains = array(); + $promotion_method_social_media = false; + $promotion_method_mobile_apps = false; + $statistics_information = false; + $promotion_method_description = false; + $members_dashboard_login_url = 'https://members.freemius.com/login/'; + + $affiliate_application_data = $fs->get_affiliate_application_data(); + + if ( $is_affiliate && $affiliate->is_pending() ) { + $readonly = 'readonly'; + $is_pending_affiliate = true; + + $paypal_email_address = $affiliate->paypal_email; + $domain = $affiliate->domain; + $statistics_information = $affiliate_application_data['stats_description']; + $promotion_method_description = $affiliate_application_data['promotion_method_description']; + + if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { + $extra_domains = $affiliate_application_data['additional_domains']; + } + + if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { + $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); + $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); + $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); + } + } else { + $current_user = Freemius::_get_current_wp_user(); + $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); + $email_address = $current_user->user_email; + $domain = fs_strip_url_protocol( get_site_url() ); + } + + $affiliate_tracking = 30; + + if ( is_object( $affiliate_terms ) ) { + $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? + ( $affiliate_terms->cookie_days . '-day' ) : + fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); + } + + $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); + + $module_id = $fs->get_id(); + $affiliate_program_terms_url = "https://freemius.com/plugin/{$module_id}/{$slug}/legal/affiliate-program/"; +?> +
    +
    +
    +
    +
    +
    + + + + is_active() ) : ?> +
    +

    %s', + $members_dashboard_login_url, + $members_dashboard_login_url + ) + ); + ?>

    +
    + + is_suspended() ) { + $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); + $message_container_class = 'notice notice-warning'; + } else if ( $affiliate->is_rejected() ) { + $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); + $message_container_class = 'error'; + } else if ( $affiliate->is_blocked() ) { + $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); + $message_container_class = 'error'; + } + ?> +
    +

    +
    + + +
    +
    + +
    +

    +

    +
    + +

    +
      +
    • + has_renewals_commission() ) : ?> +
    • + + is_session_cookie() ) ) : ?> +
    • + + has_lifetime_commission() ) : ?> +
    • + +
    • +
    • +
    • +
    +
    > +

    + +
    + + > +
    +
    + + > +
    +
    + + > +
    +
    + + > +

    + + + ... + +
    +
    > + +

    + + +
    + > +
    + + +
    +
    + +
    + /> + +
    +
    + /> + +
    +
    +
    + + + +

    + +
    +
    + + + +

    + +
    + +
    + + +
    + + +
    + + + + + +
    +
    +
    +
    + + +
    + 'affiliation', + 'module_id' => $module_id, + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); +?> \ No newline at end of file diff --git a/freemius/templates/forms/data-debug-mode.php b/freemius/templates/forms/data-debug-mode.php index e69de29..44cb442 100644 --- a/freemius/templates/forms/data-debug-mode.php +++ b/freemius/templates/forms/data-debug-mode.php @@ -0,0 +1,213 @@ +get_slug(); + $unique_affix = $fs->get_unique_affix(); + $last_license_user_id = $fs->get_last_license_user_id(); + $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id ); + + $message_above_input_field = ( ! $has_last_license_user_id ) ? + fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) : + sprintf( + fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ), + $last_license_user_id + ); + + $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' ); + $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug ); + $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug ); + $license_or_user_key_text = ( ! $has_last_license_user_id ) ? + fs_text_inline( 'License key', 'license-key' , $slug ) : + fs_text_inline( 'User key', 'user-key' , $slug ); + $input_html = ""; + + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    + {$input_html} +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/deactivation/contact.php b/freemius/templates/forms/deactivation/contact.php index e69de29..24d67e7 100644 --- a/freemius/templates/forms/deactivation/contact.php +++ b/freemius/templates/forms/deactivation/contact.php @@ -0,0 +1,23 @@ +get_slug(); + + echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) + . sprintf(" %s", + $fs->contact_url( 'technical_support' ), + fs_text_inline( 'Contact Support', 'contact-support', $slug ) + ); diff --git a/freemius/templates/forms/deactivation/form.php b/freemius/templates/forms/deactivation/form.php index e69de29..0bdcae0 100644 --- a/freemius/templates/forms/deactivation/form.php +++ b/freemius/templates/forms/deactivation/form.php @@ -0,0 +1,543 @@ +get_slug(); + + $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; + $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; + $confirmation_message = $VARS['uninstall_confirmation_message']; + + $is_anonymous = ( ! $fs->is_registered() ); + $anonymous_feedback_checkbox_html = ''; + + $reasons_list_items_html = ''; + + if ( $show_deactivation_feedback_form ) { + $reasons = $VARS['reasons']; + + foreach ( $reasons as $reason ) { + $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); + + if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { + $list_item_classes .= ' has-internal-message'; + $reason_internal_message = $reason['internal_message']; + } else { + $reason_internal_message = ''; + } + + $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); + $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); + + $reason_list_item_html = <<< HTML +
  • + +
    {$reason_internal_message}
    +
  • +HTML; + + $reasons_list_items_html .= $reason_list_item_html; + } + + if ( $is_anonymous ) { + $anonymous_feedback_checkbox_html = sprintf( + '', + fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) + ); + } + } + + // Aliases. + $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); + $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); + $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } +?> + diff --git a/freemius/templates/forms/deactivation/index.php b/freemius/templates/forms/deactivation/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/forms/deactivation/index.php +++ b/freemius/templates/forms/deactivation/index.php @@ -0,0 +1,3 @@ +get_slug(); + + $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); + $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); + $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); + + echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) + . " {$use_plugin_anonymously_text}"; \ No newline at end of file diff --git a/freemius/templates/forms/index.php b/freemius/templates/forms/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/forms/index.php +++ b/freemius/templates/forms/index.php @@ -0,0 +1,3 @@ +get_slug(); + $unique_affix = $fs->get_unique_affix(); + + $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); + $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); + $message_below_input_field = ''; + + $header_title = $fs->is_free_plan() ? + fs_text_inline( 'Activate License', 'activate-license', $slug ) : + fs_text_inline( 'Update License', 'update-license', $slug ); + + if ( $fs->is_registered() ) { + $activate_button_text = $header_title; + } else { + $freemius_site_url = $fs->has_paid_plan() ? + 'https://freemius.com/' : + // Insights platform information. + $fs->get_usage_tracking_terms_url(); + + $freemius_link = 'freemius.com'; + + $message_below_input_field = sprintf( + fs_text_inline( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), + $fs->get_module_label( true ), + $freemius_link + ); + + $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + } + + $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); + + $is_network_activation = ( + $fs->is_network_active() && + fs_is_network_admin() && + ! $fs->is_delegated_connection() + ); + $network_activation_html = ''; + + $sites_details = array(); + if ( $is_network_activation ) { + $all_sites = Freemius::get_sites(); + + foreach ( $all_sites as $site ) { + $site_details = $fs->get_site_info( $site ); + + $blog_id = Freemius::get_site_blog_id( $site ); + $install = $fs->get_install_by_blog_id($blog_id); + + if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $site_details['license_id'] = $install->license_id; + } + + $sites_details[] = $site_details; + } + + if ( $is_network_activation ) { + $vars = array( + 'id' => $fs->get_id(), + 'sites' => $sites_details, + 'require_license_key' => true + ); + + $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); + } + } + + $premium_licenses = $fs->get_available_premium_licenses(); + $available_licenses = array(); + foreach ( $premium_licenses as $premium_license ) { + $activations_left = $premium_license->left(); + if ( ! ( $activations_left > 0 ) ) { + continue; + } + + $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; + } + + $total_available_licenses = count( $available_licenses ); + if ( $total_available_licenses > 0 ) { + $license_input_html = <<< HTML +
    + + + + + + + + + + + +
    +HTML; + + if ( $total_available_licenses > 1 ) { + // Sort the licenses by number of activations left in descending order. + krsort( $available_licenses ); + + $license_input_html .= ''; + } else { + $available_licenses = array_values( $available_licenses ); + + /** + * @var FS_Plugin_License $available_license + */ + $available_license = $available_licenses[0]; + $value = sprintf( + "%s-Site %s License - %s", + ( 1 == $available_license->quota ? + 'Single' : + ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota ) + ), + $fs->_get_plan_by_id( $available_license->plan_id )->title, + $available_license->get_html_escaped_masked_secret_key() + ); + + $license_input_html .= <<< HTML + +HTML; + } + + $license_input_html .= <<< HTML +
    + +
    + +
    +
    +
    +HTML; + } else { + $license_input_html = ""; + } + + $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug ); + $ownership_change_option_html = ""; + + /** + * IMPORTANT: + * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE + * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) + * FOR MIGRATED MODULES. + */ + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    + {$license_input_html} + {$cant_find_license_key_text} + {$network_activation_html} +

    {$message_below_input_field}

    + {$ownership_change_option_html} +HTML; + + /** + * Handle the ownership change option if not an add-on or if no license yet is activated for the + * parent product in case of an add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/optout.php b/freemius/templates/forms/optout.php index e69de29..4867a8a 100644 --- a/freemius/templates/forms/optout.php +++ b/freemius/templates/forms/optout.php @@ -0,0 +1,336 @@ +get_slug(); + + $action = $fs->is_tracking_allowed() ? + 'stop_tracking' : + 'allow_tracking'; + + $reconnect_url = $fs->get_activation_url( array( + 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), + 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), + ) ); + + $plugin_title = "{$fs->get_plugin()->title}"; + $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); + $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug ); + + if ( $fs->is_premium() ) { + $opt_in_message_appreciation = fs_text_inline( 'Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard.', 'premium-opt-in-message-appreciation', $slug ); + + $opt_out_message_subtitle = sprintf( fs_text_inline( 'Warning: Opting out will block automatic updates', 'premium-opt-out-message-appreciation', $slug ), $fs->get_module_type() ); + $opt_out_message_usage_tracking = sprintf( fs_text_inline( 'Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won\'t receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you\'ll need to check for %1$s updates and install them manually.', 'premium-opt-out-message-usage-tracking', $slug ), $fs->get_module_type(), $plugin_title ); + + $primary_cta_label = fs_text_inline( 'I\'d like to keep automatic updates', 'premium-opt-out-cancel', $slug ); + } else { + $opt_in_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-in-message-appreciation', $slug ), $fs->get_module_type() ); + + $opt_out_message_subtitle = $opt_in_message_appreciation; + $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title ); + $primary_cta_label = fs_text_inline( 'On second thought - I want to continue helping', 'opt-out-cancel', $slug ); + } + + $opt_out_message_clicking_opt_out = sprintf( + fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ), + $plugin_title, + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $admin_notice_params = array( + 'id' => '', + 'slug' => $fs->get_id(), + 'type' => 'success', + 'sticky' => false, + 'plugin' => $fs->get_plugin()->title, + 'message' => $opt_in_message_appreciation + ); + + $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params ); + + $modal_content_html = " + is_premium() ? ' style="color: red"' : '' ) . ">{$opt_out_message_subtitle} +

    +

    {$opt_out_message_usage_tracking}

    +

    {$opt_out_message_clicking_opt_out}

    + "; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); +?> + diff --git a/freemius/templates/forms/premium-versions-upgrade-handler.php b/freemius/templates/forms/premium-versions-upgrade-handler.php index e69de29..f30639b 100644 --- a/freemius/templates/forms/premium-versions-upgrade-handler.php +++ b/freemius/templates/forms/premium-versions-upgrade-handler.php @@ -0,0 +1,205 @@ +get_slug(); + + $plugin_data = $fs->get_plugin_data(); + $plugin_name = $plugin_data['Name']; + $plugin_basename = $fs->get_plugin_basename(); + + $license = $fs->_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $message = sprintf( + fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . + fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), + '', + sprintf( + '%s', + is_object( $license ) ? + fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : + fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) + ), + '' + ); + + $modal_content_html = "

    {$message}

    "; + + $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); + + $renew_license_button_text = is_object( $license ) ? + fs_text_inline( 'Renew license', 'renew-license', $slug ) : + fs_text_inline( 'Buy license', 'buy-license', $slug ); + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/premium-versions-upgrade-metadata.php b/freemius/templates/forms/premium-versions-upgrade-metadata.php index e69de29..5f9fddd 100644 --- a/freemius/templates/forms/premium-versions-upgrade-metadata.php +++ b/freemius/templates/forms/premium-versions-upgrade-metadata.php @@ -0,0 +1,47 @@ +_get_license(); + + if ( ! is_object( $license ) ) { + $purchase_url = $fs->pricing_url(); + } else { + $subscription = $fs->_get_subscription( $license->id ); + + $purchase_url = $fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ); + } + + $plugin_data = $fs->get_plugin_data(); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/resend-key.php b/freemius/templates/forms/resend-key.php index e69de29..f8cafb9 100644 --- a/freemius/templates/forms/resend-key.php +++ b/freemius/templates/forms/resend-key.php @@ -0,0 +1,247 @@ +get_slug(); + + $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); + $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); + $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); + $other_text = fs_text_inline( 'Other', 'other', $slug ); + + $is_freemium = $fs->is_freemium(); + + $send_button_text_html = esc_html($send_button_text); + + $button_html = <<< HTML + +HTML; + + if ( $is_freemium ) { + $current_user = Freemius::_get_current_wp_user(); + $email = $current_user->user_email; + $esc_email = esc_attr( $email ); + $form_html = <<< HTML + +{$button_html} +HTML; + } else { + $email = ''; + $form_html = <<< HTML +{$button_html} + +HTML; + } + + $message_above_input_field = fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); + $modal_content_html = <<< HTML +

    +

    {$message_above_input_field}

    +
    + {$form_html} +
    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/forms/subscription-cancellation.php b/freemius/templates/forms/subscription-cancellation.php index e69de29..2a1d591 100644 --- a/freemius/templates/forms/subscription-cancellation.php +++ b/freemius/templates/forms/subscription-cancellation.php @@ -0,0 +1,277 @@ +get_slug(); + +/** + * @var FS_Plugin_License $license + */ +$license = $VARS['license']; + +$has_trial = $VARS['has_trial']; + +$subscription_cancellation_context = $has_trial ? + fs_text_inline( 'trial', 'trial', $slug ) : + fs_text_inline( 'subscription', 'subscription', $slug ); + +$plan = $fs->get_plan(); +$module_label = $fs->get_module_label( true ); + +if ( $VARS['is_license_deactivation'] ) { + $subscription_cancellation_text = ''; +} else { + $subscription_cancellation_text = sprintf( + fs_text_inline( + "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", + 'deactivation-or-uninstall-message', + $slug + ), + $module_label + ) . ' '; +} + + $subscription_cancellation_text .= sprintf( + fs_text_inline( + 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', + 'cancel-subscription-message', + $slug + ), + ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), + $subscription_cancellation_context +); + +$cancel_subscription_action_label = sprintf( + fs_esc_html_inline( + "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", + 'cancel-x', + $slug + ), + esc_html( $subscription_cancellation_context ), + sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), + esc_html( $module_label ) +); + +$keep_subscription_active_action_label = esc_html( sprintf( + fs_text_inline( + "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", + 'dont-cancel-x', + $slug + ), + $subscription_cancellation_context +) ); + +$subscription_cancellation_text = esc_html( $subscription_cancellation_text ); + +$subscription_cancellation_html = <<< HTML +

    {$subscription_cancellation_text}

    +
      +
    • + +
    • +
    • + +
    • +
    +HTML; + +$downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); +$cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); +/* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ +$downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); +$prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); +$after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); +$after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); +$after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); + +$subscription_cancellation_confirmation_message = $has_trial ? + fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : + sprintf( + '%s %s %s %s', + sprintf( + $downgrade_x_confirm_text, + ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + human_time_diff( time(), strtotime( $license->expiration ) ) + ), + ( + $license->is_block_features ? + ( + $fs->is_only_premium() ? + sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : + sprintf( $after_downgrade_blocking_text, $plan->title ) + ) : + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) + ), + $prices_increase_text, + fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) + ); + +fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/trial-start.php b/freemius/templates/forms/trial-start.php index e69de29..b66e727 100644 --- a/freemius/templates/forms/trial-start.php +++ b/freemius/templates/forms/trial-start.php @@ -0,0 +1,181 @@ +get_slug(); + + $message_header = sprintf( + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), + '', + '' + ); + $message_content = sprintf( + /* translators: %s: Link to freemius.com */ + fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), + $fs->get_module_type(), + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ); + + $modal_content_html = <<< HTML +

    +

    {$message_header}

    +

    {$message_content}

    +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/forms/user-change.php b/freemius/templates/forms/user-change.php index e69de29..3571b83 100644 --- a/freemius/templates/forms/user-change.php +++ b/freemius/templates/forms/user-change.php @@ -0,0 +1,296 @@ +get_slug(); + + /** + * @var object[] $license_owners + */ + $license_owners = $VARS['license_owners']; + + $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug ); + $header_title = fs_text_inline( 'Change User', 'change-user', $slug ); + $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug ); + $other_text = fs_text_inline( 'Other', 'other', $slug ); + $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug ); + + $user_change_options_html = <<< HTML +
    + + +HTML; + + foreach ( $license_owners as $license_owner ) { + $user_change_options_html .= <<< HTML + + + + +HTML; + } + + $user_change_options_html .= <<< HTML + + + + + +
    +
    + +
    + +
    +
    +
    +
    +HTML; + + $modal_content_html = <<< HTML +

    +

    {$change_user_message}

    + {$user_change_options_html} +HTML; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + diff --git a/freemius/templates/gdpr-optin-js.php b/freemius/templates/gdpr-optin-js.php index e69de29..4fdc5e3 100644 --- a/freemius/templates/gdpr-optin-js.php +++ b/freemius/templates/gdpr-optin-js.php @@ -0,0 +1,66 @@ + + \ No newline at end of file diff --git a/freemius/templates/index.php b/freemius/templates/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/index.php +++ b/freemius/templates/index.php @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/open-license-activation.php b/freemius/templates/js/open-license-activation.php index e69de29..a88e6f9 100644 --- a/freemius/templates/js/open-license-activation.php +++ b/freemius/templates/js/open-license-activation.php @@ -0,0 +1,37 @@ + + \ No newline at end of file diff --git a/freemius/templates/js/style-premium-theme.php b/freemius/templates/js/style-premium-theme.php index e69de29..942da64 100644 --- a/freemius/templates/js/style-premium-theme.php +++ b/freemius/templates/js/style-premium-theme.php @@ -0,0 +1,53 @@ +get_slug(); + +?> + \ No newline at end of file diff --git a/freemius/templates/partials/index.php b/freemius/templates/partials/index.php index e69de29..cd6990e 100644 --- a/freemius/templates/partials/index.php +++ b/freemius/templates/partials/index.php @@ -0,0 +1,2 @@ +get_slug(); + + $sites = $VARS['sites']; + $require_license_key = $VARS['require_license_key']; + + $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); + $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); +?> +|' ?> + diff --git a/freemius/templates/plugin-icon.php b/freemius/templates/plugin-icon.php index e69de29..ab0fb54 100644 --- a/freemius/templates/plugin-icon.php +++ b/freemius/templates/plugin-icon.php @@ -0,0 +1,20 @@ + +
    + +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/description.php b/freemius/templates/plugin-info/description.php index e69de29..26bc67b 100644 --- a/freemius/templates/plugin-info/description.php +++ b/freemius/templates/plugin-info/description.php @@ -0,0 +1,78 @@ +info->selling_point_0 ) || + ! empty( $plugin->info->selling_point_1 ) || + ! empty( $plugin->info->selling_point_2 ) + ) : ?> +
    +
      + + info->{'selling_point_' . $i} ) ) : ?> +
    • + +

      info->{'selling_point_' . $i} ) ?>

    • + + +
    +
    + +
    + info->description, array( + 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), + 'b' => array(), + 'i' => array(), + 'p' => array(), + 'blockquote' => array(), + 'h2' => array(), + 'h3' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array() + ) ); + ?> +
    +info->screenshots ) ) : ?> + info->screenshots ?> +
    +

    slug ) ?>

    +
      + $url ) : ?> + +
    • + + +
    • + +
    +
    + \ No newline at end of file diff --git a/freemius/templates/plugin-info/features.php b/freemius/templates/plugin-info/features.php index e69de29..b3d0fc8 100644 --- a/freemius/templates/plugin-info/features.php +++ b/freemius/templates/plugin-info/features.php @@ -0,0 +1,114 @@ +features) && is_array($plan->features)) { + foreach ( $plan->features as $feature ) { + if ( ! isset( $features_plan_map[ $feature->id ] ) ) { + $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); + } + + $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; + } + } + + // Add support as a feature. + if ( ! empty( $plan->support_email ) || + ! empty( $plan->support_skype ) || + ! empty( $plan->support_phone ) || + true === $plan->is_success_manager + ) { + if ( ! isset( $features_plan_map['support'] ) ) { + $support_feature = new stdClass(); + $support_feature->id = 'support'; + $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); + $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); + } else { + $support_feature = $features_plan_map['support']; + } + + $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; + } + } + + // Add updates as a feature for all plans. + $updates_feature = new stdClass(); + $updates_feature->id = 'updates'; + $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); + $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); + foreach ( $plans as $plan ) { + $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; + } +?> +
    + + + + + + + + + + + $data ) : ?> + + + + + + + + +
    + title ?> + pricing ) ) { + fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); + } else { + foreach ( $plan->pricing as $pricing ) { + /** + * @var FS_Pricing $pricing + */ + if ( 1 == $pricing->licenses ) { + if ( $pricing->has_annual() ) { + echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); + } else if ( $pricing->has_monthly() ) { + echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); + } else { + echo "\${$pricing->lifetime_price}"; + } + } + } + } + ?> +
    title ) ) ?> + id ] ) ) : ?> + id ]->value ) ) : ?> + id ]->value ) ?> + + + + +
    +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/index.php b/freemius/templates/plugin-info/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/plugin-info/index.php +++ b/freemius/templates/plugin-info/index.php @@ -0,0 +1,3 @@ + +
      + $url ) : ?> + +
    1. + +
    2. + +
    diff --git a/freemius/templates/powered-by.php b/freemius/templates/powered-by.php index e69de29..bb6e081 100644 --- a/freemius/templates/powered-by.php +++ b/freemius/templates/powered-by.php @@ -0,0 +1,61 @@ + +is_whitelabeled() ) : ?> +
    + + \ No newline at end of file diff --git a/freemius/templates/pricing.php b/freemius/templates/pricing.php index e69de29..469f300 100644 --- a/freemius/templates/pricing.php +++ b/freemius/templates/pricing.php @@ -0,0 +1,209 @@ +get_slug(); + $timestamp = time(); + + $context_params = array( + 'plugin_id' => $fs->get_id(), + 'plugin_public_key' => $fs->get_public_key(), + 'plugin_version' => $fs->get_plugin_version(), + ); + + $bundle_id = $fs->get_bundle_id(); + if ( ! is_null( $bundle_id ) ) { + $context_params['bundle_id'] = $bundle_id; + } + + // Get site context secure params. + if ( $fs->is_registered() ) { + $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( + $fs->get_site(), + $timestamp, + 'upgrade' + ) ); + } else { + $context_params['home_url'] = home_url(); + } + + if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) + { + $context_params['sandbox'] = FS_Security::instance()->get_secure_token( + $fs->get_plugin(), + $timestamp, + 'checkout' + ); + } + + $query_params = array_merge( $context_params, $_GET, array( + 'next' => $fs->_get_sync_license_url( false, false ), + 'plugin_version' => $fs->get_plugin_version(), + // Billing cycle. + 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), + 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', + 'currency' => $fs->apply_filters( 'default_currency', 'usd' ), + ) ); + + $use_external_pricing = $fs->should_use_external_pricing(); + + if ( ! $use_external_pricing ) { + $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); + wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); + } else { + if ( ! $fs->is_registered() ) { + $template_data = array( + 'id' => $fs->get_id(), + ); + fs_require_template( 'forms/trial-start.php', $template_data); + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), + ); + fs_require_once_template('secure-https-header.php', $view_params); + } + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } +?> +
    + +
    + $fs->contact_url(), + 'is_network_admin' => fs_is_network_admin(), + 'is_production' => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ), + 'menu_slug' => $fs->get_menu_slug(), + 'mode' => 'dashboard', + 'fs_wp_endpoint_url' => WP_FS__ADDRESS, + 'request_handler_url' => admin_url( + 'admin-ajax.php?' . http_build_query( array( + 'module_id' => $fs->get_id(), + 'action' => $fs->get_ajax_action( 'pricing_ajax_action' ), + 'security' => $fs->get_ajax_security( 'pricing_ajax_action' ) + ) ) + ), + 'selector' => '#fs_pricing_wrapper', + 'unique_affix' => $fs->get_unique_affix(), + ), $query_params ); + + wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); + ?> + +
    +
    + + + + + + +
    + + + +
    +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'pricing', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/secure-https-header.php b/freemius/templates/secure-https-header.php index e69de29..3d0a81e 100644 --- a/freemius/templates/secure-https-header.php +++ b/freemius/templates/secure-https-header.php @@ -0,0 +1,39 @@ + +
    + + get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), + $VARS['page'] + ) ) . + ' - ' . + sprintf( + '%s', + 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, + 'Freemius Inc. [US]' + ); + } + ?> +
    \ No newline at end of file diff --git a/freemius/templates/sticky-admin-notice-js.php b/freemius/templates/sticky-admin-notice-js.php index e69de29..d6d7ebe 100644 --- a/freemius/templates/sticky-admin-notice-js.php +++ b/freemius/templates/sticky-admin-notice-js.php @@ -0,0 +1,41 @@ + + diff --git a/freemius/templates/tabs-capture-js.php b/freemius/templates/tabs-capture-js.php index e69de29..236be3b 100644 --- a/freemius/templates/tabs-capture-js.php +++ b/freemius/templates/tabs-capture-js.php @@ -0,0 +1,63 @@ +get_slug(); +?> + \ No newline at end of file diff --git a/freemius/templates/tabs.php b/freemius/templates/tabs.php index e69de29..2a20b70 100644 --- a/freemius/templates/tabs.php +++ b/freemius/templates/tabs.php @@ -0,0 +1,190 @@ +get_slug(); + + $menu_items = $fs->get_menu_items(); + + $show_settings_with_tabs = $fs->show_settings_with_tabs(); + + $tabs = array(); + foreach ( $menu_items as $priority => $items ) { + foreach ( $items as $item ) { + if ( ! $item['show_submenu'] ) { + $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? + 'support' : + $item['menu_slug']; + + if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { + continue; + } + + if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { + continue; + } + } + + $url = $fs->_get_admin_page_url( $item['menu_slug'] ); + $title = $item['menu_title']; + + $tab = array( + 'label' => $title, + 'href' => $url, + 'slug' => $item['menu_slug'], + ); + + if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { + $tab['href'] .= '&trial=true'; + } + + $tabs[] = $tab; + } + } +?> + \ No newline at end of file diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 9e7e559..59953d4 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -245,7 +245,8 @@ function radio_station_get_shows_data( $show = false ) { $show = radio_station_get_show( $show ); $show = radio_station_get_show_data_meta( $show, true ); $show = radio_station_convert_show_shifts( $show ); - // $show = radio_station_get_show_description( $show ); + // 2.4.0.6: enabled show description/excerpt for multiple single shows + $show = radio_station_get_show_description( $show ); $shows[] = $show; } } else { @@ -258,10 +259,12 @@ function radio_station_get_shows_data( $show = false ) { $show = radio_station_get_show( $show ); $show = radio_station_get_show_data_meta( $show, true ); $show = radio_station_convert_show_shifts( $show ); - // $show = radio_station_get_show_description( $show ); + // 2.4.0.6: enabled show description/excerpt for single show + $show = radio_station_get_show_description( $show ); $shows = array( $show ); } } else { + // --- all shows --- $shows = radio_station_get_shows(); if ( count( $shows ) > 0 ) { foreach ( $shows as $i => $show ) { diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 26d27b2..5da3947 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1571,7 +1571,9 @@ function radio_station_show_list_shortcode( $type, $atts ) { // --- new item div --- $classes = array( 'show-' . $type ); - if ( $newpage ) {$classes[] = 'first-item';} + if ( $newpage ) { + $classes[] = 'first-item'; + } $class = implode( ' ', $classes ); $list .= '
    '; @@ -1582,7 +1584,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { $user = $post; $user_id = $user->data->ID; - // TODO: check for user avatar ? + // TODO: add check for user avatar ? $list .= '
    '; diff --git a/includes/support-functions.php b/includes/support-functions.php index 0c54835..23111f1 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -767,7 +767,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { if ( $default_data ) { return $default_data; if ( RADIO_STATION_DEBUG ) { - echo 'Using Cacehed Data:' . print_r( $default_data, true ) . ''; + echo 'Using Cached Data:' . print_r( $default_data, true ) . ''; } } } @@ -855,6 +855,18 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { $query .= $wpdb->prepare( " LIMIT %d", $args['limit'] ); } $results = $wpdb->get_results( $query, ARRAY_A ); + + // 2.4.0.6: add processing of post excerpts + if ( $results && is_array( $results ) ) { + foreach ( $results as $i => $result ) { + if ( empty( $result['post_excerpt'] ) ) { + $length = apply_filters( 'radio_station_show_data_excerpt_length', 55 ); + $more = apply_filters( 'radio_station_show_data_excerpt_more', '' ); + $result['post_excerpt'] = radio_station_trim_excerpt( $result['post_content'], $length, $more, false ); + $results[$i] = $result; + } + } + } // --- non-post (user) IDS --- if ( isset( $no_profile_ids ) && ( count( $no_profile_ids ) > 0 ) ) { @@ -971,6 +983,8 @@ function radio_station_get_show_data_meta( $show, $single = false ) { // --- get avatar and thumbnail URL --- // 2.3.1: added show avatar and image URLs + // 2.4.0.6: added get show avatar ID + $avatar_id = radio_station_get_show_avatar_id( $show->ID ); $avatar_url = radio_station_get_show_avatar_url( $show->ID ); if ( !$avatar_url ) { $avatar_url = ''; @@ -984,6 +998,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { // --- create array and return --- // 2.3.1: added show avatar and image URLs + // 2.4.0.6: added avatar and image IDs $show_data = array( 'id' => $show->ID, 'name' => $show->post_title, @@ -999,7 +1014,9 @@ function radio_station_get_show_data_meta( $show, $single = false ) { 'languages' => $language_list, 'schedule' => $show_shifts, 'avatar_url' => $avatar_url, + 'avatar_id' => $avatar_id, 'image_url' => $thumbnail_url, + 'image_id' => $thumbnail_id, ); // --- data route / feed for show --- @@ -1047,7 +1064,7 @@ function radio_station_get_show_description( $show_data ) { if ( !empty( $show_post->post_excerpt ) ) { $excerpt = $show_post->post_excerpt; } else { - $length = apply_filters( 'radio_station_show_data_excerpt_length', false ); + $length = apply_filters( 'radio_station_show_data_excerpt_length', 55 ); $more = apply_filters( 'radio_station_show_data_excerpt_more', '' ); $excerpt = radio_station_trim_excerpt( $description, $length, $more, false ); } @@ -1059,6 +1076,9 @@ function radio_station_get_show_description( $show_data ) { // --- add to existing show data --- $show_data['description'] = $description; $show_data['excerpt'] = $excerpt; + + // echo "Description: " . print_r( $description, true ) . PHP_EOL; + // echo "Excerpt: " . print_r( $excerpt, true ) . PHP_EOL; return $show_data; } @@ -1122,6 +1142,8 @@ function radio_station_get_override_data_meta( $override ) { // --- get avatar and thumbnail URL --- // 2.3.1: added show avatar and image URLs + // 2.4.0.6: get avatar image attachment ID + $avatar_id = radio_station_get_show_avatar_id( $override_id ); $avatar_url = radio_station_get_show_avatar_url( $override_id ); if ( !$avatar_url ) { $avatar_url = ''; @@ -1135,6 +1157,7 @@ function radio_station_get_override_data_meta( $override ) { // --- create array and return --- // 2.3.1: added show avatar and image URLs + // 2.4.0.6: added avatar and image IDs $override_data = array( 'id' => $override_id, 'name' => $override->post_title, @@ -1145,7 +1168,9 @@ function radio_station_get_override_data_meta( $override ) { 'hosts' => $hosts, 'producers' => $producers, 'avatar_url' => $avatar_url, + 'avatar_id' => $avatar_id, 'image_url' => $thumbnail_url, + 'image_id' => $thumbnail_id, ); // --- linked Show ID --- @@ -3339,12 +3364,15 @@ function radio_station_get_show_avatar_url( $show_id, $size = 'thumbnail' ) { // --- get the attachment image source --- $avatar_url = false; if ( $avatar_id ) { + // 2.4.0.6: added show avatar size filter + $size = apply_filters( 'radio_station_show_avatar_size', $size ); $avatar_src = wp_get_attachment_image_src( $avatar_id, $size ); $avatar_url = $avatar_src[0]; } // --- filter and return --- - $avatar_url = apply_filters( 'radio_station_show_avatar_url', $avatar_url, $show_id ); + // 2.4.0.6: added third argument for avatar size + $avatar_url = apply_filters( 'radio_station_show_avatar_url', $avatar_url, $show_id, $size ); return $avatar_url; } @@ -3360,12 +3388,15 @@ function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = a // --- get the attachment image tag --- $avatar = false; if ( $avatar_id ) { + // 2.4.0.6: added show avatar size filter + $size = apply_filters( 'radio_station_show_avatar_size', $size ); $avatar = wp_get_attachment_image( $avatar_id, $size, false, $attr ); } // --- filter and return --- // 2.3.3.9: change conflicting (duplicate) filter name for show avatar - $avatar = apply_filters( 'radio_station_show_avatar_output', $avatar, $show_id ); + // 2.4.0.6: added third argument for avatar size + $avatar = apply_filters( 'radio_station_show_avatar_output', $avatar, $show_id, $size ); return $avatar; } @@ -4381,7 +4412,8 @@ function radio_station_convert_show_shifts( $show ) { $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); // 2.3.3.9: added missing shift encore designation - $encore = ( 'on' == $shift['encore'] ) ? true : false; + // 2.4.0.6: fix to undefined index warning for encore + $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; $schedule[$i] = array( 'day' => $shift['day'], 'start' => $start_hour . ':' . $shift['start_min'], diff --git a/js/radio-station-page.js b/js/radio-station-page.js index 21ba53f..5536d84 100644 --- a/js/radio-station-page.js +++ b/js/radio-station-page.js @@ -16,8 +16,8 @@ function radio_show_player() { } /* Switch Section Tabs */ -function radio_show_tab(tab) { - prefix = radio_page_type(); +function radio_show_tab(prefix,tab) { + /* prefix = radio_page_type(); */ if ( (typeof jQuery == 'function') && jQuery('#'+prefix+'-'+tab+'-tab') ) { jQuery('.'+prefix+'-tab').removeClass('tab-active').addClass('tab-inactive'); jQuery('#'+prefix+'-'+tab+'-tab').removeClass('tab-inactive').addClass('tab-active'); diff --git a/radio-station.php b/radio-station.php index 756a9d3..e39e5f4 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.5.4 +Version: 2.4.0.5.6 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -2805,6 +2805,11 @@ function radio_station_add_show_links( $content ) { // note: playlists are linked via single-playlist-content.php template + // 2.4.0.6: bug out if no post object + if ( !is_object( $post ) ) { + return $content; + } + // --- filter to allow related post types --- $post_type = $post->post_type; $post_types = array( 'post' ); diff --git a/readme.txt b/readme.txt index 3f30c95..dba804d 100644 --- a/readme.txt +++ b/readme.txt @@ -227,6 +227,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed: undefined warning for debugshifts * Fixed: current show in schedule when on exact start second * Added: filters for time and date separators +* Added: description/excerpt to single show data endpoint = 2.4.0.5 = * Fixed: plugin conflicts causing fatal errors diff --git a/templates/single-show-content.php b/templates/single-show-content.php index b727c8b..87f01fb 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -961,7 +961,7 @@ } else { $class = "tab-inactive"; } - echo '
    ' . $newline; + echo '
    ' . $newline; echo esc_html( $sections[$section]['anchor'] ) . $newline; echo '
    ' . $newline; if ( ( $i + 1 ) < count( $sections ) ) { @@ -1047,7 +1047,7 @@ $js .= " hash = window.location.hash.substring(1);"; $js .= " if (hash.indexOf('show-') > -1) {"; $js .= " tab = hash.replace('show-', '');"; - $js .= " radio_show_tab(tab);"; + $js .= " radio_show_tab('show',tab);"; $js .= " }"; $js .= " }"; $js .= "}, 500);"; From 81f44f52fdf6547a7309c0a45cf3182f8d8d9e9c Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 3 Mar 2022 22:17:15 +1000 Subject: [PATCH 252/377] dev refix tab changing --- js/radio-station-page.js | 6 ++++++ readme.txt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/js/radio-station-page.js b/js/radio-station-page.js index 5536d84..c07161e 100644 --- a/js/radio-station-page.js +++ b/js/radio-station-page.js @@ -20,6 +20,7 @@ function radio_show_tab(prefix,tab) { /* prefix = radio_page_type(); */ if ( (typeof jQuery == 'function') && jQuery('#'+prefix+'-'+tab+'-tab') ) { jQuery('.'+prefix+'-tab').removeClass('tab-active').addClass('tab-inactive'); + jQuery('.'+prefix+'-section').removeClass('tab-active').addClass('tab-inactive'); jQuery('#'+prefix+'-'+tab+'-tab').removeClass('tab-inactive').addClass('tab-active'); jQuery('#'+prefix+'-'+tab).removeClass('tab-inactive').addClass('tab-active'); } else if (document.getElementById(prefix+'-'+tab+'-tab')) { @@ -27,6 +28,10 @@ function radio_show_tab(prefix,tab) { for (i = 0; i < tabs.length; i++) { tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); } + tabs = document.getElementsByClassName(prefix+'-section'); + for (i = 0; i < sections.length; i++) { + tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); + } button = document.getElementById(prefix+'-'+tab+'-tab'); button.className = button.className.replace('-tab-inactive', '-tab-active'); content = document.getElementById(prefix+'-'+tab); @@ -37,6 +42,7 @@ function radio_show_tab(prefix,tab) { /* Responsive Page */ function radio_page_responsive() { prefix = radio_page_type(); + if (!document.getElementById(prefix+'-content')) {return;} /* Check to Add Narrow Class */ if (typeof jQuery == 'function') { diff --git a/readme.txt b/readme.txt index dba804d..abc6505 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.8.1 +Tested up to: 5.9 Stable tag: 2.4.0.5 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 019018336f68d8a621ac20aad8712bd79226c01d Mon Sep 17 00:00:00 2001 From: majick777 Date: Sat, 5 Mar 2022 11:28:06 +1000 Subject: [PATCH 253/377] dev updates for 2.4.0.6 release --- js/radio-station-page.js | 4 +- radio-station.php | 125 ++++++++++++++++++++---------- readme.md | 4 +- readme.txt | 2 +- templates/single-show-content.php | 17 ++-- 5 files changed, 97 insertions(+), 55 deletions(-) diff --git a/js/radio-station-page.js b/js/radio-station-page.js index c07161e..cb19939 100644 --- a/js/radio-station-page.js +++ b/js/radio-station-page.js @@ -28,9 +28,9 @@ function radio_show_tab(prefix,tab) { for (i = 0; i < tabs.length; i++) { tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); } - tabs = document.getElementsByClassName(prefix+'-section'); + sections = document.getElementsByClassName(prefix+'-section'); for (i = 0; i < sections.length; i++) { - tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); + sections[i].className = sections[i].className.replace('-tab-active', '-tab-inactive'); } button = document.getElementById(prefix+'-'+tab+'-tab'); button.className = button.className.replace('-tab-inactive', '-tab-active'); diff --git a/radio-station.php b/radio-station.php index e39e5f4..ee03090 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.5.6 +Version: 2.4.0.6 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -378,15 +378,19 @@ 'section' => 'feeds', ), - // --- Clear Transients --- + // === Performance === + // 2.4.0.6: separated performance section + + // --- Disable Transients --- + // 2.4.0.6: change label from Clear Transients 'clear_transients' => array( 'type' => 'checkbox', - 'label' => __( 'Clear Transients', 'radio-station' ), + 'label' => __( 'Disable Transients', 'radio-station' ), 'default' => '', 'value' => 'yes', 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), 'tab' => 'general', - 'section' => 'feeds', + 'section' => 'performance', ), // --- Transient Caching --- @@ -397,7 +401,7 @@ 'value' => 'yes', 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), 'tab' => 'general', - 'section' => 'feeds', + 'section' => 'performance', 'pro' => true, ), @@ -1062,7 +1066,9 @@ ), - // ==== Archives === + // ==== Post Type Archives === + // 2.4.0.6: move archives to separate tab + // 2.4.0.6: added post type archives section // --- Shows Archive Page --- 'show_archive_page' => array( @@ -1071,8 +1077,8 @@ 'options' => 'PAGEID', 'default' => '', 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // --- Automatic Display --- @@ -1082,8 +1088,8 @@ 'value' => 'yes', 'default' => 'yes', 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // ? --- Redirect Shows Archive --- ? @@ -1093,8 +1099,8 @@ // 'value' => 'yes', // 'default' => '', // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', + // 'tab' => 'archives', + // 'section' => 'posttypes', // ), // --- Overrides Archive Page --- @@ -1104,8 +1110,8 @@ 'options' => 'PAGEID', 'default' => '', 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // --- Automatic Display --- @@ -1115,8 +1121,8 @@ 'value' => 'yes', 'default' => 'yes', 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // ? --- Redirect Overrides Archive --- ? @@ -1126,8 +1132,8 @@ // 'value' => 'yes', // 'default' => '', // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', + // 'tab' => 'archives', + // 'section' => 'posttypes', // ), // --- Playlists Archive Page --- @@ -1137,8 +1143,8 @@ 'options' => 'PAGEID', 'default' => '', 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // --- Automatic Display --- @@ -1148,8 +1154,8 @@ 'value' => 'yes', 'default' => 'yes', 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'posttypes', ), // ? --- Redirect Playlists Archive --- ? @@ -1159,10 +1165,44 @@ // 'value' => 'yes', // 'default' => '', // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', + // 'tab' => 'archives', + // 'section' => 'posttypes', // ), + // --- [Pro] Team Archive Page --- + // 2.4.0.6: added option for team archive page + 'team_archive_page' => array( + 'label' => __( 'Team Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // --- [Pro] Automatic Display --- + // 2.4.0.6: added option for team archive page + 'team_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'select', + 'options' => array( + '' => __( 'Off', 'radio-station' ), + 'yes' => __( 'List', 'radio-station' ), + // 'grid' => __( 'Grid', 'radio-station' ), + ), + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // === Taxonomy Archives === + // 2.4.0.6: added taxonomy archives section + // --- Genres Archive Page --- 'genre_archive_page' => array( 'label' => __( 'Genres Archive Page', 'radio-station' ), @@ -1170,8 +1210,8 @@ 'options' => 'PAGEID', 'default' => '', 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'taxonomies', ), // --- Automatic Display --- @@ -1181,8 +1221,8 @@ 'value' => 'yes', 'default' => 'yes', 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'taxonomies', ), // ? --- Redirect Genres Archives --- ? @@ -1192,8 +1232,8 @@ // 'value' => 'yes', // 'default' => '', // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', + // 'tab' => 'archives', + // 'section' => 'taxonomies', // ), // --- Languages Archive Page --- @@ -1204,8 +1244,8 @@ 'options' => 'PAGEID', 'default' => '', 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'taxonomies', ), // --- Automatic Display --- @@ -1216,8 +1256,8 @@ 'value' => 'yes', 'default' => 'yes', 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', - 'tab' => 'pages', - 'section' => 'archives', + 'tab' => 'archives', + 'section' => 'taxonomies', ), // ? --- Redirect Languages Archives --- ? @@ -1227,8 +1267,8 @@ // 'value' => 'yes', // 'default' => '', // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', + // 'tab' => 'archives', + // 'section' => 'taxonomies', // ), // === Single Templates === @@ -1429,10 +1469,12 @@ // 2.3.2: add widget options tab // 2.3.3.8: added player options tab // 2.3.3.8: move templates section onto pages tab + // 2.4.0.6: added separate archives tab 'tabs' => array( 'general' => __( 'General', 'radio-station' ), - 'pages' => __( 'Pages', 'radio-station' ), 'player' => __( 'Player', 'radio-station' ), + 'pages' => __( 'Pages', 'radio-station' ), + 'archives' => __( 'Archives', 'radio-station' ), // 'templates' => __( 'Templates', 'radio-station' ), 'widgets' => __( 'Widgets', 'radio-station' ), 'roles' => __( 'Roles', 'radio-station' ), @@ -1441,21 +1483,26 @@ // --- Section Labels --- // 2.3.2: add widget loading section // 2.3.3.9: added profile pages section + // 2.4.0.6: added performance section + // 2.4.0.6: added posttypes and taxonomies archive sections 'sections' => array( 'broadcast' => __( 'Broadcast', 'radio-station' ), 'station' => __( 'Station', 'radio-station' ), 'feeds' => __( 'Feeds', 'radio-station' ), + 'performance' => __( 'Performance', 'radio-station' ), 'basic' => __( 'Basic Defaults', 'radio-station' ), 'advanced' => __( 'Advanced Defaults', 'radio-station' ), 'colors' => __( 'Player Colors', 'radio-station' ), 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), - 'single' => __( 'Single Templates', 'radio-station' ), - 'archive' => __( 'Archive Templates', 'radio-station' ), 'schedule' => __( 'Schedule Page', 'radio-station' ), + 'single' => __( 'Single Templates', 'radio-station' ), + // 'archive' => __( 'Archive Templates', 'radio-station' ), 'show' => __( 'Show Pages', 'radio-station' ), 'profile' => __( 'Profile Pages', 'radio-station' ), 'episode' => __( 'Episode Pages', 'radio-station' ), 'archives' => __( 'Archives', 'radio-station' ), + 'posttypes' => __( 'Post Types', 'radio-station' ), + 'taxonomies' => __( 'Taxonomies', 'radio-station' ), 'loading' => __( 'Widget Loading', 'radio-station' ), 'permissions' => __( 'Permissions', 'radio-station' ), ), diff --git a/readme.md b/readme.md index c7931d5..3bba0c1 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.8 +Tested up to: 5.9 -Stable tag: 2.4.0.3 +Stable tag: 2.4.0.6 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index abc6505..9266e79 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.9 -Stable tag: 2.4.0.5 +Stable tag: 2.4.0.6 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 87f01fb..f2ae69e 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -945,6 +945,7 @@ if ( isset( $sections[$section_order[0]] ) ) { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $sections[$section_order[0]]['heading']; + // phpcs:ignore WordPress.Security.OutputNotEscaped echo $sections[$section_order[0]]['content']; } unset( $section_order[0] ); @@ -956,11 +957,7 @@ foreach ( $section_order as $section ) { if ( isset( $sections[$section] ) ) { $found_section = true; - if ( 0 == $i ) { - $class = "tab-active"; - } else { - $class = "tab-inactive"; - } + $class = ( 0 == $i ) ? 'tab-active' : 'tab-inactive'; echo '
    ' . $newline; echo esc_html( $sections[$section]['anchor'] ) . $newline; echo '
    ' . $newline; @@ -972,7 +969,8 @@ } // 2.3.3.9: add end tab right spacer if ( $found_section ) { - echo '
     
    ' . $newline; + // 2.4.0.6: add class to last spacer + echo '
     
    ' . $newline; } echo '
    ' . $newline; } @@ -1011,11 +1009,8 @@ // --- add tab classes to section --- $classes = array( 'show-tab' ); - if ( 0 == $i ) { - $classes[] = 'tab-active'; - } else { - $classes[] = 'tab-inactive'; - } + $tab_class = ( 0 == $i ) ? 'tab-active' : 'tab-inactive'; + $classes[] = $tab_class; $class = implode( ' ', $classes ); $sections[$section]['content'] = str_replace( 'class="show-section-content"', 'class="' . esc_attr( $class ) . '"', $sections[$section]['content'] ); From 0c726fb4bd7d0f9f64a2adec94f09447485c3171 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 8 Mar 2022 10:35:52 +1000 Subject: [PATCH 254/377] dev updates including #413 --- CHANGELOG.md | 5 + css/rs-shortcodes.css | 1 + docs/Filters.md | 517 +++-- includes/extra-strings.php | 179 +- languages/radio-station.pot | 4254 ++++++++++++++++++++++++++++------- loader.php | 6 +- radio-station.php | 19 +- reader.php | 11 +- readme.md | 2 +- readme.txt | 7 +- 10 files changed, 3923 insertions(+), 1078 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a495042..e3d324d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.4.0.7 +* Fix: remove debug output breaking redirects/data endpoints +* Updated: main language translation file +* Added: list of Pro filters to documentation + ### 2.4.0.6 * Update: Freemius SDK (2.4.3) * Updated: documentation links to new demo site address diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index a642b29..b188629 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -147,6 +147,7 @@ .show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail { padding-right: 30px; margin-top: 10px; + min-width:! 150px; } .show-post-info, .show-playlist-info, .show-host-thumbnail, .show-producer-thumbnail .show-episode-info { diff --git a/docs/Filters.md b/docs/Filters.md index 5402872..094069a 100644 --- a/docs/Filters.md +++ b/docs/Filters.md @@ -51,37 +51,41 @@ Here is a full list of available filters within the plugin, grouped by file and | File / *Function* | Filter | Value | Extra Args | | - | - | |**radio-station.php**|||| -|*radio_station_get_template*|`radio_station_template_dir_hierarchy` | ` $dirs` | `$template`, `$paths`| -|*radio_station_automatic_pages_content_set*|`radio_station_automatic_schedule_atts` | ` $atts` | | +|*radio_station_localize_script*|`radio_station_time_separator` | ` ':'` | `'javascript'`| +|*radio_station_streaming_data*|`radio_station_localization_script` | ` $js` | | +| |`radio_station_streaming_data` | ` $data` | `$station`| +|*radio_station_doing_template*|`radio_station_player_allowed_origins` | ` $allowed` | | +|*radio_station_phone_number*|`radio_station_template_dir_hierarchy` | ` $dirs` | `$template`, `$paths`| +|*radio_station_automatic_pages_content_get*|`radio_station_automatic_schedule_atts` | ` $atts` | | | |`radio_station_automatic_show_archive_atts` | ` $atts` | | | |`radio_station_automatic_override_archive_atts` | ` $atts` | | | |`radio_station_automatic_playlist_archive_atts` | ` $atts` | | | |`radio_station_automatic_genre_archive_atts` | ` $atts` | | -|*radio_station_single_content_template*|`radio_station_'.$post_type.'_content_templates` | ` $templates` | `$post_type`| +| |`radio_station_automatic_languagee_archive_atts` | ` $atts` | | +| |`radio_station_'.$post_type.'_content_templates` | ` $templates` | `$post_type`| +| |`radio_station_single_template_post_data` | ` $post` | `$post_type`| | |`radio_station_content_'.$post_type` | ` $output` | `$post_id`| -|*radio_station_get_host_template*|`radio_station_host_templates` | ` $templates` | | -|*radio_station_get_producer_template*|`radio_station_producer_templates` | ` $templates` | | -|*radio_station_add_show_links*|`radio_station_show_related_post_types` | ` $post_types` | | +|*radio_station_override_content_template*|`radio_station_host_templates` | ` $templates` | | +| |`radio_station_producer_templates` | ` $templates` | | +|*radio_station_archive_template_hierarchy*|`radio_station_show_related_post_types` | ` $post_types` | | | |`radio_station_link_to_show_positions` | ` $positions` | `$post_type`, `$post`| | |`radio_station_link_to_show_before` | ` $before` | `$post`, `$related_shows`| | |`radio_station_link_to_show_after` | ` $after` | `$post`, `$related_shows`| -|*radio_station_get_show_post_link*|`radio_station_show_related_post_types` | ` $related_post_types` | | -| |`radio_station_link_show_posts` | ` true` | `$post`| |**radio-station-admin.php**|||| -|*radio_station_settings_cap_check*|`radio_station_settings_capability` | ` 'manage_options'` | | -|*radio_station_add_admin_menus*|`radio_station_menu_position` | ` 5` | | +|*radio_station_license_activation_link*|`radio_station_settings_capability` | ` 'manage_options'` | | +| |`radio_station_menu_position` | ` 5` | | | |`radio_station_manage_options_capability` | ` 'manage_options'` | | | |`radio_station_export_playlists` | ` false` | | |*radio_station_role_editor*|`radio_station_role_editor_message` | ` true` | | |**includes/data-feeds.php**|||| -|*radio_station_api_link_header*|`radio_station_api_discovery_header` | ` $header` | | -|*radio_station_api_discovery_link*|`radio_station_api_discovery_link` | ` $link` | | -|*radio_station_api_discovery_rsd*|`radio_station_api_discovery_rsd` | ` $link` | | -|*radio_station_get_station_data*|`radio_station_station_data` | ` $station_data` | | +|*radio_station_api_discovery_link*|`radio_station_api_discovery_header` | ` $header` | | +| |`radio_station_api_discovery_link` | ` $link` | | +| |`radio_station_api_discovery_rsd` | ` $link` | | +|*radio_station_add_station_data*|`radio_station_station_data` | ` $station_data` | | |*radio_station_get_broadcast_data*|`radio_station_broadcast_data` | ` $broadcast` | | |*radio_station_get_shows_data*|`radio_station_shows_data` | ` $shows` | `$show`| -|*radio_station_get_genres_data*|`radio_station_genres_data` | ` $genres` | `$genre`| -|*radio_station_get_languages_data*|`radio_station_languages_data` | ` $languages_data` | `$language`| +|*radio_station_get_languages_data*|`radio_station_genres_data` | ` $genres` | `$genre`| +|*radio_station_station_endpoint*|`radio_station_languages_data` | ` $languages_data` | `$language`| |*radio_station_register_rest_routes*|`radio_station_route_slug_base` | ` 'radio'` | | | |`radio_station_route_slug_station` | ` 'station'` | | | |`radio_station_route_slug_broadcast` | ` 'broadcast'` | | @@ -89,40 +93,28 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_route_slug_shows` | ` 'shows'` | | | |`radio_station_route_slug_genres` | ` 'genres'` | | | |`radio_station_route_slug_languages` | ` 'languages'` | | -|*radio_station_get_route_urls*|`radio_station_route_urls` | ` $routes` | | -|*radio_station_route_radio*|`radio_station_route_slug_base` | ` 'radio'` | | +|*radio_station_route_radio*|`radio_station_route_urls` | ` $routes` | | +| |`radio_station_route_slug_base` | ` 'radio'` | | |*radio_station_route_station*|`radio_station_route_station` | ` $station` | `$request`| -|*radio_station_route_broadcast*|`radio_station_route_broadcast` | ` $broadcast` | `$request`| +| |`radio_station_route_broadcast` | ` $broadcast` | `$request`| |*radio_station_route_schedule*|`radio_station_route_schedule` | ` $schedule` | `$request`| -|*radio_station_route_shows*|`radio_station_route_shows` | ` $show_list` | `$request`| -|*radio_station_route_genres*|`radio_station_route_genres` | ` $genre_list` | `$request`| -|*radio_station_route_languages*|`radio_station_route_languages` | ` $language_list` | `$request`| -|*radio_station_add_feeds*|`radio_station_feed_slug_base` | ` 'radio'` | | -| |`radio_station_feed_slug_station` | ` 'station'` | | -| |`radio_station_feed_slug_broadcast` | ` 'broadcast'` | | -| |`radio_station_feed_slug_schedule` | ` 'schedule'` | | -| |`radio_station_feed_slug_shows` | ` 'shows'` | | -| |`radio_station_feed_slug_genres` | ` 'genres'` | | -| |`radio_station_feed_slug_languages` | ` 'languages'` | | -| |`radio_station_feed_slugs` | ` $feeds` | | -|*radio_station_get_feed_urls*|`radio_station_feed_urls` | ` $feeds` | | -|*radio_station_feed_radio*|`radio_station_feed_slug_base` | ` 'radio'` | | -| |`radio_station_feed_radio` | ` $radio` | | -|*radio_station_feed_station*|`radio_station_feed_station` | ` $station` | | -|*radio_station_feed_broadcast*|`radio_station_feed_broadcast` | ` $broadcast` | | -|*radio_station_feed_schedule*|`radio_station_feed_schedule` | ` $schedule` | | -|*radio_station_feed_shows*|`radio_station_feed_shows` | ` $show_list` | | -|*radio_station_feed_genres*|`radio_station_feed_genres` | ` $genre_list` | | -|*radio_station_feed_languages*|`radio_station_feed_languages` | ` $language_list` | | +|*radio_station_route_genres*|`radio_station_route_shows` | ` $show_list` | `$request`| +|*radio_station_route_languages*|`radio_station_route_genres` | ` $genre_list` | `$request`| +| |`radio_station_route_languages` | ` $language_list` | `$request`| |**includes/master-schedule.php**|||| -|*radio_station_master_schedule*|`radio_station_schedule_clock` | ` array()` | `$atts`| +|*radio_station_master_schedule*|`radio_station_master_schedule_default_atts` | ` $defaults` | `$view`, `$views`| +| |`radio_station_schedule_clock` | ` array()` | `$atts`| | |`radio_station_schedule_clock` | ` array()` | `$atts`| | |`radio_station_schedule_control_order` | ` $control_order` | `$atts`| | |`radio_station_schedule_controls` | ` $controls` | `$atts`| -| |`radio_station_schedule_override` | ` ''` | `$atts`| -| |`master_schedule_table_view` | ` $output` | `$atts`| -| |`master_schedule_tabs_view` | ` $output` | `$atts`| -| |`master_schedule_list_view` | ` $output` | `$atts`| +| |`radio_station_schedule_controls_output` | ` $output` | `$atts`| +| |`radio_station_schedule_override` | ` $output` | `$atts`| +| |`master_schedule_table_view` | ` $html` | `$atts`| +| |`master_schedule_tabs_view` | ` $html` | `$atts`| +| |`master_schedule_list_view` | ` $html` | `$atts`| +|*radio_station_ajax_schedule_loader*|`radio_station_master_schedule_loader_js` | ` $js` | | +|*radio_station_master_schedule_genre_selector*|`radio_station_master_schedule_load_script` | ` $js` | `$atts`| +| |`radio_station_master_schedule_table_js` | ` $js` | | |**includes/post-types.php**|||| |*radio_station_create_post_types*|`radio_station_post_type_show` | ` $post_type` | | | |`radio_station_post_type_playlist` | ` $post_type` | | @@ -130,55 +122,86 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_host_interface` | ` false` | | | |`radio_station_post_type_host` | ` $post_type` | | | |`radio_station_producer_interface` | ` false` | | -| |`radio_station_post_type_producer` | ` $post_type` | | -|*radio_station_register_show_taxonomies*|`radio_station_genre_taxonomy_args` | ` $args` | | +|*radio_station_post_type_editor*|`radio_station_post_type_producer` | ` $post_type` | | +|*radio_station_add_featured_image_support*|`radio_station_admin_bar_post_types` | ` $post_types` | `'new'`| +| |`radio_station_admin_bar_post_types` | ` $post_types` | `'edit'`| +| |`radio_station_admin_bar_post_types` | ` $post_types` | `'view'`| +| |`radio_station_genre_taxonomy_args` | ` $args` | | | |`radio_station_language_taxonomy_args` | ` $args` | | |**includes/post-types-admin.php**|||| -|*radio_station_add_playlist_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'playlist'`| -|*radio_station_add_post_show_metabox*|`radio_station_show_related_post_types` | ` $post_types` | | -|*radio_station_add_show_info_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'shows'`| -|*radio_station_add_show_shifts_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'shifts'`| +|*radio_remove_language*|`radio_station_language_edit_styles` | ` $css` | | +| |`radio_station_language_edit_script` | ` $js` | | +| |`radio_station_metabox_position` | ` 'rstop'` | `'shows'`| +|*radio_station_add_show_hosts_metabox*|`radio_station_show_edit_styles` | ` $css` | | +|*radio_station_add_show_producers_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'shifts'`| +|*radio_station_shift_edit_script*|`radio_station_shift_list_edit_styles` | ` $css` | | +| |`radio_station_shift_edit_script` | ` $js` | | |*radio_station_add_show_helper_box*|`radio_station_metabox_position` | ` 'rstop'` | `'helper'`| -|*radio_station_add_schedule_override_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'overrides'`| +|*radio_station_add_override_show_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'overrides'`| +| |`radio_station_override_edit_styles` | ` $css` | | +|*radio_station_override_show_script*|`radio_station_override_show_script` | ` $js` | | +| |`radio_station_metabox_position` | ` 'rstop'` | `'overrides'`| +| |`radio_station_override_list_edit_styles` | ` $css` | | +|*radio_station_override_save_data*|`radio_station_override_edit_script` | ` $js` | | +|*radio_station_override_sortable_columns*|`radio_station_show_avatar` | ` $thumbnail_url` | `$post_id`| |*radio_station_override_past_future_filter*|`radio_station_overrides_past_future_default` | ` $pastfuture` | | +| |`radio_station_metabox_position` | ` 'rstop'` | `'playlist'`| +|*radio_track_add*|`radio_station_tracks_list_styles` | ` $css` | | |**includes/shortcodes.php**|||| -|*radio_station_timezone_shortcode*|`radio_station_timezone_select` | ` ''` | `'radio-station-timezone-'.$instance`, `$atts`| -| |`radio_station_timezone_shortcode` | ` $output` | `$atts`| -|*radio_station_clock_shortcode*|`radio_station_clock_timezone_select` | ` ''` | `'radio-station-clock-'.$instance`, `$atts`| -| |`radio_station_clock` | ` $clock` | `$atts`| -|*radio_station_archive_list_shortcode*|`radio_station_'.$type.'_archive_post_args` | ` $args` | | +|*radio_station_clock_shortcode*|`radio_station_timezone_shortcode` | ` $output` | `$atts`| +|*radio_station_archive_list_shortcode*|`radio_station_clock` | ` $clock` | `$atts`| +| |`radio_station_'.$type.'_archive_post_args` | ` $args` | | | |`radio_station_'.$type.'_archive_posts` | ` $archive_posts` | | -| |`radio_station_time_format_start` | ` $start_data_format` | `$type.'-archive'`, `$atts`| -| |`radio_station_time_format_end` | ` $end_data_format` | `$type.'-archive'`, `$atts`| +| |`radio_station_time_separator` | ` $time_separator` | `$post_type.'-archive'`| +| |`radio_station_time_format_start` | ` $start_data_format` | `$post_type.'-archive'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `$post_type.'-archive'`, `$atts`| +| |`radio_station_archive_shortcode_no_records` | ` $message` | `$post_type`, `$atts`| | |`radio_station_archive_'.$type.'_list_excerpt_length` | ` false` | | | |`radio_station_archive_'.$type.'_list_excerpt_more` | ` '[…]'` | | -| |`radio_station_episode_archive_meta` | ` ''` | | -| |`radio_station_host_archive_meta` | ` ''` | | -| |`radio_station_producer_archive_meta` | ` ''` | | +| |`radio_station_archive_shortcode_info_order` | ` $infokeys` | `$post_type`, `$atts`| +|*radio_station_show_archive_list*|`radio_station_show_times_separator` | ` $separator` | `'override'`| | |`radio_station_'.$type.'_archive_content` | ` $post_content` | `$post_id`| | |`radio_station_'.$type.'_archive_excerpt` | ` $excerpt` | `$post_id`| -| |`radio_station_'.$type.'_archive_list` | ` $list` | `$atts`| -|*radio_station_genre_archive_list*|`radio_station_genre_archive_post_args` | ` $args` | | +| |`radio_station_archive_shortcode_info_custom` | ` ''` | `$post_id`, `$post_type`, `$atts`| +| |`radio_station_archive_shortcode_info` | ` $info` | `$post_id`, `$post_type`, `$atts`| +| |`radio_station_'.$type.'_archive_list` | ` $list` | `$atts`, `$post_type`| +| |`radio_station_genre_archive_post_args` | ` $args` | | | |`radio_station_genre_archive_posts` | ` $posts` | | | |`radio_station_genre_image` | ` false` | `$genre`| -| |`radio_station_genre_archive_list` | ` $list` | `$atts`| -|*radio_station_language_archive_list*|`radio_station_language_archive_list` | ` $list` | `$atts`| -|*radio_station_show_list_shortcode*|`radio_station_get_show_episodes` | ` false` | `$show_id`, `$args`| +| |`radio_station_genre_archive_excerpt_length` | ` false` | | +| |`radio_station_genre_archive_excerpt_more` | ` '[…]'` | | +| |`radio_station_genre_archive_excerpt` | ` $excerpt` | `$post->ID`| +|*radio_station_language_archive_list*|`radio_station_genre_archive_list` | ` $list` | `$atts`| +|*radio_station_archive_pagination*|`radio_station_language_archive_post_args` | ` $args` | | +| |`radio_station_language_archive_posts` | ` $posts` | | +| |`radio_station_language_archive_excerpt_length` | ` false` | | +| |`radio_station_language_archive_excerpt_more` | ` '[…]'` | | +| |`radio_station_genre_archive_excerpt` | ` $excerpt` | `$post->ID`| +| |`radio_station_language_archive_list` | ` $list` | `$atts`| +|*radio_station_show_posts_archive*|`radio_station_get_show_hosts` | ` false` | `$show_id`, `$args`| +| |`radio_station_get_show_producers` | ` false` | `$show_id`, `$args`| +| |`radio_station_get_show_episodes` | ` false` | `$show_id`, `$args`| | |`radio_station_show_'.$type.'_list_excerpt_length` | ` false` | | | |`radio_station_show_'.$type.'_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_show_'.$type.'_content` | ` $bio_content` | `$user_id`| +| |`radio_station_show_'.$type.'_excerpt` | ` $excerpt` | `$user_id`| +| |`radio_station_show_list_archive_avatar` | ` $thumbnail` | `$post['ID']`, `$type`| | |`radio_station_show_'.$type.'_content` | ` $post_content` | `$post_id`| | |`radio_station_show_'.$type.'_excerpt` | ` $excerpt` | `$post_id`| | |`radio_station_show_'.$type.'_list` | ` $list` | `$atts`| -|*radio_station_current_show_shortcode*|`radio_station_current_show_dynamic` | ` 0` | `$atts`| +| |`radio_station_current_show_dynamic` | ` false` | `$atts`| | |`radio_station_widgets_ajax_override` | ` $ajax` | `'current-show'`, `$widget`| | |`radio_station_current_show_widget_excerpt_length` | ` false` | | | |`radio_station_current_show_widget_excerpt_more` | ` '[…]'` | | | |`radio_station_current_show_shortcode_excerpt_length` | ` false` | | | |`radio_station_current_show_shortcode_excerpt_more` | ` '[…]'` | | +| |`radio_station_time_separator` | ` $time_separator` | `'current-show'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'current-show'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'current-show'`, `$atts`| | |`radio_station_current_show_link` | ` $show_link` | `$show_id`, `$atts`| +| |`radio_station_show_times_separator` | ` $separator` | `'current-show'`| | |`radio_station_current_show_title_display` | ` $title` | `$show_id`, `$atts`| +| |`radio_station_current_show_avatar_size` | ` $atts['avatar_size']` | `$show_id`| | |`radio_station_current_show_avatar` | ` $show_avatar` | `$show_id`, `$atts`| | |`radio_station_current_show_avatar_display` | ` $avatar` | `$show_id`, `$atts`| | |`radio_station_dj_link` | ` $host_link` | `$host`| @@ -193,14 +216,17 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_current_show_section_order` | ` $order` | `$atts`| | |`radio_station_no_current_show_text` | ` $no_current_show` | `$atts`| | |`radio_station_countdown_dynamic` | ` false` | `'current-show'`, `$atts`, `$current_shift_end`| -|*radio_station_current_show*|`radio_station_current_show_load_script` | ` $js` | `$atts`| -|*radio_station_upcoming_shows_shortcode*|`radio_station_upcoming_shows_dynamic` | ` 0` | `$atts`| +|*radio_station_upcoming_shows_shortcode*|`radio_station_current_show_load_script` | ` $js` | `$atts`| +| |`radio_station_upcomins_shows_dynamic` | ` false` | `$atts`| | |`radio_station_widgets_ajax_override` | ` $ajax` | `'upcoming-shows'`, `$widget`| | |`radio_station_upcoming_shows_section_order` | ` $order` | `$atts`| +| |`radio_station_time_separator` | ` $time_separator` | `'upcoming-shows'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'upcoming-shows'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'upcoming-shows'`, `$atts`| | |`radio_station_upcoming_show_link` | ` $show_link` | `$show_id`, `$atts`| +| |`radio_station_show_times_separator` | ` $separator` | `'upcoming-shows'`| | |`radio_station_upcoming_show_title_display` | ` $title` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_avatar_size` | ` $atts['avatar_size']` | `$show_id`| | |`radio_station_upcoming_show_avatar` | ` $show_avatar` | `$show_id`, `$atts`| | |`radio_station_upcoming_show_avatar_display` | ` $avatar` | `$show_id`, `$atts`| | |`radio_station_dj_link` | ` $host_link` | `$host`| @@ -208,96 +234,96 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_upcoming_show_encore_display` | ` $encore` | `$show_id`, `$atts`| | |`radio_station_upcoming_show_shifts_display` | ` $shift_display` | `$show_id`, `$atts`| | |`radio_station_upcoming_shows_custom_display` | ` ''` | `$show_id`, `$atts`| -| |`radio_station_no_upcoming_shows_text` | ` $no_upcoming_shows` | `$atts`| -| |`radio_station_countdown_dynamic` | ` false` | `'upcoming-shows'`, `$atts`, `$next_start_time`| -|*radio_station_upcoming_shows*|`radio_station_upcoming_shows_load_script` | ` $js` | `$atts`| -|*radio_station_current_playlist_shortcode*|`radio_station_current_playlist_dynamic` | ` 0` | `$atts`| -| |`radio_station_widgets_ajax_override` | ` $ajax` | `'current-playlist'`, `$widget`| -| |`radio_station_countdown_dynamic` | ` false` | `'current-playlist'`, `$atts`, `$shift_end_time`| -| |`radio_station_current_playlist_tracks_display` | ` $tracks` | `$playlist`, `$atts`| -| |`radio_station_current_playlist_link_display` | ` $link` | `$playlist`, `$atts`| -| |`radio_station_current_playlist_custom_display` | ` ''` | `$playlist`, `$atts`| -| |`radio_station_current_playlist_section_order` | ` $order` | `$atts`| -| |`radio_station_no_current_playlist_text` | ` $no_current_playlist` | `$atts`| -| |`radio_station_current_playlist_no_playlist_display` | ` $no_playlist` | `$atts`| -|*radio_station_current_playlist*|`radio_station_current_playlist_load_script` | ` $js` | `$atts`| |**includes/support-functions.php**|||| |*radio_station_get_shows*|`radio_station_get_shows` | ` $shows` | `$defaults`| -|*radio_station_get_show_shifts*|`radio_station_show_shifts` | ` $day_shifts` | | -|*radio_station_get_overrides*|`radio_station_get_overrides` | ` $override_list` | `$start_date`, `$end_date`| -|*radio_station_get_show_data*|`radio_station_cached_data` | ` false` | `$datatype`, `$show_id`| +|*radio_station_get_overrides*|`radio_station_show_day_shifts` | ` $day_shifts` | | +|*radio_station_get_show_data*|`radio_station_get_overrides` | ` $override_list` | `$start_date`, `$end_date`| +| |`radio_station_cached_data` | ` false` | `$datatype`, `$show_id`| +|*radio_station_get_show_data_meta*|`radio_station_show_data_excerpt_length` | ` 55` | | +| |`radio_station_show_data_excerpt_more` | ` ''` | | | |`radio_station_show_'.$datatype` | ` $results` | `$show_id`, `$args`| -|*radio_station_get_show_data_meta*|`radio_station_show_data_meta` | ` $show_data` | `$show_id`| -|*radio_station_get_show_description*|`radio_station_show_data_excerpt_length` | ` false` | | +|*radio_station_get_show_description*|`radio_station_show_data_meta` | ` $show_data` | `$show_id`| +| |`radio_station_show_data_excerpt_length` | ` 55` | | | |`radio_station_show_data_excerpt_more` | ` ''` | | | |`radio_station_show_data_description` | ` $description` | `$show_id`| | |`radio_station_show_data_excerpt` | ` $excerpt` | `$show_id`| -|*radio_station_get_override_data_meta*|`radio_station_override_data` | ` $override_data` | `$override_id`| -|*radio_station_get_current_schedule*|`radio_station_previous_show` | ` $prev_shift` | `$time`| +| |`radio_station_override_data` | ` $override_data` | `$override_id`| +| |`radio_station_linked_overrides` | ` $override_ids` | `$post_id`| +| |`radio_station_linked_override_times` | ` $overrides` | `$post_id`| | |`radio_station_previous_show` | ` $prev_shift` | `$time`| -| |`radio_station_current_schedule` | ` $show_shifts` | `$time`| -|*radio_station_get_current_show*|`radio_station_previous_show` | ` $prev_shift` | `$time`| +| |`radio_station_previous_show` | ` $prev_shift` | `$time`| +|*radio_station_get_current_show*|`radio_station_current_schedule` | ` $show_shifts` | `$time`| +|*radio_station_get_previous_show*|`radio_station_previous_show` | ` $prev_shift` | `$time`| | |`radio_station_current_show` | ` $current_show` | `$time`| -|*radio_station_get_next_show*|`radio_station_next_show` | ` $next_show` | `$time`| -|*radio_station_get_next_shows*|`radio_station_next_show` | ` $next_show` | `$time`| +|*radio_station_get_current_playlist*|`radio_station_next_show` | ` $next_show` | `$time`| | |`radio_station_next_shows` | ` $next_shows` | `$limit`, `$show_shifts`| | |`radio_station_next_shows` | ` $next_shows` | `$limit`, `$show_shifts`| -|*radio_station_get_genres*|`radio_station_get_genres` | ` $genres` | `$args`| -|*radio_station_get_genre_shows*|`radio_station_show_genres_query_args` | ` $args` | `$genre`| -|*radio_station_get_language_shows*|`radio_station_show_languages_query_args` | ` $args` | `$language`| -|*radio_station_get_show_avatar_id*|`radio_station_show_avatar_id` | ` $avatar_id` | `$show_id`| -|*radio_station_get_show_avatar_url*|`radio_station_show_avatar_url` | ` $avatar_url` | `$show_id`| -|*radio_station_get_show_avatar*|`radio_station_show_avatar` | ` $avatar` | `$show_id`| +| |`radio_station_get_genres` | ` $genres` | `$args`| +|*radio_station_get_language_shows*|`radio_station_show_genres_query_args` | ` $args` | `$genre`| +| |`radio_station_show_languages_query_args` | ` $args` | `$language`| +|*radio_station_update_show_avatar*|`radio_station_show_avatar_post_types` | ` $post_types` | | +|*radio_station_get_show_avatar_url*|`radio_station_show_avatar_id` | ` $avatar_id` | `$show_id`| +| |`radio_station_show_avatar_size` | ` $size` | | +| |`radio_station_show_avatar_url` | ` $avatar_url` | `$show_id`, `$size`| +| |`radio_station_show_avatar_size` | ` $size` | | +| |`radio_station_show_avatar_output` | ` $avatar` | `$show_id`, `$size`| |*radio_station_get_stream_url*|`radio_station_stream_url` | ` $streaming_url` | | -|*radio_station_get_stream_formats*|`radio_station_stream_formats` | ` $formats` | | +|*radio_station_get_stream_formats*|`radio_station_fallback_url` | ` $fallback_url` | | +| |`radio_station_stream_formats` | ` $formats` | | |*radio_station_get_station_url*|`radio_station_station_url` | ` $station_url` | | -|*radio_station_get_station_image_url*|`radio_station_station_image_url` | ` $station_image` | | -|*radio_station_get_schedule_url*|`radio_station_schedule_url` | ` $schedule_url` | | -|*radio_station_get_api_url*|`radio_station_api_url` | ` $api_url` | | +|*radio_station_get_schedule_url*|`radio_station_station_image_url` | ` $station_image` | | +| |`radio_station_schedule_url` | ` $schedule_url` | | +| |`radio_station_api_url` | ` $api_url` | | |*radio_station_get_route_url*|`radio_station_route_slug_base` | ` 'radio'` | | | |`radio_station_route_slug_'.$route` | ` $route` | | |*radio_station_get_feed_url*|`radio_station_feed_slug_'.$feedname` | ` $feedname` | | -|*radio_station_get_host_url*|`radio_station_host_url` | ` $host_url` | `$host_id`| -|*radio_station_get_producer_url*|`radio_station_producer_url` | ` $producer_url` | `$producer_id`| -|*radio_station_patreon_button*|`radio_station_patreon_button` | ` $button` | `$page`| -|*radio_station_get_timezone_options*|`radio_station_get_timezone_options` | ` $options` | `$include_wp_timezone`| +| |`radio_station_host_url` | ` $host_url` | `$host_id`| +|*radio_station_get_upgrade_url*|`radio_station_producer_url` | ` $producer_url` | `$producer_id`| +|*radio_station_patreon_button_styles*|`radio_station_patreon_button` | ` $button` | `$page`| +|*radio_station_get_weekday*|`radio_station_get_timezone_options` | ` $options` | `$include_wp_timezone`| |*radio_station_get_schedule_weekdays*|`radio_station_schedule_weekday_start` | ` $weekstart` | | -|*radio_station_get_languages*|`radio_station_get_languages` | ` $translations` | | -|*radio_station_get_language_options*|`radio_station_get_language_options` | ` $languages` | `$include_wp_default`| -|*radio_station_trim_excerpt*|`radio_station_excerpt_length` | ` $length` | | -| |`radio_station_excerpt_more` | ` ' […]'` | | -| |`radio_station_trim_excerpt` | ` $excerpt` | `$raw_content`, `$length`, `$more`, `$permalink`| |**includes/class-current-show-widget.php**|||| -|*form*|`radio_station_current_show_widget_fields` | ` $fields` | `$this`, `$instance`| -|*update*|`radio_station_current_show_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| -|*widget*|`radio_station_current_show_widget_override` | ` $output` | `$args`, `$atts`| +|*update*|`radio_station_current_show_widget_fields` | ` $fields` | `$this`, `$instance`| +|*widget*|`radio_station_current_show_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +| |`radio_station_current_show_widget_atts` | ` $atts` | `$instance`| |**includes/class-upcoming-shows-widget.php**|||| -|*form*|`radio_station_upcoming_shows_widget_fields` | ` $fields` | `$this`, `$instance`| -|*update*|`radio_station_upcoming_shows_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| -|*widget*|`radio_station_upcoming_shows_widget_override` | ` $output` | `$args`, `$atts`| +|*update*|`radio_station_upcoming_shows_widget_fields` | ` $fields` | `$this`, `$instance`| +|*widget*|`radio_station_upcoming_shows_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +| |`radio_station_upcoming_shows_widget_atts` | ` $atts` | `$instance`| |**includes/class-current-playlist-widget.php**|||| -|*form*|`radio_station_playlist_widget_fields` | ` $fields` | `$this`, `$instance`| -|*update*|`radio_station_playlist_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| -|*widget*|`radio_station_current_playlist_widget_override` | ` $output` | `$args`, `$atts`| +|*update*|`radio_station_playlist_widget_fields` | ` $fields` | `$this`, `$instance`| +|*widget*|`radio_station_playlist_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +| |`radio_station_current_playlist_widget_atts` | ` $atts` | `$instance`| +| |`radio_station_current_playlist_widget_override` | ` $output` | `$args`, `$atts`| |**includes/class-radio-clock-widget.php**|||| -| |`radio_station_radio_clock_widget_override` | ` $output` | `$args`, `$atts`| +| |`radio_station_clock_widget_atts` | ` $atts` | `$instance`| +|**includes/class-radio-player-widget.php**|||| +|*form*|`radio_station_player_theme_options` | ` $options` | | +| |`radio_station_player_button_options` | ` $options` | | +|*update*|`radio_station_player_widget_fields` | ` $fields` | `$this`, `$instance`| +|*widget*|`radio_station_player_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| |**templates/master-schedule-table.php**|||| -| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'table'`| +| |`radio_station_schedule_start_time` | ` $start_time` | `'table'`, `$atts`| +| |`radio_station_show_time_separator` | ` $shifts_separator` | `'schedule-table'`| +| |`radio_station_time_separator` | ` $time_separator` | `'schedule-table'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-table'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-table'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'table'`| | |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'table'`| | |`radio_station_schedule_table_excerpt_length` | ` false` | | | |`radio_station_schedule_table_excerpt_more` | ` '[…]'` | | -| |`radio_station_schedule_table_info_order` | ` $infokeys` | | | |`radio_station_schedule_arrows` | ` $arrows` | `'table'`| +| |`radio_station_schedule_table_info_order` | ` $infokeys` | | +| |`radio_station_schedule_loader_control` | ` ''` | `'table'`, `'left'`| +| |`radio_station_schedule_loader_control` | ` ''` | `'table'`, `'right'`| | |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'table'`| | |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'table'`| | |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'table'`| | |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'table'`| +| |`radio_station_show_edit_link` | ` ''` | `$show_id`, `$shift['id']`, `'table'`| | |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'table'`| | |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'table'`| -| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'table'`, `$shift`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'table'`, `$shift`, `$tcount`| | |`radio_station_schedule_show_time_display` | ` true` | `$show_id`, `'table'`, `$shift`| | |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'table'`| | |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'table'`| @@ -307,24 +333,29 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'table'`| | |`radio_station_schedule_show_excerpt_display` | ` $excerpy` | `$show_id`, `'table'`| | |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'table'`| +| |`radio_station_schedule_add_link` | ` ''` | `$times`, `'table'`| |**templates/master-schedule-tabs.php**|||| -| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'tabs'`| +| |`radio_station_schedule_start_time` | ` $start_time` | `'tabs'`| +| |`radio_station_show_times_separator` | ` $shifts_separator` | `'schedule-tabs'`| +| |`radio_station_time_separator` | ` $time_separator` | `'schedule-tabs'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-tabs'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-tabs'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'tabs'`| | |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'tabs'`| | |`radio_station_schedule_tabs_excerpt_length` | ` false` | | | |`radio_station_schedule_tabs_excerpt_more` | ` '[…]'` | | -| |`radio_station_schedule_tabs_info_order` | ` $infokeys` | | | |`radio_station_schedule_arrows` | ` $arrows` | `'tabs'`| +| |`radio_station_schedule_tabs_info_order` | ` $infokeys` | | +| |`radio_station_schedule_loader_control` | ` ''` | `'tabs'`, `'left'`| | |`radio_station_schedule_tabs_avatar_position_start` | ` $avatar_position` | | | |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'tabs'`| +| |`radio_station_show_edit_link` | ` ''` | `$show_id`, `$shift['id']`, `'tabs'`| | |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'tabs'`| -| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'tabs'`, `$shift`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'tabs'`, `$shift`, `$tcount`| | |`radio_station_schedule_show_times_display` | ` true` | `$show_id`, `'tabs'`, `$shift`| | |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'tabs'`| @@ -335,15 +366,17 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'tabs'`| | |`radio_station_schedule_show_excerpt_display` | ` $excerpt` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_loader_control` | ` ''` | `'tabs'`, `'right'`| |**templates/master-schedule-legacy.php**|||| | |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'legacy'`| | |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show['id']`, `'legacy'`| | |`radio_station_schedule_show_link` | ` $show_link` | `$show['id']`, `'legacy'`| -| |`radio_station_schedule_show_time` | ` $times` | `$show['id']`, `'legacy'`| +| |`radio_station_schedule_show_time` | ` $times` | `$show['id']`, `'legacy'`, `false`, `false`| | |`radio_station_schedule_show_encore` | ` $encore` | `$show['id']`, `'legacy'`| | |`radio_station_schedule_show_file` | ` $show_file` | `$show['id']`, `'legacy'`| |**templates/master-schedule-list.php**|||| -| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'list'`| +| |`radio_station_show_times_separator` | ` $shifts_separator` | `'schedule-list'`| +| |`radio_station_time_separator` | ` $time_separator` | `'schedule-list'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-list'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-list'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'list'`| @@ -355,9 +388,10 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'list'`| | |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'list'`| | |`radio_station_schedule_show_title` | ` $title` | `$show_id`, `'list'`| +| |`radio_station_show_edit_link` | ` ''` | `$show_id`, `$shift['id']`, `'list'`| | |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'list'`| | |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'list'`| -| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'list'`, `$shift`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'list'`, `$shift`, `$tcount`| | |`radio_station_schedule_show_time_display` | ` true` | `$show_id`, `'list'`, `$shift`| | |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'list'`| | |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'list'`| @@ -369,7 +403,7 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'list'`| |**templates/single-playlist-content.php**|||| | |`radio_station_link_playlist_to_show_before` | ` $before` | `$post`, `$show`| -| |`radio_station_link_playlist_to_show_before` | ` $after` | `$post`, `$show`| +| |`radio_station_link_playlist_to_show_after` | ` $after` | `$post`, `$show`| |**templates/single-show-content.php**|||| | |`radio_station_show_title` | ` $show_title` | `$post_id`| | |`radio_station_show_header` | ` $header_id` | `$post_id`| @@ -389,6 +423,8 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_patreon` | ` $show_patreon` | `$post_id`| | |`radio_station_show_rss` | ` $show_rss` | `$post_id`| | |`radio_station_show_social_icons` | ` false` | `$post_id`| +| |`radio_station_time_format_start` | ` $start_data_format` | `'show-template'`, `$post_id`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'show-template'`, `$post_id`| | |`radio_station_show_website_title` | ` $title` | `$post_id`| | |`radio_station_show_home_icon` | ` $icon` | `$post_id`| | |`radio_station_show_phone_title` | ` $title` | `$post_id`| @@ -398,10 +434,8 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_rss_title` | ` $title` | `$post_id`| | |`radio_station_show_rss_icon` | ` $icon` | `$post_id`| | |`radio_station_show_page_icons` | ` $show_icons` | `$post_id`| -| |`radio_station_show_page_latest_limit` | ` $latest_limit` | `$post_id`| | |`radio_station_show_page_posts_limit` | ` false` | `$post_id`| | |`radio_station_show_page_playlist_limit` | ` false` | `$post_id`| -| |`radio_station_show_page_episodes` | ` false` | `$post_id`| | |`radio_station_show_jump_links` | ` 'yes'` | `$post_id`| | |`radio_station_show_avatar_size` | ` 'medium'` | `$post_id`, `'show-page'`| | |`radio_station_show_social_icons_display` | ` ''` | | @@ -422,9 +456,11 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_times_label` | ` $label` | `$post_id`| | |`radio_station_show_no_shifts_label` | ` $label` | `$post_id`| | |`radio_station_show_timezone_label` | ` $label` | `$post_id`| -| |`radio_station_time_format_start` | ` $start_data_format` | `'show-template'`, `$post_id`| -| |`radio_station_time_format_end` | ` $end_data_format` | `'show-template'`, `$post_id`| +| |`radio_station_show_times_separator` | ` $separator` | `'show-content'`| | |`radio_station_show_encore_label` | ` $label` | `$post_id`| +| |`radio_station_override_date_format` | ` 'j F'` | | +| |`radio_station_override_show_past_dates` | ` false` | | +| |`radio_station_show_times_separator` | ` $separator` | `'override-content'`| | |`radio_station_show_schedule_link_title` | ` $title` | `$post_id`| | |`radio_station_show_schedule_link_anchor` | ` $label` | `$post_id`| | |`radio_station_show_page_blocks` | ` $blocks` | `$post_id`| @@ -432,14 +468,11 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_less_label` | ` $label` | `$post_id`| | |`radio_station_show_description_label` | ` $label` | `$post_id`| | |`radio_station_show_description_anchor` | ` $anchor` | `$post_id`| -| |`radio_station_show_episodes_label` | ` $label` | `$post_id`| -| |`radio_station_show_episodes_anchor` | ` $anchor` | `$post_id`| -| |`radio_station_show_page_episodes_shortcode` | ` $shortcode` | `$post_id`| | |`radio_station_show_posts_label` | ` $label` | `$post_id`| -| |`radio_station_show_posts_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_posts_anchor` | ` $posts_label` | `$post_id`| | |`radio_station_show_page_posts_shortcode` | ` $shortcode` | `$post_id`| -| |`radio_station-show_playlists_label` | ` $label` | `$post_id`| -| |`radio_station_show_playlists_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_playlists_label` | ` $label` | `$post_id`| +| |`radio_station_show_playlists_anchor` | ` $playlist_label` | `$post_id`| | |`radio_station_show_page_playlists_shortcode` | ` $shortcode` | `$post_id`| | |`radio_station_show_page_sections` | ` $sections` | `$post_id`| | |`radio_station_show_header_size` | ` 'full'` | `$post_id`| @@ -448,13 +481,211 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_latest_posts_label` | ` $label` | `$post_id`| | |`radio_station_show_page_latest_shortcode` | ` $shortcode` | `$post_id`| | |`radio_station_show_page_section_order` | ` $section_order` | `$post_id`| +|**player/radio-player.php**|||| +|*radio_station_player_output*|`radio_station_player_output_args` | ` $args` | `$instance`| +| |`radio_station_player_station_image_tag` | ` $image` | `$args['image']`, `$args`, `$instance`| +|*radio_station_player_shortcode*|`radio_station_player_section_order` | ` $section_order` | `$args`| +| |`radio_station_player_control_order` | ` $control_order` | `$args`, `$instance`| +| |`radio_station_player_station_text_alt` | ` $station_text_alt` | `$args`, `$instance`| +| |`radio_station_player_show_text_alt` | ` $show_text_alt` | `$args`, `$instance`| +| |`radio_station_player_html` | ` $player` | `$args`, `$instance`| +| |`radio_station_player_default_title_display` | ` $title` | | +| |`radio_station_player_default_image_display` | ` $image` | | +| |`radio_station_player_default_script` | ` $script` | | +| |`radio_station_player_default_layout` | ` $layout` | | +| |`radio_station_player_default_volume` | ` $volume` | | +| |`radio_station_player_default_theme` | ` $theme` | | +| |`radio_station_player_default_buttons` | ` $buttons` | | +| |`radio_station_player_shortcode_attributes` | ` $atts` | | +| |`radio_station_player_default_title` | ` ''` | | +| |`radio_station_player_default_image` | ` ''` | | +|*radio_station_player_ajax*|`radio_station_player_output` | ` $override` | `$atts`| +| |`radio_station_player_atts` | ` $atts` | | +| |`radio_station_player_mediaelements_interface` | ` $html` | `$atts`, `$post_id`| +|*radio_station_player_enqueue_script*|`radio_station_player_pageload_script` | ` ''` | | +| |`radio_station_player_scripts` | ` $js` | | +| |`radio_station_player_fallbacks` | ` $fallbacks` | | +|*radio_station_player_enqueue_mediaelements*|`radio_station_player_mediaelement_settings` | ` $player_settings` | | +|*radio_station_player_script*|`radio_station_player_save_interval` | ` $save_interval` | | +| |`radio_station_player_jplayer_swf_path` | ` ''` | | +| |`radio_station_player_title` | ` $player_title` | | +| |`radio_station_player_image` | ` $player_image` | | +| |`radio_station_player_volume` | ` $player_volume ) )` | | +| |`radio_station_player_single` | ` $player_single` | | +| |`radio_station_player_fallbacks` | ` $fallbacks` | | +| |`radio_station_player_debug` | ` $debug` | | +|*radio_station_player_iframe*|`radio_station_player_data` | ` false` | `$station`| +|*radio_station_player_script_howler*|`radio_station_player_script_amplitude` | ` $js` | | +| |`radio_station_player_script_howler` | ` $js` | | +| |`radio_station_player_script_jplayer` | ` $js` | | ## [Pro] Pro Filter List Below is a list of filters that are available within [Radio Station Pro](https://radiostation.pro). -*TODO* +| File / *Function* | Filter | Value | Extra Args | +| - | - | +|**radio-station-pro.php**|||| +| |`radio_station_editor_relogin_script` | ` $js` | `$type`| +|*radio_station_pro_thickbox_loading_image*|`radio_station_thickbox_loading_icon_url` | ` $thickbox_loading_url` | | +| |`radio_station_thickbox_styles` | ` $css` | | +|*radio_station_pro_set_roles*|`radio_station_user_shows` | ` $shows` | `$type`, `$user_id`| +|**includes/rsp-data-feeds.php**|||| +|*radio_station_pro_register_rest_routes*|`radio_station_route_slug_base` | ` 'radio'` | | +| |`radio_station_route_slug_episodes` | ` false` | | +| |`radio_station_route_slug_hosts` | ` 'hosts'` | | +| |`radio_station_route_slug_producers` | ` 'producers'` | | +|*radio_station_pro_route_episodes*|`radio_station_route_episodes` | ` $episode_list` | | +| |`radio_station_feed_hosts` | ` $host_list` | | +|*radio_station_pro_route_producers*|`radio_station_route_producers` | ` $producer_list` | | +|**includes/rsp-episodes.php**|||| +|*radio_station_pro_register_taxonomies*|`radio_station_topic_taxonomy_args` | ` $args` | | +|*radio_station_pro_set_data_slug*|`radio_station_guest_taxonomy_args` | ` $args` | | +| |`radio_station_episode_url` | ` $episode_url` | `$episode_id`| +|*radio_station_pro_get_show_episodes*|`radio_station_episode_avatar_output` | ` $avatar` | `$episode_id`| +| |`radio_station_episode_avatar_id` | ` $avatar_id` | `$episode_id`| +|*radio_station_pro_get_show_page_episodes*|`radio_station_show_page_episodes_limit` | ` false` | `$post_id`| +|**includes/rsp-episodes-admin.php**|||| +|*radio_station_pro_add_episodes_submenu*|`radio_station_metabox_position` | ` 'rstop'` | `'profiles'`| +|*radio_episode_type*|`radio_station_episode_edit_styles` | ` $css` | | +| |`radio_station_update_segments` | ` false` | `$post_id`| +|**includes/rsp-import-export.php**|||| +|*radio_station_create_show_image_archive*|`radio_station_valid_with_paragraph_tags` | ` true` | | +|**includes/rsp-metadata.php**|||| +|*radio_station_pro_get_stream_metadata*|`radio_station_stream_metadata_types` | ` $data_types` | | +|*radio_station_pro_broadcast_data*|`radio_station_stream_metadata` | ` $np` | `$stream`| +| |`radio_station_stream_metadata_url` | ` $metadata` | `$broadcast`| +| |`radio_station_metadata_cache_interval` | ` 5` | | +| |`radio_station_current_song` | ` $currentsong` | | +|*radio_station_pro_icy_stream_title*|`radio_station_icy_metadata_method` | ` $method` | | +|*radio_station_pro_icy_song_info*|`radio_station_pro_metadata` | ` $metadata` | `$stream`, `'shoutcast1'`| +|*radio_station_pro_shoutcast2_current_song*|`radio_station_pro_metadata` | ` $metadata` | `$stream`, `'shoutcast2'`| +| |`radio_station_stream_mount_index` | ` $mount` | `$stream`| +|**includes/rsp-player.php**|||| +|*radio_station_pro_player_scripts*|`radio_station_player_bar_metadata_cycle` | ` $metadata_cycle` | | +| |`radio_station_pro_scripts` | ` $js` | | +| |`radio_station_player_bar_atts` | ` $atts` | | +|**includes/rsp-post-types.php**|||| +| |`radio_station_pro_post_type_episodes` | ` $episodes` | | +|**includes/rsp-profiles.php**|||| +|*radio_station_pro_get_profile_posts*|`radio_station_'.$profile_type.'_'.$data_type` | ` $results` | `$author_id`, `$args`| +| |`radio_station_show_avatar_output` | ` $avatar` | `$profile_id`, `$type`| +| |`radio_station_profile_avatar_id` | ` $avatar_id` | `$profile_id`, `$type`| +| |`radio_station_'.$type.'_hosts_label` | ` $label` | `$post_id`| +| |`radio_station_'.$type.'_hosts_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_'.$type.'_page_hosts_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station_'.$type.'_producers_label` | ` $label` | `$post_id`| +| |`radio_station_'.$type.'_producers_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_'.$type.'_page_producers_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station_'.$type.'_team_label` | ` $label` | `$post_id`| +| |`radio_station_'.$type.'_team_anchor` | ` $anchor` | `$post_id`| +|**includes/rsp-profiles-admin.php**|||| +|*radio_station_pro_add_profile_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'profiles'`| +|*radio_station_pro_add_image_metaboxes*|`radio_station_profile_edit_styles` | ` $css` | | +|**includes/rsp-schedule-editor.php**|||| +|*radio_station_pro_schedule_editor_menu*|`radio_station_pro_view_images` | ` false` | | +| |`radio_station_pro_schedule_editor_atts` | ` $atts` | | +|**includes/rsp-schedule-views.php**|||| +|*radio_station_pro_schedule_loader_control*|`radio_station_schedule_arrows` | ` $arrows` | `$view`| +| |`master_schedule_grid_view` | ` $html` | `$atts`| +| |`master_schedule_calendar_view` | ` $html` | `$atts`| +|*radio_shift_grid*|`radio_station_pro_master_schedule_grid_js` | ` $js` | | +|*radio_calendar_show_highlight*|`radio_station_pro_master_schedule_calendar_js` | ` $js` | | +|*radio_slide_check*|`radio_station_show_slider_script` | ` $js` | | +| |`radio_station_pro_view_order` | ` $view_order` | `$atts`| +|*radio_switch_view*|`radio_station_pro_view_images` | ` false` | `$atts`| +|**includes/rsp-shortcodes.php**|||| +|*radio_station_pro_archive_list_shortcode*|`radio_station_'.$type.'_archive_post_args` | ` $args` | | +| |`radio_station_'.$type.'_archive_posts` | ` $archive_posts` | | +| |`radio_station_time_separator` | ` $time_separator` | `$post_type.'-archive'`| +| |`radio_station_time_format_start` | ` $start_data_format` | `$post_type.'-archive'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `$post_type.'-archive'`, `$atts`| +| |`radio_station_archive_shortcode_no_records` | ` $message` | `$post_type`, `$atts`| +| |`radio_station_archive_'.$type.'_list_excerpt_length` | ` false` | | +| |`radio_station_archive_'.$type.'_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_archive_shortcode_info_order` | ` $infokeys` | `$post_type`, `$atts`| +| |`radio_station_'.$type.'_archive_avatar_size` | ` 'thumbnail'` | `$post_id`, `$type.'-archive'`| +|*radio_station_pro_profile_list_shortcode*|`radio_station_archive_shortcode_meta` | ` ''` | `$post_id`, `$post_type`, `$atts`| +| |`radio_station_'.$type.'_archive_content` | ` $post_content` | `$post_id`| +| |`radio_station_'.$type.'_archive_excerpt` | ` $excerpt` | `$post_id`| +| |`radio_station_archive_shortcode_info_custom` | ` ''` | `$post_id`, `$post_type`, `$atts`| +| |`radio_station_archive_shortcode_info` | ` $info` | `$post_id`, `$post_type`, `$atts`| +| |`radio_station_'.$type.'_archive_list` | ` $list` | `$atts`| +| |`radio_station_'.$profile_type.'_'.$type.'_list_excerpt_length` | ` false` | | +| |`radio_station_'.$profile_type.'_'.$type.'_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_'.$profile_type.'_'.$type.'_content` | ` $post_content` | `$post_id`| +| |`radio_station_'.$profile_type.'_'.$type.'_excerpt` | ` $excerpt` | `$post_id`| +|**includes/rsp-social.php**|||| +|*radio_station_pro_get_social_icon*|`radio_station_social_icons_services` | ` $services` | | +| |`radio_station_social_icon_url` | ` $icon_url` | `$service`| +|*radio_station_pro_social_icons_inputs*|`radio_station_social_icon_dir` | ` get_stylesheet_directory() . '/images/'` | | +| |`radio_station_social_icon_path` | ` get_stylesheet_directory_uri() . '/images/'` | | +| |`radio_station_social_icon_output` | ` $html` | `$service`| +|*radio_social_first_last*|`radio_station_pro_social_icon_script` | ` $js` | | +|*radio_station_pro_social_icons_save*|`radio_station_social_icon_edit_styles` | ` $css` | | +|**includes/rsp-timezones.php**|||| +|*radio_station_pro_timezone_resources*|`radio_station_timezone_switcher_styles` | ` $css` | | +|**templates/master-schedule-grid.php**|||| +| |`radio_station_schedule_start_time` | ` $start_time` | `'grid'`| +| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'schedule-grid'`| +| |`radio_station_time_separator` | ` $time_separator` | `'schedule-grid'`| +| |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-grid'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-grid'`, `$atts`| +| |`radio_station_schedule_start_day` | ` false` | `'grid'`| +| |`radio_station_schedule_show_avatar_size` | ` $avatar_size` | `'grid'`| +| |`radio_station_schedule_arrows` | ` $arrows` | `'grid'`| +| |`radio_station_schedule_grid_info_order` | ` $infokeys` | | +| |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'grid'`| +| |`radio_station_show_edit_link` | ` ''` | `$show_id`, `$shift['id']`, `'grid'`| +| |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'grid'`, `$shift`, `$tcount`| +| |`radio_station_schedule_show_times_display` | ` true` | `$show_id`, `'grid'`, `$shift`| +| |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_file_anchor` | ` $anchor` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_file_display` | ` $file` | `$show_file`, `$show_id`, `'grid'`| +| |`radio_station_schedule_show_genres` | ` $genres` | `$show_id`, `'grid'`| +| |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'grid'`| +| |`radio_station_schedule_loader_control` | ` ''` | `'grid'`, `'left'`| +| |`radio_station_schedule_loader_control` | ` ''` | `'grid'`, `'right'`| +| |`radio_station_master_schedule_styles_grid` | ` $css` | | +|**templates/master-schedule-calendar.php**|||| +| |`radio_station_schedule_start_time` | ` $start_time` | `'calendar'`| +| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'schedule-calendar'`| +| |`radio_station_time_separator` | ` $time_separator` | `'schedule-calendar'`| +| |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-calendar'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-calendar'`, `$atts`| +| |`radio_station_schedule_start_day` | ` false` | `'calendar'`| +| |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'calendar'`| +| |`radio_station_schedule_tabs_excerpt_length` | ` false` | | +| |`radio_station_schedule_tabs_excerpt_more` | ` '[…]'` | | +| |`radio_station_schedule_arrows` | ` $arrows` | `'calendar'`| +| |`radio_station_schedule_calendar_info_order` | ` $infokeys` | | +| |`radio_station_schedule_add_link` | ` ''` | `$times`, `'calendar'`| +| |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'calendar'`| +| |`radio_station_show_edit_link` | ` ''` | `$show_id`, `$shift['id']`, `'calendar'`| +| |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'calendar'`, `$shift`, `$tcount`| +| |`radio_station_schedule_show_times_display` | ` true` | `$show_id`, `'calendar'`, `$shift`| +| |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_file_anchor` | ` $anchor` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_file_display` | ` $file` | `$show_file`, `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_genres` | ` $genres` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'calendar'`| +| |`radio_station_schedule_show_excerpt_display` | ` $excerpt` | `$show_id`, `'calendar'`| diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 309e54c..fd67617 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -1,18 +1,40 @@ \n" "Language-Team: LANGUAGE \n" -#: includes/class-dj-upcoming-widget.php:13 -msgid "Display the upcoming Shows." +#: help/contextual-help-config.php:49 includes/extra-strings.php:130 +msgid "Import" msgstr "" -#: includes/class-dj-upcoming-widget.php:15 -msgid "(Radio Station) Upcoming Shows" +#: help/contextual-help-config.php:60 includes/extra-strings.php:134 +#: templates/playlist-export.php:140 +msgid "Export" msgstr "" -#: includes/class-dj-upcoming-widget.php:40 includes/class-dj-widget.php:41 -#: includes/class-playlist-widget.php:33 -msgid "Title" +#: help/contextual-help-config.php:71 +msgid "YAML format " msgstr "" -#: includes/class-dj-upcoming-widget.php:49 includes/class-dj-widget.php:50 -msgid "Link the title to the Show page" +#: help/contextual-help-config.php:82 +msgid "show-schedule:" msgstr "" -#: includes/class-dj-upcoming-widget.php:58 includes/class-dj-widget.php:59 -msgid "Above" +#: includes/class-current-playlist-widget.php:15 +msgid "Display currently playing playlist." msgstr "" -#: includes/class-dj-upcoming-widget.php:59 includes/class-dj-widget.php:60 -msgid "Left" +#: includes/class-current-playlist-widget.php:17 +msgid "(Radio Station) Now Playing List" msgstr "" -#: includes/class-dj-upcoming-widget.php:60 includes/class-dj-widget.php:61 -msgid "Right" +#: includes/class-current-playlist-widget.php:48 +#: includes/class-current-show-widget.php:55 +#: includes/class-current-show-widget.php:171 +#: includes/class-radio-clock-widget.php:45 +#: includes/class-upcoming-shows-widget.php:52 +#: includes/class-upcoming-shows-widget.php:147 includes/extra-strings.php:285 +msgid "Default" msgstr "" -#: includes/class-dj-upcoming-widget.php:61 includes/class-dj-widget.php:62 -msgid "Below" +#: includes/class-current-playlist-widget.php:49 +#: includes/class-current-show-widget.php:56 +#: includes/class-upcoming-shows-widget.php:53 includes/extra-strings.php:286 +msgid "On" msgstr "" -#: includes/class-dj-upcoming-widget.php:68 includes/class-dj-widget.php:70 -msgid "Show Title Position (relative to Avatar)" +#: includes/class-current-playlist-widget.php:50 +#: includes/class-current-show-widget.php:57 +#: includes/class-upcoming-shows-widget.php:54 includes/extra-strings.php:5 +#: radio-station.php:1207 +msgid "Off" msgstr "" -#: includes/class-dj-upcoming-widget.php:76 -msgid "Display Show Avatars" +#: includes/class-current-playlist-widget.php:52 +#: includes/class-current-show-widget.php:59 +#: includes/class-upcoming-shows-widget.php:56 +msgid "AJAX Load Widget?" msgstr "" -#: includes/class-dj-upcoming-widget.php:82 includes/class-dj-widget.php:84 -msgid "Avatar Width" +#: includes/class-current-playlist-widget.php:58 +#: includes/class-current-show-widget.php:65 +#: includes/class-radio-clock-widget.php:37 +#: includes/class-upcoming-shows-widget.php:62 includes/extra-strings.php:147 +#: includes/post-types-admin.php:2971 +msgid "Title" msgstr "" -#: includes/class-dj-upcoming-widget.php:85 -msgid "Width of Show Avatars (in pixels, default 75px)" +#: includes/class-current-playlist-widget.php:66 +msgid "Link to Playlist?" msgstr "" -#: includes/class-dj-upcoming-widget.php:92 -msgid "Display names of the DJs on the show" +#: includes/class-current-playlist-widget.php:73 +msgid "Show Song Title" msgstr "" -#: includes/class-dj-upcoming-widget.php:100 includes/class-dj-widget.php:102 -msgid "Link DJ names to author pages" +#: includes/class-current-playlist-widget.php:80 +msgid "Show Artist Name" msgstr "" -#: includes/class-dj-upcoming-widget.php:106 -msgid "No Additional Schedules" +#: includes/class-current-playlist-widget.php:87 +msgid " Show Album Name" msgstr "" -#: includes/class-dj-upcoming-widget.php:109 includes/class-dj-widget.php:143 -msgid "If no Show is scheduled for the current time, display this text." +#: includes/class-current-playlist-widget.php:94 +msgid "Show Record Label Name" msgstr "" -#: includes/class-dj-upcoming-widget.php:116 includes/class-dj-widget.php:110 -msgid "Display schedule info for this show" +#: includes/class-current-playlist-widget.php:101 +msgid "Show DJ Comments" msgstr "" -#: includes/class-dj-upcoming-widget.php:122 -msgid "Limit" +#: includes/class-current-playlist-widget.php:108 +msgid "Hide Widget if Empty" msgstr "" -#: includes/class-dj-upcoming-widget.php:125 -msgid "Number of upcoming Shows to display." +#: includes/class-current-playlist-widget.php:115 +#: includes/class-current-show-widget.php:183 +#: includes/class-upcoming-shows-widget.php:159 +msgid "Display Countdown Timer" msgstr "" -#: includes/class-dj-upcoming-widget.php:129 includes/class-dj-widget.php:147 -msgid "Time Format" +#: includes/class-current-show-widget.php:15 +msgid "The currently playing on-air Show." msgstr "" -#: includes/class-dj-upcoming-widget.php:132 includes/class-dj-widget.php:150 -msgid "12 Hour" +#: includes/class-current-show-widget.php:17 +msgid "(Radio Station) Current Show On-Air" msgstr "" -#: includes/class-dj-upcoming-widget.php:134 includes/class-dj-widget.php:152 -msgid "24 Hour" +#: includes/class-current-show-widget.php:30 +msgid "No Show scheduled for this time." msgstr "" -#: includes/class-dj-upcoming-widget.php:138 -msgid "Choose time format for displayed schedules." +#: includes/class-current-show-widget.php:73 +#: includes/class-upcoming-shows-widget.php:70 +msgid "Link the title to the Show page" +msgstr "" + +#: includes/class-current-show-widget.php:82 +#: includes/class-upcoming-shows-widget.php:79 +msgid "Above" +msgstr "" + +#: includes/class-current-show-widget.php:83 +#: includes/class-upcoming-shows-widget.php:80 +msgid "Left" +msgstr "" + +#: includes/class-current-show-widget.php:84 +#: includes/class-upcoming-shows-widget.php:81 +msgid "Right" msgstr "" -#: includes/class-dj-widget.php:12 -msgid "The current on-air DJ." +#: includes/class-current-show-widget.php:85 +#: includes/class-upcoming-shows-widget.php:82 +msgid "Below" msgstr "" -#: includes/class-dj-widget.php:14 -msgid "(Radio Station) Show/DJ On-Air" +#: includes/class-current-show-widget.php:92 +#: includes/class-upcoming-shows-widget.php:88 +msgid "Show Title Position (relative to Avatar)" msgstr "" -#: includes/class-dj-widget.php:78 +#: includes/class-current-show-widget.php:99 msgid "Display Show Avatar" msgstr "" -#: includes/class-dj-widget.php:87 +#: includes/class-current-show-widget.php:105 +#: includes/class-upcoming-shows-widget.php:101 +msgid "Avatar Width" +msgstr "" + +#: includes/class-current-show-widget.php:108 msgid "Width of Show Avatar (in pixels, default full width)" msgstr "" -#: includes/class-dj-widget.php:94 +#: includes/class-current-show-widget.php:114 msgid "Display names of the DJs on the Show" msgstr "" -#: includes/class-dj-widget.php:118 +#: includes/class-current-show-widget.php:121 +msgid "Link Host/DJ names to author pages" +msgstr "" + +#: includes/class-current-show-widget.php:128 +#: includes/class-upcoming-shows-widget.php:132 +msgid "Display schedule info for this show" +msgstr "" + +#: includes/class-current-show-widget.php:135 msgid "Display multiple schedules (if show airs more than once per week)" msgstr "" -#: includes/class-dj-widget.php:126 +#: includes/class-current-show-widget.php:142 +msgid "Display encore presentation text for Show" +msgstr "" + +#: includes/class-current-show-widget.php:149 msgid "Display description of show" msgstr "" -#: includes/class-dj-widget.php:134 +#: includes/class-current-show-widget.php:156 msgid "Display link to show's playlist" msgstr "" -#: includes/class-dj-widget.php:140 +#: includes/class-current-show-widget.php:162 msgid "No Show Display Text" msgstr "" -#: includes/class-dj-widget.php:156 -msgid "Choose time format for displayed schedules" +#: includes/class-current-show-widget.php:165 +msgid "Text to display if no Show is scheduled for the current time." msgstr "" -#: includes/class-playlist-widget.php:12 -msgid "Display currently playling playlist." +#: includes/class-current-show-widget.php:169 +#: includes/class-radio-clock-widget.php:43 +#: includes/class-upcoming-shows-widget.php:145 +msgid "Time Format" msgstr "" -#: includes/class-playlist-widget.php:14 -msgid "(Radio Station) Now Playing List" +#: includes/class-current-show-widget.php:172 +#: includes/class-radio-clock-widget.php:46 +#: includes/class-upcoming-shows-widget.php:148 +msgid "12 Hour" msgstr "" -#: includes/class-playlist-widget.php:42 -msgid "Show Song Title" +#: includes/class-current-show-widget.php:173 +#: includes/class-radio-clock-widget.php:47 +#: includes/class-upcoming-shows-widget.php:149 +msgid "24 Hour" msgstr "" -#: includes/class-playlist-widget.php:50 -msgid "Show Artist Name" +#: includes/class-current-show-widget.php:177 +#: includes/class-radio-clock-widget.php:51 +msgid "Choose time format for displayed schedules" msgstr "" -#: includes/class-playlist-widget.php:58 -msgid " Show Album Name" +#: includes/class-radio-clock-widget.php:14 +msgid "Display current radio and user times." msgstr "" -#: includes/class-playlist-widget.php:66 -msgid "Show Record Label Name" +#: includes/class-radio-clock-widget.php:16 +msgid "(Radio Station) Radio Clock" msgstr "" -#: includes/class-playlist-widget.php:74 -msgid "Show DJ Comments" +#: includes/class-radio-clock-widget.php:23 +msgid "Radio Clock" msgstr "" -#: includes/data-feeds.php:769 -msgid "Requested Show was not found." +#: includes/class-radio-clock-widget.php:57 +msgid "Include seconds display." msgstr "" -#: includes/data-feeds.php:770 -msgid "No Requested Shows were found." +#: includes/class-radio-clock-widget.php:62 +msgid "Day Display" msgstr "" -#: includes/data-feeds.php:771 includes/post-types-admin.php:535 -msgid "No Shows were found." +#: includes/class-radio-clock-widget.php:64 +#: includes/class-radio-clock-widget.php:83 +msgid "Full" msgstr "" -#: includes/data-feeds.php:813 -msgid "Requested Genre was not found." +#: includes/class-radio-clock-widget.php:65 +#: includes/class-radio-clock-widget.php:84 +msgid "Short" msgstr "" -#: includes/data-feeds.php:814 -msgid "No Requested Genres were found." +#: includes/class-radio-clock-widget.php:66 +#: includes/class-radio-clock-widget.php:85 includes/post-types-admin.php:4790 +#: radio-station.php:849 +msgid "None" msgstr "" -#: includes/data-feeds.php:815 -msgid "No Genres were found." +#: includes/class-radio-clock-widget.php:70 +msgid "Display day with clock times." msgstr "" -#: includes/data-feeds.php:858 -msgid "Requested Language was not found." +#: includes/class-radio-clock-widget.php:76 +msgid "Include date display." msgstr "" -#: includes/data-feeds.php:859 -msgid "No Requested Languages were found." +#: includes/class-radio-clock-widget.php:81 +msgid "Month Display" msgstr "" -#: includes/data-feeds.php:860 -msgid "No Languages were found." +#: includes/class-radio-clock-widget.php:89 +msgid "Display month with clock times." msgstr "" -#: includes/data-feeds.php:896 -msgid "Error 404 Not Found" +#: includes/class-radio-clock-widget.php:95 +msgid "Include timezone display." msgstr "" -#: includes/data-feeds.php:897 -msgid "The requested data could not be found." +#: includes/class-radio-player-widget.php:16 +msgid "Radio Station Stream Player." msgstr "" -#: includes/master-schedule.php:260 radio-station-admin.php:136 -#: templates/master-schedule-tabs.php:176 -msgid "Genres" +#: includes/class-radio-player-widget.php:18 +msgid "(Radio Station) Stream Player" msgstr "" -#: includes/master-schedule.php:276 -msgid "Click to toggle Highlight of Shows with this Genre." +#: includes/class-radio-player-widget.php:47 +msgid "Stream or File URL" msgstr "" -#: includes/post-types-admin.php:90 -msgid "Show Language" +#: includes/class-radio-player-widget.php:57 +msgid "Widget Title" msgstr "" -#: includes/post-types-admin.php:126 -msgid "Main Radio Language" +#: includes/class-radio-player-widget.php:66 +msgid "Player Station Text" msgstr "" -#: includes/post-types-admin.php:130 -msgid "Select below if Show language(s) differ." +#: includes/class-radio-player-widget.php:77 +#: includes/class-radio-player-widget.php:94 +#: includes/class-radio-player-widget.php:130 +#: includes/class-radio-player-widget.php:149 +msgid "Plugin Setting" msgstr "" -#: includes/post-types-admin.php:158 -msgid "Remove Language" +#: includes/class-radio-player-widget.php:78 radio-station.php:439 +msgid "Display Station Image" msgstr "" -#: includes/post-types-admin.php:166 -msgid "Select Language" +#: includes/class-radio-player-widget.php:79 +msgid "Do Not Display Image" msgstr "" -#: includes/post-types-admin.php:180 -msgid "Click on a Language to Add it." +#: includes/class-radio-player-widget.php:86 +msgid "Whether to display Station Image in Player." msgstr "" -#: includes/post-types-admin.php:258 -msgid "Playlist Entries" +#: includes/class-radio-player-widget.php:95 radio-station.php:457 +#: radio-station.php:475 +msgid "Amplitude" msgstr "" -#: includes/post-types-admin.php:284 includes/post-types-admin.php:659 -#: includes/shortcodes.php:1304 templates/legacy/single-playlist.php:45 -#: templates/single-playlist-content.php:11 -msgid "Artist" +#: includes/class-radio-player-widget.php:96 radio-station.php:456 +#: radio-station.php:474 +msgid "Howler" msgstr "" -#: includes/post-types-admin.php:285 includes/post-types-admin.php:658 -#: includes/shortcodes.php:1296 templates/legacy/single-playlist.php:46 -#: templates/single-playlist-content.php:12 -msgid "Song" +#: includes/class-radio-player-widget.php:97 radio-station.php:455 +#: radio-station.php:473 +msgid "jPlayer" msgstr "" -#: includes/post-types-admin.php:286 includes/shortcodes.php:1311 -#: templates/legacy/single-playlist.php:47 -#: templates/single-playlist-content.php:13 -msgid "Album" +#: includes/class-radio-player-widget.php:104 +msgid "Player Script to load by default." msgstr "" -#: includes/post-types-admin.php:287 templates/legacy/single-playlist.php:48 -#: templates/single-playlist-content.php:14 -msgid "Record Label" +#: includes/class-radio-player-widget.php:113 +msgid "Vertical (Stacked)" msgstr "" -#: includes/post-types-admin.php:311 includes/post-types-admin.php:353 -#: includes/shortcodes.php:1325 templates/single-playlist-content.php:15 -msgid "Comments" +#: includes/class-radio-player-widget.php:114 +msgid "Horizontal (Inline)" msgstr "" -#: includes/post-types-admin.php:314 includes/post-types-admin.php:354 -msgid "New" +#: includes/class-radio-player-widget.php:121 +msgid "Layout Style for Widget Area (wide or tall)" msgstr "" -#: includes/post-types-admin.php:318 includes/post-types-admin.php:355 -#: includes/post-types-admin.php:660 -msgid "Status" +#: includes/class-radio-player-widget.php:131 radio-station.php:489 +msgid "Light" msgstr "" -#: includes/post-types-admin.php:321 includes/post-types-admin.php:356 -msgid "Queued" +#: includes/class-radio-player-widget.php:132 radio-station.php:490 +msgid "Dark" msgstr "" -#: includes/post-types-admin.php:323 includes/post-types-admin.php:357 -msgid "Played" +#: includes/class-radio-player-widget.php:140 +msgid "Player Theme Style" msgstr "" -#: includes/post-types-admin.php:327 includes/post-types-admin.php:359 -#: includes/post-types-admin.php:1262 includes/post-types-admin.php:1389 -msgid "Remove" +#: includes/class-radio-player-widget.php:150 +msgid "Circular" msgstr "" -#: includes/post-types-admin.php:337 -msgid "Add Entry" +#: includes/class-radio-player-widget.php:151 +msgid "Rounded" msgstr "" -#: includes/post-types-admin.php:392 includes/post-types-admin.php:395 -msgid "Schedule" +#: includes/class-radio-player-widget.php:152 +msgid "Square" msgstr "" -#: includes/post-types-admin.php:406 includes/post-types-admin.php:409 -msgid "Publish" +#: includes/class-radio-player-widget.php:160 +msgid "Layout Style for Widget Area" msgstr "" -#: includes/post-types-admin.php:423 -msgid "Submit for Review" +#: includes/class-radio-player-widget.php:167 radio-station.php:594 +msgid "Player Start Volume" msgstr "" -#: includes/post-types-admin.php:426 includes/post-types-admin.php:441 -msgid "Update Playlist" +#: includes/class-radio-player-widget.php:176 +msgid "Use this as the default Player instance." msgstr "" -#: includes/post-types-admin.php:440 -msgid "Update" +#: includes/class-upcoming-shows-widget.php:15 +msgid "Display the upcoming Shows." msgstr "" -#: includes/post-types-admin.php:459 -msgid "Linked Show" +#: includes/class-upcoming-shows-widget.php:17 +msgid "(Radio Station) Upcoming Shows" msgstr "" -#: includes/post-types-admin.php:538 -msgid "You are not assigned to any Shows." +#: includes/class-upcoming-shows-widget.php:95 +msgid "Display Show Avatars" msgstr "" -#: includes/post-types-admin.php:546 -msgid "Unassigned" +#: includes/class-upcoming-shows-widget.php:104 +msgid "Width of Show Avatars (in pixels, default 75px)" msgstr "" -#: includes/post-types-admin.php:630 includes/post-types.php:49 -msgid "Show" +#: includes/class-upcoming-shows-widget.php:110 +msgid "Display names of the DJs on the show" msgstr "" -#: includes/post-types-admin.php:631 -msgid "Tracks" +#: includes/class-upcoming-shows-widget.php:117 +msgid "Link DJ names to author pages" msgstr "" -#: includes/post-types-admin.php:632 -msgid "Track List" +#: includes/class-upcoming-shows-widget.php:123 +msgid "No Additional Schedules Text" msgstr "" -#: includes/post-types-admin.php:654 -msgid "Show/Hide Tracklist" +#: includes/class-upcoming-shows-widget.php:126 +msgid "If no Show is scheduled for the current time, display this text." msgstr "" -#: includes/post-types-admin.php:716 -msgid "Related to Show" +#: includes/class-upcoming-shows-widget.php:138 +msgid "Limit" msgstr "" -#: includes/post-types-admin.php:758 -msgid "No Shows to Select." +#: includes/class-upcoming-shows-widget.php:141 +msgid "Number of upcoming Shows to display." msgstr "" -#: includes/post-types-admin.php:819 -msgid "Show Information" +#: includes/class-upcoming-shows-widget.php:153 +msgid "Choose time format for displayed schedules." msgstr "" -#: includes/post-types-admin.php:848 -msgid "Active" +#: includes/data-feeds.php:1341 +msgid "Error 400 No Requested Data" msgstr "" -#: includes/post-types-admin.php:850 -msgid "Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)" +#: includes/data-feeds.php:1342 +msgid "The requested data could not be found." msgstr "" -#: includes/post-types-admin.php:852 -msgid "Website Link" +#: includes/extra-strings.php:2 radio-station.php:1191 +msgid "Team Archive Page" msgstr "" -#: includes/post-types-admin.php:855 -msgid "DJ / Host Email" +#: includes/extra-strings.php:3 radio-station.php:1195 +msgid "Select the Page for displaying the Team archive list." msgstr "" -#: includes/post-types-admin.php:858 -msgid "Latest Audio File" +#: includes/extra-strings.php:4 radio-station.php:818 radio-station.php:1102 +#: radio-station.php:1135 radio-station.php:1168 radio-station.php:1204 +#: radio-station.php:1235 radio-station.php:1270 +msgid "Automatic Display" msgstr "" -#: includes/post-types-admin.php:868 -msgid "Show DJ(s) / Host(s)" +#: includes/extra-strings.php:6 radio-station.php:1208 +msgid "List" msgstr "" -#: includes/post-types-admin.php:872 includes/post-types-admin.php:988 -msgid "Show Producer(s)" +#: includes/extra-strings.php:7 +msgid "Grid" msgstr "" -#: includes/post-types-admin.php:885 -msgid "Toggle panel: %s" +#: includes/extra-strings.php:8 radio-station.php:1213 +msgid "Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:" msgstr "" -#: includes/post-types-admin.php:912 -msgid "DJs / Hosts" +#: includes/extra-strings.php:9 radio-station.php:895 +msgid "Time Spaced Grid" msgstr "" -#: includes/post-types-admin.php:975 includes/post-types-admin.php:1044 -msgid "Ctrl-Click selects multiple." +#: includes/extra-strings.php:10 radio-station.php:898 +msgid "Enable Grid View option for equalized time spacing and background imsges." msgstr "" -#: includes/post-types-admin.php:1058 -msgid "Show Schedule" +#: includes/extra-strings.php:11 radio-station.php:675 +msgid "Player Bar Height" msgstr "" -#: includes/post-types-admin.php:1162 includes/post-types-admin.php:1324 -msgid "Day" +#: includes/extra-strings.php:12 radio-station.php:679 +msgid "Set the height of the Sitewide Player Bar in pixels." msgstr "" -#: includes/post-types-admin.php:1178 includes/post-types-admin.php:1337 -#: includes/post-types-admin.php:2168 includes/post-types-admin.php:2343 -msgid "Start Time" +#: includes/extra-strings.php:13 radio-station.php:765 +msgid "Display Current Show" msgstr "" -#: includes/post-types-admin.php:1210 includes/post-types-admin.php:1359 -#: includes/post-types-admin.php:2198 includes/post-types-admin.php:2344 -msgid "End Time" +#: includes/extra-strings.php:14 radio-station.php:770 +msgid "Display the Current Show in the Player Bar." msgstr "" -#: includes/post-types-admin.php:1246 includes/post-types-admin.php:1381 -msgid "Encore" +#: includes/extra-strings.php:15 radio-station.php:778 +msgid "Display Now Playing" msgstr "" -#: includes/post-types-admin.php:1256 includes/post-types-admin.php:1385 -msgid "Disabled" +#: includes/extra-strings.php:16 +msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist" msgstr "" -#: includes/post-types-admin.php:1271 -msgid "Shift Conflicts" +#: includes/extra-strings.php:17 radio-station.php:792 +msgid "Metadata URL" msgstr "" -#: includes/post-types-admin.php:1297 -msgid "Warning! Show Shift Conflicts were detected!" +#: includes/extra-strings.php:18 radio-station.php:796 +msgid "Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location." msgstr "" -#: includes/post-types-admin.php:1298 -msgid "Please note that Shifts with conflicts are automatically disabled upon saving." +#: includes/extra-strings.php:19 radio-station.php:728 +msgid "Page Load Timeout" msgstr "" -#: includes/post-types-admin.php:1299 -msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." +#: includes/extra-strings.php:20 +msgid "Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display." msgstr "" -#: includes/post-types-admin.php:1300 -msgid "Then you can uncheck the shift Disable box and Update to re-enable the Shift." +#: includes/extra-strings.php:21 +msgid "Radio Station needs to be installed and activated to use %s." msgstr "" -#: includes/post-types-admin.php:1312 -msgid "Add Shift" +#: includes/extra-strings.php:22 +msgid "Your Radio Station plugin needs to updated to work with %s." msgstr "" -#: includes/post-types-admin.php:1316 -msgid "Are you sure you want to remove this shift?" +#: includes/extra-strings.php:23 +msgid "The required minimum version of Radio Station plugin is:" msgstr "" -#: includes/post-types-admin.php:1434 -msgid "Show Description" +#: includes/extra-strings.php:24 loader.php:1599 radio-station-admin.php:178 +#: radio-station-admin.php:182 radio-station-admin.php:212 +msgid "Settings" msgstr "" -#: includes/post-types-admin.php:1451 -msgid "The text field below is for your Show Description. It will display in the About section of your Show page." +#: includes/extra-strings.php:25 radio-station-admin.php:173 +#: radio-station-admin.php:821 radio-station-admin.php:901 +#: radio-station-admin.php:1453 radio-station-admin.php:1528 +#: radio-station-admin.php:1628 +msgid "Radio Station" msgstr "" -#: includes/post-types-admin.php:1452 -msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." +#: includes/extra-strings.php:26 radio-station-admin.php:203 +#: templates/playlist-export.php:2 +msgid "Export Playlists" msgstr "" -#: includes/post-types-admin.php:1453 -msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." +#: includes/extra-strings.php:27 +msgid "Account" msgstr "" -#: includes/post-types-admin.php:1454 -msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." +#: includes/extra-strings.php:28 +msgid "You have multiple conflicting versions of Radio Station Pro activated." msgstr "" -#: includes/post-types-admin.php:1455 -msgid "You can then assign them to a relevant Post Category for display on your site also." +#: includes/extra-strings.php:29 +msgid "Please visit your Plugins page to deactivate the incorrect one." msgstr "" -#: includes/post-types-admin.php:1476 -msgid "Show Logo" +#: includes/extra-strings.php:30 +msgid "Radio Station Pro" msgstr "" -#: includes/post-types-admin.php:1487 -msgid "Show Images" +#: includes/extra-strings.php:31 +msgid "Extending" msgstr "" -#: includes/post-types-admin.php:1532 -msgid "Set Show Avatar Image" +#: includes/extra-strings.php:32 includes/extra-strings.php:211 +msgid "You do not have permission to do that." msgstr "" -#: includes/post-types-admin.php:1536 -msgid "Remove Show Avatar Image" +#: includes/extra-strings.php:33 +msgid "Important: %s continuous player bar is active." msgstr "" -#: includes/post-types-admin.php:1568 -msgid "Set Show Header Image" +#: includes/extra-strings.php:34 +msgid "This overrides the page fade in, load timeout and loader bar settings here." msgstr "" -#: includes/post-types-admin.php:1572 -msgid "Remove Show Header Image" +#: includes/extra-strings.php:35 +msgid "Adjust those settings via your Radio Station settings page instead." msgstr "" -#: includes/post-types-admin.php:1589 -msgid "Are you sure you want to remove this image?" +#: includes/extra-strings.php:36 +msgid "Guests" msgstr "" -#: includes/post-types-admin.php:1948 -msgid "Active?" +#: includes/extra-strings.php:37 +msgid "Topics" msgstr "" -#: includes/post-types-admin.php:1950 -msgid "About?" +#: includes/extra-strings.php:38 +msgid "Episodes" msgstr "" -#: includes/post-types-admin.php:1951 -msgid "Shifts" +#: includes/extra-strings.php:39 +msgid "Episode Information" msgstr "" -#: includes/post-types-admin.php:1953 -msgid "Hosts" +#: includes/extra-strings.php:40 includes/post-types-admin.php:5648 +#: includes/post-types.php:46 +msgid "Show" msgstr "" -#: includes/post-types-admin.php:1954 -msgid "Show Avatar" +#: includes/extra-strings.php:41 +msgid "Select Show" msgstr "" -#: includes/post-types-admin.php:1970 includes/post-types-admin.php:1981 -#: includes/post-types-admin.php:2443 -msgid "Yes" +#: includes/extra-strings.php:42 includes/post-types-admin.php:5836 +msgid "Draft" msgstr "" -#: includes/post-types-admin.php:1971 includes/post-types-admin.php:1979 -#: includes/post-types-admin.php:2441 -msgid "No" +#: includes/extra-strings.php:43 includes/post-types-admin.php:5843 +msgid "No Shows to Select." msgstr "" -#: includes/post-types-admin.php:1999 -msgid "This Shift is Disabled." +#: includes/extra-strings.php:44 +msgid "Media Type" msgstr "" -#: includes/post-types-admin.php:2004 -msgid "This Shift has Schedule Conflicts and is Disabled." +#: includes/extra-strings.php:45 +msgid "File URL" msgstr "" -#: includes/post-types-admin.php:2005 -msgid "This Shift has Schedule Conflicts." +#: includes/extra-strings.php:46 +msgid "Media Library" msgstr "" -#: includes/post-types-admin.php:2088 -msgid "Filter by show day" +#: includes/extra-strings.php:47 +msgid "Episode Number" msgstr "" -#: includes/post-types-admin.php:2090 -msgid "All show days" +#: includes/extra-strings.php:48 +msgid "Current Highest" msgstr "" -#: includes/post-types-admin.php:2117 -msgid "Override Schedule" +#: includes/extra-strings.php:49 +msgid "Broadcast Date" msgstr "" -#: includes/post-types-admin.php:2162 -msgid "Date" +#: includes/extra-strings.php:50 +msgid "Broadcast Time" msgstr "" -#: includes/post-types-admin.php:2342 -msgid "Override Date" +#: includes/extra-strings.php:51 +msgid "Episode Length" msgstr "" -#: includes/post-types-admin.php:2345 -msgid "Affected Show(s) on Date" +#: includes/extra-strings.php:52 +msgid "minutees" msgstr "" -#: includes/post-types-admin.php:2347 -msgid "Description" +#: includes/extra-strings.php:53 includes/post-types.php:96 +msgid "Playlist" msgstr "" -#: includes/post-types-admin.php:2348 -msgid "Override Image" +#: includes/extra-strings.php:54 +msgid "Select Playlist" msgstr "" -#: includes/post-types-admin.php:2423 -msgid "Inactive Show" +#: includes/extra-strings.php:55 +msgid "No Playlists to Select." msgstr "" -#: includes/post-types-admin.php:2425 -msgid "Disabled Shift" +#: includes/extra-strings.php:56 +msgid "Select Audio File" msgstr "" -#: includes/post-types-admin.php:2506 -msgid "Filter by override date" +#: includes/extra-strings.php:57 +msgid "Remove Selected Audio" msgstr "" -#: includes/post-types-admin.php:2509 -msgid "All override dates" +#: includes/extra-strings.php:58 +msgid "Are you sure you want to remove this audio?" msgstr "" -#: includes/post-types.php:48 includes/post-types.php:56 -#: radio-station-admin.php:131 -msgid "Shows" +#: includes/extra-strings.php:59 includes/post-types-admin.php:5504 +msgid "Failed. Use manual Publish or Update instead." msgstr "" -#: includes/post-types.php:50 includes/post-types.php:51 -#: radio-station-admin.php:132 -msgid "Add Show" +#: includes/extra-strings.php:60 includes/post-types-admin.php:1980 +#: includes/post-types-admin.php:4113 includes/post-types-admin.php:5506 +msgid "Expired. Publish or Update instead." msgstr "" -#: includes/post-types.php:52 -msgid "Edit Show" +#: includes/extra-strings.php:61 +msgid "Failed. No Episode ID provided." msgstr "" -#: includes/post-types.php:53 -msgid "New Show" +#: includes/extra-strings.php:62 +msgid "Failed. Invalid Episode ID." msgstr "" -#: includes/post-types.php:54 -msgid "View Show" +#: includes/extra-strings.php:63 +msgid "Search Topics" msgstr "" -#: includes/post-types.php:61 -msgid "Post type for Show descriptions" +#: includes/extra-strings.php:64 +msgid "All Topics" msgstr "" -#: includes/post-types.php:89 includes/post-types.php:97 -#: radio-station-admin.php:134 templates/single-show-content.php:520 -msgid "Playlists" +#: includes/extra-strings.php:65 +msgid "Parent Topic" msgstr "" -#: includes/post-types.php:90 -msgid "Playlist" +#: includes/extra-strings.php:66 +msgid "Parent Topic:" msgstr "" -#: includes/post-types.php:91 includes/post-types.php:92 -msgid "Add Playlist" +#: includes/extra-strings.php:67 +msgid "Edit Topic" msgstr "" -#: includes/post-types.php:93 -msgid "Edit Playlist" +#: includes/extra-strings.php:68 +msgid "Update Topic" msgstr "" -#: includes/post-types.php:94 -msgid "New Playlist" +#: includes/extra-strings.php:69 +msgid "Add New Topic" msgstr "" -#: includes/post-types.php:95 includes/shortcodes.php:1337 -msgid "View Playlist" +#: includes/extra-strings.php:70 +msgid "New Topic Name" msgstr "" -#: includes/post-types.php:102 -msgid "Post type for Playlist descriptions" +#: includes/extra-strings.php:71 +msgid "Topic" msgstr "" -#: includes/post-types.php:129 radio-station-admin.php:137 -msgid "Schedule Overrides" +#: includes/extra-strings.php:72 +msgid "Search Guests" msgstr "" -#: includes/post-types.php:130 -msgid "Schedule Override" +#: includes/extra-strings.php:73 +msgid "All Guests" msgstr "" -#: includes/post-types.php:131 includes/post-types.php:132 -msgid "Add Schedule Override" +#: includes/extra-strings.php:74 +msgid "Parent Guest" msgstr "" -#: includes/post-types.php:133 -msgid "Edit Schedule Override" +#: includes/extra-strings.php:75 +msgid "Parent Guest:" msgstr "" -#: includes/post-types.php:134 -msgid "New Schedule Override" +#: includes/extra-strings.php:76 +msgid "Edit Guest" msgstr "" -#: includes/post-types.php:135 -msgid "View Schedule Override" +#: includes/extra-strings.php:77 +msgid "Update Guest" msgstr "" -#: includes/post-types.php:140 -msgid "Post type for Schedule Override" +#: includes/extra-strings.php:78 +msgid "Add New Guest" msgstr "" -#: includes/post-types.php:170 -msgid "Host Profiles" +#: includes/extra-strings.php:79 +msgid "New Guest Name" msgstr "" -#: includes/post-types.php:171 -msgid "Host Profile" +#: includes/extra-strings.php:80 +msgid "Guest" msgstr "" -#: includes/post-types.php:172 includes/post-types.php:175 -msgid "New Host Profile" +#: includes/extra-strings.php:81 +msgid "Show %s" msgstr "" -#: includes/post-types.php:173 -msgid "Add Host Profile" +#: includes/extra-strings.php:82 +msgid "Episode" msgstr "" -#: includes/post-types.php:174 -msgid "Edit Host Profile" +#: includes/extra-strings.php:83 +msgid "Aired on" msgstr "" -#: includes/post-types.php:176 -msgid "View Host Profile" +#: includes/extra-strings.php:84 includes/post-types-admin.php:4574 +msgid "Image" msgstr "" -#: includes/post-types.php:177 -msgid "Show Hosts" +#: includes/extra-strings.php:85 +msgid "Add Genre Image" msgstr "" -#: includes/post-types.php:183 -msgid "Post type for DJ / Host Profiles" +#: includes/extra-strings.php:86 +msgid "Remove Genre Image" msgstr "" -#: includes/post-types.php:212 -msgid "Producer Profiles" +#: includes/extra-strings.php:87 +msgid "Insert" msgstr "" -#: includes/post-types.php:213 -msgid "Producer Profile" +#: includes/extra-strings.php:88 +msgid "this is a success message" msgstr "" -#: includes/post-types.php:214 includes/post-types.php:217 -msgid "New Producer Profile" +#: includes/extra-strings.php:89 +msgid "this is a failure message" msgstr "" -#: includes/post-types.php:215 -msgid "Add Producer Profile" +#: includes/extra-strings.php:90 +msgid "Please upload a valid YAML file." msgstr "" -#: includes/post-types.php:216 -msgid "Edit Producer Profile" +#: includes/extra-strings.php:91 +msgid "Please upload a valid YAML file" msgstr "" -#: includes/post-types.php:218 -msgid "View Producer Profile" +#: includes/extra-strings.php:92 +msgid "Please upload a file to import." msgstr "" -#: includes/post-types.php:219 -msgid "Show Producers Profile" +#: includes/extra-strings.php:93 +msgid "Please upload a file to import" msgstr "" -#: includes/post-types.php:225 -msgid "Post type for Producer Profiles" +#: includes/extra-strings.php:94 +msgid "Successfully parsed and imported YAML file, deleting pre-existing show data." msgstr "" -#: includes/post-types.php:332 -msgid "Edit" +#: includes/extra-strings.php:95 +msgid "Successfully parsed and imported YAML file. Pre-existing show data remains unchanged." msgstr "" -#: includes/post-types.php:357 -msgctxt "taxonomy general name" -msgid "Genres" +#: includes/extra-strings.php:96 +msgid "Export successful. Use the following link(s to download your data." msgstr "" -#: includes/post-types.php:358 -msgctxt "taxonomy singular name" -msgid "Genre" +#: includes/extra-strings.php:97 +msgid "Data file" msgstr "" -#: includes/post-types.php:359 -msgid "Search Genres" +#: includes/extra-strings.php:98 +msgid "Image zip file" msgstr "" -#: includes/post-types.php:360 -msgid "All Genres" +#: includes/extra-strings.php:99 +msgid "Image tgz file" msgstr "" -#: includes/post-types.php:361 -msgid "Parent Genre" +#: includes/extra-strings.php:100 +msgid "Download one of the image files and the data file. See help for details on how to stage an import including images." msgstr "" -#: includes/post-types.php:362 -msgid "Parent Genre:" +#: includes/extra-strings.php:101 +msgid "Show data file (YAML" msgstr "" -#: includes/post-types.php:363 -msgid "Edit Genre" +#: includes/extra-strings.php:102 +msgid "Images not exported. See help for details on how to include images." msgstr "" -#: includes/post-types.php:364 -msgid "Update Genre" +#: includes/extra-strings.php:103 +msgid "YAML import error. See below for details." msgstr "" -#: includes/post-types.php:365 -msgid "Add New Genre" +#: includes/extra-strings.php:104 +msgid "Failed to create export folder" msgstr "" -#: includes/post-types.php:366 -msgid "New Genre Name" +#: includes/extra-strings.php:105 +msgid "No image exported for" msgstr "" -#: includes/post-types.php:367 -msgid "Genre" +#: includes/extra-strings.php:106 +msgid "Failed to copy file. See below for details" msgstr "" -#: includes/post-types.php:401 -msgctxt "taxonomy general name" -msgid "Languages" +#: includes/extra-strings.php:107 +msgid "Failed to create show_images.zip" msgstr "" -#: includes/post-types.php:402 -msgctxt "taxonomy singular name" -msgid "Language" +#: includes/extra-strings.php:108 +msgid "Each show in the YAML file must define at minimum the following keys: " msgstr "" -#: includes/post-types.php:403 -msgid "Search Languages" +#: includes/extra-strings.php:109 +msgid "show-title: may not be null." msgstr "" -#: includes/post-types.php:404 -msgid "All Languages" +#: includes/extra-strings.php:110 +msgid "show-description: may not be null." msgstr "" -#: includes/post-types.php:405 -msgid "Parent Language" +#: includes/extra-strings.php:111 +msgid "show-image: must be a URL reference to an existing image." msgstr "" -#: includes/post-types.php:406 -msgid "Parent Language:" +#: includes/extra-strings.php:112 +msgid "show-avatar: must be a URL reference to an existing image." msgstr "" -#: includes/post-types.php:407 -msgid "Edit Language" +#: includes/extra-strings.php:113 +msgid "show-header: must be a URL reference to an existing image." msgstr "" -#: includes/post-types.php:408 -msgid "Update Language" +#: includes/extra-strings.php:114 +msgid "upload-images: may not be null." msgstr "" -#: includes/post-types.php:409 -msgid "Add New Language" +#: includes/extra-strings.php:115 +msgid "show-url: must be a valid web address." msgstr "" -#: includes/post-types.php:410 -msgid "New Language Name" +#: includes/extra-strings.php:116 +msgid "show-podcast: must be a valid web address." msgstr "" -#: includes/post-types.php:411 -msgid "Language" +#: includes/extra-strings.php:117 +msgid "show-user-list: must be a simple array of valid email addresses." msgstr "" -#: includes/shortcodes.php:51 -msgid "Radio Time" +#: includes/extra-strings.php:118 +msgid "show-producer-list: must be a simple array of valid email addresses." msgstr "" -#: includes/shortcodes.php:61 -msgid "Your Time" +#: includes/extra-strings.php:119 +msgid "show-email: must be a valid email address." msgstr "" -#: includes/shortcodes.php:180 -msgid "No Shows in this Genre were found." +#: includes/extra-strings.php:120 +msgid "show-active: may not be null." msgstr "" -#: includes/shortcodes.php:182 -msgid "No Shows were found to display." +#: includes/extra-strings.php:121 +msgid "YAML data parsed successfully, but contains formatting errors. See below for details." msgstr "" -#: includes/shortcodes.php:185 -msgid "No Playlists were found to display." +#: includes/extra-strings.php:122 +msgid "Data file errors noted as follows:" msgstr "" -#: includes/shortcodes.php:187 -msgid "No Overrides were found to display." +#: includes/extra-strings.php:123 +msgid "show-schedule[] time blocks must be in 24h format and have the form \"04:55\" (note 0 padding." msgstr "" -#: includes/shortcodes.php:354 -msgid "No Genres were found to display." +#: includes/extra-strings.php:124 +msgid "Error, for show-schedule[], only \"disabled\" and \"encore\" flags are allowed" msgstr "" -#: includes/shortcodes.php:443 -msgid "No Shows in this Genre." +#: includes/extra-strings.php:125 +msgid "show-schedule[] must reference an array of time blocks containing at least one element." msgstr "" -#: includes/shortcodes.php:599 -msgid "Published on " +#: includes/extra-strings.php:126 +msgid "Invalid weekday. show-schedule[] must be one of \"sun\"..\"sat\", or \"sunday\"..\"saturday\" (case insensitive." msgstr "" -#: includes/shortcodes.php:863 includes/shortcodes.php:1138 -#: templates/single-show-content.php:422 -msgid "Encore Presentation" +#: includes/extra-strings.php:127 +msgid " show-schedule: must define at least one weekday." msgstr "" -#: includes/shortcodes.php:876 includes/shortcodes.php:1150 -#: radio-station-admin.php:500 templates/master-schedule-legacy.php:168 -#: templates/master-schedule-list.php:75 -#: templates/master-schedule-table.php:189 -#: templates/master-schedule-tabs.php:89 -msgid "with" +#: includes/extra-strings.php:128 +msgid "Import/Export Show Data" msgstr "" -#: includes/shortcodes.php:907 includes/shortcodes.php:1181 -#: templates/master-schedule-legacy.php:179 -#: templates/master-schedule-list.php:83 -#: templates/master-schedule-table.php:200 -#: templates/master-schedule-tabs.php:99 templates/single-show-content.php:229 -#: templates/single-show-content.php:256 -msgid "and" +#: includes/extra-strings.php:129 radio-station-admin.php:209 +msgid "Import/Export" msgstr "" -#: includes/shortcodes.php:1084 -msgid "No Shows Upcoming" +#: includes/extra-strings.php:131 +msgid "Import show data from a YAML file." msgstr "" -#: includes/shortcodes.php:1318 -msgid "Label" +#: includes/extra-strings.php:132 +msgid "Delete existing show data" msgstr "" -#: includes/shortcodes.php:1346 -msgid "No Playlist available." +#: includes/extra-strings.php:133 +msgid "WARNING" msgstr "" -#: includes/shortcodes.php:1411 -msgid "More Playlists" +#: includes/extra-strings.php:135 +msgid "Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options." msgstr "" -#: includes/support-functions.php:1803 -msgid "Africa" +#: includes/extra-strings.php:136 +msgid "Advanced" msgstr "" -#: includes/support-functions.php:1804 -msgid "America" +#: includes/extra-strings.php:137 +msgid "YAML file name" msgstr "" -#: includes/support-functions.php:1805 -msgid "Asia" +#: includes/extra-strings.php:138 +msgid "Default similar to" msgstr "" -#: includes/support-functions.php:1806 -msgid "Atlantic" +#: includes/extra-strings.php:139 +msgid "URL where show images will be staged for import (see help" msgstr "" -#: includes/support-functions.php:1807 -msgid "Australia" +#: includes/extra-strings.php:140 +msgid "Metadata Source" msgstr "" -#: includes/support-functions.php:1808 -msgid "Europe" +#: includes/extra-strings.php:141 +msgid "Default Plugin Setting" msgstr "" -#: includes/support-functions.php:1809 -msgid "Indian" +#: includes/extra-strings.php:142 +msgid "Override: Metadata On" msgstr "" -#: includes/support-functions.php:1810 -msgid "Pacific" +#: includes/extra-strings.php:143 +msgid "Override: Metadata Off" msgstr "" -#: includes/support-functions.php:1811 -msgid "Antarctica" +#: includes/extra-strings.php:144 +msgid "Override: Metadata URL" msgstr "" -#: includes/support-functions.php:1854 -msgid "WordPress Timezone" +#: includes/extra-strings.php:145 +msgid "Override: Current Playlist" msgstr "" -#: includes/support-functions.php:1920 -msgid "WordPress Setting" +#: includes/extra-strings.php:146 +msgid "Metadata Override URL" msgstr "" -#: includes/support-functions.php:2853 -msgid "More Show Blog Posts" +#: includes/extra-strings.php:148 +msgid "Add Episode" msgstr "" -#: loader.php:959 -msgid "Plugin Name" +#: includes/extra-strings.php:149 +msgid "Edit Episode" msgstr "" -#: loader.php:961 -msgid "Requires at least" +#: includes/extra-strings.php:150 +msgid "New Episode" msgstr "" -#: loader.php:961 loader.php:962 -msgid "WordPress" +#: includes/extra-strings.php:151 +msgid "View Episode" msgstr "" -#: loader.php:962 -msgid "Tested up to" +#: includes/extra-strings.php:152 +msgid "Search Episodes" msgstr "" -#: loader.php:963 -msgid "Stable Tag" +#: includes/extra-strings.php:153 +msgid "No Episodes found" msgstr "" -#: loader.php:964 -msgid "Contributors" +#: includes/extra-strings.php:154 +msgid "No Episodes found in Trash" msgstr "" -#: loader.php:984 -msgid "Extra Notes" +#: includes/extra-strings.php:155 +msgid "All Episodes" msgstr "" -#: loader.php:1139 -msgid "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" +#: includes/extra-strings.php:156 +msgid "Post Type for Show Episodes" msgstr "" -#: loader.php:1204 radio-station-admin.php:121 radio-station-admin.php:125 -#: radio-station-admin.php:143 -msgid "Settings" +#: includes/extra-strings.php:157 +msgid "Edit Your Host Profile" msgstr "" -#: loader.php:1323 -msgid "by" +#: includes/extra-strings.php:158 +msgid "Host Profile" msgstr "" -#: loader.php:1332 +#: includes/extra-strings.php:159 +msgid "Edit Your Producer Profile" +msgstr "" + +#: includes/extra-strings.php:160 +msgid "Producer Profile" +msgstr "" + +#: includes/extra-strings.php:161 +msgid "Profile Information" +msgstr "" + +#: includes/extra-strings.php:162 includes/post-types-admin.php:469 +#: includes/post-types-admin.php:3103 +msgid "Website Link" +msgstr "" + +#: includes/extra-strings.php:163 +msgid "%s Email" +msgstr "" + +#: includes/extra-strings.php:164 includes/post-types-admin.php:491 +#: includes/post-types-admin.php:3145 +msgid "Show Phone" +msgstr "" + +#: includes/extra-strings.php:165 includes/post-types-admin.php:522 +#: includes/post-types-admin.php:3209 +msgid "Patreon Page ID" +msgstr "" + +#: includes/extra-strings.php:166 +msgid "Host Image" +msgstr "" + +#: includes/extra-strings.php:167 +msgid "Producer Image" +msgstr "" + +#: includes/extra-strings.php:168 +msgid "Episode Image" +msgstr "" + +#: includes/extra-strings.php:169 +msgid "Set %s Avatar Image" +msgstr "" + +#: includes/extra-strings.php:170 +msgid "Remove %s Avatar Image" +msgstr "" + +#: includes/extra-strings.php:171 includes/post-types-admin.php:1803 +#: loader.php:2263 +msgid "Are you sure you want to remove this image?" +msgstr "" + +#: includes/extra-strings.php:172 includes/post-types-admin.php:1804 +msgid "Select or Upload Image" +msgstr "" + +#: includes/extra-strings.php:173 includes/post-types-admin.php:1805 +msgid "Use this Image" +msgstr "" + +#: includes/extra-strings.php:174 +msgid "User Save Error! The selected User is already assigned to a %s Profile." +msgstr "" + +#: includes/extra-strings.php:175 includes/post-types-admin.php:2557 +#: includes/post-types-admin.php:3061 includes/post-types.php:195 +msgid "Hosts" +msgstr "" + +#: includes/extra-strings.php:176 +msgid "Host User" +msgstr "" + +#: includes/extra-strings.php:177 includes/post-types-admin.php:3082 +#: includes/post-types.php:247 +msgid "Producers" +msgstr "" + +#: includes/extra-strings.php:178 +msgid "Producer User" +msgstr "" + +#: includes/extra-strings.php:179 +msgid "Team" +msgstr "" + +#: includes/extra-strings.php:180 +msgid "All" +msgstr "" + +#: includes/extra-strings.php:181 +msgid "Editors" +msgstr "" + +#: includes/extra-strings.php:182 includes/post-types.php:45 +#: includes/post-types.php:53 radio-station-admin.php:188 +msgid "Shows" +msgstr "" + +#: includes/extra-strings.php:183 includes/post-types-admin.php:617 +msgid "DJs / Hosts" +msgstr "" + +#: includes/extra-strings.php:184 +msgid "Show Editors" +msgstr "" + +#: includes/extra-strings.php:185 +msgid "Role Assignment Interface" +msgstr "" + +#: includes/extra-strings.php:186 +msgid "You can assign Radio Station roles to users directly here." +msgstr "" + +#: includes/extra-strings.php:187 +msgid "They can also be assigned via the WordPress user editor." +msgstr "" + +#: includes/extra-strings.php:188 +msgid "Grant Role to User(s" +msgstr "" + +#: includes/extra-strings.php:189 +msgid "Remove Role from User(s" +msgstr "" + +#: includes/extra-strings.php:190 +msgid "Update User Roles" +msgstr "" + +#: includes/extra-strings.php:191 +msgid "You have unsaved role changes." +msgstr "" + +#: includes/extra-strings.php:192 +msgid "role added to user" +msgstr "" + +#: includes/extra-strings.php:193 +msgid "role removed from user" +msgstr "" + +#: includes/extra-strings.php:194 +msgid "Visual Schedule Editor" +msgstr "" + +#: includes/extra-strings.php:195 +msgid "Table" +msgstr "" + +#: includes/extra-strings.php:196 +msgid "Tabs" +msgstr "" + +#: includes/extra-strings.php:197 +msgid "Calendar" +msgstr "" + +#: includes/extra-strings.php:198 +msgid "Your session has expired. You can log in again from this page or go to the login page." +msgstr "" + +#: includes/extra-strings.php:199 +msgid "You have unsaved changes. Are you sure you want to close this window?" +msgstr "" + +#: includes/extra-strings.php:200 +msgid "Save Shift" +msgstr "" + +#: includes/extra-strings.php:201 includes/post-types-admin.php:860 +msgid "Save Shifts" +msgstr "" + +#: includes/extra-strings.php:202 +msgid "Save Override" +msgstr "" + +#: includes/extra-strings.php:203 includes/post-types-admin.php:3424 +msgid "Save Overrides" +msgstr "" + +#: includes/extra-strings.php:204 +msgid "Edit Show Shifts" +msgstr "" + +#: includes/extra-strings.php:205 +msgid "Edit Override Times" +msgstr "" + +#: includes/extra-strings.php:206 +msgid "Edit Show Shift" +msgstr "" + +#: includes/extra-strings.php:207 +msgid "Edit Override Time" +msgstr "" + +#: includes/extra-strings.php:208 +msgid "Add to Schedule" +msgstr "" + +#: includes/extra-strings.php:209 +msgid "Add Show or Override to Schedule" +msgstr "" + +#: includes/extra-strings.php:210 +msgid "Error. Provided Show ID does not exist." +msgstr "" + +#: includes/extra-strings.php:212 +msgid "Warning. Provided Shift ID is not valid." +msgstr "" + +#: includes/extra-strings.php:213 +msgid "Warning. Provided Shift ID no longer exists." +msgstr "" + +#: includes/extra-strings.php:214 +msgid "Display Shifts" +msgstr "" + +#: includes/extra-strings.php:215 +msgid "All Show Shifts" +msgstr "" + +#: includes/extra-strings.php:216 +msgid "Warning. Provided Override Shift ID no longer exists." +msgstr "" + +#: includes/extra-strings.php:217 +msgid "Override" +msgstr "" + +#: includes/extra-strings.php:218 +msgid "Display Override" +msgstr "" + +#: includes/extra-strings.php:219 +msgid "All Override Times" +msgstr "" + +#: includes/extra-strings.php:220 +msgid "Error. Provided ID is not a Show or Override." +msgstr "" + +#: includes/extra-strings.php:221 +msgid "Error! Invalid date value passed." +msgstr "" + +#: includes/extra-strings.php:222 +msgid "Error! Invalid day value passed." +msgstr "" + +#: includes/extra-strings.php:223 +msgid "Timeslot Type" +msgstr "" + +#: includes/extra-strings.php:224 +msgid "Override Date" +msgstr "" + +#: includes/extra-strings.php:225 +msgid "Shift Day" +msgstr "" + +#: includes/extra-strings.php:226 includes/post-types-admin.php:1189 +#: includes/post-types-admin.php:1537 +msgid "Encore" +msgstr "" + +#: includes/extra-strings.php:227 includes/post-types-admin.php:1196 +#: includes/post-types-admin.php:1548 includes/post-types-admin.php:3742 +#: includes/post-types-admin.php:4052 includes/post-types-admin.php:4730 +msgid "Disabled" +msgstr "" + +#: includes/extra-strings.php:228 includes/post-types-admin.php:1119 +#: includes/post-types-admin.php:1453 includes/post-types-admin.php:3632 +#: includes/post-types-admin.php:3968 +msgid "Start Time" +msgstr "" + +#: includes/extra-strings.php:229 includes/post-types-admin.php:1152 +#: includes/post-types-admin.php:1494 includes/post-types-admin.php:3672 +#: includes/post-types-admin.php:4001 +msgid "End Time" +msgstr "" + +#: includes/extra-strings.php:230 +msgid "Show Type" +msgstr "" + +#: includes/extra-strings.php:231 +msgid "Override Type" +msgstr "" + +#: includes/extra-strings.php:232 +msgid "Select Show..." +msgstr "" + +#: includes/extra-strings.php:233 includes/post-types-admin.php:863 +msgid "Add Shift" +msgstr "" + +#: includes/extra-strings.php:234 +msgid "Select Override..." +msgstr "" + +#: includes/extra-strings.php:235 includes/post-types-admin.php:3427 +msgid "Add Override" +msgstr "" + +#: includes/extra-strings.php:236 +msgid "Open in a New Window" +msgstr "" + +#: includes/extra-strings.php:237 +msgid "Create New Show" +msgstr "" + +#: includes/extra-strings.php:238 +msgid "Create New Override" +msgstr "" + +#: includes/extra-strings.php:239 +msgid "Adding Show Shift..." +msgstr "" + +#: includes/extra-strings.php:240 +msgid "Show Shift Added." +msgstr "" + +#: includes/extra-strings.php:241 includes/post-types-admin.php:857 +msgid "Clear Shifts" +msgstr "" + +#: includes/extra-strings.php:242 +msgid "Adding Override Time..." +msgstr "" + +#: includes/extra-strings.php:243 +msgid "Override Time Saved." +msgstr "" + +#: includes/extra-strings.php:244 includes/post-types-admin.php:3421 +msgid "Clear Overrides" +msgstr "" + +#: includes/extra-strings.php:245 +msgid "Load Schedule for Previous Week" +msgstr "" + +#: includes/extra-strings.php:246 +msgid "Load Schedule for Next Week" +msgstr "" + +#: includes/extra-strings.php:247 +msgid "View" +msgstr "" + +#: includes/extra-strings.php:248 +msgid "No Hosts were found to display." +msgstr "" + +#: includes/extra-strings.php:249 +msgid "No Producers were found to display." +msgstr "" + +#: includes/extra-strings.php:250 +msgid "No Episodes were found to display." +msgstr "" + +#: includes/extra-strings.php:251 includes/shortcodes.php:1645 +msgid "Published on " +msgstr "" + +#: includes/extra-strings.php:252 +msgid "No %s were found to display." +msgstr "" + +#: includes/extra-strings.php:253 +msgid "Social Icons" +msgstr "" + +#: includes/extra-strings.php:254 +msgid "Soundcloud" +msgstr "" + +#: includes/extra-strings.php:255 +msgid "Mixcloud" +msgstr "" + +#: includes/extra-strings.php:256 +msgid "Spotify" +msgstr "" + +#: includes/extra-strings.php:257 +msgid "Twitch" +msgstr "" + +#: includes/extra-strings.php:258 +msgid "Discord" +msgstr "" + +#: includes/extra-strings.php:259 +msgid "Facebook" +msgstr "" + +#: includes/extra-strings.php:260 +msgid "Twitter" +msgstr "" + +#: includes/extra-strings.php:261 +msgid "YouTube" +msgstr "" + +#: includes/extra-strings.php:262 +msgid "LinkedIn" +msgstr "" + +#: includes/extra-strings.php:263 +msgid "Xing" +msgstr "" + +#: includes/extra-strings.php:264 +msgid "Instagram" +msgstr "" + +#: includes/extra-strings.php:265 +msgid "WhatsApp" +msgstr "" + +#: includes/extra-strings.php:266 +msgid "Pinterest" +msgstr "" + +#: includes/extra-strings.php:267 +msgid "Custom" +msgstr "" + +#: includes/extra-strings.php:268 +msgid "Add a Social Icon" +msgstr "" + +#: includes/extra-strings.php:269 +msgid "Move this Icon Up" +msgstr "" + +#: includes/extra-strings.php:270 +msgid "Move this Icon Down" +msgstr "" + +#: includes/extra-strings.php:271 +msgid "Remove this Social Icon" +msgstr "" + +#: includes/extra-strings.php:272 +msgid "URL" +msgstr "" + +#: includes/extra-strings.php:273 +msgid "Icon URL" +msgstr "" + +#: includes/extra-strings.php:274 +msgid "Unknown" +msgstr "" + +#: includes/extra-strings.php:275 +msgid "Social URL Page Link" +msgstr "" + +#: includes/extra-strings.php:276 +msgid "Social Icon Image URL" +msgstr "" + +#: includes/extra-strings.php:277 +msgid "Are you sure you want to remove this Social Icon?" +msgstr "" + +#: includes/extra-strings.php:278 +msgid "No Social Icon Services defined." +msgstr "" + +#: includes/extra-strings.php:279 +msgid "You can reorder this icon after saving it." +msgstr "" + +#: includes/extra-strings.php:280 +msgid "Change Timezone" +msgstr "" + +#: includes/extra-strings.php:281 +msgid "Select Region..." +msgstr "" + +#: includes/extra-strings.php:282 +msgid "Clear" +msgstr "" + +#: includes/extra-strings.php:283 +msgid "Cancel" +msgstr "" + +#: includes/extra-strings.php:284 +msgid "Select Timezone..." +msgstr "" + +#: includes/extra-strings.php:287 +msgid "Automatic Reloading" +msgstr "" + +#: includes/extra-strings.php:288 templates/master-schedule-list.php:28 +#: templates/master-schedule-table.php:29 +#: templates/master-schedule-table.php:587 +#: templates/master-schedule-tabs.php:30 templates/master-schedule-tabs.php:485 +msgid "to" +msgstr "" + +#: includes/extra-strings.php:289 includes/shortcodes.php:2254 +#: includes/shortcodes.php:2928 radio-station-admin.php:1552 +#: templates/master-schedule-div.php:122 +#: templates/master-schedule-legacy.php:173 +#: templates/master-schedule-list.php:311 +#: templates/master-schedule-table.php:507 +#: templates/master-schedule-tabs.php:406 +msgid "with" +msgstr "" + +#: includes/extra-strings.php:290 includes/shortcodes.php:2291 +#: includes/shortcodes.php:2965 templates/master-schedule-div.php:133 +#: templates/master-schedule-legacy.php:184 +#: templates/master-schedule-list.php:326 +#: templates/master-schedule-table.php:523 +#: templates/master-schedule-tabs.php:420 templates/single-show-content.php:356 +#: templates/single-show-content.php:395 +msgid "and" +msgstr "" + +#: includes/extra-strings.php:291 templates/master-schedule-div.php:188 +#: templates/master-schedule-legacy.php:229 +#: templates/master-schedule-list.php:410 +#: templates/master-schedule-table.php:603 +#: templates/master-schedule-tabs.php:502 +msgid "encore airing" +msgstr "" + +#: includes/extra-strings.php:292 templates/master-schedule-div.php:198 +#: templates/master-schedule-legacy.php:244 +#: templates/master-schedule-list.php:431 +#: templates/master-schedule-table.php:623 +#: templates/master-schedule-tabs.php:523 +msgid "Audio File" +msgstr "" + +#: includes/extra-strings.php:293 includes/master-schedule.php:653 +#: radio-station-admin.php:193 templates/master-schedule-list.php:453 +#: templates/master-schedule-tabs.php:544 +msgid "Genres" +msgstr "" + +#: includes/extra-strings.php:294 +msgid "No Shows" +msgstr "" + +#: includes/extra-strings.php:295 +msgid "No Shows scheduled for this date." +msgstr "" + +#: includes/extra-strings.php:296 templates/master-schedule-table.php:172 +#: templates/master-schedule-tabs.php:185 +msgid "Previous Day" +msgstr "" + +#: includes/extra-strings.php:297 templates/master-schedule-table.php:185 +#: templates/master-schedule-tabs.php:201 +msgid "Next Day" +msgstr "" + +#: includes/extra-strings.php:298 templates/master-schedule-tabs.php:222 +msgid "Viewing" +msgstr "" + +#: includes/extra-strings.php:299 templates/master-schedule-tabs.php:646 +msgid "No Shows scheduled for this day." +msgstr "" + +#: includes/extra-strings.php:300 templates/single-show-content.php:144 +msgid "Show RSS Feed" +msgstr "" + +#: includes/extra-strings.php:301 templates/single-show-content.php:245 +msgid "Become a Supporter for" +msgstr "" + +#: includes/extra-strings.php:302 +msgid "Episode Info" +msgstr "" + +#: includes/extra-strings.php:303 +msgid "Broadcasted" +msgstr "" + +#: includes/extra-strings.php:304 +msgid "on" +msgstr "" + +#: includes/extra-strings.php:305 templates/single-show-content.php:331 +msgid "Hosted by" +msgstr "" + +#: includes/extra-strings.php:306 templates/single-show-content.php:370 +msgid "Produced by" +msgstr "" + +#: includes/extra-strings.php:307 templates/single-show-content.php:750 +msgid "Show More" +msgstr "" + +#: includes/extra-strings.php:308 templates/single-show-content.php:753 +msgid "Show Less" +msgstr "" + +#: includes/extra-strings.php:309 +msgid "Listen to this %s" +msgstr "" + +#: includes/extra-strings.php:310 radio-station.php:1491 +msgid "Player" +msgstr "" + +#: includes/extra-strings.php:311 +msgid "Download this Episode" +msgstr "" + +#: includes/extra-strings.php:312 +msgid "About this %s" +msgstr "" + +#: includes/extra-strings.php:313 templates/single-show-content.php:778 +msgid "About" +msgstr "" + +#: includes/extra-strings.php:314 +msgid "%s Show" +msgstr "" + +#: includes/extra-strings.php:315 +msgid "%s Playlist" +msgstr "" + +#: includes/extra-strings.php:316 templates/single-show-content.php:992 +msgid "Jump to" +msgstr "" + +#: includes/extra-strings.php:317 +msgid "Visit %s Website" +msgstr "" + +#: includes/extra-strings.php:318 +msgid "%s Phone Number" +msgstr "" + +#: includes/extra-strings.php:319 +msgid "Email %s" +msgstr "" + +#: includes/extra-strings.php:320 +msgid "%s RSS Feed" +msgstr "" + +#: includes/extra-strings.php:321 templates/single-show-content.php:277 +msgid "Download Latest Broadcast" +msgstr "" + +#: includes/extra-strings.php:322 +msgid "Profile Info" +msgstr "" + +#: includes/extra-strings.php:323 +msgid "Website" +msgstr "" + +#: includes/extra-strings.php:324 +msgid "Email" +msgstr "" + +#: includes/extra-strings.php:325 +msgid "Phone" +msgstr "" + +#: includes/legacy.php:623 +msgid "More Show Blog Posts" +msgstr "" + +#. translators: 1: PHP function name, 2: Version number, 3: Alternative +#. function name. + +#: includes/legacy.php:660 +msgid "%1$s is deprecated since Radio Station version %2$s! Use %3$s or check documentation for updated functionality." +msgstr "" + +#: includes/master-schedule.php:662 +msgid "Click to toggle Highlight of Shows with this Genre." +msgstr "" + +#: includes/post-types-admin.php:173 +msgid "Show Language" +msgstr "" + +#: includes/post-types-admin.php:210 +msgid "Main Radio Language" +msgstr "" + +#: includes/post-types-admin.php:214 +msgid "Select below if Show language(s) differ." +msgstr "" + +#: includes/post-types-admin.php:239 +msgid "Remove Language" +msgstr "" + +#: includes/post-types-admin.php:248 +msgid "Select Language" +msgstr "" + +#: includes/post-types-admin.php:264 +msgid "Click on a Language to Add it." +msgstr "" + +#: includes/post-types-admin.php:413 +msgid "Show Information" +msgstr "" + +#: includes/post-types-admin.php:457 +msgid "Active" +msgstr "" + +#: includes/post-types-admin.php:462 +msgid "Check this box if show is currently active (Show will not appear on schedule if unchecked.)" +msgstr "" + +#: includes/post-types-admin.php:480 includes/post-types-admin.php:3124 +msgid "Show Email" +msgstr "" + +#: includes/post-types-admin.php:501 +msgid "Latest Audio File" +msgstr "" + +#: includes/post-types-admin.php:512 includes/post-types-admin.php:3187 +msgid "Disable Download" +msgstr "" + +#: includes/post-types-admin.php:541 +msgid "Show DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:545 includes/post-types-admin.php:699 +msgid "Show Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:549 +msgid "Show Language(s)" +msgstr "" + +#: includes/post-types-admin.php:686 includes/post-types-admin.php:764 +msgid "Ctrl-Click selects multiple." +msgstr "" + +#: includes/post-types-admin.php:781 +msgid "Show Schedule" +msgstr "" + +#: includes/post-types-admin.php:827 +msgid "This Show is inactive!" +msgstr "" + +#: includes/post-types-admin.php:828 +msgid "All Shifts are inactive also until Show is activated." +msgstr "" + +#: includes/post-types-admin.php:868 +msgid "Saving Show Shifts..." +msgstr "" + +#: includes/post-types-admin.php:869 +msgid "Show Shifts Saved." +msgstr "" + +#: includes/post-types-admin.php:952 +msgid "Are you sure you want to remove this shift?" +msgstr "" + +#: includes/post-types-admin.php:953 +msgid "Are you sure you want to clear the shift list?" +msgstr "" + +#: includes/post-types-admin.php:1104 includes/post-types-admin.php:1436 +#: radio-station.php:2020 +msgid "Day" +msgstr "" + +#: includes/post-types-admin.php:1201 includes/post-types-admin.php:1555 +msgid "Duplicate Shift" +msgstr "" + +#: includes/post-types-admin.php:1206 includes/post-types-admin.php:1562 +msgid "Remove Shift" +msgstr "" + +#: includes/post-types-admin.php:1226 +msgid "Warning! Show Shift Conflicts were detected!" +msgstr "" + +#: includes/post-types-admin.php:1227 +msgid "Please note that Shifts with conflicts are automatically disabled upon saving." +msgstr "" + +#: includes/post-types-admin.php:1228 +msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." +msgstr "" + +#: includes/post-types-admin.php:1229 +msgid "Then you can uncheck the shift Disable box and Save again to re-enable the Shift." +msgstr "" + +#: includes/post-types-admin.php:1572 +msgid "Shift Conflicts" +msgstr "" + +#: includes/post-types-admin.php:1578 +msgid "This Show" +msgstr "" + +#: includes/post-types-admin.php:1626 +msgid "Show Description" +msgstr "" + +#: includes/post-types-admin.php:1642 +msgid "The text field below is for your Show Description. It will display in the About section of your Show page." +msgstr "" + +#: includes/post-types-admin.php:1643 +msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." +msgstr "" + +#: includes/post-types-admin.php:1644 +msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." +msgstr "" + +#: includes/post-types-admin.php:1645 +msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." +msgstr "" + +#: includes/post-types-admin.php:1646 +msgid "This way you can then assign them to a relevant Post Category for display on your site also." +msgstr "" + +#: includes/post-types-admin.php:1672 +msgid "Show Logo" +msgstr "" + +#: includes/post-types-admin.php:1688 +msgid "Show Images" +msgstr "" + +#: includes/post-types-admin.php:1736 +msgid "Set Show Avatar Image" +msgstr "" + +#: includes/post-types-admin.php:1743 +msgid "Remove Show Avatar Image" +msgstr "" + +#: includes/post-types-admin.php:1779 +msgid "Set Show Header Image" +msgstr "" + +#: includes/post-types-admin.php:1786 +msgid "Remove Show Header Image" +msgstr "" + +#: includes/post-types-admin.php:1978 includes/post-types-admin.php:4117 +msgid "Failed. Publish or Update instead." +msgstr "" + +#: includes/post-types-admin.php:1982 +msgid "Error! No Show ID provided." +msgstr "" + +#: includes/post-types-admin.php:1987 +msgid "Failed. Invalid Show." +msgstr "" + +#: includes/post-types-admin.php:2410 +msgid "Warning! Shift conflicts detected." +msgstr "" + +#: includes/post-types-admin.php:2552 +msgid "Active?" +msgstr "" + +#: includes/post-types-admin.php:2554 +msgid "About?" +msgstr "" + +#: includes/post-types-admin.php:2555 +msgid "Shifts" +msgstr "" + +#: includes/post-types-admin.php:2562 includes/post-types-admin.php:2746 +#: includes/post-types-admin.php:3019 +msgid "Show Avatar" +msgstr "" + +#: includes/post-types-admin.php:2578 includes/post-types-admin.php:2594 +#: includes/post-types-admin.php:4782 +msgid "Yes" +msgstr "" + +#: includes/post-types-admin.php:2580 includes/post-types-admin.php:2592 +#: includes/post-types-admin.php:4780 +msgid "No" +msgstr "" + +#: includes/post-types-admin.php:2637 +msgid "This Shift is Disabled." +msgstr "" + +#: includes/post-types-admin.php:2646 +msgid "This Shift has Schedule Conflicts and is Disabled." +msgstr "" + +#: includes/post-types-admin.php:2648 +msgid "This Shift has Schedule Conflicts." +msgstr "" + +#: includes/post-types-admin.php:2657 +msgid "This Show is not currently active." +msgstr "" + +#: includes/post-types-admin.php:2697 +msgid "This shift is disabled as no day is set." +msgstr "" + +#: includes/post-types-admin.php:2788 +msgid "Filter by show day" +msgstr "" + +#: includes/post-types-admin.php:2790 +msgid "All show days" +msgstr "" + +#: includes/post-types-admin.php:2812 +msgid "Override Show Data" +msgstr "" + +#: includes/post-types-admin.php:2850 +msgid "Link to Show" +msgstr "" + +#: includes/post-types-admin.php:2858 +msgid "No Show Link" +msgstr "" + +#: includes/post-types-admin.php:2894 +msgid "If selected, Override data will be used from the Linked Show." +msgstr "" + +#: includes/post-types-admin.php:2911 +msgid "If checked, assigned Genre terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:2929 +msgid "If checked, assigned Language terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:2949 +msgid "Usage Note" +msgstr "" + +#: includes/post-types-admin.php:2950 +msgid " Unchecked boxes use Show data, checked boxes use Override data." +msgstr "" + +#: includes/post-types-admin.php:2955 +msgid "Override?" +msgstr "" + +#: includes/post-types-admin.php:2957 +msgid "Override Data" +msgstr "" + +#: includes/post-types-admin.php:2977 +msgid "Use Title Editor metabox above." +msgstr "" + +#: includes/post-types-admin.php:2987 +msgid "Description" +msgstr "" + +#: includes/post-types-admin.php:2993 +msgid "Use Content Editor metabox below." +msgstr "" + +#: includes/post-types-admin.php:3003 +msgid "Excerpt" +msgstr "" + +#: includes/post-types-admin.php:3009 +msgid "Use Excerpt Editor metabox below." +msgstr "" + +#: includes/post-types-admin.php:3025 +msgid "Use Show Images metabox." +msgstr "" + +#: includes/post-types-admin.php:3040 +msgid "Featured Image" +msgstr "" + +#: includes/post-types-admin.php:3046 +msgid "Use Feature Image metabox." +msgstr "" + +#: includes/post-types-admin.php:3067 +msgid "Use Host assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3088 +msgid "Use Producer assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3166 +msgid "Latest Audio" +msgstr "" + +#: includes/post-types-admin.php:3194 +msgid "Check to Disable" +msgstr "" + +#: includes/post-types-admin.php:3236 +msgid "Override DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:3240 +msgid "Override Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:3384 +msgid "Override Schedule" +msgstr "" + +#: includes/post-types-admin.php:3432 +msgid "Saving Overrides..." +msgstr "" + +#: includes/post-types-admin.php:3433 +msgid "Overrides Saved." +msgstr "" + +#: includes/post-types-admin.php:3621 includes/post-types-admin.php:3962 +#: templates/playlist-export.php:14 +msgid "Start Date" +msgstr "" + +#: includes/post-types-admin.php:3754 includes/post-types-admin.php:4057 +msgid "Duplicate Override" +msgstr "" + +#: includes/post-types-admin.php:3760 includes/post-types-admin.php:4062 +msgid "Remove Override" +msgstr "" + +#: includes/post-types-admin.php:3805 +msgid "Are you sure you want to remove this Override?" +msgstr "" + +#: includes/post-types-admin.php:3806 +msgid "Are you sure you want to clear all overrides?" +msgstr "" + +#: includes/post-types-admin.php:4037 +msgid "Multiday" +msgstr "" + +#: includes/post-types-admin.php:4042 templates/playlist-export.php:78 +msgid "End Date" +msgstr "" + +#: includes/post-types-admin.php:4115 +msgid "Failed. Invalid Override." +msgstr "" + +#: includes/post-types-admin.php:4563 +msgid "Override Time(s)" +msgstr "" + +#: includes/post-types-admin.php:4564 +msgid "Affected Show(s)" +msgstr "" + +#: includes/post-types-admin.php:4569 includes/post-types-admin.php:5374 +msgid "Linked Show" +msgstr "" + +#: includes/post-types-admin.php:4724 +msgid "Inactive" +msgstr "" + +#: includes/post-types-admin.php:4808 +msgid "Override Logo" +msgstr "" + +#: includes/post-types-admin.php:4882 +msgid "Filter by override date" +msgstr "" + +#: includes/post-types-admin.php:4884 +msgid "All Override Months" +msgstr "" + +#: includes/post-types-admin.php:4912 +msgid "Past and Future" +msgstr "" + +#: includes/post-types-admin.php:4914 includes/post-types.php:154 +msgid "All Overrides" +msgstr "" + +#: includes/post-types-admin.php:4915 +msgid "Past Overrides" +msgstr "" + +#: includes/post-types-admin.php:4916 +msgid "Overrides Today" +msgstr "" + +#: includes/post-types-admin.php:4917 +msgid "Future Overrides" +msgstr "" + +#: includes/post-types-admin.php:4941 +msgid "Playlist Entries" +msgstr "" + +#: includes/post-types-admin.php:4961 includes/post-types-admin.php:5255 +msgid "Move Track Up" +msgstr "" + +#: includes/post-types-admin.php:4962 includes/post-types-admin.php:5256 +msgid "Move Track Down" +msgstr "" + +#: includes/post-types-admin.php:4963 includes/post-types-admin.php:5257 +msgid "Duplicate Track" +msgstr "" + +#: includes/post-types-admin.php:4964 includes/post-types-admin.php:5258 +msgid "Remove Track" +msgstr "" + +#: includes/post-types-admin.php:4978 +msgid "Clear Tracks" +msgstr "" + +#: includes/post-types-admin.php:4981 +msgid "Save Tracks" +msgstr "" + +#: includes/post-types-admin.php:4984 +msgid "Add Track" +msgstr "" + +#: includes/post-types-admin.php:4989 +msgid "Saving Playlist Tracks..." +msgstr "" + +#: includes/post-types-admin.php:4990 +msgid "Playlist Tracks Saved." +msgstr "" + +#: includes/post-types-admin.php:4998 +msgid "Tracks marked New are moved to the end of Playlist on update." +msgstr "" + +#: includes/post-types-admin.php:5001 +msgid "Are you sure you want to clear the track list?" +msgstr "" + +#: includes/post-types-admin.php:5106 includes/post-types-admin.php:5314 +msgid "Length" +msgstr "" + +#: includes/post-types-admin.php:5114 includes/post-types-admin.php:5331 +#: includes/shortcodes.php:3440 templates/single-playlist-content.php:49 +msgid "Comments" +msgstr "" + +#: includes/post-types-admin.php:5115 includes/post-types-admin.php:5336 +msgid "New" +msgstr "" + +#: includes/post-types-admin.php:5117 includes/post-types-admin.php:5341 +#: includes/post-types-admin.php:5678 +msgid "Status" +msgstr "" + +#: includes/post-types-admin.php:5119 includes/post-types-admin.php:5343 +msgid "Queued" +msgstr "" + +#: includes/post-types-admin.php:5120 includes/post-types-admin.php:5344 +msgid "Played" +msgstr "" + +#: includes/post-types-admin.php:5123 includes/post-types-admin.php:5349 +msgid "Move" +msgstr "" + +#: includes/post-types-admin.php:5247 includes/post-types-admin.php:5677 +#: includes/shortcodes.php:3419 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:45 +msgid "Artist" +msgstr "" + +#: includes/post-types-admin.php:5248 includes/post-types-admin.php:5676 +#: includes/shortcodes.php:3411 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:46 +msgid "Song" +msgstr "" + +#: includes/post-types-admin.php:5249 includes/shortcodes.php:3426 +#: templates/legacy/single-playlist.php:47 +#: templates/single-playlist-content.php:47 +msgid "Album" +msgstr "" + +#: includes/post-types-admin.php:5250 templates/legacy/single-playlist.php:48 +msgid "Record Label" +msgstr "" + +#: includes/post-types-admin.php:5316 +msgctxt "minutes unit" +msgid "m" +msgstr "" + +#: includes/post-types-admin.php:5328 +msgctxt "seconds unit" +msgid "s" +msgstr "" + +#: includes/post-types-admin.php:5459 +msgid "No Shows were found." +msgstr "" + +#: includes/post-types-admin.php:5462 +msgid "You are not assigned to any Shows." +msgstr "" + +#: includes/post-types-admin.php:5470 +msgid "Unassigned" +msgstr "" + +#: includes/post-types-admin.php:5508 +msgid "Failed. No Playlist ID provided." +msgstr "" + +#: includes/post-types-admin.php:5513 +msgid "Failed. Invalid Playlist ID." +msgstr "" + +#: includes/post-types-admin.php:5649 +msgid "Tracks" +msgstr "" + +#: includes/post-types-admin.php:5650 +msgid "Track List" +msgstr "" + +#: includes/post-types-admin.php:5672 +msgid "Show/Hide Tracklist" +msgstr "" + +#: includes/post-types-admin.php:5750 +msgid "Related to Show" +msgstr "" + +#: includes/post-types-admin.php:5813 +msgid "Select Show(s)" +msgstr "" + +#: includes/post-types-admin.php:5980 +msgid "Related Show(s)" +msgstr "" + +#: includes/post-types-admin.php:5995 +msgid "No Shows available to Select." +msgstr "" + +#: includes/post-types-admin.php:6018 +msgid "Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6063 includes/post-types.php:49 +msgid "Edit Show" +msgstr "" + +#: includes/post-types-admin.php:6146 +msgid "Set Related Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6277 +msgid "Updated Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:6283 +msgid "Failed to Update Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:6529 +msgid "Failed. Please relogin and try again." +msgstr "" + +#: includes/post-types.php:47 includes/post-types.php:48 +#: radio-station-admin.php:189 +msgid "Add Show" +msgstr "" + +#: includes/post-types.php:50 +msgid "New Show" +msgstr "" + +#: includes/post-types.php:51 +msgid "View Show" +msgstr "" + +#: includes/post-types.php:55 +msgid "Search Shows" +msgstr "" + +#: includes/post-types.php:56 +msgid "No Shows found" +msgstr "" + +#: includes/post-types.php:57 +msgid "No Shows found in Trash" +msgstr "" + +#: includes/post-types.php:58 +msgid "All Shows" +msgstr "" + +#: includes/post-types.php:65 +msgid "Shows Archive" +msgstr "" + +#: includes/post-types.php:95 includes/post-types.php:103 +#: radio-station-admin.php:191 +msgid "Playlists" +msgstr "" + +#: includes/post-types.php:97 includes/post-types.php:98 +msgid "Add Playlist" +msgstr "" + +#: includes/post-types.php:99 +msgid "Edit Playlist" +msgstr "" + +#: includes/post-types.php:100 +msgid "New Playlist" +msgstr "" + +#: includes/post-types.php:101 includes/shortcodes.php:2338 +#: includes/shortcodes.php:3456 +msgid "View Playlist" +msgstr "" + +#: includes/post-types.php:105 +msgid "Search Playlists" +msgstr "" + +#: includes/post-types.php:106 +msgid "No Playlists found" +msgstr "" + +#: includes/post-types.php:107 +msgid "No Playlists found in Trash" +msgstr "" + +#: includes/post-types.php:108 +msgid "All Playlists" +msgstr "" + +#: includes/post-types.php:115 +msgid "Playlists Archive" +msgstr "" + +#: includes/post-types.php:143 radio-station-admin.php:194 +msgid "Schedule Overrides" +msgstr "" + +#: includes/post-types.php:144 +msgid "Schedule Override" +msgstr "" + +#: includes/post-types.php:145 includes/post-types.php:146 +msgid "Add Schedule Override" +msgstr "" + +#: includes/post-types.php:147 +msgid "Edit Schedule Override" +msgstr "" + +#: includes/post-types.php:148 +msgid "New Schedule Override" +msgstr "" + +#: includes/post-types.php:149 +msgid "View Schedule Override" +msgstr "" + +#: includes/post-types.php:151 +msgid "Search Overrides" +msgstr "" + +#: includes/post-types.php:152 +msgid "No Overrides found" +msgstr "" + +#: includes/post-types.php:153 +msgid "No Overrides found in Trash" +msgstr "" + +#: includes/post-types.php:155 +msgid "Overrides" +msgstr "" + +#: includes/post-types.php:162 +msgid "Schedule Overrides Archive" +msgstr "" + +#: includes/post-types.php:196 +msgid "Host" +msgstr "" + +#: includes/post-types.php:197 +msgid "Add New Host Profile" +msgstr "" + +#: includes/post-types.php:198 +msgid "Add Host Profile" +msgstr "" + +#: includes/post-types.php:199 +msgid "Edit Host Profile" +msgstr "" + +#: includes/post-types.php:200 +msgid "New Host Profile" +msgstr "" + +#: includes/post-types.php:201 +msgid "View Host Profile" +msgstr "" + +#: includes/post-types.php:202 +msgid "Show Hosts" +msgstr "" + +#: includes/post-types.php:204 +msgid "Search Host Profiles" +msgstr "" + +#: includes/post-types.php:205 +msgid "No Host Profiles found" +msgstr "" + +#: includes/post-types.php:206 +msgid "No Host Profiles found in Trash" +msgstr "" + +#: includes/post-types.php:207 +msgid "All Host Profiles" +msgstr "" + +#: includes/post-types.php:215 +msgid "Host Profiles Archive" +msgstr "" + +#: includes/post-types.php:248 +msgid "Producer" +msgstr "" + +#: includes/post-types.php:249 +msgid "Add New Producer Profile" +msgstr "" + +#: includes/post-types.php:250 +msgid "Add Producer Profile" +msgstr "" + +#: includes/post-types.php:251 +msgid "Edit Producer Profile" +msgstr "" + +#: includes/post-types.php:252 +msgid "New Producer Profile" +msgstr "" + +#: includes/post-types.php:253 +msgid "View Producer Profile" +msgstr "" + +#: includes/post-types.php:254 +msgid "Show Producers Profile" +msgstr "" + +#: includes/post-types.php:256 +msgid "Search Producer Profiles" +msgstr "" + +#: includes/post-types.php:257 +msgid "No Producer Profiles found" +msgstr "" + +#: includes/post-types.php:258 +msgid "No Producer Profiles found in Trash" +msgstr "" + +#: includes/post-types.php:259 +msgid "All Producer Profiles" +msgstr "" + +#: includes/post-types.php:267 +msgid "Producer Profiles Archive" +msgstr "" + +#: includes/post-types.php:404 +msgid "Edit" +msgstr "" + +#: includes/post-types.php:461 +msgctxt "taxonomy general name" +msgid "Genres" +msgstr "" + +#: includes/post-types.php:462 +msgctxt "taxonomy singular name" +msgid "Genre" +msgstr "" + +#: includes/post-types.php:463 +msgid "Search Genres" +msgstr "" + +#: includes/post-types.php:464 +msgid "All Genres" +msgstr "" + +#: includes/post-types.php:465 +msgid "Parent Genre" +msgstr "" + +#: includes/post-types.php:466 +msgid "Parent Genre:" +msgstr "" + +#: includes/post-types.php:467 +msgid "Edit Genre" +msgstr "" + +#: includes/post-types.php:468 +msgid "Update Genre" +msgstr "" + +#: includes/post-types.php:469 +msgid "Add New Genre" +msgstr "" + +#: includes/post-types.php:470 +msgid "New Genre Name" +msgstr "" + +#: includes/post-types.php:471 +msgid "Genre" +msgstr "" + +#: includes/post-types.php:507 +msgctxt "taxonomy general name" +msgid "Languages" +msgstr "" + +#: includes/post-types.php:508 +msgctxt "taxonomy singular name" +msgid "Language" +msgstr "" + +#: includes/post-types.php:509 +msgid "Search Languages" +msgstr "" + +#: includes/post-types.php:510 +msgid "All Languages" +msgstr "" + +#: includes/post-types.php:511 +msgid "Parent Language" +msgstr "" + +#: includes/post-types.php:512 +msgid "Parent Language:" +msgstr "" + +#: includes/post-types.php:513 +msgid "Edit Language" +msgstr "" + +#: includes/post-types.php:514 +msgid "Update Language" +msgstr "" + +#: includes/post-types.php:515 +msgid "Add New Language" +msgstr "" + +#: includes/post-types.php:516 +msgid "New Language Name" +msgstr "" + +#: includes/post-types.php:517 +msgid "Language" +msgstr "" + +#: includes/shortcodes.php:75 includes/shortcodes.php:81 +#: includes/shortcodes.php:94 includes/shortcodes.php:96 +#: templates/single-show-content.php:553 templates/single-show-content.php:560 +msgid "UTC" +msgstr "" + +#: includes/shortcodes.php:111 +msgid "Radio Timezone" +msgstr "" + +#: includes/shortcodes.php:118 +msgid "Your Timezone" +msgstr "" + +#: includes/shortcodes.php:216 +msgid "Radio Time" +msgstr "" + +#: includes/shortcodes.php:226 +msgid "Your Time" +msgstr "" + +#: includes/shortcodes.php:579 +msgid "No Shows in the requested Genre and Language were found." +msgstr "" + +#: includes/shortcodes.php:581 +msgid "No Shows in the requested Genre were found." +msgstr "" + +#: includes/shortcodes.php:583 +msgid "No Shows in the requested Language were found." +msgstr "" + +#: includes/shortcodes.php:585 +msgid "No Shows were found to display." +msgstr "" + +#: includes/shortcodes.php:588 +msgid "No Playlists were found to display." +msgstr "" + +#: includes/shortcodes.php:590 +msgid "No Overrides were found to display." +msgstr "" + +#: includes/shortcodes.php:918 +msgid "No Genres were found to display." +msgstr "" + +#: includes/shortcodes.php:1026 +msgid "No Shows in this Genre." +msgstr "" + +#: includes/shortcodes.php:1182 +msgid "No Languages were found to display." +msgstr "" + +#: includes/shortcodes.php:1278 +msgid "No Shows in this Language." +msgstr "" + +#: includes/shortcodes.php:1594 +msgid "View all posts by %s" +msgstr "" + +#: includes/shortcodes.php:2067 templates/single-show-content.php:502 +msgid "Show Times" +msgstr "" + +#: includes/shortcodes.php:2317 includes/shortcodes.php:2986 +#: templates/single-show-content.php:654 +msgid "Encore Presentation" +msgstr "" + +#: includes/shortcodes.php:2435 +msgid "No Show currently scheduled." +msgstr "" + +#: includes/shortcodes.php:3035 +msgid "No Upcoming Shows Scheduled." +msgstr "" + +#: includes/shortcodes.php:3433 templates/single-playlist-content.php:48 +msgid "Label" +msgstr "" + +#: includes/shortcodes.php:3474 +msgid "No Current Playlist available." +msgstr "" + +#: includes/shortcodes.php:3584 +msgid "This Show has started." +msgstr "" + +#: includes/shortcodes.php:3585 +msgid "This Show has ended." +msgstr "" + +#: includes/shortcodes.php:3586 +msgid "This Playlist has ended." +msgstr "" + +#: includes/shortcodes.php:3587 +msgid "Commencing in" +msgstr "" + +#: includes/shortcodes.php:3588 +msgid "Remaining Time" +msgstr "" + +#: includes/shortcodes.php:3742 +msgid "More Playlists" +msgstr "" + +#: includes/support-functions.php:3928 +msgid "Africa" +msgstr "" + +#: includes/support-functions.php:3929 +msgid "America" +msgstr "" + +#: includes/support-functions.php:3930 +msgid "Asia" +msgstr "" + +#: includes/support-functions.php:3931 +msgid "Atlantic" +msgstr "" + +#: includes/support-functions.php:3932 +msgid "Australia" +msgstr "" + +#: includes/support-functions.php:3933 +msgid "Europe" +msgstr "" + +#: includes/support-functions.php:3934 +msgid "Indian" +msgstr "" + +#: includes/support-functions.php:3935 +msgid "Pacific" +msgstr "" + +#: includes/support-functions.php:3936 +msgid "Antarctica" +msgstr "" + +#: includes/support-functions.php:3976 +msgid "WordPress Timezone" +msgstr "" + +#: includes/support-functions.php:4587 +msgid "WordPress Setting" +msgstr "" + +#: loader.php:1278 +msgid "Plugin Name" +msgstr "" + +#: loader.php:1280 +msgid "Requires at least" +msgstr "" + +#: loader.php:1280 loader.php:1281 +msgid "WordPress" +msgstr "" + +#: loader.php:1281 +msgid "Tested up to" +msgstr "" + +#: loader.php:1283 +msgid "Stable Tag" +msgstr "" + +#: loader.php:1285 +msgid "Contributors" +msgstr "" + +#: loader.php:1311 +msgid "Extra Notes" +msgstr "" + +#: loader.php:1516 +msgid "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" +msgstr "" + +#: loader.php:1616 +msgid "Upgrade" +msgstr "" + +#: loader.php:1623 +msgid "Pro Details" +msgstr "" + +#: loader.php:1636 +msgid "Add Ons" +msgstr "" + +#: loader.php:1690 +msgid "Notices" +msgstr "" + +#: loader.php:1813 +msgid "by" +msgstr "" + +#: loader.php:1823 +msgid "Plugin Homepage" +msgstr "" + +#: loader.php:1823 +msgid "Home" +msgstr "" + +#: loader.php:1827 +msgid "View Plugin" +msgstr "" + +#: loader.php:1827 msgid "Readme" msgstr "" -#: loader.php:1334 -msgid "Docs" +#: loader.php:1830 +msgid "Plugin Documentation" +msgstr "" + +#: loader.php:1830 +msgid "Docs" +msgstr "" + +#: loader.php:1833 +msgid "Plugin Support" +msgstr "" + +#: loader.php:1833 +msgid "Support" +msgstr "" + +#: loader.php:1836 +msgid "Plugin Development" +msgstr "" + +#: loader.php:1836 +msgid "Dev" +msgstr "" + +#: loader.php:1884 radio-station-admin.php:1470 +msgid "Rate on WordPress.Org" +msgstr "" + +#: loader.php:1901 radio-station.php:1589 +msgid "Share the Plugin Love" +msgstr "" + +#: loader.php:1918 radio-station.php:1591 +msgid "Support this Plugin" +msgstr "" + +#: loader.php:1935 +msgid "Settings Updated." +msgstr "" + +#: loader.php:1937 +msgid "Error! Settings NOT Updated." +msgstr "" + +#: loader.php:1939 +msgid "Settings Reset!" +msgstr "" + +#: loader.php:2112 radio-station.php:1490 +msgid "General" +msgstr "" + +#: loader.php:2117 +msgid "Are you sure you want to reset to default settings?" +msgstr "" + +#: loader.php:2218 +msgid "Reset Settings" +msgstr "" + +#: loader.php:2220 +msgid "Save Settings" +msgstr "" + +#: loader.php:2391 +msgid "Use Ctrl and Click to Select" +msgstr "" + +#: loader.php:2430 +msgid "Premium Feature." +msgstr "" + +#: loader.php:2432 +msgid "Upgrade Now" +msgstr "" + +#: loader.php:2439 +msgid "Details" +msgstr "" + +#: loader.php:2442 +msgid "Coming soon in Pro version!" +msgstr "" + +#: loader.php:2767 +msgid "Add Image" +msgstr "" + +#: loader.php:2775 +msgid "Remove Image" +msgstr "" + +#: player/radio-player.php:282 radio-station.php:266 +msgid "Station Logo Image" +msgstr "" + +#: player/radio-player.php:302 +msgid "Station Name" +msgstr "" + +#: player/radio-player.php:323 +msgid "Play Radio Stream" +msgstr "" + +#: player/radio-player.php:334 +msgid "Mute" +msgstr "" + +#: player/radio-player.php:337 +msgid "Volume Down" +msgstr "" + +#: player/radio-player.php:342 radio-station.php:521 +msgid "Volume Slider" +msgstr "" + +#: player/radio-player.php:353 +msgid "Volume Up" +msgstr "" + +#: player/radio-player.php:356 +msgid "Max" +msgstr "" + +#: player/radio-player.php:380 +msgid "Show Title" +msgstr "" + +#: player/radio-player.php:386 +msgid "Show Logo Image" +msgstr "" + +#: radio-station-admin.php:159 +msgid "You do not have permissions to access that page." +msgstr "" + +#: radio-station-admin.php:209 +msgid "Import/Export Shows" +msgstr "" + +#: radio-station-admin.php:214 +msgid "Documentation" +msgstr "" + +#: radio-station-admin.php:214 +msgid "Help" +msgstr "" + +#: radio-station-admin.php:334 +msgid "Role Assignments" +msgstr "" + +#: radio-station-admin.php:335 +msgid "You can assign a Radio Station role to users through the WordPress User editor." +msgstr "" + +#: radio-station-admin.php:339 +msgid "Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user." +msgstr "" + +#: radio-station-admin.php:346 +msgid "Find out more about Radio Station Pro" +msgstr "" + +#: radio-station-admin.php:453 +msgid "Back to Documentation Index" +msgstr "" + +#: radio-station-admin.php:612 +msgid "Right-click and download this file to save your export" +msgstr "" + +#: radio-station-admin.php:752 radio-station-admin.php:826 +msgid "Take a moment to Update for a better experience. In this update" +msgstr "" + +#: radio-station-admin.php:820 +msgid "A new version of" +msgstr "" + +#: radio-station-admin.php:822 +msgid "is available." +msgstr "" + +#: radio-station-admin.php:835 +msgid "Update Now" +msgstr "" + +#: radio-station-admin.php:838 radio-station-admin.php:921 +msgid "Full Update Details" +msgstr "" + +#: radio-station-admin.php:849 radio-station-admin.php:940 +#: radio-station-admin.php:1243 radio-station-admin.php:1326 +#: radio-station-admin.php:1481 +msgid "Dismiss this Notice" +msgstr "" + +#: radio-station-admin.php:902 +msgid "Update Notice" +msgstr "" + +#: radio-station-admin.php:908 +msgid "Thanks for Updating! You can enjoy these improvements now" +msgstr "" + +#: radio-station-admin.php:928 +msgid "Plugin Settings" +msgstr "" + +#: radio-station-admin.php:1200 +msgid "Time Sensitive Free Offer" +msgstr "" + +#: radio-station-admin.php:1203 +msgid "We are excited to announce the opening of the new" +msgstr "" + +#: radio-station-admin.php:1205 +msgid "Allowing listeners to newly discover Stations and Shows - which can include yours..." +msgstr "" + +#: radio-station-admin.php:1207 +msgid "Because while launching," +msgstr "" + +#: radio-station-admin.php:1207 +msgid "we are offering 30 days free listing to all Radio Station users!" +msgstr "" + +#: radio-station-admin.php:1208 +msgid "Interested in more exposure and listeners for your Radio Station, for free?" +msgstr "" + +#: radio-station-admin.php:1219 +msgid "Yes please!" +msgstr "" + +#: radio-station-admin.php:1222 +msgid "More Details" +msgstr "" + +#: radio-station-admin.php:1226 +msgid "Great! I'm listed, dismiss this notice." +msgstr "" + +#: radio-station-admin.php:1231 +msgid "Offer valid until end of July 2020." +msgstr "" + +#: radio-station-admin.php:1232 +msgid "Activate your 30 days before it ends!" +msgstr "" + +#: radio-station-admin.php:1278 +msgid "Radio Station Pro Launch Discount!" +msgstr "" + +#: radio-station-admin.php:1280 +msgid "We are thrilled to announce the upcoming launch of Radio Station PRO" +msgstr "" + +#: radio-station-admin.php:1281 radio-station-admin.php:1289 +msgid "Jam-packed with new features to \"level up\" your Station's online presence." +msgstr "" + +#: radio-station-admin.php:1282 +msgid "During the launch," +msgstr "" + +#: radio-station-admin.php:1282 radio-station-admin.php:1290 +msgid "we are offering 30% discount to existing Radio Station users!" +msgstr "" + +#: radio-station-admin.php:1283 +msgid "Sign up to the exclusive launch list to receive your discount code when we go LIVE." +msgstr "" + +#: radio-station-admin.php:1286 +msgid "Radio Station PRO Launch is LIVE!" +msgstr "" + +#: radio-station-admin.php:1288 +msgid "The long anticipated moment has arrived. The doors are open to get PRO" +msgstr "" + +#: radio-station-admin.php:1290 +msgid "Remember," +msgstr "" + +#: radio-station-admin.php:1292 +msgid "Sign up here to receive your exclusive launch discount code." +msgstr "" + +#: radio-station-admin.php:1307 +msgid "Yes, I'm in!" +msgstr "" + +#: radio-station-admin.php:1309 +msgid "Go PRO" +msgstr "" + +#: radio-station-admin.php:1314 +msgid "Thanks, already done." +msgstr "" + +#: radio-station-admin.php:1450 +msgid "Help support us to make improvements, modifications and introduce new features!" +msgstr "" + +#: radio-station-admin.php:1451 +msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" +msgstr "" + +#: radio-station-admin.php:1452 +msgid "since June 2019" +msgstr "" + +#: radio-station-admin.php:1454 +msgid " plugin development has been actively taken over by" +msgstr "" + +#: radio-station-admin.php:1457 +msgid "And due to our continued efforts we now have a community of over two thousand active stations!" +msgstr "" + +#: radio-station-admin.php:1458 +msgid "We invite you to" +msgstr "" + +#: radio-station-admin.php:1460 +msgid "Become a Radio Station Patreon Supporter" +msgstr "" + +#: radio-station-admin.php:1461 +msgid "to make it better for everyone" +msgstr "" + +#: radio-station-admin.php:1529 +msgid "has detected" +msgstr "" + +#: radio-station-admin.php:1530 +msgid "Schedule conflicts!" +msgstr "" + +#: radio-station-admin.php:1534 +msgid "The following Shows have conflicting Shift times" +msgstr "" + +#: radio-station-admin.php:1573 +msgid "Go to Show List" +msgstr "" + +#: radio-station-admin.php:1574 +msgid "Conflicts are highlighted" +msgstr "" + +#: radio-station-admin.php:1575 +msgid "in Show Shift column." +msgstr "" + +#: radio-station-admin.php:1580 +msgid "This notice will persist" +msgstr "" + +#: radio-station-admin.php:1581 +msgid "until conflicts are resolved." +msgstr "" + +#: radio-station-admin.php:1630 +msgid "Stay tuned! Subscribe to Radio Station's" +msgstr "" + +#: radio-station-admin.php:1632 +msgid "Plugin Updates and Announcements List" +msgstr "" + +#: radio-station.php:198 +msgid "Streaming URL" +msgstr "" + +#: radio-station.php:200 +msgid "Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player." +msgstr "" + +#: radio-station.php:209 +msgid "Streaming Format" +msgstr "" + +#: radio-station.php:211 +msgid "Select streaming format for streaming URL." +msgstr "" + +#: radio-station.php:220 +msgid "Fallback Stream URL" +msgstr "" + +#: radio-station.php:222 +msgid "Enter an alternative Streaming URL for Player fallback." +msgstr "" + +#: radio-station.php:231 +msgid "Fallback Format" +msgstr "" + +#: radio-station.php:233 +msgid "Select streaming fallback for fallback URL." +msgstr "" + +#: radio-station.php:242 +msgid "Main Broadcast Language" +msgstr "" + +#: radio-station.php:244 +msgid "Select the main language used on your Radio Station." +msgstr "" + +#: radio-station.php:255 +msgid "Station Title" +msgstr "" + +#: radio-station.php:257 +msgid "Name of your Radio Station. For use in Stream Player and Data Feeds." +msgstr "" + +#: radio-station.php:268 +msgid "Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256" +msgstr "" + +#: radio-station.php:277 +msgid "Location Timezone" +msgstr "" + +#: radio-station.php:279 +msgid "Select your Broadcast Location for Radio Timezone display." +msgstr "" + +#: radio-station.php:288 +msgid "12 Hour Format" +msgstr "" + +#: radio-station.php:289 +msgid "24 Hour Format" +msgstr "" + +#: radio-station.php:291 +msgid "Clock Time Format" +msgstr "" + +#: radio-station.php:293 +msgid "Default Time Format for display output. Can be overridden in each shortcode or widget." +msgstr "" + +#: radio-station.php:303 +msgid "Station Phone" msgstr "" -#: loader.php:1335 -msgid "Support" +#: radio-station.php:305 +msgid "Main call in phone number for the Station (for requests etc.)" msgstr "" -#: loader.php:1336 -msgid "Dev" +#: radio-station.php:316 +msgid "Show Phone Display" msgstr "" -#: loader.php:1374 radio-station-admin.php:432 -msgid "Rate on WordPress.Org" +#: radio-station.php:317 +msgid "Display Station phone number on Shows where a Show phone number is not set." msgstr "" -#: loader.php:1385 -msgid "Share the Plugin Love" +#: radio-station.php:327 +msgid "Station Email" msgstr "" -#: loader.php:1396 radio-station.php:581 -msgid "Support this Plugin" +#: radio-station.php:328 +msgid "Main email address for the Station (for requests etc.)" msgstr "" -#: loader.php:1409 -msgid "Settings Updated." +#: radio-station.php:339 +msgid "Show Email Display" msgstr "" -#: loader.php:1411 -msgid "Error! Settings NOT Updated." +#: radio-station.php:340 +msgid "Display Station email address on Shows where a Show email address is not set." msgstr "" -#: loader.php:1413 -msgid "Settings Reset!" +#: radio-station.php:350 +msgid "Enable Data Routes" msgstr "" -#: loader.php:1531 radio-station.php:542 -msgid "General" +#: radio-station.php:353 +msgid "Enables Station Data Routes via WordPress REST API." msgstr "" -#: loader.php:1536 -msgid "Are you sure you want to reset to default settings?" +#: radio-station.php:361 +msgid "Enable Data Feeds" msgstr "" -#: loader.php:1628 -msgid "Reset Settings" +#: radio-station.php:364 +msgid "Enable Station Data Feeds via WordPress Feed links." msgstr "" -#: loader.php:1630 -msgid "Save Settings" +#: radio-station.php:373 +msgid "Ping Netmix Directory" msgstr "" -#: loader.php:1710 -msgid "Use Ctrl and Click to Select" +#: radio-station.php:376 +msgid "If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule." msgstr "" -#: loader.php:1737 -msgid "Available in Pro Version." +#: radio-station.php:388 +msgid "Disable Transients" msgstr "" -#: loader.php:1738 -msgid "Click Here to Upgrade!" +#: radio-station.php:391 +msgid "Clear Schedule transients with every pageload. Less efficient but more reliable." msgstr "" -#: loader.php:1740 -msgid "Coming soon in Pro version!" +#: radio-station.php:399 +msgid "Show Transients" msgstr "" -#: radio-station-admin.php:103 -msgid "You do not have permissions to access that page." +#: radio-station.php:402 +msgid "Use Show Transient Data to improve Schedule calculation performance." msgstr "" -#: radio-station-admin.php:116 radio-station-admin.php:121 -#: radio-station-admin.php:419 radio-station-admin.php:480 -#: radio-station-admin.php:594 radio-station-admin.php:661 -msgid "Radio Station" +#: radio-station.php:427 +msgid "Display Station Title" msgstr "" -#: radio-station-admin.php:142 templates/admin-export.php:2 -msgid "Export Playlists" +#: radio-station.php:430 +msgid "Display your Radio Station Title in Player by default." msgstr "" -#: radio-station-admin.php:144 -msgid "Help" +#: radio-station.php:442 +msgid "Display your Radio Station Image in Player by default." msgstr "" -#: radio-station-admin.php:236 -msgid "Role Assignments" +#: radio-station.php:452 +msgid "Player Script" msgstr "" -#: radio-station-admin.php:237 -msgid "You can assign a Radio Station role to users through the WordPress User editor." +#: radio-station.php:459 +msgid "Default audio script to use for playback in the Player." msgstr "" -#: radio-station-admin.php:358 -msgid "Right-click and download this file to save your export" +#: radio-station.php:470 +msgid "Fallback Scripts" msgstr "" -#: radio-station-admin.php:416 -msgid "Help support us to make improvements, modifications and introduce new features!" +#: radio-station.php:477 +msgid "Enabled fallback audio scripts to try when the default Player script fails." msgstr "" -#: radio-station-admin.php:417 -msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" +#: radio-station.php:486 +msgid "Default Player Theme" msgstr "" -#: radio-station-admin.php:418 -msgid "since June 2019" +#: radio-station.php:492 +msgid "Default Player Controls theme style." msgstr "" -#: radio-station-admin.php:420 -msgid " plugin development has been actively taken over by" +#: radio-station.php:501 +msgid "Default Player Buttons" msgstr "" -#: radio-station-admin.php:422 -msgid "We invite you to" +#: radio-station.php:504 +msgid "Circular Buttons" msgstr "" -#: radio-station-admin.php:424 -msgid "Become a Radio Station Patreon Supporter" +#: radio-station.php:505 +msgid "Rounded Buttons" msgstr "" -#: radio-station-admin.php:425 -msgid "to make it better for everyone" +#: radio-station.php:506 +msgid "Square Buttons" msgstr "" -#: radio-station-admin.php:441 radio-station-admin.php:619 -#: radio-station-admin.php:690 -msgid "Dismiss this Notice" +#: radio-station.php:508 +msgid "Default Player Buttons shape style." msgstr "" -#: radio-station-admin.php:481 -msgid "has detected" +#: radio-station.php:518 +msgid "Volume Controls" msgstr "" -#: radio-station-admin.php:482 -msgid "Schedule conflicts!" +#: radio-station.php:522 +msgid "Volume Plus / Minus" msgstr "" -#: radio-station-admin.php:486 -msgid "The following Shows have conflicting Shift times" +#: radio-station.php:523 +msgid "Mute Volume Toggle" msgstr "" -#: radio-station-admin.php:518 -msgid "Go to Show List" +#: radio-station.php:524 +msgid "Maximize Volume" msgstr "" -#: radio-station-admin.php:519 -msgid "Conflicts are highlighted" +#: radio-station.php:526 +msgid "Which volume controls to display in the Player by default." msgstr "" -#: radio-station-admin.php:520 -msgid "in Show Shift column." +#: radio-station.php:534 +msgid "Player Debug Mode" msgstr "" -#: radio-station-admin.php:525 -msgid "This notice will persist" +#: radio-station.php:537 +msgid "Output player debug information in browser javascript console." msgstr "" -#: radio-station-admin.php:526 -msgid "until conflicts are resolved." +#: radio-station.php:548 +msgid "Playing Icon Highlight Color" msgstr "" -#: radio-station-admin.php:593 -msgid "A new version of" +#: radio-station.php:550 +msgid "Default highlight color to use for Play button icon when playing." msgstr "" -#: radio-station-admin.php:595 -msgid "is available." +#: radio-station.php:559 +msgid "Control Icons Highlight Color" msgstr "" -#: radio-station-admin.php:599 -msgid "Take a moment to Upgrade for a better experience. In this update..." +#: radio-station.php:561 +msgid "Default highlight color to use for Control button icons when active." msgstr "" -#: radio-station-admin.php:605 radio-station-admin.php:674 -msgid "Update Details" +#: radio-station.php:570 +msgid "Volume Knob Color" msgstr "" -#: radio-station-admin.php:610 -msgid "Update Now" +#: radio-station.php:572 +msgid "Default Knob Color for Player Volume Slider." msgstr "" -#: radio-station-admin.php:662 -msgid "Update Notice" +#: radio-station.php:581 +msgid "Volume Track Color" msgstr "" -#: radio-station-admin.php:667 -msgid "Thanks for Upgrading! You can enjoy these improvements now" +#: radio-station.php:583 +msgid "Default Track Color for Player Volume Slider." msgstr "" -#: radio-station-admin.php:680 -msgid "Plugin Settings" +#: radio-station.php:599 +msgid "Initial volume for when the Player starts playback." msgstr "" -#: radio-station-admin.php:856 -msgid "Stay tuned! Subscribe to Radio Station's" +#: radio-station.php:608 +msgid "Single Player at Once" msgstr "" -#: radio-station-admin.php:857 -msgid "Plugin Updates and Announcements List" +#: radio-station.php:611 +msgid "Stop any existing Players on the page or in other windows or tabs when a Player is started." msgstr "" -#: radio-station.php:115 -msgid "Streaming URL" +#: radio-station.php:620 +msgid "Autoresume Playback" msgstr "" -#: radio-station.php:117 -msgid "Enter the Streaming URL for your Radio Station." +#: radio-station.php:623 +msgid "Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page." msgstr "" -#: radio-station.php:125 -msgid "Main Broadcast Language" +#: radio-station.php:646 +msgid "Bar Defaults Note" msgstr "" -#: radio-station.php:127 -msgid "Select the main language used on your Radio Station." +#: radio-station.php:647 +msgid "The Bar Player uses the default configurations set above." msgstr "" -#: radio-station.php:147 -msgid "Location Timezone" +#: radio-station.php:648 +msgid "You can override these in specific Player Widgets." msgstr "" -#: radio-station.php:149 -msgid "Select your Broadcast Location for Timezone display." +#: radio-station.php:656 +msgid "Sitewide Player Bar" msgstr "" -#: radio-station.php:157 -msgid "12 Hour Format" +#: radio-station.php:659 +msgid "No Player Bar" msgstr "" -#: radio-station.php:158 -msgid "24 Hour Format" +#: radio-station.php:660 +msgid "Top Player Bar" msgstr "" -#: radio-station.php:160 -msgid "Clock Time Format" +#: radio-station.php:661 +msgid "Bottom Player Bar" msgstr "" -#: radio-station.php:162 -msgid "Default Time Format Display for plugin output. Can be overridden in each shortcode or widget." +#: radio-station.php:665 +msgid "Add a fixed position Player Bar which displays Sitewide." msgstr "" -#: radio-station.php:169 -msgid "Enable Data Routes" +#: radio-station.php:686 +msgid "Fade In Player Bar" msgstr "" -#: radio-station.php:172 -msgid "Enables Station Data Routes via WordPress REST API." +#: radio-station.php:691 +msgid "Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display." msgstr "" -#: radio-station.php:179 -msgid "Enable Data Feeds" +#: radio-station.php:701 +msgid "Continuous Playback" msgstr "" -#: radio-station.php:182 -msgid "Enable Station Data Feeds via WordPress feed links." +#: radio-station.php:704 +msgid "Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists." msgstr "" -#: radio-station.php:189 -msgid "Show Shift Feeds" +#: radio-station.php:713 +msgid "Page Fade Time" msgstr "" -#: radio-station.php:192 -msgid "Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts." +#: radio-station.php:718 +msgid "Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display." msgstr "" -#: radio-station.php:200 -msgid "Transient Caching" +#: radio-station.php:733 +msgid "Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)" msgstr "" -#: radio-station.php:203 -msgid "Use Transient Caching to improve Schedule calculation performance." +#: radio-station.php:742 +msgid "Bar Player Text Color" msgstr "" -#: radio-station.php:213 -msgid "Master Schedule Page" +#: radio-station.php:744 +msgid "Text color for the fixed position Sitewide Bar Player." msgstr "" -#: radio-station.php:215 -msgid "Select the Page you are displaying the Master Schedule on." +#: radio-station.php:753 +msgid "Bar Player Background Color" msgstr "" -#: radio-station.php:222 radio-station.php:345 radio-station.php:376 -#: radio-station.php:407 -msgid "Automatic Display" +#: radio-station.php:755 +msgid "Background color for the fixed position Sitewide Bar Player." +msgstr "" + +#: radio-station.php:783 +msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)" +msgstr "" + +#: radio-station.php:808 +msgid "Master Schedule Page" +msgstr "" + +#: radio-station.php:810 +msgid "Select the Page you are displaying the Master Schedule on." msgstr "" -#: radio-station.php:225 +#: radio-station.php:821 msgid "Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: " msgstr "" -#: radio-station.php:232 +#: radio-station.php:829 msgid "Schedule View Default" msgstr "" -#: radio-station.php:235 +#: radio-station.php:832 radio-station.php:879 msgid "Table View" msgstr "" -#: radio-station.php:236 +#: radio-station.php:833 radio-station.php:881 msgid "List View" msgstr "" -#: radio-station.php:237 +#: radio-station.php:834 msgid "Divs View" msgstr "" -#: radio-station.php:238 +#: radio-station.php:835 radio-station.php:880 msgid "Tabbed View" msgstr "" -#: radio-station.php:239 +#: radio-station.php:836 msgid "Legacy Table" msgstr "" -#: radio-station.php:241 +#: radio-station.php:838 msgid "View type to use for automatic display on Master Schedule Page." msgstr "" -#: radio-station.php:249 +#: radio-station.php:846 +msgid "Schedule Clock?" +msgstr "" + +#: radio-station.php:850 +msgid "Clock" +msgstr "" + +#: radio-station.php:851 templates/single-show-content.php:548 +msgid "Timezone" +msgstr "" + +#: radio-station.php:853 +msgid "Radio Time section display above program Schedule." +msgstr "" + +#: radio-station.php:861 msgid "View Switching" msgstr "" -#: radio-station.php:252 -msgid "Enable View Switching on the Master Schedule." +#: radio-station.php:864 +msgid "Enable View Switching on the automatic Master Schedule page." +msgstr "" + +#: radio-station.php:874 +msgid "Available Views" +msgstr "" + +#: radio-station.php:882 +msgid "Grid View" +msgstr "" + +#: radio-station.php:883 +msgid "Calendar View" +msgstr "" + +#: radio-station.php:885 +msgid "Switcher Views available on automatic Master Schedule page." msgstr "" -#: radio-station.php:261 +#: radio-station.php:909 radio-station.php:1022 radio-station.php:1056 msgid "Info Blocks Position" msgstr "" -#: radio-station.php:263 +#: radio-station.php:911 radio-station.php:1024 radio-station.php:1058 msgid "Float Left" msgstr "" -#: radio-station.php:264 +#: radio-station.php:912 radio-station.php:1025 radio-station.php:1059 msgid "Float Right" msgstr "" -#: radio-station.php:265 +#: radio-station.php:913 radio-station.php:1026 radio-station.php:1060 msgid "Float Top" msgstr "" -#: radio-station.php:268 +#: radio-station.php:916 msgid "Where to position Show info blocks relative to Show Page content." msgstr "" -#: radio-station.php:275 -msgid "Content Header Image" +#: radio-station.php:924 +msgid "Show Content Layout" msgstr "" -#: radio-station.php:278 -msgid "If your template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." +#: radio-station.php:926 radio-station.php:1040 radio-station.php:1074 +msgid "Tabbed" msgstr "" -#: radio-station.php:285 templates/single-show-content.php:617 -msgid "Latest Show Posts" +#: radio-station.php:927 radio-station.php:1041 radio-station.php:1075 +msgid "Standard" +msgstr "" + +#: radio-station.php:930 +msgid "How to display extra sections below Show description. In content tabs or standard layout down the page." +msgstr "" + +#: radio-station.php:939 +msgid "Content Header Images" msgstr "" -#: radio-station.php:290 -msgid "Number of Latest Blog Posts to Show above Show Page tabs." +#: radio-station.php:942 +msgid "If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." msgstr "" -#: radio-station.php:297 +#: radio-station.php:963 msgid "Posts per Page" msgstr "" -#: radio-station.php:302 -msgid "Blog Posts per page on the Show Page tab." +#: radio-station.php:968 +msgid "Linked Show Posts per page on the Show Page tab/display." msgstr "" -#: radio-station.php:312 +#: radio-station.php:979 msgid "Playlists per Page" msgstr "" -#: radio-station.php:314 -msgid "Playlists per page on the Show Page tab." +#: radio-station.php:981 +msgid "Playlists per page on the Show Page tab/display" msgstr "" -#: radio-station.php:335 -msgid "Show Archives Page" +#: radio-station.php:989 +msgid "Episodes per Page" msgstr "" -#: radio-station.php:339 +#: radio-station.php:994 +msgid "Number of Show Episodes per page on the Show page tab/display." +msgstr "" + +#: radio-station.php:1003 +msgid "Combined Team Tab" +msgstr "" + +#: radio-station.php:1006 +msgid "Do Not Combine" +msgstr "" + +#: radio-station.php:1007 +msgid "Combined List" +msgstr "" + +#: radio-station.php:1010 +msgid "Combine team members (eg. hosts, producers) into a single display tab." +msgstr "" + +#: radio-station.php:1029 +msgid "Where to position Profile info blocks relative to Profile Page content." +msgstr "" + +#: radio-station.php:1038 +msgid "Profile Content Layout" +msgstr "" + +#: radio-station.php:1044 +msgid "How to display extra sections below Profile description. In content tabs or standard layout down the page." +msgstr "" + +#: radio-station.php:1063 +msgid "Where to position Episode info blocks relative to Episode Page content." +msgstr "" + +#: radio-station.php:1072 +msgid "Episode Content Layout" +msgstr "" + +#: radio-station.php:1078 +msgid "How to display extra sections below Episode description. In content tabs or standard layout down the page." +msgstr "" + +#: radio-station.php:1091 +msgid "Shows Archive Page" +msgstr "" + +#: radio-station.php:1095 msgid "Select the Page for displaying the Show archive list." msgstr "" -#: radio-station.php:349 +#: radio-station.php:1106 msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" msgstr "" -#: radio-station.php:366 -msgid "Playlist Archives Page" +#: radio-station.php:1124 +msgid "Overrides Archive Page" +msgstr "" + +#: radio-station.php:1128 +msgid "Select the Page for displaying the Override archive list." +msgstr "" + +#: radio-station.php:1139 +msgid "Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: radio-station.php:1157 +msgid "Playlists Archive Page" msgstr "" -#: radio-station.php:370 +#: radio-station.php:1161 msgid "Select the Page for displaying the Playlist archive list." msgstr "" -#: radio-station.php:380 +#: radio-station.php:1172 msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" msgstr "" -#: radio-station.php:397 -msgid "Genre Archives Page" +#: radio-station.php:1224 +msgid "Genres Archive Page" msgstr "" -#: radio-station.php:401 +#: radio-station.php:1228 msgid "Select the Page for displaying the Genre archive list." msgstr "" -#: radio-station.php:411 +#: radio-station.php:1239 msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" msgstr "" -#: radio-station.php:430 +#: radio-station.php:1258 +msgid "Languages Archive Page" +msgstr "" + +#: radio-station.php:1262 +msgid "Select the Page for displaying the Language archive list." +msgstr "" + +#: radio-station.php:1274 +msgid "Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: radio-station.php:1295 msgid "Templates Change Note" msgstr "" -#: radio-station.php:431 +#: radio-station.php:1296 msgid "Since 2.3.0, the way that Templates are implemented has changed." msgstr "" -#: radio-station.php:432 +#: radio-station.php:1297 msgid "See the Documentation for more information:" msgstr "" -#: radio-station.php:433 +#: radio-station.php:1298 msgid "Templates Documentation" msgstr "" -#: radio-station.php:438 +#: radio-station.php:1305 msgid "Show Template" msgstr "" -#: radio-station.php:441 radio-station.php:464 +#: radio-station.php:1308 radio-station.php:1336 msgid "Theme Page Template (page.php)" msgstr "" -#: radio-station.php:442 radio-station.php:465 +#: radio-station.php:1309 radio-station.php:1337 msgid "Theme Post Template (single.php)" msgstr "" -#: radio-station.php:443 +#: radio-station.php:1310 radio-station.php:1338 msgid "Theme Singular Template (singular.php)" msgstr "" -#: radio-station.php:444 radio-station.php:466 +#: radio-station.php:1311 radio-station.php:1339 msgid "Legacy Plugin Template" msgstr "" -#: radio-station.php:447 +#: radio-station.php:1314 msgid "Which template to use for displaying Show content." msgstr "" -#: radio-station.php:452 radio-station.php:474 +#: radio-station.php:1321 radio-station.php:1349 msgid "Combined Method" msgstr "" -#: radio-station.php:456 +#: radio-station.php:1325 msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" msgstr "" -#: radio-station.php:461 +#: radio-station.php:1333 msgid "Playlist Template" msgstr "" -#: radio-station.php:469 +#: radio-station.php:1342 msgid "Which template to use for displaying Playlist content." msgstr "" -#: radio-station.php:478 +#: radio-station.php:1353 msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" msgstr "" -#: radio-station.php:488 +#: radio-station.php:1364 +msgid "AJAX Load Widgets?" +msgstr "" + +#: radio-station.php:1367 +msgid "Defaults plugin widgets to AJAX loading. Can also be set on individual widgets." +msgstr "" + +#: radio-station.php:1375 +msgid "Dynamic Reloading?" +msgstr "" + +#: radio-station.php:1378 +msgid "Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets." +msgstr "" + +#: radio-station.php:1387 +msgid "Convert Show Times" +msgstr "" + +#: radio-station.php:1390 +msgid "Automatically display Show times converted into the visitor timezone, based on their browser setting." +msgstr "" + +#: radio-station.php:1399 +msgid "User Timezone Switching" +msgstr "" + +#: radio-station.php:1402 +msgid "Allow visitors to select their Timezone manually for Show time translations." +msgstr "" + +#: radio-station.php:1415 +msgid "Show Editing Permissions" +msgstr "" + +#: radio-station.php:1416 +msgid "By default, only Hosts and Producers that are assigned to a Show can edit that Show." +msgstr "" + +#: radio-station.php:1417 +msgid "This means an Administrator or Show Editor must assign these users to the Show first." +msgstr "" + +#: radio-station.php:1426 +msgid "Playlist Permissions" +msgstr "" + +#: radio-station.php:1427 +msgid "Any user with a Host or Producer role can create Playlists." +msgstr "" + +#: radio-station.php:1435 msgid "Show Editor Role" msgstr "" -#: radio-station.php:489 +#: radio-station.php:1436 msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." msgstr "" -#: radio-station.php:490 +#: radio-station.php:1437 msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." msgstr "" -#: radio-station.php:507 +#: radio-station.php:1438 +msgid "This is so a manager can edit the schedule without requiring full site administration role." +msgstr "" + +#: radio-station.php:1446 msgid "Add to Author Capabilities" msgstr "" -#: radio-station.php:510 +#: radio-station.php:1449 msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." msgstr "" -#: radio-station.php:516 +#: radio-station.php:1457 msgid "Add to Editor Capabilities" msgstr "" -#: radio-station.php:519 +#: radio-station.php:1460 msgid "Allow users with WordPress Editor role to edit all Radio Station post types." msgstr "" -#: radio-station.php:543 +#: radio-station.php:1492 msgid "Pages" msgstr "" -#: radio-station.php:544 -msgid "Templates" +#: radio-station.php:1493 radio-station.php:1519 +msgid "Archives" msgstr "" -#: radio-station.php:545 -msgid "Roles" +#: radio-station.php:1495 +msgid "Widgets" msgstr "" -#: radio-station.php:548 -msgid "Station" +#: radio-station.php:1496 +msgid "Roles" msgstr "" -#: radio-station.php:549 +#: radio-station.php:1505 msgid "Broadcast" msgstr "" -#: radio-station.php:550 -msgid "Times" +#: radio-station.php:1506 +msgid "Station" msgstr "" -#: radio-station.php:551 +#: radio-station.php:1507 msgid "Feeds" msgstr "" -#: radio-station.php:552 -msgid "Single Templates" +#: radio-station.php:1508 +msgid "Performance" +msgstr "" + +#: radio-station.php:1509 +msgid "Basic Defaults" +msgstr "" + +#: radio-station.php:1510 +msgid "Advanced Defaults" +msgstr "" + +#: radio-station.php:1511 +msgid "Player Colors" msgstr "" -#: radio-station.php:553 -msgid "Archive Templates" +#: radio-station.php:1512 +msgid "Sitewide Bar Player" msgstr "" -#: radio-station.php:554 +#: radio-station.php:1513 msgid "Schedule Page" msgstr "" -#: radio-station.php:555 +#: radio-station.php:1514 +msgid "Single Templates" +msgstr "" + +#: radio-station.php:1516 msgid "Show Pages" msgstr "" -#: radio-station.php:556 -msgid "Archives" +#: radio-station.php:1517 +msgid "Profile Pages" +msgstr "" + +#: radio-station.php:1518 +msgid "Episode Pages" +msgstr "" + +#: radio-station.php:1520 +msgid "Post Types" +msgstr "" + +#: radio-station.php:1521 +msgid "Taxonomies" +msgstr "" + +#: radio-station.php:1522 +msgid "Widget Loading" msgstr "" -#: radio-station.php:557 +#: radio-station.php:1523 msgid "Permissions" msgstr "" -#: radio-station.php:577 +#: radio-station.php:1587 msgid "Rate on WordPress.org" msgstr "" -#: radio-station.php:1406 -msgid "DJ / Host" +#: radio-station.php:2014 +msgid "Second" msgstr "" -#: radio-station.php:1418 -msgid "Show Producer" +#: radio-station.php:2015 +msgid "Seconds" msgstr "" -#: radio-station.php:1491 -msgid "Show Editor" +#: radio-station.php:2016 +msgid "Minute" msgstr "" -#: templates/admin-export.php:14 -msgid "Start Date" +#: radio-station.php:2017 +msgid "Minutes" msgstr "" -#: templates/admin-export.php:84 -msgid "End Date" +#: radio-station.php:2018 +msgid "Hour" msgstr "" -#: templates/admin-export.php:156 -msgid "Export" +#: radio-station.php:2019 +msgid "Hours" +msgstr "" + +#: radio-station.php:2021 +msgid "Days" +msgstr "" + +#: radio-station.php:2928 +msgid "%s for Shows" +msgstr "" + +#: radio-station.php:2930 +msgid "%s for Show" +msgstr "" + +#: radio-station.php:2939 +msgid "More %s for Shows" +msgstr "" + +#: radio-station.php:2941 +msgid "More %s for Show" +msgstr "" + +#: radio-station.php:3206 +msgid "Previous Related Show" +msgstr "" + +#: radio-station.php:3211 +msgid "Next Related Show" +msgstr "" + +#: radio-station.php:3350 radio-station.php:3369 radio-station.php:3370 +#: radio-station.php:3580 +msgid "DJ / Host" +msgstr "" + +#: radio-station.php:3374 +msgid "Show Producer" +msgstr "" + +#: radio-station.php:3454 +msgid "Show Editor" msgstr "" #: templates/legacy/archive-playlist.php:19 @@ -1614,15 +4229,15 @@ msgstr "" msgid "Newer posts" msgstr "" -#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:100 +#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:101 #: templates/legacy/playlist-archive-template.php:66 -#: templates/legacy/show-blog-archive-template.php:76 +#: templates/legacy/show-blog-archive-template.php:78 msgid "Nothing Found" msgstr "" -#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:104 +#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:105 #: templates/legacy/playlist-archive-template.php:70 -#: templates/legacy/show-blog-archive-template.php:80 +#: templates/legacy/show-blog-archive-template.php:82 msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." msgstr "" @@ -1630,7 +4245,7 @@ msgstr "" msgid "Author Archives: %s" msgstr "" -#: templates/legacy/author.php:74 +#: templates/legacy/author.php:75 msgid "About %s" msgstr "" @@ -1642,7 +4257,7 @@ msgstr "" msgid "Blog Archive" msgstr "" -#: templates/legacy/show-blog-archive-template.php:53 +#: templates/legacy/show-blog-archive-template.php:55 msgid "Posted by" msgstr "" @@ -1658,124 +4273,71 @@ msgstr "" msgid "No entries for this playlist" msgstr "" -#: templates/master-schedule-div.php:143 -#: templates/master-schedule-legacy.php:219 -#: templates/master-schedule-list.php:131 -#: templates/master-schedule-table.php:242 -#: templates/master-schedule-tabs.php:146 -msgid "encore airing" +#: templates/single-playlist-content.php:17 +msgid "Playlist for Show" msgstr "" -#: templates/master-schedule-div.php:151 -#: templates/master-schedule-legacy.php:232 -#: templates/master-schedule-list.php:144 -#: templates/master-schedule-table.php:254 -#: templates/master-schedule-tabs.php:159 -msgid "Audio File" +#: templates/single-playlist-content.php:80 +msgid "No played tracks found for this Playlist yet." msgstr "" -#: templates/master-schedule-list.php:117 -#: templates/master-schedule-tabs.php:133 -msgid "to" +#: templates/single-playlist-content.php:88 +msgid "No entries found for this Playlist" msgstr "" -#: templates/master-schedule-tabs.php:187 -msgid "No Shows found for this day." +#: templates/single-playlist-content.php:95 +msgid "All Playlists for Show" msgstr "" -#: templates/single-playlist-content.php:38 -msgid "No entries for this Playlist" +#: templates/single-show-content.php:98 +msgid "Visit Show Website" msgstr "" -#: templates/single-show-content.php:63 -msgid "Show Website" +#: templates/single-show-content.php:113 +msgid "Call in Phone Number" msgstr "" -#: templates/single-show-content.php:75 +#: templates/single-show-content.php:128 msgid "Email Show Host" msgstr "" -#: templates/single-show-content.php:88 -msgid "Show RSS Feed" -msgstr "" - -#: templates/single-show-content.php:191 -msgid "Download Latest Broadcast" -msgstr "" - -#: templates/single-show-content.php:209 +#: templates/single-show-content.php:322 msgid "Show Info" msgstr "" -#: templates/single-show-content.php:214 -msgid "Hosted by" -msgstr "" - -#: templates/single-show-content.php:241 -msgid "Produced by" -msgstr "" - -#: templates/single-show-content.php:325 -msgid "Show Times" +#: templates/single-show-content.php:456 +msgid "Call in" msgstr "" -#: templates/single-show-content.php:330 +#: templates/single-show-content.php:509 msgid "Not Currently Scheduled." msgstr "" -#: templates/single-show-content.php:360 -msgid "Timezone" -msgstr "" - -#: templates/single-show-content.php:362 templates/single-show-content.php:366 -msgid "UTC" -msgstr "" - -#: templates/single-show-content.php:444 -msgid "Show More" -msgstr "" - -#: templates/single-show-content.php:445 -msgid "Show Less" -msgstr "" - -#: templates/single-show-content.php:459 templates/single-show-content.php:716 -msgid "About the Show" -msgstr "" - -#: templates/single-show-content.php:460 -msgid "About" -msgstr "" - -#: templates/single-show-content.php:478 -msgid "Show Episodes" -msgstr "" - -#: templates/single-show-content.php:479 -msgid "Episodes" +#: templates/single-show-content.php:714 +msgid "Scheduled Dates" msgstr "" -#: templates/single-show-content.php:499 -msgid "Show Posts" +#: templates/single-show-content.php:724 +msgid "Go to Full Station Schedule Page" msgstr "" -#: templates/single-show-content.php:500 -msgid "Posts" +#: templates/single-show-content.php:727 +msgid "Full Station Schedule" msgstr "" -#: templates/single-show-content.php:519 -msgid "Show Playlists" +#: templates/single-show-content.php:774 +msgid "About the %s" msgstr "" -#: templates/single-show-content.php:681 -msgid "Jump to" +#: templates/single-show-content.php:915 +msgid "Latest Show Posts" msgstr "" #. Plugin Name of the plugin/theme msgid "Radio Station" msgstr "" #. Plugin URI of the plugin/theme -msgid "https://netmix.com/radio-station" +msgid "https://radiostation.pro/radio-station" msgstr "" #. Description of the plugin/theme @@ -1783,9 +4345,9 @@ msgid "Adds Show pages, DJ role, playlist and on-air programming functionality t msgstr "" #. Author of the plugin/theme -msgid "Tony Zeoli " +msgid "Tony Zeoli, Tony Hayes" msgstr "" #. Author URI of the plugin/theme -msgid "https://netmix.com/radio-station" +msgid "https://netmix.com/" msgstr "" diff --git a/loader.php b/loader.php index b1e710b..2efa806 100644 --- a/loader.php +++ b/loader.php @@ -1353,7 +1353,7 @@ public function load_freemius() { // if ( !is_admin() ) { // return; // } - echo 'Freemius Loading...'; + // echo 'Freemius Loading...'; $args = $this->args; $namespace = $this->namespace; @@ -1467,9 +1467,9 @@ public function load_freemius() { // --- filter settings before initializing --- $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings ); - // if ( $this->debug ) { + if ( $this->debug ) { echo 'Freemius Settings: ' . print_r( $settings, true ) . ''; - // } + } if ( !$settings || !is_array( $settings ) ) { return; } diff --git a/radio-station.php b/radio-station.php index ee03090..a850515 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.6 +Version: 2.4.0.7 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -997,6 +997,23 @@ 'pro' => true, ), + // --- [Pro] Combined Team Tab --- + // 2.4.0.7: added combined team grid option + 'combined_team_tab' => array( + 'type' => 'select', + 'label' => __( 'Combined Team Tab', 'radio-station' ), + 'default' => 'yes', + 'options' => array( + '' => __( 'Do Not Combine', 'radio-station' ), + 'yes' => __( 'Combined List', 'radio-station' ), + // 'grid' => __( 'Combined Grid', 'radio-station' ), + ), + 'helper' => __( 'Combine team members (eg. hosts, producers) into a single display tab.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, + ), + // === Profile Pages === // 2.3.3.9: added proflie page settings diff --git a/reader.php b/reader.php index cdf90a5..43f2996 100644 --- a/reader.php +++ b/reader.php @@ -3644,7 +3644,11 @@ public function __construct() { * * @param boolean $preserve_shortcodes Defaults to $this->preserve_shortcodes. */ - $this->preserve_shortcodes = apply_filters( 'jetpack_markdown_preserve_shortcodes', $this->preserve_shortcodes ) && function_exists( 'get_shortcode_regex' ); + if ( function_exists( 'apply_filters' ) ) { + $this->preserve_shortcodes = apply_filters( 'jetpack_markdown_preserve_shortcodes', $this->preserve_shortcodes ) && function_exists( 'get_shortcode_regex' ); + } else { + $this->preserve_shortcodes = $this->preserve_shortcodes && function_exists( 'get_shortcode_regex' ); + } $this->preserve_latex = function_exists( 'latex_markup' ); $this->strip_paras = function_exists( 'wpautop' ); @@ -3689,7 +3693,10 @@ public function transform( $text ) { * * @param array $custom_patterns Array of custom patterns to be ignored by Markdown. */ - $custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', array() ); + $custom_patterns = array(); + if ( function_exists( 'apply_filters' ) ) { + $custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', $custom_patterns ); + } if ( is_array( $custom_patterns ) && ! empty( $custom_patterns ) ) { foreach ( $custom_patterns as $pattern ) { $text = preg_replace_callback( $pattern, array( $this, '_doRemoveText'), $text ); diff --git a/readme.md b/readme.md index 3bba0c1..c0e54f8 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3.1 Tested up to: 5.9 -Stable tag: 2.4.0.6 +Stable tag: 2.4.0.7 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 9266e79..a844374 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.9 -Stable tag: 2.4.0.6 +Stable tag: 2.4.0.7 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -218,6 +218,11 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.4.0.7 = +* Fix: remove debug output breaking redirects/data endpoints +* Updated: main language translation file +* Added: list of Pro filters to documentation + = 2.4.0.6 = * Update: Freemius SDK (2.4.3) * Updated: documentation links to new demo site address From 0bb7301f962dee0e135fc9c8dbdc42eb4be4c823 Mon Sep 17 00:00:00 2001 From: Andrew DePaula Date: Wed, 30 Mar 2022 11:30:52 -0700 Subject: [PATCH 255/377] Contextual help bugfix. Removes error message due to 'contextual_help' action hook being deprecated by wordpress. --- freemius/assets/img/radio-station.png | Bin 0 -> 9380 bytes help/contextual-help-config.php | 13 ++++++----- help/export.php | 27 ++++++++++++++++++++++ help/import.php | 31 ++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 freemius/assets/img/radio-station.png create mode 100644 help/export.php create mode 100644 help/import.php diff --git a/freemius/assets/img/radio-station.png b/freemius/assets/img/radio-station.png new file mode 100644 index 0000000000000000000000000000000000000000..33902b4a99ec9a3199804151277daabfc610406b GIT binary patch literal 9380 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r literal 0 HcmV?d00001 diff --git a/help/contextual-help-config.php b/help/contextual-help-config.php index df98394..f76a01e 100644 --- a/help/contextual-help-config.php +++ b/help/contextual-help-config.php @@ -6,11 +6,14 @@ * Licence: GPL3 */ + define( 'RADIO_STATION_PREFIX', 'radio-station' ); + // ----------------------- // Contextual Help Screens // ----------------------- -add_action( 'contextual_help', 'radio_station_contextual_help', 10, 3 ); -function radio_station_contextual_help( $contextual_help, $screen_id, $screen ) { +add_action( 'current_screen', 'radio_station_contextual_help', 10, 3 ); +function radio_station_contextual_help() { + $screen = get_current_screen(); // Add a custom if statement stanza per screen you wish to include help on. // Make sure the screen id in the if condition matches the page where you want the included help to appear @@ -19,7 +22,7 @@ function radio_station_contextual_help( $contextual_help, $screen_id, $screen ) // if statement multiple times (with a unique help tab id for each one). // uncomment line below and view log file to id a particular screen - // error_log("Displaying screen '". print_r($screen->id,true)."'\n", 3, "/tmp/my-errors.log"); //code to write a line to wp-content/debug.log (works) + // error_log("Displaying screen '". print_r($screen->id,true)."'\n", 0); //code to write a line to wp-content/debug.log (works) // --- edit show contextual help --- // TODO: re-enable when Managing Shows help text is written @@ -37,7 +40,7 @@ function radio_station_contextual_help( $contextual_help, $screen_id, $screen ) } */ // --- contextual help for import/export screen --- - $prefix = $screen->parent_base . '_page_'; + $prefix = RADIO_STATION_PREFIX . '_page_'; if ( $prefix . 'import-export-shows' == $screen->id ) { // --- import feature documentation tab --- @@ -85,7 +88,7 @@ function radio_station_contextual_help( $contextual_help, $screen_id, $screen ) } } - return $contextual_help; + // return $contextual_help; } diff --git a/help/export.php b/help/export.php new file mode 100644 index 0000000..a89b216 --- /dev/null +++ b/help/export.php @@ -0,0 +1,27 @@ + + +

    Exporting All Show Data

    +

    +To export your show data, simply click the Export button. Under Advanced you can optionally +fill in the two additional fields below to specify a custom export file name, and the URL to a location on the Internet, +where you will stage the show image files for re-import. +

    +

    +When the system is done processing, you'll see a message box with links to download the files. Either one file, or three files are generated +depending on the optional data supplied, and must be downloaded seperately. The YAML file contains the show's metadata (title, +description, schedule, etc..). The image archive is is made available as a zip file, or tgz file of all the images each show references. +Download the one you wish to work with. +

    +

    +Image file archives are not generated unless an image location URL is provided. +If an image location URL is supplied, this url will be prefixed to the file name in the YAML file against each URL. Otherwise, +no images will be exported, and the resulting YAML file will not contain any image references. The assumption is made, that +if this data is to be re-imported, all images will be found at the one base URL provided. +

    diff --git a/help/import.php b/help/import.php new file mode 100644 index 0000000..531f85a --- /dev/null +++ b/help/import.php @@ -0,0 +1,31 @@ + + + +

    Import Show Data via YAML file

    +

    + To import and optionally replace all your show data with whatever is contained in a data file. Click the + Select file button below and select the file you wish to import. Check the box to delete + existing data if desired, then click the import button. +

    +

    + + WARNING! If "Delete existing show data" is checked, importing will erase everything you currently have in your show database and + replace it with whatever is in the file you import. There is no undo. Take a backup first. + +

    From bb0499c0ad1679e8334ff83948705a32a03c440e Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 7 Apr 2022 22:31:25 +1000 Subject: [PATCH 256/377] dev updates --- .idea/workspace.xml | 5 +++-- CHANGELOG.md | 4 ++++ css/rs-shortcodes.css | 2 +- help/contextual-help-config.php | 12 ++++++------ radio-station-admin.php | 5 +++-- radio-station.php | 33 +++++++++++++++++++++++++++++++-- readme.txt | 4 ++++ 7 files changed, 52 insertions(+), 13 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1e179f1..81d9f47 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -111,6 +111,7 @@ + @@ -118,10 +119,10 @@ - - - - - - - - 1575948187082 - - - - - - - - - - - - - C:\!WP\InstantWP_4.5\iwpserver\htdocs\wordpress - - - - - - file://$PROJECT_DIR$/radio-station.php - 487 - - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a178e..8167ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.5.0 +* Added: Wordpress Radio Blocks (converted Widgets) +* Updated: Plugin Panel (1.2.6) +* Updated: Moment JS (2.29.4) with WP Loading +* Improved: Refactored Schedule Engine Class (1.0.0) +* Improved: Standardized Widget Input Fields +* Improved: WordPress Coding Standards +* Improved: Sanitization using KSES +* Improved: Translation Implementation +* Improved: use WP JSON functions for data endpoints +* Fixed: Countdowns with multiple widget instances +* Fixed: Radio Player iOS no volume control detection + ### 2.4.0.9 * Update: Sysend (1.11.1) for Radio Player * Fixed: missing register REST routes permission_callback argument diff --git a/blocks/archive.js b/blocks/archive.js new file mode 100644 index 0000000..1281e72 --- /dev/null +++ b/blocks/archive.js @@ -0,0 +1,258 @@ +/** + * === Radio Archive Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + archive_options = [ + { label: __( 'Shows', 'radio-station' ), value: 'shows' }, + { label: __( 'Overrides', 'radio-station' ), value: 'overrides' }, + { label: __( 'Playlists', 'radio-station' ), value: 'playlists' }, + { label: __( 'Shows by Genre', 'radio-station' ), value: 'genres' }, + { label: __( 'Shows by Language', 'radio-station' ), value: 'languages' }, + ]; + pro_archive_options = archive_options; + pro_archive_options[5] = { label: __( 'Episodes', 'radio-station' ), value: 'episodes' }; + pro_archive_options[6] = { label: __( 'Hosts', 'radio-station' ), value: 'hosts' }; + pro_archive_options[7] = { label: __( 'Producers', 'radio-station' ), value: 'producers' }; + /* pro_archive_options[8] = { label: __( 'Team', 'radio-station' ), value: 'team' }; */ + + registerBlockType( 'radio-station/archive', { + + /* --- Block Settings --- */ + title: '[Radio Station] Archive List', + description: __( 'Archive list for Radio Station record types.', 'radio-station' ), + icon: 'media-audio', + category: 'widgets', + example: {}, + attributes: { + /* --- Archive List Details --- */ + archive_type: { type: 'string', default: 'shows' }, + view: { type: 'string', default: 'list' }, + perpage: { type: 'number', default: 10 }, + pagination: { type: 'boolean', default: true }, + hide_empty: { type: 'boolean', default: false }, + + /* --- Archive Record Query --- */ + orderby: { type: 'string', default: 'title' }, + order: { type: 'string', default: 'ASC' }, + status: { type: 'string', default: 'publish' }, + genre: { type: 'string', default: '' }, + language: { type: 'string', default: '' }, + + /* === Archive Record Display === */ + description: { type: 'string', default: 'excerpt' }, + time_format: { type: 'string', default: '' }, + show_avatars: { type: 'boolean', default: true }, /* shows and overrides only */ + with_shifts: { type: 'boolean', default: true }, /* shows only */ + show_dates: { type: 'boolean', default: true }, /* overrides only */ + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Controls + */ + edit: (props) => { + const atts = props.attributes; + if ( atts.pro ) { + archive_type_options = pro_archive_options; + archive_type_help = __( 'Which type of records to display.', 'radio-station' ); + } else { + archive_type_options = archive_options; + archive_type_help = __( 'Episodes, Hosts and Producer archives available in Pro version.', 'radio-station' ); + } + + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/archive', className: 'radio-block-archive', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + /* === Archive List Details === */ + el( PanelBody, { title: __( 'Archive List Details', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Archive Type --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Archive Type', 'radio-station' ), + help: archive_type_help, + options: archive_type_options, + onChange: ( value ) => { + props.setAttributes( { archive_type: value } ); + }, + value: atts.archive_type, + }) + ), + /* --- Archive View --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Archive View', 'radio-station' ), + options : [ + { label: __( 'List View', 'radio-station' ), value: 'list' }, + { label: __( 'Grid View', 'radio-station' ), value: 'grid' }, + ], + onChange: ( value ) => { + props.setAttributes( { view: value } ); + }, + value: atts.view + }) + ), + /* --- Per Page --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Records Per Page', 'radio-station' ), + help: __( 'Use 0 for all records.', 'radio-station' ), + min: 0, + max: 100, + onChange: ( value ) => { + props.setAttributes( { perpage: value } ); + }, + value: atts.perpage + }) + ), + /* --- Pagination --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Pagination?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { pagination: value } ); + }, + checked: atts.pagination, + }) + ), + /* --- Hide if Empty --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Hide if Empty?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { hide_empty: value } ); + }, + checked: atts.hide_empty, + }) + ), + ), + + /* === Archive Record Query === */ + el( PanelBody, { title: __( 'Archive Record Query', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + el( SelectControl, { + label: __( 'Order By', 'radio-station' ), + options: [ + { label: __( 'Title', 'radio-station' ), value: 'title' }, + { label: __( 'Publish Date', 'radio-station' ), value: 'date' }, + { label: __( 'Modified Date', 'radio-station' ), value: 'modified' }, + ], + onChange: ( value ) => { + props.setAttributes( { orderby: value } ); + }, + value: atts.orderby + }), + el( RadioControl, { + label: __( 'Order', 'radio-station' ), + options: [ + { label: __( 'Ascending', 'radio-station' ), value: 'ASC' }, + { label: __( 'Descending', 'radio-station' ), value: 'DESC' }, + ], + onChange: ( value ) => { + props.setAttributes( { order: value } ); + }, + selected: atts.order + }), + /* TODO: --- Status Picker ? --- */ + /* TODO: --- Genre Picker ? --- */ + /* TODO: --- Language Picker ? --- */ + ), + + /* === Archive Record Display === */ + el( PanelBody, { title: __( 'Archive Record Display', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Time Format --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Time Display Format', 'radio-station' ), + options: [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( '12 Hour', 'radio-station' ), value: '12' }, + { label: __( '24 Hour', 'radio-station' ), value: '24' }, + ], + onChange: ( value ) => { + props.setAttributes( { time_format: value } ); + }, + value: atts.time_format + }) + ), + /* --- Description --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Description Display Format', 'radio-station' ), + options: [ + { label: __( 'View Default', 'radio-station' ), value: '' }, + { label: __( 'None', 'radio-station' ), value: 'none' }, + { label: __( 'Excerpt', 'radio-station' ), value: 'excerpt' }, + { label: __( 'Full', 'radio-station' ), value: 'full' }, + ], + onChange: ( value ) => { + props.setAttributes( { description: value } ); + }, + value: atts.description + }) + ), + /* --- Image Display (conditional) --- */ + ( ( atts.archive_type == 'shows' || atts.archive_type == 'overrides' ) && + el( PanelRow, { className: 'shows-only overrides-only' }, + el( ToggleControl, { + label: __( 'Display Image?', 'radio-station' ), + help: __( 'This setting is for Shows and Overrides.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_avatars: value } ); + }, + checked: atts.show_avatars + }) + ) + ), + /* --- With Shifts Only (conditional) --- */ + ( ( atts.archive_type == 'shows' ) && + el( PanelRow, { className: 'shows-only' }, + el( ToggleControl, { + label: __( 'Only Shows with Shifts?', 'radio-station' ), + help: __( 'This setting is for Shows only.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { with_shifts: value } ); + }, + checked: atts.with_shifts + }) + ) + ), + /* --- Override Dates (conditional) --- */ + ( ( atts.archive_type == 'overrides' ) && + el( PanelRow, { className: 'overrides-only' }, + el( ToggleControl, { + label: __( 'Display Override Dates?', 'radio-station' ), + help: __( 'This setting is for Overrides only.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_dates: value } ); + }, + checked: atts.show_dates + }) + ) + ) + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/clock.js b/blocks/clock.js new file mode 100644 index 0000000..b4f4c93 --- /dev/null +++ b/blocks/clock.js @@ -0,0 +1,135 @@ +/** + * === Radio Clock Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + registerBlockType( 'radio-station/clock', { + + /* --- Block Settings --- */ + title: '[Radio Station] Radio Clock', + description: __( 'Radio Station Clock time display.', 'radio-station' ), + icon: 'clock', + category: 'widgets', + example: {}, + attributes: { + /* --- Clock Display Options --- */ + time_format: { type: 'string', default: '' }, + day: { type: 'string', default: 'full' }, + date: { type: 'boolean', default: true }, + month: { type: 'string', default: 'full' }, + zone: { type: 'boolean', default: true }, + seconds: { type: 'boolean', default: true }, + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/clock', className: 'radio-block-clock', attributes: atts } ), + el( InspectorControls, {}, + el ( Panel, {}, + el( PanelBody, { title: __( 'Clock Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* Time Display Format */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Time Display Format', 'radio-station' ), + options: [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( '12 Hour', 'radio-station' ), value: '12' }, + { label: __( '24 Hour', 'radio-station' ), value: '24' }, + ], + onChange: ( value ) => { + props.setAttributes( { time_format: value } ); + }, + value: atts.time_format + }) + ), + /* Day Display Format */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Day Display Format', 'radio-station' ), + options: [ + { label: __( 'Full', 'radio-station' ), value: 'full' }, + { label: __( 'Short', 'radio-station' ), value: 'short' }, + { label: __( 'None', 'radio-station' ), value: 'none' }, + ], + onChange: ( value ) => { + props.setAttributes( { day: value } ); + }, + value: atts.day + }) + ), + /* Date Display */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Date?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { date: value } ); + }, + checked: atts.date, + }) + ), + /* Month Display Format */ + el( PanelRow, {}, + el( SelectControl, { + label: 'Month Display Format', + options: [ + { label: __( 'Full', 'radio-station' ), value: 'full' }, + { label: __( 'Short', 'radio-station' ), value: 'short' }, + { label: __( 'None', 'radio-station' ), value: 'none' }, + ], + onChange: ( value ) => { + props.setAttributes( { month: value } ); + }, + value: atts.month + }) + ), + /* Timezone Display */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Timezone?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { zone: value } ); + }, + checked: atts.zone, + }) + ), + /* Seconds Display */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Seconds?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { seconds: value } ); + }, + checked: atts.seconds, + }) + ), + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/current-playlist.js b/blocks/current-playlist.js new file mode 100644 index 0000000..e0e927d --- /dev/null +++ b/blocks/current-playlist.js @@ -0,0 +1,208 @@ +/** + * === Radio Current Playlist Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + registerBlockType( 'radio-station/current-playlist', { + + /* --- Block Settings --- */ + title: '[Radio Station] Current Playlist', + description: __( 'Radio Station current playlist block.', 'radio-station' ), + icon: 'playlist-audio', + category: 'widgets', + example: {}, + attributes: { + /* --- Loading Options --- */ + ajax: { type: 'string', default: '' }, + /* dynamic: { type: 'string', default: '' }, */ + hide_empty: { type: 'boolean', default: false }, + + /* --- Playlist Display Options --- */ + link: { type: 'boolean', default: true }, + countdown: { type: 'boolean', default: true }, + no_playlist: { type: 'string', default: '' }, + + /* --- Track Display Options --- */ + song: { type: 'boolean', default: true }, + artist: { type: 'boolean', default: true }, + album: { type: 'boolean', default: false }, + label: { type: 'boolean', default: false }, + comments: { type: 'boolean', default: false }, + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/current-playlist', className: 'radio-block-schedule', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + + // === Loading Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + el( PanelRow, {}, + el( SelectControl, { + label: __( 'AJAX Load Block', 'radio-station' ), + help: __( 'To bypass page caching.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { ajax: value } ); + }, + value: atts.ajax + }) + ), + /* --- [Pro] Dynamic Reloading --- */ + el( PanelRow, {}, + ( ( atts.pro ) && + el( SelectControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Reloads at show changeover times.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { dynamic: value } ); + }, + value: atts.dynamic + }) + ), ( ( !atts.pro ) && + el( BaseControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Show changeover reloading available in Pro.', 'radio-station' ), + }) + ) + ), + /* --- Hide if Empty --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Hide if Empty?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { hide_empty: value } ); + }, + checked: atts.hide_empty, + }) + ), + ), + + /* === Playlist Display Panel === */ + el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Link Playlist --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Playlist Page', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link: value } ); + }, + checked: atts.link, + }) + ), + /* --- No Playlist Text --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'No Current Playlist Text', 'radio-station' ), + help: __( 'Blank for default. 0 for none.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { no_playlist: value } ); + }, + value: atts.no_playlist + }) + ), + /* --- Countdown --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Playlist Countdown', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { countdown: value } ); + }, + checked: atts.countdown, + }) + ), + ), + + /* === Track Display Options === */ + el( PanelBody, { title: __( 'Track Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Song Display --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Song Title', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { song: value } ); + }, + checked: atts.song, + }) + ), + /* --- Artist Display --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Artist', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { artist: value } ); + }, + checked: atts.artist, + }) + ), + /* --- Display Album --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Album', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { album: value } ); + }, + checked: atts.album, + }) + ), + /* --- Display Record Label --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Record Label', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { label: value } ); + }, + checked: atts.label, + }) + ), + /* --- Display Comments --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Track Comments', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { comments: value } ); + }, + checked: atts.comments, + }) + ), + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/current-show.js b/blocks/current-show.js new file mode 100644 index 0000000..6a3bb3e --- /dev/null +++ b/blocks/current-show.js @@ -0,0 +1,335 @@ +/** + * === Radio Current Show Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + /* --- create image size options --- */ + image_size_options = []; + image_sizes = wp.data.select( 'core/block-editor' ).getSettings().imageSizes; + for ( i = 0; i < image_sizes.length; i++ ) { + image_size_options[i] = { label: image_sizes[i].name, value: image_sizes[i].slug }; + } + + registerBlockType( 'radio-station/current-show', { + + /* --- Block Settings --- */ + title: '[Radio Station] Current Show', + description: __( 'Radio Station current show block.', 'radio-station' ), + icon: 'controls-play', + category: 'widgets', + example: {}, + attributes: { + /* --- Loading Options --- */ + ajax: { type: 'string', default: '' }, + /* dynamic: { type: 'string', default: '' }, */ + no_shows: { type: 'string', default: '' }, + hide_empty: { type: 'boolean', default: false }, + + /* --- Show Display Options --- */ + show_link: { type: 'boolean', default: true }, + title_position: { type: 'string', default: 'right' }, + show_avatar: { type: 'boolean', default: true }, + avatar_size: { type: 'string', default: 'thumbnail' }, + avatar_width: { type: 'number', default: 0 }, + + /* --- Show Time Display Options --- */ + show_sched: { type: 'boolean', default: true }, + show_all_sched: { type: 'boolean', default: false }, + countdown: { type: 'boolean', default: true }, + time_format: { type: 'string', default: '' }, + + /* --- Extra Display Options --- */ + display_hosts: { type: 'boolean', default: false }, + link_hosts: { type: 'boolean', default: true }, + /* display_producers: { type: 'boolean', default: false }, */ + /* link_producers: { type: 'boolean', default: false }, */ + show_desc: { type: 'boolean', default: false }, + show_playlist: { type: 'boolean', default: false }, + show_encore: { type: 'boolean', default: true }, + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/current-show', className: 'radio-block-schedule', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + + // === Loading Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + el( PanelRow, {}, + el( SelectControl, { + label: __( 'AJAX Load Block', 'radio-station' ), + help: __( 'To bypass page caching.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { ajax: value } ); + }, + value: atts.ajax + }) + ), + /* --- [Pro] Dynamic Reloading --- */ + el( PanelRow, {}, + ( ( atts.pro ) && + el( SelectControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Reloads at show changeover times.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { dynamic: value } ); + }, + value: atts.dynamic + }) + ), ( ( !atts.pro ) && + el( BaseControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Show changeover reloading available in Pro.', 'radio-station' ), + }) + ) + ), + /* --- No Shows Text --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'No Current Show Text', 'radio-station' ), + help: __( 'Blank for default. 0 for none.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { no_shows: value } ); + }, + value: atts.no_shows + }) + ), + /* --- Hide if Empty --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Hide if Empty?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { hide_empty: value } ); + }, + checked: atts.hide_empty, + }) + ), + ), + + /* === Show Display Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Show Link --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Show Page', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_link: value } ); + }, + checked: atts.show_link, + }) + ), + /* --- Title Position --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Title Position', 'radio-station' ), + options : [ + { label: __( 'Above Image', 'radio-station' ), value: 'above' }, + { label: __( 'Left of Image', 'radio-station' ), value: 'left' }, + { label: __( 'Right of Image', 'radio-station' ), value: 'right' }, + { label: __( 'Below Image', 'radio-station' ), value: 'below' }, + ], + onChange: ( value ) => { + props.setAttributes( { title_position: value } ); + }, + value: atts.title_position + }) + ), + /* --- Show Avatar --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Image', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_avatar: value } ); + }, + checked: atts.show_avatar, + }) + ), + /* --- Avatar Size --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Image Size', 'radio-station' ), + options: image_size_options, + onChange: ( value ) => { + props.setAttributes( { avatar_size: value } ); + }, + selected: atts.avatar_size + }) + ), + /* --- Avatar Width --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Image Width Override', 'radio-station' ), + help: __( '0 for default.', 'radio-station' ), + min: 0, + max: 1000, + onChange: ( value ) => { + props.setAttributes( { avatar_width: value } ); + }, + value: atts.avatar_width + }) + ), + ), + + /* === Show Time Display Options === */ + el( PanelBody, { title: __( 'Show Time Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Show Time --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Time', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_sched: value } ); + }, + checked: atts.show_sched, + }) + ), + /* --- Show Schedule --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display All Show Times', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_all_sched: value } ); + }, + checked: atts.show_all_sched, + }) + ), + /* --- Countdown --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Remaining Time Countdown', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { countdown: value } ); + }, + checked: atts.countdown, + }) + ), + /* --- Time Format --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Time Display Format', 'radio-station' ), + options: [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( '12 Hour', 'radio-station' ), value: '12' }, + { label: __( '24 Hour', 'radio-station' ), value: '24' }, + ], + onChange: ( value ) => { + props.setAttributes( { time_format: value } ); + }, + value: atts.time_format + }) + ), + ), + + /* === Extra Displays Panel === */ + el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Display Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Hosts', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { display_hosts: value } ); + }, + checked: atts.display_hosts, + }) + ), + /* --- Link Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Host Profile', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link_hosts: value } ); + }, + checked: atts.link_hosts, + }) + ), + /* --- Display Producers --- */ + /* el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Producers', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { display_hosts: value } ); + }, + checked: atts.display_hosts, + }) + ), */ + /* --- Link Producers --- */ + /* el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Producer Profile', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link_producers: value } ); + }, + checked: atts.link_producers, + }) + ), */ + /* --- Show Description Excerpt --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Show Description Excerpt', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_desc: value } ); + }, + checked: atts.show_desc, + }) + ), + /* --- Show Playlist --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Playlist', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_playlist: value } ); + }, + checked: atts.show_playlist, + }) + ), + /* --- Show Encore --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display if Encore Airing', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_encore: value } ); + }, + checked: atts.show_encore, + }) + ), + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/editor.js b/blocks/editor.js new file mode 100644 index 0000000..250cc41 --- /dev/null +++ b/blocks/editor.js @@ -0,0 +1,105 @@ +/* --- Subscribe to Block State --- */ +/* ref: https://wordpress.stackexchange.com/a/358256/76440 */ +( () => { + let blocksState = wp.data.select( 'core/block-editor' ).getBlocks(); + wp.data.subscribe( _.debounce( ()=> { + newBlocksState = wp.data.select( 'core/block-editor' ).getBlocks(); + if ( blocksState.length !== newBlocksState.length ) { + + /* --- recheck for needed scripts --- */ + schedule = player = archive = clock = false; + s_multi = s_table = s_tabs = s_list = s_grid = s_calendar = false; + for ( i = 0; i < newBlocksState.length; i++ ) { + block = newBlocksState[i]; + if ( block.name == 'radio-station/clock' ) { + clock = true; + } else if ( block.name == 'radio-station/schedule' ) { + + if ( block.attributes.clock ) {clock = true;} + + /* --- Schedule Views --- */ + schedule = true; + if ( !block.attributes.pro ) { + if ( block.attributes.view == 'table' ) {console.log('Table View Schedule Found'); s_table = true;} + else if ( block.attributes.view == 'tabs' ) {console.log('Tab View Schedule Found'); s_tabs = true;} + else if ( block.attributes.view == 'grid' ) {console.log('Grid View Schedule Found'); s_grid = true;} + else if ( block.attributes.view == 'calendar' ) {console.log('Calendar View Schedule Found'); s_calendar = true;} + else if ( block.attributes.view == 'list' ) {console.log('List View Schedule Found'); s_list = true;} + } + + /* --- [Pro] Multiple Views --- */ + if ( block.attributes.pro ) { + s_multi = true; + if ( block.attributes.views.includes( 'table' ) ) {console.log('Table View Schedule Found'); s_table = true;} + if ( block.attributes.views.includes( 'tabs' ) ) {console.log('Tab View Schedule Found'); s_tabs = true;} + if ( block.attributes.views.includes( 'grid' ) ) {console.log('Grid View Schedule Found');s_grid = true;} + if ( block.attributes.views.includes( 'calendar' ) ) {console.log('Calendar View Schedule Found'); s_calendar = true;} + if ( block.attributes.views.includes( 'list' ) ) {console.log('List View Schedule Found'); s_list = true;} + } + + } else if ( block.name == 'radio-station/player' ) { + player = true; + } else if ( block.name == 'radio-station/archive' ) { + archive = true; + if ( block.attributes.pagination ) { + /* TODO: check pagination type */ + } + } + } + if (clock && !jQuery('#radio-clock-js').length) {radio_station_load_block_script('clock');} + if (schedule) { + /* --- schedule view scripts --- */ + if (s_multi && !jQuery('#radio-schedule-multiview-js').length) { + radio_station_load_block_script('schedule-multiview'); + } + if (s_table && !jQuery('#radio-schedule-table-js').length) { + radio_station_load_block_script('schedule-table'); + var radio_load_table = setInterval(function() { if (typeof radio_table_initialize == 'function') { + radio_table_initialize(); clearInterval(radio_load_table); + } }, 1000); + } + if (s_tabs && !jQuery('#radio-schedule-tabs-js').length) { + radio_station_load_block_script('schedule-tabs'); + var radio_load_tabs = setInterval(function() { if (typeof radio_tabs_initialize == 'function') { + radio_tabs_init = false; radio_tabs_initialize(); clearInterval(radio_load_grid); + } }, 1000); + } + if (s_list && !jQuery('#radio-schedule-list-js').length) { + radio_station_load_block_script('schedule-list'); + var radio_load_list = setInterval(function() { if (typeof radio_list_hightlight == 'function') { + radio_list_hightlight(); clearInterval(radio_load_list); + } }, 1000); + } + if (s_grid && !jQuery('#radio-schedule-grid-js').length) { + radio_station_load_block_script('schedule-grid'); + var radio_load_grid = setInterval(function() { if (typeof radio_grid_initialize == 'function') { + radio_grid_init = false; radio_grid_initialize(); radio_grid_time_spacing(); clearInterval(radio_load_grid); + } }, 1000); + } + if (s_calendar && !jQuery('#radio-schedule-calendar-js').length) { + radio_station_load_block_script('schedule-calendar'); + var radio_load_calendar = setInterval(function() { if (typeof radio_calendar_initialize == 'function') { + radio_calendar_init = false; radio_calendar_initialize(); radio_shows_slider(); clearInterval(radio_load_calendar); + } }, 1000); + } + } + if (player) { + radio_station_load_block_script('player'); + /* TODO: initialize player ? + var radio_load_player = setInterval(function() { if (typeof ??? == 'function') { + radio_player_init = false; ???(); clearInterval(radio_load_player); + } }, 1000); */ + } + if (archive ) { + /* TODO: check for archive pagination type */ + } + } + blocksState = newBlocksState; + }, 300 ) ); +} )(); + +function radio_station_load_block_script(handle) { + id = 'radio-'+handle+'-js'; + url = radio.ajax_url+'?action=radio_station_block_script&handle='+handle; + jQuery('html head').append(''); +} diff --git a/blocks/player.js b/blocks/player.js new file mode 100644 index 0000000..51d5932 --- /dev/null +++ b/blocks/player.js @@ -0,0 +1,195 @@ +/** + * === Radio Player Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + registerBlockType( 'radio-station/player', { + + /* --- Block Settings --- */ + title: __( '[Radio Station] Stream Player', 'radio-station' ), + description: __( 'Audio stream player block.', 'radio-station' ), + icon: 'controls-volumeon', + category: 'media', + example: {}, + attributes: { + /* --- Player Content --- */ + url: { type: 'string', default: '' }, + station: { type: 'string', default: '' }, + image: { type: 'string', default: 'default' }, + /* --- Player Options --- */ + script: { type: 'string', default: 'default' }, + volume: { type: 'number', default: 77 }, + default: { type: 'boolean', default: false }, + /* --- Player Styles --- */ + layout: { type: 'string', default: 'horizontal' }, + theme: { type: 'string', default: 'default' }, + buttons: { type: 'string', default: 'default' }, + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/player', className: 'radio-block-player', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + /* === Player Content === */ + el( PanelBody, { title: __( 'Player Content', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Stream URL --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'Stream URL', 'radio-station' ), + help: __( 'Leave blank to use default stream.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { url: value } ); + }, + value: atts.url, + }) + ), + /* --- Station/Player Text --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'Player Text', 'radio-station' ), + help: __( 'Empty for default, 0 for none.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { station: value } ); + }, + value: atts.station + }) + ), + /* --- Image --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Image', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Display Station Image', 'radio-station' ), value: 'on' }, + { label: __( 'Do Not Display Station Image', 'radio-station' ), value: 'off' }, + /* { label: __( 'Display Custom Image', 'radio-station' ), value: 'custom' }, */ + ], + onChange: ( value ) => { + props.setAttributes( { image: value } ); + }, + value: atts.image + }) + ) + ), + + /* === Player Options === */ + el( PanelBody, { title: __( 'Player Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Script --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Script', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Amplitude', 'radio-station' ), value: 'amplitude' }, + { label: __( 'Howler', 'radio-station' ), value: 'howler' }, + { label: __( 'jPlayer', 'radio-station' ), value: 'jplayer' }, + ], + onChange: ( value ) => { + props.setAttributes( { script: value } ); + }, + value: atts.script + }) + ), + /* --- Volume --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Initial Volume', 'radio-station' ), + min: 0, + max: 100, + onChange: ( value ) => { + props.setAttributes( { volume: value } ); + }, + value: atts.volume + }) + ), + /* --- Default Player --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Use as Default Player', 'radio-station' ), + help: __( 'Use this player as the player on this page?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { default: value } ); + }, + checked: atts.default, + }) + ), + ), + + /* === Player Styles === */ + el( PanelBody, { title: __( 'Player Styles', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Player Layout --- */ + el( PanelRow, {}, + el( RadioControl, { + label: __( 'Player Layout', 'radio-station' ), + options : [ + { label: __( 'Vertical (Stacked)', 'radio-station' ), value: 'vertical' }, + { label: __( 'Horizontal (Inline)', 'radio-station' ), value: 'horizontal' }, + ], + onChange: ( value ) => { + props.setAttributes( { layout: value } ); + }, + checked: atts.layout + }) + ), + /* --- Player Theme --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Theme', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Light', 'radio-station' ), value: 'light' }, + { label: __( 'Dark', 'radio-station' ), value: 'dark' }, + ], + onChange: ( value ) => { + props.setAttributes( { theme: value } ); + }, + value: atts.theme + }) + ), + /* --- Player Buttons --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Buttons', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Circular', 'radio-station' ), value: 'circular' }, + { label: __( 'Rounded', 'radio-station' ), value: 'rounded' }, + { label: __( 'Square', 'radio-station' ), value: 'square' }, + ], + onChange: ( value ) => { + props.setAttributes( { buttons: value } ); + }, + value: atts.buttons + }) + ) + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/schedule.js b/blocks/schedule.js new file mode 100644 index 0000000..2a82f3a --- /dev/null +++ b/blocks/schedule.js @@ -0,0 +1,431 @@ +/** + * === Radio Schedule Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + /* --- set schedule view options --- */ + schedule_views = [ + { label: __( 'Table', 'radio-station' ), value: 'table' }, + { label: __( 'Tabbed', 'radio-station' ), value: 'tabs' }, + { label: __( 'List', 'radio-station' ), value: 'list' }, + ]; + pro_views = [ + { label: __( 'Table', 'radio-station' ), value: 'table' }, + { label: __( 'Tabbed', 'radio-station' ), value: 'tabs' }, + { label: __( 'Grid', 'radio-station' ), value: 'grid' }, + { label: __( 'Calendar', 'radio-station' ), value: 'calendar' }, + { label: __( 'List', 'radio-station' ), value: 'list' }, + ]; + default_setting = [ { label: __( 'Plugin Setting', 'radio-station' ), value: '' } ]; + default_views = default_setting.concat(pro_views); + + registerBlockType( 'radio-station/schedule', { + + /* --- Block Settings --- */ + title: '[Radio Station] Program Schedule', + description: __( 'Radio Station Schedule block.', 'radio-station' ), + icon: 'calendar-alt', + category: 'widgets', + example: {}, + attributes: { + + /* --- Schedule Display --- */ + view: { type: 'string', default: 'table' }, + image_position: { type: 'string', default: 'left' }, + hide_past_shows: { type: 'boolean', default: false }, + + /* --- Times Display --- */ + display_day: { type: 'string', default: 'short' }, + display_month: { type: 'string', default: 'short' }, + start_day: { type: 'string', default: '' }, + time_format: { type: 'string', default: '' }, + /* days: { type: '', default: false },*/ + /* start_date: { type: '', default: false }, */ + /* active_date: { type: '', default: false }, */ + /* display_date: { type: 'string', default: 'jS' }, */ + + /* --- Extra Displays --- */ + selector: { type: 'boolean', default: true }, + clock: { type: 'boolean', default: true }, + timezone: { type: 'boolean', default: true }, + + /* --- Show Display --- */ + show_times: { type: 'boolean', default: true }, + show_link: { type: 'boolean', default: true }, + show_image: { type: 'boolean', default: false }, + show_desc: { type: 'boolean', default: false }, + show_hosts: { type: 'boolean', default: false }, + link_hosts: { type: 'boolean', default: false }, + show_genres: { type: 'boolean', default: false }, + show_encore: { type: 'boolean', default: true }, + // show_file: { type: 'boolean', default: false }, + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/schedule', className: 'radio-block-schedule', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + /* === Schedule Display Panel === */ + el( PanelBody, { title: __( 'Schedule Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- View Selection --- */ + ( ( !atts.pro ) && + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Schedule View', 'radio-station' ), + help: __( 'Grid and Calendar Views available in Pro version.', 'radio-station' ), + options: schedule_views, + onChange: ( value ) => { + props.setAttributes( { view: value } ); + }, + value: atts.view + }) + ) + ), + ( ( !atts.pro ) && + el( PanelRow, {}, + el( BaseControl, { + label: __( 'View Switching', 'radio-station' ), + help: __( 'Multiple view switching available in Pro version.', 'radio-station' ), + }) + ) + ), + /* --- [Pro] Multiple View Selection --- */ + /* ( ( atts.pro && atts.multi_view ) && */ + ( ( atts.pro ) && + el( PanelRow, {}, + el( SelectControl, { + multiple: true, + label: __( 'Select Schedule Views', 'radio-station' ), + help: __( 'Ctrl-Click to select multiple views.', 'radio-station' ), + options: pro_views, + onChange: ( value ) => { + props.setAttributes( { views: value } ); + }, + value: atts.views + }) + ) + ), + /* --- [Pro] Default View --- */ + ( ( atts.pro ) && + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Default View', 'radio-station' ), + options: default_views, + onChange: ( value ) => { + props.setAttributes( { default_view: value } ); + }, + value: atts.default_view + }) + ) + ), + /* --- Tab View Options */ + ( ( ( !atts.pro && ( atts.view == 'tabs' ) ) + || ( atts.pro && atts.views.includes('tabs') ) ) && + /* --- Image Position --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Image Position', 'radio-station' ), + help: __( 'Affects Tabbed View only.', 'radio-station' ), + options: [ + { label: __( 'Left', 'radio-station' ), value: 'left' }, + { label: __( 'Right', 'radio-station' ), value: 'right' } + ], + onChange: ( value ) => { + props.setAttributes( { image_position: value } ); + }, + value: atts.image_position + }) + ) + ), + ( ( ( !atts.pro && ( atts.view == 'tabs' ) ) + || ( atts.pro && atts.views.includes('tabs') ) ) && + /* --- Hide Past Shows */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Hide Past Shows', 'radio-station' ), + help: __( 'Affects Tabbed View only.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { hide_past_shows: value } ); + }, + checked: atts.hide_past_shows, + }) + ) + ), + /* --- [Pro] Grid View Options --- */ + ( ( atts.pro && atts.views.includes('grid') ) && + /* --- Grid Width --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Grid Width', 'radio-station' ), + help: __( 'Grid view Show column width in pixels.', 'radio-station' ), + min: 0, + max: 1000, + onChange: ( value ) => { + props.setAttributes( { gridwith: value } ); + }, + value: atts.gridwidth + }) + ) + ), + ( ( atts.pro && atts.views.includes('grid') ) && + /* --- Time Spaced Grid --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Time Spaced Grid', 'radio-station' ), + help: __( 'Line up Shows by times in Grid view.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { time_spaced: value } ); + }, + checked: atts.time_spaced, + }) + ) + ), + /* --- [Pro] Calendar View Options --- */ + ( ( atts.pro && atts.views.includes('calendar') ) && + /* --- Calendar Weeks --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Calendar Weeks', 'radio-station' ), + help: __( 'Week rows to display in view.', 'radio-station' ), + min: 1, + max: 8, + onChange: ( value ) => { + props.setAttributes( { weeks: value } ); + }, + value: atts.weeks + }) + ) + ), + ( ( atts.pro && atts.views.includes('calendar') ) && + /* --- Previous Weeks --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Previous Weeks', 'radio-station' ), + help: __( 'Previous Weeks Display', 'radio-station' ), + min: 0, + max: 4, + onChange: ( value ) => { + props.setAttributes( { previous_weeks: value } ); + }, + value: atts.previous_weeks, + }) + ) + ) + ), + + /* === Time Display Options === */ + el( PanelBody, { title: __( 'Time Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Day Display --- */ + el( PanelRow, {}, + el( RadioControl, { + label: __( 'Day Display Format', 'radio-station' ), + options : [ + { label: 'Abbreviated', value: 'short' }, + { label: 'Full Name', value: 'full' } + ], + onChange: ( value ) => { + props.setAttributes( { display_day: value } ); + }, + selected: atts.display_day + }) + ), + /* --- Month Display --- */ + el( PanelRow, {}, + el( RadioControl, { + label: __( 'Month Display Format', 'radio-station' ), + options: [ + { label: __( 'Abbreviated', 'radio-station' ), value: 'short' }, + { label: __( 'Full Name', 'radio-station' ), value: 'full' } + ], + onChange: ( value ) => { + props.setAttributes( { display_month: value } ); + }, + selected: atts.display_month + }) + ), + /* --- Schedule Start Day --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Schedule Start Day', 'radio-station' ), + options: [ + { label: __( 'WP Start of Week', 'radio-station' ), value: '' }, + { label: __( 'Today', 'radio-station' ), value: 'today' }, + { label: __( 'Monday', 'radio-station' ), value: 'Monday' }, + { label: __( 'Tuesday', 'radio-station' ), value: 'Tuesday' }, + { label: __( 'Wednesday', 'radio-station' ), value: 'Wednesday' }, + { label: __( 'Thursday', 'radio-station' ), value: 'Thursday' }, + { label: __( 'Friday', 'radio-station' ), value: 'Friday' }, + { label: __( 'Saturday', 'radio-station' ), value: 'Saturday' }, + { label: __( 'Sunday', 'radio-station' ), value: 'Sunday' } + ], + onChange: ( value ) => { + props.setAttributes( { start_day: value } ); + }, + value: atts.start_day + }) + ), + /* --- Time Format --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Time Display Format', 'radio-station' ), + options: [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( '12 Hour', 'radio-station' ), value: '12' }, + { label: __( '24 Hour', 'radio-station' ), value: '24' } + ], + onChange: ( value ) => { + props.setAttributes( { time_format: value } ); + }, + value: atts.time_format + }) + ) + ), + + /* === Show Display Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Show Times --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Time', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_times: value } ); + }, + checked: atts.show_times, + }) + ), + /* --- Show Link --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Shows', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_link: value } ); + }, + checked: atts.show_link, + }) + ), + /* --- Show Image --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Image', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_image: value } ); + }, + checked: atts.show_image, + }) + ), + /* --- Show Description --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Description', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_desc: value } ); + }, + checked: atts.show_desc, + }) + ), + /* --- Show Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Hosts', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_hosts: value } ); + }, + checked: atts.show_hosts, + }) + ), + /* --- Link Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Hosts', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link_hosts: value } ); + }, + checked: atts.link_hosts, + }) + ), + /* --- Show Genres --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Genres', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_genres: value } ); + }, + checked: atts.show_genres, + }) + ), + /* --- Show Encore --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Encore', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_encore: value } ); + }, + checked: atts.show_encore, + }) + ), + ), + + /* === Extra Displays Panel === */ + el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), initialOpen: false }, + /* --- Genre Selector --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Genre Selector', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { selector: value } ); + }, + checked: atts.selector, + }) + ), + /* --- Clock --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Radio Clock', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { clock: value } ); + }, + checked: atts.clock, + }) + ), + /* --- Timezone --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Radio Timezone', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { timezone: value } ); + }, + checked: atts.timezone, + }) + ), + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/blocks/upcoming-shows.js b/blocks/upcoming-shows.js new file mode 100644 index 0000000..48e06a9 --- /dev/null +++ b/blocks/upcoming-shows.js @@ -0,0 +1,316 @@ +/** + * === Radio Upcoming Shows Block === + */ +(() => { + + const el = window.wp.element.createElement; + const { serverSideRender: ServerSideRender } = window.wp; + const { registerBlockType } = window.wp.blocks; + const { InspectorControls } = window.wp.blockEditor; + const { Fragment } = window.wp.element; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { __, _e } = window.wp.i18n; + + /* --- create image size options --- */ + image_size_options = []; + image_sizes = wp.data.select( 'core/block-editor' ).getSettings().imageSizes; + for ( i = 0; i < image_sizes.length; i++ ) { + image_size_options[i] = { label: image_sizes[i].name, value: image_sizes[i].slug }; + } + + registerBlockType( 'radio-station/upcoming-shows', { + + /* --- Block Settings --- */ + title: '[Radio Station] Upcoming Shows', + description: __( 'Radio Station upcoming shows block.', 'radio-station' ), + icon: 'controls-forward', + category: 'widgets', + example: {}, + attributes: { + /* --- Loading Options --- */ + limit: { type: 'number', default: 1 }, + ajax: { type: 'string', default: '' }, + /* dynamic: { type: 'string', default: '' }, */ + no_shows: { type: 'string', default: '' }, + hide_empty: { type: 'boolean', default: false }, + + /* --- Show Display Options --- */ + show_link: { type: 'boolean', default: true }, + title_position: { type: 'string', default: 'right' }, + show_avatar: { type: 'boolean', default: true }, + avatar_size: { type: 'string', default: 'thumbnail' }, + avatar_width: { type: 'number', default: 0 }, + + /* --- Show Time Display Options --- */ + show_sched: { type: 'boolean', default: true }, + countdown: { type: 'boolean', default: true }, + time_format: { type: 'string', default: '' }, + + /* --- Extra Display Options --- */ + display_hosts: { type: 'boolean', default: false }, + link_hosts: { type: 'boolean', default: true }, + /* display_producers: { type: 'boolean', default: false }, */ + /* link_producers: { type: 'boolean', default: false }, */ + show_encore: { type: 'boolean', default: true }, + + /* --- Hidden Switches --- */ + block: { type: 'boolean', default: true }, + pro: { type: 'boolean', default: false } + }, + + /** + * Edit Block Control + */ + edit: (props) => { + const atts = props.attributes; + return ( + el( Fragment, {}, + el( ServerSideRender, { block: 'radio-station/upcoming-shows', className: 'radio-block-schedule', attributes: atts } ), + el( InspectorControls, {}, + el( Panel, {}, + + // === Loading Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Shows to Display --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Upcoming Shows to Display', 'radio-station' ), + min: 1, + max: 10, + onChange: ( value ) => { + props.setAttributes( { limit: value } ); + }, + value: atts.limit + }) + ), + /* --- AJAX Load --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'AJAX Load Block', 'radio-station' ), + help: __( 'To bypass page caching.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { ajax: value } ); + }, + value: atts.ajax + }) + ), + /* --- [Pro] Dynamic Reloading --- */ + el( PanelRow, {}, + ( ( atts.pro ) && + el( SelectControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Reloads at show changeover times.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { dynamic: value } ); + }, + value: atts.dynamic + }) + ), ( ( !atts.pro ) && + el( BaseControl, { + label: __( 'Dynamic Reloading', 'radio-station' ), + help: __( 'Show changeover reloading available in Pro.', 'radio-station' ), + }) + ) + ), + /* --- No Shows Text --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'No Upcoming Shows Text', 'radio-station' ), + help: __( 'Blank for default. 0 for none.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { no_shows: value } ); + }, + value: atts.no_shows + }) + ), + /* --- Hide if Empty --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Hide if Empty?', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { hide_empty: value } ); + }, + checked: atts.hide_empty, + }) + ), + ), + + /* === Show Display Options === */ + el( PanelBody, { title: __( 'Show Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Show Link --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Show Page', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_title: value } ); + }, + checked: atts.show_link, + }) + ), + /* --- Title Position --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Show Title Position', 'radio-station' ), + options : [ + { label: __( 'Above Image', 'radio-station' ), value: 'above' }, + { label: __( 'Left of Image', 'radio-station' ), value: 'left' }, + { label: __( 'Right of Image', 'radio-station' ), value: 'right' }, + { label: __( 'Below Image', 'radio-station' ), value: 'below' }, + ], + onChange: ( value ) => { + props.setAttributes( { title_position: value } ); + }, + value: atts.title_position + }) + ), + /* --- Show Avatar --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Image', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_avatar: value } ); + }, + checked: atts.show_avatar, + }) + ), + /* --- Avatar Size --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Image Size', 'radio-station' ), + options: image_size_options, + onChange: ( value ) => { + props.setAttributes( { avatar_size: value } ); + }, + selected: atts.avatar_size + }) + ), + /* --- Avatar Width --- */ + el( PanelRow, {}, + el( RangeControl, { + label: __( 'Image Width Override', 'radio-station' ), + help: __( '0 for default.', 'radio-station' ), + min: 0, + max: 1000, + onChange: ( value ) => { + props.setAttributes( { avatar_width: value } ); + }, + value: atts.avatar_width + }) + ), + ), + + /* === Show Time Display Options === */ + el( PanelBody, { title: __( 'Show Time Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Show Time --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Time', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_sched: value } ); + }, + checked: atts.show_sched, + }) + ), + /* --- Countdown --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Remaining Time Countdown', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { countdown: value } ); + }, + checked: atts.countdown, + }) + ), + /* --- Time Format --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Time Display Format', 'radio-station' ), + options: [ + { label: __( 'Plugin Setting', 'radio-station' ), value: '' }, + { label: __( '12 Hour', 'radio-station' ), value: '12' }, + { label: __( '24 Hour', 'radio-station' ), value: '24' }, + ], + onChange: ( value ) => { + props.setAttributes( { time_format: value } ); + }, + value: atts.time_format + }) + ), + ), + + /* === Extra Displays Panel === */ + el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Display Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Hosts', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { display_hosts: value } ); + }, + checked: atts.display_hosts, + }) + ), + /* --- Link Hosts --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Host Profile', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link_hosts: value } ); + }, + checked: atts.link_hosts, + }) + ), + /* --- Display Producers --- */ + /* el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Show Producers', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { display_hosts: value } ); + }, + checked: atts.display_hosts, + }) + ), */ + /* --- Link Producers --- */ + /* el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Link to Producer Profile', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { link_producers: value } ); + }, + checked: atts.link_producers, + }) + ), */ + /* --- Show Encore --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display if Encore Airing', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { show_encore: value } ); + }, + checked: atts.show_encore, + }) + ), + ) + /* end panels */ + ) + ) + ) + ); + }, + + /** + * Returns nothing because this is a dynamic block rendered via PHP + */ + save: () => null, + }); +})(); diff --git a/css/rs-block-editor.css b/css/rs-block-editor.css new file mode 100644 index 0000000..5d3ba33 --- /dev/null +++ b/css/rs-block-editor.css @@ -0,0 +1,3 @@ +.components-panel .components-panel__body.radio-block-controls .components-panel__row label { + width: 100%; max-width: 100%; min-width: 150px; overflow: visible; +} diff --git a/css/rs-blocks.css b/css/rs-blocks.css new file mode 100644 index 0000000..e69de29 diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index b188629..003470f 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -72,6 +72,7 @@ .radio-station-clock-widget .radio-station-server-clock, .radio-station-clock-widget .radio-station-user-clock { font-size: 0.9em; margin-top: 10px; + display: inline-block; } .radio-station-clock-widget .radio-server-zone, .radio-station-clock-widget .radio-user-zone { @@ -135,11 +136,8 @@ padding-bottom: 5px; } -.show-post-thumbnail, .show-post-info, -.show-playlist-thumbnail, .show-playlist-info, -.show-host-thumbnail, .show-host-info, -.show-producer-thumbnail, .show-producer-info, -.show-episode-thumbnail, .show-episode-info { +.show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail, +.show-post-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { display: table-cell; vertical-align: top; } @@ -147,10 +145,10 @@ .show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail { padding-right: 30px; margin-top: 10px; - min-width:! 150px; + min-width: 150px; } -.show-post-info, .show-playlist-info, .show-host-thumbnail, .show-producer-thumbnail .show-episode-info { +.show-post-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { max-width: 600px; } diff --git a/css/rs-templates.css b/css/rs-templates.css index 96da9cb..571b61d 100644 --- a/css/rs-templates.css +++ b/css/rs-templates.css @@ -214,6 +214,10 @@ /* Show Tabs */ /* --------- */ +#show-content .show-tabs { + font-size: 0; +} + #show-content .show-tabs .show-tab { display: inline-block; min-width: 100px; diff --git a/includes/blocks.php b/includes/blocks.php new file mode 100644 index 0000000..cb40e87 --- /dev/null +++ b/includes/blocks.php @@ -0,0 +1,388 @@ + 'radio_station_timezone_shortcode', + 'clock' => 'radio_station_clock_shortcode', + // --- Stream Player --- + 'player' => 'radio_station_player_shortcode', + // --- Master Schedule --- + 'schedule' => 'radio_station_master_schedule', + // --- Archive Lists --- + 'archive' => 'radio_station_archive_list', + // --- Shows --- + 'current-show' => 'radio_station_current_show_shortcode', + 'upcoming-shows' => 'radio_station_upcoming_shows_shortcode', + 'current-playlist' => 'radio_station_current_playlist_shortcode', + // --- Show Related --- + // 'show_posts' => 'radio_station_show_posts_archive', + // 'show_playlists' => 'radio_station_show_playlists_archive', + ); + + // --- filter and return --- + $callbacks = apply_filters( 'radio_station_block_callbacks', $callbacks ); + return $callbacks; +} + +// -------------------- +// Get Block Attributes +// -------------------- +function radio_station_get_block_attributes() { + + // --- set block names and related attributes --- + $attributes = array( + + // === Master Schedule === + 'schedule' => array( + // --- Schedule Display --- + 'view' => array( 'type' => 'string', 'default' => 'table' ), + 'image_position' => array( 'type' => 'string', 'default' => 'left' ), + 'hide_past_shows' => array( 'type' => 'boolean', 'default' => false ), + // --- Time Display --- + 'display_day' => array( 'type' => 'string', 'default' => 'short' ), + 'display_month' => array( 'type' => 'string', 'default' => 'short' ), + 'start_day' => array( 'type' => 'string', 'default' => '' ), + 'time_format' => array( 'type' => 'string', 'default' => '' ), + // --- Extra Displays --- + 'selector' => array( 'type' => 'boolean', 'default' => true ), + 'clock' => array( 'type' => 'boolean', 'default' => true ), + 'timezone' => array( 'type' => 'boolean', 'default' => true ), + // --- Show Display --- + 'show_times' => array( 'type' => 'boolean', 'default' => true ), + 'show_link' => array( 'type' => 'boolean', 'default' => true ), + 'show_image' => array( 'type' => 'boolean', 'default' => false ), + 'show_desc' => array( 'type' => 'boolean', 'default' => false ), + 'show_hosts' => array( 'type' => 'boolean', 'default' => false ), + 'link_hosts' => array( 'type' => 'boolean', 'default' => false ), + 'show_genres' => array( 'type' => 'boolean', 'default' => false ), + 'show_encore' => array( 'type' => 'boolean', 'default' => true ), + + /* 'views' => array( 'type' => 'array', 'default' => array( 'table', 'tabs', 'grid', 'calendar' ) ), + 'gridwidth' => array( 'type' => 'number', 'default' => 150 ), + 'time_spaced' => array( 'type' => 'boolean', 'default' => true ), + 'weeks' => array( 'type' => 'number', 'default' => 3 ), + 'previous_weeks' => array( 'type' => 'number', 'default' => 1 ), */ + ), + + // === Stream Player === + 'player' => array( + // --- Player Content --- + 'url' => array( 'type' => 'string', 'default' => '' ), + 'station' => array( 'type' => 'string', 'default' => '' ), + 'image' => array( 'type' => 'string', 'default' => 'default' ), + // ---- Player Options --- + 'script' => array( 'type' => 'string', 'default' => 'default' ), + 'volume' => array( 'type' => 'number', 'default' => 77 ), + 'default' => array( 'type' => 'boolean', 'default' => false ), + // --- Player Styles --- + 'layout' => array( 'type' => 'string', 'default' => 'horizontal' ), + 'theme' => array( 'type' => 'string', 'default' => 'default' ), + 'buttons' => array( 'type' => 'string', 'default' => 'default' ), + ), + + // === Radio Clock === + 'timezone' => array(), + 'clock' => array( + 'time_format' => array( 'type' => 'string', 'default' => '' ), + 'day' => array( 'type' => 'string', 'default' => 'full' ), + 'date' => array( 'type' => 'boolean', 'default' => true ), + 'month' => array( 'type' => 'string', 'default' => 'full' ), + 'zone' => array( 'type' => 'boolean', 'default' => true ), + 'seconds' => array( 'type' => 'boolean', 'default' => true ), + ), + + // === Archive Lists === + 'archive' => array( + // --- Archive List Details --- + 'archive_type' => array( 'type' => 'string', 'default' => 'shows' ), + 'view' => array( 'type' => 'string', 'default' => 'list' ), + 'perpage' => array( 'type' => 'number', 'default' => 10 ), + 'pagination' => array( 'type' => 'boolean', 'default' => true ), + 'hide_empty' => array( 'type' => 'boolean', 'default' => false ), + // --- Archive Record Query --- + 'orderby' => array( 'type' => 'string', 'default' => 'title' ), + 'order' => array( 'type' => 'string', 'default' => 'ASC' ), + 'status' => array( 'type' => 'string', 'default' => 'publish' ), + 'genre' => array( 'type' => 'string', 'default' => '' ), + 'language' => array( 'type' => 'string', 'default' => '' ), + // --- Archive Record Display --- + 'description' => array( 'type' => 'string', 'default' => 'excerpt' ), + 'time_format' => array( 'type' => 'string', 'default' => '' ), + 'show_avatars' => array( 'type' => 'boolean', 'default' => true ), /* shows and overrides only */ + 'with_shifts' => array( 'type' => 'boolean', 'default' => true ), /* shows only */ + 'show_dates' => array( 'type' => 'boolean', 'default' => true ), /* overrides only */ + ), + + // === Shows Shortcodes === + // --- Current Show --- + 'current-show' => array( + // --- Loading Options --- + 'ajax' => array( 'type' => 'string', 'default' => '' ), + // dynamic' => array( 'type' => 'boolean', 'default' => true ), + 'no_shows' => array( 'type' => 'string', 'default' => '' ), + 'hide_empty' => array( 'type' => 'boolean', 'default' => false ), + // --- Show Display Options --- + 'show_link' => array( 'type' => 'boolean', 'default' => true ), + 'title_position' => array( 'type' => 'string', 'default' => 'right' ), + 'show_avatar' => array( 'type' => 'boolean', 'default' => true ), + 'avatar_size' => array( 'type' => 'string', 'default' => 'thumbnail' ), + 'avatar_width' => array( 'type' => 'number', 'default' => 0 ), + // --- Show Time Display Options --- + 'show_sched' => array( 'type' => 'boolean', 'default' => true ), + 'show_all_sched' => array( 'type' => 'boolean', 'default' => false ), + 'countdown' => array( 'type' => 'boolean', 'default' => true ), + 'time_format' => array( 'type' => 'string', 'default' => '' ), + // --- Extra Display Options --- + 'display_hosts' => array( 'type' => 'boolean', 'default' => false ), + 'link_hosts' => array( 'type' => 'boolean', 'default' => true ), + // 'display_producers' => array( 'type' => 'boolean', 'default' => false ), + // 'link_producers' => array( 'type' => 'boolean', 'default' => false ), + 'show_desc' => array( 'type' => 'boolean', 'default' => false ), + 'show_playlist' => array( 'type' => 'boolean', 'default' => false ), + 'show_encore' => array( 'type' => 'boolean', 'default' => true ), + ), + // --- Upcoming Shows --- + 'upcoming-shows' => array( + // --- Loading Options --- + 'ajax' => array( 'type' => 'string', 'default' => '' ), + 'limit' => array( 'type' => 'number', 'default' => 1 ), + 'dynamic' => array( 'type' => 'boolean', 'default' => true ), + 'no_shows' => array( 'type' => 'string', 'default' => '' ), + 'hide_empty' => array( 'type' => 'boolean', 'default' => false ), + // --- Show Display Options --- + 'show_link' => array( 'type' => 'boolean', 'default' => true ), + 'title_position' => array( 'type' => 'string', 'default' => 'right' ), + 'show_avatar' => array( 'type' => 'boolean', 'default' => true ), + 'avatar_size' => array( 'type' => 'string', 'default' => 'thumbnail' ), + 'avatar_width' => array( 'type' => 'number', 'default' => 0 ), + // --- Show Time Display Options --- + 'show_sched' => array( 'type' => 'boolean', 'default' => true ), + 'countdown' => array( 'type' => 'boolean', 'default' => true ), + 'time_format' => array( 'type' => 'string', 'default' => '' ), + // --- Extra Display Options --- + 'display_hosts' => array( 'type' => 'boolean', 'default' => false ), + 'link_hosts' => array( 'type' => 'boolean', 'default' => true ), + // 'display_producers' => array( 'type' => 'boolean', 'default' => false ), + // 'link_producers' => array( 'type' => 'boolean', 'default' => false ), + 'show_encore' => array( 'type' => 'boolean', 'default' => true ), + ), + // --- Current Playlist --- + 'current-playlist' => array( + // --- Loading Options --- + 'ajax' => array( 'type' => 'string', 'default' => '' ), + // dynamic' => array( 'type' => 'boolean', 'default' => true ), + 'no_playlist' => array( 'type' => 'string', 'default' => '' ), + 'hide_empty' => array( 'type' => 'boolean', 'default' => false ), + // --- Playlist Display Options --- + 'link' => array( 'type' => 'boolean', 'default' => true ), + 'countdown' => array( 'type' => 'boolean', 'default' => true ), + // --- Track Display Options --- + 'song' => array( 'type' => 'boolean', 'default' => true ), + 'artist' => array( 'type' => 'boolean', 'default' => true ), + 'album' => array( 'type' => 'boolean', 'default' => false ), + 'label' => array( 'type' => 'boolean', 'default' => false ), + 'comments' => array( 'type' => 'boolean', 'default' => false ), + ), + + // === Show Related === + // 'show_posts' => array(), + // 'show_playlists' => array(), + + ); + + // --- add default switches to each block --- + foreach ( $attributes as $block_slug => $attribute_list ) { + $attribute_list['block'] = array( 'type' => 'boolean', 'default' => true ); + $attribute_list['pro'] = array( 'type' => 'boolean', 'default' => false ); + $attributes[$block_slug] = $attribute_list; + } + + // --- filter and return --- + $attributes = apply_filters( 'radio_station_block_attributes', $attributes ); + return $attributes; +} + +// --------------- +// Register Blocks +// --------------- +// 2.5.0: added shortcode blocks for Gutenberg block editor +add_action( 'init', 'radio_station_register_blocks' ); +function radio_station_register_blocks() { + + // --- get block callbacks and attributes --- + $callbacks = radio_station_get_block_callbacks(); + $attributes = radio_station_get_block_attributes(); + + // --- loop block names to register blocks --- + foreach ( $callbacks as $block_slug => $callback ) { + $block_key = 'radio-station/' . $block_slug; + $args = array( 'render_callback' => $callback, 'attributes' => $attributes[$block_slug] ); + $args = apply_filters( 'radio_station_block_args', $args, $block_slug, $callback ); + register_block_type( $block_key, $args ); + } +} + +// --------------------------- +// Enqueue Block Editor Assets +// --------------------------- +// 2.5.0: added for editor block scripts/styles +// reF: https://jasonyingling.me/enqueueing-scripts-and-styles-for-gutenberg-blocks/ +add_action( 'enqueue_block_editor_assets', 'raddio_station_block_editor_assets' ); +function raddio_station_block_editor_assets() { + + // --- get block callabacks --- + $callbacks = radio_station_get_block_callbacks(); + + // --- set block dependencies --- + $deps = array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-components', 'wp-editor' ); + + // --- set base block URL and path --- + $blocks_url = plugins_url( '/blocks/', RADIO_STATION_FILE ); + $blocks_path = RADIO_STATION_DIR . '/blocks/'; + + // --- loop callbacks to enqueue scripts --- + $block_scripts = array(); + foreach ( $callbacks as $block_slug => $callback ) { + $block_path = $blocks_path . $block_slug . '.js'; + if ( file_exists( $block_path ) ) { + + // --- set script data --- + $block_scripts[$block_slug] = array( + 'slug' => $block_slug, + 'handle' => $block_slug . '-js', + 'url' => $blocks_url . $block_slug . '.js', + 'version' => filemtime( $block_path ), + 'deps' => $deps, + ); + + } + } + + // --- filter scripts and loop to enqueue --- + $block_scripts = apply_filters( 'radio_station_block_scripts', $block_scripts ); + foreach ( $block_scripts as $script ) { + wp_enqueue_script( $script['handle'], $script['url'], $script['deps'], $script['version'], true ); + } + + // --- enqueue admin script for localized variables --- + $script_url = plugins_url( '/js/radio-station-admin.js', RADIO_STATION_FILE ); + $script_path = RADIO_STATION_DIR . 'js/radio-station-admin.js'; + $version = filemtime( $script_path ); + wp_enqueue_script( 'radio-station-admin', $script_url, $deps, $version, true ); + $js = radio_station_localization_script(); + wp_add_inline_script( 'radio-station-admin', $js ); + + // --- block editor support for conditional loading --- + $script_url = plugins_url( '/blocks/editor.js', RADIO_STATION_FILE ); + $script_path = RADIO_STATION_DIR . 'blocks/editor.js'; + $version = filemtime( $script_path ); + wp_enqueue_script( 'radio-blockedit-js', $script_url, $deps, $version, true ); + + // --- enqueue shortcode styles for blocks --- + $deps = array( 'wp-edit-blocks' ); + $stylesheets = array( 'shortcodes', 'schedule' ); // 'block-editor', 'blocks' + foreach ( $stylesheets as $stylekey ) { + $style_path = RADIO_STATION_DIR . 'css/rs-' . $stylekey . '.css'; + $style_url = plugins_url( 'css/rs-' . $stylekey . '.css', RADIO_STATION_FILE ); + $version = filemtime( $style_path ); + wp_enqueue_style( 'rs-' . $stylekey, $style_url, array(), $version, 'all' ); + } + + // --- add block control style fix inline --- + // - fix cutoff label widths - + $css = '.components-panel .components-panel__body.radio-block-controls .components-panel__row label { + width: 100%; max-width: 100%; min-width: 150px; overflow: visible;}' . "\n"; + // - select multiple height fix - + // ref: https://github.com/WordPress/gutenberg/issues/27166 + $css .= '.components-panel .components-panel__body.radio-block-controls .components-select-control__input[multiple] {min-height: 100px;}'; + $css = apply_filters( 'radio_station_block_control_styles', $css ); + wp_add_inline_style( 'wp-edit-blocks', $css ); + + // --- enqueue radio player styles --- + if ( array_key_exists( 'player', $callbacks ) ) { + $suffix = ''; // dev temp + $style_path = RADIO_STATION_DIR . 'player/css/radio-player' . $suffix . '.css'; + $style_url = plugins_url( '/player/css/radio-player' . $suffix . '.css', RADIO_STATION_FILE ); + $version = filemtime( $style_path ); + wp_enqueue_style( 'radio-player', $style_url, array(), $version, 'all' ); + + // --- enqueue player control styles inline --- + $control_styles = radio_station_player_control_styles( false ); + wp_add_inline_style( 'radio-player', $control_styles ); + } +} + +// ----------------------------- +// Enqueue Frontend Block Styles +// ----------------------------- +// note: this is currently unnecessary as styles are enqueued in shortcodes +// and the shortcodes are used as the block render_callback functions already +// 2.5.0: added for any future frontend block style fixes +// add_action( 'enqueue_block_assets', 'radio_station_enqueue_block_assets' ); +function radio_station_enqueue_block_assets() { + + // --- enqueue shortcode styles for blocks --- + // radio_station_enqueue_style( 'blocks' ); +} + +// ---------------------- +// AJAX Load Block Script +// ---------------------- +add_action( 'wp_ajax_radio_station_block_script', 'radio_station_block_script' ); +function radio_station_block_script() { + + if ( !isset( $_REQUEST['handle'] ) ) { + exit; + } + + $js = ''; + $handle = sanitize_text_field( $_REQUEST['handle'] ); + if ( 'clock' == $handle ) { + $js .= file_get_contents( RADIO_STATION_DIR . '/js/radio-station-clock.js' ); + } elseif ( 'countdown' == $handle ) { + $js .= file_get_contents( RADIO_STATION_DIR . '/js/radio-station-countdown.js' ); + } elseif ( 'player' == $handle ) { + $js .= file_get_contents( RADIO_STATION_DIR . '/player/js/radio-player.js' ); + } elseif ( 'schedule-table' == $handle ) { + $js .= radio_station_master_schedule_table_js(); + } elseif ( 'schedule-tabs' == $handle ) { + $js .= radio_station_master_schedule_tabs_js(); + } elseif ( 'schedule-list' == $handle ) { + $js .= radio_station_master_schedule_list_js(); + } + + // --- filter javascript --- + $js = apply_filters( 'radio_station_block_script', $js, $handle ); + + // --- output javascript --- + header( 'Content-Type: application/javascript' ); + if ( '' != $js ) { + echo $js; + } + exit; +} + diff --git a/includes/data-feeds.php b/includes/data-feeds.php index ae82614..ca84a5e 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -45,6 +45,10 @@ // - Not Found Feed Error // - Format Data to XML // - Convert Array to XML +// === Shift Conversions === +// - Convert Show Shift +// - Convert Show Shifts +// - Convert Schedule Shifts // --------------------- @@ -76,8 +80,9 @@ function radio_station_api_discovery_link() { $link = ""; $link = apply_filters( 'radio_station_api_discovery_link', $link ); if ( $link ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $link; + // 2.5.0: sanitize with wp_kses and allowed HTML + $allowed_html = radio_station_allowed_html( 'link' ); + echo wp_kses( $link, $allowed_html ); } } @@ -90,7 +95,7 @@ function radio_station_api_discovery_rsd() { $link = ''; $link = apply_filters( 'radio_station_api_discovery_rsd', $link ); if ( $link ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $link; } } @@ -112,11 +117,11 @@ function radio_station_add_feed_query_vars( $query_vars ) { // --- add query vars --- foreach ( $vars as $var ) { if ( !in_array( $var, $query_vars ) ) { - $query_vars[] = $var; - } - } + $query_vars[] = $var; + } + } - return $query_vars; + return $query_vars; } // ---------------- @@ -176,8 +181,9 @@ function radio_station_get_station_data() { 'updated' => $updated, 'success' => true, ); - $station_data = apply_filters( 'radio_station_station_data', $station_data ); + // --- filter and return --- + $station_data = apply_filters( 'radio_station_station_data', $station_data ); return $station_data; } @@ -205,7 +211,8 @@ function radio_station_get_broadcast_data() { // 2.3.3.5: just in case transients are the same if ( $current_show == $next_show ) { - $now = radio_station_get_time(); + // 2.5.0: change from radio_station_get_time + $now = radio_station_get_now(); $next_show = radio_station_get_next_show( $now ); $next_show = radio_station_convert_show_shift( $next_show ); } @@ -213,14 +220,15 @@ function radio_station_get_broadcast_data() { // 2.3.3.5: added current playlist to broadcast data $current_playlist = radio_station_get_current_playlist(); - // --- return broadcast info --- + // --- set broadcast info --- $broadcast = array( 'current_show' => $current_show, 'next_show' => $next_show, 'current_playlist' => $current_playlist, ); - $broadcast = apply_filters( 'radio_station_broadcast_data', $broadcast ); + // --- filter and return --- + $broadcast = apply_filters( 'radio_station_broadcast_data', $broadcast ); return $broadcast; } @@ -237,11 +245,8 @@ function radio_station_get_shows_data( $show = false ) { $show_ids = explode( ',', $show ); foreach ( $show_ids as $show ) { $id = absint( $show ); - if ( $id < 1 ) { - $show = sanitize_title( $show ); - } else { - $show = $id; - } + // 2.5.0: shortened conditional to single line + $show = ( $id < 1 ) ? sanitize_title( $show ) : $id; $show = radio_station_get_show( $show ); $show = radio_station_get_show_data_meta( $show, true ); $show = radio_station_convert_show_shifts( $show ); @@ -251,11 +256,8 @@ function radio_station_get_shows_data( $show = false ) { } } else { $id = absint( $show ); - if ( $id < 1 ) { - $show = sanitize_title( $show ); - } else { - $show = $id; - } + // 2.5.0: shortened conditional to single line + $show = ( $id < 1 ) ? sanitize_title( $show ) : $id; $show = radio_station_get_show( $show ); $show = radio_station_get_show_data_meta( $show, true ); $show = radio_station_convert_show_shifts( $show ); @@ -274,9 +276,10 @@ function radio_station_get_shows_data( $show = false ) { } } } + + // --- filter and return --- // 2.3.3.8: add show querystring parameter as second filter argument $shows = apply_filters( 'radio_station_shows_data', $shows, $show ); - return $shows; } @@ -311,7 +314,7 @@ function radio_station_get_genres_data( $genre = false ) { $genres[$name]['shows'] = array(); $genres[$name]['show_count'] = 0; if ( is_object( $shows ) && property_exists( $shows, 'posts' ) - && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { + && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { $genres[$name]['show_count'] = count( $shows->posts ); foreach ( $shows->posts as $show ) { $show = radio_station_get_show_data_meta( $show ); @@ -321,9 +324,10 @@ function radio_station_get_genres_data( $genre = false ) { } } } + + // --- filter and return --- // 2.3.3.8: add genre querystring parameter as second filter argument $genres = apply_filters( 'radio_station_genres_data', $genres, $genre ); - return $genres; } @@ -383,7 +387,7 @@ function radio_station_get_languages_data( $language = false ) { $languages_data[$slug]['shows'] = array(); $languages_data[$slug]['show_count'] = 0; if ( is_object( $shows ) && property_exists( $shows, 'posts' ) - && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { + && is_array( $shows->posts ) && ( count( $shows->posts ) > 0 ) ) { $languages_data[$slug]['show_count'] = count( $shows->posts ); foreach ( $shows->posts as $show ) { $show = radio_station_get_show_data_meta( $show ); @@ -409,8 +413,8 @@ function radio_station_get_languages_data( $language = false ) { } } + // --- filter and return --- $languages_data = apply_filters( 'radio_station_languages_data', $languages_data, $language ); - return $languages_data; } @@ -428,43 +432,31 @@ function radio_station_station_endpoint() { } $station = array(); - + // --- broadcast --- $broadcast = radio_station_get_broadcast_data(); $station['broadcast'] = $broadcast; - + // --- schedule --- $schedule = radio_station_get_current_schedule(); $schedule = radio_station_convert_schedule_shifts( $schedule ); - if ( count( $schedule ) > 0 ) { - $station['schedule'] = $schedule; - } else { - $station['schedule'] = array(); - } - + // 2.5.0: shortened conditional to single line + $station['schedule'] = ( count( $schedule ) > 0 ) ? $schedule : array(); + // --- shows --- $shows = radio_station_get_shows_data(); - if ( count( $shows ) > 0 ) { - $station['shows'] = $shows; - } else { - $station['shows'] = array(); - } - + // 2.5.0: shortened conditional to single line + $station['shows'] = ( count( $shows ) > 0 ) ? $shows : array(); + // --- genres --- $genres = radio_station_get_genres_data(); - if ( count( $genres ) > 0 ) { - $station['genres'] = $genres; - } else { - $station['genres'] = array(); - } + // 2.5.0: shortened conditional to single line + $station['genres'] = ( count( $genres ) > 0 ) ? $genres : array(); // --- languages --- $languages = radio_station_get_languages_data(); - if ( count( $languages ) > 0 ) { - $station['languages'] = $languages; - } else { - $station['languages'] = array(); - } + // 2.5.0: shortened conditional to single line + $station['languages'] = ( count( $languages ) > 0 ) ? $languages : array(); return $station; } @@ -480,7 +472,7 @@ function radio_station_broadcast_endpoint() { $broadcast = radio_station_get_broadcast_data(); if ( RADIO_STATION_DEBUG ) { - echo "Broadcast: " . print_r( $broadcast, true ); + echo "Broadcast: " . esc_html( print_r( $broadcast, true ) ); } return $broadcast; } @@ -527,7 +519,7 @@ function radio_station_schedule_endpoint() { } } } - + // --- set no shifts error --- if ( ( 0 == count( $schedule ) ) || ( 0 == $shiftcount ) ) { $code = 'no_scheduled_shifts'; @@ -540,7 +532,7 @@ function radio_station_schedule_endpoint() { // TODO: get schedule for specific date ? } else { - + // --- check there are any shifts --- $shiftcount = 0; if ( count( $schedule ) > 0 ) { @@ -548,16 +540,16 @@ function radio_station_schedule_endpoint() { $shiftcount = $shiftcount + count( $shifts ); } } - + // --- set no shifts error --- if ( ( 0 == count( $schedule ) ) || ( 0 == $shiftcount ) ) { $code = 'no_schedule'; $message = 'No Show shifts were found in the Schedule.'; $error = new WP_Error( $code, $message, array( 'status' => 400 ) ); } - + } - + // --- maybe set request error --- if ( isset( $error ) ) { $schedule = $error; @@ -565,9 +557,9 @@ function radio_station_schedule_endpoint() { // --- maybe output debug info --- if ( RADIO_STATION_DEBUG ) { - echo "Weekday: " . $weekday . PHP_EOL; - echo "Weekdays: " . print_r( $weekdays, true ) . PHP_EOL; - echo "Schedule: " . print_r( $schedule, true ); + echo "Weekday: " . esc_html( $weekday ) . PHP_EOL; + echo "Weekdays: " . esc_html( print_r( $weekdays, true ) ) . PHP_EOL; + echo "Schedule: " . esc_html( print_r( $schedule, true ) ); } return $schedule; @@ -613,10 +605,10 @@ function radio_station_shows_endpoint() { // --- maybe output debug info --- if ( RADIO_STATION_DEBUG ) { - echo "Show: " . $show . PHP_EOL; - echo "Shows: " . print_r( $shows, true ); + echo "Show: " . esc_html( $show ) . PHP_EOL; + echo "Shows: " . esc_html( print_r( $shows, true ) ); } - + return $shows; } @@ -660,10 +652,10 @@ function radio_station_genres_endpoint() { // --- maybe output debug info --- if ( RADIO_STATION_DEBUG ) { - echo "Genre: " . $genre . PHP_EOL; - echo "Genres: " . print_r( $genres, true ); + echo "Genre: " . esc_html( $genre ) . PHP_EOL; + echo "Genres: " . esc_html( print_r( $genres, true ) ); } - + return $genres; } @@ -690,8 +682,8 @@ function radio_station_languages_endpoint() { // --- get language list data --- $languages = radio_station_get_languages_data( $language ); if ( RADIO_STATION_DEBUG ) { - echo "Language: " . $language . PHP_EOL; - echo "Languages: " . print_r( $languages, true ); + echo "Language: " . esc_html( $language ) . PHP_EOL; + echo "Languages: " . esc_html( print_r( $languages, true ) ); } // --- maybe return route error --- @@ -708,7 +700,7 @@ function radio_station_languages_endpoint() { } $languages = new WP_Error( $code, $message, array( 'status' => 400 ) ); } - + return $languages; } @@ -725,7 +717,7 @@ function radio_station_register_rest_routes() { // --- check rest routes are enabled --- $enabled = radio_station_get_setting( 'enable_data_routes' ); - if ( 'yes' != $enabled ) { + if ( 'yes' !== (string) $enabled ) { return; } @@ -871,8 +863,7 @@ function radio_station_route_station( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $station, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $station, true ) ) . PHP_EOL; exit; } @@ -893,8 +884,7 @@ function radio_station_route_broadcast( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $broadcast, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $broadcast, true ) ) . PHP_EOL; exit; } @@ -915,11 +905,10 @@ function radio_station_route_schedule( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $schedule, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $schedule, true ) ) . PHP_EOL; exit; } - + return $schedule; } @@ -939,8 +928,7 @@ function radio_station_route_shows( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $show_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $show_list, true ) ) . PHP_EOL; exit; } @@ -963,11 +951,10 @@ function radio_station_route_genres( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $genre_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $genre_list, true ) ) . PHP_EOL; exit; } - + return $genre_list; } @@ -987,11 +974,10 @@ function radio_station_route_languages( $request ) { // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $language_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $language_list, true ) ) . PHP_EOL; exit; } - + return $language_list; } @@ -1028,7 +1014,7 @@ function radio_station_add_feeds() { // --- check feeds are enabled --- $enabled = radio_station_get_setting( 'enable_data_feeds' ); - if ( 'yes' != $enabled ) { + if ( 'yes' !== (string) $enabled ) { return; } @@ -1085,8 +1071,8 @@ function radio_station_add_feeds() { $rewrite_rules = get_option( 'rewrite_rules' ); // note this should provide an array not a string if ( !$rewrite_rules || !is_array( $rewrite_rules ) - || !array_key_exists( $baserule, $rewrite_rules ) - || !array_key_exists( $feedrule, $rewrite_rules ) ) { + || !array_key_exists( $baserule, $rewrite_rules ) + || !array_key_exists( $feedrule, $rewrite_rules ) ) { flush_rewrite_rules( false ); } @@ -1126,7 +1112,6 @@ function radio_station_get_feed_urls() { // --- filter and return --- $feeds = apply_filters( 'radio_station_feed_urls', $feeds ); - return $feeds; } @@ -1146,7 +1131,7 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { $key = '/' . $base . '/' . $endpoint; $routes[$key] = array( 'namespace' => $base, - 'methods' => array ( 'GET' ), + 'methods' => array( 'GET' ), // 'endpoints' => array(), // 'url' => $url, '_links' => array( @@ -1160,11 +1145,11 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { header( 'Content-Type: text/plain' ); - echo "Output: " . print_r( $radio, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $radio, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $radio ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $radio, 200 ); } } @@ -1179,15 +1164,15 @@ function radio_station_feed_station( $comment_feed, $feed_name ) { $station = radio_station_add_station_data( $station ); $station['endpoints'] = radio_station_get_feed_urls(); $station = apply_filters( 'radio_station_feed_station', $station ); + $status_code = 200; // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $station, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $station, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $station ); + // 2.5.0: use wp_json_encode and wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $station, $status_code ); } } @@ -1203,14 +1188,15 @@ function radio_station_feed_broadcast( $comment_feed, $feed_name ) { $broadcast = radio_station_add_station_data( $broadcast ); $broadcast['endpoints'] = radio_station_get_feed_urls(); $broadcast = apply_filters( 'radio_station_feed_broadcast', $broadcast ); + $broadcast = 200; // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $broadcast, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $broadcast, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - echo json_encode( $broadcast ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $broadcast, $status_code ); } } @@ -1225,15 +1211,15 @@ function radio_station_feed_schedule( $comment_feed, $feed_name ) { $schedule = radio_station_add_station_data( $schedule ); $schedule['endpoints'] = radio_station_get_feed_urls(); $schedule = apply_filters( 'radio_station_feed_schedule', $schedule ); + $status_code = 200; // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $schedule, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $schedule, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $schedule ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $schedule, $status_code ); } } @@ -1246,21 +1232,22 @@ function radio_station_feed_shows( $comment_feed, $feed_name ) { $show_list = radio_station_shows_endpoint(); if ( is_wp_error( $show_list ) ) { $show_list = radio_station_feed_not_found( $show_list ); + $status_code = 200; } else { $show_list = array( 'shows' => $show_list ); $show_list = radio_station_add_station_data( $show_list ); $show_list['endpoints'] = radio_station_get_feed_urls(); + $status_code = 200; } $show_list = apply_filters( 'radio_station_feed_shows', $show_list ); // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $show_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $show_list, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $show_list ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $show_list, $status_code ); } } @@ -1273,23 +1260,23 @@ function radio_station_feed_genres( $comment_feed, $feed_name ) { $genre_list = radio_station_genres_endpoint(); if ( is_wp_error( $genre_list ) ) { $genre_list = radio_station_feed_not_found( $genre_list ); + $status_code = 400; } else { $genre_list = array( 'genres' => $genre_list ); $genre_list = radio_station_add_station_data( $genre_list ); $genre_list['endpoints'] = radio_station_get_feed_urls(); + $status_code = 200; } $genre_list = apply_filters( 'radio_station_feed_genres', $genre_list ); // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $genre_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $genre_list, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $genre_list ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $genre_list, $status_code ); } - } // ------------------- @@ -1300,21 +1287,22 @@ function radio_station_feed_languages( $comment_feed, $feed_name ) { $language_list = radio_station_languages_endpoint(); if ( is_wp_error( $language_list ) ) { $language_list = radio_station_feed_not_found( $language_list ); + $status_code = 400; } else { $language_list = array( 'languages' => $language_list ); $language_list = radio_station_add_station_data( $language_list ); $language_list['endpoints'] = radio_station_get_feed_urls(); + $status_code = 200; } - $language_list = apply_filters( 'radio_station_feed_languages', $language_list ); + $language_list = apply_filters( 'radio_station_feed_languages', $language_list, $status_code ); // --- output debug display or feed data --- if ( RADIO_STATION_DEBUG ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "Output: " . print_r( $language_list, true ) . PHP_EOL; + echo "Output: " . esc_html( print_r( $language_list, true ) ) . PHP_EOL; } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $language_list ); + // 2.5.0: use wp_send_json instead of echo json_encode + // header( 'Content-Type: application/json' ); + wp_send_json( $language_list, $status_code ); } } @@ -1336,7 +1324,8 @@ function radio_station_feed_not_found( $error ) { // 2.3.3.8: change status code to 400 bad request from 404 not found $error = array( 'success' => false, - 'errors' => array( + // 2.5.0: change errors key to standarad data key + 'data' => array( 'status' => 400, 'code' => $code, 'title' => __( 'Error 400 No Requested Data', 'radio-station' ), @@ -1344,8 +1333,78 @@ function radio_station_feed_not_found( $error ) { 'detail' => $message, ), ); - return $error; } +// ------------------------- +// === Shift Conversions === +// ------------------------- +// 2.5.0: moved from support-functions.php + +// ------------------ +// Convert Show Shift +// ------------------ +// 2.3.0: 24 format shift for broadcast data endpoint +function radio_station_convert_show_shift( $shift ) { + + // note: timezone can be ignored here as just getting hours and minutes + if ( isset( $shift['start'] ) ) { + $shift['start'] = date( 'H:i', strtotime( $shift['start'] ) ); + } + if ( isset( $shift['end'] ) ) { + $shift['end'] = date( 'H:i', strtotime( $shift['end'] ) ); + } + return $shift; +} + +// ------------------- +// Convert Show Shifts +// ------------------- +// 2.3.0: 24 format shifts for show data endpoints +function radio_station_convert_show_shifts( $show ) { + + if ( isset( $show['schedule'] ) ) { + $schedule = $show['schedule']; + foreach ( $schedule as $i => $shift ) { + // 2.3.3.9: fixed to not use radio_station_convert_hour + $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); + $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); + // 2.3.3.9: added missing shift encore designation + // 2.4.0.6: fix to undefined index warning for encore + $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; + $schedule[$i] = array( + 'day' => $shift['day'], + 'start' => $start_hour . ':' . $shift['start_min'], + 'end' => $end_hour . ':' . $shift['end_min'], + 'encore' => $encore, + ); + } + $show['schedule'] = $schedule; + } + return $show; +} + +// ----------------------- +// Convert Schedule Shifts +// ----------------------- +// 2.3.0: 24 format shifts for schedule data endpoint +function radio_station_convert_schedule_shifts( $schedule ) { + + if ( is_array( $schedule ) && ( count( $schedule ) > 0 ) ) { + foreach ( $schedule as $day => $shows ) { + $new_shows = array(); + if ( is_array( $shows ) && ( count( $shows ) > 0 ) ) { + foreach ( $shows as $time => $show ) { + $new_show = $show; + $new_show['start'] = radio_station_convert_shift_time( $show['start'], 24 ); + $new_show['end'] = radio_station_convert_shift_time( $show['end'], 24 ); + $new_shows[] = $new_show; + } + } + $schedule[$day] = $new_shows; + } + } + return $schedule; +} + diff --git a/includes/legacy.php b/includes/legacy.php index 93bc975..9015ed0 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -403,10 +403,11 @@ function radio_station_get_now_playing( $time = false ) { // TODO: improve handling of playlists for overrides if ( isset( $current_show['override'] ) && $current_show['override'] ) { + // TODO: check if override is linked to show $playlist = apply_filters( 'radio_station_override_now_playing', false, $current_show['override'] ); return $playlist; } - + // 2.3.3.9: fix to assign current show shifts to playlist data // TODO: match current playlist to assigned show shift ? $playlist = array(); @@ -432,6 +433,9 @@ function radio_station_get_now_playing( $time = false ) { ), ); $playlist_posts = get_posts( $args ); + // 2.5.0: added filter for playlist posts + $playlist_posts = apply_filters( 'radio_station_playlist_posts', $playlist_posts, $show_id, $time ); + $playlist['query'] = $args; $playlist['posts'] = $playlist_posts; @@ -481,7 +485,6 @@ function radio_station_get_now_playing( $time = false ) { // --- filter and return tracks --- // 2.3.2: added time argument to filter $playlist = apply_filters( 'radio_station_show_now_playing', $playlist, $show_id, $time ); - return $playlist; } @@ -663,7 +666,7 @@ function radio_station_deprecated_function( $function, $replacement, $version ) } } } - + if ( !function_exists( 'station_current_schedule' ) ) { function station_current_schedule( $scheds = array() ) { radio_station_deprecated_function( __FUNCTION__, 'radio_station_current_schedule', '2.2.0' ); @@ -677,7 +680,7 @@ function station_convert_time( $time = array() ) { return radio_station_convert_time( $time ); } } - + if ( !function_exists( 'station_convert_schedule_to_24hour' ) ) { function station_convert_schedule_to_24hour( $sched = array() ) { radio_station_deprecated_function( __FUNCTION__, 'radio_station_convert_schedule_to_24hour', '2.2.0' ); @@ -705,7 +708,7 @@ function myplaylist_get_now_playing() { return radio_station_get_now_playing(); } } - + if ( !function_exists( 'master_get_overrides' ) ) { function master_get_overrides( $currenthour = false, $date = false ) { radio_station_deprecated_function( __FUNCTION__, 'radio_station_master_get_overrides', '2.2.0' ); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index b1d22cb..2f252bc 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -45,6 +45,11 @@ function radio_station_master_schedule( $atts ) { $atts['days'] = $atts['single_day']; unset( $atts['single_day'] ); } + // 2.5.0: convert time attribute to time_format + if ( isset( $atts['time'] ) ) { + $atts['time_format'] = $atts['time']; + unset( $atts['time'] ); + } // --- get default clock display setting --- $clock = radio_station_get_setting( 'schedule_clock' ); @@ -65,6 +70,8 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: added active_date attribute for schedule switching $time_format = (int) radio_station_get_setting( 'clock_time_format' ); $defaults = array( + // --- schedule view --- + 'view' => 'table', // --- control display options --- 'selector' => 1, @@ -72,10 +79,6 @@ function radio_station_master_schedule( $atts ) { 'timezone' => 1, // --- schedule display options --- - 'time' => $time_format, - 'show_times' => 1, - 'show_link' => 1, - 'view' => 'table', 'days' => false, 'start_day' => false, 'start_date' => false, @@ -83,13 +86,11 @@ function radio_station_master_schedule( $atts ) { 'display_day' => 'short', 'display_date' => 'jS', 'display_month' => 'short', - - // --- converted and deprecated --- - // 'list' => 0, - // 'show_djs' => 0, - // 'display_show_time' => 1, + 'time_format' => $time_format, // --- show display options --- + 'show_link' => 1, + 'show_times' => 1, 'show_image' => 0, 'show_desc' => 0, 'show_hosts' => 0, @@ -97,6 +98,11 @@ function radio_station_master_schedule( $atts ) { 'show_genres' => 0, 'show_encore' => 1, 'show_file' => 0, + + // --- converted and deprecated --- + // 'list' => 0, + // 'show_djs' => 0, + // 'display_show_time' => 1, ); // 2.3.0: change some defaults for tabbed and list view // 2.3.2: check for comma separated view list @@ -150,7 +156,7 @@ function radio_station_master_schedule( $atts ) { $defaults['display_date'] = false; } elseif ( in_array( 'list', $views ) ) { $defaults['list_show_genres'] = 1; - $defaults['list_display_date'] = false; + $defaults['list_display_date'] = false; } if ( ( 'divs' == $atts['view'] ) || ( in_array( 'divs', $views ) ) ) { // 2.3.3.8: moved divs view only default here @@ -164,7 +170,11 @@ function radio_station_master_schedule( $atts ) { // --- merge attributes with defaults --- $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); if ( RADIO_STATION_DEBUG ) { - echo 'Master Schedule Shortcode Attributes: ' . print_r( $atts, true ) . ''; + echo 'Master Schedule Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; + } + // 2.5.0: force empty start_day string to false + if ( '' == $atts['start_day'] ) { + $atts['start_day'] = false; } // --- enqueue schedule stylesheet --- @@ -176,15 +186,13 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: remove check if clock shortcode present // 2.3.3.6: set new line for easier debug viewing - $newline = ''; - if ( RADIO_STATION_DEBUG ) { - $newline = "\n"; - } + // 2.5.0: just set new line for output readability + // $newline = "\n"; // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views // 2.3.2: moved shortcode calls inside and added filters - $output .= '
    ' . $newline; + $output .= '
    ' . "\n"; $controls = array(); @@ -192,26 +200,26 @@ function radio_station_master_schedule( $atts ) { if ( $atts['clock'] ) { // --- radio clock --- - $controls['clock'] = '
    ' . $newline; + $controls['clock'] = '
    ' . "\n"; $clock_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); - $controls['clock'] .= PHP_EOL . '
    ' . $newline; + $controls['clock'] .= "\n" . '
    ' . "\n"; } elseif ( $atts['timezone'] ) { // --- radio timezone --- - $controls['timezone'] = '
    ' . $newline; + $controls['timezone'] = '
    ' . "\n"; $timezone_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); - $controls['timezone'] .= PHP_EOL . '
    ' . $newline; + $controls['timezone'] .= "\n" . '
    ' . "\n"; } // --- genre selector --- if ( $atts['selector'] ) { - $controls['selector'] = '
    ' . $newline; + $controls['selector'] = '
    ' . "\n"; $controls['selector'] .= radio_station_master_schedule_genre_selector(); - $controls['selector'] .= PHP_EOL . '
    ' . $newline; + $controls['selector'] .= "\n" . '
    ' . "\n"; } // 2.3.1: add filters for control order @@ -230,13 +238,13 @@ function radio_station_master_schedule( $atts ) { } } - $output .= '

    ' . $newline; + $output .= '

    ' . "\n"; $output = apply_filters( 'radio_station_schedule_controls_output', $output, $atts ); // --- hidden inputs for calendar start/active dates --- // 2.3.3.9: added for schedule week change reloading if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { - if ( $atts['start_date'] == date( 'Y-m-d', strtotime( $atts['start_date'] ) ) ) { + if ( date( 'Y-m-d', strtotime( $atts['start_date'] ) ) == $atts['start_date'] ) { $start_date = $atts['start_date']; } } @@ -281,35 +289,65 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: use output buffering on templates // 2.3.3.9: get and enqueue scripts inline directly if ( 'table' == $atts['view'] ) { + + // --- load table view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-table.php' ); require $template; $html = ob_get_contents(); ob_end_clean(); - $html = apply_filters( 'master_schedule_table_view', $html, $atts ); + + // --- enqueue table view script --- $js = radio_station_master_schedule_table_js(); wp_add_inline_script( 'radio-station', $js ); + + // --- filter and return --- + // 2.5.0: added prefixed filter for consistency + $html = apply_filters( 'radio_station_master_schedule_table_view', $html, $atts ); + // note: keep backwards compatible non-prefixed filter + $html = apply_filters( 'master_schedule_table_view', $html, $atts ); return $output . $html; + } elseif ( 'tabs' == $atts['view'] ) { + + // --- load tabs view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-tabs.php' ); require $template; $html = ob_get_contents(); ob_end_clean(); - $html = apply_filters( 'master_schedule_tabs_view', $html, $atts ); + + // --- enqueue tabs view script --- $js = radio_station_master_schedule_tabs_js(); - wp_add_inline_script( 'radio-station', $js ); + wp_add_inline_script( 'radio-station', $js ); + + // --- filter and return --- + // 2.5.0: added prefixed filter for consistency + $html = apply_filters( 'radio_station_master_schedule_tabs_view', $html, $atts ); + // note: keep backwards compatible non-prefixed filter + $html = apply_filters( 'master_schedule_tabs_view', $html, $atts ); return $output . $html; + } elseif ( 'list' == $atts['view'] ) { + + // --- load list view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-list.php' ); require $template; $html = ob_get_contents(); ob_end_clean(); - $html = apply_filters( 'master_schedule_list_view', $html, $atts ); + + // --- enqueue list view script --- $js = radio_station_master_schedule_list_js(); wp_add_inline_script( 'radio-station', $js ); + + // --- filter and return --- + // 2.5.0: added prefixed filter for consistency + $html = apply_filters( 'radio_station_master_schedule_list_view', $html, $atts ); + // note: keep backwards compatible non-prefixed filter + $html = apply_filters( 'master_schedule_list_view', $html, $atts ); return $output . $html; + } // ---------------------- @@ -425,10 +463,10 @@ function radio_station_master_schedule( $atts ) { // if it ends after midnight, fix it // if it starts at night and ends in the morning, end hour is on the following day if ( ( 'pm' === $time['time']['start_meridian'] && 'am' === $time['time']['end_meridian'] ) || - // if the start and end times are identical, assume the end time is the following day - ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || - // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day - ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) + // if the start and end times are identical, assume the end time is the following day + ( $time['time']['start_hour'] . $time['time']['start_min'] . $time['time']['start_meridian'] === $time['time']['end_hour'] . $time['time']['end_min'] . $time['time']['end_meridian'] ) || + // if the start hour is in the morning, and greater than the end hour, assume end hour is the following day + ( 'am' === $time['time']['start_meridian'] && $time['time']['start_hour'] > $time['time']['end_hour'] ) ) { if ( 12 === (int) $atts['time'] ) { @@ -459,7 +497,7 @@ function radio_station_master_schedule( $atts ) { // --- include the specified master schedule output template --- // 2.3.0: check for user theme templates if ( 'divs' == $atts['view'] ) { - $output = ''; // no selector / clock support yet + $output = ''; // no selector / clock support $template = radio_station_get_template( 'file', 'master-schedule-div.php' ); require $template; } elseif ( 'legacy' == $atts['view'] ) { @@ -509,7 +547,7 @@ function radio_station_master_schedule_loader_js( $atts ) { else if (direction == 'previous') {offset = -(7 * 24 * 60 * 60 * 1000);} else if (direction == 'next') {offset = (7 * 24 * 60 * 60 * 1000);} newdate = new Date(new Date(startdate).getTime() + offset).toISOString().substr(0,10); - url = '" . $loader_url . "&view='+view; + url = '" . esc_urL( $loader_url ) . "&view='+view; timestamp = Math.floor((new Date()).getTime() / 1000); url += '×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; if (startday != '') {url += '&start_day='+startday;} @@ -518,13 +556,13 @@ function radio_station_master_schedule_loader_js( $atts ) { if (document.getElementById('schedule-'+view+'-loader').src != url) { document.getElementById('schedule-'+view+'-loader').src = url; } - }" . PHP_EOL; + }" . "\n"; // --- filter and return --- $js = apply_filters( 'radio_station_master_schedule_loader_js', $js ); return $js; -} - +} + // -------------------- // AJAX Schedule Loader // -------------------- @@ -533,21 +571,21 @@ function radio_station_master_schedule_loader_js( $atts ) { function radio_station_ajax_schedule_loader() { // --- maybe clear cached data --- - if ( isset( $_REQUEST['clear'] ) && ( '1' == $_REQUEST['clear'] ) ) { + if ( isset( $_REQUEST['clear'] ) && ( '1' == sanitize_title( $_REQUEST['clear'] ) ) ) { radio_station_clear_cached_data( false ); } // --- sanitize shortcode attributes --- + $debug = true; $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); - if ( RADIO_STATION_DEBUG ) { - print_r( $_REQUEST ); - echo "Sanitized Master Schedule Shortcode Attributes: " . print_r( $atts, true ); + if ( RADIO_STATION_DEBUG || $debug ) { + echo esc_html( print_r( $_REQUEST, true ) ); + echo "Sanitized Master Schedule Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); } // --- output schedule contents --- - echo '
    '; + // 2.5.0: remove unused schedule contents wrap echo radio_station_master_schedule( $atts ); - echo '
    '; $js = ''; if ( strstr( $atts['view'], ',' ) ) { @@ -574,50 +612,68 @@ function radio_station_ajax_schedule_loader() { } // --- send new schedule to parent window --- - // $js .= "document.getElementById('schedule-contents').innerHTML = '';" . PHP_EOL; - $js .= "schedule = document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML;" . PHP_EOL; - $js .= "parent.document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML = schedule;" . PHP_EOL; + $js .= "schedule = document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML = schedule;" . "\n"; if ( isset( $panels_id ) ) { - $js .= "panels = document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML;" . PHP_EOL; - $js .= "parent.document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML = panels;" . PHP_EOL; + $js .= "panels = document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML = panels;" . "\n"; + // 2.5.0: unset panels to prevent possible multiple view conflict + unset( $panels_id ); } // --- copy the new start date value to the parent window --- - $js .= "start_date = document.getElementById('schedule-start-date').value;" . PHP_EOL; - $js .= "parent.document.getElementById('schedule-start-date').value = start_date;" . PHP_EOL; - - // --- maybe retrigger view(s) javascript in parent window --- - // (uses set interval cycle in case script not yet loaded) - $js .= "var genres_highlighted = false;" . PHP_EOL; - $js .= "schedule_loader = setInterval(function() {" . PHP_EOL; - $js .= "if (!genres_highlighted && (typeof parent.radio_genre_highlight == 'function')) {" . PHP_EOL; - $js .= "parent.radio_genre_highlight();" . PHP_EOL; - $js .= "genres_highlighted = true;" . PHP_EOL; - $js .= "}" . PHP_EOL; + // TODO: get by child class of schedule_id! + $js .= "start_date = document.getElementById('schedule-start-date').value;" . "\n"; + $js .= "parent.document.getElementById('schedule-start-date').value = start_date;" . "\n"; + + } + + // --- maybe retrigger view(s) javascript in parent window --- + // (uses set interval cycle in case script not yet loaded) + $js .= "var genres_highlighted = false;" . "\n"; + $js .= "schedule_loader = setInterval(function() {" . "\n"; + + // --- genre highlighter --- + $js .= "if (!genres_highlighted && (typeof parent.radio_genre_highlight == 'function')) {" . "\n"; + $js .= "parent.radio_genre_highlight();" . "\n"; + $js .= "genres_highlighted = true;" . "\n"; + $js .= "}" . "\n"; + + // 2.5.0: loop views to add individual init scripts + foreach ( $views as $view ) { + + $init_js = ''; if ( 'table' == $view ) { - $js .= "if (typeof parent.radio_table_initialize == 'function') {"; - $js .= "parent.radio_table_initialize();" . PHP_EOL; - $js .= "clearInterval(schedule_loader);" . PHP_EOL; - $js .= "}" . PHP_EOL; + $init_js .= "if (typeof parent.radio_table_initialize == 'function') {" . "\n"; + $init_js .= "parent.radio_table_initialize();" . "\n"; + // $init_js .= "clearInterval(schedule_loader);" . "\n"; + $init_js .= "}" . "\n"; } elseif ( 'tabs' == $view ) { - $js .= "if (typeof parent.radio_tabs_initialize == 'function') {"; - $js .= "parent.radio_tabs_init = false;" . PHP_EOL; - $js .= "parent.radio_tabs_initialize();" . PHP_EOL; - $js .= "clearInterval(schedule_loader);" . PHP_EOL; - $js .= "}" . PHP_EOL; + $init_js .= "if (typeof parent.radio_tabs_initialize == 'function') {" . "\n"; + $init_js .= "parent.radio_tabs_init = false;" . "\n"; + $init_js .= "parent.radio_tabs_initialize();" . "\n"; + // $init_js .= "clearInterval(schedule_loader);" . "\n"; + $init_js .= "}" . "\n"; } elseif ( 'list' == $view ) { - $js .= "if (typeof parent.radio_list_highlight == 'function') {"; - $js .= "parent.radio_list_highlight();" . PHP_EOL; - $js .= "clearInterval(schedule_loader);" . PHP_EOL; + $init_js .= "if (typeof parent.radio_list_highlight == 'function') {" . "\n"; + $init_js .= "parent.radio_list_highlight();" . "\n"; + // $init_js .= "clearInterval(schedule_loader);" . "\n"; + $init_js .= "}" . "\n"; } - $js .= "if (typeof parent.radio_convert_times == 'function') {parent.radio_convert_times();}" . PHP_EOL; + // 2.5.0: added individual init script filtering + $init_js = apply_filters( 'radio_station_master_schedule_init_script', $init_js, $view, $atts ); - // --- placeholder for extra loader functions --- - // (do not remove or modify this line) - $js .= "/* LOADER PLACEHOLDER */"; + } - $js .= "}, 2000);" . PHP_EOL; - } + $js .= "if (typeof parent.radio_convert_times == 'function') {parent.radio_convert_times();}" . "\n"; + + // --- placeholder for extra loader functions --- + // (do not remove or modify this line - for backwards compatibility) + $js .= "/* LOADER PLACEHOLDER */"; + + $js .= "clearInterval(schedule_loader);" . "\n"; + + $js .= "}, 2000);" . "\n"; // --- filter load script and output --- $js = apply_filters( 'radio_station_master_schedule_load_script', $js, $atts ); @@ -626,7 +682,6 @@ function radio_station_ajax_schedule_loader() { } exit; - } @@ -658,7 +713,7 @@ function radio_station_master_schedule_genre_selector() { $genre_links = array(); foreach ( $genres as $i => $genre ) { $slug = sanitize_title_with_dashes( $genre->name ); - $onclick = "radio_genre_highlight('" . esc_attr( $slug ) . "');"; + $onclick = "radio_genre_highlight('" . esc_attr( $slug ) . "');"; $title = __( 'Click to toggle Highlight of Shows with this Genre.', 'radio-station' ); $genre_link = ''; $genre_link .= esc_html( $genre->name ) . ''; @@ -884,7 +939,7 @@ classes = end.attr('class').split(' '); /* Shift Day Left / Right */ function radio_shift_day(leftright) { radio_table_responsive(leftright); return false; - }" . PHP_EOL; + }" . "\n"; // --- filter and return --- // 2.3.3.9: add filter and return instead of inline enqueue @@ -924,7 +979,7 @@ function radio_station_master_schedule_tabs_js() { jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); jQuery('#'+panelID).addClass('active-day-panel'); }); - }" . PHP_EOL; + }" . "\n"; // --- tabbed view responsiveness --- // 2.3.0: added for tabbed responsiveness @@ -1121,7 +1176,7 @@ classes = end.attr('class').split(' '); /* Shift Day Left / Right */ function radio_shift_tab(leftright) { radio_tabs_responsive(leftright); return false; - }" . PHP_EOL; + }" . "\n"; // --- filter and return --- // 2.3.3.9: add filter and return instead of inline enqueue @@ -1182,10 +1237,11 @@ classes = jQuery(this).attr('class').split(/\s+/); if (radio_list_split) { jQuery('.'+radio_list_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } - }" . PHP_EOL; + }" . "\n"; // --- filter and return --- // 2.3.3.9: add filter and return instead of inline enqueue - $js = apply_filters( 'radio_station_master_schedule_list_js', $js ); + $js = apply_filters( 'radio_station_master_schedule_list_js', $js ); return $js; } + diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 8dd4ba8..781df86 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -93,25 +93,25 @@ function radio_station_top_meta_boxes() { $current_screen = get_current_screen(); if ( RADIO_STATION_DEBUG ) { - echo ""; - echo ""; - echo ""; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; $metabox_order = get_user_option( 'meta-box-order_' . $current_screen->id ); $hidden_metaboxes = get_user_option( 'metaboxhidden_' . $current_screen->id ); $screen_layout = get_user_option( 'screen_layout_' . $current_screen->id ); - echo ""; - echo ""; - echo ""; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; } // --- top metabox output --- // 2.3.2: change metabox ID from rs-top - // (- is not supported in metabox ID for sort order saving) - // (causing bug where sorted metaboxes disappear completely!) + // "-" is not supported in metabox ID for sort order saving! + // causing bug where sorted metaboxes disappear completely. do_meta_boxes( $current_screen, 'rstop', $post ); if ( RADIO_STATION_DEBUG ) { - echo ""; + echo '' . "\n"; } } @@ -149,10 +149,6 @@ function radio_station_modify_taxonomy_metabox_positions() { if ( isset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ) ) { unset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ); } - - // if ( RADIO_STATION_DEBUG ) { - // echo ""; - // } } @@ -207,63 +203,63 @@ function radio_station_show_language_metabox() { } if ( isset( $label ) ) { - echo '' . esc_html( __( 'Main Radio Language', 'radio-station' ) ) . ':
    '; - echo esc_html( $label ) . '
    '; + echo '' . esc_html( __( 'Main Radio Language', 'radio-station' ) ) . ':
    ' . "\n"; + echo esc_html( $label ) . '
    ' . "\n"; } - echo '
    ' . esc_html( __( 'Select below if Show language(s) differ.', 'radio-station' ) ) . '
    ' . PHP_EOL; + echo '
    ' . esc_html( __( 'Select below if Show language(s) differ.', 'radio-station' ) ) . '
    ' . "\n"; - echo '
      ' . PHP_EOL; + echo '
        ' . "\n"; - // --- loop existing terms --- - $term_slugs = array(); - foreach ( $terms as $term ) { + // --- loop existing terms --- + $term_slugs = array(); + foreach ( $terms as $term ) { - $slug = $term->slug; - $term_slugs[] = $slug; - $label = $term->name; - if ( !empty( $term->description ) ) { - $label .= ' (' . $term->description . ')'; - } + $slug = $term->slug; + $term_slugs[] = $slug; + $label = $term->name; + if ( !empty( $term->description ) ) { + $label .= ' (' . $term->description . ')'; + } - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; - // --- hidden input for term saving --- - // echo ''; - echo '' . PHP_EOL; + // --- hidden input for term saving --- + // echo ''; + echo '' . "\n"; - // --- language term label --- - echo '' . PHP_EOL; + // --- language term label --- + echo '' . "\n"; - // --- remove term button --- - echo '' . PHP_EOL; + // --- remove term button --- + echo '' . "\n"; - echo '
      • ' . PHP_EOL; - } + echo '' . "\n"; + } - echo '
      ' . PHP_EOL; + echo '
    ' . "\n"; - // --- new language selection list --- - echo '
    ' . PHP_EOL; + // --- new language selection list --- + echo '
    ' . "\n"; - // --- add language term button --- - echo '
    ' . esc_html( __( 'Click on a Language to Add it.', 'radio-station' ) ) . '
    ' . PHP_EOL; + // --- add language term button --- + echo '
    ' . esc_html( __( 'Click on a Language to Add it.', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- language selection javascript --- $js = "function radio_add_language() { @@ -319,15 +315,14 @@ function radio_remove_language(term) { margin-left: 10px; padding: 0 7px; color: #E00; border-radius: 7px; }"; // 2.3.3.9: added language edit styles filter + // 2.5.0: use wp_kses_post on CSS output $css = apply_filters( 'radio_station_language_edit_styles', $css ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo ''; + echo ''; // --- add script inline --- // 2.3.3.9: added language edit script filter $js = apply_filters( 'radio_station_language_edit_script', $js ); wp_add_inline_script( 'radio-station-admin', $js ); - } // ---------------------------- @@ -351,7 +346,8 @@ function radio_station_language_term_filter( $post_id ) { } // --- loop and set posted terms --- - $terms = $_POST[RADIO_STATION_LANGUAGES_SLUG]; + // 2.5.0: use sanitize_text_field on posted value + $terms = sanitize_text_field( $_POST[RADIO_STATION_LANGUAGES_SLUG] ); $term_ids = array(); if ( is_array( $terms ) && ( count( $terms ) > 0 ) ) { @@ -391,7 +387,6 @@ function radio_station_language_term_filter( $post_id ) { // --- set the language terms --- wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); - } @@ -449,92 +444,93 @@ function radio_station_show_info_metabox() { // 2.3.2: increase label width to 120px for disable download field label // 2.3.3.9: move meta_inner ID to class // 2.3.3.9: change input paragraphs to list style - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; - echo '
      ' . PHP_EOL; + echo '
        ' . "\n"; // --- show active switch --- - echo '
      • ' . PHP_EOL; - echo '
      • ' . "\n"; + echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . esc_html( __( 'Check this box if show is currently active (Show will not appear on schedule if unchecked.)', 'radio-station' ) ) . '' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
        ' . "\n"; + echo '' . PHP_EOL; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . esc_html( __( 'Check this box if show is currently active (Show will not appear on schedule if unchecked.)', 'radio-station' ) ) . '' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; // --- show website link --- - echo '
      • ' . PHP_EOL; - echo '
        '; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; + echo '
        ' . "\n"; + echo '
      • ' . "\n"; // --- show email address --- // 2.3.3.6: change text string from DJ / Host email (as maybe multiple hosts) - echo '
      • ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
        '. PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; + echo '
        ' . "\n"; + echo '
      • ' . "\n"; // --- show phone number --- // 2.3.3.6: added Show phone number input field - echo '
      • ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; + echo '
        ' . "\n"; + echo '
      • ' . "\n"; // --- show latest audio --- - echo '
      • ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; + echo '
        ' . "\n"; + echo '
      • ' . "\n"; // --- disable download switch --- // 2.3.2: added show download disable field - echo '
      • ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; echo '
        '; - echo '
      • ' . PHP_EOL; + echo '' . "\n"; // 2.3.0: added patreon page field - echo '
      • ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo ' https://patreon.com/' . PHP_EOL; - echo '
        ' . PHP_EOL; - echo '
      • ' . PHP_EOL; + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '
        ' . "\n"; + echo ' https://patreon.com/' . "\n"; + echo '
        ' . "\n"; + echo '
      • ' . "\n"; // 2.3.3.5: added action for further custom fields do_action( 'radio_station_show_fields', $post_id, 'show' ); - echo '
      ' . PHP_EOL; + echo '
    ' . "\n"; // --- close meta inner box --- - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- inside show metaboxes --- // 2.3.0: move metaboxes together inside meta @@ -554,37 +550,39 @@ function radio_station_show_info_metabox() { ); // --- display inside metaboxes --- - echo '
    ' . PHP_EOL; - $i = 1; - foreach ( $inside_metaboxes as $key => $metabox ) { + echo '
    ' . "\n"; - $classes = array( 'postbox' ); - if ( 1 == $i ) { - $classes[] = 'first'; - } elseif ( count( $inside_metaboxes ) == $i ) { - $classes[] = 'last'; - } - $class = implode( ' ', $classes ); + $i = 1; + foreach ( $inside_metaboxes as $key => $metabox ) { - echo '
    ' . PHP_EOL; + $classes = array( 'postbox' ); + if ( 1 == $i ) { + $classes[] = 'first'; + } elseif ( count( $inside_metaboxes ) == $i ) { + $classes[] = 'last'; + } + $class = implode( ' ', $classes ); - // echo ''; + echo '
    ' . "\n"; - // 2.3.2: remove class="hndle" to prevent box sorting - $widget_title = $metabox['title']; - echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . PHP_EOL; - echo '
    ' . PHP_EOL; - call_user_func( $metabox['callback'] ); - echo '
    ' . PHP_EOL; + // echo ''; - echo '
    ' . PHP_EOL; + // 2.3.2: remove class="hndle" to prevent box sorting + $widget_title = $metabox['title']; + echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . "\n"; + echo '
    ' . "\n"; + call_user_func( $metabox['callback'] ); + echo '
    ' . "\n"; - $i++; - } - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + + $i++; + } + + echo '
    ' . "\n"; // --- set metabox styles --- // 2.3.3.9: remove !important from display inline-block rule @@ -601,9 +599,9 @@ function radio_station_show_info_metabox() { // --- filter and output styles --- // 2.3.3.9: added edit styles filter + // 2.5.0: added wp_kses_post to CSS output $css = apply_filters( 'radio_station_show_edit_styles', $css ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo ''; + echo ''; } // ------------------------------ @@ -656,17 +654,19 @@ function radio_station_show_hosts_metabox() { foreach ( $hosts as $i => $host ) { // 2.2.8: remove strict in_array checking if ( in_array( $host->ID, $current ) ) { - unset( $hosts[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $hosts, $host ); // prepend the user to the beginning of the array + // unset first, or prepending will change the index numbers and cause you to delete the wrong item + unset( $hosts[$i] ); + // prepend the user to the beginning of the array + array_unshift( $hosts, $host ); } } // --- Host Selection Input --- // 2.2.2: add fix to make DJ multi-select input full metabox width // 2.3.3.9: move meta_inner ID to class - echo '
    '; - echo '' . "\n"; + echo '' . "\n"; foreach ( $hosts as $host ) { // 2.2.2: set DJ display name maybe with username $display_name = $host->display_name; @@ -679,15 +679,17 @@ function radio_station_show_hosts_metabox() { if ( in_array( $host->ID, $current ) ) { echo ' selected="selected"'; } - echo '>' . esc_html( $display_name ) . '' . PHP_EOL; + echo '>' . esc_html( $display_name ) . '' . "\n"; } - echo '' . PHP_EOL; + echo '' . "\n"; - // --- multiple selection helper text --- - // 2.3.0: added multiple selection helper text - echo '
    ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
    ' . PHP_EOL; + // --- multiple selection helper text --- + // 2.3.0: added multiple selection helper text + echo '
    '; + echo esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ); + echo '
    ' . "\n"; - echo '
    ' . PHP_EOL; + echo '' . "\n"; } // ------------------------------------ @@ -745,8 +747,8 @@ function radio_station_show_producers_metabox() { // --- Producer Selection Input --- // 2.3.3.9: move meta_inner ID to class - echo '
    ' . PHP_EOL; - echo '' . "\n"; echo '' . PHP_EOL; foreach ( $producers as $producer ) { $display_name = $producer->display_name; @@ -757,16 +759,16 @@ function radio_station_show_producers_metabox() { if ( in_array( $producer->ID, $current ) ) { echo ' selected="selected"'; } - echo '>' . esc_html( $display_name ) . '' . PHP_EOL; + echo '>' . esc_html( $display_name ) . '' . "\n"; } - echo '' . PHP_EOL; + echo '' . "\n"; // --- multiple selection helper text --- echo '
    '; - esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ); - echo '
    ' . PHP_EOL; + esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ); + echo '
    ' . "\n"; - echo ''; + echo '' . "\n"; } // ----------------------- @@ -798,96 +800,106 @@ function radio_station_show_shifts_metabox() { // --- hidden debug fields --- // 2.3.2: added save debugging field if ( RADIO_STATION_DEBUG ) { - echo ''; + echo '' . "\n"; } if ( RADIO_STATION_SAVE_DEBUG ) { - echo ''; + echo '' . "\n"; } // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); // 2.3.3.9: move meta_inner ID to class - echo '
    ' . PHP_EOL; - - echo '
    ' . PHP_EOL; - - // 2.3.2: added to bypass shift check on add (for debugging) - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - echo '' . PHP_EOL; - } + echo '
    ' . "\n"; - // --- output show shifts table --- - // 2.3.2: separated table function (for AJAX saving) - $table = radio_station_show_shifts_table( $post->ID ); - - // --- show inactive message --- - // 2.3.0: added show inactive reminder message - if ( !$table['active'] ) { - // 2.3.3.9: change to mismatched div class - echo '
    ' . PHP_EOL; - echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; - echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); - echo '
    ' . PHP_EOL; - } + echo '
    ' . "\n"; - // --- shift conflicts message --- - // 2.3.0: added instructions for fixing shift conflicts - // 2.3.3.9: check conflict count instead of boolean test - if ( count( $table['conflicts'] ) > 0 ) { - radio_station_shifts_conflict_message(); - } + // 2.3.2: added to bypass shift check on add (for debugging) + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + echo '' . "\n"; + } - // --- output shift list --- - if ( '' != $table['list'] ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $table['list']; - } - echo '
    ' . PHP_EOL; - - // --- shift save/add buttons --- - // 2.3.0: center add shift button - // 2.3.2: added show shifts AJAX save button (for existing posts only) - // 2.3.2: added show shifts clear button - // 2.3.2: added table and shifts saved message - // 2.3.2: fix centering by removing span wrapper - // 2.3.2: change from button-primary to button-secondary - // 2.3.3.9: change shift-table-buttons ID to shift-buttons class - // echo '' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; + // --- output show shifts table --- + // 2.3.2: separated table function (for AJAX saving) + $table = radio_station_show_shifts_table( $post->ID ); - // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { - echo '' . PHP_EOL; - } - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + // --- show inactive message --- + // 2.3.0: added show inactive reminder message + if ( !$table['active'] ) { + // 2.3.3.9: change to mismatched div class + echo '
    ' . "\n"; + echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; + echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); + echo '
    ' . "\n"; + } - // --- get shift edit javascript --- - // 2.3.3.9: moved to separate function - $js = radio_station_shift_edit_script(); + // --- shift conflicts message --- + // 2.3.0: added instructions for fixing shift conflicts + // 2.3.3.9: check conflict count instead of boolean test + if ( count( $table['conflicts'] ) > 0 ) { + radio_station_shifts_conflict_message(); + } + + // --- output shift list --- + if ( '' != $table['list'] ) { + // 2.5.0: use wp_kses on table output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $table['list'], $allowed ); + } + + echo '
    ' . "\n"; + + // --- shift save/add buttons --- + // 2.3.0: center add shift button + // 2.3.2: added show shifts AJAX save button (for existing posts only) + // 2.3.2: added show shifts clear button + // 2.3.2: added table and shifts saved message + // 2.3.2: fix centering by removing span wrapper + // 2.3.2: change from button-primary to button-secondary + // 2.3.3.9: change shift-table-buttons ID to shift-buttons class + // echo '' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n";; + echo ''; + echo '' . "\n"; + echo '' . "\n"; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { + echo '' . "\n"; + } + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + + // --- get shift edit javascript --- + // 2.3.3.9: moved to separate function + $js = radio_station_shift_edit_script(); - // --- enqueue inline script --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); - // --- shift display styles --- - // 2.3.2: added dashed border to new shift - // 2.3.3.9: moved styles to separate function - $css = radio_station_shifts_list_styles(); - echo '' . PHP_EOL; + // --- shift display styles --- + // 2.3.2: added dashed border to new shift + // 2.3.3.9: moved styles to separate function + // 2.5.0: use wp_kses_post on styles output + $css = radio_station_shifts_list_styles(); + echo '' . "\n"; // --- close meta inner --- - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; } // ------------------ @@ -928,25 +940,27 @@ function radio_station_shifts_list_styles() { // ----------------- function radio_station_shift_edit_script() { + // --- get meridiem translations --- + // 2.2.7: added meridiem translations + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + // --- set days, hours and minutes arrays --- + // 2.5.0: add number_format_i18n to hours and minutes $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); $hours = $mins = array(); - for ( $i = 1; $i <= 12; $i ++ ) { - $hours[$i] = $i; + for ( $i = 1; $i < 13; $i++ ) { + $hours[$i] = number_format_i18n( $i ); } - for ( $i = 0; $i < 60; $i ++ ) { + for ( $i = 0; $i < 60; $i++ ) { if ( $i < 10 ) { - $min = '0' . $i; + $min = number_format_i18n( 0 ) . number_format_i18n( $i ); } else { - $min = $i; + $min = number_format_i18n( $i ); } $mins[$i] = $min; } - // 2.2.7: added meridiem translations - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); - // --- show shifts scripts --- // 2.3.0: added confirmation to remove shift button // 2.3.2: removed document ready functions wrapper @@ -963,7 +977,7 @@ function radio_station_shift_edit_script() { jQuery('#shifts-list').children().remove(); jQuery('
    ').appendTo('#shifts-list'); } - }" . PHP_EOL; + }" . "\n"; // --- save shifts via AJAX --- // 2.3.2: added form input cloning to saving show shifts @@ -990,7 +1004,7 @@ function radio_station_shift_edit_script() { if (jQuery('#shift_ID').length) {jQuery('#shift_ID').attr('id','').attr('name','shift_id').appendTo('#shift-save-form');} jQuery('#shifts-saving-message').show(); jQuery('#shift-save-form').submit(); - }" . PHP_EOL; + }" . "\n"; // --- check select change --- // 2.3.3: added select change detection @@ -1004,7 +1018,7 @@ function radio_station_shift_edit_script() { else {jQuery('#'+el.id).addClass('changed');} uid = origid.substr(0,8); radio_check_shift(uid); - }" . PHP_EOL; + }" . "\n"; // --- check checkbox change --- // 2.3.3: added checkbox change detection @@ -1016,7 +1030,7 @@ function radio_station_shift_edit_script() { else {jQuery('#'+el.id).addClass('changed');} uid = origid.substr(0,8); radio_check_shift(uid); - }" . PHP_EOL; + }" . "\n"; // --- check shift change --- // 2.3.3: added shift change detection @@ -1030,11 +1044,11 @@ function radio_station_shift_edit_script() { if (shiftchanged) {jQuery('#shift-'+id).addClass('changed');} else {jQuery('#shift-'+id).removeClass('changed');} radio_check_shifts(); - }" . PHP_EOL; + }" . "\n"; // 2.3.3.6: store possible existing onbeforeunload function // (to help prevent conflicts with other plugins using this event) - $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . PHP_EOL; + $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . "\n"; $js .= "function radio_check_shifts() { if (jQuery('.show-shift.changed').length) { if (!onbeforeunloadset) { @@ -1048,7 +1062,7 @@ function radio_station_shift_edit_script() { onbeforeunloadset = false; } } - }" . PHP_EOL; + }" . "\n"; // --- add new shift --- // 2.3.2: separate function for onclick @@ -1064,7 +1078,7 @@ function radio_station_shift_edit_script() { values.encore = ''; values.disabled = ''; radio_shift_add(values); - }" . PHP_EOL; + }" . "\n"; // --- remove shift ---- // 2.3.2: separate function for onclick @@ -1074,7 +1088,7 @@ function radio_station_shift_edit_script() { if (!agree) {return false;} shiftid = el.id.replace('shift-','').replace('-remove',''); jQuery('#'+el.id).closest('.shift-wrapper').remove(); - }" . PHP_EOL; + }" . "\n"; // --- duplicate shift --- // 2.3.2: separate function for onclick @@ -1092,127 +1106,124 @@ function radio_station_shift_edit_script() { if (jQuery('#shift-'+shiftid+'-encore').prop('checked')) {values.encore = 'on';} values.disabled = 'yes'; radio_shift_add(values); - }" . PHP_EOL; + }" . "\n"; // --- add shift function --- // 2.3.2: added missing shift wrapper class // 2.3.2: set new count based on new shift children // 2.3.3: add input IDs so new shifts can be duplicated - $js .= "function radio_shift_add(values) { - var count = jQuery('#new-shifts').children().length + 1; - output = '
    '; - output += '
      '; - output += '
    • '; - output += ': '; - output += '';"; - - // - start hour - - foreach ( $hours as $hour ) { - $js .= "output += '';"; - - // - start minute - - foreach ( $mins as $min ) { - $js .= "output += '
    • ';" . PHP_EOL; - - // - disabled shift - - $js .= "output += '
    • '; - output += ''; - output += '
    • ';" . PHP_EOL; - - // - duplicate shift - - $js .= "output += '
    • '; - output += ''; - output += '
    • ';" . PHP_EOL; - - // - remove shift - - $js .= "output += '
    • '; - output += ''; - output += '
    • ';" . PHP_EOL; - - // --- append new shift list item --- - $js .= "output += '
    '; - jQuery('#new-shifts').append(output); - return false; - }" . PHP_EOL; + $js .= "function radio_shift_add(values) {" . "\n"; + $js .= "var count = jQuery('#new-shifts').children().length + 1;" . "\n"; + $js .= "output = '
    ';" . "\n"; + $js .= "output += '
      ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + $js .= "output += ': ';" . "\n"; + $js .= "output += ''" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- start time --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += ': ';" . "\n"; + // --- start hour --- + $js .= "output += ' ';" . "\n"; + // --- start minutes --- + $js .= "output += '';" . "\n"; + // --- start meridian --- + $js .= "output += ' '" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- end time --- + $js .= "output += '
    • ';" . "\n"; + // --- end hour --- + $js .= "output += ': '" . "\n"; + $js .= "output += ' ';" . "\n"; + // --- end min --- + $js .= "output += ' ';" . "\n"; + // --- end meridian --- + $js .= "output += ' ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- encore --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- disabled shift --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- duplicate shift --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- remove shift --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += ''" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- append new shift list item --- + $js .= "output += '
    ';" . "\n"; + $js .= "jQuery('#new-shifts').append(output);" . "\n"; + $js .= "return false;" . "\n"; + $js .= "}" . "\n"; $js = apply_filters( 'radio_station_shift_edit_script', $js ); return $js; @@ -1224,13 +1235,13 @@ function radio_station_shift_edit_script() { // 2.3.3.9: add hidden argument function radio_station_shifts_conflict_message( $hidden = false ) { - echo '' . "\n"; } } } @@ -2717,7 +2741,7 @@ function radio_station_show_column_data( $column, $post_id ) { if ( is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { foreach ( $hosts as $host ) { $user_info = get_userdata( $host ); - echo esc_html( $user_info->display_name ) . '
    '; + echo esc_html( $user_info->display_name ) . '
    ' . "\n"; } } } @@ -2734,7 +2758,7 @@ function radio_station_show_column_data( $column, $post_id ) { if ( is_array( $producers ) && ( count( $producers ) > 0 ) ) { foreach ( $producers as $producer ) { $user_info = get_userdata( $producer ); - echo esc_html( $user_info->display_name ) . '
    '; + echo esc_html( $user_info->display_name ) . '
    ' . "\n"; } } } @@ -2745,7 +2769,9 @@ function radio_station_show_column_data( $column, $post_id ) { $image_url = radio_station_get_show_avatar_url( $post_id ); if ( $image_url ) { // 2.3.3.9: fix to use esc_attr instead of esc_attr - echo '
    ' . esc_attr( __( 'Show Avatar', 'radio-station' ) ) . '
    '; + echo '
    ' . "\n"; + echo '' . esc_attr( __( 'Show Avatar', 'radio-station' ) ) . '' . "\n"; + echo '
    ' . "\n"; } } @@ -2755,19 +2781,25 @@ function radio_station_show_column_data( $column, $post_id ) { // Show List Column Styles // ----------------------- // 2.2.7: added show column styles -add_action( 'admin_footer', 'radio_station_show_column_styles' ); -function radio_station_show_column_styles() { +// 2.5.0: renamed from radio_station_show_column_styles for consistency +add_action( 'admin_footer', 'radio_station_show_admin_list_styles' ); +function radio_station_show_admin_list_styles() { $current_screen = get_current_screen(); if ( 'edit-' . RADIO_STATION_SHOW_SLUG !== $current_screen->id ) { return; } - echo ""; + .show-shift.disabled.conflict {border: 1px dashed red;}"; + + // 2.5.0: added missing style filter + // TODO: use wp_add_inline_style here ? + $css = apply_filters( 'radio_station_show_list_styles', $css ); + echo '' . "\n"; } // ------------------------- @@ -2782,20 +2814,22 @@ function radio_station_show_day_filter( $post_type, $which ) { } // -- maybe get specified day --- - $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; + // 2.5.0: use sanitize_text_field on get request value + $d = isset( $_GET['weekday'] ) ? sanitize_text_field( $_GET['weekday'] ) : 0; // --- show day selector --- $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); - echo '' . PHP_EOL; - echo '' . PHP_EOL; + echo '' . "\n"; + echo '' . "\n"; } @@ -2831,7 +2865,7 @@ function radio_station_override_show_metabox() { wp_nonce_field( 'radio-station', 'show_data_nonce' ); // --- open meta inner wrap --- - echo '
    '; + echo '
    ' . "\n"; // --- get all shows --- // 2.4.0.4: fix to remove show limit in query @@ -2847,93 +2881,99 @@ function radio_station_override_show_metabox() { // --- link to Show fields --- $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); - echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . esc_html( __( 'If selected, Override data will be used from the Linked Show.', 'radio-station' ) ) . '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + + // --- sync genre taxonomy --- + $sync_genres = get_post_meta( $post_id, 'sync_genres', true ); + echo '
  • ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . esc_html( __( 'If checked, assigned Genre terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . "\n"; + echo '
    ' . "\n"; + echo '
  • ' . "\n"; + + // --- sync language taxonomy --- + // 2.5.0: fix old table cell markup to list item + $sync_languages = get_post_meta( $post_id, 'sync_languages', true ); + echo '
  • ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . esc_html( __( 'If checked, assigned Language terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . "\n"; + echo '
    ' . "\n"; + echo '
  • ' . "\n"; // --- close linked show list --- - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- get show meta --- // $active = get_post_meta( $post->ID, 'show_active', true ); @@ -2945,292 +2985,306 @@ function radio_station_override_show_metabox() { $patreon_id = get_post_meta( $post_id, 'show_patreon', true ); $linked_fields = get_post_meta( $post_id, 'linked_show_fields', true ); - echo '
    ' . PHP_EOL; - - echo '
    ' . PHP_EOL; - echo '' . esc_html( __( 'Usage Note', 'radio-station' ) ) . '' . PHP_EOL; - echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ) . PHP_EOL; - echo '

    ' . PHP_EOL; - - // --- table headings --- - echo '' . PHP_EOL; - - // TODO: show active ? - // echo '

    - // - // ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

    '; - - // --- title --- - echo '' . PHP_EOL; - - // --- content --- - echo '' . PHP_EOL; - - // --- excerpt --- - echo '' . PHP_EOL; - - // --- avatar --- - echo '' . PHP_EOL; - - // --- featured image --- - echo '' . PHP_EOL; - - // --- hosts --- - echo '' . PHP_EOL; - - // --- producers --- - echo '' . PHP_EOL; - - // --- website --- - echo '' . PHP_EOL; - - // --- email --- - echo '' . PHP_EOL; - - // --- phone --- - echo '' . PHP_EOL; - - // --- audio file --- - echo '' . PHP_EOL; - - // --- disable download --- - echo '' . PHP_EOL; - - // --- patreon --- - echo '' . PHP_EOL; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + echo '' . esc_html( __( 'Usage Note', 'radio-station' ) ) . '' . "\n"; + echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ) . "\n"; + echo '

    ' . "\n"; + + // --- table headings --- + echo '
    ' . PHP_EOL; - echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . esc_html( __( 'Override Data', 'radio-station' ) ) . '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' '; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' '; - echo '' . PHP_EOL; - echo ''; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo ' ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . "\n"; + + echo '' . "\n"; + + // TODO: show active ? + // echo '

    + // + // ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

    '; + + // --- title --- + echo '' . "\n"; + + // --- content --- + echo '' . "\n"; + + // --- excerpt --- + echo '' . "\n"; + + // --- avatar --- + echo '' . "\n"; + + // --- featured image --- + echo '' . "\n"; + + // --- hosts --- + echo '' . "\n"; + + // --- producers --- + echo '' . "\n"; + + // --- website --- + echo '' . "\n"; + + // --- email --- + echo '' . "\n"; + + // --- phone --- + echo '' . "\n"; + + // --- audio file --- + echo '' . "\n"; + + // --- disable download --- + echo '' . "\n"; + + // --- patreon --- + echo '' . "\n"; - do_action( 'radio_station_show_fields', $post_id, 'override' ); + do_action( 'radio_station_show_fields', $post_id, 'override' ); - // --- close field table --- - echo '
    ' . "\n"; + echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . '' . "\n"; + echo '' . "\n"; + echo '' . esc_html( __( 'Override Data', 'radio-station' ) ) . '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' '; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo ' ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; + // --- close field table --- + echo '' . "\n"; + echo '
    ' . "\n"; // --- close meta inner --- - echo '
    '; + echo '
    ' . "\n"; // --- inside show metaboxes --- $inside_metaboxes = array( @@ -3267,46 +3321,44 @@ function radio_station_override_show_metabox() { echo ' style="display:none;"'; } } - echo '>' . PHP_EOL; + echo '>' . "\n"; // --- inside metabox contents --- $widget_title = $metabox['title']; - echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . "\n"; + echo '
    ' . "\n"; call_user_func( $metabox['callback'] ); - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- close inside metabox --- - echo '' . PHP_EOL; - - $i ++; + echo '' . "\n"; + $i++; } - echo '' . PHP_EOL; + echo '' . "\n"; // --- input list field styles --- // 2.3.3.9: add styles for table to list conversion - $css = '.input-label, .input-field, .input-helper {display: inline-block;} + $css = ".input-label, .input-field, .input-helper {display: inline-block;} .input-field {max-width: 120px;} - .input-field, .input-helper {margin-left: 20px;} - ' . PHP_EOL; + .input-field, .input-helper {margin-left: 20px;}" . "\n"; // --- output inside metabox styles --- - $css .= '#show-inside-metaboxes .postbox {display: inline-block; min-width: 230px; max-width: 250px; vertical-align: top;} + $css .= "#show-inside-metaboxes .postbox {display: inline-block; min-width: 230px; max-width: 250px; vertical-align: top;} #show-inside-metaboxes .postbox.first {margin-right: 20px;} #show-inside-metaboxes .postbox.last {margin-left: 20px;} - #show-inside-metaboxes .postbox select {max-width: 200px;}'; + #show-inside-metaboxes .postbox select {max-width: 200px;}" . "\n"; // 2.3.3.9: filter + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style ? $css = apply_filters( 'radio_station_override_edit_styles', $css ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo ''; + echo ''; // --- get override show script --- $js = radio_station_override_show_script(); // --- enqueue inline script --- wp_add_inline_script( 'radio-station-admin', $js ); - } // ------------------------- @@ -3332,7 +3384,7 @@ function radio_station_override_show_script() { jQuery('#linked-fields-message').show(); radio_sync_genres(); radio_sync_languages(); } - }" . PHP_EOL; + }" . "\n"; // --- show/hide linked input field --- $js .= "function radio_check_linked(id) { @@ -3346,25 +3398,26 @@ function radio_station_override_show_script() { /* console.log(divid); */ if (display) {jQuery(dataid).hide(); jQuery(divid).show();} else {jQuery(divid).hide(); jQuery(dataid).show();} - }" . PHP_EOL; + }" . "\n"; // --- show/hide genre taxonomy metaboxs --- $js .= "function radio_sync_genres() { checked = jQuery('#override-genres-input').prop('checked'); if (checked) {jQuery('" . esc_js( $genres_div ) . "').hide();} else {jQuery('" . esc_js( $genres_div ) . "').show();} - }" . PHP_EOL; + }" . "\n"; // --- show/hide language taxonomy metabox --- $js .= "function radio_sync_languages() { checked = jQuery('#override-languages-input').prop('checked'); if (checked) {jQuery('" . esc_js( $languages_div ) . "').hide();} else {jQuery('" . esc_js( $languages_div ) . "').show();} - }" . PHP_EOL; + }" . "\n"; // --- check to hide linked metaboxes on pageload --- - $js .= "jQuery(document).ready(function() {radio_link_check();});" .PHP_EOL; + $js .= "jQuery(document).ready(function() {radio_link_check();});" . "\n"; + // --- filter and return --- $js = apply_filters( 'radio_station_override_show_script', $js ); return $js; } @@ -3404,44 +3457,48 @@ function radio_station_schedule_override_metabox() { // 2.2.7: add explicit width to date picker field to ensure date is visible // 2.3.0: convert template style output to straight php output // 2.3.3.9: change meta_inner ID to class - echo '
    '; + echo '
    ' . "\n"; // --- override list table --- - echo '
    '; - - $table = radio_station_overrides_table( $post->ID ); - if ( '' != $table['list'] ) { - echo $table['list']; - } - - echo '
    '; + echo '
    ' . "\n"; + $table = radio_station_overrides_table( $post->ID ); + if ( '' != $table['list'] ) { + // 2.5.0: use wp_kses on table output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $table['list'], $allowed ); + } + echo '
    ' . "\n"; // --- override save/add buttons --- // 2.3.3.9: added for AJAX save and multiple overrides // 2.3.3.9: change override-table-buttons to override-buttons - echo '
    ' . PHP_EOL; - - // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { - echo '' . PHP_EOL; - } - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + echo '' . "\n"; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { + echo '' . "\n"; + } + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; // --- override list styles --- // 2.3.0: added datepicker z-index to fix conflict with editor buttons // 2.3.3.9: apply class styles to override list // 2.3.3.9: moved styles to separate function + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style $css = radio_station_overrides_list_styles(); - echo ''; + echo ''; // --- enqueue datepicker script and styles --- // 2.3.0: enqueue for override post type only @@ -3452,19 +3509,18 @@ function radio_station_schedule_override_metabox() { // --- initialize datepicker fields --- // 2.3.3.9: also initialize end date field - $js .= "jQuery(document).ready(function() { - jQuery('.override-date').each(function() { - jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); - }); - });" . PHP_EOL; + $js .= "jQuery(document).ready(function() {" . "\n"; + $js .= " jQuery('.override-date').each(function() {" . "\n"; + $js .= " jQuery(this).datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= " });" . "\n"; + $js .= "});" . "\n"; // --- enqueue inline script --- // 2.3.0: enqeue instead of echoing wp_add_inline_script( 'radio-station-admin', $js ); // --- close meta inner --- - echo '
    '; - + echo '
    ' . "\n"; } // --------------------- @@ -3500,10 +3556,26 @@ function radio_station_overrides_list_styles() { // 2.3.3.9: separated out for AJAX saving/display function radio_station_overrides_table( $post_id ) { + // --- get meridiem translation --- // 2.2.7: added meridiem translations $am = radio_station_translate_meridiem( 'am' ); $pm = radio_station_translate_meridiem( 'pm' ); + // --- set days, hours and minutes arrays --- + $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $hours = $mins = array(); + for ( $i = 1; $i < 13; $i++ ) { + $hours[$i] = number_format_i18n( $i ); + } + for ( $i = 0; $i < 60; $i++ ) { + if ( $i < 10 ) { + $min = number_format_i18n( 0 ) . number_format_i18n( $i ); + } else { + $min = number_format_i18n( $i ); + } + $mins[$i] = $min; + } + // --- get the saved meta as an array --- $overrides = get_post_meta( $post_id, 'show_override_sched', true ); // 2.3.3.9: maybe convert single override to array @@ -3527,7 +3599,9 @@ function radio_station_overrides_table( $post_id ) { } } - echo 'Current Overrides: ' . print_r( $overrides, true ) . '' . PHP_EOL; + if ( RADIO_STATION_DEBUG ) { + echo 'Current Overrides: ' . esc_html( print_r( $overrides, true ) ) . '' . "\n"; + } if ( !$overrides ) { @@ -3610,166 +3684,168 @@ function radio_station_overrides_table( $post_id ) { // 2.3.3.9: use override shift ID for override wrapper ID --- $id = $override['id']; - $list .= '
    ' . PHP_EOL; + $list .= '
    ' . "\n"; - $list .= '
      ' . PHP_EOL; + $list .= '
        ' . "\n"; // 2.3.3.9: add hidden input for unique override time ID - $list .= '' . PHP_EOL; + $list .= '' . "\n"; // --- Override (Start) Date --- - $list .= '
      • ' . PHP_EOL; + $list .= '
      • ' . "\n"; - $list .= ':' . PHP_EOL; + $list .= ': '; $date = ( !empty( $override['date'] ) ) ? trim( $override['date'] ) : ''; - $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; - $list .= '
      • ' . PHP_EOL; + $list .= '' . "\n"; // --- Override Start Time --- - $list .= '
      • ' . PHP_EOL; + $list .= '
      • ' . "\n"; // --- start time label -- - $list .= ':' . PHP_EOL; + $list .= ':' . "\n"; // --- start hour --- - $list .= '' . "\n"; + $list .= '' . "\n"; + // 2.5.0: use hour array and possibly translated label + foreach ( $hours as $hour => $label ) { + $list .= '' . "\n"; } - $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; // --- start minute --- - $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + // 2.5.0: use minute array and possibly translated label + foreach ( $mins as $min => $label ) { + $list .= '' . "\n"; } - $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; // --- start meridian --- - $list .= ''; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; - $list .= '
      • ' . PHP_EOL; + $list .= '' . "\n"; // --- Override End Time --- // 2.3.4: add common end minutes to top of options - $list .= '
      • ' . PHP_EOL; + $list .= '
      • ' . "\n"; // --- end time label --- - $list .= ':' . PHP_EOL; + $list .= ':' . "\n"; // --- end hour --- - $list .= '' . "\n"; + $list .= '' . "\n"; + // 2.5.0: use hour array and possibly translated label + foreach ( $hours as $hour => $label ) { + $list .= '' . "\n"; } - $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; // --- end minutes --- - $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + // 2.5.0: use hour array and possibly translated label + foreach ( $mins as $min => $label ) { + $list .= '' . "\n"; } - $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; + $list .= '' . "\n"; // --- end meridian --- - $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; $list .= '' . PHP_EOL; - $list .= '' . PHP_EOL; + $list .= '' . "\n"; - $list .= '
      • ' . PHP_EOL; + $list .= '' . "\n"; - // - multiday switch - + // --- multiday switch --- // 2.3.3.9: added multiday checkbox prototype - /* $list .= '' . PHP_EOL; */ + $list .= '>' . "\n"; + $list .= ': ' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; */ // --- disabled --- // 2.3.3.9: added disabled override checkox - $list .= '
      • ' . PHP_EOL; + $list .= '
      • ' . "\n"; $list .= '' . PHP_EOL; + $list .= '> ' . "\n"; $list .= '' . PHP_EOL; - $list .= '
      • ' . PHP_EOL; + $list .= '' . "\n"; // --- remove shift icon --- - $list .= '
      • ' . PHP_EOL; + $list .= '
      • ' . "\n"; $title = __( 'Remove Override', 'radio-station' ); $list .= '' . PHP_EOL; - $list .= '
      • ' . PHP_EOL; + $list .= '' . "\n"; - $list .= '
      ' . PHP_EOL; - $list .= '
    ' . PHP_EOL; + $list .= '' . "\n"; + $list .= '
    ' . "\n"; } // --- new overrides div --- - $list .= '
    ' . PHP_EOL; + $list .= '
    ' . "\n"; // --- set table output to return --- $table = array( @@ -3785,22 +3861,24 @@ function radio_station_overrides_table( $post_id ) { // ------------------------- function radio_station_override_edit_script() { + // --- get meridiem translation --- + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + // --- set days, hours and minutes arrays --- $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); $hours = $mins = array(); - for ( $i = 1; $i <= 12; $i ++ ) { - $hours[$i] = $i; + for ( $i = 1; $i < 13; $i++ ) { + $hours[$i] = number_format_i18n( $i ); } - for ( $i = 0; $i < 60; $i ++ ) { + for ( $i = 0; $i < 60; $i++ ) { if ( $i < 10 ) { - $min = '0' . $i; + $min = number_format_i18n( 0 ) . number_format_i18n( $i ); } else { - $min = $i; + $min = number_format_i18n( $i ); } $mins[$i] = $min; } - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); // --- show shifts scripts --- $c = 0; @@ -3808,14 +3886,15 @@ function radio_station_override_edit_script() { $confirm_clear = __( 'Are you sure you want to clear all overrides?', 'radio-station' ); // --- clear all shifts function --- + // 2.5.0: fix to remove weird symbol from new-overrides ID $js = "function radio_overrides_clear() { if (jQuery('#overrides-list').children().length) { var agree = confirm('" . esc_js( $confirm_clear ) . "'); if (!agree) {return false;} jQuery('#overrides-list').children().remove(); - jQuery('
    ').appendTo('#overrides-list'); + jQuery('
    ').appendTo('#overrides-list'); } - }" . PHP_EOL; + }" . "\n"; // --- save shifts via AJAX --- // 2.3.2: added form input cloning to saving show shifts @@ -3839,7 +3918,7 @@ function radio_station_override_edit_script() { jQuery('#post_ID').clone().attr('id','').attr('name','override_id').appendTo('#override-save-form'); jQuery('#override-saving-message').show(); jQuery('#override-save-form').submit(); - }" . PHP_EOL; + }" . "\n"; // --- check multiday selection --- // 2.3.3.9: added to show hide end date field @@ -3847,15 +3926,14 @@ function radio_station_override_edit_script() { if (jQuery('#override-multiday-'+id).prop('checked')) { jQuery('#override-'+id+'-end-date').show(); } else {jQuery('#override-'+id+'-end-date').hide();} - }" . PHP_EOL; + }" . "\n"; - // TODO: input change highlighting + // TODO: input change highlighting ? + // ##### // --- check disabled selection --- // TODO: ... - $js .= "function radio_check_disabled() { - - }" . PHP_EOL; + $js .= "function radio_check_disabled() {}" . "\n"; // --- check select change --- /* $js .= "function radio_check_select(el) { @@ -3926,7 +4004,7 @@ function radio_station_override_edit_script() { values.multiday = ''; values.end_date = ''; radio_override_add(values); - }" . PHP_EOL; + }" . "\n"; // --- remove override --- $js .= "function radio_override_remove(el) { @@ -3934,7 +4012,7 @@ function radio_station_override_edit_script() { if (!agree) {return false;} /* overrideid = el.id.replace('override-','').replace('-remove',''); */ jQuery('#'+el.id).closest('.override-wrapper').remove(); - }" . PHP_EOL; + }" . "\n"; // --- duplicate shift --- $js .= "function radio_override_duplicate(el) { @@ -3953,124 +4031,129 @@ function radio_station_override_edit_script() { /* values.end_date = jQuery('#override-'+overrideid+'-end-date'); */ values.disabled = 'yes'; radio_override_add(values); - }" . PHP_EOL; + }" . "\n"; // --- add override function --- - $js .= "function radio_override_add(values) { - var count = jQuery('#new-overrides').children().length + 1; - output = '
    '; - output += '
      '; - output += '
    • '; - output += '" . esc_js( __( 'Start Date', 'radio-station' ) ) . ": '; - output += ''; - output += '
    • ';"; - - // --- start hour --- - $js .= "output += '
    • '; - output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": '; - output += '';"; - foreach ( $hours as $hour ) { - $js .= "output += '';"; - foreach ( $mins as $min ) { - $js .= "output += '
    • ';"; - - // - disable override - - $js .= "output += '
    • '; - output += ''; - output += ''; - output += '
    • ';"; - - // - remove override time - - $js .= "output += '
    • '; - output += ''; - output += '
    • ';"; - - // --- append new override list item --- - $js .= "output += '
    '; - jQuery('#new-overrides').append(output); - jQuery('#override-new' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'}); - jQuery('#override-new' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'}); - return false; - }" . PHP_EOL; + $js .= "function radio_override_add(values) {" . "\n"; + $js .= "var count = jQuery('#new-overrides').children().length + 1;" . "\n"; + $js .= "output = '
    ';" . "\n"; + $js .= "output += '
      ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '" . esc_js( __( 'Start Date', 'radio-station' ) ) . ": ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- start hour --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": ';" . "\n"; + $js .= "output += ' ';" . "\n"; + + // --- start minute --- + $js .= "output += '';" . "\n"; + + // --- start meridian --- + $js .= "output += ' ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // - end time - + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '" . esc_js( __( 'End Time', 'radio-station' ) ) . ": ';" . "\n"; + // --- end hour --- + $js .= "output += ' ';" . "\n"; + + // --- end min --- + $js .= "output += ' ';" . "\n"; + + // --- end meridian --- + $js .= "output += ' ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- multiday switch --- + /* $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '" . esc_js( __( 'End Date', 'radio-station' ) ) . ": ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "if (values.end_date != '') {output += ' value=\"' + values.end_date+ '\"';}" . "\n"; + $js .= "output += '>';" . "\n"; + $js .= "output += '
    • ';" . "\n"; */ + + // --- disable override --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + // --- remove override time --- + $js .= "output += '
    • ';" . "\n"; + $js .= "output += '';" . "\n"; + $js .= "output += '
    • ';" . "\n"; + + $js .= "output += '
    ';" . "\n"; + $js .= "output += '
    ';" . "\n"; + + // --- append new override list item --- + $js .= "jQuery('#new-overrides').append(output);" . "\n"; + $js .= "jQuery('#override-new' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= "jQuery('#override-new' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= "return false;" . "\n"; + + $js .= "}" . "\n"; $js = apply_filters( 'radio_station_override_edit_script', $js ); return $js; @@ -4121,13 +4204,12 @@ function radio_station_override_save_data( $post_id ) { // --- send error to parent frame --- if ( $error ) { - echo ""; - + echo ""; exit; } } @@ -4223,7 +4305,8 @@ function radio_station_override_save_data( $post_id ) { $show_field = 'show_' . $field; $linked = false; if ( isset( $_POST[$show_field . '_link'] ) ) { - $linked = $_POST[$show_field . '_link']; + // 2.5.0: use sanitize_text_field on posted value + $linked = sanitize_text_field( $_POST[$show_field . '_link'] ); } $linked_show_fields[$show_field] = ( 'yes' == $linked ) ? true : false; if ( !in_array( $show_field, $non_input_fields ) ) { @@ -4246,7 +4329,8 @@ function radio_station_override_save_data( $post_id ) { // --- get the show override data --- $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); if ( isset( $_POST['new_override'] ) ) { - $new_shift = $_POST['new_override']; + // 2.5.0: use sanitize_text_field on posted value + $new_shift = sanitize_text_field( $_POST['new_override'] ); $new_shift['id'] = radio_station_unique_shift_id(); $show_sched = $current_scheds; $show_sched[] = $new_shift; @@ -4460,20 +4544,20 @@ function radio_station_override_save_data( $post_id ) { // 2.3.3.9: remove duplicate action check // --- (hidden) debug information --- - echo "Previous Overrides: " . print_r( $current_scheds, true ) . PHP_EOL; - echo "New Overrides: " . print_r( $new_scheds, true ) . PHP_EOL; + echo "Previous Overrides: " . esc_html( print_r( $current_scheds, true ) ) . "\n"; + echo "New Overrides: " . esc_html( print_r( $new_scheds, true ) ) . "\n"; // --- display shifts saved message --- // 2.3.3.9: fade out overrides saved message $show_override_nonce = wp_create_nonce( 'radio-station' ); - echo ""; + echo "" . "\n"; // 2.3.3.9: added check if override schedule changed if ( $sched_changed ) { @@ -4485,23 +4569,24 @@ function radio_station_override_save_data( $post_id ) { // radio_station_overrides_conflict_message(); // } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $table['list']; + // 2.5.0: use wp_kses on table output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $table['list'], $allowed ); // echo '
    '; echo ''; // --- refresh show shifts list --- - $js = "if (!parent.document.getElementById('overrides-list')) {console.log('? OH YEAH ?');} - overridelist = parent.document.getElementById('overrides-list'); - overridelist.innerHTML = document.getElementById('overrides-list').innerHTML; - overridelist.style.display = '';" . PHP_EOL; + $js = "if (!parent.document.getElementById('overrides-list')) {console.log('Error. Could not find override list!');}" . "\n"; + $js .= "overridelist = parent.document.getElementById('overrides-list');" . "\n"; + $js .= "overridelist.innerHTML = document.getElementById('overrides-list').innerHTML;" . "\n"; + $js .= "overridelist.style.display = '';" . "\n"; // --- reload the current schedule view --- // 2.4.0.3: added missing check for window parent function - $js .= "if (window.parent && (typeof parent.radio_load_schedule == 'function')) { - parent.radio_load_schedule(false,false,true); - }" . PHP_EOL; + $js .= "if (window.parent && (typeof parent.radio_load_schedule == 'function')) {" . "\n"; + $js .= "parent.radio_load_schedule(false,false,true);" . "\n"; + $js .= "}" . PHP_EOL; // $js .= "if (parent.window.onbeforeunloadset) { // parent.window.onbeforeunload = parent.storedonbeforeunload; @@ -4515,11 +4600,12 @@ function radio_station_override_save_data( $post_id ) { // } // --- re-initialize datepicker fields in parent window --- - $js .= "parent.jQuery('.override-date').each(function() { - parent.jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); - });" . PHP_EOL; + $js .= "parent.jQuery('.override-date').each(function() {" . "\n"; + $js .= "parent.jQuery(this).datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= "});" . "\n"; // --- output the scripts --- + // TODO: use wp_add_inline_script ? echo ''; // 2.3.3.9: trigger action for save or add override time @@ -4532,13 +4618,12 @@ function radio_station_override_save_data( $post_id ) { // --- return early when adding single override --- // 2.3.3.9: added for single override action - if ( 'radio_station_add_override_time' == $_REQUEST['action'] ) { + if ( 'radio_station_add_override_time' == sanitize_text_field( $_REQUEST['action'] ) ) { return; } exit; } - } // ---------------------------------- @@ -4548,12 +4633,15 @@ function radio_station_override_save_data( $post_id ) { add_filter( 'manage_edit-' . RADIO_STATION_OVERRIDE_SLUG . '_columns', 'radio_station_override_columns', 6 ); function radio_station_override_columns( $columns ) { + // --- unset images columns --- if ( isset( $columns['thumbnail'] ) ) { unset( $columns['thumbnail'] ); } if ( isset( $columns['post_thumb'] ) ) { unset( $columns['post_thumb'] ); } + + // --- modify existing columns --- $date = $columns['date']; unset( $columns['date'] ); $genres = $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG]; @@ -4561,6 +4649,7 @@ function radio_station_override_columns( $columns ) { $languages = $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG]; unset( $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] ); + // --- add override columns --- // 2.3.3.9: list date and times in single column $columns['override_times'] = esc_attr( __( 'Override Time(s)', 'radio-station' ) ); $columns['shows_affected'] = esc_attr( __( 'Affected Show(s)', 'radio-station' ) ); @@ -4601,12 +4690,12 @@ function radio_station_override_column_data( $column, $post_id ) { foreach ( $overrides as $i => $override ) { if ( count( $overrides ) > 1 ) { - echo '' . ( $i + 1 ) . ': ' . PHP_EOL; + echo '' . ( $i + 1 ) . ': ' . "\n"; } // 2.3.3.9: maybe display disabled for this override time if ( isset( $override['disabled'] ) && ( 'yes' == $override['disabled'] ) ) { - echo '[Disabled]
    ' . PHP_EOL; + echo '[Disabled]
    ' . "\n"; } // 2.3.2: no need to apply timezone conversions here @@ -4615,22 +4704,22 @@ function radio_station_override_column_data( $column, $post_id ) { $month = radio_station_translate_month( $month, true ); $weekday = date( 'l', $datetime ); $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ) . PHP_EOL; - echo '
    ' . PHP_EOL; + echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ) . "\n"; + echo '
    ' . "\n"; // 2.3.3.9: merge override times into this columns // 2.3.3.9: display according to selected time format $time_format = radio_station_get_setting( 'clock_time_format' ); if ( 12 == $time_format ) { echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . esc_html( $override['start_meridian'] ); - echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ) . PHP_EOL; + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ) . "\n"; } elseif ( 24 == $time_format ) { $start_hour = radio_station_convert_hour( $override['start_hour'] . ' ' . $override['start_meridian'] ); $end_hour = radio_station_convert_hour( $override['end_hour'] . ' ' . $override['end_meridian'] ); echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ); - echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . PHP_EOL; + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . "\n"; } - echo '
    ' . PHP_EOL; + echo '
    '; } } elseif ( 'shows_affected' == $column ) { @@ -4702,19 +4791,19 @@ function radio_station_override_column_data( $column, $post_id ) { } if ( RADIO_STATION_DEBUG ) { - echo $weekday . ': ' . $start . ' to ' . $end . '
    ' . PHP_EOL; - echo $override['date'] . ': ' . $shift_start . ' to ' . $shift_end . '
    ' . PHP_EOL; - echo $override['date'] . ': ' . $override_start . ' to ' . $override_end . '
    ' . PHP_EOL; + echo esc_html( $weekday ) . ': ' . esc_html( $start ) . ' to ' . esc_html( $end ) . '
    ' . "\n"; + echo esc_html( $override['date'] ) . ': ' . esc_html( $shift_start ) . ' to ' . esc_html( $shift_end ) . '
    ' . "\n"; + echo esc_html( $override['date'] ) . ': ' . esc_html( $override_start ) . ' to ' . esc_html( $override_end ) . '
    ' . "\n"; echo '
    ' . PHP_EOL; } // --- compare override time overlaps to get affected shows --- // 2.3.2: fix to override overlap checking logic if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_start ) ) - || ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) - || ( $override_start == $shift_start ) - || ( ( $override_start > $shift_start ) && ( $override_end < $shift_end ) ) - || ( ( $override_start > $shift_start ) && ( $override_start < $shift_end ) ) ) { + || ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) + || ( $override_start == $shift_start ) + || ( ( $override_start > $shift_start ) && ( $override_end < $shift_end ) ) + || ( ( $override_start > $shift_start ) && ( $override_start < $shift_end ) ) ) { // 2.3.0: adjust cell display to two line (to allow for long show titles) // 2.3.2: deduplicate show check (if same show as last show displayed) @@ -4725,7 +4814,7 @@ function radio_station_override_column_data( $column, $post_id ) { if ( 'on' != $active ) { $affected .= '[' . esc_html( __( 'Inactive', 'radio-station' ) ) . '] '; } - $affected .= '' . $show_shift['post_title'] . '
    '; + $affected .= '' . $show_shift['post_title'] . '
    ' . "\n"; } if ( isset( $shift['disabled'] ) && $shift['disabled'] ) { @@ -4759,15 +4848,15 @@ function radio_station_override_column_data( $column, $post_id ) { foreach ( $overrides as $i => $override ) { if ( isset( $affected_shows[$i] ) ) { if ( $i == 0 ) { - echo '
    '; + echo '
    ' . "\n"; } else { - echo '
    '; + echo '
    ' . "\n"; } if ( count( $overrides ) > 1 ) { - echo '' . ( $i + 1 ). ': '; + echo '' . ( $i + 1 ). ': ' . "\n"; } echo $affected_shows[$i]; - echo '
    '; + echo '
    ' . "\n"; } } @@ -4807,7 +4896,9 @@ function radio_station_override_column_data( $column, $post_id ) { $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); $thumbnail_url = apply_filters( 'radio_station_show_avatar', $thumbnail_url, $post_id ); if ( $thumbnail_url ) { - echo '
    ' . esc_attr( __( 'Override Logo', 'radio-station' ) ) . '
    ' . PHP_EOL; + echo '
    '; + echo '' . esc_attr( __( 'Override Logo', 'radio-station' ) ) . '' . "\n"; + echo '
    ' . "\n"; } } } @@ -4825,22 +4916,28 @@ function radio_station_override_sortable_columns( $columns ) { // ------------------------------- // Schedule Override Column Styles // ------------------------------- -add_action( 'admin_footer', 'radio_station_override_column_styles' ); -function radio_station_override_column_styles() { +// 2.5.0: renamed from radio_station_override_column_styles for consistency +add_action( 'admin_footer', 'radio_station_override_admin_list_styles' ); +function radio_station_override_admin_list_styles() { $currentscreen = get_current_screen(); if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { return; } - - // 2.3.2: set override image column width to override image width - // 2.3.3.9: set override times column width - echo ""; + .override_image img {width: 100%; height: auto;}"; + + // 2.3.2: set override image column width to override image width + // 2.3.3.9: set override times column width + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style + $css = apply_filters( 'radio_station_override_list_styles', $css ); + echo ''; } // ---------------------------------- @@ -4881,16 +4978,16 @@ function radio_station_override_date_filter( $post_type, $which ) { $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; // --- month override selector --- - echo '' . PHP_EOL; - echo '' . "\n"; + echo '' . "\n"; + if ( count( $months ) > 0 ) { + foreach ( $months as $key => $data ) { + $label = $wp_locale->get_month( $data['month'] ) . ' ' . $data['year']; + echo '' . "\n"; + } } - } - echo '' . PHP_EOL; + echo '' . "\n"; } @@ -4911,14 +5008,13 @@ function radio_station_override_past_future_filter( $post_type, $which ) { // --- past / future override selector --- // 2.3.3.5: added option for today filtering - echo '' . PHP_EOL; - echo '' . PHP_EOL; - + echo '' . "\n"; + echo '' . "\n"; } @@ -4966,267 +5062,270 @@ function radio_station_playlist_metabox() { $remove_title = __( 'Remove Track', 'radio-station' ); // 2.3.3.9: move meta_inner ID to class - echo '
    ' . PHP_EOL; - - // 2.3.2: separate track list table - echo radio_station_playlist_track_table( $post->ID ); - - // --- track save/add buttons --- - // 2.3.2: change track save from button-primary to button-secondary - // 2.3.2: added playlist AJAX save button (for existing posts only) - // 2.3.2: added playlist tracks clear button - // 2.3.2: added table and track saved message - echo '' . PHP_EOL; - - // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - if ( 'add' != $current_screen->action ) { - echo '' . PHP_EOL; - } - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; - - echo '
    ' . PHP_EOL; - - // --- move new tracks message --- - // 2.3.2: added new track move message - echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    ' . PHP_EOL; - - // --- clear all tracks function --- - $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); - $js = "function radio_tracks_clear() { - if (jQuery('#track-table tr').length) { - var agree = confirm('" . esc_js( $confirm_clear ) . "'); - if (!agree) {return false;} - jQuery('#track-table tr').remove(); - trackcount = 1; - } - }" . PHP_EOL; - - // --- save tracks via AJAX --- - // 2.3.2: added form input cloning to save playlist tracks - $ajaxurl = admin_url( 'admin-ajax.php' ); - $js .= "function radio_tracks_save() { - jQuery('#track-save-form, #track-save-frame').remove(); - form = '
    '; - form += '
    '; - jQuery('#wpbody').append(form); - if (!jQuery('#track-save-frame').length) { - frame = ''; - jQuery('#wpbody').append(frame); - } - /* copy tracklist input fields and nonce */ - jQuery('#track-table input').each(function() {jQuery(this).clone().appendTo('#track-save-form');}); - jQuery('#track-table select').each(function() { - name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); - jQuery('').appendTo('#track-save-form'); - }); - jQuery('#playlist_tracks_nonce').clone().attr('id','').appendTo('#track-save-form'); - jQuery('#post_ID').clone().attr('id','').attr('name','playlist_id').appendTo('#track-save-form'); - jQuery('#tracks-saving-message').show(); - jQuery('#track-save-form').submit(); - }" . PHP_EOL; - - // --- move track up or down --- - // 2.3.2: added move track function - $js .= "function radio_track_move(updown, n) { - /* swap track rows */ - if (updown == 'up') { - m = n - 1; - jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); - jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - } else if (updown == 'down') { - m = n + 1; - jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); - jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - } - /* reset track classes */ - radio_track_classes(); - - /* swap track count */ - jQuery('#track-'+n+'-rowa .track-count').html(m); - jQuery('#track-'+m+'-rowa .track-count').html(n); - - /* swap input name keys */ - jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); - }); - jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); - }); + echo '
    ' . "\n"; + + // 2.3.2: separate track list table + echo radio_station_playlist_track_table( $post->ID ); + + // --- track save/add buttons --- + // 2.3.2: change track save from button-primary to button-secondary + // 2.3.2: added playlist AJAX save button (for existing posts only) + // 2.3.2: added playlist tracks clear button + // 2.3.2: added table and track saved message + echo '' . "\n"; + echo '' . "\n"; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + if ( 'add' != $current_screen->action ) { + echo '' . "\n"; + } + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- move new tracks message --- + // 2.3.2: added new track move message + echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    ' . "\n"; + + // --- clear all tracks function --- + $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); + $js = "function radio_tracks_clear() { + if (jQuery('#track-table tr').length) { + var agree = confirm('" . esc_js( $confirm_clear ) . "'); + if (!agree) {return false;} + jQuery('#track-table tr').remove(); + trackcount = 1; + } + }" . "\n"; + + // --- save tracks via AJAX --- + // 2.3.2: added form input cloning to save playlist tracks + $ajaxurl = admin_url( 'admin-ajax.php' ); + $js .= "function radio_tracks_save() { + jQuery('#track-save-form, #track-save-frame').remove(); + form = '
    '; + form += '
    '; + jQuery('#wpbody').append(form); + if (!jQuery('#track-save-frame').length) { + frame = ''; + jQuery('#wpbody').append(frame); + } + /* copy tracklist input fields and nonce */ + jQuery('#track-table input').each(function() {jQuery(this).clone().appendTo('#track-save-form');}); + jQuery('#track-table select').each(function() { + name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); + jQuery('').appendTo('#track-save-form'); + }); + jQuery('#playlist_tracks_nonce').clone().attr('id','').appendTo('#track-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','playlist_id').appendTo('#track-save-form'); + jQuery('#tracks-saving-message').show(); + jQuery('#track-save-form').submit(); + }" . "\n"; + + // --- move track up or down --- + // 2.3.2: added move track function + $js .= "function radio_track_move(updown, n) { + /* swap track rows */ + if (updown == 'up') { + m = n - 1; + jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + } else if (updown == 'down') { + m = n + 1; + jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + } + /* reset track classes */ + radio_track_classes(); + + /* swap track count */ + jQuery('#track-'+n+'-rowa .track-count').html(m); + jQuery('#track-'+m+'-rowa .track-count').html(n); + + /* swap input name keys */ + jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); + }); + jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); + }); - /* swap button actions */ - jQuery('#track-'+n+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+m+');'); - jQuery('#track-'+n+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+m+');'); - jQuery('#track-'+m+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+n+');'); - jQuery('#track-'+m+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+n+');'); - jQuery('#track-'+n+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+m+');'); - jQuery('#track-'+n+'-rowb .track-remove').attr('onclick','radio_track_remove('+m+');'); - jQuery('#track-'+m+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+n+');'); - jQuery('#track-'+m+'-rowb .track-remove').attr('onclick','radio_track_remove('+n+');'); - - /* swap row IDs */ - jQuery('#track-'+m+'-rowa').attr('id', 'track-0-rowa'); - jQuery('#track-'+m+'-rowb').attr('id', 'track-0-rowb'); - jQuery('#track-'+n+'-rowa').attr('id', 'track-'+m+'-rowa'); - jQuery('#track-'+n+'-rowb').attr('id', 'track-'+m+'-rowb'); - jQuery('#track-0-rowa').attr('id', 'track-'+n+'-rowa'); - jQuery('#track-0-rowb').attr('id', 'track-'+n+'-rowb'); - }" . PHP_EOL; - - // --- reset first and last track classes --- - $js .= "function radio_track_classes() { - jQuery('.track-rowa, .track-rowb').removeClass('first-track').removeClass('last-track'); - jQuery('.track-rowa').first().addClass('first-track'); jQuery('.track-rowa').last().addClass('last-track'); - jQuery('.track-rowb').first().addClass('first-track'); jQuery('.track-rowb').last().addClass('last-track'); - }" . PHP_EOL; - - // --- add track function --- - // 2.3.0: set javascript as string to enqueue - // 2.3.2: added missing track-meta cell class - // 2.3.2: added track move arrows - // 2.3.2: added first and last row classes - // 2.3.2: set to standalone onclick function - // 2.3.3.9: added track length select inputs - $js .= "function radio_track_add() { - if (trackcount == 1) {classes = 'first-track last-track';} else {classes = 'last-track';} - output = ''; - output += ''+trackcount+''; - output += ''; - output += ''; - output += ''; - output += ''; - output += ''; - output += ''; - output += '
    " . esc_js( __( 'Length', 'radio-station' ) ) . ": '; - output += 'm : '; - output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += '
    " . esc_js( __( 'Length', 'radio-station' ) ) . ": '; + output += 'm : '; + output += 's
    '; + output += '
    " . esc_js( __( 'Comments', 'radio-station' ) ) . ":
    '; + output += '
    " . esc_js( __( 'New', 'radio-station' ) ) . ":
    '; + output += '
    '; + output += '
    " . esc_js( __( 'Status', 'radio-station' ) ) . ":
    '; + output += '
    '; + output += ''; + output += '
    " . esc_js( __( 'Move', 'radio-station') ) . "
    : '; + output += '
    '; + output += '
    '; + output += '
    '; + output += '
    '; + output += ''; + output += ''; + + jQuery('#track-table').append(output); + trackcount++; + radio_track_classes(); + return false; + }" . "\n"; + + // --- duplicate track function --- + $js .= "function radio_track_duplicate(id) { + var i; var nextid = id + 1; + /* shift rows down */ + for (i = trackcount; i > id; i--) { + jQuery('#track-'+i+'-rowa, #track-'+i+'-rowb').each(function() { + jQuery(this).attr('id', jQuery(this).attr('id').replace(i, (i+1))); + jQuery(this).find('.track-count').html(i+1); + jQuery(this).find('input, select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+i+']', '['+(i+1)+']')); + }); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); + }); } - output += 's
    '; - output += '
    " . esc_js( __( 'Comments', 'radio-station' ) ) . ":
    '; - output += '
    " . esc_js( __( 'New', 'radio-station' ) ) . ":
    '; - output += '
    '; - output += '
    " . esc_js( __( 'Status', 'radio-station' ) ) . ":
    '; - output += '
    '; - output += ''; - output += '
    " . esc_js( __( 'Move', 'radio-station') ) . "
    : '; - output += '
    '; - output += '
    '; - output += '
    '; - output += '
    '; - output += ''; - output += ''; - - jQuery('#track-table').append(output); - trackcount++; - radio_track_classes(); - return false; - }" . PHP_EOL; - - // --- duplicate track function --- - $js .= "function radio_track_duplicate(id) { - var i; var nextid = id + 1; - /* shift rows down */ - for (i = trackcount; i > id; i--) { - jQuery('#track-'+i+'-rowa, #track-'+i+'-rowb').each(function() { - jQuery(this).attr('id', jQuery(this).attr('id').replace(i, (i+1))); - jQuery(this).find('.track-count').html(i+1); + /* add duplicate row */ + jQuery('#track-'+id+'-rowa').clone().attr('id','track-'+nextid+'-rowa').insertAfter('#track-'+id+'-rowb'); + jQuery('#track-'+id+'-rowb').clone().attr('id','track-'+nextid+'-rowb').insertAfter('#track-'+nextid+'-rowa'); + jQuery('#track-'+nextid+'-rowa .track-count').html(nextid); + jQuery('#track-'+nextid+'-rowa, #track-'+nextid+'-rowb').each(function() { jQuery(this).find('input, select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+i+']', '['+(i+1)+']')); + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+id+']', '['+nextid+']')); }); - jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); - jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); - jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); - jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\", '+nextid+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\", '+nextid+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+nextid+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+nextid+');'); }); - } - /* add duplicate row */ - jQuery('#track-'+id+'-rowa').clone().attr('id','track-'+nextid+'-rowa').insertAfter('#track-'+id+'-rowb'); - jQuery('#track-'+id+'-rowb').clone().attr('id','track-'+nextid+'-rowb').insertAfter('#track-'+nextid+'-rowa'); - jQuery('#track-'+nextid+'-rowa .track-count').html(nextid); - jQuery('#track-'+nextid+'-rowa, #track-'+nextid+'-rowb').each(function() { - jQuery(this).find('input, select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+id+']', '['+nextid+']')); + radio_track_classes(); + trackcount++; + }" . "\n"; + + // --- remove track function --- + // 2.3.2: reset first and last classes on remove + // 2.3.2: set to standalone onclick function + $js .= "function radio_track_remove(id) { + jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb').remove(); + radio_track_classes(); + trackcount--; + /* renumber track count */ + var tcount = 1; + jQuery('.track-rowa').each(function() { + jQuery(this).find('.track-count').html(tcount); tcount++; }); - jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\", '+nextid+');'); - jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\", '+nextid+');'); - jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+nextid+');'); - jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+nextid+');'); - }); - radio_track_classes(); - trackcount++; - }" . PHP_EOL; - - // --- remove track function --- - // 2.3.2: reset first and last classes on remove - // 2.3.2: set to standalone onclick function - $js .= "function radio_track_remove(id) { - jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb').remove(); - radio_track_classes(); - trackcount--; - /* renumber track count */ - var tcount = 1; - jQuery('.track-rowa').each(function() { - jQuery(this).find('.track-count').html(tcount); tcount++; - }); - }" . PHP_EOL; - - // --- set track count --- - // 2.3.2: set count from row count length - // 2.3.2: removed document ready wrapper - $js .= "var trackcount = jQuery('.track-rowa').length + 1;"; + }" . "\n"; - // --- enqueue inline script --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // --- set track count --- + // 2.3.2: set count from row count length + // 2.3.2: removed document ready wrapper + $js .= "var trackcount = jQuery('.track-rowa').length + 1;"; - // --- track list styles --- - // 2.3.0: added track meta style fix - // 2.3.2: added track meta select font size fix - // 2.3.2: added track move arrow styles - // 2.3.2: added table buttons styling - // 2.3.2: added track save message styling - // 2.3.3.9: margin top fixes for move arrows - // 2.3.3.9: added track time styling - $css = '.track-meta div {display: inline-block; margin-right: 3px;} - .track-time, .track-comments {display: inline-block;} - .track-comments {margin-left: 30px;} - .track-minutes, .track-seconds {width: 30px;} - .track-meta select, .track-time select, .track-time input {font-size: 12px;} - .track-move {margin-top: -5px;} - .track-arrow-up, .track-arrow-down {font-size: 36px; line-height: 24px; cursor: pointer; margin-top: -10px;} - tr.first-track .track-arrow-up, tr.last-track .track-arrow-down {display: none;} - tr.first-track .track-arrow-down {margin-left: 20px;} - .track-controls .track-arrow-up, .track-controls .track-arrow-down, - .track-controls .track-move, .track-controls .remove-track, .track-controls .duplicate-track {display: inline-block; vertical-align: middle;} - .track-controls .track-duplicate, .track-controls .track-remove {float: right; margin-right: 15px; cursor: pointer;} - #track-table-buttons {margin-top: 20px;} - #track-table-buttons .clear-tracks, #track-table-buttons .save-tracks, #track-table-buttons .add-track { - cursor: pointer; display: block; width: 120px; padding: 8px; text-align: center; line-height: 1em;} - #tracks-saving-message, #tracks-saved-message { - background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}' . PHP_EOL; + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); - // --- filter and output --- - // 2.3.3.9: added track list style filter - $css = apply_filters( 'radio_station_tracks_list_styles', $css ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo ''; + // --- track list styles --- + // 2.3.0: added track meta style fix + // 2.3.2: added track meta select font size fix + // 2.3.2: added track move arrow styles + // 2.3.2: added table buttons styling + // 2.3.2: added track save message styling + // 2.3.3.9: margin top fixes for move arrows + // 2.3.3.9: added track time styling + $css = '.track-meta div {display: inline-block; margin-right: 3px;} + .track-time, .track-comments {display: inline-block;} + .track-comments {margin-left: 30px;} + .track-minutes, .track-seconds {width: 30px;} + .track-meta select, .track-time select, .track-time input {font-size: 12px;} + .track-move {margin-top: -5px;} + .track-arrow-up, .track-arrow-down {font-size: 36px; line-height: 24px; cursor: pointer; margin-top: -10px;} + tr.first-track .track-arrow-up, tr.last-track .track-arrow-down {display: none;} + tr.first-track .track-arrow-down {margin-left: 20px;} + .track-controls .track-arrow-up, .track-controls .track-arrow-down, + .track-controls .track-move, .track-controls .remove-track, .track-controls .duplicate-track {display: inline-block; vertical-align: middle;} + .track-controls .track-duplicate, .track-controls .track-remove {float: right; margin-right: 15px; cursor: pointer;} + #track-table-buttons {margin-top: 20px;} + #track-table-buttons .clear-tracks, #track-table-buttons .save-tracks, #track-table-buttons .add-track { + cursor: pointer; display: block; width: 120px; padding: 8px; text-align: center; line-height: 1em;} + #tracks-saving-message, #tracks-saved-message { + background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}' . PHP_EOL; + + // --- filter and output --- + // 2.3.3.9: added track list style filter + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style + $css = apply_filters( 'radio_station_tracks_list_styles', $css ); + echo ''; // --- close meta inner --- - echo '
    '; + echo '
    ' . "\n"; // 2.3.2: removed publish button duplication // (replaced with track save AJAX button) @@ -5244,124 +5343,132 @@ function radio_station_playlist_track_table( $playlist_id ) { // --- open track table --- // TODO: convert track table to list ? - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - - // --- set button titles --- - // 2.3.2: added titles for icon buttons - $move_up_title = __( 'Move Track Up', 'radio-station' ); - $move_down_title = __( 'Move Track Down', 'radio-station' ); - $duplicate_title = __( 'Duplicate Track', 'radio-station' ); - $remove_title = __( 'Remove Track', 'radio-station' ); - - // 2.3.2: removed [0] array key - $c = 1; - if ( isset( $entries ) && !empty( $entries ) ) { - - foreach ( $entries as $track ) { - if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) - || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) - || isset( $track['playlist_entry_minutes'] ) || isset( $track['playlist_entry_seconds'] ) - || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) - || isset( $track['playlist_entry_status'] ) ) { - - // 2.3.3.9: set any unset keys to empty - $entry_keys = array( - 'playlist_entry_artist', - 'playlist_entry_song', - 'playlist_entry_album', - 'playlist_entry_label', - 'playlist_entry_minutes', - 'playlist_entry_seconds', - 'playlist_entry_comments', - 'playlist_entry_new', - 'playlist_entry_status', - ); - foreach ( $entry_keys as $entry_key ) { - if ( !isset( $track[$entry_key] ) ) { - $track[$entry_key] = ''; + echo '
    ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + + // --- set button titles --- + // 2.3.2: added titles for icon buttons + $move_up_title = __( 'Move Track Up', 'radio-station' ); + $move_down_title = __( 'Move Track Down', 'radio-station' ); + $duplicate_title = __( 'Duplicate Track', 'radio-station' ); + $remove_title = __( 'Remove Track', 'radio-station' ); + + // 2.3.2: removed [0] array key + $c = 1; + if ( isset( $entries ) && !empty( $entries ) ) { + + foreach ( $entries as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_minutes'] ) || isset( $track['playlist_entry_seconds'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { + + // 2.3.3.9: set any unset keys to empty + $entry_keys = array( + 'playlist_entry_artist', + 'playlist_entry_song', + 'playlist_entry_album', + 'playlist_entry_label', + 'playlist_entry_minutes', + 'playlist_entry_seconds', + 'playlist_entry_comments', + 'playlist_entry_new', + 'playlist_entry_status', + ); + foreach ( $entry_keys as $entry_key ) { + if ( !isset( $track[$entry_key] ) ) { + $track[$entry_key] = ''; + } } - } - // --- track row A --- - $class = ''; - if ( 1 == $c ) { - $class = 'first-track'; - } elseif ( $c == count( $entries ) ) { - $class = 'last-track'; - } - echo '' . PHP_EOL; - - // --- track count --- - echo '' . PHP_EOL; - - // --- track entry inputs --- - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo ''; - - // --- track row B --- - echo '' . PHP_EOL; - - // --- track length --- - // 2.3.3.9: added track length inputs - // 2.3.3.9: add unit translation wrappers with notes - echo '' . "\n"; + + // --- track count --- + echo '' . "\n"; + + // --- track entry inputs --- + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + + echo '' . "\n"; + + // --- track row B --- + echo '' . "\n"; + + // --- track length --- + // 2.3.3.9: added track length inputs + // 2.3.3.9: add unit translation wrappers with notes + echo '' . "\n"; + + // --- track meta --- + echo '' . "\n"; + + // 2.3.2: added move track arrows + echo '' . "\n"; + + // --- increment track count --- + $c++; } - echo '' . _x( 's', 'seconds unit', 'radio-station' ) . '' . PHP_EOL; - - // --- track comments --- - echo '
    ' . esc_html( __( 'Comments', 'radio-station' ) ) . ': ' . PHP_EOL; - echo '
    ' . PHP_EOL; - - // --- track meta --- - echo '' . PHP_EOL; - - // 2.3.2: added move track arrows - echo '' . PHP_EOL; - - $c ++; } } - } - echo '
    ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '
    ' . esc_html( $c ) . '
    ' . esc_html( __( 'Length', 'radio-station' ) ) . ': ' . PHP_EOL; - echo ''; - echo _x( 'm', 'minutes unit', 'radio-station' ) . ' : ' . PHP_EOL; - echo '
    ' . esc_html( $c ) . '
    ' . "\n"; + echo '
    ' . esc_html( __( 'Length', 'radio-station' ) ) . ': ' . "\n"; + echo ''; + echo _x( 'm', 'minutes unit', 'radio-station' ) . ' : ' . "\n"; + echo '' . _x( 's', 'seconds unit', 'radio-station' ) . '
    ' . "\n"; + + // --- track comments --- + echo '
    ' . esc_html( __( 'Comments', 'radio-station' ) ) . ': ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . esc_html( __( 'New', 'radio-station' ) ) . ':
    ' . "\n"; + // 2.3.2: remove new value checking as now used and cleared on save + // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + // ' . checked( $track['playlist_entry_new'] ) . ' + echo '
    ' . "\n"; + echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . esc_html( __( 'Move', 'radio-station') ) . ':
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- remove track button --- + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . PHP_EOL; - echo '
    ' . esc_html( __( 'New', 'radio-station' ) ) . ':
    ' . PHP_EOL; - // 2.3.2: remove new value checking as now used and cleared on save - // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; - // ' . checked( $track['playlist_entry_new'] ) . ' - echo '
    ' . PHP_EOL; - echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '
    ' . esc_html( __( 'Move', 'radio-station') ) . ':
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; - - // --- remove track button --- - echo '
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '
    '; + + echo '' . "\n"; } // ----------------------------------- @@ -5394,7 +5501,7 @@ function radio_station_playlist_show_metabox() { // 2.3.0: moved up to check for any shows // 2.3.3.9: allow assignment to draft ahows $args = array( - 'numberposts' => - 1, + 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', @@ -5413,8 +5520,8 @@ function radio_station_playlist_show_metabox() { // 2.3.0: added check for new Show Editor role // 2.3.0: added check for edit_others_shows capability if ( !in_array( 'administrator', $user->roles ) - && !in_array( 'show-editor', $user->roles ) - && !current_user_can( 'edit_others_shows' ) ) { + && !in_array( 'show-editor', $user->roles ) + && !current_user_can( 'edit_others_shows' ) ) { // --- get the user lists for all shows --- $allowed_shows = array(); @@ -5443,7 +5550,7 @@ function radio_station_playlist_show_metabox() { // 2.3.3.9: allow assignment to draft shows $args = array( - 'numberposts' => - 1, + 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'aSC', @@ -5456,27 +5563,27 @@ function radio_station_playlist_show_metabox() { } // 2.3.3.9: move meta_inner ID to class - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; if ( !$have_shows ) { - echo esc_html( __( 'No Shows were found.', 'radio-station' ) ) . PHP_EOL; + echo esc_html( __( 'No Shows were found.', 'radio-station' ) ) . "\n"; } else { if ( count( $shows ) < 1 ) { - echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ) . PHP_EOL; + echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ) . "\n"; } else { // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); // --- select show to assign playlist to --- $current = get_post_meta( $post->ID, 'playlist_show_id', true ); - echo '' . PHP_EOL; + echo '' . "\n"; } } - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; } // -------------------- @@ -5518,12 +5625,12 @@ function radio_station_playlist_save_data( $post_id ) { // --- send error message to parent window --- if ( $error ) { - echo ""; + echo "" . "\n"; exit; } @@ -5538,7 +5645,8 @@ function radio_station_playlist_save_data( $post_id ) { if ( isset( $_POST['playlist_tracks_nonce'] ) && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $prev_playlist = get_post_meta( $post_id, 'playlist', true ); - $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); + // 2.5.0: use sanitize_text_field on posted value + $playlist = isset( $_POST['playlist'] ) ? sanitize_text_field( $_POST['playlist'] ) : array(); if ( count( $playlist ) > 0 ) { // move songs that are still queued to the end of the list so that order is maintained @@ -5609,21 +5717,21 @@ function radio_station_playlist_save_data( $post_id ) { // --- display tracks saved message --- // 2.3.3.9: fadeout tracks saved message $playlist_tracks_nonce = wp_create_nonce( 'radio-station' ); - echo ""; + echo "" . "\n"; // --- refresh track list table --- // 2.3.3.9: added check if playlist changed if ( $playlist_changed ) { echo radio_station_playlist_track_table( $post_id ); - echo ""; + echo ""; } exit; @@ -5637,16 +5745,22 @@ function radio_station_playlist_save_data( $post_id ) { // 2.2.7: added data columns to playlist list display add_filter( 'manage_edit-' . RADIO_STATION_PLAYLIST_SLUG . '_columns', 'radio_station_playlist_columns', 6 ); function radio_station_playlist_columns( $columns ) { + + // --- remove image columns --- if ( isset( $columns['thumbnail'] ) ) { unset( $columns['thumbnail'] ); } if ( isset( $columns['post_thumb'] ) ) { unset( $columns['post_thumb'] ); } + + // --- modify existing columns --- $date = $columns['date']; unset( $columns['date'] ); $comments = $columns['comments']; unset( $columns['comments'] ); + + // --- add playlist columns --- $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); @@ -5666,36 +5780,38 @@ function radio_station_playlist_column_data( $column, $post_id ) { if ( 'show' == $column ) { $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); $post = get_post( $show_id ); - echo '' . esc_html( $post->post_title ) . '' . PHP_EOL; + echo '' . esc_html( $post->post_title ) . '' . "\n"; } elseif ( 'trackcount' == $column ) { - echo count( $tracks ) . PHP_EOL; + echo count( $tracks ) . "\n"; } elseif ( 'tracklist' == $column ) { - echo '' . PHP_EOL; - echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
    ' . PHP_EOL; - echo '' . PHP_EOL; + echo ''; + echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
    ' . "\n"; + echo '' . "\n"; } } // --------------------------- // Playlist List Column Styles // --------------------------- -add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); -function radio_station_playlist_column_styles() { +// 2.5.0: renamed function from radio_station_playlist_column_styles +add_action( 'admin_footer', 'radio_station_playlist_admin_list_styles' ); +function radio_station_playlist_admin_list_styles() { $currentscreen = get_current_screen(); if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { return; @@ -5709,15 +5825,17 @@ function radio_station_playlist_column_styles() { .tracklist-table td {padding: 0px 10px;}"; // 2.3.3.9: add playlist list styles filter + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style ? $css = apply_filters( 'radio_station_playlist_list_styles', $css ); - echo ''; + echo ''; // --- expand/collapse tracklist data --- - $js = "function showhidetracklist(postid) { - if (document.getElementById('tracklist-'+postid).style.display == 'none') { - document.getElementById('tracklist-'+postid).style.display = ''; - } else {document.getElementById('tracklist-'+postid).style.display = 'none';} - }"; + $js = "function showhidetracklist(postid) {" . "\n"; + $js .= " if (document.getElementById('tracklist-'+postid).style.display == 'none') {" . "\n"; + $js .= " document.getElementById('tracklist-'+postid).style.display = '';" . "\n"; + $js .= " } else {document.getElementById('tracklist-'+postid).style.display = 'none';}" . "\n"; + $js .= "}" . "\n"; // --- enqueue script inline --- // 2.3.0: enqueue instead of echo @@ -5775,7 +5893,7 @@ function radio_station_post_show_metabox() { // 2.3.3.9: allow for post assignment to draft Shows $args = array( - 'numberposts' => - 1, + 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', @@ -5804,51 +5922,57 @@ function radio_station_post_show_metabox() { } // 2.3.3.9: move meta_inner ID to class - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; - if ( count( $shows ) > 0 ) { + if ( count( $shows ) > 0 ) { - // --- select related show input --- - // 2.2.3.4: allow for multiple selections - // 2.3.3.9: use metakey for post type - echo '' . "\n"; + echo '' . "\n"; + + // --- loop shows for selection options --- + // 2.3.3.4: check for multiple selections + foreach ( $shows as $show ) { + + // 2.3.3.6: check capability of user to edit each Show + // (override global post object temporarily to do this) + $post = $show; + echo '' . "\n"; } - } - echo '>' . esc_html( $show->post_title ); - if ( 'draft' == $show->post_status ) { - echo ' (' . esc_html( __( 'Draft', 'radio-station' ) ) . ')'; - } - echo '' . PHP_EOL; + echo '' . "\n"; + } else { + // --- no shows message --- + echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ) . "\n"; } - echo '' . PHP_EOL; - } else { - // --- no shows message --- - echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ) . PHP_EOL; - } - echo '
    ' . PHP_EOL; + + echo '
    ' . "\n"; // --- related shows post box styles --- // 2.3.3.6: add style for pre-selected option - echo ""; + // 2.5.0: added missing style filter + // 2.5.0: use wp_kses_post on style output + // TODO: use wp_add_inline_style ? + $css = ".pre-selected {background-color:#BBB;}"; + $css = apply_filters( 'radio_station_post_show_box_styles', $css ); + echo '' . "\n"; // 2.3.3.6: revert current post global $post = $stored_post; @@ -5966,7 +6090,7 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { // --- get all shows --- // 2.3.3.9: allow selection shows with draft status $args = array( - 'numberposts' => - 1, + 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', @@ -5975,13 +6099,13 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { ); $shows = get_posts( $args ); - echo '' . "\n"; // --- related shows post box styles --- // 2.3.3.6: add style for pre-selected option - echo "" . PHP_EOL; + $css = '.pre-selected {background-color:#BBB;}'; + $css = apply_filters( 'radio_station_quick_edit_post_styles', $css ); + echo '' . "\n"; // 2.3.3.6: restore stored post object $post = $stored_post; @@ -6069,13 +6195,17 @@ function radio_station_post_column_data( $column, $post_id ) { } echo esc_html( $show->post_title ) . '
    '; if ( current_user_can( 'edit_shows' ) ) { - echo '' . PHP_EOL; + echo '' . "\n"; } } } } - echo '' . PHP_EOL; - echo '' . PHP_EOL; + + // 2.5.0: added missing debug check wrapper + if ( RADIO_STATION_DEBUG ) { + echo '' . "\n"; + echo '' . "\n"; + } // --- restore global post object --- $post = $stored_post; @@ -6096,7 +6226,8 @@ function radio_station_posts_quick_edit_script( $hook ) { } // 2.3.3.7: use jQuery instead of \$ for better compatibility - if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { + // 2.5.0: added sanitize_text_field to posted value + if ( !isset( $_GET['post_type'] ) || ( 'post' == sanitize_text_field( $_GET['post_type'] ) ) ) { $js = "(function($) { var \$wp_inline_edit = inlineEditPost.edit; inlineEditPost.edit = function( id ) { @@ -6162,7 +6293,8 @@ function radio_station_show_posts_bulk_edit_script( $hook ) { // 2.3.3.7: use jQuery instead of \$ for better compatibility // 2.3.3.7: do not reclone the show field if it already exists - if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { + // 2.5.0: added sanitize_text_field to posted value + if ( !isset( $_GET['post_type'] ) || ( 'post' == sanitize_text_field( $_GET['post_type'] ) ) ) { $js = "jQuery(document).ready(function() { jQuery('#bulk-action-selector-top, #bulk-action-selector-bottom').on('change', function(e) { if (jQuery(this).val() == 'related_show') { @@ -6196,7 +6328,8 @@ function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids return $redirect_to; } - $show_ids = $_REQUEST['post_showblog_id']; + // 2.5.0: added sanitize_text_field to request value + $show_ids = sanitize_text_field( $_REQUEST['post_showblog_id'] ); // 2.3.3.6: check that user can edit specified Shows $posted_show_ids = array(); @@ -6271,23 +6404,24 @@ function radio_station_posts_bulk_edit_notice() { } if ( $updated || $failed ) { - $class = ( $updated_products_count > 0 ) ? 'updated' : 'error'; - echo '
    ' . PHP_EOL; + // 2.5.0: fix to mismatched variable updated_products_count + $class = ( $updated > 0 ) ? 'updated' : 'error'; + echo '
    ' . "\n"; if ( $updated > 0 ) { // --- number of posts updated message --- $message = __( 'Updated Related Shows for %d Posts.', 'radio_station' ); $message = sprintf( $message, $updated ); - echo '

    ' . esc_html( $message ) . '

    ' . PHP_EOL; + echo '

    ' . esc_html( $message ) . '

    ' . "\n"; } if ( $failed > 0 ) { // --- number of posts failed messsage --- $message = __( 'Failed to Update Related Shows for %d Posts.', 'radio-station' ); $message = sprintf( $message, $failed ); - echo '

    ' . esc_html( $message ) . '

    ' . PHP_EOL; + echo '

    ' . esc_html( $message ) . '

    ' . "\n"; } - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; } } @@ -6309,8 +6443,10 @@ function radio_station_posts_list_styles() { .bulkactions .column-show .title {display: none;}"; // 2.3.3.9: added posts list styles filter + // 2.5.0: added wp_kses_post to style output + // TODO: use wp_add_inline_style ? $css = apply_filters( 'radio_station_posts_list_styles', $css ); - echo ''; + echo ''; } @@ -6507,9 +6643,9 @@ function radio_station_relogin_message() { // --- interim login thickbox --- // 2.3.3.9: maybe close existing thickbox - $js = "if (parent && parent.tb_remove) {try {parent.tb_remove();} catch(e) {} }" . PHP_EOL; + $js = "if (parent && parent.tb_remove) {try {parent.tb_remove();} catch(e) {} }" . "\n"; // 2.3.3.9: trigger WP interim login screen thickbox - $js .= "if (parent) {parent.jQuery(document).trigger('heartbeat-tick.wp-auth-check', [{'wp-auth-check': false}]);}" . PHP_EOL; + $js .= "if (parent) {parent.jQuery(document).trigger('heartbeat-tick.wp-auth-check', [{'wp-auth-check': false}]);}" . "\n"; // 2.3.3.9: fix to playlist action name prefix // 2.3.3.9: added support for override times @@ -6529,17 +6665,18 @@ function radio_station_relogin_message() { // --- send relogin message --- if ( isset( $type ) ) { $error = __( 'Failed. Please relogin and try again.', 'radio-station' ); - $js .- "parent.document.getElementById('" . $type . "s-saving-message').style.display = 'none'; - parent.document.getElementById('" . $type . "s-error-message').style.display = ''; - parent.document.getElementById('" . $type . "s-error-message').innerHTML = '" . esc_js( $error ) . "'; - form = parent.document.getElementById('" . $type . "-save-form'); - if (form) {form.parentNode.removeChild(form);}" . PHP_EOL; + // 2.5.0: fix to concatenation typo (.-) + // 2.5.0: added missing esc_js on type variable + $js .= "parent.document.getElementById('" . esc_js( $type ) . "s-saving-message').style.display = 'none';" . "\n"; + $js .= "parent.document.getElementById('" . esc_js( $type ) . "s-error-message').style.display = '';" . "\n"; + $js .= "parent.document.getElementById('" . esc_js( $type ) . "s-error-message').innerHTML = '" . esc_js( $error ) . "';" . "\n"; + $js .= "form = parent.document.getElementById('" . esc_js( $type ) . "-save-form');" . "\n"; + $js .= "if (form) {form.parentNode.removeChild(form);}" . "\n"; } // --- filter and output --- $js = apply_filters( 'radio_station_relogin_script', $js ); echo ''; exit; - } diff --git a/includes/post-types.php b/includes/post-types.php index 45aba1b..ffcf26d 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -20,8 +20,6 @@ // - Register Show Taxonomies // -- Genre Taxonomy // -- Language Taxonomy -// === Schedule Override Filters === -// - Add Override Template Filters // ------------------ @@ -301,6 +299,7 @@ function radio_station_create_post_types() { add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); function radio_station_post_type_editor( $can_edit, $post_type ) { + // 2.3.2: added host and producer slugs $post_types = array( RADIO_STATION_SHOW_SLUG, @@ -328,6 +327,7 @@ function radio_station_add_featured_image_support() { // 2.3.0: add override thumbnail to theme support declaration $supported_types = get_theme_support( 'post-thumbnails' ); if ( false === $supported_types ) { + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, @@ -335,12 +335,15 @@ function radio_station_add_featured_image_support() { RADIO_STATION_PRODUCER_SLUG, ); add_theme_support( 'post-thumbnails', $post_types ); + } elseif ( is_array( $supported_types ) ) { + $supported_types[0][] = RADIO_STATION_SHOW_SLUG; $supported_types[0][] = RADIO_STATION_OVERRIDE_SLUG; $supported_types[0][] = RADIO_STATION_HOST_SLUG; $supported_types[0][] = RADIO_STATION_PRODUCER_SLUG; add_theme_support( 'post-thumbnails', $supported_types[0] ); + } } @@ -543,181 +546,3 @@ function radio_station_register_show_taxonomies() { } - -// --------------------------------- -// === Schedule Override Filters === -// --------------------------------- -// (to apply linked show overrides in template single-show-content.php) -// note: post object overrides (content, excerpt) are in radio_station_override_linked_show_data - -// -------------------------------------- -// Add Schedule Override Template Filters -// -------------------------------------- -add_action( 'wp', 'radio_station_override_filters' ); -function radio_station_override_filters() { - if ( is_admin() || !is_singular() ) { - return; - } - global $post; - if ( is_object( $post ) && ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) ) { - add_filter( 'the_title', 'radio_station_override_show_title', 10, 2 ); - add_filter( 'radio_station_show_title', 'radio_station_override_show_title', 10, 2 ); - add_filter( 'radio_station_show_avatar', 'radio_station_override_show_avatar', 10, 2 ); - add_filter( 'radio_station_show_avatar_id', 'radio_station_override_show_avatar_id', 10, 2 ); - add_filter( 'radio_station_show_thumbnail', 'radio_station_override_show_thumbnail', 10, 2 ); - add_filter( 'get_post_metadata', 'radio_station_override_thumbnail_id', 11, 4 ); - // add_filter( 'radio_station_show_header', $header_id, $post_id ); - add_filter( 'radio_station_show_hosts', 'radio_station_override_show_hosts', 10, 2 ); - add_filter( 'radio_station_show_producers', 'radio_station_override_show_producers', 10, 2 ); - add_filter( 'radio_station_show_link', 'radio_station_override_show_link', 10, 2 ); - add_filter( 'radio_station_show_email', 'radio_station_override_show_email', 10, 2 ); - add_filter( 'radio_station_show_phone', 'radio_station_override_show_phone', 10, 2 ); - add_filter( 'radio_station_show_download', 'radio_station_override_show_download', 10, 2 ); - add_filter( 'radio_station_show_file', 'radio_station_override_show_file', 10, 2 ); - add_filter( 'radio_station_show_patreon', 'radio_station_override_show_patreon', 10, 2 ); - add_filter( 'radio_station_show_shifts', 'radio_station_override_show_shifts', 10, 2 ); - // add_filter( 'radio_station_show_rss', 'radio_station_override_show_rss', 10, 2 ); - // add_filter( 'radio_station_show_social_icons', 'radio_station_override_show_social_icons', 10, 2 ); - } -} - -// --- Show Title --- -function radio_station_override_show_title( $show_title, $post_id ) { - global $post; - if ( !is_object( $post ) || ( $post->ID != $post_id ) ) { - return $show_title; - } - $override = radio_station_get_show_override( $post_id, 'show_title' ); - if ( false !== $override ) { - return $override; - } - return $show_title; -} - -// --- Show Avatar --- -function radio_station_override_show_avatar( $show_avatar, $post_id ) { - if ( radio_station_doing_template() ) { - $override = radio_station_get_show_override( $post_id, 'show_avatar' ); - if ( false !== $override ) { - return $override; - } - } - return $show_avatar; -} - -// --- Show Avatar ID --- -function radio_station_override_show_avatar_id( $avatar_id, $post_id ) { - if ( radio_station_doing_template() ) { - $override = radio_station_get_show_override( $post_id, 'show_avatar' ); - if ( false !== $override ) { - return $override; - } - } - return $avatar_id; -} - -// --- Show Thumbnail (Featured Image) --- -function radio_station_override_show_thumbnail( $show_thumbnail, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_image' ); - if ( false !== $override ) { - return $override; - } - return $show_thumbnail; -} - -// --- Show Thumbnail ID --- -function radio_station_override_thumbnail_id( $id, $object_id, $meta_key, $single ) { - global $post; - if ( ( '_thumbnail_id' != $meta_key ) || !is_object( $post ) || ( $post->ID != $object_id ) ) { - return $id; - } - if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - $override = radio_station_get_show_override( $object_id, 'show_image' ); - if ( false !== $override ) { - return $override; - } - } - return $id; -} - -// --- Show Hosts --- -function radio_station_override_show_hosts( $hosts, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_user_list' ); - if ( false !== $override ) { - return $override; - } - return $hosts; -} - -// --- Show Producers --- -function radio_station_override_show_producers( $producers, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_producer_list' ); - if ( false !== $override ) { - return $override; - } - return $producers; -} - -// --- Show Website Link --- -function radio_station_override_show_link( $show_link, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_link' ); - if ( false !== $override ) { - return $override; - } - return $show_link; -} - -// --- Show Email --- -function radio_station_override_show_email( $show_email, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_email' ); - if ( false !== $override ) { - return $override; - } - return $show_email; -} - -// --- Show Phone --- -function radio_station_override_show_phone( $show_phone, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_phone' ); - if ( false !== $override ) { - return $override; - } - return $show_phone; -} - -// --- Show File --- -function radio_station_override_show_file( $show_file, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_file' ); - if ( false !== $override ) { - return $override; - } - return $show_file; -} - -// --- Disable Download --- -function radio_station_override_show_download( $show_download, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_download' ); - if ( false !== $override ) { - return $override; - } - return $show_download; -} - -// --- Show Patreon --- -function radio_station_override_show_patreon( $show_patreon, $post_id ) { - $override = radio_station_get_show_override( $post_id, 'show_patreon' ); - if ( false !== $override ) { - return $override; - } - return $show_patreon; -} - -// --- Show Shifts --- -function radio_station_override_show_shifts( $show_shifts, $post_id ) { - $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); - if ( $linked_id ) { - $show_shifts = radio_station_get_show_schedule( $linked_id ); - } - return $show_shifts; -} - diff --git a/includes/schedules.php b/includes/schedules.php new file mode 100644 index 0000000..8ad8ab5 --- /dev/null +++ b/includes/schedules.php @@ -0,0 +1,889 @@ + 'radio_station' ); +if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { + $args['debug'] = RADIO_STATION_DEBUG; +} +$rs_se = new radio_station_schedule_engine( $args ); + + +// ---------------- +// === Schedule === +// ---------------- + +// ----------------- +// Get Show Schedule +// ----------------- +function radio_station_get_show_schedule( $show_id ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->get_record_shifts( $show_id, 'show_sched' ); +} + +// --------------- +// Get Show Shifts +// --------------- +// 2.3.0: added get show shifts data grabber +// 2.3.2: added second argument to get non-split shifts +// 2.3.3: added time as third argument for ondemand shifts +function radio_station_get_show_shifts( $check_conflicts = true, $split = true, $time = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- get all show data --- + $shows = radio_station_get_shows(); + + // 2.5.0: map show data before combining to all shifts + $records = array(); + if ( count( $shows ) > 0 ) { + foreach ( $shows as $show ) { + + // 2.5.0: get metadata early (maybe via cached) + if ( isset( $radio_station_data['show-' . $show->ID] ) ) { + $metadata = $radio_station_data['show-' . $show->ID]; + } else { + $metadata = radio_station_get_show_data_meta( $show ); + $radio_station_data['show-' . $show->ID] = $metadata; + } + + $shifts = $metadata['schedule']; + unset( $metadata['schedule'] ); + $records[] = array( + 'ID' => $show->ID, + 'updated' => $show->post_modified_gmt, + 'shifts' => $shifts, + 'show' => $metadata, + ); + } + } + + // --- debug point for show data --- + if ( RADIO_STATION_DEBUG ) { + $data = 'Show Data: ' . print_r( $records, true ) . PHP_EOL; + echo '' . $data . ''; + radio_station_debug( $data ); + } + + // --- set times for schedule --- + $now = $time ? $time : radio_station_get_now(); + $timezone = radio_station_get_timezone(); + $today = radio_station_get_time( 'l', $now, $timezone ); + $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'now' => $now, + 'today' => $today, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + 'timezone' => $timezone, + ); + + // --- get shifts from records --- + $all_shifts = $rs_se->get_all_shifts( $records, $check_conflicts, $split, $time, $timezone ); + return $all_shifts; +} + +// -------------------------- +// Get All Schedule Overrides +// -------------------------- +// 2.3.0: added get schedule overrides data grabber +function radio_station_get_all_overrides( $start_date = false, $end_date = false, $timezone = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- get overrides --- + $overrides = radio_station_get_overrides(); + // echo 'OVERRIDES! ' . print_r( $overrides, true ) . ''; + + $timezone = $timezone ? $timezone : radio_station_get_timezone(); + $override_data = array(); + if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { + foreach ( $overrides as $i => $override ) { + + // --- set override ID --- + $data = array(); + $data['id'] = $override_id = $override->ID; + + // --- linked show and show title --- + // 2.3.3.9: allow for usage of linked show title + $data['slug'] = $override->post_name; + $data['title'] = $override->post_title; + $linked_id = get_post_meta( $override_id, 'linked_show_id', true ); + if ( $linked_id ) { + $override_data[$i]['linked_show'] = get_post( $linked_id ); + $linked_fields = get_post_meta( $override['ID'], 'linked_show_fields', true ); + if ( !isset( $linked_fields['show_title'] ) || !$linked_fields['show_title'] ) { + $data['title'] = $linked_show->post_title; + } + } + + // --- get override shifts --- + // 2.3.3.9: get possible array of override shifts + $override_shifts = get_post_meta( $override_id, 'show_override_sched', true ); + // 2.3.3.9: convert possible single override to array + if ( $override_shifts && is_array( $override_shifts ) && array_key_exists( 'date', $override_shifts ) ) { + $override_shifts = array( $override_shifts ); + update_post_meta( $override_id, 'show_override_sched', $override_shifts ); + } + + // --- check/update old shift format --- + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { + // 2.2.3.9: loop to add unique shift IDs and maybe resave + $update_override_shifts = false; + foreach ( $override_shifts as $j => $shift_data ) { + if ( !isset( $shift_data['id'] ) ) { + $shift_data['id'] = radio_station_unique_shift_id(); + $override_shifts[$j] = $shift_data; + $update_override_shifts = true; + } + } + if ( $update_override_shifts ) { + update_post_meta( $override_id, 'show_override_sched', $override_shifts ); + } + } + + // --- set override shifts --- + $data['shifts'] = $override_shifts; + + // --- get override metadata --- + $metadata = radio_station_get_override_data_meta( $override ); + $data['show'] = $metadata; + + // --- set override data --- + $override_data[$i] = $data; + } + } + $overrides = $override_data; + + // --- get all overrides list --- + $overrides = $rs_se->process_overrides( $overrides, $start_date, $end_date, $timezone ); + // 2.5.0: keep filter for backwards compatibility + $overrides = apply_filters( 'radio_station_get_overrides', $overrides, $start_date, $end_date, $channel ); + return $overrides; +} + +// -------------------- +// Get Current Schedule +// -------------------- +// 2.3.2: added optional time argument +// 2.3.3.5: added optional weekstart argument +function radio_station_get_current_schedule( $time = false, $weekstart = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + $show_shifts = false; + + // --- maybe get cached schedule --- + // 2.3.3: check data global first + if ( !$time ) { + if ( '' != $channel ) { + if ( isset( $radio_station_data['current_schedule_' . $channel] ) ) { + return $radio_station_data['current_schedule_' . $channel]; + } else { + $show_shifts = get_transient( 'radio_station_current_schedule_' . $channel ); + } + } elseif ( isset( $radio_station_data['current_schedule'] ) ) { + return $radio_station_data['current_schedule']; + } else { + $show_shifts = get_transient( 'radio_station_current_schedule' ); + } + } else { + // --- get schedule for time --- + // 2.3.2: added transient for time schedule + if ( '' != $channel ) { + if ( isset( $radio_station_data['current_schedule_' . $channel . '_' . $time] ) ) { + return $radio_station_data['current_schedule_'. $channel . '_' . $time]; + } else { + $show_shifts = get_transient( 'radio_station_current_schedule_' . $channel . '_' . $time ); + } + } elseif ( isset( $radio_station_data['current_schedule_' . $time] ) ) { + return $radio_station_data['current_schedule_' . $time]; + } else { + $show_shifts = get_transient( 'radio_station_current_schedule_' . $time ); + if ( RADIO_STATION_DEBUG && $show_shifts ) { + // 2.3.3.9: fix to clear transient object cache (OMFG.) + if ( isset( $_REQUEST['clear'] ) && ( '1' == sanitize_title( $_REQUEST['clear'] ) ) ) { + echo "Clearing object cache for requested schedule time." . PHP_EOL; + wp_cache_delete( 'radio_station_current_schedule_' . $time, 'transients' ); + $show_shifts = false; + } + } + } + } + + if ( !$show_shifts ) { + + // --- get weekdates --- + $now = $time ? $time : radio_station_get_now(); + $timezone = radio_station_get_timezone(); + // 2.3.3.5: add passthrough of optional week start argument + $weekdays = radio_station_get_schedule_weekdays( $weekstart, $now, $timezone ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'time' => $now, + 'timezone' => $timezone, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + ); + + // --- get all show shifts --- + // 2.3.3: also pass time to get show_shifts function + $show_shifts = radio_station_get_show_shifts( true, true, $time ); + + // 2.3.1: add any empty keys to ensure overrides are checked + foreach ( $weekdays as $weekday ) { + if ( !isset( $show_shifts[$weekday] ) ) { + $show_shifts[$weekday] = array(); + } + } + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Show Shifts: " . print_r( $show_shifts, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- get show overrides --- + // (from 12am this morning, for one week ahead and back) + // 2.3.1: get start and end dates from weekdays + // 2.3.2: use get time function with timezone + // 2.3.3.9: pass second argument as time may not be now + // $date = radio_station_get_time( 'date', $now, $timezone ); + // $start_time = strtotime( '12am ' . $date ); + // $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; + // $start_time = $start_time - ( 7 * 24 * 60 * 60 ) - 1; + // $start_date = date( 'd-m-Y', $start_time ); + // $end_date = date( 'd-m-Y', $end_time ); + $start_date = $weekdates[$weekdays[0]]; + $end_date = $weekdates[$weekdays[6]]; + $overrides = radio_station_get_all_overrides( $start_date, $end_date, $timezone ); + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $date = radio_station_get_time( 'date', $now ); + $debug = "Now: " . $now . " - Date: " . $date . PHP_EOL; + $debug .= "Week Start Date: " . $start_date . " - Week End Date: " . $end_date . PHP_EOL; + $debug .= "Schedule Overrides: " . print_r( $overrides, true ) . PHP_EOL; + radio_station_debug( $debug ); + } + + // --- combine shifts and overrides --- + $show_shifts = $rs_se->combine_shifts( $show_shifts, $overrides, $times ); + + // --- filter and process --- + // 2.3.2: added time argument to filter + // 2.3.3: apply filter only once + $show_shifts = apply_filters( 'radio_station_current_schedule', $show_shifts, $time, $weekstart, $timezone, $channel ); + + // --- process shifts --- + $show_shifts = $rs_se->process_shifts( $show_shifts, $times ); + + // 2.5.0: use do action to set current schedule data/transient + $expires = 3600; + do_action( 'radio_station_set_current_schedule', $show_shifts, $expires, $time, $channel ); + + // echo 'SHOW SHIFTS! ' . print_r( $show_shifts, true ) . ''; + + } + + return $show_shifts; +} + +// ---------------- +// Get Current Show +// ---------------- +// 2.3.0: added new get current show function +// 2.3.2: added optional time argument +function radio_station_get_current_show( $time = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- get cached current show value --- + // 2.3.3: remove current show transient + // 2.3.3: check for existing global data first + // 2.5.0: maybe get channel or combined channel/time data + if ( !$time ) { + if ( ( '' != $channel ) && isset( $radio_station_data['current_show_' . $channel] ) ) { + return $radio_station_data['current_show_' . $channel]; + } elseif ( isset( $radio_station_data['current_show'] ) ) { + return $radio_station_data['current_show']; + } + } else { + if ( isset( $radio_station_data['current_show_' . $channel . '_' . $time] ) ) { + return $radio_station_data['current_show_' . $channel . '_' . $time]; + } elseif ( isset( $radio_station_data['current_show_' . $time] ) ) { + return $radio_station_data['current_show_' . $time]; + } + } + + // --- get all show shifts --- + if ( !$time ) { + $show_shifts = radio_station_get_current_schedule( false, $channel ); + } else { + $show_shifts = radio_station_get_current_schedule( $time, $channel ); + } + + // --- get currently scheduled show --- + $timezone = radio_station_get_timezone(); + $current_show = $rs_se->get_current_shift( $show_shifts, $time, $timezone ); + + // --- filter current show --- + // 2.3.2: added time argument to filter + $current_show = apply_filters( 'radio_station_current_show', $current_show, $time, $show_shifts, $channel ); + + if ( RADIO_STATION_DEBUG ) { + echo 'Current Show: ' . esc_html( print_r( $current_show, true ) ) . PHP_EOL; + } + + // --- set to global data --- + // 2.5.0: maybe set channel or combined channel/time data + if ( !$time ) { + if ( '' != $channel ) { + $radio_station_data['current_show_' . $channel] = $current_show; + } else { + $radio_station_data['current_show'] = $current_show; + } + } else { + if ( '' != $channel ) { + $radio_station_data['current_show_' . $channel . '_' . $time] = $current_show; + } else { + $radio_station_data['current_show_' . $time] = $current_show; + } + } + + return $current_show; +} + +// ----------------- +// Get Previous Show +// ----------------- +// 2.3.3: added get previous show function +function radio_station_get_previous_show( $time = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + $prev_show = false; + + // --- get cached current show value --- + if ( !$time ) { + // 2.5.0: maybe get previous show for channel + if ( '' != $channel ) { + if ( isset( $radio_station_data['previous_show_' . $channel] ) ) { + $prev_show = $radio_station_data['previous_show_' . $channel]; + } else { + $prev_show = get_transient( 'radio_station_previous_show_' . $channel ); + } + } elseif ( isset( $radio_station_data['previous_show'] ) ) { + $prev_show = $radio_station_data['previous_show']; + } else { + $prev_show = get_transient( 'radio_station_previous_show' ); + } + } else { + // 2.5.0: maybe get previous show for channel/time combination + if ( '' != $channel ) { + if ( isset( $radio_station_data['previous_show_' . $channel . '_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $channel . '_' . $time]; + } else { + $prev_show = get_transient( 'radio_station_previous_show_' . $channel . '_' . $time ); + } + } elseif ( isset( $radio_station_data['previous_show_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $time]; + } else { + $prev_show = get_transient( 'radio_station_previous_show_' . $time ); + } + } + + // --- if not set it has expired so recheck schedule --- + if ( !$prev_show ) { + if ( !$time ) { + $schedule = radio_station_get_current_schedule( false, $channel ); + if ( '' != $channel ) { + if ( isset( $radio_station_data['previous_show_' . $channel] ) ) { + $prev_show = $radio_station_data['previous_show_' . $channel]; + } + } elseif ( isset( $radio_station_data['previous_show'] ) ) { + $prev_show = $radio_station_data['previous_show']; + } + } else { + $schedule = radio_station_get_current_schedule( $time, $channel ); + if ( '' != $channel ) { + if ( isset( $radio_station_data['previous_show_' . $channel . '_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $channel . '_' . $time]; + } + } elseif ( isset( $radio_station_data['previous_show_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $time]; + } + } + } + + // note: already filtered when set + return $prev_show; +} + +// ------------- +// Get Next Show +// ------------- +// 2.3.0: added new get next show function +// 2.3.2: added optional time argument +function radio_station_get_next_show( $time = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + $next_show = false; + + // --- get cached current show value --- + if ( !$time ) { + // 2.5.0: maybe get next show for channel + if ( '' != $channel ) { + if ( isset( $radio_station_data['next_show_' . $channel] ) ) { + $next_show = $radio_station_data['next_show_' . $channel]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $channel ); + } + } else { + if ( isset ( $radio_station_data['next_show'] ) ) { + return $radio_station_data['next_show']; + } else { + $next_show = get_transient( 'radio_station_next_show' ); + } + } + } else { + // 2.5.0: maybe get next show for channel/time combo + if ( '' != $channel ) { + if ( isset( $radio_station_data['next_show_' . $channel . '_' . $time] ) ) { + $next_show = $radio_station_data['next_show_' . $channel . '_' . $time]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $channel . '_' . $time ); + } + } + if ( isset ( $radio_station_data['next_show_' . $time] ) ) { + return $radio_station_data['next_show_' . $time]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $time ); + } + } + + // --- if not set it has expired so recheck schedule --- + if ( !$next_show ) { + if ( !$time ) { + $schedule = radio_station_get_current_schedule( false, $channel ); + // 2.5.0: maybe get next show for channel + if ( '' != $channel ) { + if ( isset( $radio_station_data['next_show_' . $channel] ) ) { + $next_show = $radio_station_data['next_show_' . $channel]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $channel ); + } + } elseif ( isset( $radio_station_data['next_show'] ) ) { + $next_show = $radio_station_data['next_show']; + } else { + $next_show = get_transient( 'radio_station_next_show' ); + } + } else { + $schedule = radio_station_get_current_schedule( $time, $channel ); + // 2.5.0: maybe get next show for channel/time combo + if ( '' != $channel ) { + if ( isset( $radio_station_data['next_show_' . $channel . '_' . $time] ) ) { + $next_show = $radio_station_data['next_show_' . $channel . '_' . $time]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $channel . '_' . $time ); + } + } elseif ( isset( $radio_station_data['next_show_' . $time] ) ) { + $next_show = $radio_station_data['next_show_' . $time]; + } else { + $next_show = get_transient( 'radio_station_next_show_' . $time ); + } + } + + // 2.3.2: added time argument to filter + // 2.3.4: moved filter to where data is set so only applied once + // $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); + } + + return $next_show; +} + +// -------------- +// Get Next Shows +// -------------- +// 2.3.0: added new get next shows function +// 2.3.2: added optional time argument +function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- get all show shifts --- + // (this check is needed to prevent an endless loop!) + if ( !$show_shifts ) { + if ( !$time ) { + $show_shifts = radio_station_get_current_schedule( false, $channel ); + } else { + $show_shifts = radio_station_get_current_schedule( $time, $channel ); + } + } + + // --- loop (remaining) shifts to add show data --- + // 2.3.2: maybe set provided time as now + $now = $time ? $time : radio_station_get_now(); + $timezone = radio_station_get_timezone(); + + // --- get next shows --- + $next_shows = $rs_se->get_next_shifts( $limit, $show_shifts, $time, $timezone ); + + // --- maybe set next show transient --- + // 2.3.3: also set global data key + // 2.3.4: moved next show filter here (before setting data) + if ( is_array( $next_shows ) && ( count( $next_shows ) > 0 ) ) { + + $next_show = $next_shows[0]; + $next_show = apply_filters( 'radio_station_next_show', $next_show, $time, $channel ); + // TODO: handle split shifts over midnight ? + $shift_end_time = radio_station_to_time( $next_show['date'] . ' ' . $next_show['end'], $timezone ); + $expires = $shift_end_time - $now - 1; + + // 2.5.0: call next shift action to set transient data + do_action( 'radio_station_set_next_shift', $next_show, $expires, $time, $channel ); + + if ( RADIO_STATION_DEBUG ) { + echo 'Next Show: ' . esc_html( print_r( $next_show, true ) ) . ''; + } + } + + // --- filter and return --- + $next_shows = apply_filters( 'radio_station_next_shows', $next_shows, $limit, $show_shifts, $time, $channel ); + return $next_shows; +} + + +// ---------------------- +// === Shift Checking === +// ---------------------- + +// ------------------------- +// Schedule Conflict Checker +// ------------------------- +// (checks all existing show shifts for schedule) +// 2.3.0: added show shift conflict checker +function radio_station_check_shifts( $all_shifts ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- get times --- + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts + // 2.5.0: set times array for conflict checking + $now = radio_station_get_now(); + $timezone = radio_station_get_timezone(); + $today = radio_station_get_time( 'l', $now, $timezone ); + $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'now' => $now, + 'today' => $today, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + 'timezone' => $timezone, + ); + + // --- check for shift conflicts --- + $checked_shifts = $rs_se->check_shifts( $all_shifts, $times ); + return $checked_shifts; +} + +// ------------------ +// Show Shift Checker +// ------------------ +// (checks shift being saved against other shows) +function radio_station_check_shift( $show_id, $shift, $scope = 'all' ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // 2.3.2: manual bypass of shift checking + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_title( $_REQUEST['check-bypass'] ) ) ) { + return false; + } + + // --- get all show shift times --- + if ( '' != $channel ) { + if ( isset( $radio_station_data['all_shifts_' . $channel] ) ) { + $all_shifts = $radio_station_data['all_shifts_' . $channel]; + } + } elseif ( isset( $radio_station_data['all_shifts'] ) ) { + // --- get stored data --- + $all_shifts = $radio_station_data['all_shifts']; + } else { + + // (with conflict checking off as we are doing that now) + $all_shifts = radio_station_get_show_shifts( false, false, false ); + + // --- store this data for efficiency --- + if ( ( '' != $channel ) && is_string( $channel ) ) { + $radio_station_data['all_shifts_' . $channel] = $all_shifts; + } else { + $radio_station_data['all_shifts'] = $all_shifts; + } + } + + // --- convert days to dates for checking --- + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts + $now = radio_station_get_now(); + $timezone = radio_station_get_timezone(); + $today = radio_station_get_time( 'l', $now, $timezone ); + $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'now' => $now, + 'today' => $today, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + 'timezone' => $timezone, + ); + + // --- check shift for conflict --- + $conflicts = $rs_se->check_shift( $show_id, $shift, $scope, $all_shifts, $times ); + return $conflicts; +} + +// ------------------ +// New Shifts Checker +// ------------------ +// (checks show shifts for conflicts with same show) +function radio_station_check_new_shifts( $new_shifts, $weekdates = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- shift checking bypass switch --- + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_title( $_REQUEST['check-bypass'] ) ) ) { + return $new_shifts; + } + + // --- maybe get weekdates --- + if ( !$weekdates ) { + $now = radio_station_get_now(); + $timezone = radio_station_get_timezone(); + $today = radio_station_get_time( 'l', $now, $timezone ); + $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); + } + + // --- check new shifts --- + $new_shifts = $rs_se->check_new_shifts( $new_shifts, $weekdates ); + return $new_shifts; +} + +// ------------------- +// Validate Shift Time +// ------------------- +// 2.3.0: added check for incomplete shift times +function radio_station_validate_shift( $shift ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + $validated = $rs_se->validate_shift( $shift ); + return $validated; +} + + +// -------------------- +// === Data Setting === +// -------------------- + +// -------------------- +// Set Current Schedule +// -------------------- +add_action( 'radio_station_set_current_schedule', 'radio_station_set_current_schedule', 10, 4 ); +function radio_station_set_current_schedule( $show_shifts, $expires, $time, $channel ) { + + global $radio_station_data; + + // --- set current schedule data global --- + // note: transients are already set by schedule engine + if ( !$time ) { + if ( '' != $channel ) { + $radio_station_data['current_schedule_' . $channel] = $show_shifts; + // set_transient( 'radio_station_current_schedule_' . $channel, $show_shifts ); + } else { + $radio_station_data['current_schedule'] = $show_shifts; + // set_transient( 'radio_station_current_schedule', $show_shifts ); + } + } else { + $time = (string) $time; + if ( '' != $channel ) { + $radio_station_data['current_schedule_' . $channel . '_' . $time] = $show_shifts; + // set_transient( 'radio_station_current_schedule_' . $channel . '_' . $time, $show_shifts ); + } else { + $radio_station_data['current_schedule_' . $time] = $show_shifts; + // set_transient( 'radio_station_current_schedule_' . $time, $show_shifts ); + } + } +} + +// ----------------- +// Set Previous Show +// ----------------- +add_action( 'radio_station_set_previous_shift', 'radio_station_set_previous_show', 10, 4 ); +function radio_station_set_previous_show( $previous_show, $expires, $time, $channel ) { + + global $radio_station_data; + $transient_key = 'previous_show'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + $radio_station_data[$transient_key] = $previous_show; + // note: sets previous_show not previous_shift + set_transient( 'radio_station_' . $transient_key, $previous_show, $expires ); +} + +// ---------------- +// Set Current Show +// ---------------- +add_action( 'radio_station_set_current_shift', 'radio_station_set_current_show', 10, 4 ); +function radio_station_set_current_show( $next_show, $expires, $time, $channel ) { + + global $radio_station_data; + $transient_key = 'current_show'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + $radio_station_data[$transient_key] = $next_show; + // note: sets current_show not current_shift + set_transient( 'radio_station_' . $transient_key, $next_show, $expires ); +} + +// ------------- +// Set Next Show +// ------------- +add_action( 'radio_station_set_next_shift', 'radio_station_set_next_show', 10, 4 ); +function radio_station_set_next_show( $next_show, $expires, $time, $channel ) { + + global $radio_station_data; + $transient_key = 'next_show'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + $radio_station_data[$transient_key] = $next_show; + // note: sets next_show not next_shift + set_transient( 'radio_station_' . $transient_key, $next_show, $expires ); +} + +// ---------------- +// Set Shift Errors +// ---------------- +add_action( 'radio_station_set_shift_errors', 'radio_station_shift_errors', 10, 2 ); +function radio_station_shift_errors( $errors, $channel ) { + // --- maybe store any found shift errors --- + if ( $errors && is_array( $errors ) && count( $errors ) > 0 ) { + if ( ( '' != $channel ) && is_string( $channel ) ) { + update_option( 'radio_station_' . $channel . '_shift_errors', $errors ); + } else { + update_option( 'radio_station_shift_errors', $errors ); + } + } else { + if ( ( '' != $channel ) && is_string( $channel ) ) { + delete_option( 'radio_station_' . $channel . '_shift_errors' ); + } else { + delete_option( 'radio_station_shift_errors' ); + } + } +} + +// ------------------- +// Set Shift Conflicts +// ------------------- +add_action( 'radio_station_set_shift_conflicts', 'radio_station_set_shift_conflicts', 10, 2 ); +function radio_station_set_shift_conflicts( $conflicts, $channel ) { + + // --- check if any conflicts found --- + if ( count( $conflicts ) > 0 ) { + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Shift Conflict Data: " . print_r( $conflicts, true ) . PHP_EOL; + if ( ( '' != $channel ) && is_string( $channel ) ) { + $debug .= '(for Channel ' . $channel . ')' . PHP_EOL; + } + radio_station_debug( $debug ); + } + + // --- save any conflicts found --- + if ( ( '' != $channel ) && is_string( $channel ) ) { + update_option( 'radio_station_' . $channel . '_schedule_conflicts', $conflicts ); + } else { + update_option( 'radio_station_schedule_conflicts', $conflicts ); + } + + } else { + + // --- clear conflicts data --- + if ( ( '' != $channel ) && is_string( $channel ) ) { + delete_option( 'radio_station_' . $channel . '_schedule_conflicts' ); + } else { + delete_option( 'radio_station_schedule_conflicts' ); + } + } +} + diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 5da3947..f9c6ffe 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -10,6 +10,7 @@ // - Radio Timezone Shortcode // - Radio Clock Shortcode // === Archive Shortcodes === +// - Archive List Shortcode Router // - Archive List Shortcode Abstract // - Show Archive Shortcode // - Playlist Archive Shortcode @@ -35,6 +36,8 @@ // - Show Playlist Shortcode + + // ----------------------- // === Time Shortcodes === // ----------------------- @@ -46,16 +49,22 @@ function radio_station_timezone_shortcode( $atts = array() ) { global $radio_station_data; + + // 2.5.0: added shortcode_atts call for filtering + $defaults = array(); + $atts = shortcode_atts( $defaults, $atts, 'radio-timezone' ); // --- set shortcode instance --- - if ( isset( $radio_station_data['timezone_instance'] ) ) { - $instance = $radio_station_data['timezone_instance']; - } else { - $instance = $radio_station_data['timezone_instance'] = 0; + // 2.5.0: simplified instance data + if ( !isset( $radio_station_data['instances']['timezone_shortcode'] ) ) { + $radio_station_data['instances']['timezone_shortcode'] = 0; } + $radio_station_data['instances']['timezone_shortcode']++; + $instance = $radio_station_data['instances']['timezone_shortcode']; // --- get radio timezone values --- - $timezone = radio_station_get_setting( 'timezone_location' ); + // $timezone = radio_station_get_setting( 'timezone_location' ); + $timezone = radio_station_get_timezone(); if ( !$timezone || ( '' == $timezone ) ) { // --- fallback to WordPress timezone --- $timezone = get_option( 'timezone_string' ); @@ -64,16 +73,26 @@ function radio_station_timezone_shortcode( $atts = array() ) { } if ( '' == $timezone ) { $offset = get_option( 'gmt_offset' ); + if ( !$offset || ( 0 == $offset ) ) { + $offset = ''; + } elseif ( $offset > 0 ) { + $offset = '+' . $offset; + } + // 2.5.0: added square brackets around UTC timezone display + $timezone_display = '[' . __( 'UTC', 'radio-station' ) . ' ' . $offset . ']'; } - } - if ( isset( $offset ) ) { - if ( !$offset || ( 0 == $offset ) ) { - $offset = ''; - } elseif ( $offset > 0 ) { - $offset = '+' . $offset; + } elseif ( strstr( $timezone, 'UTC' ) ) { + // 2.5.0: added fallback for returned UTC timezone string + $utc = __( 'UTC', 'radio-station' ); + if ( strstr( $timezone, 'UTC-') ) { + $timezone_display = '[' . str_replace( 'UTC-', $utc . ' -', $timezone ) . ']'; + } else { + $timezone_display = '[' . str_replace( 'UTC', $utc . ' +', $timezone ) . ']'; } - $timezone_display = __( 'UTC', 'radio-station' ) . ' ' . $offset; - } else { + } + + if ( !isset( $timezone_display ) ) { + // --- get offset and code from timezone location --- $datetimezone = new DateTimeZone( $timezone ); $offset = $datetimezone->getOffset( new DateTime() ); @@ -104,20 +123,23 @@ function radio_station_timezone_shortcode( $atts = array() ) { } // --- set shortcode output --- - $output = '
    '; + // 2.5.0: added instance ID to timezone div wrapper + $output = '
    ' . "\n"; // --- radio timezone --- - $output .= '
    '; - $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); - $output .= ':
    '; - $output .= '
    ' . esc_html( $timezone_display ) . '

    '; + $output .= '
    ' . "\n"; + $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ) . "\n"; + $output .= ':
    ' . "\n"; + $output .= '
    ' . "\n"; + $output .= esc_html( $timezone_display ) . "\n"; + $output .= '

    ' . "\n"; // --- user timezone --- // 2.3.3.9: change span elements to divs - $output .= '
    '; - $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ); - $output .= ':
    '; - $output .= '
    '; + $output .= '
    ' . "\n"; + $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ) . "\n"; + $output .= ':
    ' . "\n"; + $output .= '
    ' . "\n"; // 2.3.2 allow for timezone selector test // $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); @@ -125,14 +147,14 @@ function radio_station_timezone_shortcode( $atts = array() ) { // $output .= $select; // } - $output .= '
    '; + $output .= '
    ' . "\n"; // --- enqueue shortcode styles --- // 2.3.2: added for timezone shortcode styles radio_station_enqueue_style( 'shortcodes' ); // --- filter and return --- - $output = apply_filters( 'radio_station_timezone_shortcode', $output, $atts ); + $output = apply_filters( 'radio_station_timezone_shortcode', $output, $atts, $instance ); return $output; } @@ -145,28 +167,33 @@ function radio_station_clock_shortcode( $atts = array() ) { global $radio_station_data; // --- set shortcode instance --- - if ( isset( $radio_station_data['clock_instance'] ) ) { - $instance = $radio_station_data['clock_instance']; - } else { - $instance = $radio_station_data['clock_instance'] = 0; + // 2.5.0: simplified instance data + if ( !isset( $radio_station_data['instances']['clock_shortcode'] ) ) { + $radio_station_data['instances']['clock_shortcode'] = 0; } + $radio_station_data['instances']['clock_shortcode']++; + $instance = $radio_station_data['instances']['clock_shortcode']; // 2.3.3: use plugin setting if time format attribute is empty - if ( isset( $atts['time'] ) && ( '' == $atts['time'] ) ) { + // 2.5.0: fix to update time attribute to time_format + if ( isset( $atts['time'] ) ) { + if ( '' != trim( $atts['time'] ) ) { + $atts['time_format'] = $atts['time']; + } unset( $atts['time'] ); } // --- merge default attributes --- // 2.3.3: fix to incorrect setting key (clock_format) - $clock_format = radio_station_get_setting( 'clock_time_format' ); + $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( - 'time' => $clock_format, - 'seconds' => 1, - 'day' => 'full', // full / short / none - 'date' => 1, - 'month' => 'full', // full / short / none - 'zone' => 1, - 'widget' => 0, + 'time_format' => $time_format, + 'day' => 'full', // full / short / none + 'date' => 1, + 'month' => 'full', // full / short / none + 'zone' => 1, + 'seconds' => 1, + 'widget' => 0, ); $atts = shortcode_atts( $defaults, $atts, 'radio-clock' ); @@ -177,7 +204,7 @@ function radio_station_clock_shortcode( $atts = array() ) { } else { $classes[] = 'radio-station-clock-shortcode'; } - if ( 24 == $atts['time'] ) { + if ( 24 == (int) $atts['time_format'] ) { $classes[] = 'format-24'; } else { $classes[] = 'format-12'; @@ -208,27 +235,27 @@ function radio_station_clock_shortcode( $atts = array() ) { // -- open clock div --- $classlist = implode( ' ', $classes ); - $clock = '
    '; - - // --- server clock --- - $clock .= '
    '; - $clock .= '
    '; - $clock .= esc_html( __( 'Radio Time', 'radio-station' ) ); - $clock .= ':
    '; - $clock .= '
    '; - $clock .= '
    '; - $clock .= '
    '; - $clock .= '
    '; - - // --- user clock --- - $clock .= '
    '; - $clock .= '
    '; - $clock .= esc_html( __( 'Your Time', 'radio-station' ) ); - $clock .= ':
    '; - $clock .= '
    '; - $clock .= '
    '; - $clock .= '
    '; - $clock .= '
    '; + $clock = '
    ' . "\n"; + + // --- server clock --- + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= esc_html( __( 'Radio Time', 'radio-station' ) ); + $clock .= ':
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + + // --- user clock --- + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= esc_html( __( 'Your Time', 'radio-station' ) ); + $clock .= ':
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; // 2.3.2 allow for timezone selector test // $select = radio_station_timezone_select( 'radio-station-clock-' + $instance ); @@ -237,7 +264,7 @@ function radio_station_clock_shortcode( $atts = array() ) { // $clock .= $select; // } - $clock .= '
    '; + $clock .= '
    ' . "\n"; // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -246,8 +273,7 @@ function radio_station_clock_shortcode( $atts = array() ) { radio_station_enqueue_script( 'radio-station-clock', array(), true ); // --- filter and return --- - $clock = apply_filters( 'radio_station_clock', $clock, $atts ); - + $clock = apply_filters( 'radio_station_clock', $clock, $atts, $instance ); return $clock; } @@ -256,18 +282,59 @@ function radio_station_clock_shortcode( $atts = array() ) { // === Archive Shortcodes === // -------------------------- +// ----------------------------- +// Archive List Shortcode Router +// ----------------------------- +function radio_station_archive_list( $atts ) { + + // echo '' . print_r( $atts, true ) . ''; + + if ( 'shows' == $atts['archive_type'] ) { + return radio_station_show_archive_list( $atts ); + } elseif ( 'overrides' == $atts['archive_type'] ) { + return radio_station_override_archive_list( $atts ); + } elseif ( 'playlists' == $atts['archive_type'] ) { + return radio_station_playlist_archive_list( $atts ); + } elseif ( 'genres' == $atts['archive_type'] ) { + return radio_station_genre_archive_list( $atts ); + } elseif ( 'languages' == $atts['archive_type'] ) { + return radio_station_language_archive_list( $atts ); + } + + // --- filter and return --- + $output = apply_filters( 'radio_station_archive_list', '', $atts ); + return $output; +} + // ------------------------------- // Archive List Shortcode Abstract // ------------------------------- // (handles Shows, Overrides, Playlists etc.) function radio_station_archive_list_shortcode( $post_type, $atts ) { + global $radio_station_data; + // --- set type from post type --- $type = str_replace( 'rs-', '', $post_type ); + // --- set shortcode instance --- + if ( !isset( $radio_station_data['instances'][$type . '-archive-list'] ) ) { + $radio_station_data['instances'][$type . '-archive-list'] = 0; + } + $radio_station_data['instances'][$type . '-archive-list']++; + $instance = $radio_station_data['instances'][$type . '-archive-list']; + // --- get clock time format --- $time_format = radio_station_get_setting( 'clock_time_format' ); + // 2.5.0: fix for old settings names + if ( isset( $atts['time'] ) ) { + if ( '' != trim( $atts['time'] ) ) { + $atts['time_format'] = $atts['time']; + } + unset( $atts['time'] ); + } + // --- merge defaults with passed attributes --- // 2.3.3.9: add atts for specific posts // 2.4.0.4: added optional view attribute @@ -276,7 +343,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- shortcode display ---- 'description' => 'excerpt', 'hide_empty' => 0, - 'time' => $time_format, + 'time_format' => $time_format, 'view' => 'list', // --- taxonomy queries --- 'genre' => '', @@ -285,7 +352,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { 'orderby' => 'title', 'order' => 'ASC', 'status' => 'publish', - 'perpage' => - 1, + 'perpage' => -1, 'offset' => 0, 'pagination' => 1, // --- shows only --- @@ -314,13 +381,13 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- handle possible pagination offset --- if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { $page = absint( get_query_var( 'page' ) ); - if ( $page > - 1 ) { + if ( $page > -1 ) { $atts['offset'] = (int) $atts['perpage'] * $page; } } $atts = shortcode_atts( $defaults, $atts, $post_type . '-archive' ); if ( RADIO_STATION_DEBUG ) { - echo 'Shortcode Atts: ' . print_r( $atts, true ) . ''; + echo 'Shortcode Atts: ' . esc_html( print_r( $atts, true ) ) . ''; } // --- get published shows --- @@ -328,7 +395,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $args = array( 'post_type' => $post_type, 'post_status' => $atts['status'], - 'numberposts' => - 1, + 'numberposts' => -1, // 'numberposts' => $atts['perpage'], // 'offset' => $atts['offset'], 'orderby' => $atts['orderby'], @@ -367,6 +434,18 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } } + // 2.5.0: added missing override meta query check + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + if ( $atts['with_shifts'] ) { + $args['meta_query'] = array( + array( + 'key' => 'show_override_sched', + 'compare' => 'EXISTS', + ), + ); + } + } + // --- specific genres taxonomy query --- if ( !empty( $atts['genre'] ) && in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { @@ -424,14 +503,9 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // --- get posts via query --- - $args = apply_filters( 'radio_station_' . $type . '_archive_post_args', $args ); + // 2.5.0: added atts as second argument to filters + $args = apply_filters( 'radio_station_' . $type . '_archive_post_args', $args, $atts ); $archive_posts = get_posts( $args ); - $archive_posts = apply_filters( 'radio_station_' . $type . '_archive_posts', $archive_posts ); - if ( RADIO_STATION_DEBUG ) { - echo 'Archive Shortcode: ' . PHP_EOL; - echo 'Args: ' . print_r( $args, true ) . PHP_EOL; - echo 'Posts: ' . print_r( $archive_posts, true ) . ''; - } // --- process playlist taxonomy query --- if ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) { @@ -440,8 +514,11 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- check assigned show has a specified genre term --- if ( !empty( $atts['genre'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { - if ( is_array( $atts['genre'] ) ) {$genres = $atts['genre'];} - else {$genres = explode( ',', $atts['genre'] );} + if ( is_array( $atts['genre'] ) ) { + $genres = $atts['genre']; + } else { + $genres = explode( ',', $atts['genre'] ); + } foreach ( $archive_posts as $i => $archive_post ) { $found_genre = false; $show_id = get_post_meta( $archive_post->ID, 'playlist_show_id', true ); @@ -516,13 +593,22 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } } + // 2.5.0: move archive posts filter to after show taxonomy/override date checks + $archive_posts = apply_filters( 'radio_station_' . $type . '_archive_posts', $archive_posts, $atts ); + if ( RADIO_STATION_DEBUG ) { + echo 'Archive Shortcode: ' . "\b"; + echo 'Args: ' . esc_html( print_r( $args, true ) ) . "\n"; + echo 'Posts: ' . esc_html( print_r( $archive_posts, true ) ) . "\n"; + echo '' . "\n"; + } + // --- set time data formats --- // 2.3.0: added once-off meridiem pre-conversions // 2.3.2: replaced meridiem conversions with data formats // 2.4.0.6: added filter for default time format separator $time_separator = ':'; - $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, $post_type . '-archive' ); - if ( 24 == (int) $atts['time'] ) { + $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, $post_type . '-archive', $atts ); + if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; @@ -601,7 +687,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $more = apply_filters( 'radio_station_archive_' . $type . '_list_excerpt_more', '[…]' ); // --- archive list --- - $list .= '
      '; + $list .= '
        ' . "\n"; // --- set info keys --- // (note: meta is dates for overrides, shifts for shows, tracks for playlists etc.) @@ -643,10 +729,10 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } } - $list .= '
      • '; + $list .= '
      • ' . "\n"; // --- show avatar or thumbnail fallback --- - $info['avatar'] = '
        '; + $info['avatar'] = '
        ' . "\n"; $show_avatar = false; if ( $atts['show_avatars'] && in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { $attr = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); @@ -662,13 +748,14 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $info['avatar'] .= $thumbnail; } } - $info['avatar'] .= '
        '; + $info['avatar'] .= "\n" . '
        ' . "\n"; // --- title ---- - $info['title'] = '
        '; - $info['title'] .= ''; - $info['title'] .= esc_html( $title ) . ''; - $info['title'] .= '
        '; + $info['title'] = '
        ' . "\n"; + $info['title'] .= '' . "\n"; + $info['title'] .= esc_html( $title ) . "\n"; + $info['title'] .= '' . "\n"; + $info['title'] .= '
        ' . "\n"; // --- display Override date(s) --- if ( ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) && ( $atts['show_dates'] ) ) { @@ -681,7 +768,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // 2.3.1: fix to append not echo override date to archive list - $info['meta'] = '
        '; + $info['meta'] = '
        ' . "\n"; foreach ( $override_times as $override_time ) { @@ -717,15 +804,15 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override' ); // 2.3.1: fix to append not echo override date to archive list - $info['meta'] .= '' . esc_html( $start ) . ''; - $info['meta'] .= '' . esc_html( $separator ) . ''; - $info['meta'] .= '' . esc_html( $end ) . ''; - $info['meta'] .= '
        '; + $info['meta'] .= '' . esc_html( $start ) . '' . "\n"; + $info['meta'] .= '' . esc_html( $separator ) . '' . "\n"; + $info['meta'] .= '' . esc_html( $end ) . '' . "\n"; + $info['meta'] .= '
        ' . "\n"; } } - $info['meta'] .= '
        '; + $info['meta'] .= '
        ' . "\n"; } // TODO: display Show shifts meta @@ -767,12 +854,11 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { if ( 'none' == $atts['description'] ) { $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { - $info['description'] = '
        '; $content = apply_filters( 'radio_station_' . $type . '_archive_content', $post_content, $post_id ); - $info['description'] .= $content; - $info['description'] .= '
        '; + $info['description'] = '
        ' . "\n"; + $info['description'] .= $content . "\n"; + $info['description'] .= '
        ' . "\n"; } else { - $info['description'] = '
        '; if ( !empty( $post_excerpt ) ) { $excerpt = $post_excerpt; $excerpt .= ' ' . $more . ''; @@ -780,8 +866,9 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); } $excerpt = apply_filters( 'radio_station_' . $type . '_archive_excerpt', $excerpt, $post_id ); - $info['description'] .= $excerpt; - $info['description'] .= '
        '; + $info['description'] = '
        ' . "\n"; + $info['description'] .= $excerpt . "\n"; + $info['description'] .= '
        ' . "\n"; } // 2.3.3.9: add filter for custom HTML info @@ -795,11 +882,11 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } } - $list .= '
      • '; + $list .= '' . "\n"; } - $list .= '
      '; + $list .= '
    ' . "\n"; } - $list .= '
    '; + $list .= '
    ' . "\n"; // --- add archive_pagination --- if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { @@ -830,15 +917,6 @@ function radio_station_show_archive_list( $atts ) { return radio_station_archive_list_shortcode( RADIO_STATION_SHOW_SLUG, $atts ); } -// -------------------------- -// Playlist Archive Shortcode -// -------------------------- -add_shortcode( 'playlist-archive', 'radio_station_playlist_archive_list' ); -add_shortcode( 'playlists-archive', 'radio_station_playlist_archive_list' ); -function radio_station_playlist_archive_list( $atts ) { - return radio_station_archive_list_shortcode( RADIO_STATION_PLAYLIST_SLUG, $atts ); -} - // -------------------------- // Override Archive Shortcode // -------------------------- @@ -848,6 +926,15 @@ function radio_station_override_archive_list( $atts ) { return radio_station_archive_list_shortcode( RADIO_STATION_OVERRIDE_SLUG, $atts ); } +// -------------------------- +// Playlist Archive Shortcode +// -------------------------- +add_shortcode( 'playlist-archive', 'radio_station_playlist_archive_list' ); +add_shortcode( 'playlists-archive', 'radio_station_playlist_archive_list' ); +function radio_station_playlist_archive_list( $atts ) { + return radio_station_archive_list_shortcode( RADIO_STATION_PLAYLIST_SLUG, $atts ); +} + // ----------------------- // Genre Archive Shortcode // ----------------------- @@ -855,6 +942,16 @@ function radio_station_override_archive_list( $atts ) { add_shortcode( 'genres-archive', 'radio_station_genre_archive_list' ); function radio_station_genre_archive_list( $atts ) { + global $radio_station_data; + + // --- set shortcode instance --- + // 2.5.0: added for consistency + if ( !isset( $radio_station_data['instances']['genre-archive-list'] ) ) { + $radio_station_data['instances']['genre-archive-list'] = 0; + } + $radio_station_data['instances']['genre-archive-list']++; + $instance = $radio_station_data['instances']['genre-archive-list']; + // 2.3.3.9: default show description display to on $defaults = array( // --- genre display options --- @@ -914,15 +1011,15 @@ function radio_station_genre_archive_list( $atts ) { if ( $atts['hide_empty'] ) { return ''; } else { - $list = '
    '; - $list .= esc_html( __( 'No Genres were found to display.', 'radio-station' ) ); - $list .= '
    '; - + $list = '
    ' . "\n"; + $list .= esc_html( __( 'No Genres were found to display.', 'radio-station' ) ) . "\n"; + $list .= '
    ' . "\n"; return $list; } } - $list = '
    '; + // 2.5.0: added id attribute with instance + $list = '
    ' . "\n"; // --- loop genres --- foreach ( $genres as $name => $genre ) { @@ -976,17 +1073,14 @@ function radio_station_genre_archive_list( $atts ) { ); // --- get shows in genre --- - $args = apply_filters( 'radio_station_genre_archive_post_args', $args ); + // 2.5.0: added shortcode atts as second filter argument + $args = apply_filters( 'radio_station_genre_archive_post_args', $args, $atts ); $posts = get_posts( $args ); - $posts = apply_filters( 'radio_station_genre_archive_posts', $posts ); + $posts = apply_filters( 'radio_station_genre_archive_posts', $posts, $atts ); - $list .= '
    '; + $list .= '
    ' . "\n"; - if ( $posts || ( count( $posts ) > 0 ) ) { - $has_posts = true; - } else { - $has_posts = false; - } + $has_posts = ( $posts || ( count( $posts ) > 0 ) ) ? true : false; if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { // --- genre image --- @@ -996,25 +1090,31 @@ function radio_station_genre_archive_list( $atts ) { if ( absint( $atts['image_width'] ) > 0 ) { $width_style = ' style="width: ' . esc_attr( absint( $atts['image_width'] ) ) . 'px"'; } - $list .= '
    '; - $list .= $genre_image; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: added wp_kses on image output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $genre_image ); + $list .= '
    ' . "\n"; } // --- genre title --- - $list .= '

    '; + $list .= '

    ' . "\n"; if ( $atts['link_genres'] ) { - $list .= '' . $genre['name'] . ''; + $list .= ''; + $list .= esc_html( $genre['name'] ) . "\n"; + $list .= '' . "\n"; } else { - $list .= $genre['name']; + $list .= esc_html( $genre['name'] ) . "\n"; } - $list .= '

    '; + $list .= '

    ' . "\n"; // --- genre description --- if ( $atts['genre_desc'] && !empty( $genre['genre_desc'] ) ) { - $list .= '
    '; - $list .= $genre['description']; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: added wp_kses on description output + $allowed = radio_station_allowed_html( 'content', 'description' ); + $list .= wp_kses( $genre['description'], $allowed ) . "\n"; + $list .= '
    ' . "\n"; } } @@ -1023,7 +1123,7 @@ function radio_station_genre_archive_list( $atts ) { // --- no shows messages ---- if ( !$atts['hide_empty'] ) { - $list .= esc_html( __( 'No Shows in this Genre.', 'radio-station' ) ); + $list .= esc_html( __( 'No Shows in this Genre.', 'radio-station' ) ) . "\n"; } } else { @@ -1034,67 +1134,75 @@ function radio_station_genre_archive_list( $atts ) { $more = apply_filters( 'radio_station_genre_archive_excerpt_more', '[…]' ); // --- show archive list --- - $list .= '
      '; + $list .= '
        ' . "\n"; foreach ( $posts as $post ) { - $list .= '
      • '; + $list .= '
      • ' . "\n"; // --- show avatar or thumbnail fallback --- $width_style = ''; if ( absint( $atts['avatar_width'] ) > 0 ) { $width_styles = ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; } - $list .= '
        '; + $list .= '
        ' . "\n"; $show_avatar = false; if ( $atts['show_avatars'] ) { $attr = array( 'class' => 'show-thumbnail-image' ); $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); } if ( $show_avatar ) { - $list .= $show_avatar; + // 2.5.0: added wp_kses on show avatar output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $show_avatar, $allowed ). "\n"; } elseif ( $atts['thumbnails'] ) { if ( has_post_thumbnail( $post->ID ) ) { $attr = array( 'class' => 'show-thumbnail-image' ); $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); - $list .= $thumbnail; + // 2.5.0: added wp_kses on thumbnail outputting + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $thumbnail, $allowed ) . "\n"; } } - $list .= '
        '; + $list .= '
        ' . "\n"; // --- show title ---- $permalink = get_permalink( $post->ID ); - $list .= ''; + $list .= '' . "\n"; // --- show excerpt --- // 2.2.3.9: display show description if ( $atts['show_desc' ] ) { if ( !empty( $post->post_excerpt ) ) { $excerpt = $post->post_excerpt; - $excerpt .= ' ' . $more . ''; + // 2.5.0: added missing esc_html on more anchor + $excerpt .= ' ' . esc_html( $more ) . ''; } else { $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); } $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); if ( '' != $excerpt ) { - $list .= '
        '; - $list .= $excerpt; - $list .= '
        '; + $list .= '
        ' . "\n"; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $list .= wp_kses( $excerpt, $allowed ) . "\n"; + $list .= '
        ' . "\n"; } } - $list .= '
      • '; + $list .= '' . "\n"; } - $list .= '
      '; + $list .= '
    ' . "\n"; } - - $list .= '
    '; + $list .= '
    ' . "\n"; } - $list .= '
    '; + $list .= '
    ' . "\n"; // --- add archive_pagination --- // TODO: add genre archive list pagination @@ -1109,8 +1217,7 @@ function radio_station_genre_archive_list( $atts ) { radio_station_enqueue_style( 'shortcodes' ); // --- filter and return --- - $list = apply_filters( 'radio_station_genre_archive_list', $list, $atts ); - + $list = apply_filters( 'radio_station_genre_archive_list', $list, $atts, $instance ); return $list; } @@ -1122,6 +1229,16 @@ function radio_station_genre_archive_list( $atts ) { add_shortcode( 'languages-archive', 'radio_station_language_archive_list' ); function radio_station_language_archive_list( $atts ) { + global $radio_station_data; + + // --- set shortcode instance --- + // 2.5.0: set shortcode instance for consistency + if ( !isset( $radio_station_data['instances']['language-archive-list'] ) ) { + $radio_station_data['instances']['language-archive-list'] = 0; + } + $radio_station_data['instances']['language-archive-list']++; + $instance = $radio_station_data['instances']['language-archive-list']; + $defaults = array( // --- language display options --- 'languages' => '', @@ -1178,15 +1295,15 @@ function radio_station_language_archive_list( $atts ) { if ( $atts['hide_empty'] ) { return ''; } else { - $list = '
    '; - $list .= esc_html( __( 'No Languages were found to display.', 'radio-station' ) ); - $list .= '
    '; - + $list = '
    ' . "\n"; + $list .= esc_html( __( 'No Languages were found to display.', 'radio-station' ) ) . "\n"; + $list .= '
    ' . "\n"; return $list; } } - $list = '
    '; + // 2.5.0: added id attribute with instance + $list = '
    ' . "\n"; // --- loop languages --- foreach ( $languages as $name => $language ) { @@ -1240,11 +1357,11 @@ function radio_station_language_archive_list( $atts ) { ); // --- get shows in language --- - $args = apply_filters( 'radio_station_language_archive_post_args', $args ); + $args = apply_filters( 'radio_station_language_archive_post_args', $args, $atts ); $posts = get_posts( $args ); - $posts = apply_filters( 'radio_station_language_archive_posts', $posts ); + $posts = apply_filters( 'radio_station_language_archive_posts', $posts, $atts ); - $list .= '
    '; + $list .= '
    ' . "\n"; if ( $posts || ( count( $posts ) > 0 ) ) { $has_posts = true; @@ -1254,19 +1371,23 @@ function radio_station_language_archive_list( $atts ) { if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { // --- language title --- - $list .= '

    '; + $list .= '

    ' . "\n"; if ( $atts['link_languages'] ) { - $list .= '' . $language['name'] . ''; + $list .= '' . "\n"; + $list .= esc_html( $language['name'] ) . "\n"; + $list .= '' . "\n"; } else { - $list .= $language['name']; + $list .= esc_html( $language['name'] ) . "\n"; } - $list .= '

    '; + $list .= '

    ' . "\n"; // --- language description --- if ( $atts['language_desc'] && !empty( $language['language_desc'] ) ) { - $list .= '
    '; - $list .= $language['description']; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on description output + $allowed = radio_station_allowed_html( 'content', 'description' ); + $list .= wp_kses( $language['description'], $allowed ) . "\n"; + $list .= '
    ' . "\n"; } } @@ -1285,66 +1406,74 @@ function radio_station_language_archive_list( $atts ) { $more = apply_filters( 'radio_station_language_archive_excerpt_more', '[…]' ); // --- show archive list --- - $list .= '
      '; + $list .= '
        ' . "\n"; foreach ( $posts as $post ) { - $list .= '
      • '; + $list .= '
      • ' . "\n"; // --- show avatar or thumbnail fallback --- $width_style = ''; if ( absint( $atts['avatar_width'] ) > 0 ) { $width_styles = ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; } - $list .= '
        '; + $list .= '
        ' . "\n"; $show_avatar = false; if ( $atts['show_avatars'] ) { $attr = array( 'class' => 'show-thumbnail-image' ); $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); } if ( $show_avatar ) { - $list .= $show_avatar; + // 2.5.0: use wp_kses on show avatar output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $show_avatar, $allowed ) . "\n"; } elseif ( $atts['thumbnails'] ) { if ( has_post_thumbnail( $post->ID ) ) { $attr = array( 'class' => 'show-thumbnail-image' ); $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); - $list .= $thumbnail; + // 2.5.0: use wp_kses on thumbnail output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $thumbnail, $allowed ) . "\n"; } } $list .= '
        '; // --- show title ---- $permalink = get_permalink( $post->ID ); - $list .= ''; + $list .= '' . "\n"; // --- show excerpt --- if ( $atts['show_desc' ] ) { if ( !empty( $post->post_excerpt ) ) { $excerpt = $post->post_excerpt; - $excerpt .= ' ' . $more . ''; + // 2.5.0: use esc_html on more anchor text + $excerpt .= ' ' . esc_html( $more ) . '' . "\n"; } else { $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); } - $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); + // 2.5.0: fix to incorrect filter name radio_station_genre_archive_excerpt + $excerpt = apply_filters( 'radio_station_language_archive_excerpt', $excerpt, $post->ID ); if ( '' != $excerpt ) { $list .= '
        '; - $list .= $excerpt; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $list .= wp_kses( $excerpt, $allowed ); $list .= '
        '; } } - $list .= '
      • '; + $list .= '' . "\n"; } - $list .= '
      '; + $list .= '
    ' . "\n"; } - - $list .= '
    '; + $list .= '
    ' . "\n"; } - - $list .= '
    '; + $list .= '
    ' . "\n"; // --- add archive_pagination --- // TODO: add language archive list pagination @@ -1359,8 +1488,7 @@ function radio_station_language_archive_list( $atts ) { radio_station_enqueue_style( 'shortcodes' ); // --- filter and return --- - $list = apply_filters( 'radio_station_language_archive_list', $list, $atts ); - + $list = apply_filters( 'radio_station_language_archive_list', $list, $atts, $instance ); return $list; } @@ -1372,40 +1500,32 @@ function radio_station_archive_pagination( $type, $atts, $post_count ) { global $post; $permalink = get_permalink( $post->ID ); $post_pages = ceil( $post_count / $atts['perpage'] ); - if ( $atts['offset'] > 0 ) { - $current_page = $atts['offset'] / $atts['perpage']; - } else { - $current_page = 0; - } + $current_page = ( $atts['offset'] > 0 ) ? $atts['offset'] / $atts['perpage'] : 0; $prev_page = $current_page - 1; $next_page = $current_page + 1; $pagi = '

    '; - $pagi .= '
    '; + $pagi .= '
    ' . "\n"; $url = add_query_arg( 'page', $prev_page, $permalink ); - $pagi .= '
    '; + $pagi .= '
    ' . "\n"; if ( $prev_page > 0 ) { - $pagi .= ''; + $pagi .= '' . "\n"; } - $pagi .= '
    '; + $pagi .= '
    ' . "\n"; for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { - if ( $current_page == $pagenum ) { - $active = ' active'; - } else { - $active = ''; - } - $pagi .= '
    '; + $active = ( $current_page == $pagenum ) ? ' active' : ''; + $pagi .= '
    ' . "\n"; $url = add_query_arg( 'page', $pagenum, $permalink ); - $pagi .= ''; - $pagi .= esc_html( $pagenum ); - $pagi .= ''; - $pagi .= '
    '; + $pagi .= '' . "\n"; + $pagi .= esc_html( $pagenum ) . "\n"; + $pagi .= '' . "\n"; + $pagi .= '
    ' . "\n"; } $url = add_query_arg( 'page', $next_page, $permalink ); - $pagi .= '
    '; - $pagi .= ''; - $pagi .= '
    '; - $pagi .= '
    '; + $pagi .= '
    ' . "\n"; + $pagi .= '' . "\n"; + $pagi .= '
    ' . "\n"; + $pagi .= '
    ' . "\n"; return $pagi; } @@ -1464,10 +1584,26 @@ function radio_station_show_list_shortcode( $type, $atts ) { global $radio_station_data; + // --- set shortcode instance --- + // 2.5.0: added for consistency + if ( !isset( $radio_station_data['instances']['show-' . $type . '-list'] ) ) { + $radio_station_data['instances']['show-' . $type . '-list'] = 0; + } + $radio_station_data['instances']['show-' . $type . '-list']++; + $instance = $radio_station_data['instances']['show-' . $type . '-list']; + // --- get time and date formats --- $timeformat = get_option( 'time_format' ); $dateformat = get_option( 'date_format' ); + // 2.5.0: fix for latest type argument + if ( 'latest' == $type ) { + $type = 'post'; + if ( !isset( $atts['limit'] ) ) { + $atts['limit'] = 3; + } + } + // --- get shortcode attributes --- $defaults = array( 'show' => false, @@ -1488,7 +1624,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { $show_id = $radio_station_data['show-id']; if ( RADIO_STATION_DEBUG ) { - echo 'Stored Show Posts (' . $type . '):' . print_r( $posts, true ) . ''; + echo 'Stored Show Posts (' . esc_html( $type ) . '):' . esc_html( print_r( $posts, true ) ) . ''; } } else { @@ -1516,18 +1652,23 @@ function radio_station_show_list_shortcode( $type, $atts ) { $args['limit'] = $atts['limit']; } if ( 'post' == $type ) { + // TODO: add atts attribute filtering $posts = radio_station_get_show_posts( $show_id, $args ); } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + // TODO: add atts attribute filtering $posts = radio_station_get_show_playlists( $show_id, $args ); $type = 'playlist'; } elseif ( RADIO_STATION_HOST_SLUG == $type ) { - $posts = apply_filters( 'radio_station_get_show_hosts', false, $show_id, $args ); + // 2.5.0: added shortcode atts as fourth argument + $posts = apply_filters( 'radio_station_get_show_hosts', false, $show_id, $args, $atts ); $type = 'host'; } elseif ( RADIO_STATION_PRODUCER_SLUG == $type ) { - $posts = apply_filters( 'radio_station_get_show_producers', false, $show_id, $args ); + // 2.5.0: added shortcode atts as fourth argument + $posts = apply_filters( 'radio_station_get_show_producers', false, $show_id, $args, $atts ); $type = 'producer'; } elseif ( defined( 'RADIO_STATION_EPISODE_SLUG' ) && ( RADIO_STATION_EPISODE_SLUG == $type ) ) { - $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args ); + // 2.5.0: added shortcode atts as fourth argument + $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args, $atts ); $type = 'episode'; } if ( RADIO_STATION_DEBUG ) { @@ -1543,7 +1684,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { $more = apply_filters( 'radio_station_show_' . $type . '_list_excerpt_more', '[…]' ); // --- show list div --- - $list = '
    '; + $list = '
    ' . "\n"; // --- loop show posts --- $post_pages = 1; @@ -1554,19 +1695,15 @@ function radio_station_show_list_shortcode( $type, $atts ) { $newpage = $firstpage = true; } elseif ( $j == $atts['per_page'] ) { // --- close page div --- - $list .= '
    '; + $list .= '
    ' . "\n"; $newpage = true; - $post_pages ++; + $post_pages++; $j = 0; } if ( $newpage ) { // --- new page div --- - if ( !$firstpage ) { - $hide = ' style="display:none;"'; - } else { - $hide = ''; - } - $list .= '
    '; + $hide = !$firstpage ? ' style="display:none;"' : ''; + $list .= '
    ' . "\n"; } // --- new item div --- @@ -1575,7 +1712,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { $classes[] = 'first-item'; } $class = implode( ' ', $classes ); - $list .= '
    '; + $list .= '
    ' . "\n"; // 2.3.3.9: check if this object is instance of WP_User class if ( is_a( $post, 'WP_User' ) ) { @@ -1586,17 +1723,17 @@ function radio_station_show_list_shortcode( $type, $atts ) { // TODO: add check for user avatar ? - $list .= '
    '; + $list .= '
    ' . "\n"; // --- link to author page --- - $list .= '
    '; $permalink = get_author_posts_url( $user_id ); $title = __( 'View all posts by %s', 'radio-station' ); - $title = sprintf( $title, $user->display_name ); - $list .= ''; - $list .= esc_attr( $user->display_name ); - $list .= ''; - $list .= '
    '; + $title = sprintf( $title, $user->display_name ) . "\n"; + $list .= '' . "\n"; // --- author bio/excerpt --- $userdata = get_user_meta( $user_id ); @@ -1604,24 +1741,32 @@ function radio_station_show_list_shortcode( $type, $atts ) { if ( 'none' == $atts['content'] ) { $list .= ''; } elseif ( 'full' == $atts['content'] ) { - $list .= '
    '; $content = apply_filters( 'radio_station_show_' . $type . '_content', $bio_content, $user_id ); - $list .= $bio_content; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on bio content output + $allowed = radio_station_allowed_html( 'content', 'bio' ); + $list .= wp_kses( $bio_content, $allowed ) . "\n"; + $list .= '
    ' . "\n"; } else { - $list .= '
    '; $permalink = get_author_posts_url( $user_id ); $excerpt = radio_station_trim_excerpt( $bio_content, $length, $more, $permalink ); $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $user_id ); - $list .= $excerpt; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $list .= wp_kses( $excerpt, $allowed ) . "\n"; + $list .= '
    ' . "\n"; } - - $list .= '
    '; + $list .= '
    ' . "\n"; } else { + // 2.5.0: fix to maybe get post object + if ( !is_a( $post, 'WP_Post' ) ) { + $post = get_post( $post, ARRAY_A ); + } + // --- post thumbnail --- if ( $atts['thumbnails'] ) { $thumbnail = false; @@ -1632,21 +1777,26 @@ function radio_station_show_list_shortcode( $type, $atts ) { } $thumbnail = apply_filters( 'radio_station_show_list_archive_avatar', $thumbnail, $post['ID'], $type ); if ( $thumbnail ) { - $list .= '
    ' . $thumbnail . '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on thumbnail output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $thumbnail, $allowed ) . "\n"; + $list .= '
    ' . "\n"; } } - $list .= '
    '; + $list .= '
    ' . "\n"; // --- link to post --- - $list .= '
    '; $permalink = get_permalink( $post['ID'] ); $timestamp = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); $title = __( 'Published on ', 'radio-station' ) . $timestamp; - $list .= ''; - $list .= esc_attr( $post['post_title'] ); - $list .= ''; - $list .= '
    '; + $list .= '' . "\n"; // --- post excerpt --- $post_content = $post['post_content']; @@ -1654,66 +1804,74 @@ function radio_station_show_list_shortcode( $type, $atts ) { if ( 'none' == $atts['content'] ) { $list .= ''; } elseif ( 'full' == $atts['content'] ) { - $list .= '
    '; $content = apply_filters( 'radio_station_show_' . $type . '_content', $post_content, $post_id ); - $list .= $content; - $list .= '
    '; + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on content output + $allowed = radio_station_allowed_html( 'content', 'full' ); + $list .= wp_kses( $content, $allowed ) . "\n"; + $list .= '
    ' . "\n"; } else { - $list .= '
    '; $permalink = get_permalink( $post['ID'] ); if ( !empty( $post['post_excerpt'] ) ) { $excerpt = $post['post_excerpt']; - $excerpt .= ' ' . $more . ''; + // 2.5.0: use esc_html on more anchor text + $excerpt .= ' ' . esc_html( $more ) . '' . "\n"; } else { $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); } $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post_id ); - $list .= $excerpt; - $list .= '
    '; - } - $list .= '
    '; + // 2.5.0: added check excerpt is not empty + if ( '' != $excerpt ) { + $list .= '
    ' . "\n"; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $list .= wp_kses( $excerpt, $allowed ) . "\n"; + $list .= '
    ' . "\n"; + } + } + $list .= '
    ' . "\n"; } // --- close item div --- - $list .= '
    '; + $list .= '
    ' . "\n"; $j ++; } // --- close last page div --- - $list .= '
    '; + $list .= '
    ' . "\n"; // --- list pagination --- // 2.3.3.9: fix to hide left arrow display on load if ( $atts['pagination'] && ( $post_pages > 1 ) ) { - $list .= '

    '; - $list .= '
    '; - $list .= ''; - for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { - if ( 1 == $pagenum ) { - $active = ' active'; - } else { - $active = ''; + $list .= '

    ' . "\n"; + $list .= '
    ' . "\n"; + $list .= '' . "\n"; + for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { + if ( 1 == $pagenum ) { + $active = ' active'; + } else { + $active = ''; + } + $onclick = 'radio_list_page(\'show\', ' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; + $list .= '
    ' . "\n"; + $list .= '' . "\n"; + $list .= esc_html( $pagenum ) . "\n"; + $list .= '' . "\n"; + $list .= '
    ' . "\n"; } - $onclick = 'radio_list_page(\'show\', ' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; - $list .= ''; - } - $list .= '
    '; - $list .= ''; - $list .= '
    '; - $list .= ''; - $list .= ''; - $list .= '
    '; + $list .= '
    ' . "\n"; + $list .= '' . "\n"; + $list .= '
    ' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '
    ' . "\n"; } // --- close list div --- - $list .= ''; + $list .= '' . "\n"; // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -1722,8 +1880,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { add_action( 'wp_footer', 'radio_station_list_pagination_javascript' ); // --- filter and return --- - $list = apply_filters( 'radio_station_show_' . $type . '_list', $list, $atts ); - + $list = apply_filters( 'radio_station_show_' . $type . '_list', $list, $atts, $instance ); return $list; } @@ -1827,25 +1984,44 @@ function radio_station_current_show_shortcode( $atts ) { // --- set widget instance ID --- // 2.3.2: added for AJAX loading - if ( !isset( $radio_station_data['current_show_instance'] ) ) { - $radio_station_data['current_show_instance'] = 0; + if ( !isset( $radio_station_data['instances']['current_show'] ) ) { + $radio_station_data['instances']['current_show'] = 0; } - $radio_station_data['current_show_instance']++; + $radio_station_data['instances']['current_show']++; + $instance = $radio_station_data['instances']['current_show']; $output = ''; + // --- set default time format --- + $time_format = radio_station_get_setting( 'clock_time_format' ); + // 2.3.2: get default AJAX load settings + // 2.5.0: unset empty AJAX attribute to use default $ajax = radio_station_get_setting( 'ajax_widgets', false ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + if ( '' == $atts['ajax'] ) { + unset( $atts['ajax'] ); + } // --- apply filters for dynamic reload value --- - $dynamic = apply_filters( 'radio_station_current_show_dynamic', false, $atts ); + // 2.5.0: added instance as third filter argument + $dynamic = apply_filters( 'radio_station_current_show_dynamic', false, $atts, $instance ); $dynamic = $dynamic ? 1 : 0; // 2.3.3: use plugin setting if time format attribute is empty - if ( isset( $atts['time'] ) && ( '' == $atts['time'] ) ) { + // 2.5.0: fix to update time attribute to time_format + if ( isset( $atts['time'] ) ) { + if ( '' != trim( $atts['time'] ) ) { + $atts['time_format'] = $atts['time']; + } unset( $atts['time'] ); } + + // 2.5.0: change to default_name attribute key + if ( isset( $atts['default_name'] ) ) { + $atts['no_shows'] = $atts['default_name']; + unset( $atts['default_name'] ); + } // --- get shortcode attributes --- // 2.3.0: set default default_name text @@ -1855,31 +2031,37 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.3.8: added show_encore attribute (default 1) // 2.3.3.9: added avatar_size attribute (default thumbnail) // 2.3.3.9: added show_title attribute (default 1) - $time_format = radio_station_get_setting( 'clock_time_format' ); + // 2.5.0: removed show_title attribute (as always needed) + // 2.5.0: change default_name key to no_shows text $defaults = array( // --- legacy options --- 'title' => '', - 'display_hosts' => 0, - 'show_avatar' => 1, - 'show_title' => 1, + 'ajax' => $ajax, + 'dynamic' => $dynamic, + 'no_shows' => '', + 'hide_empty' => 0, + // --- show display options --- 'show_link' => 1, - 'default_name' => '', - 'time' => $time_format, - 'show_sched' => 1, - 'show_playlist' => 1, - 'show_all_sched' => 0, - 'show_desc' => 0, - // --- new options --- - // 'display_producers' => 0, + 'title_position' => 'right', + 'show_avatar' => 1, 'avatar_width' => '', 'avatar_size' => 'thumbnail', - 'title_position' => 'right', + // --- show time display options --- + 'show_sched' => 1, + 'show_all_sched' => 0, + 'countdown' => 0, + 'time_format' => $time_format, + // --- extra display options --- + 'display_hosts' => 0, 'link_hosts' => 0, + // 'display_producers' => 0, + // 'link_producers' => 0, + 'show_desc' => 0, + 'show_playlist' => 1, 'show_encore' => 1, - 'countdown' => 0, - 'ajax' => $ajax, - 'dynamic' => $dynamic, + // --- widget data --- 'widget' => 0, + 'block' => 0, 'id' => '', 'for_time' => 0, ); @@ -1919,7 +2101,7 @@ function radio_station_current_show_shortcode( $atts ) { $time = absint( $parts[0] ) . ':' . absint( $parts[1] ); $for_time = radio_station_to_time( $year . '-' . $month . '-' . $date . ' ' . $time ); $atts['for_time'] = $for_time; - echo ""; + echo ""; } } @@ -1928,28 +2110,30 @@ function radio_station_current_show_shortcode( $atts ) { $ajax = $atts['ajax']; $widget = $atts['widget']; $ajax = apply_filters( 'radio_station_widgets_ajax_override', $ajax, 'current-show', $widget ); - if ( 'on' == $ajax ) { + if ( 'on' === (string) $ajax ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { // --- AJAX load via iframe --- $ajax_url = admin_url( 'admin-ajax.php' ); - $instance = $radio_station_data['current_show_instance']; - $html = '
    '; - $html .= ''; - $html .= ""; + $html = '
    ' . "\n"; + $html .= '' . "\n"; + + // --- shortcode loader script --- + $html .= "" . "\n"; // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -1959,8 +2143,9 @@ function radio_station_current_show_shortcode( $atts ) { } // 2.3.0: maybe set float class and avatar width style + // 2.5.0: use absint and above zero check on avatart_width $widthstyle = $floatclass = ''; - if ( !empty( $atts['avatar_width'] ) ) { + if ( absint( $atts['avatar_width'] ) > 0 ) { $widthstyle = 'style="width:' . esc_attr( $atts['avatar_width'] ) . 'px;"'; } if ( 'right' == $atts['title_position'] ) { @@ -1988,10 +2173,10 @@ function radio_station_current_show_shortcode( $atts ) { if ( $atts['for_time'] ) { $current_shift = radio_station_get_current_show( $atts['for_time'] ); $time = radio_station_get_time( 'datetime', $atts['for_time'] ); - echo ''; - echo 'Current Shift For Time: ' . $atts['for_time'] . ' : ' . $time . PHP_EOL; - echo print_r( $current_shift, true ) . PHP_EOL; - echo ''; + echo '' . "\n"; + echo 'Current Shift For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n"; + echo esc_html( print_r( $current_shift, true ) ) . "\n"; + echo '' . "\n"; } else { $current_shift = radio_station_get_current_show(); } @@ -2001,26 +2186,28 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: add unique id to widget shortcode // 2.3.2: add shortcode wrap class - if ( !isset( $radio_station_data['widgets']['current-show'] ) ) { - $radio_station_data['widgets']['current-show'] = 0; - } else { - $radio_station_data['widgets']['current-show']++; - } - $id = 'current-show-widget-' . $radio_station_data['widgets']['current-show']; - $output .= '
    '; + // 2.5.0: change id key to shortcode NOT widget + // 2.5.0: simplified to use existing instance count + $id = 'current-show-shortcode-' . $instance; + $output .= '
    ' . "\n"; // --- shortcode only title --- if ( !empty( $atts['title'] ) ) { // 2.3.3.9: fix class to not conflict with actual show title - $output .= '

    '; - $output .= esc_html( $atts['title'] ); - $output .= '

    '; + $output .= '

    ' . "\n"; + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

    ' . "\n"; } } - // --- open current show list --- - $output .= '
      '; + // 2.5.0: add countdown class for countdown script targeting + $classes = array( 'current-show-list', 'on-air-list' ); + if ( $atts['countdown'] ) { + $classes[] = 'countdown'; + } + $class_list = implode( ' ', $classes ); + $output .= '
        ' . "\n"; // --- current shift display --- if ( $current_shift ) { @@ -2030,7 +2217,7 @@ function radio_station_current_show_shortcode( $atts ) { // 2.4.0.6: added filter for default time format separator $time_separator = ':'; $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'current-show' ); - if ( 24 == (int) $atts['time'] ) { + if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; @@ -2058,14 +2245,14 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.1: check early for later display if ( $atts['show_sched'] || $atts['show_all_sched'] ) { - $shift_display = '
        '; + $shift_display = '
        ' . "\n"; // --- show times subheading --- // 2.3.0: added for all shift display if ( $atts['show_all_sched'] ) { - $shift_display .= '
        '; - $shift_display .= esc_html( __( 'Show Times', 'radio-station' ) ); - $shift_display .= '
        '; + $shift_display .= '
        ' . "\n"; + $shift_display .= esc_html( __( 'Show Times', 'radio-station' ) ) . "\n"; + $shift_display .= '
        ' . "\n"; } // --- maybe show all shifts --- @@ -2145,68 +2332,69 @@ function radio_station_current_show_shortcode( $atts ) { $current_shift_start = $shift_start_time; $current_shift_end = $shift_end_time; $classes[] = 'current-shift'; - $class = implode( ' ', $classes ); + $classlist = implode( ' ', $classes ); - $current_shift_display = '
        '; - $current_shift_display .= '' . esc_html( $start ) . ''; - $current_shift_display .= '' . esc_html( $separator ) . ''; - $current_shift_display .= '' . esc_html( $end ) . ''; - $current_shift_display .= '
        '; + $current_shift_display = '
        ' . "\n"; + $current_shift_display .= '' . esc_html( $start ) . '' . "\n"; + $current_shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $current_shift_display .= '' . esc_html( $end ) . '' . "\n"; + $current_shift_display .= '
        ' . "\n"; // 2.3.3.9: add show user time div - $current_shift_display .= '
        '; - $current_shift_display .= '['; - $current_shift_display .= '' . esc_html( $separator ) . ''; - $current_shift_display .= ']'; - $current_shift_display .= '
        '; + $current_shift_display .= '
        ' . "\n"; + $current_shift_display .= '[' . "\n"; + $current_shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $current_shift_display .= ']' . "\n"; + $current_shift_display .= '
        ' . "\n"; } - $class = implode( ' ', $classes ); + $classlist = implode( ' ', $classes ); // --- shift display output --- - $shift_display .= '
        '; - if ( in_array( 'current-shift', $classes ) ) { - // (this highlights the current shift item in the full schedule list) - $shift_display .= '
        • '; - } - $shift_display .= '' . esc_html( $start ) . ''; - $shift_display .= '' . esc_html( $separator ) . ''; - $shift_display .= '' . esc_html( $end ) . ''; - - // 2.3.3.9: add show user time div - $shift_display .= '
          '; - $shift_display .= '['; - $shift_display .= '' . esc_html( $separator ) . ''; - $shift_display .= ']'; - $shift_display .= '
          '; + $shift_display .= '
          ' . "\n"; + if ( in_array( 'current-shift', $classes ) ) { + // (this highlights the current shift item in the full schedule list) + $shift_display .= '
          • ' . "\n"; + } + $shift_display .= '' . esc_html( $start ) . '' . "\n"; + $shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $shift_display .= '' . esc_html( $end ) . '' . "\n"; - if ( in_array( 'current-shift', $classes ) ) { - $shift_display .= '
          '; - } - $shift_display .= '
          '; + // 2.3.3.9: add show user time div + $shift_display .= '
          ' . "\n"; + $shift_display .= '[' . "\n"; + $shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $shift_display .= ']' . "\n"; + $shift_display .= '
          '; + + if ( in_array( 'current-shift', $classes ) ) { + $shift_display .= '
        ' . "\n"; + } + $shift_display .= '
        ' . "\n"; } - $shift_display .= '
        '; + $shift_display .= '
        ' . "\n"; } // --- set clear div --- - $html['clear'] = ''; + $html['clear'] = '' . "\n"; // --- set show title output --- // 2.3.3.9: adding show title attribute - if ( $atts['show_title'] ) { - $title = '
        '; - if ( $show_link ) { - $title .= '' . esc_html( $show['name'] ) . ''; - } else { - $title .= esc_html( $show['name'] ); - } - $title .= '
        '; - // 2.3.3.8: added current show title filter - $title = apply_filters( 'radio_station_current_show_title_display', $title, $show_id, $atts ); - if ( ( '' != $title ) && is_string( $title ) ) { - $html['title'] = $title; - } + // 2.5.0: remove show_title attribute (as always needed) + $title = '
        ' . "\n"; + if ( $show_link ) { + $title .= '' . "\n"; + } + $title .= esc_html( $show['name'] ) . "\n"; + if ( $show_link ) { + $title .= '' . "\n"; + } + $title .= '
        ' . "\n"; + // 2.3.3.8: added current show title filter + $title = apply_filters( 'radio_station_current_show_title_display', $title, $show_id, $atts ); + if ( ( '' != $title ) && is_string( $title ) ) { + $html['title'] = $title; } // --- show avatar --- @@ -2221,13 +2409,17 @@ function radio_station_current_show_shortcode( $atts ) { $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { - $avatar = '
        '; + $avatar = ''; + $avatar .= '
        ' . "\n"; } // 2.3.3.8: added avatar display filter // 2.3.3.9: moved filter outside of conditional @@ -2249,8 +2441,7 @@ function radio_station_current_show_shortcode( $atts ) { } if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $hosts = '
        '; - + $hosts = '
        ' . "\n"; $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; @@ -2312,10 +2503,11 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.3.8: added shortcode attribute check (with default 1) if ( $atts['show_encore'] ) { $encore = ''; + // note: this is set via the current show shift if ( isset( $show['encore'] ) && ( $show['encore'] ) ) { - $encore = '
        '; - $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); - $encore .= '
        '; + $encore = '
        ' . "\n"; + $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ) . "\n"; + $encore .= '
        ' . "\n"; } // 2.3.3.8: added encore display filter $encore = apply_filters( 'radio_station_current_show_encore_display', $encore, $show_id, $atts ); @@ -2330,14 +2522,16 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: use new function to get current playlist $current_playlist = radio_station_get_now_playing(); if ( RADIO_STATION_DEBUG ) { - $output .= 'Current Playlist: ' . print_r( $current_playlist, true ) . ''; + $output .= 'Current Playlist: ' . esc_html( print_r( $current_playlist, true ) ) . ''; } + // 2.5.0: set empty playlist for undefined variable warning + $playlist = ''; if ( $current_playlist && isset( $current_playlist['playlist_url'] ) ) { - $playlist = ''; + $playlist = '' . "\n"; } // 2.3.3.8: added playlist diplay filter $playlist = apply_filters( 'radio_station_current_show_playlist_display', $playlist, $show_id, $atts ); @@ -2348,7 +2542,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- countdown timer display --- if ( isset( $current_shift_end ) && $atts['countdown'] ) { - $html['countdown'] = '
        '; + $html['countdown'] = '
        ' . "\n"; } // --- show description --- @@ -2361,14 +2555,15 @@ function radio_station_current_show_shortcode( $atts ) { // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { - $excerpt = ""; $excerpt .= $show_post->post_excerpt; - $excerpt .= ' ' . $more . ''; + // 2.5.0: added esc_html to more anchor text + $excerpt .= ' ' . esc_html( $more ) . ''; + // $excerpt .= ""; } else { - $excerpt = ""; - $excerpt .= ""; + $excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); + // $excerpt . = ""; + // $excerpt .= ""; // $excerpt .= ""; - $excerpt .= radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } // --- filter excerpt by context --- @@ -2382,9 +2577,11 @@ function radio_station_current_show_shortcode( $atts ) { // --- set description --- $description = ''; if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { - $description = '
        '; - $description .= $excerpt; - $description .= '
        '; + $description = '
        ' . "\n"; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $description .= wp_kses( $excerpt, $allowed ) . "\n"; + $description .= '
        ' . "\n"; } $description = apply_filters( 'radio_station_current_show_description_display', $description, $show_id, $atts ); if ( ( '' != $description ) && is_string( $description ) ) { @@ -2406,7 +2603,7 @@ function radio_station_current_show_shortcode( $atts ) { $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show_id, $atts ); // --- open current show list item --- - $output .= '
      • '; + $output .= '
      • ' . "\n"; // --- filter display section order --- // 2.3.1: added filter for section order display @@ -2423,71 +2620,93 @@ function radio_station_current_show_shortcode( $atts ) { } // --- close current show list item --- - $output .= '
      • '; + $output .= '' . "\n"; } else { + // 2.5.0: allow for hiding when empty (with filter override) + if ( $atts['hide_empty'] ) { + $output = apply_filters( 'radio_station_current_show_shortcode', '', $atts, $instance ); + return $output; + } + // --- no current show shift display --- - $output .= '
      • '; - if ( !empty( $atts['default_name'] ) ) { - $no_current_show = esc_html( $atts['default_name'] ); - } else { - $no_current_show = esc_html( __( 'No Show currently scheduled.', 'radio-station') ); + // 2.5.0: change attribute key from default_name + // 2.5.0: remove unneeded esc_html from no shows text + // 2.5.0: allow for zero value for no text + if ( '0' != $atts['no_shows'] ) { + if ( '' != $atts['no_shows'] ) { + $no_current_show = $atts['no_shows']; + } else { + $no_current_show = __( 'No Show currently scheduled.', 'radio-station' ); + } + // 2.3.1: add filter for no current shows text + $no_current_show = apply_filters( 'radio_station_no_current_show_text', $no_current_show, $atts ); + + $output .= '
      • ' . "\n"; + // 2.5.0: use wp_kses on message output + $allowed = radio_station_allowed_html( 'message', 'no-shows' ); + $output .= wp_kses( $no_current_show, $allowed ) . "\n"; + $output .= '
      • ' . "\n"; } - // 2.3.1: add filter for no current shows text - $no_current_show = apply_filters( 'radio_station_no_current_show_text', $no_current_show, $atts ); - $output .= $no_current_show; - $output .= ''; // --- countdown timer display --- // 2.3.3.8: add countdown timer div regardless of no current show // (so timer can update when a current show starts) if ( $atts['countdown'] ) { - $output .= '
      • '; + $output .= '
      • ' . "\n"; } } - // --- close current show list --- - $output .= '
      '; - // --- countdown timers --- if ( isset( $current_shift_end ) && ( $atts['countdown'] || $atts['dynamic'] ) ) { - // 2.3.3.9: output current time override - if ( isset( $atts['for_time'] ) ) { - $output .= ''; - } + // 2.5.0: wrap countdown/dynamic data in list item + $output .= '
    • ' . "\n"; - // --- hidden inputs for current shift time --- - $output .= ''; + // 2.3.3.9: output current time override + if ( isset( $atts['for_time'] ) ) { + $output .= '' . "\n"; + } - if ( RADIO_STATION_DEBUG ) { - $output .= ''; - $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . esc_attr( $now ) . ')' . PHP_EOL; - $output .= 'Shift Start Time: ' . date( 'Y-m-d H:i:s', $current_shift_start ) . ' (' . esc_attr( $current_shift_start ) . ')' . PHP_EOL; - $output .= 'Shift End Time: ' . date( 'Y-m-d H:i:s', $current_shift_end ) . ' (' . esc_attr( $current_shift_end ) . ')' . PHP_EOL; - $output .= 'Remaining: ' . ( $current_shift_end - $now ) . PHP_EOL; - $output .= ''; - } + // --- hidden inputs for current shift time --- + $output .= '' . "\n"; - // --- for dynamic reloading --- - if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-show', $atts, $current_shift_end ); - if ( $dynamic ) { - $output .= $dynamic; + if ( RADIO_STATION_DEBUG ) { + $output .= ''; + $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . esc_attr( $now ) . ')' . PHP_EOL; + $output .= 'Shift Start Time: ' . date( 'Y-m-d H:i:s', $current_shift_start ) . ' (' . esc_attr( $current_shift_start ) . ')' . PHP_EOL; + $output .= 'Shift End Time: ' . date( 'Y-m-d H:i:s', $current_shift_end ) . ' (' . esc_attr( $current_shift_end ) . ')' . PHP_EOL; + $output .= 'Remaining: ' . ( $current_shift_end - $now ) . PHP_EOL; + $output .= ''; } - } + + // --- for dynamic reloading --- + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-show', $atts, $current_shift_end ); + if ( $dynamic ) { + $output .= $dynamic; + } + } + + $output .= '
    • ' . "\n"; } + // --- close current show list --- + $output .= '
    ' . "\n"; + // --- close shortcode div wrapper --- if ( !$atts['widget'] ) { - $output .= '
    '; + $output .= '
    ' . "\n"; } // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // --- filter and return --- + // 2.5.0: added missing shortcode output filter + $output = apply_filters( 'radio_station_current_show_shortcode', $output, $atts, $instance ); return $output; } @@ -2507,9 +2726,9 @@ function radio_station_current_show() { } // --- output widget contents --- - echo '
    '; - echo radio_station_current_show_shortcode( $atts ); - echo '
    '; + echo '
    ' . "\n"; + echo radio_station_current_show_shortcode( $atts ); + echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { @@ -2551,65 +2770,92 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- set widget instance ID --- // 2.3.2: added for AJAX loading - if ( !isset( $radio_station_data['upcoming_shows_instance'] ) ) { - $radio_station_data['upcoming_shows_instance'] = 0; + if ( !isset( $radio_station_data['instances']['upcoming_shows'] ) ) { + $radio_station_data['instances']['upcoming_shows'] = 0; } - $radio_station_data['upcoming_shows_instance']++; + $radio_station_data['instances']['upcoming_shows']++; + $instance = $radio_station_data['instances']['upcoming_shows']; $output = ''; + // --- set default time format --- + $time_format = radio_station_get_setting( 'clock_time_format' ); + // 2.3.2: get default AJAX load settings - $ajax = radio_station_get_setting( 'ajax_widgets' ); - $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - $dynamic = apply_filters( 'radio_station_upcomins_shows_dynamic', false, $atts ); + // 2.5.0: unset empty AJAX attribute to use default + $ajax = radio_station_get_setting( 'ajax_widgets', false ); + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + if ( '' == $atts['ajax'] ) { + unset( $atts['ajax'] ); + } + + // --- check for dynamic setting --- + // 2.5.0: fix typo in filter name (radio_station_upcomins_shows_dynamic) + // 2.5.0: added instance as third filter argument + $dynamic = apply_filters( 'radio_station_upcoming_shows_dynamic', false, $atts, $instance ); $dynamic = $dynamic ? 1 : 0; // 2.3.3: use plugin setting if time format attribute is empty - if ( isset( $atts['time'] ) && ( '' == $atts['time'] ) ) { + // 2.5.0: fix to update time attribute to time_format + if ( isset( $atts['time'] ) ) { + if ( '' != trim( $atts['time'] ) ) { + $atts['time_format'] = $atts['time']; + } unset( $atts['time'] ); } + // 2.3.0: convert old attributes for DJs to hosts + if ( isset( $atts['display_djs'] ) && !isset( $atts['display_hosts'] ) ) { + $atts['display_hosts'] = $atts['display_djs']; + unset( $atts['display_djs'] ); + } + if ( isset( $atts['link_djs'] ) && !isset( $atts['link_hosts'] ) ) { + $atts['link_hosts'] = $atts['link_djs']; + unset( $atts['link_djs'] ); + } + + // 2.5.0: change to default_name attribute key + if ( isset( $atts['default_name'] ) ) { + $atts['no_shows'] = $atts['default_name']; + unset( $atts['default_name'] ); + } + // 2.3.0: set default time format to plugin setting // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute // 2.3.3.8: added show_encore attribute (default 1) // 2.3.3.9: added show_title attribute (default 1) - $time_format = radio_station_get_setting( 'clock_time_format' ); + // 2.5.0: change default_name key to no_shows $defaults = array( - // --- legacy options --- + // --- widget display options --- 'title' => '', 'limit' => 1, - 'show_title' => 1, - 'show_avatar' => 0, + 'ajax' => $ajax, + 'dynamic' => $dynamic, + 'no_shows' => '', + 'hide_empty' => 0, + // --- show display options --- 'show_link' => 0, - 'time' => $time_format, + 'title_position' => 'right', + 'show_avatar' => 0, + 'avatar_size' => 'thumbnail', + 'avatar_width' => '', + // --- show time display --- 'show_sched' => 1, - 'default_name' => '', - // --- new options --- - // 'display_producers' => 0, - // 'show_desc' => 0, + 'countdown' => 0, + 'time_format' => $time_format, + // --- extra display options --- 'display_hosts' => 0, - 'show_encore' => 1, 'link_hosts' => 0, - 'avatar_width' => '', - 'avatar_size' => 'thumbnail', - 'title_position' => 'right', - 'countdown' => 0, - 'ajax' => $ajax, - 'dynamic' => $dynamic, + // 'display_producers' => 0, + // 'list_producers' => 0, + 'show_encore' => 1, + // --- instance data --- 'widget' => 0, + 'block' => 0, 'id' => '', 'for_time' => 0, ); - // 2.3.0: convert old attributes for DJs to hosts - if ( isset( $atts['display_djs'] ) && !isset( $atts['display_hosts'] ) ) { - $atts['display_hosts'] = $atts['display_djs']; - unset( $atts['display_djs'] ); - } - if ( isset( $atts['link_djs'] ) && !isset( $atts['link_hosts'] ) ) { - $atts['link_hosts'] = $atts['link_djs']; - unset( $atts['link_djs'] ); - } // 2.3.0: renamed shortcode identifier to upcoming-shows $atts = shortcode_atts( $defaults, $atts, 'upcoming-shows' ); @@ -2637,7 +2883,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $time = absint( $parts[0] ) . ':' . absint( $parts[1] ); $for_time = radio_station_to_time( $year . '-' . $month . '-' . $date . ' ' . $time ); $atts['for_time'] = $for_time; - echo ""; + echo ""; } } @@ -2651,27 +2897,31 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- AJAX load via iframe --- $ajax_url = admin_url( 'admin-ajax.php' ); - $instance = $radio_station_data['upcoming_shows_instance']; - $html = '
    '; - $html .= ''; - $html .= ""; + $html = '
    ' . "\n"; + $html .= '' . "\n"; + + // --- shortcode loader script --- + $html .= "" . "\n"; // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // --- filter and return --- + $html = apply_filters( 'radio_station_upcoming_shows_shortcode_ajax', $html, $atts, $instance ); return $html; } } @@ -2697,7 +2947,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $shows = radio_station_get_next_shows( $atts['limit'] ); } if ( RADIO_STATION_DEBUG ) { - $output .= 'Upcoming Shows: ' . print_r( $shows, true ) . ''; + $output .= 'Upcoming Shows: ' . esc_html( print_r( $shows, true ) ) . ''; } // --- open shortcode only wrapper --- @@ -2705,24 +2955,27 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: add unique id to widget shortcode // 2.3.2: add shortcode wrap class - if ( !isset( $radio_station_data['widgets']['upcoming-shows'] ) ) { - $radio_station_data['widgets']['upcoming-shows'] = 0; - } else { - $radio_station_data['widgets']['upcoming-shows']++; - } - $id = 'upcoming-shows-widget-' . $radio_station_data['widgets']['upcoming-shows']; - $output .= '
    '; + // 2.5.0: change id key to shortcodes NOT widget + // 2.5.0: simplified to use existing instance count + $id = 'upcoming-shows-shortcode-' . $instance; + $output .= '
    ' . "\n"; // --- maybe output shortcode title --- if ( !empty( $atts['title'] ) ) { - $output .= '

    '; - $output .= esc_html( $atts['title'] ); - $output .= '

    '; + $output .= '

    ' . "\n"; + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

    ' . "\n"; } } // --- open upcoming show list --- - $output .= '
      '; + // 2.5.0: added countdown class for countdown script targeting + $classes = array( 'upcoming-shows-list', 'on-air-upcoming-list' ); + if ( $atts['countdown'] ) { + $classes[] = 'countdown'; + } + $class_list = implode( ' ', $classes ); + $output .= '
        ' . "\n"; // --- shows upcoming output --- if ( $shows ) { @@ -2743,7 +2996,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.4.0.6: added filter for default time format separator $time_separator = ':'; $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'upcoming-shows' ); - if ( 24 == (int) $atts['time'] ) { + if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; @@ -2843,45 +3096,46 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $end = radio_station_translate_time( $end ); // 2.4.0.6: use filtered shift separator - $separator = ' - '; + $separator = ' - '; $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'upcoming-shows' ); // --- set shift display output --- $shift_display = '
        '; - $shift_display .= '
        '; - $shift_display .= '' . esc_html( $start ) . ''; - $shift_display .= '' . esc_html( $separator ) . ''; - $shift_display .= '' . esc_html( $end ) . ''; + $shift_display .= '
        ' . "\n"; + $shift_display .= '' . esc_html( $start ) . '' . "\n"; + $shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $shift_display .= '' . esc_html( $end ) . '' . "\n"; $shift_display .= '
        '; // 2.3.3.9: add empty user time div - $shift_display .= '
        '; - $shift_display .= '['; - $shift_display .= '' . esc_html( $separator ) . ''; - $shift_display .= ']'; - $shift_display .= '
        '; - $shift_display .= '
        '; + $shift_display .= '
        ' . "\n"; + $shift_display .= '[' . "\n"; + $shift_display .= '' . esc_html( $separator ) . '' . "\n"; + $shift_display .= ']' . "\n"; + $shift_display .= '
        ' . "\n"; + $shift_display .= '
        ' . "\n"; if ( RADIO_STATION_DEBUG ) { - $shift_display .= 'Upcoming Shift: ' . print_r( $shift, true ) . ''; + $shift_display .= 'Upcoming Shift: ' . esc_html( print_r( $shift, true ) ) . ''; } } // --- set clear div --- - $html['clear'] = ''; + $html['clear'] = '' . "\n"; // --- set show title --- // 2.3.3.9: added attribute for show title display - if ( $atts['show_title'] ) { - $title = '
        '; - if ( $show_link ) { - $title .= '' . esc_html( $show['name'] ) . ''; - } else { - $title .= esc_html( $show['name'] ); - } - $title .= '
        '; - $title = apply_filters( 'radio_station_upcoming_show_title_display', $title, $show_id, $atts ); - if ( ( '' != $title ) && is_string( $title ) ) { - $html['title'] = $title; - } + // 2.5.0: removed show_title attribute as always needed + $title = '
        ' . "\n"; + if ( $show_link ) { + $title .= '' . "\n"; + } + $title .= esc_html( $show['name'] ) . "\n"; + if ( $show_link ) { + $title .= '' . "\n"; + } + $title .= '
        ' . "\n"; + $title = apply_filters( 'radio_station_upcoming_show_title_display', $title, $show_id, $atts ); + if ( ( '' != $title ) && is_string( $title ) ) { + $html['title'] = $title; } // --- set show avatar --- @@ -2896,15 +3150,17 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); $show_avatar = apply_filters( 'radio_station_upcoming_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { - $avatar = '' . "\n"; } $avatar = apply_filters( 'radio_station_upcoming_show_avatar_display', $avatar, $show_id, $atts ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { @@ -2924,7 +3180,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $hosts = '
        '; + $hosts = '
        ' . "\n"; $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; @@ -2967,11 +3223,13 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $hosts .= ', '; } } - $hosts .= '
        '; + $hosts .= '
        ' . "\n"; } $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show_id, $atts ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { - $html['hosts'] = $hosts; + // 2.5.0: added wp_kses to hosts display output + $allowed = radio_station_allowed_html( 'content', 'hosts' ); + $html['hosts'] = wp_kses( $hosts, $allowed ); } } } @@ -2982,19 +3240,21 @@ function radio_station_upcoming_shows_shortcode( $atts ) { if ( $atts['show_encore'] ) { $encore = ''; if ( isset( $show['encore'] ) && ( 'on' == $show['encore'] ) ) { - $encore = '
        '; - $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); - $encore .= '
        '; + $encore = '
        ' . "\n"; + $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ) . "\n"; + $encore .= '
        ' . "\n"; } $encore = apply_filters( 'radio_station_upcoming_show_encore_display', $encore, $show_id, $atts ); if ( ( '' != $encore ) && is_string( $encore ) ) { - $html['encore'] = $encore; + // 2.5.0: added wp_kses to encore display output + $allowed = radio_station_allowed_html( 'content', 'encore' ); + $html['encore'] = wp_kses( $encore, $allowed ); } } // --- set countdown timer --- if ( ( 0 == $i ) && isset( $next_start_time ) && $atts['countdown'] ) { - $html['countdown'] = '
        '; + $html['countdown'] = '
        ' . "\n"; } // --- set show schedule --- @@ -3010,7 +3270,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $html['custom'] = apply_filters( 'radio_station_upcoming_shows_custom_display', '', $show_id, $atts ); // --- open upcoming show list item --- - $output .= '
      • '; + $output .= '
      • ' . "\n"; // --- add output according to section order --- // 2.3.3.8: moved section order filter out of show shift loop @@ -3021,66 +3281,86 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } // --- close upcoming show list item --- - $output .= '
      • '; + $output .= '' . "\n"; } } else { + // 2.5.0: allow for hiding when empty (with filter override) + if ( $atts['hide_empty'] ) { + $output = apply_filters( 'radio_station_upcoming_shows_shortcode', '', $atts, $instance ); + return $output; + } + // --- no shows upcoming --- // note: no countdown display added as no upcoming shows found - $output .= '
      • '; - if ( ! empty( $atts['default_name'] ) ) { - $no_upcoming_shows = esc_html( $atts['default_name'] ); - } else { - $no_upcoming_shows = esc_html( __( 'No Upcoming Shows Scheduled.', 'radio-station' ) ); + // 2.5.0: change default_name key to no_shows_text + // 2.5.0: move esc_html to output line + // 2.5.0: allow for zero value for no text + if ( '0' != $atts['no_shows'] ) { + if ( '' != $atts['no_shows'] ) { + $no_upcoming_shows = $atts['no_shows']; + } else { + $no_upcoming_shows = __( 'No Upcoming Shows Scheduled.', 'radio-station' ); + } + // 2.3.1: add filter for no current shows text + $no_upcoming_shows = apply_filters( 'radio_station_no_upcoming_shows_text', $no_upcoming_shows, $atts ); + + $output .= '
      • ' . "\n"; + // 2.5.0: use wp_kses on message output + $allowed = radio_station_allowed_html( 'message', 'no-shows' ); + $output .= wp_kses( $no_upcoming_shows, $allowed ); + $output .= '
      • ' . "\n"; } - // 2.3.1: add filter for no current shows text - $no_upcoming_shows = apply_filters( 'radio_station_no_upcoming_shows_text', $no_upcoming_shows, $atts ); - $output .= $no_upcoming_shows; - $output .= ''; - } - // --- close upcoming shows list --- - $output .= '
      '; - // --- countdown timer inputs --- // 2.3.0: added for countdowns if ( isset( $next_start_time ) && ( $atts['countdown'] || $atts['dynamic'] ) ) { - // 2.3.3.9: output current time override - if ( isset( $atts['for_time'] ) ) { - $output .= ''; - } + // 2.5.0: wrap data in hidden list item for script + $output .= '
    • ' . "\n"; - // --- hidden input for next start time --- - $output .= ''; - if ( RADIO_STATION_DEBUG ) { - $output .= ''; - $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . $now . ')' . PHP_EOL; - $output .= 'Next Start Time: ' . date('y-m-d H:i:s', $next_start_time ) . ' (' . $next_start_time . ')' . PHP_EOL; - $output .= 'Next End Time: ' . date( 'y-m-d H:i:s', $next_end_time ) . ' (' . $next_end_time . ')' . PHP_EOL; - $output .= 'Starting in: ' . ( $next_start_time - $now ) . PHP_EOL; - $output .= ''; - } + // 2.3.3.9: output current time override + if ( isset( $atts['for_time'] ) ) { + $output .= ''; + } - // --- for dynamic reloading --- - if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'upcoming-shows', $atts, $next_start_time ); - if ( $dynamic ) { - $output .= $dynamic; + // --- hidden input for next start time --- + $output .= '' . "\n"; + if ( RADIO_STATION_DEBUG ) { + $output .= ''; + $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . $now . ')' . PHP_EOL; + $output .= 'Next Start Time: ' . date('y-m-d H:i:s', $next_start_time ) . ' (' . $next_start_time . ')' . PHP_EOL; + $output .= 'Next End Time: ' . date( 'y-m-d H:i:s', $next_end_time ) . ' (' . $next_end_time . ')' . PHP_EOL; + $output .= 'Starting in: ' . ( $next_start_time - $now ) . PHP_EOL; + $output .= ''; } - } + + // --- for dynamic reloading --- + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'upcoming-shows', $atts, $next_start_time ); + if ( $dynamic ) { + $output .= $dynamic; + } + } + + $output .= '
    • ' . "\n"; } + // --- close upcoming shows list --- + $output .= '
    ' . "\n"; + // --- close shortcode only wrapper --- if ( !$atts['widget'] ) { - $output .= '
    '; + $output .= '
    ' . "\n"; } // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // 2.5.0: added missing output filter + $output = apply_filters( 'radio_station_upcoming_shows_shortcode', $output, $atts, $instance ); return $output; } @@ -3095,24 +3375,24 @@ function radio_station_upcoming_shows() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'upcoming-shows' ); if ( RADIO_STATION_DEBUG ) { - echo "Upcoming Shows Shortcode Attributes: " . print_r( $atts, true ); + echo "Upcoming Shows Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); } // --- output widget contents --- - echo '
    '; - echo radio_station_upcoming_shows_shortcode( $atts ); - echo '
    '; + echo '
    ' . "\n"; + echo radio_station_upcoming_shows_shortcode( $atts ); + echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { // --- send to parent window --- - $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; - $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; // --- restart countdowns --- if ( $atts['countdown'] ) { - $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; + $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; } } @@ -3122,7 +3402,7 @@ function radio_station_upcoming_shows() { // --- output javascript if ( '' != $js ) { - echo ""; + echo "" . "\n"; } exit; @@ -3141,38 +3421,52 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- set widget instance ID --- // 2.3.2: added for AJAX loading - if ( !isset( $radio_station_data['current_playlist_instance'] ) ) { - $radio_station_data['current_playlist_instance'] = 0; + if ( !isset( $radio_station_data['instances']['current_playlist'] ) ) { + $radio_station_data['instances']['current_playlist'] = 0; } - $radio_station_data['current_playlist_instance']++; + $radio_station_data['instances']['current_playlist']++; + $instance = $radio_station_data['instances']['current_playlist']; $output = ''; // 2.3.2: get default AJAX load settings - $ajax = radio_station_get_setting( 'ajax_widgets' ); - $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', false, $atts ); + // 2.5.0: unset empty AJAX attribute to use default + $ajax = radio_station_get_setting( 'ajax_widgets', false ); + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + if ( '' == $atts['ajax'] ) { + unset( $atts['ajax'] ); + } + + // --- check for dynamic setting --- + // 2.5.0: added instance as third filter argument + $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', false, $atts, $instance ); $dynamic = $dynamic ? 1 : 0; // --- get shortcode attributes --- // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute + // 2.5.0: added no_playlist text attribute $defaults = array( - // --- legacy options --- - 'title' => '', - 'artist' => 1, - 'song' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0, - // --- new options --- - 'link' => 1, - 'countdown' => 0, - 'ajax' => $ajax, - 'dynamic' => $dynamic, - 'widget' => 0, - 'id' => '', - 'for_time' => 0, + // --- widget display options --- + 'title' => '', + 'ajax' => $ajax, + 'dynamic' => $dynamic, + 'hide_empty' => 0, + // --- playlist display options --- + 'link' => 1, + 'no_playlist' => '', + 'countdown' => 0, + // --- track display options --- + 'song' => 1, + 'artist' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, + // --- widget data --- + 'widget' => 0, + 'block' => 0, + 'id' => '', + 'for_time' => 0, ); // 2.3.0: renamed shortcode identifier to current-playlist @@ -3185,8 +3479,9 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.3: added current time override for manual testing if ( isset( $_GET['date'] ) && isset( $_GET['time'] ) ) { - $date = trim( $_GET['date'] ); - $time = trim( $_GET['time'] ); + // 2.5.0: added sanitize_text_field to date/time + $date = trim( sanitize_text_field( $_GET['date'] ) ); + $time = trim( sanitize_text_field( $_GET['time'] ) ); if ( isset( $_GET['month'] ) ) { $month = absint( trim( $_GET['month'] ) ); } else { @@ -3202,7 +3497,7 @@ function radio_station_current_playlist_shortcode( $atts ) { $time = absint( $parts[0] ) . ':' . absint( $parts[1] ); $for_time = radio_station_to_time( $year . '-' . $month . '-' . $date . ' ' . $time ); $atts['for_time'] = $for_time; - echo ""; + echo ""; } } @@ -3216,24 +3511,27 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- AJAX load via iframe --- $ajax_url = admin_url( 'admin-ajax.php' ); - $instance = $radio_station_data['current_playlist_instance']; - $html = '
    '; - $html .= ''; - $html .= ""; + $html = '
    ' . "\n"; + $html .= '' . "\n"; + + // --- shortcode script loader --- + $html .= "" . "\n"; // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // 2.5.0: added filter for shortcode output + $html = apply_filters( 'radio_station_current_playlist_shortcode_ajax', $html, $atts, $instance ); return $html; } } @@ -3243,8 +3541,8 @@ function radio_station_current_playlist_shortcode( $atts ) { $playlist = radio_station_get_now_playing( $atts['for_time'] ); $time = radio_station_get_time( 'datetime', $atts['for_time'] ); echo ''; - echo 'Current Playlist For Time: ' . $atts['for_time'] . ' : ' . $time . PHP_EOL; - echo print_r( $playlist, true ); + echo 'Current Playlist For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n";; + echo esc_html( print_r( $playlist, true ) ); echo ''; } else { $playlist = radio_station_get_now_playing(); @@ -3256,18 +3554,22 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.0: add unique id to widget shortcode // 2.3.2: fix to shortcode classes // 2.3.2: add shortcode wrap class - if ( !isset( $radio_station_data['widgets']['current-playlist'] ) ) { - $radio_station_data['widgets']['current-playlist'] = 0; - } else { - $radio_station_data['widgets']['current-playlist']++; + // 2.5.0: change data key to shortcodes NOT widget + // 2.5.0: simplify instances to start at 1 + if ( !isset( $radio_station_data['shortcodes']['current-playlist'] ) ) { + $radio_station_data['shortcodes']['current-playlist'] = 0; } - $id = 'show-playlist-widget-' . $radio_station_data['widgets']['current-playlist']; - $output .= '
    '; + $radio_station_data['shortcodes']['current-playlist']++; + $id = 'show-playlist-shortcode-' . $radio_station_data['widgets']['current-playlist']; + $output .= '
    ' . "\n"; // --- shortcode title --- if ( !empty( $atts['title'] ) ) { // 2.3.0: added title class for shortcode - $output .= '

    ' . esc_attr( $atts['title'] ) . '

    '; + $output .= '

    ' . "\n"; + // 2.5.0: use esc_html instead of esc_attr + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

    ' . "\n"; } } @@ -3280,9 +3582,9 @@ function radio_station_current_playlist_shortcode( $atts ) { if ( $atts['countdown'] || $atts['dynamic'] ) { $html['countdown'] = ''; - // if ( RADIO_STATION_DEBUG ) { - echo 'Playlist: ' . print_r( $playlist, true ) . ''; - // } + if ( RADIO_STATION_DEBUG ) { + echo 'Playlist: ' . esc_html( print_r( $playlist, true ) ) . '' . "\n"; + } // 2.3.1: added check for playlist shifts value if ( isset( $playlist['shifts'] ) && is_array( $playlist['shifts'] ) && ( count( $playlist['shifts'] ) > 0 ) ) { @@ -3298,7 +3600,6 @@ function radio_station_current_playlist_shortcode( $atts ) { $yesterday = radio_station_get_previous_day( $today ); $weekdays = radio_station_get_schedule_weekdays( $yesterday ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - // echo 'Now: ' . $now . ' : ' . radio_station_get_time( 'datetime', $now ) . '
    '; // --- loop shifts --- foreach ( $playlist['shifts'] as $shift_id => $shift ) { @@ -3342,11 +3643,12 @@ function radio_station_current_playlist_shortcode( $atts ) { } // --- check currently playing show time --- + // 2.5.0: removed duplicate now declarations if ( $atts['for_time'] ) { - $now = $atts['for_time']; - $html['countdown'] .= ''; + // $now = $atts['for_time']; + $html['countdown'] .= '' . "\n"; } else { - $now = radio_station_get_now(); + // $now = radio_station_get_now(); } // echo 'Shift Start: ' . $shift_start_time . '(' . radio_station_get_time( 'datetime', $shift_start_time ) . ')
    '; // echo 'Shift End: ' . $shift_end_time . '(' . radio_station_get_time( 'datetime', $shift_end_time ) . ')
    '; @@ -3357,11 +3659,11 @@ function radio_station_current_playlist_shortcode( $atts ) { // echo "^^^ NOW PLAYING ^^^"; // --- hidden input for playlist end time --- - $html['countdown'] .= ''; + $html['countdown'] .= '' . "\n"; // --- for countdown timer display --- if ( $atts['countdown'] ) { - $html['countdown'] .= '
    '; + $html['countdown'] .= '
    ' . "\n"; } // --- for dynamic reloading --- @@ -3383,7 +3685,7 @@ function radio_station_current_playlist_shortcode( $atts ) { if ( $playlist && isset( $playlist['tracks'] ) && is_array( $playlist['tracks'] ) && ( count( $playlist['tracks'] ) > 0 ) ) { // 2.3.0: split div wrapper from track wrapper - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; // --- loop playlist tracks --- // 2.3.0: loop all instead of just latest @@ -3402,60 +3704,60 @@ function radio_station_current_playlist_shortcode( $atts ) { $class .= ' played'; } - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; // 2.2.3: convert span tags to div tags // 2.2.4: check value keys are set before outputting if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Song', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ); - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; + $tracks .= esc_html( __( 'Song', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ) . "\n"; + $tracks .= '
    ' . "\n"; } // 2.2.7: add label prefixes to now playing data if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ); - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; + $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ) . "\n"; + $tracks .= '
    ' . "\n"; } if ( $atts['album'] && !empty( $track['playlist_entry_album'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Album', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ); - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; + $tracks .= esc_html( __( 'Album', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ) . "\n"; + $tracks .= '
    ' . "\n"; } if ( $atts['label'] && !empty( $track['playlist_entry_label'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Label', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ); - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; + $tracks .= esc_html( __( 'Label', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ) . "\n"; + $tracks .= '
    ' . "\n"; } if ( $atts['comments'] && !empty( $track['playlist_entry_comments'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ); - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; + $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ) . "\n"; + $tracks .= '
    ' . "\n"; } - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; } - $tracks .= '
    '; + $tracks .= '
    ' . "\n"; // --- playlist permalink --- // 2.3.3.8 added playlist_link shortcode attribute (default 1) if ( $atts['link'] ) { $link = ''; if ( isset( $playlist['playlist_permalink'] ) ) { - $link = ''; + $link = '' . "\n"; } // 2.3.3.8: added playlist link display filter $link = apply_filters( 'radio_station_current_playlist_link_display', $link, $playlist, $atts ); @@ -3466,21 +3768,38 @@ function radio_station_current_playlist_shortcode( $atts ) { } else { + // 2.5.0: allow for hiding when empty (with filter override) + if ( $atts['hide_empty'] ) { + $output = apply_filters( 'radio_station_current_playlist_shortcode', '', $atts, $instance ); + return $output; + } + // 2.2.3: added missing translation wrapper // 2.3.0: added no playlist class // 2.3.1: add filter for no playlist text // 2.3.3.8: fix to unclosed double quote on class attribute - $no_playlist = '
    '; - $no_current_playlist = esc_html( __( 'No Current Playlist available.', 'radio-station' ) ); - $no_current_playlist = apply_filters( 'radio_station_no_current_playlist_text', $no_current_playlist, $atts ); - $no_playlist .= $no_current_playlist; - $no_playlist .= '
    '; - - // 2.3.3.8: added no playlist display filter - // 2.3.3.9: assign to tracks key for possible output re-ordering - $no_playlist = apply_filters( 'radio_station_current_playlist_no_playlist_display', $no_playlist, $atts ); - if ( ( '' != $no_playlist ) && is_string( $no_playlist ) ) { - $html['tracks'] = $no_playlist; + // 2.5.0: add shortcode attribute for no playlist text + // 2.5.0: allow for zero value for no text + if ( '0' != $atts['no_playlist'] ) { + if ( '' != $atts['no_playlist'] ) { + $no_current_playlist = $atts['no_playlist']; + } else { + $no_current_playlist = __( 'No Current Playlist available.', 'radio-station' ); + } + $no_current_playlist = apply_filters( 'radio_station_no_current_playlist_text', $no_current_playlist, $atts ); + + $no_playlist = '
    ' . "\n"; + // 2.5.0: use wp_kses on message output + $allowed = radio_station_allowed_html( 'message', 'no-playlist' ); + $no_playlist .= wp_kses( $no_current_playlist, $allowed ) . "\n"; + $no_playlist .= '
    ' . "\n"; + + // 2.3.3.8: added no playlist display filter + // 2.3.3.9: assign to tracks key for possible output re-ordering + $no_playlist = apply_filters( 'radio_station_current_playlist_no_playlist_display', $no_playlist, $atts ); + if ( ( '' != $no_playlist ) && is_string( $no_playlist ) ) { + $html['tracks'] = $no_playlist; + } } } @@ -3502,20 +3821,35 @@ function radio_station_current_playlist_shortcode( $atts ) { $order = array( 'tracks', 'link', 'countdown', 'custom' ); $order = apply_filters( 'radio_station_current_playlist_section_order', $order, $atts ); // $output .= print_r( array_keys( $html ), true ); - foreach ( $order as $section ) { - if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { - $output .= $html[$section]; - } + + // 2.5.0: added wrapper and countdown class for countdown script targeting + $classes = array( 'current-playlist show-playlist' ); + if ( $atts['countdown'] ) { + $classes[] = 'countdown'; } + $class_list = implode( ' ', $classes ); + $output .= '
    ' . "\n"; + + // --- loop sections to add to output --- + foreach ( $order as $section ) { + if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { + $output .= $html[$section]; + } + } + + $output .= '
    ' . "\n"; // --- close shortcode only wrapper --- if ( !$atts['widget'] ) { - $output .= '
    '; + $output .= '
    ' . "\n"; } // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // --- filter and return --- + // 2.5.0: added missing output override filter + $output = apply_filters( 'radio_station_current_playlist_shortcode', $output, $atts, $instance ); return $output; } @@ -3530,24 +3864,24 @@ function radio_station_current_playlist() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-playlist' ); if ( RADIO_STATION_DEBUG ) { - echo "Current Playlist Shortcode Attributes: " . print_r( $atts, true ); + echo "Current Playlist Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); } // --- output widget contents --- - echo '
    '; - echo radio_station_current_playlist_shortcode( $atts ); - echo '
    '; + echo '
    ' . "\n"; + echo radio_station_current_playlist_shortcode( $atts ); + echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { // --- send to parent window --- - $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; - $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; // --- restart countdowns --- if ( $atts['countdown'] ) { - $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; + $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; } } @@ -3557,7 +3891,7 @@ function radio_station_current_playlist() { // --- output javascript if ( '' != $js ) { - echo ""; + echo "" . "\n"; } exit; @@ -3573,7 +3907,7 @@ function radio_station_countdown_enqueue() { // 2.3.3.9: check if script is enqueued global $radio_station_data; - if ( isset( $radio_station_data['countdown-script'] ) ) { + if ( isset( $radio_station_data['countdown-script'] ) && $radio_station_data['countdown-script'] ) { return; } @@ -3585,8 +3919,7 @@ function radio_station_countdown_enqueue() { radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "'; radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "'; radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "'; - radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "'; - "; + radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "';"; // --- add script inline --- wp_add_inline_script( 'radio-station-countdown', $js ); @@ -3609,13 +3942,24 @@ function radio_station_countdown_enqueue() { add_shortcode( 'list-shows', 'radio_station_shortcode_list_shows' ); function radio_station_shortcode_list_shows( $atts ) { + global $radio_station_data; + + // --- set widget instance ID --- + // 2.3.2: added for AJAX loading + if ( !isset( $radio_station_data['instances']['list_shows'] ) ) { + $radio_station_data['instances']['list_shows'] = 0; + } + $radio_station_data['instances']['list_shows']++; + $instance = $radio_station_data['instances']['list_shows']; + + // --- combine shortcode atts with defaults --- $defaults = array( 'title' => false, 'genre' => '', ); $atts = shortcode_atts( $defaults, $atts, 'list-shows' ); - // grab the published shows + // --- grab the published shows --- $args = array( 'posts_per_page' => 1000, 'offset' => 0, @@ -3651,32 +3995,34 @@ function radio_station_shortcode_list_shows( $atts ) { $output = ''; - $output .= '
    '; + $output .= '
    ' . "\n"; - if ( $atts['title'] ) { - $output .= '
    '; - $output .= '

    ' . esc_html( $atts['title'] ) . '

    '; - $output .= '
    '; - } - - $output .= '
      '; - - // 2.3.0: use posts loop instead of query loop - foreach ( $posts as $post ) { - - $output .= '
    • '; - - $output .= ''; + if ( $atts['title'] ) { + $output .= '
      ' . "\n"; + $output .= '

      ' . esc_html( $atts['title'] ) . '

      ' . "\n"; + $output .= '
      ' . "\n"; + } - $output .= '
    • '; - } + $output .= '' . "\n"; - $output .= '
    '; - $output .= '
    '; + $output .= '
    ' . "\n"; + // --- filter and return --- + // 2.5.0: added shortcode filter for consistency + $output = apply_filters( 'radio_station_list_shows_shortcode', $output, $atts, $instance ); return $output; } @@ -3690,14 +4036,22 @@ function radio_station_shortcode_list_shows( $atts ) { add_shortcode( 'get-playlists', 'radio_station_shortcode_get_playlists_for_show' ); function radio_station_shortcode_get_playlists_for_show( $atts ) { - $atts = shortcode_atts( - array( - 'show' => '', - 'limit' => - 1, - ), - $atts, - 'get-playlists' + global $radio_station_data; + + // --- set widget instance ID --- + // 2.3.2: added for AJAX loading + if ( !isset( $radio_station_data['instances']['get_playlists'] ) ) { + $radio_station_data['instances']['get_playlists'] = 0; + } + $radio_station_data['instances']['get_playlists']++; + $instance = $radio_station_data['instances']['get_playlists']; + + // --- combine shortcode atts with defaults --- + $defaults = array( + 'show' => '', + 'limit' => -1, ); + $atts = shortcode_atts( $defaults, $atts, 'get-playlists' ); // don't return anything if we do not have a show if ( empty( $atts['show'] ) ) { @@ -3725,23 +4079,31 @@ function radio_station_shortcode_get_playlists_for_show( $atts ) { $output = ''; - $output .= '' . "\n"; + // --- filter and return --- + // 2.5.0: added shortcode filter for consistency + $output = apply_filters( 'radio_station_get_playlists_shortcode', $output, $atts, $instance ); return $output; } + diff --git a/includes/support-functions.php b/includes/support-functions.php index 23111f1..6191146 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -6,22 +6,21 @@ * Since: 2.3.0 */ +// ======================================= +// === Radio Station Support Functions === +// ======================================= + // === Data Functions === // - Get Show // - Get Shows -// - Get Show Schedule -// - Generate Unique Shift ID -// - Get Show Shifts // - Get Schedule Overrides +// - Generate Unique Shift ID // - Get Show Data // - Get Show Metadata // - Get Override Metadata // - Get Show Override Value -// - Get Current Schedule -// - Get Current Show -// - Get Previous Show -// - Get Next Show -// - Get Next Shows +// - Get Linked Overrides for Show +// - Get Linked Override Times // - Get Current Playlist // - Get Blog Posts for Show // - Get Playlists for Show @@ -29,11 +28,6 @@ // - Get Genres // - Get Shows for Genre // - Get Shows for Language -// === Shift Checking === -// - Schedule Conflict Checker -// - Show Shift Checker -// - New Shifts Checker -// - Validate Shift Time // === Show Avatar === // - Get Show Avatar ID // - Get Show Avatar URL @@ -51,28 +45,6 @@ // - Patreon Supporter Button // - Patreon Button Styles // - Send Directory Ping -// === Time Conversions === -// - Get Now -// - Get Timezone -// - Get Timezone Code -// - Get Date Time Object -// - String To Time -// - Get Time -// - Get Timezone Options -// - Get Weekday(s) -// - Get Month(s) -// - Get Schedule Weekdays -// - Get Schedule Weekdates -// - Get Next Day -// - Get Previous Day -// - Get Next Date -// - Get Previous Date -// - Get All Hours -// - Convert Hour to Time Format -// - Convert Shift to Time Format -// - Convert Show Shift -// - Convert Show Shifts -// - Convert Schedule Shifts // === Helper Functions === // - Get Icon Colors // - Encode URI Component @@ -85,18 +57,9 @@ // - Sanitize Input Value // - Get Meta Input Types // - Sanitize Shortcode Values -// === Transient Caching === -// - Delete Prefixed Transients -// - Clear Cached Data -// - Clear Cache on Status Transitions -// === Translations === -// - Translate Weekday -// - Replace Weekdays -// - Translate Month -// - Replace Months -// - Translate Meridiem -// - Replace Meridiems -// - Translate Time String +// - KSES Allowed HTML +// - Link Tag Allowed HTML + // ---------------------- // === Data Functions === @@ -127,19 +90,17 @@ function radio_station_get_show( $show ) { // --------- // 2.3.0: added get shows data grabber function radio_station_get_shows( $args = false ) { - + // --- set default args --- - $defaults = array( + $query_args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, 'post_status' => 'publish', - 'numberposts' => - 1, + 'numberposts' => -1, 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'show_sched', 'compare' => 'EXISTS', - // 'value' => 's:', - // 'compare' => 'LIKE', ), array( 'key' => 'show_active', @@ -154,22 +115,74 @@ function radio_station_get_shows( $args = false ) { // --- overwrite defaults with any arguments passed --- if ( $args && is_array( $args ) && ( count( $args ) > 0 ) ) { foreach ( $args as $key => $value ) { - $defaults[$key] = $value; + $query_args[$key] = $value; } } + + // 2.5.0: added query args filter + $query_args = apply_filters( 'radio_station_get_shows_args', $query_args, $args ); + + // --- get shows, filter and return --- + // 2.5.0: added missing args as third filter argument + $shows = get_posts( $query_args ); + $shows = apply_filters( 'radio_station_shows', $shows, $query_args, $args ); + // note: backwards compatible misnamed filter + $shows = apply_filters( 'radio_station_get_shows', $shows, $query_args, $args ); + return $shows; +} + +// ---------------------- +// Get Schedule Overrides +// ---------------------- +// 2.5.0: added function to match radio_station_get_shows +function radio_station_get_overrides( $args = false ) { - // --- get and return shows --- - $shows = get_posts( $defaults ); - $shows = apply_filters( 'radio_station_get_shows', $shows, $defaults ); + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; - return $shows; + // --- set default args --- + $query_args = array( + 'post_type' => RADIO_STATION_OVERRIDE_SLUG, + 'post_status' => 'publish', + 'numberposts' => -1, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'show_override_sched', + 'compare' => 'EXISTS', + ), + /* array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), */ + ), + 'orderby' => 'post_name', + 'order' => 'ASC', + ); + + // --- overwrite defaults with any arguments passed --- + if ( $args && is_array( $args ) && ( count( $args ) > 0 ) ) { + foreach ( $args as $key => $value ) { + $query_args[$key] = $value; + } + } + + // 2.5.0: added query args filter + $query_args = apply_filters( 'radio_station_get_overrides_args', $query_args, $args ); + + // --- get overrides, filter and return --- + $overrides = get_posts( $query_args ); + $overrides = apply_filters( 'radio_station_overrides', $overrides, $query_args, $args ); + return $overrides; } // ----------------- // Get Show Schedule // ----------------- // 2.3.0: added to give each shift a unique ID -function radio_station_get_show_schedule( $show_id ) { +// 2.5.0: rewritten and moved to schedules.php +/* function radio_station_get_show_schedule( $show_id ) { // --- get show shift schedule --- $shifts = get_post_meta( $show_id, 'show_sched', true ); @@ -195,7 +208,7 @@ function radio_station_get_show_schedule( $show_id ) { } return $shifts; -} +} */ // ------------------------ // Generate Unique Shift ID @@ -237,456 +250,12 @@ function radio_station_get_show_guid( $show_id ) { return $hash; } -// --------------- -// Get Show Shifts -// --------------- -// 2.3.0: added get show shifts data grabber -// 2.3.2: added second argument to get non-split shifts -// 2.3.3: added time as third argument for ondemand shifts -function radio_station_get_show_shifts( $check_conflicts = true, $split = true, $time = false ) { - - // --- get all shows --- - $errors = array(); - $shows = radio_station_get_shows(); - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - foreach ( $shows as $i => $show ) { - $shows[$i]->post_content = ''; - } - $debug = "Shows" . PHP_EOL . PHP_EOL . print_r( $shows, true ); - radio_station_debug( $debug ); - } - - // --- get weekdates for checking --- - if ( $time ) { - $now = $time; - } else { - $now = radio_station_get_now(); - } - $today = radio_station_get_time( 'l', $now ); - $weekdays = radio_station_get_schedule_weekdays( $today ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - // --- loop shows to get shifts --- - $all_shifts = array(); - if ( $shows && is_array( $shows ) && ( count( $shows ) > 0 ) ) { - foreach ( $shows as $show ) { - - $shifts = radio_station_get_show_schedule( $show->ID ); - if ( RADIO_STATION_DEBUG ) { - echo 'Shifts for Show ' . $show->ID . ': ' . print_r( $shifts, true ) . ''; - } - - if ( $shifts && is_array( $shifts) && ( count( $shifts ) > 0 ) ) { - foreach ( $shifts as $i => $shift ) { - - // 2.3.3.9: set shift ID to key - $shift['id'] = $i; - - // --- make sure shift has sufficient info --- - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $isdisabled = true; - } else { - $isdisabled = false; - } - $shift = radio_station_validate_shift( $shift ); - - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - - // --- if it was not already disabled, add to shift errors --- - if ( !$isdisabled ) { - $errors[$show->ID][] = $shift; - } - - } else { - - // --- shift is valid so continue checking --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first - $day = $shift['day']; - $thisdate = $weekdates[$day]; - $midnight = radio_station_to_time( $thisdate . ' 23:59:59' ) + 1; - $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $start_time = radio_station_to_time( $thisdate . ' ' . $start_time ); - if ( ( '11:59:59 pm' == $end ) || ( '12:00 am' == $end ) ) { - // 2.3.2: simplify using existing midnight time - $end_time = $midnight; - } else { - $end_time = radio_station_convert_shift_time( $end ); - $end_time = radio_station_to_time( $thisdate . ' ' . $end ); - } - if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { - $encore = true; - } else { - $encore = false; - } - $updated = $show->post_modified_gmt; - - if ( $split ) { - - // --- check if show goes over midnight --- - if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { - - // --- set the shift time as is --- - // 2.3.2: added date data - $all_shifts[$day][$start_time . '.' . $show->ID] = array( - 'day' => $day, - 'date' => $thisdate, - 'start' => $start, - 'end' => $end, - 'show' => $show->ID, - 'encore' => $encore, - 'split' => false, - 'updated' => $updated, - 'shift' => $shift, - 'override' => false, - ); - - } else { - - // --- split shift for this day --- - // 2.3.2: added date data - $all_shifts[$day][$start_time . '.' . $show->ID] = array( - 'day' => $day, - 'date' => $thisdate, - 'start' => $start, - 'end' => '11:59:59 pm', // midnight - 'show' => $show->ID, - 'split' => true, - 'encore' => $encore, - 'updated' => $updated, - 'shift' => $shift, - 'real_end' => $end, - 'override' => false, - ); - - // --- split shift for next day --- - // 2.3.2: added date data for next day - $nextday = radio_station_get_next_day( $day ); - $nextdate = $weekdates[$nextday]; - // 2.3.2: fix midnight timestamp for sorting - if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { - $midnight = radio_station_to_time( $nextdate . ' 00:00:00' ); - } - $all_shifts[$nextday][$midnight . '.' . $show->ID] = array( - 'day' => $nextday, - 'date' => $nextdate, - 'start' => '00:00 am', // midnight - 'end' => $end, - 'show' => $show->ID, - 'encore' => $encore, - 'split' => true, - 'updated' => $updated, - 'shift' => $shift, - 'real_start' => $start, - 'override' => false, - ); - } - - } else { - - // --- set the shift time as is --- - // 2.3.2: added for non-split argument - $all_shifts[$day][$start_time . '.' . $show->ID] = array( - 'day' => $day, - 'date' => $thisdate, - 'start' => $start, - 'end' => $end, - 'show' => $show->ID, - 'encore' => $encore, - 'split' => false, - 'updated' => $updated, - 'shift' => $shift, - 'override' => false, - ); - - } - } - } - } - } - } - - // --- maybe store any found shift errors --- - if ( count( $errors ) > 0 ) { - update_option( 'radio_station_shift_errors', $errors ); - } else { - delete_option( 'radio_station_shift_errors' ); - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Raw Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); - $debug .= "Shift Errors" . PHP_EOL . PHP_EOL . print_r( $errors, true ); - radio_station_debug( $debug ); - } - - // --- sort by start time for each day --- - // note: all_shifts keys are made unique by combining start time and show ID - // which allows them to be both be sorted and then checked for conflicts - if ( count( $all_shifts ) > 0 ) { - foreach ( $all_shifts as $day => $shifts ) { - ksort( $shifts ); - $all_shifts[$day] = $shifts; - } - } - - // --- reorder by weekdays --- - // 2.3.2: added for passing to shift checker - $sorted_shifts = array(); - foreach ( $weekdays as $weekday ) { - if ( isset( $all_shifts[$weekday] ) ) { - $sorted_shifts[$weekday] = $all_shifts[$weekday]; - } - } - $all_shifts = $sorted_shifts; - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Sorted Shifts" . PHP_EOL . PHP_EOL . print_r( $sorted_shifts, true ); - radio_station_debug( $debug ); - } - - // --- check shifts for conflicts --- - if ( $check_conflicts ) { - $all_shifts = radio_station_check_shifts( $all_shifts ); - } else { - // --- return raw data for other shift conflict checking --- - return $all_shifts; - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Conflict Checked Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); - radio_station_debug( $debug ); - } - - // --- shuffle shift days so today is first day --- - // 2.3.2: use get time function for day with timezone - // $today = date( 'l' ); - $today = radio_station_get_time( 'day' ); - $day_shifts = array(); - for ( $i = 0; $i < 7; $i ++ ) { - if ( 0 == $i ) { - $day = $today; - } else { - $day = radio_station_get_next_day( $day ); - } - if ( isset( $all_shifts[$day] ) ) { - $day_shifts[$day] = $all_shifts[$day]; - } - } - - // --- filter and return --- - // 2.3.3.9: changed conflicting filter name from radio_station_show_shifts - $day_shifts = apply_filters( 'radio_station_show_day_shifts', $day_shifts ); - - return $day_shifts; -} - -// ---------------------- -// Get Schedule Overrides -// ---------------------- -// 2.3.0: added get schedule overrides data grabber -function radio_station_get_overrides( $start_date = false, $end_date = false ) { - - // --- convert dates to times for checking --- - // (we allow an extra day either way for overflows) - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to variable conflict with start_time/end_time - if ( $start_date ) { - // 2.3.2: added missing backwards day allowance - $range_start_time = radio_station_to_time( $start_date ) - ( 24 * 60 * 60 ) + 1; - } - if ( $end_date ) { - $range_end_time = radio_station_to_time( $end_date ) + ( 24 * 60 * 60 ) - 1 ; - } - - // --- get all override IDs --- - global $wpdb; - $query = "SELECT ID,post_title,post_name FROM " . $wpdb->posts; - $query .= " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; - $query .= " AND post_status = 'publish'"; - $overrides = $wpdb->get_results( $query, ARRAY_A ); - if ( !$overrides || !is_array( $overrides ) || ( count( $overrides ) < 1 ) ) { - return false; - } - - // --- loop overrides and get data --- - $override_list = array(); - foreach ( $overrides as $i => $override ) { - - // 2.3.3.9: allow for usage of linked show title - $title = $override['post_title']; - $linked_id = get_post_meta( $override['ID'], 'linked_show_id', true ); - if ( $linked_id ) { - $linked_show = get_post( $linked_id ); - $linked_fields = get_post_meta( $override['ID'], 'linked_show_fields', true ); - if ( !isset( $linked_fields['show_title'] ) || !$linked_fields['show_title'] ) { - $title = $linked_show->post_title; - } - } - - // 2.3.3.9: get possible array of override shifts - $override_shifts = get_post_meta( $override['ID'], 'show_override_sched', true ); - // 2.3.3.9: convert possible single override to array - if ( $override_shifts && is_array( $override_shifts ) && array_key_exists( 'date', $override_shifts ) ) { - $override_shifts = array( $override_shifts ); - update_post_meta( $override['ID'], 'show_override_sched', $override_shifts ); - } - - if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { - - // 2.2.3.9: loop to add unique shift IDs and maybe resave - $update_override_shifts = false; - foreach ( $override_shifts as $j => $data ) { - if ( !isset( $data['id'] ) ) { - $data['id'] = radio_station_unique_shift_id(); - $override_shifts[$j] = $data; - $update_override_shifts = true; - } - } - if ( $update_override_shifts ) { - update_post_meta( $override['ID'], 'show_override_sched', $override_shifts ); - } - - // --- loop override shifts --- - // 2.3.3.9: loop to allow for multiple overrides - foreach ( $override_shifts as $j => $data ) { - - // 2.3.3.9: ignore disabled overrides - if ( !isset( $data['disabled'] ) || ( 'yes' != $data['disabled'] ) ) { - - $date = $data['date']; - if ( '' != $date ) { - - // 2.3.2: replace strtotime with to_time for timezones - $date_time = radio_station_to_time( $date ); - $inrange = true; - - // --- check if in specified date range --- - if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) - || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { - $inrange = false; - } - - // --- add the override data --- - if ( $inrange ) { - - // 2.3.2: get day from date directly - // $thisday = date( 'l', $date_time ); - $day = date( 'l', strtotime( $date ) ); - - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first - $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; - $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - $override_start_time = radio_station_to_time( $date . ' ' . $start_time ); - $override_end_time = radio_station_to_time( $date . ' ' . $end_time ); - // 2.3.2: fix for overrides ending at midnight - // 2.3.3.9: fix to use standardized operator check - if ( $override_end_time <= $override_start_time ) { - $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - } - // TODO: allow for multiday overrides ? - /* if ( isset( $data['multiday'] ) && ( 'yes' == $data['multiday'] ) ) { - if ( isset( $data['enddate'] ) && ( '' != $data['enddate'] ) ) { - - } - } */ - - if ( $override_start_time < $override_end_time ) { - - // --- add the override as is --- - $override_data = array( - 'override' => $override['ID'], - 'id' => $data['id'], - 'name' => $title, - 'slug' => $override['post_name'], - 'date' => $date, - 'day' => $day, - 'start' => $start, - 'end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => false, - ); - // 2.3.3.7: set array order by start time - $override_list[$date][$override_start_time] = $override_data; - - } else { - - // --- split the override overnight --- - $override_data = array( - 'override' => $override['ID'], - 'id' => $data['id'], - 'name' => $title, - 'slug' => $override['post_name'], - 'date' => $date, - 'day' => $day, - 'start' => $start, - 'end' => '11:59:59 pm', - 'real_end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => true, - ); - // 2.3.3.7: set array order by start time - $override_list[$date][$override_start_time] = $override_data; - - // --- set the next day split shift --- - // note: these should not wrap around to start of week - // 2.3.2: use get next date/day functions - // $nextday = date( 'l', $next_date_time ); - // $nextdate = date( 'Y-m-d', $next_date_time ); - $nextdate = radio_station_get_next_date( $date ); - $nextday = radio_station_get_next_day( $day ); - - $override_data = array( - 'override' => $override['ID'], - 'id' => $data['id'], - 'name' => $title, - 'slug' => $override['post_name'], - 'date' => $nextdate, - 'day' => $nextday, - 'real_start' => $start, - 'start' => '00:00 am', - 'end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => true, - ); - // 2.3.3.7: set array order by start time - $override_list[$nextdate][$override_start_time] = $override_data; - } - } - } - } - } - } - } - - // 2.3.3.7: reorder overrides by sequential times - if ( count( $override_list ) > 0 ) { - foreach ( $override_list as $day => $overrides ) { - ksort( $overrides ); - $override_list[$day] = $overrides; - } - } - - // --- filter and return --- - $override_list = apply_filters( 'radio_station_get_overrides', $override_list, $start_date, $end_date ); - - return $override_list; -} - // ------------- // Get Show Data // ------------- // 2.3.0: added get show data grabber -function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { +// 2.5.0: added optional atts as fourth argument +function radio_station_get_show_data( $datatype, $show_id, $args = array(), $atts = array() ) { // --- we need a data type and show ID --- if ( !$datatype ) { @@ -696,6 +265,10 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { return false; } + if ( RADIO_STATION_DEBUG ) { + echo 'Get ' . esc_html( $datatype ) . ' for Show ' . esc_html( $show_id ) . ''; + } + // --- get meta key for valid data types --- if ( 'posts' == $datatype ) { $metakey = 'post_showblog_id'; @@ -721,7 +294,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { $default = false; } if ( !isset( $args['columns'] ) || !is_array( $args['columns'] ) || ( count( $args['columns'] ) < 1 ) ) { - $columns = 'posts.ID, posts.post_title, posts.post_content, posts.post_excerpt, posts.post_date'; + $columns = '*'; } else { $columns = array(); $default = false; @@ -762,15 +335,16 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } // --- check for cached default show data --- - if ( $default ) { - $default_data = apply_filters( 'radio_station_cached_data', false, $datatype, $show_id ); + /* if ( $default ) { + // 2.5.0: added args and atts as optional filter arguments to match cached data + $default_data = apply_filters( 'radio_station_cached_data', false, $datatype, $show_id, $args, $atts ); if ( $default_data ) { - return $default_data; if ( RADIO_STATION_DEBUG ) { - echo 'Using Cached Data:' . print_r( $default_data, true ) . ''; + echo 'Using Cached Data:' . esc_html( print_r( $default_data, true ) ) . ''; } + return $default_data; } - } + } */ // --- get records with associated show ID --- global $wpdb; @@ -778,12 +352,13 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // 2.3.3.4: handle possible multiple show post values // 2.3.3.9: added 'i:' prefix to LIKE match value - $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = '" . $metakey . "' AND meta_value LIKE '%i:" . $show_id . "%'"; + // TODO: use wpdb prepare method on LIKE statement + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value LIKE '%i:" . $show_id . "%'"; + $query = $wpdb->prepare( $query, $metakey ); $results = $wpdb->get_results( $query, ARRAY_A ); if ( RADIO_STATION_DEBUG ) { - echo 'Related Query: ' . $query . ''; - echo 'Related Results: ' . print_r( $results, true ) . ''; + echo 'Related Query: ' . esc_html( $query ) . ''; + echo 'Related Results: ' . esc_html( print_r( $results, true ) ) . ''; } if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { return false; @@ -792,7 +367,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // --- get/check post IDs in post meta --- $post_ids = array(); foreach ( $results as $result ) { - // TODO: check if raw result is serialized or array ? + // TODO: recheck if raw result is serialized or array ? $show_ids = maybe_unserialize( $result['meta_value'] ); if ( $show_id == $result['meta_value'] || in_array( $show_id, $show_ids ) ) { $post_ids[] = $result['post_id']; @@ -808,10 +383,13 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } $post_ids = $no_profile_ids = array(); foreach ( $user_ids as $user_id ) { - $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = '" . $userkey . "' AND meta_value = %d"; - $query = $wpdb->prepare( $query, $user_id ); + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value = %d"; + $query = $wpdb->prepare( $query, array( $userkey, $user_id ) ); $profile_id = $wpdb->get_var( $query ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Query: ' . esc_html( $query ) . ''; + echo 'Related Result: ' . esc_html( print_r( $profile_id, true ) ) . ''; + } if ( $profile_id ) { $post_ids[] = $profile_id; } else { @@ -822,13 +400,12 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } else { // --- other types (episodes/playlists) --- - $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; - $query = $wpdb->prepare( $query, $show_id ); + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value = %d"; + $query = $wpdb->prepare( $query, array( $metakey, $show_id ) ); $post_metas = $wpdb->get_results( $query, ARRAY_A ); if ( RADIO_STATION_DEBUG ) { - echo 'Related Query: ' . $query . ''; - echo 'Related Results: ' . print_r( $post_metas, true ) . ''; + echo 'Related Query: ' . esc_html( $query ) . ''; + echo 'Related Results: ' . esc_html( print_r( $post_metas, true ) ) . ''; } if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { return false; @@ -840,6 +417,8 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { $post_ids[] = $post_meta['post_id']; } } + // 2.5.0: added filter for resulting post IDs + $post_ids = apply_filters( 'radio_station_show_' . $datatype . '_ids', $post_ids, $show_id, $args, $atts ); // --- check for post IDs --- if ( count( $post_ids ) < 1 ) { @@ -848,18 +427,24 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // --- get posts from post IDs --- $post_id_list = implode( ',', $post_ids ); - $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts AS posts - WHERE posts.ID IN(" . $post_id_list . ") AND posts.post_status = 'publish' - ORDER BY posts.post_date DESC"; + $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts as posts"; + $query .= " WHERE ID IN(" . $post_id_list . ") AND post_status = 'publish' ORDER BY post_date DESC"; if ( $args['limit'] ) { $query .= $wpdb->prepare( " LIMIT %d", $args['limit'] ); } $results = $wpdb->get_results( $query, ARRAY_A ); - + $results = apply_filters( 'radio_station_show_' . $datatype, $post_ids, $show_id, $args, $atts ); + + if ( RADIO_STATION_DEBUG ) { + echo '' . esc_html( $datatype ) . ' for Show ' . esc_html( $show_id ) . ': '; + echo esc_html( print_r( $results, true ) ) . ''; + } + // 2.4.0.6: add processing of post excerpts if ( $results && is_array( $results ) ) { foreach ( $results as $i => $result ) { - if ( empty( $result['post_excerpt'] ) ) { + // 2.5.0: added check that post content is not empty + if ( empty( $result['post_excerpt'] ) && !empty( $result['post_content'] ) ) { $length = apply_filters( 'radio_station_show_data_excerpt_length', 55 ); $more = apply_filters( 'radio_station_show_data_excerpt_more', '' ); $result['post_excerpt'] = radio_station_trim_excerpt( $result['post_content'], $length, $more, false ); @@ -878,12 +463,12 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // --- maybe cache default show data --- if ( $default ) { - do_action( 'radio_station_cache_data', $datatype, $show_id, $results ); + do_action( 'radio_station_cache_data', $datatype, $show_id, $results, $args, $atts ); } // --- filter and return --- - $results = apply_filters( 'radio_station_show_' . $datatype, $results, $show_id, $args ); - + // 2.5.0: added optional atts argument to filter + $results = apply_filters( 'radio_station_show_' . $datatype, $results, $show_id, $args, $atts ); return $results; } @@ -924,6 +509,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { if ( $show_schedule && is_array( $show_schedule ) && ( count( $show_schedule ) > 0 ) ) { foreach ( $show_schedule as $i => $shift ) { $shift = radio_station_validate_shift( $shift ); + $shift['id'] = $i; if ( !isset( $shift['disabled'] ) || ( 'yes' != $shift['disabled'] ) ) { $show_shifts[] = $shift; } @@ -1000,19 +586,19 @@ function radio_station_get_show_data_meta( $show, $single = false ) { // 2.3.1: added show avatar and image URLs // 2.4.0.6: added avatar and image IDs $show_data = array( - 'id' => $show->ID, - 'name' => $show->post_title, - 'slug' => $show->post_name, - 'url' => get_permalink( $show->ID ), - 'latest' => $show_file, - 'website' => $show_link, + 'id' => $show->ID, + 'name' => $show->post_title, + 'slug' => $show->post_name, + 'url' => get_permalink( $show->ID ), + 'latest' => $show_file, + 'website' => $show_link, // note: left out intentionally to avoid spam scraping // 'email' => $show_email, - 'hosts' => $hosts, - 'producers' => $producers, - 'genres' => $genre_list, - 'languages' => $language_list, - 'schedule' => $show_shifts, + 'hosts' => $hosts, + 'producers' => $producers, + 'genres' => $genre_list, + 'languages' => $language_list, + 'schedule' => $show_shifts, 'avatar_url' => $avatar_url, 'avatar_id' => $avatar_id, 'image_url' => $thumbnail_url, @@ -1076,7 +662,7 @@ function radio_station_get_show_description( $show_data ) { // --- add to existing show data --- $show_data['description'] = $description; $show_data['excerpt'] = $excerpt; - + // echo "Description: " . print_r( $description, true ) . PHP_EOL; // echo "Excerpt: " . print_r( $excerpt, true ) . PHP_EOL; @@ -1158,8 +744,10 @@ function radio_station_get_override_data_meta( $override ) { // --- create array and return --- // 2.3.1: added show avatar and image URLs // 2.4.0.6: added avatar and image IDs + // 2.5.0: add duplicated title key for compatibility $override_data = array( 'id' => $override_id, + 'title' => $override->post_title, 'name' => $override->post_title, 'slug' => $override->post_name, 'url' => get_permalink( $override_id ), @@ -1201,7 +789,6 @@ function radio_station_get_override_data_meta( $override ) { // --- filter and return --- $override_data = apply_filters( 'radio_station_override_data', $override_data, $override_id ); - return $override_data; } @@ -1319,1104 +906,127 @@ function radio_station_get_linked_override_times( $post_id ) { return $overrides; } - // -------------------- -// Get Current Schedule +// Get Current Playlist // -------------------- -// 2.3.2: added optional time argument -// 2.3.3.5: added optional weekstart argument -function radio_station_get_current_schedule( $time = false, $weekstart = false ) { +// 2.3.3.5: added get current playlist function +function radio_station_get_current_playlist() { - global $radio_station_data; + $current_show = radio_station_get_current_show(); + // 2.5.0: return false early if no current show/id + if ( !$current_show || !isset( $current_show['show']['id'] ) ) { + return false; + } + $show_id = $current_show['show']['id']; + $playlists = radio_station_get_show_playlists( $show_id ); + if ( !$playlists || !is_array( $playlists ) || ( count( $playlists ) < 1 ) ) { + return false; + } - $show_shifts = false; + $playlist_id = $playlists[0]['ID']; + $tracks = get_post_meta( $playlist_id, 'playlist', true ); + if ( !$tracks || !is_array( $tracks ) || ( count( $tracks ) < 1 ) ) { + return false; + } - // --- maybe get cached schedule --- - if ( !$time ) { - // 2.3.3: check data global first - if ( isset( $radio_station_data['current_schedule'] ) ) { - if ( RADIO_STATION_DEBUG ) { - echo "Using cached schedule pageload data for Now." . PHP_EOL; - } - return $radio_station_data['current_schedule']; - } else { - $show_shifts = get_transient( 'radio_station_current_schedule' ); - if ( RADIO_STATION_DEBUG && $show_shifts ) { - echo "Using cached transient 'radio_station_current_schedule':" . PHP_EOL; - print_r( $show_shifts ); - } + // --- split off tracks marked as queued --- + $entries = $queued = $played = array(); + foreach ( $tracks as $i => $track ) { + foreach ( $track as $key => $value ) { + unset( $track[$key] ); + $key = str_replace( 'playlist_entry_', '', $key ); + $track[$key] = $value; + $entries[$i] = $track; } - } else { - // --- get schedule for time --- - // 2.3.2: added transient for time schedule - // 2.3.3: check data global first - if ( isset( $radio_station_data['current_schedule_' . $time ] ) ) { - if ( RADIO_STATION_DEBUG ) { - echo "Using cached schedule pageload data for '" . $time . "'" . PHP_EOL; - } - return $radio_station_data['current_schedule_' . $time ]; - } else { - $show_shifts = get_transient( 'radio_station_current_schedule_' . $time ); - if ( RADIO_STATION_DEBUG && $show_shifts ) { - // 2.3.3.9: fix to clear transient object cache (OMFG.) - if ( isset( $_REQUEST['clear'] ) && ( '1' == $_REQUEST['clear'] ) ) { - echo "Clearing object cache for requested schedule time." . PHP_EOL; - wp_cache_delete( 'radio_station_current_schedule_' . $time, 'transients' ); - $show_shifts = false; - } else { - echo "Using cached transient 'radio_station_current_schedule_" . $time . "':" . PHP_EOL; - print_r( $show_shifts ); + // 2.4.0.3: fix for queued and played arrays + foreach ( $track as $key => $value ) { + if ( 'status' == $key ) { + if ( 'queued' == $value ) { + $queued[] = $track; + } elseif ( 'played' == $value ) { + $played[] = $track; } } } } - if ( !$show_shifts ) { + $latest = array(); + if ( isset( $queued[0] ) ) { + $latest = $queued[0]; + } - // --- get all show shifts --- - // 2.3.3: also pass time to get show_shifts function - $show_shifts = radio_station_get_show_shifts( true, true, $time ); + // --- get the track list for display --- + $playlist = array( + 'tracks' => $entries, + 'queued' => $queued, + 'played' => $played, + 'latest' => $latest, + 'id' => $playlist_id, + 'url' => get_permalink( $playlist_id ), + 'show' => $show_id, + 'show_url' => get_permalink( $show_id ), + ); - // --- get weekdates --- - if ( !$time ) { - $now = radio_station_get_now(); - } else { - $now = $time; - } - // 2.3.3.5: add passthrough of optional week start argument - $weekdays = radio_station_get_schedule_weekdays( $weekstart ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - // 2.3.1: add empty keys to ensure overrides are checked - foreach ( $weekdays as $weekday ) { - if ( !isset( $show_shifts[$weekday] ) ) { - $show_shifts[$weekday] = array(); - } - } + return $playlist; +} - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Show Shifts: " . print_r( $show_shifts, true ) . PHP_EOL; - radio_station_debug( $debug ); - } +// ----------------------- +// Get Blog Posts for Show +// ----------------------- +// 2.3.0: added show blog post data grabber +function radio_station_get_show_posts( $show_id = false, $args = array() ) { + return radio_station_get_show_data( 'posts', $show_id, $args ); +} - // --- get show overrides --- - // (from 12am this morning, for one week ahead and back) - // 2.3.1: get start and end dates from weekdays - // 2.3.2: use get time function with timezone - // 2.3.3.9: pass second argument as time may not be now - $date = radio_station_get_time( 'date', $now ); - - // $start_time = strtotime( '12am ' . $date ); - // $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; - // $start_time = $start_time - ( 7 * 24 * 60 * 60 ) - 1; - // $start_date = date( 'd-m-Y', $start_time ); - // $end_date = date( 'd-m-Y', $end_time ); - $start_date = $weekdates[$weekdays[0]]; - $end_date = $weekdates[$weekdays[6]]; - $override_list = radio_station_get_overrides( $start_date, $end_date ); - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Now: " . $now . " - Date: " . $date . PHP_EOL; - $debug .= "Week Start Date: " . $start_date . " - Week End Date: " . $end_date . PHP_EOL; - $debug .= "Schedule Overrides: " . print_r( $override_list, true ) . PHP_EOL; - radio_station_debug( $debug ); - } +// ---------------------- +// Get Playlists for Show +// ---------------------- +// 2.3.0: added show playlist data grabber +function radio_station_get_show_playlists( $show_id = false, $args = array() ) { + return radio_station_get_show_data( 'playlists', $show_id, $args ); +} - // --- apply overrides to the schedule --- - $debugday = 'Monday'; - if ( isset( $_REQUEST['debug-day'] ) ) { - $debugday = $_REQUEST['debug-day']; +// --------- +// Get Genre +// --------- +// 2.3.0: added genre data grabber +function radio_station_get_genre( $genre ) { + // 2.3.3.8: explicitly check for numberic genre term ID + $id = absint( $genre ); + if ( $id < 1 ) { + // $genre = sanitize_title( $genre ); + $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); + if ( !$term ) { + $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); } - $done_overrides = array(); - if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { - foreach ( $show_shifts as $day => $shifts ) { - - $date = $weekdates[$day]; - if ( RADIO_STATION_DEBUG ) { - echo "Override Date: " . $date . PHP_EOL; - } - - // 2.3.2: reset overrides for loop - $overrides = array(); - if ( isset( $override_list[$date] ) ) { - - $overrides = $override_list[$date]; - if ( RADIO_STATION_DEBUG ) { - echo "Overrides for " . $day . ": " . print_r( $overrides, true ) . PHP_EOL; - } - - // --- maybe reloop to insert any overrides before shows --- - if ( count( $overrides ) > 0 ) { - foreach ( $overrides as $i => $override ) { - - if ( $date == $override['date'] ) { - // 2.3.1: added check if override already done - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - // 2.3.3.7: remove check if override already done from here - - $override_start = radio_station_convert_shift_time( $override['start'] ); - $override_end = radio_station_convert_shift_time( $override['end'] ); - $override_start_time = radio_station_to_time( $date . ' ' . $override_start ); - $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); - if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { - // 2.3.3.8: fix to add 60 seconds instead of 1 - $override_end_time = $override_end_time + 60; - } - // 2.3.2: fix for non-split overrides ending on midnight - // 2.3.3.9: added or equals to operator - if ( $override_end_time <= $override_start_time ) { - $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - } - - // --- check for overlapped shift (if any) --- - // 2.3.1 added check for shift count - if ( count( $shifts ) > 0 ) { - // 2.3.3.7: change shifts variable in loop not just show_shifts - foreach ( $shifts as $start => $shift ) { - - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $start_time = radio_station_to_time( $date . ' ' . $shift_start ); - $end_time = radio_station_to_time( $date . ' ' . $shift_end ); - if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { - // 2.3.3.8: fix to add 60 seconds instead of 1 - $end_time = $end_time + 60; - } - // 2.3.2: fix for non-split shifts ending on midnight - // 2.3.3.9: added or equals to operator - if ( $end_time <= $start_time ) { - $end_time = $end_time + ( 24 * 60 * 60 ); - } - - if ( $day == $debugday ) { - // 2.4.0.6: fix to undefined variable warning - if ( !isset( $debugshifts ) ) { - $debugshifts = ''; - } - $debugshifts .= $day . ' Show from ' . $shift['start'] . ': ' . $start_time . ' to ' . $shift['end'] . ': ' . $end_time . PHP_EOL; - $debugshifts .= $day . ' Override from ' . $override['start'] . ': ' . $override_start_time . ' to ' . $override['end'] . ': ' . $override_end_time . PHP_EOL; - } - - // --- check if the override starts earlier than shift --- - if ( $override_start_time < $start_time ) { - - // --- check when the shift ends --- - if ( ( $override_end_time > $end_time ) - || ( $override_end_time == $end_time ) ) { - // --- overlaps so remove shift --- - if ( $day == $debugday ) { - $debugshifts .= "Removed Shift: " . print_r( $shift, true ) . PHP_EOL; - } - unset( $show_shifts[$day][$start] ); - unset( $shifts[$start] ); - } elseif ( $override_end_time > $start_time ) { - // --- add trimmed shift remainder --- - if ( $day == $debugday ) { - $debugshifts .= "Trimmed Start of Shift to " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; - } - unset( $show_shifts[$day][$start] ); - unset( $shifts[$start] ); - $shift['start'] = $override['end']; - $shift['trimmed'] = 'start'; - $shifts[$override['end']] = $shift; - $show_shifts[$day] = $shifts; - } - - // --- add the override if not already added --- - // 2.3.3.8: removed adding of overrides here - /* if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { - $done_overrides[] = $override['date'] . '--' . $i; - if ( $day == $debugday ) { - $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; - } - $shifts[$override['start']] = $override; - $show_shifts[$day] = $shifts; - } */ - - } elseif ( $override_start_time == $start_time ) { - - // --- same start so overwrite the existing shift --- - // 2.3.1: set override done instead of unsetting override - // 2.3.3.7: remove check if override already done - // $done_overrides[] = $date . '--' . $i; - if ( $day == $debugday ) { - $debugshifts .= "Replaced Shift with Override: " . print_r( $show_shifts[$day][$start], true ) . PHP_EOL; - } - $shifts[$start] = $override; - $show_shifts[$day] = $shifts; - - // --- check if there is remainder of existing show --- - if ( $override_end_time < $end_time ) { - $shift['start'] = $override['end']; - $shift['trimmed'] = 'start'; - $shifts[$override['end']] = $shift; - $show_shifts[$day] = $shifts; - if ( $day == $debugday ) { - $debugshifts .= "And trimmed Shift Start to " . $override['end'] . PHP_EOL; - } - } - // elseif ( $override_end_time == $end_time ) { - // --- remove exact override --- - // do nothing, already overridden - // } - - } elseif ( ( $override_start_time > $start_time ) - && ( $override_start_time < $end_time ) ) { - - $end = $shift['end']; - - // --- partial shift before override --- - if ( $day == $debugday ) { - $debugshifts .= "Trimmed Shift End to " . $override['start'] . ": " . print_r( $shift, true ) . PHP_EOL; - } - $shift['start'] = $start; - $shift['end'] = $override['start']; - $shift['trimmed'] = 'end'; - $shifts[$start] = $shift; - $show_shifts[$day] = $shifts; - - // --- add the override --- - $show_shifts[$day][$override['start']] = $override; - // 2.3.1: track done instead of unsetting - // 2.3.3.7: remove check if override already done here - // $done_overrides[] = $date . '--' . $i; - - // --- partial shift after override ---- - if ( $override_end_time < $end_time ) { - if ( $day == $debugday ) { - $debugshifts .= "And added partial Shift after " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; - } - $shift['start'] = $override['end']; - $shift['end'] = $end; - $shift['trimmed'] = 'start'; - $shifts[$override['end']] = $shift; - $show_shifts[$day] = $shifts; - } - } - } - } - } - } - } - } - - // --- add directly any remaining overrides --- - // 2.3.1: fix to include standalone overrides on days - // 2.3.3.8: moved override adding to fix shift order - if ( count( $overrides ) > 0 ) { - foreach ( $overrides as $i => $override ) { - if ( $date == $override['date'] ) { - // 2.3.3.7: remove check if override already done - // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { - // $done_overrides[] = $date . '--' . $i; - $show_shifts[$day][$override['start']] = $override; - if ( $day == $debugday ) { - $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; - } - // } - } - } - } - - // --- sort the shifts using 24 hour time --- - $shifts = $show_shifts[$day]; - if ( count( $shifts ) > 0 ) { - // 2.3.2: fix to clear shift keys between days - $new_shifts = $shift_keys = array(); - $keys = array_keys( $shifts ); - foreach ( $keys as $i => $key ) { - $converted = radio_station_convert_shift_time( $key, 24 ); - unset( $keys[$i] ); - $keys[$key] = $shift_keys[$key] = $converted; - } - sort( $shift_keys ); - foreach ( $shift_keys as $shift_key ) { - if ( in_array( $shift_key, $keys ) ) { - $key = array_search( $shift_key, $keys ); - $new_shifts[$key] = $shifts[$key]; - } - } - $shifts = $show_shifts[$day] = $new_shifts; - } - - if ( RADIO_STATION_DEBUG ) { - if ( isset( $debugshifts ) ) { - echo "Day Debug: " . $debugshifts . PHP_EOL; - } - echo "Shift Keys: " . print_r( $keys, true ) . PHP_EOL; - echo "Sorted Keys: " . print_r( $shift_keys, true ) . PHP_EOL; - echo "Sorted Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; - } - - $shifts = $show_shifts[$day]; - // ksort( $shifts ); - if ( RADIO_STATION_DEBUG ) { - echo "New Day Shifts: " . print_r( $shifts, true ) . PHP_EOL; - // echo "Done Overrides: " . print_r( $done_overrides, true ) . PHP_EOL; - } - } - - if ( RADIO_STATION_DEBUG ) { - $debug = "Combined Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; - radio_station_debug( $debug ); - } - } - - // --- loop all shifts to add show data --- - $prev_shift = $set_prev_shift = $prev_shift_end = false; - foreach ( $show_shifts as $day => $shifts ) { - // 2.3.1: added check for shift count - if ( count( $shifts ) > 0 ) { - foreach ( $shifts as $start => $shift ) { - - // --- check if shift is an override --- - if ( isset( $shift['override'] ) && $shift['override'] ) { - - // ---- add the override data --- - $override = radio_station_get_override_data_meta( $shift['override'] ); - $shift['show'] = $show_shifts[$day][$start]['show'] = $override; - - } else { - - // --- get (or get stored) show data --- - $show_id = $shift['show']; - if ( isset( $radio_station_data['show-' . $show_id] ) ) { - $show = $radio_station_data['show-' . $show_id]; - } else { - $show = radio_station_get_show_data_meta( $show_id ); - $radio_station_data['show-' . $show_id] = $show; - } - unset( $show['schedule'] ); - - // --- add show data back to shift --- - $shift['show'] = $show_shifts[$day][$start]['show'] = $show; - } - - if ( !isset( $current_show ) ) { - - // --- get this shift start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - - // if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) { - // $nextdate = radio_station_get_time( 'date', $shift_end_time + ( 23 * 60 * 60 ) ); - // $shift_end = $nextdate[$day] . ' ' . $shift['real_end']; - // } - - // - adjust for shifts ending past midnight - - // 2.3.3.9: added or equals to operator - if ( $shift_end_time <= $shift_start_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } - - // --- check if this is the currently scheduled show --- - if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { - - if ( isset( $maybe_next_show ) ) { - unset( $maybe_next_show ); - } - $shift['day'] = $day; - $current_show = $shift; - - // 2.3.3: set current show to global data - // 2.3.4: set previous show shift to global and transient - // 2.3.3.8: move expires declaration earlier - $expires = $shift_end_time - $now - 1; - if ( $expires > 3600 ) { - $expires = 3600; - } - if ( !$time ) { - $radio_station_data['current_show'] = $current_show; - if ( $prev_shift ) { - $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); - $radio_station_data['previous_show'] = $prev_show; - set_transient( 'radio_station_previous_show', $prev_show, $expires ); - } - } else { - $radio_station_data['current_show_' . $time ] = $current_show; - if ( $prev_shift ) { - $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); - $radio_station_data['previous_show_' . $time] = $prev_show; - set_transient( 'radio_station_previous_show_' . $time, $prev_show, $expires ); - } - } - - // 2.3.2: set temporary transient if time is specified - // 2.3.3: remove current show transient (as being unreliable) - /* if ( !$time ) { - set_transient( 'radio_station_current_show', $current_show, $expires ); - } else { - set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); - } */ - - } elseif ( $now > $shift_end_time ) { - - // 2.3.2: set previous shift flag - $set_prev_shift = true; - - } elseif ( ( $now < $shift_start_time ) && !isset( $maybe_next_show ) ) { - - // 2.3.2: set maybe next show - $maybe_next_show = $shift; - - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = 'Now: ' . $now . PHP_EOL; - $debug .= 'Date: ' . date( 'm-d H:i:s', $now ) . PHP_EOL; - $debug .= 'Shift Start: ' . $shift_start . ' (' . $shift_start_time . ')' . PHP_EOL; - $debug .= 'Shift End: ' . $shift_end . ' (' . $shift_end_time . ')' . PHP_EOL . PHP_EOL; - if ( isset ( $current_show ) ) { - $debug .= '[Current Shift] ' . print_r( $current_show, true ) . PHP_EOL; - } - $debug .= PHP_EOL; - if ( $now >= $shift_start_time ) {$debug .= "!A!";} - if ( $now < $shift_end_time ) {$debug .= "!B!";} - echo $debug; - } - - } elseif ( isset( $current_show['split'] ) && $current_show['split'] ) { - - // --- skip second part of split shift for current shift --- - // (so that it is not set as the next show) - unset( $current_show['split'] ); - - } - - // 2.3.2: change to logic to allow for no current show found - if ( !isset( $next_show ) ) { - - // --- get shift times --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_time = radio_station_convert_shift_time( $shift['start'] ); - $end_time = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - // 2.3.3.9: added or equals to operator - if ( $shift_end_time <= $shift_start_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } - - if ( isset( $current_show ) || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { - - // --- set next show --- - // 2.3.2: set date for widget - $next_show['date'] = $weekdates[$day]; - $next_show = $shift; - - } - } - - // 2.3.2: maybe set previous shift end value - if ( $set_prev_shift ) { - $prev_shift_end = $shift_end_time; - } - - // 2.3.4: set previous shift value - $prev_shift = $shift; - - } - } - } - - // --- maybe set next show transient --- - // 2.3.2: check for (possibly first) next show found - if ( !isset( $next_show ) && isset( $maybe_next_show ) ) { - $next_show = $maybe_next_show; - } - if ( isset( $next_show ) ) { - - // 2.3.2: recombine split shift end times - $shift = $next_show; - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $next_show['end'] = $shift['real_end']; - unset( $next_show['split'] ); - } - - // 2.3.2: added check that expires is set - $next_expires = $shift_end_time - $now - 1; - if ( isset( $expires ) && ( $next_expires > ( $expires + 3600 ) ) ) { - $next_expires = $expires + 3600; - } - // 2.3.2: set temporary transient if time is specified - if ( !$time ) { - set_transient( 'radio_station_next_show', $next_show, $next_expires ); - } else { - set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); - } - } - - if ( RADIO_STATION_DEBUG ) { - if ( !isset( $current_show ) ) { - $current_show = 'Not found.'; - } - echo 'Current Show: ' . print_r( $current_show, true ) . ''; - } - - // --- get next show if we did not find one --- - if ( !isset( $next_show ) ) { - - if ( RADIO_STATION_DEBUG ) { - echo "No Next Show Found. Rechecking..."; - } - - // --- pass calculated shifts with limit of 1 --- - // 2.3.2: added time argument to next shows retrieval - // 2.3.2: set next show transient within next shows function - $next_shows = radio_station_get_next_shows( 1, $show_shifts, $time ); - - } - - // --- cache current schedule data --- - // 2.3.2: set temporary transient if time is specified - // 2.3.3: also set global data for current schedule - if ( !isset( $expires ) ) { - $expires = 3600; - } - if ( !$time ) { - $radio_station_data['current_schedule'] = $show_shifts; - set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); - } else { - $radio_station_data['current_schedule_' . $time] = $show_shifts; - delete_transient( 'radio_station_current_schedule_' . $time ); - set_transient( 'radio_station_current_schedule_' . $time, $show_shifts, $expires ); - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = ''; - if ( $time ) { - $debug .= "Cached Schedule to radio_station_current_schedule_" . $time . PHP_EOL; - } - $debug .= "Show Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; - if ( isset( $current_show ) ) { - $debug .= "Current Show: " . print_r( $current_show, true ) . PHP_EOL; - } - if ( isset( $next_show ) ) { - $debug .= "Next Show: " . print_r( $next_show, true ) . PHP_EOL; - } - - $next_shows = radio_station_get_next_shows( 5, $show_shifts ); - $debug .= "Next 5 Shows: " . print_r( $next_shows, true ) . PHP_EOL; - - radio_station_debug( $debug ); - } - - // --- filter and return --- - // 2.3.2: added time argument to filter - // 2.3.3: apply filter only once - $show_shifts = apply_filters( 'radio_station_current_schedule', $show_shifts, $time ); - } - - return $show_shifts; -} - -// ---------------- -// Get Current Show -// ---------------- -// 2.3.0: added new get current show function -// 2.3.2: added optional time argument -function radio_station_get_current_show( $time = false ) { - - global $radio_station_data; - - $current_show = false; - - // --- get cached current show value --- - // 2.3.3: remove current show transient - // 2.3.3: check for existing global data first - if ( !$time && isset( $radio_station_data['current_show'] ) ) { - return $radio_station_data['current_show']; - } elseif ( isset( $radio_station_data['current_show_' . $time] ) ) { - return $radio_station_data['current_show_' . $time]; - } - - // --- get all show shifts --- - if ( !$time ) { - $show_shifts = radio_station_get_current_schedule(); - } else { - $show_shifts = radio_station_get_current_schedule( $time ); - } - - // --- get current time --- - if ( $time ) { - $now = $time; - } else { - $now = radio_station_get_now(); - } - - // --- get schedule for time --- - // 2.3.3: use weekday name instead of number (w) - // 2.3.3: add fix to start from previous day - $today = radio_station_get_time( 'l', $now ); - $yesterday = radio_station_get_previous_day( $today ); - $weekdays = radio_station_get_schedule_weekdays( $yesterday ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - if ( RADIO_STATION_DEBUG ) { - echo ''; - echo "Finding Current Show from " . $yesterday . PHP_EOL; - print_r( $weekdays ); - print_r( $weekdates ); - echo ''; - } - - // --- loop shifts to get current show --- - $current_split = $prev_show = false; - foreach ( $weekdays as $day ) { - if ( isset( $show_shifts[$day] ) ) { - $shifts = $show_shifts[$day]; - foreach ( $shifts as $start => $shift ) { - - // --- get this shift start and end times --- - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - // 2.3.3: fix for shifts split over midnight - // 2.3.3.9: added or equals to operator - if ( $shift_end_time <= $shift_start_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } - - if ( RADIO_STATION_DEBUG ) { - echo ''; - echo 'Now: ' . $now . ' - Shift Start: ' . $shift_start_time . ' - Shift End: ' . $shift_end_time . PHP_EOL; - echo print_r( $shift, true ) . PHP_EOL; - } - - // --- set current show --- - // 2.3.3: get current show directly and remove transient - // 2.4.0.6: fix to add equal to operator for start time - if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { - if ( RADIO_STATION_DEBUG ) { - echo '^^^ Current ^^^' . PHP_EOL; - } - // --- recombine possible split shift to set current show --- - $current_show = $shift; - - // 2.3.4: also set previous shift data - if ( $prev_shift ) { - $expires = $shift_end_time - $now - 1; - $previous_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); - if ( !$time ) { - $radio_station_data['previous_show'] = $previous_show; - set_transient( 'radio_station_previous_show', $previous_show, $expires ); - } else { - $radio_station_data['previous_show_' . $time] = $previous_show; - set_transient( 'radio_station_previous_show_' . $time, $previous_show, $expires ); - } - } - - /* if ( isset( $current_show['split'] ) && $current_show['split'] ) { - if ( isset( $current_show['real_start'] ) ) { - // 2.3.3: second shift half so set to previous day and date - $current_show['day'] = radio_station_get_previous_day( $shift['day'] ); - $current_show['date'] = radio_station_get_previous_date( $shift['date'] ); - $current_show['start'] = $current_show['real_start']; - } elseif ( isset( $current_show['real_end'] ) ) { - $current_show['end'] = $current_show['real_end']; - } - } */ - } - - if ( RADIO_STATION_DEBUG ) { - echo '' . PHP_EOL; - } - - // 2.3.4: store previous shift - $prev_shift = $shift; - } - } - } - - // --- filter current show --- - // 2.3.2: added time argument to filter - $current_show = apply_filters( 'radio_station_current_show', $current_show, $time ); - - if ( RADIO_STATION_DEBUG ) { - echo 'Current Show: ' . print_r( $current_show, true ) . PHP_EOL; - } - - // --- set to global data --- - if ( !$time ) { - $radio_station_data['current_show'] = $current_show; - } else { - $radio_station_data['current_show_' . $time] = $current_show; - } - - return $current_show; -} - -// ----------------- -// Get Previous Show -// ----------------- -// 2.3.3: added get previous show function -function radio_station_get_previous_show( $time = false ) { - - global $radio_station_data; - - $prev_show = false; - - // --- get cached current show value --- - if ( !$time ) { - if ( isset ( $radio_station_data['previous_show'] ) ) { - $prev_show = $radio_station_data['previous_show']; - } else { - $prev_show = get_transient( 'radio_station_previous_show' ); - } - } else { - if ( isset ( $radio_station_data['previous_show_' . $time] ) ) { - $prev_show = $radio_station_data['previous_show_' . $time]; - } else { - $prev_show = get_transient( 'radio_station_previous_show_' . $time ); - } - } - - // --- if not set it has expired so recheck schedule --- - if ( !$prev_show ) { - if ( !$time ) { - $schedule = radio_station_get_current_schedule(); - if ( isset( $radio_station_data['previous_show'] ) ) { - $prev_show = $radio_station_data['previous_show']; - } - } else { - $schedule = radio_station_get_current_schedule( $time ); - if ( isset( $radio_station_data['previous_show_' . $time] ) ) { - $prev_show = $radio_station_data['previous_show_' . $time]; - } - } - } - - // note: already filtered when set - return $prev_show; -} - -// ------------- -// Get Next Show -// ------------- -// 2.3.0: added new get next show function -// 2.3.2: added optional time argument -function radio_station_get_next_show( $time = false ) { - - global $radio_station_data; - - $next_show = false; - - // --- get cached current show value --- - if ( !$time ) { - if ( isset ( $radio_station_data['next_show'] ) ) { - return $radio_station_data['next_show']; - } else { - $next_show = get_transient( 'radio_station_next_show' ); - } - } else { - if ( isset ( $radio_station_data['next_show_' . $time] ) ) { - return $radio_station_data['next_show_' . $time]; - } else { - $next_show = get_transient( 'radio_station_next_show_' . $time ); - } - } - - // --- if not set it has expired so recheck schedule --- - if ( !$next_show ) { - if ( !$time ) { - $schedule = radio_station_get_current_schedule(); - if ( isset( $radio_station_data['next_show'] ) ) { - $next_show = $radio_station_data['next_show']; - } else { - $next_show = get_transient( 'radio_station_next_show' ); - } - } else { - $schedule = radio_station_get_current_schedule( $time ); - if ( isset( $radio_station_data['next_show_' . $time] ) ) { - $next_show = $radio_station_data['next_show_' . $time]; - } else { - $next_show = get_transient( 'radio_station_next_show_' . $time ); - } - } - - // 2.3.2: added time argument to filter - // 2.3.4: moved filter to where data is set so only applied once - // $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); - } - - return $next_show; -} - -// -------------- -// Get Next Shows -// -------------- -// 2.3.0: added new get next shows function -// 2.3.2: added optional time argument -function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = false ) { - - global $radio_station_data; - - // --- get all show shifts --- - // (this check is needed to prevent an endless loop!) - if ( !$show_shifts ) { - if ( !$time ) { - $show_shifts = radio_station_get_current_schedule(); - } else { - $show_shifts = radio_station_get_current_schedule( $time ); - } - } - - // --- loop (remaining) shifts to add show data --- - $next_shows = array(); - // 2.3.2: maybe set provided time as now - if ( $time ) { - $now = $time; } else { - $now = radio_station_get_now(); - } - - // 2.3.2: use get time function with timezone - // 2.3.2: fix to pass week day start as numerical (w) - // 2.3.3: revert to passing week day start as day (l) - // 2.3.3: added fix to start from previous day - $today = radio_station_get_time( 'l', $now ); - $yesterday = radio_station_get_previous_day( $today ); - $weekdays = radio_station_get_schedule_weekdays( $yesterday ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - if ( RADIO_STATION_DEBUG ) { - echo ''; - echo "Next Shows from " . $yesterday . PHP_EOL; - print_r( $weekdays ); - print_r( $weekdates ); - echo ''; + $term = get_term_by( 'id', $genre, RADIO_STATION_GENRES_SLUG ); } - - // --- loop shifts to find next shows --- - $current_split = false; - foreach ( $weekdays as $day ) { - if ( isset( $show_shifts[$day] ) ) { - $shifts = $show_shifts[$day]; - foreach ( $shifts as $start => $shift ) { - - // --- get this shift start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); - $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - - if ( RADIO_STATION_DEBUG ) { - echo ''; - echo 'Next? ' . $now . ' - ' . $shift_start_time . ' - ' . $shift_end_time . PHP_EOL; - echo print_r( $shift, true ) . PHP_EOL; - echo '' . PHP_EOL; - } - - // --- set current show --- - // 2.3.2: set current show transient - // 2.3.3: remove current show transient - - // --- check if show is upcoming --- - if ( $now < $shift_start_time ) { - - // --- reset skip flag --- - $skip = false; - - if ( $current_split ) { - - $skip = true; - $current_split = false; - - } elseif ( isset( $shift['split'] ) && $shift['split'] ) { - - // --- dedupe for shifts split overnight --- - if ( isset( $shift['real_end'] ) ) { - $shift['end'] = $shift['real_end']; - $current_split = true; - } elseif ( isset( $shift['real_start'] ) ) { - // 2.3.3: skip this shift instead of setting - // (because second half of current show!) - $skip = true; - } - - } else { - // --- reset split shift flag --- - $current_split = false; - } - - if ( !$skip ) { - - // --- maybe set next show transient --- - // 2.3.3: also set global data key - // 2.3.4: moved next show filter here (before setting data) - if ( !isset( $next_show ) ) { - $next_show = $shift; - $next_expires = $shift_end_time - $now - 1; - - $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); - if ( !$time ) { - $radio_station_data['next_show'] = $next_show; - set_transient( 'radio_station_next_show', $next_show, $next_expires ); - } else { - $radio_station_data['next_show_' . $time] = $next_show; - set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); - } - if ( RADIO_STATION_DEBUG ) { - echo '^^^ Next Show ^^^'; - } - } - - // --- add to next shows data --- - // 2.3.2: set date for widget display - $shift['date'] = $weekdates[$day]; - $next_shows[] = $shift; - if ( RADIO_STATION_DEBUG ) { - echo 'Next Shows: ' . print_r( $next_shows, true ) . ''; - } - - // --- return if we have reached limit --- - if ( count( $next_shows ) == $limit ) { - $next_shows = apply_filters( 'radio_station_next_shows', $next_shows, $limit, $show_shifts ); - return $next_shows; - } - } - } - } - } + if ( !$term ) { + return false; } + $genre_data = array(); + $genre_data[$term->name] = array( + 'id' => $term->term_id, + 'name' => $term->name, + 'slug' => $term->slug, + 'description' => $term->description, + 'url' => get_term_link( $term, RADIO_STATION_GENRES_SLUG ), + ); - // --- filter and return --- - $next_shows = apply_filters( 'radio_station_next_shows', $next_shows, $limit, $show_shifts ); - - return $next_shows; + return $genre_data; } -// -------------------- -// Get Current Playlist -// -------------------- -// 2.3.3.5: added get current playlist function -function radio_station_get_current_playlist() { - - $current_show = radio_station_get_current_show(); - $show_id = $current_show['show']['id']; - $playlists = radio_station_get_show_playlists( $show_id ); - if ( !$playlists || !is_array( $playlists ) || ( count ( $playlists ) < 1 ) ) { - return false; - } - - $playlist_id = $playlists[0]['ID']; - $tracks = get_post_meta( $playlist_id, 'playlist', true ); - if ( !$tracks || !is_array( $tracks ) || ( count( $tracks ) < 1 ) ) { - return false; - } +// ---------- +// Get Genres +// ---------- +// 2.3.0: added genres data grabber +function radio_station_get_genres( $args = false ) { - // --- split off tracks marked as queued --- - $entries = $queued = $played = array(); - foreach ( $tracks as $i => $track ) { - foreach ( $track as $key => $value ) { - unset( $track[$key] ); - $key = str_replace( 'playlist_entry_', '', $key ); - $track[$key] = $value; - $entries[$i] = $track; - } - // 2.4.0.3: fix for queued and played arrays - foreach ( $track as $key => $value ) { - if ( 'status' == $key ) { - if ( 'queued' == $value ) { - $queued[] = $track; - } elseif ( 'played' == $value ) { - $played[] = $track; - } - } - } - } - - $latest = array(); - if ( isset( $queued[0] ) ) { - $latest = $queued[0]; - } - - // --- get the track list for display --- - $playlist = array( - 'tracks' => $entries, - 'queued' => $queued, - 'played' => $played, - 'latest' => $latest, - 'id' => $playlist_id, - 'url' => get_permalink( $playlist_id ), - 'show' => $show_id, - 'show_url' => get_permalink( $show_id ), - ); - - return $playlist; -} - -// ----------------------- -// Get Blog Posts for Show -// ----------------------- -// 2.3.0: added show blog post data grabber -function radio_station_get_show_posts( $show_id = false, $args = array() ) { - return radio_station_get_show_data( 'posts', $show_id, $args ); -} - -// ---------------------- -// Get Playlists for Show -// ---------------------- -// 2.3.0: added show playlist data grabber -function radio_station_get_show_playlists( $show_id = false, $args = array() ) { - return radio_station_get_show_data( 'playlists', $show_id, $args ); -} - -// --------- -// Get Genre -// --------- -// 2.3.0: added genre data grabber -function radio_station_get_genre( $genre ) { - // 2.3.3.8: explicitly check for numberic genre term ID - $id = absint( $genre ); - if ( $id < 1 ) { - // $genre = sanitize_title( $genre ); - $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); - if ( !$term ) { - $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); - } - } else { - $term = get_term_by( 'id', $genre, RADIO_STATION_GENRES_SLUG ); - } - if ( !$term ) { - return false; - } - $genre_data = array(); - $genre_data[$term->name] = array( - 'id' => $term->term_id, - 'name' => $term->name, - 'slug' => $term->slug, - 'description' => $term->description, - 'url' => get_term_link( $term, RADIO_STATION_GENRES_SLUG ), - ); - - return $genre_data; -} - -// ---------- -// Get Genres -// ---------- -// 2.3.0: added genres data grabber -function radio_station_get_genres( $args = false ) { - - $defaults = array( 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'orderby' => 'name', 'hide_empty' => true ); - if ( $args && is_array( $args ) ) { - foreach ( $args as $key => $value ) { - $defaults[$key] = $value; + $defaults = array( 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'orderby' => 'name', 'hide_empty' => true ); + if ( $args && is_array( $args ) ) { + foreach ( $args as $key => $value ) { + $defaults[$key] = $value; } } $terms = get_terms( $defaults ); @@ -2427,2026 +1037,597 @@ function radio_station_get_genres( $args = false ) { 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, - 'description' => $term->description, - 'url' => get_term_link( $term, RADIO_STATION_GENRES_SLUG ), - ); - } - } - - // --- filter and return --- - $genres = apply_filters( 'radio_station_get_genres', $genres, $args ); - - return $genres; -} - -// ------------------- -// Get Shows for Genre -// ------------------- -// 2.3.0: added get shows for genre data grabber -function radio_station_get_genre_shows( $genre = false ) { - - if ( !$genre ) { - // --- get shows without a genre assigned --- - // ref: https://core.trac.wordpress.org/ticket/29181 - $tax_query = array( - array( - 'taxonomy' => RADIO_STATION_GENRES_SLUG, - 'operator' => 'NOT EXISTS', - ), - ); - } else { - // --- get shows with specific genre assigned --- - $tax_query = array( - array( - 'taxonomy' => RADIO_STATION_GENRES_SLUG, - 'field' => 'slug', - 'terms' => $genre, - ), - ); - } - $args = array( - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', - 'tax_query' => $tax_query, - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), - ), - ); - $args = apply_filters( 'radio_station_show_genres_query_args', $args, $genre ); - $shows = new WP_Query( $args ); - - return $shows; -} - -// ---------------------- -// Get Shows for Language -// ---------------------- -// 2.3.0: added get shows for language data grabber -function radio_station_get_language_shows( $language = false ) { - - if ( !$language ) { - // --- get shows without a language assigned --- - // ref: https://core.trac.wordpress.org/ticket/29181 - $tax_query = array( - array( - 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, - 'operator' => 'NOT EXISTS', - ), - ); - } else { - // --- get shows with specific language assigned --- - $tax_query = array( - array( - 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, - 'field' => 'slug', - 'terms' => $language, - ), - ); - } - - $args = array( - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', - 'tax_query' => $tax_query, - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), - ), - ); - $args = apply_filters( 'radio_station_show_languages_query_args', $args, $language ); - $shows = new WP_Query( $args ); - - return $shows; -} - - -// ---------------------- -// === Shift Checking === -// ---------------------- - -// ------------------------- -// Schedule Conflict Checker -// ------------------------- -// (checks all existing show shifts for schedule) -// 2.3.0: added show shift conflict checker -function radio_station_check_shifts( $all_shifts ) { - - // TODO: check for start of week and end of week shift conflicts? - - // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts - $now = radio_station_get_now(); - $today = radio_station_get_time( 'l', $now ); - $weekdays = radio_station_get_schedule_weekdays( $today ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - $conflicts = $checked_shifts = array(); - if ( count( $all_shifts ) > 0 ) { - $prev_shift = $prev_prev_shift = false; - // foreach ( $all_shifts as $day => $shifts ) { - foreach ( $weekdays as $day ) { - - if ( isset( $all_shifts[$day] ) ) { - $shifts = $all_shifts[$day]; - - // --- get previous and next days for comparisons --- - // 2.3.2: fix to use week date schedule - $thisdate = $weekdates[$day]; - $date_time = radio_station_to_time( $weekdates[$day] . ' 00:00' ); - - // --- check for conflicts (overlaps) --- - foreach ( $shifts as $key => $shift ) { - - // --- set first shift checked --- - // 2.3.2: added for checking against last shift - if ( !isset( $first_shift ) ) { - $first_shift = $shift; - } - $last_shift = $shift; - - // --- reset shift switches --- - $set_shift = true; - $conflict = $disabled = false; - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $disabled = true; - } - - // --- account for split midnight times --- - // 2.3.2: replace strtotime with to_time for timezones - if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { - $start_time = radio_station_to_time( $thisdate . ' 00:00' ); - } else { - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $start_time = radio_station_to_time( $thisdate . ' ' . $shift_start ); - } - if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { - $end_time = radio_station_to_time( $thisdate . ' 11:59:59' ) + 1; - } else { - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $end_time = radio_station_to_time( $thisdate . ' ' . $shift_end ); - } - - if ( false != $prev_shift ) { - - // note: previous shift start and end times set in previous loop iteration - if ( RADIO_STATION_DEBUG ) { - echo "Shift Date: " . $thisdate . " - Day: " . $day . " - Time: " . $date_time . PHP_EOL; - $prevdata = $prev_shift['shift']; - $prevday = $prev_shift['day']; - $prevdate = $prev_shift['date']; - echo "Previous Shift Date: " . $prevdate . " - Shift Day: " . $prevday . PHP_EOL; - echo "Shift: " . print_r( $shift, true ); - echo "Previous Shift: " . print_r( $prev_shift, true ); - } - - // --- detect shift conflicts --- - // (and maybe *attempt* to fix them up) - if ( isset( $prev_start_time ) && ( $start_time == $prev_start_time ) ) { - if ( $shift['split'] || $prev_shift['split'] ) { - $conflict = 'overlap'; - if ( $shift['split'] && $prev_shift['split'] ) { - // - need to compare start times on previous day - - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $data = $shift['shift']; - $real_start = radio_station_convert_shift_time( $data['real_start'] ); - $shiftdate = radio_station_get_previous_date( $thisdate ); - // $real_start_time = radio_station_to_time( $prevdate . ' ' . $real_start ); - $real_start_time = radio_station_to_time( $shiftdate . ' ' . $real_start ); - - // 2.3.2: fix to calculation of previous shift day start time - $prevdata = $prev_shift['shift']; - // $prevday = $prevdata['day']; - // $prevdate = radio_station_get_previous_date( $thisdate, $prevday ); - $prevdate = $prev_shift['date']; - $prev_real_start = radio_station_convert_shift_time( $prevdata['real_start'] ); - $prev_real_start_time = radio_station_to_time( $prevdate . ' ' . $prev_real_start ); - - // --- compare start times --- - if ( $real_start_time > $prev_real_start_time ) { - // - current shift started later (overwrite from midnight) - - $set_shift = true; - } elseif ( $real_start_time == $prev_real_start_time ) { - // - do not duplicate, already recorded - - $conflict = false; - // - total overlap, check last updated post time - - $updated = strtotime( $shift['updated'] ); - $prev_updated = strtotime( $prev_shift['updated'] ); - if ( $updated < $prev_updated ) { - $set_shift = false; - } - } - } elseif ( $shift['split'] ) { - // - the current shift has been split overnight - - // assume previous shift is correct (ignore new shift time) - $set_shift = false; - } elseif ( $prev_shift['split'] ) { - // the previous shift has been split overnight - // so we will assume the new shift start is correct - // (overwrites previous shift from midnight key) - $set_shift = true; - } - } else { - $conflict = 'same_start'; - // - we do not know which of these is correct - - // no solution here, so check most recent last updated time - // we will assume (without certainty) most recent is correct - $updated = strtotime( $shift['updated'] ); - $prev_updated = strtotime( $prev_shift['updated'] ); - if ( $updated < $prev_updated ) { - $set_shift = false; - } - } - } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { - - if ( ( $end_time > $prev_start_time ) || ( $end_time > prev_end_time ) ) { - - // --- set the previous shift end time to current shift start --- - $conflict = 'overlap'; - - // --- modify only if this shift is not disabled --- - if ( !$disabled ) { - // 2.3.2: variable type fix (from checked_shift) - // 2.3.2: fix for midnight starting aplit shifts - // 2.3.2: set checked shifts with day key directly - if ( '00:00 am' == $prev_shift['start'] ) { - $prev_shift['start'] = '12:00 am'; - } - $checked_shifts[$day][$prev_shift['start']]['end'] = $shift['start']; - $checked_shifts[$day][$prev_shift['start']]['trimmed'] = true; - - if ( RADIO_STATION_DEBUG ) { - echo "Previous Previous Shift: " . print_r( $prev_prev_shift, true ); - } - - // --- fix for real end of first part of previous split shift --- - if ( isset( $prev_shift['split'] ) && $prev_shift['split'] && isset( $prev_shift['real_start'] ) ) { - if ( isset( $prev_prev_shift ) && isset( $prev_prev_shift['split'] ) && $prev_prev_shift['split'] ) { - $checked_shifts[$prev_prev_shift['start']]['real_end'] = $shift['start']; - $checked_shifts[$prev_prev_shift['start']]['trimmed'] = true; - } - } - } - - // --- conflict debug output --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; - $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $start_time ) . ' ]'; - $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; - $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $prev_end_time ) . ' ]'; - // $debug .= "Shift: " . print_r( $shift, true ); - // $debug .= "Previous Shift: " . print_r( $prev_shift, true ); - radio_station_debug( $debug ); - } - } - } - } - - // --- maybe store shift conflict data --- - if ( $conflict ) { - - // ---- set short shift time data --- - $shift_start = $shift['shift']['start_hour'] . ':' . $shift['shift']['start_min'] . $shift['shift']['start_meridian']; - $shift_end = $shift['shift']['end_hour'] . ':' . $shift['shift']['end_min'] . $shift['shift']['end_meridian']; - $prev_shift_start = $prev_shift['shift']['start_hour'] . ':' . $prev_shift['shift']['start_min'] . $prev_shift['shift']['start_meridian']; - $prev_shift_end = $prev_shift['shift']['end_hour'] . ':' . $prev_shift['shift']['end_min'] . $prev_shift['shift']['end_meridian']; - - // --- store conflict for this shift --- - $conflicts[$shift['show']][] = array( - 'show' => $shift['show'], - 'day' => $shift['shift']['day'], - 'start' => $shift_start, - 'end' => $shift_end, - 'disabled' => $disabled, - 'with_show' => $prev_shift['show'], - 'with_day' => $prev_shift['shift']['day'], - 'with_start' => $prev_shift_start, - 'with_end' => $prev_shift_end, - 'with_disabled' => $prev_disabled, - 'conflict' => $conflict, - 'duplicate' => false, - ); - - // --- store for previous shift only if a different show --- - if ( $shift['show'] != $prev_shift['show'] ) { - $conflicts[$prev_shift['show']][] = array( - 'show' => $prev_shift['show'], - 'day' => $prev_shift['shift']['day'], - 'start' => $prev_shift_start, - 'end' => $prev_shift_end, - 'disabled' => $prev_disabled, - 'with_show' => $shift['show'], - 'with_day' => $shift['shift']['day'], - 'with_start' => $shift_start, - 'with_end' => $shift_end, - 'with_disabled' => $disabled, - 'conflict' => $conflict, - 'duplicate' => true, - ); - } - } - - // --- set current shift to previous for next iteration --- - $prev_start_time = $start_time; - $prev_end_time = $end_time; - if ( $prev_shift ) { - $prev_prev_shift = $prev_shift; - } - $prev_shift = $shift; - $prev_disabled = $disabled; - - // --- set the now checked shift data --- - // (...but only if not disabled!) - if ( $set_shift && !$disabled ) { - // - no longer need shift and post updated times - - // 2.3.3.9: keep shift ID with schedule data - $shift['id'] = $shift['shift']['id']; - unset( $shift['shift'] ); - unset( $shift['updated'] ); - if ( '00:00 am' == $shift['start'] ) { - $shift['start'] = '12:00 am'; - } - // 2.3.2: set checked shifts with day key directly - $checked_shifts[$day][$shift['start']] = $shift; - } - } - } - - // --- set checked shifts for day --- - // 2.3.2: set checked shifts with day key directly - // $all_shifts[$day] = $checked_shifts; - } - } - - // --- check last shift against first shift --- - // 2.3.2: added for possible overlap (split by weekly schedule dates) - if ( isset( $last_shift ) && ( $last_shift != $first_shift ) ) { - - // --- use days for different weeks to compare --- - $l_shift_start = radio_station_convert_shift_time( $last_shift['start'] ); - $l_shift_end = radio_station_convert_shift_time( $last_shift['end'] ); - $last_shift_start = radio_station_to_time( $last_shift['day'] . ' ' . $l_shift_start ); - $last_shift_end = radio_station_to_time( $last_shift['day'] . ' ' . $l_shift_end ); - if ( $last_shift_end < $last_shift_start ) { - $last_shift_end = $last_shift_end + ( 24 * 60 * 60 ); - } - - $f_shift_start = radio_station_convert_shift_time( $first_shift['start'] ); - $f_shift_end = radio_station_convert_shift_time( $first_shift['end'] ); - $first_shift_start = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_start ); - $first_shift_end = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_end ); - if ( $first_shift_end < $first_shift_start ) { - $first_shift_end = $first_shift_end + ( 24 * 60 * 60 ); - } - - if ( RADIO_STATION_DEBUG ) { - echo 'Last Shift End: ' . $last_shift['day'] . ' ' . $l_shift_end . ' - (' . $last_shift_end . ')' . PHP_EOL; - echo 'First Shift Start: ' . $first_shift['day'] . ' ' . $f_shift_start . ' - (' . $first_shift_start . ')' . PHP_EOL; - } - - // --- end of the week overlap check --- - // 2.3.3.9: fix to incorrect overlap logic - if ( $last_shift_end > $first_shift_start ) { - // if ( $last_shift_start < $first_shift_end ) { - - // --- record a conflict --- - if ( RADIO_STATION_DEBUG ) { - echo "First/Last Shift Overlap Conflict" . PHP_EOL; - echo "First Shift: " . print_r( $first_shift, true ); - echo "Last Shift: " . print_r( $last_shift, true ); - } - - /* - // --- store conflict for this shift --- - $conflicts[$first_shift['show']][] = array( - 'show' => $first_shift['show'], - 'day' => $first_shift['shift']['day'], - 'start' => $first_shift_start, - 'end' => $first_shift_end, - 'disabled' => $first_shift['shift']['disabled'], - 'with_show' => $last_shift['show'], - 'with_day' => $last_shift['shift']['day'], - 'with_start' => $last_shift_start, - 'with_end' => $last_shift_end, - 'with_disabled' => $last_shift['shift']['disabled'], - 'conflict' => 'overlap', - 'duplicate' => false, - ); - - // --- store for other shift if different show --- - if ( $first_shift['show'] != $last_shift['show'] ) { - $conflicts[$last_shift['show']][] = array( - 'show' => $last_shift['show'], - 'day' => $last_shift['shift']['day'], - 'start' => $last_shift_start, - 'end' => $last_shift_end, - 'disabled' => $last_shift['shift']['disabled'], - 'with_show' => $first_shift['show'], - 'with_day' => $first_shift['shift']['day'], - 'with_start' => $first_shift_start, - 'with_end' => $first_shift_end, - 'with_disabled' => $first_shift['shift']['disabled'], - 'conflict' => 'overlap', - 'duplicate' => true, - ); - } */ - } - } - - // --- check if any conflicts found --- - if ( count( $conflicts ) > 0 ) { - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Shift Conflict Data: " . print_r( $conflicts, true ) . PHP_EOL; - radio_station_debug( $debug ); - } - - // --- save any conflicts found --- - update_option( 'radio_station_schedule_conflicts', $conflicts ); - - } else { - // --- clear conflicts data --- - delete_option( 'radio_station_schedule_conflicts' ); - } - - return $checked_shifts; -} - -// ------------------ -// Show Shift Checker -// ------------------ -// (checks shift being saved against other shows) -function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { - - global $radio_station_data; - - // 2.3.2: bug out if day is empty - if ( '' == $shift['day'] ) { - return false; - } - - // 2.3.2: manual bypass of shift checking - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - return false; - } - - // --- get all show shift times --- - if ( isset( $radio_station_data['all-shifts'] ) ) { - // --- get stored data --- - $all_shifts = $radio_station_data['all-shifts']; - } else { - // (with conflict checking off as we are doing that now) - $all_shifts = radio_station_get_show_shifts( false, false ); - - // --- store this data for efficiency --- - $radio_station_data['all-shifts'] = $all_shifts; - } - - // --- convert days to dates for checking --- - // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts - $now = radio_station_get_now(); - $today = radio_station_get_time( 'l', $now ); - $weekdays = radio_station_get_schedule_weekdays( $today ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - // --- get shows to check against via context --- - $check_shifts = array(); - if ( 'all' == $context ) { - $check_shifts = $all_shifts; - } elseif ( 'shows' == $context ) { - // --- check only against other show shifts --- - foreach ( $all_shifts as $day => $day_shifts ) { - foreach ( $day_shifts as $start => $day_shift ) { - // --- ...so remove (skip) any shifts for this show --- - if ( $day_shift['show'] != $show_id ) { - $check_shifts[$day][$start] = $day_shift; - } - } - } - } - - // 2.3.2: to doubly ensure shifts are set in schedule order - $sorted_shifts = array(); - foreach ( $weekdays as $weekday ) { - if ( isset( $check_shifts[$weekday] ) ) { - $sorted_shifts[$weekday] = $check_shifts[$weekday]; - } - } - $check_shifts = $sorted_shifts; - - // --- get shift start and end time --- - // 2.3.2: fix to convert to 24 hour times first - // 2.3.3.9: set shift start and end to prevent undefined index warning later - $shift['start'] = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; - $shift['end'] = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; - $start_time = radio_station_convert_shift_time( $shift['start'] ); - $end_time = radio_station_convert_shift_time( $shift['end'] ); - - // 2.3.2: use next week day instead of date - $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - // 2.3.3.9: added or equals to operator - if ( $shift_end_time <= $shift_start_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } - - if ( RADIO_STATION_DEBUG ) { - echo "Checking Shift for Show " . $show_id . ": "; - echo $shift['day'] . " - " . $weekdates[$shift['day']] . " - " . $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; - echo "(" . $shift_start_time . ")"; - echo " to " . $weekdates[$shift['day']] . " - " . $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - echo "(" . $shift_end_time . ")" . PHP_EOL; - } - - // --- check for conflicts with other show shifts --- - $conflicts = array(); - foreach ( $check_shifts as $day => $day_shifts ) { - // 2.3.2: removed day match check - // if ( $day == $shift['day'] ) { - foreach ( $day_shifts as $i => $day_shift ) { - - if ( !isset( $first_shift ) ) { - $first_shift = $day_shift; - } - - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour times first - $day_shift_start = radio_station_convert_shift_time( $day_shift['start'] ); - $day_shift_end = radio_station_convert_shift_time( $day_shift['end'] ); - - // 2.3.2: use next week day instead of date - $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $day_shift_start ); - $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $day_shift_end ); - // 2.3.2: adjust for midnight with change to use non-split shifts - // 2.3.3.9: added or equals to operator - if ( $day_shift_end_time <= $day_shift_start_time ) { - $day_shift_end_time = $day_shift_end_time + ( 24 * 60 * 60 ); - } - - // --- ignore if this is the same shift we are checking --- - $check_shift = true; - if ( $day_shift['show'] == $show_id ) { - // ? only ignore same shift not same show ? - // if ( ( $day_shift_start_time == $shift_start_time ) && ( $day_shift_end_time == $shift_end_time ) ) { - $check_shift = false; - // } - } - - if ( $check_shift ) { - - if ( RADIO_STATION_DEBUG ) { - echo "...with Shift for Show " . $day_shift['show'] . ": "; - echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; - echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; - } - - // 2.3.2: improved shift checking logic - // 2.3.3.6: separated logic for conflict match code - $conflict = false; - if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { - // if the new shift starts before existing shift but ends after existing shift starts - $conflict = 'start overlap'; - } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { - // ...or starts before but ends after the existing shift end time - $conflict = 'blockout overlap'; - } elseif ( ( $shift_start_time == $day_shift_start_time ) ) { - // ...or new shift starts at the same time as the existing shift - $conflict = 'equal start time'; - } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { - // ...or if the new shift starts after existing shift and ends before it ends - $conflict = 'internal overlap'; - } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { - // ...or the new shift starts after the existing shift but before it ends - $conflict = 'end overlap'; - } - if ( $conflict ) { - // --- if there is a shift overlap conflict --- - $conflicts[] = $day_shift; - if ( RADIO_STATION_DEBUG ) { - echo '^^^ CONFLICT ( ' . $conflict . ' ) ^^^' . PHP_EOL; - } - } - } - } - // } - } - - // --- recheck for first shift overlaps --- - // (not implemented as not needed) - /* if ( isset( $first_shift ) ) { - // --- check for first shift overlap using next week --- - $shift_start = radio_station_convert_shift_time( $first_shift['start'] ); - $shift_end = radio_station_convert_shift_time( $first_shift['end'] ); - $first_shift_start_time = radio_station_to_time( $first_shift['date'] . ' ' . $shift_start ) + ( 7 * 24 * 60 * 60 ); - $first_shift_end_time = radio_station_to_time( $first_shift['date'] . ' ' . $shift_end ) + ( 7 * 24 * 60 * 60 ); - - if ( RADIO_STATION_DEBUG ) { - echo "...with First Shift for Show " . $first_shift['show'] . ": "; - echo $first_shift['day'] . " - " . $first_shift['date'] . " - " . $first_shift['start'] . " (" . $first_shift_start_time . ")"; - echo " to " . $first_shift['end'] . " (" . $first_shift_end_time . ")" . PHP_EOL; - } - - if ( ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_start_time ) ) - || ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_end_time ) ) - || ( $shift_start_time == $first_shift_start_time ) - || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_end_time < $first_shift_end_time ) ) - || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_start_time < $first_shift_end_time ) ) ) { - $conflicts[] = $first_shift; - if ( RADIO_STATION_DEBUG ) { - echo "^^^ CONFLICT ^^^" . PHP_EOL; - } - } - } */ - - // --- recheck for last shift --- - // (for date based schedule overflow rechecking) - if ( isset( $day_shift ) ) { - - // 2.3.3.6: added check to not last check shift against itself - // TODO: check possible re-occurrence undefined index 'start' warning here ? - if ( ( $day_shift['show'] != $show_id ) - || ( $day_shift['day'] != $shift['day'] ) - || ( $day_shift['start'] != $shift['start'] ) - || ( $day_shift['end'] != $shift['end'] ) ) { - - // --- check for new shift overlap using next week --- - $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); - $shift_end_time = $shift_end_time + ( 7 * 24 * 60 * 60 ); - - if ( RADIO_STATION_DEBUG ) { - echo "...with Last Shift (using next week):" . PHP_EOL; - // echo radio_station_get_time( 'date', $shift_start_time ) . " - " . $shift['start'] . " (" . $shift_start_time . ")"; - // echo " to " . $shift['end'] . " (" . $shift_end_time . ")" . PHP_EOL; - echo $day_shift['day'] . " - " . radio_station_get_time( 'date', $day_shift_start_time ); - echo " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; - echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; - } - - // 2.3.3.6: separated logic for conflict match code - $conflict = false; - if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { - $conflict = 'start overlap'; - } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { - $conflict = 'blockout overlap'; - } elseif ( $shift_start_time == $day_shift_start_time ) { - $conflict = 'equal start time'; - } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { - $conflict = 'internal overlap'; - } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { - $conflict = 'end overlap'; - } - if ( $conflict ) { - $conflicts[] = $day_shift; - if ( RADIO_STATION_DEBUG ) { - echo '^^^ CONFLICT ( ' . $conflict . ') ^^^' . PHP_EOL; - } - } - } - } - - if ( count( $conflicts ) == 0 ) { - return false; - } - - return $conflicts; -} - -// ------------------ -// New Shifts Checker -// ------------------ -// (checks show shifts for conflicts with same show) -function radio_station_check_new_shifts( $new_shifts ) { - - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - if ( RADIO_STATION_DEBUG ) { - echo "New Shift Checking Bypassed." . PHP_EOL; - } - return $new_shifts; - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "New Shifts: " . print_r( $new_shifts, true ); - // print_r( $debug ); - radio_station_debug( $debug ); - } - - // --- convert days to dates for checking --- - $now = radio_station_get_now(); - $weekdays = radio_station_get_schedule_weekdays(); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - // --- double loop shifts to check against others --- - foreach ( $new_shifts as $i => $shift_a ) { - - if ( '' != $shift_a['day'] ) { - - // --- get shift A start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_a_start = $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; - $shift_a_end = $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; - $shift_a_start = radio_station_convert_shift_time( $shift_a_start ); - $shift_a_end = radio_station_convert_shift_time( $shift_a_end ); - - // 2.3.2: use next week day instead of date - $shift_a_start_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_start ); - $shift_a_end_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); - // $shift_a_start_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_start ); - // $shift_a_end_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_end ); - // 2.3.3.9: added or equals to operator - if ( $shift_a_end_time <= $shift_a_start_time ) { - $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; - $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; - $debug = "Shift A Start: " . $a_start . PHP_EOL . 'Shift A End: ' . $a_end . PHP_EOL; - // print_r( $debug ); - radio_station_debug( $debug ); - } - - foreach ( $new_shifts as $j => $shift_b ) { - - if ( $i != $j ) { - - if ( RADIO_STATION_DEBUG ) { - echo $i . ' ::: ' . $j . PHP_EOL; - } - - if ( '' != $shift_b['day'] ) { - // --- get shift B start and end times --- - // 2.3.2: replace strtotime with to_time for timezones - $shift_b_start = $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; - $shift_b_end = $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; - $shift_b_start = radio_station_convert_shift_time( $shift_b_start ); - $shift_b_end = radio_station_convert_shift_time( $shift_b_end ); - - // 2.3.2: use next week day instead of date - $shift_b_start_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_start ); - $shift_b_end_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end ); - // $shift_b_start_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_start ); - // $shift_b_end_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_end ); - // 2.3.3.9: added or equals to operator - if ( $shift_b_end_time <= $shift_b_start_time ) { - $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $b_start = $shift_b['day'] . ' ' . $shift_b_start . ' (' . $shift_b_start_time . ')'; - $b_end = $shift_b['day'] . ' ' . $shift_b_end . ' (' . $shift_b_end_time . ')'; - $debug = "with Shift B Start: " . $b_start . ' - Shift B End: ' . $b_end . PHP_EOL; - // radio_station_debug( $debug, false, 'show-shift-save.log' ); - radio_station_debug( $debug ); - } - - // --- compare shift A and B times --- - if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) - || ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) - || ( $shift_a_start_time == $shift_b_start_time ) - || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) - || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) ) { - - // --- maybe disable shift B --- - // 2.3.2: added check for isset on disabled key - if ( ( !isset( $new_shifts[$i]['disabled'] ) || ( 'yes' != $new_shifts[$i]['disabled'] ) ) - && ( !isset( $new_shifts[$j]['disabled'] ) || ( 'yes' != $new_shifts[$j]['disabled'] ) ) ) { - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = PHP_EOL . "* Conflict Found! New Shift (B) Disabled "; - if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) {$debug .= "[A]";} - if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) {$debug .= "[B]";} - if ( $shift_a_start_time == $shift_b_start_time ) {$debug .= "[C]";} - if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) {$debug .= "[D]";} - if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) {$debug .= "[E]";} - $debug .= "*" . PHP_EOL; - radio_station_debug( $debug ); - } - - $new_shifts[$j]['disabled'] = 'yes'; - - } else { - if ( RADIO_STATION_DEBUG ) { - echo "[Conflict with disabled shift.]" . PHP_EOL; - } - } - } - } - } - } - } - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $debug = "Checked New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; - radio_station_debug( $debug ); - // print_r( $debug ); - } - - return $new_shifts; -} - -// ------------------- -// Validate Shift Time -// ------------------- -// 2.3.0: added check for incomplete shift times -function radio_station_validate_shift( $shift ) { - - if ( '' == $shift['day'] ) { - $shift['disabled'] = 'yes'; - } - if ( ( '' == $shift['start_meridian'] ) || ( '' == $shift['end_meridian'] ) ) { - $shift['disabled'] = 'yes'; - } - if ( ( '' == $shift['start_hour'] ) || ( '' == $shift['end_hour'] ) ) { - $shift['disabled'] = 'yes'; - } - if ( '' == $shift['start_min'] ) { - $shift['start_min'] = '00'; - } - if ( '' == $shift['end_min'] ) { - $shift['end_min'] = '00'; - } - - return $shift; -} - - -// ------------------- -// === Show Avatar === -// ------------------- - -// ------------------ -// Update Show Avatar -// ------------------ -// 2.3.0: trigger show avatar check/update when editing -add_action( 'replace_editor', 'radio_station_update_show_avatar', 10, 2 ); -function radio_station_update_show_avatar( $replace_editor, $post ) { - - // 2.3.3.9: fix to only apply to image-specific post types - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); - $post_types = apply_filters( 'radio_station_show_avatar_post_types', $post_types ); - if ( in_array( $post->post_type, $post_types ) ) { - $show_id = $post->ID; - radio_station_get_show_avatar_id( $show_id ); - } else { - // 2.3.3.9: add cleanup for non-intended post types - delete_post_meta( $post->ID, 'show_avatar' ); - delete_post_meta( $post->ID, '_rs_image_updated' ); - } - - return $replace_editor; -} - -// ------------------ -// Get Show Avatar ID -// ------------------ -// 2.3.0: added get show avatar ID with thumbnail update -// note: existing thumbnail (featured image) ID is duplicated to the show avatar ID, -// allowing for handling of Show Avatars and Featured Images separately. -function radio_station_get_show_avatar_id( $show_id ) { - - // --- get thumbnail and avatar ID --- - $avatar_id = get_post_meta( $show_id, 'show_avatar', true ); - - // --- check thumbnail to avatar updated switch --- - $updated = get_post_meta( $show_id, '_rs_image_updated', true ); - if ( !$updated ) { - if ( !$avatar_id ) { - $thumbnail_id = get_post_meta( $show_id, '_thumbnail_id', true ); - if ( $thumbnail_id ) { - // --- duplicate the existing thumbnail to avatar meta --- - $avatar_id = $thumbnail_id; - add_post_meta( $show_id, 'show_avatar', $avatar_id ); - } - } - // --- add a flag indicating image has been updated --- - add_post_meta( $show_id, '_rs_image_updated', true ); - } - - // --- filter and return --- - $avatar_id = apply_filters( 'radio_station_show_avatar_id', $avatar_id, $show_id ); - - return $avatar_id; -} - -// ------------------- -// Get Show Avatar URL -// ------------------- -// 2.3.0: added to get the show avatar URL -function radio_station_get_show_avatar_url( $show_id, $size = 'thumbnail' ) { - - // --- get avatar ID --- - $avatar_id = radio_station_get_show_avatar_id( $show_id ); - - // --- get the attachment image source --- - $avatar_url = false; - if ( $avatar_id ) { - // 2.4.0.6: added show avatar size filter - $size = apply_filters( 'radio_station_show_avatar_size', $size ); - $avatar_src = wp_get_attachment_image_src( $avatar_id, $size ); - $avatar_url = $avatar_src[0]; - } - - // --- filter and return --- - // 2.4.0.6: added third argument for avatar size - $avatar_url = apply_filters( 'radio_station_show_avatar_url', $avatar_url, $show_id, $size ); - return $avatar_url; -} - -// --------------- -// Get Show Avatar -// --------------- -// 2.3.0: added this function for getting show avatar tag -function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = array() ) { - - // --- get avatar ID --- - $avatar_id = radio_station_get_show_avatar_id( $show_id ); - - // --- get the attachment image tag --- - $avatar = false; - if ( $avatar_id ) { - // 2.4.0.6: added show avatar size filter - $size = apply_filters( 'radio_station_show_avatar_size', $size ); - $avatar = wp_get_attachment_image( $avatar_id, $size, false, $attr ); - } - - // --- filter and return --- - // 2.3.3.9: change conflicting (duplicate) filter name for show avatar - // 2.4.0.6: added third argument for avatar size - $avatar = apply_filters( 'radio_station_show_avatar_output', $avatar, $show_id, $size ); - return $avatar; -} - - -// --------------------- -// === URL Functions === -// --------------------- - -// ----------------- -// Get Streaming URL -// ----------------- -// 2.3.0: added get streaming URL helper -function radio_station_get_stream_url() { - $streaming_url = ''; - $stream = radio_station_get_setting( 'streaming_url' ); - if ( RADIO_STATION_DEBUG ) { - echo 'Stream URL Setting: '; var_dump( $stream ); echo ''; - } - if ( $stream && ( '' != $stream ) ) { - $streaming_url = $stream; - } - $streaming_url = apply_filters( 'radio_station_stream_url', $streaming_url ); - - return $streaming_url; -} - -// ----------------- -// Get Streaming URL -// ----------------- -// 2.3.3.9: added get fallback URL helper -function radio_station_get_fallback_url() { - $fallback_url = ''; - $fallback = radio_station_get_setting( 'fallback_url' ); - if ( $fallback && ( '' != $fallback ) ) { - $fallback_url = $fallback; - } - if ( RADIO_STATION_DEBUG ) { - echo 'Fallback URL Setting: '; var_dump( $fallback_url ); echo ''; - } - $fallback_url = apply_filters( 'radio_station_fallback_url', $fallback_url ); - - return $fallback_url; -} - -// Get Stream Formats -// ------------------ -// 2.3.3.7: added streaming format options -function radio_station_get_stream_formats() { - - // TODO: recheck amplitude formats ? - // [Amplitude] HTML5 Support - mp3, aac ...? - // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers - // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm - // +mpeg, oga, caf, weba, webm, dolby, flac - // [JPlayer] Audio: mp3, m4a - Video: m4v - // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv - // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv - - $formats = array( - 'aac' => 'AAC/M4A', // A/H/J - 'mp3' => 'MP3', // A/H/J - 'ogg' => 'OGG', // H - 'oga' => 'OGA', // H/J - 'webm' => 'WebM', // H/J - 'rtmpa' => 'RTMPA', // J - 'opus' => 'OPUS', // H - ); - - // --- filter and return --- - $formats = apply_filters( 'radio_station_stream_formats', $formats ); - return $formats; -} - -// --------------- -// Get Station URL -// --------------- -function radio_station_get_station_url() { - $station_url = ''; - $page_id = radio_station_get_setting( 'station_page' ); - if ( $page_id && ( '' != $page_id ) ) { - $station_url = get_permalink( $page_id ); - } - $station_url = apply_filters( 'radio_station_station_url', $station_url ); - - return $station_url; -} - -// --------------------- -// Get Station Image URL -// --------------------- -// 2.3.3.8: added get station logo image URL -function radio_station_get_station_image_url() { - $station_image = ''; - $attachment_id = radio_station_get_setting( 'station_image' ); - $image = wp_get_attachment_image_src( $attachment_id, 'full' ); - if ( is_array( $image ) ) { - $station_image = $image[0]; - } - $station_image = apply_filters( 'radio_station_station_image_url', $station_image ); - - return $station_image; -} - -// ---------------------------- -// Get Master Schedule Page URL -// ---------------------------- -// 2.3.0: added get master schedule URL permalink -function radio_station_get_schedule_url() { - $schedule_url = ''; - $page_id = radio_station_get_setting( 'schedule_page' ); - if ( $page_id && ( '' != $page_id ) ) { - $schedule_url = get_permalink( $page_id ); - } - $schedule_url = apply_filters( 'radio_station_schedule_url', $schedule_url ); - - return $schedule_url; -} - -// ------------------------- -// Get Radio Station API URL -// ------------------------- -function radio_station_get_api_url() { - $routes = radio_station_get_setting( 'enable_data_routes' ); - $feeds = radio_station_get_setting( 'enable_data_feeds' ); - $rest_url = get_rest_url( null, '/' ); - $api_url = false; - if ( ( 'yes' == $routes ) && !empty( $rest_url ) ) { - $api_url = radio_station_get_route_url( '' ); - } elseif ( 'yes' == $feeds ) { - $api_url = radio_station_get_feed_url( 'radio' ); - } - $api_url = apply_filters( 'radio_station_api_url', $api_url ); - - return $api_url; -} - -// ------------- -// Get Route URL -// ------------- -function radio_station_get_route_url( $route ) { - - global $radio_station_routes; - - // --- maybe return cached route URL --- - if ( isset( $radio_station_routes[$route] ) ) { - return $radio_station_routes[$route]; - } - - /// --- get route URL --- - $base = apply_filters( 'radio_station_route_slug_base', 'radio' ); - if ( '' != $route ) { - $route = apply_filters( 'radio_station_route_slug_' . $route, $route ); - } - if ( '' == $route ) { - $path = '/' . $base . '/'; - } elseif ( !$route ) { - return false; - } else { - $path = '/' . $base . '/' . $route . '/'; - } - - // --- cache route URL --- - // echo ""; - $radio_station_routes[$route] = $route_url = get_rest_url( null, $path ); - - return $route_url; -} - -// ------------ -// Get Feed URL -// ------------ -function radio_station_get_feed_url( $feedname ) { - - global $radio_station_feeds; - - // --- maybe return cached feed URL --- - if ( isset( $radio_station_feeds[$feedname] ) ) { - return $radio_station_feeds[$feedname]; - } - - // --- get feed URL --- - $feedname = apply_filters( 'radio_station_feed_slug_' . $feedname, $feedname ); - if ( !$feedname ) { - return false; - } - - // --- cache feed URL --- - $radio_station_feeds[$feedname] = $feed_url = get_feed_link( $feedname ); - - return $feed_url; -} - -// ---------------- -// Get Show RSS URL -// ---------------- -function radio_station_get_show_rss_url( $show_id ) { - // TODO: combine comments and full show content - $rss_url = get_post_comments_feed_link( $show_id ); - $rss_url = add_query_arg( 'withoutcomments', '1', $rss_url ); - - return $rss_url; -} - -// ------------------------- -// Get DJ / Host Profile URL -// ------------------------- -// 2.3.0: added to get DJ / Host author/profile permalink -// 2.3.3.9: moved get possible profile ID to Pro filter -function radio_station_get_host_url( $host_id ) { - $host_url = get_author_posts_url( $host_id ); - $host_url = apply_filters( 'radio_station_host_url', $host_url, $host_id ); - return $host_url; -} - -// ------------------------ -// Get Producer Profile URL -// ------------------------ -// 2.3.0: added to get Producer author/profile permalink -// 2.3.3.9: moved get possible profile ID to Pro filter -function radio_station_get_producer_url( $producer_id ) { - $producer_url = get_author_posts_url( $producer_id ); - $producer_url = apply_filters( 'radio_station_producer_url', $producer_url, $producer_id ); - return $producer_url; -} - -// --------------- -// Get Upgrade URL -// --------------- -// 2.3.0: added to get Upgrade to Pro link -function radio_station_get_upgrade_url() { - - // TODO: test Freemius upgrade to Pro URL - // ...maybe it is -addons instead of -pricing ??? - // $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - - $upgrade_url = RADIO_STATION_PRO_URL . 'pricing/'; - - return $upgrade_url; -} - -// ------------------------ -// Patreon Supporter Button -// ------------------------ -// 2.2.2: added simple patreon supporter image button -// 2.3.0: added Patreon page argument -// 2.3.0: moved from radio-station-admin.php -function radio_station_patreon_button( $page, $title = '' ) { - $image_url = plugins_url( 'images/patreon-button.jpg', RADIO_STATION_FILE ); - $button = ''; - $button .= ''; - $button .= ''; - - // 2.3.0: add button styling to footer - if ( is_admin() ) { - add_action( 'admin_footer', 'radio_station_patreon_button_styles' ); - } else { - add_action( 'wp_footer', 'radio_station_patreon_button_styles' ); + 'description' => $term->description, + 'url' => get_term_link( $term, RADIO_STATION_GENRES_SLUG ), + ); + } } // --- filter and return --- - $button = apply_filters( 'radio_station_patreon_button', $button, $page ); - return $button; -} - -// --------------------- -// Patreon Button Styles -// --------------------- -// 2.3.0: added separately in footer -function radio_station_patreon_button_styles() { - // 2.2.7: added button hover opacity - echo ''; -} + $genres = apply_filters( 'radio_station_get_genres', $genres, $args ); -// -------------------- -// Queue Directory Ping -// -------------------- -// 2.3.2: queue directory ping on saving -function radio_station_queue_directory_ping() { - // 2.3.3.9: fix to bug out during plugin activation - if ( !function_exists( 'radio_station_get_setting' ) ) { - return; - } - $queue_ping = radio_station_get_setting( 'ping_netmix_directory' ); - if ( 'yes' == $queue_ping ) { - update_option( 'radio_station_ping_directory', '1' ); - } + return $genres; } // ------------------- -// Send Directory Ping +// Get Shows for Genre // ------------------- -// 2.3.1: added directory ping function prototype -function radio_station_send_directory_ping() { - $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); - if ( 'yes' != $do_ping ) {return;} - - // --- set the URL to ping --- - // 2.3.2: fix url_encode to urlencode - $site_url = site_url(); - $url = add_query_arg( 'ping', 'directory', RADIO_STATION_NETMIX_DIR ); - $url = add_query_arg( 'station-url', urlencode( $site_url ), $url ); - $url = add_query_arg( 'timestamp', time(), $url ); +// 2.3.0: added get shows for genre data grabber +function radio_station_get_genre_shows( $genre = false ) { - // --- send the ping --- - $args = array( 'timeout' => 10 ); - if ( !function_exists( 'wp_remote_get' ) ) { - include_once ABSPATH . WPINC . '/http.php'; - } - $response = wp_remote_get( $url, $args ); - if ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { - echo 'Directory Ping Response:'; - echo ''; + if ( !$genre ) { + // --- get shows without a genre assigned --- + // ref: https://core.trac.wordpress.org/ticket/29181 + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'operator' => 'NOT EXISTS', + ), + ); + } else { + // --- get shows with specific genre assigned --- + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'slug', + 'terms' => $genre, + ), + ); } - return $response; -} + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'tax_query' => $tax_query, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ), + ); + $args = apply_filters( 'radio_station_show_genres_query_args', $args, $genre ); + $shows = new WP_Query( $args ); -// ------------------- -// Check and Send Ping -// ------------------- -// 2.3.2: send queued directory ping -add_action( 'admin_footer', 'radio_station_check_directory_ping', 99 ); -function radio_station_check_directory_ping() { - $ping = get_option( 'radio_station_ping_directory' ); - if ( $ping ) { - $response = radio_station_send_directory_ping(); - if ( !is_wp_error( $response ) && isset( $response['response']['code'] ) && ( 200 == $response['response']['code'] ) ) { - delete_option( 'radio_station_ping_directory' ); - } - } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { - $response = radio_station_send_directory_ping(); - } + return $shows; } -// ------------------------ -// === Time Conversions === -// ------------------------ - -// ------- -// Get Now -// ------- -// 2.3.2: added for current time consistency -function radio_station_get_now( $gmt = true ) { +// ---------------------- +// Get Shows for Language +// ---------------------- +// 2.3.0: added get shows for language data grabber +function radio_station_get_language_shows( $language = false ) { - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $now = strtotime( current_time( 'mysql' ) ); + if ( !$language ) { + // --- get shows without a language assigned --- + // ref: https://core.trac.wordpress.org/ticket/29181 + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'operator' => 'NOT EXISTS', + ), + ); } else { - $datetime = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); - $now = $datetime->format( 'U' ); + // --- get shows with specific language assigned --- + $tax_query = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'slug', + 'terms' => $language, + ), + ); } - return $now; + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'tax_query' => $tax_query, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ), + ); + $args = apply_filters( 'radio_station_show_languages_query_args', $args, $language ); + $shows = new WP_Query( $args ); + + return $shows; } -// ------------ -// Get Timezone -// ------------ -// 2.3.2: added get timezone with fallback -function radio_station_get_timezone() { - $timezone = radio_station_get_setting( 'timezone_location' ); - if ( !$timezone || ( '' == $timezone ) ) { +// ------------------- +// === Show Avatar === +// ------------------- - // --- fallback to WordPress timezone --- - $timezone = get_option( 'timezone_string' ); - if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { - $timezone = ''; +// --------------- +// Get Image Sizes +// --------------- +// 2.5.0: added for widget field dropdowns +function radio_station_get_image_sizes() { + + // --- get image size names --- + $image_sizes = array( + 'thumbnail' => __( 'Thumbnail' ), + 'medium' => __( 'Medium' ), + 'large' => __( 'Large' ), + 'full' => __( 'Full Size' ), + ); + $image_sizes = apply_filters( 'image_size_names_choose', $image_sizes ); + if ( isset( $image_sizes['full'] ) ) { + unset( $image_sizes['full'] ); + } + + /// --- get image size dimensions --- + $wp_additional_image_sizes = wp_get_additional_image_sizes(); + $get_intermediate_image_sizes = get_intermediate_image_sizes(); + foreach ( $get_intermediate_image_sizes as $size ) { + if ( in_array( $size, array( 'thumbnail', 'medium', 'large' ) ) ) { + $sizes[$size]['width'] = get_option( $size . '_size_w' ); + $sizes[$size]['height'] = get_option( $size . '_size_h' ); + // $sizes[$size]['crop'] = (bool) get_option( $size . '_crop' ); + } elseif ( isset( $wp_additional_image_sizes[$size] ) ) { + $sizes[$size] = array( + 'width' => $wp_additional_image_sizes[$size]['width'], + 'height' => $wp_additional_image_sizes[$size]['height'], + // 'crop' => $wp_additional_image_sizes[$size]['crop'], + ); } - } - if ( '' == $timezone ) { - $offset = get_option( 'gmt_offset' ); - $timezone = 'UTC' . $offset; + // --- loop size names to add dimensions --- + foreach ( $image_sizes as $image_size => $label ) { + if ( isset( $sizes[$image_size]['width'] ) && isset( $sizes[$image_size]['height'] ) ) { + $image_sizes[$image_size] = $label . ' (' . $sizes[$image_size]['width'] . 'x' . $sizes[$image_size]['height'] . ')'; + } } - - return $timezone; + return $image_sizes; } -// ----------------- -// Get Timezone Code -// ----------------- -// note: this should only be used for display purposes -// (as the actual code used is based on timezone/location) -function radio_station_get_timezone_code( $timezone ) { - $datetime = new DateTime( 'now', new DateTimeZone( $timezone ) ); - return $datetime->format( 'T' ); -} +// ------------------ +// Update Show Avatar +// ------------------ +// 2.3.0: trigger show avatar check/update when editing +add_action( 'replace_editor', 'radio_station_update_show_avatar', 10, 2 ); +function radio_station_update_show_avatar( $replace_editor, $post ) { -// -------------------- -// Get Date Time Object -// -------------------- -// 2.3.2: added for consistent timezone conversions -function radio_station_get_date_time( $timestring, $timezone ) { - - if ( 'UTC' == $timezone ) { - $utc = new DateTimeZone( 'UTC' ); - $datetime = new DateTime( $timestring, $utc ); - } elseif ( strstr( $timezone, 'UTC' ) ) { - $offset = str_replace( 'UTC', '', $timezone ); - $offset = (int)$offset * 60 * 60; - $utc = new DateTimeZone( 'UTC' ); - $datetime = new DateTime( $timestring, $utc ); - $timestamp = $datetime->format( 'U' ); - $timestamp = $timestamp + $offset; - $datetime->setTimestamp( $timestamp ); + // 2.3.3.9: fix to only apply to image-specific post types + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + $post_types = apply_filters( 'radio_station_show_avatar_post_types', $post_types ); + if ( in_array( $post->post_type, $post_types ) ) { + $show_id = $post->ID; + radio_station_get_show_avatar_id( $show_id ); } else { - $datetime = new DateTime( $timestring, new DateTimeZone( $timezone ) ); - // ...fix to set timestamp again just in case - // echo "A: " . print_r( $datetime, true ) . PHP_EOL; - // if ( '@' == substr( $timestring, 0, 1 ) ) { - // $timestamp = substr( $timestring, 1, strlen( $timestring ) ); - // $datetime->setTimestamp( $timestamp ); - // } - // echo "B: " . print_r( $datetime, true ) . PHP_EOL; + // 2.3.3.9: add cleanup for non-intended post types + delete_post_meta( $post->ID, 'show_avatar' ); + delete_post_meta( $post->ID, '_rs_image_updated' ); } - return $datetime; + return $replace_editor; } -// -------------- -// String To Time -// -------------- -// 2.3.2: added for timezone handling -function radio_station_to_time( $timestring ) { +// ------------------ +// Get Show Avatar ID +// ------------------ +// 2.3.0: added get show avatar ID with thumbnail update +// note: existing thumbnail (featured image) ID is duplicated to the show avatar ID, +// allowing for handling of Show Avatars and Featured Images separately. +function radio_station_get_show_avatar_id( $show_id ) { + + // --- get thumbnail and avatar ID --- + $avatar_id = get_post_meta( $show_id, 'show_avatar', true ); - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $time = strtotime( $timestring ); - } else { - $timezone = radio_station_get_timezone(); - if ( strstr( $timezone, 'UTC' ) && ( 'UTC' != $timezone ) ) { - // --- fallback for UTC offsets --- - $offset = str_replace( 'UTC', '', $timezone ); - $offset = (int)$offset * 60 * 60; - $utc = new DateTimeZone( 'UTC' ); - $datetime = new DateTime( $timestring, $utc ); - $timestamp = $datetime->getTimestamp(); - $timestamp = $timestamp - $offset; - $datetime->setTimestamp( $timestamp ); - } else { - $datetime = radio_station_get_date_time( $timestring, $timezone ); + // --- check thumbnail to avatar updated switch --- + $updated = get_post_meta( $show_id, '_rs_image_updated', true ); + if ( !$updated ) { + if ( !$avatar_id ) { + $thumbnail_id = get_post_meta( $show_id, '_thumbnail_id', true ); + if ( $thumbnail_id ) { + // --- duplicate the existing thumbnail to avatar meta --- + $avatar_id = $thumbnail_id; + add_post_meta( $show_id, 'show_avatar', $avatar_id ); + } } - $time = $datetime->format( 'U' ); - + // --- add a flag indicating image has been updated --- + add_post_meta( $show_id, '_rs_image_updated', true ); } - return $time; -} - -// -------- -// Get Time -// -------- -// 2.3.2: added for timezone adjustments -function radio_station_get_time( $key = false, $time = false ) { - - // --- get offset time and date --- - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + // --- filter and return --- + $avatar_id = apply_filters( 'radio_station_show_avatar_id', $avatar_id, $show_id ); - if ( !$time ) { - $time = radio_station_get_now(); - } - $day = date( 'l', $time ); - $date = date( 'Y-m-d', $time ); - $date_time = date( 'Y-m-d H:i:s', $time ); - $timestamp = date( 'U', $time ); + return $avatar_id; +} - } else { +// ------------------- +// Get Show Avatar URL +// ------------------- +// 2.3.0: added to get the show avatar URL +function radio_station_get_show_avatar_url( $show_id, $size = 'thumbnail' ) { - if ( !$time ) { - $timestring = 'now'; - } else { - $timestring = '@' . $time; - } + // --- get avatar ID --- + $avatar_id = radio_station_get_show_avatar_id( $show_id ); - // --- get timezone --- - $timezone = radio_station_get_timezone(); - if ( strstr( $timezone, 'UTC' ) ) { - $datetime = radio_station_get_date_time( $timestring, $timezone ); - } else { - // ...and refix for location timezones - $datetime = new DateTime( $timestring, new DateTimeZone( 'UTC' ) ); - $datetime->setTimezone( new DateTimeZone( $timezone ) ); - } + // --- get the attachment image source --- + $avatar_url = false; + if ( $avatar_id ) { + // 2.4.0.6: added show avatar size filter + $size = apply_filters( 'radio_station_show_avatar_size', $size ); + $avatar_src = wp_get_attachment_image_src( $avatar_id, $size ); + $avatar_url = $avatar_src[0]; + } - // --- set formatted strings --- - $day = $datetime->format( 'l' ); - $date = $datetime->format( 'Y-m-d' ); - $date_time = $datetime->format( 'Y-m-d H:i:s' ); - $timestamp = $datetime->format( 'U' ); + // --- filter and return --- + // 2.4.0.6: added third argument for avatar size + $avatar_url = apply_filters( 'radio_station_show_avatar_url', $avatar_url, $show_id, $size ); + return $avatar_url; +} - } +// --------------- +// Get Show Avatar +// --------------- +// 2.3.0: added this function for getting show avatar tag +function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = array() ) { - $times = array( - 'day' => $day, - 'date' => $date, - 'datetime' => $date_time, - 'timestamp' => $timestamp, - ); + // --- get avatar ID --- + $avatar_id = radio_station_get_show_avatar_id( $show_id ); - if ( $key ) { - if ( array_key_exists( $key, $times ) ) { - $time = $times[$key]; - } elseif ( isset( $datetime ) ) { - $time = $datetime->format( $key ); - } else { - $time = date( $key, $time ); - } - } elseif ( RADIO_STATION_DEBUG ) { - echo 'Time key not found: ' . $key . PHP_EOL; - echo 'Times: ' . print_r( $times, true ) . ''; + // --- get the attachment image tag --- + $avatar = false; + if ( $avatar_id ) { + // 2.4.0.6: added show avatar size filter + $size = apply_filters( 'radio_station_show_avatar_size', $size ); + $avatar = wp_get_attachment_image( $avatar_id, $size, false, $attr ); } - return $time; + // --- filter and return --- + // 2.3.3.9: change conflicting (duplicate) filter name for show avatar + // 2.4.0.6: added third argument for avatar size + $avatar = apply_filters( 'radio_station_show_avatar_output', $avatar, $show_id, $size ); + return $avatar; } -// -------------------- -// Get Timezone Options -// -------------------- -// ref: (based on) https://stackoverflow.com/a/17355238/5240159 -function radio_station_get_timezone_options( $include_wp_timezone = false ) { - // --- maybe get stored timezone options --- - $options = get_transient( 'radio-station-timezone-options' ); - if ( !$options ) { - - // --- set regions --- - $regions = array( - DateTimeZone::AFRICA => __( 'Africa', 'radio-station' ), - DateTimeZone::AMERICA => __( 'America', 'radio-station' ), - DateTimeZone::ASIA => __( 'Asia', 'radio-station' ), - DateTimeZone::ATLANTIC => __( 'Atlantic', 'radio-station' ), - DateTimeZone::AUSTRALIA => __( 'Australia', 'radio-station' ), - DateTimeZone::EUROPE => __( 'Europe', 'radio-station' ), - DateTimeZone::INDIAN => __( 'Indian', 'radio-station' ), - DateTimeZone::PACIFIC => __( 'Pacific', 'radio-station' ), - DateTimeZone::ANTARCTICA => __( 'Antarctica', 'radio-station' ), - ); +// --------------------- +// === URL Functions === +// --------------------- - // --- loop regions --- - foreach ( $regions as $region => $label ) { +// ----------------- +// Get Streaming URL +// ----------------- +// 2.3.0: added get streaming URL helper +function radio_station_get_stream_url() { + $streaming_url = ''; + $stream = radio_station_get_setting( 'streaming_url' ); + if ( RADIO_STATION_DEBUG ) { + echo 'Stream URL Setting: ' . esc_html( $stream ) . ''; + } + if ( $stream && ( '' != $stream ) ) { + $streaming_url = $stream; + } + $streaming_url = apply_filters( 'radio_station_stream_url', $streaming_url ); - // --- option group by region --- - $options['*OPTGROUP*' . $region] = $label; + return $streaming_url; +} - $timezones = DateTimeZone::listIdentifiers( $region ); - $timezone_offsets = array(); - foreach ( $timezones as $timezone ) { - $datetimezone = new DateTimeZone( $timezone ); - $offset = $datetimezone->getOffset( new DateTime() ); - $timezone_offsets[$offset][] = $timezone; - } - ksort( $timezone_offsets ); - - foreach ( $timezone_offsets as $offset => $timezones ) { - foreach ( $timezones as $timezone ) { - $prefix = $offset < 0 ? '-' : '+'; - $hour = gmdate( 'H', abs( $offset ) ); - $hour = gmdate( 'H', abs( $offset ) ); - $minutes = gmdate( 'i', abs( $offset ) ); - $code = radio_station_get_timezone_code( $timezone ); - $label = $code . ' (GMT' . $prefix . $hour . ':' . $minutes . ') - '; - $timezone_split = explode( '/', $timezone ); - unset( $timezone_split[0] ); - $timezone_joined = implode( '/', $timezone_split ); - $label .= str_replace( '_', ' ', $timezone_joined ); - $options[$timezone] = $label; - } - } - } - $expiry = 7 * 24 * 60 * 60; - set_transient( 'radio-station-timezone-options', $options, $expiry ); +// ----------------- +// Get Streaming URL +// ----------------- +// 2.3.3.9: added get fallback URL helper +function radio_station_get_fallback_url() { + $fallback_url = ''; + $fallback = radio_station_get_setting( 'fallback_url' ); + if ( $fallback && ( '' != $fallback ) ) { + $fallback_url = $fallback; } - - // --- maybe add WordPress timezone (default) option --- - if ( $include_wp_timezone ) { - $wp_timezone = array( '' => __( 'WordPress Timezone', 'radio-station' ) ); - $options = array_merge( $wp_timezone, $options ); + if ( RADIO_STATION_DEBUG ) { + echo 'Fallback URL Setting: ' . esc_html( $fallback_url ) . ''; } + $fallback_url = apply_filters( 'radio_station_fallback_url', $fallback_url ); - $options = apply_filters( 'radio_station_get_timezone_options', $options, $include_wp_timezone ); - - return $options; + return $fallback_url; } -// -------------- -// Get Weekday(s) -// -------------- -// 2.3.2: added get weekday from number helper -function radio_station_get_weekday( $day_number = null ) { - - $weekdays = array( - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', +// ------------------ +// Get Stream Formats +// ------------------ +// 2.3.3.7: added streaming format options +function radio_station_get_stream_formats() { + + // TODO: recheck amplitude formats ? + // [Amplitude] HTML5 Support - mp3, aac ...? + // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers + // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm + // +mpeg, oga, caf, weba, webm, dolby, flac + // [JPlayer] Audio: mp3, m4a - Video: m4v + // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv + // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv + + $formats = array( + 'aac' => 'AAC/M4A', // A/H/J + 'mp3' => 'MP3', // A/H/J + 'ogg' => 'OGG', // H + 'oga' => 'OGA', // H/J + 'webm' => 'WebM', // H/J + 'rtmpa' => 'RTMPA', // J + 'opus' => 'OPUS', // H ); - if ( !is_null( $day_number ) ) { - return $weekdays[$day_number]; - } - return $weekdays; + // --- filter and return --- + $formats = apply_filters( 'radio_station_stream_formats', $formats ); + return $formats; } -// ------------ -// Get Month(s) -// ------------ -// 2.3.2: added get weekday from number helper -function radio_station_get_month( $month_number = null ) { - - $months = array( - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - ); - if ( !is_null( $month_number ) ) { - return $months[$month_number]; +// --------------- +// Get Station URL +// --------------- +function radio_station_get_station_url() { + $station_url = ''; + $page_id = radio_station_get_setting( 'station_page' ); + if ( $page_id && ( '' != $page_id ) ) { + $station_url = get_permalink( $page_id ); } - return $months; + $station_url = apply_filters( 'radio_station_station_url', $station_url ); + + return $station_url; } // --------------------- -// Get Schedule Weekdays +// Get Station Image URL // --------------------- -// note: no translations here because used internally for sorting -// 2.3.0: added to get schedule weekdays from start of week -function radio_station_get_schedule_weekdays( $weekstart = false ) { - - // --- maybe get start of the week --- - if ( !$weekstart ) { - $weekstart = get_option( 'start_of_week' ); - $weekstart = apply_filters( 'radio_station_schedule_weekday_start', $weekstart ); +// 2.3.3.8: added get station logo image URL +function radio_station_get_station_image_url() { + $station_image = ''; + $attachment_id = radio_station_get_setting( 'station_image' ); + $image = wp_get_attachment_image_src( $attachment_id, 'full' ); + if ( is_array( $image ) ) { + $station_image = $image[0]; } + $station_image = apply_filters( 'radio_station_station_image_url', $station_image ); - $weekdays = array( - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ); + return $station_image; +} - // 2.3.2: also accept string format for weekstart - if ( is_string( $weekstart ) ) { - // 2.3.3.5: accept today as valid week start - if ( 'today' == $weekstart ) { - $weekstart = radio_station_get_time( 'day' ); - } - foreach ( $weekdays as $i => $weekday ) { - if ( strtolower( $weekday ) == strtolower( $weekstart ) ) { - $weekstart = $i; - } - } +// ---------------------------- +// Get Master Schedule Page URL +// ---------------------------- +// 2.3.0: added get master schedule URL permalink +function radio_station_get_schedule_url() { + $schedule_url = ''; + $page_id = radio_station_get_setting( 'schedule_page' ); + if ( $page_id && ( '' != $page_id ) ) { + $schedule_url = get_permalink( $page_id ); } + $schedule_url = apply_filters( 'radio_station_schedule_url', $schedule_url ); - // --- loop weekdays and reorder from start day --- - $start = $before = $after = array(); - foreach ( $weekdays as $i => $weekday ) { - // 2.3.2: allow matching of numerical index or weekday name - if ( ( $i == $weekstart ) || ( $weekday == $weekstart ) ) { - $start[] = $weekday; - } elseif ( $i > $weekstart ) { - $after[] = $weekday; - } elseif ( $i < $weekstart ) { - $before[] = $weekday; - } - } + return $schedule_url; +} - // --- put the days before the start day at the end --- - $weekdays = array_merge( $start, $after ); - $weekdays = array_merge( $weekdays, $before ); +// ------------------------- +// Get Radio Station API URL +// ------------------------- +function radio_station_get_api_url() { + $routes = radio_station_get_setting( 'enable_data_routes' ); + $feeds = radio_station_get_setting( 'enable_data_feeds' ); + $rest_url = get_rest_url( null, '/' ); + $api_url = false; + if ( ( 'yes' == $routes ) && !empty( $rest_url ) ) { + $api_url = radio_station_get_route_url( '' ); + } elseif ( 'yes' == $feeds ) { + $api_url = radio_station_get_feed_url( 'radio' ); + } + $api_url = apply_filters( 'radio_station_api_url', $api_url ); - return $weekdays; + return $api_url; } -// ---------------------- -// Get Schedule Weekdates -// ---------------------- -// 2.3.0: added for date based calculations -function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { +// ------------- +// Get Route URL +// ------------- +function radio_station_get_route_url( $route ) { - if ( !$time ) { - $time = radio_station_get_now(); - } + global $radio_station_routes; - // 2.3.2: use timezone setting to get offset date - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $today = date( 'l', $time ); - } else { - // 2.3.3.9: fix to use radio_station_get_time - // $timezone = radio_station_get_timezone(); - // $datetime = radio_station_get_date_time( '@' . $time, $timezone ); - // $today = $datetime->format( 'l' ); - $today = radio_station_get_time( 'day', $time ); + // --- maybe return cached route URL --- + if ( isset( $radio_station_routes[$route] ) ) { + return $radio_station_routes[$route]; } - // --- get weekday index for today --- - $weekdates = array(); - foreach ( $weekdays as $i => $weekday ) { - if ( $weekday == $today ) { - $index = $i; - } - } - foreach ( $weekdays as $i => $weekday ) { - $diff = $index - $i; - $weekdate_time = $time - ( $diff * 24 * 60 * 60 ); - // 2.3.2: include timezone adjustment - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $weekdate = date( 'Y-m-d', $weekdate_time ); - } else { - // 2.3.3.9: fix to use radio_station_get_time - // $weekdatetime = radio_station_get_date_time( '@' . $weekdate_time, $timezone ); - // $weekdate = $weekdatetime->format( 'Y-m-d' ); - $weekdate = radio_station_get_time( 'Y-m-d', $weekdate_time ); - } - $weekdates[$weekday] = $weekdate; + /// --- get route URL --- + $base = apply_filters( 'radio_station_route_slug_base', 'radio' ); + if ( '' != $route ) { + $route = apply_filters( 'radio_station_route_slug_' . $route, $route ); } - - // 2.4.0.4: check/fix for duplicate date crackliness (daylight saving?) - foreach ( $weekdates as $day => $date ) { - if ( isset( $prevdate ) && ( $prevdate == $date ) ) { - $weekdates[$day] = radio_station_get_next_date( $date ); - $found = false; - foreach ( $weekdates as $k => $v ) { - if ( $found ) { - $weekdates[$k] = radio_station_get_next_date( $v ); - } - if ( $k == $day ) { - $found = true; - } - } - } - $prevdate = $date; + if ( '' == $route ) { + $path = '/' . $base . '/'; + } elseif ( !$route ) { + return false; + } else { + $path = '/' . $base . '/' . $route . '/'; } - if ( RADIO_STATION_DEBUG ) { - echo ''; - echo 'Time: ' . $time . PHP_EOL; - echo 'Today: ' . $today . PHP_EOL; - if ( isset( $datetime ) ) { - echo 'Date Time Object: ' . print_r( $datetime, true ); - } - echo 'Weekdays: ' . print_r( $weekdays, true ); - echo 'Weekdates: ' . print_r( $weekdates, true ); - echo ''; - } + // --- cache route URL --- + // echo ""; + $radio_station_routes[$route] = $route_url = get_rest_url( null, $path ); - return $weekdates; + return $route_url; } // ------------ -// Get Next Day +// Get Feed URL // ------------ -// 2.3.0: added get next day helper -function radio_station_get_next_day( $day ) { - // note: for internal use so not translated - $day = trim( $day ); - if ( 'Sunday' == $day ) { - return 'Monday'; - } - if ( 'Monday' == $day ) { - return 'Tuesday'; - } - if ( 'Tuesday' == $day ) { - return 'Wednesday'; - } - if ( 'Wednesday' == $day ) { - return 'Thursday'; - } - if ( 'Thursday' == $day ) { - return 'Friday'; - } - if ( 'Friday' == $day ) { - return 'Saturday'; +function radio_station_get_feed_url( $feedname ) { + + global $radio_station_feeds; + + // --- maybe return cached feed URL --- + if ( isset( $radio_station_feeds[$feedname] ) ) { + return $radio_station_feeds[$feedname]; } - if ( 'Saturday' == $day ) { - return 'Sunday'; + + // --- get feed URL --- + $feedname = apply_filters( 'radio_station_feed_slug_' . $feedname, $feedname ); + if ( !$feedname ) { + return false; } - return ''; + // --- cache feed URL --- + $radio_station_feeds[$feedname] = $feed_url = get_feed_link( $feedname ); + + return $feed_url; } // ---------------- -// Get Previous Day +// Get Show RSS URL // ---------------- -// 2.3.0: added get previous day helper -function radio_station_get_previous_day( $day ) { - // note: for internal use so not translated - $day = trim( $day ); - if ( 'Sunday' == $day ) { - return 'Saturday'; - } - if ( 'Monday' == $day ) { - return 'Sunday'; - } - if ( 'Tuesday' == $day ) { - return 'Monday'; - } - if ( 'Wednesday' == $day ) { - return 'Tuesday'; - } - if ( 'Thursday' == $day ) { - return 'Wednesday'; - } - if ( 'Friday' == $day ) { - return 'Thursday'; - } - if ( 'Saturday' == $day ) { - return 'Friday'; - } - - return ''; -} +function radio_station_get_show_rss_url( $show_id ) { + // TODO: combine comments and full show content + $rss_url = get_post_comments_feed_link( $show_id ); + $rss_url = add_query_arg( 'withoutcomments', '1', $rss_url ); -// ------------- -// Get Next Date -// ------------- -// 2.3.2: added for more reliable calculations -function radio_station_get_next_date( $date, $weekday = false ) { - - // note: this is used internally so timezone not used - $timedate = strtotime( $date ); - $timedate = $timedate + ( 24 * 60 * 60 ); - if ( $weekday ) { - $day = date( 'l', $timedate ); - if ( $day != $weekday ) { - $i = 0; - while ( $day != $weekday ) { - $timedate = $timedate + ( 24 * 60 * 60 ); - $day = strtotime( 'l', $timedate ); - if ( 8 == $i ) { - // - failback for while failure - - $timedate = strtotime( $date ); - $next_date = date( 'next ' . $weekday, $timedate ); - return $next_date; - } - $i++; - } - } - } - $next_date = date( 'Y-m-d', $timedate ); - return $next_date; + return $rss_url; } -// ----------------- -// Get Previous Date -// ----------------- -// 2.3.2: added for more reliable calculations -function radio_station_get_previous_date( $date, $weekday = false ) { - - // note: this is used internally so timezone not used - $timedate = strtotime( $date ); - $timedate = $timedate - ( 24 * 60 * 60 ); - if ( $weekday ) { - $day = date( 'l', $timedate ); - if ( $day != $weekday ) { - $i = 0; - while ( $day != $weekday ) { - $timedate = $timedate - ( 24 * 60 * 60 ); - $day = strtotime( 'l', $timedate ); - if ( 8 == $i ) { - // - failback for while failure - - $timedate = strtotime( $date ); - $previous_date = date( 'previous ' . $weekday, $timedate ); - return $previous_date; - } - $i++; - } - } - } - $previous_date = date( 'Y-m-d', $timedate ); - return $previous_date; +// ------------------------- +// Get DJ / Host Profile URL +// ------------------------- +// 2.3.0: added to get DJ / Host author/profile permalink +// 2.3.3.9: moved get possible profile ID to Pro filter +function radio_station_get_host_url( $host_id ) { + $host_url = get_author_posts_url( $host_id ); + $host_url = apply_filters( 'radio_station_host_url', $host_url, $host_id ); + return $host_url; } -// ------------- -// Get All Hours -// ------------- -function radio_station_get_hours( $format = 24 ) { - $hours = array(); - if ( 24 === (int) $format ) { - $hours = array( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ); - } elseif ( 12 === (int) $format ) { - $hours = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ); - } - return $hours; +// ------------------------ +// Get Producer Profile URL +// ------------------------ +// 2.3.0: added to get Producer author/profile permalink +// 2.3.3.9: moved get possible profile ID to Pro filter +function radio_station_get_producer_url( $producer_id ) { + $producer_url = get_author_posts_url( $producer_id ); + $producer_url = apply_filters( 'radio_station_producer_url', $producer_url, $producer_id ); + return $producer_url; } -// --------------------------- -// Convert Hour to Time Format -// --------------------------- -// (note: used with suffix for on-the-hour times) -// 2.3.0: standalone function via master-schedule-default.php -// 2.3.0: optionally add suffix for both time formats -function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { - - $hour = intval( $hour ); +// --------------- +// Get Upgrade URL +// --------------- +// 2.3.0: added to get Upgrade to Pro link +function radio_station_get_upgrade_url() { - // 2.3.0: handle next and previous hours (over 24 or below 0) - if ( $hour < 0 ) { - while ( $hour < 0 ) { - $hour = $hour + 24; - } - } - if ( $hour > 24 ) { - while ( $hour > 24 ) { - $hour = $hour - 24; - } - } + // TODO: test Freemius upgrade to Pro URL + // ...maybe it is -addons instead of -pricing ??? + // $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - if ( 24 === (int) $timeformat ) { - // --- 24 hour time format --- - if ( 24 == $hour ) { - $hour = '00'; - } elseif ( $hour < 10 ) { - $hour = '0' . $hour; - } - if ( $suffix ) { - $hour .= ':00'; - } - } elseif ( 12 === (int) $timeformat ) { - // --- 12 hour time format --- - // 2.2.7: added meridiem translations - if ( ( $hour === 0 ) || ( 24 === $hour ) ) { - // midnight - $hour = '12'; - if ( $suffix ) { - $hour .= ' ' . radio_station_translate_meridiem( 'am' ); - } - } elseif ( $hour < 12 ) { - // morning - if ( $suffix ) { - $hour .= ' ' . radio_station_translate_meridiem( 'am' ); - } - } elseif ( 12 === $hour ) { - // noon - if ( $suffix ) { - $hour .= ' ' . radio_station_translate_meridiem( 'pm' ); - } - } elseif ( $hour > 12 ) { - // after-noon - $hour = $hour - 12; - if ( $suffix ) { - $hour .= ' ' . radio_station_translate_meridiem( 'pm' ); - } - } - } - // 2.3.2: fix for possible double spacing - $hour = str_replace( ' ', ' ', $hour ); + $upgrade_url = RADIO_STATION_PRO_URL . 'pricing/'; - return $hour; + return $upgrade_url; } -// ---------------------------- -// Convert Shift to Time Format -// ---------------------------- -// 2.3.0: added to convert shift time to 24 hours (or back) -function radio_station_convert_shift_time( $time, $timeformat = 24 ) { - - // note: timezone can be ignored here as just getting hours and minutes - // 2.3.3.9: added space between date and time - $timestamp = strtotime( date( 'Y-m-d' ) . ' ' . $time ); - if ( 12 == (int) $timeformat ) { - $time = date( 'g:i a', $timestamp ); - str_replace( 'am', radio_station_translate_meridiem( 'am' ), $time ); - str_replace( 'pm', radio_station_translate_meridiem( 'pm' ), $time ); - } elseif ( 24 == (int) $timeformat ) { - $time = date( 'H:i', $timestamp ); +// ------------------------ +// Patreon Supporter Button +// ------------------------ +// 2.2.2: added simple patreon supporter image button +// 2.3.0: added Patreon page argument +// 2.3.0: moved from radio-station-admin.php +function radio_station_patreon_button( $page, $title = '' ) { + $image_url = plugins_url( 'images/patreon-button.jpg', RADIO_STATION_FILE ); + $button = ''; + $button .= ''; + $button .= ''; + + // 2.3.0: add button styling to footer + if ( is_admin() ) { + add_action( 'admin_footer', 'radio_station_patreon_button_styles' ); + } else { + add_action( 'wp_footer', 'radio_station_patreon_button_styles' ); } - return $time; + // --- filter and return --- + $button = apply_filters( 'radio_station_patreon_button', $button, $page ); + return $button; } -// ------------------ -// Convert Show Shift -// ------------------ -// 2.3.0: 24 format shift for broadcast data endpoint -function radio_station_convert_show_shift( $shift ) { +// --------------------- +// Patreon Button Styles +// --------------------- +// 2.3.0: added separately in footer +function radio_station_patreon_button_styles() { + // 2.2.7: added button hover opacity + echo ''; +} - // note: timezone can be ignored here as getting hours and minutes - if ( isset( $shift['start'] ) ) { - $shift['start'] = date( 'H:i', strtotime( $shift['start'] ) ); +// -------------------- +// Queue Directory Ping +// -------------------- +// 2.3.2: queue directory ping on saving +function radio_station_queue_directory_ping() { + // 2.3.3.9: fix to bug out during plugin activation + if ( !function_exists( 'radio_station_get_setting' ) ) { + return; } - if ( isset( $shift['end'] ) ) { - $shift['end'] = date( 'H:i', strtotime( $shift['end'] ) ); + $queue_ping = radio_station_get_setting( 'ping_netmix_directory' ); + if ( 'yes' == $queue_ping ) { + update_option( 'radio_station_ping_directory', '1' ); } - return $shift; } // ------------------- -// Convert Show Shifts +// Send Directory Ping // ------------------- -// 2.3.0: 24 format shifts for show data endpoints -function radio_station_convert_show_shifts( $show ) { - - if ( isset( $show['schedule'] ) ) { - $schedule = $show['schedule']; - foreach ( $schedule as $i => $shift ) { - // 2.3.3.9: fixed to not use radio_station_convert_hour - $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); - $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); - // 2.3.3.9: added missing shift encore designation - // 2.4.0.6: fix to undefined index warning for encore - $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; - $schedule[$i] = array( - 'day' => $shift['day'], - 'start' => $start_hour . ':' . $shift['start_min'], - 'end' => $end_hour . ':' . $shift['end_min'], - 'encore' => $encore, - ); - } - $show['schedule'] = $schedule; +// 2.3.1: added directory ping function prototype +function radio_station_send_directory_ping() { + $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); + if ( 'yes' != $do_ping ) {return;} + + // --- set the URL to ping --- + // 2.3.2: fix url_encode to urlencode + $site_url = site_url(); + $url = add_query_arg( 'ping', 'directory', RADIO_STATION_NETMIX_DIR ); + $url = add_query_arg( 'station-url', urlencode( $site_url ), $url ); + $url = add_query_arg( 'timestamp', time(), $url ); + + // --- send the ping --- + $args = array( 'timeout' => 10 ); + if ( !function_exists( 'wp_remote_get' ) ) { + include_once ABSPATH . WPINC . '/http.php'; } - return $show; + $response = wp_remote_get( $url, $args ); + if ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + echo 'Directory Ping Response:'; + echo ''; + } + return $response; } -// ----------------------- -// Convert Schedule Shifts -// ----------------------- -// 2.3.0: 24 format shifts for schedule data endpoint -function radio_station_convert_schedule_shifts( $schedule ) { - - if ( is_array( $schedule ) && ( count( $schedule ) > 0 ) ) { - foreach ( $schedule as $day => $shows ) { - $new_shows = array(); - if ( is_array( $shows ) && ( count( $shows ) > 0 ) ) { - foreach ( $shows as $time => $show ) { - $new_show = $show; - $new_show['start'] = radio_station_convert_shift_time( $show['start'], 24 ); - $new_show['end'] = radio_station_convert_shift_time( $show['end'], 24 ); - $new_shows[] = $new_show; - } - } - $schedule[$day] = $new_shows; +// ------------------- +// Check and Send Ping +// ------------------- +// 2.3.2: send queued directory ping +add_action( 'admin_footer', 'radio_station_check_directory_ping', 99 ); +function radio_station_check_directory_ping() { + $ping = get_option( 'radio_station_ping_directory' ); + if ( $ping ) { + $response = radio_station_send_directory_ping(); + if ( !is_wp_error( $response ) && isset( $response['response']['code'] ) && ( 200 == $response['response']['code'] ) ) { + delete_option( 'radio_station_ping_directory' ); } + } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + $response = radio_station_send_directory_ping(); } - return $schedule; } @@ -4476,8 +1657,8 @@ function radio_station_get_icon_colors( $context = false ) { // 2.3.2: added PHP equivalent of javascript encodeURIComponent // ref: https://stackoverflow.com/a/1734255/5240159 function radio_station_encode_uri_component( $string ) { - $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ); - return strtr( rawurlencode( $string ), $revert); + $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ); + return strtr( rawurlencode( $string ), $revert ); } // ------------------ @@ -4520,7 +1701,7 @@ function radio_station_get_languages() { // --- get all language translations --- $translations = get_site_transient( 'available_translations' ); if ( ( false === $translations ) || is_wp_error( $translations ) - || !isset( $translations['translations'] ) || empty( $translations['translations'] ) ) { + || !isset( $translations['translations'] ) || empty( $translations['translations'] ) ) { // --- fallback to language selection data --- // (note this file is a minified from translations API result) // http://api.wordpress.org/translations/core/1.0/ @@ -4700,7 +1881,8 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ } // 2.3.0: added link wrapper if ( $permalink ) { - $more = ' ' . $more . ''; + // 2.5.0: add esc_html to more anchor + $more = ' ' . esc_html( $more ) . ''; } $excerpt = wp_trim_words( $content, $length, $more ); } @@ -4713,6 +1895,10 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ // Sanitize Values // --------------- function radio_station_sanitize_values( $data, $keys ) { + + // print_r( $keys ); + // print_r( $data ); + $sanitized = array(); foreach ( $keys as $key => $type ) { if ( isset( $data[$key] ) ) { @@ -4933,6 +2119,7 @@ function radio_station_sanitize_playlist_entry( $entry ) { // Sanitize Shortcode Values // ------------------------- // 2.3.2: added for AJAX widget loading +// 2.5.0: updated to match changed shortcode keys function radio_station_sanitize_shortcode_values( $type, $extras = false ) { $atts = array(); @@ -4941,25 +2128,35 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- current show attribute keys --- // 2.3.3: added for_time value + // 2.5.0: default_name to no_shows key, time to time_format key + // 2.5.0: added hide_empty, avatar_size and block keys $keys = array( + // --- general options --- 'title' => 'text', - 'limit' => 'integer', - 'show_avatar' => 'boolean', + 'ajax' => 'boolean', + 'dynamic' => 'boolean', + 'no_shows' => 'text', + 'hide_empty' => 'boolean', + // --- show display options --- 'show_link' => 'boolean', + 'title_position' => 'slug', + 'show_avatar' => 'boolean', + 'avatar_size' => 'slug', + 'avatar_width' => 'integer', + // --- show time display options --- 'show_sched' => 'boolean', - 'show_playlist' => 'boolean', 'show_all_sched' => 'boolean', - 'show_desc' => 'boolean', - 'time' => 'integer', - 'default_name' => 'text', + 'countdown' => 'boolean', + 'time_format' => 'integer', + // --- extra display options --- 'display_hosts' => 'boolean', 'link_hosts' => 'boolean', - 'avatar_width' => 'integer', - 'title_position' => 'slug', - 'ajax' => 'boolean', - 'countdown' => 'boolean', - 'dynamic' => 'boolean', + 'show_desc' => 'boolean', + 'show_playlist' => 'boolean', + 'show_encore' => 'boolean', + // --- shortcode data --- 'widget' => 'boolean', + 'block' => 'boolean', 'id' => 'integer', 'instance' => 'integer', 'for_time' => 'integer', @@ -4969,21 +2166,31 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- upcoming shows attribute keys --- // 2.3.3: added for_time value + // 2.5.0: added hide_empty, avatar_size and block keys + // 2.5.0: changed default_name to no_shows, time to time_format keys $keys = array( + // --- general options --- 'title' => 'text', 'limit' => 'integer', - 'show_avatar' => 'boolean', + 'ajax' => 'boolean', + 'dynamic' => 'boolean', + 'no_shows' => 'string', + 'hide_empty' => 'boolean', + // --- show display options --- 'show_link' => 'boolean', - 'time' => 'integer', + 'title_position' => 'slug', + 'show_avatar' => 'boolean', + 'avatar_size' => 'slug', + 'avatar_width' => 'integer', + // --- show timed display options --- 'show_sched' => 'boolean', - 'default_name' => 'string', + 'countdown' => 'boolean', + 'time_format' => 'integer', + // --- extra display options --- 'display_hosts' => 'boolean', 'link_hosts' => 'boolean', - 'avatar_width' => 'integer', - 'title_position' => 'slug', - 'ajax' => 'boolean', - 'countdown' => 'boolean', - 'dynamic' => 'boolean', + 'show_encore' => 'boolean', + // --- shortcode data --- 'widget' => 'boolean', 'id' => 'integer', 'instance' => 'integer', @@ -4995,15 +2202,20 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- current playlist attribute keys --- // 2.3.3: added for_time value $keys = array( + // --- general options --- 'title' => 'text', + 'dynamic' => 'boolean', + 'ajax' => 'boolean', + // --- playlist display options --- + 'link' => 'boolean', + 'countdown' => 'boolean', + // --- track display options --- 'artist' => 'boolean', 'song' => 'boolean', 'album' => 'boolean', 'label' => 'boolean', 'comments' => 'boolean', - 'ajax' => 'boolean', - 'countdown' => 'boolean', - 'dynamic' => 'boolean', + // --- shortcode data --- 'widget' => 'boolean', 'id' => 'integer', 'instance' => 'integer', @@ -5016,9 +2228,6 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { $keys = array( // --- schedule display options --- - 'time' => 'text', - 'show_times' => 'boolean', - 'show_link' => 'boolean', 'view' => 'text', 'days' => 'text', 'start_day' => 'text', @@ -5026,8 +2235,11 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'display_day' => 'text', 'display_date' => 'text', 'display_month' => 'text', + 'time_format' => 'text', // --- show display options --- + 'show_times' => 'boolean', + 'show_link' => 'boolean', 'show_image' => 'boolean', 'show_desc' => 'boolean', 'show_hosts' => 'boolean', @@ -5036,16 +2248,26 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'show_encore' => 'boolean', 'show_file' => 'boolean', + // --- extra display options --- + 'selector' => 'boolean', + 'clock' => 'boolean', + 'timezone' => 'boolean', + // --- view specific options --- 'divheight' => 'integer', - 'gridwidth' => 'integer', 'hide_past_shows' => 'boolean', 'image_position' => 'text', - + 'gridwidth' => 'integer', + 'time_spaced' => 'boolean', + 'weeks' => 'integer', + 'previous_weeks' => 'integer', ); } + // 2.5.0: added filter for shortcode attribute key types + $keys = apply_filters( 'radio_station_shortcode_attribute_key_types', $keys, $type ); + // --- handle extra keys --- if ( $extras && is_array( $extras ) && ( count( $extras ) > 0 ) ) { $keys = array_merge( $keys, $extras ); @@ -5056,289 +2278,99 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { return $atts; } -// ------------------------- -// === Transient Caching === -// ------------------------- - -// -------------------------- -// Delete Prefixed Transients -// -------------------------- -// 2.3.3.4: added helper for clearing transient data -function radio_station_delete_transients_with_prefix( $prefix ) { - global $wpdb; - - // 2.3.3.9: add trailing underscore to prefix - $prefix = $wpdb->esc_like( '_transient_' . $prefix . '_' ); - - // 2.3.3.9: fix to LIKE match - $query = "SELECT `option_name` FROM " . $wpdb->prefix . "options WHERE `option_name` LIKE '%" . $prefix . "%'"; - $results = $wpdb->get_results( $query, ARRAY_A ); - // if ( RADIO_STATION_DEBUG ) { - // echo $query . PHP_EOL . '
    '; - // echo 'Transients: ' . print_r( $results, true ); - // } - if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { - return; - } - - foreach ( $results as $option ) { - // 2.3.3.9: fix to replace malgunctioning ltrim - // $key = ltrim( $option['option_name'], '_transient_' ); - $key = substr( $option['option_name'], 11 ); - delete_transient( $key ); - // 2.3.3.9: also delete transient cache object by key - wp_cache_delete( $key, 'transient' ); - // if ( RADIO_STATION_DEBUG ) { - // echo "Deleting transient and cache object for '" . $key . "'" . PHP_EOL; - // } - } -} - -// ----------------- -// Clear Cached Data -// ----------------- -// 2.3.3.9: made into separate function -// 2.4.0.3: added second argument for post type -function radio_station_clear_cached_data( $post_id = false, $post_type = false ) { - - // --- clear main schedule transients --- - // 2.3.3: remove current show transient - // 2.3.4: add previous show transient - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - delete_transient( 'radio_station_previous_show' ); - - // --- clear time-based schedule transients --- - // 2.3.4: delete all prefixed transients (for times) - radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); - radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); - radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); - - // --- maybe clear show meta data --- - if ( $post_id ) { - do_action( 'radio_station_clear_data', $post_type, $post_id ); - if ( $post_type ) { - do_action( 'radio_station_clear_data', $post_type . '_meta', $post_id ); - } - } - - // --- maybe send directory ping --- - // 2.3.1: added directory update ping option - // 2.3.2: queue directory ping - radio_station_queue_directory_ping(); - - // --- set last updated schedule time --- - // 2.3.2: added for data API use - update_option( 'radio_station_schedule_updated', time() ); - -} - -// --------------------------------- -// Clear Cache on Status Transitions -// --------------------------------- -// 2.4.0.4: clear show and override caches on post status changes -add_action( 'transition_post_status', 'radio_station_clear_cache_on_transitions', 10, 3 ); -function radio_station_clear_cache_on_transitions( $new_status, $old_status, $post ) { - if ( $new_status == $old_status ) { - return; - } - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); - if ( in_array( $post->post_type, $post_types ) ) { - radio_station_clear_cached_data( $post->ID, $post->post_type ); - } -} - - -// -------------------- -// === Translations === -// -------------------- - // ----------------- -// Translate Weekday +// KSES Allowed HTML // ----------------- -// important note: translated individually as cannot translate a variable -// 2.2.7: use wp locale class to translate weekdays -// 2.3.0: allow for abbreviated and long version changeovers -// 2.3.2: default short to null for more flexibility -function radio_station_translate_weekday( $weekday, $short = null ) { - - // 2.3.0: return empty for empty select option - if ( empty( $weekday ) ) { - return ''; - } - - global $wp_locale; - - $days = radio_station_get_weekday(); - - // --- translate weekday --- - // 2.3.2: optimized weekday translations - foreach ( $days as $i => $day ) { - $abbr = substr( $day, 0, 3 ); - if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { - if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $weekday == $day ) ) ) { - return $wp_locale->get_weekday( $i ); - } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { - return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); - } - } - } - - // --- fallback if day number supplied --- - // 2.3.2: optimized day number fallback - $daynum = intval( $weekday ); - if ( ( $daynum > -1 ) && ( $daynum < 7 ) ) { - if ( $short ) { - return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $daynum ) ); - } else { - return $wp_locale->get_weekday( $daynum ); - } - } - - return $weekday; -} - -// ---------------- -// Replace Weekdays -// ---------------- -// 2.3.2: to replace with translated weekdays in a time string -function radio_station_replace_weekday( $string ) { - - $days = radio_station_get_weekday(); - foreach( $days as $day ) { - $abbr = substr( $day, 0, 3 ); - if ( strstr( $string, $day ) ) { - $translated = radio_station_translate_weekday( $day ); - $string = str_replace( $day, $translated, $string ); - } elseif ( strstr( $string, $abbr ) ) { - $translated = radio_station_translate_weekday( $abbr ); - $string = str_replace( $abbr, $translated, $string ); - } - } - - return $string; -} - -// --------------- -// Translate Month -// --------------- -// important note: translated individually as cannot translate a variable -// 2.2.7: use wp locale class to translate months -// 2.3.2: default short to null for more flexibility -function radio_station_translate_month( $month, $short = null ) { - - // 2.3.0: return empty for empty select option - if ( empty( $month ) ) { - return ''; - } - - global $wp_locale; - - $months = radio_station_get_month(); - - // --- translate month --- - // 2.3.2: optimized month translations - foreach ( $months as $i => $fullmonth ) { - $abbr = substr( $fullmonth, 0, 3 ); - if ( ( $month == $fullmonth ) || ( $month == $abbr ) ) { - if ( ( !$short && !is_null( $short ) ) - || ( is_null( $short ) && ( $month == $fullmonth ) ) ) { - return $wp_locale->get_month( ( $i + 1 ) ); - } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { - return $wp_locale->get_month_abbrev( $wp_locale->get_month( ( $i + 1 ) ) ); - } - } - } - - // --- fallback if month number supplied --- - // 2.3.2: optimized month number fallback - $monthnum = intval( $month ); - if ( ( $monthnum > 0 ) && ( $monthnum < 13 ) ) { - if ( $short ) { - return $wp_locale->get_month_abbrev( $wp_locale->get_month( $monthnum ) ); - } else { - return $wp_locale->get_month( $monthnum ); - } - } - - return $month; +// 2.5.0: added for allowing custom wp_kses output +function radio_station_allowed_html( $type, $context = false ) { + $allowed = wp_kses_allowed_html( 'post' ); + $allowed = apply_filters( 'radio_station_allowed_html', $allowed, $type, $context ); + return $allowed; } -// -------------- -// Replace Months -// -------------- -// 2.3.2: to replace with translated months in a time string -function radio_station_replace_month( $string ) { - - $months = radio_station_get_month(); - foreach( $months as $month ) { - $abbr = substr( $month, 0, 3 ); - if ( strstr( $string, $month ) ) { - $translated = radio_station_translate_month( $month ); - $string = str_replace( $month, $translated, $string ); - } elseif ( strstr( $string, $abbr ) ) { - $translated = radio_station_translate_month( $abbr ); - $string = str_replace( $abbr, $translated, $string ); - } - } - - return $string; +// --------------------- +// Link Tag Allowed HTML +// --------------------- +// 2.5.0: added for allowing link tag output for wp_kses +add_filter( 'radio_station_allowed_html', 'radio_station_link_tag_allowed_html', 10, 3 ); +function radio_station_link_tag_allowed_html( $allowed, $type, $context ) { + + if ( 'link' != $type ) { + return $allowed; + } + + $allowed['link'] = array( + 'rel' => array(), + 'href' => array(), + 'hreflang' => array(), + 'crossorigin' => array(), + 'media' => array(), + 'referrerpolicy' => array(), + 'sizes' => array(), + 'title' => array(), + 'type' => array(), + ); + return $allowed; } -// ------------------ -// Translate Meridiem -// ------------------ -// 2.2.7: added meridiem translation function -function radio_station_translate_meridiem( $meridiem ) { - global $wp_locale; - return $wp_locale->get_meridiem( $meridiem ); -} +// ---------------------------- +// Settings Inputs Allowed HTML +// ---------------------------- +add_filter( 'radio_station_allowed_html', 'radio_station_settings_allowed_html', 10, 3 ); +function radio_station_settings_allowed_html( $allowed, $type, $context ) { + + if ( ( 'content' != $type ) || ( 'settings' != $context ) ) { + return $allowed; + } + + // --- input --- + $allowed['input'] = array( + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'data' => array(), + 'placeholder' => array(), + 'style' => array(), + 'checked' => array(), + 'onclick' => array(), + ); -// ---------------- -// Replace Meridiem -// ---------------- -// 2.3.2: added optimized meridiem replacement -function radio_station_replace_meridiem( $string ) { + // --- textarea --- + $allowed['textarea'] = array( + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'placeholder' => array(), + 'style' => array(), + ); - global $radio_station_data; - if ( isset( $radio_station_data['meridiems'] ) ) { - $meridiems = $radio_station_data['meridiems']; - } else { - $meridiems = array( - 'am' => radio_station_translate_meridiem( 'am' ), - 'pm' => radio_station_translate_meridiem( 'pm' ), - 'AM' => radio_station_translate_meridiem( 'AM' ), - 'PM' => radio_station_translate_meridiem( 'PM' ), - ); - $radio_station_data['meridiems'] = $meridiems; - } + // --- select --- + $allowed['select'] = array( + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'multiselect' => array(), + 'style' => array(), + 'onchange' => array(), + ); + // --- select option --- + $allowed['option'] = array( + 'selected' => array(), + 'value' => array(), + ); - if ( strstr( $string, 'am' ) ) { - $string = str_replace( 'am', $meridiems['am'], $string ); - } - if ( strstr( $string, 'pm' ) ) { - $string = str_replace( 'pm', $meridiems['pm'], $string ); - } - if ( strstr( $string, 'AM' ) ) { - $string = str_replace( 'AM', $meridiems['AM'], $string ); - } - if ( strstr( $string, 'PM' ) ) { - $string = str_replace( 'PM', $meridiems['PM'], $string ); - } + // --- option group --- + $allowed['optgroup'] = array( + 'label' => array(), + ); - return $string; + return $allowed; } -// --------------------- -// Translate Time String -// --------------------- -// 2.3.2: replace with translated month, day and meridiem in a string -function radio_station_translate_time( $string ) { - - $string = radio_station_replace_meridiem( $string ); - $string = radio_station_replace_weekday( $string ); - $string = radio_station_replace_month( $string ); - - return $string; -} diff --git a/includes/templates.php b/includes/templates.php new file mode 100644 index 0000000..20b0e5e --- /dev/null +++ b/includes/templates.php @@ -0,0 +1,1463 @@ + $styledir . '/' . $path, + 'urlpath' => $styledirurl . '/' . $path, + ); + } + if ( $styledir != $templatedir ) { + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => $templatedir . '/' . $path, + 'urlpath' => $templatedirurl . '/' . $path, + ); + } + } + if ( defined( 'RADIO_STATION_PRO_DIR' ) ) { + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => RADIO_STATION_PRO_DIR . '/' . $path, + 'urlpath' => plugins_url( $path, RADIO_STATION_PRO_FILE ), + ); + } + } + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => RADIO_STATION_DIR . '/' . $path, + 'urlpath' => plugins_url( $path, RADIO_STATION_FILE ), + ); + } + } + $dirs = apply_filters( 'radio_station_template_dir_hierarchy', $dirs, $template, $paths ); + + // --- loop directory hierarchy to find first template --- + foreach ( $dirs as $dir ) { + + // 2.3.4: use trailingslashit to account for empty paths + $template_path = trailingslashit( $dir['path'] ) . $template; + $template_url = trailingslashit( $dir['urlpath'] ) . $template; + + if ( file_exists( $template_path ) ) { + if ( 'file' == (string) $type ) { + return $template_path; + } elseif ( 'url' === (string) $type ) { + return $template_url; + } else { + return array( 'file' => $template_path, 'url' => $template_url ); + } + } + } + + return false; +} + +// ------------------------------------- +// Station Phone Number for Shows Filter +// ------------------------------------- +// 2.3.3.6: added to return station phone for all Shows (if not set for Show) +add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); +function radio_station_phone_number( $phone, $post_id ) { + if ( $phone ) { + return $phone; + } + $shows_phone = radio_station_get_setting( 'shows_phone' ); + if ( 'yes' == $shows_phone ) { + $phone = radio_station_get_setting( 'station_phone' ); + return $phone; + } + return false; +} + +// -------------------------------------- +// Station Email Address for Shows Filter +// -------------------------------------- +// 2.3.3.8: added to return station email for all Shows (if not set for Show) +add_filter( 'radio_station_show_email', 'radio_station_email_address', 10, 2 ); +function radio_station_email_address( $email, $post_id ) { + if ( $email ) { + return $email; + } + $shows_email = radio_station_get_setting( 'shows_email' ); + if ( 'yes' == $shows_email ) { + $email = radio_station_get_setting( 'station_email' ); + return $email; + } + return false; +} + +// ------------------------------ +// Automatic Pages Content Filter +// ------------------------------ +// 2.3.0: standalone filter for automatic page content +// 2.3.1: re-add filter so the_content can be processed multiple times +// 2.3.3.6: set automatic content early and clear existing content +add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); +function radio_station_automatic_pages_content_set( $content ) { + + global $radio_station_data; + + // if ( isset( $radio_station_data['doing_excerpt'] ) && $radio_station_data['doing_excerpt'] ) { + // return $content; + // } + + // --- for automatic output on selected master schedule page --- + $schedule_page = radio_station_get_setting( 'schedule_page' ); + if ( !is_null( $schedule_page ) && !empty( $schedule_page ) ) { + if ( is_page( $schedule_page ) ) { + $automatic = radio_station_get_setting( 'schedule_auto' ); + if ( 'yes' === (string) $automatic ) { + $view = radio_station_get_setting( 'schedule_view' ); + $atts = array( 'view' => $view ); + $atts = apply_filters( 'radio_station_automatic_schedule_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[master-schedule' . $atts_string . ']'; + } + } + } + + // --- show archive page --- + // 2.3.0: added automatic display of show archive page + $show_archive_page = radio_station_get_setting( 'show_archive_page' ); + if ( !is_null( $show_archive_page ) && !empty( $show_archive_page ) ) { + if ( is_page( $show_archive_page ) ) { + $automatic = radio_station_get_setting( 'show_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'show_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_show_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[shows-archive' . $atts_string . ']'; + } + } + } + + // --- override archive page --- + // 2.3.0: added automatic display of override archive page + $override_archive_page = radio_station_get_setting( 'override_archive_page' ); + if ( !is_null( $override_archive_page ) && !empty( $override_archive_page ) ) { + if ( is_page( $override_archive_page ) ) { + $automatic = radio_station_get_setting( 'override_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'override_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_override_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[overrides-archive' . $atts_string . ']'; + } + } + } + + // --- playlist archive page --- + // 2.3.0: added automatic display of playlist archive page + $playlist_archive_page = radio_station_get_setting( 'playlist_archive_page' ); + if ( !is_null( $playlist_archive_page ) && !empty( $playlist_archive_page ) ) { + if ( is_page( $playlist_archive_page ) ) { + $automatic = radio_station_get_setting( 'playlist_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'playlist_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_playlist_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[playlists-archive' . $atts_string . ']'; + } + } + } + + // --- genre archive page --- + // 2.3.0: added automatic display of genre archive page + $genre_archive_page = radio_station_get_setting( 'genre_archive_page' ); + if ( !is_null( $genre_archive_page ) && !empty( $genre_archive_page ) ) { + if ( is_page( $genre_archive_page ) ) { + $automatic = radio_station_get_setting( 'genre_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'genre_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_genre_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[genres-archive' . $atts_string . ']'; + } + } + } + + // --- languages archive page --- + // 2.3.3.9: added automatic display of language archive page + $language_archive_page = radio_station_get_setting( '' ); + if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { + if ( is_page( $language_archive_page ) ) { + $automatic = radio_station_get_setting( 'language_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'language_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_languagee_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[languages-archive' . $atts_string . ']'; + } + } + } + + // 2.3.3.6: moved out to reduce repetitive code + if ( isset( $shortcode ) ) { + remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); + // 2.3.1: re-add filter so the_content may be processed multuple times + add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + // 2.3.3.6: clear existing content to allow for interim filters + $content = ''; + } + + return $content; +} + +// ---------------------------------- +// Automatic Pages Content Set Filter +// ---------------------------------- +// 2.3.3.6: append existing automatic page content to allow for interim filters +add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); +function radio_station_automatic_pages_content_get( $content ) { + global $radio_station_data; + if ( isset( $radio_station_data['automatic_content'] ) ) { + $content .= $radio_station_data['automatic_content']; + } + return $content; +} + + +// ------------------------------ +// Single Content Template Filter +// ------------------------------ +// 2.3.0: moved here and abstracted from templates/single-show.php +// 2.3.0: standalone filter name to allow for replacement +function radio_station_single_content_template( $content, $post_type ) { + + // --- check if single plugin post type --- + if ( !is_singular( $post_type ) ) { + return $content; + } + + // --- check for user content templates --- + // 2.3.3.9: allow for prefixed and unprefixed post types + $theme_dir = get_stylesheet_directory(); + $templates = array(); + $templates[] = $theme_dir . '/templates/single-' . $post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php'; + $unprefixed_post_type = str_replace( 'rs-', '', $post_type ); + if ( $post_type != $unprefixed_post_type ) { + $templates[] = $theme_dir . '/templates/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $unprefixed_post_type . '-content.php'; + } + + // 2.3.0: fallback to show content template for overrides + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + // $templates[] = $theme_dir . '/templates/single-rs-show-content.php'; + // $templates[] = $theme_dir . '/single-rs-show-content.php'; + // $templates[] = RADIO_STATION_DIR . '/templates/single-rs-show-content.php'; + $templates[] = $theme_dir . '/templates/single-show-content.php'; + $templates[] = $theme_dir . '/single-show-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; + } + $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); + foreach ( $templates as $template ) { + if ( file_exists( $template ) ) { + $content_template = $template; + break; + } + } + if ( !isset( $content_template ) ) { + return $content; + } + + // --- enqueue template styles --- + // 2.3.3.9: check post type for page template style enqueue + $page_templates = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); + if ( in_array( $post_type, $page_templates ) ) { + radio_station_enqueue_style( 'templates' ); + } + // 2.3.3.9: fire action for enqueueing other template styles + do_action( 'radio_station_enqueue_template_styles', $post_type ); + + // --- enqueue dashicons for frontend --- + wp_enqueue_style( 'dashicons' ); + + // --- filter post before including template --- + global $post; + $original_post = $post; + $post = apply_filters( 'radio_station_single_template_post_data', $post, $post_type ); + + // --- start buffer and include content template --- + ob_start(); + include $content_template; + $output = ob_get_contents(); + ob_end_clean(); + + // --- restore post global to be safe --- + $post = $original_post; + + // --- filter and return buffered content --- + $output = str_replace( '', $content, $output ); + $post_id = get_the_ID(); + $output = apply_filters( 'radio_station_content_' . $post_type, $output, $post_id ); + + return $output; +} + +// ------------------------------------ +// Filter for Override Show Linked Data +// ------------------------------------ +add_filter( 'radio_station_single_template_post_data', 'radio_station_override_linked_show_data', 10, 2 ); +function radio_station_override_linked_show_data( $post, $post_type ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); + if ( $linked_id ) { + $show_post = get_post( $linked_id ); + if ( $show_post ) { + $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); + if ( $linked_fields ) { + foreach ( $linked_fields as $key => $switch ) { + if ( !$switch ) { + if ( 'show_title' == $key ) { + $post->post_title = $show_post->post_title; + } elseif ( 'show_excerpt' == $key ) { + $post->post_excerpt = $show_post->post_excerpt; + } elseif ( 'show_content' == $key ) { + $post->post_content = $show_post->post_content; + } + } + } + } + } + } + } + return $post; +} + +// ---------------------------- +// Show Content Template Filter +// ---------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_show_content_template', 11 ); +function radio_station_show_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_show_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_SHOW_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multuple times + add_filter( 'the_content', 'radio_station_show_content_template', 11 ); + return $output; +} + +// -------------------------------- +// Playlist Content Template Filter +// -------------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); +function radio_station_playlist_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_PLAYLIST_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multuple times + add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); + return $output; +} + +// -------------------------------- +// Override Content Template Filter +// -------------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_override_content_template', 11 ); +function radio_station_override_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multiple times + add_filter( 'the_content', 'radio_station_override_content_template', 11 ); + return $output; +} + +// ---------------------------------- +// Override Content with Show Content +// ---------------------------------- +// 2.3.3.9: maybe use show content for override content +add_filter( 'the_content', 'radio_station_override_content', 0 ); +function radio_station_override_content( $content ) { + if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { + return $content; + } + remove_filter( 'the_content', 'radio_station_override_content', 0 ); + global $post; + $override = radio_station_get_show_override( $post->ID, 'show_content' ); + if ( false !== $override ) { + $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); + $content = $override->post_content; + } + add_filter( 'the_content', 'radio_station_override_content', 0 ); + return $content; +} + +// --------------------------------- +// DJ / Host / Producer Template Fix +// --------------------------------- +// 2.2.8: temporary fix to not 404 author pages for DJs without blog posts +// Ref: https://wordpress.org/plugins/show-authors-without-posts/ +add_filter( '404_template', 'radio_station_author_host_pages' ); +function radio_station_author_host_pages( $template ) { + + global $wp_query; + + if ( !is_author() ) { + + if ( get_query_var( 'host' ) ) { + + // --- get user by ID or name --- + $host = get_query_var( 'host' ); + if ( absint( $host ) > - 1 ) { + $user = get_user_by( 'ID', $host ); + } else { + $user = get_user_by( 'slug', $host ); + } + + // --- check if specified user has DJ/host role --- + if ( $user && in_array( 'dj', $user->roles ) ) { + $host_template = radio_station_get_host_template(); + if ( $host_template ) { + $template = $host_template; + } + } + + } elseif ( get_query_var( 'producer' ) ) { + + // --- get user by ID or name --- + $producer = get_query_var( 'producer' ); + if ( absint( $producer ) > - 1 ) { + $user = get_user_by( 'ID', $producer ); + } else { + $user = get_user_by( 'slug', $producer ); + } + + // --- check if specified user has producer role --- + if ( $user && in_array( 'producer', $user->roles ) ) { + $producer_template = radio_station_get_producer_template(); + if ( $producer_template ) { + $template = $producer_template; + } + } + + } elseif ( get_query_var( 'author' ) && ( 0 == $wp_query->posts->post ) ) { + + // --- get the author user --- + if ( get_query_var( 'author_name' ) ) { + $author = get_user_by( 'slug', get_query_var( 'author_name' ) ); + } else { + $author = get_userdata( get_query_var( 'author' ) ); + } + + if ( $author ) { + + // --- check if author has DJ, producer or administrator role --- + if ( in_array( 'dj', $author->roles ) + || in_array( 'producer', $author->roles ) + || in_array( 'administrator', $author->roles ) ) { + + // TODO: maybe check if user is assigned to any shows ? + $template = get_author_template(); + } + } + + } + + } + + return $template; +} + +// ---------------------- +// Get DJ / Host Template +// ---------------------- +// 2.3.0: added get DJ template function +// (modified template hierarchy from get_page_template) +function radio_station_get_host_template() { + + $templates = array(); + $hostname = get_query_var( 'host' ); + if ( $hostname ) { + $hostname_decoded = urldecode( $hostname ); + if ( $hostname_decoded !== $hostname ) { + $templates[] = 'host-' . $hostname_decoded . '.php'; + } + $templates[] = 'host-' . $hostname . '.php'; + } + $templates[] = 'single-host.php'; + + $templates = apply_filters( 'radio_station_host_templates', $templates ); + return get_query_template( RADIO_STATION_HOST_SLUG, $templates ); +} + +// --------------------- +// Get Producer Template +// --------------------- +// 2.3.0: added get producer template function +// (modified template hierarchy from get_page_template) +function radio_station_get_producer_template() { + + $templates = array(); + $producername = get_query_var( 'producer' ); + if ( $producername ) { + $producername_decoded = urldecode( $producername ); + if ( $producername_decoded !== $producername ) { + $templates[] = 'producer-' . $producername_decoded . '.php'; + } + $templates[] = 'producer-' . $producername . '.php'; + } + $templates[] = 'single-producer.php'; + + $templates = apply_filters( 'radio_station_producer_templates', $templates ); + return get_query_template( RADIO_STATION_PRODUCER_SLUG, $templates ); +} + +// ------------------------- +// Single Template Hierarchy +// ------------------------- +function radio_station_single_template_hierarchy( $templates ) { + + global $post; + + // --- remove single.php as the show / playlist fallback --- + // (allows for user selection of page.php or single.php later) + if ( ( RADIO_STATION_SHOW_SLUG === (string) $post->post_type ) + || ( RADIO_STATION_OVERRIDE_SLUG === (string) $post->post_type ) + || ( RADIO_STATION_PLAYLIST_SLUG === (string) $post->post_type ) ) { + $i = array_search( 'single.php', $templates ); + if ( false !== $i ) { + unset( $templates[$i] ); + } + } + + return $templates; +} + +// ----------------------- +// Single Templates Loader +// ----------------------- +add_filter( 'single_template', 'radio_station_load_template', 10, 3 ); +function radio_station_load_template( $single_template, $type, $templates ) { + + global $post; + + // --- handle single templates --- + $post_type = $post->post_type; + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); + // TODO: RADIO_STATION_EPISODE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG + if ( in_array( $post_type, $post_types ) ) { + + // --- check for existing template override --- + // note: single.php is removed from template hierarchy via filter + remove_filter( 'single_template', 'radio_station_load_template' ); + add_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); + $template = get_single_template(); + remove_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); + + // --- use legacy template --- + if ( $template ) { + + // --- use the found user template --- + $single_template = $template; + + // --- check for combined template and content filter --- + $combined = radio_station_get_setting( $post_type . '_template_combined' ); + if ( 'yes' != $combined ) { + remove_filter( 'the_content', 'radio_station_' . $post_type . '_content_template', 11 ); + } + + } else { + + // --- get template selection --- + // 2.3.0: removed default usage of single show/playlist templates (not theme agnostic) + // 2.3.0: added option for use of template hierarchy + $show_template = radio_station_get_setting( $post_type . '_template' ); + + // --- maybe use legacy template --- + if ( 'legacy' === (string) $show_template ) { + return RADIO_STATION_DIR . '/templates/legacy/single-' . $post_type . '.php'; + } + + // --- use post or page template --- + // 2.3.3.8: added missing singular.php template setting + if ( 'post' == $show_template ) { + $templates = array( 'single.php' ); + } elseif ( 'page' == $show_template ) { + $templates = array( 'page.php' ); + } elseif ( 'singular' == $show_template ) { + $template = array( 'singular.php' ); + } + + // --- add standard fallbacks to index --- + // 2.3.3.8: remove singular fallback as it is explicitly chosen + $templates[] = 'index.php'; + $single_template = get_query_template( $post_type, $templates ); + } + } + + return $single_template; +} + +// -------------------------- +// Archive Template Hierarchy +// -------------------------- +add_filter( 'archive_template_hierarchy', 'radio_station_archive_template_hierarchy' ); +function radio_station_archive_template_hierarchy( $templates ) { + + // --- add extra template search path of /templates/ --- + $post_types = array_filter( (array) get_query_var( 'post_type' ) ); + if ( count( $post_types ) == 1 ) { + $post_type = reset( $post_types ); + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); + if ( in_array( $post_type, $post_types ) ) { + $template = array( 'templates/archive-' . $post_type . '.php' ); + // 2.3.0: add fallback to show archive template for overrides + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + $template[] = 'templates/archive-' . RADIO_STATION_SHOW_SLUG . '.php'; + } + $templates = array_merge( $template, $templates ); + } + } + + return $templates; +} + +// ------------------------ +// Archive Templates Loader +// ------------------------ +// TODO: implement standard archive page overrides via plugin settings +// add_filter( 'archive_template', 'radio_station_post_type_archive_template', 10, 3 ); +function radio_station_post_type_archive_template( $archive_template, $type, $templates ) { + global $post; + + // --- check for archive template override --- + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); + foreach ( $post_types as $post_type ) { + if ( is_post_type_archive( $post_type ) ) { + $override = radio_station_get_setting( $post_type . '_archive_override' ); + if ( 'yes' !== (string) $override ) { + $archive_template = get_page_template(); + add_filter( 'the_content', 'radio_station_' . $post_type . '_archive', 11 ); + } + } + } + + return $archive_template; +} + +// ------------------------- +// Add Links to Back to Show +// ------------------------- +// 2.3.0: add links to show from show posts and playlists +// 2.3.3.6: allow for multiple related show post assignments +add_filter( 'the_content', 'radio_station_add_show_links', 20 ); +function radio_station_add_show_links( $content ) { + + global $post; + + // note: playlists are linked via single-playlist-content.php template + + // 2.4.0.6: bug out if no post object + if ( !is_object( $post ) ) { + return $content; + } + + // --- filter to allow related post types --- + $post_type = $post->post_type; + $post_types = array( 'post' ); + $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); + + if ( in_array( $post_type, $post_types ) ) { + + // --- link show posts --- + $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); + // 2.3.3.6: convert string value if not multiple + if ( $related_shows && !is_array( $related_shows ) ) { + $related_shows = array( $related_shows ); + } + // 2.3.3.6: remove possible zero values + // 2.3.3.7: added count check for before looping + if ( $related_shows && ( count( $related_shows ) > 0 ) ) { + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } + } + } + if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { + + $positions = array( 'after' ); + $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post_type, $post ); + if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { + if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { + + // --- set related shows link(s) --- + // 2.3.3.6: get all related show links + $show_links = ''; + $hash_ref = '#show-' . str_replace( 'rs-', '', $post_type ) . 's'; + foreach ( $related_shows as $related_show ) { + $show = get_post( $related_show ); + $title = $show->post_title; + $permalink = get_permalink( $show->ID ) . $hash_ref; + if ( '' != $show_links ) { + $show_links .= ', '; + } + $show_links .= '' . esc_html( $title ) . ''; + } + + // --- set post type labels --- + $before = $after = ''; + $post_type_object = get_post_type_object( $post_type ); + $singular = $post_type_object->labels->singular_name; + $plural = $post_type_object->labels->name; + + // --- before content links --- + if ( in_array( 'before', $positions ) ) { + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( '%s for Shows', 'radio-station' ), $singular ); + } else { + $label = sprintf( __( '%s for Show', 'radio-station' ), $singular ); + } + $before = $label . ': ' . $show_links . '

    '; + $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $related_shows ); + } + + // --- after content links --- + if ( in_array( 'after', $positions ) ) { + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( 'More %s for Shows', 'radio-station' ), $plural ); + } else { + $label = sprintf( __( 'More %s for Show', 'radio-station' ), $plural ); + } + $after = '
    ' . $label . ': ' . $show_links; + $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $related_shows ); + } + $content = $before . $content . $after; + } + } + } + + } + + // --- adjacent post links debug output --- + if ( RADIO_STATION_DEBUG ) { + $content .= 'Previous Post Link: ' . esc_html( get_previous_post_link() ) . '' . PHP_EOL; + $content .= 'Next Post Link: ' . esc_html( get_next_post_link() ) . '' . PHP_EOL; + } + + return $content; +} + +// ------------------------- +// Show Posts Adjacent Links +// ------------------------- +// 2.3.0: added show post adjacent links filter +add_filter( 'next_post_link', 'radio_station_get_show_post_link', 11, 5 ); +add_filter( 'previous_post_link', 'radio_station_get_show_post_link', 11, 5 ); +function radio_station_get_show_post_link( $output, $format, $link, $adjacent_post, $adjacent ) { + + global $radio_station_data, $post; + + // --- filter next and previous Show links --- + // 2.3.4: add filtering for adjacent show links + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( in_array( $post->post_type, $post_types ) ) { + + // 2.5.0: get timezone for time conversion + $timezone = radio_station_get_timezone(); + + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + // 2.3.3.6: get next/previous Show for override date/time + // 2.3.3.9: modified to handle multiple override times + // 2.3.3.9: added check that schedule key is set + $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); + if ( $scheds && is_array( $scheds ) ) { + if ( array_key_exists( 'date', $scheds ) ) { + $sched = array( $scheds ); + } + $now = time(); + foreach ( $scheds as $sched ) { + $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; + // 2.5.0: fix to use to_time not get_time! + $override_time = radio_station_to_time( $override_start, $timezone ) + 1; + if ( !isset( $time ) ) { + $time = $override_time; + } elseif ( ( $time < $now ) && ( $override_time > $now ) ) { + $time = $override_time; + } + } + if ( 'next' == $adjacent ) { + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $show = radio_station_get_previous_show( $time ); + } + } + } else { + $shifts = get_post_meta( $post->ID, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + if ( count( $shifts ) < 1 ) { + // 2.3.3.6: default to standard adjacent post link + return $output; + } + if ( 1 == count( $shifts ) ) { + $shift = $shifts[0]; + $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + // 2.3.3.9: fix to put addition outside bracket + // 2.5.0: fix to use to_time not get_time! + $time = radio_station_to_time( $shift_start, $timezone ) + 1; + if ( 'next' == $adjacent ) { + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $show = radio_station_get_previous_show( $time ); + } + } else { + // 2.3.3.6: added method for Show with multiple shifts + $now = radio_station_get_now(); + $show_shifts = radio_station_get_current_schedule(); + if ( !$show_shifts ) { + return $output; + } + + // --- get upcoming shift for Show --- + $next_shift = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !$next_shift && ( $day_shift['show']['id'] == $post->ID ) ) { + if ( !isset( $last_shift ) ) { + $last_shift = $day_shift; + } + $start = $day_shift['date'] . ' ' . $day_shift['start']; + $start_time = radio_station_to_time( $start ); + $end = $day_shift['date'] . ' ' . $day_shift['end']; + $end_time = radio_station_to_time( $end ); + if ( ( $start_time > $now ) || ( $now < $end_time ) ) { + $next_shift = $day_shift; + } + } + } + } + if ( !$next_shift ) { + $next_shift = $last_shift; + } + // echo "Next Show Shift: " . print_r( $next_shift, true ); + + // --- reverse order for finding previous show shift --- + if ( 'previous' == $adjacent ) { + foreach ( $show_shifts as $day => $day_shifts ) { + $show_shifts[$day] = array_reverse( $day_shifts, true ); + } + $show_shifts = array_reverse( $show_shifts, true ); + } + + // --- loop shifts to find adjacent shift's Show --- + $found = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !isset( $first_shift ) && ( $day_shift['show']['id'] != $post->ID ) ) { + $first_shift = $day_shift; + } + // echo "Shift: " . print_r( $day_shift, true ) . PHP_EOL; + if ( !isset( $show ) ) { + if ( $found && ( $day_shift['show']['id'] != $post->ID ) ) { + $show = $day_shift['show']; + } elseif ( !$found ) { + if ( $next_shift == $day_shift ) { + $found = true; + } + } + } + } + } + if ( !isset( $show ) && isset( $first_shift ) ) { + $show = $first_shift['show']; + } + } + } + } + + // --- generate adjacent Show link --- + if ( isset( $show ) ) { + if ( 'next' == $adjacent ) { + $rel = 'next'; + } elseif ( 'previous' == $adjacent ) { + $rel = 'prev'; + } + $adjacent_post = get_post( $show['id'] ); + + // --- adjacent post title --- + // 2.4.0.3: added fix for missing post title + $post_title = $adjacent_post->post_title; + // if ( empty( $adjacent_post->post_title ) ) { + // $post_title = $title; + // } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + } + + return $output; + } + + // --- filter to allow related post types --- + $related_post_types = array( 'post' ); + $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); + if ( in_array( $post->post_type, $related_post_types ) ) { + + // --- filter to allow disabling --- + $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); + if ( !$link_show_posts ) { + return $output; + } + + // --- get related show --- + $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); + if ( !$related_show ) { + return $output; + } + if ( is_array( $related_show ) ) { + $related_shows = $related_show; + } else { + $related_shows = array( $related_show ); + } + // 2.3.3.6: remove possible saved zero value + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } + } + // 2.5.0: change to less than 1, instead of equals 0 + if ( count( $related_shows ) < 1 ) { + return $output; + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows A: ' . esc_html( print_r( $related_shows, true ) ) . ''; + } + + // --- get more Shows related to this related Post --- + // 2.3.3.6: allow for multiple related posts + // 2.3.3.9: added 'i:' prefix to LIKE value matches + // TODO: use prepare method on like placeholders (tricky, requires testing) + global $wpdb; + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; + if ( count( $related_shows ) > 1 ) { + // 2.5.0: fix to loop related_shows (plural) + foreach ( $related_shows as $i => $show_id ) { + if ( $i > 0 ) { + $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $show_id . "%'"; + } + } + } + $results = $wpdb->get_results( $query, ARRAY_A ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows B: ' . esc_html( print_r( $results, true ) ) . ''; + } + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { + return $output; + } + $related_posts = array(); + foreach ( $results as $result ) { + $values = maybe_unserialize( $result['meta_value'] ); + if ( RADIO_STATION_DEBUG ) { + echo 'Post ' . esc_html( $result['post_id'] ) . ' Related Show Values : ' . esc_html( print_r( $values, true ) ) . ''; + } + // --- double check Show ID is actually a match --- + if ( ( $result['meta_value'] == $related_show ) || ( is_array( $values ) && array_intersect( $related_shows, $values ) ) ) { + // --- recheck post is of the same post type --- + $query = "SELECT post_type FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $related_post_type = $wpdb->get_var( $wpdb->prepare( $query, $result['post_id'] ) ); + if ( $related_post_type == $post->post_type ) { + $related_posts[] = $result['post_id']; + } + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts B: ' . esc_html( print_r( $related_posts, true ) ) . ''; + } + if ( 0 == count( $related_posts ) ) { + return $output; + } + + // --- get adjacent post query --- + // 2.3.3.6: use post__in related post array instead of meta_query + $args = array( + 'post_type' => $post->post_type, + 'posts_per_page' => 1, + 'orderby' => 'post_modified', + 'post__in' => $related_posts, + 'ignore_sticky_posts' => true, + ); + + // --- setup for previous or next post --- + // 2.3.3.6: set date_query instead of meta_query + $post_type_object = get_post_type_object( $post->post_type ); + if ( 'previous' == $adjacent ) { + $args['order'] = 'DESC'; + $args['date_query'] = array( array( 'before' => $post->post_date ) ); + $rel = 'prev'; + $title = __( 'Previous Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + } elseif ( 'next' == $adjacent ) { + $args['order'] = 'ASC'; + $args['date_query'] = array( array( 'after' => $post->post_date ) ); + $rel = 'next'; + $title = __( 'Next Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + } + + // --- get the adjacent post --- + // 2.3.3.6: use date_query instead of looping posts + $show_posts = get_posts( $args ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts Args: ' . esc_html( print_r( $args, true ) ) . ''; + } + if ( 0 == count( $show_posts ) ) { + return $output; + } + $adjacent_post = $show_posts[0]; + if ( RADIO_STATION_DEBUG ) { + echo 'Related Adjacent Post: ' . esc_html( print_r( $adjacent_post, true ) ) . ''; + } + + // --- adjacent post title --- + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + // --- adjacent post link --- + // (from function get_adjacent_post_link) + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + + } + + return $output; +} + + +// ============= +// Query Filters +// ============= + +// ----------------------------- +// Playlist Archive Query Filter +// ----------------------------- +// 2.3.0: added to replace old archive template meta query +add_filter( 'pre_get_posts', 'radio_station_show_playlist_query' ); +function radio_station_show_playlist_query( $query ) { + + if ( RADIO_STATION_PLAYLIST_SLUG == $query->get( 'post_type' ) ) { + + // --- not needed if using legacy template --- + $styledir = get_stylesheet_directory(); + if ( file_exists( $styledir . '/archive-playlist.php' ) + || file_exists( $styledir . '/templates/archive-playlist.php' ) ) { + return; + } + // 2.3.0: also check in parent theme directory + $templatedir = get_template_directory(); + if ( $templatedir != $styledir ) { + if ( file_exists( $templatedir . '/archive-playlist.php' ) + || file_exists( $templatedir . '/templates/archive-playlist.php' ) ) { + return; + } + } + + // --- check if show ID or slug is set -- + // TODO: maybe use get_query_var here ? + if ( isset( $_GET['show_id'] ) ) { + $show_id = absint( $_GET['show_id'] ); + if ( $show_id < 0 ) { + unset( $show_id ); + } + } elseif ( isset( $_GET['show'] ) ) { + $show = sanitize_title( $_GET['show'] ); + global $wpdb; + $show_query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; + $show_id = $wpdb->get_var( $wpdb->prepare( $show_query, $show ) ); + if ( !$show_id ) { + unset( $show_id ); + } + } + + // --- maybe add the playlist meta query --- + if ( isset( $show_id ) ) { + $meta_query = array( + 'key' => 'playlist_show_id', + 'value' => $show_id, + ); + $query->set( $meta_query ); + } + } +} + + +// --------------------------------- +// === Schedule Override Filters === +// --------------------------------- +// 2.5.0: moved here from includes/post-types.php +// (to apply linked show overrides in template single-show-content.php) +// note: post object overrides (content, excerpt) are in radio_station_override_linked_show_data + +// -------------------------------------- +// Add Schedule Override Template Filters +// -------------------------------------- +add_action( 'wp', 'radio_station_override_filters' ); +function radio_station_override_filters() { + + if ( is_admin() || !is_singular() ) { + return; + } + + global $post; + if ( is_object( $post ) && ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) ) { + add_filter( 'the_title', 'radio_station_override_show_title', 10, 2 ); + add_filter( 'radio_station_show_title', 'radio_station_override_show_title', 10, 2 ); + add_filter( 'radio_station_show_avatar', 'radio_station_override_show_avatar', 10, 2 ); + add_filter( 'radio_station_show_avatar_id', 'radio_station_override_show_avatar_id', 10, 2 ); + add_filter( 'radio_station_show_thumbnail', 'radio_station_override_show_thumbnail', 10, 2 ); + add_filter( 'get_post_metadata', 'radio_station_override_thumbnail_id', 11, 4 ); + // add_filter( 'radio_station_show_header', 'radio_station_override_show_header', 10, 2 ); + add_filter( 'radio_station_show_hosts', 'radio_station_override_show_hosts', 10, 2 ); + add_filter( 'radio_station_show_producers', 'radio_station_override_show_producers', 10, 2 ); + add_filter( 'radio_station_show_link', 'radio_station_override_show_link', 10, 2 ); + add_filter( 'radio_station_show_email', 'radio_station_override_show_email', 10, 2 ); + add_filter( 'radio_station_show_phone', 'radio_station_override_show_phone', 10, 2 ); + add_filter( 'radio_station_show_download', 'radio_station_override_show_download', 10, 2 ); + add_filter( 'radio_station_show_file', 'radio_station_override_show_file', 10, 2 ); + add_filter( 'radio_station_show_patreon', 'radio_station_override_show_patreon', 10, 2 ); + add_filter( 'radio_station_show_shifts', 'radio_station_override_show_shifts', 10, 2 ); + // add_filter( 'radio_station_show_rss', 'radio_station_override_show_rss', 10, 2 ); + // add_filter( 'radio_station_show_social_icons', 'radio_station_override_show_social_icons', 10, 2 ); + } +} + +// ------------------- +// Override Show Title +// ------------------- +function radio_station_override_show_title( $show_title, $post_id ) { + global $post; + if ( !is_object( $post ) || ( $post->ID != $post_id ) ) { + return $show_title; + } + $override = radio_station_get_show_override( $post_id, 'show_title' ); + if ( false !== $override ) { + return $override; + } + return $show_title; +} + +// -------------------- +// Override Show Avatar +// -------------------- +function radio_station_override_show_avatar( $show_avatar, $post_id ) { + if ( radio_station_doing_template() ) { + $override = radio_station_get_show_override( $post_id, 'show_avatar' ); + if ( false !== $override ) { + return $override; + } + } + return $show_avatar; +} + +// ----------------------- +// Override Show Avatar ID +// ----------------------- +function radio_station_override_show_avatar_id( $avatar_id, $post_id ) { + if ( radio_station_doing_template() ) { + $override = radio_station_get_show_override( $post_id, 'show_avatar' ); + if ( false !== $override ) { + return $override; + } + } + return $avatar_id; +} + +// ----------------------- +// Override Show Thumbnail +// ----------------------- +function radio_station_override_show_thumbnail( $show_thumbnail, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_image' ); + if ( false !== $override ) { + return $override; + } + return $show_thumbnail; +} + +// -------------------------- +// Override Show Thumbnail ID +// -------------------------- +function radio_station_override_thumbnail_id( $id, $object_id, $meta_key, $single ) { + global $post; + if ( ( '_thumbnail_id' != $meta_key ) || !is_object( $post ) || ( $post->ID != $object_id ) ) { + return $id; + } + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + $override = radio_station_get_show_override( $object_id, 'show_image' ); + if ( false !== $override ) { + return $override; + } + } + return $id; +} + +// -------------------- +// Override Show Header +// -------------------- +/* function radio_station_override_show_header( $header_id, $post_id ) { + return $header_id; +} */ + +// ------------------- +// Override Show Hosts +// ------------------- +function radio_station_override_show_hosts( $hosts, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_user_list' ); + if ( false !== $override ) { + return $override; + } + return $hosts; +} + +// ----------------------- +// Override Show Producers +// ----------------------- +function radio_station_override_show_producers( $producers, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_producer_list' ); + if ( false !== $override ) { + return $override; + } + return $producers; +} + +// ------------------ +// Override Show Link +// ------------------ +function radio_station_override_show_link( $show_link, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_link' ); + if ( false !== $override ) { + return $override; + } + return $show_link; +} + +// ------------------- +// Override Show Email +// ------------------- +function radio_station_override_show_email( $show_email, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_email' ); + if ( false !== $override ) { + return $override; + } + return $show_email; +} + +// ------------------- +// Override Show Phone +// ------------------- +function radio_station_override_show_phone( $show_phone, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_phone' ); + if ( false !== $override ) { + return $override; + } + return $show_phone; +} + +// ------------------ +// Override Show File +// ------------------ +function radio_station_override_show_file( $show_file, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_file' ); + if ( false !== $override ) { + return $override; + } + return $show_file; +} + +// ------------------------- +// Override Disable Download +// ------------------------- +function radio_station_override_show_download( $show_download, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_download' ); + if ( false !== $override ) { + return $override; + } + return $show_download; +} + +// --------------------- +// Override Show Patreon +// --------------------- +function radio_station_override_show_patreon( $show_patreon, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_patreon' ); + if ( false !== $override ) { + return $override; + } + return $show_patreon; +} + +// -------------------- +// Override Show Shifts +// -------------------- +function radio_station_override_show_shifts( $show_shifts, $post_id ) { + $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); + if ( $linked_id ) { + $show_shifts = radio_station_get_show_schedule( $linked_id ); + } + return $show_shifts; +} + diff --git a/includes/times.php b/includes/times.php new file mode 100644 index 0000000..7870e11 --- /dev/null +++ b/includes/times.php @@ -0,0 +1,443 @@ +get_now( $gmt ); + return $now; +} + +// ------------ +// Get Timezone +// ------------ +// 2.3.2: added get timezone with fallback +// 2.5.0: added optional channel argument +function radio_station_get_timezone() { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + $timezone = radio_station_get_setting( 'timezone_location' ); + if ( !$timezone || ( '' == $timezone ) ) { + $timezone = $rs_se->get_timezone(); + } + + // --- filter and return --- + // 2.5.0: added filter and channel argument + $timezone = apply_filters( 'radio_station_timezone', $timezone, $channel ); + return $timezone; +} + +// ----------------- +// Get Timezone Code +// ----------------- +// note: this should only be used for display purposes +// (as the actual code used is based on timezone/location) +function radio_station_get_timezone_code( $timezone ) { + global $rs_se; + // note: channel not explicitly needed here + return $rs_se->get_timezone_code( $timezone ); +} + +// -------------------- +// Get Timezone Options +// -------------------- +// ref: (based on) https://stackoverflow.com/a/17355238/5240159 +function radio_station_get_timezone_options( $include_wp_timezone = false ) { + + global $rs_se; + + // --- maybe get stored timezone options --- + $options = get_transient( 'radio_station_timezone_options' ); + if ( !$options ) { + $options = $rs_se->get_timezone_options( false ); + $expiry = 7 * 24 * 60 * 60; + set_transient( 'radio_station_timezone_options', $options, $expiry ); + } + + // --- maybe add WordPress timezone (default) option --- + if ( $include_wp_timezone ) { + $wp_timezone = array( '' => __( 'WordPress Timezone', 'radio-station' ) ); + $options = array_merge( $wp_timezone, $options ); + } + + // --- filter and return --- + $options = apply_filters( 'radio_station_get_timezone_options', $options, $include_wp_timezone ); + return $options; +} + +// -------------------- +// Get Date Time Object +// -------------------- +// 2.3.2: added for consistent timezone conversions +function radio_station_get_date_time( $timestring, $timezone ) { + global $rs_se; + // note: channel not explicitly needed here + return $rs_se->get_date_time( $timestring, $timezone ); +} + +// -------------- +// String To Time +// -------------- +// 2.3.2: added for timezone handling +// 2.5.0: added optional timezone argument for consistency +function radio_station_to_time( $timestring, $timezone = false ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + if ( !$timezone ) { + $timezone = radio_station_get_timezone( $channel ); + } + $time = $rs_se->to_time( $timestring, $timezone ); + return $time; +} + +// --------- +// Get Times +// --------- +// 2.5.0: added for separation / abstraction +function radio_station_get_times( $time = false, $timezone = false ) { + + global $radio_station_data, $rs_se; + // note: channel not explicitly needed here + + // --- maybe get current time --- + if ( !$time ) { + $time = radio_station_get_now(); + } + + // --- maybe get timezone --- + if ( !$timezone ) { + $timezone = radio_station_get_timezone(); + } + + // --- get offset time and date --- + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $times = $rs_se->get_times( $time, false ); + } else { + $timezone = radio_station_get_timezone(); + $times = $rs_se->get_times( $time, $timezone ); + } + + return $times; +} + +// -------- +// Get Time +// -------- +// 2.3.2: added for timezone adjustments +function radio_station_get_time( $key = false, $time = false, $timezone = false ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + // --- maybe get timezone --- + if ( !$timezone ) { + $timezone = radio_station_get_timezone(); + } + + // --- get formatted times --- + $times = radio_station_get_times( $time, $timezone ); + if ( RADIO_STATION_DEBUG ) { + echo 'Times: ' . esc_html( print_r( $times, true ) ) . '' . "\n"; + } + if ( !$key ) { + return $times; + } + + // --- return time key --- + if ( array_key_exists( $key, $times ) ) { + $time = $times[$key]; + } elseif ( isset( $times['object'] ) ) { + $datetime = $times['object']; + $time = $datetime->format( $key ); + } else { + $time = date( $key, $time ); + if ( RADIO_STATION_DEBUG ) { + echo 'Time key not found: ' . esc_html( $key ) . '' . "\n"; + } + } + return $time; +} + +// -------------- +// Get Weekday(s) +// -------------- +// 2.3.2: added get weekday from number helper +function radio_station_get_weekday( $day_number = null ) { + global $rs_se; + return $rs_se->get_weekday( $day_number ); +} + +// ------------ +// Get Month(s) +// ------------ +// 2.3.2: added get weekday from number helper +function radio_station_get_month( $month_number = null ) { + global $rs_se; + return $rs_se->get_month( $month_number ); +} + +// --------------------- +// Get Schedule Weekdays +// --------------------- +// note: no translations here because used internally for sorting +// 2.3.0: added to get schedule weekdays from start of week +function radio_station_get_schedule_weekdays( $weekstart = false ) { + global $radio_station_data, $rs_se; + $rs_se->channel = $channel = $radio_station_data['channel']; + $weekdays = $rs_se->get_schedule_weekdays( $weekstart ); + return $weekdays; +} + +// ---------------------- +// Get Schedule Weekdates +// ---------------------- +// 2.3.0: added for date based calculations +function radio_station_get_schedule_weekdates( $weekdays, $time = false, $timezone = false, $channel = '' ) { + + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + + if ( !$time ) { + $time = radio_station_get_now(); + } + if ( !$timezone ) { + $timezone = radio_station_get_timezone(); + } + + $weekdates = $rs_se->get_schedule_weekdates( $weekdays, $time, $timezone ); + return $weekdates; +} + +// ------------ +// Get Next Day +// ------------ +// 2.3.0: added get next day helper +function radio_station_get_next_day( $day ) { + global $rs_se; + return $rs_se->get_next_day( $day ); +} + +// ---------------- +// Get Previous Day +// ---------------- +// 2.3.0: added get previous day helper +function radio_station_get_previous_day( $day ) { + global $rs_se; + return $rs_se->get_previous_day( $day ); +} + +// ------------- +// Get Next Date +// ------------- +// 2.3.2: added for more reliable calculations +function radio_station_get_next_date( $date, $weekday = false ) { + global $rs_se; + return $rs_se->get_next_date( $date, $weekday ); +} + +// ----------------- +// Get Previous Date +// ----------------- +// 2.3.2: added for more reliable calculations +function radio_station_get_previous_date( $date, $weekday = false ) { + global $rs_se; + return $rs_se->get_previous_date( $date, $weekday ); +} + +// ------------ +// Get All Days +// ------------ +function radio_station_get_days( $translate = false ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->get_days( $translate ); +} + +// ------------- +// Get All Hours +// ------------- +function radio_station_get_hours( $format = 24, $translate = false ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->get_hours( $format, $translate ); +} + +// --------------- +// Get All Minutes +// --------------- +function radio_station_get_minutes( $translate = false ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->get_minutes( $translate ); +} + + +// --------------------------- +// Convert Hour to Time Format +// --------------------------- +// (note: used with suffix for on-the-hour times) +// 2.3.0: standalone function via master-schedule-default.php +// 2.3.0: optionally add suffix for both time formats +function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->convert_hour( $hour, $timeformat, $suffix ); +} + +// ---------------------------- +// Convert Shift to Time Format +// ---------------------------- +// 2.3.0: added to convert shift time to 24 hours (or back) +function radio_station_convert_shift_time( $time, $timeformat = 24 ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->convert_shift_time( $time, $timeformat ); +} + + +// -------------------- +// === Translations === +// -------------------- + +// ---------- +// Get Locale +// ---------- +function radio_station_get_locale() { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + $locale = $rs_se->get_locale(); + return $locale; +} + +// ----------------- +// Translate Weekday +// ----------------- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate weekdays +// 2.3.0: allow for abbreviated and long version changeovers +// 2.3.2: default short to null for more flexibility +function radio_station_translate_weekday( $weekday, $short = null ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->translate_weekday( $weekday, $short ); +} + +// ---------------- +// Replace Weekdays +// ---------------- +// 2.3.2: to replace with translated weekdays in a time string +function radio_station_replace_weekday( $string ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->replace_weekday( $string ); +} + +// --------------- +// Translate Month +// --------------- +// important note: translated individually as cannot translate a variable +// 2.2.7: use wp locale class to translate months +// 2.3.2: default short to null for more flexibility +function radio_station_translate_month( $month, $short = null ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->translate_month( $month, $short ); +} + +// -------------- +// Replace Months +// -------------- +// 2.3.2: to replace with translated months in a time string +function radio_station_replace_month( $string ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->replace_month( $string ); +} + +// ------------------ +// Translate Meridiem +// ------------------ +// 2.2.7: added meridiem translation function +function radio_station_translate_meridiem( $meridiem ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->translate_meridiem( $meridiem ); +} + +// ---------------- +// Replace Meridiem +// ---------------- +// 2.3.2: added optimized meridiem replacement +function radio_station_replace_meridiem( $string ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->replace_meridiem( $string ); +} + +// --------------------- +// Translate Time String +// --------------------- +// 2.3.2: replace with translated month, day and meridiem in a string +function radio_station_translate_time( $string ) { + global $radio_station_data, $rs_se; + $channel = $rs_se->channel = $radio_station_data['channel']; + return $rs_se->translate_time( $string ); +} + diff --git a/includes/user-roles.php b/includes/user-roles.php new file mode 100644 index 0000000..b72e1d4 --- /dev/null +++ b/includes/user-roles.php @@ -0,0 +1,436 @@ + true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'read_shows' => true, + 'edit_playlists' => true, + 'edit_published_playlists' => true, + // by default DJs cannot edit others playlists + // 'edit_others_playlists' => false, + 'read_playlists' => true, + 'publish_playlists' => true, + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, + ); + + // --- add the DJ role --- + // 2.3.0: translate DJ role name + // 2.3.0: change label from 'DJ' to 'DJ / Host' + // 2.3.0: check/add profile capabilities to hosts + $wp_roles->add_role( 'dj', __( 'DJ / Host', 'radio-station' ), $caps ); + $role_caps = $wp_roles->roles['dj']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } + $host_caps = array( + 'edit_hosts', + 'edit_published_hosts', + 'delete_hosts', + 'read_hosts', + 'publish_hosts', + ); + foreach ( $host_caps as $cap ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { + $wp_roles->add_cap( 'dj', $cap, true ); + } + } + // 2.3.3.9: fix for existing DJ role old name + $wp_roles->roles['dj']['name'] = __( 'DJ / Host', 'radio_station' ); + $wp_roles->role_names['dj'] = __( 'DJ / Host', 'radio_station' ); + + // --- add Show Producer role --- + // 2.3.0: add equivalent capability role for Show Producer + $wp_roles->add_role( 'producer', __( 'Show Producer', 'radio-station' ), $caps ); + $role_caps = $wp_roles->roles['producer']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } + $producer_caps = array( + 'edit_producers', + 'edit_published_producers', + 'delete_producers', + 'read_producers', + 'publish_producers', + ); + foreach ( $producer_caps as $cap ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { + $wp_roles->add_cap( 'producer', $cap, true ); + } + } + + // --- grant all capabilities to Show Editors --- + // 2.3.0: set Show Editor role capabilities + $caps = array( + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'edit_private_shows' => true, + 'delete_shows' => true, + 'delete_published_shows' => true, + 'delete_others_shows' => true, + 'delete_private_shows' => true, + 'read_shows' => true, + 'publish_shows' => true, + + 'edit_playlists' => true, + 'edit_published_playlists' => true, + 'edit_others_playlists' => true, + 'edit_private_playlists' => true, + 'delete_playlists' => true, + 'delete_published_playlists' => true, + 'delete_others_playlists' => true, + 'delete_private_playlists' => true, + 'read_playlists' => true, + 'publish_playlists' => true, + + 'edit_overrides' => true, + 'edit_overrides_playlists' => true, + 'edit_others_overrides' => true, + 'edit_private_overrides' => true, + 'delete_overrides' => true, + 'delete_published_overrides' => true, + 'delete_others_overrides' => true, + 'delete_private_overrides' => true, + 'read_overrides' => true, + 'publish_overrides' => true, + + 'edit_hosts' => true, + 'edit_published_hosts' => true, + 'edit_others_hosts' => true, + 'delete_hosts' => true, + 'read_hosts' => true, + 'publish_hosts' => true, + + 'edit_producers' => true, + 'edit_published_producers' => true, + 'edit_others_producers' => true, + 'delete_producers' => true, + 'read_producers' => true, + 'publish_producers' => true, + + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_others_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, + ); + + // --- add the Show Editor role --- + // 2.3.0: added Show Editor role + $wp_roles->add_role( 'show-editor', __( 'Show Editor', 'radio-station' ), $caps ); + + // --- check plugin setting for authors --- + $add_author_caps = radio_station_get_setting( 'add_author_capabilities' ); + if ( 'yes' === (string) $add_author_caps ) { + + // --- grant show edit capabilities to author users --- + $author_caps = $wp_roles->roles['author']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $author_caps ) ) { + $author_caps = array(); + } + $extra_caps = array( + 'edit_shows', + 'edit_published_shows', + 'read_shows', + 'publish_shows', + + 'edit_playlists', + 'edit_published_playlists', + 'read_playlists', + 'publish_playlists', + + 'edit_overrides', + 'edit_published_overrides', + 'read_overrides', + 'publish_overrides', + ); + foreach ( $extra_caps as $cap ) { + if ( !array_key_exists( $cap, $author_caps ) || ( !$author_caps[$cap] ) ) { + $wp_roles->add_cap( 'author', $cap, true ); + } + } + } + + // --- specify edit caps (for editors and admins) --- + // 2.3.0: added show override, host and producer capabilities + $edit_caps = array( + 'edit_shows', + 'edit_published_shows', + 'edit_others_shows', + 'edit_private_shows', + 'delete_shows', + 'delete_published_shows', + 'delete_others_shows', + 'delete_private_shows', + 'read_shows', + 'publish_shows', + + 'edit_playlists', + 'edit_published_playlists', + 'edit_others_playlists', + 'edit_private_playlists', + 'delete_playlists', + 'delete_published_playlists', + 'delete_others_playlists', + 'delete_private_playlists', + 'read_playlists', + 'publish_playlists', + + 'edit_overrides', + 'edit_published_overrides', + 'edit_others_overrides', + 'edit_private_overrides', + 'delete_overrides', + 'delete_published_overrides', + 'delete_others_overrides', + 'delete_private_overrides', + 'read_overrides', + 'publish_overrides', + + 'edit_hosts', + 'edit_published_hosts', + 'edit_others_hosts', + 'delete_hosts', + 'delete_others_hosts', + 'read_hosts', + 'publish_hosts', + + 'edit_producers', + 'edit_published_producers', + 'edit_others_producers', + 'delete_producers', + 'delete_others_producers', + 'read_producers', + 'publish_producers', + ); + + // --- check plugin setting for editors --- + $add_editor_caps = radio_station_get_setting( 'add_editor_capabilities' ); + if ( 'yes' === (string) $add_editor_caps ) { + + // --- grant show edit capabilities to editor users --- + $editor_caps = $wp_roles->roles['editor']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $editor_caps ) ) { + $editor_caps = array(); + } + foreach ( $edit_caps as $cap ) { + if ( !array_key_exists( $cap, $editor_caps ) || ( !$editor_caps[$cap] ) ) { + $wp_roles->add_cap( 'editor', $cap, true ); + } + } + } + + // --- grant all plugin capabilities to admin users --- + $admin_caps = $wp_roles->roles['administrator']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $admin_caps ) ) { + $admin_caps = array(); + } + foreach ( $edit_caps as $cap ) { + if ( !array_key_exists( $cap, $admin_caps ) || ( !$admin_caps[$cap] ) ) { + $wp_roles->add_cap( 'administrator', $cap, true ); + } + } + +} + +// ---------------------------------- +// Admin Fix for DJ / Host Role Label +// ---------------------------------- +// 2.3.3.9: added for user edit screen crackliness +add_filter( 'editable_roles', 'radio_station_role_check_test', 9 ); +function radio_station_role_check_test( $roles ) { + if ( RADIO_STATION_DEBUG && is_admin() ) { + echo 'DJ Role: ' . esc_html( print_r( $roles['dj'], true ) ) . ''; + } + $roles['dj']['name'] = __( 'DJ / Host', 'radio-station' ); + return $roles; +} + +// --------------------------------- +// maybe Revoke Edit Show Capability +// --------------------------------- +// (revoke ability to edit show if user is not assigned to it) +add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 4 ); +function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { + + global $post, $wp_roles; + + // 2.4.0.4.1: fix for early capability check plugin conflict + if ( !function_exists( 'radio_station_get_setting' ) ) { + return $allcaps; + } + + // --- check if super admin --- + // 2.3.3.6: get user object from fourth argument instead + // ? fix to not revoke edit caps from super admin ? + // (not implemented, as causing a connection reset error!) + // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { + // return $allcaps; + // } + + // --- debug passed capability arguments --- + // TODO: get post object from args instead of global ? + if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { + echo 'Cap Args: ' . esc_html( print_r( $args, true ) ) . ''; + } + + // --- check for editor role --- + // 2.3.3.6: check editor roles first separately + // 2.4.0.4: only add WordPress editor role if on in settings + $editor_roles = array( 'administrator', 'show-editor' ); + $editor_role_caps = radio_station_get_setting( 'add_editor_capabilities' ); + if ( 'yes' == $editor_role_caps ) { + $editor_roles[] = 'editor'; + } + foreach ( $editor_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + return $allcaps; + } + } + + // --- get roles with edit shows capability --- + $edit_show_roles = $edit_others_shows_roles = array(); + if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { + foreach ( $wp_roles->roles as $name => $role ) { + // 2.3.0: fix to skip roles with no capabilities assigned + if ( isset( $role['capabilities'] ) ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + // 2.3.0: change publish_shows cap check to edit_shows + if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_show_roles ) ) { + $edit_show_roles[] = $name; + } + } + // 2.3.3.6: add check for edit-others_shows capability + if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_others_shows_roles ) ) { + $edit_others_shows_roles[] = $name; + } + } + } + } + } + } + + // 2.3.3.6: preserve if user has edit_others_shows capability + foreach ( $edit_others_shows_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + // 2.4.0.4: do not automatically assume capability match + // return $allcaps; + $found = true; + } + } + + // 2.2.8: remove strict in_array checking + $found = false; + foreach ( $edit_show_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + $found = true; + } + } + + // --- maybe revoke edit show capability for post --- + // 2.3.3.6: fix to incorrect logic for removing edit show capability + if ( $found ) { + + // --- limit this to published shows --- + // 2.3.0: added object and property_exists check to be safe + if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + + // 2.3.0: removed is_admin check (so works with frontend edit show link) + // 2.3.0: moved check if show is published inside + if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { + + // --- get show hosts and producers --- + $hosts = get_post_meta( $post->ID, 'show_user_list', true ); + $producers = get_post_meta( $post->ID, 'show_producer_list', true ); + + // 2.3.0.4: convert possible (old) non-array values + if ( !$hosts || empty( $hosts ) ) { + $hosts = array(); + } elseif ( !is_array( $hosts ) ) { + $hosts = array( $hosts ); + } + if ( !$producers || empty( $producers ) ) { + $producers = array(); + } elseif ( !is_array( $producers ) ) { + $producers = array( $producers ); + } + + // ---- revoke editing capability if not assigned to this show --- + // 2.2.8: remove strict in_array checking + // 2.3.0: also check new Producer role + if ( !in_array( $user->ID, $hosts ) && !in_array( $user->ID, $producers ) ) { + + // --- remove the edit_shows capability --- + $allcaps['edit_shows'] = false; + $allcaps['edit_others_shows'] = false; + if ( RADIO_STATION_DEBUG ) { + echo 'Removed Edit Show Caps for Post ID ' . esc_html( $post->ID ) . ''; + } + + // 2.3.0: move check if show is published inside + if ( 'publish' == $post->post_status ) { + $allcaps['edit_published_shows'] = false; + } + } else { + // 2.4.0.4: add edit others shows capability + // (fix for when not original show author) + $allcaps['edit_shows'] = true; + $allcaps['edit_others_shows'] = true; + if ( RADIO_STATION_DEBUG ) { + echo 'Added Edit Show Caps for Post ID ' . esc_html( $post->ID ) . ''; + } + + } + } + } + } + + return $allcaps; +} diff --git a/js/moment.js b/js/moment.js index 1484d6c..2e55983 100644 --- a/js/moment.js +++ b/js/moment.js @@ -1,5 +1,5 @@ //! moment.js -//! version : 2.29.1 +//! version : 2.29.2 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com @@ -76,8 +76,9 @@ function map(arr, fn) { var res = [], - i; - for (i = 0; i < arr.length; ++i) { + i, + arrLen = arr.length; + for (i = 0; i < arrLen; ++i) { res.push(fn(arr[i], i)); } return res; @@ -206,7 +207,10 @@ updateInProgress = false; function copyConfig(to, from) { - var i, prop, val; + var i, + prop, + val, + momentPropertiesLen = momentProperties.length; if (!isUndefined(from._isAMomentObject)) { to._isAMomentObject = from._isAMomentObject; @@ -239,8 +243,8 @@ to._locale = from._locale; } - if (momentProperties.length > 0) { - for (i = 0; i < momentProperties.length; i++) { + if (momentPropertiesLen > 0) { + for (i = 0; i < momentPropertiesLen; i++) { prop = momentProperties[i]; val = from[prop]; if (!isUndefined(val)) { @@ -295,8 +299,9 @@ var args = [], arg, i, - key; - for (i = 0; i < arguments.length; i++) { + key, + argLen = arguments.length; + for (i = 0; i < argLen; i++) { arg = ''; if (typeof arguments[i] === 'object') { arg += '\n[' + i + '] '; @@ -446,7 +451,8 @@ ); } - var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, + var formattingTokens = + /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, formatFunctions = {}, formatTokenFunctions = {}; @@ -750,8 +756,9 @@ if (typeof units === 'object') { units = normalizeObjectUnits(units); var prioritized = getPrioritizedUnits(units), - i; - for (i = 0; i < prioritized.length; i++) { + i, + prioritizedLen = prioritized.length; + for (i = 0; i < prioritizedLen; i++) { this[prioritized[i].unit](units[prioritized[i].unit]); } } else { @@ -781,7 +788,8 @@ matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 // any word (or two) characters or numbers including two/three word month in arabic. // includes scottish gaelic two word and hyphenated months - matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, + matchWord = + /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, regexes; regexes = {}; @@ -807,15 +815,12 @@ return regexEscape( s .replace('\\', '') - .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function ( - matched, - p1, - p2, - p3, - p4 - ) { - return p1 || p2 || p3 || p4; - }) + .replace( + /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, + function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + } + ) ); } @@ -827,7 +832,8 @@ function addParseToken(token, callback) { var i, - func = callback; + func = callback, + tokenLen; if (typeof token === 'string') { token = [token]; } @@ -836,7 +842,8 @@ array[callback] = toInt(input); }; } - for (i = 0; i < token.length; i++) { + tokenLen = token.length; + for (i = 0; i < tokenLen; i++) { tokens[token[i]] = func; } } @@ -947,12 +954,12 @@ // LOCALES - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split( - '_' - ), - defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split( - '_' - ), + var defaultLocaleMonths = + 'January_February_March_April_May_June_July_August_September_October_November_December'.split( + '_' + ), + defaultLocaleMonthsShort = + 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, defaultMonthsShortRegex = matchWord, defaultMonthsRegex = matchWord; @@ -1394,14 +1401,12 @@ addRegexToken('W', match1to2); addRegexToken('WW', match1to2, match2); - addWeekParseToken(['w', 'ww', 'W', 'WW'], function ( - input, - week, - config, - token - ) { - week[token.substr(0, 1)] = toInt(input); - }); + addWeekParseToken( + ['w', 'ww', 'W', 'WW'], + function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + } + ); // HELPERS @@ -1526,9 +1531,8 @@ return ws.slice(n, 7).concat(ws.slice(0, n)); } - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split( - '_' - ), + var defaultLocaleWeekdays = + 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), defaultWeekdaysRegex = matchWord, @@ -2076,6 +2080,11 @@ return globalLocale; } + function isLocaleNameSane(name) { + // Prevent names that look like filesystem paths, i.e contain '/' or '\' + return name.match('^[^/\\\\]*$') != null; + } + function loadLocale(name) { var oldLocale = null, aliasedRequire; @@ -2084,7 +2093,8 @@ locales[name] === undefined && typeof module !== 'undefined' && module && - module.exports + module.exports && + isLocaleNameSane(name) ) { try { oldLocale = globalLocale._abbr; @@ -2301,8 +2311,10 @@ // iso 8601 regex // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + var extendedIsoRegex = + /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + basicIsoRegex = + /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, isoDates = [ ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], @@ -2333,7 +2345,8 @@ ], aspNetJsonRegex = /^\/?Date\((-?\d+)/i, // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 - rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, + rfc2822 = + /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, obsOffsets = { UT: 0, GMT: 0, @@ -2356,12 +2369,13 @@ allowTime, dateFormat, timeFormat, - tzFormat; + tzFormat, + isoDatesLen = isoDates.length, + isoTimesLen = isoTimes.length; if (match) { getParsingFlags(config).iso = true; - - for (i = 0, l = isoDates.length; i < l; i++) { + for (i = 0, l = isoDatesLen; i < l; i++) { if (isoDates[i][1].exec(match[1])) { dateFormat = isoDates[i][0]; allowTime = isoDates[i][2] !== false; @@ -2373,7 +2387,7 @@ return; } if (match[3]) { - for (i = 0, l = isoTimes.length; i < l; i++) { + for (i = 0, l = isoTimesLen; i < l; i++) { if (isoTimes[i][1].exec(match[3])) { // match[2] should be 'T' or space timeFormat = (match[2] || ' ') + isoTimes[i][0]; @@ -2753,12 +2767,13 @@ skipped, stringLength = string.length, totalParsedInputLength = 0, - era; + era, + tokenLen; tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { + tokenLen = tokens.length; + for (i = 0; i < tokenLen; i++) { token = tokens[i]; parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; @@ -2853,15 +2868,16 @@ i, currentScore, validFormatFound, - bestFormatIsValid = false; + bestFormatIsValid = false, + configfLen = config._f.length; - if (config._f.length === 0) { + if (configfLen === 0) { getParsingFlags(config).invalidFormat = true; config._d = new Date(NaN); return; } - for (i = 0; i < config._f.length; i++) { + for (i = 0; i < configfLen; i++) { currentScore = 0; validFormatFound = false; tempConfig = copyConfig({}, config); @@ -3102,7 +3118,8 @@ function isDurationValid(m) { var key, unitHasDecimal = false, - i; + i, + orderLen = ordering.length; for (key in m) { if ( hasOwnProp(m, key) && @@ -3115,7 +3132,7 @@ } } - for (i = 0; i < ordering.length; ++i) { + for (i = 0; i < orderLen; ++i) { if (m[ordering[i]]) { if (unitHasDecimal) { return false; // only allow non-integers for smallest unit @@ -3440,7 +3457,8 @@ // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere // and further modified to allow for strings containing both week and day - isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + isoRegex = + /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; function createDuration(input, key) { var duration = input, @@ -3661,9 +3679,10 @@ 'ms', ], i, - property; + property, + propertyLen = properties.length; - for (i = 0; i < properties.length; i += 1) { + for (i = 0; i < propertyLen; i += 1) { property = properties[i]; propertyTest = propertyTest || hasOwnProp(input, property); } @@ -4286,19 +4305,17 @@ addRegexToken('NNNN', matchEraName); addRegexToken('NNNNN', matchEraNarrow); - addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function ( - input, - array, - config, - token - ) { - var era = config._locale.erasParse(input, token, config._strict); - if (era) { - getParsingFlags(config).era = era; - } else { - getParsingFlags(config).invalidEra = input; + addParseToken( + ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], + function (input, array, config, token) { + var era = config._locale.erasParse(input, token, config._strict); + if (era) { + getParsingFlags(config).era = era; + } else { + getParsingFlags(config).invalidEra = input; + } } - }); + ); addRegexToken('y', matchUnsigned); addRegexToken('yy', matchUnsigned); @@ -4590,14 +4607,12 @@ addRegexToken('GGGGG', match1to6, match6); addRegexToken('ggggg', match1to6, match6); - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function ( - input, - week, - config, - token - ) { - week[token.substr(0, 2)] = toInt(input); - }); + addWeekParseToken( + ['gggg', 'ggggg', 'GGGG', 'GGGGG'], + function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + } + ); addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { week[token] = hooks.parseTwoDigitYear(input); @@ -5620,7 +5635,7 @@ //! moment.js - hooks.version = '2.29.1'; + hooks.version = '2.29.2'; setHookCallback(createLocal); diff --git a/js/moment.min.js b/js/moment.min.js index 57cd2d4..f614861 100644 --- a/js/moment.min.js +++ b/js/moment.min.js @@ -1,2 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function f(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(m(e,t))return;return 1}function r(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function a(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function d(e,t){for(var n=[],s=0;s>>0,s=0;sFe(e)?(r=e+1,a-Fe(e)):(r=e,a);return{year:r,dayOfYear:o}}function Ae(e,t,n){var s,i,r=Ge(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+je(i=e.year()-1,t,n):a>je(e.year(),t,n)?(s=a-je(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function je(e,t,n){var s=Ge(e,t,n),i=Ge(e+1,t,n);return(Fe(e)-s+i)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),A("week",5),A("isoWeek",5),ce("w",te),ce("ww",te,Q),ce("W",te),ce("WW",te,Q),ge(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=Z(e)});function Ie(e,t){return e.slice(t,7).concat(e.slice(0,t))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),A("day",11),A("weekday",11),A("isoWeekday",11),ce("d",te),ce("e",te),ce("E",te),ce("dd",function(e,t){return t.weekdaysMinRegex(e)}),ce("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ce("dddd",function(e,t){return t.weekdaysRegex(e)}),ge(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:y(n).invalidWeekday=e}),ge(["d","e","E"],function(e,t,n,s){t[s]=Z(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),$e="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),qe=de,Be=de,Je=de;function Qe(){function e(e,t){return t.length-e.length}for(var t,n,s,i,r=[],a=[],o=[],u=[],l=0;l<7;l++)t=_([2e3,1]).day(l),n=me(this.weekdaysMin(t,"")),s=me(this.weekdaysShort(t,"")),i=me(this.weekdays(t,"")),r.push(n),a.push(s),o.push(i),u.push(n),u.push(s),u.push(i);r.sort(e),a.sort(e),o.sort(e),u.sort(e),this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Xe),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),L("hour","h"),A("hour",13),ce("a",et),ce("A",et),ce("H",te),ce("h",te),ce("k",te),ce("HH",te,Q),ce("hh",te,Q),ce("kk",te,Q),ce("hmm",ne),ce("hmmss",se),ce("Hmm",ne),ce("Hmmss",se),ye(["H","HH"],Me),ye(["k","kk"],function(e,t,n){var s=Z(e);t[Me]=24===s?0:s}),ye(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ye(["h","hh"],function(e,t,n){t[Me]=Z(e),y(n).bigHour=!0}),ye("hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s)),y(n).bigHour=!0}),ye("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i)),y(n).bigHour=!0}),ye("Hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s))}),ye("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i))});var tt=z("Hours",!0);var nt,st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Te,monthsShort:Ne,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return nt}function ut(t){var e;if(void 0===it[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=nt._abbr,require("./locale/"+t),lt(e)}catch(e){it[t]=null}return it[t]}function lt(e,t){var n;return e&&((n=r(t)?dt(e):ht(e,t))?nt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),nt._abbr}function ht(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])Y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ut(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new x(b(s,t)),rt[e]&&rt[e].forEach(function(e){ht(e.name,e.config)}),lt(e),it[e]}function dt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return nt;if(!o(e)){if(t=ut(e))return t;e=[e]}return ot(e)}function ct(e){var t,n=e._a;return n&&-2===y(e).overflow&&(t=n[ve]<0||11xe(n[pe],n[ve])?ke:n[Me]<0||24je(n,r,a)?y(e)._overflowWeeks=!0:null!=u?y(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[pe]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=St(e._a[pe],s[pe]),(e._dayOfYear>Fe(r)||0===e._dayOfYear)&&(y(e)._overflowDayOfYear=!0),n=Ve(r,0,e._dayOfYear),e._a[ve]=n.getUTCMonth(),e._a[ke]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=u[t]=s[t];for(;t<7;t++)e._a[t]=u[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[Me]&&0===e._a[De]&&0===e._a[Se]&&0===e._a[Ye]&&(e._nextDay=!0,e._a[Me]=0),e._d=(e._useUTC?Ve:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,u),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Me]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(y(e).weekdayMismatch=!0)}}function Ot(e){if(e._f!==f.ISO_8601)if(e._f!==f.RFC_2822){e._a=[],y(e).empty=!0;for(var t,n,s,i,r,a,o,u=""+e._i,l=u.length,h=0,d=H(e._f,e._locale).match(N)||[],c=0;cn.valueOf():n.valueOf()"}),pn.toJSON=function(){return this.isValid()?this.toISOString():null},pn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},pn.unix=function(){return Math.floor(this.valueOf()/1e3)},pn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},pn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},pn.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},pn.isLocal=function(){return!!this.isValid()&&!this._isUTC},pn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},pn.isUtc=At,pn.isUTC=At,pn.zoneAbbr=function(){return this._isUTC?"UTC":""},pn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},pn.dates=n("dates accessor is deprecated. Use date instead.",fn),pn.months=n("months accessor is deprecated. Use month instead",Ue),pn.years=n("years accessor is deprecated. Use year instead",Le),pn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),pn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!r(this._isDSTShifted))return this._isDSTShifted;var e,t={};return v(t,this),(t=bt(t))._a?(e=(t._isUTC?_:Tt)(t._a),this._isDSTShifted=this.isValid()&&0>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),k("w",p),k("ww",p,w),k("W",p),k("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),k("d",p),k("e",p),k("E",p),k("dd",function(e,t){return t.weekdaysMinRegex(e)}),k("ddd",function(e,t){return t.weekdaysShortRegex(e)}),k("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=v,et=v,tt=v;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),k("a",rt),k("A",rt),k("H",p),k("h",p),k("k",p),k("HH",p,w),k("hh",p,w),k("kk",p,w),k("hmm",ge),k("hmmss",we),k("Hmm",ge),k("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});v=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ve),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0 -1) { divs = clocks[j].children; for (k = 0; k < divs.length; k++) { - if (divs[k].className == 'radio-server-time') {divs[k].innerHTML = servertime;} - if (divs[k].className == 'radio-server-date') {divs[k].innerHTML = serverdate;} - if (init && zone && (divs[k].className == 'radio-server-zone') ) {divs[k].innerHTML = serverzone;} + if ((divs[k].className == 'radio-server-time') && (divs[k].innerHTML != servertime)) {divs[k].innerHTML = servertime;} + else if ((divs[k].className == 'radio-server-date') && (divs[k].innerHTML != serverdate)) {divs[k].innerHTML = serverdate;} + else if (init && zone && (divs[k].className == 'radio-server-zone') && (divs[k].innerHTML != serverzone)) {divs[k].innerHTML = serverzone;} } } @@ -163,9 +163,9 @@ function radio_clock_date_time(init) { if (classes.indexOf('radio-station-user-clock') > -1) { divs = clocks[j].children; for (k = 0; k < divs.length; k++) { - if (divs[k].className == 'radio-user-time') {divs[k].innerHTML = usertime;} - if (divs[k].className == 'radio-user-date') {divs[k].innerHTML = userdate;} - if (init && zone && (divs[k].className == 'radio-user-zone') ) {divs[k].innerHTML = userzone;} + if ((divs[k].className == 'radio-user-time') && (divs[k].innerHTML != usertime)) {divs[k].innerHTML = usertime;} + else if ((divs[k].className == 'radio-user-date') && (divs[k].innerHTML != userdate)) {divs[k].innerHTML = userdate;} + else if (init && zone && (divs[k].className == 'radio-user-zone') && (divs[k].innerHTML != userzone)) {divs[k].innerHTML = userzone;} } } } diff --git a/js/radio-station-countdown.js b/js/radio-station-countdown.js index 1c141aa..745932c 100644 --- a/js/radio-station-countdown.js +++ b/js/radio-station-countdown.js @@ -19,7 +19,7 @@ function radio_countdown() { } /* Current Show Countdown */ - jQuery('.current-show-end').each(function() { + jQuery('.current-show-list.countdown .current-show-end').each(function() { showendtime = parseInt(jQuery(this).val()); diff = showendtime - radio.time.current; if (radio.debug) { @@ -33,7 +33,7 @@ function radio_countdown() { }); /* Upcoming Show Countdown */ - jQuery('.upcoming-show-times').each(function() { + jQuery('.upcoming-shows-list.countdown .upcoming-show-times').each(function() { times = jQuery(this).val().split('-'); times[0] = parseInt(times[0]); diffa = times[0] - radio.time.current; times[1] = parseInt(times[1]); diffb = times[1] - radio.time.current; @@ -60,7 +60,9 @@ function radio_countdown() { }); /* Continue Countdown */ - if ( jQuery('.current-show-end') || jQuery('.upcoming-show-times') || jQuery('.current-playlist-end') ) { + if ( jQuery('.current-show-list.countdown .current-show-end') + || jQuery('.upcoming-shows-list.countdown .upcoming-show-times') + || jQuery('.current-playlist-end') ) { setTimeout('radio_countdown();', 1000); } } diff --git a/js/radio-station.js b/js/radio-station.js index 2994e53..e055314 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -1,6 +1,6 @@ -/* --------------------- */ -/* Radio Station ScriptS */ -/* --------------------- */ +/* -------------------- */ +/* Radio Station Script */ +/* -------------------- */ /* Smooth Scrolling */ function radio_scroll_to(id) { @@ -15,14 +15,14 @@ function radio_scroll_to(id) { } /* Get Day of Week */ -function radio_get_weekday(x) { - if (x == '0') {day = 'sunday';} - else if (x == '1') {day = 'monday';} - else if (x == '2') {day = 'tuesday';} - else if (x == '3') {day = 'wednesday';} - else if (x == '4') {day = 'thursday';} - else if (x == '5') {day = 'friday';} - else if (x == '6') {day = 'saturday';} +function radio_get_weekday(d) { + if (d == '0') {day = 'sunday';} + else if (d == '1') {day = 'monday';} + else if (d == '2') {day = 'tuesday';} + else if (d == '3') {day = 'wednesday';} + else if (d == '4') {day = 'thursday';} + else if (d == '5') {day = 'friday';} + else if (d == '6') {day = 'saturday';} return day; } @@ -44,7 +44,10 @@ radio_cookie = { while (c.charAt(0) == ' ') { c = c.substring(1,c.length); if (c.indexOf(nameeq) == 0) { - return JSON.parse(c.substring(nameeq.length, c.length)); + /* 2.5.0: fix for possible empty value */ + value = c.substring(nameeq.length, c.length).trim(); + if (value == '') {return null;} + return JSON.parse(value); } } } diff --git a/loader.php b/loader.php index 177f71f..d4c6a52 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.2.2 +// Version: 1.2.5 // -------------- // Note: Changelog and structure at end of file. // @@ -97,6 +97,7 @@ // =========================== // usage: change class prefix to the plugin function prefix if ( !class_exists( 'radio_station_loader' ) ) { + // phpcs:ignore PEAR.NamingConventions.ValidClassName.Invalid,PEAR.NamingConventions.ValidClassName.StartWithCapital class radio_station_loader { public $args = null; @@ -131,8 +132,11 @@ public function __construct( $args ) { // 1.1.4: fix to debug prefix key $prefix = $args['settings'] . '-'; } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST[$prefix . 'debug'] ) ) { - if ( ( '1' == $_REQUEST[$prefix . 'debug'] ) || ( 'yes' == $_REQUEST[$prefix . 'debug'] ) ) { + // 1.2.5: use sanitize_text_field on request variable + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( in_array( sanitize_text_field( $_REQUEST[$prefix . 'debug'] ), array( '1', 'yes' ) ) ) { $this->debug = true; } } @@ -189,9 +193,12 @@ public function setup_plugin() { if ( !isset( $args['dir'] ) ) { $args['dir'] = dirname( $args['file'] ); } + // phpcs:ignore WordPress.WP.AlternativeFunctions $fh = fopen( $args['file'], 'r' ); + // phpcs:ignore WordPress.WP.AlternativeFunctions $data = fread( $fh, 2048 ); $this->data = str_replace( "\r", "\n", $data ); + // phpcs:ignore WordPress.WP.AlternativeFunctions fclose( $fh ); // --- Version --- @@ -424,13 +431,16 @@ public function reset_settings() { if ( !isset( $_REQUEST['page'] ) ) { return; } - if ( $_REQUEST['page'] != $args['slug'] ) { + // 1.0.5: use sanitize_title on request variables + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( sanitize_title( $_REQUEST['page'] ) != $args['slug'] ) { return; } if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } - if ( 'reset' != $_POST[$args['namespace'] . '_update_settings'] ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( 'reset' != sanitize_title( $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } @@ -521,11 +531,8 @@ public function update_settings() { // --- get posted value --- // 1.0.6: set null value for unchecked checkbox fix - $posted = null; + // 1.2.5: moved get posted value to within each type with sanitization $postkey = $args['settings'] . '_' . $key; - if ( isset( $_POST[$postkey] ) ) { - $posted = $_POST[$postkey]; - } $newsettings = null; // --- maybe validate special options --- @@ -574,14 +581,17 @@ public function update_settings() { } if ( $this->debug ) { - echo 'Saving Setting Key ' . $key . ' (' . $postkey . '): ' . print_r( $posted, true ) . '
    '; - echo 'Type: ' . $type . ' - Valid Options ' . $key . ': ' . print_r( $valid, true ) . '
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Saving Setting Key ' . esc_html( $key ) . ' (' . esc_html( $postkey ) . ')
    ' . PHP_EOL; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Type: ' . esc_html( $type ) . ' - Valid Options ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
    ' . PHP_EOL; } // --- sanitize value according to type --- if ( strstr( $type, '/' ) ) { // --- implicit radio / select --- + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; $valid = explode( '/', $type ); if ( in_array( $posted, $valid ) ) { $settings[$key] = $posted; @@ -592,6 +602,7 @@ public function update_settings() { // --- checkbox / toggle --- // 1.0.6: fix to new unchecked checkbox value // 1.0.9: maybe validate to specified checkbox value + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; if ( isset( $values['value'] ) ) { $valid = array( $values['value'] ); } else { @@ -606,14 +617,15 @@ public function update_settings() { } elseif ( 'textarea' == $type ) { // --- text area --- - // TODO: maybe use sanitize text field ? - $posted = stripslashes( $posted ); + // 1.2.5: use sanitize_textarea_field with stripslashes + $posted = isset( $_POST[$postkey] ) ? sanitize_textarea_field( stripslashes( $_POST[$postkey] ) ) : null; $settings[$key] = $posted; } elseif ( 'text' == $type ) { // --- text field (slug) --- // 1.0.9: move text field sanitization to validation + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; if ( !is_string( $valid ) ) { $valid = 'TEXT'; } @@ -623,6 +635,7 @@ public function update_settings() { // --- number field value --- // 1.0.9: added support for number step, minimum and maximum + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; $newsettings = $posted; $valid = 'NUMERIC'; if ( isset( $values['step'] ) ) { @@ -645,8 +658,9 @@ public function update_settings() { $optionkey = $args['settings'] . '_' . $key . '-' . $option; if ( isset( $_POST[$optionkey] ) ) { // 1.1.2: check for value if specified - if ( ( isset( $values['value'] ) && ( $values['value'] == $_POST[$optionkey] ) ) - || ( !isset( $values['value'] ) && ( 'yes' == $_POST[$optionkey] ) ) ) { + // 1.2.5: apply sanitize_text_field to posted value + if ( ( isset( $values['value'] ) && ( sanitize_text_field( $_POST[$optionkey] ) == $values['value'] ) ) + || ( !isset( $values['value'] ) && ( 'yes' == sanitize_text_field( $_POST[$optionkey] ) ) ) ) { // 1.1.0: fixed to save only array of key values $posted[] = $option; } @@ -658,6 +672,7 @@ public function update_settings() { // -- comma separated values --- // 1.0.4: added comma separated values option + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; if ( strstr( $posted, ',' ) ) { $posted = explode( ',', $posted ); } else { @@ -683,6 +698,7 @@ public function update_settings() { } elseif ( ( 'radio' == $type ) || ( 'select' == $type ) ) { // --- explicit radio or select value --- + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; if ( is_string( $valid ) ) { $newsettings = $posted; } elseif ( is_array( $valid ) && array_key_exists( $posted, $valid ) ) { @@ -693,39 +709,72 @@ public function update_settings() { // --- multiselect values --- // 1.0.9: added multiselect value saving + $posted = isset( $_POST[$postkey] ) ? array_map( 'sanitize_text_field', $_POST[$postkey] ) : array(); $newsettings = array_values( $posted ); } elseif ( 'image' == $type ) { // --- check attachment ID value --- // 1.1.7: add image attachment ID saving - if ( '' != $posted ) { + $posted = isset( $_POST[$postkey] ) ? absint( $_POST[$postkey] ) : null; + if ( $posted ) { $attachment = wp_get_attachment_image_src( $posted, 'full' ); if ( is_array( $attachment ) ) { $settings[$key] = $posted; } } - } elseif ( ( 'color' == $type ) || ( 'coloralpha' == $type ) ) { + } elseif ( 'color' == $type ) { - // --- color or color alpha setting --- + // --- hex color setting --- // 1.1.7: added color picker value saving - // TODO: maybe validate this setting ? + // 1.2.5: use sanitize_hex_color on color field + $posted = isset( $_POST[$postkey] ) ? sanitize_hex_color( $_POST[$postkey] ) : null; + $settings[$key] = $posted; + + } elseif ( 'coloralpha' == $type ) { + + // --- color alpha setting --- + // 1.2.5: separated color alpha setting condition + // 1.2.5: added rgba version of sanitization + // ref: https://wordpress.stackexchange.com/a/262578/76440 + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + if ( !is_null( $posted ) ) { + $posted = str_replace( ' ', '', $posted ); + $values = array(); + sscanf( $color, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $alpha ); + foreach ( $values as $key => $value ) { + $value = absint( $value ); + if ( $value < 0 ) { + $values[$key] = 0; + } elseif ( $value > 255 ) { + $values[$key] = 255; + } + } + if ( $alpha < 0 ) { + $alpha = 0; + } elseif ( $alpha > 1 ) { + $alpha = 1; + } + $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $alpha . ')'; + } $settings[$key] = $posted; } if ( $this->debug ) { - echo 'New Settings for Key ' . $key . ': '; + echo 'New Settings for Key ' . esc_html( $key ) . ': '; // 1.2.0: added isset check for newsetting if ( !is_null( $newsettings ) ) { - echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo '(To-validate) ' . esc_html( print_r( $newsettings, true ) ) . '
    ' . PHP_EOL; } else { // 1.1.7 handle if (new) key not set yet if ( isset( $settings[$key] ) ) { - echo '(validated) ' . print_r( $settings[$key], true ) . '
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo '(Validated) ' . esc_html( print_r( $settings[$key], true ) ) . '
    ' . PHP_EOL; } else { - echo 'No setting yet for key ' . $key . '
    '; + echo 'No setting yet for key ' . esc_html( $key ) . '
    ' . PHP_EOL; } } } @@ -742,7 +791,7 @@ public function update_settings() { foreach ( $newsettings as $newkey => $newvalue ) { $newsetting = $this->validate_setting( $newvalue, $valid, $validate_args ); if ( $this->debug ) { - echo 'Validated Setting array value ' . $newvalue . ' to ' . $newsetting; + echo 'Validated Setting array value ' . esc_html( $newvalue ) . ' to ' . esc_html( $newsetting ); } if ( $newsetting || ( '' == $newsetting ) ) { $newsettings[$newkey] = $newsetting; @@ -769,7 +818,7 @@ public function update_settings() { $newvalue = $this->validate_setting( $value, $valid, $validate_args ); $newvalues[] = $newvalue; if ( $this->debug ) { - echo 'Validated Setting value ' . $value . ' to ' . $newvalue; + echo 'Validated Setting value ' . esc_html( $value ) . ' to ' . esc_html( $newvalue ) . '
    ' . PHP_EOL; } } $newsettings = implode( ',', $newvalues ); @@ -779,7 +828,7 @@ public function update_settings() { // 1.1.9: fix to allow saving of zero value // 1.2.1: fix to allow saving of empty value if ( $this->debug ) { - echo 'Validated Setting single value ' . $newsettings . ' to ' . $newsetting . '
    '; + echo 'Validated Setting single value ' . esc_html( $newsettings ) . ' to ' . esc_html( $newsetting ) . '
    ' . PHP_EOL; } if ( $newsetting || ( '' == $newsetting ) || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $settings[$key] = $newsetting; @@ -788,8 +837,10 @@ public function update_settings() { } if ( $this->debug ) { - echo 'Valid Options for Key ' . $key . ': ' . print_r( $valid, true ) . '
    '; - echo 'Validated Settings for Key ' . $key . ': ' . print_r( $settings[$key], true ) . '
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Valid Options for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
    ' . PHP_EOL; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Validated Settings for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $settings[$key], true ) ) . '
    ' . PHP_EOL; } } @@ -812,9 +863,10 @@ public function update_settings() { // --- output new settings --- if ( $this->debug ) { - echo "
    All New Settings:
    "; - print_r( $settings ); - echo "

    "; + echo '
    All New Settings:
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo esc_html( print_r( $settings, true ) ); + echo '

    '; } if ( $settings && is_array( $settings ) ) { @@ -830,14 +882,15 @@ public function update_settings() { // --- save settings tab --- // 1.2.0: validate and save current settings tab if ( isset( $_POST['settingstab'] ) ) { - $currenttab = $_POST['settingstab']; $tabs = array(); foreach ( $options as $key => $option ) { - if ( isset ( $option['tab'] ) && !in_array( $option['tab'], $tabs ) ) { + if ( isset( $option['tab'] ) && !in_array( $option['tab'], $tabs ) ) { $tabs[] = $option['tab']; } } if ( count( $tabs ) > 0 ) { + // 1.2.5: sanitize current tab value before validating + $currenttab = sanitize_title( $_POST['settingstab'] ); if ( in_array( $currenttab, $tabs ) ) { $settings['settingstab'] = $currenttab; } elseif ( in_array( 'general', $tabs ) ) { @@ -848,7 +901,6 @@ public function update_settings() { } } - // --- update the plugin settings --- $settings['savetime'] = time(); update_option( $args['option'], $settings ); @@ -973,8 +1025,9 @@ public function validate_setting( $posted, $valid, $args ) { // 1.1.7: remove FILTER_VALIDATE_URL check - not working!? if ( $this->debug ) { - $check = filter_var( $url, FILTER_VALIDATE_URL ); - echo 'Validated URL: ' . print_r( $check, true ) . '
    '; + $check = filter_var( $posted, FILTER_VALIDATE_URL ); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Validated URL: ' . esc_html( print_r( $check, true ) ) . '
    '; } // if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) { // $posted = ''; @@ -1003,7 +1056,8 @@ public function validate_setting( $posted, $valid, $args ) { } $user = get_user_by( 'email', $email ); if ( $user ) { - $posted = $username; + // 1.2.3: fix to set email value + $posted = $email; } else { $posted = ''; } @@ -1240,7 +1294,8 @@ public function load_helpers() { // ------------------- public function maybe_load_thickbox() { $args = $this->args; - if ( isset( $_REQUEST['page'] ) && ( $_REQUEST['page'] == $args['slug'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] ) ) { add_thickbox(); } } @@ -1253,7 +1308,7 @@ public function readme_viewer() { $args = $this->args; $dir = $args['dir']; - echo ""; + echo ''; // 1.0.7: changed readme.php to reader.php (for Github) $readme = $dir . '/readme.txt'; @@ -1275,17 +1330,17 @@ public function readme_viewer() { $parsed = $readme->parse_readme_contents( $contents ); // --- output plugin info --- - echo "" . esc_html( __( 'Plugin Name' ) ) . ": " . esc_html( $parsed['name'] ) . "
    "; - // echo "" . esc_html( __( 'Tags' ) ) . ": " . esc_html( implode( ', ', $parsed['tags'] ) ) . "
    "; - echo "" . esc_html( __( 'Requires at least' ) ) . ": " . esc_html( __( 'WordPress' ) ) . " v" . esc_html( $parsed['requires_at_least'] ) . "
    "; - echo "" . esc_html( __( 'Tested up to' ) ) . ": " . esc_html( __( 'WordPress' ) ) . " v" . esc_html( $parsed['tested_up_to'] ) . "
    "; + echo '' . esc_html( __( 'Plugin Name' ) ) . ': ' . esc_html( $parsed['name'] ) . '
    ' . PHP_EOL; + // echo '' . esc_html( __( 'Tags' ) ) . ': ' . esc_html( implode( ', ', $parsed['tags'] ) ) . '
    ' . PHP_EOL; + echo '' . esc_html( __( 'Requires at least' ) ) . ': ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['requires_at_least'] ) . '
    ' . PHP_EOL; + echo '' . esc_html( __( 'Tested up to' ) ) . ': ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['tested_up_to'] ) . '
    ' . PHP_EOL; if ( isset( $parsed['stable_tag'] ) ) { - echo "" . esc_html( __( 'Stable Tag' ) ) . ": " . esc_html( $parsed['stable_tag'] ) . "
    "; + echo '' . esc_html( __( 'Stable Tag' ) ) . ': ' . esc_html( $parsed['stable_tag'] ) . '
    ' . PHP_EOL; } - echo "" . esc_html( __( 'Contributors' ) ) . ": " . esc_html( implode( ', ', $parsed['contributors'] ) ) . "
    "; - // echo "Donate Link: ".$parsed['donate_link']."
    "; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo "
    " . $parsed['short_description'] . "

    "; + echo '' . esc_html( __( 'Contributors' ) ) . ': ' . esc_html( implode( ', ', $parsed['contributors'] ) ) . '
    ' . PHP_EOL; + // echo 'Donate Link: ' . esc_html( $parsed['donate_link'] ) . '
    '; + // 1.2.5: use wp_kses_post on plugin short description markup + echo '
    ' . wp_kses_post( $parsed['short_description'] ) . '

    ' . PHP_EOL; // --- output sections --- // possible sections: 'description', 'installation', 'frequently_asked_questions', @@ -1302,25 +1357,24 @@ public function readme_viewer() { $parts[$i] = strtoupper( substr( $part, 0, 1 ) ) . substr( $part, 1 ); } $title = implode( ' ', $parts ); - echo "

    " . esc_html( $title ) . "

    "; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $section; + echo '

    ' . esc_html( $title ) . '

    ' . PHP_EOL; + // 1.2.5: use wp_kses_post on readme section output + echo wp_kses_post( $section ); } } if ( isset( $parsed['remaining_content'] ) && !empty( $remaining_content ) ) { - echo "

    " . esc_html( __( 'Extra Notes' ) ) . "

    "; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $parsed['remaining_content']; + echo '

    ' . esc_html( __( 'Extra Notes' ) ) . '

    ' . PHP_EOL; + // 1.2.5: use wp_kses_post on readme extra notes output + echo wp_kses_post( $parsed['remaining_content'] ); } } else { // --- fallback text-only display --- - $contents = str_replace( "\n", "
    ", $contents ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $contents; + $contents = str_replace( "\n", '
    ', $contents ); + echo wp_kses_post( $contents ); } - echo ""; + echo ''; exit; } @@ -1362,7 +1416,7 @@ public function load_freemius() { if ( !isset( $args['freemius_id'] ) || !isset( $args['freemius_key'] ) ) { return; } - + // --- check for free / premium plan --- // convert plan string value of 'free' or 'premium' to boolean premium switch // TODO: check for active addons also ? @@ -1376,7 +1430,9 @@ public function load_freemius() { // --- maybe redirect link to plugin support forum --- // TODO: change to use new Freemius 2.3.0 support link filter ? - if ( isset( $_REQUEST['page'] ) && ( $args['slug'] . '-wp-support-forum' == $_REQUEST['page'] ) && is_admin() ) { + // 1.0.5: use sanitize_text_field on request variable + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) { if ( !function_exists( 'wp_redirect' ) ) { include ABSPATH . WPINC . '/pluggable.php'; } @@ -1389,6 +1445,7 @@ public function load_freemius() { // $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); // } $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] ); + // phpcs:ignore WordPress.Security.SafeRedirect wp_redirect( $support_url ); exit; } @@ -1468,7 +1525,8 @@ public function load_freemius() { // --- filter settings before initializing --- $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings ); if ( $this->debug ) { - echo 'Freemius Settings: ' . print_r( $settings, true ) . ''; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Freemius Settings: ' . esc_html( print_r( $settings, true ) ) . '' . PHP_EOL; } if ( !$settings || !is_array( $settings ) ) { return; @@ -1477,7 +1535,8 @@ public function load_freemius() { // --- initialize Freemius now --- $freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings ); if ( $this->debug ) { - echo 'Freemius Object: ' . print_r( $freemius, true ) . ''; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Freemius Object: ' . esc_html( print_r( $freemius, true ) ) . '' . PHP_EOL; } // --- set plugin basename --- @@ -1512,8 +1571,10 @@ public function freemius_connect_message( $message, $user_first_name, $plugin_ti // default: 'Never miss an important update - opt-in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' // 1.0.9: fix to remove incorrect first argument in string replacement $message = __fs( 'hey-x' ) . '
    '; + // 1.2.4: added ordering to replacement arguments $message .= sprintf( - __( "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" ), + // Translators: plugin title, user name, site link, freemius link + __( 'If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s' ), '' . $plugin_title . '', '' . $user_login . '', $site_link, @@ -1596,7 +1657,7 @@ public function plugin_links( $links, $file ) { // (depending on whether top level menu or Settings submenu item) $page = $this->menu_added ? 'admin.php' : 'options-general.php'; $settings_url = add_query_arg( 'page', $args['slug'], admin_url( $page ) ); - $settings_link = "" . esc_html( __( 'Settings' ) ) . ""; + $settings_link = '' . esc_html( __( 'Settings' ) ) . ''; $link = array( 'settings' => $settings_link ); $links = array_merge( $link, $links ); @@ -1614,7 +1675,7 @@ public function plugin_links( $links, $file ) { $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : ''; } - $upgrade_link = "" . esc_html( __('Upgrade' ) ) . ""; + $upgrade_link = '" . esc_html( __( 'Upgrade' ) ) . ''; $link = array( 'upgrade' => $upgrade_link ); $links = array_merge( $link, $links ); @@ -1622,7 +1683,7 @@ public function plugin_links( $links, $file ) { // 1.2.0: added separate pro details link if ( isset( $args['pro_link'] ) ) { $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; - $pro_link = "" . esc_html( __('Pro Details' ) ) . ""; + $pro_link = '' . esc_html( __( 'Pro Details' ) ) . ''; $link = array( 'pro-details' => $pro_link ); $links = array_merge( $link, $links ); } @@ -1636,14 +1697,15 @@ public function plugin_links( $links, $file ) { if ( isset( $args['addons_link'] ) ) { $addons_url = $args['addons_link']; $addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : ''; - $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; + $addons_link = '' . esc_html( __( 'Add Ons' ) ) . ''; $link = array( 'addons' => $addons_link ); $links = array_merge( $link, $links ); } } if ( $this->debug ) { - echo 'Plugin Links for ' . $file . ': ' . print_r( $links, true ) . ''; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Plugin Links for ' . esc_html( $file ) . ': ' . esc_html( print_r( $links, true ) ) . '' . PHP_EOL; } } @@ -1653,21 +1715,26 @@ public function plugin_links( $links, $file ) { // ----------- // Message Box // ----------- + // 1.2.5: use output buffering for no-echo public function message_box( $message, $echo ) { - $box = ""; - $box .= ""; - $box .= "
    "; - $box .= "
    "; - $box .= $message; - $box .= "
    "; - $box .= "
    "; - if ( $echo ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $box; - } else { + + if ( !$echo ) { + ob_start(); + } + + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; + // 1.2.5: added wp_kses_post to message output + echo wp_kses_post( $message ) . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; + if ( !$echo ) { + $box = ob_get_contents(); + ob_end_clean(); return $box; } - return ''; } // ------------ @@ -1679,27 +1746,30 @@ public function notice_boxer() { $args = $this->args; // --- bug out if not on radio station pages --- + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( !isset( $_REQUEST['page'] ) ) { return; } - if ( $args['slug'] != substr( $_REQUEST['page'], 0, strlen( $args['slug'] ) ) ) { + // 1.0.5: use sanitize_title on request variable + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( substr( sanitize_title( $_REQUEST['page'] ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) { return; } - + // 1.2.2: bug out if adminsanity notices are loaded if ( isset( $GLOBALS['radio_station_data']['load']['notices'] ) && $GLOBALS['radio_station_data']['load']['notices'] ) { return; } // --- output notice box --- - echo '
    '; - echo '

    '; - echo '   '; - echo '' . __( 'Notices' ) . '   '; - echo '

    '; + echo '
    ' . PHP_EOL; + echo '

    ' . PHP_EOL; + echo '   ' . PHP_EOL; + echo '' . esc_html( __( 'Notices' ) ) . '   ' . PHP_EOL; + echo '

    ' . PHP_EOL; - echo ''; - echo '
    '; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- toggle notice box script --- echo ""; } } @@ -2849,7 +3062,8 @@ public function setting_styles() { // --- plugin header styles --- // 1.2.0: moved from plugin header section - $styles[] = '.pluginlink {text-decoration:none;}'; + // 1.2.3: remove underline from plugin icon spans + $styles[] = '.pluginlink, .pluginlink span {text-decoration:none;}'; $styles[] = '.smalllink {font-size:11px;}'; $styles[] = '.readme:hover {text-decoration:underline;}'; @@ -2873,8 +3087,9 @@ public function setting_styles() { $styles[] = '.settings-input input.setting-radio {}'; $styles[] = '.settings-input input.setting-checkbox {}'; $styles[] = '.settings-input input.setting-text {width:100%;}'; - $styles[] = '.settings-input input.setting-numeric {display:inline-block; width:50%; text-align:center;}'; + $styles[] = '.settings-input input.setting-numeric {display:inline-block; width:50%; text-align:center; vertical-align:middle;}'; $styles[] = '.settings-input input.setting-button {display:inline-block; padding:0px 5px;}'; + $styles[] = '.settings-input input.setting-button.number-down-button {padding:9px 7px;}'; $styles[] = '.settings-input input.setting-textarea {width:100%;}'; $styles[] = '.settings-input select.setting-select {min-width:100px; max-width:100%;}'; @@ -2903,8 +3118,8 @@ public function setting_styles() { $namespace = $this->namespace; $styles = apply_filters( $namespace . '_admin_page_styles', $styles ); echo ""; } @@ -3157,6 +3372,18 @@ function radio_station_settings_row( $option, $setting ) { } } + // ------------------ + // Settings Resources + // ------------------ + // 1.2.3: added for separate enqueueing of resources from table + if ( !function_exists( 'radio_station_settings_resources' ) ) { + function radio_station_settings_resources( $media, $color_picker ) { + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); + $instance = $GLOBALS[$namespace . '_instance']; + $instance->settings_resources( $media, $color_picker ); + } + } + } } @@ -3212,6 +3439,23 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.2.6 == +// - expanded wp_kses allowed input attributes + +// == 1.2.5 == +// - improved posted value input sanitization +// - corrected textarea field sanitization +// - added color and color alpha sanitization +// - use wp_kses and allowed HTML on outputs + +// == 1.2.4 == +// - fix to missing declaration on new settings_resources function + +// == 1.2.3 == +// - added separate enqueueing of settings resources +// - remove underline from plugin link icon spans +// - skip welcome message output if empty + // == 1.2.2 == // - merge in plugin links instead of using array_unshift // - update plugin repository rating URL diff --git a/options.php b/options.php index c3d540a..6b9f125 100644 --- a/options.php +++ b/options.php @@ -1,55 +1,59 @@ array( 'type' => 'text', 'options' => 'URL', 'label' => __( 'Streaming URL', 'radio-station' ), 'default' => '', - 'helper' => __( 'Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player.', 'radio-station' ), + 'helper' => __( 'Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds.', 'radio-station' ), 'tab' => 'general', 'section' => 'broadcast', ), - // --- Stream Format --- + // --- [Player] Stream Format --- 'streaming_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Streaming Format', 'radio-station' ), + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Streaming Format', 'radio-station' ), 'default' => 'aac', - 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', + 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', ), - // --- Fallback Stream URL --- + // --- [Player] Fallback Stream URL --- 'fallback_url' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Fallback Stream URL', 'radio-station' ), + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Fallback Stream URL', 'radio-station' ), 'default' => '', - 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', + 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', ), - // --- Fallback Stream Format --- + // --- [Player] Fallback Stream Format --- 'fallback_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Fallback Format', 'radio-station' ), + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Fallback Format', 'radio-station' ), 'default' => 'ogg', - 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', + 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', ), // --- Main Radio Language --- @@ -65,9 +69,9 @@ // === Station === - // --- Station Title --- + // --- [Player] Station Title --- // 2.3.3.8: added station title field - 'station_title' => array( + 'station_title' => array( 'type' => 'text', 'label' => __( 'Station Title', 'radio-station' ), 'default' => '', @@ -76,9 +80,9 @@ 'section' => 'station', ), - // --- Station Image --- + // --- [Player] Station Image --- // 2.3.3.8: added station logo image field - 'station_image' => array( + 'station_image' => array( 'type' => 'image', 'label' => __( 'Station Logo Image', 'radio-station' ), 'default' => '', @@ -126,7 +130,7 @@ // --- Phone for Shows --- // 2.3.3.6: added default to station phone option - 'shows_phone' => array( + 'shows_phone' => array( 'type' => 'checkbox', 'default' => '', 'value' => 'yes', @@ -138,7 +142,7 @@ // --- Station Email Address --- // 2.3.3.8: added station email address option - 'station_email' => array( + 'station_email' => array( 'type' => 'email', 'default' => '', 'label' => __( 'Station Email', 'radio-station' ), @@ -149,7 +153,7 @@ // --- Email for Shows --- // 2.3.3.8: added default to email address option - 'shows_email' => array( + 'shows_email' => array( 'type' => 'checkbox', 'default' => '', 'value' => 'yes', @@ -236,10 +240,18 @@ // === Basic Stream Player === - // TODO: add note about these defaults being overrideable in widgets + // --- Defaults Note --- + // 2.5.0: added note about defaults being overrideable in widgets + 'player_bar_note' => array( + 'type' => 'note', + 'label' => __( 'Player Defaults Note', 'radio-station' ), + 'helper' => __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), - // --- Player Title --- - 'player_title' => array ( + // --- [Player] Player Title --- + 'player_title' => array( 'type' => 'checkbox', 'label' => __( 'Display Station Title', 'radio-station' ), 'default' => 'yes', @@ -247,11 +259,10 @@ 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Player Image --- - 'player_image' => array( + // --- [Player] Player Image --- + 'player_image' => array( 'type' => 'checkbox', 'label' => __( 'Display Station Image', 'radio-station' ), 'default' => 'yes', @@ -259,12 +270,11 @@ 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Player Script --- + // --- [Player] Player Script --- // 2.4.0.3: change script default to jplayer - 'player_script' => array( + 'player_script' => array( 'type' => 'select', 'label' => __( 'Player Script', 'radio-station' ), 'default' => 'jplayer', @@ -276,13 +286,12 @@ 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Fallback Scripts --- + // --- [Player] Fallback Scripts --- // 2.4.0.3: added fallback enable/disable switching // 2.4.0.3: fixed option label from Player Script - 'player_fallbacks' => array( + 'player_fallbacks' => array( 'type' => 'multicheck', 'label' => __( 'Fallback Scripts', 'radio-station' ), 'default' => array( 'amplitude', 'howler', 'jplayer' ), @@ -294,11 +303,10 @@ 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Player Theme --- - 'player_theme' => array( + // --- [Player] Player Theme --- + 'player_theme' => array( 'type' => 'select', 'label' => __( 'Default Player Theme', 'radio-station' ), 'default' => 'light', @@ -309,11 +317,10 @@ 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Player Buttons --- - 'player_buttons' => array( + // --- [Player] Player Buttons --- + 'player_buttons' => array( 'type' => 'select', 'label' => __( 'Default Player Buttons', 'radio-station' ), 'default' => 'rounded', @@ -325,12 +332,11 @@ 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), - // --- Volume Controls --- + // --- [Player] Volume Controls --- // 2.4.0.3: added enable/disable volume controls option - 'player_volumes' => array( + 'player_volumes' => array( 'type' => 'multicheck', 'label' => __( 'Volume Controls', 'radio-station' ), 'default' => array( 'slider', 'updown', 'mute', 'max' ), @@ -345,8 +351,8 @@ 'section' => 'basic', ), - // --- Player Debug Mode --- - 'player_debug' => array( + // --- [Player] Player Debug Mode --- + 'player_debug' => array( 'type' => 'checkbox', 'label' => __( 'Player Debug Mode', 'radio-station' ), 'default' => '', @@ -354,13 +360,12 @@ 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', - 'pro' => false, ), // === Player Colours === // --- [Pro/Player] Playing Highlight Color --- - 'player_playing_color' => array( + 'player_playing_color' => array( 'type' => 'color', 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), 'default' => '#70E070', @@ -371,7 +376,7 @@ ), // --- [Pro/Player] Control Icons Highlight Color --- - 'player_buttons_color' => array( + 'player_buttons_color' => array( 'type' => 'color', 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), 'default' => '#00A0E0', @@ -382,7 +387,7 @@ ), // --- [Pro/Player] Volume Knob Color --- - 'player_thumb_color' => array( + 'player_thumb_color' => array( 'type' => 'color', 'label' => __( 'Volume Knob Color', 'radio-station' ), 'default' => '#80C080', @@ -393,7 +398,7 @@ ), // --- [Pro/Player] Volume Track Color --- - 'player_range_color' => array( + 'player_range_color' => array( 'type' => 'coloralpha', 'label' => __( 'Volume Track Color', 'radio-station' ), 'default' => '#80C080', @@ -405,8 +410,8 @@ // === Advanced Stream Player === - // --- Player Volume --- - 'player_volume' => array( + // --- [Player] Player Volume --- + 'player_volume' => array( 'type' => 'number', 'label' => __( 'Player Start Volume', 'radio-station' ), 'default' => 77, @@ -419,8 +424,8 @@ 'pro' => false, ), - // --- Single Player --- - 'player_single' => array( + // --- [Player] Single Player --- + 'player_single' => array( 'type' => 'checkbox', 'label' => __( 'Single Player at Once', 'radio-station' ), 'default' => 'yes', @@ -443,8 +448,8 @@ 'pro' => true, ), - // --- [Pro] Popup Player Window --- - /* 'player_popup' => array( + // --- [Pro/Player] Popup Player Window --- + /* 'player_popup' => array( 'type' => 'checkbox', 'label' => __( 'Popup Player Window', 'radio-station' ), 'default' => '', @@ -458,17 +463,17 @@ // === Sitewide Player Bar === // --- Player Bar Note --- - 'player_bar_note' => array( + 'player_bar_note' => array( 'type' => 'note', 'label' => __( 'Bar Defaults Note', 'radio-station' ), 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) - . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), + . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', ), // --- [Pro/Player] Sitewide Player Bar --- - 'player_bar' => array( + 'player_bar' => array( 'type' => 'select', 'label' => __( 'Sitewide Player Bar', 'radio-station' ), 'default' => 'off', @@ -484,7 +489,7 @@ ), // --- [Pro/Player] Player Bar Height --- - 'player_bar_height' => array( + 'player_bar_height' => array( 'type' => 'number', 'min' => 40, 'max' => 400, @@ -498,7 +503,7 @@ ), // --- [Pro/Player] Fade In Player Bar --- - 'player_bar_fadein' => array( + 'player_bar_fadein' => array( 'type' => 'number', 'label' => __( 'Fade In Player Bar', 'radio-station' ), 'default' => 2500, @@ -554,7 +559,7 @@ ), // --- [Pro/Player] Bar Player Text Color --- - 'player_bar_text' => array( + 'player_bar_text' => array( 'type' => 'color', 'label' => __( 'Bar Player Text Color', 'radio-station' ), 'default' => '#FFFFFF', @@ -565,7 +570,7 @@ ), // --- [Pro/Player] Bar Player Background Color --- - 'player_bar_background' => array( + 'player_bar_background' => array( 'type' => 'coloralpha', 'label' => __( 'Bar Player Background Color', 'radio-station' ), 'default' => 'rgba(0,0,0,255)', @@ -577,7 +582,7 @@ // --- [Pro/Player] Display Current Show --- // 2.4.0.3: added for current show display - 'player_bar_currentshow' => array( + 'player_bar_currentshow' => array( 'type' => 'checkbox', 'label' => __( 'Display Current Show', 'radio-station' ), 'value' => 'yes', @@ -590,7 +595,7 @@ // --- [Pro/Player] Display Metadata --- // 2.4.0.3: added for now playing metadata display - 'player_bar_nowplaying' => array( + 'player_bar_nowplaying' => array( 'type' => 'checkbox', 'label' => __( 'Display Now Playing', 'radio-station' ), 'value' => 'yes', @@ -603,7 +608,7 @@ // --- [Pro/Player] Metadata URL --- // 2.4.0.3: added for alternative stream metadata URL - 'player_bar_metadata' => array( + 'player_bar_metadata' => array( 'type' => 'text', 'options' => 'URL', 'label' => __( 'Metadata URL', 'radio-station' ), @@ -619,7 +624,7 @@ // === Master Schedule Page === // --- Schedule Page --- - 'schedule_page' => array( + 'schedule_page' => array( 'type' => 'select', 'options' => 'PAGEID', 'label' => __( 'Master Schedule Page', 'radio-station' ), @@ -641,7 +646,7 @@ ), // --- Default Schedule View --- - 'schedule_view' => array( + 'schedule_view' => array( 'type' => 'select', 'label' => __( 'Schedule View Default', 'radio-station' ), 'default' => 'table', @@ -658,7 +663,7 @@ ), // --- Schedule Clock Display --- - 'schedule_clock' => array( + 'schedule_clock' => array( 'type' => 'select', 'label' => __( 'Schedule Clock?', 'radio-station' ), 'default' => 'clock', @@ -673,7 +678,7 @@ ), // --- [Pro/Plus] Schedule Switcher --- - 'schedule_switcher' => array( + 'schedule_switcher' => array( 'type' => 'checkbox', 'label' => __( 'View Switching', 'radio-station' ), 'default' => '', @@ -686,7 +691,7 @@ // --- [Pro/Plus] Available Views --- // 2.3.2: added additional views option - 'schedule_views' => array( + 'schedule_views' => array( 'type' => 'multicheck', 'label' => __( 'Available Views', 'radio-station' ), // note: unstyled list view not included in defaults @@ -707,7 +712,7 @@ // --- [Pro/Plus] Time Spaced Grid View --- // 2.4.0.4: added grid view time spacing option - 'schedule_timegrid' => array( + 'schedule_timegrid' => array( 'type' => 'checkbox', 'label' => __( 'Time Spaced Grid', 'radio-station' ), 'default' => '', @@ -802,16 +807,16 @@ // --- [Pro] Show Episodes per Page --- 'show_episodes_per_page' => array( - 'type' => 'number', - 'label' => __( 'Episodes per Page', 'radio-station' ), - 'step' => 1, - 'min' => 1, - 'max' => 1000, - 'default' => 10, - 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - 'pro' => true, + 'type' => 'number', + 'label' => __( 'Episodes per Page', 'radio-station' ), + 'step' => 1, + 'min' => 1, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, ), // --- [Pro] Combined Team Tab --- @@ -905,7 +910,7 @@ // 2.4.0.6: added post type archives section // --- Shows Archive Page --- - 'show_archive_page' => array( + 'show_archive_page' => array( 'label' => __( 'Shows Archive Page', 'radio-station' ), 'type' => 'select', 'options' => 'PAGEID', @@ -938,7 +943,7 @@ // ), // --- Overrides Archive Page --- - 'override_archive_page' => array( + 'override_archive_page' => array( 'label' => __( 'Overrides Archive Page', 'radio-station' ), 'type' => 'select', 'options' => 'PAGEID', @@ -1024,7 +1029,7 @@ 'options' => array( '' => __( 'Off', 'radio-station' ), 'yes' => __( 'List', 'radio-station' ), - // 'grid' => __( 'Grid', 'radio-station' ), + // 'grid' => __( 'Grid', 'radio-station' ), ), 'value' => 'yes', 'default' => 'yes', @@ -1049,7 +1054,7 @@ ), // --- Automatic Display --- - 'genre_archive_auto' => array( + 'genre_archive_auto' => array( 'label' => __( 'Automatic Display', 'radio-station' ), 'type' => 'checkbox', 'value' => 'yes', @@ -1084,7 +1089,7 @@ // --- Automatic Display --- // 2.3.3.9: added language archive automatic page - 'language_archive_auto' => array( + 'language_archive_auto' => array( 'label' => __( 'Automatic Display', 'radio-station' ), 'type' => 'checkbox', 'value' => 'yes', @@ -1108,18 +1113,18 @@ // === Single Templates === // --- Templates Change Note --- - 'templates_change_note' => array( + 'templates_change_note' => array( 'type' => 'note', 'label' => __( 'Templates Change Note', 'radio-station' ), 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) - . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) - . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', + . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) + . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', 'tab' => 'pages', 'section' => 'single', ), // --- Show Template --- - 'show_template' => array( + 'show_template' => array( 'label' => __( 'Show Template', 'radio-station' ), 'type' => 'select', 'options' => array( @@ -1135,7 +1140,7 @@ ), // --- Combined Template Method --- - 'show_template_combined' => array( + 'show_template_combined' => array( 'label' => __( 'Combined Method', 'radio-station' ), 'type' => 'checkbox', 'value' => 'yes', @@ -1147,7 +1152,7 @@ // --- Playlist Template --- // 2.3.3.8: added missing singular.php option to match show_template - 'playlist_template' => array( + 'playlist_template' => array( 'label' => __( 'Playlist Template', 'radio-station' ), 'type' => 'select', 'options' => array( @@ -1228,18 +1233,18 @@ // --- Show Editing Permission Note --- // 2.4.0.3: added role to show assignment note - 'permissions_show_role_note' => array( + 'permissions_show_role_note' => array( 'type' => 'note', 'label' => __( 'Show Editing Permissions', 'radio-station' ), 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) - . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), + . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), 'tab' => 'roles', 'section' => 'permissions', ), // --- Playlist Editing Role Note --- // 2.4.0.3: added role to playlist assignment note - 'permissions_playlist_role_note' => array( + 'permissions_playlist_role_note' => array( 'type' => 'note', 'label' => __( 'Playlist Permissions', 'radio-station' ), 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), @@ -1248,12 +1253,12 @@ ), // --- Show Editor Role Note --- - 'show_editor_role_note' => array( + 'show_editor_role_note' => array( 'type' => 'note', 'label' => __( 'Show Editor Role', 'radio-station' ), 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) - . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) - . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), + . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) + . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), 'tab' => 'roles', 'section' => 'permissions', ), @@ -1304,7 +1309,7 @@ // 2.3.3.8: added player options tab // 2.3.3.8: move templates section onto pages tab // 2.4.0.6: added separate archives tab - 'tabs' => array( + 'tabs' => array( 'general' => __( 'General', 'radio-station' ), 'player' => __( 'Player', 'radio-station' ), 'pages' => __( 'Pages', 'radio-station' ), @@ -1319,7 +1324,7 @@ // 2.3.3.9: added profile pages section // 2.4.0.6: added performance section // 2.4.0.6: added posttypes and taxonomies archive sections - 'sections' => array( + 'sections' => array( 'broadcast' => __( 'Broadcast', 'radio-station' ), 'station' => __( 'Station', 'radio-station' ), 'feeds' => __( 'Feeds', 'radio-station' ), diff --git a/phpcs.xml b/phpcs.xml index 6d531aa..e34dad7 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,5 +1,5 @@ - + @@ -7,8 +7,6 @@ A Ruleset for checking Radio Station plugin. */phpunit.xml* - */languages/* - */templates/legacy/* */tests/* *\.(css|js) @@ -27,20 +25,25 @@ + + + + + + + - - @@ -66,6 +69,7 @@ + @@ -92,6 +96,6 @@ warning - + diff --git a/player/css/radio-player.css b/player/css/radio-player.css index a2f0c58..d3af137 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -38,6 +38,7 @@ .rp-interface { position: relative; background-color: transparent; + width: 36%; } .rp-station-info, .rp-interface, .rp-volume-controls, .rp-controls-holder , .rp-script-switcher, .rp-show-info { @@ -47,10 +48,13 @@ .rp-station-info, .rp-interface, .rp-show-info { text-align: center; vertical-align: top; - width: 33%; margin-top: 7px; } +.rp-station-info, .rp-show-info { + width: 32%; +} + .rp-station-text-alt, .rp-show-text-alt { display: none; } @@ -244,7 +248,7 @@ } .rp-mute, .rp-volume-max, .rp-volume-down, .rp-volume-up { - width: 18px; height: 18px; padding: 0; + width: 18px; height: 18px; padding: 0; margin: 0; } .light .rp-mute { diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 4ead44b..ac946bc 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -1,6 +1,6 @@ /* =============================== */ /* === Radio Player Javascript === */ -/* --------- Version 1.0.0 ------- */ +/* --------- Version 1.0.1 ------- */ /* =============================== */ /* === Cookie Value Function === */ @@ -20,7 +20,10 @@ radio_player_cookie = { while (c.charAt(0) == ' ') { c = c.substring(1, c.length); if (c.indexOf(nameeq) == 0) { - return JSON.parse(c.substring(nameeq.length, c.length)); + /* 1.0.1: fix for possible empty value */ + value = c.substring(nameeq.length, c.length).trim(); + if (value == '') {return null;} + return JSON.parse(value); } } } @@ -686,12 +689,17 @@ function radio_player_save_user_state() { /* --- volume change audio test --- */ /* ref: https://stackoverflow.com/a/62094756/5240159 */ -function radio_player_audio_test() { - testaudio = new Audio(); - try {testaudio.volume = 0.5;} catch(e) {return false;} - /* note: volume cannot be changed from 1 on iOS 12 and below */ - if (testaudio.volume === 1) {return false;} - return true; +function radio_player_volume_test() { + isIOS = ['iPad Simulator','iPhone Simulator','iPod Simulator','iPad','iPhone','iPod'].includes(navigator.platform); + if (radio.debug && isIOS) {console.log('iOS Mobile Device Detected. ');} + isAppleDevice = navigator.userAgent.includes('Macintosh'); + if (radio.debug && isAppleDevice) {console.log('Apple Device Detected.');} + isTouchScreen = navigator.maxTouchPoints >= 1; + if (radio.debug && isTouchScreen) {console.log('Touch Screen Detected. ');} + iosAudioFailure = false; testaudio = new Audio(); + try {testaudio.volume = 0.5;} catch(e) {if (radio.debug) {console.log('Caught Volume Change Error.');} iosAudioFailure = true;} + if (testaudio.volume === 1) {if (radio.debug) {console.log('Volume could not be changed.');} iosAudioFailure = true;} + return isIOS || (isAppleDevice && (isTouchScreen || iosAudioFailure)); } /* === Multi-Window/Tab Support === */ @@ -803,14 +811,14 @@ jQuery(document).ready(function() { jQuery(document).ready(function() { /* --- hide all volume controls if no support (iOS) --- */ - volumesupport = radio_player_audio_test(); - if (!volumesupport) {jQuery('.rp-volume-controls').hide();} + novolumesupport = radio_player_volume_test(); + if (novolumesupport) {jQuery('.rp-volume-controls').hide(); jQuery('.rp-play-pause-button-bg').css('margin-right','0');} /* --- bind pause/play button clicks --- */ jQuery('.rp-play-pause-button').on('click', function() { container = jQuery(this).parents('.radio-container'); instance = container.attr('id').replace('radio_container_',''); - radio_player.debug = true; // DEV TEMP + /* radio_player.debug = true; */ if (radio_player.debug) {console.log('Play/Pause Button Click:'); console.log(jQuery(this));} if (radio_player_is_playing(instance)) { if (radio_player.debug) {console.log('Trigger Pause of Player Instance '+instance);} diff --git a/player/radio-player.php b/player/radio-player.php index ac66562..28c0865 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -516,7 +516,7 @@ function radio_station_player_shortcode( $atts ) { // --- maybe debug shortcode attributes -- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { echo 'Passed Radio Player Shortcode Attributes: '; - echo print_r( $atts, true ) . ''; + echo esc_html( print_r( $atts, true ) ) . ''; } // --- set base defaults --- @@ -636,8 +636,7 @@ function radio_station_player_shortcode( $atts ) { // --- maybe debug shortcode attributes -- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo 'Combined Radio Player Shortcode Attributes: '; - echo print_r( $atts, true ) . ''; + echo 'Combined Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } // --- maybe get station title --- @@ -703,13 +702,12 @@ function radio_station_player_shortcode( $atts ) { // --- maybe open shortcode wrapper --- if ( !$atts['widget'] ) { - $player .= '
    ' . PHP_EOL; + $player .= '
    ' . "\n"; } // --- maybe debug shortcode attributes -- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo 'Parsed Radio Player Shortcode Attributes: '; - echo print_r( $atts, true ) . ''; + echo 'Parsed Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; } // --- get player HTML --- @@ -717,7 +715,7 @@ function radio_station_player_shortcode( $atts ) { // -- maybe close shortcode wrapper --- if ( !$atts['widget'] ) { - $player .= '
    ' . PHP_EOL; + $player .= '
    ' . "\n"; } } @@ -747,32 +745,30 @@ function radio_station_player_ajax() { // --- sanitize shortcode attributes --- $atts = radio_station_player_sanitize_shortcode_values(); if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { - echo ''; - echo 'Radio Player Shortcode Attributes: ' . print_r( $atts, true ); - echo ''; + echo 'Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; } // --- output head --- - echo ''; - wp_head(); - echo ''; + echo '' . "\n"; + wp_head() . "\n"; + echo '' . "\n" . '' . "\n"; // --- output widget contents --- - echo '
    '; + echo '
    ' . "\n"; echo radio_station_player_shortcode( $atts ); - echo '
    '; + echo '
    ' . "\n"; // --- output (hidden) footer for scripts --- - echo '
    '; + echo '
    ' . "\n"; wp_footer(); - echo '
    '; + echo '
    ' . "\n"; // --- maybe add background color --- if ( isset( $atts['background'] ) ) { - echo ''; + echo '' . "\n"; } - echo ''; + echo '' . "\n"; exit; } @@ -1097,7 +1093,7 @@ function radio_station_player_core_scripts() { $js = radio_station_player_get_settings(); if ( function_exists( 'wp_add_inline_script' ) ) { // --- add inline script --- - wp_add_inline_script( 'radio-player', $js ); + wp_add_inline_script( 'radio-player', $js, 'before' ); } else { // --- print settings directly --- echo ""; @@ -1140,6 +1136,13 @@ function radio_station_player_enqueue_script( $script ) { // $js = radio_station_player_script_mediaelements(); // } + // --- set specific script as enqueued --- + $radio_player['enqeued_' . $script] = true; + + if ( isset( $radio_player['enqueue_inline_scripts'] ) && $radio_player['enqueue_inline_scripts'] ) { + return; + } + // 2.4.0.3: load all player scripts regardless $js .= radio_station_player_script_howler(); $js .= radio_station_player_script_jplayer(); @@ -1149,7 +1152,7 @@ function radio_station_player_enqueue_script( $script ) { if ( function_exists( 'apply_filters') ) { $pageload = apply_filters( 'radio_station_player_pageload_script', '' ); if ( '' != $pageload ) { - $js .= "jQuery(document).ready(function() {" . PHP_EOL . $pageload . PHP_EOL . "});" . PHP_EOL; + $js .= "\n" . "jQuery(document).ready(function() {" . "\n" . $pageload . "\n" . "});" . "\n"; } } @@ -1162,7 +1165,7 @@ function radio_station_player_enqueue_script( $script ) { if ( '' != $js ) { if ( function_exists( 'wp_add_inline_script' ) ) { // --- add inline script --- - wp_add_inline_script( 'radio-player', $js ); + wp_add_inline_script( 'radio-player', $js, 'after' ); } else { // --- print script directly --- echo ""; @@ -1170,7 +1173,8 @@ function radio_station_player_enqueue_script( $script ) { } // --- set specific script as enqueued --- - $radio_player['enqeued_' . $script] = true; + $radio_player['enqeued_inline_scripts'] = true; + } // -------------------------------- @@ -1538,16 +1542,12 @@ function radio_station_player_get_settings() { if ( defined( 'RADIO_PLAYER_DEBUG' ) ) { $debug = RADIO_PLAYER_DEBUG; } - if ( $debug ) { - $debug = 'true'; - } else { - $debug = 'false'; - } + $debug = $debug ? 'true' : 'false'; // --- set radio player settings and radio data objects --- // (with empty arrays for instances, script types, failbacks, audio targets and stream data) - $js .= "var radio_player = {settings:player_settings, scripts:scripts, formats:formats, loading:false, debug:" . esc_js( $debug ) . "}" . PHP_EOL; - $js .= "var radio_data = {state:{}, players:[], scripts:[], failed:[], data:[], types:[], channels:[], faders:[]}" . PHP_EOL; + $js .= "var radio_player = {settings:player_settings, scripts:scripts, formats:formats, loading:false, debug:" . esc_js( $debug ) . "}" . "\n"; + $js .= "var radio_data = {state:{}, players:[], scripts:[], failed:[], data:[], types:[], channels:[], faders:[]}" . "\n"; // --- logged in / user state settings --- $loggedin = 'false'; @@ -1556,23 +1556,23 @@ function radio_station_player_get_settings() { $user_id = get_current_user_id(); $state = get_user_meta( $user_id, 'radio_player_state', true ); } - $js .= "radio_data.state.loggedin = " . esc_js( $loggedin ) . ";" . PHP_EOL; + $js .= "radio_data.state.loggedin = " . esc_js( $loggedin ) . ";" . "\n"; // ---- maybe set play state --- $playing = 'false'; if ( isset( $state['playing'] ) && $state['playing'] ) { $playing = 'true'; } - $js .= "radio_data.state.playing = " . esc_js( $playing ) . "; " . PHP_EOL; + $js .= "radio_data.state.playing = " . esc_js( $playing ) . "; " . "\n"; // --- maybe set station ID --- if ( isset( $state ) && isset( $state['station'] ) ) { $station = abs( intval( $state['station'] ) ); } if ( isset( $station ) && $station && ( $station > 0 ) ) { - $js .= "radio_data.state.station = " . esc_js( $station ) . ";" . PHP_EOL; + $js .= "radio_data.state.station = " . esc_js( $station ) . ";" . "\n"; } else { - $js .= "radio_data.state.station = 0;" . PHP_EOL; + $js .= "radio_data.state.station = 0;" . "\n"; } // --- maybe set user volume --- @@ -1580,14 +1580,14 @@ function radio_station_player_get_settings() { if ( isset( $state ) && isset( $state['volume'] ) ) { $player_volume = abs( intval( $state['volume'] ) ); } - $js .= "radio_data.state.volume = " . esc_js( $player_volume ) . "; " . PHP_EOL; + $js .= "radio_data.state.volume = " . esc_js( $player_volume ) . "; " . "\n"; // --- maybe set user mute --- $player_mute = 'false'; if ( isset( $state ) && isset( $state['mute'] ) && ( $state['mute'] ) ) { $player_mute = 'true'; } - $js .= "radio_data.state.mute = " . esc_js( $player_mute ) . "; " . PHP_EOL; + $js .= "radio_data.state.mute = " . esc_js( $player_mute ) . "; " . "\n"; // --- set main radio stream data --- $js .= "radio_data.state.data = {};" . PHP_EOL; @@ -1599,10 +1599,10 @@ function radio_station_player_get_settings() { } if ( $data && is_array( $data ) ) { foreach ( $data as $key => $value ) { - $js .= "radio_data.state.data['" . $key . "'] = '" . $value . "';" . PHP_EOL; + $js .= "radio_data.state.data['" . $key . "'] = '" . $value . "';" . "\n"; } } - $js .= "radio_player.stream_data = radio_data.state.data;" . PHP_EOL; + $js .= "radio_player.stream_data = radio_data.state.data;" . "\n"; // --- attempt to set player state from cookies --- $js .= "var radio_player_state_loaded = false; @@ -1612,7 +1612,7 @@ function radio_station_player_get_settings() { radio_player_custom_event('rp-state-loaded', false); clearInterval(radio_player_state_loader); } - }, 1000);" . PHP_EOL; + }, 1000);" . "\n"; /* --- periodic save to user meta --- */ $js .= "rp_save_interval = radio_player.settings.saveinterval * 1000; @@ -1621,7 +1621,7 @@ function radio_station_player_get_settings() { if (!radio_data.state.loggedin) {clearInterval(radio_player_state_saver); return;} radio_player_save_user_state(); } - }, rp_save_interval);" . PHP_EOL; + }, rp_save_interval);" . "\n"; return $js; } @@ -1633,7 +1633,7 @@ function radio_station_player_get_settings() { function radio_station_player_iframe() { // echo 'FRAME TEST'; if ( function_exists( 'is_user_logged_in') && is_user_logged_in() ) { - echo ""; + echo "" . "\n"; } } @@ -1649,7 +1649,7 @@ function radio_station_player_iframe() { function radio_station_player_state() { // --- reset saving state in parent frame --- - echo ""; + echo "" . "\n"; if ( !function_exists( 'get_current_user_id' ) || !function_exists( 'update_user_meta' ) ) { exit; @@ -1657,7 +1657,9 @@ function radio_station_player_state() { // --- get current user ID --- $user_id = get_current_user_id(); - if ( 0 == $user_id ) {exit;} + if ( 0 == $user_id ) { + exit; + } // --- get user state values --- $playing = $_REQUEST['playing']; @@ -1666,12 +1668,14 @@ function radio_station_player_state() { $mute = $_REQUEST['mute']; // --- sanitize user state values --- - if ( $playing ) {$playing = true;} else {$playing = false;} + $playing = $playing ? true : false; $volume = abs( intval( $volume ) ); if ( $volume < 1 ) {$volume = false;} elseif ( $volume > 100 ) {$volume = 100;} $station = abs( intval( $station ) ); - if ( $station < 1 ) {$station = false;} - if ( $mute ) {$mute = true;} else {$mute = false;} + if ( $station < 1 ) { + $station = false; + } + $mute = $mute ? true : false; // --- save player state to user meta --- $state = array( @@ -2270,7 +2274,7 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { // --- debug script / skin used --- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo 'Script: ' . $script . ' - Skin: ' . $skin . ''; + echo 'Script: ' . esc_html( $script ) . ' - Skin: ' . esc_html( $skin ) . '' . "\n"; } // --- enqueue base player styles --- @@ -2311,8 +2315,8 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { // --- debug skin path / URL --- if ( isset( $_REQUEST['player-debug'] ) ) { - echo 'Skin Path: ' . $path . ''; - echo 'Skin URL: ' . $url . ''; + echo 'Skin Path: ' . esc_html( $path ) . '' . "\n"; + echo 'Skin URL: ' . esc_html( $url ) . '' . "\n"; } return; } @@ -2345,8 +2349,8 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { // --- debug skin path / URL --- if ( isset( $_REQUEST['player-debug'] ) ) { - echo 'Skin Path: ' . $path . ''; - echo 'Skin URL: ' . $url . ''; + echo 'Skin Path: ' . esc_html( $path ) . '' . "\n"; + echo 'Skin URL: ' . esc_html( $url ) . '' . "\n"; } } @@ -2514,7 +2518,7 @@ function radio_station_player_control_styles( $instance ) { } @keyframes glowingplaying { from {background-color: " . $colors['playing'] . ";} to {background-color: " . $colors['playing'] . "C0;} -}" . PHP_EOL; +}" . "\n"; // --- Active Volume Buttons Color --- $css .= "/* Volume Buttons */ @@ -2523,7 +2527,7 @@ function radio_station_player_control_styles( $instance ) { " . $container . " .rp-volume-up:focus, " . $container . " .rp-volume-up:hover, " . $container . " .rp-volume-down:focus, " . $container . " .rp-volume-down:hover { background-color: " . $colors['buttons'] . "; -}" . PHP_EOL; +}" . "\n"; // --- Volume Range Input and Container --- // ref: http://danielstern.ca/range.css/#/ @@ -2534,7 +2538,7 @@ function radio_station_player_control_styles( $instance ) { $css .= "margin: 0; background-color: transparent; vertical-align: middle; -webkit-appearance: none; border: none;} " . $container . " .rp-volume-controls input[type=range]:focus {outline: none; box-shadow: none;} " . $container . " .rp-volume-controls input[type=range]::-moz-focus-inner, -" . $container . " .rp-volume-controls input[type=range]::-moz-focus-outer {outline: none; box-shadow: none;}" . PHP_EOL; +" . $container . " .rp-volume-controls input[type=range]::-moz-focus-outer {outline: none; box-shadow: none;}" . "\n"; // --- Range Track (synced Background Div) --- // 2.4.0.3: add position absolute/top on slider background (cross-browser display fix) @@ -2544,7 +2548,7 @@ function radio_station_player_control_styles( $instance ) { border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 3px; background: rgba(128, 128, 128, 0.5); } " . $container . ".playing .rp-volume-controls .rp-volume-slider-bg {background: " . $colors['track'] . ";} -" . $container . ".playing.muted .rp-volume-controls .rp-volume-slider-bg {background: rgba(128, 128, 128, 0.5);}" . PHP_EOL; +" . $container . ".playing.muted .rp-volume-controls .rp-volume-slider-bg {background: rgba(128, 128, 128, 0.5);}" . "\n"; // --- Slider Range Track (Clickable Transparent) --- $css .= "/* Range Track */ @@ -2574,12 +2578,12 @@ function radio_station_player_control_styles( $instance ) { " . $container .".playing .rp-volume-controls input[type=range]::-ms-thumb {background: " . $colors['thumb'] . "}; @supports (-ms-ime-align:auto) { " . $container . " .rp-volume-controls input[type=range] {margin: 0;} -}"; +}" . "\n"; // --- dummy element style for thumb width --- // note: since *actual* range input thumb width is hard/impossible to get with jQuery, // if changing the thumb width style, override this style also for volume background to match! - $css .= PHP_EOL . $container . " .rp-volume-thumb {display: none; width: 18px;}" . PHP_EOL; + $css .= $container . " .rp-volume-thumb {display: none; width: 18px;}" . "\n"; // --- get volume control display settings --- // 2.4.1.4: added volume control visibility options @@ -2593,16 +2597,16 @@ function radio_station_player_control_styles( $instance ) { $volumes = apply_filters( 'radio_station_player_volumes_display', $volumes ); } if ( !in_array( 'slider', $volumes ) ) { - $css .= PHP_EOL . $container . " .rp-volume-slider-container {display: none;}" . PHP_EOL; + $css .= "\n" . $container . " .rp-volume-slider-container {display: none;}" . "\n"; } if ( !in_array( 'updown', $volumes ) ) { - $css .= PHP_EOL . $container . " .rp-volume-up, " . $container . " .rp-volume-down {display: none;}" . PHP_EOL; + $css .= "\n" . $container . " .rp-volume-up, " . $container . " .rp-volume-down {display: none;}" . "\n"; } if ( !in_array( 'mute', $volumes ) ) { - $css .= PHP_EOL . $container . " .rp-mute {display: none;}" . PHP_EOL; + $css .= "\n" . $container . " .rp-mute {display: none;}" . "\n"; } if ( !in_array( 'max', $volumes ) ) { - $css .= PHP_EOL . $container . " .rp-volume-max {display: none;}" . PHP_EOL; + $css .= "\n" . $container . " .rp-volume-max {display: none;}" . "\n"; } // --- filter and return --- @@ -2658,8 +2662,12 @@ function radio_station_player_validate_boolean( $var ) { return $var; } - if ( is_string( $var ) && 'false' === strtolower( $var ) ) { - return false; + if ( is_string( $var ) ) { + if ( 'false' === strtolower( $var ) ) { + return false; + } elseif ( 'true' === strtolower( $var ) ) { + return true; + } } return (bool) $var; @@ -2691,3 +2699,4 @@ function esc_url( $url ) { return $url; } } + diff --git a/radio-station-admin.php b/radio-station-admin.php index ad7470a..181ffc8 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -13,10 +13,13 @@ // - Add Admin Menu and Submenu Items // - Fix to Expand Main Menu for Submenu Items // - Taxonomy Submenu Item Fix +// - Fix to Redirect Plugin Settings Menu Link // - Display Import/Export Show Page // - Display Plugin Help Page // - Parse Markdown Doc File // x Display Playlist Export Page +// - Display Plugin Docs Page +// - // === Update Notices === // - Get Plugin Upgrade Notice // - Parse Plugin Update Notice @@ -84,7 +87,7 @@ function radio_station_admin_styles() { global $post; if ( is_object( $post ) && property_exists( $post, 'post_type' ) ) { if ( ( RADIO_STATION_PLAYLIST_SLUG === $post->post_type ) - || ( RADIO_STATION_OVERRIDE_SLUG === $post->post_type ) ) { + || ( RADIO_STATION_OVERRIDE_SLUG === $post->post_type ) ) { echo '.wp-editor-container textarea.wp-editor-area {height: 100px;}' . "\n"; } } @@ -98,13 +101,15 @@ function radio_station_admin_styles() { // ------------------------------ // 2.4.0.3: filter license activation link for free version on plugin page // note: this is because Pro is a separate plugin not a replacement one + // add_filter( 'plugin_action_links', 'radio_station_plugin_links', 99, 2 ); -function radio_station_plugin_links( $links, $file ) { +/* function radio_station_plugin_links( $links, $file ) { if ( RADIO_STATION_BASENAME == $file ) { - echo 'RS Plugin Links: ' . print_r( $links, true ) . ''; + echo 'RS Plugin Links: ' . esc_html( print_r( $links, true ) ) . ''; } return $links; -} +} */ + add_filter( 'plugin_action_links_' . RADIO_STATION_BASENAME, 'radio_station_plugin_page_links', 20, 2 ); // add_filter( 'network_admin_plugin_action_links_' . RADIO_STATION_BASENAME, , 'radio_station_license_activation_link', 20, 2 ); function radio_station_plugin_page_links( $links, $file ) { @@ -112,7 +117,7 @@ function radio_station_plugin_page_links( $links, $file ) { global $radio_station_data; if ( RADIO_STATION_DEBUG ) { - echo 'RS Plugin Links A: ' . print_r( $links, true ) . '' . PHP_EOL; + echo 'RS Plugin Links A: ' . esc_html( print_r( $links, true ) ) . '' . PHP_EOL; } foreach ( $links as $key => $link ) { @@ -120,7 +125,7 @@ function radio_station_plugin_page_links( $links, $file ) { // if ( RADIO_STATION_DEBUG ) { // echo 'Plugin Link ' . $key . ': ' . $link . '' . PHP_EOL; // } - + // 2.4.0.6: remove addons link from Free by default if ( strstr( $link, '-addons' ) ) { if ( !$radio_station_data['settings']['hasaddons'] ) { @@ -143,7 +148,7 @@ function radio_station_plugin_page_links( $links, $file ) { } if ( RADIO_STATION_DEBUG ) { - echo 'RS Plugin Links B: ' . print_r( $links, true ) . '' . PHP_EOL; + echo 'RS Plugin Links B: ' . esc_html( print_r( $links, true ) ) . '' . PHP_EOL; } return $links; @@ -277,7 +282,7 @@ function radio_station_fix_genre_parent( $parent_file = '' ) { global $pagenow, $post; $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); $taxonomies = array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ); - if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && in_array( $_GET['taxonomy'], $taxonomies ) ) { + if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && in_array( sanitize_text_field( $_GET['taxonomy'] ), $taxonomies ) ) { // 2.3.0: also apply to language taxonomy $parent_file = 'radio-station'; } elseif ( ( 'post.php' === $pagenow ) && in_array( $post->post_type, $post_types ) ) { @@ -296,12 +301,12 @@ function radio_station_fix_genre_parent( $parent_file = '' ) { function radio_station_taxonomy_submenu_fix() { global $pagenow; - if ( ( 'edit-tags.php' == $pagenow ) && isset( $_GET['taxonomy'] ) ) { + if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) ) { $js = ''; - if ( RADIO_STATION_GENRES_SLUG == $_GET['taxonomy'] ) { + if ( RADIO_STATION_GENRES_SLUG == sanitize_text_field( $_GET['taxonomy'] ) ) { $js = "jQuery('#toplevel_page_radio-station ul li').each(function() { - if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . RADIO_STATION_GENRES_SLUG . "') { + if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . esc_js( RADIO_STATION_GENRES_SLUG ) . "') { jQuery(this).addClass('current').find('a').addClass('current').attr('aria-current', 'page'); } });"; @@ -310,7 +315,7 @@ function radio_station_taxonomy_submenu_fix() { // 2.3.0: add fix for language taxonomy also if ( RADIO_STATION_LANGUAGES_SLUG == $_GET['taxonomy'] ) { $js = "jQuery('#toplevel_page_radio-station ul li').each(function() { - if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . RADIO_STATION_LANGUAGES_SLUG . "') { + if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "') { jQuery(this).addClass('current').find('a').addClass('current').attr('aria-current', 'page'); } });"; @@ -324,6 +329,29 @@ function radio_station_taxonomy_submenu_fix() { } } +// ----------------------------------------- +// Fix to Redirect Plugin Settings Menu Link +// ----------------------------------------- +// 2.3.2: added settings submenu page redirection fix +// 2.5.0: moved to admin file and changed to admin_init hook +add_action( 'admin_init', 'radio_station_settings_page_redirect' ); +function radio_station_settings_page_redirect() { + + // --- bug out if not plugin settings page --- + if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != sanitize_text_field( $_REQUEST['page'] ) ) ) { + return; + } + + // --- check if link is for options-general.php --- + if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { + + // --- redirect to plugin settings page (admin.php) --- + $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + wp_redirect( $url ); + exit; + } +} + // -------------------- // Role Assignment Info // -------------------- @@ -345,13 +373,23 @@ function radio_station_role_editor() { // 2.4.0.3: change text to reflect inclusion in Pro echo esc_html( __( 'Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user.', 'radio-station' ) ) . '
    '; - // --- Pro upgrade link --- // TODO: maybe add picture of role editing interface ? + + // --- Pro upgrade link --- + // 2.5.0: add direct upgrade link + $pricing_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); + echo ''; + echo esc_html( __( 'Upgrade Now', 'radio-station' ) ); + echo ''; + echo '  |  '; + + // --- Pro details/pricing link --- $upgrade_url = radio_station_get_upgrade_url(); echo ''; // echo esc_html( __( 'Upgrade to Radio Station Pro', 'radio-station' ) ); - echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); + echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); echo ' →.'; + echo '

    '; } // ------------------------------- @@ -394,12 +432,13 @@ function radio_station_plugin_docs_page() { if ( !in_array( $doc, array( '.', '..' ) ) ) { $id = str_replace( '.md', '', $doc ); echo '' . PHP_EOL; } } @@ -434,8 +473,7 @@ function radio_station_plugin_docs_page() { '; // --- output announcement content --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo radio_station_announcement_content( false ); + radio_station_announcement_content( false ); } @@ -482,7 +520,7 @@ function radio_station_parse_doc( $id ) { $after = substr( $formatted, $pos2, strlen( $formatted ) ); $pos3 = stripos( $after, $tag_end ); $anchor = sanitize_title( substr( $after, 0, $pos3 ) ); - $alink = ''; + $alink = ''; $newheading = $alink . ''; $formatted = $before . $newheading . $after; } @@ -521,6 +559,7 @@ function radio_station_parse_doc( $id ) { // ---------------------------- // Display Playlist Export Page // ---------------------------- +// note: this is a legacy export playlist function // TODO: rewrite playlist export function ? function radio_station_playlist_export_page() { @@ -558,9 +597,9 @@ function radio_station_playlist_export_page() { TO_DAYS(posts.post_date) >= TO_DAYS(%s) AND TO_DAYS(posts.post_date) <= TO_DAYS(%s) ORDER BY posts.post_date ASC"; + // prepare query before executing - $query = $wpdb->prepare( $sql, array( $start, $end ) ); - $playlists = $wpdb->get_results( $query ); + $playlists = $wpdb->get_results( $wpdb->prepare( $sql, array( $start, $end ) ) ); if ( ! $playlists ) { $list = 'No playlists found for this period.'; @@ -608,6 +647,7 @@ function radio_station_playlist_export_page() { wp_mkdir_p( $dir ); } + // TODO: use WP Filesystem for writing $f = fopen( $dir . $file, 'w' ); fwrite( $f, $output ); fclose( $f ); @@ -773,7 +813,7 @@ function radio_station_plugin_update_message( $plugin_data, $response ) { add_action( 'admin_notices', 'radio_station_admin_update_notice' ); function radio_station_admin_update_notice() { // --- do not display on settings page --- - if ( isset( $_GET['page'] ) && ( 'radio-station' == $_GET['page'] ) ) { + if ( isset( $_GET['page'] ) && ( 'radio-station' === sanitize_text_field( $_GET['page'] ) ) ) { return; } radio_station_update_notice(); @@ -798,8 +838,8 @@ function radio_station_update_notice() { } // --- ignore if updating now --- - if ( isset( $_GET['action'] ) && ( 'upgrade-plugin' == $_GET['action'] ) - && isset( $_GET['plugin'] ) && ( $notice['plugin_file'] == $_GET['plugin'] ) ) { + if ( isset( $_GET['action'] ) && ( 'upgrade-plugin' === sanitize_text_field( $_GET['action'] ) ) + && isset( $_GET['plugin'] ) && ( $notice['plugin_file'] === sanitize_text_field( $_GET['plugin'] ) ) ) { return; } @@ -814,50 +854,50 @@ function radio_station_update_notice() { $update_url = wp_nonce_url( $update_url, 'upgrade-plugin_' . $notice['plugin_file'] ); // --- output update available notice --- - echo '
    '; + echo '
    ' . PHP_EOL; - echo '
      '; + echo '
        ' . PHP_EOL; if ( isset( $notice['icon_url'] ) ) { - echo '
      • '; - echo ''; - echo '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
      • ' . PHP_EOL; } - echo '
      • '; - echo esc_html( __( 'A new version of', 'radio-station' ) ) . '
        '; - echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        '; - echo esc_html( __( 'is available.', 'radio-station' ) ); - echo '
      • '; + echo '
      • ' . PHP_EOL; + echo esc_html( __( 'A new version of', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'is available.', 'radio-station' ) ) . PHP_EOL; + echo '
      • ' . PHP_EOL; - echo '
      • '; - echo '' . esc_html( __( 'Take a moment to Update for a better experience. In this update', 'radio-station' ) ) . ":
        "; - echo '
          '; + echo '
        • ' . PHP_EOL; + echo '' . esc_html( __( 'Take a moment to Update for a better experience. In this update', 'radio-station' ) ) . ":
          " . PHP_EOL; + echo '
            ' . PHP_EOL; foreach ( $notice['lines'] as $line ) { - echo '
          • ' . esc_html( $line ) . '
          • '; + echo '
          • ' . esc_html( $line ) . '
          • ' . PHP_EOL; } - echo '
          '; - echo '
        • '; + echo '
        ' . PHP_EOL; + echo '
      • ' . PHP_EOL; - echo '
      • '; - echo '' . esc_html( __( 'Update Now', 'radio-station' ) ) . ''; + echo '
      • ' . PHP_EOL; + echo '' . esc_html( __( 'Update Now', 'radio-station' ) ) . '' . PHP_EOL; if ( '' != $notice['url'] ) { - echo '

        '; - echo '' . esc_html( __( 'Full Update Details', 'radio-station' ) ) . ' →'; + echo '

        ' . PHP_EOL; + echo '' . esc_html( __( 'Full Update Details', 'radio-station' ) ) . ' →' . PHP_EOL; } - echo '
      • '; + echo '' . PHP_EOL; - echo '
      '; + echo '
    ' . PHP_EOL; // --- dismiss notice link --- $dismiss_url = add_query_arg( 'action', 'radio_station_notice_dismiss', admin_url( 'admin-ajax.php' ) ); $dismiss_url = add_query_arg( 'upgrade', $notice['update_id'], $dismiss_url ); - echo '
    '; - echo ''; - echo ''; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; // --- notice dismissal iframe (once only) --- radio_station_admin_notice_iframe(); @@ -873,7 +913,7 @@ function radio_station_notice() { // --- get latest notice --- $notices = radio_station_get_notices(); if ( RADIO_STATION_DEBUG ) { - echo "Notices: " . print_r( $notices, true ) . ""; + echo 'Notices: ' . esc_html( print_r( $notices, true ) ) . '' . PHP_EOL; } if ( count( $notices ) < 1 ) { return; @@ -885,69 +925,73 @@ function radio_station_notice() { // --- bug out if already read --- $read = get_option( 'radio_station_read_notices' ); if ( RADIO_STATION_DEBUG ) { - echo "Read Notices: " . print_r( $read, true ) . ""; - echo "Latest Notices: " . print_r( $notice, true ) . ""; + echo 'Read Notices: ' . esc_html( print_r( $read, true ) ) . '' . PHP_EOL; + echo 'Latest Notices: ' . esc_html( print_r( $notice, true ) ) . '' . PHP_EOL; } if ( $read && isset( $read[$notice_id] ) && ( '1' == $read[$notice_id] ) ) { return; } // --- display plugin notice --- - echo '
    '; + echo '
    ' . PHP_EOL; // --- output plugin notice text --- - echo '
      '; + echo '
        ' . PHP_EOL; // --- plugin icon --- $icon_url = plugins_url( 'images/radio-station.png', RADIO_STATION_FILE ); - echo '
      • '; - echo ''; - echo '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
      • ' . PHP_EOL; // --- notice title --- - echo '
      • '; - echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        '; - echo '' . esc_html( __( 'Update Notice', 'radio-station' ) ) . ''; - echo '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo '' . esc_html( __( 'Update Notice', 'radio-station' ) ) . '' . PHP_EOL; + echo '
      • ' . PHP_EOL; // --- notice details --- - echo '
      • '; - echo '
        '; - echo '' . esc_html( __( 'Thanks for Updating! You can enjoy these improvements now', 'radio-station' ) ) . ':'; - echo '
        '; - echo '
          '; + echo '
        • ' . PHP_EOL; + echo '
          ' . PHP_EOL; + echo '' . esc_html( __( 'Thanks for Updating! You can enjoy these improvements now', 'radio-station' ) ) . ':' . PHP_EOL; + echo '
          ' . PHP_EOL; + echo '
            ' . PHP_EOL; foreach ( $notice['lines'] as $line ) { - echo '
          • ' . $line . '
          • '; + // 2.5.0: added wp_kses to line output + $allowed = radio_station_allowed_html( 'content', 'notice' ); + echo '
          • ' . wp_kses( $line, $allowed ) . '
          • ' . PHP_EOL; } - echo '
          '; - echo '
        • '; + echo '
        ' . PHP_EOL; + echo '
      • ' . PHP_EOL; - echo '
      • '; + echo '
      • ' . PHP_EOL; // --- link to update blog post --- if ( isset( $notice['url'] ) && ( '' != $notice['url'] ) ) { - echo '' . esc_html( __( 'Full Update Details', 'radio-station' ) ) . ' →'; - echo '

        '; + echo '' . esc_html( __( 'Full Update Details', 'radio-station' ) ) . ' →' . PHP_EOL; + echo '

        ' . PHP_EOL; } // --- link to settings page --- - if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != $_REQUEST['page'] ) ) { + if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' !== sanitize_text_field( $_REQUEST['page'] ) ) ) { $settings_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - echo '' . esc_html( __( 'Plugin Settings', 'radio-station' ) ) . ''; + echo '' . esc_html( __( 'Plugin Settings', 'radio-station' ) ) . '' . PHP_EOL; } - echo '
      • '; + echo '' . PHP_EOL; - echo '
      '; + echo '
    ' . PHP_EOL; // --- notice dismissal button --- $dismiss_url = add_query_arg( 'action', 'radio_station_notice_dismiss', admin_url( 'admin-ajax.php' ) ); $dismiss_url = add_query_arg( 'notice', $notice['id'], $dismiss_url ); - echo '
    '; - echo ''; - echo ''; - echo '
    '; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; + + // --- close notice wrap --- + echo '
    ' . PHP_EOL; // --- notice dismissal iframe (once only) --- radio_station_admin_notice_iframe(); @@ -1010,6 +1054,7 @@ function radio_station_get_notices() { function radio_station_notice_dismiss() { if ( current_user_can( 'manage_options' ) || current_user_can( 'update_plugins' ) ) { if ( isset( $_GET['notice'] ) ) { + $notice = absint( $_GET['notice'] ); if ( $notice < 0 ) { return; @@ -1020,8 +1065,10 @@ function radio_station_notice_dismiss() { } $notices[$notice] = '1'; update_option( 'radio_station_read_notices', $notices ); - echo ""; + echo "" . PHP_EOL; + } elseif ( isset( $_GET['upgrade'] ) ) { + $upgrade = absint( $_GET['upgrade'] ); if ( $upgrade < 0 ) { return; @@ -1032,7 +1079,8 @@ function radio_station_notice_dismiss() { } $upgrades[$upgrade] = '1'; update_option( 'radio_station_read_upgrades', $upgrades ); - echo ""; + echo "" . PHP_EOL; + } } exit; @@ -1050,7 +1098,7 @@ function radio_station_notice_dismiss() { function radio_station_admin_notice_iframe() { global $radio_station_notice_iframe; if ( !isset( $radio_station_notice_iframe ) || !$radio_station_notice_iframe ) { - echo ''; + echo '' . PHP_EOL; $radio_station_notice_iframe = true; } } @@ -1070,7 +1118,7 @@ function radio_station_settings_page_top() { $offer_end = strtotime( '2020-08-01 00:01' ); if ( $now < $offer_end ) { if ( !get_option( 'radio_station_listing_offer_accepted' ) ) { - echo radio_station_listing_offer_content( false ); + radio_station_listing_offer_content( false ); } } */ @@ -1079,22 +1127,25 @@ function radio_station_settings_page_top() { $offer_start = strtotime( '2021-07-20 00:01' ); $offer_end = strtotime( '2021-07-26 00:01' ); if ( $now < $offer_end ) { - $user_ids = get_current_user_id(); + $user_id = get_current_user_id(); $user_ids = get_option( 'radio_station_launch_offer_accepted' ); if ( !$user_ids || !is_array( $user_ids ) || !in_array( $user_id, $user_ids ) ) { $prelaunch = ( $now < $offer_start ) ? true : false; - if ( isset( $_GET['offertest'] ) && ( '1' == $_GET['offertest'] ) ) { - $prelaunch = false; - } elseif ( isset( $_GET['offertest'] ) && ( '2' == $_GET['offertest'] ) ) { - $prelaunch = true; + if ( isset( $_GET['offertest'] ) ) { + $offertest = sanitize_title( $_GET['offertest'] ); + if ( '1' == $offertest ) { + $prelaunch = false; + } elseif ( '2' == $offertest ) { + $prelaunch = true; + } } - echo radio_station_launch_offer_content( false, $prelaunch ); + radio_station_launch_offer_content( false, $prelaunch ); } } // --- plugin update notice --- radio_station_update_notice(); - echo '
    '; + echo '
    ' . PHP_EOL; } // --------------------------- @@ -1104,8 +1155,7 @@ function radio_station_settings_page_top() { function radio_station_settings_page_bottom() { // 2.3.1: move mailchimp form for listing offer radio_station_mailchimp_form(); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo radio_station_announcement_content( false ); + radio_station_announcement_content( false ); } // ------------------------------ @@ -1118,7 +1168,7 @@ function radio_station_listing_offer_notice() { // --- bug out on certain plugin pages --- $pages = array( 'radio-station', 'radio-station-docs' ); - if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages ) ) { + if ( isset( $_REQUEST['page'] ) && in_array( sanitize_text_field( $_REQUEST['page'], $pages ) ) ) { return; } @@ -1135,10 +1185,9 @@ function radio_station_listing_offer_notice() { } // --- display plugin announcement --- - echo '
    '; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo radio_station_listing_offer_content(); - echo '
    '; + echo '
    ' . PHP_EOL; + radio_station_listing_offer_content(); + echo '
    ' . PHP_EOL; // --- notice dismissal frame (once) --- radio_station_admin_notice_iframe(); @@ -1152,7 +1201,7 @@ function radio_station_launch_offer_notice( $rspage = false ) { // --- bug out on certain plugin pages --- $pages = array( 'radio-station', 'radio-station-docs' ); - if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages ) ) { + if ( isset( $_REQUEST['page'] ) && in_array( sanitize_text_field( $_REQUEST['page'] ), $pages ) ) { return; } @@ -1177,11 +1226,10 @@ function radio_station_launch_offer_notice( $rspage = false ) { } // --- display plugin announcement --- - echo '
    '; - // phpcs:ignore WordPress.Security.OutputNotEscaped + echo '
    ' . PHP_EOL; $prelaunch = ( $now < $offer_start ) ? true : false; - echo radio_station_launch_offer_content( true, $prelaunch ); - echo '
    '; + radio_station_launch_offer_content( true, $prelaunch ); + echo '
    ' . PHP_EOL; // --- notice dismissal frame (once) --- radio_station_admin_notice_iframe(); @@ -1195,71 +1243,70 @@ function radio_station_listing_offer_content( $dismissable = true ) { $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_listing_offer_dismiss' ); $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); - $blurb = '
      '; + echo '
        ' . PHP_EOL; // --- directory logo image --- $logo_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); - $blurb .= '
      • '; - $blurb .= ''; - $blurb .= '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
      • ' . PHP_EOL; // --- free listing offer text --- - $blurb .= '
      • '; - $blurb .= '
        ' . esc_html( __( 'Time Sensitive Free Offer', 'radio-station' ) ) . '
        '; + echo '
      • ' . PHP_EOL; + echo '
        ' . esc_html( __( 'Time Sensitive Free Offer', 'radio-station' ) ) . '
        ' . PHP_EOL; - $blurb .= '

        '; - $blurb .= esc_html( __( 'We are excited to announce the opening of the new', 'radio-station' ) ); - $blurb .= ' Netmix Station Directory!!!
        '; - $blurb .= esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...', 'radio-station' ) ) . '
        '; + echo '

        ' . PHP_EOL; + echo esc_html( __( 'We are excited to announce the opening of the new', 'radio-station' ) ) . PHP_EOL; + echo ' Netmix Station Directory!!!
        ' . PHP_EOL; + echo esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...', 'radio-station' ) ) . '
        ' . PHP_EOL; - $blurb .= esc_html( __( 'Because while launching,') ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?', 'radio-station' ) ); - $blurb .= '

      • '; + echo esc_html( __( 'Because while launching,' ) ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?', 'radio-station' ) ) . PHP_EOL; + echo '

        '; // --- accept / decline offer button links --- - $blurb .= '
      • '; - $blurb .= '
        '; - $onclick = ''; + echo '
      • ' . PHP_EOL; + echo '
        ' . PHP_EOL; + echo ''; - $blurb .= '
        '; + echo '>' . esc_html( __( 'Yes please!', 'radio-station' ) ) . '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
        ' . PHP_EOL; + echo '' . esc_html( __( 'More Details', 'radio-station' ) ) . '' . PHP_EOL; + echo '

        ' . PHP_EOL; - $blurb .= ''; - $blurb .= '

        '; + echo '' . PHP_EOL; + echo '

      • ' . PHP_EOL; - $blurb .= '
        '; - $blurb .= esc_html( __( 'Offer valid until end of July 2020.', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'Activate your 30 days before it ends!', 'radio-station' ) ); - $blurb .= '
        '; + echo '
        ' . PHP_EOL; + echo esc_html( __( 'Offer valid until end of July 2020.', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'Activate your 30 days before it ends!', 'radio-station' ) ) . PHP_EOL; + echo '
        ' . PHP_EOL; - $blurb .= '
      • '; + echo '' . PHP_EOL; - $blurb .= '
      '; + echo '
    ' . PHP_EOL; // --- dismiss notice icon --- if ( $dismissable ) { - $blurb .= '
    '; - $blurb .= ''; - $blurb .= ''; - $blurb .= ''; - $blurb .= '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; } // --- display dismiss link script --- - $blurb .= ""; + }" . PHP_EOL; - return $blurb; } // -------------------- @@ -1270,80 +1317,83 @@ function radio_station_launch_offer_content( $dismissable = true, $prelaunch ) { $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_launch_offer_dismiss' ); $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); - $blurb = '
      '; + echo '
        ' . PHP_EOL; // --- directory logo image --- // $launch_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); $launch_image = plugins_url( 'images/pro-launch.gif', RADIO_STATION_FILE ); - $blurb .= '
      • '; - $blurb .= ''; - $blurb .= '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
      • ' . PHP_EOL; // --- free listing offer text --- - $blurb .= '
      • '; + echo '
      • ' . PHP_EOL; if ( $prelaunch ) { - $blurb .= '
        ' . esc_html( __( 'Radio Station Pro Launch Discount!', 'radio-station' ) ) . '
        '; - $blurb .= '

        '; - $blurb .= esc_html( __( 'We are thrilled to announce the upcoming launch of Radio Station PRO', 'radio-station' ) ) . ' !!!
        '; - $blurb .= esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'During the launch,') ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'Sign up to the exclusive launch list to receive your discount code when we go LIVE.', 'radio-station' ) ); - $blurb .= '

        '; + echo '
        ' . esc_html( __( 'Radio Station Pro Launch Discount!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo '

        ' . PHP_EOL; + echo esc_html( __( 'We are thrilled to announce the upcoming launch of Radio Station PRO', 'radio-station' ) ) . ' !!!
        ' . PHP_EOL; + echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'During the launch,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'Sign up to the exclusive launch list to receive your discount code when we go LIVE.', 'radio-station' ) ) . PHP_EOL; + echo '

        ' . PHP_EOL; } else { - $blurb .= '
        ' . esc_html( __( 'Radio Station PRO Launch is LIVE!', 'radio-station' ) ) . '
        '; - $blurb .= '

        '; - $blurb .= esc_html( __( 'The long anticipated moment has arrived. The doors are open to get PRO', 'radio-station' ) ) . ' !!!
        '; - $blurb .= esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'Remember,') ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
        '; - $blurb .= ''; - $blurb .= esc_html( __( 'Sign up here to receive your exclusive launch discount code.', 'radio-station' ) ); - $blurb .= '

        '; + echo '
        ' . esc_html( __( 'Radio Station PRO Launch is LIVE!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo '

        ' . PHP_EOL; + echo esc_html( __( 'The long anticipated moment has arrived. The doors are open to get PRO', 'radio-station' ) ) . ' !!!
        ' . PHP_EOL; + echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'Remember,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo '' . PHP_EOL; + echo esc_html( __( 'Sign up here to receive your exclusive launch discount code.', 'radio-station' ) ) . PHP_EOL; + echo '

        ' . PHP_EOL; } - $blurb .= '
      • '; + echo '' . PHP_EOL; // --- accept / decline offer button links --- - $blurb .= '
      • '; - $blurb .= '
        '; - $onclick = ''; - if ( $dismissable ) { - $onclick = ' onclick="radio_display_dismiss_link();"'; - } - $blurb .= '
        '; + echo '
      • ' . PHP_EOL; + echo '
        ' . PHP_EOL; + echo ''; + echo '
      • ' . PHP_EOL; - $blurb .= ''; - $blurb .= '

        '; + echo '' . PHP_EOL; + echo '
        ' . PHP_EOL; - $blurb .= '
      • '; + echo '' . PHP_EOL; - $blurb .= '
      '; + echo '
    ' . PHP_EOL; // --- dismiss notice icon --- if ( $dismissable ) { - $blurb .= '
    '; - $blurb .= ''; - $blurb .= ''; - $blurb .= ''; - $blurb .= '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; } // --- display dismiss link script --- - $blurb .= ""; + }" . PHP_EOL; - return $blurb; } // -------------------------- @@ -1365,7 +1415,7 @@ function radio_station_listing_offer_dismiss() { } // --- hide the announcement in parent frame --- - echo ""; + echo "" . PHP_EOL; exit; } @@ -1405,7 +1455,7 @@ function radio_station_launch_offer_dismiss() { } // --- hide the announcement in parent frame --- - echo ""; + echo "" . PHP_EOL; exit; } @@ -1424,14 +1474,13 @@ function radio_station_announcement_notice() { // --- bug out on certain plugin pages --- // 2.2.8: remove strict in_array checking $pages = array( 'radio-station', 'radio-station-docs' ); - if ( isset( $_REQUEST['page'] ) && in_array( $_REQUEST['page'], $pages ) ) { + if ( isset( $_REQUEST['page'] ) && in_array( sanitize_text_field( $_REQUEST['page'] ), $pages ) ) { return; } // --- display plugin announcement --- echo '
    '; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo radio_station_announcement_content(); + radio_station_announcement_content(); echo '
    '; // --- notice dismissal frame (once) --- @@ -1445,53 +1494,57 @@ function radio_station_announcement_notice() { // 2.2.2: added simple patreon supporter blurb function radio_station_announcement_content( $dismissable = true ) { - $blurb = '
      '; + echo '
        ' . PHP_EOL; // --- plugin image --- $plugin_image = plugins_url( 'images/radio-station.png', RADIO_STATION_FILE ); - $blurb .= '
      • '; - $blurb .= ''; - $blurb .= '
      • '; + echo '
      • '; + echo ''; + echo '
      • '; // --- takeover announcement --- - $blurb .= '
      • '; - $blurb .= '' . esc_html( __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) ) . ',
        '; - $blurb .= esc_html( __( 'since June 2019', 'radio-station' ) ) . ', '; - $blurb .= '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . ' '; - $blurb .= esc_html( __( ' plugin development has been actively taken over by', 'radio-station' ) ); - $blurb .= ' Netmix.
        '; - // 2.3.3.9: add updated text after 2000 user milestone - $blurb .= esc_html( __( 'And due to our continued efforts we now have a community of over two thousand active stations!', 'radio-station' ) ) . '
        '; - $blurb .= esc_html( __( 'We invite you to', 'radio-station' ) ); - $blurb .= ' '; - $blurb .= esc_html( __( 'Become a Radio Station Patreon Supporter', 'radio-station' ) ); - $blurb .= ' ' . esc_html( __( 'to make it better for everyone', 'radio-station' ) ) . '!'; - $blurb .= '
      • '; - $blurb .= '
      • '; - $blurb .= radio_station_patreon_button( 'radiostation' ); + echo '
      • ' . PHP_EOL; + echo '' . esc_html( __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) ) . ',
        ' . PHP_EOL; + echo esc_html( __( 'since June 2019', 'radio-station' ) ) . ', ' . PHP_EOL; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . ' ' . PHP_EOL; + echo esc_html( __( ' plugin development has been actively taken over by', 'radio-station' ) ) . PHP_EOL; + echo ' Netmix.
        ' . PHP_EOL; + // 2.3.3.9: add updated text after 2000 user mileston + echo esc_html( __( 'And due to our continued efforts we now have a community of over two thousand active stations!', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'We invite you to', 'radio-station' ) ) . PHP_EOL; + echo ' ' . PHP_EOL; + echo esc_html( __( 'Become a Radio Station Patreon Supporter', 'radio-station' ) ) . PHP_EOL; + echo ' ' . esc_html( __( 'to make it better for everyone', 'radio-station' ) ) . '!' . PHP_EOL; + echo '
      • ' . PHP_EOL; + echo '
      • ' . PHP_EOL; + + $button = radio_station_patreon_button( 'radiostation' ); + // 2.5.0: added wp_kses to button output + $allowed = radio_station_allowed_html( 'button', 'patreon' ); + echo wp_kses( $button , $allowed ); + // 2.2.7: added WordPress.Org star rating link // 2.3.0: only show for dismissable notice if ( $dismissable ) { - $blurb .= '

        '; - $blurb .= ''; - $blurb .= esc_html( __( 'Rate on WordPress.Org', 'radio-station' ) ); - $blurb .= ''; + echo '

        ' . PHP_EOL; + echo '' . PHP_EOL; + echo esc_html( __( 'Rate on WordPress.Org', 'radio-station' ) ) . PHP_EOL; + echo '' . PHP_EOL; } - $blurb .= '
      • '; - $blurb .= '
      '; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- dismiss notice icon --- if ( $dismissable ) { $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - $blurb .= '
    '; - $blurb .= ''; - $blurb .= ''; - $blurb .= ''; - $blurb .= '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; } - return $blurb; } // ------------------------------------ @@ -1504,7 +1557,7 @@ function radio_station_announcement_dismiss() { // --- set option to dismissed --- update_option( 'radio_station_announcement_dismissed', true ); // --- hide the announcement in parent frame --- - echo ""; + echo "" . PHP_EOL; exit; } } @@ -1527,21 +1580,21 @@ function radio_station_shift_conflict_notice() { $conflicts = get_option( 'radio_station_schedule_conflicts' ); if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { - echo '
    '; + echo '
    ' . PHP_EOL; - echo '
      '; + echo '
        ' . PHP_EOL; // 2.3.3.9: remove unnecessary left margin on first list item - echo '
      • '; - echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        '; - echo esc_html( __( 'has detected', 'radio-station' ) ) . '
        '; - echo esc_html( __( 'Schedule conflicts!', 'radio-station' ) ); - echo '
      • '; + echo '
      • ' . PHP_EOL; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'has detected', 'radio-station' ) ) . '
        ' . PHP_EOL; + echo esc_html( __( 'Schedule conflicts!', 'radio-station' ) ) . PHP_EOL; + echo '
      • ' . PHP_EOL; - echo '
      • '; - echo '' . esc_html( __( 'The following Shows have conflicting Shift times', 'radio-station' ) ) . ":
        "; + echo '
      • ' . PHP_EOL; + echo '' . esc_html( __( 'The following Shows have conflicting Shift times', 'radio-station' ) ) . ":
        " . PHP_EOL; - echo '
          '; + echo '
            ' . PHP_EOL; $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); foreach ( $conflicts as $show => $show_conflicts ) { foreach ( $show_conflicts as $conflict ) { @@ -1577,21 +1630,21 @@ function radio_station_shift_conflict_notice() { // --- show list link --- $show_list_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - echo '
          • '; - echo '' . esc_html( __( 'Go to Show List', 'radio-station' ) ) . ' →
            '; - echo esc_html( __( 'Conflicts are highlighted', 'radio-station' ) ) . '
            '; - echo esc_html( __( 'in Show Shift column.', 'radio-station' ) ); - echo '
          • '; + echo '
          • ' . PHP_EOL; + echo '' . esc_html( __( 'Go to Show List', 'radio-station' ) ) . ' →
            ' . PHP_EOL; + echo esc_html( __( 'Conflicts are highlighted', 'radio-station' ) ) . '
            ' . PHP_EOL; + echo esc_html( __( 'in Show Shift column.', 'radio-station' ) ) . PHP_EOL; + echo '
          • ' . PHP_EOL; // --- undismissable error notice --- - echo '
          • '; - echo esc_html( __( 'This notice will persist', 'radio-station' ) ) . '
            '; - echo esc_html( __( 'until conflicts are resolved.', 'radio-station' ) ); - echo '
          • '; + echo '
          • ' . PHP_EOL; + echo esc_html( __( 'This notice will persist', 'radio-station' ) ) . '
            ' . PHP_EOL; + echo esc_html( __( 'until conflicts are resolved.', 'radio-station' ) ) . PHP_EOL; + echo '
          • ' . PHP_EOL; - echo '
          '; + echo '
        ' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; } } @@ -1649,19 +1702,19 @@ function radio_station_mailchimp_form() { '; + echo '' . PHP_EOL; // --- AJAX subscription call --- // 2.3.0: added to record subscribers - $recordurl = add_query_arg( 'action', 'radio_station_record_subscribe', admin_url( 'admin-ajax.php') ); + $recordurl = add_query_arg( 'action', 'radio_station_record_subscribe', admin_url( 'admin-ajax.php' ) ); echo ""; + });" . PHP_EOL; } @@ -1672,18 +1725,21 @@ function radio_station_mailchimp_form() { add_action( 'wp_ajax_radio_station_record_subscribe', 'radio_station_record_subscribe' ); function radio_station_record_subscribe() { + // note: there is a typo in this option not worth fixing $email = sanitize_email( $_GET['email'] ); $subscribed = get_option( 'radio_station_subcribed' ); - if ( !$subscribed || !is_array($subscribed ) ) { + if ( !$subscribed || !is_array( $subscribed ) ) { add_option( 'radio_station_subcribed', array( $email ) ); } else { $subscribed[] = $email; update_option( 'radio_station_subcribed', $subscribed ); } - echo ""; - exit; + // --- submit form in parent window --- + echo "" . PHP_EOL; + + exit; } // ------------------ @@ -1694,27 +1750,28 @@ function radio_station_record_subscribe() { add_action( 'wp_ajax_radio_station_clear_option', 'radio_station_clear_plugin_options' ); function radio_station_clear_plugin_options() { - if ( !current_user_can( 'manage_options' ) ) {return;} - - if ( isset( $_GET['option'] ) && ( 'subscribed' == $_GET['option'] ) ) { - // note: there is a typo in this option not worth fixing - delete_option( 'radio_station_subcribed' ); - } - if ( isset( $_GET['option'] ) && ( 'notices' == $_GET['option'] ) ) { - delete_option( 'radio_station_read_notices' ); - } - if ( isset( $_GET['option'] ) && ( 'upgrades' == $_GET['option'] ) ) { - delete_option( 'radio_station_read_upgrades' ); - } - if ( isset( $_GET['option'] ) && ( 'announcement' == $_GET['option'] ) ) { - delete_option( 'radio_station_announcement_dismissed' ); - } - // 2.3.1: added clearing of listing offer options - if ( isset( $_GET['option'] ) && ( 'listingoffer' == $_GET['option'] ) ) { - delete_option( 'radio_station_listing_offer_dismissed' ); + if ( !current_user_can( 'manage_options' ) ) { + return; } - if ( isset( $_GET['option'] ) && ( 'offeraccepted' == $_GET['option'] ) ) { - delete_option( 'radio_station_listing_offer_accepted' ); + + if ( isset( $_GET['option'] ) ) { + // 2.5.0: condensed logic and added sanitize_title + $option = sanitize_title( $_GET['option'] ); + if ( 'subscribed' == $option ) { + // note: there is a typo in this option not worth fixing + delete_option( 'radio_station_subcribed' ); + } elseif ( 'notices' == $option ) { + delete_option( 'radio_station_read_notices' ); + } elseif ( 'upgrades' == $option ) { + delete_option( 'radio_station_read_upgrades' ); + } elseif ( 'announcement' == $option ) { + delete_option( 'radio_station_announcement_dismissed' ); + } elseif ( 'listingoffer' == $option ) { + // 2.3.1: added clearing of listing offer options + delete_option( 'radio_station_listing_offer_dismissed' ); + } elseif ( 'offeraccepted' == $option ) { + delete_option( 'radio_station_listing_offer_accepted' ); + } } exit; diff --git a/radio-station.php b/radio-station.php index bb43c98..7c9d62d 100644 --- a/radio-station.php +++ b/radio-station.php @@ -2,7 +2,7 @@ /** * @package Radio Station - * @version 2.4.0 + * @version 2.5.0 */ /* @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.9 +Version: 2.5.0 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -33,56 +33,47 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -// === Setup === +// === Plugin Setup === // - Define Plugin Constants // - Set Debug Mode Constant // - Define Plugin Data Slugs // - Include Plugin Files // - Plugin Options and Defaults -// - Pro Version Install Check // - Plugin Loader Settings +// - Bundle Plan Settings Filter // - Start Plugin Loader Instance // - Include Plugin Admin Files // - Load Plugin Text Domain +// === Plugin Functions === +// - Check Plan Options // - Check Plugin Version -// - Flush Rewrite Rules on Deactivation +// - Plugin Activation Action +// x Activation Welcome Redirect +// - Plugin Deactivation Action +// - Filter Plugin Updates Transient +// - Filter Freemius Plugin Icon Path +// - Set Allowed Origins for Radio Player +// === Resource Handling === +// - Enqueue Plugin Scripts +// - Register Moment JS // - Enqueue Plugin Script // - Enqueue Plugin Stylesheet // - Enqueue Datepicker // - Enqueue Localized Script Values // - Localization Script -// === Template Filters === -// - Get Template -// - Station Phone Number Filter -// - Automatic Pages Content Filter -// - Single Content Template Filter -// - Show Content Template Filter -// - Playlist Content Template Filter -// - Override Content Template Filter -// - DJ / Host / Producer Template Fix -// - Get DJ / Host Template -// - Get Producer Template -// - Single Template Hierarchy -// - Single Templates Loader -// - Archive Template Hierarchy -// x Archive Templates Loader -// - Add Links Back to Show -// - Show Posts Adjacent Links -// === Query Filters === -// - Playlist Archive Query Filter -// - Schedule Override Filters -// === User Roles === -// - Set Roles and Capabilities -// - Admin Fix for DJ / Host Role Label -// - maybe Revoke Edit Show Capability +// === Transient Caching === +// - Delete Prefixed Transients +// - Clear Cached Data +// - Clear Cache on Status Transitions // === Debugging === // - maybe Clear Transient Data // - Debug Output and Logging +// - Freemius Object Debug -// ------------- -// === Setup === -// ------------- +// -------------------- +// === Plugin Setup === +// -------------------- // ----------------------- // Define Plugin Constants @@ -91,6 +82,7 @@ // 2.4.0.3: remove separate constant for API docs link // 2.4.0.3: update home URLs to radiostation.pro // 2.4.0.8: added RADIO_STATION_SLUG constant +// 2.5.0: added RADIO_STATION_PATREON constant define( 'RADIO_STATION_SLUG', 'radio-station' ); define( 'RADIO_STATION_FILE', __FILE__ ); define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); @@ -100,6 +92,7 @@ // define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); define( 'RADIO_STATION_PRO_URL', 'https://radiostation.pro/' ); define( 'RADIO_STATION_NETMIX_DIR', 'https://netmix.com/' ); +define( 'RADIO_STATION_PATREON', 'https://patreon.com/radiostation' ); // ------------------------ // Define Plugin Data Slugs @@ -128,17 +121,13 @@ // 2.3.0: added debug mode constant // 2.3.2: added saving debug mode constant if ( !defined( 'RADIO_STATION_DEBUG' ) ) { - $rs_debug = false; - if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { - $rs_debug = true; - } + // 2.5.0: use simplified single line condition + $rs_debug = ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) ? true : false; define( 'RADIO_STATION_DEBUG', $rs_debug ); } if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { - $rs_save_debug = false; - if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { - $rs_save_debug = true; - } + // 2.5.0: use simplified single line condition + $rs_save_debug = ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) ? true : false; define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); } @@ -148,10 +137,14 @@ // 2.3.0: include new data feeds file // 2.3.0: renamed widget files to match new widget names // 2.3.0: separate file for legacy support functions +// 2.5.0: moved templates and user roles to separate include files +// 2.5.0: moved times and schedules to separate include files // --- Main Includes --- -require RADIO_STATION_DIR . '/includes/post-types.php'; require RADIO_STATION_DIR . '/includes/support-functions.php'; +require RADIO_STATION_DIR . '/includes/post-types.php'; +require RADIO_STATION_DIR . '/includes/templates.php'; +require RADIO_STATION_DIR . '/includes/user-roles.php'; require RADIO_STATION_DIR . '/includes/data-feeds.php'; require RADIO_STATION_DIR . '/includes/legacy.php'; @@ -159,17 +152,30 @@ // 2.4.0.4: include player as standard require RADIO_STATION_DIR . '/player/radio-player.php'; +// --- Scheduler --- +// note: keep this load order as schedules instantiates schedule engine class +require RADIO_STATION_DIR . '/scheduler/schedule-engine.php'; +require RADIO_STATION_DIR . '/includes/schedules.php'; +require RADIO_STATION_DIR . '/includes/times.php'; + // --- Shortcodes --- require RADIO_STATION_DIR . '/includes/master-schedule.php'; require RADIO_STATION_DIR . '/includes/shortcodes.php'; // --- Widgets --- // 2.4.0.4: move player widget here -require RADIO_STATION_DIR . '/includes/class-current-show-widget.php'; -require RADIO_STATION_DIR . '/includes/class-upcoming-shows-widget.php'; -require RADIO_STATION_DIR . '/includes/class-current-playlist-widget.php'; -require RADIO_STATION_DIR . '/includes/class-radio-clock-widget.php'; -require RADIO_STATION_DIR . '/includes/class-radio-player-widget.php'; +// 2.5.0: move widget classes to widgets directory +require RADIO_STATION_DIR . '/widgets/class-current-show-widget.php'; +require RADIO_STATION_DIR . '/widgets/class-upcoming-shows-widget.php'; +require RADIO_STATION_DIR . '/widgets/class-current-playlist-widget.php'; +require RADIO_STATION_DIR . '/widgets/class-radio-clock-widget.php'; +require RADIO_STATION_DIR . '/widgets/class-radio-player-widget.php'; + +// --- Blocks --- +// 2.5.0: added for registering block types +if ( function_exists( 'register_block_type' ) ) { + require RADIO_STATION_DIR . '/includes/blocks.php'; +} // --- Feature Development --- // 2.3.0: add feature branch development includes @@ -177,7 +183,7 @@ $features = array( 'import-export' ); foreach ( $features as $feature ) { $filepath = RADIO_STATION_DIR . '/includes/' . $feature . '.php'; - if ( file_exists ( $filepath ) ) { + if ( file_exists( $filepath ) ) { include $filepath; } } @@ -187,52 +193,12 @@ // --------------------------- // 2.3.0: added plugin options // 2.4.0.8: moved options array to separate file +// 2.5.0: move plan options check to separate function $timezones = radio_station_get_timezone_options( true ); $languages = radio_station_get_language_options( true ); $formats = radio_station_get_stream_formats(); -include RADIO_STATION_DIR . '/options.php'; - - -// ------------------------- -// Pro Version Install Check -// ------------------------- -// 2.4.0.3: added check active/installed Pro version -// 2.4.0.4: add defaults for has_addons and has_plans -$has_addons = false; -$has_plans = true; -$plan = 'free'; - -// --- check for deactivated pro plugin --- -// 2.4.0.4: remove unnecessary second argument to wp_cache_get -$plugins = wp_cache_get( 'plugins' ); -if ( !$plugins ) { - if ( function_exists( 'get_plugins' ) ) { - $plugins = get_plugins(); - } else { - $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; - if ( file_exists( $plugin_path ) ) { - include $plugin_path; - $plugins = get_plugins(); - } - } -} -if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { - foreach ( $plugins as $slug => $plugin ) { - if ( strstr( $slug, 'radio-station-pro.php' ) ) { - // 2.4.0.4: only set premium for upgrade version - if ( isset( $plugin['Name'] ) && strstr( $plugin['Name'], '(Premium)' ) ) { - $plan = 'premium'; - break; - } else { - // 2.4.0.4: detect and force enable addon version - $plan = 'premium'; - $has_addons = true; - $has_plans = false; - break; - } - } - } -} +$plan_options = radio_station_check_plan_options(); +require RADIO_STATION_DIR . '/options.php'; // ---------------------- // Plugin Loader Settings @@ -279,26 +245,36 @@ // 2.4.0.4: change upgrade_link to -upgrade 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', - 'hasplans' => $has_plans, + 'hasplans' => $plan_options['has_plans'], 'upgrade_link' => add_query_arg( 'page', RADIO_STATION_SLUG . '-pricing', admin_url( 'admin.php' ) ), 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', - 'hasaddons' => $has_addons, + 'hasaddons' => $plan_options['has_addons'], 'addons_link' => add_query_arg( 'page', RADIO_STATION_SLUG . '-addons', admin_url( 'admin.php' ) ), - 'plan' => $plan, - // 2.4.0.6: add bundles configuration - // 'bundle_id' => '9521', - // 'bundle_public_key' => 'pk_a2650f223ef877e87fe0fdfc4442b', - // 'bundle_license_auto_activation' => true, + 'plan' => $plan_options['plan_type'], ); +// --------------------------- +// Bundle Plan Settings Filter +// --------------------------- +// 2.5.0: added for bundle pricing configuration +add_filter( 'freemius_init_settings_radio_station', 'radio_station_freemius_bundle_config' ); +function radio_station_freemius_bundle_config( $settings ) { + // 2.4.0.6: add bundles configuration + $settings['bundle_id'] = '9521'; + $settings['bundle_public_key'] = 'pk_a2650f223ef877e87fe0fdfc4442b'; + $settings['bundle_license_auto_activation'] = true; + return $settings; +} + // ------------------------- // Set Plugin Option Globals // ------------------------- global $radio_station_data; +$radio_station_data['channel'] = ''; $radio_station_data['options'] = $options; $radio_station_data['settings'] = $settings; if ( RADIO_STATION_DEBUG ) { - echo 'Radio Station Settings: ' . print_r( $settings, true ) . ''; + echo 'Radio Station Settings: ' . esc_html( print_r( $settings, true ) ) . ''; } // ---------------------------- @@ -313,6 +289,8 @@ // 2.2.7: added conditional load of admin includes // 2.2.7: moved all admin functions to radio-station-admin.php if ( is_admin() ) { + + // --- Admin Includes --- require RADIO_STATION_DIR . '/radio-station-admin.php'; require RADIO_STATION_DIR . '/includes/post-types-admin.php'; @@ -332,6 +310,64 @@ function radio_station_init() { load_plugin_textdomain( 'radio-station', false, RADIO_STATION_DIR . '/languages' ); } + +// ------------------------ +// === Plugin Functions === +// ------------------------ + +// ------------------ +// Check Plan Options +// ------------------ +// 2.5.0: moved to separate function +function radio_station_check_plan_options() { + + // 2.4.0.3: added check active/installed Pro version + // 2.4.0.4: add defaults for has_addons and has_plans + $has_addons = false; + $has_plans = true; + $plan = 'free'; + + // --- check for deactivated pro plugin --- + // 2.4.0.4: remove unnecessary second argument to wp_cache_get + $plugins = wp_cache_get( 'plugins' ); + if ( !$plugins ) { + if ( function_exists( 'get_plugins' ) ) { + $plugins = get_plugins(); + } else { + $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; + if ( file_exists( $plugin_path ) ) { + include $plugin_path; + $plugins = get_plugins(); + } + } + } + if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { + foreach ( $plugins as $slug => $plugin ) { + if ( strstr( $slug, 'radio-station-pro.php' ) ) { + // 2.4.0.4: only set premium for upgrade version + if ( isset( $plugin['Name'] ) && strstr( $plugin['Name'], '(Premium)' ) ) { + $plan = 'premium'; + break; + } else { + // 2.4.0.4: detect and force enable addon version + $plan = 'premium'; + $has_addons = true; + $has_plans = false; + break; + } + } + } + } + + // 2.5.0: set as array for returning + $plan_options = array( + 'has_plans' => $has_plans, + 'has_addons' => $has_addons, + 'plan_type' => $plan, + ); + return $plan_options; +} + // -------------------- // Check Plugin Version // -------------------- @@ -372,9 +408,9 @@ function radio_station_check_version() { } } -// ----------------- -// Plugin Activation -// ----------------- +// ------------------------ +// Plugin Activation Action +// ------------------------ // (run on plugin activation, and thus also after a plugin update) // 2.2.8: fix for mismatched flag function name register_activation_hook( RADIO_STATION_FILE, 'radio_station_plugin_activation' ); @@ -392,7 +428,7 @@ function radio_station_plugin_activation() { // --- set welcome redirect transient --- // TODO: check if handled by Freemius activation // set_transient( 'radio_station_welcome', 1, 7 ); - + // 2.4.0.8: clear plugin updates transient on activation delete_site_transient( 'update_plugins' ); } @@ -411,11 +447,12 @@ function radio_station_welcome_redirect() { exit; } */ -// ----------------------------------- -// Flush Rewrite Rules on Deactivation -// ----------------------------------- +// -------------------------- +// Plugin Deactivation Action +// -------------------------- // 2.4.0.8: clear plugin updates transient on deactivation -register_deactivation_hook( RADIO_STATION_FILE, 'radio-station_deactivatoin'); +// 2.5.0: fix to typo in deactivation hook function +register_deactivation_hook( RADIO_STATION_FILE, 'radio_station_deactivation' ); function radio_station_deactivation() { flush_rewrite_rules(); delete_site_transient( 'update_plugins' ); @@ -455,9 +492,38 @@ function radio_station_freemius_plugin_url_path( $default_path ) { if ( file_exists( $default_path ) ) { return $icon_path; } - return $local_path; + // 2.5.0: fix to incorrect return of undefined local_path + return $default_path; +} + +// ------------------------------------ +// Set Allowed Origins for Radio Player +// ------------------------------------ +// 2.3.3.9: added for embedded radio player control +add_filter( 'allowed_http_origins', 'radio_station_allowed_player_origins' ); +function radio_station_allowed_player_origins( $origins ) { + + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + return $origins; + } + if ( !isset( $_REQUEST['action'] ) || ( 'radio_player' != $_REQUEST['action'] ) ) { + return $origins; + } + + $netmix = untrailingslashit( RADIO_STATION_NETMIX_DIR ); + $allowed = array( $netmix ); + $allowed = apply_filters( 'radio_station_player_allowed_origins', $allowed ); + foreach ( $allowed as $allow ) { + $origins[] = $allow; + } + return $origins; } + +// ------------------------- +// === Resource Handling === +// ------------------------- + // ---------------------- // Enqueue Plugin Scripts // ---------------------- @@ -482,12 +548,30 @@ function radio_station_enqueue_scripts() { $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); - // --- Moment.js --- + // --- register moment js --- + // 2.5.0: move to separate function for reusability + radio_station_register_moment(); + wp_enqueue_script( 'moment' ); +} + +// ------------------ +// Register Moment JS +// ------------------ +// 2.5.0: moved to separate funciton for resusability +function radio_station_register_moment() { + + // --- set script suffix --- + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + $suffix = ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) ? '' : $suffix; + + // --- enqueue Moment.js --- // ref: https://momentjs.com // 2.3.3.9: added for improved time format display - $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'momentjs', $moment_url, array(), '2.29.1', false ); - + // 2.5.0: add check for registered script in WP core + if ( !wp_script_is( 'moment', 'registered' ) ) { + $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'moment', $moment_url, array(), '2.29.4', false ); + } } // --------------------- @@ -505,9 +589,8 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = // 2.3.2: use plugin version for releases $plugin_version = radio_station_plugin_version(); $version_length = strlen( $plugin_version ); - // TODO: maybe allow for minor version release numbers - // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { - if ( 5 == $version_length ) { + // 2.5.0: allow for minor version release numbers + if ( 4 > $version_length ) { $version = $plugin_version; } else { $version = filemtime( $template['file'] ); @@ -526,7 +609,8 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = // ?.?.?: widgets.css style conditional enqueueing moved to within widget classes // 2.3.0: added abstracted method for enqueueing plugin stylesheets // 2.3.0: moved master schedule style enqueueing to conditional in master-schedule.php -function radio_station_enqueue_style( $stylekey ) { +// 2.5.0: added optional dependencies argument to style enqueues +function radio_station_enqueue_style( $stylekey, $deps = array() ) { // --- check style enqueued switch --- global $radio_station_styles; @@ -546,9 +630,8 @@ function radio_station_enqueue_style( $stylekey ) { // 2.3.2: use plugin version for releases $plugin_version = radio_station_plugin_version(); $version_length = strlen( $plugin_version ); - // TODO: maybe allow for minor version release numbers ? - // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { - if ( 5 == $version_length ) { + // 2.5.0: allow for minor version release numbers + if ( 4 > $version_length ) { $version = $plugin_version; } else { $version = filemtime( $template['file'] ); @@ -556,7 +639,7 @@ function radio_station_enqueue_style( $stylekey ) { $url = $template['url']; // --- enqueue styles in footer --- - wp_enqueue_style( 'rs-' . $stylekey, $url, array(), $version, 'all' ); + wp_enqueue_style( 'rs-' . $stylekey, $url, $deps, $version, 'all' ); // --- set style enqueued switch --- $radio_station_styles[$stylekey] = true; @@ -577,8 +660,7 @@ function radio_station_enqueue_datepicker() { // --- enqueue jquery datepicker styles --- // 2.3.0: update theme styles from 1.8.2 to 1.12.1 // 2.3.0: use local datepicker styles instead of via Google - // $protocol = 'http'; - // if ( is_ssl() ) {$protocol .= 's';} + // $protocol = is_ssl() ? 'https' : 'http'; // $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'; // wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.12.1' ); $style = radio_station_get_template( 'both', 'jquery-ui.css', 'css' ); @@ -604,20 +686,20 @@ function radio_station_localization_script() { // --- create settings objects --- $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; - + // 2.4.0.6: add filterable time display separator $time_separator = apply_filters( 'radio_station_time_separator', ':', 'javascript' ); $js .= " radio.sep = '" . esc_js( $time_separator ) . "';"; // --- set AJAX URL --- // 2.3.2: add admin AJAX URL - $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; + $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . "\n"; // --- clock time format --- // TODO: maybe set time format ? // ref: https://devhints.io/wip/intl-datetime $clock_format = radio_station_get_setting( 'clock_time_format' ); - $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; + $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . "\n"; // --- detect touchscreens --- // ref: https://stackoverflow.com/a/52855084/5240159 @@ -625,9 +707,9 @@ function radio_station_localization_script() { // --- set debug flag --- if ( RADIO_STATION_DEBUG ) { - $js .= "radio.debug = true;" . PHP_EOL; + $js .= "radio.debug = true;" . "\n"; } else { - $js .= "radio.debug = false;" . PHP_EOL; + $js .= "radio.debug = false;" . "\n"; } // --- radio timezone --- @@ -647,9 +729,9 @@ function radio_station_localization_script() { } elseif ( $offset > 0 ) { $offset = '+' . $offset; } - $js .= "radio.timezone.code = 'UTC" . esc_js( $offset ) . "'; "; - $js .= "radio.timezone.utc = '" . esc_js( $offset ) . "'; "; - $js .= "radio.timezone.utczone = true; "; + $js .= "radio.timezone.code = 'UTC" . esc_js( $offset ) . "';" . "\n"; + $js .= "radio.timezone.utc = '" . esc_js( $offset ) . "';" . "\n"; + $js .= "radio.timezone.utczone = true;" . "\n"; } else { @@ -666,23 +748,23 @@ function radio_station_localization_script() { } $utc_offset = 'UTC' . $utc_offset; $code = radio_station_get_timezone_code( $timezone ); - $js .= "radio.timezone.location = '" . esc_js( $timezone ) . "'; "; - $js .= "radio.timezone.offset = " . esc_js( $offset ) . "; "; - $js .= "radio.timezone.code = '" . esc_js( $code ) . "'; "; - $js .= "radio.timezone.utc = '" . esc_js( $utc_offset ) . "'; "; - $js .= "radio.timezone.utczone = false; "; + $js .= "radio.timezone.location = '" . esc_js( $timezone ) . "';" . "\n"; + $js .= "radio.timezone.offset = " . esc_js( $offset ) . ";" . "\n"; + $js .= "radio.timezone.code = '" . esc_js( $code ) . "';" . "\n"; + $js .= "radio.timezone.utc = '" . esc_js( $utc_offset ) . "';" . "\n"; + $js .= "radio.timezone.utczone = false;" . "\n"; } if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $js .= "radio.timezone.adjusted = false; "; + $js .= "radio.timezone.adjusted = false;" . "\n"; } else { - $js .= "radio.timezone.adjusted = true; "; + $js .= "radio.timezone.adjusted = true;" . "\n"; } // --- set user timezone offset --- // (and convert offset minutes to seconds) - $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . PHP_EOL; + $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . "\n"; // --- translated months array --- // 2.3.2: also translate short month labels @@ -701,8 +783,8 @@ function radio_station_localization_script() { $short .= ", "; } } - $js .= ");" . PHP_EOL; - $js .= $short . ");" . PHP_EOL; + $js .= ");" . "\n"; + $js .= $short . ");" . "\n"; // --- translated days array --- // 2.3.2: also translate short day labels @@ -721,8 +803,8 @@ function radio_station_localization_script() { $short .= ", "; } } - $js .= ");" . PHP_EOL; - $js .= $short . ");" . PHP_EOL; + $js .= ");" . "\n"; + $js .= $short . ");" . "\n"; // --- translated time unit strings --- $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; @@ -734,28 +816,27 @@ function radio_station_localization_script() { $js .= "radio.units.hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "'; "; $js .= "radio.units.hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "'; "; $js .= "radio.units.day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; "; - $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; " . PHP_EOL; + $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; " . "\n"; // --- time key map --- // 2.3.3.9: added for PHP Date Format to MomentJS conversions // (object of approximate 'PHP date() key':'moment format() key' conversions) $js .= "radio.moment_map = {'d':'D', 'j':'D', 'w':'e', 'D':'e', 'l':'e', 'N':'e', 'S':'Do', "; $js .= "'F':'M', 'm':'M', 'n':'M', 'M':'M', 'Y':'YYYY', 'y':'YY',"; - $js .= "'a':'a', 'A':'a', 'g':'h', 'G':'H', 'g':'h', 'H':'H', 'i':'m', 's':'s'}" . PHP_EOL; + $js .= "'a':'a', 'A':'a', 'g':'h', 'G':'H', 'g':'h', 'H':'H', 'i':'m', 's':'s'}" . "\n"; // --- convert show times --- // 2.3.3.9: $usertimes = radio_station_get_setting( 'convert_show_times' ); - if ( 'yes' == $usertimes ) { - $js .= "radio.convert_show_times = true;" . PHP_EOL; + if ( 'yes' === (string) $usertimes ) { + $js .= "radio.convert_show_times = true;" . "\n"; } else { - $js .= "radio.convert_show_times = false;" . PHP_EOL; + $js .= "radio.convert_show_times = false;" . "\n"; } // --- add inline script --- $js = apply_filters( 'radio_station_localization_script', $js ); return $js; - } // ------------------------- @@ -773,1744 +854,197 @@ function radio_station_streaming_data( $data, $station = false ) { 'fformat' => radio_station_get_setting( 'fallback_format' ), ); if ( RADIO_STATION_DEBUG ) { - echo 'Player Stream Data: ' . print_r( $data, true ) . ''; + echo 'Player Stream Data: ' . esc_html( print_r( $data, true ) ) . '' . "\n"; } $data = apply_filters( 'radio_station_streaming_data', $data, $station ); return $data; } -// ----------------------------------------- -// Fix to Redirect Plugin Settings Menu Link -// ----------------------------------------- -// 2.3.2: added settings submenu page redirection fix -add_action( 'init', 'radio_station_settings_page_redirect' ); -function radio_station_settings_page_redirect() { - // --- bug out if not admin page --- - if ( !is_admin() ) { - return; - } +// ------------------------- +// === Transient Caching === +// ------------------------- +// 2.5.0: moved here from includes/support-functions.php - // --- but out if not plugin settings page --- - if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != $_REQUEST['page'] ) ) { +// -------------------------- +// Delete Prefixed Transients +// -------------------------- +// 2.3.3.4: added helper for clearing transient data +function radio_station_delete_transients_with_prefix( $prefix ) { + global $wpdb; + + // 2.3.3.9: add trailing underscore to prefix + $prefix = $wpdb->esc_like( '_transient_' . $prefix . '_' ); + + // 2.3.3.9: fix to LIKE match + $query = "SELECT `option_name` FROM " . $wpdb->prefix . "options WHERE `option_name` LIKE '%" . $prefix . "%'"; + $results = $wpdb->get_results( $query, ARRAY_A ); + // if ( RADIO_STATION_DEBUG ) { + // echo $query . PHP_EOL . '
    '; + // echo 'Transients: ' . print_r( $results, true ); + // } + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { return; } - // --- check if link is for options-general.php --- - if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { - - // --- redirect to plugin settings page (admin.php) --- - $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - wp_redirect( $url ); - exit; - } -} - -// ------------------------------------ -// Set Allowed Origins for Radio Player -// ------------------------------------ -// 2.3.3.9: added for embedded radio player control -add_filter( 'allowed_http_origins', 'radio_station_allowed_player_origins' ); -function radio_station_allowed_player_origins( $origins ) { - if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { - return $origins; - } - if ( !isset( $_REQUEST['action'] ) || ( 'radio_player' != $_REQUEST['action'] ) ) { - return $origins; - } - $allowed = array( 'https://netmix.com' ); - $allowed = apply_filters( 'radio_station_player_allowed_origins', $allowed ); - foreach ( $allowed as $allow ) { - $origins[] = $allow; - } - return $origins; -} - - -// ------------------------ -// === Template Filters === -// ------------------------ - -// -------------------- -// Doing Template Check -// -------------------- -// 2.3.3.9: added to help distinguish filter contexts -function radio_station_doing_template() { - global $radio_station_data; - if ( isset( $radio_station_data['doing-template'] ) && $radio_station_data['doing-template'] ) { - return true; + foreach ( $results as $option ) { + // 2.3.3.9: fix to replace malgunctioning ltrim + // $key = ltrim( $option['option_name'], '_transient_' ); + $key = substr( $option['option_name'], 11 ); + delete_transient( $key ); + // 2.3.3.9: also delete transient cache object by key + wp_cache_delete( $key, 'transient' ); + // if ( RADIO_STATION_DEBUG ) { + // echo "Deleting transient and cache object for '" . $key . "'" . PHP_EOL; + // } } - return false; } -// ------------ -// Get Template -// ------------ -// 2.3.0: added for template file hierarchy -function radio_station_get_template( $type, $template, $paths = false ) { +// ----------------- +// Clear Cached Data +// ----------------- +// 2.3.3.9: made into separate function +// 2.4.0.3: added second argument for post type +function radio_station_clear_cached_data( $post_id = false, $post_type = false ) { - global $radio_station_data; + // --- clear main schedule transients --- + // 2.3.3: remove current show transient + // 2.3.4: add previous show transient + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_next_show' ); + delete_transient( 'radio_station_previous_show' ); - // --- maybe set default paths --- - if ( !$paths ) { - if ( isset( $radio_station_data['template-dirs'] ) ) { - $dirs = $radio_station_data['template-dirs']; - } - $paths = array( 'templates', '' ); - } elseif ( is_string( $paths ) ) { - if ( 'css' == $paths ) { - if ( isset( $radio_station_data['style-dirs'] ) ) { - $dirs = $radio_station_data['style-dirs']; - } - $paths = array( 'css', 'styles', '' ); - } elseif ( 'js' == $paths ) { - if ( isset( $radio_station_data['script-dirs'] ) ) { - $dirs = $radio_station_data['script-dirs']; - } - $paths = array( 'js', 'scripts', '' ); - } - } + // --- clear time-based schedule transients --- + // 2.3.4: delete all prefixed transients (for times) + radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); + radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); + radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); - if ( !isset( $dirs ) ) { - $dirs = array(); - $styledir = get_stylesheet_directory(); - $styledirurl = get_stylesheet_directory_uri(); - $templatedir = get_template_directory(); - $templatedirurl = get_template_directory_uri(); - - // --- maybe generate default hierarchies --- - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => $styledir . '/' . $path, - 'urlpath' => $styledirurl . '/' . $path, - ); - } - if ( $styledir != $templatedir ) { - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => $templatedir . '/' . $path, - 'urlpath' => $templatedirurl . '/' . $path, - ); - } - } - if ( defined( 'RADIO_STATION_PRO_DIR' ) ) { - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => RADIO_STATION_PRO_DIR . '/' . $path, - 'urlpath' => plugins_url( $path, RADIO_STATION_PRO_FILE ), - ); - } - } - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => RADIO_STATION_DIR . '/' . $path, - 'urlpath' => plugins_url( $path, RADIO_STATION_FILE ), - ); + // --- maybe clear show meta data --- + if ( $post_id ) { + do_action( 'radio_station_clear_data', $post_type, $post_id ); + if ( $post_type ) { + do_action( 'radio_station_clear_data', $post_type . '_meta', $post_id ); } } - $dirs = apply_filters( 'radio_station_template_dir_hierarchy', $dirs, $template, $paths ); - - // --- loop directory hierarchy to find first template --- - foreach ( $dirs as $dir ) { - // 2.3.4: use trailingslashit to account for empty paths - $template_path = trailingslashit( $dir['path'] ) . $template; - $template_url = trailingslashit( $dir['urlpath'] ) . $template; - - if ( file_exists( $template_path ) ) { - if ( 'file' == (string) $type ) { - return $template_path; - } elseif ( 'url' === (string) $type ) { - return $template_url; - } else { - return array( 'file' => $template_path, 'url' => $template_url ); - } - } - } + // --- maybe send directory ping --- + // 2.3.1: added directory update ping option + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); - return false; -} + // --- set last updated schedule time --- + // 2.3.2: added for data API use + update_option( 'radio_station_schedule_updated', time() ); -// ------------------------------------- -// Station Phone Number for Shows Filter -// ------------------------------------- -// 2.3.3.6: added to return station phone for all Shows (if not set for Show) -add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); -function radio_station_phone_number( $phone, $post_id ) { - if ( $phone ) { - return $phone; - } - $shows_phone = radio_station_get_setting( 'shows_phone' ); - if ( 'yes' == $shows_phone ) { - $phone = radio_station_get_setting( 'station_phone' ); - return $phone; - } - return false; } -// -------------------------------------- -// Station Email Address for Shows Filter -// -------------------------------------- -// 2.3.3.8: added to return station email for all Shows (if not set for Show) -add_filter( 'radio_station_show_email', 'radio_station_email_address', 10, 2 ); -function radio_station_email_address( $email, $post_id ) { - if ( $email ) { - return $email; +// --------------------------------- +// Clear Cache on Status Transitions +// --------------------------------- +// 2.4.0.4: clear show and override caches on post status changes +add_action( 'transition_post_status', 'radio_station_clear_cache_on_transitions', 10, 3 ); +function radio_station_clear_cache_on_transitions( $new_status, $old_status, $post ) { + if ( $new_status == $old_status ) { + return; } - $shows_email = radio_station_get_setting( 'shows_email' ); - if ( 'yes' == $shows_email ) { - $email = radio_station_get_setting( 'station_email' ); - return $email; + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( in_array( $post->post_type, $post_types ) ) { + radio_station_clear_cached_data( $post->ID, $post->post_type ); } - return false; } -// ------------------------------ -// Automatic Pages Content Filter -// ------------------------------ -// 2.3.0: standalone filter for automatic page content -// 2.3.1: re-add filter so the_content can be processed multiple times -// 2.3.3.6: set automatic content early and clear existing content -add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); -function radio_station_automatic_pages_content_set( $content ) { - - global $radio_station_data; - // if ( isset( $radio_station_data['doing_excerpt'] ) && $radio_station_data['doing_excerpt'] ) { - // return $content; - // } - - // --- for automatic output on selected master schedule page --- - $schedule_page = radio_station_get_setting( 'schedule_page' ); - if ( !is_null( $schedule_page ) && !empty( $schedule_page ) ) { - if ( is_page( $schedule_page ) ) { - $automatic = radio_station_get_setting( 'schedule_auto' ); - if ( 'yes' === (string) $automatic ) { - $view = radio_station_get_setting( 'schedule_view' ); - $atts = array( 'view' => $view ); - $atts = apply_filters( 'radio_station_automatic_schedule_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[master-schedule' . $atts_string . ']'; - } - } - } +// ----------------- +// === Debugging === +// ----------------- - // --- show archive page --- - // 2.3.0: added automatic display of show archive page - $show_archive_page = radio_station_get_setting( 'show_archive_page' ); - if ( !is_null( $show_archive_page ) && !empty( $show_archive_page ) ) { - if ( is_page( $show_archive_page ) ) { - $automatic = radio_station_get_setting( 'show_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'show_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_show_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[shows-archive' . $atts_string . ']'; - } +// -------------------------- +// maybe Clear Transient Data +// -------------------------- +// 2.3.0: clear show transients if debugging +// 2.3.1: added action to init hook +// 2.3.1: check clear show transients option +add_action( 'init', 'radio_station_clear_transients' ); +function radio_station_clear_transients() { + $clear_transients = radio_station_get_setting( 'clear_transients' ); + if ( RADIO_STATION_DEBUG || ( 'yes' === (string) $clear_transients ) ) { + // 2.3.2: do not clear on AJAX calls + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return; } + // 2.3.3.9: just use clear cached data function + radio_station_clear_cached_data( false ); } +} - // --- override archive page --- - // 2.3.0: added automatic display of override archive page - $override_archive_page = radio_station_get_setting( 'override_archive_page' ); - if ( !is_null( $override_archive_page ) && !empty( $override_archive_page ) ) { - if ( is_page( $override_archive_page ) ) { - $automatic = radio_station_get_setting( 'override_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'override_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_override_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[overrides-archive' . $atts_string . ']'; - } - } - } +// ------------------------ +// Debug Output and Logging +// ------------------------ +// 2.3.0: added debugging function +function radio_station_debug( $data, $echo = true, $file = false ) { - // --- playlist archive page --- - // 2.3.0: added automatic display of playlist archive page - $playlist_archive_page = radio_station_get_setting( 'playlist_archive_page' ); - if ( !is_null( $playlist_archive_page ) && !empty( $playlist_archive_page ) ) { - if ( is_page( $playlist_archive_page ) ) { - $automatic = radio_station_get_setting( 'playlist_archive_auto' ); - if ( 'yes' == $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'playlist_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_playlist_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[playlists-archive' . $atts_string . ']'; - } - } + // --- maybe output debug info --- + if ( $echo ) { + // 2.3.0: added span wrap for hidden display + // 2.3.1.1: added class for page source searches + // 2.5.0: wrap in esc_html anyway (though it might mangle debug output) + echo '' . PHP_EOL; } - // --- genre archive page --- - // 2.3.0: added automatic display of genre archive page - $genre_archive_page = radio_station_get_setting( 'genre_archive_page' ); - if ( !is_null( $genre_archive_page ) && !empty( $genre_archive_page ) ) { - if ( is_page( $genre_archive_page ) ) { - $automatic = radio_station_get_setting( 'genre_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'genre_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_genre_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[genres-archive' . $atts_string. ']'; - } + // --- check for logging constant --- + if ( defined( 'RADIO_STATION_DEBUG_LOG' ) ) { + if ( !$file && RADIO_STATION_DEBUG_LOG ) { + $file = 'radio-station.log'; + } elseif ( false === RADIO_STATION_DEBUG_LOG ) { + $file = false; } } - // --- languages archive page --- - // 2.3.3.9: added automatic display of language archive page - $language_archive_page = radio_station_get_setting( '' ); - if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { - if ( is_page( $language_archive_page ) ) { - $automatic = radio_station_get_setting( 'language_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'language_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_languagee_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[languages-archive' . $atts_string. ']'; - } + // --- write to debug file --- + if ( $file ) { + if ( !is_dir( RADIO_STATION_DIR . '/debug' ) ) { + wp_mkdir_p( RADIO_STATION_DIR . '/debug' ); } + $file = RADIO_STATION_DIR . '/debug/' . $file; + error_log( $data, 3, $file ); } - - // 2.3.3.6: moved out to reduce repetitive code - if ( isset( $shortcode ) ) { - remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); - remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); - $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); - add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); - // 2.3.3.6: clear existing content to allow for interim filters - $content = ''; - } - - return $content; } -// ---------------------------------- -// Automatic Pages Content Set Filter -// ---------------------------------- -// 2.3.3.6: append existing automatic page content to allow for interim filters -add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); -function radio_station_automatic_pages_content_get( $content ) { - global $radio_station_data; - if ( isset( $radio_station_data['automatic_content'] ) ) { - $content .= $radio_station_data['automatic_content']; +// --------------------- +// Freemius Object Debug +// --------------------- +// 2.4.0.4: added to debug freemius instance +add_action( 'shutdown', 'radio_station_freemius_debug' ); +function radio_station_freemius_debug() { + if ( is_admin() && RADIO_STATION_DEBUG && current_user_can( 'manage_options' ) ) { + // 2.4.0.6: check if global instance is set directly + if ( isset( $GLOBALS['radio_station_freemius'] ) ) { + $instance = $GLOBALS['radio_station_freemius']; + echo 'Freemius Object: ' . esc_html( print_r( $instance, true ) ) . ''; + } } - return $content; } - -// ------------------------------ -// Single Content Template Filter -// ------------------------------ -// 2.3.0: moved here and abstracted from templates/single-show.php -// 2.3.0: standalone filter name to allow for replacement -function radio_station_single_content_template( $content, $post_type ) { - - // --- check if single plugin post type --- - if ( !is_singular( $post_type ) ) { - return $content; - } - - // --- check for user content templates --- - // 2.3.3.9: allow for prefixed and unprefixed post types - $theme_dir = get_stylesheet_directory(); - $templates = array(); - $templates[] = $theme_dir . '/templates/single-' . $post_type . '-content.php'; - $templates[] = $theme_dir . '/single-' . $post_type . '-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php'; - $unprefixed_post_type = str_replace( 'rs-', '', $post_type ); - if ( $post_type != $unprefixed_post_type ) { - $templates[] = $theme_dir . '/templates/single-' . $unprefixed_post_type . '-content.php'; - $templates[] = $theme_dir . '/single-' . $unprefixed_post_type . '-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-' . $unprefixed_post_type . '-content.php'; - } - - // 2.3.0: fallback to show content template for overrides - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - // $templates[] = $theme_dir . '/templates/single-rs-show-content.php'; - // $templates[] = $theme_dir . '/single-rs-show-content.php'; - // $templates[] = RADIO_STATION_DIR . '/templates/single-rs-show-content.php'; - $templates[] = $theme_dir . '/templates/single-show-content.php'; - $templates[] = $theme_dir . '/single-show-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; - } - $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); - foreach ( $templates as $template ) { - if ( file_exists( $template ) ) { - $content_template = $template; - break; - } +// --------------------------- +// Debug Footer Scripts/Styles +// --------------------------- +// add_action( 'wp_footer', 'radio_station_script_debug', 1 ); +function radio_station_script_debug() { + if ( !RADIO_STATION_DEBUG ) { + return; } - if ( !isset( $content_template ) ) { - return $content; + if ( isset( $_REQUEST['debug-script'] ) ) { + $handle = $_REQUEST['debug-script']; + global $wp_scripts; + $debug = $wp_scripts->registered[$handle]; + echo 'Script object for ' . $handle . ': ' . print_r( $debug, true ) . '' . "\n"; } - - // --- enqueue template styles --- - // 2.3.3.9: check post type for page template style enqueue - $page_templates = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - if ( in_array( $post_type, $page_templates ) ) { - radio_station_enqueue_style( 'templates' ); + if ( isset( $_REQUEST['debug-style'] ) ) { + $handle = $_REQUEST['debug-style']; + global $wp_styles; + $debug = $wp_styles->registered[$handle]; + echo 'Style object for ' . $handle . ': ' . print_r( $debug, true ) . '' . "\n"; } - // 2.3.3.9: fire action for enqueueing other template styles - do_action( 'radio_station_enqueue_template_styles', $post_type ); - - // --- enqueue dashicons for frontend --- - wp_enqueue_style( 'dashicons' ); - - // --- filter post before including template --- - global $post; - $original_post = $post; - $post = apply_filters( 'radio_station_single_template_post_data', $post, $post_type ); - - // --- start buffer and include content template --- - ob_start(); - include $content_template; - $output = ob_get_contents(); - ob_end_clean(); - - // --- restore post global to be safe --- - $post = $original_post; - - // --- filter and return buffered content --- - $output = str_replace( '', $content, $output ); - $post_id = get_the_ID(); - $output = apply_filters( 'radio_station_content_' . $post_type, $output, $post_id ); - - return $output; } -// ------------------------------------ -// Filter for Override Show Linked Data -// ------------------------------------ -add_filter( 'radio_station_single_template_post_data', 'radio_station_override_linked_show_data', 10, 2 ); -function radio_station_override_linked_show_data( $post, $post_type ) { - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); - if ( $linked_id ) { - $show_post = get_post( $linked_id ); - if ( $show_post ) { - $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); - if ( $linked_fields ) { - foreach ( $linked_fields as $key => $switch ) { - if ( !$switch ) { - if ( 'show_title' == $key ) { - $post->post_title = $show_post->post_title; - } elseif ( 'show_excerpt' == $key ) { - $post->post_excerpt = $show_post->post_excerpt; - } elseif ( 'show_content' == $key ) { - $post->post_content = $show_post->post_content; - } - } - } - } - } - } - } - return $post; -} - -// ---------------------------- -// Show Content Template Filter -// ---------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_show_content_template', 11 ); -function radio_station_show_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_show_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_SHOW_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multuple times - add_filter( 'the_content', 'radio_station_show_content_template', 11 ); - return $output; -} - -// -------------------------------- -// Playlist Content Template Filter -// -------------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); -function radio_station_playlist_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_PLAYLIST_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multuple times - add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); - return $output; -} - -// -------------------------------- -// Override Content Template Filter -// -------------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_override_content_template', 11 ); -function radio_station_override_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multiple times - add_filter( 'the_content', 'radio_station_override_content_template', 11 ); - return $output; -} - -// ---------------------------------- -// Override Content with Show Content -// ---------------------------------- -// 2.3.3.9: maybe use show content for override content -add_filter( 'the_content', 'radio_station_override_content', 0 ); -function radio_station_override_content( $content ) { - if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { - return $content; - } - remove_filter( 'the_content', 'radio_station_override_content', 0 ); - global $post; - $override = radio_station_get_show_override( $post->ID, 'show_content' ); - if ( false !== $override ) { - $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); - $content = $override->post_content; - } - add_filter( 'the_content', 'radio_station_override_content', 0 ); - return $content; -} - -// --------------------------------- -// DJ / Host / Producer Template Fix -// --------------------------------- -// 2.2.8: temporary fix to not 404 author pages for DJs without blog posts -// Ref: https://wordpress.org/plugins/show-authors-without-posts/ -add_filter( '404_template', 'radio_station_author_host_pages' ); -function radio_station_author_host_pages( $template ) { - - global $wp_query; - if ( !is_author() ) { - - if ( get_query_var( 'host' ) ) { - - // --- get user by ID or name --- - $host = get_query_var( 'host' ); - if ( absint( $host ) > - 1 ) { - $user = get_user_by( 'ID', $host ); - } else { - $user = get_user_by( 'slug', $host ); - } - - // --- check if specified user has DJ/host role --- - if ( $user && in_array( 'dj', $user->roles ) ) { - $host_template = radio_station_get_host_template(); - if ( $host_template ) { - $template = $host_template; - } - } - - } elseif ( get_query_var( 'producer' ) ) { - - // --- get user by ID or name --- - $producer = get_query_var( 'producer' ); - if ( absint( $producer ) > - 1 ) { - $user = get_user_by( 'ID', $producer ); - } else { - $user = get_user_by( 'slug', $producer ); - } - - // --- check if specified user has producer role --- - if ( $user && in_array( 'producer', $user->roles ) ) { - $producer_template = radio_station_get_producer_template(); - if ( $producer_template ) { - $template = $producer_template; - } - } - - } elseif ( get_query_var( 'author' ) && ( 0 == $wp_query->posts->post ) ) { - - // --- get the author user --- - if ( get_query_var( 'author_name' ) ) { - $author = get_user_by( 'slug', get_query_var( 'author_name' ) ); - } else { - $author = get_userdata( get_query_var( 'author' ) ); - } - - if ( $author ) { - - // --- check if author has DJ, producer or administrator role --- - if ( in_array( 'dj', $author->roles ) - || in_array( 'producer', $author->roles ) - || in_array( 'administrator', $author->roles ) ) { - - // TODO: maybe check if user is assigned to any shows ? - $template = get_author_template(); - } - } - - } - - } - - return $template; -} - -// ---------------------- -// Get DJ / Host Template -// ---------------------- -// 2.3.0: added get DJ template function -// (modified template hierarchy from get_page_template) -function radio_station_get_host_template() { - - $templates = array(); - $hostname = get_query_var( 'host' ); - if ( $hostname ) { - $hostname_decoded = urldecode( $hostname ); - if ( $hostname_decoded !== $hostname ) { - $templates[] = 'host-' . $hostname_decoded . '.php'; - } - $templates[] = 'host-' . $hostname . '.php'; - } - $templates[] = 'single-host.php'; - - $templates = apply_filters( 'radio_station_host_templates', $templates ); - - return get_query_template( RADIO_STATION_HOST_SLUG, $templates ); -} - -// --------------------- -// Get Producer Template -// --------------------- -// 2.3.0: added get producer template function -// (modified template hierarchy from get_page_template) -function radio_station_get_producer_template() { - - $templates = array(); - $producername = get_query_var( 'producer' ); - if ( $producername ) { - $producername_decoded = urldecode( $producername ); - if ( $producername_decoded !== $producername ) { - $templates[] = 'producer-' . $producername_decoded . '.php'; - } - $templates[] = 'producer-' . $producername . '.php'; - } - $templates[] = 'single-producer.php'; - - $templates = apply_filters( 'radio_station_producer_templates', $templates ); - - return get_query_template( RADIO_STATION_PRODUCER_SLUG, $templates ); -} - -// ------------------------- -// Single Template Hierarchy -// ------------------------- -function radio_station_single_template_hierarchy( $templates ) { - - global $post; - - // --- remove single.php as the show / playlist fallback --- - // (allows for user selection of page.php or single.php later) - if ( ( RADIO_STATION_SHOW_SLUG === (string) $post->post_type ) - || ( RADIO_STATION_OVERRIDE_SLUG === (string) $post->post_type ) - || ( RADIO_STATION_PLAYLIST_SLUG === (string) $post->post_type ) ) { - $i = array_search( 'single.php', $templates ); - if ( false !== $i ) { - unset( $templates[$i] ); - } - } - - return $templates; -} - -// ----------------------- -// Single Templates Loader -// ----------------------- -add_filter( 'single_template', 'radio_station_load_template', 10, 3 ); -function radio_station_load_template( $single_template, $type, $templates ) { - - global $post; - - // --- handle single templates --- - $post_type = $post->post_type; - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - // TODO: RADIO_STATION_EPISODE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG - if ( in_array( $post_type, $post_types ) ) { - - // --- check for existing template override --- - // note: single.php is removed from template hierarchy via filter - remove_filter( 'single_template', 'radio_station_load_template' ); - add_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); - $template = get_single_template(); - remove_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); - - // --- use legacy template --- - if ( $template ) { - - // --- use the found user template --- - $single_template = $template; - - // --- check for combined template and content filter --- - $combined = radio_station_get_setting( $post_type . '_template_combined' ); - if ( 'yes' != $combined ) { - remove_filter( 'the_content', 'radio_station_' . $post_type . '_content_template', 11 ); - } - - } else { - - // --- get template selection --- - // 2.3.0: removed default usage of single show/playlist templates (not theme agnostic) - // 2.3.0: added option for use of template hierarchy - $show_template = radio_station_get_setting( $post_type . '_template' ); - - // --- maybe use legacy template --- - if ( 'legacy' === (string) $show_template ) { - return RADIO_STATION_DIR . '/templates/legacy/single-' . $post_type . '.php'; - } - - // --- use post or page template --- - // 2.3.3.8: added missing singular.php template setting - if ( 'post' == $show_template ) { - $templates = array( 'single.php' ); - } elseif ( 'page' == $show_template ) { - $templates = array( 'page.php' ); - } elseif ( 'singular' == $show_template ) { - $template = array( 'singular.php' ); - } - - // --- add standard fallbacks to index --- - // 2.3.3.8: remove singular fallback as it is explicitly chosen - $templates[] = 'index.php'; - $single_template = get_query_template( $post_type, $templates ); - } - } - - return $single_template; -} - -// -------------------------- -// Archive Template Hierarchy -// -------------------------- -add_filter( 'archive_template_hierarchy', 'radio_station_archive_template_hierarchy' ); -function radio_station_archive_template_hierarchy( $templates ) { - - // --- add extra template search path of /templates/ --- - $post_types = array_filter( (array) get_query_var( 'post_type' ) ); - if ( count( $post_types ) == 1 ) { - $post_type = reset( $post_types ); - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); - if ( in_array( $post_type, $post_types ) ) { - $template = array( 'templates/archive-' . $post_type . '.php' ); - // 2.3.0: add fallback to show archive template for overrides - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - $template[] = 'templates/archive-' . RADIO_STATION_SHOW_SLUG . '.php'; - } - $templates = array_merge( $template, $templates ); - } - } - - return $templates; -} - -// ------------------------ -// Archive Templates Loader -// ------------------------ -// TODO: implement standard archive page overrides via plugin settings -// add_filter( 'archive_template', 'radio_station_post_type_archive_template', 10, 3 ); -function radio_station_post_type_archive_template( $archive_template, $type, $templates ) { - global $post; - - // --- check for archive template override --- - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); - foreach ( $post_types as $post_type ) { - if ( is_post_type_archive( $post_type ) ) { - $override = radio_station_get_setting( $post_type . '_archive_override' ); - if ( 'yes' !== (string) $override ) { - $archive_template = get_page_template(); - add_filter( 'the_content', 'radio_station_' . $post_type . '_archive', 11 ); - } - } - } - - return $archive_template; -} - -// ------------------------- -// Add Links to Back to Show -// ------------------------- -// 2.3.0: add links to show from show posts and playlists -// 2.3.3.6: allow for multiple related show post assignments -add_filter( 'the_content', 'radio_station_add_show_links', 20 ); -function radio_station_add_show_links( $content ) { - - global $post; - - // note: playlists are linked via single-playlist-content.php template - - // 2.4.0.6: bug out if no post object - if ( !is_object( $post ) ) { - return $content; - } - - // --- filter to allow related post types --- - $post_type = $post->post_type; - $post_types = array( 'post' ); - $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); - - if ( in_array( $post_type, $post_types ) ) { - - // --- link show posts --- - $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); - // 2.3.3.6: convert string value if not multiple - if ( $related_shows && !is_array( $related_shows ) ) { - $related_shows = array( $related_shows ); - } - // 2.3.3.6: remove possible zero values - // 2.3.3.7: added count check for before looping - if ( $related_shows && ( count( $related_shows ) > 0 ) ) { - foreach ( $related_shows as $i => $related_show ) { - if ( 0 == $related_show ) { - unset( $related_shows[$i] ); - } - } - } - if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { - - $positions = array( 'after' ); - $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post_type, $post ); - if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { - if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { - - // --- set related shows link(s) --- - // 2.3.3.6: get all related show links - $show_links = ''; - $hash_ref = '#show-' . str_replace( 'rs-', '', $post_type ) . 's'; - foreach ( $related_shows as $related_show ) { - $show = get_post( $related_show ); - $title = $show->post_title; - $permalink = get_permalink( $show->ID ) . $hash_ref; - if ( '' != $show_links ) { - $show_links .= ', '; - } - $show_links .= '' . esc_html( $title ) . ''; - } - - // --- set post type labels --- - $before = $after = ''; - $post_type_object = get_post_type_object( $post_type ); - $singular = $post_type_object->labels->singular_name; - $plural = $post_type_object->labels->name; - - // --- before content links --- - if ( in_array( 'before', $positions ) ) { - if ( count( $related_shows ) > 1 ) { - $label = sprintf( __( '%s for Shows', 'radio-station' ), $singular ); - } else { - $label = sprintf( __( '%s for Show', 'radio-station' ), $singular ); - } - $before = $label . ': ' . $show_links . '

    '; - $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $related_shows ); - } - - // --- after content links --- - if ( in_array( 'after', $positions ) ) { - if ( count( $related_shows ) > 1 ) { - $label = sprintf( __( 'More %s for Shows', 'radio-station' ), $plural ); - } else { - $label = sprintf( __( 'More %s for Show', 'radio-station' ), $plural ); - } - $after = '
    ' . $label . ': ' . $show_links; - $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $related_shows ); - } - $content = $before . $content . $after; - } - } - } - - } - - // --- adjacent post links debug output --- - if ( RADIO_STATION_DEBUG ) { - $content .= 'Previous Post Link: ' . get_previous_post_link() . '' . PHP_EOL; - $content .= 'Next Post Link: ' . get_next_post_link() . '' . PHP_EOL; - } - - return $content; -} - -// ------------------------- -// Show Posts Adjacent Links -// ------------------------- -// 2.3.0: added show post adjacent links filter -add_filter( 'next_post_link', 'radio_station_get_show_post_link', 11, 5 ); -add_filter( 'previous_post_link', 'radio_station_get_show_post_link', 11, 5 ); -function radio_station_get_show_post_link( $output, $format, $link, $adjacent_post, $adjacent ) { - - global $radio_station_data, $post; - - // --- filter next and previous Show links --- - // 2.3.4: add filtering for adjacent show links - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); - if ( in_array( $post->post_type, $post_types ) ) { - if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - // 2.3.3.6: get next/previous Show for override date/time - // 2.3.3.9: modified to handle multiple override times - // 2.3.3.9: added check that schedule key is set - $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); - if ( $scheds && is_array( $scheds ) ) { - if ( array_key_exists( 'date', $scheds ) ) { - $sched = array( $scheds ); - } - $now = time(); - foreach ( $scheds as $sched ) { - $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; - $override_time = radio_station_get_time( ( $override_start + 1 ) ); - if ( !isset( $time ) ) { - $time = $override_time; - } elseif ( ( $time < $now ) && ( $override_time > $now ) ) { - $time = $override_time; - } - } - if ( 'next' == $adjacent ) { - $show = radio_station_get_next_show( $time ); - } elseif ( 'previous' == $adjacent ) { - $show = radio_station_get_previous_show( $time ); - } - } - } else { - $shifts = get_post_meta( $post->ID, 'show_sched', true ); - if ( $shifts && is_array( $shifts ) ) { - if ( count( $shifts ) < 1 ) { - // 2.3.3.6: default to standard adjacent post link - return $output; - } - if ( 1 == count( $shifts ) ) { - $shift = $shifts[0]; - $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - // 2.3.3.9: fix to put addition outside bracket - $time = radio_station_get_time( $shift_start ) + 1; - if ( 'next' == $adjacent ) { - $show = radio_station_get_next_show( $time ); - } elseif ( 'previous' == $adjacent ) { - $show = radio_station_get_previous_show( $time ); - } - } else { - // 2.3.3.6: added method for Show with multiple shifts - $now = radio_station_get_now(); - $show_shifts = radio_station_get_current_schedule(); - if ( !$show_shifts ) { - return $output; - } - - // --- get upcoming shift for Show --- - $next_shift = false; - foreach ( $show_shifts as $day => $day_shifts ) { - foreach ( $day_shifts as $day_shift ) { - if ( !$next_shift && ( $day_shift['show']['id'] == $post->ID ) ) { - if ( !isset( $last_shift ) ) { - $last_shift = $day_shift; - } - $start = $day_shift['date'] . ' ' . $day_shift['start']; - $start_time = radio_station_to_time( $start ); - $end = $day_shift['date'] . ' ' . $day_shift['end']; - $end_time = radio_station_to_time( $end ); - if ( ( $start_time > $now ) || ( $now < $end_time ) ) { - $next_shift = $day_shift; - } - } - } - } - if ( !$next_shift ) { - $next_shift = $last_shift; - } - // echo "Next Show Shift: " . print_r( $next_shift, true ); - - // --- reverse order for finding previous show shift --- - if ( 'previous' == $adjacent ) { - foreach ( $show_shifts as $day => $day_shifts ) { - $show_shifts[$day] = array_reverse( $day_shifts, true ); - } - $show_shifts = array_reverse( $show_shifts, true ); - } - - // --- loop shifts to find adjacent shift's Show --- - $found = false; - foreach ( $show_shifts as $day => $day_shifts ) { - foreach ( $day_shifts as $day_shift ) { - if ( !isset( $first_shift ) && ( $day_shift['show']['id'] != $post->ID ) ) { - $first_shift = $day_shift; - } - // echo "Shift: " . print_r( $day_shift, true ) . PHP_EOL; - if ( !isset( $show ) ) { - if ( $found && ( $day_shift['show']['id'] != $post->ID ) ) { - $show = $day_shift['show']; - } elseif ( !$found ) { - if ( $next_shift == $day_shift ) { - $found = true; - } - } - } - } - } - if ( !isset( $show ) && isset( $first_shift ) ) { - $show = $first_shift['show']; - } - } - } - } - - // --- generate adjacent Show link --- - if ( isset( $show ) ) { - if ( 'next' == $adjacent ) { - $rel = 'next'; - } elseif ( 'previous' == $adjacent ) { - $rel = 'prev'; - } - $adjacent_post = get_post( $show['id'] ); - - // --- adjacent post title --- - // 2.4.0.3: added fix for missing post title - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; - } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); - } - - return $output; - } - - // --- filter to allow related post types --- - $related_post_types = array( 'post' ); - $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); - if ( in_array( $post->post_type, $related_post_types ) ) { - - // --- filter to allow disabling --- - $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); - if ( !$link_show_posts ) { - return $output; - } - - // --- get related show --- - $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); - if ( !$related_show ) { - return $output; - } - if ( is_array( $related_show ) ) { - $related_shows = $related_show; - } else { - $related_shows = array( $related_show ); - } - // 2.3.3.6: remove possible saved zero value - foreach ( $related_shows as $i => $related_show ) { - if ( 0 == $related_show ) { - unset( $related_shows[$i] ); - } - } - if ( 0 == count( $related_shows ) ) { - return $output; - } - if ( RADIO_STATION_DEBUG ) { - echo 'Related Shows A: ' . print_r( $related_shows, true ) . ''; - } - - // --- get more Shows related to this related Post --- - // 2.3.3.6: allow for multiple related posts - // 2.3.3.9: added 'i:' prefix to LIKE value matches - global $wpdb; - $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; - if ( count( $related_shows ) > 1 ) { - foreach ( $related_show as $i => $show_id ) { - if ( $i > 0 ) { - $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $show_id . "%'"; - } - } - } - $results = $wpdb->get_results( $query, ARRAY_A ); - if ( RADIO_STATION_DEBUG ) { - echo 'Related Shows B: ' . print_r( $results, true ) . ''; - } - if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { - return $output; - } - $related_posts = array(); - foreach ( $results as $result ) { - $values = maybe_unserialize( $result['meta_value'] ); - if ( RADIO_STATION_DEBUG ) { - echo 'Post ' . $result['post_id'] . ' Related Show Values : ' . print_r( $values, true ) . ''; - } - // --- double check Show ID is actually a match --- - if ( ( $result['meta_value'] == $related_show ) || ( is_array( $values ) && array_intersect( $related_shows, $values ) ) ) { - // --- recheck post is of the same post type --- - $query = "SELECT post_type FROM " . $wpdb->prefix . "posts WHERE ID = %d"; - $query = $wpdb->prepare( $query, $result['post_id'] ); - $related_post_type = $wpdb->get_var( $query ); - if ( $related_post_type == $post->post_type ) { - $related_posts[] = $result['post_id']; - } - } - } - if ( RADIO_STATION_DEBUG ) { - echo 'Related Posts B: ' . print_r( $related_posts, true ) . ''; - } - if ( 0 == count( $related_posts ) ) { - return $output; - } - - // --- get adjacent post query --- - // 2.3.3.6: use post__in related post array instead of meta_query - $args = array( - 'post_type' => $post->post_type, - 'posts_per_page' => 1, - 'orderby' => 'post_modified', - 'post__in' => $related_posts, - 'ignore_sticky_posts' => true, - ); - - // --- setup for previous or next post --- - // 2.3.3.6: set date_query instead of meta_query - $post_type_object = get_post_type_object( $post->post_type ); - if ( 'previous' == $adjacent ) { - $args['order'] = 'DESC'; - $args['date_query'] = array( array( 'before' => $post->post_date ) ); - $rel = 'prev'; - $title = __( 'Previous Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - } elseif ( 'next' == $adjacent ) { - $args['order'] = 'ASC'; - $args['date_query'] = array( array( 'after' => $post->post_date ) ); - $rel = 'next'; - $title = __( 'Next Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - } - - // --- get the adjacent post --- - // 2.3.3.6: use date_query instead of looping posts - $show_posts = get_posts( $args ); - if ( RADIO_STATION_DEBUG ) { - echo 'Related Posts Args: ' . print_r( $args, true ) . ''; - } - if ( 0 == count( $show_posts ) ) { - return $output; - } - $adjacent_post = $show_posts[0]; - if ( RADIO_STATION_DEBUG ) { - echo 'Related Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; - } - - // --- adjacent post title --- - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; - } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - - // --- adjacent post link --- - // (from function get_adjacent_post_link) - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); - - } - - return $output; -} - - -// ============= -// Query Filters -// ============= - -// ----------------------------- -// Playlist Archive Query Filter -// ----------------------------- -// 2.3.0: added to replace old archive template meta query -add_filter( 'pre_get_posts', 'radio_station_show_playlist_query' ); -function radio_station_show_playlist_query( $query ) { - - if ( RADIO_STATION_PLAYLIST_SLUG == $query->get( 'post_type' ) ) { - - // --- not needed if using legacy template --- - $styledir = get_stylesheet_directory(); - if ( file_exists( $styledir . '/archive-playlist.php' ) - || file_exists( $styledir . '/templates/archive-playlist.php' ) ) { - return; - } - // 2.3.0: also check in parent theme directory - $templatedir = get_template_directory(); - if ( $templatedir != $styledir ) { - if ( file_exists( $templatedir . '/archive-playlist.php' ) - || file_exists( $templatedir . '/templates/archive-playlist.php' ) ) { - return; - } - } - - // --- check if show ID or slug is set -- - // TODO: maybe use get_query_var here ? - if ( isset( $_GET['show_id'] ) ) { - $show_id = absint( $_GET['show_id'] ); - if ( $show_id < 0 ) { - unset( $show_id ); - } - } elseif ( isset( $_GET['show'] ) ) { - $show = sanitize_title( $_GET['show'] ); - global $wpdb; - $show_query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; - $show_query = $wpdb->prepare( $show_query, $show ); - $show_id = $wpdb->get_var( $show_query ); - if ( !$show_id ) { - unset( $show_id ); - } - } - - // --- maybe add the playlist meta query --- - if ( isset( $show_id ) ) { - $meta_query = array( - 'key' => 'playlist_show_id', - 'value' => $show_id, - ); - $query->set( $meta_query ); - } - } -} - - -// ------------------ -// === User Roles === -// ------------------ - -// -------------------------- -// Set Roles and Capabilities -// -------------------------- -if ( is_multisite() ) { - add_action( 'init', 'radio_station_set_roles', 10, 0 ); - // 2.3.1: added possible fix for roles not being set on multisite - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} -function radio_station_set_roles() { - - global $wp_roles; - - // --- set only necessary capabilities for DJs --- - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, - // by default DJs cannot edit others playlists - // 'edit_others_playlists' => false, - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true, - ); - - // --- add the DJ role --- - // 2.3.0: translate DJ role name - // 2.3.0: change label from 'DJ' to 'DJ / Host' - // 2.3.0: check/add profile capabilities to hosts - $wp_roles->add_role( 'dj', __( 'DJ / Host', 'radio-station' ), $caps ); - $role_caps = $wp_roles->roles['dj']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $role_caps ) ) { - $role_caps = array(); - } - $host_caps = array( - 'edit_hosts', - 'edit_published_hosts', - 'delete_hosts', - 'read_hosts', - 'publish_hosts' - ); - foreach ( $host_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { - $wp_roles->add_cap( 'dj', $cap, true ); - } - } - // 2.3.3.9: fix for existing DJ role old name - $wp_roles->roles['dj']['name'] = __( 'DJ / Host', 'radio_station' ); - $wp_roles->role_names['dj'] = __( 'DJ / Host', 'radio_station' ); - - // --- add Show Producer role --- - // 2.3.0: add equivalent capability role for Show Producer - $wp_roles->add_role( 'producer', __( 'Show Producer', 'radio-station' ), $caps ); - $role_caps = $wp_roles->roles['producer']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $role_caps ) ) { - $role_caps = array(); - } - $producer_caps = array( - 'edit_producers', - 'edit_published_producers', - 'delete_producers', - 'read_producers', - 'publish_producers', - ); - foreach ( $producer_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { - $wp_roles->add_cap( 'producer', $cap, true ); - } - } - - // --- grant all capabilities to Show Editors --- - // 2.3.0: set Show Editor role capabilities - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'edit_private_shows' => true, - 'delete_shows' => true, - 'delete_published_shows' => true, - 'delete_others_shows' => true, - 'delete_private_shows' => true, - 'read_shows' => true, - 'publish_shows' => true, - - 'edit_playlists' => true, - 'edit_published_playlists' => true, - 'edit_others_playlists' => true, - 'edit_private_playlists' => true, - 'delete_playlists' => true, - 'delete_published_playlists' => true, - 'delete_others_playlists' => true, - 'delete_private_playlists' => true, - 'read_playlists' => true, - 'publish_playlists' => true, - - 'edit_overrides' => true, - 'edit_overrides_playlists' => true, - 'edit_others_overrides' => true, - 'edit_private_overrides' => true, - 'delete_overrides' => true, - 'delete_published_overrides' => true, - 'delete_others_overrides' => true, - 'delete_private_overrides' => true, - 'read_overrides' => true, - 'publish_overrides' => true, - - 'edit_hosts' => true, - 'edit_published_hosts' => true, - 'edit_others_hosts' => true, - 'delete_hosts' => true, - 'read_hosts' => true, - 'publish_hosts' => true, - - 'edit_producers' => true, - 'edit_published_producers' => true, - 'edit_others_producers' => true, - 'delete_producers' => true, - 'read_producers' => true, - 'publish_producers' => true, - - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_others_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true, - ); - - // --- add the Show Editor role --- - // 2.3.0: added Show Editor role - $wp_roles->add_role( 'show-editor', __( 'Show Editor', 'radio-station' ), $caps ); - - // --- check plugin setting for authors --- - if ( radio_station_get_setting( 'add_author_capabilities' ) == 'yes' ) { - - // --- grant show edit capabilities to author users --- - $author_caps = $wp_roles->roles['author']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $author_caps ) ) { - $author_caps = array(); - } - $extra_caps = array( - 'edit_shows', - 'edit_published_shows', - 'read_shows', - 'publish_shows', - - 'edit_playlists', - 'edit_published_playlists', - 'read_playlists', - 'publish_playlists', - - 'edit_overrides', - 'edit_published_overrides', - 'read_overrides', - 'publish_overrides', - ); - foreach ( $extra_caps as $cap ) { - if ( !array_key_exists( $cap, $author_caps ) || ( !$author_caps[$cap] ) ) { - $wp_roles->add_cap( 'author', $cap, true ); - } - } - } - - // --- specify edit caps (for editors and admins) --- - // 2.3.0: added show override, host and producer capabilities - $edit_caps = array( - 'edit_shows', - 'edit_published_shows', - 'edit_others_shows', - 'edit_private_shows', - 'delete_shows', - 'delete_published_shows', - 'delete_others_shows', - 'delete_private_shows', - 'read_shows', - 'publish_shows', - - 'edit_playlists', - 'edit_published_playlists', - 'edit_others_playlists', - 'edit_private_playlists', - 'delete_playlists', - 'delete_published_playlists', - 'delete_others_playlists', - 'delete_private_playlists', - 'read_playlists', - 'publish_playlists', - - 'edit_overrides', - 'edit_published_overrides', - 'edit_others_overrides', - 'edit_private_overrides', - 'delete_overrides', - 'delete_published_overrides', - 'delete_others_overrides', - 'delete_private_overrides', - 'read_overrides', - 'publish_overrides', - - 'edit_hosts', - 'edit_published_hosts', - 'edit_others_hosts', - 'delete_hosts', - 'delete_others_hosts', - 'read_hosts', - 'publish_hosts', - - 'edit_producers', - 'edit_published_producers', - 'edit_others_producers', - 'delete_producers', - 'delete_others_producers', - 'read_producers', - 'publish_producers', - ); - - // --- check plugin setting for editors --- - if ( radio_station_get_setting( 'add_editor_capabilities' ) == 'yes' ) { - - // --- grant show edit capabilities to editor users --- - $editor_caps = $wp_roles->roles['editor']['capabilities']; - // 2.3.1.1: added check if capabilities is an array - if ( !is_array( $editor_caps ) ) { - $editor_caps = array(); - } - foreach ( $edit_caps as $cap ) { - if ( !array_key_exists( $cap, $editor_caps ) || ( !$editor_caps[$cap] ) ) { - $wp_roles->add_cap( 'editor', $cap, true ); - } - } - } - - // --- grant all plugin capabilities to admin users --- - $admin_caps = $wp_roles->roles['administrator']['capabilities']; - // 2.3.1.1: added check if capabilities is an array - if ( !is_array( $admin_caps ) ) { - $admin_caps = array(); - } - foreach ( $edit_caps as $cap ) { - if ( !array_key_exists( $cap, $admin_caps ) || ( !$admin_caps[$cap] ) ) { - $wp_roles->add_cap( 'administrator', $cap, true ); - } - } - -} - -// ---------------------------------- -// Admin Fix for DJ / Host Role Label -// ---------------------------------- -// 2.3.3.9: added for user edit screen crackliness -add_filter( 'editable_roles', 'radio_station_role_check_test', 9 ); -function radio_station_role_check_test( $roles ) { - if ( RADIO_STATION_DEBUG && is_admin() ) { - echo "DJ Role: " . print_r( $roles['dj'], true ); - } - $roles['dj']['name'] = __( 'DJ / Host', 'radio-station' ); - return $roles; -} - -// --------------------------------- -// maybe Revoke Edit Show Capability -// --------------------------------- -// (revoke ability to edit show if user is not assigned to it) -add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 4 ); -function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { - - global $post, $wp_roles; - - // 2.4.0.4.1: fix for early capability check plugin conflict - if ( !function_exists( 'radio_station_get_setting' ) ) { - return $allcaps; - } - - // --- check if super admin --- - // 2.3.3.6: get user object from fourth argument instead - // ? fix to not revoke edit caps from super admin ? - // (not implemented, as causing a connection reset error!) - // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { - // return $allcaps; - // } - - // --- debug passed capability arguments --- - // TODO: get post object from args instead of global ? - if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { - echo 'Cap Args: ' . print_r( $args, true ) . ''; - } - - // --- check for editor role --- - // 2.3.3.6: check editor roles first separately - // 2.4.0.4: only add WordPress editor role if on in settings - $editor_roles = array( 'administrator', 'show-editor' ); - $editor_role_caps = radio_station_get_setting( 'add_editor_capabilities' ); - if ( 'yes' == $editor_role_caps ) { - $editor_roles[] = 'editor'; - } - foreach ( $editor_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - return $allcaps; - } - } - - // --- get roles with edit shows capability --- - $edit_show_roles = $edit_others_shows_roles = array(); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach ( $wp_roles->roles as $name => $role ) { - // 2.3.0: fix to skip roles with no capabilities assigned - if ( isset( $role['capabilities'] ) ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - // 2.3.0: change publish_shows cap check to edit_shows - if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_show_roles ) ) { - $edit_show_roles[] = $name; - } - } - // 2.3.3.6: add check for edit-others_shows capability - if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_others_shows_roles ) ) { - $edit_others_shows_roles[] = $name; - } - } - } - } - } - } - - // 2.3.3.6: preserve if user has edit_others_shows capability - foreach ( $edit_others_shows_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - // 2.4.0.4: do not automatically assume capability match - // return $allcaps; - $found = true; - } - } - - // 2.2.8: remove strict in_array checking - $found = false; - foreach ( $edit_show_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - $found = true; - } - } - - // --- maybe revoke edit show capability for post --- - // 2.3.3.6: fix to incorrect logic for removing edit show capability - if ( $found ) { - - // --- limit this to published shows --- - // 2.3.0: added object and property_exists check to be safe - if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { - - // 2.3.0: removed is_admin check (so works with frontend edit show link) - // 2.3.0: moved check if show is published inside - if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { - - // --- get show hosts and producers --- - $hosts = get_post_meta( $post->ID, 'show_user_list', true ); - $producers = get_post_meta( $post->ID, 'show_producer_list', true ); - - // 2.3.0.4: convert possible (old) non-array values - if ( !$hosts || empty( $hosts ) ) { - $hosts = array(); - } elseif ( !is_array( $hosts ) ) { - $hosts = array( $hosts ); - } - if ( !$producers || empty( $producers ) ) { - $producers = array(); - } elseif ( !is_array( $producers ) ) { - $producers = array( $producers ); - } - - // ---- revoke editing capability if not assigned to this show --- - // 2.2.8: remove strict in_array checking - // 2.3.0: also check new Producer role - if ( !in_array( $user->ID, $hosts ) && !in_array( $user->ID, $producers ) ) { - - // --- remove the edit_shows capability --- - $allcaps['edit_shows'] = false; - $allcaps['edit_others_shows'] = false; - if ( RADIO_STATION_DEBUG ) { - echo "Removed Edit Show Caps (" . $post->ID . ")"; - } - - // 2.3.0: move check if show is published inside - if ( 'publish' == $post->post_status ) { - $allcaps['edit_published_shows'] = false; - } - } else { - // 2.4.0.4: add edit others shows capability - // (fix for when not original show author) - $allcaps['edit_shows'] = true; - $allcaps['edit_others_shows'] = true; - if ( RADIO_STATION_DEBUG ) { - echo "Added Edit Show Caps (" . $post->ID . ")"; - } - - } - } - } - } - - return $allcaps; -} - - -// ================= -// --- Debugging --- -// ================= - -// -------------------------- -// maybe Clear Transient Data -// -------------------------- -// 2.3.0: clear show transients if debugging -// 2.3.1: added action to init hook -// 2.3.1: check clear show transients option -add_action( 'init', 'radio_station_clear_transients' ); -function radio_station_clear_transients() { - $clear_transients = radio_station_get_setting( 'clear_transients' ); - if ( RADIO_STATION_DEBUG || ( 'yes' == $clear_transients ) ) { - // 2.3.2: do not clear on AJAX calls - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - return; - } - // 2.3.3.9: just use clear cached data function - radio_station_clear_cached_data( false ); - } -} - -// ------------------------ -// Debug Output and Logging -// ------------------------ -// 2.3.0: added debugging function -function radio_station_debug( $data, $echo = true, $file = false ) { - - // --- maybe output debug info --- - if ( $echo ) { - // 2.3.0: added span wrap for hidden display - // 2.3.1.1: added class for page source searches - echo '' . PHP_EOL; - } - - // --- check for logging constant --- - if ( defined( 'RADIO_STATION_DEBUG_LOG' ) ) { - if ( !$file && RADIO_STATION_DEBUG_LOG ) { - $file = 'radio-station.log'; - } elseif ( false === RADIO_STATION_DEBUG_LOG ) { - $file = false; - } - } - - // --- write to debug file --- - if ( $file ) { - if ( !is_dir( RADIO_STATION_DIR . '/debug' ) ) { - wp_mkdir_p( RADIO_STATION_DIR . '/debug' ); - } - $file = RADIO_STATION_DIR . '/debug/' . $file; - error_log( $data, 3, $file ); - } -} - -// --------------------- -// Freemius Object Debug -// --------------------- -// 2.4.0.4: added to debug freemius instance -add_action( 'shutdown', 'radio_station_freemius_debug' ); -function radio_station_freemius_debug() { - if ( is_admin() && RADIO_STATION_DEBUG && current_user_can( 'manage_options' ) ) { - // 2.4.0.6: check if global instance is set directly - if ( isset( $GLOBALS['radio_station_freemius'] ) ) { - $instance = $GLOBALS['radio_station_freemius']; - echo 'Freemius Object: ' . print_r( $instance, true ) . ''; - } - } -} diff --git a/readme.md b/readme.md index b9522f3..25d7bbd 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.9.3 +Tested up to: 6.0 -Stable tag: 2.4.0.9 +Stable tag: 2.5.0 License: GPLv2 or later @@ -207,7 +207,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet #### How do I install the latest Development version for testing? -If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: +If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: 1. Download the `develop` branch zip from the Github repository at: `https://github.com/netmix/radio-station/tree/develop/` @@ -227,6 +227,12 @@ You can now visit your site to make sure nothing is broken. If you experience is ## Upgrade Notices +#### 2.5.0 +* Radio Station Blocks for Gutenberg Block Editor! +* https://radiostation.pro/radio-station-2-5-0-release-with-blocks/ +* Refactored Schedule Engine Class +* Improved translations and sanitization + #### 2.4.0 * Radio Station Stream Player Widget! * https://netmix.com/radio-station-2-4-0-release-with-stream-player/ diff --git a/readme.txt b/readme.txt index 4553140..76baba0 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.9.3 -Stable tag: 2.4.0.9 +Tested up to: 6.0 +Stable tag: 2.5.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -195,7 +195,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet = How do I install the latest Development version for testing? = -If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: +If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: 1. Download the `develop` branch zip from the Github repository at: `https://github.com/netmix/radio-station/tree/develop/` @@ -218,6 +218,19 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.5.0 = +* Added: Wordpress Radio Blocks (converted Widgets) +* Updated: Plugin Panel (1.2.6) +* Updated: Moment JS (2.29.4) with WP Loading +* Improved: Refactored Schedule Engine Class (1.0.0) +* Improved: Standardized Widget Input Fields +* Improved: WordPress Coding Standards +* Improved: Sanitization using KSES +* Improved: Translation Implementation +* Improved: use WP JSON functions for data endpoints +* Fixed: Countdowns with multiple widget instances +* Fixed: Radio Player iOS no volume control detection + = 2.4.0.9 = * Update: Sysend (1.11.1) for Radio Player * Fixed: missing register REST routes permission_callback argument @@ -770,6 +783,12 @@ You can now visit your site to make sure nothing is broken. If you experience is == Upgrade Notice == += 2.5.0 = +* Radio Station Blocks for Gutenberg Block Editor! +* https://netmix.com/radio-station-2-5-0-release-with-blocks/ +* Refactored Schedule Engine Class +* Improved translations and sanitization + = 2.4.0 = * Radio Station Stream Player Widget! * https://netmix.com/radio-station-2-4-0-release-with-stream-player/ diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php new file mode 100644 index 0000000..44edda9 --- /dev/null +++ b/scheduler/schedule-engine.php @@ -0,0 +1,3478 @@ +channel = $args['channel']; + } + if ( isset( $args['context'] ) ) { + $this->context = $args['context']; + } + if ( isset( $args['locale'] ) ) { + $this->locale = $args['locale']; + } else { + $this->locale = get_locale(); + } + + // --- check debug constants --- + if ( defined( 'SCHEDULE_ENGINE_DEBUG' ) && SCHEDULE_ENGINE_DEBUG ) { + $this->debug = SCHEDULE_ENGINE_DEBUG; + } elseif ( isset( $args['debug'] ) ) { + $this->debug = $args['debug']; + } + if ( defined( 'SCHEDULE_ENGINE_DEBUG_LOG' ) && SCHEDULE_ENGINE_DEBUG_LOG ) { + $this->debug_log = SCHEDULE_ENGINE_DEBUG_LOG; + } elseif ( isset( $args['debug_log'] ) ) { + $this->debug_log = $args['debug_log']; + } + + // --- add class actions --- + add_action( 'schedule_engine_set_current_schedule', array( $this, 'set_current_schedule' ), 10, 5 ); + add_action( 'schedule_engine_set_previous_shift', array( $this, 'set_previous_shift' ), 10, 5 ); + add_action( 'schedule_engine_set_current_shift', array( $this, 'set_current_shift' ), 10, 5 ); + add_action( 'schedule_engine_set_next_shift', array( $this, 'set_next_shift' ), 10, 5 ); + + } + + + // --------------- + // === Caching === + // --------------- + + // -------------------- + // Set Current Schedule + // -------------------- + public function set_current_schedule( $current_schedule, $expires, $time, $channel, $context ) { + + // --- require a context --- + if ( '' == $context ) { + return; + } + + // --- set current schedule transient --- + $transient_key = 'current_schedule'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + set_transient( $context . '_' . $transient_key, $current_schedule, $expires ); + } + + // ------------------ + // Set Previous Shift + // ------------------ + public function set_previous_shift( $previous_shift, $expires, $time, $channel, $context ) { + + // --- require a context --- + if ( '' == $context ) { + return; + } + + // --- set previous shift transient --- + $transient_key = 'previous_shift'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + set_transient( $context . '_' . $transient_key, $previous_shift, $expires ); + } + + // ----------------- + // Set Current Shift + // ----------------- + function set_current_shift( $next_show, $expires, $time, $channel, $context ) { + + // --- require a context --- + if ( '' == $context ) { + return; + } + + // --- set current shift transient --- + $transient_key = 'current_shift'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + set_transient( $context . '_' . $transient_key, $next_show, $expires ); + } + + // -------------- + // Set Next Shift + // -------------- + public function set_next_shift( $next_shift, $expires, $time, $channel, $context ) { + + // --- require a context --- + if ( '' == $context ) { + return; + } + + // --- set next shift transient --- + $transient_key = 'next_shift'; + if ( '' != $channel ) { + $transient_key .= '_' . $channel; + } + if ( $time ) { + $time = (string) $time; + $transient_key .= '_' . $time; + } + set_transient( $context . '_' . $transient_key, $next_shift, $expires ); + } + + + // ---------------- + // === Schedule === + // ---------------- + + // ----------------- + // Get Record Shifts + // ----------------- + public function get_record_shifts( $record_id, $key ) { + + // --- get show shift schedule --- + $shifts = get_post_meta( $record_id, $key, true ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + $changed = false; + foreach ( $shifts as $i => $shift ) { + + // --- check for unique ID length --- + if ( strlen( $i ) != 8 ) { + + // --- generate unique shift ID --- + unset( $shifts[$i] ); + $unique_id = $this->unique_shift_id(); + $shifts[$unique_id] = $shift; + $changed = true; + } + } + + // --- update shifts to save unique ID indexes --- + if ( $changed ) { + update_post_meta( $record_id, $key, $shifts ); + } + } + + return $shifts; + } + + // ------------------------ + // Generate Unique Shift ID + // ------------------------ + public function unique_shift_id() { + + // --- set channel / context --- + $channel = $this->channel; + $context = $this->context; + + if ( '' != $channel ) { + $option_key = $context . '_' . $channel . '_shift_ids'; + } else { + $option_key = $context . '_shift_ids'; + } + + // --- get all shift IDs --- + $shift_ids = get_option( $option_key ); + if ( !$shift_ids ) { + $shift_ids = array(); + } + $unique_id = wp_generate_password( 8, false, false ); + if ( in_array( $unique_id, $shift_ids ) ) { + while ( in_array( $unique_id, $shift_ids ) ) { + $unique_id = wp_generate_password( 8, false, false ); + } + $shift_ids[] = $unique_id; + } + + // --- store the unique shift ID --- + update_option( $option_key, $shift_ids ); + + // --- return the unique ID --- + return $unique_id; + } + + // -------------------- + // Generate Hashed GUID + // -------------------- + // 2.3.2: add hashing function for hashed GUID + function get_hashed_guid( $record_id ) { + + global $wpdb; + $query = "SELECT guid FROM " . $wpdb->posts . " WHERE ID = %d"; + // 2.5.0: fix to use prepare method on query + $guid = $wpdb->get_var( $wpdb->prepare( $query, $record_id ) ); + if ( !$guid ) { + $guid = get_permalink( $record_id ); + } + $hash = md5( $guid ); + + return $hash; + } + + // -------------- + // Get All Shifts + // -------------- + // 2.3.0: added get show shifts data grabber + // 2.3.2: added second argument to get non-split shifts + // 2.3.3: added time as third argument for ondemand shifts + public function get_all_shifts( $records, $check_conflicts = true, $split = true, $time = false, $timezone = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- get weekdates for checking --- + $now = $time ? $time : $this->get_now(); + $timezone = $timezone ? $timezone : $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + $weekdays = $this->get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'now' => $now, + 'today' => $today, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + 'timezone' => $timezone, + ); + + // --- process records into shift data --- + $all_shifts = $this->get_shift_data( $records, $split, $times ); + $all_shifts = $this->sort_shifts( $all_shifts, $weekdays ); + + // --- check shifts for conflicts --- + if ( $check_conflicts ) { + $all_shifts = $this->check_shifts( $all_shifts, $times ); + } else { + // --- return raw data for other shift conflict checking --- + return $all_shifts; + } + + // --- sort into day shifts (starting today) --- + $all_shifts = $this->sort_day_shifts( $all_shifts, $today, $timezone ); + $all_shifts = apply_filters( 'schedule_engine_all_shifts', $all_shifts, $records, $check_conflicts, $split, $time, $timezone, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $all_shifts = apply_filters( $context . '_all_shifts', $all_shifts, $records, $check_conflicts, $split, $time, $timezone, $channel ); + } + return $all_shifts; + } + + // -------------- + // Get Shift Data + // -------------- + public function get_shift_data( $records, $split, $times ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- set time date to short names --- + $now = $times['now']; + $today = $times['today']; + $weekdays = $times['weekdays']; + $weekdates = $times['weekdates']; + $timezone = $times['timezone']; + + // --- loop shows to get shifts --- + // TODO: maybe use filter for setting record shift key? + // $key = apply_filters( 'schedule_engine_shift_key', false, $context ); + $errors = $all_shifts = array(); + if ( $records && is_array( $records ) && ( count( $records ) > 0 ) ) { + foreach ( $records as $record ) { + + // --- set short record ID --- + $id = $record['ID']; + + // --- get record shifts --- + // note: shifts must be already set + $shifts = $record['shifts']; + if ( $this->debug ) { + echo 'Shifts for Record ' . esc_html( $id ) . ': ' . esc_html( print_r( $shifts, true ) ) . ''; + } + + if ( $shifts && is_array( $shifts) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $i => $shift ) { + + // 2.3.3.9: set shift ID to key + // 2.5.0: use already set key + if ( !isset( $shift['id'] ) ) { + $shift['id'] = $i; + } + + // --- make sure shift has sufficient info --- + $isdisabled = ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) ? true : false; + $shift = $this->validate_shift( $shift ); + + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + + // --- if it was not already disabled, add to shift errors --- + if ( !$isdisabled ) { + $errors[$id][] = $shift; + } + + } else { + + // --- shift is valid so continue checking --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first + $day = $shift['day']; + $thisdate = $weekdates[$day]; + $midnight = $this->to_time( $thisdate . ' 23:59:59', $timezone ) + 1; + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $start_time = $this->convert_shift_time( $start ); + $start_time = $this->to_time( $thisdate . ' ' . $start_time, $timezone ); + if ( ( '11:59:59 pm' == $end ) || ( '12:00 am' == $end ) ) { + // 2.3.2: simplify using existing midnight time + $end_time = $midnight; + } else { + $end_time = $this->convert_shift_time( $end ); + $end_time = $this->to_time( $thisdate . ' ' . $end, $timezone ); + } + $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; + $updated = $record['updated']; + + if ( $split ) { + + // --- check if show goes over midnight --- + if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { + + // --- set the shift time as is --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $id] = array( + 'ID' => $id, + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => $end, + // 'show' => $id, + 'encore' => $encore, + 'split' => false, + 'updated' => $updated, + 'shift' => $shift, + 'override' => false, + ); + + } else { + + // --- split shift for this day --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $id] = array( + 'ID' => $id, + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => '11:59:59 pm', // midnight + // 'show' => $id, + 'split' => true, + 'encore' => $encore, + 'updated' => $updated, + 'shift' => $shift, + 'real_end' => $end, + 'override' => false, + ); + + // --- split shift for next day --- + // 2.3.2: added date data for next day + $nextday = $this->get_next_day( $day ); + $nextdate = $weekdates[$nextday]; + // 2.3.2: fix midnight timestamp for sorting + if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { + $midnight = $this->to_time( $nextdate . ' 00:00:00', $timezone ); + } + $all_shifts[$nextday][$midnight . '.' . $id] = array( + 'ID' => $id, + 'day' => $nextday, + 'date' => $nextdate, + 'start' => '00:00 am', // midnight + 'end' => $end, + // 'show' => $id, + 'encore' => $encore, + 'split' => true, + 'updated' => $updated, + 'shift' => $shift, + 'real_start' => $start, + 'override' => false, + ); + } + + } else { + + // --- set the shift time as is --- + // 2.3.2: added for non-split argument + $all_shifts[$day][$start_time . '.' . $id] = array( + 'ID' => $id, + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => $end, + // 'show' => $id, + 'encore' => $encore, + 'split' => false, + 'updated' => $updated, + 'shift' => $shift, + 'override' => false, + ); + + } + } + } + } + } + } + + // --- debug point --- + if ( $this->debug ) { + $data = "Raw Shifts" . PHP_EOL . print_r( $all_shifts, true ) . PHP_EOL; + $data .= "Shift Errors" . PHP_EOL . print_r( $errors, true ) . PHP_EOL; + $this->debug_log( $data ); + } + + // --- do action to record shift error --- + do_action( 'schedule_engine_set_shift_errors', $errors, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + do_action( $context . '_set_shift_errors', $errors, $channel ); + } + + // --- filter and return --- + $all_shifts = apply_filters( 'schedule_engine_shift_data', $all_shifts, $split, $times, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $all_shifts = apply_filters( $context . '_shift_data', $all_shifts, $split, $channel, $times ); + } + return $all_shifts; + } + + // ----------- + // Sort Shifts + // ----------- + public function sort_shifts( $all_shifts, $weekdays ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- sort by start time for each day --- + // note: all_shifts keys are made unique by combining start time and show ID + // which allows them to be both be sorted and then checked for conflicts + if ( count( $all_shifts ) > 0 ) { + foreach ( $all_shifts as $day => $shifts ) { + ksort( $shifts ); + $all_shifts[$day] = $shifts; + } + } + + // --- reorder by weekdays --- + // 2.3.2: added for passing to shift checker + $sorted_shifts = array(); + foreach ( $weekdays as $weekday ) { + if ( isset( $all_shifts[$weekday] ) ) { + $sorted_shifts[$weekday] = $all_shifts[$weekday]; + } + } + + // --- debug point --- + if ( $this->debug ) { + $data = "Sorted Shifts" . PHP_EOL . print_r( $sorted_shifts, true ) . PHP_EOL; + $this->debug_log( $data ); + } + + // --- filter and return --- + $sorted_shifts = apply_filters( 'schedule_engine_sorted_shifts', $sorted_shifts, $all_shifts, $weekdays, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + apply_filters( $context . '_sorted_shifts', $sorted_shifts, $all_shifts, $weekdays, $channel, $context ); + } + return $sorted_shifts; + } + + // --------------- + // Sort Day Shifts + // --------------- + public function sort_day_shifts( $all_shifts, $today = false, $timezone = false ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + if ( !$today ) { + $now = $this->get_now(); + $timezone = $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + } + + // --- shuffle shift days so today is first day --- + // 2.3.2: use get time function for day with timezone + // 2.5.0: removed get today here as already set + $day_shifts = array(); + for ( $i = 0; $i < 7; $i ++ ) { + // 2.5.0: shorten conditional logic to one line + $day = ( 0 === $i ) ? $today : $this->get_next_day( $day ); + if ( isset( $all_shifts[$day] ) ) { + $day_shifts[$day] = $all_shifts[$day]; + } + } + + // --- filter and return --- + // 2.3.3.9: changed conflicting filter name from radio_station_show_shifts + $day_shifts = apply_filters( 'schedule_engine_day_shifts', $day_shifts, $all_shifts, $today, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $day_shifts = apply_filters( $context . '_day_shifts', $day_shifts, $all_shifts, $today, $channel ); + } + return $day_shifts; + } + + // -------------------------- + // Process Schedule Overrides + // -------------------------- + // 2.3.0: added get schedule overrides data grabber + public function process_overrides( $overrides, $start_date = false, $end_date = false, $timezone = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- loop overrides and get data --- + $override_list = array(); + foreach ( $overrides as $i => $override ) { + + // echo 'Override: ' . print_r( $override, true ) . ''; + + $override_shifts = $override['shifts']; + $show = $override['show']; + // if ( !isset( $show['title'] ) ) { + // echo 'Show! ' . print_r( $show, true ) . ''; + // } + + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { + + // --- loop override shifts --- + // 2.3.3.9: loop to allow for multiple overrides + foreach ( $override_shifts as $j => $data ) { + + // echo 'Shift Data: ' . print_r( $override, true ) . ''; + + // 2.3.3.9: ignore disabled overrides + if ( !isset( $data['disabled'] ) || ( 'yes' != $data['disabled'] ) ) { + + $date = $data['date']; + if ( '' != $date ) { + + // 2.3.2: replace strtotime with to_time for timezones + $date_time = $this->to_time( $date, $timezone ); + $inrange = true; + + // --- check if in specified date range --- + if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) + || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { + $inrange = false; + } + + // --- add the override data --- + if ( $inrange ) { + + // 2.3.2: get day from date directly + // $thisday = date( 'l', $date_time ); + $day = date( 'l', strtotime( $date ) ); + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first + $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; + $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; + $start_time = $this->convert_shift_time( $start ); + $end_time = $this->convert_shift_time( $end ); + $override_start_time = $this->to_time( $date . ' ' . $start_time, $timezone ); + $override_end_time = $this->to_time( $date . ' ' . $end_time, $timezone ); + // 2.3.2: fix for overrides ending at midnight + // 2.3.3.9: fix to use standardized operator check + if ( $override_end_time <= $override_start_time ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } + // TODO: allow for multiday overrides ? + /* if ( isset( $data['multiday'] ) && ( 'yes' == $data['multiday'] ) ) { + if ( isset( $data['enddate'] ) && ( '' != $data['enddate'] ) ) { + + } + } */ + + if ( $override_start_time < $override_end_time ) { + + // --- add the override as is --- + $override_data = array( + 'override' => $show['id'], + 'id' => $data['id'], + 'name' => $show['title'], + 'slug' => $show['slug'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => $end, + 'url' => get_permalink( $show['id'] ), + 'split' => false, + ); + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; + + } else { + + // --- split the override overnight --- + $override_data = array( + 'override' => $show['id'], + 'id' => $data['id'], + 'name' => $show['title'], + 'slug' => $show['slug'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => '11:59:59 pm', + 'real_end' => $end, + 'url' => get_permalink( $show['id'] ), + 'split' => true, + ); + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; + + // --- set the next day split shift --- + // note: these should not wrap around to start of week + // 2.3.2: use get next date/day functions + // $nextday = date( 'l', $next_date_time ); + // $nextdate = date( 'Y-m-d', $next_date_time ); + $nextdate = radio_station_get_next_date( $date ); + $nextday = radio_station_get_next_day( $day ); + + $override_data = array( + 'override' => $show['id'], + 'id' => $data['id'], + 'name' => $show['title'], + 'slug' => $show['slug'], + 'date' => $nextdate, + 'day' => $nextday, + 'real_start' => $start, + 'start' => '00:00 am', + 'end' => $end, + 'url' => get_permalink( $show['id'] ), + 'split' => true, + ); + // 2.3.3.7: set array order by start time + $override_list[$nextdate][$override_start_time] = $override_data; + } + } + } + } + } + } + } + + // 2.3.3.7: reorder overrides by sequential times + if ( count( $override_list ) > 0 ) { + foreach ( $override_list as $day => $overrides ) { + ksort( $overrides ); + $override_list[$day] = $overrides; + } + } + + // --- filter and return --- + $override_list = apply_filters( 'schedule_engine_all_overrides', $override_list, $overrides, $start_date, $end_date, $timezone, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $override_list = apply_filters( $context . '_all_overrides', $override_list, $overrides, $start_date, $end_date, $timezone, $channel ); + } + return $override_list; + } + + // -------------------- + // Get Current Schedule + // -------------------- + // 2.3.2: added optional time argument + // 2.3.3.5: added optional weekstart argument + public function get_current_schedule( $records, $overrides, $time = false, $weekstart = false, $timezone = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- get all show shifts --- + // 2.3.3: also pass time to get show_shifts function + $show_shifts = $this->get_all_shifts( $records, true, true, $time, $timezone ); + + // --- get weekdates --- + $now = $time ? $time : $this->get_now(); + $timezone = $timezone ? $timezone : $this->get_timezone(); + // $today = $this->get_time( 'l', $now, $timezone ); + // 2.3.3.5: add passthrough of optional week start argument + $weekdays = $this->get_schedule_weekdays( $weekstart, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + // 2.5.0: set an array of times data + $times = array( + 'time' => $now, + 'timezone' => $timezone, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + ); + + // 2.3.1: add empty keys to ensure overrides are checked + foreach ( $weekdays as $weekday ) { + if ( !isset( $show_shifts[$weekday] ) ) { + $show_shifts[$weekday] = array(); + } + } + + // --- debug point --- + if ( $this->debug ) { + $debug = "Shifts: " . print_r( $show_shifts, true ) . PHP_EOL; + $this->debug_log( $debug ); + } + + // --- get show overrides --- + // (from 12am this morning, for one week ahead and back) + // 2.3.1: get start and end dates from weekdays + // 2.3.2: use get time function with timezone + // 2.3.3.9: pass second argument as time may not be now + $start_date = $weekdates[$weekdays[0]]; + $end_date = $weekdates[$weekdays[6]]; + $overrides = apply_filters( 'schedule_engine_overrides', array(), $times, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $overrides = apply_filters( $context . '_overrides', $overrides, $times, $channel ); + } + $overrides = $this->process_overrides( $overrides, $start_date, $end_date, $timezone ); + + // --- debug point --- + if ( $this->debug ) { + $debug = "Now: " . $now . " - Date: " . $date . PHP_EOL; + $debug .= "Week Start Date: " . $start_date . " - Week End Date: " . $end_date . PHP_EOL; + $debug .= "Schedule Overrides: " . print_r( $overrides, true ) . PHP_EOL; + $this->debug_log( $debug ); + } + + $show_shifts = $this->combine_shifts( $show_shifts, $overrides, $times ); + + // --- filter and return --- + // 2.3.2: added time argument to filter + // 2.3.3: apply filter only once + $show_shifts = apply_filters( 'schedule_engine_current_schedule', $show_shifts, $time, $weekstart, $timezone, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $show_shifts = apply_filters( $context . '_current_schedule', $show_shifts, $time, $weekstart, $timezone, $channel ); + } + + // --- cache expiry time --- + // 2.3.2: set temporary transient if time is specified + // 2.3.3: also set global data for current schedule + if ( !isset( $expires ) ) { + $expires = 3600; + } + + // 2.5.0 do set current schedule action + do_action( 'schedule_engine_set_current_schedule', $show_shifts, $expires, $time, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + do_action( $context . '_set_current_schedule', $show_shifts, $expires, $time, $channel ); + } + + // --- process shifts --- + $show_shifts = $this->process_shifts( $show_shifts, $times ); + + return $show_shifts; + } + + // -------------- + // Combine Shifts + // -------------- + public function combine_shifts( $show_shifts, $override_list, $times ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- set variables from times --- + $time = $now = $times['time']; + $timezone = $times['timezone']; + $weekdays = $times['weekdays']; + $weekdates = $times['weekdates']; + + // --- apply overrides to the schedule --- + $debugday = 'Monday'; + if ( isset( $_REQUEST['debug-day'] ) ) { + $debugday = sanitize_text_field( $_REQUEST['debug-day'] ); + } + $done_overrides = array(); + if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { + foreach ( $show_shifts as $day => $shifts ) { + + $date = $weekdates[$day]; + if ( $this->debug ) { + echo "Override Date: " . esc_html( $date ) . PHP_EOL; + } + + // 2.3.2: reset overrides for loop + $overrides = array(); + if ( isset( $override_list[$date] ) ) { + + $overrides = $override_list[$date]; + if ( $this->debug ) { + echo "Overrides for " . esc_html( $day ) . ": " . esc_html( print_r( $overrides, true ) ) . PHP_EOL; + } + + // --- maybe reloop to insert any overrides before shows --- + if ( count( $overrides ) > 0 ) { + foreach ( $overrides as $i => $override ) { + + if ( $date == $override['date'] ) { + + // 2.3.1: added check if override already done + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + // 2.3.3.7: remove check if override already done from here + $override_start = $this->convert_shift_time( $override['start'] ); + $override_end = $this->convert_shift_time( $override['end'] ); + $override_start_time = $this->to_time( $date . ' ' . $override_start, $timezone ); + $override_end_time = $this->to_time( $date . ' ' . $override_end, $timezone ); + if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { + // 2.3.3.8: fix to add 60 seconds instead of 1 + $override_end_time = $override_end_time + 60; + } + // 2.3.2: fix for non-split overrides ending on midnight + // 2.3.3.9: added or equals to operator + if ( $override_end_time <= $override_start_time ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } + + // --- check for overlapped shift (if any) --- + // 2.3.1 added check for shift count + if ( count( $shifts ) > 0 ) { + // 2.3.3.7: change shifts variable in loop not just show_shifts + foreach ( $shifts as $start => $shift ) { + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = $this->convert_shift_time( $shift['start'] ); + $shift_end = $this->convert_shift_time( $shift['end'] ); + $start_time = $this->to_time( $date . ' ' . $shift_start, $timezone ); + $end_time = $this->to_time( $date . ' ' . $shift_end, $timezone ); + if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { + // 2.3.3.8: fix to add 60 seconds instead of 1 + $end_time = $end_time + 60; + } + // 2.3.2: fix for non-split shifts ending on midnight + // 2.3.3.9: added or equals to operator + if ( $end_time <= $start_time ) { + $end_time = $end_time + ( 24 * 60 * 60 ); + } + + if ( $day == $debugday ) { + // 2.4.0.6: fix to undefined variable warning + if ( !isset( $debugshifts ) ) { + $debugshifts = ''; + } + $debugshifts .= $day . ' Show from ' . $shift['start'] . ': ' . $start_time . ' to ' . $shift['end'] . ': ' . $end_time . PHP_EOL; + $debugshifts .= $day . ' Override from ' . $override['start'] . ': ' . $override_start_time . ' to ' . $override['end'] . ': ' . $override_end_time . PHP_EOL; + } + + // --- check if the override starts earlier than shift --- + if ( $override_start_time < $start_time ) { + + // --- check when the shift ends --- + if ( ( $override_end_time > $end_time ) + || ( $override_end_time == $end_time ) ) { + // --- overlaps so remove shift --- + if ( $day == $debugday ) { + $debugshifts .= "Removed Shift: " . print_r( $shift, true ) . PHP_EOL; + } + unset( $show_shifts[$day][$start] ); + unset( $shifts[$start] ); + } elseif ( $override_end_time > $start_time ) { + // --- add trimmed shift remainder --- + if ( $day == $debugday ) { + $debugshifts .= "Trimmed Start of Shift to " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; + } + unset( $show_shifts[$day][$start] ); + unset( $shifts[$start] ); + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; + } + + // --- add the override if not already added --- + // 2.3.3.8: removed adding of overrides here + /* if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { + $done_overrides[] = $override['date'] . '--' . $i; + if ( $day == $debugday ) { + $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; + } + $shifts[$override['start']] = $override; + $show_shifts[$day] = $shifts; + } */ + + } elseif ( $override_start_time == $start_time ) { + + // --- same start so overwrite the existing shift --- + // 2.3.1: set override done instead of unsetting override + // 2.3.3.7: remove check if override already done + // $done_overrides[] = $date . '--' . $i; + if ( $day == $debugday ) { + $debugshifts .= "Replaced Shift with Override: " . print_r( $show_shifts[$day][$start], true ) . PHP_EOL; + } + $shifts[$start] = $override; + $show_shifts[$day] = $shifts; + + // --- check if there is remainder of existing show --- + if ( $override_end_time < $end_time ) { + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; + if ( $day == $debugday ) { + $debugshifts .= "And trimmed Shift Start to " . $override['end'] . PHP_EOL; + } + } + // elseif ( $override_end_time == $end_time ) { + // --- remove exact override --- + // do nothing, already overridden + // } + + } elseif ( ( $override_start_time > $start_time ) + && ( $override_start_time < $end_time ) ) { + + $end = $shift['end']; + + // --- partial shift before override --- + if ( $day == $debugday ) { + $debugshifts .= "Trimmed Shift End to " . $override['start'] . ": " . print_r( $shift, true ) . PHP_EOL; + } + $shift['start'] = $start; + $shift['end'] = $override['start']; + $shift['trimmed'] = 'end'; + $shifts[$start] = $shift; + $show_shifts[$day] = $shifts; + + // --- add the override --- + $show_shifts[$day][$override['start']] = $override; + // 2.3.1: track done instead of unsetting + // 2.3.3.7: remove check if override already done here + // $done_overrides[] = $date . '--' . $i; + + // --- partial shift after override ---- + if ( $override_end_time < $end_time ) { + if ( $day == $debugday ) { + $debugshifts .= "And added partial Shift after " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; + } + $shift['start'] = $override['end']; + $shift['end'] = $end; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; + } + } + } + } + } + } + } + } + + // --- add directly any remaining overrides --- + // 2.3.1: fix to include standalone overrides on days + // 2.3.3.8: moved override adding to fix shift order + if ( count( $overrides ) > 0 ) { + foreach ( $overrides as $i => $override ) { + if ( $date == $override['date'] ) { + // 2.3.3.7: remove check if override already done + // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { + // $done_overrides[] = $date . '--' . $i; + $show_shifts[$day][$override['start']] = $override; + if ( $day == $debugday ) { + $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; + } + // } + } + } + } + + // --- sort the shifts using 24 hour time --- + $shifts = $show_shifts[$day]; + if ( count( $shifts ) > 0 ) { + // 2.3.2: fix to clear shift keys between days + $new_shifts = $shift_keys = array(); + $keys = array_keys( $shifts ); + foreach ( $keys as $i => $key ) { + $converted = $this->convert_shift_time( $key, 24 ); + unset( $keys[$i] ); + $keys[$key] = $shift_keys[$key] = $converted; + } + sort( $shift_keys ); + foreach ( $shift_keys as $shift_key ) { + if ( in_array( $shift_key, $keys ) ) { + $key = array_search( $shift_key, $keys ); + $new_shifts[$key] = $shifts[$key]; + } + } + $shifts = $show_shifts[$day] = $new_shifts; + } + + if ( $this->debug ) { + if ( isset( $debugshifts ) ) { + $this->debug_log( "Day Debug: " . $debugshifts . PHP_EOL ); + } + echo "Shift Keys: " . esc_html( print_r( $keys, true ) ) . PHP_EOL; + echo "Sorted Keys: " . esc_html( print_r( $shift_keys, true ) ) . PHP_EOL; + echo "Sorted Shifts: " . esc_html( print_r( $new_shifts, true ) ) . PHP_EOL; + } + + $shifts = $show_shifts[$day]; + // ksort( $shifts ); + if ( $this->debug ) { + echo "New Day Shifts: " . esc_html( print_r( $shifts, true ) ) . PHP_EOL; + // echo "Done Overrides: " . print_r( $done_overrides, true ) . PHP_EOL; + } + } + + } + + if ( $this->debug ) { + $debug = "Combined Schedule: " . esc_html( print_r( $show_shifts, true ) ). PHP_EOL; + $this->debug_log( $debug ); + } + + return $show_shifts; + } + + // -------------- + // Process Shifts + // -------------- + public function process_shifts( $show_shifts, $times ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- set variables from times --- + $time = $now = $times['time']; + $timezone = $times['timezone']; + $weekdays = $times['weekdays']; + $weekdates = $times['weekdates']; + + // --- loop all shifts to check current --- + $prev_shift = $set_prev_shift = $prev_shift_end = false; + foreach ( $show_shifts as $day => $shifts ) { + + // 2.3.1: added check for shift count + if ( count( $shifts ) > 0 ) { + foreach ( $shifts as $start => $shift ) { + + // --- check if shift is an override --- + if ( isset( $shift['override'] ) && $shift['override'] ) { + + // ---- add the override data --- + $override = radio_station_get_override_data_meta( $shift['override'] ); + $shift['show'] = $show_shifts[$day][$start]['show'] = $override; + + } else { + + // --- get (or get stored) show data --- + $show_id = $shift['ID']; + if ( isset( $radio_station_data['show-' . $show_id] ) ) { + $show = $radio_station_data['show-' . $show_id]; + } else { + $show = radio_station_get_show_data_meta( $show_id ); + $radio_station_data['show-' . $show_id] = $show; + } + unset( $show['schedule'] ); + + // --- add show data back to shift --- + $shift['show'] = $show_shifts[$day][$start]['show'] = $show; + } + + if ( !isset( $current_show ) ) { + + // --- get this shift start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = $this->convert_shift_time( $shift['start'] ); + $shift_end = $this->convert_shift_time( $shift['end'] ); + $shift_start_time = $this->to_time( $weekdates[$day] . ' ' . $shift_start, $timezone ); + $shift_end_time = $this->to_time( $weekdates[$day] . ' ' . $shift_end, $timezone ); + + // if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) { + // $nextdate = radio_station_get_time( 'date', $shift_end_time + ( 23 * 60 * 60 ) ); + // $shift_end = $nextdate[$day] . ' ' . $shift['real_end']; + // } + + // - adjust for shifts ending past midnight - + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + // --- check if this is the currently scheduled show --- + if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { + + if ( isset( $maybe_next_show ) ) { + unset( $maybe_next_show ); + } + $shift['day'] = $day; + $current_show = $shift; + + // 2.3.3: set current show to global data + // 2.3.4: set previous show shift to global and transient + // 2.3.3.8: move expires declaration earlier + $expires = $shift_end_time - $now - 1; + if ( $expires > 3600 ) { + $expires = 3600; + } + + // 2.5.0: do set current shift action + do_action( 'schedule_engine_set_current_shift', $current_show, $expires, $time, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + do_action( $context . '_set_current_shift', $current_show, $expires, $time, $channel ); + } + + /* if ( !$time ) { + $radio_station_data['current_show'] = $current_show; + if ( $prev_shift ) { + $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + $radio_station_data['previous_show'] = $prev_show; + set_transient( 'radio_station_previous_show', $prev_show, $expires ); + } + } else { + $radio_station_data['current_show_' . $time ] = $current_show; + if ( $prev_shift ) { + $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + $radio_station_data['previous_show_' . $time] = $prev_show; + set_transient( 'radio_station_previous_show_' . $time, $prev_show, $expires ); + } + } */ + + // 2.3.2: set temporary transient if time is specified + // 2.3.3: remove current show transient (as being unreliable) + /* if ( !$time ) { + set_transient( 'radio_station_current_show', $current_show, $expires ); + } else { + set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); + } */ + + } elseif ( $now > $shift_end_time ) { + + // 2.3.2: set previous shift flag + $set_prev_shift = true; + + } elseif ( ( $now < $shift_start_time ) && !isset( $maybe_next_show ) ) { + + // 2.3.2: set maybe next show + $maybe_next_show = $shift; + + } + + // --- debug point --- + if ( $this->debug ) { + $debug = 'Now: ' . $now . PHP_EOL; + $debug .= 'Date: ' . date( 'm-d H:i:s', $now ) . PHP_EOL; + $debug .= 'Shift Start: ' . $shift_start . ' (' . $shift_start_time . ')' . PHP_EOL; + $debug .= 'Shift End: ' . $shift_end . ' (' . $shift_end_time . ')' . PHP_EOL . PHP_EOL; + if ( isset ( $current_show ) ) { + $debug .= '[Current Shift] ' . print_r( $current_show, true ) . PHP_EOL; + } + $debug .= PHP_EOL; + if ( $now >= $shift_start_time ) {$debug .= "!A!";} + if ( $now < $shift_end_time ) {$debug .= "!B!";} + echo $debug; + } + + } elseif ( isset( $current_show['split'] ) && $current_show['split'] ) { + + // --- skip second part of split shift for current shift --- + // (so that it is not set as the next show) + unset( $current_show['split'] ); + + } + + // 2.3.2: change to logic to allow for no current show found + if ( !isset( $next_show ) ) { + + // --- get shift times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_time = $this->convert_shift_time( $shift['start'] ); + $end_time = $this->convert_shift_time( $shift['end'] ); + $shift_start_time = $this->to_time( $weekdates[$day] . ' ' . $shift_start, $timezone ); + $shift_end_time = $this->to_time( $weekdates[$day] . ' ' . $shift_end, $timezone ); + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + if ( isset( $current_show ) || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { + + // --- set next show --- + // 2.3.2: set date for widget + $next_show['date'] = $weekdates[$day]; + $next_show = $shift; + + } + } + + // 2.3.2: maybe set previous shift end value + if ( $set_prev_shift ) { + $prev_shift_end = $shift_end_time; + } + + // 2.3.4: set previous shift value + $prev_shift = $shift; + + } + } + } + + // --- maybe set next show transient --- + // 2.3.2: check for (possibly first) next show found + if ( !isset( $next_show ) && isset( $maybe_next_show ) ) { + $next_show = $maybe_next_show; + } + if ( isset( $next_show ) ) { + + // 2.3.2: recombine split shift end times + $shift = $next_show; + if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { + $next_show['end'] = $shift['real_end']; + unset( $next_show['split'] ); + } + + // 2.3.2: added check that expires is set + $next_expires = $shift_end_time - $now - 1; + if ( isset( $expires ) && ( $next_expires > ( $expires + 3600 ) ) ) { + $next_expires = $expires + 3600; + } + + // 2.5.0: do next shift record action + do_action( $context . '_set_next_shift', $next_show, $next_expires, $time, $channel ); + + // 2.3.2: set temporary transient if time is specified + /* if ( !$time ) { + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } else { + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); + } */ + } + + if ( $this->debug ) { + if ( !isset( $current_show ) ) { + echo 'Current Show Not Found.'; + } else { + echo 'Current Show: ' . esc_html( print_r( $current_show, true ) ) . ''; + } + } + + // --- get next show if we did not find one --- + if ( !isset( $next_show ) ) { + + if ( $this->debug ) { + echo "No Next Show Found. Rechecking..."; + } + + // --- fallback to using get next shift function --- + // 2.3.2: added time argument to next shows retrieval + // 2.3.2: set next show transient within next shows function + $next_shows = $this->get_next_shift( $show_shifts, $time, $timezone ); + + } + + // --- debug point --- + if ( $this->debug ) { + $debug = ''; + if ( isset( $current_show ) ) { + $debug .= "Current Show: " . print_r( $current_show, true ) . PHP_EOL; + } + if ( isset( $next_show ) ) { + $debug .= "Next Show: " . print_r( $next_show, true ) . PHP_EOL; + } + $next_shows = $this->get_next_shifts( 5, $show_shifts, $time, $timezone ); + $debug .= "Next 5 Shows: " . print_r( $next_shows, true ) . PHP_EOL; + $this->debug_log( $debug ); + } + + return $show_shifts; + } + + // ------------------ + // Get Current Shifts + // ------------------ + // 2.3.0: added new get current show function + // 2.3.2: added optional time argument + public function get_current_shifts( $schedule_shifts = false, $time = false, $timezone = false ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + $current_shift = $previous_shift = $next_shift = false; + + // --- get all scheduled shifts --- + if ( !$schedule_shifts ) { + if ( !$time ) { + $schedule_shifts = $this->get_all_shifts(); + } else { + $schedule_shifts = $this->get_all_shifts( $time ); + } + } + + // --- get current time --- + $now = $time ? $time : $this->get_now(); + + // --- get schedule for time --- + // 2.3.3: use weekday name instead of number (w) + // 2.3.3: add fix to start from previous day + $today = $this->get_time( 'l', $now, $timezone ); + $yesterday = $this->get_previous_day( $today ); + $weekdays = $this->get_schedule_weekdays( $yesterday ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + + if ( $this->debug ) { + echo ''; + echo 'Finding Current Show from ' . esc_html( $yesterday ) . "\n"; + echo 'Weekdays: ' . esc_html( print_r( $weekdays, true ) ) . "\n"; + echo 'Weekdates: ' . esc_html( print_r( $weekdates, true ) ) . "\n"; + echo ''; + } + + // --- loop shifts to get current show --- + $current_split = $prev_show = false; + foreach ( $weekdays as $day ) { + if ( isset( $schedule_shifts[$day] ) ) { + $shifts = $schedule_shifts[$day]; + foreach ( $shifts as $start => $shift ) { + + // --- get this shift start and end times --- + $shift_start = $this->convert_shift_time( $shift['start'] ); + $shift_end = $this->convert_shift_time( $shift['end'] ); + $shift_start_time = $this->to_time( $weekdates[$day] . ' ' . $shift_start, $timezone ); + $shift_end_time = $this->to_time( $weekdates[$day] . ' ' . $shift_end, $timezone ); + // 2.3.3: fix for shifts split over midnight + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + // if ( $this->debug ) { + if ( $this->debug ) { + echo ''; + echo 'Now: ' . esc_html( $now ) . ' - Shift Start: ' . esc_html( $shift_start_time ) . ' - Shift End: ' . esc_html( $shift_end_time ) . "\n"; + echo 'Shift: ' . esc_html( print_r( $shift, true ) ) . "\n"; + } + + // --- set current show --- + // 2.3.3: get current show directly and remove transient + // 2.4.0.6: fix to add equal to operator for start time + if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { + + if ( $this->debug ) { + echo '^^^ Current ^^^' . PHP_EOL; + } + // --- recombine possible split shift to set current show --- + $current_shift = $shift; + + // 2.3.4: also set previous shift data + if ( $prev_shift ) { + + $previous_shift = $prev_shift; + $expires = $shift_end_time - $now - 1; + + do_action( 'schedule_engine_previous_shift', $previous_shift, $expires, $time, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + do_action( $context . '_previous_shift', $previous_shift, $expires, $time, $channel ); + } + + /* $previous_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + if ( !$time ) { + $radio_station_data['previous_show'] = $previous_show; + set_transient( 'radio_station_previous_show', $previous_show, $expires ); + } else { + $radio_station_data['previous_show_' . $time] = $previous_show; + set_transient( 'radio_station_previous_show_' . $time, $previous_show, $expires ); + } */ + + } + + /* if ( isset( $current_show['split'] ) && $current_show['split'] ) { + if ( isset( $current_show['real_start'] ) ) { + // 2.3.3: second shift half so set to previous day and date + $current_show['day'] = radio_station_get_previous_day( $shift['day'] ); + $current_show['date'] = radio_station_get_previous_date( $shift['date'] ); + $current_show['start'] = $current_show['real_start']; + } elseif ( isset( $current_show['real_end'] ) ) { + $current_show['end'] = $current_show['real_end']; + } + } */ + } + + if ( $this->debug ) { + echo '' . PHP_EOL; + } + + // 2.3.4: store previous shift + $prev_shift = $shift; + } + } + } + + // --- filter current show --- + // 2.3.2: added time argument to filter + $current_shifts = array( + 'current' => $current_shift, + 'previous' => $previous_shift, + // 'next' => $next_shift, + ); + $current_shifts = apply_filters( 'schedule_engine_current_shifts', $current_shifts, $time, $schedule_shifts, $timezone ); + return $current_shifts; + } + + // ----------------- + // Get Current Shift + // ----------------- + public function get_current_shift( $schedule_shifts = false, $time = false, $timezone = false ) { + // TODO: check for cached current shift? + $current_shifts = $this->get_current_shifts( $schedule_shifts, $time, $timezone ); + $current_shift = $current_shifts['current']; + return $current_shift; + } + + // ------------------ + // Get Previous Shift + // ------------------ + public function get_previous_shift( $schedule_shifts = false, $time = false, $timezone = false ) { + // TODO: check for cached previous shift? + if ( !$schedule_shifts ) { + if ( !$time ) { + $schedule_shifts = $this->get_all_shifts(); + } else { + $schedule_shifts = $this->get_all_shifts( $time ); + } + } + $current_shifts = $this->get_current_shifts( $schedule_shifts, $time, $timezone ); + $previous_shift = $current_shifts['previous']; + return $previous_shift; + } + + // -------------- + // Get Next Shift + // -------------- + public function get_next_shift( $schedule_shifts = false, $time = false, $timezone = false ) { + if ( !$schedule_shifts ) { + if ( !$time ) { + $schedule_shifts = $this->get_all_shifts(); + } else { + $schedule_shifts = $this->get_all_shifts( $time ); + } + } + // $current_shifts = $this->get_current_shifts( $schedule_shifts, $time, $timezone ); + // $next_shift = $current_shifts['next']; + $next_shifts = $this->get_next_shifts( 1, $schedule_shifts, $time, $timezone ); + $next_shift = ( is_array( $next_shifts ) && ( count( $next_shifts ) > 0 ) ) ? $next_shifts[0] : false; + return $next_shift; + } + + // ------------------ + // Get Next Scheduled + // ------------------ + // 2.3.0: added new get next shows function + // 2.3.2: added optional time argument + public function get_next_shifts( $limit = 3, $scheduled_shifts = false, $time = false, $timezone = false ) { + + // --- get all show shifts --- + // (this check is needed to prevent an endless loop!) + if ( !$scheduled_shifts ) { + if ( !$time ) { + $scheduled_shifts = $this->get_all_shifts(); + } else { + $scheduled_shifts = $this->get_all_shifts( $time ); + } + } + + // --- loop (remaining) shifts to add show data --- + $next_shows = array(); + // 2.3.2: maybe set provided time as now + $now = $time ? $time : $this->get_now(); + $timezone = $timezone ? $timezone : $this->get_timezone(); + + // 2.3.2: use get time function with timezone + // 2.3.2: fix to pass week day start as numerical (w) + // 2.3.3: revert to passing week day start as day (l) + // 2.3.3: added fix to start from previous day + $today = $this->get_time( 'l', $now, $timezone ); + $yesterday = $this->get_previous_day( $today ); + $weekdays = $this->get_schedule_weekdays( $yesterday ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + + if ( $this->debug ) { + echo ''; + echo 'Next Shows from ' . esc_html( $yesterday ) . "\n"; + echo 'Weekdays: ' . esc_html( print_r( $weekdays ) ) . "\n"; + echo 'Weekdates: ' . esc_html( print_r( $weekdates ) ) . "\n"; + echo ''; + } + + // --- loop shifts to find next shows --- + $current_split = false; + foreach ( $weekdays as $day ) { + if ( isset( $scheduled_shifts[$day] ) ) { + $shifts = $scheduled_shifts[$day]; + foreach ( $shifts as $start => $shift ) { + + // --- get this shift start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = $this->convert_shift_time( $shift['start'] ); + $shift_end = $this->convert_shift_time( $shift['end'] ); + $shift_start_time = $this->to_time( $weekdates[$day] . ' ' . $shift_start, $timezone ); + $shift_end_time = $this->to_time( $weekdates[$day] . ' ' . $shift_end, $timezone ); + + if ( $this->debug ) { + echo ''; + echo 'Next? ' . esc_html( $now ) . ' - ' . esc_html( $shift_start_time ) . ' - ' . esc_html( $shift_end_time ) . "\n"; + echo 'Shift: ' . esc_html( print_r( $shift, true ) ) . "\n"; + echo '' . "\n"; + } + + // --- set current show --- + // 2.3.2: set current show transient + // 2.3.3: remove current show transient + + // --- check if show is upcoming --- + if ( $now < $shift_start_time ) { + + // --- reset skip flag --- + $skip = false; + + if ( $current_split ) { + + $skip = true; + $current_split = false; + + } elseif ( isset( $shift['split'] ) && $shift['split'] ) { + + // --- dedupe for shifts split overnight --- + if ( isset( $shift['real_end'] ) ) { + $shift['end'] = $shift['real_end']; + $current_split = true; + } elseif ( isset( $shift['real_start'] ) ) { + // 2.3.3: skip this shift instead of setting + // (because second half of current show!) + $skip = true; + } + + } else { + // --- reset split shift flag --- + $current_split = false; + } + + if ( !$skip ) { + + // --- add to next shows data --- + // 2.3.2: set date for widget display + $shift['date'] = $weekdates[$day]; + $next_shows[] = $shift; + if ( $this->debug ) { + echo 'Next Shows: ' . esc_html( print_r( $next_shows, true ) ) . '' . "\n"; + } + + // --- return if we have reached limit --- + if ( count( $next_shows ) == $limit ) { + $next_shows = apply_filters( 'schedule_engine_next_scheduled', $next_shows, $limit, $scheduled_shifts, $time, $timezone ); + return $next_shows; + } + } + } + } + } + } + + // --- filter and return --- + $next_shows = apply_filters( 'schedule_engine_next_scheduled', $next_shows, $limit, $scheduled_shifts, $time, $timezone ); + return $next_shows; + } + + + // ---------------------- + // === Shift Checking === + // ---------------------- + + // ------------------------- + // Schedule Conflict Checker + // ------------------------- + // (checks all existing show shifts for schedule) + // 2.3.0: added show shift conflict checker + public function check_shifts( $all_shifts, $times = false ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + // TODO: check for start of week and end of week shift conflicts? + + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts + if ( $times ) { + $now = $times['now']; + $timezone = $times['timezone']; + $today = $times['today']; + $weekdays = $times['weekdays']; + $weekdates = $times['weekdates']; + } else { + $now = $this->get_now(); + $timezone = $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + $weekdays = $this->get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + } + + // echo "*****TIMEZONE*****: " . $timezone; + $conflicts = $checked_shifts = array(); + if ( count( $all_shifts ) > 0 ) { + $prev_shift = $prev_prev_shift = false; + // foreach ( $all_shifts as $day => $shifts ) { + foreach ( $weekdays as $day ) { + + if ( isset( $all_shifts[$day] ) ) { + $shifts = $all_shifts[$day]; + + // --- get previous and next days for comparisons --- + // 2.3.2: fix to use week date schedule + $thisdate = $weekdates[$day]; + $date_time = $this->to_time( $weekdates[$day] . ' 00:00', $timezone ); + + // --- check for conflicts (overlaps) --- + foreach ( $shifts as $key => $shift ) { + + // --- set first shift checked --- + // 2.3.2: added for checking against last shift + if ( !isset( $first_shift ) ) { + $first_shift = $shift; + } + $last_shift = $shift; + + // --- reset shift switches --- + $set_shift = true; + $conflict = false; + $disabled = ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) ? true : false; + + // --- account for split midnight times --- + // 2.3.2: replace strtotime with to_time for timezones + if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { + $start_time = $this->to_time( $thisdate . ' 00:00', $timezone ); + } else { + $shift_start = $this->convert_shift_time( $shift['start'] ); + $start_time = $this->to_time( $thisdate . ' ' . $shift_start, $timezone ); + } + if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { + $end_time = $this->to_time( $thisdate . ' 11:59:59', $timezone ) + 1; + } else { + $shift_end = $this->convert_shift_time( $shift['end'] ); + $end_time = $this->to_time( $thisdate . ' ' . $shift_end, $timezone ); + } + + if ( false != $prev_shift ) { + + // note: previous shift start and end times set in previous loop iteration + if ( $this->debug ) { + echo "Shift Date: " . esc_html( $thisdate ) . " - Day: " . esc_html( $day ) . " - Time: " . esc_html( $date_time ) . "\n"; + $prevdata = $prev_shift['shift']; + $prevday = $prev_shift['day']; + $prevdate = $prev_shift['date']; + echo "Previous Shift Date: " . esc_html( $prevdate ) . " - Shift Day: " . esc_html( $prevday ) . "\n"; + echo "Shift: " . esc_html( print_r( $shift, true ) ); + echo "Previous Shift: " . esc_html( print_r( $prev_shift, true ) ); + } + + // --- detect shift conflicts --- + // (and maybe *attempt* to fix them up) + if ( isset( $prev_start_time ) && ( $start_time == $prev_start_time ) ) { + if ( $shift['split'] || $prev_shift['split'] ) { + $conflict = 'overlap'; + if ( $shift['split'] && $prev_shift['split'] ) { + // - need to compare start times on previous day - + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $data = $shift['shift']; + $real_start = $this->convert_shift_time( $data['real_start'] ); + $shiftdate = $this->get_previous_date( $thisdate ); + // $real_start_time = radio_station_to_time( $prevdate . ' ' . $real_start ); + $real_start_time = $this->to_time( $shiftdate . ' ' . $real_start, $timezone ); + + // 2.3.2: fix to calculation of previous shift day start time + $prevdata = $prev_shift['shift']; + // $prevday = $prevdata['day']; + // $prevdate = radio_station_get_previous_date( $thisdate, $prevday ); + $prevdate = $prev_shift['date']; + $prev_real_start = $this->convert_shift_time( $prevdata['real_start'] ); + $prev_real_start_time = $this->to_time( $prevdate . ' ' . $prev_real_start, $timezone ); + + // --- compare start times --- + if ( $real_start_time > $prev_real_start_time ) { + // - current shift started later (overwrite from midnight) - + $set_shift = true; + } elseif ( $real_start_time == $prev_real_start_time ) { + // - do not duplicate, already recorded - + $conflict = false; + // - total overlap, check last updated post time - + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; + } + } + } elseif ( $shift['split'] ) { + // - the current shift has been split overnight - + // assume previous shift is correct (ignore new shift time) + $set_shift = false; + } elseif ( $prev_shift['split'] ) { + // the previous shift has been split overnight + // so we will assume the new shift start is correct + // (overwrites previous shift from midnight key) + $set_shift = true; + } + } else { + $conflict = 'same_start'; + // - we do not know which of these is correct - + // no solution here, so check most recent last updated time + // we will assume (without certainty) most recent is correct + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; + } + } + } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { + + // 2.5.0: fix to missing variable operator on prev_end_time + if ( ( $end_time > $prev_start_time ) || ( $end_time > $prev_end_time ) ) { + + // --- set the previous shift end time to current shift start --- + $conflict = 'overlap'; + + // --- modify only if this shift is not disabled --- + if ( !$disabled ) { + // 2.3.2: variable type fix (from checked_shift) + // 2.3.2: fix for midnight starting aplit shifts + // 2.3.2: set checked shifts with day key directly + if ( '00:00 am' == $prev_shift['start'] ) { + $prev_shift['start'] = '12:00 am'; + } + $checked_shifts[$day][$prev_shift['start']]['end'] = $shift['start']; + $checked_shifts[$day][$prev_shift['start']]['trimmed'] = true; + + if ( $this->debug ) { + echo "Previous Previous Shift: " . esc_html( print_r( $prev_prev_shift, true ) ); + } + + // --- fix for real end of first part of previous split shift --- + if ( isset( $prev_shift['split'] ) && $prev_shift['split'] && isset( $prev_shift['real_start'] ) ) { + if ( isset( $prev_prev_shift ) && isset( $prev_prev_shift['split'] ) && $prev_prev_shift['split'] ) { + $checked_shifts[$prev_prev_shift['start']]['real_end'] = $shift['start']; + $checked_shifts[$prev_prev_shift['start']]['trimmed'] = true; + } + } + } + + // --- conflict debug output --- + if ( $this->debug ) { + $debug = "Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; + $debug .= '[ ' . $this->get_time( "m-d l H:i", $start_time ) . ' ]'; + $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; + $debug .= '[ ' . $this->get_time( "m-d l H:i", $prev_end_time ) . ' ]'; + // $debug .= "Shift: " . print_r( $shift, true ); + // $debug .= "Previous Shift: " . print_r( $prev_shift, true ); + $this->debug_log( $debug ); + } + } + } + } + + // --- maybe store shift conflict data --- + if ( $conflict ) { + + // ---- set short shift time data --- + $shift_start = $shift['shift']['start_hour'] . ':' . $shift['shift']['start_min'] . $shift['shift']['start_meridian']; + $shift_end = $shift['shift']['end_hour'] . ':' . $shift['shift']['end_min'] . $shift['shift']['end_meridian']; + $prev_shift_start = $prev_shift['shift']['start_hour'] . ':' . $prev_shift['shift']['start_min'] . $prev_shift['shift']['start_meridian']; + $prev_shift_end = $prev_shift['shift']['end_hour'] . ':' . $prev_shift['shift']['end_min'] . $prev_shift['shift']['end_meridian']; + + // --- store conflict for this shift --- + // $conflicts[$shift['show']][] = array( + $conflicts[$shift['ID']][] = array( + // 'show' => $shift['show'], + 'ID' => $shift['ID'], + 'day' => $shift['shift']['day'], + 'start' => $shift_start, + 'end' => $shift_end, + 'disabled' => $disabled, + // 'with_show' => $prev_shift['show'], + 'with_show' => $prev_shift['ID'], + 'with_day' => $prev_shift['shift']['day'], + 'with_start' => $prev_shift_start, + 'with_end' => $prev_shift_end, + 'with_disabled' => $prev_disabled, + 'conflict' => $conflict, + 'duplicate' => false, + ); + + // --- store for previous shift only if a different show --- + // if ( $shift['show'] != $prev_shift['show'] ) { + if ( $shift['ID'] != $prev_shift['ID'] ) { + $conflicts[$prev_shift['ID']][] = array( + // 'show' => $prev_shift['show'], + 'ID' => $shift['ID'], + 'day' => $prev_shift['shift']['day'], + 'start' => $prev_shift_start, + 'end' => $prev_shift_end, + 'disabled' => $prev_disabled, + // 'with_show' => $shift['show'], + 'with_show' => $shift['ID'], + 'with_day' => $shift['shift']['day'], + 'with_start' => $shift_start, + 'with_end' => $shift_end, + 'with_disabled' => $disabled, + 'conflict' => $conflict, + 'duplicate' => true, + ); + } + } + + // --- set current shift to previous for next iteration --- + $prev_start_time = $start_time; + $prev_end_time = $end_time; + if ( $prev_shift ) { + $prev_prev_shift = $prev_shift; + } + $prev_shift = $shift; + $prev_disabled = $disabled; + + // --- set the now checked shift data --- + // (...but only if not disabled!) + if ( $set_shift && !$disabled ) { + // - no longer need shift and post updated times - + // 2.3.3.9: keep shift ID with schedule data + $shift['id'] = $shift['shift']['id']; + unset( $shift['shift'] ); + unset( $shift['updated'] ); + if ( '00:00 am' == $shift['start'] ) { + $shift['start'] = '12:00 am'; + } + // 2.3.2: set checked shifts with day key directly + $checked_shifts[$day][$shift['start']] = $shift; + } + } + } + + // --- set checked shifts for day --- + // 2.3.2: set checked shifts with day key directly + // $all_shifts[$day] = $checked_shifts; + } + } + + // --- check last shift against first shift --- + // 2.3.2: added for possible overlap (split by weekly schedule dates) + if ( isset( $last_shift ) && ( $last_shift != $first_shift ) ) { + + // --- use days for different weeks to compare --- + $l_shift_start = $this->convert_shift_time( $last_shift['start'] ); + $l_shift_end = $this->convert_shift_time( $last_shift['end'] ); + $last_shift_start = $this->to_time( $last_shift['day'] . ' ' . $l_shift_start, $timezone ); + $last_shift_end = $this->to_time( $last_shift['day'] . ' ' . $l_shift_end, $timezone ); + if ( $last_shift_end < $last_shift_start ) { + $last_shift_end = $last_shift_end + ( 24 * 60 * 60 ); + } + + $f_shift_start = $this->convert_shift_time( $first_shift['start'] ); + $f_shift_end = $this->convert_shift_time( $first_shift['end'] ); + $first_shift_start = $this->to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_start, $timezone ); + $first_shift_end = $this->to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_end, $timezone ); + if ( $first_shift_end < $first_shift_start ) { + $first_shift_end = $first_shift_end + ( 24 * 60 * 60 ); + } + + if ( $this->debug ) { + echo 'Last Shift End: ' . esc_html( $last_shift['day'] ) . ' ' . esc_html( $l_shift_end ) . ' - (' . esc_html( $last_shift_end ) . ')' . "\n"; + echo 'First Shift Start: ' . esc_html( $first_shift['day'] ) . ' ' . $f_shift_start . ' - (' . $first_shift_start . ')' . "\n"; + } + + // --- end of the week overlap check --- + // 2.3.3.9: fix to incorrect overlap logic + if ( $last_shift_end > $first_shift_start ) { + // if ( $last_shift_start < $first_shift_end ) { + + // --- record a conflict --- + if ( $this->debug ) { + echo "First/Last Shift Overlap Conflict" . "\n"; + echo "First Shift: " . esc_html( print_r( $first_shift, true ) ) . "\n"; + echo "Last Shift: " . esc_html( print_r( $last_shift, true ) ) . "\n"; + } + + /* + // --- store conflict for this shift --- + $conflicts[$first_shift['show']][] = array( + 'show' => $first_shift['show'], + 'day' => $first_shift['shift']['day'], + 'start' => $first_shift_start, + 'end' => $first_shift_end, + 'disabled' => $first_shift['shift']['disabled'], + 'with_show' => $last_shift['show'], + 'with_day' => $last_shift['shift']['day'], + 'with_start' => $last_shift_start, + 'with_end' => $last_shift_end, + 'with_disabled' => $last_shift['shift']['disabled'], + 'conflict' => 'overlap', + 'duplicate' => false, + ); + + // --- store for other shift if different show --- + if ( $first_shift['show'] != $last_shift['show'] ) { + $conflicts[$last_shift['show']][] = array( + 'show' => $last_shift['show'], + 'day' => $last_shift['shift']['day'], + 'start' => $last_shift_start, + 'end' => $last_shift_end, + 'disabled' => $last_shift['shift']['disabled'], + 'with_show' => $first_shift['show'], + 'with_day' => $first_shift['shift']['day'], + 'with_start' => $first_shift_start, + 'with_end' => $first_shift_end, + 'with_disabled' => $first_shift['shift']['disabled'], + 'conflict' => 'overlap', + 'duplicate' => true, + ); + } */ + } + } + + // --- do shift conflict action --- + do_action( 'schedule_engine_set_shift_conflicts', $conflicts, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + do_action( $context . '_set_shift_conflicts', $conflicts, $channel ); + } + + // --- return checked results --- + return $checked_shifts; + } + + // ------------- + // Shift Checker + // ------------- + // (checks shift being saved against other shows) + public function check_shift( $record_id, $shift, $scope = 'all', $all_shifts, $times = false ) { + + // --- set channel and context --- + $channel = $this->channel; + $context = $this->context; + + // 2.3.2: bug out if day is empty + if ( '' == $shift['day'] ) { + return false; + } + + // --- convert days to dates for checking --- + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts + if ( $times ) { + $now = $times['now']; + $timezone = $times['timezone']; + $today = $times['today']; + $weekdays = $times['weekdays']; + $weekdates = $times['weekdates']; + } else { + $now = $this->get_now(); + $timezone = $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + $weekdays = $this->get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + } + + // --- get shows to check against via scope --- + $check_shifts = array(); + if ( 'all' == $scope ) { + $check_shifts = $all_shifts; + } elseif ( 'other' == $scope ) { + // --- check only against other shifts --- + // 2.5.0: changed scope from shows to other + foreach ( $all_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $start => $day_shift ) { + // --- ...so remove (skip) any shifts for this record --- + // 2.5.0: changed show key to ID key + // if ( $day_shift['show'] != $record_id ) { + if ( $day_shift['ID'] != $record_id ) { + $check_shifts[$day][$start] = $day_shift; + } + } + } + } + + // 2.3.2: to doubly ensure shifts are set in schedule order + $sorted_shifts = array(); + foreach ( $weekdays as $weekday ) { + if ( isset( $check_shifts[$weekday] ) ) { + $sorted_shifts[$weekday] = $check_shifts[$weekday]; + } + } + $check_shifts = $sorted_shifts; + + // --- get shift start and end time --- + // 2.3.2: fix to convert to 24 hour times first + // 2.3.3.9: set shift start and end to prevent undefined index warning later + $shift['start'] = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + $shift['end'] = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; + $start_time = $this->convert_shift_time( $shift['start'] ); + $end_time = $this->convert_shift_time( $shift['end'] ); + + // 2.3.2: use next week day instead of date + $shift_start_time = $this->to_time( $weekdates[$shift['day']] . ' ' . $start_time, $timezone ); + $shift_end_time = $this->to_time( $weekdates[$shift['day']] . ' ' . $end_time, $timezone ); + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + if ( $this->debug ) { + echo "Checking Shift for Show " . $record_id . ": "; + echo $shift['day'] . " - " . $weekdates[$shift['day']] . " - " . $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + echo "(" . $shift_start_time . ")"; + echo " to " . $weekdates[$shift['day']] . " - " . $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + echo "(" . $shift_end_time . ")" . PHP_EOL; + } + + // --- check for conflicts with other show shifts --- + $conflicts = array(); + foreach ( $check_shifts as $day => $day_shifts ) { + // 2.3.2: removed day match check + // if ( $day == $shift['day'] ) { + foreach ( $day_shifts as $i => $day_shift ) { + + if ( !isset( $first_shift ) ) { + $first_shift = $day_shift; + } + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour times first + $day_shift_start = $this->convert_shift_time( $day_shift['start'] ); + $day_shift_end = $this->convert_shift_time( $day_shift['end'] ); + + // 2.3.2: use next week day instead of date + $day_shift_start_time = $this->to_time( $day_shift['date'] . ' ' . $day_shift_start, $timezone ); + $day_shift_end_time = $this->to_time( $day_shift['date'] . ' ' . $day_shift_end, $timezone ); + // 2.3.2: adjust for midnight with change to use non-split shifts + // 2.3.3.9: added or equals to operator + if ( $day_shift_end_time <= $day_shift_start_time ) { + $day_shift_end_time = $day_shift_end_time + ( 24 * 60 * 60 ); + } + + // --- ignore if this is the same shift we are checking --- + $check_shift = true; + // if ( $day_shift['show'] == $record_id ) { + if ( $day_shift['ID'] == $record_id ) { + // ? only ignore same shift not same show ? + // if ( ( $day_shift_start_time == $shift_start_time ) && ( $day_shift_end_time == $shift_end_time ) ) { + $check_shift = false; + // } + } + + if ( $check_shift ) { + + if ( $this->debug ) { + echo "...with Shift for Show " . esc_html( $day_shift['show'] ) . ": "; + echo esc_html( $day_shift['day'] ) . " - " . esc_html( $day_shift['date'] ) . " - " . esc_html( $day_shift['start'] ) . " (" . esc_html( $day_shift_start_time ) . ")"; + echo " to " . esc_html( $day_shift['end'] ) . " (" . esc-html( $day_shift_end_time ) . ")" . "\n"; + } + + // 2.3.2: improved shift checking logic + // 2.3.3.6: separated logic for conflict match code + $conflict = false; + if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { + // if the new shift starts before existing shift but ends after existing shift starts + $conflict = 'start overlap'; + } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { + // ...or starts before but ends after the existing shift end time + $conflict = 'blockout overlap'; + } elseif ( ( $shift_start_time == $day_shift_start_time ) ) { + // ...or new shift starts at the same time as the existing shift + $conflict = 'equal start time'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { + // ...or if the new shift starts after existing shift and ends before it ends + $conflict = 'internal overlap'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { + // ...or the new shift starts after the existing shift but before it ends + $conflict = 'end overlap'; + } + if ( $conflict ) { + // --- if there is a shift overlap conflict --- + $conflicts[] = $day_shift; + if ( $this->debug ) { + echo '^^^ CONFLICT ( ' . esc-html( $conflict ) . ' ) ^^^' . "\n"; + } + } + } + } + // } + } + + // --- recheck for first shift overlaps --- + // (not implemented as not needed) + /* if ( isset( $first_shift ) ) { + // --- check for first shift overlap using next week --- + $shift_start = $this->convert_shift_time( $first_shift['start'] ); + $shift_end = $this->convert_shift_time( $first_shift['end'] ); + $first_shift_start_time = $this->to_time( $first_shift['date'] . ' ' . $shift_start, $timezone ) + ( 7 * 24 * 60 * 60 ); + $first_shift_end_time = $this->to_time( $first_shift['date'] . ' ' . $shift_end, $timezone ) + ( 7 * 24 * 60 * 60 ); + + if ( $this->debug ) { + echo "...with First Shift for Show " . $first_shift['show'] . ": "; + echo $first_shift['day'] . " - " . $first_shift['date'] . " - " . $first_shift['start'] . " (" . $first_shift_start_time . ")"; + echo " to " . $first_shift['end'] . " (" . $first_shift_end_time . ")" . PHP_EOL; + } + + if ( ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_start_time ) ) + || ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_end_time ) ) + || ( $shift_start_time == $first_shift_start_time ) + || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_end_time < $first_shift_end_time ) ) + || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_start_time < $first_shift_end_time ) ) ) { + $conflicts[] = $first_shift; + if ( $this->debug ) { + echo "^^^ CONFLICT ^^^" . PHP_EOL; + } + } + } */ + + // --- recheck for last shift --- + // (for date based schedule overflow rechecking) + if ( isset( $day_shift ) ) { + + // 2.3.3.6: added check to not last check shift against itself + // TODO: check possible re-occurrence of undefined index 'start' warning here ? + if ( ( $day_shift['show'] != $record_id ) + || ( $day_shift['day'] != $shift['day'] ) + || ( $day_shift['start'] != $shift['start'] ) + || ( $day_shift['end'] != $shift['end'] ) ) { + + // --- check for new shift overlap using next week --- + $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); + $shift_end_time = $shift_end_time + ( 7 * 24 * 60 * 60 ); + + if ( $this->debug ) { + echo "...with Last Shift (using next week):" . PHP_EOL; + // echo radio_station_get_time( 'date', $shift_start_time ) . " - " . $shift['start'] . " (" . $shift_start_time . ")"; + // echo " to " . $shift['end'] . " (" . $shift_end_time . ")" . PHP_EOL; + echo $day_shift['day'] . " - " . $this->get_time( 'date', $day_shift_start_time ); + echo " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; + echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; + } + + // 2.3.3.6: separated logic for conflict match code + $conflict = false; + if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { + $conflict = 'start overlap'; + } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { + $conflict = 'blockout overlap'; + } elseif ( $shift_start_time == $day_shift_start_time ) { + $conflict = 'equal start time'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { + $conflict = 'internal overlap'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { + $conflict = 'end overlap'; + } + if ( $conflict ) { + $conflicts[] = $day_shift; + if ( $this->debug ) { + echo '^^^ CONFLICT ( ' . $conflict . ') ^^^' . PHP_EOL; + } + } + } + } + + if ( count( $conflicts ) == 0 ) { + return false; + } + + return $conflicts; + } + + // ------------------ + // New Shifts Checker + // ------------------ + // (checks show shifts for conflicts with same show) + public function check_new_shifts( $new_shifts, $weekdates = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- debug point --- + if ( $this->debug ) { + $data = "New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; + } + + // --- convert days to dates for checking --- + if ( !$weekdates ) { + $now = $this->get_now(); + $timezone = $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + $weekdays = $this->get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + } + + // --- double loop shifts to check against others --- + $new_shifts_in = $new_shifts; + foreach ( $new_shifts as $i => $shift_a ) { + + if ( '' != $shift_a['day'] ) { + + // --- get shift A start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_a_start = $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; + $shift_a_end = $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; + $shift_a_start = $this->convert_shift_time( $shift_a_start ); + $shift_a_end = $this->convert_shift_time( $shift_a_end ); + + // 2.3.2: use next week day instead of date + $shift_a_start_time = $this->to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_start ); + $shift_a_end_time = $this->to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); + // $shift_a_start_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_start ); + // $shift_a_end_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_end ); + // 2.3.3.9: added or equals to operator + if ( $shift_a_end_time <= $shift_a_start_time ) { + $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); + } + + // --- debug point --- + if ( $this->debug ) { + $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; + $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; + $data .= "Shift A Start: " . $a_start . ' - Shift A End: ' . $a_end . "\n"; + } + + foreach ( $new_shifts as $j => $shift_b ) { + + if ( $i != $j ) { + + if ( $this->debug ) { + $data .= $i . ' ::: ' . $j . "\n"; + } + + if ( '' != $shift_b['day'] ) { + // --- get shift B start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + $shift_b_start = $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; + $shift_b_end = $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; + $shift_b_start = $this->convert_shift_time( $shift_b_start ); + $shift_b_end = $this->convert_shift_time( $shift_b_end ); + + // 2.3.2: use next week day instead of date + $shift_b_start_time = $this->to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_start, $timezone ); + $shift_b_end_time = $this->to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end, $timezone ); + // 2.3.3.9: added or equals to operator + if ( $shift_b_end_time <= $shift_b_start_time ) { + $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); + } + + // --- debug point --- + if ( $this->debug ) { + $b_start = $shift_b['day'] . ' ' . $shift_b_start . ' (' . $shift_b_start_time . ')'; + $b_end = $shift_b['day'] . ' ' . $shift_b_end . ' (' . $shift_b_end_time . ')'; + $data .= "with Shift B Start: " . $b_start . ' - Shift B End: ' . $b_end . "\n"; + } + + // --- compare shift A and B times --- + if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) + || ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) + || ( $shift_a_start_time == $shift_b_start_time ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) ) { + + // --- maybe disable shift B --- + // 2.3.2: added check for isset on disabled key + if ( ( !isset( $new_shifts[$i]['disabled'] ) || ( 'yes' != $new_shifts[$i]['disabled'] ) ) + && ( !isset( $new_shifts[$j]['disabled'] ) || ( 'yes' != $new_shifts[$j]['disabled'] ) ) ) { + + // --- debug point --- + if ( $this->debug ) { + // TODO: write more detailed explanations for debug conditions + $data .= "* Conflict Found! New Shift (B) Disabled "; + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) { + $data .= "[A]"; + } + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) { + $data .= "[B]"; + } + if ( $shift_a_start_time == $shift_b_start_time ) { + $data .= "[C]"; + } + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) { + $data .= "[D]"; + } + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) { + $data .= "[E]"; + } + $data .= "*" . PHP_EOL; + } + + $new_shifts[$j]['disabled'] = 'yes'; + + } else { + if ( $this->debug ) { + $data .= "[Conflict with disabled shift.]" . "\n"; + } + } + } + } + } + } + } + } + + // --- debug point --- + if ( $this->debug ) { + $data .= "Checked New Shifts: " . esc_html( print_r( $new_shifts, true ) ) . "\n"; + $this->debug_log( $data, false ); + } + + // --- filter and return --- + $new_shifts = apply_filters( 'schedule_engine_checked_new_shifts', $new_shifts, $new_shifts_in, $weekdates, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $new_shifts = apply_filters( $context . '_checked_new_shifts', $new_shifts, $new_shifts_in, $weekdates, $channel ); + } + return $new_shifts; + } + + // ------------------- + // Validate Shift Time + // ------------------- + // 2.3.0: added check for incomplete shift times + public function validate_shift( $shift ) { + + if ( '' == $shift['day'] ) { + $shift['disabled'] = 'yes'; + } + if ( ( '' == $shift['start_meridian'] ) || ( '' == $shift['end_meridian'] ) ) { + $shift['disabled'] = 'yes'; + } + if ( ( '' == $shift['start_hour'] ) || ( '' == $shift['end_hour'] ) ) { + $shift['disabled'] = 'yes'; + } + if ( '' == $shift['start_min'] ) { + $shift['start_min'] = '00'; + } + if ( '' == $shift['end_min'] ) { + $shift['end_min'] = '00'; + } + + return $shift; + } + + + // ------------------------ + // === Time Conversions === + // ------------------------ + + + // ------- + // Get Now + // ------- + public function get_now( $gmt = true ) { + + if ( defined( 'SCHEDULE_ENGINE_USE_SERVER_TIMES' ) && SCHEDULE_ENGINE_USE_SERVER_TIMES ) { + $now = strtotime( current_time( 'mysql' ) ); + } else { + $datetime = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); + $now = $datetime->format( 'U' ); + } + + return $now; + } + + // ------------ + // Get Timezone + // ------------ + public function get_timezone() { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- fallback to WordPress timezone --- + $timezone = get_option( 'timezone_string' ); + if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { + $offset = get_option( 'gmt_offset' ); + $timezone = 'UTC' . $offset; + } elseif ( '' == $timezone ) { + // 2.5.0: handle empty (not set) timezone location + $timezone = 'UTC'; + } + + $timezone = apply_filters( 'schedule_engine_timezone', $timezone, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $timezone = apply_filters( $context . '_timezone', $timezone, $channel ); + } + return $timezone; + } + + // ----------------- + // Get Timezone Code + // ----------------- + // note: this should only be used for display purposes + // (as the actual code used is based on timezone/location) + public function get_timezone_code( $timezone ) { + $datetime = new DateTime( 'now', new DateTimeZone( $timezone ) ); + $code = $datetime->format( 'T' ); + return $code; + } + + // -------------------- + // Get Date Time Object + // -------------------- + // 2.3.2: added for consistent timezone conversions + public function get_date_time( $timestring, $timezone ) { + + // echo "*TIMEZONE*: " . $timezone . PHP_EOL; + + if ( 'UTC' == $timezone ) { + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + } elseif ( strstr( $timezone, 'UTC' ) ) { + $offset = str_replace( 'UTC', '', $timezone ); + $offset = (int) $offset * 60 * 60; + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + $timestamp = $datetime->format( 'U' ); + $timestamp = $timestamp + $offset; + $datetime->setTimestamp( $timestamp ); + } elseif ( $timezone ) { + $datetime = new DateTime( $timestring, new DateTimeZone( $timezone ) ); + // ...fix to set timestamp again just in case + // echo "A: " . print_r( $datetime, true ) . PHP_EOL; + // if ( '@' == substr( $timestring, 0, 1 ) ) { + // $timestamp = substr( $timestring, 1, strlen( $timestring ) ); + // $datetime->setTimestamp( $timestamp ); + // } + // echo "B: " . print_r( $datetime, true ) . PHP_EOL; + } else { + echo "Error in timezone passed!"; + // print_r( debug_backtrace() ); + // exit; + } + + return $datetime; + } + + // -------------- + // String To Time + // -------------- + // 2.3.2: added for timezone handling + public function to_time( $timestring, $timezone = false ) { + + if ( defined( 'SCHEDULE_ENGINE_USE_SERVER_TIMES' ) && SCHEDULE_ENGINE_USE_SERVER_TIMES ) { + $time = strtotime( $timestring ); + } else { + if ( strstr( $timezone, 'UTC' ) && ( 'UTC' != $timezone ) ) { + // --- fallback for UTC offsets --- + $offset = str_replace( 'UTC', '', $timezone ); + $offset = (int) $offset * 60 * 60; + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + $timestamp = $datetime->getTimestamp(); + $timestamp = $timestamp - $offset; + $datetime->setTimestamp( $timestamp ); + } else { + if ( !$timezone ) { + $timezone = $this->get_timezone(); + } + $datetime = $this->get_date_time( $timestring, $timezone ); + } + $time = $datetime->format( 'U' ); + } + + return $time; + } + + // --------- + // Get Times + // --------- + // 2.5.0: added for separation / abstraction + public function get_times( $time = false, $timezone = false ) { + + // --- get offset time and date --- + if ( !$timezone || ( defined( 'SCHEDULE_ENGINE_USE_SERVER_TIMES' ) && SCHEDULE_ENGINE_USE_SERVER_TIMES ) ) { + + if ( !$time ) { + $time = $this->get_now(); + } + $day = date( 'l', $time ); + $date = date( 'Y-m-d', $time ); + $date_time = date( 'Y-m-d H:i:s', $time ); + $timestamp = date( 'U', $time ); + + } else { + + // 2.5.0: shortened et timestring condition + $timestring = $time ? '@' . $time : 'now'; + + // --- get datetime object --- + if ( strstr( $timezone, 'UTC' ) ) { + $datetime = $this->get_date_time( $timestring, $timezone ); + } else { + // ...and refix for location timezones + $datetime = new DateTime( $timestring, new DateTimeZone( 'UTC' ) ); + $datetime->setTimezone( new DateTimeZone( $timezone ) ); + } + + // --- set formatted strings --- + $day = $datetime->format( 'l' ); + $date = $datetime->format( 'Y-m-d' ); + $date_time = $datetime->format( 'Y-m-d H:i:s' ); + $timestamp = $datetime->format( 'U' ); + + } + + // --- set times array --- + $times = array( + 'day' => $day, + 'date' => $date, + 'datetime' => $date_time, + 'timestamp' => $timestamp, + ); + + // --- maybe set datetime object --- + if ( isset( $datetime ) ) { + $times['object'] = $datetime; + } + + return $times; + } + + // -------- + // Get Time + // -------- + // 2.3.2: added for timezone adjustments + public function get_time( $key = false, $time = false, $timezone = false ) { + + $times = $this->get_times( $time, $timezone ); + + if ( !$key ) { + return $times; + } + + if ( array_key_exists( $key, $times ) ) { + // --- use preformatted time --- + $time = $times[$key]; + } elseif ( isset( $times['object'] ) ) { + // --- use datetime object for formatting --- + $datetime = $times['object']; + $time = $datetime->format( $key ); + } else { + // --- fallback to server date --- + // TODO: maybe use gmdate and timezone offset ? + $time = date( $key, $time ); + } + return $time; + } + + // -------------------- + // Get Timezone Options + // -------------------- + // ref: (based on) https://stackoverflow.com/a/17355238/5240159 + public function get_timezone_options( $include_wp_timezone = false ) { + + // --- maybe get stored timezone options --- + $options = get_transient( 'schedule_engine_timezone_options' ); + if ( !$options ) { + + // --- set regions --- + $regions = array( + DateTimeZone::AFRICA => __( 'Africa', 'schedule-engine' ), + DateTimeZone::AMERICA => __( 'America', 'schedule-engine' ), + DateTimeZone::ASIA => __( 'Asia', 'schedule-engine' ), + DateTimeZone::ATLANTIC => __( 'Atlantic', 'schedule-engine' ), + DateTimeZone::AUSTRALIA => __( 'Australia', 'schedule-engine' ), + DateTimeZone::EUROPE => __( 'Europe', 'schedule-engine' ), + DateTimeZone::INDIAN => __( 'Indian', 'schedule-engine' ), + DateTimeZone::PACIFIC => __( 'Pacific', 'schedule-engine' ), + DateTimeZone::ANTARCTICA => __( 'Antarctica', 'schedule-engine' ), + ); + + // --- loop regions --- + foreach ( $regions as $region => $label ) { + + // --- option group by region --- + $options['*OPTGROUP*' . $region] = $label; + + $timezones = DateTimeZone::listIdentifiers( $region ); + $timezone_offsets = array(); + foreach ( $timezones as $timezone ) { + $datetimezone = new DateTimeZone( $timezone ); + $offset = $datetimezone->getOffset( new DateTime() ); + $timezone_offsets[$offset][] = $timezone; + } + ksort( $timezone_offsets ); + + foreach ( $timezone_offsets as $offset => $timezones ) { + foreach ( $timezones as $timezone ) { + $prefix = $offset < 0 ? '-' : '+'; + $hour = gmdate( 'H', abs( $offset ) ); + $hour = gmdate( 'H', abs( $offset ) ); + $minutes = gmdate( 'i', abs( $offset ) ); + $code = $this->get_timezone_code( $timezone ); + $label = $code . ' (GMT' . $prefix . $hour . ':' . $minutes . ') - '; + $timezone_split = explode( '/', $timezone ); + unset( $timezone_split[0] ); + $timezone_joined = implode( '/', $timezone_split ); + $label .= str_replace( '_', ' ', $timezone_joined ); + $options[$timezone] = $label; + } + } + } + $expiry = 7 * 24 * 60 * 60; + set_transient( 'schedule_engine_timezone_options', $options, $expiry ); + } + + // --- maybe add WordPress timezone (default) option --- + if ( $include_wp_timezone ) { + $wp_timezone = array( '' => __( 'WordPress Timezone', 'schedule-engine' ) ); + $options = array_merge( $wp_timezone, $options ); + } + + // --- filter and return --- + $options = apply_filters( 'schedule_engine_timezone_options', $options, $include_wp_timezone ); + return $options; + } + + // -------------- + // Get Weekday(s) + // -------------- + // 2.3.2: added get weekday from number helper + public function get_weekday( $day_number = null ) { + + // 2.5.0: use internal get_weekdays function + $weekdays = $this->get_weekdays(); + if ( !is_null( $day_number ) ) { + return $weekdays[$day_number]; + } + return $weekdays; + } + + // ------------ + // Get Month(s) + // ------------ + // 2.3.2: added get weekday from number helper + public function get_month( $month_number = null ) { + + // 2.5.0: use internal get_months function + $months = $this->get_months(); + if ( !is_null( $month_number ) ) { + return $months[$month_number]; + } + return $months; + } + + // --------------------- + // Get Schedule Weekdays + // --------------------- + // note: no translations here because used internally for sorting + // 2.3.0: added to get schedule weekdays from start of week + // 2.5.0: added time and timezone arguments for when weekstart is today + public function get_schedule_weekdays( $weekstart = false, $time = false, $timezone = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- maybe get start of the week --- + if ( !$weekstart ) { + $weekstart = get_option( 'start_of_week' ); + $weekstart = apply_filters( 'schedule_engine_schedule_weekday_start', $weekstart, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $weekstart = apply_filters( $context . '_schedule_weekday_start', $weekstart, $channel ); + } + } + + // 2.5.0: use internal get_weekdays function + $weekdays = $this->get_weekdays(); + + // 2.3.2: also accept string format for weekstart + if ( is_string( $weekstart ) ) { + // 2.3.3.5: accept today as valid week start + if ( 'today' == $weekstart ) { + // 2.5.0: added time and timezone arguments + $timezone = $timezone ? $timezone : $this->get_timezone(); + $weekstart = $this->get_time( 'day', $time, $timezone ); + } + foreach ( $weekdays as $i => $weekday ) { + if ( strtolower( $weekday ) == strtolower( $weekstart ) ) { + $weekstart = $i; + } + } + } + + // --- loop weekdays and reorder from start day --- + $start = $before = $after = array(); + foreach ( $weekdays as $i => $weekday ) { + // 2.3.2: allow matching of numerical index or weekday name + if ( ( $i == $weekstart ) || ( $weekday == $weekstart ) ) { + $start[] = $weekday; + } elseif ( $i > $weekstart ) { + $after[] = $weekday; + } elseif ( $i < $weekstart ) { + $before[] = $weekday; + } + } + + // --- put the days before the start day at the end --- + $weekdays = array_merge( $start, $after ); + $weekdays = array_merge( $weekdays, $before ); + + // --- filter and return --- + $weekdays = apply_filters( 'schedule_engine_schedule_weekdays', $weekdays, $weekstart, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $weekdays = apply_filters( $context . '_schedule_weekdays', $weekdays, $weekstart, $channel ); + } + return $weekdays; + } + + // ---------------------- + // Get Schedule Weekdates + // ---------------------- + // 2.3.0: added for date based calculations + public function get_schedule_weekdates( $weekdays, $time = false, $timezone = false ) { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- maybe get current time --- + if ( !$time ) { + $time = $this->get_now(); + } + + // 2.3.2: use timezone setting to get offset date + if ( defined( 'SCHEDULE_ENGINE_USE_SERVER_TIMES' ) && SCHEDULE_ENGINE_USE_SERVER_TIMES ) { + $today = date( 'l', $time ); + } else { + // 2.3.3.9: fix to use radio_station_get_time + $today = $this->get_time( 'day', $time, $timezone ); + } + + // --- get weekday index for today --- + $weekdates = array(); + foreach ( $weekdays as $i => $weekday ) { + if ( $weekday == $today ) { + $index = $i; + } + } + foreach ( $weekdays as $i => $weekday ) { + $diff = $index - $i; + $weekdate_time = $time - ( $diff * 24 * 60 * 60 ); + // 2.3.2: include timezone adjustment + if ( defined( 'SCHEDULE_ENGINE_USE_SERVER_TIMES' ) && SCHEDULE_ENGINE_USE_SERVER_TIMES ) { + $weekdate = date( 'Y-m-d', $weekdate_time ); + } else { + // 2.3.3.9: fix to use radio_station_get_time + $weekdate = $this->get_time( 'Y-m-d', $weekdate_time, $timezone ); + } + $weekdates[$weekday] = $weekdate; + } + + // 2.4.0.4: check/fix for duplicate date crackliness (daylight saving?) + foreach ( $weekdates as $day => $date ) { + if ( isset( $prevdate ) && ( $prevdate == $date ) ) { + $weekdates[$day] = $this->get_next_date( $date ); + $found = false; + foreach ( $weekdates as $k => $v ) { + if ( $found ) { + $weekdates[$k] = $this->get_next_date( $v ); + } + if ( $k == $day ) { + $found = true; + } + } + } + $prevdate = $date; + } + + // --- filter and return --- + $weekdates = apply_filters( 'schedule_engine_schedule_weekdates', $weekdates, $weekdays, $time, $timezone, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $weekdates = apply_filters( $context . '_schedule_weekdates', $weekdates, $weekdays, $time, $timezone, $channel ); + } + + return $weekdates; + } + + // ------------ + // Get Next Day + // ------------ + // 2.3.0: added get next day helper + public function get_next_day( $day ) { + // note: for internal use so not translated + $day = trim( $day ); + if ( 'Sunday' == $day ) { + return 'Monday'; + } + if ( 'Monday' == $day ) { + return 'Tuesday'; + } + if ( 'Tuesday' == $day ) { + return 'Wednesday'; + } + if ( 'Wednesday' == $day ) { + return 'Thursday'; + } + if ( 'Thursday' == $day ) { + return 'Friday'; + } + if ( 'Friday' == $day ) { + return 'Saturday'; + } + if ( 'Saturday' == $day ) { + return 'Sunday'; + } + + return ''; + } + + // ---------------- + // Get Previous Day + // ---------------- + // 2.3.0: added get previous day helper + public function get_previous_day( $day ) { + // note: for internal use so not translated + $day = trim( $day ); + if ( 'Sunday' == $day ) { + return 'Saturday'; + } + if ( 'Monday' == $day ) { + return 'Sunday'; + } + if ( 'Tuesday' == $day ) { + return 'Monday'; + } + if ( 'Wednesday' == $day ) { + return 'Tuesday'; + } + if ( 'Thursday' == $day ) { + return 'Wednesday'; + } + if ( 'Friday' == $day ) { + return 'Thursday'; + } + if ( 'Saturday' == $day ) { + return 'Friday'; + } + + return ''; + } + + // ------------- + // Get Next Date + // ------------- + // 2.3.2: added for more reliable calculations + public function get_next_date( $date, $weekday = false ) { + + // note: this is used internally so timezone not needed + $timedate = strtotime( $date ); + $timedate = $timedate + ( 24 * 60 * 60 ); + if ( $weekday ) { + $day = date( 'l', $timedate ); + if ( $day != $weekday ) { + $i = 0; + while ( $day != $weekday ) { + $timedate = $timedate + ( 24 * 60 * 60 ); + $day = strtotime( 'l', $timedate ); + if ( 8 == $i ) { + // - failback for while failure - + $timedate = strtotime( $date ); + $next_date = date( 'next ' . $weekday, $timedate ); + return $next_date; + } + $i++; + } + } + } + $next_date = date( 'Y-m-d', $timedate ); + return $next_date; + } + + // ----------------- + // Get Previous Date + // ----------------- + // 2.3.2: added for more reliable calculations + public function get_previous_date( $date, $weekday = false ) { + + // note: this is used internally so timezone not used + $timedate = strtotime( $date ); + $timedate = $timedate - ( 24 * 60 * 60 ); + if ( $weekday ) { + $day = date( 'l', $timedate ); + if ( $day != $weekday ) { + $i = 0; + while ( $day != $weekday ) { + $timedate = $timedate - ( 24 * 60 * 60 ); + $day = strtotime( 'l', $timedate ); + if ( 8 == $i ) { + // - failback for while failure - + $timedate = strtotime( $date ); + $previous_date = date( 'previous ' . $weekday, $timedate ); + return $previous_date; + } + $i++; + } + } + } + $previous_date = date( 'Y-m-d', $timedate ); + return $previous_date; + } + + // --------------------------- + // Convert Hour to Time Format + // --------------------------- + // (note: used with suffix for on-the-hour times) + // 2.3.0: standalone function via master-schedule-default.php + // 2.3.0: optionally add suffix for both time formats + public function convert_hour( $hour, $timeformat = 24, $suffix = true ) { + + $hour = intval( $hour ); + + // 2.3.0: handle next and previous hours (over 24 or below 0) + if ( $hour < 0 ) { + while ( $hour < 0 ) { + $hour = $hour + 24; + } + } + if ( $hour > 24 ) { + while ( $hour > 24 ) { + $hour = $hour - 24; + } + } + + if ( 24 === (int) $timeformat ) { + // --- 24 hour time format --- + if ( 24 == $hour ) { + $hour = '00'; + } elseif ( $hour < 10 ) { + $hour = '0' . $hour; + } + if ( $suffix ) { + $hour .= ':00'; + } + } elseif ( 12 === (int) $timeformat ) { + // --- 12 hour time format --- + // 2.2.7: added meridiem translations + if ( ( $hour === 0 ) || ( 24 === $hour ) ) { + // midnight + $hour = '12'; + if ( $suffix ) { + $hour .= ' ' . $this->translate_meridiem( 'am' ); + } + } elseif ( $hour < 12 ) { + // morning + if ( $suffix ) { + $hour .= ' ' . $this->translate_meridiem( 'am' ); + } + } elseif ( 12 === $hour ) { + // noon + if ( $suffix ) { + $hour .= ' ' . $this->translate_meridiem( 'pm' ); + } + } elseif ( $hour > 12 ) { + // after-noon + $hour = $hour - 12; + if ( $suffix ) { + $hour .= ' ' . $this->translate_meridiem( 'pm' ); + } + } + } + // 2.3.2: fix for possible double spacing crackliness + $hour = str_replace( ' ', ' ', $hour ); + + return $hour; + } + + // ---------------------------- + // Convert Shift to Time Format + // ---------------------------- + // 2.3.0: added to convert shift time to 24 hours (or back) + public function convert_shift_time( $time, $timeformat = 24 ) { + + // note: timezone can be ignored here as just getting hours and minutes + // 2.3.3.9: added space between date and time + $timestamp = strtotime( date( 'Y-m-d' ) . ' ' . $time ); + if ( 12 == (int) $timeformat ) { + $time = date( 'g:i a', $timestamp ); + str_replace( 'am', $this->translate_meridiem( 'am' ), $time ); + str_replace( 'pm', $this->translate_meridiem( 'pm' ), $time ); + } elseif ( 24 == (int) $timeformat ) { + $time = date( 'H:i', $timestamp ); + } + + return $time; + } + + + // -------------------- + // === Translations === + // -------------------- + + // ---------- + // Get Locale + // ---------- + public function get_locale() { + + // --- get channel and context --- + $channel = $this->channel; + $context = $this->context; + + // --- get set locale --- + $locale = $this->locale; + + // --- filter and return --- + $locale = apply_filters( 'schedule_engine_locale', $locale, $channel, $context ); + if ( ( '' != $context ) && is_string( $context ) ) { + $locale = apply_filters( $context . '_locale', $locale, $channel ); + } + return $locale; + } + + // -------------- + // Get All Months + // -------------- + public function get_months( $translate = false ) { + $months = array( + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ); + if ( $translate ) { + foreach ( $months as $i => $month ) { + $months[$i] = $this->translate_month( $month ); + } + } + return $months; + } + + // ------------ + // Get All Days + // ------------ + public function get_weekdays( $translate = false ) { + $days = array( + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday' + ); + if ( $translate ) { + foreach ( $days as $i => $day ) { + $days[$i] = $this->translate_weekday( $day ); + } + } + return $days; + } + + // ------------- + // Get All Hours + // ------------- + public function get_hours( $format = 24, $translate = false ) { + $hours = array(); + if ( 24 === (int) $format ) { + $hours = array( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ); + } elseif ( 12 === (int) $format ) { + $hours = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ); + } + if ( $translate ) { + foreach ( $hours as $i => $hour ) { + $hours[$i] = number_format_i18n( $hour ); + } + } + return $hours; + } + + // --------------- + // Get All Minutes + // --------------- + public function get_minutes( $translate = false ) { + + $mins = array(); + for ( $i = 0; $i < 60; $i++ ) { + if ( $i < 10 ) { + $min = '0' . $i; + } else { + $min = $i; + } + $mins[$i] = $min; + } + if ( $translate ) { + foreach ( $mins as $i => $min ) { + $mins[$i] = number_format_i18n( $min ); + } + } + return $mins; + } + + // ----------------- + // Translate Weekday + // ----------------- + // important note: translated individually as cannot translate a variable + // 2.2.7: use wp locale class to translate weekdays + // 2.3.0: allow for abbreviated and long version changeovers + // 2.3.2: default short to null for more flexibility + public function translate_weekday( $weekday, $short = null ) { + + // 2.3.0: return empty for empty select option + if ( empty( $weekday ) ) { + return ''; + } + + // --- get weekdays --- + $days = $this->get_weekday(); + + // --- maybe switch locales --- + // 2.5.0: added for possible locale switching + $locale = $this->locale; + if ( '' != $locale ) { + $switcher = new WP_Locale_Switcher(); + $switcher->switch_to_locale( $locale ); + } + global $wp_locale; + + // --- translate weekday --- + // 2.3.2: optimized weekday translations + foreach ( $days as $i => $day ) { + $abbr = substr( $day, 0, 3 ); + if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $weekday == $day ) ) ) { + $weekday = $wp_locale->get_weekday( $i ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); + } + } + } + + // --- fallback if day number supplied --- + // 2.3.2: optimized day number fallback + if ( !isset ($weekday ) ) { + $daynum = intval( $weekday ); + if ( ( $daynum > -1 ) && ( $daynum < 7 ) ) { + if ( $short ) { + $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $daynum ) ); + } else { + $weekday = $wp_locale->get_weekday( $daynum ); + } + } + } + + // --- maybe switch back to original locale --- + // 2.5.0: added for possible locale switching + if ( '' != $locale ) { + $switcher->restore_current_locale(); + } + return $weekday; + } + + // ---------------- + // Replace Weekdays + // ---------------- + // 2.3.2: to replace with translated weekdays in a time string + public function replace_weekday( $string ) { + + $days = $this->get_weekday(); + foreach( $days as $day ) { + $abbr = substr( $day, 0, 3 ); + if ( strstr( $string, $day ) ) { + $translated = $this->translate_weekday( $day ); + $string = str_replace( $day, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = $this->translate_weekday( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } + } + + return $string; + } + + // --------------- + // Translate Month + // --------------- + // important note: translated individually as cannot translate a variable + // 2.2.7: use wp locale class to translate months + // 2.3.2: default short to null for more flexibility + public function translate_month( $month, $short = null ) { + + // 2.3.0: return empty for empty select option + if ( empty( $month ) ) { + return ''; + } + + // --- get months --- + $months = $this->get_month(); + + // --- maybe switch locales --- + // 2.5.0: added for possible locale switching + $locale = $this->locale; + if ( '' != $locale ) { + $switcher = new WP_Locale_Switcher(); + $switcher->switch_to_locale( $locale ); + } + global $wp_locale; + + // --- translate month --- + // 2.3.2: optimized month translations + foreach ( $months as $i => $fullmonth ) { + $abbr = substr( $fullmonth, 0, 3 ); + if ( ( $month == $fullmonth ) || ( $month == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) + || ( is_null( $short ) && ( $month == $fullmonth ) ) ) { + $month = $wp_locale->get_month( ( $i + 1 ) ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( ( $i + 1 ) ) ); + } + } + } + + // --- fallback if month number supplied --- + // 2.3.2: optimized month number fallback + if ( !isset( $month ) ) { + $monthnum = intval( $month ); + if ( ( $monthnum > 0 ) && ( $monthnum < 13 ) ) { + if ( $short ) { + $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( $monthnum ) ); + } else { + $month = $wp_locale->get_month( $monthnum ); + } + } + } + + // --- maybe switch back to original locale --- + // 2.5.0: added for possible locale switching + if ( '' != $locale ) { + $switcher->restore_current_locale(); + } + return $month; + } + + // -------------- + // Replace Months + // -------------- + // 2.3.2: to replace with translated months in a time string + public function replace_month( $string ) { + + $months = $this->get_month(); + foreach( $months as $month ) { + $abbr = substr( $month, 0, 3 ); + if ( strstr( $string, $month ) ) { + $translated = $this->translate_month( $month ); + $string = str_replace( $month, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = $this->translate_month( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } + } + + return $string; + } + + // ------------------ + // Translate Meridiem + // ------------------ + // 2.2.7: added meridiem translation function + public function translate_meridiem( $meridiem ) { + + // --- maybe switch locales --- + // 2.5.0: added for possible locale switching + $locale = $this->locale; + if ( '' != $locale ) { + $switcher = new WP_Locale_Switcher(); + $switcher->switch_to_locale( $locale ); + } + + // --- get translated meridiem --- + global $wp_locale; + $meridiem = $wp_locale->get_meridiem( $meridiem ); + + // --- maybe switch back to original locale --- + // 2.5.0: added for possible locale switching + if ( '' != $locale ) { + $switcher->restore_current_locale(); + } + + return $meridiem; + } + + // ---------------- + // Replace Meridiem + // ---------------- + // 2.3.2: added optimized meridiem replacement + public function replace_meridiem( $string ) { + + if ( strstr( $string, 'am' ) ) { + $am = $this->translate_meridiem( 'am' ); + $string = str_replace( 'am', $am, $string ); + } + if ( strstr( $string, 'pm' ) ) { + $pm = $this->translate_meridiem( 'pm' ); + $string = str_replace( 'pm', $pm, $string ); + } + if ( strstr( $string, 'AM' ) ) { + $am = $this->translate_meridiem( 'AM' ); + $string = str_replace( 'AM', $am, $string ); + } + if ( strstr( $string, 'PM' ) ) { + $pm = $this->translate_meridiem( 'PM' ); + $string = str_replace( 'PM', $pm, $string ); + } + + return $string; + } + + // --------------------- + // Translate Time String + // --------------------- + // 2.3.2: replace with translated month, day and meridiem in a string + public function translate_time( $string ) { + + $string = $this->replace_meridiem( $string ); + $string = $this->replace_weekday( $string ); + $string = $this->replace_month( $string ); + + return $string; + } + + + // ----------------- + // === Debugging === + // ----------------- + + // ----------------------- + // Write to Debug Log File + // ----------------------- + public function debug_log( $data, $echo = true, $filename = false ) { + + // --- maybe output debug info --- + if ( $echo ) { + echo '' . PHP_EOL; + } + + // --- check for logging constant --- + if ( !$filename && $this->debug_log ) { + $filename = 'schedule-engine.log'; + } elseif ( false === $this->debug_log ) { + $filename = false; + } + + // --- maybe write to file --- + if ( $filename ) { + + // --- maybe create debug directory --- + if ( !is_dir( dirname( __FILE__ ) . '/debug' ) ) { + wp_mkdir_p( dirname( __FILE__ ) . '/debug' ); + } + + // --- write to debug file path --- + $filepath = dirname( __FILE__ ) . '/debug/' . $filename; + error_log( $data, 3, $filepath ); + } + } + +// --- close schedule engine class --- +} + diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php index 11b6542..05aa6b6 100644 --- a/templates/master-schedule-div.php +++ b/templates/master-schedule-div.php @@ -8,7 +8,7 @@ // --- set shift time formats --- // 2.3.2: set time formats early -if ( 24 == (int) $atts['time'] ) { +if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H:i'; } else { $start_data_format = $end_data_format = 'g:i a'; @@ -41,7 +41,7 @@ $output .= ''; // output the schedule -$output .= '
    '; +$output .= '
    '; // $weekdays = array_keys( $days_of_the_week ); $output .= '
    '; @@ -58,9 +58,9 @@ // output the hour labels $output .= '
    '; - if ( 12 === (int) $atts['time'] ) { + if ( 12 === (int) $atts['time_format'] ) { // random date needed to convert time to 12-hour format - $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); } else { // random date needed to convert time to 24-hour format $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); @@ -119,7 +119,7 @@ if ( $show_names ) { - $output .= ' ' . esc_html( __( 'with', 'radio-station' ) ) . ''; + $output .= ' ' . esc_html( __( 'with', 'radio-station' ) ) . ''; foreach ( $show_names as $name ) { @@ -145,7 +145,7 @@ $output .= ''; - if ( 12 === (int) $atts['time'] ) { + if ( 12 === (int) $atts['time_format'] ) { $show_time = date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); $show_time .= ' - '; $show_time .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); @@ -155,14 +155,14 @@ $show_time .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); } - /* if ( 12 === (int) $atts['time'] ) { + /* if ( 12 === (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'g:i a'; } else { $start_data_format = $end_data_format = 'H:i'; } $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, '', $atts ); $start_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, '', $atts ); - + $start = radio_station_get_time( $shift_start_time ); $end = radio_station_get_time( $shift_end_time ); $start = radio_station_translate_time( $start ); diff --git a/templates/master-schedule-legacy.php b/templates/master-schedule-legacy.php index 697df2f..9d86fbc 100644 --- a/templates/master-schedule-legacy.php +++ b/templates/master-schedule-legacy.php @@ -38,7 +38,7 @@ $output .= '
    '; // 2.2.7: added meridiem translations - if ( 12 === (int) $atts['time'] ) { + if ( 12 === (int) $atts['time_format'] ) { if ( 0 === $hour ) { $output .= '12' . radio_station_translate_meridiem( 'am' ); } elseif ( (int) $hour < 12 ) { @@ -195,7 +195,7 @@ $output .= ''; - if ( 12 === (int) $atts['time'] ) { + if ( 12 === (int) $atts['time_format'] ) { // 2.2.7: added meridiem translation $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index e237c36..f397873 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -18,7 +18,7 @@ $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; } else { // 2.3.3.9: set start date to current date - $start_date = $date; + $start_date = $date; } $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); @@ -29,7 +29,7 @@ $shifts_separator = apply_filters( 'radio_station_show_times_separator', $shifts_separator, 'schedule-list' ); $time_separator = ':'; $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-list' ); -if ( 24 == (int) $atts['time'] ) { +if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; @@ -47,11 +47,11 @@ $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'list' ); } if ( $start_day ) { - $schedule = radio_station_get_current_schedule( $start_time , $start_day ); + $schedule = radio_station_get_current_schedule( $start_time, $start_day ); } elseif ( $start_time != $now ) { // 2.3.3.9: load current or time-specific schedule $schedule = radio_station_get_current_schedule( $start_time ); -} else { +} else { $schedule = radio_station_get_current_schedule(); } $weekdays = radio_station_get_schedule_weekdays( $start_day ); @@ -73,7 +73,7 @@ $infokeys = apply_filters( 'radio_station_schedule_list_info_order', $infokeys ); // --- start list schedule output --- -$list = '
      ' . $newline; +$list = '
        ' . "\n"; $tcount = 0; // 2.3.0: loop weekdays instead of legacy master list @@ -150,23 +150,23 @@ // 2.2.2: use translate function for weekday string // 2.3.2: added optional display-date attribute $display_day = radio_station_translate_weekday( $weekday ); - $list .= '
      • ' . $newline; + $list .= '
      • ' . "\n"; $list .= '' . $newline; + $list .= '>' . esc_html( $display_day ) . '' . "\n"; if ( $atts['display_date'] ) { - $list .= ' ' . esc_html( $date_subheading ) . '' . $newline; + $list .= ' ' . esc_html( $date_subheading ) . '' . "\n"; } // 2.3.2: add output of day start and end times // 2.3.2: replace strtotime with to_time for timezones - $list .= '' . $newline; - $list .= '' . $newline; + $list .= '' . "\n"; + $list .= '' . "\n"; // --- open day list --- - $list .= '
          ' . $newline; + $list .= '
            ' . "\n"; // --- get shifts for this day --- if ( isset( $schedule[$weekday] ) ) { @@ -252,7 +252,7 @@ // --- open show list item --- $classlist = implode( ' ', $classes ); - $list .= '
          • ' . $newline; + $list .= '
          • ' . "\n"; // --- show avatar --- if ( $atts['show_image'] ) { @@ -260,17 +260,18 @@ $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show_id, 'list' ); if ( $show_avatar ) { - $avatar = '
            ' . $newline; + $avatar = '
            ' . "\n"; + if ( $show_link ) { + $avatar .= ''; + } + $avatar .= $show_avatar; if ( $show_link ) { - $avatar .= '' . $show_avatar . ''; - } else { - $avatar .= $show_avatar; + $avatar .= ''; } - $avatar .= '
            ' . $newline; + $avatar .= '
            ' . "\n"; $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'list' ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $info['avatar'] = $avatar; - // $list .= $avatar; } } } @@ -281,9 +282,9 @@ } else { $show_title = esc_html( $show['name'] ); } - $title = '' . $newline; - $title .= $show_title; - $title .= '' . $newline; + $title = '' . "\n"; + $title .= $show_title; + $title .= '' . "\n"; $title = apply_filters( 'radio_station_schedule_show_title', $title, $show_id, 'list' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; @@ -308,15 +309,15 @@ $count = 0; $host_count = count( $show['hosts'] ); $show_hosts .= ''; - $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); + $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); // 2.3.2: fix variable to close span tag - $show_hosts .= ' ' . $newline; + $show_hosts .= ' ' . "\n"; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . "\n"; } else { $show_hosts .= esc_html( $host['name'] ); } @@ -333,13 +334,12 @@ // 2.3.3.5: fix to incorrect context value (tabs) $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'list' ); if ( $show_hosts ) { - $hosts = '
            ' . $newline; - $hosts .= $show_hosts; - $hosts .= '
            ' . $newline; + $hosts = '
            ' . "\n"; + $hosts .= $show_hosts; + $hosts .= '
            ' . "\n"; $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'list' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; - // $list .= $hosts; } } } @@ -364,15 +364,15 @@ // 2.3.0: filter show time by show and context // 2.4.0.6: used filtered shift times separator - $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; - $show_time .= '' . esc_html( $end ) . '' . $newline; + $show_time = '' . esc_html( $start ) . '' . "\n"; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . "\n"; + $show_time .= '' . esc_html( $end ) . '' . "\n"; } else { // 2.3.3.8: added for now playing check - $show_time = '' . $newline; - $show_time .= '' . $newline; + $show_time = '' . "\n"; + $show_time .= '' . "\n"; } @@ -385,16 +385,16 @@ if ( !$display ) { $times .= ' style="display:none;"'; } - $times .= '>' . $show_time . '
    ' . $newline; + $times .= '>' . $show_time . '
    ' . "\n"; // 2.3.3.9: added internal spans for user time // 2.4.0.6: use filtered shift times separator $times .= '
    '; - $times .= '[' . $newline; - $times .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; - $times .= ']' . $newline; - $times .= '
    ' . $newline; + $times .= '[' . "\n"; + $times .= ' ' . esc_html( $shifts_separator ) . ' ' . "\n"; + $times .= ']' . "\n"; + $times .= '
    ' . "\n"; $info['times'] = $times; - $tcount ++; + $tcount++; // --- encore --- if ( $atts['show_encore'] ) { @@ -407,12 +407,11 @@ $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show_id, 'list' ); if ( 'on' == $show_encore ) { $encore = '
    '; - $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); - $encore .= '
    ' . $newline; + $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); + $encore .= '
    ' . "\n"; $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show_id, 'list' ); if ( ( '' != $encore ) && is_string( $encore ) ) { $info['encore'] = $encore; - // $list .= $encore; } } } @@ -430,15 +429,14 @@ if ( $show_file && !empty( $show_file ) && !$disable_download ) { $anchor = __( 'Audio File', 'radio-station' ); $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show_id, 'list' ); - $file = '
    ' . $newline; - $file .= ''; - $file .= esc_html( $anchor ); - $file .= '' . $newline; - $file .= '
    ' . $newline; + $file = '
    ' . "\n"; + $file .= ''; + $file .= esc_html( $anchor ); + $file .= '' . "\n"; + $file .= '
    ' . "\n"; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'list' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; - // $list .= $file; } } } @@ -447,20 +445,19 @@ // (defaults to on) // 2.3.0: add genres to list view if ( $atts['show_genres'] ) { - $genres = '
    ' . $newline; + $genres = '
    ' . "\n"; $show_genres = array(); if ( count( $terms ) > 0 ) { $genres .= esc_html( __( 'Genres', 'radio-station' ) ) . ': '; foreach ( $terms as $term ) { - $show_genres[] = '' . esc_html( $term->name ) . '' . $newline; + $show_genres[] = '' . esc_html( $term->name ) . '' . "\n"; } $genres .= implode( ', ', $show_genres ); } - $genres .= '
    ' . $newline; + $genres .= '
    ' . "\n"; $genres = apply_filters( 'radio_station_schedule_show_genres_display', $genres, $show_id, 'list' ); if ( ( '' != $genres ) && is_string( $genres ) ) { $info['genres'] = $genres; - // $list .= $genres; } } @@ -480,12 +477,11 @@ $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'list' ); // --- set excerpt display --- - $excerpt = '
    ' . $newline; - $excerpt .= $show_excerpt . $newline; - $excerpt .= '
    ' . $newline; + $excerpt = '
    ' . "\n"; + $excerpt .= $show_excerpt . "\n"; + $excerpt .= '
    ' . "\n"; if ( ( '' != 'excerpt' ) && is_string( $excerpt ) ) { $info['excerpt'] = $excerpt; - // $list .= $excerpt; } } @@ -504,20 +500,21 @@ } } - $list .= '' . $newline; + $list .= '' . "\n"; } } - $list .= '' . $newline; + $list .= '' . "\n"; // --- close master list day item --- - $list .= '' . $newline; + $list .= '' . "\n"; } } // --- close master list --- -$list .= '' . $newline; +$list .= '' . "\n"; // --- hidden iframe for schedule reloading --- -$list .= '' . $newline; +$list .= '' . "\n"; + +echo $list; -echo $list; \ No newline at end of file diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 08d5e4f..92348b1 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -18,7 +18,7 @@ $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; } else { // 2.3.3.9: set start date to current date - $start_date = $date; + $start_date = $date; } $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); $start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'table', $atts ); @@ -30,7 +30,7 @@ $shifts_separator = apply_filters( 'radio_station_show_time_separator', $shifts_separator, 'schedule-table' ); $time_separator = ':'; $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-table' ); -if ( 24 == (int) $atts['time'] ) { +if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; @@ -77,19 +77,19 @@ $infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); // --- clear floats --- -$table = '
    ' . $newline; +$table = '
    ' . "\n"; // --- start master program table --- -$table .= '' . $newline; +$table .= '
    ' . "\n"; // --- weekday table headings row --- // 2.3.2: added hour column heading id -$table .= '' . $newline; -$table .= '' . "\n"; +$table .= '' . $newline; +$table .= '' . "\n"; foreach ( $weekdays as $i => $weekday ) { @@ -161,38 +161,37 @@ $classes[] = 'current-day'; // $classes[] = 'selected-day'; } - $class = implode( ' ', $classes ); + $classlist = implode( ' ', $classes ); // --- output table column heading --- // 2.3.0: added left/right arrow responsive controls // 2.3.1: added (negative) return to arrow onclick functions // 2.3.2: added check for optional display_date attribute - $table .= '' . $newline; + $table .= '' . "\n"; } } -$table .= '' . $newline; +$table .= '' . "\n"; // --- loop schedule hours --- $tcount = 0; @@ -200,16 +199,16 @@ // 2.3.1: fix to set translated hour for display only $raw_hour = $hour; - $hour_display = radio_station_convert_hour( $hour, $atts['time'] ); + $hour_display = radio_station_convert_hour( $hour, $atts['time_format'] ); if ( 1 == strlen( $hour ) ) { $hour = '0' . $hour; } // --- start hour row --- - $table .= '' . $newline; + $table .= '' . "\n"; // --- set data format for timezone conversions --- - if ( 24 == (int) $atts['time'] ) { + if ( 24 == (int) $atts['time_format'] ) { $hour_data_format = "H:i"; } else { $hour_data_format = "g a"; @@ -228,22 +227,21 @@ $class = implode( ' ', $classes ); // --- hour heading --- - $table .= '' . $newline; + $table .= '
    '; + $table .= esc_html( $hour_display ); + $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + $table .= '' . "\n"; foreach ( $weekdays as $i => $weekday ) { @@ -376,15 +374,15 @@ } } - if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { + if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == sanitize_title( $_GET['rs-shift-debug'] ) ) ) { if ( !isset( $shiftdebug ) ) {$shiftdebug = '';} if ( $display ) { $shiftdebug .= 'Now: ' . $now . ' (' . radio_station_get_time( 'datetime', $now ) . ') -- Today: ' . $today . '
    '; $shiftdebug .= 'Day: ' . $weekday . ' - Raw Hour: ' . $raw_hour . ' - Hour: ' . $hour . ' - Hour Display: ' . $hour_display . '
    '; $shiftdebug .= 'Hour Start: ' . $hour_start . ' (' . date( 'Y-m-d l H:i', $hour_start ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $hour_start ) . ')
    '; $shiftdebug .= 'Hour End: ' . $hour_end . ' (' . date( 'Y-m-d l H: i', $hour_end ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $hour_end ) . ')
    '; - $shiftdebug .= 'Shift Start: ' . $shift_start_time . ' (' . date( 'Y-m-d l H: i', $shift_start_time ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start_time ) . ')' . '
    '; - $shiftdebug .= 'Shift End: ' . $shift_end_time . ' (' . date( 'Y-m-d l H: i', $shift_end_time ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end_time ) . ')' . '
    '; + $shiftdebug .= 'Shift Start: ' . $shift_start_time . ' (' . date( 'Y-m-d l H: i', $shift_start_time ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start_time ) . ')
    '; + $shiftdebug .= 'Shift End: ' . $shift_end_time . ' (' . date( 'Y-m-d l H: i', $shift_end_time ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end_time ) . ')
    '; $shiftdebug .= 'Display: ' . ( $display ? 'yes' : 'no' ) . ' - '; $shiftdebug .= 'New Shift: ' . ( $newshift ? 'yes' : 'no' ) . ' - '; $shiftdebug .= 'Now Playing: ' . ( $nowplaying ? 'YES' : 'no' ) . ' - '; @@ -426,13 +424,13 @@ $divclasses[] = 'overnight'; $divclasses[] = 'split-' . $split_id; } - $divclass = implode( ' ', $divclasses ); + $divclasslist = implode( ' ', $divclasses ); // --- start the cell contents --- if ( !isset( $cell ) ) { $cell = ''; } - $cell .= '
    ' . $newline; + $cell .= '
    ' . "\n"; if ( $showcontinued ) { @@ -440,8 +438,8 @@ $cell .= ' '; // 2.3.2: set shift times for highlighting - $cell .= '' . $newline; - $cell .= '' . $newline; + $cell .= '' . "\n"; + $cell .= '' . "\n"; } else { @@ -461,28 +459,27 @@ } $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show_id, 'table' ); if ( $show_avatar ) { - $avatar = '
    ' . $newline; + $avatar = '
    ' . "\n"; if ( $show_link ) { - $avatar .= '' . $show_avatar . '' . $newline; + $avatar .= '' . $show_avatar . '' . "\n"; } else { - $avatar .= $show_avatar . $newline; + $avatar .= $show_avatar . "\n"; } - $avatar .= '
    ' . $newline; + $avatar .= '
    ' . "\n"; $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'table' ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $info['avatar'] = $avatar; - // $cell .= $avatar; } } // --- show title --- - $title = '
    ' . $newline; + $title = '
    ' . "\n"; if ( $show_link ) { - $title .= '' . esc_html( $show['name'] ) . '' . $newline; + $title .= '' . esc_html( $show['name'] ) . '' . "\n"; } else { - $title .= esc_html( $show['name'] ) . $newline; + $title .= esc_html( $show['name'] ) . "\n"; } - $title .= '
    ' . $newline; + $title .= '
    ' . "\n"; $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'table' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; @@ -504,8 +501,8 @@ if ( $show['hosts'] && is_array( $show['hosts'] ) && ( count( $show['hosts'] ) > 0 ) ) { $show_hosts .= ' '; - $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); - $show_hosts .= ' ' . $newline; + $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); + $show_hosts .= ' ' . "\n"; $count = 0; $host_count = count( $show['hosts'] ); @@ -513,7 +510,7 @@ $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . "\n"; } else { $show_hosts .= esc_html( $host['name'] ); } @@ -529,13 +526,12 @@ $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'table' ); if ( $show_hosts ) { - $hosts = '
    ' . $newline; + $hosts = '
    ' . "\n"; $hosts .= $show_hosts; - $hosts .= '
    ' . $newline; + $hosts .= '
    ' . "\n"; $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'table' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; - // $cell .= $hosts; } } } @@ -559,15 +555,15 @@ $end = radio_station_translate_time( $end ); // --- set show time output --- - $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; - $show_time .= '' . esc_html( $end ) . '' . $newline; + $show_time = '' . esc_html( $start ) . '' . "\n"; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . "\n"; + $show_time .= '' . esc_html( $end ) . '' . "\n"; } else { // 2.3.3.8: added for now playing check - $show_time = '' . $newline; - $show_time .= '' . $newline; + $show_time = '' . "\n"; + $show_time .= '' . "\n"; } @@ -575,20 +571,20 @@ // 2.3.3.9: added tcount argument to filter $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'table', $shift, $tcount ); $times = '
    ' . $newline; - // 2.3.3.9: added internal spans for user time - $times .= '
    ' . $newline; - $times .= '[' . $newline; - $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; - $times .= ']' . $newline; - $times .= '
    ' . $newline; + // note: unlike other display filters this hides/shows times rather than string filtering + $display = apply_filters( 'radio_station_schedule_show_time_display', true, $show_id, 'table', $shift ); + if ( !$display ) { + $times .= ' style="display:none;"'; + } + $times .= '>' . $show_time . '
    ' . "\n"; + // 2.3.3.9: added internal spans for user time + $times .= '
    ' . "\n"; + $times .= '[' . "\n"; + $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . "\n"; + $times .= ']' . "\n"; + $times .= '
    ' . "\n"; $info['times'] = $times; - $tcount ++; + $tcount++; // --- encore airing --- // 2.3.1: added isset check for encore switch @@ -600,12 +596,11 @@ $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show_id, 'table' ); if ( 'on' == $show_encore ) { $encore = '
    '; - $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); - $encore .= '
    ' . $newline; + $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); + $encore .= '
    ' . "\n"; $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show_id, 'table' ); if ( ( '' != $encore ) && is_string( $encore ) ) { $info['encore'] = $encore; - // $cell .= $encore; } } @@ -623,15 +618,14 @@ $anchor = __( 'Audio File', 'radio-station' ); // 2.3.3.8: fix to incorrect context argument 'tabs' $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show_id, 'table' ); - $file = '
    ' . $newline; - $file .= ''; - $file .= esc_html( $anchor ); - $file .= '' . $newline; - $file .= '
    ' . $newline; + $file = '
    ' . "\n"; + $file .= ''; + $file .= esc_html( $anchor ); + $file .= '' . "\n"; + $file .= '
    ' . "\n"; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'table' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; - // $cell .= $link; } } @@ -644,7 +638,8 @@ // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { $show_excerpt = $show_post->post_excerpt; - $show_excerpt .= ' ' . $more . ''; + // 2.5.0: use esc_html on more anchor text + $show_excerpt .= ' ' . esc_html( $more ) . '' . "\n"; } else { $show_excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } @@ -653,13 +648,13 @@ $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'table' ); // --- output excerpt --- - $excerpt = '
    ' . $newline; - $excerpt .= $show_excerpt . $newline; - $excerpt .= '
    ' . $newline; - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpy, $show_id, 'table' ); + $excerpt = '
    ' . "\n"; + $excerpt .= $show_excerpt . "\n"; + $excerpt .= '
    ' . "\n"; + // 2.5.0: fix to variable typo (excerpy) + $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpt, $show_id, 'table' ); if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { $info['excerpt'] = $excerpt; - // $cell .= $excerpt; } } @@ -680,7 +675,7 @@ } } - $cell .= '
    ' . $newline; + $cell .= '' . "\n"; // 2.3.1: fix to ensure reset showcontinued flag in cell if ( isset( $shift['finished'] ) && $shift['finished'] ) { @@ -712,28 +707,28 @@ $cell .= '
    ' . $add_link . '
    '; } } - $cellclass = implode( ' ', $cellclasses ); - $table .= '' . $newline; + $cellclasslist = implode( ' ', $cellclasses ); + $table .= '' . "\n"; } } // --- close hour row --- - $table .= '' . $newline; + $table .= '' . "\n"; } -$table .= '
    ' . $newline; +$table .= '
    ' . "\n"; // 2.3.3.9: added filters for week loader controls $table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'left' ); $table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'right' ); -$table .= '' . $newline; - $table .= '
    ' . $newline; - $table .= '' . $arrows['left'] . '' . $newline; - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - if ( $atts['display_date'] ) { - $table .= '
    ' . esc_html( $date_subheading ) . '
    ' . $newline; - } - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - $table .= '' . $arrows['right'] . '' . $newline; - $table .= '
    ' . $newline; - // 2.3.2: add day start and end time date - $table .= '' . $newline; - $table .= '' . $newline; - - $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + $table .= '' . $arrows['left'] . '' . "\n"; + $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + if ( $atts['display_date'] ) { + $table .= '
    ' . esc_html( $date_subheading ) . '
    ' . "\n"; + } + $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + $table .= '' . $arrows['right'] . '' . "\n"; + $table .= '
    ' . "\n"; + // 2.3.2: add day start and end time date + $table .= '' . "\n"; + $table .= '' . "\n"; + $table .= '
    ' . $newline; - - if ( isset( $_GET['hourdebug'] ) && ( '1' == $_GET['hourdebug'] ) ) { - $table .= ''; - $table .= 'Now' . $now . '(' . date( 'H:i', $now ) . ')
    '; - $table .= 'Hour Start' . $hour_start . '(' . date( 'H:i', $hour_start ) . ')
    '; - $table .= 'Hour End' . $hour_end . '(' . date( 'H:i', $hour_end ) . ')
    '; - $table .= '
    '; - } + $table .= '
    ' . "\n"; + if ( isset( $_GET['hourdebug'] ) && ( '1' == sanitize_title( $_GET['hourdebug'] ) ) ) { + $table .= ''; + $table .= 'Now' . esc_html( $now ) . '(' . esc_html( date( 'H:i', $now ) ) . ')
    '; + $table .= 'Hour Start' . esc_html( $hour_start ) . '(' . esc_html( date( 'H:i', $hour_start ) ) . ')
    '; + $table .= 'Hour End' . esc_html( $hour_end ) . '(' . esc_html( date( 'H:i', $hour_end ) ) . ')
    '; + $table .= '
    '; + } - $table .= '
    '; - $table .= esc_html( $hour_display ); - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - $table .= '
    ' . $newline; - if ( isset( $cell ) ) { - $table .= $cell; - } - $table .= '
    ' . $newline; - $table .= '
    ' . "\n"; + $table .= '
    ' . "\n"; + if ( isset( $cell ) ) { + $table .= $cell; + } + $table .= '
    ' . "\n"; + $table .= '
    ' . $newline; +$table .= '' . "\n"; // --- hidden iframe for schedule reloading --- -$table .= '' . $newline; +$table .= '' . "\n"; -if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - $table .= '
    Shift Debug Info:
    ' . $shiftdebug . '
    '; +if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == sanitize_title( $_GET['rs-shift-debug'] ) ) ) { + $table .= '
    Shift Debug Info:
    ' . esc_html( $shiftdebug ) . '
    '; } -echo $table; \ No newline at end of file +echo $table; diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index ac61d5e..c2fc0d1 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -8,7 +8,7 @@ $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); -$today = radio_station_get_time( 'day', $now ); +$today = radio_station_get_time( 'day', $now ); // --- check if start date is set --- // 2.3.3.9: added for non-now schedule displays @@ -31,7 +31,7 @@ $shifts_separator = apply_filters( 'radio_station_show_times_separator', $shifts_separator, 'schedule-tabs' ); $time_separator = ':'; $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-tabs' ); -if ( 24 == (int) $atts['time'] ) { +if ( 24 == (int) $atts['time_format'] ) { $start_data_format = $end_data_format = 'H:i'; } else { $start_data_format = $end_data_format = 'g:i a'; @@ -59,6 +59,8 @@ $weekdays = radio_station_get_schedule_weekdays( $start_day ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $start_time ); +echo 'Show Schedule: ' . print_r( $schedule, true ) . ''; + // --- filter show avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'tabs' ); @@ -85,9 +87,9 @@ // 2.3.3.9: added filter for week loader control $load_prev = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'left' ); if ( '' != $load_prev ) { - $tabs .= '
  • ' . $newline; - $tabs .= $load_prev . $newline; - $tabs .= '
  • ' . $newline; + $tabs .= '
  • ' . "\n"; + $tabs .= $load_prev . "\n"; + $tabs .= '
  • ' . "\n"; } // --- start tabbed schedule output --- @@ -176,34 +178,34 @@ $classes[] = 'current-day'; $classes[] = 'active-day-tab'; } - $classlist = implode( ' ', $classes ); + $classlist = implode( ' ', $classes ); // 2.3.0: added left/right arrow responsive controls // 2.3.1: added (negative) return to arrow onclick functions - $tabs .= '
  • ' . $newline; - $tabs .= '
    ' . $newline; - $tabs .= '' . $arrows['left'] . '' . $newline; - $tabs .= '
    ' . $newline; + $tabs .= '
  • ' . "\n"; + $tabs .= '
    ' . "\n"; + $tabs .= '' . $arrows['left'] . '' . "\n"; + $tabs .= '
    ' . "\n"; // 2.3.2: added optional display_date attribute and subheading - $tabs .= '
    ' . $newline; + $tabs .= '
    ' . "\n"; $tabs .= '
    ' . $newline; + $tabs .= '>' . esc_html( $display_day ) . '
    ' . "\n"; if ( $atts['display_date'] ) { - $tabs .= '
    ' . esc_html( $date_subheading ) . '
    ' . $newline; + $tabs .= '
    ' . esc_html( $date_subheading ) . '
    ' . "\n"; } - $tabs .= '
    ' . $newline; + $tabs .= '
    ' . "\n"; - $tabs .= '
    ' . $newline; - $tabs .= '' . $arrows['right'] . '' . $newline; - $tabs .= '
    ' . $newline; - $tabs .= '
    ' . $newline; + $tabs .= '
    ' . "\n"; + $tabs .= '' . $arrows['right'] . '' . "\n"; + $tabs .= '
    ' . "\n"; + $tabs .= '
    ' . "\n"; // 2.3.2: add start and end day times for automatic highlighting - $tabs .= '' . $newline; - $tabs .= '' . $newline; + $tabs .= '' . "\n"; + $tabs .= '' . "\n"; $tabs .= '
  • '; // 2.2.7: separate headings from panels for tab view @@ -213,13 +215,13 @@ $classes[] = 'active-day-panel'; } $classlist = implode( ' ', $classes ); - $panels .= '
      ' . $newline; + $panels .= '
        ' . "\n"; // 2.3.2: added extra current day display // 2.3.3: use numeric day index for ID instead of weekday name $display_day = radio_station_translate_weekday( $weekday, false ); - $panels .= '
        ' . $newline; - $panels .= __( 'Viewing', 'radio-station' ) . ': ' . esc_html( $display_day ) . '
        '; + $panels .= '
        ' . "\n"; + $panels .= esc_html( __( 'Viewing', 'radio-station' ) ) . ': ' . esc_html( $display_day ) . '
        ' . "\n"; // --- get shifts for this day --- if ( isset( $schedule[$weekday] ) ) { @@ -303,8 +305,8 @@ if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { if ( !isset( $shiftdebug ) ) {$shiftdebug = '';} $shiftdebug .= 'Now: ' . $now . ' (' . radio_station_get_time( 'datetime', $now ) . ') -- Today: ' . $today . '
        '; - $shiftdebug .= 'Shift Start: ' . $shift_start . ' (' . date( 'Y-m-d l H: i', $shift_start ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start ) . ')' . '
        '; - $shiftdebug .= 'Shift End: ' . $shift_end . ' (' . date( 'Y-m-d l H: i', $shift_end ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end ) . ')' . '
        '; + $shiftdebug .= 'Shift Start: ' . $shift_start . ' (' . date( 'Y-m-d l H: i', $shift_start ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start ) . ')
        '; + $shiftdebug .= 'Shift End: ' . $shift_end . ' (' . date( 'Y-m-d l H: i', $shift_end ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end ) . ')
        '; } // 2.3.0: add genre classes for highlighting @@ -319,7 +321,7 @@ if ( 1 == $j ) { $classes[] = 'first-show'; } - if ( $j == count( $shifts ) ) { + if ( count( $shifts ) == $j ) { $classes[] = 'last-show'; } // 2.3.2: check for now playing shift @@ -333,8 +335,8 @@ } // --- open list item --- - $classlist = implode( ' ' , $classes ); - $panels .= '
      • ' . $newline; + $classlist = implode( ' ', $classes ); + $panels .= '
      • ' . "\n"; // --- Show Image --- // (defaults to display on) @@ -357,18 +359,19 @@ $classlist = implode( ' ', $classes ); if ( $show_avatar ) { - $avatar = '
        ' . $newline; + $avatar = '
        ' . "\n"; if ( $show_link ) { - $avatar .= '' . $show_avatar . '' . $newline; - } else { - $avatar .= $show_avatar; + $avatar .= ''; } - $avatar .= '
        ' . $newline; + $avatar .= $show_avatar . "\n"; + if ( $show_link ) { + $avatar .= '' . "\n"; + } + $avatar .= '
        ' . "\n"; } else { - $avatar = '
        ' . $newline; + $avatar = '
        ' . "\n"; } $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'tabs' ); - // $panels .= $avatar; } // --- show title --- @@ -377,9 +380,9 @@ } else { $show_title = esc_html( $show['name'] ); } - $title = '
        ' . $newline; - $title .= $show_title . $newline; - $title .= '
        ' . $newline; + $title = '
        ' . "\n"; + $title .= $show_title . "\n"; + $title .= '
        ' . "\n"; $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'tabs' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; @@ -404,13 +407,13 @@ $host_count = count( $show['hosts'] ); $show_hosts .= ' '; $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); - $show_hosts .= ' ' . $newline; + $show_hosts .= ' ' . "\n"; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . "\n"; } else { $show_hosts .= esc_html( $host['name'] ); } @@ -426,13 +429,12 @@ $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'tabs' ); if ( $show_hosts ) { - $hosts = '
        ' . $newline; + $hosts = '
        ' . "\n"; $hosts .= $show_hosts; - $hosts .= '
        ' . $newline; + $hosts .= '
        ' . "\n"; $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'tabs' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; - // $panels .= $hosts; } } } @@ -456,16 +458,16 @@ $end = radio_station_translate_time( $end ); // 2.3.0: filter show time by show and context - $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; - $show_time .= '' . esc_html( $end ) . '' . $newline; + $show_time = '' . esc_html( $start ) . '' . "\n"; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . "\n"; + $show_time .= '' . esc_html( $end ) . '' . "\n"; } else { // 2.3.2: added for now playing check // 2.3.3.8: moved for better conditional logic - $show_time = '' . $newline; - $show_time .= '' . $newline; + $show_time = '' . "\n"; + $show_time .= '' . "\n"; } @@ -478,33 +480,28 @@ if ( !$display ) { $times .= ' style="display:none;"'; } - $times .= '>' . $show_time . '
    ' . $newline; + $times .= '>' . $show_time . '' . "\n"; // 2.3.3.9: added internal spans for user time - $times .= '
    ' . $newline; - $times .= '[' . $newline; - $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; - $times .= ']' . $newline; - $times .= '
    ' . $newline; + $times .= '
    ' . "\n"; + $times .= '[' . "\n"; + $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . "\n"; + $times .= ']' . "\n"; + $times .= '
    ' . "\n"; $info['times'] = $times; $tcount ++; // --- encore --- // 2.3.0: filter encore switch by show and context if ( $atts['show_encore'] ) { - if ( isset( $shift['encore'] ) ) { - $show_encore = $shift['encore']; - } else { - $show_encore = false; - } + $show_encore = isset( $shift['encore'] ) ? $shift['encore'] : false; $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show_id, 'tabs' ); if ( 'on' == $show_encore ) { $encore = '
    '; $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); - $encore .= '
    ' . $newline; + $encore .= '' . "\n"; $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show_id, 'tabs' ); if ( ( '' != $encore ) && is_string( $encore ) ) { $info['encore'] = $encore; - // $panel .= $encore; } } } @@ -522,15 +519,14 @@ if ( $show_file && !empty( $show_file ) && !$disable_download ) { $anchor = __( 'Audio File', 'radio-station' ); $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show_id, 'tabs' ); - $file = '
    ' . $newline; + $file = '
    ' . "\n"; $file .= ''; $file .= esc_html( $anchor ); - $file .= '' . $newline; - $file .= '
    ' . $newline; + $file .= '' . "\n"; + $file .= '
    ' . "\n"; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'tabs' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; - // $panels .= $link; } } } @@ -538,20 +534,19 @@ // --- show genres --- // (defaults to display on) if ( $atts['show_genres'] ) { - $genres = '
    ' . $newline; + $genres = '
    ' . "\n"; $show_genres = array(); if ( count( $terms ) > 0 ) { $genres .= esc_html( __( 'Genres', 'radio-station' ) ) . ': '; foreach ( $terms as $term ) { - $show_genres[] = '' . esc_html( $term->name ) . '' . $newline; + $show_genres[] = '' . esc_html( $term->name ) . '' . "\n"; } $genres .= implode( ', ', $show_genres ); } - $genres .= '
    ' . $newline; + $genres .= '
    ' . "\n"; $genres = apply_filters( 'radio_station_schedule_show_genres', $genres, $show_id, 'tabs' ); if ( ( '' != $genres ) && is_string( $genres ) ) { $info['genres'] = $genres; - // $panels .= $genres; } } @@ -582,7 +577,7 @@ $classes[] = 'right-image'; } $classlist = implode( ' ', $classes ); - $panels .= '
    ' . $newline; + $panels .= '
    ' . "\n"; // --- add item info according to key order --- // 2.3.3.8: added for possible order rearrangement @@ -593,7 +588,7 @@ } // --- close show info section --- - $panels .= '
    ' . $newline; + $panels .= '
    ' . "\n"; // --- right aligned show avatar --- // 2.3.3.8: add image according to position @@ -626,9 +621,9 @@ $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'tabs' ); // --- output excerpt --- - $excerpt = '
    ' . $newline; - $excerpt .= $show_excerpt . $newline; - $excerpt .= '
    ' . $newline; + $excerpt = '
    ' . "\n"; + $excerpt .= $show_excerpt . "\n"; + $excerpt .= '
    ' . "\n"; $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpt, $show_id, 'tabs' ); if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { $panels .= $excerpt; @@ -642,43 +637,45 @@ if ( !$foundshows ) { // 2.3.2: added no shows class - $panels .= '
  • ' . $newline; - $panels .= esc_html( __( 'No Shows scheduled for this day.', 'radio-station' ) ); - $panels .= '
  • ' . $newline; + // 2.5.0: added filter for no shows message + $no_shows_message = __( 'No Shows scheduled for this day.', 'radio-station' ); + $no_shows_message = apply_filters( 'radio_station_schedule_no_shows_message', $no_shows_message, 'tabs' ); + $panels .= '
  • ' . "\n"; + $panels .= esc_html( $no_shows_message ); + $panels .= '
  • ' . "\n"; } } - $panels .= '' . $newline; + $panels .= '' . "\n"; } // 2.3.3.9: added filter for week loader control $load_next = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'right' ); if ( '' != $load_next ) { - $tabs .= '
  • ' . $newline; - $tabs .= $load_next . $newline; - $tabs .= '
  • ' . $newline; + $tabs .= '
  • ' . "\n"; + $tabs .= $load_next . "\n"; + $tabs .= '
  • ' . "\n"; } // --- add day tabs to output --- -$html = '
      ' . $newline; -$html .= $tabs; -$html .= '
    ' . $newline; +echo '
      ' . "\n"; +echo $tabs; +echo '
    ' . "\n"; // --- add day panels to output --- // 2.3.3.8: check for hide past shows attribute -$html .= '
    ' . "\n"; +echo $panels; +echo '
    ' . "\n"; // --- hidden iframe for schedule reloading --- -$html .= '' . $newline; +echo '' . "\n"; -if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - $html .= '
    Shift Debug Info:
    ' . $shiftdebug . '
    '; +if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == sanitize_title( $_GET['rs-shift-debug'] ) ) ) { + echo '
    Shift Debug Info:
    ' . esc_html( $shiftdebug ) . '
    '; } -echo $html; \ No newline at end of file diff --git a/templates/single-playlist-content.php b/templates/single-playlist-content.php index 9ac2ece..dba0f1a 100644 --- a/templates/single-playlist-content.php +++ b/templates/single-playlist-content.php @@ -10,13 +10,16 @@ // --- link show playlists (top) --- $post_id = get_the_ID(); $related_show = get_post_meta( $post_id, 'playlist_show_id', true ); +// 2.5.0: get allowed HTMl for filtered content +$allowed = radio_station_allowed_html( 'content', 'playlist' ); if ( $related_show ) { $show = get_post( $related_show ); $permalink = get_permalink( $show->ID ); $show_link = '' . $show->post_title . ''; $before = __( 'Playlist for Show', 'radio-station' ) . ': ' . $show_link . '
    '; $before = apply_filters( 'radio_station_link_playlist_to_show_before', $before, $post, $show ); - echo $before; + // 2.5.0: uses wp_kses on before section + echo wp_kses( $before, $allowed ); } // --- output the playlist post content --- @@ -38,16 +41,16 @@ if ( $found ) { - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; + echo '
    ' . "\n"; + echo '
    #' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Label', 'radio-station' ) ) . '' . esc_html( __( 'Comments', 'radio-station' ) ) . '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; $count = 1; foreach ( $playlist as $entry ) { @@ -59,43 +62,44 @@ // $new_class = 'class="new"'; // } - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; $count++; } } - echo '
    #' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Label', 'radio-station' ) ) . '' . esc_html( __( 'Comments', 'radio-station' ) ) . '
    ' . esc_attr( $count ) . '' . esc_html( $entry['playlist_entry_artist'] ) . '' . esc_html( $entry['playlist_entry_song'] ) . '' . esc_html( $entry['playlist_entry_album'] ) . '' . esc_html( $entry['playlist_entry_label'] ) . '' . esc_html( $entry['playlist_entry_comments'] ) . '
    ' . esc_attr( $count ) . '' . esc_html( $entry['playlist_entry_artist'] ) . '' . esc_html( $entry['playlist_entry_song'] ) . '' . esc_html( $entry['playlist_entry_album'] ) . '' . esc_html( $entry['playlist_entry_label'] ) . '' . esc_html( $entry['playlist_entry_comments'] ) . '
    ' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '' . "\n"; + echo '' . "\n"; } else { // 2.4.0.3: added text to indicate no played tracks - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; echo esc_html( __( 'No played tracks found for this Playlist yet.', 'radio-station' ) ); - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; } } else { // --- not playlist entries message --- - echo '
    ' . PHP_EOL; - echo esc_html( __( 'No entries found for this Playlist', 'radio-station' ) ) . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + echo esc_html( __( 'No entries found for this Playlist', 'radio-station' ) ) . "\n"; + echo '
    ' . "\n"; } // --- link show playlists (bottom) --- if ( $related_show ) { $show_playlists_link = '← ' . __( 'All Playlists for Show', 'radio-station' ) . ': ' . $show->post_title . ''; - $after = '
    ' . $show_playlists_link . PHP_EOL;; + $after = '
    ' . $show_playlists_link . "\n"; // 2.4.0.3: fix filter name to after not before $after = apply_filters( 'radio_station_link_playlist_to_show_after', $after, $post, $show ); - echo $after; + // 2.5.0: uses wp_kses on after section + echo wp_kses( $after, $allowed ); } diff --git a/templates/single-show-content.php b/templates/single-show-content.php index f2ae69e..ff46294 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -17,7 +17,7 @@ $post_type = $radio_station_data['show-type'] = $post->post_type; // 2.3.3.6: set new line for easier debug viewing -$newline = RADIO_STATION_DEBUG ? "\n" : ''; +// 2.5.0: remove newline variable to use standard line breaks // --- get schedule time format --- $time_format = (int) radio_station_get_setting( 'clock_time_format', $post_id ); @@ -45,12 +45,9 @@ $show_rss = false; // TEMP // 2.3.2: added show download disabled check -$show_download = true; -$download = get_post_meta( $post_id, 'show_download', true ); -if ( 'on' == $download ) { - // note: on = disabled - $show_download = false; -} +// note: on = disabled +$disable_download = get_post_meta( $post_id, 'show_download', true ); +$show_download = ( 'on' == $disable_download ) ? false : true; // --- filter all show meta data --- // 2.3.2: added show download filter @@ -97,13 +94,13 @@ if ( $show_link ) { $title = __( 'Visit Show Website', 'radio-station' ); $title = apply_filters( 'radio_station_show_website_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . "\n"; $icon = apply_filters( 'radio_station_show_home_icon', $icon, $post_id ); - $show_icons['home'] = '
    ' . $newline; - $show_icons['home'] .= '' . $newline; - $show_icons['home'] .= $icon; - $show_icons['home'] .= '' . $newline; - $show_icons['home'] .= '
    ' . $newline; + $show_icons['home'] = '
    ' . "\n"; + $show_icons['home'] .= '' . "\n"; + $show_icons['home'] .= $icon . "\n"; + $show_icons['home'] .= '' . "\n"; + $show_icons['home'] .= '
    ' . "\n"; } // --- phone number icon --- @@ -112,13 +109,13 @@ if ( $show_phone ) { $title = __( 'Call in Phone Number', 'radio-station' ); $title = apply_filters( 'radio_station_show_phone_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . "\n"; $icon = apply_filters( 'radio_station_show_phone_icon', $icon, $post_id ); - $show_icons['phone'] = '
    ' . $newline; - $show_icons['phone'] .= '' . $newline; - $show_icons['phone'] .= $icon . $newline; - $show_icons['phone'] .= '' . $newline; - $show_icons['phone'] .= '
    ' . $newline; + $show_icons['phone'] = '
    ' . "\n"; + $show_icons['phone'] .= '' . "\n"; + $show_icons['phone'] .= $icon . "\n"; + $show_icons['phone'] .= '' . "\n"; + $show_icons['phone'] .= '
    ' . "\n"; } // --- email DJ / host icon --- @@ -127,13 +124,13 @@ if ( $show_email ) { $title = __( 'Email Show Host', 'radio-station' ); $title = apply_filters( 'radio_station_show_email_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . "\n"; $icon = apply_filters( 'radio_station_show_email_icon', $icon, $post_id ); - $show_icons['email'] = '
    ' . $newline; - $show_icons['email'] .= '' . $newline; - $show_icons['email'] .= $icon . $newline; - $show_icons['email'] .= '' . $newline; - $show_icons['email'] .= '
    ' . $newline; + $show_icons['email'] = '
    ' . "\n"; + $show_icons['email'] .= '' . "\n"; + $show_icons['email'] .= $icon . "\n"; + $show_icons['email'] .= '' . "\n"; + $show_icons['email'] .= '
    ' . "\n"; } // --- show RSS feed icon --- @@ -141,15 +138,15 @@ // 2.3.3.8: added aria label to link and hidden to span icon if ( $show_rss ) { $feed_url = radio_station_get_show_rss_url( $post_id ); - $title = __( 'Show RSS Feed', 'radio-station' ); + $title = __( 'Show RSS Feed', 'radio-station' ); $title = apply_filters( 'radio_station_show_rss_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . "\n"; $icon = apply_filters( 'radio_station_show_rss_icon', $icon, $post_id ); - $show_icons['rss'] = '
    ' . $newline; - $show_icons['rss'] .= '' . $newline; - $show_icons['rss'] .= $icon . $newline; - $show_icons['rss'] .= '' . $newline; - $show_icons['rss'] .= '
    ' . $newline; + $show_icons['rss'] = '
    ' . "\n"; + $show_icons['rss'] .= '' . "\n"; + $show_icons['rss'] .= $icon . "\n"; + $show_icons['rss'] .= '' . "\n"; + $show_icons['rss'] .= '
    ' . "\n"; } // --- filter show icons --- @@ -213,18 +210,18 @@ } else { $class = ''; } - $blocks['show_images'] = '
    ' . $newline; - $blocks['show_images'] .= $show_avatar; - $blocks['show_images'] .= '
    ' . $newline; + $blocks['show_images'] = '
    ' . "\n"; + $blocks['show_images'] .= $show_avatar . "\n"; + $blocks['show_images'] .= '
    ' . "\n"; } } // --- Show Icons --- // 2.3.3.9: remove unnecessary image block condition check if ( count( $show_icons ) > 0 ) { - $image_blocks['icons'] = '
    ' . $newline; - $image_blocks['icons'] .= implode( $newline, $show_icons ); - $image_blocks['icons'] .= '
    ' . $newline; + $image_blocks['icons'] = '
    ' . "\n"; + $image_blocks['icons'] .= implode( "\n", $show_icons ); + $image_blocks['icons'] .= '
    ' . "\n"; } // --- Social Icons --- @@ -232,20 +229,20 @@ if ( $social_icons ) { $social_icons = apply_filters( 'radio_station_show_social_icons_display', '' ); if ( '' != $social_icons ) { - $image_blocks['social'] = '' . $newline; + $image_blocks['social'] = '' . "\n"; } } // --- Show Patreon Button --- $patreon_button = ''; if ( $show_patreon ) { - $patreon_button .= '
    ' . $newline; + $patreon_button .= '
    ' . "\n"; $title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; $title = apply_filters( 'radio_station_show_patreon_title', $title, $post_id ); $patreon_button .= radio_station_patreon_button( $show_patreon, $title ); - $patreon_button .= '
    ' . $newline; + $patreon_button .= '
    ' . "\n"; } // 2.3.1: added filter for patreon button $patreon_button = apply_filters( 'radio_station_show_patreon_button', $patreon_button, $post_id ); @@ -259,16 +256,16 @@ // 2.3.3.4: add filter for title text on Show Download link icon if ( $show_file ) { - $image_blocks['player'] = '
    ' . $newline; + $image_blocks['player'] = '
    ' . "\n"; $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); if ( $label && ( '' != $label ) ) { $image_blocks['player'] .= '' . esc_html( $label ) . '
    '; } $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; $player_embed = do_shortcode( $shortcode ); - $image_blocks['player'] .= '
    ' . $newline; - $image_blocks['player'] .= $player_embed . $newline; - $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= '
    ' . "\n"; + $image_blocks['player'] .= $player_embed . "\n"; + $image_blocks['player'] .= '
    ' . "\n"; // --- Download Audio Icon --- // 2.3.2: check show download switch @@ -276,14 +273,14 @@ if ( $show_download ) { $title = __( 'Download Latest Broadcast', 'radio-station' ); $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); - $image_blocks['player'] .= '
    ' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= '
    ' . "\n"; + $image_blocks['player'] .= '' . "\n"; + $image_blocks['player'] .= '' . "\n"; + $image_blocks['player'] .= '' . "\n"; + $image_blocks['player'] .= '
    ' . "\n"; } - $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= '
    ' . "\n"; } // 2.3.3.6: allow subblock order to be changed @@ -304,7 +301,7 @@ $blocks['show_images'] .= $image_blocks[$image_block]; } } - $blocks['show_images'] .= '' . $newline; + $blocks['show_images'] .= '' . "\n"; } @@ -321,16 +318,16 @@ // 2.3.3.4: added class to show info label tag $label = __( 'Show Info', 'radio-station' ); $label = apply_filters( 'radio_station_show_info_label', $label, $post_id ); - $blocks['show_meta'] = '

    ' . esc_html( $label ) . '

    ' . $newline; + $blocks['show_meta'] = '

    ' . esc_html( $label ) . '

    ' . "\n"; // --- Show DJs / Hosts --- // 2.3.3.4: added filter for hosted by label // 2.3.3.4: replace bold title tag with span and class if ( $hosts ) { - $meta_blocks['hosts'] = '
    ' . $newline; + $meta_blocks['hosts'] = '
    ' . "\n"; $label = __( 'Hosted by', 'radio-station' ); $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); - $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . "\n"; $count = 0; // 2.4.0.4: convert possible (old) non-array values if ( !is_array( $hosts ) ) { @@ -348,17 +345,17 @@ } $meta_blocks['hosts'] .= esc_html( $user_info->display_name ); if ( $host_url ) { - $meta_blocks['hosts'] .= '' . $newline; + $meta_blocks['hosts'] .= '' . "\n"; } if ( ( ( 1 === $count ) && ( 2 === $host_count ) ) - || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) === $count ) ) ) { + || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) === $count ) ) ) { $meta_blocks['hosts'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( count( $hosts ) > 2 ) && ( $count < count( $hosts ) ) ) { $meta_blocks['hosts'] .= ', '; } } - $meta_blocks['hosts'] .= '
    ' . $newline; + $meta_blocks['hosts'] .= '
    ' . "\n"; } // --- Show Producers --- @@ -366,14 +363,14 @@ // 2.3.3.4: added filter for produced by label // 2.3.3.4: replace bold title tag with span and class if ( $producers ) { - $meta_blocks['producers'] = '
    ' . $newline; + $meta_blocks['producers'] = '
    ' . "\n"; $label = __( 'Produced by', 'radio-station' ); $label = apply_filters( 'radio_station_show_producers_label', $label, $post_id ); - $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . "\n"; $count = 0; // 2.4.0.4: convert possible (old) non-array values if ( !is_array( $producers ) ) { - $producers = array( $producers ); + $producers = array( $producers ); } $producer_count = count( $producers ); foreach ( $producers as $producer ) { @@ -387,17 +384,17 @@ } $meta_blocks['producers'] .= esc_html( $user_info->display_name ); if ( $producer_url ) { - $meta_blocks['producers'] .= '' . $newline; + $meta_blocks['producers'] .= '' . "\n"; } if ( ( ( 1 === $count ) && ( 2 === $producer_count ) ) - || ( ( $producer_count > 2 ) && ( ( $producer_count - 1 ) === $count ) ) ) { + || ( ( $producer_count > 2 ) && ( ( $producer_count - 1 ) === $count ) ) ) { $meta_blocks['producers'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( count( $producers ) > 2 ) && ( $count < count( $producers ) ) ) { $meta_blocks['producers'] .= ', '; } } - $meta_blocks['producers'] .= '
    ' . $newline; + $meta_blocks['producers'] .= '
    ' . "\n"; } // --- Show Genre(s) --- @@ -412,15 +409,15 @@ $label = $tax_object->labels->name; } $label = apply_filters( 'radio_station_show_genres_label', $label, $post_id ); - $meta_blocks['genres'] = '
    ' . $newline; - $meta_blocks['genres'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['genres'] = '
    ' . "\n"; + $meta_blocks['genres'] .= '' . esc_html( $label ) . ': ' . "\n"; $genre_links = array(); foreach ( $genres as $genre ) { $genre_link = get_term_link( $genre ); - $genre_links[] = '' . esc_html( $genre->name ) . '' . $newline; + $genre_links[] = '' . esc_html( $genre->name ) . '' . "\n"; } - $meta_blocks['genres'] .= implode( ', ', $genre_links ) . $newline; - $meta_blocks['genres'] .= '
    ' . $newline; + $meta_blocks['genres'] .= implode( ', ', $genre_links ) . "\n"; + $meta_blocks['genres'] .= '
    ' . "\n"; } // --- Show Language(s) --- @@ -435,8 +432,8 @@ $label = $tax_object->labels->name; } $label = apply_filters( 'radio_station_show_languages_label', $label, $post_id ); - $meta_blocks['languages'] = '
    ' . $newline; - $meta_blocks['languages'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['languages'] = '
    ' . "\n"; + $meta_blocks['languages'] .= '' . esc_html( $label ) . ': ' . "\n"; $language_links = array(); foreach ( $languages as $language ) { $lang_label = $language->name; @@ -444,10 +441,10 @@ $lang_label .= ' (' . $language->description . ')'; } $language_link = get_term_link( $language ); - $language_links[] = '' . esc_html( $lang_label ) . '' . $newline; + $language_links[] = '' . esc_html( $lang_label ) . '' . "\n"; } - $meta_blocks['languages'] .= implode( ', ', $language_links ) . $newline; - $meta_blocks['languages'] .= '
    ' . $newline; + $meta_blocks['languages'] .= implode( ', ', $language_links ) . "\n"; + $meta_blocks['languages'] .= '
    ' . "\n"; } // --- Show Phone --- @@ -455,8 +452,8 @@ $meta_blocks['phone'] = '
    '; $label = __( 'Call in', 'radio-station' ); $label = apply_filters( 'radio_station_show_phone_label', $label, $post_id ); - $meta_blocks['phone'] .= '' . esc_html( $label ) . ': ' . $newline; - $meta_blocks['phone'] .= '' . esc_html( $show_phone ) . ''; + $meta_blocks['phone'] .= '' . esc_html( $label ) . ': ' . "\n"; + $meta_blocks['phone'] .= '' . esc_html( $show_phone ) . ''; $meta_blocks['phone'] .= '
    '; } @@ -466,8 +463,8 @@ $meta_block_order = array( 'hosts', 'producers', 'genres', 'languages', 'phone' ); $meta_block_order = apply_filters( 'radio_station_show_meta_block_order', $meta_block_order, $post_id ); if ( RADIO_STATION_DEBUG ) { - echo 'Meta Block Order: ' . print_r( $meta_block_order, true ) . ''; - echo ''; + echo 'Meta Block Order: ' . esc_html( print_r( $meta_block_order, true ) ) . '' . "\n"; + echo '' . "\n"; } // --- combine meta blocks to show meta block --- @@ -501,7 +498,7 @@ // --- show times title --- $label = __( 'Show Times', 'radio-station' ); $label = apply_filters( 'radio_station_show_times_label', $label, $post_id ); -$blocks['show_times'] = '

    ' . esc_html( $label ) . '

    ' . $newline; +$blocks['show_times'] = '

    ' . esc_html( $label ) . '

    ' . "\n"; // --- check if show is active and has shifts --- if ( !$active || !$shifts ) { @@ -547,24 +544,24 @@ // 2.3.3.9: added span wrap with class for actual timezone $label = __( 'Timezone', 'radio-station' ); $label = apply_filters( 'radio_station_show_timezone_label', $label, $post_id ); - $blocks['show_times'] .= '' . esc_html( $label ) . ': ' . $newline; + $blocks['show_times'] .= '' . esc_html( $label ) . ': ' . "\n"; if ( !isset( $timezone_code ) ) { $blocks['show_times'] .= ''; $blocks['show_times'] .= esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset; - $blocks['show_times'] .= ''; + $blocks['show_times'] .= ''; } else { $blocks['show_times'] .= ''; $blocks['show_times'] .= esc_html( $timezone_code ); $blocks['show_times'] .= ''; $blocks['show_times'] .= ''; $blocks['show_times'] .= ' [' . esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset . ']'; - $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '' . "\n"; } // TODO: --- display user timezone --- // $block['show_times'] .= ... - $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '
    ' . "\n"; $found_encore = false; $weekdays = radio_station_get_schedule_weekdays(); @@ -603,27 +600,27 @@ $class = implode( ' ', $classes ); // 2.4.0.6: use filtered shift separator - $separator = ' - '; + $separator = ' - '; $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'show-content' ); // --- set show time output --- // 2.3.4: fix to start data_format attribute - $show_time = '
    ' . $newline; - $show_time .= '' . esc_html( $start_display ) . '' . $newline; - $show_time .= ' - ' . $newline; - $show_time .= '' . esc_html( $end_display ) . '' . $newline; + $show_time = '
    ' . "\n"; + $show_time .= '' . esc_html( $start_display ) . '' . "\n"; + $show_time .= ' - ' . "\n"; + $show_time .= '' . esc_html( $end_display ) . '' . "\n"; if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { $found_encore = true; - $show_time .= '*' . $newline; + $show_time .= '*' . "\n"; } - $show_time .= '
    ' . $newline; + $show_time .= '
    ' . "\n"; // 2.3.3.9: add user show time div - $show_time .= '
    ' . $newline; - $show_time .= '[' . $newline; - $show_time .= '' . esc_html( $separator ) . '' . $newline; - $show_time .= ']' . $newline; - $show_time .= '
    ' . $newline; + $show_time .= '
    ' . "\n"; + $show_time .= '[' . "\n"; + $show_time .= '' . esc_html( $separator ) . '' . "\n"; + $show_time .= ']' . "\n"; + $show_time .= '
    ' . "\n"; $show_times[] = $show_time; } @@ -631,34 +628,32 @@ } $show_times_count = count( $show_times ); if ( $show_times_count > 0 ) { - $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '' . "\n"; } } // --- * encore note --- // 2.3.3.4: added filter for encore label if ( $found_encore ) { - $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '' . "\n"; } - $blocks['show_times'] .= '
    ' . $newline; + $blocks['show_times'] .= '' . "\n"; $weekday = radio_station_translate_weekday( $day, true ); - $blocks['show_times'] .= '' . esc_html( $weekday ) . ': ' . $newline; - $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '' . esc_html( $weekday ) . ': ' . "\n"; + $blocks['show_times'] .= '' . "\n"; foreach ( $show_times as $i => $show_time ) { - $blocks['show_times'] .= '' . $show_time . '' . $newline; + $blocks['show_times'] .= '' . $show_time . '' . "\n"; // if ( $i < ( $show_times_count - 1 ) ) { // $blocks['show_times'] .= '
    '; // } } - $blocks['show_times'] .= '
    ' . $newline; - $blocks['show_times'] .= '* ' . $newline; - $blocks['show_times'] .= ''; $label = __( 'Encore Presentation', 'radio-station' ); $label = apply_filters( 'radio_station_show_encore_label', $label, $post_id ); - $blocks['show_times'] .= esc_html( $label ); - $blocks['show_times'] .= '' . $newline; - $blocks['show_times'] .= '
    ' . "\n"; + $blocks['show_times'] .= '* ' . "\n"; + $blocks['show_times'] .= '' . esc_html( $label ) . '' . "\n"; + $blocks['show_times'] .= '
    ' . $newline; + $blocks['show_times'] .= '' . "\n"; } // 2.3.3.9: maybe output linked override times @@ -670,7 +665,7 @@ $now = radio_station_get_now(); foreach ( $overrides as $override ) { if ( 'yes' != $override['disabled'] ) { - + $start = $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; $end = $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; $override_start_time = radio_station_to_time( $start ); @@ -690,29 +685,29 @@ $date = radio_station_get_time( $date_format, $date_time ); // 2.4.0.6: use filtered shift separator - $separator = ' - '; + $separator = ' - '; $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); - $scheduled .= '
    ' . $newline; - $scheduled .= '' . esc_html( $date ) . '' . $newline; - $scheduled .= '' . esc_html( $start_display ) . '' . $newline; - $scheduled .= '' . esc_html( $separator ) . '' . $newline; - $scheduled .= '' . esc_html( $end_display ) . '' . $newline; - $scheduled .= '
    ' . $newline; - - $scheduled .= '
    ' . $newline; - $scheduled .= '[' . $newline; - $scheduled .= '' . $newline; - $scheduled .= '' . esc_html( $separator ) . '' . $newline; - $scheduled .= ']' . $newline; - $scheduled .= '
    ' . $newline; + $scheduled .= '
    ' . "\n"; + $scheduled .= '' . esc_html( $date ) . '' . "\n"; + $scheduled .= '' . esc_html( $start_display ) . '' . "\n"; + $scheduled .= '' . esc_html( $separator ) . '' . "\n"; + $scheduled .= '' . esc_html( $end_display ) . '' . "\n"; + $scheduled .= '
    ' . "\n"; + + $scheduled .= '
    ' . "\n"; + $scheduled .= '[' . "\n"; + $scheduled .= '' . "\n"; + $scheduled .= '' . esc_html( $separator ) . '' . "\n"; + $scheduled .= ']' . "\n"; + $scheduled .= '
    ' . "\n"; } } } } if ( '' != $scheduled ) { - $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    '; - $blocks['show_times'] .= $scheduled . '
    '; + $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    ' . "\n"; + $blocks['show_times'] .= $scheduled . '
    ' . "\n"; } // --- maybe add link to full schedule page --- @@ -720,14 +715,14 @@ $schedule_page = radio_station_get_setting( 'schedule_page' ); if ( $schedule_page && !empty( $schedule_page ) ) { $schedule_link = get_permalink( $schedule_page ); - $blocks['show_times'] .= '' . "\n"; } // --- filter all show info blocks --- @@ -744,17 +739,17 @@ // -------------------- $show_description = false; if ( strlen( trim( $content ) ) > 0 ) { - $show_description = '
    ' . $content . '
    ' . $newline; - $show_description .= '
    ' . $newline; - $show_desc_buttons = '
    ' . $newline; + $show_description = '
    ' . $content . '
    ' . "\n"; + $show_description .= '
    ' . "\n"; + $show_desc_buttons = '
    ' . "\n"; $label = __( 'Show More', 'radio-station' ); $label = apply_filters( 'radio_station_show_more_label', $label, $post_id ); - $show_desc_buttons .= ' ' . $newline; + $show_desc_buttons .= ' ' . "\n"; $label = __( 'Show Less', 'radio-station' ); $label = apply_filters( 'radio_station_show_less_label', $label, $post_id ); - $show_desc_buttons .= ' ' . $newline; - $show_desc_buttons .= ' ' . $newline; - $show_desc_buttons .= '
    ' . $newline; + $show_desc_buttons .= ' ' . "\n"; + $show_desc_buttons .= ' ' . "\n"; + $show_desc_buttons .= '
    ' . "\n"; } // ------------- @@ -770,21 +765,21 @@ // 2.3.3.4: added filter for show description label and anchor if ( $show_description ) { - $sections['about']['heading'] = '' . $newline; + $sections['about']['heading'] = '' . "\n"; $label = __( 'About the %s', 'radio-station' ); $label = sprintf( $label, $show_label ); $label = apply_filters( 'radio_station_show_description_label', $label, $post_id ); - $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . "\n"; $anchor = __( 'About', 'radio-station' ); $anchor = apply_filters( 'radio_station_show_description_anchor', $anchor, $post_id ); $sections['about']['anchor'] = $anchor; - $sections['about']['content'] = '

    ' . $newline; - $sections['about']['content'] .= '
    ' . $newline; + $sections['about']['content'] = '

    ' . "\n"; + $sections['about']['content'] .= '
    ' . "\n"; $sections['about']['content'] .= $show_description; - $sections['about']['content'] .= '
    ' . $newline; + $sections['about']['content'] .= '
    ' . "\n"; $sections['about']['content'] .= $show_desc_buttons; - $sections['about']['content'] .= '
    ' . $newline; + $sections['about']['content'] .= '
    ' . "\n"; } // --- Show Episodes Tab --- @@ -798,19 +793,19 @@ // 2.3.3.9: get label from post type object $posts_type = get_post_type_object( 'post' ); $posts_label = $posts_type->labels->name; - $sections['posts']['heading'] = '' . $newline; + $sections['posts']['heading'] = '' . "\n"; $label = $show_label . ' ' . $posts_label; $label = apply_filters( 'radio_station_show_posts_label', $label, $post_id ); - $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . "\n"; $anchor = apply_filters( 'radio_station_show_posts_anchor', $posts_label, $post_id ); $sections['posts']['anchor'] = $anchor; $radio_station_data['show-posts'] = $show_posts; - $sections['posts']['content'] = '

    ' . $newline; + $sections['posts']['content'] = '

    ' . "\n"; $shortcode = '[show-posts-archive per_page="' . $posts_per_page . '" show="' . $post_id . '"]'; $shortcode = apply_filters( 'radio_station_show_page_posts_shortcode', $shortcode, $post_id ); $sections['posts']['content'] .= do_shortcode( $shortcode ); - $sections['posts']['content'] .= '
    ' . $newline; + $sections['posts']['content'] .= '
    ' . "\n"; } // --- Show Playlists Tab --- @@ -824,16 +819,16 @@ $label = $show_label . ' ' . $playlist_label; // 2.3.3.9: fix to filter name (replace dash with underscore) $label = apply_filters( 'radio_station_show_playlists_label', $label, $post_id ); - $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . "\n"; $anchor = apply_filters( 'radio_station_show_playlists_anchor', $playlist_label, $post_id ); $sections['playlists']['anchor'] = $anchor; $radio_station_data['show-playlists'] = $show_playlists; - $sections['playlists']['content'] = '

    ' . $newline; + $sections['playlists']['content'] = '

    ' . "\n"; $shortcode = '[show-playlists-archive per_page="' . $playlists_per_page . '" show="' . $post_id . '"]'; $shortcode = apply_filters( 'radio_station_show_page_playlists_shortcode', $shortcode, $post_id ); $sections['playlists']['content'] .= do_shortcode( $shortcode ); - $sections['playlists']['content'] .= '
    ' . $newline; + $sections['playlists']['content'] .= '
    ' . "\n"; } // 2.3.3.8: remove duplicate post_id filter argument @@ -851,9 +846,9 @@ $class = $block_position . '-blocks'; } -echo '' . $newline; -echo '
    ' . $newline; -echo '' . $newline; +// echo '' . "\n"; +echo '
    ' . "\n"; +echo '' . "\n"; // --- Show Header --- // 2.3.0: added new optional show header display @@ -874,7 +869,7 @@ // --- Show Info Blocks --- // 2.3.3.4: add show-info element ID to div tag - echo '
    ' . $newline; + echo '
    ' . "\n"; // --- filter block order --- $block_order = array( 'show_images', 'show_meta', 'show_times' ); @@ -896,36 +891,36 @@ $class = implode( ' ', $classes ); // --- output blocks --- - echo '
    ' . $newline; + echo '
    ' . "\n"; // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $blocks[$block] . $newline; - echo '
    ' . $newline; + echo $blocks[$block] . "\n"; + echo '
    ' . "\n"; $first = ''; } } } - echo '
    ' . $newline; + echo '
    ' . "\n"; - echo '
    ' . $newline; + echo '
    ' . "\n"; // --- Display Latest Show Posts --- if ( $show_latest ) { $label = __( 'Latest Show Posts', 'radio-station' ); $label = apply_filters( 'radio_station_show_latest_posts_label', $label, $post_id ); - echo '
    ' . $newline; - echo '
    ' . $newline; - echo '' . esc_html( $label ) . '' . $newline; - echo '
    ' . $newline; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . esc_html( $label ) . '' . "\n"; + echo '
    ' . "\n"; $radio_station_data['show-latests'] = $show_latest; $shortcode = '[show-latest-archive show="' . $post_id . '" thumbnails="0" pagination="0" content="none"]'; $shortcode = apply_filters( 'radio_station_show_page_latest_shortcode', $shortcode, $post_id ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo do_shortcode( $shortcode ); - echo '
    ' . $newline; + echo '
    ' . "\n"; } @@ -936,7 +931,7 @@ // --- Display Show Sections --- // 2.3.0: filter show sections for display if ( ( is_array( $sections ) && ( count( $sections ) > 0 ) ) - && is_array( $section_order ) && ( count( $section_order ) > 0 ) ) { + && is_array( $section_order ) && ( count( $section_order ) > 0 ) ) { // --- tabs for tabbed layout --- if ( 'tabbed' == $section_layout ) { @@ -950,7 +945,7 @@ } unset( $section_order[0] ); - echo '
    ' . $newline; + echo '
    ' . "\n"; $i = 0; $found_section = false; @@ -958,11 +953,11 @@ if ( isset( $sections[$section] ) ) { $found_section = true; $class = ( 0 == $i ) ? 'tab-active' : 'tab-inactive'; - echo '
    ' . $newline; - echo esc_html( $sections[$section]['anchor'] ) . $newline; - echo '
    ' . $newline; + echo '
    ' . "\n"; + echo esc_html( $sections[$section]['anchor'] ) . "\n"; + echo '
    ' . "\n"; if ( ( $i + 1 ) < count( $sections ) ) { - echo '
     
    ' . $newline; + echo '
     
    ' . "\n"; } $i++; } @@ -970,12 +965,12 @@ // 2.3.3.9: add end tab right spacer if ( $found_section ) { // 2.4.0.6: add class to last spacer - echo '
     
    ' . $newline; + echo '
     
    ' . "\n"; } - echo '
    ' . $newline; + echo '
    ' . "\n"; } - echo '
    ' . $newline; + echo '
    ' . "\n"; $i = 0; foreach ( $section_order as $section ) { if ( isset( $sections[$section] ) ) { @@ -988,8 +983,8 @@ // --- section jump links --- if ( 'yes' == $jump_links ) { - echo '' . "\n"; } } else { @@ -1018,17 +1013,16 @@ // --- section content --- // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $sections[$section]['content'] . $newline; - - $i ++; + echo $sections[$section]['content'] . "\n"; + $i++; } } - echo '
    ' . $newline; + echo '
    ' . "\n"; } - echo '
    ' . $newline; -echo '
    ' . $newline; -echo '' . $newline; + echo '
    ' . "\n"; +echo '
    ' . "\n"; +// echo '' . "\n"; // --- enqueue show page script --- // 2.3.0: enqueue script instead of echoing diff --git a/includes/class-current-playlist-widget.php b/widgets/class-current-playlist-widget.php similarity index 61% rename from includes/class-current-playlist-widget.php rename to widgets/class-current-playlist-widget.php index 88ab9ac..e491dcd 100644 --- a/includes/class-current-playlist-widget.php +++ b/widgets/class-current-playlist-widget.php @@ -27,99 +27,142 @@ public function form( $instance ) { // 2.3.0: added countdown display option // 2.3.2: added AJAX load option // 2.3.3.8: added playlist link option + // 2.5.0: added no_playlist text option + // --- widget display options --- $title = $instance['title']; - $link = isset( $instance['link'] ) ? $instance['link'] : true; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : true; + // --- playlist display options --- + $link_playlist = isset( $instance['link'] ) ? $instance['link'] : true; + $no_playlist = isset( $instance['no_playlist'] ) ? $instance['no_playlist'] : ''; + $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; + // --- track display options --- $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; $song = isset( $instance['song'] ) ? $instance['song'] : true; $album = isset( $instance['album'] ) ? $instance['album'] : false; $label = isset( $instance['label'] ) ? $instance['label'] : false; $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : true; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; // 2.3.0: convert template style code to strings // 2.3.2: added AJAX load option field // 2.3.3.8: added playlist link field - $fields = ' -

    + $fields = array(); + + // --- Widget Title --- + $fields['title'] = '

    + +

    '; + + // --- AJAX Loading --- + $fields['ajax'] = '

    -

    +

    '; -

    -

    +
    + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

    '; + + // --- Hide if Empty --- + $fields['hide_empty'] = '

    + -

    +

    '; + + // === Playlist Display Options === -

    + // --- Link to Playlist --- + $fields['link_playlist'] = '

    -

    +

    '; + + // --- No Playlist Text --- + $fields['no_playlist'] = '

    + + ' . esc_html( __( 'Empty for default, 0 for none.', 'radio-station' ) ) . ' +

    '; -

    + // --- Countdown --- + $fields['countdown'] = '

    + +

    '; + + // === Track Display Options === + + // --- Display Song --- + $fields['song'] = '

    -

    +

    '; -

    + // --- Display Artist --- + $fields['artist'] = '

    -

    +

    '; -

    + // --- Display Album --- + $fields['album'] = '

    -

    +

    '; -

    + // --- Display Label --- + $fields['label'] = '

    -

    +

    '; -

    + // --- Display Comments --- + $fields['comments'] = '

    -

    - -

    - -

    - -

    -

    '; // --- filter and output --- // 2.3.0: filter to allow for extra fields - $fields = apply_filters( 'radio_station_playlist_widget_fields', $fields, $this, $instance ); - echo $fields; + // 2.5.0: added filter for array fields for ease of adding fields + $fields = apply_filters( 'radio_station_playlist_widget_fields_list', $fields, $this, $instance ); + $fields_html = implode( "\n", $fields ); + $fields_html = apply_filters( 'radio_station_playlist_widget_fields', $fields_html, $this, $instance ); + // 2.5.0: use wp_kses on field settings output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $fields_html, $allowed ); } // --- update widget instance --- @@ -131,16 +174,21 @@ public function update( $new_instance, $old_instance ) { // 2.3.0: added countdown display option // 2.3.2: added AJAX load option // 2.3.3.8: added playlist link option + // 2.5.0: added no_playlist text option + // --- widget display options --- $instance['title'] = $new_instance['title']; + $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? 1 : 0; + // --- playlist display options --- $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; + $instance['no_playlist'] = isset ( $new_instance['no_playlist'] ) ? $new_instance['no_playlist'] : ''; + $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; + // --- track display options --- $instance['artist'] = isset( $new_instance['artist'] ) ? 1 : 0; $instance['song'] = isset( $new_instance['song'] ) ? 1 : 0; $instance['album'] = isset( $new_instance['album'] ) ? 1 : 0; $instance['label'] = isset( $new_instance['label'] ) ? 1 : 0; $instance['comments'] = isset( $new_instance['comments'] ) ? 1 : 0; - $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; // 2.3.0: apply filters to widget instance update $instance = apply_filters( 'radio_station_playlist_widget_update', $instance, $new_instance, $old_instance ); @@ -154,48 +202,58 @@ public function widget( $args, $instance ) { // --- set widget id --- // 2.3.0: added unique widget id + // 2.5.0: simplify widget id setting if ( !isset( $radio_station_data['widgets']['current-playlist'] ) ) { - $id = $radio_station_data['widgets']['current-playlist'] = 0; - } else { - $id = $radio_station_data['widgets']['current-playlist']++; + $radio_station_data['widgets']['current-playlist'] = 0; } + $radio_station_data['widgets']['current-playlist']++; + $id = $radio_station_data['widgets']['current-playlist']; // 2.3.0: filter widget_title whether empty or not // 2.3.0: added hide widget if empty option // 2.3.0: added countdown display option // 2.3.2: set fallback options to numeric for shortcode // 2.3.2: added AJAX load option + // --- widget display options --- $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 0; + // --- playlist display options --- $link = isset( $instance['link'] ) ? $instance['link'] : 1; - $artist = $instance['artist']; + $no_playlist = isset( $instance['no_playlist'] ) ? $instance['no_playlist'] : ''; + $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; + // --- track display options --- $song = $instance['song']; + $artist = $instance['artist']; $album = $instance['album']; $label = $instance['label']; $comments = $instance['comments']; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 1; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; // --- set shortcode attributes for display --- + // 2.3.2: only set AJAX attribute if overriding default + // 2.5.0: set AJAX attribute anyway (checked in shortcode) + // 2.5.0: added no_playlist and hide_empty attributes + // 2.5.0: removed title attribute (only used for shortcodes) $atts = array( - 'title' => $title, - 'link' => $link, - 'artist' => $artist, - 'song' => $song, - 'album' => $album, - 'label' => $label, - 'comments' => $comments, - 'countdown' => $countdown, - 'widget' => 1, - 'id' => $id, + // --- widget display options --- + 'ajax' => $ajax, + 'hide_empty' => $hide_empty, + // --- playlist display options --- + 'link' => $link, + 'no_playlist' => '', + 'countdown' => $countdown, + // --- track display options --- + 'artist' => $artist, + 'song' => $song, + 'album' => $album, + 'label' => $label, + 'comments' => $comments, + // --- widget data --- + 'widget' => 1, + 'id' => $id, ); - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - // 2.3.3.9: add filter for default widget attributes $atts = apply_filters( 'radio_station_current_playlist_widget_atts', $atts, $instance ); @@ -210,47 +268,50 @@ public function widget( $args, $instance ) { // 2.3.0: added hide widget if empty option if ( !$hide_empty || ( $hide_empty && $output ) ) { + // 2.5.0: get context filtered allowed HTML + $allowed = radio_station_allowed_html( 'widget', 'current-playlist' ); + // --- beore widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_widget'], $allowed ); // --- open widget container --- // 2.3.0: add unique id to widget // 2.3.2: add class to widget // 2.4.0.1: add current-playlist-widget class - echo '
    '; - - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - - // --- check for widget output override --- - // 2.3.3.9: added missing override filter - $output = apply_filters( 'radio_station_current_playlist_widget_override', $output, $args, $atts ); - - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - // --- close widget contents wrapper --- - echo '
    '; + echo '
    ' . "\n"; + + // --- output widget title --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_title'], $allowed ); + if ( !empty( $title ) ) { + echo wp_kses( $title, $allowed ); + } + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_title'], $allowed ); + + // 2.3.3.9: add div wrapper for widget contents + echo '
    ' . "\n"; + + // --- check for widget output override --- + // 2.3.3.9: added missing override filter + // 2.5.0: remove duplicated override filter + + // --- output widget display --- + // TODO: test wp_kses on shortcode output + // echo wp_kses( $output, $allowed ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; + + // --- close widget contents wrapper --- + echo '
    ' . "\n"; // --- close widget container --- - echo '
    '; + echo '
    ' . "\n"; // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_widget'], $allowed ); // --- enqueue widget stylesheet in footer --- // (this means it will only load if widget is on page) @@ -270,3 +331,4 @@ function radio_station_register_current_playlist_widget() { // note: widget class name to remain unchanged for backwards compatibility register_widget( 'Playlist_Widget' ); } + diff --git a/includes/class-current-show-widget.php b/widgets/class-current-show-widget.php similarity index 50% rename from includes/class-current-show-widget.php rename to widgets/class-current-show-widget.php index e4c5add..1f6b58e 100644 --- a/includes/class-current-show-widget.php +++ b/widgets/class-current-show-widget.php @@ -24,171 +24,243 @@ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); // 2.3.3: set time format default to plugin setting - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : __( 'No Show scheduled for this time.', 'radio-station' ); - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - // 2.2.4: added title position, avatar width and DJ link options // 2.3.0: added countdown display option // 2.3.2: added AJAX load option // 2.3.3.8: added show encore text display option + // 2.5.0: add avatar_size field + + // ---- widget options --- + $title = $instance['title']; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; + $no_shows = isset( $instance['default'] ) ? $instance['default'] : ''; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : false; + + // --- show display options --- + $show_link = isset( $instance['link'] ) ? $instance['link'] : false; $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; + $show_avatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $avatar_size = isset( $instance['avatar_size'] ) ? $instance['avatar_size'] : 'thumbnail'; $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; + + // --- show time display options --- + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; + $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; + $time_format = isset( $instance['time'] ) ? $instance['time'] : ''; + + // --- extra display options --- + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; + $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : true; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; + + // --- set image size options --- + $image_sizes = radio_station_get_image_sizes(); + $image_size_options = ''; + foreach ( $image_sizes as $image_size => $label ) { + $image_size_options .= '' . "\n"; + } // 2.3.0: convert template style code to strings + // 2.5.0: use fields array + $fields = array(); + + // === Widget Display Options === + + // --- Widget Title --- + $fields['title'] = '

    + +

    '; + + // --- AJAX Loading --- // 2.3.2: added AJAX load option field - $fields = ' -

    + $fields['ajax'] = '

    -

    +

    '; -

    -

    +
    + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

    '; + + // --- No Current Show Text --- + $fields['no_shows'] = '

    + + ' . esc_html( __( 'Empty for default. 0 for none.', 'radio-station' ) ) . ' +

    '; + + // --- Hide if Empty --- + $fields['hide_empty'] = '

    + -

    +

    '; -

    + // === Show Display Options === + + // --- Show Link --- + $fields['show_link'] = '

    -

    +

    '; -

    + // --- Title Position --- + $field = '

    -

    +

    '; + $fields['title_position'] = $field; -

    + // --- Show Avatar --- + $fields['show_avatar'] = '

    -

    +

    '; + + // --- Avatar Size --- + $fields['avatar_size'] = '

    + +

    '; -

    + // --- Avatar Width --- + $fields['avatar_width'] = '

    - ' . esc_html( __( 'Width of Show Avatar (in pixels, default full width)', 'radio-station' ) ) . ' -

    + ' . esc_html( __( 'Show Avatar Width override. 0 or empty for none.', 'radio-station' ) ) . ' +

    '; -

    - -

    + // === Show Time Display Options === -

    - -

    - -

    + // --- Display Show Time --- + $fields['show_sched'] = '

    -

    +

    '; -

    + // --- Display All Shifts --- + $fields['show_all_sched'] = '

    -

    +

    '; -

    -

    + -

    +

    '; -

    -

    + -

    +

    '; -

    -

    + -

    +

    '; -

    -

    + - ' . esc_html( __( 'Text to display if no Show is scheduled for the current time.', 'radio-station' ) ) . ' -

    +

    '; -

    -

    + -
    - ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' -

    +

    '; -

    -

    + -

    '; +

    '; + + // --- Display Show Encore --- + $fields['show_encore'] = '

    + +

    '; // --- filter and output --- // 2.3.0: added field filter for extra fields // 2.3.2: fix for second and third filter arguments - $fields = apply_filters( 'radio_station_current_show_widget_fields', $fields, $this, $instance ); - echo $fields; + // 2.5.0: added filter for array fields for ease of adding fields + $fields = apply_filters( 'radio_station_current_show_widget_fields_list', $fields, $this, $instance ); + $fields_html = implode( "\n", $fields ); + $fields_html = apply_filters( 'radio_station_current_show_widget_fields', $fields_html, $this, $instance ); + // 2.5.0: use wp_kses on field settings output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $fields_html, $allowed ); } // --- update widget instance values --- @@ -196,27 +268,32 @@ public function update( $new_instance, $old_instance ) { $instance = $old_instance; - // 2.2.7: fix checkbox value saving - $instance['title'] = $new_instance['title']; - $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; - $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; - $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; - $instance['default'] = $new_instance['default']; - $instance['time'] = $new_instance['time']; - $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; - $instance['show_playlist'] = isset( $new_instance['show_playlist'] ) ? 1 : 0; - $instance['show_all_sched'] = isset( $new_instance['show_all_sched'] ) ? 1 : 0; - $instance['show_desc'] = isset( $new_instance['show_desc'] ) ? 1 : 0; - // 2.2.4: added title position and avatar width settings + // 2.2.7: fix checkbox value saving // 2.3.0: added countdown display option // 2.3.2: added ajax load option + // --- widget display options --- + $instance['title'] = $new_instance['title']; + $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; + $instance['default'] = $new_instance['default']; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? $new_instance['hide_empty'] : 0; + // --- show display options --- + $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; $instance['title_position'] = $new_instance['title_position']; + $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; + $instance['avatar_size'] = isset( $new_instance['avatar_size'] ) ? $new_instance['avatar_size'] : 'thumbnail'; $instance['avatar_width'] = $new_instance['avatar_width']; + // --- show time display options --- + $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; + $instance['show_all_sched'] = isset( $new_instance['show_all_sched'] ) ? 1 : 0; + $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; + $instance['time'] = $new_instance['time']; + // --- extra display options --- + $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; $instance['link_djs'] = isset( $new_instance['link_djs'] ) ? 1 : 0; + $instance['show_desc'] = isset( $new_instance['show_desc'] ) ? 1 : 0; + $instance['show_playlist'] = isset( $new_instance['show_playlist'] ) ? 1 : 0; $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; // 2.3.0: filter widget update instance $instance = apply_filters( 'radio_station_current_show_widget_update', $instance, $new_instance, $old_instance ); @@ -230,93 +307,80 @@ public function widget( $args, $instance ) { // --- set widget id --- // 2.3.0: added unique widget id + // 2.5.0: simplify widget id setting if ( !isset( $radio_station_data['widgets']['current-show'] ) ) { - $id = $radio_station_data['widgets']['current-show'] = 0; - } else { - $id = $radio_station_data['widgets']['current-show']++; + $radio_station_data['widgets']['current-show'] = 0; } + $radio_station_data['widgets']['current-show']++; + $id = $radio_station_data['widgets']['current-show']; + // 2.2.4: added title position, avatar width and DJ link settings // 2.3.0: filter widget_title whether empty or not + // 2.3.2: fix old false values to use 0 for shortcodes + // 2.3.2: added AJAX load option + // 2.5.0: added avatar_size for show image size + + // --- widget display options --- $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); - $display_djs = $instance['display_djs']; - $djavatar = $instance['djavatar']; - $link = $instance['link']; - $default = empty( $instance['default'] ) ? '' : $instance['default']; - $time = empty( $instance['time'] ) ? '' : $instance['time']; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; + $no_shows = empty( $instance['default'] ) ? '' : $instance['default']; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 0; + // --- show display options --- + $show_link = $instance['link']; + $title_position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; + $show_avatar = $instance['djavatar']; + $avatar_size = isset( $instance['avatar_size'] ) ? $instance['avatar_size'] : 'thumbnail'; + $avatar_width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; + // --- show time display options --- $show_sched = $instance['show_sched']; - $show_playlist = $instance['show_playlist']; - - // 2.3.2: fix old false values to use 0 for shortcodes - // keep the default settings for people updating from 1.6.2 or earlier $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : 0; - // keep the default settings for people updating from 2.0.12 or earlier - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : 0; - $encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : 0; - - // 2.2.4: added title position, avatar width and DJ link settings - // 2.3.2: added AJAX load option - $position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; - $width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; + $time_format = empty( $instance['time'] ) ? '' : $instance['time']; + // --- extra display options --- + $display_hosts = $instance['display_djs']; + $link_hosts = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; + $show_playlist = $instance['show_playlist']; + $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : 0; + $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : 0; // --- set shortcode attributes --- // 2.3.0: map widget options to shortcode attributes // 2.3.2: added AJAX load option + // 2.3.2: only set AJAX attribute if overriding default + // 2.5.0: set AJAX attribute anyway (checked in shortcode) + // 2.5.0: changed default_name key to no_shows $atts = array( - // --- legacy widget options --- + // --- widget display options --- 'title' => $title, - 'display_hosts' => $display_djs, - 'show_avatar' => $djavatar, - 'show_link' => $link, - 'default_name' => $default, - 'time' => $time, + 'ajax' => $ajax, + 'no_shows' => $no_shows, + 'hide_empty' => $hide_empty, + // --- show display options --- + 'show_link' => $show_link, + 'title_position' => $title_position, + 'show_avatar' => $show_avatar, + 'avatar_size' => $avatar_size, + 'avatar_width' => $avatar_width, + // --- show time display options --- 'show_sched' => $show_sched, - 'show_playlist' => $show_playlist, 'show_all_sched' => $show_all_sched, - 'show_desc' => $show_desc, - // --- new widget options --- - 'title_position' => $position, - 'avatar_width' => $width, - 'link_djs' => $link_djs, - 'show_encore' => $encore, 'countdown' => $countdown, + 'time_format' => $time_format, + // --- extra display options --- + 'display_hosts' => $display_hosts, + 'link_hosts' => $link_hosts, + 'show_desc' => $show_desc, + 'show_playlist' => $show_playlist, + 'show_encore' => $show_encore, + // --- widget data --- 'widget' => 1, 'id' => $id, ); - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - // 2.3.3.9: add filter for default widget attributes $atts = apply_filters( 'radio_station_current_show_widget_atts', $atts, $instance ); - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add unique id to widget - // 2.3.2: add class to widget - // 2.4.0.1: add current-show-widget class - echo '
    '; - - // --- widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - // --- get default display output --- // 2.3.0: use shortcode to generate default widget output $output = radio_station_current_show_shortcode( $atts ); @@ -325,28 +389,58 @@ public function widget( $args, $instance ) { // 2.3.0: added this override filter $output = apply_filters( 'radio_station_current_show_widget_override', $output, $args, $atts ); - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - // --- close widget contents wrapper --- - echo '
    '; + // 2.5.0: added hide widget if empty option + if ( !$hide_empty || ( $hide_empty && $output ) ) { + + // 2.5.0: get context filtered allowed HTML + $allowed = radio_station_allowed_html( 'widget', 'current-show' ); + + // --- before widget --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_widget'], $allowed ); + + // --- open widget container --- + // 2.3.0: add unique id to widget + // 2.3.2: add class to widget + // 2.4.0.1: add current-show-widget class + echo '
    ' . "\n"; + + // --- widget title --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_title'], $allowed ); + if ( !empty( $title ) ) { + echo wp_kses( $title, $allowed ); + } + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_title'], $allowed ); + + // 2.3.3.9: add div wrapper for widget contents + echo '
    ' . "\n"; + + // --- output widget display --- + // TODO: test wp_kses on shortcode output + // echo wp_kses( $output, $allowed ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; + + // --- close widget contents wrapper --- + echo '
    ' . "\n"; + + // --- close widget container --- + echo '
    ' . "\n"; + + // --- after widget --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_widget'], $allowed ); + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + // 2.2.4: renamed djonair.css to widgets.css and load for all widgets + // 2.3.0: widgets.css merged into rs-shortcodes.css + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'shortcodes' ); - // --- close widget container --- - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.2.4: renamed djonair.css to widgets.css and load for all widgets - // 2.3.0: widgets.css merged into rs-shortcodes.css - // 2.3.0: use abstracted method for enqueueing widget styles - radio_station_enqueue_style( 'shortcodes' ); + } } } @@ -358,3 +452,4 @@ function radio_station_register_current_show_widget() { // note: widget class name to remain unchanged for backwards compatibility register_widget( 'DJ_Widget' ); } + diff --git a/includes/class-radio-clock-widget.php b/widgets/class-radio-clock-widget.php similarity index 61% rename from includes/class-radio-clock-widget.php rename to widgets/class-radio-clock-widget.php index b88981d..58080bf 100644 --- a/includes/class-radio-clock-widget.php +++ b/widgets/class-radio-clock-widget.php @@ -23,79 +23,90 @@ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => __( 'Radio Clock', 'radio-station' ) ) ); $title = isset( $instance['title'] ) ? $instance['title'] : ''; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $seconds = isset( $instance['seconds'] ) ? $instance['seconds'] : 0; + $time_format = isset( $instance['time'] ) ? $instance['time'] : ''; $day = isset( $instance['day'] ) ? $instance['day'] : 'full'; $date = isset( $instance['date'] ) ? $instance['date'] : 1; $month = isset( $instance['month'] ) ? $instance['month'] : 'full'; + $seconds = isset( $instance['seconds'] ) ? $instance['seconds'] : 0; $zone = isset( $instance['zone'] ) ? $instance['zone'] : 1; // --- widget options form --- - echo ' -

    + // 2.5.0: use fields array + $fields = array(); + + // --- Widget Title --- + $fields['title'] = '

    -

    - -

    - -
    - ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' -

    - -

    - -

    +

    '; -

    -

    + -
    - ' . esc_html( __( 'Display day with clock times.', 'radio-station' ) ) . ' -

    +

    '; -

    + // --- Display Date --- + $fields['date_display'] = '

    -

    +

    '; -

    -

    + -
    - ' . esc_html( __( 'Display month with clock times.', 'radio-station' ) ) . ' -

    - -

    +

    '; + + // --- Display Seconds --- + $fields['seconds'] = '

    + +

    '; + + // --- Time Format --- + $fields['time_format'] = '

    + +

    '; + + // --- Timezone Display ---- + $fields['timezone_display'] = '

    '; + // --- filter and output --- + // 2.5.0: added missing field filters for consistency + $fields = apply_filters( 'radio_station_clock_widget_fields_list', $fields, $this, $instance ); + $fields_html = implode( '', $fields ); + $fields_html = apply_filters( 'radio_station_clock_widget_fields', $fields_html, $this, $instance ); + // 2.5.0: use wp_kses on field settings output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $fields_html, $allowed ); } // --- update widget instance --- @@ -106,12 +117,14 @@ public function update( $new_instance, $old_instance ) { // --- update widget options --- $instance['time'] = isset( $new_instance['time'] ) ? $new_instance['time'] : 12; - $instance['seconds'] = isset( $new_instance['seconds'] ) ? 1 : 0; $instance['day'] = isset( $new_instance['day'] ) ? $new_instance['day'] : 'full'; $instance['date'] = isset( $new_instance['date'] ) ? 1 : 0; $instance['month'] = isset( $new_instance['month'] ) ? $new_instance['month'] : 'full'; + $instance['seconds'] = isset( $new_instance['seconds'] ) ? 1 : 0; $instance['zone'] = isset( $new_instance['zone'] ) ? 1 : 0; - + + // 2.5.0: filter widget update instance + $instance = apply_filters( 'radio_station_clock_widget_update', $instance, $new_instance, $old_instance ); return $instance; } @@ -122,75 +135,84 @@ public function widget( $args, $instance ) { // --- set widget id --- // 2.3.3.9: added unique widget id + // 2.5.0: simplify widget id setting if ( !isset( $radio_station_data['widgets']['clock'] ) ) { - $id = $radio_station_data['widgets']['clock'] = 0; - } else { - $id = $radio_station_data['widgets']['clock']++; + $radio_station_data['widgets']['clock'] = 0; } + $radio_station_data['widgets']['clock']++; + $id = $radio_station_data['widgets']['clock']; // 2.3.0: added hide widget if empty option $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); $time = $instance['time']; - $seconds = $instance['seconds']; $day = $instance['day']; $date = $instance['date']; $zone = $instance['zone']; $month = $instance['month']; + $seconds = $instance['seconds']; // --- set shortcode attributes for display --- $atts = array( + // --- clock options --- 'time' => $time, - 'seconds' => $seconds, 'day' => $day, 'date' => $date, 'month' => $month, 'zone' => $zone, + 'seconds' => $seconds, + // --- widget data --- 'widget' => 1, + 'id' => $id, ); // 2.3.3.9: add missing filter for clock widget attributes $atts = apply_filters( 'radio_station_clock_widget_atts', $atts, $instance ); + // 2.5.0: get context filtered allowed HTML + $allowed = radio_station_allowed_html( 'widget', 'radio-clock' ); + // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_widget'], $allowed ); // --- open widget container --- // 2.3.0: add instance id and class to widget container - echo '
    '; + echo '
    ' . "\n"; - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; + // --- output widget title --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_title'], $allowed ); + if ( !empty( $title ) ) { + echo wp_kses( $title, $allowed ); + } + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_title'], $allowed ); - echo '
    '; + echo '
    ' . "\n"; - // --- get default display output --- - $output = radio_station_clock_shortcode( $atts ); + // --- get default display output --- + $output = radio_station_clock_shortcode( $atts ); - // --- check for widget output override --- - $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); + // --- check for widget output override --- + $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); - // --- output widget display --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; + // --- output widget display --- + // TODO: test wp_kses on output ? + // echo wp_kses( $output, $allowed ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; - // --- close widget contents --- - echo '
    '; + // --- close widget contents --- + echo '
    ' . "\n"; // --- close widget container --- - echo '
    '; + echo '
    ' . "\n"; // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_widget'], $allowed ); // --- enqueue widget stylesheet in footer --- // (this means it will only load if widget is on page) @@ -205,3 +227,4 @@ public function widget( $args, $instance ) { function radio_station_register_radio_clock_widget() { register_widget( 'Radio_Clock_Widget' ); } + diff --git a/includes/class-radio-player-widget.php b/widgets/class-radio-player-widget.php similarity index 66% rename from includes/class-radio-player-widget.php rename to widgets/class-radio-player-widget.php index 2203e3b..5f4860d 100644 --- a/includes/class-radio-player-widget.php +++ b/widgets/class-radio-player-widget.php @@ -23,11 +23,13 @@ public function __construct() { public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); + + // 2.5.0: fix player image default value to 'default' $url = isset( $instance['url'] ) ? $instance['url'] : ''; // $format = isset( $instance['format'] ) ? $instance['format'] : ''; $title = isset( $instance['title'] ) ? $instance['title'] : ''; $station = isset( $instance['station'] ) ? $instance['station'] : ''; - $image = isset( $instance['image'] ) ? $instance['image'] : ''; + $image = isset( $instance['image'] ) ? $instance['image'] : 'default'; $script = isset( $instance['script'] ) ? $instance['script'] : 'default'; $layout = isset( $instance['layout'] ) ? $instance['layout'] : 'vertical'; $theme = isset( $instance['theme'] ) ? $instance['theme'] : 'default'; @@ -40,90 +42,117 @@ public function form( $instance ) { // $hosts = isset( $instance['show_hosts'] ) ? $instance['show_hosts'] : false; // $producers = isset( $instance['show_producers'] ) ? $instance['show_producers'] : false; - // --- stream or file URL --- - $fields = ' -

    -
    - ' . esc_html( 'Note: Leave blank to use default stream URL.', 'radio-station' ) . ' -

    ' . PHP_EOL; + // 2.5.0: set fields array + $fields = array(); + + // === Player Content === + $fields['player_styles'] = '

    ' . esc_html( __( 'Player Content', 'radio-station' ) ) . '

    ' . "\n"; - // --- widget title --- - $fields .= ' -

    + // --- Widget Title --- + $fields['title'] = '

    -

    ' . PHP_EOL; +

    '; - // --- station text --- - $fields .= ' -

    + // --- Stream or File URL --- + $fields['url'] = '

    +
    + ' . esc_html( 'Leave blank to use default stream URL.', 'radio-station' ) . ' +

    '; + + // --- Station Text --- + $fields['station'] = '


    - (' . esc_html( 'Empty for default, 0 for none', 'radio-station' ) . ') -

    ' . PHP_EOL; - - // --- station image --- - $fields .= '

    -

    ' . PHP_EOL; +

    '; + $fields['image'] = $field; - // --- player script --- - $fields .= '

    + // === Player Options === + $fields['player_options'] = '

    ' . esc_html( __( 'Player Options', 'radio-station' ) ) . '

    ' . "\n"; + + // --- Player Script --- + $field = '

    -

    ' . PHP_EOL; +

    '; + $fields['script'] = $field; + + // --- Player Volume --- + $fields['volume'] = '

    + +

    '; - // --- player layout --- - $fields .= '

    + // --- Default Player --- + $fields['default'] = '

    + +

    '; + + // === Player Styles === + $fields['player_styles'] = '

    ' . esc_html( __( 'Player Styles', 'radio-station' ) ) . '

    ' . "\n"; + + // --- Player Layout --- + $field = '

    -

    ' . PHP_EOL; +

    '; + $fields['layout'] = $field; - // --- player theme --- - $fields .= '

    + // --- Player Theme --- + $field = '

    -

    ' . PHP_EOL; +

    '; + $fields['theme'] = $field; - // --- player buttons --- - $fields .= '

    + // --- Player Buttons --- + $field = '

    -

    ' . PHP_EOL; - - // --- player volume --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player default instance --- - $fields .= '

    - -

    ' . PHP_EOL; +

    '; + $fields['buttons'] = $field; // --- filter and output --- - $fields = apply_filters( 'radio_station_player_widget_fields', $fields, $this, $instance ); - echo $fields; + // 2.5.0: added filter for array fields for ease of adding fields + $fields = apply_filters( 'radio_station_player_widget_fields_list', $fields, $this, $instance ); + $fields_html = implode( "\n", $fields ); + $fields_html = apply_filters( 'radio_station_player_widget_fields', $fields_html, $this, $instance ); + // 2.5.0: use wp_kses on field settings output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $fields_html, $allowed ); } // --- update widget instance values --- @@ -197,14 +216,14 @@ public function update( $new_instance, $old_instance ) { $instance['buttons'] = isset( $new_instance['buttons'] ) ? $new_instance['buttons'] : $old_instance['buttons']; $instance['volume'] = isset( $new_instance['volume'] ) ? $new_instance['volume'] : $old_instance['volume']; if ( '' != $instance['volume'] ) { - $instance['volume'] = absint( $instance['volume'] ); + $instance['volume'] = absint( $instance['volume'] ); if ( $instance['volume'] > 100 ) { $instance['volume'] = 100; } elseif ( $instance['volume'] < 0 ) { $instance['volume'] = 0; } } - $instance['default'] = isset( $new_instance['default'] ) ? 1 : 0;; + $instance['default'] = isset( $new_instance['default'] ) ? 1 : 0; // --- additional displays --- // $instance['show_display'] = isset( $new_instance['show_display'] ) ? 1 : 0; @@ -222,11 +241,12 @@ public function widget( $args, $instance ) { global $radio_station_data; // --- set widget id --- + // 2.5.0: simplify widget id setting if ( !isset( $radio_station_data['widgets']['player'] ) ) { - $id = $radio_station_data['widgets']['player'] = 1; - } else { - $id = $radio_station_data['widgets']['player']++; + $radio_station_data['widgets']['player'] = 0; } + $radio_station_data['widgets']['player']++; + $id = $radio_station_data['widgets']['player']; // --- get widget options --- $url = $instance['url']; @@ -267,7 +287,7 @@ public function widget( $args, $instance ) { 'buttons' => $buttons, 'volume' => $volume, 'default' => $default, - // --- additional displays + // --- additional displays --- // 'shows' => $shows, // 'hosts' => $hosts, // 'producers' => $producers, @@ -277,40 +297,51 @@ public function widget( $args, $instance ) { 'id' => $id, ); + // 2.5.0: add filter for default widget attributes + $atts = apply_filters( 'radio_station_player_widget_atts', $atts, $instance ); + + // --- maybe debug widget attributes -- + // 2.5.0: added for debugging widget attributes + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + echo 'Radio Player Widget Attributes: '; + echo esc_html( print_r( $atts, true ) ) . ''; + } + + // 2.5.0: get context filtered allowed HTML + $allowed = radio_station_allowed_html( 'widget', 'radio-player' ); + // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_widget'], $allowed ); // --- open widget container --- - $id = 'radio-player-widget-' . $id; - echo '
    '; - - // --- widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; + // 2.5.0: added class radio-player-widget + echo '
    ' . "\n"; + + // --- widget title --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_title'], $allowed ); + if ( !empty( $title ) ) { + echo wp_kses( $title, $allowed ); + } + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_title'], $allowed ); - // --- get default display output --- - $output = radio_station_player_shortcode( $atts ); + // --- get default display output --- + $output = radio_station_player_shortcode( $atts ); - // --- check for widget output override --- - $output = apply_filters( 'radio_station_player_widget_override', $output, $args, $atts ); + // --- check for widget output override --- + $output = apply_filters( 'radio_station_player_widget_override', $output, $args, $atts ); - // --- output widget display --- - if ( $output ) { + // --- output widget display --- // phpcs:ignore WordPress.Security.OutputNotEscaped echo $output; - } - echo '
    '; + echo '
    ' . "\n"; // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_widget'], $allowed ); } } @@ -322,3 +353,4 @@ public function widget( $args, $instance ) { function radio_station_register_player_widget() { register_widget( 'Radio_Player_Widget' ); } + diff --git a/includes/class-upcoming-shows-widget.php b/widgets/class-upcoming-shows-widget.php similarity index 53% rename from includes/class-upcoming-shows-widget.php rename to widgets/class-upcoming-shows-widget.php index af3b2ed..3262c47 100644 --- a/includes/class-upcoming-shows-widget.php +++ b/widgets/class-upcoming-shows-widget.php @@ -24,145 +24,220 @@ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); // 2.3.3: set time format default to empty (plugin setting) - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - // 2.2.4: added title position, avatar width and DJ link options // 2.3.0: added countdown field option // 2.3.2: added AJAX load option + // 2.5.0: added avatar_size and show_encore fields + + // --- widget display options --- + $title = $instance['title']; + $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; + $no_shows = isset( $instance['default'] ) ? $instance['default'] : ''; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : false; + // --- show display options --- + $link = isset( $instance['link'] ) ? $instance['link'] : false; $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; + $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; + $avatar_size = isset( $intance['avatar_size'] ) ? $instance['avatar_size'] : 'thumbnail'; $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; + // --- show time display options ---- + $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; + $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : ''; + $time = isset( $instance['time'] ) ? $instance['time'] : ''; + // --- extra display options --- + $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : true; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : ''; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; + + // --- set image size options --- + $image_sizes = radio_station_get_image_sizes(); + $image_size_options = ''; + foreach ( $image_sizes as $image_size => $label ) { + $image_size_options .= '' . "\n"; + } // 2.3.0: convert template style code to straight php echo // 2.3.2: added AJAX load option field - $fields = ' -

    + // 2.5.0: create fields array for filtering + $fields = array(); + + // === Widget Loading Options === + + // --- Widget Title --- + $fields['title'] = '

    + +

    '; + + // --- Number of Shows --- + $fields['limit'] = '

    + + ' . esc_html( __( 'Number of upcoming Shows to display.', 'radio-station' ) ) . ' +

    '; + + // --- AJAX Loading --- + $fields['ajax'] = '

    -

    +

    '; -

    -

    +
    + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

    '; + + // --- No Shows Text --- + $fields['no_shows'] = '

    + -

    + ' . esc_html( __( 'Empty for default, 0 for none.', 'radio-station' ) ) . ' +

    '; + + // --- Hide if Empty --- + $fields['hide_empty'] = '

    + +

    '; -

    + // === Show Display Options === + + // --- Link to Show --- + $fields['show_link'] - '

    -

    +

    '; -

    + // --- Title Position --- + $field = '

    -

    +

    '; + $fields['title_position'] = $field; -

    + // --- Show Avatar --- + $fields['show_avatar'] ='

    -

    +

    '; -

    + // --- Avatar Size --- + $fields['avatar_size'] = '

    + +

    '; + + // --- Avatar Width --- + $fields['avatar_width'] = '

    - ' . esc_html( __( 'Width of Show Avatars (in pixels, default 75px)', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - -

    + ' . esc_html( __( 'Show Avatar Width override. 0 or empty for none.', 'radio-station' ) ) . ' +

    '; -

    - - ' . esc_html( __( 'If no Show is scheduled for the current time, display this text.', 'radio-station' ) ) . ' -

    + // === Show Time Options === -

    + // --- Display Show Time --- + $fields['show_sched'] = '

    -

    +

    '; -

    -

    + - ' . esc_html( __( 'Number of upcoming Shows to display.', 'radio-station' ) ) . ' -

    +

    '; -

    -

    + -
    - ' . esc_html( __( 'Choose time format for displayed schedules.', 'radio-station' ) ) . ' -

    +

    '; -

    -

    + +

    '; + + // --- Link Hosts --- + $fields['link_hosts'] = '

    + +

    '; + + // --- Show Encore --- + $fields['show_encore'] = '

    +

    '; // --- filter and output --- - $fields = apply_filters( 'radio_station_upcoming_shows_widget_fields', $fields, $this, $instance ); - echo $fields; + // 2.5.0: added filter for array fields for ease of adding fields + $fields = apply_filters( 'radio_station_upcoming_shows_widget_field_list', $fields, $this, $instance ); + $fields_html = implode( "\n", $fields ); + $fields_html = apply_filters( 'radio_station_upcoming_shows_widget_fields', $fields_html, $this, $instance ); + // 2.5.0: use wp_kses on field settings output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $fields_html, $allowed ); } // --- update widget instance --- @@ -170,24 +245,30 @@ public function update( $new_instance, $old_instance ) { $instance = $old_instance; - $instance['title'] = $new_instance['title']; - $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; - $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; - $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; - $instance['default'] = $new_instance['default']; - $instance['limit'] = $new_instance['limit']; - $instance['time'] = $new_instance['time']; - $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; - // 2.2.4: added title position, avatar width and DJ link settings // 2.3.0: added countdown display option // 2.3.2: added AJAX load option + // 2.5.0; added hide_empty, avatar_size, show_title + // --- widget display options --- + $instance['title'] = $new_instance['title']; + $instance['limit'] = $new_instance['limit']; + $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; + $instance['default'] = $new_instance['default']; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? $new_instance['hide_empty'] : 0; + // --- show display options --- + $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; $instance['title_position'] = $new_instance['title_position']; + $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; + $instance['avatar_size'] = isset( $new_instance['avatar_size'] ) ? $new_instance['avatar_size'] : 'thumbnail'; $instance['avatar_width'] = $new_instance['avatar_width']; + // --- show time display options ---- + $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; + $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; + $instance['time'] = $new_instance['time']; + // --- extra display options --- + $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; $instance['link_djs'] = isset( $new_instance['link_djs'] ) ? 1 : 0; $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; // 2.3.0: added widget filter instance to update $instance = apply_filters( 'radio_station_upcoming_shows_widget_update', $instance, $new_instance, $old_instance ); @@ -201,88 +282,79 @@ public function widget( $args, $instance ) { // --- set widget id --- // 2.3.0: added unique widget id + // 2.5.0: simplify widget id setting if ( !isset( $radio_station_data['widgets']['upcoming-shows'] ) ) { - $id = $radio_station_data['widgets']['upcoming-shows'] = 0; - } else { - $id = $radio_station_data['widgets']['upcoming-shows']++; + $radio_station_data['widgets']['upcoming-shows'] = 0; } + $radio_station_data['widgets']['upcoming-shows']++; + $id = $radio_station_data['widgets']['upcoming-shows']; + // --- get widget title --- // 2.3.0: filter widget_title whether empty or not $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); - $display_djs = $instance['display_djs']; - $djavatar = $instance['djavatar']; - $link = $instance['link']; - $default = empty( $instance['default'] ) ? '' : $instance['default']; - $limit = empty( $instance['limit'] ) ? '1' : $instance['limit']; - $time = empty( $instance['time'] ) ? '' : $instance['time']; - $show_sched = $instance['show_sched']; // 2.2.4: added title position, avatar width and DJ link settings // 2.3.0: added countdown display option // 2.3.2: added AJAX load option + // 2.5.0: renamed default to no_shows + // 2.5.0; added avatar_size, show_title + // --- widget display options --- + $limit = empty( $instance['limit'] ) ? '1' : $instance['limit']; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; + $no_shows = empty( $instance['default'] ) ? '' : $instance['default']; + $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 1; + // --- show display options --- + $link = $instance['link']; $position = empty( $instance['title_position'] ) ? 'right' : $instance['title_position']; + $dj_avatar = $instance['djavatar']; + $avatar_size = isset( $instance['avatar_size'] ) ? $instance['avatar_size'] : 'thumbnail'; $width = empty( $instance['avatar_width'] ) ? '75' : $instance['avatar_width']; + // --- show time display options ---- + $show_sched = $instance['show_sched']; + $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; + $time_format = empty( $instance['time'] ) ? '' : $instance['time']; + // --- extra display options --- + $display_djs = $instance['display_djs']; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $encore = isset( $instance['encore'] ) ? $instnace['encore'] : 0; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; // --- set shortcode attributes --- // 2.3.0: map widget options to shortcode attributes // 2.3.2: added AJAX load option + // 2.3.2: only set AJAX attribute if overriding default + // 2.5.0: set AJAX attribute anyway (checked in shortcode) + // 2.5.0: changed default_name key to no_shows + // 2.5.0: changed time key to time_format $atts = array( - // --- legacy widget options --- + // --- widget display options --- 'title' => $title, - 'display_djs' => $display_djs, - 'show_avatar' => $djavatar, - 'show_link' => $link, - 'default' => $default, 'limit' => $limit, - 'time' => $time, - 'show_sched' => $show_sched, - // --- new widget options --- - // 'show_playlist' => $show_playlist, - // 'show_desc' => $show_desc, + 'ajax' => $ajax, + 'no_shows' => $no_shows, + 'hide_empty' => $hide_empty, + // --- show display options --- + 'show_link' => $link, 'title_position' => $position, + 'show_avatar' => $dj_avatar, + 'avatar_size' => $avatar_size, 'avatar_width' => $width, + // --- show time display options ---- + 'show_sched' => $show_sched, + 'countdown' => $countdown, + 'time_format' => $time_format, + // --- extra display options --- + 'display_djs' => $display_djs, 'link_djs' => $link_djs, 'show_encore' => $encore, - 'countdown' => $countdown, + // --- widget data --- 'widget' => 1, 'id' => $id, ); - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - // 2.3.3.9: add filter for default widget attributes $atts = apply_filters( 'radio_station_upcoming_shows_widget_atts', $atts, $instance ); - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add unique id to widget - // 2.3.2: add class to widget - // 2.4.0.1: add upcoming-shows-widget class - echo '
    '; - - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - // --- get default display output --- // 2.3.0: use shortcode to generate default widget output $output = radio_station_upcoming_shows_shortcode( $atts ); @@ -291,28 +363,59 @@ public function widget( $args, $instance ) { // 2.3.0: added this override filter $output = apply_filters( 'radio_station_upcoming_shows_widget_override', $output, $args, $atts ); - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } + // 2.5.0: added hide widget if empty option + if ( !$hide_empty || ( $hide_empty && $output ) ) { + + // 2.5.0: get context filtered allowed HTML + $allowed = radio_station_allowed_html( 'widget', 'upcoming-shows' ); - // --- close widget contents wrapper --- - echo '
    '; + // --- before widget --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_widget'], $allowed ); - // --- close widget container --- - echo '
    '; + // --- open widget container --- + // 2.3.0: add unique id to widget + // 2.3.2: add class to widget + // 2.4.0.1: add upcoming-shows-widget class + echo '
    ' . "\n"; - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; + // --- output widget title --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['before_title'], $allowed ); + if ( !empty( $title ) ) { + echo wp_kses( $title, $allowed ); + } + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_title'], $allowed ); - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.2.4: renamed djonair.css to widgets.css and load for all widgets - // 2.3.0: widgets.css merged into rs-shortcodes.css - // 2.3.0: use abstracted method for enqueueing widget styles - radio_station_enqueue_style( 'shortcodes' ); + // 2.3.3.9: add div wrapper for widget contents + echo '
    ' . "\n"; + + + // --- output widget display --- + // TODO: test wp_kses on output + // echo wp_kses( $output, $allowed ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; + + // --- close widget contents wrapper --- + echo '
    ' . "\n"; + + // --- close widget container --- + echo '
    ' . "\n"; + + // --- after widget --- + // 2.5.0: use wp_kses on output + echo wp_kses( $args['after_widget'], $allowed ); + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + // 2.2.4: renamed djonair.css to widgets.css and load for all widgets + // 2.3.0: widgets.css merged into rs-shortcodes.css + // 2.3.0: use abstracted method for enqueueing widget styles + radio_station_enqueue_style( 'shortcodes' ); + + } } } @@ -325,3 +428,4 @@ function radio_station_register_upcoming_shows_widget() { // note: widget class name to remain unchanged for backwards compatibility register_widget( 'DJ_Upcoming_Widget' ); } + From 2ba1728b5c90f433cc71568beb2a5563257a73af Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 3 Oct 2022 22:20:04 +1000 Subject: [PATCH 265/377] development updates --- .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/php.xml | 8 + .idea/radio-station.iml | 8 + .idea/workspace.xml | 71 ++ blocks/current-playlist.js | 11 + blocks/editor.js | 2 +- blocks/player.js | 39 +- blocks/schedule.js | 74 +- css/rs-schedule.css | 256 ++--- includes/blocks.php | 15 +- includes/legacy.php | 38 +- includes/master-schedule.php | 810 +++++++++------- includes/post-types-admin.php | 224 +++-- includes/schedules.php | 11 +- includes/shortcodes.php | 249 ++--- includes/support-functions.php | 161 +-- includes/templates.php | 13 +- js/moment.js | 8 +- js/moment.min.js | 2 +- js/radio-station-clock.js | 10 +- js/radio-station-countdown.js | 8 +- js/radio-station.js | 6 +- languages/radio-station-nl_NL.po | 1074 ++++++++++----------- loader.php | 41 +- options.php | 11 +- player/css/radio-player.css | 124 +-- player/images/controls-dark.png | Bin 2423 -> 0 bytes player/images/controls-light.png | Bin 2301 -> 0 bytes player/images/pause-dark-circular.png | Bin 1405 -> 4977 bytes player/images/pause-dark-rounded.png | Bin 1693 -> 3187 bytes player/images/pause-dark-square.png | Bin 1346 -> 2957 bytes player/images/pause-light-circular.png | Bin 1393 -> 5028 bytes player/images/pause-light-rounded.png | Bin 1779 -> 3170 bytes player/images/pause-light-square.png | Bin 1398 -> 2668 bytes player/images/play-dark-circular.png | Bin 1565 -> 4657 bytes player/images/play-dark-rounded.png | Bin 1842 -> 2923 bytes player/images/play-dark-square.png | Bin 1488 -> 2668 bytes player/images/play-light-circular.png | Bin 1556 -> 4681 bytes player/images/play-light-rounded.png | Bin 1929 -> 2906 bytes player/images/play-light-square.png | Bin 1557 -> 2668 bytes player/images/volume-controls-dark.png | Bin 0 -> 7013 bytes player/images/volume-controls-light.png | Bin 0 -> 7032 bytes player/js/radio-player.js | 2 +- player/radio-player.php | 486 ++++++---- radio-station-admin.php | 19 +- radio-station.php | 63 +- readme.txt | 6 +- scheduler/schedule-engine.php | 6 +- templates/master-schedule-list.php | 14 +- templates/master-schedule-table.php | 33 +- templates/master-schedule-tabs.php | 98 +- widgets/class-current-playlist-widget.php | 50 +- widgets/class-current-show-widget.php | 10 +- widgets/class-radio-player-widget.php | 150 +-- widgets/class-upcoming-shows-widget.php | 12 +- 56 files changed, 2457 insertions(+), 1780 deletions(-) create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/radio-station.iml create mode 100644 .idea/workspace.xml delete mode 100644 player/images/controls-dark.png delete mode 100644 player/images/controls-light.png create mode 100644 player/images/volume-controls-dark.png create mode 100644 player/images/volume-controls-light.png diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..477cda5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..03d51a6 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/radio-station.iml b/.idea/radio-station.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/radio-station.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..b47346a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1657688918584 + + + + + + + + + + + + + + + + + + + + + + + + + C:\!WP\InstantWP_4.5\iwpserver\htdocs\wordpress + + \ No newline at end of file diff --git a/blocks/current-playlist.js b/blocks/current-playlist.js index e0e927d..7374b9c 100644 --- a/blocks/current-playlist.js +++ b/blocks/current-playlist.js @@ -26,6 +26,7 @@ hide_empty: { type: 'boolean', default: false }, /* --- Playlist Display Options --- */ + playlist_title: { type: 'boolean', default: false }, link: { type: 'boolean', default: true }, countdown: { type: 'boolean', default: true }, no_playlist: { type: 'string', default: '' }, @@ -107,6 +108,16 @@ /* === Playlist Display Panel === */ el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: false }, + /* --- Playlist Title --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Playlist Title', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { playlist_title: value } ); + }, + checked: atts.playlist_title, + }) + ), /* --- Link Playlist --- */ el( PanelRow, {}, el( ToggleControl, { diff --git a/blocks/editor.js b/blocks/editor.js index 250cc41..1ca26f3 100644 --- a/blocks/editor.js +++ b/blocks/editor.js @@ -61,7 +61,7 @@ if (s_tabs && !jQuery('#radio-schedule-tabs-js').length) { radio_station_load_block_script('schedule-tabs'); var radio_load_tabs = setInterval(function() { if (typeof radio_tabs_initialize == 'function') { - radio_tabs_init = false; radio_tabs_initialize(); clearInterval(radio_load_grid); + radio_tabs_init = false; radio_tabs_initialize(); clearInterval(radio_load_tabs); } }, 1000); } if (s_list && !jQuery('#radio-schedule-list-js').length) { diff --git a/blocks/player.js b/blocks/player.js index 51d5932..5a6c35b 100644 --- a/blocks/player.js +++ b/blocks/player.js @@ -22,7 +22,7 @@ attributes: { /* --- Player Content --- */ url: { type: 'string', default: '' }, - station: { type: 'string', default: '' }, + title: { type: 'string', default: '' }, image: { type: 'string', default: 'default' }, /* --- Player Options --- */ script: { type: 'string', default: 'default' }, @@ -60,15 +60,15 @@ value: atts.url, }) ), - /* --- Station/Player Text --- */ + /* --- Player Title Text --- */ el( PanelRow, {}, el( TextControl, { - label: __( 'Player Text', 'radio-station' ), + label: __( 'Player Title Text', 'radio-station' ), help: __( 'Empty for default, 0 for none.', 'radio-station' ), onChange: ( value ) => { - props.setAttributes( { station: value } ); + props.setAttributes( { title: value } ); }, - value: atts.station + value: atts.title }) ), /* --- Image --- */ @@ -77,8 +77,8 @@ label: __( 'Player Image', 'radio-station' ), options : [ { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, - { label: __( 'Display Station Image', 'radio-station' ), value: 'on' }, - { label: __( 'Do Not Display Station Image', 'radio-station' ), value: 'off' }, + { label: __( 'Display Station Image', 'radio-station' ), value: '1' }, + { label: __( 'Do Not Display Station Image', 'radio-station' ), value: '0' }, /* { label: __( 'Display Custom Image', 'radio-station' ), value: 'custom' }, */ ], onChange: ( value ) => { @@ -123,13 +123,36 @@ el( PanelRow, {}, el( ToggleControl, { label: __( 'Use as Default Player', 'radio-station' ), - help: __( 'Use this player as the player on this page?', 'radio-station' ), + help: __( 'Make this the default player on this page.', 'radio-station' ), onChange: ( value ) => { props.setAttributes( { default: value } ); }, checked: atts.default, }) ), + /* --- Popup Player Button --- */ + /* el( PanelRow, {}, + ( ( atts.pro ) && + el( SelectControl, { + label: __( 'Popup Player', 'radio-station' ), + help: __( 'Enables button to open Player in separate window.', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { popup: value } ); + }, + value: atts.popup + }) + ), ( ( !atts.pro ) && + el( BaseControl, { + label: __( 'Popup Player', 'radio-station' ), + help: __( 'Popup Player Button available in Pro.', 'radio-station' ), + }) + ) + ), */ ), /* === Player Styles === */ diff --git a/blocks/schedule.js b/blocks/schedule.js index 2a82f3a..a5941ca 100644 --- a/blocks/schedule.js +++ b/blocks/schedule.js @@ -42,6 +42,12 @@ image_position: { type: 'string', default: 'left' }, hide_past_shows: { type: 'boolean', default: false }, + /* --- Header Displays --- */ + time_header: { type: 'string', default: 'clock' }, + /* clock: { type: 'boolean', default: true }, */ + /* timezone: { type: 'boolean', default: true }, */ + selector: { type: 'boolean', default: true }, + /* --- Times Display --- */ display_day: { type: 'string', default: 'short' }, display_month: { type: 'string', default: 'short' }, @@ -52,11 +58,6 @@ /* active_date: { type: '', default: false }, */ /* display_date: { type: 'string', default: 'jS' }, */ - /* --- Extra Displays --- */ - selector: { type: 'boolean', default: true }, - clock: { type: 'boolean', default: true }, - timezone: { type: 'boolean', default: true }, - /* --- Show Display --- */ show_times: { type: 'boolean', default: true }, show_link: { type: 'boolean', default: true }, @@ -231,6 +232,35 @@ ) ), + /* === Header Displays Panel === */ + el( PanelBody, { title: __( 'Header Display Options', 'radio-station' ), initialOpen: false }, + /* --- Clock/Timezone Header --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Radio Time Header', 'radio-station' ), + options: [ + { label: __( 'Display Radio Clock', 'radio-station' ), value: 'clock' }, + { label: __( 'Display Radio Timezone', 'radio-station' ), value: 'timezone' }, + { label: __( 'No Time Header Display', 'radio-station' ), value: 'none' } + ], + onChange: ( value ) => { + props.setAttributes( { time_header: value } ); + }, + value: atts.time_header + }) + ), + /* --- Genre Highlighter --- */ + el( PanelRow, {}, + el( ToggleControl, { + label: __( 'Display Genre Highlighter', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { selector: value } ); + }, + checked: atts.selector, + }) + ), + ), + /* === Time Display Options === */ el( PanelBody, { title: __( 'Time Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, /* --- Day Display --- */ @@ -381,40 +411,6 @@ checked: atts.show_encore, }) ), - ), - - /* === Extra Displays Panel === */ - el( PanelBody, { title: __( 'Extra Display Options', 'radio-station' ), initialOpen: false }, - /* --- Genre Selector --- */ - el( PanelRow, {}, - el( ToggleControl, { - label: __( 'Display Genre Selector', 'radio-station' ), - onChange: ( value ) => { - props.setAttributes( { selector: value } ); - }, - checked: atts.selector, - }) - ), - /* --- Clock --- */ - el( PanelRow, {}, - el( ToggleControl, { - label: __( 'Display Radio Clock', 'radio-station' ), - onChange: ( value ) => { - props.setAttributes( { clock: value } ); - }, - checked: atts.clock, - }) - ), - /* --- Timezone --- */ - el( PanelRow, {}, - el( ToggleControl, { - label: __( 'Display Radio Timezone', 'radio-station' ), - onChange: ( value ) => { - props.setAttributes( { timezone: value } ); - }, - checked: atts.timezone, - }) - ), ) /* end panels */ ) diff --git a/css/rs-schedule.css b/css/rs-schedule.css index a2530b7..b80665f 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -16,6 +16,7 @@ /* ---------------- */ .show-title, .show-edit-link { display: inline-block; + vertical-align: middle; } .show-edit-link, .show-add-link, .shift-edit-link { @@ -42,11 +43,11 @@ /* Schedule Controls */ /* ----------------- */ -#master-schedule-controls-wrapper { +.master-schedule-controls-wrapper { width: 100%; } -#master-schedule-clock-wrapper, #master-schedule-timezone-wrapper, #master-schedule-selector-wrapper { +.master-schedule-clock-wrapper, .master-schedule-timezone-wrapper, .master-schedule-selector-wrapper { font-size: 0.8em; vertical-align: top; text-align: left; @@ -56,16 +57,16 @@ /* Genre Selector */ /* -------------- */ -#master-genre-list { +.master-genre-list { font-size: 1em; float: left; } -#master-genre-list span.heading { +.master-genre-list span.heading { font-weight: bold; } -#master-genre-list .genre-highlight.highlighted { +.master-genre-list .genre-highlight.highlighted { font-weight: bold; background-color: rgba( 235, 235, 0, 0.4 ); border: 1px dashed orange; @@ -75,20 +76,24 @@ /* Table View Styles */ /* ----------------- */ -#master-program-schedule { +.master-program-schedule { width: 100%; height: 100%; } -#master-program-schedule tr { +.master-program-schedule tr { height: 100%; } -#master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row { +.master-program-schedule .master-program-hour-heading { + max-width: 120px; +} + +.master-program-schedule .master-program-hour-heading, .master-program-schedule .master-program-hour-row { width: 100px; } -#master-program-schedule th { +.master-program-schedule th { width: auto; text-align: center; vertical-align: top; @@ -96,66 +101,82 @@ min-width: 80px; } -#master-program-schedule th.master-program-hour { +.master-program-schedule th.master-program-hour { min-width: 80px; } -#master-program-schedule .master-program-day.current-day { +.master-program-schedule .master-program-day.current-day { border: 1px solid #F00000; } -#master-program-schedule th .headings { +#.aster-program-schedule th .headings { display: inline-block; } -#master-program-schedule th .day-heading { +.master-program-schedule th .day-heading { font-size: 1em; } -#master-program-schedule th .date-subheading { +.master-program-schedule th .date-subheading { font-size: 0.75em; } -#master-program-schedule th a { +.master-program-schedule th a { text-decoration: none; } .master-schedule-table-arrow-left, .master-schedule-table-arrow-right { display: inline-block; - width: 50%; - line-height: 2em; - font-size: 1em; + font-size: 2em; + line-height: 1em; vertical-align: top; cursor: pointer; } -#master-program-schedule th .shift-left-arrow { +.master-schedule-table-arrow-left .master-schedule-table-arrow, +.master-schedule-table-arrow-right .master-schedule-table-arrow { + width: 1em; +} + +.master-schedule-table-arrow-left .master-schedule-table-arrow:hover, +.master-schedule-table-arrow-right .master-schedule-table-arrow:hover { + background-color: rgba(128,128,128,0.5); + border-radius: 1em; +} + +.master-program-schedule th .shift-left-arrow { float: left; font-size: 2em; font-weight: bold; line-height: 0.5em; } -#master-program-schedule th .shift-right-arrow { +.master-program-schedule th .shift-right-arrow { float: right; font-size: 2em; font-weight: bold; line-height: 0.5em; } -#master-program-schedule th .shift-left-arrow, #master-program-schedule th .shift-right-arrow { +.master-program-schedule th .shift-left-arrow, .master-program-schedule th .shift-right-arrow { display: none; } -#master-program-schedule th.first-column .shift-left-arrow, #master-program-schedule th.last-column .shift-right-arrow { +.master-program-schedule th.first-column .shift-left-arrow, .master-program-schedule th.last-column .shift-right-arrow { + display: inline-block; + vertical-align: middle; +} + +.master-program-schedule .master-program-day .headings { display: inline-block; + vertical-align: middle; } -#master-program-schedule .master-program-hour-row th { +.master-program-schedule .master-program-hour-row th { padding-top: 0; } -#master-program-schedule td { +.master-program-schedule td { vertical-align: top; font-size: 1em; text-align: center; @@ -163,28 +184,28 @@ height: 100%; } -#master-program-schedule td { +.master-program-schedule td { border: 1px solid #222222; } -#master-program-schedule td.show-info { +.master-program-schedule td.show-info { padding: 0; } -#master-program-schedule td div { +.master-program-schedule td div { border-top: 1px solid #EEEEEE; font-size: 0.9em; } -#master-program-schedule .master-program-hour.current-hour { +.master-program-schedule .master-program-hour.current-hour { border: 1px solid #F00000; } -#master-program-schedule .master-program-hour div { +.master-program-schedule .master-program-hour div { padding-bottom: 1em; } -#master-program-schedule .show-wrap { +.master-program-schedule .show-wrap { position: relative; display: -ms-grid; display: grid; @@ -192,85 +213,90 @@ height: 100%; } -#master-program-schedule .show-title, #master-program-schedule .show-image, #master-program-schedule .show-desc { +.master-program-schedule .show-title, .master-program-schedule .show-desc { max-width: 120px; text-align: center; } -#master-program-schedule .show-info.overflow { +.master-program-schedule .show-image { + max-width: 120px; + margin: 0 auto; +} + +.master-program-schedule .show-info.overflow { border-bottom: transparent !important; position: relative; } -#master-program-schedule .show-info.continued { +.master-program-schedule .show-info.continued { border-top: transparent !important; } -#master-program-schedule .show-info .master-show-entry { +.master-program-schedule .show-info .master-show-entry { position: relative; display: block; /* height: 100%; */ } -#master-program-schedule .show-info .master-show-entry.finished { +.master-program-schedule .show-info .master-show-entry.finished { height: 99%; } -#master-program-schedule .show-info .master-show-entry.nowplaying { +.master-program-schedule .show-info .master-show-entry.nowplaying { border: 2px solid #F00000; } -#master-program-schedule .show-info .master-show-entry.overflow, -#master-program-schedule .show-info .master-show-entry.nowplaying.overflow { +.master-program-schedule .show-info .master-show-entry.overflow, +.master-program-schedule .show-info .master-show-entry.nowplaying.overflow { border-bottom: 1px solid transparent; } -#master-program-schedule .show-info .master-show-entry.continued, -#master-program-schedule .show-info .master-show-entry.nowplaying.continued { +.master-program-schedule .show-info .master-show-entry.continued, +.master-program-schedule .show-info .master-show-entry.nowplaying.continued { border-top: 1px solid transparent; } -#master-program-schedule .show-info .master-show-entry.continued.overflow, -#master-program-schedule .show-info .master-show-entry.nowplaying.continued.overflow { +.master-program-schedule .show-info .master-show-entry.continued.overflow, +.master-program-schedule .show-info .master-show-entry.nowplaying.continued.overflow { border-bottom: 1px solid transparent; } -#master-program-schedule .master-show-entry.highlighted { +.master-program-schedule .master-show-entry.highlighted { border: 1px dashed orange; background-color: rgba(235,235,0,0.4); } -#master-program-schedule .master-show-entry.before-current { +.master-program-schedule .master-show-entry.before-current { opacity: 0.8; } -#master-program-schedule span.show-title, -#master-program-schedule span.show-file, -#master-program-schedule span.show-time, -#master-program-schedule span.show-encore { +.master-program-schedule span.show-title, +.master-program-schedule span.show-file, +.master-program-schedule span.show-time, +.master-program-schedule span.show-encore { display: block; } -#master-program-schedule .show-host-names { +.master-program-schedule .show-host-names { font-size: 0.8em; } -#master-program-schedule .show-time, #master-program-schedule .show-user-time { +.master-program-schedule .show-time, .master-program-schedule .show-user-time { font-size: 0.8em; } -#master-program-schedule .show-encore { +.master-program-schedule .show-encore { font-size: 0.8em; color: #EE8E66; } -#master-program-schedule .show-file { +.master-program-schedule .show-file { margin-bottom: 5px; margin-top: 5px; font-size: 0.6em; } -#master-program-schedule .show-file a { +.master-program-schedule .show-file a { width: 95%; height: 20px; background-color: #931B25; @@ -280,19 +306,19 @@ margin-bottom: 3px; } -#master-program-schedule .show-file a:hover { +.master-program-schedule .show-file a:hover { background-color: #C51D2E; color: #ffffff; } -#master-program-schedule .show-desc { +.master-program-schedule .show-desc { font-size: 0.6em; } /* List View Styles */ /* ---------------- */ -#master-list .master-list-day-item.highlighted { +.master-list .master-list-day-item.highlighted { border: 1px dashed orange; background-color: rgba(235,235,0,0.4); } @@ -301,32 +327,32 @@ /* Tabbed View Styles */ /* ------------------ */ -#master-schedule-tabs { +.master-schedule-tabs { border: none; padding: 0; margin: 0; width: 100%; } -#master-schedule-tabs .shift-left-arrow { +.master-schedule-tabs .shift-left-arrow { float: left; } -#master-schedule-tabs .shift-right-arrow { +.master-schedule-tabs .shift-right-arrow { float: right; } -#master-schedule-tabs .shift-left-arrow, #master-schedule-tabs .shift-right-arrow, -#master-schedule-tabs .first-tab.day-0 .shift-left-arrow, #master-schedule-tabs .last-tab.day-6 .shift-right-arrow { +.master-schedule-tabs .shift-left-arrow, .master-schedule-tabs .shift-right-arrow, +.master-schedule-tabs .first-tab.day-0 .shift-left-arrow, .master-schedule-tabs .last-tab.day-6 .shift-right-arrow { display: none; padding: 0; } -#master-schedule-tabs .shift-left-arrow a, #master-schedule-tabs .shift-right-arrow a { +.master-schedule-tabs .shift-left-arrow a, .master-schedule-tabs .shift-right-arrow a { text-decoration: none; } -#master-schedule-tabs .first-tab .shift-left-arrow, #master-schedule-tabs .last-tab .shift-right-arrow { +.master-schedule-tabs .first-tab .shift-left-arrow, .master-schedule-tabs .last-tab .shift-right-arrow { display: inline-block; font-size: 2em; font-weight: bold; @@ -339,13 +365,23 @@ display: inline-block; margin: 0; padding: 0 10px; - font-size: 1em; - line-height: 2em; + font-size: 2em; + line-height: 1em; vertical-align: top; cursor: pointer; } -#master-schedule-tabs .master-schedule-tabs-day { +.master-schedule-tabs-loader .master-schedule-tabs-arrow { + width: 1em; + text-align: center; +} + +.master-schedule-tabs-loader .master-schedule-tabs-arrow:hover { + background-color: rgba(128,128,128,0.5); + border-radius: 1em; +} + +.master-schedule-tabs .master-schedule-tabs-day { display: inline-block; position: relative; min-width: 100px; @@ -363,45 +399,45 @@ border-bottom: 0; } -#master-schedule-tabs .master-schedule-tabs-day.start-tab, -#master-schedule-tabs .master-schedule-tabs-day.first-tab { +.master-schedule-tabs .master-schedule-tabs-day.start-tab, +.master-schedule-tabs .master-schedule-tabs-day.first-tab { margin-left: 0; } -#master-schedule-tabs .master-schedule-tabs-day.current-day { - border: 2px solid #000000; +.master-schedule-tabs .master-schedule-tabs-day.current-day { + border: 1px solid #F00000; border-bottom: 0; } -#master-schedule-tabs .master-schedule-tabs-headings { +.master-schedule-tabs .master-schedule-tabs-headings { display: inline-block; padding: 5px 10px 5px 10px; } -#master-schedule-tabs .master-schedule-tabs-day-name { +.master-schedule-tabs .master-schedule-tabs-day-name { /* display: inline-block; padding: 5px 10px 5px 10px; */ display: block; font-size: 1em; } -#master-schedule-tabs .master-schedule-tabs-date { +.master-schedule-tabs .master-schedule-tabs-date { display: block; font-size: 0.75em; } -#master-schedule-tabs .master-schedule-tabs-day.active-day-tab { +.master-schedule-tabs .master-schedule-tabs-day.active-day-tab { font-weight: bold; background-color: #ffffff; color: #000000; /* border-bottom: 0; */ } -#master-schedule-tabs .master-schedule-tabs-day .master-schedule-tab-bottom { +.master-schedule-tabs .master-schedule-tabs-day .master-schedule-tab-bottom { display: none; } -#master-schedule-tabs .master-schedule-tabs-day.active-day-tab .master-schedule-tab-bottom { +.master-schedule-tabs .master-schedule-tabs-day.active-day-tab .master-schedule-tab-bottom { position: absolute; display: block; bottom: 0; @@ -412,17 +448,17 @@ z-index: 999; } -#master-schedule-tab-panels { +.master-schedule-tab-panels { min-width: 300px; } -#master-schedule-tab-panels .master-schedule-tabs-selected { +.master-schedule-tab-panels .master-schedule-tabs-selected { display: none; padding: 10px 10px 0 10px; margin: 0; } -#master-schedule-tab-panels .master-schedule-tabs-panel { +.master-schedule-tab-panels .master-schedule-tabs-panel { display: none; float: left; margin: 0; @@ -433,30 +469,30 @@ list-style: none; } -#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show { +.master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show { padding: 10px 10px; margin: 0 10px; clear: both; list-style: none; } -#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.nowplaying { +.master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.nowplaying { border: 1px solid #F00000; } -#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.first-show { +.master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.first-show { margin-top: 20px; } -#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.last-show { +.master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-show.last-show { margin-bottom: 20px; } -#master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-no-shows { +.master-schedule-tab-panels .master-schedule-tabs-panel .master-schedule-tabs-no-shows { padding-top: 30px; padding-bottom: 30px; } -#master-schedule-tab-panels .master-schedule-tabs-panel.active-day-panel { +.master-schedule-tab-panels .master-schedule-tabs-panel.active-day-panel { display: block; border: 1px solid #333333; background-color: #ffffff; @@ -464,71 +500,71 @@ border-radius: 0 7px 7px 7px; } -#master-schedule-tab-panels .master-schedule-tabs-show.before-current { +.master-schedule-tab-panels .master-schedule-tabs-show.before-current { opacity: 0.8; } -#master-schedule-tab-panels.hide-past-shows .master-show-entry.before-current { +.master-schedule-tab-panels.hide-past-shows .master-show-entry.before-current { display: none; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-info, -#master-schedule-tab-panels .master-schedule-tabs-show .show-image, -#master-schedule-tab-panels .master-schedule-tabs-show .show-desc { +.master-schedule-tab-panels .master-schedule-tabs-show .show-info, +.master-schedule-tab-panels .master-schedule-tabs-show .show-image, +.master-schedule-tab-panels .master-schedule-tabs-show .show-desc { display: inline-block; vertical-align: top; font-size: 1em; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-image { +.master-schedule-tab-panels .master-schedule-tabs-show .show-image { margin-right: 10px; width: 180px; text-align: center; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-image.right-image { +.master-schedule-tab-panels .master-schedule-tabs-show .show-image.right-image { margin-right: 0; margin-left: 10px; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-info { +.master-schedule-tab-panels .master-schedule-tabs-show .show-info { min-width: 260px; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-info.has-show-desc { +.master-schedule-tab-panels .master-schedule-tabs-show .show-info.has-show-desc { width: 260px; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-info.left-image { +.master-schedule-tab-panels .master-schedule-tabs-show .show-info.left-image { text-align: left; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-info.right-image { +.master-schedule-tab-panels .master-schedule-tabs-show .show-info.right-image { text-align: right; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-desc { +.master-schedule-tab-panels .master-schedule-tabs-show .show-desc { max-width: 400px; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-time, -#master-schedule-tab-panels .master-schedule-tabs-show .show-user-time, -#master-schedule-tab-panels .master-schedule-tabs-show .show-host-names { +.master-schedule-tab-panels .master-schedule-tabs-show .show-time, +.master-schedule-tab-panels .master-schedule-tabs-show .show-user-time, +.master-schedule-tab-panels .master-schedule-tabs-show .show-host-names { font-size: 0.9em; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-encore, -#master-schedule-tab-panels .master-schedule-tabs-show .show-file, -#master-schedule-tab-panels .master-schedule-tabs-show .show-genres { +.master-schedule-tab-panels .master-schedule-tabs-show .show-encore, +.master-schedule-tab-panels .master-schedule-tabs-show .show-file, +.master-schedule-tab-panels .master-schedule-tabs-show .show-genres { font-size: 0.8em; } -#master-schedule-tab-panels .master-schedule-tabs-show .show-desc { +.master-schedule-tab-panels .master-schedule-tabs-show .show-desc { font-size: 0.75em; margin-left: 20px; } -#master-schedule-tab-panels .master-schedule-tabs-show.highlighted { +.master-schedule-tab-panels .master-schedule-tabs-show.highlighted { border: 1px dashed orange; background-color: rgba(235,235,0,0.4); } @@ -595,18 +631,18 @@ /* Media Queries */ @media screen and (max-width: 782px) { - #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 120px;} + .master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 120px;} } @media screen and (max-width: 599px) { - #master-program-schedule .show-image, - #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 90px;} - #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 75px;} + .master-program-schedule .show-image, + .master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 90px;} + .master-program-schedule .master-program-hour-heading, .master-program-schedule .master-program-hour-row {width: 75px;} #master-schedule-controls-wrapper .radio-station-server-clock, #master-schedule-controls-wrapper .radio-station-user-clock {margin-bottom: 10px;} #master-schedule-controls-wrapper .radio-clock-title {display: block; min-height: auto; margin-bottom: 5px;} } @media screen and (max-width: 299px) { - #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 50px;} + .master-program-schedule .master-program-hour-heading, .master-program-schedule .master-program-hour-row {width: 50px;} } diff --git a/includes/blocks.php b/includes/blocks.php index cb40e87..b220b7c 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -62,15 +62,16 @@ function radio_station_get_block_attributes() { 'view' => array( 'type' => 'string', 'default' => 'table' ), 'image_position' => array( 'type' => 'string', 'default' => 'left' ), 'hide_past_shows' => array( 'type' => 'boolean', 'default' => false ), + // --- Header Displays --- + 'time_header' => array( 'type' => 'string', 'default' => 'clock' ), + 'clock' => array( 'type' => 'boolean', 'default' => true ), + 'timezone' => array( 'type' => 'boolean', 'default' => true ), + 'selector' => array( 'type' => 'boolean', 'default' => true ), // --- Time Display --- 'display_day' => array( 'type' => 'string', 'default' => 'short' ), 'display_month' => array( 'type' => 'string', 'default' => 'short' ), 'start_day' => array( 'type' => 'string', 'default' => '' ), 'time_format' => array( 'type' => 'string', 'default' => '' ), - // --- Extra Displays --- - 'selector' => array( 'type' => 'boolean', 'default' => true ), - 'clock' => array( 'type' => 'boolean', 'default' => true ), - 'timezone' => array( 'type' => 'boolean', 'default' => true ), // --- Show Display --- 'show_times' => array( 'type' => 'boolean', 'default' => true ), 'show_link' => array( 'type' => 'boolean', 'default' => true ), @@ -92,7 +93,7 @@ function radio_station_get_block_attributes() { 'player' => array( // --- Player Content --- 'url' => array( 'type' => 'string', 'default' => '' ), - 'station' => array( 'type' => 'string', 'default' => '' ), + 'title' => array( 'type' => 'string', 'default' => '' ), 'image' => array( 'type' => 'string', 'default' => 'default' ), // ---- Player Options --- 'script' => array( 'type' => 'string', 'default' => 'default' ), @@ -198,6 +199,7 @@ function radio_station_get_block_attributes() { 'no_playlist' => array( 'type' => 'string', 'default' => '' ), 'hide_empty' => array( 'type' => 'boolean', 'default' => false ), // --- Playlist Display Options --- + 'playlist_title' => array( 'type' => 'boolean', 'default' => false ), 'link' => array( 'type' => 'boolean', 'default' => true ), 'countdown' => array( 'type' => 'boolean', 'default' => true ), // --- Track Display Options --- @@ -316,7 +318,7 @@ function raddio_station_block_editor_assets() { // - fix cutoff label widths - $css = '.components-panel .components-panel__body.radio-block-controls .components-panel__row label { width: 100%; max-width: 100%; min-width: 150px; overflow: visible;}' . "\n"; - // - select multiple height fix - + // - multiple select minimum height fix - // ref: https://github.com/WordPress/gutenberg/issues/27166 $css .= '.components-panel .components-panel__body.radio-block-controls .components-select-control__input[multiple] {min-height: 100px;}'; $css = apply_filters( 'radio_station_block_control_styles', $css ); @@ -342,6 +344,7 @@ function raddio_station_block_editor_assets() { // note: this is currently unnecessary as styles are enqueued in shortcodes // and the shortcodes are used as the block render_callback functions already // 2.5.0: added for any future frontend block style fixes +// note: according to WP docs this fired on both editor and frontend // add_action( 'enqueue_block_assets', 'radio_station_enqueue_block_assets' ); function radio_station_enqueue_block_assets() { diff --git a/includes/legacy.php b/includes/legacy.php index 9015ed0..5d6b95f 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -402,19 +402,46 @@ function radio_station_get_now_playing( $time = false ) { $show_id = $current_show['show']['id']; // TODO: improve handling of playlists for overrides + $override = false; if ( isset( $current_show['override'] ) && $current_show['override'] ) { - // TODO: check if override is linked to show - $playlist = apply_filters( 'radio_station_override_now_playing', false, $current_show['override'] ); - return $playlist; + $override = true; + // 2.5.0: check if override is linked to show + $override_id = $show_id; + $linked_show = get_post_meta( $override_id, 'linked_show_id', true ); + if ( $linked_show ) { + $show_id = $linked_show; + } else { + $playlist = apply_filters( 'radio_station_override_now_playing', false, $current_show['override'] ); + return $playlist; + } } // 2.3.3.9: fix to assign current show shifts to playlist data - // TODO: match current playlist to assigned show shift ? + // TODO: maybe match current playlist to assigned show shift ? $playlist = array(); $shifts = get_post_meta( $show_id, 'show_sched', true ); if ( $shifts ) { $playlist['shifts'] = $shifts; } + // 2.5.0: merge in override shifts + if ( $override ) { + $override_shifts = get_post_meta( $override_id, 'show_override_sched', true ); + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { + if ( isset( $$playlist['shifts'] ) ) { + $playlist['shifts'] = array_merge( $playlist['shifts'], $override_shifts ); + } else { + $playlist['shifts'] = $override_shifts; + } + } + $recurring_shifts = get_post_meta( $override_id, 'show_recurring_sched', true ); + if ( $recurring_shifts && is_array( $recurring_shifts ) && ( count( $recurring_shifts ) > 0 ) ) { + if ( isset( $$playlist['shifts'] ) ) { + $playlist['shifts'] = array_merge( $playlist['shifts'], $recurring_shifts ); + } else { + $playlist['shifts'] = $recurring_shifts; + } + } + } // --- grab the most recent playlist for the current show --- $args = array( @@ -440,7 +467,6 @@ function radio_station_get_now_playing( $time = false ) { $playlist['posts'] = $playlist_posts; // TODO: check for playlist linked to this shift / date? - if ( $playlist_posts && is_array( $playlist_posts ) && ( count( $playlist_posts ) > 0 ) ) { // --- fetch the tracks for the playlist --- @@ -474,8 +500,10 @@ function radio_station_get_now_playing( $time = false ) { // --- add show and playlist data --- // 2.3.0: add IDs and URLs instead of just playlist URL + // 2.5.0: add playlist title to playlist array $playlist['show'] = $show_id; $playlist['show_url'] = get_permalink( $show_id ); + $playlist['title'] = $playlist_post->post_title; $playlist['playlist'] = $playlist_post->ID; $playlist['playlist_url'] = get_permalink( $playlist_post->ID ); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 2f252bc..391283d 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -20,6 +20,18 @@ // ------------------------- add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { + + global $radio_station_data; + + // 2.5.0: maybe set schedule instances array + if ( !isset( $radio_station_data['schedules'] ) ) { + $radio_station_data['schedules'] = array(); + } + if ( !isset( $radio_station_data['schedules']['instances'] ) ) { + $radio_station_data['schedules']['instances'] = -1; + } + $radio_station_data['schedules']['instances']++; + $instances = $radio_station_data['schedules']['instances']; // --- make attributes backward compatible --- // 2.3.0: convert old list attribute to view @@ -51,8 +63,27 @@ function radio_station_master_schedule( $atts ) { unset( $atts['time'] ); } - // --- get default clock display setting --- + // 2.5.0: convert widget option time_header to clock/timezone atts + if ( isset( $atts['time_header'] ) ) { + if ( 'clock' == $atts['time_header'] ) { + $atts['clock'] = 1; + $atts['timezone'] = 0; + } elseif ( 'timezone' == $atts['time_header'] ) { + $atts['clock'] = 0; + $atts['timezone'] = 1; + } elseif ( 'none' == $atts['time_header'] ) { + $atts['clock'] = 0; + $atts['timezone'] = 0; + } + } + + if ( RADIO_STATION_DEBUG ) { + echo 'Master Schedule Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; + } + + // --- get default clock display settings --- $clock = radio_station_get_setting( 'schedule_clock' ); + $time_format = (int) radio_station_get_setting( 'clock_time_format' ); // --- merge shortcode attributes with defaults --- // 2.3.0: added show_desc (default off) @@ -68,7 +99,6 @@ function radio_station_master_schedule( $atts ) { // 2.3.2: added display_day, display_date and display_month attributes // 2.3.3.9: added start_date attribute for non-now schedules // 2.3.3.9: added active_date attribute for schedule switching - $time_format = (int) radio_station_get_setting( 'clock_time_format' ); $defaults = array( // --- schedule view --- 'view' => 'table', @@ -99,6 +129,10 @@ function radio_station_master_schedule( $atts ) { 'show_encore' => 1, 'show_file' => 0, + // --- instance values --- + 'widget' => 0, + 'block' => 0, + // --- converted and deprecated --- // 'list' => 0, // 'show_djs' => 0, @@ -134,29 +168,37 @@ function radio_station_master_schedule( $atts ) { // 2.3.3: revert show description default for tabbed view // 2.3.3.8: add default show image position (left aligned) // 2.3.3.8: add default for hide past shows (false) + // 2.5.0: change date display default to true $defaults['show_image'] = 1; $defaults['show_hosts'] = 1; $defaults['show_genres'] = 1; $defaults['display_day'] = 'full'; - $defaults['display_date'] = false; + $defaults['display_date'] = true; $defaults['image_position'] = 'left'; $defaults['hide_past_shows'] = false; } elseif ( in_array( 'tabs', $views ) ) { - $defaults['tabs_show_image'] = 1; - $defaults['tabs_show_hosts'] = 1; - $defaults['tabs_show_genres'] = 1; - $defaults['tabs_display_day'] = 'full'; - $defaults['tabs_display_date'] = false; - $defaults['tabs_image_position'] = 'left'; - $defaults['tabs_hide_past_shows'] = false; + // note: these apply to tab view only + $defaults['image_position'] = 'left'; + $defaults['hide_past_shows'] = false; + // 2.5.0: remove default separate view settings for widgets + if ( ( !isset( $atts['widget'] ) || !$atts['widget'] ) && ( !isset( $atts['block'] ) || !$atts['block'] ) ) { + $defaults['tabs_show_image'] = 1; + $defaults['tabs_show_hosts'] = 1; + $defaults['tabs_show_genres'] = 1; + $defaults['tabs_display_day'] = 'full'; + $defaults['tabs_display_date'] = true; + } } if ( 'list' == $atts['view'] ) { // 2.3.2: add display date attribute $defaults['show_genres'] = 1; $defaults['display_date'] = false; } elseif ( in_array( 'list', $views ) ) { - $defaults['list_show_genres'] = 1; - $defaults['list_display_date'] = false; + // 2.5.0: remove default separate view settings for widgets + if ( ( !isset( $atts['widget'] ) || !$atts['widget'] ) && ( !isset( $atts['block'] ) || !$atts['block'] ) ) { + $defaults['list_show_genres'] = 1; + $defaults['list_display_date'] = false; + } } if ( ( 'divs' == $atts['view'] ) || ( in_array( 'divs', $views ) ) ) { // 2.3.3.8: moved divs view only default here @@ -165,7 +207,7 @@ function radio_station_master_schedule( $atts ) { } } // 2.3.3.9: filter defaults according to view(s) - $defaults = apply_filters( 'radio_station_master_schedule_default_atts', $defaults, $view, $views ); + $defaults = apply_filters( 'radio_station_master_schedule_default_atts', $defaults, $view, $views, $atts ); // --- merge attributes with defaults --- $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); @@ -192,7 +234,9 @@ function radio_station_master_schedule( $atts ) { // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views // 2.3.2: moved shortcode calls inside and added filters - $output .= '
    ' . "\n"; + // 2.5.0: added instances IDs and use class for selector + $id = ( 0 == $instances ) ? '' : '-' . $instances; + $output .= '
    ' . "\n"; $controls = array(); @@ -200,25 +244,25 @@ function radio_station_master_schedule( $atts ) { if ( $atts['clock'] ) { // --- radio clock --- - $controls['clock'] = '
    ' . "\n"; $clock_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); - $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); + $controls['clock'] = '
    ' . "\n"; + $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); $controls['clock'] .= "\n" . '
    ' . "\n"; } elseif ( $atts['timezone'] ) { // --- radio timezone --- - $controls['timezone'] = '
    ' . "\n"; $timezone_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); - $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); + $controls['timezone'] = '
    ' . "\n"; + $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); $controls['timezone'] .= "\n" . '
    ' . "\n"; } // --- genre selector --- if ( $atts['selector'] ) { - $controls['selector'] = '
    ' . "\n"; - $controls['selector'] .= radio_station_master_schedule_genre_selector(); + $controls['selector'] = '
    ' . "\n"; + $controls['selector'] .= radio_station_master_schedule_genre_selector( $instances ); $controls['selector'] .= "\n" . '
    ' . "\n"; } @@ -274,7 +318,9 @@ function radio_station_master_schedule( $atts ) { $override = apply_filters( 'radio_station_schedule_override', $output, $atts ); if ( strstr( $override, '' ) ) { $override = str_replace( '', '', $override ); - return $override; + // 2.5.0: added wrapper to shortcode output + $output = '
    ' . $override . '
    ' . "\n"; + return $output; } // ------------------------- @@ -290,6 +336,13 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: get and enqueue scripts inline directly if ( 'table' == $atts['view'] ) { + // 2.5.0: set view instance number + if ( !isset( $radio_station_data['schedules']['table'] ) ) { + $radio_station_data['schedules']['table'] = -1; + } + $radio_station_data['schedules']['table']++; + $instance = $radio_station_data['schedules']['table']; + // --- load table view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-table.php' ); @@ -306,10 +359,19 @@ function radio_station_master_schedule( $atts ) { $html = apply_filters( 'radio_station_master_schedule_table_view', $html, $atts ); // note: keep backwards compatible non-prefixed filter $html = apply_filters( 'master_schedule_table_view', $html, $atts ); - return $output . $html; + // 2.5.0: added shortcode wrapper + $output = '
    ' . $output . $html . '
    ' . "\n"; + return $output; } elseif ( 'tabs' == $atts['view'] ) { + // 2.5.0: set view instance number + if ( !isset( $radio_station_data['schedules']['tabs'] ) ) { + $radio_station_data['schedules']['tabs'] = -1; + } + $radio_station_data['schedules']['tabs']++; + $instance = $radio_station_data['schedules']['tabs']; + // --- load tabs view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-tabs.php' ); @@ -326,10 +388,19 @@ function radio_station_master_schedule( $atts ) { $html = apply_filters( 'radio_station_master_schedule_tabs_view', $html, $atts ); // note: keep backwards compatible non-prefixed filter $html = apply_filters( 'master_schedule_tabs_view', $html, $atts ); - return $output . $html; + // 2.5.0: added shortcode wrapper + $output = '
    ' . $output . $html . '
    ' . "\n"; + return $output; } elseif ( 'list' == $atts['view'] ) { + // 2.5.0: set view instance number + if ( !isset( $radio_station_data['schedules']['list'] ) ) { + $radio_station_data['schedules']['list'] = -1; + } + $radio_station_data['schedules']['list']++; + $instance = $radio_station_data['schedules']['list']; + // --- load list view template --- ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-list.php' ); @@ -346,7 +417,9 @@ function radio_station_master_schedule( $atts ) { $html = apply_filters( 'radio_station_master_schedule_list_view', $html, $atts ); // note: keep backwards compatible non-prefixed filter $html = apply_filters( 'master_schedule_list_view', $html, $atts ); - return $output . $html; + // 2.5.0: added shortcode wrapper + $output = '
    ' . $output . $html . '
    ' . "\n"; + return $output; } @@ -514,16 +587,11 @@ function radio_station_master_schedule( $atts ) { function radio_station_master_schedule_loader_js( $atts ) { // --- set AJAX URL with attribute keys --- - $loader_url = esc_url( add_query_arg( 'action', 'radio_station_schedule', admin_url( 'admin-ajax.php' ) ) ); - $ignore_keys = array( 'start_date', 'view' ); - foreach ( $atts as $key => $value ) { - if ( !in_array( $key, $ignore_keys ) ) { - $loader_url .= '&' . esc_js( $key ) . '=' . esc_js( $value ); - } - } + $loader_url = add_query_arg( 'action', 'radio_station_schedule', admin_url( 'admin-ajax.php' ) ); // --- schedule loader function --- - $js = "function radio_load_schedule(direction,view,clear) { + // 2.5.0: added instance ID argument + $js = "function radio_load_schedule(instance,direction,view,clear) { if (document.getElementById('schedule-loader-frame')) { view = radio_cookie.get('admin_schedule_view'); radio_load_view(view); return; } @@ -531,14 +599,15 @@ function radio_station_master_schedule_loader_js( $atts ) { startdate = document.getElementById('schedule-start-date').value; activedate = document.getElementById('schedule-active-date').value; if (!view) { + if (( 0 == instance) || ( false === instance)) {id = '';} else {id = '-'+instance;} if (jQuery('.master-schedule-view-tab.current-view').length) { view = jQuery('.master-schedule-view-tab.current-view').attr('id').replace('master-schedule-view-tab-',''); } else { - if (jQuery('#master-program-schedule').length) {view = 'table';} - else if (jQuery('#master-schedule-tabs').length) {view = 'tabs';} - else if (jQuery('#master-schedule-list').length) {view = 'list';} - else if (jQuery('#master-schedule-grid').length) {view = 'grid';} - else if (jQuery('#master-schedule-calendar').length) {view = 'calendar';} + if (jQuery('#master-program-schedule'+id).length) {view = 'table';} + else if (jQuery('#master-schedule-tabs'+id).length) {view = 'tabs';} + else if (jQuery('#master-schedule-list'+id).length) {view = 'list';} + else if (jQuery('#master-schedule-grid'+id).length) {view = 'grid';} + else if (jQuery('#master-schedule-calendar'+id).length) {view = 'calendar';} } } if (radio.debug) {console.log('Reloading Schedule View: '+view);} @@ -547,7 +616,15 @@ function radio_station_master_schedule_loader_js( $atts ) { else if (direction == 'previous') {offset = -(7 * 24 * 60 * 60 * 1000);} else if (direction == 'next') {offset = (7 * 24 * 60 * 60 * 1000);} newdate = new Date(new Date(startdate).getTime() + offset).toISOString().substr(0,10); - url = '" . esc_urL( $loader_url ) . "&view='+view; + url = '" . esc_url( $loader_url ); + // 2.6.0: add args directly to avoid double escaping + $ignore_keys = array( 'start_date', 'view' ); + foreach ( $atts as $key => $value ) { + if ( !in_array( $key, $ignore_keys ) ) { + $js .= '&' . esc_js( $key ) . '=' . esc_js( $value ); + } + } + $js .= "&view='+view+'&instance='+instance; timestamp = Math.floor((new Date()).getTime() / 1000); url += '×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; if (startday != '') {url += '&start_day='+startday;} @@ -575,6 +652,9 @@ function radio_station_ajax_schedule_loader() { radio_station_clear_cached_data( false ); } + // 2.5.0: get schedule instance ID + $instance = absint( $_REQUEST['instance'] ); + // --- sanitize shortcode attributes --- $debug = true; $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); @@ -593,6 +673,7 @@ function radio_station_ajax_schedule_loader() { } else { $views = array( $atts['view'] ); } + echo "Views: " . esc_html( print_r( $views, true ) ) . "\n"; foreach ( $views as $view ) { $view = trim( $view ); @@ -611,11 +692,25 @@ function radio_station_ajax_schedule_loader() { $schedule_id = 'master-schedule-calendar'; } - // --- send new schedule to parent window --- + // --- get schedule shortcode output --- $js .= "schedule = document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML;" . "\n"; - $js .= "parent.document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML = schedule;" . "\n"; if ( isset( $panels_id ) ) { $js .= "panels = document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML;" . "\n"; + } + + // 2.5.0: maybe append instance ID + if ( $instance > 0 ) { + $schedule_id .= '-' . $instance; + if ( isset( $panels_ids ) ) { + $panels_id .= ' ' . $instance; + } + } + + // --- send new schedule to parent window --- + $js .= "if (parent.document.getElementById('" . esc_attr( $schedule_id ) . "')) {" . "\n"; + $js .= "parent.document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML = schedule;" . "\n"; + $js .= "}" . "\n"; + if ( isset( $panels_id ) ) { $js .= "parent.document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML = panels;" . "\n"; // 2.5.0: unset panels to prevent possible multiple view conflict unset( $panels_id ); @@ -635,7 +730,8 @@ function radio_station_ajax_schedule_loader() { // --- genre highlighter --- $js .= "if (!genres_highlighted && (typeof parent.radio_genre_highlight == 'function')) {" . "\n"; - $js .= "parent.radio_genre_highlight();" . "\n"; + // TODO: add (wrapper) instance to radio_genre_highlight call + // $js .= "parent.radio_genre_highlight();" . "\n"; $js .= "genres_highlighted = true;" . "\n"; $js .= "}" . "\n"; @@ -645,31 +741,35 @@ function radio_station_ajax_schedule_loader() { $init_js = ''; if ( 'table' == $view ) { $init_js .= "if (typeof parent.radio_table_initialize == 'function') {" . "\n"; + // 2.5.0: set table state to uninitialized + $init_js .= "parent.radio_table_init = false;" . "\n"; $init_js .= "parent.radio_table_initialize();" . "\n"; - // $init_js .= "clearInterval(schedule_loader);" . "\n"; + $init_js .= "if (parent.radio.debug) {console.log('Table Reinitialized');}" . "\n"; $init_js .= "}" . "\n"; } elseif ( 'tabs' == $view ) { $init_js .= "if (typeof parent.radio_tabs_initialize == 'function') {" . "\n"; $init_js .= "parent.radio_tabs_init = false;" . "\n"; $init_js .= "parent.radio_tabs_initialize();" . "\n"; - // $init_js .= "clearInterval(schedule_loader);" . "\n"; + $init_js .= "if (parent.radio.debug) {console.log('Tabs Reinitialized');}" . "\n"; $init_js .= "}" . "\n"; } elseif ( 'list' == $view ) { $init_js .= "if (typeof parent.radio_list_highlight == 'function') {" . "\n"; $init_js .= "parent.radio_list_highlight();" . "\n"; - // $init_js .= "clearInterval(schedule_loader);" . "\n"; $init_js .= "}" . "\n"; } + // 2.5.0: added individual init script filtering $init_js = apply_filters( 'radio_station_master_schedule_init_script', $init_js, $view, $atts ); + $js .= $init_js; } + // --- maybe retrigger convert to user times --- $js .= "if (typeof parent.radio_convert_times == 'function') {parent.radio_convert_times();}" . "\n"; // --- placeholder for extra loader functions --- // (do not remove or modify this line - for backwards compatibility) - $js .= "/* LOADER PLACEHOLDER */"; + $js .= "/* LOADER PLACEHOLDER */" . "\n"; $js .= "clearInterval(schedule_loader);" . "\n"; @@ -689,7 +789,7 @@ function radio_station_ajax_schedule_loader() { // Show Genre Selector // ------------------- // 2.3.3.9: change name from radio_station_master_schedule_selector -function radio_station_master_schedule_genre_selector() { +function radio_station_master_schedule_genre_selector( $instances ) { // --- get genres --- $args = array( @@ -704,7 +804,8 @@ function radio_station_master_schedule_genre_selector() { } // --- open genre highlighter div --- - $html = '
    '; + $id = ( 0 == $instances ) ? '' : '-' . $instances; + $html = '
    '; $html .= '' . esc_html( __( 'Genres', 'radio-station' ) ) . ': '; // --- genre highlight links --- @@ -713,9 +814,11 @@ function radio_station_master_schedule_genre_selector() { $genre_links = array(); foreach ( $genres as $i => $genre ) { $slug = sanitize_title_with_dashes( $genre->name ); - $onclick = "radio_genre_highlight('" . esc_attr( $slug ) . "');"; + // 2.5.0: added instance argument to genre highlight function + $onclick = "radio_genre_highlight(" . esc_js( $instances ) . ",'" . esc_js( $slug ) . "');"; $title = __( 'Click to toggle Highlight of Shows with this Genre.', 'radio-station' ); - $genre_link = ''; + // 2.5.0: remove element ID in favour of class + $genre_link = ''; $genre_link .= esc_html( $genre->name ) . ''; $genre_links[] = $genre_link; } @@ -728,37 +831,48 @@ function radio_station_master_schedule_genre_selector() { // 2.3.0: improved to work with table, tabs or list view // 2.3.3.9: added genre class targets for grid and calendar views // 2.3.3.9: added accepting false to retrigger highlights for AJAX + // 2.5.0: added instance argument to genre highlight function + // 2.5.0: modified to use traversal via passed wrapper instance + // 2.5.0: added genre- prefix to class selectors $js = "var radio_genres_selected = new Array(); - function radio_genre_highlight(genre) { - classes = '.master-show-entry, .master-schedule-tabs-show, .master-list-day-item, .master-schedule-grid-show, .master-schedule-calendar-date, .master-schedule-calendar-show'; + function radio_genre_highlight(instance,genre) { + if (0 == instance) {id = '';} else {id = '-'+instance;} + wrapper = jQuery('#master-schedule'+id); + classes = ['.master-show-entry', '.master-schedule-tabs-show', '.master-list-day-item', '.master-schedule-grid-show', '.master-schedule-calendar-date', '.master-schedule-calendar-show', '.shows-slider-item']; + if (radio.debug) {console.log('Genres Before: '+radio_genres_selected[instance]);} if (genre === false) { - jQuery(classes).removeClass('highlighted'); - for (i = 0; i < radio_genres_selected.length; i++) { - jQuery('.'+radio_genres_selected[i]).addClass('highlighted'); - } + for (i = 0; i < classes.length; i++) { + wrapper.find(classes[i]).removeClass('highlighted'); + } + if (typeof radio_genres_selected.instance != 'undefined') { + for (i = 0; i < radio_genres_selected[instance].length; i++) { + wrapper.find('.genre-'+radio_genres_selected[instance][i]).addClass('highlighted'); + } + } } else { - if (jQuery('#genre-highlight-'+genre).hasClass('highlighted')) { - jQuery('#genre-highlight-'+genre).removeClass('highlighted'); - jQuery(classes).removeClass('highlighted'); - + if (wrapper.find('.genre-highlight-'+genre).hasClass('highlighted')) { + wrapper.find('.genre-highlight-'+genre).removeClass('highlighted'); + for (i = 0; i < classes.length; i++) { + wrapper.find(classes[i]).removeClass('highlighted'); + } j = 0; new_genre_highlights = new Array(); - for (i = 0; i < radio_genres_selected.length; i++) { - if (radio_genres_selected[i] != genre) { - jQuery('.'+radio_genres_selected[i]).addClass('highlighted'); - new_genre_highlights[j] = radio_genres_selected[i]; j++; + if (typeof radio_genres_selected[instance] != 'undefined') { + for (i = 0; i < radio_genres_selected[instance].length; i++) { + if (radio_genres_selected[instance][i] != genre) { + wrapper.find('.genre-'+radio_genres_selected[instance][i]).addClass('highlighted'); + new_genre_highlights[j] = radio_genres_selected[instance][i]; j++; + } } } - radio_genres_selected = new_genre_highlights; - + radio_genres_selected[instance] = new_genre_highlights; } else { - jQuery('#genre-highlight-'+genre).addClass('highlighted'); - radio_genres_selected[radio_genres_selected.length] = genre; - jQuery('.'+genre).each(function () { - jQuery(this).addClass('highlighted'); - }); + wrapper.find('.genre-highlight-'+genre).addClass('highlighted'); + wrapper.find('.genre-'+genre).addClass('highlighted'); + if (typeof radio_genres_selected[instance] == 'undefined') {radio_genres_selected[instance] = new Array();} + radio_genres_selected[instance][radio_genres_selected[instance].length] = genre; } } - /* console.log(jQuery(classes)); */ + if (radio.debug) {console.log('Genres After: '+radio_genres_selected[instance]);} }"; // --- enqueue script --- @@ -772,6 +886,7 @@ classes = '.master-show-entry, .master-schedule-tabs-show, .master-list-day-item // Table View Javascript // --------------------- // 2.3.0: added for table responsiveness +// TODO: use traversals with instance IDs function radio_station_master_schedule_table_js() { // 2.3.2: added current show highlighting cycle @@ -789,156 +904,166 @@ function radio_station_master_schedule_table_js() { var radio_table_highlighting = setInterval(radio_table_highlight, 60000); }); jQuery(window).resize(function () { - radio_resize_debounce(function() {radio_table_responsive(false);}, 500, 'scheduletable'); + radio_resize_debounce(function() {radio_table_responsive(false,false);}, 500, 'scheduletable'); }); /* Table Initialize */ function radio_table_initialize() { - radio_table_responsive(false); + radio_table_responsive(false,false); radio_table_highlight(); radio_table_init = true; } /* Current Time Highlighting */ function radio_table_highlight() { - if (!jQuery('.master-program-day').length) {return;} - radio.current_time = Math.floor((new Date()).getTime() / 1000); - radio.offset_time = radio.current_time + radio.timezone.offset; - if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} - if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} - jQuery('.master-program-day').each(function() { - start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - jQuery(this).addClass('current-day'); - } else {jQuery(this).removeClass('current-day');} - }); - jQuery('.master-program-hour').each(function() { - hour = parseInt(jQuery(this).find('.master-program-server-hour').attr('data')); - offset_time = radio.current_time + radio.timezone.offset; - current = new Date(offset_time * 1000).toISOString(); - currenthour = current.substr(11, 2); - if (currenthour.substr(0,1) == '0') {currenthour = currenthour.substr(1,1);} - if (hour == currenthour) {jQuery(this).addClass('current-hour');} - else {jQuery(this).removeClass('current-hour');} - }); - for (i = 0; i < 7; i++) { - jQuery('#master-program-schedule .day-'+i).each(function() { - var radio_table_shift = false; - jQuery(this).find('.master-show-entry').each(function() { - start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (radio.debug) {console.log(jQuery(this)); console.log(start+' - '+end);} - if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - if (radio.debug) {console.log('^ Now Playing ^');} - jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); - /* also highlight split shift via matching shift class */ - if (jQuery(this).hasClass('overnight')) { - classes = jQuery(this).attr('class').split(/\s+/); - for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_table_shift = classes[i];} + if (!jQuery('.master-program-schedule').length) {return;} + jQuery('.master-program-schedule').each(function() { + var scheduletable = jQuery(this); + radio.current_time = Math.floor((new Date()).getTime() / 1000); + radio.offset_time = radio.current_time + radio.timezone.offset; + if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} + if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + jQuery(this).find('.master-program-day').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + jQuery(this).addClass('current-day'); + } else {jQuery(this).removeClass('current-day');} + }); + jQuery(this).find('.master-program-hour').each(function() { + hour = parseInt(jQuery(this).find('.master-program-server-hour').attr('data')); + offset_time = radio.current_time + radio.timezone.offset; + current = new Date(offset_time * 1000).toISOString(); + currenthour = current.substr(11, 2); + if (currenthour.substr(0,1) == '0') {currenthour = currenthour.substr(1,1);} + if (hour == currenthour) {jQuery(this).addClass('current-hour');} + else {jQuery(this).removeClass('current-hour');} + }); + for (i = 0; i < 7; i++) { + jQuery(this).find('.master-program-schedule .day-'+i).each(function() { + var radio_table_shift = false; + jQuery(this).find('.master-show-entry').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (radio.debug) {console.log(jQuery(this)); console.log(start+' - '+end);} + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + if (radio.debug) {console.log('^ Now Playing ^');} + jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_table_shift = classes[i];} + } } + } else { + jQuery(this).removeClass('nowplaying'); + if (start > radio.offset_time) {jQuery(this).addClass('after-current');} + else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} } - } else { - jQuery(this).removeClass('nowplaying'); - if (start > radio.offset_time) {jQuery(this).addClass('after-current');} - else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} + }); + if (radio_table_shift) { + scheduletable.find('.'+radio_table_shift).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } }); - if (radio_table_shift) { - jQuery('.'+radio_table_shift).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); - } - }); - } + } + }); } /* Make Table Responsive */ - function radio_table_responsive(leftright) { - if (!jQuery('.master-program-day').length) {return;} - - fallback = -1; selected = -1; foundday = false; - if (!leftright || (leftright == 'left')) { - if (jQuery('.master-program-day.first-column').length) { - start = jQuery('.master-program-day.first-column'); - } else {start = jQuery('.master-program-day').first(); fallback = 0;} - classes = start.attr('class').split(' '); - } else if (leftright == 'right') { - if (jQuery('.master-program-day.last-column').length) { - end = jQuery('.master-program-day.last-column'); - } else {end = jQuery('.master-program-day').last(); fallback = 6;} - classes = end.attr('class').split(' '); - } - for (i = 0; i < classes.length; i++) { - if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} - } - if (selected < 0) {selected = fallback;} - if (radio.debug) {console.log('Current Column: '+selected);} - - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - if (!jQuery('.master-program-day.day-'+selected).length) { - while (!foundday) { - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (jQuery('.master-program-day.day-'+selected).length) {foundday = true;} - if ((selected < 0) || (selected > 6)) {selected = fallback; foundday = true;} + function radio_table_responsive(leftright,instance) { + if (!jQuery('.master-program-schedule').length) {return;} + if (instance) { + if (0 == instance) {id = '';} else {id = '-'+instance;} + tableschedules = jQuery('#master-program-schedule'+id); + } else {tableschedules = jQuery('.master-program-schedule');} + + tableschedules.each(function() { + fallback = -1; selected = -1; foundday = false; + if (!leftright || (leftright == 'left')) { + if (jQuery(this).find('.master-program-day.first-column').length) { + start = jQuery(this).find('.master-program-day.first-column'); + } else {start = jQuery(this).find('.master-program-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery(this).find('.master-program-day.last-column').length) { + end = jQuery(this).find('.master-program-day.last-column'); + } else {end = jQuery(this).find('.master-program-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); } - } - if (radio.debug) {console.log('Selected Column: '+selected);} - - totalwidth = jQuery('#master-program-hour-heading').width(); - jQuery('.master-program-day, .master-program-hour-row .show-info').removeClass('first-column').removeClass('last-column').hide(); - jQuery('#master-program-schedule').css('width','100%'); - tablewidth = jQuery('#master-program-schedule').width(); - jQuery('#master-program-schedule').css('width','auto'); - columns = 0; firstcolumn = -1; lastcolumn = 7; endtable = false; - for (i = selected; i < 7; i++) { - if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { - if ((i > 0) && (i == selected)) {jQuery('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} - else if (i < 6) {jQuery('.master-program-day.day-'+i).addClass('last-column');} - jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); - colwidth = jQuery('.master-program-day.day-'+i).width(); - totalwidth = totalwidth + colwidth; - if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} - jQuery('.master-program-day.day-'+i).removeClass('last-column'); - if (totalwidth > tablewidth) { - if (radio.debug) {console.log('Hiding Column '+i);} - jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; - } else { - if (radio.debug) {console.log('Showing Column '+i);} - jQuery('.master-program-day.day-'+i).removeClass('last-column'); - totalwidth = totalwidth - colwidth + jQuery('.master-program-day.day-'+i).width(); - lastcolumn = i; columns++; + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} + } + if (selected < 0) {selected = fallback;} + if (radio.debug) {console.log('Current Column: '+selected);} + + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} + if (!jQuery(this).find('.master-program-day.day-'+selected).length) { + while (!foundday) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery(this).find('.master-program-day.day-'+selected).length) {foundday = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundday = true;} } } - - } - if (lastcolumn < 6) {jQuery('.master-program-day.day-'+lastcolumn).addClass('last-column');} - - if (leftright == 'right') { - for (i = (selected - 1); i > -1; i--) { - if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { - jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); - colwidth = jQuery('.master-program-day.day-'+i).width(); + if (radio.debug) {console.log('Selected Column: '+selected);} + + totalwidth = jQuery(this).find('.master-program-hour-heading').width(); + jQuery(this).find('.master-program-day, .master-program-hour-row .show-info').removeClass('first-column').removeClass('last-column').hide(); + jQuery(this).css('width','100%'); + tablewidth = jQuery(this).width(); + jQuery(this).css('width','auto'); + columns = 0; firstcolumn = -1; lastcolumn = 7; endtable = false; + for (i = selected; i < 7; i++) { + if (!endtable && (jQuery(this).find('.master-program-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} + else if (i < 6) {jQuery(this).find('.master-program-day.day-'+i).addClass('last-column');} + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); + colwidth = jQuery(this).find('.master-program-day.day-'+i).width(); totalwidth = totalwidth + colwidth; if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} + jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); if (totalwidth > tablewidth) { if (radio.debug) {console.log('Hiding Column '+i);} - jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; } else { if (radio.debug) {console.log('Showing Column '+i);} - jQuery('.master-program-day').removeClass('first-column'); - jQuery('.master-program-day.day-'+i).addClass('first-column'); - columns++; + jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); + totalwidth = totalwidth - colwidth + jQuery(this).find('.master-program-day.day-'+i).width(); + lastcolumn = i; columns++; + } + } + + } + if (lastcolumn < 6) {jQuery(this).find('.master-program-day.day-'+lastcolumn).addClass('last-column');} + + if (leftright == 'right') { + for (i = (selected - 1); i > -1; i--) { + if (!endtable && (jQuery(this).find('.master-program-day.day-'+i).length)) { + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); + colwidth = jQuery(this).find('.master-program-day.day-'+i).width(); + totalwidth = totalwidth + colwidth; + if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} + if (totalwidth > tablewidth) { + if (radio.debug) {console.log('Hiding Column '+i);} + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); + endtable = true; + } else { + if (radio.debug) {console.log('Showing Column '+i);} + jQuery(this).find('.master-program-day').removeClass('first-column'); + jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); + columns++; + } } } } - } - jQuery('#master-program-schedule').css('width','100%'); + jQuery(this).css('width','100%'); + }); } - /* Shift Day Left / Right */ - function radio_shift_day(leftright) { - radio_table_responsive(leftright); return false; + /* Shift Day Left / Right */ + function radio_shift_day(leftright,instance) { + radio_table_responsive(leftright,instance); return false; }" . "\n"; // --- filter and return --- @@ -951,11 +1076,12 @@ function radio_shift_day(leftright) { // Tabbed View Javascript // ---------------------- // 2.2.7: added for tabbed schedule view +// TODO: use traversals with instance IDs function radio_station_master_schedule_tabs_js() { // --- tab switching function --- // 2.3.2: added fallback if current day is not viewed - // TODO: check current server time for onload display + // TODO: check current server time for onload display ? /* date = new Date(); dayweek = date.getDay(); day = radio_get_weekday(dayweek); if (jQuery('#master-schedule-tabs-header-'+day).length) { id = jQuery('.master-schedule-tabs-day.selected-day').first().attr('id'); @@ -970,7 +1096,7 @@ function radio_station_master_schedule_tabs_js() { // 2.3.3.6: allow for clicking on date to change days // 2.3.3.8: make entire heading label div clickable to change tabs // 2.3.3.9: make into function and add to document ready code block - $js = "function radio_tabs_clicks() { + /* $js = "function radio_tabs_clicks() { jQuery('.master-schedule-tabs-headings').bind('click', function (event) { headerID = jQuery(event.target).closest('li').attr('id'); panelID = headerID.replace('header', 'day'); @@ -979,6 +1105,20 @@ function radio_station_master_schedule_tabs_js() { jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); jQuery('#'+panelID).addClass('active-day-panel'); }); + }" . "\n"; */ + // 2.5.0: use relative traversal from click target instead of IDs + $js = "function radio_tabs_clicks() { + if (radio.debug) {console.log('Binding Tabbed Schedule Tab Clicks');} + jQuery('.master-schedule-tabs-headings').bind('click', function (event) { + if (jQuery(event.target).hasClass('master-schedule-tabs-headings')) {day = jQuery(event.target).attr('data-href');} + else {day = jQuery(event.target).closest('.master-schedule-tabs-headings').attr('data-href');} + schedule = jQuery(event.target).closest('.master-schedule-tabs'); + panels = schedule.parent().find('.master-schedule-tab-panels'); + schedule.find('.master-schedule-tabs-day').removeClass('active-day-tab'); + schedule.find('.master-schedule-tabs-day-'+day).addClass('active-day-tab'); + panels.find('.master-schedule-tabs-panel').removeClass('active-day-panel'); + panels.find('.master-schedule-tabs-panel-'+day).addClass('active-day-panel'); + }); }" . "\n"; // --- tabbed view responsiveness --- @@ -997,185 +1137,195 @@ function radio_station_master_schedule_tabs_js() { var radio_tab_highlighting = setInterval(radio_tabs_show_highlight, 60000); }); jQuery(window).resize(function () { - radio_resize_debounce(function() {radio_tabs_responsive(false);}, 500, 'scheduletabs'); + radio_resize_debounce(function() {radio_tabs_responsive(false,false);}, 500, 'scheduletabs'); }); + /* Initialize Tabs */ function radio_tabs_initialize() { radio_tabs_clicks(); - radio_tabs_responsive(false); + radio_tabs_responsive(false,false); radio_tabs_show_highlight(); } /* Set Day Tab on Load */ - function radio_tabs_active_tab(day) { + function radio_tabs_active_tab(day,scheduleid) { if (radio_tabs_init) {return;} - if (!jQuery('.master-schedule-tabs-headings').length) {return;} - jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); - jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); + if (!jQuery('.master-schedule-tabs').length) {return;} + jQuery(this).find('.master-schedule-tabs-day').removeClass('active-day-tab'); + jQuery(this).find('.master-schedule-tabs-panel').removeClass('active-day-panel'); if (!day) { - jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); - jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); + jQuery('#'+scheduleid).find('.master-schedule-tabs-day').first().addClass('active-day-tab'); + jQuery('#'+scheduleid).find('.master-schedule-tabs-panel').first().addClass('active-day-panel'); } else { - jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); - jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); + jQuery('#'+scheduleid).find('.master-schedule-tabs-day-'+day).addClass('active-day-tab'); + jQuery('#'+scheduleid).find('.master-schedule-tabs-panel-'+day).addClass('active-day-panel'); } radio_tabs_init = true; } /* Current Show Highlighting */ function radio_tabs_show_highlight() { - if (!jQuery('.master-schedule-tabs-headings').length) {return;} - radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); - radio.offset_time = radio.current_time + radio.timezone.offset; - if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} - if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} - jQuery('.master-schedule-tabs-day').each(function() { - start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - jQuery(this).addClass('current-day'); - day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - radio_tabs_active_tab(day); - } else {jQuery(this).removeClass('current-day');} - }); - radio_tabs_active_tab(false); /* fallback */ - var radio_tabs_split = false; - jQuery('.master-schedule-tabs-show').each(function() { - start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (radio.debug) {console.log(start+' - '+end);} - if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - if (radio.debug) {console.log('^ Now Playing ^');} - jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); - /* also highlight split shift via matching shift class */ - if (jQuery(this).hasClass('overnight')) { - classes = jQuery(this).attr('class').split(/\s+/); - for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_tabs_split = classes[i];} + if (!jQuery('.master-schedule-tabs').length) {return;} + jQuery('.master-schedule-tabs').each(function() { + scheduleid = jQuery(this).attr('id'); + radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); + radio.offset_time = radio.current_time + radio.timezone.offset; + if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} + if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + jQuery(this).find('.master-schedule-tabs-day').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if ((start < radio.offset_time) && (end > radio.offset_time)) { + jQuery(this).addClass('current-day'); + day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); + radio_tabs_active_tab(day,scheduleid); + } else {jQuery(this).removeClass('current-day');} + }); + radio_tabs_active_tab(false,scheduleid); /* fallback */ + var radio_tabs_split = false; + jQuery(this).find('.master-schedule-tabs-show').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (radio.debug) {console.log(start+' - '+end);} + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + if (radio.debug) {console.log('^ Now Playing ^');} + jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_tabs_split = classes[i];} + } } + } else { + jQuery(this).removeClass('nowplaying'); + if (start > radio.offset_time) {jQuery(this).addClass('after-current');} + else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} } - } else { - jQuery(this).removeClass('nowplaying'); - if (start > radio.offset_time) {jQuery(this).addClass('after-current');} - else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} + }); + if (radio_tabs_split) { + jQuery(this).find('.'+radio_tabs_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } }); - if (radio_tabs_split) { - jQuery('.'+radio_tabs_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); - } } /* Make Tabs Responsive */ - function radio_tabs_responsive(leftright) { - if (!jQuery('.master-schedule-tabs-headings').length) {return;} - - fallback = -1; selected = -1; foundtab = false; - if (!leftright || (leftright == 'left')) { - if (jQuery('.master-schedule-tabs-day.first-tab').length) { - start = jQuery('.master-schedule-tabs-day.first-tab'); - } else {start = jQuery('.master-schedule-tabs-day').first(); fallback = 0;} - classes = start.attr('class').split(' '); - } else if (leftright == 'right') { - if (jQuery('.master-schedule-tabs-day.last-tab').length) { - end = jQuery('.master-schedule-tabs-day.last-tab'); - } else {end = jQuery('.master-schedule-tabs-day').last(); fallback = 6;} - classes = end.attr('class').split(' '); - } - for (i = 0; i < classes.length; i++) { - if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} - } - if (selected < 0) {selected = fallback;} - if (radio.debug) {console.log('Current Tab: '+selected);} - - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - if (!jQuery('.master-schedule-tabs-day.day-'+selected).length) { - while (!foundtab) { - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (jQuery('.master-schedule-tabs-day.day-'+selected).length) {foundtab = true;} - if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} + function radio_tabs_responsive(leftright,instance) { + if (!jQuery('.master-schedule-tabs').length) {return;} + if (instance) { + if (0 == instance) {id = '';} else {id = '-'+instance;} + tabschedules = jQuery('#master-schedule-tabs'+id); + } else {tabschedules = jQuery('.master-schedule-tabs');} + + tabschedules.each(function() { + fallback = -1; selected = -1; foundtab = false; + if (!leftright || (leftright == 'left')) { + if (jQuery(this).find('.master-schedule-tabs-day.first-tab').length) { + start = jQuery(this).find('.master-schedule-tabs-day.first-tab'); + } else {start = jQuery(this).find('.master-schedule-tabs-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery(this).find('.master-schedule-tabs-day.last-tab').length) { + end = jQuery(this).find('.master-schedule-tabs-day.last-tab'); + } else {end = jQuery(this).find('.master-schedule-tabs-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); } - } - if (radio.debug) {console.log('Selected Tab: '+selected);} - - jQuery('#master-schedule-tabs').css('width','100%'); - tabswidth = jQuery('#master-schedule-tabs').width(); - jQuery('#master-schedule-tabs').css('width','auto'); - jQuery('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); - - totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; - if (jQuery('#master-schedule-tabs-loader-left').length) {totalwidth = totalwidth + jQuery('#master-schedule-tabs-loader-left').width();} - if (jQuery('#master-schedule-tabs-loader-right').length) {totalwidth = totalwidth + jQuery('#master-schedule-tabs-loader-right').width();} - - for (i = selected; i < 7; i++) { - if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { - if ((i > 0) && (i == selected)) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} - else if (i < 6) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} - tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).show().width(); - mleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); - mright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); - totalwidth = totalwidth + tabwidth + mleft + mright; - if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} - if (totalwidth > tabswidth) { - if (radio.debug) {console.log('Hiding Tab '+i);} - jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; - } else { - jQuery('.master-schedule-tabs-day.day-'+i).removeClass('last-tab'); - totalwidth = totalwidth - tabwidth + jQuery('.master-schedule-tabs-day.day-'+i).width(); - if (radio.debug) {console.log('Showing Tab '+i);} - lasttab = i; tabs++; + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} + } + if (selected < 0) {selected = fallback;} + if (radio.debug) {console.log('Current Tab: '+selected);} + + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} + if (!jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) { + while (!foundtab) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) {foundtab = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} } } - } - if (lasttab < 6) {jQuery('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} - - if (leftright == 'right') { - for (i = (selected - 1); i > -1; i--) { - if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { - tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).show().width(); - mleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); - mright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); + if (radio.debug) {console.log('Selected Tab: '+selected);} + + jQuery(this).css('width','100%'); + tabswidth = jQuery(this).width(); + jQuery(this).css('width','auto'); + jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); + + totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; + if (jQuery(this).find('.master-schedule-tabs-loader-left').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-left').width();} + if (jQuery(this).find('.master-schedule-tabs-loader-right').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-right').width();} + + for (i = selected; i < 7; i++) { + if (!endtabs && (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} + else if (i < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} + tabwidth = jQuery(this).find('.master-schedule-tabs-day.day-'+i).show().width(); + mleft = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); + mright = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); totalwidth = totalwidth + tabwidth + mleft + mright; if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} if (totalwidth > tabswidth) { if (radio.debug) {console.log('Hiding Tab '+i);} - jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; + jQuery(this).find('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; } else { - jQuery('.master-schedule-tabs-day').removeClass('first-tab'); - jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); + jQuery(this).find('.master-schedule-tabs-day.day-'+i).removeClass('last-tab'); + totalwidth = totalwidth - tabwidth + jQuery(this).find('.master-schedule-tabs-day.day-'+i).width(); if (radio.debug) {console.log('Showing Tab '+i);} - tabs++; + lasttab = i; tabs++; } } } - } - jQuery('#master-schedule-tabs').css('width','100%'); + if (lasttab < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} + + if (leftright == 'right') { + for (i = (selected - 1); i > -1; i--) { + if (!endtabs && (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length)) { + tabwidth = jQuery(this).find('.master-schedule-tabs-day.day-'+i).show().width(); + mleft = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); + mright = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); + totalwidth = totalwidth + tabwidth + mleft + mright; + if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} + if (totalwidth > tabswidth) { + if (radio.debug) {console.log('Hiding Tab '+i);} + jQuery(this).find('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; + } else { + jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab'); + jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); + if (radio.debug) {console.log('Showing Tab '+i);} + tabs++; + } + } + } + } + jQuery(this).css('width','100%'); - /* display selected day message if outside view */ - activeday = false; - for (i = 0; i < 7; i++) { - if (jQuery('.master-schedule-tabs-day.day-'+i).length) { - if (jQuery('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) {activeday = i;} + /* display selected day message if outside view */ + activeday = false; + for (i = 0; i < 7; i++) { + if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length) { + if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) {activeday = i;} + } + } + jQuery(this).find('.master-schedule-tabs-selected').hide(); + if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { + jQuery(this).find('.master-schedule-tabs-selected-'+activeday).show(); } - } - jQuery('.master-schedule-tabs-selected').hide(); - if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { - jQuery('#master-schedule-tabs-selected-'+activeday).show(); - } - if (radio.debug) { - console.log('Active Day: '+activeday); - console.log('Selected: '+selected); - console.log('Fallback: '+fallback); - console.log('First Tab: '+firsttab); - console.log('Last Tab: '+lasttab); - console.log('Visible Tabs: '+tabs); - } + if (radio.debug) { + console.log('Active Day: '+activeday); + console.log('Selected: '+selected); + console.log('Fallback: '+fallback); + console.log('First Tab: '+firsttab); + console.log('Last Tab: '+lasttab); + console.log('Visible Tabs: '+tabs); + } + }); } /* Shift Day Left / Right */ - function radio_shift_tab(leftright) { - radio_tabs_responsive(leftright); return false; + function radio_shift_tab(leftright,instance) { + radio_tabs_responsive(leftright,instance); return false; }" . "\n"; // --- filter and return --- @@ -1188,6 +1338,7 @@ function radio_shift_tab(leftright) { // List View Javascript // -------------------- // 2.3.2: added for list schedule view +// TODO: use traversals with instance IDs function radio_station_master_schedule_list_js() { // --- list view javascript --- @@ -1200,6 +1351,7 @@ function radio_station_master_schedule_list_js() { radio_list_highlight(); var radio_list_highlighting = setInterval(radio_list_highlight, 60000); }); + /* Current Show Highlighting */ function radio_list_highlight() { if (!jQuery('.master-list-day').length) {return;} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 781df86..f1a5044 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -928,7 +928,7 @@ function radio_station_shifts_list_styles() { cursor: pointer; display:block; width: 150px; padding: 8px; text-align: center; line-height: 1em;} .shift-duplicate, .shift-remove {cursor: pointer;} #shifts-saving-message, #shifts-saved-message { - background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}" . PHP_EOL; + background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}" . "\n"; // 2.3.3.9: added shift edit styles filter $css = apply_filters( 'radio_station_shift_list_edit_styles', $css ); @@ -947,7 +947,9 @@ function radio_station_shift_edit_script() { // --- set days, hours and minutes arrays --- // 2.5.0: add number_format_i18n to hours and minutes - $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + // 2.5.0: use get_schedule_weekdays to honour start of week value + // $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $days = array_merge( array( '' ), radio_station_get_schedule_weekdays() ); $hours = $mins = array(); for ( $i = 1; $i < 13; $i++ ) { $hours[$i] = number_format_i18n( $i ); @@ -955,6 +957,8 @@ function radio_station_shift_edit_script() { for ( $i = 0; $i < 60; $i++ ) { if ( $i < 10 ) { $min = number_format_i18n( 0 ) . number_format_i18n( $i ); + // 2.5.0: make sure values have leading 0 + $i = '0' . $i; } else { $min = number_format_i18n( $i ); } @@ -1066,17 +1070,9 @@ function radio_station_shift_edit_script() { // --- add new shift --- // 2.3.2: separate function for onclick + // 2.5.0: shorten value object $js .= "function radio_shift_new() { - values = {}; - values.day = ''; - values.start_hour = ''; - values.start_min = ''; - values.start_meridian = ''; - values.end_hour = ''; - values.end_min = ''; - values.end_meridian = ''; - values.encore = ''; - values.disabled = ''; + values = {day:'', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', encore:'', disabled:''}; radio_shift_add(values); }" . "\n"; @@ -1262,7 +1258,9 @@ function radio_station_show_shifts_table( $post_id ) { // --- set days, hours and minutes arrays --- // 2.5.0: add number_format_i18n to hours and minutes - $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + // 2.5.0: use get_schedule_weekdays to honour start of week value + // $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $days = array_merge( array( '' ), radio_station_get_schedule_weekdays() ); $hours = $mins = array(); for ( $i = 1; $i < 13; $i++ ) { $hours[$i] = number_format_i18n( $i ); @@ -1270,6 +1268,8 @@ function radio_station_show_shifts_table( $post_id ) { for ( $i = 0; $i < 60; $i++ ) { if ( $i < 10 ) { $min = number_format_i18n( 0 ) . number_format_i18n( $i ); + // 2.5.0: make sure values have leading 0 + $i = '0' . $i; } else { $min = number_format_i18n( $i ); } @@ -1672,10 +1672,12 @@ function radio_station_show_helper_box() { // echo '
    ' . esc_html( 'In future, Radio Station Pro will include an Episodes post type', 'radio-station' ) ); // TODO: change this text/link when Pro Episodes become available // $upgrade_url = radio_station_get_upgrade_url(); - // echo '
    '; - // // echo esc_html( __( "Upgrade to Radio Station Pro', 'radio-station' ) ); - // echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); - // echo ' →.'; + // $pricing_url = radio_station_get_pricing_url(); + // echo '
    '; + // echo esc_html( __( 'Upgrade to Radio Station Pro.', 'radio-station' ) ); + // echo ' | '; + // echo esc_html( __( 'Find out more.', 'radio-station' ) ); + // echo ''; } @@ -2115,11 +2117,16 @@ function radio_station_show_save_data( $post_id ) { } // --- show avatar image --- - $avatar = absint( $_POST['show_avatar'] ); - if ( $avatar > 0 ) { - // $prev_avatar = get_post_meta( $post_id, 'show_avatar', true ); - // if ( $avatar != $prev_avatar ) {$show_meta_changed = true;} - update_post_meta( $post_id, 'show_avatar', $avatar ); + // 2.5.0: delete post meta if removing avatar + if ( '' == $_POST['show_avatar'] ) { + delete_post_meta( $post_id, 'show_avatar' ); + } else { + $avatar = absint( $_POST['show_avatar'] ); + if ( $avatar > 0 ) { + // $prev_avatar = get_post_meta( $post_id, 'show_avatar', true ); + // if ( $avatar != $prev_avatar ) {$show_meta_changed = true;} + update_post_meta( $post_id, 'show_avatar', $avatar ); + } } // --- add image updated flag --- @@ -2151,7 +2158,9 @@ function radio_station_show_save_data( $post_id ) { } $new_ids = array(); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + // 2.5.0: use get_schedule_weekdays to honour start of week value + // $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + $days = array_merge( array( '' ), radio_station_get_schedule_weekdays() ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $i => $shift ) { @@ -2419,8 +2428,9 @@ function radio_station_show_save_data( $post_id ) { // --- reload the current schedule view --- // 2.4.0.3: added missing check for window parent function + // 2.5.0: added extra instance argument $js .= "if (window.parent && (typeof parent.radio_load_schedule == 'function')) {" . "\n"; - $js .= " parent.radio_load_schedule(false,false,true);" . "\n"; + $js .= " parent.radio_load_schedule(false,false,false,true);" . "\n"; $js .= "}" . "\n"; // 2.3.3.6: clear changes may not have been saved window reload message @@ -2818,7 +2828,9 @@ function radio_station_show_day_filter( $post_type, $which ) { $d = isset( $_GET['weekday'] ) ? sanitize_text_field( $_GET['weekday'] ) : 0; // --- show day selector --- - $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + // 2.5.0: use get_schedule_weekdays to honour start of week value + // $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $days = radio_station_get_schedule_weekdays(); echo '' . "\n"; echo '' . PHP_EOL; + $list .= '' . "\n"; $list .= '' . "\n"; $list .= '' . "\n"; @@ -3818,25 +3842,23 @@ function radio_station_overrides_table( $post_id ) { $list .= ' checked="checked"'; } $list .= '> ' . "\n"; - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; $list .= '' . "\n"; // --- remove shift icon --- $list .= '
  • ' . "\n"; $title = __( 'Remove Override', 'radio-station' ); - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; $list .= '
  • ' . "\n"; $list .= '' . "\n"; @@ -3865,8 +3887,8 @@ function radio_station_override_edit_script() { $am = radio_station_translate_meridiem( 'am' ); $pm = radio_station_translate_meridiem( 'pm' ); - // --- set days, hours and minutes arrays --- - $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + // --- set hours and minutes arrays --- + // 2.5.0: remove days as not needed here $hours = $mins = array(); for ( $i = 1; $i < 13; $i++ ) { $hours[$i] = number_format_i18n( $i ); @@ -3874,6 +3896,8 @@ function radio_station_override_edit_script() { for ( $i = 0; $i < 60; $i++ ) { if ( $i < 10 ) { $min = number_format_i18n( 0 ) . number_format_i18n( $i ); + // 2.5.0: make sure value has leading 0 + $i = '0' . $i; } else { $min = number_format_i18n( $i ); } @@ -3909,7 +3933,15 @@ function radio_station_override_edit_script() { jQuery('#wpbody').append(frame); } /* copy override input fields and nonce */ - jQuery('#overrides-list input').each(function() {jQuery(this).clone().appendTo('#override-save-form');}); + jQuery('#overrides-list input').each(function() { + inputtype = jQuery(this).attr('type'); + if ((jQuery(this).attr('name') != '') && (inputtype != 'hidden')) { + if ((inputtype != 'checkbox') || ((inputtype == 'checkbox') && (jQuery(this).prop('checked')))) { + name = jQuery(this).attr('name'); value = jQuery(this).val(); + jQuery('').appendTo('#override-save-form'); + } + } + }); jQuery('#overrides-list select').each(function() { name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); jQuery('').appendTo('#override-save-form'); @@ -3991,18 +4023,10 @@ function radio_station_override_edit_script() { }" . PHP_EOL; */ // --- add new override --- + // 2.5.0: shorten value object $todate = date( 'Y-m-d', time() ); $js .= "function radio_override_new() { - values = {}; - values.date = '" . $todate . "'; - values.start_hour = ''; - values.start_min = ''; - values.start_meridian = ''; - values.end_hour = ''; - values.end_min = ''; - values.end_meridian = ''; - values.multiday = ''; - values.end_date = ''; + values = {date:'" . esc_js( $todate ) . "', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', multiday:'', end_date:'', disabled:''}; radio_override_add(values); }" . "\n"; @@ -4017,6 +4041,7 @@ function radio_station_override_edit_script() { // --- duplicate shift --- $js .= "function radio_override_duplicate(el) { overrideid = el.id.replace('override-','').replace('-duplicate',''); + console.log('Override ID: '+overrideid); values = {}; values.date = jQuery('#override-'+overrideid+'-date').val(); values.start_hour = jQuery('#override-'+overrideid+'-start-hour').val(); @@ -4038,15 +4063,17 @@ function radio_station_override_edit_script() { $js .= "var count = jQuery('#new-overrides').children().length + 1;" . "\n"; $js .= "output = '
    ';" . "\n"; $js .= "output += '
      ';" . "\n"; + + // --- start date --- $js .= "output += '
    • ';" . "\n"; $js .= "output += '" . esc_js( __( 'Start Date', 'radio-station' ) ) . ": ';" . "\n"; - $js .= "output += '';" . "\n"; + $js .= "output += '';" . "\n"; $js .= "output += '
    • ';" . "\n"; // --- start hour --- $js .= "output += '
    • ';" . "\n"; $js .= "output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": ';" . "\n"; - $js .= "output += '';" . "\n"; // 2.5.0: use possibly translated hour label foreach ( $hours as $hour => $label ) { $js .= "output += '
    • ';" . "\n"; @@ -4081,7 +4109,7 @@ function radio_station_override_edit_script() { $js .= "output += '
    • ';" . "\n"; $js .= "output += '" . esc_js( __( 'End Time', 'radio-station' ) ) . ": ';" . "\n"; // --- end hour --- - $js .= "output += '';" . "\n"; // 2.5.0: use possibly translated hour label foreach ( $hours as $hour => $label ) { $js .= "output += '
    • ';" . "\n"; // --- multiday switch --- /* $js .= "output += '
    • ';" . "\n"; - $js .= "output += '';" . "\n"; $js .= "output += '" . esc_js( __( 'End Date', 'radio-station' ) ) . ": ';" . "\n"; - $js .= "output += '';" . "\n"; + $js .= "output += '';" . "\n"; $js .= "if (values.end_date != '') {output += ' value=\"' + values.end_date+ '\"';}" . "\n"; $js .= "output += '>';" . "\n"; $js .= "output += '
    • ';" . "\n"; */ // --- disable override --- $js .= "output += '
    • ';" . "\n"; - $js .= "output += ' " . esc_js( __( 'Disabled', 'radio-station' ) ) . "';" . "\n"; $js .= "output += '
    • ';" . "\n"; // --- duplicate override time --- $js .= "output += '
    • ';" . "\n"; - $js .= "output += '';" . "\n"; + $js .= "output += '';" . "\n"; $js .= "output += '
    • ';" . "\n"; // --- remove override time --- $js .= "output += '
    • ';" . "\n"; - $js .= "output += '';" . "\n"; + $js .= "output += '';" . "\n"; $js .= "output += '
    • ';" . "\n"; $js .= "output += '
    ';" . "\n"; @@ -4149,8 +4178,8 @@ function radio_station_override_edit_script() { // --- append new override list item --- $js .= "jQuery('#new-overrides').append(output);" . "\n"; - $js .= "jQuery('#override-new' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; - $js .= "jQuery('#override-new' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= "jQuery('#override-new-' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + // $js .= "jQuery('#override-new-' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; $js .= "return false;" . "\n"; $js .= "}" . "\n"; @@ -4357,9 +4386,9 @@ function radio_station_override_save_data( $post_id ) { 'end_hour' => '', 'end_min' => '', 'end_meridian' => '', - 'disabled' => '', // 'multiday' => '', // 'end_date' => '', + 'disabled' => '', ); // --- sanitize values before saving --- @@ -4391,9 +4420,10 @@ function radio_station_override_save_data( $post_id ) { } } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { // 2.2.3: fix to validate 00 minute value - if ( empty( $value ) ) { + // 2.5.0: refix to validate 00 minute value + if ( empty( $value ) || ( '00' == $value ) ) { $isvalid = true; - } elseif ( absint( $value ) > - 1 && absint( $value ) < 61 ) { + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { $isvalid = true; } } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { @@ -4402,7 +4432,8 @@ function radio_station_override_save_data( $post_id ) { if ( in_array( $value, $valid ) ) { $isvalid = true; } - } elseif ( ( 'disabled' === $key ) || ( 'multiday' == $key ) ) { + } elseif ( ( 'disabled' == $key ) || ( 'multiday' == $key ) ) { + // note: not set again if already empty if ( 'yes' == $value ) { $isvalid = true; } @@ -4420,7 +4451,8 @@ function radio_station_override_save_data( $post_id ) { } // 2.3.3.9: add unique override shift ID - if ( !isset( $new_sched['id'] ) ) { + // 2.5.0: fix to check if empty instead of isset + if ( '' == $new_sched['id'] ) { $new_sched['id'] = radio_station_unique_shift_id(); } @@ -4542,6 +4574,7 @@ function radio_station_override_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { // 2.3.3.9: remove duplicate action check + sanitize_text_field( $_REQUEST['action'] ); // --- (hidden) debug information --- echo "Previous Overrides: " . esc_html( print_r( $current_scheds, true ) ) . "\n"; @@ -4550,13 +4583,14 @@ function radio_station_override_save_data( $post_id ) { // --- display shifts saved message --- // 2.3.3.9: fade out overrides saved message $show_override_nonce = wp_create_nonce( 'radio-station' ); - echo "" . "\n"; // 2.3.3.9: added check if override schedule changed @@ -4584,8 +4618,9 @@ function radio_station_override_save_data( $post_id ) { // --- reload the current schedule view --- // 2.4.0.3: added missing check for window parent function + // 2.5.0: added extra instance argument $js .= "if (window.parent && (typeof parent.radio_load_schedule == 'function')) {" . "\n"; - $js .= "parent.radio_load_schedule(false,false,true);" . "\n"; + $js .= "parent.radio_load_schedule(false,false,false,true);" . "\n"; $js .= "}" . PHP_EOL; // $js .= "if (parent.window.onbeforeunloadset) { @@ -4609,16 +4644,16 @@ function radio_station_override_save_data( $post_id ) { echo ''; // 2.3.3.9: trigger action for save or add override time - if ( 'radio_station_override_save' == $_REQUEST['action'] ) { + if ( 'radio_station_override_save' == $action ) { do_action( 'radio_station_override_save_time', $shift_id ); - } elseif ( 'radio_station_add_override_time' == $_REQUEST['action'] ) { + } elseif ( 'radio_station_add_override_time' == $action ) { do_action( 'radio_station_override_add_time' ); } } // --- return early when adding single override --- // 2.3.3.9: added for single override action - if ( 'radio_station_add_override_time' == sanitize_text_field( $_REQUEST['action'] ) ) { + if ( 'radio_station_add_override_time' == $action ) { return; } @@ -5299,10 +5334,12 @@ function radio_station_playlist_metabox() { // 2.3.2: added track save message styling // 2.3.3.9: margin top fixes for move arrows // 2.3.3.9: added track time styling + // 2.5.0: increase width for track seconds $css = '.track-meta div {display: inline-block; margin-right: 3px;} .track-time, .track-comments {display: inline-block;} .track-comments {margin-left: 30px;} - .track-minutes, .track-seconds {width: 30px;} + .track-minutes {width: 30px;} + .track-seconds {width: 50px;} .track-meta select, .track-time select, .track-time input {font-size: 12px;} .track-move {margin-top: -5px;} .track-arrow-up, .track-arrow-down {font-size: 36px; line-height: 24px; cursor: pointer; margin-top: -10px;} @@ -5645,8 +5682,7 @@ function radio_station_playlist_save_data( $post_id ) { if ( isset( $_POST['playlist_tracks_nonce'] ) && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $prev_playlist = get_post_meta( $post_id, 'playlist', true ); - // 2.5.0: use sanitize_text_field on posted value - $playlist = isset( $_POST['playlist'] ) ? sanitize_text_field( $_POST['playlist'] ) : array(); + $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); if ( count( $playlist ) > 0 ) { // move songs that are still queued to the end of the list so that order is maintained @@ -5721,8 +5757,8 @@ function radio_station_playlist_save_data( $post_id ) { echo "parent.document.getElementById('tracks-saved-message').style.display = '';" . "\n"; echo "if (typeof parent.jQuery == 'function') {parent.jQuery('#tracks-saved-message').fadeOut(3000);}" . "\n"; echo "else {setTimeout(function() {parent.document.getElementById('tracks-saved-message').style.display = 'none';}, 3000);}" . "\n"; - echo "form = parent.document.getElementById('track-save-form');" . "\n"; - echo "if (form) {form.parentNode.removeChild(form);}" . "\n"; + // echo "form = parent.document.getElementById('track-save-form');" . "\n"; + // echo "if (form) {form.parentNode.removeChild(form);}" . "\n"; echo "parent.document.getElementById('playlist_tracks_nonce').value = '" . esc_js( $playlist_tracks_nonce ) . "';" . "\n"; echo "" . "\n"; @@ -5732,6 +5768,8 @@ function radio_station_playlist_save_data( $post_id ) { echo radio_station_playlist_track_table( $post_id ); echo ""; + + print_r( $playlist ); } exit; diff --git a/includes/schedules.php b/includes/schedules.php index 8ad8ab5..a77b3af 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -189,6 +189,9 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false } } + // 2.5.0: added filter for override shifts + $override_shifts = apply_filters( 'radio_station_override_shifts', $override_shifts, $override, $start_date, $end_date, $timezone ); + // --- set override shifts --- $data['shifts'] = $override_shifts; @@ -205,7 +208,7 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false // --- get all overrides list --- $overrides = $rs_se->process_overrides( $overrides, $start_date, $end_date, $timezone ); // 2.5.0: keep filter for backwards compatibility - $overrides = apply_filters( 'radio_station_get_overrides', $overrides, $start_date, $end_date, $channel ); + $overrides = apply_filters( 'radio_station_get_overrides', $overrides, $start_date, $end_date, $timezone ); return $overrides; } @@ -588,8 +591,12 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = $next_show = $next_shows[0]; $next_show = apply_filters( 'radio_station_next_show', $next_show, $time, $channel ); - // TODO: handle split shifts over midnight ? + $shift_start_time = radio_station_to_time( $next_show['date'] . ' ' . $next_show['start'], $timezone ); $shift_end_time = radio_station_to_time( $next_show['date'] . ' ' . $next_show['end'], $timezone ); + // 2.5.0: handle split shifts over midnight + if ( $shift_end_time < $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } $expires = $shift_end_time - $now - 1; // 2.5.0: call next shift action to set transient data diff --git a/includes/shortcodes.php b/includes/shortcodes.php index f9c6ffe..60540c6 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -36,8 +36,6 @@ // - Show Playlist Shortcode - - // ----------------------- // === Time Shortcodes === // ----------------------- @@ -257,13 +255,6 @@ function radio_station_clock_shortcode( $atts = array() ) { $clock .= '
    ' . "\n"; $clock .= '
    ' . "\n"; - // 2.3.2 allow for timezone selector test - // $select = radio_station_timezone_select( 'radio-station-clock-' + $instance ); - // $select = apply_filters( 'radio_station_clock_timezone_select', '', 'radio-station-clock-' . $instance, $atts ); - // if ( '' != $select ) { - // $clock .= $select; - // } - $clock .= '
    ' . "\n"; // --- enqueue shortcode styles --- @@ -367,6 +358,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { 'thumbnails' => 0, // --- for playlists --- // 'track_count' => 0, + // 'display_tracks' => 0, // --- specific posts --- 'show' => false, 'override' => false, @@ -379,7 +371,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { $page = absint( get_query_var( 'page' ) ); if ( $page > -1 ) { $atts['offset'] = (int) $atts['perpage'] * $page; @@ -655,7 +647,8 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $classes[] = $atts['view']; } $class_list = implode( ' ', $classes ); - $list = '
    '; + // 2.5.0: add element ID for archive shortcode instance + $list = '
    '; if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { // --- no shows messages ---- @@ -757,6 +750,9 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $info['title'] .= '' . "\n"; $info['title'] .= '
    ' . "\n"; + // 2.5.0: added meta div open wrapper + $info['meta'] = '
    ' . "\n"; + // --- display Override date(s) --- if ( ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) && ( $atts['show_dates'] ) ) { @@ -768,7 +764,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // 2.3.1: fix to append not echo override date to archive list - $info['meta'] = '
    ' . "\n"; + $info['meta'] .= '
    ' . "\n"; foreach ( $override_times as $override_time ) { @@ -816,24 +812,29 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // TODO: display Show shifts meta - // if ( RADIO_STATION_SHOW_SLUG == $post_type ) { - // if ( $atts['show_shifts'] ) { - // $shifts = radio_station_get_show_schedule( $post_id ); - // } - // $info['meta'] = ''; - // } + /* if ( ( RADIO_STATION_SHOW_SLUG == $post_type ) && ( $atts['show_shifts'] ) ) { + $shifts = radio_station_get_show_schedule( $post_id ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $i => $shift ) { + $info['meta'] .= ''; + } + } + } */ // TODO: playlist tracks / track count meta - // if ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) { - // $tracks = get_post_meta( $post_id, 'playlist', true ); - // $track_count = count( $tracks ); - // $info['meta'] = ''; - // } + /* if ( ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) && $atts['display_tracks'] ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + if ( $tracks && is_array( $tracks ) && ( count( $tracks ) > 0 ) ) { + $track_count = count( $tracks ); + foreach ( $tracks as $i => $track ) { + $info['meta'] = ''; + } + } + } */ // 2.3.3.9: filter meta display for different post types - if ( !isset( $info['meta'] ) ) { - $info['meta'] = ''; - } + // 2.5.0: added meta div close wrapper + $info['meta'] .= '
    '; $info['meta'] = apply_filters ( 'radio_station_archive_shortcode_meta', $info['meta'], $post_id, $post_type, $atts ); // TODO: display genre and language terms ? @@ -891,7 +892,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- add archive_pagination --- if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { if ( $post_count > $atts['perpage'] ) { - $list .= radio_station_archive_pagination( $post_type, $atts, $post_count ); + $list .= radio_station_archive_pagination( $instance, $post_type, $atts, $post_count ); } } @@ -964,7 +965,7 @@ function radio_station_genre_archive_list( $atts ) { 'pagination' => 1, // --- query args --- 'status' => 'publish', - 'perpage' => - 1, + 'perpage' => -1, 'offset' => 0, 'orderby' => 'title', 'order' => 'ASC', @@ -977,9 +978,9 @@ function radio_station_genre_archive_list( $atts ) { ); // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { $page = absint( get_query_var( 'page' ) ); - if ( $page > - 1 ) { + if ( $page > -1 ) { $atts['offset'] = (int) $atts['perpage'] * $page; } } @@ -1011,7 +1012,7 @@ function radio_station_genre_archive_list( $atts ) { if ( $atts['hide_empty'] ) { return ''; } else { - $list = '
    ' . "\n"; + $list = '
    ' . "\n"; $list .= esc_html( __( 'No Genres were found to display.', 'radio-station' ) ) . "\n"; $list .= '
    ' . "\n"; return $list; @@ -1019,17 +1020,20 @@ function radio_station_genre_archive_list( $atts ) { } // 2.5.0: added id attribute with instance - $list = '
    ' . "\n"; + $list = '
    ' . "\n"; // --- loop genres --- + // 2.5.0: track all post count + $all_post_count = 0; foreach ( $genres as $name => $genre ) { // --- get published shows --- // TODO: also display Overrides in Genre archive list ? $args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, - 'numberposts' => $atts['perpage'], - 'offset' => $atts['offset'], + // 'numberposts' => $atts['perpage'], + // 'offset' => $atts['offset'], + 'numberposts' => -1, 'orderby' => $atts['orderby'], 'order' => $atts['order'], 'post_status' => $atts['status'], @@ -1080,7 +1084,13 @@ function radio_station_genre_archive_list( $atts ) { $list .= '
    ' . "\n"; - $has_posts = ( $posts || ( count( $posts ) > 0 ) ) ? true : false; + if ( $posts || ( count( $posts ) > 0 ) ) { + $has_posts = true; + // 2.5.0: add to all post count + $all_post_count = $all_post_count + count( $posts ); + } else { + $has_posts = false; + } if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { // --- genre image --- @@ -1205,13 +1215,15 @@ function radio_station_genre_archive_list( $atts ) { $list .= '
    ' . "\n"; // --- add archive_pagination --- - // TODO: add genre archive list pagination - // if ( $atts['pagination'] ) { - // $list .= radio_station_archive_pagination( 'genre', $atts, $post_count ); - // } + // 2.5.0: enable genre archive list pagination + if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { + if ( $post_count > $atts['perpage'] ) { + $list .= radio_station_archive_pagination( $instance, 'genre', $atts, $post_count ); + } + } // --- enqueue pagination javascript --- - // add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); + add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -1248,7 +1260,7 @@ function radio_station_language_archive_list( $atts ) { 'pagination' => 1, // --- query args --- 'status' => 'publish', - 'perpage' => - 1, + 'perpage' => -1, 'offset' => 0, 'orderby' => 'title', 'order' => 'ASC', @@ -1261,9 +1273,9 @@ function radio_station_language_archive_list( $atts ) { ); // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { $page = absint( get_query_var( 'page' ) ); - if ( $page > - 1 ) { + if ( $page > -1 ) { $atts['offset'] = (int) $atts['perpage'] * $page; } } @@ -1295,7 +1307,7 @@ function radio_station_language_archive_list( $atts ) { if ( $atts['hide_empty'] ) { return ''; } else { - $list = '
    ' . "\n"; + $list = '
    ' . "\n"; $list .= esc_html( __( 'No Languages were found to display.', 'radio-station' ) ) . "\n"; $list .= '
    ' . "\n"; return $list; @@ -1306,14 +1318,17 @@ function radio_station_language_archive_list( $atts ) { $list = '
    ' . "\n"; // --- loop languages --- + // 2.5.0: track all post count + $all_post_count = 0; foreach ( $languages as $name => $language ) { // --- get published shows --- // TODO: also display Overrides in archive list ? $args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, - 'numberposts' => $atts['perpage'], - 'offset' => $atts['offset'], + // 'numberposts' => $atts['perpage'], + // 'offset' => $atts['offset'], + 'numberposts' => -1, 'orderby' => $atts['orderby'], 'order' => $atts['order'], 'post_status' => $atts['status'], @@ -1365,6 +1380,8 @@ function radio_station_language_archive_list( $atts ) { if ( $posts || ( count( $posts ) > 0 ) ) { $has_posts = true; + // 2.5.0: add to all post count + $all_post_count = $all_post_count + count( $posts ); } else { $has_posts = false; } @@ -1476,13 +1493,15 @@ function radio_station_language_archive_list( $atts ) { $list .= '
    ' . "\n"; // --- add archive_pagination --- - // TODO: add language archive list pagination - // if ( $atts['pagination'] ) { - // $list .= radio_station_archive_pagination( 'language', $atts, $post_count ); - // } + // 2.5.0: enable language archive list pagination + if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { + if ( $post_count > $atts['perpage'] ) { + $list .= radio_station_archive_pagination( $instance, 'language', $atts, $post_count ); + } + } // --- enqueue pagination javascript --- - // add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); + add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -1495,35 +1514,40 @@ function radio_station_language_archive_list( $atts ) { // ------------------ // Archive Pagination // ------------------ -function radio_station_archive_pagination( $type, $atts, $post_count ) { +function radio_station_archive_pagination( $instance, $type, $atts, $post_count ) { global $post; $permalink = get_permalink( $post->ID ); $post_pages = ceil( $post_count / $atts['perpage'] ); - $current_page = ( $atts['offset'] > 0 ) ? $atts['offset'] / $atts['perpage'] : 0; + $current_page = ( $atts['offset'] > 0 ) ? ( $atts['offset'] / $atts['perpage'] ) : 0; $prev_page = $current_page - 1; $next_page = $current_page + 1; $pagi = '

    '; $pagi .= '
    ' . "\n"; - $url = add_query_arg( 'page', $prev_page, $permalink ); $pagi .= '
    ' . "\n"; if ( $prev_page > 0 ) { - $pagi .= '' . "\n"; + $url = add_query_arg( 'page', $prev_page, $permalink ); + $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $prev_page ) . "');"; + $pagi .= '' . "\n"; } $pagi .= '
    ' . "\n"; - for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { - $active = ( $current_page == $pagenum ) ? ' active' : ''; + for ( $page_num = 1; $page_num < ( $post_pages + 1 ); $page_num++ ) { + $active = ( $current_page == $page_num ) ? ' active' : ''; $pagi .= '' . "\n"; } - $url = add_query_arg( 'page', $next_page, $permalink ); $pagi .= '
    ' . "\n"; - $pagi .= '' . "\n"; + if ( $next_page < ( $post_pages + 1 ) ) { + $url = add_query_arg( 'page', $next_page, $permalink ); + $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $next_page ) . "');"; + $pagi .= '' . "\n"; + } $pagi .= '
    ' . "\n"; $pagi .= '
    ' . "\n"; @@ -1536,11 +1560,9 @@ function radio_station_archive_pagination( $type, $atts, $post_count ) { // 2.3.3.9: renamed function to distinguish from list pagination function radio_station_archive_pagination_javascript() { - // TEMP - return; - // --- fade out current page and fade in selected page --- $js = "function radio_archive_page(id, types, pagenum) { + return; /* TEMP */ currentpage = document.getElementById('archive-'+id+'-'+types+'-current-page').value; if (pagenum == 'next') { pagenum = parseInt(currentpage) + 1; @@ -1551,21 +1573,9 @@ function radio_station_archive_pagination_javascript() { pagenum = parseInt(currentpage) - 1; if (pagenum < 1) {return;} } - if (typeof jQuery == 'function') { - jQuery('.archive-'+id+'-'+types+'-page').fadeOut(500); - jQuery('#archive-'+id+'-'+types+'-page-'+pagenum).fadeIn(1000); - jQuery('.archive-'+id+'-'+types+'-page-button').removeClass('active'); - jQuery('#archive-'+id+'-'+types+'-page-button-'+pagenum).addClass('active'); - jQuery('#archive-'+id+'-'+types+'-current-page').val(pagenum); - } else { - pages = document.getElementsByClassName('archive-'+id+'-'+types+'-page'); - for (i = 0; i < pages.length; i++) {pages[i].style.display = 'none';} - document.getElementById('archive-'+id+'-'+types+'-page-'+pagenum).style.display = ''; - buttons = document.getElementsByClassName('archive-'+id+'-'+types+'-page-button'); - for (i = 0; i < buttons.length; i++) {buttons[i].classList.remove('active');} - document.getElementById('archive-'+id+'-'+types+'-page-button-'+pagenum).classList.add('active'); - document.getElementById('archive-'+id+'-'+types+'-current-page').value = pagenum; - } + + + return false; }"; // --- enqueue script inline --- @@ -3446,27 +3456,29 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute // 2.5.0: added no_playlist text attribute + // 2.5.0: added playlist_title switch attribute $defaults = array( // --- widget display options --- - 'title' => '', - 'ajax' => $ajax, - 'dynamic' => $dynamic, - 'hide_empty' => 0, + 'title' => '', + 'ajax' => $ajax, + 'dynamic' => $dynamic, + 'hide_empty' => 0, // --- playlist display options --- - 'link' => 1, - 'no_playlist' => '', - 'countdown' => 0, + 'playlist_title' => 0, + 'link' => 1, + 'no_playlist' => '', + 'countdown' => 0, // --- track display options --- - 'song' => 1, - 'artist' => 1, - 'album' => 0, - 'label' => 0, - 'comments' => 0, + 'song' => 1, + 'artist' => 1, + 'album' => 0, + 'label' => 0, + 'comments' => 0, // --- widget data --- - 'widget' => 0, - 'block' => 0, - 'id' => '', - 'for_time' => 0, + 'widget' => 0, + 'block' => 0, + 'id' => '', + 'for_time' => 0, ); // 2.3.0: renamed shortcode identifier to current-playlist @@ -3710,37 +3722,37 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.2.4: check value keys are set before outputting if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { $tracks .= '
    ' . "\n"; - $tracks .= esc_html( __( 'Song', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ) . "\n"; + $tracks .= '' . esc_html( __( 'Song', 'radio-station' ) ) . ''; + $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ) . '' . "\n"; $tracks .= '
    ' . "\n"; } // 2.2.7: add label prefixes to now playing data if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { $tracks .= '
    ' . "\n"; - $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ) . "\n"; + $tracks .= '' . esc_html( __( 'Artist', 'radio-station' ) ) . ''; + $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ) . '' . "\n"; $tracks .= '
    ' . "\n"; } if ( $atts['album'] && !empty( $track['playlist_entry_album'] ) ) { $tracks .= '
    ' . "\n"; - $tracks .= esc_html( __( 'Album', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ) . "\n"; + $tracks .= '' . esc_html( __( 'Album', 'radio-station' ) ) . ''; + $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ) . '' . "\n"; $tracks .= '
    ' . "\n"; } if ( $atts['label'] && !empty( $track['playlist_entry_label'] ) ) { $tracks .= '
    ' . "\n"; - $tracks .= esc_html( __( 'Label', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ) . "\n"; + $tracks .= '' . esc_html( __( 'Label', 'radio-station' ) ) . ''; + $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ) . '' . "\n"; $tracks .= '
    ' . "\n"; } if ( $atts['comments'] && !empty( $track['playlist_entry_comments'] ) ) { $tracks .= '
    ' . "\n"; - $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ) . "\n"; + $tracks .= '' . esc_html( __( 'Comments', 'radio-station' ) ) . ''; + $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ) . '' . "\n"; $tracks .= '
    ' . "\n"; } @@ -3752,10 +3764,16 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.3.8 added playlist_link shortcode attribute (default 1) if ( $atts['link'] ) { $link = ''; - if ( isset( $playlist['playlist_permalink'] ) ) { + // 2.5.0: fix to playlist_permalink key + if ( isset( $playlist['playlist_url'] ) ) { $link = '' . "\n"; } @@ -3764,6 +3782,9 @@ function radio_station_current_playlist_shortcode( $atts ) { if ( ( '' != $link ) && is_string( $link ) ) { $html['link'] = $link; } + } elseif ( $atts['playlist_title'] ) { + // 2.5.0: maybe show playlist title + $html['link'] = esc_html( $playlist['title'] ); } } else { @@ -3914,15 +3935,9 @@ function radio_station_countdown_enqueue() { // --- enqueue countdown script --- radio_station_enqueue_script( 'radio-station-countdown', array( 'radio-station' ), true ); - // --- set countdown labels --- - $js = "radio.labels.showstarted = '" . esc_js( __( 'This Show has started.', 'radio-station' ) ) . "'; - radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "'; - radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "'; - radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "'; - radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "';"; - // --- add script inline --- - wp_add_inline_script( 'radio-station-countdown', $js ); + // 2.5.0: moved countdown labels to main script localization + // wp_add_inline_script( 'radio-station-countdown', $js ); // 2.3.3.9: flag script as enqueued $radio_station_data['countdown-script'] = true; diff --git a/includes/support-functions.php b/includes/support-functions.php index 6191146..199a926 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -14,6 +14,7 @@ // - Get Show // - Get Shows // - Get Schedule Overrides +// x Get Show Schedule // - Generate Unique Shift ID // - Get Show Data // - Get Show Metadata @@ -137,16 +138,13 @@ function radio_station_get_shows( $args = false ) { // 2.5.0: added function to match radio_station_get_shows function radio_station_get_overrides( $args = false ) { - global $radio_station_data, $rs_se; - $channel = $rs_se->channel = $radio_station_data['channel']; - // --- set default args --- $query_args = array( 'post_type' => RADIO_STATION_OVERRIDE_SLUG, 'post_status' => 'publish', 'numberposts' => -1, 'meta_query' => array( - 'relation' => 'AND', + // 'relation' => 'AND', array( 'key' => 'show_override_sched', 'compare' => 'EXISTS', @@ -182,33 +180,6 @@ function radio_station_get_overrides( $args = false ) { // ----------------- // 2.3.0: added to give each shift a unique ID // 2.5.0: rewritten and moved to schedules.php -/* function radio_station_get_show_schedule( $show_id ) { - - // --- get show shift schedule --- - $shifts = get_post_meta( $show_id, 'show_sched', true ); - if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { - $changed = false; - foreach ( $shifts as $i => $shift ) { - - // --- check for unique ID length --- - if ( strlen( $i ) != 8 ) { - - // --- generate unique shift ID --- - unset( $shifts[$i] ); - $unique_id = radio_station_unique_shift_id(); - $shifts[$unique_id] = $shift; - $changed = true; - } - } - - // --- update shifts to save unique ID indexes --- - if ( $changed ) { - update_post_meta( $show_id, 'show_sched', $shifts ); - } - } - - return $shifts; -} */ // ------------------------ // Generate Unique Shift ID @@ -427,17 +398,18 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att // --- get posts from post IDs --- $post_id_list = implode( ',', $post_ids ); - $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts as posts"; + $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts"; $query .= " WHERE ID IN(" . $post_id_list . ") AND post_status = 'publish' ORDER BY post_date DESC"; if ( $args['limit'] ) { $query .= $wpdb->prepare( " LIMIT %d", $args['limit'] ); } $results = $wpdb->get_results( $query, ARRAY_A ); - $results = apply_filters( 'radio_station_show_' . $datatype, $post_ids, $show_id, $args, $atts ); + $results = apply_filters( 'radio_station_show_' . $datatype, $results, $show_id, $args, $atts ); if ( RADIO_STATION_DEBUG ) { echo '' . esc_html( $datatype ) . ' for Show ' . esc_html( $show_id ) . ': '; - echo esc_html( print_r( $results, true ) ) . ''; + echo esc_html( print_r( $results, true ) ); + echo 'Query: ' . $query . ''; } // 2.4.0.6: add processing of post excerpts @@ -1525,16 +1497,19 @@ function radio_station_get_producer_url( $producer_id ) { // --------------- // 2.3.0: added to get Upgrade to Pro link function radio_station_get_upgrade_url() { - - // TODO: test Freemius upgrade to Pro URL - // ...maybe it is -addons instead of -pricing ??? - // $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - - $upgrade_url = RADIO_STATION_PRO_URL . 'pricing/'; - + $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); return $upgrade_url; } +// --------------- +// Get Pricing URL +// --------------- +// 2.5.0: added to get link to Pricing page +function radio_station_get_pricing_url() { + $pricing_url = RADIO_STATION_PRO_URL . 'pricing/'; + return $pricing_url; +} + // ------------------------ // Patreon Supporter Button // ------------------------ @@ -1896,9 +1871,6 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ // --------------- function radio_station_sanitize_values( $data, $keys ) { - // print_r( $keys ); - // print_r( $data ); - $sanitized = array(); foreach ( $keys as $key => $type ) { if ( isset( $data[$key] ) ) { @@ -1920,6 +1892,11 @@ function radio_station_sanitize_values( $data, $keys ) { } } } + + // echo 'Sanitize Keys: '; print_r( $keys ); + // echo 'Sanitize Data: '; print_r( $data ); + // echo 'Sanitized: '; print_r( $sanitized ); + return $sanitized; } @@ -1933,21 +1910,23 @@ function radio_station_sanitize_input( $prefix, $key ) { $types = radio_station_get_meta_input_types(); // 2.4.0.3: bug out if post key not set - if ( !isset( $_POST[$postkey] ) ) { - return ''; + // 2.5.0: set empty value as default + $value = ''; + if ( isset( $_POST[$postkey] ) ) { + $posted_value = $_POST[$postkey]; } if ( in_array( $key, $types['file'] ) ) { - $value = wp_strip_all_tags( trim( $_POST[$postkey] ) ); + $value = wp_strip_all_tags( trim( $posted_value ) ); } elseif ( in_array( $key, $types['email'] ) ) { - $value = sanitize_email( trim( $_POST[$postkey] ) ); + $value = sanitize_email( trim( $posted_value ) ); } elseif ( in_array( $key, $types['url'] ) ) { - $value = filter_var( trim( $_POST[$postkey] ), FILTER_SANITIZE_URL ); + $value = filter_var( trim( $posted_value ), FILTER_SANITIZE_URL ); } elseif ( in_array( $key, $types['slug'] ) ) { - $value = sanitize_title( $_POST[$postkey] ); + $value = sanitize_title( $posted_value ); } elseif ( in_array( $key, $types['phone'] ) ) { // 2.3.3.6: added phone number with character filter validation - $value = trim( $_POST[$postkey] ); + $value = trim( $posted_value ); if ( strlen( $value ) > 0 ) { $value = str_split( $value, 1 ); $value = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $value ); @@ -1959,7 +1938,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } } elseif ( in_array( $key, $types['numeric'] ) ) { - $value = absint( $_POST[$postkey] ); + $value = absint( $posted_value ); if ( $value < 0 ) { $value = ''; } @@ -1970,8 +1949,8 @@ function radio_station_sanitize_input( $prefix, $key ) { // 2.2.8: removed strict in_array checking // 2.3.2: fix for unchecked boxes index warning $value = ''; - if ( isset( $_POST[$postkey] ) ) { - $value = $_POST[$postkey]; + if ( isset( $posted_value ) ) { + $value = $posted_value; } if ( !in_array( $value, array( '', 'on', 'yes' ) ) ) { $value = ''; @@ -1980,8 +1959,8 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['user'] ) ) { // --- user selection inputs --- - if ( isset( $_POST[$postkey] ) ) { - $value = $_POST[$postkey]; + if ( isset( $posted_value ) ) { + $value = $posted_value; } if ( !isset( $value ) || !is_array( $value ) ) { $value = array(); @@ -1999,7 +1978,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['date'] ) ) { // --- datepicker date field --- - $date = $_POST[$postkey]; + $date = $posted_value; $parts = explode( '-', $date ); if ( 3 == count( $parts ) ) { if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { @@ -2010,7 +1989,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['hour'] ) ) { // --- hours (24) --- - $value = absint( $_POST[$postkey] ); + $value = absint( $posted_value ); if ( ( $value < 0 ) || ( $value > 23 ) ) { $value = '00'; } elseif ( $value < 10 ) { @@ -2022,7 +2001,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['mins'] ) ) { // --- minutes (or seconds) --- - $value = absint( $_POST[$postkey] ); + $value = absint( $posted_value ); if ( ( $value < 0 ) || ( $value > 60 ) ) { $value = '00'; } elseif ( $value < 10 ) { @@ -2035,7 +2014,7 @@ function radio_station_sanitize_input( $prefix, $key ) { // --- meridiems --- $valid = array( '', 'am', 'pm' ); - $value = $_POST[$postkey]; + $value = $posted_value; if ( !in_array( $value, $valid ) ) { $value = ''; } @@ -2062,7 +2041,7 @@ function radio_station_get_meta_input_types() { 'phone' => array( 'phone' ), 'date' => array( 'date' ), 'hour' => array( 'hour' ), - 'mins' => array( 'mins', 'minutes', 'seconds' ), + 'mins' => array( 'mins', 'minutes', 'secs', 'seconds' ), 'meridiem' => array( 'meridian', 'meridiem' ), ); @@ -2131,6 +2110,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // 2.5.0: default_name to no_shows key, time to time_format key // 2.5.0: added hide_empty, avatar_size and block keys $keys = array( + // --- general options --- 'title' => 'text', 'ajax' => 'boolean', @@ -2151,6 +2131,8 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- extra display options --- 'display_hosts' => 'boolean', 'link_hosts' => 'boolean', + // 'display_producers' => 'boolean', + // 'link_producers' => 'boolean', 'show_desc' => 'boolean', 'show_playlist' => 'boolean', 'show_encore' => 'boolean', @@ -2158,8 +2140,8 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'widget' => 'boolean', 'block' => 'boolean', 'id' => 'integer', - 'instance' => 'integer', 'for_time' => 'integer', + 'instance' => 'integer', ); } elseif ( 'upcoming-shows' == $type ) { @@ -2189,49 +2171,65 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- extra display options --- 'display_hosts' => 'boolean', 'link_hosts' => 'boolean', + // 'display_producers' => 'boolean', + // 'link_producers' => 'boolean', 'show_encore' => 'boolean', // --- shortcode data --- 'widget' => 'boolean', + 'block' => 'boolean', 'id' => 'integer', - 'instance' => 'integer', 'for_time' => 'integer', + 'instance' => 'integer', ); } elseif ( 'current-playlist' == $type ) { // --- current playlist attribute keys --- // 2.3.3: added for_time value + // 2.5.0: added hide_empty, no_playlist, playlist_title, block $keys = array( // --- general options --- - 'title' => 'text', - 'dynamic' => 'boolean', - 'ajax' => 'boolean', + 'title' => 'text', + 'dynamic' => 'boolean', + 'ajax' => 'boolean', + 'hide_empty' => 'boolean', // --- playlist display options --- - 'link' => 'boolean', - 'countdown' => 'boolean', + 'playlist_title' => 'boolean', + 'link' => 'boolean', + 'no_playlist' => 'text', + 'countdown' => 'boolean', // --- track display options --- - 'artist' => 'boolean', - 'song' => 'boolean', - 'album' => 'boolean', - 'label' => 'boolean', - 'comments' => 'boolean', + 'artist' => 'boolean', + 'song' => 'boolean', + 'album' => 'boolean', + 'label' => 'boolean', + 'comments' => 'boolean', // --- shortcode data --- - 'widget' => 'boolean', - 'id' => 'integer', - 'instance' => 'integer', - 'for_time' => 'integer', + 'widget' => 'boolean', + 'block' => 'boolean', + 'id' => 'integer', + 'for_time' => 'integer', + 'instance' => 'integer', ); + } elseif ( 'master-schedule' == $type ) { // --- master schedule attribute keys --- // 2.3.3.9: added for AJAX schedule loading + // 2.5.0: added active_date, $keys = array( + // --- control display options --- + // 'selector' => 'boolean', + // 'clock' => 'boolean', + // 'timezone' => 'boolean', + // --- schedule display options --- 'view' => 'text', 'days' => 'text', 'start_day' => 'text', 'start_date' => 'text', + 'active_date' => 'text', 'display_day' => 'text', 'display_date' => 'text', 'display_month' => 'text', @@ -2261,6 +2259,10 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'time_spaced' => 'boolean', 'weeks' => 'integer', 'previous_weeks' => 'integer', + + // --- shortcode data --- + 'block' => 'boolean', + 'instance' => 'boolean', ); } @@ -2316,6 +2318,7 @@ function radio_station_link_tag_allowed_html( $allowed, $type, $context ) { // ---------------------------- // Settings Inputs Allowed HTML // ---------------------------- +// 2.5.0: added for admin settings inputs add_filter( 'radio_station_allowed_html', 'radio_station_settings_allowed_html', 10, 3 ); function radio_station_settings_allowed_html( $allowed, $type, $context ) { @@ -2371,6 +2374,10 @@ function radio_station_settings_allowed_html( $allowed, $type, $context ) { 'label' => array(), ); + // --- allow onclick on spans and divs --- + $allowed['span']['onclick'] = array(); + $allowed['div']['onclick'] = array(); + return $allowed; } diff --git a/includes/templates.php b/includes/templates.php index 20b0e5e..31a9c6f 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -190,6 +190,7 @@ function radio_station_email_address( $email, $post_id ) { // 2.3.0: standalone filter for automatic page content // 2.3.1: re-add filter so the_content can be processed multiple times // 2.3.3.6: set automatic content early and clear existing content +// 2.5.0: fix to append to shortcode attribute strings add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); function radio_station_automatic_pages_content_set( $content ) { @@ -211,7 +212,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[master-schedule' . $atts_string . ']'; @@ -235,7 +236,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[shows-archive' . $atts_string . ']'; @@ -259,7 +260,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[overrides-archive' . $atts_string . ']'; @@ -283,7 +284,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[playlists-archive' . $atts_string . ']'; @@ -307,7 +308,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[genres-archive' . $atts_string . ']'; @@ -331,7 +332,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[languages-archive' . $atts_string . ']'; diff --git a/js/moment.js b/js/moment.js index 2e55983..7998adb 100644 --- a/js/moment.js +++ b/js/moment.js @@ -1,5 +1,5 @@ //! moment.js -//! version : 2.29.2 +//! version : 2.29.4 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com @@ -2454,7 +2454,7 @@ function preprocessRFC2822(s) { // Remove comments and folding whitespace and replace multiple-spaces with a single space return s - .replace(/\([^)]*\)|[\n\t]/g, ' ') + .replace(/\([^()]*\)|[\n\t]/g, ' ') .replace(/(\s\s+)/g, ' ') .replace(/^\s\s*/, '') .replace(/\s\s*$/, ''); @@ -5635,7 +5635,7 @@ //! moment.js - hooks.version = '2.29.2'; + hooks.version = '2.29.4'; setHookCallback(createLocal); @@ -5682,4 +5682,4 @@ return hooks; -}))); +}))); \ No newline at end of file diff --git a/js/moment.min.js b/js/moment.min.js index f614861..a2590e0 100644 --- a/js/moment.min.js +++ b/js/moment.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var H;function f(){return H.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function F(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function L(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(c(e,t))return;return 1}function o(e){return void 0===e}function u(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function V(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function G(e,t){for(var n=[],s=e.length,i=0;i>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),k("w",p),k("ww",p,w),k("W",p),k("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),k("d",p),k("e",p),k("E",p),k("dd",function(e,t){return t.weekdaysMinRegex(e)}),k("ddd",function(e,t){return t.weekdaysShortRegex(e)}),k("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=v,et=v,tt=v;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),k("a",rt),k("A",rt),k("H",p),k("h",p),k("k",p),k("HH",p,w),k("hh",p,w),k("kk",p,w),k("hmm",ge),k("hmmss",we),k("Hmm",ge),k("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});v=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ve),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0 '' ); $posttype = strtolower( substr( $option['options'], 0, 4 ) ); if ( ( ( 'page' == $posttype ) && !isset( $pageoptions ) ) || ( ( 'post' == $posttype ) && !isset( $postoptions ) ) ) { @@ -2556,6 +2555,9 @@ public function setting_row( $option ) { $query = $wpdb->prepare( $query, $posttype ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $results = $wpdb->get_results( $query, ARRAY_A ); + + // 1.2.7: fix by moving page/post options variable here + $pageoptions = $postoptions = array( '' => '' ); if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { if ( strlen( $result['post_title'] ) > 35 ) { @@ -3069,9 +3071,10 @@ public function setting_styles() { // --- settings tab styles --- // 1.1.0: added max-width:100% to select input + // 1.2.7: add pointer cursor to inactive tabs $styles[] = '.settings-tab-button {display:inline-block; font-size:15px; padding:7px 14px; margin-right:20px; border-radius:7px;}'; $styles[] = '.settings-tab-button.active {font-weight:bold; background-color:#0073aa; color:#FFF; border:1px solid #FFF;}'; - $styles[] = '.settings-tab-button.inactive {font-weight:bold; background-color:#F5F5F5; color:#0073aa; border:1px solid #000;}'; + $styles[] = '.settings-tab-button.inactive {font-weight:bold; background-color:#F5F5F5; color:#0073aa; border:1px solid #000; cursor:pointer;}'; $styles[] = '.settings-tab-button.inactive:hover {background-color:#FFFFFF; color:#00a0d2;}'; $styles[] = '.settings-tab.active {display:block;}'; $styles[] = '.settings-tab.inactive {display:none;}'; @@ -3084,19 +3087,18 @@ public function setting_styles() { // --- setting input styles --- $styles[] = '.settings-input {vertical-align:top; min-width:100px; max-width:300px;}'; - $styles[] = '.settings-input input.setting-radio {}'; - $styles[] = '.settings-input input.setting-checkbox {}'; + // $styles[] = '.settings-input input.setting-radio {}'; + // $styles[] = '.settings-input input.setting-checkbox {}'; $styles[] = '.settings-input input.setting-text {width:100%;}'; $styles[] = '.settings-input input.setting-numeric {display:inline-block; width:50%; text-align:center; vertical-align:middle;}'; $styles[] = '.settings-input input.setting-button {display:inline-block; padding:0px 5px;}'; - $styles[] = '.settings-input input.setting-button.number-down-button {padding:9px 7px;}'; + $styles[] = '.settings-input input.setting-button.number-down-button {padding:0px 7px; font-weight:bold;}'; $styles[] = '.settings-input input.setting-textarea {width:100%;}'; $styles[] = '.settings-input select.setting-select {min-width:100px; max-width:100%;}'; // --- toggle input styles --- // Ref: https://www.w3schools.com/howto/howto_css_switch.asp - $styles[] = ' - .setting-toggle {position:relative; display:inline-block; width:30px; height:17px;} + $styles[] = '.setting-toggle {position:relative; display:inline-block; width:30px; height:17px;} .setting-toggle input {opacity:0; width:0; height:0;} .setting-slider {position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background-color:#ccc; @@ -3105,14 +3107,19 @@ public function setting_styles() { .setting-slider:before {position:absolute; content:""; height:13px; width:13px; left:2px; bottom:2px; background-color:white; -webkit-transition:.4s; transition:.4s; } - input:checked + .setting-slider {background-color: #2196F3;} - input:focus + .setting-slider {box-shadow: 0 0 1px #2196F3;} + input:checked + .setting-slider {background-color:#2196F3;} + input:focus + .setting-slider {box-shadow:0 0 1px #2196F3;} input:checked + .setting-slider:before { -webkit-transform:translateX(13px); -ms-transform:translateX(13px); transform:translateX(13px); } .setting-slider.round {border-radius: 17px;} - .setting-slider.round:before {border-radius: 50%;} - '; + .setting-slider.round:before {border-radius: 50%;}'; + + // --- color picker styles --- + // 1.2.7: added to overlay active color picker + $styles[] = '.wp-picker-active {position:absolute; z-index:999; background-color:#FFF; border:1px solid #999; padding:5px;}'; + $styles[] = '.wp-picker-active .wp-picker-input-wrap {display:block;}'; + $styles[] = '.wp-color-picker {max-width:200px;}'; // --- filter and output styles --- $namespace = $this->namespace; @@ -3439,6 +3446,10 @@ function radio_station_settings_resources( $media, $color_picker ) { // CHANGELOG // ========= +// == 1.2.7 == +// - added color picker dropdown overlay styling +// - added pointer cursor style to inactive tab + // == 1.2.6 == // - expanded wp_kses allowed input attributes diff --git a/options.php b/options.php index 6b9f125..259667d 100644 --- a/options.php +++ b/options.php @@ -448,17 +448,18 @@ 'pro' => true, ), - // --- [Pro/Player] Popup Player Window --- - /* 'player_popup' => array( + // --- [Pro/Player] Popup Player Button --- + // 2.5.0: enabled popup player button + 'player_popup' => array( 'type' => 'checkbox', - 'label' => __( 'Popup Player Window', 'radio-station' ), + 'label' => __( 'Popup Player Button', 'radio-station' ), 'default' => '', 'value' => 'yes', - 'helper' => __( 'Add a popup icon to your Player to open it in a separate window.', 'radio-station' ), + 'helper' => __( 'Add button to open Popup Player in separate window.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', 'pro' => true, - ), */ + ), // === Sitewide Player Bar === diff --git a/player/css/radio-player.css b/player/css/radio-player.css index d3af137..b41c445 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -59,7 +59,11 @@ display: none; } -.radio-container.vertical .rp-station-info, +.radio-container.popup { + padding: 10px; +} + +.radio-container.vertical .rp-station-info, .radio-container.vertical .rp-interface, .radio-container.vertical .rp-show-info { display: block; @@ -68,7 +72,7 @@ text-align: left; } -.rp-controls, .rp-volume-controls, .rp-script-switcher { +.rp-controls, .rp-volume-controls, .rp-popup, .rp-script-switcher { display: inline-block; vertical-align: middle; margin-top: -10px; @@ -114,6 +118,7 @@ .rp-play-pause-button-bg, .rp-play-pause-button { width: 40px; height: 40px; + background-size: 40px 40px; } .radio-container.circular .rp-play-pause-button-bg { @@ -134,51 +139,51 @@ } .radio-container.light.circular .rp-play-pause-button { - background:url("../images/play-light-circular.png"); + background-image: url("../images/play-light-circular.png?v=2"); } .radio-container.light.rounded .rp-play-pause-button { - background:url("../images/play-light-rounded.png"); + background-image: url("../images/play-light-rounded.png?v=2"); } .radio-container.light.square .rp-play-pause-button { - background:url("../images/play-light-square.png"); + background-image: url("../images/play-light-square.png?v=2"); } .radio-container.dark.circular .rp-play-pause-button { - background:url("../images/play-dark-circular.png"); + background-image: url("../images/play-dark-circular.png?v=2"); } .radio-container.dark.rounded .rp-play-pause-button { - background:url("../images/play-dark-rounded.png"); + background-image: url("../images/play-dark-rounded.png?v=2"); } .radio-container.dark.square .rp-play-pause-button { - background:url("../images/play-dark-square.png"); + background-image: url("../images/play-dark-square.png?v=2"); } .radio-container.paused.light.circular .rp-play-pause-button { - background:url("../images/pause-light-circular.png"); + background-image: url("../images/pause-light-circular.png?v=2"); } .radio-container.paused.light.rounded .rp-play-pause-button { - background:url("../images/pause-light-rounded.png"); + background-image: url("../images/pause-light-rounded.png?v=2"); } .radio-container.paused.light.square .rp-play-pause-button { - background:url("../images/pause-light-square.png"); + background-image: url("../images/pause-light-square.png?v=2"); } .radio-container.paused.dark.circular .rp-play-pause-button { - background:url("../images/pause-dark-circular.png"); + background-image: url("../images/pause-dark-circular.png?v=2"); } .radio-container.paused.dark.rounded .rp-play-pause-button { - background:url("../images/pause-dark-rounded.png"); + background-image: url("../images/pause-dark-rounded.png?v=2"); } .radio-container.paused.dark.square .rp-play-pause-button { - background:url("../images/pause-dark-square.png"); + background-image: url("../images/pause-dark-square.png?v=2"); } /* .rp-stop, .rp-previous, .rp-next { @@ -217,13 +222,14 @@ .rp-volume-slider-container { position: relative; opacity: 0.9; + height: 30px; } .rp-volume-slider-container:hover, .rp-volume-slider-container:focus { opacity: 1; } -.rp-volume-controls button { +.rp-volume-controls button, .rp-popup button { overflow: hidden; text-indent: -9999px; border: none; @@ -231,81 +237,55 @@ opacity: 0.9; } +.rp-popup { + margin-top: -12px; +} + .rp-volume-controls button:hover, .rp-volume-controls button:focus { opacity: 1; } -.radio-container.circular .rp-volume-controls button { +.radio-container.circular .rp-volume-controls button, .radio-container.circular .rp-popup button { border-radius: 9px; } -.radio-container.rounded .rp-volume-controls button { +.radio-container.rounded .rp-volume-controls button, .radio-container.rounded .rp-popup button { border-radius: 6px; } -.radio-container.square .rp-volume-controls button { +.radio-container.square .rp-volume-controls button, .radio-container.square .rp-popup button { border-radius: 1px; } -.rp-mute, .rp-volume-max, .rp-volume-down, .rp-volume-up { - width: 18px; height: 18px; padding: 0; margin: 0; -} - -.light .rp-mute { - background: url("../images/controls-light.png?v=2") 0px 0px no-repeat; -} -.dark .rp-mute { - background: url("../images/controls-dark.png?v=2") 0px 0px no-repeat; -} - -.light.muted .rp-mute, .light .rp-mute:hover { - background: url("../images/controls-light.png?v=2") -18px -18px no-repeat; -} -.dark.muted .rp-mute, .dark .rp-mute:hover { - background: url("../images/controls-dark.png?v=2") -18px -18px no-repeat; -} - -.light .rp-volume-max { - background: url("../images/controls-light.png?v=2") 0 -36px no-repeat; -} -.dark .rp-volume-max { - background: url("../images/controls-dark.png?v=2") 0 -36px no-repeat; -} - -.light.maxed .rp-volume-max, .light .rp-volume-max:focus, .light .rp-volume-max:hover { - background: url("../images/controls-light.png?v=2") -18px -36px no-repeat; -} -.dark.maxed .rp-volume-max, .dark .rp-volume-max:focus, .dark .rp-volume-max:hover { - background: url("../images/controls-dark.png?v=2") -18px -36px no-repeat; -} - -.light .rp-volume-up { - background: url("../images/controls-light.png?v=2") 0 -54px no-repeat; -} -.dark .rp-volume-up { - background: url("../images/controls-dark.png?v=2") 0 -54px no-repeat; -} - -.light .rp-volume-up:focus, .light .rp-volume-up:hover { - background: url("../images/controls-light.png?v=2") -18px -54px no-repeat; -} -.dark .rp-volume-up:focus, .dark .rp-volume-up:hover { - background: url("../images/controls-dark.png?v=2") -18px -54px no-repeat; +button.rp-mute, button.rp-volume-max, button.rp-volume-down, button.rp-volume-up, button.rp-popup-button, +button.rp-mute:hover, button.rp-volume-max:hover, button.rp-volume-down:hover, button.rp-volume-up:hover, button.rp-popup-button:hover, +button.rp-mute:focus, button.rp-volume-max:focus, button.rp-volume-down:focus, button.rp-volume-up:focus, button.rp-popup-button:focus { + width: 18px; height: 18px; padding: 0; margin: 0; background-size: 36px; background-repeat: no-repeat; } -.light .rp-volume-down { - background: url("../images/controls-light.png?v=2") 0 -72px no-repeat; +.light .rp-mute, .light .rp-volume-max, .light .rp-volume-up, .light .rp-volume-down, .light .rp-popup-button { + background-image: url("../images/volume-controls-light.png?v=1"); } -.dark .rp-volume-down { - background: url("../images/controls-dark.png?v=2") 0 -72px no-repeat; +.dark .rp-mute, .dark .rp-volume-max, .dark .rp-volume-up, .dark .rp-volume-down, .dark .rp-popup-button { + background-image: url("../images/volume-controls-dark.png?v=1"); } -.light .rp-volume-down:focus, .light .rp-volume-down:hover { - background: url("../images/controls-light.png?v=2") -18px -72px no-repeat; -} -.dark .rp-volume-down:focus, .dark .rp-volume-down:hover { - background: url("../images/controls-dark.png?v=2") -18px -72px no-repeat; -} +.light .rp-mute {background-position: 0px 0px;} +.dark .rp-mute {background-position: 0px 0px;} +.light.muted .rp-mute, .light .rp-mute:hover, +.dark.muted .rp-mute, .dark .rp-mute:hover {background-position: -18px -18px;} +.light .rp-volume-max, .dark .rp-volume-max {background-position: 0 -36px;} +.light.maxed .rp-volume-max, .light .rp-volume-max:focus, .light .rp-volume-max:hover, +.dark.maxed .rp-volume-max, .dark .rp-volume-max:focus, .dark .rp-volume-max:hover {background-position: -18px -36px;} +.light .rp-volume-up, .dark .rp-volume-up {background-position: 0 -54px;} +.light .rp-volume-up:focus, .light .rp-volume-up:hover, +.dark .rp-volume-up:focus, .dark .rp-volume-up:hover {background-position: -18px -54px;} +.light .rp-volume-down, .dark .rp-volume-down {background-position: 0 -72px;} +.light .rp-volume-down:focus, .light .rp-volume-down:hover, +.dark .rp-volume-down:focus, .dark .rp-volume-down:hover {background-position: -18px -72px;} +.light .rp-popup-button, .dark .rp-popup-button {background-position: 0 -90px;} +.light .rp-popup-button:focus, .light .rp-popup-button:hover, +.dark .rp-popup-button:focus, .dark .rp-popup-button:hover {background-position: -18px -90px;} /* @end */ /* @roup Now Playing Info */ diff --git a/player/images/controls-dark.png b/player/images/controls-dark.png deleted file mode 100644 index 0941a76783a81f319c166583fc9bd8b33a468b75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2423 zcmV--35fQIP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!Ts+LmG$D4GhW zQ%mTF)KJyN2_)FDuGTHqg%nAVJ@4!exm@lnxw3*NfL>rQ%iW!aXJ^mMygS;}t6vnz zAx*OmY%1_wIFGtk9fqUhqO(A_zre>$jmolbSX{)m-?-%gf)i2HLj0(KLYRIxGGv%1N}NZJ9#sU>-rid9;%N8p-!BH_lne;D_wwaS zdiCm+1;RNC#`owsoz{PRC(c&WXP^CHj5$darpgau@4p3ubtT>#}$&gM5 zA|Gniix*#yF~?AGbouA9hR~Jv8p3O6y6 zHcM-3YoakEAbdG5V8yPiNnV_aq=s5rTBxqBP6UXckkm6Xv-It^|K)T1B-}!R014-U z^vNGTElX~JN=_1-+qZ8k=OMc!$90swdGm&LcXw%heO>g9J>jLy?&kjHk`q&IrI?ch z=hm%T>dDc;aSbS+FVHvN{F@FA4rqCK*)WlLY7H&6^%Ul{kTTyia-K z^74upUEA9`w7I##e_&m3NKByCv74BaV9sP;Uw?u*2~Un+gC?cuv@#ur zQucf8ZS4iJampN{l>J`R${tcWMi!er>!<`%N$D6_ZXDNuyc{Fi6-ukFr@S1aic<_o zBo`8n@9}bswnJArI7-K;;`nxqpM3l&+mHrvj2gcozmAdH8Qfm1f2i>r$H8IF6DgHr zEJ>sGj%Lba&(rtmSJc~eUi_y25HY7F$7o8;3HJ8(S~sbs>p_agFAB%F@@|Q`x;l8x z>gBpc{Dz)Ad#3pFaxuq%W3+J$JI6Jt8k*^cg#~Ik)yzE?q?Of08Xf(U z*5BWsKp7kyObiSRBsf?8E&w*cF)}B~oJki>A`pwk##UBV2<7eDw<7w+#zqkYTqAl3 z$5T^Z^RY(5zZs^brFS$p|2>66Q99e%POo3j)6(*4f*GPPC&8S_D_5>m1eM98zOp!z z7AMJ^NsE*4?HE^Aw&?ZZJnd)qMUCINyzbjEsyN!`pI6!sUDt%Kjz*)@(a|A#X=36@ zg{Q72i(`N@F)<+mbMw@8xeohU9b+}s>KN@h6319gwK_)V_-*M~^|6s}$B0D5J^B2B zsBsTuZ!ZgEFr-6ZsyfAi}K`FXkO63^OpSx)M zda%#vXgg~ZvZbVQ4amze=J>;Lb90lLo15vvg$u&KspRDt9UMb)iUElv<5JBr;=8?p zucgyz0qs85PN6{Q2^}1zV|3vd;P`fo*cHKght(fbg3j;1zNyu>V^nYq-+PRmyyoD^ zHa0fIw#^npPeoekbX&A0$0+w0nPa$deD5)aLIG-PYvWPG-{I^C(wQ`MojyxnJ^!0E zuDm7p7%h(Bh55F9(ktIB}Rt|XJ@BKU`%9Kv9ZozT8CgYAvpnJ+Mk-5;stSA{FcuajfTPD1TN=1eE3kz z)OYXR6}5wt+}_?5zl#BljEo4MgV|KBz+S={H#av&)6>(|88e`fk>4A&Zh%T%yLN4C zdir1HmYz7nLgM0y5QtYWSSDn_!st}((?dyiPO*pJZF_opsJpwn3<^&KVdb|Akmt{z z3!7rfe75H1MsN%#=fQ&qqSx-+xl`7A7AKiXnZDd}ExU2!MlpvF(9@?+#j(3&XBWQ= zieio-a|i)3N4%bLneB_O?6}A?Aap0}0g79`)GB-1YMekk9#@VC3|Wl2OeP~nlrxXY zB?m%f1~6xmQzLQt^5ulZNm!R`GTVFh5Vnai|KPI)w)U`MPSVD4$)A#b0)zHp`eh~c z&!Llms_7)4YB~w1nyNZRr#Esqy_y|k70!uc^pu@b)iFvT0J{M3;~1qBoZ1|tKON2Z pI2y_k>Lj3QIti$nj#)rN{|AOwkCjw@6=MJZ002ovPDHLkV1ic#u!R5s diff --git a/player/images/controls-light.png b/player/images/controls-light.png deleted file mode 100644 index ea6513f78ce10564452e147af85d9dc2e506f31e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2301 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!ThYRH6*vzq8 zaZFJ~$lyEvH#bL5pT44JPhZGZR^2eUFZbhTtaHJCtc$;qk{lbH-=F*AjBM6&A_JIBZZSdgD1(c!MX5{zcqqk={R0MVoteW%og)G`eA#U z@`VEJ)vDy+nw01!?{&ooqO+?I#|G!p#mj+UARWgnm-h&zRN|V;&gKp+eYZr#og!6> z6?t<|evUyYavVo6LL3{MtCy}c1)~(jG49{L?+sj~QW1@>3$J-(_Q>G0!;_k(Nms7? zlTs<}$WMU`L@mUG-VX1)lWuP->&d~Vy zxCEh1!uTF1<a)*2nPHBl<9Ipbmx{Xa4x04j$urv8+K~{*y~m*rkq_gYm zKVJrt8oGSB`E^GQF~`zzjOFF!Iz|Bh9RIjnF4O${Jnio8()#+kXiM>HF+F{SYwqb2 zKviB$3{FO;#3jhF!I_?(4g}@CWqOX8N_9|YXPUaYI(b@B)Z5!bBO@b}&1R{)yIXeA zi{D_X*SK!xSm4~bb4R-l`K36KlYipG3F_(Tp~1mHF*^Q)S8|7&$D2z^EV<2Mjs?!$ zyLa`AV~7(OkZBt9(MP|fj*bpGdh}>yK#*u>LOr-Cd6FCpoO}1~#RS#h7;9_Ien_cS ztI_J}Ds66V(%9IkKo0q1!~*Ky@4}KCTXL*8PGWj2h=qQpd>sZ0;}iKh!wKacYk9L`mlu8>-XF@&=;X&=)1)-^>k-NUE)zoD#@`!9Fc>G9pj_<$5dF) z)9A=i`tJKY4G#>_ncsX&pMU;oBFD%aJAz{+c8m}H{VzIpY?w}sjni{Z^rfX0`s<(m z&co=wSw2pzV`PpkITmwl$*~eU#^HDS>HU$D)RpcMHO^C}lh`rpIFWwHaq41}ip3)F z2#RT&nz|hDL+wa%Y;dNgrbJ*-p4zX-*S=lH*h=j>M!)v$7?UW@F^2W_ebSTlTcgB| z5s8X>OtVAOxCiq0R|WEM0*HHk8M-_+ixkb8kXGTkuZSlb7#I+na6dzTC>7}SYOyWHsJ<~W$ByD8{>E6T zxQit=Q7m|w&J5*Ve@}lt{}ZQIhN{$`oG$!9C&mVbL8;%jfJ3Krrk`bHlPN_J&B-4pAIA=J z5<5niHeAOVg5&=F0b1E!rkz#yHx{HgagLGu+EJXuj`78dmtxLUpAs)peSIA?bm)lJ zCONT=kvVo0C$VEZbm))>8U@ps=42e~AL3w>IYte~ihRrBzA<9$=;HYX1Veu8)G_Mp zz$eRQ#eHLp;8@^91{CKQv1V*-ZHa3W6BF8Cpg6}E;)r0N#Ey}tTl^ygiMdBn$2v() zz%d$z$$cD=Z|I9$LU8OD*Y%`CQXiihqnS)@7?D%9e<7GRDL;BZc*-{^Edqk0%jI&S z;J7_CKY6uUV@BRa!Ew|*O0fYweE2X>_ABmdpFVwh1_j5mIa09!Vf(=Wl|9V!;f;BW#5 zIoGaT6D#$tTen0V;#hrsJ>uL8Xm)m1_#CXJY6p%J_PDXJF`As5lr3&Rv$OxT+x-Bl z^x(mRnaRoDxu5hz3k!*hCqf``!C;$E1sh{n?Jo}v`8nPQ!P_n_F4EG{QUD511Yzg* z3y|~Y&kLL4${nGNjV*BOFz5R9>tfVy-@Y9fJ;|}MS=X1x?q%#Iy&OV77cX8E$I()I zS@V0s99!iO0%DGso}k?RzpU3D!wGwU5|uCQ%HOvYCpMtb(NXbp7X`=r75d)1d2Lk9u1(m_D2bP!N0c?+Y? zF^0WS$mzB17+Y`-9Am8f9B+hlj!_8#_ytH3$Ec*?wC5O;>0opHX><@!D;)$BFQWeg XAPepgv7F#&00000NkvXXu0mjfH>-Hn diff --git a/player/images/pause-dark-circular.png b/player/images/pause-dark-circular.png index 07ccb47c4e75f3781cabb904cfbb45fcd085fddd..63b5d6c686abbd1572f09d501a5d6648de691c4f 100644 GIT binary patch literal 4977 zcmcIodpwi<`A%~n#IptJxHiXHk zAsLdzup)-VHXDA^_xpREr+R*W{_fZ7zVFxP{#>v3^|`O}bv-aMH5A${y_<)JN66Ud zj0N|dxpnjJS_xdV*i5-?q?_novQ{SeAEcB!3>*oo^wWXN>t1KdLa^u%`qtXLS|J z-T;;p8ssu|`7)Z=G!A2dx)Du5M~A55LLQVa#iCswv8RnOdgJ1wxbd zGDVxN-yJgDx@??<%aNi|8dAw{pGU)J-^!%wZn?>zRN`*X#^?zIb*=%KU-xz!Bz-$* z6f{Y+2X_|w1nj5MPL6}C%^LG}0}#oQpVe5ASUV4OMB1ERXps4O{!Remv1D6D3P(-v zVPL5}?T#iepBI35AlY0$LMoM@uUM#WK4fU z={88|J;eoI?V*L$;$^MLpu$a&QL}`}rL+h-$}ygn1&v|!K^b>(-kF`tT09xGPaA5{ zs7=!iOAiBEK`Is6Bx}`Sl}D)UFllwK_jYOl^?tL+Db^tquE*|o);+HIsM>T)FGvpvDyXI125U?{k;fzjkmpfSR2OD>pw4 z0{U=@`=*5Gi=5ze&yAqu^*Vi@)H|^&6h*tH4=cH;v(~El1o~K=NU#o9wCTG6!g=zc zZF9w-g2W`dTx+_%uqmoocZDOvH(bJjf@7c?MvT_~-DB)#@0;rCq%!)ShmnT|*1 z1Iei7escWwxO|WGOD+_vb6ryLRk{(=z<>PIpXpx(dRXHZZN#@N7mSA1K zY~F;^tiL$Law_?{9zu;j^eg)G9C4ms3B!-(QEhzV-&u(_S&PKYK*ZR)+nr*G9)syp zSe)1BUEG-#Y^Q{fJ_im_Xo{Bw7Ab8}Ag$YSQO7FE%s6?hZL{iSJwQtbQu*T}X5D=G zjT!n*QwA$;mz}bhB~eM5zKW|7QB9gK5_!H=h%g>s^U!%qm~+{hys^ga}RB_FO8=3taC)pp^f3DR+d{h9Zp=^ zn8ug>qmZ+^KRL}lYjzWgF<14=eyXu`tc&mq(pp<5rA;*D25O~-9go00=7j=zuMCy5 zU&(@tlz;pUN3Wn(u-UcyT%tio){BIWLJ5qyN%FAGk+5P}MO?~*(0h}N$&fnNu&H-e zDJ~Sn8(~5GRo&tI3U!rSG}WKbuvKz1i7lt7RnoFIz9A+po8L&KS;Xpy*P1*0glQFG zvF1{QEIw?}G3ungCYZG&LiB6a8{BNykJ8H@ zCEm)&T>H+J{^^Kw<+F}riC>w1qlx;<)67CI38f5n3O~(tb~DkE;nH3E>2OmwVzjSR z9rXF5A#!j+=kRF)EDhedVlid-%KZoJk}u68_mP2fs*Y!7&Df8YU{dFvC(6Q#KtGCQ zykSEf@nQvMv?Oa0k#BJG&kR+=2mcP|&j;K0UpR>|X|>bCR=+Y$A;D&h(_>a7z_K?A z*6-z{Cz9j-g{c?PeiQw?3@uVtpDHrp;ex)f00IX;9d>W*6aW9qLNk3 zo(~oHNqFua${Ed$zx{taI#4zkE79=}7p@*Hn{>o|SIa&rxqqVc!q75wd2q*Cl+@#s ze|A@#_=j@^R(4nD?&EbM@D!ki2NXRKPff!7zp(|H_MluKaw`n*@ z{NKj5eEPdiTWdgZl)qJgz>BKPB#i`{IxpQWcz5cs5kc#SiX-<`84C6$I$aD*N&f=wPm9rn-G45VS~qj z_fLK_1jH)@9DR58gIDBW#WX{YuwCVkzbwrYb9!3GihOaqIknoLyvI&=m*lVE$d6wWy^fvARpXFpkNq0Y8-+4on5_bH#A2j{XY*2Azch5FoI%x%<1qtn60^y?ngIf1-lUMs8qI`vokn2MA6S7J_XNH4b zK}|@r?`fgW^6+&+{ls|UXDMxSogae?cwX3|p8c_o_lg)7t?ty0@pc&1_yIWYO~g;R zA>8v7X>+8}igcWtyN10FqX|ti{dCh>ZcnqA&1;Ab0cT#_!CT zv%GAq!O-f_?!(mWMs|ji#WTe)wnHno5c`&ellNkR(xPOBrj_*P5-~|tF`?YbZD+W) z{L6>r0n#e8C2Pt*3{>yfK&{>hsj}e3Tg~|c1m-El?i-g1m|5VAlEt;YI+3zd<4YH} zbL&XaS4|-7HIUX(p}bLG27c9L^q`juMf_abE_sks|Hz5sFV(_TD@~KNa#_u;Vi40j zw}mGO4i)PcJT&gRzZ!FMbEGo*wn|drtXwaeOf6*3xivbU9B7x!>S4Rhi{EQo2h_Yw z011D@JGkM40t<)U@O04FD7Yb560Yh4jgO^2dADJAKbDC7BTQA(xD~}rQ6`=$F(eQl zjs^EkSq-1qr7+?*G5ZSKIn19pfms#n^^gg~JQ@m7@kxCcdu<)+G0q=i-xh4lLFKQc z5!0XD(A@!fYiu0;ySRC4hB1~QVKRO_$?5#uRo#$VLyYew|H7Tv(GHI9viZmf_%Sf? z>Rf**W_`~|a$*aiRv7~ud!qy@tO>=w2)5Vr-$%@#KAs5zmVHR8EcI{ZHX$MdeqV+Z z$uXa(kNc+f)U!|+5M$YQS>fVKnI%ylsHBt}$i^JGERq6i>XR4lp&y?%A~l!>y*v3< zfZO=M9V86lBxV**4k6&{Cbs&s_MEuFd(WJ2|8`+AaNLO4J} z&tS&a*2nW)ToF&jfvJ^a?$t41-^B=jvYUxuec;;V(CDOwfh<~&&tv}$((M%C`{4UEh1W#0{8H5itoK$_fq_ub^Vb6B*(-w#B8(`9^aT|9V9S7HS_0{sFvIKL6JnYKB;K1N0z zKz7~Bv}}|Q0g8`{K}wSq2MxQ%)B5fTYhl9bfD=Q_1j|f;urtpb$8VHEf$53X3%&x7 z^xY5~7b~fJY0zUbG;=x3vk}M|taT)XYS7RkkaP{ueZ)tos(*%o zy&&+tE|~Fs=s~916PKPf*v2p)5g5JDMVe z)Ls+L=i^#3fT2ezay?4zv~%D{SOm*q7Sy@NZ)_o)L1xiD64zxqmJTo=(hO0TA4f+n n+}XjNR*Q1fz62D-VN=+EG>1?+9K@Y=@$hhagfpf3&bR*u_m2rp delta 1384 zcmV-u1(*8qCjAN_iBL{Q4GJ0x0000DNk~Le0000e0000e2nGNE0F3^)ZIK};f5HF& z4#EKyC`y0;00k0BL_t(oN6netPn%^Jhf7<&%3CM}1}rXdv1720pvxSIPGg7(i3f8> z%VKt5Vs>ESA2I(26B7?L;lOy{DaoRuk%T!jgA5e>LT%3Q{aefa0S_X-b@uq{L=sW{Mho9vv9aK)p=4 zKpEZK+-%S1^Bzh|p-}K{Z*RN3UT?0owY5OmZf|ejWZfbqOnD6K7PVtJ1_aDYb-7q9 zzLiWSyAz4T8?-k7>SR1uQS=A2Q9P7lOG&+fK;W)rSvlHsjL$g*bp#09f2&lJ@pxQd z=YfijjEsC-nsugdIQ%(q1Kr)-4}g{Hf27=hmiy}h8mnDTr_*DrtE;bIu`R$pD$PEh z0Q~|`Uw?mp*yr;_82ScCS-z(B1q4nn{>`h|~!A#rIj@zy4aw|U?2JRCxc4*Qw za=F}zSS)su?zPhBQ&m||yOj;`CV;OUhFkX&mb{3&Q=FT*v9YlYf2_D{rk+~RxOP&z z6}X0O2q)s1@A2%jG$##bvD$ zb@YAJfD>U2yX;o`~4WzY6#C^n9<>(Ddud$-|wjsUhxu&+)=46e6HcR= zMoT#}aQ~Bv&f?zhG*)T%Ic5C(1&U~oMLgT;?d?s`)b)t1ourB2ut?xqWP1memX_9) zx0s2LfRe+Kb6(Ga9j!?!AKy=9VUa%Unpaz4oBmy$XI+34_-y6*eEkBP zu7t~1!1`J|Q;q@4GHwq6cM-VrB-vil#3Dr(KIuB-i8VdCKu>X|Wr;PnSo3d3M~9A& q#d_|nr2_+23Rs<^hxorCT&^eR6{JepD<%X00000QDg~V00)Icb{P=Dq7as#jS_ZI(;bAEFlfV(ErKip zEn#Z`K~N)#HbIsNAP^oH*^vnH0#Vr#APEF|g3HXmnbZH~hd=7ose5kScTS!9?)~Zp z#??_-0j2jd%w-Qp8%Rx$7EikP4sU(Z8UlGpRg|2S zldiW$pTJ&*K(@7R4w?1~73ZYj4+MK3g6D-`0`csn^N{GXp-}`x)Oo^gQ-m?XA_7Tv zg+LUiob0TYyO!K{jxz4)K8yktSj^72W-Fr3nI7Y<&dVNBSUy}6cuNQT&Y8g7ba{EvETl9 zXiyI7{0vd=)A>^~)1yWaflJCY3LMXHoxD|++S=SX6asG1Z^2^GY z8e9rRtn{|2lx_6s7XD+4={~FI#l0iF0Lj~g#OYAs(Shyk6wxiBqQa z^=^9_wUW$tZ^RXoXA2Ru*$&QiAmr}K#ANzkNG80FWzw=rJ5Qj&9##c<`cix^UUXzO z>g6Qd_#?7Bv5Tj1aR0 z(absDod_l@@LK)&4&Y1RC{)WQ^L%R;6u<@MfMI)a>P z^-h{$-2P8B{^zX$Zx#>6_WfBa`k(#YwQ2BQ@_;y1#h45ttA^G5ilSMu?Dz(6ilyR1?#%^^ z_#m>{S3K4h8C)Nt7pt|$FtCebqnyJAk-}lNOIf>VP-4p6lC>R|y4`F}Zo=i*-NMi| zB`DELgh=p}+*-)Rl!8m5>Nwi^cwfZq+sX7~n!}I@N72kr8o2qv@s__0k2Ev2)j8Ql zD=i3q3Z@k#h*EhUZ{Vj9pAyuwiOy(B5rvA0#cf@zY5MGXfB5GpM*T9D0|;%5`1d7y z2wOT?AaZ&5Rh}x)9hqNwwf?r+>$S}89-@bB%u!Mf%Jb-l9Ims`TC~Zbk2$0#(D7EZ z;qnI&SD=w@DagZvyI3AA_9ab8ahgkfj^Z-)B@3i$iVH4G?_q6kX6}+hQ`2I_oR!!+ z3)}^4TEpu{^A;>uMTAbNGOj(5A-Of>4Sb0Fotg`@;U9Cc0FK?QbuFbmL^be`fH!`( zlr$|l@T9h^!ugr>k0Jxof$%MWrD_bb%cMSLMBqz>_V^a7_oT)yvnW;0f&`iN!Tk zi|%PUKTn%+dr9x@0q$My_7Jz4lP3;lB!I)kQzd`jC+Fn?Kaw0DS(n>DGax b#OQ>g24lC{^D5Fe0mRAP)vm_oOv1kau!mM* delta 1674 zcmV;526g%K7@Z9viBL{Q4GJ0x0000DNk~Le0000e0000e2nGNE0F3^)ZIK};f589% z4#5Gqk!$S$00uHiL_t(oN6lEvP9sMYt!`fCVIW{$N?7s@tVXcp8?r>OVG(5!CCXwX z*p8HpqKSkA671$5*x9q=8$c`&Vt7lu4aV-AbE|HV_r(%IQ*a=Bc! z4S=mrxV~6jU5)1F=fjPSjaqDdVPPSF)6QzO`sc&LgSol6nI*Zat1DBfRAQneMh671 zTM?uzWvHjJ;3C9GaA06S&infMid|h@l=*jabMtI*aWPn5Uypo(-Q{xme-DxYso9H* z3zDRw86O{?abj{~2 zmyu6SPR=l?j8}w1VMmpWoXa>OVC!a}EjK~L_QAnHiP zm|O2bu$yT;Jv}uL2uY4+e*$&v4q7G|brQQmmy)x6P7#nRD9OBKs?ioAd9+|k0UXhugz-_b080j+3w0L~*Rmah9xqTXvES56Oq z(n9039YnZca6VEPdB^F*?A_hn&5{!#1bI_n{;|W1jg294HE{Bbe@XoOF;QF`83of<#-n2!1S8IQrDQE z!irm@BSK^)`?5h{e?ZPJ$t4KV5Lq_@`?M%pMTSTZsug@mKC$$v6%+Ic;KH5QK6R#Y z3E4M-S_}{g0!1c`*xz&7)~)5n^Eir3fID_AdAj*gC2PEJl%FsWC3@+@ql44xKJ zQ^%QT3>QMCnfeKWYBUYp%TjBHk}U>&a~(F{uLC0TfeAo2QCJ_iy@ zr?wT@CJ}u5e<-FS=o5Vo34-=kC(x`ff^Uc{sjYLcND!1eNzmHRYoJSqq7mUb2a6_i zu#PMv&w}`_1z2lohM1szr14b?YY+|@NdsR0hpbO7hS=k^tpjlI`-Py61mm*`o)0 zeM%PJ$^3@>d3t(!nKiG|$Rd-5Oe+V?n0TI>n_J%B-=CeDnwqiELL(kIk$llnB1w|x z`tT=MYr8_P)loCpY=Issp6~4JlyM=Li$*^ND8x zQ%V!QN=HXWk{}!SO}x9iTcQF-WTOa(Lr08FuE>P;$fRq94$d#OLpmjPc9Q@v!l?K5 z_DbB{(0~cSTrMYD9wdoZ^AcVQp$kScB1(K~s#;#~J0( zhQNH`)(bh1OFG3L$vB_S7e_`$xJMIxJv?&hh5;IJg-y1i13DF-iDJ0d;tFN#0CB`N zf1t~nr#KD|zn047azx+c!1Q8O`N)1D$jw3!{EKt3P$(29CMINF%ABN8aST%|cobo> zm`K+_q$dgxEx&~LcnGUfC?f-@Rrxw2gb2q|);vawKIxl&!Q~(5U8$UaTfjiRQmOok z(S*8BryDuk0eiv+HKA)J+l6EkaQdjcV1C_^5Hm(Hc*-QUfFb`kY+h4mZEZ~hZLzep z6oAwZF!BoxaZZo~iLr2dq!pnwB*iHmk|s$OlH-o5TbMuKhAq=4vYLE({61mKe^zJt U2E$W9jQ{`u07*qoM6N<$f{BX*YybcN diff --git a/player/images/pause-dark-square.png b/player/images/pause-dark-square.png index aa640db22a9f0b8713719e9883af073f5dbbf340..c081c1b7a3cf83db60a8b0ae21828ab2ecc772d3 100644 GIT binary patch literal 2957 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 delta 1761 zcmV<71|Iq181oGwiBL{Q4GJ0x0000DNk~Le0000e0000e2nGNE0F3^)ZIK};f5HF& z4#EKyC`y0;00xLjL_t(oN6lDWPa8)Ron7-0zGFxbLZlEv)k=AXr^x(^Nc|C2rB?99&Gx%b>V zv$LKtUpoSq3(n5Y!sFxPAPmE2f0atboSdAPa=C0SFE7o-#f7O>t0o9+ms+hRIb@ehY$0+ySuD2BO@c`(W6H>Cw8mY*w`q*mXHJ~ z=6R5>3=Ivv1i2&12th(4Tn(H&Ws+Y9X-iZ*Xrwf8ia;9@@AeWn-Mnfb8Q{Ic5a$pm zgOx;HNust<3mBUh(B*+R4DPhGe(h(urCHr|! z!69dt2^^R4yTVOHhpgh;dES zPiRa~zpaA6HuAkoe``q;lXwy9<>LGI?X<3xL@|sNQQbeDL}`Sh{#;Jw!|DH{7xjspCo1;N#;tF@XJ0L$@p@rM}bz{@NEte zQ`9Hsph8xAf9Yd|8^Krlxkx)l5>ob5%7X5p@!0x3ZV^h%AFZ3vgdowrl6FC2>7l)e zY>NmskD?k%mwhynNk`Bp+6)PTP~%Fd)<1%`2qo4Ebq*E@g7PK_ZdUYvkY0`&5w3Hv zXfX%tXvgz*5Wlqmb4{%f6SO-uCaih5#FGZRzJ#n#e=dgTL57|c@XvPe z6f~O9j6}SUdSxH&$XKXe`!NV=k!(xVuJnqd;uCY>Q*WfRlk4c{kd!X_Xh)_F=yj$! zcC?d-pl`hHQ61W*WM^h(_}Bc0rKP0>=6p`09ho#_S~(~|vHbe=>xDOO-ptR<&E;&g zP>D}Yeq#6wWGAyA_< zq{h^^-{zB3v_p;%DX%+^MD%z?2vWM~qc2$%OXEq;rKj z=O^1BjS@S1NPs`W$X8ca3*4V;z=R-=y?Ero2J9mWQI(NER~U0DkVrj!SptM#dyPyZ zK&9xW4fir`Z>|@V2RRW!kP{LO`H6`M9^RPkf45)Nuz7(7@y=dW86%`u0>aiN9&~6T zf8rjDt~gCGMjs{0qYitPS3G(l2XaXa@tr$&UhohR>s5Yoa*}5>(Yv?*xZ#EZDshQb zHlqO=6`P5Koll%7V;#g5>p)-TEaH0m_FbV=Dn+zCGBT3q@M2YYVcQ)Lxq)J__%lx# z_V)JXk+^jDsjoTK|NPy)@f1{HCiMYB@5)T`+KQaq$zp33(jHQ!kR_Q3T7}P!qalvcBa!12}!Sj1iSe zMHYAtA4iNYC&@8l!^6XQ)1j|dm$TUN{YYgz1|ta)W6nAfH)4{g z05=izx0~9Bq)CzlUBtv`v_D4~6v`xgeJ}g*G$UG3JZx2fx%#5*;|MOo|!*8f&x9y(`xcc&lFHokd*um^UIoz`kKD1?b4Ggw;Xm$iBsG@xyVI(bc7i~LrKaE~~xDs}#ysDapuCAmlPeZRl zuRoB=%JbaQWfKWIj+9t#6RH!kLy5!d9kD%D1LwowzV9Y;lyz6XEjaubnHKwbS);pw z$!vYRc2cog)yR$=P4-)CWY0LU@+3nM;uz^rOTr78d;Uz9yG^(p>CII^aKw&%e>9H` z%>nn|Gv$|F9{e9lF1IJuN^VSx6Ccb*%0uMfz#gb;kZmmRvV6I{YeE{$AakiJLo;qI zSo-h;1L1N{MlnkLB#`jONgSidPZIw98RKyK#fJCGUkyQuwqQo;n^+a^Y^GL?cQ+9d zck$oQk*rxJx;$DURQ4kS1!bI5v`vzZOc^Fp(%~nOoo3OAwrUi#C-QhB{O#MVPiJmj z!r1PQrZ7@#*kp_s{_b?dv|fV=l$}*lZ zcrJvU_ejq8SrzpFX%dSC^wm?mq2;e9lRmEg0%OTZ6yqU%u%uZ@jrD<68J9X*;pU!& z3sMMM0dhwtRLW;{G)|;XxxRU3g)_$HQ8*b)Dda+EpfHP1T}ir^BwUuu>IKO0&C&rp z@5_(sQm48BL_?axqfZ*G{lP#F0mgM{>=7dOP7Mwv%LfFNwlb16ak}9+lx!=Is`150 z$~hHf5f^$B7(wpLL-cLW8qpd0g8(j;)DPEohno+K#~hyz_AaV+s#pXq?5kKba>w8T z6g#U&YB&=O7<1LQ-WSXEy0JzFS|hb)xN`Iupo~W{E}~d_x216DfZ>{~5hkZ@V|eYF zqY6L+eKR&$6Y~jY@cdUrzz4DL-8g>#k+;lc<0U|uG(=10=tp;&rcu^#5R4=N4{6FR z;YbgTqyP2gF-jYpak5Ythq5jO5Gu+^IOA9I=8UIfKR=U1bw(EtD~ zvM#Vpqes*R=D$(KiZI(@mR|yD(~r6V53+@|MSR@`feIxf03Ost9+Gn9j7Y4cE&<9? zN$23&PgAwG447aL%dTy0aIFxl9Wv1UrO(Zrw#`s6UthSX{(>_PP=xP>dm3=}b)m#4+k}4y_rzVpW`FQa~XB~i;S>bTO&Gr?PQZAGN5(3JU ziSZI#nC0L&_5?_@-L`tfh;jSy3`#}@PInMwH}S=|C<#YN^`rRt)qMO0!e-%V+Rr=X zzzS7jKOWMBNlq)RpdF9}?oz(ha!Ri-*cba~ll{8eRD=~`)=cSqakHmPNCebSRuUJ& z`8;r!sbd_L#}SeX1G3(BZ`91LVw*Ujlb$+t*IU3MyQhl;3hn-q1RJ)ZRX^Mq-33Z$sCz4W-f2({XJf>4l(mVOYd)EWWlitxjSBVr48pQA^N80n${POyTG8US delta 1377 zcmV-n1)los6!r=siBL{Q4GJ0x0000DNk~Le0000e0000e2nGNE0F3^)ZIK};f589% z4#5Gqk!$S$00j$4L_t(oN6lDEQsYPvm1I710QPJM7A$&JM9a>-Wqt0PBod3iZ`e}C73#bTjS zmZkRdTvf8Fs#FM!b8a1?9TEKke??JbTU%Qyj{Nxe_%GGExVV_i=kpUN3AsYZjb2}0 zKS&bebeJMP1oQz(Ep#**W!u}^D!}34;YTZH%&^ThY_%swx@Zxo6_t2XKV_B|@)2VZ-Cq!d05w51 zHh`Qlv96239g(h=Z8Wn6?YW&4Is%`$Fr+5pO9tPdk7V(je>45XQC>rF|4m)aaRifBB;PmQ|%@PZuLlf&c@_W=m<4R`x?(cbGQ|av$OsTbaw2 zDmM|5Hw6@E=h;GH=aM;Outkst7%TFT8u@%ZS34XC^XE=_K%dkbNo=`}HF?d6Y!pgb5f=dog$y2tpxN=@Z>WtphbnAqYFhOA`!af1t=@;opFKT2{e60RIzf$o7ejP_g~yaV^3wY$f?a@cBu1i0 zKer&ZBT_o>Lm{?2e+hWaK5#@!e!4E|2T#*Vs4tFvXAlBX5b!1m$us@3o)NMzkbh;UgMElS~ zP=@h@|H&Y+P0&Mdb4Nqay^e+uUU3E+{K(2vsK2!W)Ra|7e-M;APd<7tC3yWGvXNX3 z^@l!do2O1q(4M&>FbED(2O7G3M#px<5FG>k@?p@{(;m-;Q86Hk09ONJl8t|`JAbxo z4AE&Iex0ZO`t~?Li!OeiH!Erovj{mjILP+)_WnFOJDZ?Jzl$L{UEo-OLtKnTp3P>H ztE;P%KfZn)e}kU#kw;x+{|gn7B-(uV1E@Jpu#b8jY`26Ss+ZtqCqJicRRG zCjuSysO@+y)mx0$UjPDF3@Omi4g_oMe#{n#qo2s6pl+jo_%PrVT#F#S);l{pIsm}2 znE)Qt3F`v<(}J=!!HW(GP+K@5Q=@@Fc{;#_S51^fLB82kFBAYacr~|_Y(V6m!jNV}?#U{bOeLf&mM+pp zHo*enVPA?rh-X-K4A2^V@F77Rz&q-jo0}7~7mG!P_tkH2Z)oRoA?vDeMq!T&nlBgK z-&#NqX9_-0gDhXMVNP~0-`(9sk}4A0-`{UuHvxkn3?LxLwr9MT$WVfoy?$RH&Rs+; j34&4uf~N7yBFp{*jR1b9iRD&i00000NkvXXu0mjf@0xbC diff --git a/player/images/play-dark-circular.png b/player/images/play-dark-circular.png index a9694aea908efac608b9af7226c071427fe0a5f6..f05771cc6dd4f4a66fe69a120c190154c25b64ef 100644 GIT binary patch literal 4657 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 SLWp~#!vnLhy;N!D9`}C)2{DiW delta 1533 zcmVFP1D`EZP6im;YhO zl3i}eE|y*F*2HYW5{%A;WMhH_mN8(UXu(1$6k7KA_C3{BRNj`C`Av@JJ?E6ZpFYp| z@jS;O&mMH~;zci29VI{sQv4KYe~q$CS*1j7+_;faXCcZP&_K0N&QMNnY;2TmZf^Q0 zMag8cczb)>TU=b6C@CpPQnt&=$~O4iI3-M(1a^zsu^a;eR!ns+l}dfEy1H5&jYi+& zc?F;f`V(12KcJN2qoj&#)dqvXn}I+e!SgWv!%jgR0Rs0nRp0XRvcOIQf3x} zQD+K=!(Rb+u)4bXPhjQxAjLa!d&C_9`eRj9)gN_rb^p@*4#=oIk}J)V z9TFUF{B&_~u{9Qp9R=3Eo>g(%I&d<;Pv%bDwURharqOGlMbZTl!3o1uD0F7I( z2`(Ow`+>V|kGL-&yBdUx4uePvJ-d5*f$d$9UUuJao!9)f487C=y} zp5x<-?ZI>B&K+xVe{#~Zu&^*dpkPM!CK#@7As$bv?UQ?{F9V2$>(iZ1#DpGv{P^)L z=IyPyxw%_(6|o8j%gf6@v_Da}_};&3YHH%Rd023DgiPg_)*dZT6B85G(9n=Yy=S+> zl7gv($2tK96w^_wW6v5J8?z{$nVFdZHsC@*qlz!70|E@ve-NOeU{^FdJ8O-MjHE%C z@P$Bn6$DcU1?X2KOugfv-m9jkr>)z!Z(HNz;{(j`d_Xr1lV5<6CZOZ!I$sf5Spb@r zBRyjg;2JqZoLZs#zNoXa(`smFFfB)_^!a?+YSsj}Tv}SXPVLGf$rgK>)!*N5wYIi~ z*nUE$+lkhde{@kV3vgB07xkt_+)z_flXd?5d8@s>Jwz~?4_HQ3hG|uR5v7LY37Pp= zu$r5jt?uq_>+IRHAzT{5O=cuNi3KJZEaEj;)wtK|tx1nM6rVRwwus{_Tq?*Azuxo~ zCID**UZ%vBc}tk1?d`RW9Xl2xfA1~CojDQ%EWhymO7x!v3zN(h zx%r@J@2bOx4|{rgdaRa~77uxD!R~xfVjw@!q8&@O^^Zi*(96N!$9~n2PgLWklrsaj zKq@+jdw)`2_Cy+1#?LQMMEgd>y92egwJ{!bJ(AWlzYx8q2yaqM@E`}hVlT&`mm5+d zkL5;Lf4w)c-n8k?v7mHU*AdG2`Gapw-@Z%lVh!Bqy?@cXXZPC5nb}xz8!JAjsHkXD z#;>FrOTKG{_eH>ciPz$X4jq!!zQKAqN!yO4r$hzFu&yJN@dG&lWS1FsRkEDm8XFsD zn16!Ptyhj-x;Fs10pzHzBiJyLN~(cuH^Z(-e{&8aOv!QvE@Fmq^x6=Mtw$G;mt(Qm zDJ9j)TiJr#xPH@WePHI?_rT?U{gy32yQ%t=RFiI=QhJ*2UI|$G0+Z9`zP1vcm1ynl z0yA4cD#E>h%X_eBSu`5$AnH8`timE4>>Agz4q|<;_OnVrk{nx`9ACczrz_#i6|hc= zQ(fg4FeBskHsCseJ56F#Od6Y_=ptR4Jg1&d7w9qev~^<5Ej}|xJgDts{B`bpneqnA j7O>h!_wm0WJf8mmT9C8Xu)hib00000NkvXXu0mjfALH)4 diff --git a/player/images/play-dark-rounded.png b/player/images/play-dark-rounded.png index 1c5aa3ee1035b68d9c88b0a4228cef4cf5fbd77c..3f063a220677df81fe772cdae4a159b8b5d8d781 100644 GIT binary patch literal 2923 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 b^@e;hrw{u69tG?B8HD9z<&!RFmNonhQ48c9 delta 1468 zcmV;t1w;Dm6wnJHiBL{Q4GJ0x0000DNk~Le0000e0000e2nGNE0F3^)ZIK};f5HF& z4#EKyC`y0;00m`9L_t(oN6lDCOYBM%PTE;zrMb{f1uH5=tvMA9UVm+<>27JwOXxRC=s~^5$XFRfGpq{ z@d9kT7n0C5DOxCE^!N8ONnmDX=3gS{cy@n(-`(He?=CJb{ll{*=z&jd6cj3GwSNCH zf}9#FD?qF3V1>5I)Skxl=Z_koUKFed@X=w>ej4*X^ufjWFhNXO*$Bu}e=MnwWs@?f zJm@Krl&Y|Tx@B1AQnuU^0&EPmMoiw=55*0GhL(B&2R~s6K{ghEoG_8DiU3nALRXJj zq_YCLPUw7X)1>g=!2l~Nq4`3oX{91#aTiKUUFdS>B1NqC=t{}%88@=7)n&= ziGR9KhJ#qL$BQ8(1T9b;e;~CoDaUfK>m2iRLF$A2qLd+(l(~@@c~QUt=X3`NJ5>72 zcFUF+J{uuj=G0nTn@b$lg!oe@B`}|+*PtKWg!tU>t;B$@l+OrImuzo~!hWPgV22S> z(q5+s2x4NW(l_K6)eWXeyN~TGUSS4lR!@YW1uG)^Q!tNhUDP*%e@wvCG|jl~{Kp(% zWZ&N2nhy^T&G+~BroXB4fNm|AS}5-UW|xVc5L95x0+uy1!uiwFQ*e5E>aMS^gU83m z=En!$!U|DE@=IgVvh|qfPyrCm6SKZ$?e_N8ot>Szo12^F^Ye3)2R%R^fQ>JPeocZP zOAaU`$eDz$>iZ|aM|;v?MAjDgMXGqAG9ys>+9>c4EjXNP(qM~fANI>A#7}HxXH=Mo=R&d zA^7Lz1y)yA-QM1wTUc0#hKGlv{0+Sh;fg(&qSuwr&d$2+?QOTbyc~^Ue&z_xQ+@H(%P}C*KT54`y%r?oOAArK~5$gFMria$%GPX5IC16u5o=!;e)|nFX zBn~9LA_y3XGQt4neByOr7h{5)Jrl&Hmp~>^P(wCQVx9^%wC^n68fS17oG9pBQ9(IN`zxE0W_!* z<^||417<6N4;>~zVxfdY%?t$0Q_+>6IKr7Y(ETef*(B0Pc{OH6f|%T@K*yGdgo+Qc zMazI6iAK8If{)z`nIPCE1mVR5KmdAjgdMbGe@_B8oDsV1$022^Kst@K3O*ar7+4@Y z>^p60@eN2aOcevTMc*gL1NcTw|8aM5jxS4J^osuZ`H4pwaRVz^1ndzA^MLy63Jsq4 z0PwhkpijvO2L=W}DmG#h6BFqtU=TC|Uo_*jiBzG=7l>OG(NY4S6oa5>{Iv*z{{dfT W5?x=5{wTBn0000 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 Ca`wvr delta 1524 zcmV*n~+2e5Q$Qy98kGLQJMo12&ocM)k8%Oh!f)C z^*?|(AXV~_NRcQxaNtJO4?rN=+)7h|pnxEJ8O*ma2Ae+5?%1^nfwjF(A89l@GxqM! z@9ezuzT*U43>qCB)uXbam{BY!eljk1e!h~lcRulKVv8bu5!pl+euL%Dr)bY%2+ zJcg5#lZ4aL)5P=hbA57hvNt6q#b-1c&(qSnw*^c0jxRDUj+pPocd9eSJzGdm4L!(&-V8A?kq1am%(CZ z0Q*K6jZOe{z>22g;^NtijEs47y#ypJf2rOJ2spm@Evu`mH!+=`0_-bgL>mEgC@U-L z&!VEDRWx4!a$gw<*QJ1jfAjnOZHtSGHxCXD3IO-D7}8XLd~PzCoMmNYYp|;0GDyDA zVG`5YBH&E%f}?nEx7(dOH#b*^W-Fe}#TBM^0HJ4`R$;mCMPJ;+XB;0N=dP@*e1zsx zrT0ID0pb@t;DQkET-+>oQFvA>TvyG`&Q2v5sztY~AfTAq2Ci0oe?ij)p+8(0kb7zKLeAz zU;-Ar<#M^q+#y8!TH!|a`T2P}7L#3cX(BM}EG;eF$7MO&0l<438yjEZ7pUI878VwC z&CSiy)YOy?Zo-a0e?g4ICNO-9z=N;ZZMOBCUN&G>)z+i%dAaWD>XKSoS|m7KJKkYU ztm*Z7Gf;g@Ho{pEGNr(@@}OE>{CsC;r}X5>6I#^1zrP<7DDLeCQxYz%1T@2&8&R_( zX0^AsOJieWy6Nd@I~;Jdp!9LBL5sM!}lMX0u6ce{F3tD7MUKAoUD_QCkRT zVkC^3k*}ue?(UYFnwq5X@o_s`SR|l8l9@~d)FF`M3#BS}4G7;6SJ$hfqeFt15G_>- z-kgB$q@<){)C}F@B8S5vm6n!@mMW!ziLS;?z+J>-9@LsFl2G;b_DUloBT{vBwasic z+eEh-d6;yef8HhFA+s;k+IU)si)3(cP^zu1wPj{zMgkVl4Z(CsKo?U(CSUSU46@tp zQh$HH)Y#Z)!<5=EO=2W6kp)aNm`g9Rsu_d9K<}+u2+_rrqQ1U93EPyeva-^K=@Grr ziD@c=mwB=Uo-&)4m-h=(L(#rgiHSF8VHsrn0OlO$Lkei!321q9Q z!C+wy?+T|mN3^e1d3m{RU|>M1s;bfF6SkW zBbf2yi+##S{KRL#E$x()l&qooqhJW?q6G=bgPa#Ujz~>SJ&UI$d2hh^D=I3M(d7Be zJBLW`4N0)7(^0iD<7ZNhmHk*5(MEt9;MH>zA7EkuzNRbTYDIT!2biG(B=@#Q*duFU zQKQr8yo0DW2e1r_xUqAb*W6NgU;48&K>4uGpJFTh16!dVV1XmxpewmbssRHM(_Rj^ zO8c1dyT~09ktX_)&sKVwt9fSw0cad?c7;rRAf1Gb%(J z6)mh3vNSNKnJgbEshIe-QW6<$6nvC*Kk(jlS9je%&Ue;6XYc*nXP@)^_P$8)c3TD0 zg@HhzRqlJS`+=FM96zoAzL(k~wgZzIdB2+rsOq);M?m-?0=*9n0zJ&t{1O5IWR3W} zhshui{JCc{FdCFtOMvXNnojUEQ2*({wiX*v}pI$8)8%h_}d1cHMZ(rDTA^~_DA zG-SmZ2n#DYd5)$f`qWc%d`c{JY8dVGn*6lex?z&vBOS<%i;HWmS^;euO{dq&g@Gs9|9-;=Tni6$(Mh$l*DQiYy%FK5 zPA_3Fe;?4oS(9n>V$xMYh^l2 z=|p{2+_u^QfnU-3OOn`p*E=C`rFf=>G&CH17dad_Ki@!`OybVXA@w-%4{%53(bGn{ zWh?EOm$}axeMf1@AL_C50{Bp-(ew6^x#@$CA0Cd3<-PEa<~$JvQk|*RS=Bo}US9)^ z9(7r~Gj-djJ}7hQxy8tovu0R}&@j@1Ya-SY*Q{&)Q!=EH@L-F%m3%cEHHVqE?r?c4 zKSAQU`DV;#Tlr>WU`Ew_NxZXqUw`c4a&i?OR9EVmW_G|%+r7t*4TP3yiX^FT@ac4} zymN5h8B~nma8AZyFLrIu3&HbGj$d@ymVIzpu_8~AbfNmq>>~qsxaG}3=NJ$bAZ(EdEJtUo_Syl5a+lZ!?sz`LY94K( zpKn_s7hU>lGB4x36EbQAF!*3zBl_aCSxX1HS?8^{M5e4r!La&`Uv^mo{z4;pv{X*6 zPtK!d*DQ4o`fjlb(3?zt>!E2$?mgvYluiQjmpG5s; zP}pXw`(dflJBh$@w@MMDWmmr=tU&7Z>NWe5oJWIGa@Oz5TL5Frl%Da(Xug`T@#FiA z#H$9Y0b4hf?IcM2Q+`fNfl(vzvT~UWinYnlX;tI)0yfWA#C!7M4(!s#z>S`9y6SNQ zD8o@uVn>3F0ViP$q`qhHMG3D>|Ct))N)AR!-<@XC@kEp*vL=J{g!+DN(!$+on>*}n zE{k@MK_<+Fd$}TWGN|2oM*JmW{BO0YnX$(&zrM?q8ftLd$3W}4tc%IpBw{zT^f$nX z{0N3jB&?#kNv|rUe%e?i(_fLj*slMKmK=whIY4lrV)+s9V6;OnZpM#rl@ltvIK)Z=|KlopI(jiv`P)l2^V9E=@N9|OR zd$P-6{?|N9q$&^+MDB~0T0-j*l{uaf`xqg(a5765xHl%F=W;Tei7M3VR;B!@E0GG3 z3v?yd0!oOkKEPl(SuFZi zb~&%OZaG=bJR-p&8YfpJt379e&2$u9PF5X@p?S1{HZi0=VXVYocq6Gp5a$XAc$J$Q z*q(b#dTrKXvoZsMAL{%d-E{}Lf>emRCAtXWJCRS3O4V2R1wkA#B-~d?$AwQ3#2yBr zT_tfM1+dD9T=twQ$?J9=gmhOQ>`GO7A+Kr!-pZr_2cwj}e~q_=XAqQu&Bdr!N_2C{ zJgja|ODGP72U~WKrygrd{M~TUYrr*r_xLG`lS=`Rw%BhhIS{pV$5Vxqe@&~}?X#vbhPZ)#PuPk+s`;WBQ5ZPQ*qOo%*c4PDdm$;OZqJNDn- zHSxt{bzQXm`)Re`Q)^lz;m2NvfFoy0VnX4}wybL4CT;YZUB^J;7=S^{)V^#ZpqE)j z5AkdlKpPFvt<6_38<;VnCF=85ckJJfJL7s2kl_5BNn;h6O<$t?bJYlp2wSGI^~+R- zfAb0>(EJaPF_wwU$-`vz!k#Y%>>YhhGQN`G41H7#H>43n@vd4hCL}yc6;qqBWo84# z_v9w>>GOo^<{Y?9|qt{$EP&l-ZeLjC9 zEdz6Gbo`?WXEvgKN;~enBC3^BP&J-^XQS;Q?e$65&EI-_yGEJoC-re|C|s#KKODdZ z`h-Wt167`766W~#5sDeAeAh$yFPTOk>KtY13KyysDv9MatZjj^P4YM*3H*s19e#mf z+zHM}uJp@Il+HlDGCR+5ANe2W@I*(VhZgOFa;oEVt53qQ{%hYe6Pr?f9oBod`hsD;#2zsj(~dpZ<#U!wV%oRhI4A z;=0Hw?s=14XgoZ8gtalH!`!r>_?6gm zVoljaMffxrOMcd9j4i24DIo=S^rT7ZBWE-BF8Z0Lp!&b>4kL!V0r&sYQg*~_*k!Z? Yw!9^7$`Zbt0$N{?JI)(h4I?WO{$USM_?TJ2R8X;=x}}O?6kjdhhpMeRNlu z?;U~51@rUsxzC?J2f1ACa;;V~f3vf*rdq9<#l=Olu&`j3mzPZt*e3OQU2@1u-U@LX zORnwsoX_XYx^?SJcXzkhuwjGQxN+k$?(>kj-rL(dy=l{?`2G9$xt%+A))RtLQ&RyN z_be?f{RyIGdV0D*au5`9U%!48qH2KPvl?y%%F+osq_QLiBBrOO$AE;8f9&t?FM%j! z#&+%6H3j>@z`#J9X#e=}WAEJD+#e+K;lqc*>({SYX0~nHW)2)UFzm#>DPFvIQHCuc z2~x~UAYa?EWy^Jto1u&lMD+5A&~G5v3u1*=uU?7ghYlSYW=FCUV_XXBRXTF6ClLXg zw*z&#ju+iepFSq=e`5v*2TOf@eT?oE5T2I-gz#TRclNSdCnqNj1ac%tHGvv- z7d4ZN8c9r{O3B$e*AS2=G?MX%M6nz?kGkt-+5GtPPs5uxZ_Y6=pFVvma5w?K^G^tJ ztW_!%=~jf-?Ay2RB-Qd4P>Y5K;5d?G>AL?k8nqVk=93s$$#D9AUoNiuqkPI;I z(8a?Dl)*|OladgEoEYd{vvuoM2ro;qILpd55EB{lNrYWFmImp{fDVgC4JWJs|7;|e zV0-)a?VJx+Xh;bwZjp@$ z(J9%rIR%HDU6MNlF2xbZ3!QI$8HKuYY*>42Z z7=RepM16%O1oho22y7+iUG`n8WbpdBtT&h~39m$%D9o&}xIMbic;u-@&w!XNdY?Uj)={ot4obR!f!-k{7GWwkz}q!eO$P3Av$&H zRP^A%gCZ9Ef1*0A@`T^UFDI-`yqj}~Dauo0P$8?mw6Vg?kR_JI`Sa(a`}gmgCr_TR z+a;o{oFfUj_f*P)?xA_#`Yrwk!Z3`^ojWH)F;3@ZrNDHbP-`L2pL5!NH=<7;IoI-AQng+`fG~ zI(F<>h|N&Q?E`frvld{ksT~r6cBjUKH4m3~(u5ZcwNhFv7NeUtZ$^beA;hO!$lV&D zYno3ke}Op9XPXD$;1idY3_T0rpKahN(CGO}X;6(S#>U2?6DLk^qZML*;YypgPeaS? z%OI%7vMtqiVnrWP#9a8)8)@w1=ulTzm)wtzjz)Lx+=-4HITB)v8cJaXt;s2%*NL$2 zXh+*51QU{pM-6D5l4bM`9z1An-MSSWJ$jU%f9WAN;?1x+xdvodIjkTkV9s*o>Y&06 zCye2&#D+e^7Bys-WYIA(Czfc2^XwtDB?K7}?XoPnZ1nV=<$j${UePH@k@A}JNJNiU zgrJ_4C}2+h*(z&9i7?kXpIoP;5=c{P%RMCLHiQu5NmHvaM38`zXhoP{olm?Ia57Sb zf8T_BRuII3YVgrnW&?U;qX>vWM~p=-$$<9AWOIc&=Ol}hDTe*NOxt-$X$9xfg~etZ~je;BaVPOQ`*G$UG3JZx2fx%#5*;|MOo|!*8f&x9y(`xcc&lFHokd*um^UIoz`kKD1?b4Ggw;Xm$iBsG@xyVI(bc7i~LrKaE~~xDs}#ysDapuCAmlPeZRl zuRoB=%JbaQWfKWIj+9t#6RH!kLy5!d9kD%D1LwowzV9Y;lyz6XEjaubnHKwbS);pw z$!vYRc2cog)yR$=P4-)CWY0LU@+3nM;uz^rOTr78d;Uz9yG^(p>CII^aKw&%e>9H` z%>nn|Gv$|F9{e9lF1IJuN^VSx6Ccb*%0uMfz#gb;kZmmRvV6I{YeE{$AakiJLo;qI zSo-h;1L1N{MlnkLB#`jONgSidPZIw98RKyK#fJCGUkyQuwqQo;n^+a^Y^GL?cQ+9d zck$oQk*rxJx;$DURQ4kS1!bI5v`vzZOc^Fp(%~nOoo3OAwrUi#C-QhB{O#MVPiJmj z!r1PQrZ7@#*kp_s{_b?dv|fV=l$}*lZ zcrJvU_ejq8SrzpFX%dSC^wm?mq2;e9lRmEg0%OTZ6yqU%u%uZ@jrD<68J9X*;pU!& z3sMMM0dhwtRLW;{G)|;XxxRU3g)_$HQ8*b)Dda+EpfHP1T}ir^BwUuu>IKO0&C&rp z@5_(sQm48BL_?axqfZ*G{lP#F0mgM{>=7dOP7Mwv%LfFNwlb16ak}9+lx!=Is`150 z$~hHf5f^$B7(wpLL-cLW8qpd0g8(j;)DPEohno+K#~hyz_AaV+s#pXq?5kKba>w8T z6g#U&YB&=O7<1LQ-WSXEy0JzFS|hb)xN`Iupo~W{E}~d_x216DfZ>{~5hkZ@V|eYF zqY6L+eKR&$6Y~jY@cdUrzz4DL-8g>#k+;lc<0U|uG(=10=tp;&rcu^#5R4=N4{6FR z;YbgTqyP2gF-jYpak5Ythq5jO5Gu+^IOA9I=8UIfKR=U1bw(EtD~ zvM#Vpqes*R=D$(KiZI(@mR|yD(~r6V53+@|MSR@`feIxf03Ost9+Gn9j7Y4cE&<9? zN$23&PgAwG447aL%dTy0aIFxl9Wv1UrO(Zrw#`s6UthSX{(>_PP=xP>dm3=}b)m#4+k}4y_rzVpW`FQa~XB~i;S>bTO&Gr?PQZAGN5(3JU ziSZI#nC0L&_5?_@-L`tfh;jSy3`#}@PInMwH}S=|C<#YN^`rRt)qMO0!e-%V+Rr=X zzzS7jKOWMBNlq)RpdF9}?oz(ha!Ri-*cba~ll{8eRD=~`)=cSqakHmPNCebSRuUJ& z`8;r!sbd_L#}SeX1G3(BZ`91LVw*Ujlb$+t*IU3MyQhl;3hn-q1RJ)ZRX^Mq-33Z$sCz4W-f2({XJf>4l(mVOYd)EWWlitxjSBVr48pQA^N80n${POyTG8US delta 1537 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 KAv|&nxcEOj`3Hjl literal 0 HcmV?d00001 diff --git a/player/images/volume-controls-light.png b/player/images/volume-controls-light.png new file mode 100644 index 0000000000000000000000000000000000000000..6d9302933b85649db32feb3920d0e1dbd514a296 GIT binary patch literal 7032 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 Radio Player Output Arguments: '; echo print_r( $args, true ) . ''; } @@ -220,7 +221,7 @@ function radio_station_player_output( $args = array() ) { $instance = 0; if ( isset( $args['id'] ) && ( '' != $args['id'] ) ) { $id = abs( intval( $args['id'] ) ) ; - if ( $instance > 0 ) { + if ( $instance > -1 ) { $instance = $id; } } @@ -230,8 +231,8 @@ function radio_station_player_output( $args = array() ) { } } $radio_player['instances'][] = $instance; - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo 'Player Instance: ' . $instance . ' - Instances: ' . print_r( $radio_player['instances'], true ) . ''; + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + echo 'Player Instance: ' . esc_html( $instance ) . ' - Instances: ' . esc_html( print_r( $radio_player['instances'], true ) ) . ''; } // --- filter player output args --- @@ -241,9 +242,8 @@ function radio_station_player_output( $args = array() ) { } // --- maybe debug output arguments --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo 'Parsed Radio Player Output Arguments: '; - echo print_r( $args, true ) . ''; + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + echo 'Parsed Radio Player Output Arguments: ' . esc_html( print_r( $args, true ) ) . ''; } // --- set instanced container IDs --- @@ -256,30 +256,34 @@ function radio_station_player_output( $args = array() ) { $classes[] = 'default-player'; } $class_list = implode( ' ', $classes ); - $html['player_open'] = '
    ' . PHP_EOL; + $html['player_open'] = '
    ' . "\n"; // --- set Player container --- $classes = array( 'radio-container', 'rp-audio', 'rp-audio-stream' ); $classes[] = $args['layout']; $classes[] = $args['theme']; $classes[] = $args['buttons']; + // 2.5.0: added filter for radio container class + if ( function_exists( 'apply_filters' ) ) { + $classes = apply_filters( 'radio_station_player_container_classes', $classes, $args, $instance ); + } $class_list = implode( ' ', $classes ); - $html['player_open'] .= '
    ' . PHP_EOL; - $html['player_open'] .= '
    ' . PHP_EOL; - $html['player_close'] = '
    ' . PHP_EOL; + $html['player_open'] .= '
    ' . "\n"; + $html['player_open'] .= '
    ' . "\n"; + $html['player_close'] = '
    ' . "\n"; // --- set interface wrapper --- - $html['interface_open'] = '
    ' . PHP_EOL; - $html['interface_close'] = '
    ' . PHP_EOL; + $html['interface_open'] = '
    ' . "\n"; + $html['interface_close'] = '
    ' . "\n"; // --- Station Info --- - $html['station'] = '
    ' . PHP_EOL; + $html['station'] = '
    ' . "\n"; // --- station logo image --- $image = ''; $classes = array( 'rp-station-image' ); if ( ( '0' != (string)$args['image'] ) && ( 0 !== $args['image'] ) && ( '' != $args['image'] ) ) { - $image = '' . PHP_EOL; + $image = '' . "\n"; if ( function_exists( 'apply_filters' ) ) { // 2.4.0.3: fix atts to args in third filter argument $image = apply_filters( 'radio_station_player_station_image_tag', $image, $args['image'], $args, $instance ); @@ -293,103 +297,107 @@ function radio_station_player_output( $args = array() ) { } $class_list = implode( ' ', $classes ); $html['station'] .= '
    '; - $html['station'] .= $image . '
    ' . PHP_EOL; + $html['station'] .= $image . '
    ' . "\n"; // --- station text display --- - $html['station'] .= '
    '; + $html['station'] .= '
    ' . "\n"; // --- station title --- $station_text_html = '
    '; if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { $station_text_html .= esc_html( $args['title'] ); } - $station_text_html .= '
    ' . PHP_EOL; + $station_text_html .= '
    ' . "\n"; // --- station timezone / location / frequency --- - // TODO: add timezone and/or frequency display ? - $station_text_html .= '
    ' . PHP_EOL; - $station_text_html .= '
    ' . PHP_EOL; + // 2.5.0: add filters for timezone / frequency / location display + // TODO: add timezone / frequency / location attributes ? + $timezone_display = isset( $args['timezone'] ) ? $args['timezone'] : ''; + $timezone_display = apply_filters( 'radio_player_timezone_display', $timezone_display, $args, $instance ); + $station_text_html .= '
    ' . esc_html( $timezone_display ) . '
    ' . "\n"; + + $frequency_display = isset( $args['frequency'] ) ? $args['frequency'] : ''; + $frequency_display = apply_filters( 'radio_player_frequency_display', $frequency_display, $args, $instance ); + $station_text_html .= '
    ' . "\n"; + + $location_display = isset( $args['location'] ) ? $args['location'] : ''; + $frequency_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); + $station_text_html .= '
    ' . "\n"; $html['station'] .= $station_text_html; - $html['station'] .= '
    ' . PHP_EOL; + $html['station'] .= '
    ' . "\n"; - $html['station'] .= '
    ' . PHP_EOL; + $html['station'] .= '
    ' . "\n"; // --- Stream Play/Pause Control --- - $html['controls'] = '
    ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; - // $html['controls'] .= ' ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; - $html['controls'] .= '
    ' . PHP_EOL; + $html['controls'] = '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; + // $html['controls'] .= ' ' . "\n"; + $html['controls'] .= '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; // --- Volume Controls --- - $html['volume'] = '
    ' . PHP_EOL; + $html['volume'] = '
    ' . "\n"; // --- Volume Mute --- - // amplitude-mute - $html['volume'] .= ' ' . PHP_EOL; + $html['volume'] .= '' . "\n"; // --- Volume Decrease --- - $html['volume'] .= ' ' . PHP_EOL; + // 2.5.0: fix to attribute typo area-label + $html['volume'] .= '' . "\n"; // --- Custom Range volume slider --- - $html['volume'] .= '
    ' . PHP_EOL; - $html['volume'] .= '
    ' . PHP_EOL; - $html['volume'] .= ' ' . PHP_EOL; - $html['volume'] .= '
    ' . PHP_EOL; - $html['volume'] .= '
    ' . PHP_EOL; - - // --- jPlayer/Howler volume bar slider --- - // $html['volume'] .= '
    ' . "\n"; + $html['volume'] .= '
    ' . "\n"; + $html['volume'] .= '
    ' . "\n"; // --- Volume Increase --- - $html['volume'] .= ' ' . PHP_EOL; + $html['volume'] .= '' . "\n"; // --- Volume Max --- - $html['volume'] .= ' ' . PHP_EOL; + $html['volume'] .= '' . "\n"; - $html['volume'] .= '
    ' . PHP_EOL; + $html['volume'] .= '
    ' . "\n"; // --- dropdown script switcher for testing --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - $html['switcher'] = '
    ' . PHP_EOL; + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + $html['switcher'] = '
    ' . "\n"; $html['switcher'] .= '
    *
    '; - $html['switcher'] .= '' . "\n"; $scripts = array( 'amplitude' => 'Amplitude', 'jplayer' => 'jPlayer', 'howler' => 'Howler' ); foreach ( $scripts as $script => $label ) { $html['switcher'] .= '' . "\n"; } - $html['switcher'] .= '' . PHP_EOL; - $html['switcher'] .= '
    ' . PHP_EOL; + $html['switcher'] .= '' . "\n"; + $html['switcher'] .= '
    ' . "\n"; } // --- Current Show Texts --- // TODO: add other show info divs ( with expander ) ? - $show_text_html = '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - $show_text_html .= '
    ' . PHP_EOL; - - $html['show'] = '
    ' . PHP_EOL; - $html['show'] .= $show_text_html; - $html['show'] .= '
    ' . PHP_EOL; - - // --- Progress Bar --- + $show_text_html = '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + $show_text_html .= '
    ' . "\n"; + + $html['show'] = '
    ' . "\n"; + $html['show'] .= $show_text_html; + $html['show'] .= '
    ' . "\n"; + + // --- Progress Bar --- // (for files - not implemented yet) /* $html['progress'] = '
    '; $html['progress'] .= '
    '; @@ -410,11 +418,11 @@ function radio_station_player_output( $args = array() ) { // $html['no-solution'] .= '
    ' . PHP_EOL; // --- Current Track --- - $html['track'] = '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; + $html['track'] = '
    ' . "\n"; + $html['track'] .= '
    ' . "\n"; + $html['track'] .= '
    ' . "\n"; + $html['track'] .= '
    ' . "\n"; + $html['track'] .= '
    ' . "\n"; // --- set section order --- $section_order = array( 'station', 'interface', 'show' ); @@ -447,24 +455,25 @@ function radio_station_player_output( $args = array() ) { $control_order = apply_filters( 'radio_station_player_control_order', $control_order, $args, $instance ); } - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { - echo ''; - echo ''; + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + echo 'Section Order: ' . esc_html( print_r( $section_order, true ) ) . '' ."\n"; + echo '' . esc_html( print_r( $control_order, true ) ) . '' . "\n"; } // --- set alternative text sections --- // 2.4.0.2: added for alternative display methods // 2.4.0.3: added missing function_exists wrappers - $station_text_alt = '
    ' . $station_text_html . '
    ' . PHP_EOL; + $station_text_alt = '
    ' . $station_text_html . '
    ' . "\n"; if ( function_exists( 'apply_filters' ) ) { $station_text_alt = apply_filters( 'radio_station_player_station_text_alt', $station_text_alt, $args, $instance ); } - $show_text_alt = '
    ' . $show_text_html . '
    ' . PHP_EOL; + $show_text_alt = '
    ' . $show_text_html . '
    ' . "\n"; if ( function_exists( 'apply_filters' ) ) { $show_text_alt = apply_filters( 'radio_station_player_show_text_alt', $show_text_alt, $args, $instance ); } - // --- create player from sections --- + // --- create player from html sections --- + $html = apply_filters( 'radio_station_player_section_html', $html, $args, $instance ); $player = $html['player_open']; foreach ( $section_order as $section ) { if ( 'interface' == $section ) { @@ -514,7 +523,7 @@ function radio_station_player_shortcode( $atts ) { } // --- maybe debug shortcode attributes -- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Passed Radio Player Shortcode Attributes: '; echo esc_html( print_r( $atts, true ) ) . ''; } @@ -593,6 +602,7 @@ function radio_station_player_shortcode( $atts ) { // --- set default atts --- // 2.4.0.1: add player ID attribute + // 2.5.0: added block and popup attribute $defaults = array( 'url' => '', 'format' => '', @@ -609,11 +619,13 @@ function radio_station_player_shortcode( $atts ) { 'default' => false, 'widget' => 0, 'id' => '', + 'block' => 0, + 'popup' => 0, ); - // --- unset attribites set to default --- + // --- unset any attributes set to default --- foreach ( $atts as $key => $value ) { - if ( 'default' == $value ) { + if ( 'default' === $value ) { unset( $atts[$key] ); } } @@ -635,7 +647,7 @@ function radio_station_player_shortcode( $atts ) { } // --- maybe debug shortcode attributes -- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Combined Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } @@ -679,8 +691,8 @@ function radio_station_player_shortcode( $atts ) { } // DEV TEMP: allow default script override via querystring - // if ( isset( $_REQUEST['script'] ) && in_array( $_REQUEST['script'], $scripts ) ) { - // $atts['script'] = $_REQUEST['script']; + // if ( isset( $_REQUEST['script'] ) && in_array( sanitize_text_field( $_REQUEST['script'], $scripts ) ) ) { + // $atts['script'] = sanitize_text_field( $_REQUEST['script'] ); // } // --- check script override constant --- @@ -706,7 +718,7 @@ function radio_station_player_shortcode( $atts ) { } // --- maybe debug shortcode attributes -- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Parsed Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; } @@ -744,17 +756,55 @@ function radio_station_player_ajax() { // --- sanitize shortcode attributes --- $atts = radio_station_player_sanitize_shortcode_values(); - if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { + // 2.5.0: check for popup attribute + $popup = ( isset( $atts['popup'] ) && $atts['popup'] ) ? true : false; + // 2.5.0: clear widget/block/popup attributes + $atts['widget'] = $atts['block'] = $atts['popup'] = 0; + + // --- debug shortcode attributes --- + // if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { echo 'Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; - } + // } // --- output head --- + // 2.5.0: buffer head content to maybe replace window title tag echo '' . "\n"; - wp_head() . "\n"; - echo '' . "\n" . '' . "\n"; + ob_start(); + wp_head(); + $head = ob_get_contents(); + if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { + if ( stristr( $head, '' ) + 1; + $chunks = str_split( $head, $posa ); + unset( $chunks[0] ); + $head = implode( '', $chunks ); + $posb = stripos( $head, '' ) + strlen( '' ); + $chunks = str_split( $head, $posb ); + unset( $chunks[0] ); + $after = implode( '', $chunks ); + $head = $before . "\n" . '' . esc_html( $atts['title'] ) . '' . "\n" . $after; + } else { + $head .= '' . esc_html( $atts['title'] ) . '' . "\n"; + } + } + echo $head . "\n"; + echo '' . "\n"; + + // --- open body --- + echo '' . "\n"; // --- output widget contents --- - echo '
    ' . "\n"; + // 2.5.0: maybe add popup player class + echo '' . "\n"; @@ -765,7 +815,13 @@ function radio_station_player_ajax() { // --- maybe add background color --- if ( isset( $atts['background'] ) ) { - echo '' . "\n"; + $background = $atts['background']; + } else { + // 2.5.0: fallaback to apply_filters + $background = apply_filters( 'radio_player_background_color', '' ); + } + if ( '' != $background ) { + echo '' . "\n"; } echo '' . "\n"; @@ -790,12 +846,16 @@ function radio_station_player_sanitize_shortcode_values() { 'default' => 'boolean', 'widget' => 'boolean', 'background' => 'text', + // 2.5.0: added block attribute + 'block' => 'boolean', ); + // 2.5.0: added filter for attribute keys + $keys = apply_filters( 'radio_station_player_attribute_keys', $keys ); + // --- sanitize values by key type --- $atts = radio_station_player_sanitize_values( $_REQUEST, $keys ); return $atts; - } // --------------- @@ -1091,12 +1151,14 @@ function radio_station_player_core_scripts() { // note: intentionally here after player scripts are set if ( !isset( $radio_player['enqeued_player'] ) ) { $js = radio_station_player_get_settings(); - if ( function_exists( 'wp_add_inline_script' ) ) { - // --- add inline script --- - wp_add_inline_script( 'radio-player', $js, 'before' ); - } else { - // --- print settings directly --- - echo ""; + if ( '' != $js ) { + if ( function_exists( 'wp_add_inline_script' ) ) { + // --- add inline script --- + wp_add_inline_script( 'radio-player', $js, 'before' ); + } else { + // --- print settings directly --- + echo ""; + } } $radio_player['enqueued_player'] = true; } @@ -1112,7 +1174,7 @@ function radio_station_player_enqueue_script( $script ) { $radio_player = array(); } - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Default Player Script: ' . $script . ''; } @@ -1315,11 +1377,11 @@ function radio_station_player_enqueue_howler( $infooter ) { if ( function_exists( 'add_action' ) ) { add_action( 'wp_ajax_radio_station_player_script', 'radio_station_player_script' ); add_action( 'wp_ajax_nopriv_radio_station_player_script', 'radio_station_player_script' ); -} elseif ( isset( $_REQUEST['action'] ) && ( 'radio_station_player_script' == $_REQUEST['action'] ) ) { +} elseif ( isset( $_REQUEST['action'] ) && ( 'radio_station_player_script' == sanitize_text_field( $_REQUEST['action'] ) ) ) { radio_station_player_script(); } function radio_station_player_script() { - $script = $_REQUEST['script']; + $script = sanitize_text_field( $_REQUEST['script'] ); $js = ''; if ( 'amplitude' == $script ) { // $js = file_get_contents( dirname( __FILE__ ) . '/js/amplitude' . $suffix . '.js' ); @@ -1354,6 +1416,12 @@ function radio_station_player_script() { function radio_station_player_get_settings() { global $radio_player; + + if ( isset( $radio_player['localized-script'] ) ) { + return ''; + } + $radio_player['localized-script'] = true; + $js = ''; // ---- set AJAX URL --- @@ -1454,15 +1522,16 @@ function radio_station_player_get_settings() { $js .= "'image': '" . esc_url( $player_image ) . "', "; $js .= "'singular': " . esc_js( $player_single ) . ", "; // $js .= "'suffix': '" . esc_js( $suffix ) . "', "; - $js .= "}" . PHP_EOL; + $js .= "}" . "\n"; // --- maybe limit available scripts for testing purposes --- $valid_scripts = array( 'amplitude', 'howler', 'jplayer' ); // 2.4.0.3: set single script override only - if ( isset( $_REQUEST['player-script'] ) && in_array( $_REQUST['player-script'], $valid_scripts ) ) { + // 2.5.0: fix to typo in $_REQUST['player-script'] + if ( isset( $_REQUEST['player-script'] ) && in_array( sanitize_text_field( $_REQUEST['player-script'] ), $valid_scripts ) ) { // 2.4.0.3: only allow admin to override script for testing purposes if ( function_exists( 'current_user_can' ) && current_user_can( 'manage_options' ) ) { - $player_script = $_REQUEST['player-script']; + $player_script = sanitize_text_field( $_REQUEST['player-script'] ); } } $scripts = array( $player_script ); @@ -1481,7 +1550,7 @@ function radio_station_player_get_settings() { // 2.4.0.3: allow for admin-only fallback script override if ( isset( $_REQUEST['fallback-scripts'] ) ) { if ( function_exists( 'current_user_can' ) && current_user_can( 'manage_options' ) ) { - $fallback_scripts = explode( ',', $_REQUEST['fallback-scripts'] ); + $fallback_scripts = explode( ',', sanitize_text_field( $_REQUEST['fallback-scripts'] ) ); if ( count( $fallback_scripts ) > 0 ) { foreach ( $fallback_scripts as $i => $fallback_script ) { if ( !in_array( $fallback_script, $valid_scripts ) ) { @@ -1511,7 +1580,7 @@ function radio_station_player_get_settings() { } // $js .= "'media': '" . $radio_player['media_script']['url'] . '?version=' . $radio_player['media_script']['version'] . "', " // $js .= "'elements': '" . $radio_player['elements_script']['url'] . '?version=' . $radio_player['elements_script']['version'] . "', "; - $js .= "}" . PHP_EOL; + $js .= "}" . "\n"; // --- set player script supported formats --- // TODO: recheck supported amplitude formats ? @@ -1527,7 +1596,7 @@ function radio_station_player_get_settings() { $js .= "'jplayer': ['mp3','m4a','webm','oga','rtmpa','wav','flac'], "; $js .= "'amplitude': ['mp3','aac'], "; // $js .= "'mediaelements': ['mp3','wma','wav'], "; - $js .= "}" . PHP_EOL; + $js .= "}" . "\n"; // --- set debug mode --- $debug = false; @@ -1536,7 +1605,7 @@ function radio_station_player_get_settings() { } elseif ( function_exists( 'apply_filters' ) ) { $debug = apply_filters( 'radio_station_player_debug', $debug ); } - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { $debug = true; } if ( defined( 'RADIO_PLAYER_DEBUG' ) ) { @@ -1559,10 +1628,8 @@ function radio_station_player_get_settings() { $js .= "radio_data.state.loggedin = " . esc_js( $loggedin ) . ";" . "\n"; // ---- maybe set play state --- - $playing = 'false'; - if ( isset( $state['playing'] ) && $state['playing'] ) { - $playing = 'true'; - } + // 2.5.0: set playing variable in single line + $playing = ( isset( $state['playing'] ) && $state['playing'] ) ? 'true' : 'false'; $js .= "radio_data.state.playing = " . esc_js( $playing ) . "; " . "\n"; // --- maybe set station ID --- @@ -1590,7 +1657,7 @@ function radio_station_player_get_settings() { $js .= "radio_data.state.mute = " . esc_js( $player_mute ) . "; " . "\n"; // --- set main radio stream data --- - $js .= "radio_data.state.data = {};" . PHP_EOL; + $js .= "radio_data.state.data = {};" . "\n"; if ( function_exists( 'apply_filters' ) ) { $station = ( isset( $state['station'] ) ) ? $state['station'] : 0; // note: this is the main stream data filter hooked into by Radio Station plugin @@ -1662,10 +1729,10 @@ function radio_station_player_state() { } // --- get user state values --- - $playing = $_REQUEST['playing']; - $volume = $_REQUEST['volume']; - $station = $_REQUEST['station']; - $mute = $_REQUEST['mute']; + $playing = sanitize_text_field( $_REQUEST['playing'] ); + $volume = sanitize_text_field( $_REQUEST['volume'] ); + $station = sanitize_text_field( $_REQUEST['station'] ); + $mute = sanitize_text_field( $_REQUEST['mute'] ); // --- sanitize user state values --- $playing = $playing ? true : false; @@ -2244,10 +2311,7 @@ function radio_station_player_get_default_script() { // --------------------- function radio_station_player_enqueue_styles( $script = false, $skin = false ) { - $suffix = '.min'; - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $suffix = ''; - } + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; // --- get default if not passed by shortcode attribute --- if ( !$script ) { @@ -2273,7 +2337,7 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { } */ // --- debug script / skin used --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Script: ' . esc_html( $script ) . ' - Skin: ' . esc_html( $skin ) . '' . "\n"; } @@ -2304,17 +2368,28 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { // --- enqueue player control styles inline --- $control_styles = radio_station_player_control_styles( false ); - wp_add_inline_style( 'radio-player', $control_styles ); + if ( '' != $control_styles ) { + wp_add_inline_style( 'radio-player', $control_styles ); + } } else { // --- output style tag directly --- $url = 'css/radio-player' . $suffix . '.css'; - if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + if ( defined( 'RADIO_PLAYER_URL' ) ) { + $url = RADIO_PLAYER_URL . $url; + } radio_station_player_style_tag( 'radio-player', $url, $version ); + + // 2.5.0: added missing non-WP control style output + $control_styles = radio_station_player_control_styles( false ); + if ( '' != $control_styles ) { + echo ''; + } + } // --- debug skin path / URL --- - if ( isset( $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Skin Path: ' . esc_html( $path ) . '' . "\n"; echo 'Skin URL: ' . esc_html( $url ) . '' . "\n"; } @@ -2348,7 +2423,7 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { } // --- debug skin path / URL --- - if ( isset( $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Skin Path: ' . esc_html( $path ) . '' . "\n"; echo 'Skin URL: ' . esc_html( $url ) . '' . "\n"; } @@ -2388,7 +2463,7 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { } // --- debug skin path / URL --- - if ( isset( $_REQUEST['player-debug'] ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) ) { echo 'Skin Path: ' . $path . ''; echo 'Skin URL: ' . $url . ''; } @@ -2464,12 +2539,17 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { // --------------------- function radio_station_player_control_styles( $instance ) { + global $radio_player; + // --- set default control colors --- + // 2.5.0: added empty text and background color styles $colors = array( - 'playing' => '#70E070', - 'buttons' => '#00A0E0', - 'track' => '#80C080', - 'thumb' => '#80C080', + 'text' => '', + 'background' => '', + 'playing' => '#70E070', + 'buttons' => '#00A0E0', + 'track' => '#80C080', + 'thumb' => '#80C080', ); // --- get color settings --- @@ -2478,35 +2558,67 @@ function radio_station_player_control_styles( $instance ) { $colors['buttons'] = radio_station_get_setting( 'player_buttons_color' ); $colors['thumb'] = radio_station_get_setting( 'player_thumb_color' ); $colors['track'] = radio_station_get_setting( 'player_range_color' ); - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { + $colors['text'] = apply_filters( 'radio_station_player_text_color', $colors['text'], $instance ); + $colors['background'] = apply_filters( 'radio_station_player_background_color', $colors['background'], $instance ); $colors['playing'] = apply_filters( 'radio_station_player_playing_color', $colors['playing'], $instance ); $colors['buttons'] = apply_filters( 'radio_station_player_buttons_color', $colors['buttons'], $instance ); $colors['thumb'] = apply_filters( 'radio_station_player_thumb_color', $colors['thumb'], $instance ); $colors['track'] = apply_filters( 'radio_station_player_range_color', $colors['track'], $instance ); } - // --- maybe set player container selector --- - $container = '.radio-container'; - if ( $instance ) { + // --- check for player instance --- + if ( false !== $instance ) { + + // -- set instance container selector --- $container = '#radio_container_' . $instance; + // --- get colors for container instance --- - if ( isset( $radio_player['instance-colors'][$instance] ) ) { - $instance_colors = $radio_player['instance-colors']; - foreach ( $instance_colors as $key => $instance_color ) { - if ( $instance_color && ( '' != $instance_color ) ) { - $colors[$key] = $instance_color; + if ( isset( $radio_player['instance-props'][$instance] ) ) { + $instance_props = $radio_player['instance-props'][$instance]; + foreach ( $instance_props as $key => $value ) { + if ( substr( $key, -6, 6 ) == '_color' ) { + if ( $value && ( '' != $value ) ) { + if ( substr( $value, 0, 3 ) !== 'rgb' ) { + $value = '#' . $value; + } + $key = str_replace( '_color', '', $key ); + $colors[$key] = $value; + } } } } + } else { + // 2.5.0: added check to do once only + if ( isset( $radio_player['control-styles'] ) ) { + return ''; + } + $radio_player['control-styles'] = true; + + // --- set generic container selector --- + $container = '.radio-container'; } + // 2.4.0.3: added missing function_exists wrapper if ( function_exists( 'apply_filters' ) ) { $colors = apply_filters( 'radio_station_player_control_colors', $colors, $instance ); } + $css = ''; + + // --- Player Colors --- + // 2.5.0: added for main color styling override + if ( isset( $colors['text'] ) && ( '' != $colors['text'] ) ) { + $css .= $container . " {color: " . $colors['text'] . ";}" . "\n"; + } + if ( isset( $colors['background'] ) && ( '' != $colors['background'] ) ) { + $css .= $container . " {background-color: " . $colors['background'] . ";}" . "\n"; + } + // --- Play Button --- // 2.4.0.2: fix to glowingloading animation reference - $css = "/* Playing Button */ + $css .= "/* Playing Button */ " . $container . ".loaded .rp-play-pause-button-bg {background-color: " . $colors['buttons'] . ";} " . $container . ".playing .rp-play-pause-button-bg {background-color: " . $colors['playing'] . ";} " . $container . ".error .rp-play-pause-button-bg {background-color: #CC0000;} @@ -2521,11 +2633,13 @@ function radio_station_player_control_styles( $instance ) { }" . "\n"; // --- Active Volume Buttons Color --- + // 2.5.0: added popup player button selector $css .= "/* Volume Buttons */ " . $container . " .rp-mute:hover, " . $container . ".muted .rp-mute, " . $container . ".muted .rp-mute:hover, " . $container . " .rp-volume-max:focus, " . $container . " .rp-volume-max:hover, " . $container . ".maxed .rp-volume-max, " . $container . " .rp-volume-up:focus, " . $container . " .rp-volume-up:hover, -" . $container . " .rp-volume-down:focus, " . $container . " .rp-volume-down:hover { +" . $container . " .rp-volume-down:focus, " . $container . " .rp-volume-down:hover, +" . $container . " .rp-popup-button:focus, " . $container . " .rp-popup-button:hover { background-color: " . $colors['buttons'] . "; }" . "\n"; @@ -2533,9 +2647,10 @@ function radio_station_player_control_styles( $instance ) { // ref: http://danielstern.ca/range.css/#/ // ref: https://css-tricks.fcom/sliding-nightmare-understanding-range-input/ // 2.4.0.4: added no border style to range input (border added on some themes) + // 2.5.0: added input height 100% to fix vertical slider alignment $css .= "/* Range Input */ " . $container . " .rp-volume-controls input[type=range] {"; - $css .= "margin: 0; background-color: transparent; vertical-align: middle; -webkit-appearance: none; border: none;} + $css .= "height: 100%; margin: 0; background-color: transparent; vertical-align: middle; -webkit-appearance: none; border: none;} " . $container . " .rp-volume-controls input[type=range]:focus {outline: none; box-shadow: none;} " . $container . " .rp-volume-controls input[type=range]::-moz-focus-inner, " . $container . " .rp-volume-controls input[type=range]::-moz-focus-outer {outline: none; box-shadow: none;}" . "\n"; @@ -2552,13 +2667,15 @@ function radio_station_player_control_styles( $instance ) { // --- Slider Range Track (Clickable Transparent) --- $css .= "/* Range Track */ -" . $container . " .rp-volume-controls input[type=range]::-webkit-slider-runnable-track {height: 9px; background: transparent; -webkit-appearance: none;} -" . $container . " .rp-volume-controls input[type=range]::-moz-range-track {height: 9px; background: transparent;} -" . $container . " .rp-volume-controls input[type=range]::-ms-track {height: 9px; color: transparent; background: transparent; border-color: transparent;}" . PHP_EOL; +" . $container . " .rp-volume-controls input[type=range]::-webkit-slider-runnable-track {height: 9px; background: transparent; -webkit-appearance: none; color: transparent} +" . $container . " .rp-volume-controls input[type=range]::-moz-range-track {height: 9px; background: transparent; color: transparent;} +" . $container . " .rp-volume-controls input[type=range]::-ms-track {height: 9px; color: transparent; background: transparent; border-color: transparent;}" . "\n"; // 2.4.0.3: remove float on range input (cross-browser display fix) // " . $container . " .rp-volume-controls input[type=range] {float: left; margin-top: -9px;} // --- Slider Range Thumb --- + $thumb_radius = '9px'; + $css .= "/* Range Thumb */ " . $container . " .rp-volume-controls input[type=range]::-webkit-slider-thumb { width: 18px; height: 18px; cursor: pointer; background: rgba(128, 128, 128, 1); @@ -2573,9 +2690,12 @@ function radio_station_player_control_styles( $instance ) { width: 18px; height: 18px; cursor: pointer; background: rgba(128, 128, 128, 1); border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 9px; margin-top: 0px; } -" . $container .".playing .rp-volume-controls input[type=range]::-webkit-slider-thumb {background: " . $colors['thumb'] . "}; -" . $container .".playing .rp-volume-controls input[type=range]::-moz-range-thumb {background: " . $colors['thumb'] . "}; -" . $container .".playing .rp-volume-controls input[type=range]::-ms-thumb {background: " . $colors['thumb'] . "}; +" . $container . ".rounded .rp-volume-controls input[type=range]::-webkit-slider-thumb {border-radius: 5px !important;} +" . $container . ".square .rp-volume-controls input[type=range]::-webkit-slider-thumb {border-radius: 0px !important;} +" . $container . ".playing .rp-volume-controls input[type=range]::-webkit-slider-thumb {background: " . $colors['thumb'] . "}; +" . $container . ".playing .rp-volume-controls input[type=range]::-moz-range-thumb {background: " . $colors['thumb'] . "}; +" . $container . ".playing .rp-volume-controls input[type=range]::-ms-thumb {background: " . $colors['thumb'] . "}; +" . $container . " input[type=range]::-ms-tooltip {display: none;} @supports (-ms-ime-align:auto) { " . $container . " .rp-volume-controls input[type=range] {margin: 0;} }" . "\n"; @@ -2587,25 +2707,33 @@ function radio_station_player_control_styles( $instance ) { // --- get volume control display settings --- // 2.4.1.4: added volume control visibility options - if ( function_exists( 'radio_station_get_setting' ) ) { - $volumes = radio_station_get_setting( 'player_volumes' ); - if ( !is_array( $volumes ) ) { - $volumes = array( 'slider', 'updown', 'mute', 'max' ); + // 2.5.0: added check for instance property for volume controls + if ( isset( $radio_player['instance-props'][$instance]['volume-controls'] ) ) { + $volume_controls = $radio_player['instance-props'][$instance]['volume-controls']; + } elseif ( function_exists( 'radio_station_get_setting' ) ) { + $volume_controls = radio_station_get_setting( 'player_volumes' ); + if ( !is_array( $volume_controls ) ) { + $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); } - } elseif ( function_exists( 'apply_filters' ) ) { - $volumes = array( 'slider', 'updown', 'mute', 'max' ); - $volumes = apply_filters( 'radio_station_player_volumes_display', $volumes ); } - if ( !in_array( 'slider', $volumes ) ) { + if ( function_exists( 'apply_filters' ) ) { + if ( !isset( $volume_controls ) ) { + $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); + } + $volume_controls = apply_filters( 'radio_station_player_volumes_display', $volume_controls ); + } + + // --- volume display styles --- + if ( !in_array( 'slider', $volume_controls ) ) { $css .= "\n" . $container . " .rp-volume-slider-container {display: none;}" . "\n"; } - if ( !in_array( 'updown', $volumes ) ) { + if ( !in_array( 'updown', $volume_controls ) ) { $css .= "\n" . $container . " .rp-volume-up, " . $container . " .rp-volume-down {display: none;}" . "\n"; } - if ( !in_array( 'mute', $volumes ) ) { + if ( !in_array( 'mute', $volume_controls ) ) { $css .= "\n" . $container . " .rp-mute {display: none;}" . "\n"; } - if ( !in_array( 'max', $volumes ) ) { + if ( !in_array( 'max', $volume_controls ) ) { $css .= "\n" . $container . " .rp-volume-max {display: none;}" . "\n"; } @@ -2623,7 +2751,7 @@ function radio_station_player_control_styles( $instance ) { // ------------------ // add_filter( 'style_loader_tag', 'radio_station_player_debug_skin',10, 2 ); // function radio_station_player_debug_skin( $tag, $handle ) { -// if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { +// if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { // if ( 'rp-jplayer' == $handle ) { // echo "[!Radio Player JPlayer Handle Found!]"; // } @@ -2700,3 +2828,9 @@ function esc_url( $url ) { } } +if ( !function_exists( 'sanitize_text_field' ) ) { + function sanitize_text_field( $text ) { + return $text; + } +} + diff --git a/radio-station-admin.php b/radio-station-admin.php index 181ffc8..0793673 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -1,6 +1,6 @@ parent.document.getElementById('radio-station-announcement-notice').style.display = 'none';" . PHP_EOL; - exit; } + exit; } // -------------------------- @@ -1776,3 +1786,4 @@ function radio_station_clear_plugin_options() { exit; } + diff --git a/radio-station.php b/radio-station.php index 7c9d62d..a89e863 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.0 +Version: 2.4.9 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -451,9 +451,9 @@ function radio_station_welcome_redirect() { // Plugin Deactivation Action // -------------------------- // 2.4.0.8: clear plugin updates transient on deactivation -// 2.5.0: fix to typo in deactivation hook function -register_deactivation_hook( RADIO_STATION_FILE, 'radio_station_deactivation' ); -function radio_station_deactivation() { +// 2.5.0: fix to typo in deactivation hook function and add _plugin +register_deactivation_hook( RADIO_STATION_FILE, 'radio_station_plugin_deactivation' ); +function radio_station_plugin_deactivation() { flush_rewrite_rules(); delete_site_transient( 'update_plugins' ); } @@ -528,8 +528,9 @@ function radio_station_allowed_player_origins( $origins ) { // Enqueue Plugin Scripts // ---------------------- // 2.3.0: added for enqueueing main Radio Station script -add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_scripts' ); -function radio_station_enqueue_scripts() { +// 2.5.0: change to prevent conflict with radio station theme +add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_plugin_scripts' ); +function radio_station_enqueue_plugin_scripts() { // --- enqueue custom stylesheet if found --- // 2.3.0: added for automatic custom style loading @@ -570,7 +571,7 @@ function radio_station_register_moment() { // 2.5.0: add check for registered script in WP core if ( !wp_script_is( 'moment', 'registered' ) ) { $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'moment', $moment_url, array(), '2.29.4', false ); + wp_register_script( 'moment', $moment_url, array(), '2.29.4', false ); } } @@ -668,14 +669,39 @@ function radio_station_enqueue_datepicker() { } +// --------------------------- +// Enqueue Widget Color Picker +// --------------------------- +// 2.5.0: added for widget color options +function radio_station_enqueue_color_picker() { + + // --- enqueue color picker --- + $suffix = SCRIPT_DEBUG ? '' : '.min'; + $url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true ); + + // --- init color picker fields --- + $js = "jQuery(document).ready(function() {"; + $js .= "if (jQuery('.color-picker').length) {jQuery('.color-picker').wpColorPicker();}"; + $js .= "});" . "\n"; + wp_add_inline_script( 'wp-color-picker-a', $js ); +} + // ------------------------------- // Enqueue Localized Script Values // ------------------------------- add_action( 'wp_enqueue_scripts', 'radio_station_localize_script' ); function radio_station_localize_script() { + // 2.5.0: check flag to run once only + global $radio_station; + if ( isset( $radio_station['script-localized'] ) ) { + return; + } + $js = radio_station_localization_script(); wp_add_inline_script( 'radio-station', $js ); + $radio_station['script-localized'] = true; } // ------------------- @@ -712,6 +738,13 @@ function radio_station_localization_script() { $js .= "radio.debug = false;" . "\n"; } + // 2.5.0: set separate clock debug flag + if ( isset( $_REQUEST['rs-clock-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['rs-clock-debug'] ) ) ) { + $js .= "radio.clock_debug = true;" . "\n"; + } else { + $js .= "radio.clock_debug = false;" . "\n"; + } + // --- radio timezone --- // 2.3.2: added get timezone function $timezone = radio_station_get_timezone(); @@ -806,6 +839,14 @@ function radio_station_localization_script() { $js .= ");" . "\n"; $js .= $short . ");" . "\n"; + // --- set countdown labels --- + // 2.5.0: moved here from countdown enqueue function + $js .= "radio.labels.showstarted = '" . esc_js( __( 'This Show has started.', 'radio-station' ) ) . "';" . "\n"; + $js .= "radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "';" . "\n"; + $js .= "radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "';" . "\n"; + $js .= "radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "';" . "\n"; + $js .= "radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "';" . "\n"; + // --- translated time unit strings --- $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; $js .= "radio.units.pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; "; @@ -827,12 +868,10 @@ function radio_station_localization_script() { // --- convert show times --- // 2.3.3.9: + // 2.5.0: shorten logic for convert show times $usertimes = radio_station_get_setting( 'convert_show_times' ); - if ( 'yes' === (string) $usertimes ) { - $js .= "radio.convert_show_times = true;" . "\n"; - } else { - $js .= "radio.convert_show_times = false;" . "\n"; - } + $convert_show_times = ( 'yes' === (string) $usertimes ) ? 'true' : 'false'; + $js .= "radio.convert_show_times = " . esc_js( $convert_show_times ) . ";" . "\n"; // --- add inline script --- $js = apply_filters( 'radio_station_localization_script', $js ); diff --git a/readme.txt b/readme.txt index 76baba0..0910da7 100644 --- a/readme.txt +++ b/readme.txt @@ -219,9 +219,10 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.5.0 = -* Added: Wordpress Radio Blocks (converted Widgets) -* Updated: Plugin Panel (1.2.6) +* Added: Radio Station Blocks! (converted Widgets) +* Updated: Plugin Panel (1.2.7) * Updated: Moment JS (2.29.4) with WP Loading +* Changed: Tab Schedule default date display on * Improved: Refactored Schedule Engine Class (1.0.0) * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards @@ -788,6 +789,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * https://netmix.com/radio-station-2-5-0-release-with-blocks/ * Refactored Schedule Engine Class * Improved translations and sanitization +* Numerous bugfixes and improvements = 2.4.0 = * Radio Station Stream Player Widget! diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index 44edda9..6c02fb3 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -642,7 +642,7 @@ public function process_overrides( $overrides, $start_date = false, $end_date = // 2.3.3.9: loop to allow for multiple overrides foreach ( $override_shifts as $j => $data ) { - // echo 'Shift Data: ' . print_r( $override, true ) . ''; + echo 'Override Data: ' . print_r( $data, true ) . '' . "\n"; // 2.3.3.9: ignore disabled overrides if ( !isset( $data['disabled'] ) || ( 'yes' != $data['disabled'] ) ) { @@ -2045,7 +2045,7 @@ public function check_shifts( $all_shifts, $times = false ) { // Shift Checker // ------------- // (checks shift being saved against other shows) - public function check_shift( $record_id, $shift, $scope = 'all', $all_shifts, $times = false ) { + public function check_shift( $record_id, $shift, $scope, $all_shifts, $times = false ) { // --- set channel and context --- $channel = $this->channel; @@ -3242,7 +3242,7 @@ public function translate_weekday( $weekday, $short = null ) { if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $weekday == $day ) ) ) { $weekday = $wp_locale->get_weekday( $i ); - } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + } elseif ( $short || ( is_null( $short ) && ( $weekday == $abbr ) ) ) { $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); } } diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index f397873..0360538 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -73,10 +73,13 @@ $infokeys = apply_filters( 'radio_station_schedule_list_info_order', $infokeys ); // --- start list schedule output --- -$list = '
      ' . "\n"; +// 2.5.0: append instance to element ID +$list = '
        ' . "\n"; $tcount = 0; // 2.3.0: loop weekdays instead of legacy master list +// 2.5.0: set odd/even variable +$oddeven = 'even'; foreach ( $weekdays as $weekday ) { // --- maybe skip all days but those specified --- @@ -139,8 +142,11 @@ $date_subheading .= ' ' . radio_station_translate_month( $month, true ); } - // 2.3.2: add classes + // 2.3.2: add classes to day + // 2.5.0: add odd/even class day $classes = array( 'master-list-day' ); + $oddeven = ( 'even' == $oddeven ) ? 'odd' : 'even'; + $classes[] = $oddeven . '-day'; $weekdate = $weekdates[$weekday]; if ( $weekdate == $date ) { $classes[] = 'current-day'; @@ -237,7 +243,9 @@ $terms = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG, array() ); if ( $terms && ( count( $terms ) > 0 ) ) { foreach ( $terms as $term ) { - $classes[] = strtolower( $term->slug ); + // 2.5.0: add genre- prefix to genre terms + // $classes[] = strtolower( $term->slug ); + $classes[] = 'genre-' . strtolower( $term->slug ); } } // 2.3.2: check for now playing shift diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 92348b1..3d0a318 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -80,17 +80,24 @@ $table = '
        ' . "\n"; // --- start master program table --- -$table .= '' . "\n"; +// 2.5.0: maybe add instance to element ID +// 2.5.0: set oddeven variable +$oddeven = 'even'; +$id = ( 0 == $instance ) ? '' : '-' . $instance; +$table .= '
        ' . "\n"; // --- weekday table headings row --- // 2.3.2: added hour column heading id $table .= '' . "\n"; -$table .= '' . "\n"; +// 2.5.0: set odd/even day variable +$oddeven_day = 'even'; foreach ( $weekdays as $i => $weekday ) { // --- maybe skip all days but those specified --- @@ -156,7 +163,10 @@ // --- set heading classes --- // 2.3.0: add day and check for highlighting + // 2.5.0: add odd/even day class $classes = array( 'master-program-day', 'day-' . $i, strtolower( $weekday ), 'date-' . $weekdate ); + $oddeven_day = ( 'even' == $oddeven_day ) ? 'odd' : 'even'; + $classes[] = $oddeven_day . '-day'; if ( ( $now > $day_start_time ) && ( $now < $day_end_time ) ) { $classes[] = 'current-day'; // $classes[] = 'selected-day'; @@ -169,7 +179,7 @@ // 2.3.2: added check for optional display_date attribute $table .= '' . "\n"; // --- loop schedule hours --- +// 2.5.0: set odd/even hour variable $tcount = 0; +$oddeven_hour = 'odd'; foreach ( $hours as $hour ) { // 2.3.1: fix to set translated hour for display only @@ -218,7 +230,10 @@ // 2.3.0: check current hour for highlighting // 2.3.1: fix to use untranslated hour (12 hr format bug) // 2.3.2: replace strtotime with to_time function for timezone + // 2.5.0: add odd/even hour class $classes = array( 'master-program-hour' ); + $oddeven_hour = ( 'odd' == $oddeven_hour ) ? 'even' : 'odd'; + $classes[] = $oddeven_hour . '-hour'; $hour_start = radio_station_to_time( $date . ' ' . $hour . ':00' ); $hour_end = $hour_start + ( 60 * 60 ); if ( ( $now > $hour_start ) && ( $now < $hour_end ) ) { @@ -417,7 +432,9 @@ } if ( isset( $show['genres'] ) && is_array( $show['genres'] ) && ( count( $show['genres'] ) > 0 ) ) { foreach ( $show['genres'] as $genre ) { - $divclasses[] = sanitize_title_with_dashes( $genre ); + // 2.5.0: add genre- prefix to genre terms + // $divclasses[] = sanitize_title_with_dashes( $genre ); + $divclasses[] = 'genre-' . sanitize_title_with_dashes( $genre ); } } if ( $split_id ) { diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index c2fc0d1..989d06a 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -59,8 +59,6 @@ $weekdays = radio_station_get_schedule_weekdays( $start_day ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $start_time ); -echo 'Show Schedule: ' . print_r( $schedule, true ) . ''; - // --- filter show avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'tabs' ); @@ -85,15 +83,18 @@ $start_tab = false; // 2.3.3.9: added filter for week loader control -$load_prev = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'left' ); +$load_prev = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'left', $instance ); if ( '' != $load_prev ) { - $tabs .= '
      • ' . "\n"; - $tabs .= $load_prev . "\n"; + // 2.5.0: remove ID in favour of class + $tabs .= '
      • ' . "\n"; + $tabs .= $load_prev . "\n"; $tabs .= '
      • ' . "\n"; } // --- start tabbed schedule output --- // 2.3.0: loop weekdays instead of legacy master list +// 2.5.0: set odd/even day variable +$oddeven = 'even'; foreach ( $weekdays as $i => $weekday ) { // --- maybe skip all days but those specified --- @@ -166,9 +167,13 @@ } // --- set tab classes --- - $weekdate = $weekdates[$weekday]; - $classes = array( 'master-schedule-tabs-day', 'day-' . $i ); // 2.3.3.5: add extra class for starting tab + // 2.5.0: added weekday specific class + // 2.5.0: add odd/even day class + $weekdate = $weekdates[$weekday]; + $classes = array( 'master-schedule-tabs-day', 'master-schedule-tabs-day-' . strtolower( $weekday ), 'day-' . $i ); + $oddeven = ( 'even' == $oddeven ) ? 'odd' : 'even'; + $classes[] = $oddeven . '-day'; if ( !$start_tab ) { $classes[] = 'start-tab'; $start_tab = true; @@ -183,44 +188,46 @@ // 2.3.0: added left/right arrow responsive controls // 2.3.1: added (negative) return to arrow onclick functions $tabs .= '
      • ' . "\n"; - $tabs .= '
        ' . "\n"; - $tabs .= '' . $arrows['left'] . '' . "\n"; - $tabs .= '
        ' . "\n"; - - // 2.3.2: added optional display_date attribute and subheading - $tabs .= '
        ' . "\n"; - $tabs .= '
        ' . "\n"; - if ( $atts['display_date'] ) { - $tabs .= '
        ' . esc_html( $date_subheading ) . '
        ' . "\n"; - } - $tabs .= '
        ' . "\n"; - - $tabs .= '
        ' . "\n"; - $tabs .= '' . $arrows['right'] . '' . "\n"; - $tabs .= '
        ' . "\n"; - $tabs .= '
        ' . "\n"; - // 2.3.2: add start and end day times for automatic highlighting - $tabs .= '' . "\n"; - $tabs .= '' . "\n"; + $tabs .= '
        ' . "\n"; + $tabs .= '' . $arrows['left'] . '' . "\n"; + $tabs .= '
        ' . "\n"; + + // 2.3.2: added optional display_date attribute and subheading + $tabs .= '
        ' . "\n"; + $tabs .= '
        ' . "\n"; + if ( $atts['display_date'] ) { + $tabs .= '
        ' . esc_html( $date_subheading ) . '
        ' . "\n"; + } + $tabs .= '
        ' . "\n"; + + $tabs .= '
        ' . "\n"; + $tabs .= '' . $arrows['right'] . '' . "\n"; + $tabs .= '
        ' . "\n"; + $tabs .= '
        ' . "\n"; + // 2.3.2: add start and end day times for automatic highlighting + $tabs .= '' . "\n"; + $tabs .= '' . "\n"; $tabs .= '
      • '; // 2.2.7: separate headings from panels for tab view - $classes = array( 'master-schedule-tabs-panel' ); + // 2.5.0: move panel ID to class list for selectors + $classes = array( 'master-schedule-tabs-panel', 'master-schedule-tabs-panel-' . esc_attr( strtolower( $weekday ) ) ); if ( $weekdate == $date ) { // $classes[] = 'selected-day'; $classes[] = 'active-day-panel'; } $classlist = implode( ' ', $classes ); - $panels .= '
          ' . "\n"; + $panels .= '
            ' . "\n"; // 2.3.2: added extra current day display // 2.3.3: use numeric day index for ID instead of weekday name + // 2.5.0: remove ID in favour of class $display_day = radio_station_translate_weekday( $weekday, false ); - $panels .= '
            ' . "\n"; + $panels .= '
            ' . "\n"; $panels .= esc_html( __( 'Viewing', 'radio-station' ) ) . ': ' . esc_html( $display_day ) . '
            ' . "\n"; // --- get shifts for this day --- @@ -303,7 +310,7 @@ // --- shift debug --- // 2.3.2: added shift debugging if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - if ( !isset( $shiftdebug ) ) {$shiftdebug = '';} + $shiftdebug = isset( $shiftdebug ) ? $shiftdebug : ''; $shiftdebug .= 'Now: ' . $now . ' (' . radio_station_get_time( 'datetime', $now ) . ') -- Today: ' . $today . '
            '; $shiftdebug .= 'Shift Start: ' . $shift_start . ' (' . date( 'Y-m-d l H: i', $shift_start ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_start ) . ')
            '; $shiftdebug .= 'Shift End: ' . $shift_end . ' (' . date( 'Y-m-d l H: i', $shift_end ) . ' - ' . radio_station_get_time( 'Y-m-d l H:i', $shift_end ) . ')
            '; @@ -314,7 +321,9 @@ $terms = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG, array() ); if ( $terms && ( count( $terms ) > 0 ) ) { foreach ( $terms as $term ) { - $classes[] = strtolower( $term->slug ); + // 2.5.0: add genre- prefix to genre terms + // $classes[] = strtolower( $term->slug ); + $classes[] = 'genre-' . strtolower( $term->slug ); } } // 2.3.2: add first and last classes @@ -650,26 +659,30 @@ } // 2.3.3.9: added filter for week loader control -$load_next = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'right' ); +$load_next = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'right', $instance ); if ( '' != $load_next ) { - $tabs .= '
          • ' . "\n"; - $tabs .= $load_next . "\n"; + // 2.5.0; remove ID in favour of class + $tabs .= '
          • ' . "\n"; + $tabs .= $load_next . "\n"; $tabs .= '
          • ' . "\n"; } // --- add day tabs to output --- -echo '
              ' . "\n"; -echo $tabs; +// 2.5.0: maybe add instance to element ID +$id = ( 0 == $instance ) ? '' : '-' . $instance; +echo '
                ' . "\n"; + echo $tabs; echo '
              ' . "\n"; // --- add day panels to output --- // 2.3.3.8: check for hide past shows attribute -echo '
              ' . "\n"; -echo $panels; + echo $panels; echo '
              ' . "\n"; // --- hidden iframe for schedule reloading --- @@ -678,4 +691,3 @@ if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == sanitize_title( $_GET['rs-shift-debug'] ) ) ) { echo '
              Shift Debug Info:
              ' . esc_html( $shiftdebug ) . '
              '; } - diff --git a/widgets/class-current-playlist-widget.php b/widgets/class-current-playlist-widget.php index e491dcd..178eac9 100644 --- a/widgets/class-current-playlist-widget.php +++ b/widgets/class-current-playlist-widget.php @@ -28,11 +28,13 @@ public function form( $instance ) { // 2.3.2: added AJAX load option // 2.3.3.8: added playlist link option // 2.5.0: added no_playlist text option + // 2.5.0: added playlist_title option // --- widget display options --- $title = $instance['title']; $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : true; // --- playlist display options --- + $playlist_title = isset( $instance['playlist_title'] ) ? $instance['playlist_title'] : false; $link_playlist = isset( $instance['link'] ) ? $instance['link'] : true; $no_playlist = isset( $instance['no_playlist'] ) ? $instance['no_playlist'] : ''; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; @@ -43,6 +45,10 @@ public function form( $instance ) { $label = isset( $instance['label'] ) ? $instance['label'] : false; $comments = isset( $instance['comments'] ) ? $instance['comments'] : false; + // --- get upgrade URLs --- + $pricing_url = radio_station_get_pricing_url(); + $upgrade_url = radio_station_get_upgrade_url(); + // 2.3.0: convert template style code to strings // 2.3.2: added AJAX load option field // 2.3.3.8: added playlist link field @@ -69,12 +75,10 @@ public function form( $instance ) {

              '; // --- [Pro] Dynamic Reloading --- - $pricing_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - $upgrade_url = radio_station_get_upgrade_url(); $fields['dynamic'] = '


              - ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | - ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . '

              '; // --- Hide if Empty --- @@ -87,6 +91,14 @@ public function form( $instance ) { // === Playlist Display Options === + // --- Link to Playlist --- + $fields['playlist_title'] = '

              + +

              '; + // --- Link to Playlist --- $fields['link_playlist'] = '

              '; // --- [Pro] Dynamic Reloading --- - $pricing_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - $upgrade_url = radio_station_get_upgrade_url(); $fields['dynamic'] = '


              - ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | - ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . '

              '; // --- No Current Show Text --- diff --git a/widgets/class-radio-player-widget.php b/widgets/class-radio-player-widget.php index 5f4860d..b2e136d 100644 --- a/widgets/class-radio-player-widget.php +++ b/widgets/class-radio-player-widget.php @@ -42,6 +42,10 @@ public function form( $instance ) { // $hosts = isset( $instance['show_hosts'] ) ? $instance['show_hosts'] : false; // $producers = isset( $instance['show_producers'] ) ? $instance['show_producers'] : false; + // --- get upgrade URLs --- + $upgrade_url = radio_station_get_upgrade_url(); + $pricing_url = radio_station_get_pricing_url(); + // 2.5.0: set fields array $fields = array(); @@ -76,20 +80,21 @@ public function form( $instance ) { // --- Station Image --- // 2.5.0: fix to image field key (script) + // TODO: support for custom image? $field = '


              +

              '; $fields['image'] = $field; @@ -99,27 +104,27 @@ public function form( $instance ) { // --- Player Script --- $field = '

              + ' . esc_html( __( 'Default Player Script', 'radio-station' ) ) . '
              +

              '; $fields['script'] = $field; // --- Player Volume --- + // TODO: improve this to a number field control? $fields['volume'] = '

              '; @@ -127,69 +132,82 @@ public function form( $instance ) { $fields['default'] = '

              '; + // --- [Pro] Popup Player --- + $fields['popup'] = '

              +
              + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

              '; + // === Player Styles === $fields['player_styles'] = '

              ' . esc_html( __( 'Player Styles', 'radio-station' ) ) . '

              ' . "\n"; // --- Player Layout --- $field = '

              + ' . esc_html( __( 'Player Widget Layout', 'radio-station' ) ) . '
              +

              '; $fields['layout'] = $field; // --- Player Theme --- $field = '

              + ' . esc_html( __( 'Player Theme Style', 'radio-station' ) ) . '
              +

              '; $fields['theme'] = $field; // --- Player Buttons --- $field = '

              + ' . esc_html( __( 'Buttons Style', 'radio-station' ) ) . '
              +

              '; $fields['buttons'] = $field; + + // --- [Pro] Color Options --- + // 2.5.0: added Pro color options message + $fields['color_options'] = '

              ' . esc_html( __( '[Pro] Color Options', 'radio-station' ) ) . '

              ' . "\n"; + $fields['color_options'] = '

              +
              + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

              '; // --- filter and output --- // 2.5.0: added filter for array fields for ease of adding fields diff --git a/widgets/class-upcoming-shows-widget.php b/widgets/class-upcoming-shows-widget.php index 3262c47..6fded48 100644 --- a/widgets/class-upcoming-shows-widget.php +++ b/widgets/class-upcoming-shows-widget.php @@ -50,6 +50,10 @@ public function form( $instance ) { $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : true; + // --- get upgrade URLs --- + $pricing_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); + $upgrade_url = radio_station_get_upgrade_url(); + // --- set image size options --- $image_sizes = radio_station_get_image_sizes(); $image_size_options = ''; @@ -94,12 +98,10 @@ public function form( $instance ) {

              '; // --- [Pro] Dynamic Reloading --- - $pricing_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - $upgrade_url = radio_station_get_upgrade_url(); $fields['dynamic'] = '


              - ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | - ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . '

              '; // --- No Shows Text --- @@ -122,7 +124,7 @@ public function form( $instance ) { // === Show Display Options === // --- Link to Show --- - $fields['show_link'] - '

              + $fields['show_link'] = '

              * The return value is cast to an integer. */ + #[ReturnTypeWillChange] public function count() { return count( $this->_data ); } diff --git a/freemius/languages/freemius-cs_CZ.mo b/freemius/languages/freemius-cs_CZ.mo index f51ed3b6e97f323cca5034d7b26ec7ebb405c9de..278bd9f36432117ac36ff3e440138aef52ef07da 100644 GIT binary patch delta 16351 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¶`E5M$^hN>(NMnk~>qi1C> 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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index 420c0a4d649886c862c39da081ccbd6152dafa52..f674098e690154f50c836f1e78bcf884664ac38f 100644 GIT binary patch delta 15378 zcmd_u34B!Lz5nr(jeQB5>~IK#up}X26C$hZ5H#7xVojSF0FL7IwKYbjLY#$IfQ2k=2WhP|;Wi$USzI2)a8 z%W8`+;1zfp+vB&`9PMtFRR{C1CXT>bI1$y)46KfYScCqpGWS9_e!;R9;UFGZiX-tw z)WGWJSk`z-(LSt?&%5{Ea<6}ZHR#{EL_wiRCZ4I7 ziduzs*aUmK*YmL>^_fU8tXomf?L^J=5GoRHVm-Xz-cRmfCeR!eflSoEdSIj%g{#~P z^H3wL!n$}Hs^N{O0p5=p_$W5R)MugxBS^kkA0Vq@)$L*Y{nY6)4l#H)N=<>5jl*C)LW>IK6J1D35QX) z`dZc)7>=62I#fh{f$Dd6gu*%sFQc+_W}dlG>beLO;@eRnz8BTuL#R+ciHg*VI2}Jk z&9L89=KdJeKxep?q9znZO(e3Ig4TQqHp6wOf&3g5>fK0wTBlI^xIsU*7G8;JAcz{s z0=x&8qe7eB-?W>BYOgWstJLxt??L1rm&JiHM||a z$Bn4#caAVKxf`{1+g)R*Bs+j}@eQ}0H`21$!PY3$l0;AuyA6BbI_!=w;@||!`er2Y zkB4$JTaX*$aSFbUno$S#j*@Z^YAL3pmMVaqaRsu|tX-IepP|}onQvY`-B7u33ufTm zn1%;25zpik{}Kx4xiA(d@*mC)YcuNl39P_@C``sxHlS3Fl*HVTXEwm)QL3>TVntf($%;DH=?#>)xbLL$N20#d^2| z6^T1g1HBv7(PnIf+ff}pe4VwnS~e4AjgA z;}tj@6@i(qbMY|s1-KmtP35Bm&*DmaZJHVAboOi$>N8Oj4PiU2RfK}p{C-sEo{QtyMx_9Co{i%@<)C}gJmdJ-1cqOXin^D_y4QgV0P|qDlE#>>D2{xKd z{CiPoHru>x#-Y}30bYR-%)&cSYx_L1TGl(*8?)z_&^wr`IvIQu&Ox2^r;xc=-xio7 zdRn2G*aB28+*BAb$+U(GO0K)G6F!7p@Cc^iB~%A(icF{{pzbfij`$Fc!`D$0XziG{ zVmfNgdt-CVN8K+*ErCBmL7}=AHKIe<7Ehoa{0cR&KfCpM#U|8OxVA%uHWT&xUZ`z0 z%)LGpwKQ`u70Xadv(FWB~FKmGWP&qLjt6?Q3 z;Ud&?H)D0Y4b}b~r~z(7<<3LMKqFR+f=2iPDi@Am2&YT^1l!;W)PQ%O2J`~f)c${kLNXVQqegZT^YIMwtzq?;YX&e6b^R_R zIM!)YL-}Rqch|ViLWO!R>i&&b3x9%&)GDlvQLID%)@BMC=_9Ba?LsBZb9w;ZK{a?9 ze}P}(2HZ|~?!fXY{-q5Q6YN| zhv2uU0S&4!9bAh#Xy)TmT!Zv&HJ)ca(Uu?^!McQ5I45W#u?97v^{7bPA0+S$?sV&Wu?zLXsHOVCtzW|5Q13XOS1l$jFuyNAJvR@v8>&zf zSmL@I6`?f|_kk}^q4^e-%{3RAdKxyQ-VN2@)tG~$aWdYBGVl=AxEt4|ci*!{OytV274^j!X-Z)e z1+D!~yb2GYk|T*$6v{kI!76Nl5p0W_P&0oDwM0iyIdB16V*QoIuGpIT2-Ffd*acUu zB>q=Y_$3##R-d~MR$FCuK?m%?_5P><&O>#$3^nk3kR-Bpp=NX#)$nP|#njbi09T`) zn}v#8(7k@=YT~bC+R26b_&nCew@@?uEoxxa8Z&^Vu9=w3<-xAQu@3bySOce_+VkKz zoQG}j04frvP?7vDLO~BUTWhvkC)d8H4o0IwKOXgiDX5uMV0GMzn%ToxAD>2T+t*PO zIfWY7Cs+%=cI!#En}J4BDJYc9-3z%`mwFzmfsv?eo{U3q4QgOVaU!0;`q=pn(@|fn zNqsmf7bc(vJ{L8SrKnx95@|nT-AADw7am6aaIfn@^MLglY6kD326o21|0mb)Q3I{9 z&OF!HwH@wCV$0%S?tiqw`~frlPIE#Y#`xd=uH0z$=^!lS!Mm{tlkYNrT=HTL^_AEF z_n`*#I;P>TQEUD^HpJ@xV*Y^H0((pOr~L$uR_*V*#e( zB2-7KQ4!jNTJs&KwR{3K^L_61!>EBA!9n;gj>H!Cm|atdO)A1Wr zyKT2bOhG;k{csuva4uHElgQYt4{!wDe7|`|97Z+NdaL;Z=9Q=ce1Pic514^X zx0y8`h@Gg<##~$;q0oWCQ|Q4{*cAsqU}jd1N~&$BknVPU9h0e_K@Ip5xBeY!2@@YQ zGp>!QH$_FrM*Xgndq0vzK_TjkYG{aiV+szV?!n>s5VptPqZ(|u-3+KDvcs$%s3mz3 zwN%GY2h({}4t$5ok;V_1TxyFXdBn=1pqUN8>3A)wp~q24vmf=tSKaF;-1^6u$o0=r z$@xcYhStx_0GnfHs%fZD=VLd#2{qxzFq!_XJrp$a{m726-oWbU+hNu+j2ghLsDZ6T z4fq~ZD0g8^+>2`GpnLted;KJ8JAaG|@dB<#|HHIL|5nn^O-HGy8#bonlcFZ!EA01}8R&FuOnn~Kz-4ZIEk?9P4^T+MJ*Xf48rAXF zxEGUt!N1#ZKi0!3kDGs17hy5=16U0+o-j+1jq0!;YIjUTMXC(dUj=HZZ+(LJ`zgH7 zg_byWr+Ltes{2tRUV-X3iuG|T=HgCF!jDk{IEUZk=WhM?F*A@0sNdJxWtOlxs@`!I z@t;ef7Z)1f7Sw^V6BU7Tr~!O|J@8xXjyX@VMOlhMR4zQY+x+2SKL)7x`=vS3A3;U* z71XXchaK>H?1$|md-!uHg*n&Ead@(k~6{!8Y6>Z#$4e=x@Nk4P1 zH`;G@O;2pa^?s<|Psi3c4;9I^xB>4%mM~&vKW#!j2WN2OPSmdW0taB|8FO^*M2+|; zY6*d{G6Fl z8`KXwq9T)px}WFPhoX{lqFeW%me7wnM{dQQxC$HLv#8%6$0Yii6NKBzSvf@*k_>m=0D%|va#Flt~`SQpn~L?geK!UcR3*@@P+ z=S_Xe3#LBiMU!mbq1MzoWCqd;5H4UR?)$cvg#7?bcuY>W}q^XpN$@YEr#KZS!_ zP>BABN|ubnW`x6BC!zMc2i0H{ufmO3j>qsO9Q+cm=S0gofYqtjc-5GS?Wi|HMJf*! znL)1-e?2&r3kuzA)WP7z&bSaYv#qF^J&SqxDk`MaU!xJMgPAxM)!q%Lc9x*pxdRpY zU!juvO;l2*MqW36!5o2_QR*A!Pb&GSv;HVv&-KrbnC&;_O%qxls)G<};7d@+wF>Lt z7F4c0ii+4?)DrA>?;myVM^3qgPjDO$e1)39@S|p%jKlWSXJd1`1$F;k%)kdxGk*=0 zOBb;%);eYe+65a>&&3)z1U2A%Q;%5Jx))}n8t`HxtU`6P8ns_Hpk}lkwG=y1&+kXA z>1(Ki=NL}Ke?>)NsE&W-dJwhNuV5oQf@tzWeY08ksTC>o^=e>(c>SR5kHBV>9eRceFfFvJE)oe7K`vKYKHlzO^!^% z*3`YI-~AMoOY2b`@4y_~iIeHy`iMf93w_R*e;z-Ie(H74ntur`Le2PRxEN34)mV7W z9HrZ_zpkTZ-u}E9$d#yFG!kvR9y4(fYCt>i3hn==DJX=;T|dY6)NB6En1zYdCt`J+ zjGEyLR0lU;H(ZSqaX%^t>VIPXfazc->JQ_1Jcf!)`%n2D{aZN{ltgndfDs&kpQA>a z{h7Hx1krwBEEpQ}i zLWQng_j&-;Uu1<_Sc{tRdejWIV13+y6LBwU5ZsRFGDrB5!LZMsDW-nb@&LXon5GqKaCpL ztJoKhU_REmXa+JBleGT}DKzFr397?Is0M$E&2cR%Nq3+cKID2FHK31B9iKz}?vJP$ zUv%r%C3C$7>iJaEz?xv~b`+Ys52T}Jl;zqREjv14?cnImD^v2*Lm44EJyaA};P(YQ zMYhlDFG~+)Vxi*?Id+*-m1%qYMYh);3VVD$PuLsq$1B0gLX|^ysb{`p7dVbTniFVS zGhFJ}PPxbHi%tx5p^HEyZ8DeoRRjY?{9?AxQ{eapq-EMpc1d=g9lu%Ot1R*Q(~|X| z24tn@c2K*ac(Zne&r|4V1mS=k@)SFEd7wx=7J4cf;R46@2f_@u$PSl!!ghLhe(Mb} zXxmxnDGb-J3j-BZcAz*qan-%e?*)u7pbim0^v*2?i@G%)Bf8VXu$z+MNPHdqFAlzhiHcW|XHy*aw{^CFGP>*@Zr@;|~+{c$92UQBlwdG3EH6x&K#}V_97mCe)!N zKh2aY_|e5BEhl8#3YB@1m7}ln%24?7W5mI+D=Uf^MttaAfBcHySs1o^+RP*v%C-hy ze)@8wy|V4;fy#fhq3D!p17i<#U6saJEw+5%E6K5`Lt7^ng-b~u<&VuE zOKg5pZWmR$J!abEU_3WGmEnLEq|lADnfTvGCr`0@)t;*GIUW)=J(L}*7+#Ustnf!s zh-YDT^u);Gaq%?v{LnKAY^V3^5E^KWV#6xOJ+|LjK>Ei+@WW*-zHXF~eSi5;tna9% zi7kW9yh<-o|I6-XdwA!^w{&#On6+)~EPGT&xg8D$Dhqs0s5HR7A_VLtZzxph#J(Pr zm)IzusANr6REA!TN;G#|(^PA$waEZ{9sgO~vA!gcLPxK3t)8Q`j}*to&7GDM+gv^_;d)NO0Eb_N4i?)nXEq0}KU`WF z@_It$nKma@IOwe`uPC)UIiU)t(979g#R13TP9X{4C$Txd$}1_v6Qpx?^3~vWpcMtzu=cB0!kgbi`S^Ak*gk$6Ja5&lGXM`vMi^#Pmn4 zYV&DQMc(3KC&&}-9&N3x=LN;t5^^d$@x5Y(srkf$;Rh3H(r+Q}J*O!8=gO-ho?u!0 zt;R3hZB9T43NJ=Bj8k3_2zr8q@$x>5Z+Fi2_&NSx`SJgeTQm~8a{jF3SjvqXk{fDY zb0E4O1*J?Ywmx!4at(H_RphA(ZEJsPLM^>ntoZ9Cnzr=48kcuM^!ufoCvX_Cst?rp0Sh0k4W@ik^tSa6JWj-RecE_lViDbM=M`@w(1V9$8) zkH4hSQ{v>2!n22ti8j40GdAV6oW%cu S{NzZ@!N7CiPOy5Yc?yCkS{6F)7w0F(& z|NS3GgKl4&k};UG@IUjB^ydvNW9K&XPOden%v&6`3xbs;(Tq)fYK-Bev)Ou4@21r? zC+K^qj4zAWg-xHO)E%i~roiC~J#5d9{b9=k3H8PVD!hDn=->!*o|sRhoqzlD==+w# z{+(~5(g%(w#|H0M_doM(6s;IOKGx=uD--Qe{`q{77xBgXe}65y-$|1nE4=21uc5y_ z2IHSf!}##hY0t-mzsj7$#Nt2tR@(ho#((=;DZ1u~jD&*N?k6@SBxQ{n8l4ww94(DS zyZ?=^C+lh_#5r$`b;A6+#v1Oc3djHB9=Fj!3{e^ z725MsO04}eZzm^&V;2whOU(Ow-&NK%PN>os_JplTL&xao4;9qy1m*mHI=q#G2&z zi()suTqouK=P$Kb?IX`5#hyDhDIr6jQ`Xh&ZYNmb@jE`ZOt!cs|G_;aJ}27y_|aJ1 zx923cpBn$((F^e}-#kD12J@ZFiSXe>YW$rqF>ADkn|$Yl<11Id7n(kc{)t~^k$?Qt z>_^{FKibRxz?a$hfBe(z-~Tdu?Bu3|Rzvj}612+>?kNcKDeZ1fJN_qt=&;{(jQp2B zy7>3tKjDil(Fw)o{$^ToH08!wvD-hmnAF5m#y{?O1N<;pULLXAP9I2cKif*2=!mlq z)(U&e9ZUZSMn5^5nNSdIcCKd&PTrE@pf_x}-#%9SYfdkm=!|pQlk^3aaK3X3eJlUX QuP`f`aDVgYkn@ZG4OST-C;$Ke delta 11250 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 z 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 J9i@K8{R_92cFF(% diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo index e10ef71aa02158461e55b43881f21c6790851396..a8876057c1664a0850b737f6cf1ee3273f15035c 100644 GIT binary patch delta 16836 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 PTk#jR*Nyi%ch~;`5hnDk delta 11480 zcmaLdX?#>gy2tU;N!UUl5O%`S7b!vI)sgs16W#w_- z{16*GofNmi;y?K@memZ$)lvd z&$#s$-THRCmG%l8g1@5{klo3$MlruNjzTOA&tnUG30vaZsDUqFBL3_?zu~sG=xioz zi#2(kj&-m%*2m%40DW%zEF^f=W0;CNus-u!UsBM{E}?e*2i8U}y);ldYQ+OkJD!MI z*xlFzr@QsFsD(a{wQvt=+&57Rd>=V2>vL?3RvPh7z$O&bu`Ox`T`+{%s0nxBI^2hY zabh}A!}X|$UB;KOUKg{_*D;y;F}w*cqH-&-t7T(%nW&vaF%cg@o#k@WLbhRh+=m3y`UZ6^>v7lgUM6b%zPJ{n zqbMk(7f}OVMhz6#%j_T#8&GeC>X(7qQ5Gt415g7`K)rVtCgF4}!`ZkTFQFo`@MiP= za-=kAPI~+5> zvc}^COvbI)7>}Sr{|)MBu3-mE;wo{v)-9NdrKoW?;#jO0Nc?s7H3yk5QwrWheHg}I z5J%vBNd8!FU_M^MVw^VE9KkWHLj8N3jlZB4He-kh`FzxE+K5Wd9jFDrIfVGD;d2_= z;1B446TIQ^+SYXLx!>zBT=?$^6}6@Mxgq?jVU$|DJZn%NYt$r*cm^^IIP0<6ENPj5jLmZ8nwgQFdpy1syH31VG-(jmb%XuVomDH zP}h8&TaRw1pcTD})$theEwa8qB~$H@CIZb-3vGv*s1s_U47Z+zI{R$YL{r@M0=FJ= zy&s$Kd?B*IsP!y`hBUm0+Ue`A@8S{apW%zRfjFPT!J`Q?rru!|v>&yB*Rd6TjLq;0 zDl$o9OeCA5a_T0mk8QAu4lRp<&i+>Hgws(;xeROJX4Jy=VLBd0MdAvk;dLy+_N26K z%Ss%~Oby1FNKKt!B9(`_HHD~TeGF?VX`Y~<&_9h@@kZ3d+pz<_hT742RKM7X=7

        ' . "\n"; -// 2.3.3.9: added filters for week loader controls -$table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'left' ); -$table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'right' ); +// 2.5.0: change element ID to class for selectors +$table .= '' . "\n"; + // 2.3.3.9: added filters for week loader controls + $table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'left', $instance ); + $table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'right', $instance ); $table .= '' . "\n"; $table .= '
        ' . "\n"; - $table .= '' . $arrows['left'] . '' . "\n"; + $table .= '' . $arrows['left'] . '' . "\n"; $table .= '
        ' . "\n"; $table .= '
        ' . "\n"; $table .= '
        ' . $arrows['right'] . '' . "\n"; + $table .= '' . $arrows['right'] . '' . "\n"; $table .= '
        ' . "\n"; // 2.3.2: add day start and end time date $table .= '' . "\n"; @@ -194,7 +204,9 @@ $table .= '
        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 JP|dUHe*w6jNAv&y diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo index 64185843a9072ee59c23cf09a5edcd78ed9adb6d..662725dfc38d11b3776143c5fec541dd0be56fae 100644 GIT binary patch delta 15343 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 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 1ad1ff4a138655df268dba88bffa8830ef28e0dd..8bbafc31df2b84ea50a064cdaa578db25eb76fef 100644 GIT binary patch delta 15101 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 delta 11110 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 diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index 8eadcf8e0fc21d20cab4e551adc8376fcbffad68..be8d837685261748c99eff906f7a076f4fc1f054 100644 GIT binary patch delta 17798 zcmciH2YeM(-v9AQNazxJFGC462_2<_AVmXGM6gh9l1VafOSmNnqSsY%RaArlam9*? zg1Wj~b;Z7*i)CFAdqq@GS1h~M6~*WCoim~A?*IAk{{PSG;q@}_IWu!-&iS3+Idf)w zVQ;PPF3Cy1*EDCH#Xm3GvfAVQ%~i6j;$BA_%jOiy8*vpzaX@eW!&~r7d<849Zy(Dl z!)tLS{*3uJy}+^>;F7?LkV~blRczGez&31(PvY(PG7iC|g;Waf!};hGSym@}8e8H% z?212O8?^gcRzn8BA$LXm1oQ-v`6zg(-Yf-Qx>F=A`Fa4|Xg@)pRKy zhnJx)ydG7-Td)V-gRSvZRH(l|op%t`fZRcr6~mUO8(xAp;*~fN3%KcNm>f*}6|!g9 zu^B(XrnrWVZ-=*I7yJV%IX}Xln035KzTViG@>~pI8p$_nFVZX4aVJ<-6C8ytaT+Qz z6{rYQpFsSZv2i{-a&S#>U_GiLH(_1e8ti`rb>0qCM4mxK>J`+DJ_z=IgC|k8hFaDs zFagzoHK>T(i@NWFX*Sld@f<2k=MFOmDg&3JLVPtU#5bUBcn2!f51=CTM?3>RK(%nh zaC7_=R7KAYtVA^^iE2ptJT}zl7h-E%gR00~s8BzM!B&s2kum#RS&P!X1*ig$7s1Tlqy6`I057(mK6?hKit+*EZj4~H| z67~BRQ2SrURu~>_B5^S))E>6aB67Hj@>63}fi#)O^wyRgXJZnVU>iJus%h;LjqOpP z8-Tjt@L>O`*pae>?eJn$#c#%Kco(kM@o}bsKjT@H^Tu0NIrp~~v5||rQGNRcHpl&_ ziX22uFfGU^-M9^^uM07Rzee5oQ_RKO31$Lnfa;1qsOt_49E?kS6pt|A=R9D5Y7haBxH0ypW#LrRJYgcR@K7CQSa1r*v z4cG;DU^ebACjJ#{e9Dd~IGsPt9o9{#{jXycj-G0|;yzS}pTniN3su3BPc=5;=3obmp+b5kuEX`HVOcocvWjp% zaw+R7R8GBzw>CK5h{7c;~<=Z zjqpNLBvzv;x&d{go3I&fLf!D;VE^`@{4D0N|0Pro?ZpO))lY2b0(DO}703&0hZ=r8 zP%R&eEpY-W0&@cw;xm+&;3gb1n?(tJj4SblIi{j#FlJj&o{MT|0z0d((rl>DZ$X9b zanuc;MP=!qu{pkkN}BzsBy4!5DL2JFlutlqdl?>w%TX1*9Q)#WRK#|oa^`tVN7(q0 z4Gq6arlpD8@ncj(`pz?<9D*8-qfj?K6V-wRs4fblDxN~!_yW}Myb9H@hfwFeit5Vu zPz`K0pZE`EqxF3Au$hYLyCv8X(^!btp!)VHq_?a$a0nJHFrjy_zsh9r`M3Zz*Y82v zV*ONNrsz4PreRA^xp01I+9cCe>`-!Di#_oU?2Wsy3m!z>pktW{^)%G+<=7qXz^V9W zR0BFV=Bb#E>hmGk28&V0%TZkrO|zj;-GHjmPV9uQqfY!DRk2@!a-(t+>Xw0>QK2nB z{eCcNn4J{tpN;C81=tuDp}J@}>bmL6*~n$%H>mM@6Oy>rL#PuQSD3Eoh`P~WY>T5% zIdKNo#uV1V<*4&6z&dyt>iVlu6igoZp?1-16D!dt0p{KE)#{ctdn1i-)4NeEx*wG^ z+jRoIfx6&6ycfU2b-0P}T!SMbW_-Vl%AHzKlT%$$H=YtW2hXNlikIM%=#T%2F*CtD zfK55@94ch*;yC;XRiQCe<_4#uCYowofmb2-wwf-Y=pO>YPdBhZ^Pb{pFwri*FpIpzC*ctHIG`XwZ#0s z1a;nG)M!|WYQTkom!cwcRXRA~YgA}{LS=Kkb4GYRqTcrVJV)7AERc_bnkObmWQx8J8r_`@P2HJ+fgBZ8P%8Hqn`Kq z%S}#Z+We9XIq@L>#3!TP^852e#_BIPu8#hsXspJ6X-a;b^ba8yGUpsxEF>iF2p zOym|}d&=iwx)mGOv7x@-iop6`Y=?O( zjeW2K<%y^+aIiOCv6A>7&&KcBp}zVuII;E>W)yV8f$SfNs^DVO4KG1e{CXsbtou+(sK~{G{nuPc{FO{w*^!4&VFP>x)x!5t6|=4~6=)S$fH~|Q z8#n)=LI%eG)1K8_l;e?~QA52|9HVSW4|DA&5$R5aa~4TZ8zu%ka7M|l|P0+UeLd>W3! zt56l&jnnaU%)?%*&5ed)J<1bMxiAe?@r9^{tU!&DmB{te){Sg5V#gNL54Q#GFeg|q zpjz-Is$%)H$V)bhGWMKCRvk` z`&+Bo=!L(~`1zLkxy*&tHZ`WwxKHU!>=O0s)U!|nm>jL)Mg@_FDls0tlK4b$A4OqRFB)|5{` zHE22(-~v=cue^!)>%xz+LoL{io$zf`UmrwecfFfUGIc=R_;^&vN8tz@kE+10unDe2 zUH1mm{tPOI9>YHP9Ja&nZcdwQY`;QiPI8xh9) zu|FyY_M`THk6p3tZKga5Re=y5k5{JI=*Y&?s3iOddtveI<_ktODumAkeu!$}PpBkp zw8@0L4dzfTLbY@tHpMZh$j(GHkU-0%-L*CtU%TD zTvSe6in(|V*2e2m6}}aF-~*`0yo-hS2~NhATZjfOz`A$~*47=ju%S@>4jC!dQ^5(L zyG&#jVtw|fQ5Cry6^Zp&2OkOc??CnaE>wv3Vm;iCs^C|69%kRokjD$Kky_mJ9&=)6 zWF%UBP&x4|YHIxim90PE&@3wTTNaX(r`~H?@)D{-Z=uHZA=K~Bz0VBS>#z;wM^P<* z4b?S!Fx`ob@7Rzne`ih{f~xs6q}#1Ck?ys!wwkWljMbDM!X?-@W4h!H%%gk{`dx}P z;*V^G6q2I|6>qkgvv zyAh#lP%VArK~v$UP``T_)rI?l{eOFq_+QM9tlygxF2SLcuf^kVH|j#~<7oU8hhwjY zs4zEjP%XW58{x)Vu@XByY`Wx9)abe!Tj3wDCBBKB@vAf&x?$5tf^m;+C|Bb=ygDd< ziG3*7d(^CO24gnm1$YUTqO$xi*b4_e#?Zrg7{*6YInwKK(^XSY*H6!6Lp9loI^h*m zhWUlKUx@uQ;-6(> z1Ur7jayGEW+~4|`4b}KFRNwzCIHBPm&Hk2w9Z)yuhPuHJY=$#}N%6`>##oi z@53JW2_3?nHN!!a zyP_^Q2Q_NW!i#Vnrm)Qmgr5^up>FWzizY%JqPqBd%*A%Q%#`1G7x7of2C+k-9gpg> z(^1J+hRW`Zs0;o9C*$*|3$=L3tQp&3A>|}$zSxM0>|LnHJd6$SeN<8&Ks9($dN-2; z8&}|99Qv}EkYZ@l1>fLI_P2P|gwDmasy{J*JEhz6s-S8c(g`Wh+51<;D z`&#f&YZh#D#918Zg}Ol+b>bzc2&~1nm_dz(KjZQE8mi@uUN>Df0yX}ppsuqBTVfm) znafZWTZ0WX{%;Hp+=Wd!a6hVLJ5eor8`VYourYoc9IyR`Ij=3MD~eFbI0zNuvr)f$ z2vzZCP}lnqJLBirMdLs3O>$NxrZ^h;K z5k@iemWj|VY(n`Ew!+456B&*Fo@{8Gj>Z;vHnzq%DpV^`EnAN|?+Mg+eg{>Ny+Qc^ zs>}Y0O446&C{BLIOx+jZbjo+*m3Ro#3f&d&27Q7WCO4u&y&ZM@<>2_6L3tnQ#-9cI zzeDwH_Isv<4X^{{4yc9LW{k=alqi7ha>zogX|7JFpvO`_b^&`TK zw_*zW?lW0_J1XSwqPpaRp!@~4r~Ebc#>V^2C>oCHg4L*`dmC%xKJ0=Aa1-XGKQnc<`eS+80BVqPnsxs)B=1IWiyf zF&$x}eimP&F_#@T{>5D2HmpZEgZjZ^s0uzGxEohfeiQ5CqR-6ls!=(#0u|!T*byH? z{r(*sfgfT>LAMW>rPGC2!GS}V!1B*c4c|ij!1}_xl8wYE6u06D+W)0-DEbo(D%mbW zMd(KCj=#fh_y#IcKVwUEdW)}22n$esa}w%8m8h1aF&l5iI=Cs=|65e3cVlndiyB@H z|7t!s%)?QXx8MN$6c=LqznLWcHTF=_t!G0adIDGAdw3F-eQjE>CGas+OLpN%{2rAH zgTFB^7U$umln>$ry!c!5ka->z!MyLx7u90SqRUrfdLRe9@68ms6BU{da5VmiE)M&_ zB;Rc~iSjOnMNbW!f$FN$QOOlS8Qj6q$nG&mj$JSW)yE7X0iMCH($ zVE=V#Hguy+sNr`XDybes)%0o94|bz&@D{4Y`%xAB8P&oZE6XqEp?=pAmAqYp{SyPH zVr%x#LRBao4GttweZMRyuffKYe}nUJ3o3;5$sFCF8LDifBG)Uh5LMy+L3wE4NK{3~ zq8eI^oR_v{u%Viqi5h0}u^E=2B2gW<0@dQRfg4bf+lZ>j-GL9Fe)l-4VlUtXdughjMOaJYe=!>hRSNaP-=JD>Cw9d9usyzrs`&xbd4EHlmsQJDq#mjw%>vs8`}0u^ z>Kp7IhiXVM=IRDBg9B&aag`=7KcB6@8C>#zY-B{Ew#8ai(PS};9YR4{doT%3?)~Q~y(y^UL$PIhb zW4*l>yT{U9PGi@Is(7r7U(64ON}TYhE(NwzR8cg{_77HtQx$HsOO8%dg{=Jk-E`fA ze_6XK94d8GgJjH3gvuQ|5-ZakOG7DYxWuueu_VbadGk-pPpeH;A-BvfPeuKPgu-reslCJvhiSA^WiN@v7rD_2 z_9QD~sibc2#N(+d)96$*>4vGS-7^-qmsC1Yduc2cKO8paoGLf&5XU7{$*FeYR3hRe z>~c4rNcJkS@-rXyoSxM$veYgOyG}GoC-_0LLuF6+q!P)R(+G=Wr>e@Rh~F4D>hFm<=OpbxHf@V1imb6UXV+YG zaFKmREcKsW$eT51bmq1`SLAjri6tu)AH^B`=p54)C*jTQ-!5}f|8H}KhGS93rroh{ z%FnM*S%g}eZncBVNf2Bb<>yb%G?Pt<%n3uTs*_hmwyJfrosxu`bi7WZdU~IY{9Q|> zVNFDX2v{dYy{m6&?L9E+)wb%;AQVgfA$@LN$F%$$qJjFMC`Iu zaK{2KeNS66KK?~6h01jUji#!w6Czdf6GfS-302vxOaH0CehLL+mM$W2IH zJAY8O#As_WLsi)wvZKxt65ns_k=^QN9jGML4*w`KbaJcgc5!EMN(p&5K^YiswLh4> zQ%+gc$u6`f_lVfZcq~;Cb`q5_Mii}O47rI!%E|n2%CPKa#RQ=6GzDOMO#r<9Q(HB* zrnnVx#lP508ksRuuc=ih=ne0s>ESMgb}SlR%D82w@B^xDBIn7;kj<#hPh^J9Se~1? ze$K0PT02R5ymO>}_RMH3wv5_jA`2?A>#N|{-T8@E@Fy?hlxEj%Rvrt7V@p)wnq2AO zmAEU@H6`W>jvtw*{y%3FhKfI-*!csyC5CCPvNZnw>29ULy!bzLJNlO-Qs}rVx7M9A zpSX|L%G?y0nspZQUyLcQO7n;9n3Xy>DNF>FEpB%l4a%T7Im-If3evsJ}b90(RoOp#2+04fIiP2v7FZA|KMT`plRr8E7j=_=`>;F2|ug?}DccDVwh9!$ln zVuY9hM~pQB3T&QwJe%0yoHAzeaI7jqO#kVs8hC0{nOk1p4vpB8df zA19`oYELGfygjQP_bugV=ahNBq=u(M@kRc#j9&ypoPZD%9)}DTCsGxQhvI~>W(@j6 zohjU(uK!k<9Vc^Kvq|=dR0Xsxi&H7c2!TX;x-!dUZ}+My*vATAH}E>&031 z^#HN_r-;{O#k+NDMuB%|#ZA+gh#2nlUV^QXSlLpZ-%Dwho|oKpsh=r2!++9-O7xu7 z6H|{1Gh7$yL6+b-U6eB^67e=%QqY3as+~f00?Q8~)eFVM z$brKd=-M0^wMUM$``e@Kem2ML&Li3A-kq&{yT9txub)on#|iy8VZiXr%*%#krziXC ziTs3?V0INxjznoF&Nz%a>iuxg5z9ihV}~8`DH%^KBLc2n)}v-|rZO``EXqPDsory= z)voD9o;~(VEtQCTJXY=6;U4C3OdR7ed$8Sy5zEupE_LFidcd&Ov$=WF-zeiR*Q^Rf zimc+0?Y-|c_ikRf(A3#1*LK{gxrY3XX+mL%rUliGo9Uj?RNPrcy=hZv<&MoZXR3BF zJMM-Z9zrp@l)>i@c@Cwb+&5O`(#}LE$@8Ji^(We*zW#j}T+BgSH;%wck$yZFoFvHn%{d1UTA)*`rLnvB@!+xh(SzwPLVZ> z{`j|VH!T%ATqj*~*1x=FO(It5Y-OV!7&X;mvFw(U_KI>c^)GWUbNa;L6E5rhL?XnAdXF*Fn%~<;CF_z(p0559)0@7w zYo^t@fmyvYn*7X;l#q!$TJOL;SB}@kONF})@X;ND1PcFYQ89=!JI?1ugiY;&$RZ9{Qw z;=)SK+qmK3y3<$#$M~&x*7bHjMZh0!e%lVmP~Cl`slmVtG4Q<5&X(Er$FAAk-s|+x zartJ>AbeF!C?#%~3i%f*36aa(lj_Z$<~6xtao$XSkD zy&p(+vGVfnxaIE+W}3N7C;5}d69?L6Ufj4WtISi+NAnmhVMw+k5udU^tT& zHBqh^CSz5<|K@(y1ePAwWZqumZlY8Z-9KExyWsX0x*R#qfQzPL)&DdeGsT;JlikmZ z$Y_k|R`Wx=+GSo)Jp9KZOHump=&2{uWb@Q)uk@~st;YIY8)Nlj`aS5zOxwHXX4N@8 zRuSep-mY8Pj6OVBxz^b^w5Sr*eBbdx|jYfyBL6rP? zu5I&Jl@`_o_TgmE>&Nx?%*gIJ);uR@1QFt$D=0c)-bVERDW~s=aFO@*Vpa*ZG}h^_Xz0ncvG|UeEh_rH@SAN7a_Ao41yuj+!|0z>bZ!YIc(m+8r{lEr)+a z-UE^xrs`R`$rl+kbEb09$V=Kw1w`Q_V zWf5l39fRv+4n2_0X+WY9BTGH!RejprTm5iFFZTJrVWjgU*Z4NGPLOMUP!lNDi#S<(;{Yjx_5co@us8gzX#_0rBFBM^L@3v;%+%MXc z>#5_v9x+|1aaExT^T6$EPvcA$Ir>BrTqi*Lvr4ud_Mkr#Y z9@5*@Gt=+9pr?3f$D*Shf0gHtgu@R;PUKnd9-as&>t-Yn4+jd&Ax!3eh`g%Jt#TFC zBJbRW%L2NTd+MZ^mbttWaS_tM%^bgTTy|&POf^EoJ+$sg__aKI ze}5c!=RDgdH>}lOX4A7nvU|*$QS1lPpPx(z&Yo6q>~NhnX`1)r^MleX6IrEc;hUeZ z{3S+(UeEk?kfqGbrXh?24bdP!6wkO5iD|Yt=3!>$vLbUO-iP^;$12QUgBHoxatIr66N=NmO)Ea}m4cu8Dv zc>QVC%8u&*{6{r8<#jooBS_S-(pvW>;zeLsq&cI`0sEV<=EGgk))zwZ^Nm-^9CLdaA#)+QTjFZCG8)RkrzOg*3Id6_h z$2u`sTg~!Uf|+e^_RbnN)*n6ASSAGjy@Yqp!^4Pa&MpbZO1Y`B?dWOe=vgOYzumUh z@h3V_YQt3IHz+^R*`I0MD61DGI-|d4b#%;_@v7hZxk-8O=p^UJ5`E)|?CHA@ZG=UBa}@eAH-96eUG=!g@2;>0cV=im71 zlm5>>`|xCTc|ho+4~frG;qVeu1JM6Mq-TJJl7^)}Vdd15~J@HUaCcEhQvs+g{2 z4?pT!W*MVb3e7DwAAFdmNe2xx^PNZ2j(?}3%w2ol%<>lR9g?0zY#6CgjVV86%@c*1 zvQYD%aYyPIeCmnvxk6Ru3y*#s3Goum3*LYFd;e~=%?QbC+S?*K%_IJ(*^-_JW{6Nw zLp@h`;2*V;H;>K#>)Ptr*_OJ`o~G}O!;W2VrEBKtf3@K1b>uO|H=C$Q*)m>YS%mv5 zyz)?7A0+r1rrF*+-j$uaz6D=8g4V>;`TUO?-eqmR;>0yLqw1*6is=721`Ipiky>RA zepHc_*|WcAZU?id^S@8jJYjeh(^%!@(+k{3qCVc!UzGLK!;v>B>-1n5W9b7LbEj+h4QB+JW_uI9yuH?}%82*q g=lNdVSFJh*lO0X>FTnsy@SacS`P03Z|J9xU1zd-X#{d8T delta 11516 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 a&shDA$Ftd4f2Q5)$Go1#tKai_w*D{Xr#|Wc diff --git a/freemius/languages/freemius-ja.mo b/freemius/languages/freemius-ja.mo index 435d386993431669959d581e89ef41ddeca1a2bd..27b73f3b30ee11a2edeca2a2e231da30b623094d 100644 GIT binary patch delta 15235 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 I$*wp48|mnq>;M1& delta 11152 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 SndjQRwwcS7w6?(I+Vme4O6XYt diff --git a/freemius/languages/freemius-ru_RU.mo b/freemius/languages/freemius-ru_RU.mo index bcc9a0dc80a6cdcb1d98e57a7f02daeb340fc6f6..ae560518bd8794ed82459c2a3d7775e3d79f517b 100644 GIT binary patch delta 15209 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 YA?cT=pGeJ9X}jBXv(xNI-nzE`4qpLto&W#< delta 11444 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 delta 11352 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 HvF`r>Ab5hG delta 11790 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 NR^QcgM|`*Re*kJknL_{o diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot index 24d1d20..209d7ed 100644 --- a/freemius/languages/freemius.pot +++ b/freemius/languages/freemius.pot @@ -1,4 +1,4 @@ -# Copyright (C) 2021 freemius +# Copyright (C) 2022 freemius # This file is distributed under the same license as the freemius package. msgid "" msgstr "" @@ -8,6 +8,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Freemius Team \n" "Last-Translator: Vova Feldman \n" +"POT-Creation-Date: 2022-07-06 12:49+0000\n" "Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" @@ -16,796 +17,32 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: includes/class-freemius.php:1919, templates/account.php:912 +#: includes/class-freemius.php:1932, templates/account.php:941 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" -#: includes/class-freemius.php:1926 +#: includes/class-freemius.php:1939 msgid "Would you like to proceed with the update?" msgstr "" -#: includes/class-freemius.php:2138 -msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." -msgstr "" - -#: includes/class-freemius.php:2140 -msgid "Error" -msgstr "" - -#: includes/class-freemius.php:2540 -msgid "I found a better %s" -msgstr "" - -#: includes/class-freemius.php:2542 -msgid "What's the %s's name?" -msgstr "" - -#: includes/class-freemius.php:2548 -msgid "It's a temporary %s. I'm just debugging an issue." -msgstr "" - -#: includes/class-freemius.php:2550 -msgid "Deactivation" -msgstr "" - -#: includes/class-freemius.php:2551 -msgid "Theme Switch" -msgstr "" - -#: includes/class-freemius.php:2560, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 -msgid "Other" -msgstr "" - -#: includes/class-freemius.php:2568 -msgid "I no longer need the %s" -msgstr "" - -#: includes/class-freemius.php:2575 -msgid "I only needed the %s for a short period" -msgstr "" - -#: includes/class-freemius.php:2581 -msgid "The %s broke my site" -msgstr "" - -#: includes/class-freemius.php:2588 -msgid "The %s suddenly stopped working" -msgstr "" - -#: includes/class-freemius.php:2598 -msgid "I can't pay for it anymore" -msgstr "" - -#: includes/class-freemius.php:2600 -msgid "What price would you feel comfortable paying?" -msgstr "" - -#: includes/class-freemius.php:2606 -msgid "I don't like to share my information with you" -msgstr "" - -#: includes/class-freemius.php:2627 -msgid "The %s didn't work" -msgstr "" - -#: includes/class-freemius.php:2637 -msgid "I couldn't understand how to make it work" -msgstr "" - -#: includes/class-freemius.php:2645 -msgid "The %s is great, but I need specific feature that you don't support" -msgstr "" - -#: includes/class-freemius.php:2647 -msgid "What feature?" -msgstr "" - -#: includes/class-freemius.php:2651 -msgid "The %s is not working" -msgstr "" - -#: includes/class-freemius.php:2653 -msgid "Kindly share what didn't work so we can fix it for future users..." -msgstr "" - -#: includes/class-freemius.php:2657 -msgid "It's not what I was looking for" -msgstr "" - -#: includes/class-freemius.php:2659 -msgid "What you've been looking for?" -msgstr "" - -#: includes/class-freemius.php:2663 -msgid "The %s didn't work as expected" -msgstr "" - -#: includes/class-freemius.php:2665 -msgid "What did you expect?" -msgstr "" - -#: includes/class-freemius.php:3520, templates/debug.php:20 +#: includes/class-freemius.php:3751, templates/debug.php:20 msgid "Freemius Debug" msgstr "" -#: includes/class-freemius.php:4272 -msgid "I don't know what is cURL or how to install it, help me!" -msgstr "" - -#: includes/class-freemius.php:4274 -msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." -msgstr "" - -#: includes/class-freemius.php:4281 -msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." -msgstr "" - -#: includes/class-freemius.php:4386 -msgid "Yes - do your thing" -msgstr "" - -#: includes/class-freemius.php:4391 -msgid "No - just deactivate" -msgstr "" - -#: includes/class-freemius.php:4436, includes/class-freemius.php:4930, includes/class-freemius.php:6191, includes/class-freemius.php:13368, includes/class-freemius.php:14110, includes/class-freemius.php:17542, includes/class-freemius.php:17647, includes/class-freemius.php:17822, includes/class-freemius.php:20056, includes/class-freemius.php:20414, includes/class-freemius.php:20424, includes/class-freemius.php:21109, includes/class-freemius.php:22015, includes/class-freemius.php:22148, includes/class-freemius.php:22304, templates/add-ons.php:57 -msgctxt "exclamation" -msgid "Oops" -msgstr "" - -#: includes/class-freemius.php:4505 -msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." -msgstr "" - -#: includes/class-freemius.php:4927 -msgctxt "addonX cannot run without pluginY" -msgid "%s cannot run without %s." -msgstr "" - -#: includes/class-freemius.php:4928 -msgctxt "addonX cannot run..." -msgid "%s cannot run without the plugin." -msgstr "" - -#: includes/class-freemius.php:5127, includes/class-freemius.php:5152, includes/class-freemius.php:21180 -msgid "Unexpected API error. Please contact the %s's author with the following error." -msgstr "" - -#: includes/class-freemius.php:5857 -msgid "Premium %s version was successfully activated." -msgstr "" - -#: includes/class-freemius.php:5869, includes/class-freemius.php:7774 -msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." -msgid "W00t" -msgstr "" - -#: includes/class-freemius.php:5884 -msgid "You have a %s license." -msgstr "" - -#: includes/class-freemius.php:5888, includes/class-freemius.php:16947, includes/class-freemius.php:16958, includes/class-freemius.php:20325, includes/class-freemius.php:20689, includes/class-freemius.php:20758, includes/class-freemius.php:20930 -msgctxt "interjection expressing joy or exuberance" -msgid "Yee-haw" -msgstr "" - -#: includes/class-freemius.php:6174 -msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." -msgstr "" - -#: includes/class-freemius.php:6178 -msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." -msgstr "" - -#: includes/class-freemius.php:6187, templates/add-ons.php:186, templates/account/partials/addon.php:381 -msgid "More information about %s" -msgstr "" - -#: includes/class-freemius.php:6188 -msgid "Purchase License" -msgstr "" - -#: includes/class-freemius.php:7125, templates/connect.php:171 -msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." -msgstr "" - -#: includes/class-freemius.php:7129 -msgid "start the trial" -msgstr "" - -#: includes/class-freemius.php:7130, templates/connect.php:175 -msgid "complete the install" -msgstr "" - -#: includes/class-freemius.php:7249 -msgid "You are just one step away - %s" -msgstr "" - -#: includes/class-freemius.php:7252 -msgctxt "%s - plugin name. As complete \"PluginX\" activation now" -msgid "Complete \"%s\" Activation Now" -msgstr "" - -#: includes/class-freemius.php:7334 -msgid "We made a few tweaks to the %s, %s" -msgstr "" - -#: includes/class-freemius.php:7338 -msgid "Opt in to make \"%s\" better!" -msgstr "" - -#: includes/class-freemius.php:7773 -msgid "The upgrade of %s was successfully completed." -msgstr "" - -#: includes/class-freemius.php:10255, includes/class-fs-plugin-updater.php:1087, includes/class-fs-plugin-updater.php:1282, includes/class-fs-plugin-updater.php:1289, templates/auto-installation.php:32 -msgid "Add-On" -msgstr "" - -#: includes/class-freemius.php:10257, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549 -msgid "Plugin" -msgstr "" - -#: includes/class-freemius.php:10258, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71 -msgid "Theme" -msgstr "" - -#: includes/class-freemius.php:13188 +#: includes/class-freemius.php:13791 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" -#: includes/class-freemius.php:13202 -msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." -msgstr "" - -#: includes/class-freemius.php:13207 -msgid "User Dashboard" -msgstr "" - -#: includes/class-freemius.php:13208 -msgid "revert it now" -msgstr "" - -#: includes/class-freemius.php:13266 +#: includes/class-freemius.php:13869 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" -#: includes/class-freemius.php:13339 +#: includes/class-freemius.php:13942 msgid "Invalid new user ID or email address." msgstr "" -#: includes/class-freemius.php:13369, includes/class-freemius.php:22259 -msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." -msgstr "" - -#: includes/class-freemius.php:13370, includes/class-freemius.php:22260 -msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." -msgstr "" - -#: includes/class-freemius.php:13377, includes/class-freemius.php:22267 -msgid "Change Ownership" -msgstr "" - -#: includes/class-freemius.php:13977 -msgid "Invalid site details collection." -msgstr "" - -#: includes/class-freemius.php:14097 -msgid "We couldn't find your email address in the system, are you sure it's the right address?" -msgstr "" - -#: includes/class-freemius.php:14099 -msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" -msgstr "" - -#: includes/class-freemius.php:14373 -msgid "Account is pending activation." -msgstr "" - -#: includes/class-freemius.php:14485, templates/forms/premium-versions-upgrade-handler.php:47 -msgid "Buy a license now" -msgstr "" - -#: includes/class-freemius.php:14497, templates/forms/premium-versions-upgrade-handler.php:46 -msgid "Renew your license now" -msgstr "" - -#: includes/class-freemius.php:14501 -msgid "%s to access version %s security & feature updates, and support." -msgstr "" - -#: includes/class-freemius.php:16929 -msgid "%s activation was successfully completed." -msgstr "" - -#: includes/class-freemius.php:16943 -msgid "Your account was successfully activated with the %s plan." -msgstr "" - -#: includes/class-freemius.php:16954, includes/class-freemius.php:20754 -msgid "Your trial has been successfully started." -msgstr "" - -#: includes/class-freemius.php:17540, includes/class-freemius.php:17645, includes/class-freemius.php:17820 -msgid "Couldn't activate %s." -msgstr "" - -#: includes/class-freemius.php:17541, includes/class-freemius.php:17646, includes/class-freemius.php:17821 -msgid "Please contact us with the following message:" -msgstr "" - -#: includes/class-freemius.php:17642, templates/forms/data-debug-mode.php:162 -msgid "An unknown error has occurred." -msgstr "" - -#: includes/class-freemius.php:18178, includes/class-freemius.php:23340 -msgid "Upgrade" -msgstr "" - -#: includes/class-freemius.php:18184 -msgid "Start Trial" -msgstr "" - -#: includes/class-freemius.php:18186 -msgid "Pricing" -msgstr "" - -#: includes/class-freemius.php:18266, includes/class-freemius.php:18268 -msgid "Affiliation" -msgstr "" - -#: includes/class-freemius.php:18296, includes/class-freemius.php:18298, templates/account.php:242, templates/debug.php:324 -msgid "Account" -msgstr "" - -#: includes/class-freemius.php:18312, includes/class-freemius.php:18314, includes/customizer/class-fs-customizer-support-section.php:60 -msgid "Contact Us" -msgstr "" - -#: includes/class-freemius.php:18325, includes/class-freemius.php:18327, includes/class-freemius.php:23354, templates/account.php:121, templates/account/partials/addon.php:44 -msgid "Add-Ons" -msgstr "" - -#: includes/class-freemius.php:18361 -msgctxt "ASCII arrow left icon" -msgid "←" -msgstr "" - -#: includes/class-freemius.php:18361 -msgctxt "ASCII arrow right icon" -msgid "➤" -msgstr "" - -#: includes/class-freemius.php:18363, templates/pricing.php:109 -msgctxt "noun" -msgid "Pricing" -msgstr "" - -#: includes/class-freemius.php:18576, includes/customizer/class-fs-customizer-support-section.php:67 -msgid "Support Forum" -msgstr "" - -#: includes/class-freemius.php:19550 -msgid "Your email has been successfully verified - you are AWESOME!" -msgstr "" - -#: includes/class-freemius.php:19551 -msgctxt "a positive response" -msgid "Right on" -msgstr "" - -#: includes/class-freemius.php:20057 -msgid "seems like the key you entered doesn't match our records." -msgstr "" - -#: includes/class-freemius.php:20081 -msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." -msgstr "" - -#: includes/class-freemius.php:20316 -msgid "Your %s Add-on plan was successfully upgraded." -msgstr "" - -#: includes/class-freemius.php:20318 -msgid "%s Add-on was successfully purchased." -msgstr "" - -#: includes/class-freemius.php:20321 -msgid "Download the latest version" -msgstr "" - -#: includes/class-freemius.php:20407 -msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" -msgstr "" - -#: includes/class-freemius.php:20413, includes/class-freemius.php:20423, includes/class-freemius.php:20889, includes/class-freemius.php:20978 -msgid "Error received from the server:" -msgstr "" - -#: includes/class-freemius.php:20423 -msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." -msgstr "" - -#: includes/class-freemius.php:20651, includes/class-freemius.php:20894, includes/class-freemius.php:20949, includes/class-freemius.php:21056 -msgctxt "something somebody says when they are thinking about what you have just said." -msgid "Hmm" -msgstr "" - -#: includes/class-freemius.php:20664 -msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." -msgstr "" - -#: includes/class-freemius.php:20665, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46 -msgctxt "trial period" -msgid "Trial" -msgstr "" - -#: includes/class-freemius.php:20670 -msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." -msgstr "" - -#: includes/class-freemius.php:20674, includes/class-freemius.php:20733 -msgid "Please contact us here" -msgstr "" - -#: includes/class-freemius.php:20685 -msgid "Your plan was successfully activated." -msgstr "" - -#: includes/class-freemius.php:20686 -msgid "Your plan was successfully upgraded." -msgstr "" - -#: includes/class-freemius.php:20703 -msgid "Your plan was successfully changed to %s." -msgstr "" - -#: includes/class-freemius.php:20719 -msgid "Your license has expired. You can still continue using the free %s forever." -msgstr "" - -#: includes/class-freemius.php:20721 -msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." -msgstr "" - -#: includes/class-freemius.php:20729 -msgid "Your license has been cancelled. If you think it's a mistake, please contact support." -msgstr "" - -#: includes/class-freemius.php:20742 -msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." -msgstr "" - -#: includes/class-freemius.php:20768 -msgid "Your free trial has expired. You can still continue using all our free features." -msgstr "" - -#: includes/class-freemius.php:20770 -msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." -msgstr "" - -#: includes/class-freemius.php:20885 -msgid "It looks like the license could not be activated." -msgstr "" - -#: includes/class-freemius.php:20927 -msgid "Your license was successfully activated." -msgstr "" - -#: includes/class-freemius.php:20953 -msgid "It looks like your site currently doesn't have an active license." -msgstr "" - -#: includes/class-freemius.php:20977 -msgid "It looks like the license deactivation failed." -msgstr "" - -#: includes/class-freemius.php:21006 -msgid "Your %s license was successfully deactivated." -msgstr "" - -#: includes/class-freemius.php:21007 -msgid "Your license was successfully deactivated, you are back to the %s plan." -msgstr "" - -#: includes/class-freemius.php:21010 -msgid "O.K" -msgstr "" - -#: includes/class-freemius.php:21063 -msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." -msgstr "" - -#: includes/class-freemius.php:21072 -msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." -msgstr "" - -#: includes/class-freemius.php:21114 -msgid "You are already running the %s in a trial mode." -msgstr "" - -#: includes/class-freemius.php:21125 -msgid "You already utilized a trial before." -msgstr "" - -#: includes/class-freemius.php:21139 -msgid "Plan %s do not exist, therefore, can't start a trial." -msgstr "" - -#: includes/class-freemius.php:21150 -msgid "Plan %s does not support a trial period." -msgstr "" - -#: includes/class-freemius.php:21161 -msgid "None of the %s's plans supports a trial period." -msgstr "" - -#: includes/class-freemius.php:21211 -msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" -msgstr "" - -#: includes/class-freemius.php:21247 -msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." -msgstr "" - -#: includes/class-freemius.php:21266 -msgid "Your %s free trial was successfully cancelled." -msgstr "" - -#: includes/class-freemius.php:21582 -msgid "Version %s was released." -msgstr "" - -#: includes/class-freemius.php:21582 -msgid "Please download %s." -msgstr "" - -#: includes/class-freemius.php:21589 -msgid "the latest %s version here" -msgstr "" - -#: includes/class-freemius.php:21594 -msgid "New" -msgstr "" - -#: includes/class-freemius.php:21599 -msgid "Seems like you got the latest release." -msgstr "" - -#: includes/class-freemius.php:21600 -msgid "You are all good!" -msgstr "" - -#: includes/class-freemius.php:21903 -msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." -msgstr "" - -#: includes/class-freemius.php:22043 -msgid "Site successfully opted in." -msgstr "" - -#: includes/class-freemius.php:22044, includes/class-freemius.php:23050 -msgid "Awesome" -msgstr "" - -#: includes/class-freemius.php:22060, templates/forms/optout.php:41 -msgid "We appreciate your help in making the %s better by letting us track some usage data." -msgstr "" - -#: includes/class-freemius.php:22061 -msgid "Thank you!" -msgstr "" - -#: includes/class-freemius.php:22068 -msgid "We will no longer be sending any usage data of %s on %s to %s." -msgstr "" - -#: includes/class-freemius.php:22226 -msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." -msgstr "" - -#: includes/class-freemius.php:22232 -msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." -msgstr "" - -#: includes/class-freemius.php:22237 -msgid "%s is the new owner of the account." -msgstr "" - -#: includes/class-freemius.php:22239 -msgctxt "as congratulations" -msgid "Congrats" -msgstr "" - -#: includes/class-freemius.php:22275 -msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." -msgstr "" - -#: includes/class-freemius.php:22287 -msgid "Please provide your full name." -msgstr "" - -#: includes/class-freemius.php:22292 -msgid "Your name was successfully updated." -msgstr "" - -#: includes/class-freemius.php:22353 -msgid "You have successfully updated your %s." -msgstr "" - -#: includes/class-freemius.php:22412 -msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." -msgstr "" - -#: includes/class-freemius.php:22415 -msgid "Click here" -msgstr "" - -#: includes/class-freemius.php:22513 -msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." -msgstr "" - -#: includes/class-freemius.php:22514 -msgctxt "advance notice of something that will need attention." -msgid "Heads up" -msgstr "" - -#: includes/class-freemius.php:23090 -msgctxt "exclamation" -msgid "Hey" -msgstr "" - -#: includes/class-freemius.php:23090 -msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." -msgstr "" - -#: includes/class-freemius.php:23098 -msgid "No commitment for %s days - cancel anytime!" -msgstr "" - -#: includes/class-freemius.php:23099 -msgid "No credit card required" -msgstr "" - -#: includes/class-freemius.php:23106, templates/forms/trial-start.php:53 -msgctxt "call to action" -msgid "Start free trial" -msgstr "" - -#: includes/class-freemius.php:23183 -msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" -msgstr "" - -#: includes/class-freemius.php:23192 -msgid "Learn more" -msgstr "" - -#: includes/class-freemius.php:23378, templates/account.php:558, templates/account.php:708, templates/connect.php:179, templates/connect.php:461, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321 -msgid "Activate License" -msgstr "" - -#: includes/class-freemius.php:23379, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271 -msgid "Change License" -msgstr "" - -#: includes/class-freemius.php:23500, templates/account/partials/site.php:169 -msgid "Opt Out" -msgstr "" - -#: includes/class-freemius.php:23502, includes/class-freemius.php:23508, templates/account/partials/site.php:49, templates/account/partials/site.php:169 -msgid "Opt In" -msgstr "" - -#: includes/class-freemius.php:23738 -msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" -msgstr "" - -#: includes/class-freemius.php:23746 -msgid "Activate %s features" -msgstr "" - -#: includes/class-freemius.php:23759 -msgid "Please follow these steps to complete the upgrade" -msgstr "" - -#: includes/class-freemius.php:23763 -msgid "Download the latest %s version" -msgstr "" - -#: includes/class-freemius.php:23767 -msgid "Upload and activate the downloaded version" -msgstr "" - -#: includes/class-freemius.php:23769 -msgid "How to upload and activate?" -msgstr "" - -#: includes/class-freemius.php:23903 -msgid "%sClick here%s to choose the sites where you'd like to activate the license on." -msgstr "" - -#: includes/class-freemius.php:24072 -msgid "Auto installation only works for opted-in users." -msgstr "" - -#: includes/class-freemius.php:24082, includes/class-freemius.php:24115, includes/class-fs-plugin-updater.php:1261, includes/class-fs-plugin-updater.php:1275 -msgid "Invalid module ID." -msgstr "" - -#: includes/class-freemius.php:24091, includes/class-fs-plugin-updater.php:1297 -msgid "Premium version already active." -msgstr "" - -#: includes/class-freemius.php:24098 -msgid "You do not have a valid license to access the premium version." -msgstr "" - -#: includes/class-freemius.php:24105 -msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." -msgstr "" - -#: includes/class-freemius.php:24123, includes/class-fs-plugin-updater.php:1296 -msgid "Premium add-on version already installed." -msgstr "" - -#: includes/class-freemius.php:24473 -msgid "View paid features" -msgstr "" - -#: includes/class-freemius.php:24795 -msgid "Thank you so much for using %s and its add-ons!" -msgstr "" - -#: includes/class-freemius.php:24796 -msgid "Thank you so much for using %s!" -msgstr "" - -#: includes/class-freemius.php:24802 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." -msgstr "" - -#: includes/class-freemius.php:24806 -msgid "Thank you so much for using our products!" -msgstr "" - -#: includes/class-freemius.php:24807 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." -msgstr "" - -#: includes/class-freemius.php:24826 -msgid "%s and its add-ons" -msgstr "" - -#: includes/class-freemius.php:24835 -msgid "Products" -msgstr "" - -#: includes/class-freemius.php:24842, templates/connect.php:275 -msgid "Yes" -msgstr "" - -#: includes/class-freemius.php:24843, templates/connect.php:276 -msgid "send me security & feature updates, educational content and offers." -msgstr "" - -#: includes/class-freemius.php:24844, templates/connect.php:281 -msgid "No" -msgstr "" - -#: includes/class-freemius.php:24846, templates/connect.php:283 -msgid "do %sNOT%s send me security & feature updates, educational content and offers." -msgstr "" - -#: includes/class-freemius.php:24856 -msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" -msgstr "" - -#: includes/class-freemius.php:24858, templates/connect.php:290 -msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" -msgstr "" - -#: includes/class-freemius.php:25140 -msgid "License key is empty." +#: includes/class-freemius.php:23326 +msgid "Bundle" msgstr "" #: includes/class-fs-plugin-updater.php:206, templates/forms/premium-versions-upgrade-handler.php:57 @@ -816,31 +53,23 @@ msgstr "" msgid "Buy license" msgstr "" -#: includes/class-fs-plugin-updater.php:327, includes/class-fs-plugin-updater.php:360 +#: includes/class-fs-plugin-updater.php:364, includes/class-fs-plugin-updater.php:331 msgid "There is a %s of %s available." msgstr "" -#: includes/class-fs-plugin-updater.php:329, includes/class-fs-plugin-updater.php:365 +#: includes/class-fs-plugin-updater.php:369, includes/class-fs-plugin-updater.php:333 msgid "new Beta version" msgstr "" -#: includes/class-fs-plugin-updater.php:330, includes/class-fs-plugin-updater.php:366 +#: includes/class-fs-plugin-updater.php:370, includes/class-fs-plugin-updater.php:334 msgid "new version" msgstr "" -#: includes/class-fs-plugin-updater.php:389 +#: includes/class-fs-plugin-updater.php:393 msgid "Important Upgrade Notice:" msgstr "" -#: includes/class-fs-plugin-updater.php:1326 -msgid "Installing plugin: %s" -msgstr "" - -#: includes/class-fs-plugin-updater.php:1367 -msgid "Unable to connect to the filesystem. Please confirm your credentials." -msgstr "" - -#: includes/class-fs-plugin-updater.php:1549 +#: includes/class-fs-plugin-updater.php:1551 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" @@ -848,29 +77,30 @@ msgstr "" msgid "Purchase More" msgstr "" -#: includes/fs-plugin-info-dialog.php:542, templates/account/partials/addon.php:385 +#: includes/fs-plugin-info-dialog.php:542, templates/account/partials/addon.php:390 msgctxt "verb" msgid "Purchase" msgstr "" +#. translators: %s: N-days trial #: includes/fs-plugin-info-dialog.php:546 msgid "Start my free %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:744 -msgid "Install Free Version Update Now" +#: includes/fs-plugin-info-dialog.php:754 +msgid "Install Free Version Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:745, templates/account.php:641 -msgid "Install Update Now" +#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:423, templates/account/partials/addon.php:370 +msgid "Install Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:754 -msgid "Install Free Version Now" +#: includes/fs-plugin-info-dialog.php:744 +msgid "Install Free Version Update Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:365, templates/account/partials/addon.php:418 -msgid "Install Now" +#: includes/fs-plugin-info-dialog.php:745, templates/account.php:650 +msgid "Install Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:771 @@ -878,20 +108,20 @@ msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:772, templates/account.php:101, templates/add-ons.php:37, templates/account/partials/addon.php:25 +#: includes/fs-plugin-info-dialog.php:772, templates/account.php:109, templates/add-ons.php:37, templates/account/partials/addon.php:30 msgctxt "as download latest version" msgid "Download Latest" msgstr "" -#: includes/fs-plugin-info-dialog.php:787, templates/add-ons.php:329, templates/account/partials/addon.php:356, templates/account/partials/addon.php:412 +#: includes/fs-plugin-info-dialog.php:787, templates/add-ons.php:329, templates/account/partials/addon.php:417, templates/account/partials/addon.php:361 msgid "Activate this add-on" msgstr "" -#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:458 +#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:483 msgid "Activate Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:790, templates/account.php:125, templates/add-ons.php:330, templates/account/partials/addon.php:48 +#: includes/fs-plugin-info-dialog.php:790, templates/account.php:133, templates/add-ons.php:330, templates/account/partials/addon.php:53 msgid "Activate" msgstr "" @@ -1004,6 +234,7 @@ msgctxt "noun" msgid "Price" msgstr "" +#. translators: %s: Discount (e.g. discount of $5 or 10%) #: includes/fs-plugin-info-dialog.php:1290 msgid "Save %s" msgstr "" @@ -1020,7 +251,7 @@ msgstr "" msgid "Details" msgstr "" -#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:112, templates/debug.php:201, templates/debug.php:238, templates/debug.php:455, templates/account/partials/addon.php:36 +#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:120, templates/debug.php:215, templates/debug.php:252, templates/debug.php:466, templates/account/partials/addon.php:41 msgctxt "product version" msgid "Version" msgstr "" @@ -1034,7 +265,8 @@ msgstr "" msgid "Last Updated" msgstr "" -#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:527 +#. translators: %s: time period (e.g. "2 hours" ago) +#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:536 msgctxt "x-ago" msgid "%s ago" msgstr "" @@ -1055,10 +287,12 @@ msgstr "" msgid "Downloaded" msgstr "" +#. translators: %s: 1 or One (Number of times downloaded) #: includes/fs-plugin-info-dialog.php:1366 msgid "%s time" msgstr "" +#. translators: %s: Number of times downloaded #: includes/fs-plugin-info-dialog.php:1368 msgid "%s times" msgstr "" @@ -1083,22 +317,27 @@ msgstr "" msgid "based on %s" msgstr "" +#. translators: %s: 1 or One #: includes/fs-plugin-info-dialog.php:1415 msgid "%s rating" msgstr "" +#. translators: %s: Number larger than 1 #: includes/fs-plugin-info-dialog.php:1417 msgid "%s ratings" msgstr "" +#. translators: %s: 1 or One #: includes/fs-plugin-info-dialog.php:1432 msgid "%s star" msgstr "" +#. translators: %s: Number larger than 1 #: includes/fs-plugin-info-dialog.php:1434 msgid "%s stars" msgstr "" +#. translators: %s: # of stars (e.g. 5 stars) #: includes/fs-plugin-info-dialog.php:1446 msgid "Click to see reviews that provided a rating of %s" msgstr "" @@ -1107,18 +346,18 @@ msgstr "" msgid "Contributors" msgstr "" -#: includes/fs-plugin-info-dialog.php:1489, includes/fs-plugin-info-dialog.php:1491 +#: includes/fs-plugin-info-dialog.php:1491, includes/fs-plugin-info-dialog.php:1489 msgid "Warning" msgstr "" -#: includes/fs-plugin-info-dialog.php:1489 -msgid "This plugin has not been tested with your current version of WordPress." -msgstr "" - #: includes/fs-plugin-info-dialog.php:1491 msgid "This plugin has not been marked as compatible with your version of WordPress." msgstr "" +#: includes/fs-plugin-info-dialog.php:1489 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "" + #: includes/fs-plugin-info-dialog.php:1510 msgid "Paid add-on must be deployed to Freemius." msgstr "" @@ -1127,14 +366,6 @@ msgstr "" msgid "Add-on must be deployed to WordPress.org or Freemius." msgstr "" -#: includes/fs-plugin-info-dialog.php:1532 -msgid "Newer Version (%s) Installed" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1533 -msgid "Newer Free Version (%s) Installed" -msgstr "" - #: includes/fs-plugin-info-dialog.php:1540 msgid "Latest Version Installed" msgstr "" @@ -1143,293 +374,330 @@ msgstr "" msgid "Latest Free Version Installed" msgstr "" -#: templates/account.php:102, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:311 +#: includes/fs-plugin-info-dialog.php:1532 +msgid "Newer Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1533 +msgid "Newer Free Version (%s) Installed" +msgstr "" + +#: templates/account.php:110, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:311 msgid "Downgrading your plan" msgstr "" -#: templates/account.php:103, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:312 +#: templates/account.php:111, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:312 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account.php:105, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 +#: templates/account.php:113, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" -#: templates/account.php:106, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:315 +#: templates/account.php:114, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:315 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" -#: templates/account.php:107, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +#: templates/account.php:115, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" -#: templates/account.php:108, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:316 +#: templates/account.php:116, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:316 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" -#: templates/account.php:109, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:317 +#: templates/account.php:117, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:317 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") -#: templates/account.php:111, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +#: templates/account.php:119, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") -#: templates/account.php:114, templates/account/partials/addon.php:38, templates/account/partials/site.php:291 +#: templates/account.php:122, templates/account/partials/addon.php:43, templates/account/partials/site.php:291 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") -#: templates/account.php:116, templates/account/partials/addon.php:40, templates/account/partials/site.php:293 +#: templates/account.php:124, templates/account/partials/addon.php:45, templates/account/partials/site.php:293 msgid "Expires in %s" msgstr "" -#: templates/account.php:117 +#: templates/account.php:125 msgctxt "as synchronize license" msgid "Sync License" msgstr "" -#: templates/account.php:118, templates/account/partials/addon.php:41 +#: templates/account.php:126, templates/account/partials/addon.php:46 msgid "Cancel Trial" msgstr "" -#: templates/account.php:119, templates/account/partials/addon.php:42 +#: templates/account.php:127, templates/account/partials/addon.php:47 msgid "Change Plan" msgstr "" -#: templates/account.php:120, templates/account/partials/addon.php:43 +#: templates/account.php:128, templates/account/partials/addon.php:48 msgctxt "verb" msgid "Upgrade" msgstr "" -#: templates/account.php:122, templates/account/partials/addon.php:45, templates/account/partials/site.php:318 +#: templates/account.php:129, templates/account/partials/addon.php:49 +msgid "Add-Ons" +msgstr "" + +#: templates/account.php:130, templates/account/partials/addon.php:50, templates/account/partials/site.php:318 msgctxt "verb" msgid "Downgrade" msgstr "" -#: templates/account.php:124, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:47, templates/account/partials/site.php:33 +#: templates/account.php:131, templates/add-ons.php:250, templates/account/partials/addon.php:51 +msgctxt "trial period" +msgid "Trial" +msgstr "" + +#: templates/account.php:132, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 msgid "Free" msgstr "" -#: templates/account.php:126, templates/debug.php:371, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:49 +#: templates/account.php:134, templates/debug.php:385, templates/account/partials/addon.php:54 msgctxt "as product pricing plan" msgid "Plan" msgstr "" -#: templates/account.php:127 +#: templates/account.php:135 msgid "Bundle Plan" msgstr "" -#: templates/account.php:250 +#: templates/account.php:251, templates/debug.php:338 +msgid "Account" +msgstr "" + +#: templates/account.php:259 msgid "Free Trial" msgstr "" -#: templates/account.php:261 +#: templates/account.php:270 msgid "Account Details" msgstr "" -#: templates/account.php:268, templates/forms/data-debug-mode.php:33 -msgid "Start Debug" +#: templates/account.php:279 +msgid "Stop Debug" msgstr "" -#: templates/account.php:270 -msgid "Stop Debug" +#: templates/account.php:277, templates/forms/data-debug-mode.php:33 +msgid "Start Debug" msgstr "" -#: templates/account.php:277 +#: templates/account.php:286 msgid "Billing & Invoices" msgstr "" -#: templates/account.php:288 -msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +#: templates/account.php:299 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" msgstr "" -#: templates/account.php:290 -msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +#: templates/account.php:297 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" msgstr "" -#: templates/account.php:293 +#: templates/account.php:302 msgid "Delete Account" msgstr "" -#: templates/account.php:305, templates/account/partials/addon.php:231, templates/account/partials/deactivate-license-button.php:35 +#: templates/account.php:314, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" -#: templates/account.php:328, templates/forms/subscription-cancellation.php:125 +#: templates/account.php:337, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" -#: templates/account.php:328, templates/account/partials/addon.php:255 +#: templates/account.php:337, templates/account/partials/addon.php:260 msgid "Cancel Subscription" msgstr "" -#: templates/account.php:357, templates/account/partials/addon.php:340 +#: templates/account.php:366, templates/account/partials/addon.php:345 msgctxt "as synchronize" msgid "Sync" msgstr "" -#: templates/account.php:372, templates/debug.php:505 +#: templates/account.php:381, templates/debug.php:523 msgid "Name" msgstr "" -#: templates/account.php:378, templates/debug.php:506 +#: templates/account.php:387, templates/debug.php:524 msgid "Email" msgstr "" -#: templates/account.php:385, templates/debug.php:369, templates/debug.php:555 +#: templates/account.php:394, templates/debug.php:383, templates/debug.php:573 msgid "User ID" msgstr "" -#: templates/account.php:403, templates/account.php:721, templates/account.php:754, templates/debug.php:236, templates/debug.php:363, templates/debug.php:452, templates/debug.php:504, templates/debug.php:553, templates/debug.php:632, templates/account/payments.php:35, templates/debug/logger.php:21 +#: templates/account.php:403, templates/account.php:411, templates/debug.php:372, templates/debug.php:567 +msgid "Plugin" +msgstr "" + +#: templates/account.php:404, templates/account.php:412, templates/debug.php:372, templates/debug.php:567, templates/forms/deactivation/form.php:107 +msgid "Theme" +msgstr "" + +#: templates/account.php:412, templates/account.php:732, templates/account.php:783, templates/debug.php:250, templates/debug.php:377, templates/debug.php:463, templates/debug.php:522, templates/debug.php:571, templates/debug.php:650, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" -#: templates/account.php:410 +#: templates/account.php:419 msgid "Site ID" msgstr "" -#: templates/account.php:413 +#: templates/account.php:422 msgid "No ID" msgstr "" -#: templates/account.php:418, templates/debug.php:243, templates/debug.php:372, templates/debug.php:456, templates/debug.php:508, templates/account/partials/site.php:227 +#: templates/account.php:427, templates/debug.php:257, templates/debug.php:386, templates/debug.php:467, templates/debug.php:526, templates/account/partials/site.php:227 msgid "Public Key" msgstr "" -#: templates/account.php:424, templates/debug.php:373, templates/debug.php:457, templates/debug.php:509, templates/account/partials/site.php:239 +#: templates/account.php:433, templates/debug.php:387, templates/debug.php:468, templates/debug.php:527, templates/account/partials/site.php:239 msgid "Secret Key" msgstr "" -#: templates/account.php:427 +#: templates/account.php:436 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" -#: templates/account.php:454, templates/account/partials/site.php:120, templates/account/partials/site.php:122 -msgid "Trial" +#: templates/account.php:490, templates/debug.php:579, templates/account/partials/site.php:260 +msgid "License Key" msgstr "" -#: templates/account.php:481, templates/debug.php:561, templates/account/partials/site.php:260 -msgid "License Key" +#: templates/account.php:463, templates/account/partials/site.php:122, templates/account/partials/site.php:120 +msgid "Trial" msgstr "" -#: templates/account.php:512 +#: templates/account.php:521 msgid "Join the Beta program" msgstr "" -#: templates/account.php:518 +#: templates/account.php:527 msgid "not verified" msgstr "" -#: templates/account.php:527, templates/account/partials/addon.php:190 -msgid "Expired" +#: templates/account.php:598 +msgid "Free version" msgstr "" -#: templates/account.php:587 +#: templates/account.php:596 msgid "Premium version" msgstr "" -#: templates/account.php:589 -msgid "Free version" +#: templates/account.php:536, templates/account/partials/addon.php:195 +msgid "Expired" +msgstr "" + +#: templates/account.php:567, templates/account.php:719, templates/connect.php:198, templates/connect.php:486, includes/managers/class-fs-clone-manager.php:1123, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 +msgid "Activate License" msgstr "" -#: templates/account.php:601 +#: templates/account.php:610 msgid "Verify Email" msgstr "" -#: templates/account.php:615 -msgid "Download %s Version" +#: templates/account.php:687, templates/forms/user-change.php:27 +msgid "Change User" msgstr "" -#: templates/account.php:631 -msgid "Download Paid Version" +#: templates/account.php:674 +msgid "What is your %s?" msgstr "" -#: templates/account.php:649, templates/account.php:892, templates/account/partials/site.php:248, templates/account/partials/site.php:270 +#: templates/account.php:682, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "" + +#: templates/account.php:658, templates/account.php:921, templates/account/partials/site.php:248, templates/account/partials/site.php:270 msgctxt "verb" msgid "Show" msgstr "" -#: templates/account.php:664 -msgid "What is your %s?" +#: templates/account.php:661, templates/account.php:718, templates/account/partials/addon.php:327, templates/account/partials/site.php:271 +msgid "Change License" msgstr "" -#: templates/account.php:672, templates/account/billing.php:21 -msgctxt "verb" -msgid "Edit" +#: templates/account.php:624 +msgid "Download %s Version" msgstr "" -#: templates/account.php:676, templates/forms/user-change.php:27 -msgid "Change User" +#: templates/account.php:640 +msgid "Download Paid Version" msgstr "" -#: templates/account.php:700 +#: templates/account.php:711 msgid "Sites" msgstr "" -#: templates/account.php:713 +#: templates/account.php:724 msgid "Search by address" msgstr "" -#: templates/account.php:722, templates/debug.php:366 +#: templates/account.php:733, templates/debug.php:380 msgid "Address" msgstr "" -#: templates/account.php:723 +#: templates/account.php:734 msgid "License" msgstr "" -#: templates/account.php:724 +#: templates/account.php:735 msgid "Plan" msgstr "" -#: templates/account.php:757 +#: templates/account.php:786 msgctxt "as software license" msgid "License" msgstr "" -#: templates/account.php:886 +#: templates/account.php:915 msgctxt "verb" msgid "Hide" msgstr "" -#: templates/account.php:908, templates/forms/data-debug-mode.php:31 +#: templates/account.php:937, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 msgid "Processing" msgstr "" -#: templates/account.php:911 +#: templates/account.php:940 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" -#: templates/account.php:969 +#: templates/account.php:998 msgid "Cancelling %s" msgstr "" -#: templates/account.php:969, templates/account.php:986, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +#: templates/account.php:998, templates/account.php:1015, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 msgid "trial" msgstr "" -#: templates/account.php:984, templates/forms/deactivation/form.php:150 +#: templates/account.php:1013, templates/forms/deactivation/form.php:195 msgid "Cancelling %s..." msgstr "" -#: templates/account.php:987, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +#: templates/account.php:1016, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 msgid "subscription" msgstr "" -#: templates/account.php:1001 +#: templates/account.php:1030 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" -#: templates/account.php:1075 +#: templates/account.php:1104 msgid "Disabling white-label mode" msgstr "" -#: templates/account.php:1076 +#: templates/account.php:1105 msgid "Enabling white-label mode" msgstr "" @@ -1441,10 +709,19 @@ msgstr "" msgid "Add Ons for %s" msgstr "" +#: templates/add-ons.php:57 +msgctxt "exclamation" +msgid "Oops" +msgstr "" + #: templates/add-ons.php:58 msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." msgstr "" +#: templates/add-ons.php:186, templates/account/partials/addon.php:386 +msgid "More information about %s" +msgstr "" + #: templates/add-ons.php:229 msgctxt "active add-on" msgid "Active" @@ -1455,11 +732,16 @@ msgctxt "installed add-on" msgid "Installed" msgstr "" -#: templates/admin-notice.php:13, templates/forms/license-activation.php:222, templates/forms/resend-key.php:77 +#: templates/admin-notice.php:13, templates/forms/license-activation.php:250, templates/forms/resend-key.php:80 msgctxt "as close a window" msgid "Dismiss" msgstr "" +#: templates/auto-installation.php:32 +msgid "Add-On" +msgstr "" + +#. translators: %s: Number of seconds #: templates/auto-installation.php:45 msgid "%s sec" msgstr "" @@ -1480,215 +762,147 @@ msgstr "" msgid "Cancel Installation" msgstr "" -#: templates/checkout.php:180 -msgid "Checkout" -msgstr "" - -#: templates/checkout.php:180 -msgid "PCI compliant" -msgstr "" - #. translators: %s: name (e.g. Hey John,) -#: templates/connect.php:112 +#: templates/connect.php:121 msgctxt "greeting" msgid "Hey %s," msgstr "" -#: templates/connect.php:162 +#: templates/connect.php:181 msgid "Allow & Continue" msgstr "" -#: templates/connect.php:166 -msgid "Re-send activation email" +#: templates/connect.php:210, templates/connect.php:217 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." msgstr "" -#: templates/connect.php:170 -msgid "Thanks %s!" +#: templates/connect.php:211, templates/connect.php:218 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:221 +msgid "If you skip this, that's okay! %1$s will still work just fine." msgstr "" -#: templates/connect.php:180, templates/forms/license-activation.php:46 +#: templates/connect.php:199, templates/forms/license-activation.php:46 msgid "Agree & Activate License" msgstr "" -#: templates/connect.php:184 +#: templates/connect.php:203 msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" -#: templates/connect.php:191 -msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +#: templates/connect.php:185 +msgid "Re-send activation email" msgstr "" -#: templates/connect.php:192 -msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +#: templates/connect.php:189 +msgid "Thanks %s!" msgstr "" -#: templates/connect.php:198 -msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +#: templates/connect.php:190 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." msgstr "" -#: templates/connect.php:199 -msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +#: templates/connect.php:194 +msgid "complete the install" msgstr "" -#: templates/connect.php:233 +#: templates/connect.php:251 msgid "We're excited to introduce the Freemius network-level integration." msgstr "" -#: templates/connect.php:236 +#: templates/connect.php:265 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "" + +#: templates/connect.php:254 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" -#: templates/connect.php:238 +#: templates/connect.php:256 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" -#: templates/connect.php:240 +#: templates/connect.php:258 msgid "%s's paid features" msgstr "" -#: templates/connect.php:245 +#: templates/connect.php:263 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" -#: templates/connect.php:247 -msgid "During the update process we detected %s site(s) in the network that are still pending your attention." -msgstr "" - -#: templates/connect.php:256, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 +#: templates/connect.php:274, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 msgid "License key" msgstr "" -#: templates/connect.php:259, templates/forms/license-activation.php:22 +#: templates/connect.php:277, templates/forms/license-activation.php:22 msgid "Can't find your license key?" msgstr "" -#: templates/connect.php:318, templates/connect.php:700, templates/forms/deactivation/retry-skip.php:20 +#: templates/connect.php:308 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "" + +#: templates/connect.php:340, templates/connect.php:730, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" -#: templates/connect.php:321 +#: templates/connect.php:343 msgid "Delegate to Site Admins" msgstr "" -#: templates/connect.php:321 +#: templates/connect.php:343 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" -#: templates/connect.php:346 +#: templates/connect.php:368 msgid "License issues?" msgstr "" -#: templates/connect.php:362 -msgid "Your Profile Overview" -msgstr "" - -#: templates/connect.php:363 -msgid "Name and email address" -msgstr "" - -#: templates/connect.php:370 -msgid "So you can manage and control your license remotely from the User Dashboard." -msgstr "" - -#: templates/connect.php:371 -msgid "Your Site Overview" -msgstr "" - -#: templates/connect.php:372 -msgid "Site URL, WP version, PHP info" -msgstr "" - -#: templates/connect.php:379 -msgid "Admin Notices" -msgstr "" - -#: templates/connect.php:380, templates/connect.php:398 -msgid "Updates, announcements, marketing, no spam" -msgstr "" - -#: templates/connect.php:387 -msgid "So you can reuse the license when the %s is no longer active." -msgstr "" - -#: templates/connect.php:388 -msgid "Current %s Status" -msgstr "" - -#: templates/connect.php:389 -msgid "Active, deactivated, or uninstalled" -msgstr "" - -#: templates/connect.php:397 -msgid "Newsletter" -msgstr "" - -#: templates/connect.php:405 -msgid "Plugins & Themes" -msgstr "" - -#: templates/connect.php:405 -msgid "optional" -msgstr "" - -#: templates/connect.php:406 -msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts." -msgstr "" - -#: templates/connect.php:407 -msgid "Title, slug, version, and is active" +#: templates/connect.php:454 +msgid "What permissions are being granted?" msgstr "" -#: templates/connect.php:424 +#: templates/connect.php:448 msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management." msgstr "" -#: templates/connect.php:426 +#: templates/connect.php:450 msgid "diagnostic data" msgstr "" -#: templates/connect.php:427 -msgid "Freemius is our licensing and software updates engine" -msgstr "" - -#: templates/connect.php:430 -msgid "What permissions are being granted?" +#: templates/connect.php:485 +msgid "Have a license key?" msgstr "" -#: templates/connect.php:457 +#: templates/connect.php:482 msgid "Don't have a license key?" msgstr "" -#: templates/connect.php:460 -msgid "Have a license key?" -msgstr "" - -#: templates/connect.php:468 +#: templates/connect.php:493 msgid "Privacy Policy" msgstr "" -#: templates/connect.php:470 +#: templates/connect.php:495 msgid "License Agreement" msgstr "" -#: templates/connect.php:470 +#: templates/connect.php:495 msgid "Terms of Service" msgstr "" -#: templates/connect.php:866 +#: templates/connect.php:896 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" -#: templates/connect.php:867 +#: templates/connect.php:897 msgctxt "as activating plugin" msgid "Activating" msgstr "" -#: templates/contact.php:78 -msgid "Contact" -msgstr "" - #: templates/debug.php:17 msgctxt "as turned off" msgid "Off" @@ -1708,237 +922,234 @@ msgctxt "as code debugging" msgid "Debugging" msgstr "" -#: templates/debug.php:52, templates/debug.php:248, templates/debug.php:374, templates/debug.php:510 +#: templates/debug.php:54, templates/debug.php:262, templates/debug.php:388, templates/debug.php:528 msgid "Actions" msgstr "" -#: templates/debug.php:62 +#: templates/debug.php:64 msgid "Are you sure you want to delete all Freemius data?" msgstr "" -#: templates/debug.php:62 +#: templates/debug.php:64 msgid "Delete All Accounts" msgstr "" -#: templates/debug.php:69 +#: templates/debug.php:71 msgid "Clear API Cache" msgstr "" -#: templates/debug.php:77 +#: templates/debug.php:79 msgid "Clear Updates Transients" msgstr "" -#: templates/debug.php:84 +#: templates/debug.php:88 +msgid "Reset Deactivation Snoozing" +msgstr "" + +#: templates/debug.php:96 msgid "Sync Data From Server" msgstr "" -#: templates/debug.php:93 +#: templates/debug.php:105 msgid "Migrate Options to Network" msgstr "" -#: templates/debug.php:98 +#: templates/debug.php:110 msgid "Load DB Option" msgstr "" -#: templates/debug.php:101 +#: templates/debug.php:113 msgid "Set DB Option" msgstr "" -#: templates/debug.php:180 +#: templates/debug.php:194 msgid "Key" msgstr "" -#: templates/debug.php:181 +#: templates/debug.php:195 msgid "Value" msgstr "" -#: templates/debug.php:197 +#: templates/debug.php:211 msgctxt "as software development kit versions" msgid "SDK Versions" msgstr "" -#: templates/debug.php:202 +#: templates/debug.php:216 msgid "SDK Path" msgstr "" -#: templates/debug.php:203, templates/debug.php:242 +#: templates/debug.php:217, templates/debug.php:256 msgid "Module Path" msgstr "" -#: templates/debug.php:204 +#: templates/debug.php:218 msgid "Is Active" msgstr "" -#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:35 +#: templates/debug.php:246, templates/debug/plugins-themes-sync.php:35 msgid "Plugins" msgstr "" -#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:56 +#: templates/debug.php:246, templates/debug/plugins-themes-sync.php:56 msgid "Themes" msgstr "" -#: templates/debug.php:237, templates/debug.php:368, templates/debug.php:454, templates/debug/scheduled-crons.php:80 +#: templates/debug.php:251, templates/debug.php:382, templates/debug.php:465, templates/debug/scheduled-crons.php:80 msgid "Slug" msgstr "" -#: templates/debug.php:239, templates/debug.php:453 +#: templates/debug.php:253, templates/debug.php:464 msgid "Title" msgstr "" -#: templates/debug.php:240 +#: templates/debug.php:254 msgctxt "as application program interface" msgid "API" msgstr "" -#: templates/debug.php:241 +#: templates/debug.php:255 msgid "Freemius State" msgstr "" -#: templates/debug.php:245 +#: templates/debug.php:259 msgid "Network Blog" msgstr "" -#: templates/debug.php:246 +#: templates/debug.php:260 msgid "Network User" msgstr "" -#: templates/debug.php:283 +#: templates/debug.php:297 msgctxt "as connection was successful" msgid "Connected" msgstr "" -#: templates/debug.php:284 +#: templates/debug.php:298 msgctxt "as connection blocked" msgid "Blocked" msgstr "" -#: templates/debug.php:320 +#: templates/debug.php:334 msgid "Simulate Trial Promotion" msgstr "" -#: templates/debug.php:332 +#: templates/debug.php:346 msgid "Simulate Network Upgrade" msgstr "" -#: templates/debug.php:357 +#. translators: %s: 'plugin' or 'theme' +#: templates/debug.php:371 msgid "%s Installs" msgstr "" -#: templates/debug.php:359 +#: templates/debug.php:373 msgctxt "like websites" msgid "Sites" msgstr "" -#: templates/debug.php:365, templates/account/partials/site.php:156 +#: templates/debug.php:379, templates/account/partials/site.php:156 msgid "Blog ID" msgstr "" -#: templates/debug.php:370 +#: templates/debug.php:384 msgid "License ID" msgstr "" -#: templates/debug.php:434, templates/debug.php:533, templates/account/partials/addon.php:435 +#: templates/debug.php:445, templates/debug.php:551, templates/account/partials/addon.php:440 msgctxt "verb" msgid "Delete" msgstr "" -#: templates/debug.php:448 +#: templates/debug.php:459 msgid "Add Ons of module %s" msgstr "" -#: templates/debug.php:500 +#: templates/debug.php:518 msgid "Users" msgstr "" -#: templates/debug.php:507 +#: templates/debug.php:525 msgid "Verified" msgstr "" -#: templates/debug.php:549 +#: templates/debug.php:567 msgid "%s Licenses" msgstr "" -#: templates/debug.php:554 +#: templates/debug.php:572 msgid "Plugin ID" msgstr "" -#: templates/debug.php:556 +#: templates/debug.php:574 msgid "Plan ID" msgstr "" -#: templates/debug.php:557 +#: templates/debug.php:575 msgid "Quota" msgstr "" -#: templates/debug.php:558 +#: templates/debug.php:576 msgid "Activated" msgstr "" -#: templates/debug.php:559 +#: templates/debug.php:577 msgid "Blocking" msgstr "" -#: templates/debug.php:560, templates/debug.php:631, templates/debug/logger.php:22 +#: templates/debug.php:578, templates/debug.php:649, templates/debug/logger.php:22 msgid "Type" msgstr "" -#: templates/debug.php:562 +#: templates/debug.php:580 msgctxt "as expiration date" msgid "Expiration" msgstr "" -#: templates/debug.php:590 +#: templates/debug.php:608 msgid "Debug Log" msgstr "" -#: templates/debug.php:594 +#: templates/debug.php:612 msgid "All Types" msgstr "" -#: templates/debug.php:601 +#: templates/debug.php:619 msgid "All Requests" msgstr "" -#: templates/debug.php:606, templates/debug.php:635, templates/debug/logger.php:25 +#: templates/debug.php:624, templates/debug.php:653, templates/debug/logger.php:25 msgid "File" msgstr "" -#: templates/debug.php:607, templates/debug.php:633, templates/debug/logger.php:23 +#: templates/debug.php:625, templates/debug.php:651, templates/debug/logger.php:23 msgid "Function" msgstr "" -#: templates/debug.php:608 +#: templates/debug.php:626 msgid "Process ID" msgstr "" -#: templates/debug.php:609 +#: templates/debug.php:627 msgid "Logger" msgstr "" -#: templates/debug.php:610, templates/debug.php:634, templates/debug/logger.php:24 +#: templates/debug.php:628, templates/debug.php:652, templates/debug/logger.php:24 msgid "Message" msgstr "" -#: templates/debug.php:612 +#: templates/debug.php:630 msgid "Filter" msgstr "" -#: templates/debug.php:620 +#: templates/debug.php:638 msgid "Download" msgstr "" -#: templates/debug.php:636, templates/debug/logger.php:26 +#: templates/debug.php:654, templates/debug/logger.php:26 msgid "Timestamp" msgstr "" -#: templates/secure-https-header.php:28 -msgid "Secure HTTPS %s page, running from an external domain" -msgstr "" - -#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 -msgid "Support" -msgstr "" - #: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 msgctxt "milliseconds" msgid "ms" @@ -1952,6 +1163,120 @@ msgstr "" msgid "Requests" msgstr "" +#: includes/managers/class-fs-clone-manager.php:703 +msgid "Invalid clone resolution action." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:851 +msgid "products" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1039 +msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1040 +msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1033 +msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1066 +msgid "the above-mentioned sites" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1079 +msgid "Is %2$s a duplicate of %4$s?" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1080 +msgid "Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1085 +msgid "Long-Term Duplicate" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1090 +msgid "Duplicate Website" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1096 +msgid "Is %2$s the new home of %4$s?" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1098 +msgid "Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1099, templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1099 +msgid "data" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1105 +msgid "Migrate License" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1106 +msgid "Migrate" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1112 +msgid "Is %2$s a new website?" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1113 +msgid "Yes, %2$s is a new and different website that is separate from %4$s." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1115 +msgid "It requires license activation." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1122 +msgid "New Website" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1145 +msgctxt "Clone resolution admin notice products list label" +msgid "Products" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1230 +msgid "You marked this website, %s, as a temporary duplicate of %s." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1231 +msgid "You marked this website, %s, as a temporary duplicate of these sites" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1245 +msgid "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first)." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1248 +msgctxt "\"The \", e.g.: \"The plugin\"" +msgid "The %s's" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1251 +msgid "The following products'" +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1259 +msgid "If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s." +msgstr "" + +#: includes/managers/class-fs-clone-manager.php:1261 +msgid "activate a license here" +msgstr "" + #: templates/account/billing.php:22 msgctxt "verb" msgid "Update" @@ -2105,143 +1430,143 @@ msgstr "" msgid "Next" msgstr "" -#: templates/forms/affiliation.php:82 +#: templates/forms/affiliation.php:83 msgid "Non-expiring" msgstr "" -#: templates/forms/affiliation.php:85 +#: templates/forms/affiliation.php:86 msgid "Apply to become an affiliate" msgstr "" -#: templates/forms/affiliation.php:107 -msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +#: templates/forms/affiliation.php:132 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." msgstr "" -#: templates/forms/affiliation.php:122 -msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +#: templates/forms/affiliation.php:129 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." msgstr "" -#: templates/forms/affiliation.php:125 +#: templates/forms/affiliation.php:126 msgid "Your affiliation account was temporarily suspended." msgstr "" -#: templates/forms/affiliation.php:128 -msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +#: templates/forms/affiliation.php:123 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." msgstr "" -#: templates/forms/affiliation.php:131 -msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +#: templates/forms/affiliation.php:108 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." msgstr "" -#: templates/forms/affiliation.php:144 +#: templates/forms/affiliation.php:145 msgid "Like the %s? Become our ambassador and earn cash ;-)" msgstr "" -#: templates/forms/affiliation.php:145 +#: templates/forms/affiliation.php:146 msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" msgstr "" -#: templates/forms/affiliation.php:148 +#: templates/forms/affiliation.php:149 msgid "Program Summary" msgstr "" -#: templates/forms/affiliation.php:150 +#: templates/forms/affiliation.php:151 msgid "%s commission when a customer purchases a new license." msgstr "" -#: templates/forms/affiliation.php:152 +#: templates/forms/affiliation.php:153 msgid "Get commission for automated subscription renewals." msgstr "" -#: templates/forms/affiliation.php:155 +#: templates/forms/affiliation.php:156 msgid "%s tracking cookie after the first visit to maximize earnings potential." msgstr "" -#: templates/forms/affiliation.php:158 +#: templates/forms/affiliation.php:159 msgid "Unlimited commissions." msgstr "" -#: templates/forms/affiliation.php:160 +#: templates/forms/affiliation.php:161 msgid "%s minimum payout amount." msgstr "" -#: templates/forms/affiliation.php:161 +#: templates/forms/affiliation.php:162 msgid "Payouts are in USD and processed monthly via PayPal." msgstr "" -#: templates/forms/affiliation.php:162 +#: templates/forms/affiliation.php:163 msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." msgstr "" -#: templates/forms/affiliation.php:165 +#: templates/forms/affiliation.php:166 msgid "Affiliate" msgstr "" -#: templates/forms/affiliation.php:168, templates/forms/resend-key.php:23 +#: templates/forms/affiliation.php:169, templates/forms/resend-key.php:23 msgid "Email address" msgstr "" -#: templates/forms/affiliation.php:172 +#: templates/forms/affiliation.php:173 msgid "Full name" msgstr "" -#: templates/forms/affiliation.php:176 +#: templates/forms/affiliation.php:177 msgid "PayPal account email address" msgstr "" -#: templates/forms/affiliation.php:180 +#: templates/forms/affiliation.php:181 msgid "Where are you going to promote the %s?" msgstr "" -#: templates/forms/affiliation.php:182 +#: templates/forms/affiliation.php:183 msgid "Enter the domain of your website or other websites from where you plan to promote the %s." msgstr "" -#: templates/forms/affiliation.php:184 +#: templates/forms/affiliation.php:185 msgid "Add another domain" msgstr "" -#: templates/forms/affiliation.php:188 +#: templates/forms/affiliation.php:189 msgid "Extra Domains" msgstr "" -#: templates/forms/affiliation.php:189 +#: templates/forms/affiliation.php:190 msgid "Extra domains where you will be marketing the product from." msgstr "" -#: templates/forms/affiliation.php:199 +#: templates/forms/affiliation.php:200 msgid "Promotion methods" msgstr "" -#: templates/forms/affiliation.php:202 +#: templates/forms/affiliation.php:203 msgid "Social media (Facebook, Twitter, etc.)" msgstr "" -#: templates/forms/affiliation.php:206 +#: templates/forms/affiliation.php:207 msgid "Mobile apps" msgstr "" -#: templates/forms/affiliation.php:210 +#: templates/forms/affiliation.php:211 msgid "Website, email, and social media statistics (optional)" msgstr "" -#: templates/forms/affiliation.php:213 +#: templates/forms/affiliation.php:214 msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." msgstr "" -#: templates/forms/affiliation.php:217 +#: templates/forms/affiliation.php:218 msgid "How will you promote us?" msgstr "" -#: templates/forms/affiliation.php:220 +#: templates/forms/affiliation.php:221 msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." msgstr "" -#: templates/forms/affiliation.php:232, templates/forms/resend-key.php:22 +#: templates/forms/affiliation.php:233, templates/forms/resend-key.php:22 msgid "Cancel" msgstr "" -#: templates/forms/affiliation.php:234 +#: templates/forms/affiliation.php:235 msgid "Become an affiliate" msgstr "" @@ -2261,6 +1586,55 @@ msgstr "" msgid "User key" msgstr "" +#: templates/forms/data-debug-mode.php:162 +msgid "An unknown error has occurred." +msgstr "" + +#: templates/forms/email-address-update.php:32 +msgid "Email address update" +msgstr "" + +#: templates/forms/email-address-update.php:33, templates/forms/user-change.php:81 +msgctxt "close window" +msgid "Dismiss" +msgstr "" + +#: templates/forms/email-address-update.php:38 +msgid "Enter the new email address" +msgstr "" + +#: templates/forms/email-address-update.php:42 +msgid "Are both %s and %s your email addresses?" +msgstr "" + +#: templates/forms/email-address-update.php:50 +msgid "Yes - both addresses are mine" +msgstr "" + +#: templates/forms/email-address-update.php:57 +msgid "%s is my client's email address" +msgstr "" + +#: templates/forms/email-address-update.php:66 +msgid "%s is my email address" +msgstr "" + +#: templates/forms/email-address-update.php:75 +msgid "Would you like to merge %s into %s?" +msgstr "" + +#: templates/forms/email-address-update.php:84 +msgid "Yes - move all my data and assets from %s to %s" +msgstr "" + +#: templates/forms/email-address-update.php:94 +msgid "No - only move this site's data to %s" +msgstr "" + +#: templates/forms/email-address-update.php:292, templates/forms/email-address-update.php:298 +msgid "Update" +msgstr "" + #: templates/forms/license-activation.php:23 msgid "Please enter the license key that you received in the email right after the purchase:" msgstr "" @@ -2273,7 +1647,7 @@ msgstr "" msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." msgstr "" -#: templates/forms/license-activation.php:183 +#: templates/forms/license-activation.php:211 msgid "Associate with the license owner's account." msgstr "" @@ -2287,6 +1661,18 @@ msgctxt "verb" msgid "Opt In" msgstr "" +#: templates/forms/optout.php:41 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "" + +#: templates/forms/optout.php:44 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:45 +msgid "On second thought - I want to continue helping" +msgstr "" + #: templates/forms/optout.php:34 msgid "Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard." msgstr "" @@ -2303,14 +1689,6 @@ msgstr "" msgid "I'd like to keep automatic updates" msgstr "" -#: templates/forms/optout.php:44 -msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." -msgstr "" - -#: templates/forms/optout.php:45 -msgid "On second thought - I want to continue helping" -msgstr "" - #: templates/forms/optout.php:49 msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." msgstr "" @@ -2331,6 +1709,14 @@ msgstr "" msgid " %s to access version %s security & feature updates, and support." msgstr "" +#: templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + #: templates/forms/premium-versions-upgrade-handler.php:54 msgid "New Version Available" msgstr "" @@ -2344,7 +1730,15 @@ msgstr "" msgid "Send License Key" msgstr "" -#: templates/forms/resend-key.php:57 +#: templates/forms/resend-key.php:24, templates/forms/user-change.php:29 +msgid "Other" +msgstr "" + +#: templates/forms/resend-key.php:58 +msgid "Enter the email address you've used during the purchase and we will resend you the license key." +msgstr "" + +#: templates/forms/resend-key.php:59 msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." msgstr "" @@ -2356,10 +1750,6 @@ msgstr "" msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" msgstr "" -#: templates/forms/subscription-cancellation.php:52 -msgid "license" -msgstr "" - #: templates/forms/subscription-cancellation.php:57 msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." msgstr "" @@ -2380,18 +1770,25 @@ msgstr "" msgid "Proceed" msgstr "" -#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:216 msgid "Cancel %s & Proceed" msgstr "" +#. translators: %1$s: Number of trial days; %2$s: Plan name; #: templates/forms/trial-start.php:22 msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." msgstr "" +#. translators: %s: Link to freemius.com #: templates/forms/trial-start.php:28 msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." msgstr "" +#: templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "" + #: templates/forms/user-change.php:26 msgid "By changing the user, you agree to transfer the account ownership to:" msgstr "" @@ -2404,44 +1801,31 @@ msgstr "" msgid "Enter email address" msgstr "" -#: templates/forms/user-change.php:81 -msgctxt "close window" -msgid "Dismiss" -msgstr "" - -#: templates/js/style-premium-theme.php:39 -msgid "Premium" +#: templates/partials/network-activation.php:36 +msgid "Activate license on all pending sites." msgstr "" -#: templates/js/style-premium-theme.php:42 -msgid "Beta" +#: templates/partials/network-activation.php:37 +msgid "Apply on all pending sites." msgstr "" -#: templates/partials/network-activation.php:27 +#: templates/partials/network-activation.php:32 msgid "Activate license on all sites in the network." msgstr "" -#: templates/partials/network-activation.php:28 +#: templates/partials/network-activation.php:33 msgid "Apply on all sites in the network." msgstr "" -#: templates/partials/network-activation.php:31 -msgid "Activate license on all pending sites." -msgstr "" - -#: templates/partials/network-activation.php:32 -msgid "Apply on all pending sites." -msgstr "" - -#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +#: templates/partials/network-activation.php:45, templates/partials/network-activation.php:79 msgid "allow" msgstr "" -#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +#: templates/partials/network-activation.php:48, templates/partials/network-activation.php:82 msgid "delegate" msgstr "" -#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +#: templates/partials/network-activation.php:52, templates/partials/network-activation.php:86 msgid "skip" msgstr "" @@ -2449,6 +1833,10 @@ msgstr "" msgid "Click to view full-size screenshot %d" msgstr "" +#: templates/plugin-info/features.php:43 +msgid "Support" +msgstr "" + #: templates/plugin-info/features.php:56 msgid "Unlimited Updates" msgstr "" @@ -2467,16 +1855,24 @@ msgid "Last license" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account/partials/addon.php:29 +#: templates/account/partials/addon.php:34 msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." msgstr "" -#: templates/account/partials/addon.php:185 +#: templates/account/partials/addon.php:200 +msgid "No expiration" +msgstr "" + +#: templates/account/partials/addon.php:190 msgid "Cancelled" msgstr "" -#: templates/account/partials/addon.php:195 -msgid "No expiration" +#: templates/account/partials/site.php:49, templates/account/partials/site.php:169 +msgid "Opt In" +msgstr "" + +#: templates/account/partials/site.php:169 +msgid "Opt Out" msgstr "" #: templates/account/partials/site.php:189 @@ -2503,47 +1899,63 @@ msgstr "" msgid "Contact Support" msgstr "" -#: templates/forms/deactivation/form.php:64 +#: templates/forms/deactivation/form.php:65 msgid "Anonymous feedback" msgstr "" -#: templates/forms/deactivation/form.php:70 +#: templates/forms/deactivation/form.php:71 +msgid "hour" +msgstr "" + +#: templates/forms/deactivation/form.php:76 +msgid "hours" +msgstr "" + +#: templates/forms/deactivation/form.php:81, templates/forms/deactivation/form.php:86 +msgid "days" +msgstr "" + +#: templates/forms/deactivation/form.php:106 msgid "Deactivate" msgstr "" -#: templates/forms/deactivation/form.php:72 +#: templates/forms/deactivation/form.php:108 msgid "Activate %s" msgstr "" -#: templates/forms/deactivation/form.php:87 +#: templates/forms/deactivation/form.php:111 +msgid "Submit & %s" +msgstr "" + +#: templates/forms/deactivation/form.php:130 msgid "Quick Feedback" msgstr "" -#: templates/forms/deactivation/form.php:91 +#: templates/forms/deactivation/form.php:134 msgid "If you have a moment, please let us know why you are %s" msgstr "" -#: templates/forms/deactivation/form.php:91 +#: templates/forms/deactivation/form.php:134 msgid "deactivating" msgstr "" -#: templates/forms/deactivation/form.php:91 +#: templates/forms/deactivation/form.php:134 msgid "switching" msgstr "" -#: templates/forms/deactivation/form.php:369 -msgid "Submit & %s" +#: templates/forms/deactivation/form.php:448 +msgid "Kindly tell us the reason so we can improve." msgstr "" -#: templates/forms/deactivation/form.php:390 -msgid "Kindly tell us the reason so we can improve." +#: templates/forms/deactivation/form.php:478 +msgid "Snooze & %s" msgstr "" -#: templates/forms/deactivation/form.php:515 +#: templates/forms/deactivation/form.php:638 msgid "Yes - %s" msgstr "" -#: templates/forms/deactivation/form.php:522 +#: templates/forms/deactivation/form.php:645 msgid "Skip & %s" msgstr "" diff --git a/freemius/package.json b/freemius/package.json index 4c339ed..8f7dd3f 100644 --- a/freemius/package.json +++ b/freemius/package.json @@ -4,7 +4,7 @@ "author": "Freemius, Inc.", "license": "GPL-3.0", "homepage": "https://freemius.com", - "version": "2.4.2", + "version": "2.4.3", "main": "gulpfile.js", "dependencies": {}, "scripts": { diff --git a/freemius/start.php b/freemius/start.php index d250add..89bbfb0 100644 --- a/freemius/start.php +++ b/freemius/start.php @@ -15,7 +15,7 @@ * * @var string */ - $this_sdk_version = '2.4.3'; + $this_sdk_version = '2.4.5'; #region SDK Selection Logic -------------------------------------------------------------------- diff --git a/readme.md b/readme.md index 25d7bbd..059d8c3 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.0 +Tested up to: 6.0.2 Stable tag: 2.5.0 diff --git a/readme.txt b/readme.txt index 0910da7..1e59251 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.0 +Tested up to: 6.0.2 Stable tag: 2.5.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -220,6 +220,7 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) +* Updated: Freemius SDK (2.4.5) * Updated: Plugin Panel (1.2.7) * Updated: Moment JS (2.29.4) with WP Loading * Changed: Tab Schedule default date display on From f8f2b1a43b1e7b5617cc05d2be8ec8de4fbb4459 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 11 Oct 2022 21:24:54 +1000 Subject: [PATCH 268/377] player button tweak --- player/css/radio-player.css | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/player/css/radio-player.css b/player/css/radio-player.css index fbcdcf0..4d3f85c 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -229,7 +229,7 @@ opacity: 1; } -.rp-volume-controls button, .rp-popup button { +.radio-container .rp-volume-controls button, .radio-container .rp-popup button { overflow: hidden; text-indent: -9999px; border: none; @@ -241,7 +241,7 @@ margin-top: -12px; } -.rp-volume-controls button:hover, .rp-volume-controls button:focus { +.radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus { opacity: 1; } @@ -257,16 +257,18 @@ border-radius: 1px; } -button.rp-mute, button.rp-volume-max, button.rp-volume-down, button.rp-volume-up, button.rp-popup-button, -button.rp-mute:hover, button.rp-volume-max:hover, button.rp-volume-down:hover, button.rp-volume-up:hover, button.rp-popup-button:hover, -button.rp-mute:focus, button.rp-volume-max:focus, button.rp-volume-down:focus, button.rp-volume-up:focus, button.rp-popup-button:focus { +.rp-volume-controls button.rp-mute, .rp-volume-controls button.rp-mute:hover, .rp-volume-controls button.rp-mute:focus, +.rp-volume-controls button.rp-volume-down, .rp-volume-controls button.rp-volume-down:hover, .rp-volume-controls button.rp-volume-down:focus, +.rp-volume-controls button.rp-volume-up, .rp-volume-controls button.rp-volume-up:hover, .rp-volume-controls button.rp-volume-up:focus, +.rp-volume-controls button.rp-volume-max, .rp-volume-controls button.rp-volume-max:hover, .rp-volume-controls button.rp-volume-max:focus, +.rp-popup button.rp-popup-button, .rp-popup button.rp-popup-button:hover, .rp-popup button.rp-popup-button:focus { width: 18px; height: 18px; padding: 0; margin: 0; background-size: 36px; background-repeat: no-repeat; } -.light .rp-mute, .light .rp-volume-max, .light .rp-volume-up, .light .rp-volume-down, .light .rp-popup-button { +.light button.rp-mute, .light button.rp-volume-max, .light button.rp-volume-up, .light button.rp-volume-down, button.light button.rp-popup-button { background-image: url("../images/volume-controls-light.png?v=1"); } -.dark .rp-mute, .dark .rp-volume-max, .dark .rp-volume-up, .dark .rp-volume-down, .dark .rp-popup-button { +.dark button.rp-mute, .dark button.rp-volume-max, .dark button.rp-volume-up, .dark button.rp-volume-down, .dark button.rp-popup-button { background-image: url("../images/volume-controls-dark.png?v=1"); } From ef53fd17c8fe43f3dce72e95ea66159876dd9257 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 11 Oct 2022 22:25:51 +1000 Subject: [PATCH 269/377] bump dev version to 2.4.9.2 --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index d1917bd..9978cec 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.1 +Version: 2.4.9.2 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From 1de7b0ec2d879c455d0228781f2dc319b512d4ab Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 12 Oct 2022 17:24:16 +1000 Subject: [PATCH 270/377] colorpicker sanitization fix --- loader.php | 36 +++++++++++++++++++++++------------- player/radio-player.php | 4 ++++ radio-station.php | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/loader.php b/loader.php index ac3c8a9..b3dc6f3 100644 --- a/loader.php +++ b/loader.php @@ -742,21 +742,30 @@ public function update_settings() { if ( !is_null( $posted ) ) { $posted = str_replace( ' ', '', $posted ); $values = array(); - sscanf( $color, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $alpha ); - foreach ( $values as $key => $value ) { - $value = absint( $value ); - if ( $value < 0 ) { - $values[$key] = 0; - } elseif ( $value > 255 ) { - $values[$key] = 255; + // 1.2.7: fix color variable to posted + // 1.2.7: make alpha a value key not separate + sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] ); + // echo 'rgba sscanf values: ' . print_r( $values, true ) . "\n"; + // 1.2.7: fix for use of duplicate key variable + foreach ( $values as $k => $v ) { + if ( 'alpha' != $k ) { + // --- sanitize rgb values --- + $v = absint( $v ); + if ( $v < 0 ) { + $values[$k] = 0; + } elseif ( $v > 255 ) { + $values[$k] = 255; + } + } else { + // --- sanitize alpha value --- + if ( $v < 0 ) { + $values['alpha'] = 0; + } elseif ( $v > 1 ) { + $values['alpha'] = 1; + } } } - if ( $alpha < 0 ) { - $alpha = 0; - } elseif ( $alpha > 1 ) { - $alpha = 1; - } - $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $alpha . ')'; + $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')'; } $settings[$key] = $posted; @@ -3447,6 +3456,7 @@ function radio_station_settings_resources( $media, $color_picker ) { // ========= // == 1.2.7 == +// - fix color picker alpha sanitization / saving // - added color picker dropdown overlay styling // - added pointer cursor style to inactive tab diff --git a/player/radio-player.php b/player/radio-player.php index a94c0ba..7b321f9 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -1162,6 +1162,10 @@ function radio_station_player_core_scripts() { } $radio_player['enqueued_player'] = true; } + + // 2.5.0: added do action for player scripts + do_action( 'radio_station_player_enqueued_scripts' ); + } // --------------------- diff --git a/radio-station.php b/radio-station.php index 9978cec..c915198 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.2 +Version: 2.4.9.3 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From 7edd568a2bdd1605a258b7515abef433278ef7cf Mon Sep 17 00:00:00 2001 From: majick777 Date: Sun, 16 Oct 2022 17:25:58 +1000 Subject: [PATCH 271/377] dev updates --- includes/master-schedule.php | 8 +++---- includes/shortcodes.php | 2 +- includes/templates.php | 41 +++++++++++++++++++++++------------- player/css/radio-player.css | 22 +++++++++---------- player/js/radio-player.js | 10 ++++++++- player/radio-player.php | 41 ++++++++++++++++++++---------------- radio-station.php | 2 +- 7 files changed, 75 insertions(+), 51 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 391283d..485f0b3 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -886,7 +886,7 @@ classes = ['.master-show-entry', '.master-schedule-tabs-show', '.master-list-day // Table View Javascript // --------------------- // 2.3.0: added for table responsiveness -// TODO: use traversals with instance IDs +// 2.5,0: use DOM traversals with jQuery and classes function radio_station_master_schedule_table_js() { // 2.3.2: added current show highlighting cycle @@ -940,7 +940,7 @@ function radio_table_highlight() { else {jQuery(this).removeClass('current-hour');} }); for (i = 0; i < 7; i++) { - jQuery(this).find('.master-program-schedule .day-'+i).each(function() { + jQuery(this).find('.day-'+i).each(function() { var radio_table_shift = false; jQuery(this).find('.master-show-entry').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); @@ -1183,7 +1183,7 @@ function radio_tabs_show_highlight() { }); radio_tabs_active_tab(false,scheduleid); /* fallback */ var radio_tabs_split = false; - jQuery(this).find('.master-schedule-tabs-show').each(function() { + jQuery(this).parent().find('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (radio.debug) {console.log(start+' - '+end);} @@ -1204,7 +1204,7 @@ classes = jQuery(this).attr('class').split(/\s+/); } }); if (radio_tabs_split) { - jQuery(this).find('.'+radio_tabs_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + jQuery(this).parent().find('.'+radio_tabs_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } }); } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 27c576b..6f1a0db 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1773,7 +1773,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { } else { // 2.5.0: fix to maybe get post object - if ( !is_a( $post, 'WP_Post' ) ) { + if ( is_object( $post ) && !is_a( $post, 'WP_Post' ) ) { $post = get_post( $post, ARRAY_A ); } diff --git a/includes/templates.php b/includes/templates.php index 31a9c6f..3a15790 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -946,7 +946,9 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po return $output; } if ( 1 == count( $shifts ) ) { - $shift = $shifts[0]; + // 2.5.0: fix to get first item (not via index key 0) + $first_key = array_keys( $shifts )[0]; + $shift = $shifts[$first_key]; $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; // 2.3.3.9: fix to put addition outside bracket // 2.5.0: fix to use to_time not get_time! @@ -1023,6 +1025,12 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- generate adjacent Show link --- if ( isset( $show ) ) { + + // 2.5.0: fix to maybe get show key data + if ( isset( $show['show'] ) ) { + $show = $show['show']; + } + if ( 'next' == $adjacent ) { $rel = 'next'; } elseif ( 'previous' == $adjacent ) { @@ -1030,20 +1038,23 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } $adjacent_post = get_post( $show['id'] ); - // --- adjacent post title --- - // 2.4.0.3: added fix for missing post title - $post_title = $adjacent_post->post_title; - // if ( empty( $adjacent_post->post_title ) ) { - // $post_title = $title; - // } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); + // 2.5.0: added check for valid post + if ( $adjacent_post ) { + // --- adjacent post title --- + // 2.4.0.3: added fix for missing post title + $post_title = $adjacent_post->post_title; + // if ( empty( $adjacent_post->post_title ) ) { + // $post_title = $title; + // } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + } } return $output; diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 4d3f85c..03658c2 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -1,11 +1,11 @@ /* - * Radio Player Skin - * https://netmix.com/radio-player/ + * === Radio Stream Player Skin === + * https://radiostation.pro/stream-player/ * * Author: Tony Hayes - * Skin Version: 1.0.0 (Radio Player 1.0.0) + * Skin Version: 1.0.1 (Stream Player 2.5.0) * - * Based on Blue Monday Skin for jPlayer 2.9.2 ~ (c) 2009-2014 Happyworm Ltd ~ MIT License + * originally based on Blue Monday Skin for jPlayer 2.9.2 ~ (c) 2009-2014 Happyworm Ltd ~ MIT License */ .rp-player { @@ -38,7 +38,7 @@ .rp-interface { position: relative; background-color: transparent; - width: 36%; + width: 35%; } .rp-station-info, .rp-interface, .rp-volume-controls, .rp-controls-holder , .rp-script-switcher, .rp-show-info { @@ -162,27 +162,27 @@ background-image: url("../images/play-dark-square.png?v=2"); } -.radio-container.paused.light.circular .rp-play-pause-button { +.radio-container.playing.light.circular .rp-play-pause-button { background-image: url("../images/pause-light-circular.png?v=2"); } -.radio-container.paused.light.rounded .rp-play-pause-button { +.radio-container.playing.light.rounded .rp-play-pause-button { background-image: url("../images/pause-light-rounded.png?v=2"); } -.radio-container.paused.light.square .rp-play-pause-button { +.radio-container.playing.light.square .rp-play-pause-button { background-image: url("../images/pause-light-square.png?v=2"); } -.radio-container.paused.dark.circular .rp-play-pause-button { +.radio-container.playing.dark.circular .rp-play-pause-button { background-image: url("../images/pause-dark-circular.png?v=2"); } -.radio-container.paused.dark.rounded .rp-play-pause-button { +.radio-container.playing.dark.rounded .rp-play-pause-button { background-image: url("../images/pause-dark-rounded.png?v=2"); } -.radio-container.paused.dark.square .rp-play-pause-button { +.radio-container.playing.dark.square .rp-play-pause-button { background-image: url("../images/pause-dark-square.png?v=2"); } diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 8b46f58..c15f5fb 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -506,6 +506,7 @@ function radio_player_default_instance() { jQuery('.radio-player').each(function() { if (!instance && jQuery(this).hasClass('default-player')) { instance = parseInt(jQuery(this).attr('id').replace('radio_player_', '')); + return instance; } }); return instance; @@ -813,7 +814,14 @@ jQuery(document).ready(function() { /* --- hide all volume controls if no support (iOS) --- */ novolumesupport = radio_player_volume_test(); - if (novolumesupport) {jQuery('.rp-volume-controls').hide(); jQuery('.rp-play-pause-button-bg').css('margin-right','0');} + if (novolumesupport) { + jQuery('.rp-volume-controls').each(function() { + jQuery(this).hide(); + container = jQuery(this).closest('.radio-container'); + container.addClass('no-volume-controls'); + container.find('.rp-play-pause-button-bg').css('margin-right','0'); + } + } /* --- bind pause/play button clicks --- */ jQuery('.rp-play-pause-button').on('click', function() { diff --git a/player/radio-player.php b/player/radio-player.php index 7b321f9..73c6f77 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -263,6 +263,10 @@ function radio_station_player_output( $args = array(), $echo = false ) { $classes[] = $args['layout']; $classes[] = $args['theme']; $classes[] = $args['buttons']; + + // TODO: check volume controls display settings + // $classes[] = 'no-volume-controls'; + // 2.5.0: added filter for radio container class if ( function_exists( 'apply_filters' ) ) { $classes = apply_filters( 'radio_station_player_container_classes', $classes, $args, $instance ); @@ -320,9 +324,10 @@ function radio_station_player_output( $args = array(), $echo = false ) { $frequency_display = apply_filters( 'radio_player_frequency_display', $frequency_display, $args, $instance ); $station_text_html .= '
        ' . "\n"; + // 2.5.0: fix to mismatched location variable and class $location_display = isset( $args['location'] ) ? $args['location'] : ''; - $frequency_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); - $station_text_html .= '
        ' . "\n"; + $location_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); + $station_text_html .= '
        ' . "\n"; $html['station'] .= $station_text_html; @@ -397,25 +402,25 @@ function radio_station_player_output( $args = array(), $echo = false ) { $html['show'] .= $show_text_html; $html['show'] .= ' ' . "\n"; - // --- Progress Bar --- + // --- Playback Progress Bar --- // (for files - not implemented yet) - /* $html['progress'] = '
        '; - $html['progress'] .= '
        '; - $html['progress'] .= '
        '; - $html['progress'] .= '
        '; - $html['progress'] .= '
        '; - $html['progress'] .= '
         
        ' . PHP_EOL; - $html['progress'] .= '
         
        ' . PHP_EOL; - $html['progress'] .= '
        '; - $html['progress'] .= ' '; - $html['progress'] .= ' '; - $html['progress'] .= '
        ' . PHP_EOL; */ + /* $html['progress'] = '
        ' . "\n"; + $html['progress'] .= '
        ' . "\n"; + $html['progress'] .= '
        ' . "\n"; + $html['progress'] .= '
        ' . "\n"; + $html['progress'] .= '
        ' . "\n"; + $html['progress'] .= '
         
        ' . "\n"; + $html['progress'] .= '
         
        ' . "\n"; + $html['progress'] .= '
        ' . "\n"; + $html['progress'] .= ' ' . "\n"; + $html['progress'] .= ' ' . "\n"; + $html['progress'] .= '
        ' . "\n"; */ // --- no solution section --- - // $html['no-solution'] = '
        ' . PHP_EOL; - // $html['no-solution'] .= '' . esc_html( __( 'Update Required' ) ) . '' . PHP_EOL; - // $html['no-solution'] .= 'To play the media you will need to either update your browser to a recent version or update your Flash plugin.' . PHP_EOL; - // $html['no-solution'] .= '
        ' . PHP_EOL; + // $html['no-solution'] = '
        ' . "\n"; + // $html['no-solution'] .= '' . esc_html( __( 'Update Required' ) ) . '' . "\n"; + // $html['no-solution'] .= 'To play the media you will need to either update your browser to a recent version or update your Flash plugin.' . "\n"; + // $html['no-solution'] .= '
        ' . "\n"; // --- Current Track --- $html['track'] = '
        ' . "\n"; diff --git a/radio-station.php b/radio-station.php index c915198..3a3b8c7 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.3 +Version: 2.4.9.4 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From b2ac5edb5dd37aa1f2e0eb511622dd3e50853012 Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 17 Oct 2022 14:30:14 +1000 Subject: [PATCH 272/377] player script quickfix --- player/js/radio-player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/js/radio-player.js b/player/js/radio-player.js index c15f5fb..5383cc6 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -820,7 +820,7 @@ jQuery(document).ready(function() { container = jQuery(this).closest('.radio-container'); container.addClass('no-volume-controls'); container.find('.rp-play-pause-button-bg').css('margin-right','0'); - } + }); } /* --- bind pause/play button clicks --- */ From aa6ef0ef064d6e5424907b234dd7770c0874976e Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 17 Oct 2022 22:12:00 +1000 Subject: [PATCH 273/377] playlist admin list script fix --- includes/post-types-admin.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index f1a5044..98e79c6 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -5878,7 +5878,8 @@ function radio_station_playlist_admin_list_styles() { // --- enqueue script inline --- // 2.3.0: enqueue instead of echo // 2.3.3.9: filter playlist list script - $js = apply_filters( 'radio_station_playlist_list_script', $css ); + // 2.5.0: fix incorrect variable to filter (css) + $js = apply_filters( 'radio_station_playlist_list_script', $js ); wp_add_inline_script( 'radio-station-admin', $js ); } From de3a97e17ee1d8d26ad08bfe19d40c76091f05ce Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 24 Oct 2022 22:25:57 +1000 Subject: [PATCH 274/377] dev updates --- includes/master-schedule.php | 292 ++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 144 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 485f0b3..a3e9d82 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -979,85 +979,87 @@ function radio_table_responsive(leftright,instance) { } else {tableschedules = jQuery('.master-program-schedule');} tableschedules.each(function() { - fallback = -1; selected = -1; foundday = false; - if (!leftright || (leftright == 'left')) { - if (jQuery(this).find('.master-program-day.first-column').length) { - start = jQuery(this).find('.master-program-day.first-column'); - } else {start = jQuery(this).find('.master-program-day').first(); fallback = 0;} - classes = start.attr('class').split(' '); - } else if (leftright == 'right') { - if (jQuery(this).find('.master-program-day.last-column').length) { - end = jQuery(this).find('.master-program-day.last-column'); - } else {end = jQuery(this).find('.master-program-day').last(); fallback = 6;} - classes = end.attr('class').split(' '); - } - for (i = 0; i < classes.length; i++) { - if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} - } - if (selected < 0) {selected = fallback;} - if (radio.debug) {console.log('Current Column: '+selected);} - - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - if (!jQuery(this).find('.master-program-day.day-'+selected).length) { - while (!foundday) { - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (jQuery(this).find('.master-program-day.day-'+selected).length) {foundday = true;} - if ((selected < 0) || (selected > 6)) {selected = fallback; foundday = true;} + if (jQuery(this).find('.master-program-day').length) { + fallback = -1; selected = -1; foundday = false; + if (!leftright || (leftright == 'left')) { + if (jQuery(this).find('.master-program-day.first-column').length) { + start = jQuery(this).find('.master-program-day.first-column'); + } else {start = jQuery(this).find('.master-program-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery(this).find('.master-program-day.last-column').length) { + end = jQuery(this).find('.master-program-day.last-column'); + } else {end = jQuery(this).find('.master-program-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); } - } - if (radio.debug) {console.log('Selected Column: '+selected);} - - totalwidth = jQuery(this).find('.master-program-hour-heading').width(); - jQuery(this).find('.master-program-day, .master-program-hour-row .show-info').removeClass('first-column').removeClass('last-column').hide(); - jQuery(this).css('width','100%'); - tablewidth = jQuery(this).width(); - jQuery(this).css('width','auto'); - columns = 0; firstcolumn = -1; lastcolumn = 7; endtable = false; - for (i = selected; i < 7; i++) { - if (!endtable && (jQuery(this).find('.master-program-day.day-'+i).length)) { - if ((i > 0) && (i == selected)) {jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} - else if (i < 6) {jQuery(this).find('.master-program-day.day-'+i).addClass('last-column');} - jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); - colwidth = jQuery(this).find('.master-program-day.day-'+i).width(); - totalwidth = totalwidth + colwidth; - if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} - jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); - if (totalwidth > tablewidth) { - if (radio.debug) {console.log('Hiding Column '+i);} - jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; - } else { - if (radio.debug) {console.log('Showing Column '+i);} - jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); - totalwidth = totalwidth - colwidth + jQuery(this).find('.master-program-day.day-'+i).width(); - lastcolumn = i; columns++; + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} + } + if (selected < 0) {selected = fallback;} + if (radio.debug) {console.log('Current Column: '+selected);} + + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} + if (!jQuery(this).find('.master-program-day.day-'+selected).length) { + while (!foundday) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery(this).find('.master-program-day.day-'+selected).length) {foundday = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundday = true;} } } - - } - if (lastcolumn < 6) {jQuery(this).find('.master-program-day.day-'+lastcolumn).addClass('last-column');} - - if (leftright == 'right') { - for (i = (selected - 1); i > -1; i--) { + if (radio.debug) {console.log('Selected Column: '+selected);} + + totalwidth = jQuery(this).find('.master-program-hour-heading').width(); + jQuery(this).find('.master-program-day, .master-program-hour-row .show-info').removeClass('first-column').removeClass('last-column').hide(); + jQuery(this).css('width','100%'); + tablewidth = jQuery(this).width(); + jQuery(this).css('width','auto'); + columns = 0; firstcolumn = -1; lastcolumn = 7; endtable = false; + for (i = selected; i < 7; i++) { if (!endtable && (jQuery(this).find('.master-program-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} + else if (i < 6) {jQuery(this).find('.master-program-day.day-'+i).addClass('last-column');} jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); colwidth = jQuery(this).find('.master-program-day.day-'+i).width(); totalwidth = totalwidth + colwidth; if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} + jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); if (totalwidth > tablewidth) { if (radio.debug) {console.log('Hiding Column '+i);} - jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); - endtable = true; + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; } else { if (radio.debug) {console.log('Showing Column '+i);} - jQuery(this).find('.master-program-day').removeClass('first-column'); - jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); - columns++; + jQuery(this).find('.master-program-day.day-'+i).removeClass('last-column'); + totalwidth = totalwidth - colwidth + jQuery(this).find('.master-program-day.day-'+i).width(); + lastcolumn = i; columns++; } } + } + if (lastcolumn < 6) {jQuery(this).find('.master-program-day.day-'+lastcolumn).addClass('last-column');} + + if (leftright == 'right') { + for (i = (selected - 1); i > -1; i--) { + if (!endtable && (jQuery(this).find('.master-program-day.day-'+i).length)) { + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); + colwidth = jQuery(this).find('.master-program-day.day-'+i).width(); + totalwidth = totalwidth + colwidth; + if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} + if (totalwidth > tablewidth) { + if (radio.debug) {console.log('Hiding Column '+i);} + jQuery(this).find('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); + endtable = true; + } else { + if (radio.debug) {console.log('Showing Column '+i);} + jQuery(this).find('.master-program-day').removeClass('first-column'); + jQuery(this).find('.master-program-day.day-'+i).addClass('first-column'); + columns++; + } + } + } + } + jQuery(this).css('width','100%'); } - jQuery(this).css('width','100%'); }); } @@ -1218,69 +1220,49 @@ function radio_tabs_responsive(leftright,instance) { } else {tabschedules = jQuery('.master-schedule-tabs');} tabschedules.each(function() { - fallback = -1; selected = -1; foundtab = false; - if (!leftright || (leftright == 'left')) { - if (jQuery(this).find('.master-schedule-tabs-day.first-tab').length) { - start = jQuery(this).find('.master-schedule-tabs-day.first-tab'); - } else {start = jQuery(this).find('.master-schedule-tabs-day').first(); fallback = 0;} - classes = start.attr('class').split(' '); - } else if (leftright == 'right') { - if (jQuery(this).find('.master-schedule-tabs-day.last-tab').length) { - end = jQuery(this).find('.master-schedule-tabs-day.last-tab'); - } else {end = jQuery(this).find('.master-schedule-tabs-day').last(); fallback = 6;} - classes = end.attr('class').split(' '); - } - for (i = 0; i < classes.length; i++) { - if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} - } - if (selected < 0) {selected = fallback;} - if (radio.debug) {console.log('Current Tab: '+selected);} - - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - if (!jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) { - while (!foundtab) { - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) {foundtab = true;} - if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} + if (jQuery(this).find('.master-schedule-tabs-day').length) { + fallback = -1; selected = -1; foundtab = false; + if (!leftright || (leftright == 'left')) { + if (jQuery(this).find('.master-schedule-tabs-day.first-tab').length) { + start = jQuery(this).find('.master-schedule-tabs-day.first-tab'); + } else {start = jQuery(this).find('.master-schedule-tabs-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery(this).find('.master-schedule-tabs-day.last-tab').length) { + end = jQuery(this).find('.master-schedule-tabs-day.last-tab'); + } else {end = jQuery(this).find('.master-schedule-tabs-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); } - } - if (radio.debug) {console.log('Selected Tab: '+selected);} - - jQuery(this).css('width','100%'); - tabswidth = jQuery(this).width(); - jQuery(this).css('width','auto'); - jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); - - totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; - if (jQuery(this).find('.master-schedule-tabs-loader-left').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-left').width();} - if (jQuery(this).find('.master-schedule-tabs-loader-right').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-right').width();} - - for (i = selected; i < 7; i++) { - if (!endtabs && (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length)) { - if ((i > 0) && (i == selected)) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} - else if (i < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} - tabwidth = jQuery(this).find('.master-schedule-tabs-day.day-'+i).show().width(); - mleft = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); - mright = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); - totalwidth = totalwidth + tabwidth + mleft + mright; - if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} - if (totalwidth > tabswidth) { - if (radio.debug) {console.log('Hiding Tab '+i);} - jQuery(this).find('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; - } else { - jQuery(this).find('.master-schedule-tabs-day.day-'+i).removeClass('last-tab'); - totalwidth = totalwidth - tabwidth + jQuery(this).find('.master-schedule-tabs-day.day-'+i).width(); - if (radio.debug) {console.log('Showing Tab '+i);} - lasttab = i; tabs++; + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} + } + if (selected < 0) {selected = fallback;} + if (radio.debug) {console.log('Current Tab: '+selected);} + + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} + if (!jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) { + while (!foundtab) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery(this).find('.master-schedule-tabs-day.day-'+selected).length) {foundtab = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} } } - } - if (lasttab < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} - - if (leftright == 'right') { - for (i = (selected - 1); i > -1; i--) { + if (radio.debug) {console.log('Selected Tab: '+selected);} + + jQuery(this).css('width','100%'); + tabswidth = jQuery(this).width(); + jQuery(this).css('width','auto'); + jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); + + totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; + if (jQuery(this).find('.master-schedule-tabs-loader-left').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-left').width();} + if (jQuery(this).find('.master-schedule-tabs-loader-right').length) {totalwidth = totalwidth + jQuery(this).find('.master-schedule-tabs-loader-right').width();} + + for (i = selected; i < 7; i++) { if (!endtabs && (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} + else if (i < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} tabwidth = jQuery(this).find('.master-schedule-tabs-day.day-'+i).show().width(); mleft = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); mright = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); @@ -1290,35 +1272,57 @@ classes = end.attr('class').split(' '); if (radio.debug) {console.log('Hiding Tab '+i);} jQuery(this).find('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; } else { - jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab'); - jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); + jQuery(this).find('.master-schedule-tabs-day.day-'+i).removeClass('last-tab'); + totalwidth = totalwidth - tabwidth + jQuery(this).find('.master-schedule-tabs-day.day-'+i).width(); if (radio.debug) {console.log('Showing Tab '+i);} - tabs++; + lasttab = i; tabs++; } } } - } - jQuery(this).css('width','100%'); + if (lasttab < 6) {jQuery(this).find('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} + + if (leftright == 'right') { + for (i = (selected - 1); i > -1; i--) { + if (!endtabs && (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length)) { + tabwidth = jQuery(this).find('.master-schedule-tabs-day.day-'+i).show().width(); + mleft = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); + mright = parseInt(jQuery(this).find('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); + totalwidth = totalwidth + tabwidth + mleft + mright; + if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} + if (totalwidth > tabswidth) { + if (radio.debug) {console.log('Hiding Tab '+i);} + jQuery(this).find('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; + } else { + jQuery(this).find('.master-schedule-tabs-day').removeClass('first-tab'); + jQuery(this).find('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); + if (radio.debug) {console.log('Showing Tab '+i);} + tabs++; + } + } + } + } + jQuery(this).css('width','100%'); - /* display selected day message if outside view */ - activeday = false; - for (i = 0; i < 7; i++) { - if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length) { - if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) {activeday = i;} + /* display selected day message if outside view */ + activeday = false; + for (i = 0; i < 7; i++) { + if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).length) { + if (jQuery(this).find('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) {activeday = i;} + } + } + jQuery(this).find('.master-schedule-tabs-selected').hide(); + if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { + jQuery(this).find('.master-schedule-tabs-selected-'+activeday).show(); } - } - jQuery(this).find('.master-schedule-tabs-selected').hide(); - if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { - jQuery(this).find('.master-schedule-tabs-selected-'+activeday).show(); - } - if (radio.debug) { - console.log('Active Day: '+activeday); - console.log('Selected: '+selected); - console.log('Fallback: '+fallback); - console.log('First Tab: '+firsttab); - console.log('Last Tab: '+lasttab); - console.log('Visible Tabs: '+tabs); + if (radio.debug) { + console.log('Active Day: '+activeday); + console.log('Selected: '+selected); + console.log('Fallback: '+fallback); + console.log('First Tab: '+firsttab); + console.log('Last Tab: '+lasttab); + console.log('Visible Tabs: '+tabs); + } } }); } From 9261b8f30f92432e8a421a51586dcd766955a097 Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 24 Oct 2022 22:28:25 +1000 Subject: [PATCH 275/377] development updates --- css/rs-schedule.css | 9 +- css/rs-shortcodes.css | 1 + includes/legacy.php | 27 +- includes/post-types-admin.php | 601 +++++++++++++++++++++++++++------- loader.php | 15 +- player/css/radio-player.css | 3 +- player/radio-player.php | 145 ++++++-- radio-station.php | 2 +- readme.txt | 4 +- scheduler/schedule-engine.php | 9 +- 10 files changed, 659 insertions(+), 157 deletions(-) diff --git a/css/rs-schedule.css b/css/rs-schedule.css index b80665f..c0b5ecc 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -365,20 +365,21 @@ display: inline-block; margin: 0; padding: 0 10px; - font-size: 2em; - line-height: 1em; + font-size: 50px; + line-height: 35px; vertical-align: top; cursor: pointer; } .master-schedule-tabs-loader .master-schedule-tabs-arrow { - width: 1em; + width: 50px; text-align: center; + padding-bottom: 15px; } .master-schedule-tabs-loader .master-schedule-tabs-arrow:hover { background-color: rgba(128,128,128,0.5); - border-radius: 1em; + border-radius: 25px; } .master-schedule-tabs .master-schedule-tabs-day { diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index 003470f..0dcc422 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -190,6 +190,7 @@ ul.current-show-list, ul.current-show-list li, ul.upcoming-shows-list, ul.upcoming-shows-list li { list-style: none; + padding-left: 0; } .current-show-avatar, .upcoming-show-avatar, .on-air-dj-avatar { diff --git a/includes/legacy.php b/includes/legacy.php index 5d6b95f..bec6af0 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -400,6 +400,9 @@ function radio_station_get_now_playing( $time = false ) { return false; } $show_id = $current_show['show']['id']; + // if ( RADIO_STATION_DEBUG ) { + echo 'Playlist Current Show Data: ' . print_r( $current_show, true ) . '' . "\n"; + // } // TODO: improve handling of playlists for overrides $override = false; @@ -423,11 +426,12 @@ function radio_station_get_now_playing( $time = false ) { if ( $shifts ) { $playlist['shifts'] = $shifts; } + // 2.5.0: merge in override shifts if ( $override ) { $override_shifts = get_post_meta( $override_id, 'show_override_sched', true ); if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { - if ( isset( $$playlist['shifts'] ) ) { + if ( isset( $playlist['shifts'] ) ) { $playlist['shifts'] = array_merge( $playlist['shifts'], $override_shifts ); } else { $playlist['shifts'] = $override_shifts; @@ -435,7 +439,7 @@ function radio_station_get_now_playing( $time = false ) { } $recurring_shifts = get_post_meta( $override_id, 'show_recurring_sched', true ); if ( $recurring_shifts && is_array( $recurring_shifts ) && ( count( $recurring_shifts ) > 0 ) ) { - if ( isset( $$playlist['shifts'] ) ) { + if ( isset( $playlist['shifts'] ) ) { $playlist['shifts'] = array_merge( $playlist['shifts'], $recurring_shifts ); } else { $playlist['shifts'] = $recurring_shifts; @@ -445,7 +449,7 @@ function radio_station_get_now_playing( $time = false ) { // --- grab the most recent playlist for the current show --- $args = array( - 'numberposts' => 1, + 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_date', 'order' => 'DESC', @@ -469,10 +473,23 @@ function radio_station_get_now_playing( $time = false ) { // TODO: check for playlist linked to this shift / date? if ( $playlist_posts && is_array( $playlist_posts ) && ( count( $playlist_posts ) > 0 ) ) { + $found = false; + foreach ( $playlist_posts as $playlist_post ) { + $shift_id = get_post_meta( $playlist_post->ID, 'playlist_shift_id', true ); + if ( $shift_id == $current_show['id'] ) { + $playlist_id = $playlist_post->ID; + $found = true; + } + } + + if ( !$found ) { + // --- if not found use most recently published show playlist --- + $playlist_id = $playlist_posts[0]->ID; + } + // --- fetch the tracks for the playlist --- // 2.3.0: added singular argument to true - $playlist_post = $playlist_posts[0]; - $tracks = get_post_meta( $playlist_post->ID, 'playlist', true ); + $tracks = get_post_meta( $playlist_id, 'playlist', true ); if ( $tracks && is_array( $tracks ) && ( count( $tracks ) > 0 ) ) { diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 98e79c6..5373a30 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -60,6 +60,9 @@ // - Add Playlist List Columns // - Playlist List Column Data // - Playlist List Column Styles +// - Playlist List Column Scripts +// - Playlist Quick Edit Fields +// - Playlist Quick Edit Script // === Posts === // - Add Related Shows Metabox // - Related Shows Metabox @@ -1052,7 +1055,7 @@ function radio_station_shift_edit_script() { // 2.3.3.6: store possible existing onbeforeunload function // (to help prevent conflicts with other plugins using this event) - $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . "\n"; + /* $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . "\n"; $js .= "function radio_check_shifts() { if (jQuery('.show-shift.changed').length) { if (!onbeforeunloadset) { @@ -1066,8 +1069,33 @@ function radio_station_shift_edit_script() { onbeforeunloadset = false; } } + }" . "\n"; */ + + // 2.5.0: replace window.onbeforeunload merhod with event listener + // ref: https://stackoverflow.com/a/58841521/5240159 + $js .= "function radio_check_shifts() { + if (jQuery('.show-shift.changed').length) { + window.addEventListener('beforeunload', radio_onbeforeunload); + } else { + window.removeEventListener('beforeunload', radio_onbeforeunload); + } }" . "\n"; + // --- onbeforeunload event function --- + // (this display ~"do you want to leave this page?" dialogue) + $js .= "function radio_onbeforeunload(e) { + e.preventDefault(); + e.returnValue = ''; + }" . "\n"; + + // --- remove event listener on publish/update --- + // 2.5.0: added so publish/update does not trigger event + $js .= "jQuery(document).ready(function() { + jQuery('#publish').on('click', function() { + window.removeEventListener('beforeunload', radio_onbeforeunload); + } + }"; + // --- add new shift --- // 2.3.2: separate function for onclick // 2.5.0: shorten value object @@ -1662,22 +1690,24 @@ function radio_station_show_helper_box() { echo '

        ' . "\n"; echo esc_html( __( 'The text field below is for your Show Description. It will display in the About section of your Show page.', 'radio-station' ) ) . "\n"; echo ' ' . esc_html( __( 'It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.', 'radio-station' ) ) . "\n"; - echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . '
        ' . "\n"; + echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . "\n"; + echo '

        ' . "\n"; + echo '

        ' . "\n"; echo esc_html( __( 'We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.', 'radio-station' ) ) . "\n"; echo ' ' . esc_html( __( 'This way you can then assign them to a relevant Post Category for display on your site also.', 'radio-station' ) ) . "\n"; echo '

        ' . "\n"; - // [Pro Blurb] - // TODO: upgrade to Pro for upcoming Show Episodes blurb - // echo '
        ' . esc_html( 'In future, Radio Station Pro will include an Episodes post type', 'radio-station' ) ); - // TODO: change this text/link when Pro Episodes become available - // $upgrade_url = radio_station_get_upgrade_url(); - // $pricing_url = radio_station_get_pricing_url(); - // echo '
        '; - // echo esc_html( __( 'Upgrade to Radio Station Pro.', 'radio-station' ) ); - // echo ' | '; - // echo esc_html( __( 'Find out more.', 'radio-station' ) ); - // echo ''; + // 2.5.0: activate upgrade to Pro blurb + $upgrade_url = radio_station_get_upgrade_url(); + $pricing_url = radio_station_get_pricing_url(); + $message = '

        ' . esc_html( __( 'Radio Station Pro includes an Episodes post type for adding past episodes.', 'radio-station' ) ) . "\n"; + $message .= esc_html( __( 'These are then automatically listed on your Show page in an Episodes tab.', 'radio-station' ) ) . "\n"; + $message .= ' ' . esc_html( __( 'Go Pro!', 'radio-station' ) ) . ''; + $message .= ' | ' . esc_html( __( 'Find out more...', 'radio-station' ) ) . ''; + $message .= '

        '; + $message = apply_filters( 'radio_station_show_helper_episode_message', $message ); + $allowed_html = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $message, $allowed_html ); } @@ -1903,57 +1933,57 @@ function radio_station_show_images_save() { global $post; // --- sanitize posted values --- - if ( isset( $_GET['post_id'] ) ) { + // 2.3.3.6: get post for checking capability + // 2.5.0: simplified sanitization and auth check logic + if ( isset( $_GET['post_id'] ) && ( absint( $_GET['post_id'] ) > 0 ) ) { $post_id = absint( $_GET['post_id'] ); - if ( $post_id < 1 ) { - unset( $post_id ); - } - } + $post = get_post( $post_id ); + if ( $post ) { - // 2.3.3.6: get post for checking capability - $post = get_post( $post_id ); - if ( !$post ) { - exit; - } + // --- check edit capability --- + if ( !current_user_can( 'edit_shows' ) ) { + exit; + } - // --- check edit capability --- - if ( !current_user_can( 'edit_shows' ) ) { - exit; - } + // --- verify nonce value --- + if ( !isset( $_GET['nonce'] ) || !wp_verify_nonce( $_GET['nonce'], 'show-images-autosave' ) ) { + exit; + } - // --- verify nonce value --- - if ( !isset( $_GET['nonce'] ) || !wp_verify_nonce( $_GET['nonce'], 'show-images-autosave' ) ) { - exit; - } + // --- refresh parent frame nonce --- + $images_save_nonce = wp_create_nonce( 'show-images-autosave' ); + echo ""; - if ( isset( $_GET['image_id'] ) ) { - $image_id = absint( $_GET['image_id'] ); - if ( $image_id < 1 ) { - unset( $image_id ); - } - } - if ( isset( $_GET['image_type'] ) ) { - if ( in_array( $_GET['image_type'], array( 'header', 'avatar' ) ) ) { - $image_type = $_GET['image_type']; - } - } + // --- get image ID --- + if ( isset( $_GET['image_id'] ) ) { + $image_id = absint( $_GET['image_id'] ); + if ( $image_id < 1 ) { + unset( $image_id ); + } + } - if ( isset( $post_id ) && isset( $image_id ) && isset( $image_type ) ) { - update_post_meta( $post_id, 'show_' . $image_type, $image_id ); - } else { - exit; - } + // --- get image type --- + if ( isset( $_GET['image_type'] ) ) { + if ( in_array( $_GET['image_type'], array( 'header', 'avatar' ) ) ) { + $image_type = $_GET['image_type']; + } + } - // --- add image updated flag --- - // (help prevent duplication on new posts) - $updated = get_post_meta( $post_id, '_rs_image_updated', true ); - if ( !$updated ) { - add_post_meta( $post_id, '_rs_image_updated', true ); + // --- update show avatar image ID --- + if ( isset( $image_id ) && isset( $image_type ) ) { + update_post_meta( $post_id, 'show_' . $image_type, $image_id ); + + // --- maybe add image updated flag --- + // (help prevent duplication on new posts) + $updated = get_post_meta( $post_id, '_rs_image_updated', true ); + if ( !$updated ) { + add_post_meta( $post_id, '_rs_image_updated', true ); + } + + } + } } - // --- refresh parent frame nonce --- - $images_save_nonce = wp_create_nonce( 'show-images-autosave' ); - echo ""; exit; } @@ -5546,24 +5576,18 @@ function radio_station_playlist_show_metabox() { 'post_status' => array( 'publish', 'draft' ) ); $shows = get_posts( $args ); - if ( count( $shows ) > 0 ) { - $have_shows = true; - } else { - $have_shows = false; - } + $have_shows = ( count( $shows ) > 0 ) ? true : false; // --- maybe restrict show selection to user-assigned shows --- // 2.2.8: remove strict argument from in_array checking // 2.3.0: added check for new Show Editor role // 2.3.0: added check for edit_others_shows capability - if ( !in_array( 'administrator', $user->roles ) - && !in_array( 'show-editor', $user->roles ) - && !current_user_can( 'edit_others_shows' ) ) { + if ( !in_array( 'administrator', $user->roles ) && !in_array( 'show-editor', $user->roles ) && !current_user_can( 'edit_others_shows' ) ) { // --- get the user lists for all shows --- + // 2.5.0: shorten query to one line $allowed_shows = array(); - $query = "SELECT pm.meta_value, pm.post_id FROM " . $wpdb->prefix . "postmeta pm"; - $query .= " WHERE pm.meta_key = 'show_user_list'"; + $query = "SELECT meta_value, post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = 'show_user_list'"; $show_user_lists = $wpdb->get_results( $query ); // ---- check each list for the current user --- @@ -5601,26 +5625,163 @@ function radio_station_playlist_show_metabox() { // 2.3.3.9: move meta_inner ID to class echo '
        ' . "\n"; + + $metabox = ''; if ( !$have_shows ) { - echo esc_html( __( 'No Shows were found.', 'radio-station' ) ) . "\n"; + + $metabox .= esc_html( __( 'No Shows were found.', 'radio-station' ) ) . "\n"; + } else { + if ( count( $shows ) < 1 ) { - echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ) . "\n"; + + $metabox .= esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ) . "\n"; + } else { // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); // --- select show to assign playlist to --- - $current = get_post_meta( $post->ID, 'playlist_show_id', true ); - echo '' . "\n"; + $current_show = get_post_meta( $post->ID, 'playlist_show_id', true ); + $metabox .= '
        ' . "\n"; + $metabox .= '' . esc_html( __( 'Assign to Show', 'radio-station' ) ) . '
        ' . "\n"; + $metabox .= '' . "\n"; + $metabox .= '

        ' . "\n"; + + // 2.5.0: added playlist show shift selector + $current_shift = get_post_meta( $post->ID, 'playlist_shift_id', true ); + $select = radio_station_playlist_show_shift_select( $current_show, $current_shift ); + $metabox .= $select; + + // 2.5.0: filter playlist metabox output + $metabox = apply_filters( 'radio_station_playlist_show_metabox', $metabox, $post ); + $allowed_html = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $metabox, $allowed_html ); + + // --- selection AJAX loader iframe ---- + echo '' . "\n"; + + // 2.5.0: playlist selection javascript + add_action( 'admin_footer', 'radio_station_playlist_selection_script' ); + } } + + // --- close metabox_inner --- echo '
        ' . "\n"; + +} + +// ----------------------------------- +// Playlist Episode Assignment Message +// ----------------------------------- +add_filter( 'radio_station_playlist_show_metabox', 'radio_station_playlist_episode_message', 10, 2 ); +function radio_station_playlist_episode_message( $metabox, $post ) { + + // --- assign to Episodes upgrade to Pro message --- + $upgrade_url = radio_station_get_upgrade_url(); + $pricing_url = radio_station_get_pricing_url(); + $message = '
        ' . esc_html( __( 'You can assign Playlists to Show Episodes in Radio Station Pro.', 'radio-station' ) ) . '
        ' . "\n"; + $message .= '' . esc_html( __( 'Go PRO', 'radio-station' ) ) . ' | ' . "\n"; + $message .= '' . esc_html( __( 'Find Out More...', 'radio-station' ) ) . '
        ' . "\n"; + + // --- append message and return --- + $metabox .= $message; + return $metabox; +} + +// ---------------------------------- +// Playlist Show Selection Javascript +// ---------------------------------- +function radio_station_playlist_selection_script() { + + $ajax_url = add_query_arg( 'action', 'radio_station_select_show_shifts', admin_url( 'admin-ajax.php' ) ); + $js = "var playlist_ajax_url = '" . esc_url( $ajax_url ) . "'; + function radio_playlist_show_shifts() { + select = document.getElementById('playlist-show-select'); + show_id = select.options[select.selectedIndex].value; + playlist_id = document.getElementById('post_ID').value; + url = playlist_ajax_url+'&playlist_id='+playlist_id+'&show_id='+show_id; + frame = document.getElementById('playlist-shift-selection-frame'); + if (frame.src != url) { + select = document.getElementById('playlist-show-shift-select'); + if (select) {setAttribute('disabled','disabled');} + frame.src = url; + } + }" . "\n"; + $js = apply_filters( 'radio_station_playlist_selection_script', $js ); + + echo '' . "\n"; + +} + +// ------------------------ +// AJAX Get Shift Selection +// ------------------------ +// 2.5.0: added AJAX action for show shift selection loading +add_action( 'wp_ajax_radio_station_select_show_shifts', 'radio_station_select_show_shifts' ); +function radio_station_select_show_shifts() { + + // --- sanitize posted values --- + $playlist_id = absint( $_GET['playlist_id'] ); + $show_id = absint( $_GET['show_id'] ); + if ( $show_id > 0 ) { + $show = get_post( $show_id ); + } else { + $show_id = false; + } + + // --- output show shift selector --- + $current_shift = $playlist_id ? get_post_meta( $playlist_id, 'playlist_shift_id', true ) : false; + $select = radio_station_playlist_show_shift_select( $show_id, $current_shift ); + $allowed_html = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $select, $allowed_html ); + + // --- send shift select output to parent frame --- + echo "" . "\n"; + + exit; +} + +// ---------------------------- +// Playlist Show Shift Selector +// ---------------------------- +function radio_station_playlist_show_shift_select( $show_id, $current_shift ) { + + $select = '
        '; + if ( $show_id ) { + $shifts = get_post_meta( $show_id, 'show_sched', true ); + $select .= '' . esc_html( __( 'Assign to Show Shift', 'radio-station' ) ) . '
        ' . "\n"; + $select .= '' . "\n"; + } else { + $select = esc_html( __( 'No Show selected.', 'radio-station' ) ); + } + $select .= '

        ' . "\n"; + return $select; } // -------------------- @@ -5640,36 +5801,40 @@ function radio_station_playlist_save_data( $post_id ) { // 2.3.2: added AJAX track saving checks if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != $_REQUEST['action'] ) ) { - return; - } + // 2.5.0: added check to exclude quick edit action + if ( !isset( $_POST['playlist-quick-edit'] ) || ( '1' != $_POST['playlist-quick-edit'] ) ) { - $error = false; - if ( !current_user_can( 'edit_playlists' ) ) { - $error = __( 'Failed. Use manual Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { - $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { - $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); - } else { - $post_id = absint( $_POST['playlist_id'] ); - $post = get_post( $post_id ); - if ( !$post ) { - $error = __( 'Failed. Invalid Playlist ID.', 'radio-station' ); + // 2.3.3: added double check for AJAX action match + if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != $_REQUEST['action'] ) ) { + return; } - } - // --- send error message to parent window --- - if ( $error ) { - echo "" . "\n"; + $error = false; + if ( !current_user_can( 'edit_playlists' ) ) { + $error = __( 'Failed. Use manual Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { + $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); + } else { + $post_id = absint( $_POST['playlist_id'] ); + $post = get_post( $post_id ); + if ( !$post ) { + $error = __( 'Failed. Invalid Playlist ID.', 'radio-station' ); + } + } - exit; + // --- send error message to parent window --- + if ( $error ) { + echo "" . "\n"; + + exit; + } } } @@ -5718,11 +5883,17 @@ function radio_station_playlist_save_data( $post_id ) { // --- verify playlist related to show nonce --- if ( isset( $_POST['playlist_show_nonce'] ) && wp_verify_nonce( $_POST['playlist_show_nonce'], 'radio-station' ) ) { + // 2.5.0: also get previous shift (if any) $show_changed = false; $prev_show = get_post_meta( $post_id, 'playlist_show_id', true ); - $show = $_POST['playlist_show_id']; + $prev_shift = get_post_meta( $post_id, 'playlist_shift_id', true ); + + // 2.5.0: added sanitize_text_field to post request + $show = sanitize_text_field( $_POST['playlist_show_id'] ); if ( empty( $show ) ) { delete_post_meta( $post_id, 'playlist_show_id' ); + // 2.5.0: also delete shift association + delete_post_meta( $post_id, 'playlist_shift_id' ); if ( $prev_show ) { $show = $prev_show; $show_changed = true; @@ -5733,8 +5904,21 @@ function radio_station_playlist_save_data( $post_id ) { update_post_meta( $post_id, 'playlist_show_id', $show ); $show_changed = true; } + // 2.5.0: maybe save selected shift + if ( isset( $_POST['playlist_shift_id'] ) ) { + $shift_id = sanitize_text_field( $_POST['playlist_shift_id'] ); + if ( '' != $shift_id ) { + update_post_meta( $post_id, 'playlist_shift_id', $shift_id ); + if ( $shift_id != $prev_shift ) { + $show_changed = true; + } + } + } } + // 2.5.0: added filter to check for other show changes + $show_changed = apply_filters( 'radio_station_playlist_show_changed', $show_changed, $post_id ); + // 2.3.0: maybe clear cached data to be safe // 2.3.3: remove current show transient // 2.3.4: add previous show transient @@ -5814,14 +5998,66 @@ function radio_station_playlist_columns( $columns ) { // 2.2.7: added data columns for show list display add_action( 'manage_' . RADIO_STATION_PLAYLIST_SLUG . '_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); function radio_station_playlist_column_data( $column, $post_id ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + if ( 'show' == $column ) { + + // 2.5.0: also get show data for quick edit + global $post; + $stored_post = $post; + + // --- get Shows linked to playlist --- + $data = ''; + $show_ids = $disabled = array(); $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); - $post = get_post( $show_id ); - echo '' . esc_html( $post->post_title ) . '' . "\n"; + if ( $show_id ) { + if ( is_array( $show_id ) ) { + $show_ids = $show_id; + foreach ( $show_ids as $i => $show_id ) { + if ( 0 == $show_id ) { + unset( $show_ids[$i] ); + } + } + $data = implode( ',', $show_ids ); + } elseif ( $show_id > 0 ) { + $show_ids = array( $show_id ); + $data = $show_id; + } + } + + // --- display Shows linked to post --- + if ( count( $show_ids ) > 0 ) { + foreach ( $show_ids as $show_id ) { + $show = get_post( trim( $show_id ) ); + if ( $show ) { + $post = $show; + if ( current_user_can( 'edit_shows' ) ) { + echo '' . PHP_EOL; + } else { + $disabled[] = $show_id; + } + echo esc_html( $show->post_title ) . '
        ' . "\n"; + if ( current_user_can( 'edit_shows' ) ) { + echo '
        ' . "\n"; + } + } + } + } + + // --- hidden show and disabled show IDs --- + echo '' . "\n"; + echo '' . "\n"; + + // --- restore global post object --- + $post = $stored_post; + } elseif ( 'trackcount' == $column ) { + echo count( $tracks ) . "\n"; + } elseif ( 'tracklist' == $column ) { + echo ''; echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
        ' . "\n"; echo '
        ' . "\n"; echo '
    ' . "\n"; + } } @@ -5868,6 +6105,15 @@ function radio_station_playlist_admin_list_styles() { $css = apply_filters( 'radio_station_playlist_list_styles', $css ); echo ''; +} + +// ---------------------------- +// Playlist List Column Scripts +// ---------------------------- +// 2.5.0: separated function from playlist list column styles +add_action( 'admin_footer', 'radio_station_playlist_admin_list_scripts' ); +function radio_station_playlist_admin_list_scripts() { + // --- expand/collapse tracklist data --- $js = "function showhidetracklist(postid) {" . "\n"; $js .= " if (document.getElementById('tracklist-'+postid).style.display == 'none') {" . "\n"; @@ -5884,6 +6130,130 @@ function radio_station_playlist_admin_list_styles() { } +// -------------------------------- +// Playlist Quick Edit Input Fields +// -------------------------------- +// 2.5.0: added for quick selection of playlist show +add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_playlist', 10, 2 ); +function radio_station_quick_edit_playlist( $column_name, $post_type ) { + + global $post, $radio_station_data; + $stored_post = $post; + + if ( 'playlist' != $post_type ) { + return; + } + + if ( isset( $radio_station_data['playlist-quick-edit'] ) ) { + return; + } + + // --- get all shows --- + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => array( 'publish', 'draft' ), + ); + $shows = get_posts( $args ); + + // --- show select input field --- + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- hidden fields for quick edit saving --- + wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); + echo '' . "\n"; + + // --- related shows post box styles --- + $css = '.pre-selected {background-color:#BBB;}'; + $css = apply_filters( 'radio_station_quick_edit_playlist_styles', $css ); + echo '' . "\n"; + + // 2.3.3.6: restore stored post object + $post = $stored_post; + + // --- set flag to prevent duplication --- + $radio_station_data['playlist-quick-edit'] = true; +} + +// -------------------------- +// Playlist Quick Edit Script +// -------------------------- +// ref: https://codex.wordpress.org/Plugin_API/Action_Reference/quick_edit_custom_box +// 2.5.0: added for quick selection of playlist show +add_action( 'admin_enqueue_scripts', 'radio_station_playlists_quick_edit_script' ); +function radio_station_playlists_quick_edit_script( $hook ) { + + if ( 'edit.php' != $hook ) { + return; + } + + if ( RADIO_STATION_PLAYLIST_SLUG == sanitize_text_field( $_GET['post_type'] ) ) { + $js = "(function($) { + var \$wp_inline_edit = inlineEditPost.edit; + inlineEditPost.edit = function( id ) { + \$wp_inline_edit.apply(this, arguments); + var post_id = 0; var disabled_ids; + if (typeof(id) == 'object') {post_id = parseInt(this.getId(id));} + if (post_id > 0) { + var show_ids = jQuery('#post-'+post_id+' .column-show .show-ids').text(); + if (show_ids != '') { + if (show_ids.indexOf(',') > -1) {ids = show_ids.split(',');} + else {ids = new Array(); ids[0] = show_ids;} + for (i = 0; i < ids.length; i++) { + var thisshowid = ids[i]; + jQuery('#edit-'+post_id+' .select-show option').each(function() { + if (jQuery(this).val() == thisshowid) {jQuery(this).attr('selected','selected');} + }); + } + /* disable uneditable options */ + disabled = jQuery('#post-'+post_id+' .column-show .disabled-ids').text(); + if (disabled != '') { + if (disabled.indexOf(',') > -1) {disabled_ids = disabled.split(',');} + else {disabled_ids = new Array(); disabled_ids[0] = disabled;} + jQuery('#edit-'+post_id+' .select-show option').each(function() { + for (j = 0; j < disabled_ids.length; j++) { + if (jQuery(this).val() == disabled_ids[j]) { + jQuery(this).attr('disabled','disabled'); + if (jQuery(this).attr('selected') == 'selected') {jQuery(this).addClass('pre-selected');} + } + } + }); + } + } + } + }; + })(jQuery);"; + + wp_add_inline_script( 'radio-station-admin', $js ); + } +} + + // ------------- // === Posts === // ------------- @@ -6117,7 +6487,7 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { $stored_post = $post; // 2.3.3.5: added fix for post type context - if ( $post_type != 'post' ) { + if ( 'post' != $post_type ) { return; } @@ -6227,12 +6597,13 @@ function radio_station_post_column_data( $column, $post_id ) { // 2.3.3.6: only link to Shows user can edit $post = $show; if ( current_user_can( 'edit_shows' ) ) { - echo '' . PHP_EOL; + // 2.5.0: added missing esc_url wrapper + echo '' . PHP_EOL; } else { // 2.3.3.6: set disabled (uneditable) data $disabled[] = $show_id; } - echo esc_html( $show->post_title ) . '
    '; + echo esc_html( $show->post_title ) . '
    ' . "\n"; if ( current_user_can( 'edit_shows' ) ) { echo '
    ' . "\n"; } @@ -6240,11 +6611,9 @@ function radio_station_post_column_data( $column, $post_id ) { } } - // 2.5.0: added missing debug check wrapper - if ( RADIO_STATION_DEBUG ) { - echo '' . "\n"; - echo '' . "\n"; - } + // --- hidden show and disabled show IDs --- + echo '' . "\n"; + echo '' . "\n"; // --- restore global post object --- $post = $stored_post; @@ -6254,8 +6623,8 @@ function radio_station_post_column_data( $column, $post_id ) { // ------------------------------- // Related Shows Quick Edit Script // ------------------------------- -// 2.3.3.4: added Related Show Quick Edit value population script // ref: https://codex.wordpress.org/Plugin_API/Action_Reference/quick_edit_custom_box +// 2.3.3.4: added Related Show Quick Edit value population script // 2.3.3.6: disable uneditable Show select options add_action( 'admin_enqueue_scripts', 'radio_station_posts_quick_edit_script' ); function radio_station_posts_quick_edit_script( $hook ) { @@ -6615,12 +6984,13 @@ function radio_station_columns_query_filter( $query ) { $yearmonth = $_GET['month']; $start_date = date( $yearmonth . '-01' ); $end_date = date( $yearmonth . '-t' ); - $meta_query = array( + // 2.5.0: fix to use double array for meta_query + $meta_query = array( array( 'key' => 'show_override_date', 'value' => array( $start_date, $end_date ), 'compare' => 'BETWEEN', 'type' => 'DATE', - ); + ) ); $query->set( 'meta_query', $meta_query ); } @@ -6659,7 +7029,8 @@ function radio_station_columns_query_filter( $query ) { ); $meta_query = $combined_query; } else { - $meta_query = $pastfuture_query; + // 2.5.0: fix to use double array for single meta_query + $meta_query = array( $pastfuture_query ); } $query->set( 'meta_query', $meta_query ); } diff --git a/loader.php b/loader.php index b3dc6f3..bc67e00 100644 --- a/loader.php +++ b/loader.php @@ -744,7 +744,13 @@ public function update_settings() { $values = array(); // 1.2.7: fix color variable to posted // 1.2.7: make alpha a value key not separate - sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] ); + // 1.2.7: check number of commas to see if alpha is set + $commas = substr_count( $posted, ',' ); + if ( 3 == $commas ) { + sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] ); + } elseif ( 2 == $commas ) { + sscanf( $posted, 'rgba(%d,%d,%d)', $values['red'], $values['green'], $values['blue'] ); + } // echo 'rgba sscanf values: ' . print_r( $values, true ) . "\n"; // 1.2.7: fix for use of duplicate key variable foreach ( $values as $k => $v ) { @@ -765,7 +771,11 @@ public function update_settings() { } } } - $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')'; + if ( 3 == $commas ) { + $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')'; + } elseif ( 2 == $commas ) { + $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ')'; + } } $settings[$key] = $posted; @@ -3457,6 +3467,7 @@ function radio_station_settings_resources( $media, $color_picker ) { // == 1.2.7 == // - fix color picker alpha sanitization / saving +// - allow color picker alpha to not include alpha // - added color picker dropdown overlay styling // - added pointer cursor style to inactive tab diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 03658c2..aaea9bd 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -69,7 +69,6 @@ display: block; width: 100%; vertical-align: top; - text-align: left; } .rp-controls, .rp-volume-controls, .rp-popup, .rp-script-switcher { @@ -265,7 +264,7 @@ width: 18px; height: 18px; padding: 0; margin: 0; background-size: 36px; background-repeat: no-repeat; } -.light button.rp-mute, .light button.rp-volume-max, .light button.rp-volume-up, .light button.rp-volume-down, button.light button.rp-popup-button { +.light button.rp-mute, .light button.rp-volume-max, .light button.rp-volume-up, .light button.rp-volume-down, .light button.rp-popup-button { background-image: url("../images/volume-controls-light.png?v=1"); } .dark button.rp-mute, .dark button.rp-volume-max, .dark button.rp-volume-up, .dark button.rp-volume-down, .dark button.rp-popup-button { diff --git a/player/radio-player.php b/player/radio-player.php index 73c6f77..e1ac6eb 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -221,7 +221,7 @@ function radio_station_player_output( $args = array(), $echo = false ) { $instance = 0; if ( isset( $args['id'] ) && ( '' != $args['id'] ) ) { $id = abs( intval( $args['id'] ) ) ; - if ( $instance > -1 ) { + if ( $id > -1 ) { $instance = $id; } } @@ -752,6 +752,31 @@ function radio_station_player_shortcode( $atts ) { return $player; } +// ----------------------------------- +// Set Player Default Color Attributes +// ----------------------------------- +add_filter( 'radio_station_player_shortcode_attributes', 'radio_station_player_default_colors' ); +function radio_station_player_default_colors( $atts ) { + + // --- map bar color settings to shortcode attributes --- + // 2.5.0: added for mapping colors to attributes and instance + if ( isset( $atts['sitewide'] ) ) { + $keys = array( + 'playing_color' => 'player_playing_color', + 'buttons_color' => 'player_buttons_color', + 'track_color' => 'player_range_color', + 'thumb_color' => 'player_thumb_color', + ); + foreach ( $keys as $att => $key ) { + if ( !isset( $atts[$att] ) ) { + $atts[$att] = radio_station_get_setting( $key ); + } + } + } + + return $atts; +} + // ------------------- // Player AJAX Display // ------------------- @@ -761,22 +786,25 @@ function radio_station_player_ajax() { // --- sanitize shortcode attributes --- $atts = radio_station_player_sanitize_shortcode_values(); - // 2.5.0: check for popup attribute - $popup = ( isset( $atts['popup'] ) && $atts['popup'] ) ? true : false; - // 2.5.0: clear widget/block/popup attributes - $atts['widget'] = $atts['block'] = $atts['popup'] = 0; - // --- debug shortcode attributes --- - // if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { - echo 'Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; - // } + // 2.5.0: anti-conflict with WP theme querystring overrides + if ( isset( $_GET['theme'] ) ) { + unset( $_GET['theme'] ); + } + if ( isset( $_POST['theme'] ) ) { + unset( $_POST['theme'] ); + } + if ( isset( $_REQUEST['theme'] ) ) { + unset( $_REQUEST['theme'] ); + } - // --- output head --- + // --- open HTML and head --- // 2.5.0: buffer head content to maybe replace window title tag - echo '' . "\n"; + echo '' . "\n"; ob_start(); wp_head(); $head = ob_get_contents(); + ob_end_clean(); if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { if ( stristr( $head, '' . "\n"; + // 2.5.0: check for popup attribute + $popup = ( isset( $atts['popup'] ) && $atts['popup'] ) ? true : false; + // 2.5.0: clear widget/block/popup attributes + $atts['widget'] = $atts['block'] = $atts['popup'] = 0; + + // 2.5.0: strip text color attribute (applied to div container) + $text_color = ''; + if ( isset( $atts['text_color'] ) ) { + $text_color = $atts['text_color']; + unset( $atts['text_color'] ); + if ( isset( $atts['text'] ) ) { + unset( $atts['text'] ); + } + } elseif ( isset( $atts['text'] ) ) { + $text_color = $atts['text']; + unset( $atts['text_color'] ); + } elseif ( function_exists( 'apply_filters' ) ) { + $text_color = apply_filters( 'radio_player_text_color', $text_color ); + } + + // 2.5.0: strip background color attribute (applied to window body) + $background_color = ''; + if ( isset( $atts['background_color'] ) ) { + $background_color = $atts['background_color']; + unset( $atts['background_color'] ); + if ( isset( $atts['background'] ) ) { + unset( $atts['background'] ); + } + } elseif ( isset( $atts['background'] ) ) { + $background_color = $atts['background']; + unset( $atts['background'] ); + } elseif ( function_exists( 'apply_filters' ) ) { + // 2.5.0: fallaback to apply_filters + $background_color = apply_filters( 'radio_player_background_color', $background_color ); + } + + // --- debug shortcode attributes --- + // if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { + echo 'Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; + // } + // --- output widget contents --- // 2.5.0: maybe add popup player class echo '' . "\n"; - // --- maybe add background color --- - if ( isset( $atts['background'] ) ) { - $background = $atts['background']; - } else { - // 2.5.0: fallaback to apply_filters - $background = apply_filters( 'radio_player_background_color', '' ); - } - if ( '' != $background ) { - echo '' . "\n"; - } + // --- close body and HTML --- + echo '' . "\n"; - echo '' . "\n"; exit; } @@ -839,6 +924,8 @@ function radio_station_player_ajax() { function radio_station_player_sanitize_shortcode_values() { // --- current show attribute keys --- + // 2.5.0: added alternative text attribute + // 2.5.0: added block attribute $keys = array( 'url' => 'url', 'title' => 'text', @@ -851,7 +938,7 @@ function radio_station_player_sanitize_shortcode_values() { 'default' => 'boolean', 'widget' => 'boolean', 'background' => 'text', - // 2.5.0: added block attribute + 'text' => 'text', 'block' => 'boolean', ); @@ -2578,6 +2665,7 @@ function radio_station_player_control_styles( $instance ) { } // --- check for player instance --- + // 2.5.0: improved instance and ID matching if ( false !== $instance ) { // -- set instance container selector --- @@ -2588,8 +2676,16 @@ function radio_station_player_control_styles( $instance ) { $instance_props = $radio_player['instance-props'][$instance]; foreach ( $instance_props as $key => $value ) { if ( substr( $key, -6, 6 ) == '_color' ) { + + // 2.5.0: ignore text and background for bar instance + if ( isset( $radio_player['bar-instance'] ) && ( $instance == $radio_player['bar-instance'] ) ) { + if ( in_array( $key, array( 'text_color', 'background_color' ) ) ) { + $value = ''; + } + } + if ( $value && ( '' != $value ) ) { - if ( substr( $value, 0, 3 ) !== 'rgb' ) { + if ( 'rgb' != ( substr( $value, 0, 3 ) ) && ( '#' != substr( $value, 0, 1 ) ) ) { $value = '#' . $value; } $key = str_replace( '_color', '', $key ); @@ -2598,6 +2694,7 @@ function radio_station_player_control_styles( $instance ) { } } } + } else { // 2.5.0: added check to do once only if ( isset( $radio_player['control-styles'] ) ) { diff --git a/radio-station.php b/radio-station.php index 3a3b8c7..b39473c 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.4 +Version: 2.4.9.5 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 1e59251..79502c9 100644 --- a/readme.txt +++ b/readme.txt @@ -224,12 +224,14 @@ You can now visit your site to make sure nothing is broken. If you experience is * Updated: Plugin Panel (1.2.7) * Updated: Moment JS (2.29.4) with WP Loading * Changed: Tab Schedule default date display on -* Improved: Refactored Schedule Engine Class (1.0.0) +* Improved: Refactored Schedule Engine Class (2.5.0) * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards * Improved: Sanitization using KSES * Improved: Translation Implementation * Improved: use WP JSON functions for data endpoints +* Added: assign Playlist to a specific Show Shift +* Added: Quick Edit of Playlist to assign to Show * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index c6e64cc..e447c4a 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -1,8 +1,11 @@ Date: Tue, 25 Oct 2022 21:43:42 +1000 Subject: [PATCH 276/377] dev updates --- player/css/radio-player.css | 4 ---- player/radio-player.php | 7 ++++++- radio-station.php | 5 ++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/player/css/radio-player.css b/player/css/radio-player.css index aaea9bd..18985c7 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -236,10 +236,6 @@ opacity: 0.9; } -.rp-popup { - margin-top: -12px; -} - .radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus { opacity: 1; } diff --git a/player/radio-player.php b/player/radio-player.php index e1ac6eb..2e71a25 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -800,10 +800,15 @@ function radio_station_player_ajax() { // --- open HTML and head --- // 2.5.0: buffer head content to maybe replace window title tag + // note: do not remove these span tags, they magically "fix" broken output buffering!? echo '' . "\n"; + echo ''; ob_start(); + echo ''; wp_head(); + echo ''; $head = ob_get_contents(); + echo ''; ob_end_clean(); if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { if ( stristr( $head, ' Date: Mon, 31 Oct 2022 21:53:25 +1000 Subject: [PATCH 277/377] dev updates --- CHANGELOG.md | 10 +- blocks/archive.js | 2 +- blocks/clock.js | 2 +- blocks/current-playlist.js | 2 +- blocks/current-show.js | 2 +- blocks/editor.js | 19 +- blocks/player.js | 2 +- blocks/schedule.js | 2 +- blocks/upcoming-shows.js | 2 +- css/rs-shortcodes.css | 21 +- docs/FAQ.md | 27 +- docs/Options.md | 38 +- docs/Player.md | 132 ++++- docs/Pro.md | 23 +- docs/Roadmap.md | 48 +- docs/Shortcodes.md | 58 +- docs/Widgets.md | 136 ++++- includes/blocks.php | 37 +- includes/master-schedule.php | 2 + includes/post-types-admin.php | 13 +- includes/shortcodes.php | 1028 +++++++++++++++++++++------------ options.php | 6 +- radio-station.php | 2 +- readme.txt | 6 +- 24 files changed, 1139 insertions(+), 481 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7016fe8..eb027f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated: Freemius SDK (2.4.5) * Updated: Plugin Panel (1.2.7) * Updated: Moment JS (2.29.4) with WP Loading -* Changed: Tab Schedule default date display on -* Improved: Refactored Schedule Engine Class (1.0.0) +* Improved: Refactored Schedule Engine Class (2.5.0) +* Improved: Redesigned higher resolution player buttons * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards * Improved: Sanitization using KSES * Improved: Translation Implementation * Improved: use WP JSON functions for data endpoints +* Improved: Schedule Templates to use Classes and Instances +* Improved: Tab Schedule default date display on +* Added: assign Playlist to a specific Show Shift +* Added: Quick Edit of Playlist to assign to Show * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection +* Fixed: Mobile detection (via any pointer type) +* Fixed: Adjacent Post Links (where show has one shift) ### 2.4.0.9 * Update: Sysend (1.11.1) for Radio Player diff --git a/blocks/archive.js b/blocks/archive.js index 1281e72..54dd2f9 100644 --- a/blocks/archive.js +++ b/blocks/archive.js @@ -30,7 +30,7 @@ title: '[Radio Station] Archive List', description: __( 'Archive list for Radio Station record types.', 'radio-station' ), icon: 'media-audio', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { /* --- Archive List Details --- */ diff --git a/blocks/clock.js b/blocks/clock.js index b4f4c93..84420ce 100644 --- a/blocks/clock.js +++ b/blocks/clock.js @@ -17,7 +17,7 @@ title: '[Radio Station] Radio Clock', description: __( 'Radio Station Clock time display.', 'radio-station' ), icon: 'clock', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { /* --- Clock Display Options --- */ diff --git a/blocks/current-playlist.js b/blocks/current-playlist.js index 7374b9c..65e9042 100644 --- a/blocks/current-playlist.js +++ b/blocks/current-playlist.js @@ -17,7 +17,7 @@ title: '[Radio Station] Current Playlist', description: __( 'Radio Station current playlist block.', 'radio-station' ), icon: 'playlist-audio', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { /* --- Loading Options --- */ diff --git a/blocks/current-show.js b/blocks/current-show.js index 6a3bb3e..bd711f1 100644 --- a/blocks/current-show.js +++ b/blocks/current-show.js @@ -24,7 +24,7 @@ title: '[Radio Station] Current Show', description: __( 'Radio Station current show block.', 'radio-station' ), icon: 'controls-play', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { /* --- Loading Options --- */ diff --git a/blocks/editor.js b/blocks/editor.js index 1ca26f3..1a9ebfb 100644 --- a/blocks/editor.js +++ b/blocks/editor.js @@ -1,3 +1,12 @@ + +/* === Radio Station Block Editor Scripts === */ + +/* --- Add Block Category Icon --- */ +/* ( function() { + const AntennaSVG = ''; + wp.blocks.updateCategory( 'radio-station-blocks', { icon: AntennaSVG } ); +} )(); */ + /* --- Subscribe to Block State --- */ /* ref: https://wordpress.stackexchange.com/a/358256/76440 */ ( () => { @@ -22,9 +31,9 @@ if ( !block.attributes.pro ) { if ( block.attributes.view == 'table' ) {console.log('Table View Schedule Found'); s_table = true;} else if ( block.attributes.view == 'tabs' ) {console.log('Tab View Schedule Found'); s_tabs = true;} + else if ( block.attributes.view == 'list' ) {console.log('List View Schedule Found'); s_list = true;} else if ( block.attributes.view == 'grid' ) {console.log('Grid View Schedule Found'); s_grid = true;} else if ( block.attributes.view == 'calendar' ) {console.log('Calendar View Schedule Found'); s_calendar = true;} - else if ( block.attributes.view == 'list' ) {console.log('List View Schedule Found'); s_list = true;} } /* --- [Pro] Multiple Views --- */ @@ -32,7 +41,7 @@ s_multi = true; if ( block.attributes.views.includes( 'table' ) ) {console.log('Table View Schedule Found'); s_table = true;} if ( block.attributes.views.includes( 'tabs' ) ) {console.log('Tab View Schedule Found'); s_tabs = true;} - if ( block.attributes.views.includes( 'grid' ) ) {console.log('Grid View Schedule Found');s_grid = true;} + if ( block.attributes.views.includes( 'grid' ) ) {console.log('Grid View Schedule Found'); s_grid = true;} if ( block.attributes.views.includes( 'calendar' ) ) {console.log('Calendar View Schedule Found'); s_calendar = true;} if ( block.attributes.views.includes( 'list' ) ) {console.log('List View Schedule Found'); s_list = true;} } @@ -79,18 +88,18 @@ if (s_calendar && !jQuery('#radio-schedule-calendar-js').length) { radio_station_load_block_script('schedule-calendar'); var radio_load_calendar = setInterval(function() { if (typeof radio_calendar_initialize == 'function') { - radio_calendar_init = false; radio_calendar_initialize(); radio_shows_slider(); clearInterval(radio_load_calendar); + radio_calendar_init = false; radio_calendar_initialize(); radio_sliders_check(); clearInterval(radio_load_calendar); } }, 1000); } } if (player) { radio_station_load_block_script('player'); - /* TODO: initialize player ? + /* TODO: maybe initialize player ? var radio_load_player = setInterval(function() { if (typeof ??? == 'function') { radio_player_init = false; ???(); clearInterval(radio_load_player); } }, 1000); */ } - if (archive ) { + if (archive) { /* TODO: check for archive pagination type */ } } diff --git a/blocks/player.js b/blocks/player.js index 5a6c35b..eb09e35 100644 --- a/blocks/player.js +++ b/blocks/player.js @@ -17,7 +17,7 @@ title: __( '[Radio Station] Stream Player', 'radio-station' ), description: __( 'Audio stream player block.', 'radio-station' ), icon: 'controls-volumeon', - category: 'media', + category: 'radio-station', example: {}, attributes: { /* --- Player Content --- */ diff --git a/blocks/schedule.js b/blocks/schedule.js index a5941ca..d052eed 100644 --- a/blocks/schedule.js +++ b/blocks/schedule.js @@ -33,7 +33,7 @@ title: '[Radio Station] Program Schedule', description: __( 'Radio Station Schedule block.', 'radio-station' ), icon: 'calendar-alt', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { diff --git a/blocks/upcoming-shows.js b/blocks/upcoming-shows.js index 48e06a9..0be71a3 100644 --- a/blocks/upcoming-shows.js +++ b/blocks/upcoming-shows.js @@ -24,7 +24,7 @@ title: '[Radio Station] Upcoming Shows', description: __( 'Radio Station upcoming shows block.', 'radio-station' ), icon: 'controls-forward', - category: 'widgets', + category: 'radio-station', example: {}, attributes: { /* --- Loading Options --- */ diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index 0dcc422..09a0989 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -156,7 +156,15 @@ margin-bottom: 10px; } -.show-pagination-button { +/* Archive Pagination */ +/* ------------------ */ +.show-pagination-buttons, .show-pagination-buttons::after, .archive-pagination-buttons, .archive-pagination-buttons::after { + clear: both; + content: ""; + display: block; +} + +.show-pagination-button, .archive-pagination-button { float: left; display: inline-block; width: 30px; @@ -171,17 +179,22 @@ padding: 5px; } -.show-pagination-button.arrow-button { +.show-pagination-button a, .show-pagination-button a:hover, +.archive-pagination-button a, .archive-pagination a:hover { + text-decoration: none; +} + +.show-pagination-button.arrow-button, .archive-pagination-button.arrow-button { padding-top: 4px; padding-bottom: 6px; } -.show-pagination-button.active { +.show-pagination-button.active, .archive-pagination-button.active { background-color: transparent; font-weight: bold; } -.show-pagination-button:hover { +.show-pagination-button:hover, .archive-pagination-button:hover { background-color: #f5f5f5; } diff --git a/docs/FAQ.md b/docs/FAQ.md index 0b02b1e..ea736f0 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -111,12 +111,29 @@ We haven't built an interface between Google Calendar and Radio Station just yet ### How do I install the latest Development version for testing? -If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: +If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: -1. Download the `develop` branch zip from the Github repository at: +1. Visit the `develop` branch of the Radio Station Github repository at: `https://github.com/netmix/radio-station/tree/develop/` -2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` -3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` -4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` +2. Click on the green "Code" button and select Download a ZIP. +3. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-develop/` +4. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old/` +5. Rename the subdirectory `/wp-content/plugins/radio-station-develop/` to `/wp-content/plugins/radio-station/` + +Then upload to WordPress via the plugin installer as normal. +Note that it will install to /wp-content/plugins/radio-station-develop/, and because of this won't overwrite your existing installation, so you'll need to deactivate that before activating the development version. You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. + +Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. + + +### What about Pro Beta Version Testing? + +We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: + +1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://dashboard.freemius.com). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. +2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. + +We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. + diff --git a/docs/Options.md b/docs/Options.md index b2efb8a..d7f0de0 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -124,6 +124,10 @@ Default Player Controls theme style. Light or dark to match your theme. Default: rounded. Key: player_buttons Default Player Buttons shape style. Circular, rounded or square. +#### Player Volume Controls +Default: all. Key: player_volumes +Which volume controls to display in the Player by default. + #### Player Debug Mode Default: off. Key: player_debug Output player debug information in browser javascript console. @@ -150,12 +154,16 @@ Initial volume for when the Player starts playback. 0-100 #### Single Player Default: on. Key: player_single -Stop any existing Players on the page or in other windows or tabs when a Player is started. +Stop any existing Player instances on the page or in other windows or tabs when a Player is started. #### [Pro] Player Autoresume Default: on. Key: player_autoresume Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page. +#### [Pro] Player Popup +Default: off. Key: player_popup +Add button to open Popup Player in separate window. + #### [Pro] Sitewide Player Bar Default: off. Key: player_bar Add a fixed position Player Bar which displays Sitewide. Fixed top or bottom position. @@ -180,6 +188,18 @@ Text color for the fixed position Sitewide Bar Player. Default: black. Key: player_bar_background Background color for the fixed position Sitewide Bar Player. +#### [Pro] Bar Player Current Show +Default: on. Key: player_bar_currentshow +Display the Current Show in the Player Bar. + +#### [Pro] Bar Player Now Player +Default: on. Key: player_bar_nowplaying +Display the Now Playing Track metadata in the Player Bar. + +#### [Pro] Bar Player Metadata Source +Default: none (use Stream URL.) Key: player_bar_metadata +Alternative metadata source URL for Now Playing Track metadata. + ## Pages @@ -271,6 +291,11 @@ Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode: `[playlists-archive]` See [Playlist Archives Shortcode](./Shortcodes.md#playlist-archives-shortcode) for more info. +#### [Pro] Team Archives Page +Default: None. Key: team_archive_page +Replaces selected page content with default Team Archive (Hosts and Producers.) +Alternatively customize display using the shortcode: `[team-archive]` + #### Genre Archives Page Default: None. Key: genre_archive_page Select the Page for displaying the Genre archive list. @@ -315,11 +340,18 @@ Whether to load Widget contents via AJAX by default. This prevents stale cached Default: On. Key: dynamic_reload Whether to reload Widgets automatically on change of Current Show. Can also be set on a per widget basis. +#### [Pro] Convert Show Times +Default: On. Key: convert_show_times +Automatically display Show times converted into the visitor timezone, based on their browser setting. + +#### [Pro] User Timezone Switching +Default: On. Key: timezone_switching +Allow visitors to select their Timezone manually for Show time conversions. + ## Roles -Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types. -You can assign this Role to any user to give them full Station Schedule updating permissions without giving them a WordPress administrator role. +Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types. You can assign this Role to any user to give them complete Station Schedule and Radio Station Post Type updating permissions without giving them a full WordPress administrator role. See [Roles](./Roles.md#show-editor-role] for more info. ### Permissions diff --git a/docs/Player.md b/docs/Player.md index 8013db6..a49cc46 100644 --- a/docs/Player.md +++ b/docs/Player.md @@ -5,23 +5,133 @@ ## Radio Player -### Default Player Settings +The Radio Player is available as a Shortcode, Widget or Block. In Pro it is also available as a Sitewide Bar Player, and as an Elementor Widget and Beaver Builder Module. -Default settings for the Player can be set on the Plugin Settings page on the Player tab. These will be used in widgets wherever the widget options are set to "default". This saves you from setting them twice, but also means that you can override these defaults in individual widgets as needed. (see [Options](./Options.md#player) for a list of these options.) +### Default Player Settings +Default settings for the Player can be set on the Plugin Settings page on the Player tab. These will be used in widgets wherever the widget options are set to "Default". This saves you from setting them twice, but also means that you can override these defaults in individual widgets as needed. (see [Options](./Options.md#player) for a list of these options.) ### Radio Player Widget -A Player widget instance can be added via the WordPress admin Appearance -> Widgets page. +A Player widget instance can be added via the WordPress Admin Appearance -> Widgets page. The widget options correspond to the shortcode attributes below, allowing you control over the widget output. +### Radio Player Block + +A Player block can be added via the Block Editor by clicking the blue + icon. The `[Radio Station] Stream Player` Block can be found in the Media category. + #### [Pro] Sitewide Bar Player [Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player. It isn't added via the Widgets Page, but is instead configured via the Plugin Settings page under the Player tab. It has the following options: * Display fixed in the header or footer area (top or bottom of page, unaffected by scrolling.) * ... +* Popup Player Button : adds a button to open the player in a separate window. + + + + +// --- [Pro/Player] Playing Highlight Color --- + 'player_playing_color' => array( + 'type' => 'color', + 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), + 'default' => '#70E070', + 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Control Icons Highlight Color --- + 'player_buttons_color' => array( + 'type' => 'color', + 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), + 'default' => '#00A0E0', + 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Volume Knob Color --- + 'player_thumb_color' => array( + 'type' => 'color', + 'label' => __( 'Volume Knob Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Volume Track Color --- + 'player_range_color' => array( + 'type' => 'coloralpha', + 'label' => __( 'Volume Track Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // === Advanced Stream Player === + + // --- [Player] Player Volume --- + 'player_volume' => array( + 'type' => 'number', + 'label' => __( 'Player Start Volume', 'radio-station' ), + 'default' => 77, + 'min' => 0, + 'step' => 1, + 'max' => 100, + 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- [Player] Single Player --- + 'player_single' => array( + 'type' => 'checkbox', + 'label' => __( 'Single Player at Once', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- [Pro/Player] Player Autoresume --- + 'player_autoresume' => array( + 'type' => 'checkbox', + 'label' => __( 'Autoresume Playback', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), + + + Bar Player Text Color + Text color for the fixed position Sitewide Bar Player. + + Bar Player Background Color + Background color for the fixed position Sitewide Bar Player. + + Current Show Display + Display the Current Show in the Player Bar. + + Now Playing Metadata + Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist + + Metadata URL + Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location. + @@ -42,8 +152,20 @@ The following attributes are available for this shortcode: [Demo Site Example Output](https://demo.radiostation.pro/player-shortcode/) - -#### Shortcode in Templates +#### Using the Shortcode in Templates Remember, if you want to display a Shortcode within a custom Template (or elsewhere in custom code), you can use the WordPress `do_shortcode` function. eg. `do_shortcode('[radio-player]');` +#### [Pro] Extra Shortcode Options + +* **Color Options** +* `text_color` : . Default none (inherit.) +* `background_color` : . Default none (inherit.) +* `playing_color` : . Default Plugin Setting. +* `buttons_color` : . Default Plugin Setting. +* `track_color` : . Default Plugin Setting. +* `thumb_color` : . Default Plugin Setting. +* `popup` : Add button to open Popup Player in separate window. 0 or 1. Default 0. + + + diff --git a/docs/Pro.md b/docs/Pro.md index 0c14c43..ae6913c 100644 --- a/docs/Pro.md +++ b/docs/Pro.md @@ -6,46 +6,59 @@ Pro Feature Documentation is included where relevant within the existing Free ve But in order to find the Pro feature documentation more easily, an index is provided here linking to each of those sections. -Note that additional Pro Documentation will be added as new Features are released into the Pro version. +Note that additional Pro Documentation will be added in the relevant section as new Features are released into the Pro version. + +If you are interested in testing cutting edge features in development for this next release, read about [Installing the Development Version](./FAQ.md#how-do-i-install-the-latest-development-version-for-testing) and [Pro Beta Version Testing](./#pro-beta-version-testing). #### API + [Episodes Data Endpoint](./API.md#pro-episodes-endpoint) [Hosts Data Endpoint](./API.md#pro-hosts-endpoint) [Producers Data Endpoint](./API.md#pro-producers-endpoint) #### Data + [Show Episodes](./Data.md#pro-show-episodes) [Host and Producer Profiles](./Data.md#pro-host-and-producer-profiles) #### Display + [Profile Images](./Display.md#pro-profile-images) [Genre Images and Colors](./Display.md#genre-images-and-colors) #### Filters + [Pro Filters List](./Filters.md#pro-pro-filter-list) #### Manage -[Visual Shift Editor)(./Manage.md#pro-visual-shift-editor) + +[Visual Shift Editor](./Manage.md#pro-visual-shift-editor) #### Options + [Pro Options](./Options.md) (throughout options list) #### Roles + [Role Editor Interface](./Roles.md#pro-role-editor-interface) #### Shortcodes + [Schedule Grid View](./Shortcodes.md#master-schedule-shortcode) [Schedule Calendar View](./Shortcodes.md#master-schedule-shortcode) [Multiple View Switching](./Shortcodes.md#pro-multiple-view-switching) +[Timezone Switching](./Shortcodes.md#pro-user-timezone-switching) [Hosts Archive Shortcode](./Shortcodes.md#pro-hosts-archive-shortcode) [Producers Archive Shortcode](./Shortcodes.md#pro-producers-archive-shortcode) +[Team Archive Shortcode](./Shortcodes.md#pro-team-archive-shortcode) [Show Episodes Archive Shortcode](./Shortcodes.md#pro-show-episodes-archive-shortcode) #### Widgets + [Sitewide Bar Player(./Player.md#pro-sitewide-bar-player) [Timezone Switcher](./Widgets.md#pro-timezone-switcher) - - - +[Extra Block Options](./Widgets.md#pro-extra-block-options) +[Elementor Widgets](./Widgets.md#pro-elementor-widgets) +[Beaver Builder Modules]('./Widgets.md#pro-beaver-builder-modules) diff --git a/docs/Roadmap.md b/docs/Roadmap.md index a991859..3b18973 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -2,12 +2,14 @@ *** +Since taking over the development of the free Radio Station plugin in June 2019, we've made massive improvements to every aspect of the project. Two years later, we released the Pro version and have continued to update and improve both versions, adding innovative features as we go. -Current Major Release Version: 2.3.3 +The below table is a simplified overview to help keep track of all the major different features in Free and Pro, both those that we've added already and planned upcoming features. (If you want to contribute ideas or suggestions, please do so by opening a [Github Issue](https://github.com/netmix/radio-station/).) -Upcoming Major Release Version: 2.3.4 +If you are interested in testing cutting edge features in development for this next release, read about [Installing the Development Version](./FAQ.md#how-do-i-install-the-latest-development-version-for-testing) and [Pro Beta Version Testing](./#pro-beta-version-testing). -For minor release notes, see the [Changelog](./CHANGELOG.md) +For minor release notes, see the [Radio Station Changelog](./CHANGELOG.md) +(Note: The Radio Station Pro changelog can be found in the plugin's `readme.txt`) Normal = Feature Completed @@ -17,22 +19,30 @@ Normal = Feature Completed | Free | Version | Pro | Version | | --- | --- | --- | --- | -| Scheduler with Conflict Checker | 2.3.0 | *with Visual Schedule Editor* | 2.3.5 | -| Master Schedule Views | 2.2.8 | with Grid View and View Switching | 2.3.4 | -| Show Page Display Template | 2.3.0 | *with Social Icons* | 2.3.5 | -| Show Genre Assignments | 2.2.8 | with Genre Term Image Support | 2.3.0 | +| SCHEDULE | +| Scheduler with Conflict Checker | 2.3.0 | with Visual Schedule Editor | 2.3.5 | +| Table, Tab and List Schedule Views | 2.2.8 | with Grid, Calendar and View Switching | 2.3.4 | +| Weekly Show Shifts and Date Overrides | 2.2.8 | with Recurring Periodic Overrides | 2.6.0 | +| CONTENT | +| Show Posts and Linked Overrides | 2.2.8 | with Show Episodes Post Type | 2.4.1 | +| Playlist with Track Editor | 2.2.8 | **Show Episode Segment Editor** | | +| Archive Shortcodes with Automatic Pages | 2.3.0 | with Episode and Team Archives | 2.4.1.8 | +| Show Page Display Template | 2.3.0 | with Social Icons | 2.3.5 | +| Show Hosts and Producers | 2.2.8 | with Profile Page Templates | 2.4.1 | +| PLAYER | +| Streaming Player Widget/Shortcode | 2.3.4 | with Sitewide Persistant Player Bar | 2.4.1 | +| Player Theme and Button Options | 2.3.4 | with Extra Skin and Color Options | 2.6.0 | +| *File Playing Support and Buttons* | | *with Playlist Track Support* | | +| WIDGETS/SHORTCODES | +| Radio Blocks (Gutenberg Block Editor) | 2.5.0 | Elementor and Beaver Builder Modules | 2.6.0 | +| Show Widget/Shortcodes with Countdowns | 2.2.8 | with Dynamic Reloading | 2.4.1 | +| Current Playlist Widget/Shortcode | 2.2.8 | Player Track Metadata Display | 2.4.1.3 | +| Radio Clock Widget/Shortcode | 2.3.2 | with User Timezone Switcher | 2.4.1 | +| EXTRAS | | Host / Producer / Show Editor Roles | 2.3.0 | with Role Assignment Interface | 2.3.0 | -| **Streaming Widget Player** | 2.3.4 | **with Sitewide Persistant Player** | 2.3.3 | -| Current and Upcoming Show Widgets | 2.2.8 | - | - | -| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.4 | -| Playlist Widget | 2.2.8 | *with Track Affiliate Links* | 2.3.5 | -| Show Posts and Playlists | 2.2.8 | *with Show Episodes Post Type* | 2.3.4 | -| Post Type Archive Shortcodes | 2.3.0 | *with Grid View* | 2.3.4 | -| Show Hosts and Producers | 2.2.8 | *with Guests and Profile Page Templates* | 2.3.4 | -| REST/Feed API Endpoints | 2.3.0 | *with Episodes, Hosts and Producers* | 2.3.4 | -| Radio Clock and Widget | 2.3.2 | *with Timezone Switcher* | 2.3.4 | -| Schedule Caching | 2.3.0 | with Show Meta Caching | 2.3.0 | -| Standard HTML Markup | 2.3.0 | with Schema.Org Markup | 2.3.4 | +| REST/Feed API Endpoints | 2.3.0 | with Episodes, Hosts and Producers | 2.4.1 | +| Standard HTML Markup Output | 2.3.0 | *with Schema.Org SEO Markup* | | +| Show Genre Assignments | 2.2.8 | Episode Guests and Topics | 2.4.1 | [comment]: # (Show and Override Feeds) -Interested in "going pro"? [Find out more about Radio Station Pro](https://radiostation.pro/). +Interested in going Pro? For a full feature list comparison, [click here to find out more about Radio Station Pro](https://radiostation.pro/pricing/). diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 5292ae5..7105179 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -11,7 +11,7 @@ Shortcode Output Examples can be seen on the [Radio Station Demo Site](http://de ### Master Schedule Shortcode -Use the shortcode `[master-schedule]` on any page. This will generate a full-page schedule in one of five Views: +Use the shortcode `[radio-schedule]` or `[master-schedule]` on any page. This will generate a full-page schedule in one of five Views: * [Table](https://demo.radiostation.pro/master-schedule/table-view/) (default) - responsive program in table form * [Tabbed](https://demo.radiostation.pro/master-schedule/tabbed-view/) - responsive styled list view with day selection tabs @@ -23,32 +23,36 @@ Use the shortcode `[master-schedule]` on any page. This will generate a full-pag The above View names are linked to examples on the Demo Site. -Note that Divs and Legacy Views are considered deprecated as they do not honour Schedule Overrides, but have been kept for backwards compatibility. The legacy Divs view also has display issues. +Note that Divs and Legacy Views are considered deprecated as they do not honour Schedule Overrides, but have been kept for backwards compatibility only. The legacy Divs view also has display issues. The following attributes are available for the shortcode: +* ** Layout Display Options ** * *view* : Which View to use for display output. 'table', 'tabs', 'list', 'divs', 'legacy', 'grid', 'calendar'. Default 'table'. -* *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. -* *clock* : Display Radio Clock above schedule. 0 or 1. Default is the Plugin Setting. -* *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. +* *time* : Display time format you with to use. 12 and 24. Default is Plugin Setting. +* *clock* : Display Radio Clock above schedule. (See clock shortcode.) 0 or 1. Default is Plugin Setting. +* ** Show Display Attributes ** * *show_times* : Whether to display the show shift's start and end times. 0 or 1. Default 1. +* *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. * *show_image* : Whether the display the show's avatar. 0 or 1. Default 0 (1 for Tabbed View.) -* *show_genres* : Whether to display a list of show genres. 0 or 1. Default 0 (1 for Tabbed View.) * *show_desc* : Whether to display Show Description excerpt. 0 or 1. * *show_hosts* : Whether to display a list of show hosts. 0 or 1. Default 0. * *link_hosts* : Whether to link each show host to their profile page. 0 or 1. Default 0. +* *show_genres* : Whether to display a list of show genres. 0 or 1. Default 0 (1 for Tabbed View.) * *show_encore* : Whether to display 'encore airing' for a show shift. 0 or 1. Default 1. * *show_file* : Whether to add a link to the latest audio file. 0 or 1. Default 0. +* ** Time Display Attributes ** * *days* : Display for single day or multiple days (string or 0-6, comma separated.) Default all. -* *start_day* : day of the week to start schedule (string or 0-6, or 'today') Default WordPress setting. +* *start_day* : day of the week to start schedule (string or 0-6, or 'today') Defaults to WordPress setting. * *display_day* : Full or short day heading ('full' or 'short') Default short for Table, full for Tabs/List. * *display_date* : Date format for date subheading. 0 for none. Default 'jS' for Table/List, 0 for Tabs. * *display_month* : Full or short month subheading ('full', 'short') Default 'short'. -* *image_position* : Show image position for Tabs view. 'right', 'left' or 'alternate'. Default 'left'. -* *hide_past_shows* : Hide shows that are finished in the Schedule for Tabs view. 0 or 1. Default 0. -* *divheight* : Set the height, in pixels, of the individual divs. For legacy 'divs' view only. Default 45. -* *gridheight* : Set the width, in pixels, of the grid columns. For Pro 'grid' view only. Default 150. -* 'time_spaced* : Enabled time spacing with background images. For Pro 'grid' view only. 0 or 1. Default 0. +* ** View-specific Attributes ** +* *image_position* : Tabs View: Show image position placement. 'right', 'left' or 'alternate'. Default 'left'. +* *hide_past_shows* : Tabs View: Hide shows that are finished in the Schedule. 0 or 1. Default 0. +* *gridwidth* : [Pro] Grid View: Set the width, in pixels of the grid columns. Default 150. +* *divheight* : [Legacy] Divs View: Set the height, in pixels, of the individual divs. Default 45. +* 'time_spaced* : [Pro] Grid View: Enabled time spacing with background images. 0 or 1. Default 0. * *weeks* : Number of weeks to display in calendar. For Pro 'calendar' view only. Default 4. * *previous_weeks* : Number of past weeks to display in calendar. For Pro 'calendar' view only. Default 1. @@ -85,18 +89,24 @@ Displays the Radio Station Timezone selected via Plugin Settings. There are no a `[radio-clock]` -Added in 2.3.2. Displays the current server time and user time. Also available as a Widget. +Displays the current server time and user time. Also available as a Widget. The following attributes are available for this shortcode: -* *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. -* *seconds* : Display seconds with current times. 0 or 1. Default 0. +* *time_format* : Display time format you with to use. 12 and 24. Default is Plugin Setting. * *day* : Display day after current times. 'full', 'short' or 'none'. Default 'full'. * *date* : Display date after current times. 0 or 1. Default 1. * *month* : Display months after current times. 'full', 'short' or 'none'. Default 'full'. * *zone* : Display timezone after current times. 0 or 1. Default 1. +* *seconds* : Display seconds with current times. 0 or 1. Default 0. + +The Radio Clock can also be displayed by default above the Master Schedule by enabling this in the Plugin Settings, or by adding `clock="1"` to the Master Schedule shortcode. (It's display attributes as part of the schedule can changed using the `radio_station_schedule_clock` filter.) + + +##### [Pro] User Timezone Switching + +In [Radio Station Pro](https://radiostation.pro), where the Radio Clock is displayed (whether as a widget or part of the schedule) the user is given an extra link "Change Timezone" through which they can select their local timezone. The Radio Clock (and/or Schedule) will then provide an additional converted display for displayed times into the user's selected timezone. -The Radio Clock can also be displayed by default above the Master Schedule by enabling this in the Plugin Settings. (It's display attributes there can changed via the `radio_station_schedule_clock` filter.) ## Archive Shortcodes @@ -250,6 +260,18 @@ The following attributes are available for this shortcode: [Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/producers-archive/) +### [Pro] Team Archive Shortcode + +`[team-archive]` + +The Team Archive Shortcode + +* *view* : Layout display view. 'list' or 'grid'. Default list. + + + +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/team-archive/) + ### Show Posts Archive Shortcode `[show-posts-archive]` (or `[show-post-archive]`) @@ -305,7 +327,7 @@ This shortcode will be available in [Radio Station Pro](https://radiostation.pro `[show-list]` -This shortcode is considered Deprecated. Use the [Shows Archive Shortcode](#shows-archive-shortcode) instead: `[shows-archive]` +This shortcode is considered *Deprecated*. Use the [Shows Archive Shortcode](#shows-archive-shortcode) instead: `[shows-archive]` The following attributes are available for this shortcode: @@ -317,7 +339,7 @@ Examples: `[list-shows genre="pop"]`, `[list-shows genre="pop,rock,metal"]` `[show-playlists]` (or `[get-playlists]` -This shortcode is considered Deprecated. Use the [Show Playlists Archive Shortcode](#show-playlists-archive-shortcode) instead: `[show-playlists-archive]` +This shortcode is considered *Deprecated*. Use the [Show Playlists Archive Shortcode](#show-playlists-archive-shortcode) instead: `[show-playlists-archive]` The following attributes are available for this shortcode: diff --git a/docs/Widgets.md b/docs/Widgets.md index 5a3fcca..84e8e78 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -2,46 +2,72 @@ *** +[Radio Player Widget](./Widgets.md#radio-player-widget) +[Radio Player Shortcode](./Widgets.md#radio-player-shortcode) +[[Pro] Sitewide Bar Player](./Widgets.md#pro-sitewide-bar-player) +[Current Show Widget](./Widgets.md#current-show-widget) +[Current Show Shortcode](./Widgets.md#current-show-shortcode) +[Upcoming Shows Widget](./Widgets.md#upcoming-shows-widget) +[Upcoming Shows Shortcode](./Widgets.md#upcoming-shows-shortcode) +[Current Playlist Widget](./Widgets.md#current-playlist-widget) +[Current Playlist Shortcode](./Widgets.md#current-playlist-shortcode) +[Radio Clock Widget](./Widgets.md#radio-clock-widget) +[Radio Station Blocks](./Widgets.md#radio-station-blocks) +[[Pro] Extra Block Options](./Widgets.md#pro-extra-block-options) +[[Pro] Beaver Builder Modules](./Widgets.md#pro-beaver-builder-modules) +[[Pro] Elementor Widgets](./Widgets.md#pro-elementor-widgets) + You can add any of the Radio Station Plugin Widgets to your site's sidebar widget areas via the WordPress Appearance -> Widgets screen. -Note Widgets are displayed via their corresponding Shortcodes to prevent code duplication and maintain display consistency. (The selected Widget options are simply converted into Shortcode attributes.) This also means if you want to display a Widget within a custom Template, you can use the corresponding shortcode in your template file. eg. `do_shortcode('[current-show]');` +Note Widgets are displayed via their corresponding Shortcodes to prevent code duplication and maintain display consistency. (The selected Widget options are converted into Shortcode attributes.) This also means if you want to display a Widget within a custom Template, you can use the corresponding shortcode in your template file. eg. `do_shortcode('[current-show]');` [Radio Station Demo Site](http://demo.radiostation.pro) ## Radio Player Widget + Since 2.4.0, Radio Station includes a Streaming Player Widget! (see [Radio Player Widget](./Player.md#radio-player-widget)) ### Radio Player Shortcode + `[radio-player]` (see [Radio Player Shortcode](./Player.md#radio-player-shortcode)) #### [Pro] Sitewide Bar Player -[Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player. It isn't added via the Widgets Page, but is instead configured via the Plugin Settings Page under the Player tab. (see [Sitewide Bar Player](./Player.md#pro-sitewide-bar-player) ) +[Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player. It isn't added via the Widgets Page, but is instead configured via the Plugin Settings Page under the Player tab. (see [Sitewide Bar Player](./Player.md#pro-sitewide-bar-player) ) ## Current Show Widget + Displays the currently playing Show - if there is one scheduled to play right now. ### Current Show Shortcode + `[current-show]` (legacy supported name: `[dj-widget]`) -The following attributes are available for this shortcode: +The following attribute options are available for this shortcode: -* *title* : The title you would like to appear over the Current Show block. Default is 'Current Show'. -* *show_avatar* : Display a show's thumbnail. 0 or 1. Default is 1. +* **Load Display Attributes** +* *ajax* : Whether to AJAX load this widget/shortcode. 0 or 1. Default is Plugin Setting. +* *dynamic* : [Pro] Whether to dynamically reload on show changeover time. 0 or 1. Default 1. +* *title* : The title you would like to appear over the Current Show. Default is 'Current Show'. +* *no_shows* : The text displayed when no show is scheduled for the current time. Default empty. +* *hide_empty*: Hide the content of the widget/shortcode if there is no current show. 0 or 1. Default 0. +* **Show Display Attributes** +* *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. * *show_link* : Display a link to a show's page. 0 or 1 Default is 1. -* *display_hosts* : Display the names of the Hosts/DJs on the show. 0 or 1. Default is 0. -* *link_hosts* : Link Show hosts to their profile pages. 0 or 1. Default is 0. -* *default_name* : The text displayed when no show is scheduled for the current time. -* *time* : The time format used for displaying schedules. 12 or 24. Default is Plugin Setting. +* *show_avatar* : Display a show's thumbnail. 0 or 1. Default is 1. +* *avatar_width* : Set a width style in pixels for Show Avatars. Default is not to set. +* **Time Display Attributes** * *show_sched* : Display the Show's schedule. 0 or 1. Default is 1. -* *show_playlist* : Display a link to the show's current playlist, if any. 0 or 1. Default is 1. * *show_all_sched* : Displays all schedules for a show if it airs on multiple days. 0 or 1. Default is 0. +* *countdown* : Display a Countdown until the current Show ends. 0 or 1. Default is 0. +* *time_format* : The time format used for displaying schedules. 12 or 24. Default is Plugin Setting. +* **Extra Display Options** +* *display_hosts* : Display the names of the Hosts/DJs on the show. 0 or 1. Default is 0. +* *link_hosts* : Link Show hosts to their profile pages. 0 or 1. Default is 0. * *show_desc* : Display an excerpt of the show's description. 0 or 1. Default is 0. +* *show_playlist* : Display a link to the show's current playlist, if any. 0 or 1. Default is 1. * *show_encore* : Display encore presentation text (when set for Show). 0 or 1. Default is 1. -* *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. -* *avatar_width* : Set a width style in pixels for Show Avatars. Default is not to set. -* *countdown* : Display a Countdown until the current Show ends. 0 or 1. Default is 0. Example: `[current-show title="Now On-Air" show_avatar="1" show_link="1" show_sched="1"]` @@ -49,48 +75,68 @@ Example: `[current-show title="Now On-Air" show_avatar="1" show_link="1" show_sc ## Upcoming Shows Widget + Displays a limited list of Upcoming Shows - if there are Shows scheduled. ### Upcoming Shows Shortcode + `[upcoming-shows]` (legacy supported name: `[dj-coming-up-widget]`) -The following attributes are available for this shortcode: +The following attribute options are available for this shortcode: -* *title* : The title you would like to appear over the Upcoming Shows. +* **Load Display Attributes** * *limit* : The number of upcoming shows to display. 0 or 1. Default is 1. -* *show_avatar* : Display upcoming Show Avatars. 0 or 1. Default is 0. +* *ajax* : Whether to AJAX load this widget/shortcode. 0 or 1. Default is Plugin Setting. +* *dynamic* : [Pro] Whether to dynamically reload on show changeover time. 0 or 1. Default 1. +* *title* : The title you would like to appear over the Upcoming Shows. +* *no_shows* : The text displayed when no upcoming show is scheduled. Default empty. +* *hide_empty*: Hide the content of the widget/shortcode if there is no current show. 0 or 1. Default 0. +* **Show Display Attributes** * *show_link* : Link the Show title to the Show's page. 0 or 1. Default is 0. -* *display_hosts* : Display the names of the DJs on the show. 0 or 1. Default is 0. -* *link_hosts* : Link Show hosts to their profile pages. 0 or 1. Default is 0. -* *time* : The time format used for displaying schedules. 12 or 24. Default is global Plugin Setting. -* *show_sched* : Display the show's schedules. 0 or 1. Default is 1. -* *show_encore* : Display encore presentation text (when set for Show). 0 or 1. Default is 1. -* *default_name* : The text you would like to display when no upcoming show is scheduled. Default is none. * *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. +* *show_avatar* : Display upcoming Show Avatars. 0 or 1. Default is 0. +* *avatar_size* : The name of the (WordPress) size to use for the Show Avatar. Default * *avatar_width* : Set a width style in pixels for Show Avatars. Default is not to set. +* **Time Display Attributes** +* *show_sched* : Display the show's schedules. 0 or 1. Default is 1. * *countdown* : Display the Countdown until the next Show. 0 or 1. Default is 0. +* *time_format* : The time format used for displaying schedules. 12 or 24. Default is Plugin Setting. +* **Extra Display Attributes** +* *display_hosts* : Display the names of the DJs on the show. 0 or 1. Default is 0. +* *link_hosts* : Link Show hosts to their profile pages. 0 or 1. Default is 0. +* *show_encore* : Display encore presentation text (when set for Show). 0 or 1. Default is 1. -Example: `[upcoming-shows title="Coming Up On-Air" show_avatar="1" show_link="1" limit="3" time="12" schow_sched="1"]` +Example: `[upcoming-shows title="Coming Up On-Air" show_avatar="1" show_link="1" limit="3" time_format="12" schow_sched="1"]` [Demo Site Example Output](https://demo.radiostation.pro/extra-shortcodes/upcoming-shows-widget/) ## Current Playlist Widget + Displays the Playlist assigned to the currently playing Show - if there is one assigned to it. ### Current Playlist Shortcode + `[current-playlist]` (legacy supported name `[now-playing]`) -The following attributes are available for this shortcode: +The following attribute options are available for this shortcode: -* *title* : The title you would like to appear over the Playlist display block -* *link* : Link to the Playlist's page. 0 or 1. Default is 1. +* **Load Display Attributes** +* *ajax* : Whether to AJAX load this widget/shortcode. 0 or 1. Default is Plugin Setting. +* *dynamic* : [Pro] Whether to dynamically reload on show changeover time. 0 or 1. Default 1. +* *hide_empty*: Hide the content of the widget/shortcode if there is no current playlist. 0 or 1. Default 0. +* **Playlist Display Attributes** +* *title* : The title you would like to appear over the Playlist display. Default empty. +* *playlist_title* : Whether to display the name of the current playlist. 0 or 1. Default 0. +* *link* : Whether to link the playlist title to the playlist's page. 0 or 1. Default 1. +* *countdown* : Display the Playlist remaining time Countdown. 0 or 1. Default is 0. +* *no_shows* : The text displayed when there is no current playlist. Default empty. +* **Track Display Attributes** * *artist* : Display artist name. 0 or 1 Default is 1. * *song* : Display song name. 0 or 1. Default is 1. * *album* : Display album name. 0 or 1. Default is 0. * *label* : Display label name. 0 or 1. Default is 0. * *comments* : Display DJ comments. 0 or 1. Default is 0. -* *countdown* : Display the Playlist remaining time Countdown. 0 or 1. Default is 0. Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" label="1" comments="0"]` @@ -98,10 +144,44 @@ Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" l ## Radio Clock Widget + `[radio-clock]` (see [Radio Clock Shortcode](./Shortcodes.md#radio-clock-shortcode)) -As of 2.3.2, Radio Station now includes a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page) alongside the site visitor's current time (via browser detection.) +Radio Station includes a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page) alongside the site visitor's current time (via browser timezone detection.) #### [Pro] Timezone Switcher + [Radio Station Pro](https://radiostation.pro) includes a user Timezone Switcher in the which will allow your listener's to select a timezone and display adjusted Schedule and Show times. + +## Page Builder Widgets + +Page builder widgets have been introduced in Free 2.5.0 (Gutenberg Blocks) and Pro 2.6.0 (Elementor and Beaver Builder Modules.) All have the same settings as the standard widgets and support dynamic live previewing. (All output is rendered via the original shortcode functions, whether the settings are passed from shortcode attributes, widget options, block settings, or module settings.) + +### Radio Station Blocks + +Radio Blocks have been added to allow direct adding of any of the Radio Station widgets as Blocks in the WordPress Block Editor (aka Gutenberg.) You can find these by adding a new Block (pressing the blue + button.) All the Radio Blocks can be found grouped in the "Radio Station" category section (scroll down and the different categories will load.) + +Once you have added a Radio Station Block, click on the Block and then click the Gear Settings icon in the top right of the Block Editor. The settings for the block will appear in the panel on the right. Changing any of the settings will reload the block preview dynamically. + +#### Conditional Field Options + +It is worth noting that there are a few conditional fields in some of the page builder widgets (this is true regardless of the builder.) Most significantly, some options specific to a Schedule view will appear only if that view is selected. Additionally, some Archive view options will only appear for specific post types. + +### [Pro] Extra Block Options + +Some of the extra features available in Pro are also available in their relevent matching page builder widgets (these options will only display when the Pro plugin is activated): + +* The Radio Schedule Block has the extra Grid and Calendar Views, and multiview switching. +* The Radio Archive Block can display Episode, Host and Producer post type archives. +* The Radio Player Block supports extra theming and color options like the Sitewide Player Bar. +* The Current Show/Playlist and Upcoming Show Blocks support dynamic reloading at Show changeovers. + +### [Pro] Beaver Builder Modules + +Readio Station Beaver Builder Modules can be found in the Radio Station category section of the Modules tab (ensure "Standard Modules" is selected in the dropdown.) Hover any module on the page and click the Settings spanner icon to customize the module's settings as desired. Each module has color and typography options in the Style tab. Changing any of the settings will reload the module preview dynamically. + +### [Pro] Elementor Widgets + +Radio Station Elementor Widgets can be found in the Radio Station category section of the Elements Tab in Elementor. Drag a Radio Station widget element to your content area as normal. Click on any element to customize the Settings in the left pane as desired. Each element widget has color and typography options in the Style tab. Changing any of the settings will reload the element preview dynamically. + diff --git a/includes/blocks.php b/includes/blocks.php index b220b7c..daf60af 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -7,6 +7,7 @@ // @since 2.5.0 // === Block Editor Support === +// - Register Block Category // - Get Block Callbacks // - Get Block Attributes // - Register Blocks @@ -18,6 +19,32 @@ // === Block Editor Support === // ---------------------------- +// ----------------------- +// Register Block Category +// ----------------------- +add_action( 'plugins_loaded', 'radio_station_register_block_categories' ); +function radio_station_register_block_categories() { + global $wp_version; + if ( version_compare( $wp_version, '5.8', '>=' ) ) { + add_filter( 'block_categories_all', 'radio_station_register_block_category' ); + } else { + add_filter( 'block_categories', 'radio_station_register_block_category' ); + } +} +function radio_station_register_block_category( $categories ) { + $new_categories = array(); + foreach ( $categories as $category ) { + if ( 'embed' == $category['slug'] ) { + $new_categories[] = array( + 'slug' => 'radio-station', + 'title' => __( 'Radio Station', 'radio-station' ), + ); + } + $new_categories[] = $category; + } + return $new_categories; +} + // ------------------- // Get Block Callbacks // ------------------- @@ -242,7 +269,11 @@ function radio_station_register_blocks() { // --- loop block names to register blocks --- foreach ( $callbacks as $block_slug => $callback ) { $block_key = 'radio-station/' . $block_slug; - $args = array( 'render_callback' => $callback, 'attributes' => $attributes[$block_slug] ); + $args = array( + 'render_callback' => $callback, + 'attributes' => $attributes[$block_slug], + 'category' => 'radio-station', + ); $args = apply_filters( 'radio_station_block_args', $args, $block_slug, $callback ); register_block_type( $block_key, $args ); } @@ -253,8 +284,8 @@ function radio_station_register_blocks() { // --------------------------- // 2.5.0: added for editor block scripts/styles // reF: https://jasonyingling.me/enqueueing-scripts-and-styles-for-gutenberg-blocks/ -add_action( 'enqueue_block_editor_assets', 'raddio_station_block_editor_assets' ); -function raddio_station_block_editor_assets() { +add_action( 'enqueue_block_editor_assets', 'radio_station_block_editor_assets' ); +function radio_station_block_editor_assets() { // --- get block callabacks --- $callbacks = radio_station_get_block_callbacks(); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index a3e9d82..f117e22 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -18,6 +18,8 @@ // ------------------------- // Master Schedule Shortcode // ------------------------- +// 2.5.0: added optional radio-schedule shortcode alias +add_shortcode( 'radio-schedule', 'radio_station_master_schedule' ); add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 5373a30..e4b3354 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -221,7 +221,8 @@ function radio_station_show_language_metabox() { $slug = $term->slug; $term_slugs[] = $slug; $label = $term->name; - if ( !empty( $term->description ) ) { + // 2.5.0: only append description to label if different to name + if ( !empty( $term->description ) && ( $term->description != $label ) ) { $label .= ' (' . $term->description . ')'; } @@ -349,14 +350,16 @@ function radio_station_language_term_filter( $post_id ) { } // --- loop and set posted terms --- - // 2.5.0: use sanitize_text_field on posted value - $terms = sanitize_text_field( $_POST[RADIO_STATION_LANGUAGES_SLUG] ); + $terms = $_POST[RADIO_STATION_LANGUAGES_SLUG]; $term_ids = array(); if ( is_array( $terms ) && ( count( $terms ) > 0 ) ) { $languages = radio_station_get_languages(); foreach ( $terms as $i => $term_slug ) { + // 2.5.0: use sanitize_text_field on posted value + $term_slug = sanitize_text_field( $term_slug ); + foreach ( $languages as $j => $language ) { if ( strtolower( $language['language'] ) == strtolower( $term_slug ) ) { @@ -1093,8 +1096,8 @@ function radio_station_shift_edit_script() { $js .= "jQuery(document).ready(function() { jQuery('#publish').on('click', function() { window.removeEventListener('beforeunload', radio_onbeforeunload); - } - }"; + }); + });" . "\n"; // --- add new shift --- // 2.3.2: separate function for onclick diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 6f1a0db..cd20e77 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -371,15 +371,24 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { } // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { - $page = absint( get_query_var( 'page' ) ); - if ( $page > -1 ) { - $atts['offset'] = (int) $atts['perpage'] * $page; + // 2.5.0: fix to work by offset + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) ) { + if ( !isset( $atts['offset'] ) ) { + if ( isset( $_REQUEST['offset'] ) ) { + $atts['offset'] = absint( $_REQUEST['offset'] ); + } elseif ( get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > -1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } } } + + // --- process shortcode attributes --- $atts = shortcode_atts( $defaults, $atts, $post_type . '-archive' ); if ( RADIO_STATION_DEBUG ) { - echo 'Shortcode Atts: ' . esc_html( print_r( $atts, true ) ) . ''; + echo '' . esc_html( $type ) . ' Archive Shortcode Atts: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } // --- get published shows --- @@ -622,19 +631,30 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- manually apply offset and perpage limit --- // 2.3.3.9: added to enable pagination count - if ( $atts['offset'] && ( $atts['perpage'] > 0 ) ) { + // 2.5.0: fix by removing post count resets + if ( ( $atts['offset'] > 0 ) && ( $atts['perpage'] > 0 ) ) { if ( $post_count > $atts['offset'] ) { $offset_posts = array(); foreach ( $archive_posts as $i => $archive_post ) { - if ( ( $i > $atts['offset'] ) && ( count( $offset_posts ) < $atts['perpage'] ) ) { - $offset_posts[] = $archive_post; + if ( count( $offset_posts ) < $atts['perpage'] ) { + if ( ( $i + 1 ) > $atts['offset'] ) { + $offset_posts[] = $archive_post; + } } } $archive_posts = $offset_posts; - $post_count = count( $archive_posts ); + if ( RADIO_STATION_DEBUG ) { + echo 'Offset Archive Posts: ' . esc_html( print_r( $archive_posts ) ) . '' . "\n"; + } } else { $archive_posts = array(); - $post_count = 0; + } + } elseif ( ( $atts['perpage'] > 0 ) && ( $post_count > $atts['perpage'] ) ) { + // 2.5.0: fix to per page pagination + foreach ( $archive_posts as $i => $archive_post ) { + if ( ( $i + 1 ) > $atts['perpage'] ) { + unset( $archive_posts[$i] ); + } } } } @@ -642,7 +662,8 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- output list or no results message --- // 2.4.0.4: remove rs- prefix from element classes // 2.4.0.4: maybe add view class - $classes = array( $type . '-archives' ); + // 2.5.0: add generic radio-archives class + $classes = array( 'radio-archive', $type . '-archives' ); if ( $atts['view'] ) { $classes[] = $atts['view']; } @@ -680,7 +701,8 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $more = apply_filters( 'radio_station_archive_' . $type . '_list_excerpt_more', '[…]' ); // --- archive list --- - $list .= '
      ' . "\n"; + // 2.5.0: added generic radio-archive-list class + $list .= '
        ' . "\n"; // --- set info keys --- // (note: meta is dates for overrides, shifts for shows, tracks for playlists etc.) @@ -688,7 +710,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $infokeys = apply_filters( 'radio_station_archive_shortcode_info_order', $infokeys, $post_type, $atts ); // --- loop post archive --- - foreach ( $archive_posts as $archive_post ) { + foreach ( $archive_posts as $i => $archive_post ) { $info = array(); @@ -978,24 +1000,37 @@ function radio_station_genre_archive_list( $atts ) { ); // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { - $page = absint( get_query_var( 'page' ) ); - if ( $page > -1 ) { - $atts['offset'] = (int) $atts['perpage'] * $page; + // 2.5.0: fix to work by offset + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) ) { + if ( !isset( $atts['offset'] ) ) { + if ( isset( $_REQUEST['offset'] ) ) { + $atts['offset'] = absint( $_REQUEST['offset'] ); + } elseif ( get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > -1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } } } + + // --- process shortcode attributes --- $atts = shortcode_atts( $defaults, $atts, 'genre-archive' ); + if ( RADIO_STATION_DEBUG ) { + echo 'Genre Archive Shortcode Atts: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; + } // --- maybe get specified genre(s) --- - if ( !empty( $atts['genres'] ) ) { - $genres = explode( ',', $atts['genres'] ); - foreach ( $genres as $i => $genre ) { - $genre = trim( $genre ); + // 2.5.0: also gather genres ID array + $genres = $genre_ids = array(); + if ( '' != trim( $atts['genres'] ) ) { + $genre_slugs = explode( ',', $atts['genres'] ); + foreach ( $genre_slugs as $genre_slug ) { + $genre_slug = trim( $genre_slug ); $genre = radio_station_get_genre( $genre ); if ( $genre ) { - $genres[$i] = $genre; - } else { - unset( $genres[$i] ); + $genres[$genre['name']] = $genre; + $genre_ids[] = $genre['id']; } } } else { @@ -1005,209 +1040,297 @@ function radio_station_genre_archive_list( $atts ) { $args['hide_empty'] = false; } $genres = radio_station_get_genres( $args ); + foreach ( $genres as $genre ) { + $genre_ids[] = $genre['id']; + } } // --- check if we have genres --- - if ( !$genres || ( count( $genres ) == 0 ) ) { + // 2.5.0: improve logic to allow filtering + if ( 0 === count( $genres ) ) { + if ( $atts['hide_empty'] ) { - return ''; + $list = ''; } else { $list = '
        ' . "\n"; $list .= esc_html( __( 'No Genres were found to display.', 'radio-station' ) ) . "\n"; $list .= '
        ' . "\n"; - return $list; } + $list = apply_filters( 'radio_station_genre_archive_list', $list, $atts, $instance ); + return $list; + } - // 2.5.0: added id attribute with instance - $list = '
        ' . "\n"; + // --- get published shows --- + // TODO: also display Overrides in Genre archive list ? + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + // 'numberposts' => $atts['perpage'], + // 'offset' => $atts['offset'], + 'numberposts' => -1, + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'post_status' => $atts['status'], + ); - // --- loop genres --- - // 2.5.0: track all post count - $all_post_count = 0; - foreach ( $genres as $name => $genre ) { + if ( $atts['with_shifts'] ) { - // --- get published shows --- - // TODO: also display Overrides in Genre archive list ? - $args = array( - 'post_type' => RADIO_STATION_SHOW_SLUG, - // 'numberposts' => $atts['perpage'], - // 'offset' => $atts['offset'], - 'numberposts' => -1, - 'orderby' => $atts['orderby'], - 'order' => $atts['order'], - 'post_status' => $atts['status'], + // --- active shows with shifts --- + $args['meta_query'] = array( + 'relation' => 'AND', + array( + 'key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), ); - if ( $atts['with_shifts'] ) { + } else { - // --- active shows with shifts --- - $args['meta_query'] = array( - 'relation' => 'AND', - array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), - ); + // --- just active shows --- + $args['meta_query'] = array( + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ); + } - } else { + // --- set genre taxonomy query --- + // 2.5.0: get all shows with a genre term assigned + $args['tax_query'] = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'term_id', + 'terms' => $genre_ids, + // 'field' => 'slug', + // 'terms' => $genre['slug'], + ), + ); - // --- just active shows --- - $args['meta_query'] = array( - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), - ); + // --- get shows in genre --- + // 2.5.0: added shortcode atts as second filter argument + $args = apply_filters( 'radio_station_genre_archive_post_args', $args, $atts ); + $posts = get_posts( $args ); + $posts = apply_filters( 'radio_station_genre_archive_posts', $posts, $atts ); + $post_count = count( $posts ); + if ( RADIO_STATION_DEBUG ) { + echo 'Genre Archive Args: ' . esc_html( print_r( $args, true ) ) . "\n"; + echo 'Genre Archive Shows: ' . esc_html( print_r( $posts, true ) ) . '' . "\n"; + } + + // --- get genre terms for each post --- + $genre_posts = array(); + if ( $posts && is_array( $posts ) && ( $post_count > 0 ) ) { + foreach ( $posts as $i => $post ) { + $post_terms[$i] = wp_get_post_terms( $post->ID, RADIO_STATION_GENRES_SLUG ); + if ( $post_terms[$i] ) { + foreach ( $genres as $genre ) { + foreach ( $post_terms[$i] as $term ) { + if ( $genre['id'] == $term->term_id ) { + if ( isset( $genre_posts[$genre['id']] ) ) { + if ( !in_array( $post->ID, $genre_posts[$genre['id']] ) ) { + $genre_posts[$genre['id']][] = $post->ID; + } + } else { + $genre_posts[$genre['id']] = array( $post->ID ); + } + } + } + } + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Genre Posts: ' . esc_html( print_r( $genre_posts, true ) ) . '' . "\n"; } + } else { - // --- set genre taxonomy query --- - $args['tax_query'] = array( - array( - 'taxonomy' => RADIO_STATION_GENRES_SLUG, - 'field' => 'slug', - 'terms' => $genre['slug'], - ), - ); + // 2.5.0: added no shows with genres message + $list = '
        ' . "\n"; + $list .= esc_html( __( 'No Shows were found with Genres assigned.', 'radio-station' ) ) . "\n"; + $list .= '
        ' . "\n"; + $list = apply_filters( 'radio_station_genre_archive_list', $list, $atts, $instance ); + return $list; + + } + + // 2.5.0: added id attribute with instance + $list = '
        ' . "\n"; - // --- get shows in genre --- - // 2.5.0: added shortcode atts as second filter argument - $args = apply_filters( 'radio_station_genre_archive_post_args', $args, $atts ); - $posts = get_posts( $args ); - $posts = apply_filters( 'radio_station_genre_archive_posts', $posts, $atts ); + // --- loop genres --- + // 2.5.0: track post display count + $display_count = 1; + $start = ( isset( $atts['offset'] ) && ( $atts['offset'] > 0 ) ) ? ( $atts['offset'] ) : 0; + $end = ( $atts['perpage'] > -1 ) ? ( $start + $atts['perpage'] + 1 ) : 999999; + foreach ( $genres as $name => $genre ) { $list .= '
        ' . "\n"; - if ( $posts || ( count( $posts ) > 0 ) ) { - $has_posts = true; - // 2.5.0: add to all post count - $all_post_count = $all_post_count + count( $posts ); - } else { - $has_posts = false; - } - if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { + // 2.5.0: modified to check for shows in this genre + $heading = ''; + if ( !isset( $genre_posts[$genre['id']] ) ) { + + if ( !$atts['hide_empty'] ) { + + // --- genre image --- + $genre_image = apply_filters( 'radio_station_genre_image', false, $genre ); + if ( $genre_image ) { + $list .= '
        0 ) { + $list .= ' style="width: ' . esc_attr( absint( $atts['image_width'] ) ) . 'px"'; + } + $list .= '>' . "\n"; + // 2.5.0: added wp_kses on image output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $list .= wp_kses( $genre_image ); + $list .= '
        ' . "\n"; + } + + // --- genre title --- + $list .= '

        ' . "\n"; + if ( $atts['link_genres'] ) { + $list .= ''; + $list .= esc_html( $genre['name'] ) . "\n"; + $list .= '' . "\n"; + } else { + $list .= esc_html( $genre['name'] ) . "\n"; + } + $list .= '

        ' . "\n"; + + // --- no shows messages ---- + $list .= esc_html( __( 'No Shows in this Genre.', 'radio-station' ) ) . "\n"; + + } + + } else { // --- genre image --- $genre_image = apply_filters( 'radio_station_genre_image', false, $genre ); if ( $genre_image ) { - $width_style = ''; + $heading = '
        0 ) { - $width_style = ' style="width: ' . esc_attr( absint( $atts['image_width'] ) ) . 'px"'; + $heading .= ' style="width: ' . esc_attr( absint( $atts['image_width'] ) ) . 'px"'; } - $list .= '
        ' . "\n"; + $heading .= '>' . "\n"; // 2.5.0: added wp_kses on image output $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $genre_image ); - $list .= '
        ' . "\n"; + $heading .= wp_kses( $genre_image ); + $heading .= '
        ' . "\n"; } // --- genre title --- - $list .= '

        ' . "\n"; + $heading .= '

        ' . "\n"; if ( $atts['link_genres'] ) { - $list .= ''; - $list .= esc_html( $genre['name'] ) . "\n"; - $list .= '' . "\n"; + $heading .= ''; + $heading .= esc_html( $genre['name'] ) . "\n"; + $heading .= '' . "\n"; } else { - $list .= esc_html( $genre['name'] ) . "\n"; + $heading .= esc_html( $genre['name'] ) . "\n"; } - $list .= '

        ' . "\n"; - + $heading .= '

        ' . "\n"; + // --- genre description --- if ( $atts['genre_desc'] && !empty( $genre['genre_desc'] ) ) { - $list .= '
        ' . "\n"; + $heading .= '
        ' . "\n"; // 2.5.0: added wp_kses on description output $allowed = radio_station_allowed_html( 'content', 'description' ); - $list .= wp_kses( $genre['description'], $allowed ) . "\n"; - $list .= '
        ' . "\n"; - } - - } - - if ( !$has_posts ) { - - // --- no shows messages ---- - if ( !$atts['hide_empty'] ) { - $list .= esc_html( __( 'No Shows in this Genre.', 'radio-station' ) ) . "\n"; + $heading .= wp_kses( $genre['description'], $allowed ) . "\n"; + $heading .= '
        ' . "\n"; } - } else { - // --- filter excerpt length and more --- // 2.3.3.9: added for show description excerpts $length = apply_filters( 'radio_station_genre_archive_excerpt_length', false ); $more = apply_filters( 'radio_station_genre_archive_excerpt_more', '[…]' ); - // --- show archive list --- - $list .= '
          ' . "\n"; - + $items = array(); foreach ( $posts as $post ) { - $list .= '
        • ' . "\n"; - // --- show avatar or thumbnail fallback --- - $width_style = ''; - if ( absint( $atts['avatar_width'] ) > 0 ) { - $width_styles = ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; - } - $list .= '
          ' . "\n"; - $show_avatar = false; - if ( $atts['show_avatars'] ) { - $attr = array( 'class' => 'show-thumbnail-image' ); - $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); - } - if ( $show_avatar ) { - // 2.5.0: added wp_kses on show avatar output - $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $show_avatar, $allowed ). "\n"; - } elseif ( $atts['thumbnails'] ) { - if ( has_post_thumbnail( $post->ID ) ) { - $attr = array( 'class' => 'show-thumbnail-image' ); - $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); - // 2.5.0: added wp_kses on thumbnail outputting - $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $thumbnail, $allowed ) . "\n"; - } - } - $list .= '
          ' . "\n"; + if ( in_array( $post->ID, $genre_posts[$genre['id']] ) ) { - // --- show title ---- - $permalink = get_permalink( $post->ID ); - $list .= '' . "\n"; + // echo $name . ' >>' . $start . ' - ' . $display_count . ' - ' . $end . '<<
          ' . "\n"; + if ( ( $display_count > $start ) && ( $display_count < $end ) ) { - // --- show excerpt --- - // 2.2.3.9: display show description - if ( $atts['show_desc' ] ) { - if ( !empty( $post->post_excerpt ) ) { - $excerpt = $post->post_excerpt; - // 2.5.0: added missing esc_html on more anchor - $excerpt .= ' ' . esc_html( $more ) . ''; - } else { - $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); - } - $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); - - if ( '' != $excerpt ) { - $list .= '
          ' . "\n"; - // 2.5.0: use wp_kses on excerpt output - $allowed = radio_station_allowed_html( 'content', 'excerpt' ); - $list .= wp_kses( $excerpt, $allowed ) . "\n"; - $list .= '
          ' . "\n"; + $item = '
        • ' . "\n"; + + // --- show avatar or thumbnail fallback --- + $item .= '
          0 ) { + $item .= ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; + } + $item .= '>' . "\n"; + $show_avatar = false; + if ( $atts['show_avatars'] ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); + } + if ( $show_avatar ) { + // 2.5.0: added wp_kses on show avatar output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $item .= wp_kses( $show_avatar, $allowed ). "\n"; + } elseif ( $atts['thumbnails'] ) { + if ( has_post_thumbnail( $post->ID ) ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); + // 2.5.0: added wp_kses on thumbnail outputting + $allowed = radio_station_allowed_html( 'media', 'image' ); + $item .= wp_kses( $thumbnail, $allowed ) . "\n"; + } + } + $item .= '
          ' . "\n"; + + // --- show title ---- + $permalink = get_permalink( $post->ID ); + $item .= '' . "\n"; + + // --- show excerpt --- + // 2.2.3.9: display show description + if ( $atts['show_desc' ] ) { + if ( !empty( $post->post_excerpt ) ) { + $excerpt = $post->post_excerpt; + // 2.5.0: added missing esc_html on more anchor + $excerpt .= ' ' . esc_html( $more ) . ''; + } else { + $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); + } + $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); + + if ( '' != $excerpt ) { + $item .= '
          ' . "\n"; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $item .= wp_kses( $excerpt, $allowed ) . "\n"; + $item .= '
          ' . "\n"; + } + } + + $item .= '
        • ' . "\n"; + $items[] = $item; + } - } + + $display_count++; - $list .= '' . "\n"; + } + } + + // --- show archive list --- + if ( count( $items ) > 0 ) { + $list .= $heading; + $list .= '
            ' . "\n"; + $list .= implode( "\n", $items ); + $list .= '
          ' . "\n"; } - $list .= '
        ' . "\n"; } $list .= '
        ' . "\n"; } @@ -1251,6 +1374,7 @@ function radio_station_language_archive_list( $atts ) { $radio_station_data['instances']['language-archive-list']++; $instance = $radio_station_data['instances']['language-archive-list']; + // TODO: attribute in include/exclude main language term ? $defaults = array( // --- language display options --- 'languages' => '', @@ -1273,24 +1397,41 @@ function radio_station_language_archive_list( $atts ) { ); // --- handle possible pagination offset --- - if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { - $page = absint( get_query_var( 'page' ) ); - if ( $page > -1 ) { - $atts['offset'] = (int) $atts['perpage'] * $page; + // 2.5.0: fix to work by offset + if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) ) { + if ( !isset( $atts['offset'] ) ) { + if ( isset( $_REQUEST['offset'] ) ) { + $atts['offset'] = absint( $_REQUEST['offset'] ); + } elseif ( get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > -1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } } } + + // --- process shortcode attributes --- $atts = shortcode_atts( $defaults, $atts, 'language-archive' ); + if ( RADIO_STATION_DEBUG ) { + echo 'Language Archive Shortcode Atts: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; + } // --- maybe get specified language(s) --- - if ( !empty( $atts['languages'] ) ) { - $languages = explode( ',', $atts['languages'] ); - foreach ( $languages as $i => $language ) { - $language = trim( $language ); - $language = radio_station_get_language( $language ); + // 2.5.0: also gather language term IDs + $languages = $language_ids = array(); + if ( '' != trim( $atts['languages'] ) ) { + $language_slugs = explode( ',', $atts['languages'] ); + foreach ( $language_slugs as $language_slug ) { + $language_slug = trim( $language_slug ); + // 2.5.0: convert main language slug to get default + if ( 'main' == $language_slug ) { + $language_slug = false; + } + $language = radio_station_get_language( $language_slug ); if ( $language ) { - $languages[$i] = $language; - } else { - unset( $language[$i] ); + $languages[$language['name']] = $language; + $language_ids[] = $language['id']; } } } else { @@ -1300,193 +1441,327 @@ function radio_station_language_archive_list( $atts ) { $args['hide_empty'] = false; } $languages = radio_station_get_language_terms( $args ); + // 2.5.0: merge in main language + $main_language = radio_station_get_language(); + if ( !isset( $languages[$main_language['name']] ) ) { + $languages[$main_language['name']] = $main_language; + } + foreach ( $languages as $language ) { + $language_ids[] = $language['id']; + } + // echo 'Languages IDs: '; print_r( $language_ids, true ); } // --- check if we have languages --- - if ( !$languages || ( count( $languages ) == 0 ) ) { + // 2.5.0: modified to allow filtering + if ( 0 == count( $languages ) ) { if ( $atts['hide_empty'] ) { - return ''; + $list = ''; } else { $list = '
        ' . "\n"; $list .= esc_html( __( 'No Languages were found to display.', 'radio-station' ) ) . "\n"; $list .= '
        ' . "\n"; - return $list; } + $list = apply_filters( 'radio_station_language_archive_list', $list, $atts, $instance ); + return $list; } - // 2.5.0: added id attribute with instance - $list = '
        ' . "\n"; + // --- get published shows --- + // TODO: also display Overrides in archive list ? + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + // 'numberposts' => $atts['perpage'], + // 'offset' => $atts['offset'], + 'numberposts' => -1, + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'post_status' => $atts['status'], + ); - // --- loop languages --- - // 2.5.0: track all post count - $all_post_count = 0; - foreach ( $languages as $name => $language ) { + if ( $atts['with_shifts'] ) { - // --- get published shows --- - // TODO: also display Overrides in archive list ? - $args = array( - 'post_type' => RADIO_STATION_SHOW_SLUG, - // 'numberposts' => $atts['perpage'], - // 'offset' => $atts['offset'], - 'numberposts' => -1, - 'orderby' => $atts['orderby'], - 'order' => $atts['order'], - 'post_status' => $atts['status'], + // --- active shows with shifts --- + $args['meta_query'] = array( + 'relation' => 'AND', + array( + 'key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), ); - if ( $atts['with_shifts'] ) { + } else { - // --- active shows with shifts --- - $args['meta_query'] = array( - 'relation' => 'AND', - array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), - ); + // --- just active shows --- + $args['meta_query'] = array( + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ); + } - } else { + // --- set language taxonomy query --- + // 2.5.0: get all shows with a language term assigned + $args['tax_query'] = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'term_id', + 'terms' => $language_ids, + // 'field' => 'slug', + // 'terms' => $language['slug'], + ), + ); - // --- just active shows --- - $args['meta_query'] = array( - array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', - ), + // --- get shows in language --- + $args = apply_filters( 'radio_station_language_archive_post_args', $args, $atts ); + $posts = get_posts( $args ); + + $posts = apply_filters( 'radio_station_language_archive_posts', $posts, $atts ); + $post_count = count( $posts ); + if ( RADIO_STATION_DEBUG ) { + echo 'Language Archive Args: ' . esc_html( print_r( $args, true ) ) . "\n"; + echo 'Language Archive Shows: ' . esc_html( print_r( $posts, true ) ) . '' . "\n"; + } + + // --- get language terms for each post --- + $language_posts = array(); + if ( $posts && is_array( $posts ) && ( $post_count > 0 ) ) { + foreach ( $posts as $i => $post ) { + $post_terms[$i] = wp_get_post_terms( $post->ID, RADIO_STATION_LANGUAGES_SLUG ); + if ( $post_terms[$i] ) { + foreach ( $languages as $language ) { + foreach ( $post_terms[$i] as $term ) { + if ( $language['id'] == $term->term_id ) { + if ( isset( $language_posts[$language['id']] ) ) { + if ( !in_array( $post->ID, $language_posts[$language['id']] ) ) { + $language_posts[$language['id']][] = $post->ID; + } + } else { + $language_posts[$language['id']] = array( $post->ID ); + } + } + } + } + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Language Posts: ' . esc_html( print_r( $language_posts, true ) ) . '' . "\n"; + } + } + + // --- maybe merge in main language posts --- + // 2.5.0: added for when no specific language is specified + if ( '' == trim ( $atts['languages'] ) ) { + $main_language = radio_station_get_language(); + + $args['tax_query'][0]['operator'] = 'NOT IN'; + $main_posts = get_posts( $args ); + if ( RADIO_STATION_DEBUG ) { + echo 'Main Language: ' . esc_html( print_r( $main_language, true ) ) . '' . "\n"; + echo 'Shows with No Language Terms: ' . esc_html( print_r( $main_posts, true ) ) . '' . "\n"; + } + + if ( count( $main_posts ) > 0 ) { + + // --- loop to set language posts array --- + foreach ( $main_posts as $main_post ) { + if ( isset( $language_posts[$main_language['id']] ) ) { + if ( !in_array( $main_post->ID, $language_posts[$main_language['id']] ) ) { + $language_posts[$main_language['id']][] = $main_post->ID; + } + } else { + $language_posts[$main_language['id']] = array( $main_post->ID ); + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Combined Language Posts: ' . esc_html( print_r( $language_posts, true ) ) . '' . "\n"; + } + + // --- merge and refetch (to reorder) --- + $posts = array_merge( $posts, $main_posts ); + $post_ids = array(); + foreach ( $posts as $post ) { + $post_ids[] = $post->ID; + } + if ( RADIO_STATION_DEBUG ) { + echo 'Combined Shows: ' . esc_html( print_r( $posts, true ) ) . '' . "\n"; + echo 'Combined Show IDs: ' . esc_html( print_r( $post_ids, true ) ) . '' . "\n"; + } + + $args = array( + 'numberposts' => -1, + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'include' => $post_ids, ); + $posts = get_posts( $args ); + if ( RADIO_STATION_DEBUG ) { + echo 'Sorted Shows: ' . esc_html( print_r( $posts, true ) ) . '' . "\n"; + } } + } + + // 2.5.0: added no shows with languages message + $post_count = count( $posts ); + if ( !$posts || !is_array( $posts ) || ( 0 == $post_count ) ) { - // --- set language taxonomy query --- - $args['tax_query'] = array( - array( - 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, - 'field' => 'slug', - 'terms' => $language['slug'], - ), - ); + $list = '
        ' . "\n"; + $list .= esc_html( __( 'No Shows were found with Languages assigned.', 'radio-station' ) ) . "\n"; + $list .= '
        ' . "\n"; + $list = apply_filters( 'radio_station_language_archive_list', $list, $atts, $instance ); + return $list; + + } - // --- get shows in language --- - $args = apply_filters( 'radio_station_language_archive_post_args', $args, $atts ); - $posts = get_posts( $args ); - $posts = apply_filters( 'radio_station_language_archive_posts', $posts, $atts ); + // 2.5.0: added id attribute with instance + $list = '
        ' . "\n"; + // --- loop languages --- + // 2.5.0: track post display count + $display_count = 1; + $start = ( isset( $atts['offset'] ) && ( $atts['offset'] > 0 ) ) ? ( $atts['offset'] ) : 0; + $end = ( $atts['perpage'] > -1 ) ? ( $start + $atts['perpage'] + 1 ) : 999999; + foreach ( $languages as $name => $language ) { + $list .= '
        ' . "\n"; - if ( $posts || ( count( $posts ) > 0 ) ) { - $has_posts = true; - // 2.5.0: add to all post count - $all_post_count = $all_post_count + count( $posts ); - } else { - $has_posts = false; - } - if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { + $heading = ''; + if ( !isset( $language_posts[$language['id']] ) ) { + + if ( !$atts['hide_empty'] ) { + + // --- language title --- + $list .= '

        ' . "\n"; + if ( $atts['link_languages'] ) { + $list .= '' . "\n"; + $list .= esc_html( $language['name'] ) . "\n"; + $list .= '' . "\n"; + } else { + $list .= esc_html( $language['name'] ) . "\n"; + } + $list .= '

        ' . "\n"; + + $list .= esc_html( __( 'No Shows in this Language.', 'radio-station' ) ); + } + + } else { + // --- language title --- - $list .= '

        ' . "\n"; + $heading = '

        ' . "\n"; if ( $atts['link_languages'] ) { - $list .= '' . "\n"; - $list .= esc_html( $language['name'] ) . "\n"; - $list .= '' . "\n"; + $heading .= '' . "\n"; + $heading .= esc_html( $language['name'] ) . "\n"; + $heading .= '' . "\n"; } else { - $list .= esc_html( $language['name'] ) . "\n"; + $heading .= esc_html( $language['name'] ) . "\n"; } - $list .= '

        ' . "\n"; + $heading .= '

        ' . "\n"; // --- language description --- if ( $atts['language_desc'] && !empty( $language['language_desc'] ) ) { - $list .= '
        ' . "\n"; + $heading .= '
        ' . "\n"; // 2.5.0: use wp_kses on description output $allowed = radio_station_allowed_html( 'content', 'description' ); - $list .= wp_kses( $language['description'], $allowed ) . "\n"; - $list .= '
        ' . "\n"; + $heading .= wp_kses( $language['description'], $allowed ) . "\n"; + $heading .= '
        ' . "\n"; } - } + // --- filter excerpt length and more --- + $length = apply_filters( 'radio_station_language_archive_excerpt_length', false ); + $more = apply_filters( 'radio_station_language_archive_excerpt_more', '[…]' ); - if ( !$has_posts ) { + $items = array(); + foreach ( $posts as $post ) { - // --- no shows messages ---- - if ( !$atts['hide_empty'] ) { - $list .= esc_html( __( 'No Shows in this Language.', 'radio-station' ) ); - } + if ( in_array( $post->ID, $language_posts[$language['id']] ) ) { - } else { + // echo $name . ' >>' . $start . ' - ' . $display_count . ' - ' . $end . '<<
        ' . "\n"; + if ( ( $display_count > $start ) && ( $display_count < $end ) ) { - // --- filter excerpt length and more --- - $length = apply_filters( 'radio_station_language_archive_excerpt_length', false ); - $more = apply_filters( 'radio_station_language_archive_excerpt_more', '[…]' ); + $item = '
      • ' . "\n"; - // --- show archive list --- - $list .= '
          ' . "\n"; + // --- show avatar or thumbnail fallback --- + $item .= '
          0 ) { + $item .= ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; + } + $item .= '>' . "\n"; + $show_avatar = false; + if ( $atts['show_avatars'] ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); + } + if ( $show_avatar ) { + // 2.5.0: use wp_kses on show avatar output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $item .= wp_kses( $show_avatar, $allowed ) . "\n"; + } elseif ( $atts['thumbnails'] ) { + if ( has_post_thumbnail( $post->ID ) ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); + // 2.5.0: use wp_kses on thumbnail output + $allowed = radio_station_allowed_html( 'media', 'image' ); + $item .= wp_kses( $thumbnail, $allowed ) . "\n"; + } + } + $item .= '
          '; + + // --- show title ---- + $permalink = get_permalink( $post->ID ); + $item .= '' . "\n"; + + // --- show excerpt --- + if ( $atts['show_desc' ] ) { + if ( !empty( $post->post_excerpt ) ) { + $excerpt = $post->post_excerpt; + // 2.5.0: use esc_html on more anchor text + $excerpt .= ' ' . esc_html( $more ) . '' . "\n"; + } else { + $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); + } + // 2.5.0: fix to incorrect filter name radio_station_genre_archive_excerpt + $excerpt = apply_filters( 'radio_station_language_archive_excerpt', $excerpt, $post->ID ); + + if ( '' != $excerpt ) { + $item .= '
          '; + // 2.5.0: use wp_kses on excerpt output + $allowed = radio_station_allowed_html( 'content', 'excerpt' ); + $item .= wp_kses( $excerpt, $allowed ); + $item .= '
          '; + } + } - foreach ( $posts as $post ) { - $list .= '
        • ' . "\n"; + $item .= '
        • ' . "\n"; - // --- show avatar or thumbnail fallback --- - $width_style = ''; - if ( absint( $atts['avatar_width'] ) > 0 ) { - $width_styles = ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; - } - $list .= '
          ' . "\n"; - $show_avatar = false; - if ( $atts['show_avatars'] ) { - $attr = array( 'class' => 'show-thumbnail-image' ); - $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); - } - if ( $show_avatar ) { - // 2.5.0: use wp_kses on show avatar output - $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $show_avatar, $allowed ) . "\n"; - } elseif ( $atts['thumbnails'] ) { - if ( has_post_thumbnail( $post->ID ) ) { - $attr = array( 'class' => 'show-thumbnail-image' ); - $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); - // 2.5.0: use wp_kses on thumbnail output - $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $thumbnail, $allowed ) . "\n"; + $items[] = $item; } - } - $list .= '
          '; - - // --- show title ---- - $permalink = get_permalink( $post->ID ); - $list .= '' . "\n"; - // --- show excerpt --- - if ( $atts['show_desc' ] ) { - if ( !empty( $post->post_excerpt ) ) { - $excerpt = $post->post_excerpt; - // 2.5.0: use esc_html on more anchor text - $excerpt .= ' ' . esc_html( $more ) . '' . "\n"; - } else { - $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); - } - // 2.5.0: fix to incorrect filter name radio_station_genre_archive_excerpt - $excerpt = apply_filters( 'radio_station_language_archive_excerpt', $excerpt, $post->ID ); - - if ( '' != $excerpt ) { - $list .= '
          '; - // 2.5.0: use wp_kses on excerpt output - $allowed = radio_station_allowed_html( 'content', 'excerpt' ); - $list .= wp_kses( $excerpt, $allowed ); - $list .= '
          '; - } + $display_count++; + } + } - $list .= '' . "\n"; + if ( count( $items ) > 0 ) { + // --- show archive list --- + $list .= $heading; + $list .= '
            ' . "\n"; + $list .= implode( "\n", $items ); + $list .= '
          ' . "\n"; + } - $list .= '
        ' . "\n"; } $list .= '
      • ' . "\n"; } @@ -1516,40 +1791,53 @@ function radio_station_language_archive_list( $atts ) { // ------------------ function radio_station_archive_pagination( $instance, $type, $atts, $post_count ) { + // 2.5.0: fix to current page, default to 1 + // 2.5.0: use offset value instead of page global $post; $permalink = get_permalink( $post->ID ); $post_pages = ceil( $post_count / $atts['perpage'] ); - $current_page = ( $atts['offset'] > 0 ) ? ( $atts['offset'] / $atts['perpage'] ) : 0; + $current_page = ( isset( $atts['offset'] ) && ( $atts['offset'] > 0 ) ) ? (int) ( $atts['offset'] / $atts['perpage'] ) + 1 : 1; $prev_page = $current_page - 1; $next_page = $current_page + 1; - $pagi = '

        '; - $pagi .= '
        ' . "\n"; - $pagi .= '
        ' . "\n"; + $pagi = '
        ' . "\n"; + $pagi .= '
        ' . "\n"; if ( $prev_page > 0 ) { - $url = add_query_arg( 'page', $prev_page, $permalink ); - $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $prev_page ) . "');"; - $pagi .= '' . "\n"; + $pagi .= '
        ' . "\n"; + // $url = add_query_arg( 'page', $prev_page, $permalink ); + if ( $prev_page > 1 ) { + $url = add_query_arg( 'offset', ( $prev_page * $atts['perpage'] ), $permalink ); + } + $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $prev_page ) . "');"; + $pagi .= '' . "\n"; + $pagi .= '
        ' . "\n"; } - $pagi .= '
        ' . "\n"; for ( $page_num = 1; $page_num < ( $post_pages + 1 ); $page_num++ ) { $active = ( $current_page == $page_num ) ? ' active' : ''; $pagi .= '
        ' . "\n"; - $url = add_query_arg( 'page', $page_num, $permalink ); + // $url = add_query_arg( 'page', $page_num, $permalink ); + if ( $page_num > 1 ) { + $offset = ( ( $page_num - 1 ) * $atts['perpage'] ); + $url = add_query_arg( 'offset', $offset, $permalink ); + } $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $page_num ) . "');"; $pagi .= '' . "\n"; $pagi .= esc_html( $page_num ) . "\n"; $pagi .= '' . "\n"; $pagi .= '
        ' . "\n"; - } - $pagi .= '
        ' . "\n"; - if ( $next_page < ( $post_pages + 1 ) ) { - $url = add_query_arg( 'page', $next_page, $permalink ); + } + if ( $next_page < ( $post_pages + 1 ) ) { + $pagi .= '
        ' . "\n"; + // $url = add_query_arg( 'page', $next_page, $permalink ); + $offset = ( ( $next_page - 1 ) * $atts['perpage'] ); + $url = add_query_arg( 'offset', $offset, $permalink ); $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $next_page ) . "');"; $pagi .= '' . "\n"; - } - $pagi .= '
        ' . "\n"; + $pagi .= '
        ' . "\n"; + } + $pagi .= '
        ' . "\n"; + $pagi .= '
        ' . "\n"; return $pagi; } @@ -1854,12 +2142,12 @@ function radio_station_show_list_shortcode( $type, $atts ) { // --- list pagination --- // 2.3.3.9: fix to hide left arrow display on load if ( $atts['pagination'] && ( $post_pages > 1 ) ) { - $list .= '

        ' . "\n"; - $list .= '
        ' . "\n"; + $list .= '
        ' . "\n"; + $list .= '
        ' . "\n"; $list .= '' . "\n"; - for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { + for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum++ ) { if ( 1 == $pagenum ) { $active = ' active'; } else { @@ -2008,8 +2296,8 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.2: get default AJAX load settings // 2.5.0: unset empty AJAX attribute to use default $ajax = radio_station_get_setting( 'ajax_widgets', false ); - $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - if ( '' == $atts['ajax'] ) { + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + if ( isset( $atts['ajax'] ) && ( '' == $atts['ajax'] ) ) { unset( $atts['ajax'] ); } @@ -2201,13 +2489,15 @@ function radio_station_current_show_shortcode( $atts ) { $id = 'current-show-shortcode-' . $instance; $output .= '
        ' . "\n"; - // --- shortcode only title --- - if ( !empty( $atts['title'] ) ) { - // 2.3.3.9: fix class to not conflict with actual show title - $output .= '

        ' . "\n"; - $output .= esc_html( $atts['title'] ) . "\n"; - $output .= '

        ' . "\n"; - } + } + + // --- shortcode title --- + // 2.5.0: also display title for non-shortcodes if set + if ( ( '' != $atts['title'] ) && ( 0 != $atts['title'] ) ) { + // 2.3.3.9: fix class to not conflict with actual show title + $output .= '

        ' . "\n"; + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

        ' . "\n"; } // --- open current show list --- @@ -2732,7 +3022,7 @@ function radio_station_current_show() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-show' ); if ( RADIO_STATION_DEBUG ) { - echo "Current Show Shortcode Attributes: " . print_r( $atts, true ); + echo 'Current Show Shortcode Attributes: ' . print_r( $atts, true ) . '' . "\n"; } // --- output widget contents --- @@ -2802,7 +3092,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.5.0: unset empty AJAX attribute to use default $ajax = radio_station_get_setting( 'ajax_widgets', false ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - if ( '' == $atts['ajax'] ) { + if ( isset( $atts['ajax'] ) && ( '' == $atts['ajax'] ) ) { unset( $atts['ajax'] ); } @@ -2977,12 +3267,14 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $id = 'upcoming-shows-shortcode-' . $instance; $output .= '
        ' . "\n"; - // --- maybe output shortcode title --- - if ( !empty( $atts['title'] ) ) { - $output .= '

        ' . "\n"; - $output .= esc_html( $atts['title'] ) . "\n"; - $output .= '

        ' . "\n"; - } + } + + // --- shortcode title --- + // 2.5.0: also maybe output for non-shortcodes + if ( ( '' != $atts['title'] ) && ( 0 != $atts['title'] ) ) { + $output .= '

        ' . "\n"; + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

        ' . "\n"; } // --- open upcoming show list --- @@ -3392,7 +3684,7 @@ function radio_station_upcoming_shows() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'upcoming-shows' ); if ( RADIO_STATION_DEBUG ) { - echo "Upcoming Shows Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); + echo 'Upcoming Shows Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } // --- output widget contents --- @@ -3457,7 +3749,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.5.0: unset empty AJAX attribute to use default $ajax = radio_station_get_setting( 'ajax_widgets', false ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - if ( '' == $atts['ajax'] ) { + if ( isset( $atts['ajax'] ) && ( '' == $atts['ajax'] ) ) { unset( $atts['ajax'] ); } @@ -3589,14 +3881,16 @@ function radio_station_current_playlist_shortcode( $atts ) { $id = 'show-playlist-shortcode-' . $radio_station_data['widgets']['current-playlist']; $output .= '
        ' . "\n"; - // --- shortcode title --- - if ( !empty( $atts['title'] ) ) { - // 2.3.0: added title class for shortcode - $output .= '

        ' . "\n"; - // 2.5.0: use esc_html instead of esc_attr - $output .= esc_html( $atts['title'] ) . "\n"; - $output .= '

        ' . "\n"; - } + } + + // --- shortcode title --- + // 2.5.0: also maybe display for non-shortcodes + if ( ( '' == $atts['title'] ) && ( 0 != $atts['title'] ) ) { + // 2.3.0: added title class for shortcode + $output .= '

        ' . "\n"; + // 2.5.0: fixed to use esc_html instead of esc_attr + $output .= esc_html( $atts['title'] ) . "\n"; + $output .= '

        ' . "\n"; } // --- set empty HTML array --- @@ -3899,7 +4193,7 @@ function radio_station_current_playlist() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-playlist' ); if ( RADIO_STATION_DEBUG ) { - echo "Current Playlist Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); + echo 'Current Playlist Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } // --- output widget contents --- diff --git a/options.php b/options.php index 259667d..1bce75e 100644 --- a/options.php +++ b/options.php @@ -430,7 +430,7 @@ 'label' => __( 'Single Player at Once', 'radio-station' ), 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), + 'helper' => __( 'Stop any existing Player instances on the page or in other windows or tabs when a Player is started.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', 'pro' => false, @@ -1030,7 +1030,7 @@ 'options' => array( '' => __( 'Off', 'radio-station' ), 'yes' => __( 'List', 'radio-station' ), - // 'grid' => __( 'Grid', 'radio-station' ), + 'grid' => __( 'Grid', 'radio-station' ), ), 'value' => 'yes', 'default' => 'yes', @@ -1223,7 +1223,7 @@ 'label' => __( 'User Timezone Switching', 'radio-station' ), 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Allow visitors to select their Timezone manually for Show time translations.', 'radio-station' ), + 'helper' => __( 'Allow visitors to select their Timezone manually for Show time conversions.', 'radio-station' ), 'tab' => 'widgets', 'section' => 'loading', 'pro' => true, diff --git a/radio-station.php b/radio-station.php index 752ccd9..ec7382e 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.5 +Version: 2.4.9.6 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 79502c9..055c4c5 100644 --- a/readme.txt +++ b/readme.txt @@ -223,17 +223,21 @@ You can now visit your site to make sure nothing is broken. If you experience is * Updated: Freemius SDK (2.4.5) * Updated: Plugin Panel (1.2.7) * Updated: Moment JS (2.29.4) with WP Loading -* Changed: Tab Schedule default date display on * Improved: Refactored Schedule Engine Class (2.5.0) +* Improved: Redesigned higher resolution player buttons * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards * Improved: Sanitization using KSES * Improved: Translation Implementation * Improved: use WP JSON functions for data endpoints +* Improved: Schedule Templates to use Classes and Instances +* Improved: Tab Schedule default date display on * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection +* Fixed: Mobile detection (via any pointer type) +* Fixed: Adjacent Post Links (where show has one shift) = 2.4.0.9 = * Update: Sysend (1.11.1) for Radio Player From 6ada5f2fe7de91111599092492dbce271c5a482e Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 7 Nov 2022 22:08:09 +1000 Subject: [PATCH 278/377] dev updates --- CHANGELOG.md | 1 + blocks/editor.js | 14 +- css/rs-shortcodes.css | 15 + docs/Options.md | 6 +- docs/Player.md | 176 +--- docs/Shortcodes.md | 8 +- docs/Widgets.md | 15 - includes/post-types-admin.php | 314 +++--- includes/shortcodes.php | 24 +- loader.php | 11 +- options.php | 17 + player/css/radio-player.css | 39 +- player/images/volume-controls-dark.png | Bin 7013 -> 6992 bytes player/images/volume-controls-light.png | Bin 7032 -> 7011 bytes player/js/amplitude.js | 1265 ++++++++++++----------- player/js/amplitude.min.js | 2 +- player/js/howler.js | 170 ++- player/js/howler.min.js | 4 +- player/radio-player.php | 166 ++- readme.txt | 7 +- widgets/class-radio-player-widget.php | 58 +- 21 files changed, 1286 insertions(+), 1026 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb027f3..88c343c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection * Fixed: Mobile detection (via any pointer type) +* Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) ### 2.4.0.9 diff --git a/blocks/editor.js b/blocks/editor.js index 1a9ebfb..e799d97 100644 --- a/blocks/editor.js +++ b/blocks/editor.js @@ -1,11 +1,17 @@ /* === Radio Station Block Editor Scripts === */ +const el = window.wp.element.createElement; +const { Icon } = wp.components; + /* --- Add Block Category Icon --- */ -/* ( function() { - const AntennaSVG = ''; - wp.blocks.updateCategory( 'radio-station-blocks', { icon: AntennaSVG } ); -} )(); */ +( function() { + const antennaSVG = () => ( + el( Icon, {icon: ''} + ) + ); + wp.blocks.updateCategory( 'radio-station-blocks', { icon: antennaSVG } ); +} )(); /* --- Subscribe to Block State --- */ /* ref: https://wordpress.stackexchange.com/a/358256/76440 */ diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index 09a0989..bdeac68 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -129,6 +129,21 @@ font-size: 0.8em; } +.show-archives.grid .show-archive-item, .override-archives.grid .override-archive-item, +.genres-archive.grid .show-archive-item, .languages-archive.grid .show-archive-item, +.playlist-archives.grid .playlist-archive-item { + display: inline-block; + vertical-align: top; + text-align: center; + width: 220px; +} + +.show-archives.grid .show-archive-item-thumbnail, .override-archives.grid .override-archive-item-thumbnail, +.genres-archive.grid .show-archive-item-thumbnail, .languages-archive.grid .show-archive-item-thumbnail, +.playlist-archives.grid .playlist-archive-item-thumbnail { + float: none; +} + /* Show Subarchive Shortcodes */ /* -------------------------- */ .show-post, .show-playlist, .show-host, .show-producer, .show-episode { diff --git a/docs/Options.md b/docs/Options.md index d7f0de0..8379ed2 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -192,10 +192,14 @@ Background color for the fixed position Sitewide Bar Player. Default: on. Key: player_bar_currentshow Display the Current Show in the Player Bar. -#### [Pro] Bar Player Now Player +#### [Pro] Bar Player Now Playing Default: on. Key: player_bar_nowplaying Display the Now Playing Track metadata in the Player Bar. +#### [Pro] Bar Player Track Animation +Default: backandforth. Key: player_bar_track_animation +How to animate the currently playing track display. + #### [Pro] Bar Player Metadata Source Default: none (use Stream URL.) Key: player_bar_metadata Alternative metadata source URL for Now Playing Track metadata. diff --git a/docs/Player.md b/docs/Player.md index a49cc46..6ab61a3 100644 --- a/docs/Player.md +++ b/docs/Player.md @@ -5,135 +5,56 @@ ## Radio Player -The Radio Player is available as a Shortcode, Widget or Block. In Pro it is also available as a Sitewide Bar Player, and as an Elementor Widget and Beaver Builder Module. +The Radio Player is available as a Shortcode, Widget or Block. In Pro it is also available as a Sitewide Bar Player, as well as an Elementor Widget and Beaver Builder Module. ### Default Player Settings Default settings for the Player can be set on the Plugin Settings page on the Player tab. These will be used in widgets wherever the widget options are set to "Default". This saves you from setting them twice, but also means that you can override these defaults in individual widgets as needed. (see [Options](./Options.md#player) for a list of these options.) -### Radio Player Widget +* Player Title: Display your Radio Station Title in Player by default. +* Player Image: Display your Radio Station Image in Player by default. +* Script: Default audio script to use for Radio Streaming Player. Ampliture, Howler or Jplayer. +* Fallback Scripts: Fallback audio scripts to try if/when the default Player script fails. +* Theme: Default Player Controls theme style. Light or dark to match your theme. +* Buttons: Default Player Buttons shape style. Circular, rounded or square. +* Volume Controls: Which volume controls to display in the Player by default. +* Player Debug Mode: Output player debug information in browser javascript console. +* Start Volume: Initial volume for when the Player starts playback. +* Single Player: Stop other Player instances when a Player is started. -A Player widget instance can be added via the WordPress Admin Appearance -> Widgets page. +### Radio Player Widget -The widget options correspond to the shortcode attributes below, allowing you control over the widget output. +A Player widget instance can be added to any widget area via the WordPress Admin Appearance -> Widgets page. The widget options correspond to the shortcode attributes below, allowing you control over the widget display output. ### Radio Player Block -A Player block can be added via the Block Editor by clicking the blue + icon. The `[Radio Station] Stream Player` Block can be found in the Media category. +A Player block can be added via the Block Editor by clicking the blue + icon. The `[Radio Station] Stream Player` Block can be found in the Radio Station block category (above the Embed category.) #### [Pro] Sitewide Bar Player -[Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player. It isn't added via the Widgets Page, but is instead configured via the Plugin Settings page under the Player tab. It has the following options: - -* Display fixed in the header or footer area (top or bottom of page, unaffected by scrolling.) -* ... -* Popup Player Button : adds a button to open the player in a separate window. - - - - -// --- [Pro/Player] Playing Highlight Color --- - 'player_playing_color' => array( - 'type' => 'color', - 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), - 'default' => '#70E070', - 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Control Icons Highlight Color --- - 'player_buttons_color' => array( - 'type' => 'color', - 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), - 'default' => '#00A0E0', - 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Volume Knob Color --- - 'player_thumb_color' => array( - 'type' => 'color', - 'label' => __( 'Volume Knob Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Volume Track Color --- - 'player_range_color' => array( - 'type' => 'coloralpha', - 'label' => __( 'Volume Track Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // === Advanced Stream Player === - - // --- [Player] Player Volume --- - 'player_volume' => array( - 'type' => 'number', - 'label' => __( 'Player Start Volume', 'radio-station' ), - 'default' => 77, - 'min' => 0, - 'step' => 1, - 'max' => 100, - 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- [Player] Single Player --- - 'player_single' => array( - 'type' => 'checkbox', - 'label' => __( 'Single Player at Once', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- [Pro/Player] Player Autoresume --- - 'player_autoresume' => array( - 'type' => 'checkbox', - 'label' => __( 'Autoresume Playback', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => true, - ), - - - Bar Player Text Color - Text color for the fixed position Sitewide Bar Player. - - Bar Player Background Color - Background color for the fixed position Sitewide Bar Player. - - Current Show Display - Display the Current Show in the Player Bar. - - Now Playing Metadata - Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist - - Metadata URL - Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location. +[Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player, including continuous uninterruptable playback via the integrated Teleporter page transition plugin. The Player Bar isn't added via the Widgets page, but is instead configured via the Plugin Settings page under the Player tab. It has the following main options: +* Bar Position: Fixed in the header or footer area (top or bottom of page, unaffected by scrolling.) +* Bar Height: Set the absolute height of the Player Bar in pixels. +* Fade In Player Bar: How long to take to fade in Player Bar after page load. +* Continous Playback: Enables uninterrupt playback while navigating between pages. +* Player Page Fade: How long to take to fade between pages when continous playback is enabled. +* Text Color and Background Color: Bar Player Default text and background colors. +* Autoresume Playback: attempts to autoresume playback for returning visitors. +* Popup Player Button: adds a button to open the player in a separate window. +* Current Show Display: whether to display the Current Show in the Player Bar. +* Now Playing Display: whether to display current track information via stream metadata. +* Metadata URL: alternative URL for metadata retrieval (normally via stream URL.) +#### [Pro] Extra Color Options +The Player Bar also includes some extra color options to match the look and feel of your site (these are also used as defaults for other Player instances.) + +* Playing Icon Highlight Color: Play button highlight color when playing stream. +* Control Icons Highlight Color: Volume Button Hover highlight and player loading highlight. +* Volume Track and Knob Colors: To style the volume slider track and it's thumb knob. + +And in addition to the existing Light and Dark button themes, you can also choose from colored button themes of: Red, Orange, Yellow, Light Green, Green, Cyan, Light Blue, Blue, Purple or Magenta. Matching Play/Pause and Volume/Track control images will be used when you activate these theme options. ### Radio Player Shortcode @@ -141,7 +62,7 @@ A Player block can be added via the Block Editor by clicking the blue + icon. Th The following attributes are available for this shortcode: -* *url* : Stream or file URL. Default: plugin setting. +* *url* : Stream or file URL. Default: Plugin setting. * *script* : Default audio script. 'amplitude', 'jplayer', or 'howler'. Default: amplitude * *layout* : Player section layout. 'horizontal', 'vertical'. Default: 'vertical' * *theme* : Player Buttom theme. 'light' or 'dark'. Default: 'light'. @@ -149,6 +70,8 @@ The following attributes are available for this shortcode: * *title* : Player/Station Title. String. 0 for none * *image* : Player/Station Image. URL (recommended size 256x256). Default none. * *volume* : Initial Player Volume. 0-100. Default: 77 +* *volumes* : Volume Control Buttons, comma separated. (slider,updown,mute,max) Default: all. +* *default* : Use this as the default Player on the page. 0 or 1. Default 0. [Demo Site Example Output](https://demo.radiostation.pro/player-shortcode/) @@ -156,16 +79,19 @@ The following attributes are available for this shortcode: Remember, if you want to display a Shortcode within a custom Template (or elsewhere in custom code), you can use the WordPress `do_shortcode` function. eg. `do_shortcode('[radio-player]');` -#### [Pro] Extra Shortcode Options - -* **Color Options** -* `text_color` : . Default none (inherit.) -* `background_color` : . Default none (inherit.) -* `playing_color` : . Default Plugin Setting. -* `buttons_color` : . Default Plugin Setting. -* `track_color` : . Default Plugin Setting. -* `thumb_color` : . Default Plugin Setting. -* `popup` : Add button to open Popup Player in separate window. 0 or 1. Default 0. - +#### [Pro] Extra Widget and Shortcode Options +All additional options available in Pro are also available for individual Player widgets and shortcodes (including Gutenberg Block, Elementor Widget and Beaver Builder Module.) +* **Color Options** +* *text_color* : Player Text Color. Defaults to none (inherit.) +* *background_color* : Player Background Color. Defaults to none (inherit.) +* *playing_color* : Playing Highlight Color. Defaults to Plugin Setting. +* *buttons_color* : Buttons Highlight Color. Defaults to Plugin Setting. +* *track_color* : Volume Track Color. Defaults to Plugin Setting. +* *thumb_color* : Volume Thumb Color. Defaults to Plugin Setting. +* **Extra Options** +* *popup* : Add button to open Popup Player in separate window. 0 or 1. Defaults to Plugin Setting. +* *currentshow* : Display current Show information in player section. 0 or 1. Defaults to Plugin Setting. +* *nowplaying* : Display currently playing track via stream metadata/playlist. 0 or 1. Defaults to Plugin Setting. +* *animation* : Track animation to use. none, lefttoright, righttoleft, backandforth. Default: backandforth diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 7105179..2e9ae87 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -188,6 +188,7 @@ The following attributes are available for this shortcode: * *link_genres* : Link Genre titles to term pages. 0 or 1. Default 1. * *genre_desc' : Display Genre term description. 0 or 1. Default 1. * *genre_images' : [Pro] Display Genre images. 0 or 1. Default 1. +* *view* : Layout display view. 'list' or 'grid'. Default 'grid'. * *image_width' : [Pro] Set a width style in pixels for Genre images. Default is 100. * *hide_empty' : No output if no records to display for Genre. 0 or 1. Default 1. * *status* : Query for Show status. Default 'publish'. @@ -212,6 +213,7 @@ The following attributes are available for this shortcode: * *languages* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) * *link_languages* : Link Genre titles to term pages. 0 or 1. Default 1. * *language_desc' : Display Genre term description. 0 or 1. Default 1. +* *view* : Layout display view. 'list' or 'grid'. Default 'grid'. * *hide_empty' : No output if no records to display for Genre. 0 or 1. Default 1. * *status* : Query for Show status. Default 'publish'. * *perpage* : Query for number of Shows. Default -1 (all) @@ -264,11 +266,11 @@ The following attributes are available for this shortcode: `[team-archive]` -The Team Archive Shortcode +The following attributes are available for this shortcode: * *view* : Layout display view. 'list' or 'grid'. Default list. - +... [Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/team-archive/) @@ -303,7 +305,7 @@ The following attributes are available for this shortcode: `[show-episodes-archive]` (or `[show-episode-archive]`) -This shortcode will be available in [Radio Station Pro](https://radiostation.pro) +This shortcode is available in [Radio Station Pro](https://radiostation.pro) ## Widget Shortcodes diff --git a/docs/Widgets.md b/docs/Widgets.md index 84e8e78..a8ddea5 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -2,21 +2,6 @@ *** -[Radio Player Widget](./Widgets.md#radio-player-widget) -[Radio Player Shortcode](./Widgets.md#radio-player-shortcode) -[[Pro] Sitewide Bar Player](./Widgets.md#pro-sitewide-bar-player) -[Current Show Widget](./Widgets.md#current-show-widget) -[Current Show Shortcode](./Widgets.md#current-show-shortcode) -[Upcoming Shows Widget](./Widgets.md#upcoming-shows-widget) -[Upcoming Shows Shortcode](./Widgets.md#upcoming-shows-shortcode) -[Current Playlist Widget](./Widgets.md#current-playlist-widget) -[Current Playlist Shortcode](./Widgets.md#current-playlist-shortcode) -[Radio Clock Widget](./Widgets.md#radio-clock-widget) -[Radio Station Blocks](./Widgets.md#radio-station-blocks) -[[Pro] Extra Block Options](./Widgets.md#pro-extra-block-options) -[[Pro] Beaver Builder Modules](./Widgets.md#pro-beaver-builder-modules) -[[Pro] Elementor Widgets](./Widgets.md#pro-elementor-widgets) - You can add any of the Radio Station Plugin Widgets to your site's sidebar widget areas via the WordPress Appearance -> Widgets screen. Note Widgets are displayed via their corresponding Shortcodes to prevent code duplication and maintain display consistency. (The selected Widget options are converted into Shortcode attributes.) This also means if you want to display a Widget within a custom Template, you can use the corresponding shortcode in your template file. eg. `do_shortcode('[current-show]');` diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index e4b3354..168b654 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1480,165 +1480,165 @@ function radio_station_show_shifts_table( $post_id ) { $list .= '
          ' . "\n"; - // --- shift day selection --- - $list .= '
        • ' . "\n"; - $list .= ': ' . "\n"; - $class = ( '' == $shift['day'] ) ? 'incomplete' : ''; - $list .= '' . "\n"; + // 2.3.0: simplify by looping days + foreach ( $days as $day ) { + // 2.3.0: add weekday translation to display + $list .= '' . "\n"; + } + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '
        • ' . "\n"; + + // --- shift start time --- + $list .= '
        • ' . "\n"; + $list .= ': ' . "\n"; + + // --- start hour selection --- + $class = ( '' == $shift['start_hour'] ) ? 'incomplete' : ''; + $list .= '' . "\n"; - $list .= '' . "\n"; - $list .= '
        • ' . "\n"; - - // --- shift start time --- - $list .= '
        • ' . "\n"; - $list .= ': ' . "\n"; - - // --- start hour selection --- - $class = ( '' == $shift['start_hour'] ) ? 'incomplete' : ''; - $list .= '' . "\n"; - $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '' . "\n"; + + // --- start minute selection --- + $list .= '' . "\n"; + $list .= '' . "\n"; + + // --- start meridiem selection --- + $class = ( '' == $shift['start_meridian'] ) ? 'incomplete' : ''; + $list .= '' . "\n"; + $list .= '' . "\n"; + + $list .= '
        • ' . "\n"; + + // --- shift end time --- + $list .= '
        • ' . "\n"; + $list .= ': ' . "\n"; + + // --- end hour selection --- + $class = ( '' == $shift['end_hour'] ) ? 'incomplete' : ''; + $list .= '' . "\n"; + $list .= '' . "\n"; - // --- start minute selection --- - $list .= '' . "\n"; + $list .= '' . "\n"; $list .= '' . "\n"; $list .= '' . "\n"; $list .= '' . "\n"; $list .= '' . "\n"; // 2.5.0: use (possibly translated) minutes foreach ( $mins as $min => $label ) { - $class = ( $min == $shift['start_min'] ) ? 'original' : ''; - // 2.5.0: added number format internationalization - $list .= '' . "\n"; + $class = ( $min == $shift['end_min'] ) ? 'original' : ''; + $list .= '' . "\n"; } $list .= '' . "\n"; - $list .= '' . "\n"; - - // --- start meridiem selection --- - $class = ( '' == $shift['start_meridian'] ) ? 'incomplete' : ''; - $list .= '' . "\n"; - $list .= '' . "\n"; - - $list .= '
        • ' . "\n"; - - // --- shift end time --- - $list .= '
        • ' . "\n"; - $list .= ': ' . "\n"; - - // --- end hour selection --- - $class = ( '' == $shift['end_hour'] ) ? 'incomplete' : ''; - $list .= '' . "\n"; - $list .= '' . "\n"; - - // --- end minute selection --- - $list .= '' . "\n"; + + // --- end meridiem selection --- + $class = ( '' == $shift['end_meridian'] ) ? 'incomplete' : ''; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '
        • ' . "\n"; + + // --- encore presentation --- + if ( !isset( $shift['encore'] ) ) { + $shift['encore'] = ''; } - $list .= '' . "\n"; - $list .= '' . "\n"; - - // --- end meridiem selection --- - $class = ( '' == $shift['end_meridian'] ) ? 'incomplete' : ''; - $list .= '' . "\n"; - $list .= '' . "\n"; - $list .= '' . "\n"; - - // --- encore presentation --- - if ( !isset( $shift['encore'] ) ) { - $shift['encore'] = ''; - } - $list .= '
        • ' . "\n"; - $list .= '' . "\n"; - $list .= ' ' . "\n"; - $list .= '' . "\n"; - $list .= '
        • ' . "\n"; - - // --- shift disabled --- - // 2.3.0: added disabled checkbox to shift row - if ( !isset( $shift['disabled'] ) ) { - $shift['disabled'] = ''; - } - $list .= '
        • ' . "\n"; - $list .= '' . "\n"; - $list .= ' ' . "\n"; - $list .= '' . "\n"; - $list .= '
        • ' . "\n"; - - // --- duplicate shift icon --- - // 2.3.0: added duplicate shift icon - $list .= '
        • ' . "\n"; - $title = __( 'Duplicate Shift', 'radio-station' ) . "\n"; - $list .= '' . "\n"; - $list .= '
        • ' . "\n"; - - // --- remove shift icon --- - // 2.3.0: change remove button to icon - $list .= '
        • ' . "\n"; - $title = __( 'Remove Shift', 'radio-station' ) . "\n"; - $list .= '' . "\n"; - $list .= '
        • ' . "\n"; - - $list .= '
        ' . "\n"; - - // --- output any shift conflicts found --- - if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { - - $list .= '
        ' . "\n"; - $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': ' . "\n"; - foreach ( $conflicts as $j => $conflict ) { - if ( $j > 0 ) { - $list .= ', '; + $list .= '
      • ' . "\n"; + $list .= '' . "\n"; + $list .= ' ' . "\n"; + $list .= '' . "\n"; + $list .= '
      • ' . "\n"; + + // --- shift disabled --- + // 2.3.0: added disabled checkbox to shift row + if ( !isset( $shift['disabled'] ) ) { + $shift['disabled'] = ''; } - if ( $conflict['show'] == $post_id ) { - $list .= '' . esc_html( __('This Show', 'radio-station' ) ) . '' . "\n"; - } else { - $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); - $show_title = get_the_title( $conflict['show'] ); - $list .= '' . esc_html( $show_title ) . '' . "\n"; + $list .= '
      • ' . "\n"; + $list .= '' . "\n"; + $list .= ' ' . "\n"; + $list .= '' . "\n"; + $list .= '
      • ' . "\n"; + + // --- duplicate shift icon --- + // 2.3.0: added duplicate shift icon + $list .= '
      • ' . "\n"; + $title = __( 'Duplicate Shift', 'radio-station' ) . "\n"; + $list .= '' . "\n"; + $list .= '
      • ' . "\n"; + + // --- remove shift icon --- + // 2.3.0: change remove button to icon + $list .= '
      • ' . "\n"; + $title = __( 'Remove Shift', 'radio-station' ) . "\n"; + $list .= '' . "\n"; + $list .= '
      • ' . "\n"; + + $list .= '
      ' . "\n"; + + // --- output any shift conflicts found --- + if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { + + $list .= '
      ' . "\n"; + $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': ' . "\n"; + foreach ( $conflicts as $j => $conflict ) { + if ( $j > 0 ) { + $list .= ', '; + } + if ( $conflict['show'] == $post_id ) { + $list .= '' . esc_html( __('This Show', 'radio-station' ) ) . '' . "\n"; + } else { + $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); + $show_title = get_the_title( $conflict['show'] ); + $list .= '' . esc_html( $show_title ) . '' . "\n"; + } + $conflict_start = esc_html( $conflict['shift']['start_hour'] ) . ':' . esc_html( $conflict['shift']['start_min'] ) . ' ' . esc_html( $conflict['shift']['start_meridian'] ); + $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); + $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; } - $conflict_start = esc_html( $conflict['shift']['start_hour'] ) . ':' . esc_html( $conflict['shift']['start_min'] ) . ' ' . esc_html( $conflict['shift']['start_meridian'] ); - $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); - $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; + $list .= '

      ' . "\n"; } - $list .= '

    ' . "\n"; - } // --- close shift wrapper --- $list .= '
    ' . "\n"; @@ -3511,7 +3511,7 @@ function radio_station_schedule_override_metabox() { // --- override list table --- echo '
    ' . "\n"; $table = radio_station_overrides_table( $post->ID ); - if ( '' != $table['list'] ) { + if ( '' != $table['html'] ) { // 2.5.0: use wp_kses on table output $allowed = radio_station_allowed_html( 'content', 'settings' ); echo wp_kses( $table['list'], $allowed ); @@ -3904,7 +3904,7 @@ function radio_station_overrides_table( $post_id ) { // --- set table output to return --- $table = array( - 'list' => $list, + 'html' => $list, // 'conflicts' => $conflicts, ); @@ -4630,18 +4630,18 @@ function radio_station_override_save_data( $post_id ) { if ( $sched_changed ) { // --- output new show shifts list --- - echo '
    '; - $table = radio_station_overrides_table( $post_id ); - // if ( $table['conflicts'] ) { - // radio_station_overrides_conflict_message(); - // } + echo '
    ' . "\n"; - // 2.5.0: use wp_kses on table output - $allowed = radio_station_allowed_html( 'content', 'settings' ); - echo wp_kses( $table['list'], $allowed ); + $table = radio_station_overrides_table( $post_id ); + // if ( $table['conflicts'] ) { + // radio_station_overrides_conflict_message(); + // } + // 2.5.0: use wp_kses on table output + $allowed = radio_station_allowed_html( 'content', 'settings' ); + echo wp_kses( $table['html'], $allowed ); + // echo '
    '; - // echo '
    '; - echo '
    '; + echo '
    ' . "\n"; // --- refresh show shifts list --- $js = "if (!parent.document.getElementById('overrides-list')) {console.log('Error. Could not find override list!');}" . "\n"; diff --git a/includes/shortcodes.php b/includes/shortcodes.php index cd20e77..3d3af1c 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -663,10 +663,9 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // 2.4.0.4: remove rs- prefix from element classes // 2.4.0.4: maybe add view class // 2.5.0: add generic radio-archives class + // 2.5.0: always add view class $classes = array( 'radio-archive', $type . '-archives' ); - if ( $atts['view'] ) { - $classes[] = $atts['view']; - } + $classes[] = $atts['view']; $class_list = implode( ' ', $classes ); // 2.5.0: add element ID for archive shortcode instance $list = '
    '; @@ -976,12 +975,14 @@ function radio_station_genre_archive_list( $atts ) { $instance = $radio_station_data['instances']['genre-archive-list']; // 2.3.3.9: default show description display to on + // 2.5.0: added view attribute for grid view $defaults = array( // --- genre display options --- 'genres' => '', 'link_genres' => 1, 'genre_desc' => 1, 'genre_images' => 1, + 'view' => 'list', 'image_width' => 150, 'hide_empty' => 1, 'pagination' => 1, @@ -999,6 +1000,11 @@ function radio_station_genre_archive_list( $atts ) { 'show_desc' => 1, ); + // 2.5.0: change show description default for grid view + if ( isset( $atts['view'] ) && ( 'grid' == $atts['view'] ) ) { + $defaults['show_desc'] = 0; + } + // --- handle possible pagination offset --- // 2.5.0: fix to work by offset if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) ) { @@ -1160,7 +1166,8 @@ function radio_station_genre_archive_list( $atts ) { } // 2.5.0: added id attribute with instance - $list = '
    ' . "\n"; + // 2.5.0: add view attribute to class list + $list = '
    ' . "\n"; // --- loop genres --- // 2.5.0: track post display count @@ -1380,6 +1387,7 @@ function radio_station_language_archive_list( $atts ) { 'languages' => '', 'link_languages' => 1, 'language_desc' => 1, + 'view' => 'list', 'hide_empty' => 1, 'pagination' => 1, // --- query args --- @@ -1396,6 +1404,11 @@ function radio_station_language_archive_list( $atts ) { 'show_desc' => 1, ); + // 2.5.0: change show description default for grid view + if ( isset( $atts['view'] ) && ( 'grid' == $atts['view'] ) ) { + $defaults['show_desc'] = 0; + } + // --- handle possible pagination offset --- // 2.5.0: fix to work by offset if ( isset( $atts['perpage'] ) && ( $atts['perpage'] > 0 ) ) { @@ -1621,7 +1634,8 @@ function radio_station_language_archive_list( $atts ) { } // 2.5.0: added id attribute with instance - $list = '
    ' . "\n"; + // 2.5.0: add view attribute to class list + $list = '
    ' . "\n"; // --- loop languages --- // 2.5.0: track post display count diff --git a/loader.php b/loader.php index bc67e00..2c37c79 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // ================================= // // -------------- -// Version: 1.2.7 +// Version: 1.2.8 // -------------- // Note: Changelog and structure at end of file. // @@ -749,7 +749,8 @@ public function update_settings() { if ( 3 == $commas ) { sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] ); } elseif ( 2 == $commas ) { - sscanf( $posted, 'rgba(%d,%d,%d)', $values['red'], $values['green'], $values['blue'] ); + // 1.2.8: remove a from rgba (failing for non-alpha selections) + sscanf( $posted, 'rgb(%d,%d,%d)', $values['red'], $values['green'], $values['blue'] ); } // echo 'rgba sscanf values: ' . print_r( $values, true ) . "\n"; // 1.2.7: fix for use of duplicate key variable @@ -774,7 +775,8 @@ public function update_settings() { if ( 3 == $commas ) { $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')'; } elseif ( 2 == $commas ) { - $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ')'; + // 1.2.8: remove a from rgba (for non-alpha selections) + $posted = 'rgb(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ')'; } } $settings[$key] = $posted; @@ -3465,6 +3467,9 @@ function radio_station_settings_resources( $media, $color_picker ) { // CHANGELOG // ========= +// == 1.2.8 == +// - fix saving non-alpha colours in coloralpha fields + // == 1.2.7 == // - fix color picker alpha sanitization / saving // - allow color picker alpha to not include alpha diff --git a/options.php b/options.php index 1bce75e..cf0b9d6 100644 --- a/options.php +++ b/options.php @@ -607,6 +607,23 @@ 'pro' => true, ), + // --- [Pro/Player] Track Animation --- + 'player_bar_track_animation' => array( + 'type' => 'select', + 'label' => __( 'Track Animation', 'radio-station' ), + 'default' => 'backandforth', + 'options' => array( + 'none' => __( 'No Animation', 'radio-station' ), + 'lefttoright' => __( 'Left to Right Ticker', 'radio-station' ), + 'righttoleft' => __( 'Right to Left Ticker', 'radio-station' ), + 'backandforth' => __( 'Back and Forth', 'radio-station' ), + ), + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'How to animate the currently playing track display.', 'radio-station' ), + 'pro' => true, + ), + // --- [Pro/Player] Metadata URL --- // 2.4.0.3: added for alternative stream metadata URL 'player_bar_metadata' => array( diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 18985c7..e80d6cd 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -135,6 +135,7 @@ .rp-play-pause-button:hover, .rp-play-pause-button:focus { opacity: 1; + scale: 1.1; } .radio-container.light.circular .rp-play-pause-button { @@ -236,8 +237,10 @@ opacity: 0.9; } -.radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus { +.radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus, +.radio-container .rp-popup button:hover, .radio-container .rp-popup button:focus { opacity: 1; + scale: 1.1; } .radio-container.circular .rp-volume-controls button, .radio-container.circular .rp-popup button { @@ -261,28 +264,22 @@ } .light button.rp-mute, .light button.rp-volume-max, .light button.rp-volume-up, .light button.rp-volume-down, .light button.rp-popup-button { - background-image: url("../images/volume-controls-light.png?v=1"); + background-image: url("../images/volume-controls-light.png?v=3"); } .dark button.rp-mute, .dark button.rp-volume-max, .dark button.rp-volume-up, .dark button.rp-volume-down, .dark button.rp-popup-button { - background-image: url("../images/volume-controls-dark.png?v=1"); -} - -.light .rp-mute {background-position: 0px 0px;} -.dark .rp-mute {background-position: 0px 0px;} -.light.muted .rp-mute, .light .rp-mute:hover, -.dark.muted .rp-mute, .dark .rp-mute:hover {background-position: -18px -18px;} -.light .rp-volume-max, .dark .rp-volume-max {background-position: 0 -36px;} -.light.maxed .rp-volume-max, .light .rp-volume-max:focus, .light .rp-volume-max:hover, -.dark.maxed .rp-volume-max, .dark .rp-volume-max:focus, .dark .rp-volume-max:hover {background-position: -18px -36px;} -.light .rp-volume-up, .dark .rp-volume-up {background-position: 0 -54px;} -.light .rp-volume-up:focus, .light .rp-volume-up:hover, -.dark .rp-volume-up:focus, .dark .rp-volume-up:hover {background-position: -18px -54px;} -.light .rp-volume-down, .dark .rp-volume-down {background-position: 0 -72px;} -.light .rp-volume-down:focus, .light .rp-volume-down:hover, -.dark .rp-volume-down:focus, .dark .rp-volume-down:hover {background-position: -18px -72px;} -.light .rp-popup-button, .dark .rp-popup-button {background-position: 0 -90px;} -.light .rp-popup-button:focus, .light .rp-popup-button:hover, -.dark .rp-popup-button:focus, .dark .rp-popup-button:hover {background-position: -18px -90px;} + background-image: url("../images/volume-controls-dark.png?v=3"); +} + +.rp-mute {background-position: 0px 0px;} +.muted .rp-mute, .rp-mute:hover {background-position: -18px -18px;} +.rp-volume-max {background-position: 0 -36px;} +.maxed .rp-volume-max, .rp-volume-max:focus, .rp-volume-max:hover {background-position: -18px -36px;} +.rp-volume-up {background-position: 0 -54px;} +.rp-volume-up:focus, .rp-volume-up:hover {background-position: -18px -54px;} +.rp-volume-down {background-position: 0 -72px;} +.rp-volume-down:focus, .rp-volume-down:hover {background-position: -18px -72px;} +.rp-popup-button {background-position: 0 -90px;} +.rp-popup-button:focus, .rp-popup-button:hover {background-position: -18px -90px;} /* @end */ /* @roup Now Playing Info */ diff --git a/player/images/volume-controls-dark.png b/player/images/volume-controls-dark.png index a128758b4f629f0a9c448ea780aad663f2eb999e..518227b08a4807b19509b8ebc6666e05c0429b75 100644 GIT binary patch literal 6992 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 KAv|&nxcEOj`3Hjl diff --git a/player/images/volume-controls-light.png b/player/images/volume-controls-light.png index 6d9302933b85649db32feb3920d0e1dbd514a296..8207eece9e08fa65fed1fb68936d726efc2c0a4e 100644 GIT binary patch literal 7011 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 literal 7032 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 2 && arguments[2] !== undefined ? arguments[2] : false; + /* Prepare the song change. */ @@ -1336,7 +1365,7 @@ var AudioNavigation = function () { /* Set new information now that the song has changed. */ - afterSongChange(); + afterSongChange(direct); } /** @@ -1346,8 +1375,12 @@ var AudioNavigation = function () { * @prop {string} playlist - The playlist we are changing the song on. * @prop {object} song - The song we are changing to in the playlist. * @prop {number} index - The inded of the song we are changing to in the playlist. + * @prop {boolean} direct - Determines if it was a direct click on the song. We + * then don't care if shuffle is on or not */ function changeSongPlaylist(playlist, song, index) { + var direct = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + /* Prepare the song change. */ @@ -1366,7 +1399,7 @@ var AudioNavigation = function () { /* Set new information now that the song has changed. */ - afterSongChange(); + afterSongChange(direct); } /** @@ -1393,24 +1426,27 @@ var AudioNavigation = function () { If an album changes, fire an album change. */ if (_checks2.default.newAlbum(song)) { - _callbacks2.default.run("album_change"); + _callbacks2.default.run("album_change", null); } } /** * Updates data on the display after a song has changed. * + * @prop {boolean} direct - Determines if it was a direct click on the song. + * We then don't care if shuffle is on or not. + * * @access private */ - function afterSongChange() { + function afterSongChange(direct) { _metaDataElements2.default.displayMetaData(); - _containerElements2.default.setActive(); + _containerElements2.default.setActive(direct); _timeElements2.default.resetDurationTimes(); /* Run the song change callback. */ - _callbacks2.default.run("song_change"); + _callbacks2.default.run("song_change", null); } /** @@ -1425,7 +1461,7 @@ var AudioNavigation = function () { we run the `playlist_changed` callback. */ if (_config2.default.active_playlist != playlist) { - _callbacks2.default.run("playlist_changed"); + _callbacks2.default.run("playlist_changed", null); /* Set the active playlist to the playlist parameter. Only need to set if it's different. @@ -1808,422 +1844,166 @@ var _config = __webpack_require__(0); var _config2 = _interopRequireDefault(_config); -var _debug = __webpack_require__(4); - -var _debug2 = _interopRequireDefault(_debug); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** - * AmplitudeJS Callback Utility + * These methods help display the audio's meta data * - * @module utilities/callbacks - */ -/** - * Imports the config module - * @module config + * @module visual/MetaDataElements */ -var Callbacks = function () { +var MetaDataElements = function () { /** - * Initializes the callbacks for the player. + * Displays the active song's metadata. This is called after a song has + * been changed. This method takes the active song and displays the + * metadata. So once the new active song is set, we update all of the + * screen elements. + * + * @access public */ - function initialize() { - /* - Event: abort - https://www.w3schools.com/tags/av_event_abort.asp - */ - _config2.default.audio.addEventListener("abort", function () { - run("abort"); - }); - - /* - Event: error - https://www.w3schools.com/tags/av_event_error.asp - */ - _config2.default.audio.addEventListener("error", function () { - run("error"); - }); - + function displayMetaData() { /* - Event: loadeddata - https://www.w3schools.com/tags/av_event_loadeddata.asp + Define the image meta data keys. These are managed separately + since we aren't actually changing the inner HTML of these elements. */ - _config2.default.audio.addEventListener("loadeddata", function () { - run("loadeddata"); - }); + var imageMetaDataKeys = ["cover_art_url", "station_art_url", "podcast_episode_cover_art_url"]; /* - Event: loadedmetadata - https://www.w3schools.com/tags/av_event_loadedmetadata.asp + Get all of the song info elements */ - _config2.default.audio.addEventListener("loadedmetadata", function () { - run("loadedmetadata"); - }); + var songInfoElements = document.querySelectorAll("[data-amplitude-song-info]"); /* - Event: loadstart - https://www.w3schools.com/tags/av_event_loadstart.asp + Iterate over all of the song info elements. We will either + set these to the new values, or clear them if the active song + doesn't have the info set. */ - _config2.default.audio.addEventListener("loadstart", function () { - run("loadstart"); - }); + for (var i = 0; i < songInfoElements.length; i++) { + /* + Get the info so we can check if the active meta data has the + key. + */ + var info = songInfoElements[i].getAttribute("data-amplitude-song-info"); - /* - Event: pause - https://www.w3schools.com/tags/av_event_pause.asp - */ - _config2.default.audio.addEventListener("pause", function () { - run("pause"); - }); + /* + Grab the playlist and song index. + */ + var playlist = songInfoElements[i].getAttribute("data-amplitude-playlist"); + var songIndex = songInfoElements[i].getAttribute("data-amplitude-song-index"); - /* - Event: playing - https://www.w3schools.com/tags/av_event_playing.asp - */ - _config2.default.audio.addEventListener("playing", function () { - run("playing"); - }); + /* + Ensure that we don't set any individual elements now. We set this with the + sync meta data method. The reason we don't set them here is because + all individual songs would get the now playing artwork. If the playlists + match or the element is a main element meaning it doesn't + belong to a playlist or a song, then we set the song info. + */ + if (songIndex == null && (_config2.default.active_playlist == playlist || playlist == null && songIndex == null)) { + /* + If the active metadata has the key, then we set it, + otherwise we clear it. If it's an image element then + we default it to the default info if needed. + */ + var val = _config2.default.active_metadata[info] != undefined ? _config2.default.active_metadata[info] : null; + if (imageMetaDataKeys.indexOf(info) >= 0) { + val = val || _config2.default.default_album_art; + songInfoElements[i].setAttribute("src", val); + } else { + val = val || ""; + songInfoElements[i].innerHTML = val; + } + } + } + } + /** + * Displays the playlist meta data. + */ + function displayPlaylistMetaData() { /* - Event: play - https://www.w3schools.com/tags/av_event_play.asp + Define the image meta data keys. These are managed separately + since we aren't actually changing the inner HTML of these elements. */ - _config2.default.audio.addEventListener("play", function () { - run("play"); - }); + var imageMetaDataKeys = ["image_url"]; /* - Event: progress - https://www.w3schools.com/tags/av_event_progress.asp + Get all of the playlist info elements */ - _config2.default.audio.addEventListener("progress", function () { - run("progress"); - }); + var playlistInfoElements = document.querySelectorAll("[data-amplitude-playlist-info]"); /* - Event: ratechange - https://www.w3schools.com/tags/av_event_ratechange.asp + Iterate over all of the playlist info elements. We will either + set these to the new values, or clear them if the active song + doesn't have the info set. */ - _config2.default.audio.addEventListener("ratechange", function () { - run("ratechange"); - }); + for (var i = 0; i < playlistInfoElements.length; i++) { + /* + Get the info so we can check if the active meta data has the + key. + */ + var info = playlistInfoElements[i].getAttribute("data-amplitude-playlist-info"); + var playlist = playlistInfoElements[i].getAttribute("data-amplitude-playlist"); - /* - Event: seeked - https://www.w3schools.com/tags/av_event_seeked.asp - */ - _config2.default.audio.addEventListener("seeked", function () { - run("seeked"); - }); + if (_config2.default.playlists[playlist][info] != undefined) { + if (imageMetaDataKeys.indexOf(info) >= 0) { + playlistInfoElements[i].setAttribute("src", _config2.default.playlists[playlist][info]); + } else { + playlistInfoElements[i].innerHTML = _config2.default.playlists[playlist][info]; + } + } else { + /* + We look for the default album art because + the actual key didn't exist. If the default album + art doesn't exist then we set the src attribute + to null. + */ + if (imageMetaDataKeys.indexOf(info) >= 0) { + if (_config2.default.default_playlist_art != "") { + playlistInfoElements[i].setAttribute("src", _config2.default.default_playlist_art); + } else { + playlistInfoElements[i].setAttribute("src", ""); + } + } else { + playlistInfoElements[i].innerHTML = ""; + } + } + } + } + /** + * Sets the first song in the playlist. This is used to fill in the meta + * data in the playlist + * + * @param {object} song - The song we are setting to be the first song in the playlist + * @param {string} playlist - Key of the playlist we are setting the first song in + */ + function setFirstSongInPlaylist(song, playlist) { /* - Event: seeking - https://www.w3schools.com/tags/av_event_seeking.asp + Define the image meta data keys. These are managed separately + since we aren't actually changing the inner HTML of these elements. */ - _config2.default.audio.addEventListener("seeking", function () { - run("seeking"); - }); + var imageMetaDataKeys = ["cover_art_url", "station_art_url", "podcast_episode_cover_art_url"]; /* - Event: stalled - https://www.w3schools.com/tags/av_event_stalled.asp + Get all of the song info elements */ - _config2.default.audio.addEventListener("stalled", function () { - run("stalled"); - }); + var songInfoElements = document.querySelectorAll('[data-amplitude-song-info][data-amplitude-playlist="' + playlist + '"]'); /* - Event: suspend - https://www.w3schools.com/tags/av_event_suspend.asp + Iterate over all of the song info elements. We will either + set these to the new values, or clear them if the active song + doesn't have the info set. */ - _config2.default.audio.addEventListener("suspend", function () { - run("suspend"); - }); + for (var i = 0; i < songInfoElements.length; i++) { + /* + Get the info so we can check if the active meta data has the + key. + */ + var info = songInfoElements[i].getAttribute("data-amplitude-song-info"); - /* - Event: timeupdate - https://www.w3schools.com/tags/av_event_timeupdate.asp - */ - _config2.default.audio.addEventListener("timeupdate", function () { - run("timeupdate"); - }); - - /* - Event: volumechange - https://www.w3schools.com/tags/av_event_volumechange.asp - */ - _config2.default.audio.addEventListener("volumechange", function () { - run("volumechange"); - }); - - /* - Event: waiting - https://www.w3schools.com/tags/av_event_waiting.asp - */ - _config2.default.audio.addEventListener("waiting", function () { - run("waiting"); - }); - - /* - Event: canplay - https://www.w3schools.com/tags/av_event_canplay.asp - */ - _config2.default.audio.addEventListener("canplay", function () { - run("canplay"); - }); - - /* - Event: canplaythrough - https://www.w3schools.com/tags/av_event_canplaythrough.asp - */ - _config2.default.audio.addEventListener("canplaythrough", function () { - run("canplaythrough"); - }); - - /* - Event: durationchange - https://www.w3schools.com/tags/av_event_durationchange.asp - */ - _config2.default.audio.addEventListener("durationchange", function () { - run("durationchange"); - }); - - /* - Event: ended - https://www.w3schools.com/tags/av_event_ended.asp - */ - _config2.default.audio.addEventListener("ended", function () { - run("ended"); - }); - } - - /** - * Runs a user defined callback method - * - * Public Accessor: Callbacks.run( callbackName ) - * - * @access public - * @param {string} callbackName - The name of the callback we are going to run. - */ - function run(callbackName) { - /* - Checks to see if a user defined a callback method for the - callback we are running. - */ - if (_config2.default.callbacks[callbackName]) { - /* - Build the callback function - */ - var callbackFunction = _config2.default.callbacks[callbackName]; - - /* - Write a debug message stating the callback we are running - */ - _debug2.default.writeMessage("Running Callback: " + callbackName); - - /* - Run the callback function and catch any errors - */ - try { - callbackFunction(); - } catch (error) { - if (error.message == "CANCEL EVENT") { - throw error; - } else { - _debug2.default.writeMessage("Callback error: " + error.message); - } - } - } - } - - return { - initialize: initialize, - run: run - }; -}(); - -/** - * Imports the debug module - * @module utilities/debug - */ -exports.default = Callbacks; -module.exports = exports["default"]; - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _config = __webpack_require__(0); - -var _config2 = _interopRequireDefault(_config); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/** - * These methods help display the audio's meta data - * - * @module visual/MetaDataElements - */ -var MetaDataElements = function () { - /** - * Displays the active song's metadata. This is called after a song has - * been changed. This method takes the active song and displays the - * metadata. So once the new active song is set, we update all of the - * screen elements. - * - * @access public - */ - function displayMetaData() { - /* - Define the image meta data keys. These are managed separately - since we aren't actually changing the inner HTML of these elements. - */ - var imageMetaDataKeys = ["cover_art_url", "station_art_url", "podcast_episode_cover_art_url"]; - - /* - Get all of the song info elements - */ - var songInfoElements = document.querySelectorAll("[data-amplitude-song-info]"); - - /* - Iterate over all of the song info elements. We will either - set these to the new values, or clear them if the active song - doesn't have the info set. - */ - for (var i = 0; i < songInfoElements.length; i++) { - /* - Get the info so we can check if the active meta data has the - key. - */ - var info = songInfoElements[i].getAttribute("data-amplitude-song-info"); - - /* - Grab the playlist and song index. - */ - var playlist = songInfoElements[i].getAttribute("data-amplitude-playlist"); - var songIndex = songInfoElements[i].getAttribute("data-amplitude-song-index"); - - /* - Ensure that we don't set any individual elements now. We set this with the - sync meta data method. The reason we don't set them here is because - all individual songs would get the now playing artwork. If the playlists - match or the element is a main element meaning it doesn't - belong to a playlist or a song, then we set the song info. - */ - if (songIndex == null && (_config2.default.active_playlist == playlist || playlist == null && songIndex == null)) { - /* - If the active metadata has the key, then we set it, - otherwise we clear it. If it's an image element then - we default it to the default info if needed. - */ - var val = _config2.default.active_metadata[info] != undefined ? _config2.default.active_metadata[info] : null; - if (imageMetaDataKeys.indexOf(info) >= 0) { - val = val || _config2.default.default_album_art; - songInfoElements[i].setAttribute("src", val); - } else { - val = val || ""; - songInfoElements[i].innerHTML = val; - } - } - } - } - - /** - * Displays the playlist meta data. - */ - function displayPlaylistMetaData() { - /* - Define the image meta data keys. These are managed separately - since we aren't actually changing the inner HTML of these elements. - */ - var imageMetaDataKeys = ["image_url"]; - - /* - Get all of the playlist info elements - */ - var playlistInfoElements = document.querySelectorAll("[data-amplitude-playlist-info]"); - - /* - Iterate over all of the playlist info elements. We will either - set these to the new values, or clear them if the active song - doesn't have the info set. - */ - for (var i = 0; i < playlistInfoElements.length; i++) { - /* - Get the info so we can check if the active meta data has the - key. - */ - var info = playlistInfoElements[i].getAttribute("data-amplitude-playlist-info"); - var playlist = playlistInfoElements[i].getAttribute("data-amplitude-playlist"); - - if (_config2.default.playlists[playlist][info] != undefined) { - if (imageMetaDataKeys.indexOf(info) >= 0) { - playlistInfoElements[i].setAttribute("src", _config2.default.playlists[playlist][info]); - } else { - playlistInfoElements[i].innerHTML = _config2.default.playlists[playlist][info]; - } - } else { - /* - We look for the default album art because - the actual key didn't exist. If the default album - art doesn't exist then we set the src attribute - to null. - */ - if (imageMetaDataKeys.indexOf(info) >= 0) { - if (_config2.default.default_playlist_art != "") { - playlistInfoElements[i].setAttribute("src", _config2.default.default_playlist_art); - } else { - playlistInfoElements[i].setAttribute("src", ""); - } - } else { - playlistInfoElements[i].innerHTML = ""; - } - } - } - } - - /** - * Sets the first song in the playlist. This is used to fill in the meta - * data in the playlist - * - * @param {object} song - The song we are setting to be the first song in the playlist - * @param {string} playlist - Key of the playlist we are setting the first song in - */ - function setFirstSongInPlaylist(song, playlist) { - /* - Define the image meta data keys. These are managed separately - since we aren't actually changing the inner HTML of these elements. - */ - var imageMetaDataKeys = ["cover_art_url", "station_art_url", "podcast_episode_cover_art_url"]; - - /* - Get all of the song info elements - */ - var songInfoElements = document.querySelectorAll('[data-amplitude-song-info][data-amplitude-playlist="' + playlist + '"]'); - - /* - Iterate over all of the song info elements. We will either - set these to the new values, or clear them if the active song - doesn't have the info set. - */ - for (var i = 0; i < songInfoElements.length; i++) { - /* - Get the info so we can check if the active meta data has the - key. - */ - var info = songInfoElements[i].getAttribute("data-amplitude-song-info"); - - /* - Get the song info element playlist. - */ - var elementPlaylist = songInfoElements[i].getAttribute("data-amplitude-playlist"); + /* + Get the song info element playlist. + */ + var elementPlaylist = songInfoElements[i].getAttribute("data-amplitude-playlist"); /* If the playlists match or the element is a main element, then @@ -2358,6 +2138,140 @@ var MetaDataElements = function () { exports.default = MetaDataElements; module.exports = exports["default"]; +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _config = __webpack_require__(0); + +var _config2 = _interopRequireDefault(_config); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Handles all of the visual syncing to the state of the config for the repeat + * elements. + * + * @module visual/RepeatElements + */ +var RepeatElements = function () { + /** + * Syncs repeat for all of the repeat buttons. Users + * can apply styles to the 'amplitude-repeat-on' and + * 'amplitude-repeat-off' classes. They represent the state + * of the player. + */ + function syncRepeat() { + /* + Gets all of the repeat classes + */ + var repeatClasses = document.getElementsByClassName("amplitude-repeat"); + + /* + Iterate over all of the repeat classes. If repeat is on, + then add the 'amplitude-repeat-on' class and remove the + 'amplitude-repeat-off' class. If it's off, then do the + opposite. + */ + for (var i = 0; i < repeatClasses.length; i++) { + if (_config2.default.repeat) { + repeatClasses[i].classList.add("amplitude-repeat-on"); + repeatClasses[i].classList.remove("amplitude-repeat-off"); + } else { + repeatClasses[i].classList.remove("amplitude-repeat-on"); + repeatClasses[i].classList.add("amplitude-repeat-off"); + } + } + } + + /** + * Syncs repeat for all of the playlist repeat buttons. Users + * can apply styles to the `amplitude-repeat-on` and `amplitude-repeat-off` + * classes. They repreent the state of the playlist in the player. + */ + function syncRepeatPlaylist(playlist) { + /* + Gets all of the repeat buttons. + */ + var repeatButtons = document.getElementsByClassName("amplitude-repeat"); + + /* + Iterate over all of the repeat buttons + */ + for (var i = 0; i < repeatButtons.length; i++) { + /* + Ensure that the repeat button belongs to matches the + playlist we are syncing the state for. + */ + if (repeatButtons[i].getAttribute("data-amplitude-playlist") == playlist) { + /* + If the state of the playlist is shuffled on, true, then + we add the 'amplitude-repeat-on' class and remove the + 'amplitude-repeat-off' class. If the player is not shuffled + then we do the opposite. + */ + if (_config2.default.playlists[playlist].repeat) { + repeatButtons[i].classList.add("amplitude-repeat-on"); + repeatButtons[i].classList.remove("amplitude-repeat-off"); + } else { + repeatButtons[i].classList.add("amplitude-repeat-off"); + repeatButtons[i].classList.remove("amplitude-repeat-on"); + } + } + } + } + + /** + * Syncs repeat for all of the repeat song buttons. Users + * can apply styles to the 'amplitude-repeat-song-on' and + * 'amplitude-repeat-song-off' classes. They represent the state + * of the player. + */ + function syncRepeatSong() { + /* + Gets all of the repeat song classes + */ + var repeatSongClasses = document.getElementsByClassName("amplitude-repeat-song"); + + /* + Iterate over all of the repeat song classes. If repeat is on, + then add the 'amplitude-repeat-song-on' class and remove the + 'amplitude-repeat-song-off' class. If it's off, then do the + opposite. + */ + for (var i = 0; i < repeatSongClasses.length; i++) { + if (_config2.default.repeat_song) { + repeatSongClasses[i].classList.add("amplitude-repeat-song-on"); + repeatSongClasses[i].classList.remove("amplitude-repeat-song-off"); + } else { + repeatSongClasses[i].classList.remove("amplitude-repeat-song-on"); + repeatSongClasses[i].classList.add("amplitude-repeat-song-off"); + } + } + } + + /* + Returns the publically available methods. + */ + return { + syncRepeat: syncRepeat, + syncRepeatPlaylist: syncRepeatPlaylist, + syncRepeatSong: syncRepeatSong + }; +}(); /** + * Imports the config module + * @module config + */ +exports.default = RepeatElements; +module.exports = exports["default"]; + /***/ }), /* 9 */ /***/ (function(module, exports, __webpack_require__) { @@ -2373,123 +2287,245 @@ var _config = __webpack_require__(0); var _config2 = _interopRequireDefault(_config); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var _debug = __webpack_require__(4); + +var _debug2 = _interopRequireDefault(_debug); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * AmplitudeJS Callback Utility + * + * @module utilities/callbacks + */ +/** + * Imports the config module + * @module config + */ +var Callbacks = function () { + /** + * Initializes the callbacks for the player. + */ + function initialize() { + /* + Event: abort + https://www.w3schools.com/tags/av_event_abort.asp + */ + _config2.default.audio.addEventListener("abort", function (e) { + run("abort", e); + }); + + /* + Event: error + https://www.w3schools.com/tags/av_event_error.asp + */ + _config2.default.audio.addEventListener("error", function (e) { + run("error", e); + }); + + /* + Event: loadeddata + https://www.w3schools.com/tags/av_event_loadeddata.asp + */ + _config2.default.audio.addEventListener("loadeddata", function (e) { + run("loadeddata", e); + }); + + /* + Event: loadedmetadata + https://www.w3schools.com/tags/av_event_loadedmetadata.asp + */ + _config2.default.audio.addEventListener("loadedmetadata", function (e) { + run("loadedmetadata", e); + }); + + /* + Event: loadstart + https://www.w3schools.com/tags/av_event_loadstart.asp + */ + _config2.default.audio.addEventListener("loadstart", function (e) { + run("loadstart", e); + }); + + /* + Event: pause + https://www.w3schools.com/tags/av_event_pause.asp + */ + _config2.default.audio.addEventListener("pause", function (e) { + run("pause", e); + }); + + /* + Event: playing + https://www.w3schools.com/tags/av_event_playing.asp + */ + _config2.default.audio.addEventListener("playing", function (e) { + run("playing", e); + }); + + /* + Event: play + https://www.w3schools.com/tags/av_event_play.asp + */ + _config2.default.audio.addEventListener("play", function (e) { + run("play", e); + }); + + /* + Event: progress + https://www.w3schools.com/tags/av_event_progress.asp + */ + _config2.default.audio.addEventListener("progress", function (e) { + run("progress", e); + }); + + /* + Event: ratechange + https://www.w3schools.com/tags/av_event_ratechange.asp + */ + _config2.default.audio.addEventListener("ratechange", function (e) { + run("ratechange", e); + }); + + /* + Event: seeked + https://www.w3schools.com/tags/av_event_seeked.asp + */ + _config2.default.audio.addEventListener("seeked", function (e) { + run("seeked", e); + }); + + /* + Event: seeking + https://www.w3schools.com/tags/av_event_seeking.asp + */ + _config2.default.audio.addEventListener("seeking", function (e) { + run("seeking", e); + }); + + /* + Event: stalled + https://www.w3schools.com/tags/av_event_stalled.asp + */ + _config2.default.audio.addEventListener("stalled", function (e) { + run("stalled", e); + }); + + /* + Event: suspend + https://www.w3schools.com/tags/av_event_suspend.asp + */ + _config2.default.audio.addEventListener("suspend", function (e) { + run("suspend", e); + }); + + /* + Event: timeupdate + https://www.w3schools.com/tags/av_event_timeupdate.asp + */ + _config2.default.audio.addEventListener("timeupdate", function (e) { + run("timeupdate", e); + }); + + /* + Event: volumechange + https://www.w3schools.com/tags/av_event_volumechange.asp + */ + _config2.default.audio.addEventListener("volumechange", function (e) { + run("volumechange", e); + }); + + /* + Event: waiting + https://www.w3schools.com/tags/av_event_waiting.asp + */ + _config2.default.audio.addEventListener("waiting", function (e) { + run("waiting", e); + }); -/** - * Handles all of the visual syncing to the state of the config for the repeat - * elements. - * - * @module visual/RepeatElements - */ -var RepeatElements = function () { - /** - * Syncs repeat for all of the repeat buttons. Users - * can apply styles to the 'amplitude-repeat-on' and - * 'amplitude-repeat-off' classes. They represent the state - * of the player. - */ - function syncRepeat() { /* - Gets all of the repeat classes + Event: canplay + https://www.w3schools.com/tags/av_event_canplay.asp */ - var repeatClasses = document.getElementsByClassName("amplitude-repeat"); + _config2.default.audio.addEventListener("canplay", function (e) { + run("canplay", e); + }); /* - Iterate over all of the repeat classes. If repeat is on, - then add the 'amplitude-repeat-on' class and remove the - 'amplitude-repeat-off' class. If it's off, then do the - opposite. + Event: canplaythrough + https://www.w3schools.com/tags/av_event_canplaythrough.asp */ - for (var i = 0; i < repeatClasses.length; i++) { - if (_config2.default.repeat) { - repeatClasses[i].classList.add("amplitude-repeat-on"); - repeatClasses[i].classList.remove("amplitude-repeat-off"); - } else { - repeatClasses[i].classList.remove("amplitude-repeat-on"); - repeatClasses[i].classList.add("amplitude-repeat-off"); - } - } - } + _config2.default.audio.addEventListener("canplaythrough", function (e) { + run("canplaythrough", e); + }); - /** - * Syncs repeat for all of the playlist repeat buttons. Users - * can apply styles to the `amplitude-repeat-on` and `amplitude-repeat-off` - * classes. They repreent the state of the playlist in the player. - */ - function syncRepeatPlaylist(playlist) { /* - Gets all of the repeat buttons. + Event: durationchange + https://www.w3schools.com/tags/av_event_durationchange.asp */ - var repeatButtons = document.getElementsByClassName("amplitude-repeat"); + _config2.default.audio.addEventListener("durationchange", function (e) { + run("durationchange", e); + }); /* - Iterate over all of the repeat buttons + Event: ended + https://www.w3schools.com/tags/av_event_ended.asp */ - for (var i = 0; i < repeatButtons.length; i++) { - /* - Ensure that the repeat button belongs to matches the - playlist we are syncing the state for. - */ - if (repeatButtons[i].getAttribute("data-amplitude-playlist") == playlist) { - /* - If the state of the playlist is shuffled on, true, then - we add the 'amplitude-repeat-on' class and remove the - 'amplitude-repeat-off' class. If the player is not shuffled - then we do the opposite. - */ - if (_config2.default.playlists[playlist].repeat) { - repeatButtons[i].classList.add("amplitude-repeat-on"); - repeatButtons[i].classList.remove("amplitude-repeat-off"); - } else { - repeatButtons[i].classList.add("amplitude-repeat-off"); - repeatButtons[i].classList.remove("amplitude-repeat-on"); - } - } - } + _config2.default.audio.addEventListener("ended", function (e) { + run("ended", e); + }); } /** - * Syncs repeat for all of the repeat song buttons. Users - * can apply styles to the 'amplitude-repeat-song-on' and - * 'amplitude-repeat-song-off' classes. They represent the state - * of the player. + * Runs a user defined callback method + * + * Public Accessor: Callbacks.run( callbackName ) + * + * @access public + * @param {string} callbackName - The name of the callback we are going to run. */ - function syncRepeatSong() { + function run(callbackName, event) { /* - Gets all of the repeat song classes + Checks to see if a user defined a callback method for the + callback we are running. */ - var repeatSongClasses = document.getElementsByClassName("amplitude-repeat-song"); + if (_config2.default.callbacks[callbackName]) { + /* + Build the callback function + */ + var callbackFunction = _config2.default.callbacks[callbackName]; - /* - Iterate over all of the repeat song classes. If repeat is on, - then add the 'amplitude-repeat-song-on' class and remove the - 'amplitude-repeat-song-off' class. If it's off, then do the - opposite. - */ - for (var i = 0; i < repeatSongClasses.length; i++) { - if (_config2.default.repeat_song) { - repeatSongClasses[i].classList.add("amplitude-repeat-song-on"); - repeatSongClasses[i].classList.remove("amplitude-repeat-song-off"); - } else { - repeatSongClasses[i].classList.remove("amplitude-repeat-song-on"); - repeatSongClasses[i].classList.add("amplitude-repeat-song-off"); + /* + Write a debug message stating the callback we are running + */ + _debug2.default.writeMessage("Running Callback: " + callbackName); + + /* + Run the callback function and catch any errors + */ + try { + callbackFunction(event); + } catch (error) { + if (error.message == "CANCEL EVENT") { + throw error; + } else { + _debug2.default.writeMessage("Callback error: " + error.message); + } } } } - /* - Returns the publically available methods. - */ return { - syncRepeat: syncRepeat, - syncRepeatPlaylist: syncRepeatPlaylist, - syncRepeatSong: syncRepeatSong + initialize: initialize, + run: run }; -}(); /** - * Imports the config module - * @module config - */ -exports.default = RepeatElements; +}(); + +/** + * Imports the debug module + * @module utilities/debug + */ +exports.default = Callbacks; module.exports = exports["default"]; /***/ }), @@ -4573,7 +4609,7 @@ var _audioNavigation = __webpack_require__(3); var _audioNavigation2 = _interopRequireDefault(_audioNavigation); -var _callbacks = __webpack_require__(7); +var _callbacks = __webpack_require__(9); var _callbacks2 = _interopRequireDefault(_callbacks); @@ -4601,7 +4637,7 @@ var _playPauseElements = __webpack_require__(2); var _playPauseElements2 = _interopRequireDefault(_playPauseElements); -var _metaDataElements = __webpack_require__(8); +var _metaDataElements = __webpack_require__(7); var _metaDataElements2 = _interopRequireDefault(_metaDataElements); @@ -4609,7 +4645,7 @@ var _playbackSpeedElements = __webpack_require__(18); var _playbackSpeedElements2 = _interopRequireDefault(_playbackSpeedElements); -var _repeatElements = __webpack_require__(9); +var _repeatElements = __webpack_require__(8); var _repeatElements2 = _interopRequireDefault(_repeatElements); @@ -4701,44 +4737,44 @@ var Initializer = function () { Activates the audio context after an event for the user. */ document.documentElement.addEventListener("mousedown", function () { - if (_config2.default.context.state !== 'running') { + if (_config2.default.context.state !== "running") { _config2.default.context.resume(); } }); document.documentElement.addEventListener("keydown", function () { - if (_config2.default.context.state !== 'running') { + if (_config2.default.context.state !== "running") { _config2.default.context.resume(); } }); document.documentElement.addEventListener("keyup", function () { - if (_config2.default.context.state !== 'running') { + if (_config2.default.context.state !== "running") { _config2.default.context.resume(); } }); /* - Set the user waveform settings if provided. - */ + Set the user waveform settings if provided. + */ if (userConfig.waveforms != undefined && userConfig.waveforms.sample_rate != undefined) { _config2.default.waveforms.sample_rate = userConfig.waveforms.sample_rate; } /* - Initialize the waveform. - */ + Initialize the waveform. + */ _waveform2.default.init(); /* - If the user is registering visualizations on init, - we set them right away. - */ + If the user is registering visualizations on init, + we set them right away. + */ if (userConfig.visualizations != undefined && userConfig.visualizations.length > 0) { /* - Iterate over all of the visualizations and - register them in our player. - */ + Iterate over all of the visualizations and + register them in our player. + */ for (var i = 0; i < userConfig.visualizations.length; i++) { _visualizations2.default.register(userConfig.visualizations[i].object, userConfig.visualizations[i].params); } @@ -4848,7 +4884,7 @@ var Initializer = function () { /* Check to see if the user entered a start song */ - if (userConfig.start_song != undefined && userConfig.starting_playlist) { + if (userConfig.start_song != undefined && !userConfig.starting_playlist) { /* Ensure what has been entered is an integer. */ @@ -4998,7 +5034,7 @@ var Initializer = function () { /* Run after init callback */ - _callbacks2.default.run("initialized"); + _callbacks2.default.run("initialized",null); } /** @@ -5129,10 +5165,10 @@ var Initializer = function () { } } - /** + /** * Initializes the index of the song in the songs array so * we can reference it if needed - * + * * @access private */ function initializeDefaultSongIndexes() { @@ -5181,10 +5217,12 @@ var WaveForm = function () { Initialize the local variables used in the Waveform. */ var buffer = ""; - var sampleRate = _config2.default.waveforms.sample_rate; + var sampleRate = ""; var peaks = ""; function init() { + sampleRate = _config2.default.waveforms.sample_rate; + /* Grabs all of the waveform elements on the page. */ @@ -5829,7 +5867,7 @@ var BufferedProgressElements = function () { var playlist = songBufferedProgressBars[i].getAttribute("data-amplitude-playlist"); var song = songBufferedProgressBars[i].getAttribute("data-amplitude-song-index"); - if (playlist == null && song == null) { + if (playlist == null && song == null && !isNaN(_config2.default.buffered)) { songBufferedProgressBars[i].value = parseFloat(parseFloat(_config2.default.buffered) / 100); } } @@ -5851,7 +5889,7 @@ var BufferedProgressElements = function () { for (var i = 0; i < songBufferedProgressBarsPlaylist.length; i++) { var song = songBufferedProgressBarsPlaylist[i].getAttribute("data-amplitude-song-index"); - if (song == null) { + if (song == null && !isNaN(_config2.default.buffered)) { songBufferedProgressBarsPlaylist[i].value = parseFloat(parseFloat(_config2.default.buffered) / 100); } } @@ -5873,7 +5911,7 @@ var BufferedProgressElements = function () { for (var i = 0; i < songBufferedProgressBarsSongs.length; i++) { var playlist = songBufferedProgressBarsSongs[i].getAttribute("data-amplitude-playlist"); - if (playlist == null) { + if (playlist == null && !isNaN(_config2.default.buffered)) { songBufferedProgressBarsSongs[i].value = parseFloat(parseFloat(_config2.default.buffered) / 100); } } @@ -5895,7 +5933,9 @@ var BufferedProgressElements = function () { set them to 0 which is like re-setting them. */ for (var i = 0; i < songBufferedProgressBarsSongsInPlaylist.length; i++) { - songBufferedProgressBarsSongsInPlaylist[i].value = parseFloat(parseFloat(_config2.default.buffered) / 100); + if (!isNaN(_config2.default.buffered)) { + songBufferedProgressBarsSongsInPlaylist[i].value = parseFloat(parseFloat(_config2.default.buffered) / 100); + } } } @@ -5952,10 +5992,6 @@ var _audioNavigation = __webpack_require__(3); var _audioNavigation2 = _interopRequireDefault(_audioNavigation); -var _callbacks = __webpack_require__(7); - -var _callbacks2 = _interopRequireDefault(_callbacks); - var _core = __webpack_require__(1); var _core2 = _interopRequireDefault(_core); @@ -5977,11 +6013,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * Imports the AmplitudeJS Core Methods * @module core/Core */ - - /** - * Imports the Audio Navigation Utility - * @module utilities/AudioNavigation + * Imports the config module + * @module config */ var Ended = function () { /** @@ -6015,7 +6049,7 @@ var Ended = function () { /* Stops the active song. */ - AmplitudeCore.stop(); + _core2.default.stop(); /* Sync the play pause elements. @@ -6041,12 +6075,8 @@ var Ended = function () { /** - * Imports the Callback Utility - * @module utilities/callbacks - */ -/** - * Imports the config module - * @module config + * Imports the Audio Navigation Utility + * @module utilities/AudioNavigation */ exports.default = Ended; module.exports = exports["default"]; @@ -6941,7 +6971,7 @@ var _audioNavigation = __webpack_require__(3); var _audioNavigation2 = _interopRequireDefault(_audioNavigation); -var _repeatElements = __webpack_require__(9); +var _repeatElements = __webpack_require__(8); var _repeatElements2 = _interopRequireDefault(_repeatElements); @@ -7304,7 +7334,7 @@ var _playPauseElements = __webpack_require__(2); var _playPauseElements2 = _interopRequireDefault(_playPauseElements); -var _callbacks = __webpack_require__(7); +var _callbacks = __webpack_require__(9); var _callbacks2 = _interopRequireDefault(_callbacks); @@ -7539,8 +7569,6 @@ var Pause = function () { if (playlistAttribute != null && songIndexAttribute != null) { handleSongInPlaylistPause(playlistAttribute, songIndexAttribute); } - - _configState2.default.setPlayerState(); } } @@ -7769,8 +7797,6 @@ var Play = function () { if (playlistAttribute != null && songIndexAttribute != null) { handleSongInPlaylistPlay(playlistAttribute, songIndexAttribute); } - - _configState2.default.setPlayerState(); } } @@ -8084,8 +8110,6 @@ var PlayPause = function () { if (playlist != null && song != null) { handleSongInPlaylistPlayPause(playlist, song); } - - _configState2.default.setPlayerState(); } } @@ -8136,7 +8160,7 @@ var PlayPause = function () { we go from the first song in the shuffle playlist array. */ if (_config2.default.playlists[playlist].shuffle) { - _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].shuffle_list[0], 0); + _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].shuffle_list[0], 0, true); } else { _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].songs[0], 0); } @@ -8186,7 +8210,7 @@ var PlayPause = function () { /* We then change the song to the index selected. */ - _audioNavigation2.default.changeSong(_config2.default.songs[song], song); + _audioNavigation2.default.changeSong(_config2.default.songs[song], song, true); } /* @@ -8200,7 +8224,7 @@ var PlayPause = function () { The song selected is different, so we change the song. */ - _audioNavigation2.default.changeSong(_config2.default.songs[song], song); + _audioNavigation2.default.changeSong(_config2.default.songs[song], song, true); } /* @@ -8249,7 +8273,7 @@ var PlayPause = function () { /* We then change the song to the index selected. */ - _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].songs[song], song); + _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].songs[song], song, true); } /* @@ -8263,7 +8287,7 @@ var PlayPause = function () { The song selected is different, so we change the song. */ - _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].songs[song], song); + _audioNavigation2.default.changeSongPlaylist(playlist, _config2.default.playlists[playlist].songs[song], song, true); } /* @@ -8623,7 +8647,7 @@ var _repeater = __webpack_require__(12); var _repeater2 = _interopRequireDefault(_repeater); -var _repeatElements = __webpack_require__(9); +var _repeatElements = __webpack_require__(8); var _repeatElements2 = _interopRequireDefault(_repeatElements); @@ -8751,7 +8775,7 @@ var _repeater = __webpack_require__(12); var _repeater2 = _interopRequireDefault(_repeater); -var _repeatElements = __webpack_require__(9); +var _repeatElements = __webpack_require__(8); var _repeatElements2 = _interopRequireDefault(_repeatElements); @@ -9424,11 +9448,6 @@ var Stop = function () { Stops the active song. */ _core2.default.stop(); - - /* - Set the state of the player. - */ - _configState2.default.setPlayerState(); } } @@ -9488,7 +9507,7 @@ var _time = __webpack_require__(23); var _time2 = _interopRequireDefault(_time); -var _callbacks = __webpack_require__(7); +var _callbacks = __webpack_require__(9); var _callbacks2 = _interopRequireDefault(_callbacks); @@ -10174,7 +10193,7 @@ var _shuffleElements = __webpack_require__(19); var _shuffleElements2 = _interopRequireDefault(_shuffleElements); -var _repeatElements = __webpack_require__(9); +var _repeatElements = __webpack_require__(8); var _repeatElements2 = _interopRequireDefault(_repeatElements); @@ -10194,7 +10213,7 @@ var _playPauseElements = __webpack_require__(2); var _playPauseElements2 = _interopRequireDefault(_playPauseElements); -var _metaDataElements = __webpack_require__(8); +var _metaDataElements = __webpack_require__(7); var _metaDataElements2 = _interopRequireDefault(_metaDataElements); @@ -10331,9 +10350,9 @@ var Amplitude = function () { /** * Sets the playback speed - * + * * Public Accessor: Amplitude.setPlaybackSpeed( speed ) - * + * * @access public */ function setPlaybackSpeed(speed) { @@ -10686,6 +10705,38 @@ var Amplitude = function () { return _config2.default.songs.length - 1; } + /** + * Adds a song to the beginning of the config array. + * This will allow Amplitude to play the song in a + * playlist type setting. + * + * Public Accessor: Amplitude.addSong( song_json ) + * + * @access public + * @param {object} song - JSON representation of a song. + * @returns {number} New index of the song (0) + */ + function prependSong(song) { + /* + Ensures we have a songs array to push to. + */ + if (_config2.default.songs == undefined) { + _config2.default.songs = []; + } + + _config2.default.songs.unshift(song); + + if (_config2.default.shuffle_on) { + _config2.default.shuffle_list.unshift(song); + } + + if (_soundcloud2.default.isSoundCloudURL(song.url)) { + _soundcloud2.default.resolveIndividualStreamableURL(song.url, null, _config2.default.songs.length - 1, _config2.default.shuffle_on); + } + + return 0; + } + /** * Adds a song to a playlist. This will allow Amplitude to play the song in the * playlist @@ -10855,11 +10906,6 @@ var Amplitude = function () { Reset all of the duration time elements. */ _timeElements2.default.resetDurationTimes(); - - /* - Sets the state of the player. - */ - _configState2.default.setPlayerState(); } /** @@ -10897,11 +10943,6 @@ var Amplitude = function () { */ _core2.default.play(); - /* - Sets the state of the player. - */ - _configState2.default.setPlayerState(); - /* Sync all of the play pause buttons. */ @@ -10945,11 +10986,6 @@ var Amplitude = function () { Play the song */ _core2.default.play(); - - /* - Set the state of the player - */ - _configState2.default.setPlayerState(); } /** @@ -10963,8 +10999,6 @@ var Amplitude = function () { */ function play() { _core2.default.play(); - - _configState2.default.setPlayerState(); } /** @@ -10978,8 +11012,18 @@ var Amplitude = function () { */ function pause() { _core2.default.pause(); + } - _configState2.default.setPlayerState(); + /** + * Allows the user to stop whatever the active song is directly + * through Javascript. + * + * Public Accessor: Amplitude.stop(); + * + * @access public + */ + function stop() { + _core2.default.stop(); } /** @@ -11452,6 +11496,7 @@ var Amplitude = function () { getSongAtIndex: getSongAtIndex, getSongAtPlaylistIndex: getSongAtPlaylistIndex, addSong: addSong, + prependSong: prependSong, addSongToPlaylist: addSongToPlaylist, removeSong: removeSong, removeSongFromPlaylist: removeSongFromPlaylist, @@ -11460,6 +11505,7 @@ var Amplitude = function () { playPlaylistSongAtIndex: playPlaylistSongAtIndex, play: play, pause: pause, + stop: stop, getAudio: getAudio, getAnalyser: getAnalyser, next: next, @@ -11488,7 +11534,7 @@ var Amplitude = function () { }; }(); -/** +/** * Playback Speed Elements * @module visual/PlaybackSpeedElements */ @@ -11543,8 +11589,8 @@ var Amplitude = function () { * @module core/Core */ /** - * @name Amplitude.js - * @author Dan Pastori (521 Dimensions) + * @name AmplitudeJS + * @author Dan Pastori (Server Side Up) */ /** * AmplitudeJS Initializer Module @@ -11577,7 +11623,7 @@ var _checks = __webpack_require__(5); var _checks2 = _interopRequireDefault(_checks); -var _metaDataElements = __webpack_require__(8); +var _metaDataElements = __webpack_require__(7); var _metaDataElements2 = _interopRequireDefault(_metaDataElements); @@ -11872,9 +11918,12 @@ var ContainerElements = function () { * Applies the class 'amplitude-active-song-container' to the element * containing visual information regarding the active song. * + * @prop {boolean} direct - Determines if it was a direct click on the song. We + * then don't care if shuffle is on or not. + * * @access public */ - function setActive() { + function setActive(direct) { /* Gets all of the song container elements. */ @@ -11892,12 +11941,20 @@ var ContainerElements = function () { that represents the song at the index. */ if (_config2.default.active_playlist == "" || _config2.default.active_playlist == null) { - var activeIndex = ''; + var activeIndex = ""; - if (_config2.default.shuffle_on) { - activeIndex = _config2.default.shuffle_list[_config2.default.active_index].index; - } else { + /* + If we click directly on the song element, we ignore + whether it's in shuffle or not. + */ + if (direct) { activeIndex = _config2.default.active_index; + } else { + if (_config2.default.shuffle_on) { + activeIndex = _config2.default.shuffle_list[_config2.default.active_index].index; + } else { + activeIndex = _config2.default.active_index; + } } if (document.querySelectorAll('.amplitude-song-container[data-amplitude-song-index="' + activeIndex + '"]')) { @@ -11910,10 +11967,14 @@ var ContainerElements = function () { } } } else { - if (_config2.default.active_playlist != null && _config2.default.active_playlist != '') { + /* + If we have an active playlist or the action took place directly on the + song element, we ignore the shuffle. + */ + if (_config2.default.active_playlist != null && _config2.default.active_playlist != "" || direct) { var activePlaylistIndex = _config2.default.playlists[_config2.default.active_playlist].active_index; } else { - var activePlaylistIndex = ''; + var activePlaylistIndex = ""; if (_config2.default.playlists[_config2.default.active_playlist].shuffle) { activePlaylistIndex = _config2.default.playlists[_config2.default.active_playlist].shuffle_list[_config2.default.playlists[_config2.default.active_playlist].active_index].index; @@ -13355,7 +13416,7 @@ module.exports = exports["default"]; /* 59 */ /***/ (function(module, exports) { -module.exports = {"name":"amplitudejs","version":"5.0.3","description":"A JavaScript library that allows you to control the design of your media controls in your webpage -- not the browser. No dependencies (jQuery not required) https://521dimensions.com/open-source/amplitudejs","main":"dist/amplitude.js","devDependencies":{"babel-core":"^6.26.3","babel-loader":"^7.1.5","babel-plugin-add-module-exports":"0.2.1","babel-polyfill":"^6.26.0","babel-preset-es2015":"^6.18.0","husky":"^1.3.1","jest":"^23.6.0","prettier":"1.15.1","pretty-quick":"^1.11.1","watch":"^1.0.2","webpack":"^2.7.0"},"directories":{"doc":"docs"},"files":["dist"],"funding":{"type":"opencollective","url":"https://opencollective.com/amplitudejs"},"scripts":{"build":"node_modules/.bin/webpack","watch":"watch 'node_modules/.bin/webpack' dist","prettier":"npx pretty-quick","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/521dimensions/amplitudejs.git"},"keywords":["webaudio","html5","javascript","audio-player"],"author":"521 Dimensions (https://521dimensions.com)","license":"MIT","bugs":{"url":"https://github.com/521dimensions/amplitudejs/issues"},"homepage":"https://github.com/521dimensions/amplitudejs#readme"} +module.exports = {"name":"amplitudejs","version":"5.3.2","description":"A JavaScript library that allows you to control the design of your media controls in your webpage -- not the browser. No dependencies (jQuery not required) https://521dimensions.com/open-source/amplitudejs","main":"dist/amplitude.js","devDependencies":{"babel-core":"^6.26.3","babel-loader":"^7.1.5","babel-plugin-add-module-exports":"0.2.1","babel-polyfill":"^6.26.0","babel-preset-es2015":"^6.18.0","husky":"^1.3.1","jest":"^23.6.0","prettier":"1.15.1","pretty-quick":"^1.11.1","watch":"^1.0.2","webpack":"^2.7.0"},"directories":{"doc":"docs"},"files":["dist"],"funding":{"type":"opencollective","url":"https://opencollective.com/amplitudejs"},"scripts":{"build":"node_modules/.bin/webpack","prettier":"npx pretty-quick","preversion":"npx pretty-quick && npm run test","postversion":"git push && git push --tags","test":"jest","version":"npm run build && git add -A dist"},"repository":{"type":"git","url":"git+https://github.com/521dimensions/amplitudejs.git"},"keywords":["webaudio","html5","javascript","audio-player"],"author":"521 Dimensions (https://521dimensions.com)","license":"MIT","bugs":{"url":"https://github.com/521dimensions/amplitudejs/issues"},"homepage":"https://github.com/521dimensions/amplitudejs#readme"} /***/ }) /******/ ]); diff --git a/player/js/amplitude.min.js b/player/js/amplitude.min.js index f28fa9d..353ece0 100644 --- a/player/js/amplitude.min.js +++ b/player/js/amplitude.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Amplitude",[],t):"object"==typeof exports?exports.Amplitude=t():e.Amplitude=t()}(this,function(){return function(e){function t(l){if(a[l])return a[l].exports;var u=a[l]={i:l,l:!1,exports:{}};return e[l].call(u.exports,u,u.exports,t),u.l=!0,u.exports}var a={};return t.m=e,t.c=a,t.i=function(e){return e},t.d=function(e,a,l){t.o(e,a)||Object.defineProperty(e,a,{configurable:!1,enumerable:!0,get:l})},t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(a,"a",a),a},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=47)}([function(e,t,a){"use strict";var l=a(59);e.exports={version:l.version,audio:new Audio,active_metadata:{},active_album:"",active_index:0,active_playlist:null,playback_speed:1,callbacks:{},songs:[],playlists:{},start_song:"",starting_playlist:"",starting_playlist_song:"",repeat:!1,repeat_song:!1,shuffle_list:{},shuffle_on:!1,default_album_art:"",default_playlist_art:"",debug:!1,volume:.5,pre_mute_volume:.5,volume_increment:5,volume_decrement:5,soundcloud_client:"",soundcloud_use_art:!1,soundcloud_song_count:0,soundcloud_songs_ready:0,is_touch_moving:!1,buffered:0,bindings:{},continue_next:!0,delay:0,player_state:"stopped",web_audio_api_available:!1,context:null,source:null,analyser:null,visualizations:{available:[],active:[],backup:""},waveforms:{sample_rate:100,built:[]}}},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(5),d=(l(i),a(3)),s=(l(d),a(2)),o=(l(s),a(8)),f=(l(o),a(7)),r=l(f),c=a(4),p=l(c),v=a(16),y=l(v),g=function(){function e(){y.default.stop(),y.default.run(),n.default.active_metadata.live&&s(),/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)&&!n.default.paused&&s();var e=n.default.audio.play();void 0!==e&&e.then(function(e){}).catch(function(e){}),n.default.audio.play(),n.default.audio.playbackRate=n.default.playback_speed}function t(){y.default.stop(),n.default.audio.pause(),n.default.paused=!0,n.default.active_metadata.live&&d()}function a(){y.default.stop(),0!=n.default.audio.currentTime&&(n.default.audio.currentTime=0),n.default.audio.pause(),n.default.active_metadata.live&&d(),r.default.run("stop")}function l(e){n.default.audio.muted=0==e,n.default.volume=e,n.default.audio.volume=e/100}function u(e){n.default.active_metadata.live||(n.default.audio.currentTime=n.default.audio.duration*(e/100))}function i(e){n.default.audio.addEventListener("canplaythrough",function(){n.default.audio.duration>=e&&e>0?n.default.audio.currentTime=e:p.default.writeMessage("Amplitude can't skip to a location greater than the duration of the audio or less than 0")},{once:!0})}function d(){n.default.audio.src="",n.default.audio.load()}function s(){n.default.audio.src=n.default.active_metadata.url,n.default.audio.load()}function o(e){n.default.playback_speed=e,n.default.audio.playbackRate=n.default.playback_speed}return{play:e,pause:t,stop:a,setVolume:l,setSongLocation:u,skipToLocation:i,disconnectStream:d,reconnectStream:s,setPlaybackSpeed:o}}();t.default=g,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){t(),a(),l(),n()}function t(){for(var e=u.default.audio.paused?"paused":"playing",t=document.querySelectorAll(".amplitude-play-pause"),a=0;a0&&void 0!==arguments[0]&&arguments[0],t=null,a={},l=!1;n.default.repeat_song?n.default.shuffle_on?(t=n.default.shuffle_list[n.default.active_index].index,a=n.default.shuffle_list[t]):(t=n.default.active_index,a=n.default.songs[t]):n.default.shuffle_on?(parseInt(n.default.active_index)+11&&void 0!==arguments[1]&&arguments[1],a=null,l={},u=!1;n.default.repeat_song?n.default.playlists[e].shuffle?(a=n.default.playlists[e].active_index,l=n.default.playlists[e].shuffle_list[a]):(a=n.default.playlists[e].active_index,l=n.default.playlists[e].songs[a]):n.default.playlists[e].shuffle?(parseInt(n.default.playlists[e].active_index)+1=0?parseInt(n.default.active_index-1):parseInt(n.default.songs.length-1),t=n.default.shuffle_on?n.default.shuffle_list[e]:n.default.songs[e]),u(t,e),d.default.play(),p.default.sync(),o.default.run("prev"),n.default.repeat_song&&o.default.run("song_repeated")}function l(e){var t=null,a={};n.default.repeat_song?n.default.playlists[e].shuffle?(t=n.default.playlists[e].active_index,a=n.default.playlists[e].shuffle_list[t]):(t=n.default.playlists[e].active_index,a=n.default.playlists[e].songs[t]):(t=parseInt(n.default.playlists[e].active_index)-1>=0?parseInt(n.default.playlists[e].active_index-1):parseInt(n.default.playlists[e].songs.length-1),a=n.default.playlists[e].shuffle?n.default.playlists[e].shuffle_list[t]:n.default.playlists[e].songs[t]),c(e),i(e,a,t),d.default.play(),p.default.sync(),o.default.run("prev"),n.default.repeat_song&&o.default.run("song_repeated")}function u(e,t){s(e),n.default.audio.src=e.url,n.default.active_metadata=e,n.default.active_album=e.album,n.default.active_index=parseInt(t),f()}function i(e,t,a){s(t),n.default.audio.src=t.url,n.default.active_metadata=t,n.default.active_album=t.album,n.default.active_index=null,n.default.playlists[e].active_index=parseInt(a),f()}function s(e){d.default.stop(),p.default.syncToPause(),y.default.resetElements(),m.default.resetElements(),h.default.resetCurrentTimes(),r.default.newAlbum(e)&&o.default.run("album_change")}function f(){A.default.displayMetaData(),M.default.setActive(),h.default.resetDurationTimes(),o.default.run("song_change")}function c(e){n.default.active_playlist!=e&&(o.default.run("playlist_changed"),n.default.active_playlist=e,null!=e&&(n.default.playlists[e].active_index=0))}return{setNext:e,setNextPlaylist:t,setPrevious:a,setPreviousPlaylist:l,changeSong:u,changeSongPlaylist:i,setActivePlaylist:c}}();t.default=P,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e){u.default.debug&&console.log(e)}return{writeMessage:e}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e,t){return u.default.active_playlist!=e||(null==u.default.active_playlist&&null==e?u.default.active_index!=t:u.default.active_playlist==e&&u.default.playlists[e].active_index!=t)}function t(e){return u.default.active_album!=e}function a(e){return u.default.active_playlist!=e}function l(e){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(e)}function n(e){return!isNaN(e)&&parseInt(Number(e))==e&&!isNaN(parseInt(e,10))}return{newSong:e,newAlbum:t,newPlaylist:a,isURL:l,isInt:n}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){u.default.audio=new Audio,u.default.active_metadata={},u.default.active_album="",u.default.active_index=0,u.default.active_playlist=null,u.default.playback_speed=1,u.default.callbacks={},u.default.songs=[],u.default.playlists={},u.default.start_song="",u.default.starting_playlist="",u.default.starting_playlist_song="",u.default.repeat=!1,u.default.shuffle_list={},u.default.shuffle_on=!1,u.default.default_album_art="",u.default.default_playlist_art="",u.default.debug=!1,u.default.volume=.5,u.default.pre_mute_volume=.5,u.default.volume_increment=5,u.default.volume_decrement=5,u.default.soundcloud_client="",u.default.soundcloud_use_art=!1,u.default.soundcloud_song_count=0,u.default.soundcloud_songs_ready=0,u.default.continue_next=!0}function t(){u.default.audio.paused&&0==u.default.audio.currentTime&&(u.default.player_state="stopped"),u.default.audio.paused&&u.default.audio.currentTime>0&&(u.default.player_state="paused"),u.default.audio.paused||(u.default.player_state="playing")}return{resetConfig:e,setPlayerState:t}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(4),d=l(i),s=function(){function e(){n.default.audio.addEventListener("abort",function(){t("abort")}),n.default.audio.addEventListener("error",function(){t("error")}),n.default.audio.addEventListener("loadeddata",function(){t("loadeddata")}),n.default.audio.addEventListener("loadedmetadata",function(){t("loadedmetadata")}),n.default.audio.addEventListener("loadstart",function(){t("loadstart")}),n.default.audio.addEventListener("pause",function(){t("pause")}),n.default.audio.addEventListener("playing",function(){t("playing")}),n.default.audio.addEventListener("play",function(){t("play")}),n.default.audio.addEventListener("progress",function(){t("progress")}),n.default.audio.addEventListener("ratechange",function(){t("ratechange")}),n.default.audio.addEventListener("seeked",function(){t("seeked")}),n.default.audio.addEventListener("seeking",function(){t("seeking")}),n.default.audio.addEventListener("stalled",function(){t("stalled")}),n.default.audio.addEventListener("suspend",function(){t("suspend")}),n.default.audio.addEventListener("timeupdate",function(){t("timeupdate")}),n.default.audio.addEventListener("volumechange",function(){t("volumechange")}),n.default.audio.addEventListener("waiting",function(){t("waiting")}),n.default.audio.addEventListener("canplay",function(){t("canplay")}),n.default.audio.addEventListener("canplaythrough",function(){t("canplaythrough")}),n.default.audio.addEventListener("durationchange",function(){t("durationchange")}),n.default.audio.addEventListener("ended",function(){t("ended")})}function t(e){if(n.default.callbacks[e]){var t=n.default.callbacks[e];d.default.writeMessage("Running Callback: "+e);try{t()}catch(e){if("CANCEL EVENT"==e.message)throw e;d.default.writeMessage("Callback error: "+e.message)}}}return{initialize:e,run:t}}();t.default=s,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],t=document.querySelectorAll("[data-amplitude-song-info]"),a=0;a=0?(d=d||u.default.default_album_art,t[a].setAttribute("src",d)):(d=d||"",t[a].innerHTML=d)}}}function t(){for(var e=["image_url"],t=document.querySelectorAll("[data-amplitude-playlist-info]"),a=0;a=0?t[a].setAttribute("src",u.default.playlists[n][l]):t[a].innerHTML=u.default.playlists[n][l]:e.indexOf(l)>=0?""!=u.default.default_playlist_art?t[a].setAttribute("src",u.default.default_playlist_art):t[a].setAttribute("src",""):t[a].innerHTML=""}}function a(e,t){for(var a=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],l=document.querySelectorAll('[data-amplitude-song-info][data-amplitude-playlist="'+t+'"]'),u=0;u=0?l[u].setAttribute("src",e[n]):l[u].innerHTML=e[n]:a.indexOf(n)>=0?""!=e.default_album_art?l[u].setAttribute("src",e.default_album_art):l[u].setAttribute("src",""):l[u].innerHTML="")}}function l(){for(var e=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],a=document.querySelectorAll("[data-amplitude-song-info]"),l=0;l=0?(s=s||u.default.default_album_art,a[l].setAttribute("src",s)):a[l].innerHTML=s}if(null!=n&&null!=i){var o=a[l].getAttribute("data-amplitude-song-info");void 0!=u.default.playlists[i].songs[n][o]&&(e.indexOf(o)>=0?a[l].setAttribute("src",u.default.playlists[i].songs[n][o]):a[l].innerHTML=u.default.playlists[i].songs[n][o])}}t()}return{displayMetaData:e,setFirstSongInPlaylist:a,syncMetaData:l,displayPlaylistMetaData:t}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=document.getElementsByClassName("amplitude-repeat"),t=0;t0;a--){d(e,a,Math.floor(Math.random()*u.default.songs.length+1)-1)}u.default.shuffle_list=e}function i(e){for(var t=new Array(u.default.playlists[e].songs.length),a=0;a0;l--){d(t,l,Math.floor(Math.random()*u.default.playlists[e].songs.length+1)-1)}u.default.playlists[e].shuffle_list=t}function d(e,t,a){var l=e[t];e[t]=e[a],e[a]=l}return{setShuffle:e,toggleShuffle:t,setShufflePlaylist:a,toggleShufflePlaylist:l,shuffleSongs:n,shufflePlaylistSongs:i}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e,u,i){t(e),a(e,u),l(e,i),n(e,u)}function t(e){e=isNaN(e)?0:e;for(var t=document.querySelectorAll(".amplitude-song-slider"),a=0;a0&&e.length>0)for(var i=0;i0?Object.keys(n.default.visualizations.available)[0]:null;null!=l&&i(l,e)}}function a(e,t){if(t==n.default.active_playlist){var a=n.default.playlists[n.default.active_playlist].songs[n.default.playlists[n.default.active_playlist].active_index].visualization,l=n.default.playlists[n.default.active_playlist].visualization,u=n.default.visualization;if(void 0!=a&&void 0!=n.default.visualizations.available[a])i(a,e);else if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else if(void 0!=u&&void 0!=n.default.visualizations.available[u])i(u,e);else{var d=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=d&&i(d,e)}}}function l(e,t){if(t==n.default.active_index){var a=n.default.songs[n.default.active_index].visualization,l=n.default.visualization;if(void 0!=a&&void 0!=n.default.visualizations.available[a])i(a,e);else if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else{var u=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=u&&i(u,e)}}}function u(e,t,a){if(t==n.default.active_playlist&&n.default.playlists[t].active_index==a){var l=n.default.playlists[n.default.active_playlist].songs[n.default.playlists[n.default.active_playlist].active_index].visualization,u=n.default.playlists[n.default.active_playlist].visualization,d=n.default.visualization;if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else if(void 0!=u&&void 0!=n.default.visualizations.available[u])i(u,e);else if(void 0!=d&&void 0!=n.default.visualizations.available[d])i(d,e);else{var s=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=s&&i(s,e)}}}function i(e,t){var a=new n.default.visualizations.available[e].object;a.setPreferences(n.default.visualizations.available[e].preferences),a.startVisualization(t),n.default.visualizations.active.push(a)}function d(){for(var e=0;e0)for(var t=0;t3&&void 0!==arguments[3]&&arguments[3];SC.get("/resolve/?url="+e,function(e){e.streamable?null!=t?(n.default.playlists[t].songs[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client,l&&(n.default.playlists[t].shuffle_list[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client),n.default.soundcloud_use_art&&(n.default.playlists[t].songs[a].cover_art_url=e.artwork_url,l&&(n.default.playlists[t].shuffle_list[a].cover_art_url=e.artwork_url)),n.default.playlists[t].songs[a].soundcloud_data=e,l&&(n.default.playlists[t].shuffle_list[a].soundcloud_data=e)):(n.default.songs[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client,l&&(n.default.shuffle_list[a].stream_url,n.default.soundcloud_client),n.default.soundcloud_use_art&&(n.default.songs[a].cover_art_url=e.artwork_url,l&&(n.default.shuffle_list[a].cover_art_url=e.artwork_url)),n.default.songs[a].soundcloud_data=e,l&&(n.default.shuffle_list[a].soundcloud_data=e)):null!=t?AmplitudeHelpers.writeDebugMessage(n.default.playlists[t].songs[a].name+" by "+n.default.playlists[t].songs[a].artist+" is not streamable by the Soundcloud API"):AmplitudeHelpers.writeDebugMessage(n.default.songs[a].name+" by "+n.default.songs[a].artist+" is not streamable by the Soundcloud API")})}function u(e,t){SC.get("/resolve/?url="+e,function(e){e.streamable?(n.default.songs[t].url=e.stream_url+"?client_id="+n.default.soundcloud_client,n.default.soundcloud_use_art&&(n.default.songs[t].cover_art_url=e.artwork_url),n.default.songs[t].soundcloud_data=e):AmplitudeHelpers.writeDebugMessage(n.default.songs[t].name+" by "+n.default.songs[t].artist+" is not streamable by the Soundcloud API"),++n.default.soundcloud_songs_ready==n.default.soundcloud_song_count&&d.default.setConfig(s)})}function i(e){var t=/^https?:\/\/(soundcloud.com|snd.sc)\/(.*)$/;return e.match(t)}var s={};return{loadSoundCloud:e,resolveIndividualStreamableURL:l,isSoundCloudURL:i}}();t.default=s,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=document.getElementsByClassName("amplitude-playback-speed"),t=0;t0))for(var u=0;u0&&C.default.initialize(e.playlists),void 0!=e.start_song&&e.starting_playlist?g.default.isInt(e.start_song)?E.default.changeSong(i.default.songs[e.start_song],e.start_song):v.default.writeMessage("You must enter an integer index for the start song."):E.default.changeSong(i.default.songs[0],0),void 0!=e.shuffle_on&&e.shuffle_on&&(i.default.shuffle_on=!0,_.default.shuffleSongs(),E.default.changeSong(i.default.shuffle_list[0],0)),i.default.continue_next=void 0==e.continue_next||e.continue_next,i.default.playback_speed=void 0!=e.playback_speed?e.playback_speed:1,s.default.setPlaybackSpeed(i.default.playback_speed),i.default.audio.preload=void 0!=e.preload?e.preload:"auto",i.default.callbacks=void 0!=e.callbacks?e.callbacks:{},i.default.bindings=void 0!=e.bindings?e.bindings:{},i.default.volume=void 0!=e.volume?e.volume:50,i.default.delay=void 0!=e.delay?e.delay:0,i.default.volume_increment=void 0!=e.volume_increment?e.volume_increment:5,i.default.volume_decrement=void 0!=e.volume_decrement?e.volume_decrement:5,s.default.setVolume(i.default.volume),l(e),n(),void 0!=e.starting_playlist&&""!=e.starting_playlist&&(i.default.active_playlist=e.starting_playlist,void 0!=e.starting_playlist_song&&""!=e.starting_playlist_song?void 0!=u(e.playlists[e.starting_playlist].songs[parseInt(e.starting_playlist_song)])?E.default.changeSongPlaylist(i.default.active_playlist,e.playlists[e.starting_playlist].songs[parseInt(e.starting_playlist_song)],parseInt(e.starting_playlist_song)):(E.default.changeSongPlaylist(i.default.active_playlist,e.playlists[e.starting_playlist].songs[0],0),v.default.writeMessage("The index of "+e.starting_playlist_song+" does not exist in the playlist "+e.starting_playlist)):E.default.changeSong(i.default.active_playlist,e.playlists[e.starting_playlist].songs[0],0),V.default.sync()),T.default.run("initialized")}function l(e){void 0!=e.default_album_art?i.default.default_album_art=e.default_album_art:i.default.default_album_art="",void 0!=e.default_playlist_art?i.default.default_playlist_art=e.default_playlist_art:i.default.default_playlist_art=""}function n(){I.default.syncMain(),q.default.setMuted(0==i.default.volume),H.default.sync(),G.default.sync(),D.default.resetCurrentTimes(),V.default.syncToPause(),F.default.syncMetaData(),X.default.syncRepeatSong()}function d(e){var t=0,a=void 0;for(a in e)e.hasOwnProperty(a)&&t++;return v.default.writeMessage("You have "+t+" playlist(s) in your config"),t}function o(){for(var e=0;e0)for(var t=0;tp&&(p=y),yn[2*o])&&(n[2*o]=p),(0===i||c0}var r="",c=u.default.waveforms.sample_rate,p="";return{init:e,build:t,determineIfUsingWaveforms:f}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){var e={},t=(Math.floor(u.default.audio.currentTime%60)<10?"0":"")+Math.floor(u.default.audio.currentTime%60),a=Math.floor(u.default.audio.currentTime/60),l="00";return a<10&&(a="0"+a),a>=60&&(l=Math.floor(a/60),(a%=60)<10&&(a="0"+a)),e.seconds=t,e.minutes=a,e.hours=l,e}function t(){var e={},t=(Math.floor(u.default.audio.duration%60)<10?"0":"")+Math.floor(u.default.audio.duration%60),a=Math.floor(u.default.audio.duration/60),l="00";return a<10&&(a="0"+a),a>=60&&(l=Math.floor(a/60),(a%=60)<10&&(a="0"+a)),e.seconds=isNaN(t)?"00":t,e.minutes=isNaN(a)?"00":a,e.hours=isNaN(l)?"00":l.toString(),e}function a(){return u.default.audio.currentTime/u.default.audio.duration*100}function l(e){u.default.active_metadata.live||isFinite(e)&&(u.default.audio.currentTime=e)}return{computeCurrentTimes:e,computeSongDuration:t,computeSongCompletionPercentage:a,setCurrentTime:l}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){t(),a(),l(),n()}function t(){for(var e=document.getElementsByClassName("amplitude-buffered-progress"),t=0;t0||navigator.userAgent.match(/Trident.*rv\:11\./)?(a[l].removeEventListener("change",k.default.handle),a[l].addEventListener("change",k.default.handle)):(a[l].removeEventListener("input",k.default.handle),a[l].addEventListener("input",k.default.handle))}function x(){for(var e=window.navigator.userAgent,t=e.indexOf("MSIE "),a=document.getElementsByClassName("amplitude-volume-slider"),l=0;l0||navigator.userAgent.match(/Trident.*rv\:11\./)?(a[l].removeEventListener("change",O.default.handle),a[l].addEventListener("change",O.default.handle)):(a[l].removeEventListener("input",O.default.handle),a[l].addEventListener("input",O.default.handle))}function P(){for(var e=document.getElementsByClassName("amplitude-next"),t=0;t=0){var e=n.default.audio.buffered.end(n.default.audio.buffered.length-1),t=n.default.audio.duration;n.default.buffered=e/t*100}d.default.sync()}return{handle:e}}();t.default=s,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(12),d=l(i),s=a(9),o=l(s),f=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist");null==e&&t(),null!=e&&a(e)}}function t(){d.default.setRepeat(!n.default.repeat),o.default.syncRepeat()}function a(e){d.default.setRepeatPlaylist(!n.default.playlists[e].repeat,e),o.default.syncRepeatPlaylist(e)}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(12),d=l(i),s=a(9),o=l(s),f=function(){function e(){n.default.is_touch_moving||(d.default.setRepeatSong(!n.default.repeat_song),o.default.syncRepeatSong())}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(13),d=l(i),s=a(19),o=l(s),f=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist");null==e?t():a(e)}}function t(){d.default.toggleShuffle(),o.default.syncMain(n.default.shuffle_on)}function a(e){d.default.toggleShufflePlaylist(e),o.default.syncPlaylist(e)}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(4),d=l(i),s=a(3),o=l(s),f=a(5),r=l(f),c=a(1),p=l(c),v=a(2),y=l(v),g=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist"),l=this.getAttribute("data-amplitude-song-index"),u=this.getAttribute("data-amplitude-location");null==u&&d.default.writeMessage("You must add an 'data-amplitude-location' attribute in seconds to your 'amplitude-skip-to' element."),null==l&&d.default.writeMessage("You must add an 'data-amplitude-song-index' attribute to your 'amplitude-skip-to' element."),null!=u&&null!=l&&(null==e?t(parseInt(l),parseInt(u)):a(e,parseInt(l),parseInt(u)))}}function t(e,t){o.default.changeSong(n.default.songs[e],e),p.default.play(),y.default.syncGlobal(),y.default.syncSong(),p.default.skipToLocation(t)}function a(e,t,a){r.default.newPlaylist(e)&&o.default.setActivePlaylist(e),o.default.changeSongPlaylist(e,n.default.playlists[e].songs[t],t),p.default.play(),y.default.syncGlobal(),y.default.syncPlaylist(),y.default.syncSong(),p.default.skipToLocation(a)}return{handle:e}}();t.default=g,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(23),d=l(i),s=a(14),o=l(s),f=function(){function e(){var e=this.value,i=n.default.audio.duration*(e/100),d=this.getAttribute("data-amplitude-playlist"),s=this.getAttribute("data-amplitude-song-index");null==d&&null==s&&t(i,e),null!=d&&null==s&&a(i,e,d),null==d&&null!=s&&l(i,e,s),null!=d&&null!=s&&u(i,e,d,s)}function t(e,t){n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,n.default.active_playlist,n.default.active_index))}function a(e,t,a){n.default.active_playlist==a&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,a,n.default.active_index)))}function l(e,t,a){n.default.active_index==a&&null==n.default.active_playlist&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,n.default.active_playlist,a)))}function u(e,t,a,l){n.default.playlists[a].active_index==l&&n.default.active_playlist==a&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,a,l)))}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(6),d=l(i),s=a(2),o=l(s),f=a(1),r=l(f),c=function(){function e(){n.default.is_touch_moving||(o.default.syncToPause(),r.default.stop(),d.default.setPlayerState())}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(24),d=l(i),s=a(15),o=l(s),f=a(14),r=l(f),c=a(20),p=l(c),v=a(23),y=l(v),g=a(7),m=(l(g),function(){function e(){t(),d.default.sync(),a(),l()}function t(){if(n.default.audio.buffered.length-1>=0){var e=n.default.audio.buffered.end(n.default.audio.buffered.length-1),t=n.default.audio.duration;n.default.buffered=e/t*100}}function a(){if(!n.default.active_metadata.live){var e=y.default.computeCurrentTimes(),t=y.default.computeSongCompletionPercentage(),a=y.default.computeSongDuration();o.default.syncCurrentTimes(e),r.default.sync(t,n.default.active_playlist,n.default.active_index),p.default.sync(t),o.default.syncDurationTimes(e,a)}}function l(){var e=Math.floor(n.default.audio.currentTime);if(void 0!=n.default.active_metadata.time_callbacks&&void 0!=n.default.active_metadata.time_callbacks[e])n.default.active_metadata.time_callbacks[e].run||(n.default.active_metadata.time_callbacks[e].run=!0,n.default.active_metadata.time_callbacks[e]());else for(var t in n.default.active_metadata.time_callbacks)n.default.active_metadata.time_callbacks.hasOwnProperty(t)&&(n.default.active_metadata.time_callbacks[t].run=!1)}return{handle:e}}());t.default=m,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){if(!n.default.is_touch_moving){var e=null;e=n.default.volume-n.default.volume_increment>0?n.default.volume-n.default.volume_increment:0,d.default.setVolume(e),o.default.setMuted(0==n.default.volume),r.default.sync()}}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){d.default.setVolume(this.value),o.default.setMuted(0==n.default.volume),r.default.sync()}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){if(!n.default.is_touch_moving){var e=null;e=n.default.volume+n.default.volume_increment<=100?n.default.volume+n.default.volume_increment:100,d.default.setVolume(e),o.default.setMuted(0==n.default.volume),r.default.sync()}}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){var e=window.AudioContext||window.webkitAudioContext||window.mozAudioContext||window.oAudioContext||window.msAudioContext;e?(u.default.context=new e,u.default.analyser=u.default.context.createAnalyser(),u.default.audio.crossOrigin="anonymous",u.default.source=u.default.context.createMediaElementSource(u.default.audio),u.default.source.connect(u.default.analyser),u.default.analyser.connect(u.default.context.destination)):AmplitudeHelpers.writeDebugMessage("Web Audio API is unavailable! We will set any of your visualizations with your back up definition!")}function t(){var e=window.AudioContext||window.webkitAudioContext||window.mozAudioContext||window.oAudioContext||window.msAudioContext;return u.default.web_audio_api_available=!1,e?(u.default.web_audio_api_available=!0,!0):(u.default.web_audio_api_available=!1,!1)}function a(){var e=document.querySelectorAll(".amplitude-wave-form"),t=document.querySelectorAll(".amplitude-visualization");return e.length>0||t.length>0}return{configureWebAudioAPI:e,webAudioAPIAvailable:t,determineUsingAnyFX:a}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(21),n=l(u),i=a(0),d=l(i),s=a(1),o=l(s),f=a(13),r=l(f),c=a(6),p=l(c),v=a(3),y=l(v),g=a(12),m=l(g),_=a(5),h=l(_),b=a(16),A=l(b),x=a(19),M=l(x),P=a(9),S=l(P),L=a(14),w=l(L),E=a(20),k=l(E),T=a(15),O=l(T),C=a(2),j=l(C),I=a(8),N=l(I),q=a(18),z=l(q),H=a(4),B=l(H),D=a(17),R=l(D),V=function(){function e(e){n.default.initialize(e)}function t(){return d.default}function a(){n.default.rebindDisplay()}function l(){return d.default.active_playlist}function u(){return d.default.playback_speed}function i(e){o.default.setPlaybackSpeed(e),z.default.sync()}function s(){return d.default.repeat}function f(e){return d.default.playlists[e].repeat}function c(){return d.default.shuffle_on}function v(e){return d.default.playlists[e].shuffle}function g(e){r.default.setShuffle(e),M.default.syncMain()}function _(e,t){r.default.setShufflePlaylist(e,t),M.default.syncMain(),M.default.syncPlaylist(e)}function b(e){m.default.setRepeat(e),S.default.syncRepeat()}function x(e,t){m.default.setRepeatPlaylist(t,e),S.default.syncRepeatPlaylist(e)}function P(e){d.default.is_touch_moving||(m.default.setRepeatSong(!d.default.repeat_song),S.default.syncRepeatSong())}function L(){return d.default.default_album_art}function E(){return d.default.default_playlist_art}function T(e){d.default.default_album_art=e}function C(e){d.default.default_plalist_art=e}function I(){return d.default.audio.currentTime/d.default.audio.duration*100}function q(){return d.default.audio.currentTime}function H(){return d.default.audio.duration}function D(e){"number"==typeof e&&e>0&&e<100&&(d.default.audio.currentTime=d.default.audio.duration*(e/100))}function V(e){d.default.debug=e}function U(){return d.default.active_metadata}function F(){return d.default.playlists[d.default.active_playlist]}function W(e){return d.default.songs[e]}function G(e,t){return d.default.playlists[e].songs[t]}function Y(e){return void 0==d.default.songs&&(d.default.songs=[]),d.default.songs.push(e),d.default.shuffle_on&&d.default.shuffle_list.push(e),R.default.isSoundCloudURL(e.url)&&R.default.resolveIndividualStreamableURL(e.url,null,d.default.songs.length-1,d.default.shuffle_on),d.default.songs.length-1}function X(e,t){return void 0!=d.default.playlists[t]?(d.default.playlists[t].songs.push(e),d.default.playlists[t].shuffle&&d.default.playlists[t].shuffle_list.push(e),R.default.isSoundCloudURL(e.url)&&R.default.resolveIndividualStreamableURL(e.url,t,d.default.playlists[t].songs.length-1,d.default.playlists[t].shuffle),d.default.playlists[t].songs.length-1):(B.default.writeMessage("Playlist doesn't exist!"),null)}function J(e,t,a){if(void 0==d.default.playlists[e]){d.default.playlists[e]={};var l=["repeat","shuffle","shuffle_list","songs","src"];for(var u in t)l.indexOf(u)<0&&(d.default.playlists[e][u]=t[u]);return d.default.playlists[e].songs=a,d.default.playlists[e].active_index=null,d.default.playlists[e].repeat=!1,d.default.playlists[e].shuffle=!1,d.default.playlists[e].shuffle_list=[],d.default.playlists[e]}return B.default.writeMessage("A playlist already exists with that key!"),null}function $(e){d.default.songs.splice(e,1)}function Q(e,t){void 0!=d.default.playlists[t]&&d.default.playlists[t].songs.splice(e,1)}function K(e){e.url?(d.default.audio.src=e.url,d.default.active_metadata=e,d.default.active_album=e.album):B.default.writeMessage("The song needs to have a URL!"),o.default.play(),j.default.sync(),N.default.displayMetaData(),w.default.resetElements(),k.default.resetElements(),O.default.resetCurrentTimes(),O.default.resetDurationTimes(),p.default.setPlayerState()}function Z(e){o.default.stop(),h.default.newPlaylist(null)&&(y.default.setActivePlaylist(null),y.default.changeSong(d.default.songs[e],e)),h.default.newSong(null,e)&&y.default.changeSong(d.default.songs[e],e),o.default.play(),p.default.setPlayerState(),j.default.sync()}function ee(e,t){o.default.stop(),h.default.newPlaylist(t)&&(y.default.setActivePlaylist(t),y.default.changeSongPlaylist(t,d.default.playlists[t].songs[e],e)),h.default.newSong(t,e)&&y.default.changeSongPlaylist(t,d.default.playlists[t].songs[e],e),j.default.sync(),o.default.play(),p.default.setPlayerState()}function te(){o.default.play(),p.default.setPlayerState()}function ae(){o.default.pause(),p.default.setPlayerState()}function le(){return d.default.audio}function ue(){return d.default.analyser}function ne(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;""==e||null==e?null==d.default.active_playlist||""==d.default.active_playlist?y.default.setNext():y.default.setNextPlaylist(d.default.active_playlist):y.default.setNextPlaylist(e)}function ie(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;""==e||null==e?null==d.default.active_playlist||""==d.default.active_playlist?y.default.setPrevious():y.default.setPreviousPlaylist(d.default.active_playlist):y.default.setPreviousPlaylist(e)}function de(){return d.default.songs}function se(e){return d.default.playlists[e].songs}function oe(){return d.default.shuffle_on?d.default.shuffle_list:d.default.songs}function fe(e){return d.default.playlists[e].shuffle?d.default.playlists[e].shuffle_list:d.default.playlists[e].songs}function re(){return parseInt(d.default.active_index)}function ce(){return d.default.version}function pe(){return d.default.buffered}function ve(e,t){var a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;e=parseInt(e),null!=a?(h.default.newPlaylist(a)&&y.default.setActivePlaylist(a),y.default.changeSongPlaylist(a,d.default.playlists[a].songs[t],t),o.default.play(),j.default.syncGlobal(),j.default.syncPlaylist(),j.default.syncSong(),o.default.skipToLocation(e)):(y.default.changeSong(d.default.songs[t],t),o.default.play(),j.default.syncGlobal(),j.default.syncSong(),o.default.skipToLocation(e))}function ye(e,t){var a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(""!=a&&null!=a&&void 0!=d.default.playlists[a])for(var l in t)t.hasOwnProperty(l)&&"url"!=l&&"URL"!=l&&"live"!=l&&"LIVE"!=l&&(d.default.playlists[a].songs[e][l]=t[l]);else for(var l in t)t.hasOwnProperty(l)&&"url"!=l&&"URL"!=l&&"live"!=l&&"LIVE"!=l&&(d.default.songs[e][l]=t[l]);N.default.displayMetaData(),N.default.syncMetaData()}function ge(e,t){if(void 0!=d.default.playlists[e]){var a=["repeat","shuffle","shuffle_list","songs","src"];for(var l in t)t.hasOwnProperty(l)&&a.indexOf(l)<0&&(d.default.playlists[e][l]=t[l]);N.default.displayPlaylistMetaData()}else B.default.writeMessage("You must provide a valid playlist key!")}function me(e){d.default.delay=e}function _e(){return d.default.delay}function he(){return d.default.player_state}function be(e,t){A.default.register(e,t)}function Ae(e,t){void 0!=d.default.playlists[e]?void 0!=d.default.visualizations.available[t]?d.default.playlists[e].visualization=t:B.default.writeMessage("A visualization does not exist for the key provided."):B.default.writeMessage("The playlist for the key provided does not exist")}function xe(e,t){d.default.songs[e]?void 0!=d.default.visualizations.available[t]?d.default.songs[e].visualization=t:B.default.writeMessage("A visualization does not exist for the key provided."):B.default.writeMessage("A song at that index is undefined")}function Me(e,t,a){void 0!=d.default.playlists[e].songs[t]?void 0!=d.default.visualizations.available[a]?d.default.playlists[e].songs[t].visualization=a:B.default.writeMessage("A visualization does not exist for the key provided."):B.default.writeMessage("The song in the playlist at that key is not defined")}function Pe(e){void 0!=d.default.visualizations.available[e]?d.default.visualization=e:B.default.writeMessage("A visualization does not exist for the key provided.")}function Se(e){o.default.setVolume(e)}function Le(){return d.default.volume}return{init:e,getConfig:t,bindNewElements:a,getActivePlaylist:l,getPlaybackSpeed:u,setPlaybackSpeed:i,getRepeat:s,getRepeatPlaylist:f,getShuffle:c,getShufflePlaylist:v,setShuffle:g,setShufflePlaylist:_,setRepeat:b,setRepeatSong:P,setRepeatPlaylist:x,getDefaultAlbumArt:L,setDefaultAlbumArt:T,getDefaultPlaylistArt:E,setDefaultPlaylistArt:C,getSongPlayedPercentage:I,setSongPlayedPercentage:D,getSongPlayedSeconds:q,getSongDuration:H,setDebug:V,getActiveSongMetadata:U,getActivePlaylistMetadata:F,getSongAtIndex:W,getSongAtPlaylistIndex:G,addSong:Y,addSongToPlaylist:X,removeSong:$,removeSongFromPlaylist:Q,playNow:K,playSongAtIndex:Z,playPlaylistSongAtIndex:ee,play:te,pause:ae,getAudio:le,getAnalyser:ue,next:ne,prev:ie,getSongs:de,getSongsInPlaylist:se,getSongsState:oe,getSongsStatePlaylist:fe,getActiveIndex:re,getVersion:ce,getBuffered:pe,skipTo:ve,setSongMetaData:ye,setPlaylistMetaData:ge,setDelay:me,getDelay:_e,getPlayerState:he,addPlaylist:J,registerVisualization:be,setPlaylistVisualization:Ae,setSongVisualization:xe,setSongInPlaylistVisualization:Me,setGlobalVisualization:Pe,getVolume:Le,setVolume:Se}}();t.default=V,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(4),d=l(i),s=a(5),o=l(s),f=a(8),r=l(f),c=a(17),p=l(c),v=function(){function e(e){n.default.playlists=e,a(),l(),t(),u(),i(),s(),f()}function t(){for(var e in n.default.playlists)n.default.playlists[e].active_index=null}function a(){for(var e in n.default.playlists)if(n.default.playlists.hasOwnProperty(e)&&n.default.playlists[e].songs)for(var t=0;t0&&(a=e.hours+":"+a);for(var l=0;l0&&(a=e.hours+":"+a);for(var l=0;l0&&(a=e.hours+":"+a);for(var l=0;l0&&(l=e.hours+":"+l);for(var n=0;n0&&(a=i+":"+a)}return a}return{sync:e,resetTimes:i}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e){t(e),a(e),l(e),n(e)}function t(e){for(var t=document.querySelectorAll(".amplitude-duration-hours"),a=0;a0&&(t=e.hours+":"+t)),t}return{sync:e,resetTimes:i}}();t.default=n,e.exports=t.default},function(e,t){e.exports={name:"amplitudejs",version:"5.0.3",description:"A JavaScript library that allows you to control the design of your media controls in your webpage -- not the browser. No dependencies (jQuery not required) https://521dimensions.com/open-source/amplitudejs",main:"dist/amplitude.js",devDependencies:{"babel-core":"^6.26.3","babel-loader":"^7.1.5","babel-plugin-add-module-exports":"0.2.1","babel-polyfill":"^6.26.0","babel-preset-es2015":"^6.18.0",husky:"^1.3.1",jest:"^23.6.0",prettier:"1.15.1","pretty-quick":"^1.11.1",watch:"^1.0.2",webpack:"^2.7.0"},directories:{doc:"docs"},files:["dist"],funding:{type:"opencollective",url:"https://opencollective.com/amplitudejs"},scripts:{build:"node_modules/.bin/webpack",watch:"watch 'node_modules/.bin/webpack' dist",prettier:"npx pretty-quick",test:"jest"},repository:{type:"git",url:"git+https://github.com/521dimensions/amplitudejs.git"},keywords:["webaudio","html5","javascript","audio-player"],author:"521 Dimensions (https://521dimensions.com)",license:"MIT",bugs:{url:"https://github.com/521dimensions/amplitudejs/issues"},homepage:"https://github.com/521dimensions/amplitudejs#readme"}}])}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Amplitude",[],t):"object"==typeof exports?exports.Amplitude=t():e.Amplitude=t()}(this,function(){return function(e){function t(l){if(a[l])return a[l].exports;var u=a[l]={i:l,l:!1,exports:{}};return e[l].call(u.exports,u,u.exports,t),u.l=!0,u.exports}var a={};return t.m=e,t.c=a,t.i=function(e){return e},t.d=function(e,a,l){t.o(e,a)||Object.defineProperty(e,a,{configurable:!1,enumerable:!0,get:l})},t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(a,"a",a),a},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=47)}([function(e,t,a){"use strict";var l=a(59);e.exports={version:l.version,audio:new Audio,active_metadata:{},active_album:"",active_index:0,active_playlist:null,playback_speed:1,callbacks:{},songs:[],playlists:{},start_song:"",starting_playlist:"",starting_playlist_song:"",repeat:!1,repeat_song:!1,shuffle_list:{},shuffle_on:!1,default_album_art:"",default_playlist_art:"",debug:!1,volume:.5,pre_mute_volume:.5,volume_increment:5,volume_decrement:5,soundcloud_client:"",soundcloud_use_art:!1,soundcloud_song_count:0,soundcloud_songs_ready:0,is_touch_moving:!1,buffered:0,bindings:{},continue_next:!0,delay:0,player_state:"stopped",web_audio_api_available:!1,context:null,source:null,analyser:null,visualizations:{available:[],active:[],backup:""},waveforms:{sample_rate:100,built:[]}}},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(5),d=(l(i),a(3)),s=(l(d),a(2)),o=(l(s),a(7)),f=(l(o),a(9)),r=l(f),c=a(4),p=l(c),v=a(16),y=l(v),g=a(6),m=l(g),_=function(){function e(){y.default.stop(),y.default.run(),n.default.active_metadata.live&&s(),/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)&&!n.default.paused&&s();var e=n.default.audio.play();void 0!==e&&e.then(function(e){}).catch(function(e){}),n.default.audio.play(),n.default.audio.playbackRate=n.default.playback_speed,m.default.setPlayerState()}function t(){y.default.stop(),n.default.audio.pause(),n.default.paused=!0,n.default.active_metadata.live&&d(),m.default.setPlayerState()}function a(){y.default.stop(),0!=n.default.audio.currentTime&&(n.default.audio.currentTime=0),n.default.audio.pause(),n.default.active_metadata.live&&d(),m.default.setPlayerState(),r.default.run("stop")}function l(e){n.default.audio.muted=0==e,n.default.volume=e,n.default.audio.volume=e/100}function u(e){n.default.active_metadata.live||(n.default.audio.currentTime=n.default.audio.duration*(e/100))}function i(e){n.default.audio.addEventListener("canplaythrough",function(){n.default.audio.duration>=e&&e>0?n.default.audio.currentTime=e:p.default.writeMessage("Amplitude can't skip to a location greater than the duration of the audio or less than 0")},{once:!0})}function d(){n.default.audio.src="",n.default.audio.load()}function s(){n.default.audio.src=n.default.active_metadata.url,n.default.audio.load()}function o(e){n.default.playback_speed=e,n.default.audio.playbackRate=n.default.playback_speed}return{play:e,pause:t,stop:a,setVolume:l,setSongLocation:u,skipToLocation:i,disconnectStream:d,reconnectStream:s,setPlaybackSpeed:o}}();t.default=_,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){t(),a(),l(),n()}function t(){for(var e=u.default.audio.paused?"paused":"playing",t=document.querySelectorAll(".amplitude-play-pause"),a=0;a0&&void 0!==arguments[0]&&arguments[0],t=null,a={},l=!1;n.default.repeat_song?n.default.shuffle_on?(t=n.default.shuffle_list[n.default.active_index].index,a=n.default.shuffle_list[t]):(t=n.default.active_index,a=n.default.songs[t]):n.default.shuffle_on?(parseInt(n.default.active_index)+11&&void 0!==arguments[1]&&arguments[1],a=null,l={},u=!1;n.default.repeat_song?n.default.playlists[e].shuffle?(a=n.default.playlists[e].active_index,l=n.default.playlists[e].shuffle_list[a]):(a=n.default.playlists[e].active_index,l=n.default.playlists[e].songs[a]):n.default.playlists[e].shuffle?(parseInt(n.default.playlists[e].active_index)+1=0?parseInt(n.default.active_index-1):parseInt(n.default.songs.length-1),t=n.default.shuffle_on?n.default.shuffle_list[e]:n.default.songs[e]),u(t,e),d.default.play(),p.default.sync(),o.default.run("prev"),n.default.repeat_song&&o.default.run("song_repeated")}function l(e){var t=null,a={};n.default.repeat_song?n.default.playlists[e].shuffle?(t=n.default.playlists[e].active_index,a=n.default.playlists[e].shuffle_list[t]):(t=n.default.playlists[e].active_index,a=n.default.playlists[e].songs[t]):(t=parseInt(n.default.playlists[e].active_index)-1>=0?parseInt(n.default.playlists[e].active_index-1):parseInt(n.default.playlists[e].songs.length-1),a=n.default.playlists[e].shuffle?n.default.playlists[e].shuffle_list[t]:n.default.playlists[e].songs[t]),c(e),i(e,a,t),d.default.play(),p.default.sync(),o.default.run("prev"),n.default.repeat_song&&o.default.run("song_repeated")}function u(e,t){var a=arguments.length>2&&void 0!==arguments[2]&&arguments[2];s(e),n.default.audio.src=e.url,n.default.active_metadata=e,n.default.active_album=e.album,n.default.active_index=parseInt(t),f(a)}function i(e,t,a){var l=arguments.length>3&&void 0!==arguments[3]&&arguments[3];s(t),n.default.audio.src=t.url,n.default.active_metadata=t,n.default.active_album=t.album,n.default.active_index=null,n.default.playlists[e].active_index=parseInt(a),f(l)}function s(e){d.default.stop(),p.default.syncToPause(),y.default.resetElements(),m.default.resetElements(),h.default.resetCurrentTimes(),r.default.newAlbum(e)&&o.default.run("album_change")}function f(e){A.default.displayMetaData(),M.default.setActive(e),h.default.resetDurationTimes(),o.default.run("song_change")}function c(e){n.default.active_playlist!=e&&(o.default.run("playlist_changed"),n.default.active_playlist=e,null!=e&&(n.default.playlists[e].active_index=0))}return{setNext:e,setNextPlaylist:t,setPrevious:a,setPreviousPlaylist:l,changeSong:u,changeSongPlaylist:i,setActivePlaylist:c}}();t.default=P,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e){u.default.debug&&console.log(e)}return{writeMessage:e}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e,t){return u.default.active_playlist!=e||(null==u.default.active_playlist&&null==e?u.default.active_index!=t:u.default.active_playlist==e&&u.default.playlists[e].active_index!=t)}function t(e){return u.default.active_album!=e}function a(e){return u.default.active_playlist!=e}function l(e){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(e)}function n(e){return!isNaN(e)&&parseInt(Number(e))==e&&!isNaN(parseInt(e,10))}return{newSong:e,newAlbum:t,newPlaylist:a,isURL:l,isInt:n}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){u.default.audio=new Audio,u.default.active_metadata={},u.default.active_album="",u.default.active_index=0,u.default.active_playlist=null,u.default.playback_speed=1,u.default.callbacks={},u.default.songs=[],u.default.playlists={},u.default.start_song="",u.default.starting_playlist="",u.default.starting_playlist_song="",u.default.repeat=!1,u.default.shuffle_list={},u.default.shuffle_on=!1,u.default.default_album_art="",u.default.default_playlist_art="",u.default.debug=!1,u.default.volume=.5,u.default.pre_mute_volume=.5,u.default.volume_increment=5,u.default.volume_decrement=5,u.default.soundcloud_client="",u.default.soundcloud_use_art=!1,u.default.soundcloud_song_count=0,u.default.soundcloud_songs_ready=0,u.default.continue_next=!0}function t(){u.default.audio.paused&&0==u.default.audio.currentTime&&(u.default.player_state="stopped"),u.default.audio.paused&&u.default.audio.currentTime>0&&(u.default.player_state="paused"),u.default.audio.paused||(u.default.player_state="playing")}return{resetConfig:e,setPlayerState:t}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],t=document.querySelectorAll("[data-amplitude-song-info]"),a=0;a=0?(d=d||u.default.default_album_art,t[a].setAttribute("src",d)):(d=d||"",t[a].innerHTML=d)}}}function t(){for(var e=["image_url"],t=document.querySelectorAll("[data-amplitude-playlist-info]"),a=0;a=0?t[a].setAttribute("src",u.default.playlists[n][l]):t[a].innerHTML=u.default.playlists[n][l]:e.indexOf(l)>=0?""!=u.default.default_playlist_art?t[a].setAttribute("src",u.default.default_playlist_art):t[a].setAttribute("src",""):t[a].innerHTML=""}}function a(e,t){for(var a=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],l=document.querySelectorAll('[data-amplitude-song-info][data-amplitude-playlist="'+t+'"]'),u=0;u=0?l[u].setAttribute("src",e[n]):l[u].innerHTML=e[n]:a.indexOf(n)>=0?""!=e.default_album_art?l[u].setAttribute("src",e.default_album_art):l[u].setAttribute("src",""):l[u].innerHTML="")}}function l(){for(var e=["cover_art_url","station_art_url","podcast_episode_cover_art_url"],a=document.querySelectorAll("[data-amplitude-song-info]"),l=0;l=0?(s=s||u.default.default_album_art,a[l].setAttribute("src",s)):a[l].innerHTML=s}if(null!=n&&null!=i){var o=a[l].getAttribute("data-amplitude-song-info");void 0!=u.default.playlists[i].songs[n][o]&&(e.indexOf(o)>=0?a[l].setAttribute("src",u.default.playlists[i].songs[n][o]):a[l].innerHTML=u.default.playlists[i].songs[n][o])}}t()}return{displayMetaData:e,setFirstSongInPlaylist:a,syncMetaData:l,displayPlaylistMetaData:t}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=document.getElementsByClassName("amplitude-repeat"),t=0;t0;a--){d(e,a,Math.floor(Math.random()*u.default.songs.length+1)-1)}u.default.shuffle_list=e}function i(e){for(var t=new Array(u.default.playlists[e].songs.length),a=0;a0;l--){d(t,l,Math.floor(Math.random()*u.default.playlists[e].songs.length+1)-1)}u.default.playlists[e].shuffle_list=t}function d(e,t,a){var l=e[t];e[t]=e[a],e[a]=l}return{setShuffle:e,toggleShuffle:t,setShufflePlaylist:a,toggleShufflePlaylist:l,shuffleSongs:n,shufflePlaylistSongs:i}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e,u,i){t(e),a(e,u),l(e,i),n(e,u)}function t(e){e=isNaN(e)?0:e;for(var t=document.querySelectorAll(".amplitude-song-slider"),a=0;a0&&e.length>0)for(var i=0;i0?Object.keys(n.default.visualizations.available)[0]:null;null!=l&&i(l,e)}}function a(e,t){if(t==n.default.active_playlist){var a=n.default.playlists[n.default.active_playlist].songs[n.default.playlists[n.default.active_playlist].active_index].visualization,l=n.default.playlists[n.default.active_playlist].visualization,u=n.default.visualization;if(void 0!=a&&void 0!=n.default.visualizations.available[a])i(a,e);else if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else if(void 0!=u&&void 0!=n.default.visualizations.available[u])i(u,e);else{var d=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=d&&i(d,e)}}}function l(e,t){if(t==n.default.active_index){var a=n.default.songs[n.default.active_index].visualization,l=n.default.visualization;if(void 0!=a&&void 0!=n.default.visualizations.available[a])i(a,e);else if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else{var u=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=u&&i(u,e)}}}function u(e,t,a){if(t==n.default.active_playlist&&n.default.playlists[t].active_index==a){var l=n.default.playlists[n.default.active_playlist].songs[n.default.playlists[n.default.active_playlist].active_index].visualization,u=n.default.playlists[n.default.active_playlist].visualization,d=n.default.visualization;if(void 0!=l&&void 0!=n.default.visualizations.available[l])i(l,e);else if(void 0!=u&&void 0!=n.default.visualizations.available[u])i(u,e);else if(void 0!=d&&void 0!=n.default.visualizations.available[d])i(d,e);else{var s=Object.keys(n.default.visualizations.available).length>0?Object.keys(n.default.visualizations.available)[0]:null;null!=s&&i(s,e)}}}function i(e,t){var a=new n.default.visualizations.available[e].object;a.setPreferences(n.default.visualizations.available[e].preferences),a.startVisualization(t),n.default.visualizations.active.push(a)}function d(){for(var e=0;e0)for(var t=0;t3&&void 0!==arguments[3]&&arguments[3];SC.get("/resolve/?url="+e,function(e){e.streamable?null!=t?(n.default.playlists[t].songs[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client,l&&(n.default.playlists[t].shuffle_list[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client),n.default.soundcloud_use_art&&(n.default.playlists[t].songs[a].cover_art_url=e.artwork_url,l&&(n.default.playlists[t].shuffle_list[a].cover_art_url=e.artwork_url)),n.default.playlists[t].songs[a].soundcloud_data=e,l&&(n.default.playlists[t].shuffle_list[a].soundcloud_data=e)):(n.default.songs[a].url=e.stream_url+"?client_id="+n.default.soundcloud_client,l&&(n.default.shuffle_list[a].stream_url,n.default.soundcloud_client),n.default.soundcloud_use_art&&(n.default.songs[a].cover_art_url=e.artwork_url,l&&(n.default.shuffle_list[a].cover_art_url=e.artwork_url)),n.default.songs[a].soundcloud_data=e,l&&(n.default.shuffle_list[a].soundcloud_data=e)):null!=t?AmplitudeHelpers.writeDebugMessage(n.default.playlists[t].songs[a].name+" by "+n.default.playlists[t].songs[a].artist+" is not streamable by the Soundcloud API"):AmplitudeHelpers.writeDebugMessage(n.default.songs[a].name+" by "+n.default.songs[a].artist+" is not streamable by the Soundcloud API")})}function u(e,t){SC.get("/resolve/?url="+e,function(e){e.streamable?(n.default.songs[t].url=e.stream_url+"?client_id="+n.default.soundcloud_client,n.default.soundcloud_use_art&&(n.default.songs[t].cover_art_url=e.artwork_url),n.default.songs[t].soundcloud_data=e):AmplitudeHelpers.writeDebugMessage(n.default.songs[t].name+" by "+n.default.songs[t].artist+" is not streamable by the Soundcloud API"),++n.default.soundcloud_songs_ready==n.default.soundcloud_song_count&&d.default.setConfig(s)})}function i(e){var t=/^https?:\/\/(soundcloud.com|snd.sc)\/(.*)$/;return e.match(t)}var s={};return{loadSoundCloud:e,resolveIndividualStreamableURL:l,isSoundCloudURL:i}}();t.default=s,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){for(var e=document.getElementsByClassName("amplitude-playback-speed"),t=0;t0))for(var u=0;u0&&C.default.initialize(e.playlists),void 0==e.start_song||e.starting_playlist?E.default.changeSong(i.default.songs[0],0):g.default.isInt(e.start_song)?E.default.changeSong(i.default.songs[e.start_song],e.start_song):v.default.writeMessage("You must enter an integer index for the start song."),void 0!=e.shuffle_on&&e.shuffle_on&&(i.default.shuffle_on=!0,_.default.shuffleSongs(),E.default.changeSong(i.default.shuffle_list[0],0)),i.default.continue_next=void 0==e.continue_next||e.continue_next,i.default.playback_speed=void 0!=e.playback_speed?e.playback_speed:1,s.default.setPlaybackSpeed(i.default.playback_speed),i.default.audio.preload=void 0!=e.preload?e.preload:"auto",i.default.callbacks=void 0!=e.callbacks?e.callbacks:{},i.default.bindings=void 0!=e.bindings?e.bindings:{},i.default.volume=void 0!=e.volume?e.volume:50,i.default.delay=void 0!=e.delay?e.delay:0,i.default.volume_increment=void 0!=e.volume_increment?e.volume_increment:5,i.default.volume_decrement=void 0!=e.volume_decrement?e.volume_decrement:5,s.default.setVolume(i.default.volume),l(e),n(),void 0!=e.starting_playlist&&""!=e.starting_playlist&&(i.default.active_playlist=e.starting_playlist,void 0!=e.starting_playlist_song&&""!=e.starting_playlist_song?void 0!=u(e.playlists[e.starting_playlist].songs[parseInt(e.starting_playlist_song)])?E.default.changeSongPlaylist(i.default.active_playlist,e.playlists[e.starting_playlist].songs[parseInt(e.starting_playlist_song)],parseInt(e.starting_playlist_song)):(E.default.changeSongPlaylist(i.default.active_playlist,e.playlists[e.starting_playlist].songs[0],0),v.default.writeMessage("The index of "+e.starting_playlist_song+" does not exist in the playlist "+e.starting_playlist)):E.default.changeSong(i.default.active_playlist,e.playlists[e.starting_playlist].songs[0],0),V.default.sync()),T.default.run("initialized")}function l(e){void 0!=e.default_album_art?i.default.default_album_art=e.default_album_art:i.default.default_album_art="",void 0!=e.default_playlist_art?i.default.default_playlist_art=e.default_playlist_art:i.default.default_playlist_art=""}function n(){j.default.syncMain(),q.default.setMuted(0==i.default.volume),H.default.sync(),G.default.sync(),D.default.resetCurrentTimes(),V.default.syncToPause(),F.default.syncMetaData(),X.default.syncRepeatSong()}function d(e){var t=0,a=void 0;for(a in e)e.hasOwnProperty(a)&&t++;return v.default.writeMessage("You have "+t+" playlist(s) in your config"),t}function o(){for(var e=0;e0)for(var t=0;tp&&(p=y),yn[2*o])&&(n[2*o]=p),(0===i||c0}var r="",c="",p="";return{init:e,build:t,determineIfUsingWaveforms:f}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){var e={},t=(Math.floor(u.default.audio.currentTime%60)<10?"0":"")+Math.floor(u.default.audio.currentTime%60),a=Math.floor(u.default.audio.currentTime/60),l="00";return a<10&&(a="0"+a),a>=60&&(l=Math.floor(a/60),(a%=60)<10&&(a="0"+a)),e.seconds=t,e.minutes=a,e.hours=l,e}function t(){var e={},t=(Math.floor(u.default.audio.duration%60)<10?"0":"")+Math.floor(u.default.audio.duration%60),a=Math.floor(u.default.audio.duration/60),l="00";return a<10&&(a="0"+a),a>=60&&(l=Math.floor(a/60),(a%=60)<10&&(a="0"+a)),e.seconds=isNaN(t)?"00":t,e.minutes=isNaN(a)?"00":a,e.hours=isNaN(l)?"00":l.toString(),e}function a(){return u.default.audio.currentTime/u.default.audio.duration*100}function l(e){u.default.active_metadata.live||isFinite(e)&&(u.default.audio.currentTime=e)}return{computeCurrentTimes:e,computeSongDuration:t,computeSongCompletionPercentage:a,setCurrentTime:l}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){t(),a(),l(),n()}function t(){for(var e=document.getElementsByClassName("amplitude-buffered-progress"),t=0;t0||navigator.userAgent.match(/Trident.*rv\:11\./)?(a[l].removeEventListener("change",k.default.handle),a[l].addEventListener("change",k.default.handle)):(a[l].removeEventListener("input",k.default.handle),a[l].addEventListener("input",k.default.handle))}function x(){for(var e=window.navigator.userAgent,t=e.indexOf("MSIE "),a=document.getElementsByClassName("amplitude-volume-slider"),l=0;l0||navigator.userAgent.match(/Trident.*rv\:11\./)?(a[l].removeEventListener("change",O.default.handle),a[l].addEventListener("change",O.default.handle)):(a[l].removeEventListener("input",O.default.handle),a[l].addEventListener("input",O.default.handle))}function P(){for(var e=document.getElementsByClassName("amplitude-next"),t=0;t=0){var e=n.default.audio.buffered.end(n.default.audio.buffered.length-1),t=n.default.audio.duration;n.default.buffered=e/t*100}d.default.sync()}return{handle:e}}();t.default=s,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(12),d=l(i),s=a(8),o=l(s),f=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist");null==e&&t(),null!=e&&a(e)}}function t(){d.default.setRepeat(!n.default.repeat),o.default.syncRepeat()}function a(e){d.default.setRepeatPlaylist(!n.default.playlists[e].repeat,e),o.default.syncRepeatPlaylist(e)}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(12),d=l(i),s=a(8),o=l(s),f=function(){function e(){n.default.is_touch_moving||(d.default.setRepeatSong(!n.default.repeat_song),o.default.syncRepeatSong())}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(13),d=l(i),s=a(19),o=l(s),f=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist");null==e?t():a(e)}}function t(){d.default.toggleShuffle(),o.default.syncMain(n.default.shuffle_on)}function a(e){d.default.toggleShufflePlaylist(e),o.default.syncPlaylist(e)}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(4),d=l(i),s=a(3),o=l(s),f=a(5),r=l(f),c=a(1),p=l(c),v=a(2),y=l(v),g=function(){function e(){if(!n.default.is_touch_moving){var e=this.getAttribute("data-amplitude-playlist"),l=this.getAttribute("data-amplitude-song-index"),u=this.getAttribute("data-amplitude-location");null==u&&d.default.writeMessage("You must add an 'data-amplitude-location' attribute in seconds to your 'amplitude-skip-to' element."),null==l&&d.default.writeMessage("You must add an 'data-amplitude-song-index' attribute to your 'amplitude-skip-to' element."),null!=u&&null!=l&&(null==e?t(parseInt(l),parseInt(u)):a(e,parseInt(l),parseInt(u)))}}function t(e,t){o.default.changeSong(n.default.songs[e],e),p.default.play(),y.default.syncGlobal(),y.default.syncSong(),p.default.skipToLocation(t)}function a(e,t,a){r.default.newPlaylist(e)&&o.default.setActivePlaylist(e),o.default.changeSongPlaylist(e,n.default.playlists[e].songs[t],t),p.default.play(),y.default.syncGlobal(),y.default.syncPlaylist(),y.default.syncSong(),p.default.skipToLocation(a)}return{handle:e}}();t.default=g,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(23),d=l(i),s=a(14),o=l(s),f=function(){function e(){var e=this.value,i=n.default.audio.duration*(e/100),d=this.getAttribute("data-amplitude-playlist"),s=this.getAttribute("data-amplitude-song-index");null==d&&null==s&&t(i,e),null!=d&&null==s&&a(i,e,d),null==d&&null!=s&&l(i,e,s),null!=d&&null!=s&&u(i,e,d,s)}function t(e,t){n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,n.default.active_playlist,n.default.active_index))}function a(e,t,a){n.default.active_playlist==a&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,a,n.default.active_index)))}function l(e,t,a){n.default.active_index==a&&null==n.default.active_playlist&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,n.default.active_playlist,a)))}function u(e,t,a,l){n.default.playlists[a].active_index==l&&n.default.active_playlist==a&&(n.default.active_metadata.live||(d.default.setCurrentTime(e),o.default.sync(t,a,l)))}return{handle:e}}();t.default=f,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(6),d=(l(i),a(2)),s=l(d),o=a(1),f=l(o),r=function(){function e(){n.default.is_touch_moving||(s.default.syncToPause(),f.default.stop())}return{handle:e}}();t.default=r,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(24),d=l(i),s=a(15),o=l(s),f=a(14),r=l(f),c=a(20),p=l(c),v=a(23),y=l(v),g=a(9),m=(l(g),function(){function e(){t(),d.default.sync(),a(),l()}function t(){if(n.default.audio.buffered.length-1>=0){var e=n.default.audio.buffered.end(n.default.audio.buffered.length-1),t=n.default.audio.duration;n.default.buffered=e/t*100}}function a(){if(!n.default.active_metadata.live){var e=y.default.computeCurrentTimes(),t=y.default.computeSongCompletionPercentage(),a=y.default.computeSongDuration();o.default.syncCurrentTimes(e),r.default.sync(t,n.default.active_playlist,n.default.active_index),p.default.sync(t),o.default.syncDurationTimes(e,a)}}function l(){var e=Math.floor(n.default.audio.currentTime);if(void 0!=n.default.active_metadata.time_callbacks&&void 0!=n.default.active_metadata.time_callbacks[e])n.default.active_metadata.time_callbacks[e].run||(n.default.active_metadata.time_callbacks[e].run=!0,n.default.active_metadata.time_callbacks[e]());else for(var t in n.default.active_metadata.time_callbacks)n.default.active_metadata.time_callbacks.hasOwnProperty(t)&&(n.default.active_metadata.time_callbacks[t].run=!1)}return{handle:e}}());t.default=m,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){if(!n.default.is_touch_moving){var e=null;e=n.default.volume-n.default.volume_increment>0?n.default.volume-n.default.volume_increment:0,d.default.setVolume(e),o.default.setMuted(0==n.default.volume),r.default.sync()}}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){d.default.setVolume(this.value),o.default.setMuted(0==n.default.volume),r.default.sync()}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(1),d=l(i),s=a(10),o=l(s),f=a(11),r=l(f),c=function(){function e(){if(!n.default.is_touch_moving){var e=null;e=n.default.volume+n.default.volume_increment<=100?n.default.volume+n.default.volume_increment:100,d.default.setVolume(e),o.default.setMuted(0==n.default.volume),r.default.sync()}}return{handle:e}}();t.default=c,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(){var e=window.AudioContext||window.webkitAudioContext||window.mozAudioContext||window.oAudioContext||window.msAudioContext;e?(u.default.context=new e,u.default.analyser=u.default.context.createAnalyser(),u.default.audio.crossOrigin="anonymous",u.default.source=u.default.context.createMediaElementSource(u.default.audio),u.default.source.connect(u.default.analyser),u.default.analyser.connect(u.default.context.destination)):AmplitudeHelpers.writeDebugMessage("Web Audio API is unavailable! We will set any of your visualizations with your back up definition!")}function t(){var e=window.AudioContext||window.webkitAudioContext||window.mozAudioContext||window.oAudioContext||window.msAudioContext;return u.default.web_audio_api_available=!1,e?(u.default.web_audio_api_available=!0,!0):(u.default.web_audio_api_available=!1,!1)}function a(){var e=document.querySelectorAll(".amplitude-wave-form"),t=document.querySelectorAll(".amplitude-visualization");return e.length>0||t.length>0}return{configureWebAudioAPI:e,webAudioAPIAvailable:t,determineUsingAnyFX:a}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(21),n=l(u),i=a(0),d=l(i),s=a(1),o=l(s),f=a(13),r=l(f),c=a(6),p=(l(c),a(3)),v=l(p),y=a(12),g=l(y),m=a(5),_=l(m),h=a(16),b=l(h),A=a(19),x=l(A),M=a(8),P=l(M),S=a(14),L=l(S),w=a(20),E=l(w),k=a(15),T=l(k),O=a(2),C=l(O),N=a(7),j=l(N),I=a(18),q=l(I),z=a(4),H=l(z),B=a(17),D=l(B),R=function(){function e(e){n.default.initialize(e)}function t(){return d.default}function a(){n.default.rebindDisplay()}function l(){return d.default.active_playlist}function u(){return d.default.playback_speed}function i(e){o.default.setPlaybackSpeed(e),q.default.sync()}function s(){return d.default.repeat}function f(e){return d.default.playlists[e].repeat}function c(){return d.default.shuffle_on}function p(e){return d.default.playlists[e].shuffle}function y(e){r.default.setShuffle(e),x.default.syncMain()}function m(e,t){r.default.setShufflePlaylist(e,t),x.default.syncMain(),x.default.syncPlaylist(e)}function h(e){g.default.setRepeat(e),P.default.syncRepeat()}function A(e,t){g.default.setRepeatPlaylist(t,e),P.default.syncRepeatPlaylist(e)}function M(e){d.default.is_touch_moving||(g.default.setRepeatSong(!d.default.repeat_song),P.default.syncRepeatSong())}function S(){return d.default.default_album_art}function w(){return d.default.default_playlist_art}function k(e){d.default.default_album_art=e}function O(e){d.default.default_plalist_art=e}function N(){return d.default.audio.currentTime/d.default.audio.duration*100}function I(){return d.default.audio.currentTime}function z(){return d.default.audio.duration}function B(e){"number"==typeof e&&e>0&&e<100&&(d.default.audio.currentTime=d.default.audio.duration*(e/100))}function R(e){d.default.debug=e}function V(){return d.default.active_metadata}function U(){return d.default.playlists[d.default.active_playlist]}function F(e){return d.default.songs[e]}function W(e,t){return d.default.playlists[e].songs[t]}function G(e){return void 0==d.default.songs&&(d.default.songs=[]),d.default.songs.push(e),d.default.shuffle_on&&d.default.shuffle_list.push(e),D.default.isSoundCloudURL(e.url)&&D.default.resolveIndividualStreamableURL(e.url,null,d.default.songs.length-1,d.default.shuffle_on),d.default.songs.length-1}function Y(e){return void 0==d.default.songs&&(d.default.songs=[]),d.default.songs.unshift(e),d.default.shuffle_on&&d.default.shuffle_list.unshift(e),D.default.isSoundCloudURL(e.url)&&D.default.resolveIndividualStreamableURL(e.url,null,d.default.songs.length-1,d.default.shuffle_on),0}function X(e,t){return void 0!=d.default.playlists[t]?(d.default.playlists[t].songs.push(e),d.default.playlists[t].shuffle&&d.default.playlists[t].shuffle_list.push(e),D.default.isSoundCloudURL(e.url)&&D.default.resolveIndividualStreamableURL(e.url,t,d.default.playlists[t].songs.length-1,d.default.playlists[t].shuffle),d.default.playlists[t].songs.length-1):(H.default.writeMessage("Playlist doesn't exist!"),null)}function J(e,t,a){if(void 0==d.default.playlists[e]){d.default.playlists[e]={};var l=["repeat","shuffle","shuffle_list","songs","src"];for(var u in t)l.indexOf(u)<0&&(d.default.playlists[e][u]=t[u]);return d.default.playlists[e].songs=a,d.default.playlists[e].active_index=null,d.default.playlists[e].repeat=!1,d.default.playlists[e].shuffle=!1,d.default.playlists[e].shuffle_list=[],d.default.playlists[e]}return H.default.writeMessage("A playlist already exists with that key!"),null}function $(e){d.default.songs.splice(e,1)}function Q(e,t){void 0!=d.default.playlists[t]&&d.default.playlists[t].songs.splice(e,1)}function K(e){e.url?(d.default.audio.src=e.url,d.default.active_metadata=e,d.default.active_album=e.album):H.default.writeMessage("The song needs to have a URL!"),o.default.play(),C.default.sync(),j.default.displayMetaData(),L.default.resetElements(),E.default.resetElements(),T.default.resetCurrentTimes(),T.default.resetDurationTimes()}function Z(e){o.default.stop(),_.default.newPlaylist(null)&&(v.default.setActivePlaylist(null),v.default.changeSong(d.default.songs[e],e)),_.default.newSong(null,e)&&v.default.changeSong(d.default.songs[e],e),o.default.play(),C.default.sync()}function ee(e,t){o.default.stop(),_.default.newPlaylist(t)&&(v.default.setActivePlaylist(t),v.default.changeSongPlaylist(t,d.default.playlists[t].songs[e],e)),_.default.newSong(t,e)&&v.default.changeSongPlaylist(t,d.default.playlists[t].songs[e],e),C.default.sync(),o.default.play()}function te(){o.default.play()}function ae(){o.default.pause()}function le(){o.default.stop()}function ue(){return d.default.audio}function ne(){return d.default.analyser}function ie(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;""==e||null==e?null==d.default.active_playlist||""==d.default.active_playlist?v.default.setNext():v.default.setNextPlaylist(d.default.active_playlist):v.default.setNextPlaylist(e)}function de(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;""==e||null==e?null==d.default.active_playlist||""==d.default.active_playlist?v.default.setPrevious():v.default.setPreviousPlaylist(d.default.active_playlist):v.default.setPreviousPlaylist(e)}function se(){return d.default.songs}function oe(e){return d.default.playlists[e].songs}function fe(){return d.default.shuffle_on?d.default.shuffle_list:d.default.songs}function re(e){return d.default.playlists[e].shuffle?d.default.playlists[e].shuffle_list:d.default.playlists[e].songs}function ce(){return parseInt(d.default.active_index)}function pe(){return d.default.version}function ve(){return d.default.buffered}function ye(e,t){var a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;e=parseInt(e),null!=a?(_.default.newPlaylist(a)&&v.default.setActivePlaylist(a),v.default.changeSongPlaylist(a,d.default.playlists[a].songs[t],t),o.default.play(),C.default.syncGlobal(),C.default.syncPlaylist(),C.default.syncSong(),o.default.skipToLocation(e)):(v.default.changeSong(d.default.songs[t],t),o.default.play(),C.default.syncGlobal(),C.default.syncSong(),o.default.skipToLocation(e))}function ge(e,t){var a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(""!=a&&null!=a&&void 0!=d.default.playlists[a])for(var l in t)t.hasOwnProperty(l)&&"url"!=l&&"URL"!=l&&"live"!=l&&"LIVE"!=l&&(d.default.playlists[a].songs[e][l]=t[l]);else for(var l in t)t.hasOwnProperty(l)&&"url"!=l&&"URL"!=l&&"live"!=l&&"LIVE"!=l&&(d.default.songs[e][l]=t[l]);j.default.displayMetaData(),j.default.syncMetaData()}function me(e,t){if(void 0!=d.default.playlists[e]){var a=["repeat","shuffle","shuffle_list","songs","src"];for(var l in t)t.hasOwnProperty(l)&&a.indexOf(l)<0&&(d.default.playlists[e][l]=t[l]);j.default.displayPlaylistMetaData()}else H.default.writeMessage("You must provide a valid playlist key!")}function _e(e){d.default.delay=e}function he(){return d.default.delay}function be(){return d.default.player_state}function Ae(e,t){b.default.register(e,t)}function xe(e,t){void 0!=d.default.playlists[e]?void 0!=d.default.visualizations.available[t]?d.default.playlists[e].visualization=t:H.default.writeMessage("A visualization does not exist for the key provided."):H.default.writeMessage("The playlist for the key provided does not exist")}function Me(e,t){d.default.songs[e]?void 0!=d.default.visualizations.available[t]?d.default.songs[e].visualization=t:H.default.writeMessage("A visualization does not exist for the key provided."):H.default.writeMessage("A song at that index is undefined")}function Pe(e,t,a){void 0!=d.default.playlists[e].songs[t]?void 0!=d.default.visualizations.available[a]?d.default.playlists[e].songs[t].visualization=a:H.default.writeMessage("A visualization does not exist for the key provided."):H.default.writeMessage("The song in the playlist at that key is not defined")}function Se(e){void 0!=d.default.visualizations.available[e]?d.default.visualization=e:H.default.writeMessage("A visualization does not exist for the key provided.")}function Le(e){o.default.setVolume(e)}function we(){return d.default.volume}return{init:e,getConfig:t,bindNewElements:a,getActivePlaylist:l,getPlaybackSpeed:u,setPlaybackSpeed:i,getRepeat:s,getRepeatPlaylist:f,getShuffle:c,getShufflePlaylist:p,setShuffle:y,setShufflePlaylist:m,setRepeat:h,setRepeatSong:M,setRepeatPlaylist:A,getDefaultAlbumArt:S,setDefaultAlbumArt:k,getDefaultPlaylistArt:w,setDefaultPlaylistArt:O,getSongPlayedPercentage:N,setSongPlayedPercentage:B,getSongPlayedSeconds:I,getSongDuration:z,setDebug:R,getActiveSongMetadata:V,getActivePlaylistMetadata:U,getSongAtIndex:F,getSongAtPlaylistIndex:W,addSong:G,prependSong:Y,addSongToPlaylist:X,removeSong:$,removeSongFromPlaylist:Q,playNow:K,playSongAtIndex:Z,playPlaylistSongAtIndex:ee,play:te,pause:ae,stop:le,getAudio:ue,getAnalyser:ne,next:ie,prev:de,getSongs:se,getSongsInPlaylist:oe,getSongsState:fe,getSongsStatePlaylist:re,getActiveIndex:ce,getVersion:pe,getBuffered:ve,skipTo:ye,setSongMetaData:ge,setPlaylistMetaData:me,setDelay:_e,getDelay:he,getPlayerState:be,addPlaylist:J,registerVisualization:Ae,setPlaylistVisualization:xe,setSongVisualization:Me,setSongInPlaylistVisualization:Pe,setGlobalVisualization:Se,getVolume:we,setVolume:Le}}();t.default=R,e.exports=t.default},function(e,t,a){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=a(0),n=l(u),i=a(4),d=l(i),s=a(5),o=l(s),f=a(7),r=l(f),c=a(17),p=l(c),v=function(){function e(e){n.default.playlists=e,a(),l(),t(),u(),i(),s(),f()}function t(){for(var e in n.default.playlists)n.default.playlists[e].active_index=null}function a(){for(var e in n.default.playlists)if(n.default.playlists.hasOwnProperty(e)&&n.default.playlists[e].songs)for(var t=0;t0&&(a=e.hours+":"+a);for(var l=0;l0&&(a=e.hours+":"+a);for(var l=0;l0&&(a=e.hours+":"+a);for(var l=0;l0&&(l=e.hours+":"+l);for(var n=0;n0&&(a=i+":"+a)}return a}return{sync:e,resetTimes:i}}();t.default=n,e.exports=t.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=a(0),u=function(e){return e&&e.__esModule?e:{default:e}}(l),n=function(){function e(e){t(e),a(e),l(e),n(e)}function t(e){for(var t=document.querySelectorAll(".amplitude-duration-hours"),a=0;a0&&(t=e.hours+":"+t)),t}return{sync:e,resetTimes:i}}();t.default=n,e.exports=t.default},function(e,t){e.exports={name:"amplitudejs",version:"5.3.2",description:"A JavaScript library that allows you to control the design of your media controls in your webpage -- not the browser. No dependencies (jQuery not required) https://521dimensions.com/open-source/amplitudejs",main:"dist/amplitude.js",devDependencies:{"babel-core":"^6.26.3","babel-loader":"^7.1.5","babel-plugin-add-module-exports":"0.2.1","babel-polyfill":"^6.26.0","babel-preset-es2015":"^6.18.0",husky:"^1.3.1",jest:"^23.6.0",prettier:"1.15.1","pretty-quick":"^1.11.1",watch:"^1.0.2",webpack:"^2.7.0"},directories:{doc:"docs"},files:["dist"],funding:{type:"opencollective",url:"https://opencollective.com/amplitudejs"},scripts:{build:"node_modules/.bin/webpack",prettier:"npx pretty-quick",preversion:"npx pretty-quick && npm run test",postversion:"git push && git push --tags",test:"jest",version:"npm run build && git add -A dist"},repository:{type:"git",url:"git+https://github.com/521dimensions/amplitudejs.git"},keywords:["webaudio","html5","javascript","audio-player"],author:"521 Dimensions (https://521dimensions.com)",license:"MIT",bugs:{url:"https://github.com/521dimensions/amplitudejs/issues"},homepage:"https://github.com/521dimensions/amplitudejs#readme"}}])}); \ No newline at end of file diff --git a/player/js/howler.js b/player/js/howler.js index 328c6a4..a2758c7 100644 --- a/player/js/howler.js +++ b/player/js/howler.js @@ -1,8 +1,8 @@ /*! - * howler.js v2.1.3 + * howler.js v2.2.3 * howlerjs.com * - * (c) 2013-2019, James Simpson of GoldFire Studios + * (c) 2013-2020, James Simpson of GoldFire Studios * goldfirestudios.com * * MIT License @@ -150,6 +150,20 @@ return self; }, + /** + * Handle stopping all sounds globally. + */ + stop: function() { + var self = this || Howler; + + // Loop through all Howls and stop them. + for (var i=0; i=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator&&e._navigator.userAgent.match(/OPR\/([0-6].)/g),a=r&&parseInt(r[0].split("/")[1],10)<33;return e._codecs={mp3:!(a||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!o.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),webm:!!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(var t=0;t0?i._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(i._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;i._sprite=e,i._ended=!1;var p=function(){i._paused=!1,i._seek=_,i._start=c,i._stop=f,i._loop=!(!i._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(i);var m=i._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(i);var e=i._muted||t._muted?0:i._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),i._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?i._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):i._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[i._id]=setTimeout(t._ended.bind(t,i),l)),o||setTimeout(function(){t._emit("play",i._id),t._loadQueue()},0)};"running"===n.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(i._id))}else{var h=function(){m.currentTime=_,m.muted=i._muted||t._muted||n._muted||m.muted,m.volume=i._volume*n.volume(),m.playbackRate=i._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o||(t._emit("play",i._id),t._loadQueue())}).catch(function(){t._playLock=!1,t._emit("playerror",i._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),i._ended=!0,i._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",i._id),t._loadQueue()),m.playbackRate=i._rate,m.paused)return void t._emit("playerror",i._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||i._loop?t._endTimers[i._id]=setTimeout(t._ended.bind(t,i),l):(t._endTimers[i._id]=function(){t._ended(i),m.removeEventListener("ended",t._endTimers[i._id],!1)},m.addEventListener("ended",t._endTimers[i._id],!1))}catch(e){t._emit("playerror",i._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0;var g=function(){h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(i._id)}}return i._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),i+=d*r,i=Math.max(0,i),i=Math.min(1,i),i=Math.round(100*i)/100,u._webAudio?e._volume=i:u.volume(i,e._id,!0),a&&(u._volume=i),(on&&i>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var i;if("number"!=typeof e)return i=t._soundById(o),i?i._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var d=0;d=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return t;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var i=t._soundById(o);if(i){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var d=t.playing(o)?n.ctx.currentTime-i._playStart:0,_=i._rateSeek?i._rateSeek-i._seek:0;return i._seek+(_+d*Math.abs(i._rate))}return i._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),i._seek=e,i._ended=!1,t._clearTimer(o),t._webAudio||!i._node||isNaN(i._node.duration)||(i._node.currentTime=e);var l=function(){t._emit("seek",o),s&&t.play(o,!0)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._node.src=o._src,e._node.preload="auto",e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void d(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,d(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},d=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());(n._navigator&&n._navigator.standalone&&!r||n._navigator&&!n._navigator.standalone&&!r)&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof window?(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t):"undefined"!=typeof global&&(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t)}(); +/*! howler.js v2.2.3 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/([0-6].)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); /*! Spatial Plugin */ !function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var s=a._orientation;return n="number"!=typeof n?s[1]:n,t="number"!=typeof t?s[2]:t,r="number"!=typeof r?s[3]:r,o="number"!=typeof o?s[4]:o,i="number"!=typeof i?s[5]:i,"number"!=typeof e?s:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a 'stream', 'url' => '', 'format' => '', 'fallback' => '', @@ -203,6 +206,7 @@ function radio_station_player_output( $args = array(), $echo = false ) { 'theme' => 'light', 'buttons' => 'rounded', 'volume' => 77, + 'volumes' => array( 'slider', 'updown', 'mute', 'max' ), 'default' => false, ); @@ -257,16 +261,24 @@ function radio_station_player_output( $args = array(), $echo = false ) { } $class_list = implode( ' ', $classes ); $html['player_open'] = '
    ' . "\n"; - + + // 2.5.0: added preconnect/dns-prefetch link tags for URL host + if ( 'stream' == $args['media'] ) { + $url_host = parse_url( $args['url'], PHP_URL_HOST ); + $html['player_open'] .= '' . "\n"; + $html['player_open'] .= '' . "\n"; + } + // --- set Player container --- $classes = array( 'radio-container', 'rp-audio', 'rp-audio-stream' ); $classes[] = $args['layout']; $classes[] = $args['theme']; $classes[] = $args['buttons']; + // 2.5.0: maybe add no volume controls class + if ( 0 == count( $args['volumes'] ) ) { + $classes[] = 'no-volume-controls'; + } - // TODO: check volume controls display settings - // $classes[] = 'no-volume-controls'; - // 2.5.0: added filter for radio container class if ( function_exists( 'apply_filters' ) ) { $classes = apply_filters( 'radio_station_player_container_classes', $classes, $args, $instance ); @@ -513,6 +525,22 @@ function radio_station_player_output( $args = array(), $echo = false ) { return $player; } +// -------------------------- +// Store Player Instance Args +// -------------------------- +if ( function_exists('add_filter' ) ) { + add_filter( 'radio_station_player_output_args', 'radio_station_player_instance_args', 10, 2 ); +} +function radio_station_player_instance_args( $args, $instance ) { + + global $radio_player; + + // 2.5.0: store volume control arguments + $radio_player['instance-props'][$instance]['volume-controls'] = $args['volumes']; + + return $args; +} + // ---------------- // Player Shortcode // ---------------- @@ -587,6 +615,26 @@ function radio_station_player_shortcode( $atts ) { $volume = apply_filters( 'radio_station_player_default_volume', $volume ); } + // --- set volume control displays --- + // 2.5.0: moved from CSS output function + if ( defined( 'RADIO_PLAYER_VOLUME_CONTROLS' ) ) { + $volume_controls = RADIO_PLAYER_VOLUME_CONTROLS; + if ( is_string( $volume_controls ) ) { + $volume_controls = explode( ',', $volume_controls ); + } + } elseif ( function_exists( 'radio_station_get_setting' ) ) { + $volume_controls = radio_station_get_setting( 'player_volumes' ); + if ( !is_array( $volume_controls ) ) { + $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); + } + } + if ( function_exists( 'apply_filters' ) ) { + if ( !isset( $volume_controls ) ) { + $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); + } + $volume_controls = apply_filters( 'radio_station_player_volumes_display', $volume_controls ); + } + // --- set default player theme --- if ( defined( 'RADIO_PLAYER_THEME' ) ) { $theme = RADIO_PLAYER_THEME; @@ -609,22 +657,28 @@ function radio_station_player_shortcode( $atts ) { // 2.4.0.1: add player ID attribute // 2.5.0: added block and popup attribute $defaults = array( + // --- content attributes --- + 'media' => 'stream', 'url' => '', 'format' => '', 'fallback' => '', 'fformat' => '', 'title' => $title, 'image' => $image, + // --- player options --- 'script' => $script, 'layout' => $layout, 'theme' => $theme, 'buttons' => $buttons, // 'skin' => $skin, 'volume' => $volume, + 'volumes' => $volume_controls, 'default' => false, + // --- id attributes --- 'widget' => 0, 'id' => '', 'block' => 0, + // --- extra attributes --- 'popup' => 0, ); @@ -1161,6 +1215,7 @@ function radio_station_player_core_scripts() { $suffix = '.min'; if ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || ( defined( 'RADIO_STATION_DEBUG') && RADIO_STATION_DEBUG ) ) {$suffix = '';} + $suffix = ''; // DEV TEST // --- set amplitude script --- $path = dirname( __FILE__ ) . '/js/amplitude' . $suffix . '.js'; @@ -1179,7 +1234,7 @@ function radio_station_player_core_scripts() { $url = RADIO_PLAYER_URL . $url; } } - $radio_player['amplitude_script'] = array( 'version' => '5.0.3', 'url' => $url, 'path' => $path ); + $radio_player['amplitude_script'] = array( 'version' => '5.3.2', 'url' => $url, 'path' => $path ); // --- set jplayer script --- $path = dirname( __FILE__ ) . '/js/jplayer' . $suffix . '.js'; @@ -1213,7 +1268,7 @@ function radio_station_player_core_scripts() { $url = RADIO_PLAYER_URL . $url; } } - $radio_player['howler_script'] = array( 'version' => '2.3.1', 'url' => $url, 'path' => $path ); + $radio_player['howler_script'] = array( 'version' => '2.2.3', 'url' => $url, 'path' => $path ); // --- set core media elements script --- /* $version = '4.2.6'; // as of WP 4.9 @@ -1862,6 +1917,9 @@ function radio_station_player_state() { // "mp3" "aac" ... (+HTML5 Browser Supported Sources) function radio_station_player_script_amplitude() { + // $method = 'callbacks'; + $method = 'listeners'; + // --- load amplitude player --- $js = "function radio_player_amplitude(instance, url, format, fallback, fformat) { @@ -1900,15 +1958,22 @@ function radio_station_player_script_amplitude() { if (radio_player.debug) {console.log('Init Amplitude: '+instance+' : '+url+' : '+format+' : '+fallback+' : '+fformat);} radio_player_instance = Amplitude; radio_player_instance.init({ + 'debug': radio_player.debug, 'songs': songs, 'volume': volume, 'volume_increment': 5, 'volume_decrement': 5, 'continue_next': false, - 'preload': 'none', - 'callbacks': { - /* bug: initialized callback is not being triggered - 'initialized': function(e) { + 'preload': 'none'," . "\n"; + + // 2.5.0: callbacks disabled as no event is being passed to get player instance + if ( 'callbacks' == $method ) { + + $js .= "'callbacks': { + + /* amp 5.0.3 bug: initialized callback not firing! */ + /* amp 5.3.2 initialized callback is now firing */ + /* 'initialized': function(e) { radio_player.loading = false; instance = radio_player_event_instance(e, 'Loaded', 'amplitude'); radio_player_event_handler('loaded', {instance:instance, script:'amplitude'}); @@ -1918,13 +1983,15 @@ function radio_station_player_script_amplitude() { station = jQuery('#radio_player_'+instance).attr('station-id'); if (station) {radio_player_set_state('station', station);} }, */ - /* bug: play callback event is not being triggered + /* amp 5.0.3 bug: play callback event is not being triggered */ + /* amp 5.3.2: play callback is now being triggered */ 'play': function(e) { radio_player.loading = false; instance = radio_player_event_instance(e, 'Playing', 'amplitude'); radio_player_event_handler('playing', {instance:instance, script:'amplitude'}); radio_player_pause_others(instance); - }, */ + }, + /* bug: pause callback is still not firing */ 'pause': function(e) { instance = radio_player_event_instance(e, 'Paused', 'amplitude'); radio_player_event_handler('paused', {instance:instance, script:'amplitude'}); @@ -1940,15 +2007,17 @@ function radio_station_player_script_amplitude() { radio_player_player_volume(instance, 'amplitude', volume); } }, - /* bug: no event is being passed in callback to get instance - 'error': function(e) { + /* 'error': function(e) { + console.log('Amplitude Error'); console.log(e); instance = radio_player_event_instance(e, 'Error', 'amplitude'); if (radio_player.debug) {console.log(e);} radio_player_event_handler('error', {instance:instance, script:'amplitude'}); radio_player_player_fallback(instance, 'amplitude', 'Amplitude Error'); }, */ - } - }); + }"; + } + + $js .= "}); radio_data.players[instance] = radio_player_instance; radio_data.scripts[instance] = 'amplitude'; @@ -1957,7 +2026,8 @@ function radio_station_player_script_amplitude() { if (radio_player.debug) {console.log('Amplitude Audio Element:'); console.log(audio);} audio.setAttribute('instance-id', instance); - /* bind loaded to canplay event (as initialized callback not firing!) */ + /* amp 5.0.3 bind loaded to canplay event (as initialized callback not firing!) */ + /* amp 5.3.2 initialized callback is now firing */ audio.addEventListener('canplay', function(e) { radio_player.loading = false; instance = radio_player_event_instance(e, 'Loaded', 'amplitude'); @@ -1967,9 +2037,11 @@ function radio_station_player_script_amplitude() { if (channel) {radio_player_set_state('channel', channel);} station = jQuery('#radio_player_'+instance).attr('station-id'); if (station) {radio_player_set_state('station', station);} - }, false); - - /* bind play(ing) event (as play callback not firing!) */ + }, false);" . "\n"; + + if ( 'listeners' == $method ) { + $js .= "/* amp 5.0.3: bind play(ing) event (as play callback not firing!) */ + /* amp 5.3.2: play callback is now firing */ audio.addEventListener('playing', function(e) { radio_player.loading = false; instance = radio_player_event_instance(e, 'Playing', 'amplitude'); @@ -1977,13 +2049,14 @@ function radio_station_player_script_amplitude() { radio_player_pause_others(instance); }, false); - /* bind pause and stop events */ - audio.addEventListener('pause', function(e) { - instance = radio_player_event_instance(e, 'Paused', 'amplitude'); - radio_player_event_handler('paused', {instance:instance, script:'amplitude'}); + /* bind volume change event */ + audio.addEventListener('volumechange', function(e) { + instance = radio_player_event_instance(e, 'Volume', 'amplitude'); + if (instance && (radio_data.scripts[instance] == 'amplitude')) { + volume = radio_data.players[instance].getConfig().volume; + radio_player_player_volume(instance, 'amplitude', volume); + } }, false); - /* audio.addEventListener('stop', function(e) {}, false); */ - /* audio.addEventListener('volumechange', function(e) {}, false); */ /* bind error event (as event not being passed in callback) */ audio.addEventListener('error', function(e) { @@ -1992,8 +2065,25 @@ function radio_station_player_script_amplitude() { radio_player_event_handler('error', {instance:instance, script:'amplitude'}); radio_player_player_fallback(instance, 'amplitude', 'Amplitude Error'); }, false); + + /* listen for pause event */ + document.addEventListener('rp-pause', function(e) { + instance = e.detail.instance; + if (radio_data.scripts[instance] == 'amplitude') { + radio_player_event_handler('paused', {instance:instance, script:'amplitude'}); + } + }, false); + + /* listen for stop event */ + document.addEventListener('rp-stop', function(e) { + instance = e.detail.instance; + if (radio_data.scripts[instance] == 'amplitude') { + radio_player_event_handler('stopped', {instance:instance, script:'amplitude'}); + } + }, false);" . "\n"; + } - /* match script select dropdown value */ + $js .= "/* match script select dropdown value */ if (jQuery('#'+container_id+' .rp-script-select').length) { jQuery('#'+container_id+' .rp-script-select').val('amplitude'); } @@ -2006,7 +2096,7 @@ function radio_station_player_script_amplitude() { /* ref: https://521dimensions.com/open-source/amplitudejs/docs/configuration/ 'station_art_url': stationartwork, - 'default_album_art': defaultartwork, + 'default_album_art': defaultartwork, 'soundcloud_client': soundcloudkey, 'debug': debug, 'start_song': currentindex, @@ -2817,21 +2907,11 @@ function radio_station_player_control_styles( $instance ) { $css .= $container . " .rp-volume-thumb {display: none; width: 18px;}" . "\n"; // --- get volume control display settings --- - // 2.4.1.4: added volume control visibility options + // 2.4.1.4: added volume control visibility options filter // 2.5.0: added check for instance property for volume controls + $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); if ( isset( $radio_player['instance-props'][$instance]['volume-controls'] ) ) { $volume_controls = $radio_player['instance-props'][$instance]['volume-controls']; - } elseif ( function_exists( 'radio_station_get_setting' ) ) { - $volume_controls = radio_station_get_setting( 'player_volumes' ); - if ( !is_array( $volume_controls ) ) { - $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); - } - } - if ( function_exists( 'apply_filters' ) ) { - if ( !isset( $volume_controls ) ) { - $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); - } - $volume_controls = apply_filters( 'radio_station_player_volumes_display', $volume_controls ); } // --- volume display styles --- diff --git a/readme.txt b/readme.txt index 055c4c5..9b13fa5 100644 --- a/readme.txt +++ b/readme.txt @@ -221,7 +221,9 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) * Updated: Freemius SDK (2.4.5) -* Updated: Plugin Panel (1.2.7) +* Updated: Plugin Panel (1.2.8) +* Updated: AmplitudeJS (5.3.2) +* Updated: Howler (2.2.3) * Updated: Moment JS (2.29.4) with WP Loading * Improved: Refactored Schedule Engine Class (2.5.0) * Improved: Redesigned higher resolution player buttons @@ -234,10 +236,13 @@ You can now visit your site to make sure nothing is broken. If you experience is * Improved: Tab Schedule default date display on * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show +* Added: Volume Control options to Player widget * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection * Fixed: Mobile detection (via any pointer type) +* Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) +* Fixed: Workaround Amplitude pause event not firing = 2.4.0.9 = * Update: Sysend (1.11.1) for Radio Player diff --git a/widgets/class-radio-player-widget.php b/widgets/class-radio-player-widget.php index b2e136d..fa507d8 100644 --- a/widgets/class-radio-player-widget.php +++ b/widgets/class-radio-player-widget.php @@ -25,6 +25,7 @@ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); // 2.5.0: fix player image default value to 'default' + // $media = isset( $instance['media'] ? $instance['media'] : ''; $url = isset( $instance['url'] ) ? $instance['url'] : ''; // $format = isset( $instance['format'] ) ? $instance['format'] : ''; $title = isset( $instance['title'] ) ? $instance['title'] : ''; @@ -37,6 +38,14 @@ public function form( $instance ) { $volume = isset( $instance['volume'] ) ? $instance['volume'] : ''; $default = isset( $instance['default'] ) ? $instance['default'] : 0; + // 2.5.0: set volume control selection default + $volumes = isset( $instance['volumes'] ) ? $instance['volumes'] : 'slider,updown,mute,max'; + $volumes = explode( ',', $volumes ); + $volume_slider = in_array( 'slider', $volumes ) ? true : false; + $volume_updown = in_array( 'updown', $volumes ) ? true : false; + $volume_mute = in_array( 'mute', $volumes ) ? true : false; + $volume_max = in_array( 'max', $volumes ) ? true : false; + // --- additional displays --- // $shows = isset( $instance['show_display'] ) ? $instance['show_display'] : false; // $hosts = isset( $instance['show_hosts'] ) ? $instance['show_hosts'] : false; @@ -128,6 +137,18 @@ public function form( $instance ) {

    '; + // --- Player Volume Controls --- + // 2.5.0: added for consistency with main plugin settings + $fields['volumes'] = '

    + +

    '; + // --- Default Player --- $fields['default'] = '

    ' . esc_html( __( '[Pro] Color Options', 'radio-station' ) ) . '

    ' . "\n"; + $fields['color_options'] .= '

    ' . esc_html( __( '[Pro] Color Options', 'radio-station' ) ) . '

    ' . "\n"; $fields['color_options'] = '


    ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | ' . esc_html( __( 'More Details', 'radio-station' ) ) . '

    '; + // --- [Pro] Track Animation --- + // 2.5.0: added Pro track animation message + $fields['advanced_options'] = '

    ' . esc_html( __( '[Pro] Advanced Options', 'radio-station' ) ) . '

    ' . "\n"; + $fields['advanced_options'] .= '

    +
    +

      + ' . esc_html( __( 'Upgrade to Pro', 'radio-station' ) ) . ' | + ' . esc_html( __( 'More Details', 'radio-station' ) ) . ' +

      '; + // --- filter and output --- // 2.5.0: added filter for array fields for ease of adding fields $fields = apply_filters( 'radio_station_player_widget_fields_list', $fields, $this, $instance ); @@ -224,6 +255,7 @@ public function update( $new_instance, $old_instance ) { // --- get new widget options --- $instance = $old_instance; + // $instance['media'] = isset( $new_instance['media'] ) : 'stream'; $instance['url'] = isset( $new_instance['url'] ) ? $new_instance['url'] : ''; $instance['title'] = isset( $new_instance['title'] ) ? $new_instance['title'] : ''; $instance['station'] = isset( $new_instance['station'] ) ? $new_instance['station'] : ''; @@ -243,6 +275,22 @@ public function update( $new_instance, $old_instance ) { } $instance['default'] = isset( $new_instance['default'] ) ? 1 : 0; + // 2.5.0: save volume control selections + $volumes = array(); + if ( isset( $new_instance['volume_slider'] ) ) { + $volumes[] = 'slider'; + } + if ( isset( $new_instance['volume_updown'] ) ) { + $volumes[] = 'updown'; + } + if ( isset( $new_instance['volume_mute'] ) ) { + $volumes[] = 'mute'; + } + if ( isset( $new_instance['volume_max'] ) ) { + $volumes[] = 'max'; + } + $instance['volumes'] = implode( ',', $volumes ); + // --- additional displays --- // $instance['show_display'] = isset( $new_instance['show_display'] ) ? 1 : 0; // $instance['show_hosts'] = isset( $new_instance['show_hosts'] ) ? 1 : 0; @@ -267,7 +315,9 @@ public function widget( $args, $instance ) { $id = $radio_station_data['widgets']['player']; // --- get widget options --- + $media = isset( $instance['media'] ) ? $instance['media'] : 'stream'; $url = $instance['url']; + // $fallback = $instance['fallback']; $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); $station = $instance['station']; @@ -286,6 +336,8 @@ public function widget( $args, $instance ) { $volume = 'default'; } $default = $instance['default']; + // 2.5.0: added volume controls + $volumes = isset( $instance['volumes'] ) ? explode( ',', $instance['volumes'] ) : array( 'slider', 'updown', 'mute', 'max' ); // --- additional displays --- // $instance['show_display'] = $instance['show_display'] ) ? 1 : 0; @@ -294,9 +346,12 @@ public function widget( $args, $instance ) { // --- set shortcode attributes --- // note: station text mapped to shortcode title attribute + // 2.5.0: added media and volumes attributes $atts = array( // --- main player settings --- + 'media' => $media, 'url' => $url, + // 'fallback' => $fallback 'title' => $station, 'image' => $image, 'script' => $script, @@ -304,6 +359,7 @@ public function widget( $args, $instance ) { 'theme' => $theme, 'buttons' => $buttons, 'volume' => $volume, + 'volumes' => $volumes, 'default' => $default, // --- additional displays --- // 'shows' => $shows, From 0341cbb0f097651a91b5158c9530b76a79cefbe3 Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 7 Nov 2022 22:13:56 +1000 Subject: [PATCH 279/377] bump dev version --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index ec7382e..386ef10 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.6 +Version: 2.4.9.7 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From 4c36c80a3a4014d577da810e94b6b4dd0bfe1418 Mon Sep 17 00:00:00 2001 From: majick777 Date: Sun, 13 Nov 2022 17:30:27 +1000 Subject: [PATCH 280/377] bump readme tested up to --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 9b13fa5..64dd4e3 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.0.2 +Tested up to: 6.1 Stable tag: 2.5.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -225,7 +225,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) * Updated: Moment JS (2.29.4) with WP Loading -* Improved: Refactored Schedule Engine Class (2.5.0) +* Improved: Refactored Schedule Engine Class * Improved: Redesigned higher resolution player buttons * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards From aab11e301a6d21e74f50d39e8e4f379ddc1f4ebe Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 14 Nov 2022 12:23:02 +1000 Subject: [PATCH 281/377] loader csv save fix --- CHANGELOG.md | 9 +++++++-- loader.php | 25 +++++++++++-------------- readme.md | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c343c..fbef529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.5.0 * Added: Radio Station Blocks! (converted Widgets) * Updated: Freemius SDK (2.4.5) -* Updated: Plugin Panel (1.2.7) +* Updated: Plugin Panel (1.2.8) +* Updated: AmplitudeJS (5.3.2) +* Updated: Howler (2.2.3) * Updated: Moment JS (2.29.4) with WP Loading -* Improved: Refactored Schedule Engine Class (2.5.0) +* Improved: Refactored Schedule Engine Class * Improved: Redesigned higher resolution player buttons * Improved: Standardized Widget Input Fields * Improved: WordPress Coding Standards @@ -23,11 +25,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: Tab Schedule default date display on * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show +* Added: Volume Control options to Player widget * Fixed: Countdowns with multiple widget instances * Fixed: Radio Player iOS no volume control detection * Fixed: Mobile detection (via any pointer type) * Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) +* Fixed: Workaround Amplitude pause event not firing + ### 2.4.0.9 * Update: Sysend (1.11.1) for Radio Player diff --git a/loader.php b/loader.php index 2c37c79..08a271c 100644 --- a/loader.php +++ b/loader.php @@ -582,9 +582,9 @@ public function update_settings() { if ( $this->debug ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo 'Saving Setting Key ' . esc_html( $key ) . ' (' . esc_html( $postkey ) . ')
      ' . PHP_EOL; + echo 'Saving Setting Key ' . esc_html( $key ) . ' (' . esc_html( $postkey ) . ')
      ' . "\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo 'Type: ' . esc_html( $type ) . ' - Valid Options ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
      ' . PHP_EOL; + echo 'Type: ' . esc_html( $type ) . ' - Valid Options ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
      ' . "\n"; } // --- sanitize value according to type --- @@ -676,7 +676,8 @@ public function update_settings() { if ( strstr( $posted, ',' ) ) { $posted = explode( ',', $posted ); } else { - $posted[0] = $posted; + // 1.2.8: fix to convert string to array + $posted = array( $posted ); } foreach ( $posted as $i => $value ) { $posted[$i] = trim( $value ); @@ -788,14 +789,14 @@ public function update_settings() { // 1.2.0: added isset check for newsetting if ( !is_null( $newsettings ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo '(To-validate) ' . esc_html( print_r( $newsettings, true ) ) . '
      ' . PHP_EOL; + echo '(To-validate) ' . esc_html( print_r( $newsettings, true ) ) . '
      ' . "\n"; } else { // 1.1.7 handle if (new) key not set yet if ( isset( $settings[$key] ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions echo '(Validated) ' . esc_html( print_r( $settings[$key], true ) ) . '
      ' . PHP_EOL; } else { - echo 'No setting yet for key ' . esc_html( $key ) . '
      ' . PHP_EOL; + echo 'No setting yet for key ' . esc_html( $key ) . '
      ' . "\n"; } } } @@ -839,7 +840,7 @@ public function update_settings() { $newvalue = $this->validate_setting( $value, $valid, $validate_args ); $newvalues[] = $newvalue; if ( $this->debug ) { - echo 'Validated Setting value ' . esc_html( $value ) . ' to ' . esc_html( $newvalue ) . '
      ' . PHP_EOL; + echo 'Validated Setting value ' . esc_html( $value ) . ' to ' . esc_html( $newvalue ) . '
      ' . "\n"; } } $newsettings = implode( ',', $newvalues ); @@ -849,7 +850,7 @@ public function update_settings() { // 1.1.9: fix to allow saving of zero value // 1.2.1: fix to allow saving of empty value if ( $this->debug ) { - echo 'Validated Setting single value ' . esc_html( $newsettings ) . ' to ' . esc_html( $newsetting ) . '
      ' . PHP_EOL; + echo 'Validated Setting single value ' . esc_html( $newsettings ) . ' to ' . esc_html( $newsetting ) . '
      ' . "\n"; } if ( $newsetting || ( '' == $newsetting ) || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $settings[$key] = $newsetting; @@ -859,9 +860,9 @@ public function update_settings() { if ( $this->debug ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo 'Valid Options for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
      ' . PHP_EOL; + echo 'Valid Options for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '
      ' . "\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo 'Validated Settings for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $settings[$key], true ) ) . '
      ' . PHP_EOL; + echo 'Validated Settings for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $settings[$key], true ) ) . '
      ' . "\n"; } } @@ -3469,6 +3470,7 @@ function radio_station_settings_resources( $media, $color_picker ) { // == 1.2.8 == // - fix saving non-alpha colours in coloralpha fields +// - fix saving of single value in CSV field // == 1.2.7 == // - fix color picker alpha sanitization / saving @@ -3617,8 +3619,3 @@ function radio_station_settings_resources( $media, $color_picker ) { // == 0.9.0 == // - Development Version - -// ----------------- -// Development TODOs -// ----------------- -// - use sanitize text field on textarea ? diff --git a/readme.md b/readme.md index 059d8c3..1aec371 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.0.2 +Tested up to: 6.1 Stable tag: 2.5.0 From 589ac14b36e84d3f018491b6cff3f4ba2df95254 Mon Sep 17 00:00:00 2001 From: majick777 Date: Sat, 19 Nov 2022 13:29:44 +1000 Subject: [PATCH 282/377] player style fixes --- docs/Player.md | 19 +++++++++++++++++++ options.php | 3 ++- player/css/radio-player.css | 6 +++++- player/radio-player.php | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/Player.md b/docs/Player.md index 6ab61a3..bd54c67 100644 --- a/docs/Player.md +++ b/docs/Player.md @@ -22,6 +22,8 @@ Default settings for the Player can be set on the Plugin Settings page on the Pl * Start Volume: Initial volume for when the Player starts playback. * Single Player: Stop other Player instances when a Player is started. +Please note you will not see volume controls when using an iOS device as they are not supported. + ### Radio Player Widget A Player widget instance can be added to any widget area via the WordPress Admin Appearance -> Widgets page. The widget options correspond to the shortcode attributes below, allowing you control over the widget display output. @@ -46,6 +48,23 @@ A Player block can be added via the Block Editor by clicking the blue + icon. Th * Now Playing Display: whether to display current track information via stream metadata. * Metadata URL: alternative URL for metadata retrieval (normally via stream URL.) +#### [Pro] Continous Player Integration + +Developers should note that the continous player page transitions are implemented by adding click event listeners to `a` link tags. This means that dynamic links - those that do not exist on page load but are added in content later - will need special treatment to preserve continuous playback. Some examples of these include some mobile menus or breadcrumbs that are created by javascript dynamically on hover, and AJAX loaded "more" or filtered content. + +The solution to integrated these dynamic links is to use a filter to target the classes of the dynamic links. Knowing these claseses to target, Teleporter will then handle them via click event bubbling. Here's an example of how that can be done. (Note the format is just a comma separated list of classes without a `.` prefix:) + +``` +add_filter( 'teleporter_dynamic_link_classes', 'my_custom_dynamic_link_classes' ); +function my_custom_dynamic_link_classes( $classes ) { + $classes = 'mobile-menu,elementor-item'; + return $classes; +} +``` + +Similarly, if there are links that you wish to force to not transition for some reason, you can use the `teleporter_ignore_link_classes` filter in the same way. + + #### [Pro] Extra Color Options The Player Bar also includes some extra color options to match the look and feel of your site (these are also used as defaults for other Player instances.) diff --git a/options.php b/options.php index cf0b9d6..2fcc70f 100644 --- a/options.php +++ b/options.php @@ -336,10 +336,11 @@ // --- [Player] Volume Controls --- // 2.4.0.3: added enable/disable volume controls option + // 2.5.0: default to volume slider only 'player_volumes' => array( 'type' => 'multicheck', 'label' => __( 'Volume Controls', 'radio-station' ), - 'default' => array( 'slider', 'updown', 'mute', 'max' ), + 'default' => array( 'slider' ), 'options' => array( 'slider' => __( 'Volume Slider', 'radio-station' ), 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), diff --git a/player/css/radio-player.css b/player/css/radio-player.css index e80d6cd..9f3877b 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -71,7 +71,7 @@ vertical-align: top; } -.rp-controls, .rp-volume-controls, .rp-popup, .rp-script-switcher { +.rp-controls, .rp-volume-controls, .rp-script-switcher { display: inline-block; vertical-align: middle; margin-top: -10px; @@ -82,6 +82,10 @@ vertical-align: middle; } +.rp-popup { + display: inline-block; +} + .rp-audio .rp-interface, .rp-audio-stream .rp-interface { height: 80px; padding-top: 5px; diff --git a/player/radio-player.php b/player/radio-player.php index 4dc53dd..2d6c305 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -2858,10 +2858,10 @@ function radio_station_player_control_styles( $instance ) { // --- Range Track (synced Background Div) --- // 2.4.0.3: add position absolute/top on slider background (cross-browser display fix) + // 2.6.0: set top bottom and height to 10px for consistent display $css .= "/* Range Track */ " . $container . " .rp-volume-controls .rp-volume-slider-bg { - position: absolute; top: 9px; overflow: hidden; height: 9px; margin-left: 9px; z-index: -1; - border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 3px; background: rgba(128, 128, 128, 0.5); + position: absolute; top: 10px; bottom: 10px; overflow: hidden; height: 10px; margin-left: 9px; z-index: -1; border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 3px; background: rgba(128, 128, 128, 0.5); } " . $container . ".playing .rp-volume-controls .rp-volume-slider-bg {background: " . $colors['track'] . ";} " . $container . ".playing.muted .rp-volume-controls .rp-volume-slider-bg {background: rgba(128, 128, 128, 0.5);}" . "\n"; From 7cfad4d1ad49aff44df6bc7e02d3cb4200fa640a Mon Sep 17 00:00:00 2001 From: majick777 Date: Mon, 9 Jan 2023 16:31:43 +1000 Subject: [PATCH 283/377] 2.4.9.9 updates --- CHANGELOG.md | 4 +- blocks/archive.js | 2 +- blocks/clock.js | 2 +- blocks/current-playlist.js | 2 +- blocks/current-show.js | 2 +- blocks/editor.js | 20 +- blocks/player.js | 455 ++- blocks/schedule.js | 2 +- blocks/upcoming-shows.js | 2 +- docs/Display.md | 31 +- docs/FAQ.md | 2 + docs/Player.md | 57 +- docs/Pro.md | 10 + docs/Roadmap.md | 5 +- .../178afa6030e76635dbe835e111d2c507.png | Bin 0 -> 6020 bytes .../27b5a722a5553d9de0170325267fccec.png | Bin 0 -> 6501 bytes .../4375c4a3ddc6f637c2ab9a2d7220f91e.png | Bin 0 -> 9380 bytes .../5480ed23b199531a8cbc05924f26952b.png | Bin 0 -> 7262 bytes .../b4f3b958f4a019862d81b15f3f8eee3a.svg | 402 ++ .../c03f665db27af43971565560adfba594.png | Bin 0 -> 6208 bytes .../cb5fc4f6ec7ada72e986f6e7dde365bf.png | Bin 0 -> 23473 bytes .../dd89563360f0272635c8f0ab7d7f1402.png | Bin 0 -> 12087 bytes .../e366d70661d8ad2493bd6afbd779f125.png | Bin 0 -> 11124 bytes .../f3aac72a8e63997d6bb888f816457e9b.png | Bin 0 -> 6040 bytes .../f928f1be99776af83e8e6be4baf8ffe7.svg | 227 ++ .../fde48e4609a6ddc11d639fc2421f2afd.png | Bin 0 -> 11237 bytes freemius-pricing/freemius-pricing.css | 9 + freemius-pricing/freemius-pricing.js | 2 + .../freemius-pricing.js.LICENSE.txt | 44 + freemius/assets/css/admin/account.css | 2 +- freemius/assets/css/admin/add-ons.css | 3 +- freemius/assets/css/admin/affiliation.css | 2 +- .../assets/css/admin/clone-resolution.css | 1 + freemius/assets/css/admin/common.css | 3 +- freemius/assets/css/admin/connect.css | 2 +- freemius/assets/css/admin/debug.css | 2 +- freemius/assets/css/admin/dialog-boxes.css | 3 +- .../assets/css/admin/gdpr-optin-notice.css | 2 +- freemius/assets/css/admin/optout.css | 1 + freemius/assets/css/customizer.css | 2 +- freemius/config.php | 10 +- freemius/includes/class-freemius-abstract.php | 67 +- freemius/includes/class-freemius.php | 3286 +++++++++++------ freemius/includes/class-fs-admin-notices.php | 80 +- freemius/includes/class-fs-api.php | 30 +- freemius/includes/class-fs-lock.php | 110 + freemius/includes/class-fs-logger.php | 3 +- freemius/includes/class-fs-plugin-updater.php | 59 +- freemius/includes/class-fs-storage.php | 186 +- freemius/includes/class-fs-user-lock.php | 56 +- .../class-fs-customizer-upsell-control.php | 4 +- .../entities/class-fs-affiliate-terms.php | 4 + .../includes/entities/class-fs-plugin-tag.php | 4 + freemius/includes/entities/class-fs-site.php | 43 +- freemius/includes/entities/class-fs-user.php | 13 +- freemius/includes/fs-core-functions.php | 32 +- freemius/includes/fs-essential-functions.php | 104 +- freemius/includes/fs-plugin-info-dialog.php | 59 +- freemius/includes/i18n.php | 605 --- .../class-fs-admin-notice-manager.php | 95 +- .../managers/class-fs-clone-manager.php | 1660 +++++++++ .../managers/class-fs-option-manager.php | 115 +- .../managers/class-fs-permission-manager.php | 698 ++++ .../managers/class-fs-plugin-manager.php | 23 +- freemius/includes/sdk/FreemiusBase.php | 340 +- freemius/includes/sdk/FreemiusWordPress.php | 1430 +++---- .../supplements/fs-migration-2.5.1.php | 31 + freemius/languages/freemius-cs_CZ.mo | Bin 65437 -> 52613 bytes freemius/languages/freemius-da_DK.mo | Bin 64319 -> 65452 bytes freemius/languages/freemius-de_DE.mo | Bin 68503 -> 69915 bytes freemius/languages/freemius-en.mo | Bin 63483 -> 64883 bytes freemius/languages/freemius-es_ES.mo | Bin 67730 -> 68396 bytes freemius/languages/freemius-fr_FR.mo | Bin 68043 -> 68762 bytes freemius/languages/freemius-he_IL.mo | Bin 66683 -> 67872 bytes freemius/languages/freemius-hu_HU.mo | Bin 65595 -> 66410 bytes freemius/languages/freemius-it_IT.mo | Bin 66422 -> 67300 bytes freemius/languages/freemius-ja.mo | Bin 71666 -> 72288 bytes freemius/languages/freemius-nl_NL.mo | Bin 66062 -> 67088 bytes freemius/languages/freemius-ru_RU.mo | Bin 79692 -> 79360 bytes freemius/languages/freemius-ta.mo | Bin 97067 -> 95123 bytes freemius/languages/freemius-zh_CN.mo | Bin 61151 -> 62810 bytes freemius/languages/freemius.pot | 1752 ++++++--- freemius/package.json | 27 - freemius/require.php | 2 + freemius/start.php | 8 +- freemius/templates/account.php | 95 +- freemius/templates/account/billing.php | 2 +- .../account/partials/disconnect-button.php | 104 + freemius/templates/account/partials/site.php | 14 +- freemius/templates/account/payments.php | 21 +- freemius/templates/admin-notice.php | 5 +- freemius/templates/auto-installation.php | 2 +- freemius/templates/clone-resolution-js.php | 79 + freemius/templates/connect.php | 297 +- freemius/templates/connect/index.php | 3 + freemius/templates/connect/permission.php | 43 + .../templates/connect/permissions-group.php | 72 + freemius/templates/contact.php | 2 +- freemius/templates/debug.php | 94 +- freemius/templates/firewall-issues-js.php | 3 +- freemius/templates/forms/affiliation.php | 12 +- freemius/templates/forms/data-debug-mode.php | 2 +- .../templates/forms/deactivation/form.php | 225 +- .../templates/forms/email-address-update.php | 347 ++ .../templates/forms/license-activation.php | 56 +- freemius/templates/forms/optout.php | 448 +-- freemius/templates/forms/resend-key.php | 7 +- freemius/templates/forms/trial-start.php | 2 +- freemius/templates/forms/user-change.php | 2 +- freemius/templates/gdpr-optin-js.php | 2 +- freemius/templates/js/permissions.php | 546 +++ .../templates/partials/network-activation.php | 2 +- freemius/templates/plugin-icon.php | 4 +- .../templates/plugin-info/description.php | 2 +- freemius/templates/plugin-info/features.php | 2 +- freemius/templates/powered-by.php | 14 +- freemius/templates/pricing.php | 23 +- freemius/templates/sticky-admin-notice-js.php | 3 +- freemius/templates/tabs-capture-js.php | 2 +- includes/blocks.php | 19 +- includes/class-current-playlist-widget.php | 272 ++ includes/class-current-show-widget.php | 360 ++ includes/class-radio-clock-widget.php | 207 ++ includes/class-radio-player-widget.php | 324 ++ includes/class-upcoming-shows-widget.php | 327 ++ includes/extra-strings.php | 431 ++- includes/post-types-admin.php | 6 +- includes/templates.php | 3 +- loader.php | 29 +- options.php | 54 +- player/css/radio-player.css | 2 +- player/images/controls-dark.png | Bin 0 -> 2423 bytes player/images/controls-light.png | Bin 0 -> 2301 bytes player/js/radio-player.js | 4 +- player/radio-player.php | 239 +- radio-station-admin.php | 719 ++-- radio-station.php | 31 +- readme.md | 2 +- readme.txt | 7 +- scheduler/schedule-engine.php | 6 +- widgets/class-radio-player-widget.php | 8 +- 141 files changed, 13043 insertions(+), 4783 deletions(-) create mode 100644 freemius-pricing/178afa6030e76635dbe835e111d2c507.png create mode 100644 freemius-pricing/27b5a722a5553d9de0170325267fccec.png create mode 100644 freemius-pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png create mode 100644 freemius-pricing/5480ed23b199531a8cbc05924f26952b.png create mode 100644 freemius-pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg create mode 100644 freemius-pricing/c03f665db27af43971565560adfba594.png create mode 100644 freemius-pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png create mode 100644 freemius-pricing/dd89563360f0272635c8f0ab7d7f1402.png create mode 100644 freemius-pricing/e366d70661d8ad2493bd6afbd779f125.png create mode 100644 freemius-pricing/f3aac72a8e63997d6bb888f816457e9b.png create mode 100644 freemius-pricing/f928f1be99776af83e8e6be4baf8ffe7.svg create mode 100644 freemius-pricing/fde48e4609a6ddc11d639fc2421f2afd.png create mode 100644 freemius-pricing/freemius-pricing.css create mode 100644 freemius-pricing/freemius-pricing.js create mode 100644 freemius-pricing/freemius-pricing.js.LICENSE.txt create mode 100644 freemius/assets/css/admin/clone-resolution.css create mode 100644 freemius/assets/css/admin/optout.css create mode 100644 freemius/includes/class-fs-lock.php delete mode 100644 freemius/includes/i18n.php create mode 100644 freemius/includes/managers/class-fs-clone-manager.php create mode 100644 freemius/includes/managers/class-fs-permission-manager.php create mode 100644 freemius/includes/supplements/fs-migration-2.5.1.php delete mode 100644 freemius/package.json create mode 100644 freemius/templates/account/partials/disconnect-button.php create mode 100644 freemius/templates/clone-resolution-js.php create mode 100644 freemius/templates/connect/index.php create mode 100644 freemius/templates/connect/permission.php create mode 100644 freemius/templates/connect/permissions-group.php create mode 100644 freemius/templates/forms/email-address-update.php create mode 100644 freemius/templates/js/permissions.php create mode 100644 includes/class-current-playlist-widget.php create mode 100644 includes/class-current-show-widget.php create mode 100644 includes/class-radio-clock-widget.php create mode 100644 includes/class-radio-player-widget.php create mode 100644 includes/class-upcoming-shows-widget.php create mode 100644 player/images/controls-dark.png create mode 100644 player/images/controls-light.png diff --git a/CHANGELOG.md b/CHANGELOG.md index fbef529..ac2d2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.5.0 * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.4.5) -* Updated: Plugin Panel (1.2.8) +* Updated: Freemius SDK (2.5.3) +* Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) * Updated: Moment JS (2.29.4) with WP Loading diff --git a/blocks/archive.js b/blocks/archive.js index 54dd2f9..44d1054 100644 --- a/blocks/archive.js +++ b/blocks/archive.js @@ -74,7 +74,7 @@ return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/archive', className: 'radio-block-archive', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/archive', className: 'radio-archive-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, /* === Archive List Details === */ diff --git a/blocks/clock.js b/blocks/clock.js index 84420ce..6c5c9a4 100644 --- a/blocks/clock.js +++ b/blocks/clock.js @@ -40,7 +40,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/clock', className: 'radio-block-clock', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/clock', className: 'radio-clock-block', attributes: atts } ), el( InspectorControls, {}, el ( Panel, {}, el( PanelBody, { title: __( 'Clock Display Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, diff --git a/blocks/current-playlist.js b/blocks/current-playlist.js index 65e9042..02c1f3a 100644 --- a/blocks/current-playlist.js +++ b/blocks/current-playlist.js @@ -50,7 +50,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/current-playlist', className: 'radio-block-schedule', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/current-playlist', className: 'radio-playlist-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, diff --git a/blocks/current-show.js b/blocks/current-show.js index bd711f1..874748f 100644 --- a/blocks/current-show.js +++ b/blocks/current-show.js @@ -67,7 +67,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/current-show', className: 'radio-block-schedule', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/current-show', className: 'radio-current-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, diff --git a/blocks/editor.js b/blocks/editor.js index e799d97..6d798b3 100644 --- a/blocks/editor.js +++ b/blocks/editor.js @@ -4,15 +4,6 @@ const el = window.wp.element.createElement; const { Icon } = wp.components; -/* --- Add Block Category Icon --- */ -( function() { - const antennaSVG = () => ( - el( Icon, {icon: ''} - ) - ); - wp.blocks.updateCategory( 'radio-station-blocks', { icon: antennaSVG } ); -} )(); - /* --- Subscribe to Block State --- */ /* ref: https://wordpress.stackexchange.com/a/358256/76440 */ ( () => { @@ -113,8 +104,15 @@ const { Icon } = wp.components; }, 300 ) ); } )(); +/* --- Load Block Script --- */ function radio_station_load_block_script(handle) { id = 'radio-'+handle+'-js'; - url = radio.ajax_url+'?action=radio_station_block_script&handle='+handle; - jQuery('html head').append(''); + if (!document.getElementById(id)) { + url = radio.ajax_url+'?action=radio_station_block_script&handle='+handle; + jQuery('html head').append(''); + } + if (typeof radio_station_pro_block_scripts == 'function') { + radio_station_pro_load_block_scripts(handle); + } } + diff --git a/blocks/player.js b/blocks/player.js index eb09e35..a991b96 100644 --- a/blocks/player.js +++ b/blocks/player.js @@ -3,15 +3,18 @@ */ (() => { + /* --- Import Modules/Components --- */ const el = window.wp.element.createElement; const { serverSideRender: ServerSideRender } = window.wp; const { registerBlockType } = window.wp.blocks; const { InspectorControls } = window.wp.blockEditor; const { Fragment } = window.wp.element; - const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, ColorPicker, Dropdown, Button, Panel, PanelBody, PanelRow } = window.wp.components; const { __, _e } = window.wp.i18n; - - registerBlockType( 'radio-station/player', { + + /* --- Register Block --- */ + if ( !getBlockType('radio-station/player' ) ) { + registerBlockType( 'radio-station/player', { /* --- Block Settings --- */ title: __( '[Radio Station] Stream Player', 'radio-station' ), @@ -27,6 +30,7 @@ /* --- Player Options --- */ script: { type: 'string', default: 'default' }, volume: { type: 'number', default: 77 }, + volumes: { type: 'array', default: ['slider'] }, default: { type: 'boolean', default: false }, /* --- Player Styles --- */ layout: { type: 'string', default: 'horizontal' }, @@ -44,7 +48,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/player', className: 'radio-block-player', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/player', className: 'radio-player-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, /* === Player Content === */ @@ -119,6 +123,24 @@ value: atts.volume }) ), + /* --- Volume controls --- */ + el( PanelRow, {}, + el( SelectControl, { + multiple: true, + label: __( 'Volume Controls', 'radio-station' ), + help: __( 'Ctrl-Click to select multiple controls.', 'radio-station' ), + options: [ + { label: __( 'Volume Slider', 'radio-station' ), value: 'slider' }, + { label: __( 'Up and Down Buttons', 'radio-station' ), value: 'updown' }, + { label: __( 'Mute Button', 'radio-station' ), value: 'mute' }, + { label: __( 'Maximize Button', 'radio-station' ), value: 'max' }, + ], + onChange: ( value ) => { + props.setAttributes( { volumes: value } ); + }, + value: atts.volumes + }) + ), /* --- Default Player --- */ el( PanelRow, {}, el( ToggleControl, { @@ -131,7 +153,7 @@ }) ), /* --- Popup Player Button --- */ - /* el( PanelRow, {}, + el( PanelRow, {}, ( ( atts.pro ) && el( SelectControl, { label: __( 'Popup Player', 'radio-station' ), @@ -152,11 +174,11 @@ help: __( 'Popup Player Button available in Pro.', 'radio-station' ), }) ) - ), */ + ), ), /* === Player Styles === */ - el( PanelBody, { title: __( 'Player Styles', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + el( PanelBody, { title: __( 'Player Design', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, /* --- Player Layout --- */ el( PanelRow, {}, el( RadioControl, { @@ -172,19 +194,48 @@ }) ), /* --- Player Theme --- */ - el( PanelRow, {}, - el( SelectControl, { - label: __( 'Player Theme', 'radio-station' ), - options : [ - { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, - { label: __( 'Light', 'radio-station' ), value: 'light' }, - { label: __( 'Dark', 'radio-station' ), value: 'dark' }, - ], - onChange: ( value ) => { - props.setAttributes( { theme: value } ); - }, - value: atts.theme - }) + ( ( !atts.pro ) && + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Theme', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Light', 'radio-station' ), value: 'light' }, + { label: __( 'Dark', 'radio-station' ), value: 'dark' }, + ], + onChange: ( value ) => { + props.setAttributes( { theme: value } ); + }, + value: atts.theme + }) + ) + ), + /* [Pro] Extra Theme Color Options */ + ( ( atts.pro ) && + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Player Theme', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'Light', 'radio-station' ), value: 'light' }, + { label: __( 'Dark', 'radio-station' ), value: 'dark' }, + { label: __( 'Red', 'radio-station' ), value: 'red' }, + { label: __( 'Orange', 'radio-station' ), value: 'orange' }, + { label: __( 'Yellow', 'radio-station' ), value: 'yellow' }, + { label: __( 'Light Green', 'radio-station' ), value: 'light-green' }, + { label: __( 'Green', 'radio-station' ), value: 'green' }, + { label: __( 'Cyan', 'radio-station' ), value: 'cyan' }, + { label: __( 'Light Blue', 'radio-station' ), value: 'light-blue' }, + { label: __( 'Blue', 'radio-station' ), value: 'blue' }, + { label: __( 'Purple', 'radio-station' ), value: 'purple' }, + { label: __( 'Magenta', 'radio-station' ), value: 'magenta' }, + ], + onChange: ( value ) => { + props.setAttributes( { theme: value } ); + }, + value: atts.theme + }) + ) ), /* --- Player Buttons --- */ el( PanelRow, {}, @@ -202,6 +253,367 @@ value: atts.buttons }) ) + ), + + /* === [Pro] Player Colors === */ + ( ( atts.pro ) && + el( PanelBody, { title: __( 'Player Colors', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + + /* --- Text Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Text Color', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + disableAlpha: true, + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {text_color: color.hex} ); + }, + color: atts.text_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-text_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Text Color', 'radio-station' ) + }, + ( ('' != atts.text_color) ? atts.text_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {text_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Text Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.text_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-text_color {background-color:'+atts.text_color+'}' ) + ) + ) + ) + } ) + ) + ), + + /* --- Background Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Background Color', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {background_color: color.hex} ); + }, + color: atts.background_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-background_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Background Color', 'radio-station' ) + }, + ( ('' != atts.background_color) ? atts.background_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {background_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Background Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.background_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-background_color {background-color:'+atts.background_color+'}' ) + ) + ) + ) + } ) + ) + ), + + /* --- Playing Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Playing Highlight', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {playing_color: color.hex} ); + }, + color: atts.playing_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-playing_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Playing Highlight Color', 'radio-station' ) + }, + ( ('' != atts.playing_color) ? atts.playing_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {playing_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Playing Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.playing_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-playing_color {background-color:'+atts.playing_color+'}' ) + ) + ) + ) + } ) + ) + ), + + /* --- Buttons Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Buttons Highlight', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {buttons_color: color.hex} ); + }, + color: atts.buttons_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-buttons_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Button Highlight Color', 'radio-station' ) + }, + ( ('' != atts.buttons_color) ? atts.buttons_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {buttons_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Button Highlight Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.buttons_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-buttons_color {background-color:'+atts.buttons_color+'}' ) + ) + ) + ) + } ) + ) + ), + + /* --- Track Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Volume Track', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {track_color: color.hex} ); + }, + color: atts.track_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-track_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Volume Track Color', 'radio-station' ) + }, + ( ('' != atts.track_color) ? atts.track_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {track_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Volume Track Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.track_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-track_color {background-color:'+atts.track_color+'}' ) + ) + ) + ) + } ) + ) + ), + + /* --- Thumb Color --- */ + el( PanelRow, {}, + el( BaseControl, { + label: __( 'Volume Thumb', 'radio-station' ), + className: 'color-dropdown-control' + }, + el( Dropdown, { + renderContent: () => ( + el( ColorPicker, { + defaultValue: '', + onChangeComplete: color => { + props.setAttributes( {thumb_color: color.hex} ); + }, + color: atts.thumb_color + }) + ), + renderToggle: (args) => ( + el( 'div', {className: 'color-dropdown-buttons'}, + el ( Button, { + className: 'color-dropdown-thumb_color', + onClick: args.onToggle, + variant: 'secondary', + 'aria-expanded': args.isOpen, + 'aria-haspopup': 'true', + 'aria-label': __( 'Select Volume Thumb Color', 'radio-station' ) + }, + ( ('' != atts.thumb_color) ? atts.thumb_color : __( 'Select', 'radio-station' ) ) + ), + el( Button, { + onClick: () => { + props.setAttributes( {thumb_color: ''} ); + args.onClose(); + }, + isSmall: true, + variant: 'tertiary', + 'aria-label': __( 'Clear Volume Thumb Color Selection', 'radio-station' ) + }, + __( 'Clear', 'radio-station' ) + ), + ( ( '' != atts.thumb_color ) && + el( 'style', {}, '.components-button.is-secondary.color-dropdown-thumb_color {background-color:'+atts.thumb_color+'}' ) + ) + ) + ) + } ) + ) + ), + /* --- end color options --- */ + ) + ), + + /* === Advanced Options === */ + ( ( atts.pro ) && + el( PanelBody, { title: __( 'Advanced Options', 'radio-station' ), className: 'radio-block-controls', initialOpen: true }, + /* --- Current Show Display --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Current Show Display', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { currentshow: value } ); + }, + value: atts.currentshow + }) + ), + /* ---Now Playing Display --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Now Playing Track Display', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'On', 'radio-station' ), value: 'on' }, + { label: __( 'Off', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { nowplaying: value } ); + }, + value: atts.nowplaying + }) + ), + /* --- Track Animation --- */ + el( PanelRow, {}, + el( SelectControl, { + label: __( 'Track Animation', 'radio-station' ), + options : [ + { label: __( 'Plugin Setting', 'radio-station' ), value: 'default' }, + { label: __( 'No Animation', 'radio-station' ), value: 'none' }, + { label: __( 'Left to Right Ticker', 'radio-station' ), value: 'lefttoright' }, + { label: __( 'Right to Left Ticker', 'radio-station' ), value: 'righttoleft' }, + { label: __( 'Back and Forth', 'radio-station' ), value: 'backandforth' }, + { label: __( '', 'radio-station' ), value: 'off' }, + ], + onChange: ( value ) => { + props.setAttributes( { animation: value } ); + }, + value: atts.animation + }) + ), + /* --- Metadata URL --- */ + el( PanelRow, {}, + el( TextControl, { + label: __( 'Metadata Source URL', 'radio-station' ), + help: __( 'Defaults to Stream URL.', 'radio-station' ), + onChange: ( value ) => { + props.setAttributes( { metadata: value } ); + }, + value: atts.metadata + }) + ), + ) ) /* end panels */ ) @@ -214,5 +626,6 @@ * Returns nothing because this is a dynamic block rendered via PHP */ save: () => null, - }); + }); + } })(); diff --git a/blocks/schedule.js b/blocks/schedule.js index d052eed..ae1ae35 100644 --- a/blocks/schedule.js +++ b/blocks/schedule.js @@ -81,7 +81,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/schedule', className: 'radio-block-schedule', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/schedule', className: 'radio-schedule-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, /* === Schedule Display Panel === */ diff --git a/blocks/upcoming-shows.js b/blocks/upcoming-shows.js index 0be71a3..3041bbe 100644 --- a/blocks/upcoming-shows.js +++ b/blocks/upcoming-shows.js @@ -65,7 +65,7 @@ const atts = props.attributes; return ( el( Fragment, {}, - el( ServerSideRender, { block: 'radio-station/upcoming-shows', className: 'radio-block-schedule', attributes: atts } ), + el( ServerSideRender, { block: 'radio-station/upcoming-shows', className: 'radio-upcoming-block', attributes: atts } ), el( InspectorControls, {}, el( Panel, {}, diff --git a/docs/Display.md b/docs/Display.md index 327a224..48cf428 100644 --- a/docs/Display.md +++ b/docs/Display.md @@ -2,7 +2,7 @@ *** -For live display examples see [Radio Station Demo Site](http://demo.radiostation.pro). +For live display examples see [Radio Station Demo Site](https://demo.radiostation.pro). ## Styling @@ -12,18 +12,18 @@ You can find the base styles in the `/css` directory of the plugin, prefixed wit #### Custom Styling -You may wish to add your own styles to suit your site's look and feel. One easy way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. This is preferable to modifying the base style files, as your changes will be overwritten in a plugin update. +You may wish to add your own styles to suit your site's look and feel. One easy way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. This is preferable to modifying the base style files directly, as your changes will be overwritten in a plugin update. ## Timezone Conversions #### Automatic Display -Radio Station will now display a shift time converted to the users timezone under every shift time displayed. This conversion is based on the selected Timezone in your plugin settings and the default timezone detected in the users browser. This feature is on by default but can be disabled in the Plugin Settings. +Radio Station will now display a shift time converted to the users timezone under every shift time displayed. This conversion is based on the selected Timezone in your plugin settings and the default timezone detected in the users browser. This feature is on by default, but can also be disabled in the Plugin Settings. #### [Pro] User Timezone Switching -In [Radio Station Pro](https://radiostation.pro), there is an additional interface displayed in the Clock/Timezone shortcode (automatically displayed above schedules and also in the Radio Clock widget.) This allows your visitors/listeners to select a specific timezone other than what is in their browser. This can be useful for travellers especially, for keeping up with their home Shows, or tuning into a new local station without needing to update their computer clock. +In [Radio Station Pro](https://radiostation.pro), there is an additional interface displayed in the Clock/Timezone shortcode (automatically displayed above Schedules and also in the Radio Clock widget.) This allows your visitors/listeners to select a specific timezone other than what is detected in their browser. This can be useful for travellers especially, for keeping up with their home Shows, or tuning into a new local station without needing to update their computer clock. ## Automatic Pages @@ -34,7 +34,7 @@ Since one of the main purposes of this plugin is to provide your listeners with The following Views are available for the Master Schedule: -* Table - [default] responsive program grid in table form +* Table - [default] responsive program in table form * Tabbed - responsive styled list view with day selection tabs * List - unstyled plain list view for custom development use * Divs - [display issues] legacy unstyled div based view @@ -42,6 +42,14 @@ The following Views are available for the Master Schedule: Table or Tabbed are recommended for the best ready display result. By enabling the "Automatic Display" option for this page, the selected Master Schedule View will automatically replace the content of that page, using with default attributes. If you want to use other display options to customize what displays in the Schedule - or wish to combine it with other content on that page - then disable the Automatic Display option and instead manually place the [Master Schedule Shortcode](./Shortcodes.md#master-schedule-shortcode) `[master-schedule]` in the page content instead. +#### [Pro] Extra Master Schedule Views + +In [Radio Station Pro](https://radiostation.pro), two additional schedule views are available for displaying your station schedule: + +* Grid - vertically stacked responsive show grid based on days +* Calendar - responsive weekly schedule view with horizontal day show slider + +You can set these are the default view in the settings, or display them via the master schedule shortcode. If using the Grid view there is also an extra option to line up the grid according to the show time slots. ### Archive Pages @@ -70,7 +78,6 @@ If you have *not* customized templates to match your Theme, and yet they are pre We recognize this is may be a rather confusing and complex situation for existing users transitioning to 2.3.0, and if this is the case suggest rereading these section until it becomes clear. We are however confident that our solution going forward here is the best one, as it maintains backwards compatibility, honours the WordPress Template Hierarchy and enables implementing the revamped content displays. Fortunately, the majority of users will not be affected by these changes at all, but if you are the above options should easily resolve this. - ### Show Page Content The Show Page Content display has undergone the biggest of changes so far within the plugin, with an entirely new responsive layout for Shows that is displayed within the content area. This has required a lot of thought to allow for in-built flexibility, because it has to account for variations of what information is supplied for a Show and what is not. In other words, whether it is bare or full it, still needs to look good! Note also that there are some Plugin Options relating to this layout, namely where the info blocks are floated, and whether the content sections below are tabbed or display in full in a series. @@ -108,7 +115,11 @@ Since 2.3.0, image support has been added to Schedule Overrides so that they beh ### [Pro] Profile Images -In [Radio Station Pro](https://radiostation.pro), Host and Producer Profiles also support having images added, which then display on thee public profile pages for the host or producer. +In [Radio Station Pro](https://radiostation.pro), Show Host and Producer Profiles also support having images added, which then display on the public profile pages for the Host or Producer. + +### [Pro] Episode Images + +In [Radio Station Pro](https://radiostation.pro), Episodes also support having images added, which then display on the public page for that Show Episode. ### [Pro] Genre Images and Colors @@ -127,13 +138,11 @@ This is because some strings have never been translated into certain languages, #### Time Related Translations -Unlike other word strings, those involving dates and times are automatically translated by the plugin into your WordPress language using the `WP_Locale` class. - -This means you do not have to retranslate common strings for days of the week, month names, or am/pm meridian strings. +Unlike other word strings, those involving dates and times are automatically translated by the plugin into your WordPress language using the `WP_Locale` class. This means you do not have to retranslate common strings for days of the week, month names, or am/pm meridian strings. #### Using a Translation Plugin -If you wish to use a translation plugin instead, you can find the relevant language filed in the /languages/ directory of the plugin. Follow the translation plugin documentation on how to use these files. We welcome submissions of updated language files. Please contact us via email or submit a pull request via the [Github repository](https://github.com/netmix/radio-station/). +If you wish to use a translation plugin instead, you can find the relevant language filed in the `/languages/` directory of the plugin. Follow the translation plugin documentation on how to use these files. We welcome submissions of updated language files. Please contact us via email or submit a pull request via the [Github repository](https://github.com/netmix/radio-station/). diff --git a/docs/FAQ.md b/docs/FAQ.md index ea736f0..5bc6b2e 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -135,5 +135,7 @@ We are constantly improving and adding new features to [Radio Station Pro](https 1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://dashboard.freemius.com). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. 2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. +**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github if that version is not yet available via the WordPress repository. + We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. diff --git a/docs/Player.md b/docs/Player.md index bd54c67..7a7ddf0 100644 --- a/docs/Player.md +++ b/docs/Player.md @@ -11,16 +11,16 @@ The Radio Player is available as a Shortcode, Widget or Block. In Pro it is also Default settings for the Player can be set on the Plugin Settings page on the Player tab. These will be used in widgets wherever the widget options are set to "Default". This saves you from setting them twice, but also means that you can override these defaults in individual widgets as needed. (see [Options](./Options.md#player) for a list of these options.) -* Player Title: Display your Radio Station Title in Player by default. -* Player Image: Display your Radio Station Image in Player by default. -* Script: Default audio script to use for Radio Streaming Player. Ampliture, Howler or Jplayer. -* Fallback Scripts: Fallback audio scripts to try if/when the default Player script fails. -* Theme: Default Player Controls theme style. Light or dark to match your theme. -* Buttons: Default Player Buttons shape style. Circular, rounded or square. -* Volume Controls: Which volume controls to display in the Player by default. -* Player Debug Mode: Output player debug information in browser javascript console. -* Start Volume: Initial volume for when the Player starts playback. -* Single Player: Stop other Player instances when a Player is started. +* *Player Title*: Display your Radio Station Title in Player by default. +* *Player Image*: Display your Radio Station Image in Player by default. +* *Script*: Default audio script to use for Radio Streaming Player. Ampliture, Howler or Jplayer. +* *Fallback Scripts*: Fallback audio scripts to try if/when the default Player script fails. +* *Theme*: Default Player Controls theme style. Light or dark to match your theme. +* *Buttons*: Default Player Buttons shape style. Circular, rounded or square. +* *Volume Controls*: Which volume controls to display in the Player by default. +* *Player Debug Mode*: Output player debug information in browser javascript console. +* *Start Volume*: Initial volume for when the Player starts playback. +* *Single Player*: Stop other Player instances when a Player is started. Please note you will not see volume controls when using an iOS device as they are not supported. @@ -36,23 +36,23 @@ A Player block can be added via the Block Editor by clicking the blue + icon. Th [Radio Station Pro](https://radiostation.pro) includes a Sitewide Bar Streaming Player, including continuous uninterruptable playback via the integrated Teleporter page transition plugin. The Player Bar isn't added via the Widgets page, but is instead configured via the Plugin Settings page under the Player tab. It has the following main options: -* Bar Position: Fixed in the header or footer area (top or bottom of page, unaffected by scrolling.) -* Bar Height: Set the absolute height of the Player Bar in pixels. -* Fade In Player Bar: How long to take to fade in Player Bar after page load. -* Continous Playback: Enables uninterrupt playback while navigating between pages. -* Player Page Fade: How long to take to fade between pages when continous playback is enabled. -* Text Color and Background Color: Bar Player Default text and background colors. -* Autoresume Playback: attempts to autoresume playback for returning visitors. -* Popup Player Button: adds a button to open the player in a separate window. -* Current Show Display: whether to display the Current Show in the Player Bar. -* Now Playing Display: whether to display current track information via stream metadata. -* Metadata URL: alternative URL for metadata retrieval (normally via stream URL.) +* *Bar Position*: Fixed in the header or footer area (top or bottom of page, unaffected by scrolling.) +* *Bar Height*: Set the absolute height of the Player Bar in pixels. +* *Fade In Player Bar*: How long to take to fade in Player Bar after page load. +* *Continous Playback*: Enables uninterrupt playback while navigating between pages. +* *Player Page Fade*: How long to take to fade between pages when continous playback is enabled. +* *Text Color and Background Color*: Bar Player Default text and background colors. +* *Autoresume Playback*: attempts to autoresume playback for returning visitors. +* *Popup Player Button*: adds a button to open the player in a separate window. +* *Current Show Display*: whether to display the Current Show in the Player Bar. +* *Now Playing Display*: whether to display current track information via stream metadata. +* *Metadata URL*: alternative URL for metadata retrieval (normally via stream URL.) #### [Pro] Continous Player Integration Developers should note that the continous player page transitions are implemented by adding click event listeners to `a` link tags. This means that dynamic links - those that do not exist on page load but are added in content later - will need special treatment to preserve continuous playback. Some examples of these include some mobile menus or breadcrumbs that are created by javascript dynamically on hover, and AJAX loaded "more" or filtered content. -The solution to integrated these dynamic links is to use a filter to target the classes of the dynamic links. Knowing these claseses to target, Teleporter will then handle them via click event bubbling. Here's an example of how that can be done. (Note the format is just a comma separated list of classes without a `.` prefix:) +The solution to integrated these dynamic links is to use a filter to allow Teleporter (the page transition plugin) target the classes of the dynamic links. Knowing these claseses to target, Teleporter will then handle them via click event bubbling. Here's an example of how that can be done. (Note the format is just a comma separated list of classes without a `.` prefix:) ``` add_filter( 'teleporter_dynamic_link_classes', 'my_custom_dynamic_link_classes' ); @@ -62,22 +62,21 @@ function my_custom_dynamic_link_classes( $classes ) { } ``` -Similarly, if there are links that you wish to force to not transition for some reason, you can use the `teleporter_ignore_link_classes` filter in the same way. - +Similarly, if there are links that you wish to force to not transition for some reason, you can use the `teleporter_ignore_link_classes` filter in the same way. If you need to use selectors other than classes, you can use the filters `teleporter_dynamic_selectors` and `teleporter_ignore_selectors` to add those respectively also. #### [Pro] Extra Color Options The Player Bar also includes some extra color options to match the look and feel of your site (these are also used as defaults for other Player instances.) -* Playing Icon Highlight Color: Play button highlight color when playing stream. -* Control Icons Highlight Color: Volume Button Hover highlight and player loading highlight. -* Volume Track and Knob Colors: To style the volume slider track and it's thumb knob. +* *Playing Icon Highlight Color*: Play button highlight color when playing stream. +* *Control Icons Highlight Color*: Volume Button Hover highlight and player loading highlight. +* *Volume Track and Knob Colors*: To style the volume slider track and it's thumb knob. And in addition to the existing Light and Dark button themes, you can also choose from colored button themes of: Red, Orange, Yellow, Light Green, Green, Cyan, Light Blue, Blue, Purple or Magenta. Matching Play/Pause and Volume/Track control images will be used when you activate these theme options. ### Radio Player Shortcode -`[radio-player]` +`[radio-player]` or `[stream-player]` The following attributes are available for this shortcode: @@ -92,6 +91,8 @@ The following attributes are available for this shortcode: * *volumes* : Volume Control Buttons, comma separated. (slider,updown,mute,max) Default: all. * *default* : Use this as the default Player on the page. 0 or 1. Default 0. +Example Shortcode: `[radio-player url="http://example.com/my-stream-url/"]` + [Demo Site Example Output](https://demo.radiostation.pro/player-shortcode/) #### Using the Shortcode in Templates diff --git a/docs/Pro.md b/docs/Pro.md index ae6913c..1971ca9 100644 --- a/docs/Pro.md +++ b/docs/Pro.md @@ -11,6 +11,16 @@ Note that additional Pro Documentation will be added in the relevant section as If you are interested in testing cutting edge features in development for this next release, read about [Installing the Development Version](./FAQ.md#how-do-i-install-the-latest-development-version-for-testing) and [Pro Beta Version Testing](./#pro-beta-version-testing). +### Radio Station Plus + +Note that the following features are *not* available in Radio Station Plus: + +- Persistent Stream Player Bar +- Visual Schedule Editor Interface +- Episodes and Segments (and related data/features) +- Page Builder Modules (for Elementor and Beaver Builder) + + #### API [Episodes Data Endpoint](./API.md#pro-episodes-endpoint) diff --git a/docs/Roadmap.md b/docs/Roadmap.md index 3b18973..b18b10d 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -17,6 +17,7 @@ Normal = Feature Completed **bold** = Next Major Feature(s) + | Free | Version | Pro | Version | | --- | --- | --- | --- | | SCHEDULE | @@ -41,8 +42,10 @@ Normal = Feature Completed | EXTRAS | | Host / Producer / Show Editor Roles | 2.3.0 | with Role Assignment Interface | 2.3.0 | | REST/Feed API Endpoints | 2.3.0 | with Episodes, Hosts and Producers | 2.4.1 | -| Standard HTML Markup Output | 2.3.0 | *with Schema.Org SEO Markup* | | +| Standard HTML Markup Output | 2.3.0 | *with SEO Schema Markup* | | | Show Genre Assignments | 2.2.8 | Episode Guests and Topics | 2.4.1 | + + [comment]: # (Show and Override Feeds) Interested in going Pro? For a full feature list comparison, [click here to find out more about Radio Station Pro](https://radiostation.pro/pricing/). diff --git a/freemius-pricing/178afa6030e76635dbe835e111d2c507.png b/freemius-pricing/178afa6030e76635dbe835e111d2c507.png new file mode 100644 index 0000000000000000000000000000000000000000..d5debf2b75c672c2f8c7296ef12f952fe9f0c122 GIT binary patch literal 6020 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 literal 0 HcmV?d00001 diff --git a/freemius-pricing/27b5a722a5553d9de0170325267fccec.png b/freemius-pricing/27b5a722a5553d9de0170325267fccec.png new file mode 100644 index 0000000000000000000000000000000000000000..943dad7b163e2f0915fa2561f41a4c748e46859d GIT binary patch literal 6501 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 LNkvXXu0mjfQZ~4F literal 0 HcmV?d00001 diff --git a/freemius-pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png b/freemius-pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png new file mode 100644 index 0000000000000000000000000000000000000000..33902b4a99ec9a3199804151277daabfc610406b GIT binary patch literal 9380 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r literal 0 HcmV?d00001 diff --git a/freemius-pricing/5480ed23b199531a8cbc05924f26952b.png b/freemius-pricing/5480ed23b199531a8cbc05924f26952b.png new file mode 100644 index 0000000000000000000000000000000000000000..eda6d6fb12df205fd9fd23ff0ff63d1d57d2ec97 GIT binary patch literal 7262 zcmaKRWl$X5+AZ!R1b2qPeFhsexFxtl@L_Nm7zTIO03n9EyM;h-Cjo*6cTI2z5S&ZS zci!{eANSm@>gxUMT5CPBS5TzcwEO zDx`uTQU`8>^s+?2P-Lv(Rxp5yv!yLe8)j+klz{r)l|ixaA#i2zZhO` zXV*veqnMPpt0mMCh6Gr_Z0%hnfk!PJK!ClqB+x)e4W#BO2eY$R_C>&SeARWKzK&2) zYoOF~fP}Z$qkuCEX$kOlc5-nS^OgkuOIPf1{db!W2>2HS=_m>OpP~%aGy!sO1Pma= z3*v!-gh2pdFfUj@5CVa41NcE;5Fd!256sU4hKLF9i-ACZe_z1IYzS)`F>QIpf9HCP zB!PBFq^lSopO=>xua^KX9AV1`78Mo!tHICD^N8Sa_i;g5dh@urGyS6=4|9hi>|K%e za2LQ|MN2EV2T~IFSn2eBPF>d|+PC-@aCUvnEDuB2d%~<05pZX~zgiZv z|L?U3{a3wzbFKe-EyDkm%lB9e-`|7%zYhAJtw;C#UH-?mkHLS84|92RJL0dc^W6zi zP$(x<k*VL5wQ5*z4KxsueA{`y$quQmJ$DRFpj$_pr+= z8Y^}Z0fwGz28~Yr#p{P72s%v^Ue8Jp(amgX2k7?ZH)`)KzI)FVL-r^dSxtz?{bdiX z?lC6wcQhRfjNZtgpf+@xn}^YvX6ffl%4mRK%JJ?HRL&EIxwsA)w0Kn~>8^)|B$lV> zbKJxG-=sU-`B4J8JA1$3C*M3xZdlVX>C+S$h|YDb!tQz6o=R}eg-&VVd2hQl0lmDr zL0!~Nf;>FjVb#7pSqV^<2Hm5scMwQOoLYE=FjEEHT@tu4zs2e4c;F_;mRd$BHN|r0 z+2~kdkh{P2?nNm$LcJoErX?hjp1p0RkLJn28$%T!dI)nfOk%$8A;B3dR~_ywv0cY5 zzk4|1>FMammTtU{z~UV|ehabv|Qy z6=u)M&~tsA4pKVAvd2;y_*mUR`Q1#S_xNT<3CrLUJ(dE83zk{c0|8%lg#~;1M(^?Q zcUdf|XINN_?bS6%HwJQxF6maRZF3ua!pIf#MD)2^f`<^5wTG1>f{KiJf}jTp)GHct z!3>aXbp-Y2wUwh$f{u<%tW(2IdlJ&g)pzH2oT?jECG!j@s6Wd-Byr|}Zbxo7Db%WQ zIL-tya1jUn3S<56(}i73N`{0L;yxIU@t3^z(gIjwaYp~_*SlJb#%r%hR_=L0!MgbB zd#1p^QHI2%URBR+Mylw;p8vz22c0tA?JsgM?2Z&1vn=f+S-?6~Bgp{$wU*JnY>0j= zk8dsQGO7WgIu%R#bt2-oZ1Sub=GSMPjgI!p(9|#@DIq&;TCVq_ST4}kyV1u~=8O|V zNr5Rz>WV?D80XQI<``f&ni1>4UdYkvoCV>cH@ev_@hhe7Kg?O}YCZVc(wDfEiRq$_ z#0~~c{Q;hJc+lT^l;(pL9%s4BnP&6-+lh%yf0z>?ZGUPntFW4v$PU@_ER+@QbDA4H zDfaI0y{G0Po5`}?@Mha8jV*wqiXvZ$h>)hFT=gp_#4J|rR`eSdid6$c&{V04Zbhgf zRO~Z`_Nyd!=}KqhKHa=v+EG3u{A_7=E$nM%qA=*WEFLY&@8on~$j(HCUG!}(EBC}> z1f-h1fV;aI%Xg;xI`sILCScwAJcBYqR+MRnD5BX~B(2rV@##7wd?8x)-L%JRW%c)Y zH7-dYFyZfhkm+y@Z{LOpkTIcSa>smH9DQK~lx{qI!6n}WC-#T0ddAIeX~}X#u+15P z6Jiy;>e>q=*cCK>oA2eGJwU{Mql>s_;&l3?MhHJsu)NGp<#zs1)o3>rYwY-mQSm{s%1M64jovh{^|D929p48S4X+k`R0J!IZV;8tA*jJe@?}_F>=}ypf z7cxYTWK?c@!*yfjqlGfl5B2kcK)aU$B>AP`nxx{n+t(iOQKoTQVT P2NY+Q0x0a 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 CHc1fx literal 0 HcmV?d00001 diff --git a/freemius-pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg b/freemius-pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg new file mode 100644 index 0000000..930d34e --- /dev/null +++ b/freemius-pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius-pricing/c03f665db27af43971565560adfba594.png b/freemius-pricing/c03f665db27af43971565560adfba594.png new file mode 100644 index 0000000000000000000000000000000000000000..1d0ebff74ad4ebfdd89a508a2880c787f11412ab GIT binary patch literal 6208 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 R=YKv_QP7lsEo%|*{{e1;dy)VE literal 0 HcmV?d00001 diff --git a/freemius-pricing/dd89563360f0272635c8f0ab7d7f1402.png b/freemius-pricing/dd89563360f0272635c8f0ab7d7f1402.png new file mode 100644 index 0000000000000000000000000000000000000000..e2bd9b32658e1479a6589d5467cdff16f1c2bce0 GIT binary patch literal 12087 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 Za48O6_8h{j_kWJEON%Rr)rc4d{x3#FmGJ-o literal 0 HcmV?d00001 diff --git a/freemius-pricing/e366d70661d8ad2493bd6afbd779f125.png b/freemius-pricing/e366d70661d8ad2493bd6afbd779f125.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb194450232df293d4269448de456ea27c6f5d4 GIT binary patch literal 11124 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 S1TbR&0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius-pricing/fde48e4609a6ddc11d639fc2421f2afd.png b/freemius-pricing/fde48e4609a6ddc11d639fc2421f2afd.png new file mode 100644 index 0000000000000000000000000000000000000000..045d4b25f9727fada65dcfd66cf0a0a3d167ee84 GIT binary patch literal 11237 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

      {^6Retfv z6e)+Y86 z&-OAhn&%B~c$`zOH~YnViV(=PdD(}Fcr6gb z@qCJTX)KfxzL`hqdq$pyR{#-Ot7VpvXTX&Lf;o10Uz!M^YMI*SFN%7P`VNK(D>QTdyCP-Z<%CK(t*T4j;W{3$D2mM!AexlA- zUb<}1Dx;~3vaWm)z9em_tc|P?cssQ$Hj)Y6^M;Y^!#S7DAf(wTVsi4od$#co(U{KZ4KkEm$NX_fw^6flAOA1wX{X>{{THMY+L{U literal 0 HcmV?d00001 diff --git a/freemius-pricing/freemius-pricing.css b/freemius-pricing/freemius-pricing.css new file mode 100644 index 0000000..dc46e1c --- /dev/null +++ b/freemius-pricing/freemius-pricing.css @@ -0,0 +1,9 @@ +/* Radio Station - Freemius Pricing Page v2 Styles */ + +.fs-packages .fs-plan-features ul li:first-child {display: none;} +.fs-packages li:first-child .fs-plan-features ul li:first-child {display: block; font-weight: bold;} +.fs-packages li:nth-child(3) .fs-plan-features ul li:nth-child(3) {font-weight: bold;} +.fs-packages .fs-plan-features ul li:nth-child(2) {font-weight: bold;} +.fs-packages li:first-child .fs-plan-features ul li:nth-child(2) {font-weight: normal;} + +#fs_pricing_app .fs-packages .fs-button {background: #2da1d0;} diff --git a/freemius-pricing/freemius-pricing.js b/freemius-pricing/freemius-pricing.js new file mode 100644 index 0000000..583c201 --- /dev/null +++ b/freemius-pricing/freemius-pricing.js @@ -0,0 +1,2 @@ +/*! For license information please see freemius-pricing.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Freemius=t():e.Freemius=t()}(self,(()=>(()=>{var e={487:e=>{var t={utf8:{stringToBytes:function(e){return t.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(t.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n{var t,n;t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n={rotl:function(e,t){return e<>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&n.rotl(e,8)|4278255360&n.rotl(e,24);for(var t=0;t0;e--)t.push(Math.floor(256*Math.random()));return t},bytesToWords:function(e){for(var t=[],n=0,a=0;n>>5]|=e[n]<<24-a%32;return t},wordsToBytes:function(e){for(var t=[],n=0;n<32*e.length;n+=8)t.push(e[n>>>5]>>>24-n%32&255);return t},bytesToHex:function(e){for(var t=[],n=0;n>>4).toString(16)),t.push((15&e[n]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],n=0;n>>6*(3-r)&63)):n.push("=");return n.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var n=[],a=0,i=0;a>>6-2*i);return n}},e.exports=n},477:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),i=n.n(a),r=n(645),o=n.n(r)()(i());o.push([e.id,'#root,#fs_pricing_app{background:#f1f1f1;height:auto;line-height:normal;font-size:13px;margin:0}#root,#root span,#root input,#root select,#root label,#root a,#root div,#root th,#root td,#fs_pricing_app,#fs_pricing_app span,#fs_pricing_app input,#fs_pricing_app select,#fs_pricing_app label,#fs_pricing_app a,#fs_pricing_app div,#fs_pricing_app th,#fs_pricing_app td{font-family:Open Sans,sans-serif}#root h1,#root h2,#root h3,#root h4,#root ul,#root blockquote,#fs_pricing_app h1,#fs_pricing_app h2,#fs_pricing_app h3,#fs_pricing_app h4,#fs_pricing_app ul,#fs_pricing_app blockquote{margin:0;padding:0;text-align:center}#root h1,#fs_pricing_app h1{font-size:2.5em}#root h2,#fs_pricing_app h2{font-size:1.5em}#root h3,#fs_pricing_app h3{font-size:1.2em}#root ul,#fs_pricing_app ul{list-style-type:none}#root p,#fs_pricing_app p{font-size:.9em}#root p,#root blockquote,#fs_pricing_app p,#fs_pricing_app blockquote{color:#606060}#root strong,#fs_pricing_app strong{font-weight:700}#root li,#root dd,#fs_pricing_app li,#fs_pricing_app dd{margin:0}#root .fs-app-header .fs-page-title,#fs_pricing_app .fs-app-header .fs-page-title{margin:15px 0;text-align:left}#root .fs-app-header .fs-page-title h2,#fs_pricing_app .fs-app-header .fs-page-title h2{vertical-align:middle}#root .fs-app-header .fs-page-title h3,#fs_pricing_app .fs-app-header .fs-page-title h3{font-size:small;padding:3px;border-radius:2px;vertical-align:sub}#root .fs-app-header .fs-plugin-title-and-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo{padding-top:12px;padding-bottom:12px;border:1px solid #ccc;border-radius:3px;text-align:center;background:#fff}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#root .fs-app-header .fs-plugin-title-and-logo h1,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo h1{display:inline-block;vertical-align:middle;margin:0 10px}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:48px;height:48px}#root .fs-trial-message,#fs_pricing_app .fs-trial-message{padding:20px;background:#ffe4bf;color:#e07b00;font-weight:700;text-align:center;border:2px solid #ff8c00;font-size:1.2em}#root .fs-app-main,#fs_pricing_app .fs-app-main{text-align:center}#root .fs-app-main .fs-section,#fs_pricing_app .fs-app-main .fs-section{margin:auto;display:block}#root .fs-app-main .fs-section .fs-section-header,#fs_pricing_app .fs-app-main .fs-section .fs-section-header{font-weight:700}#root .fs-app-main>.fs-section,#fs_pricing_app .fs-app-main>.fs-section{padding:20px;margin:4em auto 0}#root .fs-app-main>.fs-section:nth-child(even),#fs_pricing_app .fs-app-main>.fs-section:nth-child(even){background:#fff}#root .fs-app-main>.fs-section>header,#fs_pricing_app .fs-app-main>.fs-section>header{margin:0 0 3em}#root .fs-app-main>.fs-section>header h2,#fs_pricing_app .fs-app-main>.fs-section>header h2{margin:0;font-size:2.5em}#root .fs-app-main .fs-section--plans-and-pricing,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing{padding:20px 60px;margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section{margin:1.5em auto 0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child{margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount{font-weight:700;font-size:small}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header{text-align:center;background:#f9f9f9;padding:20px;border-radius:5px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2{margin-bottom:10px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4{font-weight:400}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles{display:inline-block;vertical-align:middle;padding:0 10px;width:auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles{border:1px solid #ccc;border-radius:20px;overflow:hidden}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li{display:inline-block;font-weight:700;margin:0;padding:10px;cursor:pointer}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle{background:#1fbc99;color:#fff}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:not(:last-child),#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:not(:last-child){border-right:1px solid #ccc}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation{padding:15px;background:#fff;border:1px solid #ccc;border-radius:8px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2{margin-bottom:10px;font-weight:700}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p{font-size:small;margin:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee{max-width:857px;margin:30px auto;position:relative}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title{color:#1fbc99;font-weight:700;margin-bottom:15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message{font-size:small;line-height:20px;margin-bottom:15px;padding:0 15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img{position:absolute;width:90px;top:50%;right:0;margin-top:-45px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge{display:inline-block;vertical-align:middle;position:relative;box-shadow:none;background:rgba(0,0,0,0)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge{margin-left:20px;margin-top:13px}#root .fs-app-main .fs-section--testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials{border-top:1px solid #ccc;border-bottom:1px solid #ccc;padding:4em 4em 1.6em}#root .fs-app-main .fs-section--testimonials .fs-section-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-section-header{margin-left:-30px;margin-right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav{margin:40px auto auto;display:block;width:auto;position:relative}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{top:50%;border:1px solid #c9c9c9;border-radius:14px;cursor:pointer;margin-top:11px;position:absolute}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon{display:inline-block;height:1em;width:1em;line-height:1em;color:#c9c9c9;padding:5px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev{margin-left:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track{margin:auto;overflow:hidden;position:relative;display:block;padding-top:45px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials{width:10000px;display:block;position:relative;transition:left .5s ease,right .5s ease}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{float:left;font-size:small;border-radius:15px;position:relative;width:340px;box-sizing:border-box;margin:0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{box-sizing:border-box}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating{color:#f7941d}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{background:#f7f7f7;padding:10px;margin:0 2em;border:1px solid #e2e2e2}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{border-radius:0 0 20px 20px;border-top:0 none}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header{border-bottom:0 none;border-radius:20px 20px 0 0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo{border:1px solid #e4e4e4;border-radius:44px;padding:5px;background:#fff;width:76px;height:76px;position:relative;margin-top:-54px;left:50%;margin-left:-44px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img{max-width:100%;border-radius:40px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4{margin:15px 0 6px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote{color:#2da1d0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message{line-height:18px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author{margin-top:35px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name{font-weight:700;margin-bottom:2px;color:#505050}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author:last-child,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author:last-child{color:#8f8f8f}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{margin:45px 0 25px;position:relative}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li{position:relative;display:inline-block;margin:0 8px}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button{cursor:pointer;border:1px solid #d2d2d2;vertical-align:middle;display:inline-block;line-height:0;width:8px;height:8px;padding:0;color:#0000;outline:none;border-radius:4px;overflow:hidden}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span{display:inline-block;width:100%;height:100%;background:#f7f7f7}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button{border:0 none}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span{background:#c9c9c9}#root .fs-app-main .fs-section--faq,#fs_pricing_app .fs-app-main .fs-section--faq{background:#f1f1f1}#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{max-width:850px;text-align:left;columns:2;column-gap:20px}@media only screen and (max-width: 600px){#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{columns:1}}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item{width:100%;display:inline-block;vertical-align:top;margin:0 0 20px;overflow:hidden}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{margin:0;text-align:left}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3{background:#f7f7f7;padding:15px;font-weight:700;border:1px solid #dbdbdb;border-bottom:1px solid #dedede;border-radius:3px 3px 0 0}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{background:#fff;font-size:small;padding:15px;line-height:20px;border:1px solid #dbdbdb;border-top:0 none;border-radius:0 0 3px 3px}#root .fs-button,#fs_pricing_app .fs-button{background:#e0e0e0;padding:10px;display:inline-block;text-transform:uppercase;font-weight:700;font-size:18px;width:100%;border-radius:4px;border-bottom:3px solid #a0a0a0}#root .fs-button.fs-button--size-small,#fs_pricing_app .fs-button.fs-button--size-small{font-size:14px;width:auto}#root .fs-placeholder:before,#fs_pricing_app .fs-placeholder:before{content:"";display:inline-block}#root .fs-modal,#fs_pricing_app .fs-modal{position:fixed;inset:0;z-index:1000;zoom:1;text-align:left;display:block!important}#root .fs-modal .fs-modal-content-container,#fs_pricing_app .fs-modal .fs-modal-content-container{display:block;position:absolute;left:50%;background:#fff;box-shadow:0 0 8px 2px #0000004d}#root .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header{background:#534741;padding:15px}#root .fs-modal .fs-modal-content-container .fs-modal-header h3,#root .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close{color:#fff}#root .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content{font-size:1.2em}#root .fs-modal--loading,#fs_pricing_app .fs-modal--loading{background-color:#0000004d}#root .fs-modal--loading .fs-modal-content-container,#fs_pricing_app .fs-modal--loading .fs-modal-content-container{width:220px;margin-left:-126px;padding:15px;border:1px solid #ccc;text-align:center;top:50%}#root .fs-modal--loading .fs-modal-content-container span,#fs_pricing_app .fs-modal--loading .fs-modal-content-container span{display:block;font-weight:700;font-size:16px;text-align:center;color:#29abe1;margin-bottom:10px}#root .fs-modal--loading .fs-modal-content-container i,#fs_pricing_app .fs-modal--loading .fs-modal-content-container i{display:block;width:128px;margin:0 auto;height:15px;background:url(//img.freemius.com/blue-loader.gif)}#root .fs-modal--refund-policy,#root .fs-modal--trial-confirmation,#fs_pricing_app .fs-modal--refund-policy,#fs_pricing_app .fs-modal--trial-confirmation{background:rgba(0,0,0,.7)}#root .fs-modal--refund-policy .fs-modal-content-container,#root .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container{width:510px;margin-left:-255px;top:20%}#root .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#root .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close{line-height:24px;font-size:24px;position:absolute;top:-12px;right:-12px;cursor:pointer}#root .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#root .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content{background:#f2f2f2;height:100%;padding:1px 15px}#root .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#root .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer{padding:20px;text-align:right;border-top:1px solid #e4e4e4;background:#f2f2f2}#root .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#root .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial{margin:0 7px}#root .fs-modal--trial-confirmation .fs-button,#fs_pricing_app .fs-modal--trial-confirmation .fs-button{width:auto;font-size:13px;line-height:26px;height:28px;padding:0 10px 1px;border-width:1px;text-transform:none;font-weight:400;box-shadow:0 1px #ccc;background:#f7f7f7;border-color:#ccc;color:#555;cursor:pointer;outline:none}#root .fs-modal--trial-confirmation .fs-button:hover,#fs_pricing_app .fs-modal--trial-confirmation .fs-button:hover{background:#fafafa;border-color:#999;color:#23282d}#root .fs-modal--trial-confirmation .fs-button:active,#fs_pricing_app .fs-modal--trial-confirmation .fs-button:active{background:#eee;border-color:#999;box-shadow:inset 0 2px 5px -3px #00000080;transform:translateY(1px)}#root .fs-modal--trial-confirmation .fs-button.fs-button--primary,#fs_pricing_app .fs-modal--trial-confirmation .fs-button.fs-button--primary{background:#0085ba;border-color:#0073aa #006799 #006799;box-shadow:0 1px #006799;color:#fff;text-decoration:none}@media only screen and (max-width: 768px){#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{display:none!important}#root .fs-app-main .fs-section>header h2,#fs_pricing_app .fs-app-main .fs-section>header h2{font-size:1.5em}}@media only screen and (max-width: 455px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}#root .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span,#fs_pricing_app .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span{display:none}}@media only screen and (max-width: 375px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}}@media only screen and (max-width: 445px){#root .fs-app-header .fs-page-title h3,#fs_pricing_app .fs-app-header .fs-page-title h3{margin-left:0;margin-top:10px}}\n',""]);const s=o},333:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),i=n.n(a),r=n(645),o=n.n(r)()(i());o.push([e.id,"#fs_pricing_app .fs-modal,#fs_pricing_wrapper .fs-modal,#fs_pricing_wrapper #fs_pricing_app .fs-modal{position:fixed;inset:0;z-index:1000;zoom:1;text-align:left;display:block!important}#fs_pricing_app .fs-modal .fs-modal-content-container,#fs_pricing_wrapper .fs-modal .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container{display:block;position:absolute;left:50%;background:#fff;box-shadow:0 0 8px 2px #0000004d}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header{background:#534741;padding:15px}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close{color:#fff}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content{font-size:1.2em}#fs_pricing_app .fs-modal--loading,#fs_pricing_wrapper .fs-modal--loading,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading{background-color:#0000004d}#fs_pricing_app .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container{width:220px;margin-left:-126px;padding:15px;border:1px solid #ccc;text-align:center;top:50%}#fs_pricing_app .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container span{display:block;font-weight:700;font-size:16px;text-align:center;color:#29abe1;margin-bottom:10px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container i{display:block;width:128px;margin:0 auto;height:15px;background:url(//img.freemius.com/blue-loader.gif)}#fs_pricing_app .fs-modal--refund-policy,#fs_pricing_app .fs-modal--trial-confirmation,#fs_pricing_wrapper .fs-modal--refund-policy,#fs_pricing_wrapper .fs-modal--trial-confirmation,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation{background:rgba(0,0,0,.7)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container{width:510px;margin-left:-255px;top:20%}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close{line-height:24px;font-size:24px;position:absolute;top:-12px;right:-12px;cursor:pointer}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content{background:#f2f2f2;height:100%;padding:1px 15px}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer{padding:20px;text-align:right;border-top:1px solid #e4e4e4;background:#f2f2f2}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial{margin:0 7px}#fs_pricing_app .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button{width:auto;font-size:13px;line-height:26px;height:28px;padding:0 10px 1px;border-width:1px;text-transform:none;font-weight:400;box-shadow:0 1px #ccc;background:#f7f7f7;border-color:#ccc;color:#555;cursor:pointer;outline:none}#fs_pricing_app .fs-modal--trial-confirmation .fs-button:hover,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button:hover,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button:hover{background:#fafafa;border-color:#999;color:#23282d}#fs_pricing_app .fs-modal--trial-confirmation .fs-button:active,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button:active,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button:active{background:#eee;border-color:#999;box-shadow:inset 0 2px 5px -3px #00000080;transform:translateY(1px)}#fs_pricing_app .fs-modal--trial-confirmation .fs-button.fs-button--primary,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button.fs-button--primary,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button.fs-button--primary{background:#0085ba;border-color:#0073aa #006799 #006799;box-shadow:0 1px #006799;color:#fff;text-decoration:none}\n",""]);const s=o},267:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),i=n.n(a),r=n(645),o=n.n(r)()(i());o.push([e.id,'#root .fs-package,#fs_pricing_app .fs-package{display:inline-block;vertical-align:top;background:#fff;border-bottom:3px solid #e8e8e8;width:315px;box-sizing:border-box}#root .fs-package:first-child,#root .fs-package+.fs-package,#fs_pricing_app .fs-package:first-child,#fs_pricing_app .fs-package+.fs-package{border-left:1px solid #e8e8e8}#root .fs-package:last-child,#fs_pricing_app .fs-package:last-child{border-right:1px solid #e8e8e8}#root .fs-package:not(.fs-featured-plan):first-child,#root .fs-package:not(.fs-featured-plan):first-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child .fs-plan-title{border-top-left-radius:10px}#root .fs-package .fs-package-content,#fs_pricing_app .fs-package .fs-package-content{vertical-align:middle;padding-bottom:30px}#root .fs-package .fs-plan-title,#fs_pricing_app .fs-package .fs-plan-title{padding:10px 0;background:#f8f8f9;text-transform:uppercase;border-bottom:1px solid #f1f1f2;border-right:1px solid #f1f1f2;width:100%;text-align:center}#root .fs-package .fs-plan-title:last-child,#fs_pricing_app .fs-package .fs-plan-title:last-child{border-right:none}#root .fs-package .fs-plan-description,#root .fs-package .fs-undiscounted-price,#root .fs-package .fs-licenses,#root .fs-package .fs-upgrade-button,#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-licenses,#fs_pricing_app .fs-package .fs-upgrade-button,#fs_pricing_app .fs-package .fs-plan-features{margin-top:10px}#root .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-plan-description{text-transform:uppercase}#root .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-undiscounted-price{margin:auto;position:relative;display:inline-block;color:gray;top:6px}#root .fs-package .fs-undiscounted-price:after,#fs_pricing_app .fs-package .fs-undiscounted-price:after{content:"";border-bottom:1px solid #dd89a8;position:absolute;left:-2px;top:50%;width:100%;padding:0 2px}#root .fs-package .fs-selected-pricing-amount,#fs_pricing_app .fs-package .fs-selected-pricing-amount{margin:5px 0}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol{font-size:39px}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer{font-size:58px;margin:0 5px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{display:inline-block;vertical-align:middle}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer){line-height:18px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{display:block;font-size:12px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction{vertical-align:top}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{vertical-align:bottom}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{color:#606060}#root .fs-package .fs-selected-pricing-amount-free,#fs_pricing_app .fs-package .fs-selected-pricing-amount-free{font-size:48px}#root .fs-package .fs-selected-pricing-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-cycle{margin-bottom:5px;text-transform:uppercase;color:#606060}#root .fs-package .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity{color:#47aed6}#root .fs-package .fs-selected-pricing-license-quantity .fs-tooltip,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity .fs-tooltip{margin-left:5px}#root .fs-package .fs-upgrade-button-container,#fs_pricing_app .fs-package .fs-upgrade-button-container{padding:0 13px;display:block}#root .fs-package .fs-upgrade-button-container .fs-upgrade-button,#fs_pricing_app .fs-package .fs-upgrade-button-container .fs-upgrade-button{margin-top:20px;margin-bottom:5px;outline:none;cursor:pointer}#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-features{text-align:left;margin-left:13px}#root .fs-package .fs-plan-features li,#fs_pricing_app .fs-package .fs-plan-features li{font-size:16px;display:flex;margin-bottom:8px}#root .fs-package .fs-plan-features li:not(:first-child),#fs_pricing_app .fs-package .fs-plan-features li:not(:first-child){margin-top:8px}#root .fs-package .fs-plan-features li>span,#root .fs-package .fs-plan-features li .fs-tooltip,#fs_pricing_app .fs-package .fs-plan-features li>span,#fs_pricing_app .fs-package .fs-plan-features li .fs-tooltip{font-size:small;vertical-align:middle;display:inline-block}#root .fs-package .fs-plan-features li .fs-feature-title,#fs_pricing_app .fs-package .fs-plan-features li .fs-feature-title{margin:0 5px;color:#606060;max-width:260px;overflow-wrap:break-word}#root .fs-package .fs-plan-features li .fs-icon,#root .fs-package .fs-plan-features li .fs-tooltip,#fs_pricing_app .fs-package .fs-plan-features li .fs-icon,#fs_pricing_app .fs-package .fs-plan-features li .fs-tooltip{color:#2da1d0}#root .fs-package .fs-support-and-main-features,#fs_pricing_app .fs-package .fs-support-and-main-features{margin-top:12px;padding-top:18px;padding-bottom:18px}#root .fs-package .fs-support-and-main-features .fs-plan-support,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-support{margin-bottom:15px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li{font-size:small}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title{margin:0 2px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child),#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child){margin-top:5px}#root .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities{border-collapse:collapse;position:relative;width:100%}#root .fs-package .fs-license-quantities,#root .fs-package .fs-license-quantities input,#fs_pricing_app .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities input{cursor:pointer}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span{background:#2da1d0;color:#fff;display:inline;padding:4px 8px;border-radius:4px;font-weight:700;margin:0 5px;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount{visibility:hidden}#root .fs-package .fs-license-quantities .fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container{line-height:30px;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;font-size:small}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected{background:#2da1d0;color:#fff}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected .fs-license-quantity-discount>span,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected .fs-license-quantity-discount>span{background:#fff;color:#2da1d0}#root .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price),#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price){text-align:left}#root .fs-package .fs-license-quantities .fs-license-quantity,#root .fs-package .fs-license-quantities .fs-license-quantity-discount,#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{vertical-align:middle;color:#606060}#root .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity{position:relative;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity input,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity input{position:relative;margin-top:-1px;margin-left:7px;margin-right:7px}#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{position:relative;margin-right:auto;padding-right:7px;white-space:nowrap;font-variant-numeric:tabular-nums;text-align:right}#root .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child),#fs_pricing_app .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child){border-color:#0000}#root .fs-package.fs-featured-plan .fs-plan-title,#fs_pricing_app .fs-package.fs-featured-plan .fs-plan-title{background:#1fbc99}#root .fs-package .fs-most-popular,#fs_pricing_app .fs-package .fs-most-popular{display:none}#root .fs-package.fs-featured-plan .fs-most-popular,#fs_pricing_app .fs-package.fs-featured-plan .fs-most-popular{display:block;line-height:2.8em;margin-top:-2.8em;border-radius:20px 20px 0 0;color:#fff;background:#158369;text-transform:uppercase;font-size:14px}#root .fs-package.fs-featured-plan .fs-plan-title,#fs_pricing_app .fs-package.fs-featured-plan .fs-plan-title{color:#fff}#root .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity{color:#1fbc99}#root .fs-package.fs-featured-plan .fs-license-quantity-selected .fs-license-quantity:before,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantity-selected .fs-license-quantity:before{content:"";position:absolute;top:0;bottom:0;left:-1px;border-left:2px solid #1fbc99}#root .fs-package.fs-featured-plan .fs-license-quantity-selected .fs-license-quantity-price:after,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantity-selected .fs-license-quantity-price:after{content:"";position:absolute;top:0;bottom:0;right:-1px;border-right:2px solid #1fbc99}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected{background:#1fbc99}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount>span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount>span{color:#1fbc99}#root .fs-package.fs-featured-plan .fs-upgrade-button,#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-upgrade-button,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-discount span{background:#1fbc99;color:#fff}#root .fs-package.fs-featured-plan .fs-upgrade-button,#fs_pricing_app .fs-package.fs-featured-plan .fs-upgrade-button{border-bottom:3px solid #15846a}#root .fs-package.fs-featured-plan .fs-tooltip .fs-icon,#fs_pricing_app .fs-package.fs-featured-plan .fs-tooltip .fs-icon{color:#1fbc99!important}#root .fs-package .fs-license-quantity-selected .fs-license-quantity,#root .fs-package .fs-license-quantity-selected .fs-license-quantity-discount,#root .fs-package .fs-license-quantity-selected .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantity-selected .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantity-selected .fs-license-quantity-discount,#fs_pricing_app .fs-package .fs-license-quantity-selected .fs-license-quantity-price{color:#fff}\n',""]);const s=o},700:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),i=n.n(a),r=n(645),o=n.n(r)()(i());o.push([e.id,'#root .fs-section--packages,#fs_pricing_app .fs-section--packages{display:inline-block;width:100%;position:relative}#root .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--packages .fs-packages-menu{display:none;flex-wrap:wrap;justify-content:center}#root .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--packages .fs-packages-tab{display:none}#root .fs-section--packages .fs-package-tab,#fs_pricing_app .fs-section--packages .fs-package-tab{display:inline-block;flex:1}#root .fs-section--packages .fs-package-tab a,#fs_pricing_app .fs-section--packages .fs-package-tab a{display:block;padding:4px 10px 7px;border-bottom:2px solid rgba(0,0,0,0);color:#000;text-align:center;text-decoration:none}#root .fs-section--packages .fs-package-tab.fs-package-tab--selected a,#fs_pricing_app .fs-section--packages .fs-package-tab.fs-package-tab--selected a{border-color:#0085ba}#root .fs-section--packages .fs-packages-nav,#fs_pricing_app .fs-section--packages .fs-packages-nav{position:relative;overflow:hidden;margin:auto}#root .fs-section--packages .fs-packages-nav:before,#root .fs-section--packages .fs-packages-nav:after,#fs_pricing_app .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:after{position:absolute;top:0;bottom:0;width:60px;margin-bottom:32px}#root .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:before{z-index:1}#root .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before{content:"";left:0;background:linear-gradient(to right,rgba(204,204,204,.5882352941),transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-next-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-next-plan:after{content:"";right:0;background:linear-gradient(to left,rgba(204,204,204,.5882352941),transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after{top:2.8em}#root .fs-section--packages .fs-prev-package,#root .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-next-package{position:absolute;top:50%;margin-top:-11px;cursor:pointer;font-size:48px;z-index:1}#root .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-prev-package{visibility:hidden;z-index:2}#root .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:2.8em}#root .fs-section--packages .fs-packages,#fs_pricing_app .fs-section--packages .fs-packages{width:auto;display:flex;flex-direction:row;margin-left:auto;margin-right:auto;margin-bottom:30px;border-top-right-radius:10px;position:relative;transition:left .5s ease,right .5s ease;padding-top:5px}#root .fs-section--packages .fs-packages:before,#fs_pricing_app .fs-section--packages .fs-packages:before{content:"";position:absolute;top:0;right:0;bottom:0;width:100px;height:100px}@media only screen and (max-width: 768px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#root .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu{display:block;font-size:24px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab{display:flex;font-size:18px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#root .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:0}}@media only screen and (max-width: 455px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}#root .fs-section--plans-and-pricing,#fs_pricing_app .fs-section--plans-and-pricing{padding:10px}}@media only screen and (max-width: 375px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}}\n',""]);const s=o},302:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),i=n.n(a),r=n(645),o=n.n(r)()(i());o.push([e.id,'#root .fs-tooltip,#fs_pricing_app .fs-tooltip{cursor:help;position:relative;color:#2da1d0!important}#root .fs-tooltip .fs-tooltip-message,#fs_pricing_app .fs-tooltip .fs-tooltip-message{position:absolute;width:200px;background:#000;z-index:1;display:none;border-radius:4px;color:#fff;padding:8px;text-align:left;line-height:18px}#root .fs-tooltip .fs-tooltip-message:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message:before{content:"";position:absolute;z-index:1}#root .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none),#fs_pricing_app .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none){display:block}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right{transform:translateY(-50%);left:30px;top:8px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before{left:-8px;top:50%;margin-top:-6px;border-top:6px solid rgba(0,0,0,0);border-bottom:6px solid rgba(0,0,0,0);border-right:8px solid #000}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top{left:50%;bottom:30px;transform:translate(-50%)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before{left:50%;bottom:-8px;margin-left:-6px;border-right:6px solid rgba(0,0,0,0);border-left:6px solid rgba(0,0,0,0);border-top:8px solid #000}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right{right:-10px;bottom:30px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before{right:10px;bottom:-8px;margin-left:-6px;border-right:6px solid rgba(0,0,0,0);border-left:6px solid rgba(0,0,0,0);border-top:8px solid #000}\n',""]);const s=o},645:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",a=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),a&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),a&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,a,i,r){"string"==typeof e&&(e=[[null,e,void 0]]);var o={};if(a)for(var s=0;s0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=r),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),i&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=i):u[4]="".concat(i)),t.push(u))}},t}},81:e=>{"use strict";e.exports=function(e){return e[1]}},867:(e,t,n)=>{let a=document.getElementById("fs_pricing_wrapper");a&&a.dataset&&a.dataset.publicUrl&&(n.p=a.dataset.publicUrl)},738:e=>{function t(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}e.exports=function(e){return null!=e&&(t(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&t(e.slice(0,0))}(e)||!!e._isBuffer)}},568:(e,t,n)=>{var a,i,r,o,s;a=n(12),i=n(487).utf8,r=n(738),o=n(487).bin,(s=function(e,t){e.constructor==String?e=t&&"binary"===t.encoding?o.stringToBytes(e):i.stringToBytes(e):r(e)?e=Array.prototype.slice.call(e,0):Array.isArray(e)||e.constructor===Uint8Array||(e=e.toString());for(var n=a.bytesToWords(e),l=8*e.length,c=1732584193,u=-271733879,f=-1732584194,p=271733878,d=0;d>>24)|4278255360&(n[d]<<24|n[d]>>>8);n[l>>>5]|=128<>>9<<4)]=l;var m=s._ff,g=s._gg,h=s._hh,y=s._ii;for(d=0;d>>0,u=u+v>>>0,f=f+_>>>0,p=p+k>>>0}return a.endian([c,u,f,p])})._ff=function(e,t,n,a,i,r,o){var s=e+(t&n|~t&a)+(i>>>0)+o;return(s<>>32-r)+t},s._gg=function(e,t,n,a,i,r,o){var s=e+(t&a|n&~a)+(i>>>0)+o;return(s<>>32-r)+t},s._hh=function(e,t,n,a,i,r,o){var s=e+(t^n^a)+(i>>>0)+o;return(s<>>32-r)+t},s._ii=function(e,t,n,a,i,r,o){var s=e+(n^(t|~a))+(i>>>0)+o;return(s<>>32-r)+t},s._blocksize=16,s._digestsize=16,e.exports=function(e,t){if(null==e)throw new Error("Illegal argument "+e);var n=a.wordsToBytes(s(e,t));return t&&t.asBytes?n:t&&t.asString?o.bytesToString(n):a.bytesToHex(n)}},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach((function(e){a[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},a)).join("")}catch(e){return!1}}()?Object.assign:function(e,r){for(var o,s,l=i(e),c=1;c{"use strict";var a=n(414);function i(){}function r(){}r.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,r,o){if(o!==a){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:r,resetWarningCache:i};return n.PropTypes=n,n}},697:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},448:(e,t,n)=>{"use strict";var a=n(294),i=n(418),r=n(840);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n

    -
    +
    @@ -1344,7 +1345,10 @@ class="fs-annual-discount">
  • slug ) ?> - : slug ), $api->requires ) ) ?> + : slug ), $api->requires ) + ) ?>
  • requires_php ) ) { + ?> +
  • + slug ); ?>: + slug ), $api->requires_php ) + ); + ?> +
  • + downloaded ) ) { ?>
  • @@ -1485,9 +1502,43 @@ class="fs-annual-discount">
    tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { + $requires_php = isset( $api->requires_php ) ? $api->requires_php : null; + $requires_wp = isset( $api->requires ) ? $api->requires : null; + + $compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' ); + + // Strip off any -alpha, -RC, -beta, -src suffixes. + list( $wp_version ) = explode( '-', $GLOBALS['wp_version'] ); + + $compatible_wp = empty( $requires_wp ) || version_compare( $wp_version, $requires_wp, '>=' ); + $tested_wp = ( empty( $api->tested ) || version_compare( $wp_version, $api->tested, '<=' ) ); + + if ( ! $compatible_php ) { + echo '

    ' . fs_text_inline( 'Error', 'error', $api->slug ) . ': ' . fs_text_inline( 'This plugin requires a newer version of PHP.', 'newer-php-required-error', $api->slug ); + + if ( current_user_can( 'update_php' ) ) { + $wp_get_update_php_url = function_exists( 'wp_get_update_php_url' ) ? + wp_get_update_php_url() : + 'https://wordpress.org/support/update-php/'; + + printf( + /* translators: %s: URL to Update PHP page. */ + ' ' . fs_text_inline( 'Click here to learn more about updating PHP.', 'php-update-learn-more-link', $api->slug ), + esc_url( $wp_get_update_php_url ) + ); + + if ( function_exists( 'wp_update_php_annotation' ) ) { + wp_update_php_annotation( '

    ', '' ); + } + } else { + echo '

    '; + } + echo '
    '; + } + + if ( ! $tested_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; - } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { + } else if ( ! $compatible_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; } diff --git a/freemius/includes/i18n.php b/freemius/includes/i18n.php deleted file mode 100644 index 34270f7..0000000 --- a/freemius/includes/i18n.php +++ /dev/null @@ -1,605 +0,0 @@ - 'You are just one step away - %s', - * - * We can use the filter: - * fs_override_i18n( array( - * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), - * 'skip' => __( 'Not today', '{your-text_domain}' ), - * ), '{plugin_slug}' ); - * - * Or with the Freemius instance: - * - * my_freemius->override_i18n( array( - * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), - * 'skip' => __( 'Not today', '{your-text_domain}' ), - * ) ); - */ - global $fs_text; - - $fs_text = array( - 'account' => _fs_text( 'Account' ), - 'addon' => _fs_text( 'Add-On' ), - 'contact-us' => _fs_text( 'Contact Us' ), - 'contact-support' => _fs_text( 'Contact Support' ), - 'change-ownership' => _fs_text( 'Change Ownership' ), - 'support' => _fs_text( 'Support' ), - 'support-forum' => _fs_text( 'Support Forum' ), - 'add-ons' => _fs_text( 'Add-Ons' ), - 'upgrade' => _fs_x( 'Upgrade', 'verb' ), - 'awesome' => _fs_text( 'Awesome' ), - 'pricing' => _fs_x( 'Pricing', 'noun' ), - 'price' => _fs_x( 'Price', 'noun' ), - 'unlimited-updates' => _fs_text( 'Unlimited Updates' ), - 'downgrade' => _fs_x( 'Downgrade', 'verb' ), - 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ), - 'cancel-trial' => _fs_text( 'Cancel Trial' ), - 'free-trial' => _fs_text( 'Free Trial' ), - 'start-free-x' => _fs_text( 'Start my free %s' ), - 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ), - 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ), - 'details' => _fs_text( 'Details' ), - 'account-details' => _fs_text( 'Account Details' ), - 'delete' => _fs_x( 'Delete', 'verb' ), - 'show' => _fs_x( 'Show', 'verb' ), - 'hide' => _fs_x( 'Hide', 'verb' ), - 'edit' => _fs_x( 'Edit', 'verb' ), - 'update' => _fs_x( 'Update', 'verb' ), - 'date' => _fs_text( 'Date' ), - 'amount' => _fs_text( 'Amount' ), - 'invoice' => _fs_text( 'Invoice' ), - 'billing' => _fs_text( 'Billing' ), - 'payments' => _fs_text( 'Payments' ), - 'delete-account' => _fs_text( 'Delete Account' ), - 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ), - 'plan' => _fs_x( 'Plan', 'as product pricing plan' ), - 'change-plan' => _fs_text( 'Change Plan' ), - 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ), - 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ), - 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ), - 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ), - 'new' => _fs_text( 'New' ), - 'free' => _fs_text( 'Free' ), - 'trial' => _fs_x( 'Trial', 'as trial plan' ), - 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ), - 'purchase' => _fs_x( 'Purchase', 'verb' ), - 'purchase-license' => _fs_text( 'Purchase License' ), - 'buy' => _fs_x( 'Buy', 'verb' ), - 'buy-license' => _fs_text( 'Buy License' ), - 'license-single-site' => _fs_text( 'Single Site License' ), - 'license-unlimited' => _fs_text( 'Unlimited Licenses' ), - 'license-x-sites' => _fs_text( 'Up to %s Sites' ), - 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ), - 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ), - 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ), - 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ), - 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now', - '%s - plugin name. As complete "Jetpack" activation now' ), - 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ), - 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ), - 'error' => _fs_text( 'Error' ), - 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), - 'learn-more' => _fs_text( 'Learn more' ), - 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."), - 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'), - - #region Affiliation - 'affiliation' => _fs_text( 'Affiliation' ), - 'affiliate' => _fs_text( 'Affiliate' ), - 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ), - 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ), - 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ), - 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ), - 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ), - 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ), - 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ), - 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ), - 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ), - 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ), - 'program-summary' => _fs_text( 'Program Summary' ), - 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ), - 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ), - 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ), - 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ), - 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ), - 'become-an-affiliate' => _fs_text( 'Become an affiliate' ), - 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ), - 'full-name' => _fs_text( 'Full name' ), - 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ), - 'promotion-methods' => _fs_text( 'Promotion methods' ), - 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ), - 'mobile-apps' => _fs_text( 'Mobile apps' ), - 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ), - 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ), - 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ), - 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ), - 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ), - 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ), - 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ), - 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ), - 'add-another-domain' => _fs_text( 'Add another domain' ), - 'remove' => _fs_x( 'Remove', 'Remove domain' ), - 'email-address-is-required' => _fs_text( 'Email address is required.' ), - 'domain-is-required' => _fs_text( 'Domain is required.' ), - 'invalid-domain' => _fs_text( 'Invalid domain' ), - 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ), - 'processing' => _fs_text( 'Processing...' ), - 'non-expiring' => _fs_text( 'Non-expiring' ), - 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ), - #endregion Affiliation - - #region Account - 'expiration' => _fs_x( 'Expiration', 'as expiration date' ), - 'license' => _fs_x( 'License', 'as software license' ), - 'not-verified' => _fs_text( 'not verified' ), - 'verify-email' => _fs_text( 'Verify Email' ), - 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ), - 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ), - 'no-expiration' => _fs_text( 'No expiration' ), - 'expired' => _fs_text( 'Expired' ), - 'cancelled' => _fs_text( 'Cancelled' ), - 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ), - 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ), - /* translators: %s: Version number (e.g. 4.6 or higher) */ - 'x-or-higher' => _fs_text( '%s or higher' ), - 'version' => _fs_x( 'Version', 'as plugin version' ), - 'name' => _fs_text( 'Name' ), - 'email' => _fs_text( 'Email' ), - 'email-address' => _fs_text( 'Email address' ), - 'verified' => _fs_text( 'Verified' ), - 'module' => _fs_text( 'Module' ), - 'module-type' => _fs_text( 'Module Type' ), - 'plugin' => _fs_text( 'Plugin' ), - 'plugins' => _fs_text( 'Plugins' ), - 'theme' => _fs_text( 'Theme' ), - 'themes' => _fs_text( 'Themes' ), - 'path' => _fs_x( 'Path', 'as file/folder path' ), - 'title' => _fs_text( 'Title' ), - 'free-version' => _fs_text( 'Free version' ), - 'premium-version' => _fs_text( 'Premium version' ), - 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ), - 'id' => _fs_text( 'ID' ), - 'users' => _fs_text( 'Users' ), - 'module-installs' => _fs_text( '%s Installs' ), - 'sites' => _fs_x( 'Sites', 'like websites' ), - 'user-id' => _fs_text( 'User ID' ), - 'site-id' => _fs_text( 'Site ID' ), - 'public-key' => _fs_text( 'Public Key' ), - 'secret-key' => _fs_text( 'Secret Key' ), - 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ), - 'no-id' => _fs_text( 'No ID' ), - 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ), - 'sync' => _fs_x( 'Sync', 'as synchronize' ), - 'activate-license' => _fs_text( 'Activate License' ), - 'activate-free-version' => _fs_text( 'Activate Free Version' ), - 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ), - 'activating-license' => _fs_text( 'Activating license...' ), - 'change-license' => _fs_text( 'Change License' ), - 'update-license' => _fs_text( 'Update License' ), - 'deactivate-license' => _fs_text( 'Deactivate License' ), - 'activate' => _fs_text( 'Activate' ), - 'deactivate' => _fs_text( 'Deactivate' ), - 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ), - 'skip-and-x' => _fs_text( 'Skip & %s' ), - 'no-deactivate' => _fs_text( 'No - just deactivate' ), - 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ), - 'active' => _fs_x( 'Active', 'active mode' ), - 'is-active' => _fs_x( 'Is Active', 'is active mode?' ), - 'install-now' => _fs_text( 'Install Now' ), - 'install-update-now' => _fs_text( 'Install Update Now' ), - 'more-information-about-x' => _fs_text( 'More information about %s' ), - 'localhost' => _fs_text( 'Localhost' ), - 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ), - 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ), - 'last-license' => _fs_text( 'Last license' ), - 'what-is-your-x' => _fs_text( 'What is your %s?' ), - 'activate-this-addon' => _fs_text( 'Activate this add-on' ), - 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ), - 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ), - 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ), - 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ), - 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ), - 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ), - 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ), - 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ), - #endregion Account - - 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ), - 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ), - #region Plugin Deactivation - 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ), - 'quick-feedback' => _fs_text( 'Quick feedback' ), - 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ), - 'deactivating' => _fs_text( 'deactivating' ), - 'deactivation' => _fs_text( 'Deactivation' ), - 'theme-switch' => _fs_text( 'Theme Switch' ), - 'switching' => _fs_text( 'switching' ), - 'switch' => _fs_text( 'Switch' ), - 'activate-x' => _fs_text( 'Activate %s' ), - 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ), - 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ), - 'cancel' => _fs_text( 'Cancel' ), - 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ), - 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ), - 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ), - 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ), - 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ), - 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ), - 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ), - 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ), - 'reason-other' => _fs_x( 'Other', - 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ), - 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ), - 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ), - 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ), - 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ), - 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ), - 'reason-not-working' => _fs_text( 'The %s is not working' ), - 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ), - 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ), - 'placeholder-feature' => _fs_text( 'What feature?' ), - 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ), - 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ), - 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ), - 'reason-didnt-work' => _fs_text( "The %s didn't work" ), - 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ), - 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ), - #endregion Plugin Deactivation - - #region Connect - 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ), - 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ), - 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ), - 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ), - 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ), - 'complete-the-install' => _fs_text( 'complete the install' ), - 'start-the-trial' => _fs_text( 'start the trial' ), - 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ), - 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ), - 'what-permissions' => _fs_text( 'What permissions are being granted?' ), - 'permissions-profile' => _fs_text( 'Your Profile Overview' ), - 'permissions-profile_desc' => _fs_text( 'Name and email address' ), - 'permissions-site' => _fs_text( 'Your Site Overview' ), - 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ), - 'permissions-events' => _fs_text( 'Current %s Events' ), - 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ), - 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ), - 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ), - 'permissions-admin-notices' => _fs_text( 'Admin Notices' ), - 'permissions-newsletter' => _fs_text( 'Newsletter' ), - 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ), - 'privacy-policy' => _fs_text( 'Privacy Policy' ), - 'tos' => _fs_text( 'Terms of Service' ), - 'activating' => _fs_x( 'Activating', 'as activating plugin' ), - 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ), - 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ), - 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ), - 'skip' => _fs_x( 'Skip', 'verb' ), - 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ), - 'resend-activation-email' => _fs_text( 'Re-send activation email' ), - 'license-key' => _fs_text( 'License key' ), - 'send-license-key' => _fs_text( 'Send License Key' ), - 'sending-license-key' => _fs_text( 'Sending license key' ), - 'have-license-key' => _fs_text( 'Have a license key?' ), - 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ), - 'cant-find-license-key' => _fs_text( "Can't find your license key?" ), - 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ), - 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ), - 'opt-in' => _fs_text( 'Opt In' ), - 'opt-out' => _fs_text( 'Opt Out' ), - 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ), - 'opting-out' => _fs_text( 'Opting out...' ), - 'opting-in' => _fs_text( 'Opting in...' ), - 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ), - 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ), - 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ), - 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ), - 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ), - 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ), - 'continue' => _fs_text( 'Continue' ), - 'allow' => _fs_text( 'allow' ), - 'delegate' => _fs_text( 'delegate' ), - #endregion Connect - - #region Screenshots - 'screenshots' => _fs_text( 'Screenshots' ), - 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ), - #endregion Screenshots - - #region Debug - 'freemius-debug' => _fs_text( 'Freemius Debug' ), - 'on' => _fs_x( 'On', 'as turned on' ), - 'off' => _fs_x( 'Off', 'as turned off' ), - 'debugging' => _fs_x( 'Debugging', 'as code debugging' ), - 'freemius-state' => _fs_text( 'Freemius State' ), - 'connected' => _fs_x( 'Connected', 'as connection was successful' ), - 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ), - 'api' => _fs_x( 'API', 'as application program interface' ), - 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ), - 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ), - 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ), - 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ), - 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ), - 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ), - 'actions' => _fs_text( 'Actions' ), - 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ), - 'start-fresh' => _fs_text( 'Start Fresh' ), - 'clear-api-cache' => _fs_text( 'Clear API Cache' ), - 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ), - 'scheduled-crons' => _fs_text( 'Scheduled Crons' ), - 'cron-type' => _fs_text( 'Cron Type' ), - 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ), - 'module-licenses' => _fs_text( '%s Licenses' ), - 'debug-log' => _fs_text( 'Debug Log' ), - 'all' => _fs_text( 'All' ), - 'file' => _fs_text( 'File' ), - 'function' => _fs_text( 'Function' ), - 'process-id' => _fs_text( 'Process ID' ), - 'logger' => _fs_text( 'Logger' ), - 'message' => _fs_text( 'Message' ), - 'download' => _fs_text( 'Download' ), - 'filter' => _fs_text( 'Filter' ), - 'type' => _fs_text( 'Type' ), - 'all-types' => _fs_text( 'All Types' ), - 'all-requests' => _fs_text( 'All Requests' ), - #endregion Debug - - #region Expressions - 'congrats' => _fs_x( 'Congrats', 'as congratulations' ), - 'oops' => _fs_x( 'Oops', 'exclamation' ), - 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ), - 'woot' => _fs_x( 'W00t', - '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ), - 'right-on' => _fs_x( 'Right on', 'a positive response' ), - 'hmm' => _fs_x( 'Hmm', - 'something somebody says when they are thinking about what you have just said. ' ), - 'ok' => _fs_text( 'O.K' ), - 'hey' => _fs_x( 'Hey', 'exclamation' ), - 'heads-up' => _fs_x( 'Heads up', - 'advance notice of something that will need attention.' ), - #endregion Expressions - - #region Admin Notices - 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ), - 'you-are-good' => _fs_text( 'You are all good!' ), - 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ), - 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ), - 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ), - 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ), - 'x-updated' => _fs_text( 'You have successfully updated your %s.' ), - 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ), - 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ), - 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ), - 'no-cc-required' => _fs_text( 'No credit card required' ), - 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ), - 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ), - 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ), - 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ), - 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ), - 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ), - 'download-latest-version' => _fs_text( 'Download the latest version' ), - 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ), - 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ), - 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ), - 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.', - '%s - product name, e.g. Facebook add-on was successfully...' ), - 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ), - 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ), - 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ), - 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ), - 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ), - 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ), - 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ), - 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ), - 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ), - 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ), - 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ), - 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ), - 'server-error-message' => _fs_text( 'Error received from the server:' ), - 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ), - 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ), - 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ), - 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ), - 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ), - 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ), - 'please-download-x' => _fs_text( 'Please download %s.' ), - 'latest-x-version' => _fs_x( 'the latest %s version here', - '%s - plan name, as the latest professional version here' ), - 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ), - 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ), - 'starting-trial' => _fs_text( 'Starting trial' ), - 'please-wait' => _fs_text( 'Please wait' ), - 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ), - 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ), - 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ), - 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ), - 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ), - 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ), - 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ), - 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ), - 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ), - 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ), - 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ), - 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ), - 'contact-us-here' => _fs_text( 'Please contact us here' ), - 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ), - #endregion Admin Notices - #region Connectivity Issues - 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ), - 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ), - 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ), - 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ), - 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ), - 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.', - 'as pluginX requires an access to our API' ), - 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ), - 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ), - 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), - 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ), - 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ), - 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ), - 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), - 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ), - 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ), - 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ), - 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ), - 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ), - 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ), - 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ), - 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ), - 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ), - 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ), - 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', - '%1$s - plugin title, %2$s - API domain' ), - 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ), - #endregion Connectivity Issues - #region Change Owner - 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ), - 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ), - 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ), - #endregion Change Owner - 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.', - 'addonX cannot run without pluginY' ), - 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ), - 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.', - 'pluginX activation was successfully...' ), - 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ), - 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ), - 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ), - #-------------------------------------------------------------------------------- - #region Add-On Licensing - #-------------------------------------------------------------------------------- - 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ), - 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ), - #endregion - #-------------------------------------------------------------------------------- - #region Billing Cycles - #-------------------------------------------------------------------------------- - 'monthly' => _fs_x( 'Monthly', 'as every month' ), - 'mo' => _fs_x( 'mo', 'as monthly period' ), - 'annual' => _fs_x( 'Annual', 'as once a year' ), - 'annually' => _fs_x( 'Annually', 'as once a year' ), - 'once' => _fs_x( 'Once', 'as once a year' ), - 'year' => _fs_x( 'year', 'as annual period' ), - 'lifetime' => _fs_text( 'Lifetime' ), - 'best' => _fs_x( 'Best', 'e.g. the best product' ), - 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ), - 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ), - #endregion Billing Cycles - 'view-details' => _fs_text( 'View details' ), - #-------------------------------------------------------------------------------- - #region Trial - #-------------------------------------------------------------------------------- - 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ), - /* translators: %1$s: Number of trial days; %2$s: Plan name; */ - 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ), - /* translators: %s: Link to freemius.com */ - 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ), - - #endregion - #-------------------------------------------------------------------------------- - #region Billing Details - #-------------------------------------------------------------------------------- - 'business-name' => _fs_text( 'Business name' ), - 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ), - 'address-line-n' => _fs_text( 'Address Line %d' ), - 'country' => _fs_text( 'Country' ), - 'select-country' => _fs_text( 'Select Country' ), - 'city' => _fs_text( 'City' ), - 'town' => _fs_text( 'Town' ), - 'state' => _fs_text( 'State' ), - 'province' => _fs_text( 'Province' ), - 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ), - #endregion - #-------------------------------------------------------------------------------- - #region Module Installation - #-------------------------------------------------------------------------------- - 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ), - 'auto-installation' => _fs_text( 'Automatic Installation' ), - /* translators: %s: Number of seconds */ - 'x-sec' => _fs_text( '%s sec' ), - 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ), - 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ), - 'cancel-installation' => _fs_text( 'Cancel Installation' ), - 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ), - 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ), - 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ), - 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ), - 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ), - 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ), - 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ), - #endregion - - /* translators: %s: Page name */ - 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ), - 'pci-compliant' => _fs_text( 'PCI compliant' ), - 'view-paid-features' => _fs_text( 'View paid features' ), - ); - - /** - * Localization of the strings in the plugin/theme info dialog box. - * - * $fs_module_info_text should ONLY include strings that are not located in $fs_text. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - */ - global $fs_module_info_text; - - $fs_module_info_text = array( - 'description' => _fs_x( 'Description', 'Plugin installer section title' ), - 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ), - 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ), - 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ), - 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ), - 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ), - /* translators: %s: 1 or One */ - 'x-star' => _fs_text( '%s star' ), - /* translators: %s: Number larger than 1 */ - 'x-stars' => _fs_text( '%s stars' ), - /* translators: %s: 1 or One */ - 'x-rating' => _fs_text( '%s rating' ), - /* translators: %s: Number larger than 1 */ - 'x-ratings' => _fs_text( '%s ratings' ), - /* translators: %s: 1 or One (Number of times downloaded) */ - 'x-time' => _fs_text( '%s time' ), - /* translators: %s: Number of times downloaded */ - 'x-times' => _fs_text( '%s times' ), - /* translators: %s: # of stars (e.g. 5 stars) */ - 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ), - 'last-updated:' => _fs_text( 'Last Updated' ), - 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ), - 'author:' => _fs_x( 'Author:', 'as the plugin author' ), - 'compatible-up-to:' => _fs_text( 'Compatible up to:' ), - 'downloaded:' => _fs_text( 'Downloaded:' ), - 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ), - 'plugin-homepage' => _fs_text( 'Plugin Homepage' ), - 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ), - 'average-rating' => _fs_text( 'Average Rating' ), - 'based-on-x' => _fs_text( 'based on %s' ), - 'warning:' => _fs_text( 'Warning:' ), - 'contributors' => _fs_text( 'Contributors' ), - 'plugin-install' => _fs_text( 'Plugin Install' ), - 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ), - 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ), - 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ), - 'latest-installed' => _fs_text( 'Latest Version Installed' ), - ); diff --git a/freemius/includes/managers/class-fs-admin-notice-manager.php b/freemius/includes/managers/class-fs-admin-notice-manager.php index b238d9b..3734be1 100644 --- a/freemius/includes/managers/class-fs-admin-notice-manager.php +++ b/freemius/includes/managers/class-fs-admin-notice-manager.php @@ -160,7 +160,10 @@ protected function __construct( false, isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, - $is_network_and_blog_admins + $is_network_and_blog_admins, + isset( $msg['dismissible'] ) ? + $msg['dismissible'] : + null ); } } @@ -224,9 +227,6 @@ function _admin_notices_hook() { return; } - - $show_admin_notices = ( ! $this->is_gutenberg_page() ); - foreach ( $this->_notices as $id => $msg ) { if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { if ( get_current_user_id() != $msg['wp_user_id'] ) { @@ -269,7 +269,7 @@ function _admin_notices_hook() { $show_notice = call_user_func_array( 'fs_apply_filter', array( $this->_module_unique_affix, 'show_admin_notice', - $show_admin_notices, + $this->show_admin_notices(), $msg ) ); @@ -323,6 +323,30 @@ function is_gutenberg_page() { return false; } + /** + * Check if admin notices should be shown on page. E.g., we don't want to show notices in the Visual Editor. + * + * @author Xiaheng Chen (@xhchen) + * @since 2.4.2 + * + * @return bool + */ + function show_admin_notices() { + global $pagenow; + + if ( 'about.php' === $pagenow ) { + // Don't show admin notices on the About page. + return false; + } + + if ( $this->is_gutenberg_page() ) { + // Don't show admin notices in Gutenberg (visual editor). + return false; + } + + return true; + } + /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * @@ -339,6 +363,8 @@ function is_gutenberg_page() { * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. + * @param bool|null $is_dismissible + * @param array $data * * @uses add_action() */ @@ -351,7 +377,9 @@ function add( $store_if_sticky = true, $wp_user_id = null, $plugin_title = null, - $is_network_and_blog_admins = false + $is_network_and_blog_admins = false, + $is_dismissible = null, + $data = array() ) { $notices_type = $this->get_notices_type(); @@ -371,14 +399,16 @@ function add( } $message_object = array( - 'message' => $message, - 'title' => $title, - 'type' => $type, - 'sticky' => $is_sticky, - 'id' => $id, - 'manager_id' => $this->_id, - 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), - 'wp_user_id' => $wp_user_id, + 'message' => $message, + 'title' => $title, + 'type' => $type, + 'sticky' => $is_sticky, + 'id' => $id, + 'manager_id' => $this->_id, + 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), + 'wp_user_id' => $wp_user_id, + 'dismissible' => $is_dismissible, + 'data' => $data ); if ( $is_sticky && $store_if_sticky ) { @@ -393,15 +423,16 @@ function add( * @since 1.0.7 * * @param string|string[] $ids + * @param bool $store */ - function remove_sticky( $ids ) { + function remove_sticky( $ids, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } foreach ( $ids as $id ) { // Remove from sticky storage. - $this->_sticky_storage->remove( $id ); + $this->_sticky_storage->remove( $id, $store ); if ( isset( $this->_notices[ $id ] ) ) { unset( $this->_notices[ $id ] ); @@ -437,14 +468,32 @@ function has_sticky( $id ) { * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. + * @param bool $is_dimissible + * @param array $data */ - function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false ) { + function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dimissible = true, $data = array() ) { if ( ! empty( $this->_module_unique_affix ) ) { $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); } - $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dimissible, $data ); + } + + /** + * Retrieves the data of an sticky notice. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.3 + * + * @param string $id Message ID. + * + * @return array|null + */ + function get_sticky( $id ) { + return isset( $this->_sticky_storage->{$id} ) ? + $this->_sticky_storage->{$id} : + null; } /** @@ -452,9 +501,15 @@ function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id * * @author Vova Feldman (@svovaf) * @since 1.0.8 + * + * @param bool $is_temporary @since 2.5.1 */ - function clear_all_sticky() { - $this->_sticky_storage->clear_all(); + function clear_all_sticky( $is_temporary = false ) { + if ( $is_temporary ) { + $this->_notices = array(); + } else { + $this->_sticky_storage->clear_all(); + } } #-------------------------------------------------------------------------------- diff --git a/freemius/includes/managers/class-fs-clone-manager.php b/freemius/includes/managers/class-fs-clone-manager.php new file mode 100644 index 0000000..e992ef1 --- /dev/null +++ b/freemius/includes/managers/class-fs-clone-manager.php @@ -0,0 +1,1660 @@ +_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true ); + $this->_network_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, true ); + + $this->maybe_migrate_options(); + + $this->_notices = FS_Admin_Notices::instance( 'global_clone_resolution_notices', '', '', true ); + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . '_clone_manager', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + } + + /** + * Migrate clone resolution options from 2.5.0 array-based structure, to a new flat structure. + * + * The reason this logic is not in a separate migration script is that we want to be 100% sure data is migrated before any execution of clone logic. + * + * @todo Delete this one in the future. + */ + private function maybe_migrate_options() { + $storages = array( + $this->_storage, + $this->_network_storage + ); + + foreach ( $storages as $storage ) { + $clone_data = $storage->get_option( self::OPTION_NAME ); + if ( is_array( $clone_data ) && ! empty( $clone_data ) ) { + foreach ( $clone_data as $key => $val ) { + if ( ! is_null( $val ) ) { + $storage->set_option( $key, $val ); + } + } + + $storage->unset_option( self::OPTION_NAME, true ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _init() { + if ( is_admin() ) { + if ( Freemius::is_admin_post() ) { + add_action( 'admin_post_fs_clone_resolution', array( $this, '_handle_clone_resolution' ) ); + } + + if ( Freemius::is_ajax() ) { + Freemius::add_ajax_action_static( 'handle_clone_resolution', array( $this, '_clone_resolution_action_ajax_handler' ) ); + } else { + if ( + empty( $this->get_clone_identification_timestamp() ) && + ( + ! fs_is_network_admin() || + ! ( $this->is_clone_resolution_options_notice_shown() || $this->is_temporary_duplicate_notice_shown() ) + ) + ) { + $this->hide_clone_admin_notices(); + } else if ( ! Freemius::is_cron() && ! Freemius::is_admin_post() ) { + $this->try_resolve_clone_automatically(); + $this->maybe_show_clone_admin_notice(); + + add_action( 'admin_footer', array( $this, '_add_clone_resolution_javascript' ) ); + } + } + } + } + + /** + * Retrieves the timestamp that was stored when a clone was identified. + * + * @return int|null + */ + function get_clone_identification_timestamp() { + return $this->get_option( 'clone_identification_timestamp', true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + * + * @param string $sdk_last_version + */ + function maybe_update_clone_resolution_support_flag( $sdk_last_version ) { + if ( null !== $this->hide_manual_resolution ) { + return; + } + + $this->hide_manual_resolution = ( + ! empty( $sdk_last_version ) && + version_compare( $sdk_last_version, '2.5.0', '<' ) + ); + } + + /** + * Stores the time when a clone was identified. + */ + function store_clone_identification_timestamp() { + $this->clone_identification_timestamp = time(); + } + + /** + * Retrieves the timestamp for the temporary duplicate mode's expiration. + * + * @return int + */ + function get_temporary_duplicate_expiration_timestamp() { + $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? + $this->temporary_duplicate_mode_selection_timestamp : + $this->get_clone_identification_timestamp(); + + return ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ); + } + + /** + * Determines if the SDK should handle clones. The SDK handles clones only up to 3 times with 3 min interval. + * + * @return bool + */ + private function should_handle_clones() { + if ( ! isset( $this->request_handler_timestamp ) ) { + return true; + } + + if ( $this->request_handler_retries_count >= self::CLONE_RESOLUTION_MAX_RETRIES ) { + return false; + } + + // Give the logic that handles clones enough time to finish (it is given 3 minutes for now). + return ( time() > ( $this->request_handler_timestamp + self::CLONE_RESOLUTION_MAX_EXECUTION_TIME ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + * + * @return bool + */ + function should_hide_manual_resolution() { + return ( true === $this->hide_manual_resolution ); + } + + /** + * Executes the clones handler logic if it should be executed, i.e., based on the return value of the should_handle_clones() method. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function maybe_run_clone_resolution() { + if ( ! $this->should_handle_clones() ) { + return; + } + + $this->request_handler_retries_count = isset( $this->request_handler_retries_count ) ? + ( $this->request_handler_retries_count + 1 ) : + 1; + + $this->request_handler_timestamp = time(); + + $handler_id = ( rand() . microtime() ); + $this->request_handler_id = $handler_id; + + // Add cookies to trigger request with the same user access permissions. + $cookies = array(); + foreach ( $_COOKIE as $name => $value ) { + $cookies[] = new WP_Http_Cookie( array( + 'name' => $name, + 'value' => $value, + ) ); + } + + wp_remote_post( + admin_url( 'admin-post.php' ), + array( + 'method' => 'POST', + 'body' => array( + 'action' => 'fs_clone_resolution', + 'handler_id' => $handler_id, + ), + 'timeout' => 0.01, + 'blocking' => false, + 'sslverify' => false, + 'cookies' => $cookies, + ) + ); + } + + /** + * Executes the clones handler logic. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _handle_clone_resolution() { + $handler_id = fs_request_get( 'handler_id' ); + + if ( empty( $handler_id ) ) { + return; + } + + if ( + ! isset( $this->request_handler_id ) || + $this->request_handler_id !== $handler_id + ) { + return; + } + + if ( ! $this->try_automatic_resolution() ) { + $this->clear_temporary_duplicate_notice_shown_timestamp(); + } + } + + #-------------------------------------------------------------------------------- + #region Automatic Clone Resolution + #-------------------------------------------------------------------------------- + + /** + * @var array All installs cache. + */ + private $all_installs; + + /** + * Checks if a given instance's install is a clone of another subsite in the network. + * + * @author Vova Feldman (@svovaf) + * + * @return FS_Site + */ + private function find_network_subsite_clone_install( Freemius $instance ) { + if ( ! is_multisite() ) { + // Not a multi-site network. + return null; + } + + if ( ! isset( $this->all_installs ) ) { + $this->all_installs = Freemius::get_all_modules_sites(); + } + + // Check if there's another blog that has the same site. + $module_type = $instance->get_module_type(); + $sites_by_module_type = ! empty( $this->all_installs[ $module_type ] ) ? + $this->all_installs[ $module_type ] : + array(); + + $slug = $instance->get_slug(); + $sites_by_slug = ! empty( $sites_by_module_type[ $slug ] ) ? + $sites_by_module_type[ $slug ] : + array(); + + $current_blog_id = get_current_blog_id(); + + $current_install = $instance->get_site(); + + foreach ( $sites_by_slug as $site ) { + if ( + $current_install->id == $site->id && + $current_blog_id != $site->blog_id + ) { + // Clone is identical to an install on another subsite in the network. + return $site; + } + } + + return null; + } + + /** + * Tries to find a different install of the context product that is associated with the current URL and loads it. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param Freemius $instance + * @param string $url + * + * @return object + */ + private function find_other_install_by_url( Freemius $instance, $url ) { + $result = $instance->get_api_user_scope()->get( "/plugins/{$instance->get_id()}/installs.json?url=" . urlencode( $url ) . "&all=true", true ); + + $current_install = $instance->get_site(); + + if ( $instance->is_api_result_object( $result, 'installs' ) ) { + foreach ( $result->installs as $install ) { + if ( $install->id == $current_install->id ) { + continue; + } + + if ( + $instance->is_only_premium() && + ! FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + continue; + } + + // When searching for installs by a URL, the API will first strip any paths and search for any matching installs by the subdomain. Therefore, we need to test if there's a match between the current URL and the install's URL before continuing. + if ( $url !== fs_strip_url_protocol( untrailingslashit( $install->url ) ) ) { + continue; + } + + // Found a different install that is associated with the current URL, load it and replace the current install with it if no updated install is found. + return $install; + } + } + + return null; + } + + /** + * Delete the current install associated with a given instance and opt-in/activate-license to create a fresh install. + * + * @author Vova Feldman (@svovaf) + * @since 2.5.0 + * + * @param Freemius $instance + * @param string|false $license_key + * + * @return bool TRUE if successfully connected. FALSE if failed and had to restore install from backup. + */ + private function delete_install_and_connect( Freemius $instance, $license_key = false ) { + $user = Freemius::_get_user_by_id( $instance->get_site()->user_id ); + + $instance->delete_current_install( true ); + + if ( ! is_object( $user ) ) { + // Get logged-in WordPress user. + $current_user = Freemius::_get_current_wp_user(); + + // Find the relevant FS user by email address. + $user = Freemius::_get_user_by_email( $current_user->user_email ); + } + + if ( is_object( $user ) ) { + // When a clone is found, we prefer to use the same user of the original install for the opt-in. + $instance->install_with_user( $user, $license_key, false, false ); + } else { + // If no user is found, activate with the license. + $instance->opt_in( + false, + false, + false, + $license_key + ); + } + + if ( is_object( $instance->get_site() ) ) { + // Install successfully created. + return true; + } + + // Restore from backup. + $instance->restore_backup_site(); + + return false; + } + + /** + * Try to resolve the clone situation automatically. + * + * @param Freemius $instance + * @param string $current_url + * @param bool $is_localhost + * @param bool|null $is_clone_of_network_subsite + * + * @return bool If managed to automatically resolve the clone. + */ + private function try_resolve_clone_automatically_by_instance( + Freemius $instance, + $current_url, + $is_localhost, + $is_clone_of_network_subsite = null + ) { + // Try to find a different install of the context product that is associated with the current URL. + $associated_install = $this->find_other_install_by_url( $instance, $current_url ); + + if ( is_object( $associated_install ) ) { + // Replace the current install with a different install that is associated with the current URL. + $instance->store_site( new FS_Site( clone $associated_install ) ); + $instance->sync_install( array( 'is_new_site' => true ), true ); + + return true; + } + + if ( ! $instance->is_premium() ) { + // For free products, opt-in with the context user to create new install. + return $this->delete_install_and_connect( $instance ); + } + + $license = $instance->_get_license(); + $can_activate_license = ( is_object( $license ) && ! $license->is_utilized( $is_localhost ) ); + + if ( ! $can_activate_license ) { + // License can't be activated, therefore, can't be automatically resolved. + return false; + } + + if ( ! WP_FS__IS_LOCALHOST_FOR_SERVER && ! $is_localhost ) { + $is_clone_of_network_subsite = ( ! is_null( $is_clone_of_network_subsite ) ) ? + $is_clone_of_network_subsite : + is_object( $this->find_network_subsite_clone_install( $instance ) ); + + if ( ! $is_clone_of_network_subsite ) { + return false; + } + } + + // If the site is a clone of another subsite in the network, or a localhost one, try to auto activate the license. + return $this->delete_install_and_connect( $instance, $license->secret_key ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + private function try_resolve_clone_automatically() { + $clone_action = $this->get_clone_resolution_action_from_config(); + + if ( ! empty( $clone_action ) ) { + $this->try_resolve_clone_automatically_by_config( $clone_action ); + return; + } + + $this->try_automatic_resolution(); + } + + /** + * Tries to resolve the clone situation automatically based on the config in the wp-config.php file. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param string $clone_action + */ + private function try_resolve_clone_automatically_by_config( $clone_action ) { + $fs_instances = array(); + + if ( self::OPTION_LONG_TERM_DUPLICATE === $clone_action ) { + $instances = Freemius::_get_all_instances(); + + foreach ( $instances as $instance ) { + if ( ! $instance->is_registered() ) { + continue; + } + + if ( ! $instance->is_clone() ) { + continue; + } + + $license = $instance->has_features_enabled_license() ? + $instance->_get_license() : + null; + + if ( + is_object( $license ) && + ! $license->is_utilized( + ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( Freemius::get_unfiltered_site_url() ) ) + ) + ) { + $fs_instances[] = $instance; + } + } + + if ( empty( $fs_instances ) ) { + return; + } + } + + $this->resolve_cloned_sites( $clone_action, $fs_instances ); + } + + /** + * @author Leo Fajard (@leorw) + * @since 2.5.0 + * + * @return string|null + */ + private function get_clone_resolution_action_from_config() { + if ( ! defined( 'FS__RESOLVE_CLONE_AS' ) ) { + return null; + } + + if ( ! in_array( + FS__RESOLVE_CLONE_AS, + array( + self::OPTION_NEW_HOME, + self::OPTION_TEMPORARY_DUPLICATE, + self::OPTION_LONG_TERM_DUPLICATE, + ) + ) ) { + return null; + } + + return FS__RESOLVE_CLONE_AS; + } + + /** + * Tries to recover the install of a newly created subsite or resolve it if it's a clone. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param Freemius $instance + */ + function maybe_resolve_new_subsite_install_automatically( Freemius $instance ) { + if ( ! $instance->is_user_in_admin() ) { + // Try to recover an install or resolve a clone only when there's a user in admin to prevent doing it prematurely (e.g., the install can get replaced with clone data again). + return; + } + + if ( ! is_multisite() ) { + return; + } + + $new_blog_install_map = $this->new_blog_install_map; + + if ( empty( $new_blog_install_map ) || ! is_array( $new_blog_install_map ) ) { + return; + } + + $is_network_admin = fs_is_network_admin(); + + if ( ! $is_network_admin ) { + // If not in network admin, handle the current site. + $blog_id = get_current_blog_id(); + } else { + // If in network admin, handle only the first site. + $blog_ids = array_keys( $new_blog_install_map ); + $blog_id = $blog_ids[0]; + } + + if ( ! isset( $new_blog_install_map[ $blog_id ] ) ) { + // There's no site to handle. + return; + } + + $expected_install_id = $new_blog_install_map[ $blog_id ]['install_id']; + + $current_install = $instance->get_install_by_blog_id( $blog_id ); + $current_install_id = is_object( $current_install ) ? + $current_install->id : + null; + + if ( $expected_install_id == $current_install_id ) { + // Remove the current site's information from the map to prevent handling it again. + $this->remove_new_blog_install_info_from_storage( $blog_id ); + + return; + } + + require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; + + $lock = new FS_Lock( self::OPTION_NAME . '_subsite' ); + + if ( ! $lock->try_lock(60) ) { + return; + } + + $instance->switch_to_blog( $blog_id ); + + $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); + $current_install_url = is_object( $current_install ) ? + fs_strip_url_protocol( untrailingslashit( $current_install->url ) ) : + null; + + // This can be `false` even if the install is a clone as the URL can be updated as part of the cloning process. + $is_clone = ( ! is_null( $current_install_url ) && $current_url !== $current_install_url ); + + if ( ! FS_Site::is_valid_id( $expected_install_id ) ) { + $expected_install = null; + } else { + $expected_install = $instance->fetch_install_by_id( $expected_install_id ); + } + + if ( FS_Api::is_api_result_entity( $expected_install ) ) { + // Replace the current install with the expected install. + $instance->store_site( new FS_Site( clone $expected_install ) ); + $instance->sync_install( array( 'is_new_site' => true ), true ); + } else { + $network_subsite_clone_install = null; + + if ( ! $is_clone ) { + // It is possible that `$is_clone` is `false` but the install is actually a clone as the following call checks the install ID and not the URL. + $network_subsite_clone_install = $this->find_network_subsite_clone_install( $instance ); + } + + if ( $is_clone || is_object( $network_subsite_clone_install ) ) { + // If there's no expected install (or it couldn't be fetched) and the current install is a clone, try to resolve the clone automatically. + $is_localhost = FS_Site::is_localhost_by_address( $current_url ); + + $resolved = $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost, is_object( $network_subsite_clone_install ) ); + + if ( ! $resolved && is_object( $network_subsite_clone_install ) ) { + if ( empty( $this->get_clone_identification_timestamp() ) ) { + $this->store_clone_identification_timestamp(); + } + + // Since the clone couldn't be identified based on the URL, replace the stored install with the cloned install so that the manual clone resolution notice will appear. + $instance->store_site( clone $network_subsite_clone_install ); + } + } + } + + $instance->restore_current_blog(); + + // Remove the current site's information from the map to prevent handling it again. + $this->remove_new_blog_install_info_from_storage( $blog_id ); + + $lock->unlock(); + } + + /** + * If a new install was created after creating a new subsite, its ID is stored in the blog-install map so that it can be recovered in case it's replaced with a clone install (e.g., when the newly created subsite is a clone). The IDs of the clone subsites that were created while not running this version of the SDK or a higher version will also be stored in the said map so that the clone manager can also try to resolve them later on. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param int $blog_id + * @param FS_Site $site + */ + function store_blog_install_info( $blog_id, $site = null ) { + $new_blog_install_map = $this->new_blog_install_map; + + if ( + empty( $new_blog_install_map ) || + ! is_array( $new_blog_install_map ) + ) { + $new_blog_install_map = array(); + } + + $install_id = null; + + if ( is_object( $site ) ) { + $install_id = $site->id; + } + + $new_blog_install_map[ $blog_id ] = array( 'install_id' => $install_id ); + + $this->new_blog_install_map = $new_blog_install_map; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param int $blog_id + */ + private function remove_new_blog_install_info_from_storage( $blog_id ) { + $new_blog_install_map = $this->new_blog_install_map; + + unset( $new_blog_install_map[ $blog_id ] ); + $this->new_blog_install_map = $new_blog_install_map; + } + + /** + * Tries to resolve all clones automatically. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return bool If managed to automatically resolve all clones. + */ + private function try_automatic_resolution() { + $this->_logger->entrance(); + + require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; + + $lock = new FS_Lock( self::OPTION_NAME ); + + /** + * Try to acquire lock for the next 60 sec based on the thread ID. + */ + if ( ! $lock->try_lock( 60 ) ) { + return false; + } + + $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); + $is_localhost = FS_Site::is_localhost_by_address( $current_url ); + + $require_manual_resolution = false; + + $instances = Freemius::_get_all_instances(); + + foreach ( $instances as $instance ) { + if ( ! $instance->is_registered() ) { + continue; + } + + if ( ! $instance->is_clone() ) { + continue; + } + + if ( ! $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost ) ) { + $require_manual_resolution = true; + } + } + + // Create a 1-day lock. + $lock->lock( WP_FS__TIME_24_HOURS_IN_SEC ); + + return ( ! $require_manual_resolution ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Manual Clone Resolution + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _add_clone_resolution_javascript() { + $vars = array( 'ajax_action' => Freemius::get_ajax_action_static( 'handle_clone_resolution' ) ); + + fs_require_once_template( 'clone-resolution-js.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _clone_resolution_action_ajax_handler() { + $this->_logger->entrance(); + + check_ajax_referer( Freemius::get_ajax_action_static( 'handle_clone_resolution' ), 'security' ); + + $clone_action = fs_request_get( 'clone_action' ); + $blog_id = is_multisite() ? + fs_request_get( 'blog_id' ) : + 0; + + if ( is_multisite() && $blog_id == get_current_blog_id() ) { + $blog_id = 0; + } + + if ( empty( $clone_action ) ) { + Freemius::shoot_ajax_failure( array( + 'message' => fs_text_inline( 'Invalid clone resolution action.', 'invalid-clone-resolution-action-error' ), + 'redirect_url' => '', + ) ); + } + + $result = $this->resolve_cloned_sites( $clone_action, array(), $blog_id ); + + Freemius::shoot_ajax_success( $result ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param string $clone_action + * @param Freemius[] $fs_instances + * @param int $blog_id + * + * @return array + */ + private function resolve_cloned_sites( $clone_action, $fs_instances = array(), $blog_id = 0 ) { + $this->_logger->entrance(); + + $result = array(); + + $instances_with_clone = array(); + $instances_with_clone_count = 0; + $install_by_instance_id = array(); + + $instances = ( ! empty( $fs_instances ) ) ? + $fs_instances : + Freemius::_get_all_instances(); + + $should_switch_to_blog = ( $blog_id > 0 ); + + foreach ( $instances as $instance ) { + if ( $should_switch_to_blog ) { + $instance->switch_to_blog( $blog_id ); + } + + if ( $instance->is_registered() && $instance->is_clone() ) { + $instances_with_clone[] = $instance; + + $instances_with_clone_count ++; + + $install_by_instance_id[ $instance->get_id() ] = $instance->get_site(); + } + } + + if ( self::OPTION_TEMPORARY_DUPLICATE === $clone_action ) { + $this->store_temporary_duplicate_timestamp(); + } else { + $redirect_url = ''; + + foreach ( $instances_with_clone as $instance ) { + if ( $should_switch_to_blog ) { + $instance->switch_to_blog( $blog_id ); + } + + $has_error = false; + + if ( self::OPTION_NEW_HOME === $clone_action ) { + $instance->sync_install( array( 'is_new_site' => true ), true ); + + if ( $instance->is_clone() ) { + $has_error = true; + } + } else { + $instance->_handle_long_term_duplicate(); + + if ( ! is_object( $instance->get_site() ) ) { + $has_error = true; + } + } + + if ( $has_error && 1 === $instances_with_clone_count ) { + $redirect_url = $instance->get_activation_url(); + } + } + + $result = ( array( 'redirect_url' => $redirect_url ) ); + } + + foreach ( $instances_with_clone as $instance ) { + if ( $should_switch_to_blog ) { + $instance->switch_to_blog( $blog_id ); + } + + // No longer a clone, send an update. + if ( ! $instance->is_clone() ) { + $instance->send_clone_resolution_update( + $clone_action, + $install_by_instance_id[ $instance->get_id() ] + ); + } + } + + if ( 'temporary_duplicate_license_activation' !== $clone_action ) { + $this->remove_clone_resolution_options_notice(); + } else { + $this->remove_temporary_duplicate_notice(); + } + + if ( $should_switch_to_blog ) { + foreach ( $instances as $instance ) { + $instance->restore_current_blog(); + } + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + private function hide_clone_admin_notices() { + $this->remove_clone_resolution_options_notice( false ); + $this->remove_temporary_duplicate_notice( false ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function maybe_show_clone_admin_notice() { + $this->_logger->entrance(); + + if ( fs_is_network_admin() ) { + $existing_notice_ids = $this->maybe_remove_notices(); + + if ( ! empty( $existing_notice_ids ) ) { + fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); + } + + return; + } + + $first_instance_with_clone = null; + + $site_urls = array(); + $sites_with_license_urls = array(); + $sites_with_premium_version_count = 0; + $product_ids = array(); + $product_titles = array(); + + $instances = Freemius::_get_all_instances(); + + foreach ( $instances as $instance ) { + if ( ! $instance->is_registered() ) { + continue; + } + + if ( ! $instance->is_clone( true ) ) { + continue; + } + + $install = $instance->get_site(); + + $site_urls[] = $install->url; + $product_ids[] = $instance->get_id(); + $product_titles[] = $instance->get_plugin_title(); + + if ( is_null( $first_instance_with_clone ) ) { + $first_instance_with_clone = $instance; + } + + if ( is_object( $instance->_get_license() ) ) { + $sites_with_license_urls[] = $install->url; + } + + if ( $instance->is_premium() ) { + $sites_with_premium_version_count ++; + } + } + + if ( empty( $site_urls ) && empty( $sites_with_license_urls ) ) { + $this->hide_clone_admin_notices(); + + return; + } + + $site_urls = array_unique( $site_urls ); + $sites_with_license_urls = array_unique( $sites_with_license_urls ); + + $module_label = fs_text_inline( 'products', 'products' ); + $admin_notice_module_title = null; + + $has_temporary_duplicate_mode_expired = $this->has_temporary_duplicate_mode_expired(); + + if ( + ! $this->was_temporary_duplicate_mode_selected() || + $has_temporary_duplicate_mode_expired + ) { + if ( ! empty( $site_urls ) ) { + fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); + + $doc_url = 'https://freemius.com/help/documentation/wordpress-sdk/safe-mode-clone-resolution-duplicate-website/'; + + if ( 1 === count( $instances ) ) { + $doc_url = fs_apply_filter( + $first_instance_with_clone->get_unique_affix(), + 'clone_resolution_documentation_url', + $doc_url + ); + } + + $this->add_manual_clone_resolution_admin_notice( + $product_ids, + $product_titles, + $site_urls, + Freemius::get_unfiltered_site_url(), + ( count( $site_urls ) === count( $sites_with_license_urls ) ), + ( count( $site_urls ) === $sites_with_premium_version_count ), + $doc_url + ); + } + + return; + } + + if ( empty( $sites_with_license_urls ) ) { + return; + } + + if ( ! $this->is_temporary_duplicate_notice_shown() ) { + $last_time_temporary_duplicate_notice_shown = $this->temporary_duplicate_notice_shown_timestamp; + $was_temporary_duplicate_notice_shown_before = is_numeric( $last_time_temporary_duplicate_notice_shown ); + + if ( $was_temporary_duplicate_notice_shown_before ) { + $temporary_duplicate_mode_expiration_timestamp = $this->get_temporary_duplicate_expiration_timestamp(); + $current_time = time(); + + if ( + $current_time > $temporary_duplicate_mode_expiration_timestamp || + $current_time < ( $temporary_duplicate_mode_expiration_timestamp - ( 2 * WP_FS__TIME_24_HOURS_IN_SEC ) ) + ) { + // Do not show the notice if the temporary duplicate mode has already expired or it will expire more than 2 days from now. + return; + } + } + } + + if ( 1 === count( $sites_with_license_urls ) ) { + $module_label = $first_instance_with_clone->get_module_label( true ); + $admin_notice_module_title = $first_instance_with_clone->get_plugin_title(); + } + + fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); + + $this->add_temporary_duplicate_sticky_notice( + $product_ids, + $this->get_temporary_duplicate_admin_notice_string( $sites_with_license_urls, $product_titles, $module_label ), + $admin_notice_module_title + ); + } + + /** + * Removes the notices from the storage if the context product is either no longer active on the context subsite or it's active but there's no longer any clone. This prevents the notices from being shown on the network-level admin page when they are no longer relevant. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + * + * @return string[] + */ + private function maybe_remove_notices() { + $notices = array( + 'clone_resolution_options_notice' => $this->_notices->get_sticky( 'clone_resolution_options_notice', true ), + 'temporary_duplicate_notice' => $this->_notices->get_sticky( 'temporary_duplicate_notice', true ), + ); + + $instances = Freemius::_get_all_instances(); + + foreach ( $notices as $id => $notice ) { + if ( ! is_array( $notice ) ) { + unset( $notices[ $id ] ); + continue; + } + + if ( empty( $notice['data'] ) || ! is_array( $notice['data'] ) ) { + continue; + } + + if ( empty( $notice['data']['product_ids'] ) || empty( $notice['data']['blog_id'] ) ) { + continue; + } + + $product_ids = $notice['data']['product_ids']; + $blog_id = $notice['data']['blog_id']; + $has_clone = false; + + if ( ! is_null( get_site( $blog_id ) ) ) { + foreach ( $product_ids as $product_id ) { + if ( ! isset( $instances[ 'm_' . $product_id ] ) ) { + continue; + } + + $instance = $instances[ 'm_' . $product_id ]; + + $plugin_basename = $instance->get_plugin_basename(); + + $is_plugin_active = is_plugin_active_for_network( $plugin_basename ); + + if ( ! $is_plugin_active ) { + switch_to_blog( $blog_id ); + + $is_plugin_active = is_plugin_active( $plugin_basename ); + + restore_current_blog(); + } + + if ( ! $is_plugin_active ) { + continue; + } + + $install = $instance->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) ) { + continue; + } + + $subsite_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); + + $has_clone = ( fs_strip_url_protocol( trailingslashit( $install->url ) ) !== $subsite_url ); + } + } + + if ( ! $has_clone ) { + $this->_notices->remove_sticky( $id, true, false ); + unset( $notices[ $id ] ); + } + } + + return array_keys( $notices ); + } + + /** + * Adds a notice that provides the logged-in WordPress user with manual clone resolution options. + * + * @param number[] $product_ids + * @param string[] $site_urls + * @param string $current_url + * @param bool $has_license + * @param bool $is_premium + * @param string $doc_url + */ + private function add_manual_clone_resolution_admin_notice( + $product_ids, + $product_titles, + $site_urls, + $current_url, + $has_license, + $is_premium, + $doc_url + ) { + $this->_logger->entrance(); + + $total_sites = count( $site_urls ); + $sites_list = ''; + + $total_products = count( $product_titles ); + $products_list = ''; + + if ( 1 === $total_products ) { + $notice_header = sprintf( + '

    %s

    ', + fs_esc_html_inline( '%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.', 'single-cloned-site-safe-mode-message' ) + ); + } else { + $notice_header = sprintf( + '

    %s

    ', + ( 1 === $total_sites ) ? + fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s', 'multiple-products-cloned-site-safe-mode-message' ) : + fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s', 'multiple-products-multiple-cloned-sites-safe-mode-message' ) + ); + + foreach ( $product_titles as $product_title ) { + $products_list .= sprintf( '
  • %s
  • ', $product_title ); + } + + $products_list = '
      ' . $products_list . '
    '; + + foreach ( $site_urls as $site_url ) { + $sites_list .= sprintf( + '
  • %s
  • ', + $site_url, + fs_strip_url_protocol( $site_url ) + ); + } + + $sites_list = '
      ' . $sites_list . '
    '; + } + + $remote_site_link = '' . (1 === $total_sites ? + sprintf( + '%s', + $site_urls[0], + fs_strip_url_protocol( $site_urls[0] ) + ) : + fs_text_inline( 'the above-mentioned sites', 'above-mentioned-sites' )) . ''; + + $current_site_link = sprintf( + '%s', + $current_url, + fs_strip_url_protocol( $current_url ) + ); + + $button_template = ''; + $option_template = '
    %s

    %s

    %s
    '; + + $duplicate_option = sprintf( + $option_template, + fs_esc_html_inline( 'Is %2$s a duplicate of %4$s?', 'duplicate-site-confirmation-message' ), + fs_esc_html_inline( 'Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.', 'duplicate-site-message' ), + ( $this->has_temporary_duplicate_mode_expired() ? + sprintf( + $button_template, + 'long_term_duplicate', + fs_text_inline( 'Long-Term Duplicate', 'long-term-duplicate' ) + ) : + sprintf( + $button_template, + 'temporary_duplicate', + fs_text_inline( 'Duplicate Website', 'duplicate-site' ) + ) ) + ); + + $migration_option = sprintf( + $option_template, + fs_esc_html_inline( 'Is %2$s the new home of %4$s?', 'migrate-site-confirmation-message' ), + sprintf( + fs_esc_html_inline( 'Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.', 'migrate-site-message' ), + ( $has_license ? fs_text_inline( 'license', 'license' ) : fs_text_inline( 'data', 'data' ) ) + ), + sprintf( + $button_template, + 'new_home', + $has_license ? + fs_text_inline( 'Migrate License', 'migrate-product-license' ) : + fs_text_inline( 'Migrate', 'migrate-product-data' ) + ) + ); + + $new_website = sprintf( + $option_template, + fs_esc_html_inline( 'Is %2$s a new website?', 'new-site-confirmation-message' ), + fs_esc_html_inline( 'Yes, %2$s is a new and different website that is separate from %4$s.', 'new-site-message' ) . + ($is_premium ? + ' ' . fs_text_inline( 'It requires license activation.', 'new-site-requires-license-activation-message' ) : + '' + ), + sprintf( + $button_template, + 'new_website', + ( ! $is_premium || ! $has_license ) ? + fs_text_inline( 'New Website', 'new-website' ) : + fs_text_inline( 'Activate License', 'activate-license' ) + ) + ); + + $blog_id = get_current_blog_id(); + + /** + * %1$s - single product's title or product titles list. + * %2$s - site's URL. + * %3$s - single install's URL or install URLs list. + * %4$s - Clone site's link or "the above-mentioned sites" if there are multiple clone sites. + */ + $message = sprintf( + $notice_header . + '
    ' . + $duplicate_option . + $migration_option . + $new_website . '
    ' . + sprintf( '
    Unsure what to do? Read more here.
    ', $doc_url ), + // %1$s + ( 1 === $total_products ? + sprintf( '%s', $product_titles[0] ) : + ( 1 === $total_sites ? + sprintf( '
    %s
    ', $products_list ) : + sprintf( '

    %s:

    %s
    ', fs_esc_html_x_inline( 'Products', 'Clone resolution admin notice products list label', 'products' ), $products_list ) ) + ), + // %2$s + $current_site_link, + // %3$s + ( 1 === $total_sites ? + $remote_site_link : + $sites_list ), + // %4$s + $remote_site_link + ); + + $this->_notices->add_sticky( + $message, + 'clone_resolution_options_notice', + '', + 'warn', + true, + null, + null, + true, + // Intentionally not dismissible. + false, + array( + 'product_ids' => $product_ids, + 'blog_id' => $blog_id + ) + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Temporary Duplicate (Short Term) + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return string + */ + private function get_temporary_duplicate_admin_notice_string( + $site_urls, + $product_titles, + $module_label + ) { + $this->_logger->entrance(); + + $temporary_duplicate_end_date = $this->get_temporary_duplicate_expiration_timestamp(); + $temporary_duplicate_end_date = date( 'M j, Y', $temporary_duplicate_end_date ); + + $current_url = Freemius::get_unfiltered_site_url(); + $current_site_link = sprintf( + '%s', + $current_url, + fs_strip_url_protocol( $current_url ) + ); + + $total_sites = count( $site_urls ); + $sites_list = ''; + + $total_products = count( $product_titles ); + $products_list = ''; + + if ( $total_sites > 1 ) { + foreach ( $site_urls as $site_url ) { + $sites_list .= sprintf( + '
  • %s
  • ', + $site_url, + fs_strip_url_protocol( $site_url ) + ); + } + + $sites_list = '
      ' . $sites_list . '
    '; + } + + if ( $total_products > 1 ) { + foreach ( $product_titles as $product_title ) { + $products_list .= sprintf( '
  • %s
  • ', $product_title ); + } + + $products_list = '
      ' . $products_list . '
    '; + } + + return sprintf( + sprintf( + '
    %s
    ', + ( 1 === $total_sites ? + sprintf( '

    %s

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of %s.', 'temporary-duplicate-message' ) ) : + sprintf( '

    %s:

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of these sites', 'temporary-duplicate-of-sites-message' ) ) . '%s' ) + ) . '%s', + $current_site_link, + ( 1 === $total_sites ? + sprintf( + '%s', + $site_urls[0], + fs_strip_url_protocol( $site_urls[0] ) + ) : + $sites_list ), + sprintf( + '

    %s

    %s

    %s

    ', + esc_attr( admin_url( 'admin-ajax.php?_fs_network_admin=false', 'relative' ) ), + sprintf( + fs_esc_html_inline( "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).", 'duplicate-site-confirmation-message' ), + ( 1 === $total_products ? + sprintf( + fs_esc_html_x_inline( "The %s's", '"The ", e.g.: "The plugin"', 'the-product-x'), + "{$module_label}" + ) : + fs_esc_html_inline( "The following products'", 'the-following-products' ) ), + sprintf( '%s', $temporary_duplicate_end_date ) + ), + ( 1 === $total_products ? + '' : + sprintf( '
    %s
    ', $products_list ) + ), + sprintf( + fs_esc_html_inline( 'If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.', 'duplicate-site-message' ), + sprintf( '%s', $temporary_duplicate_end_date), + sprintf( '%s', fs_esc_html_inline( 'activate a license here', 'activate-license-here' ) ) + ) + ) + ); + } + + /** + * Determines if the temporary duplicate mode has already expired. + * + * @return bool + */ + function has_temporary_duplicate_mode_expired() { + $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? + $this->get_option( 'temporary_duplicate_mode_selection_timestamp', true ) : + $this->get_clone_identification_timestamp(); + + if ( ! is_numeric( $temporary_duplicate_mode_start_timestamp ) ) { + return false; + } + + return ( time() > ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) ); + } + + /** + * Determines if the logged-in WordPress user manually selected the temporary duplicate mode for the site. + * + * @return bool + */ + function was_temporary_duplicate_mode_selected() { + return is_numeric( $this->temporary_duplicate_mode_selection_timestamp ); + } + + /** + * Stores the time when the logged-in WordPress user selected the temporary duplicate mode for the site. + */ + private function store_temporary_duplicate_timestamp() { + $this->temporary_duplicate_mode_selection_timestamp = time(); + } + + /** + * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. + * + * @param bool $store + */ + function remove_clone_resolution_options_notice( $store = true ) { + $this->_notices->remove_sticky( 'clone_resolution_options_notice', true, $store ); + } + + /** + * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. + * + * @param bool $store + */ + function remove_temporary_duplicate_notice( $store = true ) { + $this->_notices->remove_sticky( 'temporary_duplicate_notice', true, $store ); + } + + /** + * Determines if the manual clone resolution options notice is currently being shown. + * + * @return bool + */ + function is_clone_resolution_options_notice_shown() { + return $this->_notices->has_sticky( 'clone_resolution_options_notice', true ); + } + + /** + * Determines if the temporary duplicate notice is currently being shown. + * + * @return bool + */ + function is_temporary_duplicate_notice_shown() { + return $this->_notices->has_sticky( 'temporary_duplicate_notice', true ); + } + + /** + * Determines if a site was marked as a temporary duplicate and if it's still a temporary duplicate. + * + * @return bool + */ + function is_temporary_duplicate_by_blog_id( $blog_id ) { + $timestamp = $this->get_option( 'temporary_duplicate_mode_selection_timestamp', false, $blog_id ); + + return ( + is_numeric( $timestamp ) && + time() < ( $timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) + ); + } + + /** + * Determines the last time the temporary duplicate notice was shown. + * + * @return int|null + */ + function last_time_temporary_duplicate_notice_was_shown() { + return $this->temporary_duplicate_notice_shown_timestamp; + } + + /** + * Clears the time that has been stored when the temporary duplicate notice was shown. + */ + function clear_temporary_duplicate_notice_shown_timestamp() { + unset( $this->temporary_duplicate_notice_shown_timestamp ); + } + + /** + * Adds a temporary duplicate notice that provides the logged-in WordPress user with an option to activate a license for the site. + * + * @param number[] $product_ids + * @param string $message + * @param string|null $plugin_title + */ + function add_temporary_duplicate_sticky_notice( + $product_ids, + $message, + $plugin_title = null + ) { + $this->_logger->entrance(); + + $this->_notices->add_sticky( + $message, + 'temporary_duplicate_notice', + '', + 'promotion', + true, + null, + $plugin_title, + true, + true, + array( + 'product_ids' => $product_ids, + 'blog_id' => get_current_blog_id() + ) + ); + + $this->temporary_duplicate_notice_shown_timestamp = time(); + } + + #endregion + + /** + * @author Leo Fajardo + * @since 2.5.0 + * + * @param string $key + * + * @return bool + */ + private function should_use_network_storage( $key ) { + return ( 'new_blog_install_map' === $key ); + } + + /** + * @param string $key + * @param number|null $blog_id + * + * @return FS_Option_Manager + */ + private function get_storage( $key, $blog_id = null ) { + if ( is_numeric( $blog_id ) ){ + return FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, $blog_id ); + } + + return $this->should_use_network_storage( $key ) ? + $this->_network_storage : + $this->_storage; + } + + /** + * @param string $name + * @param bool $flush + * @param number|null $blog_id + * + * @return mixed + */ + private function get_option( $name, $flush = false, $blog_id = null ) { + return $this->get_storage( $name, $blog_id )->get_option( $name, null, $flush ); + } + + #-------------------------------------------------------------------------------- + #region Magic methods + #-------------------------------------------------------------------------------- + + /** + * @param string $name + * @param int|string $value + */ + function __set( $name, $value ) { + $this->get_storage( $name )->set_option( $name, $value, true ); + } + + /** + * @param string $name + * + * @return bool + */ + function __isset( $name ) { + return $this->get_storage( $name )->has_option( $name, true ); + } + + /** + * @param string $name + */ + function __unset( $name ) { + $this->get_storage( $name )->unset_option( $name, true ); + } + + /** + * @param string $name + * + * @return null|int|string + */ + function __get( $name ) { + return $this->get_option( + $name, + // Reload storage from DB when accessing request_handler_* options to avoid race conditions. + fs_starts_with( $name, 'request_handler' ) + ); + } + + #endregion + } diff --git a/freemius/includes/managers/class-fs-option-manager.php b/freemius/includes/managers/class-fs-option-manager.php index 9d59abf..1573847 100644 --- a/freemius/includes/managers/class-fs-option-manager.php +++ b/freemius/includes/managers/class-fs-option-manager.php @@ -11,14 +11,11 @@ } /** - * 3-layer lazy options manager. - * layer 3: Memory - * layer 2: Cache (if there's any caching plugin and if WP_FS__DEBUG_SDK is FALSE) - * layer 1: Database (options table). All options stored as one option record in the DB to reduce number of DB - * queries. + * 2-layer lazy options manager. + * layer 2: Memory + * layer 1: Database (options table). All options stored as one option record in the DB to reduce number of DB queries. * - * If load() is not explicitly called, starts as empty manager. Same thing about saving the data - you have to - * explicitly call store(). + * If load() is not explicitly called, starts as empty manager. Same thing about saving the data - you have to explicitly call store(). * * Class Freemius_Option_Manager */ @@ -157,59 +154,33 @@ static function get_manager( function load( $flush = false ) { $this->_logger->entrance(); - $option_name = $this->get_option_manager_name(); - - if ( $flush || ! isset( $this->_options ) ) { - if ( isset( $this->_options ) ) { - // Clear prev options. - $this->clear(); - } - - $cache_group = $this->get_cache_group(); - - if ( WP_FS__DEBUG_SDK ) { - - // Don't use cache layer in DEBUG mode. - $load_options = empty( $this->_options ); - - } else { - - $this->_options = wp_cache_get( - $option_name, - $cache_group - ); + if ( ! $flush && isset( $this->_options ) ) { + return; + } - $load_options = ( false === $this->_options ); - } + if ( isset( $this->_options ) ) { + // Clear prev options. + $this->clear(); + } - $cached = true; + $option_name = $this->get_option_manager_name(); - if ( $load_options ) { - if ( $this->_is_network_storage ) { - $this->_options = get_site_option( $option_name ); - } else if ( $this->_blog_id > 0 ) { - $this->_options = get_blog_option( $this->_blog_id, $option_name ); - } else { - $this->_options = get_option( $option_name ); - } + if ( $this->_is_network_storage ) { + $this->_options = get_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + $this->_options = get_blog_option( $this->_blog_id, $option_name ); + } else { + $this->_options = get_option( $option_name ); + } - if ( is_string( $this->_options ) ) { - $this->_options = json_decode( $this->_options ); - } + if ( is_string( $this->_options ) ) { + $this->_options = json_decode( $this->_options ); + } // $this->_logger->info('get_option = ' . var_export($this->_options, true)); - if ( false === $this->_options ) { - $this->clear(); - } - - $cached = false; - } - - if ( ! WP_FS__DEBUG_SDK && ! $cached ) { - // Set non encoded cache. - wp_cache_set( $option_name, $this->_options, $cache_group ); - } + if ( false === $this->_options ) { + $this->clear(); } } @@ -272,10 +243,15 @@ function delete() { * @since 1.0.6 * * @param string $option + * @param bool $flush * * @return bool */ - function has_option( $option ) { + function has_option( $option, $flush = false ) { + if ( ! $this->is_loaded() || $flush ) { + $this->load( $flush ); + } + return array_key_exists( $option, $this->_options ); } @@ -285,14 +261,15 @@ function has_option( $option ) { * * @param string $option * @param mixed $default + * @param bool $flush * * @return mixed */ - function get_option( $option, $default = null ) { + function get_option( $option, $default = null, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); - if ( ! $this->is_loaded() ) { - $this->load(); + if ( ! $this->is_loaded() || $flush ) { + $this->load( $flush ); } if ( is_array( $this->_options ) ) { @@ -310,7 +287,7 @@ function get_option( $option, $default = null ) { /** * If it's an object, return a clone of the object, otherwise, * external changes of the object will actually change the value - * of the object in the options manager which may lead to an unexpected + * of the object in the option manager which may lead to an unexpected * behaviour and data integrity when a store() call is triggered. * * Example: @@ -436,10 +413,6 @@ function store() { } else { update_option( $option_name, $this->_options, $this->_autoload ); } - - if ( ! WP_FS__DEBUG_SDK ) { - wp_cache_set( $option_name, $this->_options, $this->get_cache_group() ); - } } /** @@ -499,23 +472,5 @@ private function get_option_manager_name() { return $this->_id; } - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - private function get_cache_group() { - $group = WP_FS__SLUG; - - if ( $this->_is_network_storage ) { - $group .= '_ms'; - } else if ( $this->_blog_id > 0 ) { - $group .= "_s{$this->_blog_id}"; - } - - return $group; - } - #endregion } diff --git a/freemius/includes/managers/class-fs-permission-manager.php b/freemius/includes/managers/class-fs-permission-manager.php new file mode 100644 index 0000000..727bf4d --- /dev/null +++ b/freemius/includes/managers/class-fs-permission-manager.php @@ -0,0 +1,698 @@ + + */ + private static $_instances = array(); + + const PERMISSION_USER = 'user'; + const PERMISSION_SITE = 'site'; + const PERMISSION_EVENTS = 'events'; + const PERMISSION_ESSENTIALS = 'essentials'; + const PERMISSION_DIAGNOSTIC = 'diagnostic'; + const PERMISSION_EXTENSIONS = 'extensions'; + const PERMISSION_NEWSLETTER = 'newsletter'; + + /** + * @param Freemius $fs + * + * @return self + */ + static function instance( Freemius $fs ) { + $id = $fs->get_id(); + + if ( ! isset( self::$_instances[ $id ] ) ) { + self::$_instances[ $id ] = new self( $fs ); + } + + return self::$_instances[ $id ]; + } + + /** + * @param Freemius $fs + */ + protected function __construct( Freemius $fs ) { + $this->_fs = $fs; + $this->_storage = FS_Storage::instance( $fs->get_module_type(), $fs->get_slug() ); + } + + /** + * @return string[] + */ + static function get_all_permission_ids() { + return array( + self::PERMISSION_USER, + self::PERMISSION_SITE, + self::PERMISSION_EVENTS, + self::PERMISSION_ESSENTIALS, + self::PERMISSION_DIAGNOSTIC, + self::PERMISSION_EXTENSIONS, + self::PERMISSION_NEWSLETTER, + ); + } + + /** + * @return string[] + */ + static function get_api_managed_permission_ids() { + return array( + self::PERMISSION_USER, + self::PERMISSION_SITE, + self::PERMISSION_EXTENSIONS, + ); + } + + /** + * @param string $permission + * + * @return bool + */ + static function is_supported_permission( $permission ) { + return in_array( $permission, self::get_all_permission_ids() ); + } + + /** + * @param bool $is_license_activation + * @param array[] $extra_permissions + * + * @return array[] + */ + function get_permissions( $is_license_activation, array $extra_permissions = array() ) { + return $is_license_activation ? + $this->get_license_activation_permissions( $extra_permissions ) : + $this->get_opt_in_permissions( $extra_permissions ); + } + + #-------------------------------------------------------------------------------- + #region Opt-In Permissions + #-------------------------------------------------------------------------------- + + /** + * @param array[] $extra_permissions + * + * @return array[] + */ + function get_opt_in_permissions( + array $extra_permissions = array(), + $load_default_from_storage = false, + $is_optional = false + ) { + $permissions = array_merge( + $this->get_opt_in_required_permissions( $load_default_from_storage ), + $this->get_opt_in_optional_permissions( $load_default_from_storage, $is_optional ), + $extra_permissions + ); + + return $this->get_sorted_permissions_by_priority( $permissions ); + } + + /** + * @param bool $load_default_from_storage + * + * @return array[] + */ + function get_opt_in_required_permissions( $load_default_from_storage = false ) { + return array( $this->get_user_permission( $load_default_from_storage ) ); + } + + /** + * @param bool $load_default_from_storage + * @param bool $is_optional + * + * @return array[] + */ + function get_opt_in_optional_permissions( + $load_default_from_storage = false, + $is_optional = false + ) { + return array_merge( + $this->get_opt_in_diagnostic_permissions( $load_default_from_storage, $is_optional ), + array( $this->get_extensions_permission( + false, + false, + $load_default_from_storage + ) ) + ); + } + + /** + * @param bool $load_default_from_storage + * @param bool $is_optional + * + * @return array[] + */ + function get_opt_in_diagnostic_permissions( + $load_default_from_storage = false, + $is_optional = false + ) { + // Alias. + $fs = $this->_fs; + + $permissions = array(); + + $permissions[] = $this->get_permission( + self::PERMISSION_SITE, + 'admin-links', + $fs->get_text_inline( 'View Basic Website Info', 'permissions-site' ), + $fs->get_text_inline( 'Homepage URL & title, WP & PHP versions, and site language', 'permissions-site_desc' ), + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + $fs->get_text_inline( 'To provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-site_tooltip' ), + $fs->get_module_label( true ) + ), + 10, + $is_optional, + true, + $load_default_from_storage + ); + + $permissions[] = $this->get_permission( + self::PERMISSION_EVENTS, + 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), + sprintf( $fs->get_text_inline( 'View Basic %s Info', 'permissions-events' ), $fs->get_module_label() ), + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + $fs->get_text_inline( 'Current %s & SDK versions, and if active or uninstalled', 'permissions-events_desc' ), + $fs->get_module_label( true ) + ), + '', + 20, + $is_optional, + true, + $load_default_from_storage + ); + + return $permissions; + } + + #endregion + + #-------------------------------------------------------------------------------- + #region License Activation Permissions + #-------------------------------------------------------------------------------- + + /** + * @param array[] $extra_permissions + * + * @return array[] + */ + function get_license_activation_permissions( + array $extra_permissions = array(), + $include_optional_label = true + ) { + $permissions = array_merge( + $this->get_license_required_permissions(), + $this->get_license_optional_permissions( $include_optional_label ), + $extra_permissions + ); + + return $this->get_sorted_permissions_by_priority( $permissions ); + } + + /** + * @param bool $load_default_from_storage + * + * @return array[] + */ + function get_license_required_permissions( $load_default_from_storage = false ) { + // Alias. + $fs = $this->_fs; + + $permissions = array(); + + $permissions[] = $this->get_permission( + self::PERMISSION_ESSENTIALS, + 'admin-links', + $fs->get_text_inline( 'View License Essentials', 'permissions-essentials' ), + $fs->get_text_inline( + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + 'Homepage URL, %s version, SDK version', + $fs->get_module_label() + ), + 'permissions-essentials_desc' + ), + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + $fs->get_text_inline( 'To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.', 'permissions-essentials_tooltip' ), + $fs->get_module_label( true ) + ), + 10, + false, + true, + $load_default_from_storage + ); + + $permissions[] = $this->get_permission( + self::PERMISSION_EVENTS, + 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), + sprintf( $fs->get_text_inline( 'View %s State', 'permissions-events' ), $fs->get_module_label() ), + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + $fs->get_text_inline( 'Is active, deactivated, or uninstalled', 'permissions-events_desc-paid' ), + $fs->get_module_label( true ) + ), + sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_label( true ) ), + 20, + false, + true, + $load_default_from_storage + ); + + return $permissions; + } + + /** + * @return array[] + */ + function get_license_optional_permissions( + $include_optional_label = false, + $load_default_from_storage = false + ) { + return array( + $this->get_diagnostic_permission( $include_optional_label, $load_default_from_storage ), + $this->get_extensions_permission( true, $include_optional_label, $load_default_from_storage ), + ); + } + + /** + * @param bool $include_optional_label + * @param bool $load_default_from_storage + * + * @return array + */ + function get_diagnostic_permission( + $include_optional_label = false, + $load_default_from_storage = false + ) { + return $this->get_permission( + self::PERMISSION_DIAGNOSTIC, + 'wordpress-alt', + $this->_fs->get_text_inline( 'View Diagnostic Info', 'permissions-diagnostic' ) . ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ), + $this->_fs->get_text_inline( 'WordPress & PHP versions, site language & title', 'permissions-diagnostic_desc' ), + sprintf( + /* translators: %s: 'Plugin' or 'Theme' */ + $this->_fs->get_text_inline( 'To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-diagnostic_tooltip' ), + $this->_fs->get_module_label( true ) + ), + 25, + true, + true, + $load_default_from_storage + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Common Permissions + #-------------------------------------------------------------------------------- + + /** + * @param bool $is_license_activation + * @param bool $include_optional_label + * @param bool $load_default_from_storage + * + * @return array + */ + function get_extensions_permission( + $is_license_activation, + $include_optional_label = false, + $load_default_from_storage = false + ) { + $is_on_by_default = ! $is_license_activation; + + return $this->get_permission( + self::PERMISSION_EXTENSIONS, + 'block-default', + $this->_fs->get_text_inline( 'View Plugins & Themes List', 'permissions-extensions' ) . ( $is_license_activation ? ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ) : '' ), + $this->_fs->get_text_inline( 'Names, slugs, versions, and if active or not', 'permissions-extensions_desc' ), + $this->_fs->get_text_inline( 'To ensure compatibility and avoid conflicts with your installed plugins and themes.', 'permissions-events_tooltip' ), + 25, + true, + $is_on_by_default, + $load_default_from_storage + ); + } + + /** + * @param bool $load_default_from_storage + * + * @return array + */ + function get_user_permission( $load_default_from_storage = false ) { + return $this->get_permission( + self::PERMISSION_USER, + 'admin-users', + $this->_fs->get_text_inline( 'View Basic Profile Info', 'permissions-profile' ), + $this->_fs->get_text_inline( 'Your WordPress user\'s: first & last name, and email address', 'permissions-profile_desc' ), + $this->_fs->get_text_inline( 'Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.', 'permissions-profile_tooltip' ), + 5, + false, + true, + $load_default_from_storage + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Optional Permissions + #-------------------------------------------------------------------------------- + + /** + * @return array[] + */ + function get_newsletter_permission() { + return $this->get_permission( + self::PERMISSION_NEWSLETTER, + 'email-alt', + $this->_fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), + $this->_fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + '', + 15 + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Permissions Storage + #-------------------------------------------------------------------------------- + + /** + * @param int|null $blog_id + * + * @return bool + */ + function is_extensions_tracking_allowed( $blog_id = null ) { + return $this->is_permission_allowed( self::PERMISSION_EXTENSIONS, ! $this->_fs->is_premium(), $blog_id ); + } + + /** + * @param int|null $blog_id + * + * @return bool + */ + function is_essentials_tracking_allowed( $blog_id = null ) { + return $this->is_permission_allowed( self::PERMISSION_ESSENTIALS, true, $blog_id ); + } + + /** + * @param bool $default + * + * @return bool + */ + function is_diagnostic_tracking_allowed( $default = true ) { + return $this->_fs->is_premium() ? + $this->is_permission_allowed( self::PERMISSION_DIAGNOSTIC, $default ) : + $this->is_permission_allowed( self::PERMISSION_SITE, $default ); + } + + /** + * @param int|null $blog_id + * + * @return bool + */ + function is_homepage_url_tracking_allowed( $blog_id = null ) { + return $this->is_permission_allowed( $this->get_site_permission_name(), true, $blog_id ); + } + + /** + * @param int|null $blog_id + * + * @return bool + */ + function update_site_tracking( $is_enabled, $blog_id = null, $only_if_not_set = false ) { + $permissions = $this->get_site_tracking_permission_names(); + + $result = true; + foreach ( $permissions as $permission ) { + if ( ! $only_if_not_set || ! $this->is_permission_set( $permission, $blog_id ) ) { + $result = ( $result && $this->update_permission_tracking_flag( $permission, $is_enabled, $blog_id ) ); + } + } + + return $result; + } + + /** + * @param string $permission + * @param bool $default + * @param int|null $blog_id + * + * @return bool + */ + function is_permission_allowed( $permission, $default = false, $blog_id = null ) { + if ( ! self::is_supported_permission( $permission ) ) { + return $default; + } + + return $this->is_permission( $permission, true, $blog_id ); + } + + /** + * @param string $permission + * @param bool $is_allowed + * @param int|null $blog_id + * + * @return bool + */ + function is_permission( $permission, $is_allowed, $blog_id = null ) { + if ( ! self::is_supported_permission( $permission ) ) { + return false; + } + + $tag = "is_{$permission}_tracking_allowed"; + + return ( $is_allowed === $this->_fs->apply_filters( + $tag, + $this->_storage->get( + $tag, + $this->get_permission_default( $permission ), + $blog_id, + FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED + ) + ) ); + } + + /** + * @param string $permission + * @param int|null $blog_id + * + * @return bool + */ + function is_permission_set( $permission, $blog_id = null ) { + $tag = "is_{$permission}_tracking_allowed"; + + $permission = $this->_storage->get( + $tag, + null, + $blog_id, + FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED + ); + + return is_bool( $permission ); + } + + /** + * @param string[] $permissions + * @param bool $is_allowed + * + * @return bool `true` if all given permissions are in sync with `$is_allowed`. + */ + function are_permissions( $permissions, $is_allowed, $blog_id = null ) { + foreach ( $permissions as $permission ) { + if ( ! $this->is_permission( $permission, $is_allowed, $blog_id ) ) { + return false; + } + } + + return true; + } + + /** + * @param string $permission + * @param bool $is_enabled + * @param int|null $blog_id + * + * @return bool `false` if permission not supported or `$is_enabled` is not a boolean. + */ + function update_permission_tracking_flag( $permission, $is_enabled, $blog_id = null ) { + if ( is_bool( $is_enabled ) && self::is_supported_permission( $permission ) ) { + $this->_storage->store( + "is_{$permission}_tracking_allowed", + $is_enabled, + $blog_id, + FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED + ); + + return true; + } + + return false; + } + + /** + * @param array $permissions + */ + function update_permissions_tracking_flag( $permissions ) { + foreach ( $permissions as $permission => $is_enabled ) { + $this->update_permission_tracking_flag( $permission, $is_enabled ); + } + } + + #endregion + + + /** + * @param string $permission + * + * @return bool + */ + function get_permission_default( $permission ) { + if ( + $this->_fs->is_premium() && + self::PERMISSION_EXTENSIONS === $permission + ) { + return false; + } + + // All permissions except for the extensions in paid version are on by default when the user opts in to usage tracking. + return true; + } + + /** + * @return string + */ + function get_site_permission_name() { + return $this->_fs->is_premium() ? + self::PERMISSION_ESSENTIALS : + self::PERMISSION_SITE; + } + + /** + * @return string[] + */ + function get_site_tracking_permission_names() { + return $this->_fs->is_premium() ? + array( + FS_Permission_Manager::PERMISSION_ESSENTIALS, + FS_Permission_Manager::PERMISSION_EVENTS, + ) : + array( FS_Permission_Manager::PERMISSION_SITE ); + } + + #-------------------------------------------------------------------------------- + #region Rendering + #-------------------------------------------------------------------------------- + + /** + * @param array $permission + */ + function render_permission( array $permission ) { + fs_require_template( 'connect/permission.php', $permission ); + } + + /** + * @param array $permissions_group + */ + function render_permissions_group( array $permissions_group ) { + $permissions_group[ 'fs' ] = $this->_fs; + + fs_require_template( 'connect/permissions-group.php', $permissions_group ); + } + + function require_permissions_js() { + fs_require_once_template( 'js/permissions.php', $params ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @param string $id + * @param string $dashicon + * @param string $label + * @param string $desc + * @param string $tooltip + * @param int $priority + * @param bool $is_optional + * @param bool $is_on_by_default + * @param bool $load_from_storage + * + * @return array + */ + private function get_permission( + $id, + $dashicon, + $label, + $desc, + $tooltip = '', + $priority = 10, + $is_optional = false, + $is_on_by_default = true, + $load_from_storage = false + ) { + $is_on = $load_from_storage ? + $this->is_permission_allowed( $id, $is_on_by_default ) : + $is_on_by_default; + + return array( + 'id' => $id, + 'icon-class' => $this->_fs->apply_filters( "permission_{$id}_icon", "dashicons dashicons-{$dashicon}" ), + 'label' => $this->_fs->apply_filters( "permission_{$id}_label", $label ), + 'tooltip' => $this->_fs->apply_filters( "permission_{$id}_tooltip", $tooltip ), + 'desc' => $this->_fs->apply_filters( "permission_{$id}_desc", $desc ), + 'priority' => $this->_fs->apply_filters( "permission_{$id}_priority", $priority ), + 'optional' => $is_optional, + 'default' => $this->_fs->apply_filters( "permission_{$id}_default", $is_on ), + ); + } + + /** + * @param array $permissions + * + * @return array[] + */ + private function get_sorted_permissions_by_priority( array $permissions ) { + // Allow filtering of the permissions list. + $permissions = $this->_fs->apply_filters( 'permission_list', $permissions ); + + // Sort by priority. + uasort( $permissions, 'fs_sort_by_priority' ); + + return $permissions; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-plugin-manager.php b/freemius/includes/managers/class-fs-plugin-manager.php index bacf160..bafca67 100644 --- a/freemius/includes/managers/class-fs-plugin-manager.php +++ b/freemius/includes/managers/class-fs-plugin-manager.php @@ -211,10 +211,23 @@ function set( FS_Plugin $plugin, $store = false ) { * @return bool|\FS_Plugin */ function get() { - return isset( $this->_module ) ? - $this->_module : - false; + if ( isset( $this->_module ) ) { + return $this->_module; + } + + if ( empty( $this->_module_id ) ) { + return false; + } + + /** + * Return an FS_Plugin entity that has its `id` and `is_live` properties set (`is_live` is initialized in the FS_Plugin constructor) to avoid triggering an error that is relevant to these properties when the FS_Plugin entity is used before the `parse_settings()` method is called. This can happen when creating a regular WordPress site by cloning a subsite of a multisite network and the data that is stored in the network-level storage is not cloned. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + $plugin = new FS_Plugin(); + $plugin->id = $this->_module_id; + + return $plugin; } - - } \ No newline at end of file diff --git a/freemius/includes/sdk/FreemiusBase.php b/freemius/includes/sdk/FreemiusBase.php index a486e90..42ecbc2 100644 --- a/freemius/includes/sdk/FreemiusBase.php +++ b/freemius/includes/sdk/FreemiusBase.php @@ -46,174 +46,172 @@ require_once FS_SDK__EXCEPTIONS_PATH . $e . '.php'; } - if ( class_exists( 'Freemius_Api_Base' ) ) { - return; - } - - abstract class Freemius_Api_Base { - const VERSION = '1.0.4'; - const FORMAT = 'json'; - - protected $_id; - protected $_public; - protected $_secret; - protected $_scope; - protected $_isSandbox; - - /** - * @param string $pScope 'app', 'developer', 'plugin', 'user' or 'install'. - * @param number $pID Element's id. - * @param string $pPublic Public key. - * @param string $pSecret Element's secret key. - * @param bool $pIsSandbox Whether or not to run API in sandbox mode. - */ - public function Init( $pScope, $pID, $pPublic, $pSecret, $pIsSandbox = false ) { - $this->_id = $pID; - $this->_public = $pPublic; - $this->_secret = $pSecret; - $this->_scope = $pScope; - $this->_isSandbox = $pIsSandbox; - } - - public function IsSandbox() { - return $this->_isSandbox; - } - - function CanonizePath( $pPath ) { - $pPath = trim( $pPath, '/' ); - $query_pos = strpos( $pPath, '?' ); - $query = ''; - - if ( false !== $query_pos ) { - $query = substr( $pPath, $query_pos ); - $pPath = substr( $pPath, 0, $query_pos ); - } - - // Trim '.json' suffix. - $format_length = strlen( '.' . self::FORMAT ); - $start = $format_length * ( - 1 ); //negative - if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { - $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); - } - - switch ( $this->_scope ) { - case 'app': - $base = '/apps/' . $this->_id; - break; - case 'developer': - $base = '/developers/' . $this->_id; - break; - case 'user': - $base = '/users/' . $this->_id; - break; - case 'plugin': - $base = '/plugins/' . $this->_id; - break; - case 'install': - $base = '/installs/' . $this->_id; - break; - default: - throw new Freemius_Exception( 'Scope not implemented.' ); - } - - return '/v' . FS_API__VERSION . $base . - ( ! empty( $pPath ) ? '/' : '' ) . $pPath . - ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; - } - - abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); - - /** - * @param string $pPath - * @param string $pMethod - * @param array $pParams - * - * @return object[]|object|null - */ - private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { - $pMethod = strtoupper( $pMethod ); - - try { - $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); - } catch ( Freemius_Exception $e ) { - // Map to error object. - $result = (object) $e->getResult(); - } catch ( Exception $e ) { - // Map to error object. - $result = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - - return $result; - } - - public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { - return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); - } - - /** - * Base64 decoding that does not need to be urldecode()-ed. - * - * Exactly the same as PHP base64 encode except it uses - * `-` instead of `+` - * `_` instead of `/` - * No padded = - * - * @param string $input Base64UrlEncoded() string - * - * @return string - */ - protected static function Base64UrlDecode( $input ) { - /** - * IMPORTANT NOTE: - * This is a hack suggested by @otto42 and @greenshady from - * the theme's review team. The usage of base64 for API - * signature encoding was approved in a Slack meeting - * held on Tue (10/25 2016). - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @since 1.2.2 - * @author Vova Feldman (@svovaf) - */ - $fn = 'base64' . '_decode'; - return $fn( strtr( $input, '-_', '+/' ) ); - } - - /** - * Base64 encoding that does not need to be urlencode()ed. - * - * Exactly the same as base64 encode except it uses - * `-` instead of `+ - * `_` instead of `/` - * - * @param string $input string - * - * @return string Base64 encoded string - */ - protected static function Base64UrlEncode( $input ) { - /** - * IMPORTANT NOTE: - * This is a hack suggested by @otto42 and @greenshady from - * the theme's review team. The usage of base64 for API - * signature encoding was approved in a Slack meeting - * held on Tue (10/25 2016). - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @since 1.2.2 - * @author Vova Feldman (@svovaf) - */ - $fn = 'base64' . '_encode'; - $str = strtr( $fn( $input ), '+/', '-_' ); - $str = str_replace( '=', '', $str ); - - return $str; - } - } + if ( ! class_exists( 'Freemius_Api_Base' ) ) { + abstract class Freemius_Api_Base { + const VERSION = '1.0.4'; + const FORMAT = 'json'; + + protected $_id; + protected $_public; + protected $_secret; + protected $_scope; + protected $_isSandbox; + + /** + * @param string $pScope 'app', 'developer', 'plugin', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string $pSecret Element's secret key. + * @param bool $pIsSandbox Whether or not to run API in sandbox mode. + */ + public function Init( $pScope, $pID, $pPublic, $pSecret, $pIsSandbox = false ) { + $this->_id = $pID; + $this->_public = $pPublic; + $this->_secret = $pSecret; + $this->_scope = $pScope; + $this->_isSandbox = $pIsSandbox; + } + + public function IsSandbox() { + return $this->_isSandbox; + } + + function CanonizePath( $pPath ) { + $pPath = trim( $pPath, '/' ); + $query_pos = strpos( $pPath, '?' ); + $query = ''; + + if ( false !== $query_pos ) { + $query = substr( $pPath, $query_pos ); + $pPath = substr( $pPath, 0, $query_pos ); + } + + // Trim '.json' suffix. + $format_length = strlen( '.' . self::FORMAT ); + $start = $format_length * ( - 1 ); //negative + if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { + $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); + } + + switch ( $this->_scope ) { + case 'app': + $base = '/apps/' . $this->_id; + break; + case 'developer': + $base = '/developers/' . $this->_id; + break; + case 'user': + $base = '/users/' . $this->_id; + break; + case 'plugin': + $base = '/plugins/' . $this->_id; + break; + case 'install': + $base = '/installs/' . $this->_id; + break; + default: + throw new Freemius_Exception( 'Scope not implemented.' ); + } + + return '/v' . FS_API__VERSION . $base . + ( ! empty( $pPath ) ? '/' : '' ) . $pPath . + ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; + } + + abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); + + /** + * @param string $pPath + * @param string $pMethod + * @param array $pParams + * + * @return object[]|object|null + */ + private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + $pMethod = strtoupper( $pMethod ); + + try { + $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); + } + + /** + * Base64 decoding that does not need to be urldecode()-ed. + * + * Exactly the same as PHP base64 encode except it uses + * `-` instead of `+` + * `_` instead of `/` + * No padded = + * + * @param string $input Base64UrlEncoded() string + * + * @return string + */ + protected static function Base64UrlDecode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_decode'; + return $fn( strtr( $input, '-_', '+/' ) ); + } + + /** + * Base64 encoding that does not need to be urlencode()ed. + * + * Exactly the same as base64 encode except it uses + * `-` instead of `+ + * `_` instead of `/` + * + * @param string $input string + * + * @return string Base64 encoded string + */ + protected static function Base64UrlEncode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_encode'; + $str = strtr( $fn( $input ), '+/', '-_' ); + $str = str_replace( '=', '', $str ); + + return $str; + } + } + } \ No newline at end of file diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index 354273d..3c5da90 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -1,715 +1,715 @@ - '7.37' ); - - if ( ! defined( 'FS_API__PROTOCOL' ) ) { - define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); - } - - if ( ! defined( 'FS_API__LOGGER_ON' ) ) { - define( 'FS_API__LOGGER_ON', false ); - } - - if ( ! defined( 'FS_API__ADDRESS' ) ) { - define( 'FS_API__ADDRESS', '://api.freemius.com' ); - } - if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { - define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); - } - - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - return; - } - - class Freemius_Api_WordPress extends Freemius_Api_Base { - private static $_logger = array(); - - /** - * @param string $pScope 'app', 'developer', 'user' or 'install'. - * @param number $pID Element's id. - * @param string $pPublic Public key. - * @param string|bool $pSecret Element's secret key. - * @param bool $pSandbox Whether or not to run API in sandbox mode. - */ - public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { - // If secret key not provided, use public key encryption. - if ( is_bool( $pSecret ) ) { - $pSecret = $pPublic; - } - - parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); - } - - public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { - $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); - - if ( ':' === $address[0] ) { - $address = self::$_protocol . $address; - } - - return $address . $pCanonizedPath; - } - - #---------------------------------------------------------------------------------- - #region Servers Clock Diff - #---------------------------------------------------------------------------------- - - /** - * @var int Clock diff in seconds between current server to API server. - */ - private static $_clock_diff = 0; - - /** - * Set clock diff for all API calls. - * - * @since 1.0.3 - * - * @param $pSeconds - */ - public static function SetClockDiff( $pSeconds ) { - self::$_clock_diff = $pSeconds; - } - - /** - * Find clock diff between current server to API server. - * - * @since 1.0.2 - * @return int Clock diff in seconds. - */ - public static function FindClockDiff() { - $time = time(); - $pong = self::Ping(); - - return ( $time - strtotime( $pong->timestamp ) ); - } - - #endregion - - /** - * @var string http or https - */ - private static $_protocol = FS_API__PROTOCOL; - - /** - * Set API connection protocol. - * - * @since 1.0.4 - */ - public static function SetHttp() { - self::$_protocol = 'http'; - } - - /** - * @since 1.0.4 - * - * @return bool - */ - public static function IsHttps() { - return ( 'https' === self::$_protocol ); - } - - /** - * Sign request with the following HTTP headers: - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @param string $pResourceUrl - * @param array $pWPRemoteArgs - * - * @return array - */ - function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { - $auth = $this->GenerateAuthorizationParams( - $pResourceUrl, - $pWPRemoteArgs['method'], - ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' - ); - - $pWPRemoteArgs['headers']['Date'] = $auth['date']; - $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; - - if ( ! empty( $auth['content_md5'] ) ) { - $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; - } - - return $pWPRemoteArgs; - } - - /** - * Generate Authorization request headers: - * - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @author Vova Feldman - * - * @param string $pResourceUrl - * @param string $pMethod - * @param string $pPostParams - * - * @return array - * @throws Freemius_Exception - */ - function GenerateAuthorizationParams( - $pResourceUrl, - $pMethod = 'GET', - $pPostParams = '' - ) { - $pMethod = strtoupper( $pMethod ); - - $eol = "\n"; - $content_md5 = ''; - $content_type = ''; - $now = ( time() - self::$_clock_diff ); - $date = date( 'r', $now ); - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $content_type = 'application/json'; - - if ( ! empty( $pPostParams ) ) { - $content_md5 = md5( $pPostParams ); - } - } - - $string_to_sign = implode( $eol, array( - $pMethod, - $content_md5, - $content_type, - $date, - $pResourceUrl - ) ); - - // If secret and public keys are identical, it means that - // the signature uses public key hash encoding. - $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; - - $auth = array( - 'date' => $date, - 'authorization' => $auth_type . ' ' . $this->_id . ':' . - $this->_public . ':' . - self::Base64UrlEncode( hash_hmac( - 'sha256', $string_to_sign, $this->_secret - ) ) - ); - - if ( ! empty( $content_md5 ) ) { - $auth['content_md5'] = $content_md5; - } - - return $auth; - } - - /** - * Get API request URL signed via query string. - * - * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). - * - * @param string $pPath - * - * @throws Freemius_Exception - * - * @return string - */ - function GetSignedUrl( $pPath ) { - $resource = explode( '?', $this->CanonizePath( $pPath ) ); - $pResourceUrl = $resource[0]; - - $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); - - return Freemius_Api_WordPress::GetUrl( - $pResourceUrl . '?' . - ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . - 'authorization=' . urlencode( $auth['authorization'] ) . - '&auth_date=' . urlencode( $auth['date'] ) - , $this->_isSandbox ); - } - - /** - * @author Vova Feldman - * - * @param string $pUrl - * @param array $pWPRemoteArgs - * - * @return mixed - */ - private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { - $start = microtime( true ); - - $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); - - if ( FS_API__LOGGER_ON ) { - $end = microtime( true ); - - $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); - $is_http_error = is_wp_error( $response ); - - self::$_logger[] = array( - 'id' => count( self::$_logger ), - 'start' => $start, - 'end' => $end, - 'total' => ( $end - $start ), - 'method' => $pWPRemoteArgs['method'], - 'path' => $pUrl, - 'body' => $has_body ? $pWPRemoteArgs['body'] : null, - 'result' => ! $is_http_error ? - $response['body'] : - json_encode( $response->get_error_messages() ), - 'code' => ! $is_http_error ? $response['response']['code'] : null, - 'backtrace' => debug_backtrace(), - ); - } - - return $response; - } - - /** - * @return array - */ - static function GetLogger() { - return self::$_logger; - } - - /** - * @param string $pCanonizedPath - * @param string $pMethod - * @param array $pParams - * @param null|array $pWPRemoteArgs - * @param bool $pIsSandbox - * @param null|callable $pBeforeExecutionFunction - * - * @return object[]|object|null - * - * @throws \Freemius_Exception - */ - private static function MakeStaticRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null, - $pIsSandbox = false, - $pBeforeExecutionFunction = null - ) { - // Connectivity errors simulation. - if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { - self::ThrowCloudFlareDDoSException(); - } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { - self::ThrowSquidAclException(); - } - - if ( empty( $pWPRemoteArgs ) ) { - $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . - home_url(); - - $pWPRemoteArgs = array( - 'method' => strtoupper( $pMethod ), - 'connect_timeout' => 10, - 'timeout' => 60, - 'follow_redirects' => true, - 'redirection' => 5, - 'user-agent' => $user_agent, - 'blocking' => true, - ); - } - - if ( ! isset( $pWPRemoteArgs['headers'] ) || - ! is_array( $pWPRemoteArgs['headers'] ) - ) { - $pWPRemoteArgs['headers'] = array(); - } - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; - - if ( is_array( $pParams ) && 0 < count( $pParams ) ) { - $pWPRemoteArgs['body'] = json_encode( $pParams ); - } - } - - $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); - - $resource = explode( '?', $pCanonizedPath ); - - if ( FS_SDK__HAS_CURL ) { - // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait - // for 2 seconds if the server does not support this header. - $pWPRemoteArgs['headers']['Expect'] = ''; - } - - if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { - $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; - } - - if ( false !== $pBeforeExecutionFunction && - is_callable( $pBeforeExecutionFunction ) - ) { - $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); - } - - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - - if ( is_wp_error( $result ) ) { - /** - * @var WP_Error $result - */ - if ( self::IsCurlError( $result ) ) { - /** - * With dual stacked DNS responses, it's possible for a server to - * have IPv6 enabled but not have IPv6 connectivity. If this is - * the case, cURL will try IPv4 first and if that fails, then it will - * fall back to IPv6 and the error EHOSTUNREACH is returned by the - * operating system. - */ - $matches = array(); - $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; - if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { - /** - * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. - * @author Vova Feldman (@svovaf) - */ - if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { - if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { -// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); - // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. - add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); - - // Re-run request. - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - } - } - } - } - - if ( is_wp_error( $result ) ) { - self::ThrowWPRemoteException( $result ); - } - } - - $response_body = $result['body']; - - if ( empty( $response_body ) ) { - return null; - } - - $decoded = json_decode( $response_body ); - - if ( is_null( $decoded ) ) { - if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && - preg_match( '/text\/javascript/', $response_body ) - ) { - self::ThrowCloudFlareDDoSException( $response_body ); - } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && - preg_match( '/squid/', $response_body ) - ) { - self::ThrowSquidAclException( $response_body ); - } else { - $decoded = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $response_body, - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - } - - return $decoded; - } - - - /** - * Makes an HTTP request. This method can be overridden by subclasses if - * developers want to do fancier things or use something other than wp_remote_request() - * to make the request. - * - * @param string $pCanonizedPath The URL to make the request to - * @param string $pMethod HTTP method - * @param array $pParams The parameters to use for the POST body - * @param null|array $pWPRemoteArgs wp_remote_request options. - * - * @return object[]|object|null - * - * @throws Freemius_Exception - */ - public function MakeRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null - ) { - $resource = explode( '?', $pCanonizedPath ); - - // Only sign request if not ping.json connectivity test. - $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); - - return self::MakeStaticRequest( - $pCanonizedPath, - $pMethod, - $pParams, - $pWPRemoteArgs, - $this->_isSandbox, - $sign_request ? array( &$this, 'SignRequest' ) : null - ); - } - - /** - * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter - * - * @param resource $handle A cURL handle returned by curl_init() - * - * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to - * CURL_IPRESOLVE_V4 - * - * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e - */ - static function CurlResolveToIPv4( $handle ) { - curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); - - return $handle; - } - - #---------------------------------------------------------------------------------- - #region Connectivity Test - #---------------------------------------------------------------------------------- - - /** - * If successful connectivity to the API endpoint using ping.json endpoint. - * - * - OR - - * - * Validate if ping result object is valid. - * - * @param mixed $pPong - * - * @return bool - */ - public static function Test( $pPong = null ) { - $pong = is_null( $pPong ) ? - self::Ping() : - $pPong; - - return ( - is_object( $pong ) && - isset( $pong->api ) && - 'pong' === $pong->api - ); - } - - /** - * Ping API to test connectivity. - * - * @return object - */ - public static function Ping() { - try { - $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); - } catch ( Freemius_Exception $e ) { - // Map to error object. - $result = (object) $e->getResult(); - } catch ( Exception $e ) { - // Map to error object. - $result = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - - return $result; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Connectivity Exceptions - #---------------------------------------------------------------------------------- - - /** - * @param \WP_Error $pError - * - * @return bool - */ - private static function IsCurlError( WP_Error $pError ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - return ( 0 === strpos( $message, 'cURL' ) ); - } - - /** - * @param WP_Error $pError - * - * @throws Freemius_Exception - */ - private static function ThrowWPRemoteException( WP_Error $pError ) { - if ( self::IsCurlError( $pError ) ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - #region Check if there are any missing cURL methods. - - $curl_required_methods = array( - 'curl_version', - 'curl_exec', - 'curl_init', - 'curl_close', - 'curl_setopt', - 'curl_setopt_array', - 'curl_error', - ); - - // Find all missing methods. - $missing_methods = array(); - foreach ( $curl_required_methods as $m ) { - if ( ! function_exists( $m ) ) { - $missing_methods[] = $m; - } - } - - if ( ! empty( $missing_methods ) ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'cUrlMissing', - 'message' => $message, - 'code' => 'curl_missing', - 'http' => 402 - ), - 'missing_methods' => $missing_methods, - ) ); - } - - #endregion - - // cURL error - "cURL error {{errno}}: {{error}}". - $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); - - $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; - $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; - - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $code, - 'message' => $message, - 'type' => 'CurlException', - ), - ) ); - } else { - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $pError->get_error_code(), - 'message' => $pError->get_error_message(), - 'type' => 'WPRemoteException', - ), - ) ); - } - - throw $e; - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowCloudFlareDDoSException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'CloudFlareDDoSProtection', - 'message' => $pResult, - 'code' => 'cloudflare_ddos_protection', - 'http' => 402 - ) - ) ); - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowSquidAclException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'SquidCacheBlock', - 'message' => $pResult, - 'code' => 'squid_cache_block', - 'http' => 402 - ) - ) ); - } - - #endregion - } + '7.37' ); + + if ( ! defined( 'FS_API__PROTOCOL' ) ) { + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); + } + + if ( ! defined( 'FS_API__LOGGER_ON' ) ) { + define( 'FS_API__LOGGER_ON', false ); + } + + if ( ! defined( 'FS_API__ADDRESS' ) ) { + define( 'FS_API__ADDRESS', '://api.freemius.com' ); + } + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); + } + + if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { + class Freemius_Api_WordPress extends Freemius_Api_Base { + private static $_logger = array(); + + /** + * @param string $pScope 'app', 'developer', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string|bool $pSecret Element's secret key. + * @param bool $pSandbox Whether or not to run API in sandbox mode. + */ + public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { + // If secret key not provided, use public key encryption. + if ( is_bool( $pSecret ) ) { + $pSecret = $pPublic; + } + + parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); + } + + public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { + $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); + + if ( ':' === $address[0] ) { + $address = self::$_protocol . $address; + } + + return $address . $pCanonizedPath; + } + + #---------------------------------------------------------------------------------- + #region Servers Clock Diff + #---------------------------------------------------------------------------------- + + /** + * @var int Clock diff in seconds between current server to API server. + */ + private static $_clock_diff = 0; + + /** + * Set clock diff for all API calls. + * + * @since 1.0.3 + * + * @param $pSeconds + */ + public static function SetClockDiff( $pSeconds ) { + self::$_clock_diff = $pSeconds; + } + + /** + * Find clock diff between current server to API server. + * + * @since 1.0.2 + * @return int Clock diff in seconds. + */ + public static function FindClockDiff() { + $time = time(); + $pong = self::Ping(); + + return ( $time - strtotime( $pong->timestamp ) ); + } + + #endregion + + /** + * @var string http or https + */ + private static $_protocol = FS_API__PROTOCOL; + + /** + * Set API connection protocol. + * + * @since 1.0.4 + */ + public static function SetHttp() { + self::$_protocol = 'http'; + } + + /** + * @since 1.0.4 + * + * @return bool + */ + public static function IsHttps() { + return ( 'https' === self::$_protocol ); + } + + /** + * Sign request with the following HTTP headers: + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @param string $pResourceUrl + * @param array $pWPRemoteArgs + * + * @return array + */ + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { + $auth = $this->GenerateAuthorizationParams( + $pResourceUrl, + $pWPRemoteArgs['method'], + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' + ); + + $pWPRemoteArgs['headers']['Date'] = $auth['date']; + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; + + if ( ! empty( $auth['content_md5'] ) ) { + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; + } + + return $pWPRemoteArgs; + } + + /** + * Generate Authorization request headers: + * + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @author Vova Feldman + * + * @param string $pResourceUrl + * @param string $pMethod + * @param string $pPostParams + * + * @return array + * @throws Freemius_Exception + */ + function GenerateAuthorizationParams( + $pResourceUrl, + $pMethod = 'GET', + $pPostParams = '' + ) { + $pMethod = strtoupper( $pMethod ); + + $eol = "\n"; + $content_md5 = ''; + $content_type = ''; + $now = ( time() - self::$_clock_diff ); + $date = date( 'r', $now ); + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $content_type = 'application/json'; + + if ( ! empty( $pPostParams ) ) { + $content_md5 = md5( $pPostParams ); + } + } + + $string_to_sign = implode( $eol, array( + $pMethod, + $content_md5, + $content_type, + $date, + $pResourceUrl + ) ); + + // If secret and public keys are identical, it means that + // the signature uses public key hash encoding. + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; + + $auth = array( + 'date' => $date, + 'authorization' => $auth_type . ' ' . $this->_id . ':' . + $this->_public . ':' . + self::Base64UrlEncode( hash_hmac( + 'sha256', $string_to_sign, $this->_secret + ) ) + ); + + if ( ! empty( $content_md5 ) ) { + $auth['content_md5'] = $content_md5; + } + + return $auth; + } + + /** + * Get API request URL signed via query string. + * + * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). + * + * @param string $pPath + * + * @throws Freemius_Exception + * + * @return string + */ + function GetSignedUrl( $pPath ) { + $resource = explode( '?', $this->CanonizePath( $pPath ) ); + $pResourceUrl = $resource[0]; + + $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); + + return Freemius_Api_WordPress::GetUrl( + $pResourceUrl . '?' . + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . + 'authorization=' . urlencode( $auth['authorization'] ) . + '&auth_date=' . urlencode( $auth['date'] ) + , $this->_isSandbox ); + } + + /** + * @author Vova Feldman + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { + $bt = debug_backtrace(); + + $start = microtime( true ); + + $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); + + if ( FS_API__LOGGER_ON ) { + $end = microtime( true ); + + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); + $is_http_error = is_wp_error( $response ); + + self::$_logger[] = array( + 'id' => count( self::$_logger ), + 'start' => $start, + 'end' => $end, + 'total' => ( $end - $start ), + 'method' => $pWPRemoteArgs['method'], + 'path' => $pUrl, + 'body' => $has_body ? $pWPRemoteArgs['body'] : null, + 'result' => ! $is_http_error ? + $response['body'] : + json_encode( $response->get_error_messages() ), + 'code' => ! $is_http_error ? $response['response']['code'] : null, + 'backtrace' => $bt, + ); + } + + return $response; + } + + /** + * @return array + */ + static function GetLogger() { + return self::$_logger; + } + + /** + * @param string $pCanonizedPath + * @param string $pMethod + * @param array $pParams + * @param null|array $pWPRemoteArgs + * @param bool $pIsSandbox + * @param null|callable $pBeforeExecutionFunction + * + * @return object[]|object|null + * + * @throws \Freemius_Exception + */ + private static function MakeStaticRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null, + $pIsSandbox = false, + $pBeforeExecutionFunction = null + ) { + // Connectivity errors simulation. + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { + self::ThrowCloudFlareDDoSException(); + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { + self::ThrowSquidAclException(); + } + + if ( empty( $pWPRemoteArgs ) ) { + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . + home_url(); + + $pWPRemoteArgs = array( + 'method' => strtoupper( $pMethod ), + 'connect_timeout' => 10, + 'timeout' => 60, + 'follow_redirects' => true, + 'redirection' => 5, + 'user-agent' => $user_agent, + 'blocking' => true, + ); + } + + if ( ! isset( $pWPRemoteArgs['headers'] ) || + ! is_array( $pWPRemoteArgs['headers'] ) + ) { + $pWPRemoteArgs['headers'] = array(); + } + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; + + if ( is_array( $pParams ) && 0 < count( $pParams ) ) { + $pWPRemoteArgs['body'] = json_encode( $pParams ); + } + } + + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); + + $resource = explode( '?', $pCanonizedPath ); + + if ( FS_SDK__HAS_CURL ) { + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait + // for 2 seconds if the server does not support this header. + $pWPRemoteArgs['headers']['Expect'] = ''; + } + + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { + $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; + } + + if ( false !== $pBeforeExecutionFunction && + is_callable( $pBeforeExecutionFunction ) + ) { + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); + } + + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + + if ( is_wp_error( $result ) ) { + /** + * @var WP_Error $result + */ + if ( self::IsCurlError( $result ) ) { + /** + * With dual stacked DNS responses, it's possible for a server to + * have IPv6 enabled but not have IPv6 connectivity. If this is + * the case, cURL will try IPv4 first and if that fails, then it will + * fall back to IPv6 and the error EHOSTUNREACH is returned by the + * operating system. + */ + $matches = array(); + $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { + /** + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. + * @author Vova Feldman (@svovaf) + */ + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { +// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); + // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); + + // Re-run request. + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + } + } + } + } + + if ( is_wp_error( $result ) ) { + self::ThrowWPRemoteException( $result ); + } + } + + $response_body = $result['body']; + + if ( empty( $response_body ) ) { + return null; + } + + $decoded = json_decode( $response_body ); + + if ( is_null( $decoded ) ) { + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && + preg_match( '/text\/javascript/', $response_body ) + ) { + self::ThrowCloudFlareDDoSException( $response_body ); + } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && + preg_match( '/squid/', $response_body ) + ) { + self::ThrowSquidAclException( $response_body ); + } else { + $decoded = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $response_body, + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + } + + return $decoded; + } + + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than wp_remote_request() + * to make the request. + * + * @param string $pCanonizedPath The URL to make the request to + * @param string $pMethod HTTP method + * @param array $pParams The parameters to use for the POST body + * @param null|array $pWPRemoteArgs wp_remote_request options. + * + * @return object[]|object|null + * + * @throws Freemius_Exception + */ + public function MakeRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null + ) { + $resource = explode( '?', $pCanonizedPath ); + + // Only sign request if not ping.json connectivity test. + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); + + return self::MakeStaticRequest( + $pCanonizedPath, + $pMethod, + $pParams, + $pWPRemoteArgs, + $this->_isSandbox, + $sign_request ? array( &$this, 'SignRequest' ) : null + ); + } + + /** + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter + * + * @param resource $handle A cURL handle returned by curl_init() + * + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to + * CURL_IPRESOLVE_V4 + * + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e + */ + static function CurlResolveToIPv4( $handle ) { + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + + return $handle; + } + + #---------------------------------------------------------------------------------- + #region Connectivity Test + #---------------------------------------------------------------------------------- + + /** + * If successful connectivity to the API endpoint using ping.json endpoint. + * + * - OR - + * + * Validate if ping result object is valid. + * + * @param mixed $pPong + * + * @return bool + */ + public static function Test( $pPong = null ) { + $pong = is_null( $pPong ) ? + self::Ping() : + $pPong; + + return ( + is_object( $pong ) && + isset( $pong->api ) && + 'pong' === $pong->api + ); + } + + /** + * Ping API to test connectivity. + * + * @return object + */ + public static function Ping() { + try { + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Exceptions + #---------------------------------------------------------------------------------- + + /** + * @param \WP_Error $pError + * + * @return bool + */ + private static function IsCurlError( WP_Error $pError ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + return ( 0 === strpos( $message, 'cURL' ) ); + } + + /** + * @param WP_Error $pError + * + * @throws Freemius_Exception + */ + private static function ThrowWPRemoteException( WP_Error $pError ) { + if ( self::IsCurlError( $pError ) ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + #region Check if there are any missing cURL methods. + + $curl_required_methods = array( + 'curl_version', + 'curl_exec', + 'curl_init', + 'curl_close', + 'curl_setopt', + 'curl_setopt_array', + 'curl_error', + ); + + // Find all missing methods. + $missing_methods = array(); + foreach ( $curl_required_methods as $m ) { + if ( ! function_exists( $m ) ) { + $missing_methods[] = $m; + } + } + + if ( ! empty( $missing_methods ) ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'cUrlMissing', + 'message' => $message, + 'code' => 'curl_missing', + 'http' => 402 + ), + 'missing_methods' => $missing_methods, + ) ); + } + + #endregion + + // cURL error - "cURL error {{errno}}: {{error}}". + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); + + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; + + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $code, + 'message' => $message, + 'type' => 'CurlException', + ), + ) ); + } else { + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $pError->get_error_code(), + 'message' => $pError->get_error_message(), + 'type' => 'WPRemoteException', + ), + ) ); + } + + throw $e; + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowCloudFlareDDoSException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'CloudFlareDDoSProtection', + 'message' => $pResult, + 'code' => 'cloudflare_ddos_protection', + 'http' => 402 + ) + ) ); + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowSquidAclException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'SquidCacheBlock', + 'message' => $pResult, + 'code' => 'squid_cache_block', + 'http' => 402 + ) + ) ); + } + + #endregion + } + } \ No newline at end of file diff --git a/freemius/includes/supplements/fs-migration-2.5.1.php b/freemius/includes/supplements/fs-migration-2.5.1.php new file mode 100644 index 0000000..6252649 --- /dev/null +++ b/freemius/includes/supplements/fs-migration-2.5.1.php @@ -0,0 +1,31 @@ + $install ) { + if ( true === $install->is_disconnected ) { + $permission_manager->update_site_tracking( + false, + ( 0 == $blog_id ) ? null : $blog_id, + // Update only if permissions are not yet set. + true + ); + } + } + } + } \ No newline at end of file diff --git a/freemius/languages/freemius-cs_CZ.mo b/freemius/languages/freemius-cs_CZ.mo index 278bd9f36432117ac36ff3e440138aef52ef07da..7e86c17e7ed46f693ceb2cae1dc9cb7a33a2c3bc 100644 GIT binary patch delta 16073 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 F{{waq&|m-n delta 18724 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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index f674098e690154f50c836f1e78bcf884664ac38f..9e2c18d7cd886eca9cb24a766e0f19983bab2aa8 100644 GIT binary patch delta 17922 zcmeI&33wD$y8rP?*h$zo*^4D?h5#bFAgk;UWC?;8JLyW&(CLodolS5W+y)gHuuwq} zSwuk@QCn1W+($(n#c{z6Tu@w4aRX&W#QXjADTHyZ^XS}rpPB#jAD?IZ)Hz*s>YVqy z=Tudkhrg+^<%cTqw;EQx+Tx#esg~6WJ2p|tvNF3JcNmA=DX+&chOptumURj)!dZAX z7Gb?i%kpCdPQ^W#fxWt0RvPAb=3{lsid$h08uCLKZp6#5FZRfyQ7ph*Y}v!I+Twg{ ziq~TYybGJ-F4T>_z;tZd)3Rz|2ds_VksDh5u?C)wHMzev+N;P;R#?_d9Lfnk9E0mo z9sC3*<58T5lX_X!TwIOUV-3Pl1vg+DyameAcv+}%G|2}r2d@ z*oW%jXQ+n0M|G$MCw0a~coGgmg?Jk3JRhpV^DvAHP&fJ%*WlMU8n0qrr{No@2=(Yk z{5Nwjsh{cLVQfh`t-ocp$Ihsv9EV-75Q&<#4BO%3I0W~h4?7O9Eb`F06qzOKE^L7B zp>pOBDl+Ks)4=s-nP?C1S(8Y*NXQB#xS)t`$xuLw25VpN1;SO*uPZn)H| zUyGwCZ^26Z3N@hMAang?aSqghWq1|dgv!=_r(x!vW?-{6$_VpZ&8d!W12O3chYVOa*lQ4+t$R()IFGCXD+KyUgpCE&=8W4Y7 zKLORDDX0$RqLQox)oulw2b z3S5n9u-8y?{SZ|B7;J`nP!T?ax=$^3Gj*r|_R;!p$3Y7!a&RU_Fow@!a~wL{EWcTv zC8*F{ifZ_GUi}7aLwP%DDqcg4_#ke_AMjfJKEmY4l#!M-oBLb&92DTgSPeUoF$!fS zHpYRdj!Z^vFagw!i&1mE2y^jG^x>d0Ob2ejYLvHQTYMN5fwxiZe~5AQ@Q_!bMN0lz zby1-oht+X19>p0r8K;jizi&ice=BMVws}5=O1_;q7vJ*AW5+7wL>V>JOUDxbnjEa8 zqBpKXt?yTGSPEetXGS`1yk-4{`g2eb*n>*GcX2wlnqWp8K_%}}B)P0xP*eLLcE#7Q zKc-GJJLjN@#9yHdP%#xRKqbweu`_;*sn}o=n-4Zds;mXr51&Eu)Ji2R#W)Og-c~HZ zX4z)y7Ne$OJyzl-R7cy#rR#4wU$aceaPx?r8>?WmqVfC|y;s3iLem5kq` zMqD+=ETeSP?`=>K?27fUk5?Xr>fjVqt`*{`Sb}vm)Qub{M0cZl`Vi_yk6|Nx8g;`x zUj06={3+^22T@5@b*AY+Th#R@d*xF+hoF}6IMnL$W2QnK;6M?$#&ZK|Be?_D;Z9V? z&SSA_B&Dd4UV-iKT5N^eQIXh%y1~1swYSw!ep3mQ!Y%5x;yk^YBfaezqA%uQ~jlVp%U?OX}Y|hxn@} z-=1Se^gU{hYUP@dG(>fzHER8yj2h{5)OiuqT+T<0_zvuY+pr6Mg6dG?b4@a~!tRuN zqo&q(Zrp4jrBw8#;#u5*sid-&$Fryrevhr_`GP!_DCJf8W{M7>=DM-pHG=z5bN`rEehsy3-$6yDUV-Vr z0BlQnEb6)dsw3xl<%OsmS?n2iIjBj+8dNslf(`Iqul{M&1utTK+=H5u1E_|NU^PrD zG|ROCvLLK(sPitwrnn4spIcDLcn7x7`hS80CBr*d9gm=rtV)r&pa#~UTpx9#lTaOP zgIWbys16TAb#wwM2XZiqS7H!T0w%H{RL8Hz`da@taiE@V!!+jjaXg9g&bh{qP|1=q z&vc{-s>7{O9U6eO@N}$-V=)aU;b5GFop2>iz=x5bS?Tnu8uz!N9He2T=Ow66FV_jU z9@XPbs1e?aiqvDM>z>ARd<7M$y{G~0N9D|6Ovk##rrnmfm2xJ=ujXJk2iIUEWHyLe zVYA+Qppq#UyJ8&Gfz6(GBTL(Q7*}Ge5;M2kup#AVunq3P5qJdEp`quQc4wSN{I%hf zQE>?_Mfzejj+l463z3CkeTUugTqaZzS&ACb3REQ4p>DJZJL6Nh1wY4$xH@J&K=xxM zh8r6Z{f8p+2R1U=}vVbFc$epdz{&wXZydirllI1{?~|#uTTwCU&s>;`{Hz5 zj3e=XxYWG9---h$e}HPw{;heto{y(d-imqn zDaNxon7r7GbS>sm{synZ8JAhsc>EH(;NZ*6GAh6hlyAojd=b0iVeE+Qudpl*t_TgH@+7& z_q(wre&gA2smYlvROlyQH;gVN{{1*uLq${E<(>E$YPqES&V;Tbs)I98H!Mc2nu}1m zb3JNATTu=F347tUsE%}9X3iUhiriVK`U~P5D2Y~KJ-iLm@B!2apF(x)LsSQjqHfq| zxmkXlJu@+#`d(NQhu|a}jcxE6)T-Hrir`DA`^67%P@jXNp7pLW4cl0a`V7 z18ZOfsv`?g4Xr@6a}#O++fW^P6l>%2Uio!Y2R}d}7`Hz6Dr&f9z1K%I&<2&o-Eai@ zu_|st-SGFQ8$5`$@EO$Hzl`ee0n`9$tuPx_Jyg5xu`Xs|Ev^5-$pb>=oiGK}@Eojz zbG_dyJTF8wv;=kDO3#gWcNPBVgu|#WT*;#wH)9AVtTH2i0B2GDBNlOgtL=4W&t8n} zD1U}MvEKD2H-@5abS5fvx!4eka3jt~t(JDH&0kDsVlL%6H<%nMM&;0zs9d-S6{*KD z`ToC$1I=BPHReW*Q6XxFn(LmZIqZ)b`3SFmGO9y4I26ytF}M!3O7`Pqv~DyzW;V{H zxCZI7RckHrCt}vdwWguXo_C?(jC#oZ7uQMGhM3uv+oH!q~EH6imcs*($ z+c6V&U=KWmia^HtxJjPVnW#ooj79x02Q`9n)QGRdbX~vXFRJH* zyz)e>N%>6F$Y*=yLaaf#1a(~nR>uoa=Ut3MEN(61Kn-5!{jdc`QN9l=@hEn{3Wi-1 zSED+%5!pJd2T@a#eXE(WIjB%aQ91Bitc`0>xwILT?Dt|bt^XYyoJGYusD=jKW|C$! z)}}lIRqyl4Axx!QiE3~G>RE9)s)HM_D{euBeitg~YHl$((jOI(;aG?JTcbHp1g4`x zR*0J0FscJ_OvlSnBfl2az*ekZi32@3~Yh3aVC~w40ocQf+O!R9ra;j%9W^gmU`uDQB$<# z4&vXQgGZ>)h3}z8Y~9J4g*3btpTN2}=PvX49mE34J5Wj0BVneZFY1OPQLE)FRHUNV z2rE!iy##adg9Pypad5`n=6Spgb-|y#@-9@*ze083J5&d2Z}TF8y(nj370f|(Xf__j zJe-WadrbYE*qHKtsP>+VbD+7{?Nxk?b15Ij2AF@Z33(~%#u^(_CR=MBoH$$)u<@oI!DD=-^6Wos-(R#qVEN0+Xih0-{x1i43 zhxzy&w!_>%m{+SssMWCv3-LCbh>adJN$NvwK&!A=>;E+lbmAEgnLnA{j>?I)51VJe zaIB;}23z7|*bU!8MXuH(X2eagDdjA*aTGSh0#s60qK!AAR@o!iD!KkSXhy|8Y>nTe zLfrCEGop@oHRbWBxqcV>V57&(*Y8-2Q@$BBvQCeiE!aVI{1Vg@{eZ3Tls}sNVlw)4 zVG#!!$up=CyoySq1DJz{urE%0!qi`k^(kM0^>8&RVp~uV+KJj1K0%H25URcJQTM6( zr1`x8##Pah12xnIRql_P<58#vX5*=tk6K16Q8(CvRq!6Hj`yQ}f7C0#;<+2EQvV*N z;YZj44?aoYl-2c~GIQMm)v)c^4Hc17Q4Ng8Ow7hQcmb;8S6~~w9@pXhn2KlbFjJe0 zx^X#X;6c9>vs^d$j5xs$0r*EMy_!?_s^QXQdEa-^1KU` zE03Vs`wGc+>l^$H`kyh8eJ}1Ee1fI?@HM`PfoIJ~M*WEsQ+WT!cGQ3VoQYI}=S^gq zVGZiDuom{kx;PZO;$+lFE3r1N#DTa0718)x91P{)Jyd8r?lcWf!Mc>sLN!!`%89FR z2yQ|p<%dXctTr#0Pqz>863QL^Ot#bU%{ZI#lP{X(*Zw6F=@Cf#acdk0>iJpN2=g%= z%TaT7F)D=1P*bwP`+c+b`#qkI;Us>49u<++FPry<3{-h6cE%~FfmUKYE!>+qXiLRC zs2tdZ4R8-?&Ob+W@UT}-eZ`d1upaeIun}gUZZrV3JcpqMa3(7B=b+9HU^T47R$BjY z4z$i!qegxZ)nLjlGX+hsG3Acf90#D*`BYQ~!>9;VpgOR?^J3H-FG01p95tmIQ9I+^ z7+2Ol#X%o@1*4eus`-;?9D7p!6g7g@ubIenK{YrK^%v7gsGV)r>t<&wN8Ml`mgYkgcFXp}``A>#;TEyKn-&fa*Z&cT6^C zVl&FwsAX1!+E@-?1)lw`xxp)_XTrd}CL(iCBhN!6c@W!RJjy{w4z9v2crQ*z|9j?d zKlfuMnTl^3&`@o!k9)(oO zvAWj(E)M)u>_J^H{9}_elTcHUi^_!yP+5HiY6{kP-h)cM=e_cK*n;wRs2eujZ+`E9 z8fbs4iGwl5{jD=N$i)XRge^ZYe=@xsPo~`CQ*Ms4aX7w?L$K9nX0^;lt%6mkZ2vu~ zqYq&w{u$N5qo_zWI$$E(4dXqi80l5Sumk0#o_C?&BgyI_wm%vh~Sk9zVp>u=ZEx z2BYyT%1dw|et=5C!mrI&^8=Vgx&A@3!JUr!i)kr#r~bExi2ooCwjDAX&X+iga`SJ@ z>vasZt~X&GzKhdvm!Cse~dunzV{<<2lv?u_)D;Qc-g zSq*V3j{{|Iq4&dl)Cev^b!Ztj#MP*dY(?E@yXS*gm-3_70$)H4=o8N`QSE<=T7GHY z8|!15)_)TYG~#wR1-qeoyZ|+li%}iA3U%XEo;RS*TZd|RGwQ~-qdI=K_xmHL>vy0A zuoD%T*RdY=w{~-&P=1OE@wcc3YaTTh)Y3X?(;FKor4%}&cP85T4U26OoOL-4o7t)8+F6!s0(vZBQ5mG#h#_8^DjVk=t5Kn zF7-kb7RFbz)$T{wWhq^Vvlar(kgVjA(m1 z=8F_MvBB+g^Mbz6y!Jyz1Oxf=>>?-P*s-u3bbOJJT^y$5%L|vr?9vjyFBS+D+Sy~W zPwU|uk|^nUVM@@>=ESID=NCC#Tp2En*iNx85VUZD{HY4_V!_Vl|G0J^{K|9Z}{Z7z{ajFIxB39v0 zp%byo0?|NTFd4m!Q#wYotP#FY=a^j(NL)AIoz#}arJ+DRAqf#QJ0+Z9Pd{^l-N}vxVnL=iC!1q3_9t=G+}osmVJTN6o*VaViu=s=``jBR zp6=Gn=^r0kpaf;)QPL`G=leqWPSE6RG7n0)lrfU7(fmlDM009uZXBPVj4~@C<8?N3SZxm9lL;I4e|nnd?0 zZ=|L>CH7R3lu%mZ-4`Y_O;npUDzyu_5)GCX!aqo!P&nqEJ!3%Jn&fbnT^xu;?Lcu! zI1=-*0+Wl|p#^pXJL=>!U9n2LoGpNRMM-%UAtNbrDml(4(oAC>Gh@#Sh0DpuLPD*@ z5OMMyW{*V?3lw+iAMg6W`>@wUaI7`J!|yCmiu-M;ylJgvtVu za7cN>6cvQCtjQ%h(zN{JhR^(!4cpU-0^Sx;KYnR%?^xEvebU!#hM9z4+3#N#5bqW<|EXL2!q9a8 zRiQ}?KId@tYWCF1P=3O3_NJt1na?oW=Z3+Bsd2MX{%$`{J}j6JO;U0PmtTF%PD6JrGDbzw*c(6qx4T9vI=Se|12jd~SM5y06k6VIGw{P8QCaliHj$ ze*7uVNRwM#8T-~%p?2<<#fKWOo>)c!maw-eyZb_giTlI-s>JoyQ0~*B<}u||u+-I! zVcBDCClaAuhQh-x`Mi!smF&qdMx66XooFoU*jq%5ZC_7%?WVSm9ig0!DpVRHY+5ip z4OtaDnR)Q}!%kFB*Ya>=UZ(B#Deaar&^-`Y=?;me#noD13o?um1&e)5Q9@3hVRMg;PdZN!qGnGx_-RKTRgv_bK%l#Kq5ZxcMS zlu9PZZ1O}Q%e^nUaX4=|$qmUod&xhViABOerZf4P$Ybr;D%$a*FxtVBsJNuS@(stJckx&L-`{-T9RI`AMk zHaR~xH~)JJ^QZIH?8H@h!o<1v$If*Fr4PIQ^2g&`G&RQBwPpm!`r*l^7fxk3gGpWa3IQgZ)BRDykA$M3#n^wxcfAl2G{59LX`$J{t zgmZo@#r=N%l1_j3QujA6bo`op{Y%6bTvxTGlC~faDR%8eW8D`Hv`Q>nG(NTO@l{*O zt83@zK;F|M(HNHoeGu{$d#^O!=ZxR4EJ_@`Xi)9yX0YzirR@`ur3I;pZp+_IsWFhR zh1`+j-BdT3i(Upr*k2Ruivjx{r{9pez ztCQlsd_$+W_kCb9MI}ziuRY9LrmU(T_uc=quebCQKHoxaqcxwqoo?Kcc>l&Dzv%mI z&xRQ(Zsx}M@!{+oKlgp|fBOkHo(Gyg@%6@MQrnIQ7oYG6H{N@!^IZ_;6D|;R=WWja zb>DC=Z%$9`tqJicX8ZT}hI{h&CslQSJF4T)e8+Wl-@5aOniVnc zOK$C5lTzxP!G8N|Kjl74WdBb;{?bplrH{9Dhvu~Tf8aCjuTS2P;tqSd4WDs4drk`ki=Cj~IwQiqiof(5 z>^~oOw)_9*$6d2O^{ejYzL=3%^WqyRjn6#xLVv@I!phB>Yx1fEBdlV hjfVOQi~i$J@=tKd4?%bF;dY7bhi^@B3;+7se*uyl(_jDq delta 17095 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 diff --git a/freemius/languages/freemius-de_DE.mo b/freemius/languages/freemius-de_DE.mo index 879edb7d910c7c2574d05b81c35c99f1e9a40477..96666e9a7a2f7a343d3bb4f7f976c63db38ecd2e 100644 GIT binary patch delta 20438 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 delta 18967 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

    yV&3 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 diff --git a/freemius/languages/freemius-en.mo b/freemius/languages/freemius-en.mo index c2d4990171bf00422904c7fd88682cd09463227c..39fb1f5dbb089b90f53ff1b7a9f6aa6d6f9df51b 100644 GIT binary patch delta 17750 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

    Fy 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 delta 17791 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 diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo index 662725dfc38d11b3776143c5fec541dd0be56fae..0e57ec17ce90651df4e59e6a01c6e40d033f31de 100644 GIT binary patch delta 17956 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%0D(csh>7ez+QE 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 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 8bbafc31df2b84ea50a064cdaa578db25eb76fef..9e55775cc13d7ba6d407b5d1748bb3333f523036 100644 GIT binary patch delta 17919 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 Pn#a>Q;o7SbJ)iv#Swg1| delta 16614 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 diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index d9b9ff48e9fe99aafe07388e315527e3906eabb7..ae6993886a4cd0db0c7436411824a291d1a81325 100644 GIT binary patch delta 18031 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 SaI8fl=h!p8MCK1QeE$KY(%Crx delta 17515 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

    0oh<}fI;jO4p-;ava(>NF3 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 EKblo7umAu6 diff --git a/freemius/languages/freemius-ja.mo b/freemius/languages/freemius-ja.mo index 27b73f3b30ee11a2edeca2a2e231da30b623094d..613a59212620e6ba60da988fb6ee3b12b6c6e204 100644 GIT binary patch delta 17988 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

    b_@m4Sdpq#}40GJi&^{r&iPP@K zr-ye&DBwF~$#Hg_IAvnyG+?(Rj(Q$u(SV-@HPf6Vc5c}3b)T3gt228jR1geY#51Ze zkXQKQ@f6_%3;PT7Y)Bj=CG2_Da-=YEUhq(ghP}Z^5l4-AE~V;uz{2xsEjc{{#1S`x^XmFwQ|CzW(KPlHTPeU6 zb`N9c{a`_Sd#GD-OfL;(UTtcgQvR82b#%xQ;}$;1gjC`=^)s zBhl;=FA-5rd_CxOirQXIgfb4QU}==FY18m9WM}YT=DFt!`6GI`mW9I8vTUbIX&cWO z&f)NKr%$9xO!Wph0;q$NMNek4EI4{JJ=xaD?aI?MRHi)|4O!y?#q`EoTtbSchBc1& zmBjoers~IDyR&8cN)w0r$@AvKfkTo%+SeJ#}u?Pach&owjLj zU?5kkjaU*iGXh?U@|m=#4k=!h64C$hWr9bQmXZlFhdfcpcHTU?ZZI!7i9^Xeb6I~> z6Ag!on9jsoB9F5Zk7bU(6Yq9r`MEvHj5r-vG-5{VP>@##_rac6lqcT7B8fJn4Mpmod4X;{Q1PyKDjMVnK@^B^g<_4 zy3_HMJr?7pu~9a!H6g&VACP#IQI>dzJEdq;IK+Fg|7Ybn=6&{*@}Is4CMp^3zC{f_ zv6UmHy;14z5@^ABtp#8Y4@9C))0r7nI&_FyIp$3u{_M;yp5}#Kp6~%J1Yc>MW>U|- zkiIrJjS>f>9d!EVG;XNn_pcs@S-<7XcYc`FGVZ@9>T!-;yr9L;-sb+>n;gF;-uvRQ z**B&*_RZ-|*4*Cl=-h&&t|xbEDQ~PTBWLh>9*#u0qsR+EZ?XF-<9@~Xe0r6LAD`c| za(OdGr{BVicz9ucQoPOL*F7m`*i*gEx7SZ}e9jPOo73DGl) z7v51NrRu;?@yX9NCwzBnr~8IEzwJxyp$!)#Ij7%~(c<78@eej{I`xC>|A`N@_&M?R z<(w-AH~)nXwARkzuWQGh`;$DC2hWU}54A{q$+lNK)z9Qi{T-ibfd?K;>A!#Omi_bQ z{GWWTWj?$;DW3EgtKXB;_y6*@T4rSb+zkh=o^xR3)d$wIVy=(dkLM?wZ#8Gxs2k$- zpNJ(_8|MGn7u)iu)+c9;`IGG_pLDSgu5;QQEQ=pG z_{>i}=XQKJC^>UnA%E3PyjuU_hh3y;{D+UHdYp%YE&l1pU2eSO)4nM&-U$E5&pS_+ zb9!l8KJV06l^k`=6cYfXd()Yt}{kV($&ab=wz{g$Ui_v-K z=&Jwy!aMO1`der3r<3;!UwChQc}@JOuUdKHwZ64IvBW3XANQeGUtgA^{^^Har|&wX z@1J+s{<&-R&%I^;{2kW8yKj4c@t-WG$+5ceUf*BuX_@1<|DX@Qr;l}t_dlMNwDelf z6TkAqZ|g9RC#PKcx%@%Z{S(TGzf5~id=56wEsaJKf21+zxB1@V>CC@}SecP!&sFgF zwmwq9GdQUy)r3#^8{cpFaNL?*+0&$QE&W|a|6)W_{2?Uqsl2Q(-P6Q*n~?@sqL NO4|B-9Zyrwe*rz(mel|N delta 17616 zcmeI%33wD$zW4F!B!qq67oiCRlqIsb0SZKRMHWQ?_a^C(hNL^BJ784oq$4PsCgDkVbYCH$YU}mvJ1oCILphj^n5^f_lHdI>n-Q=6&9odGGVS&->i_-0@TARCS$m z{^x(HPK8gmru=zRO7!CfDT^%r&rY(eR=Bm1N|rVJyfbEUcopSGaXtpIM<@P=EAe`K z2XpcK&X$#pOYj=}3Da?O7t5-N(_G7tTScw;9Heq$CpN%+_&C0e7h!Q%I)$6@7W8LW zRvUZ?n_vaD!;{z?z2{q2E$ofeaR}DH(WrKA#HyHu)o9{dja3d_0^`*Xnz9E$r< z9jn{TvPNPWPQpT5hJV0hEbeYut#LZ4gAbt^+JklRMfdzWZvD4djrOe*94Is?#IqjO zL(M{4Y>XGV^~13P<(rUTSa+kY+kzVD0aPU3#5(w$dp@P789;MX1iGL)))S)_a?r=E zn1bqQG1kUes2i7~I=B)$;*;16-$jM`KTy}5Kn7sSb&PiCRDrIq8u#X;1DWHZ|ZGMA+a1AQdPopBWAIIb8 zs1aV;$DF?s)zKSWb5R3|pav41&VlAUhRtvRsv{dvq27k%r}YVH9oN5d|;*F~+#9B5==R0yY|Zag3L!-eQf1>Qz^3ogXY zea#K`p??1ws{Va!ih2D^B<@Cq+QwEMB8T%Szt~?Lh?0ps-da#`BSvrG$y0eaC6jLcSRrGgKGFVR>3NR%>$?=YAQOT?%UHf6PZnGBt}y>n8QIT z-j6?HIjVl~5HpfxsJUD18b>ABKAeQFyXD?PEsGUw4MR;y6cw>q*b^6E58RKL9?SaM zP~z`|@(Pw9Cr0A6co;RJ_N*NxWq;IEj7LpX2+zaW$V#)eVpseMb-$Lw&CBO}R4$ZZ zM_h(!_yQ(j<#6IZk%Qw@T!o|g56=#3Ija7BEWmyv%v5Yfh4>H_;~S_BUNO>yJPT`5 zE<+{ld{oCDMKA7f>wkyol)sF+CmM`0bJY`DaiS0Ei8T(-!4N8>^KcQCqn2gY(Uz5g zw;;E&?nC9&$4LHKH(zZ!wgT1Reb@tgUt{W{`5dT+^H9mO3YBbC##k1KYgNZCI2@G| z#aIjPa-D*I0v{0SsdQ7iRY^Pp&e3SAniLm8;#8Hg9) zRaggOs7O43>gY05L(8!tu0}Pu%dLOjEx(L)secodL!V(y#p)ynxD4DL25*lrKSLdp6d_J5e2-jpt)IDq;svIr9oe^EvpD z11-N?9!o=L@hB=H=ih8Xc@b(k_C+;(J!%BEqNXSh)$t-!!zHNYc^_(EJ5blXi<-)h zQ3Gsv3-P~@gJ!pwm(2*&+)cwK7{#u*7&W&qBC}7t|de(n} zjKw-R!91eJWto9ZL*>Fcr4HPAZSgnAU}{GHeV*Wd^| zj2gf>e)CpLN6q;~*c^wW&gY<}AQxHMe3toQ#^HJ5l$I&gP&B2M?mw?{Xw@tsSTf>rFIM(Hhmzh1df7 zqH@F=Rs zbpmE2=b$>6f$I3BuGgY+C5TlqhOKcns>AD09eN3?YyH2%K?)V`qI&ir4#!GluVM9^ zWI8YfRlfuYj#YuW(eTOUcUQaKj0*K6)cHHG29}~CH5Y55jkRdsTF!xb`UGl3TTw~# zye_~GP&cf=P53=7!qtRlFkCqNeIww|oLWqTFFBuUbr=W_~{b zb=?%yYA8kxAm(~6Dnj=~-3z`&h2|tGo2%bu%4yhu^7*J6UXI=H3LJxXAbD!Nfn%|E zvH3h-fO>FwZfB`tdo05&yd00Bo}kfAcbF{qVIwM*V{P1u_3(LA$lpfIv1+#DK(+J zAFrldff;y7%s3J2dsup?h^!^9jqyFyRDFrP@E6p5d);kzzY9?fAH)e*Wdv?&KmIndm1!9I8Zl^n_3MWO7CRj?RaU=-WnQq;(Ip{D2!R1SQHEwSz#V`n^v z@(|P%_^}htokRRD=3qM&nyatf3sdKsRnQ)LQhyn$gHuoq&O~+mAtZ^ct*8+lMBTUo zyJNk1rURFwuDcl(xk9&o@jT+MWZFVSU3?L1;yb7jev0atb)V@#Q`at-LUpF=V5~*? zO00(CQ1|oU2%LhgaUUuYpP(Z7Qrp@4>H31Xzu50Al7R!@avY5&F{fo>WFsm&#Psk1E{Qa**xml;}FqaGO!fZT> zO1|Ms%xWk>CE<3gkFR1!{1{U)^+6NK+W0u-bexQ@;BmZosd-(uU1oA?2R0%9tPeP7 zk6)vbqtQd=MmCQF!D7QsTL77`W8`bf0RL9r2?nQOr zZPe5?e>7@(mief;@Fv$Va*Z_$b>q&f%!ft~976d7j==$snT{>Nb(FWeW#8i_cS>+H z^;@w$rmi-R^3Iq^c}$dpbPg7y=420c#*S-D1J|G;u@)8D-KbE$gIdpjLPh2TreKS; zW=h(ies`YhrKre_#8kWyl|#`iw<3f}lBw>AdvOrug*X*I#O^qnVJfs`s0WXYtT1a0 zszbfkn;aR0T0I5W79T_XZa-=nzlZf`-#Ws9M)V!7!1^1^NVlNo?iJJzKf{{%ja&Z{ zCR1+sgjr5a@p8%+V-36=HGmnYP``p#V(ll*{l=rS{||7`krQELrC7^QA-ZIv=|DeJ z`C3#*Z$ypgHdF_eVjX-06~QM_*FTHZaX;#JZ{ThCC4PqfO`IqHtRb7tT#d$sl(SI{ z{0%kdEuS*w3vhzRvaZ7j^*gtihBD&j5qlx3eiZh`mrzNke?Czp(@{Cm2Q}ak7}bfX z9Hik~R5EQsHGCL%;!*rPu6){zaQZej50vNQOsu`#JeU@u?za>h;bv@(FQFpx2{yz^ zEW;nSGye+tT{}#OTGE}6v~LqY6J`I2Y(M+(fw zEbOq)gm^YyM0pFU!Q-eY`2{b<_ECOy3kN>zhR>lc_%q&%O`kXKf)%Le#17O{eT+%y zdBOalG6mJaJiH2@!+fm!qWMRv>DZid^OwwkdSWr!m4zrdE*dB5q%5ImQ1 z9(r*Rssr0F9e?N6|ALx2?*X$F>x4}x55aTrW=zF9aS@hbFRlOP2h9fK8Vqq_9&*n5 z35ViKFPrsU=MaA-p`3x5f>*H<{(=f^r&r7uP=8cIOYkmSj+bNWS53VSl>@ohjP|V} z4hB(iAL@qhppxwu)QuXvW^$o9YJH!FHLwq=;lb|t8{KjqY6@eh`>es6a2K}4W`|9r zdSf!}TLaySp{V6?H7faXuoh0ms#xaM&%qXym!TTk=GOlnwK~3VO?ll+n(GGnQxn{o`*H5e;TjU^{5cne#b;) z6ed&7LFL3GRKpQe&e*8P?Rjtk=p&PV(*R1=SQK4Iab@6fRi%+9&{1?o?zv3g< z@jWxw`%!C_I@P%-L< zYjFT>am(MMl2ngJt>~%Xrs=*hF!7AN9OMY15vAPRg{CzIrtE-$Gv~xf7t9} zI>MB^hw~|a`-!tEwB#db5R4h7}d}~oQhef>p#RM_$_MWH7d+U z=sDO@>wh2zS{}DzGET(WI2m>0VyuEQu_?|)b?`A%hxeg6_#R%4pJEu>v6PCi9NS~< zqvn1+P#x`u&fouU=0HCzLG}0r)DJ#Fb-;7X{Gbl@q}&x%??XM1f^NA4wJhi1aC{Ii z!%FOf?T?$k72S#(C?CS8Mn3hA=7-Z!9hij**`uh4?8F+l7j^wXR70<$BJq1vhkkI& zDPNf9gcmi}Ls93)VUmZP4}MBH_ec<^A7qw|yL}r6L~#e>Qux_i+Q| zQQw-7pG3`Zx4)PX^+uJiK;3u@D#T%&f}1dcZND=)vKSSCC$Jsv#1;65CUpKulS|Jm$Zj^Oo_Z~nz(`A$ru`~x<@7M92I zVt4F~S7RTXhq~@%JV)8|B?p;Q)bMzmKa&kd&CyisgiBF9f64V#Jcshz*bKkH^RWi0 zJsNxCwKx|C;^(LlcTDy;|DZ7r)xnpr5A9nYap1*fDIRAB)C+ScuS0d9UKMk`59&rY zqE^Q|R0D@_0oG0RSUd1h^y3XxJysz;ha>QUY91$&WvD-3?#HN>$p_WVNK$KfoH=ZR zmr;K{F2a0NvLx3uHyVjsDL;a0;F?+<>mJ;X8*xN!kHvzqenOr9rjEyXaAnu^IGfZZ zs0Z0sbv;pML(sOK$N7iBA*d|96G;wh8!D8UbXPwp#|HQcYE^uUP4O6Nij>Lypem}v zLs0d$>r&M3R-%%5LxZTt$>tqYD699l7w&T}IDpFPcii$3_xv}gto|!rgiRZo-;Y4u z_-a(cx1c(l!-Wk9d!@ppnA3dTjE355O<(D@G9ztAEA;i;g(OjCO0zYYhX>z z*T=Saj$7Z)bujAwS78c91MWc()$=f_p%PTX_n?xr+&$m4v8iv5>hJ}q>-)OpL8$B= ziE1DRtKek!{1j9>cVJbm|9jnv`!R_V4`V%Cff~tEsD}2U8am*X52GGTAD}w=nOpt_ zYg7IaHKo;>n4GGGMU)4lIR_SPqY@Okqk#Qcr~g+ z6RB)UMU7cD%4_EGgci+XWu`>lK~sjVnI0*Yqf|XZQTnUV5mV9k^got?c|j&^sa& z31s=h@sk()F*%;zYlO$Xz4skWvho61lLNtt-n5Yg5%0*NNLm-~G{2p>wMmO?Rr`X) z-fUmQ=glb$<$Ke^-bl!s9uM^SBB^_UFA|uL=l2#BP-zVa1%rNdED$NyMUh;;mwWqz zVco_boEQk&qx(0kU+C}Z4@Z0x@&e&pe|Cm9)1KEgy<4H*8w^eJ`lk8xr_e1+K=cBpT5>NOExq$nK!rSH$4ky%-xqF}@t=+E;<{NBvG zJa49{3Y$80LS6EiTJN*~m-vbzp?pS><;%-sn0}uVSbkq@d*(K}EFAI{hl;#ezM!`# z?DqyD-cZmR;x2{Wa3JCjXLyI@I9E*b1tZRw{e?u@#|BPK4{8f+jG?B@=V-UY(Mk-qrz3qpmy!r~0?$e=ScCoBQe-C)R@7Ya^f7^ga} z;YD)!F%!&;=Z6aY%nXyF@rR3aU>{spzvKV94-qDha0kNn=t0fwkG2-Y`(OTSvVF_o z2jj^@uJOcI54|Kge$MdkJheJzlN*z1Gvu9Ye|oryU4O)1AC?)4V>us^SQ6rmz`bc4~OkV<9gNgcJ+qHR+5?(83( zbk}V+?~{Yb7aVph8zDc)(JV8_VmL7aX*of?R9=ky-! z!%_1*R`|3E=)>Q6NP5R;N$c@dP;_=9v{)DjPc~zn=J)0XCg$>xAi$yge7`!yvh@3T ztUJGV=ZmL{^L*sVFf8YKXB^oYa44jQ2kWpP()H8`JMwT1`u)zG&VO*^We0); zflKLszBk_&EYfq>K72#Vc*V_MrP|N=n>EWT;>8j6c5tro^ZIdDZv1`!=brfCiQjwd zHzr-({MxIBcTosVPvfYeqj)~#gzQ$6hh=hW-$b7M3{BZt7|JstKa~cB{`^qHPY9fs zoT7OxQ|dkQx`~gUJlYfAnRk6syjQ5Y#~xFV(?MC|yhYB=3#CQ2S2LuLr1xxZkG{!U zomWd9PoTnLJ36#kJYLW@*?uIls7D}QZ^HZnYfK=L=QsalX))w3X9l89Gm9qlc693F zH)&n*LLwBQB3_kYW+0-cwzq&mnDr6}hl>;}C0D+$*z04y{bqFL{Z-(^)yuL{?EMN$ z4$+8&?FVnW)qd-{2GugWqw@T|u-^_vI;VR5g@vI)`@7p-h$@h$CP5v`4h31PCI^E) zZpF&X_c>b%UM3U#k%+%A!+Vvf@wpXxpB8%kw<&af-iKXS!+}sC&*|H>P=fwx-W)$K zch;cEQoWJ96GNfwC>`b!p4VgjyhRHA&a2y3P(bzu^yW41I3=L+nP)#s&CA=*S%E}N z3GnOJ=&8|USj@VUvUJQ@{r)`XZPJD9OC+}_9Pown`*d-FRv0MCFUa+t>kk*GU2PwT z0q^51W+liQMoX!P1Lp?kWu%Pl)-7T`UA*6JbVsYGuQ13Mtg*)4UbbHD^TA})guD<> zk^i##t+C|rnUOkAGA9{m+6}pK_jK636N=9sxie00IFw(T2V+?CsHb8^yw4pMdhEv2 z)3oX9aC*~s<_gUy_hkL^UuAVTBYwg3X`WWv?S%EzBt4m)34RTN4NNfN&$d_HwJ<(0 zdVfk)_pwo4P~9`kE*e$8{MqWBCh_dMuS{w;sxXujV6!)pwJ|l|pJt8Mw&xl>2v1eT zD`xhoQPq7=*c~27t8?ZpuI|SNKaiU=DwOYc(n6#2PY7ifd&9nBmS?WtNlYyOy%GXJ zCog>ycusNBJbAsjJ~juFir9gLd7EUU49(AvKVkpgV=r7Zvi?=Wt{T+!I`eYq?d_h? z&HjARuv)`?!HGpgu(x-T&+fE1J?>kaQe|-iPkMa9vW(>Twns{w&3Js{isqh9iP)ow z*oH)Gb)qbuD7!aNx~cM^SrxN)R?J;o@yv`w>B>aunndY?iIQ0Snnyz(Po}ePe|}Y> z?$M1akKRA?SjmjzB}cvGs|va@|&~(8Rq| zEl9*RCCWHarUPyro0BND6Qyg_Qb}nd7E6@Q)NCe7=OkjY%+ZcS>BdCaJ&CeCiP*Co zA1^6A@+^JZbZq6^MCsj$(q|H-%Mvl0ep-pLb&1$xiCB5p2A*p6pWdh%-}FSy6#L-j zbK4C#`}Iz?C?kLKa{oX19q{$$(o^374?T5LQk&lfJGP)=*6iaCK4w+SVDzi)Hy>{p zJze*YEFoqP1|mCPyEB}&&PVzUYJ zk==_G(%91qmF9iTk-Y?VroHfmMr}0R6&sgUKD3-`Di?05oV7?nj>V2X9XqmXrrrJF z#`T%WClh6>oF-^!Lz?~bfmYp_KTXBb|JwPqL|KWOX0gqQvL`f^WwX-kr=DpXt$gT- zioK-31``f)(TEV(FZImV068rXxE_Dj$qFL11L0&PL}d5-X;G zo|PA;8%Ylnk&0($99vCi*C$Hn*L7HxC|%C2=tcRIdYsW59&9!``hmin4tVXf7TlpNa=WzJp7 z$OeK-*wo~)wYw@8GrRXX`NTTqn2Xi3vz9b@6L+Rh%QrUHIl0rhwoI$uX^$z<#!7eA zi?Yr7H)2rl|A8Ph`JYMr_a-sxx06WL{x>9RllY5o7baae zGWgGYBw9RVDt4_d|E9jDQGC>UDV|{?|L1e@|DVsr8~*z~7cx{TD;kE7_Yl zYM3_>TkE`$_qaQul9=`~8+n(q4QGc?`2<^HHg%`2iY+LAwyvjD{KgaKdZO&_jxJ=I zx$)Sudyj4B!cESmQLl9N=d+K^;^jZz%m>?)qs!PxZ9KArz4-?1#A5UHif1$CZnpK( zPL#2wXJ)qX-y?hN<7?Rl{N`@uOe$NN%5}@5$IA9{uZp|ZSIoHg_==fF_OcULkSKjD zQTl-M3E-rF#=4HnoXsd3K6jSgE<5`|U2isp+8A-o+KQR;j%mZB4UWd3y$JngKRE03 zrmwQ?-OG3;f9b%PZ=j1y_4V`HP@c6=*p$zjQ!$%B5sD3!r$%Pi z_@aHh{O1LpMTb4zGM(?J^ze}#vzbWciN2=fufC;Dt*F2AA@%f0Ps5~cM|Lkcvg1DO zMo-T_<0yMTGhas7*d4NoT%!#lyYan?%2Sd(Z*Q-f;<>6ys`C-I(Qf-wlc@WNLeEcq zUMf?|oc(Fp1_oO(pKv@-F`tbn^Wo%~CXp~LJuBPUyPowcQgiK+V|!+u>fQ1)H!pig zABuEJVce9k>l|#IlpZd>qJ}4Xd!HJfK}qL2&nz~!42-WB2D71Zb&P$ub^4>ol(+A$ zlOe2 diff --git a/freemius/languages/freemius-nl_NL.mo b/freemius/languages/freemius-nl_NL.mo index 0bb243cec45d6fbd5bcbf129f6f74ef697307b1e..23c0850ce51873617e6db771ed4382af139e4926 100644 GIT binary patch delta 17949 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 Vei@$sUGsSPcN;x7wDWil{3mMditzvd delta 17268 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 Cz~+Vk diff --git a/freemius/languages/freemius-ru_RU.mo b/freemius/languages/freemius-ru_RU.mo index ae560518bd8794ed82459c2a3d7775e3d79f517b..dc69a3636a2b2468a50807c988ea2f850cbae788 100644 GIT binary patch delta 17952 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 I`$qe}0jXDZUH||9 delta 17591 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 diff --git a/freemius/languages/freemius-ta.mo b/freemius/languages/freemius-ta.mo index 0d0fb5f99b3433fd378b59413d0dce8344e3543f..a4e6dba6fbe3f5c6e29bcba2daca9fca0d8bbeb1 100644 GIT binary patch delta 18099 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 delta 17427 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 YbTnVH_FY%1G&??DHLS4bs0-@<09064O#lD@ diff --git a/freemius/languages/freemius-zh_CN.mo b/freemius/languages/freemius-zh_CN.mo index 48ee0ccd00e719a24cf2c03ba047b695484ad0a5..2e7b3a39ea492ef5cbd607490c25d73ec138191a 100644 GIT binary patch delta 18082 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 delta 16973 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\n" "Last-Translator: Vova Feldman \n" -"POT-Creation-Date: 2022-07-06 12:49+0000\n" "Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" @@ -17,34 +16,810 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: includes/class-freemius.php:1932, templates/account.php:941 +#: includes/class-freemius.php:1744, templates/account.php:947 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" -#: includes/class-freemius.php:1939 +#: includes/class-freemius.php:1751 msgid "Would you like to proceed with the update?" msgstr "" -#: includes/class-freemius.php:3751, templates/debug.php:20 +#: includes/class-freemius.php:1976 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:1978, includes/fs-plugin-info-dialog.php:1517 +msgid "Error" +msgstr "" + +#: includes/class-freemius.php:2424 +msgid "I found a better %s" +msgstr "" + +#: includes/class-freemius.php:2426 +msgid "What's the %s's name?" +msgstr "" + +#: includes/class-freemius.php:2432 +msgid "It's a temporary %s - I'm troubleshooting an issue" +msgstr "" + +#: includes/class-freemius.php:2434 +msgid "Deactivation" +msgstr "" + +#: includes/class-freemius.php:2435 +msgid "Theme Switch" +msgstr "" + +#: includes/class-freemius.php:2444, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 +msgid "Other" +msgstr "" + +#: includes/class-freemius.php:2452 +msgid "I no longer need the %s" +msgstr "" + +#: includes/class-freemius.php:2459 +msgid "I only needed the %s for a short period" +msgstr "" + +#: includes/class-freemius.php:2465 +msgid "The %s broke my site" +msgstr "" + +#: includes/class-freemius.php:2472 +msgid "The %s suddenly stopped working" +msgstr "" + +#: includes/class-freemius.php:2482 +msgid "I can't pay for it anymore" +msgstr "" + +#: includes/class-freemius.php:2484 +msgid "What price would you feel comfortable paying?" +msgstr "" + +#: includes/class-freemius.php:2490 +msgid "I don't like to share my information with you" +msgstr "" + +#: includes/class-freemius.php:2511 +msgid "The %s didn't work" +msgstr "" + +#: includes/class-freemius.php:2521 +msgid "I couldn't understand how to make it work" +msgstr "" + +#: includes/class-freemius.php:2529 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "" + +#: includes/class-freemius.php:2531 +msgid "What feature?" +msgstr "" + +#: includes/class-freemius.php:2535 +msgid "The %s is not working" +msgstr "" + +#: includes/class-freemius.php:2537 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2541 +msgid "It's not what I was looking for" +msgstr "" + +#: includes/class-freemius.php:2543 +msgid "What you've been looking for?" +msgstr "" + +#: includes/class-freemius.php:2547 +msgid "The %s didn't work as expected" +msgstr "" + +#: includes/class-freemius.php:2549 +msgid "What did you expect?" +msgstr "" + +#: includes/class-freemius.php:3637, templates/debug.php:24 msgid "Freemius Debug" msgstr "" -#: includes/class-freemius.php:13791 +#: includes/class-freemius.php:4444 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "" + +#: includes/class-freemius.php:4446 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "" + +#: includes/class-freemius.php:4453 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4558 +msgid "Yes - do your thing" +msgstr "" + +#: includes/class-freemius.php:4563 +msgid "No - just deactivate" +msgstr "" + +#: includes/class-freemius.php:4608, includes/class-freemius.php:5139, includes/class-freemius.php:6334, includes/class-freemius.php:13998, includes/class-freemius.php:14747, includes/class-freemius.php:18513, includes/class-freemius.php:18618, includes/class-freemius.php:18795, includes/class-freemius.php:21078, includes/class-freemius.php:21456, includes/class-freemius.php:21470, includes/class-freemius.php:22158, includes/class-freemius.php:23174, includes/class-freemius.php:23304, includes/class-freemius.php:23434, templates/add-ons.php:57 +msgctxt "exclamation" +msgid "Oops" +msgstr "" + +#: includes/class-freemius.php:4683 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "" + +#: includes/class-freemius.php:5108 +msgid "You have purchased a %s license." +msgstr "" + +#: includes/class-freemius.php:5112 +msgid " The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box." +msgstr "" + +#: includes/class-freemius.php:5122, includes/class-freemius.php:6031, includes/class-freemius.php:17889, includes/class-freemius.php:17900, includes/class-freemius.php:21347, includes/class-freemius.php:21738, includes/class-freemius.php:21807, includes/class-freemius.php:21979 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "" + +#: includes/class-freemius.php:5136 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "" + +#: includes/class-freemius.php:5137 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "" + +#: includes/class-freemius.php:5418 +msgid "There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn't work, contact the %s's author with the following:" +msgstr "" + +#: includes/class-freemius.php:6000 +msgid "Premium %s version was successfully activated." +msgstr "" + +#: includes/class-freemius.php:6012, includes/class-freemius.php:7992 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "" + +#: includes/class-freemius.php:6027 +msgid "You have a %s license." +msgstr "" + +#: includes/class-freemius.php:6317 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:6321 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "" + +#: includes/class-freemius.php:6330, templates/add-ons.php:186, templates/account/partials/addon.php:386 +msgid "More information about %s" +msgstr "" + +#: includes/class-freemius.php:6331 +msgid "Purchase License" +msgstr "" + +#: includes/class-freemius.php:7318, templates/connect.php:216 +msgid "You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:7322 +msgid "start the trial" +msgstr "" + +#: includes/class-freemius.php:7323, templates/connect.php:220 +msgid "complete the opt-in" +msgstr "" + +#: includes/class-freemius.php:7456 +msgid "You are just one step away - %s" +msgstr "" + +#: includes/class-freemius.php:7459 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "" + +#: includes/class-freemius.php:7541 +msgid "We made a few tweaks to the %s, %s" +msgstr "" + +#: includes/class-freemius.php:7545 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7991 +msgid "The upgrade of %s was successfully completed." +msgstr "" + +#: includes/class-freemius.php:10709, includes/class-fs-plugin-updater.php:1090, includes/class-fs-plugin-updater.php:1305, includes/class-fs-plugin-updater.php:1312, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "" + +#: includes/class-freemius.php:10711, templates/account.php:411, templates/account.php:419, templates/debug.php:395, templates/debug.php:615 +msgid "Plugin" +msgstr "" + +#: includes/class-freemius.php:10712, templates/account.php:412, templates/account.php:420, templates/debug.php:395, templates/debug.php:615, templates/forms/deactivation/form.php:107 +msgid "Theme" +msgstr "" + +#: includes/class-freemius.php:13817 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" -#: includes/class-freemius.php:13869 +#: includes/class-freemius.php:13831 +msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." +msgstr "" + +#: includes/class-freemius.php:13836, templates/account/partials/disconnect-button.php:84 +msgid "User Dashboard" +msgstr "" + +#: includes/class-freemius.php:13837 +msgid "revert it now" +msgstr "" + +#: includes/class-freemius.php:13895 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" -#: includes/class-freemius.php:13942 +#: includes/class-freemius.php:13969 msgid "Invalid new user ID or email address." msgstr "" -#: includes/class-freemius.php:23326 +#: includes/class-freemius.php:13999 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "" + +#: includes/class-freemius.php:14000 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:14007 +msgid "Change Ownership" +msgstr "" + +#: includes/class-freemius.php:14614 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:14734 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:14736 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:15034 +msgid "Account is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again." +msgstr "" + +#: includes/class-freemius.php:15148, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php:15160, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:15164 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:17871 +msgid "%s opt-in was successfully completed." +msgstr "" + +#: includes/class-freemius.php:17885 +msgid "Your account was successfully activated with the %s plan." +msgstr "" + +#: includes/class-freemius.php:17896, includes/class-freemius.php:21803 +msgid "Your trial has been successfully started." +msgstr "" + +#: includes/class-freemius.php:18511, includes/class-freemius.php:18616, includes/class-freemius.php:18793 +msgid "Couldn't activate %s." +msgstr "" + +#: includes/class-freemius.php:18512, includes/class-freemius.php:18617, includes/class-freemius.php:18794 +msgid "Please contact us with the following message:" +msgstr "" + +#: includes/class-freemius.php:18613, templates/forms/data-debug-mode.php:162 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php:19153, includes/class-freemius.php:24542 +msgid "Upgrade" +msgstr "" + +#: includes/class-freemius.php:19159 +msgid "Start Trial" +msgstr "" + +#: includes/class-freemius.php:19161 +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:19241, includes/class-freemius.php:19243 +msgid "Affiliation" +msgstr "" + +#: includes/class-freemius.php:19271, includes/class-freemius.php:19273, templates/account.php:264, templates/debug.php:362 +msgid "Account" +msgstr "" + +#: includes/class-freemius.php:19287, includes/class-freemius.php:19289, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "" + +#: includes/class-freemius.php:19300, includes/class-freemius.php:19302, includes/class-freemius.php:24556, templates/account.php:134, templates/account/partials/addon.php:49 +msgid "Add-Ons" +msgstr "" + +#: includes/class-freemius.php:19336 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:19336 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php:19338, templates/pricing.php:110 +msgctxt "noun" +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:19551, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "" + +#: includes/class-freemius.php:20572 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "" + +#: includes/class-freemius.php:20573 +msgctxt "a positive response" +msgid "Right on" +msgstr "" + +#: includes/class-freemius.php:21079 +msgid "seems like the key you entered doesn't match our records." +msgstr "" + +#: includes/class-freemius.php:21103 +msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." +msgstr "" + +#: includes/class-freemius.php:21338 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:21340 +msgid "%s Add-on was successfully purchased." +msgstr "" + +#: includes/class-freemius.php:21343 +msgid "Download the latest version" +msgstr "" + +#: includes/class-freemius.php:21449 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php:21455, includes/class-freemius.php:21469, includes/class-freemius.php:21938, includes/class-freemius.php:22027 +msgid "Error received from the server:" +msgstr "" + +#: includes/class-freemius.php:21469 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php:21700, includes/class-freemius.php:21943, includes/class-freemius.php:21998, includes/class-freemius.php:22105 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "" + +#: includes/class-freemius.php:21713 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php:21714, templates/account.php:136, templates/add-ons.php:250, templates/account/partials/addon.php:51 +msgctxt "trial period" +msgid "Trial" +msgstr "" + +#: includes/class-freemius.php:21719 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "" + +#: includes/class-freemius.php:21723, includes/class-freemius.php:21782 +msgid "Please contact us here" +msgstr "" + +#: includes/class-freemius.php:21734 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:21735 +msgid "Your plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:21752 +msgid "Your plan was successfully changed to %s." +msgstr "" + +#: includes/class-freemius.php:21768 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "" + +#: includes/class-freemius.php:21770 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:21778 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "" + +#: includes/class-freemius.php:21791 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "" + +#: includes/class-freemius.php:21817 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "" + +#: includes/class-freemius.php:21819 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:21934 +msgid "It looks like the license could not be activated." +msgstr "" + +#: includes/class-freemius.php:21976 +msgid "Your license was successfully activated." +msgstr "" + +#: includes/class-freemius.php:22002 +msgid "It looks like your site currently doesn't have an active license." +msgstr "" + +#: includes/class-freemius.php:22026 +msgid "It looks like the license deactivation failed." +msgstr "" + +#: includes/class-freemius.php:22055 +msgid "Your %s license was successfully deactivated." +msgstr "" + +#: includes/class-freemius.php:22056 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "" + +#: includes/class-freemius.php:22059 +msgid "O.K" +msgstr "" + +#: includes/class-freemius.php:22112 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:22121 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "" + +#: includes/class-freemius.php:22163 +msgid "You are already running the %s in a trial mode." +msgstr "" + +#: includes/class-freemius.php:22174 +msgid "You already utilized a trial before." +msgstr "" + +#: includes/class-freemius.php:22188 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "" + +#: includes/class-freemius.php:22199 +msgid "Plan %s does not support a trial period." +msgstr "" + +#: includes/class-freemius.php:22210 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:22259 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "" + +#: includes/class-freemius.php:22295 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:22314 +msgid "Your %s free trial was successfully cancelled." +msgstr "" + +#: includes/class-freemius.php:22641 +msgid "Version %s was released." +msgstr "" + +#: includes/class-freemius.php:22641 +msgid "Please download %s." +msgstr "" + +#: includes/class-freemius.php:22648 +msgid "the latest %s version here" +msgstr "" + +#: includes/class-freemius.php:22653 +msgid "New" +msgstr "" + +#: includes/class-freemius.php:22658 +msgid "Seems like you got the latest release." +msgstr "" + +#: includes/class-freemius.php:22659 +msgid "You are all good!" +msgstr "" + +#: includes/class-freemius.php:23062 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "" + +#: includes/class-freemius.php:23202 +msgid "Site successfully opted in." +msgstr "" + +#: includes/class-freemius.php:23203, includes/class-freemius.php:24252 +msgid "Awesome" +msgstr "" + +#: includes/class-freemius.php:23219 +msgid "Sharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to." +msgstr "" + +#: includes/class-freemius.php:23220 +msgid "Thank you!" +msgstr "" + +#: includes/class-freemius.php:23229 +msgid "Diagnostic data will no longer be sent from %s to %s." +msgstr "" + +#: includes/class-freemius.php:23384 +msgid "A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours." +msgstr "" + +#: includes/class-freemius.php:23386 +msgid "A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder." +msgstr "" + +#: includes/class-freemius.php:23393 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "" + +#: includes/class-freemius.php:23398 +msgid "%s is the new owner of the account." +msgstr "" + +#: includes/class-freemius.php:23400 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "" + +#: includes/class-freemius.php:23417 +msgid "Please provide your full name." +msgstr "" + +#: includes/class-freemius.php:23422 +msgid "Your name was successfully updated." +msgstr "" + +#: includes/class-freemius.php:23483 +msgid "You have successfully updated your %s." +msgstr "" + +#: includes/class-freemius.php:23542 +msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." +msgstr "" + +#: includes/class-freemius.php:23545 +msgid "Click here" +msgstr "" + +#: includes/class-freemius.php:23582, includes/class-freemius.php:23579 msgid "Bundle" msgstr "" +#: includes/class-freemius.php:23662 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "" + +#: includes/class-freemius.php:23663 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "" + +#: includes/class-freemius.php:24292 +msgctxt "exclamation" +msgid "Hey" +msgstr "" + +#: includes/class-freemius.php:24292 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "" + +#: includes/class-freemius.php:24300 +msgid "No commitment for %s days - cancel anytime!" +msgstr "" + +#: includes/class-freemius.php:24301 +msgid "No credit card required" +msgstr "" + +#: includes/class-freemius.php:24308, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "" + +#: includes/class-freemius.php:24385 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:24394 +msgid "Learn more" +msgstr "" + +#: includes/class-freemius.php:24580, templates/account.php:573, templates/account.php:725, templates/connect.php:223, templates/connect.php:449, includes/managers/class-fs-clone-manager.php:1295, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 +msgid "Activate License" +msgstr "" + +#: includes/class-freemius.php:24581, templates/account.php:667, templates/account.php:724, templates/account/partials/addon.php:327, templates/account/partials/site.php:273 +msgid "Change License" +msgstr "" + +#: includes/class-freemius.php:24688, templates/account/partials/site.php:170 +msgid "Opt Out" +msgstr "" + +#: includes/class-freemius.php:24690, includes/class-freemius.php:24696, templates/account/partials/site.php:49, templates/account/partials/site.php:170 +msgid "Opt In" +msgstr "" + +#: includes/class-freemius.php:24931 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:24941 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:24954 +msgid "Please follow these steps to complete the upgrade" +msgstr "" + +#: includes/class-freemius.php:24958 +msgid "Download the latest %s version" +msgstr "" + +#: includes/class-freemius.php:24962 +msgid "Upload and activate the downloaded version" +msgstr "" + +#: includes/class-freemius.php:24964 +msgid "How to upload and activate?" +msgstr "" + +#: includes/class-freemius.php:25098 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "" + +#: includes/class-freemius.php:25267 +msgid "Auto installation only works for opted-in users." +msgstr "" + +#: includes/class-freemius.php:25277, includes/class-freemius.php:25310, includes/class-fs-plugin-updater.php:1284, includes/class-fs-plugin-updater.php:1298 +msgid "Invalid module ID." +msgstr "" + +#: includes/class-freemius.php:25286, includes/class-fs-plugin-updater.php:1320 +msgid "Premium version already active." +msgstr "" + +#: includes/class-freemius.php:25293 +msgid "You do not have a valid license to access the premium version." +msgstr "" + +#: includes/class-freemius.php:25300 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "" + +#: includes/class-freemius.php:25318, includes/class-fs-plugin-updater.php:1319 +msgid "Premium add-on version already installed." +msgstr "" + +#: includes/class-freemius.php:25672 +msgid "View paid features" +msgstr "" + +#: includes/class-freemius.php:25994 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:25995 +msgid "Thank you so much for using %s!" +msgstr "" + +#: includes/class-freemius.php:26001 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "" + +#: includes/class-freemius.php:26005 +msgid "Thank you so much for using our products!" +msgstr "" + +#: includes/class-freemius.php:26006 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "" + +#: includes/class-freemius.php:26025 +msgid "%s and its add-ons" +msgstr "" + +#: includes/class-freemius.php:26034 +msgid "Products" +msgstr "" + +#: includes/class-freemius.php:26041, templates/connect.php:324 +msgid "Yes" +msgstr "" + +#: includes/class-freemius.php:26042, templates/connect.php:325 +msgid "send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:26043, templates/connect.php:330 +msgid "No" +msgstr "" + +#: includes/class-freemius.php:26045, templates/connect.php:332 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:26055 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php:26057, templates/connect.php:339 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "" + +#: includes/class-freemius.php:26339 +msgid "License key is empty." +msgstr "" + #: includes/class-fs-plugin-updater.php:206, templates/forms/premium-versions-upgrade-handler.php:57 msgid "Renew license" msgstr "" @@ -53,15 +828,15 @@ msgstr "" msgid "Buy license" msgstr "" -#: includes/class-fs-plugin-updater.php:364, includes/class-fs-plugin-updater.php:331 +#: includes/class-fs-plugin-updater.php:331, includes/class-fs-plugin-updater.php:364 msgid "There is a %s of %s available." msgstr "" -#: includes/class-fs-plugin-updater.php:369, includes/class-fs-plugin-updater.php:333 +#: includes/class-fs-plugin-updater.php:333, includes/class-fs-plugin-updater.php:369 msgid "new Beta version" msgstr "" -#: includes/class-fs-plugin-updater.php:370, includes/class-fs-plugin-updater.php:334 +#: includes/class-fs-plugin-updater.php:334, includes/class-fs-plugin-updater.php:370 msgid "new version" msgstr "" @@ -69,635 +844,604 @@ msgstr "" msgid "Important Upgrade Notice:" msgstr "" -#: includes/class-fs-plugin-updater.php:1551 +#: includes/class-fs-plugin-updater.php:1349 +msgid "Installing plugin: %s" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1390 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1572 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" -#: includes/fs-plugin-info-dialog.php:541 +#: includes/fs-plugin-info-dialog.php:542 msgid "Purchase More" msgstr "" -#: includes/fs-plugin-info-dialog.php:542, templates/account/partials/addon.php:390 +#: includes/fs-plugin-info-dialog.php:543, templates/account/partials/addon.php:390 msgctxt "verb" msgid "Purchase" msgstr "" -#. translators: %s: N-days trial -#: includes/fs-plugin-info-dialog.php:546 +#: includes/fs-plugin-info-dialog.php:547 msgid "Start my free %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:754 -msgid "Install Free Version Now" +#: includes/fs-plugin-info-dialog.php:745 +msgid "Install Free Version Update Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:423, templates/account/partials/addon.php:370 -msgid "Install Now" +#: includes/fs-plugin-info-dialog.php:746, templates/account.php:656 +msgid "Install Update Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:744 -msgid "Install Free Version Update Now" +#: includes/fs-plugin-info-dialog.php:755 +msgid "Install Free Version Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:745, templates/account.php:650 -msgid "Install Update Now" +#: includes/fs-plugin-info-dialog.php:756, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:370, templates/account/partials/addon.php:423 +msgid "Install Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:771 +#: includes/fs-plugin-info-dialog.php:772 msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:772, templates/account.php:109, templates/add-ons.php:37, templates/account/partials/addon.php:30 +#: includes/fs-plugin-info-dialog.php:773, templates/account.php:114, templates/add-ons.php:37, templates/account/partials/addon.php:30 msgctxt "as download latest version" msgid "Download Latest" msgstr "" -#: includes/fs-plugin-info-dialog.php:787, templates/add-ons.php:329, templates/account/partials/addon.php:417, templates/account/partials/addon.php:361 +#: includes/fs-plugin-info-dialog.php:788, templates/add-ons.php:329, templates/account/partials/addon.php:361, templates/account/partials/addon.php:417 msgid "Activate this add-on" msgstr "" -#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:483 +#: includes/fs-plugin-info-dialog.php:790, templates/connect.php:446 msgid "Activate Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:790, templates/account.php:133, templates/add-ons.php:330, templates/account/partials/addon.php:53 +#: includes/fs-plugin-info-dialog.php:791, templates/account.php:138, templates/add-ons.php:330, templates/account/partials/addon.php:53 msgid "Activate" msgstr "" -#: includes/fs-plugin-info-dialog.php:1002 +#: includes/fs-plugin-info-dialog.php:1003 msgctxt "Plugin installer section title" msgid "Description" msgstr "" -#: includes/fs-plugin-info-dialog.php:1003 +#: includes/fs-plugin-info-dialog.php:1004 msgctxt "Plugin installer section title" msgid "Installation" msgstr "" -#: includes/fs-plugin-info-dialog.php:1004 +#: includes/fs-plugin-info-dialog.php:1005 msgctxt "Plugin installer section title" msgid "FAQ" msgstr "" -#: includes/fs-plugin-info-dialog.php:1005, templates/plugin-info/description.php:55 +#: includes/fs-plugin-info-dialog.php:1006, templates/plugin-info/description.php:55 msgid "Screenshots" msgstr "" -#: includes/fs-plugin-info-dialog.php:1006 +#: includes/fs-plugin-info-dialog.php:1007 msgctxt "Plugin installer section title" msgid "Changelog" msgstr "" -#: includes/fs-plugin-info-dialog.php:1007 +#: includes/fs-plugin-info-dialog.php:1008 msgctxt "Plugin installer section title" msgid "Reviews" msgstr "" -#: includes/fs-plugin-info-dialog.php:1008 +#: includes/fs-plugin-info-dialog.php:1009 msgctxt "Plugin installer section title" msgid "Other Notes" msgstr "" -#: includes/fs-plugin-info-dialog.php:1023 +#: includes/fs-plugin-info-dialog.php:1024 msgctxt "Plugin installer section title" msgid "Features & Pricing" msgstr "" -#: includes/fs-plugin-info-dialog.php:1033 +#: includes/fs-plugin-info-dialog.php:1034 msgid "Plugin Install" msgstr "" -#: includes/fs-plugin-info-dialog.php:1105 +#: includes/fs-plugin-info-dialog.php:1106 msgctxt "e.g. Professional Plan" msgid "%s Plan" msgstr "" -#: includes/fs-plugin-info-dialog.php:1131 +#: includes/fs-plugin-info-dialog.php:1132 msgctxt "e.g. the best product" msgid "Best" msgstr "" -#: includes/fs-plugin-info-dialog.php:1137, includes/fs-plugin-info-dialog.php:1157 +#: includes/fs-plugin-info-dialog.php:1138, includes/fs-plugin-info-dialog.php:1158 msgctxt "as every month" msgid "Monthly" msgstr "" -#: includes/fs-plugin-info-dialog.php:1140 +#: includes/fs-plugin-info-dialog.php:1141 msgctxt "as once a year" msgid "Annual" msgstr "" -#: includes/fs-plugin-info-dialog.php:1143 +#: includes/fs-plugin-info-dialog.php:1144 msgid "Lifetime" msgstr "" -#: includes/fs-plugin-info-dialog.php:1157, includes/fs-plugin-info-dialog.php:1159, includes/fs-plugin-info-dialog.php:1161 +#: includes/fs-plugin-info-dialog.php:1158, includes/fs-plugin-info-dialog.php:1160, includes/fs-plugin-info-dialog.php:1162 msgctxt "e.g. billed monthly" msgid "Billed %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1159 +#: includes/fs-plugin-info-dialog.php:1160 msgctxt "as once a year" msgid "Annually" msgstr "" -#: includes/fs-plugin-info-dialog.php:1161 +#: includes/fs-plugin-info-dialog.php:1162 msgctxt "as once a year" msgid "Once" msgstr "" -#: includes/fs-plugin-info-dialog.php:1167 +#: includes/fs-plugin-info-dialog.php:1168 msgid "Single Site License" msgstr "" -#: includes/fs-plugin-info-dialog.php:1169 +#: includes/fs-plugin-info-dialog.php:1170 msgid "Unlimited Licenses" msgstr "" -#: includes/fs-plugin-info-dialog.php:1171 +#: includes/fs-plugin-info-dialog.php:1172 msgid "Up to %s Sites" msgstr "" -#: includes/fs-plugin-info-dialog.php:1181, templates/plugin-info/features.php:82 +#: includes/fs-plugin-info-dialog.php:1182, templates/plugin-info/features.php:82 msgctxt "as monthly period" msgid "mo" msgstr "" -#: includes/fs-plugin-info-dialog.php:1188, templates/plugin-info/features.php:80 +#: includes/fs-plugin-info-dialog.php:1189, templates/plugin-info/features.php:80 msgctxt "as annual period" msgid "year" msgstr "" -#: includes/fs-plugin-info-dialog.php:1242 +#: includes/fs-plugin-info-dialog.php:1243 msgctxt "noun" msgid "Price" msgstr "" -#. translators: %s: Discount (e.g. discount of $5 or 10%) -#: includes/fs-plugin-info-dialog.php:1290 +#: includes/fs-plugin-info-dialog.php:1291 msgid "Save %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1300 +#: includes/fs-plugin-info-dialog.php:1301 msgid "No commitment for %s - cancel anytime" msgstr "" -#: includes/fs-plugin-info-dialog.php:1303 +#: includes/fs-plugin-info-dialog.php:1304 msgid "After your free %s, pay as little as %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1314 +#: includes/fs-plugin-info-dialog.php:1315 msgid "Details" msgstr "" -#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:120, templates/debug.php:215, templates/debug.php:252, templates/debug.php:466, templates/account/partials/addon.php:41 +#: includes/fs-plugin-info-dialog.php:1319, templates/account.php:125, templates/debug.php:232, templates/debug.php:269, templates/debug.php:514, templates/account/partials/addon.php:41 msgctxt "product version" msgid "Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:1325 +#: includes/fs-plugin-info-dialog.php:1326 msgctxt "as the plugin author" msgid "Author" msgstr "" -#: includes/fs-plugin-info-dialog.php:1332 +#: includes/fs-plugin-info-dialog.php:1333 msgid "Last Updated" msgstr "" -#. translators: %s: time period (e.g. "2 hours" ago) -#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:536 +#: includes/fs-plugin-info-dialog.php:1338, templates/account.php:544 msgctxt "x-ago" msgid "%s ago" msgstr "" -#: includes/fs-plugin-info-dialog.php:1346 +#: includes/fs-plugin-info-dialog.php:1347 msgid "Requires WordPress Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:1347 +#: includes/fs-plugin-info-dialog.php:1350, includes/fs-plugin-info-dialog.php:1370 msgid "%s or higher" msgstr "" -#: includes/fs-plugin-info-dialog.php:1354 +#: includes/fs-plugin-info-dialog.php:1358 msgid "Compatible up to" msgstr "" -#: includes/fs-plugin-info-dialog.php:1362 +#: includes/fs-plugin-info-dialog.php:1366 +msgid "Requires PHP Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1379 msgid "Downloaded" msgstr "" -#. translators: %s: 1 or One (Number of times downloaded) -#: includes/fs-plugin-info-dialog.php:1366 +#: includes/fs-plugin-info-dialog.php:1383 msgid "%s time" msgstr "" -#. translators: %s: Number of times downloaded -#: includes/fs-plugin-info-dialog.php:1368 +#: includes/fs-plugin-info-dialog.php:1385 msgid "%s times" msgstr "" -#: includes/fs-plugin-info-dialog.php:1379 +#: includes/fs-plugin-info-dialog.php:1396 msgid "WordPress.org Plugin Page" msgstr "" -#: includes/fs-plugin-info-dialog.php:1388 +#: includes/fs-plugin-info-dialog.php:1405 msgid "Plugin Homepage" msgstr "" -#: includes/fs-plugin-info-dialog.php:1397, includes/fs-plugin-info-dialog.php:1481 +#: includes/fs-plugin-info-dialog.php:1414, includes/fs-plugin-info-dialog.php:1498 msgid "Donate to this plugin" msgstr "" -#: includes/fs-plugin-info-dialog.php:1404 +#: includes/fs-plugin-info-dialog.php:1421 msgid "Average Rating" msgstr "" -#: includes/fs-plugin-info-dialog.php:1411 +#: includes/fs-plugin-info-dialog.php:1428 msgid "based on %s" msgstr "" -#. translators: %s: 1 or One -#: includes/fs-plugin-info-dialog.php:1415 +#: includes/fs-plugin-info-dialog.php:1432 msgid "%s rating" msgstr "" -#. translators: %s: Number larger than 1 -#: includes/fs-plugin-info-dialog.php:1417 +#: includes/fs-plugin-info-dialog.php:1434 msgid "%s ratings" msgstr "" -#. translators: %s: 1 or One -#: includes/fs-plugin-info-dialog.php:1432 +#: includes/fs-plugin-info-dialog.php:1449 msgid "%s star" msgstr "" -#. translators: %s: Number larger than 1 -#: includes/fs-plugin-info-dialog.php:1434 +#: includes/fs-plugin-info-dialog.php:1451 msgid "%s stars" msgstr "" -#. translators: %s: # of stars (e.g. 5 stars) -#: includes/fs-plugin-info-dialog.php:1446 +#: includes/fs-plugin-info-dialog.php:1463 msgid "Click to see reviews that provided a rating of %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1459 +#: includes/fs-plugin-info-dialog.php:1476 msgid "Contributors" msgstr "" -#: includes/fs-plugin-info-dialog.php:1491, includes/fs-plugin-info-dialog.php:1489 -msgid "Warning" +#: includes/fs-plugin-info-dialog.php:1517 +msgid "This plugin requires a newer version of PHP." msgstr "" -#: includes/fs-plugin-info-dialog.php:1491 -msgid "This plugin has not been marked as compatible with your version of WordPress." +#: includes/fs-plugin-info-dialog.php:1526 +msgid "Click here to learn more about updating PHP." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1540, includes/fs-plugin-info-dialog.php:1542 +msgid "Warning" msgstr "" -#: includes/fs-plugin-info-dialog.php:1489 +#: includes/fs-plugin-info-dialog.php:1540 msgid "This plugin has not been tested with your current version of WordPress." msgstr "" -#: includes/fs-plugin-info-dialog.php:1510 +#: includes/fs-plugin-info-dialog.php:1542 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1561 msgid "Paid add-on must be deployed to Freemius." msgstr "" -#: includes/fs-plugin-info-dialog.php:1511 +#: includes/fs-plugin-info-dialog.php:1562 msgid "Add-on must be deployed to WordPress.org or Freemius." msgstr "" -#: includes/fs-plugin-info-dialog.php:1540 -msgid "Latest Version Installed" +#: includes/fs-plugin-info-dialog.php:1583 +msgid "Newer Version (%s) Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1541 -msgid "Latest Free Version Installed" +#: includes/fs-plugin-info-dialog.php:1584 +msgid "Newer Free Version (%s) Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1532 -msgid "Newer Version (%s) Installed" +#: includes/fs-plugin-info-dialog.php:1591 +msgid "Latest Version Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1533 -msgid "Newer Free Version (%s) Installed" +#: includes/fs-plugin-info-dialog.php:1592 +msgid "Latest Free Version Installed" msgstr "" -#: templates/account.php:110, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:311 +#: templates/account.php:115, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:313 msgid "Downgrading your plan" msgstr "" -#: templates/account.php:111, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:312 +#: templates/account.php:116, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:314 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account.php:113, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 +#: templates/account.php:118, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:316 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" -#: templates/account.php:114, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:315 +#: templates/account.php:119, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:317 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" -#: templates/account.php:115, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 +#: templates/account.php:120, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" -#: templates/account.php:116, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:316 +#: templates/account.php:121, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:318 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" -#: templates/account.php:117, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:317 +#: templates/account.php:122, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:319 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") -#: templates/account.php:119, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 +#: templates/account.php:124, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") -#: templates/account.php:122, templates/account/partials/addon.php:43, templates/account/partials/site.php:291 +#: templates/account.php:127, templates/account/partials/addon.php:43, templates/account/partials/site.php:293 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") -#: templates/account.php:124, templates/account/partials/addon.php:45, templates/account/partials/site.php:293 +#: templates/account.php:129, templates/account/partials/addon.php:45, templates/account/partials/site.php:295 msgid "Expires in %s" msgstr "" -#: templates/account.php:125 +#: templates/account.php:130 msgctxt "as synchronize license" msgid "Sync License" msgstr "" -#: templates/account.php:126, templates/account/partials/addon.php:46 +#: templates/account.php:131, templates/account/partials/addon.php:46 msgid "Cancel Trial" msgstr "" -#: templates/account.php:127, templates/account/partials/addon.php:47 +#: templates/account.php:132, templates/account/partials/addon.php:47 msgid "Change Plan" msgstr "" -#: templates/account.php:128, templates/account/partials/addon.php:48 +#: templates/account.php:133, templates/account/partials/addon.php:48 msgctxt "verb" msgid "Upgrade" msgstr "" -#: templates/account.php:129, templates/account/partials/addon.php:49 -msgid "Add-Ons" -msgstr "" - -#: templates/account.php:130, templates/account/partials/addon.php:50, templates/account/partials/site.php:318 +#: templates/account.php:135, templates/account/partials/addon.php:50, templates/account/partials/site.php:320 msgctxt "verb" msgid "Downgrade" msgstr "" -#: templates/account.php:131, templates/add-ons.php:250, templates/account/partials/addon.php:51 -msgctxt "trial period" -msgid "Trial" -msgstr "" - -#: templates/account.php:132, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 +#: templates/account.php:137, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 msgid "Free" msgstr "" -#: templates/account.php:134, templates/debug.php:385, templates/account/partials/addon.php:54 +#: templates/account.php:139, templates/debug.php:408, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:54 msgctxt "as product pricing plan" msgid "Plan" msgstr "" -#: templates/account.php:135 +#: templates/account.php:140 msgid "Bundle Plan" msgstr "" -#: templates/account.php:251, templates/debug.php:338 -msgid "Account" -msgstr "" - -#: templates/account.php:259 +#: templates/account.php:272 msgid "Free Trial" msgstr "" -#: templates/account.php:270 +#: templates/account.php:283 msgid "Account Details" msgstr "" -#: templates/account.php:279 -msgid "Stop Debug" -msgstr "" - -#: templates/account.php:277, templates/forms/data-debug-mode.php:33 +#: templates/account.php:290, templates/forms/data-debug-mode.php:33 msgid "Start Debug" msgstr "" -#: templates/account.php:286 -msgid "Billing & Invoices" +#: templates/account.php:292 +msgid "Stop Debug" msgstr "" #: templates/account.php:299 -msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" -msgstr "" - -#: templates/account.php:297 -msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" -msgstr "" - -#: templates/account.php:302 -msgid "Delete Account" +msgid "Billing & Invoices" msgstr "" -#: templates/account.php:314, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 +#: templates/account.php:322, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" -#: templates/account.php:337, templates/forms/subscription-cancellation.php:125 +#: templates/account.php:345, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" -#: templates/account.php:337, templates/account/partials/addon.php:260 +#: templates/account.php:345, templates/account/partials/addon.php:260 msgid "Cancel Subscription" msgstr "" -#: templates/account.php:366, templates/account/partials/addon.php:345 +#: templates/account.php:374, templates/account/partials/addon.php:345 msgctxt "as synchronize" msgid "Sync" msgstr "" -#: templates/account.php:381, templates/debug.php:523 +#: templates/account.php:389, templates/debug.php:571 msgid "Name" msgstr "" -#: templates/account.php:387, templates/debug.php:524 +#: templates/account.php:395, templates/debug.php:572 msgid "Email" msgstr "" -#: templates/account.php:394, templates/debug.php:383, templates/debug.php:573 +#: templates/account.php:402, templates/debug.php:406, templates/debug.php:621 msgid "User ID" msgstr "" -#: templates/account.php:403, templates/account.php:411, templates/debug.php:372, templates/debug.php:567 -msgid "Plugin" -msgstr "" - -#: templates/account.php:404, templates/account.php:412, templates/debug.php:372, templates/debug.php:567, templates/forms/deactivation/form.php:107 -msgid "Theme" -msgstr "" - -#: templates/account.php:412, templates/account.php:732, templates/account.php:783, templates/debug.php:250, templates/debug.php:377, templates/debug.php:463, templates/debug.php:522, templates/debug.php:571, templates/debug.php:650, templates/account/payments.php:35, templates/debug/logger.php:21 +#: templates/account.php:420, templates/account.php:738, templates/account.php:789, templates/debug.php:267, templates/debug.php:400, templates/debug.php:511, templates/debug.php:570, templates/debug.php:619, templates/debug.php:698, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" -#: templates/account.php:419 +#: templates/account.php:427 msgid "Site ID" msgstr "" -#: templates/account.php:422 +#: templates/account.php:430 msgid "No ID" msgstr "" -#: templates/account.php:427, templates/debug.php:257, templates/debug.php:386, templates/debug.php:467, templates/debug.php:526, templates/account/partials/site.php:227 +#: templates/account.php:435, templates/debug.php:274, templates/debug.php:409, templates/debug.php:515, templates/debug.php:574, templates/account/partials/site.php:228 msgid "Public Key" msgstr "" -#: templates/account.php:433, templates/debug.php:387, templates/debug.php:468, templates/debug.php:527, templates/account/partials/site.php:239 +#: templates/account.php:441, templates/debug.php:410, templates/debug.php:516, templates/debug.php:575, templates/account/partials/site.php:241 msgid "Secret Key" msgstr "" -#: templates/account.php:436 +#: templates/account.php:444 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" -#: templates/account.php:490, templates/debug.php:579, templates/account/partials/site.php:260 -msgid "License Key" +#: templates/account.php:471, templates/account/partials/site.php:120, templates/account/partials/site.php:122 +msgid "Trial" msgstr "" -#: templates/account.php:463, templates/account/partials/site.php:122, templates/account/partials/site.php:120 -msgid "Trial" +#: templates/account.php:498, templates/debug.php:627, templates/account/partials/site.php:262 +msgid "License Key" msgstr "" -#: templates/account.php:521 +#: templates/account.php:529 msgid "Join the Beta program" msgstr "" -#: templates/account.php:527 +#: templates/account.php:535 msgid "not verified" msgstr "" -#: templates/account.php:598 -msgid "Free version" +#: templates/account.php:544, templates/account/partials/addon.php:195 +msgid "Expired" msgstr "" -#: templates/account.php:596 +#: templates/account.php:602 msgid "Premium version" msgstr "" -#: templates/account.php:536, templates/account/partials/addon.php:195 -msgid "Expired" -msgstr "" - -#: templates/account.php:567, templates/account.php:719, templates/connect.php:198, templates/connect.php:486, includes/managers/class-fs-clone-manager.php:1123, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 -msgid "Activate License" +#: templates/account.php:604 +msgid "Free version" msgstr "" -#: templates/account.php:610 +#: templates/account.php:616 msgid "Verify Email" msgstr "" -#: templates/account.php:687, templates/forms/user-change.php:27 -msgid "Change User" -msgstr "" - -#: templates/account.php:674 -msgid "What is your %s?" +#: templates/account.php:630 +msgid "Download %s Version" msgstr "" -#: templates/account.php:682, templates/account/billing.php:21 -msgctxt "verb" -msgid "Edit" +#: templates/account.php:646 +msgid "Download Paid Version" msgstr "" -#: templates/account.php:658, templates/account.php:921, templates/account/partials/site.php:248, templates/account/partials/site.php:270 +#: templates/account.php:664, templates/account.php:927, templates/account/partials/site.php:250, templates/account/partials/site.php:272 msgctxt "verb" msgid "Show" msgstr "" -#: templates/account.php:661, templates/account.php:718, templates/account/partials/addon.php:327, templates/account/partials/site.php:271 -msgid "Change License" +#: templates/account.php:680 +msgid "What is your %s?" msgstr "" -#: templates/account.php:624 -msgid "Download %s Version" +#: templates/account.php:688, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" msgstr "" -#: templates/account.php:640 -msgid "Download Paid Version" +#: templates/account.php:693, templates/forms/user-change.php:27 +msgid "Change User" msgstr "" -#: templates/account.php:711 +#: templates/account.php:717 msgid "Sites" msgstr "" -#: templates/account.php:724 +#: templates/account.php:730 msgid "Search by address" msgstr "" -#: templates/account.php:733, templates/debug.php:380 +#: templates/account.php:739, templates/debug.php:403 msgid "Address" msgstr "" -#: templates/account.php:734 +#: templates/account.php:740 msgid "License" msgstr "" -#: templates/account.php:735 +#: templates/account.php:741 msgid "Plan" msgstr "" -#: templates/account.php:786 +#: templates/account.php:792 msgctxt "as software license" msgid "License" msgstr "" -#: templates/account.php:915 +#: templates/account.php:921 msgctxt "verb" msgid "Hide" msgstr "" -#: templates/account.php:937, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 +#: templates/account.php:943, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 msgid "Processing" msgstr "" -#: templates/account.php:940 +#: templates/account.php:946 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" -#: templates/account.php:998 +#: templates/account.php:1004 msgid "Cancelling %s" msgstr "" -#: templates/account.php:998, templates/account.php:1015, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 +#: templates/account.php:1004, templates/account.php:1021, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 msgid "trial" msgstr "" -#: templates/account.php:1013, templates/forms/deactivation/form.php:195 +#: templates/account.php:1019, templates/forms/deactivation/form.php:195 msgid "Cancelling %s..." msgstr "" -#: templates/account.php:1016, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 +#: templates/account.php:1022, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 msgid "subscription" msgstr "" -#: templates/account.php:1030 +#: templates/account.php:1036 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" -#: templates/account.php:1104 +#: templates/account.php:1110 msgid "Disabling white-label mode" msgstr "" -#: templates/account.php:1105 +#: templates/account.php:1111 msgid "Enabling white-label mode" msgstr "" @@ -709,19 +1453,10 @@ msgstr "" msgid "Add Ons for %s" msgstr "" -#: templates/add-ons.php:57 -msgctxt "exclamation" -msgid "Oops" -msgstr "" - #: templates/add-ons.php:58 msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." msgstr "" -#: templates/add-ons.php:186, templates/account/partials/addon.php:386 -msgid "More information about %s" -msgstr "" - #: templates/add-ons.php:229 msgctxt "active add-on" msgid "Active" @@ -732,16 +1467,11 @@ msgctxt "installed add-on" msgid "Installed" msgstr "" -#: templates/admin-notice.php:13, templates/forms/license-activation.php:250, templates/forms/resend-key.php:80 +#: templates/admin-notice.php:13, templates/forms/license-activation.php:243, templates/forms/resend-key.php:80 msgctxt "as close a window" msgid "Dismiss" msgstr "" -#: templates/auto-installation.php:32 -msgid "Add-On" -msgstr "" - -#. translators: %s: Number of seconds #: templates/auto-installation.php:45 msgid "%s sec" msgstr "" @@ -762,147 +1492,164 @@ msgstr "" msgid "Cancel Installation" msgstr "" +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + #. translators: %s: name (e.g. Hey John,) #: templates/connect.php:121 msgctxt "greeting" msgid "Hey %s," msgstr "" -#: templates/connect.php:181 -msgid "Allow & Continue" +#: templates/connect.php:189 +msgid "Never miss an important update" msgstr "" -#: templates/connect.php:210, templates/connect.php:217 -msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +#: templates/connect.php:197 +msgid "Thank you for updating to %1$s v%2$s!" msgstr "" -#: templates/connect.php:211, templates/connect.php:218 -msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +#: templates/connect.php:207 +msgid "Allow & Continue" msgstr "" -#: templates/connect.php:221 -msgid "If you skip this, that's okay! %1$s will still work just fine." +#: templates/connect.php:211 +msgid "Re-send activation email" msgstr "" -#: templates/connect.php:199, templates/forms/license-activation.php:46 -msgid "Agree & Activate License" +#: templates/connect.php:215 +msgid "Thanks %s!" msgstr "" -#: templates/connect.php:203 +#: templates/connect.php:227 msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" -#: templates/connect.php:185 -msgid "Re-send activation email" +#: templates/connect.php:237 +msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" -#: templates/connect.php:189 -msgid "Thanks %s!" +#: templates/connect.php:239 +msgid "Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" -#: templates/connect.php:190 -msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +#. translators: %s: module type (plugin, theme, or add-on) +#: templates/connect.php:245 +msgid "We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" -#: templates/connect.php:194 -msgid "complete the install" +#: templates/connect.php:248 +msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info." msgstr "" -#: templates/connect.php:251 -msgid "We're excited to introduce the Freemius network-level integration." +#: templates/connect.php:249 +msgid "Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info." msgstr "" -#: templates/connect.php:265 -msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +#: templates/connect.php:252 +msgid "If you skip this, that's okay! %1$s will still work just fine." msgstr "" -#: templates/connect.php:254 +#: templates/connect.php:282 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:285 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" -#: templates/connect.php:256 +#: templates/connect.php:287 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" -#: templates/connect.php:258 +#: templates/connect.php:289 msgid "%s's paid features" msgstr "" -#: templates/connect.php:263 +#: templates/connect.php:294 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" -#: templates/connect.php:274, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 -msgid "License key" +#: templates/connect.php:296 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." msgstr "" -#: templates/connect.php:277, templates/forms/license-activation.php:22 -msgid "Can't find your license key?" +#: templates/connect.php:305, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:42 +msgid "License key" msgstr "" -#: templates/connect.php:308 -msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +#: templates/connect.php:308, templates/forms/license-activation.php:22 +msgid "Can't find your license key?" msgstr "" -#: templates/connect.php:340, templates/connect.php:730, templates/forms/deactivation/retry-skip.php:20 +#: templates/connect.php:371, templates/connect.php:695, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" -#: templates/connect.php:343 +#: templates/connect.php:374 msgid "Delegate to Site Admins" msgstr "" -#: templates/connect.php:343 +#: templates/connect.php:374 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" -#: templates/connect.php:368 +#: templates/connect.php:401 msgid "License issues?" msgstr "" -#: templates/connect.php:454 -msgid "What permissions are being granted?" +#: templates/connect.php:425 +msgid "For delivery of security & feature updates, and license management, %s needs to" msgstr "" -#: templates/connect.php:448 -msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management." +#: templates/connect.php:430 +msgid "This will allow %s to" msgstr "" -#: templates/connect.php:450 -msgid "diagnostic data" +#: templates/connect.php:445 +msgid "Don't have a license key?" msgstr "" -#: templates/connect.php:485 +#: templates/connect.php:448 msgid "Have a license key?" msgstr "" -#: templates/connect.php:482 -msgid "Don't have a license key?" +#: templates/connect.php:456 +msgid "Freemius is our licensing and software updates engine" msgstr "" -#: templates/connect.php:493 +#: templates/connect.php:459 msgid "Privacy Policy" msgstr "" -#: templates/connect.php:495 +#: templates/connect.php:461 msgid "License Agreement" msgstr "" -#: templates/connect.php:495 +#: templates/connect.php:461 msgid "Terms of Service" msgstr "" -#: templates/connect.php:896 +#: templates/connect.php:881 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" -#: templates/connect.php:897 +#: templates/connect.php:882 msgctxt "as activating plugin" msgid "Activating" msgstr "" +#: templates/contact.php:78 +msgid "Contact" +msgstr "" + #: templates/debug.php:17 msgctxt "as turned off" msgid "Off" @@ -913,243 +1660,250 @@ msgctxt "as turned on" msgid "On" msgstr "" -#: templates/debug.php:20 +#: templates/debug.php:24 msgid "SDK" msgstr "" -#: templates/debug.php:24 +#: templates/debug.php:28 msgctxt "as code debugging" msgid "Debugging" msgstr "" -#: templates/debug.php:54, templates/debug.php:262, templates/debug.php:388, templates/debug.php:528 +#: templates/debug.php:58, templates/debug.php:279, templates/debug.php:411, templates/debug.php:576 msgid "Actions" msgstr "" -#: templates/debug.php:64 +#: templates/debug.php:68 msgid "Are you sure you want to delete all Freemius data?" msgstr "" -#: templates/debug.php:64 +#: templates/debug.php:68 msgid "Delete All Accounts" msgstr "" -#: templates/debug.php:71 +#: templates/debug.php:75 msgid "Clear API Cache" msgstr "" -#: templates/debug.php:79 +#: templates/debug.php:83 msgid "Clear Updates Transients" msgstr "" -#: templates/debug.php:88 +#: templates/debug.php:92 msgid "Reset Deactivation Snoozing" msgstr "" -#: templates/debug.php:96 +#: templates/debug.php:100 msgid "Sync Data From Server" msgstr "" -#: templates/debug.php:105 +#: templates/debug.php:109 msgid "Migrate Options to Network" msgstr "" -#: templates/debug.php:110 +#: templates/debug.php:114 msgid "Load DB Option" msgstr "" -#: templates/debug.php:113 +#: templates/debug.php:117 msgid "Set DB Option" msgstr "" -#: templates/debug.php:194 +#: templates/debug.php:211 msgid "Key" msgstr "" -#: templates/debug.php:195 +#: templates/debug.php:212 msgid "Value" msgstr "" -#: templates/debug.php:211 +#: templates/debug.php:228 msgctxt "as software development kit versions" msgid "SDK Versions" msgstr "" -#: templates/debug.php:216 +#: templates/debug.php:233 msgid "SDK Path" msgstr "" -#: templates/debug.php:217, templates/debug.php:256 +#: templates/debug.php:234, templates/debug.php:273 msgid "Module Path" msgstr "" -#: templates/debug.php:218 +#: templates/debug.php:235 msgid "Is Active" msgstr "" -#: templates/debug.php:246, templates/debug/plugins-themes-sync.php:35 +#: templates/debug.php:263, templates/debug/plugins-themes-sync.php:35 msgid "Plugins" msgstr "" -#: templates/debug.php:246, templates/debug/plugins-themes-sync.php:56 +#: templates/debug.php:263, templates/debug/plugins-themes-sync.php:56 msgid "Themes" msgstr "" -#: templates/debug.php:251, templates/debug.php:382, templates/debug.php:465, templates/debug/scheduled-crons.php:80 +#: templates/debug.php:268, templates/debug.php:405, templates/debug.php:513, templates/debug/scheduled-crons.php:80 msgid "Slug" msgstr "" -#: templates/debug.php:253, templates/debug.php:464 +#: templates/debug.php:270, templates/debug.php:512 msgid "Title" msgstr "" -#: templates/debug.php:254 +#: templates/debug.php:271 msgctxt "as application program interface" msgid "API" msgstr "" -#: templates/debug.php:255 +#: templates/debug.php:272 msgid "Freemius State" msgstr "" -#: templates/debug.php:259 +#: templates/debug.php:276 msgid "Network Blog" msgstr "" -#: templates/debug.php:260 +#: templates/debug.php:277 msgid "Network User" msgstr "" -#: templates/debug.php:297 +#: templates/debug.php:321 msgctxt "as connection was successful" msgid "Connected" msgstr "" -#: templates/debug.php:298 +#: templates/debug.php:322 msgctxt "as connection blocked" msgid "Blocked" msgstr "" -#: templates/debug.php:334 +#: templates/debug.php:358 msgid "Simulate Trial Promotion" msgstr "" -#: templates/debug.php:346 +#: templates/debug.php:370 msgid "Simulate Network Upgrade" msgstr "" -#. translators: %s: 'plugin' or 'theme' -#: templates/debug.php:371 +#: templates/debug.php:394 msgid "%s Installs" msgstr "" -#: templates/debug.php:373 +#: templates/debug.php:396 msgctxt "like websites" msgid "Sites" msgstr "" -#: templates/debug.php:379, templates/account/partials/site.php:156 +#: templates/debug.php:402, templates/account/partials/site.php:156 msgid "Blog ID" msgstr "" -#: templates/debug.php:384 +#: templates/debug.php:407 msgid "License ID" msgstr "" -#: templates/debug.php:445, templates/debug.php:551, templates/account/partials/addon.php:440 +#: templates/debug.php:493, templates/debug.php:599, templates/account/partials/addon.php:440 msgctxt "verb" msgid "Delete" msgstr "" -#: templates/debug.php:459 +#: templates/debug.php:507 msgid "Add Ons of module %s" msgstr "" -#: templates/debug.php:518 +#: templates/debug.php:566 msgid "Users" msgstr "" -#: templates/debug.php:525 +#: templates/debug.php:573 msgid "Verified" msgstr "" -#: templates/debug.php:567 +#: templates/debug.php:615 msgid "%s Licenses" msgstr "" -#: templates/debug.php:572 +#: templates/debug.php:620 msgid "Plugin ID" msgstr "" -#: templates/debug.php:574 +#: templates/debug.php:622 msgid "Plan ID" msgstr "" -#: templates/debug.php:575 +#: templates/debug.php:623 msgid "Quota" msgstr "" -#: templates/debug.php:576 +#: templates/debug.php:624 msgid "Activated" msgstr "" -#: templates/debug.php:577 +#: templates/debug.php:625 msgid "Blocking" msgstr "" -#: templates/debug.php:578, templates/debug.php:649, templates/debug/logger.php:22 +#: templates/debug.php:626, templates/debug.php:697, templates/debug/logger.php:22 msgid "Type" msgstr "" -#: templates/debug.php:580 +#: templates/debug.php:628 msgctxt "as expiration date" msgid "Expiration" msgstr "" -#: templates/debug.php:608 +#: templates/debug.php:656 msgid "Debug Log" msgstr "" -#: templates/debug.php:612 +#: templates/debug.php:660 msgid "All Types" msgstr "" -#: templates/debug.php:619 +#: templates/debug.php:667 msgid "All Requests" msgstr "" -#: templates/debug.php:624, templates/debug.php:653, templates/debug/logger.php:25 +#: templates/debug.php:672, templates/debug.php:701, templates/debug/logger.php:25 msgid "File" msgstr "" -#: templates/debug.php:625, templates/debug.php:651, templates/debug/logger.php:23 +#: templates/debug.php:673, templates/debug.php:699, templates/debug/logger.php:23 msgid "Function" msgstr "" -#: templates/debug.php:626 +#: templates/debug.php:674 msgid "Process ID" msgstr "" -#: templates/debug.php:627 +#: templates/debug.php:675 msgid "Logger" msgstr "" -#: templates/debug.php:628, templates/debug.php:652, templates/debug/logger.php:24 +#: templates/debug.php:676, templates/debug.php:700, templates/debug/logger.php:24 msgid "Message" msgstr "" -#: templates/debug.php:630 +#: templates/debug.php:678 msgid "Filter" msgstr "" -#: templates/debug.php:638 +#: templates/debug.php:686 msgid "Download" msgstr "" -#: templates/debug.php:654, templates/debug/logger.php:26 +#: templates/debug.php:702, templates/debug/logger.php:26 msgid "Timestamp" msgstr "" +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "" + #: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 msgctxt "milliseconds" msgid "ms" @@ -1163,120 +1917,211 @@ msgstr "" msgid "Requests" msgstr "" -#: includes/managers/class-fs-clone-manager.php:703 +#: includes/managers/class-fs-clone-manager.php:839 msgid "Invalid clone resolution action." msgstr "" -#: includes/managers/class-fs-clone-manager.php:851 +#: includes/managers/class-fs-clone-manager.php:1024 msgid "products" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1039 -msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" +#: includes/managers/class-fs-clone-manager.php:1205 +msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1040 -msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" +#: includes/managers/class-fs-clone-manager.php:1211 +msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1033 -msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." +#: includes/managers/class-fs-clone-manager.php:1212 +msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1066 +#: includes/managers/class-fs-clone-manager.php:1238 msgid "the above-mentioned sites" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1079 +#: includes/managers/class-fs-clone-manager.php:1251 msgid "Is %2$s a duplicate of %4$s?" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1080 +#: includes/managers/class-fs-clone-manager.php:1252 msgid "Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1085 +#: includes/managers/class-fs-clone-manager.php:1257 msgid "Long-Term Duplicate" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1090 +#: includes/managers/class-fs-clone-manager.php:1262 msgid "Duplicate Website" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1096 +#: includes/managers/class-fs-clone-manager.php:1268 msgid "Is %2$s the new home of %4$s?" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1098 +#: includes/managers/class-fs-clone-manager.php:1270 msgid "Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1099, templates/forms/subscription-cancellation.php:52 +#: includes/managers/class-fs-clone-manager.php:1271, templates/forms/subscription-cancellation.php:52 msgid "license" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1099 +#: includes/managers/class-fs-clone-manager.php:1271 msgid "data" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1105 +#: includes/managers/class-fs-clone-manager.php:1277 msgid "Migrate License" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1106 +#: includes/managers/class-fs-clone-manager.php:1278 msgid "Migrate" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1112 +#: includes/managers/class-fs-clone-manager.php:1284 msgid "Is %2$s a new website?" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1113 +#: includes/managers/class-fs-clone-manager.php:1285 msgid "Yes, %2$s is a new and different website that is separate from %4$s." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1115 +#: includes/managers/class-fs-clone-manager.php:1287 msgid "It requires license activation." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1122 +#: includes/managers/class-fs-clone-manager.php:1294 msgid "New Website" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1145 +#: includes/managers/class-fs-clone-manager.php:1319 msgctxt "Clone resolution admin notice products list label" msgid "Products" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1230 +#: includes/managers/class-fs-clone-manager.php:1408 msgid "You marked this website, %s, as a temporary duplicate of %s." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1231 +#: includes/managers/class-fs-clone-manager.php:1409 msgid "You marked this website, %s, as a temporary duplicate of these sites" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1245 +#: includes/managers/class-fs-clone-manager.php:1423 msgid "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first)." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1248 +#: includes/managers/class-fs-clone-manager.php:1426 msgctxt "\"The \", e.g.: \"The plugin\"" msgid "The %s's" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1251 +#: includes/managers/class-fs-clone-manager.php:1429 msgid "The following products'" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1259 +#: includes/managers/class-fs-clone-manager.php:1437 msgid "If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s." msgstr "" -#: includes/managers/class-fs-clone-manager.php:1261 +#: includes/managers/class-fs-clone-manager.php:1439 msgid "activate a license here" msgstr "" +#: includes/managers/class-fs-permission-manager.php:182 +msgid "View Basic Website Info" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:183 +msgid "Homepage URL & title, WP & PHP versions, and site language" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:186 +msgid "To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:198 +msgid "View Basic %s Info" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:201 +msgid "Current %s & SDK versions, and if active or uninstalled" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:252 +msgid "View License Essentials" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:253 +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:263 +msgid "To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:275 +msgid "View %s State" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:278 +msgid "Is active, deactivated, or uninstalled" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:281 +msgid "So you can reuse the license when the %s is no longer active." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:317 +msgid "View Diagnostic Info" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:317, includes/managers/class-fs-permission-manager.php:354 +msgid "optional" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:318 +msgid "WordPress & PHP versions, site language & title" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:321 +msgid "To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:354 +msgid "View Plugins & Themes List" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:355 +msgid "Names, slugs, versions, and if active or not" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:356 +msgid "To ensure compatibility and avoid conflicts with your installed plugins and themes." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:373 +msgid "View Basic Profile Info" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:374 +msgid "Your WordPress user's: first & last name, and email address" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:375 +msgid "Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features." +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:396 +msgid "Newsletter" +msgstr "" + +#: includes/managers/class-fs-permission-manager.php:397 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + #: templates/account/billing.php:22 msgctxt "verb" msgid "Update" @@ -1342,6 +2187,16 @@ msgstr "" msgid "Invoice" msgstr "" +#: templates/connect/permissions-group.php:31, templates/forms/optout.php:26, templates/js/permissions.php:78 +msgctxt "verb" +msgid "Opt Out" +msgstr "" + +#: templates/connect/permissions-group.php:32, templates/js/permissions.php:77 +msgctxt "verb" +msgid "Opt In" +msgstr "" + #: templates/debug/api-calls.php:56 msgid "API" msgstr "" @@ -1438,24 +2293,24 @@ msgstr "" msgid "Apply to become an affiliate" msgstr "" -#: templates/forms/affiliation.php:132 -msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +#: templates/forms/affiliation.php:108 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." msgstr "" -#: templates/forms/affiliation.php:129 -msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +#: templates/forms/affiliation.php:123 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." msgstr "" #: templates/forms/affiliation.php:126 msgid "Your affiliation account was temporarily suspended." msgstr "" -#: templates/forms/affiliation.php:123 -msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +#: templates/forms/affiliation.php:129 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." msgstr "" -#: templates/forms/affiliation.php:108 -msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +#: templates/forms/affiliation.php:132 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." msgstr "" #: templates/forms/affiliation.php:145 @@ -1562,7 +2417,7 @@ msgstr "" msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." msgstr "" -#: templates/forms/affiliation.php:233, templates/forms/resend-key.php:22 +#: templates/forms/affiliation.php:233, templates/forms/resend-key.php:22, templates/account/partials/disconnect-button.php:92 msgid "Cancel" msgstr "" @@ -1586,10 +2441,6 @@ msgstr "" msgid "User key" msgstr "" -#: templates/forms/data-debug-mode.php:162 -msgid "An unknown error has occurred." -msgstr "" - #: templates/forms/email-address-update.php:32 msgid "Email address update" msgstr "" @@ -1643,62 +2494,40 @@ msgstr "" msgid "Update License" msgstr "" -#: templates/forms/license-activation.php:41 -msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." -msgstr "" - -#: templates/forms/license-activation.php:211 -msgid "Associate with the license owner's account." -msgstr "" - -#: templates/forms/optout.php:30 -msgctxt "verb" -msgid "Opt Out" +#: templates/forms/license-activation.php:34 +msgid "The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license." msgstr "" -#: templates/forms/optout.php:31 -msgctxt "verb" -msgid "Opt In" +#: templates/forms/license-activation.php:39 +msgid "Agree & Activate License" msgstr "" -#: templates/forms/optout.php:41 -msgid "We appreciate your help in making the %s better by letting us track some usage data." +#: templates/forms/license-activation.php:204 +msgid "Associate with the license owner's account." msgstr "" #: templates/forms/optout.php:44 -msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgid "Communication" msgstr "" -#: templates/forms/optout.php:45 -msgid "On second thought - I want to continue helping" +#: templates/forms/optout.php:56 +msgid "Stay Connected" msgstr "" -#: templates/forms/optout.php:34 -msgid "Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard." +#: templates/forms/optout.php:61 +msgid "Diagnostic Info" msgstr "" -#: templates/forms/optout.php:36 -msgid "Warning: Opting out will block automatic updates" +#: templates/forms/optout.php:77 +msgid "Keep Sharing" msgstr "" -#: templates/forms/optout.php:37 -msgid "Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won't receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you'll need to check for %1$s updates and install them manually." +#: templates/forms/optout.php:82 +msgid "Extensions" msgstr "" -#: templates/forms/optout.php:39 -msgid "I'd like to keep automatic updates" -msgstr "" - -#: templates/forms/optout.php:49 -msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." -msgstr "" - -#: templates/forms/optout.php:74 -msgid "Plugins & themes tracking" -msgstr "" - -#: templates/forms/optout.php:261 -msgid "Saved" +#: templates/forms/optout.php:104 +msgid "Keep automatic updates" msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:40 @@ -1709,14 +2538,6 @@ msgstr "" msgid " %s to access version %s security & feature updates, and support." msgstr "" -#: templates/forms/premium-versions-upgrade-handler.php:46 -msgid "Renew your license now" -msgstr "" - -#: templates/forms/premium-versions-upgrade-handler.php:47 -msgid "Buy a license now" -msgstr "" - #: templates/forms/premium-versions-upgrade-handler.php:54 msgid "New Version Available" msgstr "" @@ -1730,10 +2551,6 @@ msgstr "" msgid "Send License Key" msgstr "" -#: templates/forms/resend-key.php:24, templates/forms/user-change.php:29 -msgid "Other" -msgstr "" - #: templates/forms/resend-key.php:58 msgid "Enter the email address you've used during the purchase and we will resend you the license key." msgstr "" @@ -1774,21 +2591,14 @@ msgstr "" msgid "Cancel %s & Proceed" msgstr "" -#. translators: %1$s: Number of trial days; %2$s: Plan name; #: templates/forms/trial-start.php:22 msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." msgstr "" -#. translators: %s: Link to freemius.com #: templates/forms/trial-start.php:28 msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." msgstr "" -#: templates/forms/trial-start.php:53 -msgctxt "call to action" -msgid "Start free trial" -msgstr "" - #: templates/forms/user-change.php:26 msgid "By changing the user, you agree to transfer the account ownership to:" msgstr "" @@ -1801,12 +2611,16 @@ msgstr "" msgid "Enter email address" msgstr "" -#: templates/partials/network-activation.php:36 -msgid "Activate license on all pending sites." +#: templates/js/permissions.php:337, templates/js/permissions.php:485 +msgid "Saved" msgstr "" -#: templates/partials/network-activation.php:37 -msgid "Apply on all pending sites." +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" msgstr "" #: templates/partials/network-activation.php:32 @@ -1817,6 +2631,14 @@ msgstr "" msgid "Apply on all sites in the network." msgstr "" +#: templates/partials/network-activation.php:36 +msgid "Activate license on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:37 +msgid "Apply on all pending sites." +msgstr "" + #: templates/partials/network-activation.php:45, templates/partials/network-activation.php:79 msgid "allow" msgstr "" @@ -1833,10 +2655,6 @@ msgstr "" msgid "Click to view full-size screenshot %d" msgstr "" -#: templates/plugin-info/features.php:43 -msgid "Support" -msgstr "" - #: templates/plugin-info/features.php:56 msgid "Unlimited Updates" msgstr "" @@ -1859,35 +2677,43 @@ msgstr "" msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." msgstr "" +#: templates/account/partials/addon.php:190 +msgid "Cancelled" +msgstr "" + #: templates/account/partials/addon.php:200 msgid "No expiration" msgstr "" -#: templates/account/partials/addon.php:190 -msgid "Cancelled" +#: templates/account/partials/disconnect-button.php:74 +msgid "By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s." msgstr "" -#: templates/account/partials/site.php:49, templates/account/partials/site.php:169 -msgid "Opt In" +#: templates/account/partials/disconnect-button.php:78 +msgid "Disconnecting the website will permanently remove %s from your User Dashboard's account." msgstr "" -#: templates/account/partials/site.php:169 -msgid "Opt Out" +#: templates/account/partials/disconnect-button.php:84 +msgid "If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there." +msgstr "" + +#: templates/account/partials/disconnect-button.php:88 +msgid "Are you sure you would like to proceed with the disconnection?" msgstr "" -#: templates/account/partials/site.php:189 +#: templates/account/partials/site.php:190 msgid "Owner Name" msgstr "" -#: templates/account/partials/site.php:201 +#: templates/account/partials/site.php:202 msgid "Owner Email" msgstr "" -#: templates/account/partials/site.php:213 +#: templates/account/partials/site.php:214 msgid "Owner ID" msgstr "" -#: templates/account/partials/site.php:286 +#: templates/account/partials/site.php:288 msgid "Subscription" msgstr "" diff --git a/freemius/package.json b/freemius/package.json deleted file mode 100644 index 8f7dd3f..0000000 --- a/freemius/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "freemius-wordpress-sdk", - "description": "Monetization, analytics, and marketing automation platform for plugin & theme developers.", - "author": "Freemius, Inc.", - "license": "GPL-3.0", - "homepage": "https://freemius.com", - "version": "2.4.3", - "main": "gulpfile.js", - "dependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "prune": "rimraf .codeclimate.yml .git .github .gitignore .travis.yml gulpfile.js composer.json" - }, - "repository": "Freemius/wordpress-sdk.git", - "devDependencies": { - "gulp": "^3.9.1", - "gulp-clean": "^0.3.2", - "gulp-fs": "0.0.2", - "gulp-gettext": "^0.3.0", - "gulp-path": "^3.0.3", - "gulp-pofill": "^1.0.0", - "gulp-rename": "^1.2.2", - "gulp-sort": "^2.0.0", - "gulp-transifex": "^0.1.17", - "gulp-wp-pot": "^1.3.5" - } -} diff --git a/freemius/require.php b/freemius/require.php index fcbb880..baf28d9 100644 --- a/freemius/require.php +++ b/freemius/require.php @@ -21,6 +21,8 @@ // require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-abstract-manager.php'; require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-option-manager.php'; require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-gdpr-manager.php'; + require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-clone-manager.php'; + require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-permission-manager.php'; require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-cache-manager.php'; require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-admin-notice-manager.php'; require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-admin-menu-manager.php'; diff --git a/freemius/start.php b/freemius/start.php index 89bbfb0..d3f410c 100644 --- a/freemius/start.php +++ b/freemius/start.php @@ -15,7 +15,7 @@ * * @var string */ - $this_sdk_version = '2.4.5'; + $this_sdk_version = '2.5.3'; #region SDK Selection Logic -------------------------------------------------------------------- @@ -68,7 +68,11 @@ if ( ! isset( $fs_active_plugins ) ) { // Load all Freemius powered active plugins. - $fs_active_plugins = get_option( 'fs_active_plugins', new stdClass() ); + $fs_active_plugins = get_option( 'fs_active_plugins' ); + + if ( ! is_object( $fs_active_plugins ) ) { + $fs_active_plugins = new stdClass(); + } if ( ! isset( $fs_active_plugins->plugins ) ) { $fs_active_plugins->plugins = array(); diff --git a/freemius/templates/account.php b/freemius/templates/account.php index 0921366..7f4bca6 100644 --- a/freemius/templates/account.php +++ b/freemius/templates/account.php @@ -46,6 +46,7 @@ $site = $fs->get_site(); $name = $user->get_name(); $license = $fs->_get_license(); + $is_license_foreign = ( is_object( $license ) && $user->id != $license->user_id ); $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); $subscription = ( is_object( $license ) ? @@ -58,10 +59,19 @@ $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial ); $trial_plan = $fs->get_trial_plan(); + $is_plan_change_supported = ( + ! $fs->is_single_plan() && + ! $fs->apply_filters( 'hide_plan_change', false ) + ); + if ( $has_paid_plan ) { $fs->_add_license_activation_dialog_box(); } + if ( $fs->should_handle_user_change() ) { + $fs->_add_email_address_update_dialog_box(); + } + $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ? $fs->get_installs_ids_with_foreign_licenses() : array(); @@ -86,12 +96,15 @@ ) ); } - $payments = $fs->_fetch_payments(); + $show_billing = ( ! $is_whitelabeled && ! $fs->apply_filters( 'hide_billing_and_payments_info', false ) ); + if ( $show_billing ) { + $payments = $fs->_fetch_payments(); - $show_billing = ( ! $is_whitelabeled && is_array( $payments ) && 0 < count( $payments ) ); + $show_billing = ( is_array( $payments ) && 0 < count( $payments ) ); + } - $has_tabs = $fs->_add_tabs_before_content(); + $has_tabs = $fs->_add_tabs_before_content(); if ( $has_tabs ) { $query_params['tabs'] = 'true'; @@ -140,6 +153,7 @@ $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); $view_params = array( 'freemius' => $fs, + 'user' => $fs->get_user(), 'license' => $license, 'site' => $site_info, 'install' => $install, @@ -234,6 +248,14 @@ $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); + + $available_license = ( $fs->is_free_plan() && ! fs_is_network_admin() ) ? + $fs->_get_available_premium_license( $site->is_localhost() ) : + null; + + $available_license_paid_plan = is_object( $available_license ) ? + $fs->_get_plan_by_id( $available_license->plan_id ) : + null; ?>

    apply_filters( 'hide_account_tabs', false ) ) : ?> @@ -274,24 +296,19 @@ class="nav-tab">
  •  • 
  • -
  • +
  •  • 
  • -
    - - - -
    + $fs, + 'license' => $available_license, + 'license_paid_plan' => $available_license_paid_plan, + ); + fs_require_template( 'account/partials/disconnect-button.php', $view_params ); ?>
  •  • 
  • @@ -330,7 +347,7 @@ class="dashicons dashicons-admin-network">
  •  • 
  • - is_single_plan() ) : ?> +
  • @@ -366,7 +383,7 @@ class="dashicons dashicons-image-rotate"> 'user_name', 'title' => fs_text_inline( 'Name', 'name', $slug ), @@ -435,7 +452,7 @@ class="dashicons dashicons-image-rotate"> $fs->get_plugin_version() ); - if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) { + if ( ! fs_is_network_admin() && $is_premium ) { $profile[] = array( 'id' => 'beta_program', 'title' => '', @@ -537,15 +554,13 @@ class="fs-tag "> @@ -839,7 +874,7 @@ class="fs-tag fs-can_use_premium_code() ? 'success' : 'warn' ?>" $VARS['id'] ); + $view_params = array( 'id' => $VARS['id'], 'payments' => $payments ); fs_require_once_template( 'account/billing.php', $view_params ); fs_require_once_template( 'account/payments.php', $view_params ); } @@ -919,7 +954,7 @@ class="fs-tag fs-can_use_premium_code() ? 'success' : 'warn' ?>" if ( ! isChecked || confirm( '' ) ) { $.ajax( { - url : ajaxurl, + url : , method: 'POST', data : { action : 'get_ajax_action( 'set_beta_mode' ) ?>', @@ -1062,7 +1097,7 @@ class="fs-tag fs-can_use_premium_code() ? 'success' : 'warn' ?>" var $toggleLink = $( this ); $.ajax( { - url : ajaxurl, + url : , method: 'POST', data : { action : 'get_ajax_action( 'toggle_whitelabel_mode' ) ?>', @@ -1095,4 +1130,4 @@ class="fs-tag fs-can_use_premium_code() ? 'success' : 'warn' ?>" 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file + fs_require_template( 'powered-by.php', $params ); diff --git a/freemius/templates/account/billing.php b/freemius/templates/account/billing.php index a4de409..5140f86 100644 --- a/freemius/templates/account/billing.php +++ b/freemius/templates/account/billing.php @@ -377,7 +377,7 @@ class="button">, method : 'POST', data : { action : 'get_ajax_action( 'update_billing' ) ?>', diff --git a/freemius/templates/account/partials/disconnect-button.php b/freemius/templates/account/partials/disconnect-button.php new file mode 100644 index 0000000..0f4725a --- /dev/null +++ b/freemius/templates/account/partials/disconnect-button.php @@ -0,0 +1,104 @@ +_get_subscription( $license->id ) : + null; + + $has_active_subscription = ( + is_object( $license_subscription ) && + $license_subscription->is_active() + ); + + $button_id = "fs_disconnect_button_{$fs->get_id()}"; + + $website_link = sprintf( '%s', fs_strip_url_protocol( untrailingslashit( Freemius::get_unfiltered_site_url() ) ) ); +?> + + +
    + + + + + + esc_html_inline( 'Disconnect', 'disconnect' ) ?> + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/site.php b/freemius/templates/account/partials/site.php index fd9b237..a60f5d0 100644 --- a/freemius/templates/account/partials/site.php +++ b/freemius/templates/account/partials/site.php @@ -23,7 +23,7 @@ $is_whitelabeled = $fs->is_whitelabeled(); $has_paid_plan = $fs->has_paid_plan(); $is_premium = $fs->is_premium(); - $main_user = $fs->get_user(); + $main_user = $VARS['user']; $blog_id = $site['blog_id']; $install = $VARS['install']; @@ -32,7 +32,7 @@ $trial_plan = $fs->get_trial_plan(); $free_text = fs_text_inline( 'Free', 'free', $slug ); - if ( $is_whitelabeled && $fs->is_delegated_connection( $blog_id ) ) { + if ( $is_whitelabeled && is_object( $install ) && $fs->is_delegated_connection( $blog_id ) ) { $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id ); } ?> @@ -159,14 +159,15 @@ class="dashicons dashicons-arrow-right-alt2">
    license_id ) ) : ?> + is_homepage_url_tracking_allowed( $blog_id ) ?>
    id}", ':' ) ) ?> - + } ?>" data-is-disconnected="">
    @@ -174,7 +175,7 @@ class="dashicons dashicons-arrow-right-alt2">
    - user_id != $main_user->id ) : ?> + user_id != $main_user->id ) : ?> : - public_key ) ?> + public_key ) ?> + diff --git a/freemius/templates/account/payments.php b/freemius/templates/account/payments.php index fd54c9b..c2fb1a7 100644 --- a/freemius/templates/account/payments.php +++ b/freemius/templates/account/payments.php @@ -10,19 +10,19 @@ exit; } - /** - * @var array $VARS - * @var Freemius $fs - */ - $fs = freemius( $VARS['id'] ); + /** + * @var array $VARS + * @var Freemius $fs + */ + $fs = freemius( $VARS['id'] ); - $slug = $fs->get_slug(); + /** + * @var FS_Payment[] $payments + */ + $payments = $VARS['payments']; - $payments = $fs->_fetch_payments(); - - $show_payments = ( is_array( $payments ) && 0 < count( $payments ) ); + $slug = $fs->get_slug(); - if ( $show_payments ) : ?>
    @@ -56,4 +56,3 @@ class="button button-small"
    "> - +
    diff --git a/freemius/templates/auto-installation.php b/freemius/templates/auto-installation.php index 6b8183c..23bbf41 100644 --- a/freemius/templates/auto-installation.php +++ b/freemius/templates/auto-installation.php @@ -164,7 +164,7 @@ class="button button-primary">, method : 'POST', data : data, success: function (resultObj) { diff --git a/freemius/templates/clone-resolution-js.php b/freemius/templates/clone-resolution-js.php new file mode 100644 index 0000000..e8f4e9b --- /dev/null +++ b/freemius/templates/clone-resolution-js.php @@ -0,0 +1,79 @@ + + \ No newline at end of file diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 9cc7cab..8244aad 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -25,6 +25,15 @@ $fs->_enqueue_connect_essentials(); + /** + * Enqueueing the styles in `_enqueue_connect_essentials()` is too late, as we need them in the HEADER. Therefore, inject the styles inline to avoid FOUC. + * + * @author Vova Feldman (@svovaf) + */ + echo "\n"; + $current_user = Freemius::_get_current_wp_user(); $first_name = $current_user->user_firstname; @@ -32,7 +41,7 @@ $first_name = $current_user->nickname; } - $site_url = get_site_url(); + $site_url = Freemius::get_unfiltered_site_url(); $protocol_pos = strpos( $site_url, '://' ); if ( false !== $protocol_pos ) { $site_url = substr( $site_url, $protocol_pos + 3 ); @@ -118,6 +127,14 @@ if ( is_null( $is_gdpr_required ) ) { $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); } + + $activation_state = array( + 'is_license_activation' => $require_license_key, + 'is_pending_activation' => $is_pending_activation, + 'is_gdpr_required' => $is_gdpr_required, + 'is_network_level_activation' => $is_network_level_activation, + 'is_dialog' => $is_optin_dialog, + ); ?> @@ -138,26 +155,54 @@ * @author Vova Feldman * @since 2.3.2 */ - $fs->do_action( 'connect/before' ); + $fs->do_action( 'connect/before', $activation_state ); ?>
    -
    - - - $fs->get_id() ); - fs_require_once_template( 'plugin-icon.php', $vars ); - ?> - - -
    +
    + + $fs->get_id(), + 'size' => $size, + ); + + fs_require_once_template( 'plugin-icon.php', $vars ); + ?> + +
    +
    + do_action( 'connect/before_message', $activation_state ) ?> + -

    +

    apply_filters( 'connect_error_esc_html', esc_html( $error ) ) ?>

    + is_plugin_update() ) { + echo $fs->apply_filters( 'connect-header', sprintf( + '

    %s

    ', + esc_html( fs_text_inline( 'Never miss an important update', 'connect-header' ) ) + ) ); + } else { + echo $fs->apply_filters( 'connect-header_on-update', sprintf( + '

    %s

    ', + sprintf( + esc_html( + /* translators: %1$s: plugin name (e.g., "Awesome Plugin"); %2$s: version (e.g., "1.2.3") */ + fs_text_inline('Thank you for updating to %1$s v%2$s!', 'connect-header_on-update' ) + ), + esc_html( $fs->get_plugin_name() ), + $fs->get_plugin_version() + ) + ) ); + } + } + ?>

    is_enable_anonymous() $message = $fs->apply_filters( 'pending_activation_message', sprintf( /* translators: %s: name (e.g. Thanks John!) */ fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . - fs_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message', $slug ), + fs_text_inline( 'You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.', 'pending-activation-message', $slug ), $first_name, '' . $fs->get_plugin_name() . '', '' . $current_user->user_email . '', - fs_text_inline( 'complete the install', 'complete-the-install', $slug ) + fs_text_inline( 'complete the opt-in', 'complete-the-opt-in', $slug ) ) ); } else if ( $require_license_key ) { - $button_label = $is_network_upgrade_mode ? - fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) : - fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); + $button_label = fs_text_inline( 'Activate License', 'activate-license', $slug ); $message = $fs->apply_filters( 'connect-message_on-premium', @@ -186,21 +229,32 @@ class="wrapis_enable_anonymous() $fs->get_plugin_name() ); } else { - $filter = 'connect_message'; - $default_optin_message = $is_gdpr_required ? - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) : - fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug); + $filter = 'connect_message'; - if ( $fs->is_plugin_update() ) { + if ( ! $fs->is_plugin_update() ) { + $default_optin_message = esc_html( sprintf( ( $is_gdpr_required ? + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ) : + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( 'Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ) ), $fs->get_module_label( true ) ) ); + } else { // If Freemius was added on a plugin update, set different // opt-in message. - $default_optin_message = $is_gdpr_required ? - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ) : - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ); - // If user customized the opt-in message on update, use + /* translators: %s: module type (plugin, theme, or add-on) */ + $default_optin_message = esc_html( sprintf( fs_text_inline( 'We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message_on-update_why' ), $fs->get_module_label( true ) ) ); + + $default_optin_message .= '

    ' . esc_html( $is_gdpr_required ? + fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) : + fs_text_inline( 'Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) ); + + if ( $fs->is_enable_anonymous() ) { + $default_optin_message .= ' ' . esc_html( fs_text_inline( 'If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update_skip', $slug ) ); + } + + // If user customized the opt-in message on update, use // that message. Otherwise, fallback to regular opt-in - // custom message if exist. + // custom message if exists. if ( $fs->has_filter( 'connect_message_on_update' ) ) { $filter = 'connect_message_on_update'; } @@ -208,17 +262,12 @@ class="wrapis_enable_anonymous() $message = $fs->apply_filters( $filter, - ($is_network_upgrade_mode ? - '' : - /* translators: %s: name (e.g. Hey John,) */ - $hey_x_text . '
    ' - ) . sprintf( - esc_html( $default_optin_message ), + $default_optin_message, '' . esc_html( $fs->get_plugin_name() ) . '', '' . $current_user->user_login . '', '' . $site_url . '', - $freemius_link + $freemius_link ), $first_name, $fs->get_plugin_name(), @@ -266,7 +315,7 @@ class="wrapis_enable_anonymous() * @author Vova Feldman * @since 2.1.2 */ - $fs->do_action( 'connect/after_license_input' ); + $fs->do_action( 'connect/after_license_input', $activation_state ); ?> is_enable_anonymous() echo fs_get_template( 'partials/network-activation.php', $vars ); ?> + + do_action( 'connect/after_message', $activation_state ) ?>

    + do_action( 'connect/before_actions', $activation_state ) ?> + is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> @@ -326,6 +379,7 @@ class="button button-secondary" tabindex="2">get_unique_affix() ?>_activate_existing"> get_public_key() ) ?> + @@ -336,6 +390,7 @@ class="button button-secondary" tabindex="2">" value=""> +
    'dashicons dashicons-admin-users', - 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ), - 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ), - 'priority' => 5, - ); - } - - $permissions['site'] = array( - 'icon-class' => 'dashicons dashicons-admin-settings', - 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ), - 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), - 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ), - 'priority' => 10, - ); - - if ( ! $require_license_key ) { - $permissions['notices'] = array( - 'icon-class' => 'dashicons dashicons-testimonial', - 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), - 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), - 'priority' => 13, - ); - } - - $permissions['events'] = array( - 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), - 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ), - 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), - 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ), - 'priority' => 20, - ); - // Add newsletter permissions if enabled. if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { - $permissions['newsletter'] = array( - 'icon-class' => 'dashicons dashicons-email-alt', - 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), - 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), - 'priority' => 15, - ); + $permissions[] = $permission_manager->get_newsletter_permission(); } - $permissions['extensions'] = array( - 'icon-class' => 'dashicons dashicons-menu', - 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ), - 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ), - 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ), - 'priority' => 25, - 'optional' => true, - 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key ) + $permissions = $permission_manager->get_permissions( + $require_license_key, + $permissions ); - // Allow filtering of the permissions list. - $permissions = $fs->apply_filters( 'permission_list', $permissions ); - - // Sort by priority. - uasort( $permissions, 'fs_sort_by_priority' ); - if ( ! empty( $permissions ) ) : ?>
    - -

    get_module_label( true ), - sprintf('%s', fs_esc_html_inline('diagnostic data', 'send-data')), - 'freemius.com ' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '' - ) ?>

    - - + +
    ', + sprintf( '%s', $fs->get_plugin_title() ) + ) ?> + + ', + sprintf( '%s', $fs->get_plugin_title() ) + ) ?>
      $permission ) : ?> -
    • - - -
      -
      -
      - - -
      - class="fs-tooltip-trigger"> - -

      -
      -
    • - -
    + foreach ( $permissions as $permission ) { + $permission_manager->render_permission( $permission ); + } + ?>
    @@ -463,7 +451,10 @@ class="fs-permission fs-">

    +
    + Powered by Freemius get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) ?> +   -     -   @@ -477,7 +468,7 @@ class="fs-permission fs-"> * @author Vova Feldman * @since 2.3.2 */ - $fs->do_action( 'connect/after' ); + $fs->do_action( 'connect/after', $activation_state ); if ( $is_optin_dialog ) { ?>
    @@ -531,13 +522,12 @@ class="fs-permission fs-"> // Reset loading mode. $primaryCta.html(primaryCtaLabel); $primaryCta.prop('disabled', false); - $(document.body).css({'cursor': 'auto'}); - $('.fs-loading').removeClass('fs-loading'); + $( '.fs-loading' ).removeClass( 'fs-loading' ); console.log('resetLoadingMode - Primary button was enabled'); }, setLoadingMode = function () { - $(document.body).css({'cursor': 'wait'}); + $( document.body ).addClass( 'fs-loading' ); }; $('.fs-actions .button').on('click', function () { @@ -601,6 +591,11 @@ class="fs-permission fs-"> updatePrimaryCtaText( actionType ); }); + $sitesListContainer.delegate( 'td:not(:first-child)', 'click', function() { + // If a site row is clicked, trigger a click on the checkbox. + $( this ).parent().find( 'td:first-child input' ).click(); + } ); + $sitesListContainer.delegate( '.action', 'click', function( evt ) { var $this = $( evt.target ); if ( $this.hasClass( 'selected' ) ) { @@ -707,15 +702,25 @@ function updatePrimaryCtaText( actionType ) { var ajaxOptin = ( requireLicenseKey || isNetworkActive ); $form.on('submit', function () { - var $extensionsPermission = $('#fs-permission-extensions .fs-switch'), - isExtensionsTrackingAllowed = ($extensionsPermission.length > 0) ? - $extensionsPermission.hasClass('fs-on') : + var $extensionsPermission = $( '#fs_permission_extensions .fs-switch' ), + isExtensionsTrackingAllowed = ( $extensionsPermission.length > 0 ) ? + $extensionsPermission.hasClass( 'fs-on' ) : null; - if (null === isExtensionsTrackingAllowed) { - $('input[name=is_extensions_tracking_allowed]').remove(); + var $diagnosticPermission = $( '#fs_permission_diagnostic .fs-switch' ), + isDiagnosticTrackingAllowed = ( $diagnosticPermission.length > 0 ) ? + $diagnosticPermission.hasClass( 'fs-on' ) : + null; + + if ( null === isExtensionsTrackingAllowed ) { + $( 'input[name=is_extensions_tracking_allowed]' ).remove(); } else { - $('input[name=is_extensions_tracking_allowed]').val(isExtensionsTrackingAllowed ? 1 : 0); + $( 'input[name=is_extensions_tracking_allowed]' ).val( isExtensionsTrackingAllowed ? 1 : 0 ); + } + + // We are not showing switch to enable/disable diagnostic tracking while activating free version. So, don't remove hidden `is_diagnostic_tracking_allowed` element from DOM and change the value only if switch is available. + if ( null !== isDiagnosticTrackingAllowed ) { + $( 'input[name=is_diagnostic_tracking_allowed]' ).val( isDiagnosticTrackingAllowed ? 1 : 0 ); } /** @@ -772,6 +777,8 @@ function updatePrimaryCtaText( actionType ) { data.is_marketing_allowed = isMarketingAllowed; data.is_extensions_tracking_allowed = isExtensionsTrackingAllowed; + + data.is_diagnostic_tracking_allowed = isDiagnosticTrackingAllowed; } $marketingOptin.removeClass( 'error' ); @@ -794,7 +801,6 @@ function updatePrimaryCtaText( actionType ) { url : $this.find( '.url' ).val(), title : $this.find( '.title' ).val(), language: $this.find( '.language' ).val(), - charset : $this.find( '.charset' ).val(), blog_id : $this.find( '.blog-id' ).find( 'span' ).text() }; @@ -822,7 +828,7 @@ function updatePrimaryCtaText( actionType ) { * @since 1.2.1.5 */ $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : data, success: function (result) { @@ -858,6 +864,15 @@ function updatePrimaryCtaText( actionType ) { return true; }); + $( '#fs_connect .fs-permissions .fs-switch' ).on( 'click', function () { + $( this ) + .toggleClass( 'fs-on' ) + .toggleClass( 'fs-off' ); + + $( this ).closest( '.fs-permission' ) + .toggleClass( 'fs-disabled' ); + }); + $primaryCta.on('click', function () { console.log('Primary button was clicked'); @@ -874,12 +889,6 @@ function updatePrimaryCtaText( actionType ) { return false; }); - $( '.fs-switch' ).click( function () { - $(this) - .toggleClass( 'fs-on' ) - .toggleClass( 'fs-off' ); - }); - if (requireLicenseKey) { /** * Submit license key on enter. @@ -1005,7 +1014,7 @@ function updatePrimaryCtaText( actionType ) { $primaryCta.html('...'); $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>', @@ -1035,4 +1044,4 @@ function updatePrimaryCtaText( actionType ) { //endregion })(jQuery); - \ No newline at end of file + diff --git a/freemius/templates/connect/index.php b/freemius/templates/connect/index.php new file mode 100644 index 0000000..0316c6a --- /dev/null +++ b/freemius/templates/connect/index.php @@ -0,0 +1,3 @@ + +
  • + + +
    +
    +
    + + +
    + class="fs-tooltip-trigger"> + +

    +
    +
  • \ No newline at end of file diff --git a/freemius/templates/connect/permissions-group.php b/freemius/templates/connect/permissions-group.php new file mode 100644 index 0000000..3abcc43 --- /dev/null +++ b/freemius/templates/connect/permissions-group.php @@ -0,0 +1,72 @@ +get_text_x_inline( 'Opt Out', 'verb', 'opt-out' ); + $opt_in_text = $fs->get_text_x_inline( 'Opt In', 'verb', 'opt-in' ); + + if ( empty( $permission_group[ 'prompt' ] ) ) { + $is_enabled = false; + + foreach ( $permission_group[ 'permissions' ] as $permission ) { + if ( true === $permission[ 'default' ] ) { + // Even if one of the permissions is on, treat as if the entire group is on. + $is_enabled = true; + break; + } + } + } else { + $is_enabled = ( isset( $permission_group['is_enabled'] ) && true === $permission_group['is_enabled'] ); + } +?> +
    +
    +
    + + +
    +

    +
      + render_permission( $permission ); + } + ?> +
    +
    \ No newline at end of file diff --git a/freemius/templates/contact.php b/freemius/templates/contact.php index bba1018..5fdd6e3 100644 --- a/freemius/templates/contact.php +++ b/freemius/templates/contact.php @@ -69,7 +69,7 @@ $query_params = array_merge( $_GET, array_merge( $context_params, array( 'plugin_version' => $fs->get_plugin_version(), 'wp_login_url' => wp_login_url(), - 'site_url' => get_site_url(), + 'site_url' => Freemius::get_unfiltered_site_url(), // 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", ) ) ); diff --git a/freemius/templates/debug.php b/freemius/templates/debug.php index 4dbf3a3..1139fe8 100644 --- a/freemius/templates/debug.php +++ b/freemius/templates/debug.php @@ -16,6 +16,10 @@ $off_text = fs_text_x_inline( 'Off', 'as turned off' ); $on_text = fs_text_x_inline( 'On', 'as turned on' ); + + $has_any_active_clone = false; + + $is_multisite = is_multisite(); ?>

    newest->version ?>

    @@ -35,7 +39,7 @@ .toggleClass( 'fs-on' ) .toggleClass( 'fs-off' ); - $.post( ajaxurl, { + $.post( , { action: 'fs_toggle_debug_mode', // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline. _wpnonce : , @@ -79,6 +83,16 @@ + + + +
    + + + +
    + +
    @@ -102,6 +116,15 @@ + + + Resolve Clone(s) + @@ -111,7 +134,7 @@ var optionName = prompt('Please enter the option name:'); if (optionName) { - $.post(ajaxurl, { + $.post(, { action : 'fs_get_db_option', // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline. _wpnonce : , @@ -132,7 +155,7 @@ var optionValue = prompt('Please enter the option value:'); if (optionValue) { - $.post(ajaxurl, { + $.post(, { action : 'fs_set_db_option', // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline. _wpnonce : , @@ -175,6 +198,10 @@ 'key' => 'WP_FS__DIR', 'val' => WP_FS__DIR, ), + array( + 'key' => 'wp_using_ext_object_cache()', + 'val' => wp_using_ext_object_cache() ? 'true' : 'false', + ), ) ?>
    @@ -229,7 +256,7 @@ WP_FS__MODULE_TYPE_THEME ); ?> - + get_option( $module_type . 's' ), FS_Plugin::get_class_name() ) ?> 0 ) : ?> @@ -245,7 +272,7 @@ - + @@ -268,7 +295,14 @@ } } ?> - id ) : null ?> + id ); + + $active_modules_by_id[ $data->id ] = true; + } + ?> has_api_connectivity() && $fs->is_on() ) { echo ' style="background: #E6FFE6; font-weight: bold"'; @@ -298,7 +332,7 @@ } ?> file ?> public_key ?> - + 0 ) : ?>

    + $sites ) : ?> - + blog_id : + null; + + if ( is_null( $site_url ) || $is_multisite ) { + $site_url = Freemius::get_unfiltered_site_url( + $blog_id, + true, + true + ); + } + + $is_active_clone = ( $site->is_clone( $site_url ) && isset( $active_modules_by_id[ $site->plugin_id ] ) ); + + if ( $is_active_clone ) { + $has_any_active_clone = true; + } + ?> - id ?> + + id ?> + + + + - blog_id ?> + url ) ?> @@ -484,8 +539,15 @@ * @var FS_User[] $users */ $users = $VARS['users']; + $user_ids_map = array(); $users_with_developer_license_by_id = array(); + if ( is_array( $users ) && ! empty( $users ) ) { + foreach ( $users as $user ) { + $user_ids_map[ $user->id ] = true; + } + } + foreach ( $module_types as $module_type ) { /** * @var FS_Plugin_License[] $licenses @@ -578,7 +640,7 @@ is_block_features ? 'Blocking' : 'Flexible' ?> is_whitelabeled ? 'Whitelabeled' : 'Normal' ?> is_whitelabeled ? + echo ( $license->is_whitelabeled || ! isset( $user_ids_map[ $license->user_id ] ) ) ? $license->get_html_escaped_masked_secret_key() : esc_html( $license->secret_key ); ?> @@ -726,7 +788,7 @@ class="dashicons dashicons-download"> , { action : 'fs_get_debug_log', // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline. _wpnonce : , diff --git a/freemius/templates/firewall-issues-js.php b/freemius/templates/firewall-issues-js.php index 6a3f2a5..2b12a15 100644 --- a/freemius/templates/firewall-issues-js.php +++ b/freemius/templates/firewall-issues-js.php @@ -48,8 +48,7 @@ $( this ).css({'cursor': 'wait'}); - // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php - $.post( ajaxurl, data, function( response ) { + $.post( , data, function( response ) { if ( 1 == response ) { // Refresh page on success. location.reload(); diff --git a/freemius/templates/forms/affiliation.php b/freemius/templates/forms/affiliation.php index fe6d694..0085065 100644 --- a/freemius/templates/forms/affiliation.php +++ b/freemius/templates/forms/affiliation.php @@ -13,8 +13,10 @@ /** * @var array $VARS * @var Freemius $fs + * @var string $plugin_title */ - $fs = freemius( $VARS['id'] ); + $fs = freemius( $VARS['id'] ); + $plugin_title = $VARS['plugin_title']; $slug = $fs->get_slug(); @@ -22,7 +24,6 @@ $affiliate = $fs->get_affiliate(); $affiliate_terms = $fs->get_affiliate_terms(); - $plugin_title = $fs->get_plugin_title(); $module_type = $fs->is_plugin() ? WP_FS__MODULE_TYPE_PLUGIN : WP_FS__MODULE_TYPE_THEME; @@ -45,7 +46,7 @@ $promotion_method_mobile_apps = false; $statistics_information = false; $promotion_method_description = false; - $members_dashboard_login_url = 'https://members.freemius.com/login/'; + $members_dashboard_login_url = 'https://users.freemius.com/login'; $affiliate_application_data = $fs->get_affiliate_application_data(); @@ -71,7 +72,7 @@ $current_user = Freemius::_get_current_wp_user(); $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); $email_address = $current_user->user_email; - $domain = fs_strip_url_protocol( get_site_url() ); + $domain = Freemius::get_unfiltered_site_url( null, true ); } $affiliate_tracking = 30; @@ -365,7 +366,7 @@ } $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'submit_affiliate_application' ) ?>', @@ -506,4 +507,3 @@ function showErrorMessage(message) { 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params ); -?> \ No newline at end of file diff --git a/freemius/templates/forms/data-debug-mode.php b/freemius/templates/forms/data-debug-mode.php index 44cb442..24c3648 100644 --- a/freemius/templates/forms/data-debug-mode.php +++ b/freemius/templates/forms/data-debug-mode.php @@ -140,7 +140,7 @@ function setDeveloperLicenseDebugMode( licenseOrUserKey ) { }; $.ajax( { - url : ajaxurl, + url : , method : 'POST', data : data, beforeSend: function () { diff --git a/freemius/templates/forms/deactivation/form.php b/freemius/templates/forms/deactivation/form.php index 0bdcae0..ec9fcf1 100644 --- a/freemius/templates/forms/deactivation/form.php +++ b/freemius/templates/forms/deactivation/form.php @@ -24,6 +24,7 @@ $anonymous_feedback_checkbox_html = ''; $reasons_list_items_html = ''; + $snooze_select_html = ''; if ( $show_deactivation_feedback_form ) { $reasons = $VARS['reasons']; @@ -64,6 +65,41 @@ fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) ); } + + $snooze_periods = array( + array( + 'increment' => fs_text_inline( 'hour', $slug ), + 'quantity' => number_format_i18n(1), + 'value' => 6 * WP_FS__TIME_10_MIN_IN_SEC, + ), + array( + 'increment' => fs_text_inline( 'hours', $slug ), + 'quantity' => number_format_i18n(24), + 'value' => WP_FS__TIME_24_HOURS_IN_SEC, + ), + array( + 'increment' => fs_text_inline( 'days', $slug ), + 'quantity' => number_format_i18n(7), + 'value' => WP_FS__TIME_WEEK_IN_SEC, + ), + array( + 'increment' => fs_text_inline( 'days', $slug ), + 'quantity' => number_format_i18n(30), + 'value' => 30 * WP_FS__TIME_24_HOURS_IN_SEC, + ), + ); + + $snooze_select_html = ''; } // Aliases. @@ -71,6 +107,13 @@ $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); + $submit_deactivate_text = sprintf( + fs_text_inline( 'Submit & %s', 'deactivation-modal-button-submit', $slug ), + $fs->is_plugin() ? + $deactivate_text : + sprintf( $activate_x_text, $theme_text ) + ); + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { @@ -92,6 +135,7 @@ + '

    ' + ' ' @@ -101,6 +145,7 @@ selectedReasonID = false, redirectLink = '', $anonymousFeedback = $modal.find( '.anonymous-feedback-label' ), + $feedbackSnooze = $modal.find( '.feedback-from-snooze-label' ), isAnonymous = , otherReasonID = , dontShareDataReasonID = , @@ -135,7 +180,7 @@ ?> $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'cancel_subscription_or_trial' ) ?>', @@ -230,10 +275,6 @@ function registerEventHandlers() { } ?> $modal.on('input propertychange', '.reason-input input', function () { - if (!isOtherReasonSelected()) { - return; - } - var reason = $(this).val().trim(); /** @@ -241,9 +282,12 @@ function registerEventHandlers() { * to change the message color back to default. */ if (reason.length > 0) { - $('.message').removeClass('error-message'); - enableDeactivateButton(); - } + $('.message').removeClass('error-message'); + } + + toggleDeactivationButtonPrimary( reason.length > 0 ); + + changeDeactivateButtonText(); }); $modal.on('blur', '.reason-input input', function () { @@ -260,8 +304,8 @@ function registerEventHandlers() { */ if (0 === $userReason.val().trim().length) { $('.message').addClass('error-message'); - disableDeactivateButton(); - } + changeDeactivateButtonText(); + } }, 150); }); @@ -276,15 +320,34 @@ function registerEventHandlers() { var _this = $(this); if (_this.hasClass('allow-deactivate')) { - var $radio = $modal.find('input[type="radio"]:checked'); + var + $radio = $modal.find('input[type="radio"]:checked'), + isReasonSelected = (0 < $radio.length), + userReason = ''; + + if ( isReasonSelected ) { + var $selectedReason = $radio.parents('li:first'), + $reasonInput = $selectedReason.find('textarea, input[type="text"]'); - if (0 === $radio.length) { + if ( 0 < $reasonInput.length ) { + userReason = $reasonInput.val().trim(); + } + } + + if ( otherReasonID == selectedReasonID && '' === userReason ) { + // If the 'Other' is selected and a reason is not provided (aka it's empty), treat it as if a reason wasn't selected at all. + isReasonSelected = false; + } + + _parent.find( '.fs-modal-footer .button' ).addClass( 'disabled' ); + + if ( ! isReasonSelected ) { if ( ! deleteThemeUpdateData ) { // If no selected reason, just deactivate the plugin. window.location.href = redirectLink; } else { $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'delete_theme_update_data' ) ?>', @@ -292,8 +355,7 @@ function registerEventHandlers() { module_id: 'get_id() ?>' }, beforeSend: function() { - _parent.find( '.fs-modal-footer .button' ).addClass( 'disabled' ); - _parent.find( '.fs-modal-footer .button-secondary' ).text( 'Processing...' ); + _parent.find( '.fs-modal-footer .button-deactivate' ).text( '...' ); }, complete : function() { window.location.href = redirectLink; @@ -304,28 +366,27 @@ function registerEventHandlers() { return; } - var $selected_reason = $radio.parents('li:first'), - $input = $selected_reason.find('textarea, input[type="text"]'), - userReason = ( 0 !== $input.length ) ? $input.val().trim() : ''; + var snoozePeriod = 0, + shouldSnooze = $feedbackSnooze.find( '.feedback-from-snooze-checkbox' ).is( ':checked' ); - if (isOtherReasonSelected() && ( '' === userReason )) { - return; - } + if ( shouldSnooze && == selectedReasonID ) { + snoozePeriod = parseInt($feedbackSnooze.find('select').val(), 10); + } $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { - action : 'get_ajax_action( 'submit_uninstall_reason' ) ?>', - security : 'get_ajax_security( 'submit_uninstall_reason' ) ?>', - module_id : 'get_id() ?>', - reason_id : $radio.val(), - reason_info : userReason, - is_anonymous: isAnonymousFeedback() + action : 'get_ajax_action( 'submit_uninstall_reason' ) ?>', + security : 'get_ajax_security( 'submit_uninstall_reason' ) ?>', + module_id : 'get_id() ?>', + reason_id : $radio.val(), + reason_info : userReason, + is_anonymous : isAnonymousFeedback(), + snooze_period: snoozePeriod }, beforeSend: function () { - _parent.find('.fs-modal-footer .button').addClass('disabled'); - _parent.find('.fs-modal-footer .button-secondary').text('Processing...'); + _parent.find('.fs-modal-footer .button-deactivate').text('...'); }, complete : function () { // Do not show the dialog box, deactivate the plugin. @@ -365,20 +426,17 @@ function registerEventHandlers() { $modal.find('.reason-input').remove(); $modal.find( '.internal-message' ).hide(); - $modal.find('.button-deactivate').html('is_plugin() ? - $deactivate_text : - sprintf( $activate_x_text, $theme_text ) - ) ) ?>'); - - enableDeactivateButton(); + $modal.find('.button-deactivate').html(''); if ( _parent.hasClass( 'has-internal-message' ) ) { _parent.find( '.internal-message' ).show(); } - if (_parent.hasClass('has-input')) { + if ( ! _parent.hasClass('has-input') ) { + toggleDeactivationButtonPrimary( true ); + } else { + toggleDeactivationButtonPrimary( false ); + var inputType = _parent.data('input-type'), inputPlaceholder = _parent.data('input-placeholder'), reasonInputHtml = '
    ' + ( ( 'textfield' === inputType ) ? '' : '' ) + '
    '; @@ -388,11 +446,60 @@ function registerEventHandlers() { if (isOtherReasonSelected()) { showMessage(''); - disableDeactivateButton(); - } + changeDeactivateButtonText(); + } } + + $anonymousFeedback.toggle( != selectedReasonID ); + $feedbackSnooze.toggle( == selectedReasonID ); + + if ( == selectedReasonID ) { + updateDeactivationButtonOnTrouble(); + } }); + var toggleDeactivationButtonPrimary = function ( isPrimary ) { + if ( isPrimary ) { + $modal.find('.button-deactivate') + .removeClass( 'button-secondary' ) + .addClass( 'button-primary' ); + } else { + $modal.find('.button-deactivate') + .addClass( 'button-secondary' ) + .removeClass( 'button-primary' ); + } + }; + + var snooze = false; + + var updateDeactivationButtonOnTrouble = function () { + if ( snooze ) { + $modal.find('.button-deactivate').html('is_plugin() ? + $deactivate_text : + sprintf( $activate_x_text, $theme_text ) + ) ) ?>'); + } else { + $modal.find('.button-deactivate').html('is_plugin() ? + $deactivate_text : + sprintf( $activate_x_text, $theme_text ) + ) ?>'); + } + }; + + $feedbackSnooze.on( 'click', 'input', function () { + var $spans = $feedbackSnooze.find( 'span' ); + + snooze = ( ! snooze ); + + $( $spans[0] ).toggle(); + $( $spans[1] ).toggle(); + + updateDeactivationButtonOnTrouble(); + }); + // If the user has clicked outside the window, cancel it. $modal.on('click', function (evt) { var $target = $(evt.target); @@ -453,8 +560,6 @@ function closeModal() { function resetModal() { selectedReasonID = false; - enableDeactivateButton(); - // Uncheck all radio buttons. $modal.find('input[type="radio"]').prop('checked', false); @@ -463,8 +568,8 @@ function resetModal() { $modal.find('.message').hide(); - if ( isAnonymous ) { - $anonymousFeedback.find( 'input' ).prop( 'checked', false ); + if ( isAnonymous ) { + $anonymousFeedback.find( 'input' ).prop( 'checked', apply_filters( 'default_to_anonymous_feedback', false ) ? 'true' : 'false' ?> ); // Hide, since by default there is no selected reason. $anonymousFeedback.hide(); @@ -491,13 +596,31 @@ function showMessage(message) { $modal.find('.message').text(message).show(); } - function enableDeactivateButton() { - $modal.find('.button-deactivate').removeClass('disabled'); - } + /** + * @author Xiaheng Chen (@xhchen) + * + * @since 2.4.2 + */ + function changeDeactivateButtonText() { + if ( ! isOtherReasonSelected()) { + return; + } - function disableDeactivateButton() { - $modal.find('.button-deactivate').addClass('disabled'); - } + var + $userReason = $modal.find('.reason-input input'), + $deactivateButton = $modal.find('.button-deactivate'); + + if (0 === $userReason.val().trim().length) { + // If the reason is empty, just change the text to 'Deactivate' (plugin) or 'Activate themeX' (theme). + $deactivateButton.html('is_plugin() ? + $deactivate_text : + sprintf( $activate_x_text, $theme_text ) + ?>'); + } else { + $deactivateButton.html(''); + } + } function showPanel(panelType) { $modal.find( '.fs-modal-panel' ).removeClass( 'active' ); diff --git a/freemius/templates/forms/email-address-update.php b/freemius/templates/forms/email-address-update.php new file mode 100644 index 0000000..49a300f --- /dev/null +++ b/freemius/templates/forms/email-address-update.php @@ -0,0 +1,347 @@ +get_slug(); + + $user = $fs->get_user(); + $current_email_address = $user->email; + + fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); +?> + \ No newline at end of file diff --git a/freemius/templates/forms/license-activation.php b/freemius/templates/forms/license-activation.php index facc12a..a2a10bf 100644 --- a/freemius/templates/forms/license-activation.php +++ b/freemius/templates/forms/license-activation.php @@ -30,17 +30,10 @@ if ( $fs->is_registered() ) { $activate_button_text = $header_title; } else { - $freemius_site_url = $fs->has_paid_plan() ? - 'https://freemius.com/' : - // Insights platform information. - $fs->get_usage_tracking_terms_url(); - - $freemius_link = 'freemius.com'; - $message_below_input_field = sprintf( - fs_text_inline( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), + fs_text_inline( 'The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), $fs->get_module_label( true ), - $freemius_link + "{$fs->get_plugin_title()}" ); $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); @@ -59,23 +52,51 @@ if ( $is_network_activation ) { $all_sites = Freemius::get_sites(); + $subsite_data_by_install_id = array(); + $install_url_by_install_id = array(); + foreach ( $all_sites as $site ) { $site_details = $fs->get_site_info( $site ); + if ( FS_Clone_Manager::instance()->is_temporary_duplicate_by_blog_id( $site_details['blog_id'] ) ) { + continue; + } + $blog_id = Freemius::get_site_blog_id( $site ); $install = $fs->get_install_by_blog_id($blog_id); - if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { - $site_details['license_id'] = $install->license_id; - } + if ( is_object( $install ) ) { + if ( isset( $subsite_data_by_install_id[ $install->id ] ) ) { + $clone_subsite_data = $subsite_data_by_install_id[ $install->id ]; + $clone_install_url = $install_url_by_install_id[ $install->id ]; - $sites_details[] = $site_details; + if ( + /** + * If we already have an install with the same URL as the subsite it's stored in, skip the current subsite. Otherwise, replace the existing install's data with the current subsite's install's data if the URLs match. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + fs_strip_url_protocol( untrailingslashit( $clone_install_url ) ) === fs_strip_url_protocol( untrailingslashit( $clone_subsite_data['url'] ) ) || + fs_strip_url_protocol( untrailingslashit( $install->url ) ) !== fs_strip_url_protocol( untrailingslashit( $site_details['url'] ) ) + ) { + continue; + } + } + + if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $site_details['license_id'] = $install->license_id; + } + + $subsite_data_by_install_id[ $install->id ] = $site_details; + $install_url_by_install_id[ $install->id ] = $install->url; + } } if ( $is_network_activation ) { $vars = array( 'id' => $fs->get_id(), - 'sites' => $sites_details, + 'sites' => array_values( $subsite_data_by_install_id ), 'require_license_key' => true ); @@ -341,7 +362,7 @@ class="fs-available-license-key" $activateLicenseButton.html( '...' ); $.ajax( { - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>', @@ -589,7 +610,6 @@ function registerEventHandlers() { url : $this.find( '.url' ).val(), title : $this.find( '.title' ).val(), language: $this.find( '.language' ).val(), - charset : $this.find( '.charset' ).val(), blog_id : $this.find( '.blog-id' ).find( 'span' ).text() }; @@ -607,7 +627,7 @@ function registerEventHandlers() { } $.ajax({ - url: ajaxurl, + url: , method: 'POST', data: data, beforeSend: function () { @@ -867,4 +887,4 @@ function showError( msg ) { } }); })( jQuery ); - \ No newline at end of file + diff --git a/freemius/templates/forms/optout.php b/freemius/templates/forms/optout.php index 4867a8a..f6f0ca5 100644 --- a/freemius/templates/forms/optout.php +++ b/freemius/templates/forms/optout.php @@ -17,320 +17,166 @@ $fs = freemius( $VARS['id'] ); $slug = $fs->get_slug(); - $action = $fs->is_tracking_allowed() ? - 'stop_tracking' : - 'allow_tracking'; - $reconnect_url = $fs->get_activation_url( array( 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), ) ); - $plugin_title = "{$fs->get_plugin()->title}"; - $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); - $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug ); - - if ( $fs->is_premium() ) { - $opt_in_message_appreciation = fs_text_inline( 'Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard.', 'premium-opt-in-message-appreciation', $slug ); - - $opt_out_message_subtitle = sprintf( fs_text_inline( 'Warning: Opting out will block automatic updates', 'premium-opt-out-message-appreciation', $slug ), $fs->get_module_type() ); - $opt_out_message_usage_tracking = sprintf( fs_text_inline( 'Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won\'t receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you\'ll need to check for %1$s updates and install them manually.', 'premium-opt-out-message-usage-tracking', $slug ), $fs->get_module_type(), $plugin_title ); - - $primary_cta_label = fs_text_inline( 'I\'d like to keep automatic updates', 'premium-opt-out-cancel', $slug ); - } else { - $opt_in_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-in-message-appreciation', $slug ), $fs->get_module_type() ); - - $opt_out_message_subtitle = $opt_in_message_appreciation; - $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title ); - $primary_cta_label = fs_text_inline( 'On second thought - I want to continue helping', 'opt-out-cancel', $slug ); - } - - $opt_out_message_clicking_opt_out = sprintf( - fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ), - $plugin_title, - sprintf( - '%s', - 'https://freemius.com', - 'freemius.com' - ) - ); + $plugin_title = "" . esc_html( $fs->get_plugin()->title ) . ""; + $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); - $admin_notice_params = array( - 'id' => '', - 'slug' => $fs->get_id(), - 'type' => 'success', - 'sticky' => false, - 'plugin' => $fs->get_plugin()->title, - 'message' => $opt_in_message_appreciation - ); - - $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params ); - - $modal_content_html = " - is_premium() ? ' style="color: red"' : '' ) . ">{$opt_out_message_subtitle}

    -

    -

    {$opt_out_message_usage_tracking}

    -

    {$opt_out_message_clicking_opt_out}

    - "; + $permission_manager = FS_Permission_Manager::instance( $fs ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); + fs_enqueue_local_style( 'fs_optout', '/admin/optout.css' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + if ( ! $fs->is_premium() ) { + $optional_permissions = array( $permission_manager->get_extensions_permission( false, + false, + true + ) ); + + $permission_groups = array( + array( + 'id' => 'communication', + 'type' => 'required', + 'title' => $fs->get_text_inline( 'Communication', 'communication' ), + 'desc' => '', + 'permissions' => $permission_manager->get_opt_in_required_permissions( true ), + 'is_enabled' => $fs->is_registered(), + 'prompt' => array( + $fs->esc_html_inline( "Sharing your name and email allows us to keep you in the loop about new features and important updates, warn you about security issues before they become public knowledge, and send you special offers.", 'opt-out-message_user' ), + sprintf( + $fs->esc_html_inline( 'By clicking "Opt Out", %s will no longer be able to view your name and email.', + 'opt-out-message-clicking-opt-out' ), + $plugin_title + ), + ), + 'prompt_cancel_label' => $fs->get_text_inline( 'Stay Connected', 'stay-connected' ) + ), + array( + 'id' => 'diagnostic', + 'type' => 'required', + 'title' => $fs->get_text_inline( 'Diagnostic Info', 'diagnostic-info' ), + 'desc' => '', + 'permissions' => $permission_manager->get_opt_in_diagnostic_permissions( true ), + 'is_enabled' => $fs->is_tracking_allowed(), + 'prompt' => array( + sprintf( + $fs->esc_html_inline( 'Sharing diagnostic data helps to provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break the website, and recognize which languages & regions the %s should be translated and tailored to.', + 'opt-out-message-clicking-opt-out' ), + $fs->get_module_type() + ), + sprintf( + $fs->esc_html_inline( 'By clicking "Opt Out", diagnostic data will no longer be sent to %s.', + 'opt-out-message-clicking-opt-out' ), + $plugin_title + ), + ), + 'prompt_cancel_label' => $fs->get_text_inline( 'Keep Sharing', 'keep-sharing' ) + ), + array( + 'id' => 'extensions', + 'type' => 'optional', + 'title' => $fs->get_text_inline( 'Extensions', 'extensions' ), + 'desc' => '', + 'permissions' => $optional_permissions, + ), + ); + } else { + $optional_permissions = $permission_manager->get_license_optional_permissions( false, true ); + + $permission_groups = array( + array( + 'id' => 'essentials', + 'type' => 'required', + 'title' => $fs->esc_html_inline( 'Required', 'required' ), + 'desc' => sprintf( $fs->esc_html_inline( 'For automatic delivery of security & feature updates, and license management & protection, %s needs to:', + 'license-sync-disclaimer' ), + '' . esc_html( $fs->get_plugin_title() ) . '' ), + 'permissions' => $permission_manager->get_license_required_permissions( true ), + 'is_enabled' => $permission_manager->is_essentials_tracking_allowed(), + 'prompt' => array( + sprintf( $fs->esc_html_inline( 'To ensure that security & feature updates are automatically delivered directly to your WordPress Admin Dashboard while protecting your license from unauthorized abuse, %2$s needs to view the website’s homepage URL, %1$s version, SDK version, and whether the %1$s is active.', 'premium-opt-out-message-usage-tracking' ), $fs->get_module_type(), $plugin_title ), + sprintf( $fs->esc_html_inline( 'By opting out from sharing this information with the updates server, you’ll have to check for new %1$s releases and manually download & install them. Not just a hassle, but missing an update can put your site at risk and cause undue compatibility issues, so we highly recommend keeping these essential permissions on.', 'opt-out-message-clicking-opt-out' ), $fs->get_module_type(), $plugin_title ), + ), + 'prompt_cancel_label' => $fs->get_text_inline( 'Keep automatic updates', 'premium-opt-out-cancel' ) + ), + array( + 'id' => 'optional', + 'type' => 'optional', + 'title' => $fs->esc_html_inline( 'Optional', 'optional' ), + 'desc' => sprintf( $fs->esc_html_inline( 'For ongoing compatibility with your website, you can optionally allow %s to:', + 'optional-permissions-disclaimer' ), $plugin_title ), + 'permissions' => $optional_permissions, + ), + ); + } + + $ajax_action = 'toggle_permission_tracking'; + + $form_id = "fs_opt_out_{$fs->get_id()}"; ?> + + +require_permissions_js( false ) ?> + diff --git a/freemius/templates/forms/resend-key.php b/freemius/templates/forms/resend-key.php index f8cafb9..dc723e5 100644 --- a/freemius/templates/forms/resend-key.php +++ b/freemius/templates/forms/resend-key.php @@ -54,7 +54,10 @@ HTML; } - $message_above_input_field = fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); + $message_above_input_field = $fs->is_only_premium() ? + fs_esc_html_inline( "Enter the email address you've used during the purchase and we will resend you the license key.", 'ask-for-upgrade-email-address-premium-only', $slug ) : + fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); + $modal_content_html = <<< HTML

    {$message_above_input_field}

    @@ -142,7 +145,7 @@ function registerEventHandlers() { } $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'resend_license_key' ) ?>', diff --git a/freemius/templates/forms/trial-start.php b/freemius/templates/forms/trial-start.php index b66e727..812363b 100644 --- a/freemius/templates/forms/trial-start.php +++ b/freemius/templates/forms/trial-start.php @@ -80,7 +80,7 @@ function registerEventHandlers() { var $button = $(this); $.ajax({ - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'start_trial' ) ?>', diff --git a/freemius/templates/forms/user-change.php b/freemius/templates/forms/user-change.php index 3571b83..3051b38 100644 --- a/freemius/templates/forms/user-change.php +++ b/freemius/templates/forms/user-change.php @@ -194,7 +194,7 @@ function registerEventHandlers() { disableUserChangeButton(); $.ajax( { - url : ajaxurl, + url : , method : 'POST', data : { action : 'get_ajax_action( 'change_user' ) ?>', diff --git a/freemius/templates/gdpr-optin-js.php b/freemius/templates/gdpr-optin-js.php index 4fdc5e3..9ead86d 100644 --- a/freemius/templates/gdpr-optin-js.php +++ b/freemius/templates/gdpr-optin-js.php @@ -38,7 +38,7 @@ } $.ajax({ - url : ajaxurl + '?' + $.param({ + url : + '?' + $.param({ action : 'get_ajax_action( 'gdpr_optin_action' ) ?>', security : 'get_ajax_security( 'gdpr_optin_action' ) ?>', module_id: 'get_id() ?>' diff --git a/freemius/templates/js/permissions.php b/freemius/templates/js/permissions.php new file mode 100644 index 0000000..dbc7dfd --- /dev/null +++ b/freemius/templates/js/permissions.php @@ -0,0 +1,546 @@ + + \ No newline at end of file diff --git a/freemius/templates/partials/network-activation.php b/freemius/templates/partials/network-activation.php index 12f152f..889560c 100644 --- a/freemius/templates/partials/network-activation.php +++ b/freemius/templates/partials/network-activation.php @@ -58,7 +58,7 @@
    - +
    - +
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/description.php b/freemius/templates/plugin-info/description.php index 26bc67b..ea33bd4 100644 --- a/freemius/templates/plugin-info/description.php +++ b/freemius/templates/plugin-info/description.php @@ -52,7 +52,7 @@ info->screenshots ) ) : ?> info->screenshots ?>
    -

    slug ) ?>

    +

    slug ) ?>

    ' . "\n"; // --- notice dismissal iframe (once only) --- radio_station_admin_notice_iframe(); @@ -1071,7 +1091,7 @@ function radio_station_notice_dismiss() { } $notices[$notice] = '1'; update_option( 'radio_station_read_notices', $notices ); - echo "" . PHP_EOL; + echo "" . "\n"; } elseif ( isset( $_GET['upgrade'] ) ) { @@ -1085,7 +1105,7 @@ function radio_station_notice_dismiss() { } $upgrades[$upgrade] = '1'; update_option( 'radio_station_read_upgrades', $upgrades ); - echo "" . PHP_EOL; + echo "" . "\n"; } } @@ -1104,7 +1124,7 @@ function radio_station_notice_dismiss() { function radio_station_admin_notice_iframe() { global $radio_station_notice_iframe; if ( !isset( $radio_station_notice_iframe ) || !$radio_station_notice_iframe ) { - echo '' . PHP_EOL; + echo '' . "\n"; $radio_station_notice_iframe = true; } } @@ -1151,7 +1171,7 @@ function radio_station_settings_page_top() { // --- plugin update notice --- radio_station_update_notice(); - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; } // --------------------------- @@ -1191,9 +1211,9 @@ function radio_station_listing_offer_notice() { } // --- display plugin announcement --- - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; radio_station_listing_offer_content(); - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- notice dismissal frame (once) --- radio_station_admin_notice_iframe(); @@ -1232,10 +1252,10 @@ function radio_station_launch_offer_notice( $rspage = false ) { } // --- display plugin announcement --- - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; $prelaunch = ( $now < $offer_start ) ? true : false; radio_station_launch_offer_content( true, $prelaunch ); - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; // --- notice dismissal frame (once) --- radio_station_admin_notice_iframe(); @@ -1249,69 +1269,70 @@ function radio_station_listing_offer_content( $dismissable = true ) { $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_listing_offer_dismiss' ); $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); - echo '
      ' . PHP_EOL; - - // --- directory logo image --- - $logo_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); - echo '
    • ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    • ' . PHP_EOL; - - // --- free listing offer text --- - echo '
    • ' . PHP_EOL; - echo '
      ' . esc_html( __( 'Time Sensitive Free Offer', 'radio-station' ) ) . '
      ' . PHP_EOL; - - echo '

      ' . PHP_EOL; - echo esc_html( __( 'We are excited to announce the opening of the new', 'radio-station' ) ) . PHP_EOL; - echo ' Netmix Station Directory!!!
      ' . PHP_EOL; - echo esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...', 'radio-station' ) ) . '
      ' . PHP_EOL; - - echo esc_html( __( 'Because while launching,' ) ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?', 'radio-station' ) ) . PHP_EOL; - echo '

    • '; - - // --- accept / decline offer button links --- - echo '
    • ' . PHP_EOL; - echo '
      ' . PHP_EOL; - echo '
      ' . PHP_EOL; - echo '' . esc_html( __( 'Yes please!', 'radio-station' ) ) . '' . PHP_EOL; - echo '
      ' . PHP_EOL; - echo '
      ' . PHP_EOL; - echo '' . esc_html( __( 'More Details', 'radio-station' ) ) . '' . PHP_EOL; - echo '

      ' . PHP_EOL; + echo '
        ' . "\n"; + + // --- directory logo image --- + $logo_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); + echo '
      • ' . "\n"; + echo '' . "\n"; + echo '
      • ' . "\n"; + + // --- free listing offer text --- + echo '
      • ' . "\n"; + echo '
        ' . esc_html( __( 'Time Sensitive Free Offer', 'radio-station' ) ) . '
        ' . "\n"; + + echo '

        ' . "\n"; + echo esc_html( __( 'We are excited to announce the opening of the new', 'radio-station' ) ) . "\n"; + echo ' Netmix Station Directory!!!
        ' . "\n"; + echo esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...', 'radio-station' ) ) . '
        ' . "\n"; + + echo esc_html( __( 'Because while launching,' ) ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!', 'radio-station' ) ) . '
        ' . "\n"; + echo esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?', 'radio-station' ) ) . "\n"; + echo '

        ' . "\n"; + echo '
      • ' . "\n"; + + // --- accept / decline offer button links --- + echo '
      • ' . "\n"; + echo '
        ' . "\n"; + echo '' . "\n"; + echo '
        ' . "\n"; - echo '' . PHP_EOL; - echo '

        ' . PHP_EOL; + echo '' . "\n"; + echo '

      ' . "\n"; - echo '
      ' . PHP_EOL; - echo esc_html( __( 'Offer valid until end of July 2020.', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'Activate your 30 days before it ends!', 'radio-station' ) ) . PHP_EOL; - echo '
      ' . PHP_EOL; + echo '
      ' . "\n"; + echo esc_html( __( 'Offer valid until end of July 2020.', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'Activate your 30 days before it ends!', 'radio-station' ) ) . "\n"; + echo '
      ' . "\n"; - echo '
    • ' . PHP_EOL; + echo '' . "\n"; - echo '
    ' . PHP_EOL; + echo '' . "\n"; // --- dismiss notice icon --- if ( $dismissable ) { - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; } // --- display dismiss link script --- echo "" . PHP_EOL; + }" . "\n"; } @@ -1325,72 +1346,72 @@ function radio_station_launch_offer_content( $dismissable = true, $prelaunch = f $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); echo '
      ' . PHP_EOL; - // --- directory logo image --- - // $launch_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); - $launch_image = plugins_url( 'images/pro-launch.gif', RADIO_STATION_FILE ); - echo '
    • ' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    • ' . PHP_EOL; - - // --- free listing offer text --- - echo '
    • ' . PHP_EOL; - - if ( $prelaunch ) { - echo '
      ' . esc_html( __( 'Radio Station Pro Launch Discount!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo '

      ' . PHP_EOL; - echo esc_html( __( 'We are thrilled to announce the upcoming launch of Radio Station PRO', 'radio-station' ) ) . ' !!!
      ' . PHP_EOL; - echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'During the launch,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'Sign up to the exclusive launch list to receive your discount code when we go LIVE.', 'radio-station' ) ) . PHP_EOL; - echo '

      ' . PHP_EOL; - } else { - echo '
      ' . esc_html( __( 'Radio Station PRO Launch is LIVE!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo '

      ' . PHP_EOL; - echo esc_html( __( 'The long anticipated moment has arrived. The doors are open to get PRO', 'radio-station' ) ) . ' !!!
      ' . PHP_EOL; - echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'Remember,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo '' . PHP_EOL; - echo esc_html( __( 'Sign up here to receive your exclusive launch discount code.', 'radio-station' ) ) . PHP_EOL; - echo '

      ' . PHP_EOL; - } - - echo '
    • ' . PHP_EOL; - - // --- accept / decline offer button links --- - echo '
    • ' . PHP_EOL; - echo '
      ' . PHP_EOL; - echo '
      ' . PHP_EOL; - if ( $prelaunch ) { - echo '' . esc_html( __( "Yes, I'm in!", 'radio-station' ) ) . '' . PHP_EOL; - } else { - echo '' . esc_html( __( 'Go PRO', 'radio-station' ) ) . '' . PHP_EOL; - } - echo '
      ' . PHP_EOL; + // --- directory logo image --- + // $launch_image = plugins_url( 'images/netmix-logo.png', RADIO_STATION_FILE ); + $launch_image = plugins_url( 'images/pro-launch.gif', RADIO_STATION_FILE ); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '
    • ' . "\n"; + + // --- free listing offer text --- + echo '
    • ' . "\n"; + + if ( $prelaunch ) { + echo '
      ' . esc_html( __( 'Radio Station Pro Launch Discount!', 'radio-station' ) ) . '
      ' . "\n"; + echo '

      ' . "\n"; + echo esc_html( __( 'We are thrilled to announce the upcoming launch of Radio Station PRO', 'radio-station' ) ) . ' !!!
      ' . "\n"; + echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'During the launch,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'Sign up to the exclusive launch list to receive your discount code when we go LIVE.', 'radio-station' ) ) . "\n"; + echo '

      ' . "\n"; + } else { + echo '
      ' . esc_html( __( 'Radio Station PRO Launch is LIVE!', 'radio-station' ) ) . '
      ' . "\n"; + echo '

      ' . "\n"; + echo esc_html( __( 'The long anticipated moment has arrived. The doors are open to get PRO', 'radio-station' ) ) . ' !!!
      ' . "\n"; + echo esc_html( __( 'Jam-packed with new features to "level up" your Station\'s online presence.', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'Remember,' ) ) . ' ' . esc_html( __( 'we are offering 30% discount to existing Radio Station users!', 'radio-station' ) ) . '
      ' . "\n"; + echo '' . "\n"; + echo esc_html( __( 'Sign up here to receive your exclusive launch discount code.', 'radio-station' ) ) . PHP_EOL; + echo '

      ' . "\n"; + } + + echo '
    • ' . "\n"; + + // --- accept / decline offer button links --- + echo '
    • ' . "\n"; + echo '
      ' . "\n"; + echo '
      ' . "\n"; + if ( $prelaunch ) { + echo '' . esc_html( __( "Yes, I'm in!", 'radio-station' ) ) . '' . "\n"; + } else { + echo '' . esc_html( __( 'Go PRO', 'radio-station' ) ) . '' . "\n"; + } + echo '
      ' . "\n"; - echo '' . PHP_EOL; - echo '

      ' . PHP_EOL; + echo '' . "\n"; + echo '

    • ' . "\n"; - echo '
    • ' . PHP_EOL; + echo '' . "\n"; - echo '
    ' . PHP_EOL; + echo '' . "\n"; // --- dismiss notice icon --- if ( $dismissable ) { - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; } // --- display dismiss link script --- @@ -1398,7 +1419,7 @@ function radio_station_launch_offer_content( $dismissable = true, $prelaunch = f document.getElementById('launch-offer-accept-button').style.display = 'none'; document.getElementById('launch-offer-dismiss-link').style.display = ''; document.getElementById('radio-station-notice-iframe').src = '" . esc_url( $accept_dismiss_url ) . "'; - }" . PHP_EOL; + }" . "\n"; } @@ -1416,12 +1437,12 @@ function radio_station_listing_offer_dismiss() { // --- set option to dismissed --- update_option( 'radio_station_listing_offer_dismissed', true ); - if ( isset( $_REQUEST['accept'] ) && ( '1' == $_REQUEST['accept'] ) ) { + if ( isset( $_REQUEST['accept'] ) && ( '1' == sanitize_text_field( $_REQUEST['accept'] ) ) ) { update_option( 'radio_station_listing_offer_accepted', true ); } // --- hide the announcement in parent frame --- - echo "" . PHP_EOL; + echo "" . "\n"; exit; } @@ -1461,7 +1482,7 @@ function radio_station_launch_offer_dismiss() { } // --- hide the announcement in parent frame --- - echo "" . PHP_EOL; + echo "" . "\n"; exit; } @@ -1504,55 +1525,55 @@ function radio_station_announcement_notice() { // 2.2.2: added simple patreon supporter blurb function radio_station_announcement_content( $dismissable = true ) { - echo '
      ' . PHP_EOL; - - // --- plugin image --- - $plugin_image = plugins_url( 'images/radio-station.png', RADIO_STATION_FILE ); - echo '
    • '; - echo ''; - echo '
    • '; - - // --- takeover announcement --- - echo '
    • ' . PHP_EOL; - echo '' . esc_html( __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) ) . ',
      ' . PHP_EOL; - echo esc_html( __( 'since June 2019', 'radio-station' ) ) . ', ' . PHP_EOL; - echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . ' ' . PHP_EOL; - echo esc_html( __( ' plugin development has been actively taken over by', 'radio-station' ) ) . PHP_EOL; - echo ' Netmix.
      ' . PHP_EOL; - // 2.3.3.9: add updated text after 2000 user mileston - echo esc_html( __( 'And due to our continued efforts we now have a community of over two thousand active stations!', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'We invite you to', 'radio-station' ) ) . PHP_EOL; - echo ' ' . PHP_EOL; - echo esc_html( __( 'Become a Radio Station Patreon Supporter', 'radio-station' ) ) . PHP_EOL; - echo ' ' . esc_html( __( 'to make it better for everyone', 'radio-station' ) ) . '!' . PHP_EOL; - echo '
    • ' . PHP_EOL; - echo '
    • ' . PHP_EOL; - - $button = radio_station_patreon_button( 'radiostation' ); - // 2.5.0: added wp_kses to button output - $allowed = radio_station_allowed_html( 'button', 'patreon' ); - echo wp_kses( $button , $allowed ); - - // 2.2.7: added WordPress.Org star rating link - // 2.3.0: only show for dismissable notice - if ( $dismissable ) { - echo '

      ' . PHP_EOL; - echo '' . PHP_EOL; - echo esc_html( __( 'Rate on WordPress.Org', 'radio-station' ) ) . PHP_EOL; - echo '' . PHP_EOL; - } - echo '
    • ' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
      ' . "\n"; + + // --- plugin image --- + $plugin_image = plugins_url( 'images/radio-station.png', RADIO_STATION_FILE ); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '
    • ' . "\n"; + + // --- takeover announcement --- + echo '
    • ' . "\n"; + echo '' . esc_html( __( 'Help support us to make improvements, modifications and introduce new features!', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'With over a thousand radio station users thanks to the original plugin author Nikki Blight', 'radio-station' ) ) . ',
      ' . "\n"; + echo esc_html( __( 'since June 2019', 'radio-station' ) ) . ', ' . "\n"; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . ' ' . "\n"; + echo esc_html( __( ' plugin development has been actively taken over by', 'radio-station' ) ) . "\n"; + echo ' Netmix.
      ' . "\n"; + // 2.3.3.9: add updated text after 2000 user mileston + echo esc_html( __( 'And due to our continued efforts we now have a community of over two thousand active stations!', 'radio-station' ) ) . '
      ' . "\n"; + echo esc_html( __( 'We invite you to', 'radio-station' ) ) . "\n"; + echo ' ' . "\n"; + echo esc_html( __( 'Become a Radio Station Patreon Supporter', 'radio-station' ) ) . "\n"; + echo ' ' . esc_html( __( 'to make it better for everyone', 'radio-station' ) ) . '!' . "\n"; + echo '
    • ' . "\n"; + echo '
    • ' . "\n"; + + $button = radio_station_patreon_button( 'radiostation' ); + // 2.5.0: added wp_kses to button output + $allowed = radio_station_allowed_html( 'button', 'patreon' ); + echo wp_kses( $button , $allowed ); + + // 2.2.7: added WordPress.Org star rating link + // 2.3.0: only show for dismissable notice + if ( $dismissable ) { + echo '

      ' . "\n"; + echo '' . "\n"; + echo esc_html( __( 'Rate on WordPress.Org', 'radio-station' ) ) . "\n"; + echo '' . "\n"; + } + echo '
    • ' . "\n"; + echo '
    ' . "\n"; // --- dismiss notice icon --- if ( $dismissable ) { $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_announcement_dismiss' ); - echo '
    ' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; } } @@ -1567,7 +1588,7 @@ function radio_station_announcement_dismiss() { // --- set option to dismissed --- update_option( 'radio_station_announcement_dismissed', true ); // --- hide the announcement in parent frame --- - echo "" . PHP_EOL; + echo "" . "\n"; } exit; } @@ -1590,71 +1611,71 @@ function radio_station_shift_conflict_notice() { $conflicts = get_option( 'radio_station_schedule_conflicts' ); if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { - echo '
    ' . PHP_EOL; - - echo '
      ' . PHP_EOL; - - // 2.3.3.9: remove unnecessary left margin on first list item - echo '
    • ' . PHP_EOL; - echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'has detected', 'radio-station' ) ) . '
      ' . PHP_EOL; - echo esc_html( __( 'Schedule conflicts!', 'radio-station' ) ) . PHP_EOL; - echo '
    • ' . PHP_EOL; - - echo '
    • ' . PHP_EOL; - echo '' . esc_html( __( 'The following Shows have conflicting Shift times', 'radio-station' ) ) . ":
      " . PHP_EOL; - - echo '
        ' . PHP_EOL; - $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); - foreach ( $conflicts as $show => $show_conflicts ) { - foreach ( $show_conflicts as $conflict ) { - if ( !$conflict['duplicate'] ) { - echo '
      • '; - $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); - $show_title = get_the_title( $conflict['show'] ); - echo '' . esc_attr( $show_title ) . ''; - if ( $conflict['disabled'] ) { - $disabled = ' [disabled] '; - } else { - $disabled = ''; - } - echo ' (' . esc_html( $disabled ) . esc_html( $conflict['day'] ) . ' '; - echo esc_html( $conflict['start'] ) . ' - ' . esc_html( $conflict['end'] ) . ')'; - echo ' ' . esc_html( __( 'with', 'radio-station' ) ) . ' '; - $show_edit_link = add_query_arg( 'post', $conflict['with_show'], $edit_link ); - $show_title = get_the_title( $conflict['with_show'] ); - echo '' . esc_attr( $show_title ) . ''; - if ( $conflict['with_disabled'] ) { - $disabled = ' [disabled] '; - } else { - $disabled = ''; + echo '
        ' . "\n"; + + echo '
          ' . "\n"; + + // 2.3.3.9: remove unnecessary left margin on first list item + echo '
        • ' . "\n"; + echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
          ' . "\n"; + echo esc_html( __( 'has detected', 'radio-station' ) ) . '
          ' . "\n"; + echo esc_html( __( 'Schedule conflicts!', 'radio-station' ) ) . "\n"; + echo '
        • ' . "\n"; + + echo '
        • ' . "\n"; + echo '' . esc_html( __( 'The following Shows have conflicting Shift times', 'radio-station' ) ) . ":
          " . "\n"; + + echo '
            ' . "\n"; + $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); + foreach ( $conflicts as $show => $show_conflicts ) { + foreach ( $show_conflicts as $conflict ) { + if ( !$conflict['duplicate'] ) { + echo '
          • '; + $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); + $show_title = get_the_title( $conflict['show'] ); + echo '' . esc_attr( $show_title ) . ''; + if ( $conflict['disabled'] ) { + $disabled = ' [disabled] '; + } else { + $disabled = ''; + } + echo ' (' . esc_html( $disabled ) . esc_html( $conflict['day'] ) . ' '; + echo esc_html( $conflict['start'] ) . ' - ' . esc_html( $conflict['end'] ) . ')'; + echo ' ' . esc_html( __( 'with', 'radio-station' ) ) . ' '; + $show_edit_link = add_query_arg( 'post', $conflict['with_show'], $edit_link ); + $show_title = get_the_title( $conflict['with_show'] ); + echo '' . esc_attr( $show_title ) . ''; + if ( $conflict['with_disabled'] ) { + $disabled = ' [disabled] '; + } else { + $disabled = ''; + } + echo ' (' . esc_html( $disabled ) . esc_html( $conflict['with_day'] ) . ' '; + echo esc_html( $conflict['with_start'] ) . ' - ' . esc_html( $conflict['with_end'] ) . ')'; + echo '
          • '; + } + } } - echo ' (' . esc_html( $disabled ) . esc_html( $conflict['with_day'] ) . ' '; - echo esc_html( $conflict['with_start'] ) . ' - ' . esc_html( $conflict['with_end'] ) . ')'; - echo ''; - } - } - } - echo '
          '; - echo '
        • '; + echo '
        '; + echo '
      • '; - // --- show list link --- - $show_list_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - echo '
      • ' . PHP_EOL; - echo '' . esc_html( __( 'Go to Show List', 'radio-station' ) ) . ' →
        ' . PHP_EOL; - echo esc_html( __( 'Conflicts are highlighted', 'radio-station' ) ) . '
        ' . PHP_EOL; - echo esc_html( __( 'in Show Shift column.', 'radio-station' ) ) . PHP_EOL; - echo '
      • ' . PHP_EOL; + // --- show list link --- + $show_list_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + echo '
      • ' . "\n"; + echo '' . esc_html( __( 'Go to Show List', 'radio-station' ) ) . ' →
        ' . "\n"; + echo esc_html( __( 'Conflicts are highlighted', 'radio-station' ) ) . '
        ' . "\n"; + echo esc_html( __( 'in Show Shift column.', 'radio-station' ) ) . "\n"; + echo '
      • ' . "\n"; - // --- undismissable error notice --- - echo '
      • ' . PHP_EOL; - echo esc_html( __( 'This notice will persist', 'radio-station' ) ) . '
        ' . PHP_EOL; - echo esc_html( __( 'until conflicts are resolved.', 'radio-station' ) ) . PHP_EOL; - echo '
      • ' . PHP_EOL; + // --- undismissable error notice --- + echo '
      • ' . "\n"; + echo esc_html( __( 'This notice will persist', 'radio-station' ) ) . '
        ' . "\n"; + echo esc_html( __( 'until conflicts are resolved.', 'radio-station' ) ) . "\n"; + echo '
      • ' . "\n"; - echo '
      ' . PHP_EOL; + echo '
    ' . "\n"; - echo '
    ' . PHP_EOL; + echo '' . "\n"; } } @@ -1712,7 +1733,7 @@ function radio_station_mailchimp_form() { ' . PHP_EOL; + echo '' . "\n"; // --- AJAX subscription call --- // 2.3.0: added to record subscribers @@ -1724,7 +1745,7 @@ function radio_station_mailchimp_form() { url = '" . esc_url_raw( $recordurl ) . "&email='+encodeURIComponent(email); document.getElementById('mc-subscribe-record').src = url; }); - });" . PHP_EOL; + });" . "\n"; } @@ -1747,7 +1768,7 @@ function radio_station_record_subscribe() { // --- submit form in parent window --- echo "" . PHP_EOL; + echo "parent.jQuery('#mc-embedded-subscribe-form').submit();" . "\n"; exit; } @@ -1765,8 +1786,8 @@ function radio_station_clear_plugin_options() { } if ( isset( $_GET['option'] ) ) { - // 2.5.0: condensed logic and added sanitize_title - $option = sanitize_title( $_GET['option'] ); + // 2.5.0: condensed logic and added sanitize_text_field + $option = sanitize_text_field( $_GET['option'] ); if ( 'subscribed' == $option ) { // note: there is a typo in this option not worth fixing delete_option( 'radio_station_subcribed' ); diff --git a/radio-station.php b/radio-station.php index 386ef10..9a7d216 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.7 +Version: 2.4.9.9 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -163,6 +163,7 @@ require RADIO_STATION_DIR . '/includes/shortcodes.php'; // --- Widgets --- +// 2.3.1: added radio player widget file // 2.4.0.4: move player widget here // 2.5.0: move widget classes to widgets directory require RADIO_STATION_DIR . '/widgets/class-current-show-widget.php'; @@ -179,7 +180,6 @@ // --- Feature Development --- // 2.3.0: add feature branch development includes -// 2.3.1: added radio player widget file $features = array( 'import-export' ); foreach ( $features as $feature ) { $filepath = RADIO_STATION_DIR . '/includes/' . $feature . '.php'; @@ -210,7 +210,7 @@ $settings = array( // --- Plugin Info --- 'slug' => RADIO_STATION_SLUG, - 'file' => __FILE__, + 'file' => RADIO_STATION_FILE, 'version' => '0.0.1', // --- Menus and Links --- @@ -283,6 +283,7 @@ function radio_station_freemius_bundle_config( $settings ) { require RADIO_STATION_DIR . '/loader.php'; $instance = new radio_station_loader( $settings ); + // -------------------------- // Include Plugin Admin Files // -------------------------- @@ -310,6 +311,26 @@ function radio_station_init() { load_plugin_textdomain( 'radio-station', false, RADIO_STATION_DIR . '/languages' ); } +// ---------------------------- +// Add Pricing Page Path Filter +// ---------------------------- +// 2.5.0: added for Freemius Pricing Page v2 +add_action( 'radio_station_loaded', 'radio_station_add_pricing_path_filter' ); +function radio_station_add_pricing_path_filter() { + global $radio_station_freemius; + if ( method_exists( $radio_station_freemius, 'add_filter' ) ) { + $radio_station_freemius->add_filter( 'freemius_pricing_js_path', 'radio_station_pricing_page_path' ); + } +} + +// ------------------------ +// Pricing Page Path Filter +// ------------------------ +// 2.6.0: added for Freemius Pricing Page v2 +function radio_station_pricing_page_path( $default_pricing_js_path ) { + return RADIO_STATION_DIR . '/freemius-pricing/freemius-pricing.js'; +} + // ------------------------ // === Plugin Functions === @@ -361,7 +382,7 @@ function radio_station_check_plan_options() { // 2.5.0: set as array for returning $plan_options = array( - 'has_plans' => $has_plans, + 'has_plans' => $has_plans, 'has_addons' => $has_addons, 'plan_type' => $plan, ); @@ -885,7 +906,7 @@ function radio_station_localization_script() { // Filter for Streaming Data // ------------------------- // 2.3.3.7: added streaming data filter for player integration -add_filter( 'radio_station_player_data', 'radio_station_streaming_data' ); +add_filter( 'radio_player_data', 'radio_station_streaming_data' ); function radio_station_streaming_data( $data, $station = false ) { $data = array( 'script' => radio_station_get_setting( 'player_script' ), diff --git a/readme.md b/readme.md index 1aec371..74cd47c 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.1 +Tested up to: 6.1.1 Stable tag: 2.5.0 diff --git a/readme.txt b/readme.txt index 64dd4e3..06d55ca 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.1 +Tested up to: 6.1.1 Stable tag: 2.5.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -220,8 +220,8 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.4.5) -* Updated: Plugin Panel (1.2.8) +* Updated: Freemius SDK (2.5.3) +* Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) * Updated: Moment JS (2.29.4) with WP Loading @@ -234,6 +234,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Improved: use WP JSON functions for data endpoints * Improved: Schedule Templates to use Classes and Instances * Improved: Tab Schedule default date display on +* Added: Freemius Pricing Page v2 * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show * Added: Volume Control options to Player widget diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index e447c4a..5ac3650 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -412,6 +412,7 @@ public function get_shift_data( $records, $split, $times ) { // --- shift is valid so continue checking --- // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to conver to 24 hour format first + // # midnight calculation $day = $shift['day']; $thisdate = $weekdates[$day]; $midnight = $this->to_time( $thisdate . ' 23:59:59', $timezone ) + 1; @@ -1749,6 +1750,7 @@ public function check_shifts( $all_shifts, $times = false ) { // --- account for split midnight times --- // 2.3.2: replace strtotime with to_time for timezones + // # midnight if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { $start_time = $this->to_time( $thisdate . ' 00:00', $timezone ); } else { @@ -2169,7 +2171,7 @@ public function check_shift( $record_id, $shift, $scope, $all_shifts, $times = f if ( $this->debug ) { echo "...with Shift for Show " . esc_html( $day_shift['show'] ) . ": "; echo esc_html( $day_shift['day'] ) . " - " . esc_html( $day_shift['date'] ) . " - " . esc_html( $day_shift['start'] ) . " (" . esc_html( $day_shift_start_time ) . ")"; - echo " to " . esc_html( $day_shift['end'] ) . " (" . esc-html( $day_shift_end_time ) . ")" . "\n"; + echo " to " . esc_html( $day_shift['end'] ) . " (" . esc_html( $day_shift_end_time ) . ")" . "\n"; } // 2.3.2: improved shift checking logic @@ -2195,7 +2197,7 @@ public function check_shift( $record_id, $shift, $scope, $all_shifts, $times = f // --- if there is a shift overlap conflict --- $conflicts[] = $day_shift; if ( $this->debug ) { - echo '^^^ CONFLICT ( ' . esc-html( $conflict ) . ' ) ^^^' . "\n"; + echo '^^^ CONFLICT ( ' . esc_html( $conflict ) . ' ) ^^^' . "\n"; } } } diff --git a/widgets/class-radio-player-widget.php b/widgets/class-radio-player-widget.php index fa507d8..b66d1cd 100644 --- a/widgets/class-radio-player-widget.php +++ b/widgets/class-radio-player-widget.php @@ -75,7 +75,7 @@ public function form( $instance ) { ' . esc_html( __( 'Stream or File URL', 'radio-station' ) ) . ':
    - ' . esc_html( 'Leave blank to use default stream URL.', 'radio-station' ) . ' + ' . esc_html( __( 'Leave blank to use default stream URL.', 'radio-station' ) ) . '

    '; // --- Station Text --- @@ -84,7 +84,7 @@ public function form( $instance ) { ' . esc_html( __( 'Player Station Text', 'radio-station' ) ) . ':
    - (' . esc_html( 'Empty for default, 0 for none.', 'radio-station' ) . ') + (' . esc_html( __( 'Empty for default, 0 for none.', 'radio-station' ) ) . ')

    '; // --- Station Image --- @@ -194,6 +194,7 @@ public function form( $instance ) { 'dark' => __( 'Dark', 'radio-station' ), ); $options = apply_filters( 'radio_station_player_theme_options', $options ); + $options = apply_filters( 'radio_player_theme_options', $options ); foreach ( $options as $option => $label ) { $field .= ''; } @@ -214,6 +215,7 @@ public function form( $instance ) { 'square' => __( 'Square', 'radio-station' ), ); $options = apply_filters( 'radio_station_player_button_options', $options ); + $options = apply_filters( 'radio_player_button_options', $options ); foreach ( $options as $option => $label ) { $field .= ''; } @@ -402,7 +404,7 @@ public function widget( $args, $instance ) { echo wp_kses( $args['after_title'], $allowed ); // --- get default display output --- - $output = radio_station_player_shortcode( $atts ); + $output = radio_player_shortcode( $atts ); // --- check for widget output override --- $output = apply_filters( 'radio_station_player_widget_override', $output, $args, $atts ); From 4299f3e31b671175abd984dfa5a976e5cb205768 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 10 Jan 2023 21:06:28 +1000 Subject: [PATCH 284/377] bump changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2d2de..6f858a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: use WP JSON functions for data endpoints * Improved: Schedule Templates to use Classes and Instances * Improved: Tab Schedule default date display on +* Added: Freemius Pricing Page v2 * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show * Added: Volume Control options to Player widget @@ -33,7 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Adjacent Post Links (where show has one shift) * Fixed: Workaround Amplitude pause event not firing - ### 2.4.0.9 * Update: Sysend (1.11.1) for Radio Player * Fixed: missing register REST routes permission_callback argument From 14249012dd2cf763c459c3ac6009bd87d5e1c47b Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 20 Jan 2023 08:57:42 +1000 Subject: [PATCH 285/377] dev updates bugfix and pricing page styles --- freemius-pricing/freemius-pricing.css | 12 +-- includes/post-types-admin.php | 120 +++++++++++++------------- scheduler/schedule-engine.php | 5 ++ 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/freemius-pricing/freemius-pricing.css b/freemius-pricing/freemius-pricing.css index dc46e1c..b7f250f 100644 --- a/freemius-pricing/freemius-pricing.css +++ b/freemius-pricing/freemius-pricing.css @@ -1,9 +1,9 @@ /* Radio Station - Freemius Pricing Page v2 Styles */ -.fs-packages .fs-plan-features ul li:first-child {display: none;} -.fs-packages li:first-child .fs-plan-features ul li:first-child {display: block; font-weight: bold;} -.fs-packages li:nth-child(3) .fs-plan-features ul li:nth-child(3) {font-weight: bold;} -.fs-packages .fs-plan-features ul li:nth-child(2) {font-weight: bold;} -.fs-packages li:first-child .fs-plan-features ul li:nth-child(2) {font-weight: normal;} +#fs_pricing_app .fs-packages .fs-plan-features li:first-child {display: none;} +#fs_pricing_app .fs-packages li:first-child .fs-plan-features li:first-child {display: block; font-weight: bold;} +#fs_pricing_app .fs-packages li:nth-child(3) .fs-plan-features li:nth-child(3) {font-weight: bold;} +#fs_pricing_app .fs-packages .fs-plan-features li:nth-child(2) {font-weight: bold;} +#fs_pricing_app .fs-packages li:first-child .fs-plan-features li:nth-child(2) {font-weight: normal;} -#fs_pricing_app .fs-packages .fs-button {background: #2da1d0;} +#fs_pricing_app .fs-packages .fs-button {color: #FFF; background: #2da1d0;} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index ab48e61..389b64b 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2677,6 +2677,7 @@ function radio_station_show_column_data( $column, $post_id ) { $shifts = get_post_meta( $post_id, 'show_sched', true ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + // 2.5.0: fix variable inconsistency for sorted shifts $sorted_shifts = $dayless_shifts = array(); foreach ( $shifts as $shift ) { // 2.3.2: added check that shift day is not empty @@ -2686,77 +2687,80 @@ function radio_station_show_column_data( $column, $post_id ) { $shift_time = radio_station_convert_shift_time( $shift_time ); $shift_time = $weekdates[$shift['day']] . $shift_time; $timestamp = radio_station_to_time( $shift_time ); - $sortedshifts[$timestamp] = $shift; + $sorted_shifts[$timestamp] = $shift; } else { $dayless_shifts[] = $shift; } } - ksort( $sortedshifts ); - foreach ( $sortedshifts as $shift ) { + // 2.5.0: added count check for sorted_shifts + if ( count( $sorted_shifts ) > 0 ) { + ksort( $sorted_shifts ); + foreach ( $sorted_shifts as $shift ) { - // 2.3.0: highlight disabled shifts - $classes = array( 'show-shift' ); - $disabled = false; - $title = ''; - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $disabled = true; - $classes[] = 'disabled'; - $title = __( 'This Shift is Disabled.', 'radio-station' ); - } - - // --- check and highlight conflicts --- - // 2.3.0: added shift conflict checking - $conflicts = radio_station_check_shift( $post_id, $shift ); - if ( $conflicts ) { - $classes[] = 'conflict'; - if ( $disabled ) { - $title = __( 'This Shift has Schedule Conflicts and is Disabled.', 'radio-station' ); - } else { - $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); - } - } - - // 2.3.0: also highlight if the show is not active - if ( !$active ) { - if ( !in_array( 'disabled', $classes ) ) { + // 2.3.0: highlight disabled shifts + $classes = array( 'show-shift' ); + $disabled = false; + $title = ''; + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $disabled = true; $classes[] = 'disabled'; + $title = __( 'This Shift is Disabled.', 'radio-station' ); } - $title = __( 'This Show is not currently active.', 'radio-station' ); - } - $classlist = implode( ' ', $classes ); - - echo '
    ' . "\n"; - - // --- get shift start and end times --- - // 2.3.2: fix to convert to 24 hour time - $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; - $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - $start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - $end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - - // --- make weekday filter selections bold --- - // 2.3.0: fix to bolding only if weekday isset - $bold = false; - if ( isset( $_GET['weekday'] ) ) { - $weekday = trim( sanitize_text_field( $_GET['weekday'] ) ); - $nextday = radio_station_get_next_day( $weekday ); - // 2.3.0: handle shifts that go overnight for weekday filter - if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { - echo ''; - $bold = true; + + // --- check and highlight conflicts --- + // 2.3.0: added shift conflict checking + $conflicts = radio_station_check_shift( $post_id, $shift ); + if ( $conflicts ) { + $classes[] = 'conflict'; + if ( $disabled ) { + $title = __( 'This Shift has Schedule Conflicts and is Disabled.', 'radio-station' ); + } else { + $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); } } - echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); - echo ' ' . esc_html( $start ) . ' - ' . esc_html( $end ); - if ( $bold ) { - echo ''; + // 2.3.0: also highlight if the show is not active + if ( !$active ) { + if ( !in_array( 'disabled', $classes ) ) { + $classes[] = 'disabled'; + } + $title = __( 'This Show is not currently active.', 'radio-station' ); } + $classlist = implode( ' ', $classes ); + + echo '
    ' . "\n"; + + // --- get shift start and end times --- + // 2.3.2: fix to convert to 24 hour time + $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + + // --- make weekday filter selections bold --- + // 2.3.0: fix to bolding only if weekday isset + $bold = false; + if ( isset( $_GET['weekday'] ) ) { + $weekday = trim( sanitize_text_field( $_GET['weekday'] ) ); + $nextday = radio_station_get_next_day( $weekday ); + // 2.3.0: handle shifts that go overnight for weekday filter + if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { + echo ''; + $bold = true; + } + } - echo '
    ' . "\n"; + echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); + echo ' ' . esc_html( $start ) . ' - ' . esc_html( $end ); + if ( $bold ) { + echo ''; + } + + echo '
    ' . "\n"; + } } // --- dayless shifts --- diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index 5ac3650..82b94ba 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -10,6 +10,11 @@ // - Set Scheduler Debug Mode // - Open Schedule Engine Class // - Class Constructor +// === Caching === +// - Set Current Schedule +// - Set Previous Shift +// - Set Current Shift +// - Set Next Shift // === Schedule === // - Get Record Shifts // - Generate Unique Shift ID From 8d7993cfbbe86df831efcbe97b2e32c5d4a1838f Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 9 Feb 2023 16:05:32 +1000 Subject: [PATCH 286/377] block fixes --- blocks/current-playlist.js | 2 +- blocks/player.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/blocks/current-playlist.js b/blocks/current-playlist.js index 02c1f3a..e58e191 100644 --- a/blocks/current-playlist.js +++ b/blocks/current-playlist.js @@ -8,7 +8,7 @@ const { registerBlockType } = window.wp.blocks; const { InspectorControls } = window.wp.blockEditor; const { Fragment } = window.wp.element; - const { TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; + const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, Panel, PanelBody, PanelRow } = window.wp.components; const { __, _e } = window.wp.i18n; registerBlockType( 'radio-station/current-playlist', { diff --git a/blocks/player.js b/blocks/player.js index a991b96..bf4f8b5 100644 --- a/blocks/player.js +++ b/blocks/player.js @@ -7,6 +7,7 @@ const el = window.wp.element.createElement; const { serverSideRender: ServerSideRender } = window.wp; const { registerBlockType } = window.wp.blocks; + const { getBlockType } = window.wp.blocks; const { InspectorControls } = window.wp.blockEditor; const { Fragment } = window.wp.element; const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, ColorPicker, Dropdown, Button, Panel, PanelBody, PanelRow } = window.wp.components; From 7726ff630b7fe7981bccb85a50ce1abc6f02ef21 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 31 Mar 2023 14:42:22 +1000 Subject: [PATCH 287/377] fix pro doc line spacing --- docs/Pro.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/Pro.md b/docs/Pro.md index 1971ca9..53a4914 100644 --- a/docs/Pro.md +++ b/docs/Pro.md @@ -24,17 +24,21 @@ Note that the following features are *not* available in Radio Station Plus: #### API [Episodes Data Endpoint](./API.md#pro-episodes-endpoint) + [Hosts Data Endpoint](./API.md#pro-hosts-endpoint) + [Producers Data Endpoint](./API.md#pro-producers-endpoint) #### Data [Show Episodes](./Data.md#pro-show-episodes) + [Host and Producer Profiles](./Data.md#pro-host-and-producer-profiles) #### Display [Profile Images](./Display.md#pro-profile-images) + [Genre Images and Colors](./Display.md#genre-images-and-colors) #### Filters @@ -56,19 +60,30 @@ Note that the following features are *not* available in Radio Station Plus: #### Shortcodes [Schedule Grid View](./Shortcodes.md#master-schedule-shortcode) + [Schedule Calendar View](./Shortcodes.md#master-schedule-shortcode) + [Multiple View Switching](./Shortcodes.md#pro-multiple-view-switching) + [Timezone Switching](./Shortcodes.md#pro-user-timezone-switching) + [Hosts Archive Shortcode](./Shortcodes.md#pro-hosts-archive-shortcode) + [Producers Archive Shortcode](./Shortcodes.md#pro-producers-archive-shortcode) + [Team Archive Shortcode](./Shortcodes.md#pro-team-archive-shortcode) + [Show Episodes Archive Shortcode](./Shortcodes.md#pro-show-episodes-archive-shortcode) #### Widgets -[Sitewide Bar Player(./Player.md#pro-sitewide-bar-player) +[Sitewide Bar Player](./Player.md#pro-sitewide-bar-player) + [Timezone Switcher](./Widgets.md#pro-timezone-switcher) + [Extra Block Options](./Widgets.md#pro-extra-block-options) + [Elementor Widgets](./Widgets.md#pro-elementor-widgets) + [Beaver Builder Modules]('./Widgets.md#pro-beaver-builder-modules) From a2d837fc0b2bf6f24b3bebb8b055b363ba5cfc10 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 25 Apr 2023 16:19:33 +1000 Subject: [PATCH 288/377] dev updates incl Freemius 2.5.5 --- CHANGELOG.md | 6 +- docs/FAQ-new.md | 218 +++ docs/FAQ.md | 76 +- freemius/assets/css/admin/common.css | 2 +- freemius/config.php | 2 +- freemius/includes/class-freemius.php | 1024 +++++------- freemius/includes/class-fs-api.php | 266 +-- freemius/includes/class-fs-plugin-updater.php | 64 +- .../includes/entities/class-fs-plugin.php | 5 + freemius/includes/entities/class-fs-site.php | 5 +- .../managers/class-fs-gdpr-manager.php | 12 - .../managers/class-fs-permission-manager.php | 15 +- freemius/includes/sdk/FreemiusWordPress.php | 1453 +++++++++-------- freemius/languages/freemius-fr_FR.mo | Bin 68762 -> 58948 bytes freemius/languages/freemius-he_IL.mo | Bin 67872 -> 54828 bytes freemius/languages/freemius-hu_HU.mo | Bin 66410 -> 48200 bytes freemius/languages/freemius-it_IT.mo | Bin 67300 -> 62258 bytes freemius/languages/freemius-ja.mo | Bin 72288 -> 60542 bytes freemius/languages/freemius-nl_NL.mo | Bin 67088 -> 57340 bytes freemius/languages/freemius-ru_RU.mo | Bin 79360 -> 64951 bytes freemius/languages/freemius-ta.mo | Bin 95123 -> 85287 bytes freemius/languages/freemius-zh_CN.mo | Bin 62810 -> 57962 bytes freemius/languages/freemius.pot | 723 ++++---- freemius/start.php | 2 +- .../templates/api-connectivity-message-js.php | 32 + freemius/templates/checkout.php | 11 +- freemius/templates/connect.php | 50 +- freemius/templates/debug.php | 12 +- freemius/templates/firewall-issues-js.php | 62 - .../templates/forms/license-activation.php | 2 + freemius/templates/forms/optout.php | 2 +- includes/data-feeds.php | 15 +- includes/legacy.php | 6 +- includes/master-schedule.php | 2 +- includes/schedules.php | 5 +- includes/templates.php | 2 +- loader.php | 2 +- player/compat.php | 13 + player/radio-player.php | 6 +- radio-station-admin.php | 4 +- radio-station.php | 6 +- readme.md | 4 +- readme.txt | 12 +- templates/single-show-content.php | 6 +- 44 files changed, 2151 insertions(+), 1976 deletions(-) create mode 100644 docs/FAQ-new.md create mode 100644 freemius/templates/api-connectivity-message-js.php delete mode 100644 freemius/templates/firewall-issues-js.php create mode 100644 player/compat.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f858a4..26fb58a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -### 2.5.0 += 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.5.3) +* Updated: Freemius SDK (2.5.5) * Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: use WP JSON functions for data endpoints * Improved: Schedule Templates to use Classes and Instances * Improved: Tab Schedule default date display on +* Improved: use wp_send_json for feed endpoints * Added: Freemius Pricing Page v2 * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show @@ -33,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) * Fixed: Workaround Amplitude pause event not firing +* Security Fix: Escape all debug output content ### 2.4.0.9 * Update: Sysend (1.11.1) for Radio Player diff --git a/docs/FAQ-new.md b/docs/FAQ-new.md new file mode 100644 index 0000000..0d95dd1 --- /dev/null +++ b/docs/FAQ-new.md @@ -0,0 +1,218 @@ +# Radio Station Plugin FAQ + +*** + +## Getting Started + +### How do I get started with Radio Station (Free or PRO)? + +Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. + +### Where can I find the full Radio Station documentation (Free or PRO)? + +The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. + +### How do I get support for Radio Station (Free or PRO)? + +For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. + +### What is the difference between Radio Station (Free) and Radio Station PRO? + +Radio Station PRO provides both enhancements to existing Free features, as well as introducing a number of additional standalone features: + +- Persistant Player Bar +- Dynamic Widget Reloading +- ... + +### Can I try Radio Station PRO before I purchase the plugin? + +Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card number when you sign up for the free trial and can cancel any time before the trial is up. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date of your registration. [Click here to start your trial.](https://radiostation.pro/pricing) + + +## Account and Billing + +### How do I access my Radio Station PRO account? + +We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. + +### Can I request a refund for Radio Station PRO? + +We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. + +### How do I cancel my Radio Station PRO subscription? + +Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bugfixes and feature additions. + + +## Plugin Usage + +### How do I schedule a Show? + +Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. +In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. + +### How do I display a full schedule of my Station's shows? + +Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) + +### What if I want to schedule a special event or one-off schedule change? + +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) + +### How do I change the Show Image displayed in the widgets and schedule? + +The widgets and schedule will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station > Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Simply set a new image for the Show. + +### How do I style how the plugin displays content on the front end of my site? + +The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. + + +## Widgets, Blocks and Shortcodes + +### What Widgets/Blocks are available with this plugin? + +The following Widgets are available to add via the WordPress Appearance > Widgets page: + +- Streaming Player +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. + +### Do the Widgets reload automatically? + +In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). + +Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) + +### What Shortcodes are available in Radio Station? + +See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: + +* `[master-schedule]` - Master Program Schedule Display +* `[current-show]` - Current Show Widget +* `[upcoming-shows]` - Upcoming Shows Widget +* `[current-playlist]` - Current Playlist Widget +* `[shows-archive]` - Archive List of Shows +* `[genres-archive]` - Archive List of Shows sorted by Genre +* `[languages-archive]` - Archive List of Shows sorted by Language +* `[overrides-archive]` - Archive List of Schedule overrides +* `[playlists-archive]` - Archive List of Show Playlists + +(Note old shortcode aliases will still work in current and future versions to prevent breakage.) + +### Do you include Page Builder support? + +The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). + + +## User Plugin Roles + +### How do I assign a User as the Host or Producer to one or more Shows? + +You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard > Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). + +### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? + +There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it' +s Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). + +### How do I use a different image from the Gravatar for a Host/Producer? + +If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). + + +## Languages and Translations + +### What languages other than English is the plugin available in? + +As of April 1st, 2023, known languages include the following: + +* Albanian (sq_AL) +* Dutch (nl_NL) +* French (fr_FR) +* German (de_DE) +* Italian (it_IT) +* Russian (ru_RU) +* Serbian (sr_RS) +* Spanish (es_ES) +* Catalan (ca) + +### Can Radio Station be translated into my language? + +You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. + + +## Troubleshooting + +### Why aren't all my Shows displaying in the Schedule? + +Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. + +### I'm seeing a 404 Not Found error when I click on the link for a Show! + +Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. + +### Why can't Show Hosts or Producers can't edit a Show page? + +The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. + +### Where is my data stored? Can I export my data? + +Radio Station is stores your site's settings and all post-type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard > Tools > Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard > Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. + + +## Integrations + +### Can I use this plugin for Podcasts? + +While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. + +### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? + +Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. + +### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? + +We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. + +### Can I import Show datae from Pro.Radio or the JOAN (Jock on Air Now) plugin? + +We do not have a method of importing data directly from JOAN or Pro.Radio + + +## Development Versions + +### How do I install the latest Development version for testing? + +If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: + +1. Visit the `develop` branch of the Radio Station Github repository at: +`https://github.com/netmix/radio-station/tree/develop/` +2. Click on the green "Code" button and select Download a ZIP. +3. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-develop/` +4. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old/` +5. Rename the subdirectory `/wp-content/plugins/radio-station-develop/` to `/wp-content/plugins/radio-station/` + +Then upload to WordPress via the plugin installer as normal. +Note that it will install to /wp-content/plugins/radio-station-develop/, and because of this won't overwrite your existing installation, so you'll need to deactivate that before activating the development version. + +You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. + +Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. + + +### What about Pro Beta Version Testing? + +We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: + +1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://users.freemius.com/login). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. +2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. + +**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github (if the required version is not yet available via the WordPress repository.) + +We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. + diff --git a/docs/FAQ.md b/docs/FAQ.md index 5bc6b2e..4e2e5ca 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -2,37 +2,66 @@ *** -### How do I get started with Radio Station? +### How do I get started with Radio Station (Free or PRO)? Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. -### Where can I find the full plugin documentation? +### Where can I find the full Radio Station documentation (Free or PRO)? -The latest documentation can be found online at [NetMix.com](https://netmix.com/radio-station/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. + +### How do I get support for Radio Station (Free or PRO)? + +For Radio Station customers using the free, open-source version of our plugin, you can contact us in our support channel in the [WordPress support forums here](https://wordpress.org/support/plugin/radio-station/. For Radio Station PRO subscribers, you can email us at [support@radiostation.pro](mailto:support@radiostation.pro) and someone will respond to your inquiry within 12 to 24 hours. All support inquiries will be handled in the order they are received. Before contacting support, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station (Free and PRO). + +### How do I access my Radio Station PRO account? + +We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. + +### Can I try Radio Station PRO before I purchase the plugin? + +Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card number when you sign up for the free trial and can cancel any time before the trial is up. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date of your registration. + +### Can I request a refund for Radio Station PRO? + +We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30-day period is exhausted refunds are not available. + +### How do I cancel my Radio Station PRO subscription? + +Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bugfixes and feature additions. ### How do I schedule a Show? Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. +In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Stationn menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. ### How do I display a full schedule of my Station's shows? -In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) +Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) -### I've scheduled all my Shows, but some are not showing up on the program schedule? +### Why aren't all my Shows displaying in the program schedule? Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. ### What if I want to schedule a special event or one-off schedule change? -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by clicking Schedule Override in the Radio Station admin menu. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. + +### Where is my data stored? Can I export my data? + +Radio Station PRO is a WordPress plugin, which functions similarly to other WordPress themes and plugins by storing your site's settings and all post-type data in your WordPress MySQL database, which is connected to your website and accessed through your WordPress hosting account with the provider you chose. Similarly to all WordPress themes and plugins, when changing settings or adding content to a WordPress website, Radio Station (free and PRO) stores data in the MySQL database as it is added, edited, and published, updated, or saved. You can export your data using WordPress Dashboard > Tools > Export feature or Radio Station PRO’s Export feature located at WordPress Dashboard > Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. -### I'm seeing 404 Not Found errors when I click on the link for a Show! +### Can I import Show datae from Pro.Radio or the JOAN (Jock on Air Now) plugin? -Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin. +We do not have a method of importing data directly from JOAN or Pro.Radio -### What if I want to change or style the plugin's displays? +### I'm seeing a 404 Not Found error when I click on the link for a Show! -The default styles for Radio Station have intionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. +Try re-saving your site's permalink settings via Settings -> Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. + +### What if I want to style how Radio Station's schedule displays on the frond end? + +The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. ### What Widgets are available with this plugin? @@ -41,9 +70,9 @@ Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player ### Do the Widgets reload automatically? -Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in this free version. This capability has been added as a new feature to the [Pro version](https://radiostation.pro) however - so that the widgets refresh exactly at Show changeover times. +Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) -### What Shortcodes are available with this plugin? +### What Shortcodes are available in Radio Station? See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: @@ -57,25 +86,28 @@ See the [Shortcode Documentation](./Shortcodes.md) for more details and a full l * `[overrides-archive]` - Archive List of Schedule overrides * `[playlists-archive]` - Archive List of Show Playlists -Note old shortcode aliases will still work in current and future versions to prevent breakage. +(Note old shortcode aliases will still work in current and future versions to prevent breakage.) + +### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? + +There are a number of different options depending on what your goals are.To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. This may help keep clear lines of separation for editorial responsibility over your content, as you may not want users with Autors or Conftirubyute roles to edit Shows, but to give a specific user the ability to do so. You can find more information on roles in the [Roles Documentation](./Roles.md) -### I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? +### How do I change the Show Image displayed in the widgets and schedule? -There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](./Roles.md) +The widgets and schedule will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station > Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Simply set a new image for the Show. -### How do I change the Show Avatar displayed in the sidebar widget? +### Why don’t all WordPress Users appear in the Hosts or Producers lists on a Show Edit screen? -The avatar is whatever image is assigned as the Show's Avatar. All you have to do is set a new Show Avatar on the Edit page for that Show. +You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit screen. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. -### Why don't any users show up in the Hosts or Producers list on the Show edit page? = +### Why can't Show Hosts or Producers can't edit a Show page? -You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit page. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. +The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. -### My Show Hosts and Producers can't edit a Show page. What do I do? +### How do I use a different image from the Gravatar for a Host/Producer? -The only Hosts and Producers that can edit a show are the ones listed as being Hosts or Producers for that Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Host/Producer's Shows without permission. +If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows lets you the ability to add a different image to your Host/Producer's Uuser account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do just this, it's a little out mostly out of the scope of this plugin’s features. However, in our the Pro version, you can create separate profile pages to showcase each of your Hosts and Producers, to which, you can and assign profile images that appear on those to these profile pages. If you would like to enable this feature, pelase upgrade here. -### I don't want to use Gravatar for my Host/Producer's image on their profile page. Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css index ae55727..900103a 100644 --- a/freemius/assets/css/admin/common.css +++ b/freemius/assets/css/admin/common.css @@ -1 +1 @@ -.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:#fff;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);box-shadow:0 2px 1px -1px rgba(0,0,0,.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,.1),inset 0 1px 3px 0 rgba(0,0,0,.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.5);z-index:999;-moz-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}body.fs-loading,body.fs-loading *{cursor:wait !important}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media(max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}}.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,.3);box-shadow:0 2px 2px rgba(6,113,6,.3);opacity:.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:"↳";padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:"↲"}.fs-submenu-item.pricing.upgrade-mode{color:#adff2f}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:#fff;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);box-shadow:0 2px 1px -1px rgba(0,0,0,.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,.1),inset 0 1px 3px 0 rgba(0,0,0,.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.5);z-index:999;-moz-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}body.fs-loading,body.fs-loading *{cursor:wait !important}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media(max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}}.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}#fs_connect .fs-error ol,#fs_connect .fs-error .fs-api-request-error-show-details-link,#fs_connect .fs-error .fs-api-request-error-details,.fs-modal .notice-error ol,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-modal .notice-error .fs-api-request-error-details,.fs-notice.error ol,.fs-notice.error .fs-api-request-error-show-details-link,.fs-notice.error .fs-api-request-error-details{text-align:left}#fs_connect .fs-error ol,.fs-modal .notice-error ol,.fs-notice.error ol{list-style-type:disc}#fs_connect .fs-error .fs-api-request-error-show-details-link,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-notice.error .fs-api-request-error-show-details-link{text-decoration:none;color:#2271b1;box-shadow:none}#fs_connect .fs-error .fs-api-request-error-details,.fs-modal .notice-error .fs-api-request-error-details,.fs-notice.error .fs-api-request-error-details{border:1px solid #ccc;padding:5px;overflow:auto;max-height:150px}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,.3);box-shadow:0 2px 2px rgba(6,113,6,.3);opacity:.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:"↳";padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:"↲"}.fs-submenu-item.pricing.upgrade-mode{color:#adff2f}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/config.php b/freemius/config.php index b86887c..a9a3f1b 100644 --- a/freemius/config.php +++ b/freemius/config.php @@ -146,7 +146,7 @@ #-------------------------------------------------------------------------------- if ( ! defined( 'WP_FS__IS_HTTP_REQUEST' ) ) { - define( 'WP_FS__IS_HTTP_REQUEST', isset( $_SERVER['HTTP_HOST'] ) ); + define( 'WP_FS__IS_HTTP_REQUEST', isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_METHOD'] ) ); } if ( ! defined( 'WP_FS__IS_HTTPS' ) ) { diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index 543c193..1cafc38 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -1536,13 +1536,17 @@ private function register_constructor_hooks() { add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); /** - * Handle request to reset anonymous mode for `get_reconnect_url()`. + * Handle request to reset anonymous mode for `get_reconnect_url()` or reset the pending activation mode. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ - if ( fs_request_is_action( 'reset_anonymous_mode' ) && - $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) + if ( + ( + fs_request_is_action( 'reset_anonymous_mode' ) || + fs_request_is_action( 'reset_pending_activation_mode' ) + ) && + $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) ) { add_action( 'admin_init', array( &$this, 'connect_again' ) ); } @@ -4030,42 +4034,58 @@ private function should_run_connectivity_test( $flush_if_no_connectivity = false } /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 + * @author Leo Fajardo (@leorw) + * @since 2.5.4 * - * @param int|null $blog_id Since 2.0.0. - * @param bool $is_gdpr_test Since 2.0.2. Perform only the GDPR test. + * @param bool $is_update * - * @return object|false + * @return bool */ - private function ping( $blog_id = null, $is_gdpr_test = false ) { - if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) { - return false; + private function should_turn_fs_on( $is_update = true ) { + if ( + empty( $this->_plugin->opt_in_moderation ) || + ! is_array( $this->_plugin->opt_in_moderation ) + ) { + return true; } - $version = $this->get_plugin_version(); + $optin_config = $this->_plugin->opt_in_moderation; - $is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ); + if ( + WP_FS__IS_LOCALHOST && + ( ! isset( $optin_config['localhost'] ) || false !== $optin_config['localhost'] ) + ) { + return true; + } - $params = array( - 'is_update' => json_encode( $is_update ), - 'version' => $version, - 'sdk' => $this->version, - 'is_admin' => json_encode( is_admin() ), - 'is_ajax' => json_encode( self::is_ajax() ), - 'is_cron' => json_encode( self::is_cron() ), - 'is_gdpr_test' => $is_gdpr_test, - 'is_http' => json_encode( WP_FS__IS_HTTP_REQUEST ), - ); + $optin_config_key = $is_update ? + 'updates' : + 'new'; - if ( is_multisite() && function_exists( 'get_network' ) ) { - $params['network_uid'] = $this->get_anonymous_network_id(); + if ( ! isset( $optin_config[ $optin_config_key ] ) ) { + return true; } - return $this->get_api_plugin_scope()->ping( - $this->get_anonymous_id( $blog_id ), - $params - ); + $visibility_percentage = $optin_config[ $optin_config_key ]; + + if ( 0 == $visibility_percentage ) { + return false; + } + + if ( ! is_numeric( $visibility_percentage ) ) { + return true; + } + + $min = 1; + $max = 100; + + if ( function_exists( 'random_int' ) ) { + $random = random_int( $min, $max ); + } else { + $random = rand( $min, $max ); + } + + return ( $random <= $visibility_percentage ); } /** @@ -4076,7 +4096,7 @@ private function ping( $blog_id = null, $is_gdpr_test = false ) { * * @param bool $flush_if_no_connectivity * - * @return bool + * @return bool|null */ function has_api_connectivity( $flush_if_no_connectivity = false ) { $this->_logger->entrance(); @@ -4089,7 +4109,7 @@ function has_api_connectivity( $flush_if_no_connectivity = false ) { isset( $this->_storage->connectivity_test ) && true === $this->_storage->connectivity_test['is_connected'] ) { - unset( $this->_storage->connectivity_test ); + $this->clear_connectivity_info(); } if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { @@ -4106,36 +4126,47 @@ function has_api_connectivity( $flush_if_no_connectivity = false ) { return $this->_has_api_connection; } - $pong = $this->ping(); - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + if ( + ! empty( $this->_storage->connectivity_test ) && + isset( $this->_storage->connectivity_test['is_active'] ) + ) { + $is_active = $this->_storage->connectivity_test['is_active']; + } else { + $is_active = $this->should_turn_fs_on( $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ) ); - if ( ! $is_connected ) { - // API failure. - $this->_add_connectivity_issue_message( $pong ); + $this->store_connectivity_info( (object) array( 'is_active' => $is_active ), null ); } - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + if ( $is_active ) { + $this->_is_on = true; } - - $this->store_connectivity_info( $pong, $is_connected ); return $this->_has_api_connection; } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + private function clear_connectivity_info() { + unset( $this->_storage->connectivity_test ); + + FS_Api::clear_force_http_flag(); + } + /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * - * @param object $pong - * @param bool $is_connected + * @param object $pong + * @param bool|null $is_connected */ private function store_connectivity_info( $pong, $is_connected ) { $this->_logger->entrance(); $version = $this->get_plugin_version(); - if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { + if ( false === $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { $is_active = false; } else { $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); @@ -4162,6 +4193,20 @@ private function store_connectivity_info( $pong, $is_connected ) { $this->_is_on = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param bool $is_connected + */ + private function update_connectivity_info( $is_connected ) { + $this->store_connectivity_info( + // This is true since we update the connection info only after a successful opt-in or license activation which means that Freemius has already been on even before the process. + (object) array( 'is_active' => true ), + $is_connected + ); + } + /** * Force turning Freemius on. * @@ -4359,379 +4404,6 @@ static function is_valid_email( $email ) { return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); } - /** - * Generate API connectivity issue message. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param mixed $api_result - * @param bool $is_first_failure - */ - function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) { - if ( ! $this->is_premium() && $this->_enable_anonymous ) { - // Don't add message if it's the free version and can run anonymously. - return; - } - - if ( ! function_exists( 'wp_nonce_url' ) ) { - require_once ABSPATH . 'wp-includes/functions.php'; - } - - $current_user = self::_get_current_wp_user(); -// $admin_email = get_option( 'admin_email' ); - $admin_email = $current_user->user_email; - - // Aliases. - $deactivate_plugin_title = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' ); - $deactivate_plugin_desc = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' ); - $install_previous_title = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' ); - $install_previous_desc = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' ); - $fix_issue_title = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' ); - $fix_issue_desc = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' ); - /* translators: %s: product title (e.g. "Awesome Plugin" requires access to...) */ - $x_requires_access_to_api = $this->esc_html_inline( '%s requires access to our API.', 'x-requires-access-to-api' ); - $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' ); - $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' ); - - if ( $this->is_premium() ) { - /* translators: This string is optionally prepended to 'plugin requires access to our API.' */ - $x_requires_access_to_api = $this->esc_html_inline( 'For automatic delivery of security & feature updates,', 'requires-api-for' ) . ' ' . $x_requires_access_to_api; - } - - $message = false; - if ( is_object( $api_result ) && - isset( $api_result->error ) && - isset( $api_result->error->code ) - ) { - switch ( $api_result->error->code ) { - case 'curl_missing': - $missing_methods = ''; - if ( is_array( $api_result->missing_methods ) && - ! empty( $api_result->missing_methods ) - ) { - foreach ( $api_result->missing_methods as $m ) { - if ( 'curl_version' === $m ) { - continue; - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods .= ', '; - } - - $missing_methods .= sprintf( '%s', $m ); - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods = sprintf( - '

    %s %s', - $this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ), - $missing_methods - ); - } - } - - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' . - $missing_methods . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ), - ' - ' . sprintf( - $this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'cloudflare_ddos_protection': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'squid_cache_block': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s - %s', - $this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ), - sprintf( - $this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - sprintf( - $this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ), - // We use a filter since the plugin might require additional API connectivity. - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '', - $this->_module_type - ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; -// default: -// $message = $this->get_text_inline( 'connectivity-test-fails-message' ); -// break; - } - } - - $message_id = 'failed_connect_api'; - $type = 'error'; - - $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' ); - - if ( false === $message ) { - if ( $is_first_failure ) { - // First attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '

    ' . - '%s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    %s %s
    ', - sprintf( - '%s', - $this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' ) - ), - sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $this->get_text_inline( 'No - just deactivate', 'no-deactivate' ) - ) - ) - ); - - $message_id = 'failed_connect_api_first'; - $type = 'promotion'; - } else { - // Second connectivity attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - } - } - - $this->_admin_notices->add_sticky( - $message, - $message_id, - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - $type - ); - } - - /** - * Handle user request to resolve connectivity issue. - * This method will send an email to Freemius API technical staff for resolution. - * The email will contain server's info and installed plugins (might be caching issue). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function _email_about_firewall_issue() { - check_admin_referer( 'fs_resolve_firewall_issues' ); - - if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { - return; - } - - $this->_admin_notices->remove_sticky( 'failed_connect_api' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); - exit; - } - - $current_user = self::_get_current_wp_user(); - $admin_email = $current_user->user_email; - - $error_type = fs_request_get( 'error_type', 'general' ); - - switch ( $error_type ) { - case 'squid': - $title = 'Squid ACL Blocking Issue'; - break; - case 'cloudflare': - $title = 'CloudFlare Blocking Issue'; - break; - default: - $title = 'API Connectivity Issue'; - break; - } - - $custom_email_sections = array(); - - // Add 'API Error' custom email section. - $custom_email_sections['api_error'] = array( - 'title' => 'API Error', - 'rows' => array( - 'ping' => array( - 'API Error', - is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong ) - ), - ) - ); - - // Send email with technical details to resolve API connectivity issues. - $this->send_email( - 'api@freemius.com', // recipient - $title . ' [' . $this->get_plugin_name() . ']', // subject - $custom_email_sections, - array( "Reply-To: $admin_email <$admin_email>" ) // headers - ); - - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ), - '' . $admin_email . '' - ), - 'server_details_sent' - ); - - // Action was taken, tell that API connectivity troubleshooting should be off now. - - echo "1"; - exit; - } - - /** - * Handle connectivity test retry approved by the user. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - */ - function _retry_connectivity_test() { - check_admin_referer( 'fs_retry_connectivity_test' ); - - if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { - return; - } - - $this->_admin_notices->remove_sticky( 'failed_connect_api_first' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); - } else { - // Add connectivity issue message after 2nd failed attempt. - $this->_add_connectivity_issue_message( $pong, false ); - - echo "1"; - } - - exit; - } - - static function _add_firewall_issues_javascript() { - $params = array(); - fs_require_once_template( 'firewall-issues-js.php', $params ); - } - #endregion #---------------------------------------------------------------------------------- @@ -4966,6 +4638,36 @@ function dynamic_init( array $plugin_info ) { $this->register_after_settings_parse_hooks(); + /** + * If anonymous but there's already a user entity and the user's site is associated with a valid license or trial period, update the anonymous mode accordingly. + * + * @todo Remove this entire `if` block after several releases as starting from this version, the anonymous mode will already be updated accordingly after a purchase. + */ + if ( $this->is_anonymous() ) { + $is_network_level = ( $this->_is_network_active && fs_is_network_admin() ); + + if ( + ! $is_network_level || + FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) + ) { + if ( $this->is_paying_or_trial() ) { + $this->reset_anonymous_mode( $is_network_level ); + } + } else { + $network = get_network(); + + if ( is_object( $network ) ) { + $main_blog_id = $network->site_id; + $first_install = $this->get_install_by_blog_id( $main_blog_id ); + + if ( is_object( $first_install ) ) { + $this->_storage->network_install_blog_id = $main_blog_id; + $this->_storage->network_user_id = $first_install->user_id; + } + } + } + } + if ( $this->should_stop_execution() ) { return; } @@ -4976,50 +4678,9 @@ function dynamic_init( array $plugin_info ) { $this->_has_api_connection = true; $this->_is_on = true; } else { - if ( ! $this->has_api_connectivity() ) { - if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) || - $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { - if ( ! $this->_enable_anonymous || $this->is_premium() ) { - // If anonymous mode is disabled, add firewall admin-notice message. - add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) ); - - $ajax_action_suffix = $this->_slug . ( $this->is_theme() ? ':theme' : '' ); - add_action( "wp_ajax_fs_resolve_firewall_issues_{$ajax_action_suffix}", array( - &$this, - '_email_about_firewall_issue' - ) ); - - add_action( "wp_ajax_fs_retry_connectivity_test_{$ajax_action_suffix}", array( - &$this, - '_retry_connectivity_test' - ) ); - - /** - * Currently the admin notice manager relies on the module's type and slug. The new AJAX actions manager uses module IDs, hence, consider to replace the if block above with the commented code below after adjusting the admin notices manager to work with module IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - /*$this->add_ajax_action( 'resolve_firewall_issues', array( - &$this, - '_email_about_firewall_issue' - ) ); - - $this->add_ajax_action( 'retry_connectivity_test', array( - &$this, - '_retry_connectivity_test' - ) );*/ - } - } - + if ( false === $this->has_api_connectivity() ) { return; } else { - $this->_admin_notices->remove_sticky( array( - 'failed_connect_api_first', - 'failed_connect_api', - ) ); - if ( $this->_anonymous_mode ) { // Simulate anonymous mode. $this->_is_anonymous = true; @@ -5764,6 +5425,7 @@ private function parse_settings( &$plugin_info ) { 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ), 'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ), 'bundle_public_key' => $this->get_option( $plugin_info, 'bundle_public_key', null ), + 'opt_in_moderation' => $this->get_option( $plugin_info, 'opt_in', null ), ) ); if ( $plugin->is_updated() ) { @@ -5922,10 +5584,7 @@ private function should_stop_execution() { return true; } - if ( self::is_ajax() && - ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) && - ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { + if ( self::is_ajax() ) { /** * During activation, if running in AJAX mode, unless there's a sticky * connectivity issue notice, don't run Freemius. @@ -6021,14 +5680,13 @@ function _plugin_code_type_changed() { $this->do_action( 'after_free_version_reactivation' ); if ( $this->is_paying() && ! $this->is_premium() ) { - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( /* translators: %s: License type (e.g. you have a professional license) */ $this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ), $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ), + 'plan_upgraded' ); } } @@ -7299,31 +6957,87 @@ function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { * @author Vova Feldman (@svovaf) * @since 1.0.7 * - * @param bool|string $email + * @param bool|string $email_address * @param bool $is_pending_trial Since 1.2.1.5 * @param bool $is_suspicious_email Since 2.5.0 Set to true when there's an indication that email address the user opted in with is fake/dummy/placeholder. + * @param bool $has_upgrade_context Since 2.5.3 + * @param bool $support_email_address Since 2.5.3 */ function _add_pending_activation_notice( - $email = false, + $email_address = false, $is_pending_trial = false, - $is_suspicious_email = false + $is_suspicious_email = false, + $has_upgrade_context = false, + $support_email_address = false ) { - if ( ! is_string( $email ) ) { - $current_user = self::_get_current_wp_user(); - $email = $current_user->user_email; + if ( ! is_string( $email_address ) ) { + $current_user = self::_get_current_wp_user(); + $email_address = $current_user->user_email; + } + + $formatted_message_args = array( + "{$this->get_plugin_name()}", + "{$email_address}", + ); + + if ( ! $has_upgrade_context || ! fs_is_network_admin() ) { + /* translators: %3$s: action (e.g.: "start the trial" or "complete the opt-in") */ + $formatted_message = $this->get_text_inline( 'You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s.', 'pending-activation-message' ); + + $formatted_message_args[] = $is_pending_trial ? + $this->get_text_inline( 'start the trial', 'start-the-trial' ) : + $this->get_text_inline( 'complete the opt-in', 'complete-the-opt-in' ); + + $notice_title = $this->get_text_inline( 'Thanks!', 'thanks' ); + } else { + /* translators: %3$s: What the user is expected to receive via email (e.g.: "the installation instructions" or "a license key") */ + $formatted_message = $this->get_text_inline( 'You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes.' ); + + if ( $this->has_release_on_freemius() ) { + $formatted_message_args[] = $this->get_text_x_inline( + 'the installation instructions', + 'Part of the message telling the user what they should receive via email.', + 'the-installation-instructions-phrase' + ); + } else { + $formatted_message_args[] = $this->get_text_x_inline( + 'a license key', + 'Part of the message telling the user what they should receive via email.', + 'a-license-key-phrase' + ); + + $formatted_message .= ( ' ' . sprintf( + /* translators: %s: activation link (e.g.: Click here) */ + $this->get_text_inline( '%s to activate the license once you get it.', 'license-activation-link-message' ), + sprintf( + '%s', + $this->get_activation_url( array( + 'fs_action' => 'reset_pending_activation_mode', + 'require_license' => 'true', + 'fs_unique_affix' => $this->get_unique_affix(), + ) ), + $this->get_text_x_inline( 'Click here', 'Part of an activation link message.', 'click-here' ) + ) + ) ); + } + + $formatted_message_args[] = ( ! empty( $support_email_address ) ) ? + ( "{$support_email_address}" ) : + $this->get_text_x_inline( + "the product's support email address", + 'Part of the message that tells the user to check their spam folder for a specific email.', + 'product-support-email-address-phrase' + ); + + $formatted_message .= ( ' ' . $this->get_text_inline( 'If you didn\'t get the email, try checking your spam folder or search for emails from %4$s.', 'check-spam-folder-message' ) ); + + $notice_title = $this->get_text_inline( 'Thanks for upgrading.', 'after-upgrade-thank-you-message' ); } $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.', 'pending-activation-message' ), - '' . $this->get_plugin_name() . '', - '' . $email . '', - ( $is_pending_trial ? - $this->get_text_inline( 'start the trial', 'start-the-trial' ) : - $this->get_text_inline( 'complete the opt-in', 'complete-the-opt-in' ) ) - ), + vsprintf( $formatted_message, $formatted_message_args ), 'activation_pending', - 'Thanks!' + $notice_title ); } @@ -8091,7 +7805,7 @@ function _activate_plugin_event_hook() { $has_api_connectivity = $this->has_api_connectivity( WP_FS__DEV_MODE || $is_premium_version_activation ); if ( ! $this->_anonymous_mode && - $has_api_connectivity && + ( false !== $has_api_connectivity ) && ! $this->_isAutoInstall ) { // Store hint that the plugin was just activated to enable auto-redirection to settings. @@ -8728,9 +8442,9 @@ function _deactivate_plugin_hook() { ) ); } } else { - if ( ! $this->has_api_connectivity() ) { + if ( false === $this->has_api_connectivity() && ! $this->is_premium() ) { // Reset connectivity test cache. - unset( $this->_storage->connectivity_test ); + $this->clear_connectivity_info(); $storage_keys_for_removal[] = 'connectivity_test'; } @@ -9030,6 +8744,26 @@ private function reset_anonymous_mode( $network_or_blog_ids = false ) { } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + */ + private function update_license_required_permissions_if_anonymous() { + if ( ! $this->is_anonymous() ) { + return; + } + + $this->reset_anonymous_mode( fs_is_network_admin() ); + + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + 'essentials' => true, + 'events' => true, + 'diagnostic' => false, + 'extensions' => false, + 'site' => false, + ) ); + } + /** * This is used to ensure that before redirecting to the opt-in page after resetting the anonymous mode or * deleting the account in the network level, the URL of the page to redirect to is correct. @@ -9054,15 +8788,27 @@ private function maybe_set_slug_and_network_menu_exists_flag() { * @since 1.1.7 */ function connect_again() { - if ( ! $this->is_anonymous() ) { + if ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) { return; } - $this->reset_anonymous_mode( fs_is_network_admin() ); + if ( $this->is_anonymous() ) { + $this->reset_anonymous_mode( fs_is_network_admin() ); + } + + $activation_url_params = array(); + + if ( $this->is_pending_activation() ) { + $this->clear_pending_activation_mode(); + + if ( fs_request_get_bool( 'require_license' ) ) { + $activation_url_params['require_license'] = true; + } + } $this->maybe_set_slug_and_network_menu_exists_flag(); - fs_redirect( $this->get_activation_url() ); + fs_redirect( $this->get_activation_url( $activation_url_params ) ); } /** @@ -11770,7 +11516,7 @@ function get_plan() { function is_trial() { $this->_logger->entrance(); - if ( ! $this->is_registered() || ! is_object( $this->_site ) ) { + if ( ! $this->is_registered( true ) || ! is_object( $this->_site ) ) { return false; } @@ -11884,7 +11630,7 @@ function get_trial_plan() { function is_paying() { $this->_logger->entrance(); - if ( ! $this->is_registered() ) { + if ( ! $this->is_registered( true ) ) { return false; } @@ -14249,6 +13995,8 @@ private function activate_license( } if ( is_object( $user ) ) { + $result = true; + if ( $is_network_activation_or_migration && ! $has_valid_blog_id ) { // If no specific blog ID was provided, activate the license for all sites in the network. $blog_2_install_map = array(); @@ -14270,22 +14018,10 @@ private function activate_license( if ( ! empty( $blog_2_install_map ) ) { $result = $fs->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } - if ( empty( $error ) && ! empty( $site_ids ) ) { + if ( true === $result && ! empty( $site_ids ) ) { $result = $fs->activate_license_on_many_sites( $user, $license_key, $site_ids ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } } else { if ( $fs->is_registered() ) { @@ -14311,13 +14047,11 @@ private function activate_license( $api = $fs->get_api_site_scope(); - $install = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + $result = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + + if ( ! FS_Api::is_api_error( $result ) ) { + $install = $result; - if ( FS_Api::is_api_error( $install ) ) { - $error = FS_Api::is_api_error_object( $install ) ? - $install->error->message : - var_export( $install->error, true ); - } else { $fs->reconnect_locally( $has_valid_blog_id ); if ( @@ -14330,16 +14064,18 @@ private function activate_license( } } else /* ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) */ { $result = $fs->activate_license_on_site( $user, $license_key ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } } - if ( empty( $error ) ) { + if ( true !== $result && ! FS_Api::is_api_result_entity( $result ) ) { + if ( FS_Api::is_blocked( $result ) ) { + $result->error->message = $this->generate_api_blocked_notice_message_from_result( $result ); + } + + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } else { $fs->network_upgrade_mode_completed(); $fs->_user = $user; @@ -14890,7 +14626,7 @@ function has_affiliate_program() { private function get_plugin_id_for_affiliate_terms() { return $this->has_bundle_context() ? $this->get_bundle_id() : - $this->_plugin_id; + $this->_plugin->id; } /** @@ -15162,7 +14898,11 @@ function version_upgrade_checkout_link( $new_version ) { return sprintf( $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), - sprintf( '%s', $url, $purchase_license_text ), + sprintf( + '%s', + $this->apply_filters( 'update_notice_checkout_url', $url ), + $purchase_license_text + ), $new_version ); } @@ -15232,7 +14972,7 @@ function checkout_url( */ $params = array_merge( $params, $extra ); - return $this->_get_admin_page_url( 'pricing', $params, $network ); + return $this->apply_filters( 'checkout_url', $this->_get_admin_page_url( 'pricing', $params, $network ) ); } /** @@ -17669,9 +17409,25 @@ function opt_in( $this->maybe_modify_api_curl_error_message( $result ); + if ( FS_Api::is_blocked( $result ) ) { + $result->error->message = $this->generate_api_blocked_notice_message_from_result( $result ); + } + + $is_connected = null; + + if ( empty( $license_key ) && $this->is_enable_anonymous() ) { + $this->skip_connection( fs_is_network_admin() ); + + $is_connected = ( ! FS_Api::is_blocked( $result ) ); + } + + $this->update_connectivity_info( $is_connected ); + return $result; } + $this->update_connectivity_info( true ); + // Module is being uninstalled, don't handle the returned data. if ( $is_uninstall ) { return true; @@ -17848,6 +17604,9 @@ function setup_network_account( // Only network level opt-in can have more than one install. $is_network_level_opt_in = true; } + + $this->update_connectivity_info( true ); + // $is_network_level_opt_in = self::is_ajax_action_static( 'network_activate', $this->_module_id ); // If Freemius was OFF before, turn it on. $this->turn_on(); @@ -17880,24 +17639,23 @@ function setup_network_account( ! $this->has_settings_menu() ) { if ( $this->is_paying() ) { - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ), $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ), + 'plan_upgraded' ); } else { $trial_plan = $this->get_trial_plan(); - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $trial_plan->title ), + ), 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $trial_plan->title ); } } @@ -17974,6 +17732,10 @@ function _install_with_new_user() { return; } + $has_pending_activation_confirmation_param = fs_request_has( 'pending_activation' ); + + $this->update_license_required_permissions_if_anonymous(); + if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) || // @todo This logic should be improved because it's executed on every load of a theme. $this->is_theme() @@ -18010,13 +17772,15 @@ function _install_with_new_user() { fs_request_get_bool( 'auto_install' ) ); } - } else if ( fs_request_has( 'pending_activation' ) ) { + } else if ( $has_pending_activation_confirmation_param ) { $this->set_pending_confirmation( fs_request_get( 'user_email' ), true, false, false, - fs_request_get_bool( 'is_suspicious_email' ) + fs_request_get_bool( 'is_suspicious_email' ), + fs_request_get_bool( 'has_upgrade_context' ), + fs_request_get( 'support_email_address' ) ); } } @@ -18269,6 +18033,9 @@ private function install_many_with_new_user( * @param bool $redirect * @param string|bool $license_key Since 1.2.1.5 * @param bool $is_pending_trial Since 1.2.1.5 + * @param bool $is_suspicious_email Since 2.5.0 + * @param bool $has_upgrade_context Since 2.5.3 + * @param bool|string $support_email_address Since 2.5.3 * * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page. */ @@ -18277,22 +18044,32 @@ private function set_pending_confirmation( $redirect = true, $license_key = false, $is_pending_trial = false, - $is_suspicious_email = false + $is_suspicious_email = false, + $has_upgrade_context = false, + $support_email_address = false ) { - if ( $this->_ignore_pending_mode ) { + $is_network_admin = fs_is_network_admin(); + + if ( $this->_ignore_pending_mode && ! $has_upgrade_context ) { /** * If explicitly asked to ignore pending mode, set to anonymous mode - * if require confirmation before finalizing the opt-in. + * if require confirmation before finalizing the opt-in except after completing a purchase (otherwise, in this case, they wouldn't see any notice telling them that they should receive their license key via email). * * @author Vova Feldman * @since 1.2.1.6 */ - $this->skip_connection( fs_is_network_admin() ); + $this->skip_connection( $is_network_admin ); } else { // Install must be activated via email since // user with the same email already exist. $this->_storage->is_pending_activation = true; - $this->_add_pending_activation_notice( $email, $is_pending_trial, $is_suspicious_email ); + $this->_add_pending_activation_notice( + $email, + $is_pending_trial, + $is_suspicious_email, + $has_upgrade_context, + $support_email_address + ); } if ( ! empty( $license_key ) ) { @@ -18555,6 +18332,14 @@ private function _activate_addon_account( return; } + $permission_ids = FS_Permission_Manager::get_all_permission_ids(); + $permissions = array(); + foreach ( $permission_ids as $permission_id ) { + $permissions[ $permission_id ] = FS_Permission_Manager::instance( $parent_fs )->is_permission( $permission_id, true ); + } + + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( $permissions ); + /** * Do not override the `uid` if network-level opt-in since the call to `get_sites_for_network_level_optin()` * already returns the data for the current blog. @@ -18816,6 +18601,8 @@ private function activate_parent_account( Freemius $parent_fs ) { // Sync add-on plans. $parent_fs->_sync_plans(); + $parent_fs->update_license_required_permissions_if_anonymous(); + $parent_fs->_set_account( $user, $parent_fs->_site ); } @@ -18878,7 +18665,7 @@ function _prepare_admin_menu() { $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); - if ( ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || + if ( ( false === $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || $should_hide_site_admin_settings ) { $this->_menu->remove_menu_item( $should_hide_site_admin_settings ); @@ -21414,14 +21201,11 @@ private function _sync_plugin_license( // Show API message only if not background sync or if paying customer. if ( ! $background || $this->is_paying() ) { // Try to ping API to see if not blocked. - if ( ! FS_Api::test() ) { + if ( FS_Api::is_blocked( $result ) ) { /** - * Failed to ping API - blocked! - * * @author Vova Feldman (@svovaf) * @since 1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages. */ - if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { // Add notice immediately if not a background sync. $add_notice = ( ! $background ); @@ -21445,20 +21229,15 @@ private function _sync_plugin_license( // Add notice instantly for not-background sync and only after 3 failed attempts for background sync. if ( $add_notice ) { self::$_global_admin_notices->add( - sprintf( - $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', 'server-blocking-access' ), - $this->get_plugin_name(), - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '' - ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + $this->generate_api_blocked_notice_message_from_result( $result ), + '', 'error', $background, 'api_blocked' ); + add_action( 'admin_footer', array( 'Freemius', '_add_api_connectivity_notice_handler_js' ) ); + // Notice was just shown, reset connectivity counter. delete_transient( '_fs_api_connection_retry_counter' ); } @@ -21467,7 +21246,7 @@ private function _sync_plugin_license( // Authentication params are broken. $this->_admin_notices->add( $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + '', 'error' ); } @@ -21729,14 +21508,7 @@ private function _sync_plugin_license( break; case 'upgraded': case 'activated': - $this->_admin_notices->add_sticky( - ( 'activated' === $plan_change ) ? - $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ) : - $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) . - $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); + $this->add_after_plan_activation_or_upgrade_instructions_notice( 'upgraded' === $plan_change ); $this->_admin_notices->remove_sticky( array( 'trial_started', @@ -21798,13 +21570,13 @@ private function _sync_plugin_license( $this->_admin_notices->remove_sticky( 'plan_upgraded' ); break; case 'trial_started': - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $this->get_trial_plan()->title ), + ), 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $this->get_trial_plan()->title ); $this->_admin_notices->remove_sticky( array( @@ -21841,6 +21613,42 @@ private function _sync_plugin_license( } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param mixed $result + * + * @return string + */ + private function generate_api_blocked_notice_message_from_result( $result ) { + $api_domains = $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com', + ) ); + + $api_domains_list_items = ''; + + foreach( $api_domains as $api_domain ) { + $api_domains_list_items .= "
  • {$api_domain}
  • "; + } + + $error_message = sprintf( + $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$s', 'server-blocking-access' ), + $this->get_plugin_name(), + "
      {$api_domains_list_items}
    " . $this->get_text_inline( 'Show error details', 'show-error-details' ) . " " + ); + + $error_message = + "
    {$error_message}
    " . + ''; + + return $error_message; + } + /** * Include the required JS at the footer of the admin to trigger the license activation dialog box. * @@ -21972,11 +21780,9 @@ protected function _activate_license( $background = false, $premium_license = nu } if ( ! $background ) { - $this->_admin_notices->add_sticky( - $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) . - $this->get_complete_upgrade_instructions(), - 'license_activated', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $this->add_complete_upgrade_instructions_notice( + $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ), + 'license_activated' ); } @@ -24966,6 +24772,42 @@ private function get_complete_upgrade_instructions( $plan_title = '' ) { } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + * + * @param string $message_before_the_instructions + * @param string $message_id + * @param string $plan_title + */ + private function add_complete_upgrade_instructions_notice( + $message_before_the_instructions, + $message_id, + $plan_title = '' + ) { + $this->_admin_notices->add_sticky( + $message_before_the_instructions . + $this->get_complete_upgrade_instructions( $plan_title ), + $message_id, + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + * + * @param bool $is_upgrade + */ + private function add_after_plan_activation_or_upgrade_instructions_notice( $is_upgrade = true ) { + $this->add_complete_upgrade_instructions_notice( + $is_upgrade ? + $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) : + $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ), + 'plan_upgraded' + ); + } + /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -25019,25 +24861,21 @@ static function safe_remote_post( self::enrich_request_for_debug( $url, $request ); } - $response = wp_remote_post( $url, $request ); + if ( ! isset( $request['method'] ) ) { + $request['method'] = 'POST'; + } - if ( $response instanceof WP_Error ) { - if ( 'https://' === substr( $url, 0, 8 ) && - isset( $response->errors ) && - isset( $response->errors['http_request_failed'] ) - ) { - $http_error = strtolower( $response->errors['http_request_failed'][0] ); + $response = FS_Api::remote_request( $url, $request ); - if ( false !== strpos( $http_error, 'ssl' ) || - false !== strpos( $http_error, 'curl error 35' ) - ) { - // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). - $url = 'http://' . substr( $url, 8 ); + if ( + 'https://' === substr( $url, 0, 8 ) && + FS_Api::is_ssl_error_response( $response ) + ) { + // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). + $url = 'http://' . substr( $url, 8 ); - $request['timeout'] = 15; - $response = wp_remote_post( $url, $request ); - } - } + $request['timeout'] = 15; + $response = FS_Api::remote_request( $url, $request ); } if ( false !== $cache_key ) { @@ -25916,24 +25754,6 @@ function fetch_remote_icon_url() { #region GDPR #-------------------------------------------------------------------------------- - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @return bool - */ - function fetch_and_store_current_user_gdpr_anonymously() { - $pong = $this->ping( null, true ); - - if ( ! $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) { - return false; - } else { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - return $pong->is_gdpr_required; - } - } - /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -26294,6 +26114,14 @@ private function disable_opt_in_notice_and_lock_user() { FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + static function _add_api_connectivity_notice_handler_js() { + fs_require_once_template( 'api-connectivity-message-js.php' ); + } + /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -26387,7 +26215,7 @@ private function fetch_installs_licenses_owners_data( $install_ids ) { '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) ); - $license_owners = null; + $license_owners = array(); if ( $this->is_api_result_object( $response, 'owners' ) ) { $license_owners = $response->owners; diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php index 0eb2807..74f475f 100644 --- a/freemius/includes/class-fs-api.php +++ b/freemius/includes/class-fs-api.php @@ -189,13 +189,15 @@ private function _sync_clock_diff( $diff = false ) { * @param string $path * @param string $method * @param array $params - * @param bool $retry Is in retry or first call attempt. + * @param bool $in_retry Is in retry or first call attempt. * * @return array|mixed|string|void */ - private function _call( $path, $method = 'GET', $params = array(), $retry = false ) { + private function _call( $path, $method = 'GET', $params = array(), $in_retry = false ) { $this->_logger->entrance( $method . ':' . $path ); + $force_http = ( ! $in_retry && self::$_options->get_option( 'api_force_http', false ) ); + if ( self::is_temporary_down() ) { $result = $this->get_temporary_unavailable_error(); } else { @@ -224,12 +226,15 @@ private function _call( $path, $method = 'GET', $params = array(), $retry = fals $result = $this->_api->Api( $path, $method, $params ); - if ( null !== $result && - isset( $result->error ) && - isset( $result->error->code ) && - 'request_expired' === $result->error->code + if ( + ! $in_retry && + null !== $result && + isset( $result->error ) && + isset( $result->error->code ) ) { - if ( ! $retry ) { + $retry = false; + + if ( 'request_expired' === $result->error->code ) { $diff = isset( $result->error->timestamp ) ? ( time() - strtotime( $result->error->timestamp ) ) : false; @@ -237,15 +242,35 @@ private function _call( $path, $method = 'GET', $params = array(), $retry = fals // Try to sync clock diff. if ( false !== $this->_sync_clock_diff( $diff ) ) { // Retry call with new synced clock. - return $this->_call( $path, $method, $params, true ); + $retry = true; } + } else if ( + Freemius_Api_WordPress::IsHttps() && + FS_Api::is_ssl_error_response( $result ) + ) { + $force_http = true; + $retry = true; + } + + if ( $retry ) { + if ( $force_http ) { + $this->toggle_force_http( true ); + } + + $result = $this->_call( $path, $method, $params, true ); } } } - if ( $this->_logger->is_on() && self::is_api_error( $result ) ) { - // Log API errors. - $this->_logger->api_error( $result ); + if ( self::is_api_error( $result ) ) { + if ( $this->_logger->is_on() ) { + // Log API errors. + $this->_logger->api_error( $result ); + } + + if ( $force_http ) { + $this->toggle_force_http( false ); + } } return $result; @@ -337,6 +362,40 @@ function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN return $cached_result; } + /** + * @todo Remove this method after migrating Freemius::safe_remote_post() to FS_Api::call(). + * + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param string $url + * @param array $remote_args + * + * @return mixed + */ + static function remote_request( $url, $remote_args ) { + if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { + require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php'; + } + + if ( method_exists( 'Freemius_Api_WordPress', 'RemoteRequest' ) ) { + return Freemius_Api_WordPress::RemoteRequest( $url, $remote_args ); + } + + // The following is for backward compatibility when a modified PHP SDK version is in use and the `Freemius_Api_WordPress:RemoteRequest()` method doesn't exist. + $response = wp_remote_request( $url, $remote_args ); + + if ( + empty( $response['headers'] ) || + empty( $response['headers']['x-api-server'] ) + ) { + // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). + $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); + } + + return $response; + } + /** * Check if there's a cached version of the API request. * @@ -407,50 +466,37 @@ private function get_cache_key( $path, $method = 'GET', $params = array() ) { return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); } - /** - * Test API connectivity. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 If fails, try to fallback to HTTP. - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if - * temporary down. - * - * @return bool True if successful connectivity to the API. - */ - static function test() { - self::_init(); - - $cache_key = 'ping_test'; - - $test = self::$_cache->get_valid( $cache_key, null ); - - if ( is_null( $test ) ) { - $test = Freemius_Api_WordPress::Test(); - - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option( 'api_force_http', true, true ); - - $test = Freemius_Api_WordPress::Test(); - - if ( false === $test ) { - /** - * API connectivity test fail also in HTTP request, therefore, - * fallback to HTTPS to keep connection secure. - * - * @since 1.1.6 - */ - self::$_options->set_option( 'api_force_http', false, true ); - } - } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param bool $is_http + */ + private function toggle_force_http( $is_http ) { + self::$_options->set_option( 'api_force_http', $is_http, true ); - self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); - } + if ( $is_http ) { + Freemius_Api_WordPress::SetHttp(); + } else if ( method_exists( 'Freemius_Api_WordPress', 'SetHttps' ) ) { + Freemius_Api_WordPress::SetHttps(); + } + } - return $test; - } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param mixed $response + * + * @return bool + */ + static function is_blocked( $response ) { + return ( + self::is_api_error_object( $response, true ) && + isset( $response->error->code ) && + 'api_blocked' === $response->error->code + ); + } /** * Check if API is temporary down. @@ -485,56 +531,6 @@ private function get_temporary_unavailable_error() { ); } - /** - * Ping API for connectivity test, and return result object. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param null|string $unique_anonymous_id - * @param array $params - * - * @return object - */ - function ping( $unique_anonymous_id = null, $params = array() ) { - $this->_logger->entrance(); - - if ( self::is_temporary_down() ) { - return $this->get_temporary_unavailable_error(); - } - - $pong = is_null( $unique_anonymous_id ) ? - Freemius_Api_WordPress::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ) ) ); - - if ( $this->is_valid_ping( $pong ) ) { - return $pong; - } - - if ( self::should_try_with_http( $pong ) ) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option( 'api_force_http', true, true ); - - $pong = is_null( $unique_anonymous_id ) ? - Freemius_Api_WordPress::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ) ) ); - - if ( ! $this->is_valid_ping( $pong ) ) { - self::$_options->set_option( 'api_force_http', false, true ); - } - } - - return $pong; - } - /** * Check if based on the API result we should try * to re-run the same request with HTTP instead of HTTPS. @@ -564,20 +560,6 @@ private static function should_try_with_http( $result ) { } - /** - * Check if valid ping request result. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @param mixed $pong - * - * @return bool - */ - function is_valid_ping( $pong ) { - return Freemius_Api_WordPress::Test( $pong ); - } - function get_url( $path = '' ) { return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); } @@ -595,6 +577,14 @@ static function clear_cache() { self::$_cache->clear(); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + static function clear_force_http_flag() { + self::$_options->unset_option( 'api_force_http' ); + } + #---------------------------------------------------------------------------------- #region Error Handling #---------------------------------------------------------------------------------- @@ -617,14 +607,52 @@ static function is_api_error( $result ) { * @since 2.0.0 * * @param mixed $result + * @param bool $ignore_message * * @return bool Is API result contains an error. */ - static function is_api_error_object( $result ) { + static function is_api_error_object( $result, $ignore_message = false ) { return ( is_object( $result ) && isset( $result->error ) && - isset( $result->error->message ) + ( $ignore_message || isset( $result->error->message ) ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param WP_Error|object|string $response + * + * @return bool + */ + static function is_ssl_error_response( $response ) { + $http_error = null; + + if ( $response instanceof WP_Error ) { + if ( + isset( $response->errors ) && + isset( $response->errors['http_request_failed'] ) + ) { + $http_error = strtolower( $response->errors['http_request_failed'][0] ); + } + } else if ( + self::is_api_error_object( $response ) && + ! empty( $response->error->message ) + ) { + $http_error = $response->error->message; + } + + return ( + ! empty( $http_error ) && + ( + false !== strpos( $http_error, 'curl error 35' ) || + ( + false === strpos( $http_error, '' ) && + false !== strpos( $http_error, 'ssl' ) + ) + ) ); } diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php index f8cd64d..ba8f969 100644 --- a/freemius/includes/class-fs-plugin-updater.php +++ b/freemius/includes/class-fs-plugin-updater.php @@ -166,15 +166,19 @@ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { $contents = ob_get_clean(); - $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); + $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' ); - if ( false !== $update_button_id_attribute_pos ) { - $update_button_start_pos = strrpos( - substr( $contents, 0, $update_button_id_attribute_pos ), + if ( false === $install_or_update_button_id_attribute_pos ) { + $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); + } + + if ( false !== $install_or_update_button_id_attribute_pos ) { + $install_or_update_button_start_pos = strrpos( + substr( $contents, 0, $install_or_update_button_id_attribute_pos ), '', $update_button_id_attribute_pos ) + strlen( '' ) ); + $install_or_update_button_end_pos = ( strpos( $contents, '', $install_or_update_button_id_attribute_pos ) + strlen( '' ) ); /** * The part of the contents without the update button. @@ -182,20 +186,20 @@ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents = substr( $contents, 0, $update_button_start_pos ); + $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos ); - $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) ); + $install_or_update_button = substr( $contents, $install_or_update_button_start_pos, ( $install_or_update_button_end_pos - $install_or_update_button_start_pos ) ); /** * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, * the text will be "Renew license" and will link to the checkout page with the license's billing cycle * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. */ - $update_button = preg_replace( - '/(\)(.+)(\<\/a>)/is', + $install_or_update_button = preg_replace( + '/(\)(.+)(\<\/a>)/is', is_object( $license ) ? sprintf( - '$1$3%s$5%s$7', + '$1$4%s$6%s$8', $this->_fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : @@ -206,11 +210,11 @@ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) ) : sprintf( - '$1$3%s$5%s$7', + '$1$4%s$6%s$8', $this->_fs->pricing_url(), fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) ), - $update_button + $install_or_update_button ); /** @@ -219,7 +223,7 @@ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents .= $update_button; + $modified_contents .= $install_or_update_button; /** * Append the remaining part of the contents after the update button. @@ -227,7 +231,7 @@ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents .= substr( $contents, $update_button_end_pos ); + $modified_contents .= substr( $contents, $install_or_update_button_end_pos ); $contents = $modified_contents; } @@ -838,28 +842,34 @@ function delete_update_data() { * @return bool|mixed */ static function _fetch_plugin_info_from_repository( $action, $args ) { - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/'; - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { - $url = set_url_scheme( $url, 'https' ); - } - - $args = array( - 'timeout' => 15, - 'body' => array( + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/'; + $url = add_query_arg( + array( 'action' => $action, - 'request' => serialize( $args ) - ) + 'request' => $args, + ), + $url ); - $request = wp_remote_post( $url, $args ); + if ( wp_http_supports( array( 'ssl' ) ) ) { + $url = set_url_scheme( $url, 'https' ); + } + + // The new endpoint version serves only GET requests. + $request = wp_remote_get( $url, array( 'timeout' => 15 ) ); if ( is_wp_error( $request ) ) { return false; } - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) ); + $res = json_decode( wp_remote_retrieve_body( $request ), true ); + + if ( is_array( $res ) ) { + // Object casting is required in order to match the info/1.0 format. We are not decoding directly into an object as we need some fields to remain an array (e.g., $res->sections). + $res = (object) $res; + } - if ( ! is_object( $res ) && ! is_array( $res ) ) { + if ( ! is_object( $res ) || isset( $res->error ) ) { return false; } diff --git a/freemius/includes/entities/class-fs-plugin.php b/freemius/includes/entities/class-fs-plugin.php index 2bc039a..ad5a3de 100644 --- a/freemius/includes/entities/class-fs-plugin.php +++ b/freemius/includes/entities/class-fs-plugin.php @@ -104,6 +104,11 @@ class FS_Plugin extends FS_Scope_Entity { * @var null|string */ public $bundle_public_key; + /** + * @since 2.5.4 + * @var null|array + */ + public $opt_in_moderation; const AFFILIATE_MODERATION_CUSTOMERS = 'customers'; diff --git a/freemius/includes/entities/class-fs-site.php b/freemius/includes/entities/class-fs-site.php index eedf6fe..19cca04 100644 --- a/freemius/includes/entities/class-fs-site.php +++ b/freemius/includes/entities/class-fs-site.php @@ -187,7 +187,10 @@ static function is_localhost_by_address( $url ) { // Cloudways fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || // Kinsta - ( fs_starts_with( $subdomain, 'staging-' ) && ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) ) || + ( + ( fs_starts_with( $subdomain, 'staging-' ) || fs_starts_with( $subdomain, 'env-' ) ) && + ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) + ) || // DesktopServer fs_ends_with( $subdomain, '.dev.cc' ) || // Pressable diff --git a/freemius/includes/managers/class-fs-gdpr-manager.php b/freemius/includes/managers/class-fs-gdpr-manager.php index a64abb0..5b89d86 100644 --- a/freemius/includes/managers/class-fs-gdpr-manager.php +++ b/freemius/includes/managers/class-fs-gdpr-manager.php @@ -85,18 +85,6 @@ private function update_option( $name, $value ) { $this->_storage->set_option( $this->_option_name, $this->_data, true ); } - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @return bool|null - */ - public function is_required() { - return isset( $this->_data['required'] ) ? - $this->_data['required'] : - null; - } - /** * @author Leo Fajardo (@leorw) * @since 2.1.0 diff --git a/freemius/includes/managers/class-fs-permission-manager.php b/freemius/includes/managers/class-fs-permission-manager.php index 727bf4d..7d75e5d 100644 --- a/freemius/includes/managers/class-fs-permission-manager.php +++ b/freemius/includes/managers/class-fs-permission-manager.php @@ -97,6 +97,15 @@ static function is_supported_permission( $permission ) { return in_array( $permission, self::get_all_permission_ids() ); } + /** + * @since 2.5.3 + * + * @return bool + */ + function is_premium_context() { + return ( $this->_fs->is_premium() || $this->_fs->can_use_premium_code() ); + } + /** * @param bool $is_license_activation * @param array[] $extra_permissions @@ -430,7 +439,7 @@ function is_essentials_tracking_allowed( $blog_id = null ) { * @return bool */ function is_diagnostic_tracking_allowed( $default = true ) { - return $this->_fs->is_premium() ? + return $this->is_premium_context() ? $this->is_permission_allowed( self::PERMISSION_DIAGNOSTIC, $default ) : $this->is_permission_allowed( self::PERMISSION_SITE, $default ); } @@ -592,7 +601,7 @@ function get_permission_default( $permission ) { * @return string */ function get_site_permission_name() { - return $this->_fs->is_premium() ? + return $this->is_premium_context() ? self::PERMISSION_ESSENTIALS : self::PERMISSION_SITE; } @@ -601,7 +610,7 @@ function get_site_permission_name() { * @return string[] */ function get_site_tracking_permission_names() { - return $this->_fs->is_premium() ? + return $this->is_premium_context() ? array( FS_Permission_Manager::PERMISSION_ESSENTIALS, FS_Permission_Manager::PERMISSION_EVENTS, diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index 3c5da90..6998e75 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -1,715 +1,738 @@ - '7.37' ); - - if ( ! defined( 'FS_API__PROTOCOL' ) ) { - define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); - } - - if ( ! defined( 'FS_API__LOGGER_ON' ) ) { - define( 'FS_API__LOGGER_ON', false ); - } - - if ( ! defined( 'FS_API__ADDRESS' ) ) { - define( 'FS_API__ADDRESS', '://api.freemius.com' ); - } - if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { - define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); - } - - if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { - class Freemius_Api_WordPress extends Freemius_Api_Base { - private static $_logger = array(); - - /** - * @param string $pScope 'app', 'developer', 'user' or 'install'. - * @param number $pID Element's id. - * @param string $pPublic Public key. - * @param string|bool $pSecret Element's secret key. - * @param bool $pSandbox Whether or not to run API in sandbox mode. - */ - public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { - // If secret key not provided, use public key encryption. - if ( is_bool( $pSecret ) ) { - $pSecret = $pPublic; - } - - parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); - } - - public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { - $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); - - if ( ':' === $address[0] ) { - $address = self::$_protocol . $address; - } - - return $address . $pCanonizedPath; - } - - #---------------------------------------------------------------------------------- - #region Servers Clock Diff - #---------------------------------------------------------------------------------- - - /** - * @var int Clock diff in seconds between current server to API server. - */ - private static $_clock_diff = 0; - - /** - * Set clock diff for all API calls. - * - * @since 1.0.3 - * - * @param $pSeconds - */ - public static function SetClockDiff( $pSeconds ) { - self::$_clock_diff = $pSeconds; - } - - /** - * Find clock diff between current server to API server. - * - * @since 1.0.2 - * @return int Clock diff in seconds. - */ - public static function FindClockDiff() { - $time = time(); - $pong = self::Ping(); - - return ( $time - strtotime( $pong->timestamp ) ); - } - - #endregion - - /** - * @var string http or https - */ - private static $_protocol = FS_API__PROTOCOL; - - /** - * Set API connection protocol. - * - * @since 1.0.4 - */ - public static function SetHttp() { - self::$_protocol = 'http'; - } - - /** - * @since 1.0.4 - * - * @return bool - */ - public static function IsHttps() { - return ( 'https' === self::$_protocol ); - } - - /** - * Sign request with the following HTTP headers: - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @param string $pResourceUrl - * @param array $pWPRemoteArgs - * - * @return array - */ - function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { - $auth = $this->GenerateAuthorizationParams( - $pResourceUrl, - $pWPRemoteArgs['method'], - ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' - ); - - $pWPRemoteArgs['headers']['Date'] = $auth['date']; - $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; - - if ( ! empty( $auth['content_md5'] ) ) { - $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; - } - - return $pWPRemoteArgs; - } - - /** - * Generate Authorization request headers: - * - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @author Vova Feldman - * - * @param string $pResourceUrl - * @param string $pMethod - * @param string $pPostParams - * - * @return array - * @throws Freemius_Exception - */ - function GenerateAuthorizationParams( - $pResourceUrl, - $pMethod = 'GET', - $pPostParams = '' - ) { - $pMethod = strtoupper( $pMethod ); - - $eol = "\n"; - $content_md5 = ''; - $content_type = ''; - $now = ( time() - self::$_clock_diff ); - $date = date( 'r', $now ); - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $content_type = 'application/json'; - - if ( ! empty( $pPostParams ) ) { - $content_md5 = md5( $pPostParams ); - } - } - - $string_to_sign = implode( $eol, array( - $pMethod, - $content_md5, - $content_type, - $date, - $pResourceUrl - ) ); - - // If secret and public keys are identical, it means that - // the signature uses public key hash encoding. - $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; - - $auth = array( - 'date' => $date, - 'authorization' => $auth_type . ' ' . $this->_id . ':' . - $this->_public . ':' . - self::Base64UrlEncode( hash_hmac( - 'sha256', $string_to_sign, $this->_secret - ) ) - ); - - if ( ! empty( $content_md5 ) ) { - $auth['content_md5'] = $content_md5; - } - - return $auth; - } - - /** - * Get API request URL signed via query string. - * - * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). - * - * @param string $pPath - * - * @throws Freemius_Exception - * - * @return string - */ - function GetSignedUrl( $pPath ) { - $resource = explode( '?', $this->CanonizePath( $pPath ) ); - $pResourceUrl = $resource[0]; - - $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); - - return Freemius_Api_WordPress::GetUrl( - $pResourceUrl . '?' . - ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . - 'authorization=' . urlencode( $auth['authorization'] ) . - '&auth_date=' . urlencode( $auth['date'] ) - , $this->_isSandbox ); - } - - /** - * @author Vova Feldman - * - * @param string $pUrl - * @param array $pWPRemoteArgs - * - * @return mixed - */ - private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { - $bt = debug_backtrace(); - - $start = microtime( true ); - - $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); - - if ( FS_API__LOGGER_ON ) { - $end = microtime( true ); - - $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); - $is_http_error = is_wp_error( $response ); - - self::$_logger[] = array( - 'id' => count( self::$_logger ), - 'start' => $start, - 'end' => $end, - 'total' => ( $end - $start ), - 'method' => $pWPRemoteArgs['method'], - 'path' => $pUrl, - 'body' => $has_body ? $pWPRemoteArgs['body'] : null, - 'result' => ! $is_http_error ? - $response['body'] : - json_encode( $response->get_error_messages() ), - 'code' => ! $is_http_error ? $response['response']['code'] : null, - 'backtrace' => $bt, - ); - } - - return $response; - } - - /** - * @return array - */ - static function GetLogger() { - return self::$_logger; - } - - /** - * @param string $pCanonizedPath - * @param string $pMethod - * @param array $pParams - * @param null|array $pWPRemoteArgs - * @param bool $pIsSandbox - * @param null|callable $pBeforeExecutionFunction - * - * @return object[]|object|null - * - * @throws \Freemius_Exception - */ - private static function MakeStaticRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null, - $pIsSandbox = false, - $pBeforeExecutionFunction = null - ) { - // Connectivity errors simulation. - if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { - self::ThrowCloudFlareDDoSException(); - } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { - self::ThrowSquidAclException(); - } - - if ( empty( $pWPRemoteArgs ) ) { - $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . - home_url(); - - $pWPRemoteArgs = array( - 'method' => strtoupper( $pMethod ), - 'connect_timeout' => 10, - 'timeout' => 60, - 'follow_redirects' => true, - 'redirection' => 5, - 'user-agent' => $user_agent, - 'blocking' => true, - ); - } - - if ( ! isset( $pWPRemoteArgs['headers'] ) || - ! is_array( $pWPRemoteArgs['headers'] ) - ) { - $pWPRemoteArgs['headers'] = array(); - } - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; - - if ( is_array( $pParams ) && 0 < count( $pParams ) ) { - $pWPRemoteArgs['body'] = json_encode( $pParams ); - } - } - - $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); - - $resource = explode( '?', $pCanonizedPath ); - - if ( FS_SDK__HAS_CURL ) { - // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait - // for 2 seconds if the server does not support this header. - $pWPRemoteArgs['headers']['Expect'] = ''; - } - - if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { - $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; - } - - if ( false !== $pBeforeExecutionFunction && - is_callable( $pBeforeExecutionFunction ) - ) { - $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); - } - - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - - if ( is_wp_error( $result ) ) { - /** - * @var WP_Error $result - */ - if ( self::IsCurlError( $result ) ) { - /** - * With dual stacked DNS responses, it's possible for a server to - * have IPv6 enabled but not have IPv6 connectivity. If this is - * the case, cURL will try IPv4 first and if that fails, then it will - * fall back to IPv6 and the error EHOSTUNREACH is returned by the - * operating system. - */ - $matches = array(); - $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; - if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { - /** - * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. - * @author Vova Feldman (@svovaf) - */ - if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { - if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { -// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); - // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. - add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); - - // Re-run request. - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - } - } - } - } - - if ( is_wp_error( $result ) ) { - self::ThrowWPRemoteException( $result ); - } - } - - $response_body = $result['body']; - - if ( empty( $response_body ) ) { - return null; - } - - $decoded = json_decode( $response_body ); - - if ( is_null( $decoded ) ) { - if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && - preg_match( '/text\/javascript/', $response_body ) - ) { - self::ThrowCloudFlareDDoSException( $response_body ); - } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && - preg_match( '/squid/', $response_body ) - ) { - self::ThrowSquidAclException( $response_body ); - } else { - $decoded = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $response_body, - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - } - - return $decoded; - } - - - /** - * Makes an HTTP request. This method can be overridden by subclasses if - * developers want to do fancier things or use something other than wp_remote_request() - * to make the request. - * - * @param string $pCanonizedPath The URL to make the request to - * @param string $pMethod HTTP method - * @param array $pParams The parameters to use for the POST body - * @param null|array $pWPRemoteArgs wp_remote_request options. - * - * @return object[]|object|null - * - * @throws Freemius_Exception - */ - public function MakeRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null - ) { - $resource = explode( '?', $pCanonizedPath ); - - // Only sign request if not ping.json connectivity test. - $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); - - return self::MakeStaticRequest( - $pCanonizedPath, - $pMethod, - $pParams, - $pWPRemoteArgs, - $this->_isSandbox, - $sign_request ? array( &$this, 'SignRequest' ) : null - ); - } - - /** - * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter - * - * @param resource $handle A cURL handle returned by curl_init() - * - * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to - * CURL_IPRESOLVE_V4 - * - * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e - */ - static function CurlResolveToIPv4( $handle ) { - curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); - - return $handle; - } - - #---------------------------------------------------------------------------------- - #region Connectivity Test - #---------------------------------------------------------------------------------- - - /** - * If successful connectivity to the API endpoint using ping.json endpoint. - * - * - OR - - * - * Validate if ping result object is valid. - * - * @param mixed $pPong - * - * @return bool - */ - public static function Test( $pPong = null ) { - $pong = is_null( $pPong ) ? - self::Ping() : - $pPong; - - return ( - is_object( $pong ) && - isset( $pong->api ) && - 'pong' === $pong->api - ); - } - - /** - * Ping API to test connectivity. - * - * @return object - */ - public static function Ping() { - try { - $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); - } catch ( Freemius_Exception $e ) { - // Map to error object. - $result = (object) $e->getResult(); - } catch ( Exception $e ) { - // Map to error object. - $result = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - - return $result; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Connectivity Exceptions - #---------------------------------------------------------------------------------- - - /** - * @param \WP_Error $pError - * - * @return bool - */ - private static function IsCurlError( WP_Error $pError ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - return ( 0 === strpos( $message, 'cURL' ) ); - } - - /** - * @param WP_Error $pError - * - * @throws Freemius_Exception - */ - private static function ThrowWPRemoteException( WP_Error $pError ) { - if ( self::IsCurlError( $pError ) ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - #region Check if there are any missing cURL methods. - - $curl_required_methods = array( - 'curl_version', - 'curl_exec', - 'curl_init', - 'curl_close', - 'curl_setopt', - 'curl_setopt_array', - 'curl_error', - ); - - // Find all missing methods. - $missing_methods = array(); - foreach ( $curl_required_methods as $m ) { - if ( ! function_exists( $m ) ) { - $missing_methods[] = $m; - } - } - - if ( ! empty( $missing_methods ) ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'cUrlMissing', - 'message' => $message, - 'code' => 'curl_missing', - 'http' => 402 - ), - 'missing_methods' => $missing_methods, - ) ); - } - - #endregion - - // cURL error - "cURL error {{errno}}: {{error}}". - $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); - - $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; - $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; - - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $code, - 'message' => $message, - 'type' => 'CurlException', - ), - ) ); - } else { - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $pError->get_error_code(), - 'message' => $pError->get_error_message(), - 'type' => 'WPRemoteException', - ), - ) ); - } - - throw $e; - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowCloudFlareDDoSException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'CloudFlareDDoSProtection', - 'message' => $pResult, - 'code' => 'cloudflare_ddos_protection', - 'http' => 402 - ) - ) ); - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowSquidAclException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'SquidCacheBlock', - 'message' => $pResult, - 'code' => 'squid_cache_block', - 'http' => 402 - ) - ) ); - } - - #endregion - } - } \ No newline at end of file + '7.37' ); + + if ( ! defined( 'FS_API__PROTOCOL' ) ) { + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); + } + + if ( ! defined( 'FS_API__LOGGER_ON' ) ) { + define( 'FS_API__LOGGER_ON', false ); + } + + if ( ! defined( 'FS_API__ADDRESS' ) ) { + define( 'FS_API__ADDRESS', '://api.freemius.com' ); + } + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); + } + + if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { + class Freemius_Api_WordPress extends Freemius_Api_Base { + private static $_logger = array(); + + /** + * @param string $pScope 'app', 'developer', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string|bool $pSecret Element's secret key. + * @param bool $pSandbox Whether or not to run API in sandbox mode. + */ + public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { + // If secret key not provided, use public key encryption. + if ( is_bool( $pSecret ) ) { + $pSecret = $pPublic; + } + + parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); + } + + public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { + $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); + + if ( ':' === $address[0] ) { + $address = self::$_protocol . $address; + } + + return $address . $pCanonizedPath; + } + + #---------------------------------------------------------------------------------- + #region Servers Clock Diff + #---------------------------------------------------------------------------------- + + /** + * @var int Clock diff in seconds between current server to API server. + */ + private static $_clock_diff = 0; + + /** + * Set clock diff for all API calls. + * + * @since 1.0.3 + * + * @param $pSeconds + */ + public static function SetClockDiff( $pSeconds ) { + self::$_clock_diff = $pSeconds; + } + + /** + * Find clock diff between current server to API server. + * + * @since 1.0.2 + * @return int Clock diff in seconds. + */ + public static function FindClockDiff() { + $time = time(); + $pong = self::Ping(); + + return ( $time - strtotime( $pong->timestamp ) ); + } + + #endregion + + /** + * @var string http or https + */ + private static $_protocol = FS_API__PROTOCOL; + + /** + * Set API connection protocol. + * + * @since 1.0.4 + */ + public static function SetHttp() { + self::$_protocol = 'http'; + } + + /** + * Sets API connection protocol to HTTPS. + * + * @since 2.5.4 + */ + public static function SetHttps() { + self::$_protocol = 'https'; + } + + /** + * @since 1.0.4 + * + * @return bool + */ + public static function IsHttps() { + return ( 'https' === self::$_protocol ); + } + + /** + * Sign request with the following HTTP headers: + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @param string $pResourceUrl + * @param array $pWPRemoteArgs + * + * @return array + */ + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { + $auth = $this->GenerateAuthorizationParams( + $pResourceUrl, + $pWPRemoteArgs['method'], + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' + ); + + $pWPRemoteArgs['headers']['Date'] = $auth['date']; + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; + + if ( ! empty( $auth['content_md5'] ) ) { + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; + } + + return $pWPRemoteArgs; + } + + /** + * Generate Authorization request headers: + * + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @author Vova Feldman + * + * @param string $pResourceUrl + * @param string $pMethod + * @param string $pPostParams + * + * @return array + * @throws Freemius_Exception + */ + function GenerateAuthorizationParams( + $pResourceUrl, + $pMethod = 'GET', + $pPostParams = '' + ) { + $pMethod = strtoupper( $pMethod ); + + $eol = "\n"; + $content_md5 = ''; + $content_type = ''; + $now = ( time() - self::$_clock_diff ); + $date = date( 'r', $now ); + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $content_type = 'application/json'; + + if ( ! empty( $pPostParams ) ) { + $content_md5 = md5( $pPostParams ); + } + } + + $string_to_sign = implode( $eol, array( + $pMethod, + $content_md5, + $content_type, + $date, + $pResourceUrl + ) ); + + // If secret and public keys are identical, it means that + // the signature uses public key hash encoding. + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; + + $auth = array( + 'date' => $date, + 'authorization' => $auth_type . ' ' . $this->_id . ':' . + $this->_public . ':' . + self::Base64UrlEncode( hash_hmac( + 'sha256', $string_to_sign, $this->_secret + ) ) + ); + + if ( ! empty( $content_md5 ) ) { + $auth['content_md5'] = $content_md5; + } + + return $auth; + } + + /** + * Get API request URL signed via query string. + * + * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). + * + * @param string $pPath + * + * @throws Freemius_Exception + * + * @return string + */ + function GetSignedUrl( $pPath ) { + $resource = explode( '?', $this->CanonizePath( $pPath ) ); + $pResourceUrl = $resource[0]; + + $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); + + return Freemius_Api_WordPress::GetUrl( + $pResourceUrl . '?' . + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . + 'authorization=' . urlencode( $auth['authorization'] ) . + '&auth_date=' . urlencode( $auth['date'] ) + , $this->_isSandbox ); + } + + /** + * @author Vova Feldman + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { + $bt = debug_backtrace(); + + $start = microtime( true ); + + $response = self::RemoteRequest( $pUrl, $pWPRemoteArgs ); + + if ( FS_API__LOGGER_ON ) { + $end = microtime( true ); + + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); + $is_http_error = is_wp_error( $response ); + + self::$_logger[] = array( + 'id' => count( self::$_logger ), + 'start' => $start, + 'end' => $end, + 'total' => ( $end - $start ), + 'method' => $pWPRemoteArgs['method'], + 'path' => $pUrl, + 'body' => $has_body ? $pWPRemoteArgs['body'] : null, + 'result' => ! $is_http_error ? + $response['body'] : + json_encode( $response->get_error_messages() ), + 'code' => ! $is_http_error ? $response['response']['code'] : null, + 'backtrace' => $bt, + ); + } + + return $response; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + static function RemoteRequest( $pUrl, $pWPRemoteArgs ) { + $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); + + if ( + empty( $response['headers'] ) || + empty( $response['headers']['x-api-server'] ) + ) { + // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). + $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); + } + + return $response; + } + + /** + * @return array + */ + static function GetLogger() { + return self::$_logger; + } + + /** + * @param string $pCanonizedPath + * @param string $pMethod + * @param array $pParams + * @param null|array $pWPRemoteArgs + * @param bool $pIsSandbox + * @param null|callable $pBeforeExecutionFunction + * + * @return object[]|object|null + * + * @throws \Freemius_Exception + */ + private static function MakeStaticRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null, + $pIsSandbox = false, + $pBeforeExecutionFunction = null + ) { + // Connectivity errors simulation. + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { + self::ThrowCloudFlareDDoSException(); + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { + self::ThrowSquidAclException(); + } + + if ( empty( $pWPRemoteArgs ) ) { + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . + home_url(); + + $pWPRemoteArgs = array( + 'method' => strtoupper( $pMethod ), + 'connect_timeout' => 10, + 'timeout' => 60, + 'follow_redirects' => true, + 'redirection' => 5, + 'user-agent' => $user_agent, + 'blocking' => true, + ); + } + + if ( ! isset( $pWPRemoteArgs['headers'] ) || + ! is_array( $pWPRemoteArgs['headers'] ) + ) { + $pWPRemoteArgs['headers'] = array(); + } + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; + + if ( is_array( $pParams ) && 0 < count( $pParams ) ) { + $pWPRemoteArgs['body'] = json_encode( $pParams ); + } + } + + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); + + $resource = explode( '?', $pCanonizedPath ); + + if ( FS_SDK__HAS_CURL ) { + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait + // for 2 seconds if the server does not support this header. + $pWPRemoteArgs['headers']['Expect'] = ''; + } + + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { + $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; + } + + if ( false !== $pBeforeExecutionFunction && + is_callable( $pBeforeExecutionFunction ) + ) { + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); + } + + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + + if ( is_wp_error( $result ) ) { + /** + * @var WP_Error $result + */ + if ( self::IsCurlError( $result ) ) { + /** + * With dual stacked DNS responses, it's possible for a server to + * have IPv6 enabled but not have IPv6 connectivity. If this is + * the case, cURL will try IPv4 first and if that fails, then it will + * fall back to IPv6 and the error EHOSTUNREACH is returned by the + * operating system. + */ + $matches = array(); + $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { + /** + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. + * @author Vova Feldman (@svovaf) + */ + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { +// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); + // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); + + // Re-run request. + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + } + } + } + } + + if ( is_wp_error( $result ) ) { + self::ThrowWPRemoteException( $result ); + } + } + + $response_body = $result['body']; + + if ( empty( $response_body ) ) { + return null; + } + + $decoded = json_decode( $response_body ); + + if ( is_null( $decoded ) ) { + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && + preg_match( '/text\/javascript/', $response_body ) + ) { + self::ThrowCloudFlareDDoSException( $response_body ); + } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && + preg_match( '/squid/', $response_body ) + ) { + self::ThrowSquidAclException( $response_body ); + } else { + $decoded = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $response_body, + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + } + + return $decoded; + } + + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than wp_remote_request() + * to make the request. + * + * @param string $pCanonizedPath The URL to make the request to + * @param string $pMethod HTTP method + * @param array $pParams The parameters to use for the POST body + * @param null|array $pWPRemoteArgs wp_remote_request options. + * + * @return object[]|object|null + * + * @throws Freemius_Exception + */ + public function MakeRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null + ) { + $resource = explode( '?', $pCanonizedPath ); + + // Only sign request if not ping.json connectivity test. + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); + + return self::MakeStaticRequest( + $pCanonizedPath, + $pMethod, + $pParams, + $pWPRemoteArgs, + $this->_isSandbox, + $sign_request ? array( &$this, 'SignRequest' ) : null + ); + } + + /** + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter + * + * @param resource $handle A cURL handle returned by curl_init() + * + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to + * CURL_IPRESOLVE_V4 + * + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e + */ + static function CurlResolveToIPv4( $handle ) { + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + + return $handle; + } + + #---------------------------------------------------------------------------------- + #region Connectivity Test + #---------------------------------------------------------------------------------- + + /** + * This method exists only for backward compatibility to prevent a fatal error from happening when called from an outdated piece of code. + * + * @param mixed $pPong + * + * @return bool + */ + public static function Test( $pPong = null ) { + return ( + is_object( $pPong ) && + isset( $pPong->api ) && + 'pong' === $pPong->api + ); + } + + /** + * Ping API to test connectivity. + * + * @return object + */ + public static function Ping() { + try { + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Exceptions + #---------------------------------------------------------------------------------- + + /** + * @param \WP_Error $pError + * + * @return bool + */ + private static function IsCurlError( WP_Error $pError ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + return ( 0 === strpos( $message, 'cURL' ) ); + } + + /** + * @param WP_Error $pError + * + * @throws Freemius_Exception + */ + private static function ThrowWPRemoteException( WP_Error $pError ) { + if ( self::IsCurlError( $pError ) ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + #region Check if there are any missing cURL methods. + + $curl_required_methods = array( + 'curl_version', + 'curl_exec', + 'curl_init', + 'curl_close', + 'curl_setopt', + 'curl_setopt_array', + 'curl_error', + ); + + // Find all missing methods. + $missing_methods = array(); + foreach ( $curl_required_methods as $m ) { + if ( ! function_exists( $m ) ) { + $missing_methods[] = $m; + } + } + + if ( ! empty( $missing_methods ) ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'cUrlMissing', + 'message' => $message, + 'code' => 'curl_missing', + 'http' => 402 + ), + 'missing_methods' => $missing_methods, + ) ); + } + + #endregion + + // cURL error - "cURL error {{errno}}: {{error}}". + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); + + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; + + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $code, + 'message' => $message, + 'type' => 'CurlException', + ), + ) ); + } else { + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $pError->get_error_code(), + 'message' => $pError->get_error_message(), + 'type' => 'WPRemoteException', + ), + ) ); + } + + throw $e; + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowCloudFlareDDoSException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'CloudFlareDDoSProtection', + 'message' => $pResult, + 'code' => 'cloudflare_ddos_protection', + 'http' => 402 + ) + ) ); + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowSquidAclException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'SquidCacheBlock', + 'message' => $pResult, + 'code' => 'squid_cache_block', + 'http' => 402 + ) + ) ); + } + + #endregion + } + } diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo index 0e57ec17ce90651df4e59e6a01c6e40d033f31de..181dea5d15dbba99a7a2aae4d5d6f1d4ea6114d7 100644 GIT binary patch delta 12607 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 CbMAov delta 13657 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 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 9e55775cc13d7ba6d407b5d1748bb3333f523036..85f5554f38a73c65c2e8410d4264fc2192555cf3 100644 GIT binary patch delta 12832 zcmb{2cYIV;-pBDfNg#w0Lg)}+C<&njLa(7oC(=Pc%8(2sKnlqO$f68Yq=Od-7(jX# zk)f)vG*?9sv5-Yvnu05WD=K1Hsf z3-(`+3|Vbawg*{O6h2d4k!4M;|6e;(O(WidZ(uqOY+zYU@idOWpD_jdHMFc`Jcfg? zS|iJf#wl0=^Nd@usAc)BH>gB%;0SKV53nU>#97uM+<{|oWMj*!fp234yoR;$N34t$ znm8Rb!IH#7F$|NiIHn;TTGKHU{aB3ttp%oGMPY+wt;J59uo;<@bsKwPc)T;vVc3s& zA|~QnI1!7Hj(p6>EWuUgK+WbF85g5c))XT! z0n1@O)G8&L_P<~Z@nR%-)BFCQ=*yN=Zv98c_%2AFG>*XJBdK zxu_f2r~z%jy0{xF;bm0H@1f3nj2f_ubR6lcYBQC@q7~6{c52ljY%1P0?xSRNE z)Sg(@+On$RW~_xLP@D87#^Mts+g3~)%c_aPF#%_w2j4<1VN_eoVim2PSO#;k0xoV# z{*{_7G$;daVp%+nA$ZZWe}Wp&7pSFqVA_8{o%bhdhQaNe3>C*x#1&8-)-mlZu^Vw0 z%*B`eR5YWY_RbAzpau|wuV7o$?!9Q*|8BgGO1ag+NqH$$hZRw&kH)Il5{KhJ)I|50 z<8Pyu(tp8J{*Id2*QgmiK&}0cSP6s3s|HdPmHHTD<6Heu&-ZL33D!>3^|w(2`UW+i zN2m-J?daS$9J!z0s!l~SsfXpUCF;b!sLeDO*$&od)Q#7m29l3jk{vi5kK-zA+R3@! zd#LL_L$zPWikQvYOc`E`p?dybqoSGaL^iH<6szC^d=86su`C~Yurgjj4fF?N(XLMB zqEI)EMjda1)v+IHDbi5`UxdeTHRjR3)$bW+lYE0CiGRcKIE0;21m8iW@(h;4k5L1; zgTeR|)p2llXRRyZ7~(1D!ON%tG~<~og8i@t4ne;QMgEoY?`a6bI=!7##$hw!R;cHD z5^9DUaV8!_ZMOJ6%mOE)W_Sj*S+Ap(;4x|`OZ9bL#j)6kcp&P2v-*<%K~z@Jpw015 ztc!ZF2SF@Ozy`=amd%e5cnh^D+du2PaD13X96+64leA>wLex_Ij!J#G{?2n>1vSuB zekxjv*Rcd%MD6OUs2SWxHwF!G+G}GpaZ6PD^B9URVie9tz1g;4H9U^WYyrmLEnJPo z20D+ge41$Pyj<82JrR4Wd1QWS$4X%kdO%~6}A9je2ArhS-+N259#kJ@BYQ3F_s zx_*O+w;2zh_QE@;r|AkdRI0C0DT&2++sN{$7e_7JjGa*f`wBIaZ%`dQ!I~I0+*$hu zs7&=lbub9EiJwF5nUSbXINQVv(BFWD*Qr#*t5^yjq6YF58)Hefyav!7waYu99>e{p znKt6Br3^Jk4Y<8x7kdM%65oB^={RVVGw@=lr6@Ow{A!8dM!0K?-&x};G_WD9d)N{? zjkT=zaT>-e_Bb=n!YF3;5Sg46p5!dmL{uggqV~iJ)MK{^!*LI4Ajh#j-o{#3%Af3X z&;+#<<4`9o!x%h{yi=^3s2Mc&I(wiMDo#XYY6vQG^H2lWk2UZl>b&cyf!sCmBUI-7 zPfVp4`Bf^*qHYj{db}E%_5{=gJum_XqLwTPb>k^m1ZQC-dhp;BHMb-X?*zy^tp3e4K}j#VS6< z`K~t|c_6HZ*a(MaIhmP$M)FV{ZN$2G0{7t^OvG2R$$t+jH`yT#G1lh{WC&^v zC!tcFgFA2r4#HYFmbD3|;!3PIm6s6iMxDQMnzQzKsCd1Jw_rEoofwG^{8Y+N(N|bK z9+4Q1^-wctZQ_ntpZHl+YI99I8+qwj`%u@F$aRiaK|M9KPy=XSj7Mdzl{xNTPem!) zj+O8rmckEFBfN?g@xC$SFV0#Sw!s|K{og=;Gb+UwI0Hz)al|vQKVC=8yz$GRE z%@u=M`vlYs2BG%Ga#TwHhT7dlmO7iT1S<70sJ+k~)$#MFJun-qV!rX{Qu41g|CELp ze2De2(lY1!eOJ_DIRkaxE2yXB04j4Ipa%E^)nVjvXW+3|mbepYLW5EFor3YW9yO4U z{8V(}Jyh!cGzV&}a59pBrD^Yvdbf{7&2TbmU`tQ~*owN(F$}>A#>-fe_zG&1e}&KD zeXNfDcD%kcvk_PYr=vQwF#@+5520@SE-K~cQRjb*@%SBvVzpPCfkdP3(*|{)UZ@F- zKn-X-7T5EiZ5n2xMz|RDVp(P4eOQY4FzNpOcEvE;8CW6?ARdV7cpIvtLl}mq zQG4J+)WGjx5k3F<>p&zFMRiaa!!a6lVRK`GIo=a>1FJVdTRRMA)JF_ zuocg;`nU3^sI0{nxDG4hCDZ;bYNq#4Gy4_QaY(+?UJlh>3)LQn%3Oju-V;@Z8i!#| z;^*?6{;kdCz!u|H<2K`V4CaCzCf;M*YutxyChK)9i^oj+1=JE;G{?U{4fGD`>3Nu6 zIDa@Dgff_plQFjODP@24@e|!lJ}2P)|d9R7Rgc4e&3h zOwLE$$M#dv=G%aE@c`-|TPmHBEI!jU$RobDRmiG7=b~Ei&Uei%tF2*QhHPn(; z$1d0!8=!v%l_)AZQRN&q#dk3de>LrOH#xuG$D+!jO@&yCF`DWUtcnLwZ^DmI1N<7b zBo9!%d3mgsZ4c^0hhPPdr8Z6t&AwZF6q$IjY>iI(QS^SY*4?Q4FfY8=D%N z8JinhY-j$OaZ4JsNm9f=Ix5!La_rhNlyQ*J~(Ef-PeJu=7j9X*fOg(~ZeuNw1F z8CZ|H?~WbHKjm$6!aEpDe8R-1aX9fA6G!djub9NuP-QM^0Q0amE=Mgz0F}wpsB+D; ze~Img@1gcajDMGNgU0wL4Q)}S&2Hy}wy4LcE9!>BQEQuxO8qRHh}%);MeN}RB?iN= zF6zT99-Cn&bKHkDiTycLW>U#B2P*7!zMEA<%_t4^rdx?Bd$1Mm#ZLGY_QpEgg!+y0cXEP%|EYDkB{I)=1OvJnF`yOuX8-1}D&-XX2+uYrpe8aG^>mY=GgY z=f5p#PfbBL&PSCU#+_J4&)+W7aMAb?hI8U26JIypFy2JHFK(Imq47s-OZ!h4hjDK> zpCO}A+w67J@kR&uK}o?}9Z0335r2hxK~#B@ACy+;!PX{z)tHakr0X#aH(>`1Kj^$C z`l9yA0Mrsp!g!pEF?bk@zef#3Uu#0Z zjVjT`7}R-nP29%V7S(UNL*!py1iF|5!;K?QyMH8VMt)QW%ZxUvtVQjGbr_Aiu@hcG zT_17S89;MXhPz{99E6(CTt5}9;WAYDH)>>0Fcd?MsFjP2rHtXI^GaiRtYB=2nsFBs z55{`L6H#TAakbIE##Hi9n{O>@#)q*voC>4 zoQcJw%4_&OZo=-EdR(on{{g3By@?}fIfzPK_*>2kqbZgpZijlK^)vBs)XdYcFqK%5 zco|m59j5(VtW5kdYGU_L4xc&Wx&I9C0+N3`X5x2-e38)LOoZRdFY( zoJ9@fobeLsyf09DhT1FDF&Lv!nW%^UQdC+~QR$8vX%AG&hhZh0gc|ugY=LV~in^%{LT#*(4dlkst{|vaRaL3ji?mw zK@BK?+A}B7jh~@X{0PMd#e=9#_6zF86n2gul*YIpo0!;t!&Gh>Z($=&xQ&`=#JkRn zs-gxKk17cmjU7$w#U8}tu?y}u?GKGVq8{I$Q1^Y}wEL}MeEB8>4H?Ev;}p~lval{L!caVj zsrVKS!#W@EgYts$MXW_U)5M!m?~g4ezKY7o7wFe+eLzLeckp@VD_D8drbc-0)gZT!mkweik*^ZehXA(jii!9BO*1#>O zat5Env#9q*)CILNc&vl1O+3mt8W+(%#>DrG-(67tDfdmo1LOCo8T|`;Vf00v#k=2_ zX`F)EGg&wS_hK_F`jPW|w?&m{I0L8S82s6^5B6Vj{-int`*7d~)LOUy*!h@EM$LRH zs+>h-i7y)!dn=DzoV`z@u~B8RziJj*TtgP8P!iu;{c4*{u@EX&4D!3jh7kMp~^Pn zcH<6IM>|nBJcPM;0yV%^pE_}e~BuTrN(8b6PKHKE$V`G z7=+tQ`%dF7EKU1v)OE*E13Y7nUo!0OSXC1N+eUneiW=^I4@6?$V$)+XK{0AEDZQm`I=P|SmzC>IO7SL5N==`j-m=`;nVvNF_>9TP-Yj=f-JW!}N5l5` zQZv%slT*_txzoJa*`5jBxR9=sQDk@^?n&O<|G)dvt-Zc}761Qz$HIXz z-0c4j)|cTVSS_jlJH1M(hlG2RQpcwz{b%HwW@c7Ka!!)3Znir+Co?mHoD{B?+moD} zMb@nT_Wn4AVP3Q#W^eCl@YKL$^2Gmy(m6>d8)PsIB13 zO3g{jOmWA03y18PJUQ2$n$GF$$E=KWW{{MTmX<@M@cMdjA;X$9@dXa&&Ts`rJPZ!9 zZ~b&QFlFWfS76{T6pZvXDFxLid&AlI5L1(jNCudC4K1t4CZ>MLs4P0D&E3|(`n%9@2 z=4@)?GLqRep4@EqkhidxT(>97OLuHYo#Yvpk>hhVn4Y~954&n&PPUJWQj_CCdZwiX z_SuhI_M5ARmr3lE*sak}Z&o%tx~)4tu8AGCrdR2{p7aShZ0ojc=rO(e+5^@s3Orcz zt*h`Yz~9M7=1+B5fw;Itm&>wCZX96G*_cLcbCceB5Ab&Hua9ZF`)0TOk;?|reqxHZr_CC8iXW1_pSMcFs6)w9FDe8~>| z$74zaj$ynE+@ z{lVP>I$=z9WBaFn#@QXet!qc#Tf!Z0-m6f!dVvz(FLqTSW4jAyx`sroa{qVAmVNqP zZ~bR?mVMaS9{%t=SC@IQj_v-rwY~c1GrIk`N8OyQkn;FTJMe3c-Sf8?&idDHOPuDd zf6V5zz-v#6y6mfeMA@~Up0@fy*`AW#vT#yN0@~qw|7YL9U}) zLR@j0N8W=XuDelMVFs!HQCq=+>>}P^rDbzQ?NC?U2&>=~+BUO-{N31_WaS+Qb@_{| fU@jXAlU2gXo|fuM(&Ns5kX+0aleedsYwG_1t+*`+ delta 13711 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 Je7Pa#e*nfmP3Hgr diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index db6f4b74b3d1683edcd5b7caea4cd2a58a635d08..c841956b9ad7e08058aca7ed2ce24012d41ee382 100644 GIT binary patch delta 12237 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 delta 13151 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 diff --git a/freemius/languages/freemius-ja.mo b/freemius/languages/freemius-ja.mo index 613a59212620e6ba60da988fb6ee3b12b6c6e204..687eb1ab53078d1d61eb42e781db4c0de28b6a9c 100644 GIT binary patch delta 12822 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 rV&dGgqegQ$Yc^gI>@04 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 diff --git a/freemius/languages/freemius-ta.mo b/freemius/languages/freemius-ta.mo index a4e6dba6fbe3f5c6e29bcba2daca9fca0d8bbeb1..ae1d263713b2fb1baf0ffc37a6220a208d5c543a 100644 GIT binary patch delta 12729 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 KSLoG;2mc!*3425U delta 12999 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 + \ No newline at end of file diff --git a/freemius/templates/checkout.php b/freemius/templates/checkout.php index a0969cc..3e8da51 100644 --- a/freemius/templates/checkout.php +++ b/freemius/templates/checkout.php @@ -60,6 +60,7 @@ 'plugin_version' => $fs->get_plugin_version(), 'mode' => 'dashboard', 'trial' => fs_request_get_bool( 'trial' ), + 'is_ms' => ( fs_is_network_admin() && $fs->is_network_active() ), ); $plan_id = fs_request_get( 'plan_id' ); @@ -277,16 +278,18 @@ FS.PostMessage.receiveOnce('pending_activation', function (data) { var requestData = { - user_email: data.user_email + user_email : data.user_email, + support_email_address: data.support_email_address }; if (true === data.auto_install) requestData.auto_install = true; $.form('_get_admin_page_url( 'account', array( - 'fs_action' => $fs->get_unique_affix() . '_activate_new', - 'plugin_id' => $plugin_id, - 'pending_activation' => true, + 'fs_action' => $fs->get_unique_affix() . '_activate_new', + 'plugin_id' => $plugin_id, + 'pending_activation' => true, + 'has_upgrade_context' => true, ) ), $fs->get_unique_affix() . '_activate_new' ) ?>', requestData).submit(); }); diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 8244aad..e8ef508 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -67,8 +67,14 @@ $error = fs_request_get( 'error' ); + $has_release_on_freemius = $fs->has_release_on_freemius(); + $require_license_key = $is_premium_only || - ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) ); + ( + $is_freemium && + ( $is_premium_code || ! $has_release_on_freemius ) && + fs_request_get_bool( 'require_license', ( $is_premium_code || $has_release_on_freemius ) ) + ); if ( $is_pending_activation ) { $require_license_key = false; @@ -120,18 +126,10 @@ /* translators: %s: name (e.g. Hey John,) */ $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); - $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ? - FS_GDPR_Manager::instance()->is_required() : - false; - - if ( is_null( $is_gdpr_required ) ) { - $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); - } - $activation_state = array( 'is_license_activation' => $require_license_key, 'is_pending_activation' => $is_pending_activation, - 'is_gdpr_required' => $is_gdpr_required, + 'is_gdpr_required' => true, 'is_network_level_activation' => $is_network_level_activation, 'is_dialog' => $is_optin_dialog, ); @@ -179,7 +177,7 @@ class="wrapis_enable_anonymous() do_action( 'connect/before_message', $activation_state ) ?> -

    apply_filters( 'connect_error_esc_html', esc_html( $error ) ) ?>

    +
    apply_filters( 'connect_error_esc_html', esc_html( $error ) ) ?>
    is_enable_anonymous() $filter = 'connect_message'; if ( ! $fs->is_plugin_update() ) { - $default_optin_message = esc_html( sprintf( ( $is_gdpr_required ? - /* translators: %s: module type (plugin, theme, or add-on) */ - fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ) : - /* translators: %s: module type (plugin, theme, or add-on) */ - fs_text_inline( 'Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ) ), $fs->get_module_label( true ) ) ); + $default_optin_message = esc_html( + sprintf( + /* translators: %s: module type (plugin, theme, or add-on) */ + fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ), + $fs->get_module_label( true ) + ) + ); } else { // If Freemius was added on a plugin update, set different // opt-in message. @@ -244,9 +244,7 @@ class="wrapis_enable_anonymous() /* translators: %s: module type (plugin, theme, or add-on) */ $default_optin_message = esc_html( sprintf( fs_text_inline( 'We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message_on-update_why' ), $fs->get_module_label( true ) ) ); - $default_optin_message .= '

    ' . esc_html( $is_gdpr_required ? - fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) : - fs_text_inline( 'Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) ); + $default_optin_message .= '

    ' . esc_html( fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) ); if ( $fs->is_enable_anonymous() ) { $default_optin_message .= ' ' . esc_html( fs_text_inline( 'If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update_skip', $slug ) ); @@ -274,7 +272,7 @@ class="wrapis_enable_anonymous() $current_user->user_login, '
    ' . $site_url . '', $freemius_link, - $is_gdpr_required + true ); } @@ -408,10 +406,10 @@ class="button button-secondary" tabindex="2">is_permission_requested( 'newsletter' ) ) { - $permissions[] = $permission_manager->get_newsletter_permission(); - } + // Add newsletter permissions if enabled. + if ( $fs->is_permission_requested( 'newsletter' ) ) { + $permissions[] = $permission_manager->get_newsletter_permission(); + } $permissions = $permission_manager->get_permissions( $require_license_key, @@ -840,7 +838,7 @@ function updatePrimaryCtaText( actionType ) { resetLoadingMode(); // Show error. - $('.fs-content').prepend('

    ' + (resultObj.error.message ? resultObj.error.message : resultObj.error) + '

    '); + $('.fs-content').prepend('
    ' + (resultObj.error.message ? resultObj.error.message : resultObj.error) + '
    '); } }, error: function () { @@ -1045,3 +1043,5 @@ function updatePrimaryCtaText( actionType ) { //endregion })(jQuery); + has_api_connectivity() && $fs->is_on() ) { + $has_api_connectivity = $fs->has_api_connectivity(); + + if ( true === $has_api_connectivity && $fs->is_on() ) { echo ' style="background: #E6FFE6; font-weight: bold"'; } else { echo ' style="background: #ffd0d0; font-weight: bold"'; @@ -314,12 +316,14 @@
    - has_api_connectivity() ) { + >has_api_connectivity() ? + echo esc_html( true === $has_api_connectivity ? fs_text_x_inline( 'Connected', 'as connection was successful' ) : - fs_text_x_inline( 'Blocked', 'as connection blocked' ) + ( false === $has_api_connectivity ? + fs_text_x_inline( 'Blocked', 'as connection blocked' ) : + fs_text_x_inline( 'Unknown', 'API connectivity state is unknown' ) ) ); } ?> is_on() ) { diff --git a/freemius/templates/firewall-issues-js.php b/freemius/templates/firewall-issues-js.php deleted file mode 100644 index 2b12a15..0000000 --- a/freemius/templates/firewall-issues-js.php +++ /dev/null @@ -1,62 +0,0 @@ - - diff --git a/freemius/templates/forms/license-activation.php b/freemius/templates/forms/license-activation.php index a2a10bf..2217c36 100644 --- a/freemius/templates/forms/license-activation.php +++ b/freemius/templates/forms/license-activation.php @@ -888,3 +888,5 @@ function showError( msg ) { }); })( jQuery ); +is_premium() ) { + if ( ! $permission_manager->is_premium_context() ) { $optional_permissions = array( $permission_manager->get_extensions_permission( false, false, true diff --git a/includes/data-feeds.php b/includes/data-feeds.php index ca84a5e..923d038 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -495,9 +495,9 @@ function radio_station_schedule_endpoint() { $weekday = $singular = $multiple = false; if ( isset( $_GET['weekday'] ) ) { - $weekday = $_GET['weekday']; - - if ( strstr( $_GET['weekday'], ',' ) ) { + // 2.5.0: added sanitize_text_field + $weekday = sanitize_text_field( $_GET['weekday'] ); + if ( strstr( $weekday, ',' ) ) { $multiple = true; $weekdays = explode( ',', $weekday ); } else { @@ -577,7 +577,8 @@ function radio_station_shows_endpoint() { // --- get show query parameter --- $show = $singular = $multiple = false; if ( isset( $_GET['show'] ) ) { - $show = $_GET['show']; + // 2.5.0: added sanitize_text_field + $show = sanitize_text_field( $_GET['show'] ); if ( strstr( $show, ',' ) ) { $multiple = true; } else { @@ -624,7 +625,8 @@ function radio_station_genres_endpoint() { // --- get genre query parameter --- $genre = $singular = $multiple = false; if ( isset( $_GET['genre'] ) ) { - $genre = $_GET['genre']; + // 2.5.0: added sanitize_text_field + $genre = sanitize_text_field( $_GET['genre'] ); if ( strstr( $genre, ',' ) ) { $multiple = true; } else { @@ -671,7 +673,8 @@ function radio_station_languages_endpoint() { // --- get language query parameter --- $language = $singular = $multiple = false; if ( isset( $_GET['language'] ) ) { - $language = $_GET['language']; + // 2.5.0: added sanitize_text_field + $language = sanitize_text_field( $_GET['language'] ); if ( strstr( $language, ',' ) ) { $multiple = true; } else { diff --git a/includes/legacy.php b/includes/legacy.php index bec6af0..c4eb5e7 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -400,9 +400,9 @@ function radio_station_get_now_playing( $time = false ) { return false; } $show_id = $current_show['show']['id']; - // if ( RADIO_STATION_DEBUG ) { - echo 'Playlist Current Show Data: ' . print_r( $current_show, true ) . '' . "\n"; - // } + if ( RADIO_STATION_DEBUG ) { + echo 'Playlist Current Show Data: ' . esc_html( print_r( $current_show, true ) ) . '' . "\n"; + } // TODO: improve handling of playlists for overrides $override = false; diff --git a/includes/master-schedule.php b/includes/master-schedule.php index f117e22..8d355d5 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -661,7 +661,7 @@ function radio_station_ajax_schedule_loader() { $debug = true; $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); if ( RADIO_STATION_DEBUG || $debug ) { - echo esc_html( print_r( $_REQUEST, true ) ); + echo "Full Request Inputs: " . esc_html( print_r( $_REQUEST, true ) ); echo "Sanitized Master Schedule Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); } diff --git a/includes/schedules.php b/includes/schedules.php index a77b3af..861e30e 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -105,8 +105,9 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, // --- debug point for show data --- if ( RADIO_STATION_DEBUG ) { + echo ''; + echo 'Show Data: ' . esc_html( print_r( $records, true ) ) . ''; $data = 'Show Data: ' . print_r( $records, true ) . PHP_EOL; - echo '' . $data . ''; radio_station_debug( $data ); } @@ -289,7 +290,7 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = "Show Shifts: " . print_r( $show_shifts, true ) . PHP_EOL; + $debug = "Show Shifts: " . esc_html( print_r( $show_shifts, true ) ) . PHP_EOL; radio_station_debug( $debug ); } diff --git a/includes/templates.php b/includes/templates.php index d926e7c..350cec1 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -318,7 +318,7 @@ function radio_station_automatic_pages_content_set( $content ) { // --- languages archive page --- // 2.3.3.9: added automatic display of language archive page - $language_archive_page = radio_station_get_setting( '' ); + $language_archive_page = radio_station_get_setting( 'language_archive_page' ); if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { if ( is_page( $language_archive_page ) ) { $automatic = radio_station_get_setting( 'language_archive_auto' ); diff --git a/loader.php b/loader.php index 48e849c..2334cca 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // ================================= // // -------------- -// Version: 1.2.8 +// Version: 1.2.9 // -------------- // Note: Changelog and structure at end of file. // diff --git a/player/compat.php b/player/compat.php new file mode 100644 index 0000000..5f6199d --- /dev/null +++ b/player/compat.php @@ -0,0 +1,13 @@ +Update Transient: ' . print_r( $pluginupdates, true ) . ''; + echo 'Update Transient: ' . esc_html( print_r( $pluginupdates, true ) ) . ''; } // 2.4.0.9: check for object for PHP8 @@ -726,7 +726,7 @@ function radio_station_get_upgrade_notice() { } } if ( RADIO_STATION_DEBUG && $notice ) { - echo 'Update Notice: ' . print_r( $notice, true ) . ''; + echo 'Update Notice: ' . esc_html( print_r( $notice, true ) ) . ''; } return $notice; } diff --git a/radio-station.php b/radio-station.php index 9a7d216..fff69e7 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.9 +Version: 2.4.9.10 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -944,7 +944,7 @@ function radio_station_delete_transients_with_prefix( $prefix ) { $results = $wpdb->get_results( $query, ARRAY_A ); // if ( RADIO_STATION_DEBUG ) { // echo $query . PHP_EOL . '
    '; - // echo 'Transients: ' . print_r( $results, true ); + // echo 'Transients: ' . esc_html( print_r( $results, true ) ) . "\n"; // } if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { return; @@ -958,7 +958,7 @@ function radio_station_delete_transients_with_prefix( $prefix ) { // 2.3.3.9: also delete transient cache object by key wp_cache_delete( $key, 'transient' ); // if ( RADIO_STATION_DEBUG ) { - // echo "Deleting transient and cache object for '" . $key . "'" . PHP_EOL; + // echo "Deleting transient and cache object for '" . esc_html( $key ) . "'" . "\n"; // } } } diff --git a/readme.md b/readme.md index 74cd47c..2f2f394 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.1.1 +Tested up to: 6.2 Stable tag: 2.5.0 @@ -232,6 +232,8 @@ You can now visit your site to make sure nothing is broken. If you experience is * https://radiostation.pro/radio-station-2-5-0-release-with-blocks/ * Refactored Schedule Engine Class * Improved translations and sanitization +* Numerous bugfixes and improvements +* Escape debug output security fix #### 2.4.0 * Radio Station Stream Player Widget! diff --git a/readme.txt b/readme.txt index 06d55ca..4a0bbbd 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,10 @@ -=== Radio Station === +=== Radio Station by netmix® - Manage and play your show schedule in WordPress! +=== Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.1.1 +Tested up to: 6.2 Stable tag: 2.5.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -12,6 +13,8 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I == Description == +[__Live Demo__](https://demo.radiostation.pro/) | [__Documentation__](https://radiostation.pro/docs/) | [__Support__](https://wordpress.org/support/plugin/radio-station/) | [__Upgrade to Pro!__](https://radiostation.pro/pricing/) + Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. @@ -220,7 +223,7 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.5.3) +* Updated: Freemius SDK (2.5.5) * Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) @@ -234,6 +237,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Improved: use WP JSON functions for data endpoints * Improved: Schedule Templates to use Classes and Instances * Improved: Tab Schedule default date display on +* Improved: use wp_send_json for feed endpoints * Added: Freemius Pricing Page v2 * Added: assign Playlist to a specific Show Shift * Added: Quick Edit of Playlist to assign to Show @@ -244,6 +248,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) * Fixed: Workaround Amplitude pause event not firing +* Security Fix: Escape all debug output content = 2.4.0.9 = * Update: Sysend (1.11.1) for Radio Player @@ -803,6 +808,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Refactored Schedule Engine Class * Improved translations and sanitization * Numerous bugfixes and improvements +* Escape debug output security fix = 2.4.0 = * Radio Station Stream Player Widget! diff --git a/templates/single-show-content.php b/templates/single-show-content.php index ff46294..70229b3 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -288,8 +288,8 @@ $image_block_order = array( 'icons', 'social', 'patreon', 'player' ); $image_block_order = apply_filters( 'radio_station_show_image_block_order', $image_block_order, $post_id ); if ( RADIO_STATION_DEBUG ) { - echo 'Image Block Order: ' . print_r( $image_block_order, true ) . ''; - echo ''; + echo 'Image Block Order: ' . esc_html( print_r( $image_block_order, true ) ) . ''; + // echo 'Image Blocks: ' . esc_html( print_r( $image_blocks, true ) ) . ''; } // --- combine image blocks to show images block --- @@ -464,7 +464,7 @@ $meta_block_order = apply_filters( 'radio_station_show_meta_block_order', $meta_block_order, $post_id ); if ( RADIO_STATION_DEBUG ) { echo 'Meta Block Order: ' . esc_html( print_r( $meta_block_order, true ) ) . '' . "\n"; - echo '' . "\n"; + // echo 'Meta Blocks: ' . esc_html( print_r( $meta_blocks, true ) ) . '' . "\n"; } // --- combine meta blocks to show meta block --- From 066edfc868bbbf7e14752083fa10f9fde2b07ce1 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 25 Apr 2023 22:32:18 +1000 Subject: [PATCH 289/377] update freemius 2.5.7 --- freemius/includes/class-fs-api.php | 9 ++++++--- freemius/includes/sdk/FreemiusWordPress.php | 9 ++++++--- freemius/languages/freemius-cs_CZ.mo | Bin 52613 -> 36563 bytes freemius/languages/freemius-da_DK.mo | Bin 65452 -> 69088 bytes freemius/languages/freemius-de_DE.mo | Bin 69915 -> 73551 bytes freemius/languages/freemius-en.mo | Bin 64883 -> 68519 bytes freemius/languages/freemius-es_ES.mo | Bin 68396 -> 72032 bytes freemius/languages/freemius-fr_FR.mo | Bin 58948 -> 49369 bytes freemius/languages/freemius-he_IL.mo | Bin 54828 -> 40497 bytes freemius/languages/freemius-hu_HU.mo | Bin 48200 -> 26759 bytes freemius/languages/freemius-it_IT.mo | Bin 62258 -> 59634 bytes freemius/languages/freemius-ja.mo | Bin 60542 -> 48367 bytes freemius/languages/freemius-nl_NL.mo | Bin 57340 -> 47831 bytes freemius/languages/freemius-ru_RU.mo | Bin 64951 -> 49143 bytes freemius/languages/freemius-ta.mo | Bin 85287 -> 77216 bytes freemius/languages/freemius-zh_CN.mo | Bin 57962 -> 55459 bytes freemius/start.php | 2 +- 17 files changed, 13 insertions(+), 7 deletions(-) diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php index 74f475f..ef56fad 100644 --- a/freemius/includes/class-fs-api.php +++ b/freemius/includes/class-fs-api.php @@ -371,7 +371,7 @@ function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN * @param string $url * @param array $remote_args * - * @return mixed + * @return array|WP_Error The response array or a WP_Error on failure. */ static function remote_request( $url, $remote_args ) { if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { @@ -386,8 +386,11 @@ static function remote_request( $url, $remote_args ) { $response = wp_remote_request( $url, $remote_args ); if ( - empty( $response['headers'] ) || - empty( $response['headers']['x-api-server'] ) + is_array( $response ) && + ( + empty( $response['headers'] ) || + empty( $response['headers']['x-api-server'] ) + ) ) { // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index 6998e75..efb9e0d 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -348,14 +348,17 @@ private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { * @param string $pUrl * @param array $pWPRemoteArgs * - * @return mixed + * @return array|WP_Error The response array or a WP_Error on failure. */ static function RemoteRequest( $pUrl, $pWPRemoteArgs ) { $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); if ( - empty( $response['headers'] ) || - empty( $response['headers']['x-api-server'] ) + is_array( $response ) && + ( + empty( $response['headers'] ) || + empty( $response['headers']['x-api-server'] ) + ) ) { // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) ); diff --git a/freemius/languages/freemius-cs_CZ.mo b/freemius/languages/freemius-cs_CZ.mo index 7e86c17e7ed46f693ceb2cae1dc9cb7a33a2c3bc..8dd3c06700bb3b0f13c99ef0d0ff75ef8ffd06fc 100644 GIT binary patch delta 11023 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 literal 52613 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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index 9e2c18d7cd886eca9cb24a766e0f19983bab2aa8..07ffb83ffb3c636cb7d4405fe266139173aa16c6 100644 GIT binary patch delta 14816 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 u2R6?ek{X4i8?)BlGijTLx!jwX2`mc;8G1v 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 diff --git a/freemius/languages/freemius-de_DE.mo b/freemius/languages/freemius-de_DE.mo index 96666e9a7a2f7a343d3bb4f7f976c63db38ecd2e..2d951e98adae3c53d5ff5b0d3b04d4911a1e0d89 100644 GIT binary patch delta 14803 zcmZwNcYKc5|HttwM686!uo4j?Ms^T;OT-Rh)oeu64w2a0_NdXCRhz0&RZ1zPN~>0V zY0cKC7PYFawkW>T@AbaV$@k%p-}QK$JkRHIo$I>JboW8r-cZMJIet{Ol6W5vVs-3W*Kt0|_mHy5gi-o&jj zKX%0e*avmN49troFdt5+?=p!QHt`lVWXDo$hCiS>Shj)VBx5|b#%VYJ&*E~d-_UUw zNaqrU;Ez}uU!X2nieW5_VK@ilQTZ=jRMe9bs3AIwzIY7-@Bs#5p2j90gcXP*F$z;q z?Ixk_cnKE5H8#HubpwY{BXALQ;U6&`UAJr^xQXdudDMxjp*mC_D`E>Qj+v+-o{eg^ z6xHE%n1MS{7b=(FIIA!mlkhlpz#@reg!p^F44EA#n6sy1H;ll~F&6J)5iH3(YRyEVMy3ho#rEjW^_{L%JV^|(JBFh=G#2yW zJez+9)owL<;d;~vZ9yOW6m`L`ZT=#1Z_W)Ijg_058+s3Q{6X~R`p!`*3-B^(wGL^a zo^gCqwvNVp#M98FJDNvDt8*c0 zj^D!qxF0LwQH;X}7=~qAIS!Aw(+qX|6jX<1qB^t$HN+cH=iP=n-xuhOCs2#}Vk^d9 z4Syq{J9~sR@FnWRHCmek>!PM42}fa9T!d%Q8~e2}$7iGRE-Z!t$z~)YQP*jR>QFP( z>hF@w_?M(Ik3<*Tj9Hkgt$DGGMD=vObt7u%K1WT_5u3k+dhQ>frlN2=)A0!0OI#h_ z(S9s~GuyLZahZ!sUn-9=5BA`_>4Rw)fWuI4xapW1*PuVHN6qyf?1g?E%u|qw>cBbF z`5s_te2f}_q8-ioLr_!VinNIuScgO+YUn4SR_S#77v~~_(v%Frf?VI3N5v19q3&QS zhT>N?e-riC{bTc`UN>`DAA`v^#iE#orEm;th?imoT!)MCIL2WwUYF50A6=T$gQ}o! zZ}WNoHkKwngX;NTsHqv0X7a019sL41)H#n@12y`XjwE1i;%?X&dtoH5Lr*-7YJaW| zmsRCw66*Qys3G!BH;XF_wFqNTtG5Q~@o8Z9x5I+O-LWwCv+*cY2dAPQ*JW4@-$O0N z?@=RkJDuBBPyZyL3q3+Dj_0Tg`t~*X;;6VZ>OvJ!i>wB!1MSfZd)heNIt)vYACJ28 zcTgj-0(IlZUAA%oLrC1fwU~<)rH-vf-N`1@g$`gDJb{|~`>4k-e?N190L(>P0s}A@ z!!Qo>U}qcmzzAa36e?Q%+fXl_qo|&p#~8eh>WE)|v)YT|1mdaag}Dcqktu-LYKNC` zChA7|3}n9tZ3mghwGfYtIuefDkjsgsl8;1PEP#pVgKe=g_C($3OjNt~QFHkbYDjNj zJl?@d7&63kC<*mcBx4lzMNRD@?1-DNx|aRFRF08|&oGb2zoJVzan zhY9sXU-ZE;s1wJa=CD5MaZNzp-cAoxyIrWE{|a@XOBjGRP*2Sh3_|}AjK3C9%m}l{ zs-q65jk=R2s0+13b+jF_Xq;43hXcV(@6B#V$AhO<^Y9r0au0?hH80v;DqB?qK zB;#L@Iett+can?OoeV;MtcL1HGgOb;qB=APHRqYAMK})Cu}N4D=c1bz}m z8}`D*Sa=*?)6n%kl`txG$D8N9H)=60KrONzs197Q-p204f8kP0=26ky-a$Wnfg$M2 z$Tq;bxqs=l>9u!6cFOz;X zB0j?pvFv2?aeW$-h)Yc|uiSK0M;4%_ZWHFw^S^`21`>NP1$#|(oHe);=VJ%Dc^hw` zI&x~dne%g~_y-$b!$jhnsF4VtVHRmUEJ~b=n$o`Lh1rU^zB7(WIL^krxZB1DPzT(# z`6sBS!Jd=3|_#}==HYwt+xW!CtiY@>+=|j_fZ}8VIkGU zYN+!M!>+j7MP(wDA`8r~#|yAM@okL8+6#^Sv4{tc97d7E;5RqTx=dDz?Hc_*!R4r) ze~kWk9E0&H>cr154#QTNj&w$~8;%;e88*KKH6ll`F#dq<@BeqH=nfyDdKS3abRZIS zqDJV69j#r_o46Nhbq~Ten2jNL7)#*|)ChX4F&7L*U*bq>J#_#6--e2YJOy>YYp6TQ zLOt&rP#yUQ)$=b=Cpv=#@dm0xe_#Q8ZsWXbO$P%|BUsAD@#sTbe=Xy$6SN{x3%g(w zT!6ac3#bcTMqS`n%#Tk{bDwLS>2NUW25O)m)Y}Tg0oF^TlLA)Ff;8UE6dp4SPe!We+ONbM26z+3T z8BV3zX7eew3H6wLi&`v?QFoH>19O4^)Zz@n4>1<2;Z;0{ep}3|_$*c=ZcL*f9DwC< zI%@H5!EkgPqoN`D8@(~tRudOSO+g51k(I;z7>Qn3-C7U36DMFhT#H&me`7Z+^pSak z4nWqovj>l3=WXthb2(+Un+t?lqtQK==tIML$T3b!yZ;#$AkO`<>2M*`NClx5SrnGW zMAV{9$Er9QtKe2tyPq(>p8qFQw8(PpFhlEyx`T?SJ8g`53Yue6Y-i)8=u5l?H8LMt z_o1fbYt&=-40R*Lcbbk@K)qMu(8cwgBr3YlQT!B7VLDF#gkL7`K58yI?&2ai9*bbi zZu6>bgjzELQSIhq1n$KK_zP;T!#H^rY>Ojs3c8}GJfo6?mG+p=?V+g0Yawd!Y(*`? z<5&pKqt?bv)KuKXV))R;-h0i66+?|oY1EWfMzxPYou~F*#=kg~<|H&ky|FV6#c_BH zV=(bEbE0f4LF__yIq#y5OW9}MoT(TNI-(CLYKik6^8ZZ)do>s?ed zmpf34>@fP{NgMx!x`R6ygbz_C@a65I69%H@I0V(fo~VxWL7it9F2HHXEIY-&Fc z4z0xy+=Yys%Q;7-42h?x$1vzC^Xqs7as){ z6fdAg>L1kNe2%FYaKudc2pp^D{}z?f?C5>eoNzMgM6*zpSgDm=y&F`VFYFqAHyuHe}?g& zMrG$2vnXnvHAC42=aTPVVf{el_tjo6*|9`3-Ti)IZxLv^&l_xy!h?Xe0z zLY=SD59WgHF^hNz`rr*0l`>TR!~*Dl$;?#<)+L^SUU(P-@Eg>DKcP80RyLo4y1`WR#erBDUE^$G4(dcJQ61TjTHQy{AHA=b6NF-J;&9Z3qOmB(+x@LD zFL5Uu_e5Q=A6CTCs2AE=KJ*= zj8q!x&L&$IVm$GB)YEVY$Kh3c6TALoM(h{VV!V$VTvQ%WiQxHN@iV{i5+A$HFCmI= zm|r0NxM>db|HZr~N}`6aGWub>jhkU2aR<~0E<=szZcN65s2laYWu`U|U0Ot4sc6oo zVo98XTD==lBXSA5<6oGF$+!7_hl{Ze&bnj%7W6fy(D9yk&9~qk_sk;o{ngw+ZPcP` zgz8wkUm1Tbu5LDwfts_?sJWbu+W)4_uR@(*3l_wEn1V-9HxP8+eCU)z#kEjtrx8Zr zFx2rYF$A~XXZ*dWoFoy5XHkpeuJs{$6Td{wnfGtzi$fr4=%Y~=YGm`x(TBJYy&v74^KQq2_WFM&cs7{}5_~PT2Sqs-qWC7ru#w z@PUnAqNcLoA7(_$VPj%f4Jxy!jKLY``M}Km94t)y1?s@l=!>_pF?0L`)v-E%nKjcH zix6j`Ix-bQaXxCUcc35cM|J!((ovUlmP!i}*RT?Xv$6(b3QoX7mW5;cQ0M1GI3>O!8Y=*AORI;d~ zVB3EwxE{6G9$_$+eQ6d|1JsFI;u&m*L$SQ$;r*zGrPz^%I?*K5TrI-N_!(;GZej@fdKqJ|9B~`e zV#`E59g|V-hgGPjYy%F$SpG?DKCX08IY}iZpND%G$EJGzA5&3} z>lOBJug;FBRX+j~a4ojNTi6S$`g%AMaUu4_0RAjG0mot-KEt{g=|@LB_(L^D6MyP& zI(Q2;qU8fT_$}MTrxledBo1S3{0B#4l|T>oJNiTHLmX1n+}RXdMf@9f!#Tyw+PH&B z#D2w1+!Zy%Q&Ed`3u**@z%A%e!ox}C`p#A=>fsC2kVTgCaKAVjpyswY>alwrwK)5t z7Ufjb3+4mV>i)#WpQGMzN6`~6pdPo&cK4=m)rg8P>XRR>bQeJJpXEN$~HKUTHTjWcl-d=^T(*g z`3$w{Utk$5#J|t#RU2!qgIcUjQO9>hb@X-A9jBu@I1D@C*kGQ2J)dVt=)g;;1Aj+d z_#vu;Pf+b%+Bk116Bk0AxG3tlvZ(fzP#20pb-Wg8MC+nAwn0r%Hy0HhH~@9vP}C5Q zL|t&Ajc1~s+xf_%c0NQc%8yV_hku9}($`T>(-73iOt;QQy~OWHYQ_8Xb)om?td7F689>doFXEkLyr8VUadEQ0Namsk& z4{W<~sK=Vuiu04pCTrUZEU?a{fwoBoCzQOV<2Af$bK%w;u2Ff@1_|~&3gc}(TNfnm zPkBL|_m$)7NuW)iMkQ&OkJ5uOnVmVC7T8YWQuahS`O&Y$L#b!Ia?F3IkEG~BXB4HW zZM%ZpKwJ07#rTh7=NBYflH5sMtF$U*6|uK%xEjllYfHOe>RC2lk$4gL4B}|&f%qEn zChBicW>9*O`!BgC)NA7B7)dEZJ|FKN_x1w`KN8v+QqS4GrZR%kkA3>qtL+qii6QRD z{SRVJUwf=gIg!LW2#Vl&n~b$>Ue8hI)AhfmGJxbX>U`Y0x4Q=S&oF*ICC}%EQ`8<@ z1NA=0*&47<-;6Hf60B}_Y{z?)CbWOXz6~}%N0M{j{}^`YBk^6-rWeCNcZ~~@+e`f^ zWi9nF9JGS6fqDya-Ka-mKVp5Z2N6%FUV^%|E0pz=Ta;?#t5DLZZ^abNe`f-1-;!v| z!F%u&@$dNR)`d8eM0G4pJ8f@M{}m5n6>?|XS$;_&*4DzdQ+VM)7lqVFu)n2uQk(b!Wi0iEZQa=}6-5$#2B6rT;@OumMx zx_>@m{}oCih0W#sho3_T+M>=en^Kv$E(V)M)XXo9Eo?jj_mc~9@8O#c@o)~Vfb(z| z@^#UXK;35GUZOrTqs|YhN~$5QlEu4D4~>koa8Y+C*O-Q zmU>f)w)YLrKQ>N4eVqGZ369%BolkD}zZ>Nt{)G5V;uMNDS1Xd*5)AI2`r2HT{7X!w z45O|;NNG!=d_sK$IUje1wPE*tXybO+&DKlXeG1o5KSRl+G&k2Z_y3HYTPeR&exzud z`pSW4si&B<`+qsS!NK(@zmN~J?Ka!}<;kBU_mR!_u=$R}%P1?^KLKlDhn(kc3yJ(B zvT0m}l4cJ&OkUd@_9anoN&N$NhCj8~eM4+qhnVlB?tgBXK>Hiy-?00{0rp+OpYfxd ze9rtIBAG$aHVw<$dI{`EZXzd4rhd)lW8E!zSrK<(->+C1SCW5^l1eE+&V%A2{~Jb7 z-k~fZ{>mQj8cd+ySlt^t+Y!&E^rD`#4W}MQ&=p77{7vHJv`eCtrv4rIp_Db$FH^pu zUXoIqdH^n>cxZQin$yDi7x}{Mf1S7;_4AZ%lrJg%d+<YSvorN$lrog|_A$oJdd!mC}Hmw#noho9h35eoL(fZDz9XHuZO~J8>5Einf(2okSyw7iAsg5@jtr z)v?}`N7R>5-l8<)1arxKNI6OzL($fU(w=?V_OY)XWg&5W;*nSxQ}KZsu;r(u6F1}c zPp3GQDU_V;FqI>mpo>k6XWwvJZ%Mw5ZTlt`BfpoNAEgQP2y&gMhvOgUYujFAUo>Sm zIc*IozfdM?{&!PJW5>I;aV6U*lh}v)c1j#|Z`zfm{(!QWqRpR@Xpfs^b?@NEHxwUo z#j!3nw>xrCU(fOHDCYW32ZCdEXCHPhCH^nv1i6CPoU((`k$rJk$hH;BiJQF=M-hKP z+=;e@`Q2G<+v9WS(iK8h4$$1eM!rt0pH{YBJR-!DX zT(G%qSb$@-<+bPh_my)u@nFrqMFUUD0(P7=ZJpM{yNJId-jB<%pWRnOC-7`Gc(CKy zIW{)S30;^P+h}J-ct)9>*ITxU*%?3fPKokS?bAo}AKpJ>NK`^=}r2jW)4r!svSy$sG0@3L?xvU zNzY8p%E+u8+BPG-PyejYs?k-VtFXs}Q7P%EnQ8snre^hvime?Q{r~Mq9F;aGyH9!_ qH3*L$FuZ`H8#ebFHas#kdvIuK_RfAwZ&%ov@`Zn`odch2FaCcj+WGbX delta 11134 zcmYk?34Bf0+Q;#I$ec(dVkQ#Akbxv-LCsSQA*LFdrV+`Y72zH1iqEk#c8PbKA-D{eqBrR%ifb?oH=qJOf|~Fb z48prO6QA4h8Ffq``KSynbJHkJVgmx>B_C}!aV)I`7G z3j7n>;e7VB2cAb|sCEO#*@RtC0Y1U<%qxmYUU9tGXafF2Fhaz`c=>a8x)SS|4nEpqSmM)>0Nliab7{) zn1Nd1a8!nJu^3K7O*q?*uf#U=x8PWOfLhS7MDzLSr~u~Td|Zou%csmsix+7Br4GQ)|RM+bwMqtH)`+uqq_`^VKj7XrlL|m z7b$dSFY1`xK^Ef#lYc#+6Dpu?sDM&XMK%)k;5^jxrlA&+k0o&>>b^awg?-wL{ihC` zuNcsSofpgpJyBay9P_XWF2H4|2gWxy4`_xOZ;z$%5-P=yP!sv`Hd8>sNL@M+SPpw* zAIxsyWF>o#co3C= zE2!t+Le>>rfB)0JR0Xt%p&?cLImtW!vx2 z!KAzoYOiOb4=%zwxC%MmZs#*RUs6=ww!$jVj*R zsN&jy+S>zI6VG8oEYjJ$ITKNt%*14T9ZNI6^F58~4E%~kFt`gZ9}Go?oC&BR{TiuL zrwC~ojxA94ZNrgRI>~I^G}KnC#<92&6=>ydW@{4BpMF0KVSZ;Y4Xt1thT~jaz>hEz zzp&%C(VM=1ck^N?fqK)$VMT0*zL<{HFbm(sg&2=9EV~xIjM~~+=vDxiXm9|W0X75P2} zl%gL|MfLzyjL%Rj_ULVnQ2^?C7%GJ|F$n9~ej8MP-B86g80+Fl48(P)4DCV%`e|>L zuZa#bPy&ykCcK0izhV2op(c8WDl(5gCV)z)`=V_>!P*RUj60%Mo`%XqCMp99t!vyg z^di}gtMCLWvQZp%tztYgi_Y=IF4H3Gps=56J9m%?8O7k7Cl0(JT%SJLU~j% zMWc$V9x9O57=^>JGA=?re;+FKzuW6&)AjympfwHNEzVHX3O+&Y{bAcbhdQ@cQJD!E zWCCb}mFRaseJ&Ff$SB*Nh^mok)_hbZSD=b{1G!SvU+`ndS{R0u}hXs0FP>1-cvk+2YSL$$uFJP7E>LLKTZ^ zs0pMbD&mT$fEuAMzJMOs0sXNHHpRYJ4Hsc2JcuOC2_PzcJ_r4AtaYlJh9aA1Z&;0r zcq3|s1*lXVMt$%Y2H<}+60n< z+PcxG=elvd&i_;z$qdAdah#Pn5$9pVYkY0Oov05i%`+>&HU+f>?^(B@Tj%qr9k__)=s!fIJZQZ6UtRdWi}mp-HpMz`X#Z)9;=QX#-@@X!096C)up*wql6W7hp*Lx( zjkT~WzKm5c50$}XsQ1cIROXJOp7R5izz3+$drhR#jYj>6=8wZPY)$_%R>dllOi{H* z?dd|)2RC9RJck?z=Lt5$j5p1mZ@_T+pP~Z3f=$qibm;l5urE$?)0jx(C+vY~Qyix| z?!{UdFx8ld!7jdHqcSt$E%W{T18hY97t{m8-!@;b$D=0PhOgppn1o%YnT4*z6#DMJ zXe^=8Yr5mSi1)DuHhsq&qd{1O{#J~{lUNg9Fw}Ls0jO@CLE56d@`zdmZDa)4fWvvU_3rX1yXaa zxvvc>b1$LBUq{u@VhqB~=+FGlJ{nr#QB-6%Q2{(hO;}={IeyixF&MyjJo;cW?1Jqu z3>TuReK#tDr%)5##p3wf8Z@8$doU1A!xJM>ABe_yY>VEQhYDmO>Ol)o4_b@6(`jDc7S^|_|jcJ_L=1>|239>72`9AaoQb+`k#!wzF3OKi zIDqk&7jf2cKTg5%i_N=UM{5jyZ_L9tFb5xFbsXwmYK~Pt1~af175Q=W!m}8PmvIaJ ziFI-1GROH0PvZz&xZFI@bA>6kSkx)$fGXAzsA8Rg%E%tng4|!)fs?3|U&F#ZL#6Bw zR3QJgI`5nB0p8e+@iZ)ld$1pVkDW1grTIN@JRYN8Zk2f@|BQP6Eu-7HPeXh6H);#? z%kx$UMBR7*Ra8e%5r2=$)MZpr-NQ=gv&Ix@4Xi`I2`1o3)O~AE3)+XOsV^~9=l?Vf zt>89lrJid|)dyf(`k}Vp4^{O8Q4h+szJc13X{cj&0JV?{sK9?ky+Z?Y%slU4 zfd=N$NXHKA&ClxFP{mPZ179kzIcmiZuqJwJG&NHTbzcvR#tHZWZba?%EmQ_dY%)dI z7Gvojz;<{C-IZz7`M?~fURa*~NK_HdL>0>-^uP`1hg(o7EwKF)=uQ7T`r>u;#NX`o z2dL*fL%nGOHk%AZZYCkU8K}d+1e}4j(T7skgBqgFc}v`j{ZXkc^`UuFRz;nTL{y5C zP=OCZO_+@VI0-fJY|O$9_Il~9bErMOh6*rZ zy9uN^>N$0hx2)413-AJJ;(Q888C``M-;Ckt9=O9~UT_*Sf%ZcMl!jrLhsxZ07=imSNC)CFjjtHEiwCe^w+U$U9&;Mr z!EDC=L>h7>q> z%xqO0YAaquw;nK(Mjf1ss^TwD69pYId(|3;(jSC+!=1#2cn^zU&BJ_u#29=DCu0g; z!fx2~bMqU~9L%Nf`GpC1+!y3u#o>HuQd$XT(T&7N+==n{BPxadN6h;m4E4%xh?OxJ zLvako;S4*zACl4D+ZLJ&bSL-!xk7rWwyi-*dISa4uTVS z%+$gmRG?m8bA&Ml6L3H3d4HfLEPLFnydJ8iKEMe4*iFNa#(B)Z8`uQ9{Kpj64Ajcz zqdvF+_2Av8%w4hL&(Vv1z&FMcsQb&IFV?`~n1HIaR<`d>rlAL=p#qtVs`A;WB0Pn9 zz)e(f-A7II2utBpd%eWB=KWC-b$=vk!rE9Jo1;HwpeN=c^SPaIH1vU~s8r2C58RBk zaXS{ns~C)Tu?9LP%vRMz1$G&A?yqA2{*6)S{hitS1k?h%qYn;151s!(H1xrtSQjVT z8$L!A-yy7zf1)xK^}ShH2Wwxf$M{ezf~#->uElpS;-tyg7SzN$aXs$GXt#?m6sJft z19N^LI`vPRe{k4y#(eNR26FuxDuwqj1fSY|&{^{XM0r#OQ&Aax9lPS&sEnRPZS6&@ zi4o_>zc-B+Y3Mj5V*-xA(zps$^?R`mmimz_V1G=+r1R#tpqZFV;E@;1x8boD%>-vr zwc@&Dip~cWSQ%7pRlY?2HBg@c?OAiwUUs%ObhG2>7{qusDg$p~GR{UV;5zD$&u!ax zF5B-B7|nP?)aTPM40BO|~YA;{F80?1%WEv_%^KAcJRG=$R z6K_V9?OdS_IC$*4fmP?;KoO0gTY1#?iDS%TWCO&G58UqC}EJy$ru@k0LV1mEQS zj8AY6ess&cA2NP5nOctFjDLr*cpo)UrQ2rZQ8TE%`8#REe9Xb9yXMdL3RFf;qE>niz3?W6<6Vr#pg+vN zs!2q*j#24*=EtF-7(;(QR={7;AHD9I2Z!KE`ekq=-oXOw|A3#;G4xNe=3-)0Ah#bH z@1dUa7pew|JThO+ydIJN5)5Q8ASa-X*<37+n@}tG1a&G7p;CPYb^JX3GG9ajP_@wp zHBleiPeawhYv_R!aSBdF-RJk1{nx~$9-BRhLOrNGYOnfXO`L?$xEXbh&szV&D)dV} zF~!yhbvinrCQL^KIs~UZQQaB&$<0dSPS5bjH!7>>7%)GK=QKu&ptD$=ejjA*@V-frg74a$5N-v-? z^f$J`z~>w}?2bve65HT?Y>Ey3HNOpyMrHB}D!>O=9eo{_GZ*8rE%Q5vX((b}m#eT! zBQT17OKgq9QJL6*Dfll=#FQeg!hf}L72lxWuBfZ<&-D>(LjMVNrWosaxC(zso$To< z3~&cF;`%*I*ZGg~auvS$=3-+me2!!BIgY|S{xxPA-o{Mq>fe>&~Oe)RkJx(aJ# z7q+8++4igXxe8O9geuzCP#IWVu!4CjJby zf-g|_9k=~cwtpV=;OnT*-L=;rq9*zW6}V@R$*2zo&@YGDqUvrM`e1$3L`_h8*$Opb zC)@9VI=2ImqIU97ML7<2IFy6`ne9V0unY_?YzU zocvAA;&!fYm=#fE`0$-2`X<#b8W)$lGj;myvJHF0w#rUV&CSdj5!*U7H@$JVUsBiD z?&)JPbN+8AAuhgNYgCJtYsJ$99i4rp+R!M55MtK|6Qj{PzwPR~-s;K%@ zY|5v!N{t#HTB@{KE&V>8?{jkd`QvxpZpZz+UgsL;I_F&1`<-|D^o3{ELa#YRT{Ao# zr%Ht5-n!OxoF>$|;c8rpso1EVdn20xUAl8p@oPM|#1K;3e7=i6@EDpwOT#C_+9fyH)&S4N< z#IpDs>VkzC#)4Q9XJP~@zZcb!!>AF;c2OxrR`o|j#J8E{_9XFPa+OWV>*W6Jgk8S zu^c`|X2`B-WE8`BVj@Qv23o?&dGv!btQy0Cl6&Aq`=!MDdyo;4gMLl{4eQ=7q zfq6siI3M%iV$=w&#QeAgb-@ERe;TlhR!*S-S9r@PMf}I+B=~-+Si(jKEz{DH#7ycIH#dYbG(R( zAAW{mco3uT4u)W0tmE*AI}K4M7>(-CcvOdGpoVxE>cneN=i7z(@GxpopGNI>3w2`; zVm1F%o{`XrtF$u*Mxmyp0glEFI3KeyANFi-PB0jiABlnJ+rf-vIn;$}p*qwMwffs( zNt}XRaYYBle(Ct9Y-RCxc4)xsMK~05MC)4p@+)rE?7pfio zaeQZ9ayT3N<3r4ion5>)^Hb@HMX^7sBjeB$GqDIRM$PqB?2X>><|!C}>c9!q3GZN8 ze25x>yb0#~MNm^x&c;=+KCvr?iiSQNwMxg~E1ZlBiZiK;X}^Fv!MCU>xM_WaT6}-w zVDx*-#G|kl@%yN`K7bm@V;GHRu!f$04<@3ChvU>l-Dx_vIRQUHEjr&s$DuDybJU$K z!Q!|Fi{UveiFYs@HO_S*0wXaQb>0kQ@SJrRh<{-y*LVDT@PR_29uB|;$U1Y@VjcVq z_2P->={Q4i5Nf~6_zu3&%S_p)s3|y!WAHqxqivZlO-VB5#VJ@AXJauJmDN;&ai4AY z4)xgmZ1aWtn7NF`(&X!70CvSPn1&kSSy%}dVkREKD2(TISrtD(P3dlQVeY=zgvwMZ zW$`$w=l77gagvfvem-ibcOl0*CsAvlNR>6hng~w3)pRj(5>iF$` z^g~1Bkzy8CNz@_?L9N~@sK=+KZEuMh!cJHad)jy?s)J)ti)=R5#6_sZc?LB?KcG5# z7j>NnDU5>_#}g8|VDA2=AwMcEin>r~)FP{b>Od>ZgK;)awD!jmd#5b*JM|J1#-Zr-U%^^<4a2ZV zs_9Sz)Kk$MBe6SbYNulYF2~yV6tgiRO-~2&|CEaEFocJ>G(BI7yc?V^hnYDlGu+&H z1Jqh*hQ1hw1+XWoBSSC(GqF4#K|OxAQA1yBglTVrm57I6dp(EqsAx!Uq2~U9jrGY? zmDmS0GIdZLNJ2fX!%)X%pgOYH#v72Kbw0IzjvC3YQOBP{Jyk!VOASw`=ztek2y-*B z`7r=>;)X{yLx< z3EfFu)P)+OdfF1TXu6wO0hgQ5|WB>TwHHhmufpJ^-}{hoL%_jty}#YEF+}EZ#$&Xs6~_bNo`& z{;RB8Q61awvOAna_4qvM4u3>FZVylgK0!UEFHs%z8)xpQFcu>YMzz;Qowylp$9T*{ zulM*qiAyj9qsE)Z-Srj~Ev64qi!2M(feY3jum|xyT!hVeR5Z8OurU6HL71D7Y=#w4 z9U6!_?*t6Q6*v|5;81KZ(f!rVZ9EJkh$o^q*LODC#7^u*d>wUQ!)a!& zV^L2@JgNgdt;wj78-zZ17Bym*QH%N~)T)1p>R{gKW-5ZLwb7-yY;P0ESdw@g>O>1r z2d+VH+=GMg5Wa(4Z=-U--{csJ_0PiHdz>Y>jp zvuXn{k~kF0U;=6mM_?tKj@9r}EP*$$0=_^EY0$^!#nJ|Sh})sg(*^Z-k3=0m4Ljk! zj~V|2Dt@z>FYJRAaUHh9lc*O@@j2$e2rNsSh-o+$8{q@goQJc3g0V5G!%5fxr=iaO zA528Qxy(PNxTwTaxr?2#{yg)}|JeEk`g`zQ5F=@SHQ#)1uae1Yn0O%SgsU+cU!pD; z#lzkgQ?VmnzyvJ6(ER-{8kZ2eu26ZC$_O6rFg%Q{@IGp2t1mV$jCc$u9*Y{Gwa9ck zCr~Gj=WJ@pMfJXb9J!R`)F|iT6=M?YYdXfeNS# z*TWLn3rpc>>wMH)e~KFVv#2%kJJ!MAOO+)@dO58@s;Mz>Yzq20Sn?gSO_OtGf?MUjV=v&78M<^6?I41 zsOS9!sw25pnG*$|PE-kX2X#;#YKDH;(Z=0T9UO=n!C^L@j`@k_qK;p=it(>UWebUB zco)^Ph}Gu#tcJRSNvI2Dpf9dKt$_`wjvqzc$TcjCw^0}P0}EiTHRd>fYbjKF$QqY9 zaTE#ltdZRz*4oAH*ay{tA=WXN=Else0n`PdJS5YI-c7u65<8UDHHtdIi z8_imJ&qXDK#9~yB4qH!Q9^%WWJGzeR@CowRI14wKUrf(pZ{jJN&C_xlwU#`$m_=9; zHByaG*XfG+a02?GYX%j~T_);+D^YWwg+935#`|o11RIk-fvwSRtLb1Ib|CJL?Qjnc zMz3w=7t=Ikz?{6>&3OwN`TLKGE?5o=VmQvkdN%(xsv~DnBXk+H2ydgFmS?EP)%R1A zFN=Eqt79a-h1zc_>W-J7*2G$Op84NNMMIp88iA{*4&B09_`t@&pP3GZp-xm6)uE;s zitSM!yXlw@XQTF8hJ|oFrr}=HbwYQTZ$(a3Dy>MI!FVjT(>%|EaU=0`R0pGXnHNn< zEKl4Q^+KA8VYnJ=;3+JJudopY?>2w7_r}V^Ut)Fq1zrABg7%n2Qyn!ltxzM-1@-** zLOqtlZT?+Uhg_(snQQY)QTwey-Qfn*2yMsw_&Mr&Cv5)e9-jXeB<_(IgOPj99c7_T za17OfQ#coYL@n0g`%HekH3K!|>rg|!19icJsG&cJdPQHwZulH^qn-9M|JtF?e$&%o z*72x2n~u7pxmXYvVsXqub>s+Y=ucr3K0`fbl@6HWV^PN^pgNR@y52z42#;}5(TOLa zPMCqZlVw;8H==etjJmVqSO+hlPVD=+Ij#U|N=o2ptb+4#9qN2B2hH)VQ29<6h_1U- zG{mn^7b^UP=}<|mMI3=8u@82|aX1n$VF_$=$aFN>ItDdzvr*?=Wb<1vi1;X$!kfqq zyPTI)7&XV|FvF`IP>Uq-i1{;o2=*t=#@tx#OEZ+UuqbgeR7bj^UNFN^7f#1+_!0KT zpD+oVA2l6VkGb{vf0RmD8nRI%a33{SzoMq(m5qJBGT&SRQ9~bxTBKd^752ss*z1^S z--bGVCu#}~Sx=!B-xVCp^__b*(fL1S$dgcWy%05&t1ucjp`P#Ss9#L|zczOocijAd zF#uy|zl#Z2F5BGcyQoDw54EPYpr-UFy27bkqf!^WPncI`W7H6)VjNCJEtYRF6dz$O z3^-|i2`z!`h$o>I=Q(VI-lxppdM$AP@h*G^OP@AVHtRIwpPNJ$i7~hp)zi>3W=a~P zULbu?t9uCQ4qO zanw2UH9Q$Lr*lzLbqkwd-}9z}Yf&9PhMJm27fgN_s-qdGMVEzI1HRvwjs&15aioh% zQz}td1xKP6u0kEK-ntXj^CPGcx{iMM9JL5@T{L&>i~hv^sP+n|5v+;@F~-Ji(LIt~ zsQAz@1Z(0bEP$I(L$n{&)2~n$I*vu~ENX~v+57_=KS5pSFVrIQy<|F25p{fZ8`m+q zoMu$?{B}Uy`5@Ftq@qS(sdWPe5%0!TcnQ_9^#7VW8H>8pIam&tVQD;s8i}i@^WQ}+ z-iKIJllq8?7LVt*<^>auimPE|OhB#vNthoOqdJy_)o>T8BR}C#ypI#G%Vl%N|3!_| z4>(Hu;Z;n$!i{KA)&7nhJa{3XUX3@tHy!y0bw|0_TT@g3bti$Sj+Do6td6?VMAUxo zVojWgy5rqg3lCrz{)*~Qi62Xz|9<$N=nt3|TqwX-*buLWD zCt-Ku6+fCOdV!kj5;x3R2u7`?>Zr99W8*eA7=PVi90?6!Z!C<-s3A^AwNF9ycs91j zg*Lv6#fTr-*z2ZwZ1bT;rao#hreIkdg*s0rsw2y9GX6?zA)%q&Wj%}<%9E%QTtq!q zw{8A+)c&uq5c=LSQ&JLj;xN>Utq$sOZHRAS7u3{kLyi1F7ZqLTA{NCTP>;TrKlM@OU9z!V&T`*8@mB7ZV(z$K`je~G%I z^QfNQMEzv?7fLb#ZF`u6Q z7%Dn(E8LDC7J8Ch_KrOP(=!gHc-oPHj4{;H;erTrlCTem2 zfkEi|Ghbe~z7s}8JsN^K@nj6d)i@Oo;81M*i}`N14f_#?J~EH(Ow^Dcz zY2*BVn8!5)b)LqkWK-Ba94W?qmbMuSoVi%S2BpUr`elktMro?x!HirFW z9+%#z3ol1?a0eE`BN&Mnunhi-#jwl^^H@h?HR41pfgfQ7T#Fh}S2h(-Do;^E@*H&{ z=Wp|T2cb?-13O_~Ou${(9R2<=Kg)H%cElr5Q+gD2+$Gea_Ihdl`mKnKi05N~p8tzf zf=N8UN|^VR`318E>H=w)h`Vqi7W~)zl{^PK6W_tASl97zI{@{I=>pWqZ1C`K|KRv7 z)*~*Q%fo%XSd7;5pGBns4Og)*7WedU|71E2b*HDXHliS1naXJX~ z6wO5q?e`dhuTbxYig`TTKU}uQkBCR0&ie|(xxN$6hrfnw0%{88pcdU?)Kl;oYVP)7 zIs68--!s(eeu-Lao<1J#7g1jHA`U=JZ7EcHS=3`4iaI_DU22H24Nb8iaV(a`9;o&) z=!cU~C!UE~gc+C%KS6bLHR{A0QTy$%?nPbrAnGak8g<@_J{~Ujj#q8N4b+K#LG}C@ z>H>daUi=%i>b-r<35r{Tu?YDv)P);Zo1iY(64kLdY>z!q9oX#aGCkW#LI-|W#fyeA-{sUqwBW)zV%nz{wJ#AuTUNK@H0117}cR5)OjkqsHi7Vs2hJ{H{-Jdy>O2{!{TA7HCGu1{Gc_!147~HLo_}ozDTgTUl6;#T z-lIOw?wB8!>qxs*B3?&4!N$MZwi?7d!S3G<1K8(n;`!K*+-wT(E9XlLqh#1)_5Yu^ zzvEYB$MqyXqA_Q?O8p1Q07`k{m$!*k_L$#^ms8%S=>2e)d_l@_$~fW;cE56{ zS9uY-uYHLgRjJUKtk#gEbzZR!aAO6}gr>KvjXtAbKTG)M;kQRO#qloiBz?T(*dS#s^!uN3tWHXlMfmwYO573xJ%AG7PJr%U$m{Yd3_N5OYFY?(NyY?Scuv@DTCZKhLqf1>dz^kP#?pI zmQmJHZ%rWu{Fj@g>R_>Ti(G^I8W;Iex@bD77fv z)MnS8+T7cA4$j$rq%xTD0Xy!;RLbX++&b{JZ7B8bzzSqt>-X8l6 zIad$c5KeLl2UVdArMzKxT7ZMuCmJihcG6MA-;!U4<;cg`wyNanlS`y%YfrwGsk;At zM*H`aW)wD;bBce55a@IHA!RxxlsE=Unw_ZerGa{D%Ez2=Bpx7F!rj7uRwo`#z9P=T zG|W%AM`=WS%yG3z8$@Yci=SQP84mfH?=ogQ-k~8H)*Uv{uTD7 zyhFXCJ)i}9?4Ul1TwZ%jv~AmD;||!(*2~y7g)69^r@TXH&GBsJ{`q6a*-ZJF@&iTN zf0v>y zmvta{4@x#g+b;&^C*ohJpQNPO++k~9+HSe{XR1wzBu;o7=U}uw`E>UAhTIKG8FI6T z8&IMs4asSnM82`R_W%AlOSL=ud`R0J>I*TEcsTVCyPGSS#2XYZ%1X)=$|p3A$3B#& z)E7}^QCe|=8RRxm{zDu|(bkvJi8gKfX{%3}N8Ero9Ye7XKGF_s-jsgCE&2P?38XTa zlCvG9a*Pvnv5EI+8)oZm$hWimPRC;8_mV3_X+k}MTs-w~`~?fzeg8{a70NDh+8R-A zQzq*9-$f;fhQ)U0N_MAV#CfT2qg1Ev$9}=oA5-R0wE0t-+vBEL-3|P6f|8G10M^G= zw!wq?YL3rT%=Mkl1Yg_6WEvL`zo2B3^Tk$_Pbu-VRl|ICU$L0DPPSh)_wk(5E?yomE*A2rymKu!fKQRY!D+1yt2 z=0SkuWMwhl+={rNh8yS#{_pwOX)Xg zWN>uV8dYo1V#3I{l%(Ow13M;-92i+WCb;VVuc5{0v6giZ0-^bH+-EO~Me{{cI*ZcjxuGjUtuJ?Pk4NKi_KPa;>(B<%7 zRHXiIY=_arYcUhYpnn6$X@t|TAMU~otQh4uLogS6;#sVRanX)b9#gDo=s}!`{x|{G z6s(LZunz9PYIqVg(M|Nm$_*W-9M(ZEj7Ck^0zL6r zEQ_6N+}*}~8?yekTu7y(Gp4!s12)BNHok;P(Derm4fGh5ktcVBV*pmgR;Uv9Lfw~&N_Zq@;wz|$u44h- z#!k41eeH!mp(+&H+;QH+WK@FpFqrwB@+};vHXY$u9lKxz4nwNuEW}!P1mp1%refV^ z9EWr0%tUs{*@1reE9%VLMODVTrKvzL1`yXlH*Eg2pZ#x5Lm72I9g>u%7aZp$)QuUa z6^=$#CjQe+h2%@#KkxXZ=n`6s+D^-zg6vnHSxmW*0Z3Tp2MpsOm4Q8e`0%s`cXA#%{2 zy{Omh8nPJ2kNRtXuBe21pb{F4I%H!}1LvZ~n}u3PK32j))P1{A3p*Ik{&NnT&*{*> z&a>u0chr{nVlLLf#kdkRU|d@>Ks>6yGgiT~s1n~rO;nDrnG*6t&ZScegD?gAVpcmB z`$^+VI;vsY1e0k$>sVCjW}*gu)Ap~!n#6liTk#`m#dmNo{)y}dajJvul}f8sP8 ziigl0!(5ygl`;wgu_Y>zZm18;aMZ-3QF}cN2jdw`#a10n0;|xScrS+FAyfs99IPzDt#C9z;5^i`(QWh-Pv4Uj~ZYTY70KFevCSN-{1&5XXEFdGbQhf z+Uo^a7MEdTT!XycE~kV>yHZ^2VpiJadB>T`4TDgJ?kx7ks$I=W$Dj`F9Mqv&huYJ< zSRa4D7Wf3Cv3ZgiHywKtPePrQuQ8nYol7)I)8UiMM+be8E+-e8<7deEbe`a7Y(rUe z-)0<(0o~1(O-F4(0ZzgqRH8L|m@R3JdM#5>hdT``F~2i`Mkvm+7q(zM;={K8GI|nw zzF>jxxCrAgf@Q~GKh&1aM$LO#a`2rOdzlPZpfcZ&jj$rd1n zypLM3lVV<a4#jZf*& zgs1I=Uu^svYNDH{L#D5W5(q{O5N_ibYfIGY+Yz<$mr#{RM^#{vbv5eyQS73zhQ^nu z%trH)YbE1Q6TN}8FdwVq2dGMXkDA~t>hNB`K)i%HI}cEYF|?nF!>|EySFD1$=!33> zG?ZBZ#^PpFB4<#i{XFV*OycvU6@P`Q)CnAqXYdU67+@9>J&^0A_#R*|{ig<*L~f%N zbRV@v?t`B$#N||=p-ifw-rsQ4N_(Jg9D~}+iKrD9V^iFY5qJfaknc<8FjmEA;#kzy z4#eI#4x8ao{2ZTPoZf?@9O3_G^E2W#)v@IqxzcY-6RTRg*#ED zI*5AkQ}o8~Pzjzxt>`l9%-lh5^crmju7cZ$BX9}+h;QSVG3I;Vo@ripR|6V4O#QJw zPC+HG-ugcBYCHRJ8CD%@_I5k^6Mu#^@iex_d#Hq3k2B-GfcoHM;S8LEt601-X6=66Y!D76INw_S>{NiyLqljb1n?(Afwr(71 z+$s1jPQ#vh|05gR)PoaIhi(q?<~WNm1s@^j$?5e9+k!_> zU%rf29p`nNiwRhf&ucS$0rj@bM$LP4D)m=}KhxohS5ar+A=be9l&2DQ!Y~|&u{a&8 z;V!I$$5AD{h5E46$TJnIg&HRk1F$pd`2pAi=jHK0ZyM+5=zvY8@vj)1ggR6Os6D-o zdhiK`V9aagx8G!pCq9Uk@iB&CAj?<6O|dl&K#l(n_QP|ShtIgCn_rXn;R|$BoMFEC zFI(S2zfz8K7*(0?XPSSvJ8$qXack6o6RZ<^ zfvwPO{?jwza@x{R2`8dE?#3WIfGYJV)EW2(HL=eEv-gcKn7FGo1GU%FQKestk@y8R z$2+Jkih9%B*B-0t{ZFN#(oIHXxD_?w5me@ zv;8Mf6}f>GvD91UlkSHh%bXnS8}|DB#nfK|yDu@B`J-+OwML=_YJ$42tu+aUmEqOHcJzO| z%>2dF?H%()+<>Y;W!|xV7=jr%8HeDnsI%1BwZgn!>8Oe3T9={+@jBFsHsN~w5cMfd zTWS7cx*P`+cUxuNmTjoB^bP6|UPe{QtH8`t7qyTssD-*xXlU@a@h!zh+o4I_$`vN)2h&Xnops|J!5n^7inn1KTwD4F3v;u zHKuVf5`3avvO!fhCchp`ft*#2v%_x}+_W1Y3;z89TW1ogfZ@bJs2{rts1o-_-Isw%I14lJb<{k!v4HuV2Q)h2 zJ4NPa!7o^oxEUM%9`;5h_%8;d|GVbLYz*oU^}q-mgN<-0*1}^LkJm938*DJYh|I@^ z#Cy@@N8>6D9hyg|(gbWY6{w8?#F41iGT!#LMD~e;De%O!UAU)Rs*_z4vdR z=38R>H*DnnPo!ff9h2}rYDL*BTLUaaC9o71;bzofjo)PYpR*1`m3$nQ!>OnVXQ4{} zHmY(P@I^d=T4;^U)L%D5Z8n*FDzOsOb3dWB(b^8CtAdK2({$$z-bBGI3AC^k* zn?z%+iKvzLM~yqg_UEDw^#asZY(WxtIbYD&OUDnmR5xrlha_x=`8B)+4yAuFy5mDs zDNF4%?{!5~B6YD0#$x~`p!T{C4#xLUZ-MVFlRy@_>*xOh8X;U*jH3@>34CLJ(`a|P8&<;?4^n^a zY4$<${!c=^Ubj(O;(f@xmQmmh)OKss98xz z)IwZafO zRKf=6kFn^D38?GcP#O2fWK6U14%8{$XX9@%hWJNRWjs%q1mZA+xE*SoR8%6vPf&k# zWO8z!#1*_2yx7q%WQ1>50Uo1f%yo?(74(cqG`N6!dUwDes^v^bmFbzo^8^mY9irQHfSYRV)mZa3fTr9k3~O!)%;^ zqtJDqMi`BuCr#$_Q7c-B%5)3rPp11(AD*M9jQ_;|;`^vXe10?uS3<2k4z=gaQHQV{ zDzT2(246ttbvX-Zbfset4n?=qW`L2X2gX@nMI|=dUSEzXbs=hn+fbF-hkEWK)EW93 zmEcdP1zo^Oc=PFX_TS?tGjITI<3e3rf1* z96-DWmto*pv$b2$pZH_chpYtKGrx0(hB9h#&J3K4Rfxyn44jFh(fhplx7}nMLVO#e zvG0FP#b%;bJP%cgcd#ZFVmKbcV!VP$xcC?9--*Tr8c`T|!6cG`+Pl%Hy_|^e;;Yyb z>;B51n2T>=wTtF2rkhds=U*~={thZGuyGM462FJ4#EnbrzfNiC%jR_mMD1w<^uU%j zZikV?-O&@L*!XqS;o6FNZl}HeG3qTjhVFR6dKy)^^Y(g&D=t&Aq$}oB_d^|$Y*dER zP+PIsT8x3jpV;^`1`+>-D!un_=DBL_B`H>tgVq=1{doZRrBkbA=d!-(n`-#CUw^ z#?vi#Ijd=e(y<$r@hNPLPf!!Ix@rD|l81T3XRsFz_{;pobUVfnyWKK2M*YR~WmIKy zZkvC$uf=DG&tX5TdPo1NX8*HkXu=KnGG4^)*y*lW=`tKl{3kBQ$Y zU*89zO8YL>!(*uL!%b9$g6^5Wm_CcWbshBwZYT7!^?k`Y>?HlyMQ|N{lP+NP&UcZKVt#6~Ae~d0Y=<&c@sDKrTt73JGKwVEn4V;7; zxG(zPKrD?TQHhR04Llxo-&E`CsEKExH!ebryZQn3*Nq$Og<{k|dr_GmMosVumdB&$ ziziS6T(n+C9o9Rj@yb3l`k*HCMe zUW9sXlfAyndca=)1eN$PRKj1Q7H|%g&{fnp|7c=m@)(t&`y&%qK*g0&r4B(Q7LCtf z9Ja@isKmF~>$^}}_#f0npQ92j!D@IOgYZ5o0axH-(+EP9J{0vpIBMWHYcqSj6)NEb zRDxaX^`6$gsBs3O?i*s`QRsx$%MQ)S3{6c-OV7>@osgcDonH{&aOa8^Jx2QVif*5k zo|-c}b4+xH)SUE|q4~eBOD+vDMUC_GifWe`pEERneNnPs(sN0P(S3N9TU&<4#Wc*n zRTNQb)Xw1bnWcKBr)H&Pj7`nSh;G<2G{&oMbob2kA;WW`|L;P2ZrZ5vL(+%nhRB!^ t+5hjJ%<)-i>1i3MS=s41Ekk?v>KNU^%L&b&AK#7?{Y&lavT-b$Me#1?N8c#(pc0ssI2wJh17^kq z)B}?+BM!z)_)(PGBqrFzG_1{udDs{)qB>ZlmgB@?1V-aH?2V^!0Y=q!90t<4goW@L z2H|Ve0|OaGKMcXi7=g;~cT-VMj-iI=G-k(N&>x>*ZcJa-z%9`)dB7=i9vHc_yi>0vR{jVhr!6on=5J(=dxo|mZu6JS=%-P!Pf@D+&&Yrd`==?i44t;TAQE$2e z^Wi?sjh8S0pI|A>!Z=sK2n@yksQWI&R@(oYsN^N#-O+sM3ZfTree8jakagy4#7KON z`tU?_VoPBPs@)A7fbVrSQ??5=1!r*>UPg5^j``A*^v5hb-81@{Z zM0^U>^QWk(>D$fZ7o$452f5Tai&_Jb38o|UF&**ySQoosSzLzR_#>+QnFJoI%5@Uz z`6JX2Wlc1TD+IL&!%(X?619D5+4C(h3voyE!=5%Cit6B4)OMYZmGBGHV!VJFq1%bP zwtD&p2|ef;YH_?mJurKBlh22WgHR7Dfm&pds1CHm4A|MmiPnLbpZtfYH=m0diG`>a zKkBxXb6AMPO%2O zxD%Ekc8{T=)xQz-;W>ip*;y=)H&7kP*~_fRwdYh5SjHzmemvJKMMH2dO z-bLHKX1iu%%cvuzkr#41VN^1asDYWW0s3NdEQOs>Z#ogxZUt&CH=u^}CPv^LEQy8s zn+`QX?TT0o#qOx7orQ6@8Y^qr|3~F}5)nyed;Eub!!R~;L3+Ls`9wG;2AU}Y_T(7qwkSqOMzr>d0~%Z$*vlF6&{`NdAm&-QW@xZL53s#4FSV>6y^%m>qqw zFzUwTQF9oD+OGAHueZ|))owd#=ntSCbP4_OCTiFGg9R|xV8&mIsQh5F$SR{QsD^rz zdZ-6AMRl|VvS^%mREPVaI_ky<{1h21XD_neoJvE?$Sy^7{Cm_3okw-_&Je~w3v>LO zgxdUnt@IE(7>Wz-wqLygok)P=85+ceV%)4|-RHwwhuSO#^z4(h%!xDmVHZ1fw+ zuW9IBK_!GrjStO!?}}PXGf|6d3#tQGthccv@l%|Kv1}F1?H$aCudxtjXJqSPc~pl6 zq3-($=EXHQPWyi!m3|}|jWWO6ZAX3#IORs0eVc|F@_nc`I)WOBGpGk$#uE4v*I|*5 z&5!FJu@Q0L81u4D0v?k^ujzvvrcg%pPig~^>l1gcugc)&%jlV-(aM$MlLG2D_ zqIs~7)ekjdxiJ$CM~&1N)MB2FS{rLn9s3G31;@~>$_*--!7<%weH7I zJdJ_qnquZI2#XU(V0r9_`EfE9#g(X$JcjA;8D_#4sQY+LHQPEEb$z9&jDHI%2_)ih z2iC`|(;TN5w!ml{jQZdl#PWC!gD}H%^RM3G7)3k>HP>e`7$2ZI?8`!`ft67AABY`r zhnvbMDmiAFe;&`omc+NQ99By+_QD)4wj73%|8kc3b^QWXCH7}i>xMB{5!av|con;1 z0Gquzeu!~+61$+g*j#=+qB0hnVi23UB=*5ZI14qjS1<&f&&+qBIBJBNBh&4qqHe64 zm4Y^y14m&&oQYa%8&Ok!1gmTRU!hW&M7{-P2xC#(YAOccEYwhM#N7BJ>cPKbe$2ek ze42|{Yoq46BWmb}qt?VyjKmX|2faU6JLbOtm3$PDw93*JO^=rLx-S2oVL)O64vHG+XQjzC}HsHKd*ZqSTGRcwd#a3<=F&!HZ8 z8TEks=!5^D=H6?W>2N{R3q)d0tckjBbIgVvQP=gcrrPuFWo~ohNhH*xdA7ky>n7Xq z8&n4lTTkPybdK{Yf^%f0>F{xEOZ*dF!;-6*f6TbrjKnnTKs*lz;Gb?Py{IItF{||? zYUrP#hPcF+=D}4^Q_%pmD5G%&c0-Mv&sxXXi}kP%dapBUY5;0YO~<0R3blBTV+gwM zP{~Xs+j{fB{HRr08Z%%G)KJ#LtQc$a9WfJeH*AA_QEzqt3t*uQW+bX%H1SOAjlUv0 z*J--ZGjeX{HWl6IzV$EEgI}ZG)O(ZplW72|-73^bY(jN>C;H(5)Pql>*2ZJZjy{{s z-va}%JaJRhb{~P6wEt&O(SH9Nb%QN7K8bqso2WN`jCJr8>cLT4%;IZ;>R1A*eLt*- z!%*9BJ?j3)Q62r&#!oPr=R4`Qni~wo<;0^g4vTMdoDXm$YOb#0SD0?Q8JS&Jg7{}F zjxSIT4BlZr;ZYbx+yiy}N*kZRp2S(WnY$8|6e^8y4d%r^P($Ri)9m{qs5h>R8qzwb z?b#AFGHp@UC7{}kM2*A*)KsQf=VLbFrRa~Fb~65&t3xDo<6p2TKEYvF<7@Lzr5> zzJYo0KF&q2Z_EwnV{YO#);*}HJc~Z~2m0c_=vfoH%+v+!V*H15B7%fYY{Fc)5A|ke zQFC?!)8kXr8~uZN<2P6g^X@hssex6An__hwXWfVD$ZOQ%&G@b9ke{219vp~z!*ZAd zBT*e_g6er2^b9eoBO~qkk1>*Xn#~_UO~DVS7rB6%>j$_IaSC2X<&*ZA2hT)}+)C7acA%zc4+i2{4A#&8 zKW)N)zv+26UZp`zWSX4<2h50^#bLy^F%{zvng^Z7!o>Gci^}(q`SKOPbi{Qr7#pHG z+ylGdUUX~!2OlbseT?{qG z63_VF3RdG7++g$J zr|d74n2r1>)FPUOT2$*X7!P26{0-~jf2a=DJ#D5a2{i&kP~VYRrx}0UXf27Xcod7{ zIb@oh*H{`4p5b?Qe2lSJ?5z2rl!7&hUFXcu*TPxET`(N~LOrm=d9!`%V-j&k)bmcb zspO$@5%uOz@B@5~5!m*E>G>2?yVvLz z%ba&RL#XJD+^B}rF#wmKw$m=u9PUT`P%zMprXaVZFRhY{2o$XZUoKxl(^ClU8WhmLtYf#&A^L6v%_8e-?FJn%8gIW_g zZ*85@e;;hz2ErA z;7qKEgMR1VkGL5txfrcm=9k(&x6KQjMUCW3)css{%n0PW!}x2?i<8hIs*Kte?_oM@ zivjq)%@0P+=@=VN!;ZuYF(bY}y@2bki371BaR_P+v`4MAiKup;-DUjMv+X4E;%>}@ zXD}OHMeT+M=!1Wwc7yAld6PV-d?>1YBm55pkCkfvZ7X}g^d1R*OcU0^=HXSO2>UbH{2uEWUY>irk zADGzfBvOecF%b3OJ=h#?;cSe2Vs7vQYNXC!A-sb{G2PafgrgQ&9h+~5dToX958!3Ih>LOiQ}gS2+@I$A@DeML5B|%%X&hD{9)g;R zHRy}mus`m{d063@d68q1=R4P^1mRPxgE^m@p>K`Fh`XZ}=Op|LzrcQ2^KUa{Yq1Y; zv470AosJsfeW*7+j9PrBu^?W=lK33m>!<|1Fn^^!f%S;@&v{KeGkwR!M8)b&qLi})p~gL(dA{M%3|@SjaxA6S__mVJyz$@3MNsJE-fk=X80tS4-5;k3fCc=3`;pha2%Gw&(dyS}rqp z4^bbWLjEq#Y7a%taTUyf%`phuqHZ_@eQ*(Kb+5MZX6r8WCVvRi;W3*(X}y4MeHw4r z#3SoJsMYP2+dL>QYE49;9@r3dcQ4jteb)&1O8{9-a_&#cTJwpw-S00luZLNe_bWx}WwzRfIUDpwHzh2k_ z2j+2`#6{cSI%+5%p@!^l)Q$c{UFhUBaVAs;eNi3DW%Gfk>x-bKtPE;o!ciTIKuvKI z)Cjb5Q&Ge2)_$lPq*y<+jz@jTrlC5v1e@Y=?2J!P_leDC7F`?E8>gZ=_%Z5(G#NEz zD^VXd_W>%}m*-G7c#7)43mbdoH*fBPnxY)2o(I}|8EZw<_0>^xU)LIK&$mN%qccUr z!7-ZPg7&|T?`6EF~^x6JOYC{w7{b8Bd9( z+#$~=!8uCN51TJ-yP~KcZmePFSJt2BsAeB9(>leZJ-?c%8cbfp+8(dkbNbxAd;CK1 zsZG?k_fZ&O>#2GmaWBei>MR+@t;M5b1|@)onJAqoAKQ-UTr zl%AZcYx5^@Kei1OIq!J{e%N`Ay9UpH zL-_9t^6W?_kG(h&^>hC1QHyi>a$d$csGo2;Hc{?T>e2os=T<7oF2PsakpT>Z;JW2cr-#yw9CzGg*LA28`o%(&;i{a!>d9wWSNvz{N+fMb~ z`VU7M=l;Tno{0Bi5Xi6qBRMpFL+FH-m|+4JSQL9Ph(ZroI#&Kks{C?$#YQ|(8J z{tS1HGJ<+-@);@b9$ARTQYuqA=$u`D|7)|Ei{BpCsr02xpy4hYK>3!EK^MMrBvJ23 zuDreW6^?(W%_7c!^Ui(WKJyGYcV~N|42|Y1WrbCD?kY9yG z$hWrV!pPMm*O8(lmVBhCdj9*2^H(ShC>(C*2mafipgHOulPIN#YhXdsh?@D=?t3;K zjJwGd@SNcvp~Qo@xHwM5fyi%d&Rt3^;={JxPV1leIphtEF(igj>Zk`CACh}} z%(JR4uQwxfP`yz_Q0l&?a= zMU?-je}XqD!IYZZ z9qwi%b<{U_{u|B7isav5JY^vDmiB^1wAe;{Fgag)O@uwS&c-e9eOnK*=M*lXeu|Pz zX=0vh-v1>h*Ha!*u2FQ1edof{)LWUf=YKNx;NmFC@8m;lyEXQFG4dzKZLs-HHXlbk zpYl29KfUeiu@rtz z{tHSxB{Mk}#ZCSpmZ8k0%ppEtuXpz&@Uwx#$ri+uC|#((JqA&)K+pk)+Wc?C3uxDf z5=8xH@&hPKs9&ZWryfA5M%^E0QQjUuQV%3}qy68R8~kNYe)YD6^(lEj&VNAMg8Es? zM#_FlF7i4aP-alSN3I{mi}IXY47R6yqedLTp8xV^8FIy`PeeW6sY+#`J=vD}_mskv zmgI_abcrD3>TpIjN3yr97iPpE8Zom>WzXw~lgzxI9Hi0;MJAbnN0>O-dSZ6!8!& zh4J`A4LE!#iNuZh_otJO${5Pq;~>#J3HsyE9$D02gRJw8EGuybNZIn#xOMMfi0`;u4D?yK%pMj(6LzuoNdaEk3Ny)}+!&g}2^l8dH92#E3iji-opK9NcSAucZE= z_2W|#s|AOLg;xv>s}x$HYH)=r)hbpf6Bbc1EpB%0v=y`47j72czej3(51J(o>e8T9 z=IAET4MN)_ChPiY!4=AdrM;eAG+kWlhG|9Tq^5m2r-ZB7)>CtmT<)aQ2kdXWp?z!=6yp31rLZ%jt`DctFky|>-~A{Leg$Dt+sXa OcOO*Rdh2P!eE$b3@(r>8 delta 11138 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 By|VxS diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo index 181dea5d15dbba99a7a2aae4d5d6f1d4ea6114d7..bc2889ccc8b8a88ef020b5aae1df2e67b93323a6 100644 GIT binary patch delta 12452 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 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 85f5554f38a73c65c2e8410d4264fc2192555cf3..5b4b235504b3b7a094a397e1e0576b39e3311479 100644 GIT binary patch delta 11501 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 E3wTnXWB>pF diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index 822f8fa867ae8736740887cec20dabab99d7387f..bd75896c53fe9564516efa969b987b18995e694a 100644 GIT binary patch delta 9884 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 diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index c841956b9ad7e08058aca7ed2ce24012d41ee382..66469075a6e3bf522002f346610846ff038ddb4f 100644 GIT binary patch delta 13896 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 delta 15914 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 diff --git a/freemius/languages/freemius-ja.mo b/freemius/languages/freemius-ja.mo index 687eb1ab53078d1d61eb42e781db4c0de28b6a9c..6acf2df5e215c77d81ace8ff9df9039037fff573 100644 GIT binary patch delta 11980 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 delta 23037 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 delta 21077 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 diff --git a/freemius/languages/freemius-ru_RU.mo b/freemius/languages/freemius-ru_RU.mo index 4046fa530828a6bf59120fd432d795a6ef82d144..c4982fd53ce6acd0b8ef2abfa7568e98c06167b5 100644 GIT binary patch delta 11030 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 ZKV(0^-u#lentg-1^~%e(_ph?1{{j`2me>FQ delta 26057 zcmb`P2YejG*@ssy;D!M=+`Sf-+$6c;ZsQ_~a1-tjz?^j}>1^pvzB@@45DwD~4s8tv z4A^uC%^(~wU?3kPAtWR+1VTv&q2>!E)DR%R_xxw(BpFP;kYDy^?%A2$o$}5*@4WNw zici08bKe(jGcR{*cd5s5V;j%w4~u&$<#|;jS~!c-YSP!iyI>qH8tHjs;ZNbQ@MBmB z7Z!M46y6UP!vTe!mj~Cv&TzBi^>820%XoKj(v1s0fj7cm!U-@@t71Ib2A3HJlG0fU98##IXgYU|YEVIL{jnyTgHSI#dUagKEGjaDVFe z&g5hx+~h6{9KT(}ELNne4(;Fl0-d-;<+ zZ!kOt&VVPw2z&r)2>Va*JVwzw5*`Q}U@v(76yz__Tt|i^a5wA@cfxk?DVP5=RD)iG z8k%=q{@P(6Dcsz>ia zjs1tPH*AAk)sTKrqR)pYzPAu+ey@iJ!Mhpi`B$MD^cGZuK7o?(K8MtL`)zD2D zPBa#Gz|Qa~D6M`0st4~tAGSH#<{u36NKb&uKMw8-e+c`-v!E>7b#MUO2_@Oz!+iK> zcq#0#$WGtPHJlWY(F8S?UqgiAEkrC#0PnX@4Lop(ZTT2Dmh^2<`7c8?^eZT3>#@|1 zeH~Oo&VW+VD)?U#RCoO% zC<$(X`@@@E`aal`^kYy4?gcm=z6!gls|PK&BFcwq>1e2m#zQH|RHzCUy8PuXeF9WP zTwV?Po~Qb$5nuoy~-S3v2^51^EAy-S}1Gb72kgOh{c3vfU90aQc2hNEB? zR9+332BqaQp{C)TP(3ZgYDq%lp&C3*=^S(e_9Ok~akk<%$J>T?fEtP($0L8WcmNry zco-ZGCqOCXiI8FPE`;UqO?V2Ne}e7tlTZb{1vRGcL-jmwr8VUv;4soBLN#y$>;*4| zh47Y@89T<$k%2;be}xm^%o9ED30M!uC>^mqPQw25>;p*Wysl+-s8&NsVgr;;Tm&`k zw!yCO$50K~2}i(JVJ_S+6SWnLh8l`hP&Zrv^Wjd2P4QlV>cObM>cB*(^n55uErpWY znNSV56Apq8L*4f>R72i$=}({}pZU_AbU?lmWp}6o`a?~x0+&An>VYHR0dNu2kd;9d zycV{Hr@`LvY=~ZaTcPgz6iW2%D{MXeVGqs!JWl#@VG5M8EQR~PwXiK*2i34s;J)xI zsEU3B)zA=XHe3(Y;Jct2`crra{54F$HkGUsuny+K*PxmIonp2}eV|%80(NGMC&6x{ zi&xtoEqAPevI`sFzVK?OhTjO)u%E(?@E5Qh+zr*h=iqesDpUjfH8?rwpLYZ&NXOd* zRlpapGt8m6;(kyf?*nyx1eC}}!%px}D2W{fb^p<@3p^gGfvcf9mV~k&r@=1pGMG`p z*K=|yycb>ukE*r?3!o?nI^*Fq?bcgyxwsmWQu#6p(^+os^U%wc2lq$WURg0 zpd|Vz90;FDF#dBm`H&1fIHAT?G!M!kRl&31nUJ=4oz_}kZyjVpcptz*xGZTUb0$>J z&V!Q3W~hp;fkWXgcsqOp&WD$zkpB^!yn==lz+q|Ikfl Tn(weTi*GF%LEYdvop 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 diff --git a/freemius/languages/freemius-ta.mo b/freemius/languages/freemius-ta.mo index ae1d263713b2fb1baf0ffc37a6220a208d5c543a..0fbe1eb42c0db62c20d833cafe9dd4c04cc27960 100644 GIT binary patch delta 13025 zcmZwN2Ur!y+Q9K0s$# z#+IlzmZ;I7#@=%iV>B_}|2?zb&*yu-eeU?1cXrP1?6k9-+%K29ue$DLyIIUF+u$he zVi;xdiML^#Bfr0blp4nC8io;re_|Ro2r-Og+>C>;c&K4?!Z8?xXR!eOYWWU5h`nkW zMlmdkwXqU@Z5TGA7nyz(c!wEAMa)1y{2U8n4(7*w=!GXxE_fb2@e1a{pRM?T6~Dk} z%KyZcSU+4hU502GIAmmi(xFv6Lv*;!ai6FldO0dO2gKo^o#?g zK?kr3p2AZ25~T;dqIA8IC=ITF=@@}FxzQRjD{vFGz?R%idSpIs!fhxG>%qX4!Bh;y z=_qq%3-WNrNt9{x789{t9mAma4I384?dXd~u`ph(L;uSYKA@mDzCbs0tE-v5qJg>VfhADJTujLLZ!jQqPX^04q@%unDDp4oc73_K+Dv z<}7}WaSio_uAzMJK93UMOJ)xRv1peH*bB3p&^`DP-LZSL9)iB;N}P()^%*FW?KDcg8thj77=h9Q2`EF? z(=r)(R$~;p$@u#(JJT#wQK|CYK7>!Qr!?kJOG5Xu;jw&FP$ zK)eb)@d(PCIfXKrf51?5Z>6`~D3k}DigdE!)|yam+`P5E;bD{;`47!Rpe0wpeES;a=ikAg^)Cu)ZU zF~PDQ%8gP{8kT_-@KY>{8_^3-pnU#3${e_aGPFNp5Z=V<=+<7(nJ_FwY->qIZrBxT z;y{!enm8QoD6`eQgMNakC{H>IWgVL+H`<2sWV=v$?g&c5PNUrD5>~@IC=D+Z@2qDt zg2_l1N1{AIGSQm&Zj6!N(}8!LK9B2t)8BPQh?&o1h#1F_u;AD=7O) z>CSp18igJ*9ka<4;ly&3vE7P=@f(x|oWNjwhJonE-Q)%hQMxn@<@{2tj30-}G~`z+R^4=$=C>??($)SbpRbBVv949# z8f6GNp*QwL8H#k2>rO=H`kzfkmdirqf5sO6k&3R}b(a@KxlvV=Ssjito7m8KvdVP#W|Pmcl|k zwUtrkKogW3cS4zzy-+6QXp|wHh%&c6L%IHJWMvwTp7j4Z`ZlKrByO3uCb}%8eJJ%$*#ZhTr2LIp0sOmOUsBcpv2fo}vt)?G2goWK@6s zB@%$`h+AR{T!hkvH&HI|!15VNm;PqOe_HWB7)QAq`?q{&v_={GKIn_-C`0=x(g2&0 zZ51p>Sw>q>y7saa-$Wk9$Ui`rC!;hZ!*UeLlTJXHq_@Da*f`NN962kHiuL%Dt! zrpWrQOGak>8VtlY*c8ig)!Nt}WeDsjW4#q+Z1-Xap22eXC(2NiOwyAq1pSCRqdZs| zy5bU)hJJySxWBQ1jCA=Kl&xDe?r;~d6g#8ADpPC=%LaTS+hyyogmX zAXU$mR#=gE0S4hNtcKUI61or9*9%4||71A*FHgLgg0i?DrK_)E1AK-u1i@+g$rI6? zcomk$4JbWy1f}b5q1@;Xl&&vJx0S)#mI)|Bm4=mZb~^npW8-Tup}Ns*@&*AJjipD>lXY-e^=B&X-Eo6y=f>twZvwfIDkbdxQa#a zF&4nLC{O4)LN}}mN&^~WLF|P2us^oP6qMz>8|6u_p!C%5DA)DQ)RQ;B(iToeDm2A# z?1P>-4~wH6<%3($8~33!>?}$Henc<4YsD{6dgO1EdOlftqpE;z#7Wo@ha%Ut8T-k| zh0bDryo%DGJ1ArQ4vV4NNPWSQSeUpXO8p4ShSvG!DA!BCLfFqbKiqOOO2Z~$L0SKE zoEc^|?qz{~iEF7aU@Tua=rNA%R^fOi7dD)ryE<{AUIp8+3ibX%>H10^>$wq&b%}f6 zDqM(Bd_IzU?k7(EgwK;Vf+p!-C_cjK#CuUD&pnL9qLcMr9fvZz+hPcQgfcWMQ6}j& z9D^s3HX1Qg^xu#N@oVBg(G_#1>N&F$Z8CZGk&!Mwj-hx3<$@m5^bP${p0F9#$5hKN zu`BUyjKX@K@?MB(xEbGIEnNSZ-gvH~G~8#pe$d$I^nVNm=@b;l9VqMe1fIp8Q5v>& zhF%4SP{#aclmh!Y2@g_U;dB0|!90}fRLRyo z*AHcXS&D(U!$wA)@FL1s-p6441H&+IkzS6S(1*AeI>#QX60bz*!ILN#^s?(A3_-bG zZ@z^+v6f$_K< z%b;4zMB{_yFp#+BI(dO$qrqXgAIoFa^@b6REwCR>$9DJtWjWX0z>ssDp_pVNGx1B- zCMWJ})ZW;nFLY(Ip2e?F=1A2o{4IbD(3j7JZq-j1_Z6>GlqX;%yoz$8cPMitB1ig} zxr1(a9X&8l%6qUQ+$SR~kJ+hP-XEpwSEF>{WemYPD6_n%Lk~%xuk{?sL>cpJQxoDn|4XdkM7pL<+R$vD>Cs~oQffP^)DAY@fdOGefmA% zCJrPnw_gw0B;=mPES!wd2Y4tvhSIZJ4(g#;e@Hjf{V?C6G^Frh`bEaL;t^i2Fbid@ zUtlyAJgO&GI}9LRj16%QO1)Pov%DCsTa0Cqd1&lH?rC&BZWtpKo7)L`lX%9zm|Miv zzt!I%lWiyUyVwOhOobY!^d9{bDKcK-a@>1bujg)O^w12!K+2cn7(9eMugGpEz^L?+Muq3(?S3sFOK`0llfwDZCU}@}v zp_qYEZzakNH=rNx#0Gd4X`s!>cTP{TQdp7`K`0k&ge|ZWeuT>~0SleiJ7j;9db6+y zu12}R4qS$3a0d>#pl|5+Z@tXBU@+wqajdM1jb!A>0)JpGFgBG@CeP@Lx-0u#(mR|D z2T{Hri=o$Ly`hxD3dFrI6enUK+=}aPFLuO!SM+k;ffI=DppUG}{y*{yHIBl;_z>j= z@mF=r2jVQ^l_)o;eoe3Eo;ZO9U&C#bcl(KNQew{=^ppz^aFcPS{B)lF`R&v#J;Z*u z^_Now+GG}wB@>0&D6{!A%3QdJ(lbx7623&K?|VmY#StjweQ_>MLFtKNzv!RoqR^8# z5f@_$&PSKKx+l!L^uLVl2@3qN&OP0w-B5ZU8@+HN%2+$F5}rcY%b#I)tigE6?4E{F zKL_Q3_E_c5Fr2vL1N{K4a6EC+1Nz^OF1tlRa|+%*)HZ#jC(8_sp?n8QgZ_iA=DDvnjKE(T(MtbtR|3%8@}3;U2!Gu zA?bq72BS1|E)J3Pe}K#x3W8WG+wdjI7_RvA@0T~Pi5Gql6gvI4vxsDjPKFNube*+oWs(D z!CuNa>Edu0aSHawOIR6e7f{Zh@%msr;tMzz3lvn&{bVT?CSHfr(Sg^nVIkhXoa?f% zGPYCD3S;pN%KLibBFg#Wa|eRflz)#hN&R>aqtlEb_#1Hw?^BhD`xaNmXyR#nxzEGD zaW>BJQAQq?=Cw)Y&|8%Czn~=VUvi;BrIhpUfFf)LQt=A1{TsVWD`N)d=af;#cf=*j z=_kL7=ZKq^SI*_<>#v+IvHfrq<>yhJv_*h2{v|PXLVE?}oP3WlA8~10pmJ^;6|oKl zZBU+Y3QC0wD3k0OR=}qiiA5^v@>p~w?u!1{6J>UfL)nlvVK82>{0o~ASK&QuAlg#N zlp%8-WqCY7`3`UiQqKQOUJzyd_qI$$x$p#(`s*zBqI~W=$||^q((u1gCbKu=Bdf;; z<@13^!)-=uGAGvkp_GNz)ejulmvbI;F3=Yyd1 zj0-ROGW$Ky4}(w|&;&hY{SP4{Elo%H;AE5rEylXI41@3@#^D>3Cy5T$4Qz+uK0;EdE0$|6e1%JDBF4~(slCdNS-9w(pPYQqYEcG6TCk@C$pQfIiz0X zzrjC93#|$p@jl62%FzQqJY-JDpI7e>`9HR-A_yRjqr55>Aj$EWG%tmWY(lR%xmZd$ zDr$^>KDf|dt9m%Ar)@Nk-4I*_V@g-<%rVig>&M!4ET&>Sae!Fy! z9DJEM|Fk;pjM!+XRG<9sR$PpmPNb|I>1)blTrZGM#~3_@FR>9x4z>yBaRhU8)`%rv zm;4axoD|z?5{xEgTa|>l#Jnds|NH)0t2~*qPNW?q`CIM@<&#L4$^SxXL%tx5l%p-F z0cCPjB6V@*_^+i{kaF3|=aYVt@vlO`*QCc3bj3aB%Z2_!ejRBq=`88PBZs&xNxmZ% zSSO|-yR`Fg@b4NcF9m#~I}g4toIhYo(32$p{mHrM+~S0668W*WoV5-`>|#BY6lD;1 zBRwa{;Y-?RbjVE7%>(-U^mq#Ek42MW^45LgA=i0>c zq&t+Ix60+*cUJxv^8Y54l17r)^!|H%Os+riX53AhZ=K7=VOA~M04r02LN+$%)*C^Y zowSs&-9*0Wk{lo5V$uSVyDqXBLkS*|XdDjisYZ-aeRQYNMDhc!-Jb_(pl%fpYm@!=Z=yR$zQVSeSvlo z@1w@g80D-@kI8vi|1%`>e~ynS^P)^%ujE)o{3AZY2^fX)qH!Gykv<{G-+pnVk~BucK&;`xAGPKTk^k|U1WW@$)peJ&!Mawu?wlW zRi2MJeaT<4K6=k8?}fZ{%b$KWBZ45;s_@p*+j@c!;)R@VW_@rQ@hL06)>2WoHTiJT zY?3Rny#4-TU26no0VFwYk^DF>moG#5Ret~blFUxhek$=A=zQaSMjUN@=o9jC+#ofz z%8HWTMZPeOBi$x{pR|Mgr#KMbAI->|qwGi0S@MhUSL@_dS>sIzI*~e9h07>wVdbk? zzUL|DJZb*L_XQY48LtjT7HK<4j*d7FBe4MWx8gw3Ny=*BZCp;W{YG$xREwYwsUG?F zM<4RV`S3SZK`PcE4JWx#-huP^agBBUXUh-y*9X^o$ca(J6-i@sCHmhwR^ni*@&f>gC0S)-DCIqDC1sCE1u0)a+DTpxAJSpTSVs+9&$+jxo8%jk zj*+*0X?@^(3MP~GlU|YJD8adRr23?e#OIu)hVw6)E9Cu2uSpX)f5AGJOa2h4F>$a} zCd?pprhF0BBl(iwAm9H@3491fqXXs0!zNb#gf4RanbnZ8N~D>j!IY=qX;Lw(P6AdY zzHG(9DN-n9+eq_BFGxj5#YsW(=J}gd*~78`Czg?qCCwsll3tVKIDyNp{8D^l<>lN( z^0g%sV6F~}F}DS~o877pb#$mPQ8`A0c2MT)nhnjKVYAI*;Tv6J;#-*wBYQe3Mh$g! z+^*xV%;0(n=DvCZ%vSXeo8Ao~94-xiROY=#P0X;yd(GF42RjxuX|2qWF_Gqgrg@I; zu?v*>w%KKuv_YnCbDQ~l^Co6|i^h&6EjlT)VJn9tsC8jwrnQ;nDAJaTFKU-y#KC`e2H)jlOZ&ny~&n%u= z-jR^HM43g??wVhvA2X+Cd}{vw(XZy`nc0O?h9_laB@WLpI;Ui%COPJh{zSRZ_TaG_ z%#?8t%%lm+%*GQ>n2{e>G{=2h#qpDEtuj|ls%XYd?%{YfC7*JP`*eVtIdN8wBX;&k zH}mrR=jQnZ`>JP*pc@8e1&q?Ice9?t$V?iTo;E1c4auhs3K*VbCiV(5qs@7ahh}wE zDI_6jREj)sNVCMOr1}97VG)rbVNoIB^#a1{){hLY9#$*TY@2N_*fue3$jHPYRLM#- zZ)ewX?b5lqS$a`Lv-P3`)zIYCEq^?)@kQPa4Nh1=o z(nr(}h)+)%l#&$?85$89!5JNfbV^DbF)%qkF)KMFtbRb~|92v8%)r!|HG-oau>u_H>+tsn~3)96OT}zcQ zo30w?II!w7<%nA|Tsi0Lp0!=fs_Tk60@hXXa>V7lb}>&J@ijZ|nC-Z=qpfm;I^tbS z+phhNM&IPPnNRjMw;xohlsW&HyCZ(zR%QRWobq${9th=%2dlc8X-AHm+m^eVlXv?$ zIv;K4;#hOMm#g{uWIIQjldn~=yqui8ZIkkHR_5g#%FEeB-t2z*H%GfOC6$?U_NiI^ zSt-Y`@BVN#CtoOQ&VT9dn0le8i(}CbU%5D%U9RThNc*v=tGVO)E|0vNd3iaT%$z%= z?cPz!-JJbXU-Qzj;?BU~cH@AXdGuBV^Osva^0&$i&IrZ2yMMe*|HnaQ#`BtH+vjT>&!7M1=D7RYUgZ&=I5s{p)l@gi+OK%3 zGLF1gja?o7Z>|)vFDtLg+kL!LC3}4@6=%kuaL=9QrCi+XGYYHUb3ZAf;$7?~+o%fm znr&1;dy^35X?HKCl5;bPsnxEz#eCIfm)t|8l&_2ZNJ~}D+FT delta 20066 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 bvXAHCl?vXTfUDPiJWp?gdwUAqO7i|c=Q03g delta 15875 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 n8R?_1%*jZP+q1ulN@)MPE}7{udq1d>?)- Date: Wed, 26 Apr 2023 22:30:47 +1000 Subject: [PATCH 290/377] inline script/style fix --- CHANGELOG.md | 2 +- includes/blocks.php | 9 ++- includes/master-schedule.php | 15 ++-- includes/post-types-admin.php | 35 +++++--- includes/shortcodes.php | 18 +++-- player/compat.php | 5 +- player/radio-player.php | 23 ++++-- radio-station-admin.php | 6 +- radio-station.php | 127 +++++++++++++++++++++++++++++- readme.txt | 36 ++++----- templates/single-show-content.php | 3 +- 11 files changed, 220 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26fb58a..9f4d69a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.5.5) +* Updated: Freemius SDK (2.5.7) * Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) diff --git a/includes/blocks.php b/includes/blocks.php index 0612e0b..9fd8710 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -328,7 +328,8 @@ function radio_station_block_editor_assets() { $version = filemtime( $script_path ); wp_enqueue_script( 'radio-station-admin', $script_url, $deps, $version, true ); $js = radio_station_localization_script(); - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); // --- block editor support for conditional loading --- $script_url = plugins_url( '/blocks/editor.js', RADIO_STATION_FILE ); @@ -362,7 +363,8 @@ function radio_station_block_editor_assets() { // --- add style fixes inline --- $css = implode( "\n", $css ); $css = apply_filters( 'radio_station_block_control_styles', $css ); - wp_add_inline_style( 'wp-edit-blocks', $css ); + // 2.5.0: use radio_station_add_inline_style + radio_station_add_inline_style( 'wp-edit-blocks', $css ); // --- enqueue radio player styles --- if ( array_key_exists( 'player', $callbacks ) ) { @@ -374,7 +376,8 @@ function radio_station_block_editor_assets() { // --- enqueue player control styles inline --- $control_styles = radio_player_control_styles( false ); - wp_add_inline_style( 'radio-player', $control_styles ); + // 2.5.0: use radio_station_add_inline_style + radio_station_add_inline_style( 'radio-player', $control_styles ); } } diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 8d355d5..493bdc5 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -312,7 +312,8 @@ function radio_station_master_schedule( $atts ) { // --- enqueue schedule loader script --- $js = radio_station_master_schedule_loader_js( $atts ); - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); // --- schedule display override --- // 2.3.1: add full schedule override filter @@ -354,7 +355,8 @@ function radio_station_master_schedule( $atts ) { // --- enqueue table view script --- $js = radio_station_master_schedule_table_js(); - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); // --- filter and return --- // 2.5.0: added prefixed filter for consistency @@ -383,7 +385,8 @@ function radio_station_master_schedule( $atts ) { // --- enqueue tabs view script --- $js = radio_station_master_schedule_tabs_js(); - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); // --- filter and return --- // 2.5.0: added prefixed filter for consistency @@ -412,7 +415,8 @@ function radio_station_master_schedule( $atts ) { // --- enqueue list view script --- $js = radio_station_master_schedule_list_js(); - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); // --- filter and return --- // 2.5.0: added prefixed filter for consistency @@ -879,7 +883,8 @@ classes = ['.master-show-entry', '.master-schedule-tabs-show', '.master-list-day // --- enqueue script --- // 2.3.0: add script code to existing handle - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); return $html; } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 389b64b..c7b05a7 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -326,7 +326,8 @@ function radio_remove_language(term) { // --- add script inline --- // 2.3.3.9: added language edit script filter $js = apply_filters( 'radio_station_language_edit_script', $js ); - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } // ---------------------------- @@ -895,7 +896,8 @@ function radio_station_show_shifts_metabox() { // --- enqueue inline script --- // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); // --- shift display styles --- // 2.3.2: added dashed border to new shift @@ -1921,7 +1923,8 @@ function radio_station_show_images_metabox() { // --- enqueue script inline --- // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); // 2.3.3.9: add media modal close button style fix echo ''; @@ -3409,7 +3412,8 @@ function radio_station_override_show_metabox() { $js = radio_station_override_show_script(); // --- enqueue inline script --- - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } // ------------------------- @@ -3572,7 +3576,8 @@ function radio_station_schedule_override_metabox() { // --- enqueue inline script --- // 2.3.0: enqeue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); // --- override metabox bottom action --- // 2.5.0: added override metabox action @@ -4679,8 +4684,9 @@ function radio_station_override_save_data( $post_id ) { $js .= "});" . "\n"; // --- output the scripts --- - // TODO: use wp_add_inline_script ? - echo ''; + // 2.5.0: use radio_station_add_inline_script + // echo ''; + radio_station_add_inline_script( 'radio-station-admin', $js ); // 2.3.3.9: trigger action for save or add override time if ( 'radio_station_override_save' == $action ) { @@ -5363,7 +5369,8 @@ function radio_station_playlist_metabox() { // --- enqueue inline script --- // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); // --- track list styles --- // 2.3.0: added track meta style fix @@ -6134,8 +6141,9 @@ function radio_station_playlist_admin_list_scripts() { // 2.3.0: enqueue instead of echo // 2.3.3.9: filter playlist list script // 2.5.0: fix incorrect variable to filter (css) + // 2.5.0: use radio_station_add_inline_script $js = apply_filters( 'radio_station_playlist_list_script', $js ); - wp_add_inline_script( 'radio-station-admin', $js ); + radio_station_add_inline_script( 'radio-station-admin', $js ); } @@ -6258,7 +6266,8 @@ function radio_station_playlists_quick_edit_script( $hook ) { }; })(jQuery);"; - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } } @@ -6681,7 +6690,8 @@ function radio_station_posts_quick_edit_script( $hook ) { }; })(jQuery);"; - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } } @@ -6725,7 +6735,8 @@ function radio_station_show_posts_bulk_edit_script( $hook ) { }); });"; - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 3d3af1c..e0de86d 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1881,7 +1881,8 @@ function radio_station_archive_pagination_javascript() { }"; // --- enqueue script inline --- - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); } @@ -1984,7 +1985,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { $type = 'episode'; } if ( RADIO_STATION_DEBUG ) { - echo 'Show Posts (' . $type . '):' . print_r( $posts, true ) . ''; + echo 'Show Posts (' . esc_html( $type ) . '):' . esc_html( print_r( $posts, true ) ) . ''; } } if ( !isset( $posts ) || !$posts || !is_array( $posts ) || ( count( $posts ) == 0 ) ) { @@ -2273,7 +2274,8 @@ function radio_station_list_pagination_javascript() { // --- enqueue script inline --- // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); } @@ -3036,7 +3038,7 @@ function radio_station_current_show() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-show' ); if ( RADIO_STATION_DEBUG ) { - echo 'Current Show Shortcode Attributes: ' . print_r( $atts, true ) . '' . "\n"; + echo 'Current Show Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . '' . "\n"; } // --- output widget contents --- @@ -3653,10 +3655,10 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $output .= '' . "\n"; if ( RADIO_STATION_DEBUG ) { $output .= ''; - $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . $now . ')' . PHP_EOL; - $output .= 'Next Start Time: ' . date('y-m-d H:i:s', $next_start_time ) . ' (' . $next_start_time . ')' . PHP_EOL; - $output .= 'Next End Time: ' . date( 'y-m-d H:i:s', $next_end_time ) . ' (' . $next_end_time . ')' . PHP_EOL; - $output .= 'Starting in: ' . ( $next_start_time - $now ) . PHP_EOL; + $output .= 'Now: ' . esc_html( date( 'Y-m-d H:i:s', $now ) ) . ' (' . esc_html( $now ) . ')' . PHP_EOL; + $output .= 'Next Start Time: ' . esc_html( date('y-m-d H:i:s', $next_start_time ) ) . ' (' . esc_html( $next_start_time ) . ')' . PHP_EOL; + $output .= 'Next End Time: ' . esc_html( date( 'y-m-d H:i:s', $next_end_time ) ) . ' (' . esc_html( $next_end_time ) . ')' . PHP_EOL; + $output .= 'Starting in: ' . esc_html( $next_start_time - $now ) . PHP_EOL; $output .= ''; } diff --git a/player/compat.php b/player/compat.php index 5f6199d..8c1cece 100644 --- a/player/compat.php +++ b/player/compat.php @@ -6,8 +6,11 @@ if ( !function_exists( 'radio_station_player_enqueue_styles' ) ) { function radio_station_player_enqueue_styles() { - + radio_player_enqueue_styles(); } } + + + diff --git a/player/radio-player.php b/player/radio-player.php index b1db042..d8eae9b 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -1334,8 +1334,14 @@ function radio_player_core_scripts() { $js = radio_player_get_settings(); if ( '' != $js ) { if ( function_exists( 'wp_add_inline_script' ) ) { - // --- add inline script --- - wp_add_inline_script( 'radio-player', $js, 'before' ); + // 2.5.0: added check if script already done + if ( !wp_script_is( 'done', 'radio-player' ) ) { + // --- add inline script --- + wp_add_inline_script( 'radio-player', $js, 'before' ); + } else { + // --- print settings directly --- + echo ""; + } } else { // --- print settings directly --- echo ""; @@ -1414,8 +1420,14 @@ function radio_player_enqueue_script( $script ) { // --- output script tag --- if ( '' != $js ) { if ( function_exists( 'wp_add_inline_script' ) ) { - // --- add inline script --- - wp_add_inline_script( 'radio-player', $js, 'after' ); + // 2.5.0: added check if script already done + if ( !wp_script_is( 'done', 'radio-player' ) ) { + // --- add inline script --- + wp_add_inline_script( 'radio-player', $js, 'after' ); + } else { + // --- print script directly --- + echo ""; + } } else { // --- print script directly --- echo ""; @@ -2611,7 +2623,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { // --- enqueue player control styles inline --- $control_styles = radio_player_control_styles( false ); if ( '' != $control_styles ) { - wp_add_inline_style( 'radio-player', $control_styles ); + // 2.5.0: use radio_station_add_inline_style + radio_station_add_inline_style( 'radio-player', $control_styles ); } } else { diff --git a/radio-station-admin.php b/radio-station-admin.php index 22d0b7d..8fd7e49 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -62,7 +62,8 @@ function radio_station_enqueue_admin_scripts() { if ( RADIO_STATION_DEBUG ) { $js = "radio_admin.debug = true;"; - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } // --- enqueue admin styles --- @@ -337,7 +338,8 @@ function radio_station_taxonomy_submenu_fix() { // --- enqueue script inline --- // 2.3.0: enqueue instead of echoing if ( '' != $js ) { - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-admin', $js ); } } } diff --git a/radio-station.php b/radio-station.php index fff69e7..15d4f67 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.10 +Version: 2.4.9.11 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -57,7 +57,13 @@ // - Enqueue Plugin Scripts // - Register Moment JS // - Enqueue Plugin Script +// - Add Inline Script +// - Print Footer Scripts +// - Print Admin Footer Scripts // - Enqueue Plugin Stylesheet +// - Add Inline Style +// - Print Footer Styles +// - Print Admin Footer Styles // - Enqueue Datepicker // - Enqueue Localized Script Values // - Localization Script @@ -625,6 +631,63 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = } } +// ----------------- +// Add Inline Script +// ----------------- +// 2.5.0: added for missed inline scripts (via shortcodes) +function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { + + // --- add check if script is already done --- + if ( !wp_script_is( 'done', $handle ) ) { + // --- handle javascript normally --- + wp_add_inline_script( $handle, $js, $position ); + } else { + // --- store extra javascript for later output --- + if ( !strstr( $handle, '-admin' ) ) { + global $radio_station_scripts; + add_action( 'wp_print_footer_scripts', 'radio_station_print_footer_scripts', 20 ); + if ( !isset( $radio_station_scripts[$handle] ) ) { + $radio_station_scripts[$handle] = ''; + } + $radio_station_scripts[$handle] .= $js; + } else { + global $radio_station_admin_scripts; + add_action( 'admin_print_footer_scripts', 'radio_station_admin_print_footer_scripts', 20 ); + if ( !isset( $radio_station_admin_scripts[$handle] ) ) { + $radio_station_admin_scripts[$handle] = ''; + } + $radio_station_admin_scripts[$handle] .= $js; + } + } +} + +// -------------------- +// Print Footer Scripts +// -------------------- +// 2.5.0: added for missed inline scripts +function radio_station_print_footer_scripts() { + global $radio_station_scripts; + if ( is_array( $radio_station_scripts ) && ( count( $radio_station_scripts ) > 0 ) ) { + foreach ( $radio_station_scripts as $handle => $js ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + } + } +} + +// -------------------------- +// Print Admin Footer Scripts +// -------------------------- +function radio_station_admin_print_footer_scripts() { + global $radio_station_admin_scripts; + if ( is_array( $radio_station_admin_scripts ) && ( count( $radio_station_admin_scripts ) > 0 ) ) { + foreach ( $radio_station_admin_scripts as $handle => $js ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + } + } +} + // ------------------------- // Enqueue Plugin Stylesheet // ------------------------- @@ -669,6 +732,62 @@ function radio_station_enqueue_style( $stylekey, $deps = array() ) { } } +// ----------------- +// Add Inline Styles +// ----------------- +// 2.5.0: added for missed inline styles (via shortcodes) +function radio_station_add_inline_style( $handle, $css ) { + + // --- add check if style is already done --- + if ( !wp_style_is( 'done', $handle ) ) { + // --- handle javascript normally --- + wp_add_inline_style( $handle, $css ); + } else { + // --- store extra javascript for later output --- + if ( !strstr( $handle, '-admin' ) ) { + global $radio_station_styles; + add_action( 'wp_print_footer_scripts', 'radio_station_print_footer_styles', 20 ); + if ( !isset( $radio_station_styles[$handle] ) ) { + $radio_station_styles[$handle] = ''; + } + $radio_station_styles[$handle] .= $css; + } else { + global $radio_station_admin_styles; + add_action( 'admin_print_footer_scripts', 'radio_station_admin_print_footer_styles', 20 ); + if ( !isset( $radio_station_admin_styles[$handle] ) ) { + $radio_station_admin_styles[$handle] = ''; + } + $radio_station_admin_styles[$handle] .= $css; + } + } +} + +// ------------------- +// Print Footer Styles +// ------------------- +// 2.5.0: added for missed inline styles +function radio_station_print_footer_styles() { + global $radio_station_styles; + if ( is_array( $radio_station_styles ) && ( count( $radio_station_styles ) > 0 ) ) { + foreach ( $radio_station_styles as $handle => $css ) { + echo ''; + } + } +} + +// ------------------------- +// Print Admin Footer Styles +// ------------------------- +// 2.5.0: added for missed inline styles +function radio_station_admin_print_footer_styles() { + global $radio_station_admin_styles; + if ( is_array( $radio_station_admin_styles ) && ( count( $radio_station_admin_styles ) > 0 ) ) { + foreach ( $radio_station_admin_styles as $handle => $css ) { + echo ''; + } + } +} + // ------------------ // Enqueue Datepicker // ------------------ @@ -705,7 +824,8 @@ function radio_station_enqueue_color_picker() { $js = "jQuery(document).ready(function() {"; $js .= "if (jQuery('.color-picker').length) {jQuery('.color-picker').wpColorPicker();}"; $js .= "});" . "\n"; - wp_add_inline_script( 'wp-color-picker-a', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'wp-color-picker-a', $js ); } // ------------------------------- @@ -721,7 +841,8 @@ function radio_station_localize_script() { } $js = radio_station_localization_script(); - wp_add_inline_script( 'radio-station', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station', $js ); $radio_station['script-localized'] = true; } diff --git a/readme.txt b/readme.txt index 4a0bbbd..fc4c905 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -=== Radio Station by netmix® - Manage and play your show schedule in WordPress! +=== Radio Station by netmix® - Manage and play your Show Schedule in WordPress! === Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate @@ -15,31 +15,31 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I [__Live Demo__](https://demo.radiostation.pro/) | [__Documentation__](https://radiostation.pro/docs/) | [__Support__](https://wordpress.org/support/plugin/radio-station/) | [__Upgrade to Pro!__](https://radiostation.pro/pricing/) -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. += RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = -The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. +**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters to create, manage and play a Show schedule on a WordPress website. Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. -A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has it's own dedicated page to display all the Show details in a responsive layout. +*Radio stations* and *TV channels* can use it to showcase their existing programme schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. -The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. -As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://radiostation.pro/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://demo.radiostation.pro). += SO WHAT DOES RADIO STATION BY netmix® DO? = -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also continuing to develop more exciting features and functionality for [Radio Station Pro](https://radiostation.pro/). +At core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the frontend of your site in one of three in-built schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) -= Updating from Prior to 2.3.0 = -Since 2.3.0, the first major feature update since plugin takeover in July 2019, Radio Station has incorporated a whole bunch of enhancements (see the changelog for a full list)... but here is a shortlist of the main new features: +You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** and assigned to Shows to give them edit access. **Genre** and **Language** taxonomies are available to group your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. + +**PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. And you can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! + + + + + + + +We worked hard to maintain backwards compatibility and test the new features thoroughly. -* an Updated Show Page Layout (based on Content Filters not Templates) -* Responsive Schedule Views (with integrated Override support) -* Revamped Schedule calculations (with Show Shift Conflict Checking) -* Producer and Show Editor Roles (for improved Show management) -* Language Taxonomy Assignments (for Shows and Overrides) -* Admin Plugin Settings Page (with a plethora of new options) -* ...and a Radio Station Data API via the WordPress REST API! -If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards compatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. = Support and Contribution = @@ -223,7 +223,7 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) -* Updated: Freemius SDK (2.5.5) +* Updated: Freemius SDK (2.5.7) * Updated: Plugin Panel (1.2.9) * Updated: AmplitudeJS (5.3.2) * Updated: Howler (2.2.3) diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 70229b3..673fbd0 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -1040,7 +1040,8 @@ $js .= " }"; $js .= " }"; $js .= "}, 500);"; - wp_add_inline_script( 'radio-station-page', $js ); + // 2.5.0: use radio_station_add_inline_script + radio_station_add_inline_script( 'radio-station-page', $js ); } // 2.3.3.9: turn off doing template flag From e1733d8c717d4af45a5b6842e4f45390ff53dfb6 Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 27 Apr 2023 21:35:29 +1000 Subject: [PATCH 291/377] printed script check fix --- radio-station.php | 10 ++++++---- readme.md | 40 ++++++++++++++++++++-------------------- readme.txt | 17 +++++++++-------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/radio-station.php b/radio-station.php index 15d4f67..a6e734e 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.11 +Version: 2.4.9.12 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -627,7 +627,9 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = $url = $template['url']; // --- enqueue script --- - wp_enqueue_script( $scriptkey, $url, $deps, $version, $infooter ); + // 2.5.0: register script before enqueueing + wp_register_script( $scriptkey, $url, $deps, $version, $infooter ); + wp_enqueue_script( $scriptkey ); } } @@ -638,7 +640,7 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { // --- add check if script is already done --- - if ( !wp_script_is( 'done', $handle ) ) { + if ( !wp_script_is( $handle, 'done' ) ) { // --- handle javascript normally --- wp_add_inline_script( $handle, $js, $position ); } else { @@ -739,7 +741,7 @@ function radio_station_enqueue_style( $stylekey, $deps = array() ) { function radio_station_add_inline_style( $handle, $css ) { // --- add check if style is already done --- - if ( !wp_style_is( 'done', $handle ) ) { + if ( !wp_style_is( $handle, 'done' ) ) { // --- handle javascript normally --- wp_add_inline_style( $handle, $css ); } else { diff --git a/readme.md b/readme.md index 2f2f394..5b93f11 100644 --- a/readme.md +++ b/readme.md @@ -23,41 +23,42 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html ## Description -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. Its functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. +[__Live Demo__](https://demo.radiostation.pro/) | [__Documentation__](https://radiostation.pro/docs/) | [__Support__](https://wordpress.org/support/plugin/radio-station/) | [__Upgrade to Pro!__](https://radiostation.pro/pricing/) -The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. += RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = -A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has its own dedicated page to display all the Show details in a responsive layout. +**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters to create, manage and play a Show schedule on a WordPress website. Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. -The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. +*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. -As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://radiostation.pro/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostation.pro). -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also continuing to develop more exciting features and functionality for [Radio Station Pro](https://radiostation.pro/). += SO WHAT DOES RADIO STATION BY netmix® DO? = -### Updating from Prior to 2.3.0 +At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) -Since 2.3.0, the first major feature update since plugin takeover in July 2019, Radio Station has incorporated a whole bunch of enhancements (see the changelog for a full list)... but here is a shortlist of the main new features: -* an Updated Show Page Layout (based on Content Filters not Templates) -* Responsive Schedule Views (with integrated Override support) -* Revamped Schedule calculations (with Show Shift Conflict Checking) -* Producer and Show Editor Roles (for improved Show management) -* Language Taxonomy Assignments (for Shows and Overrides) -* Admin Plugin Settings Page (with a plethora of new options) -* ...and a Radio Station Data API via the WordPress REST API! +You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** with edit privelages when assigned to a Show. **Genre** and **Language** taxonomies are available to group and filter your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. -If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards compatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. +**PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. You can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All are available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! -### Support and Contribution += WANT MORE? UPGRADE TO RADIO STATION PRO! = + +In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* + +**PLUS**, building on our jamming free **Stream Player**, we’ve created our own *innovative page transition technology* and then combined it with a purpose-built sticky **Sitewide Player Bar**, allowing your listeners to navigate while continuing to play your audio ***uninterrupted***! This is a real game-changer for your listener's online experience of your site. The Player Bar includes *animated live track metadata*, *responsive display sections* and *extended color and button themes*. + +[Click here to find out more about Radio Station PRO](https://radiostation.pro/pricing/). + += Support and Contribution = We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). +We work hard to maintain backwards compatibility and test any new features thoroughly. If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. -### Quickstart Guide += Quickstart Guide = Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://radiostation.pro/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. @@ -75,11 +76,10 @@ Radio Station has several in-built [Data](https://radiostation.pro/docs/Data.md) This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://radiostation.pro/docs/Roadmap.md) if you are interested in seeing what is coming up next! -### Upgrading to Radio Station Pro += Upgrading to Radio Station Pro = Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://radiostation.pro/). - ## Installation 1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. diff --git a/readme.txt b/readme.txt index fc4c905..10671f8 100644 --- a/readme.txt +++ b/readme.txt @@ -19,25 +19,25 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I **Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters to create, manage and play a Show schedule on a WordPress website. Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. -*Radio stations* and *TV channels* can use it to showcase their existing programme schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. +*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. = SO WHAT DOES RADIO STATION BY netmix® DO? = -At core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the frontend of your site in one of three in-built schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) +At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) -You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** and assigned to Shows to give them edit access. **Genre** and **Language** taxonomies are available to group your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. +You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** with edit privelages when assigned to a Show. **Genre** and **Language** taxonomies are available to group and filter your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. -**PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. And you can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! +**PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. You can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All are available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! += WANT MORE? UPGRADE TO RADIO STATION PRO! = +In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* +**PLUS**, building on our jamming free **Stream Player**, we’ve created our own *innovative page transition technology* and then combined it with a purpose-built sticky **Sitewide Player Bar**, allowing your listeners to navigate while continuing to play your audio ***uninterrupted***! This is a real game-changer for your listener's online experience of your site. The Player Bar includes *animated live track metadata*, *responsive display sections* and *extended color and button themes*. - - - -We worked hard to maintain backwards compatibility and test the new features thoroughly. +[Click here to find out more about Radio Station PRO](https://radiostation.pro/pricing/). @@ -46,6 +46,7 @@ We worked hard to maintain backwards compatibility and test the new features tho We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). +We work hard to maintain backwards compatibility and test any new features thoroughly. If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. From 3ea27ee183fc440c48e2f65f53d47ce3d56ebf31 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 28 Apr 2023 16:55:21 +1000 Subject: [PATCH 292/377] update readme and quickstart --- docs/index.md | 23 +++---- readme.md | 168 +++++++++++++++++++++++++++++++++++++++++++------- readme.txt | 166 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 302 insertions(+), 55 deletions(-) diff --git a/docs/index.md b/docs/index.md index b9170e5..be8e6ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,24 +23,25 @@ ### Quickstart Guide -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](./FAQ.md) for Frequently Asked Questions as you may find the answer there. +Once you have installed and activated the **Radio Station** plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. Note if you have a specific question, you can check out the [Frequently Asked Questions](./FAQ.md) as you may find the answer there. -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](./Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](./Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. +Firstly, you can visit the Plugin Settings screen to adjust the default [Plugin Options](./Options.md) to your liking. Here you can set your Radio Timezone and Language, along with your Streaming URL and Station Logo, as well as other global plugin settings. Also from this Settings page you can assign [Automatic Pages](./Display.md#automatic-pages) and Views for your Program Schedule display and for other plugin post type archive displays. But first you will want to add some Shows to display in your Schedule! -Now that you have your Streaming URL set, at this point you might want to set up the Radio Stream Player next. You can add this as a widget to your sidebar widget area from the Appearance -> Widgets admin page. Or you can use the Player shortcode to embed it in a page. See the [Player](./Player.md) documentation for more details. +To do this, click on Shows in the adimn submenu, then on "Add a New Show" at the top. Give it a Shift timeslot and a description and then click Publish. Then view the Show page by clicking the Show Permalink under the show title. (Depending on your Theme, you may wish to adjust the [Templates](./Display.md#page-templates) used.) You can also assign different [Images](./Display.md#images) to Shows. -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](./Display.md#page-templates) used. You can also assign different [Images](./Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](./Manage.md) your Shows and other Station data via the WordPress Admin area. +Next, have a look at your Program Schedule page to see the Show displayed there also. Keep adding Shows until you have your Schedule filled in! You can also add schedule Overrides for specific date and time blocks only. For ease of use they can be fully or partially linked to an existing Show. You can further [Manage](./Manage.md) your Shows and other Station data via the WordPress Admin area. -Next you may want to give some users on your site some plugin [Roles](./Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. +Now you may want to give some users on your site some plugin [Roles](./Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a user role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and allow them to edit that Show. You can also assign the Show Editor role if you have someone who needs to edit all plugin post types without being a site Administator. -Along with the Player widget, there are a few [Widgets](./Widgets.md) you can add via your Appearance -> Widgets menu. There is one for displaying the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. +There are a number of [Widgets](./Widgets.md) you can add to your site via your *Appearance -> Widgets* admin submenu. These are also available as [Shortcodes](./Shortcodes.md) and [Blocks](./Widgets.md#radio-station-blocks). There are widgets for the Current Show or Playlist, and another to display Upcoming Shows. In this way you can also add a Stream Player, Radio Clock, Schedule View or Show List anywhere you like. -Then there are also a number of other [Shortcodes](./Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. +Radio Station has several in-built [Data](./Data.md) types. These include [Custom Post Types](./Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](./Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](./Filters.md#data-filters) throughout the plugin. We have also incorporated a [Data API](./API.md) in the plugin available via REST and/or WordPress Feeds, and this data is accessible in JSON format. -Radio Station has several in-built [Data](./Data.md) types. These include [Custom Post Types](./Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](./Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via [Custom Filters](./Filters.md) throughout the plugin. We have also incorporated an [API](./API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. +This plugin is under active development and we are continuously working to enhance the free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](./Roadmap.md) if you are interested in seeing what is coming up next! -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](./Roadmap.md) if you are interested in seeing what is coming up next! +#### [Pro] Professional Version Documentation +For ease of reference, documentation of features that are included in [Radio Station Pro](https://radiostation.pro) are included within the Free Documentation here, simply marked with `[Pro]` like the heading above. For a list of all these features linked to their revelant sections see the [Pro Feature Index](./Pro.md) #### Plugin Support and Contributing @@ -48,7 +49,3 @@ If you are wanting to Submit a Bug or Feature Request, you can do so via the [Wo Similarly, you can Contribute directly to the plugin via submitting an Issue or Pull Request on the [Github Plugin Repository](https://github.com/netmix/radio-station/). Or if you would prefer to get involved in the plugin's development even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. - -#### [Pro] Professional Version Documentation - -For ease of reference, documentation of features that are included in [Radio Station Pro](https://radiostation.pro) are included within the Free Documentation here, simply marked with `[Pro]` like the heading above. For a list of all these features linked to their revelant sections see the [Pro Feature Index](./Pro.md) \ No newline at end of file diff --git a/readme.md b/readme.md index 5b93f11..9448917 100644 --- a/readme.md +++ b/readme.md @@ -23,62 +23,188 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html ## Description -[__Live Demo__](https://demo.radiostation.pro/) | [__Documentation__](https://radiostation.pro/docs/) | [__Support__](https://wordpress.org/support/plugin/radio-station/) | [__Upgrade to Pro!__](https://radiostation.pro/pricing/) +[Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) + = RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = -**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters to create, manage and play a Show schedule on a WordPress website. Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. +**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters of all types, to manage your Show schedule and play your audio stream on a WordPress website. Thousands of broadcasters worldwide use **Radio Station** *by netmix®* to manage their schedules, showcase their shows, attach playlists, and feature broadcast teams. + +Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. -*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. +*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup, and for other *online curators* to schedule content for their audience base. = SO WHAT DOES RADIO STATION BY netmix® DO? = At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) - You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** with edit privelages when assigned to a Show. **Genre** and **Language** taxonomies are available to group and filter your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. **PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. You can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All are available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! +[Want more? Click here to find out about **Radio Station PRO**](https://radiostation.pro/). + + += RADIO STATION BY netmix® FEATURES = + +**Radio Station** *by netmix®* is the most feature-packed WordPress plugin for broadcasters today. With features that address both station and audience use cases, your team will be eager to add content your audience will find more engaging. The improved user experience provided by our plugin will help your station website increase visitor time spent on the site, which a critical metric your advertising partners will be keen to know. + +**Key Show Features**: + +- Add **Shows** with Weekly Shifts +- Automatic Shift **Conflict Checker** +- Add Show **Description** and **Images** +- Add Timeslot **Overrides** (linkable to Shows) +- Assign **Host** and **Producer** user roles +- Assign **Genre** and **Language** Terms +- Assign Blog **Related Posts** to Shows +- Add **Playlists** and **Latest Audio** to Shows +- Show Page Content Layout Options +- Display Station/Show Phone Number +- Denote Encore Presentations (Repeats) + +**Key Station and Schedule Features**: + +- **3 Schedule Layouts**: Table, Tabs, List +- **Automatic Pages** for Schedule and Archives +- Full Schedule and Show **Data API** +- Set Station Name and Logo Image +- Set and Display Station Timezone +- Set Main Station Language +- Automatic Current Show Highlighting +- Genres Highlighted by User Selection +- Converts Show Times to Listener Timezone + +**Key Stream Player Features**: + +- Works with virtually any Stream source +- Compatible with Shoutcast, Icecast, Live365, and Radio.co +- 3 In-built Audio Scripts: Howler, Amplitude, jPlayer +- 7 Streaming Formats Support: MP3, AAC/M4A, OGG, OGA, WebM, RTMPA, OPUS +- Light and Dark Player Themes +- 3 Default Player Button Styles +- Added via Shortcode, Widget or Block + + += WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT = + +All of the following **Shortcodes** are available as classic **Widgets**, and as of version 2.5.0 they are also available as **Blocks** for the Gutenberg editor: + +- Stream Player +- Show Schedule +- Show Lists +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Need the best for your channel? Further improvements and extra features can be found in [**Radio Station PRO**](https://radiostation.pro/pricing/). + + += BOOST YOUR SEO WITH RADIO STATION BY netmix® = + +We strongly believe in SEO and we're confident that managing your Show schedule with **Radio Station** *by netmix®* will give your SEO a boost! Here's why... + +When it comes to SEO, there's only one thing better than great content - and that's great content that's *well organized!* With **Radio Station**, your website is immediately optimized for search through the addition of the **Shows** custom post type, giving you the best natural placement for your Show description and content. On top of this, you benefit directly from the internal linking of **Shows** with **Playlists**, **Related Posts**, and **Genre** / **Language** taxonomies. (And in [**Radio Station PRO**](https://radiostation.pro) this is extended even further with profile pages for **Hosts* and *Producers**, and **Episodes** with **Guest** / **Topic** taxonomies!) + +Combined this with a WordPress SEO plugin (such as All In One SEO Pack), this kind of keyword-rich organized content will boost your ranking in search results. You just need to make sure you add the content for all your Shows! *netmix®* founder Tony Zeoli had a strong working relationship with the founding team at All in One SEO and used it with **Radio Station** to boost client's SEO even before taking this plugin over. We know how important SEO is to your channel - so your audience can find the Shows and related content they care about - ***yours!*** + +**SPECIAL BONUS!** Once installed, broadcasters who power their station’s WordPress websites with **Radio Station** *by netmix®* may add their station listing to our [Netmix.com](https://netmix.com) Broadcasters Directory, making your schedule instantly available to a new audience, and giving you a free high-quality backlink from a legacy domain (originally registered in 1995!) ou can also receive a Featured Listing in the Directory by supporting the further development of the free, open-source version of this plugin via [Patreon](https://www.patreon.com/radiostation). + + + = WANT MORE? UPGRADE TO RADIO STATION PRO! = In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* **PLUS**, building on our jamming free **Stream Player**, we’ve created our own *innovative page transition technology* and then combined it with a purpose-built sticky **Sitewide Player Bar**, allowing your listeners to navigate while continuing to play your audio ***uninterrupted***! This is a real game-changer for your listener's online experience of your site. The Player Bar includes *animated live track metadata*, *responsive display sections* and *extended color and button themes*. -[Click here to find out more about Radio Station PRO](https://radiostation.pro/pricing/). +[Click here to find out more about Radio Station PRO](https://radiostation.pro/). -= Support and Contribution = -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. += RADIO STATION PRO BY netmix® FEATURES = + +**Radio Station PRO** takes your broadcast website to the next level with a full rack of **Scheduling Enhancements** and **Content Tools** you can use to satisfy and impress your visitors! **ALSO** in Pro, we’ve added even more improvements to our Stream Player, including the ability to activate a **Sitewide Player Bar** with smooth page transitions, allowing your audience to browse your site content while listening to your audio stream playback ***uninterrupted!*** + +**Key Scheduling Features**: +- Front End **Visual Schedule Editor** +- Additional Schedule **Calendar and Grid Views** +- **Multi-View Switching** on Show Schedule +- Schedule Previous/Next **Week Shifting** +- Listener **Timezone Selection** Switching +- **Recurring Override** Timeslots +- Show and Schedule **Import/Export** + +**Key Content Features**: + +- Host and Producer **Profile Pages** +- Easy **Role Assignment Interface** +- Dynamic **Auto-refresh Widgets** +- **Show Episodes** linked to Shows +- Add **Topic** and **Guest** Terms to Episodes +- Automatic **Team Display** for Show Pages +- **Social Network Icons** (for Shows & Profiles) +- Page Builder Modules (**Elementor** & **Beaver Builder**) +- All Pro Settings added to Gutenberg **Blocks** + +**Key PRO Player Features**: + +- **Persistent Sitewide Player Bar** +- Single Page Application **Fading Transitions** +- Station, Player/Track and Show **Bar Sections** +- **Responsive Display** Section Navigation +- Animated **Track Metadata** Display (via Stream) +- 12 Extra Player **Button Themes** +- Bar and Player **Advanced Color Styling** +- Optional **Popup Player** Button +- Player Page Builder Module (**Elementor** & **Beaver Builder**) +- Pro Player Settings added to **Player Block** + +[Click here to Upgrade to **Radio Station PRO**](https://radiostation.pro/pricing/). + -For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). -We work hard to maintain backwards compatibility and test any new features thoroughly. += DOCUMENTATION AND SUPPORT = -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. +As there is a lot you can do with **Radio Station** *by netmix®*, we’ve made an effort to provide *complete* [Radio Station plugin Documentation](https://radiostation.pro/docs/). To get you going faster, you can find a [Quickstart Guide](https://radiostation.pro/docs/quickstart/) there (as well as in the section below.) Documentation for PRO Features can be found within the existing documentation, as well as indexed separately [here](http://radiostation.pro/docs/pro/). -= Quickstart Guide = +You can see some example displays from the plugin via the Screenshots section below, and *full live examples* for both Free and PRO features are available on the [Radio Station Plugin Demo Site](https://demo.radiostation.pro). If you are ready to upgrade or want to find out more about **Radio Station PRO** then [Click Here](https://radiostation.pro/pricing/). -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://radiostation.pro/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. +For free version plugin **Support**, you can ask in the [WordPress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to our [Github Repository Issues](https://github.com/netmix/radio-station/issues). For **PRO Support**, please [Submit a Support Ticket](https://radiostation.pro/support/). -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://radiostation.pro/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://radiostation.pro/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/Display.md#page-templates) used. You can also assign different [Images](https://radiostation.pro/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://radiostation.pro/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. += QUICKSTART GUIDE = -Next you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. +Once you have installed and activated the **Radio Station** plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. Note if you have a specific question, you can check out the [Frequently Asked Questions](https://radiostation.pro/docs/faq/) as you may find the answer there. + +Firstly, you can visit the Plugin Settings screen to adjust the default [Plugin Options](https://radiostation.pro/docs/options/) to your liking. Here you can set your Radio Timezone and Language, along with your Streaming URL and Station Logo, as well as other global plugin settings. Also from this Settings page you can assign [Automatic Pages](https://radiostation.pro/docs/display/#automatic-pages) and Views for your Program Schedule display and for other plugin post type archive displays. But first you will want to add some Shows to display in your Schedule! + +To do this, click on Shows in the adimn submenu, then on "Add a New Show" at the top. Give it a Shift timeslot and a description and then click Publish. Then view the Show page by clicking the Show Permalink under the show title. (Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/display/#page-templates) used.) You can also assign different [Images](https://radiostation.pro/docs/display/#images) to Shows. + +Next, have a look at your Program Schedule page to see the Show displayed there also. Keep adding Shows until you have your Schedule filled in! You can also add schedule Overrides for specific date and time blocks only. For ease of use they can be fully or partially linked to an existing Show. You can further [Manage](https://radiostation.pro/docs/manage/) your Shows and other Station data via the WordPress Admin area. + +Now you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/roles/). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a user role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and allow them to edit that Show. You can also assign the Show Editor role if you have someone who needs to edit all plugin post types without being a site Administator. + +There are a number of [Widgets](https://radiostation.pro/docs/widgets/) you can add to your site via your *Appearance -> Widgets* admin submenu. These are also available as [Shortcodes](https://radiostation.pro/docs/shortcodes/) and [Blocks](https://radiostation.pro/docs/widgets/#radio-station-blocks). There are widgets for the Current Show or Playlist, and another to display Upcoming Shows. In this way you can also add a Stream Player, Radio Clock, Schedule View or Show List anywhere you like. + +Radio Station has several in-built [Data](https://radiostation.pro/docs/data/) types. These include [Custom Post Types](https://radiostation.pro/docs/data/#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/data/#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](https://radiostation.pro/docs/filters/#data-filters) throughout the plugin. We have also incorporated a [Data API](https://radiostation.pro/docs/api/) in the plugin available via REST and/or WordPress Feeds, and this data is accessible in JSON format. + +This plugin is under active development and we are continuously working to enhance the free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](https://radiostation.pro/docs/roadmap/) if you are interested in seeing what is coming up next! + + += CONTRIBUTION = + +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. -There are a few [Widgets](https://radiostation.pro/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab, and the [Roadmap here](https://radiostation.pro/docs/roadmap/). Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. -Then there are also a number of other [Shortcodes](https://radiostation.pro/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. -Radio Station has several in-built [Data](https://radiostation.pro/docs/Data.md) types. These include [Custom Post Types](https://radiostation.pro/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://radiostation.pro/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. += UPGRADING TO RADIO STATION PRO = -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://radiostation.pro/docs/Roadmap.md) if you are interested in seeing what is coming up next! +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version to "level up" the plugin to make your channel's schedule and player even more useable and accessible for all your listeners! -= Upgrading to Radio Station Pro = +[Click here to learn more about Radio Station Pro](https://radiostation.pro/). -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://radiostation.pro/). ## Installation diff --git a/readme.txt b/readme.txt index 10671f8..7e759ba 100644 --- a/readme.txt +++ b/readme.txt @@ -13,64 +13,187 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I == Description == -[__Live Demo__](https://demo.radiostation.pro/) | [__Documentation__](https://radiostation.pro/docs/) | [__Support__](https://wordpress.org/support/plugin/radio-station/) | [__Upgrade to Pro!__](https://radiostation.pro/pricing/) +[Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) + = RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = -**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters to create, manage and play a Show schedule on a WordPress website. Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. +**Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters of all types, to manage your Show schedule and play your audio stream on a WordPress website. Thousands of broadcasters worldwide use **Radio Station** *by netmix®* to manage their schedules, showcase their shows, attach playlists, and feature broadcast teams. + +Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the most popular Radio Station plugin for WordPress and is used by broadcasters of all types. -*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup - for just about any kind of *media curators* to schedule content for their audience base. +*Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup, and for other *online curators* to schedule content for their audience base. = SO WHAT DOES RADIO STATION BY netmix® DO? = At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) - You can also create detailed track **Playlists** and associate them with a Show, or assign blog posts as **Related Posts** to a Show. Users can be given a plugin role of **Host** or **Producer** with edit privelages when assigned to a Show. **Genre** and **Language** taxonomies are available to group and filter your Shows. **Automatic Pages** are available for your Schedule and all plugin record lists. **PLUS** we’ve built in a *NEW* **Stream Player** so that your audience can listen to your broadcast while navigating your site. You can also quickly display the **Current Show**, **Upcoming Shows**, **Current Playlist**, **Radio Time** and **Show Lists**. All are available for easily customizable display as *Blocks*, *Widgets* and/or *Shortcodes*! +[Want more? Click here to find out about **Radio Station PRO**](https://radiostation.pro/). + + += RADIO STATION BY netmix® FEATURES = + +**Radio Station** *by netmix®* is the most feature-packed WordPress plugin for broadcasters today. With features that address both station and audience use cases, your team will be eager to add content your audience will find more engaging. The improved user experience provided by our plugin will help your station website increase visitor time spent on the site, which a critical metric your advertising partners will be keen to know. + +**Key Show Features**: + +- Add **Shows** with Weekly Shifts +- Automatic Shift **Conflict Checker** +- Add Show **Description** and **Images** +- Add Timeslot **Overrides** (linkable to Shows) +- Assign **Host** and **Producer** user roles +- Assign **Genre** and **Language** Terms +- Assign Blog **Related Posts** to Shows +- Add **Playlists** and **Latest Audio** to Shows +- Show Page Content Layout Options +- Display Station/Show Phone Number +- Denote Encore Presentations (Repeats) + +**Key Station and Schedule Features**: + +- **3 Schedule Layouts**: Table, Tabs, List +- **Automatic Pages** for Schedule and Archives +- Full Schedule and Show **Data API** +- Set Station Name and Logo Image +- Set and Display Station Timezone +- Set Main Station Language +- Automatic Current Show Highlighting +- Genres Highlighted by User Selection +- Converts Show Times to Listener Timezone + +**Key Stream Player Features**: + +- Works with virtually any Stream source +- Compatible with Shoutcast, Icecast, Live365, and Radio.co +- 3 In-built Audio Scripts: Howler, Amplitude, jPlayer +- 7 Streaming Formats Support: MP3, AAC/M4A, OGG, OGA, WebM, RTMPA, OPUS +- Light and Dark Player Themes +- 3 Default Player Button Styles +- Added via Shortcode, Widget or Block + + += WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT = + +All of the following **Shortcodes** are available as classic **Widgets**, and as of version 2.5.0 they are also available as **Blocks** for the Gutenberg editor: + +- Stream Player +- Show Schedule +- Show Lists +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Need the best for your channel? Further improvements and extra features can be found in [**Radio Station PRO**](https://radiostation.pro/pricing/). + + += BOOST YOUR SEO WITH RADIO STATION BY netmix® = + +We strongly believe in SEO and we're confident that managing your Show schedule with **Radio Station** *by netmix®* will give your SEO a boost! Here's why... + +When it comes to SEO, there's only one thing better than great content - and that's great content that's *well organized!* With **Radio Station**, your website is immediately optimized for search through the addition of the **Shows** custom post type, giving you the best natural placement for your Show description and content. On top of this, you benefit directly from the internal linking of **Shows** with **Playlists**, **Related Posts**, and **Genre** / **Language** taxonomies. (And in [**Radio Station PRO**](https://radiostation.pro) this is extended even further with profile pages for **Hosts* and *Producers**, and **Episodes** with **Guest** / **Topic** taxonomies!) + +Combined this with a WordPress SEO plugin (such as All In One SEO Pack), this kind of keyword-rich organized content will boost your ranking in search results. You just need to make sure you add the content for all your Shows! *netmix®* founder Tony Zeoli had a strong working relationship with the founding team at All in One SEO and used it with **Radio Station** to boost client's SEO even before taking this plugin over. We know how important SEO is to your channel - so your audience can find the Shows and related content they care about - ***yours!*** + +**SPECIAL BONUS!** Once installed, broadcasters who power their station’s WordPress websites with **Radio Station** *by netmix®* may add their station listing to our [Netmix.com](https://netmix.com) Broadcasters Directory, making your schedule instantly available to a new audience, and giving you a free high-quality backlink from a legacy domain (originally registered in 1995!) ou can also receive a Featured Listing in the Directory by supporting the further development of the free, open-source version of this plugin via [Patreon](https://www.patreon.com/radiostation). + + + = WANT MORE? UPGRADE TO RADIO STATION PRO! = In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* **PLUS**, building on our jamming free **Stream Player**, we’ve created our own *innovative page transition technology* and then combined it with a purpose-built sticky **Sitewide Player Bar**, allowing your listeners to navigate while continuing to play your audio ***uninterrupted***! This is a real game-changer for your listener's online experience of your site. The Player Bar includes *animated live track metadata*, *responsive display sections* and *extended color and button themes*. -[Click here to find out more about Radio Station PRO](https://radiostation.pro/pricing/). +[Click here to find out more about Radio Station PRO](https://radiostation.pro/). += RADIO STATION PRO BY netmix® FEATURES = -= Support and Contribution = +**Radio Station PRO** takes your broadcast website to the next level with a full rack of **Scheduling Enhancements** and **Content Tools** you can use to satisfy and impress your visitors! **ALSO** in Pro, we’ve added even more improvements to our Stream Player, including the ability to activate a **Sitewide Player Bar** with smooth page transitions, allowing your audience to browse your site content while listening to your audio stream playback ***uninterrupted!*** + +**Key Scheduling Features**: +- Front End **Visual Schedule Editor** +- Additional Schedule **Calendar and Grid Views** +- **Multi-View Switching** on Show Schedule +- Schedule Previous/Next **Week Shifting** +- Listener **Timezone Selection** Switching +- **Recurring Override** Timeslots +- Show and Schedule **Import/Export** + +**Key Content Features**: + +- Host and Producer **Profile Pages** +- Easy **Role Assignment Interface** +- Dynamic **Auto-refresh Widgets** +- **Show Episodes** linked to Shows +- Add **Topic** and **Guest** Terms to Episodes +- Automatic **Team Display** for Show Pages +- **Social Network Icons** (for Shows & Profiles) +- Page Builder Modules (**Elementor** & **Beaver Builder**) +- All Pro Settings added to Gutenberg **Blocks** + +**Key PRO Player Features**: + +- **Persistent Sitewide Player Bar** +- Single Page Application **Fading Transitions** +- Station, Player/Track and Show **Bar Sections** +- **Responsive Display** Section Navigation +- Animated **Track Metadata** Display (via Stream) +- 12 Extra Player **Button Themes** +- Bar and Player **Advanced Color Styling** +- Optional **Popup Player** Button +- Player Page Builder Module (**Elementor** & **Beaver Builder**) +- Pro Player Settings added to **Player Block** + +[Click here to Upgrade to **Radio Station PRO**](https://radiostation.pro/pricing/). -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. -For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). -We work hard to maintain backwards compatibility and test any new features thoroughly. += DOCUMENTATION AND SUPPORT = -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. +As there is a lot you can do with **Radio Station** *by netmix®*, we’ve made an effort to provide *complete* [Radio Station plugin Documentation](https://radiostation.pro/docs/). To get you going faster, you can find a [Quickstart Guide](https://radiostation.pro/docs/quickstart/) there (as well as in the section below.) Documentation for PRO Features can be found within the existing documentation, as well as indexed separately [here](http://radiostation.pro/docs/pro/). -= Quickstart Guide = +You can see some example displays from the plugin via the Screenshots section below, and *full live examples* for both Free and PRO features are available on the [Radio Station Plugin Demo Site](https://demo.radiostation.pro). If you are ready to upgrade or want to find out more about **Radio Station PRO** then [Click Here](https://radiostation.pro/pricing/). -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://radiostation.pro/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. +For free version plugin **Support**, you can ask in the [WordPress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to our [Github Repository Issues](https://github.com/netmix/radio-station/issues). For **PRO Support**, please [Submit a Support Ticket](https://radiostation.pro/support/). -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://radiostation.pro/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://radiostation.pro/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/Display.md#page-templates) used. You can also assign different [Images](https://radiostation.pro/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://radiostation.pro/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. += QUICKSTART GUIDE = -Next you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. +Once you have installed and activated the **Radio Station** plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. Note if you have a specific question, you can check out the [Frequently Asked Questions](https://radiostation.pro/docs/faq/) as you may find the answer there. -There are a few [Widgets](https://radiostation.pro/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. +Firstly, you can visit the Plugin Settings screen to adjust the default [Plugin Options](https://radiostation.pro/docs/options/) to your liking. Here you can set your Radio Timezone and Language, along with your Streaming URL and Station Logo, as well as other global plugin settings. Also from this Settings page you can assign [Automatic Pages](https://radiostation.pro/docs/display/#automatic-pages) and Views for your Program Schedule display and for other plugin post type archive displays. But first you will want to add some Shows to display in your Schedule! + +To do this, click on Shows in the adimn submenu, then on "Add a New Show" at the top. Give it a Shift timeslot and a description and then click Publish. Then view the Show page by clicking the Show Permalink under the show title. (Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/display/#page-templates) used.) You can also assign different [Images](https://radiostation.pro/docs/display/#images) to Shows. + +Next, have a look at your Program Schedule page to see the Show displayed there also. Keep adding Shows until you have your Schedule filled in! You can also add schedule Overrides for specific date and time blocks only. For ease of use they can be fully or partially linked to an existing Show. You can further [Manage](https://radiostation.pro/docs/manage/) your Shows and other Station data via the WordPress Admin area. + +Now you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/roles/). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a user role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and allow them to edit that Show. You can also assign the Show Editor role if you have someone who needs to edit all plugin post types without being a site Administator. + +There are a number of [Widgets](https://radiostation.pro/docs/widgets/) you can add to your site via your *Appearance -> Widgets* admin submenu. These are also available as [Shortcodes](https://radiostation.pro/docs/shortcodes/) and [Blocks](https://radiostation.pro/docs/widgets/#radio-station-blocks). There are widgets for the Current Show or Playlist, and another to display Upcoming Shows. In this way you can also add a Stream Player, Radio Clock, Schedule View or Show List anywhere you like. + +Radio Station has several in-built [Data](https://radiostation.pro/docs/data/) types. These include [Custom Post Types](https://radiostation.pro/docs/data/#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/data/#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](https://radiostation.pro/docs/filters/#data-filters) throughout the plugin. We have also incorporated a [Data API](https://radiostation.pro/docs/api/) in the plugin available via REST and/or WordPress Feeds, and this data is accessible in JSON format. + +This plugin is under active development and we are continuously working to enhance the free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](https://radiostation.pro/docs/roadmap/) if you are interested in seeing what is coming up next! + + += CONTRIBUTION = + +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. -Then there are also a number of other [Shortcodes](https://radiostation.pro/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab, and the [Roadmap here](https://radiostation.pro/docs/roadmap/). Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. -Radio Station has several in-built [Data](https://radiostation.pro/docs/Data.md) types. These include [Custom Post Types](https://radiostation.pro/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://radiostation.pro/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://radiostation.pro/docs/Roadmap.md) if you are interested in seeing what is coming up next! += UPGRADING TO RADIO STATION PRO = -= Upgrading to Radio Station Pro = +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version to "level up" the plugin to make your channel's schedule and player even more useable and accessible for all your listeners! -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://radiostation.pro/). +[Click here to learn more about Radio Station Pro](https://radiostation.pro/). == Installation == @@ -249,6 +372,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) * Fixed: Workaround Amplitude pause event not firing +* Fixed: inline scripts when main script in head tag * Security Fix: Escape all debug output content = 2.4.0.9 = From 411419eab3aaac4d4b64f35ba91813aa79e8eb90 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 28 Apr 2023 17:02:56 +1000 Subject: [PATCH 293/377] readme markdown syntax fix --- readme.md | 30 +++++++++++------------------- readme.txt | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/readme.md b/readme.md index 9448917..c338c03 100644 --- a/readme.md +++ b/readme.md @@ -5,28 +5,20 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I ## Plugin Details Contributors: tonyzeoli, majick - Donate link: https://netmix.co/donate - Tags: dj, music, playlist, radio, shows, scheduling, broadcasting - Requires at least: 3.3.1 - Tested up to: 6.2 - Stable tag: 2.5.0 - License: GPLv2 or later - License URI: http://www.gnu.org/licenses/gpl-2.0.html - ## Description [Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) -= RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = +#### RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! **Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters of all types, to manage your Show schedule and play your audio stream on a WordPress website. Thousands of broadcasters worldwide use **Radio Station** *by netmix®* to manage their schedules, showcase their shows, attach playlists, and feature broadcast teams. @@ -35,7 +27,7 @@ Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the *Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup, and for other *online curators* to schedule content for their audience base. -= SO WHAT DOES RADIO STATION BY netmix® DO? = +#### SO WHAT DOES RADIO STATION BY netmix® DO? At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) @@ -46,7 +38,7 @@ You can also create detailed track **Playlists** and associate them with a Show, [Want more? Click here to find out about **Radio Station PRO**](https://radiostation.pro/). -= RADIO STATION BY netmix® FEATURES = +#### RADIO STATION BY netmix® FEATURES **Radio Station** *by netmix®* is the most feature-packed WordPress plugin for broadcasters today. With features that address both station and audience use cases, your team will be eager to add content your audience will find more engaging. The improved user experience provided by our plugin will help your station website increase visitor time spent on the site, which a critical metric your advertising partners will be keen to know. @@ -87,7 +79,7 @@ You can also create detailed track **Playlists** and associate them with a Show, - Added via Shortcode, Widget or Block -= WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT = +#### WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT All of the following **Shortcodes** are available as classic **Widgets**, and as of version 2.5.0 they are also available as **Blocks** for the Gutenberg editor: @@ -102,7 +94,7 @@ All of the following **Shortcodes** are available as classic **Widgets**, and as Need the best for your channel? Further improvements and extra features can be found in [**Radio Station PRO**](https://radiostation.pro/pricing/). -= BOOST YOUR SEO WITH RADIO STATION BY netmix® = +#### BOOST YOUR SEO WITH RADIO STATION BY netmix® We strongly believe in SEO and we're confident that managing your Show schedule with **Radio Station** *by netmix®* will give your SEO a boost! Here's why... @@ -114,7 +106,7 @@ Combined this with a WordPress SEO plugin (such as All In One SEO Pack), this ki -= WANT MORE? UPGRADE TO RADIO STATION PRO! = +#### WANT MORE? UPGRADE TO RADIO STATION PRO! In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* @@ -123,7 +115,7 @@ In the course of improving the free version, we’ve tracked a plethora of great [Click here to find out more about Radio Station PRO](https://radiostation.pro/). -= RADIO STATION PRO BY netmix® FEATURES = +#### RADIO STATION PRO BY netmix® FEATURES **Radio Station PRO** takes your broadcast website to the next level with a full rack of **Scheduling Enhancements** and **Content Tools** you can use to satisfy and impress your visitors! **ALSO** in Pro, we’ve added even more improvements to our Stream Player, including the ability to activate a **Sitewide Player Bar** with smooth page transitions, allowing your audience to browse your site content while listening to your audio stream playback ***uninterrupted!*** @@ -164,7 +156,7 @@ In the course of improving the free version, we’ve tracked a plethora of great [Click here to Upgrade to **Radio Station PRO**](https://radiostation.pro/pricing/). -= DOCUMENTATION AND SUPPORT = +#### DOCUMENTATION AND SUPPORT As there is a lot you can do with **Radio Station** *by netmix®*, we’ve made an effort to provide *complete* [Radio Station plugin Documentation](https://radiostation.pro/docs/). To get you going faster, you can find a [Quickstart Guide](https://radiostation.pro/docs/quickstart/) there (as well as in the section below.) Documentation for PRO Features can be found within the existing documentation, as well as indexed separately [here](http://radiostation.pro/docs/pro/). @@ -173,7 +165,7 @@ You can see some example displays from the plugin via the Screenshots section be For free version plugin **Support**, you can ask in the [WordPress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to our [Github Repository Issues](https://github.com/netmix/radio-station/issues). For **PRO Support**, please [Submit a Support Ticket](https://radiostation.pro/support/). -= QUICKSTART GUIDE = +#### QUICKSTART GUIDE Once you have installed and activated the **Radio Station** plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. Note if you have a specific question, you can check out the [Frequently Asked Questions](https://radiostation.pro/docs/faq/) as you may find the answer there. @@ -192,14 +184,14 @@ Radio Station has several in-built [Data](https://radiostation.pro/docs/data/) t This plugin is under active development and we are continuously working to enhance the free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](https://radiostation.pro/docs/roadmap/) if you are interested in seeing what is coming up next! -= CONTRIBUTION = +#### CONTRIBUTION We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab, and the [Roadmap here](https://radiostation.pro/docs/roadmap/). Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. -= UPGRADING TO RADIO STATION PRO = +#### UPGRADING TO RADIO STATION PRO Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version to "level up" the plugin to make your channel's schedule and player even more useable and accessible for all your listeners! diff --git a/readme.txt b/readme.txt index 7e759ba..54eba31 100644 --- a/readme.txt +++ b/readme.txt @@ -16,7 +16,7 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I [Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) -= RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! = +#### RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! **Radio Station** *by netmix®* is the most comprehensive WordPress plugin for broadcasters of all types, to manage your Show schedule and play your audio stream on a WordPress website. Thousands of broadcasters worldwide use **Radio Station** *by netmix®* to manage their schedules, showcase their shows, attach playlists, and feature broadcast teams. @@ -25,7 +25,7 @@ Used by thousands of stations worldwide, **Radio Station** *by netmix®* is the *Radio channels* and *television networks* can use it to showcase their existing program schedule online, *podcasters* to announce upcoming episode drops, *streamers* to let their fans know of their next live webcast, *social audio moderators* to let their group know of the next meetup, and for other *online curators* to schedule content for their audience base. -= SO WHAT DOES RADIO STATION BY netmix® DO? = +#### SO WHAT DOES RADIO STATION BY netmix® DO? At its core the plugin adds a **Show** custom post type, schedulable blocks of time that contain a description, a weekly shift repeater field, uploadable images and other meta information. This lets you easily and instantly display a full show **Schedule** on the front end of your site in one of three built-in schedule views. (It also supports adding **Overrides** for handling specific dates and times that can be fully or partially linked to existing shows.) @@ -36,7 +36,7 @@ You can also create detailed track **Playlists** and associate them with a Show, [Want more? Click here to find out about **Radio Station PRO**](https://radiostation.pro/). -= RADIO STATION BY netmix® FEATURES = +#### RADIO STATION BY netmix® FEATURES **Radio Station** *by netmix®* is the most feature-packed WordPress plugin for broadcasters today. With features that address both station and audience use cases, your team will be eager to add content your audience will find more engaging. The improved user experience provided by our plugin will help your station website increase visitor time spent on the site, which a critical metric your advertising partners will be keen to know. @@ -77,7 +77,7 @@ You can also create detailed track **Playlists** and associate them with a Show, - Added via Shortcode, Widget or Block -= WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT = +#### WIDGETS, SHORTCODES, GUTENBERG BLOCK SUPPORT All of the following **Shortcodes** are available as classic **Widgets**, and as of version 2.5.0 they are also available as **Blocks** for the Gutenberg editor: @@ -92,7 +92,7 @@ All of the following **Shortcodes** are available as classic **Widgets**, and as Need the best for your channel? Further improvements and extra features can be found in [**Radio Station PRO**](https://radiostation.pro/pricing/). -= BOOST YOUR SEO WITH RADIO STATION BY netmix® = +#### BOOST YOUR SEO WITH RADIO STATION BY netmix® We strongly believe in SEO and we're confident that managing your Show schedule with **Radio Station** *by netmix®* will give your SEO a boost! Here's why... @@ -104,7 +104,7 @@ Combined this with a WordPress SEO plugin (such as All In One SEO Pack), this ki -= WANT MORE? UPGRADE TO RADIO STATION PRO! = +#### WANT MORE? UPGRADE TO RADIO STATION PRO! In the course of improving the free version, we’ve tracked a plethora of great features and developed them for inclusion in **Radio Station PRO**. The Pro version amps up your broadcast website with numerous additional **Schedule Improvements** such as *extra schedule views*, *recurring timeslots*, *auto-refreshing widgets*, and a *visual schedule editor!* It also gives you **New Content Features** like *show Episodes and Ssegments*, *Topic and Guest taxonomies*, and *page builder modules!* @@ -113,7 +113,7 @@ In the course of improving the free version, we’ve tracked a plethora of great [Click here to find out more about Radio Station PRO](https://radiostation.pro/). -= RADIO STATION PRO BY netmix® FEATURES = +#### RADIO STATION PRO BY netmix® FEATURES **Radio Station PRO** takes your broadcast website to the next level with a full rack of **Scheduling Enhancements** and **Content Tools** you can use to satisfy and impress your visitors! **ALSO** in Pro, we’ve added even more improvements to our Stream Player, including the ability to activate a **Sitewide Player Bar** with smooth page transitions, allowing your audience to browse your site content while listening to your audio stream playback ***uninterrupted!*** @@ -154,7 +154,7 @@ In the course of improving the free version, we’ve tracked a plethora of great [Click here to Upgrade to **Radio Station PRO**](https://radiostation.pro/pricing/). -= DOCUMENTATION AND SUPPORT = +#### DOCUMENTATION AND SUPPORT As there is a lot you can do with **Radio Station** *by netmix®*, we’ve made an effort to provide *complete* [Radio Station plugin Documentation](https://radiostation.pro/docs/). To get you going faster, you can find a [Quickstart Guide](https://radiostation.pro/docs/quickstart/) there (as well as in the section below.) Documentation for PRO Features can be found within the existing documentation, as well as indexed separately [here](http://radiostation.pro/docs/pro/). @@ -163,7 +163,7 @@ You can see some example displays from the plugin via the Screenshots section be For free version plugin **Support**, you can ask in the [WordPress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to our [Github Repository Issues](https://github.com/netmix/radio-station/issues). For **PRO Support**, please [Submit a Support Ticket](https://radiostation.pro/support/). -= QUICKSTART GUIDE = +#### QUICKSTART GUIDE Once you have installed and activated the **Radio Station** plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. Note if you have a specific question, you can check out the [Frequently Asked Questions](https://radiostation.pro/docs/faq/) as you may find the answer there. @@ -182,14 +182,14 @@ Radio Station has several in-built [Data](https://radiostation.pro/docs/data/) t This plugin is under active development and we are continuously working to enhance the free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](https://radiostation.pro/docs/roadmap/) if you are interested in seeing what is coming up next! -= CONTRIBUTION = +#### CONTRIBUTION We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab, and the [Roadmap here](https://radiostation.pro/docs/roadmap/). Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. -= UPGRADING TO RADIO STATION PRO = +#### UPGRADING TO RADIO STATION PRO Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version to "level up" the plugin to make your channel's schedule and player even more useable and accessible for all your listeners! From 4d93cac311b4448392dfd7abca2d11dff3f0c9e6 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 28 Apr 2023 17:04:41 +1000 Subject: [PATCH 294/377] markdown update --- CHANGELOG.md | 1 + readme.md | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4d69a..6b62e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Genre/Language Archive Pagination * Fixed: Adjacent Post Links (where show has one shift) * Fixed: Workaround Amplitude pause event not firing +* Fixed: inline scripts when main script in head tag * Security Fix: Escape all debug output content ### 2.4.0.9 diff --git a/readme.md b/readme.md index c338c03..6e47523 100644 --- a/readme.md +++ b/readme.md @@ -5,14 +5,22 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I ## Plugin Details Contributors: tonyzeoli, majick + Donate link: https://netmix.co/donate + Tags: dj, music, playlist, radio, shows, scheduling, broadcasting + Requires at least: 3.3.1 + Tested up to: 6.2 + Stable tag: 2.5.0 + License: GPLv2 or later + License URI: http://www.gnu.org/licenses/gpl-2.0.html + ## Description [Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) From 1aab723467b232ebb410f64c444d5060750e41d3 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 28 Apr 2023 22:39:54 +1000 Subject: [PATCH 295/377] prerelease readme and doc updates --- docs/FAQ-new.md | 218 ---------------------------------------------- docs/FAQ.md | 147 +++++++++++++++++++------------ radio-station.php | 2 +- readme.md | 169 +++++++++++++++++++++++++---------- readme.txt | 139 ++++++++++++++++++++--------- 5 files changed, 313 insertions(+), 362 deletions(-) delete mode 100644 docs/FAQ-new.md diff --git a/docs/FAQ-new.md b/docs/FAQ-new.md deleted file mode 100644 index 0d95dd1..0000000 --- a/docs/FAQ-new.md +++ /dev/null @@ -1,218 +0,0 @@ -# Radio Station Plugin FAQ - -*** - -## Getting Started - -### How do I get started with Radio Station (Free or PRO)? - -Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. - -### Where can I find the full Radio Station documentation (Free or PRO)? - -The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. - -### How do I get support for Radio Station (Free or PRO)? - -For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. - -### What is the difference between Radio Station (Free) and Radio Station PRO? - -Radio Station PRO provides both enhancements to existing Free features, as well as introducing a number of additional standalone features: - -- Persistant Player Bar -- Dynamic Widget Reloading -- ... - -### Can I try Radio Station PRO before I purchase the plugin? - -Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card number when you sign up for the free trial and can cancel any time before the trial is up. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date of your registration. [Click here to start your trial.](https://radiostation.pro/pricing) - - -## Account and Billing - -### How do I access my Radio Station PRO account? - -We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. - -### Can I request a refund for Radio Station PRO? - -We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. - -### How do I cancel my Radio Station PRO subscription? - -Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bugfixes and feature additions. - - -## Plugin Usage - -### How do I schedule a Show? - -Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. -In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. - -### How do I display a full schedule of my Station's shows? - -Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) - -### What if I want to schedule a special event or one-off schedule change? - -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) - -### How do I change the Show Image displayed in the widgets and schedule? - -The widgets and schedule will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station > Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Simply set a new image for the Show. - -### How do I style how the plugin displays content on the front end of my site? - -The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. - - -## Widgets, Blocks and Shortcodes - -### What Widgets/Blocks are available with this plugin? - -The following Widgets are available to add via the WordPress Appearance > Widgets page: - -- Streaming Player -- Current Show -- Upcoming Shows -- Current Playlist -- Radio Clock - -Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. - -### Do the Widgets reload automatically? - -In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). - -Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) - -### What Shortcodes are available in Radio Station? - -See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: - -* `[master-schedule]` - Master Program Schedule Display -* `[current-show]` - Current Show Widget -* `[upcoming-shows]` - Upcoming Shows Widget -* `[current-playlist]` - Current Playlist Widget -* `[shows-archive]` - Archive List of Shows -* `[genres-archive]` - Archive List of Shows sorted by Genre -* `[languages-archive]` - Archive List of Shows sorted by Language -* `[overrides-archive]` - Archive List of Schedule overrides -* `[playlists-archive]` - Archive List of Show Playlists - -(Note old shortcode aliases will still work in current and future versions to prevent breakage.) - -### Do you include Page Builder support? - -The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). - - -## User Plugin Roles - -### How do I assign a User as the Host or Producer to one or more Shows? - -You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard > Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). - -### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? - -There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it' -s Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). - -### How do I use a different image from the Gravatar for a Host/Producer? - -If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). - - -## Languages and Translations - -### What languages other than English is the plugin available in? - -As of April 1st, 2023, known languages include the following: - -* Albanian (sq_AL) -* Dutch (nl_NL) -* French (fr_FR) -* German (de_DE) -* Italian (it_IT) -* Russian (ru_RU) -* Serbian (sr_RS) -* Spanish (es_ES) -* Catalan (ca) - -### Can Radio Station be translated into my language? - -You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. - - -## Troubleshooting - -### Why aren't all my Shows displaying in the Schedule? - -Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. - -### I'm seeing a 404 Not Found error when I click on the link for a Show! - -Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. - -### Why can't Show Hosts or Producers can't edit a Show page? - -The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. - -### Where is my data stored? Can I export my data? - -Radio Station is stores your site's settings and all post-type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard > Tools > Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard > Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. - - -## Integrations - -### Can I use this plugin for Podcasts? - -While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. - -### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? - -Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. - -### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? - -We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. - -### Can I import Show datae from Pro.Radio or the JOAN (Jock on Air Now) plugin? - -We do not have a method of importing data directly from JOAN or Pro.Radio - - -## Development Versions - -### How do I install the latest Development version for testing? - -If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: - -1. Visit the `develop` branch of the Radio Station Github repository at: -`https://github.com/netmix/radio-station/tree/develop/` -2. Click on the green "Code" button and select Download a ZIP. -3. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-develop/` -4. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old/` -5. Rename the subdirectory `/wp-content/plugins/radio-station-develop/` to `/wp-content/plugins/radio-station/` - -Then upload to WordPress via the plugin installer as normal. -Note that it will install to /wp-content/plugins/radio-station-develop/, and because of this won't overwrite your existing installation, so you'll need to deactivate that before activating the development version. - -You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. - -Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. - - -### What about Pro Beta Version Testing? - -We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: - -1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://users.freemius.com/login). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. -2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. - -**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github (if the required version is not yet available via the WordPress repository.) - -We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. - diff --git a/docs/FAQ.md b/docs/FAQ.md index 4e2e5ca..3194dce 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,78 +1,85 @@ # Radio Station Plugin FAQ *** +### Getting Started -### How do I get started with Radio Station (Free or PRO)? +##### How do I get started with Radio Station (Free or PRO)? Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. -### Where can I find the full Radio Station documentation (Free or PRO)? +##### Where can I find the full Radio Station documentation (Free or PRO)? The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. -### How do I get support for Radio Station (Free or PRO)? +##### How do I get support for Radio Station (Free or PRO)? -For Radio Station customers using the free, open-source version of our plugin, you can contact us in our support channel in the [WordPress support forums here](https://wordpress.org/support/plugin/radio-station/. For Radio Station PRO subscribers, you can email us at [support@radiostation.pro](mailto:support@radiostation.pro) and someone will respond to your inquiry within 12 to 24 hours. All support inquiries will be handled in the order they are received. Before contacting support, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station (Free and PRO). +For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. -### How do I access my Radio Station PRO account? +##### Can I try Radio Station PRO before I purchase the plugin? -We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. +Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card when you sign up for the free trial and can cancel any time before the trial ends. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date your free trial began. [Click here to start your trial.](https://radiostation.pro/pricing) -### Can I try Radio Station PRO before I purchase the plugin? -Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card number when you sign up for the free trial and can cancel any time before the trial is up. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date of your registration. +### Account and Billing -### Can I request a refund for Radio Station PRO? +##### How do I access my Radio Station PRO account? -We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30-day period is exhausted refunds are not available. +We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. -### How do I cancel my Radio Station PRO subscription? +##### Can I request a refund for Radio Station PRO? -Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bugfixes and feature additions. +We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. -### How do I schedule a Show? +##### How do I cancel my Radio Station PRO subscription? -Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. -In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Stationn menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. +Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bug fixes and feature additions. Radio Station (free) will continue to operate normally. -### How do I display a full schedule of my Station's shows? -Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) +### Plugin Usage -### Why aren't all my Shows displaying in the program schedule? +##### How do I schedule a Show? -Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. +Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. +In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. -### What if I want to schedule a special event or one-off schedule change? +##### How do I display a full schedule of my Station's shows? -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. +Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) -### Where is my data stored? Can I export my data? +##### What if I want to schedule a special event or one-off schedule change? -Radio Station PRO is a WordPress plugin, which functions similarly to other WordPress themes and plugins by storing your site's settings and all post-type data in your WordPress MySQL database, which is connected to your website and accessed through your WordPress hosting account with the provider you chose. Similarly to all WordPress themes and plugins, when changing settings or adding content to a WordPress website, Radio Station (free and PRO) stores data in the MySQL database as it is added, edited, and published, updated, or saved. You can export your data using WordPress Dashboard > Tools > Export feature or Radio Station PRO’s Export feature located at WordPress Dashboard > Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) -### Can I import Show datae from Pro.Radio or the JOAN (Jock on Air Now) plugin? +##### How do I change the Show Image displayed in the widgets and schedule? -We do not have a method of importing data directly from JOAN or Pro.Radio +The schedule, widgets and show page will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station -> Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Edit the Show then simply set a new image for the Show. -### I'm seeing a 404 Not Found error when I click on the link for a Show! +##### How do I style how the plugin displays content on the front end of my site? -Try re-saving your site's permalink settings via Settings -> Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. +The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. -### What if I want to style how Radio Station's schedule displays on the frond end? -The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. +### Widgets, Blocks and Shortcodes -### What Widgets are available with this plugin? +##### What Widgets/Blocks are available with this plugin? The following Widgets are available to add via the WordPress Appearance -> Widgets page: -Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. -### Do the Widgets reload automatically? +- Streaming Player +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. + +##### Do the Widgets reload automatically? + +In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) -### What Shortcodes are available in Radio Station? +##### What Shortcodes are available in Radio Station? See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: @@ -88,32 +95,31 @@ See the [Shortcode Documentation](./Shortcodes.md) for more details and a full l (Note old shortcode aliases will still work in current and future versions to prevent breakage.) -### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? +##### Do you include Page Builder support? -There are a number of different options depending on what your goals are.To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. This may help keep clear lines of separation for editorial responsibility over your content, as you may not want users with Autors or Conftirubyute roles to edit Shows, but to give a specific user the ability to do so. You can find more information on roles in the [Roles Documentation](./Roles.md) +The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). -### How do I change the Show Image displayed in the widgets and schedule? -The widgets and schedule will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station > Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Simply set a new image for the Show. +### User Plugin Roles -### Why don’t all WordPress Users appear in the Hosts or Producers lists on a Show Edit screen? +##### How do I assign a User as the Host or Producer to one or more Shows? -You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit screen. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. +You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard -> Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). -### Why can't Show Hosts or Producers can't edit a Show page? +##### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? -The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. +There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it's Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). -### How do I use a different image from the Gravatar for a Host/Producer? +##### How do I use a different image from the Gravatar for a Host/Producer? -If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows lets you the ability to add a different image to your Host/Producer's Uuser account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do just this, it's a little out mostly out of the scope of this plugin’s features. However, in our the Pro version, you can create separate profile pages to showcase each of your Hosts and Producers, to which, you can and assign profile images that appear on those to these profile pages. If you would like to enable this feature, pelase upgrade here. +If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). -Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. +### Languages and Translations -### What languages other than English is the plugin available in? +##### What languages other than English is the plugin available in? -Right now: +As of April 1st, 2023, known languages include the following: * Albanian (sq_AL) * Dutch (nl_NL) @@ -125,23 +131,52 @@ Right now: * Spanish (es_ES) * Catalan (ca) -### Can the plugin be translated into my language? +##### Can Radio Station be translated into my language? + +You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. + + +### Troubleshooting + +##### Why aren't all my Shows displaying in the Schedule? + +Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. + +##### I'm seeing a 404 Not Found error when I click on the link for a Show! + +Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. + +##### Where is my data stored? Can I export my data? + +Radio Station is stores your site's settings and all post type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard -> Tools -> Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard -> Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. + +##### Why can't Show Hosts or Producers can't edit a Show page? + +The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. If you need a user other than Administrator to be able to edit all Shows you can assign them a Show Editor role. -You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. -### Can I use this plugin for Podcasts? +### Integrations + +##### Can I use this plugin for Podcasts? While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. -### Can I use this plugin for TwitchTV, Facebook Live, or Clubhouse shows? +##### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. -### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? +##### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. -### How do I install the latest Development version for testing? +##### Can I import Show data from Pro.Radio or the JOAN (Jock on Air Now) plugin? + +We do not have a method of importing data directly from JOAN or Pro.Radio + + +### Development Versions + +##### How do I install the latest Development version for testing? If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: @@ -159,15 +194,15 @@ You can now visit your site to make sure nothing is broken. If you experience is Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. - -### What about Pro Beta Version Testing? +##### What about Pro Beta Version Testing? We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: -1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://dashboard.freemius.com). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. +1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://users.freemius.com/login). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. 2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. -**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github if that version is not yet available via the WordPress repository. +**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github (if the required version is not yet available via the WordPress repository.) We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. + diff --git a/radio-station.php b/radio-station.php index a6e734e..1c16f67 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.9.12 +Version: 2.5.0 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index 6e47523..b7ec27a 100644 --- a/readme.md +++ b/readme.md @@ -220,52 +220,85 @@ Love Radio Station and ready for more? As the free version develops, we have als ## Frequently Asked Questions -#### How do I get started with Radio Station? = +### Getting Started -Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. +##### How do I get started with Radio Station (Free or PRO)? -#### Where can I find the full plugin documentation? +Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. -The latest documentation can be found online at [RadioStation.Pro](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +##### Where can I find the full Radio Station documentation (Free or PRO)? -#### Is there a demo site I can view to see how Radio Station works? = +The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. -Yes, visit the [Radio Station Demo Site](https://demo.radiostation.pro/) to view the Free and PRO features of Radio Station. (Note you will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users.) +##### How do I get support for Radio Station (Free or PRO)? -#### How do I schedule a Show? +For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. + +##### Can I try Radio Station PRO before I purchase the plugin? + +Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card when you sign up for the free trial and can cancel any time before the trial ends. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date your free trial began. [Click here to start your trial.](https://radiostation.pro/pricing) + + +### Account and Billing + +##### How do I access my Radio Station PRO account? + +We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. + +##### Can I request a refund for Radio Station PRO? + +We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. + +##### How do I cancel my Radio Station PRO subscription? + +Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bug fixes and feature additions. Radio Station (free) will continue to operate normally. + + +### Plugin Usage + +##### How do I schedule a Show? Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. +In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. -#### How do I display a full schedule of my Station's shows? +##### How do I display a full schedule of my Station's shows? -In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) +Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) -#### I've scheduled all my Shows, but some are not showing up on the program schedule? +##### What if I want to schedule a special event or one-off schedule change? -Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) -#### What if I want to schedule a special event or one-off schedule change? +##### How do I change the Show Image displayed in the widgets and schedule? -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by clicking Schedule Override in the Radio Station admin menu. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) +The schedule, widgets and show page will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station -> Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Edit the Show then simply set a new image for the Show. -#### I'm seeing a 404 Not Found error when I click on the link for a Show! +##### How do I style how the plugin displays content on the front end of my site? -Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin. +The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. -#### What if I want to change or style the plugin's displays? -The default styles for Radio Station have intentionally been kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. +### Widgets, Blocks and Shortcodes -#### What Widgets are available with this plugin? +##### What Widgets/Blocks are available with this plugin? The following Widgets are available to add via the WordPress Appearance -> Widgets page: -Current Show, Upcoming Shows, Current Playlist. Radio Clock and Streaming Player Widgets will also be available in future versions. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. -#### Do the Widgets reload automatically? +- Streaming Player +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. + +##### Do the Widgets reload automatically? + +In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). -Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in this free version. This capability has been added as a new feature to the [Pro version](https://radiostation.pro) however - so that the widgets refresh exactly at Show changeover times. +Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) -#### What Shortcodes are available with this plugin? +##### What Shortcodes are available in Radio Station? See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: @@ -279,31 +312,33 @@ See the [Shortcode Documentation](./Shortcodes.md) for more details and a full l * `[overrides-archive]` - Archive List of Schedule overrides * `[playlists-archive]` - Archive List of Show Playlists -Note old shortcode aliases will still work in current and future versions to prevent breakage. +(Note old shortcode aliases will still work in current and future versions to prevent breakage.) + +##### Do you include Page Builder support? + +The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). -#### I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? -There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](./Roles.md) +### User Plugin Roles -#### How do I change the Show Avatar displayed in the sidebar widget? +##### How do I assign a User as the Host or Producer to one or more Shows? -The avatar is whatever image is assigned as the Show's Avatar. All you have to do is set a new Show Avatar on the Edit page for that Show. +You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard -> Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). -#### Why don't any users show up in the Hosts or Producers list on the Show edit page? = +##### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? -You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit page. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. +There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it's Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). -#### My Show Hosts and Producers can't edit a Show page. What do I do? +##### How do I use a different image from the Gravatar for a Host/Producer? -The only Hosts and Producers that can edit a show are the ones listed as being Hosts or Producers for that Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Host/Producer's Shows without permission. +If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). -#### I don't want to use Gravatar for my Host/Producer's image on their profile page. -Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. +### Languages and Translations -#### What languages other than English is the plugin available in? +##### What languages other than English is the plugin available in? -Right now: +As of April 1st, 2023, known languages include the following: * Albanian (sq_AL) * Dutch (nl_NL) @@ -315,35 +350,79 @@ Right now: * Spanish (es_ES) * Catalan (ca) -#### Can the plugin be translated into my language? +##### Can Radio Station be translated into my language? -You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. +You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. -#### Can I use this plugin for Podcasts? + +### Troubleshooting + +##### Why aren't all my Shows displaying in the Schedule? + +Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. + +##### I'm seeing a 404 Not Found error when I click on the link for a Show! + +Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. + +##### Where is my data stored? Can I export my data? + +Radio Station is stores your site's settings and all post type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard -> Tools -> Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard -> Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. + +##### Why can't Show Hosts or Producers can't edit a Show page? + +The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. If you need a user other than Administrator to be able to edit all Shows you can assign them a Show Editor role. + + +### Integrations + +##### Can I use this plugin for Podcasts? While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. -#### Can I use this plugin for TwitchTV, Facebook Live, or Clubhouse shows? +##### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. -#### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? +##### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. -#### How do I install the latest Development version for testing? +##### Can I import Show data from Pro.Radio or the JOAN (Jock on Air Now) plugin? + +We do not have a method of importing data directly from JOAN or Pro.Radio + + +### Development Versions + +##### How do I install the latest Development version for testing? If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: -1. Download the `develop` branch zip from the Github repository at: +1. Visit the `develop` branch of the Radio Station Github repository at: `https://github.com/netmix/radio-station/tree/develop/` -2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` -3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` -4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` +2. Click on the green "Code" button and select Download a ZIP. +3. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-develop/` +4. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old/` +5. Rename the subdirectory `/wp-content/plugins/radio-station-develop/` to `/wp-content/plugins/radio-station/` + +Then upload to WordPress via the plugin installer as normal. +Note that it will install to /wp-content/plugins/radio-station-develop/, and because of this won't overwrite your existing installation, so you'll need to deactivate that before activating the development version. You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. +Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. + +##### What about Pro Beta Version Testing? + +We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: + +1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://users.freemius.com/login). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. +2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. + +**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github (if the required version is not yet available via the WordPress repository.) +We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. ## Changelog diff --git a/readme.txt b/readme.txt index 54eba31..1e0d4ce 100644 --- a/readme.txt +++ b/readme.txt @@ -209,54 +209,76 @@ Love Radio Station and ready for more? As the free version develops, we have als == Frequently Asked Questions == -= How do I get started with Radio Station? = += How do I get started with Radio Station (Free or PRO)? = -Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. +Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. -= Where can I find the full plugin documentation? = += Where can I find the full Radio Station documentation (Free or PRO)? = -The latest documentation can be found online at [RadioStation.Pro](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. -= Is there a demo site I can view to see how Radio Station works? = += How do I get support for Radio Station (Free or PRO)? = -Yes, visit the [Radio Station Demo Site](https://demo.radiostation.pro/) to view the Free and PRO features of Radio Station. (Note you will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users.) +For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. + += Can I try Radio Station PRO before I purchase the plugin? = + +Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card when you sign up for the free trial and can cancel any time before the trial ends. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date your free trial began. [Click here to start your trial.](https://radiostation.pro/pricing) + += How do I access my Radio Station PRO account? = + +We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. + += Can I request a refund for Radio Station PRO? = + +We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. + +### How do I cancel my Radio Station PRO subscription? + +Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bug fixes and feature additions. Radio Station (free) will continue to operate normally. = How do I schedule a Show? = Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. +In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. = How do I display a full schedule of my Station's shows? = -In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](https://radiostation.pro/docs/shortcodes/#master-schedule-shortcode) ) - -= I've scheduled all my Shows, but some are not showing up on the program schedule? = - -Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. +Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) = What if I want to schedule a special event or one-off schedule change? = -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by clicking Schedule Override in the Radio Station admin menu. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) -= I'm seeing a 404 Not Found error when I click on the link for a Show! = += How do I change the Show Image displayed in the widgets and schedule? = -Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin. +The schedule, widgets and show page will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station -> Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Edit the Show then simply set a new image for the Show. -= What if I want to change or style the plugin's displays? = += How do I style how the plugin displays content on the front end of my site? = -The default styles for Radio Station have intentionally been kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. +The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. -= What Widgets are available with this plugin? = += What Widgets/Blocks are available with this plugin? = The following Widgets are available to add via the WordPress Appearance -> Widgets page: -Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](https://radiostation.pro/docs/widgets/) for more details on these Widgets. + +- Streaming Player +- Current Show +- Upcoming Shows +- Current Playlist +- Radio Clock + +Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. = Do the Widgets reload automatically? = -Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in this free version. This capability has been added as a new feature to the [Pro version](https://radiostation.pro) however - so that the widgets refresh exactly at Show changeover times. +In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). + +Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) -= What Shortcodes are available with this plugin? = += What Shortcodes are available in Radio Station? = -See the [Shortcode Documentation](https://radiostation.pro/docs/shortcodes/) for more details and a full list of possible Attributes for these Shortcodes: +See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: * `[master-schedule]` - Master Program Schedule Display * `[current-show]` - Current Show Widget @@ -268,31 +290,27 @@ See the [Shortcode Documentation](https://radiostation.pro/docs/shortcodes/) for * `[overrides-archive]` - Archive List of Schedule overrides * `[playlists-archive]` - Archive List of Show Playlists -Note old shortcode aliases will still work in current and future versions to prevent breakage. - -= I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? = - -There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/roles/) +(Note old shortcode aliases will still work in current and future versions to prevent breakage.) -= How do I change the Show Avatar displayed in the sidebar widget? = += Do you include Page Builder support? = -The avatar is whatever image is assigned as the Show's Avatar. All you have to do is set a new Show Avatar on the Edit page for that Show. +The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). -= Why don't any users show up in the Hosts or Producers list on the Show edit page? = += How do I assign a User as the Host or Producer to one or more Shows? = -You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit page. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. +You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard -> Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). -= My Show Hosts and Producers can't edit a Show page. What do I do? = += How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? = -The only Hosts and Producers that can edit a show are the ones listed as being Hosts or Producers for that Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Host/Producer's Shows without permission. +There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it's Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). -= I don't want to use Gravatar for my Host/Producer's image on their profile page. = += How do I use a different image from the Gravatar for a Host/Producer? = -Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. +If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). = What languages other than English is the plugin available in? = -Right now: +As of April 1st, 2023, known languages include the following: * Albanian (sq_AL) * Dutch (nl_NL) @@ -304,15 +322,31 @@ Right now: * Spanish (es_ES) * Catalan (ca) -= Can the plugin be translated into my language? = += Can Radio Station be translated into my language? = -You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. +You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. + += Why aren't all my Shows displaying in the Schedule? = + +Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. + += I'm seeing a 404 Not Found error when I click on the link for a Show! = + +Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. + += Where is my data stored? Can I export my data? = + +Radio Station is stores your site's settings and all post type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard -> Tools -> Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard -> Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. + += Why can't Show Hosts or Producers can't edit a Show page? = + +The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. If you need a user other than Administrator to be able to edit all Shows you can assign them a Show Editor role. = Can I use this plugin for Podcasts? = While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. -= Can I use this plugin for TwitchTV, Facebook Live, or Clubhouse shows? = += Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? = Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. @@ -320,18 +354,39 @@ Sure, there's no reason why you couldn't use the plugin to display a show schedu We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. += Can I import Show data from Pro.Radio or the JOAN (Jock on Air Now) plugin? = + +We do not have a method of importing data directly from JOAN or Pro.Radio + = How do I install the latest Development version for testing? = If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: -1. Download the `develop` branch zip from the Github repository at: +1. Visit the `develop` branch of the Radio Station Github repository at: `https://github.com/netmix/radio-station/tree/develop/` -2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` -3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` -4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` +2. Click on the green "Code" button and select Download a ZIP. +3. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-develop/` +4. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old/` +5. Rename the subdirectory `/wp-content/plugins/radio-station-develop/` to `/wp-content/plugins/radio-station/` + +Then upload to WordPress via the plugin installer as normal. +Note that it will install to /wp-content/plugins/radio-station-develop/, and because of this won't overwrite your existing installation, so you'll need to deactivate that before activating the development version. You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. +Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. + += What about Pro Beta Version Testing? = + +We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: + +1. Download the Beta versions by logging in to your [Freemius User Dashboard](https://users.freemius.com/login). and navigating to the "Downloads" section. You will see a dropdown list of all the Radio Station Pro releases, including beta ones. +2. Enable the Beta program option from your Radio Station Account page in your WordPress site's Admin area, and the latest Beta version will then be available as an update. + +**Important Note**: As we are developing the Free and Pro versions in tandem, the latest Pro Beta may require you to install a development version of the Free plugin for it to work. Please see the previous section for how you can install the development version from Github (if the required version is not yet available via the WordPress repository.) + +We recommend you test these on a Staging site (or a development copy of your live site.) This way you can make sure there are no significant bugs before using it on a production site. Of course, please be willing to [report any bugs](https://github.com/netmix/radio-station/issues) that you do find so we can ensure they are not present in the next official release. + == Screenshots == 1. Table Schedule View From 2ce714fa7de4b5edaaee3a527377f0b1169b70be Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 28 Apr 2023 22:28:23 -0400 Subject: [PATCH 296/377] add new banners and icons new banners and icons to the assets folder --- assets/banner-1544x500.jpg | Bin 0 -> 94397 bytes "assets/banner-772 \303\227 250 px).jpg" | Bin 0 -> 35982 bytes assets/icon-128x128.png | Bin 21132 -> 8587 bytes assets/icon-256x256.png | Bin 164305 -> 16255 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/banner-1544x500.jpg create mode 100644 "assets/banner-772 \303\227 250 px).jpg" diff --git a/assets/banner-1544x500.jpg b/assets/banner-1544x500.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0698ec7cddd4b9e31e0d8b483f9a696954aad3a3 GIT binary patch literal 94397 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 literal 0 HcmV?d00001 diff --git a/assets/icon-128x128.png b/assets/icon-128x128.png index b54d8f6ebde7070f51e586a88cc1f854325f71d4..727503920ee1f70b1e41f7da77db9f23579415f1 100644 GIT binary patch literal 8587 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 XKs5M(+^5TYw;4W_N$NzeZBPH%N$ip- diff --git a/assets/icon-256x256.png b/assets/icon-256x256.png index 6688faa3bff1610fcdc4cc00b85729998bc1ce86..888e20b52bae09951f7aaa6666eda0ace864a854 100644 GIT binary patch literal 16255 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 G@K>k(Cz*aP5wI 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
    + > @@ -563,18 +563,18 @@ class="fs-tag "> - - + + value="get_unique_affix() ) ?>_sync_license"> get_unique_affix() . '_sync_license' ) ?> - > - - + + - - + + - - + + $country ) : ?> + value="" address_country_code, $code ) ?>> + - + diff --git a/radio-station.php b/radio-station.php index 1fc0431..563ebd0 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.4 +Version: 2.5.5 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index 1d08956..6aa2565 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3.1 Tested up to: 6.2.2 -Stable tag: 2.5.4 +Stable tag: 2.5.5 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 931a31c..fa4c036 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 6.2.2 -Stable tag: 2.5.4 +Stable tag: 2.5.5 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -401,6 +401,7 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == = 2.5.5 = +* Updated: Freemius SDK (2.5.10) * Fixed: Prefix Block element JS constant to prevent conflict (EventOn) = 2.5.4 = From 47b9631501ae951801b90ed5c09acaa6e8623323 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 21 Jul 2023 10:44:05 +1000 Subject: [PATCH 307/377] version readme updates --- CHANGELOG.md | 3 +++ readme.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd46dec..e5dee13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.5 = * Updated: Freemius SDK (2.5.10) +* Added: RSS Posts Feed: Related Show node +* Added: RSS Show Feed: Host/Producer node * Fixed: Prefix Block element JS constant to prevent conflict (EventOn) +* Fixed: RSS Posts Feed: filter by Show conflict = 2.5.4 = * Updated: Freemius SDK (2.5.9) diff --git a/readme.txt b/readme.txt index fa4c036..850f2f7 100644 --- a/readme.txt +++ b/readme.txt @@ -402,7 +402,10 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.5 = * Updated: Freemius SDK (2.5.10) +* Added: RSS Posts Feed: Related Show node +* Added: RSS Show Feed: Host/Producer node * Fixed: Prefix Block element JS constant to prevent conflict (EventOn) +* Fixed: RSS Posts Feed: filter by Show conflict = 2.5.4 = * Updated: Freemius SDK (2.5.9) From 8de6c21cdcc57a881a17b4246ad80134558d5d30 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 30 Jul 2023 16:28:10 +1000 Subject: [PATCH 308/377] translation updates --- languages/radio-station-nl_NL.mo | Bin 7610 -> 38662 bytes languages/radio-station-nl_NL.po | 6295 ++++++++++++++++++++++++++++-- languages/radio-station.pot | 5077 ++++++++++++++++-------- 3 files changed, 9282 insertions(+), 2090 deletions(-) diff --git a/languages/radio-station-nl_NL.mo b/languages/radio-station-nl_NL.mo index 551491cae31344a8120011550dfcccbe6ff1d14a..8cd973e06ac7622cc8be8a7737df6ad861a72204 100644 GIT binary patch literal 38662 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 !akJAykhB?b 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 Zstl%#cqVlTWw!nm4SD-wV)d`l{{WX3Uoijx diff --git a/languages/radio-station-nl_NL.po b/languages/radio-station-nl_NL.po index caed45d..02a0405 100644 --- a/languages/radio-station-nl_NL.po +++ b/languages/radio-station-nl_NL.po @@ -1,538 +1,6039 @@ +# Translation of Plugins - Radio Station by netmix® – Manage and play your Show Schedule in WordPress! - Stable (latest release) in Dutch +# This file is distributed under the same license as the Plugins - Radio Station by netmix® – Manage and play your Show Schedule in WordPress! - Stable (latest release) package. msgid "" msgstr "" -"Project-Id-Version: radio station\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-11-08 08:27-0600\n" -"PO-Revision-Date: 2018-05-10 10:18+0200\n" -"Language-Team: \n" +"PO-Revision-Date: 2023-07-27 15:36:11+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: _e;__;esc_attr_e\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Generator: Poedit 2.0.7\n" -"X-Poedit-Basepath: c:/xampp/htdocs/wp352test/wp-content/plugins/radio-" -"station\n" -"Last-Translator: \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: GlotPress/4.0.0-alpha.6\n" "Language: nl\n" -"X-Poedit-SearchPath-0: includes\n" -"X-Poedit-SearchPath-1: templates\n" -"X-Poedit-SearchPath-2: .\n" +"Project-Id-Version: Plugins - Radio Station by netmix® – Manage and play your Show Schedule in WordPress! - Stable (latest release)\n" -#: includes/dj-on-air.php:90 includes/dj-on-air.php:664 -#: includes/playlist.php:22 includes/playlist.php:359 includes/playlist.php:546 -msgid "View Playlist" -msgstr "Bekijk afspeellijst" +#: includes/extra-strings.php:374 +msgid "New Override" +msgstr "" + +#: includes/extra-strings.php:373 +msgid "Existing Override" +msgstr "" + +#: includes/extra-strings.php:370 +msgid "Existing Show" +msgstr "" + +#: includes/extra-strings.php:366 +msgid "Recurring Override" +msgstr "" + +#: includes/extra-strings.php:364 +msgid "Show Shift" +msgstr "" + +#: includes/extra-strings.php:353 +msgid "Selected Shift" +msgstr "" + +#: includes/extra-strings.php:319 +msgid "Shows Affected" +msgstr "" + +#: includes/extra-strings.php:317 +msgid "Cannot list recurring times without a valid end time." +msgstr "" + +#: includes/extra-strings.php:316 +msgid "Cannot list recurring times without a valid start time." +msgstr "" + +#: includes/extra-strings.php:281 +msgid "Single Overrides" +msgstr "" + +#: includes/extra-strings.php:275 +msgid "Recurring Overrides" +msgstr "" + +#: includes/extra-strings.php:249 +msgid "Leave blank to use stream URL." +msgstr "" + +#: includes/extra-strings.php:61 +msgid "Back to Plugins page." +msgstr "" + +#: includes/extra-strings.php:60 +msgid "Radio Station Settings" +msgstr "Radio Station instellingen" + +#: includes/extra-strings.php:59 +msgid "Plugin reactivated successfully." +msgstr "" + +#: includes/extra-strings.php:58 +msgid "The previous version of the plugin has been backed up if you need to restore it." +msgstr "" + +#: includes/extra-strings.php:57 +msgid "The plugin has been updated, but could not be reactivated. Please reactivate it manually." +msgstr "" + +#: includes/extra-strings.php:56 +msgid "Package contents moved to plugin directory." +msgstr "" + +#: includes/extra-strings.php:55 +msgid "Package unzipped successfully." +msgstr "" + +#: includes/extra-strings.php:54 +msgid "Downloaded plugin package from:" +msgstr "" + +#: includes/extra-strings.php:53 +msgid "Error. Downloading the plugin package failed. Please try again." +msgstr "" + +#: includes/extra-strings.php:52 +msgid "Error. Could not connect to filesystem. Please install plugin update manually." +msgstr "" + +#: includes/extra-strings.php:51 +msgid "Update Plugin via Github" +msgstr "" + +#: includes/extra-strings.php:50 +msgid "Sorry, you are not allowed to update plugins for this site." +msgstr "" + +#: includes/extra-strings.php:49 +msgid "Development Version" +msgstr "" + +#: includes/extra-strings.php:48 +msgid "Update to Radio Station" +msgstr "" + +#: includes/extra-strings.php:45 +msgid "Install Radio Station" +msgstr "" + +#: includes/extra-strings.php:44 +msgid "Activate Radio Station" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:223 +msgid "Link host names to author pages" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:215 +msgid "Display Show host names." +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:186 +msgid "Display shift info for this show" +msgstr "" + +#: widgets/class-radio-player-widget.php:239 +msgid "Advanced options available in Pro." +msgstr "" + +#: widgets/class-radio-player-widget.php:237 +msgid "[Pro] Advanced Options" +msgstr "[Pro] geavanceerde opties" + +#: widgets/class-radio-player-widget.php:230 +msgid "Color options available in Pro." +msgstr "" + +#: widgets/class-radio-player-widget.php:228 +msgid "[Pro] Color Options" +msgstr "" + +#: widgets/class-radio-player-widget.php:209 +msgid "Buttons Style" +msgstr "" + +#: widgets/class-radio-player-widget.php:173 +msgid "Player Widget Layout" +msgstr "" + +#: widgets/class-radio-player-widget.php:168 +msgid "Player Styles" +msgstr "" + +#: widgets/class-radio-player-widget.php:162 +msgid "Popup Player button available in Pro." +msgstr "" + +#: widgets/class-radio-player-widget.php:156 +msgid "Use as the default Player instance." +msgstr "" + +#: widgets/class-radio-player-widget.php:116 +msgid "Default Player Script" +msgstr "" + +#: widgets/class-radio-player-widget.php:78 +msgid "Leave blank to use default stream URL." +msgstr "" + +#: widgets/class-radio-clock-widget.php:98 +msgid "Include timezone display?" +msgstr "" + +#: widgets/class-radio-clock-widget.php:79 +msgid "Include seconds display?" +msgstr "" + +#: widgets/class-radio-clock-widget.php:60 +msgid "Include Date Display?" +msgstr "" + +#: widgets/class-current-show-widget.php:244 +msgid "Display link to Show playlist." +msgstr "" + +#: widgets/class-current-show-widget.php:236 +msgid "Display Show description excerpt." +msgstr "" + +#: widgets/class-current-show-widget.php:228 +msgid "Link Host names to author pages." +msgstr "" + +#: widgets/class-current-show-widget.php:220 +msgid "Display Show Host names." +msgstr "" + +#: widgets/class-current-show-widget.php:205 +#: widgets/class-radio-clock-widget.php:85 +#: widgets/class-upcoming-shows-widget.php:200 +msgid "Time Format Display" +msgstr "" + +#: widgets/class-current-show-widget.php:191 +msgid "Display All Show Shifts (if more than current.)" +msgstr "" + +#: widgets/class-current-show-widget.php:183 +msgid "Display Show Shift Info" +msgstr "" + +#: widgets/class-current-show-widget.php:174 +#: widgets/class-upcoming-shows-widget.php:177 +msgid "Show Avatar Width override. 0 or empty for none." +msgstr "" + +#: widgets/class-current-show-widget.php:161 +#: widgets/class-upcoming-shows-widget.php:164 +msgid "Avatar Image Size" +msgstr "" + +#: widgets/class-current-show-widget.php:127 +#: widgets/class-upcoming-shows-widget.php:130 +msgid "Link Show title to Show page." +msgstr "" + +#: widgets/class-current-show-widget.php:110 +msgid "Empty for default. 0 for none." +msgstr "" + +#: widgets/class-current-playlist-widget.php:113 +msgid "No Playlist Text" +msgstr "" + +#: widgets/class-current-playlist-widget.php:98 +msgid "Display Playlist Title?" +msgstr "" + +#: widgets/class-current-playlist-widget.php:80 +#: widgets/class-current-show-widget.php:100 +#: widgets/class-radio-player-widget.php:163 +#: widgets/class-radio-player-widget.php:231 +#: widgets/class-radio-player-widget.php:241 +#: widgets/class-upcoming-shows-widget.php:103 +msgid "Upgrade to Pro" +msgstr "" + +#: widgets/class-current-playlist-widget.php:79 +#: widgets/class-current-show-widget.php:99 +#: widgets/class-upcoming-shows-widget.php:102 +msgid "Show changeover reloading available in Pro." +msgstr "" + +#: radio-station-admin.php:820 radio-station-admin.php:896 +msgid "Read full update details." +msgstr "" + +#: options.php:249 +msgid "Note that you can override these defaults in specific Player Widgets." +msgstr "" + +#: options.php:248 +msgid "Player Defaults Note" +msgstr "" + +#: options.php:21 +msgid "Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds." +msgstr "" + +#: includes/extra-strings.php:73 includes/support-functions.php:1137 +msgid "Full Size" +msgstr "" + +#: includes/extra-strings.php:72 includes/support-functions.php:1136 +msgid "Large" +msgstr "" + +#: includes/extra-strings.php:71 includes/support-functions.php:1135 +msgid "Medium" +msgstr "" + +#: includes/extra-strings.php:70 includes/support-functions.php:1134 +msgid "Thumbnail" +msgstr "" + +#: includes/shortcodes.php:1629 +msgid "No Shows were found with Languages assigned." +msgstr "" + +#: includes/shortcodes.php:1161 +msgid "No Shows were found with Genres assigned." +msgstr "Er zijn geen programma's gevonden met toegewezen genres." + +#: includes/post-types-admin.php:5788 +msgid "Latest Show" +msgstr "Meest recente programma" + +#: includes/post-types-admin.php:5786 +msgid "Assign to Show Shift" +msgstr "" + +#: includes/post-types-admin.php:5708 +msgid "Find Out More..." +msgstr "" + +#: includes/post-types-admin.php:5706 +msgid "You can assign Playlists to Show Episodes in Radio Station Pro." +msgstr "" + +#: includes/post-types-admin.php:5664 includes/post-types-admin.php:6184 +msgid "Assign to Show" +msgstr "" + +#: includes/post-types-admin.php:3015 +msgid "Sync Languages?" +msgstr "" + +#: includes/post-types-admin.php:2995 +msgid "Sync Genres?" +msgstr "" + +#: includes/post-types-admin.php:1711 +msgid "Find out more..." +msgstr "" + +#: includes/post-types-admin.php:1710 +msgid "Go Pro!" +msgstr "" + +#: includes/post-types-admin.php:1709 +msgid "These are then automatically listed on your Show page in an Episodes tab." +msgstr "" + +#: includes/post-types-admin.php:1708 +msgid "Radio Station Pro includes an Episodes post type for adding past episodes." +msgstr "" + +#: includes/extra-strings.php:714 +msgid "Upcoming Shows Styling" +msgstr "" + +#: includes/extra-strings.php:713 +msgid "Radio Upcoming Shows" +msgstr "Radio aankomende programma's" + +#: includes/extra-strings.php:712 +msgid "Schedule Typography" +msgstr "" + +#: includes/extra-strings.php:711 +msgid "Display Show Images" +msgstr "" + +#: includes/extra-strings.php:710 +msgid "Radio Schedule" +msgstr "" + +#: includes/extra-strings.php:709 +msgid "Show Text Typography" +msgstr "" + +#: includes/extra-strings.php:708 +msgid "Station Text Typography" +msgstr "" + +#: includes/extra-strings.php:707 +msgid "Player Alignment" +msgstr "" + +#: includes/extra-strings.php:706 +msgid "Current Show Styling" +msgstr "" + +#: includes/extra-strings.php:705 +msgid "Radio Current Show" +msgstr "Radio huidige programma" + +#: includes/extra-strings.php:704 +msgid "Playlist Info Typography" +msgstr "" + +#: includes/extra-strings.php:703 +msgid "Playlist Label Typography" +msgstr "" + +#: includes/extra-strings.php:702 +msgid "Text Alignment" +msgstr "" + +#: includes/extra-strings.php:701 +msgid "Playlist Styling" +msgstr "" + +#: includes/extra-strings.php:700 +msgid "Track Display Options" +msgstr "" + +#: includes/extra-strings.php:699 +msgid "Radio Current Playlist" +msgstr "" + +#: includes/extra-strings.php:698 +msgid "Clock Alignment" +msgstr "" + +#: includes/extra-strings.php:697 +msgid "Record Meta Typography" +msgstr "" + +#: includes/extra-strings.php:696 +msgid "Record Description Typography" +msgstr "" + +#: includes/extra-strings.php:695 +msgid "Record Title Typography" +msgstr "" + +#: includes/extra-strings.php:694 +msgid "Center" +msgstr "" + +#: includes/extra-strings.php:692 +msgid "Archive Alignment" +msgstr "" + +#: includes/extra-strings.php:691 +msgid "Clock Styling" +msgstr "" + +#: includes/extra-strings.php:690 +msgid "Publish Date" +msgstr "" + +#: includes/extra-strings.php:689 +msgid "Archive Record Query" +msgstr "" + +#: includes/extra-strings.php:688 +msgid "Radio Archive" +msgstr "Radio archief" + +#: includes/extra-strings.php:687 widgets/class-upcoming-shows-widget.php:110 +msgid "No Upcoming Shows Text" +msgstr "" + +#: includes/extra-strings.php:686 +msgid "Upcoming Shows to Display" +msgstr "" + +#: includes/extra-strings.php:685 +msgid "Upcoming Shows module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:684 +msgid "Upcoming Shows" +msgstr "Aankomende programma's" + +#: includes/extra-strings.php:683 +msgid "Current Show Border Color" +msgstr "" + +#: includes/extra-strings.php:682 +msgid "Active Header Background Color" +msgstr "" + +#: includes/extra-strings.php:681 +msgid "Even Headers Background Color" +msgstr "" + +#: includes/extra-strings.php:680 +msgid "Odd Headers Background Color" +msgstr "" + +#: includes/extra-strings.php:679 +msgid "Schedule Colors" +msgstr "" + +#: includes/extra-strings.php:678 +msgid "Show Times Typography" +msgstr "" + +#: includes/extra-strings.php:677 +msgid "Show Times Text Color" +msgstr "" + +#: includes/extra-strings.php:676 +msgid "Show Title Text Color" +msgstr "" + +#: includes/extra-strings.php:675 +msgid "Subheaders Typography" +msgstr "" + +#: includes/extra-strings.php:674 +msgid "Subheaders Text Color" +msgstr "" + +#: includes/extra-strings.php:673 +msgid "Headers Typography" +msgstr "" + +#: includes/extra-strings.php:672 +msgid "Headers Text Color" +msgstr "" + +#: includes/extra-strings.php:671 +msgid "Display Encore" +msgstr "" + +#: includes/extra-strings.php:670 +msgid "Display Show Genres" +msgstr "" + +#: includes/extra-strings.php:669 +msgid "Link to Hosts" +msgstr "" + +#: includes/extra-strings.php:668 +msgid "Display Show Description" +msgstr "" + +#: includes/extra-strings.php:667 +msgid "Link to Shows" +msgstr "Link naar programma's" + +#: includes/extra-strings.php:666 +msgid "Sunday" +msgstr "zondag" + +#: includes/extra-strings.php:665 +msgid "Saturday" +msgstr "zaterdag" + +#: includes/extra-strings.php:664 +msgid "Friday" +msgstr "vrijdag" + +#: includes/extra-strings.php:663 +msgid "Thursday" +msgstr "donderdag" + +#: includes/extra-strings.php:662 +msgid "Wednesday" +msgstr "woensdag" + +#: includes/extra-strings.php:661 +msgid "Tuesday" +msgstr "dinsdag" + +#: includes/extra-strings.php:660 +msgid "Monday" +msgstr "maandag" + +#: includes/extra-strings.php:659 +msgid "Today" +msgstr "vandaag" + +#: includes/extra-strings.php:658 +msgid "WP Start of Week" +msgstr "Start van de week" + +#: includes/extra-strings.php:657 +msgid "Schedule Start Day" +msgstr "" + +#: includes/extra-strings.php:656 +msgid "Time Display Options" +msgstr "" + +#: includes/extra-strings.php:655 +msgid "Display Genre Highlighter" +msgstr "" + +#: includes/extra-strings.php:654 +msgid "No Time Header" +msgstr "" + +#: includes/extra-strings.php:653 +msgid "Display Radio Timezone" +msgstr "" + +#: includes/extra-strings.php:652 +msgid "Display Radio Clock" +msgstr "" + +#: includes/extra-strings.php:651 +msgid "Radio Time Header" +msgstr "" + +#: includes/extra-strings.php:650 +msgid "Header Display Options" +msgstr "" + +#: includes/extra-strings.php:649 +msgid "[Calendar View] Number of past weeks." +msgstr "" + +#: includes/extra-strings.php:648 +msgid "Previous Weeks" +msgstr "" + +#: includes/extra-strings.php:647 +msgid "[Calendar View] Number of future weeks." +msgstr "" + +#: includes/extra-strings.php:646 +msgid "Calendar Weeks" +msgstr "Kalender weken" + +#: includes/extra-strings.php:645 +msgid "[Grid View] Line up Shows by times." +msgstr "" + +#: includes/extra-strings.php:644 +msgid "[Grid View] Column width in pixels." +msgstr "" + +#: includes/extra-strings.php:643 +msgid "Grid Width" +msgstr "" + +#: includes/extra-strings.php:642 +msgid "[Tabbed View] Display only current and upcoming shows." +msgstr "" + +#: includes/extra-strings.php:641 +msgid "Hide Past Shows" +msgstr "" + +#: includes/extra-strings.php:639 +msgid "left" +msgstr "" + +#: includes/extra-strings.php:638 +msgid "[Tabbed View] Show avatar relative to Show text." +msgstr "" + +#: includes/extra-strings.php:637 +msgid "Image Position" +msgstr "" + +#: includes/extra-strings.php:636 +msgid "Default View" +msgstr "" + +#: includes/extra-strings.php:633 +msgid "Ctrl-click to select multiple views." +msgstr "" + +#: includes/extra-strings.php:632 +msgid "Schedule View" +msgstr "" + +#: includes/extra-strings.php:631 +msgid "Schedule Display Options" +msgstr "" + +#: includes/extra-strings.php:630 +msgid "Master Schedule module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:629 +msgid "Master Schedule" +msgstr "" + +#: includes/extra-strings.php:628 +msgid "Song Track Typography" +msgstr "" + +#: includes/extra-strings.php:627 +msgid "Song Track Text Color" +msgstr "" + +#: includes/extra-strings.php:626 +msgid "Show Typography" +msgstr "" + +#: includes/extra-strings.php:625 +msgid "Show Text Color" +msgstr "" + +#: includes/extra-strings.php:624 +msgid "Station Typography" +msgstr "" + +#: includes/extra-strings.php:623 +msgid "Station Text Color" +msgstr "" + +#: includes/extra-strings.php:622 +msgid "Player Typography" +msgstr "" + +#: includes/extra-strings.php:621 +msgid "Slider Thumb Color" +msgstr "" + +#: includes/extra-strings.php:620 +msgid "Slider Track Color" +msgstr "" + +#: includes/extra-strings.php:619 +msgid "Buttons Highlight Color" +msgstr "" + +#: includes/extra-strings.php:618 +msgid "Playing Highlight Color" +msgstr "" + +#: includes/extra-strings.php:613 +msgid "Player Buttons" +msgstr "" + +#: includes/extra-strings.php:610 +msgid "Player Theme" +msgstr "" + +#: includes/extra-strings.php:609 +msgid "Horizontal (Inline" +msgstr "" + +#: includes/extra-strings.php:608 +msgid "Vertical (Stacked" +msgstr "" + +#: includes/extra-strings.php:607 +msgid "Player Layout" +msgstr "" + +#: includes/extra-strings.php:606 +msgid "Player Design" +msgstr "" + +#: includes/extra-strings.php:605 +msgid "Defaults to Stream URL." +msgstr "" + +#: includes/extra-strings.php:604 +msgid "Metadata Source URL" +msgstr "" + +#: includes/extra-strings.php:603 +msgid "Make this the default player on this page." +msgstr "" + +#: includes/extra-strings.php:602 +msgid "Use as Default Player" +msgstr "" + +#: includes/extra-strings.php:601 +msgid "Maximize Button" +msgstr "" + +#: includes/extra-strings.php:600 +msgid "Mute Button" +msgstr "" + +#: includes/extra-strings.php:599 +msgid "Up and Down Buttons" +msgstr "" + +#: includes/extra-strings.php:596 +msgid "Initial Volume" +msgstr "" + +#: includes/extra-strings.php:591 widgets/class-radio-player-widget.php:111 +msgid "Player Options" +msgstr "" + +#: includes/extra-strings.php:590 +msgid "Do Not Display Station Image" +msgstr "" + +#: includes/extra-strings.php:588 +msgid "Player Station Image" +msgstr "" + +#: includes/extra-strings.php:587 widgets/class-current-playlist-widget.php:116 +#: widgets/class-radio-player-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:113 +msgid "Empty for default, 0 for none." +msgstr "" + +#: includes/extra-strings.php:586 +msgid "Player Title Text" +msgstr "" + +#: includes/extra-strings.php:585 +msgid "Leave blank to use default stream." +msgstr "" + +#: includes/extra-strings.php:584 +msgid "Stream URL" +msgstr "" + +#: includes/extra-strings.php:583 widgets/class-radio-player-widget.php:62 +msgid "Player Content" +msgstr "" + +#: includes/extra-strings.php:582 +msgid "Radio Station Audio stream player module." +msgstr "" + +#: includes/extra-strings.php:581 +msgid "Radio Player" +msgstr "Radio speler" + +#: includes/extra-strings.php:580 +msgid "Show Description Typography" +msgstr "" + +#: includes/extra-strings.php:579 +msgid "Show Description Text Color" +msgstr "" + +#: includes/extra-strings.php:578 +msgid "Show Title Typography" +msgstr "" + +#: includes/extra-strings.php:577 +msgid "Show Title Color" +msgstr "" + +#: includes/extra-strings.php:576 +msgid "Display if Encore Airing" +msgstr "" + +#: includes/extra-strings.php:575 +msgid "Display Show Playlist" +msgstr "" + +#: includes/extra-strings.php:574 +msgid "Show Description Excerpt" +msgstr "" + +#: includes/extra-strings.php:573 +msgid "Display Show Hosts" +msgstr "" + +#: includes/extra-strings.php:572 +msgid "Display All Show Times" +msgstr "" + +#: includes/extra-strings.php:571 +msgid "Display Show Time" +msgstr "" + +#: includes/extra-strings.php:570 +msgid "Image Width Override" +msgstr "" + +#: includes/extra-strings.php:569 +msgid "Image Size" +msgstr "" + +#: includes/extra-strings.php:568 +msgid "Display Show Image" +msgstr "" + +#: includes/extra-strings.php:567 +msgid "Below Image" +msgstr "" + +#: includes/extra-strings.php:566 +msgid "Right of Image" +msgstr "" + +#: includes/extra-strings.php:565 +msgid "Left of Image" +msgstr "" + +#: includes/extra-strings.php:564 +msgid "Above Image" +msgstr "" + +#: includes/extra-strings.php:563 +msgid "Title Position" +msgstr "" + +#: includes/extra-strings.php:562 +msgid "Link to Show Page" +msgstr "" + +#: includes/extra-strings.php:561 +msgid "Show Display Options" +msgstr "" + +#: includes/extra-strings.php:560 widgets/class-current-show-widget.php:107 +msgid "No Current Show Text" +msgstr "" + +#: includes/extra-strings.php:559 +msgid "Current Show module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:558 +msgid "Current Show" +msgstr "Huidige programma" + +#: includes/extra-strings.php:557 +msgid "Countdown Typography" +msgstr "" + +#: includes/extra-strings.php:556 +msgid "Countdown Text Color" +msgstr "" + +#: includes/extra-strings.php:555 +msgid "Playlist Title Typography" +msgstr "" + +#: includes/extra-strings.php:554 +msgid "Playlist Title Text Color" +msgstr "" + +#: includes/extra-strings.php:553 +msgid "Track Info Typography" +msgstr "" + +#: includes/extra-strings.php:552 +msgid "Track Info Text Color" +msgstr "" + +#: includes/extra-strings.php:551 +msgid "Display Track Comments" +msgstr "" + +#: includes/extra-strings.php:550 +msgid "Display Record Label" +msgstr "" + +#: includes/extra-strings.php:549 +msgid "Display Album" +msgstr "" + +#: includes/extra-strings.php:548 +msgid "Display Artist" +msgstr "" + +#: includes/extra-strings.php:547 +msgid "Display Song Title" +msgstr "" + +#: includes/extra-strings.php:546 +msgid "Show Time Display Options" +msgstr "" + +#: includes/extra-strings.php:545 +msgid "Remaining Time Countdown" +msgstr "" + +#: includes/extra-strings.php:544 +msgid "Link to Playlist Page" +msgstr "" + +#: includes/extra-strings.php:543 +msgid "Display Playlist Title" +msgstr "" + +#: includes/extra-strings.php:542 +msgid "Extra Display Options" +msgstr "" + +#: includes/extra-strings.php:541 +msgid "Blank for default. 0 for none." +msgstr "" + +#: includes/extra-strings.php:540 +msgid "No Current Playlist Text" +msgstr "" + +#: includes/extra-strings.php:539 +msgid "Reload at Show changeovers." +msgstr "" + +#: includes/extra-strings.php:538 +msgid "Dynamic Reloading" +msgstr "" + +#: includes/extra-strings.php:537 +msgid "To bypass page caching." +msgstr "" + +#: includes/extra-strings.php:536 +msgid "AJAX Loading" +msgstr "" + +#: includes/extra-strings.php:535 +msgid "Loading Options" +msgstr "" + +#: includes/extra-strings.php:534 +msgid "Current Playlist module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:533 +msgid "Current Playlist" +msgstr "" + +#: includes/extra-strings.php:532 +msgid "Timezone Switcher Typography" +msgstr "" + +#: includes/extra-strings.php:531 +msgid "Timezone Switcher Text Color" +msgstr "" + +#: includes/extra-strings.php:530 +msgid "Times Typography" +msgstr "" + +#: includes/extra-strings.php:529 +msgid "Times Text Color" +msgstr "" + +#: includes/extra-strings.php:528 +msgid "Label Typography" +msgstr "" + +#: includes/extra-strings.php:527 +msgid "Label Text Color" +msgstr "" + +#: includes/extra-strings.php:526 +msgid "Display Seconds?" +msgstr "" + +#: includes/extra-strings.php:525 +msgid "Display Timezone?" +msgstr "" + +#: includes/extra-strings.php:524 widgets/class-radio-clock-widget.php:66 +msgid "Month Display Format" +msgstr "" + +#: includes/extra-strings.php:523 +msgid "Display Date?" +msgstr "" + +#: includes/extra-strings.php:521 widgets/class-radio-clock-widget.php:47 +msgid "Day Display Format" +msgstr "" + +#: includes/extra-strings.php:520 +msgid "Radio Clock Display Options" +msgstr "" + +#: includes/extra-strings.php:519 +msgid "Radio Station Clock time display." +msgstr "" + +#: includes/extra-strings.php:517 +msgid "Meta Typography" +msgstr "" + +#: includes/extra-strings.php:516 +msgid "Meta Text Color" +msgstr "" + +#: includes/extra-strings.php:515 +msgid "Description Typography" +msgstr "" + +#: includes/extra-strings.php:514 +msgid "Description Text Color" +msgstr "" + +#: includes/extra-strings.php:513 +msgid "Title Typography" +msgstr "" + +#: includes/extra-strings.php:512 +msgid "Title Text Color" +msgstr "" + +#: includes/extra-strings.php:511 +msgid "Typography" +msgstr "" + +#: includes/extra-strings.php:510 +msgid "Alignment" +msgstr "" + +#: includes/extra-strings.php:509 +msgid "Container Styles" +msgstr "" + +#: includes/extra-strings.php:508 +msgid "Style" +msgstr "" + +#: includes/extra-strings.php:507 +msgid "This setting is for Overrides only." +msgstr "" + +#: includes/extra-strings.php:506 +msgid "Display Override Dates?" +msgstr "" + +#: includes/extra-strings.php:505 +msgid "This setting is for Shows only." +msgstr "" + +#: includes/extra-strings.php:504 +msgid "Only Shows with Shifts?" +msgstr "" + +#: includes/extra-strings.php:503 +msgid "This setting is for Shows and Overrides." +msgstr "" + +#: includes/extra-strings.php:502 +msgid "Display Image?" +msgstr "" + +#: includes/extra-strings.php:498 +msgid "View Default" +msgstr "" + +#: includes/extra-strings.php:497 +msgid "Description Display Format" +msgstr "" + +#: includes/extra-strings.php:494 +msgid "Time Display Format" +msgstr "" + +#: includes/extra-strings.php:493 +msgid "Archive Record Display" +msgstr "" + +#: includes/extra-strings.php:492 +msgid "Descending" +msgstr "" + +#: includes/extra-strings.php:491 +msgid "Ascending" +msgstr "" + +#: includes/extra-strings.php:490 +msgid "Order" +msgstr "Volgorde" + +#: includes/extra-strings.php:489 +msgid "Modified Date" +msgstr "Datum van wijziging" + +#: includes/extra-strings.php:488 +msgid "Published Date" +msgstr "Publicatiedatum" + +#: includes/extra-strings.php:486 +msgid "Order By" +msgstr "Sorteer op" + +#: includes/extra-strings.php:485 +msgid "Archive Query" +msgstr "" + +#: includes/extra-strings.php:484 +msgid "Hide if Empty?" +msgstr "" + +#: includes/extra-strings.php:481 +msgid "Display Pagination?" +msgstr "" + +#: includes/extra-strings.php:480 +msgid "Use 0 for all records." +msgstr "" + +#: includes/extra-strings.php:479 +msgid "Records Per Page" +msgstr "" + +#: includes/extra-strings.php:477 +msgid "Archive View" +msgstr "" + +#: includes/extra-strings.php:476 +msgid "Shows by Language" +msgstr "" + +#: includes/extra-strings.php:475 +msgid "Shows by Genre" +msgstr "Programma per genre" + +#: includes/extra-strings.php:472 +msgid "Archive Type" +msgstr "" + +#: includes/extra-strings.php:471 +msgid "Archive List Details" +msgstr "" + +#: includes/extra-strings.php:469 +msgid "Archive list for Radio Station record types." +msgstr "" + +#: includes/extra-strings.php:468 +msgid "Archive List" +msgstr "" + +#: includes/extra-strings.php:441 +msgid "No Shows scheduled." +msgstr "Geen programma's gepland." + +#: includes/extra-strings.php:437 +msgid "No scheduled Shows found for this date." +msgstr "" + +#: includes/extra-strings.php:429 +msgid "Automatic Changeover Reloading" +msgstr "" + +#: includes/extra-strings.php:358 +msgid "Selected Override Time" +msgstr "" + +#: includes/extra-strings.php:315 +msgid "Error! No saved recurrences were found." +msgstr "" + +#: includes/extra-strings.php:314 +msgid "Error! No matching recurrence found." +msgstr "" + +#: includes/extra-strings.php:313 +msgid "Error! Override not found." +msgstr "" + +#: includes/extra-strings.php:312 +msgid "Warning! Recurring conflicts detected." +msgstr "" + +#: includes/extra-strings.php:308 +msgid "Preview Recurrences" +msgstr "" + +#: includes/extra-strings.php:307 +msgid "Number of Weeks" +msgstr "Aantal weken" + +#: includes/extra-strings.php:306 +msgid "Extra Days" +msgstr "Extra dagen" + +#: includes/extra-strings.php:305 +msgid "Are you sure you want to clear all Recurrences?" +msgstr "" + +#: includes/extra-strings.php:304 +msgid "Are you sure you want to remove this Recurrence?" +msgstr "" + +#: includes/extra-strings.php:303 +msgid "Remove Recurrence" +msgstr "" + +#: includes/extra-strings.php:302 +msgid "Duplicate Recurrence" +msgstr "" + +#: includes/extra-strings.php:301 +msgid "Preview Recurrence Dates" +msgstr "" + +#: includes/extra-strings.php:300 +msgid "List Recurrence Dates" +msgstr "" + +#: includes/extra-strings.php:299 +msgid "Cutoff Date" +msgstr "" + +#: includes/extra-strings.php:298 +msgid "Last" +msgstr "Laatste" + +#: includes/extra-strings.php:297 +msgid "Fifth" +msgstr "vijfde" + +#: includes/extra-strings.php:296 +msgid "Fourth" +msgstr "vierde" + +#: includes/extra-strings.php:295 +msgid "Third" +msgstr "derde" + +#: includes/extra-strings.php:293 +msgid "First" +msgstr "eerste" + +#: includes/extra-strings.php:292 +msgid "On the" +msgstr "Op de" + +#: includes/extra-strings.php:291 +msgid "Weeks" +msgstr "" + +#: includes/extra-strings.php:290 +msgid "Every" +msgstr "" + +#: includes/extra-strings.php:289 +msgid "By Week Intervals" +msgstr "" + +#: includes/extra-strings.php:288 +msgid "Once Every Month" +msgstr "" + +#: includes/extra-strings.php:287 +msgid "Repeats" +msgstr "" + +#: includes/extra-strings.php:280 +msgid "Recurrences Saved." +msgstr "" + +#: includes/extra-strings.php:279 +msgid "Saving Recurrences..." +msgstr "" + +#: includes/extra-strings.php:278 +msgid "Add Recurrence" +msgstr "" + +#: includes/extra-strings.php:277 +msgid "Save Recurrences" +msgstr "" + +#: includes/extra-strings.php:276 +msgid "Clear Recurrences" +msgstr "" + +#: includes/extra-strings.php:261 +msgid "%s Phone" +msgstr "%s Telefoon" + +#: includes/extra-strings.php:252 +msgid "Popup Player" +msgstr "" + +#: includes/extra-strings.php:251 +msgid "Popup Player to separate window." +msgstr "" + +#: includes/extra-strings.php:250 +msgid "Custom Theme" +msgstr "" + +#: includes/extra-strings.php:248 +msgid "Now Playing Track Display" +msgstr "" + +#: includes/extra-strings.php:247 +msgid "Current Show Display" +msgstr "" + +#: includes/extra-strings.php:243 +msgid "Advanced Options" +msgstr "" + +#: includes/extra-strings.php:242 +msgid "Track Thumb Color" +msgstr "" + +#: includes/extra-strings.php:241 +msgid "Track Color" +msgstr "" + +#: includes/extra-strings.php:240 +msgid "Buttons Color" +msgstr "" + +#: includes/extra-strings.php:239 +msgid "Playing Color" +msgstr "" + +#: includes/extra-strings.php:238 +msgid "Player Background Color" +msgstr "" + +#: includes/extra-strings.php:237 +msgid "Player Text Color" +msgstr "" + +#: includes/extra-strings.php:236 +msgid "Color Options" +msgstr "" + +#: includes/extra-strings.php:175 +msgid "Text Color" +msgstr "" + +#: includes/extra-strings.php:174 +msgid "Border Color" +msgstr "" + +#: includes/extra-strings.php:173 +msgid "Background Color" +msgstr "" + +#: includes/extra-strings.php:138 +msgid "Not Assigned" +msgstr "" + +#: includes/extra-strings.php:137 +msgid "Episode for Show" +msgstr "Aflevering voor programma" + +#: includes/extra-strings.php:135 +msgid "Episode Show" +msgstr "Aflevering programma" + +#: includes/extra-strings.php:133 +msgid "of" +msgstr "van" + +#: includes/extra-strings.php:132 +msgid "Number" +msgstr "Aantal" + +#: includes/extra-strings.php:131 +msgid "Warning! This playlist is assigned to more than one episode." +msgstr "" + +#: includes/extra-strings.php:130 includes/post-types-admin.php:5798 +msgid "No Show selected." +msgstr "" + +#: includes/extra-strings.php:129 +msgid "No Show Episodes found." +msgstr "" + +#: includes/extra-strings.php:128 +msgid "No Episode" +msgstr "" + +#: includes/extra-strings.php:127 +msgid "Assign to Show Episode" +msgstr "" + +#: includes/extra-strings.php:123 +msgid "Set Episode Number to one above current highest?" +msgstr "" + +#: includes/extra-strings.php:122 +msgid "The Playlist data for this Episode." +msgstr "" + +#: includes/extra-strings.php:119 +msgid "Linked Playlist" +msgstr "" + +#: includes/extra-strings.php:115 +msgid "The time that the Episode aired" +msgstr "" + +#: includes/extra-strings.php:113 +msgid "Date that the Episode aired." +msgstr "" + +#: includes/extra-strings.php:111 +msgid "The Episode number in the Show series." +msgstr "" + +#: includes/extra-strings.php:110 +msgid "Auto" +msgstr "" + +#: includes/extra-strings.php:109 +msgid "Click to auto-number to highest episode plus one." +msgstr "" + +#: includes/extra-strings.php:108 +msgid "Highest" +msgstr "" + +#: includes/extra-strings.php:107 +msgid "This Episode number is in use!" +msgstr "" + +#: includes/extra-strings.php:106 +msgid "This Episode number is unique." +msgstr "" + +#: includes/extra-strings.php:104 +msgid "Opens the Media Library for uploading." +msgstr "" + +#: includes/extra-strings.php:103 +msgid "Enter the absolute URL to the media source." +msgstr "" + +#: includes/extra-strings.php:102 +msgid "Use an existing URL or add to Media Library." +msgstr "" + +#: includes/extra-strings.php:98 +msgid "The Show this is an Episode for." +msgstr "" + +#: includes/extra-strings.php:86 +msgid "Click here to add or edit Show Episodes." +msgstr "" + +#: includes/extra-strings.php:85 +msgid "You can add past Show Episodes for automatic display on Show pages." +msgstr "" + +#: includes/extra-strings.php:81 +msgid "PHP" +msgstr "" + +#: includes/extra-strings.php:80 +msgid "\"%1$s\" require \"%2$s\" version %3$s or greater." +msgstr "" + +#: includes/extra-strings.php:79 +msgid "Elementor" +msgstr "" + +#: includes/extra-strings.php:78 +msgid "Radio Station Elementor Widgets" +msgstr "" + +#: includes/extra-strings.php:77 +msgid "\"%1$s\" require \"%2$s\" to be installed and activated." +msgstr "" + +#: includes/extra-strings.php:42 options.php:460 +msgid "Add button to open Popup Player in separate window." +msgstr "" + +#: includes/extra-strings.php:41 options.php:457 +msgid "Popup Player Button" +msgstr "" + +#: includes/extra-strings.php:36 options.php:639 +msgid "How to animate the currently playing track display." +msgstr "" + +#: includes/extra-strings.php:35 options.php:635 +msgid "Back and Forth" +msgstr "" + +#: includes/extra-strings.php:34 options.php:634 +msgid "Right to Left Ticker" +msgstr "" + +#: includes/extra-strings.php:33 options.php:633 +msgid "Left to Right Ticker" +msgstr "" + +#: includes/extra-strings.php:32 options.php:632 +msgid "No Animation" +msgstr "" + +#: includes/extra-strings.php:31 options.php:629 +msgid "Track Animation" +msgstr "" + +#: includes/extra-strings.php:22 +msgid "Combine team members (eg. hosts, producers into a single display tab." +msgstr "" + +#: includes/extra-strings.php:21 +msgid "Combined Grid" +msgstr "" + +#: includes/extra-strings.php:13 +msgid "Magenta" +msgstr "" + +#: includes/extra-strings.php:12 +msgid "Purple" +msgstr "" + +#: includes/extra-strings.php:11 +msgid "Blue" +msgstr "" + +#: includes/extra-strings.php:10 +msgid "Light Blue" +msgstr "" + +#: includes/extra-strings.php:9 +msgid "Cyan" +msgstr "" + +#: includes/extra-strings.php:8 +msgid "Green" +msgstr "" + +#: includes/extra-strings.php:7 +msgid "Light Green" +msgstr "" + +#: includes/extra-strings.php:6 +msgid "Yellow" +msgstr "" + +#: includes/extra-strings.php:5 +msgid "Orange" +msgstr "" + +#: includes/extra-strings.php:4 +msgid "Red" +msgstr "" + +#: options.php:852 +msgid "Combine team members (eg. hosts, producers) into a single display tab." +msgstr "" + +#: includes/extra-strings.php:20 options.php:849 +msgid "Combined List" +msgstr "" + +#: includes/extra-strings.php:19 options.php:848 +msgid "Do Not Combine" +msgstr "" + +#: includes/extra-strings.php:18 options.php:845 +msgid "Combined Team Tab" +msgstr "" + +#: includes/extra-strings.php:393 +msgid "No Episodes were found to display." +msgstr "" + +#: includes/extra-strings.php:392 +msgid "No Producers were found to display." +msgstr "" + +#: includes/extra-strings.php:391 +msgid "No Hosts were found to display." +msgstr "" + +#: includes/extra-strings.php:328 +msgid "You have unsaved role changes." +msgstr "" + +#: includes/extra-strings.php:273 +msgid "Editors" +msgstr "" + +#: includes/extra-strings.php:272 +msgid "All" +msgstr "" + +#: includes/extra-strings.php:270 +msgid "Team" +msgstr "" + +#: includes/extra-strings.php:169 +msgid "Aired on" +msgstr "Uitgezonden op" + +#: includes/extra-strings.php:83 +msgid "Topics" +msgstr "Onderwerpen" + +#: includes/extra-strings.php:82 +msgid "Guests" +msgstr "Gasten" + +#: includes/extra-strings.php:74 +msgid "Important: %s continuous player bar is active." +msgstr "" + +#: includes/extra-strings.php:68 +msgid "Please visit your Plugins page to deactivate the incorrect one." +msgstr "" + +#: includes/extra-strings.php:67 +msgid "You have multiple conflicting versions of Radio Station Pro activated." +msgstr "" + +#: includes/extra-strings.php:65 +msgid "Account" +msgstr "" + +#: includes/extra-strings.php:46 +msgid "Your Radio Station plugin needs to be updated to work with %s." +msgstr "" + +#: includes/extra-strings.php:43 +msgid "Radio Station needs to be installed and activated to use %s." +msgstr "" + +#: options.php:1363 +msgid "Taxonomies" +msgstr "" + +#: options.php:1362 +msgid "Post Types" +msgstr "" + +#: options.php:1350 +msgid "Performance" +msgstr "" + +#: includes/extra-strings.php:17 options.php:1055 +msgid "Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: includes/extra-strings.php:15 options.php:1037 +msgid "Select the Page for displaying the Team archive list." +msgstr "" + +#: includes/extra-strings.php:14 options.php:1033 +msgid "Team Archive Page" +msgstr "" + +#: options.php:210 +msgid "Disable Transients" +msgstr "" + +#: loader.php:2553 +msgid "Details" +msgstr "" + +#: loader.php:2544 +msgid "Premium Feature." +msgstr "" + +#: includes/extra-strings.php:24 options.php:739 +msgid "Enable Grid View option for equalized time spacing and background imsges." +msgstr "" + +#: includes/extra-strings.php:23 options.php:736 +msgid "Time Spaced Grid" +msgstr "" + +#: includes/post-types.php:265 +msgid "Producer Profiles Archive" +msgstr "" + +#: includes/post-types.php:213 +msgid "Host Profiles Archive" +msgstr "" + +#: includes/post-types.php:160 +msgid "Schedule Overrides Archive" +msgstr "" + +#: includes/post-types.php:113 +msgid "Playlists Archive" +msgstr "" + +#: includes/post-types.php:63 +msgid "Shows Archive" +msgstr "Programma's archief" + +#. Author URI of the plugin +msgid "https://netmix.com/" +msgstr "https://netmix.com/" + +#. Plugin URI of the plugin +msgid "https://radiostation.pro/radio-station" +msgstr "https://radiostation.pro/radio-station" + +#: templates/single-playlist-content.php:83 +msgid "No played tracks found for this Playlist yet." +msgstr "" + +#: options.php:1280 +msgid "This is so a manager can edit the schedule without requiring full site administration role." +msgstr "" + +#: options.php:1269 +msgid "Any user with a Host or Producer role can create Playlists." +msgstr "" + +#: options.php:1268 +msgid "Playlist Permissions" +msgstr "" + +#: options.php:1259 +msgid "This means an Administrator or Show Editor must assign these users to the Show first." +msgstr "" + +#: options.php:1258 +msgid "By default, only Hosts and Producers that are assigned to a Show can edit that Show." +msgstr "" + +#: options.php:1257 +msgid "Show Editing Permissions" +msgstr "" + +#: options.php:608 +msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)" +msgstr "" + +#: options.php:558 +msgid "Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)" +msgstr "" + +#: options.php:351 +msgid "Which volume controls to display in the Player by default." +msgstr "" + +#: options.php:349 +msgid "Maximize Volume" +msgstr "" + +#: options.php:348 +msgid "Mute Volume Toggle" +msgstr "" + +#: options.php:347 +msgid "Volume Plus / Minus" +msgstr "" + +#: includes/extra-strings.php:597 options.php:343 +#: widgets/class-radio-player-widget.php:144 +msgid "Volume Controls" +msgstr "" + +#: options.php:304 +msgid "Enabled fallback audio scripts to try when the default Player script fails." +msgstr "" + +#: options.php:297 +msgid "Fallback Scripts" +msgstr "" + +#: options.php:287 +msgid "Default audio script to use for playback in the Player." +msgstr "" + +#: loader.php:2546 radio-station-admin.php:397 +msgid "Upgrade Now" +msgstr "" + +#: loader.php:1722 +msgid "Add Ons" +msgstr "" + +#: loader.php:1708 +msgid "Pro Details" +msgstr "" + +#: loader.php:1700 +msgid "Upgrade" +msgstr "" + +#: includes/extra-strings.php:419 +msgid "Social Icon Image URL" +msgstr "" + +#: includes/extra-strings.php:418 +msgid "Social URL Page Link" +msgstr "" + +#: includes/extra-strings.php:416 +msgid "Icon URL" +msgstr "" + +#: includes/extra-strings.php:415 +msgid "URL" +msgstr "" + +#: includes/extra-strings.php:410 +msgid "Custom" +msgstr "" + +#: includes/extra-strings.php:330 +msgid "role removed from user" +msgstr "" + +#: includes/extra-strings.php:329 +msgid "role added to user" +msgstr "" + +#: includes/extra-strings.php:327 +msgid "Update User Roles" +msgstr "" + +#: includes/extra-strings.php:76 +msgid "Adjust those settings via your Radio Station settings page instead." +msgstr "" + +#: includes/extra-strings.php:75 +msgid "This overrides the page fade in, load timeout and loader bar settings here." +msgstr "" + +#: includes/extra-strings.php:66 +msgid "Extending" +msgstr "" + +#: includes/extra-strings.php:69 +msgid "Radio Station Pro" +msgstr "" + +#: includes/extra-strings.php:40 +msgid "Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display." +msgstr "" + +#: includes/extra-strings.php:39 options.php:553 +msgid "Page Load Timeout" +msgstr "" + +#: includes/extra-strings.php:38 options.php:621 +msgid "Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location." +msgstr "" + +#: includes/extra-strings.php:37 options.php:617 +msgid "Metadata URL" +msgstr "" + +#: includes/extra-strings.php:30 +msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist" +msgstr "" + +#: includes/extra-strings.php:29 options.php:603 +msgid "Display Now Playing" +msgstr "" + +#: includes/extra-strings.php:28 options.php:595 +msgid "Display the Current Show in the Player Bar." +msgstr "" + +#: includes/extra-strings.php:27 options.php:590 +msgid "Display Current Show" +msgstr "" + +#: includes/extra-strings.php:26 options.php:504 +msgid "Set the height of the Sitewide Player Bar in pixels." +msgstr "" + +#: includes/extra-strings.php:25 options.php:500 +msgid "Player Bar Height" +msgstr "" + +#: includes/extra-strings.php:235 +msgid "Metadata Override URL" +msgstr "" + +#: includes/extra-strings.php:234 +msgid "Override: Current Playlist" +msgstr "" + +#: includes/extra-strings.php:233 +msgid "Override: Metadata URL" +msgstr "" + +#: includes/extra-strings.php:232 +msgid "Override: Metadata Off" +msgstr "" + +#: includes/extra-strings.php:231 +msgid "Override: Metadata On" +msgstr "" + +#: includes/extra-strings.php:230 +msgid "Default Plugin Setting" +msgstr "" + +#: includes/extra-strings.php:229 +msgid "Metadata Source" +msgstr "" + +#: includes/extra-strings.php:255 +msgid "Edit Your Producer Profile" +msgstr "" + +#: includes/extra-strings.php:253 +msgid "Edit Your Host Profile" +msgstr "" + +#: options.php:538 +msgid "Page Fade Time" +msgstr "" + +#: player/radio-player.php:414 +msgid "Show Logo Image" +msgstr "" + +#: player/radio-player.php:408 +msgid "Show Title" +msgstr "Toon titel" + +#: player/radio-player.php:384 +msgid "Max" +msgstr "" + +#: player/radio-player.php:381 +msgid "Volume Up" +msgstr "" + +#: includes/extra-strings.php:598 options.php:346 player/radio-player.php:376 +msgid "Volume Slider" +msgstr "" + +#: player/radio-player.php:371 +msgid "Volume Down" +msgstr "" + +#: player/radio-player.php:367 +msgid "Mute" +msgstr "" + +#: player/radio-player.php:357 +msgid "Play Radio Stream" +msgstr "Speel Livestream" + +#: player/radio-player.php:325 +msgid "Station Name" +msgstr "Station telefoonnummer" + +#: includes/class-radio-player-widget.php:176 +msgid "Use this as the default Player instance." +msgstr "" + +#: includes/class-radio-player-widget.php:160 +msgid "Layout Style for Widget Area" +msgstr "" + +#: includes/class-radio-player-widget.php:152 includes/extra-strings.php:616 +#: widgets/class-radio-player-widget.php:215 +msgid "Square" +msgstr "" + +#: includes/class-radio-player-widget.php:151 includes/extra-strings.php:615 +#: widgets/class-radio-player-widget.php:214 +msgid "Rounded" +msgstr "" + +#: includes/class-radio-player-widget.php:150 includes/extra-strings.php:614 +#: widgets/class-radio-player-widget.php:213 +msgid "Circular" +msgstr "" + +#: includes/class-radio-player-widget.php:140 +#: widgets/class-radio-player-widget.php:189 +msgid "Player Theme Style" +msgstr "" + +#: includes/class-radio-player-widget.php:121 +msgid "Layout Style for Widget Area (wide or tall)" +msgstr "" + +#: includes/class-radio-player-widget.php:114 +#: widgets/class-radio-player-widget.php:177 +msgid "Horizontal (Inline)" +msgstr "" + +#: includes/class-radio-player-widget.php:113 +#: widgets/class-radio-player-widget.php:176 +msgid "Vertical (Stacked)" +msgstr "" + +#: includes/class-radio-player-widget.php:104 +msgid "Player Script to load by default." +msgstr "" + +#: includes/class-radio-player-widget.php:86 +msgid "Whether to display Station Image in Player." +msgstr "" + +#: includes/class-radio-player-widget.php:79 +#: widgets/class-radio-player-widget.php:100 +msgid "Do Not Display Image" +msgstr "" + +#: includes/class-radio-player-widget.php:77 +#: includes/class-radio-player-widget.php:94 +#: includes/class-radio-player-widget.php:130 +#: includes/class-radio-player-widget.php:149 includes/extra-strings.php:244 +#: widgets/class-radio-player-widget.php:98 +#: widgets/class-radio-player-widget.php:119 +#: widgets/class-radio-player-widget.php:192 +#: widgets/class-radio-player-widget.php:212 +msgid "Plugin Setting" +msgstr "" + +#: includes/class-radio-player-widget.php:66 +#: widgets/class-radio-player-widget.php:84 +msgid "Player Station Text" +msgstr "" + +#: includes/class-radio-player-widget.php:57 +#: widgets/class-current-playlist-widget.php:60 +#: widgets/class-current-show-widget.php:79 +#: widgets/class-radio-clock-widget.php:40 +#: widgets/class-radio-player-widget.php:67 +#: widgets/class-upcoming-shows-widget.php:74 +msgid "Widget Title" +msgstr "" + +#: includes/class-radio-player-widget.php:47 +#: widgets/class-radio-player-widget.php:75 +msgid "Stream or File URL" +msgstr "" + +#: includes/class-radio-player-widget.php:18 +#: widgets/class-radio-player-widget.php:18 +msgid "(Radio Station) Stream Player" +msgstr "" + +#: includes/class-radio-player-widget.php:16 +#: widgets/class-radio-player-widget.php:16 +msgid "Radio Station Stream Player." +msgstr "" + +#: templates/single-show-content.php:769 +msgid "About the %s" +msgstr "Over het %s" + +#: templates/single-show-content.php:709 +msgid "Scheduled Dates" +msgstr "" + +#: options.php:1360 +msgid "Episode Pages" +msgstr "" + +#: options.php:1359 +msgid "Profile Pages" +msgstr "" + +#: options.php:1244 +msgid "Allow visitors to select their Timezone manually for Show time conversions." +msgstr "" + +#: options.php:1241 +msgid "User Timezone Switching" +msgstr "" + +#: options.php:1232 +msgid "Automatically display Show times converted into the visitor timezone, based on their browser setting." +msgstr "" + +#: options.php:1229 +msgid "Convert Show Times" +msgstr "" + +#: options.php:1116 +msgid "Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1104 +msgid "Select the Page for displaying the Language archive list." +msgstr "" + +#: options.php:1100 +msgid "Languages Archive Page" +msgstr "" + +#: options.php:920 +msgid "How to display extra sections below Episode description. In content tabs or standard layout down the page." +msgstr "" + +#: options.php:914 +msgid "Episode Content Layout" +msgstr "" + +#: options.php:905 +msgid "Where to position Episode info blocks relative to Episode Page content." +msgstr "" + +#: options.php:886 +msgid "How to display extra sections below Profile description. In content tabs or standard layout down the page." +msgstr "" + +#: options.php:880 +msgid "Profile Content Layout" +msgstr "" + +#: options.php:871 +msgid "Where to position Profile info blocks relative to Profile Page content." +msgstr "" + +#: options.php:835 +msgid "Number of Show Episodes per page on the Show page tab/display." +msgstr "" + +#: options.php:830 +msgid "Episodes per Page" +msgstr "" + +#: options.php:726 +msgid "Switcher Views available on automatic Master Schedule page." +msgstr "" + +#: includes/extra-strings.php:3 options.php:724 +msgid "Calendar View" +msgstr "" + +#: includes/extra-strings.php:2 options.php:723 +msgid "Grid View" +msgstr "" + +#: options.php:705 +msgid "Enable View Switching on the automatic Master Schedule page." +msgstr "" + +#: radio-station-admin.php:1547 +msgid "And due to our continued efforts we now have a community of over two thousand active stations!" +msgstr "" + +#: radio-station-admin.php:1402 +msgid "Thanks, already done." +msgstr "" + +#: includes/post-types-admin.php:5707 radio-station-admin.php:1397 +msgid "Go PRO" +msgstr "" + +#: radio-station-admin.php:1391 +msgid "Yes, I'm in!" +msgstr "" + +#: radio-station-admin.php:1376 +msgid "Sign up here to receive your exclusive launch discount code." +msgstr "" + +#: radio-station-admin.php:1374 +msgid "Remember," +msgstr "" + +#: radio-station-admin.php:1372 +msgid "The long anticipated moment has arrived. The doors are open to get PRO" +msgstr "" + +#: radio-station-admin.php:1370 +msgid "Radio Station PRO Launch is LIVE!" +msgstr "" + +#: radio-station-admin.php:1367 +msgid "Sign up to the exclusive launch list to receive your discount code when we go LIVE." +msgstr "" + +#: radio-station-admin.php:1366 radio-station-admin.php:1374 +msgid "we are offering 30% discount to existing Radio Station users!" +msgstr "" + +#: radio-station-admin.php:1366 +msgid "During the launch," +msgstr "" + +#: radio-station-admin.php:1365 radio-station-admin.php:1373 +msgid "Jam-packed with new features to \"level up\" your Station's online presence." +msgstr "" + +#: radio-station-admin.php:1364 +msgid "We are thrilled to announce the upcoming launch of Radio Station PRO" +msgstr "" + +#: radio-station-admin.php:1362 +msgid "Radio Station Pro Launch Discount!" +msgstr "" + +#: includes/shortcodes.php:2043 +msgid "View all posts by %s" +msgstr "" + +#: includes/shortcodes.php:1665 +msgid "No Shows in this Language." +msgstr "" + +#: includes/shortcodes.php:1475 +msgid "No Languages were found to display." +msgstr "" + +#: includes/shortcodes.php:682 +msgid "No Shows in the requested Language were found." +msgstr "" + +#: includes/shortcodes.php:680 +msgid "No Shows in the requested Genre were found." +msgstr "" + +#: includes/shortcodes.php:678 +msgid "No Shows in the requested Genre and Language were found." +msgstr "" + +#: includes/post-types.php:257 +msgid "All Producer Profiles" +msgstr "" + +#: includes/post-types.php:256 +msgid "No Producer Profiles found in Trash" +msgstr "" + +#: includes/post-types.php:255 +msgid "No Producer Profiles found" +msgstr "" + +#: includes/post-types.php:254 +msgid "Search Producer Profiles" +msgstr "" + +#: includes/post-types.php:247 +msgid "Add New Producer Profile" +msgstr "" + +#: includes/post-types.php:246 +msgid "Producer" +msgstr "" + +#: includes/post-types.php:205 +msgid "All Host Profiles" +msgstr "" + +#: includes/post-types.php:204 +msgid "No Host Profiles found in Trash" +msgstr "" + +#: includes/post-types.php:203 +msgid "No Host Profiles found" +msgstr "" + +#: includes/post-types.php:202 +msgid "Search Host Profiles" +msgstr "" + +#: includes/post-types.php:195 +msgid "Add New Host Profile" +msgstr "" + +#: includes/post-types.php:194 +msgid "Host" +msgstr "Presentator" + +#: includes/post-types-admin.php:7098 +msgid "Failed. Please relogin and try again." +msgstr "" + +#: includes/post-types-admin.php:5835 +msgid "Failed. No Playlist ID provided." +msgstr "" + +#: includes/post-types-admin.php:5515 +msgctxt "seconds unit" +msgid "s" +msgstr "" + +#: includes/post-types-admin.php:5503 +msgctxt "minutes unit" +msgid "m" +msgstr "" + +#: includes/post-types-admin.php:4736 +msgid "Affected Show(s)" +msgstr "" + +#: includes/post-types-admin.php:4735 +msgid "Override Time(s)" +msgstr "" + +#: includes/extra-strings.php:310 includes/post-types-admin.php:4274 +msgid "Failed. Invalid Override." +msgstr "" + +#: includes/extra-strings.php:286 +msgid "Multiday" +msgstr "" + +#: includes/post-types-admin.php:3955 +msgid "Are you sure you want to clear all overrides?" +msgstr "" + +#: includes/post-types-admin.php:3954 +msgid "Are you sure you want to remove this Override?" +msgstr "" + +#: includes/post-types-admin.php:3905 includes/post-types-admin.php:4218 +msgid "Remove Override" +msgstr "" + +#: includes/post-types-admin.php:3899 includes/post-types-admin.php:4213 +msgid "Duplicate Override" +msgstr "" + +#: includes/post-types-admin.php:3549 +msgid "Overrides Saved." +msgstr "" + +#: includes/post-types-admin.php:3548 +msgid "Saving Overrides..." +msgstr "" + +#: includes/post-types-admin.php:3350 +msgid "Override Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:3346 +msgid "Override DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:3304 +msgid "Check to Disable" +msgstr "" + +#: includes/post-types-admin.php:3274 +msgid "Latest Audio" +msgstr "" + +#: includes/post-types-admin.php:3193 +msgid "Use Producer assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3171 +msgid "Use Host assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3149 +msgid "Use Feature Image metabox." +msgstr "" + +#: includes/post-types-admin.php:3142 +msgid "Featured Image" +msgstr "" + +#: includes/post-types-admin.php:3127 +msgid "Use Show Images metabox." +msgstr "" + +#: includes/post-types-admin.php:3110 +msgid "Use Excerpt Editor metabox below." +msgstr "" + +#: includes/extra-strings.php:500 includes/post-types-admin.php:3103 +msgid "Excerpt" +msgstr "" + +#: includes/post-types-admin.php:3093 +msgid "Use Content Editor metabox below." +msgstr "" + +#: includes/post-types-admin.php:3086 +msgid "Description" +msgstr "Beschrijving" + +#: includes/post-types-admin.php:3076 +msgid "Use Title Editor metabox above." +msgstr "" + +#: includes/post-types-admin.php:3055 +msgid "Override Data" +msgstr "Overschrijf gegevens" + +#: includes/post-types-admin.php:3053 +msgid "Override?" +msgstr "" + +#: includes/post-types-admin.php:3046 +msgid " Unchecked boxes use Show data, checked boxes use Override data." +msgstr "" + +#: includes/post-types-admin.php:3045 +msgid "Usage Note" +msgstr "" + +#: includes/post-types-admin.php:3025 +msgid "If checked, assigned Language terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:3005 +msgid "If checked, assigned Genre terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:2986 +msgid "If selected, Override data will be used from the Linked Show." +msgstr "" + +#: includes/post-types-admin.php:2949 +msgid "No Show Link" +msgstr "" + +#: includes/post-types-admin.php:2941 +msgid "Link to Show" +msgstr "Link naar programma" + +#: includes/post-types-admin.php:2900 +msgid "Override Show Data" +msgstr "" + +#: includes/post-types-admin.php:2040 +msgid "Error! No Show ID provided." +msgstr "" + +#: includes/extra-strings.php:467 +msgid "Phone" +msgstr "Telefoon" + +#: includes/extra-strings.php:466 +msgid "Email" +msgstr "E-mail" + +#: includes/extra-strings.php:465 +msgid "Website" +msgstr "Site" + +#: includes/extra-strings.php:464 +msgid "Profile Info" +msgstr "" + +#: includes/extra-strings.php:462 +msgid "%s RSS Feed" +msgstr "" + +#: includes/extra-strings.php:461 +msgid "Email %s" +msgstr "" + +#: includes/extra-strings.php:460 +msgid "%s Phone Number" +msgstr "" + +#: includes/extra-strings.php:459 +msgid "Visit %s Website" +msgstr "Bezoek de site van %s" + +#: includes/extra-strings.php:457 +msgid "%s Playlist" +msgstr "%s afspeellijst" + +#: includes/extra-strings.php:456 +msgid "%s Show" +msgstr "%s programma" + +#: includes/extra-strings.php:454 +msgid "About this %s" +msgstr "Over deze %s" + +#: includes/extra-strings.php:453 +msgid "Download this Episode" +msgstr "Deze aflevering downloaden" + +#: includes/extra-strings.php:451 +msgid "Listen to this %s" +msgstr "Luister naar deze %s" + +#: includes/extra-strings.php:446 +msgid "on" +msgstr "op" + +#: includes/extra-strings.php:445 +msgid "Broadcasted" +msgstr "Uitgezonden" + +#: includes/extra-strings.php:444 +msgid "Episode Info" +msgstr "Aflevering info" + +#: includes/extra-strings.php:436 +msgid "No Shows" +msgstr "Geen programma's" + +#: includes/extra-strings.php:426 +msgid "Cancel" +msgstr "Annuleren" + +#: includes/extra-strings.php:425 +msgid "Clear" +msgstr "Wissen" + +#: includes/extra-strings.php:427 +msgid "Select Timezone..." +msgstr "Selecteer tijdzone..." + +#: includes/extra-strings.php:424 +msgid "Select Region..." +msgstr "Selecteer regio..." + +#: includes/extra-strings.php:423 +msgid "Change Timezone" +msgstr "" + +#: includes/extra-strings.php:176 +msgid "Insert" +msgstr "" + +#: includes/extra-strings.php:172 +msgid "Remove Genre Image" +msgstr "" + +#: includes/extra-strings.php:171 +msgid "Add Genre Image" +msgstr "" + +#: includes/extra-strings.php:167 +msgid "Guest" +msgstr "" + +#: includes/extra-strings.php:166 +msgid "New Guest Name" +msgstr "" + +#: includes/extra-strings.php:165 +msgid "Add New Guest" +msgstr "" + +#: includes/extra-strings.php:164 +msgid "Update Guest" +msgstr "" + +#: includes/extra-strings.php:163 +msgid "Edit Guest" +msgstr "" + +#: includes/extra-strings.php:162 +msgid "Parent Guest:" +msgstr "" + +#: includes/extra-strings.php:161 +msgid "Parent Guest" +msgstr "" + +#: includes/extra-strings.php:160 +msgid "All Guests" +msgstr "" + +#: includes/extra-strings.php:159 +msgid "Search Guests" +msgstr "" + +#: includes/extra-strings.php:158 +msgid "Topic" +msgstr "" + +#: includes/extra-strings.php:157 +msgid "New Topic Name" +msgstr "" + +#: includes/extra-strings.php:156 +msgid "Add New Topic" +msgstr "Nieuw onderwerp toevoegen" + +#: includes/extra-strings.php:155 +msgid "Update Topic" +msgstr "Onderwerp bijwerken" + +#: includes/extra-strings.php:154 +msgid "Edit Topic" +msgstr "Bewerk onderwerp" + +#: includes/extra-strings.php:153 +msgid "Parent Topic:" +msgstr "Hoofdonderwerp:" + +#: includes/extra-strings.php:152 +msgid "Parent Topic" +msgstr "Hoofdonderwerp" + +#: includes/extra-strings.php:151 +msgid "All Topics" +msgstr "Alle onderwerpen" + +#: includes/extra-strings.php:150 +msgid "Search Topics" +msgstr "Zoek onderwerpen" + +#: includes/extra-strings.php:422 +msgid "You can reorder this icon after saving it." +msgstr "" + +#: includes/extra-strings.php:421 +msgid "No Social Icon Services defined." +msgstr "" + +#: includes/extra-strings.php:420 +msgid "Are you sure you want to remove this Social Icon?" +msgstr "" + +#: includes/extra-strings.php:417 +msgid "Unknown" +msgstr "" + +#: includes/extra-strings.php:414 +msgid "Remove this Social Icon" +msgstr "" + +#: includes/extra-strings.php:413 +msgid "Move this Icon Down" +msgstr "" + +#: includes/extra-strings.php:412 +msgid "Move this Icon Up" +msgstr "" + +#: includes/extra-strings.php:411 +msgid "Add a Social Icon" +msgstr "" + +#: includes/extra-strings.php:396 +msgid "Social Icons" +msgstr "" + +#: includes/extra-strings.php:409 +msgid "Pinterest" +msgstr "" + +#: includes/extra-strings.php:408 +msgid "WhatsApp" +msgstr "" + +#: includes/extra-strings.php:407 +msgid "Instagram" +msgstr "" + +#: includes/extra-strings.php:406 +msgid "Xing" +msgstr "" + +#: includes/extra-strings.php:405 +msgid "LinkedIn" +msgstr "" + +#: includes/extra-strings.php:404 +msgid "YouTube" +msgstr "" + +#: includes/extra-strings.php:403 +msgid "Twitter" +msgstr "" + +#: includes/extra-strings.php:402 +msgid "Facebook" +msgstr "" + +#: includes/extra-strings.php:401 +msgid "Discord" +msgstr "" + +#: includes/extra-strings.php:400 +msgid "Twitch" +msgstr "" + +#: includes/extra-strings.php:399 +msgid "Spotify" +msgstr "" + +#: includes/extra-strings.php:398 +msgid "Mixcloud" +msgstr "" + +#: includes/extra-strings.php:397 +msgid "Soundcloud" +msgstr "" + +#: includes/post-types-admin.php:5287 includes/post-types-admin.php:5501 +msgid "Length" +msgstr "Duur" + +#: includes/extra-strings.php:390 options.php:1050 +msgid "List" +msgstr "Lijst" + +#: includes/extra-strings.php:271 +msgid "View" +msgstr "Weergave" + +#: includes/extra-strings.php:389 +msgid "Load Schedule for Next Week" +msgstr "Toon schema volgende week" + +#: includes/extra-strings.php:388 +msgid "Load Schedule for Previous Week" +msgstr "Toon schema vorige week" + +#: includes/extra-strings.php:387 includes/post-types-admin.php:3537 +msgid "Clear Overrides" +msgstr "" + +#: includes/extra-strings.php:386 +msgid "Override Time Saved." +msgstr "" + +#: includes/extra-strings.php:385 +msgid "Adding Override Time..." +msgstr "" + +#: includes/extra-strings.php:383 +msgid "Show Shift Added." +msgstr "" + +#: includes/extra-strings.php:382 +msgid "Adding Show Shift..." +msgstr "" + +#: includes/extra-strings.php:381 +msgid "Create New Override" +msgstr "" + +#: includes/extra-strings.php:380 +msgid "Create New Show" +msgstr "" + +#: includes/extra-strings.php:379 +msgid "Open in a New Window" +msgstr "" + +#: includes/extra-strings.php:378 includes/post-types-admin.php:3543 +msgid "Add Override" +msgstr "" + +#: includes/extra-strings.php:377 +msgid "Select Override..." +msgstr "" + +#: includes/extra-strings.php:375 +msgid "Select Show..." +msgstr "Selecteer programma..." + +#: includes/extra-strings.php:372 +msgid "Override Type" +msgstr "" + +#: includes/extra-strings.php:369 +msgid "Show Type" +msgstr "Programma type" + +#: includes/extra-strings.php:367 +msgid "Shift Day" +msgstr "" + +#: includes/extra-strings.php:318 +msgid "Override Date" +msgstr "" + +#: includes/extra-strings.php:363 +msgid "Timeslot Type" +msgstr "" + +#: includes/extra-strings.php:362 +msgid "Error! Invalid day value passed." +msgstr "" + +#: includes/extra-strings.php:361 +msgid "Error! Invalid date value passed." +msgstr "" + +#: includes/extra-strings.php:360 +msgid "Error. Provided ID is not a Show or Override." +msgstr "" + +#: includes/extra-strings.php:359 +msgid "All Override Times" +msgstr "" + +#: includes/extra-strings.php:357 +msgid "Display Override" +msgstr "" + +#: includes/extra-strings.php:356 +msgid "Override" +msgstr "" + +#: includes/extra-strings.php:355 +msgid "Warning. Provided Override Shift ID no longer exists." +msgstr "" + +#: includes/extra-strings.php:354 +msgid "All Show Shifts" +msgstr "" + +#: includes/extra-strings.php:352 +msgid "Display Shifts" +msgstr "" + +#: includes/extra-strings.php:351 +msgid "Warning. Provided Shift ID no longer exists." +msgstr "" + +#: includes/extra-strings.php:350 +msgid "Warning. Provided Shift ID is not valid." +msgstr "" + +#: includes/extra-strings.php:348 +msgid "Error. Provided Show ID does not exist." +msgstr "" + +#: includes/extra-strings.php:347 +msgid "Add Show or Override to Schedule" +msgstr "" + +#: includes/extra-strings.php:346 +msgid "Add to Schedule" +msgstr "" + +#: includes/extra-strings.php:345 +msgid "Edit Override Time" +msgstr "" + +#: includes/extra-strings.php:344 +msgid "Edit Show Shift" +msgstr "" + +#: includes/extra-strings.php:343 +msgid "Edit Override Times" +msgstr "" + +#: includes/extra-strings.php:342 +msgid "Edit Show Shifts" +msgstr "" + +#: includes/extra-strings.php:341 includes/post-types-admin.php:3540 +msgid "Save Overrides" +msgstr "" + +#: includes/extra-strings.php:340 +msgid "Save Override" +msgstr "" + +#: includes/extra-strings.php:338 +msgid "Save Shift" +msgstr "" + +#: includes/extra-strings.php:337 +msgid "You have unsaved changes. Are you sure you want to close this window?" +msgstr "" + +#: includes/extra-strings.php:336 +msgid "Your session has expired. You can log in again from this page or go to the login page." +msgstr "" + +#: includes/extra-strings.php:335 +msgid "Calendar" +msgstr "Kalender" + +#: includes/extra-strings.php:334 options.php:1051 +msgid "Grid" +msgstr "" + +#: includes/extra-strings.php:333 +msgid "Tabs" +msgstr "" + +#: includes/extra-strings.php:332 +msgid "Table" +msgstr "" + +#: includes/extra-strings.php:331 +msgid "Visual Schedule Editor" +msgstr "" + +#: includes/extra-strings.php:326 +msgid "Remove Role from User(s" +msgstr "" + +#: includes/extra-strings.php:325 +msgid "Grant Role to User(s" +msgstr "" + +#: includes/extra-strings.php:324 +msgid "They can also be assigned via the WordPress user editor." +msgstr "" + +#: includes/extra-strings.php:323 +msgid "You can assign Radio Station roles to users directly here." +msgstr "" + +#: includes/extra-strings.php:322 +msgid "Role Assignment Interface" +msgstr "" + +#: includes/extra-strings.php:321 +msgid "Show Editors" +msgstr "" + +#: includes/extra-strings.php:395 +msgid "No %s were found to display." +msgstr "" + +#: includes/extra-strings.php:149 +msgid "Post Type for Show Episodes" +msgstr "" + +#: includes/extra-strings.php:148 +msgid "All Episodes" +msgstr "" + +#: includes/extra-strings.php:147 +msgid "No Episodes found in Trash" +msgstr "" + +#: includes/extra-strings.php:146 +msgid "No Episodes found" +msgstr "" + +#: includes/extra-strings.php:145 +msgid "Search Episodes" +msgstr "" + +#: includes/extra-strings.php:144 +msgid "View Episode" +msgstr "" + +#: includes/extra-strings.php:143 +msgid "New Episode" +msgstr "" + +#: includes/extra-strings.php:142 +msgid "Edit Episode" +msgstr "" + +#: includes/extra-strings.php:141 +msgid "Add Episode" +msgstr "" + +#: includes/extra-strings.php:140 +msgid "Episode" +msgstr "Aflevering" + +#: includes/extra-strings.php:228 +msgid "URL where show images will be staged for import (see help" +msgstr "" + +#: includes/extra-strings.php:227 +msgid "Default similar to" +msgstr "" + +#: includes/extra-strings.php:226 +msgid "YAML file name" +msgstr "" + +#: includes/extra-strings.php:225 +msgid "Advanced" +msgstr "" + +#: includes/extra-strings.php:224 +msgid "Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options." +msgstr "" + +#: includes/extra-strings.php:222 +msgid "WARNING" +msgstr "" + +#: includes/extra-strings.php:221 +msgid "Delete existing show data" +msgstr "" + +#: includes/extra-strings.php:220 +msgid "Import show data from a YAML file." +msgstr "" + +#: includes/extra-strings.php:217 +msgid "Import/Export Show Data" +msgstr "Importeer/exporteer programmagegevens" + +#: includes/extra-strings.php:216 +msgid " show-schedule: must define at least one weekday." +msgstr "" + +#: includes/extra-strings.php:215 +msgid "Invalid weekday. show-schedule[] must be one of \"sun\"..\"sat\", or \"sunday\"..\"saturday\" (case insensitive." +msgstr "" + +#: includes/extra-strings.php:214 +msgid "show-schedule[] must reference an array of time blocks containing at least one element." +msgstr "" + +#: includes/extra-strings.php:213 +msgid "Error, for show-schedule[], only \"disabled\" and \"encore\" flags are allowed" +msgstr "" + +#: includes/extra-strings.php:212 +msgid "show-schedule[] time blocks must be in 24h format and have the form \"04:55\" (note 0 padding." +msgstr "" + +#: includes/extra-strings.php:211 +msgid "Data file errors noted as follows:" +msgstr "" + +#: includes/extra-strings.php:210 +msgid "YAML data parsed successfully, but contains formatting errors. See below for details." +msgstr "" + +#: includes/extra-strings.php:209 +msgid "show-active: may not be null." +msgstr "" + +#: includes/extra-strings.php:208 +msgid "show-email: must be a valid email address." +msgstr "" + +#: includes/extra-strings.php:207 +msgid "show-producer-list: must be a simple array of valid email addresses." +msgstr "" + +#: includes/extra-strings.php:206 +msgid "show-user-list: must be a simple array of valid email addresses." +msgstr "" + +#: includes/extra-strings.php:205 +msgid "show-podcast: must be a valid web address." +msgstr "" + +#: includes/extra-strings.php:204 +msgid "show-url: must be a valid web address." +msgstr "" + +#: includes/extra-strings.php:203 +msgid "upload-images: may not be null." +msgstr "" + +#: includes/extra-strings.php:202 +msgid "show-header: must be a URL reference to an existing image." +msgstr "" + +#: includes/extra-strings.php:201 +msgid "show-avatar: must be a URL reference to an existing image." +msgstr "" + +#: includes/extra-strings.php:200 +msgid "show-image: must be a URL reference to an existing image." +msgstr "" + +#: includes/extra-strings.php:199 +msgid "show-description: may not be null." +msgstr "" + +#: includes/extra-strings.php:198 +msgid "show-title: may not be null." +msgstr "" + +#: includes/extra-strings.php:197 +msgid "Each show in the YAML file must define at minimum the following keys: " +msgstr "" + +#: includes/extra-strings.php:196 +msgid "Failed to create show_images.zip" +msgstr "" + +#: includes/extra-strings.php:195 +msgid "Failed to copy file. See below for details" +msgstr "" + +#: includes/extra-strings.php:194 +msgid "No image exported for" +msgstr "" + +#: includes/extra-strings.php:193 +msgid "Failed to create export folder" +msgstr "" + +#: includes/extra-strings.php:192 +msgid "YAML import error. See below for details." +msgstr "" + +#: includes/extra-strings.php:191 +msgid "Images not exported. See help for details on how to include images." +msgstr "" + +#: includes/extra-strings.php:190 +msgid "Show data file (YAML" +msgstr "" + +#: includes/extra-strings.php:189 +msgid "Download one of the image files and the data file. See help for details on how to stage an import including images." +msgstr "" + +#: includes/extra-strings.php:188 +msgid "Image tgz file" +msgstr "" + +#: includes/extra-strings.php:187 +msgid "Image zip file" +msgstr "" + +#: includes/extra-strings.php:186 +msgid "Data file" +msgstr "" + +#: includes/extra-strings.php:185 +msgid "Export successful. Use the following link(s to download your data." +msgstr "" + +#: includes/extra-strings.php:184 +msgid "Successfully parsed and imported YAML file. Pre-existing show data remains unchanged." +msgstr "" + +#: includes/extra-strings.php:183 +msgid "Successfully parsed and imported YAML file, deleting pre-existing show data." +msgstr "" + +#: includes/extra-strings.php:182 +msgid "Please upload a file to import" +msgstr "" + +#: includes/extra-strings.php:181 +msgid "Please upload a file to import." +msgstr "" + +#: includes/extra-strings.php:180 +msgid "Please upload a valid YAML file" +msgstr "" + +#: includes/extra-strings.php:179 +msgid "Please upload a valid YAML file." +msgstr "" + +#: includes/extra-strings.php:178 +msgid "this is a failure message" +msgstr "" + +#: includes/extra-strings.php:177 +msgid "this is a success message" +msgstr "" + +#: includes/extra-strings.php:168 +msgid "Show %s" +msgstr "Programma %s" + +#: includes/extra-strings.php:257 includes/extra-strings.php:349 +msgid "You do not have permission to do that." +msgstr "Je hebt geen toestemming om dat te doen." + +#: includes/extra-strings.php:47 +msgid "The required minimum version of the Radio Station plugin is:" +msgstr "" + +#: includes/extra-strings.php:269 +msgid "Producer User" +msgstr "" + +#: includes/extra-strings.php:268 includes/post-types-admin.php:3186 +#: includes/post-types.php:245 +msgid "Producers" +msgstr "" + +#: includes/extra-strings.php:267 +msgid "Host User" +msgstr "" + +#: includes/extra-strings.php:265 +msgid "User Save Error! The selected User is already assigned to a %s Profile." +msgstr "" + +#: includes/extra-strings.php:93 includes/post-types-admin.php:1863 +msgid "Use this Image" +msgstr "" + +#: includes/extra-strings.php:92 includes/post-types-admin.php:1862 +msgid "Select or Upload Image" +msgstr "" + +#: includes/extra-strings.php:90 +msgid "Remove %s Avatar Image" +msgstr "" + +#: includes/extra-strings.php:89 +msgid "Set %s Avatar Image" +msgstr "" + +#: includes/extra-strings.php:88 +msgid "Episode Image" +msgstr "" + +#: includes/extra-strings.php:264 +msgid "Producer Image" +msgstr "" + +#: includes/extra-strings.php:263 +msgid "Host Image" +msgstr "" + +#: includes/extra-strings.php:260 +msgid "%s Email" +msgstr "" + +#: includes/extra-strings.php:258 +msgid "Profile Information" +msgstr "" + +#: includes/post-types-admin.php:5831 +msgid "Failed. Use manual Publish or Update instead." +msgstr "" + +#: includes/extra-strings.php:126 +msgid "Are you sure you want to remove this audio?" +msgstr "" + +#: includes/extra-strings.php:125 +msgid "Remove Selected Audio" +msgstr "" + +#: includes/extra-strings.php:124 +msgid "Select Audio File" +msgstr "" + +#: includes/extra-strings.php:121 +msgid "No Playlists to Select." +msgstr "" + +#: includes/extra-strings.php:120 +msgid "Select Playlist" +msgstr "" + +#: includes/extra-strings.php:117 +msgid "minutees" +msgstr "" + +#: includes/extra-strings.php:116 +msgid "Episode Length" +msgstr "Duur aflevering" + +#: includes/extra-strings.php:114 +msgid "Broadcast Time" +msgstr "Uitzendtijd" + +#: includes/extra-strings.php:112 +msgid "Broadcast Date" +msgstr "" + +#: includes/extra-strings.php:105 +msgid "Episode Number" +msgstr "" + +#: includes/extra-strings.php:101 +msgid "Media Library" +msgstr "" + +#: includes/extra-strings.php:100 +msgid "File URL" +msgstr "" + +#: includes/extra-strings.php:99 +msgid "Media Type" +msgstr "" + +#: includes/extra-strings.php:96 includes/post-types-admin.php:6384 +msgid "Draft" +msgstr "" + +#: includes/extra-strings.php:95 +msgid "Select Show" +msgstr "Selecteer programma" + +#: includes/extra-strings.php:87 +msgid "Episode Information" +msgstr "" + +#: radio-station-admin.php:1726 +msgid "Plugin Updates and Announcements List" +msgstr "Lijst met plugin-updates en -aankondigingen" + +#: radio-station-admin.php:1724 +msgid "Stay tuned! Subscribe to Radio Station's" +msgstr "Blijf op de hoogte! Abonneer je op Radio Station's" + +#: radio-station-admin.php:1675 +msgid "until conflicts are resolved." +msgstr "totdat conflicten zijn opgelost." + +#: radio-station-admin.php:1674 +msgid "This notice will persist" +msgstr "Deze kennisgeving blijft bestaan" + +#: radio-station-admin.php:1669 +msgid "in Show Shift column." +msgstr "in de kolom van programmatijdblok." + +#: radio-station-admin.php:1668 +msgid "Conflicts are highlighted" +msgstr "Conflicten zijn gemarkeerd" + +#: radio-station-admin.php:1667 +msgid "Go to Show List" +msgstr "Ga naar programmalijst" + +#: radio-station-admin.php:1628 +msgid "The following Shows have conflicting Shift times" +msgstr "De volgende programma's hebben conflicterende tijden" + +#: radio-station-admin.php:1624 +msgid "Schedule conflicts!" +msgstr "Schema-conflicten!" + +#: radio-station-admin.php:1623 +msgid "has detected" +msgstr "heeft gedetecteerd" + +#: radio-station-admin.php:1551 +msgid "to make it better for everyone" +msgstr "om het beter te maken voor iedereen" + +#: radio-station-admin.php:1550 +msgid "Become a Radio Station Patreon Supporter" +msgstr "Wordt een Patreon-supporter van Radio Station" + +#: radio-station-admin.php:1548 +msgid "We invite you to" +msgstr "We nodigen je uit om" + +#: radio-station-admin.php:1544 +msgid " plugin development has been actively taken over by" +msgstr " plugin-ontwikkeling is actief overgenomen door" + +#: radio-station-admin.php:1542 +msgid "since June 2019" +msgstr "sinds juni 2019" + +#: radio-station-admin.php:1541 +msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" +msgstr "Meer dan duizend Radio Station gebruikers met dank aan de auteur van de plugin Nikki Blight" + +#: radio-station-admin.php:1540 +msgid "Help support us to make improvements, modifications and introduce new features!" +msgstr "Ondersteun ons ook om verbeteringen en aanpassingen te maken en nieuwe onderdelen te introduceren!" + +#: radio-station-admin.php:1317 +msgid "Activate your 30 days before it ends!" +msgstr "Activeer je 30 dagen proefversie voordat deze verloopt!" + +#: radio-station-admin.php:1316 +msgid "Offer valid until end of July 2020." +msgstr "Aanbieding is geldig tot het einde van juli 2020." + +#: radio-station-admin.php:1311 +msgid "Great! I'm listed, dismiss this notice." +msgstr "Super! Ik sta op de lijst, negeer deze kennisgeving." + +#: radio-station-admin.php:1307 widgets/class-current-playlist-widget.php:81 +#: widgets/class-current-show-widget.php:101 +#: widgets/class-radio-player-widget.php:164 +#: widgets/class-radio-player-widget.php:232 +#: widgets/class-radio-player-widget.php:242 +#: widgets/class-upcoming-shows-widget.php:104 +msgid "More Details" +msgstr "Meer details" + +#: radio-station-admin.php:1304 +msgid "Yes please!" +msgstr "Ja graag!" + +#: radio-station-admin.php:1292 +msgid "Interested in more exposure and listeners for your Radio Station, for free?" +msgstr "Ben je geïnteresseerd in meer luisteraars voor je radiostation, gratis?" + +#: radio-station-admin.php:1291 +msgid "we are offering 30 days free listing to all Radio Station users!" +msgstr "We bieden een 30 dagen gratis aanmelding voor alle Radio Station gebruikers" + +#: radio-station-admin.php:1291 +msgid "Because while launching," +msgstr "Want tijdens opstarten," + +#: radio-station-admin.php:1289 +msgid "Allowing listeners to newly discover Stations and Shows - which can include yours..." +msgstr "Laat luisteraars nieuw ontdekte stations en programma's ontdekken - inclusief die van jou.." + +#: radio-station-admin.php:1287 +msgid "We are excited to announce the opening of the new" +msgstr "We zijn enorm enthousiast over de opening van de nieuwe" + +#: radio-station-admin.php:1284 +msgid "Time Sensitive Free Offer" +msgstr "Tijdafhankelijke gratis speciale aanbieding" + +#: radio-station-admin.php:1006 +msgid "Plugin Settings" +msgstr "Plugin instellingen" + +#: radio-station-admin.php:983 +msgid "Thanks for Updating! You can enjoy these improvements now" +msgstr "Bedankt voor het updaten! Je kan nu genieten van deze verbeteringen" + +#: radio-station-admin.php:977 +msgid "Update Notice" +msgstr "Updatebericht" + +#: radio-station-admin.php:919 radio-station-admin.php:1018 +#: radio-station-admin.php:1328 radio-station-admin.php:1414 +#: radio-station-admin.php:1576 +msgid "Dismiss this Notice" +msgstr "Negeer dit bericht" + +#: radio-station-admin.php:908 radio-station-admin.php:999 +msgid "Full Update Details" +msgstr "Volledige updategegevens" + +#: radio-station-admin.php:905 +msgid "Update Now" +msgstr "Nu updaten" + +#: radio-station-admin.php:888 +msgid "is available." +msgstr "is beschikbaar." + +#: radio-station-admin.php:886 +msgid "A new version of" +msgstr "Een nieuwe versie van" + +#: radio-station-admin.php:816 radio-station-admin.php:892 +msgid "Take a moment to Update for a better experience. In this update" +msgstr "Neem even de tijd om te updaten om een betere ervaring te krijgen. In deze update" + +#: radio-station-admin.php:674 +msgid "Right-click and download this file to save your export" +msgstr "Klik met rechts en download dit bestand om de export op te slaan" + +#: radio-station-admin.php:513 +msgid "Back to Documentation Index" +msgstr "Terug naar documentatie-index" + +#: radio-station-admin.php:405 +msgid "Find out more about Radio Station Pro" +msgstr "Lees meer over Radio Station Pro" + +#: radio-station-admin.php:389 +msgid "Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user." +msgstr "Radio Station Pro bevat een rol toekenning interface, zodat je eenvoudig radiostation rollen aan elke gebruiker kunt toewijzen." + +#: radio-station-admin.php:385 +msgid "You can assign a Radio Station role to users through the WordPress User editor." +msgstr "Je kan een Radio Station rol aan gebruikers koppelen via het WordPress gebruikers menu." + +#: radio-station-admin.php:384 +msgid "Role Assignments" +msgstr "Toegekende rollen" + +#: radio-station-admin.php:240 +msgid "Help" +msgstr "Help" + +#: radio-station-admin.php:240 +msgid "Documentation" +msgstr "Documentatie" + +#: includes/extra-strings.php:218 radio-station-admin.php:235 +msgid "Import/Export" +msgstr "Importeren/exporteren" + +#: radio-station-admin.php:235 +msgid "Import/Export Shows" +msgstr "Importeer/Exporteer progamma's" + +#: radio-station-admin.php:184 +msgid "You do not have permissions to access that page." +msgstr "Je hebt geen toestemming om die pagina te bezoeken." + +#: includes/extra-strings.php:458 templates/single-show-content.php:987 +msgid "Jump to" +msgstr "Ga naar" + +#: templates/single-show-content.php:910 +msgid "Latest Show Posts" +msgstr "Laatste programmaberichten" + +#: includes/extra-strings.php:84 +msgid "Episodes" +msgstr "Afleveringen" + +#: includes/extra-strings.php:455 templates/single-show-content.php:773 +msgid "About" +msgstr "Over" + +#: includes/extra-strings.php:450 templates/single-show-content.php:748 +msgid "Show Less" +msgstr "Toon minder" + +#: includes/extra-strings.php:449 templates/single-show-content.php:745 +msgid "Show More" +msgstr "Toon meer" + +#: templates/single-show-content.php:722 +msgid "Full Station Schedule" +msgstr "Volledige uitzendingsplanning" + +#: templates/single-show-content.php:719 +msgid "Go to Full Station Schedule Page" +msgstr "Ga naar de pagina met het volledige stationsschema" + +#: templates/single-show-content.php:506 +msgid "Not Currently Scheduled." +msgstr "Op dit moment niet gepland." + +#: templates/single-show-content.php:453 +msgid "Call in" +msgstr "Bel" + +#: includes/extra-strings.php:448 templates/single-show-content.php:367 +msgid "Produced by" +msgstr "Geproduceerd door" + +#: includes/extra-strings.php:447 templates/single-show-content.php:328 +msgid "Hosted by" +msgstr "Presentatie" + +#: templates/single-show-content.php:319 +msgid "Show Info" +msgstr "Programma-info" + +#: includes/extra-strings.php:463 templates/single-show-content.php:274 +msgid "Download Latest Broadcast" +msgstr "Download laatste uitzending" + +#: includes/extra-strings.php:443 templates/single-show-content.php:242 +msgid "Become a Supporter for" +msgstr "Word een supporter van" + +#: includes/extra-strings.php:442 templates/single-show-content.php:141 +msgid "Show RSS Feed" +msgstr "Toon RSS-feed" + +#: templates/single-show-content.php:125 +msgid "Email Show Host" +msgstr "E-mail presentator" + +#: templates/single-show-content.php:110 +msgid "Call in Phone Number" +msgstr "Inbel telefoonnummer" + +#: templates/single-show-content.php:95 +msgid "Visit Show Website" +msgstr "Bezoek programma site" + +#: templates/legacy/author.php:75 +msgid "About %s" +msgstr "Over %s" + +#: templates/legacy/author.php:54 +msgid "Author Archives: %s" +msgstr "Auteurarchieven: %s" + +#: templates/legacy/playlist-archive-template.php:16 +msgid "Playlist Archive" +msgstr "Afspeellijst-archief" + +#: templates/legacy/single-playlist.php:71 +msgid "No entries for this playlist" +msgstr "Niets gevonden voor deze afspeellijst" + +#: templates/legacy/single-playlist.php:49 +msgid "DJ Comments" +msgstr "DJ-commentaar" + +#: templates/legacy/single-playlist.php:30 templates/legacy/single-show.php:35 +msgid "Pages:" +msgstr "Pagina's:" + +#: templates/legacy/archive-playlist.php:59 +msgid "Newer posts" +msgstr "Nieuwe berichten" + +#: templates/legacy/archive-playlist.php:58 +msgid "Older posts" +msgstr "Oudere berichten" + +#: templates/legacy/archive-playlist.php:57 +msgid "Post navigation" +msgstr "Berichtnavigatie" + +#: templates/legacy/archive-playlist.php:19 +msgid "Playlist Archive for" +msgstr "Afspeellijstarchief voor" + +#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:105 +#: templates/legacy/playlist-archive-template.php:70 +#: templates/legacy/show-blog-archive-template.php:82 +msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." +msgstr "Er zijn geen resultaten gevonden voor het opgevraagde archief. Wellicht kan je de zoekfunctie gebruiken om naar een gerelateerd bericht te zoeken." + +#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:101 +#: templates/legacy/playlist-archive-template.php:66 +#: templates/legacy/show-blog-archive-template.php:78 +msgid "Nothing Found" +msgstr "Niets gevonden" + +#: templates/legacy/show-blog-archive-template.php:55 +msgid "Posted by" +msgstr "Geplaatst door" + +#: templates/legacy/show-blog-archive-template.php:20 +msgid "Blog Archive" +msgstr "Blogarchief" + +#: templates/single-playlist-content.php:98 +msgid "All Playlists for Show" +msgstr "Alle afspeellijsten van programma" + +#: templates/single-playlist-content.php:91 +msgid "No entries found for this Playlist" +msgstr "Geen vermeldingen gevonden voor deze afspeellijst" + +#: templates/single-playlist-content.php:19 +msgid "Playlist for Show" +msgstr "Afspeellijst voor programma" + +#: templates/playlist-export.php:78 +msgid "End Date" +msgstr "Einddatum" + +#: includes/extra-strings.php:282 includes/post-types-admin.php:3766 +#: includes/post-types-admin.php:4114 templates/playlist-export.php:14 +msgid "Start Date" +msgstr "Startdatum" + +#: includes/extra-strings.php:64 radio-station-admin.php:229 +#: templates/playlist-export.php:2 +msgid "Export Playlists" +msgstr "Exporteer afspeellijsten" + +#: templates/master-schedule-tabs.php:650 +msgid "No Shows scheduled for this day." +msgstr "Geen programma's gepland op deze dag." + +#: includes/extra-strings.php:434 templates/master-schedule-div.php:198 +#: templates/master-schedule-legacy.php:244 +#: templates/master-schedule-list.php:438 +#: templates/master-schedule-table.php:635 +#: templates/master-schedule-tabs.php:529 +msgid "Audio File" +msgstr "Audiobestand" + +#: includes/extra-strings.php:433 templates/master-schedule-div.php:188 +#: templates/master-schedule-legacy.php:229 +#: templates/master-schedule-list.php:418 +#: templates/master-schedule-table.php:616 +#: templates/master-schedule-tabs.php:509 +msgid "encore airing" +msgstr "herhaling" + +#: includes/extra-strings.php:430 templates/master-schedule-list.php:28 +#: templates/master-schedule-table.php:29 +#: templates/master-schedule-table.php:600 +#: templates/master-schedule-tabs.php:30 templates/master-schedule-tabs.php:496 +msgid "to" +msgstr "tot" + +#: includes/extra-strings.php:440 templates/master-schedule-tabs.php:231 +msgid "Viewing" +msgstr "Bekijken" + +#: includes/extra-strings.php:439 templates/master-schedule-table.php:195 +#: templates/master-schedule-tabs.php:208 +msgid "Next Day" +msgstr "Volgende dag" + +#: includes/extra-strings.php:438 templates/master-schedule-table.php:182 +#: templates/master-schedule-tabs.php:192 +msgid "Previous Day" +msgstr "Vorige dag" + +#: includes/user-roles.php:162 +msgid "Show Editor" +msgstr "Toon editor" + +#: includes/user-roles.php:82 +msgid "Show Producer" +msgstr "Programmaproducer" + +#: includes/user-roles.php:58 includes/user-roles.php:77 +#: includes/user-roles.php:78 includes/user-roles.php:290 +msgid "DJ / Host" +msgstr "DJ / presentator" + +#: includes/templates.php:1166 +msgid "Next Related Show" +msgstr "Volgend gerelateerde programma" + +#: includes/templates.php:1161 +msgid "Previous Related Show" +msgstr "Vorig gerelateerde programma" + +#: includes/templates.php:876 +msgid "More %s for Show" +msgstr "Meer %s voor programma" + +#: includes/templates.php:874 +msgid "More %s for Shows" +msgstr "Meer %s voor programma's" + +#: includes/templates.php:865 +msgid "%s for Show" +msgstr "%s voor programma" + +#: includes/templates.php:863 +msgid "%s for Shows" +msgstr "%s voor programma's" + +#: radio-station.php:1014 +msgid "Days" +msgstr "Dagen" + +#: radio-station.php:1012 +msgid "Hours" +msgstr "Uren" + +#: radio-station.php:1011 +msgid "Hour" +msgstr "Uur" + +#: radio-station.php:1010 +msgid "Minutes" +msgstr "Minuten" + +#: radio-station.php:1009 +msgid "Minute" +msgstr "Minuut" + +#: radio-station.php:1008 +msgid "Seconds" +msgstr "Seconden" + +#: includes/extra-strings.php:294 radio-station.php:1007 +msgid "Second" +msgstr "Seconde" + +#: radio-station.php:235 +msgid "Rate on WordPress.org" +msgstr "Geef je waardering op WordPress.org" + +#: options.php:1365 +msgid "Permissions" +msgstr "Rechten" + +#: options.php:1364 +msgid "Widget Loading" +msgstr "Widget wordt geladen" + +#: options.php:1335 options.php:1361 +msgid "Archives" +msgstr "Archieven" + +#: options.php:1358 +msgid "Show Pages" +msgstr "Programmapagina's" + +#: options.php:1355 +msgid "Schedule Page" +msgstr "Planningpagina" + +#: options.php:1356 +msgid "Single Templates" +msgstr "Enkel templates" + +#: options.php:1354 +msgid "Sitewide Bar Player" +msgstr "Site-brede balk Player" + +#: includes/extra-strings.php:617 options.php:1353 +msgid "Player Colors" +msgstr "Player kleuren" + +#: options.php:1352 +msgid "Advanced Defaults" +msgstr "Geavanceerde standaardinstellingen" + +#: options.php:1351 +msgid "Basic Defaults" +msgstr "Basis standaardinstellingen" + +#: options.php:1349 +msgid "Feeds" +msgstr "Feeds" + +#: options.php:1348 +msgid "Station" +msgstr "Station" + +#: options.php:1347 +msgid "Broadcast" +msgstr "Uitzending" + +#: options.php:1338 +msgid "Roles" +msgstr "Rollen" + +#: options.php:1337 +msgid "Widgets" +msgstr "WIdgets" + +#: options.php:1334 +msgid "Pages" +msgstr "Pagina's" + +#: includes/extra-strings.php:452 options.php:1333 +msgid "Player" +msgstr "Speler" + +#: options.php:1302 +msgid "Allow users with WordPress Editor role to edit all Radio Station post types." +msgstr "Sta toe dat gebruikers met de rol van editor in WordPress alle radiostation-meldingstypes bewerken." + +#: options.php:1299 +msgid "Add to Editor Capabilities" +msgstr "Voeg toe aan mogelijkheden van editor" + +#: options.php:1291 +msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." +msgstr "Sta toe dat gebruikers met de rol van auteur in WordPress hun eigen programma's en afspeellijsten bewerken." + +#: options.php:1288 +msgid "Add to Author Capabilities" +msgstr "Voeg toe aan mogelijkheden van auteur" + +#: options.php:1279 +msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." +msgstr "Je kan deze rol aan een gebruiker toewijzen voor volledige Station planning update-rechten." + +#: options.php:1278 +msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." +msgstr "Sinds 2.3.0, is er een nieuwe Programma editor rol toegevoegd met Publiceer en Bewerk rechten voor alle Radio Station berichttypes." + +#: options.php:1277 +msgid "Show Editor Role" +msgstr "Toon rol van programma-editor" + +#: options.php:1220 +msgid "Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets." +msgstr "Herlaad alle plugin widges als huidige programma wijzigt. Dit kan ook op individuele widgets worden ingesteld." + +#: options.php:1217 +msgid "Dynamic Reloading?" +msgstr "Dynamisch herladen?" + +#: options.php:1209 +msgid "Defaults plugin widgets to AJAX loading. Can also be set on individual widgets." +msgstr "Zet plugin widgets standaard op AJAX loading. Kan ook op individuele widgets worden ingesteld." + +#: options.php:1206 +msgid "AJAX Load Widgets?" +msgstr "Laad widget met AJAX?" + +#: options.php:1195 +msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" +msgstr "Voor gevorderden. Gebruik zowel een eigen template EN contentfiltering voor een afspeellijst. (Niet compatibel met vroegere templates.)" + +#: options.php:1184 +msgid "Which template to use for displaying Playlist content." +msgstr "Welke template wordt gebruikt voor weergave afspeellijst inhoud." + +#: options.php:1175 +msgid "Playlist Template" +msgstr "Afspeellijst template" + +#: options.php:1167 +msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" +msgstr "Voor gevorderden. Gebruik zowel een eigen sjabloon EN contentfiltering voor een programma. (Niet compatibel met vroegere sjablonen.)" + +#: options.php:1163 options.php:1191 +msgid "Combined Method" +msgstr "Gecombineerde methode" + +#: options.php:1156 +msgid "Which template to use for displaying Show content." +msgstr "Welke template wordt gebruikt voor weergave programma inhoud." + +#: options.php:1153 options.php:1181 +msgid "Legacy Plugin Template" +msgstr "Verouderde plugin template" + +#: options.php:1152 options.php:1180 +msgid "Theme Singular Template (singular.php)" +msgstr "Thema Singular template (singular.php)" + +#: options.php:1151 options.php:1179 +msgid "Theme Post Template (single.php)" +msgstr "Thema bericht template (single.php)" + +#: options.php:1150 options.php:1178 +msgid "Theme Page Template (page.php)" +msgstr "Themapagina template (page.php)" + +#: options.php:1147 +msgid "Show Template" +msgstr "Toon template" + +#: options.php:1140 +msgid "Templates Documentation" +msgstr "Template documentatie" + +#: options.php:1139 +msgid "See the Documentation for more information:" +msgstr "Zie de documentatie voor meer informatie:" + +#: options.php:1138 +msgid "Since 2.3.0, the way that Templates are implemented has changed." +msgstr "Sinds 2.3.0 is de manier veranderd waarop templates worden geïmplementeerd." + +#: options.php:1137 +msgid "Templates Change Note" +msgstr "Opmerking over veranderde templates" + +#: options.php:1081 +msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" +msgstr "Vervang de geselecteerde pagina inhoud met de standaard Genre archief. Of eigen weergave met de shortcode:" + +#: options.php:1070 +msgid "Select the Page for displaying the Genre archive list." +msgstr "" + +#: options.php:1066 +msgid "Genres Archive Page" +msgstr "Genre-archiefpagina" + +#: options.php:1014 +msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1003 +msgid "Select the Page for displaying the Playlist archive list." +msgstr "" + +#: options.php:999 +msgid "Playlists Archive Page" +msgstr "Afspeellijst-archiefpagina" + +#: options.php:981 +msgid "Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:970 +msgid "Select the Page for displaying the Override archive list." +msgstr "" + +#: options.php:966 +msgid "Overrides Archive Page" +msgstr "Override-archiefpagina" + +#: options.php:948 +msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:937 +msgid "Select the Page for displaying the Show archive list." +msgstr "" + +#: options.php:933 +msgid "Shows Archive Page" +msgstr "Toont archiefpagina" + +#: options.php:822 +msgid "Playlists per page on the Show Page tab/display" +msgstr "Afspeellijsten per pagina op de tab/weergave van de programmapagina" + +#: options.php:820 +msgid "Playlists per Page" +msgstr "Afspeellijsten per pagina" + +#: options.php:809 +msgid "Linked Show Posts per page on the Show Page tab/display." +msgstr "Gelinkte programaberichten per pagina op de tab/weergave van de programmapagina." + +#: options.php:804 +msgid "Posts per Page" +msgstr "Berichten per pagina" + +#: options.php:783 +msgid "If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." +msgstr "" + +#: options.php:780 +msgid "Content Header Images" +msgstr "Header afbeelding van inhoud" + +#: options.php:771 +msgid "How to display extra sections below Show description. In content tabs or standard layout down the page." +msgstr "" + +#: options.php:768 options.php:883 options.php:917 +msgid "Standard" +msgstr "Standaard" + +#: options.php:767 options.php:882 options.php:916 +msgid "Tabbed" +msgstr "Getabd" + +#: options.php:765 +msgid "Show Content Layout" +msgstr "Lay-out van de programmainhoud" + +#: options.php:757 +msgid "Where to position Show info blocks relative to Show Page content." +msgstr "" + +#: options.php:754 options.php:868 options.php:902 +msgid "Float Top" +msgstr "Zweef boven" + +#: options.php:753 options.php:867 options.php:901 +msgid "Float Right" +msgstr "Zweef rechts" + +#: options.php:752 options.php:866 options.php:900 +msgid "Float Left" +msgstr "Zweef links" + +#: options.php:750 options.php:864 options.php:898 +msgid "Info Blocks Position" +msgstr "Positie van infoblokken" + +#: options.php:715 +msgid "Available Views" +msgstr "" + +#: options.php:702 +msgid "View Switching" +msgstr "Wisseling weergave" + +#: options.php:694 +msgid "Radio Time section display above program Schedule." +msgstr "" + +#: options.php:692 templates/single-show-content.php:545 +msgid "Timezone" +msgstr "Tijdzone" + +#: options.php:691 +msgid "Clock" +msgstr "Klok" + +#: options.php:687 +msgid "Schedule Clock?" +msgstr "" + +#: options.php:679 +msgid "View type to use for automatic display on Master Schedule Page." +msgstr "" + +#: options.php:677 +msgid "Legacy Table" +msgstr "Vroegere tabel" + +#: includes/extra-strings.php:635 options.php:676 options.php:721 +msgid "Tabbed View" +msgstr "Tabweergave" + +#: options.php:675 +msgid "Divs View" +msgstr "Divs-weergave" + +#: includes/extra-strings.php:478 options.php:674 options.php:722 +msgid "List View" +msgstr "Lijstweergave" + +#: includes/extra-strings.php:634 options.php:673 options.php:720 +msgid "Table View" +msgstr "Tabelweergave" + +#: options.php:670 +msgid "Schedule View Default" +msgstr "Standaard-schemaweergave" + +#: options.php:662 +msgid "Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: " +msgstr "Vervangt de geselecteerde paginainhoud door de Hoofdplanning. Of pas aan met de shortcode:" + +#: includes/extra-strings.php:16 options.php:659 options.php:944 +#: options.php:977 options.php:1010 options.php:1046 options.php:1077 +#: options.php:1112 +msgid "Automatic Display" +msgstr "Automatisch afbeelden" + +#: options.php:651 +msgid "Select the Page you are displaying the Master Schedule on." +msgstr "Selecteer de pagina die je wil weergeven in de Hoofdplanning." + +#: options.php:649 +msgid "Master Schedule Page" +msgstr "Hoofd-schemapagina" + +#: options.php:580 +msgid "Background color for the fixed position Sitewide Bar Player." +msgstr "Achtergrondkleur voor de vaste positie van de algemene afspeelbalk." + +#: options.php:578 +msgid "Bar Player Background Color" +msgstr "Afspeelbalk achtergrondkleur" + +#: options.php:569 +msgid "Text color for the fixed position Sitewide Bar Player." +msgstr "Tekstkleur voor de vaste positie van de algemene afspeelbalk" + +#: options.php:567 +msgid "Bar Player Text Color" +msgstr "Afspeelbalk tekst kleur" + +#: options.php:543 +msgid "Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display." +msgstr "Aantal milliseconden waarover in nieuwe pagina's moet vervagen (wanneer continu afspelen is ingeschakeld.) Gebruik 0 voor directe weergave." + +#: options.php:529 +msgid "Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists." +msgstr "Onafgebroken algemene afspeelbalk als de gebruiker binnen pagina's navigeert! Pagina's worden op de achtergrond geladen en worden getoond terwijl de afspeelbalk actief blijft." + +#: options.php:526 +msgid "Continuous Playback" +msgstr "Continue afspelen" + +#: options.php:516 +msgid "Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display." +msgstr "Aantal milliseconden na laden van de pagina waarover de afspeelbalk fade in moet krijgen. Gebruik 0 voor direct weergeven." + +#: options.php:511 +msgid "Fade In Player Bar" +msgstr "Fade in afspeelbalk" + +#: options.php:490 +msgid "Add a fixed position Player Bar which displays Sitewide." +msgstr "Zet een vaste positie voor afspeelbalk voor algemene weergave" + +#: options.php:486 +msgid "Bottom Player Bar" +msgstr "Onderste afspeelbalk" + +#: options.php:485 +msgid "Top Player Bar" +msgstr "Bovenste afspeelbalk" + +#: options.php:484 +msgid "No Player Bar" +msgstr "Geen afspeelbalk" + +#: options.php:481 +msgid "Sitewide Player Bar" +msgstr "" + +#: options.php:473 +msgid "You can override these in specific Player Widgets." +msgstr "" + +#: options.php:472 +msgid "The Bar Player uses the default configurations set above." +msgstr "" + +#: options.php:471 +msgid "Bar Defaults Note" +msgstr "" + +#: options.php:447 +msgid "Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page." +msgstr "" + +#: options.php:444 +msgid "Autoresume Playback" +msgstr "" + +#: options.php:435 +msgid "Stop any existing Player instances on the page or in other windows or tabs when a Player is started." +msgstr "" + +#: options.php:432 +msgid "Single Player at Once" +msgstr "" + +#: options.php:423 +msgid "Initial volume for when the Player starts playback." +msgstr "" + +#: includes/class-radio-player-widget.php:167 options.php:418 +#: widgets/class-radio-player-widget.php:135 +msgid "Player Start Volume" +msgstr "" + +#: options.php:407 +msgid "Default Track Color for Player Volume Slider." +msgstr "" + +#: options.php:405 +msgid "Volume Track Color" +msgstr "" + +#: options.php:396 +msgid "Default Knob Color for Player Volume Slider." +msgstr "" + +#: options.php:394 +msgid "Volume Knob Color" +msgstr "" + +#: options.php:385 +msgid "Default highlight color to use for Control button icons when active." +msgstr "" + +#: options.php:383 +msgid "Control Icons Highlight Color" +msgstr "" + +#: options.php:374 +msgid "Default highlight color to use for Play button icon when playing." +msgstr "" + +#: options.php:372 +msgid "Playing Icon Highlight Color" +msgstr "" + +#: options.php:362 +msgid "Output player debug information in browser javascript console." +msgstr "" + +#: options.php:359 +msgid "Player Debug Mode" +msgstr "" + +#: options.php:333 +msgid "Default Player Buttons shape style." +msgstr "" + +#: options.php:331 +msgid "Square Buttons" +msgstr "" + +#: options.php:330 +msgid "Rounded Buttons" +msgstr "" + +#: options.php:329 +msgid "Circular Buttons" +msgstr "" + +#: options.php:326 +msgid "Default Player Buttons" +msgstr "" + +#: options.php:318 +msgid "Default Player Controls theme style." +msgstr "" + +#: includes/class-radio-player-widget.php:132 includes/extra-strings.php:612 +#: options.php:316 widgets/class-radio-player-widget.php:194 +msgid "Dark" +msgstr "Donker" + +#: includes/class-radio-player-widget.php:131 includes/extra-strings.php:611 +#: options.php:315 widgets/class-radio-player-widget.php:193 +msgid "Light" +msgstr "Licht" + +#: options.php:312 +msgid "Default Player Theme" +msgstr "" + +#: includes/class-radio-player-widget.php:97 includes/extra-strings.php:595 +#: options.php:283 options.php:300 widgets/class-radio-player-widget.php:122 +msgid "jPlayer" +msgstr "" + +#: includes/class-radio-player-widget.php:96 includes/extra-strings.php:594 +#: options.php:284 options.php:301 widgets/class-radio-player-widget.php:121 +msgid "Howler" +msgstr "" + +#: includes/class-radio-player-widget.php:95 includes/extra-strings.php:593 +#: options.php:285 options.php:302 widgets/class-radio-player-widget.php:120 +msgid "Amplitude" +msgstr "" + +#: includes/extra-strings.php:592 options.php:280 +msgid "Player Script" +msgstr "" + +#: options.php:271 +msgid "Display your Radio Station Image in Player by default." +msgstr "" + +#: includes/class-radio-player-widget.php:78 includes/extra-strings.php:589 +#: options.php:268 widgets/class-radio-player-widget.php:95 +#: widgets/class-radio-player-widget.php:99 +msgid "Display Station Image" +msgstr "Toon station afbeelding" + +#: options.php:260 +msgid "Display your Radio Station Title in Player by default." +msgstr "" + +#: options.php:257 +msgid "Display Station Title" +msgstr "Toon stationtitel" + +#: options.php:224 +msgid "Use Show Transient Data to improve Schedule calculation performance." +msgstr "" + +#: options.php:221 +msgid "Show Transients" +msgstr "" + +#: options.php:213 +msgid "Clear Schedule transients with every pageload. Less efficient but more reliable." +msgstr "" + +#: options.php:78 +msgid "If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule." +msgstr "" + +#: options.php:75 +msgid "Ping Netmix Directory" +msgstr "" + +#: options.php:198 +msgid "Enable Station Data Feeds via WordPress Feed links." +msgstr "Schakel datafeeds van het station in via feedlinks van WordPress." + +#: options.php:195 +msgid "Enable Data Feeds" +msgstr "Schakel datafeeds in" + +#: options.php:187 +msgid "Enables Station Data Routes via WordPress REST API." +msgstr "Schakelt dataroutes van het station in via de REST API van WordPress." + +#: options.php:184 +msgid "Enable Data Routes" +msgstr "Schakel dataroutes in" + +#: options.php:174 +msgid "Display Station email address on Shows where a Show email address is not set." +msgstr "" + +#: options.php:173 +msgid "Show Email Display" +msgstr "Weergave e-mail programma" + +#: options.php:162 +msgid "Main email address for the Station (for requests etc.)" +msgstr "Algemeen e-mailadres voor het station (voor vragen en dergelijke)" + +#: options.php:161 +msgid "Station Email" +msgstr "Station e-mailadres" + +#: options.php:151 +msgid "Display Station phone number on Shows where a Show phone number is not set." +msgstr "" + +#: options.php:150 +msgid "Show Phone Display" +msgstr "" + +#: options.php:139 +msgid "Main call in phone number for the Station (for requests etc.)" +msgstr "" + +#: options.php:137 +msgid "Station Phone" +msgstr "Station telefoonnummer" + +#: options.php:127 +msgid "Default Time Format for display output. Can be overridden in each shortcode or widget." +msgstr "" + +#: options.php:125 +msgid "Clock Time Format" +msgstr "Tijdnotatie klok" + +#: options.php:123 +msgid "24 Hour Format" +msgstr "24-uursnotatie" + +#: options.php:122 +msgid "12 Hour Format" +msgstr "12-uursnotatie" + +#: options.php:113 +msgid "Select your Broadcast Location for Radio Timezone display." +msgstr "" + +#: options.php:111 +msgid "Location Timezone" +msgstr "Tijdzone van locatie" + +#: options.php:102 +msgid "Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256" +msgstr "" + +#: options.php:100 player/radio-player.php:304 +msgid "Station Logo Image" +msgstr "Station logo afbeelding" + +#: options.php:91 +msgid "Name of your Radio Station. For use in Stream Player and Data Feeds." +msgstr "Naam van het radiostation. Voor gebruik in Stream Player en Data Feeds" + +#: options.php:89 +msgid "Station Title" +msgstr "Station titel" + +#: options.php:65 +msgid "Select the main language used on your Radio Station." +msgstr "Kies de hoofdtaal die je radiostation gebruikt." + +#: options.php:63 +msgid "Main Broadcast Language" +msgstr "Primaire uitzendtaal" + +#: options.php:54 +msgid "Select streaming fallback for fallback URL." +msgstr "" + +#: options.php:52 +msgid "Fallback Format" +msgstr "" + +#: options.php:43 +msgid "Enter an alternative Streaming URL for Player fallback." +msgstr "" + +#: options.php:41 +msgid "Fallback Stream URL" +msgstr "" + +#: options.php:32 +msgid "Select streaming format for streaming URL." +msgstr "" + +#: options.php:30 +msgid "Streaming Format" +msgstr "" + +#: options.php:19 +msgid "Streaming URL" +msgstr "Streaming-URL" + +#: help/contextual-help-config.php:85 +msgid "show-schedule:" +msgstr "programmaschema:" + +#: help/contextual-help-config.php:74 +msgid "YAML format " +msgstr "YAML-formaat " + +#: help/contextual-help-config.php:63 includes/extra-strings.php:223 +#: templates/playlist-export.php:140 +msgid "Export" +msgstr "Exporteer" + +#: help/contextual-help-config.php:52 includes/extra-strings.php:219 +msgid "Import" +msgstr "Importeren" + +#: includes/shortcodes.php:4445 +msgid "More Playlists" +msgstr "Meer afspeellijsten" + +#: radio-station.php:1002 +msgid "Remaining Time" +msgstr "Resterende tijd" + +#: radio-station.php:1001 +msgid "Commencing in" +msgstr "Begint over" + +#: radio-station.php:1000 +msgid "This Playlist has ended." +msgstr "De afspeellijst is afgelopen." + +#: radio-station.php:999 +msgid "This Show has ended." +msgstr "Dit programma is afgelopen." + +#: radio-station.php:998 +msgid "This Show has started." +msgstr "Dit programma is begonnen." + +#: includes/shortcodes.php:4132 +msgid "No Current Playlist available." +msgstr "" + +#: includes/shortcodes.php:4071 templates/single-playlist-content.php:51 +msgid "Label" +msgstr "Label" + +#: includes/shortcodes.php:3629 +msgid "No Upcoming Shows Scheduled." +msgstr "Geen komende programma's gepland." + +#: includes/shortcodes.php:2957 +msgid "No Show currently scheduled." +msgstr "Geen programma op dit moment gepland." + +#: includes/shortcodes.php:2825 includes/shortcodes.php:3569 +#: templates/single-show-content.php:648 +msgid "Encore Presentation" +msgstr "Herhaling" + +#: includes/extra-strings.php:432 includes/shortcodes.php:2798 +#: includes/shortcodes.php:3546 templates/master-schedule-div.php:133 +#: templates/master-schedule-legacy.php:184 +#: templates/master-schedule-list.php:335 +#: templates/master-schedule-table.php:537 +#: templates/master-schedule-tabs.php:432 templates/single-show-content.php:353 +#: templates/single-show-content.php:392 +msgid "and" +msgstr "en" + +#: includes/extra-strings.php:431 includes/shortcodes.php:2761 +#: includes/shortcodes.php:3509 radio-station-admin.php:1646 +#: templates/master-schedule-div.php:122 +#: templates/master-schedule-legacy.php:173 +#: templates/master-schedule-list.php:320 +#: templates/master-schedule-table.php:521 +#: templates/master-schedule-tabs.php:418 +msgid "with" +msgstr "met " + +#: includes/shortcodes.php:2570 templates/single-show-content.php:499 +msgid "Show Times" +msgstr "Programmatijden" + +#: includes/extra-strings.php:394 includes/shortcodes.php:2106 +msgid "Published on " +msgstr "Gepubliceerd op " + +#: includes/shortcodes.php:1213 +msgid "No Shows in this Genre." +msgstr "Geen programma's in dit genre." + +#: includes/shortcodes.php:1062 +msgid "No Genres were found to display." +msgstr "Er zijn geen genres gevonden om weer te geven." + +#: includes/shortcodes.php:689 +msgid "No Overrides were found to display." +msgstr "Er zijn geen overrides gevonden om weer te geven." + +#: includes/shortcodes.php:687 +msgid "No Playlists were found to display." +msgstr "Er zijn geen afspeellijsten gevonden om weer te geven." + +#: includes/shortcodes.php:684 +msgid "No Shows were found to display." +msgstr "Er zijn geen programma's gevonden om weer te geven." + +#: includes/shortcodes.php:251 +msgid "Your Time" +msgstr "Onze tijd" + +#: includes/shortcodes.php:241 +msgid "Radio Time" +msgstr "Radio tijd" + +#: includes/shortcodes.php:138 +msgid "Your Timezone" +msgstr "Je tijdzone" + +#: includes/shortcodes.php:129 +msgid "Radio Timezone" +msgstr "Radiotijdzone" + +#: includes/shortcodes.php:80 includes/shortcodes.php:84 +#: includes/shortcodes.php:98 includes/shortcodes.php:111 +#: includes/shortcodes.php:113 templates/single-show-content.php:550 +#: templates/single-show-content.php:557 +msgid "UTC" +msgstr "GMT" + +#: includes/data-feeds.php:1335 +msgid "The requested data could not be found." +msgstr "De gevraagde gegevens kunnen niet worden gevonden." + +#: includes/data-feeds.php:1334 +msgid "Error 400 No Requested Data" +msgstr "Fout 400, geen aangevraagde gegevens gevonden" + +#: includes/support-functions.php:1743 +msgid "WordPress Setting" +msgstr "WordPress-instelling" + +#: includes/times.php:116 scheduler/schedule-engine.php:2733 +msgid "WordPress Timezone" +msgstr "WordPress-tijdzone" + +#: scheduler/schedule-engine.php:2693 +msgid "Antarctica" +msgstr "Antarctica" + +#: scheduler/schedule-engine.php:2692 +msgid "Pacific" +msgstr "Grote Oceaan" + +#: scheduler/schedule-engine.php:2691 +msgid "Indian" +msgstr "Indische Oceaan" + +#: scheduler/schedule-engine.php:2690 +msgid "Europe" +msgstr "Europa" + +#: scheduler/schedule-engine.php:2689 +msgid "Australia" +msgstr "Australië" + +#: scheduler/schedule-engine.php:2688 +msgid "Atlantic" +msgstr "Atlantisch" + +#: scheduler/schedule-engine.php:2687 +msgid "Asia" +msgstr "Azië" + +#: scheduler/schedule-engine.php:2686 +msgid "America" +msgstr "Amerika" + +#: scheduler/schedule-engine.php:2685 +msgid "Africa" +msgstr "Afrika" + +#: includes/post-types-admin.php:5097 +msgid "Future Overrides" +msgstr "" + +#: includes/post-types-admin.php:5096 +msgid "Overrides Today" +msgstr "" + +#: includes/post-types-admin.php:5095 +msgid "Past Overrides" +msgstr "" + +#: includes/post-types-admin.php:5092 +msgid "Past and Future" +msgstr "Verleden en toekomst" + +#: includes/post-types-admin.php:5064 +msgid "All Override Months" +msgstr "" + +#: includes/post-types-admin.php:5062 +msgid "Filter by override date" +msgstr "Filter op override-datum" + +#: includes/post-types-admin.php:4981 +msgid "Override Logo" +msgstr "Override-logo" + +#: includes/post-types-admin.php:4896 +msgid "Inactive" +msgstr "Niet actief" + +#: includes/extra-strings.php:170 includes/post-types-admin.php:4746 +msgid "Image" +msgstr "Afbeelding" + +#: includes/post-types-admin.php:3494 +msgid "Override Schedule" +msgstr "Override-schema" + +#: includes/post-types-admin.php:2877 +msgid "All show days" +msgstr "Alle programmadagen" + +#: includes/post-types-admin.php:2875 +msgid "Filter by show day" +msgstr "Filter op programmadag" + +#: includes/post-types-admin.php:2773 +msgid "This shift is disabled as no day is set." +msgstr "" + +#: includes/post-types-admin.php:2731 +msgid "This Show is not currently active." +msgstr "Dit programma is op dit moment niet actief." + +#: includes/post-types-admin.php:2722 +msgid "This Shift has Schedule Conflicts." +msgstr "Het tijdblok heeft planning conflicten." + +#: includes/post-types-admin.php:2720 +msgid "This Shift has Schedule Conflicts and is Disabled." +msgstr "Dit tijdblok heeft schemaconflicten en is uitgeschakeld." + +#: includes/post-types-admin.php:2711 +msgid "This Shift is Disabled." +msgstr "Dit tijdblok is uitgeschakeld." + +#: includes/extra-strings.php:482 includes/post-types-admin.php:2653 +#: includes/post-types-admin.php:2665 includes/post-types-admin.php:4952 +msgid "No" +msgstr "Nee" + +#: includes/extra-strings.php:483 includes/post-types-admin.php:2651 +#: includes/post-types-admin.php:2667 includes/post-types-admin.php:4954 +msgid "Yes" +msgstr "Ja" + +#: includes/post-types-admin.php:2635 includes/post-types-admin.php:2823 +#: includes/post-types-admin.php:3120 +msgid "Show Avatar" +msgstr "Toon avatar" + +#: includes/extra-strings.php:266 includes/post-types-admin.php:2630 +#: includes/post-types-admin.php:3164 includes/post-types.php:193 +msgid "Hosts" +msgstr "Presentatoren" + +#: includes/post-types-admin.php:2628 +msgid "Shifts" +msgstr "Tijdblokken" + +#: includes/post-types-admin.php:2627 +msgid "About?" +msgstr "Over?" + +#: includes/post-types-admin.php:2625 +msgid "Active?" +msgstr "Actief?" + +#: includes/post-types-admin.php:2480 +msgid "Warning! Shift conflicts detected." +msgstr "" + +#: includes/post-types-admin.php:2045 +msgid "Failed. Invalid Show." +msgstr "" + +#: includes/post-types-admin.php:1844 +msgid "Remove Show Header Image" +msgstr "Verwijder afbeelding van programmakop" + +#: includes/post-types-admin.php:1837 +msgid "Set Show Header Image" +msgstr "Stel afbeelding van programma header in" + +#: includes/post-types-admin.php:1801 +msgid "Remove Show Avatar Image" +msgstr "Verwijder afbeelding van programma-avatar" + +#: includes/post-types-admin.php:1794 +msgid "Set Show Avatar Image" +msgstr "Stel afbeelding van programma-avatar in" + +#: includes/post-types-admin.php:1746 +msgid "Show Images" +msgstr "Toon afbeeldingen" + +#: includes/post-types-admin.php:1730 +msgid "Show Logo" +msgstr "Toon logo" + +#: includes/post-types-admin.php:1702 +msgid "This way you can then assign them to a relevant Post Category for display on your site also." +msgstr "" + +#: includes/post-types-admin.php:1701 +msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." +msgstr "" + +#: includes/post-types-admin.php:1698 +msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." +msgstr "" + +#: includes/post-types-admin.php:1697 +msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." +msgstr "" + +#: includes/post-types-admin.php:1696 +msgid "The text field below is for your Show Description. It will display in the About section of your Show page." +msgstr "" + +#: includes/post-types-admin.php:1680 +msgid "Show Description" +msgstr "Toon beschrijving" + +#: includes/post-types-admin.php:1632 +msgid "This Show" +msgstr "Dit programma" + +#: includes/post-types-admin.php:1626 +msgid "Shift Conflicts" +msgstr "Conflicten tussen tijdblokken" + +#: includes/post-types-admin.php:1271 +msgid "Then you can uncheck the shift Disable box and Save again to re-enable the Shift." +msgstr "" + +#: includes/post-types-admin.php:1270 +msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." +msgstr "Repareer het tijdblok en/of het tijdblok in het conflicterende programma en werk ze beide bij." + +#: includes/post-types-admin.php:1269 +msgid "Please note that Shifts with conflicts are automatically disabled upon saving." +msgstr "Let op dat tijdblokken met conflicten automatisch worden uitgeschakeld bij het opslaan." + +#: includes/post-types-admin.php:1268 +msgid "Warning! Show Shift Conflicts were detected!" +msgstr "Waarschuwing! Er zijn conflicten tussen programmatijdblokken gedetecteerd!" + +#: includes/post-types-admin.php:1248 includes/post-types-admin.php:1616 +msgid "Remove Shift" +msgstr "Verwijder tijdbok" + +#: includes/post-types-admin.php:1243 includes/post-types-admin.php:1609 +msgid "Duplicate Shift" +msgstr "Dupliceer tijdblok" + +#: includes/extra-strings.php:285 includes/post-types-admin.php:1238 +#: includes/post-types-admin.php:1602 includes/post-types-admin.php:3889 +#: includes/post-types-admin.php:4208 includes/post-types-admin.php:4902 +msgid "Disabled" +msgstr "Uitgeschakeld" + +#: includes/extra-strings.php:368 includes/post-types-admin.php:1231 +#: includes/post-types-admin.php:1591 +msgid "Encore" +msgstr "Herhaling" + +#: includes/extra-strings.php:284 includes/post-types-admin.php:1197 +#: includes/post-types-admin.php:1546 includes/post-types-admin.php:3818 +#: includes/post-types-admin.php:4155 +msgid "End Time" +msgstr "Eindtijd" + +#: includes/extra-strings.php:283 includes/post-types-admin.php:1163 +#: includes/post-types-admin.php:1502 includes/post-types-admin.php:3777 +#: includes/post-types-admin.php:4120 +msgid "Start Time" +msgstr "Starttijd" + +#: includes/post-types-admin.php:1149 includes/post-types-admin.php:1487 +#: radio-station.php:1013 +msgid "Day" +msgstr "Dag" + +#: includes/post-types-admin.php:981 +msgid "Are you sure you want to clear the shift list?" +msgstr "Weet je zeker dat je deze tijd wil verwijderen?" + +#: includes/post-types-admin.php:980 +msgid "Are you sure you want to remove this shift?" +msgstr "Weet je zeker dat je dit tijdblok wilt verwijderen?" + +#: includes/post-types-admin.php:887 +msgid "Show Shifts Saved." +msgstr "Toon opgeslagen diensten." + +#: includes/post-types-admin.php:886 +msgid "Saving Show Shifts..." +msgstr "Programma diensten opslaan..." + +#: includes/extra-strings.php:376 includes/post-types-admin.php:879 +msgid "Add Shift" +msgstr "Voeg tijdblok toe" + +#: includes/extra-strings.php:339 includes/post-types-admin.php:875 +msgid "Save Shifts" +msgstr "Diensten opslaan" + +#: includes/extra-strings.php:384 includes/post-types-admin.php:871 +msgid "Clear Shifts" +msgstr "Diensten verwijderen" + +#: includes/post-types-admin.php:839 +msgid "All Shifts are inactive also until Show is activated." +msgstr "Alle afspeellijsten zijn ook niet actief totdat progamma is geactiveerd." + +#: includes/post-types-admin.php:838 +msgid "This Show is inactive!" +msgstr "Dit programma is niet actief!" + +#: includes/post-types-admin.php:792 +msgid "Show Schedule" +msgstr "Programmaschema" + +#: includes/post-types-admin.php:696 includes/post-types-admin.php:775 +msgid "Ctrl-Click selects multiple." +msgstr "Ctrl-klik kiest meerdere." + +#: includes/extra-strings.php:320 includes/post-types-admin.php:624 +msgid "DJs / Hosts" +msgstr "DJ's / presentatoren" + +#: includes/post-types-admin.php:554 +msgid "Show Language(s)" +msgstr "Toon taal(en)" + +#: includes/post-types-admin.php:550 includes/post-types-admin.php:710 +msgid "Show Producer(s)" +msgstr "Programmaproducer(s)" + +#: includes/post-types-admin.php:546 +msgid "Show DJ(s) / Host(s)" +msgstr "Toon DJ('s)/presentator(en)" + +#: includes/extra-strings.php:262 includes/post-types-admin.php:527 +#: includes/post-types-admin.php:3319 +msgid "Patreon Page ID" +msgstr "Patreon-pagina-ID" + +#: includes/post-types-admin.php:517 includes/post-types-admin.php:3296 +msgid "Disable Download" +msgstr "Schakel download uit" + +#: includes/post-types-admin.php:506 +msgid "Latest Audio File" +msgstr "Laatste audiobestand" + +#: includes/post-types-admin.php:496 includes/post-types-admin.php:3252 +msgid "Show Phone" +msgstr "programma telefoon" + +#: includes/post-types-admin.php:485 includes/post-types-admin.php:3230 +msgid "Show Email" +msgstr "programma e-mail" + +#: includes/extra-strings.php:259 includes/post-types-admin.php:474 +#: includes/post-types-admin.php:3208 +msgid "Website Link" +msgstr "Sitelink" + +#: includes/post-types-admin.php:467 +msgid "Check this box if show is currently active (Show will not appear on schedule if unchecked.)" +msgstr "Vink dit vakje aan als de voorstelling momenteel actief is (De voorstelling verschijnt niet op het schema als ze niet aangevinkt is.)" + +#: includes/post-types-admin.php:461 +msgid "Active" +msgstr "Actief" + +#: includes/post-types-admin.php:415 +msgid "Show Information" +msgstr "Programma-informatie" + +#: includes/post-types-admin.php:6848 +msgid "Failed to Update Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:6842 +msgid "Updated Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:6707 +msgid "Set Related Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6574 +msgid "Show(s)" +msgstr "programma(s)" + +#: includes/extra-strings.php:139 includes/post-types-admin.php:6199 +#: includes/post-types-admin.php:6549 +msgid "No Shows available to Select." +msgstr "" + +#: includes/post-types-admin.php:6534 +msgid "Related Show(s)" +msgstr "Gerelateerde programma('s)" + +#: includes/extra-strings.php:97 includes/post-types-admin.php:6391 +msgid "No Shows to Select." +msgstr "Geen programma's om te selecteren." + +#: includes/post-types-admin.php:6361 +msgid "Select Show(s)" +msgstr "Selecteer programma(s)" + +#: includes/post-types-admin.php:6298 +msgid "Related to Show" +msgstr "Verwant met programma" + +#: includes/post-types-admin.php:6079 +msgid "Show/Hide Tracklist" +msgstr "Toon/verberg lijst met nummers" + +#: includes/post-types-admin.php:6005 +msgid "Track List" +msgstr "Lijst van nummers" + +#: includes/post-types-admin.php:6004 +msgid "Tracks" +msgstr "Nummers" + +#: includes/extra-strings.php:311 includes/post-types-admin.php:2036 +#: includes/post-types-admin.php:4276 +msgid "Failed. Publish or Update instead." +msgstr "" + +#: includes/post-types-admin.php:5840 +msgid "Failed. Invalid Playlist ID." +msgstr "" + +#: includes/extra-strings.php:309 includes/post-types-admin.php:2038 +#: includes/post-types-admin.php:4272 includes/post-types-admin.php:5833 +msgid "Expired. Publish or Update instead." +msgstr "" + +#: includes/post-types-admin.php:5666 includes/post-types-admin.php:5789 +#: includes/post-types-admin.php:6187 +msgid "Unassigned" +msgstr "Niet-toegewezen" + +#: includes/post-types-admin.php:5655 +msgid "You are not assigned to any Shows." +msgstr "Je bent niet toegewezen aan programma's." + +#: includes/post-types-admin.php:5649 +msgid "No Shows were found." +msgstr "Er zijn geen programma's gevonden." + +#: includes/post-types-admin.php:4741 includes/post-types-admin.php:5567 +msgid "Linked Show" +msgstr "Gelinkt programma" + +#: includes/post-types-admin.php:5435 templates/legacy/single-playlist.php:48 +msgid "Record Label" +msgstr "Platenlabel" + +#: includes/post-types-admin.php:5434 includes/shortcodes.php:4064 +#: templates/legacy/single-playlist.php:47 +#: templates/single-playlist-content.php:50 +msgid "Album" +msgstr "Album" + +#: includes/post-types-admin.php:5433 includes/post-types-admin.php:6083 +#: includes/shortcodes.php:4049 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:49 +msgid "Song" +msgstr "Nummer" + +#: includes/post-types-admin.php:5432 includes/post-types-admin.php:6084 +#: includes/shortcodes.php:4057 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:48 +msgid "Artist" +msgstr "Artiest" + +#: includes/post-types-admin.php:5304 includes/post-types-admin.php:5540 +msgid "Move" +msgstr "Verplaatsen" + +#: includes/post-types-admin.php:5301 includes/post-types-admin.php:5534 +msgid "Played" +msgstr "Afgespeeld" + +#: includes/post-types-admin.php:5300 includes/post-types-admin.php:5533 +msgid "Queued" +msgstr "In de wachtrij" -#: includes/dj-on-air.php:480 -msgid "None Upcoming" -msgstr "Geen volgende" +#: includes/post-types-admin.php:5298 includes/post-types-admin.php:5530 +#: includes/post-types-admin.php:6085 +msgid "Status" +msgstr "Status" -#: includes/dj-on-air.php:495 -msgid "The current on-air DJ." -msgstr "DJ die nu on air is." +#: includes/post-types-admin.php:5296 includes/post-types-admin.php:5525 +msgid "New" +msgstr "Nieuw" -#: includes/dj-on-air.php:496 -msgid "Radio Station: Show/DJ On-Air" -msgstr "Radiostation: programma/DJ on air" +#: includes/post-types-admin.php:5295 includes/post-types-admin.php:5518 +#: includes/shortcodes.php:4078 templates/single-playlist-content.php:52 +msgid "Comments" +msgstr "Reacties" -#: includes/dj-on-air.php:511 includes/dj-on-air.php:738 -#: includes/playlist.php:436 -msgid "Title" -msgstr "Titel" +#: includes/post-types-admin.php:5182 +msgid "Are you sure you want to clear the track list?" +msgstr "" -#: includes/dj-on-air.php:519 includes/dj-on-air.php:746 -msgid "Show Avatars" -msgstr "Toon avatars" +#: includes/post-types-admin.php:5179 +msgid "Tracks marked New are moved to the end of Playlist on update." +msgstr "" -#: includes/dj-on-air.php:526 -msgid "Link to the Show/DJ's profile" -msgstr "Link naar het profiel van programma/DJ" +#: includes/post-types-admin.php:5170 +msgid "Playlist Tracks Saved." +msgstr "" -#: includes/dj-on-air.php:533 includes/dj-on-air.php:767 -msgid "Display schedule info for this show" -msgstr "Toon de planning van dit programma" +#: includes/post-types-admin.php:5169 +msgid "Saving Playlist Tracks..." +msgstr "" -#: includes/dj-on-air.php:540 -msgid "Display link to show's playlist" -msgstr "Toon de link van de afspeellijst van dit programma" +#: includes/post-types-admin.php:5164 +msgid "Add Track" +msgstr "Voeg nummer toe" -#: includes/dj-on-air.php:545 -msgid "Default DJ Name" -msgstr "Standaard-DJ-naam" +#: includes/post-types-admin.php:5161 +msgid "Save Tracks" +msgstr "" -#: includes/dj-on-air.php:548 includes/dj-on-air.php:761 -msgid "If no Show/DJ is scheduled for the current hour, display this name/text." -msgstr "ALs er geen programma/DJ is gepland, toon dan deze naam/tekst." +#: includes/post-types-admin.php:5158 +msgid "Clear Tracks" +msgstr "" -#: includes/dj-on-air.php:552 includes/dj-on-air.php:779 -msgid "Time Format" -msgstr "Tijdformaat" +#: includes/post-types-admin.php:5143 includes/post-types-admin.php:5443 +msgid "Remove Track" +msgstr "" -#: includes/dj-on-air.php:554 includes/dj-on-air.php:781 -msgid "12-hour" -msgstr "12-uurs" +#: includes/post-types-admin.php:5142 includes/post-types-admin.php:5442 +msgid "Duplicate Track" +msgstr "" -#: includes/dj-on-air.php:555 includes/dj-on-air.php:782 -msgid "24-hour" -msgstr "24-uurs" +#: includes/post-types-admin.php:5141 includes/post-types-admin.php:5441 +msgid "Move Track Down" +msgstr "" -#: includes/dj-on-air.php:558 -msgid "Choose time format for displayed schedules" -msgstr "Kies het tijdformaat voor de weergegeven planningen" +#: includes/post-types-admin.php:5140 includes/post-types-admin.php:5440 +msgid "Move Track Up" +msgstr "" -#: includes/dj-on-air.php:722 -msgid "The upcoming DJs/Shows." -msgstr "De volgende DJ's/programma's" +#: includes/post-types-admin.php:5120 +msgid "Playlist Entries" +msgstr "Afspeellijst-ingaven" -#: includes/dj-on-air.php:723 -msgid "Radio Station: Upcoming DJ On-Air" -msgstr "Radiostation: de volgende onair-DJ" +#: includes/post-types-admin.php:264 +msgid "Click on a Language to Add it." +msgstr "Klik op een taal om die toe te voegen." -#: includes/dj-on-air.php:753 -msgid "Link to Show/DJ's user profile" -msgstr "Link naar gebruikersprofiel van programma/DJ" +#: includes/post-types-admin.php:248 +msgid "Select Language" +msgstr "Kies taal" -#: includes/dj-on-air.php:758 -msgid "No Additional Schedules" -msgstr "Geen extra planningen" +#: includes/post-types-admin.php:239 +msgid "Remove Language" +msgstr "Verwijder taal" -#: includes/dj-on-air.php:772 -msgid "Limit" -msgstr "Limiet" +#: includes/post-types-admin.php:213 +msgid "Select below if Show language(s) differ." +msgstr "Kies hieronder als de programmataal afwijkt." -#: includes/dj-on-air.php:775 -msgid "Number of upcoming DJs/Shows to display." -msgstr "Aantal volgende DJ's/Shows om weer te geven" +#: includes/post-types-admin.php:209 +msgid "Main Radio Language" +msgstr "Primaire radiotaal" -#: includes/dj-on-air.php:785 -msgid "Choose time format for displayed schedules." -msgstr "Kies het tijdformaat voor de getoonde planningen" +#: includes/post-types-admin.php:172 +msgid "Show Language" +msgstr "Toon taal" -#: includes/master_schedule.php:21 includes/master_schedule.php:22 -msgid "Schedule Override" -msgstr "Planning-override" +#: includes/post-types.php:520 +msgid "Language" +msgstr "Taal" -#: includes/master_schedule.php:23 includes/master_schedule.php:24 -msgid "Add Schedule Override" -msgstr "Voeg planning-override toe" +#: includes/post-types.php:519 +msgid "New Language Name" +msgstr "Nieuwe naam van taal" -#: includes/master_schedule.php:25 -msgid "Edit Schedule Override" -msgstr "Bewerk planning-override" +#: includes/post-types.php:518 +msgid "Add New Language" +msgstr "Voeg nieuwe taal toe" -#: includes/master_schedule.php:26 -msgid "New Schedule Override" -msgstr "Nieuwe planning-override" +#: includes/post-types.php:517 +msgid "Update Language" +msgstr "Taal bijwerken" -#: includes/master_schedule.php:27 -msgid "View Schedule Override" -msgstr "Bekijk planning-override" +#: includes/post-types.php:516 +msgid "Edit Language" +msgstr "Bewerk taal" -#: includes/master_schedule.php:30 -msgid "Post type for Schedule Override" -msgstr "Post het type voor de planning-override" +#: includes/post-types.php:515 +msgid "Parent Language:" +msgstr "Hoofdtaal:" -#: includes/master_schedule.php:49 -msgid "Override Schedule" -msgstr "Overrideplanning" +#: includes/post-types.php:514 +msgid "Parent Language" +msgstr "Hoofdtaal" -#: includes/master_schedule.php:79 -msgid "Date" -msgstr "Datum" +#: includes/post-types.php:513 +msgid "All Languages" +msgstr "Alle talen" -#: includes/master_schedule.php:82 includes/playlist.php:756 -#: includes/playlist.php:835 -msgid "Start Time" -msgstr "Starttijd" +#: includes/post-types.php:512 +msgid "Search Languages" +msgstr "Zoek talen" -#: includes/master_schedule.php:108 includes/playlist.php:782 -#: includes/playlist.php:861 -msgid "End Time" -msgstr "Eindtijd" +#: includes/post-types.php:511 +msgctxt "taxonomy singular name" +msgid "Language" +msgstr "Taal" -#: includes/master_schedule.php:432 includes/master_schedule.php:595 -msgid "encore airing" -msgstr "herhaling" +#: includes/post-types.php:510 +msgctxt "taxonomy general name" +msgid "Languages" +msgstr "Talen" -#: includes/master_schedule.php:437 includes/master_schedule.php:600 -msgid "Audio File" -msgstr "Audiobestand" +#: includes/post-types.php:474 +msgid "Genre" +msgstr "Genre" -#: includes/master_schedule.php:454 -msgid "Sun" -msgstr "Zo" +#: includes/post-types.php:473 +msgid "New Genre Name" +msgstr "Nieuwe genrenaam" -#: includes/master_schedule.php:454 -msgid "Mon" -msgstr "Ma" +#: includes/post-types.php:472 +msgid "Add New Genre" +msgstr "Voeg nieuw genre toe" -#: includes/master_schedule.php:454 -msgid "Tue" -msgstr "Di" +#: includes/post-types.php:471 +msgid "Update Genre" +msgstr "Update genre" -#: includes/master_schedule.php:454 -msgid "Wed" -msgstr "Wo" +#: includes/post-types.php:470 +msgid "Edit Genre" +msgstr "Bewerk genre" -#: includes/master_schedule.php:454 -msgid "Thu" -msgstr "Do" +#: includes/post-types.php:469 +msgid "Parent Genre:" +msgstr "Hoofdgenre:" -#: includes/master_schedule.php:454 -msgid "Fri" -msgstr "Vr" +#: includes/post-types.php:468 +msgid "Parent Genre" +msgstr "Hoofdgenre" -#: includes/master_schedule.php:454 -msgid "Sat" -msgstr "Za" +#: includes/post-types.php:467 +msgid "All Genres" +msgstr "Alle genres" + +#: includes/post-types.php:466 +msgid "Search Genres" +msgstr "Zoek genres" + +#: includes/post-types.php:465 +msgctxt "taxonomy singular name" +msgid "Genre" +msgstr "Genre" -#: includes/master_schedule.php:619 includes/playlist.php:572 +#: includes/post-types.php:464 +msgctxt "taxonomy general name" msgid "Genres" msgstr "Genres" -#: includes/playlist.php:16 templates/single-show.php:136 -msgid "Playlists" -msgstr "Afspeellijsten" +#: includes/post-types.php:407 +msgid "Edit" +msgstr "Bewerk" -#: includes/playlist.php:17 -msgid "Playlist" -msgstr "Afspeellijst" +#: includes/post-types.php:252 +msgid "Show Producers Profile" +msgstr "Profiel van programmaproducer" -#: includes/playlist.php:18 includes/playlist.php:19 -msgid "Add Playlist" -msgstr "Voeg afspeellijst toe" +#: includes/post-types.php:251 +msgid "View Producer Profile" +msgstr "Bekijk producerprofiel" -#: includes/playlist.php:20 -msgid "Edit Playlist" -msgstr "Bewerk afspeellijst" +#: includes/post-types.php:249 +msgid "Edit Producer Profile" +msgstr "Bewerk profiel van producer" + +#: includes/post-types.php:248 +msgid "Add Producer Profile" +msgstr "Voeg producerprofiel toe" + +#: includes/post-types.php:250 +msgid "New Producer Profile" +msgstr "Nieuw producerprofiel" + +#: includes/extra-strings.php:256 +msgid "Producer Profile" +msgstr "Producerprofiel" + +#: includes/post-types.php:200 +msgid "Show Hosts" +msgstr "Toon programmapresentatoren" + +#: includes/post-types.php:199 +msgid "View Host Profile" +msgstr "Bekijk presentatorprofiel" + +#: includes/post-types.php:197 +msgid "Edit Host Profile" +msgstr "Bewerk profiel van presentator" + +#: includes/post-types.php:196 +msgid "Add Host Profile" +msgstr "Voeg presentatorprofiel toe" + +#: includes/post-types.php:198 +msgid "New Host Profile" +msgstr "Nieuw presentatorprofiel" -#: includes/playlist.php:21 +#: includes/extra-strings.php:254 +msgid "Host Profile" +msgstr "Presentator-profiel" + +#: includes/extra-strings.php:473 includes/post-types.php:153 +msgid "Overrides" +msgstr "" + +#: includes/post-types-admin.php:5094 includes/post-types.php:152 +msgid "All Overrides" +msgstr "" + +#: includes/post-types.php:151 +msgid "No Overrides found in Trash" +msgstr "" + +#: includes/post-types.php:150 +msgid "No Overrides found" +msgstr "" + +#: includes/post-types.php:149 +msgid "Search Overrides" +msgstr "" + +#: includes/post-types.php:147 +msgid "View Schedule Override" +msgstr "Bekijk override van de planning" + +#: includes/post-types.php:146 +msgid "New Schedule Override" +msgstr "Nieuwe override van het schema" + +#: includes/post-types.php:145 +msgid "Edit Schedule Override" +msgstr "Bewerk override van planning" + +#: includes/post-types.php:143 includes/post-types.php:144 +msgid "Add Schedule Override" +msgstr "Maak override van het schema" + +#: includes/extra-strings.php:365 includes/post-types.php:142 +msgid "Schedule Override" +msgstr "Schema-override" + +#: includes/post-types.php:141 radio-station-admin.php:220 +msgid "Schedule Overrides" +msgstr "Schema-overrides" + +#: includes/post-types.php:106 +msgid "All Playlists" +msgstr "Alle afspeellijsten" + +#: includes/post-types.php:105 +msgid "No Playlists found in Trash" +msgstr "Geen afspeellijst gevonden in de prullenbak" + +#: includes/post-types.php:104 +msgid "No Playlists found" +msgstr "Geen afspeellijst gevonden" + +#: includes/post-types.php:103 +msgid "Search Playlists" +msgstr "Zoek afspeellijsten" + +#: includes/post-types.php:99 includes/shortcodes.php:2848 +#: includes/shortcodes.php:4099 +msgid "View Playlist" +msgstr "Bekijk afspeellijst" + +#: includes/post-types.php:98 msgid "New Playlist" msgstr "Nieuwe afspeellijst" -#: includes/playlist.php:25 -msgid "Post type for Playlist descriptions" -msgstr "Plaats type voor omschrijvingen van de afspeellijsten" +#: includes/post-types.php:97 +msgid "Edit Playlist" +msgstr "Bewerk afspeellijst" -#: includes/playlist.php:42 -msgid "Shows" -msgstr "Programma's" +#: includes/post-types.php:95 includes/post-types.php:96 +msgid "Add Playlist" +msgstr "Voeg afspeellijst toe" -#: includes/playlist.php:43 includes/playlist.php:191 -msgid "Show" -msgstr "Programma" +#: includes/post-types.php:94 +msgid "Playlist" +msgstr "Afspeellijst" -#: includes/playlist.php:44 includes/playlist.php:45 -msgid "Add Show" -msgstr "Voeg programma toe" +#: includes/extra-strings.php:474 includes/post-types.php:93 +#: includes/post-types.php:101 radio-station-admin.php:217 +msgid "Playlists" +msgstr "Afspeellijsten" -#: includes/playlist.php:46 -msgid "Edit Show" -msgstr "Bewerk programma" +#: includes/extra-strings.php:136 includes/post-types.php:56 +msgid "All Shows" +msgstr "Alle uitzendingen" -#: includes/playlist.php:47 -msgid "New Show" -msgstr "Nieuw programma" +#: includes/post-types.php:55 +msgid "No Shows found in Trash" +msgstr "Geen uitzendingen gevonden in de prullenbak" + +#: includes/post-types.php:54 +msgid "No Shows found" +msgstr "Geen uitzendingen gevonden" -#: includes/playlist.php:48 +#: includes/post-types.php:53 +msgid "Search Shows" +msgstr "Zoek uitzendingen" + +#: includes/post-types.php:49 msgid "View Show" msgstr "Bekijk programma" -#: includes/playlist.php:51 -msgid "Post type for Show descriptions" -msgstr "Plaats type voor de programma-omschrijvingen" +#: includes/extra-strings.php:371 includes/post-types.php:48 +msgid "New Show" +msgstr "Nieuw programma" -#: includes/playlist.php:71 -msgid "Playlist Entries" -msgstr "Afspeellijst-invoer" +#: includes/extra-strings.php:134 includes/post-types-admin.php:6053 +#: includes/post-types-admin.php:6620 includes/post-types.php:47 +msgid "Edit Show" +msgstr "Bewerk programma" -#: includes/playlist.php:93 templates/single-playlist.php:43 -msgid "Artist" -msgstr "Artiest" +#: includes/post-types.php:45 includes/post-types.php:46 +msgid "Add Show" +msgstr "Voeg programma toe" -#: includes/playlist.php:93 templates/single-playlist.php:44 -msgid "Song" -msgstr "Nummer" +#: includes/extra-strings.php:94 includes/post-types-admin.php:6003 +#: includes/post-types.php:44 +msgid "Show" +msgstr "Programma" -#: includes/playlist.php:93 templates/single-playlist.php:45 -msgid "Album" -msgstr "Album" +#: includes/extra-strings.php:274 includes/post-types.php:43 +#: includes/post-types.php:51 radio-station-admin.php:214 +msgid "Shows" +msgstr "Programma's" -#: includes/playlist.php:93 templates/single-playlist.php:46 -msgid "Record Label" -msgstr "Platenlabel" +#: includes/class-upcoming-shows-widget.php:153 +msgid "Choose time format for displayed schedules." +msgstr "Kies de tijdnotatie voor de getoonde schema’s." -#: includes/playlist.php:93 templates/single-playlist.php:47 -msgid "DJ Comments" -msgstr "DJ-commentaar" +#: includes/class-upcoming-shows-widget.php:141 +#: widgets/class-upcoming-shows-widget.php:85 +msgid "Number of upcoming Shows to display." +msgstr "Aantal komende programma's om weer te geven." -#: includes/playlist.php:93 -msgid "New" -msgstr "Nieuw" +#: includes/class-upcoming-shows-widget.php:138 +#: widgets/class-upcoming-shows-widget.php:82 +msgid "Limit" +msgstr "Limiet" -#: includes/playlist.php:93 -msgid "Status" -msgstr "Status" +#: includes/class-upcoming-shows-widget.php:126 +msgid "If no Show is scheduled for the current time, display this text." +msgstr "Als er geen programma is gepland voor de huidige tijd, toon dan deze tekst." -#: includes/playlist.php:93 includes/playlist.php:127 includes/playlist.php:144 -#: includes/playlist.php:807 includes/playlist.php:888 -msgid "Remove" -msgstr "Verwijder" +#: includes/class-upcoming-shows-widget.php:123 +msgid "No Additional Schedules Text" +msgstr "Geen aanvullende schematekst" -#: includes/playlist.php:119 includes/playlist.php:144 -msgid "Queued" -msgstr "In de wachtrij" +#: includes/class-upcoming-shows-widget.php:117 +msgid "Link DJ names to author pages" +msgstr "Link DJ-namen aan auteurspagina's" -#: includes/playlist.php:123 includes/playlist.php:144 -msgid "Played" -msgstr "Afgespeeld" +#: includes/class-upcoming-shows-widget.php:110 +msgid "Display names of the DJs on the show" +msgstr "Toon namen van DJ's van het programma" -#: includes/playlist.php:136 -msgid "Add Entry" -msgstr "Voeg invoer toe" +#: includes/class-upcoming-shows-widget.php:104 +msgid "Width of Show Avatars (in pixels, default 75px)" +msgstr "Breedte van programma-avatar (in pixels, standaard 75 px)" -#: includes/playlist.php:163 includes/playlist.php:164 -#: templates/single-show.php:99 -msgid "Schedule" -msgstr "Planning" +#: includes/class-upcoming-shows-widget.php:95 +#: widgets/class-upcoming-shows-widget.php:157 +msgid "Display Show Avatars" +msgstr "Toon programma-avatars" -#: includes/playlist.php:166 includes/playlist.php:167 -msgid "Publish" -msgstr "Publiceren" +#: includes/class-upcoming-shows-widget.php:17 +#: widgets/class-upcoming-shows-widget.php:17 +msgid "(Radio Station) Upcoming Shows" +msgstr "(Radio Station) Komende programma's" -#: includes/playlist.php:170 -msgid "Submit for Review" -msgstr "Opsturen voor controle" +#: includes/class-upcoming-shows-widget.php:15 +#: widgets/class-upcoming-shows-widget.php:15 +msgid "Display the upcoming Shows." +msgstr "Toon de komende programma's." -#: includes/playlist.php:171 includes/playlist.php:176 -msgid "Update Playlist" -msgstr "Afspeellijst bijwerken" +#: includes/class-current-playlist-widget.php:108 +#: widgets/class-current-playlist-widget.php:88 +#: widgets/class-current-show-widget.php:117 +#: widgets/class-upcoming-shows-widget.php:120 +msgid "Hide Widget if Empty" +msgstr "Verberg lege widget" -#: includes/playlist.php:175 -msgid "Update" -msgstr "Bijwerken" +#: includes/class-current-playlist-widget.php:101 +#: widgets/class-current-playlist-widget.php:165 +msgid "Show DJ Comments" +msgstr "Toon DJ reacties" -#: includes/playlist.php:420 -msgid "Display the current song." -msgstr "Toon het huidige nummer." +#: includes/class-current-playlist-widget.php:94 +#: widgets/class-current-playlist-widget.php:157 +msgid "Show Record Label Name" +msgstr "Toon naam platenlabel" -#: includes/playlist.php:421 -msgid "Radio Station: Now Playing" -msgstr "Radiostation: nu speelt" +#: includes/class-current-playlist-widget.php:87 +#: widgets/class-current-playlist-widget.php:149 +msgid " Show Album Name" +msgstr " Toon albumtitel" -#: includes/playlist.php:444 +#: includes/class-current-playlist-widget.php:80 +#: widgets/class-current-playlist-widget.php:141 msgid "Show Artist Name" -msgstr "Toon naam van artiest" +msgstr "Toon naam artiest" -#: includes/playlist.php:451 +#: includes/class-current-playlist-widget.php:73 +#: widgets/class-current-playlist-widget.php:133 msgid "Show Song Title" msgstr "Toon titel van nummer" -#: includes/playlist.php:458 -msgid "Show Album Name" -msgstr "Toon titel van album" +#: includes/class-current-playlist-widget.php:66 +#: widgets/class-current-playlist-widget.php:106 +msgid "Link to Playlist?" +msgstr "Link naar afspeellijst?" -#: includes/playlist.php:465 -msgid "Show Record Label Name" -msgstr "Toon platenlabel" +#: includes/class-current-playlist-widget.php:17 +#: widgets/class-current-playlist-widget.php:17 +msgid "(Radio Station) Now Playing List" +msgstr "(Radio Station) Lijst 'nu te horen'" -#: includes/playlist.php:472 -msgid "Show DJ Comments" -msgstr "Toon DJ-commentaar" +#: includes/class-current-playlist-widget.php:15 +#: widgets/class-current-playlist-widget.php:15 +msgid "Display currently playing playlist." +msgstr "Toon huidige afspeellijst." -#: includes/playlist.php:573 templates/single-show.php:60 -msgid "Genre" -msgstr "Genre" +#. translators: 1: PHP function name, 2: Version number, 3: Alternative +#. function name. +#: includes/legacy.php:708 +msgid "%1$s is deprecated since Radio Station version %2$s! Use %3$s or check documentation for updated functionality." +msgstr "" -#: includes/playlist.php:594 -msgid "Information" -msgstr "Informatie" +#: includes/legacy.php:671 +msgid "More Show Blog Posts" +msgstr "Meer programmablogberichten" -#: includes/playlist.php:612 -msgid "Active" -msgstr "Actief" +#: includes/master-schedule.php:825 +msgid "Click to toggle Highlight of Shows with this Genre." +msgstr "Klik om te wisselen naar highlight van programma's met dit genre." -#: includes/playlist.php:614 -msgid "Check this box if show is currently active (Show will not appear on programming schedule if unchecked)" -msgstr "Vink deze box aan als het huidige programma actief is (Het programma zal niet verschijnen in de programmaplanning indien niet aangevinkt)" +#: includes/extra-strings.php:435 includes/master-schedule.php:815 +#: radio-station-admin.php:219 templates/master-schedule-list.php:459 +#: templates/master-schedule-tabs.php:549 +msgid "Genres" +msgstr "Genres" -#: includes/playlist.php:616 -msgid "Current Audio File" -msgstr "Huidig audiobestand" +#: includes/class-radio-clock-widget.php:95 +msgid "Include timezone display." +msgstr "Inclusief tijdzone weergave." -#: includes/playlist.php:619 -msgid "DJ Email" -msgstr "E-mail DJ" +#: includes/class-radio-clock-widget.php:89 +msgid "Display month with clock times." +msgstr "Maandweergave met tijden" -#: includes/playlist.php:622 -msgid "Website Link" -msgstr "Website-link" +#: includes/class-radio-clock-widget.php:81 +msgid "Month Display" +msgstr "maandweergave" -#: includes/playlist.php:633 -msgid "DJs" -msgstr "DJ's" +#: includes/class-radio-clock-widget.php:76 +msgid "Include date display." +msgstr "laat datum zien" -#: includes/playlist.php:719 -msgid "Schedules" -msgstr "Planningen" +#: includes/class-radio-clock-widget.php:70 +msgid "Display day with clock times." +msgstr "Dagweergave met tijden" -#: includes/playlist.php:744 includes/playlist.php:824 -msgid "Day" -msgstr "Dag" +#: includes/class-radio-clock-widget.php:66 +#: includes/class-radio-clock-widget.php:85 includes/extra-strings.php:499 +#: includes/post-types-admin.php:4962 options.php:690 +#: widgets/class-radio-clock-widget.php:51 +#: widgets/class-radio-clock-widget.php:70 +msgid "None" +msgstr "Geen" -#: includes/playlist.php:747 includes/playlist.php:827 -msgid "Monday" -msgstr "Maandag" +#: includes/class-radio-clock-widget.php:65 +#: includes/class-radio-clock-widget.php:84 includes/extra-strings.php:522 +#: widgets/class-radio-clock-widget.php:50 +#: widgets/class-radio-clock-widget.php:69 +msgid "Short" +msgstr "Kort" -#: includes/playlist.php:748 includes/playlist.php:828 -msgid "Tuesday" -msgstr "Dinsdag" +#: includes/class-radio-clock-widget.php:64 +#: includes/class-radio-clock-widget.php:83 includes/extra-strings.php:501 +#: widgets/class-radio-clock-widget.php:49 +#: widgets/class-radio-clock-widget.php:68 +msgid "Full" +msgstr "Volledig" -#: includes/playlist.php:749 includes/playlist.php:829 -msgid "Wednesday" -msgstr "Woensdag" +#: includes/class-radio-clock-widget.php:62 +msgid "Day Display" +msgstr "Dagweergave" -#: includes/playlist.php:750 includes/playlist.php:830 -msgid "Thursday" -msgstr "Donderdag" +#: includes/class-radio-clock-widget.php:57 +msgid "Include seconds display." +msgstr "" -#: includes/playlist.php:751 includes/playlist.php:831 -msgid "Friday" -msgstr "Vrijdag" +#: includes/class-radio-clock-widget.php:23 includes/extra-strings.php:518 +#: widgets/class-radio-clock-widget.php:23 +msgid "Radio Clock" +msgstr "Radio klok" -#: includes/playlist.php:752 includes/playlist.php:832 -msgid "Saturday" -msgstr "Zaterdag" +#: includes/class-radio-clock-widget.php:16 +#: widgets/class-radio-clock-widget.php:16 +msgid "(Radio Station) Radio Clock" +msgstr "" -#: includes/playlist.php:753 includes/playlist.php:833 -msgid "Sunday" -msgstr "Zondag" +#: includes/class-radio-clock-widget.php:14 +#: widgets/class-radio-clock-widget.php:14 +msgid "Display current radio and user times." +msgstr "" -#: includes/playlist.php:806 includes/playlist.php:886 -msgid "Encore Presentation" -msgstr "Herhaling" +#: includes/class-current-playlist-widget.php:115 +#: includes/class-current-show-widget.php:183 +#: includes/class-upcoming-shows-widget.php:159 +#: widgets/class-current-playlist-widget.php:123 +#: widgets/class-current-show-widget.php:199 +#: widgets/class-upcoming-shows-widget.php:194 +msgid "Display Countdown Timer" +msgstr "Toon aftelklok" -#: includes/playlist.php:817 -msgid "Add Shift" -msgstr "Voeg tijdblok toe" +#: includes/class-current-show-widget.php:177 +#: includes/class-radio-clock-widget.php:51 +msgid "Choose time format for displayed schedules" +msgstr "Kies de tijdnotatie voor de getoonde schema’s" -#: includes/playlist.php:980 -msgid "More Playlists" -msgstr "Meer afspeellijsten" +#: includes/class-current-show-widget.php:173 +#: includes/class-radio-clock-widget.php:47 +#: includes/class-upcoming-shows-widget.php:149 includes/extra-strings.php:496 +#: widgets/class-current-show-widget.php:209 +#: widgets/class-radio-clock-widget.php:89 +#: widgets/class-upcoming-shows-widget.php:204 +msgid "24 Hour" +msgstr "24-uurs" -#: includes/playlist.php:1041 -msgid "More Blog Posts" -msgstr "Meer blogmeldingen" +#: includes/class-current-show-widget.php:172 +#: includes/class-radio-clock-widget.php:46 +#: includes/class-upcoming-shows-widget.php:148 includes/extra-strings.php:495 +#: widgets/class-current-show-widget.php:208 +#: widgets/class-radio-clock-widget.php:88 +#: widgets/class-upcoming-shows-widget.php:203 +msgid "12 Hour" +msgstr "12-uurs" -#: templates/archive-playlist.php:15 -msgid "Playlist Archive for" -msgstr "Archief van afspeellijst voor" +#: includes/class-current-show-widget.php:169 +#: includes/class-radio-clock-widget.php:43 +#: includes/class-upcoming-shows-widget.php:145 +msgid "Time Format" +msgstr "Tijd format" -#: templates/archive-playlist.php:50 templates/single-playlist.php:13 -#: templates/single-show.php:13 -msgid "Post navigation" -msgstr "Blognavigatie" +#: includes/class-current-show-widget.php:165 +msgid "Text to display if no Show is scheduled for the current time." +msgstr "" -#: templates/archive-playlist.php:51 -msgid "Older posts" -msgstr "Oudere meldingen" +#: includes/class-current-show-widget.php:162 +msgid "No Show Display Text" +msgstr "Geen weergavetekst van het programma" -#: templates/archive-playlist.php:52 -msgid "Newer posts" -msgstr "Nieuwe meldingen" +#: includes/class-current-show-widget.php:156 +msgid "Display link to show's playlist" +msgstr "Toon link van afspeellijst van dit programma" -#: templates/archive-playlist.php:61 templates/author.php:93 -#: templates/playlist-archive-template.php:63 -#: templates/show-blog-archive-template.php:71 -msgid "Nothing Found" -msgstr "Niets gevonden" +#: includes/class-current-show-widget.php:149 +msgid "Display description of show" +msgstr "Toon beschrijving van programma" -#: templates/archive-playlist.php:65 templates/author.php:97 -#: templates/playlist-archive-template.php:67 -#: templates/show-blog-archive-template.php:75 -msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." -msgstr "Excuses, maar er is niets gevonden in het archief. Misschien helpt de zoekfunctie om een melding te vinden." +#: includes/class-current-show-widget.php:142 +#: widgets/class-current-show-widget.php:252 +#: widgets/class-upcoming-shows-widget.php:231 +msgid "Display encore presentation text for Show" +msgstr "Toon informatie over de herhaling van de uitzending" -#: templates/author.php:51 -#, php-format -msgid "Author Archives: %s" -msgstr "Auteurarchieven: %s" +#: includes/class-current-show-widget.php:135 +msgid "Display multiple schedules (if show airs more than once per week)" +msgstr "Toon meerdere afspeelijsten (als programma meer dan eens per week wordt uitgezonden)" -#: templates/author.php:70 -#, php-format -msgid "About %s" -msgstr "Over: %s" +#: includes/class-current-show-widget.php:128 +#: includes/class-upcoming-shows-widget.php:132 +msgid "Display schedule info for this show" +msgstr "Toon schema van dit programma" -#: templates/playlist-archive-template.php:16 -msgid "Playlist Archive" -msgstr "Archief afspeellijsten" +#: includes/class-current-show-widget.php:121 +msgid "Link Host/DJ names to author pages" +msgstr "" -#: templates/show-blog-archive-template.php:16 -msgid "Blog Archive" -msgstr "Blogarchief" +#: includes/class-current-show-widget.php:114 +msgid "Display names of the DJs on the Show" +msgstr "Toon namen van DJ's van het programma" -#: templates/show-blog-archive-template.php:46 -msgid "Posted by" -msgstr "Geplaatst door" +#: includes/class-current-show-widget.php:108 +msgid "Width of Show Avatar (in pixels, default full width)" +msgstr "Breedte van programma-avatar (in pixels, standaard volledige breedte)" -#: templates/single-playlist.php:14 templates/single-show.php:14 -msgid "Previous" -msgstr "Vorige" +#: includes/class-current-show-widget.php:105 +#: includes/class-upcoming-shows-widget.php:101 +#: widgets/class-current-show-widget.php:171 +#: widgets/class-upcoming-shows-widget.php:174 +msgid "Avatar Width" +msgstr "Avatarbreedte" -#: templates/single-playlist.php:15 templates/single-show.php:15 -msgid "Next" -msgstr "Volgende" +#: includes/class-current-show-widget.php:99 +#: widgets/class-current-show-widget.php:154 +msgid "Display Show Avatar" +msgstr "Toon programma-avatar" -#: templates/single-playlist.php:32 templates/single-show.php:144 -msgid "Pages:" -msgstr "Pagina's:" +#: includes/class-current-show-widget.php:92 +#: includes/class-upcoming-shows-widget.php:88 +#: widgets/class-current-show-widget.php:145 +#: widgets/class-upcoming-shows-widget.php:148 +msgid "Show Title Position (relative to Avatar)" +msgstr "Toon titelpositie (relatief aan avatar)" -#: templates/single-playlist.php:65 -msgid "No entries for this playlist" -msgstr "Niets gevonden voor deze afspeellijst" +#: includes/class-current-show-widget.php:85 +#: includes/class-upcoming-shows-widget.php:82 +#: widgets/class-current-show-widget.php:139 +#: widgets/class-upcoming-shows-widget.php:142 +msgid "Below" +msgstr "Onder" -#: templates/single-show.php:32 -msgid "Hosted by" -msgstr "Gepresenteerd door" +#: includes/class-current-show-widget.php:84 +#: includes/class-upcoming-shows-widget.php:81 includes/extra-strings.php:640 +#: widgets/class-current-show-widget.php:138 +#: widgets/class-upcoming-shows-widget.php:141 +msgid "Right" +msgstr "Rechts" -#: templates/single-show.php:84 -msgid "Email the DJ" -msgstr "E-mail de DJ" +#: includes/class-current-show-widget.php:83 +#: includes/class-upcoming-shows-widget.php:80 includes/extra-strings.php:693 +#: widgets/class-current-show-widget.php:137 +#: widgets/class-upcoming-shows-widget.php:140 +msgid "Left" +msgstr "Links" -#: templates/single-show.php:88 -msgid "Show Website" -msgstr "Toon website" +#: includes/class-current-show-widget.php:82 +#: includes/class-upcoming-shows-widget.php:79 +#: widgets/class-current-show-widget.php:136 +#: widgets/class-upcoming-shows-widget.php:139 +msgid "Above" +msgstr "Boven" -#: templates/single-show.php:95 -msgid "Most recent broadcast" -msgstr "Laatste uitzending" +#: includes/class-current-show-widget.php:73 +#: includes/class-upcoming-shows-widget.php:70 +msgid "Link the title to the Show page" +msgstr "Link de titel aan de programmapagina" -#: templates/single-show.php:140 -msgid "Blog Posts" -msgstr "Blogmeldingen" +#: includes/class-current-playlist-widget.php:58 +#: includes/class-current-show-widget.php:65 +#: includes/class-radio-clock-widget.php:37 +#: includes/class-upcoming-shows-widget.php:62 includes/extra-strings.php:487 +#: includes/post-types-admin.php:3069 +msgid "Title" +msgstr "Titel" -#: radio-station.php:244 radio-station.php:330 -msgid "Export Playlists" -msgstr "Exporteer afspeellijsten" +#: includes/class-current-playlist-widget.php:52 +#: includes/class-current-show-widget.php:59 +#: includes/class-upcoming-shows-widget.php:56 +#: widgets/class-current-playlist-widget.php:73 +#: widgets/class-current-show-widget.php:93 +#: widgets/class-upcoming-shows-widget.php:96 +msgid "AJAX Load Widget?" +msgstr "Laad widget met AJAX?" -#: radio-station.php:244 radio-station.php:451 -msgid "Export" -msgstr "Exporteer" +#: includes/class-current-playlist-widget.php:50 +#: includes/class-current-show-widget.php:57 +#: includes/class-upcoming-shows-widget.php:54 includes/extra-strings.php:246 +#: options.php:1049 widgets/class-current-playlist-widget.php:71 +#: widgets/class-current-show-widget.php:91 +#: widgets/class-upcoming-shows-widget.php:94 +msgid "Off" +msgstr "Uit" -#: radio-station.php:323 -msgid "Right-click and download this file to save your export" -msgstr "Klik met rechts en download deze file om de export op te slaan" +#: includes/class-current-playlist-widget.php:49 +#: includes/class-current-show-widget.php:56 +#: includes/class-upcoming-shows-widget.php:53 includes/extra-strings.php:245 +#: widgets/class-current-playlist-widget.php:70 +#: widgets/class-current-show-widget.php:90 +#: widgets/class-upcoming-shows-widget.php:93 +msgid "On" +msgstr "Aan" -#: radio-station.php:340 -msgid "Start Date" -msgstr "Startdatum" +#: includes/class-current-playlist-widget.php:48 +#: includes/class-current-show-widget.php:55 +#: includes/class-current-show-widget.php:171 +#: includes/class-radio-clock-widget.php:45 +#: includes/class-upcoming-shows-widget.php:52 +#: includes/class-upcoming-shows-widget.php:147 includes/extra-strings.php:428 +#: widgets/class-current-playlist-widget.php:69 +#: widgets/class-current-show-widget.php:89 +#: widgets/class-current-show-widget.php:207 +#: widgets/class-radio-clock-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:92 +#: widgets/class-upcoming-shows-widget.php:202 +msgid "Default" +msgstr "Standaard" -#: radio-station.php:394 -msgid "End Date" -msgstr "Einddatum" +#: includes/class-current-show-widget.php:30 +msgid "No Show scheduled for this time." +msgstr "Geen programma gepland op deze tijd." -#: radio-station.php:466 -msgid "Related to Show" -msgstr "Verwant aan het programma" \ No newline at end of file +#: includes/class-current-show-widget.php:17 +#: widgets/class-current-show-widget.php:17 +msgid "(Radio Station) Current Show On-Air" +msgstr "(Radio Station) Huidig programma on air" + +#: includes/class-current-show-widget.php:15 +#: widgets/class-current-show-widget.php:15 +msgid "The currently playing on-air Show." +msgstr "Het huidige programma dat on air is." + +#: loader.php:2889 +msgid "Remove Image" +msgstr "Afbeelding verwijderen" + +#: loader.php:2881 +msgid "Add Image" +msgstr "Afbeelding toevoegen" + +#: loader.php:2556 +msgid "Coming soon in Pro version!" +msgstr "Binnenkort in de Pro-versie!" + +#: loader.php:2505 +msgid "Use Ctrl and Click to Select" +msgstr "Gebruik Ctrl en klik om te selecteren" + +#: includes/extra-strings.php:91 includes/post-types-admin.php:1861 +#: loader.php:3010 +msgid "Are you sure you want to remove this image?" +msgstr "Weet je zeker dat je deze afbeelding wilt verwijderen?" + +#: loader.php:2318 +msgid "Save Settings" +msgstr "Instellingen opslaan" + +#: loader.php:2316 +msgid "Reset Settings" +msgstr "Herstel instellingen" + +#: loader.php:2969 +msgid "Are you sure you want to reset to default settings?" +msgstr "Weet je zeker dat je de standaardinstellingen wilt terugzetten?" + +#: includes/extra-strings.php:470 loader.php:2224 options.php:1332 +msgid "General" +msgstr "Algemeen" + +#: loader.php:2047 +msgid "Settings Reset!" +msgstr "Instellingen hersteld!" + +#: loader.php:2045 +msgid "Error! Settings NOT Updated." +msgstr "Fout! Instellingen NIET geüpdatet." + +#: loader.php:2043 +msgid "Settings Updated." +msgstr "Instellingen geüpdatet." + +#: loader.php:2023 radio-station.php:239 +msgid "Support this Plugin" +msgstr "Steun deze plugin" + +#: loader.php:2006 radio-station.php:237 +msgid "Share the Plugin Love" +msgstr "Deel de liefde voor de plugin" + +#: loader.php:1989 radio-station-admin.php:1565 +msgid "Rate on WordPress.Org" +msgstr "Geef je waardering op WordPress.Org" + +#: loader.php:1941 +msgid "Dev" +msgstr "Dev" + +#: loader.php:1941 +msgid "Plugin Development" +msgstr "Plugin-ontwikkeling" + +#: loader.php:1938 +msgid "Support" +msgstr "Ondersteuning" + +#: loader.php:1938 +msgid "Plugin Support" +msgstr "Plugin-ondersteuning" + +#: loader.php:1935 +msgid "Docs" +msgstr "Docs" + +#: loader.php:1935 +msgid "Plugin Documentation" +msgstr "Plugin-documentatie" + +#: loader.php:1932 +msgid "Readme" +msgstr "Leesmij" + +#: loader.php:1932 +msgid "View Plugin" +msgstr "Bekijk plugin" + +#: loader.php:1928 +msgid "Home" +msgstr "Home" + +#: loader.php:1928 +msgid "Plugin Homepage" +msgstr "Plugin-homepage" + +#: loader.php:1918 +msgid "by" +msgstr "door" + +#: loader.php:1790 +msgid "Notices" +msgstr "Opmerkingen" + +#: includes/extra-strings.php:62 loader.php:1682 radio-station-admin.php:203 +#: radio-station-admin.php:207 radio-station-admin.php:238 +msgid "Settings" +msgstr "Instellingen" + +#. Translators: plugin title, user name, site link, freemius link +#: loader.php:1599 +msgid "If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s" +msgstr "" + +#: loader.php:1388 +msgid "Extra Notes" +msgstr "Extra opmerkingen" + +#: loader.php:1362 +msgid "Contributors" +msgstr "Medewerkers" + +#: loader.php:1360 +msgid "Stable Tag" +msgstr "Stable tag" + +#: loader.php:1358 +msgid "Tested up to" +msgstr "Getest tot aan" + +#: loader.php:1357 loader.php:1358 +msgid "WordPress" +msgstr "WordPress" + +#: loader.php:1357 +msgid "Requires at least" +msgstr "Vereist ten minste" + +#: loader.php:1355 +msgid "Plugin Name" +msgstr "Plugin-naam" + +#. Author of the plugin +msgid "Tony Zeoli, Tony Hayes" +msgstr "Tony Zeoli, Tony Hayes" + +#. Description of the plugin +msgid "Adds Show pages, DJ role, playlist and on-air programming functionality to your site." +msgstr "Voegt programmapagina's, DJ-rol, afspeellijst en onair-programmeerfuncties toe aan jouw site." + +#. Plugin Name of the plugin +#: includes/blocks.php:40 includes/extra-strings.php:63 +#: radio-station-admin.php:198 radio-station-admin.php:887 +#: radio-station-admin.php:976 radio-station-admin.php:1543 +#: radio-station-admin.php:1622 radio-station-admin.php:1722 +msgid "Radio Station" +msgstr "Radio Station" \ No newline at end of file diff --git a/languages/radio-station.pot b/languages/radio-station.pot index 4aeccad..4361b9a 100644 --- a/languages/radio-station.pot +++ b/languages/radio-station.pot @@ -1,39 +1,48 @@ -# Copyright (C) 2022 Radio Station +# Copyright (C) 2023 Radio Station # This file is distributed under the same license as the Radio Station package. msgid "" msgstr "" -"Project-Id-Version: Radio Station 2.4.0.6\n" +"Project-Id-Version: Radio Station 2.5.4\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/radio-station\n" -"POT-Creation-Date: 2022-03-07 23:47:11+00:00\n" +"POT-Creation-Date: 2023-07-30 06:22:39+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2022-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2023-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -#: help/contextual-help-config.php:49 includes/extra-strings.php:130 +#: help/contextual-help-config.php:52 includes/extra-strings.php:219 msgid "Import" msgstr "" -#: help/contextual-help-config.php:60 includes/extra-strings.php:134 +#: help/contextual-help-config.php:63 includes/extra-strings.php:223 #: templates/playlist-export.php:140 msgid "Export" msgstr "" -#: help/contextual-help-config.php:71 +#: help/contextual-help-config.php:74 msgid "YAML format " msgstr "" -#: help/contextual-help-config.php:82 +#: help/contextual-help-config.php:85 msgid "show-schedule:" msgstr "" +#: includes/blocks.php:40 includes/extra-strings.php:63 +#: radio-station-admin.php:198 radio-station-admin.php:887 +#: radio-station-admin.php:976 radio-station-admin.php:1543 +#: radio-station-admin.php:1622 radio-station-admin.php:1722 +msgid "Radio Station" +msgstr "" + #: includes/class-current-playlist-widget.php:15 +#: widgets/class-current-playlist-widget.php:15 msgid "Display currently playing playlist." msgstr "" #: includes/class-current-playlist-widget.php:17 +#: widgets/class-current-playlist-widget.php:17 msgid "(Radio Station) Now Playing List" msgstr "" @@ -42,76 +51,104 @@ msgstr "" #: includes/class-current-show-widget.php:171 #: includes/class-radio-clock-widget.php:45 #: includes/class-upcoming-shows-widget.php:52 -#: includes/class-upcoming-shows-widget.php:147 includes/extra-strings.php:285 +#: includes/class-upcoming-shows-widget.php:147 includes/extra-strings.php:428 +#: widgets/class-current-playlist-widget.php:69 +#: widgets/class-current-show-widget.php:89 +#: widgets/class-current-show-widget.php:207 +#: widgets/class-radio-clock-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:92 +#: widgets/class-upcoming-shows-widget.php:202 msgid "Default" msgstr "" #: includes/class-current-playlist-widget.php:49 #: includes/class-current-show-widget.php:56 -#: includes/class-upcoming-shows-widget.php:53 includes/extra-strings.php:286 +#: includes/class-upcoming-shows-widget.php:53 includes/extra-strings.php:245 +#: widgets/class-current-playlist-widget.php:70 +#: widgets/class-current-show-widget.php:90 +#: widgets/class-upcoming-shows-widget.php:93 msgid "On" msgstr "" #: includes/class-current-playlist-widget.php:50 #: includes/class-current-show-widget.php:57 -#: includes/class-upcoming-shows-widget.php:54 includes/extra-strings.php:5 -#: radio-station.php:1207 +#: includes/class-upcoming-shows-widget.php:54 includes/extra-strings.php:246 +#: options.php:1049 widgets/class-current-playlist-widget.php:71 +#: widgets/class-current-show-widget.php:91 +#: widgets/class-upcoming-shows-widget.php:94 msgid "Off" msgstr "" #: includes/class-current-playlist-widget.php:52 #: includes/class-current-show-widget.php:59 #: includes/class-upcoming-shows-widget.php:56 +#: widgets/class-current-playlist-widget.php:73 +#: widgets/class-current-show-widget.php:93 +#: widgets/class-upcoming-shows-widget.php:96 msgid "AJAX Load Widget?" msgstr "" #: includes/class-current-playlist-widget.php:58 #: includes/class-current-show-widget.php:65 #: includes/class-radio-clock-widget.php:37 -#: includes/class-upcoming-shows-widget.php:62 includes/extra-strings.php:147 -#: includes/post-types-admin.php:2971 +#: includes/class-upcoming-shows-widget.php:62 includes/extra-strings.php:487 +#: includes/post-types-admin.php:3069 msgid "Title" msgstr "" #: includes/class-current-playlist-widget.php:66 +#: widgets/class-current-playlist-widget.php:106 msgid "Link to Playlist?" msgstr "" #: includes/class-current-playlist-widget.php:73 +#: widgets/class-current-playlist-widget.php:133 msgid "Show Song Title" msgstr "" #: includes/class-current-playlist-widget.php:80 +#: widgets/class-current-playlist-widget.php:141 msgid "Show Artist Name" msgstr "" #: includes/class-current-playlist-widget.php:87 +#: widgets/class-current-playlist-widget.php:149 msgid " Show Album Name" msgstr "" #: includes/class-current-playlist-widget.php:94 +#: widgets/class-current-playlist-widget.php:157 msgid "Show Record Label Name" msgstr "" #: includes/class-current-playlist-widget.php:101 +#: widgets/class-current-playlist-widget.php:165 msgid "Show DJ Comments" msgstr "" #: includes/class-current-playlist-widget.php:108 +#: widgets/class-current-playlist-widget.php:88 +#: widgets/class-current-show-widget.php:117 +#: widgets/class-upcoming-shows-widget.php:120 msgid "Hide Widget if Empty" msgstr "" #: includes/class-current-playlist-widget.php:115 #: includes/class-current-show-widget.php:183 #: includes/class-upcoming-shows-widget.php:159 +#: widgets/class-current-playlist-widget.php:123 +#: widgets/class-current-show-widget.php:199 +#: widgets/class-upcoming-shows-widget.php:194 msgid "Display Countdown Timer" msgstr "" #: includes/class-current-show-widget.php:15 +#: widgets/class-current-show-widget.php:15 msgid "The currently playing on-air Show." msgstr "" #: includes/class-current-show-widget.php:17 +#: widgets/class-current-show-widget.php:17 msgid "(Radio Station) Current Show On-Air" msgstr "" @@ -126,35 +163,48 @@ msgstr "" #: includes/class-current-show-widget.php:82 #: includes/class-upcoming-shows-widget.php:79 +#: widgets/class-current-show-widget.php:136 +#: widgets/class-upcoming-shows-widget.php:139 msgid "Above" msgstr "" #: includes/class-current-show-widget.php:83 -#: includes/class-upcoming-shows-widget.php:80 +#: includes/class-upcoming-shows-widget.php:80 includes/extra-strings.php:693 +#: widgets/class-current-show-widget.php:137 +#: widgets/class-upcoming-shows-widget.php:140 msgid "Left" msgstr "" #: includes/class-current-show-widget.php:84 -#: includes/class-upcoming-shows-widget.php:81 +#: includes/class-upcoming-shows-widget.php:81 includes/extra-strings.php:640 +#: widgets/class-current-show-widget.php:138 +#: widgets/class-upcoming-shows-widget.php:141 msgid "Right" msgstr "" #: includes/class-current-show-widget.php:85 #: includes/class-upcoming-shows-widget.php:82 +#: widgets/class-current-show-widget.php:139 +#: widgets/class-upcoming-shows-widget.php:142 msgid "Below" msgstr "" #: includes/class-current-show-widget.php:92 #: includes/class-upcoming-shows-widget.php:88 +#: widgets/class-current-show-widget.php:145 +#: widgets/class-upcoming-shows-widget.php:148 msgid "Show Title Position (relative to Avatar)" msgstr "" #: includes/class-current-show-widget.php:99 +#: widgets/class-current-show-widget.php:154 msgid "Display Show Avatar" msgstr "" #: includes/class-current-show-widget.php:105 #: includes/class-upcoming-shows-widget.php:101 +#: widgets/class-current-show-widget.php:171 +#: widgets/class-upcoming-shows-widget.php:174 msgid "Avatar Width" msgstr "" @@ -180,6 +230,8 @@ msgid "Display multiple schedules (if show airs more than once per week)" msgstr "" #: includes/class-current-show-widget.php:142 +#: widgets/class-current-show-widget.php:252 +#: widgets/class-upcoming-shows-widget.php:231 msgid "Display encore presentation text for Show" msgstr "" @@ -207,13 +259,19 @@ msgstr "" #: includes/class-current-show-widget.php:172 #: includes/class-radio-clock-widget.php:46 -#: includes/class-upcoming-shows-widget.php:148 +#: includes/class-upcoming-shows-widget.php:148 includes/extra-strings.php:495 +#: widgets/class-current-show-widget.php:208 +#: widgets/class-radio-clock-widget.php:88 +#: widgets/class-upcoming-shows-widget.php:203 msgid "12 Hour" msgstr "" #: includes/class-current-show-widget.php:173 #: includes/class-radio-clock-widget.php:47 -#: includes/class-upcoming-shows-widget.php:149 +#: includes/class-upcoming-shows-widget.php:149 includes/extra-strings.php:496 +#: widgets/class-current-show-widget.php:209 +#: widgets/class-radio-clock-widget.php:89 +#: widgets/class-upcoming-shows-widget.php:204 msgid "24 Hour" msgstr "" @@ -223,14 +281,17 @@ msgid "Choose time format for displayed schedules" msgstr "" #: includes/class-radio-clock-widget.php:14 +#: widgets/class-radio-clock-widget.php:14 msgid "Display current radio and user times." msgstr "" #: includes/class-radio-clock-widget.php:16 +#: widgets/class-radio-clock-widget.php:16 msgid "(Radio Station) Radio Clock" msgstr "" -#: includes/class-radio-clock-widget.php:23 +#: includes/class-radio-clock-widget.php:23 includes/extra-strings.php:518 +#: widgets/class-radio-clock-widget.php:23 msgid "Radio Clock" msgstr "" @@ -243,18 +304,24 @@ msgid "Day Display" msgstr "" #: includes/class-radio-clock-widget.php:64 -#: includes/class-radio-clock-widget.php:83 +#: includes/class-radio-clock-widget.php:83 includes/extra-strings.php:501 +#: widgets/class-radio-clock-widget.php:49 +#: widgets/class-radio-clock-widget.php:68 msgid "Full" msgstr "" #: includes/class-radio-clock-widget.php:65 -#: includes/class-radio-clock-widget.php:84 +#: includes/class-radio-clock-widget.php:84 includes/extra-strings.php:522 +#: widgets/class-radio-clock-widget.php:50 +#: widgets/class-radio-clock-widget.php:69 msgid "Short" msgstr "" #: includes/class-radio-clock-widget.php:66 -#: includes/class-radio-clock-widget.php:85 includes/post-types-admin.php:4790 -#: radio-station.php:849 +#: includes/class-radio-clock-widget.php:85 includes/extra-strings.php:499 +#: includes/post-types-admin.php:4962 options.php:690 +#: widgets/class-radio-clock-widget.php:51 +#: widgets/class-radio-clock-widget.php:70 msgid "None" msgstr "" @@ -279,37 +346,53 @@ msgid "Include timezone display." msgstr "" #: includes/class-radio-player-widget.php:16 +#: widgets/class-radio-player-widget.php:16 msgid "Radio Station Stream Player." msgstr "" #: includes/class-radio-player-widget.php:18 +#: widgets/class-radio-player-widget.php:18 msgid "(Radio Station) Stream Player" msgstr "" #: includes/class-radio-player-widget.php:47 +#: widgets/class-radio-player-widget.php:75 msgid "Stream or File URL" msgstr "" #: includes/class-radio-player-widget.php:57 +#: widgets/class-current-playlist-widget.php:60 +#: widgets/class-current-show-widget.php:79 +#: widgets/class-radio-clock-widget.php:40 +#: widgets/class-radio-player-widget.php:67 +#: widgets/class-upcoming-shows-widget.php:74 msgid "Widget Title" msgstr "" #: includes/class-radio-player-widget.php:66 +#: widgets/class-radio-player-widget.php:84 msgid "Player Station Text" msgstr "" #: includes/class-radio-player-widget.php:77 #: includes/class-radio-player-widget.php:94 #: includes/class-radio-player-widget.php:130 -#: includes/class-radio-player-widget.php:149 +#: includes/class-radio-player-widget.php:149 includes/extra-strings.php:244 +#: widgets/class-radio-player-widget.php:98 +#: widgets/class-radio-player-widget.php:119 +#: widgets/class-radio-player-widget.php:192 +#: widgets/class-radio-player-widget.php:212 msgid "Plugin Setting" msgstr "" -#: includes/class-radio-player-widget.php:78 radio-station.php:439 +#: includes/class-radio-player-widget.php:78 includes/extra-strings.php:589 +#: options.php:268 widgets/class-radio-player-widget.php:95 +#: widgets/class-radio-player-widget.php:99 msgid "Display Station Image" msgstr "" #: includes/class-radio-player-widget.php:79 +#: widgets/class-radio-player-widget.php:100 msgid "Do Not Display Image" msgstr "" @@ -317,18 +400,18 @@ msgstr "" msgid "Whether to display Station Image in Player." msgstr "" -#: includes/class-radio-player-widget.php:95 radio-station.php:457 -#: radio-station.php:475 +#: includes/class-radio-player-widget.php:95 includes/extra-strings.php:593 +#: options.php:285 options.php:302 widgets/class-radio-player-widget.php:120 msgid "Amplitude" msgstr "" -#: includes/class-radio-player-widget.php:96 radio-station.php:456 -#: radio-station.php:474 +#: includes/class-radio-player-widget.php:96 includes/extra-strings.php:594 +#: options.php:284 options.php:301 widgets/class-radio-player-widget.php:121 msgid "Howler" msgstr "" -#: includes/class-radio-player-widget.php:97 radio-station.php:455 -#: radio-station.php:473 +#: includes/class-radio-player-widget.php:97 includes/extra-strings.php:595 +#: options.php:283 options.php:300 widgets/class-radio-player-widget.php:122 msgid "jPlayer" msgstr "" @@ -337,10 +420,12 @@ msgid "Player Script to load by default." msgstr "" #: includes/class-radio-player-widget.php:113 +#: widgets/class-radio-player-widget.php:176 msgid "Vertical (Stacked)" msgstr "" #: includes/class-radio-player-widget.php:114 +#: widgets/class-radio-player-widget.php:177 msgid "Horizontal (Inline)" msgstr "" @@ -348,27 +433,33 @@ msgstr "" msgid "Layout Style for Widget Area (wide or tall)" msgstr "" -#: includes/class-radio-player-widget.php:131 radio-station.php:489 +#: includes/class-radio-player-widget.php:131 includes/extra-strings.php:611 +#: options.php:315 widgets/class-radio-player-widget.php:193 msgid "Light" msgstr "" -#: includes/class-radio-player-widget.php:132 radio-station.php:490 +#: includes/class-radio-player-widget.php:132 includes/extra-strings.php:612 +#: options.php:316 widgets/class-radio-player-widget.php:194 msgid "Dark" msgstr "" #: includes/class-radio-player-widget.php:140 +#: widgets/class-radio-player-widget.php:189 msgid "Player Theme Style" msgstr "" -#: includes/class-radio-player-widget.php:150 +#: includes/class-radio-player-widget.php:150 includes/extra-strings.php:614 +#: widgets/class-radio-player-widget.php:213 msgid "Circular" msgstr "" -#: includes/class-radio-player-widget.php:151 +#: includes/class-radio-player-widget.php:151 includes/extra-strings.php:615 +#: widgets/class-radio-player-widget.php:214 msgid "Rounded" msgstr "" -#: includes/class-radio-player-widget.php:152 +#: includes/class-radio-player-widget.php:152 includes/extra-strings.php:616 +#: widgets/class-radio-player-widget.php:215 msgid "Square" msgstr "" @@ -376,7 +467,8 @@ msgstr "" msgid "Layout Style for Widget Area" msgstr "" -#: includes/class-radio-player-widget.php:167 radio-station.php:594 +#: includes/class-radio-player-widget.php:167 options.php:418 +#: widgets/class-radio-player-widget.php:135 msgid "Player Start Volume" msgstr "" @@ -385,14 +477,17 @@ msgid "Use this as the default Player instance." msgstr "" #: includes/class-upcoming-shows-widget.php:15 +#: widgets/class-upcoming-shows-widget.php:15 msgid "Display the upcoming Shows." msgstr "" #: includes/class-upcoming-shows-widget.php:17 +#: widgets/class-upcoming-shows-widget.php:17 msgid "(Radio Station) Upcoming Shows" msgstr "" #: includes/class-upcoming-shows-widget.php:95 +#: widgets/class-upcoming-shows-widget.php:157 msgid "Display Show Avatars" msgstr "" @@ -417,10 +512,12 @@ msgid "If no Show is scheduled for the current time, display this text." msgstr "" #: includes/class-upcoming-shows-widget.php:138 +#: widgets/class-upcoming-shows-widget.php:82 msgid "Limit" msgstr "" #: includes/class-upcoming-shows-widget.php:141 +#: widgets/class-upcoming-shows-widget.php:85 msgid "Number of upcoming Shows to display." msgstr "" @@ -428,3789 +525,5235 @@ msgstr "" msgid "Choose time format for displayed schedules." msgstr "" -#: includes/data-feeds.php:1341 +#: includes/data-feeds.php:1334 msgid "Error 400 No Requested Data" msgstr "" -#: includes/data-feeds.php:1342 +#: includes/data-feeds.php:1335 msgid "The requested data could not be found." msgstr "" -#: includes/extra-strings.php:2 radio-station.php:1191 -msgid "Team Archive Page" +#: includes/extra-strings.php:2 options.php:723 +msgid "Grid View" msgstr "" -#: includes/extra-strings.php:3 radio-station.php:1195 -msgid "Select the Page for displaying the Team archive list." +#: includes/extra-strings.php:3 options.php:724 +msgid "Calendar View" msgstr "" -#: includes/extra-strings.php:4 radio-station.php:818 radio-station.php:1102 -#: radio-station.php:1135 radio-station.php:1168 radio-station.php:1204 -#: radio-station.php:1235 radio-station.php:1270 -msgid "Automatic Display" +#: includes/extra-strings.php:4 +msgid "Red" msgstr "" -#: includes/extra-strings.php:6 radio-station.php:1208 -msgid "List" +#: includes/extra-strings.php:5 +msgid "Orange" +msgstr "" + +#: includes/extra-strings.php:6 +msgid "Yellow" msgstr "" #: includes/extra-strings.php:7 -msgid "Grid" +msgid "Light Green" msgstr "" -#: includes/extra-strings.php:8 radio-station.php:1213 -msgid "Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:" +#: includes/extra-strings.php:8 +msgid "Green" msgstr "" -#: includes/extra-strings.php:9 radio-station.php:895 -msgid "Time Spaced Grid" +#: includes/extra-strings.php:9 +msgid "Cyan" msgstr "" -#: includes/extra-strings.php:10 radio-station.php:898 -msgid "Enable Grid View option for equalized time spacing and background imsges." +#: includes/extra-strings.php:10 +msgid "Light Blue" msgstr "" -#: includes/extra-strings.php:11 radio-station.php:675 -msgid "Player Bar Height" +#: includes/extra-strings.php:11 +msgid "Blue" msgstr "" -#: includes/extra-strings.php:12 radio-station.php:679 -msgid "Set the height of the Sitewide Player Bar in pixels." +#: includes/extra-strings.php:12 +msgid "Purple" msgstr "" -#: includes/extra-strings.php:13 radio-station.php:765 -msgid "Display Current Show" +#: includes/extra-strings.php:13 +msgid "Magenta" msgstr "" -#: includes/extra-strings.php:14 radio-station.php:770 -msgid "Display the Current Show in the Player Bar." +#: includes/extra-strings.php:14 options.php:1033 +msgid "Team Archive Page" msgstr "" -#: includes/extra-strings.php:15 radio-station.php:778 -msgid "Display Now Playing" +#: includes/extra-strings.php:15 options.php:1037 +msgid "Select the Page for displaying the Team archive list." msgstr "" -#: includes/extra-strings.php:16 -msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist" +#: includes/extra-strings.php:16 options.php:659 options.php:944 +#: options.php:977 options.php:1010 options.php:1046 options.php:1077 +#: options.php:1112 +msgid "Automatic Display" msgstr "" -#: includes/extra-strings.php:17 radio-station.php:792 -msgid "Metadata URL" +#: includes/extra-strings.php:17 options.php:1055 +msgid "Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:" msgstr "" -#: includes/extra-strings.php:18 radio-station.php:796 -msgid "Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location." +#: includes/extra-strings.php:18 options.php:845 +msgid "Combined Team Tab" msgstr "" -#: includes/extra-strings.php:19 radio-station.php:728 -msgid "Page Load Timeout" +#: includes/extra-strings.php:19 options.php:848 +msgid "Do Not Combine" msgstr "" -#: includes/extra-strings.php:20 -msgid "Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display." +#: includes/extra-strings.php:20 options.php:849 +msgid "Combined List" msgstr "" #: includes/extra-strings.php:21 -msgid "Radio Station needs to be installed and activated to use %s." +msgid "Combined Grid" msgstr "" #: includes/extra-strings.php:22 -msgid "Your Radio Station plugin needs to updated to work with %s." +msgid "Combine team members (eg. hosts, producers into a single display tab." msgstr "" -#: includes/extra-strings.php:23 -msgid "The required minimum version of Radio Station plugin is:" +#: includes/extra-strings.php:23 options.php:736 +msgid "Time Spaced Grid" msgstr "" -#: includes/extra-strings.php:24 loader.php:1599 radio-station-admin.php:178 -#: radio-station-admin.php:182 radio-station-admin.php:212 -msgid "Settings" +#: includes/extra-strings.php:24 options.php:739 +msgid "Enable Grid View option for equalized time spacing and background imsges." msgstr "" -#: includes/extra-strings.php:25 radio-station-admin.php:173 -#: radio-station-admin.php:821 radio-station-admin.php:901 -#: radio-station-admin.php:1453 radio-station-admin.php:1528 -#: radio-station-admin.php:1628 -msgid "Radio Station" +#: includes/extra-strings.php:25 options.php:500 +msgid "Player Bar Height" msgstr "" -#: includes/extra-strings.php:26 radio-station-admin.php:203 -#: templates/playlist-export.php:2 -msgid "Export Playlists" +#: includes/extra-strings.php:26 options.php:504 +msgid "Set the height of the Sitewide Player Bar in pixels." msgstr "" -#: includes/extra-strings.php:27 -msgid "Account" +#: includes/extra-strings.php:27 options.php:590 +msgid "Display Current Show" msgstr "" -#: includes/extra-strings.php:28 -msgid "You have multiple conflicting versions of Radio Station Pro activated." +#: includes/extra-strings.php:28 options.php:595 +msgid "Display the Current Show in the Player Bar." msgstr "" -#: includes/extra-strings.php:29 -msgid "Please visit your Plugins page to deactivate the incorrect one." +#: includes/extra-strings.php:29 options.php:603 +msgid "Display Now Playing" msgstr "" #: includes/extra-strings.php:30 -msgid "Radio Station Pro" +msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist" msgstr "" -#: includes/extra-strings.php:31 -msgid "Extending" +#: includes/extra-strings.php:31 options.php:629 +msgid "Track Animation" msgstr "" -#: includes/extra-strings.php:32 includes/extra-strings.php:211 -msgid "You do not have permission to do that." +#: includes/extra-strings.php:32 options.php:632 +msgid "No Animation" msgstr "" -#: includes/extra-strings.php:33 -msgid "Important: %s continuous player bar is active." +#: includes/extra-strings.php:33 options.php:633 +msgid "Left to Right Ticker" msgstr "" -#: includes/extra-strings.php:34 -msgid "This overrides the page fade in, load timeout and loader bar settings here." +#: includes/extra-strings.php:34 options.php:634 +msgid "Right to Left Ticker" msgstr "" -#: includes/extra-strings.php:35 -msgid "Adjust those settings via your Radio Station settings page instead." +#: includes/extra-strings.php:35 options.php:635 +msgid "Back and Forth" msgstr "" -#: includes/extra-strings.php:36 -msgid "Guests" +#: includes/extra-strings.php:36 options.php:639 +msgid "How to animate the currently playing track display." msgstr "" -#: includes/extra-strings.php:37 -msgid "Topics" +#: includes/extra-strings.php:37 options.php:617 +msgid "Metadata URL" msgstr "" -#: includes/extra-strings.php:38 -msgid "Episodes" +#: includes/extra-strings.php:38 options.php:621 +msgid "Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location." msgstr "" -#: includes/extra-strings.php:39 -msgid "Episode Information" +#: includes/extra-strings.php:39 options.php:553 +msgid "Page Load Timeout" msgstr "" -#: includes/extra-strings.php:40 includes/post-types-admin.php:5648 -#: includes/post-types.php:46 -msgid "Show" +#: includes/extra-strings.php:40 +msgid "Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display." msgstr "" -#: includes/extra-strings.php:41 -msgid "Select Show" +#: includes/extra-strings.php:41 options.php:457 +msgid "Popup Player Button" msgstr "" -#: includes/extra-strings.php:42 includes/post-types-admin.php:5836 -msgid "Draft" +#: includes/extra-strings.php:42 options.php:460 +msgid "Add button to open Popup Player in separate window." msgstr "" -#: includes/extra-strings.php:43 includes/post-types-admin.php:5843 -msgid "No Shows to Select." +#: includes/extra-strings.php:43 +msgid "Radio Station needs to be installed and activated to use %s." msgstr "" #: includes/extra-strings.php:44 -msgid "Media Type" +msgid "Activate Radio Station" msgstr "" #: includes/extra-strings.php:45 -msgid "File URL" +msgid "Install Radio Station" msgstr "" #: includes/extra-strings.php:46 -msgid "Media Library" +msgid "Your Radio Station plugin needs to be updated to work with %s." msgstr "" #: includes/extra-strings.php:47 -msgid "Episode Number" +msgid "The required minimum version of the Radio Station plugin is:" msgstr "" #: includes/extra-strings.php:48 -msgid "Current Highest" +msgid "Update to Radio Station" msgstr "" #: includes/extra-strings.php:49 -msgid "Broadcast Date" +msgid "Development Version" msgstr "" #: includes/extra-strings.php:50 -msgid "Broadcast Time" +msgid "Sorry, you are not allowed to update plugins for this site." msgstr "" #: includes/extra-strings.php:51 -msgid "Episode Length" +msgid "Update Plugin via Github" msgstr "" #: includes/extra-strings.php:52 -msgid "minutees" +msgid "Error. Could not connect to filesystem. Please install plugin update manually." msgstr "" -#: includes/extra-strings.php:53 includes/post-types.php:96 -msgid "Playlist" +#: includes/extra-strings.php:53 +msgid "Error. Downloading the plugin package failed. Please try again." msgstr "" #: includes/extra-strings.php:54 -msgid "Select Playlist" +msgid "Downloaded plugin package from:" msgstr "" #: includes/extra-strings.php:55 -msgid "No Playlists to Select." +msgid "Package unzipped successfully." msgstr "" #: includes/extra-strings.php:56 -msgid "Select Audio File" +msgid "Package contents moved to plugin directory." msgstr "" #: includes/extra-strings.php:57 -msgid "Remove Selected Audio" +msgid "The plugin has been updated, but could not be reactivated. Please reactivate it manually." msgstr "" #: includes/extra-strings.php:58 -msgid "Are you sure you want to remove this audio?" +msgid "The previous version of the plugin has been backed up if you need to restore it." msgstr "" -#: includes/extra-strings.php:59 includes/post-types-admin.php:5504 -msgid "Failed. Use manual Publish or Update instead." +#: includes/extra-strings.php:59 +msgid "Plugin reactivated successfully." msgstr "" -#: includes/extra-strings.php:60 includes/post-types-admin.php:1980 -#: includes/post-types-admin.php:4113 includes/post-types-admin.php:5506 -msgid "Expired. Publish or Update instead." +#: includes/extra-strings.php:60 +msgid "Radio Station Settings" msgstr "" #: includes/extra-strings.php:61 -msgid "Failed. No Episode ID provided." +msgid "Back to Plugins page." msgstr "" -#: includes/extra-strings.php:62 -msgid "Failed. Invalid Episode ID." -msgstr "" - -#: includes/extra-strings.php:63 -msgid "Search Topics" +#: includes/extra-strings.php:62 loader.php:1682 radio-station-admin.php:203 +#: radio-station-admin.php:207 radio-station-admin.php:238 +msgid "Settings" msgstr "" -#: includes/extra-strings.php:64 -msgid "All Topics" +#: includes/extra-strings.php:64 radio-station-admin.php:229 +#: templates/playlist-export.php:2 +msgid "Export Playlists" msgstr "" #: includes/extra-strings.php:65 -msgid "Parent Topic" +msgid "Account" msgstr "" #: includes/extra-strings.php:66 -msgid "Parent Topic:" +msgid "Extending" msgstr "" #: includes/extra-strings.php:67 -msgid "Edit Topic" +msgid "You have multiple conflicting versions of Radio Station Pro activated." msgstr "" #: includes/extra-strings.php:68 -msgid "Update Topic" +msgid "Please visit your Plugins page to deactivate the incorrect one." msgstr "" #: includes/extra-strings.php:69 -msgid "Add New Topic" +msgid "Radio Station Pro" msgstr "" -#: includes/extra-strings.php:70 -msgid "New Topic Name" +#: includes/extra-strings.php:70 includes/support-functions.php:1134 +msgid "Thumbnail" msgstr "" -#: includes/extra-strings.php:71 -msgid "Topic" +#: includes/extra-strings.php:71 includes/support-functions.php:1135 +msgid "Medium" msgstr "" -#: includes/extra-strings.php:72 -msgid "Search Guests" +#: includes/extra-strings.php:72 includes/support-functions.php:1136 +msgid "Large" msgstr "" -#: includes/extra-strings.php:73 -msgid "All Guests" +#: includes/extra-strings.php:73 includes/support-functions.php:1137 +msgid "Full Size" msgstr "" #: includes/extra-strings.php:74 -msgid "Parent Guest" +msgid "Important: %s continuous player bar is active." msgstr "" #: includes/extra-strings.php:75 -msgid "Parent Guest:" +msgid "This overrides the page fade in, load timeout and loader bar settings here." msgstr "" #: includes/extra-strings.php:76 -msgid "Edit Guest" +msgid "Adjust those settings via your Radio Station settings page instead." msgstr "" #: includes/extra-strings.php:77 -msgid "Update Guest" +msgid "\"%1$s\" require \"%2$s\" to be installed and activated." msgstr "" #: includes/extra-strings.php:78 -msgid "Add New Guest" +msgid "Radio Station Elementor Widgets" msgstr "" #: includes/extra-strings.php:79 -msgid "New Guest Name" +msgid "Elementor" msgstr "" #: includes/extra-strings.php:80 -msgid "Guest" +msgid "\"%1$s\" require \"%2$s\" version %3$s or greater." msgstr "" #: includes/extra-strings.php:81 -msgid "Show %s" +msgid "PHP" msgstr "" #: includes/extra-strings.php:82 -msgid "Episode" +msgid "Guests" msgstr "" #: includes/extra-strings.php:83 -msgid "Aired on" +msgid "Topics" msgstr "" -#: includes/extra-strings.php:84 includes/post-types-admin.php:4574 -msgid "Image" +#: includes/extra-strings.php:84 +msgid "Episodes" msgstr "" #: includes/extra-strings.php:85 -msgid "Add Genre Image" +msgid "You can add past Show Episodes for automatic display on Show pages." msgstr "" #: includes/extra-strings.php:86 -msgid "Remove Genre Image" +msgid "Click here to add or edit Show Episodes." msgstr "" #: includes/extra-strings.php:87 -msgid "Insert" +msgid "Episode Information" msgstr "" #: includes/extra-strings.php:88 -msgid "this is a success message" +msgid "Episode Image" msgstr "" #: includes/extra-strings.php:89 -msgid "this is a failure message" +msgid "Set %s Avatar Image" msgstr "" #: includes/extra-strings.php:90 -msgid "Please upload a valid YAML file." +msgid "Remove %s Avatar Image" msgstr "" -#: includes/extra-strings.php:91 -msgid "Please upload a valid YAML file" +#: includes/extra-strings.php:91 includes/post-types-admin.php:1861 +#: loader.php:3010 +msgid "Are you sure you want to remove this image?" msgstr "" -#: includes/extra-strings.php:92 -msgid "Please upload a file to import." +#: includes/extra-strings.php:92 includes/post-types-admin.php:1862 +msgid "Select or Upload Image" msgstr "" -#: includes/extra-strings.php:93 -msgid "Please upload a file to import" +#: includes/extra-strings.php:93 includes/post-types-admin.php:1863 +msgid "Use this Image" msgstr "" -#: includes/extra-strings.php:94 -msgid "Successfully parsed and imported YAML file, deleting pre-existing show data." +#: includes/extra-strings.php:94 includes/post-types-admin.php:6003 +#: includes/post-types.php:44 +msgid "Show" msgstr "" #: includes/extra-strings.php:95 -msgid "Successfully parsed and imported YAML file. Pre-existing show data remains unchanged." +msgid "Select Show" msgstr "" -#: includes/extra-strings.php:96 -msgid "Export successful. Use the following link(s to download your data." +#: includes/extra-strings.php:96 includes/post-types-admin.php:6384 +msgid "Draft" msgstr "" -#: includes/extra-strings.php:97 -msgid "Data file" +#: includes/extra-strings.php:97 includes/post-types-admin.php:6391 +msgid "No Shows to Select." msgstr "" #: includes/extra-strings.php:98 -msgid "Image zip file" +msgid "The Show this is an Episode for." msgstr "" #: includes/extra-strings.php:99 -msgid "Image tgz file" +msgid "Media Type" msgstr "" #: includes/extra-strings.php:100 -msgid "Download one of the image files and the data file. See help for details on how to stage an import including images." +msgid "File URL" msgstr "" #: includes/extra-strings.php:101 -msgid "Show data file (YAML" +msgid "Media Library" msgstr "" #: includes/extra-strings.php:102 -msgid "Images not exported. See help for details on how to include images." +msgid "Use an existing URL or add to Media Library." msgstr "" #: includes/extra-strings.php:103 -msgid "YAML import error. See below for details." +msgid "Enter the absolute URL to the media source." msgstr "" #: includes/extra-strings.php:104 -msgid "Failed to create export folder" +msgid "Opens the Media Library for uploading." msgstr "" #: includes/extra-strings.php:105 -msgid "No image exported for" +msgid "Episode Number" msgstr "" #: includes/extra-strings.php:106 -msgid "Failed to copy file. See below for details" +msgid "This Episode number is unique." msgstr "" #: includes/extra-strings.php:107 -msgid "Failed to create show_images.zip" +msgid "This Episode number is in use!" msgstr "" #: includes/extra-strings.php:108 -msgid "Each show in the YAML file must define at minimum the following keys: " +msgid "Highest" msgstr "" #: includes/extra-strings.php:109 -msgid "show-title: may not be null." +msgid "Click to auto-number to highest episode plus one." msgstr "" #: includes/extra-strings.php:110 -msgid "show-description: may not be null." +msgid "Auto" msgstr "" #: includes/extra-strings.php:111 -msgid "show-image: must be a URL reference to an existing image." +msgid "The Episode number in the Show series." msgstr "" #: includes/extra-strings.php:112 -msgid "show-avatar: must be a URL reference to an existing image." +msgid "Broadcast Date" msgstr "" #: includes/extra-strings.php:113 -msgid "show-header: must be a URL reference to an existing image." +msgid "Date that the Episode aired." msgstr "" #: includes/extra-strings.php:114 -msgid "upload-images: may not be null." +msgid "Broadcast Time" msgstr "" #: includes/extra-strings.php:115 -msgid "show-url: must be a valid web address." +msgid "The time that the Episode aired" msgstr "" #: includes/extra-strings.php:116 -msgid "show-podcast: must be a valid web address." +msgid "Episode Length" msgstr "" #: includes/extra-strings.php:117 -msgid "show-user-list: must be a simple array of valid email addresses." -msgstr "" - -#: includes/extra-strings.php:118 -msgid "show-producer-list: must be a simple array of valid email addresses." +msgid "minutees" msgstr "" #: includes/extra-strings.php:119 -msgid "show-email: must be a valid email address." +msgid "Linked Playlist" msgstr "" #: includes/extra-strings.php:120 -msgid "show-active: may not be null." +msgid "Select Playlist" msgstr "" #: includes/extra-strings.php:121 -msgid "YAML data parsed successfully, but contains formatting errors. See below for details." +msgid "No Playlists to Select." msgstr "" #: includes/extra-strings.php:122 -msgid "Data file errors noted as follows:" +msgid "The Playlist data for this Episode." msgstr "" #: includes/extra-strings.php:123 -msgid "show-schedule[] time blocks must be in 24h format and have the form \"04:55\" (note 0 padding." +msgid "Set Episode Number to one above current highest?" msgstr "" #: includes/extra-strings.php:124 -msgid "Error, for show-schedule[], only \"disabled\" and \"encore\" flags are allowed" +msgid "Select Audio File" msgstr "" #: includes/extra-strings.php:125 -msgid "show-schedule[] must reference an array of time blocks containing at least one element." +msgid "Remove Selected Audio" msgstr "" #: includes/extra-strings.php:126 -msgid "Invalid weekday. show-schedule[] must be one of \"sun\"..\"sat\", or \"sunday\"..\"saturday\" (case insensitive." +msgid "Are you sure you want to remove this audio?" msgstr "" #: includes/extra-strings.php:127 -msgid " show-schedule: must define at least one weekday." +msgid "Assign to Show Episode" msgstr "" #: includes/extra-strings.php:128 -msgid "Import/Export Show Data" +msgid "No Episode" msgstr "" -#: includes/extra-strings.php:129 radio-station-admin.php:209 -msgid "Import/Export" +#: includes/extra-strings.php:129 +msgid "No Show Episodes found." +msgstr "" + +#: includes/extra-strings.php:130 includes/post-types-admin.php:5798 +msgid "No Show selected." msgstr "" #: includes/extra-strings.php:131 -msgid "Import show data from a YAML file." +msgid "Warning! This playlist is assigned to more than one episode." msgstr "" #: includes/extra-strings.php:132 -msgid "Delete existing show data" +msgid "Number" msgstr "" #: includes/extra-strings.php:133 -msgid "WARNING" +msgid "of" +msgstr "" + +#: includes/extra-strings.php:134 includes/post-types-admin.php:6053 +#: includes/post-types-admin.php:6620 includes/post-types.php:47 +msgid "Edit Show" msgstr "" #: includes/extra-strings.php:135 -msgid "Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options." +msgid "Episode Show" msgstr "" -#: includes/extra-strings.php:136 -msgid "Advanced" +#: includes/extra-strings.php:136 includes/post-types.php:56 +msgid "All Shows" msgstr "" #: includes/extra-strings.php:137 -msgid "YAML file name" +msgid "Episode for Show" msgstr "" #: includes/extra-strings.php:138 -msgid "Default similar to" +msgid "Not Assigned" msgstr "" -#: includes/extra-strings.php:139 -msgid "URL where show images will be staged for import (see help" +#: includes/extra-strings.php:139 includes/post-types-admin.php:6199 +#: includes/post-types-admin.php:6549 +msgid "No Shows available to Select." msgstr "" #: includes/extra-strings.php:140 -msgid "Metadata Source" +msgid "Episode" msgstr "" #: includes/extra-strings.php:141 -msgid "Default Plugin Setting" +msgid "Add Episode" msgstr "" #: includes/extra-strings.php:142 -msgid "Override: Metadata On" +msgid "Edit Episode" msgstr "" #: includes/extra-strings.php:143 -msgid "Override: Metadata Off" +msgid "New Episode" msgstr "" #: includes/extra-strings.php:144 -msgid "Override: Metadata URL" +msgid "View Episode" msgstr "" #: includes/extra-strings.php:145 -msgid "Override: Current Playlist" +msgid "Search Episodes" msgstr "" #: includes/extra-strings.php:146 -msgid "Metadata Override URL" +msgid "No Episodes found" +msgstr "" + +#: includes/extra-strings.php:147 +msgid "No Episodes found in Trash" msgstr "" #: includes/extra-strings.php:148 -msgid "Add Episode" +msgid "All Episodes" msgstr "" #: includes/extra-strings.php:149 -msgid "Edit Episode" +msgid "Post Type for Show Episodes" msgstr "" #: includes/extra-strings.php:150 -msgid "New Episode" +msgid "Search Topics" msgstr "" #: includes/extra-strings.php:151 -msgid "View Episode" +msgid "All Topics" msgstr "" #: includes/extra-strings.php:152 -msgid "Search Episodes" +msgid "Parent Topic" msgstr "" #: includes/extra-strings.php:153 -msgid "No Episodes found" +msgid "Parent Topic:" msgstr "" #: includes/extra-strings.php:154 -msgid "No Episodes found in Trash" +msgid "Edit Topic" msgstr "" #: includes/extra-strings.php:155 -msgid "All Episodes" +msgid "Update Topic" msgstr "" #: includes/extra-strings.php:156 -msgid "Post Type for Show Episodes" +msgid "Add New Topic" msgstr "" #: includes/extra-strings.php:157 -msgid "Edit Your Host Profile" +msgid "New Topic Name" msgstr "" #: includes/extra-strings.php:158 -msgid "Host Profile" +msgid "Topic" msgstr "" #: includes/extra-strings.php:159 -msgid "Edit Your Producer Profile" +msgid "Search Guests" msgstr "" #: includes/extra-strings.php:160 -msgid "Producer Profile" +msgid "All Guests" msgstr "" #: includes/extra-strings.php:161 -msgid "Profile Information" +msgid "Parent Guest" msgstr "" -#: includes/extra-strings.php:162 includes/post-types-admin.php:469 -#: includes/post-types-admin.php:3103 -msgid "Website Link" +#: includes/extra-strings.php:162 +msgid "Parent Guest:" msgstr "" #: includes/extra-strings.php:163 -msgid "%s Email" +msgid "Edit Guest" msgstr "" -#: includes/extra-strings.php:164 includes/post-types-admin.php:491 -#: includes/post-types-admin.php:3145 -msgid "Show Phone" +#: includes/extra-strings.php:164 +msgid "Update Guest" msgstr "" -#: includes/extra-strings.php:165 includes/post-types-admin.php:522 -#: includes/post-types-admin.php:3209 -msgid "Patreon Page ID" +#: includes/extra-strings.php:165 +msgid "Add New Guest" msgstr "" #: includes/extra-strings.php:166 -msgid "Host Image" +msgid "New Guest Name" msgstr "" #: includes/extra-strings.php:167 -msgid "Producer Image" +msgid "Guest" msgstr "" #: includes/extra-strings.php:168 -msgid "Episode Image" +msgid "Show %s" msgstr "" #: includes/extra-strings.php:169 -msgid "Set %s Avatar Image" +msgid "Aired on" msgstr "" -#: includes/extra-strings.php:170 -msgid "Remove %s Avatar Image" +#: includes/extra-strings.php:170 includes/post-types-admin.php:4746 +msgid "Image" msgstr "" -#: includes/extra-strings.php:171 includes/post-types-admin.php:1803 -#: loader.php:2263 -msgid "Are you sure you want to remove this image?" +#: includes/extra-strings.php:171 +msgid "Add Genre Image" msgstr "" -#: includes/extra-strings.php:172 includes/post-types-admin.php:1804 -msgid "Select or Upload Image" +#: includes/extra-strings.php:172 +msgid "Remove Genre Image" msgstr "" -#: includes/extra-strings.php:173 includes/post-types-admin.php:1805 -msgid "Use this Image" +#: includes/extra-strings.php:173 +msgid "Background Color" msgstr "" #: includes/extra-strings.php:174 -msgid "User Save Error! The selected User is already assigned to a %s Profile." +msgid "Border Color" msgstr "" -#: includes/extra-strings.php:175 includes/post-types-admin.php:2557 -#: includes/post-types-admin.php:3061 includes/post-types.php:195 -msgid "Hosts" +#: includes/extra-strings.php:175 +msgid "Text Color" msgstr "" #: includes/extra-strings.php:176 -msgid "Host User" +msgid "Insert" msgstr "" -#: includes/extra-strings.php:177 includes/post-types-admin.php:3082 -#: includes/post-types.php:247 -msgid "Producers" +#: includes/extra-strings.php:177 +msgid "this is a success message" msgstr "" #: includes/extra-strings.php:178 -msgid "Producer User" +msgid "this is a failure message" msgstr "" #: includes/extra-strings.php:179 -msgid "Team" +msgid "Please upload a valid YAML file." msgstr "" #: includes/extra-strings.php:180 -msgid "All" +msgid "Please upload a valid YAML file" msgstr "" #: includes/extra-strings.php:181 -msgid "Editors" +msgid "Please upload a file to import." msgstr "" -#: includes/extra-strings.php:182 includes/post-types.php:45 -#: includes/post-types.php:53 radio-station-admin.php:188 -msgid "Shows" +#: includes/extra-strings.php:182 +msgid "Please upload a file to import" msgstr "" -#: includes/extra-strings.php:183 includes/post-types-admin.php:617 -msgid "DJs / Hosts" +#: includes/extra-strings.php:183 +msgid "Successfully parsed and imported YAML file, deleting pre-existing show data." msgstr "" #: includes/extra-strings.php:184 -msgid "Show Editors" +msgid "Successfully parsed and imported YAML file. Pre-existing show data remains unchanged." msgstr "" #: includes/extra-strings.php:185 -msgid "Role Assignment Interface" +msgid "Export successful. Use the following link(s to download your data." msgstr "" #: includes/extra-strings.php:186 -msgid "You can assign Radio Station roles to users directly here." +msgid "Data file" msgstr "" #: includes/extra-strings.php:187 -msgid "They can also be assigned via the WordPress user editor." +msgid "Image zip file" msgstr "" #: includes/extra-strings.php:188 -msgid "Grant Role to User(s" +msgid "Image tgz file" msgstr "" #: includes/extra-strings.php:189 -msgid "Remove Role from User(s" +msgid "Download one of the image files and the data file. See help for details on how to stage an import including images." msgstr "" #: includes/extra-strings.php:190 -msgid "Update User Roles" +msgid "Show data file (YAML" msgstr "" #: includes/extra-strings.php:191 -msgid "You have unsaved role changes." +msgid "Images not exported. See help for details on how to include images." msgstr "" #: includes/extra-strings.php:192 -msgid "role added to user" +msgid "YAML import error. See below for details." msgstr "" #: includes/extra-strings.php:193 -msgid "role removed from user" +msgid "Failed to create export folder" msgstr "" #: includes/extra-strings.php:194 -msgid "Visual Schedule Editor" +msgid "No image exported for" msgstr "" #: includes/extra-strings.php:195 -msgid "Table" +msgid "Failed to copy file. See below for details" msgstr "" #: includes/extra-strings.php:196 -msgid "Tabs" +msgid "Failed to create show_images.zip" msgstr "" #: includes/extra-strings.php:197 -msgid "Calendar" +msgid "Each show in the YAML file must define at minimum the following keys: " msgstr "" #: includes/extra-strings.php:198 -msgid "Your session has expired. You can log in again from this page or go to the login page." +msgid "show-title: may not be null." msgstr "" #: includes/extra-strings.php:199 -msgid "You have unsaved changes. Are you sure you want to close this window?" +msgid "show-description: may not be null." msgstr "" #: includes/extra-strings.php:200 -msgid "Save Shift" +msgid "show-image: must be a URL reference to an existing image." msgstr "" -#: includes/extra-strings.php:201 includes/post-types-admin.php:860 -msgid "Save Shifts" +#: includes/extra-strings.php:201 +msgid "show-avatar: must be a URL reference to an existing image." msgstr "" #: includes/extra-strings.php:202 -msgid "Save Override" +msgid "show-header: must be a URL reference to an existing image." msgstr "" -#: includes/extra-strings.php:203 includes/post-types-admin.php:3424 -msgid "Save Overrides" +#: includes/extra-strings.php:203 +msgid "upload-images: may not be null." msgstr "" #: includes/extra-strings.php:204 -msgid "Edit Show Shifts" +msgid "show-url: must be a valid web address." msgstr "" #: includes/extra-strings.php:205 -msgid "Edit Override Times" +msgid "show-podcast: must be a valid web address." msgstr "" #: includes/extra-strings.php:206 -msgid "Edit Show Shift" +msgid "show-user-list: must be a simple array of valid email addresses." msgstr "" #: includes/extra-strings.php:207 -msgid "Edit Override Time" +msgid "show-producer-list: must be a simple array of valid email addresses." msgstr "" #: includes/extra-strings.php:208 -msgid "Add to Schedule" +msgid "show-email: must be a valid email address." msgstr "" #: includes/extra-strings.php:209 -msgid "Add Show or Override to Schedule" +msgid "show-active: may not be null." msgstr "" #: includes/extra-strings.php:210 -msgid "Error. Provided Show ID does not exist." +msgid "YAML data parsed successfully, but contains formatting errors. See below for details." +msgstr "" + +#: includes/extra-strings.php:211 +msgid "Data file errors noted as follows:" msgstr "" #: includes/extra-strings.php:212 -msgid "Warning. Provided Shift ID is not valid." +msgid "show-schedule[] time blocks must be in 24h format and have the form \"04:55\" (note 0 padding." msgstr "" #: includes/extra-strings.php:213 -msgid "Warning. Provided Shift ID no longer exists." +msgid "Error, for show-schedule[], only \"disabled\" and \"encore\" flags are allowed" msgstr "" #: includes/extra-strings.php:214 -msgid "Display Shifts" +msgid "show-schedule[] must reference an array of time blocks containing at least one element." msgstr "" #: includes/extra-strings.php:215 -msgid "All Show Shifts" +msgid "Invalid weekday. show-schedule[] must be one of \"sun\"..\"sat\", or \"sunday\"..\"saturday\" (case insensitive." msgstr "" #: includes/extra-strings.php:216 -msgid "Warning. Provided Override Shift ID no longer exists." +msgid " show-schedule: must define at least one weekday." msgstr "" #: includes/extra-strings.php:217 -msgid "Override" -msgstr "" - -#: includes/extra-strings.php:218 -msgid "Display Override" +msgid "Import/Export Show Data" msgstr "" -#: includes/extra-strings.php:219 -msgid "All Override Times" +#: includes/extra-strings.php:218 radio-station-admin.php:235 +msgid "Import/Export" msgstr "" #: includes/extra-strings.php:220 -msgid "Error. Provided ID is not a Show or Override." +msgid "Import show data from a YAML file." msgstr "" #: includes/extra-strings.php:221 -msgid "Error! Invalid date value passed." +msgid "Delete existing show data" msgstr "" #: includes/extra-strings.php:222 -msgid "Error! Invalid day value passed." -msgstr "" - -#: includes/extra-strings.php:223 -msgid "Timeslot Type" +msgid "WARNING" msgstr "" #: includes/extra-strings.php:224 -msgid "Override Date" +msgid "Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options." msgstr "" #: includes/extra-strings.php:225 -msgid "Shift Day" +msgid "Advanced" msgstr "" -#: includes/extra-strings.php:226 includes/post-types-admin.php:1189 -#: includes/post-types-admin.php:1537 -msgid "Encore" +#: includes/extra-strings.php:226 +msgid "YAML file name" msgstr "" -#: includes/extra-strings.php:227 includes/post-types-admin.php:1196 -#: includes/post-types-admin.php:1548 includes/post-types-admin.php:3742 -#: includes/post-types-admin.php:4052 includes/post-types-admin.php:4730 -msgid "Disabled" +#: includes/extra-strings.php:227 +msgid "Default similar to" msgstr "" -#: includes/extra-strings.php:228 includes/post-types-admin.php:1119 -#: includes/post-types-admin.php:1453 includes/post-types-admin.php:3632 -#: includes/post-types-admin.php:3968 -msgid "Start Time" +#: includes/extra-strings.php:228 +msgid "URL where show images will be staged for import (see help" msgstr "" -#: includes/extra-strings.php:229 includes/post-types-admin.php:1152 -#: includes/post-types-admin.php:1494 includes/post-types-admin.php:3672 -#: includes/post-types-admin.php:4001 -msgid "End Time" +#: includes/extra-strings.php:229 +msgid "Metadata Source" msgstr "" #: includes/extra-strings.php:230 -msgid "Show Type" +msgid "Default Plugin Setting" msgstr "" #: includes/extra-strings.php:231 -msgid "Override Type" +msgid "Override: Metadata On" msgstr "" #: includes/extra-strings.php:232 -msgid "Select Show..." +msgid "Override: Metadata Off" msgstr "" -#: includes/extra-strings.php:233 includes/post-types-admin.php:863 -msgid "Add Shift" +#: includes/extra-strings.php:233 +msgid "Override: Metadata URL" msgstr "" #: includes/extra-strings.php:234 -msgid "Select Override..." +msgid "Override: Current Playlist" msgstr "" -#: includes/extra-strings.php:235 includes/post-types-admin.php:3427 -msgid "Add Override" +#: includes/extra-strings.php:235 +msgid "Metadata Override URL" msgstr "" #: includes/extra-strings.php:236 -msgid "Open in a New Window" +msgid "Color Options" msgstr "" #: includes/extra-strings.php:237 -msgid "Create New Show" +msgid "Player Text Color" msgstr "" #: includes/extra-strings.php:238 -msgid "Create New Override" +msgid "Player Background Color" msgstr "" #: includes/extra-strings.php:239 -msgid "Adding Show Shift..." +msgid "Playing Color" msgstr "" #: includes/extra-strings.php:240 -msgid "Show Shift Added." +msgid "Buttons Color" msgstr "" -#: includes/extra-strings.php:241 includes/post-types-admin.php:857 -msgid "Clear Shifts" +#: includes/extra-strings.php:241 +msgid "Track Color" msgstr "" #: includes/extra-strings.php:242 -msgid "Adding Override Time..." +msgid "Track Thumb Color" msgstr "" #: includes/extra-strings.php:243 -msgid "Override Time Saved." -msgstr "" - -#: includes/extra-strings.php:244 includes/post-types-admin.php:3421 -msgid "Clear Overrides" -msgstr "" - -#: includes/extra-strings.php:245 -msgid "Load Schedule for Previous Week" -msgstr "" - -#: includes/extra-strings.php:246 -msgid "Load Schedule for Next Week" +msgid "Advanced Options" msgstr "" #: includes/extra-strings.php:247 -msgid "View" +msgid "Current Show Display" msgstr "" #: includes/extra-strings.php:248 -msgid "No Hosts were found to display." +msgid "Now Playing Track Display" msgstr "" #: includes/extra-strings.php:249 -msgid "No Producers were found to display." +msgid "Leave blank to use stream URL." msgstr "" #: includes/extra-strings.php:250 -msgid "No Episodes were found to display." +msgid "Custom Theme" msgstr "" -#: includes/extra-strings.php:251 includes/shortcodes.php:1645 -msgid "Published on " +#: includes/extra-strings.php:251 +msgid "Popup Player to separate window." msgstr "" #: includes/extra-strings.php:252 -msgid "No %s were found to display." +msgid "Popup Player" msgstr "" #: includes/extra-strings.php:253 -msgid "Social Icons" +msgid "Edit Your Host Profile" msgstr "" #: includes/extra-strings.php:254 -msgid "Soundcloud" +msgid "Host Profile" msgstr "" #: includes/extra-strings.php:255 -msgid "Mixcloud" +msgid "Edit Your Producer Profile" msgstr "" #: includes/extra-strings.php:256 -msgid "Spotify" +msgid "Producer Profile" msgstr "" -#: includes/extra-strings.php:257 -msgid "Twitch" +#: includes/extra-strings.php:257 includes/extra-strings.php:349 +msgid "You do not have permission to do that." msgstr "" #: includes/extra-strings.php:258 -msgid "Discord" +msgid "Profile Information" msgstr "" -#: includes/extra-strings.php:259 -msgid "Facebook" +#: includes/extra-strings.php:259 includes/post-types-admin.php:474 +#: includes/post-types-admin.php:3208 +msgid "Website Link" msgstr "" #: includes/extra-strings.php:260 -msgid "Twitter" +msgid "%s Email" msgstr "" #: includes/extra-strings.php:261 -msgid "YouTube" +msgid "%s Phone" msgstr "" -#: includes/extra-strings.php:262 -msgid "LinkedIn" +#: includes/extra-strings.php:262 includes/post-types-admin.php:527 +#: includes/post-types-admin.php:3319 +msgid "Patreon Page ID" msgstr "" #: includes/extra-strings.php:263 -msgid "Xing" +msgid "Host Image" msgstr "" #: includes/extra-strings.php:264 -msgid "Instagram" +msgid "Producer Image" msgstr "" #: includes/extra-strings.php:265 -msgid "WhatsApp" +msgid "User Save Error! The selected User is already assigned to a %s Profile." msgstr "" -#: includes/extra-strings.php:266 -msgid "Pinterest" +#: includes/extra-strings.php:266 includes/post-types-admin.php:2630 +#: includes/post-types-admin.php:3164 includes/post-types.php:193 +msgid "Hosts" msgstr "" #: includes/extra-strings.php:267 -msgid "Custom" +msgid "Host User" msgstr "" -#: includes/extra-strings.php:268 -msgid "Add a Social Icon" +#: includes/extra-strings.php:268 includes/post-types-admin.php:3186 +#: includes/post-types.php:245 +msgid "Producers" msgstr "" #: includes/extra-strings.php:269 -msgid "Move this Icon Up" +msgid "Producer User" msgstr "" #: includes/extra-strings.php:270 -msgid "Move this Icon Down" +msgid "Team" msgstr "" #: includes/extra-strings.php:271 -msgid "Remove this Social Icon" +msgid "View" msgstr "" #: includes/extra-strings.php:272 -msgid "URL" +msgid "All" msgstr "" #: includes/extra-strings.php:273 -msgid "Icon URL" +msgid "Editors" msgstr "" -#: includes/extra-strings.php:274 -msgid "Unknown" +#: includes/extra-strings.php:274 includes/post-types.php:43 +#: includes/post-types.php:51 radio-station-admin.php:214 +msgid "Shows" msgstr "" #: includes/extra-strings.php:275 -msgid "Social URL Page Link" +msgid "Recurring Overrides" msgstr "" #: includes/extra-strings.php:276 -msgid "Social Icon Image URL" +msgid "Clear Recurrences" msgstr "" #: includes/extra-strings.php:277 -msgid "Are you sure you want to remove this Social Icon?" +msgid "Save Recurrences" msgstr "" #: includes/extra-strings.php:278 -msgid "No Social Icon Services defined." +msgid "Add Recurrence" msgstr "" #: includes/extra-strings.php:279 -msgid "You can reorder this icon after saving it." +msgid "Saving Recurrences..." msgstr "" #: includes/extra-strings.php:280 -msgid "Change Timezone" +msgid "Recurrences Saved." msgstr "" #: includes/extra-strings.php:281 -msgid "Select Region..." +msgid "Single Overrides" msgstr "" -#: includes/extra-strings.php:282 -msgid "Clear" +#: includes/extra-strings.php:282 includes/post-types-admin.php:3766 +#: includes/post-types-admin.php:4114 templates/playlist-export.php:14 +msgid "Start Date" msgstr "" -#: includes/extra-strings.php:283 -msgid "Cancel" +#: includes/extra-strings.php:283 includes/post-types-admin.php:1163 +#: includes/post-types-admin.php:1502 includes/post-types-admin.php:3777 +#: includes/post-types-admin.php:4120 +msgid "Start Time" msgstr "" -#: includes/extra-strings.php:284 -msgid "Select Timezone..." +#: includes/extra-strings.php:284 includes/post-types-admin.php:1197 +#: includes/post-types-admin.php:1546 includes/post-types-admin.php:3818 +#: includes/post-types-admin.php:4155 +msgid "End Time" +msgstr "" + +#: includes/extra-strings.php:285 includes/post-types-admin.php:1238 +#: includes/post-types-admin.php:1602 includes/post-types-admin.php:3889 +#: includes/post-types-admin.php:4208 includes/post-types-admin.php:4902 +msgid "Disabled" +msgstr "" + +#: includes/extra-strings.php:286 +msgid "Multiday" msgstr "" #: includes/extra-strings.php:287 -msgid "Automatic Reloading" +msgid "Repeats" msgstr "" -#: includes/extra-strings.php:288 templates/master-schedule-list.php:28 -#: templates/master-schedule-table.php:29 -#: templates/master-schedule-table.php:587 -#: templates/master-schedule-tabs.php:30 templates/master-schedule-tabs.php:485 -msgid "to" +#: includes/extra-strings.php:288 +msgid "Once Every Month" msgstr "" -#: includes/extra-strings.php:289 includes/shortcodes.php:2254 -#: includes/shortcodes.php:2928 radio-station-admin.php:1552 -#: templates/master-schedule-div.php:122 -#: templates/master-schedule-legacy.php:173 -#: templates/master-schedule-list.php:311 -#: templates/master-schedule-table.php:507 -#: templates/master-schedule-tabs.php:406 -msgid "with" +#: includes/extra-strings.php:289 +msgid "By Week Intervals" msgstr "" -#: includes/extra-strings.php:290 includes/shortcodes.php:2291 -#: includes/shortcodes.php:2965 templates/master-schedule-div.php:133 -#: templates/master-schedule-legacy.php:184 -#: templates/master-schedule-list.php:326 -#: templates/master-schedule-table.php:523 -#: templates/master-schedule-tabs.php:420 templates/single-show-content.php:356 -#: templates/single-show-content.php:395 -msgid "and" +#: includes/extra-strings.php:290 +msgid "Every" msgstr "" -#: includes/extra-strings.php:291 templates/master-schedule-div.php:188 -#: templates/master-schedule-legacy.php:229 -#: templates/master-schedule-list.php:410 -#: templates/master-schedule-table.php:603 -#: templates/master-schedule-tabs.php:502 -msgid "encore airing" +#: includes/extra-strings.php:291 +msgid "Weeks" msgstr "" -#: includes/extra-strings.php:292 templates/master-schedule-div.php:198 -#: templates/master-schedule-legacy.php:244 -#: templates/master-schedule-list.php:431 -#: templates/master-schedule-table.php:623 -#: templates/master-schedule-tabs.php:523 -msgid "Audio File" +#: includes/extra-strings.php:292 +msgid "On the" msgstr "" -#: includes/extra-strings.php:293 includes/master-schedule.php:653 -#: radio-station-admin.php:193 templates/master-schedule-list.php:453 -#: templates/master-schedule-tabs.php:544 -msgid "Genres" +#: includes/extra-strings.php:293 +msgid "First" msgstr "" -#: includes/extra-strings.php:294 -msgid "No Shows" +#: includes/extra-strings.php:294 radio-station.php:1007 +msgid "Second" msgstr "" #: includes/extra-strings.php:295 -msgid "No Shows scheduled for this date." +msgid "Third" msgstr "" -#: includes/extra-strings.php:296 templates/master-schedule-table.php:172 -#: templates/master-schedule-tabs.php:185 -msgid "Previous Day" +#: includes/extra-strings.php:296 +msgid "Fourth" msgstr "" -#: includes/extra-strings.php:297 templates/master-schedule-table.php:185 -#: templates/master-schedule-tabs.php:201 -msgid "Next Day" +#: includes/extra-strings.php:297 +msgid "Fifth" msgstr "" -#: includes/extra-strings.php:298 templates/master-schedule-tabs.php:222 -msgid "Viewing" +#: includes/extra-strings.php:298 +msgid "Last" msgstr "" -#: includes/extra-strings.php:299 templates/master-schedule-tabs.php:646 -msgid "No Shows scheduled for this day." +#: includes/extra-strings.php:299 +msgid "Cutoff Date" msgstr "" -#: includes/extra-strings.php:300 templates/single-show-content.php:144 -msgid "Show RSS Feed" +#: includes/extra-strings.php:300 +msgid "List Recurrence Dates" msgstr "" -#: includes/extra-strings.php:301 templates/single-show-content.php:245 -msgid "Become a Supporter for" +#: includes/extra-strings.php:301 +msgid "Preview Recurrence Dates" msgstr "" #: includes/extra-strings.php:302 -msgid "Episode Info" +msgid "Duplicate Recurrence" msgstr "" #: includes/extra-strings.php:303 -msgid "Broadcasted" +msgid "Remove Recurrence" msgstr "" #: includes/extra-strings.php:304 -msgid "on" +msgid "Are you sure you want to remove this Recurrence?" msgstr "" -#: includes/extra-strings.php:305 templates/single-show-content.php:331 -msgid "Hosted by" +#: includes/extra-strings.php:305 +msgid "Are you sure you want to clear all Recurrences?" msgstr "" -#: includes/extra-strings.php:306 templates/single-show-content.php:370 -msgid "Produced by" +#: includes/extra-strings.php:306 +msgid "Extra Days" msgstr "" -#: includes/extra-strings.php:307 templates/single-show-content.php:750 -msgid "Show More" +#: includes/extra-strings.php:307 +msgid "Number of Weeks" msgstr "" -#: includes/extra-strings.php:308 templates/single-show-content.php:753 -msgid "Show Less" +#: includes/extra-strings.php:308 +msgid "Preview Recurrences" msgstr "" -#: includes/extra-strings.php:309 -msgid "Listen to this %s" +#: includes/extra-strings.php:309 includes/post-types-admin.php:2038 +#: includes/post-types-admin.php:4272 includes/post-types-admin.php:5833 +msgid "Expired. Publish or Update instead." msgstr "" -#: includes/extra-strings.php:310 radio-station.php:1491 -msgid "Player" +#: includes/extra-strings.php:310 includes/post-types-admin.php:4274 +msgid "Failed. Invalid Override." msgstr "" -#: includes/extra-strings.php:311 -msgid "Download this Episode" +#: includes/extra-strings.php:311 includes/post-types-admin.php:2036 +#: includes/post-types-admin.php:4276 +msgid "Failed. Publish or Update instead." msgstr "" #: includes/extra-strings.php:312 -msgid "About this %s" +msgid "Warning! Recurring conflicts detected." msgstr "" -#: includes/extra-strings.php:313 templates/single-show-content.php:778 -msgid "About" +#: includes/extra-strings.php:313 +msgid "Error! Override not found." msgstr "" #: includes/extra-strings.php:314 -msgid "%s Show" +msgid "Error! No matching recurrence found." msgstr "" #: includes/extra-strings.php:315 -msgid "%s Playlist" +msgid "Error! No saved recurrences were found." msgstr "" -#: includes/extra-strings.php:316 templates/single-show-content.php:992 -msgid "Jump to" +#: includes/extra-strings.php:316 +msgid "Cannot list recurring times without a valid start time." msgstr "" #: includes/extra-strings.php:317 -msgid "Visit %s Website" +msgid "Cannot list recurring times without a valid end time." msgstr "" #: includes/extra-strings.php:318 -msgid "%s Phone Number" +msgid "Override Date" msgstr "" #: includes/extra-strings.php:319 -msgid "Email %s" +msgid "Shows Affected" msgstr "" -#: includes/extra-strings.php:320 -msgid "%s RSS Feed" +#: includes/extra-strings.php:320 includes/post-types-admin.php:624 +msgid "DJs / Hosts" msgstr "" -#: includes/extra-strings.php:321 templates/single-show-content.php:277 -msgid "Download Latest Broadcast" +#: includes/extra-strings.php:321 +msgid "Show Editors" msgstr "" #: includes/extra-strings.php:322 -msgid "Profile Info" +msgid "Role Assignment Interface" msgstr "" #: includes/extra-strings.php:323 -msgid "Website" +msgid "You can assign Radio Station roles to users directly here." msgstr "" #: includes/extra-strings.php:324 -msgid "Email" +msgid "They can also be assigned via the WordPress user editor." msgstr "" #: includes/extra-strings.php:325 -msgid "Phone" +msgid "Grant Role to User(s" msgstr "" -#: includes/legacy.php:623 -msgid "More Show Blog Posts" +#: includes/extra-strings.php:326 +msgid "Remove Role from User(s" msgstr "" -#. translators: 1: PHP function name, 2: Version number, 3: Alternative -#. function name. - -#: includes/legacy.php:660 -msgid "%1$s is deprecated since Radio Station version %2$s! Use %3$s or check documentation for updated functionality." +#: includes/extra-strings.php:327 +msgid "Update User Roles" msgstr "" -#: includes/master-schedule.php:662 -msgid "Click to toggle Highlight of Shows with this Genre." +#: includes/extra-strings.php:328 +msgid "You have unsaved role changes." msgstr "" -#: includes/post-types-admin.php:173 -msgid "Show Language" +#: includes/extra-strings.php:329 +msgid "role added to user" msgstr "" -#: includes/post-types-admin.php:210 -msgid "Main Radio Language" +#: includes/extra-strings.php:330 +msgid "role removed from user" msgstr "" -#: includes/post-types-admin.php:214 -msgid "Select below if Show language(s) differ." +#: includes/extra-strings.php:331 +msgid "Visual Schedule Editor" msgstr "" -#: includes/post-types-admin.php:239 -msgid "Remove Language" +#: includes/extra-strings.php:332 +msgid "Table" msgstr "" -#: includes/post-types-admin.php:248 -msgid "Select Language" +#: includes/extra-strings.php:333 +msgid "Tabs" msgstr "" -#: includes/post-types-admin.php:264 -msgid "Click on a Language to Add it." +#: includes/extra-strings.php:334 options.php:1051 +msgid "Grid" msgstr "" -#: includes/post-types-admin.php:413 -msgid "Show Information" +#: includes/extra-strings.php:335 +msgid "Calendar" msgstr "" -#: includes/post-types-admin.php:457 -msgid "Active" +#: includes/extra-strings.php:336 +msgid "Your session has expired. You can log in again from this page or go to the login page." msgstr "" -#: includes/post-types-admin.php:462 -msgid "Check this box if show is currently active (Show will not appear on schedule if unchecked.)" +#: includes/extra-strings.php:337 +msgid "You have unsaved changes. Are you sure you want to close this window?" msgstr "" -#: includes/post-types-admin.php:480 includes/post-types-admin.php:3124 -msgid "Show Email" +#: includes/extra-strings.php:338 +msgid "Save Shift" msgstr "" -#: includes/post-types-admin.php:501 -msgid "Latest Audio File" +#: includes/extra-strings.php:339 includes/post-types-admin.php:875 +msgid "Save Shifts" msgstr "" -#: includes/post-types-admin.php:512 includes/post-types-admin.php:3187 -msgid "Disable Download" +#: includes/extra-strings.php:340 +msgid "Save Override" msgstr "" -#: includes/post-types-admin.php:541 -msgid "Show DJ(s) / Host(s)" +#: includes/extra-strings.php:341 includes/post-types-admin.php:3540 +msgid "Save Overrides" msgstr "" -#: includes/post-types-admin.php:545 includes/post-types-admin.php:699 -msgid "Show Producer(s)" +#: includes/extra-strings.php:342 +msgid "Edit Show Shifts" msgstr "" -#: includes/post-types-admin.php:549 -msgid "Show Language(s)" +#: includes/extra-strings.php:343 +msgid "Edit Override Times" msgstr "" -#: includes/post-types-admin.php:686 includes/post-types-admin.php:764 -msgid "Ctrl-Click selects multiple." +#: includes/extra-strings.php:344 +msgid "Edit Show Shift" msgstr "" -#: includes/post-types-admin.php:781 -msgid "Show Schedule" +#: includes/extra-strings.php:345 +msgid "Edit Override Time" msgstr "" -#: includes/post-types-admin.php:827 -msgid "This Show is inactive!" +#: includes/extra-strings.php:346 +msgid "Add to Schedule" msgstr "" -#: includes/post-types-admin.php:828 -msgid "All Shifts are inactive also until Show is activated." +#: includes/extra-strings.php:347 +msgid "Add Show or Override to Schedule" msgstr "" -#: includes/post-types-admin.php:868 -msgid "Saving Show Shifts..." +#: includes/extra-strings.php:348 +msgid "Error. Provided Show ID does not exist." msgstr "" -#: includes/post-types-admin.php:869 -msgid "Show Shifts Saved." +#: includes/extra-strings.php:350 +msgid "Warning. Provided Shift ID is not valid." msgstr "" -#: includes/post-types-admin.php:952 -msgid "Are you sure you want to remove this shift?" +#: includes/extra-strings.php:351 +msgid "Warning. Provided Shift ID no longer exists." msgstr "" -#: includes/post-types-admin.php:953 -msgid "Are you sure you want to clear the shift list?" +#: includes/extra-strings.php:352 +msgid "Display Shifts" msgstr "" -#: includes/post-types-admin.php:1104 includes/post-types-admin.php:1436 -#: radio-station.php:2020 -msgid "Day" +#: includes/extra-strings.php:353 +msgid "Selected Shift" msgstr "" -#: includes/post-types-admin.php:1201 includes/post-types-admin.php:1555 -msgid "Duplicate Shift" +#: includes/extra-strings.php:354 +msgid "All Show Shifts" msgstr "" -#: includes/post-types-admin.php:1206 includes/post-types-admin.php:1562 -msgid "Remove Shift" +#: includes/extra-strings.php:355 +msgid "Warning. Provided Override Shift ID no longer exists." msgstr "" -#: includes/post-types-admin.php:1226 -msgid "Warning! Show Shift Conflicts were detected!" +#: includes/extra-strings.php:356 +msgid "Override" msgstr "" -#: includes/post-types-admin.php:1227 -msgid "Please note that Shifts with conflicts are automatically disabled upon saving." +#: includes/extra-strings.php:357 +msgid "Display Override" msgstr "" -#: includes/post-types-admin.php:1228 -msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." +#: includes/extra-strings.php:358 +msgid "Selected Override Time" msgstr "" -#: includes/post-types-admin.php:1229 -msgid "Then you can uncheck the shift Disable box and Save again to re-enable the Shift." +#: includes/extra-strings.php:359 +msgid "All Override Times" msgstr "" -#: includes/post-types-admin.php:1572 -msgid "Shift Conflicts" +#: includes/extra-strings.php:360 +msgid "Error. Provided ID is not a Show or Override." msgstr "" -#: includes/post-types-admin.php:1578 -msgid "This Show" +#: includes/extra-strings.php:361 +msgid "Error! Invalid date value passed." msgstr "" -#: includes/post-types-admin.php:1626 -msgid "Show Description" +#: includes/extra-strings.php:362 +msgid "Error! Invalid day value passed." msgstr "" -#: includes/post-types-admin.php:1642 -msgid "The text field below is for your Show Description. It will display in the About section of your Show page." +#: includes/extra-strings.php:363 +msgid "Timeslot Type" msgstr "" -#: includes/post-types-admin.php:1643 -msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." +#: includes/extra-strings.php:364 +msgid "Show Shift" msgstr "" -#: includes/post-types-admin.php:1644 -msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." +#: includes/extra-strings.php:365 includes/post-types.php:142 +msgid "Schedule Override" msgstr "" -#: includes/post-types-admin.php:1645 -msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." +#: includes/extra-strings.php:366 +msgid "Recurring Override" msgstr "" -#: includes/post-types-admin.php:1646 -msgid "This way you can then assign them to a relevant Post Category for display on your site also." +#: includes/extra-strings.php:367 +msgid "Shift Day" msgstr "" -#: includes/post-types-admin.php:1672 -msgid "Show Logo" +#: includes/extra-strings.php:368 includes/post-types-admin.php:1231 +#: includes/post-types-admin.php:1591 +msgid "Encore" msgstr "" -#: includes/post-types-admin.php:1688 -msgid "Show Images" +#: includes/extra-strings.php:369 +msgid "Show Type" msgstr "" -#: includes/post-types-admin.php:1736 -msgid "Set Show Avatar Image" +#: includes/extra-strings.php:370 +msgid "Existing Show" msgstr "" -#: includes/post-types-admin.php:1743 -msgid "Remove Show Avatar Image" +#: includes/extra-strings.php:371 includes/post-types.php:48 +msgid "New Show" msgstr "" -#: includes/post-types-admin.php:1779 -msgid "Set Show Header Image" +#: includes/extra-strings.php:372 +msgid "Override Type" msgstr "" -#: includes/post-types-admin.php:1786 -msgid "Remove Show Header Image" +#: includes/extra-strings.php:373 +msgid "Existing Override" msgstr "" -#: includes/post-types-admin.php:1978 includes/post-types-admin.php:4117 -msgid "Failed. Publish or Update instead." +#: includes/extra-strings.php:374 +msgid "New Override" msgstr "" -#: includes/post-types-admin.php:1982 -msgid "Error! No Show ID provided." +#: includes/extra-strings.php:375 +msgid "Select Show..." msgstr "" -#: includes/post-types-admin.php:1987 -msgid "Failed. Invalid Show." +#: includes/extra-strings.php:376 includes/post-types-admin.php:879 +msgid "Add Shift" msgstr "" -#: includes/post-types-admin.php:2410 -msgid "Warning! Shift conflicts detected." +#: includes/extra-strings.php:377 +msgid "Select Override..." msgstr "" -#: includes/post-types-admin.php:2552 -msgid "Active?" +#: includes/extra-strings.php:378 includes/post-types-admin.php:3543 +msgid "Add Override" msgstr "" -#: includes/post-types-admin.php:2554 -msgid "About?" +#: includes/extra-strings.php:379 +msgid "Open in a New Window" msgstr "" -#: includes/post-types-admin.php:2555 -msgid "Shifts" +#: includes/extra-strings.php:380 +msgid "Create New Show" msgstr "" -#: includes/post-types-admin.php:2562 includes/post-types-admin.php:2746 -#: includes/post-types-admin.php:3019 -msgid "Show Avatar" +#: includes/extra-strings.php:381 +msgid "Create New Override" msgstr "" -#: includes/post-types-admin.php:2578 includes/post-types-admin.php:2594 -#: includes/post-types-admin.php:4782 -msgid "Yes" +#: includes/extra-strings.php:382 +msgid "Adding Show Shift..." msgstr "" -#: includes/post-types-admin.php:2580 includes/post-types-admin.php:2592 -#: includes/post-types-admin.php:4780 -msgid "No" +#: includes/extra-strings.php:383 +msgid "Show Shift Added." msgstr "" -#: includes/post-types-admin.php:2637 -msgid "This Shift is Disabled." +#: includes/extra-strings.php:384 includes/post-types-admin.php:871 +msgid "Clear Shifts" msgstr "" -#: includes/post-types-admin.php:2646 -msgid "This Shift has Schedule Conflicts and is Disabled." +#: includes/extra-strings.php:385 +msgid "Adding Override Time..." msgstr "" -#: includes/post-types-admin.php:2648 -msgid "This Shift has Schedule Conflicts." +#: includes/extra-strings.php:386 +msgid "Override Time Saved." msgstr "" -#: includes/post-types-admin.php:2657 -msgid "This Show is not currently active." +#: includes/extra-strings.php:387 includes/post-types-admin.php:3537 +msgid "Clear Overrides" msgstr "" -#: includes/post-types-admin.php:2697 -msgid "This shift is disabled as no day is set." +#: includes/extra-strings.php:388 +msgid "Load Schedule for Previous Week" msgstr "" -#: includes/post-types-admin.php:2788 -msgid "Filter by show day" +#: includes/extra-strings.php:389 +msgid "Load Schedule for Next Week" msgstr "" -#: includes/post-types-admin.php:2790 -msgid "All show days" +#: includes/extra-strings.php:390 options.php:1050 +msgid "List" msgstr "" -#: includes/post-types-admin.php:2812 -msgid "Override Show Data" +#: includes/extra-strings.php:391 +msgid "No Hosts were found to display." msgstr "" -#: includes/post-types-admin.php:2850 -msgid "Link to Show" +#: includes/extra-strings.php:392 +msgid "No Producers were found to display." msgstr "" -#: includes/post-types-admin.php:2858 -msgid "No Show Link" +#: includes/extra-strings.php:393 +msgid "No Episodes were found to display." msgstr "" -#: includes/post-types-admin.php:2894 -msgid "If selected, Override data will be used from the Linked Show." +#: includes/extra-strings.php:394 includes/shortcodes.php:2106 +msgid "Published on " msgstr "" -#: includes/post-types-admin.php:2911 -msgid "If checked, assigned Genre terms are synced from the Linked Show when Updating." +#: includes/extra-strings.php:395 +msgid "No %s were found to display." msgstr "" -#: includes/post-types-admin.php:2929 -msgid "If checked, assigned Language terms are synced from the Linked Show when Updating." +#: includes/extra-strings.php:396 +msgid "Social Icons" msgstr "" -#: includes/post-types-admin.php:2949 -msgid "Usage Note" +#: includes/extra-strings.php:397 +msgid "Soundcloud" msgstr "" -#: includes/post-types-admin.php:2950 -msgid " Unchecked boxes use Show data, checked boxes use Override data." +#: includes/extra-strings.php:398 +msgid "Mixcloud" msgstr "" -#: includes/post-types-admin.php:2955 -msgid "Override?" +#: includes/extra-strings.php:399 +msgid "Spotify" msgstr "" -#: includes/post-types-admin.php:2957 -msgid "Override Data" +#: includes/extra-strings.php:400 +msgid "Twitch" msgstr "" -#: includes/post-types-admin.php:2977 -msgid "Use Title Editor metabox above." +#: includes/extra-strings.php:401 +msgid "Discord" msgstr "" -#: includes/post-types-admin.php:2987 -msgid "Description" +#: includes/extra-strings.php:402 +msgid "Facebook" msgstr "" -#: includes/post-types-admin.php:2993 -msgid "Use Content Editor metabox below." +#: includes/extra-strings.php:403 +msgid "Twitter" msgstr "" -#: includes/post-types-admin.php:3003 -msgid "Excerpt" +#: includes/extra-strings.php:404 +msgid "YouTube" msgstr "" -#: includes/post-types-admin.php:3009 -msgid "Use Excerpt Editor metabox below." +#: includes/extra-strings.php:405 +msgid "LinkedIn" msgstr "" -#: includes/post-types-admin.php:3025 -msgid "Use Show Images metabox." +#: includes/extra-strings.php:406 +msgid "Xing" msgstr "" -#: includes/post-types-admin.php:3040 -msgid "Featured Image" +#: includes/extra-strings.php:407 +msgid "Instagram" msgstr "" -#: includes/post-types-admin.php:3046 -msgid "Use Feature Image metabox." +#: includes/extra-strings.php:408 +msgid "WhatsApp" msgstr "" -#: includes/post-types-admin.php:3067 -msgid "Use Host assignment box below." +#: includes/extra-strings.php:409 +msgid "Pinterest" msgstr "" -#: includes/post-types-admin.php:3088 -msgid "Use Producer assignment box below." +#: includes/extra-strings.php:410 +msgid "Custom" msgstr "" -#: includes/post-types-admin.php:3166 -msgid "Latest Audio" +#: includes/extra-strings.php:411 +msgid "Add a Social Icon" msgstr "" -#: includes/post-types-admin.php:3194 -msgid "Check to Disable" +#: includes/extra-strings.php:412 +msgid "Move this Icon Up" msgstr "" -#: includes/post-types-admin.php:3236 -msgid "Override DJ(s) / Host(s)" +#: includes/extra-strings.php:413 +msgid "Move this Icon Down" msgstr "" -#: includes/post-types-admin.php:3240 -msgid "Override Producer(s)" +#: includes/extra-strings.php:414 +msgid "Remove this Social Icon" msgstr "" -#: includes/post-types-admin.php:3384 -msgid "Override Schedule" +#: includes/extra-strings.php:415 +msgid "URL" msgstr "" -#: includes/post-types-admin.php:3432 -msgid "Saving Overrides..." +#: includes/extra-strings.php:416 +msgid "Icon URL" msgstr "" -#: includes/post-types-admin.php:3433 -msgid "Overrides Saved." +#: includes/extra-strings.php:417 +msgid "Unknown" msgstr "" -#: includes/post-types-admin.php:3621 includes/post-types-admin.php:3962 -#: templates/playlist-export.php:14 -msgid "Start Date" +#: includes/extra-strings.php:418 +msgid "Social URL Page Link" msgstr "" -#: includes/post-types-admin.php:3754 includes/post-types-admin.php:4057 -msgid "Duplicate Override" +#: includes/extra-strings.php:419 +msgid "Social Icon Image URL" msgstr "" -#: includes/post-types-admin.php:3760 includes/post-types-admin.php:4062 -msgid "Remove Override" +#: includes/extra-strings.php:420 +msgid "Are you sure you want to remove this Social Icon?" msgstr "" -#: includes/post-types-admin.php:3805 -msgid "Are you sure you want to remove this Override?" +#: includes/extra-strings.php:421 +msgid "No Social Icon Services defined." msgstr "" -#: includes/post-types-admin.php:3806 -msgid "Are you sure you want to clear all overrides?" +#: includes/extra-strings.php:422 +msgid "You can reorder this icon after saving it." msgstr "" -#: includes/post-types-admin.php:4037 -msgid "Multiday" +#: includes/extra-strings.php:423 +msgid "Change Timezone" msgstr "" -#: includes/post-types-admin.php:4042 templates/playlist-export.php:78 -msgid "End Date" +#: includes/extra-strings.php:424 +msgid "Select Region..." msgstr "" -#: includes/post-types-admin.php:4115 -msgid "Failed. Invalid Override." +#: includes/extra-strings.php:425 +msgid "Clear" msgstr "" -#: includes/post-types-admin.php:4563 -msgid "Override Time(s)" +#: includes/extra-strings.php:426 +msgid "Cancel" msgstr "" -#: includes/post-types-admin.php:4564 -msgid "Affected Show(s)" +#: includes/extra-strings.php:427 +msgid "Select Timezone..." msgstr "" -#: includes/post-types-admin.php:4569 includes/post-types-admin.php:5374 -msgid "Linked Show" +#: includes/extra-strings.php:429 +msgid "Automatic Changeover Reloading" msgstr "" -#: includes/post-types-admin.php:4724 -msgid "Inactive" +#: includes/extra-strings.php:430 templates/master-schedule-list.php:28 +#: templates/master-schedule-table.php:29 +#: templates/master-schedule-table.php:600 +#: templates/master-schedule-tabs.php:30 templates/master-schedule-tabs.php:496 +msgid "to" msgstr "" -#: includes/post-types-admin.php:4808 -msgid "Override Logo" +#: includes/extra-strings.php:431 includes/shortcodes.php:2761 +#: includes/shortcodes.php:3509 radio-station-admin.php:1646 +#: templates/master-schedule-div.php:122 +#: templates/master-schedule-legacy.php:173 +#: templates/master-schedule-list.php:320 +#: templates/master-schedule-table.php:521 +#: templates/master-schedule-tabs.php:418 +msgid "with" msgstr "" -#: includes/post-types-admin.php:4882 -msgid "Filter by override date" +#: includes/extra-strings.php:432 includes/shortcodes.php:2798 +#: includes/shortcodes.php:3546 templates/master-schedule-div.php:133 +#: templates/master-schedule-legacy.php:184 +#: templates/master-schedule-list.php:335 +#: templates/master-schedule-table.php:537 +#: templates/master-schedule-tabs.php:432 templates/single-show-content.php:353 +#: templates/single-show-content.php:392 +msgid "and" msgstr "" -#: includes/post-types-admin.php:4884 -msgid "All Override Months" +#: includes/extra-strings.php:433 templates/master-schedule-div.php:188 +#: templates/master-schedule-legacy.php:229 +#: templates/master-schedule-list.php:418 +#: templates/master-schedule-table.php:616 +#: templates/master-schedule-tabs.php:509 +msgid "encore airing" msgstr "" -#: includes/post-types-admin.php:4912 -msgid "Past and Future" +#: includes/extra-strings.php:434 templates/master-schedule-div.php:198 +#: templates/master-schedule-legacy.php:244 +#: templates/master-schedule-list.php:438 +#: templates/master-schedule-table.php:635 +#: templates/master-schedule-tabs.php:529 +msgid "Audio File" msgstr "" -#: includes/post-types-admin.php:4914 includes/post-types.php:154 -msgid "All Overrides" +#: includes/extra-strings.php:435 includes/master-schedule.php:815 +#: radio-station-admin.php:219 templates/master-schedule-list.php:459 +#: templates/master-schedule-tabs.php:549 +msgid "Genres" msgstr "" -#: includes/post-types-admin.php:4915 -msgid "Past Overrides" +#: includes/extra-strings.php:436 +msgid "No Shows" msgstr "" -#: includes/post-types-admin.php:4916 -msgid "Overrides Today" +#: includes/extra-strings.php:437 +msgid "No scheduled Shows found for this date." msgstr "" -#: includes/post-types-admin.php:4917 -msgid "Future Overrides" +#: includes/extra-strings.php:438 templates/master-schedule-table.php:182 +#: templates/master-schedule-tabs.php:192 +msgid "Previous Day" msgstr "" -#: includes/post-types-admin.php:4941 -msgid "Playlist Entries" +#: includes/extra-strings.php:439 templates/master-schedule-table.php:195 +#: templates/master-schedule-tabs.php:208 +msgid "Next Day" msgstr "" -#: includes/post-types-admin.php:4961 includes/post-types-admin.php:5255 -msgid "Move Track Up" +#: includes/extra-strings.php:440 templates/master-schedule-tabs.php:231 +msgid "Viewing" msgstr "" -#: includes/post-types-admin.php:4962 includes/post-types-admin.php:5256 -msgid "Move Track Down" +#: includes/extra-strings.php:441 +msgid "No Shows scheduled." msgstr "" -#: includes/post-types-admin.php:4963 includes/post-types-admin.php:5257 -msgid "Duplicate Track" +#: includes/extra-strings.php:442 templates/single-show-content.php:141 +msgid "Show RSS Feed" msgstr "" -#: includes/post-types-admin.php:4964 includes/post-types-admin.php:5258 -msgid "Remove Track" +#: includes/extra-strings.php:443 templates/single-show-content.php:242 +msgid "Become a Supporter for" msgstr "" -#: includes/post-types-admin.php:4978 -msgid "Clear Tracks" +#: includes/extra-strings.php:444 +msgid "Episode Info" msgstr "" -#: includes/post-types-admin.php:4981 -msgid "Save Tracks" +#: includes/extra-strings.php:445 +msgid "Broadcasted" msgstr "" -#: includes/post-types-admin.php:4984 -msgid "Add Track" +#: includes/extra-strings.php:446 +msgid "on" msgstr "" -#: includes/post-types-admin.php:4989 -msgid "Saving Playlist Tracks..." +#: includes/extra-strings.php:447 templates/single-show-content.php:328 +msgid "Hosted by" msgstr "" -#: includes/post-types-admin.php:4990 -msgid "Playlist Tracks Saved." +#: includes/extra-strings.php:448 templates/single-show-content.php:367 +msgid "Produced by" msgstr "" -#: includes/post-types-admin.php:4998 -msgid "Tracks marked New are moved to the end of Playlist on update." +#: includes/extra-strings.php:449 templates/single-show-content.php:745 +msgid "Show More" msgstr "" -#: includes/post-types-admin.php:5001 -msgid "Are you sure you want to clear the track list?" +#: includes/extra-strings.php:450 templates/single-show-content.php:748 +msgid "Show Less" msgstr "" -#: includes/post-types-admin.php:5106 includes/post-types-admin.php:5314 -msgid "Length" +#: includes/extra-strings.php:451 +msgid "Listen to this %s" msgstr "" -#: includes/post-types-admin.php:5114 includes/post-types-admin.php:5331 -#: includes/shortcodes.php:3440 templates/single-playlist-content.php:49 -msgid "Comments" +#: includes/extra-strings.php:452 options.php:1333 +msgid "Player" msgstr "" -#: includes/post-types-admin.php:5115 includes/post-types-admin.php:5336 -msgid "New" +#: includes/extra-strings.php:453 +msgid "Download this Episode" msgstr "" -#: includes/post-types-admin.php:5117 includes/post-types-admin.php:5341 -#: includes/post-types-admin.php:5678 -msgid "Status" +#: includes/extra-strings.php:454 +msgid "About this %s" msgstr "" -#: includes/post-types-admin.php:5119 includes/post-types-admin.php:5343 -msgid "Queued" +#: includes/extra-strings.php:455 templates/single-show-content.php:773 +msgid "About" msgstr "" -#: includes/post-types-admin.php:5120 includes/post-types-admin.php:5344 -msgid "Played" +#: includes/extra-strings.php:456 +msgid "%s Show" msgstr "" -#: includes/post-types-admin.php:5123 includes/post-types-admin.php:5349 -msgid "Move" +#: includes/extra-strings.php:457 +msgid "%s Playlist" msgstr "" -#: includes/post-types-admin.php:5247 includes/post-types-admin.php:5677 -#: includes/shortcodes.php:3419 templates/legacy/single-playlist.php:45 -#: templates/single-playlist-content.php:45 -msgid "Artist" +#: includes/extra-strings.php:458 templates/single-show-content.php:987 +msgid "Jump to" msgstr "" -#: includes/post-types-admin.php:5248 includes/post-types-admin.php:5676 -#: includes/shortcodes.php:3411 templates/legacy/single-playlist.php:46 -#: templates/single-playlist-content.php:46 -msgid "Song" +#: includes/extra-strings.php:459 +msgid "Visit %s Website" msgstr "" -#: includes/post-types-admin.php:5249 includes/shortcodes.php:3426 -#: templates/legacy/single-playlist.php:47 -#: templates/single-playlist-content.php:47 -msgid "Album" +#: includes/extra-strings.php:460 +msgid "%s Phone Number" msgstr "" -#: includes/post-types-admin.php:5250 templates/legacy/single-playlist.php:48 -msgid "Record Label" +#: includes/extra-strings.php:461 +msgid "Email %s" msgstr "" -#: includes/post-types-admin.php:5316 -msgctxt "minutes unit" -msgid "m" +#: includes/extra-strings.php:462 +msgid "%s RSS Feed" msgstr "" -#: includes/post-types-admin.php:5328 -msgctxt "seconds unit" -msgid "s" +#: includes/extra-strings.php:463 templates/single-show-content.php:274 +msgid "Download Latest Broadcast" msgstr "" -#: includes/post-types-admin.php:5459 -msgid "No Shows were found." +#: includes/extra-strings.php:464 +msgid "Profile Info" msgstr "" -#: includes/post-types-admin.php:5462 -msgid "You are not assigned to any Shows." +#: includes/extra-strings.php:465 +msgid "Website" msgstr "" -#: includes/post-types-admin.php:5470 -msgid "Unassigned" +#: includes/extra-strings.php:466 +msgid "Email" msgstr "" -#: includes/post-types-admin.php:5508 -msgid "Failed. No Playlist ID provided." +#: includes/extra-strings.php:467 +msgid "Phone" msgstr "" -#: includes/post-types-admin.php:5513 -msgid "Failed. Invalid Playlist ID." +#: includes/extra-strings.php:468 +msgid "Archive List" msgstr "" -#: includes/post-types-admin.php:5649 -msgid "Tracks" +#: includes/extra-strings.php:469 +msgid "Archive list for Radio Station record types." msgstr "" -#: includes/post-types-admin.php:5650 -msgid "Track List" +#: includes/extra-strings.php:470 loader.php:2224 options.php:1332 +msgid "General" msgstr "" -#: includes/post-types-admin.php:5672 -msgid "Show/Hide Tracklist" +#: includes/extra-strings.php:471 +msgid "Archive List Details" msgstr "" -#: includes/post-types-admin.php:5750 -msgid "Related to Show" +#: includes/extra-strings.php:472 +msgid "Archive Type" msgstr "" -#: includes/post-types-admin.php:5813 -msgid "Select Show(s)" +#: includes/extra-strings.php:473 includes/post-types.php:153 +msgid "Overrides" msgstr "" -#: includes/post-types-admin.php:5980 -msgid "Related Show(s)" +#: includes/extra-strings.php:474 includes/post-types.php:93 +#: includes/post-types.php:101 radio-station-admin.php:217 +msgid "Playlists" msgstr "" -#: includes/post-types-admin.php:5995 -msgid "No Shows available to Select." +#: includes/extra-strings.php:475 +msgid "Shows by Genre" msgstr "" -#: includes/post-types-admin.php:6018 -msgid "Show(s)" +#: includes/extra-strings.php:476 +msgid "Shows by Language" msgstr "" -#: includes/post-types-admin.php:6063 includes/post-types.php:49 -msgid "Edit Show" +#: includes/extra-strings.php:477 +msgid "Archive View" msgstr "" -#: includes/post-types-admin.php:6146 -msgid "Set Related Show(s)" +#: includes/extra-strings.php:478 options.php:674 options.php:722 +msgid "List View" msgstr "" -#: includes/post-types-admin.php:6277 -msgid "Updated Related Shows for %d Posts." +#: includes/extra-strings.php:479 +msgid "Records Per Page" msgstr "" -#: includes/post-types-admin.php:6283 -msgid "Failed to Update Related Shows for %d Posts." +#: includes/extra-strings.php:480 +msgid "Use 0 for all records." msgstr "" -#: includes/post-types-admin.php:6529 -msgid "Failed. Please relogin and try again." +#: includes/extra-strings.php:481 +msgid "Display Pagination?" msgstr "" -#: includes/post-types.php:47 includes/post-types.php:48 -#: radio-station-admin.php:189 -msgid "Add Show" +#: includes/extra-strings.php:482 includes/post-types-admin.php:2653 +#: includes/post-types-admin.php:2665 includes/post-types-admin.php:4952 +msgid "No" msgstr "" -#: includes/post-types.php:50 -msgid "New Show" +#: includes/extra-strings.php:483 includes/post-types-admin.php:2651 +#: includes/post-types-admin.php:2667 includes/post-types-admin.php:4954 +msgid "Yes" msgstr "" -#: includes/post-types.php:51 -msgid "View Show" +#: includes/extra-strings.php:484 +msgid "Hide if Empty?" msgstr "" -#: includes/post-types.php:55 -msgid "Search Shows" +#: includes/extra-strings.php:485 +msgid "Archive Query" msgstr "" -#: includes/post-types.php:56 -msgid "No Shows found" +#: includes/extra-strings.php:486 +msgid "Order By" msgstr "" -#: includes/post-types.php:57 -msgid "No Shows found in Trash" +#: includes/extra-strings.php:488 +msgid "Published Date" msgstr "" -#: includes/post-types.php:58 -msgid "All Shows" +#: includes/extra-strings.php:489 +msgid "Modified Date" msgstr "" -#: includes/post-types.php:65 -msgid "Shows Archive" +#: includes/extra-strings.php:490 +msgid "Order" msgstr "" -#: includes/post-types.php:95 includes/post-types.php:103 -#: radio-station-admin.php:191 -msgid "Playlists" +#: includes/extra-strings.php:491 +msgid "Ascending" msgstr "" -#: includes/post-types.php:97 includes/post-types.php:98 -msgid "Add Playlist" +#: includes/extra-strings.php:492 +msgid "Descending" msgstr "" -#: includes/post-types.php:99 -msgid "Edit Playlist" +#: includes/extra-strings.php:493 +msgid "Archive Record Display" msgstr "" -#: includes/post-types.php:100 -msgid "New Playlist" +#: includes/extra-strings.php:494 +msgid "Time Display Format" msgstr "" -#: includes/post-types.php:101 includes/shortcodes.php:2338 -#: includes/shortcodes.php:3456 -msgid "View Playlist" +#: includes/extra-strings.php:497 +msgid "Description Display Format" msgstr "" -#: includes/post-types.php:105 -msgid "Search Playlists" +#: includes/extra-strings.php:498 +msgid "View Default" msgstr "" -#: includes/post-types.php:106 -msgid "No Playlists found" +#: includes/extra-strings.php:500 includes/post-types-admin.php:3103 +msgid "Excerpt" msgstr "" -#: includes/post-types.php:107 -msgid "No Playlists found in Trash" +#: includes/extra-strings.php:502 +msgid "Display Image?" msgstr "" -#: includes/post-types.php:108 -msgid "All Playlists" +#: includes/extra-strings.php:503 +msgid "This setting is for Shows and Overrides." msgstr "" -#: includes/post-types.php:115 -msgid "Playlists Archive" +#: includes/extra-strings.php:504 +msgid "Only Shows with Shifts?" msgstr "" -#: includes/post-types.php:143 radio-station-admin.php:194 -msgid "Schedule Overrides" +#: includes/extra-strings.php:505 +msgid "This setting is for Shows only." msgstr "" -#: includes/post-types.php:144 -msgid "Schedule Override" +#: includes/extra-strings.php:506 +msgid "Display Override Dates?" msgstr "" -#: includes/post-types.php:145 includes/post-types.php:146 -msgid "Add Schedule Override" +#: includes/extra-strings.php:507 +msgid "This setting is for Overrides only." msgstr "" -#: includes/post-types.php:147 -msgid "Edit Schedule Override" +#: includes/extra-strings.php:508 +msgid "Style" msgstr "" -#: includes/post-types.php:148 -msgid "New Schedule Override" +#: includes/extra-strings.php:509 +msgid "Container Styles" msgstr "" -#: includes/post-types.php:149 -msgid "View Schedule Override" +#: includes/extra-strings.php:510 +msgid "Alignment" msgstr "" -#: includes/post-types.php:151 -msgid "Search Overrides" +#: includes/extra-strings.php:511 +msgid "Typography" msgstr "" -#: includes/post-types.php:152 -msgid "No Overrides found" +#: includes/extra-strings.php:512 +msgid "Title Text Color" msgstr "" -#: includes/post-types.php:153 -msgid "No Overrides found in Trash" +#: includes/extra-strings.php:513 +msgid "Title Typography" msgstr "" -#: includes/post-types.php:155 -msgid "Overrides" +#: includes/extra-strings.php:514 +msgid "Description Text Color" msgstr "" -#: includes/post-types.php:162 -msgid "Schedule Overrides Archive" +#: includes/extra-strings.php:515 +msgid "Description Typography" msgstr "" -#: includes/post-types.php:196 -msgid "Host" +#: includes/extra-strings.php:516 +msgid "Meta Text Color" msgstr "" -#: includes/post-types.php:197 -msgid "Add New Host Profile" +#: includes/extra-strings.php:517 +msgid "Meta Typography" msgstr "" -#: includes/post-types.php:198 -msgid "Add Host Profile" +#: includes/extra-strings.php:519 +msgid "Radio Station Clock time display." msgstr "" -#: includes/post-types.php:199 -msgid "Edit Host Profile" +#: includes/extra-strings.php:520 +msgid "Radio Clock Display Options" msgstr "" -#: includes/post-types.php:200 -msgid "New Host Profile" +#: includes/extra-strings.php:521 widgets/class-radio-clock-widget.php:47 +msgid "Day Display Format" msgstr "" -#: includes/post-types.php:201 -msgid "View Host Profile" +#: includes/extra-strings.php:523 +msgid "Display Date?" msgstr "" -#: includes/post-types.php:202 -msgid "Show Hosts" +#: includes/extra-strings.php:524 widgets/class-radio-clock-widget.php:66 +msgid "Month Display Format" msgstr "" -#: includes/post-types.php:204 -msgid "Search Host Profiles" +#: includes/extra-strings.php:525 +msgid "Display Timezone?" msgstr "" -#: includes/post-types.php:205 -msgid "No Host Profiles found" +#: includes/extra-strings.php:526 +msgid "Display Seconds?" msgstr "" -#: includes/post-types.php:206 -msgid "No Host Profiles found in Trash" +#: includes/extra-strings.php:527 +msgid "Label Text Color" msgstr "" -#: includes/post-types.php:207 -msgid "All Host Profiles" +#: includes/extra-strings.php:528 +msgid "Label Typography" msgstr "" -#: includes/post-types.php:215 -msgid "Host Profiles Archive" +#: includes/extra-strings.php:529 +msgid "Times Text Color" msgstr "" -#: includes/post-types.php:248 -msgid "Producer" +#: includes/extra-strings.php:530 +msgid "Times Typography" msgstr "" -#: includes/post-types.php:249 -msgid "Add New Producer Profile" +#: includes/extra-strings.php:531 +msgid "Timezone Switcher Text Color" msgstr "" -#: includes/post-types.php:250 -msgid "Add Producer Profile" +#: includes/extra-strings.php:532 +msgid "Timezone Switcher Typography" msgstr "" -#: includes/post-types.php:251 -msgid "Edit Producer Profile" +#: includes/extra-strings.php:533 +msgid "Current Playlist" msgstr "" -#: includes/post-types.php:252 -msgid "New Producer Profile" +#: includes/extra-strings.php:534 +msgid "Current Playlist module for Radio Station." msgstr "" -#: includes/post-types.php:253 -msgid "View Producer Profile" +#: includes/extra-strings.php:535 +msgid "Loading Options" msgstr "" -#: includes/post-types.php:254 -msgid "Show Producers Profile" +#: includes/extra-strings.php:536 +msgid "AJAX Loading" msgstr "" -#: includes/post-types.php:256 -msgid "Search Producer Profiles" +#: includes/extra-strings.php:537 +msgid "To bypass page caching." msgstr "" -#: includes/post-types.php:257 -msgid "No Producer Profiles found" +#: includes/extra-strings.php:538 +msgid "Dynamic Reloading" msgstr "" -#: includes/post-types.php:258 -msgid "No Producer Profiles found in Trash" +#: includes/extra-strings.php:539 +msgid "Reload at Show changeovers." msgstr "" -#: includes/post-types.php:259 -msgid "All Producer Profiles" +#: includes/extra-strings.php:540 +msgid "No Current Playlist Text" msgstr "" -#: includes/post-types.php:267 -msgid "Producer Profiles Archive" +#: includes/extra-strings.php:541 +msgid "Blank for default. 0 for none." msgstr "" -#: includes/post-types.php:404 -msgid "Edit" +#: includes/extra-strings.php:542 +msgid "Extra Display Options" msgstr "" -#: includes/post-types.php:461 -msgctxt "taxonomy general name" -msgid "Genres" +#: includes/extra-strings.php:543 +msgid "Display Playlist Title" msgstr "" -#: includes/post-types.php:462 -msgctxt "taxonomy singular name" -msgid "Genre" +#: includes/extra-strings.php:544 +msgid "Link to Playlist Page" msgstr "" -#: includes/post-types.php:463 -msgid "Search Genres" +#: includes/extra-strings.php:545 +msgid "Remaining Time Countdown" msgstr "" -#: includes/post-types.php:464 -msgid "All Genres" +#: includes/extra-strings.php:546 +msgid "Show Time Display Options" msgstr "" -#: includes/post-types.php:465 -msgid "Parent Genre" +#: includes/extra-strings.php:547 +msgid "Display Song Title" msgstr "" -#: includes/post-types.php:466 -msgid "Parent Genre:" +#: includes/extra-strings.php:548 +msgid "Display Artist" msgstr "" -#: includes/post-types.php:467 -msgid "Edit Genre" +#: includes/extra-strings.php:549 +msgid "Display Album" msgstr "" -#: includes/post-types.php:468 -msgid "Update Genre" +#: includes/extra-strings.php:550 +msgid "Display Record Label" msgstr "" -#: includes/post-types.php:469 -msgid "Add New Genre" +#: includes/extra-strings.php:551 +msgid "Display Track Comments" msgstr "" -#: includes/post-types.php:470 -msgid "New Genre Name" +#: includes/extra-strings.php:552 +msgid "Track Info Text Color" msgstr "" -#: includes/post-types.php:471 -msgid "Genre" +#: includes/extra-strings.php:553 +msgid "Track Info Typography" msgstr "" -#: includes/post-types.php:507 -msgctxt "taxonomy general name" -msgid "Languages" +#: includes/extra-strings.php:554 +msgid "Playlist Title Text Color" msgstr "" -#: includes/post-types.php:508 -msgctxt "taxonomy singular name" -msgid "Language" +#: includes/extra-strings.php:555 +msgid "Playlist Title Typography" msgstr "" -#: includes/post-types.php:509 -msgid "Search Languages" +#: includes/extra-strings.php:556 +msgid "Countdown Text Color" msgstr "" -#: includes/post-types.php:510 -msgid "All Languages" +#: includes/extra-strings.php:557 +msgid "Countdown Typography" msgstr "" -#: includes/post-types.php:511 -msgid "Parent Language" +#: includes/extra-strings.php:558 +msgid "Current Show" msgstr "" -#: includes/post-types.php:512 -msgid "Parent Language:" +#: includes/extra-strings.php:559 +msgid "Current Show module for Radio Station." msgstr "" -#: includes/post-types.php:513 -msgid "Edit Language" +#: includes/extra-strings.php:560 widgets/class-current-show-widget.php:107 +msgid "No Current Show Text" msgstr "" -#: includes/post-types.php:514 -msgid "Update Language" +#: includes/extra-strings.php:561 +msgid "Show Display Options" msgstr "" -#: includes/post-types.php:515 -msgid "Add New Language" +#: includes/extra-strings.php:562 +msgid "Link to Show Page" msgstr "" -#: includes/post-types.php:516 -msgid "New Language Name" +#: includes/extra-strings.php:563 +msgid "Title Position" msgstr "" -#: includes/post-types.php:517 -msgid "Language" +#: includes/extra-strings.php:564 +msgid "Above Image" msgstr "" -#: includes/shortcodes.php:75 includes/shortcodes.php:81 -#: includes/shortcodes.php:94 includes/shortcodes.php:96 -#: templates/single-show-content.php:553 templates/single-show-content.php:560 -msgid "UTC" +#: includes/extra-strings.php:565 +msgid "Left of Image" msgstr "" -#: includes/shortcodes.php:111 -msgid "Radio Timezone" +#: includes/extra-strings.php:566 +msgid "Right of Image" msgstr "" -#: includes/shortcodes.php:118 -msgid "Your Timezone" +#: includes/extra-strings.php:567 +msgid "Below Image" msgstr "" -#: includes/shortcodes.php:216 -msgid "Radio Time" +#: includes/extra-strings.php:568 +msgid "Display Show Image" msgstr "" -#: includes/shortcodes.php:226 -msgid "Your Time" +#: includes/extra-strings.php:569 +msgid "Image Size" msgstr "" -#: includes/shortcodes.php:579 -msgid "No Shows in the requested Genre and Language were found." +#: includes/extra-strings.php:570 +msgid "Image Width Override" msgstr "" -#: includes/shortcodes.php:581 -msgid "No Shows in the requested Genre were found." +#: includes/extra-strings.php:571 +msgid "Display Show Time" msgstr "" -#: includes/shortcodes.php:583 -msgid "No Shows in the requested Language were found." +#: includes/extra-strings.php:572 +msgid "Display All Show Times" msgstr "" -#: includes/shortcodes.php:585 -msgid "No Shows were found to display." +#: includes/extra-strings.php:573 +msgid "Display Show Hosts" msgstr "" -#: includes/shortcodes.php:588 -msgid "No Playlists were found to display." +#: includes/extra-strings.php:574 +msgid "Show Description Excerpt" msgstr "" -#: includes/shortcodes.php:590 -msgid "No Overrides were found to display." +#: includes/extra-strings.php:575 +msgid "Display Show Playlist" msgstr "" -#: includes/shortcodes.php:918 -msgid "No Genres were found to display." +#: includes/extra-strings.php:576 +msgid "Display if Encore Airing" msgstr "" -#: includes/shortcodes.php:1026 -msgid "No Shows in this Genre." +#: includes/extra-strings.php:577 +msgid "Show Title Color" msgstr "" -#: includes/shortcodes.php:1182 -msgid "No Languages were found to display." +#: includes/extra-strings.php:578 +msgid "Show Title Typography" msgstr "" -#: includes/shortcodes.php:1278 -msgid "No Shows in this Language." +#: includes/extra-strings.php:579 +msgid "Show Description Text Color" msgstr "" -#: includes/shortcodes.php:1594 -msgid "View all posts by %s" +#: includes/extra-strings.php:580 +msgid "Show Description Typography" msgstr "" -#: includes/shortcodes.php:2067 templates/single-show-content.php:502 -msgid "Show Times" +#: includes/extra-strings.php:581 +msgid "Radio Player" msgstr "" -#: includes/shortcodes.php:2317 includes/shortcodes.php:2986 -#: templates/single-show-content.php:654 -msgid "Encore Presentation" +#: includes/extra-strings.php:582 +msgid "Radio Station Audio stream player module." msgstr "" -#: includes/shortcodes.php:2435 -msgid "No Show currently scheduled." +#: includes/extra-strings.php:583 widgets/class-radio-player-widget.php:62 +msgid "Player Content" msgstr "" -#: includes/shortcodes.php:3035 -msgid "No Upcoming Shows Scheduled." +#: includes/extra-strings.php:584 +msgid "Stream URL" msgstr "" -#: includes/shortcodes.php:3433 templates/single-playlist-content.php:48 -msgid "Label" +#: includes/extra-strings.php:585 +msgid "Leave blank to use default stream." msgstr "" -#: includes/shortcodes.php:3474 -msgid "No Current Playlist available." +#: includes/extra-strings.php:586 +msgid "Player Title Text" msgstr "" -#: includes/shortcodes.php:3584 -msgid "This Show has started." +#: includes/extra-strings.php:587 widgets/class-current-playlist-widget.php:116 +#: widgets/class-radio-player-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:113 +msgid "Empty for default, 0 for none." msgstr "" -#: includes/shortcodes.php:3585 -msgid "This Show has ended." +#: includes/extra-strings.php:588 +msgid "Player Station Image" msgstr "" -#: includes/shortcodes.php:3586 -msgid "This Playlist has ended." +#: includes/extra-strings.php:590 +msgid "Do Not Display Station Image" msgstr "" -#: includes/shortcodes.php:3587 -msgid "Commencing in" +#: includes/extra-strings.php:591 widgets/class-radio-player-widget.php:111 +msgid "Player Options" msgstr "" -#: includes/shortcodes.php:3588 -msgid "Remaining Time" +#: includes/extra-strings.php:592 options.php:280 +msgid "Player Script" msgstr "" -#: includes/shortcodes.php:3742 -msgid "More Playlists" +#: includes/extra-strings.php:596 +msgid "Initial Volume" msgstr "" -#: includes/support-functions.php:3928 -msgid "Africa" +#: includes/extra-strings.php:597 options.php:343 +#: widgets/class-radio-player-widget.php:144 +msgid "Volume Controls" msgstr "" -#: includes/support-functions.php:3929 -msgid "America" +#: includes/extra-strings.php:598 options.php:346 player/radio-player.php:376 +msgid "Volume Slider" msgstr "" -#: includes/support-functions.php:3930 -msgid "Asia" +#: includes/extra-strings.php:599 +msgid "Up and Down Buttons" msgstr "" -#: includes/support-functions.php:3931 -msgid "Atlantic" +#: includes/extra-strings.php:600 +msgid "Mute Button" msgstr "" -#: includes/support-functions.php:3932 -msgid "Australia" +#: includes/extra-strings.php:601 +msgid "Maximize Button" msgstr "" -#: includes/support-functions.php:3933 -msgid "Europe" +#: includes/extra-strings.php:602 +msgid "Use as Default Player" msgstr "" -#: includes/support-functions.php:3934 -msgid "Indian" +#: includes/extra-strings.php:603 +msgid "Make this the default player on this page." msgstr "" -#: includes/support-functions.php:3935 -msgid "Pacific" +#: includes/extra-strings.php:604 +msgid "Metadata Source URL" msgstr "" -#: includes/support-functions.php:3936 -msgid "Antarctica" +#: includes/extra-strings.php:605 +msgid "Defaults to Stream URL." msgstr "" -#: includes/support-functions.php:3976 -msgid "WordPress Timezone" +#: includes/extra-strings.php:606 +msgid "Player Design" msgstr "" -#: includes/support-functions.php:4587 -msgid "WordPress Setting" +#: includes/extra-strings.php:607 +msgid "Player Layout" msgstr "" -#: loader.php:1278 -msgid "Plugin Name" +#: includes/extra-strings.php:608 +msgid "Vertical (Stacked" msgstr "" -#: loader.php:1280 -msgid "Requires at least" +#: includes/extra-strings.php:609 +msgid "Horizontal (Inline" msgstr "" -#: loader.php:1280 loader.php:1281 -msgid "WordPress" +#: includes/extra-strings.php:610 +msgid "Player Theme" msgstr "" -#: loader.php:1281 -msgid "Tested up to" +#: includes/extra-strings.php:613 +msgid "Player Buttons" msgstr "" -#: loader.php:1283 -msgid "Stable Tag" +#: includes/extra-strings.php:617 options.php:1353 +msgid "Player Colors" msgstr "" -#: loader.php:1285 -msgid "Contributors" +#: includes/extra-strings.php:618 +msgid "Playing Highlight Color" msgstr "" -#: loader.php:1311 -msgid "Extra Notes" +#: includes/extra-strings.php:619 +msgid "Buttons Highlight Color" msgstr "" -#: loader.php:1516 -msgid "If you want to more easily access support and feedback for this plugins features and functionality, %s can connect your user, %s at %s, to %s" +#: includes/extra-strings.php:620 +msgid "Slider Track Color" msgstr "" -#: loader.php:1616 -msgid "Upgrade" +#: includes/extra-strings.php:621 +msgid "Slider Thumb Color" msgstr "" -#: loader.php:1623 -msgid "Pro Details" +#: includes/extra-strings.php:622 +msgid "Player Typography" msgstr "" -#: loader.php:1636 -msgid "Add Ons" +#: includes/extra-strings.php:623 +msgid "Station Text Color" msgstr "" -#: loader.php:1690 -msgid "Notices" +#: includes/extra-strings.php:624 +msgid "Station Typography" +msgstr "" + +#: includes/extra-strings.php:625 +msgid "Show Text Color" +msgstr "" + +#: includes/extra-strings.php:626 +msgid "Show Typography" +msgstr "" + +#: includes/extra-strings.php:627 +msgid "Song Track Text Color" +msgstr "" + +#: includes/extra-strings.php:628 +msgid "Song Track Typography" +msgstr "" + +#: includes/extra-strings.php:629 +msgid "Master Schedule" +msgstr "" + +#: includes/extra-strings.php:630 +msgid "Master Schedule module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:631 +msgid "Schedule Display Options" +msgstr "" + +#: includes/extra-strings.php:632 +msgid "Schedule View" +msgstr "" + +#: includes/extra-strings.php:633 +msgid "Ctrl-click to select multiple views." +msgstr "" + +#: includes/extra-strings.php:634 options.php:673 options.php:720 +msgid "Table View" +msgstr "" + +#: includes/extra-strings.php:635 options.php:676 options.php:721 +msgid "Tabbed View" +msgstr "" + +#: includes/extra-strings.php:636 +msgid "Default View" +msgstr "" + +#: includes/extra-strings.php:637 +msgid "Image Position" +msgstr "" + +#: includes/extra-strings.php:638 +msgid "[Tabbed View] Show avatar relative to Show text." +msgstr "" + +#: includes/extra-strings.php:639 +msgid "left" +msgstr "" + +#: includes/extra-strings.php:641 +msgid "Hide Past Shows" +msgstr "" + +#: includes/extra-strings.php:642 +msgid "[Tabbed View] Display only current and upcoming shows." +msgstr "" + +#: includes/extra-strings.php:643 +msgid "Grid Width" +msgstr "" + +#: includes/extra-strings.php:644 +msgid "[Grid View] Column width in pixels." +msgstr "" + +#: includes/extra-strings.php:645 +msgid "[Grid View] Line up Shows by times." +msgstr "" + +#: includes/extra-strings.php:646 +msgid "Calendar Weeks" +msgstr "" + +#: includes/extra-strings.php:647 +msgid "[Calendar View] Number of future weeks." +msgstr "" + +#: includes/extra-strings.php:648 +msgid "Previous Weeks" +msgstr "" + +#: includes/extra-strings.php:649 +msgid "[Calendar View] Number of past weeks." +msgstr "" + +#: includes/extra-strings.php:650 +msgid "Header Display Options" +msgstr "" + +#: includes/extra-strings.php:651 +msgid "Radio Time Header" +msgstr "" + +#: includes/extra-strings.php:652 +msgid "Display Radio Clock" +msgstr "" + +#: includes/extra-strings.php:653 +msgid "Display Radio Timezone" +msgstr "" + +#: includes/extra-strings.php:654 +msgid "No Time Header" +msgstr "" + +#: includes/extra-strings.php:655 +msgid "Display Genre Highlighter" +msgstr "" + +#: includes/extra-strings.php:656 +msgid "Time Display Options" +msgstr "" + +#: includes/extra-strings.php:657 +msgid "Schedule Start Day" +msgstr "" + +#: includes/extra-strings.php:658 +msgid "WP Start of Week" +msgstr "" + +#: includes/extra-strings.php:659 +msgid "Today" +msgstr "" + +#: includes/extra-strings.php:660 +msgid "Monday" +msgstr "" + +#: includes/extra-strings.php:661 +msgid "Tuesday" +msgstr "" + +#: includes/extra-strings.php:662 +msgid "Wednesday" +msgstr "" + +#: includes/extra-strings.php:663 +msgid "Thursday" +msgstr "" + +#: includes/extra-strings.php:664 +msgid "Friday" +msgstr "" + +#: includes/extra-strings.php:665 +msgid "Saturday" +msgstr "" + +#: includes/extra-strings.php:666 +msgid "Sunday" +msgstr "" + +#: includes/extra-strings.php:667 +msgid "Link to Shows" +msgstr "" + +#: includes/extra-strings.php:668 +msgid "Display Show Description" +msgstr "" + +#: includes/extra-strings.php:669 +msgid "Link to Hosts" +msgstr "" + +#: includes/extra-strings.php:670 +msgid "Display Show Genres" +msgstr "" + +#: includes/extra-strings.php:671 +msgid "Display Encore" +msgstr "" + +#: includes/extra-strings.php:672 +msgid "Headers Text Color" +msgstr "" + +#: includes/extra-strings.php:673 +msgid "Headers Typography" +msgstr "" + +#: includes/extra-strings.php:674 +msgid "Subheaders Text Color" +msgstr "" + +#: includes/extra-strings.php:675 +msgid "Subheaders Typography" +msgstr "" + +#: includes/extra-strings.php:676 +msgid "Show Title Text Color" +msgstr "" + +#: includes/extra-strings.php:677 +msgid "Show Times Text Color" +msgstr "" + +#: includes/extra-strings.php:678 +msgid "Show Times Typography" +msgstr "" + +#: includes/extra-strings.php:679 +msgid "Schedule Colors" +msgstr "" + +#: includes/extra-strings.php:680 +msgid "Odd Headers Background Color" +msgstr "" + +#: includes/extra-strings.php:681 +msgid "Even Headers Background Color" +msgstr "" + +#: includes/extra-strings.php:682 +msgid "Active Header Background Color" +msgstr "" + +#: includes/extra-strings.php:683 +msgid "Current Show Border Color" +msgstr "" + +#: includes/extra-strings.php:684 +msgid "Upcoming Shows" +msgstr "" + +#: includes/extra-strings.php:685 +msgid "Upcoming Shows module for Radio Station." +msgstr "" + +#: includes/extra-strings.php:686 +msgid "Upcoming Shows to Display" +msgstr "" + +#: includes/extra-strings.php:687 widgets/class-upcoming-shows-widget.php:110 +msgid "No Upcoming Shows Text" +msgstr "" + +#: includes/extra-strings.php:688 +msgid "Radio Archive" +msgstr "" + +#: includes/extra-strings.php:689 +msgid "Archive Record Query" +msgstr "" + +#: includes/extra-strings.php:690 +msgid "Publish Date" +msgstr "" + +#: includes/extra-strings.php:691 +msgid "Clock Styling" +msgstr "" + +#: includes/extra-strings.php:692 +msgid "Archive Alignment" +msgstr "" + +#: includes/extra-strings.php:694 +msgid "Center" +msgstr "" + +#: includes/extra-strings.php:695 +msgid "Record Title Typography" +msgstr "" + +#: includes/extra-strings.php:696 +msgid "Record Description Typography" +msgstr "" + +#: includes/extra-strings.php:697 +msgid "Record Meta Typography" +msgstr "" + +#: includes/extra-strings.php:698 +msgid "Clock Alignment" +msgstr "" + +#: includes/extra-strings.php:699 +msgid "Radio Current Playlist" +msgstr "" + +#: includes/extra-strings.php:700 +msgid "Track Display Options" +msgstr "" + +#: includes/extra-strings.php:701 +msgid "Playlist Styling" +msgstr "" + +#: includes/extra-strings.php:702 +msgid "Text Alignment" +msgstr "" + +#: includes/extra-strings.php:703 +msgid "Playlist Label Typography" +msgstr "" + +#: includes/extra-strings.php:704 +msgid "Playlist Info Typography" +msgstr "" + +#: includes/extra-strings.php:705 +msgid "Radio Current Show" +msgstr "" + +#: includes/extra-strings.php:706 +msgid "Current Show Styling" +msgstr "" + +#: includes/extra-strings.php:707 +msgid "Player Alignment" +msgstr "" + +#: includes/extra-strings.php:708 +msgid "Station Text Typography" +msgstr "" + +#: includes/extra-strings.php:709 +msgid "Show Text Typography" +msgstr "" + +#: includes/extra-strings.php:710 +msgid "Radio Schedule" +msgstr "" + +#: includes/extra-strings.php:711 +msgid "Display Show Images" +msgstr "" + +#: includes/extra-strings.php:712 +msgid "Schedule Typography" +msgstr "" + +#: includes/extra-strings.php:713 +msgid "Radio Upcoming Shows" +msgstr "" + +#: includes/extra-strings.php:714 +msgid "Upcoming Shows Styling" +msgstr "" + +#: includes/legacy.php:671 +msgid "More Show Blog Posts" +msgstr "" + +#. translators: 1: PHP function name, 2: Version number, 3: Alternative +#. function name. + +#: includes/legacy.php:708 +msgid "%1$s is deprecated since Radio Station version %2$s! Use %3$s or check documentation for updated functionality." +msgstr "" + +#: includes/master-schedule.php:825 +msgid "Click to toggle Highlight of Shows with this Genre." +msgstr "" + +#: includes/post-types-admin.php:172 +msgid "Show Language" +msgstr "" + +#: includes/post-types-admin.php:209 +msgid "Main Radio Language" +msgstr "" + +#: includes/post-types-admin.php:213 +msgid "Select below if Show language(s) differ." +msgstr "" + +#: includes/post-types-admin.php:239 +msgid "Remove Language" +msgstr "" + +#: includes/post-types-admin.php:248 +msgid "Select Language" +msgstr "" + +#: includes/post-types-admin.php:264 +msgid "Click on a Language to Add it." +msgstr "" + +#: includes/post-types-admin.php:415 +msgid "Show Information" +msgstr "" + +#: includes/post-types-admin.php:461 +msgid "Active" +msgstr "" + +#: includes/post-types-admin.php:467 +msgid "Check this box if show is currently active (Show will not appear on schedule if unchecked.)" +msgstr "" + +#: includes/post-types-admin.php:485 includes/post-types-admin.php:3230 +msgid "Show Email" +msgstr "" + +#: includes/post-types-admin.php:496 includes/post-types-admin.php:3252 +msgid "Show Phone" +msgstr "" + +#: includes/post-types-admin.php:506 +msgid "Latest Audio File" +msgstr "" + +#: includes/post-types-admin.php:517 includes/post-types-admin.php:3296 +msgid "Disable Download" +msgstr "" + +#: includes/post-types-admin.php:546 +msgid "Show DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:550 includes/post-types-admin.php:710 +msgid "Show Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:554 +msgid "Show Language(s)" +msgstr "" + +#: includes/post-types-admin.php:696 includes/post-types-admin.php:775 +msgid "Ctrl-Click selects multiple." +msgstr "" + +#: includes/post-types-admin.php:792 +msgid "Show Schedule" +msgstr "" + +#: includes/post-types-admin.php:838 +msgid "This Show is inactive!" +msgstr "" + +#: includes/post-types-admin.php:839 +msgid "All Shifts are inactive also until Show is activated." +msgstr "" + +#: includes/post-types-admin.php:886 +msgid "Saving Show Shifts..." +msgstr "" + +#: includes/post-types-admin.php:887 +msgid "Show Shifts Saved." +msgstr "" + +#: includes/post-types-admin.php:980 +msgid "Are you sure you want to remove this shift?" +msgstr "" + +#: includes/post-types-admin.php:981 +msgid "Are you sure you want to clear the shift list?" +msgstr "" + +#: includes/post-types-admin.php:1149 includes/post-types-admin.php:1487 +#: radio-station.php:1013 +msgid "Day" +msgstr "" + +#: includes/post-types-admin.php:1243 includes/post-types-admin.php:1609 +msgid "Duplicate Shift" +msgstr "" + +#: includes/post-types-admin.php:1248 includes/post-types-admin.php:1616 +msgid "Remove Shift" +msgstr "" + +#: includes/post-types-admin.php:1268 +msgid "Warning! Show Shift Conflicts were detected!" +msgstr "" + +#: includes/post-types-admin.php:1269 +msgid "Please note that Shifts with conflicts are automatically disabled upon saving." +msgstr "" + +#: includes/post-types-admin.php:1270 +msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." +msgstr "" + +#: includes/post-types-admin.php:1271 +msgid "Then you can uncheck the shift Disable box and Save again to re-enable the Shift." +msgstr "" + +#: includes/post-types-admin.php:1626 +msgid "Shift Conflicts" +msgstr "" + +#: includes/post-types-admin.php:1632 +msgid "This Show" +msgstr "" + +#: includes/post-types-admin.php:1680 +msgid "Show Description" +msgstr "" + +#: includes/post-types-admin.php:1696 +msgid "The text field below is for your Show Description. It will display in the About section of your Show page." +msgstr "" + +#: includes/post-types-admin.php:1697 +msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." +msgstr "" + +#: includes/post-types-admin.php:1698 +msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." +msgstr "" + +#: includes/post-types-admin.php:1701 +msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." +msgstr "" + +#: includes/post-types-admin.php:1702 +msgid "This way you can then assign them to a relevant Post Category for display on your site also." +msgstr "" + +#: includes/post-types-admin.php:1708 +msgid "Radio Station Pro includes an Episodes post type for adding past episodes." +msgstr "" + +#: includes/post-types-admin.php:1709 +msgid "These are then automatically listed on your Show page in an Episodes tab." +msgstr "" + +#: includes/post-types-admin.php:1710 +msgid "Go Pro!" +msgstr "" + +#: includes/post-types-admin.php:1711 +msgid "Find out more..." +msgstr "" + +#: includes/post-types-admin.php:1730 +msgid "Show Logo" +msgstr "" + +#: includes/post-types-admin.php:1746 +msgid "Show Images" +msgstr "" + +#: includes/post-types-admin.php:1794 +msgid "Set Show Avatar Image" +msgstr "" + +#: includes/post-types-admin.php:1801 +msgid "Remove Show Avatar Image" +msgstr "" + +#: includes/post-types-admin.php:1837 +msgid "Set Show Header Image" +msgstr "" + +#: includes/post-types-admin.php:1844 +msgid "Remove Show Header Image" +msgstr "" + +#: includes/post-types-admin.php:2040 +msgid "Error! No Show ID provided." +msgstr "" + +#: includes/post-types-admin.php:2045 +msgid "Failed. Invalid Show." +msgstr "" + +#: includes/post-types-admin.php:2480 +msgid "Warning! Shift conflicts detected." +msgstr "" + +#: includes/post-types-admin.php:2625 +msgid "Active?" +msgstr "" + +#: includes/post-types-admin.php:2627 +msgid "About?" +msgstr "" + +#: includes/post-types-admin.php:2628 +msgid "Shifts" +msgstr "" + +#: includes/post-types-admin.php:2635 includes/post-types-admin.php:2823 +#: includes/post-types-admin.php:3120 +msgid "Show Avatar" +msgstr "" + +#: includes/post-types-admin.php:2711 +msgid "This Shift is Disabled." +msgstr "" + +#: includes/post-types-admin.php:2720 +msgid "This Shift has Schedule Conflicts and is Disabled." +msgstr "" + +#: includes/post-types-admin.php:2722 +msgid "This Shift has Schedule Conflicts." +msgstr "" + +#: includes/post-types-admin.php:2731 +msgid "This Show is not currently active." +msgstr "" + +#: includes/post-types-admin.php:2773 +msgid "This shift is disabled as no day is set." +msgstr "" + +#: includes/post-types-admin.php:2875 +msgid "Filter by show day" +msgstr "" + +#: includes/post-types-admin.php:2877 +msgid "All show days" +msgstr "" + +#: includes/post-types-admin.php:2900 +msgid "Override Show Data" +msgstr "" + +#: includes/post-types-admin.php:2941 +msgid "Link to Show" +msgstr "" + +#: includes/post-types-admin.php:2949 +msgid "No Show Link" +msgstr "" + +#: includes/post-types-admin.php:2986 +msgid "If selected, Override data will be used from the Linked Show." +msgstr "" + +#: includes/post-types-admin.php:2995 +msgid "Sync Genres?" +msgstr "" + +#: includes/post-types-admin.php:3005 +msgid "If checked, assigned Genre terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:3015 +msgid "Sync Languages?" +msgstr "" + +#: includes/post-types-admin.php:3025 +msgid "If checked, assigned Language terms are synced from the Linked Show when Updating." +msgstr "" + +#: includes/post-types-admin.php:3045 +msgid "Usage Note" +msgstr "" + +#: includes/post-types-admin.php:3046 +msgid " Unchecked boxes use Show data, checked boxes use Override data." +msgstr "" + +#: includes/post-types-admin.php:3053 +msgid "Override?" +msgstr "" + +#: includes/post-types-admin.php:3055 +msgid "Override Data" +msgstr "" + +#: includes/post-types-admin.php:3076 +msgid "Use Title Editor metabox above." +msgstr "" + +#: includes/post-types-admin.php:3086 +msgid "Description" +msgstr "" + +#: includes/post-types-admin.php:3093 +msgid "Use Content Editor metabox below." +msgstr "" + +#: includes/post-types-admin.php:3110 +msgid "Use Excerpt Editor metabox below." +msgstr "" + +#: includes/post-types-admin.php:3127 +msgid "Use Show Images metabox." +msgstr "" + +#: includes/post-types-admin.php:3142 +msgid "Featured Image" +msgstr "" + +#: includes/post-types-admin.php:3149 +msgid "Use Feature Image metabox." +msgstr "" + +#: includes/post-types-admin.php:3171 +msgid "Use Host assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3193 +msgid "Use Producer assignment box below." +msgstr "" + +#: includes/post-types-admin.php:3274 +msgid "Latest Audio" +msgstr "" + +#: includes/post-types-admin.php:3304 +msgid "Check to Disable" +msgstr "" + +#: includes/post-types-admin.php:3346 +msgid "Override DJ(s) / Host(s)" +msgstr "" + +#: includes/post-types-admin.php:3350 +msgid "Override Producer(s)" +msgstr "" + +#: includes/post-types-admin.php:3494 +msgid "Override Schedule" +msgstr "" + +#: includes/post-types-admin.php:3548 +msgid "Saving Overrides..." +msgstr "" + +#: includes/post-types-admin.php:3549 +msgid "Overrides Saved." +msgstr "" + +#: includes/post-types-admin.php:3899 includes/post-types-admin.php:4213 +msgid "Duplicate Override" +msgstr "" + +#: includes/post-types-admin.php:3905 includes/post-types-admin.php:4218 +msgid "Remove Override" +msgstr "" + +#: includes/post-types-admin.php:3954 +msgid "Are you sure you want to remove this Override?" +msgstr "" + +#: includes/post-types-admin.php:3955 +msgid "Are you sure you want to clear all overrides?" +msgstr "" + +#: includes/post-types-admin.php:4735 +msgid "Override Time(s)" +msgstr "" + +#: includes/post-types-admin.php:4736 +msgid "Affected Show(s)" +msgstr "" + +#: includes/post-types-admin.php:4741 includes/post-types-admin.php:5567 +msgid "Linked Show" +msgstr "" + +#: includes/post-types-admin.php:4896 +msgid "Inactive" +msgstr "" + +#: includes/post-types-admin.php:4981 +msgid "Override Logo" +msgstr "" + +#: includes/post-types-admin.php:5062 +msgid "Filter by override date" +msgstr "" + +#: includes/post-types-admin.php:5064 +msgid "All Override Months" +msgstr "" + +#: includes/post-types-admin.php:5092 +msgid "Past and Future" +msgstr "" + +#: includes/post-types-admin.php:5094 includes/post-types.php:152 +msgid "All Overrides" +msgstr "" + +#: includes/post-types-admin.php:5095 +msgid "Past Overrides" +msgstr "" + +#: includes/post-types-admin.php:5096 +msgid "Overrides Today" +msgstr "" + +#: includes/post-types-admin.php:5097 +msgid "Future Overrides" +msgstr "" + +#: includes/post-types-admin.php:5120 +msgid "Playlist Entries" +msgstr "" + +#: includes/post-types-admin.php:5140 includes/post-types-admin.php:5440 +msgid "Move Track Up" +msgstr "" + +#: includes/post-types-admin.php:5141 includes/post-types-admin.php:5441 +msgid "Move Track Down" +msgstr "" + +#: includes/post-types-admin.php:5142 includes/post-types-admin.php:5442 +msgid "Duplicate Track" +msgstr "" + +#: includes/post-types-admin.php:5143 includes/post-types-admin.php:5443 +msgid "Remove Track" +msgstr "" + +#: includes/post-types-admin.php:5158 +msgid "Clear Tracks" +msgstr "" + +#: includes/post-types-admin.php:5161 +msgid "Save Tracks" +msgstr "" + +#: includes/post-types-admin.php:5164 +msgid "Add Track" +msgstr "" + +#: includes/post-types-admin.php:5169 +msgid "Saving Playlist Tracks..." +msgstr "" + +#: includes/post-types-admin.php:5170 +msgid "Playlist Tracks Saved." +msgstr "" + +#: includes/post-types-admin.php:5179 +msgid "Tracks marked New are moved to the end of Playlist on update." +msgstr "" + +#: includes/post-types-admin.php:5182 +msgid "Are you sure you want to clear the track list?" +msgstr "" + +#: includes/post-types-admin.php:5287 includes/post-types-admin.php:5501 +msgid "Length" +msgstr "" + +#: includes/post-types-admin.php:5295 includes/post-types-admin.php:5518 +#: includes/shortcodes.php:4078 templates/single-playlist-content.php:52 +msgid "Comments" +msgstr "" + +#: includes/post-types-admin.php:5296 includes/post-types-admin.php:5525 +msgid "New" +msgstr "" + +#: includes/post-types-admin.php:5298 includes/post-types-admin.php:5530 +#: includes/post-types-admin.php:6085 +msgid "Status" +msgstr "" + +#: includes/post-types-admin.php:5300 includes/post-types-admin.php:5533 +msgid "Queued" +msgstr "" + +#: includes/post-types-admin.php:5301 includes/post-types-admin.php:5534 +msgid "Played" +msgstr "" + +#: includes/post-types-admin.php:5304 includes/post-types-admin.php:5540 +msgid "Move" +msgstr "" + +#: includes/post-types-admin.php:5432 includes/post-types-admin.php:6084 +#: includes/shortcodes.php:4057 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:48 +msgid "Artist" +msgstr "" + +#: includes/post-types-admin.php:5433 includes/post-types-admin.php:6083 +#: includes/shortcodes.php:4049 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:49 +msgid "Song" +msgstr "" + +#: includes/post-types-admin.php:5434 includes/shortcodes.php:4064 +#: templates/legacy/single-playlist.php:47 +#: templates/single-playlist-content.php:50 +msgid "Album" +msgstr "" + +#: includes/post-types-admin.php:5435 templates/legacy/single-playlist.php:48 +msgid "Record Label" +msgstr "" + +#: includes/post-types-admin.php:5503 +msgctxt "minutes unit" +msgid "m" +msgstr "" + +#: includes/post-types-admin.php:5515 +msgctxt "seconds unit" +msgid "s" +msgstr "" + +#: includes/post-types-admin.php:5649 +msgid "No Shows were found." +msgstr "" + +#: includes/post-types-admin.php:5655 +msgid "You are not assigned to any Shows." +msgstr "" + +#: includes/post-types-admin.php:5664 includes/post-types-admin.php:6184 +msgid "Assign to Show" +msgstr "" + +#: includes/post-types-admin.php:5666 includes/post-types-admin.php:5789 +#: includes/post-types-admin.php:6187 +msgid "Unassigned" +msgstr "" + +#: includes/post-types-admin.php:5706 +msgid "You can assign Playlists to Show Episodes in Radio Station Pro." +msgstr "" + +#: includes/post-types-admin.php:5707 radio-station-admin.php:1397 +msgid "Go PRO" +msgstr "" + +#: includes/post-types-admin.php:5708 +msgid "Find Out More..." +msgstr "" + +#: includes/post-types-admin.php:5786 +msgid "Assign to Show Shift" +msgstr "" + +#: includes/post-types-admin.php:5788 +msgid "Latest Show" +msgstr "" + +#: includes/post-types-admin.php:5831 +msgid "Failed. Use manual Publish or Update instead." +msgstr "" + +#: includes/post-types-admin.php:5835 +msgid "Failed. No Playlist ID provided." +msgstr "" + +#: includes/post-types-admin.php:5840 +msgid "Failed. Invalid Playlist ID." +msgstr "" + +#: includes/post-types-admin.php:6004 +msgid "Tracks" +msgstr "" + +#: includes/post-types-admin.php:6005 +msgid "Track List" +msgstr "" + +#: includes/post-types-admin.php:6079 +msgid "Show/Hide Tracklist" +msgstr "" + +#: includes/post-types-admin.php:6298 +msgid "Related to Show" +msgstr "" + +#: includes/post-types-admin.php:6361 +msgid "Select Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6534 +msgid "Related Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6574 +msgid "Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6707 +msgid "Set Related Show(s)" +msgstr "" + +#: includes/post-types-admin.php:6842 +msgid "Updated Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:6848 +msgid "Failed to Update Related Shows for %d Posts." +msgstr "" + +#: includes/post-types-admin.php:7098 +msgid "Failed. Please relogin and try again." +msgstr "" + +#: includes/post-types.php:45 includes/post-types.php:46 +msgid "Add Show" +msgstr "" + +#: includes/post-types.php:49 +msgid "View Show" +msgstr "" + +#: includes/post-types.php:53 +msgid "Search Shows" +msgstr "" + +#: includes/post-types.php:54 +msgid "No Shows found" +msgstr "" + +#: includes/post-types.php:55 +msgid "No Shows found in Trash" +msgstr "" + +#: includes/post-types.php:63 +msgid "Shows Archive" +msgstr "" + +#: includes/post-types.php:94 +msgid "Playlist" +msgstr "" + +#: includes/post-types.php:95 includes/post-types.php:96 +msgid "Add Playlist" +msgstr "" + +#: includes/post-types.php:97 +msgid "Edit Playlist" +msgstr "" + +#: includes/post-types.php:98 +msgid "New Playlist" +msgstr "" + +#: includes/post-types.php:99 includes/shortcodes.php:2848 +#: includes/shortcodes.php:4099 +msgid "View Playlist" +msgstr "" + +#: includes/post-types.php:103 +msgid "Search Playlists" +msgstr "" + +#: includes/post-types.php:104 +msgid "No Playlists found" +msgstr "" + +#: includes/post-types.php:105 +msgid "No Playlists found in Trash" +msgstr "" + +#: includes/post-types.php:106 +msgid "All Playlists" +msgstr "" + +#: includes/post-types.php:113 +msgid "Playlists Archive" +msgstr "" + +#: includes/post-types.php:141 radio-station-admin.php:220 +msgid "Schedule Overrides" +msgstr "" + +#: includes/post-types.php:143 includes/post-types.php:144 +msgid "Add Schedule Override" +msgstr "" + +#: includes/post-types.php:145 +msgid "Edit Schedule Override" +msgstr "" + +#: includes/post-types.php:146 +msgid "New Schedule Override" +msgstr "" + +#: includes/post-types.php:147 +msgid "View Schedule Override" +msgstr "" + +#: includes/post-types.php:149 +msgid "Search Overrides" +msgstr "" + +#: includes/post-types.php:150 +msgid "No Overrides found" +msgstr "" + +#: includes/post-types.php:151 +msgid "No Overrides found in Trash" +msgstr "" + +#: includes/post-types.php:160 +msgid "Schedule Overrides Archive" +msgstr "" + +#: includes/post-types.php:194 +msgid "Host" +msgstr "" + +#: includes/post-types.php:195 +msgid "Add New Host Profile" +msgstr "" + +#: includes/post-types.php:196 +msgid "Add Host Profile" +msgstr "" + +#: includes/post-types.php:197 +msgid "Edit Host Profile" +msgstr "" + +#: includes/post-types.php:198 +msgid "New Host Profile" +msgstr "" + +#: includes/post-types.php:199 +msgid "View Host Profile" +msgstr "" + +#: includes/post-types.php:200 +msgid "Show Hosts" +msgstr "" + +#: includes/post-types.php:202 +msgid "Search Host Profiles" +msgstr "" + +#: includes/post-types.php:203 +msgid "No Host Profiles found" +msgstr "" + +#: includes/post-types.php:204 +msgid "No Host Profiles found in Trash" +msgstr "" + +#: includes/post-types.php:205 +msgid "All Host Profiles" +msgstr "" + +#: includes/post-types.php:213 +msgid "Host Profiles Archive" msgstr "" -#: loader.php:1813 -msgid "by" +#: includes/post-types.php:246 +msgid "Producer" msgstr "" -#: loader.php:1823 -msgid "Plugin Homepage" +#: includes/post-types.php:247 +msgid "Add New Producer Profile" msgstr "" -#: loader.php:1823 -msgid "Home" +#: includes/post-types.php:248 +msgid "Add Producer Profile" msgstr "" -#: loader.php:1827 -msgid "View Plugin" +#: includes/post-types.php:249 +msgid "Edit Producer Profile" msgstr "" -#: loader.php:1827 -msgid "Readme" +#: includes/post-types.php:250 +msgid "New Producer Profile" msgstr "" -#: loader.php:1830 -msgid "Plugin Documentation" +#: includes/post-types.php:251 +msgid "View Producer Profile" msgstr "" -#: loader.php:1830 -msgid "Docs" +#: includes/post-types.php:252 +msgid "Show Producers Profile" msgstr "" -#: loader.php:1833 -msgid "Plugin Support" +#: includes/post-types.php:254 +msgid "Search Producer Profiles" msgstr "" -#: loader.php:1833 -msgid "Support" +#: includes/post-types.php:255 +msgid "No Producer Profiles found" msgstr "" -#: loader.php:1836 -msgid "Plugin Development" +#: includes/post-types.php:256 +msgid "No Producer Profiles found in Trash" msgstr "" -#: loader.php:1836 -msgid "Dev" +#: includes/post-types.php:257 +msgid "All Producer Profiles" msgstr "" -#: loader.php:1884 radio-station-admin.php:1470 -msgid "Rate on WordPress.Org" +#: includes/post-types.php:265 +msgid "Producer Profiles Archive" msgstr "" -#: loader.php:1901 radio-station.php:1589 -msgid "Share the Plugin Love" +#: includes/post-types.php:407 +msgid "Edit" msgstr "" -#: loader.php:1918 radio-station.php:1591 -msgid "Support this Plugin" +#: includes/post-types.php:464 +msgctxt "taxonomy general name" +msgid "Genres" msgstr "" -#: loader.php:1935 -msgid "Settings Updated." +#: includes/post-types.php:465 +msgctxt "taxonomy singular name" +msgid "Genre" msgstr "" -#: loader.php:1937 -msgid "Error! Settings NOT Updated." +#: includes/post-types.php:466 +msgid "Search Genres" msgstr "" -#: loader.php:1939 -msgid "Settings Reset!" +#: includes/post-types.php:467 +msgid "All Genres" msgstr "" -#: loader.php:2112 radio-station.php:1490 -msgid "General" +#: includes/post-types.php:468 +msgid "Parent Genre" msgstr "" -#: loader.php:2117 -msgid "Are you sure you want to reset to default settings?" +#: includes/post-types.php:469 +msgid "Parent Genre:" msgstr "" -#: loader.php:2218 -msgid "Reset Settings" +#: includes/post-types.php:470 +msgid "Edit Genre" msgstr "" -#: loader.php:2220 -msgid "Save Settings" +#: includes/post-types.php:471 +msgid "Update Genre" msgstr "" -#: loader.php:2391 -msgid "Use Ctrl and Click to Select" +#: includes/post-types.php:472 +msgid "Add New Genre" msgstr "" -#: loader.php:2430 -msgid "Premium Feature." +#: includes/post-types.php:473 +msgid "New Genre Name" msgstr "" -#: loader.php:2432 -msgid "Upgrade Now" +#: includes/post-types.php:474 +msgid "Genre" msgstr "" -#: loader.php:2439 -msgid "Details" +#: includes/post-types.php:510 +msgctxt "taxonomy general name" +msgid "Languages" msgstr "" -#: loader.php:2442 -msgid "Coming soon in Pro version!" +#: includes/post-types.php:511 +msgctxt "taxonomy singular name" +msgid "Language" msgstr "" -#: loader.php:2767 -msgid "Add Image" +#: includes/post-types.php:512 +msgid "Search Languages" msgstr "" -#: loader.php:2775 -msgid "Remove Image" +#: includes/post-types.php:513 +msgid "All Languages" msgstr "" -#: player/radio-player.php:282 radio-station.php:266 -msgid "Station Logo Image" +#: includes/post-types.php:514 +msgid "Parent Language" msgstr "" -#: player/radio-player.php:302 -msgid "Station Name" +#: includes/post-types.php:515 +msgid "Parent Language:" msgstr "" -#: player/radio-player.php:323 -msgid "Play Radio Stream" +#: includes/post-types.php:516 +msgid "Edit Language" msgstr "" -#: player/radio-player.php:334 -msgid "Mute" +#: includes/post-types.php:517 +msgid "Update Language" msgstr "" -#: player/radio-player.php:337 -msgid "Volume Down" +#: includes/post-types.php:518 +msgid "Add New Language" msgstr "" -#: player/radio-player.php:342 radio-station.php:521 -msgid "Volume Slider" +#: includes/post-types.php:519 +msgid "New Language Name" msgstr "" -#: player/radio-player.php:353 -msgid "Volume Up" +#: includes/post-types.php:520 +msgid "Language" msgstr "" -#: player/radio-player.php:356 -msgid "Max" +#: includes/shortcodes.php:80 includes/shortcodes.php:84 +#: includes/shortcodes.php:98 includes/shortcodes.php:111 +#: includes/shortcodes.php:113 templates/single-show-content.php:550 +#: templates/single-show-content.php:557 +msgid "UTC" msgstr "" -#: player/radio-player.php:380 -msgid "Show Title" +#: includes/shortcodes.php:129 +msgid "Radio Timezone" msgstr "" -#: player/radio-player.php:386 -msgid "Show Logo Image" +#: includes/shortcodes.php:138 +msgid "Your Timezone" msgstr "" -#: radio-station-admin.php:159 -msgid "You do not have permissions to access that page." +#: includes/shortcodes.php:241 +msgid "Radio Time" msgstr "" -#: radio-station-admin.php:209 -msgid "Import/Export Shows" +#: includes/shortcodes.php:251 +msgid "Your Time" msgstr "" -#: radio-station-admin.php:214 -msgid "Documentation" +#: includes/shortcodes.php:678 +msgid "No Shows in the requested Genre and Language were found." msgstr "" -#: radio-station-admin.php:214 -msgid "Help" +#: includes/shortcodes.php:680 +msgid "No Shows in the requested Genre were found." msgstr "" -#: radio-station-admin.php:334 -msgid "Role Assignments" +#: includes/shortcodes.php:682 +msgid "No Shows in the requested Language were found." msgstr "" -#: radio-station-admin.php:335 -msgid "You can assign a Radio Station role to users through the WordPress User editor." +#: includes/shortcodes.php:684 +msgid "No Shows were found to display." msgstr "" -#: radio-station-admin.php:339 -msgid "Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user." +#: includes/shortcodes.php:687 +msgid "No Playlists were found to display." msgstr "" -#: radio-station-admin.php:346 -msgid "Find out more about Radio Station Pro" +#: includes/shortcodes.php:689 +msgid "No Overrides were found to display." msgstr "" -#: radio-station-admin.php:453 -msgid "Back to Documentation Index" +#: includes/shortcodes.php:1062 +msgid "No Genres were found to display." msgstr "" -#: radio-station-admin.php:612 -msgid "Right-click and download this file to save your export" +#: includes/shortcodes.php:1161 +msgid "No Shows were found with Genres assigned." msgstr "" -#: radio-station-admin.php:752 radio-station-admin.php:826 -msgid "Take a moment to Update for a better experience. In this update" +#: includes/shortcodes.php:1213 +msgid "No Shows in this Genre." msgstr "" -#: radio-station-admin.php:820 -msgid "A new version of" +#: includes/shortcodes.php:1475 +msgid "No Languages were found to display." msgstr "" -#: radio-station-admin.php:822 -msgid "is available." +#: includes/shortcodes.php:1629 +msgid "No Shows were found with Languages assigned." msgstr "" -#: radio-station-admin.php:835 -msgid "Update Now" +#: includes/shortcodes.php:1665 +msgid "No Shows in this Language." msgstr "" -#: radio-station-admin.php:838 radio-station-admin.php:921 -msgid "Full Update Details" +#: includes/shortcodes.php:2043 +msgid "View all posts by %s" msgstr "" -#: radio-station-admin.php:849 radio-station-admin.php:940 -#: radio-station-admin.php:1243 radio-station-admin.php:1326 -#: radio-station-admin.php:1481 -msgid "Dismiss this Notice" +#: includes/shortcodes.php:2570 templates/single-show-content.php:499 +msgid "Show Times" msgstr "" -#: radio-station-admin.php:902 -msgid "Update Notice" +#: includes/shortcodes.php:2825 includes/shortcodes.php:3569 +#: templates/single-show-content.php:648 +msgid "Encore Presentation" msgstr "" -#: radio-station-admin.php:908 -msgid "Thanks for Updating! You can enjoy these improvements now" +#: includes/shortcodes.php:2957 +msgid "No Show currently scheduled." msgstr "" -#: radio-station-admin.php:928 -msgid "Plugin Settings" +#: includes/shortcodes.php:3629 +msgid "No Upcoming Shows Scheduled." msgstr "" -#: radio-station-admin.php:1200 -msgid "Time Sensitive Free Offer" +#: includes/shortcodes.php:4071 templates/single-playlist-content.php:51 +msgid "Label" msgstr "" -#: radio-station-admin.php:1203 -msgid "We are excited to announce the opening of the new" +#: includes/shortcodes.php:4132 +msgid "No Current Playlist available." msgstr "" -#: radio-station-admin.php:1205 -msgid "Allowing listeners to newly discover Stations and Shows - which can include yours..." +#: includes/shortcodes.php:4445 +msgid "More Playlists" msgstr "" -#: radio-station-admin.php:1207 -msgid "Because while launching," +#: includes/support-functions.php:1743 +msgid "WordPress Setting" msgstr "" -#: radio-station-admin.php:1207 -msgid "we are offering 30 days free listing to all Radio Station users!" +#: includes/templates.php:863 +msgid "%s for Shows" msgstr "" -#: radio-station-admin.php:1208 -msgid "Interested in more exposure and listeners for your Radio Station, for free?" +#: includes/templates.php:865 +msgid "%s for Show" msgstr "" -#: radio-station-admin.php:1219 -msgid "Yes please!" +#: includes/templates.php:874 +msgid "More %s for Shows" msgstr "" -#: radio-station-admin.php:1222 -msgid "More Details" +#: includes/templates.php:876 +msgid "More %s for Show" msgstr "" -#: radio-station-admin.php:1226 -msgid "Great! I'm listed, dismiss this notice." +#: includes/templates.php:1161 +msgid "Previous Related Show" msgstr "" -#: radio-station-admin.php:1231 -msgid "Offer valid until end of July 2020." +#: includes/templates.php:1166 +msgid "Next Related Show" msgstr "" -#: radio-station-admin.php:1232 -msgid "Activate your 30 days before it ends!" +#: includes/times.php:116 scheduler/schedule-engine.php:2733 +msgid "WordPress Timezone" msgstr "" -#: radio-station-admin.php:1278 -msgid "Radio Station Pro Launch Discount!" +#: includes/user-roles.php:58 includes/user-roles.php:77 +#: includes/user-roles.php:78 includes/user-roles.php:290 +msgid "DJ / Host" msgstr "" -#: radio-station-admin.php:1280 -msgid "We are thrilled to announce the upcoming launch of Radio Station PRO" +#: includes/user-roles.php:82 +msgid "Show Producer" msgstr "" -#: radio-station-admin.php:1281 radio-station-admin.php:1289 -msgid "Jam-packed with new features to \"level up\" your Station's online presence." +#: includes/user-roles.php:162 +msgid "Show Editor" msgstr "" -#: radio-station-admin.php:1282 -msgid "During the launch," +#: loader.php:1355 +msgid "Plugin Name" msgstr "" -#: radio-station-admin.php:1282 radio-station-admin.php:1290 -msgid "we are offering 30% discount to existing Radio Station users!" +#: loader.php:1357 +msgid "Requires at least" msgstr "" -#: radio-station-admin.php:1283 -msgid "Sign up to the exclusive launch list to receive your discount code when we go LIVE." +#: loader.php:1357 loader.php:1358 +msgid "WordPress" msgstr "" -#: radio-station-admin.php:1286 -msgid "Radio Station PRO Launch is LIVE!" +#: loader.php:1358 +msgid "Tested up to" msgstr "" -#: radio-station-admin.php:1288 -msgid "The long anticipated moment has arrived. The doors are open to get PRO" +#: loader.php:1360 +msgid "Stable Tag" msgstr "" -#: radio-station-admin.php:1290 -msgid "Remember," +#: loader.php:1362 +msgid "Contributors" msgstr "" -#: radio-station-admin.php:1292 -msgid "Sign up here to receive your exclusive launch discount code." +#: loader.php:1388 +msgid "Extra Notes" msgstr "" -#: radio-station-admin.php:1307 -msgid "Yes, I'm in!" +#. Translators: plugin title, user name, site link, freemius link + +#: loader.php:1599 +msgid "If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s" msgstr "" -#: radio-station-admin.php:1309 -msgid "Go PRO" +#: loader.php:1700 +msgid "Upgrade" msgstr "" -#: radio-station-admin.php:1314 -msgid "Thanks, already done." +#: loader.php:1708 +msgid "Pro Details" msgstr "" -#: radio-station-admin.php:1450 -msgid "Help support us to make improvements, modifications and introduce new features!" +#: loader.php:1722 +msgid "Add Ons" msgstr "" -#: radio-station-admin.php:1451 -msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" +#: loader.php:1790 +msgid "Notices" msgstr "" -#: radio-station-admin.php:1452 -msgid "since June 2019" +#: loader.php:1918 +msgid "by" msgstr "" -#: radio-station-admin.php:1454 -msgid " plugin development has been actively taken over by" +#: loader.php:1928 +msgid "Plugin Homepage" msgstr "" -#: radio-station-admin.php:1457 -msgid "And due to our continued efforts we now have a community of over two thousand active stations!" +#: loader.php:1928 +msgid "Home" msgstr "" -#: radio-station-admin.php:1458 -msgid "We invite you to" +#: loader.php:1932 +msgid "View Plugin" msgstr "" -#: radio-station-admin.php:1460 -msgid "Become a Radio Station Patreon Supporter" +#: loader.php:1932 +msgid "Readme" msgstr "" -#: radio-station-admin.php:1461 -msgid "to make it better for everyone" +#: loader.php:1935 +msgid "Plugin Documentation" msgstr "" -#: radio-station-admin.php:1529 -msgid "has detected" +#: loader.php:1935 +msgid "Docs" msgstr "" -#: radio-station-admin.php:1530 -msgid "Schedule conflicts!" +#: loader.php:1938 +msgid "Plugin Support" msgstr "" -#: radio-station-admin.php:1534 -msgid "The following Shows have conflicting Shift times" +#: loader.php:1938 +msgid "Support" msgstr "" -#: radio-station-admin.php:1573 -msgid "Go to Show List" +#: loader.php:1941 +msgid "Plugin Development" msgstr "" -#: radio-station-admin.php:1574 -msgid "Conflicts are highlighted" +#: loader.php:1941 +msgid "Dev" msgstr "" -#: radio-station-admin.php:1575 -msgid "in Show Shift column." +#: loader.php:1989 radio-station-admin.php:1565 +msgid "Rate on WordPress.Org" +msgstr "" + +#: loader.php:2006 radio-station.php:237 +msgid "Share the Plugin Love" +msgstr "" + +#: loader.php:2023 radio-station.php:239 +msgid "Support this Plugin" +msgstr "" + +#: loader.php:2043 +msgid "Settings Updated." +msgstr "" + +#: loader.php:2045 +msgid "Error! Settings NOT Updated." +msgstr "" + +#: loader.php:2047 +msgid "Settings Reset!" +msgstr "" + +#: loader.php:2316 +msgid "Reset Settings" +msgstr "" + +#: loader.php:2318 +msgid "Save Settings" +msgstr "" + +#: loader.php:2505 +msgid "Use Ctrl and Click to Select" +msgstr "" + +#: loader.php:2544 +msgid "Premium Feature." +msgstr "" + +#: loader.php:2546 radio-station-admin.php:397 +msgid "Upgrade Now" +msgstr "" + +#: loader.php:2553 +msgid "Details" msgstr "" -#: radio-station-admin.php:1580 -msgid "This notice will persist" +#: loader.php:2556 +msgid "Coming soon in Pro version!" msgstr "" -#: radio-station-admin.php:1581 -msgid "until conflicts are resolved." +#: loader.php:2881 +msgid "Add Image" msgstr "" -#: radio-station-admin.php:1630 -msgid "Stay tuned! Subscribe to Radio Station's" +#: loader.php:2889 +msgid "Remove Image" msgstr "" -#: radio-station-admin.php:1632 -msgid "Plugin Updates and Announcements List" +#: loader.php:2969 +msgid "Are you sure you want to reset to default settings?" msgstr "" -#: radio-station.php:198 +#: options.php:19 msgid "Streaming URL" msgstr "" -#: radio-station.php:200 -msgid "Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player." +#: options.php:21 +msgid "Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds." msgstr "" -#: radio-station.php:209 +#: options.php:30 msgid "Streaming Format" msgstr "" -#: radio-station.php:211 +#: options.php:32 msgid "Select streaming format for streaming URL." msgstr "" -#: radio-station.php:220 +#: options.php:41 msgid "Fallback Stream URL" msgstr "" -#: radio-station.php:222 +#: options.php:43 msgid "Enter an alternative Streaming URL for Player fallback." msgstr "" -#: radio-station.php:231 +#: options.php:52 msgid "Fallback Format" msgstr "" -#: radio-station.php:233 +#: options.php:54 msgid "Select streaming fallback for fallback URL." msgstr "" -#: radio-station.php:242 +#: options.php:63 msgid "Main Broadcast Language" msgstr "" -#: radio-station.php:244 +#: options.php:65 msgid "Select the main language used on your Radio Station." msgstr "" -#: radio-station.php:255 +#: options.php:75 +msgid "Ping Netmix Directory" +msgstr "" + +#: options.php:78 +msgid "If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule." +msgstr "" + +#: options.php:89 msgid "Station Title" msgstr "" -#: radio-station.php:257 +#: options.php:91 msgid "Name of your Radio Station. For use in Stream Player and Data Feeds." msgstr "" -#: radio-station.php:268 +#: options.php:100 player/radio-player.php:304 +msgid "Station Logo Image" +msgstr "" + +#: options.php:102 msgid "Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256" msgstr "" -#: radio-station.php:277 +#: options.php:111 msgid "Location Timezone" msgstr "" -#: radio-station.php:279 +#: options.php:113 msgid "Select your Broadcast Location for Radio Timezone display." msgstr "" -#: radio-station.php:288 +#: options.php:122 msgid "12 Hour Format" msgstr "" -#: radio-station.php:289 +#: options.php:123 msgid "24 Hour Format" msgstr "" -#: radio-station.php:291 +#: options.php:125 msgid "Clock Time Format" msgstr "" -#: radio-station.php:293 +#: options.php:127 msgid "Default Time Format for display output. Can be overridden in each shortcode or widget." msgstr "" -#: radio-station.php:303 +#: options.php:137 msgid "Station Phone" msgstr "" -#: radio-station.php:305 +#: options.php:139 msgid "Main call in phone number for the Station (for requests etc.)" msgstr "" -#: radio-station.php:316 +#: options.php:150 msgid "Show Phone Display" msgstr "" -#: radio-station.php:317 +#: options.php:151 msgid "Display Station phone number on Shows where a Show phone number is not set." msgstr "" -#: radio-station.php:327 +#: options.php:161 msgid "Station Email" msgstr "" -#: radio-station.php:328 +#: options.php:162 msgid "Main email address for the Station (for requests etc.)" msgstr "" -#: radio-station.php:339 +#: options.php:173 msgid "Show Email Display" msgstr "" -#: radio-station.php:340 +#: options.php:174 msgid "Display Station email address on Shows where a Show email address is not set." msgstr "" -#: radio-station.php:350 +#: options.php:184 msgid "Enable Data Routes" msgstr "" -#: radio-station.php:353 +#: options.php:187 msgid "Enables Station Data Routes via WordPress REST API." msgstr "" -#: radio-station.php:361 +#: options.php:195 msgid "Enable Data Feeds" msgstr "" -#: radio-station.php:364 +#: options.php:198 msgid "Enable Station Data Feeds via WordPress Feed links." msgstr "" -#: radio-station.php:373 -msgid "Ping Netmix Directory" -msgstr "" - -#: radio-station.php:376 -msgid "If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule." -msgstr "" - -#: radio-station.php:388 +#: options.php:210 msgid "Disable Transients" msgstr "" -#: radio-station.php:391 +#: options.php:213 msgid "Clear Schedule transients with every pageload. Less efficient but more reliable." msgstr "" -#: radio-station.php:399 +#: options.php:221 msgid "Show Transients" msgstr "" -#: radio-station.php:402 +#: options.php:224 msgid "Use Show Transient Data to improve Schedule calculation performance." msgstr "" -#: radio-station.php:427 +#: options.php:248 +msgid "Player Defaults Note" +msgstr "" + +#: options.php:249 +msgid "Note that you can override these defaults in specific Player Widgets." +msgstr "" + +#: options.php:257 msgid "Display Station Title" msgstr "" -#: radio-station.php:430 +#: options.php:260 msgid "Display your Radio Station Title in Player by default." msgstr "" -#: radio-station.php:442 +#: options.php:271 msgid "Display your Radio Station Image in Player by default." msgstr "" -#: radio-station.php:452 -msgid "Player Script" -msgstr "" - -#: radio-station.php:459 +#: options.php:287 msgid "Default audio script to use for playback in the Player." msgstr "" -#: radio-station.php:470 +#: options.php:297 msgid "Fallback Scripts" msgstr "" -#: radio-station.php:477 +#: options.php:304 msgid "Enabled fallback audio scripts to try when the default Player script fails." msgstr "" -#: radio-station.php:486 +#: options.php:312 msgid "Default Player Theme" msgstr "" -#: radio-station.php:492 +#: options.php:318 msgid "Default Player Controls theme style." msgstr "" -#: radio-station.php:501 +#: options.php:326 msgid "Default Player Buttons" msgstr "" -#: radio-station.php:504 +#: options.php:329 msgid "Circular Buttons" msgstr "" -#: radio-station.php:505 +#: options.php:330 msgid "Rounded Buttons" msgstr "" -#: radio-station.php:506 +#: options.php:331 msgid "Square Buttons" msgstr "" -#: radio-station.php:508 +#: options.php:333 msgid "Default Player Buttons shape style." msgstr "" -#: radio-station.php:518 -msgid "Volume Controls" -msgstr "" - -#: radio-station.php:522 +#: options.php:347 msgid "Volume Plus / Minus" msgstr "" -#: radio-station.php:523 +#: options.php:348 msgid "Mute Volume Toggle" msgstr "" -#: radio-station.php:524 +#: options.php:349 msgid "Maximize Volume" msgstr "" -#: radio-station.php:526 +#: options.php:351 msgid "Which volume controls to display in the Player by default." msgstr "" -#: radio-station.php:534 +#: options.php:359 msgid "Player Debug Mode" msgstr "" -#: radio-station.php:537 +#: options.php:362 msgid "Output player debug information in browser javascript console." msgstr "" -#: radio-station.php:548 +#: options.php:372 msgid "Playing Icon Highlight Color" msgstr "" -#: radio-station.php:550 +#: options.php:374 msgid "Default highlight color to use for Play button icon when playing." msgstr "" -#: radio-station.php:559 +#: options.php:383 msgid "Control Icons Highlight Color" msgstr "" -#: radio-station.php:561 +#: options.php:385 msgid "Default highlight color to use for Control button icons when active." msgstr "" -#: radio-station.php:570 +#: options.php:394 msgid "Volume Knob Color" msgstr "" -#: radio-station.php:572 +#: options.php:396 msgid "Default Knob Color for Player Volume Slider." msgstr "" -#: radio-station.php:581 +#: options.php:405 msgid "Volume Track Color" msgstr "" -#: radio-station.php:583 +#: options.php:407 msgid "Default Track Color for Player Volume Slider." msgstr "" -#: radio-station.php:599 +#: options.php:423 msgid "Initial volume for when the Player starts playback." msgstr "" -#: radio-station.php:608 +#: options.php:432 msgid "Single Player at Once" msgstr "" -#: radio-station.php:611 -msgid "Stop any existing Players on the page or in other windows or tabs when a Player is started." +#: options.php:435 +msgid "Stop any existing Player instances on the page or in other windows or tabs when a Player is started." msgstr "" -#: radio-station.php:620 +#: options.php:444 msgid "Autoresume Playback" msgstr "" -#: radio-station.php:623 +#: options.php:447 msgid "Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page." msgstr "" -#: radio-station.php:646 +#: options.php:471 msgid "Bar Defaults Note" msgstr "" -#: radio-station.php:647 +#: options.php:472 msgid "The Bar Player uses the default configurations set above." msgstr "" -#: radio-station.php:648 +#: options.php:473 msgid "You can override these in specific Player Widgets." msgstr "" -#: radio-station.php:656 +#: options.php:481 msgid "Sitewide Player Bar" msgstr "" -#: radio-station.php:659 +#: options.php:484 msgid "No Player Bar" msgstr "" -#: radio-station.php:660 +#: options.php:485 msgid "Top Player Bar" msgstr "" -#: radio-station.php:661 +#: options.php:486 msgid "Bottom Player Bar" msgstr "" -#: radio-station.php:665 +#: options.php:490 msgid "Add a fixed position Player Bar which displays Sitewide." msgstr "" -#: radio-station.php:686 +#: options.php:511 msgid "Fade In Player Bar" msgstr "" -#: radio-station.php:691 +#: options.php:516 msgid "Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display." msgstr "" -#: radio-station.php:701 +#: options.php:526 msgid "Continuous Playback" msgstr "" -#: radio-station.php:704 +#: options.php:529 msgid "Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists." msgstr "" -#: radio-station.php:713 +#: options.php:538 msgid "Page Fade Time" msgstr "" -#: radio-station.php:718 +#: options.php:543 msgid "Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display." msgstr "" -#: radio-station.php:733 +#: options.php:558 msgid "Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)" msgstr "" -#: radio-station.php:742 +#: options.php:567 msgid "Bar Player Text Color" msgstr "" -#: radio-station.php:744 +#: options.php:569 msgid "Text color for the fixed position Sitewide Bar Player." msgstr "" -#: radio-station.php:753 +#: options.php:578 msgid "Bar Player Background Color" msgstr "" -#: radio-station.php:755 +#: options.php:580 msgid "Background color for the fixed position Sitewide Bar Player." msgstr "" -#: radio-station.php:783 +#: options.php:608 msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)" msgstr "" -#: radio-station.php:808 +#: options.php:649 msgid "Master Schedule Page" msgstr "" -#: radio-station.php:810 +#: options.php:651 msgid "Select the Page you are displaying the Master Schedule on." msgstr "" -#: radio-station.php:821 +#: options.php:662 msgid "Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: " msgstr "" -#: radio-station.php:829 +#: options.php:670 msgid "Schedule View Default" msgstr "" -#: radio-station.php:832 radio-station.php:879 -msgid "Table View" -msgstr "" - -#: radio-station.php:833 radio-station.php:881 -msgid "List View" -msgstr "" - -#: radio-station.php:834 +#: options.php:675 msgid "Divs View" msgstr "" -#: radio-station.php:835 radio-station.php:880 -msgid "Tabbed View" -msgstr "" - -#: radio-station.php:836 +#: options.php:677 msgid "Legacy Table" msgstr "" -#: radio-station.php:838 +#: options.php:679 msgid "View type to use for automatic display on Master Schedule Page." msgstr "" -#: radio-station.php:846 +#: options.php:687 msgid "Schedule Clock?" msgstr "" -#: radio-station.php:850 +#: options.php:691 msgid "Clock" msgstr "" -#: radio-station.php:851 templates/single-show-content.php:548 +#: options.php:692 templates/single-show-content.php:545 msgid "Timezone" msgstr "" -#: radio-station.php:853 +#: options.php:694 msgid "Radio Time section display above program Schedule." msgstr "" -#: radio-station.php:861 +#: options.php:702 msgid "View Switching" msgstr "" -#: radio-station.php:864 +#: options.php:705 msgid "Enable View Switching on the automatic Master Schedule page." msgstr "" -#: radio-station.php:874 +#: options.php:715 msgid "Available Views" msgstr "" -#: radio-station.php:882 -msgid "Grid View" -msgstr "" - -#: radio-station.php:883 -msgid "Calendar View" -msgstr "" - -#: radio-station.php:885 +#: options.php:726 msgid "Switcher Views available on automatic Master Schedule page." msgstr "" -#: radio-station.php:909 radio-station.php:1022 radio-station.php:1056 +#: options.php:750 options.php:864 options.php:898 msgid "Info Blocks Position" msgstr "" -#: radio-station.php:911 radio-station.php:1024 radio-station.php:1058 +#: options.php:752 options.php:866 options.php:900 msgid "Float Left" msgstr "" -#: radio-station.php:912 radio-station.php:1025 radio-station.php:1059 +#: options.php:753 options.php:867 options.php:901 msgid "Float Right" msgstr "" -#: radio-station.php:913 radio-station.php:1026 radio-station.php:1060 +#: options.php:754 options.php:868 options.php:902 msgid "Float Top" msgstr "" -#: radio-station.php:916 +#: options.php:757 msgid "Where to position Show info blocks relative to Show Page content." msgstr "" -#: radio-station.php:924 +#: options.php:765 msgid "Show Content Layout" msgstr "" -#: radio-station.php:926 radio-station.php:1040 radio-station.php:1074 +#: options.php:767 options.php:882 options.php:916 msgid "Tabbed" msgstr "" -#: radio-station.php:927 radio-station.php:1041 radio-station.php:1075 +#: options.php:768 options.php:883 options.php:917 msgid "Standard" msgstr "" -#: radio-station.php:930 +#: options.php:771 msgid "How to display extra sections below Show description. In content tabs or standard layout down the page." msgstr "" -#: radio-station.php:939 +#: options.php:780 msgid "Content Header Images" msgstr "" -#: radio-station.php:942 +#: options.php:783 msgid "If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." msgstr "" -#: radio-station.php:963 +#: options.php:804 msgid "Posts per Page" msgstr "" -#: radio-station.php:968 +#: options.php:809 msgid "Linked Show Posts per page on the Show Page tab/display." msgstr "" -#: radio-station.php:979 +#: options.php:820 msgid "Playlists per Page" msgstr "" -#: radio-station.php:981 +#: options.php:822 msgid "Playlists per page on the Show Page tab/display" msgstr "" -#: radio-station.php:989 +#: options.php:830 msgid "Episodes per Page" msgstr "" -#: radio-station.php:994 +#: options.php:835 msgid "Number of Show Episodes per page on the Show page tab/display." msgstr "" -#: radio-station.php:1003 -msgid "Combined Team Tab" +#: options.php:852 +msgid "Combine team members (eg. hosts, producers) into a single display tab." +msgstr "" + +#: options.php:871 +msgid "Where to position Profile info blocks relative to Profile Page content." +msgstr "" + +#: options.php:880 +msgid "Profile Content Layout" +msgstr "" + +#: options.php:886 +msgid "How to display extra sections below Profile description. In content tabs or standard layout down the page." +msgstr "" + +#: options.php:905 +msgid "Where to position Episode info blocks relative to Episode Page content." +msgstr "" + +#: options.php:914 +msgid "Episode Content Layout" +msgstr "" + +#: options.php:920 +msgid "How to display extra sections below Episode description. In content tabs or standard layout down the page." +msgstr "" + +#: options.php:933 +msgid "Shows Archive Page" +msgstr "" + +#: options.php:937 +msgid "Select the Page for displaying the Show archive list." +msgstr "" + +#: options.php:948 +msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:966 +msgid "Overrides Archive Page" +msgstr "" + +#: options.php:970 +msgid "Select the Page for displaying the Override archive list." +msgstr "" + +#: options.php:981 +msgid "Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:999 +msgid "Playlists Archive Page" +msgstr "" + +#: options.php:1003 +msgid "Select the Page for displaying the Playlist archive list." +msgstr "" + +#: options.php:1014 +msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1066 +msgid "Genres Archive Page" +msgstr "" + +#: options.php:1070 +msgid "Select the Page for displaying the Genre archive list." +msgstr "" + +#: options.php:1081 +msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1100 +msgid "Languages Archive Page" +msgstr "" + +#: options.php:1104 +msgid "Select the Page for displaying the Language archive list." +msgstr "" + +#: options.php:1116 +msgid "Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1137 +msgid "Templates Change Note" +msgstr "" + +#: options.php:1138 +msgid "Since 2.3.0, the way that Templates are implemented has changed." +msgstr "" + +#: options.php:1139 +msgid "See the Documentation for more information:" +msgstr "" + +#: options.php:1140 +msgid "Templates Documentation" +msgstr "" + +#: options.php:1147 +msgid "Show Template" +msgstr "" + +#: options.php:1150 options.php:1178 +msgid "Theme Page Template (page.php)" +msgstr "" + +#: options.php:1151 options.php:1179 +msgid "Theme Post Template (single.php)" +msgstr "" + +#: options.php:1152 options.php:1180 +msgid "Theme Singular Template (singular.php)" +msgstr "" + +#: options.php:1153 options.php:1181 +msgid "Legacy Plugin Template" +msgstr "" + +#: options.php:1156 +msgid "Which template to use for displaying Show content." +msgstr "" + +#: options.php:1163 options.php:1191 +msgid "Combined Method" +msgstr "" + +#: options.php:1167 +msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" +msgstr "" + +#: options.php:1175 +msgid "Playlist Template" +msgstr "" + +#: options.php:1184 +msgid "Which template to use for displaying Playlist content." +msgstr "" + +#: options.php:1195 +msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" +msgstr "" + +#: options.php:1206 +msgid "AJAX Load Widgets?" +msgstr "" + +#: options.php:1209 +msgid "Defaults plugin widgets to AJAX loading. Can also be set on individual widgets." +msgstr "" + +#: options.php:1217 +msgid "Dynamic Reloading?" +msgstr "" + +#: options.php:1220 +msgid "Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets." +msgstr "" + +#: options.php:1229 +msgid "Convert Show Times" +msgstr "" + +#: options.php:1232 +msgid "Automatically display Show times converted into the visitor timezone, based on their browser setting." +msgstr "" + +#: options.php:1241 +msgid "User Timezone Switching" +msgstr "" + +#: options.php:1244 +msgid "Allow visitors to select their Timezone manually for Show time conversions." +msgstr "" + +#: options.php:1257 +msgid "Show Editing Permissions" +msgstr "" + +#: options.php:1258 +msgid "By default, only Hosts and Producers that are assigned to a Show can edit that Show." +msgstr "" + +#: options.php:1259 +msgid "This means an Administrator or Show Editor must assign these users to the Show first." +msgstr "" + +#: options.php:1268 +msgid "Playlist Permissions" +msgstr "" + +#: options.php:1269 +msgid "Any user with a Host or Producer role can create Playlists." +msgstr "" + +#: options.php:1277 +msgid "Show Editor Role" +msgstr "" + +#: options.php:1278 +msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." +msgstr "" + +#: options.php:1279 +msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." +msgstr "" + +#: options.php:1280 +msgid "This is so a manager can edit the schedule without requiring full site administration role." +msgstr "" + +#: options.php:1288 +msgid "Add to Author Capabilities" +msgstr "" + +#: options.php:1291 +msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." +msgstr "" + +#: options.php:1299 +msgid "Add to Editor Capabilities" +msgstr "" + +#: options.php:1302 +msgid "Allow users with WordPress Editor role to edit all Radio Station post types." +msgstr "" + +#: options.php:1334 +msgid "Pages" +msgstr "" + +#: options.php:1335 options.php:1361 +msgid "Archives" +msgstr "" + +#: options.php:1337 +msgid "Widgets" +msgstr "" + +#: options.php:1338 +msgid "Roles" +msgstr "" + +#: options.php:1347 +msgid "Broadcast" +msgstr "" + +#: options.php:1348 +msgid "Station" +msgstr "" + +#: options.php:1349 +msgid "Feeds" +msgstr "" + +#: options.php:1350 +msgid "Performance" +msgstr "" + +#: options.php:1351 +msgid "Basic Defaults" +msgstr "" + +#: options.php:1352 +msgid "Advanced Defaults" +msgstr "" + +#: options.php:1354 +msgid "Sitewide Bar Player" +msgstr "" + +#: options.php:1355 +msgid "Schedule Page" msgstr "" -#: radio-station.php:1006 -msgid "Do Not Combine" +#: options.php:1356 +msgid "Single Templates" msgstr "" -#: radio-station.php:1007 -msgid "Combined List" +#: options.php:1358 +msgid "Show Pages" msgstr "" -#: radio-station.php:1010 -msgid "Combine team members (eg. hosts, producers) into a single display tab." +#: options.php:1359 +msgid "Profile Pages" msgstr "" -#: radio-station.php:1029 -msgid "Where to position Profile info blocks relative to Profile Page content." +#: options.php:1360 +msgid "Episode Pages" msgstr "" -#: radio-station.php:1038 -msgid "Profile Content Layout" +#: options.php:1362 +msgid "Post Types" msgstr "" -#: radio-station.php:1044 -msgid "How to display extra sections below Profile description. In content tabs or standard layout down the page." +#: options.php:1363 +msgid "Taxonomies" msgstr "" -#: radio-station.php:1063 -msgid "Where to position Episode info blocks relative to Episode Page content." +#: options.php:1364 +msgid "Widget Loading" msgstr "" -#: radio-station.php:1072 -msgid "Episode Content Layout" +#: options.php:1365 +msgid "Permissions" msgstr "" -#: radio-station.php:1078 -msgid "How to display extra sections below Episode description. In content tabs or standard layout down the page." +#: player/radio-player.php:325 +msgid "Station Name" msgstr "" -#: radio-station.php:1091 -msgid "Shows Archive Page" +#: player/radio-player.php:357 +msgid "Play Radio Stream" msgstr "" -#: radio-station.php:1095 -msgid "Select the Page for displaying the Show archive list." +#: player/radio-player.php:367 +msgid "Mute" msgstr "" -#: radio-station.php:1106 -msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" +#: player/radio-player.php:371 +msgid "Volume Down" msgstr "" -#: radio-station.php:1124 -msgid "Overrides Archive Page" +#: player/radio-player.php:381 +msgid "Volume Up" msgstr "" -#: radio-station.php:1128 -msgid "Select the Page for displaying the Override archive list." +#: player/radio-player.php:384 +msgid "Max" msgstr "" -#: radio-station.php:1139 -msgid "Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:" +#: player/radio-player.php:408 +msgid "Show Title" msgstr "" -#: radio-station.php:1157 -msgid "Playlists Archive Page" +#: player/radio-player.php:414 +msgid "Show Logo Image" msgstr "" -#: radio-station.php:1161 -msgid "Select the Page for displaying the Playlist archive list." +#: radio-station-admin.php:184 +msgid "You do not have permissions to access that page." msgstr "" -#: radio-station.php:1172 -msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" +#: radio-station-admin.php:235 +msgid "Import/Export Shows" msgstr "" -#: radio-station.php:1224 -msgid "Genres Archive Page" +#: radio-station-admin.php:240 +msgid "Documentation" msgstr "" -#: radio-station.php:1228 -msgid "Select the Page for displaying the Genre archive list." +#: radio-station-admin.php:240 +msgid "Help" msgstr "" -#: radio-station.php:1239 -msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" +#: radio-station-admin.php:384 +msgid "Role Assignments" msgstr "" -#: radio-station.php:1258 -msgid "Languages Archive Page" +#: radio-station-admin.php:385 +msgid "You can assign a Radio Station role to users through the WordPress User editor." msgstr "" -#: radio-station.php:1262 -msgid "Select the Page for displaying the Language archive list." +#: radio-station-admin.php:389 +msgid "Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user." msgstr "" -#: radio-station.php:1274 -msgid "Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:" +#: radio-station-admin.php:405 +msgid "Find out more about Radio Station Pro" msgstr "" -#: radio-station.php:1295 -msgid "Templates Change Note" +#: radio-station-admin.php:513 +msgid "Back to Documentation Index" msgstr "" -#: radio-station.php:1296 -msgid "Since 2.3.0, the way that Templates are implemented has changed." +#: radio-station-admin.php:674 +msgid "Right-click and download this file to save your export" msgstr "" -#: radio-station.php:1297 -msgid "See the Documentation for more information:" +#: radio-station-admin.php:816 radio-station-admin.php:892 +msgid "Take a moment to Update for a better experience. In this update" msgstr "" -#: radio-station.php:1298 -msgid "Templates Documentation" +#: radio-station-admin.php:820 radio-station-admin.php:896 +msgid "Read full update details." msgstr "" -#: radio-station.php:1305 -msgid "Show Template" +#: radio-station-admin.php:886 +msgid "A new version of" msgstr "" -#: radio-station.php:1308 radio-station.php:1336 -msgid "Theme Page Template (page.php)" +#: radio-station-admin.php:888 +msgid "is available." msgstr "" -#: radio-station.php:1309 radio-station.php:1337 -msgid "Theme Post Template (single.php)" +#: radio-station-admin.php:905 +msgid "Update Now" msgstr "" -#: radio-station.php:1310 radio-station.php:1338 -msgid "Theme Singular Template (singular.php)" +#: radio-station-admin.php:908 radio-station-admin.php:999 +msgid "Full Update Details" msgstr "" -#: radio-station.php:1311 radio-station.php:1339 -msgid "Legacy Plugin Template" +#: radio-station-admin.php:919 radio-station-admin.php:1018 +#: radio-station-admin.php:1328 radio-station-admin.php:1414 +#: radio-station-admin.php:1576 +msgid "Dismiss this Notice" msgstr "" -#: radio-station.php:1314 -msgid "Which template to use for displaying Show content." +#: radio-station-admin.php:977 +msgid "Update Notice" msgstr "" -#: radio-station.php:1321 radio-station.php:1349 -msgid "Combined Method" +#: radio-station-admin.php:983 +msgid "Thanks for Updating! You can enjoy these improvements now" msgstr "" -#: radio-station.php:1325 -msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" +#: radio-station-admin.php:1006 +msgid "Plugin Settings" msgstr "" -#: radio-station.php:1333 -msgid "Playlist Template" +#: radio-station-admin.php:1284 +msgid "Time Sensitive Free Offer" msgstr "" -#: radio-station.php:1342 -msgid "Which template to use for displaying Playlist content." +#: radio-station-admin.php:1287 +msgid "We are excited to announce the opening of the new" msgstr "" -#: radio-station.php:1353 -msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" +#: radio-station-admin.php:1289 +msgid "Allowing listeners to newly discover Stations and Shows - which can include yours..." msgstr "" -#: radio-station.php:1364 -msgid "AJAX Load Widgets?" +#: radio-station-admin.php:1291 +msgid "Because while launching," msgstr "" -#: radio-station.php:1367 -msgid "Defaults plugin widgets to AJAX loading. Can also be set on individual widgets." +#: radio-station-admin.php:1291 +msgid "we are offering 30 days free listing to all Radio Station users!" msgstr "" -#: radio-station.php:1375 -msgid "Dynamic Reloading?" +#: radio-station-admin.php:1292 +msgid "Interested in more exposure and listeners for your Radio Station, for free?" msgstr "" -#: radio-station.php:1378 -msgid "Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets." +#: radio-station-admin.php:1304 +msgid "Yes please!" msgstr "" -#: radio-station.php:1387 -msgid "Convert Show Times" +#: radio-station-admin.php:1307 widgets/class-current-playlist-widget.php:81 +#: widgets/class-current-show-widget.php:101 +#: widgets/class-radio-player-widget.php:164 +#: widgets/class-radio-player-widget.php:232 +#: widgets/class-radio-player-widget.php:242 +#: widgets/class-upcoming-shows-widget.php:104 +msgid "More Details" msgstr "" -#: radio-station.php:1390 -msgid "Automatically display Show times converted into the visitor timezone, based on their browser setting." +#: radio-station-admin.php:1311 +msgid "Great! I'm listed, dismiss this notice." msgstr "" -#: radio-station.php:1399 -msgid "User Timezone Switching" +#: radio-station-admin.php:1316 +msgid "Offer valid until end of July 2020." msgstr "" -#: radio-station.php:1402 -msgid "Allow visitors to select their Timezone manually for Show time translations." +#: radio-station-admin.php:1317 +msgid "Activate your 30 days before it ends!" msgstr "" -#: radio-station.php:1415 -msgid "Show Editing Permissions" +#: radio-station-admin.php:1362 +msgid "Radio Station Pro Launch Discount!" msgstr "" -#: radio-station.php:1416 -msgid "By default, only Hosts and Producers that are assigned to a Show can edit that Show." +#: radio-station-admin.php:1364 +msgid "We are thrilled to announce the upcoming launch of Radio Station PRO" msgstr "" -#: radio-station.php:1417 -msgid "This means an Administrator or Show Editor must assign these users to the Show first." +#: radio-station-admin.php:1365 radio-station-admin.php:1373 +msgid "Jam-packed with new features to \"level up\" your Station's online presence." msgstr "" -#: radio-station.php:1426 -msgid "Playlist Permissions" +#: radio-station-admin.php:1366 +msgid "During the launch," msgstr "" -#: radio-station.php:1427 -msgid "Any user with a Host or Producer role can create Playlists." +#: radio-station-admin.php:1366 radio-station-admin.php:1374 +msgid "we are offering 30% discount to existing Radio Station users!" msgstr "" -#: radio-station.php:1435 -msgid "Show Editor Role" +#: radio-station-admin.php:1367 +msgid "Sign up to the exclusive launch list to receive your discount code when we go LIVE." msgstr "" -#: radio-station.php:1436 -msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." +#: radio-station-admin.php:1370 +msgid "Radio Station PRO Launch is LIVE!" msgstr "" -#: radio-station.php:1437 -msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." +#: radio-station-admin.php:1372 +msgid "The long anticipated moment has arrived. The doors are open to get PRO" msgstr "" -#: radio-station.php:1438 -msgid "This is so a manager can edit the schedule without requiring full site administration role." +#: radio-station-admin.php:1374 +msgid "Remember," msgstr "" -#: radio-station.php:1446 -msgid "Add to Author Capabilities" +#: radio-station-admin.php:1376 +msgid "Sign up here to receive your exclusive launch discount code." msgstr "" -#: radio-station.php:1449 -msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." +#: radio-station-admin.php:1391 +msgid "Yes, I'm in!" msgstr "" -#: radio-station.php:1457 -msgid "Add to Editor Capabilities" +#: radio-station-admin.php:1402 +msgid "Thanks, already done." msgstr "" -#: radio-station.php:1460 -msgid "Allow users with WordPress Editor role to edit all Radio Station post types." +#: radio-station-admin.php:1540 +msgid "Help support us to make improvements, modifications and introduce new features!" msgstr "" -#: radio-station.php:1492 -msgid "Pages" +#: radio-station-admin.php:1541 +msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" msgstr "" -#: radio-station.php:1493 radio-station.php:1519 -msgid "Archives" +#: radio-station-admin.php:1542 +msgid "since June 2019" msgstr "" -#: radio-station.php:1495 -msgid "Widgets" +#: radio-station-admin.php:1544 +msgid " plugin development has been actively taken over by" msgstr "" -#: radio-station.php:1496 -msgid "Roles" +#: radio-station-admin.php:1547 +msgid "And due to our continued efforts we now have a community of over two thousand active stations!" msgstr "" -#: radio-station.php:1505 -msgid "Broadcast" +#: radio-station-admin.php:1548 +msgid "We invite you to" msgstr "" -#: radio-station.php:1506 -msgid "Station" +#: radio-station-admin.php:1550 +msgid "Become a Radio Station Patreon Supporter" msgstr "" -#: radio-station.php:1507 -msgid "Feeds" +#: radio-station-admin.php:1551 +msgid "to make it better for everyone" msgstr "" -#: radio-station.php:1508 -msgid "Performance" +#: radio-station-admin.php:1623 +msgid "has detected" msgstr "" -#: radio-station.php:1509 -msgid "Basic Defaults" +#: radio-station-admin.php:1624 +msgid "Schedule conflicts!" msgstr "" -#: radio-station.php:1510 -msgid "Advanced Defaults" +#: radio-station-admin.php:1628 +msgid "The following Shows have conflicting Shift times" msgstr "" -#: radio-station.php:1511 -msgid "Player Colors" +#: radio-station-admin.php:1667 +msgid "Go to Show List" msgstr "" -#: radio-station.php:1512 -msgid "Sitewide Bar Player" +#: radio-station-admin.php:1668 +msgid "Conflicts are highlighted" msgstr "" -#: radio-station.php:1513 -msgid "Schedule Page" +#: radio-station-admin.php:1669 +msgid "in Show Shift column." msgstr "" -#: radio-station.php:1514 -msgid "Single Templates" +#: radio-station-admin.php:1674 +msgid "This notice will persist" msgstr "" -#: radio-station.php:1516 -msgid "Show Pages" +#: radio-station-admin.php:1675 +msgid "until conflicts are resolved." msgstr "" -#: radio-station.php:1517 -msgid "Profile Pages" +#: radio-station-admin.php:1724 +msgid "Stay tuned! Subscribe to Radio Station's" msgstr "" -#: radio-station.php:1518 -msgid "Episode Pages" +#: radio-station-admin.php:1726 +msgid "Plugin Updates and Announcements List" msgstr "" -#: radio-station.php:1520 -msgid "Post Types" +#: radio-station.php:235 +msgid "Rate on WordPress.org" msgstr "" -#: radio-station.php:1521 -msgid "Taxonomies" +#: radio-station.php:998 +msgid "This Show has started." msgstr "" -#: radio-station.php:1522 -msgid "Widget Loading" +#: radio-station.php:999 +msgid "This Show has ended." msgstr "" -#: radio-station.php:1523 -msgid "Permissions" +#: radio-station.php:1000 +msgid "This Playlist has ended." msgstr "" -#: radio-station.php:1587 -msgid "Rate on WordPress.org" +#: radio-station.php:1001 +msgid "Commencing in" msgstr "" -#: radio-station.php:2014 -msgid "Second" +#: radio-station.php:1002 +msgid "Remaining Time" msgstr "" -#: radio-station.php:2015 +#: radio-station.php:1008 msgid "Seconds" msgstr "" -#: radio-station.php:2016 +#: radio-station.php:1009 msgid "Minute" msgstr "" -#: radio-station.php:2017 +#: radio-station.php:1010 msgid "Minutes" msgstr "" -#: radio-station.php:2018 +#: radio-station.php:1011 msgid "Hour" msgstr "" -#: radio-station.php:2019 +#: radio-station.php:1012 msgid "Hours" msgstr "" -#: radio-station.php:2021 +#: radio-station.php:1014 msgid "Days" msgstr "" -#: radio-station.php:2928 -msgid "%s for Shows" +#: scheduler/schedule-engine.php:2685 +msgid "Africa" msgstr "" -#: radio-station.php:2930 -msgid "%s for Show" +#: scheduler/schedule-engine.php:2686 +msgid "America" msgstr "" -#: radio-station.php:2939 -msgid "More %s for Shows" +#: scheduler/schedule-engine.php:2687 +msgid "Asia" msgstr "" -#: radio-station.php:2941 -msgid "More %s for Show" +#: scheduler/schedule-engine.php:2688 +msgid "Atlantic" msgstr "" -#: radio-station.php:3206 -msgid "Previous Related Show" +#: scheduler/schedule-engine.php:2689 +msgid "Australia" msgstr "" -#: radio-station.php:3211 -msgid "Next Related Show" +#: scheduler/schedule-engine.php:2690 +msgid "Europe" msgstr "" -#: radio-station.php:3350 radio-station.php:3369 radio-station.php:3370 -#: radio-station.php:3580 -msgid "DJ / Host" +#: scheduler/schedule-engine.php:2691 +msgid "Indian" msgstr "" -#: radio-station.php:3374 -msgid "Show Producer" +#: scheduler/schedule-engine.php:2692 +msgid "Pacific" msgstr "" -#: radio-station.php:3454 -msgid "Show Editor" +#: scheduler/schedule-engine.php:2693 +msgid "Antarctica" msgstr "" #: templates/legacy/archive-playlist.php:19 @@ -4273,65 +5816,213 @@ msgstr "" msgid "No entries for this playlist" msgstr "" -#: templates/single-playlist-content.php:17 +#: templates/master-schedule-tabs.php:650 +msgid "No Shows scheduled for this day." +msgstr "" + +#: templates/playlist-export.php:78 +msgid "End Date" +msgstr "" + +#: templates/single-playlist-content.php:19 msgid "Playlist for Show" msgstr "" -#: templates/single-playlist-content.php:80 +#: templates/single-playlist-content.php:83 msgid "No played tracks found for this Playlist yet." msgstr "" -#: templates/single-playlist-content.php:88 +#: templates/single-playlist-content.php:91 msgid "No entries found for this Playlist" msgstr "" -#: templates/single-playlist-content.php:95 +#: templates/single-playlist-content.php:98 msgid "All Playlists for Show" msgstr "" -#: templates/single-show-content.php:98 +#: templates/single-show-content.php:95 msgid "Visit Show Website" msgstr "" -#: templates/single-show-content.php:113 +#: templates/single-show-content.php:110 msgid "Call in Phone Number" msgstr "" -#: templates/single-show-content.php:128 +#: templates/single-show-content.php:125 msgid "Email Show Host" msgstr "" -#: templates/single-show-content.php:322 +#: templates/single-show-content.php:319 msgid "Show Info" msgstr "" -#: templates/single-show-content.php:456 +#: templates/single-show-content.php:453 msgid "Call in" msgstr "" -#: templates/single-show-content.php:509 +#: templates/single-show-content.php:506 msgid "Not Currently Scheduled." msgstr "" -#: templates/single-show-content.php:714 +#: templates/single-show-content.php:709 msgid "Scheduled Dates" msgstr "" -#: templates/single-show-content.php:724 +#: templates/single-show-content.php:719 msgid "Go to Full Station Schedule Page" msgstr "" -#: templates/single-show-content.php:727 +#: templates/single-show-content.php:722 msgid "Full Station Schedule" msgstr "" -#: templates/single-show-content.php:774 +#: templates/single-show-content.php:769 msgid "About the %s" msgstr "" -#: templates/single-show-content.php:915 +#: templates/single-show-content.php:910 msgid "Latest Show Posts" msgstr "" + +#: widgets/class-current-playlist-widget.php:79 +#: widgets/class-current-show-widget.php:99 +#: widgets/class-upcoming-shows-widget.php:102 +msgid "Show changeover reloading available in Pro." +msgstr "" + +#: widgets/class-current-playlist-widget.php:80 +#: widgets/class-current-show-widget.php:100 +#: widgets/class-radio-player-widget.php:163 +#: widgets/class-radio-player-widget.php:231 +#: widgets/class-radio-player-widget.php:241 +#: widgets/class-upcoming-shows-widget.php:103 +msgid "Upgrade to Pro" +msgstr "" + +#: widgets/class-current-playlist-widget.php:98 +msgid "Display Playlist Title?" +msgstr "" + +#: widgets/class-current-playlist-widget.php:113 +msgid "No Playlist Text" +msgstr "" + +#: widgets/class-current-show-widget.php:110 +msgid "Empty for default. 0 for none." +msgstr "" + +#: widgets/class-current-show-widget.php:127 +#: widgets/class-upcoming-shows-widget.php:130 +msgid "Link Show title to Show page." +msgstr "" + +#: widgets/class-current-show-widget.php:161 +#: widgets/class-upcoming-shows-widget.php:164 +msgid "Avatar Image Size" +msgstr "" + +#: widgets/class-current-show-widget.php:174 +#: widgets/class-upcoming-shows-widget.php:177 +msgid "Show Avatar Width override. 0 or empty for none." +msgstr "" + +#: widgets/class-current-show-widget.php:183 +msgid "Display Show Shift Info" +msgstr "" + +#: widgets/class-current-show-widget.php:191 +msgid "Display All Show Shifts (if more than current.)" +msgstr "" + +#: widgets/class-current-show-widget.php:205 +#: widgets/class-radio-clock-widget.php:85 +#: widgets/class-upcoming-shows-widget.php:200 +msgid "Time Format Display" +msgstr "" + +#: widgets/class-current-show-widget.php:220 +msgid "Display Show Host names." +msgstr "" + +#: widgets/class-current-show-widget.php:228 +msgid "Link Host names to author pages." +msgstr "" + +#: widgets/class-current-show-widget.php:236 +msgid "Display Show description excerpt." +msgstr "" + +#: widgets/class-current-show-widget.php:244 +msgid "Display link to Show playlist." +msgstr "" + +#: widgets/class-radio-clock-widget.php:60 +msgid "Include Date Display?" +msgstr "" + +#: widgets/class-radio-clock-widget.php:79 +msgid "Include seconds display?" +msgstr "" + +#: widgets/class-radio-clock-widget.php:98 +msgid "Include timezone display?" +msgstr "" + +#: widgets/class-radio-player-widget.php:78 +msgid "Leave blank to use default stream URL." +msgstr "" + +#: widgets/class-radio-player-widget.php:116 +msgid "Default Player Script" +msgstr "" + +#: widgets/class-radio-player-widget.php:156 +msgid "Use as the default Player instance." +msgstr "" + +#: widgets/class-radio-player-widget.php:162 +msgid "Popup Player button available in Pro." +msgstr "" + +#: widgets/class-radio-player-widget.php:168 +msgid "Player Styles" +msgstr "" + +#: widgets/class-radio-player-widget.php:173 +msgid "Player Widget Layout" +msgstr "" + +#: widgets/class-radio-player-widget.php:209 +msgid "Buttons Style" +msgstr "" + +#: widgets/class-radio-player-widget.php:228 +msgid "[Pro] Color Options" +msgstr "" + +#: widgets/class-radio-player-widget.php:230 +msgid "Color options available in Pro." +msgstr "" + +#: widgets/class-radio-player-widget.php:237 +msgid "[Pro] Advanced Options" +msgstr "" + +#: widgets/class-radio-player-widget.php:239 +msgid "Advanced options available in Pro." +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:186 +msgid "Display shift info for this show" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:215 +msgid "Display Show host names." +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:223 +msgid "Link host names to author pages" +msgstr "" #. Plugin Name of the plugin/theme msgid "Radio Station" msgstr "" From fee9877ffd3a628c04b387082522e34e495316b0 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 2 Aug 2023 15:06:53 +1000 Subject: [PATCH 309/377] shortcode and query updates --- css/rs-shortcodes.css | 4 ++-- includes/shortcodes.php | 19 +++++++++++++++++-- includes/support-functions.php | 17 +++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index bdeac68..d92f752 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -147,8 +147,8 @@ /* Show Subarchive Shortcodes */ /* -------------------------- */ .show-post, .show-playlist, .show-host, .show-producer, .show-episode { - padding-top: 5px; - padding-bottom: 5px; + padding-top: 10px; + padding-bottom: 10px; } .show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail, diff --git a/includes/shortcodes.php b/includes/shortcodes.php index e0de86d..53e7e5a 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1925,7 +1925,12 @@ function radio_station_show_list_shortcode( $type, $atts ) { 'content' => 'excerpt', 'thumbnails' => 1, 'pagination' => 1, + // 2.5.6: add ordering defaults + 'orderby' => 'date', + 'order' => 'DESC', ); + // 2.5.6: add filter for default attributes + $defaults = apply_filters( 'radio_station_show_list_defaults_atts', $defaults, $type ); $atts = shortcode_atts( $defaults, $atts, 'show-' . $type . '-list' ); // --- maybe get stored post data --- @@ -1965,11 +1970,13 @@ function radio_station_show_list_shortcode( $type, $atts ) { $args['limit'] = $atts['limit']; } if ( 'post' == $type ) { - // TODO: add atts attribute filtering + // 2.5.5: added filter for show posts $posts = radio_station_get_show_posts( $show_id, $args ); + $posts = apply_filters( 'radio_station_get_show_posts', $posts, $show_id, $args, $atts ); } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { - // TODO: add atts attribute filtering + // 2.5.5: added filter for show playlists $posts = radio_station_get_show_playlists( $show_id, $args ); + $posts = apply_filters( 'radio_station_get_show_playlists', $posts, $show_id, $args, $atts ); $type = 'playlist'; } elseif ( RADIO_STATION_HOST_SLUG == $type ) { // 2.5.0: added shortcode atts as fourth argument @@ -2111,6 +2118,14 @@ function radio_station_show_list_shortcode( $type, $atts ) { $list .= '' . "\n"; $list .= '' . "\n"; + // --- post meta --- + // 2.5.6: add filtered post meta + $meta = apply_filters( 'radio_station_show_' . $type . '_shortcode_meta', '', $post, $type, $atts ); + if ( '' != $meta ) { + $allowed = radio_station_allowed_html( 'content', 'meta' ); + $list .= wp_kses( $meta, $allowed ) . "\n"; + } + // --- post excerpt --- $post_content = $post['post_content']; $post_id = $post['ID']; diff --git a/includes/support-functions.php b/includes/support-functions.php index 199a926..09b966d 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -399,7 +399,20 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att // --- get posts from post IDs --- $post_id_list = implode( ',', $post_ids ); $query = "SELECT " . $columns . " FROM " . $wpdb->prefix . "posts"; - $query .= " WHERE ID IN(" . $post_id_list . ") AND post_status = 'publish' ORDER BY post_date DESC"; + $query .= " WHERE ID IN(" . $post_id_list . ") AND post_status = 'publish'"; + // 2.5.6: allow for alternative ordering attributes + if ( !isset( $atts['orderby'] ) || ( 'date' == $atts['orderby'] ) ) { + $query .= "ORDER BY post_date"; + } elseif ( 'title' == $atts['orderby'] ) { + $query .= "ORDER BY post_title"; + } + if ( !isset( $atts['order'] ) || ( 'DESC' == $atts['order'] ) ) { + $query .= " DESC"; + } elseif ( 'ASC' == $atts['order'] ) { + $query .= " ASC"; + } + // 2.5.6: add filter to allow for modification of query + $query = apply_filters( 'radio_station_show_' . $datatype . '_query', $query, $show_id, $args, $atts ); if ( $args['limit'] ) { $query .= $wpdb->prepare( " LIMIT %d", $args['limit'] ); } @@ -409,7 +422,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att if ( RADIO_STATION_DEBUG ) { echo '' . esc_html( $datatype ) . ' for Show ' . esc_html( $show_id ) . ': '; echo esc_html( print_r( $results, true ) ); - echo 'Query: ' . $query . ''; + echo 'Query: ' . esc_html( $query ) . ''; } // 2.4.0.6: add processing of post excerpts From 811849ef286620a85734d392713e54a49631dd20 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 26 Aug 2023 18:10:57 +1000 Subject: [PATCH 310/377] development updates --- CHANGELOG.md | 6 + docs/Shortcodes.md | 22 +++- includes/data-feeds.php | 147 ++++++++++++++++++++++ includes/shortcodes.php | 127 ++++++++++++------- js/moment.js | 2 +- js/moment.min.js | 3 +- options.php | 3 +- player/css/radio-player.css | 4 +- radio-station.php | 2 +- readme.txt | 6 + widgets/class-current-playlist-widget.php | 4 +- widgets/class-current-show-widget.php | 6 +- widgets/class-upcoming-shows-widget.php | 6 +- 13 files changed, 282 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5dee13..0f98add 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.6 = +* Added: Filter for query and meta for show post list shortcode +* Updated: Language translations file (.pot) +* Updated: Bundled Dutch translation +* Fixed: hide empty widgets in AJAX mode + = 2.5.5 = * Updated: Freemius SDK (2.5.10) * Added: RSS Posts Feed: Related Show node diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index b9883f2..4287ffa 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -232,7 +232,7 @@ The following attributes are available for this shortcode: `[host-archive]` or `[hosts-archive]` -* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt', default 'none' for grid view. +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. * *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *status* : Query for Host status. Default 'publish'. * *perpage* : Query for number of Hosts. Default -1 (all) @@ -249,7 +249,7 @@ The following attributes are available for this shortcode: `[producer-archive]` or `[producers-archive]` -* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt', default 'none' for grid view. +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. * *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *status* : Query for Producer status. Default 'publish'. * *perpage* : Query for number of Producers. Default -1 (all) @@ -262,6 +262,24 @@ The following attributes are available for this shortcode: [Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/producers-archive/) +### [Pro] Episodes Archive Shortcode + +`[episode-archive]` or `[episodes-archive]` + +* *description* : Episode description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. +* *status* : Query for Episode status. Default 'publish'. +* *perpage* : Query for number of Episodes. Default -1 (all) +* *offset* : Query for episode offset. Default '' (no offset) +* *orderby* : Query to order Episode display by. Default 'title'. +* *order* : Query order for Episode. Default 'ASC'. +* *thumbnails* : Display Episode image. 0 or 1. Default 0. +* *show* : Display the Show the Episode is assigned to. 0 or 1. Default 1. +* *number* : Display the Episode Number. 0 or 1. Default 1. +* *air_date* : Display the Episode Air Date. 0 or 1. Default 1. + +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/episodes-archive/) + ### [Pro] Team Archive Shortcode `[team-archive]` diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 923d038..5a4d317 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -45,6 +45,12 @@ // - Not Found Feed Error // - Format Data to XML // - Convert Array to XML +// === RSS Feeds === +// - Show Posts Feed Conflict Fix +// - Show Posts Feed filter +// - Add Feed Item Show Node +// - Add Feed Item Host Node +// - Add Feed Item Producer Node // === Shift Conversions === // - Convert Show Shift // - Convert Show Shifts @@ -1340,6 +1346,147 @@ function radio_station_feed_not_found( $error ) { } +// ----------------- +// === RSS Feeds === +// ----------------- + +// ---------------------------- +// Show Posts Feed Conflict Fix +// ---------------------------- +// 2.5.5: added to allow filtering posts by related show +add_filter( 'parse_query', 'radio_station_feed_filter_fix', 0 ); +function radio_station_feed_filter_fix( $query ) { + + // --- override incorrect shows feed --- + if ( isset( $query->query['feed'] ) && ( 'feed' == $query->query['feed'] ) && ( 'show' == $query->query['post_type'] ) ) { + + if ( strstr( $_SERVER['REQUEST_URI'], '/shows/feed/' ) ) { + + // --- add host/producer nodes to RSS item output --- + add_action( 'rss2_item', 'radio_station_feed_item_node_hosts' ); + add_action( 'rss2_item', 'radio_station_feed_item_node_producers' ); + + } else { + + // --- fix for post feed with show filter --- + $query->query['post_type'] = 'post'; + $query->query_vars['post_type'] = 'post'; + $query->query_vars['name'] = ''; + $query->is_comment_feed = ''; + $query->is_post_type_archive = '1'; + unset( $query->query['name'] ); + + // --- add show node to RSS item output --- + add_action( 'rss2_item', 'radio_station_feed_item_node_shows' ); + + } + + } + // print_r( $query ); +} + +// ---------------------- +// Show Posts Feed Filter +// ---------------------- +// eg. /feed/?show=something +// 2.5.5: added to allow filtering posts by related show +add_filter( 'pre_get_posts', 'radio_station_feed_filter' ); +function radio_station_feed_filter( $query ) { + + // --- check for shows query parameter on posts feed --- + if ( $query->is_feed && $query->is_main_query() && isset( $query->query['post_type'] ) && ( 'post' == $query->query['post_type'] ) && isset( $query->query_vars['show'] ) ) { + $show_id = $query->query_vars['show']; + $show = get_post( $show_id ); + if ( !$show ) { + // --- get show by post slug --- + global $wpdb; + $q = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_name = %s AND post_type = %s"; + $q = $wpdb->prepare( $q, array( $show_id, RADIO_STATION_SHOW_SLUG ) ); + $show_id = $wpdb->get_var( $q ); + } + if ( $show_id ) { + $query->set( 'meta_query', array( + array( + 'key' => 'post_showblog_id', + 'value' => $show_id, + 'compare' => 'EQUALS' + ) + ) ); + } + } + + return $query; +} + +// ----------------------- +// Add Feed Item Show Node +// ----------------------- +// 2.5.5: added show node to post feed items +function radio_station_feed_item_node_shows() { + global $post; + $show_id = get_post_meta( $post->ID, 'post_showblog_id', true ); + if ( $show_id ) { + $show = get_post( $show_id ); + echo '' . esc_html( $show->post_title ) . '' . PHP_EOL; + echo '' . esc_html( $show_id ) . '' . PHP_EOL; + } +} + +// ----------------------- +// Add Feed Item Host Node +// ----------------------- +// 2.5.5: add host node to show feed items +function radio_station_feed_item_node_hosts() { + global $post; + $host_ids = get_post_meta( $post->ID, 'show_user_list', true ); + $hosts = ''; + $count = 0; + if ( $host_ids && is_array( $host_ids ) && ( count( $host_ids ) > 0 ) ) { + $host_count = count( $hosts_ids ); + foreach ( $host_ids as $host ) { + $count++; + $user = get_user_by( 'ID', $host ); + $hosts .= $user->display_name; + if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) == $count ) ) ) { + $hosts .= ' ' . __( 'and', 'radio-station' ) . ' '; + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { + $hosts .= ', '; + } + } + } + if ( '' != $hosts ) { + echo '' . esc_html( $hosts ) . '' . PHP_EOL; + } +} + +// --------------------------- +// Add Feed Item Producer Node +// --------------------------- +// 2.5.5: add producer node to show feed items +function radio_station_feed_item_node_producers() { + global $post; + $producer_ids = get_post_meta( $post->ID, 'producer_user_id', true ); + $producers = ''; + $count = 0; + if ( $producer_ids && is_array( $producer_ids ) && ( count( $producer_ids ) > 0 ) ) { + $producer_count = count( $producer_ids ); + foreach ( $producer_ids as $producer ) { + $count++; + $user = get_user_by( 'ID', $producer ); + $producers .= $user->display_name; + if ( ( ( 1 == $count ) && ( 2 == $producer_count ) ) || ( ( $producer_count > 2 ) && ( ( $producer_count - 1 ) == $count ) ) ) { + $producers .= ' ' . __( 'and', 'radio-station' ) . ' '; + } elseif ( ( $count < $producer_count ) && ( $producer_count > 2 ) ) { + $producers .= ', '; + } + } + } + if ( '' != $producers ) { + echo '' . esc_html( $producers ) . '' . PHP_EOL; + } +} + + // ------------------------- // === Shift Conversions === // ------------------------- diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 53e7e5a..4e0b9b6 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -3057,27 +3057,41 @@ function radio_station_current_show() { } // --- output widget contents --- + $output = radio_station_current_show_shortcode( $atts ); echo '
    ' . "\n"; - echo radio_station_current_show_shortcode( $atts ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { - // --- send to parent window --- - $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; - $js .= "parent.document.getElementById('rs-current-show-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + // 2.5.6: maybe hide entire parent widget if empty + if ( $atts['hide_empty'] && ( '' == trim( $output ) ) ) { - // --- maybe restart countdowns --- - if ( $atts['countdown'] ) { - // 2.5.0: replace timeout with interval and function check - // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; - $js .= "countdown = setInterval(function() {" . "\n"; - $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; - $js .= "parent.radio_countdown();" . "\n"; - $js .= "clearInterval(countdown);" . "\n"; - $js .= "}" . "\n"; - $js .= "}, 1000);" . "\n"; + $js .= "instance = parent.document.getElementById('current-show-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = 'none';" . "\n"; + + } else { + + // --- send to parent window --- + // 2.5.6: ensure parent widget is displayed + $js .= "instance = parent.document.getElementById('current-show-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = '';" . "\n"; + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('rs-current-show-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + + // --- maybe restart countdowns --- + if ( $atts['countdown'] ) { + // 2.5.0: replace timeout with interval and function check + // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; + $js .= "countdown = setInterval(function() {" . "\n"; + $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; + $js .= "parent.radio_countdown();" . "\n"; + $js .= "clearInterval(countdown);" . "\n"; + $js .= "}" . "\n"; + $js .= "}, 1000);" . "\n"; + } } } @@ -3719,29 +3733,42 @@ function radio_station_upcoming_shows() { } // --- output widget contents --- + $output = radio_station_upcoming_shows_shortcode( $atts ); echo '
    ' . "\n"; - echo radio_station_upcoming_shows_shortcode( $atts ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { - // --- send to parent window --- - $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; - $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; + // 2.6.5: maybe hide entire parent widget area if empty + if ( $atts['hide_empty'] && ( '' == trim( $output ) ) ) { - // --- restart countdowns --- - if ( $atts['countdown'] ) { - // 2.5.0: replace timeout with interval and function check - // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; - $js .= "countdown = setInterval(function() {" . "\n"; - $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; - $js .= "parent.radio_countdown();" . "\n"; - $js .= "clearInterval(countdown);" . "\n"; - $js .= "}" . "\n"; - $js .= "}, 1000);" . "\n"; - } + $js .= "instance = parent.document.getElementById('upcoming-shows-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = 'none';" . "\n"; + + } else { + // --- send to parent window --- + // 2.5.6: ensure parent widget is displayed + $js .= "instance = parent.document.getElementById('upcoming-shows-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = '';" . "\n"; + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; + + // --- restart countdowns --- + if ( $atts['countdown'] ) { + // 2.5.0: replace timeout with interval and function check + // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; + $js .= "countdown = setInterval(function() {" . "\n"; + $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; + $js .= "parent.radio_countdown();" . "\n"; + $js .= "clearInterval(countdown);" . "\n"; + $js .= "}" . "\n"; + $js .= "}, 1000);" . "\n"; + } + } } // --- filter load script --- @@ -4228,27 +4255,41 @@ function radio_station_current_playlist() { } // --- output widget contents --- + $output = radio_station_current_playlist_shortcode( $atts ); echo '
    ' . "\n"; - echo radio_station_current_playlist_shortcode( $atts ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; echo '
    ' . "\n"; $js = ''; if ( isset( $atts['instance'] ) ) { - // --- send to parent window --- - $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; - $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; + // 2.6.5: maybe hide entire parent widget area if empty + if ( $atts['hide_empty'] && ( '' == trim( $output ) ) ) { - // --- restart countdowns --- - if ( $atts['countdown'] ) { - // 2.5.0: replace timeout with interval and function check - // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; - $js .= "countdown = setInterval(function() {" . "\n"; - $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; - $js .= "parent.radio_countdown();" . "\n"; - $js .= "clearInterval(countdown);" . "\n"; - $js .= "}" . "\n"; - $js .= "}, 1000);" . "\n"; + $js .= "instance = parent.document.getElementById('current-playlist-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = 'none';" . "\n"; + + } else { + + // --- send to parent window --- + // 2.5.6: ensure parent widget is displayed + $js .= "instance = parent.document.getElementById('current-playlist-widget-" . esc_js( $atts['instance'] ) . "');" . "\n"; + $js .= "instance.style.display = '';" . "\n"; + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . "\n"; + $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . "\n"; + + // --- restart countdowns --- + if ( $atts['countdown'] ) { + // 2.5.0: replace timeout with interval and function check + // $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . "\n"; + $js .= "countdown = setInterval(function() {" . "\n"; + $js .= "if (typeof parent.radio_countdown == 'function') {" . "\n"; + $js .= "parent.radio_countdown();" . "\n"; + $js .= "clearInterval(countdown);" . "\n"; + $js .= "}" . "\n"; + $js .= "}, 1000);" . "\n"; + } } } diff --git a/js/moment.js b/js/moment.js index 7998adb..05a63b1 100644 --- a/js/moment.js +++ b/js/moment.js @@ -5682,4 +5682,4 @@ return hooks; -}))); \ No newline at end of file +}))); diff --git a/js/moment.min.js b/js/moment.min.js index a2590e0..3427886 100644 --- a/js/moment.min.js +++ b/js/moment.min.js @@ -1 +1,2 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var H;function f(){return H.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function F(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function L(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(c(e,t))return;return 1}function o(e){return void 0===e}function u(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function V(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function G(e,t){for(var n=[],s=e.length,i=0;i>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0 __( 'Continuous Playback', 'radio-station' ), 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ), + 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ) + . ' ' . __( 'Click here for setup notes.', 'radio-station' ) . '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, diff --git a/player/css/radio-player.css b/player/css/radio-player.css index d9a90fc..f688e54 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -308,11 +308,11 @@ background-size: 100% 100%; } .rp-station-info .rp-station-image {margin-right: 16px;} -.rp-station-info .rp-station-image.no-image {width: 0; height: 0; margin-right: 0;} +.rp-station-info .rp-station-image.no-image {width: 0; height: 0; margin-right: 0; display: none;} .rp-station-info .rp-station-image.no-image.new-image {width: 64px; height: 64px; margin-right: 20px;} .rp-station-info .rp-station-image.new-image img {display: none;} .rp-show-info .rp-show-image {margin-left: 16px;} -.rp-show-info .rp-show-image.no-image {width: 0; height: 0; margin-left: 0;} +.rp-show-info .rp-show-image.no-image {width: 0; height: 0; margin-left: 0; display: none;} /* @end */ /* @group No Solution error */ diff --git a/radio-station.php b/radio-station.php index 563ebd0..1146a76 100644 --- a/radio-station.php +++ b/radio-station.php @@ -605,7 +605,7 @@ function radio_station_register_moment() { // 2.5.0: add check for registered script in WP core if ( !wp_script_is( 'moment', 'registered' ) ) { $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); - wp_register_script( 'moment', $moment_url, array(), '2.29.4', false ); + wp_register_script( 'moment', $moment_url, array( 'jquery' ), '2.29.4', false ); } } diff --git a/readme.txt b/readme.txt index 850f2f7..0604f4b 100644 --- a/readme.txt +++ b/readme.txt @@ -400,6 +400,12 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.6 = +* Added: Filter for query and meta for show post list shortcode +* Updated: Language translations file (.pot) +* Updated: Bundled Dutch translation +* Fixed: hide empty widgets to work in AJAX loading mode + = 2.5.5 = * Updated: Freemius SDK (2.5.10) * Added: RSS Posts Feed: Related Show node diff --git a/widgets/class-current-playlist-widget.php b/widgets/class-current-playlist-widget.php index 178eac9..d8bfe28 100644 --- a/widgets/class-current-playlist-widget.php +++ b/widgets/class-current-playlist-widget.php @@ -230,11 +230,13 @@ public function widget( $args, $instance ) { // 2.3.2: added AJAX load option // 2.5.0: added no_playlist text option // 2.5.0: added playlist_title option + // 2.5.6: cast hide_empty to 1 or 0 + // --- widget display options --- $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 0; + $hide_empty = ( isset( $instance['hide_empty'] ) && $instance['hide_empty'] ) ? 1 : 0; // --- playlist display options --- $playlist_title = isset( $instance['playlist_title'] ) ? $instance['playlist_title'] : 0; $link = isset( $instance['link'] ) ? $instance['link'] : 1; diff --git a/widgets/class-current-show-widget.php b/widgets/class-current-show-widget.php index 30a1c72..48a0e82 100644 --- a/widgets/class-current-show-widget.php +++ b/widgets/class-current-show-widget.php @@ -274,11 +274,12 @@ public function update( $new_instance, $old_instance ) { // 2.2.7: fix checkbox value saving // 2.3.0: added countdown display option // 2.3.2: added ajax load option + // 2.5.6: fix hide_empty to 1 or 0 // --- widget display options --- $instance['title'] = $new_instance['title']; $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; $instance['default'] = $new_instance['default']; - $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? $new_instance['hide_empty'] : 0; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? 1 : 0; // --- show display options --- $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; $instance['title_position'] = $new_instance['title_position']; @@ -321,13 +322,14 @@ public function widget( $args, $instance ) { // 2.3.2: fix old false values to use 0 for shortcodes // 2.3.2: added AJAX load option // 2.5.0: added avatar_size for show image size + // 2.5.6: cast hide_empty to 1 or 0 // --- widget display options --- $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; $no_shows = empty( $instance['default'] ) ? '' : $instance['default']; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 0; + $hide_empty = ( isset( $instance['hide_empty'] ) && $instance['hide_empty'] ) ? 1 : 0; // --- show display options --- $show_link = $instance['link']; $title_position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; diff --git a/widgets/class-upcoming-shows-widget.php b/widgets/class-upcoming-shows-widget.php index 6fded48..f4012b7 100644 --- a/widgets/class-upcoming-shows-widget.php +++ b/widgets/class-upcoming-shows-widget.php @@ -251,12 +251,13 @@ public function update( $new_instance, $old_instance ) { // 2.3.0: added countdown display option // 2.3.2: added AJAX load option // 2.5.0; added hide_empty, avatar_size, show_title + // 2.5.6: fix hide_empty to 1 or 0 // --- widget display options --- $instance['title'] = $new_instance['title']; $instance['limit'] = $new_instance['limit']; $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; $instance['default'] = $new_instance['default']; - $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? $new_instance['hide_empty'] : 0; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? 1 : 0; // --- show display options --- $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; $instance['title_position'] = $new_instance['title_position']; @@ -301,11 +302,12 @@ public function widget( $args, $instance ) { // 2.3.2: added AJAX load option // 2.5.0: renamed default to no_shows // 2.5.0; added avatar_size, show_title + // 2.5.6: cast hide_empty to 1 or 0 // --- widget display options --- $limit = empty( $instance['limit'] ) ? '1' : $instance['limit']; $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; $no_shows = empty( $instance['default'] ) ? '' : $instance['default']; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 1; + $hide_empty = ( isset( $instance['hide_empty'] ) && $instance['hide_empty'] ) ? 1 : 0; // --- show display options --- $link = $instance['link']; $position = empty( $instance['title_position'] ) ? 'right' : $instance['title_position']; From 56f2c5d714a75bed7be9b006355af3c83124bf66 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 23 Sep 2023 14:13:21 +1000 Subject: [PATCH 311/377] freemius update to 2.5.11 --- CHANGELOG.md | 1 + freemius/.editorconfig | 9 ++ freemius/includes/class-freemius.php | 93 ++++++++++++------- .../debug/class-fs-debug-bar-panel.php | 90 +++++++++--------- freemius/includes/fs-core-functions.php | 2 +- freemius/includes/sdk/FreemiusWordPress.php | 8 +- freemius/phpcompat.xml | 19 ++++ freemius/phpstan.neon | 14 +++ freemius/start.php | 2 +- .../templates/forms/license-activation.php | 21 +++-- options.php | 39 +++++--- readme.txt | 1 + 12 files changed, 197 insertions(+), 102 deletions(-) create mode 100644 freemius/.editorconfig create mode 100644 freemius/phpcompat.xml create mode 100644 freemius/phpstan.neon diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f98add..50863bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). = 2.5.6 = +* Updated: Freemius SDK (2.5.11) * Added: Filter for query and meta for show post list shortcode * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation diff --git a/freemius/.editorconfig b/freemius/.editorconfig new file mode 100644 index 0000000..f0601d0 --- /dev/null +++ b/freemius/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.yml] +indent_style = space +indent_size = 2 + +[*.neon] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index b1fba7e..318ea5b 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -1531,8 +1531,8 @@ private function register_constructor_hooks() { ); $this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) ); - add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); - add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); + add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); // @phpstan-ignore-line + add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); // @phpstan-ignore-line add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); /** @@ -1642,7 +1642,7 @@ static function _remove_fs_updates_from_plugin_install_page( $updates, $transien * @author Leo Fajardo (@leorw) * @since 2.2.3 * - * @return string + * @return void */ static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { $slug_basename_map = array(); @@ -3492,6 +3492,28 @@ function is_clone( $only_if_manual_resolution_is_not_hidden = false ) { * @return string */ static function get_unfiltered_site_url( $blog_id = null, $strip_protocol = false, $add_trailing_slash = false ) { + $url = ( ! is_multisite() && defined( 'WP_SITEURL' ) ) ? WP_SITEURL : self::get_site_url_from_wp_option( $blog_id ); + + if ( $strip_protocol ) { + $url = fs_strip_url_protocol( $url ); + } + + if ( $add_trailing_slash ) { + $url = trailingslashit( $url ); + } + + return $url; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.6.0 + * + * @param int|null $blog_id + * + * @return string + */ + private static function get_site_url_from_wp_option( $blog_id = null ) { global $wp_filter; $site_url_filters = array( @@ -3518,14 +3540,6 @@ static function get_unfiltered_site_url( $blog_id = null, $strip_protocol = fals } } - if ( $strip_protocol ) { - $url = fs_strip_url_protocol( $url ); - } - - if ( $add_trailing_slash ) { - $url = trailingslashit( $url ); - } - return $url; } @@ -4080,7 +4094,7 @@ private function should_turn_fs_on( $is_update = true ) { $max = 100; if ( function_exists( 'random_int' ) ) { - $random = random_int( $min, $max ); + $random = random_int( $min, $max ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_intFound } else { $random = rand( $min, $max ); } @@ -4399,7 +4413,22 @@ static function is_valid_email( $email ) { } // Get the UTF encoded domain name. - $domain = idn_to_ascii( $parts[1] ) . '.'; + /** + * @note - The check of `defined('...')` is there to account for PHP servers compiled with some older version of ICU where the constants are not defined. + * @author - @swashata + */ + $is_new_idn_available = ( + version_compare( PHP_VERSION, '5.6.40') > 0 && + defined( 'IDNA_DEFAULT' ) && + defined( 'INTL_IDNA_VARIANT_UTS46' ) + ); + if ( $is_new_idn_available ) { + $domain = idn_to_ascii( $parts[1], IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 ); + } else { + $domain = idn_to_ascii( $parts[1] ); // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet + } + + $domain = $domain . '.'; return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); } @@ -9990,7 +10019,7 @@ function _uninstall_plugin_event( $check_user = true ) { * @param string $is_premium * @param string $caller * - * @return string + * @return void */ function set_basename( $is_premium, $caller ) { $basename = plugin_basename( $caller ); @@ -12416,7 +12445,7 @@ private function activate_license_on_many_installs( $install_2_blog_map = array(); foreach ( $blog_2_install_map as $blog_id => $install ) { - $params[] = array( 'id' => $install->id ); + $params[] = array( 'id' => $install->id, 'url' => $install->url ); $install_2_blog_map[ $install->id ] = $blog_id; } @@ -16697,7 +16726,7 @@ private static function decrypt_entity( FS_Entity $entity ) { * * @return FS_User|false */ - static function _get_user_by_email( $email ) { + public static function _get_user_by_email( $email ) { self::$_static_logger->entrance(); $email = trim( strtolower( $email ) ); @@ -17871,7 +17900,7 @@ private function install_with_new_user( * @param bool $trial_plan_id * @param bool $redirect * - * @return string If redirect is `false`, returns the next page the user should be redirected to. + * @return void */ private function install_many_pending_with_user( $user_id, @@ -23264,6 +23293,18 @@ private function _handle_account_edits() { } } + /** + * Adds CSS classes for the body tag in the admin. + * + * @param string $classes Space-separated string of class names. + * + * @return string $classes FS Admin body tag class names. + */ + public function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + return $classes; + } + /** * Account page resources load. * @@ -23280,14 +23321,7 @@ function _account_page_load() { if ( $this->has_addons() ) { wp_enqueue_script( 'plugin-install' ); add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); + add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) ); } if ( $this->has_paid_plan() && @@ -23422,14 +23456,7 @@ function _addons_page_load() { wp_enqueue_script( 'plugin-install' ); add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); + add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) ); if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { $this->_admin_notices->add( diff --git a/freemius/includes/debug/class-fs-debug-bar-panel.php b/freemius/includes/debug/class-fs-debug-bar-panel.php index 0b7969d..c325561 100644 --- a/freemius/includes/debug/class-fs-debug-bar-panel.php +++ b/freemius/includes/debug/class-fs-debug-bar-panel.php @@ -10,55 +10,59 @@ exit; } - /** - * Extends Debug Bar plugin by adding a panel to show all Freemius API requests. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * Class Freemius_Debug_Bar_Panel - */ - class Freemius_Debug_Bar_Panel extends Debug_Bar_Panel { - function init() { - $this->title( 'Freemius' ); - } + if ( class_exists( 'Debug_Bar_Panel' ) ) { - static function requests_count() { - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); + /** + * Extends Debug Bar plugin by adding a panel to show all Freemius API requests. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * Class Freemius_Debug_Bar_Panel + */ + class Freemius_Debug_Bar_Panel extends Debug_Bar_Panel { + + public function init() { + $this->title( 'Freemius' ); // @phpstan-ignore-line } - return number_format( count( $logger ) ); - } + public static function requests_count() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } - static function total_time() { - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); + return number_format( count( $logger ) ); } - $total_time = .0; - foreach ( $logger as $l ) { - $total_time += $l['total']; - } + public static function total_time() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } - return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); - } + $total_time = .0; + foreach ( $logger as $l ) { + $total_time += $l['total']; + } + + return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); + } - function render() { - ?> -
    - -
    - -
    - -
    - -
    - +
    + +
    + +
    + +
    + +
    + + + Apply PHP compatibility checks to all files + + + + + + + ./ + + + + /node_modules/* + /vendor/* + /assets/* + /languages/* + .phpstan + \ No newline at end of file diff --git a/freemius/phpstan.neon b/freemius/phpstan.neon new file mode 100644 index 0000000..02b7cc4 --- /dev/null +++ b/freemius/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + level: 0 + paths: + - config.php + - start.php + - require.php + - includes/ + - templates/ + bootstrapFiles: + - .phpstan/exceptions.php + ignoreErrors: + - + message: '#will always evaluate to true#' + path: includes/class-fs-plugin-updater.php \ No newline at end of file diff --git a/freemius/start.php b/freemius/start.php index 096f510..637a723 100644 --- a/freemius/start.php +++ b/freemius/start.php @@ -15,7 +15,7 @@ * * @var string */ - $this_sdk_version = '2.5.10'; + $this_sdk_version = '2.5.11'; #region SDK Selection Logic -------------------------------------------------------------------- diff --git a/freemius/templates/forms/license-activation.php b/freemius/templates/forms/license-activation.php index 2217c36..42a78fe 100644 --- a/freemius/templates/forms/license-activation.php +++ b/freemius/templates/forms/license-activation.php @@ -52,8 +52,9 @@ if ( $is_network_activation ) { $all_sites = Freemius::get_sites(); - $subsite_data_by_install_id = array(); - $install_url_by_install_id = array(); + $all_site_details = array(); + $subsite_url_by_install_id = array(); + $install_url_by_install_id = array(); foreach ( $all_sites as $site ) { $site_details = $fs->get_site_info( $site ); @@ -66,9 +67,9 @@ $install = $fs->get_install_by_blog_id($blog_id); if ( is_object( $install ) ) { - if ( isset( $subsite_data_by_install_id[ $install->id ] ) ) { - $clone_subsite_data = $subsite_data_by_install_id[ $install->id ]; - $clone_install_url = $install_url_by_install_id[ $install->id ]; + if ( isset( $subsite_url_by_install_id[ $install->id ] ) ) { + $clone_subsite_url = $subsite_url_by_install_id[ $install->id ]; + $clone_install_url = $install_url_by_install_id[ $install->id ]; if ( /** @@ -77,7 +78,7 @@ * @author Leo Fajardo (@leorw) * @since 2.5.0 */ - fs_strip_url_protocol( untrailingslashit( $clone_install_url ) ) === fs_strip_url_protocol( untrailingslashit( $clone_subsite_data['url'] ) ) || + fs_strip_url_protocol( untrailingslashit( $clone_install_url ) ) === fs_strip_url_protocol( untrailingslashit( $clone_subsite_url ) ) || fs_strip_url_protocol( untrailingslashit( $install->url ) ) !== fs_strip_url_protocol( untrailingslashit( $site_details['url'] ) ) ) { continue; @@ -88,15 +89,17 @@ $site_details['license_id'] = $install->license_id; } - $subsite_data_by_install_id[ $install->id ] = $site_details; - $install_url_by_install_id[ $install->id ] = $install->url; + $subsite_url_by_install_id[ $install->id ] = $site_details['url']; + $install_url_by_install_id[ $install->id ] = $install->url; } + + $all_site_details[] = $site_details; } if ( $is_network_activation ) { $vars = array( 'id' => $fs->get_id(), - 'sites' => array_values( $subsite_data_by_install_id ), + 'sites' => $all_site_details, 'require_license_key' => true ); diff --git a/options.php b/options.php index 0e25876..93c89e4 100644 --- a/options.php +++ b/options.php @@ -610,19 +610,6 @@ 'pro' => true, ), - // --- [Pro/Player] Metadata URL --- - // 2.4.0.3: added for alternative stream metadata URL - 'player_bar_metadata' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Metadata URL', 'radio-station' ), - 'default' => '', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), - 'pro' => true, - ), - // --- [Pro/Player] Track Animation --- // 2.5.0: added track animation option 'player_bar_track_animation' => array( @@ -641,6 +628,32 @@ 'pro' => true, ), + // --- [Pro/Player] Metadata URL --- + // 2.4.0.3: added for alternative stream metadata URL + 'player_bar_metadata' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Metadata URL', 'radio-station' ), + 'default' => '', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Store Track Metadata --- + // 2.5.6: added option to store stream + 'player_store_metadata' => array( + 'type' => 'checkbox', + 'label' => __( 'Store Track Metadata?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ), + 'pro' => true, + ); + // === Master Schedule Page === // --- Schedule Page --- diff --git a/readme.txt b/readme.txt index 0604f4b..b63c96e 100644 --- a/readme.txt +++ b/readme.txt @@ -401,6 +401,7 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == = 2.5.6 = +* Updated: Freemius SDK (2.5.11) * Added: Filter for query and meta for show post list shortcode * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation From cff9e9a20125241a3dc813f4437d11fe133c7e84 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 23 Sep 2023 14:16:12 +1000 Subject: [PATCH 312/377] dev updates --- includes/templates.php | 11 +++++++---- loader.php | 24 +++++++++++++++--------- readme.txt | 1 + 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/includes/templates.php b/includes/templates.php index 350cec1..2d45d1a 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -405,10 +405,13 @@ function radio_station_single_content_template( $content, $post_type ) { $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; } $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); - foreach ( $templates as $template ) { - if ( file_exists( $template ) ) { - $content_template = $template; - break; + // 2.5.6: added check that templates is still a populated array + if ( is_array( $templates ) && ( count( $templates ) > 0 ) ) { + foreach ( $templates as $template ) { + if ( file_exists( $template ) ) { + $content_template = $template; + break; + } } } if ( !isset( $content_template ) ) { diff --git a/loader.php b/loader.php index 2334cca..0a65168 100644 --- a/loader.php +++ b/loader.php @@ -227,16 +227,19 @@ public function setup_plugin() { // --- Pro Functions --- if ( !isset( $args['proslug'] ) ) { $proslug = $this->plugin_data( '@fs_premium_only' ); - // 1.0.1: if more than one file, extract pro slug based on the first filename - if ( !strstr( $proslug, ',' ) ) { - $profiles = array( $proslug ); - $proslug = trim( $proslug ); - } else { - $profiles = explode( ',', $proslug ); - $proslug = trim( $profiles[0] ); + // 1.3.0: check for pro slug string + if ( is_string( $proslug ) ) { + // 1.0.1: if more than one file, extract pro slug based on the first filename + if ( !strstr( $proslug, ',' ) ) { + $profiles = array( $proslug ); + $proslug = trim( $proslug ); + } else { + $profiles = explode( ',', $proslug ); + $proslug = trim( $profiles[0] ); + } + $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension + $args['profiles'] = $profiles; } - $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension - $args['profiles'] = $profiles; } // --- Update Loader Args --- @@ -3471,6 +3474,9 @@ function radio_station_settings_resources( $media, $color_picker ) { // CHANGELOG // ========= +// == 1.3.0 == +// - add check if pro slug data is a string + // == 1.2.9 == // - fix empty number field converting to NaN value // - add bottom padding to settings form wrap box diff --git a/readme.txt b/readme.txt index b63c96e..80236d8 100644 --- a/readme.txt +++ b/readme.txt @@ -15,6 +15,7 @@ Radio Station lets you build and manage a Show Schedule for a radio station or I [Live Demo](https://demo.radiostation.pro/) | [Documentation](https://radiostation.pro/docs/) | [Support](https://wordpress.org/support/plugin/radio-station/) | [**Upgrade to PRO**!](https://radiostation.pro/pricing/) +This plugin can be downloaded for free without any paid subscription from the [official WordPress repository](https://wordpress.org/plugins/radio-station/). #### RADIO STATION by netmix®† - THE BEST WORDPRESS PLUGIN FOR BROADCASTERS! From 5df0b83fdc939e2813879f22dd9755699e2f83ef Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 28 Sep 2023 18:50:28 +1000 Subject: [PATCH 313/377] dev updates #472 --- CHANGELOG.md | 1 + includes/support-functions.php | 33 ++++++++------ readme.txt | 1 + templates/single-show-content.php | 74 +++++++++++++++++-------------- 4 files changed, 62 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50863bf..ce583e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation * Fixed: hide empty widgets in AJAX mode +* Fixed: check linked override shifts before displaying = 2.5.5 = * Updated: Freemius SDK (2.5.10) diff --git a/includes/support-functions.php b/includes/support-functions.php index 09b966d..be209ff 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -323,9 +323,11 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att // 2.3.3.4: handle possible multiple show post values // 2.3.3.9: added 'i:' prefix to LIKE match value - // TODO: use wpdb prepare method on LIKE statement - $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value LIKE '%i:" . $show_id . "%'"; + // 2.5.6: fix prepare query syntax by separating LIKE statement + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s"; $query = $wpdb->prepare( $query, $metakey ); + // TODO: use wpdb prepare method on LIKE statement? + $query .= "AND meta_value LIKE '%i:" . $show_id . "%'"; $results = $wpdb->get_results( $query, ARRAY_A ); if ( RADIO_STATION_DEBUG ) { echo 'Related Query: ' . esc_html( $query ) . ''; @@ -871,17 +873,22 @@ function radio_station_get_linked_override_times( $post_id ) { $override_ids = radio_station_get_linked_overrides( $post_id ); $overrides = array(); - foreach ( $override_ids as $override_id ) { - $schedule = get_post_meta( $override_id, 'show_override_sched', true ); - if ( $schedule ) { - if ( !is_array( $overrides ) ) { - $override = array(); - } - if ( !is_array( $schedule ) ) { - $schedule = array( $schedule ); - } - foreach ( $schedule as $override ) { - $overrides[] = $override; + if ( $override_ids && is_array( $override_ids ) && ( count( $override_ids ) > 0 ) ) { + foreach ( $override_ids as $override_id ) { + $schedule = get_post_meta( $override_id, 'show_override_sched', true ); + if ( $schedule ) { + if ( !is_array( $overrides ) ) { + $override = array(); + } + if ( !is_array( $schedule ) ) { + $schedule = array( $schedule ); + } + foreach ( $schedule as $override ) { + // 2.5.6: add check if override is disabled + if ( 'yes' != $override['disabled'] ) { + $overrides[] = $override; + } + } } } } diff --git a/readme.txt b/readme.txt index 80236d8..1daecb4 100644 --- a/readme.txt +++ b/readme.txt @@ -406,6 +406,7 @@ We recommend you test these on a Staging site (or a development copy of your liv * Added: Filter for query and meta for show post list shortcode * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation +* Fixed: check linked override shifts before displaying * Fixed: hide empty widgets to work in AJAX loading mode = 2.5.5 = diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 673fbd0..c752d34 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -662,45 +662,51 @@ $overrides = radio_station_get_linked_override_times( $post_id ); $scheduled = ''; if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { + if ( RADIO_STATION_DEBUG ) { + echo 'LINKED OVERRIDES: ' . esc_html( print_r( $overrides, true ) ) . '' . "\n"; + } $now = radio_station_get_now(); foreach ( $overrides as $override ) { if ( 'yes' != $override['disabled'] ) { + // 2.5.6: added check that override keys are not empty + if ( !empty( $override['date'] ) && !empty( $override['start_hour'] ) && !empty( $override['start_min'] ) && !empty( $override['start_meridian'] ) && !empty( $override['end_hour'] ) && !empty( $override['end_min'] ) && !empty( $override['end_meridian'] ) ) { + + $start = $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; + $end = $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; + $override_start_time = radio_station_to_time( $start ); + $override_end_time = radio_station_to_time( $end ); + if ( $override_end_time <= $override_start_time ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } - $start = $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; - $end = $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; - $override_start_time = radio_station_to_time( $start ); - $override_end_time = radio_station_to_time( $end ); - if ( $override_end_time <= $override_start_time ) { - $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - } + // --- maybe filter out past scheduled dates --- + if ( $show_past_dates || ( $override_end_time > $now ) ) { + + $start_display = radio_station_get_time( $start_data_format, $override_start_time ); + $end_display = radio_station_get_time( $end_data_format, $override_end_time ); + $start_display = radio_station_translate_time( $start_display ); + $end_display = radio_station_translate_time( $end_display ); + $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); + $date = radio_station_get_time( $date_format, $date_time ); - // --- maybe filter out past scheduled dates --- - if ( $show_past_dates || ( $override_end_time > $now ) ) { - - $start_display = radio_station_get_time( $start_data_format, $override_start_time ); - $end_display = radio_station_get_time( $end_data_format, $override_end_time ); - $start_display = radio_station_translate_time( $start_display ); - $end_display = radio_station_translate_time( $end_display ); - $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); - $date = radio_station_get_time( $date_format, $date_time ); - - // 2.4.0.6: use filtered shift separator - $separator = ' - '; - $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); - - $scheduled .= '
    ' . "\n"; - $scheduled .= '' . esc_html( $date ) . '' . "\n"; - $scheduled .= '' . esc_html( $start_display ) . '' . "\n"; - $scheduled .= '' . esc_html( $separator ) . '' . "\n"; - $scheduled .= '' . esc_html( $end_display ) . '' . "\n"; - $scheduled .= '
    ' . "\n"; - - $scheduled .= '
    ' . "\n"; - $scheduled .= '[' . "\n"; - $scheduled .= '' . "\n"; - $scheduled .= '' . esc_html( $separator ) . '' . "\n"; - $scheduled .= ']' . "\n"; - $scheduled .= '
    ' . "\n"; + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); + + $scheduled .= '
    ' . "\n"; + $scheduled .= '' . esc_html( $date ) . '' . "\n"; + $scheduled .= '' . esc_html( $start_display ) . '' . "\n"; + $scheduled .= '' . esc_html( $separator ) . '' . "\n"; + $scheduled .= '' . esc_html( $end_display ) . '' . "\n"; + $scheduled .= '
    ' . "\n"; + + $scheduled .= '
    ' . "\n"; + $scheduled .= '[' . "\n"; + $scheduled .= '' . "\n"; + $scheduled .= '' . esc_html( $separator ) . '' . "\n"; + $scheduled .= ']' . "\n"; + $scheduled .= '
    ' . "\n"; + } } } } From 082704a762b3323864c849d859424d871f92f276 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 28 Sep 2023 21:22:36 +1000 Subject: [PATCH 314/377] update plugin panel (1.3.0) #472 --- CHANGELOG.md | 1 + loader.php | 24 +++++++++++++++++++++++- radio-station.php | 2 +- readme.txt | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce583e1..8ddaa4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.6 = * Updated: Freemius SDK (2.5.11) +* Updated: Plugin Panel (1.3.0) * Added: Filter for query and meta for show post list shortcode * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation diff --git a/loader.php b/loader.php index 0a65168..d184783 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // ================================= // // -------------- -// Version: 1.2.9 +// Version: 1.3.0 // -------------- // Note: Changelog and structure at end of file. // @@ -634,6 +634,16 @@ public function update_settings() { } $newsettings = $posted; + } elseif ( 'email' == $type ) { + + // --- email field --- + // 1.3.0: added explicitly for email field type + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + if ( !is_string( $valid ) ) { + $valid = 'EMAIL'; + } + $newsettings = $posted; + } elseif ( ( 'number' == $type ) || ( 'numeric' == $type ) ) { // --- number field value --- @@ -785,6 +795,16 @@ public function update_settings() { } $settings[$key] = $posted; + } else { + + // --- fallback to text type --- + // 1.3.0: added for unspecified option field type + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + if ( !is_string( $valid ) ) { + $valid = 'TEXT'; + } + $newsettings = $posted; + } if ( $this->debug ) { @@ -3475,6 +3495,8 @@ function radio_station_settings_resources( $media, $color_picker ) { // ========= // == 1.3.0 == +// - added explicit email option field type +// - added fallback to text option firld type // - add check if pro slug data is a string // == 1.2.9 == diff --git a/radio-station.php b/radio-station.php index 1146a76..9a9d6fe 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.5 +Version: 2.5.5.1 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 1daecb4..ca8387a 100644 --- a/readme.txt +++ b/readme.txt @@ -403,6 +403,7 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.6 = * Updated: Freemius SDK (2.5.11) +* Updated: Plugin Panel (1.3.0) * Added: Filter for query and meta for show post list shortcode * Updated: Language translations file (.pot) * Updated: Bundled Dutch translation From 7b16807b6d47e010d720b981fac73418d7c2e20e Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 31 Oct 2023 22:31:37 +1000 Subject: [PATCH 315/377] dev updates --- .idea/php.xml | 17 + .idea/radio-station.iml | 8 + .idea/workspace.xml | 81 +++- CHANGELOG.md | 6 +- css/rs-mailchimp.css | 2 +- css/rs-schedule.css | 4 +- css/rs-templates.css | 4 +- docs/Options.md | 8 + docs/Player.md | 120 +++-- docs/Pro.md | 36 +- docs/index.md | 4 +- help/contextual-help-config.php | 54 ++- help/edit-show.php | 12 +- help/import.php | 25 +- help/show-schedule.php | 38 +- help/yaml.php | 20 +- includes/blocks.php | 24 +- includes/class-current-playlist-widget.php | 272 ----------- includes/class-current-show-widget.php | 360 -------------- includes/class-radio-clock-widget.php | 207 -------- includes/class-radio-player-widget.php | 324 ------------- includes/class-upcoming-shows-widget.php | 327 ------------- includes/data-feeds.php | 63 +-- includes/extra-strings.php | 3 +- includes/legacy.php | 97 ++-- includes/master-schedule.php | 35 +- includes/post-types-admin.php | 272 ++++++----- includes/post-types.php | 3 +- includes/schedules.php | 54 ++- includes/shortcodes.php | 59 ++- includes/support-functions.php | 445 ++++++++++-------- includes/templates.php | 31 +- includes/times.php | 69 ++- includes/user-roles.php | 2 +- loader.php | 53 ++- options.php | 28 +- phpcs.xml | 22 +- player/compat.php | 7 +- player/css/radio-player.css | 6 +- player/js/radio-player.js | 2 +- player/radio-player.php | 128 +++-- radio-station-admin.php | 92 ++-- radio-station.php | 53 ++- readme.txt | 8 +- scheduler/schedule-engine.php | 212 ++++----- templates/legacy/author.php | 5 +- .../legacy/playlist-archive-template.php | 9 +- .../legacy/show-blog-archive-template.php | 4 +- templates/legacy/single-playlist.php | 13 +- templates/master-schedule-div.php | 2 + templates/master-schedule-list.php | 6 +- templates/master-schedule-table.php | 25 +- templates/master-schedule-tabs.php | 14 +- templates/playlist-export.php | 49 +- templates/single-playlist-content.php | 7 +- templates/single-show-content.php | 63 ++- widgets/class-current-playlist-widget.php | 10 +- widgets/class-current-show-widget.php | 32 +- widgets/class-radio-clock-widget.php | 3 +- widgets/class-radio-player-widget.php | 30 +- widgets/class-upcoming-shows-widget.php | 43 +- 61 files changed, 1448 insertions(+), 2564 deletions(-) delete mode 100644 includes/class-current-playlist-widget.php delete mode 100644 includes/class-current-show-widget.php delete mode 100644 includes/class-radio-clock-widget.php delete mode 100644 includes/class-radio-player-widget.php delete mode 100644 includes/class-upcoming-shows-widget.php diff --git a/.idea/php.xml b/.idea/php.xml index 4c27ae1..192cbe7 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -1,11 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/radio-station.iml b/.idea/radio-station.iml index 80ac2e3..c7d2c19 100644 --- a/.idea/radio-station.iml +++ b/.idea/radio-station.iml @@ -4,10 +4,18 @@ + + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 48cd435..5ce9b45 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,5 +1,11 @@ + + - + $PROJECT_DIR$/composer.json - + - + + + + - - - - - ' . esc_html( __( 'AJAX Load Widget?', 'radio-station' ) ) . ' - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    '; - - // --- filter and output --- - // 2.3.0: filter to allow for extra fields - $fields = apply_filters( 'radio_station_playlist_widget_fields', $fields, $this, $instance ); - echo $fields; - } - - // --- update widget instance --- - public function update( $new_instance, $old_instance ) { - - $instance = $old_instance; - - // 2.3.0: added hide widget if empty option - // 2.3.0: added countdown display option - // 2.3.2: added AJAX load option - // 2.3.3.8: added playlist link option - $instance['title'] = $new_instance['title']; - $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; - $instance['artist'] = isset( $new_instance['artist'] ) ? 1 : 0; - $instance['song'] = isset( $new_instance['song'] ) ? 1 : 0; - $instance['album'] = isset( $new_instance['album'] ) ? 1 : 0; - $instance['label'] = isset( $new_instance['label'] ) ? 1 : 0; - $instance['comments'] = isset( $new_instance['comments'] ) ? 1 : 0; - $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; - - // 2.3.0: apply filters to widget instance update - $instance = apply_filters( 'radio_station_playlist_widget_update', $instance, $new_instance, $old_instance ); - return $instance; - } - - // --- output widget display --- - public function widget( $args, $instance ) { - - global $radio_station_data; - - // --- set widget id --- - // 2.3.0: added unique widget id - if ( !isset( $radio_station_data['widgets']['current-playlist'] ) ) { - $id = $radio_station_data['widgets']['current-playlist'] = 0; - } else { - $id = $radio_station_data['widgets']['current-playlist']++; - } - - // 2.3.0: filter widget_title whether empty or not - // 2.3.0: added hide widget if empty option - // 2.3.0: added countdown display option - // 2.3.2: set fallback options to numeric for shortcode - // 2.3.2: added AJAX load option - $title = empty( $instance['title'] ) ? '' : $instance['title']; - $title = apply_filters( 'widget_title', $title ); - $link = isset( $instance['link'] ) ? $instance['link'] : 1; - $artist = $instance['artist']; - $song = $instance['song']; - $album = $instance['album']; - $label = $instance['label']; - $comments = $instance['comments']; - $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 1; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; - - // --- set shortcode attributes for display --- - $atts = array( - 'title' => $title, - 'link' => $link, - 'artist' => $artist, - 'song' => $song, - 'album' => $album, - 'label' => $label, - 'comments' => $comments, - 'countdown' => $countdown, - 'widget' => 1, - 'id' => $id, - ); - - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - - // 2.3.3.9: add filter for default widget attributes - $atts = apply_filters( 'radio_station_current_playlist_widget_atts', $atts, $instance ); - - // --- get default display output --- - // 2.3.0: use shortcode to generate default widget output - $output = radio_station_current_playlist_shortcode( $atts ); - - // --- check for widget output override --- - // 2.3.0: added this override filter - $output = apply_filters( 'radio_station_current_playlist_widget_override', $output, $args, $atts ); - - // 2.3.0: added hide widget if empty option - if ( !$hide_empty || ( $hide_empty && $output ) ) { - - // --- beore widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add unique id to widget - // 2.3.2: add class to widget - // 2.4.0.1: add current-playlist-widget class - echo '
    '; - - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - - // --- check for widget output override --- - // 2.3.3.9: added missing override filter - $output = apply_filters( 'radio_station_current_playlist_widget_override', $output, $args, $atts ); - - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - // --- close widget contents wrapper --- - echo '
    '; - - // --- close widget container --- - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.2.4: renamed djonair.css to widgets.css and load for all widgets - // 2.3.0: widgets.css merged into rs-shortcodes.css - // 2.3.0: use abstracted method for enqueueing widget styles - radio_station_enqueue_style( 'shortcodes' ); - - } - } -} - -// --- register the widget --- -// 2.2.7: revert anonymous function usage for backwards compatibility -add_action( 'widgets_init', 'radio_station_register_current_playlist_widget' ); -function radio_station_register_current_playlist_widget() { - // note: widget class name to remain unchanged for backwards compatibility - register_widget( 'Playlist_Widget' ); -} diff --git a/includes/class-current-show-widget.php b/includes/class-current-show-widget.php deleted file mode 100644 index e4c5add..0000000 --- a/includes/class-current-show-widget.php +++ /dev/null @@ -1,360 +0,0 @@ - 'DJ_Widget', - 'description' => __( 'The currently playing on-air Show.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Current Show On-Air', 'radio-station' ); - parent::__construct( 'DJ_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - - // 2.3.3: set time format default to plugin setting - $title = $instance['title']; - $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : __( 'No Show scheduled for this time.', 'radio-station' ); - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - $show_playlist = isset( $instance['show_playlist'] ) ? $instance['show_playlist'] : false; - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : false; - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - // 2.3.0: added countdown display option - // 2.3.2: added AJAX load option - // 2.3.3.8: added show encore text display option - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : true; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; - - // 2.3.0: convert template style code to strings - // 2.3.2: added AJAX load option field - $fields = ' -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - ' . esc_html( __( 'Width of Show Avatar (in pixels, default full width)', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - ' . esc_html( __( 'Text to display if no Show is scheduled for the current time.', 'radio-station' ) ) . ' -

    - -

    - -
    - ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' -

    - -

    - -

    '; - - // --- filter and output --- - // 2.3.0: added field filter for extra fields - // 2.3.2: fix for second and third filter arguments - $fields = apply_filters( 'radio_station_current_show_widget_fields', $fields, $this, $instance ); - echo $fields; - } - - // --- update widget instance values --- - public function update( $new_instance, $old_instance ) { - - $instance = $old_instance; - - // 2.2.7: fix checkbox value saving - $instance['title'] = $new_instance['title']; - $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; - $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; - $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; - $instance['default'] = $new_instance['default']; - $instance['time'] = $new_instance['time']; - $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; - $instance['show_playlist'] = isset( $new_instance['show_playlist'] ) ? 1 : 0; - $instance['show_all_sched'] = isset( $new_instance['show_all_sched'] ) ? 1 : 0; - $instance['show_desc'] = isset( $new_instance['show_desc'] ) ? 1 : 0; - - // 2.2.4: added title position and avatar width settings - // 2.3.0: added countdown display option - // 2.3.2: added ajax load option - $instance['title_position'] = $new_instance['title_position']; - $instance['avatar_width'] = $new_instance['avatar_width']; - $instance['link_djs'] = isset( $new_instance['link_djs'] ) ? 1 : 0; - $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; - - // 2.3.0: filter widget update instance - $instance = apply_filters( 'radio_station_current_show_widget_update', $instance, $new_instance, $old_instance ); - return $instance; - } - - // --- widget output --- - public function widget( $args, $instance ) { - - global $radio_station_data; - - // --- set widget id --- - // 2.3.0: added unique widget id - if ( !isset( $radio_station_data['widgets']['current-show'] ) ) { - $id = $radio_station_data['widgets']['current-show'] = 0; - } else { - $id = $radio_station_data['widgets']['current-show']++; - } - - // 2.3.0: filter widget_title whether empty or not - $title = empty( $instance['title'] ) ? '' : $instance['title']; - $title = apply_filters( 'widget_title', $title ); - $display_djs = $instance['display_djs']; - $djavatar = $instance['djavatar']; - $link = $instance['link']; - $default = empty( $instance['default'] ) ? '' : $instance['default']; - $time = empty( $instance['time'] ) ? '' : $instance['time']; - $show_sched = $instance['show_sched']; - $show_playlist = $instance['show_playlist']; - - // 2.3.2: fix old false values to use 0 for shortcodes - // keep the default settings for people updating from 1.6.2 or earlier - $show_all_sched = isset( $instance['show_all_sched'] ) ? $instance['show_all_sched'] : 0; - // keep the default settings for people updating from 2.0.12 or earlier - $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : 0; - $encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : 0; - - // 2.2.4: added title position, avatar width and DJ link settings - // 2.3.2: added AJAX load option - $position = empty( $instance['title_position'] ) ? 'bottom' : $instance['title_position']; - $width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; - - // --- set shortcode attributes --- - // 2.3.0: map widget options to shortcode attributes - // 2.3.2: added AJAX load option - $atts = array( - // --- legacy widget options --- - 'title' => $title, - 'display_hosts' => $display_djs, - 'show_avatar' => $djavatar, - 'show_link' => $link, - 'default_name' => $default, - 'time' => $time, - 'show_sched' => $show_sched, - 'show_playlist' => $show_playlist, - 'show_all_sched' => $show_all_sched, - 'show_desc' => $show_desc, - // --- new widget options --- - 'title_position' => $position, - 'avatar_width' => $width, - 'link_djs' => $link_djs, - 'show_encore' => $encore, - 'countdown' => $countdown, - 'widget' => 1, - 'id' => $id, - ); - - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - - // 2.3.3.9: add filter for default widget attributes - $atts = apply_filters( 'radio_station_current_show_widget_atts', $atts, $instance ); - - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add unique id to widget - // 2.3.2: add class to widget - // 2.4.0.1: add current-show-widget class - echo '
    '; - - // --- widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - - // --- get default display output --- - // 2.3.0: use shortcode to generate default widget output - $output = radio_station_current_show_shortcode( $atts ); - - // --- check for widget output override --- - // 2.3.0: added this override filter - $output = apply_filters( 'radio_station_current_show_widget_override', $output, $args, $atts ); - - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - // --- close widget contents wrapper --- - echo '
    '; - - // --- close widget container --- - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.2.4: renamed djonair.css to widgets.css and load for all widgets - // 2.3.0: widgets.css merged into rs-shortcodes.css - // 2.3.0: use abstracted method for enqueueing widget styles - radio_station_enqueue_style( 'shortcodes' ); - - } -} - -// --- register the widget --- -// 2.2.7: revert anonymous function usage for backwards compatibility -add_action( 'widgets_init', 'radio_station_register_current_show_widget' ); -function radio_station_register_current_show_widget() { - // note: widget class name to remain unchanged for backwards compatibility - register_widget( 'DJ_Widget' ); -} diff --git a/includes/class-radio-clock-widget.php b/includes/class-radio-clock-widget.php deleted file mode 100644 index b88981d..0000000 --- a/includes/class-radio-clock-widget.php +++ /dev/null @@ -1,207 +0,0 @@ - 'Radio_Clock_Widget', - 'description' => __( 'Display current radio and user times.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Radio Clock', 'radio-station' ); - parent::__construct( 'Radio_Clock_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => __( 'Radio Clock', 'radio-station' ) ) ); - - $title = isset( $instance['title'] ) ? $instance['title'] : ''; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $seconds = isset( $instance['seconds'] ) ? $instance['seconds'] : 0; - $day = isset( $instance['day'] ) ? $instance['day'] : 'full'; - $date = isset( $instance['date'] ) ? $instance['date'] : 1; - $month = isset( $instance['month'] ) ? $instance['month'] : 'full'; - $zone = isset( $instance['zone'] ) ? $instance['zone'] : 1; - - // --- widget options form --- - echo ' -

    - -

    - -

    - -
    - ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - -
    - ' . esc_html( __( 'Display day with clock times.', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - -
    - ' . esc_html( __( 'Display month with clock times.', 'radio-station' ) ) . ' -

    - -

    - -

    '; - - } - - // --- update widget instance --- - public function update( $new_instance, $old_instance ) { - - $instance = $old_instance; - $instance['title'] = $new_instance['title']; - - // --- update widget options --- - $instance['time'] = isset( $new_instance['time'] ) ? $new_instance['time'] : 12; - $instance['seconds'] = isset( $new_instance['seconds'] ) ? 1 : 0; - $instance['day'] = isset( $new_instance['day'] ) ? $new_instance['day'] : 'full'; - $instance['date'] = isset( $new_instance['date'] ) ? 1 : 0; - $instance['month'] = isset( $new_instance['month'] ) ? $new_instance['month'] : 'full'; - $instance['zone'] = isset( $new_instance['zone'] ) ? 1 : 0; - - return $instance; - } - - // --- output widget display --- - public function widget( $args, $instance ) { - - global $radio_station_data; - - // --- set widget id --- - // 2.3.3.9: added unique widget id - if ( !isset( $radio_station_data['widgets']['clock'] ) ) { - $id = $radio_station_data['widgets']['clock'] = 0; - } else { - $id = $radio_station_data['widgets']['clock']++; - } - - // 2.3.0: added hide widget if empty option - $title = empty( $instance['title'] ) ? '' : $instance['title']; - $title = apply_filters( 'widget_title', $title ); - - $time = $instance['time']; - $seconds = $instance['seconds']; - $day = $instance['day']; - $date = $instance['date']; - $zone = $instance['zone']; - $month = $instance['month']; - - // --- set shortcode attributes for display --- - $atts = array( - 'time' => $time, - 'seconds' => $seconds, - 'day' => $day, - 'date' => $date, - 'month' => $month, - 'zone' => $zone, - 'widget' => 1, - ); - - // 2.3.3.9: add missing filter for clock widget attributes - $atts = apply_filters( 'radio_station_clock_widget_atts', $atts, $instance ); - - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add instance id and class to widget container - echo '
    '; - - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - echo '
    '; - - // --- get default display output --- - $output = radio_station_clock_shortcode( $atts ); - - // --- check for widget output override --- - $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); - - // --- output widget display --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - - // --- close widget contents --- - echo '
    '; - - // --- close widget container --- - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.4.0.4: fix to load shortcode stylesheet - radio_station_enqueue_style( 'shortcodes' ); - - } -} - -// --- register the widget --- -add_action( 'widgets_init', 'radio_station_register_radio_clock_widget' ); -function radio_station_register_radio_clock_widget() { - register_widget( 'Radio_Clock_Widget' ); -} diff --git a/includes/class-radio-player-widget.php b/includes/class-radio-player-widget.php deleted file mode 100644 index 2203e3b..0000000 --- a/includes/class-radio-player-widget.php +++ /dev/null @@ -1,324 +0,0 @@ - 'Radio_Player_Widget', - 'description' => __( 'Radio Station Stream Player.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Stream Player', 'radio-station' ); - parent::__construct( 'Radio_Player_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - $url = isset( $instance['url'] ) ? $instance['url'] : ''; - // $format = isset( $instance['format'] ) ? $instance['format'] : ''; - $title = isset( $instance['title'] ) ? $instance['title'] : ''; - $station = isset( $instance['station'] ) ? $instance['station'] : ''; - $image = isset( $instance['image'] ) ? $instance['image'] : ''; - $script = isset( $instance['script'] ) ? $instance['script'] : 'default'; - $layout = isset( $instance['layout'] ) ? $instance['layout'] : 'vertical'; - $theme = isset( $instance['theme'] ) ? $instance['theme'] : 'default'; - $buttons = isset( $instance['buttons'] ) ? $instance['buttons'] : 'default'; - $volume = isset( $instance['volume'] ) ? $instance['volume'] : ''; - $default = isset( $instance['default'] ) ? $instance['default'] : 0; - - // --- additional displays --- - // $shows = isset( $instance['show_display'] ) ? $instance['show_display'] : false; - // $hosts = isset( $instance['show_hosts'] ) ? $instance['show_hosts'] : false; - // $producers = isset( $instance['show_producers'] ) ? $instance['show_producers'] : false; - - // --- stream or file URL --- - $fields = ' -

    -
    - ' . esc_html( 'Note: Leave blank to use default stream URL.', 'radio-station' ) . ' -

    ' . PHP_EOL; - - // --- widget title --- - $fields .= ' -

    - -

    ' . PHP_EOL; - - // --- station text --- - $fields .= ' -

    -
    - (' . esc_html( 'Empty for default, 0 for none', 'radio-station' ) . ') -

    ' . PHP_EOL; - - // --- station image --- - $fields .= '

    -

    ' . PHP_EOL; - - // --- player script --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player layout --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player theme --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player buttons --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player volume --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- player default instance --- - $fields .= '

    - -

    ' . PHP_EOL; - - // --- filter and output --- - $fields = apply_filters( 'radio_station_player_widget_fields', $fields, $this, $instance ); - echo $fields; - } - - // --- update widget instance values --- - public function update( $new_instance, $old_instance ) { - - // --- get new widget options --- - $instance = $old_instance; - $instance['url'] = isset( $new_instance['url'] ) ? $new_instance['url'] : ''; - $instance['title'] = isset( $new_instance['title'] ) ? $new_instance['title'] : ''; - $instance['station'] = isset( $new_instance['station'] ) ? $new_instance['station'] : ''; - $instance['image'] = isset( $new_instance['image'] ) ? $new_instance['image'] : $old_instance['image']; - $instance['script'] = isset( $new_instance['script'] ) ? $new_instance['script'] : $old_instance['script']; - $instance['layout'] = isset( $new_instance['layout'] ) ? $new_instance['layout'] : $old_instance['layout']; - $instance['theme'] = isset( $new_instance['theme'] ) ? $new_instance['theme'] : $old_instance['theme']; - $instance['buttons'] = isset( $new_instance['buttons'] ) ? $new_instance['buttons'] : $old_instance['buttons']; - $instance['volume'] = isset( $new_instance['volume'] ) ? $new_instance['volume'] : $old_instance['volume']; - if ( '' != $instance['volume'] ) { - $instance['volume'] = absint( $instance['volume'] ); - if ( $instance['volume'] > 100 ) { - $instance['volume'] = 100; - } elseif ( $instance['volume'] < 0 ) { - $instance['volume'] = 0; - } - } - $instance['default'] = isset( $new_instance['default'] ) ? 1 : 0;; - - // --- additional displays --- - // $instance['show_display'] = isset( $new_instance['show_display'] ) ? 1 : 0; - // $instance['show_hosts'] = isset( $new_instance['show_hosts'] ) ? 1 : 0; - // $instance['show_producers'] = isset( $new_instance['show_producers'] ) ? 1 : 0; - - // --- filter and return --- - $instance = apply_filters( 'radio_station_player_widget_update', $instance, $new_instance, $old_instance ); - return $instance; - } - - // --- widget output --- - public function widget( $args, $instance ) { - - global $radio_station_data; - - // --- set widget id --- - if ( !isset( $radio_station_data['widgets']['player'] ) ) { - $id = $radio_station_data['widgets']['player'] = 1; - } else { - $id = $radio_station_data['widgets']['player']++; - } - - // --- get widget options --- - $url = $instance['url']; - $title = empty( $instance['title'] ) ? '' : $instance['title']; - $title = apply_filters( 'widget_title', $title ); - $station = $instance['station']; - $image = $instance['image']; - if ( 'on' == $image ) { - $image = 1; - } elseif ( 'off' == $image ) { - $image = 0; - } - $script = $instance['script']; - $layout = $instance['layout']; - $theme = $instance['theme']; - $buttons = $instance['buttons']; - $volume = $instance['volume']; - if ( !$volume ) { - $volume = 'default'; - } - $default = $instance['default']; - - // --- additional displays --- - // $instance['show_display'] = $instance['show_display'] ) ? 1 : 0; - // $instance['show_hosts'] = $instance['show_hosts']; - // $instance['show_producers'] = $instance['show_producers']; - - // --- set shortcode attributes --- - // note: station text mapped to shortcode title attribute - $atts = array( - // --- main player settings --- - 'url' => $url, - 'title' => $station, - 'image' => $image, - 'script' => $script, - 'layout' => $layout, - 'theme' => $theme, - 'buttons' => $buttons, - 'volume' => $volume, - 'default' => $default, - // --- additional displays - // 'shows' => $shows, - // 'hosts' => $hosts, - // 'producers' => $producers, - // --- widget data --- - // 2.4.0.1: prefix widget player ID - 'widget' => 1, - 'id' => $id, - ); - - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - $id = 'radio-player-widget-' . $id; - echo '
    '; - - // --- widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // --- get default display output --- - $output = radio_station_player_shortcode( $atts ); - - // --- check for widget output override --- - $output = apply_filters( 'radio_station_player_widget_override', $output, $args, $atts ); - - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - } -} - -// ---------------------- -// Register Player Widget -// ---------------------- -add_action( 'widgets_init', 'radio_station_register_player_widget' ); -function radio_station_register_player_widget() { - register_widget( 'Radio_Player_Widget' ); -} diff --git a/includes/class-upcoming-shows-widget.php b/includes/class-upcoming-shows-widget.php deleted file mode 100644 index af3b2ed..0000000 --- a/includes/class-upcoming-shows-widget.php +++ /dev/null @@ -1,327 +0,0 @@ - 'DJ_Upcoming_Widget', - 'description' => __( 'Display the upcoming Shows.', 'radio-station' ), - ); - $widget_display_name = __( '(Radio Station) Upcoming Shows', 'radio-station' ); - parent::__construct( 'DJ_Upcoming_Widget', $widget_display_name, $widget_ops ); - } - - // --- widget instance form --- - public function form( $instance ) { - - $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); - - // 2.3.3: set time format default to empty (plugin setting) - $title = $instance['title']; - $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; - $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; - $default = isset( $instance['default'] ) ? $instance['default'] : ''; - $link = isset( $instance['link'] ) ? $instance['link'] : false; - $limit = isset( $instance['limit'] ) ? $instance['limit'] : 1; - $time = isset( $instance['time'] ) ? $instance['time'] : ''; - $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; - - // 2.2.4: added title position, avatar width and DJ link options - // 2.3.0: added countdown field option - // 2.3.2: added AJAX load option - $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; - $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - $show_encore = isset( $instance['show_encore'] ) ? $instance['show_encore'] : true; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : ''; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; - - // 2.3.0: convert template style code to straight php echo - // 2.3.2: added AJAX load option field - $fields = ' -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - -

    - - ' . esc_html( __( 'Width of Show Avatars (in pixels, default 75px)', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - -

    - -

    - - ' . esc_html( __( 'If no Show is scheduled for the current time, display this text.', 'radio-station' ) ) . ' -

    - -

    - -

    - -

    - - ' . esc_html( __( 'Number of upcoming Shows to display.', 'radio-station' ) ) . ' -

    - -

    - -
    - ' . esc_html( __( 'Choose time format for displayed schedules.', 'radio-station' ) ) . ' -

    - -

    - -

    '; - - // --- filter and output --- - $fields = apply_filters( 'radio_station_upcoming_shows_widget_fields', $fields, $this, $instance ); - echo $fields; - } - - // --- update widget instance --- - public function update( $new_instance, $old_instance ) { - - $instance = $old_instance; - - $instance['title'] = $new_instance['title']; - $instance['display_djs'] = isset( $new_instance['display_djs'] ) ? 1 : 0; - $instance['djavatar'] = isset( $new_instance['djavatar'] ) ? 1 : 0; - $instance['link'] = isset( $new_instance['link'] ) ? 1 : 0; - $instance['default'] = $new_instance['default']; - $instance['limit'] = $new_instance['limit']; - $instance['time'] = $new_instance['time']; - $instance['show_sched'] = isset( $new_instance['show_sched'] ) ? 1 : 0; - - // 2.2.4: added title position, avatar width and DJ link settings - // 2.3.0: added countdown display option - // 2.3.2: added AJAX load option - $instance['title_position'] = $new_instance['title_position']; - $instance['avatar_width'] = $new_instance['avatar_width']; - $instance['link_djs'] = isset( $new_instance['link_djs'] ) ? 1 : 0; - $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; - $instance['countdown'] = isset( $new_instance['countdown'] ) ? 1 : 0; - $instance['ajax'] = isset( $new_instance['ajax'] ) ? $new_instance['ajax'] : 0; - - // 2.3.0: added widget filter instance to update - $instance = apply_filters( 'radio_station_upcoming_shows_widget_update', $instance, $new_instance, $old_instance ); - return $instance; - } - - // --- output widget display --- - public function widget( $args, $instance ) { - - global $radio_station_data; - - // --- set widget id --- - // 2.3.0: added unique widget id - if ( !isset( $radio_station_data['widgets']['upcoming-shows'] ) ) { - $id = $radio_station_data['widgets']['upcoming-shows'] = 0; - } else { - $id = $radio_station_data['widgets']['upcoming-shows']++; - } - - // 2.3.0: filter widget_title whether empty or not - $title = empty( $instance['title'] ) ? '' : $instance['title']; - $title = apply_filters( 'widget_title', $title ); - $display_djs = $instance['display_djs']; - $djavatar = $instance['djavatar']; - $link = $instance['link']; - $default = empty( $instance['default'] ) ? '' : $instance['default']; - $limit = empty( $instance['limit'] ) ? '1' : $instance['limit']; - $time = empty( $instance['time'] ) ? '' : $instance['time']; - $show_sched = $instance['show_sched']; - - // 2.2.4: added title position, avatar width and DJ link settings - // 2.3.0: added countdown display option - // 2.3.2: added AJAX load option - $position = empty( $instance['title_position'] ) ? 'right' : $instance['title_position']; - $width = empty( $instance['avatar_width'] ) ? '75' : $instance['avatar_width']; - $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - $encore = isset( $instance['encore'] ) ? $instnace['encore'] : 0; - $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; - - // --- set shortcode attributes --- - // 2.3.0: map widget options to shortcode attributes - // 2.3.2: added AJAX load option - $atts = array( - // --- legacy widget options --- - 'title' => $title, - 'display_djs' => $display_djs, - 'show_avatar' => $djavatar, - 'show_link' => $link, - 'default' => $default, - 'limit' => $limit, - 'time' => $time, - 'show_sched' => $show_sched, - // --- new widget options --- - // 'show_playlist' => $show_playlist, - // 'show_desc' => $show_desc, - 'title_position' => $position, - 'avatar_width' => $width, - 'link_djs' => $link_djs, - 'show_encore' => $encore, - 'countdown' => $countdown, - 'widget' => 1, - 'id' => $id, - ); - - // 2.3.2: only set AJAX attribute if overriding default - if ( in_array( $ajax, array( 'on', 'off' ) ) ) { - $atts['ajax'] = $ajax; - } - - // 2.3.3.9: add filter for default widget attributes - $atts = apply_filters( 'radio_station_upcoming_shows_widget_atts', $atts, $instance ); - - // --- before widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_widget']; - - // --- open widget container --- - // 2.3.0: add unique id to widget - // 2.3.2: add class to widget - // 2.4.0.1: add upcoming-shows-widget class - echo '
    '; - - // --- output widget title --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['before_title']; - if ( !empty( $title ) ) { - echo esc_html( $title ); - } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_title']; - - // 2.3.3.9: add div wrapper for widget contents - echo '
    '; - - // --- get default display output --- - // 2.3.0: use shortcode to generate default widget output - $output = radio_station_upcoming_shows_shortcode( $atts ); - - // --- check for widget output override --- - // 2.3.0: added this override filter - $output = apply_filters( 'radio_station_upcoming_shows_widget_override', $output, $args, $atts ); - - // --- output widget display --- - if ( $output ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; - } - - // --- close widget contents wrapper --- - echo '
    '; - - // --- close widget container --- - echo '
    '; - - // --- after widget --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $args['after_widget']; - - // --- enqueue widget stylesheet in footer --- - // (this means it will only load if widget is on page) - // 2.2.4: renamed djonair.css to widgets.css and load for all widgets - // 2.3.0: widgets.css merged into rs-shortcodes.css - // 2.3.0: use abstracted method for enqueueing widget styles - radio_station_enqueue_style( 'shortcodes' ); - - } -} - - -// --- register the widget --- -// 2.2.7: revert anonymous function usage for backwards compatibility -add_action( 'widgets_init', 'radio_station_register_upcoming_shows_widget' ); -function radio_station_register_upcoming_shows_widget() { - // note: widget class name to remain unchanged for backwards compatibility - register_widget( 'DJ_Upcoming_Widget' ); -} diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 5a4d317..9394be2 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -87,7 +87,8 @@ function radio_station_api_discovery_link() { $link = apply_filters( 'radio_station_api_discovery_link', $link ); if ( $link ) { // 2.5.0: sanitize with wp_kses and allowed HTML - $allowed_html = radio_station_allowed_html( 'link' ); + // 2.5.6: change to link context rather than type + $allowed_html = radio_station_allowed_html( 'content', 'link' ); echo wp_kses( $link, $allowed_html ); } } @@ -370,7 +371,7 @@ function radio_station_get_languages_data( $language = false ) { $terms = get_terms( $args ); if ( count( $terms ) > 0 ) { - $all_langs = radio_station_get_languages(); + // $all_langs = radio_station_get_languages(); foreach ( $terms as $term ) { $languages_data[$term->slug] = array( 'id' => $term->term_id, @@ -504,10 +505,10 @@ function radio_station_schedule_endpoint() { // 2.5.0: added sanitize_text_field $weekday = sanitize_text_field( $_GET['weekday'] ); if ( strstr( $weekday, ',' ) ) { - $multiple = true; + // $multiple = true; $weekdays = explode( ',', $weekday ); } else { - $singular = true; + // $singular = true; $weekdays = array( $weekday ); } @@ -535,7 +536,7 @@ function radio_station_schedule_endpoint() { } elseif ( isset( $_GET['date'] ) ) { - // TODO: get schedule for specific date ? + // TODO: get schedule for specified date ? } else { @@ -999,7 +1000,7 @@ function radio_station_route_languages( $request ) { // Add Feed // -------- // (modified version of WordPress add_feed function) -function radio_station_add_feed( $feedname, $function ) { +function radio_station_add_feed( $feedname, $function_name ) { // note: removed as this is overwriting normal page slugs... // so /feed/schedule/ overwrites /schedule/ - which is no good! @@ -1010,7 +1011,7 @@ function radio_station_add_feed( $feedname, $function ) { $hook = 'do_feed_' . $feedname; remove_action( $hook, $hook ); - add_action( $hook, $function, 10, 2 ); + add_action( $hook, $function_name, 10, 2 ); return $hook; } @@ -1139,11 +1140,11 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { foreach ( $radio['endpoints'] as $endpoint => $url ) { $key = '/' . $base . '/' . $endpoint; $routes[$key] = array( - 'namespace' => $base, - 'methods' => array( 'GET' ), - // 'endpoints' => array(), - // 'url' => $url, - '_links' => array( + 'namespace' => $base, + 'methods' => array( 'GET' ), + // 'endpoints' => array(), + // 'url' => $url, + '_links' => array( 'self' => $url, ), ); @@ -1360,7 +1361,7 @@ function radio_station_feed_filter_fix( $query ) { // --- override incorrect shows feed --- if ( isset( $query->query['feed'] ) && ( 'feed' == $query->query['feed'] ) && ( 'show' == $query->query['post_type'] ) ) { - if ( strstr( $_SERVER['REQUEST_URI'], '/shows/feed/' ) ) { + if ( strstr( filter_var( $_SERVER['REQUEST_URI'], FILTER_SANITIZE_URL ), '/shows/feed/' ) ) { // --- add host/producer nodes to RSS item output --- add_action( 'rss2_item', 'radio_station_feed_item_node_hosts' ); @@ -1380,8 +1381,7 @@ function radio_station_feed_filter_fix( $query ) { add_action( 'rss2_item', 'radio_station_feed_item_node_shows' ); } - - } + } // print_r( $query ); } @@ -1401,19 +1401,21 @@ function radio_station_feed_filter( $query ) { // --- get show by post slug --- global $wpdb; $q = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_name = %s AND post_type = %s"; - $q = $wpdb->prepare( $q, array( $show_id, RADIO_STATION_SHOW_SLUG ) ); - $show_id = $wpdb->get_var( $q ); + $show_id = $wpdb->get_var( $wpdb->prepare( $q, array( $show_id, RADIO_STATION_SHOW_SLUG ) ) ); } if ( $show_id ) { - $query->set( 'meta_query', array( + $query->set( + 'meta_query', array( - 'key' => 'post_showblog_id', - 'value' => $show_id, - 'compare' => 'EQUALS' + array( + 'key' => 'post_showblog_id', + 'value' => $show_id, + 'compare' => 'EQUALS' + ) ) - ) ); + ); } - } + } return $query; } @@ -1442,7 +1444,7 @@ function radio_station_feed_item_node_hosts() { $hosts = ''; $count = 0; if ( $host_ids && is_array( $host_ids ) && ( count( $host_ids ) > 0 ) ) { - $host_count = count( $hosts_ids ); + $host_count = count( $host_ids ); foreach ( $host_ids as $host ) { $count++; $user = get_user_by( 'ID', $host ); @@ -1498,12 +1500,12 @@ function radio_station_feed_item_node_producers() { // 2.3.0: 24 format shift for broadcast data endpoint function radio_station_convert_show_shift( $shift ) { - // note: timezone can be ignored here as just getting hours and minutes + // 2.5.6: use radio_station_get_time instead of date if ( isset( $shift['start'] ) ) { - $shift['start'] = date( 'H:i', strtotime( $shift['start'] ) ); + $shift['start'] = radio_station_get_time( 'H:i', strtotime( $shift['start'] ) ); } if ( isset( $shift['end'] ) ) { - $shift['end'] = date( 'H:i', strtotime( $shift['end'] ) ); + $shift['end'] = radio_station_get_time( 'H:i', strtotime( $shift['end'] ) ); } return $shift; } @@ -1524,9 +1526,9 @@ function radio_station_convert_show_shifts( $show ) { // 2.4.0.6: fix to undefined index warning for encore $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; $schedule[$i] = array( - 'day' => $shift['day'], - 'start' => $start_hour . ':' . $shift['start_min'], - 'end' => $end_hour . ':' . $shift['end_min'], + 'day' => $shift['day'], + 'start' => $start_hour . ':' . $shift['start_min'], + 'end' => $end_hour . ':' . $shift['end_min'], 'encore' => $encore, ); } @@ -1557,4 +1559,3 @@ function radio_station_convert_schedule_shifts( $schedule ) { } return $schedule; } - diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 0f4ded0..5cbf87c 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -89,7 +89,7 @@ __( 'Set %s Avatar Image', 'radio-station' ); __( 'Remove %s Avatar Image', 'radio-station' ); __( 'Are you sure you want to remove this image?', 'radio-station' ); -__( 'Select or Upload Image' ,'radio-station' ); +__( 'Select or Upload Image', 'radio-station' ); __( 'Use this Image', 'radio-station' ); __( 'Show', 'radio-station' ); __( 'Select Show', 'radio-station' ); @@ -115,7 +115,6 @@ __( 'The time that the Episode aired', 'radio-station' ); __( 'Episode Length', 'radio-station' ); __( 'minutees', 'radio-station' ); -__( '', 'radio-station' ); __( 'Linked Playlist', 'radio-station' ); __( 'Select Playlist', 'radio-station' ); __( 'No Playlists to Select.', 'radio-station' ); diff --git a/includes/legacy.php b/includes/legacy.php index c4eb5e7..7c7ef22 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -37,17 +37,18 @@ function radio_station_current_schedule( $scheds = array() ) { // 2.3.0: added check is shift is disabled if ( !isset( $sched['disabled'] ) || ( 'yes' != $sched['disabled'] ) ) { - if ( date( 'l', $now ) !== $sched['day'] ) { + // 2.5.6: use radio_station-get_time instead of date + if ( radio_station_get_time( 'l', $now ) !== $sched['day'] ) { continue; } - $start = strtotime( date( 'Y-m-d', $now ) . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] ); + $start = strtotime( radio_station_get_time( 'Y-m-d', $now ) . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian'] ); if ( 'pm' === $sched['start_meridian'] && 'am' === $sched['end_meridian'] ) { // check for shows that run overnight into the next morning - $end = strtotime( date( 'Y-m-d', ( $now + 86400 ) ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); + $end = strtotime( radio_station_get_time( 'Y-m-d', ( $now + 86400 ) ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); } else { - $end = strtotime( date( 'Y-m-d', $now ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); + $end = strtotime( radio_station_get_time( 'Y-m-d', $now ) . $sched['end_hour'] . ':' . $sched['end_min'] . ' ' . $sched['end_meridian'] ); } // a show cannot end before it begins... if it does, it ends the following day. @@ -75,8 +76,8 @@ function radio_station_convert_time( $time = array() ) { } $now = strtotime( current_time( 'mysql' ) ); - $cur_date = date( 'Y-m-d', $now ); - $tom_date = date( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow + $cur_date = radio_station_get_time( 'Y-m-d', $now ); + $tom_date = radio_station_get_time( 'Y-m-d', ( $now + 86400 ) ); // get the date for tomorrow // --- convert to 24 hour time --- $time = radio_station_convert_schedule_to_24hour( $time ); @@ -148,7 +149,7 @@ function radio_station_dj_get_current() { // --- get the current time and day --- $now = strtotime( current_time( 'mysql' ) ); - $cur_day = date( 'l', $now ); + $cur_day = radio_station_get_time( 'l', $now ); // --- query for active shows only --- $show_shifts = $wpdb->get_results( @@ -191,7 +192,7 @@ function radio_station_dj_get_current() { } // we need to make a special allowance for shows that run from one day into the next - if ( date( 'w', strtotime( $time['day'] ) ) + 1 == date( 'w', strtotime( $cur_day ) ) ) { + if ( radio_station_get_time( 'w', strtotime( $time['day'] ) ) + 1 == radio_station_get_time( 'w', strtotime( $cur_day ) ) ) { $time = radio_station_convert_time( $time ); // because station_convert_time assumes that the show STARTS on the current day, @@ -224,9 +225,9 @@ function radio_station_dj_get_next( $limit = 1 ) { global $wpdb; // get the various times/dates we need - $cur_day = date( 'l', strtotime( current_time( 'mysql' ) ) ); - $cur_day_num = date( 'N', strtotime( current_time( 'mysql' ) ) ); - $cur_date = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); + $cur_day = radio_station_get_time( 'l', strtotime( current_time( 'mysql' ) ) ); + $cur_day_num = radio_station_get_time( 'N', strtotime( current_time( 'mysql' ) ) ); + $cur_date = radio_station_get_time( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ); $now = strtotime( current_time( 'mysql' ) ); $shows = array(); @@ -352,7 +353,7 @@ function array_replace() { $array = array(); $n = func_num_args(); - while ( $n -- > 0 ) { + while ( $n-- > 0 ) { $array += func_get_arg( $n ); } @@ -457,9 +458,9 @@ function radio_station_get_now_playing( $time = false ) { 'post_status' => 'publish', 'meta_query' => array( array( - 'key' => 'playlist_show_id', - 'value' => $show_id, - 'compare' => '=', + 'key' => 'playlist_show_id', + 'value' => $show_id, + 'compare' => '=', ), ), ); @@ -478,6 +479,7 @@ function radio_station_get_now_playing( $time = false ) { $shift_id = get_post_meta( $playlist_post->ID, 'playlist_shift_id', true ); if ( $shift_id == $current_show['id'] ) { $playlist_id = $playlist_post->ID; + $found_playlist_post = $playlist_post; $found = true; } } @@ -485,6 +487,7 @@ function radio_station_get_now_playing( $time = false ) { if ( !$found ) { // --- if not found use most recently published show playlist --- $playlist_id = $playlist_posts[0]->ID; + $found_playlist_post = $playlist_posts[0]; } // --- fetch the tracks for the playlist --- @@ -518,11 +521,12 @@ function radio_station_get_now_playing( $time = false ) { // --- add show and playlist data --- // 2.3.0: add IDs and URLs instead of just playlist URL // 2.5.0: add playlist title to playlist array + // 2.5.6: fix possible mismatch to found_playlist_post $playlist['show'] = $show_id; $playlist['show_url'] = get_permalink( $show_id ); - $playlist['title'] = $playlist_post->post_title; - $playlist['playlist'] = $playlist_post->ID; - $playlist['playlist_url'] = get_permalink( $playlist_post->ID ); + $playlist['title'] = $found_playlist_post->post_title; + $playlist['playlist'] = $found_playlist_post->ID; + $playlist['playlist_url'] = get_permalink( $found_playlist_post->ID ); } } @@ -544,19 +548,12 @@ function radio_station_master_get_overrides( $currenthour = false, $date = false $now = strtotime( current_time( 'mysql' ) ); // 2.3.0: check if date argument supplied if ( !$date ) { - $date = date( 'Y-m-d', $now ); + $date = radio_station_get_time( 'Y-m-d', $now ); } $sql_date = $wpdb->esc_like( $date ); $sql_date = '%' . $sql_date . '%'; - $show_shifts = $wpdb->get_results( - $wpdb->prepare( - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta_key = 'show_override_sched' AND - meta_value LIKE %s", - $sql_date - ) - ); + $query = "SELECT meta.post_id FROM " . $wpdb->postmeta . " AS meta WHERE meta_key = 'show_override_sched' AND meta_value LIKE %s"; + $show_shifts = $wpdb->get_results( $wpdb->prepare( $query, $sql_date ) ); $scheds = array(); if ( $show_shifts ) { @@ -568,7 +565,7 @@ function radio_station_master_get_overrides( $currenthour = false, $date = false if ( $currenthour ) { // --- convert to 24 hour time --- - $check = $time; + // $check = $time; $time = radio_station_convert_time( $time ); // --- compare to the current timestamp --- @@ -609,15 +606,8 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = return false; } - $fetch_posts = $wpdb->get_results( - $wpdb->prepare( - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = 'post_showblog_id' AND - meta.meta_value = %d", - $show_id - ) - ); + $query = "SELECT post_id FROM " . $wpdb->postmeta . " WHERE meta_key = 'post_showblog_id' AND meta_value = %d"; + $fetch_posts = $wpdb->get_results( $wpdb->prepare( $query, $show_id ) ); $blog_array = array(); $blogposts = array(); @@ -629,18 +619,14 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = // 2.2.8: fix to implode blog array to string // 2.3.0: allow for getting without limit - $query = $wpdb->prepare( - "SELECT posts.ID, posts.post_title - FROM {$wpdb->posts} AS posts - WHERE posts.ID IN(%s) AND - posts.post_status = 'publish' - ORDER BY posts.post_date DESC", - implode( ',', $blog_array ) - ); + $query = "SELECT ID, post_title FROM " . $wpdb->posts . " WHERE ID IN(%s) AND post_status = 'publish' ORDER BY post_date DESC"; if ( $limit > 0 ) { - $query .= $wpdb->prepare( " LIMIT %d", $limit ); + $query .= " LIMIT %d"; + $blogposts = $wpdb->get_results( $wpdb->prepare( $query, array( implode( ',', $blog_array ), $limit ) ) ); + } else { + $blogposts = $wpdb->get_results( $wpdb->prepare( $query, implode( ',', $blog_array ) ) ); } - $blogposts = $wpdb->get_results( $query ); + } $output = ''; @@ -655,13 +641,8 @@ function radio_station_myplaylist_get_posts_for_show( $show_id = null, $title = $output .= ''; // if the blog archive page has been created, add a link to the archive for this show - $page = $wpdb->get_results( - "SELECT meta.post_id - FROM {$wpdb->postmeta} AS meta - WHERE meta.meta_key = '_wp_page_template' AND - meta.meta_value = 'show-blog-archive-template.php' - LIMIT 1" - ); + $query = "SELECT post_id FROM " . $wpdb->postmeta . " WHERE meta.meta_key = '_wp_page_template' AND meta.meta_value = 'show-blog-archive-template.php' LIMIT 1"; + $page = $wpdb->get_results( $query ); if ( $page ) { $blog_archive = get_permalink( $page[0]->post_id ); @@ -680,10 +661,8 @@ function radio_station_shorten_string( $string, $limit ) { $shortened = $string; $array = explode( ' ', $string ); - if ( count( $array ) <= $limit ) { - // --- already at or under the limit --- - $shortened = $string; - } else { + // 2.5.6: remove unnecessary logic condition + if ( count( $array ) > $limit ) { // --- over the word limit so trim --- array_splice( $array, $limit ); $shortened = implode( ' ', $array ) . ' ...'; diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 493bdc5..923da9a 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -22,7 +22,7 @@ add_shortcode( 'radio-schedule', 'radio_station_master_schedule' ); add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { - + global $radio_station_data; // 2.5.0: maybe set schedule instances array @@ -77,7 +77,7 @@ function radio_station_master_schedule( $atts ) { $atts['clock'] = 0; $atts['timezone'] = 0; } - } + } if ( RADIO_STATION_DEBUG ) { echo 'Master Schedule Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; @@ -117,7 +117,7 @@ function radio_station_master_schedule( $atts ) { 'active_date' => false, 'display_day' => 'short', 'display_date' => 'jS', - 'display_month' => 'short', + 'display_month' => 'short', 'time_format' => $time_format, // --- show display options --- @@ -290,7 +290,8 @@ function radio_station_master_schedule( $atts ) { // --- hidden inputs for calendar start/active dates --- // 2.3.3.9: added for schedule week change reloading if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { - if ( date( 'Y-m-d', strtotime( $atts['start_date'] ) ) == $atts['start_date'] ) { + // 2.5.6: use radio_station_get_time instead of date + if ( radio_station_get_time( 'Y-m-d', strtotime( $atts['start_date'] ) ) == $atts['start_date'] ) { $start_date = $atts['start_date']; } } @@ -301,7 +302,8 @@ function radio_station_master_schedule( $atts ) { $output .= ''; $active_date = $start_date; if ( isset( $atts['active_date'] ) && $atts['active_date'] ) { - if ( $atts['active_date'] == date( 'Y-m-d', strtotime( $atts['active_date'] ) ) ) { + // 2.5.6: use radio_station_get_time instead of date + if ( $atts['active_date'] == radio_station_get_time( 'Y-m-d', strtotime( $atts['active_date'] ) ) ) { $active_date = $atts['active_date']; } } @@ -345,6 +347,8 @@ function radio_station_master_schedule( $atts ) { } $radio_station_data['schedules']['table']++; $instance = $radio_station_data['schedules']['table']; + // 2.5.6: fix for missing id definition + $id = ( 0 == $instance ) ? '' : '-' . $instance; // --- load table view template --- ob_start(); @@ -375,6 +379,8 @@ function radio_station_master_schedule( $atts ) { } $radio_station_data['schedules']['tabs']++; $instance = $radio_station_data['schedules']['tabs']; + // 2.5.6: fix for missing id definition + $id = ( 0 == $instance ) ? '' : '-' . $instance; // --- load tabs view template --- ob_start(); @@ -405,6 +411,8 @@ function radio_station_master_schedule( $atts ) { } $radio_station_data['schedules']['list']++; $instance = $radio_station_data['schedules']['list']; + // 2.5.6: fix for missing id definition + $id = ( 0 == $instance ) ? '' : '-' . $instance; // --- load list view template --- ob_start(); @@ -457,12 +465,12 @@ function radio_station_master_schedule( $atts ) { unset( $days_of_the_week[$i] ); $days_of_the_week[$i] = $add; } - $start_of_week --; + $start_of_week--; } // --- create the master_list array based on the start of the week --- $master_list = array(); - for ( $i = 0; $i < 24; $i ++ ) { + for ( $i = 0; $i < 24; $i++ ) { $master_list[$i] = $days_of_the_week; } @@ -654,7 +662,7 @@ function radio_station_master_schedule_loader_js( $atts ) { function radio_station_ajax_schedule_loader() { // --- maybe clear cached data --- - if ( isset( $_REQUEST['clear'] ) && ( '1' == sanitize_title( $_REQUEST['clear'] ) ) ) { + if ( isset( $_REQUEST['clear'] ) && ( '1' === sanitize_text_field( $_REQUEST['clear'] ) ) ) { radio_station_clear_cached_data( false ); } @@ -665,12 +673,13 @@ function radio_station_ajax_schedule_loader() { $debug = true; $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); if ( RADIO_STATION_DEBUG || $debug ) { - echo "Full Request Inputs: " . esc_html( print_r( $_REQUEST, true ) ); + echo "Full Request Inputs: " . esc_html( print_r( array_map( 'sanitize_text_field', $_REQUEST ), true ) ); echo "Sanitized Master Schedule Shortcode Attributes: " . esc_html( print_r( $atts, true ) ); } // --- output schedule contents --- // 2.5.0: remove unused schedule contents wrap + // TODO: test wp_kses on master schedule output echo radio_station_master_schedule( $atts ); $js = ''; @@ -784,6 +793,7 @@ function radio_station_ajax_schedule_loader() { // --- filter load script and output --- $js = apply_filters( 'radio_station_master_schedule_load_script', $js, $atts ); if ( '' != $js ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo ""; } @@ -798,12 +808,16 @@ function radio_station_ajax_schedule_loader() { function radio_station_master_schedule_genre_selector( $instances ) { // --- get genres --- + // 2.5.6: add taxonomy to get_terms arguments $args = array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC', ); - $genres = get_terms( RADIO_STATION_GENRES_SLUG, $args ); + // 2.5.6: remove deprecated second argument from get_terms + // $genres = get_terms( RADIO_STATION_GENRES_SLUG, $args ); + $genres = get_terms( $args ); // 2.3.2: bug out if there are no genre terms if ( !$genres || !is_array( $genres ) ) { return ''; @@ -1407,4 +1421,3 @@ classes = jQuery(this).attr('class').split(/\s+/); $js = apply_filters( 'radio_station_master_schedule_list_js', $js ); return $js; } - diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index c69cb89..c3bc2f0 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -320,8 +320,10 @@ function radio_remove_language(term) { }"; // 2.3.3.9: added language edit styles filter // 2.5.0: use wp_kses_post on CSS output + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_language_edit_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); // --- add script inline --- // 2.3.3.9: added language edit script filter @@ -341,7 +343,7 @@ function radio_station_language_term_filter( $post_id ) { if ( !isset( $_POST[RADIO_STATION_LANGUAGES_SLUG] ) ) { return; } - $check = wp_verify_nonce( $_POST['taxonomy_noncename'], 'taxonomy_' . RADIO_STATION_LANGUAGES_SLUG ); + $check = wp_verify_nonce( sanitize_text_field( $_POST['taxonomy_noncename'] ), 'taxonomy_' . RADIO_STATION_LANGUAGES_SLUG ); if ( !$check ) { return; } @@ -351,7 +353,8 @@ function radio_station_language_term_filter( $post_id ) { } // --- loop and set posted terms --- - $terms = $_POST[RADIO_STATION_LANGUAGES_SLUG]; + // 2.5.6: use array_map with sanitize_text_field on array + $terms = array_map( 'sanitize_text_field', $_POST[RADIO_STATION_LANGUAGES_SLUG] ); $term_ids = array(); if ( is_array( $terms ) && ( count( $terms ) > 0 ) ) { @@ -359,7 +362,7 @@ function radio_station_language_term_filter( $post_id ) { foreach ( $terms as $i => $term_slug ) { // 2.5.0: use sanitize_text_field on posted value - $term_slug = sanitize_text_field( $term_slug ); + // $term_slug = sanitize_text_field( $term_slug ); foreach ( $languages as $j => $language ) { @@ -578,7 +581,6 @@ function radio_station_show_info_metabox() { // echo ''; // 2.3.2: remove class="hndle" to prevent box sorting - $widget_title = $metabox['title']; echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . "\n"; echo '
    ' . "\n"; call_user_func( $metabox['callback'] ); @@ -607,8 +609,10 @@ function radio_station_show_info_metabox() { // --- filter and output styles --- // 2.3.3.9: added edit styles filter // 2.5.0: added wp_kses_post to CSS output + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_show_edit_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); } // ------------------------------ @@ -634,7 +638,7 @@ function radio_station_add_show_hosts_metabox() { // ---------------------------- function radio_station_show_hosts_metabox() { - global $post, $wp_roles, $wpdb; + global $post; // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'show_hosts_nonce' ); @@ -720,7 +724,7 @@ function radio_station_add_show_producers_metabox() { // -------------------------------- function radio_station_show_producers_metabox() { - global $post, $wp_roles, $wpdb; + global $post; // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'show_producers_nonce' ); @@ -822,7 +826,7 @@ function radio_station_show_shifts_metabox() { echo '
    ' . "\n"; // 2.3.2: added to bypass shift check on add (for debugging) - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_text_field( $_REQUEST['check-bypass'] ) ) ) { echo '' . "\n"; } @@ -903,8 +907,10 @@ function radio_station_show_shifts_metabox() { // 2.3.2: added dashed border to new shift // 2.3.3.9: moved styles to separate function // 2.5.0: use wp_kses_post on styles output + // 2.5.6: use radio_station_add_inline_style $css = radio_station_shifts_list_styles(); - echo '' . "\n"; + // echo '' . "\n"; + radio_station_add_inline_style( 'rs-admin', $css ); // --- close meta inner --- echo '
    ' . "\n"; @@ -976,7 +982,7 @@ function radio_station_shift_edit_script() { // --- show shifts scripts --- // 2.3.0: added confirmation to remove shift button // 2.3.2: removed document ready functions wrapper - $c = 0; + // $c = 0; $confirm_remove = __( 'Are you sure you want to remove this shift?', 'radio-station' ); $confirm_clear = __( 'Are you sure you want to clear the shift list?', 'radio-station' ); // $js = "var count = " . esc_attr( $c ) . ";"; @@ -1280,8 +1286,6 @@ function radio_station_shifts_conflict_message( $hidden = false ) { // 2.3.2: separate shift table function (for AJAX saving) function radio_station_show_shifts_table( $post_id ) { - global $post; - // --- edit show link --- $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); @@ -1338,7 +1342,8 @@ function radio_station_show_shifts_table( $post_id ) { } } if ( isset( $_REQUEST['start_hour'] ) ) { - $start_hour = absint( $_REQUEST['start_hour'] ); + // 2.5.6: fix to key instead of variable + $times['start_hour'] = absint( $_REQUEST['start_hour'] ); if ( ( $times['start_hour'] < 1 ) || ( $times['start_hour'] > 12 ) ) { $times['start_hour'] = 12; } @@ -1389,7 +1394,7 @@ function radio_station_show_shifts_table( $post_id ) { } } if ( isset( $_REQUEST['disabled'] ) ) { - $times['disabled'] = $_REQUEST['disabled']; + $times['disabled'] = sanitize_text_field( $_REQUEST['disabled'] ); if ( !in_array( $times['disabled'], array( '', 'yes' ) ) ) { $times['disabled'] = ''; } @@ -1760,7 +1765,7 @@ function radio_station_show_images_metabox() { global $post; - if ( isset( $_GET['avatar_refix'] ) && ( 'yes' == $_GET['avatar_refix'] ) ) { + if ( isset( $_GET['avatar_refix'] ) && ( 'yes' == sanitize_text_field( $_GET['avatar_refix'] ) ) ) { delete_post_meta( $post->ID, '_rs_image_updated', true ); $show_avatar = radio_station_get_show_avatar_id( $post->ID ); echo "Transferred ID: " . $show_avatar; @@ -1952,7 +1957,7 @@ function radio_station_show_images_save() { } // --- verify nonce value --- - if ( !isset( $_GET['nonce'] ) || !wp_verify_nonce( $_GET['nonce'], 'show-images-autosave' ) ) { + if ( !isset( $_GET['nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_GET['nonce'] ), 'show-images-autosave' ) ) { exit; } @@ -1970,8 +1975,8 @@ function radio_station_show_images_save() { // --- get image type --- if ( isset( $_GET['image_type'] ) ) { - if ( in_array( $_GET['image_type'], array( 'header', 'avatar' ) ) ) { - $image_type = $_GET['image_type']; + if ( in_array( sanitize_text_field( $_GET['image_type'] ), array( 'header', 'avatar' ) ) ) { + $image_type = sanitize_text_field( $_GET['image_type'] ); } } @@ -2017,14 +2022,14 @@ function radio_station_show_save_data( $post_id ) { } // 2.3.3: added double check for AJAX action match // 2.3.3.9: check for single or multiple shift save action - if ( 'radio_station_show_save_shift' == $_REQUEST['action'] ) { + if ( 'radio_station_show_save_shift' == sanitize_text_field( $_REQUEST['action'] ) ) { $selection = 'single'; - if ( preg_match( '/^[a-zA-Z0-9_]+$/', $_REQUEST['shift_id'] ) ) { - $shift_id = $_REQUEST['shift_id']; + if ( preg_match( '/^[a-zA-Z0-9_]+$/', sanitize_text_field( $_REQUEST['shift_id'] ) ) ) { + $shift_id = sanitize_text_field( $_REQUEST['shift_id'] ); } - } elseif ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { + } elseif ( 'radio_station_show_save_shifts' == sanitize_text_field( $_REQUEST['action'] ) ) { $selection = 'multiple'; - } elseif ( 'radio_station_add_show_shift' == $_REQUEST['action'] ) { + } elseif ( 'radio_station_add_show_shift' == sanitize_text_field( $_REQUEST['action'] ) ) { $selection = 'add'; } else { return; @@ -2034,9 +2039,9 @@ function radio_station_show_save_data( $post_id ) { $error = false; if ( !current_user_can( 'edit_shows' ) ) { $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['show_shifts_nonce'] ) || !wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { + } elseif ( !isset( $_POST['show_shifts_nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_POST['show_shifts_nonce'] ), 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['show_id'] ) || ( '' == $_POST['show_id'] ) ) { + } elseif ( !isset( $_POST['show_id'] ) || ( '' === sanitize_text_field( $_POST['show_id'] ) ) ) { $error = __( 'Error! No Show ID provided.', 'radio-station' ); } else { $post_id = absint( $_POST['show_id'] ); @@ -2064,7 +2069,7 @@ function radio_station_show_save_data( $post_id ) { // --- get posted DJ / host list --- // 2.2.7: check DJ post value is set - if ( isset( $_POST['show_hosts_nonce'] ) && wp_verify_nonce( $_POST['show_hosts_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_hosts_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_hosts_nonce'] ), 'radio-station' ) ) { // 2.3.3.9: user check moved to input sanitization function $hosts = radio_station_sanitize_input( 'show', 'user_list' ); @@ -2079,7 +2084,7 @@ function radio_station_show_save_data( $post_id ) { // --- get posted show producers --- // 2.3.0: added show producer sanitization - if ( isset( $_POST['show_producers_nonce'] ) && wp_verify_nonce( $_POST['show_producers_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_producers_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_producers_nonce'] ), 'radio-station' ) ) { // 2.3.3.9: user check moved to input sanitization function $producers = radio_station_sanitize_input( 'show', 'producer_list' ); @@ -2095,7 +2100,7 @@ function radio_station_show_save_data( $post_id ) { // --- save show meta data --- // 2.3.0: added separate nonce check for show meta - if ( isset( $_POST['show_meta_nonce'] ) && wp_verify_nonce( $_POST['show_meta_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_meta_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_meta_nonce'] ), 'radio-station' ) ) { // --- get the meta data to be saved --- // 2.3.2: added download disable switch @@ -2140,7 +2145,7 @@ function radio_station_show_save_data( $post_id ) { // --- update the show images --- - if ( isset( $_POST['show_images_nonce'] ) && wp_verify_nonce( $_POST['show_images_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_images_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_images_nonce'] ), 'radio-station' ) ) { // --- show header image --- if ( isset( $_POST['show_header'] ) ) { @@ -2154,7 +2159,7 @@ function radio_station_show_save_data( $post_id ) { // --- show avatar image --- // 2.5.0: delete post meta if removing avatar - if ( '' == $_POST['show_avatar'] ) { + if ( '' == sanitize_text_field( $_POST['show_avatar'] ) ) { delete_post_meta( $post_id, 'show_avatar' ); } else { $avatar = absint( $_POST['show_avatar'] ); @@ -2174,7 +2179,7 @@ function radio_station_show_save_data( $post_id ) { } // --- check show shift nonce --- - if ( isset( $_POST['show_shifts_nonce'] ) && wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_shifts_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_shifts_nonce'] ), 'radio-station' ) ) { // --- loop posted show shift times --- // 2.3.1: added check if any shifts are set (fix undefined index warning) @@ -2190,6 +2195,8 @@ function radio_station_show_save_data( $post_id ) { $shifts[$new_id] = $new_shift; $_POST['show_sched'] = $shifts; } else { + // TODO: test arrap_map and sanitize_text_field ? + // $shifts = array_map( 'sanitize_text_field', $_POST['show_sched ); $shifts = $_POST['show_sched']; } @@ -2360,6 +2367,8 @@ function radio_station_show_save_data( $post_id ) { if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { // --- get genre and language terms --- + // 2.5.6: set empty arry for genre and language term ids + $genre_term_ids = $language_term_ids = array(); $genre_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); if ( count( $genre_terms ) > 0 ) { foreach ( $genre_terms as $genre_term ) { @@ -2404,7 +2413,7 @@ function radio_station_show_save_data( $post_id ) { // 2.3.3.9: removed check of AJAX action as done earlier // --- debug information --- - echo "Posted Shifts: " . esc_html( print_r( $_POST['show_sched'], true ) ) . "\n"; + echo "Posted Shifts: " . esc_html( print_r( $shifts, true ) ) . "\n"; echo "New Shifts: " . esc_html( print_r( $new_shifts, true ) ) . "\n"; // --- display shifts saved message --- @@ -2424,7 +2433,7 @@ function radio_station_show_save_data( $post_id ) { // --- output new show shifts list --- echo '
    ' . "\n"; - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_text_field( $_REQUEST['check-bypass'] ) ) ) { echo '' . "\n"; } $table = radio_station_show_shifts_table( $post_id ); @@ -2496,7 +2505,7 @@ function radio_station_show_save_data( $post_id ) { // --- return early when adding single shift --- // 2.3.3.9: added for single shift action - if ( 'radio_station_add_show_shift' == $_REQUEST['action'] ) { + if ( 'radio_station_add_show_shift' == sanitize_text_field( $_REQUEST['action'] ) ) { return; } @@ -2847,9 +2856,10 @@ function radio_station_show_admin_list_styles() { .show-shift.disabled.conflict {border: 1px dashed red;}"; // 2.5.0: added missing style filter - // TODO: use wp_add_inline_style here ? + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_show_list_styles', $css ); - echo '' . "\n"; + // echo '' . "\n"; + radio_station_add_inline_style( 'rs-admin', $css ); } // ------------------------- @@ -3378,7 +3388,6 @@ function radio_station_override_show_metabox() { echo '>' . "\n"; // --- inside metabox contents --- - $widget_title = $metabox['title']; echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . "\n"; echo '
    ' . "\n"; call_user_func( $metabox['callback'] ); @@ -3404,9 +3413,10 @@ function radio_station_override_show_metabox() { // 2.3.3.9: filter // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style ? + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_override_edit_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); // --- get override show script --- $js = radio_station_override_show_script(); @@ -3556,9 +3566,10 @@ function radio_station_schedule_override_metabox() { // 2.3.3.9: apply class styles to override list // 2.3.3.9: moved styles to separate function // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style + // 2.5.6: use radio_station_add_inline_style $css = radio_station_overrides_list_styles(); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); // --- enqueue datepicker script and styles --- // 2.3.0: enqueue for override post type only @@ -3691,11 +3702,13 @@ function radio_station_overrides_table( $post_id ) { // --- check and sanitize possibly posted times --- // 2.3.3.9: for adding new override time by querystring if ( isset( $_REQUEST['date'] ) ) { - $times['date'] = $_REQUEST['date']; - $times['date'] = date( 'Y-m-d', strtotime( $times['date'] ) ); + $times['date'] = sanitize_text_field( $_REQUEST['date'] ); + // 2.5.6: use radio_station_get_time instead of date + $times['date'] = radio_station_get_time( 'Y-m-d', strtotime( $times['date'] ) ); } if ( isset( $_REQUEST['start_hour'] ) ) { - $start_hour = absint( $_REQUEST['start_hour'] ); + // 2.5.6: fix to key as variable + $times['start_hour'] = absint( $_REQUEST['start_hour'] ); if ( ( $times['start_hour'] < 1 ) || ( $times['start_hour'] > 12 ) ) { $times['start_hour'] = 1; } @@ -3710,7 +3723,7 @@ function radio_station_overrides_table( $post_id ) { } } if ( isset( $_REQUEST['start_meridian'] ) ) { - $times['start_meridian'] = $_REQUEST['start_meridian']; + $times['start_meridian'] = sanitize_text_field( $_REQUEST['start_meridian'] ); if ( !in_array( $times['start_meridian'], array( '', 'am', 'pm' ) ) ) { $times['start_meridian'] = ''; } @@ -3950,7 +3963,7 @@ function radio_station_override_edit_script() { } // --- show shifts scripts --- - $c = 0; + // $c = 0; $confirm_remove = __( 'Are you sure you want to remove this Override?', 'radio-station' ); $confirm_clear = __( 'Are you sure you want to clear all overrides?', 'radio-station' ); @@ -4069,7 +4082,8 @@ function radio_station_override_edit_script() { // --- add new override --- // 2.5.0: shorten value object - $todate = date( 'Y-m-d', time() ); + // 2.5.6: use radio_station_get_time instead of date + $todate = radio_station_get_time( 'Y-m-d', time() ); $js .= "function radio_override_new() { values = {date:'" . esc_js( $todate ) . "', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', multiday:'', end_date:'', disabled:''}; radio_override_add(values); @@ -4250,17 +4264,15 @@ function radio_station_override_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) ) { - return; - } // 2.3.3.9: match to whitelisted actions + // 2.5.6: combine test conditions $actions = array( 'radio_station_override_save', 'radio_station_add_override_time' ); - if ( !in_array( $_REQUEST['action'], $actions ) ) { + if ( !isset( $_REQUEST['action'] ) || !in_array( sanitize_text_field( $_REQUEST['action'] ), $actions ) ) { return; } // --- make sure we have a post ID for AJAX save --- - if ( !isset( $_POST['override_id'] ) || ( '' == $_POST['override_id'] ) ) { + if ( !isset( $_POST['override_id'] ) || ( '' === sanitize_text_field( $_POST['override_id'] ) ) ) { return; } $post_id = absint( $_POST['override_id'] ); @@ -4268,7 +4280,7 @@ function radio_station_override_save_data( $post_id ) { // --- check for errors --- $error = false; - if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { + if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_POST['show_override_nonce'] ), 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); } elseif ( !$post ) { $error = __( 'Failed. Invalid Override.', 'radio-station' ); @@ -4292,7 +4304,7 @@ function radio_station_override_save_data( $post_id ) { // --- verify nonce for show data --- // 2.3.3.9: added to save show override metabox data - if ( isset( $_POST['show_data_nonce'] ) && wp_verify_nonce( $_POST['show_data_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_data_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_data_nonce'] ), 'radio-station' ) ) { // --- save linked show ID --- // 2.3.3.9: save linked show ID @@ -4310,8 +4322,7 @@ function radio_station_override_save_data( $post_id ) { } // --- sync genres switch --- - $sync_genres = false; - if ( isset( $_POST['sync_genres'] ) && ( 'yes' == $_POST['sync_genres'] ) ) { + if ( isset( $_POST['sync_genres'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_genres'] ) ) ) { update_post_meta( $post_id, 'sync_genres', 'yes' ); @@ -4341,8 +4352,7 @@ function radio_station_override_save_data( $post_id ) { } // --- sync languages switch --- - $sync_languages = false; - if ( isset( $_POST['sync_languages'] ) && ( 'yes' == $_POST['sync_languages'] ) ) { + if ( isset( $_POST['sync_languages'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_languages'] ) ) ) { if ( $linked_show ) { @@ -4398,7 +4408,7 @@ function radio_station_override_save_data( $post_id ) { // --- verify this came from the our screen and with proper authorization --- // 2.3.3.9: reverse condition to allow for combined data/schedule processing - if ( isset( $_POST['show_override_nonce'] ) && wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['show_override_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_override_nonce'] ), 'radio-station' ) ) { // --- get the show override data --- $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); @@ -4408,8 +4418,10 @@ function radio_station_override_save_data( $post_id ) { $new_shift['id'] = radio_station_unique_shift_id(); $show_sched = $current_scheds; $show_sched[] = $new_shift; + // TODo: test array_map with sanitize_text_field here ? $_POST['show_sched'] = $show_sched; } else { + // TODo: test array_map with sanitize_text_field here ? $show_sched = $_POST['show_sched']; } $new_scheds = array(); @@ -4580,8 +4592,9 @@ function radio_station_override_save_data( $post_id ) { update_post_meta( $post_id, 'show_override_sched', $new_scheds ); // 2.3.3.9: clear out old unique shift IDs from prev_scheds + // 2.5.6: added isset check for prev_scheds $new_ids = array(); - if ( is_array( $prev_scheds ) && ( count( $prev_scheds ) > 0 ) ) { + if ( isset( $prev_scheds ) && is_array( $prev_scheds ) && ( count( $prev_scheds ) > 0 ) ) { if ( is_array( $new_scheds ) && ( count( $new_scheds ) > 0 ) ) { foreach ( $new_scheds as $i => $new_sched ) { $new_ids[] = $new_sched['id']; @@ -4619,7 +4632,7 @@ function radio_station_override_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { // 2.3.3.9: remove duplicate action check - sanitize_text_field( $_REQUEST['action'] ); + $action = sanitize_text_field( $_REQUEST['action'] ); // --- (hidden) debug information --- echo "Previous Overrides: " . esc_html( print_r( $current_scheds, true ) ) . "\n"; @@ -4691,7 +4704,8 @@ function radio_station_override_save_data( $post_id ) { // 2.3.3.9: trigger action for save or add override time if ( 'radio_station_override_save' == $action ) { - do_action( 'radio_station_override_save_time', $shift_id ); + // 2.5.6: fix second argument to post_id not shift_id + do_action( 'radio_station_override_save_time', $post_id ); } elseif ( 'radio_station_add_override_time' == $action ) { do_action( 'radio_station_override_add_time' ); } @@ -4744,7 +4758,7 @@ function radio_station_override_columns( $columns ) { $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] = $languages; // 2.3.2: added missing translation text domain $columns['override_image'] = esc_attr( __( 'Image', 'radio-station' ) ); - // 2.3.3.9: do not re-add published date column (to reduce confusion) + // 2.3.3.9: do not re-add the published date column (to reduce confusion) // $columns['date'] = $date; return $columns; @@ -4780,12 +4794,13 @@ function radio_station_override_column_data( $column, $post_id ) { } // 2.3.2: no need to apply timezone conversions here + // 2.5.6: use radio_station_get_time instead of date $datetime = strtotime( $override['date'] ); - $month = date( 'F', $datetime ); + $month = radio_station_get_time( 'F', $datetime ); $month = radio_station_translate_month( $month, true ); - $weekday = date( 'l', $datetime ); + $weekday = radio_station_get_time( 'l', $datetime ); $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ) . "\n"; + echo esc_html( $weekday ) . ' ' . esc_html( radio_station_get_time( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( radio_station_get_time( 'Y', $datetime ) ) . "\n"; echo '
    ' . "\n"; // 2.3.3.9: merge override times into this columns @@ -4795,8 +4810,8 @@ function radio_station_override_column_data( $column, $post_id ) { echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . esc_html( $override['start_meridian'] ); echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ) . "\n"; } elseif ( 24 == $time_format ) { - $start_hour = radio_station_convert_hour( $override['start_hour'] . ' ' . $override['start_meridian'] ); - $end_hour = radio_station_convert_hour( $override['end_hour'] . ' ' . $override['end_meridian'] ); + // $start_hour = radio_station_convert_hour( $override['start_hour'] . ' ' . $override['start_meridian'] ); + // $end_hour = radio_station_convert_hour( $override['end_hour'] . ' ' . $override['end_meridian'] ); echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ); echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . "\n"; } @@ -4827,7 +4842,8 @@ function radio_station_override_column_data( $column, $post_id ) { // --- get the override weekday --- // 2.3.2: remove date time and get day from date directly - $weekday = date( 'l', strtotime( $override['date'] ) ); + // 2.5.6: use radio_station-get_time instead of date + $weekday = radio_station_get_time( 'l', strtotime( $override['date'] ) ); // --- get start and end override times --- // 2.3.2: fix to convert to 24 hour format first @@ -4860,7 +4876,8 @@ function radio_station_override_column_data( $column, $post_id ) { // 2.3.0: validate shift time to check if complete // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first - $time = radio_station_validate_shift( $shift ); + // 2.5.6: fix to return variable for validate shift + $shift = radio_station_validate_shift( $shift ); $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; $start = radio_station_convert_shift_time( $start ); @@ -5016,9 +5033,10 @@ function radio_station_override_admin_list_styles() { // 2.3.2: set override image column width to override image width // 2.3.3.9: set override times column width // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_override_list_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); } // ---------------------------------- @@ -5055,8 +5073,8 @@ function radio_station_override_date_filter( $post_type, $which ) { } // --- maybe get specified month --- - // TODO: maybe use get_query_var for month ? - $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + $m = isset( $_GET['month'] ) ? absint( $_GET['month'] ) : 0; + $m = ( ( $m < 1 ) || ( $m > 12 ) ) ? 0 : $m; // --- month override selector --- echo '' . "\n"; @@ -5084,7 +5102,7 @@ function radio_station_override_past_future_filter( $post_type, $which ) { } // --- set past future selection / default --- - $pastfuture = isset( $_GET['pastfuture'] ) ? $_GET['pastfuture'] : ''; + $pastfuture = isset( $_GET['pastfuture'] ) ? sanitize_text_field( $_GET['pastfuture'] ) : ''; $pastfuture = apply_filters( 'radio_station_overrides_past_future_default', $pastfuture ); // --- past / future override selector --- @@ -5146,7 +5164,8 @@ function radio_station_playlist_metabox() { echo '
    ' . "\n"; // 2.3.2: separate track list table - echo radio_station_playlist_track_table( $post->ID ); + // 2.5.6: remove unnecessary echo statement + radio_station_playlist_track_table( $post->ID ); // --- track save/add buttons --- // 2.3.2: change track save from button-primary to button-secondary @@ -5404,9 +5423,10 @@ function radio_station_playlist_metabox() { // --- filter and output --- // 2.3.3.9: added track list style filter // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_tracks_list_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); // --- close meta inner --- echo '
    ' . "\n"; @@ -5819,19 +5839,19 @@ function radio_station_playlist_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { // 2.5.0: added check to exclude quick edit action - if ( !isset( $_POST['playlist-quick-edit'] ) || ( '1' != $_POST['playlist-quick-edit'] ) ) { + if ( !isset( $_POST['playlist-quick-edit'] ) || ( '1' !== sanitize_text_field( $_POST['playlist-quick-edit'] ) ) ) { // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != $_REQUEST['action'] ) ) { + if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != sanitize_text_field( $_REQUEST['action'] ) ) ) { return; } $error = false; if ( !current_user_can( 'edit_playlists' ) ) { $error = __( 'Failed. Use manual Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_POST['playlist_tracks_nonce'] ), 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); - } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { + } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == sanitize_text_field( $_POST['playlist_id'] ) ) ) { $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); } else { $post_id = absint( $_POST['playlist_id'] ); @@ -5861,7 +5881,7 @@ function radio_station_playlist_save_data( $post_id ) { // --- verify playlist nonce --- // 2.3.2: fix OR condition to AND condition - if ( isset( $_POST['playlist_tracks_nonce'] ) && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['playlist_tracks_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['playlist_tracks_nonce'] ), 'radio-station' ) ) { $prev_playlist = get_post_meta( $post_id, 'playlist', true ); $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); @@ -5898,7 +5918,7 @@ function radio_station_playlist_save_data( $post_id ) { if ( isset( $_POST['playlist_show_id'] ) ) { // --- verify playlist related to show nonce --- - if ( isset( $_POST['playlist_show_nonce'] ) && wp_verify_nonce( $_POST['playlist_show_nonce'], 'radio-station' ) ) { + if ( isset( $_POST['playlist_show_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['playlist_show_nonce'] ), 'radio-station' ) ) { // 2.5.0: also get previous shift (if any) $show_changed = false; @@ -5949,7 +5969,7 @@ function radio_station_playlist_save_data( $post_id ) { // --- AJAX saving --- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == $_POST['action'] ) ) { + if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == sanitize_text_field( $_POST['action'] ) ) ) { // --- display tracks saved message --- // 2.3.3.9: fadeout tracks saved message @@ -5966,11 +5986,12 @@ function radio_station_playlist_save_data( $post_id ) { // --- refresh track list table --- // 2.3.3.9: added check if playlist changed if ( $playlist_changed ) { - echo radio_station_playlist_track_table( $post_id ); + // 2.5.6: remove unnecessary echo statement + radio_station_playlist_track_table( $post_id ); echo ""; - print_r( $playlist ); + // echo esc_html( print_r( $playlist, true ) ); } exit; @@ -6118,9 +6139,10 @@ function radio_station_playlist_admin_list_styles() { // 2.3.3.9: add playlist list styles filter // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style ? + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_playlist_list_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); } @@ -6209,7 +6231,9 @@ function radio_station_quick_edit_playlist( $column_name, $post_type ) { // --- related shows post box styles --- $css = '.pre-selected {background-color:#BBB;}'; $css = apply_filters( 'radio_station_quick_edit_playlist_styles', $css ); - echo '' . "\n"; + // 2.5.6: use radio_station_add_inline_style + // echo '' . "\n"; + radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: restore stored post object $post = $stored_post; @@ -6397,10 +6421,11 @@ function radio_station_post_show_metabox() { // 2.3.3.6: add style for pre-selected option // 2.5.0: added missing style filter // 2.5.0: use wp_kses_post on style output - // TODO: use wp_add_inline_style ? + // 2.5.6: use radio_station_add_inline_style $css = ".pre-selected {background-color:#BBB;}"; $css = apply_filters( 'radio_station_post_show_box_styles', $css ); - echo '' . "\n"; + // echo '' . "\n"; + radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: revert current post global $post = $stored_post; @@ -6431,14 +6456,14 @@ function radio_station_post_save_data( $post_id ) { if ( isset( $_POST[$metakey] ) ) { // --- verify field save nonce --- - if ( !isset( $_POST['post_show_nonce'] ) || !wp_verify_nonce( $_POST['post_show_nonce'], 'radio-station' ) ) { + if ( !isset( $_POST['post_show_nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_POST['post_show_nonce'] ), 'radio-station' ) ) { return; } // --- get the related show ID --- $changed = false; $current_shows = get_post_meta( $post_id, $metakey, true ); - $show_ids = $_POST[$metakey]; + $show_ids = sanitize_text_field( $_POST[$metakey] ); // 2.3.3.6: maybe add existing (uneditable) Show IDs $new_show_ids = array(); @@ -6554,9 +6579,11 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { // --- related shows post box styles --- // 2.3.3.6: add style for pre-selected option + // 2.5.6: use radio_station_add_inline_style $css = '.pre-selected {background-color:#BBB;}'; $css = apply_filters( 'radio_station_quick_edit_post_styles', $css ); - echo '' . "\n"; + // echo '' . "\n"; + radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: restore stored post object $post = $stored_post; @@ -6826,10 +6853,12 @@ function radio_station_posts_bulk_edit_notice() { $updated = $failed = false; if ( isset( $_REQUEST['radio_station_related_show_updated'] ) ) { - $updated_ = intval( $_REQUEST['radio_station_related_show_updated'] ); + // 2.5.6: fix variable typo updated_ + // 2.5.6: use absint instead of intval + $updated = absint( $_REQUEST['radio_station_related_show_updated'] ); } if ( isset( $_REQUEST['radio_station_related_show_failed'] ) ) { - $failed = intval( $_REQUEST['radio_station_related_show_failed'] ); + $failed = absint( $_REQUEST['radio_station_related_show_failed'] ); } if ( $updated || $failed ) { @@ -6873,9 +6902,10 @@ function radio_station_posts_list_styles() { // 2.3.3.9: added posts list styles filter // 2.5.0: added wp_kses_post to style output - // TODO: use wp_add_inline_style ? + // 2.5.6: use radio_station_add_inline_style $css = apply_filters( 'radio_station_posts_list_styles', $css ); - echo ''; + // echo ''; + radio_station_add_inline_style( 'rs-admin', $css ); } @@ -6898,9 +6928,9 @@ function radio_station_columns_query_filter( $query ) { if ( RADIO_STATION_SHOW_SLUG === $query->get( 'post_type' ) ) { // --- check if day filter is set --- - if ( isset( $_GET['weekday'] ) && ( '0' != $_GET['weekday'] ) ) { + if ( isset( $_GET['weekday'] ) && ( '0' != sanitize_text_field( $_GET['weekday'] ) ) ) { - $weekday = $_GET['weekday']; + $weekday = sanitize_text_field( $_GET['weekday'] ); // need to loop and sync a separate meta key to enable filtering // (not really efficient but at least it makes it possible!) @@ -6911,8 +6941,8 @@ function radio_station_columns_query_filter( $query ) { $results = $radio_station_data['all-shows']; } else { global $wpdb; - $showquery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "'"; - $results = $wpdb->get_results( $showquery, ARRAY_A ); + $showquery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = %s"; + $results = $wpdb->get_results( $wpdb->prepare( $showquery, RADIO_STATION_SHOW_SLUG ), ARRAY_A ); $radio_station_data['all-shows'] = $results; } if ( $results && ( count( $results ) > 0 ) ) { @@ -6922,7 +6952,7 @@ function radio_station_columns_query_filter( $query ) { $shifts = radio_station_get_show_schedule( $post_id ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { - $shiftdays = array(); + // $shiftdays = array(); $shiftstart = $prevtime = false; foreach ( $shifts as $shift ) { if ( $shift['day'] == $weekday ) { @@ -6975,8 +7005,7 @@ function radio_station_columns_query_filter( $query ) { // 2.3.3.5: use wpdb prepare method on query global $wpdb; $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = %s"; - $overridequery = $wpdb->prepare( $overridequery, RADIO_STATION_OVERRIDE_SLUG ); - $results = $wpdb->get_results( $overridequery, ARRAY_A ); + $results = $wpdb->get_results( $wpdb->prepare( $overridequery, RADIO_STATION_OVERRIDE_SLUG ), ARRAY_A ); if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { $post_id = $result['ID']; @@ -7001,10 +7030,10 @@ function radio_station_columns_query_filter( $query ) { $query->set( 'meta_type', 'date' ); // --- apply override year/month filtering --- - if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { - $yearmonth = $_GET['month']; - $start_date = date( $yearmonth . '-01' ); - $end_date = date( $yearmonth . '-t' ); + if ( isset( $_GET['month'] ) && ( '0' != sanitize_text_field( $_GET['month'] ) ) ) { + $yearmonth = sanitize_text_field( $_GET['month'] ); + $start_date = radio_station_get_time( $yearmonth . '-01' ); + $end_date = radio_station_get_time( $yearmonth . '-t' ); // 2.5.0: fix to use double array for meta_query $meta_query = array( array( 'key' => 'show_override_date', @@ -7019,12 +7048,13 @@ function radio_station_columns_query_filter( $query ) { // 2.3.3: added past future query prototype code // 2.3.3.5: added option for today selection $valid = array( 'past', 'today', 'future' ); - if ( isset( $_GET['pastfuture'] ) && in_array( $_GET['pastfuture'], $valid ) ) { + if ( isset( $_GET['pastfuture'] ) && in_array( sanitize_text_field( $_GET['pastfuture'] ), $valid ) ) { - $date = date( 'Y-m-d', time() ); - $yesterday = date( 'Y-m-d', time() - ( 24 * 60 * 60 ) ); - $tomorrow = date( 'Y-m-d', time() + ( 24 * 60 * 60 ) ); - $pastfuture = $_GET['pastfuture']; + $now = radio_station_get_now(); + $date = radio_station_get_time( 'Y-m-d', $now ); + $yesterday = radio_station_get_time( 'Y-m-d', $now - ( 24 * 60 * 60 ) ); + $tomorrow = radio_station_get_time( 'Y-m-d', $now + ( 24 * 60 * 60 ) ); + $pastfuture = sanitize_text_field( $_GET['pastfuture'] ); if ( 'today' == $pastfuture ) { $compare = 'BETWEEN'; $value = array( $yesterday, $tomorrow ); @@ -7037,10 +7067,10 @@ function radio_station_columns_query_filter( $query ) { } $pastfuture_query = array( - 'key' => 'show_override_date', - 'value' => $value, - 'compare' => $compare, - 'type' => 'DATE', + 'key' => 'show_override_date', + 'value' => $value, + 'compare' => $compare, + 'type' => 'DATE', ); if ( isset( $meta_query ) ) { $combined_query = array( @@ -7081,7 +7111,7 @@ function radio_station_relogin_message() { // 2.3.3.9: fix to playlist action name prefix // 2.3.3.9: added support for override times // 2.3.3.9: add check for single shift action - $action = $_REQUEST['action']; + $action = sanitize_text_field( $_REQUEST['action'] ); $save_shift_actions = array( 'radio_station_show_save_shift', 'radio_station_show_save_shifts' ); if ( in_array( $action, $save_shift_actions ) ) { $type = 'shift'; diff --git a/includes/post-types.php b/includes/post-types.php index ffcf26d..fc17408 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -305,7 +305,7 @@ function radio_station_post_type_editor( $can_edit, $post_type ) { RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, - RADIO_STATION_HOST_SLUG. + RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG, ); // 2.2.8: removed strict in_array checking @@ -545,4 +545,3 @@ function radio_station_register_show_taxonomies() { register_taxonomy( RADIO_STATION_LANGUAGES_SLUG, $post_types, $args ); } - diff --git a/includes/schedules.php b/includes/schedules.php index 64d4d55..2cda727 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -74,7 +74,7 @@ function radio_station_get_show_schedule( $show_id ) { function radio_station_get_show_shifts( $check_conflicts = true, $split = true, $time = false ) { global $radio_station_data, $rs_se; - $channel = $rs_se->channel = $radio_station_data['channel']; + $channel = $rs_se->channel = $radio_station_data['channel']; // --- get all show data --- $shows = radio_station_get_shows(); @@ -83,7 +83,7 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, $records = array(); if ( count( $shows ) > 0 ) { foreach ( $shows as $show ) { - + // 2.5.0: get metadata early (maybe via cached) if ( isset( $radio_station_data['show-' . $show->ID] ) ) { $metadata = $radio_station_data['show-' . $show->ID]; @@ -112,8 +112,9 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, } // --- set times for schedule --- - $now = $time ? $time : radio_station_get_now(); + // 2.5.6: removed as setting of times is no longer necessary here $timezone = radio_station_get_timezone(); + /* $now = $time ? $time : radio_station_get_now(); $today = radio_station_get_time( 'l', $now, $timezone ); $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); @@ -123,7 +124,7 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, 'weekdays' => $weekdays, 'weekdates' => $weekdates, 'timezone' => $timezone, - ); + ); */ // --- get shifts from records --- $all_shifts = $rs_se->get_all_shifts( $records, $check_conflicts, $split, $time, $timezone ); @@ -137,7 +138,7 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, function radio_station_get_all_overrides( $start_date = false, $end_date = false, $timezone = false ) { global $radio_station_data, $rs_se; - $channel = $rs_se->channel = $radio_station_data['channel']; + $channel = $rs_se->channel = $radio_station_data['channel']; // --- get overrides --- $overrides = radio_station_get_overrides(); @@ -158,7 +159,9 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false $data['title'] = $override->post_title; $linked_id = get_post_meta( $override_id, 'linked_show_id', true ); if ( $linked_id ) { - $override_data[$i]['linked_show'] = get_post( $linked_id ); + // 2.5.6: fix to assignment of linked_show variable + $linked_show = get_post( $linked_id ); + $override_data[$i]['linked_show'] = $linked_show; // 2.5.2: fix to use override property not array key $linked_fields = get_post_meta( $override_id, 'linked_show_fields', true ); if ( !isset( $linked_fields['show_title'] ) || !$linked_fields['show_title'] ) { @@ -176,7 +179,7 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false } // --- check/update old shift format --- - if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { // 2.2.3.9: loop to add unique shift IDs and maybe resave $update_override_shifts = false; foreach ( $override_shifts as $j => $shift_data ) { @@ -196,7 +199,7 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false // --- set override shifts --- $data['shifts'] = $override_shifts; - + // --- get override metadata --- $metadata = radio_station_get_override_data_meta( $override ); $data['show'] = $metadata; @@ -222,7 +225,7 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false function radio_station_get_current_schedule( $time = false, $weekstart = false ) { global $radio_station_data, $rs_se; - $channel = $rs_se->channel = $radio_station_data['channel']; + $channel = $rs_se->channel = $radio_station_data['channel']; $show_shifts = false; // --- maybe get cached schedule --- @@ -244,7 +247,7 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // 2.3.2: added transient for time schedule if ( '' != $channel ) { if ( isset( $radio_station_data['current_schedule_' . $channel . '_' . $time] ) ) { - return $radio_station_data['current_schedule_'. $channel . '_' . $time]; + return $radio_station_data['current_schedule_' . $channel . '_' . $time]; } else { $show_shifts = get_transient( 'radio_station_current_schedule_' . $channel . '_' . $time ); } @@ -254,7 +257,7 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $show_shifts = get_transient( 'radio_station_current_schedule_' . $time ); if ( RADIO_STATION_DEBUG && $show_shifts ) { // 2.3.3.9: fix to clear transient object cache (OMFG.) - if ( isset( $_REQUEST['clear'] ) && ( '1' == sanitize_title( $_REQUEST['clear'] ) ) ) { + if ( isset( $_REQUEST['clear'] ) && ( '1' === sanitize_text_field( $_REQUEST['clear'] ) ) ) { echo "Clearing object cache for requested schedule time." . PHP_EOL; wp_cache_delete( 'radio_station_current_schedule_' . $time, 'transients' ); $show_shifts = false; @@ -304,8 +307,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // $start_time = strtotime( '12am ' . $date ); // $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; // $start_time = $start_time - ( 7 * 24 * 60 * 60 ) - 1; - // $start_date = date( 'd-m-Y', $start_time ); - // $end_date = date( 'd-m-Y', $end_time ); + // $start_date = radio_station_get_time( 'd-m-Y', $start_time ); + // $end_date = radio_station_get_time( 'd-m-Y', $end_time ); $start_date = $weekdates[$weekdays[0]]; $end_date = $weekdates[$weekdays[6]]; $overrides = radio_station_get_all_overrides( $start_date, $end_date, $timezone ); @@ -496,7 +499,7 @@ function radio_station_get_next_show( $time = false ) { $next_show = get_transient( 'radio_station_next_show_' . $channel ); } } else { - if ( isset ( $radio_station_data['next_show'] ) ) { + if ( isset( $radio_station_data['next_show'] ) ) { return $radio_station_data['next_show']; } else { $next_show = get_transient( 'radio_station_next_show' ); @@ -511,7 +514,7 @@ function radio_station_get_next_show( $time = false ) { $next_show = get_transient( 'radio_station_next_show_' . $channel . '_' . $time ); } } - if ( isset ( $radio_station_data['next_show_' . $time] ) ) { + if ( isset( $radio_station_data['next_show_' . $time] ) ) { return $radio_station_data['next_show_' . $time]; } else { $next_show = get_transient( 'radio_station_next_show_' . $time ); @@ -660,7 +663,7 @@ function radio_station_check_shift( $show_id, $shift, $scope = 'all' ) { $channel = $rs_se->channel = $radio_station_data['channel']; // 2.3.2: manual bypass of shift checking - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_title( $_REQUEST['check-bypass'] ) ) ) { + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_text_field( $_REQUEST['check-bypass'] ) ) ) { return false; } @@ -673,7 +676,7 @@ function radio_station_check_shift( $show_id, $shift, $scope = 'all' ) { // --- get stored data --- $all_shifts = $radio_station_data['all_shifts']; } else { - + // (with conflict checking off as we are doing that now) $all_shifts = radio_station_get_show_shifts( false, false, false ); @@ -697,7 +700,7 @@ function radio_station_check_shift( $show_id, $shift, $scope = 'all' ) { 'today' => $today, 'weekdays' => $weekdays, 'weekdates' => $weekdates, - 'timezone' => $timezone, + 'timezone' => $timezone, ); // --- check shift for conflict --- @@ -715,7 +718,7 @@ function radio_station_check_new_shifts( $new_shifts, $weekdates = false ) { $channel = $rs_se->channel = $radio_station_data['channel']; // --- shift checking bypass switch --- - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_title( $_REQUEST['check-bypass'] ) ) ) { + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' === sanitize_text_field( $_REQUEST['check-bypass'] ) ) ) { return $new_shifts; } @@ -754,7 +757,7 @@ function radio_station_validate_shift( $shift ) { // -------------------- add_action( 'radio_station_set_current_schedule', 'radio_station_set_current_schedule', 10, 4 ); function radio_station_set_current_schedule( $show_shifts, $expires, $time, $channel ) { - + global $radio_station_data; // --- set current schedule data global --- @@ -776,7 +779,7 @@ function radio_station_set_current_schedule( $show_shifts, $expires, $time, $cha $radio_station_data['current_schedule_' . $time] = $show_shifts; // set_transient( 'radio_station_current_schedule_' . $time, $show_shifts ); } - } + } } // ----------------- @@ -784,12 +787,12 @@ function radio_station_set_current_schedule( $show_shifts, $expires, $time, $cha // ----------------- add_action( 'radio_station_set_previous_shift', 'radio_station_set_previous_show', 10, 4 ); function radio_station_set_previous_show( $previous_show, $expires, $time, $channel ) { - + global $radio_station_data; $transient_key = 'previous_show'; if ( '' != $channel ) { $transient_key .= '_' . $channel; - } + } if ( $time ) { $time = (string) $time; $transient_key .= '_' . $time; @@ -809,7 +812,7 @@ function radio_station_set_current_show( $next_show, $expires, $time, $channel ) $transient_key = 'current_show'; if ( '' != $channel ) { $transient_key .= '_' . $channel; - } + } if ( $time ) { $time = (string) $time; $transient_key .= '_' . $time; @@ -829,7 +832,7 @@ function radio_station_set_next_show( $next_show, $expires, $time, $channel ) { $transient_key = 'next_show'; if ( '' != $channel ) { $transient_key .= '_' . $channel; - } + } if ( $time ) { $time = (string) $time; $transient_key .= '_' . $time; @@ -895,4 +898,3 @@ function radio_station_set_shift_conflicts( $conflicts, $channel ) { } } } - diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 4e0b9b6..e117b2f 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -672,6 +672,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { // --- no shows messages ---- + $message = ''; if ( RADIO_STATION_SHOW_SLUG == $post_type ) { // 2.3.3.9: improve messages if genre / language specificed if ( ( !empty( $atts['genre'] ) ) && ( !empty( $atts['genre'] ) ) ) { @@ -1033,7 +1034,8 @@ function radio_station_genre_archive_list( $atts ) { $genre_slugs = explode( ',', $atts['genres'] ); foreach ( $genre_slugs as $genre_slug ) { $genre_slug = trim( $genre_slug ); - $genre = radio_station_get_genre( $genre ); + // 2.5.6: fix to get genre by genre_slug + $genre = radio_station_get_genre( $genre_slug ); if ( $genre ) { $genres[$genre['name']] = $genre; $genre_ids[] = $genre['id']; @@ -1193,8 +1195,9 @@ function radio_station_genre_archive_list( $atts ) { } $list .= '>' . "\n"; // 2.5.0: added wp_kses on image output + // 2.5.6: fix to add missing allowed variable $allowed = radio_station_allowed_html( 'media', 'image' ); - $list .= wp_kses( $genre_image ); + $list .= wp_kses( $genre_image, $allowed ); $list .= '
    ' . "\n"; } @@ -1225,8 +1228,9 @@ function radio_station_genre_archive_list( $atts ) { } $heading .= '>' . "\n"; // 2.5.0: added wp_kses on image output + // 2.5.6: fix to add missing allowed variable $allowed = radio_station_allowed_html( 'media', 'image' ); - $heading .= wp_kses( $genre_image ); + $heading .= wp_kses( $genre_image, $allowed ); $heading .= '
    ' . "\n"; } @@ -1646,7 +1650,7 @@ function radio_station_language_archive_list( $atts ) { $list .= '
    ' . "\n"; - $heading = ''; + // $heading = ''; if ( !isset( $language_posts[$language['id']] ) ) { if ( !$atts['hide_empty'] ) { @@ -1818,7 +1822,8 @@ function radio_station_archive_pagination( $instance, $type, $atts, $post_count $pagi .= '
    ' . "\n"; if ( $prev_page > 0 ) { $pagi .= '
    ' . "\n"; - // $url = add_query_arg( 'page', $prev_page, $permalink ); + // 2.5.6: fix to set permalink as URL for first page + $url = $permalink; if ( $prev_page > 1 ) { $url = add_query_arg( 'offset', ( $prev_page * $atts['perpage'] ), $permalink ); } @@ -1829,7 +1834,7 @@ function radio_station_archive_pagination( $instance, $type, $atts, $post_count for ( $page_num = 1; $page_num < ( $post_pages + 1 ); $page_num++ ) { $active = ( $current_page == $page_num ) ? ' active' : ''; $pagi .= '
    ' . "\n"; - // $url = add_query_arg( 'page', $page_num, $permalink ); + $url = $permalink; if ( $page_num > 1 ) { $offset = ( ( $page_num - 1 ) * $atts['perpage'] ); $url = add_query_arg( 'offset', $offset, $permalink ); @@ -1842,7 +1847,6 @@ function radio_station_archive_pagination( $instance, $type, $atts, $post_count } if ( $next_page < ( $post_pages + 1 ) ) { $pagi .= '
    ' . "\n"; - // $url = add_query_arg( 'page', $next_page, $permalink ); $offset = ( ( $next_page - 1 ) * $atts['perpage'] ); $url = add_query_arg( 'offset', $offset, $permalink ); $onclick = "return radio_archive_page(" . esc_attr( $instance ) . ",'" . esc_attr( $type ) . "'," . esc_attr( $next_page ) . "');"; @@ -2061,7 +2065,8 @@ function radio_station_show_list_shortcode( $type, $atts ) { if ( 'none' == $atts['content'] ) { $list .= ''; } elseif ( 'full' == $atts['content'] ) { - $content = apply_filters( 'radio_station_show_' . $type . '_content', $bio_content, $user_id ); + // 2.5.6: fix to filter variable assignment content + $bio_content = apply_filters( 'radio_station_show_' . $type . '_content', $bio_content, $user_id ); $list .= '
    ' . "\n"; // 2.5.0: use wp_kses on bio content output $allowed = radio_station_allowed_html( 'content', 'bio' ); @@ -2109,8 +2114,8 @@ function radio_station_show_list_shortcode( $type, $atts ) { // --- link to post --- $permalink = get_permalink( $post['ID'] ); - $timestamp = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); - $title = __( 'Published on ', 'radio-station' ) . $timestamp; + $publish_time = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); + $title = __( 'Published on ', 'radio-station' ) . $publish_time; $list .= '
    ' . "\n"; $list .= '' . "\n"; // 2.5.0: use esc_html instead of esc_attr @@ -2413,8 +2418,8 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.3: add current time override for manual testing if ( isset( $_GET['date'] ) && isset( $_GET['time'] ) ) { - $date = trim( $_GET['date'] ); - $time = trim( $_GET['time'] ); + $date = trim( sanitize_text_field( $_GET['date'] ) ); + $time = trim( sanitize_text_field( $_GET['time'] ) ); if ( isset( $_GET['month'] ) ) { $month = absint( trim( $_GET['month'] ) ); } else { @@ -2886,7 +2891,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { - $excerpt .= $show_post->post_excerpt; + $excerpt = $show_post->post_excerpt; // 2.5.0: added esc_html to more anchor text $excerpt .= ' ' . esc_html( $more ) . ''; // $excerpt .= ""; @@ -3006,10 +3011,10 @@ function radio_station_current_show_shortcode( $atts ) { if ( RADIO_STATION_DEBUG ) { $output .= ''; - $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . esc_attr( $now ) . ')' . PHP_EOL; - $output .= 'Shift Start Time: ' . date( 'Y-m-d H:i:s', $current_shift_start ) . ' (' . esc_attr( $current_shift_start ) . ')' . PHP_EOL; - $output .= 'Shift End Time: ' . date( 'Y-m-d H:i:s', $current_shift_end ) . ' (' . esc_attr( $current_shift_end ) . ')' . PHP_EOL; - $output .= 'Remaining: ' . ( $current_shift_end - $now ) . PHP_EOL; + $output .= 'Now: ' . radio_station_get_time( 'Y-m-d H:i:s', $now ) . ' (' . esc_attr( $now ) . ')' . "\n"; + $output .= 'Shift Start Time: ' . radio_station_get_time( 'Y-m-d H:i:s', $current_shift_start ) . ' (' . esc_attr( $current_shift_start ) . ')' . "\n"; + $output .= 'Shift End Time: ' . radio_station_get_time( 'Y-m-d H:i:s', $current_shift_end ) . ' (' . esc_attr( $current_shift_end ) . ')' . "\n"; + $output .= 'Remaining: ' . ( $current_shift_end - $now ) . "\n"; $output .= ''; } @@ -3218,8 +3223,8 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.3: added current time override for manual testing if ( isset( $_GET['date'] ) && isset( $_GET['time'] ) ) { - $date = trim( $_GET['date'] ); - $time = trim( $_GET['time'] ); + $date = sanitize_text_field( trim( $_GET['date'] ) ); + $time = sanitize_text_field( trim( $_GET['time'] ) ); if ( isset( $_GET['month'] ) ) { $month = absint( trim( $_GET['month'] ) ); } else { @@ -3684,10 +3689,10 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $output .= '' . "\n"; if ( RADIO_STATION_DEBUG ) { $output .= ''; - $output .= 'Now: ' . esc_html( date( 'Y-m-d H:i:s', $now ) ) . ' (' . esc_html( $now ) . ')' . PHP_EOL; - $output .= 'Next Start Time: ' . esc_html( date('y-m-d H:i:s', $next_start_time ) ) . ' (' . esc_html( $next_start_time ) . ')' . PHP_EOL; - $output .= 'Next End Time: ' . esc_html( date( 'y-m-d H:i:s', $next_end_time ) ) . ' (' . esc_html( $next_end_time ) . ')' . PHP_EOL; - $output .= 'Starting in: ' . esc_html( $next_start_time - $now ) . PHP_EOL; + $output .= 'Now: ' . esc_html( radio_station_get_time( 'Y-m-d H:i:s', $now ) ) . ' (' . esc_html( $now ) . ')' . "\n"; + $output .= 'Next Start Time: ' . esc_html( radio_station_get_time('y-m-d H:i:s', $next_start_time ) ) . ' (' . esc_html( $next_start_time ) . ')' . "\n"; + $output .= 'Next End Time: ' . esc_html( radio_station_get_time( 'y-m-d H:i:s', $next_end_time ) ) . ' (' . esc_html( $next_end_time ) . ')' . "\n"; + $output .= 'Starting in: ' . esc_html( $next_start_time - $now ) . "\n"; $output .= ''; } @@ -3969,11 +3974,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- convert dates --- // 2.3.0: use weekdates for reliability - if ( $atts['for_time'] ) { - $now = $atts['for_time']; - } else { - $now = radio_station_get_now(); - } + $now = $atts['for_time'] ? $atts['for_time'] : radio_station_get_now(); $today = radio_station_get_time( 'l', $now ); $yesterday = radio_station_get_previous_day( $today ); $weekdays = radio_station_get_schedule_weekdays( $yesterday ); @@ -4025,8 +4026,6 @@ function radio_station_current_playlist_shortcode( $atts ) { if ( $atts['for_time'] ) { // $now = $atts['for_time']; $html['countdown'] .= '' . "\n"; - } else { - // $now = radio_station_get_now(); } // echo 'Shift Start: ' . $shift_start_time . '(' . radio_station_get_time( 'datetime', $shift_start_time ) . ')
    '; // echo 'Shift End: ' . $shift_end_time . '(' . radio_station_get_time( 'datetime', $shift_end_time ) . ')
    '; diff --git a/includes/support-functions.php b/includes/support-functions.php index be209ff..7b57b61 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -17,8 +17,11 @@ // x Get Show Schedule // - Generate Unique Shift ID // - Get Show Data -// - Get Show Metadata +// - Get Show Data Meta +// - Show Data Meta Filter +// - Get Show Description // - Get Override Metadata +// - Override Data Meta Filter // - Get Show Override Value // - Get Linked Overrides for Show // - Get Linked Override Times @@ -75,8 +78,7 @@ function radio_station_get_show( $show ) { if ( is_string( $show ) ) { global $wpdb; $query = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; - $query = $wpdb->prepare( $query, $show ); - $show_id = $wpdb->get_var( $query ); + $show_id = $wpdb->get_var( $wpdb->prepare( $query, $show ) ); $show = get_post( $show_id ); } elseif ( is_int( $show ) ) { $show = get_post( $show ); @@ -91,7 +93,7 @@ function radio_station_get_show( $show ) { // --------- // 2.3.0: added get shows data grabber function radio_station_get_shows( $args = false ) { - + // --- set default args --- $query_args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, @@ -100,17 +102,17 @@ function radio_station_get_shows( $args = false ) { 'meta_query' => array( 'relation' => 'AND', array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', + 'key' => 'show_sched', + 'compare' => 'EXISTS', ), array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', ), ), - 'orderby' => 'post_name', - 'order' => 'ASC', + 'orderby' => 'post_name', + 'order' => 'ASC', ); // --- overwrite defaults with any arguments passed --- @@ -119,7 +121,7 @@ function radio_station_get_shows( $args = false ) { $query_args[$key] = $value; } } - + // 2.5.0: added query args filter $query_args = apply_filters( 'radio_station_get_shows_args', $query_args, $args ); @@ -146,17 +148,17 @@ function radio_station_get_overrides( $args = false ) { 'meta_query' => array( // 'relation' => 'AND', array( - 'key' => 'show_override_sched', - 'compare' => 'EXISTS', + 'key' => 'show_override_sched', + 'compare' => 'EXISTS', ), /* array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', ), */ ), - 'orderby' => 'post_name', - 'order' => 'ASC', + 'orderby' => 'post_name', + 'order' => 'ASC', ); // --- overwrite defaults with any arguments passed --- @@ -212,7 +214,8 @@ function radio_station_get_show_guid( $show_id ) { global $wpdb; $query = "SELECT guid FROM " . $wpdb->posts . " WHERE ID = %d"; - $guid = $wpdb->get_var( $query ); + // 2.5.6: added missing prepare for query + $guid = $wpdb->get_var( $wpdb->prepare( $query, $show_id ) ); if ( !$guid ) { $guid = get_permalink( $show_id ); } @@ -324,10 +327,12 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att // 2.3.3.4: handle possible multiple show post values // 2.3.3.9: added 'i:' prefix to LIKE match value // 2.5.6: fix prepare query syntax by separating LIKE statement - $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s"; - $query = $wpdb->prepare( $query, $metakey ); - // TODO: use wpdb prepare method on LIKE statement? - $query .= "AND meta_value LIKE '%i:" . $show_id . "%'"; + // $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s"; + // $query = $wpdb->prepare( $query, $metakey ); + // $query .= "AND meta_value LIKE '%i:" . $show_id . "%'"; + // 2.5.6: then use wpdb prepare method for LIKE statement + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value LIKE %s"; + $query = $wpdb->prepare( $query, array( $metakey, "%i:" . $show_id . "%" ) ); $results = $wpdb->get_results( $query, ARRAY_A ); if ( RADIO_STATION_DEBUG ) { echo 'Related Query: ' . esc_html( $query ) . ''; @@ -357,8 +362,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att $post_ids = $no_profile_ids = array(); foreach ( $user_ids as $user_id ) { $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value = %d"; - $query = $wpdb->prepare( $query, array( $userkey, $user_id ) ); - $profile_id = $wpdb->get_var( $query ); + $profile_id = $wpdb->get_var( $wpdb->prepare( $query, array( $userkey, $user_id ) ) ); if ( RADIO_STATION_DEBUG ) { echo 'Related Query: ' . esc_html( $query ) . ''; echo 'Related Result: ' . esc_html( print_r( $profile_id, true ) ) . ''; @@ -374,8 +378,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array(), $att // --- other types (episodes/playlists) --- $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = %s AND meta_value = %d"; - $query = $wpdb->prepare( $query, array( $metakey, $show_id ) ); - $post_metas = $wpdb->get_results( $query, ARRAY_A ); + $post_metas = $wpdb->get_results( $wpdb->prepare( $query, array( $metakey, $show_id ) ), ARRAY_A ); if ( RADIO_STATION_DEBUG ) { echo 'Related Query: ' . esc_html( $query ) . ''; echo 'Related Results: ' . esc_html( print_r( $post_metas, true ) ) . ''; @@ -580,7 +583,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { 'latest' => $show_file, 'website' => $show_link, // note: left out intentionally to avoid spam scraping - // 'email' => $show_email, + // 'email' => $show_email, 'hosts' => $hosts, 'producers' => $producers, 'genres' => $genre_list, @@ -593,12 +596,12 @@ function radio_station_get_show_data_meta( $show, $single = false ) { ); // --- data route / feed for show --- - if ( radio_station_get_setting( 'enable_data_routes' ) == 'yes' ) { + if ( 'yes' == radio_station_get_setting( 'enable_data_routes' ) ) { $route_link = radio_station_get_route_url( 'shows' ); $show_route = add_query_arg( 'show', $show->post_name, $route_link ); $show_data['route'] = $show_route; } - if ( radio_station_get_setting( 'enable_data_feeds' ) == 'yes' ) { + if ( 'yes' == radio_station_get_setting( 'enable_data_feeds' ) ) { $feed_link = radio_station_get_feed_url( 'shows' ); $show_feed = add_query_arg( 'show', $show->post_name, $feed_link ); $show_data['feed'] = $show_feed; @@ -624,6 +627,24 @@ function radio_station_get_show_data_meta( $show, $single = false ) { return $show_data; } +// --------------------- +// Show Data Meta Filter +// --------------------- +// 2.5.6: added to get show data for show in schedule engine +add_filter( 'radio_station_schedule_show_data_meta', 'radio_station_show_data_meta_filter', 10, 2 ); +function radio_station_show_data_meta_filter( $show_id, $shift_id ) { + + // --- get (or get stored) show data --- + global $radio_station_data; + if ( isset( $radio_station_data['show-' . $show_id] ) ) { + $show = $radio_station_data['show-' . $show_id]; + } else { + $show = radio_station_get_show_data_meta( $show_id ); + $radio_station_data['show-' . $show_id] = $show; + } + return $show; +} + // -------------------- // Get Show Description // -------------------- @@ -779,6 +800,24 @@ function radio_station_get_override_data_meta( $override ) { return $override_data; } +// ------------------------- +// Override Data Meta Filter +// ------------------------- +// 2.5.6: added to get override data for override in schedule engine +add_filter( 'radio_station_schedule_override_data_meta', 'radio_station_override_data_meta_filter', 10, 2 ); +function radio_station_override_data_meta_filter( $override_id, $shift_id ) { + + // --- get (or get stored) override data --- + global $radio_station_data; + if ( isset( $radio_station_data['override-' . $override_id] ) ) { + $override = $radio_station_data['override-' . $override_id]; + } else { + $override = radio_station_get_override_data_meta( $override ); + $radio_station_data['override-' . $override_id] = $override; + } + return $override; +} + // ----------------------- // Get Show Override Value // ----------------------- @@ -843,8 +882,7 @@ function radio_station_get_linked_overrides( $post_id ) { // --- get linked override IDs via show ID --- global $wpdb; $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = 'linked_show_id' AND meta_value = %d"; - $query = $wpdb->prepare( $query, $show_id ); - $results = $wpdb->get_results( $query, ARRAY_A ); + $results = $wpdb->get_results( $wpdb->prepare( $query, $show_id ), ARRAY_A ); $override_ids = array(); if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { @@ -852,8 +890,7 @@ function radio_station_get_linked_overrides( $post_id ) { // --- check for published post status --- $query = "SELECT post_status FROM " . $wpdb->prefix . "posts WHERE ID = %d"; - $query = $wpdb->prepare( $query, $override_id ); - $status = $wpdb->get_var( $query ); + $status = $wpdb->get_var( $wpdb->prepare( $query, $override_id ) ); if ( 'publish' == $status ) { $override_ids[] = $override_id; } @@ -877,9 +914,6 @@ function radio_station_get_linked_override_times( $post_id ) { foreach ( $override_ids as $override_id ) { $schedule = get_post_meta( $override_id, 'show_override_sched', true ); if ( $schedule ) { - if ( !is_array( $overrides ) ) { - $override = array(); - } if ( !is_array( $schedule ) ) { $schedule = array( $schedule ); } @@ -983,6 +1017,7 @@ function radio_station_get_show_playlists( $show_id = false, $args = array() ) { // --------- // 2.3.0: added genre data grabber function radio_station_get_genre( $genre ) { + // 2.3.3.8: explicitly check for numberic genre term ID $id = absint( $genre ); if ( $id < 1 ) { @@ -1073,13 +1108,13 @@ function radio_station_get_genre_shows( $genre = false ) { 'meta_query' => array( 'relation' => 'AND', array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', + 'key' => 'show_sched', + 'compare' => 'EXISTS', ), array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', ), ), ); @@ -1122,13 +1157,13 @@ function radio_station_get_language_shows( $language = false ) { 'meta_query' => array( 'relation' => 'AND', array( - 'key' => 'show_sched', - 'compare' => 'EXISTS', + 'key' => 'show_sched', + 'compare' => 'EXISTS', ), array( - 'key' => 'show_active', - 'value' => 'on', - 'compare' => '=', + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', ), ), ); @@ -1150,7 +1185,7 @@ function radio_station_get_language_shows( $language = false ) { function radio_station_get_image_sizes() { // --- get image size names --- - $image_sizes = array( + $image_sizes = array( 'thumbnail' => __( 'Thumbnail' ), 'medium' => __( 'Medium' ), 'large' => __( 'Large' ), @@ -1346,13 +1381,13 @@ function radio_station_get_stream_formats() { // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv $formats = array( - 'aac' => 'AAC/M4A', // A/H/J - 'mp3' => 'MP3', // A/H/J - 'ogg' => 'OGG', // H - 'oga' => 'OGA', // H/J - 'webm' => 'WebM', // H/J - 'rtmpa' => 'RTMPA', // J - 'opus' => 'OPUS', // H + 'aac' => 'AAC/M4A', // A/H/J + 'mp3' => 'MP3', // A/H/J + 'ogg' => 'OGG', // H + 'oga' => 'OGA', // H/J + 'webm' => 'WebM', // H/J + 'rtmpa' => 'RTMPA', // J + 'opus' => 'OPUS', // H ); // --- filter and return --- @@ -1584,14 +1619,18 @@ function radio_station_queue_directory_ping() { // ------------------- // 2.3.1: added directory ping function prototype function radio_station_send_directory_ping() { + $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); - if ( 'yes' != $do_ping ) {return;} + if ( 'yes' != $do_ping ) { + return false; + } // --- set the URL to ping --- // 2.3.2: fix url_encode to urlencode + // 2.5.6: use rawurlencode instead of urlencode $site_url = site_url(); $url = add_query_arg( 'ping', 'directory', RADIO_STATION_NETMIX_DIR ); - $url = add_query_arg( 'station-url', urlencode( $site_url ), $url ); + $url = add_query_arg( 'station-url', rawurlencode( $site_url ), $url ); $url = add_query_arg( 'timestamp', time(), $url ); // --- send the ping --- @@ -1600,10 +1639,10 @@ function radio_station_send_directory_ping() { include_once ABSPATH . WPINC . '/http.php'; } $response = wp_remote_get( $url, $args ); - if ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + if ( isset( $_GET['rs-test-ping'] ) && ( '1' === sanitize_text_field( $_GET['rs-test-ping'] ) ) ) { echo 'Directory Ping Response:'; echo ''; + echo esC_html( print_r( $response, true ) ) . ''; } return $response; } @@ -1620,7 +1659,7 @@ function radio_station_check_directory_ping() { if ( !is_wp_error( $response ) && isset( $response['response']['code'] ) && ( 200 == $response['response']['code'] ) ) { delete_option( 'radio_station_ping_directory' ); } - } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' === sanitize_text_field( $_GET['rs-test-ping'] ) ) ) { $response = radio_station_send_directory_ping(); } } @@ -1651,9 +1690,9 @@ function radio_station_get_icon_colors( $context = false ) { // -------------------- // 2.3.2: added PHP equivalent of javascript encodeURIComponent // ref: https://stackoverflow.com/a/1734255/5240159 -function radio_station_encode_uri_component( $string ) { +function radio_station_encode_uri_component( $component ) { $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ); - return strtr( rawurlencode( $string ), $revert ); + return strtr( rawurlencode( $component ), $revert ); } // ------------------ @@ -1662,7 +1701,11 @@ function radio_station_encode_uri_component( $string ) { // 2.3.3.9: added for language archive shortcode function radio_station_get_language_terms( $args = false ) { - $defaults = array( 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, 'orderby' => 'name', 'hide_empty' => true ); + $defaults = array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'orderby' => 'name', + 'hide_empty' => true + ); if ( $args && is_array( $args ) ) { foreach ( $args as $key => $value ) { $defaults[$key] = $value; @@ -1788,8 +1831,8 @@ function radio_station_get_language( $lang = false ) { } } } - if ( isset( $_REQUEST['lang-debug'] ) && ( '1' == $_REQUEST['lang-debug'] ) ) { - echo PHP_EOL . "LANG: " . print_r( $lang, true ) . PHP_EOL; + if ( isset( $_REQUEST['lang-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['lang-debug'] ) ) ) { + echo PHP_EOL . "LANG: " . esc_html( print_r( $lang, true ) ) . PHP_EOL; } // --- get the specified language term --- @@ -1833,8 +1876,8 @@ function radio_station_get_language( $lang = false ) { $language = false; } } - if ( isset( $_REQUEST['lang-debug'] ) && ( '1' == $_REQUEST['lang-debug'] ) ) { - echo PHP_EOL . "LANGUAGE: " . print_r( $language, true ) . PHP_EOL; + if ( isset( $_REQUEST['lang-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['lang-debug'] ) ) ) { + echo 'LANGUAGE: ' . esc_html( print_r( $language, true ) ) . "\n"; } return $language; @@ -1870,14 +1913,14 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ $length = (int) apply_filters( 'radio_station_excerpt_length', $length ); } if ( !$more ) { - $more = ' […]'; + // $more = ' […]'; // $more = apply_filters( 'excerpt_more', $more); $more = apply_filters( 'radio_station_excerpt_more', ' […]' ); } // 2.3.0: added link wrapper if ( $permalink ) { // 2.5.0: add esc_html to more anchor - $more = ' ' . esc_html( $more ) . ''; + $more = ' ' . esc_html( $more ) . ''; } $excerpt = wp_trim_words( $content, $length, $more ); } @@ -1890,7 +1933,7 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ // Sanitize Values // --------------- function radio_station_sanitize_values( $data, $keys ) { - + $sanitized = array(); foreach ( $keys as $key => $type ) { if ( isset( $data[$key] ) ) { @@ -1931,114 +1974,110 @@ function radio_station_sanitize_input( $prefix, $key ) { // 2.4.0.3: bug out if post key not set // 2.5.0: set empty value as default + // 2.5.6: put POST directly in sanitize functions $value = ''; if ( isset( $_POST[$postkey] ) ) { - $posted_value = $_POST[$postkey]; - } - - if ( in_array( $key, $types['file'] ) ) { - $value = wp_strip_all_tags( trim( $posted_value ) ); - } elseif ( in_array( $key, $types['email'] ) ) { - $value = sanitize_email( trim( $posted_value ) ); - } elseif ( in_array( $key, $types['url'] ) ) { - $value = filter_var( trim( $posted_value ), FILTER_SANITIZE_URL ); - } elseif ( in_array( $key, $types['slug'] ) ) { - $value = sanitize_title( $posted_value ); - } elseif ( in_array( $key, $types['phone'] ) ) { - // 2.3.3.6: added phone number with character filter validation - $value = trim( $posted_value ); - if ( strlen( $value ) > 0 ) { - $value = str_split( $value, 1 ); - $value = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $value ); - if ( count( $value ) > 0 ) { - $value = implode( '', $value ); - } else { - $value = ''; + + if ( in_array( $key, $types['file'] ) ) { + $value = wp_strip_all_tags( trim( $_POST[$postkey] ) ); + } elseif ( in_array( $key, $types['email'] ) ) { + $value = sanitize_email( trim( $_POST[$postkey] ) ); + } elseif ( in_array( $key, $types['url'] ) ) { + $value = filter_var( trim( $_POST[$postkey] ), FILTER_SANITIZE_URL ); + } elseif ( in_array( $key, $types['slug'] ) ) { + $value = sanitize_title( $_POST[$postkey] ); + } elseif ( in_array( $key, $types['phone'] ) ) { + // 2.3.3.6: added phone number with character filter validation + $value = trim( sanitize_text_field( $_POST[$postkey] ) ); + if ( strlen( $value ) > 0 ) { + $value = str_split( $value, 1 ); + $value = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $value ); + if ( count( $value ) > 0 ) { + $value = implode( '', $value ); + } else { + $value = ''; + } } - } - } elseif ( in_array( $key, $types['numeric'] ) ) { + } elseif ( in_array( $key, $types['numeric'] ) ) { - $value = absint( $posted_value ); - if ( $value < 0 ) { - $value = ''; - } + $value = absint( $_POST[$postkey] ); + if ( $value < 0 ) { + $value = ''; + } - } elseif ( in_array( $key, $types['checkbox'] ) ) { + } elseif ( in_array( $key, $types['checkbox'] ) ) { - // --- checkbox inputs --- - // 2.2.8: removed strict in_array checking - // 2.3.2: fix for unchecked boxes index warning - $value = ''; - if ( isset( $posted_value ) ) { - $value = $posted_value; - } - if ( !in_array( $value, array( '', 'on', 'yes' ) ) ) { - $value = ''; - } + // --- checkbox inputs --- + // 2.2.8: removed strict in_array checking + // 2.3.2: fix for unchecked boxes index warning + $value = sanitize_text_field( $_POST[$postkey] ); + if ( !in_array( $value, array( '', 'on', 'yes' ) ) ) { + $value = ''; + } - } elseif ( in_array( $key, $types['user'] ) ) { + } elseif ( in_array( $key, $types['user'] ) ) { - // --- user selection inputs --- - if ( isset( $posted_value ) ) { - $value = $posted_value; - } - if ( !isset( $value ) || !is_array( $value ) ) { - $value = array(); - } else { - foreach ( $value as $i => $userid ) { - if ( !empty( $userid ) ) { - $user = get_user_by( 'ID', $userid ); - if ( !$user ) { - unset( $value[$i] ); + // --- user selection inputs --- + // 2.5.6: use array_map on posted value + $value = array_map( 'absint', $_POST[$postkey] ); + if ( !isset( $value ) || !is_array( $value ) ) { + $value = array(); + } else { + foreach ( $value as $i => $userid ) { + if ( ! empty( $userid ) ) { + $user = get_user_by( 'ID', $userid ); + if ( !$user ) { + unset( $value[ $i ] ); + } } } } - } - } elseif ( in_array( $key, $types['date'] ) ) { + } elseif ( in_array( $key, $types['date'] ) ) { - // --- datepicker date field --- - $date = $posted_value; - $parts = explode( '-', $date ); - if ( 3 == count( $parts ) ) { - if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { - $value = $date; + // --- datepicker date field --- + $date = sanitize_text_field( $_POST[$postkey] ); + $parts = explode( '-', $date ); + if ( 3 == count( $parts ) ) { + if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { + $value = $date; + } } - } - } elseif ( in_array( $key, $types['hour'] ) ) { + } elseif ( in_array( $key, $types['hour'] ) ) { - // --- hours (24) --- - $value = absint( $posted_value ); - if ( ( $value < 0 ) || ( $value > 23 ) ) { - $value = '00'; - } elseif ( $value < 10 ) { - $value = '0' . $value; - } else { - $value = (string) $value; - } + // --- hours (24) --- + $value = absint( $_POST[$postkey] ); + if ( ( $value < 0 ) || ( $value > 23 ) ) { + $value = '00'; + } elseif ( $value < 10 ) { + $value = '0' . $value; + } else { + $value = (string) $value; + } - } elseif ( in_array( $key, $types['mins'] ) ) { + } elseif ( in_array( $key, $types['mins'] ) ) { - // --- minutes (or seconds) --- - $value = absint( $posted_value ); - if ( ( $value < 0 ) || ( $value > 60 ) ) { - $value = '00'; - } elseif ( $value < 10 ) { - $value = '0' . $value; - } else { - $value = (string) $value; - } + // --- minutes (or seconds) --- + $value = absint( $_POST[$postkey] ); + if ( ( $value < 0 ) || ( $value > 60 ) ) { + $value = '00'; + } elseif ( $value < 10 ) { + $value = '0' . $value; + } else { + $value = (string) $value; + } - } elseif ( in_array( $key, $types['meridiem'] ) ) { + } elseif ( in_array( $key, $types['meridiem'] ) ) { - // --- meridiems --- - $valid = array( '', 'am', 'pm' ); - $value = $posted_value; - if ( !in_array( $value, $valid ) ) { - $value = ''; - } + // --- meridiems --- + $valid = array( '', 'am', 'pm' ); + $value = sanitize_text_field( $_POST[$postkey] ); + if ( !in_array( $value, $valid ) ) { + $value = ''; + } + } } return $value; @@ -2098,8 +2137,12 @@ function radio_station_sanitize_playlist_entry( $entry ) { $value = sanitize_text_field( $value ); } elseif ( in_array( $key, $numeric_keys ) ) { $value = absint( $value ); + // 2.5.6: set non-numeric values to blank + if ( $value < 0 ) { + $value = ''; + } if ( ( 'seconds' == $key ) && ( $value < 10 ) ) { - + // pad seconds with zero prefix ? } } elseif ( 'status' == $key ) { if ( $value != 'played' ) { @@ -2121,8 +2164,7 @@ function radio_station_sanitize_playlist_entry( $entry ) { // 2.5.0: updated to match changed shortcode keys function radio_station_sanitize_shortcode_values( $type, $extras = false ) { - $atts = array(); - + // $atts = array(); if ( 'current-show' == $type ) { // --- current show attribute keys --- @@ -2236,7 +2278,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // --- master schedule attribute keys --- // 2.3.3.9: added for AJAX schedule loading - // 2.5.0: added active_date, + // 2.5.0: added active_date, $keys = array( // --- control display options --- @@ -2252,7 +2294,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'active_date' => 'text', 'display_day' => 'text', 'display_date' => 'text', - 'display_month' => 'text', + 'display_month' => 'text', 'time_format' => 'text', // --- show display options --- @@ -2279,7 +2321,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'time_spaced' => 'boolean', 'weeks' => 'integer', 'previous_weeks' => 'integer', - + // --- shortcode data --- 'block' => 'boolean', 'instance' => 'boolean', @@ -2316,8 +2358,9 @@ function radio_station_allowed_html( $type, $context = false ) { // 2.5.0: added for allowing link tag output for wp_kses add_filter( 'radio_station_allowed_html', 'radio_station_link_tag_allowed_html', 10, 3 ); function radio_station_link_tag_allowed_html( $allowed, $type, $context ) { - - if ( 'link' != $type ) { + + // 2.5.6: change type to context + if ( 'link' != $context ) { return $allowed; } @@ -2332,6 +2375,25 @@ function radio_station_link_tag_allowed_html( $allowed, $type, $context ) { 'title' => array(), 'type' => array(), ); + + return $allowed; +} + +// ----------------------- +// Anchor Tag Allowed HTML +// ----------------------- +// 2.5.6: added for allowing extended anchor tag output for wp_kses +add_filter( 'radio_station_allowed_html', 'radio_station_anchor_tag_allowed_html', 10, 3 ); +function radio_station_anchor_tag_allowed_html( $allowed, $type, $context ) { + + // 2.5.6: added docs context for documentation links + if ( 'docs' != $context ) { + return $allowed; + } + + // 2.5.6: added data-href attribute for docs + $allowed['a']['id'] = true; + return $allowed; } @@ -2348,39 +2410,39 @@ function radio_station_settings_allowed_html( $allowed, $type, $context ) { // --- input --- $allowed['input'] = array( - 'id' => array(), - 'class' => array(), - 'name' => array(), - 'value' => array(), - 'type' => array(), - 'data' => array(), - 'placeholder' => array(), - 'style' => array(), - 'checked' => array(), - 'onclick' => array(), + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'data' => array(), + 'placeholder' => array(), + 'style' => array(), + 'checked' => array(), + 'onclick' => array(), ); // --- textarea --- $allowed['textarea'] = array( - 'id' => array(), - 'class' => array(), - 'name' => array(), - 'value' => array(), - 'type' => array(), - 'placeholder' => array(), - 'style' => array(), + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'placeholder' => array(), + 'style' => array(), ); // --- select --- $allowed['select'] = array( - 'id' => array(), - 'class' => array(), - 'name' => array(), - 'value' => array(), - 'type' => array(), - 'multiselect' => array(), - 'style' => array(), - 'onchange' => array(), + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'multiselect' => array(), + 'style' => array(), + 'onchange' => array(), ); // --- select option --- @@ -2400,4 +2462,3 @@ function radio_station_settings_allowed_html( $allowed, $type, $context ) { return $allowed; } - diff --git a/includes/templates.php b/includes/templates.php index 2d45d1a..c1adeed 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -602,8 +602,8 @@ function radio_station_author_host_pages( $template ) { // --- check if author has DJ, producer or administrator role --- if ( in_array( 'dj', $author->roles ) - || in_array( 'producer', $author->roles ) - || in_array( 'administrator', $author->roles ) ) { + || in_array( 'producer', $author->roles ) + || in_array( 'administrator', $author->roles ) ) { // TODO: maybe check if user is assigned to any shows ? $template = get_author_template(); @@ -734,7 +734,8 @@ function radio_station_load_template( $single_template, $type, $templates ) { } elseif ( 'page' == $show_template ) { $templates = array( 'page.php' ); } elseif ( 'singular' == $show_template ) { - $template = array( 'singular.php' ); + // 2.5.6: fix singular variable to plural + $templates = array( 'singular.php' ); } // --- add standard fallbacks to index --- @@ -777,7 +778,6 @@ function radio_station_archive_template_hierarchy( $templates ) { // TODO: implement standard archive page overrides via plugin settings // add_filter( 'archive_template', 'radio_station_post_type_archive_template', 10, 3 ); function radio_station_post_type_archive_template( $archive_template, $type, $templates ) { - global $post; // --- check for archive template override --- $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); @@ -911,10 +911,10 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // 2.3.4: add filtering for adjacent show links $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); if ( in_array( $post->post_type, $post_types ) ) { - + // 2.5.0: get timezone for time conversion $timezone = radio_station_get_timezone(); - + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { // 2.3.3.6: get next/previous Show for override date/time // 2.3.3.9: modified to handle multiple override times @@ -922,7 +922,8 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); if ( $scheds && is_array( $scheds ) ) { if ( array_key_exists( 'date', $scheds ) ) { - $sched = array( $scheds ); + // 2.5.6: fix variable singular to plural + $scheds = array( $scheds ); } $now = time(); foreach ( $scheds as $sched ) { @@ -1034,7 +1035,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po if ( isset( $show['show'] ) ) { $show = $show['show']; } - + if ( 'next' == $adjacent ) { $rel = 'next'; } elseif ( 'previous' == $adjacent ) { @@ -1053,7 +1054,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; + $string = ''; $inlink = str_replace( '%title', $post_title, $link ); $inlink = str_replace( '%date', $date, $inlink ); $inlink = $string . $inlink . ''; @@ -1067,7 +1068,8 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- filter to allow related post types --- $related_post_types = array( 'post' ); $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); - if ( in_array( $post->post_type, $related_post_types ) ) { + // 2.5.6: use filtered variable in array check + if ( in_array( $post->post_type, $show_post_types ) ) { // --- filter to allow disabling --- $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); @@ -1102,7 +1104,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- get more Shows related to this related Post --- // 2.3.3.6: allow for multiple related posts // 2.3.3.9: added 'i:' prefix to LIKE value matches - // TODO: use prepare method on like placeholders (tricky, requires testing) + // TODO: test prepare method on like placeholders global $wpdb; $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; @@ -1147,8 +1149,8 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- get adjacent post query --- // 2.3.3.6: use post__in related post array instead of meta_query $args = array( - 'post_type' => $post->post_type, - 'posts_per_page' => 1, + 'post_type' => $post->post_type, + 'posts_per_page' => 1, 'orderby' => 'post_modified', 'post__in' => $related_posts, 'ignore_sticky_posts' => true, @@ -1241,7 +1243,7 @@ function radio_station_show_playlist_query( $query ) { unset( $show_id ); } } elseif ( isset( $_GET['show'] ) ) { - $show = sanitize_title( $_GET['show'] ); + $show = sanitize_text_field( $_GET['show'] ); global $wpdb; $show_query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; $show_id = $wpdb->get_var( $wpdb->prepare( $show_query, $show ) ); @@ -1476,4 +1478,3 @@ function radio_station_override_show_shifts( $show_shifts, $post_id ) { } return $show_shifts; } - diff --git a/includes/times.php b/includes/times.php index 7870e11..1a8452e 100644 --- a/includes/times.php +++ b/includes/times.php @@ -63,6 +63,34 @@ function radio_station_get_now( $gmt = true ) { return $now; } +// ----------- +// Date Format +// ----------- +// 2.5.6: added for localized date formatting output +function radio_station_date( $format, $timestamp = false, $timezone = false ) { + + // --- maybe get time / timezone --- + if ( !$timestamp ) { + $timestamp = radio_station_get_now(); + } + if ( !$timezone ) { + $timezone = radio_station_get_timezone(); + } + $timezone = new DateTimeZone( $timezone ); + + if ( function_exists( 'wp_date' ) ) { + $date = wp_date( $format, $timestamp, $timezone ); + } else { + // --- fallback to calculate timezone offset manually --- + $date_time = new DateTime( $timestamp, $timezone ); + $offset = timezone_offset_get( $timezone, $date_time ); + $timestamp_with_offset = $timestamp + $offset; + $date = date_i18n( $format, $timestamp_with_offset ); + } + + return $date; +} + // ------------ // Get Timezone // ------------ @@ -138,10 +166,12 @@ function radio_station_get_date_time( $timestring, $timezone ) { // 2.3.2: added for timezone handling // 2.5.0: added optional timezone argument for consistency function radio_station_to_time( $timestring, $timezone = false ) { - global $radio_station_data, $rs_se; - $channel = $rs_se->channel = $radio_station_data['channel']; + global $rs_se; + // 2.5.6: remove unnecessary channel argument + // $radio_station_data + // $channel = $rs_se->channel = $radio_station_data['channel']; if ( !$timezone ) { - $timezone = radio_station_get_timezone( $channel ); + $timezone = radio_station_get_timezone(); } $time = $rs_se->to_time( $timestring, $timezone ); return $time; @@ -161,16 +191,15 @@ function radio_station_get_times( $time = false, $timezone = false ) { $time = radio_station_get_now(); } - // --- maybe get timezone --- - if ( !$timezone ) { - $timezone = radio_station_get_timezone(); - } - // --- get offset time and date --- if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $times = $rs_se->get_times( $time, false ); } else { - $timezone = radio_station_get_timezone(); + // --- maybe get timezone --- + // 2.5.6: moved inside to replace duplicate line + if ( !$timezone ) { + $timezone = radio_station_get_timezone(); + } $times = $rs_se->get_times( $time, $timezone ); } @@ -207,7 +236,8 @@ function radio_station_get_time( $key = false, $time = false, $timezone = false $datetime = $times['object']; $time = $datetime->format( $key ); } else { - $time = date( $key, $time ); + // 2.5.6: use radio_station_get_time instead of date + $time = radio_station_get_time( $key, $time ); if ( RADIO_STATION_DEBUG ) { echo 'Time key not found: ' . esc_html( $key ) . '' . "\n"; } @@ -307,7 +337,7 @@ function radio_station_get_previous_date( $date, $weekday = false ) { function radio_station_get_days( $translate = false ) { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - return $rs_se->get_days( $translate ); + return $rs_se->get_weekdays( $translate ); } // ------------- @@ -383,10 +413,10 @@ function radio_station_translate_weekday( $weekday, $short = null ) { // Replace Weekdays // ---------------- // 2.3.2: to replace with translated weekdays in a time string -function radio_station_replace_weekday( $string ) { +function radio_station_replace_weekday( $weekday ) { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - return $rs_se->replace_weekday( $string ); + return $rs_se->replace_weekday( $weekday ); } // --------------- @@ -405,10 +435,10 @@ function radio_station_translate_month( $month, $short = null ) { // Replace Months // -------------- // 2.3.2: to replace with translated months in a time string -function radio_station_replace_month( $string ) { +function radio_station_replace_month( $month ) { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - return $rs_se->replace_month( $string ); + return $rs_se->replace_month( $month ); } // ------------------ @@ -425,19 +455,18 @@ function radio_station_translate_meridiem( $meridiem ) { // Replace Meridiem // ---------------- // 2.3.2: added optimized meridiem replacement -function radio_station_replace_meridiem( $string ) { +function radio_station_replace_meridiem( $meridiem ) { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - return $rs_se->replace_meridiem( $string ); + return $rs_se->replace_meridiem( $meridiem ); } // --------------------- // Translate Time String // --------------------- // 2.3.2: replace with translated month, day and meridiem in a string -function radio_station_translate_time( $string ) { +function radio_station_translate_time( $time ) { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - return $rs_se->translate_time( $string ); + return $rs_se->translate_time( $time ); } - diff --git a/includes/user-roles.php b/includes/user-roles.php index b72e1d4..250fbfa 100644 --- a/includes/user-roles.php +++ b/includes/user-roles.php @@ -315,7 +315,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // --- debug passed capability arguments --- // TODO: get post object from args instead of global ? - if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { + if ( isset( $_REQUEST['cap-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['cap-debug'] ) ) ) { echo 'Cap Args: ' . esc_html( print_r( $args, true ) ) . ''; } diff --git a/loader.php b/loader.php index d184783..69a093d 100644 --- a/loader.php +++ b/loader.php @@ -436,14 +436,14 @@ public function reset_settings() { } // 1.0.5: use sanitize_title on request variables // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( sanitize_title( $_REQUEST['page'] ) != $args['slug'] ) { + if ( sanitize_text_field( $_REQUEST['page'] ) != $args['slug'] ) { return; } if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing - if ( 'reset' != sanitize_title( $_POST[$args['namespace'] . '_update_settings'] ) ) { + if ( 'reset' != sanitize_text_field( $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } @@ -454,7 +454,7 @@ public function reset_settings() { } // --- verify nonce --- - // $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' ); + // $noncecheck = wp_verify_nonce( sanitize_text_field( $_POST['_wpnonce'] ), $args['slug'] . '_update_settings' ); check_admin_referer( $args['slug'] . '_update_settings' ); // --- reset plugin settings --- @@ -484,11 +484,11 @@ public function update_settings() { // 1.0.2: fix to namespace key typo in isset check // 1.0.3: only use namespace not settings key // 1.0.9: check page is set and matches slug - if ( !isset( $_REQUEST['page'] ) || ( $_REQUEST['page'] != $args['slug'] ) ) { + if ( !isset( $_REQUEST['page'] ) || ( sanitize_text_field( $_REQUEST['page'] != $args['slug'] ) ) ) { return; } $updatekey = $args['namespace'] . '_update_settings'; - if ( !isset( $_POST[$updatekey] ) || ( 'yes' != $_POST[$args['namespace'] . '_update_settings'] ) ) { + if ( !isset( $_POST[$updatekey] ) || ( 'yes' != sanitize_text_field( $_POST[$args['namespace'] . '_update_settings'] ) ) ) { return; } @@ -499,7 +499,7 @@ public function update_settings() { } // --- verify nonce --- - // $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' ); + // $noncecheck = wp_verify_nonce( sanitize_text_field( $_POST['_wpnonce'] ), $args['slug'] . '_update_settings' ); check_admin_referer( $args['slug'] . '_update_settings' ); // --- get plugin options and default settings --- @@ -935,7 +935,7 @@ public function update_settings() { } if ( count( $tabs ) > 0 ) { // 1.2.5: sanitize current tab value before validating - $currenttab = sanitize_title( $_POST['settingstab'] ); + $currenttab = sanitize_text_field( $_POST['settingstab'] ); if ( in_array( $currenttab, $tabs ) ) { $settings['settingstab'] = $currenttab; } elseif ( in_array( 'general', $tabs ) ) { @@ -1579,10 +1579,6 @@ public function load_freemius() { // --- initialize Freemius now --- $freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings ); - if ( $this->debug ) { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo 'Freemius Object: ' . esc_html( print_r( $freemius, true ) ) . '' . PHP_EOL; - } // --- set plugin basename --- // 1.0.1: set free / premium plugin basename @@ -1593,6 +1589,12 @@ public function load_freemius() { // --- add Freemius connect message filter --- $this->freemius_connect(); + // --- Freemius Object Debug --- + if ( $this->debug && current_user_can( 'manage_options' ) ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo 'Freemius Object: ' . esc_html( print_r( $freemius, true ) ) . '' . PHP_EOL; + } + // --- fire Freemius loaded action --- do_action( $args['namespace'] . '_loaded' ); } @@ -1680,7 +1682,8 @@ public function settings_menu() { if ( !$menu_added ) { // 1.0.8: check settingsmenu switch that disables automatic settings menu adding if ( !isset( $args['settingsmenu'] ) || ( false !== $args['settingsmenu'] ) ) { - add_options_page( $args['pagetitle'], $args['menutitle'], $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' ); + // 1.3.0: use filtered pagetitle and menutitle + add_options_page( $pagetitle, $menutitle, $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' ); } } @@ -1797,7 +1800,7 @@ public function notice_boxer() { } // 1.0.5: use sanitize_title on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( substr( sanitize_title( $_REQUEST['page'] ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) { + if ( substr( sanitize_text_field( $_REQUEST['page'] ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) { return; } @@ -1864,7 +1867,8 @@ public function settings_header() { if ( isset( $_POST ) ) { echo '
    Posted Values:
    '; // phpcs:ignore WordPress.Security.NonceVerification.Missing - foreach ( $_POST as $key => $value ) { + $posted = array_map( 'sanitize_text_field', $_POST ); + foreach ( $posted as $key => $value ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions echo esc_attr( $key ) . ': ' . esc_html( print_r( $value, true ) ) . '
    ' . PHP_EOL; } @@ -2061,7 +2065,7 @@ public function settings_header() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $updated = sanitize_title( $_GET['updated'] ); + $updated = sanitize_text_field( $_GET['updated'] ); if ( 'yes' == $updated ) { $message = $settings['title'] . ' ' . __( 'Settings Updated.' ); } elseif ( 'no' == $updated ) { @@ -2079,7 +2083,7 @@ public function settings_header() { // --- maybe output welcome message --- // 1.0.5: use sanitize_title on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['welcome'] ) && ( 'true' == sanitize_title( $_REQUEST['welcome'] ) ) ) { + if ( isset( $_REQUEST['welcome'] ) && ( 'true' == sanitize_text_field( $_REQUEST['welcome'] ) ) ) { // 1.2.3: skip output if welcome message argument is empty if ( isset( $args['welcome'] ) && ( '' != $args['welcome'] ) ) { echo '
    ' . "\n"; echo '' . "\n"; echo '' . "\n"; - echo '' . "\n"; - echo '' . "\n"; - echo '' . "\n"; + if ( $found_album ) { + echo '' . "\n"; + } + if ( $found_label ) { + echo '' . "\n"; + } + if ( $found_comments ) { + echo '' . "\n"; + } echo '' . "\n"; $count = 1; @@ -66,9 +82,15 @@ echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; - echo '' . "\n"; - echo '' . "\n"; - echo '' . "\n"; + if ( $found_album ) { + echo '' . "\n"; + } + if ( $found_label ) { + echo '' . "\n"; + } + if ( $found_comments ) { + echo '' . "\n"; + } echo '' . "\n"; $count++; } diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 3bc9e74..4c277f5 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -267,8 +267,22 @@ if ( $label && ( '' != $label ) ) { $image_blocks['player'] .= '' . esc_html( $label ) . '
    '; } - $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; - $player_embed = do_shortcode( $shortcode ); + + // 2.5.8: run embed shortcode to embed external audio URLs + $wp_embed = $GLOBALS['wp_embed']; + $embed = '[embed]' . $show_file . '[/embed]'; + add_filter( 'embed_maybe_make_link', '__return_false' ); + $player_embed = $wp_embed->run_shortcode( $embed ); + remove_filter( 'embed_maybe_make_link', '__return_false' ); + + $embedded = false; + if ( $player_embed ) { + $embedded = true; + } else { + $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; + $player_embed = do_shortcode( $shortcode ); + } + $image_blocks['player'] .= '
    ' . "\n"; $image_blocks['player'] .= $player_embed . "\n"; $image_blocks['player'] .= '
    ' . "\n"; @@ -287,6 +301,11 @@ } $image_blocks['player'] .= '' . "\n"; + + // 2.5.8: full width and height fix for embeds + if ( $embedded ) { + $image_blocks['player'] .= '' . "\n"; + } } // 2.3.3.6: allow subblock order to be changed From c82890a0ec03caffe6d632cea85821fdb452042a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 11 Mar 2024 22:33:04 +1000 Subject: [PATCH 336/377] add nonce to notice dismissals --- .idea/workspace.xml | 37 +++---------------------------------- CHANGELOG.md | 1 + player/css/radio-player.css | 3 ++- radio-station-admin.php | 19 +++++++++++++++++++ readme.txt | 1 + 5 files changed, 26 insertions(+), 35 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 242abc7..fb71127 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -96,6 +96,7 @@ + @@ -103,42 +104,10 @@
    9Xu)gjBfZFs^eYM9Xvu^ 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

    GH}-JjyF=Q^i5OHql<-$VWNzTeMR=#oytTL$%iq z6yfZE4e6JNdM+9DTt62NLXA5LRf%kzjd!9FI*ID|=dX9O^Z);oSKs@ekLzjg z|4e+<$iI2|s(ZiYd)39?JYbiYU6NCpH>2$8wZ{F&V*k6tRpa<-3YPhXPCe*LD@^kJ NG__4#Y2m$N{tXL`mQw%# delta 10550 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 diff --git a/freemius/languages/freemius-nl_NL.mo b/freemius/languages/freemius-nl_NL.mo index b3024ef3e2ea50d1430aaf0c758b9fd4b196f123..37c9c5b1227d406a329bf32d1186b6219d128abf 100644 GIT binary patch delta 8684 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 EU!4TVOaK4? delta 11337 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 eiVA3-HXwOWdO$>I?a)gB7I9kZMFStZ5gzb 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 I%wvQ82W?~_OaK4? delta 9382 zcmZA534Bf0+Q;!7AwpuvKtx1>CWs*-ArZ5frv!Fc_brF9t?BPARO0 zrLhj`I*lV8ms66YITha64pr`M%e}E9<#hDK?RNieEJyi(EuTW2e-_o?%UB-2#tQhr zmc47z7|Ow@2Gy_Wa-6ax33f*Z^rt)ky)YX~;v{T@)6son)CEqVZg>v)=UnBZ5*Djv zdLDv0t`@364N=!k!VTEjMWP$t!YcSXHpJlC3>S9AAvhKr;Cb{zk0{5fhykb$L}5v6 zf&tjtmebIi@<1$(Sy%=~q3-9JOrjR8M4hV}Q$m>#u5jX-bI zjWSR-8ig9EaY&z>85n~*P;2WNa-7TYsB13dh3Y{N>clWCjnU|fiKw~kfI4mbTcX9o&IB|2PJ7f9EubE_?&kgGZ zSOV{18GMXdq{Zu-k@7}OT?Xp7Qy7LHqZ;rdmf`-+110E0J5Ez5i|SEl)YK$n5%#wA zX)FXaq(Ay%rgc1)rR>6yxWty9phm!(@mB}^F&0D6rJg2}Ovj;^hTouioWM%dYEDJH zBF12C%tJcs>_eUZ752i17=#_UdQBXRyk49V6KR{X2pKfzI40vA)JQgK#LCBaa55I5hWsVoR+@@eQB(93+h~7d&SILK z)-+E;J{C2k6Ra~(Ban|8@~x;T*^6q}VN?g+vz|o_@#jdJoUc(+QZd1FAOh8ZXw-FL zT_n0tqOE9yx?m?;PP63!))!E()*MuWm!K|KfO?)+p&EL??td5Eiw`5H{}_4loO>9A zt}0E;;)_G|ybZF>oMhC6T{s43qgs3))!<)I7j_cO`Ch2C5r}F?3)DzFj~a<#s0Ob_ zX2scnwKQv&NOZwJP;aynJOWx=RWJZMV;T;^kMIC$4NPms#c>bnG4*L~8W@G;D951I zOl#B>bU|+D^v5Wijn%bk-XhTjFQA6TvxPYz8Y6WAcE_2hhMva?__;0rgj$S`Q6mu1 z(lj6$!zibt&U2v}G9NWXE2WEM6Ny^98~yMd)X<$n9r#ZykKdq%{1>c*Pp}XAw=&0% z#LAQ>pr&dGY6=Tc*ISKh$Y#|2cA`s*?I?+E^Z{yaF5qY^$+M&p$U(JyEvg|qQ7^1x zs3|&wy6|;W18<^+{x)jPAEB=E6uV+Ip1mv_+lKMi374r*L$0B2@B^y;S6g3%x@s4IHLQpr1vgH_zqTC8g;wW35fP*M+u=RhS8d9Q@(d9#;p826SPD1s_ zg<52bZFvi-LHkfQIE^FlQ`91i@2tnkah4))S7#r(hZ?n3icnMJ+r^AvD2D3!??R#> zAAynR!eHErYS4T59G*c9?N6u?X~s&@i>f7{PO@&n zCVKuak?4T3-ObnH2;`mOtj9FGifgbzGA|MQ1Z!bXikah9Sc7sN*2dka24At|-%t&X z=waTB@yI*ONkx7CILk=X@()qZuUAjgqiU#;X^pyIAJoXaj3Kzvx(^vj=NziXcQ6VA zdYO@Ef!d#jn$igvi;K~vIeL#oH@b}aQn-x)=$UF-9E7@H9L8WessYPU$GwRffs?lW zCi+qKVEFW+2|ypLftspl)D$JBG5&!hSycGqRIG}5s1|QSjmT-#g}%b_c+cw5+cd}@ z)sVKR zbNxMz^WdE^&@9qJROArdZm7kXZud_{y$=detNk$Q>3E2Zuxf_8BQ7VEL~}kGwaOQvM&NB* z{sF5~_8rP-U^IqfKB}Q_p+@95j=;;Pk!v~3bZ8E0YS*F`@h+@@SFoywi^oL?6+T(! z#!<*DI76@n9zib;zKn1t1E4fFr4y2 zY{mVZT_hUHJNO(vMfEsrqf=ke95b=>7@mJGl5r$@G326pvJ9Kx9@O04LOt)lVFL^rYi`sL_1N{sQaBRT^Bk;? zD^P3dB0mo992CiXZ({xKHf6?}xF$D7A3V}j{OHbzswXaeJ}8y}-WJuX5GQ9_RS^4W`JC?Cas#rOe= zT0GTr&G8AS28>5vEW~=a84uuj)X2@{q5-%G%i%$sjHg{BjYwKjdWsYACXSn87FqA9 z=0^G0l==^G5PH6BUR)WdAwPhJ@gi!Z7EN>iGItIm?RI`a2EloZZ{vvR_WQpwBcL9H zV=N|N1m2Dh_<3mIEs22zD2zcyyuyxpe1Tb`k@*$9MyrzSPj=dDvrx`Lnxtfti~27)^aD`r{nb6s^K=Jc=5jYXywI4!lc642Hg9hAbJ? z)1g=fXCR~HEWvVEsgPGE)U=N&(o$(XY`4v~1rzRFlQO?FjIt~MI&q~HWoa7W0 zv3L`;=z>?7hO|J@16F)^?ynq4tC2C|J zxJWc7RaToD#p8>Vlkneo5R-Ak8q=enFpjd%TJu!2!djGbu_SIpjo5Y!#zS`hc}$@E z1UF*Cb^Kk4uFpu+vR&)V*W+QVL-||Okd@nD7G*H1<jNNR$jIN+Ckq{~?@Dy${0`g9R9lr?CQlk16;&x*|xry=8uv zPe9FK0qVwQP;1}`c0$iRW)8byHOdiho4yo((@_(SC&FCEII{$_^uJ6u3H=CE0e$FMu) z$Jhbe9Wg)Q^05r%v#7`ND%QZeM;L$2sozmEm)$Usayn|Y=b(lx5A_^xz;d`5H3Ivv zGG4+E{26tg&oR^UFjToahGPb5L}z0v=DSELk$i)G_#1{`nRm=Py*BCwiKs=FX6r|z zj-Q5V$Wp9}d$BZriMsJE)Ny~H&M$Y|ym%r}9dV`DWH4&+jYD6|v*kk6(7kCrhFZ;M zPz}9c%XjVmM_7V-&lBdcD}`!EBx?01qOO;OJZ&zg2Z=7&7o%`62IC^Ehg&cdKf|(k zA2lM*yQTpls2;}KawpW3WuiJV)$Y$jP3;<6-h-if{!fyq=Qofw_W!o&l+>GN+hY>- zrUeJ;oW;b4L<7qG(Hps>Qxh8z3(4!Dw#iuB<}4GZEO{{(AO3_Mufc@2O_a5Iv)v^= zC~JG2;6ZS23#pqz=v8~i*1d_mTio9tdDQc;xPOvOv-^cseHHd;Bxj-;>lBjoQ^Hn~ zT+^`|hhhQ#s2yxQ$)A|isYKp~yglkSfj0disx6CHuNk{^T8Zf-yG|5znk^i z)b<+jHL;4F4{$WbVN2!2v+WBK&9SyzVkf~%(EXZD!&aPEh*gRI5bqLt?%nGKwegPP zi;n)MlT0BV5{(IM^$qUN{d|rjws6s2$dlyW{>f)8Vmxt?s7o~Gm}lDxa{b+?Et7~M z@+tp`O;NA(3Yz~8L|sB}uC}PHCb5keLitr(PiVVAWK%blc!@k-m2AU^7woxR$=8v; zN!%r0jBnu)9Yb6u%G+aI8z}Upu)tO{#!*Cd>Yr`ll#3|L!c({dwVflnk=G(7kXN+z z&ub^!a$CMfzKC4UzqVz>OJ<+T{kBr257CgybmAxS?+Cpa*O~(zzH{8aT8G4sjQGPISR9+#d<=#W_ld3?qwNjM!A9;9|Mg~_iJNTxmGu)WMrg_h;(68o zcl^mW5)p*9Lqt>JnhJIsi@`(}E}$*R`Wg9QVjkt;*qSJ1_kB#R?F}N3=xFQ9sFJPv zD^#>3u9&j>GlFvJf6M!StUNN#zhP{>266RbnuNzBB*wcnp#z%#;URA za%yZ%RYi-cqNTJ{i`HDhmC+kx(Ff}|abqWLg{`UYfE{o; zY5=D&1+StfHi|VSlKxFo3YDoyLp?Yj195^A&qj5$05!wqs72a@A-E6A;c2J-I`W@+ z#6R&E9cSC6pe8f`18|t?>EDc_pb^f&lDHbZaU(XuZBBdxHLyFV2YFPt1M)|zOelt8 zYt#z%Lbc064R|=_;6zkEzoUC8g$ESc2d{?1UMZjR|-H%i!Nw9?R6WGp~-`#EomS{$(k&qQVPPoeOEGj{Bhxj&kY?QSDyE zQaBw;<2+PHE1ml7r~w{8&GaIwpQ|_rAEF<=9?$yg!hAMLZa{zBkG^;uOXD}FCA@-K ziF?=!%ha(m8iKk$7B#S$sDaN%4R{%9>sF(-Xgjj#=7gI`y+rBPh(JFc|8pqJSAzz| z^u{Zgg{>PJQv%nZwqO(L-0ns#{eD!kg>gjG?iJKAo`@Rga@2k69Cuk964X*2#Zu5{I4814m*P9F6pC4&p$3 zjKi@%(+|Tls1>`7?g9#TDTLy%=5`6EqOy1m2IE#Nhlic`JB%X!6}8ksE$j+}BI#hN zAo*;b#}u55n&@dv#?+R^sNZ=lsn(tawz3`6MeS)@)LyT_XK(}7#@nbZifC=OCdx4$ zD^Z_>nn)Td$wr{ge<5n1ld%lWL{0FW)^1x^M}=m%9kqwYko+=dPtcqF{r(b!)h3hwJ;TxGj7zWn2+kuy_y2=1oHu^!yj=dUPEQ;^KI-*Porjf z9wXEa)zMSbOue49D;IwQ24kOyy z509!CPn?804TJCnEX2?76e{@^x3dHP0xP#ks+(()z+?d@aK zUYF=-2M~faiCbV59E9p%CTdCdI@hmbEOAIDW4dB9)WBz;wr-9SuSK1TEm#9DVIch* z-{!R#A%NG(T8{#24Ws6tEW2k3sLP?U=VIXZN)y+bB|*Qynt>U zm&+7h#7C%xJv!SZ?}zFr50%vuP)oNMJ#mF|{XNvcH=_o$6HDO{)WA-o26Pp*#lPVw zjP1huXH!_z#V(;Y>!%UcL5;ixYCv641MTBD8kGaHP#v#8CFOe5%nqY39!KTYH>fST zfb?xVQtW3;N($?*8+K5k5${2D{28kLTc`dK`V-$ob@%`^Fw@QMaRBOi4C?-RI1it} zxA7d-#hmVTtKUWCzup{bu?rs#~DC8i!VCG|cyoy@F_!sO08auW?EomDk?%>2- zu`Ttf7>ouKY7>`R=6s^5v)lKqZ{P%}M- zO40-}STk&j%9VCboQ@h$HtIQU9E?-(MVE6OtT+0*_cSY8AI?IYOf!o_SUzT zUD|NehfW79hgqm3E5tBdiJI76)M@zzHPBlagZHqD&VTqzcBuxTl4LAuPuF7(9>tcJ z)Y~p)0ji_f$R)EA*)0=H;*bVrAlAW;9IsthM*f;zXUs0U`DULYgU7biN-L|vbU8rX8w zaed#pzQ^$}s@+LcyYGr+|F2W{oFV>+E4lFTFkZhnAcs#Y7Y?^eyl8~Y{_7aW^+qG@ zlK%^p6NT6W=io9th7Gwtdla85#LIK-{e4HQFuzl zaSR=2M|KsJ{SVMr_2cb;s^Bc*PDmb^Z?OeF!!jy~Cpdn9K`i|h)L!44z~6zy9uxUe zLcDR3{R1R-G7auiaeK0T4_tfIE@k8t+tFgwh-caM3IqHeYY_K;!`8on^@$Imp5uN%A(TQA>l%%zs2muLdhjB=fS;o} zT>Ykf$A9K{8bc3d7+=D-@J}3$(+KFtZ5~qaq9SsRO{%J>nWteb z9EO_FB2-eI!cO=IwYP1H?2D%dDt;N&E)SI(?_v$D1g4?)JbS(0+Zr(YKaE098qUFP zcpW1!X+E!JZs>`c`Je^%i^EJDLVOP+F=HV=R^S9o#{<{}qZirZoP)tUXC-Fh4wmB~ z*DJn*^l!>9wGRq<*Jg2D45mH=*Z@5o!5*GZMw;cY3TG}fg*sZv@)ef{JjYAknd+f&bKHK@d2sdqK zowV0AcGx{^i%PE7FbcoI=6DCyuI^4dfaj3yH!mQ&XKv#@d}9|UhmJab$oro7laClQ z@qpd-Gi3K3`xVS@FP}49&va8@OH3?xe}*-23I2hBID5a{nq^pp_-o9^2dE_+@v&X< zW2p8wQMvONt7E_c`>EI1u?;q&J_SS3J%s{!VwPe6CVygQ*bOzXm#`%EMLjqZl?(Y8 zjiEWpd?iM@}I3obW_LP5#1h4rtD3qH4R zxLr7a_#$fNZI0R(N>7X?o{O6K4lIpVaTWf7-EqM&dz^0|pKqq(aXW#9IFfiXX3@W? z@`deSI%?!ga3-EZbui$BJra@B`FwKY?x?C(pCCVQJLT#9|EAMBR{rwQz`2KObii??J7^bLZ^Ob3@Ua zco8nZ6*wE4eQQ_ZIBIL3qE>qNdG@~rh1uur3Y)PLzJ(fy#|69eahOJ&iOTMM zsP;djW^~7?kNeJc*bO77pM*cMCE;AITO ze8;!2D)D-(h$pZE-bAfX!z=dtekb%I9)vA%C>mzE5&xvP>8gG1s%!SsbT4+H-hGdP z9@K^-PzigXj#nP~<5X0~b5ZU7?bIJZCFdDjNJpN(@LI)puG^pA9^J6Vx!|V#Vd4M= z(Z2mH``s@M8@t&5YzjIKtNz>WS%us7$Ec1tkNV}9jE#S_OICnP-h6~gvZcTAr@OBI zZU;OOwI#E#c)+NE9>Ib5CmzGTcXW!}WH}q78_uI{c;Z<0u6C204@K2)Lfv-~>){=ofidid>Nn#=`Zr%u(B7rqw++)!IWPlD;7aV};uVXD z#GViBpVLX$o_IFu6rICtEdR)UzE8%Z#4B(Y4t;DtWn=&1gNk?qx=&DO`ow-J`8>7n z^!`|t`tdjfS71L3_}l*ZoP(o?-^QlsN2blfXHoBy(-?r?<21a5oMb*3UBxTW&BJB3 z5Kr>p{o95@15a1+_w_s+Pdv@bRs1pRUBXqIr9F^)%`#-w&5DvP6H7eb+hy{I_xZTY zo0#b9GOy!d`~g$_T*W!mu#~I#{2xUm7y37~E z-S`674k_a(Yf$1kO7bOIh$T+UVef7P5sC0`8R-+V9?Z(?OELUphe_23_|Ci;b18{xCW{jfi- zM!gYzc@36BcQp#yvu2orZ7~q%Ij%-McsuHb?;Za@-RB$bD$e$@sMC>%O6HE}k6lss z_eKqTGJ4=tOrU==gTiPkcB5ugFT&a#pCKNBTDomWpJp%Wbe!eK{f77(YGBnW*$$dv zBylSY!@j5iHbBj!05!nZP#wO9n$aHo z2|q>+Y*l4jzZY8*A3&|BN0eQ$U~EQQ5u@qfq^W>ood!!#d-NXafuA_>1=IlUqXt+Z z+76@zYDTH3_F1UsxgD3I27C}rRJBo2xj9i8{rhK*8WlBWt53YYYimKB4ef*LHHxp7 z5T8&ls(!ts22E-uBqS6ij_K!_F?@K@!Z8ht&W~y6IXXA1s7zi}|IQi129C}cn3)un zn^DvtZ*Wm`euc=G+(Cm!#kU-onVX)QnV*|Jcv$w}VVTXGYBj0ul+&~RbGzbeY8>k{ z{(su)lA4Cc{GW~fxt#D%x5G0=W)92!k3&rV=Q_`cW2#%! ztZq#$TB^8BqpFlrdRr|@f1mGJ%l&)(_v=31>sfoBz1LoA?R9oe@8x5@tFQXHbBg+A zI&7u#I8GV-CCG8kQr;h-TF3dTisLlKM>qoO#yQSl+>8UTSXIaAgcC6a&tO6P&H4=U zk^5J3oT69+YhpBh>^N?x7ll+Rf~q@C6pq4Bd=CRK8~yN8^vC0<6Q0BTcnJ&O_cp&} z^ZVF{`bXFT>%^M|%)oZ!i_wScJ3mnKS`Y3r9^dGaktXE|S?j&l;t4FzK%uJ43V(2lBD6q};%uq)~g`(O=BxA}5Z!`7mD zW*4eK2e1O3#FF>`)r0;?=C}}4gClSxCZJmvT0>zaZo(97!PV3w3vm-}Lp7`i16LX| zFcN2@=FAr4=A09#Y4Zfruxu^Iq4ym(7QyXU0*_)Lyj+X^*B#!Xq8Q#sU-V5jckYL} za1m73N89>1)NwUXcU}hru{r8Ooo)R?Hal8ad!xE; z6sn7-qAu_jstYqwJ+T3M;8D~am8oO)S4K6g9;)FjPz~;Y8oI7n5(l~|RHPu8f6hkK zV{`@iAJ3clU|rOO8=-ov1L{OQP&Y6H)!=a$jMGrZWub0h6{-Q7P{(JZde*&{!bA#Z z@I7o+&z$HA>Vr2i1RtQf&ab{XaakNk9)qE{40Yj;t*20To{Re2&!{=|6iZ{72Hu9a zon#7osc4E>IskiNW<$CMA7EbW-pC9=U-Ti*Kz02n)MPt_I<5+sSGwU>=NW z&aqe?d7zweI0(1mC@h|0hHMtP^HQ;j!X#XeYCu>E(}l^XS==2pSq7lSc!JI6VL16} z%#TMgz=AA0W))v&KoC%%L_agNP@LJi?v)Oiaq9y+c#DlcuV;HD5tMI!2s+F$^7w5Fmi zl!0p4D2%{2u?%iRe>{%*{5jMdxQH6sZ!rdQurm6#HFKsq29mp5P|yXtVm0iKx}XP# zV-{+*=51&0U?%EL-$6ac9@K@lq3&!qs^^ZN8g>eGp^I1vucI0s*xq}b+li&1E>1+< z!CdWH-#^D{*(-7K$#|Y!_1kS*CY~9f`{B2nl&k3i^-x_p0=0h`mdE2rr#Zi%8eX@n8M>yZ zya!grG_0h@VIc)IU>}CzDb#_tQ4RUc=B{q0OZ}|HQC%H|`g}z!g2}eN6>11NVG#C3 z4aG>*d8eZH`JYQckIN$Df6f;E(SbhQO_vuzU8o{zR>z}eb8FOG>16kJLp8h~szHNM zb7CT@VKY$;T8uUDgYNYISPGY@$iSCb(A9-|P%Zo()$)6&2K|F2F|enxJZcU!L|wQO zYEt$>P09(VA)ShvTW_JxKNop2on1ZY|A7=VGxUK$s1^@LU3ikMf7{kCL_IAlP#4^U zYS<18#zS`hMbzhiz)buvF2*^%%~KZM#|(9QHw8TwqflM5%(@;q*4d85F|e;$f+MjU zc@1oeolzHFf|@(oI19hT0otExo|e6+8@!3SfxD<7bU&t0j)JS7`4S1oHsmcZ1s9{b zFb8#lTh@E1F8$r+k8J)AHlyB`^;_Q=tx#j%2TR~c)X=_(G{Eg-+KLsZ$7lV?p$JJglPnHH$vdNNYy|q?QdC3V$7rtaY@nbn zKaJ|DP-bK`tc+pU3rFH)Y=C!AT^T#bT&OWJp`F3V8s!{A7CWcpV6)H+vo6Eh)SpBh z?=yt{Po&V7LOooB^piNPIQ_3X-b_Uq+>h$&%UBohp@txKgt_xH%uBu+OW_7o4;?{u{Wa8u z{zP?s8M>`B*0gp+4b=!Nk8?-T{~DWJRMf&8RM(amWe$u(O`?}kJv9K;z$K^)euN=- z47CtlMcv4+sPhJlHs2MsPz@P^I&K!KrEs z1F#boz<$^khoBzkJ*YdqgzBk3Q0EOAYbI~F)g4bk2Qt_mQ|BW;X8Q0sRoya=^a59q%>rF9Toi^1x1>3O#$Ni1!`smlq+-Qo)1M5NhML{2F%CzghGrFNl5WF^cmipo(|CsY8}cB2O#TRc zFngw%Gdt0($@3`%b@69d6)&Mqm~WQ3U>NESU&1<=Vf_%hlK+57So=-h3vmQ)#>ZF# z*S}>Jo~x*a2hTP)+H^Mk-QrnWpDbQR~YxjKm#o3cAA!sIk0> zvG^xe$H>LzaqNV_gb-v!H24vtY8n_NCk;g7I_3q9T z5~!GqI?*B2*t?dQKQu~VGL6z`$VTVRFb zP;?@YPINm5D2$?_-TUT3htZe(25M5>M%{VXN;7F{pzbIQH3ybrD?EZ4+kjPO0SZOs z@u=e(V0|2jmDCg1-R0(0X|*|^!Wz@!cG#5zdtiIqj-}DHmWjp(%VH#XwRL)dV4=Zb zxF5@5#r2NU2vaZ>XJZ?@g?gN8ZeYkc&rnQvQ<(Z8&n7#rZ!~_l$(-oYW;2T)qUK1& zE&MHjb+H7WtGd!?{?WS1F|J|CMoG8Q%FnaDjjD{&I0?6zOe*nrPnL#?3UpO~kr z*&f=ZmLJ_?e#>dOmse!+wKx;w_L*NUcH%MeQlFalfE?^kUUt73vT4XQop*3LHafsf z;W1RtZaHX%V*Mf0(7cEF7NsGD4%06h@Sl`D+7;w}~t~MA>z69&xUes|9 zQM0@#ty_X+ka_6rMy}~}{>*V+b+Nb|r#H#x{F}K&UiowL9Wvd0!n})}$HN>@<)m4o z?;=&s16+aoPMPPq+i5d2gD{f%6*v(OVGm3`W4e41>iOS^Me!(B#EV!EpIP1hUzjzz z1nT*0jcYI+3t@pT%^ilI4|xP?^2DG{Tm|)bHpEic1*_sH)N!j&7u2n%%Qd$7iE%Xs@lmhwFz0m-`^|iv&crtC*pE7qKh4vL!m%(mzyNHEx^OSlaT9I* zeAMJzj!U`FDeR2{{xCno9pS1e>~Hj^`GI0K25BA_`pdlgg<&l|kceYAaM&X=W;xi6 zJm@iZi-WN-KEp=X=n0P!PQxXb{x|=L4xgbK+~TPjk}l|NFsh;Ragd(>0~AhE5yP{x z4IiM!@PmKM2R^f2L7nhEM&MH{iedC?7ABxRcM3I^?qWksa+&&ZsLy4gCgWaw+fCt) z-7!9o%b7;L1bwlTk2x?5OOkiMyf_qlxL9JbCiy8}m-iP?S6-L5i@RZc>Q`X~euw3- zRX!Ju<1xfNn3Uh;xMxzhOJN?4Dd2LxM90tN{ej>tmLiSycX=mWGaN=f1pDDdERQt{ zy1YN*^}*WY=W#w33~+hZlVwzilU3dlS1@ivoeJ%^Rob6P!#HRQd^}gPqu*>`7 zb32k&)PISZq@lcr(P_>g{GEIV?^ET;`xbLK6Ub-r<-P!);#`~;>~eCk6t7L1Lr+l8 z|GOc)f9XVlC0*XX0}8Vk=)gi zeThxQ@zkF~-Dyg=%lWr*?1WhnF7M>Kg9XS-xg%ZPg(C`UQSmbB4ribaIFFiSS1ofGWAW-hrBC>VNcZTehsxCZNgYQZ~YrzBCo)ESbub9P$*5|9P07-1@#?}C&uOd z&*TB9=fAf#19jplsN>gJ_n|&_4)qjVK{fm-YBC2gK6-kBQJ;@Q8t!&lQOH9@Tda;9 z@Kv0Sx}(3WB`dhRKg%VfdS)^*O3qBw)3LIm%e&@(g5DcJ^-LaK_BH$SVJOC+8qg5) z>-irP{NPng+H(eQr4Ff-~?szJ+Sou*#->CN?6U zjq1^p=%?rZGKIQ$9W};bRg8^MUD_WtM8i=hoMZDfs2)0qYG4kkA--|uhDxE1k3*fe zxpgq=xVMq{&$p5eqRT8c>GJC%({zIMKcAsy7 z6m)}H)>{%+DX$~)6I#+&a($-@J35m*-`=CJhq`%0FUtSIKZ!;5fQ@*Q$g6tHhc9lL z6Z-S&`KJG4%W9Hv;x+0kVnIUNTWa1A3R;97vU7=Q*`f^2KQElaO1 zTS-qs|NY6k=v-rm770J>t-W@0k>{~@rK(Zn-H2ZaZ6%0}w){5bNL8}kwskFVDftMz zfwS;1F`064&YP^~FPB6w42KA9!>H5deKzqNahPNmA_Ye#1zDyiuk8;0a3stCOVe>A%3Dji~ACO-mirHht0rIvu$kzQrUY(d^b1|EE zM4bmW;R!<9Xk0?POXM|GZf7XT&xE$g)-bE;ZWBQ&u=(41y;$ZDsl*ZL$6|Z@mAFi~ zFg>9qSj((d&@IGA#CPn|R)FY1+57vCzSrL(;hn>oK@1=&Q@2$IQof6y;VpcJ_=vK$ zd|YIcDSH2XlYiT@?c!CYk83&+o+t}J;TqPeXvz%hL(U$h_n$=3HmUb^+CpW8_w`NSUZ#2RGp zAdY+y`(Lsj+(v%Vme*Qc9NUU=JTaH>A=lgQKlWT>s0$~wT_Zx-uhW+%e$(%NA5z#! z?B^g}1HEs&_sARB54}NI+jm40TUUhgZpwx5HR1=#H;EmT-^Bj-e0zz)S?aze&QM;A zzuBEL^^7+p=|r@%mCLD1vE@qE=iFsqewu&bc?HH$$E$-gj@V9U>wpU|5estsR_srl zpsoh~fGY_1?!~e1s8CZ)LPWV#aj{SbP#_s>#`Xc{+;e0={ zV?22jG0_}E|J!X94zvd!#G~Xr?7sQfl2}T8Gn`@TYm%?0+>c1NbrY%UNsJ{wMSM-U z4erG<#2>_LVheFV&)-4&!RLI+j=Iki97R+hLWnf><>ijGy+ZjZkx0254#h(3dmq0d zA5MHpY#@Je8}UMNOXIKYC!)Bw#Pe(~@VRpK-ylLMPDdYmOc8s`D(dbK0o1P~c2d?B zOdM9hZdGtS`<@UvlBN5GA)&1}`<@YXhz{guy|s?_FPck~!-&6# zDeOOQ_kBY75Yd1<*4BwRL}%(3V{M`Yb4OJi2Fogq8JgQ+xp!eoX=X29m^>eLWNsVmc`6w-N*XNO2Q;H@e##K*>i?1CXpIj#~zH;>% ziJnT$(;_{oe+1;2 z9iH_@X;%r)l=ljH)^9E0i5*?k6SUJeYih77(9>>ou)&Cbr< aHm|A^88bH0bL~X5XZyKgyDpCQ-Tr?mrCw72 diff --git a/freemius/languages/freemius-zh_CN.mo b/freemius/languages/freemius-zh_CN.mo index 6ac09184a8cf8253b5a23eeae6fcc69e0fe4d49f..d4f4fda1950f5c572e02489060430d3949d7d773 100644 GIT binary patch delta 19735 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 delta 13887 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 G`u!g!Aoo=O diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot index 3bdefb1..ff30eda 100644 --- a/freemius/languages/freemius.pot +++ b/freemius/languages/freemius.pot @@ -1,4 +1,4 @@ -# Copyright (C) 2023 freemius +# Copyright (C) 2024 freemius # This file is distributed under the same license as the freemius package. msgid "" msgstr "" @@ -8,873 +8,881 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Freemius Team \n" "Last-Translator: Vova Feldman \n" +"POT-Creation-Date: 2024-04-22 10:16+0000\n" "Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" "X-Poedit-Basepath: ..\n" -"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-KeywordsList: get_text_inline;get_text_x_inline:1,2c;$this->get_text_inline;$this->get_text_x_inline:1,2c;$this->_fs->get_text_inline;$this->_fs->get_text_x_inline:1,2c;$this->fs->get_text_inline;$this->fs->get_text_x_inline:1,2c;$fs->get_text_inline;$fs->get_text_x_inline:1,2c;$this->_parent->get_text_inline;$this->_parent->get_text_x_inline:1,2c;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" "X-Poedit-SearchPath-0: .\n" "X-Poedit-SearchPathExcluded-0: *.js\n" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: includes/class-freemius.php:1748, templates/account.php:947 +#: includes/class-freemius.php:1781, templates/account.php:943 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" -#: includes/class-freemius.php:1755 +#: includes/class-freemius.php:1788 msgid "Would you like to proceed with the update?" msgstr "" -#: includes/class-freemius.php:1980 +#: includes/class-freemius.php:2013 msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." msgstr "" -#: includes/class-freemius.php:1982, includes/fs-plugin-info-dialog.php:1517 +#: includes/class-freemius.php:2015, includes/fs-plugin-info-dialog.php:1513 msgid "Error" msgstr "" -#: includes/class-freemius.php:2428 +#: includes/class-freemius.php:2461 msgid "I found a better %s" msgstr "" -#: includes/class-freemius.php:2430 +#: includes/class-freemius.php:2463 msgid "What's the %s's name?" msgstr "" -#: includes/class-freemius.php:2436 +#: includes/class-freemius.php:2469 msgid "It's a temporary %s - I'm troubleshooting an issue" msgstr "" -#: includes/class-freemius.php:2438 +#: includes/class-freemius.php:2471 msgid "Deactivation" msgstr "" -#: includes/class-freemius.php:2439 +#: includes/class-freemius.php:2472 msgid "Theme Switch" msgstr "" -#: includes/class-freemius.php:2448, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 +#: includes/class-freemius.php:2481, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 msgid "Other" msgstr "" -#: includes/class-freemius.php:2456 +#: includes/class-freemius.php:2489 msgid "I no longer need the %s" msgstr "" -#: includes/class-freemius.php:2463 +#: includes/class-freemius.php:2496 msgid "I only needed the %s for a short period" msgstr "" -#: includes/class-freemius.php:2469 +#: includes/class-freemius.php:2502 msgid "The %s broke my site" msgstr "" -#: includes/class-freemius.php:2476 +#: includes/class-freemius.php:2509 msgid "The %s suddenly stopped working" msgstr "" -#: includes/class-freemius.php:2486 +#: includes/class-freemius.php:2519 msgid "I can't pay for it anymore" msgstr "" -#: includes/class-freemius.php:2488 +#: includes/class-freemius.php:2521 msgid "What price would you feel comfortable paying?" msgstr "" -#: includes/class-freemius.php:2494 +#: includes/class-freemius.php:2527 msgid "I don't like to share my information with you" msgstr "" -#: includes/class-freemius.php:2515 +#: includes/class-freemius.php:2548 msgid "The %s didn't work" msgstr "" -#: includes/class-freemius.php:2525 +#: includes/class-freemius.php:2558 msgid "I couldn't understand how to make it work" msgstr "" -#: includes/class-freemius.php:2533 +#: includes/class-freemius.php:2566 msgid "The %s is great, but I need specific feature that you don't support" msgstr "" -#: includes/class-freemius.php:2535 +#: includes/class-freemius.php:2568 msgid "What feature?" msgstr "" -#: includes/class-freemius.php:2539 +#: includes/class-freemius.php:2572 msgid "The %s is not working" msgstr "" -#: includes/class-freemius.php:2541 +#: includes/class-freemius.php:2574 msgid "Kindly share what didn't work so we can fix it for future users..." msgstr "" -#: includes/class-freemius.php:2545 +#: includes/class-freemius.php:2578 msgid "It's not what I was looking for" msgstr "" -#: includes/class-freemius.php:2547 +#: includes/class-freemius.php:2580 msgid "What you've been looking for?" msgstr "" -#: includes/class-freemius.php:2551 +#: includes/class-freemius.php:2584 msgid "The %s didn't work as expected" msgstr "" -#: includes/class-freemius.php:2553 +#: includes/class-freemius.php:2586 msgid "What did you expect?" msgstr "" -#: includes/class-freemius.php:3641, templates/debug.php:24 +#: includes/class-freemius.php:3685, templates/debug.php:24 msgid "Freemius Debug" msgstr "" -#: includes/class-freemius.php:4755 +#. translators: %s: License type (e.g. you have a professional license) +#: includes/class-freemius.php:4828 msgid "You have purchased a %s license." msgstr "" -#: includes/class-freemius.php:4759 +#: includes/class-freemius.php:4832 msgid " The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box." msgstr "" -#: includes/class-freemius.php:4769, includes/class-freemius.php:21125, includes/class-freemius.php:24783 +#: includes/class-freemius.php:4842, includes/class-freemius.php:21174, includes/class-freemius.php:24859 msgctxt "interjection expressing joy or exuberance" msgid "Yee-haw" msgstr "" -#: includes/class-freemius.php:4783 +#: includes/class-freemius.php:4856 msgctxt "addonX cannot run without pluginY" msgid "%s cannot run without %s." msgstr "" -#: includes/class-freemius.php:4784 +#: includes/class-freemius.php:4857 msgctxt "addonX cannot run..." msgid "%s cannot run without the plugin." msgstr "" -#: includes/class-freemius.php:4786, includes/class-freemius.php:5978, includes/class-freemius.php:13730, includes/class-freemius.php:14469, includes/class-freemius.php:18281, includes/class-freemius.php:18394, includes/class-freemius.php:18571, includes/class-freemius.php:20856, includes/class-freemius.php:21955, includes/class-freemius.php:22971, includes/class-freemius.php:23101, includes/class-freemius.php:23231, templates/add-ons.php:57 +#: includes/class-freemius.php:4859, includes/class-freemius.php:6051, includes/class-freemius.php:13828, includes/class-freemius.php:14575, includes/class-freemius.php:18330, includes/class-freemius.php:18443, includes/class-freemius.php:18620, includes/class-freemius.php:20905, includes/class-freemius.php:22020, includes/class-freemius.php:23036, includes/class-freemius.php:23166, includes/class-freemius.php:23309, templates/add-ons.php:57 msgctxt "exclamation" msgid "Oops" msgstr "" -#: includes/class-freemius.php:5065 +#: includes/class-freemius.php:5138 msgid "There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn't work, contact the %s's author with the following:" msgstr "" -#: includes/class-freemius.php:5645 +#. translators: %s: License type (e.g. you have a professional license) +#: includes/class-freemius.php:5743 +msgid "You have a %s license." +msgstr "" + +#: includes/class-freemius.php:5716 msgid "Premium %s version was successfully activated." msgstr "" -#: includes/class-freemius.php:5657, includes/class-freemius.php:7692 +#: includes/class-freemius.php:5728, includes/class-freemius.php:7765 msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." msgid "W00t" msgstr "" -#: includes/class-freemius.php:5672 -msgid "You have a %s license." -msgstr "" - -#: includes/class-freemius.php:5961 +#: includes/class-freemius.php:6034 msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." msgstr "" -#: includes/class-freemius.php:5965 +#: includes/class-freemius.php:6038 msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." msgstr "" -#: includes/class-freemius.php:5974, templates/add-ons.php:186, templates/account/partials/addon.php:386 +#: includes/class-freemius.php:6047, templates/add-ons.php:186, templates/account/partials/addon.php:386 msgid "More information about %s" msgstr "" -#: includes/class-freemius.php:5975 +#: includes/class-freemius.php:6048 msgid "Purchase License" msgstr "" -#. translators: %3$s: action (e.g.: "start the trial" or "complete the opt-in") -#: includes/class-freemius.php:6971 -msgid "You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s." -msgstr "" - -#: includes/class-freemius.php:6974 -msgid "start the trial" -msgstr "" - -#: includes/class-freemius.php:6975, templates/connect.php:218 -msgid "complete the opt-in" -msgstr "" - -#: includes/class-freemius.php:6977 -msgid "Thanks!" -msgstr "" - #. translators: %3$s: What the user is expected to receive via email (e.g.: "the installation instructions" or "a license key") -#: includes/class-freemius.php:6980 +#: includes/class-freemius.php:7053 msgid "You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes." msgstr "" -#: includes/class-freemius.php:6983 -msgctxt "Part of the message telling the user what they should receive via email." -msgid "the installation instructions" -msgstr "" - -#: includes/class-freemius.php:6989 +#: includes/class-freemius.php:7062 msgctxt "Part of the message telling the user what they should receive via email." msgid "a license key" msgstr "" -#: includes/class-freemius.php:6997 +#. translators: %s: activation link (e.g.: Click here) +#: includes/class-freemius.php:7070 msgid "%s to activate the license once you get it." msgstr "" -#: includes/class-freemius.php:7005 +#: includes/class-freemius.php:7078 msgctxt "Part of an activation link message." msgid "Click here" msgstr "" -#: includes/class-freemius.php:7012 +#: includes/class-freemius.php:7056 +msgctxt "Part of the message telling the user what they should receive via email." +msgid "the installation instructions" +msgstr "" + +#: includes/class-freemius.php:7085 msgctxt "Part of the message that tells the user to check their spam folder for a specific email." msgid "the product's support email address" msgstr "" -#: includes/class-freemius.php:7018 +#: includes/class-freemius.php:7091 msgid "If you didn't get the email, try checking your spam folder or search for emails from %4$s." msgstr "" -#: includes/class-freemius.php:7020 +#: includes/class-freemius.php:7093 msgid "Thanks for upgrading." msgstr "" -#: includes/class-freemius.php:7156 +#: includes/class-freemius.php:7044 +msgid "You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s." +msgstr "" + +#: includes/class-freemius.php:7047 +msgid "start the trial" +msgstr "" + +#: includes/class-freemius.php:7048, templates/connect.php:209 +msgid "complete the opt-in" +msgstr "" + +#: includes/class-freemius.php:7050 +msgid "Thanks!" +msgstr "" + +#: includes/class-freemius.php:7229 msgid "You are just one step away - %s" msgstr "" -#: includes/class-freemius.php:7159 +#: includes/class-freemius.php:7232 msgctxt "%s - plugin name. As complete \"PluginX\" activation now" msgid "Complete \"%s\" Activation Now" msgstr "" -#: includes/class-freemius.php:7241 +#: includes/class-freemius.php:7314 msgid "We made a few tweaks to the %s, %s" msgstr "" -#: includes/class-freemius.php:7245 +#: includes/class-freemius.php:7318 msgid "Opt in to make \"%s\" better!" msgstr "" -#: includes/class-freemius.php:7691 +#: includes/class-freemius.php:7764 msgid "The upgrade of %s was successfully completed." msgstr "" -#: includes/class-freemius.php:10441, includes/class-fs-plugin-updater.php:1100, includes/class-fs-plugin-updater.php:1315, includes/class-fs-plugin-updater.php:1322, templates/auto-installation.php:32 +#: includes/class-freemius.php:10527, includes/class-fs-plugin-updater.php:1097, includes/class-fs-plugin-updater.php:1319, includes/class-fs-plugin-updater.php:1312, templates/auto-installation.php:32 msgid "Add-On" msgstr "" -#: includes/class-freemius.php:10443, templates/account.php:411, templates/account.php:419, templates/debug.php:399, templates/debug.php:619 +#: includes/class-freemius.php:10529, templates/account.php:407, templates/account.php:415, templates/debug.php:399, templates/debug.php:619 msgid "Plugin" msgstr "" -#: includes/class-freemius.php:10444, templates/account.php:412, templates/account.php:420, templates/debug.php:399, templates/debug.php:619, templates/forms/deactivation/form.php:107 +#: includes/class-freemius.php:10530, templates/account.php:408, templates/account.php:416, templates/debug.php:399, templates/debug.php:619, templates/forms/deactivation/form.php:107 msgid "Theme" msgstr "" -#: includes/class-freemius.php:13549 +#: includes/class-freemius.php:13635 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" -#: includes/class-freemius.php:13563 +#: includes/class-freemius.php:13649 msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." msgstr "" -#: includes/class-freemius.php:13568, templates/account/partials/disconnect-button.php:84 +#: includes/class-freemius.php:13654, templates/account/partials/disconnect-button.php:84 msgid "User Dashboard" msgstr "" -#: includes/class-freemius.php:13569 +#: includes/class-freemius.php:13655 msgid "revert it now" msgstr "" -#: includes/class-freemius.php:13627 +#: includes/class-freemius.php:13713 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" -#: includes/class-freemius.php:13701 +#: includes/class-freemius.php:13799 msgid "Invalid new user ID or email address." msgstr "" -#: includes/class-freemius.php:13731 +#: includes/class-freemius.php:13829 msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." msgstr "" -#: includes/class-freemius.php:13732 +#: includes/class-freemius.php:13830 msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." msgstr "" -#: includes/class-freemius.php:13739 +#: includes/class-freemius.php:13837 msgid "Change Ownership" msgstr "" -#: includes/class-freemius.php:14336 +#: includes/class-freemius.php:14442 msgid "Invalid site details collection." msgstr "" -#: includes/class-freemius.php:14456 -msgid "We couldn't find your email address in the system, are you sure it's the right address?" +#: includes/class-freemius.php:14564 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" msgstr "" -#: includes/class-freemius.php:14458 -msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +#: includes/class-freemius.php:14562 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" msgstr "" -#: includes/class-freemius.php:14756 +#: includes/class-freemius.php:14868 msgid "Account is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again." msgstr "" -#: includes/class-freemius.php:14870, templates/forms/premium-versions-upgrade-handler.php:47 -msgid "Buy a license now" +#: includes/class-freemius.php:14994, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" msgstr "" -#: includes/class-freemius.php:14882, templates/forms/premium-versions-upgrade-handler.php:46 -msgid "Renew your license now" +#: includes/class-freemius.php:14982, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" msgstr "" -#: includes/class-freemius.php:14886 +#: includes/class-freemius.php:14998 msgid "%s to access version %s security & feature updates, and support." msgstr "" -#: includes/class-freemius.php:17621 +#: includes/class-freemius.php:17670 msgid "%s opt-in was successfully completed." msgstr "" -#: includes/class-freemius.php:17635 -msgid "Your account was successfully activated with the %s plan." +#: includes/class-freemius.php:17694, includes/class-freemius.php:21631 +msgid "Your trial has been successfully started." msgstr "" -#: includes/class-freemius.php:17645, includes/class-freemius.php:21566 -msgid "Your trial has been successfully started." +#: includes/class-freemius.php:17684 +msgid "Your account was successfully activated with the %s plan." msgstr "" -#: includes/class-freemius.php:18279, includes/class-freemius.php:18392, includes/class-freemius.php:18569 +#: includes/class-freemius.php:18328, includes/class-freemius.php:18441, includes/class-freemius.php:18618 msgid "Couldn't activate %s." msgstr "" -#: includes/class-freemius.php:18280, includes/class-freemius.php:18393, includes/class-freemius.php:18570 +#: includes/class-freemius.php:18329, includes/class-freemius.php:18442, includes/class-freemius.php:18619 msgid "Please contact us with the following message:" msgstr "" -#: includes/class-freemius.php:18389, templates/forms/data-debug-mode.php:162 +#: includes/class-freemius.php:18438, templates/forms/data-debug-mode.php:162 msgid "An unknown error has occurred." msgstr "" -#: includes/class-freemius.php:18931, includes/class-freemius.php:24339 +#: includes/class-freemius.php:18980, includes/class-freemius.php:24415 msgid "Upgrade" msgstr "" -#: includes/class-freemius.php:18937 -msgid "Start Trial" +#: includes/class-freemius.php:18988 +msgid "Pricing" msgstr "" -#: includes/class-freemius.php:18939 -msgid "Pricing" +#: includes/class-freemius.php:18986 +msgid "Start Trial" msgstr "" -#: includes/class-freemius.php:19019, includes/class-freemius.php:19021 +#: includes/class-freemius.php:19068, includes/class-freemius.php:19070 msgid "Affiliation" msgstr "" -#: includes/class-freemius.php:19049, includes/class-freemius.php:19051, templates/account.php:264, templates/debug.php:366 +#: includes/class-freemius.php:19098, includes/class-freemius.php:19100, templates/account.php:260, templates/debug.php:366 msgid "Account" msgstr "" -#: includes/class-freemius.php:19065, includes/class-freemius.php:19067, includes/customizer/class-fs-customizer-support-section.php:60 +#: includes/class-freemius.php:19114, includes/class-freemius.php:19116, includes/customizer/class-fs-customizer-support-section.php:60 msgid "Contact Us" msgstr "" -#: includes/class-freemius.php:19078, includes/class-freemius.php:19080, includes/class-freemius.php:24353, templates/account.php:134, templates/account/partials/addon.php:49 +#: includes/class-freemius.php:19127, includes/class-freemius.php:19129, includes/class-freemius.php:24429, templates/account.php:130, templates/account/partials/addon.php:49 msgid "Add-Ons" msgstr "" -#: includes/class-freemius.php:19114 +#: includes/class-freemius.php:19163 msgctxt "ASCII arrow left icon" msgid "←" msgstr "" -#: includes/class-freemius.php:19114 +#: includes/class-freemius.php:19163 msgctxt "ASCII arrow right icon" msgid "➤" msgstr "" -#: includes/class-freemius.php:19116, templates/pricing.php:110 +#: includes/class-freemius.php:19165, templates/pricing.php:110 msgctxt "noun" msgid "Pricing" msgstr "" -#: includes/class-freemius.php:19329, includes/customizer/class-fs-customizer-support-section.php:67 +#: includes/class-freemius.php:19378, includes/customizer/class-fs-customizer-support-section.php:67 msgid "Support Forum" msgstr "" -#: includes/class-freemius.php:20350 +#: includes/class-freemius.php:20399 msgid "Your email has been successfully verified - you are AWESOME!" msgstr "" -#: includes/class-freemius.php:20351 +#: includes/class-freemius.php:20400 msgctxt "a positive response" msgid "Right on" msgstr "" -#: includes/class-freemius.php:20857 +#: includes/class-freemius.php:20906 msgid "seems like the key you entered doesn't match our records." msgstr "" -#: includes/class-freemius.php:20881 +#: includes/class-freemius.php:20930 msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." msgstr "" -#: includes/class-freemius.php:21116 +#: includes/class-freemius.php:21165 msgid "Your %s Add-on plan was successfully upgraded." msgstr "" -#: includes/class-freemius.php:21118 +#. translators: %s:product name, e.g. Facebook add-on was successfully... +#: includes/class-freemius.php:21167 msgid "%s Add-on was successfully purchased." msgstr "" -#: includes/class-freemius.php:21121 +#: includes/class-freemius.php:21170 msgid "Download the latest version" msgstr "" -#: includes/class-freemius.php:21239 +#: includes/class-freemius.php:21288 msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." msgstr "" -#: includes/class-freemius.php:21239, includes/class-freemius.php:21636, includes/class-freemius.php:21737, includes/class-freemius.php:21824 +#: includes/class-freemius.php:21288, includes/class-freemius.php:21701, includes/class-freemius.php:21802, includes/class-freemius.php:21889 msgid "Error received from the server:" msgstr "" -#: includes/class-freemius.php:21470, includes/class-freemius.php:21742, includes/class-freemius.php:21795, includes/class-freemius.php:21902 +#: includes/class-freemius.php:21529, includes/class-freemius.php:21807, includes/class-freemius.php:21860, includes/class-freemius.php:21967 msgctxt "something somebody says when they are thinking about what you have just said." msgid "Hmm" msgstr "" -#: includes/class-freemius.php:21483 +#: includes/class-freemius.php:21542 msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." msgstr "" -#: includes/class-freemius.php:21484, templates/account.php:136, templates/add-ons.php:250, templates/account/partials/addon.php:51 +#: includes/class-freemius.php:21543, templates/account.php:132, templates/add-ons.php:250, templates/account/partials/addon.php:51 msgctxt "trial period" msgid "Trial" msgstr "" -#: includes/class-freemius.php:21489 +#: includes/class-freemius.php:21548 msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." msgstr "" -#: includes/class-freemius.php:21493, includes/class-freemius.php:21545 +#: includes/class-freemius.php:21552, includes/class-freemius.php:21610 msgid "Please contact us here" msgstr "" -#: includes/class-freemius.php:21515 +#: includes/class-freemius.php:21580 msgid "Your plan was successfully changed to %s." msgstr "" -#: includes/class-freemius.php:21531 +#: includes/class-freemius.php:21596 msgid "Your license has expired. You can still continue using the free %s forever." msgstr "" -#: includes/class-freemius.php:21533 +#. translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. +#: includes/class-freemius.php:21598 msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" -#: includes/class-freemius.php:21541 +#: includes/class-freemius.php:21606 msgid "Your license has been cancelled. If you think it's a mistake, please contact support." msgstr "" -#: includes/class-freemius.php:21554 +#: includes/class-freemius.php:21619 msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." msgstr "" -#: includes/class-freemius.php:21580 +#: includes/class-freemius.php:21645 msgid "Your free trial has expired. You can still continue using all our free features." msgstr "" -#: includes/class-freemius.php:21582 +#. translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. +#: includes/class-freemius.php:21647 msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" -#: includes/class-freemius.php:21628 +#: includes/class-freemius.php:21693 msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$s" msgstr "" -#: includes/class-freemius.php:21630 +#: includes/class-freemius.php:21695 msgid "Show error details" msgstr "" -#: includes/class-freemius.php:21733 +#: includes/class-freemius.php:21798 msgid "It looks like the license could not be activated." msgstr "" -#: includes/class-freemius.php:21775 +#: includes/class-freemius.php:21840 msgid "Your license was successfully activated." msgstr "" -#: includes/class-freemius.php:21799 +#: includes/class-freemius.php:21864 msgid "It looks like your site currently doesn't have an active license." msgstr "" -#: includes/class-freemius.php:21823 +#: includes/class-freemius.php:21888 msgid "It looks like the license deactivation failed." msgstr "" -#: includes/class-freemius.php:21852 +#: includes/class-freemius.php:21917 msgid "Your %s license was successfully deactivated." msgstr "" -#: includes/class-freemius.php:21853 +#: includes/class-freemius.php:21918 msgid "Your license was successfully deactivated, you are back to the %s plan." msgstr "" -#: includes/class-freemius.php:21856 +#: includes/class-freemius.php:21921 msgid "O.K" msgstr "" -#: includes/class-freemius.php:21909 +#: includes/class-freemius.php:21974 msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." msgstr "" -#: includes/class-freemius.php:21918 +#: includes/class-freemius.php:21983 msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." msgstr "" -#: includes/class-freemius.php:21960 +#: includes/class-freemius.php:22025 msgid "You are already running the %s in a trial mode." msgstr "" -#: includes/class-freemius.php:21971 +#: includes/class-freemius.php:22036 msgid "You already utilized a trial before." msgstr "" -#: includes/class-freemius.php:21985 -msgid "Plan %s do not exist, therefore, can't start a trial." +#: includes/class-freemius.php:22072 +msgid "None of the %s's plans supports a trial period." msgstr "" -#: includes/class-freemius.php:21996 -msgid "Plan %s does not support a trial period." +#: includes/class-freemius.php:22050 +msgid "Plan %s do not exist, therefore, can't start a trial." msgstr "" -#: includes/class-freemius.php:22007 -msgid "None of the %s's plans supports a trial period." +#: includes/class-freemius.php:22061 +msgid "Plan %s does not support a trial period." msgstr "" -#: includes/class-freemius.php:22056 +#: includes/class-freemius.php:22121 msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" msgstr "" -#: includes/class-freemius.php:22092 +#: includes/class-freemius.php:22157 msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." msgstr "" -#: includes/class-freemius.php:22111 +#: includes/class-freemius.php:22176 msgid "Your %s free trial was successfully cancelled." msgstr "" -#: includes/class-freemius.php:22438 -msgid "Version %s was released." +#: includes/class-freemius.php:22520 +msgid "Seems like you got the latest release." msgstr "" -#: includes/class-freemius.php:22438 -msgid "Please download %s." +#: includes/class-freemius.php:22521 +msgid "You are all good!" msgstr "" -#: includes/class-freemius.php:22445 -msgid "the latest %s version here" +#: includes/class-freemius.php:22503 +msgid "Version %s was released." msgstr "" -#: includes/class-freemius.php:22450 -msgid "New" +#: includes/class-freemius.php:22503 +msgid "Please download %s." msgstr "" -#: includes/class-freemius.php:22455 -msgid "Seems like you got the latest release." +#: includes/class-freemius.php:22510 +msgid "the latest %s version here" msgstr "" -#: includes/class-freemius.php:22456 -msgid "You are all good!" +#: includes/class-freemius.php:22515 +msgid "New" msgstr "" -#: includes/class-freemius.php:22859 +#: includes/class-freemius.php:22924 msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." msgstr "" -#: includes/class-freemius.php:22999 +#: includes/class-freemius.php:23064 msgid "Site successfully opted in." msgstr "" -#: includes/class-freemius.php:23000, includes/class-freemius.php:24049 +#: includes/class-freemius.php:23065, includes/class-freemius.php:24125 msgid "Awesome" msgstr "" -#: includes/class-freemius.php:23016 +#: includes/class-freemius.php:23091 +msgid "Diagnostic data will no longer be sent from %s to %s." +msgstr "" + +#: includes/class-freemius.php:23081 msgid "Sharing diagnostic data with %s helps to provide functionality that's more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to." msgstr "" -#: includes/class-freemius.php:23017 +#: includes/class-freemius.php:23082 msgid "Thank you!" msgstr "" -#: includes/class-freemius.php:23026 -msgid "Diagnostic data will no longer be sent from %s to %s." +#: includes/class-freemius.php:23251 +msgid "A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder." msgstr "" -#: includes/class-freemius.php:23181 +#: includes/class-freemius.php:23249 msgid "A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours." msgstr "" -#: includes/class-freemius.php:23183 -msgid "A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder." -msgstr "" - -#: includes/class-freemius.php:23190 +#: includes/class-freemius.php:23263 msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." msgstr "" -#: includes/class-freemius.php:23195 +#: includes/class-freemius.php:23269 msgid "%s is the new owner of the account." msgstr "" -#: includes/class-freemius.php:23197 +#: includes/class-freemius.php:23271 msgctxt "as congratulations" msgid "Congrats" msgstr "" -#: includes/class-freemius.php:23214 -msgid "Please provide your full name." +#: includes/class-freemius.php:23293 +msgid "Your name was successfully updated." msgstr "" -#: includes/class-freemius.php:23219 -msgid "Your name was successfully updated." +#: includes/class-freemius.php:23288 +msgid "Please provide your full name." msgstr "" -#: includes/class-freemius.php:23280 +#. translators: %s: User's account property (e.g. email address, name) +#: includes/class-freemius.php:23358 msgid "You have successfully updated your %s." msgstr "" -#: includes/class-freemius.php:23339 +#: includes/class-freemius.php:23422 msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." msgstr "" -#: includes/class-freemius.php:23342 +#: includes/class-freemius.php:23425 msgid "Click here" msgstr "" -#: includes/class-freemius.php:23379, includes/class-freemius.php:23376 +#: includes/class-freemius.php:23462 msgid "Bundle" msgstr "" -#: includes/class-freemius.php:23459 +#: includes/class-freemius.php:23535 msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." msgstr "" -#: includes/class-freemius.php:23460 +#: includes/class-freemius.php:23536 msgctxt "advance notice of something that will need attention." msgid "Heads up" msgstr "" -#: includes/class-freemius.php:24089 +#: includes/class-freemius.php:24165 msgctxt "exclamation" msgid "Hey" msgstr "" -#: includes/class-freemius.php:24089 +#: includes/class-freemius.php:24165 msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." msgstr "" -#: includes/class-freemius.php:24097 +#: includes/class-freemius.php:24173 msgid "No commitment for %s days - cancel anytime!" msgstr "" -#: includes/class-freemius.php:24098 +#: includes/class-freemius.php:24174 msgid "No credit card required" msgstr "" -#: includes/class-freemius.php:24105, templates/forms/trial-start.php:53 +#: includes/class-freemius.php:24181, templates/forms/trial-start.php:53 msgctxt "call to action" msgid "Start free trial" msgstr "" -#: includes/class-freemius.php:24182 +#: includes/class-freemius.php:24258 msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" msgstr "" -#: includes/class-freemius.php:24191 +#: includes/class-freemius.php:24267 msgid "Learn more" msgstr "" -#: includes/class-freemius.php:24377, templates/account.php:573, templates/account.php:725, templates/connect.php:221, templates/connect.php:447, includes/managers/class-fs-clone-manager.php:1295, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 +#: includes/class-freemius.php:24453, templates/account.php:569, templates/account.php:721, templates/connect.php:212, templates/connect.php:440, includes/managers/class-fs-clone-manager.php:1295, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 msgid "Activate License" msgstr "" -#: includes/class-freemius.php:24378, templates/account.php:667, templates/account.php:724, templates/account/partials/addon.php:327, templates/account/partials/site.php:273 +#: includes/class-freemius.php:24454, templates/account.php:663, templates/account.php:720, templates/account/partials/addon.php:327, templates/account/partials/site.php:273 msgid "Change License" msgstr "" -#: includes/class-freemius.php:24485, templates/account/partials/site.php:170 -msgid "Opt Out" -msgstr "" - -#: includes/class-freemius.php:24487, includes/class-freemius.php:24493, templates/account/partials/site.php:49, templates/account/partials/site.php:170 +#: includes/class-freemius.php:24569, includes/class-freemius.php:24563, templates/account/partials/site.php:49, templates/account/partials/site.php:170 msgid "Opt In" msgstr "" -#: includes/class-freemius.php:24728 -msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" -msgstr "" - -#: includes/class-freemius.php:24738 -msgid "Activate %s features" +#: includes/class-freemius.php:24561, templates/account/partials/site.php:170 +msgid "Opt Out" msgstr "" -#: includes/class-freemius.php:24751 +#: includes/class-freemius.php:24827 msgid "Please follow these steps to complete the upgrade" msgstr "" -#: includes/class-freemius.php:24755 +#. translators: %s: Plan title +#: includes/class-freemius.php:24831 msgid "Download the latest %s version" msgstr "" -#: includes/class-freemius.php:24759 +#: includes/class-freemius.php:24835 msgid "Upload and activate the downloaded version" msgstr "" -#: includes/class-freemius.php:24761 +#: includes/class-freemius.php:24837 msgid "How to upload and activate?" msgstr "" -#: includes/class-freemius.php:24796 +#: includes/class-freemius.php:24804 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:24814 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:24872 msgid "Your plan was successfully upgraded." msgstr "" -#: includes/class-freemius.php:24797 +#: includes/class-freemius.php:24873 msgid "Your plan was successfully activated." msgstr "" -#: includes/class-freemius.php:24927 +#: includes/class-freemius.php:25003 msgid "%sClick here%s to choose the sites where you'd like to activate the license on." msgstr "" -#: includes/class-freemius.php:25096 +#: includes/class-freemius.php:25172 msgid "Auto installation only works for opted-in users." msgstr "" -#: includes/class-freemius.php:25106, includes/class-freemius.php:25139, includes/class-fs-plugin-updater.php:1294, includes/class-fs-plugin-updater.php:1308 +#: includes/class-freemius.php:25182, includes/class-freemius.php:25215, includes/class-fs-plugin-updater.php:1291, includes/class-fs-plugin-updater.php:1305 msgid "Invalid module ID." msgstr "" -#: includes/class-freemius.php:25115, includes/class-fs-plugin-updater.php:1330 +#: includes/class-freemius.php:25223, includes/class-fs-plugin-updater.php:1326 +msgid "Premium add-on version already installed." +msgstr "" + +#: includes/class-freemius.php:25191, includes/class-fs-plugin-updater.php:1327 msgid "Premium version already active." msgstr "" -#: includes/class-freemius.php:25122 +#: includes/class-freemius.php:25198 msgid "You do not have a valid license to access the premium version." msgstr "" -#: includes/class-freemius.php:25129 +#: includes/class-freemius.php:25205 msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." msgstr "" -#: includes/class-freemius.php:25147, includes/class-fs-plugin-updater.php:1329 -msgid "Premium add-on version already installed." -msgstr "" - -#: includes/class-freemius.php:25501 +#: includes/class-freemius.php:25583 msgid "View paid features" msgstr "" -#: includes/class-freemius.php:25805 -msgid "Thank you so much for using %s and its add-ons!" +#: includes/class-freemius.php:25898 +msgid "Thank you so much for using our products!" msgstr "" -#: includes/class-freemius.php:25806 -msgid "Thank you so much for using %s!" +#: includes/class-freemius.php:25899 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." msgstr "" -#: includes/class-freemius.php:25812 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +#: includes/class-freemius.php:25918 +msgid "%s and its add-ons" msgstr "" -#: includes/class-freemius.php:25816 -msgid "Thank you so much for using our products!" +#: includes/class-freemius.php:25927 +msgid "Products" msgstr "" -#: includes/class-freemius.php:25817 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +#: includes/class-freemius.php:25887 +msgid "Thank you so much for using %s and its add-ons!" msgstr "" -#: includes/class-freemius.php:25836 -msgid "%s and its add-ons" +#: includes/class-freemius.php:25888 +msgid "Thank you so much for using %s!" msgstr "" -#: includes/class-freemius.php:25845 -msgid "Products" +#: includes/class-freemius.php:25894 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." msgstr "" -#: includes/class-freemius.php:25852, templates/connect.php:322 +#: includes/class-freemius.php:25934, templates/connect.php:313 msgid "Yes" msgstr "" -#: includes/class-freemius.php:25853, templates/connect.php:323 +#: includes/class-freemius.php:25935, templates/connect.php:314 msgid "send me security & feature updates, educational content and offers." msgstr "" -#: includes/class-freemius.php:25854, templates/connect.php:328 +#: includes/class-freemius.php:25936, templates/connect.php:319 msgid "No" msgstr "" -#: includes/class-freemius.php:25856, templates/connect.php:330 +#: includes/class-freemius.php:25938, templates/connect.php:321 msgid "do %sNOT%s send me security & feature updates, educational content and offers." msgstr "" -#: includes/class-freemius.php:25866 +#: includes/class-freemius.php:25948 msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" msgstr "" -#: includes/class-freemius.php:25868, templates/connect.php:337 +#: includes/class-freemius.php:25950, templates/connect.php:328 msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" msgstr "" -#: includes/class-freemius.php:26158 +#: includes/class-freemius.php:26240 msgid "License key is empty." msgstr "" -#: includes/class-fs-plugin-updater.php:210, templates/forms/premium-versions-upgrade-handler.php:57 +#: includes/class-fs-plugin-updater.php:212, templates/forms/premium-versions-upgrade-handler.php:57 msgid "Renew license" msgstr "" -#: includes/class-fs-plugin-updater.php:215, templates/forms/premium-versions-upgrade-handler.php:58 +#: includes/class-fs-plugin-updater.php:217, templates/forms/premium-versions-upgrade-handler.php:58 msgid "Buy license" msgstr "" -#: includes/class-fs-plugin-updater.php:335, includes/class-fs-plugin-updater.php:368 +#: includes/class-fs-plugin-updater.php:370, includes/class-fs-plugin-updater.php:337 msgid "There is a %s of %s available." msgstr "" -#: includes/class-fs-plugin-updater.php:337, includes/class-fs-plugin-updater.php:373 +#: includes/class-fs-plugin-updater.php:375, includes/class-fs-plugin-updater.php:339 msgid "new Beta version" msgstr "" -#: includes/class-fs-plugin-updater.php:338, includes/class-fs-plugin-updater.php:374 +#: includes/class-fs-plugin-updater.php:376, includes/class-fs-plugin-updater.php:340 msgid "new version" msgstr "" -#: includes/class-fs-plugin-updater.php:397 +#: includes/class-fs-plugin-updater.php:399 msgid "Important Upgrade Notice:" msgstr "" -#: includes/class-fs-plugin-updater.php:1359 +#: includes/class-fs-plugin-updater.php:1356 msgid "Installing plugin: %s" msgstr "" -#: includes/class-fs-plugin-updater.php:1400 +#: includes/class-fs-plugin-updater.php:1397 msgid "Unable to connect to the filesystem. Please confirm your credentials." msgstr "" -#: includes/class-fs-plugin-updater.php:1582 +#: includes/class-fs-plugin-updater.php:1579 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" @@ -887,24 +895,25 @@ msgctxt "verb" msgid "Purchase" msgstr "" +#. translators: %s: N-days trial #: includes/fs-plugin-info-dialog.php:547 msgid "Start my free %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:745 -msgid "Install Free Version Update Now" +#: includes/fs-plugin-info-dialog.php:755 +msgid "Install Free Version Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:746, templates/account.php:656 -msgid "Install Update Now" +#: includes/fs-plugin-info-dialog.php:756, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:423, templates/account/partials/addon.php:370 +msgid "Install Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:755 -msgid "Install Free Version Now" +#: includes/fs-plugin-info-dialog.php:745 +msgid "Install Free Version Update Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:756, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:370, templates/account/partials/addon.php:423 -msgid "Install Now" +#: includes/fs-plugin-info-dialog.php:746, templates/account.php:652 +msgid "Install Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:772 @@ -912,558 +921,569 @@ msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:773, templates/account.php:114, templates/add-ons.php:37, templates/account/partials/addon.php:30 +#: includes/fs-plugin-info-dialog.php:773, templates/account.php:110, templates/add-ons.php:37, templates/account/partials/addon.php:30 msgctxt "as download latest version" msgid "Download Latest" msgstr "" -#: includes/fs-plugin-info-dialog.php:788, templates/add-ons.php:329, templates/account/partials/addon.php:361, templates/account/partials/addon.php:417 +#: includes/fs-plugin-info-dialog.php:788, templates/add-ons.php:329, templates/account/partials/addon.php:417, templates/account/partials/addon.php:361 msgid "Activate this add-on" msgstr "" -#: includes/fs-plugin-info-dialog.php:790, templates/connect.php:444 +#: includes/fs-plugin-info-dialog.php:790, templates/connect.php:437 msgid "Activate Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:791, templates/account.php:138, templates/add-ons.php:330, templates/account/partials/addon.php:53 +#: includes/fs-plugin-info-dialog.php:791, templates/account.php:134, templates/add-ons.php:330, templates/account/partials/addon.php:53 msgid "Activate" msgstr "" -#: includes/fs-plugin-info-dialog.php:1003 +#: includes/fs-plugin-info-dialog.php:999 msgctxt "Plugin installer section title" msgid "Description" msgstr "" -#: includes/fs-plugin-info-dialog.php:1004 +#: includes/fs-plugin-info-dialog.php:1000 msgctxt "Plugin installer section title" msgid "Installation" msgstr "" -#: includes/fs-plugin-info-dialog.php:1005 +#: includes/fs-plugin-info-dialog.php:1001 msgctxt "Plugin installer section title" msgid "FAQ" msgstr "" -#: includes/fs-plugin-info-dialog.php:1006, templates/plugin-info/description.php:55 +#: includes/fs-plugin-info-dialog.php:1002, templates/plugin-info/description.php:55 msgid "Screenshots" msgstr "" -#: includes/fs-plugin-info-dialog.php:1007 +#: includes/fs-plugin-info-dialog.php:1003 msgctxt "Plugin installer section title" msgid "Changelog" msgstr "" -#: includes/fs-plugin-info-dialog.php:1008 +#: includes/fs-plugin-info-dialog.php:1004 msgctxt "Plugin installer section title" msgid "Reviews" msgstr "" -#: includes/fs-plugin-info-dialog.php:1009 +#: includes/fs-plugin-info-dialog.php:1005 msgctxt "Plugin installer section title" msgid "Other Notes" msgstr "" -#: includes/fs-plugin-info-dialog.php:1024 +#: includes/fs-plugin-info-dialog.php:1020 msgctxt "Plugin installer section title" msgid "Features & Pricing" msgstr "" -#: includes/fs-plugin-info-dialog.php:1034 +#: includes/fs-plugin-info-dialog.php:1030 msgid "Plugin Install" msgstr "" -#: includes/fs-plugin-info-dialog.php:1106 +#: includes/fs-plugin-info-dialog.php:1102 msgctxt "e.g. Professional Plan" msgid "%s Plan" msgstr "" -#: includes/fs-plugin-info-dialog.php:1132 +#: includes/fs-plugin-info-dialog.php:1128 msgctxt "e.g. the best product" msgid "Best" msgstr "" -#: includes/fs-plugin-info-dialog.php:1138, includes/fs-plugin-info-dialog.php:1158 +#: includes/fs-plugin-info-dialog.php:1134, includes/fs-plugin-info-dialog.php:1154 msgctxt "as every month" msgid "Monthly" msgstr "" -#: includes/fs-plugin-info-dialog.php:1141 +#: includes/fs-plugin-info-dialog.php:1137 msgctxt "as once a year" msgid "Annual" msgstr "" -#: includes/fs-plugin-info-dialog.php:1144 +#: includes/fs-plugin-info-dialog.php:1140 msgid "Lifetime" msgstr "" -#: includes/fs-plugin-info-dialog.php:1158, includes/fs-plugin-info-dialog.php:1160, includes/fs-plugin-info-dialog.php:1162 +#: includes/fs-plugin-info-dialog.php:1154, includes/fs-plugin-info-dialog.php:1156, includes/fs-plugin-info-dialog.php:1158 msgctxt "e.g. billed monthly" msgid "Billed %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1160 +#: includes/fs-plugin-info-dialog.php:1156 msgctxt "as once a year" msgid "Annually" msgstr "" -#: includes/fs-plugin-info-dialog.php:1162 +#: includes/fs-plugin-info-dialog.php:1158 msgctxt "as once a year" msgid "Once" msgstr "" -#: includes/fs-plugin-info-dialog.php:1168 +#: includes/fs-plugin-info-dialog.php:1164 msgid "Single Site License" msgstr "" -#: includes/fs-plugin-info-dialog.php:1170 +#: includes/fs-plugin-info-dialog.php:1166 msgid "Unlimited Licenses" msgstr "" -#: includes/fs-plugin-info-dialog.php:1172 +#: includes/fs-plugin-info-dialog.php:1168 msgid "Up to %s Sites" msgstr "" -#: includes/fs-plugin-info-dialog.php:1182, templates/plugin-info/features.php:82 +#: includes/fs-plugin-info-dialog.php:1178, templates/plugin-info/features.php:82 msgctxt "as monthly period" msgid "mo" msgstr "" -#: includes/fs-plugin-info-dialog.php:1189, templates/plugin-info/features.php:80 +#: includes/fs-plugin-info-dialog.php:1185, templates/plugin-info/features.php:80 msgctxt "as annual period" msgid "year" msgstr "" -#: includes/fs-plugin-info-dialog.php:1243 +#: includes/fs-plugin-info-dialog.php:1239 msgctxt "noun" msgid "Price" msgstr "" -#: includes/fs-plugin-info-dialog.php:1291 +#. translators: %s: Discount (e.g. discount of $5 or 10%) +#: includes/fs-plugin-info-dialog.php:1287 msgid "Save %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1301 +#: includes/fs-plugin-info-dialog.php:1297 msgid "No commitment for %s - cancel anytime" msgstr "" -#: includes/fs-plugin-info-dialog.php:1304 +#: includes/fs-plugin-info-dialog.php:1300 msgid "After your free %s, pay as little as %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1315 +#: includes/fs-plugin-info-dialog.php:1311 msgid "Details" msgstr "" -#: includes/fs-plugin-info-dialog.php:1319, templates/account.php:125, templates/debug.php:232, templates/debug.php:269, templates/debug.php:518, templates/account/partials/addon.php:41 +#: includes/fs-plugin-info-dialog.php:1315, templates/account.php:121, templates/debug.php:232, templates/debug.php:269, templates/debug.php:518, templates/account/partials/addon.php:41 msgctxt "product version" msgid "Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:1326 +#: includes/fs-plugin-info-dialog.php:1322 msgctxt "as the plugin author" msgid "Author" msgstr "" -#: includes/fs-plugin-info-dialog.php:1333 +#: includes/fs-plugin-info-dialog.php:1329 msgid "Last Updated" msgstr "" -#: includes/fs-plugin-info-dialog.php:1338, templates/account.php:544 +#. translators: %s: time period (e.g. "2 hours" ago) +#: includes/fs-plugin-info-dialog.php:1334, templates/account.php:540 msgctxt "x-ago" msgid "%s ago" msgstr "" -#: includes/fs-plugin-info-dialog.php:1347 +#: includes/fs-plugin-info-dialog.php:1343 msgid "Requires WordPress Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:1350, includes/fs-plugin-info-dialog.php:1370 +#. translators: %s: Version number. +#: includes/fs-plugin-info-dialog.php:1346, includes/fs-plugin-info-dialog.php:1366 msgid "%s or higher" msgstr "" -#: includes/fs-plugin-info-dialog.php:1358 +#: includes/fs-plugin-info-dialog.php:1354 msgid "Compatible up to" msgstr "" -#: includes/fs-plugin-info-dialog.php:1366 +#: includes/fs-plugin-info-dialog.php:1362 msgid "Requires PHP Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:1379 +#: includes/fs-plugin-info-dialog.php:1375 msgid "Downloaded" msgstr "" -#: includes/fs-plugin-info-dialog.php:1383 +#. translators: %s: 1 or One (Number of times downloaded) +#: includes/fs-plugin-info-dialog.php:1379 msgid "%s time" msgstr "" -#: includes/fs-plugin-info-dialog.php:1385 +#. translators: %s: Number of times downloaded +#: includes/fs-plugin-info-dialog.php:1381 msgid "%s times" msgstr "" -#: includes/fs-plugin-info-dialog.php:1396 +#: includes/fs-plugin-info-dialog.php:1392 msgid "WordPress.org Plugin Page" msgstr "" -#: includes/fs-plugin-info-dialog.php:1405 +#: includes/fs-plugin-info-dialog.php:1401 msgid "Plugin Homepage" msgstr "" -#: includes/fs-plugin-info-dialog.php:1414, includes/fs-plugin-info-dialog.php:1498 +#: includes/fs-plugin-info-dialog.php:1410, includes/fs-plugin-info-dialog.php:1494 msgid "Donate to this plugin" msgstr "" -#: includes/fs-plugin-info-dialog.php:1421 +#: includes/fs-plugin-info-dialog.php:1417 msgid "Average Rating" msgstr "" -#: includes/fs-plugin-info-dialog.php:1428 +#: includes/fs-plugin-info-dialog.php:1424 msgid "based on %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1432 +#. translators: %s: 1 or One +#: includes/fs-plugin-info-dialog.php:1428 msgid "%s rating" msgstr "" -#: includes/fs-plugin-info-dialog.php:1434 +#. translators: %s: Number larger than 1 +#: includes/fs-plugin-info-dialog.php:1430 msgid "%s ratings" msgstr "" -#: includes/fs-plugin-info-dialog.php:1449 +#. translators: %s: 1 or One +#: includes/fs-plugin-info-dialog.php:1445 msgid "%s star" msgstr "" -#: includes/fs-plugin-info-dialog.php:1451 +#. translators: %s: Number larger than 1 +#: includes/fs-plugin-info-dialog.php:1447 msgid "%s stars" msgstr "" -#: includes/fs-plugin-info-dialog.php:1463 +#. translators: %s: # of stars (e.g. 5 stars) +#: includes/fs-plugin-info-dialog.php:1459 msgid "Click to see reviews that provided a rating of %s" msgstr "" -#: includes/fs-plugin-info-dialog.php:1476 +#: includes/fs-plugin-info-dialog.php:1472 msgid "Contributors" msgstr "" -#: includes/fs-plugin-info-dialog.php:1517 +#: includes/fs-plugin-info-dialog.php:1513 msgid "This plugin requires a newer version of PHP." msgstr "" -#: includes/fs-plugin-info-dialog.php:1526 +#. translators: %s: URL to Update PHP page. +#: includes/fs-plugin-info-dialog.php:1522 msgid "Click here to learn more about updating PHP." msgstr "" -#: includes/fs-plugin-info-dialog.php:1540, includes/fs-plugin-info-dialog.php:1542 +#: includes/fs-plugin-info-dialog.php:1538, includes/fs-plugin-info-dialog.php:1536 msgid "Warning" msgstr "" -#: includes/fs-plugin-info-dialog.php:1540 -msgid "This plugin has not been tested with your current version of WordPress." +#: includes/fs-plugin-info-dialog.php:1538 +msgid "This plugin has not been marked as compatible with your version of WordPress." msgstr "" -#: includes/fs-plugin-info-dialog.php:1542 -msgid "This plugin has not been marked as compatible with your version of WordPress." +#: includes/fs-plugin-info-dialog.php:1536 +msgid "This plugin has not been tested with your current version of WordPress." msgstr "" -#: includes/fs-plugin-info-dialog.php:1561 +#: includes/fs-plugin-info-dialog.php:1557 msgid "Paid add-on must be deployed to Freemius." msgstr "" -#: includes/fs-plugin-info-dialog.php:1562 +#: includes/fs-plugin-info-dialog.php:1558 msgid "Add-on must be deployed to WordPress.org or Freemius." msgstr "" -#: includes/fs-plugin-info-dialog.php:1583 -msgid "Newer Version (%s) Installed" +#: includes/fs-plugin-info-dialog.php:1587 +msgid "Latest Version Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1584 -msgid "Newer Free Version (%s) Installed" +#: includes/fs-plugin-info-dialog.php:1588 +msgid "Latest Free Version Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1591 -msgid "Latest Version Installed" +#: includes/fs-plugin-info-dialog.php:1579 +msgid "Newer Version (%s) Installed" msgstr "" -#: includes/fs-plugin-info-dialog.php:1592 -msgid "Latest Free Version Installed" +#: includes/fs-plugin-info-dialog.php:1580 +msgid "Newer Free Version (%s) Installed" msgstr "" -#: templates/account.php:115, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:313 +#: templates/account.php:111, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:313 msgid "Downgrading your plan" msgstr "" -#: templates/account.php:116, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:314 +#: templates/account.php:112, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:314 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account.php:118, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:316 +#: templates/account.php:114, templates/forms/subscription-cancellation.php:99, templates/account/partials/addon.php:34, templates/account/partials/site.php:316 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" -#: templates/account.php:119, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:317 +#: templates/account.php:115, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:317 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" -#: templates/account.php:120, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 +#: templates/account.php:116, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" -#: templates/account.php:121, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:318 +#: templates/account.php:117, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:318 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" -#: templates/account.php:122, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:319 +#: templates/account.php:118, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:319 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") -#: templates/account.php:124, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 +#: templates/account.php:120, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") -#: templates/account.php:127, templates/account/partials/addon.php:43, templates/account/partials/site.php:293 +#: templates/account.php:123, templates/account/partials/addon.php:43, templates/account/partials/site.php:293 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") -#: templates/account.php:129, templates/account/partials/addon.php:45, templates/account/partials/site.php:295 +#: templates/account.php:125, templates/account/partials/addon.php:45, templates/account/partials/site.php:295 msgid "Expires in %s" msgstr "" -#: templates/account.php:130 +#: templates/account.php:126 msgctxt "as synchronize license" msgid "Sync License" msgstr "" -#: templates/account.php:131, templates/account/partials/addon.php:46 +#: templates/account.php:127, templates/account/partials/addon.php:46 msgid "Cancel Trial" msgstr "" -#: templates/account.php:132, templates/account/partials/addon.php:47 +#: templates/account.php:128, templates/account/partials/addon.php:47 msgid "Change Plan" msgstr "" -#: templates/account.php:133, templates/account/partials/addon.php:48 +#: templates/account.php:129, templates/account/partials/addon.php:48 msgctxt "verb" msgid "Upgrade" msgstr "" -#: templates/account.php:135, templates/account/partials/addon.php:50, templates/account/partials/site.php:320 +#: templates/account.php:131, templates/account/partials/addon.php:50, templates/account/partials/site.php:320 msgctxt "verb" msgid "Downgrade" msgstr "" -#: templates/account.php:137, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 +#: templates/account.php:133, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 msgid "Free" msgstr "" -#: templates/account.php:139, templates/debug.php:412, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:54 +#: templates/account.php:135, templates/debug.php:412, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:54 msgctxt "as product pricing plan" msgid "Plan" msgstr "" -#: templates/account.php:140 +#: templates/account.php:136 msgid "Bundle Plan" msgstr "" -#: templates/account.php:272 +#: templates/account.php:268 msgid "Free Trial" msgstr "" -#: templates/account.php:283 +#: templates/account.php:279 msgid "Account Details" msgstr "" -#: templates/account.php:290, templates/forms/data-debug-mode.php:33 -msgid "Start Debug" +#: templates/account.php:288 +msgid "Stop Debug" msgstr "" -#: templates/account.php:292 -msgid "Stop Debug" +#: templates/account.php:286, templates/forms/data-debug-mode.php:33 +msgid "Start Debug" msgstr "" -#: templates/account.php:299 +#: templates/account.php:295 msgid "Billing & Invoices" msgstr "" -#: templates/account.php:322, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 +#: templates/account.php:318, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" -#: templates/account.php:345, templates/forms/subscription-cancellation.php:125 +#: templates/account.php:341, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" -#: templates/account.php:345, templates/account/partials/addon.php:260 +#: templates/account.php:341, templates/account/partials/addon.php:260 msgid "Cancel Subscription" msgstr "" -#: templates/account.php:374, templates/account/partials/addon.php:345 +#: templates/account.php:370, templates/account/partials/addon.php:345 msgctxt "as synchronize" msgid "Sync" msgstr "" -#: templates/account.php:389, templates/debug.php:575 +#: templates/account.php:385, templates/debug.php:575 msgid "Name" msgstr "" -#: templates/account.php:395, templates/debug.php:576 +#: templates/account.php:391, templates/debug.php:576 msgid "Email" msgstr "" -#: templates/account.php:402, templates/debug.php:410, templates/debug.php:625 +#: templates/account.php:398, templates/debug.php:410, templates/debug.php:625 msgid "User ID" msgstr "" -#: templates/account.php:420, templates/account.php:738, templates/account.php:789, templates/debug.php:267, templates/debug.php:404, templates/debug.php:515, templates/debug.php:574, templates/debug.php:623, templates/debug.php:702, templates/account/payments.php:35, templates/debug/logger.php:21 +#: templates/account.php:416, templates/account.php:734, templates/account.php:785, templates/debug.php:267, templates/debug.php:404, templates/debug.php:515, templates/debug.php:574, templates/debug.php:623, templates/debug.php:702, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" -#: templates/account.php:427 +#: templates/account.php:423 msgid "Site ID" msgstr "" -#: templates/account.php:430 +#: templates/account.php:426 msgid "No ID" msgstr "" -#: templates/account.php:435, templates/debug.php:274, templates/debug.php:413, templates/debug.php:519, templates/debug.php:578, templates/account/partials/site.php:228 +#: templates/account.php:431, templates/debug.php:274, templates/debug.php:413, templates/debug.php:519, templates/debug.php:578, templates/account/partials/site.php:228 msgid "Public Key" msgstr "" -#: templates/account.php:441, templates/debug.php:414, templates/debug.php:520, templates/debug.php:579, templates/account/partials/site.php:241 +#: templates/account.php:437, templates/debug.php:414, templates/debug.php:520, templates/debug.php:579, templates/account/partials/site.php:241 msgid "Secret Key" msgstr "" -#: templates/account.php:444 +#: templates/account.php:440 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" -#: templates/account.php:471, templates/account/partials/site.php:120, templates/account/partials/site.php:122 -msgid "Trial" +#: templates/account.php:494, templates/debug.php:631, templates/account/partials/site.php:262 +msgid "License Key" msgstr "" -#: templates/account.php:498, templates/debug.php:631, templates/account/partials/site.php:262 -msgid "License Key" +#: templates/account.php:467, templates/account/partials/site.php:122, templates/account/partials/site.php:120 +msgid "Trial" msgstr "" -#: templates/account.php:529 +#: templates/account.php:525 msgid "Join the Beta program" msgstr "" -#: templates/account.php:535 +#: templates/account.php:531 msgid "not verified" msgstr "" -#: templates/account.php:544, templates/account/partials/addon.php:195 -msgid "Expired" +#: templates/account.php:600 +msgid "Free version" msgstr "" -#: templates/account.php:602 +#: templates/account.php:598 msgid "Premium version" msgstr "" -#: templates/account.php:604 -msgid "Free version" +#: templates/account.php:540, templates/account/partials/addon.php:195 +msgid "Expired" msgstr "" -#: templates/account.php:616 +#: templates/account.php:612 msgid "Verify Email" msgstr "" -#: templates/account.php:630 -msgid "Download %s Version" +#: templates/account.php:689, templates/forms/user-change.php:27 +msgid "Change User" msgstr "" -#: templates/account.php:646 -msgid "Download Paid Version" +#: templates/account.php:676 +msgid "What is your %s?" msgstr "" -#: templates/account.php:664, templates/account.php:927, templates/account/partials/site.php:250, templates/account/partials/site.php:272 +#: templates/account.php:684, templates/account/billing.php:21 msgctxt "verb" -msgid "Show" +msgid "Edit" msgstr "" -#: templates/account.php:680 -msgid "What is your %s?" +#: templates/account.php:660, templates/account.php:923, templates/account/partials/site.php:250, templates/account/partials/site.php:272 +msgctxt "verb" +msgid "Show" msgstr "" -#: templates/account.php:688, templates/account/billing.php:21 -msgctxt "verb" -msgid "Edit" +#: templates/account.php:626 +msgid "Download %s Version" msgstr "" -#: templates/account.php:693, templates/forms/user-change.php:27 -msgid "Change User" +#: templates/account.php:642 +msgid "Download Paid Version" msgstr "" -#: templates/account.php:717 +#: templates/account.php:713 msgid "Sites" msgstr "" -#: templates/account.php:730 +#: templates/account.php:726 msgid "Search by address" msgstr "" -#: templates/account.php:739, templates/debug.php:407 +#: templates/account.php:735, templates/debug.php:407 msgid "Address" msgstr "" -#: templates/account.php:740 +#: templates/account.php:736 msgid "License" msgstr "" -#: templates/account.php:741 +#: templates/account.php:737 msgid "Plan" msgstr "" -#: templates/account.php:792 +#: templates/account.php:788 msgctxt "as software license" msgid "License" msgstr "" -#: templates/account.php:921 +#: templates/account.php:917 msgctxt "verb" msgid "Hide" msgstr "" -#: templates/account.php:943, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 +#: templates/account.php:939, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 msgid "Processing" msgstr "" -#: templates/account.php:946 +#: templates/account.php:942 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" -#: templates/account.php:1004 +#: templates/account.php:1000 msgid "Cancelling %s" msgstr "" -#: templates/account.php:1004, templates/account.php:1021, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 +#: templates/account.php:1000, templates/account.php:1017, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 msgid "trial" msgstr "" -#: templates/account.php:1019, templates/forms/deactivation/form.php:195 +#: templates/account.php:1015, templates/forms/deactivation/form.php:195 msgid "Cancelling %s..." msgstr "" -#: templates/account.php:1022, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 +#: templates/account.php:1018, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 msgid "subscription" msgstr "" -#: templates/account.php:1036 +#: templates/account.php:1032 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" -#: templates/account.php:1110 +#: templates/account.php:1106 msgid "Disabling white-label mode" msgstr "" -#: templates/account.php:1111 +#: templates/account.php:1107 msgid "Enabling white-label mode" msgstr "" @@ -1489,11 +1509,12 @@ msgctxt "installed add-on" msgid "Installed" msgstr "" -#: templates/admin-notice.php:13, templates/forms/license-activation.php:243, templates/forms/resend-key.php:80 +#: templates/admin-notice.php:17, templates/forms/license-activation.php:245, templates/forms/resend-key.php:80 msgctxt "as close a window" msgid "Dismiss" msgstr "" +#. translators: %s: Number of seconds #: templates/auto-installation.php:45 msgid "%s sec" msgstr "" @@ -1523,143 +1544,144 @@ msgid "PCI compliant" msgstr "" #. translators: %s: name (e.g. Hey John,) -#: templates/connect.php:127 +#: templates/connect.php:118 msgctxt "greeting" msgid "Hey %s," msgstr "" -#: templates/connect.php:187 -msgid "Never miss an important update" +#. translators: %1$s: plugin name (e.g., "Awesome Plugin"); %2$s: version (e.g., "1.2.3") +#: templates/connect.php:186 +msgid "Thank you for updating to %1$s v%2$s!" msgstr "" -#: templates/connect.php:195 -msgid "Thank you for updating to %1$s v%2$s!" +#: templates/connect.php:178 +msgid "Never miss an important update" msgstr "" -#: templates/connect.php:205 +#: templates/connect.php:196 msgid "Allow & Continue" msgstr "" -#: templates/connect.php:209 -msgid "Re-send activation email" +#. translators: %s: module type (plugin, theme, or add-on) +#: templates/connect.php:236 +msgid "We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" -#: templates/connect.php:213 -msgid "Thanks %s!" +#: templates/connect.php:238 +msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info." msgstr "" -#: templates/connect.php:214 -msgid "You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s." +#: templates/connect.php:241 +msgid "If you skip this, that's okay! %1$s will still work just fine." msgstr "" -#: templates/connect.php:225 -msgid "Welcome to %s! To get started, please enter your license key:" +#: templates/connect.php:227 +msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" -#: templates/connect.php:236 -msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." +#: templates/connect.php:216 +msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" -#. translators: %s: module type (plugin, theme, or add-on) -#: templates/connect.php:245 -msgid "We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to." +#: templates/connect.php:200 +msgid "Re-send activation email" msgstr "" -#: templates/connect.php:247 -msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info." +#: templates/connect.php:204 +msgid "Thanks %s!" msgstr "" -#: templates/connect.php:250 -msgid "If you skip this, that's okay! %1$s will still work just fine." +#: templates/connect.php:205 +msgid "You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s." msgstr "" -#: templates/connect.php:280 +#: templates/connect.php:271 msgid "We're excited to introduce the Freemius network-level integration." msgstr "" -#: templates/connect.php:283 +#: templates/connect.php:285 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "" + +#: templates/connect.php:274 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" -#: templates/connect.php:285 +#: templates/connect.php:276 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" -#: templates/connect.php:287 +#: templates/connect.php:278 msgid "%s's paid features" msgstr "" -#: templates/connect.php:292 +#: templates/connect.php:283 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" -#: templates/connect.php:294 -msgid "During the update process we detected %s site(s) in the network that are still pending your attention." -msgstr "" - -#: templates/connect.php:303, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:42 +#: templates/connect.php:294, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:42 msgid "License key" msgstr "" -#: templates/connect.php:306, templates/forms/license-activation.php:22 +#: templates/connect.php:297, templates/forms/license-activation.php:22 msgid "Can't find your license key?" msgstr "" -#: templates/connect.php:369, templates/connect.php:693, templates/forms/deactivation/retry-skip.php:20 +#: templates/connect.php:360, templates/connect.php:690, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" -#: templates/connect.php:372 +#: templates/connect.php:363 msgid "Delegate to Site Admins" msgstr "" -#: templates/connect.php:372 +#: templates/connect.php:363 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" -#: templates/connect.php:399 +#: templates/connect.php:392 msgid "License issues?" msgstr "" -#: templates/connect.php:423 -msgid "For delivery of security & feature updates, and license management, %s needs to" -msgstr "" - -#: templates/connect.php:428 +#: templates/connect.php:421 msgid "This will allow %s to" msgstr "" -#: templates/connect.php:443 -msgid "Don't have a license key?" +#: templates/connect.php:416 +msgid "For delivery of security & feature updates, and license management, %s needs to" msgstr "" -#: templates/connect.php:446 +#: templates/connect.php:439 msgid "Have a license key?" msgstr "" -#: templates/connect.php:454 +#: templates/connect.php:436 +msgid "Don't have a license key?" +msgstr "" + +#: templates/connect.php:447 msgid "Freemius is our licensing and software updates engine" msgstr "" -#: templates/connect.php:457 +#: templates/connect.php:450 msgid "Privacy Policy" msgstr "" -#: templates/connect.php:459 -msgid "License Agreement" +#: templates/connect.php:455 +msgid "Terms of Service" msgstr "" -#: templates/connect.php:459 -msgid "Terms of Service" +#: templates/connect.php:453 +msgid "License Agreement" msgstr "" -#: templates/connect.php:879 +#: templates/connect.php:876 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" -#: templates/connect.php:880 +#: templates/connect.php:877 msgctxt "as activating plugin" msgid "Activating" msgstr "" @@ -1808,6 +1830,7 @@ msgstr "" msgid "Simulate Network Upgrade" msgstr "" +#. translators: %s: 'plugin' or 'theme' #: templates/debug.php:398 msgid "%s Installs" msgstr "" @@ -1919,6 +1942,7 @@ msgstr "" msgid "Timestamp" msgstr "" +#. translators: %s: Page name #: templates/secure-https-header.php:28 msgid "Secure HTTPS %s page, running from an external domain" msgstr "" @@ -1927,7 +1951,7 @@ msgstr "" msgid "Support" msgstr "" -#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +#: includes/debug/class-fs-debug-bar-panel.php:51, templates/debug/api-calls.php:54, templates/debug/logger.php:62 msgctxt "milliseconds" msgid "ms" msgstr "" @@ -1948,10 +1972,6 @@ msgstr "" msgid "products" msgstr "" -#: includes/managers/class-fs-clone-manager.php:1205 -msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." -msgstr "" - #: includes/managers/class-fs-clone-manager.php:1211 msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" msgstr "" @@ -1960,6 +1980,10 @@ msgstr "" msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" msgstr "" +#: includes/managers/class-fs-clone-manager.php:1205 +msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." +msgstr "" + #: includes/managers/class-fs-clone-manager.php:1238 msgid "the above-mentioned sites" msgstr "" @@ -2062,6 +2086,7 @@ msgstr "" msgid "Homepage URL & title, WP & PHP versions, and site language" msgstr "" +#. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:195 msgid "To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" @@ -2070,6 +2095,7 @@ msgstr "" msgid "View Basic %s Info" msgstr "" +#. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:210 msgid "Current %s & SDK versions, and if active or uninstalled" msgstr "" @@ -2078,9 +2104,7 @@ msgstr "" msgid "View License Essentials" msgstr "" -#: includes/managers/class-fs-permission-manager.php:262 -msgstr "" - +#. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:272 msgid "To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize." msgstr "" @@ -2089,6 +2113,7 @@ msgstr "" msgid "View %s State" msgstr "" +#. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:287 msgid "Is active, deactivated, or uninstalled" msgstr "" @@ -2109,6 +2134,7 @@ msgstr "" msgid "WordPress & PHP versions, site language & title" msgstr "" +#. translators: %s: 'Plugin' or 'Theme' #: includes/managers/class-fs-permission-manager.php:330 msgid "To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" @@ -2308,143 +2334,143 @@ msgstr "" msgid "Next" msgstr "" -#: templates/forms/affiliation.php:83 +#: templates/forms/affiliation.php:86 msgid "Non-expiring" msgstr "" -#: templates/forms/affiliation.php:86 +#: templates/forms/affiliation.php:89 msgid "Apply to become an affiliate" msgstr "" -#: templates/forms/affiliation.php:108 -msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +#: templates/forms/affiliation.php:137 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." msgstr "" -#: templates/forms/affiliation.php:123 -msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +#: templates/forms/affiliation.php:134 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." msgstr "" -#: templates/forms/affiliation.php:126 +#: templates/forms/affiliation.php:131 msgid "Your affiliation account was temporarily suspended." msgstr "" -#: templates/forms/affiliation.php:129 -msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +#: templates/forms/affiliation.php:128 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." msgstr "" -#: templates/forms/affiliation.php:132 -msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +#: templates/forms/affiliation.php:113 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." msgstr "" -#: templates/forms/affiliation.php:145 +#: templates/forms/affiliation.php:150 msgid "Like the %s? Become our ambassador and earn cash ;-)" msgstr "" -#: templates/forms/affiliation.php:146 +#: templates/forms/affiliation.php:151 msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" msgstr "" -#: templates/forms/affiliation.php:149 +#: templates/forms/affiliation.php:154 msgid "Program Summary" msgstr "" -#: templates/forms/affiliation.php:151 +#: templates/forms/affiliation.php:156 msgid "%s commission when a customer purchases a new license." msgstr "" -#: templates/forms/affiliation.php:153 +#: templates/forms/affiliation.php:158 msgid "Get commission for automated subscription renewals." msgstr "" -#: templates/forms/affiliation.php:156 +#: templates/forms/affiliation.php:161 msgid "%s tracking cookie after the first visit to maximize earnings potential." msgstr "" -#: templates/forms/affiliation.php:159 +#: templates/forms/affiliation.php:164 msgid "Unlimited commissions." msgstr "" -#: templates/forms/affiliation.php:161 +#: templates/forms/affiliation.php:166 msgid "%s minimum payout amount." msgstr "" -#: templates/forms/affiliation.php:162 +#: templates/forms/affiliation.php:167 msgid "Payouts are in USD and processed monthly via PayPal." msgstr "" -#: templates/forms/affiliation.php:163 +#: templates/forms/affiliation.php:168 msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." msgstr "" -#: templates/forms/affiliation.php:166 +#: templates/forms/affiliation.php:171 msgid "Affiliate" msgstr "" -#: templates/forms/affiliation.php:169, templates/forms/resend-key.php:23 +#: templates/forms/affiliation.php:174, templates/forms/resend-key.php:23 msgid "Email address" msgstr "" -#: templates/forms/affiliation.php:173 +#: templates/forms/affiliation.php:178 msgid "Full name" msgstr "" -#: templates/forms/affiliation.php:177 +#: templates/forms/affiliation.php:182 msgid "PayPal account email address" msgstr "" -#: templates/forms/affiliation.php:181 +#: templates/forms/affiliation.php:186 msgid "Where are you going to promote the %s?" msgstr "" -#: templates/forms/affiliation.php:183 +#: templates/forms/affiliation.php:188 msgid "Enter the domain of your website or other websites from where you plan to promote the %s." msgstr "" -#: templates/forms/affiliation.php:185 +#: templates/forms/affiliation.php:190 msgid "Add another domain" msgstr "" -#: templates/forms/affiliation.php:189 +#: templates/forms/affiliation.php:194 msgid "Extra Domains" msgstr "" -#: templates/forms/affiliation.php:190 +#: templates/forms/affiliation.php:195 msgid "Extra domains where you will be marketing the product from." msgstr "" -#: templates/forms/affiliation.php:200 +#: templates/forms/affiliation.php:205 msgid "Promotion methods" msgstr "" -#: templates/forms/affiliation.php:203 +#: templates/forms/affiliation.php:208 msgid "Social media (Facebook, Twitter, etc.)" msgstr "" -#: templates/forms/affiliation.php:207 +#: templates/forms/affiliation.php:212 msgid "Mobile apps" msgstr "" -#: templates/forms/affiliation.php:211 +#: templates/forms/affiliation.php:216 msgid "Website, email, and social media statistics (optional)" msgstr "" -#: templates/forms/affiliation.php:214 +#: templates/forms/affiliation.php:219 msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." msgstr "" -#: templates/forms/affiliation.php:218 +#: templates/forms/affiliation.php:223 msgid "How will you promote us?" msgstr "" -#: templates/forms/affiliation.php:221 +#: templates/forms/affiliation.php:226 msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." msgstr "" -#: templates/forms/affiliation.php:233, templates/forms/resend-key.php:22, templates/account/partials/disconnect-button.php:92 +#: templates/forms/affiliation.php:238, templates/forms/resend-key.php:22, templates/forms/subscription-cancellation.php:142, templates/account/partials/disconnect-button.php:92 msgid "Cancel" msgstr "" -#: templates/forms/affiliation.php:235 +#: templates/forms/affiliation.php:240 msgid "Become an affiliate" msgstr "" @@ -2525,10 +2551,14 @@ msgstr "" msgid "Agree & Activate License" msgstr "" -#: templates/forms/license-activation.php:204 +#: templates/forms/license-activation.php:206 msgid "Associate with the license owner's account." msgstr "" +#: templates/forms/optout.php:104 +msgid "Keep automatic updates" +msgstr "" + #: templates/forms/optout.php:44 msgid "Communication" msgstr "" @@ -2549,10 +2579,6 @@ msgstr "" msgid "Extensions" msgstr "" -#: templates/forms/optout.php:104 -msgid "Keep automatic updates" -msgstr "" - #: templates/forms/premium-versions-upgrade-handler.php:40 msgid "There is a new version of %s available." msgstr "" @@ -2614,10 +2640,12 @@ msgstr "" msgid "Cancel %s & Proceed" msgstr "" +#. translators: %1$s: Number of trial days; %2$s: Plan name; #: templates/forms/trial-start.php:22 msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." msgstr "" +#. translators: %s: Link to freemius.com #: templates/forms/trial-start.php:28 msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." msgstr "" @@ -2646,14 +2674,6 @@ msgstr "" msgid "Beta" msgstr "" -#: templates/partials/network-activation.php:32 -msgid "Activate license on all sites in the network." -msgstr "" - -#: templates/partials/network-activation.php:33 -msgid "Apply on all sites in the network." -msgstr "" - #: templates/partials/network-activation.php:36 msgid "Activate license on all pending sites." msgstr "" @@ -2662,6 +2682,14 @@ msgstr "" msgid "Apply on all pending sites." msgstr "" +#: templates/partials/network-activation.php:32 +msgid "Activate license on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:33 +msgid "Apply on all sites in the network." +msgstr "" + #: templates/partials/network-activation.php:45, templates/partials/network-activation.php:79 msgid "allow" msgstr "" @@ -2674,7 +2702,7 @@ msgstr "" msgid "skip" msgstr "" -#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +#: templates/plugin-info/description.php:67, templates/plugin-info/screenshots.php:26 msgid "Click to view full-size screenshot %d" msgstr "" @@ -2695,27 +2723,24 @@ msgstr "" msgid "Last license" msgstr "" -#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account/partials/addon.php:34 -msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +#: templates/account/partials/addon.php:200 +msgid "No expiration" msgstr "" #: templates/account/partials/addon.php:190 msgid "Cancelled" msgstr "" -#: templates/account/partials/addon.php:200 -msgid "No expiration" +#. translators: %s is replaced with the website's homepage address. +#: templates/account/partials/disconnect-button.php:78 +msgid "Disconnecting the website will permanently remove %s from your User Dashboard's account." msgstr "" #: templates/account/partials/disconnect-button.php:74 msgid "By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s." msgstr "" -#: templates/account/partials/disconnect-button.php:78 -msgid "Disconnecting the website will permanently remove %s from your User Dashboard's account." -msgstr "" - +#. translators: %1$s is replaced by the paid plan name, %2$s is replaced with an anchor link with the text "User Dashboard". #: templates/account/partials/disconnect-button.php:84 msgid "If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there." msgstr "" diff --git a/freemius/require.php b/freemius/require.php index cc194d2..bff3fc3 100644 --- a/freemius/require.php +++ b/freemius/require.php @@ -50,6 +50,7 @@ require_once WP_FS__DIR_INCLUDES . '/class-fs-api.php'; require_once WP_FS__DIR_INCLUDES . '/class-fs-plugin-updater.php'; require_once WP_FS__DIR_INCLUDES . '/class-fs-security.php'; + require_once WP_FS__DIR_INCLUDES . '/managers/class-fs-debug-manager.php'; require_once WP_FS__DIR_INCLUDES . '/class-fs-options.php'; require_once WP_FS__DIR_INCLUDES . '/class-fs-storage.php'; require_once WP_FS__DIR_INCLUDES . '/class-fs-admin-notices.php'; diff --git a/freemius/start.php b/freemius/start.php index 76df0bd..0a3acfd 100644 --- a/freemius/start.php +++ b/freemius/start.php @@ -15,7 +15,7 @@ * * @var string */ - $this_sdk_version = '2.6.0'; + $this_sdk_version = '2.8.0'; #region SDK Selection Logic -------------------------------------------------------------------- @@ -47,15 +47,20 @@ $file_path = fs_normalize_path( __FILE__ ); $fs_root_path = dirname( $file_path ); + // @todo: Remove this code after a few months when WP 6.3 usage is low enough. + global $wp_version; + if ( ! function_exists( 'wp_get_current_user' ) && /** * `get_stylesheet()` will rely on `wp_get_current_user()` when it is being filtered by `theme-previews.php`. That happens only when the site editor is loaded or when the site editor is sending REST requests. * @see theme-previews.php:wp_get_theme_preview_path() * - * @todo If this behavior is fixed in the core, we will remove this workaround. + * @todo This behavior is already fixed in the core (WP 6.3.2+), and this code can be removed after a few months when WP 6.3 usage is low enough. * @since WP 6.3.0 */ + version_compare( $wp_version, '6.3', '>=' ) && + version_compare( $wp_version, '6.3.1', '<=' ) && ( 'site-editor.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) || ( diff --git a/freemius/templates/account.php b/freemius/templates/account.php index ca27d07..ae009d4 100644 --- a/freemius/templates/account.php +++ b/freemius/templates/account.php @@ -22,8 +22,8 @@ * @var FS_Plugin_Tag $update */ $update = $fs->has_release_on_freemius() ? - $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) : - null; + $fs->get_update( false, false ) : + null; if ( is_object($update) ) { /** @@ -510,7 +510,7 @@ class="dashicons dashicons-image-rotate">

    > diff --git a/freemius/templates/account/partials/addon.php b/freemius/templates/account/partials/addon.php index e6d5657..9cc8e27 100644 --- a/freemius/templates/account/partials/addon.php +++ b/freemius/templates/account/partials/addon.php @@ -31,7 +31,7 @@ $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ - $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); @@ -240,19 +240,19 @@ true ); - $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); - $downgrade_confirmation_message = sprintf( - $downgrade_x_confirm_text, - ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), - $plan->title, - $human_readable_license_expiration - ); - $after_downgrade_message = ! $license->is_block_features ? sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : sprintf( $after_downgrade_blocking_text, $plan->title ); if ( ! $license->is_lifetime() && $is_active_subscription ) { + $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', diff --git a/freemius/templates/checkout.php b/freemius/templates/checkout.php index 3e8da51..e43d842 100644 --- a/freemius/templates/checkout.php +++ b/freemius/templates/checkout.php @@ -39,7 +39,7 @@ wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 3bf4ca3..165f44b 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -161,7 +161,6 @@ class="wrapis_enable_anonymous() fs_require_once_template( 'plugin-icon.php', $vars ); ?> -
    diff --git a/freemius/templates/contact.php b/freemius/templates/contact.php index 5fdd6e3..79495ef 100644 --- a/freemius/templates/contact.php +++ b/freemius/templates/contact.php @@ -39,7 +39,7 @@ wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); fs_enqueue_local_style( 'fs_checkout', '/admin/common.css' ); diff --git a/freemius/templates/debug.php b/freemius/templates/debug.php index 4626f64..0427c08 100644 --- a/freemius/templates/debug.php +++ b/freemius/templates/debug.php @@ -17,19 +17,26 @@ $off_text = fs_text_x_inline( 'Off', 'as turned off' ); $on_text = fs_text_x_inline( 'On', 'as turned on' ); + // For some reason css was missing + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + $has_any_active_clone = false; $is_multisite = is_multisite(); + + $auto_off_timestamp = wp_next_scheduled( 'fs_debug_turn_off_logging_hook' ) * 1000; ?>

    newest->version ?>

    -
    + +
    ' + ' ' + '
    ' @@ -194,7 +194,7 @@ function registerEventHandlers() { $modal.find('.fs-price-increase-warning').show(); } else { - $primaryButton.html( ); + $primaryButton.html( ); $modal.find('.fs-price-increase-warning').hide(); } @@ -271,7 +271,7 @@ function showMessage(message) { function updateButtonLabels() { $modal.find('.button-primary').text( ); - $modal.find('.button-secondary').text( ); + $modal.find('.button-secondary').text( ); } })( jQuery ); \ No newline at end of file diff --git a/freemius/templates/powered-by.php b/freemius/templates/powered-by.php index fc10b0b..e925b0c 100644 --- a/freemius/templates/powered-by.php +++ b/freemius/templates/powered-by.php @@ -36,7 +36,7 @@ if ( ! $fs->is_whitelabeled() && ! $fs->apply_filters( 'hide_freemius_powered_by', false ) ) { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); ?>
    diff --git a/freemius/templates/pricing.php b/freemius/templates/pricing.php index 842e4fe..05879a5 100644 --- a/freemius/templates/pricing.php +++ b/freemius/templates/pricing.php @@ -39,7 +39,7 @@ wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 96663af..15249f5 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -860,7 +860,8 @@ function radio_station_route_radio( $response, $handler, $request ) { $route = $request->get_route(); if ( '/' . $base == $route ) { $data = $response->data; - $date['success'] = true; + // 2.5.10: fix to incorrect variable (date) + $data['success'] = true; $data['endpoints'] = radio_station_get_route_urls(); $response->data = $data; } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index d691141..ae3fe61 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -5686,7 +5686,7 @@ function radio_station_playlist_show_metabox() { 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', - 'order' => 'aSC', + 'order' => 'ASC', 'post_type' => RADIO_STATION_SHOW_SLUG, 'post_status' => array( 'publish', 'draft' ), 'include' => implode( ',', $allowed_shows ), diff --git a/includes/support-functions.php b/includes/support-functions.php index 4fd8afc..d0f803f 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -590,7 +590,10 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $thumbnail_id = get_post_meta( $show->ID, '_thumbnail_id', true ); if ( $thumbnail_id ) { $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' ); - $thumbnail_url = $thumbnail[0]; + // 2.5.10: handle failure of return value + if ( $thumbnail && is_array( $thumbnail ) && isset( $thumbnail[0] ) ) { + $thumbnail_url = $thumbnail[0]; + } } // --- create array and return --- @@ -1312,7 +1315,10 @@ function radio_station_get_show_avatar_url( $show_id, $size = 'thumbnail' ) { // 2.4.0.6: added show avatar size filter $size = apply_filters( 'radio_station_show_avatar_size', $size ); $avatar_src = wp_get_attachment_image_src( $avatar_id, $size ); - $avatar_url = $avatar_src[0]; + // 2.5.10: handle failure to get image source + if ( $avatar_src && is_array( $avatar_src ) && isset( $avatar_src[0] ) ) { + $avatar_url = $avatar_src[0]; + } } // --- filter and return --- @@ -2504,3 +2510,56 @@ function radio_station_settings_allowed_html( $allowed, $type, $context ) { return $allowed; } + +// -------------------------- +// Player Widget Allowed HTML +// -------------------------- +// 2.5.10: added allowed HTML for player widget +add_filter( 'radio_station_allowed_html', 'radio_station_widget_player_allowed_html', 10, 3 ); +function radio_station_widget_player_allowed_html( $allowed, $type, $context ) { + + if ( ( 'widget' != $type ) || ( 'player' != $context ) ) { + return $allowed; + } + + // --- link --- + $allowed['link'] = array( + 'rel' => array(), + 'href' => array(), + ); + + // --- button --- + $allowed['button'] = array( + 'id' => array(), + 'class' => array(), + 'role' => array(), + 'title' => array(), + 'onclick' => array(), + 'tabindex' => array(), + 'aria-label' => array(), + 'style' => array(), + ); + + // --- input --- + $allowed['input'] = array( + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'data' => array(), + 'placeholder' => array(), + 'style' => array(), + 'checked' => array(), + 'onclick' => array(), + 'max' => array(), + 'min' => array(), + 'aria-label' => array(), + ); + + // --- styles --- + $allowed['style'] = array(); + + return $allowed; +} + diff --git a/js/wp-color-picker-alpha.js b/js/wp-color-picker-alpha.js index a3573d0..b549f9d 100644 --- a/js/wp-color-picker-alpha.js +++ b/js/wp-color-picker-alpha.js @@ -4,21 +4,21 @@ * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker * Only run in input and is defined data alpha in true * - * Version: 3.0.0 + * Version: 3.0.4 * https://github.com/kallookoo/wp-color-picker-alpha * Licensed under the GPLv2 license or later. */ -( function( $, undef ) { +( function ( $, undef ) { var wpColorPickerAlpha = { - 'version' : 300 + 'version': 304 }; // Always try to use the last version of this script. if ( 'wpColorPickerAlpha' in window && 'version' in window.wpColorPickerAlpha ) { var version = parseInt( window.wpColorPickerAlpha.version, 10 ); - if ( ! isNaN( version ) && version >= wpColorPickerAlpha.version ) { + if ( !isNaN( version ) && version >= wpColorPickerAlpha.version ) { return; } } @@ -29,7 +29,10 @@ } // Create new method to replace the `Color.toString()` inside the scripts. - Color.fn.to_s = function( type ) { + Color.fn.to_s = function ( type ) { + if ( this.error ) { + return ''; + } type = ( type || 'hex' ); // Change hex to rgba to return the correct color. if ( 'hex' === type && this._alpha < 1 ) { @@ -39,12 +42,53 @@ var color = ''; if ( 'hex' === type ) { color = this.toString(); - } else if ( ! this.error ) { + } else if ( 'octohex' === type ) { + color = this.toString(); + var alpha = parseInt( 255 * this._alpha, 10 ).toString( 16 ); + if ( alpha.length === 1 ) { + alpha = `0${alpha}`; + } + color += alpha; + } else { color = this.toCSS( type ).replace( /\(\s+/, '(' ).replace( /\s+\)/, ')' ); } + return color; } + Color.fn.fromHex = function ( color ) { + color = color.replace( /^#/, '' ).replace( /^0x/, '' ); + if ( 3 === color.length || 4 === color.length ) { + var extendedColor = ''; + for ( var index = 0; index < color.length; index++ ) { + extendedColor += '' + color[ index ]; + extendedColor += '' + color[ index ]; + } + color = extendedColor; + } + + if ( color.length === 8 ) { + if ( /^[0-9A-F]{8}$/i.test( color ) ) { + var alpha = parseInt( color.substring( 6 ), 16 ); + if ( !isNaN( alpha ) ) { + this.a( alpha / 255 ); + } else { + this._error(); + } + } else { + this._error(); + } + color = color.substring( 0, 6 ); + } + + if ( !this.error ) { + this.error = ! /^[0-9A-F]{6}$/i.test( color ); + } + + // console.log(color + ': ' + this.a()) + return this.fromInt( parseInt( color, 16 ) ); + } + // Register the global variable. window.wpColorPickerAlpha = wpColorPickerAlpha; @@ -71,18 +115,18 @@ * @since 3.0.0 * @access private * - * @param {Object|*} The color instance if not defined return the cuurent color. + * @param {Object|*} The color instance if not defined return the current color. * * @return {string} The element's color. */ - _getColor: function( color ) { + _getColor: function ( color ) { if ( color === undef ) { color = this._color; } if ( this.alphaOptions.alphaEnabled ) { color = color.to_s( this.alphaOptions.alphaColorType ); - if ( ! this.alphaOptions.alphaColorWithSpace ) { + if ( !this.alphaOptions.alphaColorWithSpace ) { color = color.replace( /\s+/g, '' ); } return color; @@ -97,11 +141,11 @@ * * @return {void} */ - _create: function() { + _create: function () { try { // Try to get the wpColorPicker alpha options. this.alphaOptions = this.element.wpColorPicker( 'instance' ).alphaOptions; - } catch( e ) {} + } catch ( e ) { } // We make sure there are all options $.extend( {}, this.alphaOptions, { @@ -110,6 +154,8 @@ alphaReset: false, alphaColorType: 'hex', alphaColorWithSpace: false, + alphaSkipDebounce: false, + alphaDebounceTimeout: 100, } ); this._super(); @@ -122,10 +168,9 @@ * * @return {void} */ - _addInputListeners: function( input ) { + _addInputListeners: function ( input ) { var self = this, - debounceTimeout = 100, - callback = function( event ){ + callback = function ( event ) { var val = input.val(), color = new Color( val ), val = val.replace( /^(#|(rgb|hsl)a?)/, '' ), @@ -133,9 +178,9 @@ input.removeClass( 'iris-error' ); - if ( ! color.error ) { + if ( !color.error ) { // let's not do this on keyup for hex shortcodes - if ( 'hex' !== type || ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { + if ( 'hex' !== type || !( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { // Compare color ( #AARRGGBB ) if ( color.toIEOctoHex() !== self._color.toIEOctoHex() ) { self._setOption( 'color', self._getColor( color ) ); @@ -146,13 +191,17 @@ } }; - input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); + input.on( 'change', callback ); + + if ( !self.alphaOptions.alphaSkipDebounce ) { + input.on( 'keyup', self._debounce( callback, self.alphaOptions.alphaDebounceTimeout ) ); + } // If we initialized hidden, show on first focus. The rest is up to you. if ( self.options.hide ) { - input.one( 'focus', function() { + input.one( 'focus', function () { self.show(); - }); + } ); } }, /** @@ -163,17 +212,17 @@ * * @return {void} */ - _initControls: function() { + _initControls: function () { this._super(); if ( this.alphaOptions.alphaEnabled ) { // Create Alpha controls var self = this, - stripAlpha = self.controls.strip.clone(false, false), + stripAlpha = self.controls.strip.clone( false, false ), stripAlphaSlider = stripAlpha.find( '.iris-slider-offset' ), controls = { - stripAlpha : stripAlpha, - stripAlphaSlider : stripAlphaSlider + stripAlpha: stripAlpha, + stripAlphaSlider: stripAlphaSlider }; stripAlpha.addClass( 'iris-strip-alpha' ); @@ -181,18 +230,18 @@ stripAlpha.appendTo( self.picker.find( '.iris-picker-inner' ) ); // Push new controls - $.each( controls, function( k, v ) { - self.controls[k] = v; + $.each( controls, function ( k, v ) { + self.controls[ k ] = v; } ); // Create slider self.controls.stripAlphaSlider.slider( { - orientation : 'vertical', - min : 0, - max : 100, - step : 1, - value : parseInt( self._color._alpha * 100 ), - slide : function( event, ui ) { + orientation: 'vertical', + min: 0, + max: 100, + step: 1, + value: parseInt( self._color._alpha * 100 ), + slide: function ( event, ui ) { self.active = 'strip'; // Update alpha value self._color._alpha = parseFloat( ui.value / 100 ); @@ -211,7 +260,7 @@ * * @return {void} */ - _dimensions: function( reset ) { + _dimensions: function ( reset ) { this._super( reset ); if ( this.alphaOptions.alphaEnabled ) { @@ -249,7 +298,6 @@ totalWidth = Math.round( squareWidth + ( stripWidth * 2 ) + ( stripMargin * 2 ) ); } - square.css( 'margin', '0' ); strip.width( stripWidth ).css( 'margin-left', stripMargin + 'px' ); } @@ -262,35 +310,35 @@ * * @return {void} */ - _change: function() { - var self = this, + _change: function () { + var self = this, active = self.active; self._super(); if ( self.alphaOptions.alphaEnabled ) { - var controls = self.controls, - alpha = parseInt( self._color._alpha * 100 ), - color = self._color.toRgb(), - gradient = [ + var controls = self.controls, + alpha = parseInt( self._color._alpha * 100 ), + color = self._color.toRgb(), + gradient = [ 'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%', 'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%' ], - target = self.picker.closest( '.wp-picker-container' ).find( '.wp-color-result' ); + target = self.picker.closest( '.wp-picker-container' ).find( '.wp-color-result' ); self.options.color = self._getColor(); // Generate background slider alpha, only for CSS3. - controls.stripAlpha.css( { 'background' : 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + backgroundImage + ')' } ); + controls.stripAlpha.css( { 'background': 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + backgroundImage + ')' } ); // Update alpha value if ( active ) { controls.stripAlphaSlider.slider( 'value', alpha ); } - if ( ! self._color.error ) { + if ( !self._color.error ) { self.element.removeClass( 'iris-error' ).val( self.options.color ); } - self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() { + self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function () { var color = $( this ).data( 'color' ); if ( self.alphaOptions.alphaReset ) { self._color._alpha = 1; @@ -311,7 +359,7 @@ * * @return {void} */ - _paintDimension: function( origin, control ) { + _paintDimension: function ( origin, control ) { var self = this, color = false; @@ -339,14 +387,14 @@ * * @return {void} */ - _setOption: function( key, value ) { + _setOption: function ( key, value ) { var self = this; if ( 'color' === key && self.alphaOptions.alphaEnabled ) { // cast to string in case we have a number value = '' + value; newColor = new Color( value ).setHSpace( self.options.mode ); // Check if error && Check the color to prevent callbacks with the same color. - if ( ! newColor.error && self._getColor( newColor ) !== self._getColor() ) { + if ( !newColor.error && self._getColor( newColor ) !== self._getColor() ) { self._color = newColor; self.options.color = self._getColor(); self.active = 'external'; @@ -365,7 +413,7 @@ * * @return {string} The element's color. */ - color: function( newColor ) { + color: function ( newColor ) { if ( newColor === true ) { return this._color.clone(); } @@ -398,9 +446,9 @@ * * @return {object} The current alpha options. */ - _getAlphaOptions: function() { + _getAlphaOptions: function () { var el = this.element, - type = ( el.data( 'type' ) || this.options.type ), + type = ( el.data( 'type' ) || this.options.type ), color = ( el.data( 'defaultColor' ) || el.val() ), options = { alphaEnabled: ( el.data( 'alphaEnabled' ) || false ), @@ -408,19 +456,20 @@ alphaReset: false, alphaColorType: 'rgb', alphaColorWithSpace: false, + alphaSkipDebounce: ( !!el.data( 'alphaSkipDebounce' ) || false ), }; if ( options.alphaEnabled ) { options.alphaEnabled = ( el.is( 'input' ) && 'full' === type ); } - if ( ! options.alphaEnabled ) { + if ( !options.alphaEnabled ) { return options; } options.alphaColorWithSpace = ( color && color.match( /\s/ ) ); - $.each( options, function( name, defaultValue ) { + $.each( options, function ( name, defaultValue ) { var value = ( el.data( name ) || defaultValue ); switch ( name ) { case 'alphaCustomWidth': @@ -428,7 +477,7 @@ value = ( isNaN( value ) ? defaultValue : value ); break; case 'alphaColorType': - if ( ! value.match( /^(hex|(rgb|hsl)a?)$/ ) ) { + if ( !value.match( /^((octo)?hex|(rgb|hsl)a?)$/ ) ) { if ( color && color.match( /^#/ ) ) { value = 'hex'; } else if ( color && color.match( /^hsla?/ ) ) { @@ -442,7 +491,7 @@ value = !!value; break; } - options[name] = value; + options[ name ] = value; } ); return options; @@ -455,9 +504,9 @@ * * @return {void} */ - _create: function() { + _create: function () { // Return early if Iris support is missing. - if ( ! $.support.iris ) { + if ( !$.support.iris ) { return; } @@ -475,8 +524,8 @@ * * @return {void} */ - _addListeners: function() { - if ( ! this.alphaOptions.alphaEnabled ) { + _addListeners: function () { + if ( !this.alphaOptions.alphaEnabled ) { return this._super(); } @@ -491,7 +540,7 @@ self.toggler.css( { 'position': 'relative', - 'background-image' : 'url(' + backgroundImage + ')' + 'background-image': 'url(' + backgroundImage + ')' } ); if ( isDeprecated ) { @@ -501,25 +550,25 @@ } self.colorAlpha = self.toggler.find( 'span.color-alpha' ).css( { - 'width' : '30px', - 'height' : '100%', - 'position' : 'absolute', - 'top' : 0, - 'background-color' : el.val(), + 'width': '30px', + 'height': '100%', + 'position': 'absolute', + 'top': 0, + 'background-color': el.val(), } ); // Define the correct position for ltr or rtl direction. if ( 'ltr' === self.colorAlpha.css( 'direction' ) ) { self.colorAlpha.css( { - 'border-bottom-left-radius' : '2px', - 'border-top-left-radius' : '2px', - 'left' : 0 + 'border-bottom-left-radius': '2px', + 'border-top-left-radius': '2px', + 'left': 0 } ); } else { self.colorAlpha.css( { - 'border-bottom-right-radius' : '2px', - 'border-top-right-radius' : '2px', - 'right' : 0 + 'border-bottom-right-radius': '2px', + 'border-top-right-radius': '2px', + 'right': 0 } ); } @@ -538,11 +587,11 @@ * * @returns {void} */ - change: function( event, ui ) { + change: function ( event, ui ) { self.colorAlpha.css( { 'background-color': ui.color.to_s( self.alphaOptions.alphaColorType ) } ); // fire change callback if we have one - if ( $.isFunction( self.options.change ) ) { + if ( typeof self.options.change === 'function' ) { self.options.change.call( this, event, ui ); } } @@ -558,22 +607,22 @@ * * @return {void} */ - self.wrap.on( 'click.wpcolorpicker', function( event ) { + self.wrap.on( 'click.wpcolorpicker', function ( event ) { event.stopPropagation(); - }); + } ); /** * Open or close the color picker depending on the class. * * @since 3.0.0 */ - self.toggler.click( function() { + self.toggler.on( 'click', function () { if ( self.toggler.hasClass( 'wp-picker-open' ) ) { self.close(); } else { self.open(); } - }); + } ); /** * Checks if value is empty when changing the color in the color picker. @@ -585,7 +634,7 @@ * * @return {void} */ - el.change( function( event ) { + el.on( 'change', function ( event ) { var val = $( this ).val(); if ( el.hasClass( 'iris-error' ) || val === '' || val.match( /^(#|(rgb|hsl)a?)$/ ) ) { @@ -596,7 +645,7 @@ self.colorAlpha.css( 'background-color', '' ); // fire clear callback if we have one - if ( $.isFunction( self.options.clear ) ) { + if ( typeof self.options.clear === 'function' ) { self.options.clear.call( this, event ); } } @@ -611,7 +660,7 @@ * * @return {void} */ - self.button.click( function( event ) { + self.button.on( 'click', function ( event ) { if ( $( this ).hasClass( 'wp-picker-default' ) ) { el.val( self.options.defaultColor ).change(); } else if ( $( this ).hasClass( 'wp-picker-clear' ) ) { @@ -623,7 +672,7 @@ self.colorAlpha.css( 'background-color', '' ); // fire clear callback if we have one - if ( $.isFunction( self.options.clear ) ) { + if ( typeof self.options.clear === 'function' ) { self.options.clear.call( this, event ); } @@ -632,4 +681,4 @@ } ); }, } ); -} ( jQuery ) ); \ No newline at end of file +} )( jQuery ); \ No newline at end of file diff --git a/js/wp-color-picker-alpha.min.js b/js/wp-color-picker-alpha.min.js index 1aeb1f2..fa0f96f 100644 --- a/js/wp-color-picker-alpha.min.js +++ b/js/wp-color-picker-alpha.min.js @@ -4,8 +4,8 @@ * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker * Only run in input and is defined data alpha in true * - * Version: 3.0.0 + * Version: 3.0.4 * https://github.com/kallookoo/wp-color-picker-alpha * Licensed under the GPLv2 license or later. */ -!function(e,a){var l,o={version:300};if("wpColorPickerAlpha"in window&&"version"in window.wpColorPickerAlpha){var t=parseInt(window.wpColorPickerAlpha.version,10);if(!isNaN(t)&&o.version<=t)return}Color.fn.hasOwnProperty("to_s")||(Color.fn.to_s=function(o){"hex"===(o=o||"hex")&&this._alpha<1&&(o="rgba");var a="";return"hex"===o?a=this.toString():this.error||(a=this.toCSS(o).replace(/\(\s+/,"(").replace(/\s+\)/,")")),a},window.wpColorPickerAlpha=o,l="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==",e.widget("a8c.iris",e.a8c.iris,{alphaOptions:{alphaEnabled:!1},_getColor:function(o){return o===a&&(o=this._color),this.alphaOptions.alphaEnabled?(o=o.to_s(this.alphaOptions.alphaColorType),this.alphaOptions.alphaColorWithSpace||(o=o.replace(/\s+/g,"")),o):o.toString()},_create:function(){try{this.alphaOptions=this.element.wpColorPicker("instance").alphaOptions}catch(o){}e.extend({},this.alphaOptions,{alphaEnabled:!1,alphaCustomWidth:130,alphaReset:!1,alphaColorType:"hex",alphaColorWithSpace:!1}),this._super()},_addInputListeners:function(i){function o(o){var a=i.val(),t=new Color(a),a=a.replace(/^(#|(rgb|hsl)a?)/,""),r=l.alphaOptions.alphaColorType;i.removeClass("iris-error"),t.error?""!==a&&i.addClass("iris-error"):"hex"===r&&"keyup"===o.type&&a.match(/^[0-9a-fA-F]{3}$/)||t.toIEOctoHex()!==l._color.toIEOctoHex()&&l._setOption("color",l._getColor(t))}var l=this;i.on("change",o).on("keyup",l._debounce(o,100)),l.options.hide&&i.one("focus",function(){l.show()})},_initControls:function(){var t,o,a,r;this._super(),this.alphaOptions.alphaEnabled&&(a=(o=(t=this).controls.strip.clone(!1,!1)).find(".iris-slider-offset"),r={stripAlpha:o,stripAlphaSlider:a},o.addClass("iris-strip-alpha"),a.addClass("iris-slider-offset-alpha"),o.appendTo(t.picker.find(".iris-picker-inner")),e.each(r,function(o,a){t.controls[o]=a}),t.controls.stripAlphaSlider.slider({orientation:"vertical",min:0,max:100,step:1,value:parseInt(100*t._color._alpha),slide:function(o,a){t.active="strip",t._color._alpha=parseFloat(a.value/100),t._change.apply(t,arguments)}}))},_dimensions:function(o){if(this._super(o),this.alphaOptions.alphaEnabled){for(var a=this,t=a.options,r=a.controls.square,o=a.picker.find(".iris-strip"),i=Math.round(a.picker.outerWidth(!0)-(t.border?22:0)),l=Math.round(r.outerWidth()),e=Math.round((i-l)/2),s=Math.round(e/2),n=Math.round(l+2*e+2*s);i'):t.toggler.append(''),t.colorAlpha=t.toggler.find("span.color-alpha").css({width:"30px",height:"100%",position:"absolute",top:0,"background-color":r.val()}),"ltr"===t.colorAlpha.css("direction")?t.colorAlpha.css({"border-bottom-left-radius":"2px","border-top-left-radius":"2px",left:0}):t.colorAlpha.css({"border-bottom-right-radius":"2px","border-top-right-radius":"2px",right:0}),r.iris({change:function(o,a){t.colorAlpha.css({"background-color":a.color.to_s(t.alphaOptions.alphaColorType)}),e.isFunction(t.options.change)&&t.options.change.call(this,o,a)}}),t.wrap.on("click.wpcolorpicker",function(o){o.stopPropagation()}),t.toggler.click(function(){t.toggler.hasClass("wp-picker-open")?t.close():t.open()}),r.change(function(o){var a=e(this).val();(r.hasClass("iris-error")||""===a||a.match(/^(#|(rgb|hsl)a?)$/))&&(i&&t.toggler.removeAttr("style"),t.colorAlpha.css("background-color",""),e.isFunction(t.options.clear)&&t.options.clear.call(this,o))}),t.button.click(function(o){e(this).hasClass("wp-picker-default")?r.val(t.options.defaultColor).change():e(this).hasClass("wp-picker-clear")&&(r.val(""),i&&t.toggler.removeAttr("style"),t.colorAlpha.css("background-color",""),e.isFunction(t.options.clear)&&t.options.clear.call(this,o),r.trigger("change"))})}}))}(jQuery); \ No newline at end of file +!function($,undef){var wpColorPickerAlpha={version:304};if("wpColorPickerAlpha"in window&&"version"in window.wpColorPickerAlpha){var version=parseInt(window.wpColorPickerAlpha.version,10);if(!isNaN(version)&&version>=wpColorPickerAlpha.version)return}if(!Color.fn.hasOwnProperty("to_s")){Color.fn.to_s=function(type){if(this.error)return"";"hex"===(type=type||"hex")&&this._alpha<1&&(type="rgba");var color="";if("hex"===type)color=this.toString();else if("octohex"===type){color=this.toString();var alpha=parseInt(255*this._alpha,10).toString(16);1===alpha.length&&(alpha=`0${alpha}`),color+=alpha}else color=this.toCSS(type).replace(/\(\s+/,"(").replace(/\s+\)/,")");return color},Color.fn.fromHex=function(color){if(3===(color=color.replace(/^#/,"").replace(/^0x/,"")).length||4===color.length){for(var extendedColor="",index=0;indexinnerWidth;)stripWidth=Math.round(stripWidth-2),stripMargin=Math.round(stripMargin-1),totalWidth=Math.round(squareWidth+2*stripWidth+2*stripMargin);square.css("margin","0"),strip.width(stripWidth).css("margin-left",stripMargin+"px")}},_change:function(){var self=this,active=self.active;if(self._super(),self.alphaOptions.alphaEnabled){var controls=self.controls,alpha=parseInt(100*self._color._alpha),color=self._color.toRgb(),gradient=["rgb("+color.r+","+color.g+","+color.b+") 0%","rgba("+color.r+","+color.g+","+color.b+", 0) 100%"];self.picker.closest(".wp-picker-container").find(".wp-color-result");self.options.color=self._getColor(),controls.stripAlpha.css({background:"linear-gradient(to bottom, "+gradient.join(", ")+"), url("+backgroundImage+")"}),active&&controls.stripAlphaSlider.slider("value",alpha),self._color.error||self.element.removeClass("iris-error").val(self.options.color),self.picker.find(".iris-palette-container").on("click.palette",".iris-palette",(function(){var color=$(this).data("color");self.alphaOptions.alphaReset&&(self._color._alpha=1,color=self._getColor()),self._setOption("color",color)}))}},_paintDimension:function(origin,control){var color=!1;this.alphaOptions.alphaEnabled&&"strip"===control&&(color=this._color,this._color=new Color(color.toString()),this.hue=this._color.h()),this._super(origin,control),color&&(this._color=color)},_setOption:function(key,value){if("color"!==key||!this.alphaOptions.alphaEnabled)return this._super(key,value);value=""+value,newColor=new Color(value).setHSpace(this.options.mode),newColor.error||this._getColor(newColor)===this._getColor()||(this._color=newColor,this.options.color=this._getColor(),this.active="external",this._change())},color:function(newColor){return!0===newColor?this._color.clone():undefined===newColor?this._getColor():void this.option("color",newColor)}}),$.widget("wp.wpColorPicker",$.wp.wpColorPicker,{alphaOptions:{alphaEnabled:!1},_getAlphaOptions:function(){var el=this.element,type=el.data("type")||this.options.type,color=el.data("defaultColor")||el.val(),options={alphaEnabled:el.data("alphaEnabled")||!1,alphaCustomWidth:130,alphaReset:!1,alphaColorType:"rgb",alphaColorWithSpace:!1,alphaSkipDebounce:!!el.data("alphaSkipDebounce")||!1};return options.alphaEnabled&&(options.alphaEnabled=el.is("input")&&"full"===type),options.alphaEnabled?(options.alphaColorWithSpace=color&&color.match(/\s/),$.each(options,(function(name,defaultValue){var value=el.data(name)||defaultValue;switch(name){case"alphaCustomWidth":value=value?parseInt(value,10):0,value=isNaN(value)?defaultValue:value;break;case"alphaColorType":value.match(/^((octo)?hex|(rgb|hsl)a?)$/)||(value=color&&color.match(/^#/)?"hex":color&&color.match(/^hsla?/)?"hsl":defaultValue);break;default:value=!!value}options[name]=value})),options):options},_create:function(){$.support.iris&&(this.alphaOptions=this._getAlphaOptions(),this._super())},_addListeners:function(){if(!this.alphaOptions.alphaEnabled)return this._super();var self=this,el=self.element,isDeprecated=self.toggler.is("a");this.alphaOptions.defaultWidth=el.width(),this.alphaOptions.alphaCustomWidth&&el.width(parseInt(this.alphaOptions.defaultWidth+this.alphaOptions.alphaCustomWidth,10)),self.toggler.css({position:"relative","background-image":"url("+backgroundImage+")"}),isDeprecated?self.toggler.html(''):self.toggler.append(''),self.colorAlpha=self.toggler.find("span.color-alpha").css({width:"30px",height:"100%",position:"absolute",top:0,"background-color":el.val()}),"ltr"===self.colorAlpha.css("direction")?self.colorAlpha.css({"border-bottom-left-radius":"2px","border-top-left-radius":"2px",left:0}):self.colorAlpha.css({"border-bottom-right-radius":"2px","border-top-right-radius":"2px",right:0}),el.iris({change:function(event,ui){self.colorAlpha.css({"background-color":ui.color.to_s(self.alphaOptions.alphaColorType)}),"function"==typeof self.options.change&&self.options.change.call(this,event,ui)}}),self.wrap.on("click.wpcolorpicker",(function(event){event.stopPropagation()})),self.toggler.on("click",(function(){self.toggler.hasClass("wp-picker-open")?self.close():self.open()})),el.on("change",(function(event){var val=$(this).val();(el.hasClass("iris-error")||""===val||val.match(/^(#|(rgb|hsl)a?)$/))&&(isDeprecated&&self.toggler.removeAttr("style"),self.colorAlpha.css("background-color",""),"function"==typeof self.options.clear&&self.options.clear.call(this,event))})),self.button.on("click",(function(event){$(this).hasClass("wp-picker-default")?el.val(self.options.defaultColor).change():$(this).hasClass("wp-picker-clear")&&(el.val(""),isDeprecated&&self.toggler.removeAttr("style"),self.colorAlpha.css("background-color",""),"function"==typeof self.options.clear&&self.options.clear.call(this,event),el.trigger("change"))}))}})}}(jQuery); \ No newline at end of file diff --git a/languages/radio-station.pot b/languages/radio-station.pot index 4361b9a..9356e93 100644 --- a/languages/radio-station.pot +++ b/languages/radio-station.pot @@ -1,543 +1,65 @@ -# Copyright (C) 2023 Radio Station +# Copyright (C) 2024 Radio Station # This file is distributed under the same license as the Radio Station package. msgid "" msgstr "" -"Project-Id-Version: Radio Station 2.5.4\n" +"Project-Id-Version: Radio Station 2.5.9.5\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/radio-station\n" -"POT-Creation-Date: 2023-07-30 06:22:39+00:00\n" +"POT-Creation-Date: 2024-10-27 04:44:56+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2023-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2024-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -#: help/contextual-help-config.php:52 includes/extra-strings.php:219 +#: help/contextual-help-config.php:53 includes/extra-strings.php:218 msgid "Import" msgstr "" -#: help/contextual-help-config.php:63 includes/extra-strings.php:223 -#: templates/playlist-export.php:140 +#: help/contextual-help-config.php:66 includes/extra-strings.php:222 +#: templates/playlist-export.php:147 msgid "Export" msgstr "" -#: help/contextual-help-config.php:74 +#: help/contextual-help-config.php:79 msgid "YAML format " msgstr "" -#: help/contextual-help-config.php:85 +#: help/contextual-help-config.php:92 msgid "show-schedule:" msgstr "" -#: includes/blocks.php:40 includes/extra-strings.php:63 -#: radio-station-admin.php:198 radio-station-admin.php:887 -#: radio-station-admin.php:976 radio-station-admin.php:1543 -#: radio-station-admin.php:1622 radio-station-admin.php:1722 +#: includes/blocks.php:42 includes/extra-strings.php:63 +#: radio-station-admin.php:203 radio-station-admin.php:902 +#: radio-station-admin.php:993 radio-station-admin.php:1585 +#: radio-station-admin.php:1673 radio-station-admin.php:1773 msgid "Radio Station" msgstr "" -#: includes/class-current-playlist-widget.php:15 -#: widgets/class-current-playlist-widget.php:15 -msgid "Display currently playing playlist." -msgstr "" - -#: includes/class-current-playlist-widget.php:17 -#: widgets/class-current-playlist-widget.php:17 -msgid "(Radio Station) Now Playing List" -msgstr "" - -#: includes/class-current-playlist-widget.php:48 -#: includes/class-current-show-widget.php:55 -#: includes/class-current-show-widget.php:171 -#: includes/class-radio-clock-widget.php:45 -#: includes/class-upcoming-shows-widget.php:52 -#: includes/class-upcoming-shows-widget.php:147 includes/extra-strings.php:428 -#: widgets/class-current-playlist-widget.php:69 -#: widgets/class-current-show-widget.php:89 -#: widgets/class-current-show-widget.php:207 -#: widgets/class-radio-clock-widget.php:87 -#: widgets/class-upcoming-shows-widget.php:92 -#: widgets/class-upcoming-shows-widget.php:202 -msgid "Default" -msgstr "" - -#: includes/class-current-playlist-widget.php:49 -#: includes/class-current-show-widget.php:56 -#: includes/class-upcoming-shows-widget.php:53 includes/extra-strings.php:245 -#: widgets/class-current-playlist-widget.php:70 -#: widgets/class-current-show-widget.php:90 -#: widgets/class-upcoming-shows-widget.php:93 -msgid "On" -msgstr "" - -#: includes/class-current-playlist-widget.php:50 -#: includes/class-current-show-widget.php:57 -#: includes/class-upcoming-shows-widget.php:54 includes/extra-strings.php:246 -#: options.php:1049 widgets/class-current-playlist-widget.php:71 -#: widgets/class-current-show-widget.php:91 -#: widgets/class-upcoming-shows-widget.php:94 -msgid "Off" -msgstr "" - -#: includes/class-current-playlist-widget.php:52 -#: includes/class-current-show-widget.php:59 -#: includes/class-upcoming-shows-widget.php:56 -#: widgets/class-current-playlist-widget.php:73 -#: widgets/class-current-show-widget.php:93 -#: widgets/class-upcoming-shows-widget.php:96 -msgid "AJAX Load Widget?" -msgstr "" - -#: includes/class-current-playlist-widget.php:58 -#: includes/class-current-show-widget.php:65 -#: includes/class-radio-clock-widget.php:37 -#: includes/class-upcoming-shows-widget.php:62 includes/extra-strings.php:487 -#: includes/post-types-admin.php:3069 -msgid "Title" -msgstr "" - -#: includes/class-current-playlist-widget.php:66 -#: widgets/class-current-playlist-widget.php:106 -msgid "Link to Playlist?" -msgstr "" - -#: includes/class-current-playlist-widget.php:73 -#: widgets/class-current-playlist-widget.php:133 -msgid "Show Song Title" -msgstr "" - -#: includes/class-current-playlist-widget.php:80 -#: widgets/class-current-playlist-widget.php:141 -msgid "Show Artist Name" -msgstr "" - -#: includes/class-current-playlist-widget.php:87 -#: widgets/class-current-playlist-widget.php:149 -msgid " Show Album Name" -msgstr "" - -#: includes/class-current-playlist-widget.php:94 -#: widgets/class-current-playlist-widget.php:157 -msgid "Show Record Label Name" -msgstr "" - -#: includes/class-current-playlist-widget.php:101 -#: widgets/class-current-playlist-widget.php:165 -msgid "Show DJ Comments" -msgstr "" - -#: includes/class-current-playlist-widget.php:108 -#: widgets/class-current-playlist-widget.php:88 -#: widgets/class-current-show-widget.php:117 -#: widgets/class-upcoming-shows-widget.php:120 -msgid "Hide Widget if Empty" -msgstr "" - -#: includes/class-current-playlist-widget.php:115 -#: includes/class-current-show-widget.php:183 -#: includes/class-upcoming-shows-widget.php:159 -#: widgets/class-current-playlist-widget.php:123 -#: widgets/class-current-show-widget.php:199 -#: widgets/class-upcoming-shows-widget.php:194 -msgid "Display Countdown Timer" -msgstr "" - -#: includes/class-current-show-widget.php:15 -#: widgets/class-current-show-widget.php:15 -msgid "The currently playing on-air Show." -msgstr "" - -#: includes/class-current-show-widget.php:17 -#: widgets/class-current-show-widget.php:17 -msgid "(Radio Station) Current Show On-Air" -msgstr "" - -#: includes/class-current-show-widget.php:30 -msgid "No Show scheduled for this time." -msgstr "" - -#: includes/class-current-show-widget.php:73 -#: includes/class-upcoming-shows-widget.php:70 -msgid "Link the title to the Show page" -msgstr "" - -#: includes/class-current-show-widget.php:82 -#: includes/class-upcoming-shows-widget.php:79 -#: widgets/class-current-show-widget.php:136 -#: widgets/class-upcoming-shows-widget.php:139 -msgid "Above" -msgstr "" - -#: includes/class-current-show-widget.php:83 -#: includes/class-upcoming-shows-widget.php:80 includes/extra-strings.php:693 -#: widgets/class-current-show-widget.php:137 -#: widgets/class-upcoming-shows-widget.php:140 -msgid "Left" -msgstr "" - -#: includes/class-current-show-widget.php:84 -#: includes/class-upcoming-shows-widget.php:81 includes/extra-strings.php:640 -#: widgets/class-current-show-widget.php:138 -#: widgets/class-upcoming-shows-widget.php:141 -msgid "Right" -msgstr "" - -#: includes/class-current-show-widget.php:85 -#: includes/class-upcoming-shows-widget.php:82 -#: widgets/class-current-show-widget.php:139 -#: widgets/class-upcoming-shows-widget.php:142 -msgid "Below" -msgstr "" - -#: includes/class-current-show-widget.php:92 -#: includes/class-upcoming-shows-widget.php:88 -#: widgets/class-current-show-widget.php:145 -#: widgets/class-upcoming-shows-widget.php:148 -msgid "Show Title Position (relative to Avatar)" -msgstr "" - -#: includes/class-current-show-widget.php:99 -#: widgets/class-current-show-widget.php:154 -msgid "Display Show Avatar" -msgstr "" - -#: includes/class-current-show-widget.php:105 -#: includes/class-upcoming-shows-widget.php:101 -#: widgets/class-current-show-widget.php:171 -#: widgets/class-upcoming-shows-widget.php:174 -msgid "Avatar Width" -msgstr "" - -#: includes/class-current-show-widget.php:108 -msgid "Width of Show Avatar (in pixels, default full width)" -msgstr "" - -#: includes/class-current-show-widget.php:114 -msgid "Display names of the DJs on the Show" -msgstr "" - -#: includes/class-current-show-widget.php:121 -msgid "Link Host/DJ names to author pages" -msgstr "" - -#: includes/class-current-show-widget.php:128 -#: includes/class-upcoming-shows-widget.php:132 -msgid "Display schedule info for this show" -msgstr "" - -#: includes/class-current-show-widget.php:135 -msgid "Display multiple schedules (if show airs more than once per week)" -msgstr "" - -#: includes/class-current-show-widget.php:142 -#: widgets/class-current-show-widget.php:252 -#: widgets/class-upcoming-shows-widget.php:231 -msgid "Display encore presentation text for Show" -msgstr "" - -#: includes/class-current-show-widget.php:149 -msgid "Display description of show" -msgstr "" - -#: includes/class-current-show-widget.php:156 -msgid "Display link to show's playlist" -msgstr "" - -#: includes/class-current-show-widget.php:162 -msgid "No Show Display Text" -msgstr "" - -#: includes/class-current-show-widget.php:165 -msgid "Text to display if no Show is scheduled for the current time." -msgstr "" - -#: includes/class-current-show-widget.php:169 -#: includes/class-radio-clock-widget.php:43 -#: includes/class-upcoming-shows-widget.php:145 -msgid "Time Format" -msgstr "" - -#: includes/class-current-show-widget.php:172 -#: includes/class-radio-clock-widget.php:46 -#: includes/class-upcoming-shows-widget.php:148 includes/extra-strings.php:495 -#: widgets/class-current-show-widget.php:208 -#: widgets/class-radio-clock-widget.php:88 -#: widgets/class-upcoming-shows-widget.php:203 -msgid "12 Hour" -msgstr "" - -#: includes/class-current-show-widget.php:173 -#: includes/class-radio-clock-widget.php:47 -#: includes/class-upcoming-shows-widget.php:149 includes/extra-strings.php:496 -#: widgets/class-current-show-widget.php:209 -#: widgets/class-radio-clock-widget.php:89 -#: widgets/class-upcoming-shows-widget.php:204 -msgid "24 Hour" -msgstr "" - -#: includes/class-current-show-widget.php:177 -#: includes/class-radio-clock-widget.php:51 -msgid "Choose time format for displayed schedules" -msgstr "" - -#: includes/class-radio-clock-widget.php:14 -#: widgets/class-radio-clock-widget.php:14 -msgid "Display current radio and user times." -msgstr "" - -#: includes/class-radio-clock-widget.php:16 -#: widgets/class-radio-clock-widget.php:16 -msgid "(Radio Station) Radio Clock" -msgstr "" - -#: includes/class-radio-clock-widget.php:23 includes/extra-strings.php:518 -#: widgets/class-radio-clock-widget.php:23 -msgid "Radio Clock" -msgstr "" - -#: includes/class-radio-clock-widget.php:57 -msgid "Include seconds display." -msgstr "" - -#: includes/class-radio-clock-widget.php:62 -msgid "Day Display" -msgstr "" - -#: includes/class-radio-clock-widget.php:64 -#: includes/class-radio-clock-widget.php:83 includes/extra-strings.php:501 -#: widgets/class-radio-clock-widget.php:49 -#: widgets/class-radio-clock-widget.php:68 -msgid "Full" -msgstr "" - -#: includes/class-radio-clock-widget.php:65 -#: includes/class-radio-clock-widget.php:84 includes/extra-strings.php:522 -#: widgets/class-radio-clock-widget.php:50 -#: widgets/class-radio-clock-widget.php:69 -msgid "Short" -msgstr "" - -#: includes/class-radio-clock-widget.php:66 -#: includes/class-radio-clock-widget.php:85 includes/extra-strings.php:499 -#: includes/post-types-admin.php:4962 options.php:690 -#: widgets/class-radio-clock-widget.php:51 -#: widgets/class-radio-clock-widget.php:70 -msgid "None" -msgstr "" - -#: includes/class-radio-clock-widget.php:70 -msgid "Display day with clock times." -msgstr "" - -#: includes/class-radio-clock-widget.php:76 -msgid "Include date display." -msgstr "" - -#: includes/class-radio-clock-widget.php:81 -msgid "Month Display" -msgstr "" - -#: includes/class-radio-clock-widget.php:89 -msgid "Display month with clock times." -msgstr "" - -#: includes/class-radio-clock-widget.php:95 -msgid "Include timezone display." -msgstr "" - -#: includes/class-radio-player-widget.php:16 -#: widgets/class-radio-player-widget.php:16 -msgid "Radio Station Stream Player." -msgstr "" - -#: includes/class-radio-player-widget.php:18 -#: widgets/class-radio-player-widget.php:18 -msgid "(Radio Station) Stream Player" -msgstr "" - -#: includes/class-radio-player-widget.php:47 -#: widgets/class-radio-player-widget.php:75 -msgid "Stream or File URL" -msgstr "" - -#: includes/class-radio-player-widget.php:57 -#: widgets/class-current-playlist-widget.php:60 -#: widgets/class-current-show-widget.php:79 -#: widgets/class-radio-clock-widget.php:40 -#: widgets/class-radio-player-widget.php:67 -#: widgets/class-upcoming-shows-widget.php:74 -msgid "Widget Title" -msgstr "" - -#: includes/class-radio-player-widget.php:66 -#: widgets/class-radio-player-widget.php:84 -msgid "Player Station Text" -msgstr "" - -#: includes/class-radio-player-widget.php:77 -#: includes/class-radio-player-widget.php:94 -#: includes/class-radio-player-widget.php:130 -#: includes/class-radio-player-widget.php:149 includes/extra-strings.php:244 -#: widgets/class-radio-player-widget.php:98 -#: widgets/class-radio-player-widget.php:119 -#: widgets/class-radio-player-widget.php:192 -#: widgets/class-radio-player-widget.php:212 -msgid "Plugin Setting" -msgstr "" - -#: includes/class-radio-player-widget.php:78 includes/extra-strings.php:589 -#: options.php:268 widgets/class-radio-player-widget.php:95 -#: widgets/class-radio-player-widget.php:99 -msgid "Display Station Image" -msgstr "" - -#: includes/class-radio-player-widget.php:79 -#: widgets/class-radio-player-widget.php:100 -msgid "Do Not Display Image" -msgstr "" - -#: includes/class-radio-player-widget.php:86 -msgid "Whether to display Station Image in Player." -msgstr "" - -#: includes/class-radio-player-widget.php:95 includes/extra-strings.php:593 -#: options.php:285 options.php:302 widgets/class-radio-player-widget.php:120 -msgid "Amplitude" -msgstr "" - -#: includes/class-radio-player-widget.php:96 includes/extra-strings.php:594 -#: options.php:284 options.php:301 widgets/class-radio-player-widget.php:121 -msgid "Howler" -msgstr "" - -#: includes/class-radio-player-widget.php:97 includes/extra-strings.php:595 -#: options.php:283 options.php:300 widgets/class-radio-player-widget.php:122 -msgid "jPlayer" -msgstr "" - -#: includes/class-radio-player-widget.php:104 -msgid "Player Script to load by default." -msgstr "" - -#: includes/class-radio-player-widget.php:113 -#: widgets/class-radio-player-widget.php:176 -msgid "Vertical (Stacked)" -msgstr "" - -#: includes/class-radio-player-widget.php:114 -#: widgets/class-radio-player-widget.php:177 -msgid "Horizontal (Inline)" -msgstr "" - -#: includes/class-radio-player-widget.php:121 -msgid "Layout Style for Widget Area (wide or tall)" -msgstr "" - -#: includes/class-radio-player-widget.php:131 includes/extra-strings.php:611 -#: options.php:315 widgets/class-radio-player-widget.php:193 -msgid "Light" -msgstr "" - -#: includes/class-radio-player-widget.php:132 includes/extra-strings.php:612 -#: options.php:316 widgets/class-radio-player-widget.php:194 -msgid "Dark" -msgstr "" - -#: includes/class-radio-player-widget.php:140 -#: widgets/class-radio-player-widget.php:189 -msgid "Player Theme Style" -msgstr "" - -#: includes/class-radio-player-widget.php:150 includes/extra-strings.php:614 -#: widgets/class-radio-player-widget.php:213 -msgid "Circular" -msgstr "" - -#: includes/class-radio-player-widget.php:151 includes/extra-strings.php:615 -#: widgets/class-radio-player-widget.php:214 -msgid "Rounded" -msgstr "" - -#: includes/class-radio-player-widget.php:152 includes/extra-strings.php:616 -#: widgets/class-radio-player-widget.php:215 -msgid "Square" -msgstr "" - -#: includes/class-radio-player-widget.php:160 -msgid "Layout Style for Widget Area" -msgstr "" - -#: includes/class-radio-player-widget.php:167 options.php:418 -#: widgets/class-radio-player-widget.php:135 -msgid "Player Start Volume" -msgstr "" - -#: includes/class-radio-player-widget.php:176 -msgid "Use this as the default Player instance." -msgstr "" - -#: includes/class-upcoming-shows-widget.php:15 -#: widgets/class-upcoming-shows-widget.php:15 -msgid "Display the upcoming Shows." -msgstr "" - -#: includes/class-upcoming-shows-widget.php:17 -#: widgets/class-upcoming-shows-widget.php:17 -msgid "(Radio Station) Upcoming Shows" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:95 -#: widgets/class-upcoming-shows-widget.php:157 -msgid "Display Show Avatars" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:104 -msgid "Width of Show Avatars (in pixels, default 75px)" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:110 -msgid "Display names of the DJs on the show" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:117 -msgid "Link DJ names to author pages" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:123 -msgid "No Additional Schedules Text" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:126 -msgid "If no Show is scheduled for the current time, display this text." -msgstr "" - -#: includes/class-upcoming-shows-widget.php:138 -#: widgets/class-upcoming-shows-widget.php:82 -msgid "Limit" -msgstr "" - -#: includes/class-upcoming-shows-widget.php:141 -#: widgets/class-upcoming-shows-widget.php:85 -msgid "Number of upcoming Shows to display." -msgstr "" - -#: includes/class-upcoming-shows-widget.php:153 -msgid "Choose time format for displayed schedules." -msgstr "" - -#: includes/data-feeds.php:1334 +#: includes/data-feeds.php:1352 msgid "Error 400 No Requested Data" msgstr "" -#: includes/data-feeds.php:1335 +#: includes/data-feeds.php:1353 msgid "The requested data could not be found." msgstr "" -#: includes/extra-strings.php:2 options.php:723 +#: includes/data-feeds.php:1464 includes/data-feeds.php:1491 +#: includes/extra-strings.php:431 includes/shortcodes.php:2828 +#: includes/shortcodes.php:3613 templates/master-schedule-div.php:135 +#: templates/master-schedule-legacy.php:184 +#: templates/master-schedule-list.php:335 +#: templates/master-schedule-table.php:537 +#: templates/master-schedule-tabs.php:432 templates/single-show-content.php:377 +#: templates/single-show-content.php:416 +msgid "and" +msgstr "" + +#: includes/extra-strings.php:2 options.php:752 msgid "Grid View" msgstr "" -#: includes/extra-strings.php:3 options.php:724 +#: includes/extra-strings.php:3 options.php:753 msgid "Calendar View" msgstr "" @@ -581,33 +103,33 @@ msgstr "" msgid "Magenta" msgstr "" -#: includes/extra-strings.php:14 options.php:1033 +#: includes/extra-strings.php:14 options.php:1101 msgid "Team Archive Page" msgstr "" -#: includes/extra-strings.php:15 options.php:1037 +#: includes/extra-strings.php:15 options.php:1105 msgid "Select the Page for displaying the Team archive list." msgstr "" -#: includes/extra-strings.php:16 options.php:659 options.php:944 -#: options.php:977 options.php:1010 options.php:1046 options.php:1077 -#: options.php:1112 +#: includes/extra-strings.php:16 options.php:688 options.php:986 +#: options.php:1019 options.php:1052 options.php:1088 options.php:1114 +#: options.php:1145 options.php:1180 msgid "Automatic Display" msgstr "" -#: includes/extra-strings.php:17 options.php:1055 +#: includes/extra-strings.php:17 options.php:1123 msgid "Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:" msgstr "" -#: includes/extra-strings.php:18 options.php:845 +#: includes/extra-strings.php:18 msgid "Combined Team Tab" msgstr "" -#: includes/extra-strings.php:19 options.php:848 +#: includes/extra-strings.php:19 msgid "Do Not Combine" msgstr "" -#: includes/extra-strings.php:20 options.php:849 +#: includes/extra-strings.php:20 msgid "Combined List" msgstr "" @@ -619,31 +141,31 @@ msgstr "" msgid "Combine team members (eg. hosts, producers into a single display tab." msgstr "" -#: includes/extra-strings.php:23 options.php:736 +#: includes/extra-strings.php:23 options.php:765 msgid "Time Spaced Grid" msgstr "" -#: includes/extra-strings.php:24 options.php:739 +#: includes/extra-strings.php:24 options.php:768 msgid "Enable Grid View option for equalized time spacing and background imsges." msgstr "" -#: includes/extra-strings.php:25 options.php:500 +#: includes/extra-strings.php:25 options.php:515 msgid "Player Bar Height" msgstr "" -#: includes/extra-strings.php:26 options.php:504 +#: includes/extra-strings.php:26 options.php:519 msgid "Set the height of the Sitewide Player Bar in pixels." msgstr "" -#: includes/extra-strings.php:27 options.php:590 +#: includes/extra-strings.php:27 options.php:606 msgid "Display Current Show" msgstr "" -#: includes/extra-strings.php:28 options.php:595 +#: includes/extra-strings.php:28 options.php:611 msgid "Display the Current Show in the Player Bar." msgstr "" -#: includes/extra-strings.php:29 options.php:603 +#: includes/extra-strings.php:29 options.php:619 msgid "Display Now Playing" msgstr "" @@ -651,39 +173,39 @@ msgstr "" msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist" msgstr "" -#: includes/extra-strings.php:31 options.php:629 +#: includes/extra-strings.php:31 options.php:632 msgid "Track Animation" msgstr "" -#: includes/extra-strings.php:32 options.php:632 +#: includes/extra-strings.php:32 options.php:635 msgid "No Animation" msgstr "" -#: includes/extra-strings.php:33 options.php:633 +#: includes/extra-strings.php:33 options.php:636 msgid "Left to Right Ticker" msgstr "" -#: includes/extra-strings.php:34 options.php:634 +#: includes/extra-strings.php:34 options.php:637 msgid "Right to Left Ticker" msgstr "" -#: includes/extra-strings.php:35 options.php:635 +#: includes/extra-strings.php:35 options.php:638 msgid "Back and Forth" msgstr "" -#: includes/extra-strings.php:36 options.php:639 +#: includes/extra-strings.php:36 options.php:642 msgid "How to animate the currently playing track display." msgstr "" -#: includes/extra-strings.php:37 options.php:617 +#: includes/extra-strings.php:37 options.php:651 msgid "Metadata URL" msgstr "" -#: includes/extra-strings.php:38 options.php:621 +#: includes/extra-strings.php:38 options.php:655 msgid "Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location." msgstr "" -#: includes/extra-strings.php:39 options.php:553 +#: includes/extra-strings.php:39 options.php:569 msgid "Page Load Timeout" msgstr "" @@ -691,11 +213,11 @@ msgstr "" msgid "Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display." msgstr "" -#: includes/extra-strings.php:41 options.php:457 +#: includes/extra-strings.php:41 options.php:472 msgid "Popup Player Button" msgstr "" -#: includes/extra-strings.php:42 options.php:460 +#: includes/extra-strings.php:42 options.php:475 msgid "Add button to open Popup Player in separate window." msgstr "" @@ -775,12 +297,12 @@ msgstr "" msgid "Back to Plugins page." msgstr "" -#: includes/extra-strings.php:62 loader.php:1682 radio-station-admin.php:203 -#: radio-station-admin.php:207 radio-station-admin.php:238 +#: includes/extra-strings.php:62 loader.php:1780 radio-station-admin.php:208 +#: radio-station-admin.php:212 radio-station-admin.php:243 msgid "Settings" msgstr "" -#: includes/extra-strings.php:64 radio-station-admin.php:229 +#: includes/extra-strings.php:64 radio-station-admin.php:234 #: templates/playlist-export.php:2 msgid "Export Playlists" msgstr "" @@ -805,19 +327,19 @@ msgstr "" msgid "Radio Station Pro" msgstr "" -#: includes/extra-strings.php:70 includes/support-functions.php:1134 +#: includes/extra-strings.php:70 includes/support-functions.php:1213 msgid "Thumbnail" msgstr "" -#: includes/extra-strings.php:71 includes/support-functions.php:1135 +#: includes/extra-strings.php:71 includes/support-functions.php:1214 msgid "Medium" msgstr "" -#: includes/extra-strings.php:72 includes/support-functions.php:1136 +#: includes/extra-strings.php:72 includes/support-functions.php:1215 msgid "Large" msgstr "" -#: includes/extra-strings.php:73 includes/support-functions.php:1137 +#: includes/extra-strings.php:73 includes/support-functions.php:1216 msgid "Full Size" msgstr "" @@ -889,21 +411,21 @@ msgstr "" msgid "Remove %s Avatar Image" msgstr "" -#: includes/extra-strings.php:91 includes/post-types-admin.php:1861 -#: loader.php:3010 +#: includes/extra-strings.php:91 includes/post-types-admin.php:1882 +#: loader.php:3108 msgid "Are you sure you want to remove this image?" msgstr "" -#: includes/extra-strings.php:92 includes/post-types-admin.php:1862 +#: includes/extra-strings.php:92 includes/post-types-admin.php:1883 msgid "Select or Upload Image" msgstr "" -#: includes/extra-strings.php:93 includes/post-types-admin.php:1863 +#: includes/extra-strings.php:93 includes/post-types-admin.php:1884 msgid "Use this Image" msgstr "" -#: includes/extra-strings.php:94 includes/post-types-admin.php:6003 -#: includes/post-types.php:44 +#: includes/extra-strings.php:94 includes/post-types-admin.php:6059 +#: includes/post-types.php:46 msgid "Show" msgstr "" @@ -911,11 +433,11 @@ msgstr "" msgid "Select Show" msgstr "" -#: includes/extra-strings.php:96 includes/post-types-admin.php:6384 +#: includes/extra-strings.php:96 includes/post-types-admin.php:6449 msgid "Draft" msgstr "" -#: includes/extra-strings.php:97 includes/post-types-admin.php:6391 +#: includes/extra-strings.php:97 includes/post-types-admin.php:6456 msgid "No Shows to Select." msgstr "" @@ -999,1249 +521,1278 @@ msgstr "" msgid "minutees" msgstr "" -#: includes/extra-strings.php:119 +#: includes/extra-strings.php:118 msgid "Linked Playlist" msgstr "" -#: includes/extra-strings.php:120 +#: includes/extra-strings.php:119 msgid "Select Playlist" msgstr "" -#: includes/extra-strings.php:121 +#: includes/extra-strings.php:120 msgid "No Playlists to Select." msgstr "" -#: includes/extra-strings.php:122 +#: includes/extra-strings.php:121 msgid "The Playlist data for this Episode." msgstr "" -#: includes/extra-strings.php:123 +#: includes/extra-strings.php:122 msgid "Set Episode Number to one above current highest?" msgstr "" -#: includes/extra-strings.php:124 +#: includes/extra-strings.php:123 msgid "Select Audio File" msgstr "" -#: includes/extra-strings.php:125 +#: includes/extra-strings.php:124 msgid "Remove Selected Audio" msgstr "" -#: includes/extra-strings.php:126 +#: includes/extra-strings.php:125 msgid "Are you sure you want to remove this audio?" msgstr "" -#: includes/extra-strings.php:127 +#: includes/extra-strings.php:126 msgid "Assign to Show Episode" msgstr "" -#: includes/extra-strings.php:128 +#: includes/extra-strings.php:127 msgid "No Episode" msgstr "" -#: includes/extra-strings.php:129 +#: includes/extra-strings.php:128 msgid "No Show Episodes found." msgstr "" -#: includes/extra-strings.php:130 includes/post-types-admin.php:5798 +#: includes/extra-strings.php:129 includes/post-types-admin.php:5853 msgid "No Show selected." msgstr "" -#: includes/extra-strings.php:131 +#: includes/extra-strings.php:130 msgid "Warning! This playlist is assigned to more than one episode." msgstr "" -#: includes/extra-strings.php:132 +#: includes/extra-strings.php:131 msgid "Number" msgstr "" -#: includes/extra-strings.php:133 +#: includes/extra-strings.php:132 msgid "of" msgstr "" -#: includes/extra-strings.php:134 includes/post-types-admin.php:6053 -#: includes/post-types-admin.php:6620 includes/post-types.php:47 +#: includes/extra-strings.php:133 includes/post-types-admin.php:6109 +#: includes/post-types-admin.php:6689 includes/post-types.php:49 msgid "Edit Show" msgstr "" -#: includes/extra-strings.php:135 +#: includes/extra-strings.php:134 msgid "Episode Show" msgstr "" -#: includes/extra-strings.php:136 includes/post-types.php:56 +#: includes/extra-strings.php:135 includes/post-types.php:58 msgid "All Shows" msgstr "" -#: includes/extra-strings.php:137 +#: includes/extra-strings.php:136 msgid "Episode for Show" msgstr "" -#: includes/extra-strings.php:138 +#: includes/extra-strings.php:137 msgid "Not Assigned" msgstr "" -#: includes/extra-strings.php:139 includes/post-types-admin.php:6199 -#: includes/post-types-admin.php:6549 +#: includes/extra-strings.php:138 includes/post-types-admin.php:6262 +#: includes/post-types-admin.php:6616 msgid "No Shows available to Select." msgstr "" -#: includes/extra-strings.php:140 +#: includes/extra-strings.php:139 msgid "Episode" msgstr "" -#: includes/extra-strings.php:141 +#: includes/extra-strings.php:140 msgid "Add Episode" msgstr "" -#: includes/extra-strings.php:142 +#: includes/extra-strings.php:141 msgid "Edit Episode" msgstr "" -#: includes/extra-strings.php:143 +#: includes/extra-strings.php:142 msgid "New Episode" msgstr "" -#: includes/extra-strings.php:144 +#: includes/extra-strings.php:143 msgid "View Episode" msgstr "" -#: includes/extra-strings.php:145 +#: includes/extra-strings.php:144 msgid "Search Episodes" msgstr "" -#: includes/extra-strings.php:146 +#: includes/extra-strings.php:145 msgid "No Episodes found" msgstr "" -#: includes/extra-strings.php:147 +#: includes/extra-strings.php:146 msgid "No Episodes found in Trash" msgstr "" -#: includes/extra-strings.php:148 +#: includes/extra-strings.php:147 msgid "All Episodes" msgstr "" -#: includes/extra-strings.php:149 +#: includes/extra-strings.php:148 msgid "Post Type for Show Episodes" msgstr "" -#: includes/extra-strings.php:150 +#: includes/extra-strings.php:149 msgid "Search Topics" msgstr "" -#: includes/extra-strings.php:151 +#: includes/extra-strings.php:150 msgid "All Topics" msgstr "" -#: includes/extra-strings.php:152 +#: includes/extra-strings.php:151 msgid "Parent Topic" msgstr "" -#: includes/extra-strings.php:153 +#: includes/extra-strings.php:152 msgid "Parent Topic:" msgstr "" -#: includes/extra-strings.php:154 +#: includes/extra-strings.php:153 msgid "Edit Topic" msgstr "" -#: includes/extra-strings.php:155 +#: includes/extra-strings.php:154 msgid "Update Topic" msgstr "" -#: includes/extra-strings.php:156 +#: includes/extra-strings.php:155 msgid "Add New Topic" msgstr "" -#: includes/extra-strings.php:157 +#: includes/extra-strings.php:156 msgid "New Topic Name" msgstr "" -#: includes/extra-strings.php:158 +#: includes/extra-strings.php:157 msgid "Topic" msgstr "" -#: includes/extra-strings.php:159 +#: includes/extra-strings.php:158 msgid "Search Guests" msgstr "" -#: includes/extra-strings.php:160 +#: includes/extra-strings.php:159 msgid "All Guests" msgstr "" -#: includes/extra-strings.php:161 +#: includes/extra-strings.php:160 msgid "Parent Guest" msgstr "" -#: includes/extra-strings.php:162 +#: includes/extra-strings.php:161 msgid "Parent Guest:" msgstr "" -#: includes/extra-strings.php:163 +#: includes/extra-strings.php:162 msgid "Edit Guest" msgstr "" -#: includes/extra-strings.php:164 +#: includes/extra-strings.php:163 msgid "Update Guest" msgstr "" -#: includes/extra-strings.php:165 +#: includes/extra-strings.php:164 msgid "Add New Guest" msgstr "" -#: includes/extra-strings.php:166 +#: includes/extra-strings.php:165 msgid "New Guest Name" msgstr "" -#: includes/extra-strings.php:167 +#: includes/extra-strings.php:166 msgid "Guest" msgstr "" -#: includes/extra-strings.php:168 +#: includes/extra-strings.php:167 msgid "Show %s" msgstr "" -#: includes/extra-strings.php:169 +#: includes/extra-strings.php:168 msgid "Aired on" msgstr "" -#: includes/extra-strings.php:170 includes/post-types-admin.php:4746 +#: includes/extra-strings.php:169 includes/post-types-admin.php:4794 msgid "Image" msgstr "" -#: includes/extra-strings.php:171 +#: includes/extra-strings.php:170 msgid "Add Genre Image" msgstr "" -#: includes/extra-strings.php:172 +#: includes/extra-strings.php:171 msgid "Remove Genre Image" msgstr "" -#: includes/extra-strings.php:173 +#: includes/extra-strings.php:172 msgid "Background Color" msgstr "" -#: includes/extra-strings.php:174 +#: includes/extra-strings.php:173 msgid "Border Color" msgstr "" -#: includes/extra-strings.php:175 +#: includes/extra-strings.php:174 msgid "Text Color" msgstr "" -#: includes/extra-strings.php:176 +#: includes/extra-strings.php:175 msgid "Insert" msgstr "" -#: includes/extra-strings.php:177 +#: includes/extra-strings.php:176 msgid "this is a success message" msgstr "" -#: includes/extra-strings.php:178 +#: includes/extra-strings.php:177 msgid "this is a failure message" msgstr "" -#: includes/extra-strings.php:179 +#: includes/extra-strings.php:178 msgid "Please upload a valid YAML file." msgstr "" -#: includes/extra-strings.php:180 +#: includes/extra-strings.php:179 msgid "Please upload a valid YAML file" msgstr "" -#: includes/extra-strings.php:181 +#: includes/extra-strings.php:180 msgid "Please upload a file to import." msgstr "" -#: includes/extra-strings.php:182 +#: includes/extra-strings.php:181 msgid "Please upload a file to import" msgstr "" -#: includes/extra-strings.php:183 +#: includes/extra-strings.php:182 msgid "Successfully parsed and imported YAML file, deleting pre-existing show data." msgstr "" -#: includes/extra-strings.php:184 +#: includes/extra-strings.php:183 msgid "Successfully parsed and imported YAML file. Pre-existing show data remains unchanged." msgstr "" -#: includes/extra-strings.php:185 +#: includes/extra-strings.php:184 msgid "Export successful. Use the following link(s to download your data." msgstr "" -#: includes/extra-strings.php:186 +#: includes/extra-strings.php:185 msgid "Data file" msgstr "" -#: includes/extra-strings.php:187 +#: includes/extra-strings.php:186 msgid "Image zip file" msgstr "" -#: includes/extra-strings.php:188 +#: includes/extra-strings.php:187 msgid "Image tgz file" msgstr "" -#: includes/extra-strings.php:189 +#: includes/extra-strings.php:188 msgid "Download one of the image files and the data file. See help for details on how to stage an import including images." msgstr "" -#: includes/extra-strings.php:190 +#: includes/extra-strings.php:189 msgid "Show data file (YAML" msgstr "" -#: includes/extra-strings.php:191 +#: includes/extra-strings.php:190 msgid "Images not exported. See help for details on how to include images." msgstr "" -#: includes/extra-strings.php:192 +#: includes/extra-strings.php:191 msgid "YAML import error. See below for details." msgstr "" -#: includes/extra-strings.php:193 +#: includes/extra-strings.php:192 msgid "Failed to create export folder" msgstr "" -#: includes/extra-strings.php:194 +#: includes/extra-strings.php:193 msgid "No image exported for" msgstr "" -#: includes/extra-strings.php:195 +#: includes/extra-strings.php:194 msgid "Failed to copy file. See below for details" msgstr "" -#: includes/extra-strings.php:196 +#: includes/extra-strings.php:195 msgid "Failed to create show_images.zip" msgstr "" -#: includes/extra-strings.php:197 +#: includes/extra-strings.php:196 msgid "Each show in the YAML file must define at minimum the following keys: " msgstr "" -#: includes/extra-strings.php:198 +#: includes/extra-strings.php:197 msgid "show-title: may not be null." msgstr "" -#: includes/extra-strings.php:199 +#: includes/extra-strings.php:198 msgid "show-description: may not be null." msgstr "" -#: includes/extra-strings.php:200 +#: includes/extra-strings.php:199 msgid "show-image: must be a URL reference to an existing image." msgstr "" -#: includes/extra-strings.php:201 +#: includes/extra-strings.php:200 msgid "show-avatar: must be a URL reference to an existing image." msgstr "" -#: includes/extra-strings.php:202 +#: includes/extra-strings.php:201 msgid "show-header: must be a URL reference to an existing image." msgstr "" -#: includes/extra-strings.php:203 +#: includes/extra-strings.php:202 msgid "upload-images: may not be null." msgstr "" -#: includes/extra-strings.php:204 +#: includes/extra-strings.php:203 msgid "show-url: must be a valid web address." msgstr "" -#: includes/extra-strings.php:205 +#: includes/extra-strings.php:204 msgid "show-podcast: must be a valid web address." msgstr "" -#: includes/extra-strings.php:206 +#: includes/extra-strings.php:205 msgid "show-user-list: must be a simple array of valid email addresses." msgstr "" -#: includes/extra-strings.php:207 +#: includes/extra-strings.php:206 msgid "show-producer-list: must be a simple array of valid email addresses." msgstr "" -#: includes/extra-strings.php:208 +#: includes/extra-strings.php:207 msgid "show-email: must be a valid email address." msgstr "" -#: includes/extra-strings.php:209 +#: includes/extra-strings.php:208 msgid "show-active: may not be null." msgstr "" -#: includes/extra-strings.php:210 +#: includes/extra-strings.php:209 msgid "YAML data parsed successfully, but contains formatting errors. See below for details." msgstr "" -#: includes/extra-strings.php:211 +#: includes/extra-strings.php:210 msgid "Data file errors noted as follows:" msgstr "" -#: includes/extra-strings.php:212 +#: includes/extra-strings.php:211 msgid "show-schedule[] time blocks must be in 24h format and have the form \"04:55\" (note 0 padding." msgstr "" -#: includes/extra-strings.php:213 +#: includes/extra-strings.php:212 msgid "Error, for show-schedule[], only \"disabled\" and \"encore\" flags are allowed" msgstr "" -#: includes/extra-strings.php:214 +#: includes/extra-strings.php:213 msgid "show-schedule[] must reference an array of time blocks containing at least one element." msgstr "" -#: includes/extra-strings.php:215 +#: includes/extra-strings.php:214 msgid "Invalid weekday. show-schedule[] must be one of \"sun\"..\"sat\", or \"sunday\"..\"saturday\" (case insensitive." msgstr "" -#: includes/extra-strings.php:216 +#: includes/extra-strings.php:215 msgid " show-schedule: must define at least one weekday." msgstr "" -#: includes/extra-strings.php:217 +#: includes/extra-strings.php:216 msgid "Import/Export Show Data" msgstr "" -#: includes/extra-strings.php:218 radio-station-admin.php:235 +#: includes/extra-strings.php:217 radio-station-admin.php:240 msgid "Import/Export" msgstr "" -#: includes/extra-strings.php:220 +#: includes/extra-strings.php:219 msgid "Import show data from a YAML file." msgstr "" -#: includes/extra-strings.php:221 +#: includes/extra-strings.php:220 msgid "Delete existing show data" msgstr "" -#: includes/extra-strings.php:222 +#: includes/extra-strings.php:221 msgid "WARNING" msgstr "" -#: includes/extra-strings.php:224 +#: includes/extra-strings.php:223 msgid "Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options." msgstr "" -#: includes/extra-strings.php:225 +#: includes/extra-strings.php:224 msgid "Advanced" msgstr "" -#: includes/extra-strings.php:226 +#: includes/extra-strings.php:225 msgid "YAML file name" msgstr "" -#: includes/extra-strings.php:227 +#: includes/extra-strings.php:226 msgid "Default similar to" msgstr "" -#: includes/extra-strings.php:228 +#: includes/extra-strings.php:227 msgid "URL where show images will be staged for import (see help" msgstr "" -#: includes/extra-strings.php:229 +#: includes/extra-strings.php:228 msgid "Metadata Source" msgstr "" -#: includes/extra-strings.php:230 +#: includes/extra-strings.php:229 msgid "Default Plugin Setting" msgstr "" -#: includes/extra-strings.php:231 +#: includes/extra-strings.php:230 msgid "Override: Metadata On" msgstr "" -#: includes/extra-strings.php:232 +#: includes/extra-strings.php:231 msgid "Override: Metadata Off" msgstr "" -#: includes/extra-strings.php:233 +#: includes/extra-strings.php:232 msgid "Override: Metadata URL" msgstr "" -#: includes/extra-strings.php:234 +#: includes/extra-strings.php:233 msgid "Override: Current Playlist" msgstr "" -#: includes/extra-strings.php:235 +#: includes/extra-strings.php:234 msgid "Metadata Override URL" msgstr "" -#: includes/extra-strings.php:236 +#: includes/extra-strings.php:235 msgid "Color Options" msgstr "" -#: includes/extra-strings.php:237 +#: includes/extra-strings.php:236 msgid "Player Text Color" msgstr "" -#: includes/extra-strings.php:238 +#: includes/extra-strings.php:237 msgid "Player Background Color" msgstr "" -#: includes/extra-strings.php:239 +#: includes/extra-strings.php:238 msgid "Playing Color" msgstr "" -#: includes/extra-strings.php:240 +#: includes/extra-strings.php:239 msgid "Buttons Color" msgstr "" -#: includes/extra-strings.php:241 +#: includes/extra-strings.php:240 msgid "Track Color" msgstr "" -#: includes/extra-strings.php:242 +#: includes/extra-strings.php:241 msgid "Track Thumb Color" msgstr "" -#: includes/extra-strings.php:243 +#: includes/extra-strings.php:242 msgid "Advanced Options" msgstr "" -#: includes/extra-strings.php:247 +#: includes/extra-strings.php:243 widgets/class-radio-player-widget.php:100 +#: widgets/class-radio-player-widget.php:121 +#: widgets/class-radio-player-widget.php:194 +#: widgets/class-radio-player-widget.php:214 +msgid "Plugin Setting" +msgstr "" + +#: includes/extra-strings.php:244 widgets/class-current-playlist-widget.php:72 +#: widgets/class-current-show-widget.php:85 +#: widgets/class-upcoming-shows-widget.php:88 +msgid "On" +msgstr "" + +#: includes/extra-strings.php:245 options.php:1117 +#: widgets/class-current-playlist-widget.php:73 +#: widgets/class-current-show-widget.php:86 +#: widgets/class-upcoming-shows-widget.php:89 +msgid "Off" +msgstr "" + +#: includes/extra-strings.php:246 msgid "Current Show Display" msgstr "" -#: includes/extra-strings.php:248 +#: includes/extra-strings.php:247 msgid "Now Playing Track Display" msgstr "" -#: includes/extra-strings.php:249 +#: includes/extra-strings.php:248 msgid "Leave blank to use stream URL." msgstr "" -#: includes/extra-strings.php:250 +#: includes/extra-strings.php:249 msgid "Custom Theme" msgstr "" -#: includes/extra-strings.php:251 +#: includes/extra-strings.php:250 msgid "Popup Player to separate window." msgstr "" -#: includes/extra-strings.php:252 +#: includes/extra-strings.php:251 msgid "Popup Player" msgstr "" -#: includes/extra-strings.php:253 +#: includes/extra-strings.php:252 msgid "Edit Your Host Profile" msgstr "" -#: includes/extra-strings.php:254 +#: includes/extra-strings.php:253 msgid "Host Profile" msgstr "" -#: includes/extra-strings.php:255 +#: includes/extra-strings.php:254 msgid "Edit Your Producer Profile" msgstr "" -#: includes/extra-strings.php:256 +#: includes/extra-strings.php:255 msgid "Producer Profile" msgstr "" -#: includes/extra-strings.php:257 includes/extra-strings.php:349 +#: includes/extra-strings.php:256 includes/extra-strings.php:348 msgid "You do not have permission to do that." msgstr "" -#: includes/extra-strings.php:258 +#: includes/extra-strings.php:257 msgid "Profile Information" msgstr "" -#: includes/extra-strings.php:259 includes/post-types-admin.php:474 -#: includes/post-types-admin.php:3208 +#: includes/extra-strings.php:258 includes/post-types-admin.php:479 +#: includes/post-types-admin.php:3252 msgid "Website Link" msgstr "" -#: includes/extra-strings.php:260 +#: includes/extra-strings.php:259 msgid "%s Email" msgstr "" -#: includes/extra-strings.php:261 +#: includes/extra-strings.php:260 msgid "%s Phone" msgstr "" -#: includes/extra-strings.php:262 includes/post-types-admin.php:527 -#: includes/post-types-admin.php:3319 +#: includes/extra-strings.php:261 includes/post-types-admin.php:533 +#: includes/post-types-admin.php:3363 msgid "Patreon Page ID" msgstr "" -#: includes/extra-strings.php:263 +#: includes/extra-strings.php:262 msgid "Host Image" msgstr "" -#: includes/extra-strings.php:264 +#: includes/extra-strings.php:263 msgid "Producer Image" msgstr "" -#: includes/extra-strings.php:265 +#: includes/extra-strings.php:264 msgid "User Save Error! The selected User is already assigned to a %s Profile." msgstr "" -#: includes/extra-strings.php:266 includes/post-types-admin.php:2630 -#: includes/post-types-admin.php:3164 includes/post-types.php:193 +#: includes/extra-strings.php:265 includes/post-types-admin.php:2655 +#: includes/post-types-admin.php:3208 includes/post-types.php:195 msgid "Hosts" msgstr "" -#: includes/extra-strings.php:267 +#: includes/extra-strings.php:266 msgid "Host User" msgstr "" -#: includes/extra-strings.php:268 includes/post-types-admin.php:3186 -#: includes/post-types.php:245 +#: includes/extra-strings.php:267 includes/post-types-admin.php:3230 +#: includes/post-types.php:247 msgid "Producers" msgstr "" -#: includes/extra-strings.php:269 +#: includes/extra-strings.php:268 msgid "Producer User" msgstr "" -#: includes/extra-strings.php:270 +#: includes/extra-strings.php:269 msgid "Team" msgstr "" -#: includes/extra-strings.php:271 +#: includes/extra-strings.php:270 msgid "View" msgstr "" -#: includes/extra-strings.php:272 +#: includes/extra-strings.php:271 msgid "All" msgstr "" -#: includes/extra-strings.php:273 +#: includes/extra-strings.php:272 msgid "Editors" msgstr "" -#: includes/extra-strings.php:274 includes/post-types.php:43 -#: includes/post-types.php:51 radio-station-admin.php:214 +#: includes/extra-strings.php:273 includes/post-types.php:45 +#: includes/post-types.php:53 radio-station-admin.php:219 msgid "Shows" msgstr "" -#: includes/extra-strings.php:275 +#: includes/extra-strings.php:274 msgid "Recurring Overrides" msgstr "" -#: includes/extra-strings.php:276 +#: includes/extra-strings.php:275 msgid "Clear Recurrences" msgstr "" -#: includes/extra-strings.php:277 +#: includes/extra-strings.php:276 msgid "Save Recurrences" msgstr "" -#: includes/extra-strings.php:278 +#: includes/extra-strings.php:277 msgid "Add Recurrence" msgstr "" -#: includes/extra-strings.php:279 +#: includes/extra-strings.php:278 msgid "Saving Recurrences..." msgstr "" -#: includes/extra-strings.php:280 +#: includes/extra-strings.php:279 msgid "Recurrences Saved." msgstr "" -#: includes/extra-strings.php:281 +#: includes/extra-strings.php:280 msgid "Single Overrides" msgstr "" -#: includes/extra-strings.php:282 includes/post-types-admin.php:3766 -#: includes/post-types-admin.php:4114 templates/playlist-export.php:14 +#: includes/extra-strings.php:281 includes/post-types-admin.php:3813 +#: includes/post-types-admin.php:4162 templates/playlist-export.php:14 msgid "Start Date" msgstr "" -#: includes/extra-strings.php:283 includes/post-types-admin.php:1163 -#: includes/post-types-admin.php:1502 includes/post-types-admin.php:3777 -#: includes/post-types-admin.php:4120 +#: includes/extra-strings.php:282 includes/post-types-admin.php:1172 +#: includes/post-types-admin.php:1514 includes/post-types-admin.php:3824 +#: includes/post-types-admin.php:4168 msgid "Start Time" msgstr "" -#: includes/extra-strings.php:284 includes/post-types-admin.php:1197 -#: includes/post-types-admin.php:1546 includes/post-types-admin.php:3818 -#: includes/post-types-admin.php:4155 +#: includes/extra-strings.php:283 includes/post-types-admin.php:1206 +#: includes/post-types-admin.php:1558 includes/post-types-admin.php:3865 +#: includes/post-types-admin.php:4203 msgid "End Time" msgstr "" -#: includes/extra-strings.php:285 includes/post-types-admin.php:1238 -#: includes/post-types-admin.php:1602 includes/post-types-admin.php:3889 -#: includes/post-types-admin.php:4208 includes/post-types-admin.php:4902 +#: includes/extra-strings.php:284 includes/post-types-admin.php:1247 +#: includes/post-types-admin.php:1614 includes/post-types-admin.php:3936 +#: includes/post-types-admin.php:4256 includes/post-types-admin.php:4953 msgid "Disabled" msgstr "" -#: includes/extra-strings.php:286 +#: includes/extra-strings.php:285 msgid "Multiday" msgstr "" -#: includes/extra-strings.php:287 +#: includes/extra-strings.php:286 msgid "Repeats" msgstr "" -#: includes/extra-strings.php:288 +#: includes/extra-strings.php:287 msgid "Once Every Month" msgstr "" -#: includes/extra-strings.php:289 +#: includes/extra-strings.php:288 msgid "By Week Intervals" msgstr "" -#: includes/extra-strings.php:290 +#: includes/extra-strings.php:289 msgid "Every" msgstr "" -#: includes/extra-strings.php:291 +#: includes/extra-strings.php:290 msgid "Weeks" msgstr "" -#: includes/extra-strings.php:292 +#: includes/extra-strings.php:291 msgid "On the" msgstr "" -#: includes/extra-strings.php:293 +#: includes/extra-strings.php:292 msgid "First" msgstr "" -#: includes/extra-strings.php:294 radio-station.php:1007 +#: includes/extra-strings.php:293 radio-station.php:1026 msgid "Second" msgstr "" -#: includes/extra-strings.php:295 +#: includes/extra-strings.php:294 msgid "Third" msgstr "" -#: includes/extra-strings.php:296 +#: includes/extra-strings.php:295 msgid "Fourth" msgstr "" -#: includes/extra-strings.php:297 +#: includes/extra-strings.php:296 msgid "Fifth" msgstr "" -#: includes/extra-strings.php:298 +#: includes/extra-strings.php:297 msgid "Last" msgstr "" -#: includes/extra-strings.php:299 +#: includes/extra-strings.php:298 msgid "Cutoff Date" msgstr "" -#: includes/extra-strings.php:300 +#: includes/extra-strings.php:299 msgid "List Recurrence Dates" msgstr "" -#: includes/extra-strings.php:301 +#: includes/extra-strings.php:300 msgid "Preview Recurrence Dates" msgstr "" -#: includes/extra-strings.php:302 +#: includes/extra-strings.php:301 msgid "Duplicate Recurrence" msgstr "" -#: includes/extra-strings.php:303 +#: includes/extra-strings.php:302 msgid "Remove Recurrence" msgstr "" -#: includes/extra-strings.php:304 +#: includes/extra-strings.php:303 msgid "Are you sure you want to remove this Recurrence?" msgstr "" -#: includes/extra-strings.php:305 +#: includes/extra-strings.php:304 msgid "Are you sure you want to clear all Recurrences?" msgstr "" -#: includes/extra-strings.php:306 +#: includes/extra-strings.php:305 msgid "Extra Days" msgstr "" -#: includes/extra-strings.php:307 +#: includes/extra-strings.php:306 msgid "Number of Weeks" msgstr "" -#: includes/extra-strings.php:308 +#: includes/extra-strings.php:307 msgid "Preview Recurrences" msgstr "" -#: includes/extra-strings.php:309 includes/post-types-admin.php:2038 -#: includes/post-types-admin.php:4272 includes/post-types-admin.php:5833 +#: includes/extra-strings.php:308 includes/post-types-admin.php:2059 +#: includes/post-types-admin.php:4318 includes/post-types-admin.php:5888 msgid "Expired. Publish or Update instead." msgstr "" -#: includes/extra-strings.php:310 includes/post-types-admin.php:4274 +#: includes/extra-strings.php:309 includes/post-types-admin.php:4320 msgid "Failed. Invalid Override." msgstr "" -#: includes/extra-strings.php:311 includes/post-types-admin.php:2036 -#: includes/post-types-admin.php:4276 +#: includes/extra-strings.php:310 includes/post-types-admin.php:2057 +#: includes/post-types-admin.php:4322 msgid "Failed. Publish or Update instead." msgstr "" -#: includes/extra-strings.php:312 +#: includes/extra-strings.php:311 msgid "Warning! Recurring conflicts detected." msgstr "" -#: includes/extra-strings.php:313 +#: includes/extra-strings.php:312 msgid "Error! Override not found." msgstr "" -#: includes/extra-strings.php:314 +#: includes/extra-strings.php:313 msgid "Error! No matching recurrence found." msgstr "" -#: includes/extra-strings.php:315 +#: includes/extra-strings.php:314 msgid "Error! No saved recurrences were found." msgstr "" -#: includes/extra-strings.php:316 +#: includes/extra-strings.php:315 msgid "Cannot list recurring times without a valid start time." msgstr "" -#: includes/extra-strings.php:317 +#: includes/extra-strings.php:316 msgid "Cannot list recurring times without a valid end time." msgstr "" -#: includes/extra-strings.php:318 +#: includes/extra-strings.php:317 msgid "Override Date" msgstr "" -#: includes/extra-strings.php:319 +#: includes/extra-strings.php:318 msgid "Shows Affected" msgstr "" -#: includes/extra-strings.php:320 includes/post-types-admin.php:624 +#: includes/extra-strings.php:319 includes/post-types-admin.php:631 msgid "DJs / Hosts" msgstr "" -#: includes/extra-strings.php:321 +#: includes/extra-strings.php:320 msgid "Show Editors" msgstr "" -#: includes/extra-strings.php:322 +#: includes/extra-strings.php:321 msgid "Role Assignment Interface" msgstr "" -#: includes/extra-strings.php:323 +#: includes/extra-strings.php:322 msgid "You can assign Radio Station roles to users directly here." msgstr "" -#: includes/extra-strings.php:324 +#: includes/extra-strings.php:323 msgid "They can also be assigned via the WordPress user editor." msgstr "" -#: includes/extra-strings.php:325 +#: includes/extra-strings.php:324 msgid "Grant Role to User(s" msgstr "" -#: includes/extra-strings.php:326 +#: includes/extra-strings.php:325 msgid "Remove Role from User(s" msgstr "" -#: includes/extra-strings.php:327 +#: includes/extra-strings.php:326 msgid "Update User Roles" msgstr "" -#: includes/extra-strings.php:328 +#: includes/extra-strings.php:327 msgid "You have unsaved role changes." msgstr "" -#: includes/extra-strings.php:329 +#: includes/extra-strings.php:328 msgid "role added to user" msgstr "" -#: includes/extra-strings.php:330 +#: includes/extra-strings.php:329 msgid "role removed from user" msgstr "" -#: includes/extra-strings.php:331 +#: includes/extra-strings.php:330 msgid "Visual Schedule Editor" msgstr "" -#: includes/extra-strings.php:332 +#: includes/extra-strings.php:331 msgid "Table" msgstr "" -#: includes/extra-strings.php:333 +#: includes/extra-strings.php:332 msgid "Tabs" msgstr "" -#: includes/extra-strings.php:334 options.php:1051 +#: includes/extra-strings.php:333 options.php:1119 msgid "Grid" msgstr "" -#: includes/extra-strings.php:335 +#: includes/extra-strings.php:334 msgid "Calendar" msgstr "" -#: includes/extra-strings.php:336 +#: includes/extra-strings.php:335 msgid "Your session has expired. You can log in again from this page or go to the login page." msgstr "" -#: includes/extra-strings.php:337 +#: includes/extra-strings.php:336 msgid "You have unsaved changes. Are you sure you want to close this window?" msgstr "" -#: includes/extra-strings.php:338 +#: includes/extra-strings.php:337 msgid "Save Shift" msgstr "" -#: includes/extra-strings.php:339 includes/post-types-admin.php:875 +#: includes/extra-strings.php:338 includes/post-types-admin.php:882 msgid "Save Shifts" msgstr "" -#: includes/extra-strings.php:340 +#: includes/extra-strings.php:339 msgid "Save Override" msgstr "" -#: includes/extra-strings.php:341 includes/post-types-admin.php:3540 +#: includes/extra-strings.php:340 includes/post-types-admin.php:3584 msgid "Save Overrides" msgstr "" -#: includes/extra-strings.php:342 +#: includes/extra-strings.php:341 msgid "Edit Show Shifts" msgstr "" -#: includes/extra-strings.php:343 +#: includes/extra-strings.php:342 msgid "Edit Override Times" msgstr "" -#: includes/extra-strings.php:344 +#: includes/extra-strings.php:343 msgid "Edit Show Shift" msgstr "" -#: includes/extra-strings.php:345 +#: includes/extra-strings.php:344 msgid "Edit Override Time" msgstr "" -#: includes/extra-strings.php:346 +#: includes/extra-strings.php:345 msgid "Add to Schedule" msgstr "" -#: includes/extra-strings.php:347 +#: includes/extra-strings.php:346 msgid "Add Show or Override to Schedule" msgstr "" -#: includes/extra-strings.php:348 +#: includes/extra-strings.php:347 msgid "Error. Provided Show ID does not exist." msgstr "" -#: includes/extra-strings.php:350 +#: includes/extra-strings.php:349 msgid "Warning. Provided Shift ID is not valid." msgstr "" -#: includes/extra-strings.php:351 +#: includes/extra-strings.php:350 msgid "Warning. Provided Shift ID no longer exists." msgstr "" -#: includes/extra-strings.php:352 +#: includes/extra-strings.php:351 msgid "Display Shifts" msgstr "" -#: includes/extra-strings.php:353 +#: includes/extra-strings.php:352 msgid "Selected Shift" msgstr "" -#: includes/extra-strings.php:354 +#: includes/extra-strings.php:353 msgid "All Show Shifts" msgstr "" -#: includes/extra-strings.php:355 +#: includes/extra-strings.php:354 msgid "Warning. Provided Override Shift ID no longer exists." msgstr "" -#: includes/extra-strings.php:356 +#: includes/extra-strings.php:355 msgid "Override" msgstr "" -#: includes/extra-strings.php:357 +#: includes/extra-strings.php:356 msgid "Display Override" msgstr "" -#: includes/extra-strings.php:358 +#: includes/extra-strings.php:357 msgid "Selected Override Time" msgstr "" -#: includes/extra-strings.php:359 +#: includes/extra-strings.php:358 msgid "All Override Times" msgstr "" -#: includes/extra-strings.php:360 +#: includes/extra-strings.php:359 msgid "Error. Provided ID is not a Show or Override." msgstr "" -#: includes/extra-strings.php:361 +#: includes/extra-strings.php:360 msgid "Error! Invalid date value passed." msgstr "" -#: includes/extra-strings.php:362 +#: includes/extra-strings.php:361 msgid "Error! Invalid day value passed." msgstr "" -#: includes/extra-strings.php:363 +#: includes/extra-strings.php:362 msgid "Timeslot Type" msgstr "" -#: includes/extra-strings.php:364 +#: includes/extra-strings.php:363 msgid "Show Shift" msgstr "" -#: includes/extra-strings.php:365 includes/post-types.php:142 +#: includes/extra-strings.php:364 includes/post-types.php:144 msgid "Schedule Override" msgstr "" -#: includes/extra-strings.php:366 +#: includes/extra-strings.php:365 msgid "Recurring Override" msgstr "" -#: includes/extra-strings.php:367 +#: includes/extra-strings.php:366 msgid "Shift Day" msgstr "" -#: includes/extra-strings.php:368 includes/post-types-admin.php:1231 -#: includes/post-types-admin.php:1591 +#: includes/extra-strings.php:367 includes/post-types-admin.php:1240 +#: includes/post-types-admin.php:1603 msgid "Encore" msgstr "" -#: includes/extra-strings.php:369 +#: includes/extra-strings.php:368 msgid "Show Type" msgstr "" -#: includes/extra-strings.php:370 +#: includes/extra-strings.php:369 msgid "Existing Show" msgstr "" -#: includes/extra-strings.php:371 includes/post-types.php:48 +#: includes/extra-strings.php:370 includes/post-types.php:50 msgid "New Show" msgstr "" -#: includes/extra-strings.php:372 +#: includes/extra-strings.php:371 msgid "Override Type" msgstr "" -#: includes/extra-strings.php:373 +#: includes/extra-strings.php:372 msgid "Existing Override" msgstr "" -#: includes/extra-strings.php:374 +#: includes/extra-strings.php:373 msgid "New Override" msgstr "" -#: includes/extra-strings.php:375 +#: includes/extra-strings.php:374 msgid "Select Show..." msgstr "" -#: includes/extra-strings.php:376 includes/post-types-admin.php:879 +#: includes/extra-strings.php:375 includes/post-types-admin.php:886 msgid "Add Shift" msgstr "" -#: includes/extra-strings.php:377 +#: includes/extra-strings.php:376 msgid "Select Override..." msgstr "" -#: includes/extra-strings.php:378 includes/post-types-admin.php:3543 +#: includes/extra-strings.php:377 includes/post-types-admin.php:3587 msgid "Add Override" msgstr "" -#: includes/extra-strings.php:379 +#: includes/extra-strings.php:378 msgid "Open in a New Window" msgstr "" -#: includes/extra-strings.php:380 +#: includes/extra-strings.php:379 msgid "Create New Show" msgstr "" -#: includes/extra-strings.php:381 +#: includes/extra-strings.php:380 msgid "Create New Override" msgstr "" -#: includes/extra-strings.php:382 +#: includes/extra-strings.php:381 msgid "Adding Show Shift..." msgstr "" -#: includes/extra-strings.php:383 +#: includes/extra-strings.php:382 msgid "Show Shift Added." msgstr "" -#: includes/extra-strings.php:384 includes/post-types-admin.php:871 +#: includes/extra-strings.php:383 includes/post-types-admin.php:878 msgid "Clear Shifts" msgstr "" -#: includes/extra-strings.php:385 +#: includes/extra-strings.php:384 msgid "Adding Override Time..." msgstr "" -#: includes/extra-strings.php:386 +#: includes/extra-strings.php:385 msgid "Override Time Saved." msgstr "" -#: includes/extra-strings.php:387 includes/post-types-admin.php:3537 +#: includes/extra-strings.php:386 includes/post-types-admin.php:3581 msgid "Clear Overrides" msgstr "" -#: includes/extra-strings.php:388 +#: includes/extra-strings.php:387 msgid "Load Schedule for Previous Week" msgstr "" -#: includes/extra-strings.php:389 +#: includes/extra-strings.php:388 msgid "Load Schedule for Next Week" msgstr "" -#: includes/extra-strings.php:390 options.php:1050 +#: includes/extra-strings.php:389 options.php:1118 msgid "List" msgstr "" -#: includes/extra-strings.php:391 +#: includes/extra-strings.php:390 msgid "No Hosts were found to display." msgstr "" -#: includes/extra-strings.php:392 +#: includes/extra-strings.php:391 msgid "No Producers were found to display." msgstr "" -#: includes/extra-strings.php:393 +#: includes/extra-strings.php:392 msgid "No Episodes were found to display." msgstr "" -#: includes/extra-strings.php:394 includes/shortcodes.php:2106 +#: includes/extra-strings.php:393 includes/shortcodes.php:2120 msgid "Published on " msgstr "" -#: includes/extra-strings.php:395 +#: includes/extra-strings.php:394 msgid "No %s were found to display." msgstr "" -#: includes/extra-strings.php:396 +#: includes/extra-strings.php:395 msgid "Social Icons" msgstr "" -#: includes/extra-strings.php:397 +#: includes/extra-strings.php:396 msgid "Soundcloud" msgstr "" -#: includes/extra-strings.php:398 +#: includes/extra-strings.php:397 msgid "Mixcloud" msgstr "" -#: includes/extra-strings.php:399 +#: includes/extra-strings.php:398 msgid "Spotify" msgstr "" -#: includes/extra-strings.php:400 +#: includes/extra-strings.php:399 msgid "Twitch" msgstr "" -#: includes/extra-strings.php:401 +#: includes/extra-strings.php:400 msgid "Discord" msgstr "" -#: includes/extra-strings.php:402 +#: includes/extra-strings.php:401 msgid "Facebook" msgstr "" -#: includes/extra-strings.php:403 +#: includes/extra-strings.php:402 msgid "Twitter" msgstr "" -#: includes/extra-strings.php:404 +#: includes/extra-strings.php:403 msgid "YouTube" msgstr "" -#: includes/extra-strings.php:405 +#: includes/extra-strings.php:404 msgid "LinkedIn" msgstr "" -#: includes/extra-strings.php:406 +#: includes/extra-strings.php:405 msgid "Xing" msgstr "" -#: includes/extra-strings.php:407 +#: includes/extra-strings.php:406 msgid "Instagram" msgstr "" -#: includes/extra-strings.php:408 +#: includes/extra-strings.php:407 msgid "WhatsApp" msgstr "" -#: includes/extra-strings.php:409 +#: includes/extra-strings.php:408 msgid "Pinterest" msgstr "" -#: includes/extra-strings.php:410 +#: includes/extra-strings.php:409 msgid "Custom" msgstr "" -#: includes/extra-strings.php:411 +#: includes/extra-strings.php:410 msgid "Add a Social Icon" msgstr "" -#: includes/extra-strings.php:412 +#: includes/extra-strings.php:411 msgid "Move this Icon Up" msgstr "" -#: includes/extra-strings.php:413 +#: includes/extra-strings.php:412 msgid "Move this Icon Down" msgstr "" -#: includes/extra-strings.php:414 +#: includes/extra-strings.php:413 msgid "Remove this Social Icon" msgstr "" -#: includes/extra-strings.php:415 +#: includes/extra-strings.php:414 msgid "URL" msgstr "" -#: includes/extra-strings.php:416 +#: includes/extra-strings.php:415 msgid "Icon URL" msgstr "" -#: includes/extra-strings.php:417 +#: includes/extra-strings.php:416 msgid "Unknown" msgstr "" -#: includes/extra-strings.php:418 +#: includes/extra-strings.php:417 msgid "Social URL Page Link" msgstr "" -#: includes/extra-strings.php:419 +#: includes/extra-strings.php:418 msgid "Social Icon Image URL" msgstr "" -#: includes/extra-strings.php:420 +#: includes/extra-strings.php:419 msgid "Are you sure you want to remove this Social Icon?" msgstr "" -#: includes/extra-strings.php:421 +#: includes/extra-strings.php:420 msgid "No Social Icon Services defined." msgstr "" -#: includes/extra-strings.php:422 +#: includes/extra-strings.php:421 msgid "You can reorder this icon after saving it." msgstr "" -#: includes/extra-strings.php:423 +#: includes/extra-strings.php:422 msgid "Change Timezone" msgstr "" -#: includes/extra-strings.php:424 +#: includes/extra-strings.php:423 msgid "Select Region..." msgstr "" -#: includes/extra-strings.php:425 +#: includes/extra-strings.php:424 msgid "Clear" msgstr "" -#: includes/extra-strings.php:426 +#: includes/extra-strings.php:425 msgid "Cancel" msgstr "" -#: includes/extra-strings.php:427 +#: includes/extra-strings.php:426 msgid "Select Timezone..." msgstr "" -#: includes/extra-strings.php:429 +#: includes/extra-strings.php:427 widgets/class-current-playlist-widget.php:71 +#: widgets/class-current-show-widget.php:84 +#: widgets/class-current-show-widget.php:212 +#: widgets/class-radio-clock-widget.php:89 +#: widgets/class-upcoming-shows-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:207 +msgid "Default" +msgstr "" + +#: includes/extra-strings.php:428 msgid "Automatic Changeover Reloading" msgstr "" -#: includes/extra-strings.php:430 templates/master-schedule-list.php:28 +#: includes/extra-strings.php:429 templates/master-schedule-list.php:28 #: templates/master-schedule-table.php:29 #: templates/master-schedule-table.php:600 #: templates/master-schedule-tabs.php:30 templates/master-schedule-tabs.php:496 msgid "to" msgstr "" -#: includes/extra-strings.php:431 includes/shortcodes.php:2761 -#: includes/shortcodes.php:3509 radio-station-admin.php:1646 -#: templates/master-schedule-div.php:122 +#: includes/extra-strings.php:430 includes/shortcodes.php:2791 +#: includes/shortcodes.php:3576 radio-station-admin.php:1697 +#: templates/master-schedule-div.php:124 #: templates/master-schedule-legacy.php:173 #: templates/master-schedule-list.php:320 #: templates/master-schedule-table.php:521 @@ -2249,17 +1800,7 @@ msgstr "" msgid "with" msgstr "" -#: includes/extra-strings.php:432 includes/shortcodes.php:2798 -#: includes/shortcodes.php:3546 templates/master-schedule-div.php:133 -#: templates/master-schedule-legacy.php:184 -#: templates/master-schedule-list.php:335 -#: templates/master-schedule-table.php:537 -#: templates/master-schedule-tabs.php:432 templates/single-show-content.php:353 -#: templates/single-show-content.php:392 -msgid "and" -msgstr "" - -#: includes/extra-strings.php:433 templates/master-schedule-div.php:188 +#: includes/extra-strings.php:432 templates/master-schedule-div.php:190 #: templates/master-schedule-legacy.php:229 #: templates/master-schedule-list.php:418 #: templates/master-schedule-table.php:616 @@ -2267,7 +1808,7 @@ msgstr "" msgid "encore airing" msgstr "" -#: includes/extra-strings.php:434 templates/master-schedule-div.php:198 +#: includes/extra-strings.php:433 templates/master-schedule-div.php:200 #: templates/master-schedule-legacy.php:244 #: templates/master-schedule-list.php:438 #: templates/master-schedule-table.php:635 @@ -2275,2009 +1816,2101 @@ msgstr "" msgid "Audio File" msgstr "" -#: includes/extra-strings.php:435 includes/master-schedule.php:815 -#: radio-station-admin.php:219 templates/master-schedule-list.php:459 +#: includes/extra-strings.php:434 includes/master-schedule.php:831 +#: radio-station-admin.php:224 templates/master-schedule-list.php:459 #: templates/master-schedule-tabs.php:549 msgid "Genres" msgstr "" -#: includes/extra-strings.php:436 +#: includes/extra-strings.php:435 msgid "No Shows" msgstr "" -#: includes/extra-strings.php:437 +#: includes/extra-strings.php:436 msgid "No scheduled Shows found for this date." msgstr "" -#: includes/extra-strings.php:438 templates/master-schedule-table.php:182 +#: includes/extra-strings.php:437 templates/master-schedule-table.php:182 #: templates/master-schedule-tabs.php:192 msgid "Previous Day" msgstr "" -#: includes/extra-strings.php:439 templates/master-schedule-table.php:195 +#: includes/extra-strings.php:438 templates/master-schedule-table.php:195 #: templates/master-schedule-tabs.php:208 msgid "Next Day" msgstr "" -#: includes/extra-strings.php:440 templates/master-schedule-tabs.php:231 +#: includes/extra-strings.php:439 templates/master-schedule-tabs.php:231 msgid "Viewing" msgstr "" -#: includes/extra-strings.php:441 +#: includes/extra-strings.php:440 msgid "No Shows scheduled." msgstr "" -#: includes/extra-strings.php:442 templates/single-show-content.php:141 +#: includes/extra-strings.php:441 templates/single-show-content.php:141 msgid "Show RSS Feed" msgstr "" -#: includes/extra-strings.php:443 templates/single-show-content.php:242 +#: includes/extra-strings.php:442 templates/single-show-content.php:248 msgid "Become a Supporter for" msgstr "" -#: includes/extra-strings.php:444 +#: includes/extra-strings.php:443 msgid "Episode Info" msgstr "" -#: includes/extra-strings.php:445 +#: includes/extra-strings.php:444 msgid "Broadcasted" msgstr "" -#: includes/extra-strings.php:446 +#: includes/extra-strings.php:445 msgid "on" msgstr "" -#: includes/extra-strings.php:447 templates/single-show-content.php:328 +#: includes/extra-strings.php:446 templates/single-show-content.php:352 msgid "Hosted by" msgstr "" -#: includes/extra-strings.php:448 templates/single-show-content.php:367 +#: includes/extra-strings.php:447 templates/single-show-content.php:391 msgid "Produced by" msgstr "" -#: includes/extra-strings.php:449 templates/single-show-content.php:745 +#: includes/extra-strings.php:448 templates/single-show-content.php:775 msgid "Show More" msgstr "" -#: includes/extra-strings.php:450 templates/single-show-content.php:748 +#: includes/extra-strings.php:449 templates/single-show-content.php:778 msgid "Show Less" msgstr "" -#: includes/extra-strings.php:451 +#: includes/extra-strings.php:450 msgid "Listen to this %s" msgstr "" -#: includes/extra-strings.php:452 options.php:1333 +#: includes/extra-strings.php:451 options.php:1401 msgid "Player" msgstr "" -#: includes/extra-strings.php:453 +#: includes/extra-strings.php:452 msgid "Download this Episode" msgstr "" -#: includes/extra-strings.php:454 +#: includes/extra-strings.php:453 msgid "About this %s" msgstr "" -#: includes/extra-strings.php:455 templates/single-show-content.php:773 +#: includes/extra-strings.php:454 templates/single-show-content.php:803 msgid "About" msgstr "" -#: includes/extra-strings.php:456 +#: includes/extra-strings.php:455 msgid "%s Show" msgstr "" -#: includes/extra-strings.php:457 +#: includes/extra-strings.php:456 msgid "%s Playlist" msgstr "" -#: includes/extra-strings.php:458 templates/single-show-content.php:987 +#: includes/extra-strings.php:457 templates/single-show-content.php:1027 msgid "Jump to" msgstr "" -#: includes/extra-strings.php:459 +#: includes/extra-strings.php:458 msgid "Visit %s Website" msgstr "" -#: includes/extra-strings.php:460 +#: includes/extra-strings.php:459 msgid "%s Phone Number" msgstr "" -#: includes/extra-strings.php:461 +#: includes/extra-strings.php:460 msgid "Email %s" msgstr "" -#: includes/extra-strings.php:462 +#: includes/extra-strings.php:461 msgid "%s RSS Feed" msgstr "" -#: includes/extra-strings.php:463 templates/single-show-content.php:274 +#: includes/extra-strings.php:462 templates/single-show-content.php:294 msgid "Download Latest Broadcast" msgstr "" -#: includes/extra-strings.php:464 +#: includes/extra-strings.php:463 msgid "Profile Info" msgstr "" -#: includes/extra-strings.php:465 +#: includes/extra-strings.php:464 msgid "Website" msgstr "" -#: includes/extra-strings.php:466 +#: includes/extra-strings.php:465 msgid "Email" msgstr "" -#: includes/extra-strings.php:467 +#: includes/extra-strings.php:466 msgid "Phone" msgstr "" -#: includes/extra-strings.php:468 +#: includes/extra-strings.php:467 msgid "Archive List" msgstr "" -#: includes/extra-strings.php:469 +#: includes/extra-strings.php:468 msgid "Archive list for Radio Station record types." msgstr "" -#: includes/extra-strings.php:470 loader.php:2224 options.php:1332 +#: includes/extra-strings.php:469 loader.php:2323 options.php:1400 msgid "General" msgstr "" -#: includes/extra-strings.php:471 +#: includes/extra-strings.php:470 msgid "Archive List Details" msgstr "" -#: includes/extra-strings.php:472 +#: includes/extra-strings.php:471 msgid "Archive Type" msgstr "" -#: includes/extra-strings.php:473 includes/post-types.php:153 +#: includes/extra-strings.php:472 includes/post-types.php:155 msgid "Overrides" msgstr "" -#: includes/extra-strings.php:474 includes/post-types.php:93 -#: includes/post-types.php:101 radio-station-admin.php:217 +#: includes/extra-strings.php:473 includes/post-types.php:95 +#: includes/post-types.php:103 radio-station-admin.php:222 msgid "Playlists" msgstr "" -#: includes/extra-strings.php:475 +#: includes/extra-strings.php:474 msgid "Shows by Genre" msgstr "" -#: includes/extra-strings.php:476 +#: includes/extra-strings.php:475 msgid "Shows by Language" msgstr "" -#: includes/extra-strings.php:477 +#: includes/extra-strings.php:476 msgid "Archive View" msgstr "" -#: includes/extra-strings.php:478 options.php:674 options.php:722 +#: includes/extra-strings.php:477 options.php:703 options.php:751 msgid "List View" msgstr "" -#: includes/extra-strings.php:479 +#: includes/extra-strings.php:478 msgid "Records Per Page" msgstr "" -#: includes/extra-strings.php:480 +#: includes/extra-strings.php:479 msgid "Use 0 for all records." msgstr "" -#: includes/extra-strings.php:481 +#: includes/extra-strings.php:480 msgid "Display Pagination?" msgstr "" -#: includes/extra-strings.php:482 includes/post-types-admin.php:2653 -#: includes/post-types-admin.php:2665 includes/post-types-admin.php:4952 +#: includes/extra-strings.php:481 includes/post-types-admin.php:2678 +#: includes/post-types-admin.php:2690 includes/post-types-admin.php:5003 msgid "No" msgstr "" -#: includes/extra-strings.php:483 includes/post-types-admin.php:2651 -#: includes/post-types-admin.php:2667 includes/post-types-admin.php:4954 +#: includes/extra-strings.php:482 includes/post-types-admin.php:2676 +#: includes/post-types-admin.php:2692 includes/post-types-admin.php:5005 msgid "Yes" msgstr "" -#: includes/extra-strings.php:484 +#: includes/extra-strings.php:483 msgid "Hide if Empty?" msgstr "" -#: includes/extra-strings.php:485 +#: includes/extra-strings.php:484 msgid "Archive Query" msgstr "" -#: includes/extra-strings.php:486 +#: includes/extra-strings.php:485 msgid "Order By" msgstr "" -#: includes/extra-strings.php:488 +#: includes/extra-strings.php:486 includes/post-types-admin.php:3113 +msgid "Title" +msgstr "" + +#: includes/extra-strings.php:487 msgid "Published Date" msgstr "" -#: includes/extra-strings.php:489 +#: includes/extra-strings.php:488 msgid "Modified Date" msgstr "" -#: includes/extra-strings.php:490 +#: includes/extra-strings.php:489 msgid "Order" msgstr "" -#: includes/extra-strings.php:491 +#: includes/extra-strings.php:490 msgid "Ascending" msgstr "" -#: includes/extra-strings.php:492 +#: includes/extra-strings.php:491 msgid "Descending" msgstr "" -#: includes/extra-strings.php:493 +#: includes/extra-strings.php:492 msgid "Archive Record Display" msgstr "" -#: includes/extra-strings.php:494 +#: includes/extra-strings.php:493 msgid "Time Display Format" msgstr "" -#: includes/extra-strings.php:497 +#: includes/extra-strings.php:494 widgets/class-current-show-widget.php:213 +#: widgets/class-radio-clock-widget.php:90 +#: widgets/class-upcoming-shows-widget.php:208 +msgid "12 Hour" +msgstr "" + +#: includes/extra-strings.php:495 widgets/class-current-show-widget.php:214 +#: widgets/class-radio-clock-widget.php:91 +#: widgets/class-upcoming-shows-widget.php:209 +msgid "24 Hour" +msgstr "" + +#: includes/extra-strings.php:496 msgid "Description Display Format" msgstr "" -#: includes/extra-strings.php:498 +#: includes/extra-strings.php:497 msgid "View Default" msgstr "" -#: includes/extra-strings.php:500 includes/post-types-admin.php:3103 +#: includes/extra-strings.php:498 includes/post-types-admin.php:5013 +#: options.php:719 widgets/class-radio-clock-widget.php:53 +#: widgets/class-radio-clock-widget.php:72 +msgid "None" +msgstr "" + +#: includes/extra-strings.php:499 includes/post-types-admin.php:3147 msgid "Excerpt" msgstr "" -#: includes/extra-strings.php:502 +#: includes/extra-strings.php:500 widgets/class-radio-clock-widget.php:51 +#: widgets/class-radio-clock-widget.php:70 +msgid "Full" +msgstr "" + +#: includes/extra-strings.php:501 msgid "Display Image?" msgstr "" -#: includes/extra-strings.php:503 +#: includes/extra-strings.php:502 msgid "This setting is for Shows and Overrides." msgstr "" -#: includes/extra-strings.php:504 +#: includes/extra-strings.php:503 msgid "Only Shows with Shifts?" msgstr "" -#: includes/extra-strings.php:505 +#: includes/extra-strings.php:504 msgid "This setting is for Shows only." msgstr "" -#: includes/extra-strings.php:506 +#: includes/extra-strings.php:505 msgid "Display Override Dates?" msgstr "" -#: includes/extra-strings.php:507 +#: includes/extra-strings.php:506 msgid "This setting is for Overrides only." msgstr "" -#: includes/extra-strings.php:508 +#: includes/extra-strings.php:507 msgid "Style" msgstr "" -#: includes/extra-strings.php:509 +#: includes/extra-strings.php:508 msgid "Container Styles" msgstr "" -#: includes/extra-strings.php:510 +#: includes/extra-strings.php:509 msgid "Alignment" msgstr "" -#: includes/extra-strings.php:511 +#: includes/extra-strings.php:510 msgid "Typography" msgstr "" -#: includes/extra-strings.php:512 +#: includes/extra-strings.php:511 msgid "Title Text Color" msgstr "" -#: includes/extra-strings.php:513 +#: includes/extra-strings.php:512 msgid "Title Typography" msgstr "" -#: includes/extra-strings.php:514 +#: includes/extra-strings.php:513 msgid "Description Text Color" msgstr "" -#: includes/extra-strings.php:515 +#: includes/extra-strings.php:514 msgid "Description Typography" msgstr "" -#: includes/extra-strings.php:516 +#: includes/extra-strings.php:515 msgid "Meta Text Color" msgstr "" -#: includes/extra-strings.php:517 +#: includes/extra-strings.php:516 msgid "Meta Typography" msgstr "" -#: includes/extra-strings.php:519 +#: includes/extra-strings.php:517 widgets/class-radio-clock-widget.php:25 +msgid "Radio Clock" +msgstr "" + +#: includes/extra-strings.php:518 msgid "Radio Station Clock time display." msgstr "" -#: includes/extra-strings.php:520 +#: includes/extra-strings.php:519 msgid "Radio Clock Display Options" msgstr "" -#: includes/extra-strings.php:521 widgets/class-radio-clock-widget.php:47 +#: includes/extra-strings.php:520 widgets/class-radio-clock-widget.php:49 msgid "Day Display Format" msgstr "" -#: includes/extra-strings.php:523 +#: includes/extra-strings.php:521 widgets/class-radio-clock-widget.php:52 +#: widgets/class-radio-clock-widget.php:71 +msgid "Short" +msgstr "" + +#: includes/extra-strings.php:522 msgid "Display Date?" msgstr "" -#: includes/extra-strings.php:524 widgets/class-radio-clock-widget.php:66 +#: includes/extra-strings.php:523 widgets/class-radio-clock-widget.php:68 msgid "Month Display Format" msgstr "" -#: includes/extra-strings.php:525 +#: includes/extra-strings.php:524 msgid "Display Timezone?" msgstr "" -#: includes/extra-strings.php:526 +#: includes/extra-strings.php:525 msgid "Display Seconds?" msgstr "" -#: includes/extra-strings.php:527 +#: includes/extra-strings.php:526 msgid "Label Text Color" msgstr "" -#: includes/extra-strings.php:528 +#: includes/extra-strings.php:527 msgid "Label Typography" msgstr "" -#: includes/extra-strings.php:529 +#: includes/extra-strings.php:528 msgid "Times Text Color" msgstr "" -#: includes/extra-strings.php:530 +#: includes/extra-strings.php:529 msgid "Times Typography" msgstr "" -#: includes/extra-strings.php:531 +#: includes/extra-strings.php:530 msgid "Timezone Switcher Text Color" msgstr "" -#: includes/extra-strings.php:532 +#: includes/extra-strings.php:531 msgid "Timezone Switcher Typography" msgstr "" -#: includes/extra-strings.php:533 +#: includes/extra-strings.php:532 msgid "Current Playlist" msgstr "" -#: includes/extra-strings.php:534 +#: includes/extra-strings.php:533 msgid "Current Playlist module for Radio Station." msgstr "" -#: includes/extra-strings.php:535 +#: includes/extra-strings.php:534 msgid "Loading Options" msgstr "" -#: includes/extra-strings.php:536 +#: includes/extra-strings.php:535 msgid "AJAX Loading" msgstr "" -#: includes/extra-strings.php:537 +#: includes/extra-strings.php:536 msgid "To bypass page caching." msgstr "" -#: includes/extra-strings.php:538 +#: includes/extra-strings.php:537 msgid "Dynamic Reloading" msgstr "" -#: includes/extra-strings.php:539 +#: includes/extra-strings.php:538 msgid "Reload at Show changeovers." msgstr "" -#: includes/extra-strings.php:540 +#: includes/extra-strings.php:539 msgid "No Current Playlist Text" msgstr "" -#: includes/extra-strings.php:541 +#: includes/extra-strings.php:540 msgid "Blank for default. 0 for none." msgstr "" -#: includes/extra-strings.php:542 +#: includes/extra-strings.php:541 msgid "Extra Display Options" msgstr "" -#: includes/extra-strings.php:543 +#: includes/extra-strings.php:542 msgid "Display Playlist Title" msgstr "" -#: includes/extra-strings.php:544 +#: includes/extra-strings.php:543 msgid "Link to Playlist Page" msgstr "" -#: includes/extra-strings.php:545 +#: includes/extra-strings.php:544 msgid "Remaining Time Countdown" msgstr "" -#: includes/extra-strings.php:546 +#: includes/extra-strings.php:545 msgid "Show Time Display Options" msgstr "" -#: includes/extra-strings.php:547 +#: includes/extra-strings.php:546 msgid "Display Song Title" msgstr "" -#: includes/extra-strings.php:548 +#: includes/extra-strings.php:547 msgid "Display Artist" msgstr "" -#: includes/extra-strings.php:549 +#: includes/extra-strings.php:548 msgid "Display Album" msgstr "" -#: includes/extra-strings.php:550 +#: includes/extra-strings.php:549 msgid "Display Record Label" msgstr "" -#: includes/extra-strings.php:551 +#: includes/extra-strings.php:550 msgid "Display Track Comments" msgstr "" -#: includes/extra-strings.php:552 +#: includes/extra-strings.php:551 msgid "Track Info Text Color" msgstr "" -#: includes/extra-strings.php:553 +#: includes/extra-strings.php:552 msgid "Track Info Typography" msgstr "" -#: includes/extra-strings.php:554 +#: includes/extra-strings.php:553 msgid "Playlist Title Text Color" msgstr "" -#: includes/extra-strings.php:555 +#: includes/extra-strings.php:554 msgid "Playlist Title Typography" msgstr "" -#: includes/extra-strings.php:556 +#: includes/extra-strings.php:555 msgid "Countdown Text Color" msgstr "" -#: includes/extra-strings.php:557 +#: includes/extra-strings.php:556 msgid "Countdown Typography" msgstr "" -#: includes/extra-strings.php:558 +#: includes/extra-strings.php:557 msgid "Current Show" msgstr "" -#: includes/extra-strings.php:559 +#: includes/extra-strings.php:558 msgid "Current Show module for Radio Station." msgstr "" -#: includes/extra-strings.php:560 widgets/class-current-show-widget.php:107 +#: includes/extra-strings.php:559 widgets/class-current-show-widget.php:102 msgid "No Current Show Text" msgstr "" -#: includes/extra-strings.php:561 +#: includes/extra-strings.php:560 msgid "Show Display Options" msgstr "" -#: includes/extra-strings.php:562 +#: includes/extra-strings.php:561 msgid "Link to Show Page" msgstr "" -#: includes/extra-strings.php:563 +#: includes/extra-strings.php:562 msgid "Title Position" msgstr "" -#: includes/extra-strings.php:564 +#: includes/extra-strings.php:563 msgid "Above Image" msgstr "" -#: includes/extra-strings.php:565 +#: includes/extra-strings.php:564 msgid "Left of Image" msgstr "" -#: includes/extra-strings.php:566 +#: includes/extra-strings.php:565 msgid "Right of Image" msgstr "" -#: includes/extra-strings.php:567 +#: includes/extra-strings.php:566 msgid "Below Image" msgstr "" -#: includes/extra-strings.php:568 +#: includes/extra-strings.php:567 msgid "Display Show Image" msgstr "" -#: includes/extra-strings.php:569 +#: includes/extra-strings.php:568 msgid "Image Size" msgstr "" -#: includes/extra-strings.php:570 +#: includes/extra-strings.php:569 msgid "Image Width Override" msgstr "" -#: includes/extra-strings.php:571 +#: includes/extra-strings.php:570 msgid "Display Show Time" msgstr "" -#: includes/extra-strings.php:572 +#: includes/extra-strings.php:571 msgid "Display All Show Times" msgstr "" -#: includes/extra-strings.php:573 +#: includes/extra-strings.php:572 msgid "Display Show Hosts" msgstr "" -#: includes/extra-strings.php:574 +#: includes/extra-strings.php:573 msgid "Show Description Excerpt" msgstr "" -#: includes/extra-strings.php:575 +#: includes/extra-strings.php:574 msgid "Display Show Playlist" msgstr "" -#: includes/extra-strings.php:576 +#: includes/extra-strings.php:575 msgid "Display if Encore Airing" msgstr "" -#: includes/extra-strings.php:577 +#: includes/extra-strings.php:576 msgid "Show Title Color" msgstr "" -#: includes/extra-strings.php:578 +#: includes/extra-strings.php:577 msgid "Show Title Typography" msgstr "" -#: includes/extra-strings.php:579 +#: includes/extra-strings.php:578 msgid "Show Description Text Color" msgstr "" -#: includes/extra-strings.php:580 +#: includes/extra-strings.php:579 msgid "Show Description Typography" msgstr "" -#: includes/extra-strings.php:581 +#: includes/extra-strings.php:580 msgid "Radio Player" msgstr "" -#: includes/extra-strings.php:582 +#: includes/extra-strings.php:581 msgid "Radio Station Audio stream player module." msgstr "" -#: includes/extra-strings.php:583 widgets/class-radio-player-widget.php:62 +#: includes/extra-strings.php:582 widgets/class-radio-player-widget.php:64 msgid "Player Content" msgstr "" -#: includes/extra-strings.php:584 +#: includes/extra-strings.php:583 msgid "Stream URL" msgstr "" -#: includes/extra-strings.php:585 +#: includes/extra-strings.php:584 msgid "Leave blank to use default stream." msgstr "" -#: includes/extra-strings.php:586 +#: includes/extra-strings.php:585 msgid "Player Title Text" msgstr "" -#: includes/extra-strings.php:587 widgets/class-current-playlist-widget.php:116 -#: widgets/class-radio-player-widget.php:87 -#: widgets/class-upcoming-shows-widget.php:113 +#: includes/extra-strings.php:586 widgets/class-current-playlist-widget.php:118 +#: widgets/class-radio-player-widget.php:89 +#: widgets/class-upcoming-shows-widget.php:108 msgid "Empty for default, 0 for none." msgstr "" -#: includes/extra-strings.php:588 +#: includes/extra-strings.php:587 msgid "Player Station Image" msgstr "" -#: includes/extra-strings.php:590 +#: includes/extra-strings.php:588 options.php:281 +#: widgets/class-radio-player-widget.php:97 +#: widgets/class-radio-player-widget.php:101 +msgid "Display Station Image" +msgstr "" + +#: includes/extra-strings.php:589 msgid "Do Not Display Station Image" msgstr "" -#: includes/extra-strings.php:591 widgets/class-radio-player-widget.php:111 +#: includes/extra-strings.php:590 widgets/class-radio-player-widget.php:113 msgid "Player Options" msgstr "" -#: includes/extra-strings.php:592 options.php:280 +#: includes/extra-strings.php:591 options.php:294 msgid "Player Script" msgstr "" -#: includes/extra-strings.php:596 +#: includes/extra-strings.php:592 options.php:299 options.php:317 +#: widgets/class-radio-player-widget.php:122 +msgid "Amplitude" +msgstr "" + +#: includes/extra-strings.php:593 widgets/class-radio-player-widget.php:123 +msgid "Howler" +msgstr "" + +#: includes/extra-strings.php:594 options.php:297 options.php:315 +#: widgets/class-radio-player-widget.php:124 +msgid "jPlayer" +msgstr "" + +#: includes/extra-strings.php:595 msgid "Initial Volume" msgstr "" -#: includes/extra-strings.php:597 options.php:343 -#: widgets/class-radio-player-widget.php:144 +#: includes/extra-strings.php:596 options.php:358 +#: widgets/class-radio-player-widget.php:146 msgid "Volume Controls" msgstr "" -#: includes/extra-strings.php:598 options.php:346 player/radio-player.php:376 +#: includes/extra-strings.php:597 options.php:361 player/radio-player.php:420 msgid "Volume Slider" msgstr "" -#: includes/extra-strings.php:599 +#: includes/extra-strings.php:598 msgid "Up and Down Buttons" msgstr "" -#: includes/extra-strings.php:600 +#: includes/extra-strings.php:599 msgid "Mute Button" msgstr "" -#: includes/extra-strings.php:601 +#: includes/extra-strings.php:600 msgid "Maximize Button" msgstr "" -#: includes/extra-strings.php:602 +#: includes/extra-strings.php:601 msgid "Use as Default Player" msgstr "" -#: includes/extra-strings.php:603 +#: includes/extra-strings.php:602 msgid "Make this the default player on this page." msgstr "" -#: includes/extra-strings.php:604 +#: includes/extra-strings.php:603 msgid "Metadata Source URL" msgstr "" -#: includes/extra-strings.php:605 +#: includes/extra-strings.php:604 msgid "Defaults to Stream URL." msgstr "" -#: includes/extra-strings.php:606 -msgid "Player Design" +#: includes/extra-strings.php:605 +msgid "Player Design" +msgstr "" + +#: includes/extra-strings.php:606 +msgid "Player Layout" +msgstr "" + +#: includes/extra-strings.php:607 +msgid "Vertical (Stacked" +msgstr "" + +#: includes/extra-strings.php:608 +msgid "Horizontal (Inline" +msgstr "" + +#: includes/extra-strings.php:609 +msgid "Player Theme" +msgstr "" + +#: includes/extra-strings.php:610 options.php:330 +#: widgets/class-radio-player-widget.php:195 +msgid "Light" msgstr "" -#: includes/extra-strings.php:607 -msgid "Player Layout" +#: includes/extra-strings.php:611 options.php:331 +#: widgets/class-radio-player-widget.php:196 +msgid "Dark" msgstr "" -#: includes/extra-strings.php:608 -msgid "Vertical (Stacked" +#: includes/extra-strings.php:612 +msgid "Player Buttons" msgstr "" -#: includes/extra-strings.php:609 -msgid "Horizontal (Inline" +#: includes/extra-strings.php:613 widgets/class-radio-player-widget.php:215 +msgid "Circular" msgstr "" -#: includes/extra-strings.php:610 -msgid "Player Theme" +#: includes/extra-strings.php:614 widgets/class-radio-player-widget.php:216 +msgid "Rounded" msgstr "" -#: includes/extra-strings.php:613 -msgid "Player Buttons" +#: includes/extra-strings.php:615 widgets/class-radio-player-widget.php:217 +msgid "Square" msgstr "" -#: includes/extra-strings.php:617 options.php:1353 +#: includes/extra-strings.php:616 options.php:1421 msgid "Player Colors" msgstr "" -#: includes/extra-strings.php:618 +#: includes/extra-strings.php:617 msgid "Playing Highlight Color" msgstr "" -#: includes/extra-strings.php:619 +#: includes/extra-strings.php:618 msgid "Buttons Highlight Color" msgstr "" -#: includes/extra-strings.php:620 +#: includes/extra-strings.php:619 msgid "Slider Track Color" msgstr "" -#: includes/extra-strings.php:621 +#: includes/extra-strings.php:620 msgid "Slider Thumb Color" msgstr "" -#: includes/extra-strings.php:622 +#: includes/extra-strings.php:621 msgid "Player Typography" msgstr "" -#: includes/extra-strings.php:623 +#: includes/extra-strings.php:622 msgid "Station Text Color" msgstr "" -#: includes/extra-strings.php:624 +#: includes/extra-strings.php:623 msgid "Station Typography" msgstr "" -#: includes/extra-strings.php:625 +#: includes/extra-strings.php:624 msgid "Show Text Color" msgstr "" -#: includes/extra-strings.php:626 +#: includes/extra-strings.php:625 msgid "Show Typography" msgstr "" -#: includes/extra-strings.php:627 +#: includes/extra-strings.php:626 msgid "Song Track Text Color" msgstr "" -#: includes/extra-strings.php:628 +#: includes/extra-strings.php:627 msgid "Song Track Typography" msgstr "" -#: includes/extra-strings.php:629 +#: includes/extra-strings.php:628 msgid "Master Schedule" msgstr "" -#: includes/extra-strings.php:630 +#: includes/extra-strings.php:629 msgid "Master Schedule module for Radio Station." msgstr "" -#: includes/extra-strings.php:631 +#: includes/extra-strings.php:630 msgid "Schedule Display Options" msgstr "" -#: includes/extra-strings.php:632 +#: includes/extra-strings.php:631 msgid "Schedule View" msgstr "" -#: includes/extra-strings.php:633 +#: includes/extra-strings.php:632 msgid "Ctrl-click to select multiple views." msgstr "" -#: includes/extra-strings.php:634 options.php:673 options.php:720 +#: includes/extra-strings.php:633 options.php:702 options.php:749 msgid "Table View" msgstr "" -#: includes/extra-strings.php:635 options.php:676 options.php:721 +#: includes/extra-strings.php:634 options.php:705 options.php:750 msgid "Tabbed View" msgstr "" -#: includes/extra-strings.php:636 +#: includes/extra-strings.php:635 msgid "Default View" msgstr "" -#: includes/extra-strings.php:637 +#: includes/extra-strings.php:636 msgid "Image Position" msgstr "" -#: includes/extra-strings.php:638 +#: includes/extra-strings.php:637 msgid "[Tabbed View] Show avatar relative to Show text." msgstr "" -#: includes/extra-strings.php:639 +#: includes/extra-strings.php:638 msgid "left" msgstr "" -#: includes/extra-strings.php:641 +#: includes/extra-strings.php:639 widgets/class-current-show-widget.php:133 +#: widgets/class-upcoming-shows-widget.php:136 +msgid "Right" +msgstr "" + +#: includes/extra-strings.php:640 msgid "Hide Past Shows" msgstr "" -#: includes/extra-strings.php:642 +#: includes/extra-strings.php:641 msgid "[Tabbed View] Display only current and upcoming shows." msgstr "" -#: includes/extra-strings.php:643 +#: includes/extra-strings.php:642 msgid "Grid Width" msgstr "" -#: includes/extra-strings.php:644 +#: includes/extra-strings.php:643 msgid "[Grid View] Column width in pixels." msgstr "" -#: includes/extra-strings.php:645 +#: includes/extra-strings.php:644 msgid "[Grid View] Line up Shows by times." msgstr "" -#: includes/extra-strings.php:646 +#: includes/extra-strings.php:645 msgid "Calendar Weeks" msgstr "" -#: includes/extra-strings.php:647 +#: includes/extra-strings.php:646 msgid "[Calendar View] Number of future weeks." msgstr "" -#: includes/extra-strings.php:648 +#: includes/extra-strings.php:647 msgid "Previous Weeks" msgstr "" -#: includes/extra-strings.php:649 +#: includes/extra-strings.php:648 msgid "[Calendar View] Number of past weeks." msgstr "" -#: includes/extra-strings.php:650 +#: includes/extra-strings.php:649 msgid "Header Display Options" msgstr "" -#: includes/extra-strings.php:651 +#: includes/extra-strings.php:650 msgid "Radio Time Header" msgstr "" -#: includes/extra-strings.php:652 +#: includes/extra-strings.php:651 msgid "Display Radio Clock" msgstr "" -#: includes/extra-strings.php:653 +#: includes/extra-strings.php:652 msgid "Display Radio Timezone" msgstr "" -#: includes/extra-strings.php:654 +#: includes/extra-strings.php:653 msgid "No Time Header" msgstr "" -#: includes/extra-strings.php:655 +#: includes/extra-strings.php:654 msgid "Display Genre Highlighter" msgstr "" -#: includes/extra-strings.php:656 +#: includes/extra-strings.php:655 msgid "Time Display Options" msgstr "" -#: includes/extra-strings.php:657 +#: includes/extra-strings.php:656 msgid "Schedule Start Day" msgstr "" -#: includes/extra-strings.php:658 +#: includes/extra-strings.php:657 msgid "WP Start of Week" msgstr "" -#: includes/extra-strings.php:659 +#: includes/extra-strings.php:658 msgid "Today" msgstr "" -#: includes/extra-strings.php:660 +#: includes/extra-strings.php:659 msgid "Monday" msgstr "" -#: includes/extra-strings.php:661 +#: includes/extra-strings.php:660 msgid "Tuesday" msgstr "" -#: includes/extra-strings.php:662 +#: includes/extra-strings.php:661 msgid "Wednesday" msgstr "" -#: includes/extra-strings.php:663 +#: includes/extra-strings.php:662 msgid "Thursday" msgstr "" -#: includes/extra-strings.php:664 +#: includes/extra-strings.php:663 msgid "Friday" msgstr "" -#: includes/extra-strings.php:665 +#: includes/extra-strings.php:664 msgid "Saturday" msgstr "" -#: includes/extra-strings.php:666 +#: includes/extra-strings.php:665 msgid "Sunday" msgstr "" -#: includes/extra-strings.php:667 +#: includes/extra-strings.php:666 msgid "Link to Shows" msgstr "" -#: includes/extra-strings.php:668 +#: includes/extra-strings.php:667 msgid "Display Show Description" msgstr "" -#: includes/extra-strings.php:669 +#: includes/extra-strings.php:668 msgid "Link to Hosts" msgstr "" -#: includes/extra-strings.php:670 +#: includes/extra-strings.php:669 msgid "Display Show Genres" msgstr "" -#: includes/extra-strings.php:671 +#: includes/extra-strings.php:670 msgid "Display Encore" msgstr "" -#: includes/extra-strings.php:672 +#: includes/extra-strings.php:671 msgid "Headers Text Color" msgstr "" -#: includes/extra-strings.php:673 +#: includes/extra-strings.php:672 msgid "Headers Typography" msgstr "" -#: includes/extra-strings.php:674 +#: includes/extra-strings.php:673 msgid "Subheaders Text Color" msgstr "" -#: includes/extra-strings.php:675 +#: includes/extra-strings.php:674 msgid "Subheaders Typography" msgstr "" -#: includes/extra-strings.php:676 +#: includes/extra-strings.php:675 msgid "Show Title Text Color" msgstr "" -#: includes/extra-strings.php:677 +#: includes/extra-strings.php:676 msgid "Show Times Text Color" msgstr "" -#: includes/extra-strings.php:678 +#: includes/extra-strings.php:677 msgid "Show Times Typography" msgstr "" -#: includes/extra-strings.php:679 +#: includes/extra-strings.php:678 msgid "Schedule Colors" msgstr "" -#: includes/extra-strings.php:680 +#: includes/extra-strings.php:679 msgid "Odd Headers Background Color" msgstr "" -#: includes/extra-strings.php:681 +#: includes/extra-strings.php:680 msgid "Even Headers Background Color" msgstr "" -#: includes/extra-strings.php:682 +#: includes/extra-strings.php:681 msgid "Active Header Background Color" msgstr "" -#: includes/extra-strings.php:683 +#: includes/extra-strings.php:682 msgid "Current Show Border Color" msgstr "" -#: includes/extra-strings.php:684 +#: includes/extra-strings.php:683 msgid "Upcoming Shows" msgstr "" -#: includes/extra-strings.php:685 +#: includes/extra-strings.php:684 msgid "Upcoming Shows module for Radio Station." msgstr "" -#: includes/extra-strings.php:686 +#: includes/extra-strings.php:685 msgid "Upcoming Shows to Display" msgstr "" -#: includes/extra-strings.php:687 widgets/class-upcoming-shows-widget.php:110 +#: includes/extra-strings.php:686 widgets/class-upcoming-shows-widget.php:105 msgid "No Upcoming Shows Text" msgstr "" -#: includes/extra-strings.php:688 +#: includes/extra-strings.php:687 msgid "Radio Archive" msgstr "" -#: includes/extra-strings.php:689 +#: includes/extra-strings.php:688 msgid "Archive Record Query" msgstr "" -#: includes/extra-strings.php:690 +#: includes/extra-strings.php:689 msgid "Publish Date" msgstr "" -#: includes/extra-strings.php:691 +#: includes/extra-strings.php:690 msgid "Clock Styling" msgstr "" -#: includes/extra-strings.php:692 +#: includes/extra-strings.php:691 msgid "Archive Alignment" msgstr "" -#: includes/extra-strings.php:694 +#: includes/extra-strings.php:692 widgets/class-current-show-widget.php:132 +#: widgets/class-upcoming-shows-widget.php:135 +msgid "Left" +msgstr "" + +#: includes/extra-strings.php:693 msgid "Center" msgstr "" -#: includes/extra-strings.php:695 +#: includes/extra-strings.php:694 msgid "Record Title Typography" msgstr "" -#: includes/extra-strings.php:696 +#: includes/extra-strings.php:695 msgid "Record Description Typography" msgstr "" -#: includes/extra-strings.php:697 +#: includes/extra-strings.php:696 msgid "Record Meta Typography" msgstr "" -#: includes/extra-strings.php:698 +#: includes/extra-strings.php:697 msgid "Clock Alignment" msgstr "" -#: includes/extra-strings.php:699 +#: includes/extra-strings.php:698 msgid "Radio Current Playlist" msgstr "" -#: includes/extra-strings.php:700 +#: includes/extra-strings.php:699 msgid "Track Display Options" msgstr "" -#: includes/extra-strings.php:701 +#: includes/extra-strings.php:700 msgid "Playlist Styling" msgstr "" -#: includes/extra-strings.php:702 +#: includes/extra-strings.php:701 msgid "Text Alignment" msgstr "" -#: includes/extra-strings.php:703 +#: includes/extra-strings.php:702 msgid "Playlist Label Typography" msgstr "" -#: includes/extra-strings.php:704 +#: includes/extra-strings.php:703 msgid "Playlist Info Typography" msgstr "" -#: includes/extra-strings.php:705 +#: includes/extra-strings.php:704 msgid "Radio Current Show" msgstr "" -#: includes/extra-strings.php:706 +#: includes/extra-strings.php:705 msgid "Current Show Styling" msgstr "" -#: includes/extra-strings.php:707 +#: includes/extra-strings.php:706 msgid "Player Alignment" msgstr "" -#: includes/extra-strings.php:708 +#: includes/extra-strings.php:707 msgid "Station Text Typography" msgstr "" -#: includes/extra-strings.php:709 +#: includes/extra-strings.php:708 msgid "Show Text Typography" msgstr "" -#: includes/extra-strings.php:710 +#: includes/extra-strings.php:709 msgid "Radio Schedule" msgstr "" -#: includes/extra-strings.php:711 +#: includes/extra-strings.php:710 msgid "Display Show Images" msgstr "" -#: includes/extra-strings.php:712 +#: includes/extra-strings.php:711 msgid "Schedule Typography" msgstr "" -#: includes/extra-strings.php:713 +#: includes/extra-strings.php:712 msgid "Radio Upcoming Shows" msgstr "" -#: includes/extra-strings.php:714 +#: includes/extra-strings.php:713 msgid "Upcoming Shows Styling" msgstr "" -#: includes/legacy.php:671 +#: includes/legacy.php:654 msgid "More Show Blog Posts" msgstr "" #. translators: 1: PHP function name, 2: Version number, 3: Alternative #. function name. -#: includes/legacy.php:708 +#: includes/legacy.php:689 msgid "%1$s is deprecated since Radio Station version %2$s! Use %3$s or check documentation for updated functionality." msgstr "" -#: includes/master-schedule.php:825 +#: includes/master-schedule.php:841 msgid "Click to toggle Highlight of Shows with this Genre." msgstr "" -#: includes/post-types-admin.php:172 +#: includes/post-types-admin.php:174 msgid "Show Language" msgstr "" -#: includes/post-types-admin.php:209 +#: includes/post-types-admin.php:211 msgid "Main Radio Language" msgstr "" -#: includes/post-types-admin.php:213 +#: includes/post-types-admin.php:215 msgid "Select below if Show language(s) differ." msgstr "" -#: includes/post-types-admin.php:239 +#: includes/post-types-admin.php:241 msgid "Remove Language" msgstr "" -#: includes/post-types-admin.php:248 +#: includes/post-types-admin.php:250 msgid "Select Language" msgstr "" -#: includes/post-types-admin.php:264 +#: includes/post-types-admin.php:266 msgid "Click on a Language to Add it." msgstr "" -#: includes/post-types-admin.php:415 +#: includes/post-types-admin.php:420 msgid "Show Information" msgstr "" -#: includes/post-types-admin.php:461 +#: includes/post-types-admin.php:466 msgid "Active" msgstr "" -#: includes/post-types-admin.php:467 +#: includes/post-types-admin.php:472 msgid "Check this box if show is currently active (Show will not appear on schedule if unchecked.)" msgstr "" -#: includes/post-types-admin.php:485 includes/post-types-admin.php:3230 +#: includes/post-types-admin.php:490 includes/post-types-admin.php:3274 msgid "Show Email" msgstr "" -#: includes/post-types-admin.php:496 includes/post-types-admin.php:3252 +#: includes/post-types-admin.php:501 includes/post-types-admin.php:3296 msgid "Show Phone" msgstr "" -#: includes/post-types-admin.php:506 +#: includes/post-types-admin.php:511 msgid "Latest Audio File" msgstr "" -#: includes/post-types-admin.php:517 includes/post-types-admin.php:3296 +#: includes/post-types-admin.php:523 includes/post-types-admin.php:3340 msgid "Disable Download" msgstr "" -#: includes/post-types-admin.php:546 +#: includes/post-types-admin.php:552 msgid "Show DJ(s) / Host(s)" msgstr "" -#: includes/post-types-admin.php:550 includes/post-types-admin.php:710 +#: includes/post-types-admin.php:556 includes/post-types-admin.php:717 msgid "Show Producer(s)" msgstr "" -#: includes/post-types-admin.php:554 +#: includes/post-types-admin.php:560 msgid "Show Language(s)" msgstr "" -#: includes/post-types-admin.php:696 includes/post-types-admin.php:775 +#: includes/post-types-admin.php:703 includes/post-types-admin.php:782 msgid "Ctrl-Click selects multiple." msgstr "" -#: includes/post-types-admin.php:792 +#: includes/post-types-admin.php:799 msgid "Show Schedule" msgstr "" -#: includes/post-types-admin.php:838 +#: includes/post-types-admin.php:845 msgid "This Show is inactive!" msgstr "" -#: includes/post-types-admin.php:839 +#: includes/post-types-admin.php:846 msgid "All Shifts are inactive also until Show is activated." msgstr "" -#: includes/post-types-admin.php:886 +#: includes/post-types-admin.php:893 msgid "Saving Show Shifts..." msgstr "" -#: includes/post-types-admin.php:887 +#: includes/post-types-admin.php:894 msgid "Show Shifts Saved." msgstr "" -#: includes/post-types-admin.php:980 +#: includes/post-types-admin.php:989 msgid "Are you sure you want to remove this shift?" msgstr "" -#: includes/post-types-admin.php:981 +#: includes/post-types-admin.php:990 msgid "Are you sure you want to clear the shift list?" msgstr "" -#: includes/post-types-admin.php:1149 includes/post-types-admin.php:1487 -#: radio-station.php:1013 +#: includes/post-types-admin.php:1158 includes/post-types-admin.php:1499 +#: radio-station.php:1032 msgid "Day" msgstr "" -#: includes/post-types-admin.php:1243 includes/post-types-admin.php:1609 +#: includes/post-types-admin.php:1252 includes/post-types-admin.php:1621 msgid "Duplicate Shift" msgstr "" -#: includes/post-types-admin.php:1248 includes/post-types-admin.php:1616 +#: includes/post-types-admin.php:1257 includes/post-types-admin.php:1628 msgid "Remove Shift" msgstr "" -#: includes/post-types-admin.php:1268 +#: includes/post-types-admin.php:1277 msgid "Warning! Show Shift Conflicts were detected!" msgstr "" -#: includes/post-types-admin.php:1269 +#: includes/post-types-admin.php:1278 msgid "Please note that Shifts with conflicts are automatically disabled upon saving." msgstr "" -#: includes/post-types-admin.php:1270 +#: includes/post-types-admin.php:1279 msgid "Fix the Shift and/or the Shift on the conflicting Show and Update them both." msgstr "" -#: includes/post-types-admin.php:1271 +#: includes/post-types-admin.php:1280 msgid "Then you can uncheck the shift Disable box and Save again to re-enable the Shift." msgstr "" -#: includes/post-types-admin.php:1626 +#: includes/post-types-admin.php:1640 msgid "Shift Conflicts" msgstr "" -#: includes/post-types-admin.php:1632 +#: includes/post-types-admin.php:1646 msgid "This Show" msgstr "" -#: includes/post-types-admin.php:1680 +#: includes/post-types-admin.php:1675 +msgid "Note: Show shift conflict checker is currently disabled in your plugin settings." +msgstr "" + +#: includes/post-types-admin.php:1701 msgid "Show Description" msgstr "" -#: includes/post-types-admin.php:1696 +#: includes/post-types-admin.php:1717 msgid "The text field below is for your Show Description. It will display in the About section of your Show page." msgstr "" -#: includes/post-types-admin.php:1697 +#: includes/post-types-admin.php:1718 msgid "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see." msgstr "" -#: includes/post-types-admin.php:1698 +#: includes/post-types-admin.php:1719 msgid "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules." msgstr "" -#: includes/post-types-admin.php:1701 +#: includes/post-types-admin.php:1722 msgid "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page." msgstr "" -#: includes/post-types-admin.php:1702 +#: includes/post-types-admin.php:1723 msgid "This way you can then assign them to a relevant Post Category for display on your site also." msgstr "" -#: includes/post-types-admin.php:1708 +#: includes/post-types-admin.php:1729 msgid "Radio Station Pro includes an Episodes post type for adding past episodes." msgstr "" -#: includes/post-types-admin.php:1709 +#: includes/post-types-admin.php:1730 msgid "These are then automatically listed on your Show page in an Episodes tab." msgstr "" -#: includes/post-types-admin.php:1710 +#: includes/post-types-admin.php:1731 msgid "Go Pro!" msgstr "" -#: includes/post-types-admin.php:1711 +#: includes/post-types-admin.php:1732 msgid "Find out more..." msgstr "" -#: includes/post-types-admin.php:1730 +#: includes/post-types-admin.php:1751 msgid "Show Logo" msgstr "" -#: includes/post-types-admin.php:1746 +#: includes/post-types-admin.php:1767 msgid "Show Images" msgstr "" -#: includes/post-types-admin.php:1794 +#: includes/post-types-admin.php:1815 msgid "Set Show Avatar Image" msgstr "" -#: includes/post-types-admin.php:1801 +#: includes/post-types-admin.php:1822 msgid "Remove Show Avatar Image" msgstr "" -#: includes/post-types-admin.php:1837 +#: includes/post-types-admin.php:1858 msgid "Set Show Header Image" msgstr "" -#: includes/post-types-admin.php:1844 +#: includes/post-types-admin.php:1865 msgid "Remove Show Header Image" msgstr "" -#: includes/post-types-admin.php:2040 +#: includes/post-types-admin.php:2061 msgid "Error! No Show ID provided." msgstr "" -#: includes/post-types-admin.php:2045 +#: includes/post-types-admin.php:2066 msgid "Failed. Invalid Show." msgstr "" -#: includes/post-types-admin.php:2480 +#: includes/post-types-admin.php:2505 msgid "Warning! Shift conflicts detected." msgstr "" -#: includes/post-types-admin.php:2625 +#: includes/post-types-admin.php:2650 msgid "Active?" msgstr "" -#: includes/post-types-admin.php:2627 +#: includes/post-types-admin.php:2652 msgid "About?" msgstr "" -#: includes/post-types-admin.php:2628 +#: includes/post-types-admin.php:2653 msgid "Shifts" msgstr "" -#: includes/post-types-admin.php:2635 includes/post-types-admin.php:2823 -#: includes/post-types-admin.php:3120 +#: includes/post-types-admin.php:2660 includes/post-types-admin.php:2866 +#: includes/post-types-admin.php:3164 msgid "Show Avatar" msgstr "" -#: includes/post-types-admin.php:2711 +#: includes/post-types-admin.php:2736 msgid "This Shift is Disabled." msgstr "" -#: includes/post-types-admin.php:2720 +#: includes/post-types-admin.php:2745 msgid "This Shift has Schedule Conflicts and is Disabled." msgstr "" -#: includes/post-types-admin.php:2722 +#: includes/post-types-admin.php:2747 msgid "This Shift has Schedule Conflicts." msgstr "" -#: includes/post-types-admin.php:2731 +#: includes/post-types-admin.php:2756 msgid "This Show is not currently active." msgstr "" -#: includes/post-types-admin.php:2773 +#: includes/post-types-admin.php:2798 msgid "This shift is disabled as no day is set." msgstr "" -#: includes/post-types-admin.php:2875 +#: includes/post-types-admin.php:2919 msgid "Filter by show day" msgstr "" -#: includes/post-types-admin.php:2877 +#: includes/post-types-admin.php:2921 msgid "All show days" msgstr "" -#: includes/post-types-admin.php:2900 +#: includes/post-types-admin.php:2944 msgid "Override Show Data" msgstr "" -#: includes/post-types-admin.php:2941 +#: includes/post-types-admin.php:2985 msgid "Link to Show" msgstr "" -#: includes/post-types-admin.php:2949 +#: includes/post-types-admin.php:2993 msgid "No Show Link" msgstr "" -#: includes/post-types-admin.php:2986 +#: includes/post-types-admin.php:3030 msgid "If selected, Override data will be used from the Linked Show." msgstr "" -#: includes/post-types-admin.php:2995 +#: includes/post-types-admin.php:3039 msgid "Sync Genres?" msgstr "" -#: includes/post-types-admin.php:3005 +#: includes/post-types-admin.php:3049 msgid "If checked, assigned Genre terms are synced from the Linked Show when Updating." msgstr "" -#: includes/post-types-admin.php:3015 +#: includes/post-types-admin.php:3059 msgid "Sync Languages?" msgstr "" -#: includes/post-types-admin.php:3025 +#: includes/post-types-admin.php:3069 msgid "If checked, assigned Language terms are synced from the Linked Show when Updating." msgstr "" -#: includes/post-types-admin.php:3045 +#: includes/post-types-admin.php:3089 msgid "Usage Note" msgstr "" -#: includes/post-types-admin.php:3046 +#: includes/post-types-admin.php:3090 msgid " Unchecked boxes use Show data, checked boxes use Override data." msgstr "" -#: includes/post-types-admin.php:3053 +#: includes/post-types-admin.php:3097 msgid "Override?" msgstr "" -#: includes/post-types-admin.php:3055 +#: includes/post-types-admin.php:3099 msgid "Override Data" msgstr "" -#: includes/post-types-admin.php:3076 +#: includes/post-types-admin.php:3120 msgid "Use Title Editor metabox above." msgstr "" -#: includes/post-types-admin.php:3086 +#: includes/post-types-admin.php:3130 msgid "Description" msgstr "" -#: includes/post-types-admin.php:3093 +#: includes/post-types-admin.php:3137 msgid "Use Content Editor metabox below." msgstr "" -#: includes/post-types-admin.php:3110 +#: includes/post-types-admin.php:3154 msgid "Use Excerpt Editor metabox below." msgstr "" -#: includes/post-types-admin.php:3127 +#: includes/post-types-admin.php:3171 msgid "Use Show Images metabox." msgstr "" -#: includes/post-types-admin.php:3142 +#: includes/post-types-admin.php:3186 msgid "Featured Image" msgstr "" -#: includes/post-types-admin.php:3149 +#: includes/post-types-admin.php:3193 msgid "Use Feature Image metabox." msgstr "" -#: includes/post-types-admin.php:3171 +#: includes/post-types-admin.php:3215 msgid "Use Host assignment box below." msgstr "" -#: includes/post-types-admin.php:3193 +#: includes/post-types-admin.php:3237 msgid "Use Producer assignment box below." msgstr "" -#: includes/post-types-admin.php:3274 +#: includes/post-types-admin.php:3318 msgid "Latest Audio" msgstr "" -#: includes/post-types-admin.php:3304 +#: includes/post-types-admin.php:3348 msgid "Check to Disable" msgstr "" -#: includes/post-types-admin.php:3346 +#: includes/post-types-admin.php:3390 msgid "Override DJ(s) / Host(s)" msgstr "" -#: includes/post-types-admin.php:3350 +#: includes/post-types-admin.php:3394 msgid "Override Producer(s)" msgstr "" -#: includes/post-types-admin.php:3494 +#: includes/post-types-admin.php:3538 msgid "Override Schedule" msgstr "" -#: includes/post-types-admin.php:3548 +#: includes/post-types-admin.php:3592 msgid "Saving Overrides..." msgstr "" -#: includes/post-types-admin.php:3549 +#: includes/post-types-admin.php:3593 msgid "Overrides Saved." msgstr "" -#: includes/post-types-admin.php:3899 includes/post-types-admin.php:4213 +#: includes/post-types-admin.php:3946 includes/post-types-admin.php:4261 msgid "Duplicate Override" msgstr "" -#: includes/post-types-admin.php:3905 includes/post-types-admin.php:4218 +#: includes/post-types-admin.php:3952 includes/post-types-admin.php:4266 msgid "Remove Override" msgstr "" -#: includes/post-types-admin.php:3954 +#: includes/post-types-admin.php:4001 msgid "Are you sure you want to remove this Override?" msgstr "" -#: includes/post-types-admin.php:3955 +#: includes/post-types-admin.php:4002 msgid "Are you sure you want to clear all overrides?" msgstr "" -#: includes/post-types-admin.php:4735 +#: includes/post-types-admin.php:4783 msgid "Override Time(s)" msgstr "" -#: includes/post-types-admin.php:4736 +#: includes/post-types-admin.php:4784 msgid "Affected Show(s)" msgstr "" -#: includes/post-types-admin.php:4741 includes/post-types-admin.php:5567 +#: includes/post-types-admin.php:4789 includes/post-types-admin.php:5622 msgid "Linked Show" msgstr "" -#: includes/post-types-admin.php:4896 +#: includes/post-types-admin.php:4947 msgid "Inactive" msgstr "" -#: includes/post-types-admin.php:4981 +#: includes/post-types-admin.php:5032 msgid "Override Logo" msgstr "" -#: includes/post-types-admin.php:5062 +#: includes/post-types-admin.php:5115 msgid "Filter by override date" msgstr "" -#: includes/post-types-admin.php:5064 +#: includes/post-types-admin.php:5117 msgid "All Override Months" msgstr "" -#: includes/post-types-admin.php:5092 +#: includes/post-types-admin.php:5145 msgid "Past and Future" msgstr "" -#: includes/post-types-admin.php:5094 includes/post-types.php:152 +#: includes/post-types-admin.php:5147 includes/post-types.php:154 msgid "All Overrides" msgstr "" -#: includes/post-types-admin.php:5095 +#: includes/post-types-admin.php:5148 msgid "Past Overrides" msgstr "" -#: includes/post-types-admin.php:5096 +#: includes/post-types-admin.php:5149 msgid "Overrides Today" msgstr "" -#: includes/post-types-admin.php:5097 +#: includes/post-types-admin.php:5150 msgid "Future Overrides" msgstr "" -#: includes/post-types-admin.php:5120 +#: includes/post-types-admin.php:5173 msgid "Playlist Entries" msgstr "" -#: includes/post-types-admin.php:5140 includes/post-types-admin.php:5440 +#: includes/post-types-admin.php:5193 includes/post-types-admin.php:5495 msgid "Move Track Up" msgstr "" -#: includes/post-types-admin.php:5141 includes/post-types-admin.php:5441 +#: includes/post-types-admin.php:5194 includes/post-types-admin.php:5496 msgid "Move Track Down" msgstr "" -#: includes/post-types-admin.php:5142 includes/post-types-admin.php:5442 +#: includes/post-types-admin.php:5195 includes/post-types-admin.php:5497 msgid "Duplicate Track" msgstr "" -#: includes/post-types-admin.php:5143 includes/post-types-admin.php:5443 +#: includes/post-types-admin.php:5196 includes/post-types-admin.php:5498 msgid "Remove Track" msgstr "" -#: includes/post-types-admin.php:5158 +#: includes/post-types-admin.php:5212 msgid "Clear Tracks" msgstr "" -#: includes/post-types-admin.php:5161 +#: includes/post-types-admin.php:5215 msgid "Save Tracks" msgstr "" -#: includes/post-types-admin.php:5164 +#: includes/post-types-admin.php:5218 msgid "Add Track" msgstr "" -#: includes/post-types-admin.php:5169 +#: includes/post-types-admin.php:5223 msgid "Saving Playlist Tracks..." msgstr "" -#: includes/post-types-admin.php:5170 +#: includes/post-types-admin.php:5224 msgid "Playlist Tracks Saved." msgstr "" -#: includes/post-types-admin.php:5179 +#: includes/post-types-admin.php:5233 msgid "Tracks marked New are moved to the end of Playlist on update." msgstr "" -#: includes/post-types-admin.php:5182 +#: includes/post-types-admin.php:5236 msgid "Are you sure you want to clear the track list?" msgstr "" -#: includes/post-types-admin.php:5287 includes/post-types-admin.php:5501 +#: includes/post-types-admin.php:5341 includes/post-types-admin.php:5556 msgid "Length" msgstr "" -#: includes/post-types-admin.php:5295 includes/post-types-admin.php:5518 -#: includes/shortcodes.php:4078 templates/single-playlist-content.php:52 +#: includes/post-types-admin.php:5349 includes/post-types-admin.php:5573 +#: includes/shortcodes.php:4176 templates/single-playlist-content.php:67 msgid "Comments" msgstr "" -#: includes/post-types-admin.php:5296 includes/post-types-admin.php:5525 +#: includes/post-types-admin.php:5350 includes/post-types-admin.php:5580 msgid "New" msgstr "" -#: includes/post-types-admin.php:5298 includes/post-types-admin.php:5530 -#: includes/post-types-admin.php:6085 +#: includes/post-types-admin.php:5352 includes/post-types-admin.php:5585 +#: includes/post-types-admin.php:6146 msgid "Status" msgstr "" -#: includes/post-types-admin.php:5300 includes/post-types-admin.php:5533 +#: includes/post-types-admin.php:5354 includes/post-types-admin.php:5588 msgid "Queued" msgstr "" -#: includes/post-types-admin.php:5301 includes/post-types-admin.php:5534 +#: includes/post-types-admin.php:5355 includes/post-types-admin.php:5589 msgid "Played" msgstr "" -#: includes/post-types-admin.php:5304 includes/post-types-admin.php:5540 +#: includes/post-types-admin.php:5358 includes/post-types-admin.php:5595 msgid "Move" msgstr "" -#: includes/post-types-admin.php:5432 includes/post-types-admin.php:6084 -#: includes/shortcodes.php:4057 templates/legacy/single-playlist.php:45 -#: templates/single-playlist-content.php:48 +#: includes/post-types-admin.php:5487 includes/post-types-admin.php:6145 +#: includes/shortcodes.php:4155 templates/legacy/single-playlist.php:45 +#: templates/single-playlist-content.php:58 msgid "Artist" msgstr "" -#: includes/post-types-admin.php:5433 includes/post-types-admin.php:6083 -#: includes/shortcodes.php:4049 templates/legacy/single-playlist.php:46 -#: templates/single-playlist-content.php:49 +#: includes/post-types-admin.php:5488 includes/post-types-admin.php:6144 +#: includes/shortcodes.php:4147 templates/legacy/single-playlist.php:46 +#: templates/single-playlist-content.php:59 msgid "Song" msgstr "" -#: includes/post-types-admin.php:5434 includes/shortcodes.php:4064 +#: includes/post-types-admin.php:5489 includes/shortcodes.php:4162 #: templates/legacy/single-playlist.php:47 -#: templates/single-playlist-content.php:50 +#: templates/single-playlist-content.php:61 msgid "Album" msgstr "" -#: includes/post-types-admin.php:5435 templates/legacy/single-playlist.php:48 +#: includes/post-types-admin.php:5490 templates/legacy/single-playlist.php:48 msgid "Record Label" msgstr "" -#: includes/post-types-admin.php:5503 +#: includes/post-types-admin.php:5558 msgctxt "minutes unit" msgid "m" msgstr "" -#: includes/post-types-admin.php:5515 +#: includes/post-types-admin.php:5570 msgctxt "seconds unit" msgid "s" msgstr "" -#: includes/post-types-admin.php:5649 +#: includes/post-types-admin.php:5704 msgid "No Shows were found." msgstr "" -#: includes/post-types-admin.php:5655 +#: includes/post-types-admin.php:5710 msgid "You are not assigned to any Shows." msgstr "" -#: includes/post-types-admin.php:5664 includes/post-types-admin.php:6184 +#: includes/post-types-admin.php:5719 includes/post-types-admin.php:6247 msgid "Assign to Show" msgstr "" -#: includes/post-types-admin.php:5666 includes/post-types-admin.php:5789 -#: includes/post-types-admin.php:6187 +#: includes/post-types-admin.php:5721 includes/post-types-admin.php:5844 +#: includes/post-types-admin.php:6250 msgid "Unassigned" msgstr "" -#: includes/post-types-admin.php:5706 +#: includes/post-types-admin.php:5761 msgid "You can assign Playlists to Show Episodes in Radio Station Pro." msgstr "" -#: includes/post-types-admin.php:5707 radio-station-admin.php:1397 +#: includes/post-types-admin.php:5762 radio-station-admin.php:1439 msgid "Go PRO" msgstr "" -#: includes/post-types-admin.php:5708 +#: includes/post-types-admin.php:5763 msgid "Find Out More..." msgstr "" -#: includes/post-types-admin.php:5786 +#: includes/post-types-admin.php:5841 msgid "Assign to Show Shift" msgstr "" -#: includes/post-types-admin.php:5788 +#: includes/post-types-admin.php:5843 msgid "Latest Show" msgstr "" -#: includes/post-types-admin.php:5831 +#: includes/post-types-admin.php:5886 msgid "Failed. Use manual Publish or Update instead." msgstr "" -#: includes/post-types-admin.php:5835 +#: includes/post-types-admin.php:5890 msgid "Failed. No Playlist ID provided." msgstr "" -#: includes/post-types-admin.php:5840 +#: includes/post-types-admin.php:5895 msgid "Failed. Invalid Playlist ID." msgstr "" -#: includes/post-types-admin.php:6004 +#: includes/post-types-admin.php:6060 msgid "Tracks" msgstr "" -#: includes/post-types-admin.php:6005 +#: includes/post-types-admin.php:6061 msgid "Track List" msgstr "" -#: includes/post-types-admin.php:6079 +#: includes/post-types-admin.php:6140 msgid "Show/Hide Tracklist" msgstr "" -#: includes/post-types-admin.php:6298 +#: includes/post-types-admin.php:6363 msgid "Related to Show" msgstr "" -#: includes/post-types-admin.php:6361 +#: includes/post-types-admin.php:6426 msgid "Select Show(s)" msgstr "" -#: includes/post-types-admin.php:6534 +#: includes/post-types-admin.php:6601 msgid "Related Show(s)" msgstr "" -#: includes/post-types-admin.php:6574 +#: includes/post-types-admin.php:6643 msgid "Show(s)" msgstr "" -#: includes/post-types-admin.php:6707 +#: includes/post-types-admin.php:6776 msgid "Set Related Show(s)" msgstr "" -#: includes/post-types-admin.php:6842 +#: includes/post-types-admin.php:6914 msgid "Updated Related Shows for %d Posts." msgstr "" -#: includes/post-types-admin.php:6848 +#: includes/post-types-admin.php:6920 msgid "Failed to Update Related Shows for %d Posts." msgstr "" -#: includes/post-types-admin.php:7098 +#: includes/post-types-admin.php:7171 msgid "Failed. Please relogin and try again." msgstr "" -#: includes/post-types.php:45 includes/post-types.php:46 +#: includes/post-types.php:47 includes/post-types.php:48 msgid "Add Show" msgstr "" -#: includes/post-types.php:49 +#: includes/post-types.php:51 msgid "View Show" msgstr "" -#: includes/post-types.php:53 +#: includes/post-types.php:55 msgid "Search Shows" msgstr "" -#: includes/post-types.php:54 +#: includes/post-types.php:56 msgid "No Shows found" msgstr "" -#: includes/post-types.php:55 +#: includes/post-types.php:57 msgid "No Shows found in Trash" msgstr "" -#: includes/post-types.php:63 +#: includes/post-types.php:65 msgid "Shows Archive" msgstr "" -#: includes/post-types.php:94 +#: includes/post-types.php:96 msgid "Playlist" msgstr "" -#: includes/post-types.php:95 includes/post-types.php:96 +#: includes/post-types.php:97 includes/post-types.php:98 msgid "Add Playlist" msgstr "" -#: includes/post-types.php:97 +#: includes/post-types.php:99 msgid "Edit Playlist" msgstr "" -#: includes/post-types.php:98 +#: includes/post-types.php:100 msgid "New Playlist" msgstr "" -#: includes/post-types.php:99 includes/shortcodes.php:2848 -#: includes/shortcodes.php:4099 +#: includes/post-types.php:101 includes/shortcodes.php:2878 +#: includes/shortcodes.php:4197 msgid "View Playlist" msgstr "" -#: includes/post-types.php:103 +#: includes/post-types.php:105 msgid "Search Playlists" msgstr "" -#: includes/post-types.php:104 +#: includes/post-types.php:106 msgid "No Playlists found" msgstr "" -#: includes/post-types.php:105 +#: includes/post-types.php:107 msgid "No Playlists found in Trash" msgstr "" -#: includes/post-types.php:106 +#: includes/post-types.php:108 msgid "All Playlists" msgstr "" -#: includes/post-types.php:113 +#: includes/post-types.php:115 msgid "Playlists Archive" msgstr "" -#: includes/post-types.php:141 radio-station-admin.php:220 +#: includes/post-types.php:143 radio-station-admin.php:225 msgid "Schedule Overrides" msgstr "" -#: includes/post-types.php:143 includes/post-types.php:144 +#: includes/post-types.php:145 includes/post-types.php:146 msgid "Add Schedule Override" msgstr "" -#: includes/post-types.php:145 +#: includes/post-types.php:147 msgid "Edit Schedule Override" msgstr "" -#: includes/post-types.php:146 +#: includes/post-types.php:148 msgid "New Schedule Override" msgstr "" -#: includes/post-types.php:147 +#: includes/post-types.php:149 msgid "View Schedule Override" msgstr "" -#: includes/post-types.php:149 +#: includes/post-types.php:151 msgid "Search Overrides" msgstr "" -#: includes/post-types.php:150 +#: includes/post-types.php:152 msgid "No Overrides found" msgstr "" -#: includes/post-types.php:151 +#: includes/post-types.php:153 msgid "No Overrides found in Trash" msgstr "" -#: includes/post-types.php:160 +#: includes/post-types.php:162 msgid "Schedule Overrides Archive" msgstr "" -#: includes/post-types.php:194 +#: includes/post-types.php:196 msgid "Host" msgstr "" -#: includes/post-types.php:195 +#: includes/post-types.php:197 msgid "Add New Host Profile" msgstr "" -#: includes/post-types.php:196 +#: includes/post-types.php:198 msgid "Add Host Profile" msgstr "" -#: includes/post-types.php:197 +#: includes/post-types.php:199 msgid "Edit Host Profile" msgstr "" -#: includes/post-types.php:198 +#: includes/post-types.php:200 msgid "New Host Profile" msgstr "" -#: includes/post-types.php:199 +#: includes/post-types.php:201 msgid "View Host Profile" msgstr "" -#: includes/post-types.php:200 +#: includes/post-types.php:202 msgid "Show Hosts" msgstr "" -#: includes/post-types.php:202 +#: includes/post-types.php:204 msgid "Search Host Profiles" msgstr "" -#: includes/post-types.php:203 +#: includes/post-types.php:205 msgid "No Host Profiles found" msgstr "" -#: includes/post-types.php:204 +#: includes/post-types.php:206 msgid "No Host Profiles found in Trash" msgstr "" -#: includes/post-types.php:205 +#: includes/post-types.php:207 msgid "All Host Profiles" msgstr "" -#: includes/post-types.php:213 +#: includes/post-types.php:215 msgid "Host Profiles Archive" msgstr "" -#: includes/post-types.php:246 +#: includes/post-types.php:248 msgid "Producer" msgstr "" -#: includes/post-types.php:247 +#: includes/post-types.php:249 msgid "Add New Producer Profile" msgstr "" -#: includes/post-types.php:248 +#: includes/post-types.php:250 msgid "Add Producer Profile" msgstr "" -#: includes/post-types.php:249 +#: includes/post-types.php:251 msgid "Edit Producer Profile" msgstr "" -#: includes/post-types.php:250 +#: includes/post-types.php:252 msgid "New Producer Profile" msgstr "" -#: includes/post-types.php:251 +#: includes/post-types.php:253 msgid "View Producer Profile" msgstr "" -#: includes/post-types.php:252 +#: includes/post-types.php:254 msgid "Show Producers Profile" msgstr "" -#: includes/post-types.php:254 +#: includes/post-types.php:256 msgid "Search Producer Profiles" msgstr "" -#: includes/post-types.php:255 +#: includes/post-types.php:257 msgid "No Producer Profiles found" msgstr "" -#: includes/post-types.php:256 +#: includes/post-types.php:258 msgid "No Producer Profiles found in Trash" msgstr "" -#: includes/post-types.php:257 +#: includes/post-types.php:259 msgid "All Producer Profiles" msgstr "" -#: includes/post-types.php:265 +#: includes/post-types.php:267 msgid "Producer Profiles Archive" msgstr "" -#: includes/post-types.php:407 +#: includes/post-types.php:409 msgid "Edit" msgstr "" -#: includes/post-types.php:464 +#: includes/post-types.php:466 msgctxt "taxonomy general name" msgid "Genres" msgstr "" -#: includes/post-types.php:465 +#: includes/post-types.php:467 msgctxt "taxonomy singular name" msgid "Genre" msgstr "" -#: includes/post-types.php:466 +#: includes/post-types.php:468 msgid "Search Genres" msgstr "" -#: includes/post-types.php:467 +#: includes/post-types.php:469 msgid "All Genres" msgstr "" -#: includes/post-types.php:468 +#: includes/post-types.php:470 msgid "Parent Genre" msgstr "" -#: includes/post-types.php:469 +#: includes/post-types.php:471 msgid "Parent Genre:" msgstr "" -#: includes/post-types.php:470 +#: includes/post-types.php:472 msgid "Edit Genre" msgstr "" -#: includes/post-types.php:471 +#: includes/post-types.php:473 msgid "Update Genre" msgstr "" -#: includes/post-types.php:472 +#: includes/post-types.php:474 msgid "Add New Genre" msgstr "" -#: includes/post-types.php:473 +#: includes/post-types.php:475 msgid "New Genre Name" msgstr "" -#: includes/post-types.php:474 +#: includes/post-types.php:476 msgid "Genre" msgstr "" -#: includes/post-types.php:510 +#: includes/post-types.php:512 msgctxt "taxonomy general name" msgid "Languages" msgstr "" -#: includes/post-types.php:511 +#: includes/post-types.php:513 msgctxt "taxonomy singular name" msgid "Language" msgstr "" -#: includes/post-types.php:512 +#: includes/post-types.php:514 msgid "Search Languages" msgstr "" -#: includes/post-types.php:513 +#: includes/post-types.php:515 msgid "All Languages" msgstr "" -#: includes/post-types.php:514 +#: includes/post-types.php:516 msgid "Parent Language" msgstr "" -#: includes/post-types.php:515 +#: includes/post-types.php:517 msgid "Parent Language:" msgstr "" -#: includes/post-types.php:516 +#: includes/post-types.php:518 msgid "Edit Language" msgstr "" -#: includes/post-types.php:517 +#: includes/post-types.php:519 msgid "Update Language" msgstr "" -#: includes/post-types.php:518 +#: includes/post-types.php:520 msgid "Add New Language" msgstr "" -#: includes/post-types.php:519 +#: includes/post-types.php:521 msgid "New Language Name" msgstr "" -#: includes/post-types.php:520 +#: includes/post-types.php:522 msgid "Language" msgstr "" #: includes/shortcodes.php:80 includes/shortcodes.php:84 #: includes/shortcodes.php:98 includes/shortcodes.php:111 -#: includes/shortcodes.php:113 templates/single-show-content.php:550 -#: templates/single-show-content.php:557 +#: includes/shortcodes.php:113 templates/single-show-content.php:573 +#: templates/single-show-content.php:580 msgid "UTC" msgstr "" @@ -4297,1462 +3930,1526 @@ msgstr "" msgid "Your Time" msgstr "" -#: includes/shortcodes.php:678 +#: includes/shortcodes.php:680 msgid "No Shows in the requested Genre and Language were found." msgstr "" -#: includes/shortcodes.php:680 +#: includes/shortcodes.php:682 msgid "No Shows in the requested Genre were found." msgstr "" -#: includes/shortcodes.php:682 +#: includes/shortcodes.php:684 msgid "No Shows in the requested Language were found." msgstr "" -#: includes/shortcodes.php:684 +#: includes/shortcodes.php:686 msgid "No Shows were found to display." msgstr "" -#: includes/shortcodes.php:687 +#: includes/shortcodes.php:689 msgid "No Playlists were found to display." msgstr "" -#: includes/shortcodes.php:689 +#: includes/shortcodes.php:691 msgid "No Overrides were found to display." msgstr "" -#: includes/shortcodes.php:1062 +#: includes/shortcodes.php:1066 msgid "No Genres were found to display." msgstr "" -#: includes/shortcodes.php:1161 +#: includes/shortcodes.php:1165 msgid "No Shows were found with Genres assigned." msgstr "" -#: includes/shortcodes.php:1213 +#: includes/shortcodes.php:1218 msgid "No Shows in this Genre." msgstr "" -#: includes/shortcodes.php:1475 +#: includes/shortcodes.php:1481 msgid "No Languages were found to display." msgstr "" -#: includes/shortcodes.php:1629 +#: includes/shortcodes.php:1635 msgid "No Shows were found with Languages assigned." msgstr "" -#: includes/shortcodes.php:1665 +#: includes/shortcodes.php:1671 msgid "No Shows in this Language." msgstr "" -#: includes/shortcodes.php:2043 +#: includes/shortcodes.php:2056 msgid "View all posts by %s" msgstr "" -#: includes/shortcodes.php:2570 templates/single-show-content.php:499 +#: includes/shortcodes.php:2600 templates/single-show-content.php:522 msgid "Show Times" msgstr "" -#: includes/shortcodes.php:2825 includes/shortcodes.php:3569 -#: templates/single-show-content.php:648 +#: includes/shortcodes.php:2855 includes/shortcodes.php:3636 +#: templates/single-show-content.php:671 msgid "Encore Presentation" msgstr "" -#: includes/shortcodes.php:2957 +#: includes/shortcodes.php:2993 msgid "No Show currently scheduled." msgstr "" -#: includes/shortcodes.php:3629 +#: includes/shortcodes.php:3702 msgid "No Upcoming Shows Scheduled." msgstr "" -#: includes/shortcodes.php:4071 templates/single-playlist-content.php:51 +#: includes/shortcodes.php:4169 templates/single-playlist-content.php:64 msgid "Label" msgstr "" -#: includes/shortcodes.php:4132 +#: includes/shortcodes.php:4230 msgid "No Current Playlist available." msgstr "" -#: includes/shortcodes.php:4445 +#: includes/shortcodes.php:4561 msgid "More Playlists" msgstr "" -#: includes/support-functions.php:1743 +#: includes/support-functions.php:1853 msgid "WordPress Setting" msgstr "" -#: includes/templates.php:863 +#: includes/templates.php:955 msgid "%s for Shows" msgstr "" -#: includes/templates.php:865 +#: includes/templates.php:957 msgid "%s for Show" msgstr "" -#: includes/templates.php:874 +#: includes/templates.php:966 msgid "More %s for Shows" msgstr "" -#: includes/templates.php:876 +#: includes/templates.php:968 msgid "More %s for Show" msgstr "" -#: includes/templates.php:1161 +#: includes/templates.php:1259 msgid "Previous Related Show" msgstr "" -#: includes/templates.php:1166 +#: includes/templates.php:1264 msgid "Next Related Show" msgstr "" -#: includes/times.php:116 scheduler/schedule-engine.php:2733 +#: includes/times.php:148 scheduler/schedule-engine.php:2769 msgid "WordPress Timezone" msgstr "" -#: includes/user-roles.php:58 includes/user-roles.php:77 -#: includes/user-roles.php:78 includes/user-roles.php:290 +#: includes/user-roles.php:60 includes/user-roles.php:79 +#: includes/user-roles.php:80 includes/user-roles.php:292 msgid "DJ / Host" msgstr "" -#: includes/user-roles.php:82 +#: includes/user-roles.php:84 msgid "Show Producer" msgstr "" -#: includes/user-roles.php:162 +#: includes/user-roles.php:164 msgid "Show Editor" msgstr "" -#: loader.php:1355 +#: loader.php:1443 msgid "Plugin Name" msgstr "" -#: loader.php:1357 +#: loader.php:1445 msgid "Requires at least" msgstr "" -#: loader.php:1357 loader.php:1358 +#: loader.php:1445 loader.php:1446 msgid "WordPress" msgstr "" -#: loader.php:1358 +#: loader.php:1446 msgid "Tested up to" msgstr "" -#: loader.php:1360 +#: loader.php:1448 msgid "Stable Tag" msgstr "" -#: loader.php:1362 +#: loader.php:1450 msgid "Contributors" msgstr "" -#: loader.php:1388 +#: loader.php:1476 msgid "Extra Notes" msgstr "" #. Translators: plugin title, user name, site link, freemius link -#: loader.php:1599 +#: loader.php:1696 msgid "If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s" msgstr "" -#: loader.php:1700 +#: loader.php:1798 msgid "Upgrade" msgstr "" -#: loader.php:1708 +#: loader.php:1806 msgid "Pro Details" msgstr "" -#: loader.php:1722 +#: loader.php:1820 msgid "Add Ons" msgstr "" -#: loader.php:1790 +#: loader.php:1888 msgid "Notices" msgstr "" -#: loader.php:1918 +#: loader.php:2017 msgid "by" msgstr "" -#: loader.php:1928 +#: loader.php:2027 msgid "Plugin Homepage" msgstr "" -#: loader.php:1928 +#: loader.php:2027 msgid "Home" msgstr "" -#: loader.php:1932 +#: loader.php:2031 msgid "View Plugin" msgstr "" -#: loader.php:1932 +#: loader.php:2031 msgid "Readme" msgstr "" -#: loader.php:1935 +#: loader.php:2034 msgid "Plugin Documentation" msgstr "" -#: loader.php:1935 +#: loader.php:2034 msgid "Docs" msgstr "" -#: loader.php:1938 +#: loader.php:2037 msgid "Plugin Support" msgstr "" -#: loader.php:1938 +#: loader.php:2037 msgid "Support" msgstr "" -#: loader.php:1941 +#: loader.php:2040 msgid "Plugin Development" msgstr "" -#: loader.php:1941 +#: loader.php:2040 msgid "Dev" msgstr "" -#: loader.php:1989 radio-station-admin.php:1565 +#: loader.php:2088 radio-station-admin.php:1607 msgid "Rate on WordPress.Org" msgstr "" -#: loader.php:2006 radio-station.php:237 +#: loader.php:2105 radio-station.php:241 msgid "Share the Plugin Love" msgstr "" -#: loader.php:2023 radio-station.php:239 +#: loader.php:2122 radio-station.php:243 msgid "Support this Plugin" msgstr "" -#: loader.php:2043 +#: loader.php:2142 msgid "Settings Updated." msgstr "" -#: loader.php:2045 +#: loader.php:2144 msgid "Error! Settings NOT Updated." msgstr "" -#: loader.php:2047 +#: loader.php:2146 msgid "Settings Reset!" msgstr "" -#: loader.php:2316 +#: loader.php:2415 msgid "Reset Settings" msgstr "" -#: loader.php:2318 +#: loader.php:2417 msgid "Save Settings" msgstr "" -#: loader.php:2505 +#: loader.php:2604 msgid "Use Ctrl and Click to Select" msgstr "" -#: loader.php:2544 +#: loader.php:2643 msgid "Premium Feature." msgstr "" -#: loader.php:2546 radio-station-admin.php:397 +#: loader.php:2645 radio-station-admin.php:403 msgid "Upgrade Now" msgstr "" -#: loader.php:2553 +#: loader.php:2654 msgid "Details" msgstr "" -#: loader.php:2556 +#: loader.php:2657 msgid "Coming soon in Pro version!" msgstr "" -#: loader.php:2881 +#: loader.php:2979 msgid "Add Image" msgstr "" -#: loader.php:2889 +#: loader.php:2987 msgid "Remove Image" msgstr "" -#: loader.php:2969 +#: loader.php:3067 msgid "Are you sure you want to reset to default settings?" msgstr "" -#: options.php:19 +#: options.php:20 msgid "Streaming URL" msgstr "" -#: options.php:21 +#: options.php:22 msgid "Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds." msgstr "" -#: options.php:30 +#: options.php:31 msgid "Streaming Format" msgstr "" -#: options.php:32 +#: options.php:33 msgid "Select streaming format for streaming URL." msgstr "" -#: options.php:41 +#: options.php:42 msgid "Fallback Stream URL" msgstr "" -#: options.php:43 +#: options.php:44 msgid "Enter an alternative Streaming URL for Player fallback." msgstr "" -#: options.php:52 +#: options.php:53 msgid "Fallback Format" msgstr "" -#: options.php:54 +#: options.php:55 msgid "Select streaming fallback for fallback URL." msgstr "" -#: options.php:63 +#: options.php:64 msgid "Main Broadcast Language" msgstr "" -#: options.php:65 +#: options.php:66 msgid "Select the main language used on your Radio Station." msgstr "" -#: options.php:75 +#: options.php:76 msgid "Ping Netmix Directory" msgstr "" -#: options.php:78 +#: options.php:79 msgid "If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule." msgstr "" -#: options.php:89 +#: options.php:90 msgid "Station Title" msgstr "" -#: options.php:91 +#: options.php:92 msgid "Name of your Radio Station. For use in Stream Player and Data Feeds." msgstr "" -#: options.php:100 player/radio-player.php:304 +#: options.php:101 player/radio-player.php:348 msgid "Station Logo Image" msgstr "" -#: options.php:102 +#: options.php:103 msgid "Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256" msgstr "" -#: options.php:111 +#: options.php:112 msgid "Location Timezone" msgstr "" -#: options.php:113 +#: options.php:114 msgid "Select your Broadcast Location for Radio Timezone display." msgstr "" -#: options.php:122 +#: options.php:123 msgid "12 Hour Format" msgstr "" -#: options.php:123 +#: options.php:124 msgid "24 Hour Format" msgstr "" -#: options.php:125 +#: options.php:126 msgid "Clock Time Format" msgstr "" -#: options.php:127 +#: options.php:128 msgid "Default Time Format for display output. Can be overridden in each shortcode or widget." msgstr "" -#: options.php:137 +#: options.php:138 msgid "Station Phone" msgstr "" -#: options.php:139 +#: options.php:140 msgid "Main call in phone number for the Station (for requests etc.)" msgstr "" -#: options.php:150 +#: options.php:151 msgid "Show Phone Display" msgstr "" -#: options.php:151 +#: options.php:152 msgid "Display Station phone number on Shows where a Show phone number is not set." msgstr "" -#: options.php:161 +#: options.php:162 msgid "Station Email" msgstr "" -#: options.php:162 +#: options.php:163 msgid "Main email address for the Station (for requests etc.)" msgstr "" -#: options.php:173 +#: options.php:174 msgid "Show Email Display" msgstr "" -#: options.php:174 +#: options.php:175 msgid "Display Station email address on Shows where a Show email address is not set." msgstr "" -#: options.php:184 +#: options.php:185 msgid "Enable Data Routes" msgstr "" -#: options.php:187 +#: options.php:188 msgid "Enables Station Data Routes via WordPress REST API." msgstr "" -#: options.php:195 +#: options.php:196 msgid "Enable Data Feeds" msgstr "" -#: options.php:198 +#: options.php:199 msgid "Enable Station Data Feeds via WordPress Feed links." msgstr "" -#: options.php:210 +#: options.php:211 +msgid "Shift Conflict Checker" +msgstr "" + +#: options.php:214 +msgid "Check for Shift conflicts when saving Show shift times." +msgstr "" + +#: options.php:223 msgid "Disable Transients" msgstr "" -#: options.php:213 +#: options.php:226 msgid "Clear Schedule transients with every pageload. Less efficient but more reliable." msgstr "" -#: options.php:221 +#: options.php:234 msgid "Show Transients" msgstr "" -#: options.php:224 +#: options.php:237 msgid "Use Show Transient Data to improve Schedule calculation performance." msgstr "" -#: options.php:248 +#: options.php:261 msgid "Player Defaults Note" msgstr "" -#: options.php:249 +#: options.php:262 msgid "Note that you can override these defaults in specific Player Widgets." msgstr "" -#: options.php:257 +#: options.php:270 msgid "Display Station Title" msgstr "" -#: options.php:260 +#: options.php:273 msgid "Display your Radio Station Title in Player by default." msgstr "" -#: options.php:271 +#: options.php:284 msgid "Display your Radio Station Image in Player by default." msgstr "" -#: options.php:287 +#: options.php:301 msgid "Default audio script to use for playback in the Player." msgstr "" -#: options.php:297 +#: options.php:312 msgid "Fallback Scripts" msgstr "" -#: options.php:304 +#: options.php:319 msgid "Enabled fallback audio scripts to try when the default Player script fails." msgstr "" -#: options.php:312 +#: options.php:327 msgid "Default Player Theme" msgstr "" -#: options.php:318 +#: options.php:333 msgid "Default Player Controls theme style." msgstr "" -#: options.php:326 +#: options.php:341 msgid "Default Player Buttons" msgstr "" -#: options.php:329 +#: options.php:344 msgid "Circular Buttons" msgstr "" -#: options.php:330 +#: options.php:345 msgid "Rounded Buttons" msgstr "" -#: options.php:331 +#: options.php:346 msgid "Square Buttons" msgstr "" -#: options.php:333 +#: options.php:348 msgid "Default Player Buttons shape style." msgstr "" -#: options.php:347 +#: options.php:362 msgid "Volume Plus / Minus" msgstr "" -#: options.php:348 +#: options.php:363 msgid "Mute Volume Toggle" msgstr "" -#: options.php:349 +#: options.php:364 msgid "Maximize Volume" msgstr "" -#: options.php:351 +#: options.php:366 msgid "Which volume controls to display in the Player by default." msgstr "" -#: options.php:359 +#: options.php:374 msgid "Player Debug Mode" msgstr "" -#: options.php:362 +#: options.php:377 msgid "Output player debug information in browser javascript console." msgstr "" -#: options.php:372 +#: options.php:387 msgid "Playing Icon Highlight Color" msgstr "" -#: options.php:374 +#: options.php:389 msgid "Default highlight color to use for Play button icon when playing." msgstr "" -#: options.php:383 +#: options.php:398 msgid "Control Icons Highlight Color" msgstr "" -#: options.php:385 +#: options.php:400 msgid "Default highlight color to use for Control button icons when active." msgstr "" -#: options.php:394 +#: options.php:409 msgid "Volume Knob Color" msgstr "" -#: options.php:396 +#: options.php:411 msgid "Default Knob Color for Player Volume Slider." msgstr "" -#: options.php:405 +#: options.php:420 msgid "Volume Track Color" msgstr "" -#: options.php:407 +#: options.php:422 msgid "Default Track Color for Player Volume Slider." msgstr "" -#: options.php:423 +#: options.php:433 widgets/class-radio-player-widget.php:137 +msgid "Player Start Volume" +msgstr "" + +#: options.php:438 msgid "Initial volume for when the Player starts playback." msgstr "" -#: options.php:432 +#: options.php:447 msgid "Single Player at Once" msgstr "" -#: options.php:435 +#: options.php:450 msgid "Stop any existing Player instances on the page or in other windows or tabs when a Player is started." msgstr "" -#: options.php:444 +#: options.php:459 msgid "Autoresume Playback" msgstr "" -#: options.php:447 +#: options.php:462 msgid "Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page." msgstr "" -#: options.php:471 +#: options.php:486 msgid "Bar Defaults Note" msgstr "" -#: options.php:472 +#: options.php:487 msgid "The Bar Player uses the default configurations set above." msgstr "" -#: options.php:473 +#: options.php:488 msgid "You can override these in specific Player Widgets." msgstr "" -#: options.php:481 +#: options.php:496 msgid "Sitewide Player Bar" msgstr "" -#: options.php:484 +#: options.php:499 msgid "No Player Bar" msgstr "" -#: options.php:485 +#: options.php:500 msgid "Top Player Bar" msgstr "" -#: options.php:486 +#: options.php:501 msgid "Bottom Player Bar" msgstr "" -#: options.php:490 +#: options.php:505 msgid "Add a fixed position Player Bar which displays Sitewide." msgstr "" -#: options.php:511 +#: options.php:526 msgid "Fade In Player Bar" msgstr "" -#: options.php:516 +#: options.php:531 msgid "Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display." msgstr "" -#: options.php:526 +#: options.php:541 msgid "Continuous Playback" msgstr "" -#: options.php:529 +#: options.php:544 msgid "Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists." msgstr "" -#: options.php:538 +#: options.php:545 +msgid "Click here for setup notes." +msgstr "" + +#: options.php:554 msgid "Page Fade Time" msgstr "" -#: options.php:543 +#: options.php:559 msgid "Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display." msgstr "" -#: options.php:558 +#: options.php:574 msgid "Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)" msgstr "" -#: options.php:567 +#: options.php:583 msgid "Bar Player Text Color" msgstr "" -#: options.php:569 +#: options.php:585 msgid "Text color for the fixed position Sitewide Bar Player." msgstr "" -#: options.php:578 +#: options.php:594 msgid "Bar Player Background Color" msgstr "" -#: options.php:580 +#: options.php:596 msgid "Background color for the fixed position Sitewide Bar Player." msgstr "" -#: options.php:608 +#: options.php:624 msgid "Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)" msgstr "" -#: options.php:649 +#: options.php:663 +msgid "Store Track Metadata?" +msgstr "" + +#: options.php:668 +msgid "Save now playing track metadata in a separate database table for later use." +msgstr "" + +#: options.php:678 msgid "Master Schedule Page" msgstr "" -#: options.php:651 +#: options.php:680 msgid "Select the Page you are displaying the Master Schedule on." msgstr "" -#: options.php:662 +#: options.php:691 msgid "Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: " msgstr "" -#: options.php:670 +#: options.php:699 msgid "Schedule View Default" msgstr "" -#: options.php:675 +#: options.php:704 msgid "Divs View" msgstr "" -#: options.php:677 +#: options.php:706 msgid "Legacy Table" msgstr "" -#: options.php:679 +#: options.php:708 msgid "View type to use for automatic display on Master Schedule Page." msgstr "" -#: options.php:687 +#: options.php:716 msgid "Schedule Clock?" msgstr "" -#: options.php:691 +#: options.php:720 msgid "Clock" msgstr "" -#: options.php:692 templates/single-show-content.php:545 +#: options.php:721 templates/single-show-content.php:568 msgid "Timezone" msgstr "" -#: options.php:694 +#: options.php:723 msgid "Radio Time section display above program Schedule." msgstr "" -#: options.php:702 +#: options.php:731 msgid "View Switching" msgstr "" -#: options.php:705 +#: options.php:734 msgid "Enable View Switching on the automatic Master Schedule page." msgstr "" -#: options.php:715 +#: options.php:744 msgid "Available Views" msgstr "" -#: options.php:726 +#: options.php:755 msgid "Switcher Views available on automatic Master Schedule page." msgstr "" -#: options.php:750 options.php:864 options.php:898 +#: options.php:779 options.php:895 options.php:929 msgid "Info Blocks Position" msgstr "" -#: options.php:752 options.php:866 options.php:900 +#: options.php:781 options.php:897 options.php:931 msgid "Float Left" msgstr "" -#: options.php:753 options.php:867 options.php:901 +#: options.php:782 options.php:898 options.php:932 msgid "Float Right" msgstr "" -#: options.php:754 options.php:868 options.php:902 +#: options.php:783 options.php:899 options.php:933 msgid "Float Top" msgstr "" -#: options.php:757 +#: options.php:786 msgid "Where to position Show info blocks relative to Show Page content." msgstr "" -#: options.php:765 +#: options.php:794 msgid "Show Content Layout" msgstr "" -#: options.php:767 options.php:882 options.php:916 +#: options.php:796 options.php:913 options.php:947 msgid "Tabbed" msgstr "" -#: options.php:768 options.php:883 options.php:917 +#: options.php:797 options.php:914 options.php:948 msgid "Standard" msgstr "" -#: options.php:771 +#: options.php:800 msgid "How to display extra sections below Show description. In content tabs or standard layout down the page." msgstr "" -#: options.php:780 +#: options.php:809 msgid "Content Header Images" msgstr "" -#: options.php:783 +#: options.php:812 msgid "If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead." msgstr "" -#: options.php:804 +#: options.php:833 msgid "Posts per Page" msgstr "" -#: options.php:809 +#: options.php:838 msgid "Linked Show Posts per page on the Show Page tab/display." msgstr "" -#: options.php:820 +#: options.php:849 msgid "Playlists per Page" msgstr "" -#: options.php:822 +#: options.php:851 msgid "Playlists per page on the Show Page tab/display" msgstr "" -#: options.php:830 +#: options.php:859 msgid "Episodes per Page" msgstr "" -#: options.php:835 +#: options.php:864 msgid "Number of Show Episodes per page on the Show page tab/display." msgstr "" -#: options.php:852 -msgid "Combine team members (eg. hosts, producers) into a single display tab." +#: options.php:875 +msgid "Team Tab Display" msgstr "" -#: options.php:871 -msgid "Where to position Profile info blocks relative to Profile Page content." +#: options.php:878 +msgid "No Team Display" +msgstr "" + +#: options.php:879 +msgid "Separate Role Tabs" msgstr "" #: options.php:880 +msgid "Combined Team List" +msgstr "" + +#: options.php:883 +msgid "How to display Show team members (eg. hosts, producers) on a Show page." +msgstr "" + +#: options.php:902 +msgid "Where to position Profile info blocks relative to Profile Page content." +msgstr "" + +#: options.php:911 msgid "Profile Content Layout" msgstr "" -#: options.php:886 +#: options.php:917 msgid "How to display extra sections below Profile description. In content tabs or standard layout down the page." msgstr "" -#: options.php:905 +#: options.php:936 msgid "Where to position Episode info blocks relative to Episode Page content." msgstr "" -#: options.php:914 +#: options.php:945 msgid "Episode Content Layout" msgstr "" -#: options.php:920 +#: options.php:951 msgid "How to display extra sections below Episode description. In content tabs or standard layout down the page." msgstr "" -#: options.php:933 +#: options.php:960 +msgid "Use Latest Episode" +msgstr "" + +#: options.php:963 +msgid "Automatically use the latest Episode URL for the player embed on the Show page." +msgstr "" + +#: options.php:975 msgid "Shows Archive Page" msgstr "" -#: options.php:937 +#: options.php:979 msgid "Select the Page for displaying the Show archive list." msgstr "" -#: options.php:948 +#: options.php:990 msgid "Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:" msgstr "" -#: options.php:966 +#: options.php:1008 msgid "Overrides Archive Page" msgstr "" -#: options.php:970 +#: options.php:1012 msgid "Select the Page for displaying the Override archive list." msgstr "" -#: options.php:981 +#: options.php:1023 msgid "Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:" msgstr "" -#: options.php:999 +#: options.php:1041 msgid "Playlists Archive Page" msgstr "" -#: options.php:1003 +#: options.php:1045 msgid "Select the Page for displaying the Playlist archive list." msgstr "" -#: options.php:1014 +#: options.php:1056 msgid "Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:" msgstr "" -#: options.php:1066 +#: options.php:1075 +msgid "Episodes Archive Page" +msgstr "" + +#: options.php:1079 +msgid "Select the Page for displaying the Episode archive list." +msgstr "" + +#: options.php:1092 +msgid "Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:" +msgstr "" + +#: options.php:1134 msgid "Genres Archive Page" msgstr "" -#: options.php:1070 +#: options.php:1138 msgid "Select the Page for displaying the Genre archive list." msgstr "" -#: options.php:1081 +#: options.php:1149 msgid "Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:" msgstr "" -#: options.php:1100 +#: options.php:1168 msgid "Languages Archive Page" msgstr "" -#: options.php:1104 +#: options.php:1172 msgid "Select the Page for displaying the Language archive list." msgstr "" -#: options.php:1116 +#: options.php:1184 msgid "Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:" msgstr "" -#: options.php:1137 +#: options.php:1205 msgid "Templates Change Note" msgstr "" -#: options.php:1138 +#: options.php:1206 msgid "Since 2.3.0, the way that Templates are implemented has changed." msgstr "" -#: options.php:1139 +#: options.php:1207 msgid "See the Documentation for more information:" msgstr "" -#: options.php:1140 +#: options.php:1208 msgid "Templates Documentation" msgstr "" -#: options.php:1147 +#: options.php:1215 msgid "Show Template" msgstr "" -#: options.php:1150 options.php:1178 +#: options.php:1218 options.php:1246 msgid "Theme Page Template (page.php)" msgstr "" -#: options.php:1151 options.php:1179 +#: options.php:1219 options.php:1247 msgid "Theme Post Template (single.php)" msgstr "" -#: options.php:1152 options.php:1180 +#: options.php:1220 options.php:1248 msgid "Theme Singular Template (singular.php)" msgstr "" -#: options.php:1153 options.php:1181 +#: options.php:1221 options.php:1249 msgid "Legacy Plugin Template" msgstr "" -#: options.php:1156 +#: options.php:1224 msgid "Which template to use for displaying Show content." msgstr "" -#: options.php:1163 options.php:1191 +#: options.php:1231 options.php:1259 msgid "Combined Method" msgstr "" -#: options.php:1167 +#: options.php:1235 msgid "Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)" msgstr "" -#: options.php:1175 +#: options.php:1243 msgid "Playlist Template" msgstr "" -#: options.php:1184 +#: options.php:1252 msgid "Which template to use for displaying Playlist content." msgstr "" -#: options.php:1195 +#: options.php:1263 msgid "Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)" msgstr "" -#: options.php:1206 +#: options.php:1274 msgid "AJAX Load Widgets?" msgstr "" -#: options.php:1209 +#: options.php:1277 msgid "Defaults plugin widgets to AJAX loading. Can also be set on individual widgets." msgstr "" -#: options.php:1217 +#: options.php:1285 msgid "Dynamic Reloading?" msgstr "" -#: options.php:1220 +#: options.php:1288 msgid "Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets." msgstr "" -#: options.php:1229 +#: options.php:1297 msgid "Convert Show Times" msgstr "" -#: options.php:1232 +#: options.php:1300 msgid "Automatically display Show times converted into the visitor timezone, based on their browser setting." msgstr "" -#: options.php:1241 +#: options.php:1309 msgid "User Timezone Switching" msgstr "" -#: options.php:1244 +#: options.php:1312 msgid "Allow visitors to select their Timezone manually for Show time conversions." msgstr "" -#: options.php:1257 +#: options.php:1325 msgid "Show Editing Permissions" msgstr "" -#: options.php:1258 +#: options.php:1326 msgid "By default, only Hosts and Producers that are assigned to a Show can edit that Show." msgstr "" -#: options.php:1259 +#: options.php:1327 msgid "This means an Administrator or Show Editor must assign these users to the Show first." msgstr "" -#: options.php:1268 +#: options.php:1336 msgid "Playlist Permissions" msgstr "" -#: options.php:1269 +#: options.php:1337 msgid "Any user with a Host or Producer role can create Playlists." msgstr "" -#: options.php:1277 +#: options.php:1345 msgid "Show Editor Role" msgstr "" -#: options.php:1278 +#: options.php:1346 msgid "Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types." msgstr "" -#: options.php:1279 +#: options.php:1347 msgid "You can assign this Role to any user to give them full Station Schedule updating permissions." msgstr "" -#: options.php:1280 +#: options.php:1348 msgid "This is so a manager can edit the schedule without requiring full site administration role." msgstr "" -#: options.php:1288 +#: options.php:1356 msgid "Add to Author Capabilities" msgstr "" -#: options.php:1291 +#: options.php:1359 msgid "Allow users with WordPress Author role to publish and edit their own Shows and Playlists." msgstr "" -#: options.php:1299 +#: options.php:1367 msgid "Add to Editor Capabilities" msgstr "" -#: options.php:1302 +#: options.php:1370 msgid "Allow users with WordPress Editor role to edit all Radio Station post types." msgstr "" -#: options.php:1334 +#: options.php:1402 msgid "Pages" msgstr "" -#: options.php:1335 options.php:1361 +#: options.php:1403 options.php:1429 msgid "Archives" msgstr "" -#: options.php:1337 +#: options.php:1405 msgid "Widgets" msgstr "" -#: options.php:1338 +#: options.php:1406 msgid "Roles" msgstr "" -#: options.php:1347 +#: options.php:1415 msgid "Broadcast" msgstr "" -#: options.php:1348 +#: options.php:1416 msgid "Station" msgstr "" -#: options.php:1349 +#: options.php:1417 msgid "Feeds" msgstr "" -#: options.php:1350 +#: options.php:1418 msgid "Performance" msgstr "" -#: options.php:1351 +#: options.php:1419 msgid "Basic Defaults" msgstr "" -#: options.php:1352 +#: options.php:1420 msgid "Advanced Defaults" msgstr "" -#: options.php:1354 +#: options.php:1422 msgid "Sitewide Bar Player" msgstr "" -#: options.php:1355 +#: options.php:1423 msgid "Schedule Page" msgstr "" -#: options.php:1356 +#: options.php:1424 msgid "Single Templates" msgstr "" -#: options.php:1358 +#: options.php:1426 msgid "Show Pages" msgstr "" -#: options.php:1359 +#: options.php:1427 msgid "Profile Pages" msgstr "" -#: options.php:1360 +#: options.php:1428 msgid "Episode Pages" msgstr "" -#: options.php:1362 +#: options.php:1430 msgid "Post Types" msgstr "" -#: options.php:1363 +#: options.php:1431 msgid "Taxonomies" msgstr "" -#: options.php:1364 +#: options.php:1432 msgid "Widget Loading" msgstr "" -#: options.php:1365 +#: options.php:1433 msgid "Permissions" msgstr "" -#: player/radio-player.php:325 +#: player/radio-player.php:369 msgid "Station Name" msgstr "" -#: player/radio-player.php:357 +#: player/radio-player.php:401 msgid "Play Radio Stream" msgstr "" -#: player/radio-player.php:367 +#: player/radio-player.php:411 widgets/class-radio-player-widget.php:149 msgid "Mute" msgstr "" -#: player/radio-player.php:371 +#: player/radio-player.php:415 msgid "Volume Down" msgstr "" -#: player/radio-player.php:381 +#: player/radio-player.php:425 msgid "Volume Up" msgstr "" -#: player/radio-player.php:384 +#: player/radio-player.php:428 widgets/class-radio-player-widget.php:150 msgid "Max" msgstr "" -#: player/radio-player.php:408 +#: player/radio-player.php:452 msgid "Show Title" msgstr "" -#: player/radio-player.php:414 +#: player/radio-player.php:458 msgid "Show Logo Image" msgstr "" -#: radio-station-admin.php:184 +#: radio-station-admin.php:189 msgid "You do not have permissions to access that page." msgstr "" -#: radio-station-admin.php:235 +#: radio-station-admin.php:240 msgid "Import/Export Shows" msgstr "" -#: radio-station-admin.php:240 +#: radio-station-admin.php:245 msgid "Documentation" msgstr "" -#: radio-station-admin.php:240 +#: radio-station-admin.php:245 msgid "Help" msgstr "" -#: radio-station-admin.php:384 +#: radio-station-admin.php:390 msgid "Role Assignments" msgstr "" -#: radio-station-admin.php:385 +#: radio-station-admin.php:391 msgid "You can assign a Radio Station role to users through the WordPress User editor." msgstr "" -#: radio-station-admin.php:389 +#: radio-station-admin.php:395 msgid "Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user." msgstr "" -#: radio-station-admin.php:405 +#: radio-station-admin.php:411 msgid "Find out more about Radio Station Pro" msgstr "" -#: radio-station-admin.php:513 +#: radio-station-admin.php:523 msgid "Back to Documentation Index" msgstr "" -#: radio-station-admin.php:674 +#: radio-station-admin.php:633 +msgid "No playlists found for this period." +msgstr "" + +#: radio-station-admin.php:687 msgid "Right-click and download this file to save your export" msgstr "" -#: radio-station-admin.php:816 radio-station-admin.php:892 +#: radio-station-admin.php:829 radio-station-admin.php:907 msgid "Take a moment to Update for a better experience. In this update" msgstr "" -#: radio-station-admin.php:820 radio-station-admin.php:896 +#: radio-station-admin.php:835 radio-station-admin.php:911 msgid "Read full update details." msgstr "" -#: radio-station-admin.php:886 +#: radio-station-admin.php:901 msgid "A new version of" msgstr "" -#: radio-station-admin.php:888 +#: radio-station-admin.php:903 msgid "is available." msgstr "" -#: radio-station-admin.php:905 +#: radio-station-admin.php:920 msgid "Update Now" msgstr "" -#: radio-station-admin.php:908 radio-station-admin.php:999 +#: radio-station-admin.php:923 radio-station-admin.php:1016 msgid "Full Update Details" msgstr "" -#: radio-station-admin.php:919 radio-station-admin.php:1018 -#: radio-station-admin.php:1328 radio-station-admin.php:1414 -#: radio-station-admin.php:1576 +#: radio-station-admin.php:936 radio-station-admin.php:1037 +#: radio-station-admin.php:1369 radio-station-admin.php:1456 +#: radio-station-admin.php:1620 msgid "Dismiss this Notice" msgstr "" -#: radio-station-admin.php:977 +#: radio-station-admin.php:994 msgid "Update Notice" msgstr "" -#: radio-station-admin.php:983 +#: radio-station-admin.php:1000 msgid "Thanks for Updating! You can enjoy these improvements now" msgstr "" -#: radio-station-admin.php:1006 +#: radio-station-admin.php:1023 msgid "Plugin Settings" msgstr "" -#: radio-station-admin.php:1284 +#: radio-station-admin.php:1325 msgid "Time Sensitive Free Offer" msgstr "" -#: radio-station-admin.php:1287 +#: radio-station-admin.php:1328 msgid "We are excited to announce the opening of the new" msgstr "" -#: radio-station-admin.php:1289 +#: radio-station-admin.php:1330 msgid "Allowing listeners to newly discover Stations and Shows - which can include yours..." msgstr "" -#: radio-station-admin.php:1291 +#: radio-station-admin.php:1332 msgid "Because while launching," msgstr "" -#: radio-station-admin.php:1291 +#: radio-station-admin.php:1332 msgid "we are offering 30 days free listing to all Radio Station users!" msgstr "" -#: radio-station-admin.php:1292 +#: radio-station-admin.php:1333 msgid "Interested in more exposure and listeners for your Radio Station, for free?" msgstr "" -#: radio-station-admin.php:1304 +#: radio-station-admin.php:1345 msgid "Yes please!" msgstr "" -#: radio-station-admin.php:1307 widgets/class-current-playlist-widget.php:81 -#: widgets/class-current-show-widget.php:101 -#: widgets/class-radio-player-widget.php:164 -#: widgets/class-radio-player-widget.php:232 -#: widgets/class-radio-player-widget.php:242 -#: widgets/class-upcoming-shows-widget.php:104 +#: radio-station-admin.php:1348 widgets/class-current-playlist-widget.php:83 +#: widgets/class-current-show-widget.php:96 +#: widgets/class-radio-player-widget.php:166 +#: widgets/class-radio-player-widget.php:235 +#: widgets/class-radio-player-widget.php:245 +#: widgets/class-upcoming-shows-widget.php:99 msgid "More Details" msgstr "" -#: radio-station-admin.php:1311 +#: radio-station-admin.php:1352 msgid "Great! I'm listed, dismiss this notice." msgstr "" -#: radio-station-admin.php:1316 +#: radio-station-admin.php:1357 msgid "Offer valid until end of July 2020." msgstr "" -#: radio-station-admin.php:1317 +#: radio-station-admin.php:1358 msgid "Activate your 30 days before it ends!" msgstr "" -#: radio-station-admin.php:1362 +#: radio-station-admin.php:1403 msgid "Radio Station Pro Launch Discount!" msgstr "" -#: radio-station-admin.php:1364 +#: radio-station-admin.php:1405 msgid "We are thrilled to announce the upcoming launch of Radio Station PRO" msgstr "" -#: radio-station-admin.php:1365 radio-station-admin.php:1373 +#: radio-station-admin.php:1406 radio-station-admin.php:1414 msgid "Jam-packed with new features to \"level up\" your Station's online presence." msgstr "" -#: radio-station-admin.php:1366 +#: radio-station-admin.php:1407 msgid "During the launch," msgstr "" -#: radio-station-admin.php:1366 radio-station-admin.php:1374 +#: radio-station-admin.php:1407 radio-station-admin.php:1415 msgid "we are offering 30% discount to existing Radio Station users!" msgstr "" -#: radio-station-admin.php:1367 +#: radio-station-admin.php:1408 msgid "Sign up to the exclusive launch list to receive your discount code when we go LIVE." msgstr "" -#: radio-station-admin.php:1370 +#: radio-station-admin.php:1411 msgid "Radio Station PRO Launch is LIVE!" msgstr "" -#: radio-station-admin.php:1372 +#: radio-station-admin.php:1413 msgid "The long anticipated moment has arrived. The doors are open to get PRO" msgstr "" -#: radio-station-admin.php:1374 +#: radio-station-admin.php:1415 msgid "Remember," msgstr "" -#: radio-station-admin.php:1376 +#: radio-station-admin.php:1417 msgid "Sign up here to receive your exclusive launch discount code." msgstr "" -#: radio-station-admin.php:1391 +#: radio-station-admin.php:1432 msgid "Yes, I'm in!" msgstr "" -#: radio-station-admin.php:1402 +#: radio-station-admin.php:1444 msgid "Thanks, already done." msgstr "" -#: radio-station-admin.php:1540 +#: radio-station-admin.php:1582 msgid "Help support us to make improvements, modifications and introduce new features!" msgstr "" -#: radio-station-admin.php:1541 +#: radio-station-admin.php:1583 msgid "With over a thousand radio station users thanks to the original plugin author Nikki Blight" msgstr "" -#: radio-station-admin.php:1542 +#: radio-station-admin.php:1584 msgid "since June 2019" msgstr "" -#: radio-station-admin.php:1544 +#: radio-station-admin.php:1586 msgid " plugin development has been actively taken over by" msgstr "" -#: radio-station-admin.php:1547 +#: radio-station-admin.php:1589 msgid "And due to our continued efforts we now have a community of over two thousand active stations!" msgstr "" -#: radio-station-admin.php:1548 +#: radio-station-admin.php:1590 msgid "We invite you to" msgstr "" -#: radio-station-admin.php:1550 +#: radio-station-admin.php:1592 msgid "Become a Radio Station Patreon Supporter" msgstr "" -#: radio-station-admin.php:1551 +#: radio-station-admin.php:1593 msgid "to make it better for everyone" msgstr "" -#: radio-station-admin.php:1623 +#: radio-station-admin.php:1674 msgid "has detected" msgstr "" -#: radio-station-admin.php:1624 +#: radio-station-admin.php:1675 msgid "Schedule conflicts!" msgstr "" -#: radio-station-admin.php:1628 +#: radio-station-admin.php:1679 msgid "The following Shows have conflicting Shift times" msgstr "" -#: radio-station-admin.php:1667 +#: radio-station-admin.php:1718 msgid "Go to Show List" msgstr "" -#: radio-station-admin.php:1668 +#: radio-station-admin.php:1719 msgid "Conflicts are highlighted" msgstr "" -#: radio-station-admin.php:1669 +#: radio-station-admin.php:1720 msgid "in Show Shift column." msgstr "" -#: radio-station-admin.php:1674 +#: radio-station-admin.php:1725 msgid "This notice will persist" msgstr "" -#: radio-station-admin.php:1675 +#: radio-station-admin.php:1726 msgid "until conflicts are resolved." msgstr "" -#: radio-station-admin.php:1724 +#: radio-station-admin.php:1775 msgid "Stay tuned! Subscribe to Radio Station's" msgstr "" -#: radio-station-admin.php:1726 +#: radio-station-admin.php:1777 msgid "Plugin Updates and Announcements List" msgstr "" -#: radio-station.php:235 +#: radio-station.php:239 msgid "Rate on WordPress.org" msgstr "" -#: radio-station.php:998 +#: radio-station.php:1017 msgid "This Show has started." msgstr "" -#: radio-station.php:999 +#: radio-station.php:1018 msgid "This Show has ended." msgstr "" -#: radio-station.php:1000 +#: radio-station.php:1019 msgid "This Playlist has ended." msgstr "" -#: radio-station.php:1001 +#: radio-station.php:1020 msgid "Commencing in" msgstr "" -#: radio-station.php:1002 +#: radio-station.php:1021 msgid "Remaining Time" msgstr "" -#: radio-station.php:1008 +#: radio-station.php:1027 msgid "Seconds" msgstr "" -#: radio-station.php:1009 +#: radio-station.php:1028 msgid "Minute" msgstr "" -#: radio-station.php:1010 +#: radio-station.php:1029 msgid "Minutes" msgstr "" -#: radio-station.php:1011 +#: radio-station.php:1030 msgid "Hour" msgstr "" -#: radio-station.php:1012 +#: radio-station.php:1031 msgid "Hours" msgstr "" -#: radio-station.php:1014 +#: radio-station.php:1033 msgid "Days" msgstr "" -#: scheduler/schedule-engine.php:2685 +#: scheduler/schedule-engine.php:2722 msgid "Africa" msgstr "" -#: scheduler/schedule-engine.php:2686 +#: scheduler/schedule-engine.php:2723 msgid "America" msgstr "" -#: scheduler/schedule-engine.php:2687 +#: scheduler/schedule-engine.php:2724 msgid "Asia" msgstr "" -#: scheduler/schedule-engine.php:2688 +#: scheduler/schedule-engine.php:2725 msgid "Atlantic" msgstr "" -#: scheduler/schedule-engine.php:2689 +#: scheduler/schedule-engine.php:2726 msgid "Australia" msgstr "" -#: scheduler/schedule-engine.php:2690 +#: scheduler/schedule-engine.php:2727 msgid "Europe" msgstr "" -#: scheduler/schedule-engine.php:2691 +#: scheduler/schedule-engine.php:2728 msgid "Indian" msgstr "" -#: scheduler/schedule-engine.php:2692 +#: scheduler/schedule-engine.php:2729 msgid "Pacific" msgstr "" -#: scheduler/schedule-engine.php:2693 +#: scheduler/schedule-engine.php:2730 msgid "Antarctica" msgstr "" @@ -5772,23 +5469,23 @@ msgstr "" msgid "Newer posts" msgstr "" -#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:101 -#: templates/legacy/playlist-archive-template.php:66 +#: templates/legacy/archive-playlist.php:68 templates/legacy/author.php:98 +#: templates/legacy/playlist-archive-template.php:67 #: templates/legacy/show-blog-archive-template.php:78 msgid "Nothing Found" msgstr "" -#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:105 -#: templates/legacy/playlist-archive-template.php:70 +#: templates/legacy/archive-playlist.php:72 templates/legacy/author.php:102 +#: templates/legacy/playlist-archive-template.php:71 #: templates/legacy/show-blog-archive-template.php:82 msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." msgstr "" -#: templates/legacy/author.php:54 +#: templates/legacy/author.php:51 msgid "Author Archives: %s" msgstr "" -#: templates/legacy/author.php:75 +#: templates/legacy/author.php:72 msgid "About %s" msgstr "" @@ -5820,7 +5517,7 @@ msgstr "" msgid "No Shows scheduled for this day." msgstr "" -#: templates/playlist-export.php:78 +#: templates/playlist-export.php:81 msgid "End Date" msgstr "" @@ -5828,15 +5525,15 @@ msgstr "" msgid "Playlist for Show" msgstr "" -#: templates/single-playlist-content.php:83 +#: templates/single-playlist-content.php:105 msgid "No played tracks found for this Playlist yet." msgstr "" -#: templates/single-playlist-content.php:91 +#: templates/single-playlist-content.php:113 msgid "No entries found for this Playlist" msgstr "" -#: templates/single-playlist-content.php:98 +#: templates/single-playlist-content.php:120 msgid "All Playlists for Show" msgstr "" @@ -5852,175 +5549,338 @@ msgstr "" msgid "Email Show Host" msgstr "" -#: templates/single-show-content.php:319 +#: templates/single-show-content.php:343 msgid "Show Info" msgstr "" -#: templates/single-show-content.php:453 +#: templates/single-show-content.php:477 msgid "Call in" msgstr "" -#: templates/single-show-content.php:506 +#: templates/single-show-content.php:529 msgid "Not Currently Scheduled." msgstr "" -#: templates/single-show-content.php:709 +#: templates/single-show-content.php:739 msgid "Scheduled Dates" msgstr "" -#: templates/single-show-content.php:719 +#: templates/single-show-content.php:749 msgid "Go to Full Station Schedule Page" msgstr "" -#: templates/single-show-content.php:722 +#: templates/single-show-content.php:752 msgid "Full Station Schedule" msgstr "" -#: templates/single-show-content.php:769 +#: templates/single-show-content.php:799 msgid "About the %s" msgstr "" -#: templates/single-show-content.php:910 +#: templates/single-show-content.php:941 msgid "Latest Show Posts" msgstr "" -#: widgets/class-current-playlist-widget.php:79 -#: widgets/class-current-show-widget.php:99 -#: widgets/class-upcoming-shows-widget.php:102 +#: widgets/class-current-playlist-widget.php:17 +msgid "Display currently playing playlist." +msgstr "" + +#: widgets/class-current-playlist-widget.php:19 +msgid "(Radio Station) Now Playing List" +msgstr "" + +#: widgets/class-current-playlist-widget.php:62 +#: widgets/class-current-show-widget.php:74 +#: widgets/class-radio-clock-widget.php:42 +#: widgets/class-radio-player-widget.php:69 +#: widgets/class-upcoming-shows-widget.php:69 +msgid "Widget Title" +msgstr "" + +#: widgets/class-current-playlist-widget.php:75 +#: widgets/class-current-show-widget.php:88 +#: widgets/class-upcoming-shows-widget.php:91 +msgid "AJAX Load Widget?" +msgstr "" + +#: widgets/class-current-playlist-widget.php:81 +#: widgets/class-current-show-widget.php:94 +#: widgets/class-upcoming-shows-widget.php:97 msgid "Show changeover reloading available in Pro." msgstr "" -#: widgets/class-current-playlist-widget.php:80 -#: widgets/class-current-show-widget.php:100 -#: widgets/class-radio-player-widget.php:163 -#: widgets/class-radio-player-widget.php:231 -#: widgets/class-radio-player-widget.php:241 -#: widgets/class-upcoming-shows-widget.php:103 +#: widgets/class-current-playlist-widget.php:82 +#: widgets/class-current-show-widget.php:95 +#: widgets/class-radio-player-widget.php:165 +#: widgets/class-radio-player-widget.php:234 +#: widgets/class-radio-player-widget.php:244 +#: widgets/class-upcoming-shows-widget.php:98 msgid "Upgrade to Pro" msgstr "" -#: widgets/class-current-playlist-widget.php:98 +#: widgets/class-current-playlist-widget.php:90 +#: widgets/class-current-show-widget.php:112 +#: widgets/class-upcoming-shows-widget.php:115 +msgid "Hide Widget if Empty" +msgstr "" + +#: widgets/class-current-playlist-widget.php:100 msgid "Display Playlist Title?" msgstr "" -#: widgets/class-current-playlist-widget.php:113 +#: widgets/class-current-playlist-widget.php:108 +msgid "Link to Playlist?" +msgstr "" + +#: widgets/class-current-playlist-widget.php:115 msgid "No Playlist Text" msgstr "" -#: widgets/class-current-show-widget.php:110 +#: widgets/class-current-playlist-widget.php:125 +#: widgets/class-current-show-widget.php:204 +#: widgets/class-upcoming-shows-widget.php:199 +msgid "Display Countdown Timer" +msgstr "" + +#: widgets/class-current-playlist-widget.php:135 +msgid "Show Song Title" +msgstr "" + +#: widgets/class-current-playlist-widget.php:143 +msgid "Show Artist Name" +msgstr "" + +#: widgets/class-current-playlist-widget.php:151 +msgid " Show Album Name" +msgstr "" + +#: widgets/class-current-playlist-widget.php:159 +msgid "Show Record Label Name" +msgstr "" + +#: widgets/class-current-playlist-widget.php:167 +msgid "Show DJ Comments" +msgstr "" + +#: widgets/class-current-show-widget.php:17 +msgid "The currently playing on-air Show." +msgstr "" + +#: widgets/class-current-show-widget.php:19 +msgid "(Radio Station) Current Show On-Air" +msgstr "" + +#: widgets/class-current-show-widget.php:105 msgid "Empty for default. 0 for none." msgstr "" -#: widgets/class-current-show-widget.php:127 -#: widgets/class-upcoming-shows-widget.php:130 +#: widgets/class-current-show-widget.php:122 +#: widgets/class-upcoming-shows-widget.php:125 msgid "Link Show title to Show page." msgstr "" -#: widgets/class-current-show-widget.php:161 -#: widgets/class-upcoming-shows-widget.php:164 +#: widgets/class-current-show-widget.php:131 +#: widgets/class-upcoming-shows-widget.php:134 +msgid "Above" +msgstr "" + +#: widgets/class-current-show-widget.php:134 +#: widgets/class-upcoming-shows-widget.php:137 +msgid "Below" +msgstr "" + +#: widgets/class-current-show-widget.php:140 +#: widgets/class-upcoming-shows-widget.php:143 +msgid "Show Title Position (relative to Avatar)" +msgstr "" + +#: widgets/class-current-show-widget.php:149 +msgid "Display Show Avatar" +msgstr "" + +#: widgets/class-current-show-widget.php:156 +#: widgets/class-upcoming-shows-widget.php:159 msgid "Avatar Image Size" msgstr "" -#: widgets/class-current-show-widget.php:174 -#: widgets/class-upcoming-shows-widget.php:177 +#: widgets/class-current-show-widget.php:176 +#: widgets/class-upcoming-shows-widget.php:179 +msgid "Avatar Width" +msgstr "" + +#: widgets/class-current-show-widget.php:179 +#: widgets/class-upcoming-shows-widget.php:182 msgid "Show Avatar Width override. 0 or empty for none." msgstr "" -#: widgets/class-current-show-widget.php:183 +#: widgets/class-current-show-widget.php:188 msgid "Display Show Shift Info" msgstr "" -#: widgets/class-current-show-widget.php:191 +#: widgets/class-current-show-widget.php:196 msgid "Display All Show Shifts (if more than current.)" msgstr "" -#: widgets/class-current-show-widget.php:205 -#: widgets/class-radio-clock-widget.php:85 -#: widgets/class-upcoming-shows-widget.php:200 +#: widgets/class-current-show-widget.php:210 +#: widgets/class-radio-clock-widget.php:87 +#: widgets/class-upcoming-shows-widget.php:205 msgid "Time Format Display" msgstr "" -#: widgets/class-current-show-widget.php:220 +#: widgets/class-current-show-widget.php:225 msgid "Display Show Host names." msgstr "" -#: widgets/class-current-show-widget.php:228 +#: widgets/class-current-show-widget.php:233 msgid "Link Host names to author pages." msgstr "" -#: widgets/class-current-show-widget.php:236 +#: widgets/class-current-show-widget.php:241 msgid "Display Show description excerpt." msgstr "" -#: widgets/class-current-show-widget.php:244 +#: widgets/class-current-show-widget.php:249 msgid "Display link to Show playlist." msgstr "" -#: widgets/class-radio-clock-widget.php:60 +#: widgets/class-current-show-widget.php:257 +#: widgets/class-upcoming-shows-widget.php:236 +msgid "Display encore presentation text for Show" +msgstr "" + +#: widgets/class-radio-clock-widget.php:16 +msgid "Display current radio and user times." +msgstr "" + +#: widgets/class-radio-clock-widget.php:18 +msgid "(Radio Station) Radio Clock" +msgstr "" + +#: widgets/class-radio-clock-widget.php:62 msgid "Include Date Display?" msgstr "" -#: widgets/class-radio-clock-widget.php:79 +#: widgets/class-radio-clock-widget.php:81 msgid "Include seconds display?" msgstr "" -#: widgets/class-radio-clock-widget.php:98 +#: widgets/class-radio-clock-widget.php:100 msgid "Include timezone display?" msgstr "" -#: widgets/class-radio-player-widget.php:78 +#: widgets/class-radio-player-widget.php:18 +msgid "Radio Station Stream Player." +msgstr "" + +#: widgets/class-radio-player-widget.php:20 +msgid "(Radio Station) Stream Player" +msgstr "" + +#: widgets/class-radio-player-widget.php:77 +msgid "Stream or File URL" +msgstr "" + +#: widgets/class-radio-player-widget.php:80 msgid "Leave blank to use default stream URL." msgstr "" -#: widgets/class-radio-player-widget.php:116 +#: widgets/class-radio-player-widget.php:86 +msgid "Player Station Text" +msgstr "" + +#: widgets/class-radio-player-widget.php:102 +msgid "Do Not Display Image" +msgstr "" + +#: widgets/class-radio-player-widget.php:118 msgid "Default Player Script" msgstr "" -#: widgets/class-radio-player-widget.php:156 +#: widgets/class-radio-player-widget.php:147 +msgid "Slider" +msgstr "" + +#: widgets/class-radio-player-widget.php:148 +msgid "Up/Down" +msgstr "" + +#: widgets/class-radio-player-widget.php:158 msgid "Use as the default Player instance." msgstr "" -#: widgets/class-radio-player-widget.php:162 +#: widgets/class-radio-player-widget.php:164 msgid "Popup Player button available in Pro." msgstr "" -#: widgets/class-radio-player-widget.php:168 +#: widgets/class-radio-player-widget.php:170 msgid "Player Styles" msgstr "" -#: widgets/class-radio-player-widget.php:173 +#: widgets/class-radio-player-widget.php:175 msgid "Player Widget Layout" msgstr "" -#: widgets/class-radio-player-widget.php:209 +#: widgets/class-radio-player-widget.php:178 +msgid "Vertical (Stacked)" +msgstr "" + +#: widgets/class-radio-player-widget.php:179 +msgid "Horizontal (Inline)" +msgstr "" + +#: widgets/class-radio-player-widget.php:191 +msgid "Player Theme Style" +msgstr "" + +#: widgets/class-radio-player-widget.php:211 msgid "Buttons Style" msgstr "" -#: widgets/class-radio-player-widget.php:228 +#: widgets/class-radio-player-widget.php:231 msgid "[Pro] Color Options" msgstr "" -#: widgets/class-radio-player-widget.php:230 +#: widgets/class-radio-player-widget.php:233 msgid "Color options available in Pro." msgstr "" -#: widgets/class-radio-player-widget.php:237 +#: widgets/class-radio-player-widget.php:240 msgid "[Pro] Advanced Options" msgstr "" -#: widgets/class-radio-player-widget.php:239 +#: widgets/class-radio-player-widget.php:242 msgid "Advanced options available in Pro." msgstr "" -#: widgets/class-upcoming-shows-widget.php:186 +#: widgets/class-upcoming-shows-widget.php:17 +msgid "Display the upcoming Shows." +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:19 +msgid "(Radio Station) Upcoming Shows" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:77 +msgid "Limit" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:80 +msgid "Number of upcoming Shows to display." +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:152 +msgid "Display Show Avatars" +msgstr "" + +#: widgets/class-upcoming-shows-widget.php:191 msgid "Display shift info for this show" msgstr "" -#: widgets/class-upcoming-shows-widget.php:215 +#: widgets/class-upcoming-shows-widget.php:220 msgid "Display Show host names." msgstr "" -#: widgets/class-upcoming-shows-widget.php:223 +#: widgets/class-upcoming-shows-widget.php:228 msgid "Link host names to author pages" msgstr "" #. Plugin Name of the plugin/theme diff --git a/loader.php b/loader.php index 8096654..4719092 100644 --- a/loader.php +++ b/loader.php @@ -4,17 +4,58 @@ // === Plugin Panel Loader Class === // ================================= +// ------------- +// Loader v1.3.2 +// ------------- +// Note: Changelog at end of file. + if ( !defined( 'ABSPATH' ) ) exit; -// -------------- -// Version: 1.3.0 -// -------------- -// Note: Changelog and structure at end of file. -// +// === Loader Class === +// - Initialize Loader +// - Setup Plugin +// - Get Plugin Data +// - Get Plugin Version +// - Set Pro Namespace +// === Plugin Settings === +// - Get Default Settings +// - Add Settings +// - Maybe Transfer Settings +// - Get All Plugin Settings +// - Get Plugin Setting +// - Reset Plugin Settings +// - Update Plugin Settings +// - Validate Plugin Setting +// === Plugin Loading === +// - Load Plugin Settings +// - Add Actions +// - Load Helper Libraries +// - Maybe Load Thickbox +// - Readme Viewer AJAX +// === Freemius Loading === +// - Load Freemius +// - Filter Freemius Connect +// - Freemius Connect Message +// - Connect Update Message +// === Plugin Admin === +// - Add Settings Menu +// - Plugin Page Links +// - Message Box +// - Notice Boxer +// - Plugin Page Header +// - Settings Page +// - Settings Table +// - Setting Row +// - Settings Scripts +// - Settings Styles +// === Namespaced Functions === + + // ============ // Loader Usage // ============ // 1. replace all occurrences of radio_station_ in this file with the plugin namespace prefix eg. my_plugin_ +// 2. replace all occurrences of 'radio-station' in this file with the plugin's translation text domain // 2. define plugin options, default settings, and setup arguments your main plugin file // 3. require this file in the main plugin file and instantiate the loader class (see example below) // @@ -58,11 +99,11 @@ // 'parentmenu' => 'wordquest', // parent menu slug // 'home' => 'http://mysite.com/plugins/plugin/', // 'support' => 'http://mysite.com/plugins/plugin/support/', -// 'ratetext' => __('Rate on WordPress.org'), // (overrides default rate text) +// 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), // (overrides default rate text) // 'share' => 'http://mysites.com/plugins/plugin/#share', // (set sharing URL) -// 'sharetext' => __('Share the Plugin Love'), // (overrides default sharing text) +// 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), // (overrides default sharing text) // 'donate' => 'https://patreon.com/pagename', // (overrides plugin Donate URI) -// 'donatetext' => __('Support this Plugin'), // (overrides default donate text) +// 'donatetext' => __( 'Support this Plugin', 'radio-station' ), // (overrides default donate text) // 'readme' => false, // to not link to popup readme in settings page header // 'settingsmenu' => false, // to not automatically add a settings menu [non-WQ] // @@ -75,7 +116,7 @@ // // --- WordPress.Org --- // 'wporgslug' => 'plugin-slug', // WordPress.org plugin slug // 'wporg' => false, // * rechecked later (via presence of updatechecker.php) * -// 'textdomain' => 'text-domain', // translation text domain (usually same as plugin slug) +// 'textdomain' => 'radio-station', // translation text domain (usually same as plugin slug) // // // --- Freemius --- // 'freemius_id' => '', // Freemius plugin ID @@ -438,14 +479,14 @@ public function reset_settings() { } // 1.0.5: use sanitize_title on request variables // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( sanitize_text_field( $_REQUEST['page'] ) != $args['slug'] ) { + if ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) != $args['slug'] ) { return; } if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing - if ( 'reset' != sanitize_text_field( $_POST[$args['namespace'] . '_update_settings'] ) ) { + if ( 'reset' != sanitize_text_field( wp_unslash( $_POST[$args['namespace'] . '_update_settings'] ) ) ) { return; } @@ -486,11 +527,12 @@ public function update_settings() { // 1.0.2: fix to namespace key typo in isset check // 1.0.3: only use namespace not settings key // 1.0.9: check page is set and matches slug - if ( !isset( $_REQUEST['page'] ) || ( sanitize_text_field( $_REQUEST['page'] != $args['slug'] ) ) ) { + if ( !isset( $_REQUEST['page'] ) || ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) != $args['slug'] ) ) ) { return; } $updatekey = $args['namespace'] . '_update_settings'; - if ( !isset( $_POST[$updatekey] ) || ( 'yes' != sanitize_text_field( $_POST[$args['namespace'] . '_update_settings'] ) ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( !isset( $_POST[$updatekey] ) || ( 'yes' != sanitize_text_field( wp_unslash( $_POST[$updatekey] ) ) ) ) { return; } @@ -596,7 +638,7 @@ public function update_settings() { if ( strstr( $type, '/' ) ) { // --- implicit radio / select --- - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; $valid = explode( '/', $type ); if ( in_array( $posted, $valid ) ) { $settings[$key] = $posted; @@ -607,7 +649,7 @@ public function update_settings() { // --- checkbox / toggle --- // 1.0.6: fix to new unchecked checkbox value // 1.0.9: maybe validate to specified checkbox value - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( isset( $values['value'] ) ) { $valid = array( $values['value'] ); } else { @@ -623,7 +665,7 @@ public function update_settings() { // --- text area --- // 1.2.5: use sanitize_textarea_field with stripslashes - $posted = isset( $_POST[$postkey] ) ? sanitize_textarea_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_textarea_field( wp_unslash( $_POST[$postkey] ) ) : null; // 1.3.0: move use of stripslashes to separate line if ( !is_null( $posted ) ) { $posted = stripslashes( $posted ); @@ -634,7 +676,7 @@ public function update_settings() { // --- text field (slug) --- // 1.0.9: move text field sanitization to validation - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( !is_string( $valid ) ) { $valid = 'TEXT'; } @@ -644,7 +686,7 @@ public function update_settings() { // --- email field --- // 1.3.0: added explicitly for email field type - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( !is_string( $valid ) ) { $valid = 'EMAIL'; } @@ -654,7 +696,7 @@ public function update_settings() { // --- number field value --- // 1.0.9: added support for number step, minimum and maximum - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; $newsettings = $posted; $valid = 'NUMERIC'; if ( isset( $values['step'] ) ) { @@ -678,8 +720,8 @@ public function update_settings() { if ( isset( $_POST[$optionkey] ) ) { // 1.1.2: check for value if specified // 1.2.5: apply sanitize_text_field to posted value - if ( ( isset( $values['value'] ) && ( sanitize_text_field( $_POST[$optionkey] ) == $values['value'] ) ) - || ( !isset( $values['value'] ) && ( 'yes' == sanitize_text_field( $_POST[$optionkey] ) ) ) ) { + if ( ( isset( $values['value'] ) && ( sanitize_text_field( wp_unslash( $_POST[$optionkey] ) ) == $values['value'] ) ) + || ( !isset( $values['value'] ) && ( 'yes' == sanitize_text_field( wp_unslash( $_POST[$optionkey] ) ) ) ) ) { // 1.1.0: fixed to save only array of key values $posted[] = $option; } @@ -691,7 +733,7 @@ public function update_settings() { // -- comma separated values --- // 1.0.4: added comma separated values option - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( strstr( $posted, ',' ) ) { $posted = explode( ',', $posted ); } else { @@ -718,7 +760,7 @@ public function update_settings() { } elseif ( ( 'radio' == $type ) || ( 'select' == $type ) ) { // --- explicit radio or select value --- - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( is_string( $valid ) ) { $newsettings = $posted; } elseif ( is_array( $valid ) && array_key_exists( $posted, $valid ) ) { @@ -729,14 +771,14 @@ public function update_settings() { // --- multiselect values --- // 1.0.9: added multiselect value saving - $posted = isset( $_POST[$postkey] ) ? array_map( 'sanitize_text_field', $_POST[$postkey] ) : array(); + $posted = isset( $_POST[$postkey] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST[$postkey] ) ) : array(); $newsettings = array_values( $posted ); } elseif ( 'image' == $type ) { // --- check attachment ID value --- // 1.1.7: add image attachment ID saving - $posted = isset( $_POST[$postkey] ) ? absint( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? absint( wp_unslash( $_POST[$postkey] ) ) : null; if ( $posted ) { $attachment = wp_get_attachment_image_src( $posted, 'full' ); if ( is_array( $attachment ) ) { @@ -749,7 +791,7 @@ public function update_settings() { // --- hex color setting --- // 1.1.7: added color picker value saving // 1.2.5: use sanitize_hex_color on color field - $posted = isset( $_POST[$postkey] ) ? sanitize_hex_color( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_hex_color( wp_unslash( $_POST[$postkey] ) ) : null; $settings[$key] = $posted; } elseif ( 'coloralpha' == $type ) { @@ -758,7 +800,7 @@ public function update_settings() { // 1.2.5: separated color alpha setting condition // 1.2.5: added rgba version of sanitization // ref: https://wordpress.stackexchange.com/a/262578/76440 - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( !is_null( $posted ) ) { $posted = str_replace( ' ', '', $posted ); $values = array(); @@ -805,7 +847,7 @@ public function update_settings() { // --- fallback to text type --- // 1.3.0: added for unspecified option field type - $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null; + $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null; if ( !is_string( $valid ) ) { $valid = 'TEXT'; } @@ -941,7 +983,7 @@ public function update_settings() { } if ( count( $tabs ) > 0 ) { // 1.2.5: sanitize current tab value before validating - $currenttab = sanitize_text_field( $_POST['settingstab'] ); + $currenttab = sanitize_text_field( wp_unslash( $_POST['settingstab'] ) ); if ( in_array( $currenttab, $tabs ) ) { $settings['settingstab'] = $currenttab; } elseif ( in_array( 'general', $tabs ) ) { @@ -1346,7 +1388,7 @@ public function load_helpers() { public function maybe_load_thickbox() { $args = $this->args; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] ) ) { + if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] ) ) { add_thickbox(); } } @@ -1363,6 +1405,7 @@ public function readme_viewer() { // 1.0.7: changed readme.php to reader.php (for Github) $readme = $dir . '/readme.txt'; + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $contents = file_get_contents( $readme ); $parser = $dir . '/reader.php'; @@ -1371,24 +1414,40 @@ public function readme_viewer() { // --- include Markdown Readme Parser --- include $parser; - // --- remove license info as causes breakage! --- - // TODO: find line start and end to handle other possible licenses - $contents = str_replace( 'License: GPLv2 or later', '', $contents ); - $contents = str_replace( 'License URI: http://www.gnu.org/licenses/gpl-2.0.html', '', $contents ); + // --- remove license lines as causing breakage! --- + // 1.3.1: find license lines to handle other possible licenses + // $contents = str_replace( 'License: GPLv2 or later', '', $contents ); + // $contents = str_replace( 'License URI: http://www.gnu.org/licenses/gpl-2.0.html', '', $contents ); + $strip_lines = array( 'License', 'License URI' ); + foreach( $strip_lines as $strip_line ) { + if ( strstr( $contents, $strip_line ) ) { + $pos = strpos( $contents, $strip_line . ':' ); + $chunks = str_split( $contents, $pos ); + $before = $chunks[0]; + unset( $chunks[0] ); + $remainder = implode( '', $chunks ); + $posb = strpos( $remainder, "\n" ); + $chunks = str_split( $remainder, $posb ); + unset( $chunks[0] ); + $remainder = implode( '', $chunks ); + $contents = $before . $remainder; + } + } // --- instantiate Parser class --- - $readme = new WordPress_Readme_Parser(); + // 1.3.1: prefix readme parser + $readme = new radio_station_readme_parser(); $parsed = $readme->parse_readme_contents( $contents ); // --- output plugin info --- - echo '' . esc_html( __( 'Plugin Name' ) ) . ': ' . esc_html( $parsed['name'] ) . '
    ' . "\n"; - // echo '' . esc_html( __( 'Tags' ) ) . ': ' . esc_html( implode( ', ', $parsed['tags'] ) ) . '
    ' . "\n"; - echo '' . esc_html( __( 'Requires at least' ) ) . ': ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['requires_at_least'] ) . '
    ' . "\n"; - echo '' . esc_html( __( 'Tested up to' ) ) . ': ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['tested_up_to'] ) . '
    ' . "\n"; + echo '' . esc_html( __( 'Plugin Name', 'radio-station' ) ) . ': ' . esc_html( $parsed['name'] ) . '
    ' . "\n"; + // echo '' . esc_html( __( 'Tags', 'radio-station' ) ) . ': ' . esc_html( implode( ', ', $parsed['tags'] ) ) . '
    ' . "\n"; + echo '' . esc_html( __( 'Requires at least', 'radio-station' ) ) . ': ' . esc_html( __( 'WordPress', 'radio-station' ) ) . ' v' . esc_html( $parsed['requires_at_least'] ) . '
    ' . "\n"; + echo '' . esc_html( __( 'Tested up to', 'radio-station' ) ) . ': ' . esc_html( __( 'WordPress', 'radio-station' ) ) . ' v' . esc_html( $parsed['tested_up_to'] ) . '
    ' . "\n"; if ( isset( $parsed['stable_tag'] ) ) { - echo '' . esc_html( __( 'Stable Tag' ) ) . ': ' . esc_html( $parsed['stable_tag'] ) . '
    ' . "\n"; + echo '' . esc_html( __( 'Stable Tag', 'radio-station' ) ) . ': ' . esc_html( $parsed['stable_tag'] ) . '
    ' . "\n"; } - echo '' . esc_html( __( 'Contributors' ) ) . ': ' . esc_html( implode( ', ', $parsed['contributors'] ) ) . '
    ' . "\n"; + echo '' . esc_html( __( 'Contributors', 'radio-station' ) ) . ': ' . esc_html( implode( ', ', $parsed['contributors'] ) ) . '
    ' . "\n"; // echo 'Donate Link: ' . esc_html( $parsed['donate_link'] ) . '
    '; // 1.2.5: use wp_kses_post on plugin short description markup echo '
    ' . wp_kses_post( $parsed['short_description'] ) . '

    ' . "\n"; @@ -1414,7 +1473,7 @@ public function readme_viewer() { } } if ( isset( $parsed['remaining_content'] ) && !empty( $remaining_content ) ) { - echo '

    ' . esc_html( __( 'Extra Notes' ) ) . '

    ' . "\n"; + echo '

    ' . esc_html( __( 'Extra Notes', 'radio-station' ) ) . '

    ' . "\n"; // 1.2.5: use wp_kses_post on readme extra notes output echo wp_kses_post( $parsed['remaining_content'] ); } @@ -1483,7 +1542,7 @@ public function load_freemius() { // TODO: change to use new Freemius 2.3.0 support link filter ? // 1.0.5: use sanitize_text_field on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) { + if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) { if ( !function_exists( 'wp_redirect' ) ) { include ABSPATH . WPINC . '/pluggable.php'; } @@ -1508,6 +1567,7 @@ public function load_freemius() { // --- start the Freemius SDK --- if ( !class_exists( 'Freemius' ) ) { $freemiuspath = dirname( __FILE__ ) . '/freemius/start.php'; + $freemiuspath = apply_filters( 'freemius_load_path', $freemiuspath, $namespace, $args ); if ( !file_exists( $freemiuspath ) ) { return; } @@ -1544,7 +1604,8 @@ public function load_freemius() { $args['contact'] = $premium; } if ( !isset( $args['affiliation'] ) ) { - $args['affiliaation'] = false; + // 1.3.1: fix to key typo (affiliaation) + $args['affiliation'] = false; } // --- set Freemius settings from plugin settings --- @@ -1632,7 +1693,7 @@ public function freemius_connect_message( $message, $user_first_name, $plugin_ti // 1.2.4: added ordering to replacement arguments $message .= sprintf( // Translators: plugin title, user name, site link, freemius link - __( 'If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s' ), + __( 'If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s', 'radio-station' ), '' . $plugin_title . '', '' . $user_login . '', $site_link, @@ -1716,7 +1777,7 @@ public function plugin_links( $links, $file ) { // (depending on whether top level menu or Settings submenu item) $page = $this->menu_added ? 'admin.php' : 'options-general.php'; $settings_url = add_query_arg( 'page', $args['slug'], admin_url( $page ) ); - $settings_link = '' . esc_html( __( 'Settings' ) ) . ''; + $settings_link = '' . esc_html( __( 'Settings', 'radio-station' ) ) . ''; $link = array( 'settings' => $settings_link ); $links = array_merge( $link, $links ); @@ -1734,7 +1795,7 @@ public function plugin_links( $links, $file ) { $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : ''; } - $upgrade_link = '" . esc_html( __( 'Upgrade' ) ) . ''; + $upgrade_link = '" . esc_html( __( 'Upgrade', 'radio-station' ) ) . ''; $link = array( 'upgrade' => $upgrade_link ); $links = array_merge( $link, $links ); @@ -1742,7 +1803,7 @@ public function plugin_links( $links, $file ) { // 1.2.0: added separate pro details link if ( isset( $args['pro_link'] ) ) { $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; - $pro_link = '' . esc_html( __( 'Pro Details' ) ) . ''; + $pro_link = '' . esc_html( __( 'Pro Details', 'radio-station' ) ) . ''; $link = array( 'pro-details' => $pro_link ); $links = array_merge( $link, $links ); } @@ -1756,7 +1817,7 @@ public function plugin_links( $links, $file ) { if ( isset( $args['addons_link'] ) ) { $addons_url = $args['addons_link']; $addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : ''; - $addons_link = '' . esc_html( __( 'Add Ons' ) ) . ''; + $addons_link = '' . esc_html( __( 'Add Ons', 'radio-station' ) ) . ''; $link = array( 'addons' => $addons_link ); $links = array_merge( $link, $links ); } @@ -1811,7 +1872,7 @@ public function notice_boxer() { } // 1.0.5: use sanitize_title on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( substr( sanitize_text_field( $_REQUEST['page'] ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) { + if ( substr( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) { return; } @@ -1824,7 +1885,7 @@ public function notice_boxer() { echo '
    ' . "\n"; echo '

    ' . "\n"; echo '   ' . "\n"; - echo '' . esc_html( __( 'Notices' ) ) . '   ' . "\n"; + echo '' . esc_html( __( 'Notices', 'radio-station' ) ) . '   ' . "\n"; echo '

    ' . "\n"; echo '' . "\n"; @@ -1953,7 +2014,7 @@ public function settings_header() { // ---- plugin author --- // 1.0.8: check if author URL is set if ( isset( $args['author_url'] ) ) { - echo '' . esc_html( __( 'by' ) ) . ' '; + echo '' . esc_html( __( 'by', 'radio-station' ) ) . ' '; echo '' . esc_html( $args['author'] ) . '

    ' . "\n"; } @@ -1963,20 +2024,20 @@ public function settings_header() { // 1.1.0: added title attributes to links $links = array(); if ( isset( $args['home'] ) ) { - $links[] = '' . esc_html( __( 'Home' ) ) . ''; + $links[] = '' . esc_html( __( 'Home', 'radio-station' ) ) . ''; } if ( !isset( $args['readme'] ) || ( false !== $args['readme'] ) ) { $readme_url = add_query_arg( 'action', $namespace . '_readme_viewer', admin_url( 'admin-ajax.php' ) ); - $links[] = '' . esc_html( __( 'Readme' ) ) . ''; + $links[] = '' . esc_html( __( 'Readme', 'radio-station' ) ) . ''; } if ( isset( $args['docs'] ) ) { - $links[] = '' . esc_html( __( 'Docs' ) ) . ''; + $links[] = '' . esc_html( __( 'Docs', 'radio-station' ) ) . ''; } if ( isset( $args['support'] ) ) { - $links[] = '' . esc_html( __( 'Support' ) ) . ''; + $links[] = '' . esc_html( __( 'Support', 'radio-station' ) ) . ''; } if ( isset( $args['development'] ) ) { - $links[] = '' . esc_html( __( 'Dev' ) ) . ''; + $links[] = '' . esc_html( __( 'Dev', 'radio-station' ) ) . ''; } // 1.0.9: change filter from _plugin_links to disambiguate @@ -2024,7 +2085,7 @@ public function settings_header() { if ( isset( $args['ratetext'] ) ) { $rate_text = $args['ratetext']; } else { - $rate_text = __( 'Rate on WordPress.Org' ); + $rate_text = __( 'Rate on WordPress.Org', 'radio-station' ); } $rate_link = ''; $rate_link .= '' . "\n"; @@ -2041,7 +2102,7 @@ public function settings_header() { if ( isset( $args['sharetext'] ) ) { $share_text = $args['sharetext']; } else { - $share_text = __( 'Share the Plugin Love' ); + $share_text = __( 'Share the Plugin Love', 'radio-station' ); } $share_link = ''; $share_link .= ' '; @@ -2058,7 +2119,7 @@ public function settings_header() { if ( isset( $args['donatetext'] ) ) { $donate_text = $args['donatetext']; } else { - $donate_text = __( 'Support this Plugin' ); + $donate_text = __( 'Support this Plugin', 'radio-station' ); } $donate_link = ''; $donate_link .= ' '; @@ -2076,13 +2137,13 @@ public function settings_header() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $updated = sanitize_text_field( $_GET['updated'] ); + $updated = sanitize_text_field( wp_unslash( $_GET['updated'] ) ); if ( 'yes' == $updated ) { - $message = $settings['title'] . ' ' . __( 'Settings Updated.' ); + $message = $settings['title'] . ' ' . __( 'Settings Updated.', 'radio-station' ); } elseif ( 'no' == $updated ) { - $message = __( 'Error! Settings NOT Updated.' ); + $message = __( 'Error! Settings NOT Updated.', 'radio-station' ); } elseif ( 'reset' == $updated ) { - $message = $settings['title'] . ' ' . __( 'Settings Reset!' ); + $message = $settings['title'] . ' ' . __( 'Settings Reset!', 'radio-station' ); } if ( isset( $message ) ) { echo '
    ' . "\n"; } } @@ -2259,7 +2320,7 @@ public function settings_table() { } echo '' . "\n"; } else { - $tabs = array( 'general' => __( 'General' ) ); + $tabs = array( 'general' => __( 'General', 'radio-station' ) ); } // --- reset to default script --- @@ -2351,9 +2412,9 @@ public function settings_table() { $buttons = '' . "\n"; $buttons .= '' . "\n"; $buttons .= '' . "\n"; $buttons = apply_filters( $namespace . '_admin_save_buttons', $buttons, $tab ); @@ -2540,7 +2601,7 @@ public function setting_row( $option ) { $row .= '' . "\n"; @@ -2579,9 +2640,9 @@ public function setting_row( $option ) { } if ( $upgrade_link || isset( $pro_link ) ) { // 1.2.2: change text from Available in Pro - $row .= __( 'Premium Feature.' ) . '
    '; + $row .= __( 'Premium Feature.', 'radio-station' ) . '
    '; if ( $upgrade_link ) { - $row .= '
    ' . esc_html( __( 'Upgrade Now' ) ) . ''; + $row .= '' . esc_html( __( 'Upgrade Now', 'radio-station' ) ) . ''; } if ( $upgrade_link && isset( $pro_link ) ) { $row .= ' | '; @@ -2590,10 +2651,10 @@ public function setting_row( $option ) { // 1.2.2: change text from Pro details // 1.3.0: add hash link anchor for Pro feature options $option_anchor = str_replace( '_', '-', $option['key'] ); - $row .= '' . esc_html( __( 'Details' ) ) . '' . "\n"; + $row .= '' . esc_html( __( 'Details', 'radio-station' ) ) . '' . "\n"; } } else { - $row .= esc_html( __( 'Coming soon in Pro version!' ) ); + $row .= esc_html( __( 'Coming soon in Pro version!', 'radio-station' ) ); } $row .= '' . "\n"; @@ -2915,7 +2976,7 @@ public function setting_row( $option ) { $hidden = ' hidden'; } $row .= '' . "\n"; - $row .= esc_html( __( 'Add Image' ) ); + $row .= esc_html( __( 'Add Image', 'radio-station' ) ); $row .= '' . "\n"; $hidden = ''; @@ -2923,7 +2984,7 @@ public function setting_row( $option ) { $hidden = ' hidden'; } $row .= '' . "\n"; - $row .= esc_html( __( 'Remove Image' ) ); + $row .= esc_html( __( 'Remove Image', 'radio-station' ) ); $row .= '' . "\n"; $row .= '

    ' . "\n"; @@ -3003,7 +3064,7 @@ public function setting_scripts() { // --- reset settings function --- // 1.2.5: changed function prefix for consistency // 1.2.5: changed to jQuery click function to remove onclick button attribute - $confirmreset = __( 'Are you sure you want to reset to default settings?' ); + $confirmreset = __( 'Are you sure you want to reset to default settings?', 'radio-station' ); // echo "function plugin_panel_reset_defaults() {" . "\n"; echo "jQuery('#settingsresetbutton').on('click', function() {" . "\n"; echo " agree = confirm('" . esc_js( $confirmreset ) . "');" . "\n"; @@ -3044,7 +3105,7 @@ public function setting_scripts() { } elseif ( 'media_functions' == $script ) { // --- media functions --- - $confirm_remove = __( 'Are you sure you want to remove this image?' ); + $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); echo "jQuery(function(){ var mediaframe, parentdiv; @@ -3461,52 +3522,17 @@ function radio_station_settings_resources( $media, $color_picker ) { // ========= -// STRUCTURE +// CHANGELOG // ========= -// -// === Loader Class === -// - Initialize Loader -// - Setup Plugin -// - Get Plugin Data -// - Get Plugin Version -// - Set Pro Namespace -// === Plugin Settings === -// - Get Default Settings -// - Add Settings -// - Maybe Transfer Settings -// - Get All Plugin Settings -// - Get Plugin Setting -// - Reset Plugin Settings -// - Update Plugin Settings -// - Validate Plugin Setting -// === Plugin Loading === -// - Load Plugin Settings -// - Add Actions -// - Load Helper Libraries -// - Maybe Load Thickbox -// - Readme Viewer AJAX -// === Freemius Loading === -// - Load Freemius -// - Filter Freemius Connect -// - Freemius Connect Message -// - Connect Update Message -// === Plugin Admin === -// - Add Settings Menu -// - Plugin Page Links -// - Message Box -// - Notice Boxer -// - Plugin Page Header -// - Settings Page -// - Settings Table -// - Setting Row -// - Settings Scripts -// - Settings Styles -// === Namespaced Functions === +// == 1.3.2 == +// - added isset and wp_unslash to $_REQUEST inputs -// ========= -// CHANGELOG -// ========= +// == 1.3.1 == +// - use prefixed markdown reader function +// - when reading strip any license lines causing breakage +// - update to color picker alpha library (3.0.4) +// - added text domain to translation wrappers (for replacing) // == 1.3.0 == // - fix for possible page/post options conflict diff --git a/phpcs.xml b/phpcs.xml index 999f4c8..28dba1b 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -80,6 +80,7 @@ + diff --git a/player/css/radio-player.css b/player/css/radio-player.css index bdb8b5e..2862120 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -225,12 +225,12 @@ /* @group Volume Controls */ .rp-volume-slider-container { position: relative; - opacity: 0.9; + opacity: 0.85; height: 30px; } .rp-volume-slider-container:hover, .rp-volume-slider-container:focus { - opacity: 1; + opacity: 0.99; } .radio-container .rp-volume-controls button, .radio-container .rp-popup button { @@ -243,7 +243,7 @@ .radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus, .radio-container .rp-popup button:hover, .radio-container .rp-popup button:focus { - opacity: 1; + opacity: 0.99; scale: 1.1; } diff --git a/player/css/radio-player.min.css b/player/css/radio-player.min.css index 0f5585b..98fa0b0 100644 --- a/player/css/radio-player.min.css +++ b/player/css/radio-player.min.css @@ -1 +1 @@ -.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent;width:35%}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{width:32%}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.9;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:1}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:1;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file +.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent;width:35%}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{width:32%}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file diff --git a/player/js/howler.js b/player/js/howler.js index a2758c7..bb5a463 100644 --- a/player/js/howler.js +++ b/player/js/howler.js @@ -1,5 +1,5 @@ /*! - * howler.js v2.2.3 + * howler.js v2.2.4 * howlerjs.com * * (c) 2013-2020, James Simpson of GoldFire Studios @@ -265,7 +265,7 @@ // Opera version <33 has mixed MP3 support, so we need to check for and block it. var ua = self._navigator ? self._navigator.userAgent : ''; - var checkOpera = ua.match(/OPR\/([0-6].)/g); + var checkOpera = ua.match(/OPR\/(\d+)/g); var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33); var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1; var safariVersion = ua.match(/Version\/(.*?) /); @@ -2167,6 +2167,10 @@ var self = this; var isIOS = Howler._navigator && Howler._navigator.vendor.indexOf('Apple') >= 0; + if (!node.bufferSource) { + return self; + } + if (Howler._scratchBuffer && node.bufferSource) { node.bufferSource.onended = null; node.bufferSource.disconnect(0); @@ -2586,7 +2590,7 @@ /*! * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported. * - * howler.js v2.2.3 + * howler.js v2.2.4 * howlerjs.com * * (c) 2013-2020, James Simpson of GoldFire Studios @@ -3099,18 +3103,9 @@ panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : pa.panningModel }; - // Update the panner values or create a new panner if none exists. + // Create a new panner node if one doesn't already exist. var panner = sound._panner; - if (panner) { - panner.coneInnerAngle = pa.coneInnerAngle; - panner.coneOuterAngle = pa.coneOuterAngle; - panner.coneOuterGain = pa.coneOuterGain; - panner.distanceModel = pa.distanceModel; - panner.maxDistance = pa.maxDistance; - panner.refDistance = pa.refDistance; - panner.rolloffFactor = pa.rolloffFactor; - panner.panningModel = pa.panningModel; - } else { + if (!panner) { // Make sure we have a position to setup the node with. if (!sound._pos) { sound._pos = self._pos || [0, 0, -0.5]; @@ -3118,7 +3113,18 @@ // Create a new panner node. setupPanner(sound, 'spatial'); + panner = sound._panner } + + // Update the panner values or create a new panner if none exists. + panner.coneInnerAngle = pa.coneInnerAngle; + panner.coneOuterAngle = pa.coneOuterAngle; + panner.coneOuterGain = pa.coneOuterGain; + panner.distanceModel = pa.distanceModel; + panner.maxDistance = pa.maxDistance; + panner.refDistance = pa.refDistance; + panner.rolloffFactor = pa.rolloffFactor; + panner.panningModel = pa.panningModel; } } diff --git a/player/js/howler.min.js b/player/js/howler.min.js index ac43c39..40f9a2f 100644 --- a/player/js/howler.min.js +++ b/player/js/howler.min.js @@ -1,4 +1,4 @@ -/*! howler.js v2.2.3 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ -!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/([0-6].)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); +/*! howler.js v2.2.4 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/(\d+)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(!e.bufferSource)return o;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); /*! Spatial Plugin */ -!function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var s=a._orientation;return n="number"!=typeof n?s[1]:n,t="number"!=typeof t?s[2]:t,r="number"!=typeof r?s[3]:r,o="number"!=typeof o?s[4]:o,i="number"!=typeof i?s[5]:i,"number"!=typeof e?s:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,r="number"!=typeof r?p[3]:r,o="number"!=typeof o?p[4]:o,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a volume) {updown = 'up'; oldinstance = 'fadedown-'+instance;} else if (volume > target) {updown = 'down'; oldinstance = 'fadeup-'+instance;} - if (typeof radio_data.faders[oldinstance] != 'undefined') { - clearInterval(radio_data.faders[oldinstance]); radio_data.faders.splice(oldinstance, 1); + if (typeof radio_player_data.faders[oldinstance] != 'undefined') { + clearInterval(radio_player_data.faders[oldinstance]); radio_player_data.faders.splice(oldinstance, 1); } finstance = 'fade'+updown+'-'+instance; - if (typeof radio_data.faders[finstance] != 'undefined') {clearInterval(radio_data.faders[finstance]);} - player = radio_data.players[finstance] = radio_data.players[instance]; - script = radio_data.scripts[finstance] = radio_data.scripts[instance]; + if (typeof radio_player_data.faders[finstance] != 'undefined') {clearInterval(radio_player_data.faders[finstance]);} + player = radio_player_data.players[finstance] = radio_player_data.players[instance]; + script = radio_player_data.scripts[finstance] = radio_player_data.scripts[instance]; steps = Math.ceil(volume / 5); steptime = Math.floor(fadetime / steps); - radio_data.faders[finstance] = setInterval(function(finstance, updown, target, complete) { - player = radio_data.players[finstance]; script = radio_data.scripts[finstance]; + radio_player_data.faders[finstance] = setInterval(function(finstance, updown, target, complete) { + player = radio_player_data.players[finstance]; script = radio_player_data.scripts[finstance]; volume = radio_player_get_volume(finstance); if (updown == 'up') {volume = volume + 5; if (volume > target) {volume = target;} } else if (updown == 'down') {volume = volume - 5; if (volume < target) {volume = target;} } @@ -395,16 +395,16 @@ function radio_player_fade_volume(instance, fadetime, target, complete) { if (volume == target) { if (complete == 'pause') {radio_player_pause_instance(finstance, false);} else if (complete == 'stop') {radio_player_stop_instance(finstance, false);} - radio_data.players.splice(finstance, 1); radio_data.scripts.splice(finstance, 1); - clearInterval(radio_data.faders[finstance]); + radio_player_data.players.splice(finstance, 1); radio_player_data.scripts.splice(finstance, 1); + clearInterval(radio_player_data.faders[finstance]); } }, steptime); } /* --- check if player is playing */ function radio_player_is_playing(instance) { - if (!(instance in radio_data.players)) {return false;} - player = radio_data.players[instance]; script = radio_data.scripts[instance]; + if (!(instance in radio_player_data.players)) {return false;} + player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; if (radio_player.debug) {console.log(player);} if (script == 'amplitude') { state = player.getPlayerState(); @@ -425,7 +425,7 @@ function radio_player_is_playing(instance) { /* --- change player volume --- */ function radio_player_change_volume(instance, volume) { - player = radio_data.players[instance]; script = radio_data.scripts[instance]; + player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; container = jQuery('#radio_container_'+instance); if (volume == 100) {if (!container.hasClass('maxed')) {container.addClass('maxed');}} else {container.removeClass('maxed');} slider = jQuery('#radio_container_'+instance+' .rp-volume-slider'); @@ -449,7 +449,7 @@ function radio_player_change_volume(instance, volume) { /* --- get player volume --- */ function radio_player_get_volume(instance) { - player = radio_data.players[instance]; script = radio_data.scripts[instance]; + player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; if (script == 'amplitude') {volume = player.getVolume();} else if (script == 'howler') {volume = (player.volume() * 100);} else if (script == 'jplayer') {volume = (player.jPlayer('volume') * 100);} @@ -475,7 +475,7 @@ function radio_player_volume_slider(instance, volume) { /* --- mute or unmute a player -- */ function radio_player_mute_unmute(instance, mute) { - player = radio_data.players[instance]; script = radio_data.scripts[instance]; + player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; container = jQuery('#radio_container_'+instance); if (mute) {container.addClass('muted'); eventprefix = '';} else {container.removeClass('muted'); eventprefix = 'un';} @@ -518,15 +518,15 @@ function radio_player_default_instance() { /* --- pause all other instances */ function radio_player_pause_others(instance) { - if (radio_player.settings.singular && radio_data.players.length) { - if (radio_player.debug) {console.log(radio_data.players);} - for (i in radio_data.players) { + if (radio_player.settings.singular && radio_player_data.players.length) { + if (radio_player.debug) {console.log(radio_player_data.players);} + for (i in radio_player_data.players) { if (i != instance) { /* TODO: if the stream is the same, maybe swap-fade players ? */ if (radio_player.debug) {console.log('Pausing Player Instance '+i);} /* temporarily disabled as conflicting with multiple instances usage */ /* radio_player_pause_instance(instance); */ - /* player = radio_data.players[instance]; script = radio_data.scripts[instance]; + /* player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; if ((script == 'amplitude') || (script == 'howler')) {player.pause();} else if (script == 'jplayer') {player.jPlayer('pause');} */ } @@ -560,8 +560,8 @@ function radio_player_event_instance(e, name, script) { function radio_player_match_instance(obj, e, script) { instance = false; /* if (radio_player.debug) {console.log(script+' Player Event'); console.log(e);} */ - for (i = 0; i < radio_data.players.length; i++) { - if (obj == radio_data.players[i]) {instance = i;} + for (i = 0; i < radio_player_data.players.length; i++) { + if (obj == radio_player_data.players[i]) {instance = i;} } return instance; } @@ -600,83 +600,83 @@ function radio_player_sleep_delay(sleep_ms) { /* --- load player state --- */ function radio_player_load_state() { - if (typeof radio_data.state.checked == 'undefined') { - /* radio_data.state.transition = false; */ + if (typeof radio_player_data.state.checked == 'undefined') { + /* radio_player_data.state.transition = false; */ playing = radio_player_cookie.get('playing'); - if (playing) {radio_data.state.playing = playing;} + if (playing) {radio_player_data.state.playing = playing;} channel = radio_player_cookie.get('channel'); - if (channel != null) {radio_data.state.channel = channel;} + if (channel != null) {radio_player_data.state.channel = channel;} station = radio_player_cookie.get('station'); - if (station != null) {radio_data.state.station = station;} + if (station != null) {radio_player_data.state.station = station;} volume = radio_player_cookie.get('volume'); - if (volume != null) {radio_data.state.volume = volume;} + if (volume != null) {radio_player_data.state.volume = volume;} mute = radio_player_cookie.get('mute'); - if (mute != null) {radio_data.state.mute = mute;} + if (mute != null) {radio_player_data.state.mute = mute;} if (radio_player.debug) { console.log('Loaded User Player State - Playing: '+playing+' - Station: '+station+' - Volume: '+volume+ ' - Muted: '+mute+' - Data: '); } data = radio_player_cookie.get('data'); if ((data != null) && (data.url != '')) { - radio_data.state.data = data; + radio_player_data.state.data = data; if ((data.instance == false) || (data.instance == 'undefined')) {data.instance = 1;} - radio_data.data[data.instance] = data; - if (radio_player.debug) {console.log('Radio State Data:'); console.log(radio_data.data);} + radio_player_data.data[data.instance] = data; + if (radio_player.debug) {console.log('Radio State Data:'); console.log(radio_player_data.data);} } if ((volume != null) && (data != null)) { radio_player_volume_slider(data.instance, volume); } - radio_data.state.checked = true; + radio_player_data.state.checked = true; } } /* --- store user player state --- */ function radio_player_set_state(key, value) { changed = false; - if ((key == 'playing') && (value != radio_data.state.playing)) { + if ((key == 'playing') && (value != radio_player_data.state.playing)) { radio_player_cookie.set('playing', value, 7); - radio_data.state.playing = value; changed = true; - } else if ((key == 'channel') && value && (value > 0) && (value != radio_data.state.channel)) { + radio_player_data.state.playing = value; changed = true; + } else if ((key == 'channel') && value && (value > 0) && (value != radio_player_data.state.channel)) { radio_player_cookie.set('channel', value, 30); - radio_data.state.channel = value; changed = true; - } else if ((key == 'station') && value && (value > 0) && (value != radio_data.state.station)) { + radio_player_data.state.channel = value; changed = true; + } else if ((key == 'station') && value && (value > 0) && (value != radio_player_data.state.station)) { radio_player_cookie.set('station', value, 30); - radio_data.state.station = value; changed = true; - } else if ((key == 'volume') && (value != radio_data.state.volume)) { + radio_player_data.state.station = value; changed = true; + } else if ((key == 'volume') && (value != radio_player_data.state.volume)) { radio_player_cookie.set('volume', value, 365); - radio_data.state.volume = value; changed = true; - } else if ((key == 'mute') && (value != radio_data.state.mute)) { + radio_player_data.state.volume = value; changed = true; + } else if ((key == 'mute') && (value != radio_player_data.state.mute)) { radio_player_cookie.set('mute', value, 1); - radio_data.state.mute = value; changed = true; + radio_player_data.state.mute = value; changed = true; } - if (changed) {radio_data.state.changed = true;} - detail = {'state': radio_data.state} + if (changed) {radio_player_data.state.changed = true;} + detail = {'state': radio_player_data.state} radio_player_custom_event('rp-set-state', detail); } /* --- store player instance data */ function radio_player_set_data_state(script, instance, data, start) { url = data.url; format = data.format; fallback = data.fallback; fformat = data.fformat; /* 2.5.6: fix to fallback format */ - if (typeof radio_data.data[instance] != 'undefined') { - cdata = radio_data.data[instance]; + if (typeof radio_player_data.data[instance] != 'undefined') { + cdata = radio_player_data.data[instance]; if ( (cdata.script != script) || (cdata.url != url) || (cdata.format != format) || (cdata.fallback != fallback) || (cdata.fformat != fformat) || (cdata.start != start) ) { - radio_data.failed[instance] = new Array(); + radio_player_data.failed[instance] = new Array(); } radio_player.previous_data = data; } data = {'script': script, 'instance': instance, 'url': url, 'format': format, 'fallback': fallback, 'fformat': fformat, 'start': start}; if (radio_player.debug) {console.log('Setting Data State:'); console.log(data);} - radio_data.data[instance] = data; - if (radio_data.state.data != data) { - radio_data.state.data = data; + radio_player_data.data[instance] = data; + if (radio_player_data.state.data != data) { + radio_player_data.state.data = data; radio_player_cookie.set('data', data, 7); - radio_data.state.changed = true; + radio_player_data.state.changed = true; } } /* --- save user meta values --- */ function radio_player_save_user_state() { - if (radio_data.state.changed) { - state = radio_data.state; + if (radio_player_data.state.changed) { + state = radio_player_data.state; if (state.loggedin && !state.saving) { if (state.playing) {playing = '1';} else {playing = '0';} if (state.station) {station = state.station;} else {station = '0';} @@ -688,14 +688,14 @@ function radio_player_save_user_state() { url += '&playing='+playing+'&station='+station+'&volume='+volume+'&mute='+mute+'×tamp='+timestamp; if (radio_player.debug) {url += '&debug=1';} /* document.getElementById('radio-player-state-iframe').src = url; */ - radio_data.state.saving = true; + radio_player_data.state.saving = true; jQuery.get(url, function(data) { if (radio_player.debug) {console.log(data);} - radio_data.state.saving = false; + radio_player_data.state.saving = false; }); } } - radio_data.state.changed = false; + radio_player_data.state.changed = false; } /* --- volume change audio test --- */ @@ -732,11 +732,11 @@ function radio_player_window_guid() { /* --- broadcast pause all message to other windows --- */ function radio_player_broadcast_playing(instance) { if (typeof sysend != 'undefined') { - if (typeof radio_data.types[instance] == 'undefined') {type = 0;} - else {type = radio_data.types[instance];} - if (typeof radio_data.channels[instance] == 'undefined') {channel = 0;} + if (typeof radio_player_data.types[instance] == 'undefined') {type = 0;} + else {type = radio_player_data.types[instance];} + if (typeof radio_player_data.channels[instance] == 'undefined') {channel = 0;} else { - channel = radio_data.channels[instance]; + channel = radio_player_data.channels[instance]; if (typeof channel == 'object') {console.log('Channel Data:'); console.log(channel);} data = channel; } @@ -756,8 +756,8 @@ function radio_player_check_to_pause(broadcast) { winid = parseInt(parts[0]); var radio_player_id = parseInt(parts[1]); type = parseInt(parts[2]); data = parseInt(parts[3]); windowid = radio_player_window_guid(); - if (radio_data.players.length) { - for (i = 0; i < radio_data.players.length; i++) { + if (radio_player_data.players.length) { + for (i = 0; i < radio_player_data.players.length; i++) { if ( (winid != windowid) || ((winid == windowid) && (radio_player_id != instance)) ) { /* TODO: if the station is the same, swap/fade player volumes gracefully? */ console.log('Pausing Window '+windowid+' Instance '+instance); @@ -784,7 +784,7 @@ function radio_player_broadcast_action(broadcast) { action = parts[2]; data = parseInt(parts[3]); windowid = radio_player_window_guid(); if (windowid != winid) {return;} - if (instance in radio_data.players) { + if (instance in radio_player_data.players) { if (action == 'play') {radio_player_play_instance(instance);} else if (action == 'pause') {radio_player_pause_instance(instance);} else if (action == 'stop') {radio_player_stop_instance(instance, false);} @@ -817,7 +817,7 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { /* set volume */ if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { volume = jQuery('#'+container_id+' .rp-volume-slider').val(); - } else if (typeof radio_data.state.volume != 'undefined') {volume = radio_data.state.volume;} + } else if (typeof radio_player_data.state.volume != 'undefined') {volume = radio_player_data.state.volume;} else {volume = radio_player.settings.volume;} radio_player_volume_slider(instance, volume); if (radio_player.debug) {console.log('Amplitude init Volume: '+volume);} @@ -834,8 +834,8 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { 'continue_next': false, 'preload': 'none', }); - radio_data.players[instance] = radio_player_instance; - radio_data.scripts[instance] = 'amplitude'; + radio_player_data.players[instance] = radio_player_instance; + radio_player_data.scripts[instance] = 'amplitude'; /* set instance on audio source */ audio = radio_player_instance.getAudio(); @@ -849,7 +849,7 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { instance = radio_player_event_instance(e, 'Loaded', 'amplitude'); radio_player_event_handler('loaded', {instance:instance, script:'amplitude'}); - channel = radio_data.channels[instance]; + channel = radio_player_data.channels[instance]; if (channel) {radio_player_set_state('channel', channel);} station = jQuery('#radio_player_'+instance).attr('station-id'); if (station) {radio_player_set_state('station', station);} @@ -867,8 +867,8 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { /* bind volume change event */ audio.addEventListener('volumechange', function(e) { instance = radio_player_event_instance(e, 'Volume', 'amplitude'); - if (instance && (radio_data.scripts[instance] == 'amplitude')) { - volume = radio_data.players[instance].getConfig().volume; + if (instance && (radio_player_data.scripts[instance] == 'amplitude')) { + volume = radio_player_data.players[instance].getConfig().volume; radio_player_player_volume(instance, 'amplitude', volume); } }, false); @@ -884,7 +884,7 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { /* listen for pause event */ document.addEventListener('rp-pause', function(e) { instance = e.detail.instance; - if (radio_data.scripts[instance] == 'amplitude') { + if (radio_player_data.scripts[instance] == 'amplitude') { radio_player_event_handler('paused', {instance:instance, script:'amplitude'}); } }, false); @@ -892,7 +892,7 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { /* listen for stop event */ document.addEventListener('rp-stop', function(e) { instance = e.detail.instance; - if (radio_data.scripts[instance] == 'amplitude') { + if (radio_player_data.scripts[instance] == 'amplitude') { radio_player_event_handler('stopped', {instance:instance, script:'amplitude'}); } }, false); @@ -926,7 +926,7 @@ function radio_player_howler(instance, url, format, fallback, fformat) { /* set volume */ if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { volume = jQuery('#'+container_id+' .rp-volume-slider').val(); - } else if (typeof radio_data.state.volume != 'undefined') {volume = radio_data.state.volume;} + } else if (typeof radio_player_data.state.volume != 'undefined') {volume = radio_player_data.state.volume;} else {volume = radio_player.settings.volume;} radio_player_volume_slider(instance, volume); volume = parseFloat(volume / 100); @@ -947,7 +947,7 @@ function radio_player_howler(instance, url, format, fallback, fformat) { instance = radio_player_match_instance(this, e, 'howler'); radio_player_event_handler('loaded', {instance:instance, script:'howler'}); - channel = radio_data.channels[instance]; + channel = radio_player_data.channels[instance]; if (channel) {radio_player_set_state('channel', channel);} station = jQuery('#radio_player_'+instance).attr('station-id'); if (station) {radio_player_set_state('station', station);} @@ -968,7 +968,7 @@ function radio_player_howler(instance, url, format, fallback, fformat) { }, onvolume: function(e) { instance = radio_player_match_instance(this, e, 'howler'); - if (instance && (radio_data.scripts[instance] == 'howler')) { + if (instance && (radio_player_data.scripts[instance] == 'howler')) { volume = this.volume() * 100; if (volume > 100) {volume = 100;} radio_player_player_volume(instance, 'howler', volume); @@ -987,8 +987,8 @@ function radio_player_howler(instance, url, format, fallback, fformat) { radio_player_player_fallback(instance, 'howler', 'Howler Play Error'); }, }); - radio_data.players[instance] = radio_player_instance; - radio_data.scripts[instance] = 'howler'; + radio_player_data.players[instance] = radio_player_instance; + radio_player_data.scripts[instance] = 'howler'; /* match script select dropdown value */ if (jQuery('#'+container_id+' .rp-script-select').length) { @@ -1013,7 +1013,7 @@ function radio_player_jplayer(instance, url, format, fallback, fformat) { /* set volume */ if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { volume = jQuery('#'+container_id+' .rp-volume-slider').val(); - } else if (typeof radio_data.state.volume != 'undefined') {volume = radio_data.state.volume;} + } else if (typeof radio_player_data.state.volume != 'undefined') {volume = radio_player_data.state.volume;} else {volume = radio_player.settings.volume;} radio_player_volume_slider(instance, volume); volume = parseFloat(volume / 100); @@ -1049,8 +1049,8 @@ function radio_player_jplayer(instance, url, format, fallback, fformat) { toggleDuration: false, backgroundColor: 'transparent', }); - radio_data.players[instance] = radio_player_instance; - radio_data.scripts[instance] = 'jplayer'; + radio_player_data.players[instance] = radio_player_instance; + radio_player_data.scripts[instance] = 'jplayer'; /* bind load event */ jQuery('#'+player_id).bind(jQuery.jPlayer.event.load, function(e) { @@ -1058,7 +1058,7 @@ function radio_player_jplayer(instance, url, format, fallback, fformat) { instance = radio_player_event_instance(e, 'Loaded', 'jplayer'); radio_player_event_handler('loaded', {instance:instance, script:'jplayer'}); - channel = radio_data.channels[instance]; + channel = radio_player_data.channels[instance]; if (channel) {radio_player_set_state('channel', channel);} /* station = jQuery('#radio_player_'+instance).attr('station-id'); if (station) {radio_player_set_state('station', station);} */ @@ -1085,7 +1085,7 @@ function radio_player_jplayer(instance, url, format, fallback, fformat) { /* bind volume change event */ jQuery('#'+player_id).bind(jQuery.jPlayer.event.volumechange, function(e) { instance = radio_player_event_instance(e, 'Volume', 'jplayer'); - if (instance && (radio_data.scripts[instance] == 'jplayer')) { + if (instance && (radio_player_data.scripts[instance] == 'jplayer')) { radio_player_player_volume(instance, 'jplayer', volume); } }); @@ -1168,7 +1168,7 @@ jQuery(document).ready(function() { } instance = inst; /* <- this is a fix */ if (radio_player.debug) {console.log('Trigger Play of Player Instance '+instance);} - if (instance in radio_data.players) { + if (instance in radio_player_data.players) { radio_player_play_instance(instance); } else { source = container.attr('data-href'); @@ -1197,7 +1197,7 @@ jQuery(document).ready(function() { volume = parseInt(jQuery(this).val()); if (volume == 0) {mute = true;} else {mute = false;} radio_player_volume_slider(instance, volume); - if (typeof radio_data.players[instance] != 'undefined') { + if (typeof radio_player_data.players[instance] != 'undefined') { if (radio_player.debug) {console.log('Volume Click Change: '+volume);} radio_player_change_volume(instance, volume); if (typeof window.top.current_radio != 'object') {radio_player_set_state('volume', volume);} @@ -1211,7 +1211,7 @@ jQuery(document).ready(function() { instance = container.attr('id').replace('radio_container_',''); console.log('mute click '+instance); if (container.hasClass('muted')) {mute = false;} else {mute = true;} - if (typeof radio_data.players[instance] != 'undefined') { + if (typeof radio_player_data.players[instance] != 'undefined') { if (radio_player.debug) {console.log('Mute/Unmute Player '+instance);} radio_player_mute_unmute(instance, mute); if (typeof window.top.current_radio != 'object') {radio_player_set_state('mute', mute);} @@ -1249,7 +1249,7 @@ jQuery(document).ready(function() { } radio_player_volume_slider(instance, volume); if (volume > 0) {mute = false;} else {mute = true;} - if (typeof radio_data.players[instance] != 'undefined') {radio_player_change_volume(instance, volume);} + if (typeof radio_player_data.players[instance] != 'undefined') {radio_player_change_volume(instance, volume);} if (typeof window.top.current_radio != 'object') {radio_player_set_state('volume', volume); radio_player_set_state('mute', mute);} if (typeof radio_player_sync_volume == 'function') {radio_player_sync_volume(instance, volume, mute);} }); diff --git a/player/js/rp-footer.js b/player/js/rp-footer.js new file mode 100644 index 0000000..e69de29 diff --git a/player/radio-player.php b/player/radio-player.php index 801fbf5..7905e79 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -8,6 +8,7 @@ if ( !defined( 'ABSPATH' ) ) exit; +// - Set Player Debug Mode // === Radio Player === // - Player Output // - Store Player Instance Args @@ -38,12 +39,9 @@ // - Enqueue Player Styles // - Player Control Styles // === Standalone Compatibility === -// - Output Script Tag -// - Output Style Tag +// x Output Script Tag +// x Output Style Tag // - Validate Boolean -// - Escape JS -// - Escape HTML -// - Escape URL // ------------------------- @@ -167,6 +165,30 @@ */ +// --------------------- +// Set Player Debug Mode +// --------------------- +add_action( 'init', 'radio_player_set_debug_mode' ); +function radio_player_set_debug_mode() { + if ( defined( 'RADIO_PLAYER_DEBUG' ) ) { + return; + } + $debug = false; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) ) { + $debug = true; + } + if ( function_exists( 'radio_station_get_setting' ) ) { + $debug = radio_station_get_setting( 'player_debug' ); + } + if ( function_exists( 'apply_filters' ) ) { + $debug = apply_filters( 'radio_station_player_debug', $debug ); + $debug = apply_filters( 'radio_player_debug', $debug ); + } + define( 'RADIO_PLAYER_DEBUG', true ); +} + + // -------------------- // === Radio Player === // -------------------- @@ -191,7 +213,7 @@ function radio_player_output( $args = array(), $echo = false ) { global $radio_player; // --- maybe debug output arguments --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( RADIO_PLAYER_DEBUG ) { echo 'Passed Radio Player Output Arguments: '; echo esc_html( print_r( $args, true ) ) . ''; } @@ -240,7 +262,7 @@ function radio_player_output( $args = array(), $echo = false ) { } } $radio_player['instances'][] = $instance; - if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( RADIO_PLAYER_DEBUG ) { echo 'Player Instance: ' . esc_html( $instance ) . ' - Instances: ' . esc_html( print_r( $radio_player['instances'], true ) ) . ''; } @@ -252,7 +274,8 @@ function radio_player_output( $args = array(), $echo = false ) { } // --- maybe debug output arguments --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( RADIO_PLAYER_DEBUG ) { echo 'Parsed Radio Player Output Arguments: ' . esc_html( print_r( $args, true ) ) . ''; } @@ -280,9 +303,14 @@ function radio_player_output( $args = array(), $echo = false ) { // 2.5.0: added preconnect/dns-prefetch link tags for URL host if ( 'stream' == $args['media'] ) { - $url_host = parse_url( $args['url'], PHP_URL_HOST ); + // 2.5.10: check for existence of wp_parse_url + if ( function_exists( 'wp_parse_url' ) ) { + $url_host = wp_parse_url( $args['url'], PHP_URL_HOST ); + } // else { + // $url_host = parse_url( $args['url'], PHP_URL_HOST ); + // } // 2.5.6: added to prevent possible deprecated warning in esc_url - if ( ( null != $url_host ) && ( '' != $url_host ) ) { + if ( isset( $url_host) && !is_null( $url_host ) && ( '' != $url_host ) ) { $html['player_open'] .= '' . "\n"; $html['player_open'] .= '' . "\n"; } @@ -299,7 +327,8 @@ function radio_player_output( $args = array(), $echo = false ) { $html['player_open'] .= '
    ' . "\n"; $html['player_close'] = '
    ' . "\n"; @@ -315,7 +344,8 @@ function radio_player_output( $args = array(), $echo = false ) { $image = ''; $classes = array( 'rp-station-image' ); if ( ( '0' != (string)$args['image'] ) && ( 0 !== $args['image'] ) && ( '' != $args['image'] ) ) { - $image = '' . "\n"; + // 2.5.11: added missing translation text domain + $image = '' . "\n"; if ( function_exists( 'apply_filters' ) ) { // 2.4.0.3: fix atts to args in third filter argument $image = apply_filters( 'radio_station_player_station_image_tag', $image, $args['image'], $args, $instance ); @@ -336,7 +366,7 @@ function radio_player_output( $args = array(), $echo = false ) { $html['station'] .= '
    ' . "\n"; // --- station title --- - $station_text_html = '
    '; + $station_text_html = '
    '; if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { $station_text_html .= esc_html( $args['title'] ); } @@ -368,9 +398,9 @@ function radio_player_output( $args = array(), $echo = false ) { $html['controls'] = '
    ' . "\n"; $html['controls'] .= '
    ' . "\n"; $html['controls'] .= '
    ' . "\n"; - $html['controls'] .= '
    ' . "\n"; + $html['controls'] .= '
    ' . "\n"; $html['controls'] .= '
    ' . "\n"; - // $html['controls'] .= ' ' . "\n"; + // $html['controls'] .= ' ' . "\n"; $html['controls'] .= '
    ' . "\n"; $html['controls'] .= '
    ' . "\n"; @@ -378,29 +408,29 @@ function radio_player_output( $args = array(), $echo = false ) { $html['volume'] = '
    ' . "\n"; // --- Volume Mute --- - $html['volume'] .= '' . "\n"; + $html['volume'] .= '' . "\n"; // --- Volume Decrease --- // 2.5.0: fix to attribute typo area-label - $html['volume'] .= '' . "\n"; + $html['volume'] .= '' . "\n"; // --- Custom Range volume slider --- $html['volume'] .= '
    ' . "\n"; $html['volume'] .= '
    ' . "\n"; - $html['volume'] .= '' . "\n"; + $html['volume'] .= '' . "\n"; $html['volume'] .= '
    ' . "\n"; $html['volume'] .= '
    ' . "\n"; // --- Volume Increase --- - $html['volume'] .= '' . "\n"; + $html['volume'] .= '' . "\n"; // --- Volume Max --- - $html['volume'] .= '' . "\n"; + $html['volume'] .= '' . "\n"; $html['volume'] .= '
    ' . "\n"; // --- dropdown script switcher for testing --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( RADIO_PLAYER_DEBUG ) { $html['switcher'] = '
    ' . "\n"; $html['switcher'] .= '
    *
    '; $html['switcher'] .= '
    version ?> title ?>`pMCBCEK*&!O<@_J|EKPn`9=gNx-tj4giLe6jszJ&w!*_%L_ z(x-visAF(V2f;PC1=l?>3r_-N{ast#-odTj^W7YmxA)@=^j?~tL;M&yh9OVHIZPuY zxSxZ2HzvN(QE!9u_QU`yv>wwhm^d-(lY^Y^8=DW7FLkiTkNxhu%X{Cd$Gzxi5S%m(gsY>JpE@_1g+|jpOyBi-;5PV}5p@vpvjK5N z^>QShs*^3AXld>+CSFt~PF!|o5i*TFxZ1K!nq#osx4m+o519;Q8+^b>x*+{ZHP_jz z-*87z%}Bmbo8)XZxn~I8-=p$n54QrKWfRAiXKU-_w3ChE15O0vUhTdY!?WG@Zi|U% z3pZi_=EO-`tY<=lM{m%5rioU}X#~p(kb!6X5TyglEE-yYIp6;5KWhU1|Mz~KZq>fp z{Bedg1NoCTJ{c~vD7&fqc{_qVRW}Xoas9rZL_2nPcFdj=orQ2Qo9v(a;_mX_{rMkW z{@iQfq^;OnO*!k~E&aB1ymTvj;eJl@o6FAWXTp>4ffF0^SG{p>vCzz=Ix5K+PvBX= z*g1+oLKg-LZqjxUt;@UhwZ>2BkKRv&-no#m`7 z(-`s<6*s{{I?^D3Hx+0)R(t`q4h+;IvlY%0Tn7QkOFIFNO-V5cw<$-+K=a|i)5$cD z(L@Yp-#PnE_V;nxMeV;|{*Y8$KwG_y59xk$(oeF}ZS7=H$^ngmM;$}$;>{O;V&e^m zp;*hm-;|bf9v+2+wu~a`biuJS7&-x4cQa4miR$?D#WS60-}WNK7gSodfAig)K=aJS zvzjOM^75r+`GqfES$_00r~Z1L-xZF-o8W|x+FW1H1{w9zbbN<-%(QenAdeYnr7QClu3-XB zoAw+{uujHBwTqq4e1(_l%Ld=F%pK0d!>SrQM%2<1PW%CO^)&#kn8mQS;X3?`Ax*4= z>1HeX>W>E<`pRtBzI=W;=8XCJksvyrIeQq~C)>vFXMf`Pyf4auI`b5MN4f`pMUDhd04Nm7Wh!RS`9cp79Fzg6m~jkNF^H%TRqBxDY&9e)p>3m z&N*0;m1D5HF4A2LfH30uR{3-FLq6}+_wcA=A%q~B@e%Z%^tOF6$v^sbhi0<|GjbY*~fok&Vz{{vA^HU;%ZOV${WA(%F{PWvif)W zWRlE39~1u3t3UhuPg0h@V3X|!1-n$|u|jLx@}hn2SCNZ5jrVT$?D42^vwktDcOb+% z3W;qlhvA)L$4+HJ?xxzp&lfKzTdtg1UVGu$<d`W~e%B&Qo;j#Za0KD$4AR^?xQ9@U?+|xs5jH%X@$S8Vv2h4D=E|TMz+Fdy zjgvZJi*VOcd9=vd9~T<}J4}G>SKe{>$E@|uRZ+tb;MsUWg9$E<|EhP)WLkFMohaWE z_#FH*JcC#KLQMgyC)~Z*R_34op=J5%Z>Q+ghWjmRR@04m*suJ`uP(pv(qCAWIZjXT z)lu5*>QPNce^IaPvU9VX|MZ`=rB_6O!%^{V=Z%mHEO^*zrQN^sm&z1`9aS}r!8mjl zZZ{oT2P+TpD$@Yikd7CA2B2~c!f=%yIvBy?N;-LZsmGcVPsdk2N8Y+4hnoijDULX4 z1rw!JK56&}lury;zu+>sQ+)63`ZJnGy)Z=IJ8)o1`e)4uFssp0WbE$-bUQA+m&9PV zk>Za-uGKQv6!<_ACQBvf2GkbC?I-oD7~5>%PR6j=@!}POXIJ2Y+RXJwyhT!*2W*zvbyKtFnbW=L(Czk&_~H-0wEVw*_s!*IZEP9JP<4w*m%slj|7iJzfAPl( zyN`y40rV2W)eWQhM$`1P3aU_V&58foKZ$s2u!tqLk06?{q05;1virla8xon_ZyPRq zk%{TYU9FJQ$+)L-HIy%a!F=`lDh-0w(2ARH!!Y_iouZL-gbpE*&&81pPk2;HPs&)Z3Q19#sn}2jDWwwwI=rUb6y_(gK@ZY)%rR}%|G0^Qx})N`k&-g z4>t$L&<|_tpgqm*<>=dO-oD9`&PZ&R-GW~JLd5 zCbik%OhS2#B|JnA9KsM{0)M10F99b{{W}B(mn;Aqx+$&v(y7xRAbgklr1hR~oY{!d zV?NByp^AEoDXu19NN6Xol zUoP#t<%RcvtX}2TKaPH1{V2S3`JI27w69RnaAWRJa<0 zAXO`n&p>BT-?4l|$MI$9 z4mZI4Kiri{WG`g_N9y8;Vf3)^hoY2SUM&|8GMun7MijzOW%XWx>eeySNk^UKcYkus zJbmSI&AqvtZIC}|u9f45fZ1<=WO2=O+%T&w`|?q}h3ksibA*&tch%nSPMrx}8p5!J zo|`eTF((2yvQ5b;4!qrk_M2UKP+#q);h1(j7t`H-W^?&|N0B{=m@b~mcCHCevLEkd zy4hP$J82@Eje0%sDue)RnE*MGKCbALFzJFnlvm5l_+op8a}k%ABP zP&dNo&*2$&;3a9vw`4qxn++2OU;F!Hc!_uchJXjx$_FP9o?F>WKi`J%aZS%U}P=A6(H0 ztpeU-)MbU3>9aUp75w2JFAu->y=C|5D*+Ry8_ZZGdh}82*Ln6o^J)wtt~=7XZyIy` zgBV7GNc}TF!wsu|0}p}PIt&402mq#T4b}FTPV5_-O2CHB!frx%0GbD z2?k~)PWekd4Z|U69L$O7`1K&Dx3cD4iKWM=T$q-24AJ)im(&(%sC9!$!NshQ?!m3) zGe7uBgZAxFMl@l&ffU)r876%xd$4(XR3`>N(}osPoHmLP}w;wJaH9KKC&=T3l zA=K<1Tr)5ro!FnlWVBJYjIuLt*R`*nYl**pE1&C=+JRVtlQDn^h^mX|7*-it-*CKw z&ooo9{rKkc#P*D#2Uj^fhjtKZww%9T2t$*`@DjE z7Gz~Q%*L@c-sk*F0~;=EoDB-5uj3J<24cn}xNq=l{0A!NHm)tWL5Ozt*@P2j4HT;k zw>&PIq?_i50U+LC5dEzj<%0pP4>Kr_bVGE#^WNnt-I|F-H3xJbGI%ga-CDJHfw6WmGM(a*VH+C z(gnxV@1LU!1Da(0Hf884>s+huUj3TYe9iuB7mKh(voMBrcVc1r;wxvDFaFTe%jaG` zzI^$M&$dB-$Gkv#g zQ*$fnf9^sy78M(i91nHEL7w*w0T4g*Looz)Sq;)qY6}jk^K~}jkxr+MUHU zA*9OHVQv6;XCOoHk-jAG`rWw(NoV@K_9vLD#{kwHN?I6rounT5`73ysI3o?#>_6By zGX&X2GdS`Ks4yUqVan?rWGZQMRy`|}>7YH{8At=AF26g^;Ip57Zh7N7SC{9XJH6a& zfR35QO4^xD+w7j*cKxnDhxlYZYPX=FC%vEPLf`8X8-BQ+)Na#fC+g$bVyf5Ey`|a7 z$#wqOi_49Vu64jgwp}>>OtUsS1zH_zfB5gbe{;Ega>K|DBX^f7p_227AzxDe+{X0i!LSsMm{c;t5;ZwWIPkrX8t9PyGVSKnLdQvX0zgQH%;cv^HEeIOTS-IMb)|jjE5lE)Y zb7Fq>Q%zBSv!%$DZ5bzRqs2MA89J$02+HFa`wT;#e2~sY`oUb5|-C=?`dXzQ*ykn(MR= z03xIT458K-`OO-rORvtrH!zkD^HDHYI99+h9UxhCyCIF)0Q(eLp?EUH+XzWGQXHPJ z+j~gW8tQSNP#qCJF08Erf-DMAFVTFq&L1g56)b<7dUjGr@K^GX>Sd35S5}@>}ClKstUp6K$>s0UmWqIzE+TC&3%gyUmodi2z z2I~JBXX<#T`nSU)YvH&1_Mq`=W?@4WQqm05w?x+Tps6`{ zMlkUYzyqos;%Wz=FW}PgL!7+dZR!m-|1P<(7#m+sFIXGO1 zNcnatqBilKB2=Dz9G7m6^_6K*Fcxs=II!X5cRnb~ z;d1iJUtFHQ^8YSCGIc9N+>M#tDM({Gf;|y)TdB`Q;YIMrYug^lHhosRr~e`#Od(>l z80L6=%TK_0$mYi+*rLw!o0$EWtS!gpG~T{%#t`>u>cce6~qUy|}#oX5-O$gUglryTwzZ zQmz)BgcT5;%0a60I3}r~z^Rw6EXyk|t{Q8fyqROKq7YyJN2qfJEHxgnkLumuJhJ9~ z07x}*>&#JMLJW0j@P8trf;Np;+ zP7CuPw6s7K{yVT#4g{cmU`}A2ppW31*@4QJUz_@W$~NW1Nn}rez@2!77|!4LZ+~I= z-~GKmD1fYu86sX%^>{uHk$4Z98#a(n;^vQH5Oa`3Sx3#T_{h9>h&zd#)FACw+i_tG z$E#+A2-3(ii-l?);ULIpGpV>0b9t0@bo;^0Ox|;?33w2gl>N$&*%b4av@h5wM0k?K zGj*)vX03p=4b?4#*@CWtuRi5_uqD;Z?Z;3iZX_HzTRh0!`tM2S(H(Yj?Y_MWKDxhr zzL>tBXbSSLKL674=+}Oulab$@`VP`aEf!1%`@<`GbcZ|_FibXxm(ri1(S^-g1W4$;+t=X+vM2()nHWi#T^v5nit#qj>o zo#jbw+oT=XS{u>LRB`apB)ax9E4G4X4)x&YI+rHEOmsCa;}G|(e@>ts^)&8-RB&>T zt1++vf)5kU9-*%VC1&9wyf7T}g8{9X09tS{Go(rIo@`m*3zysV>Gj(0bKt1y^*36- z@~yX)3opDDqqe*cusuzCy%E4sBQs2&bvB}LF$qoFe5o@ zgifL%kec3)Nl@9C0N`huu)Gy9^-L%9x3mb@1|*ZOax@Mzn6mpe(!WM>eBk6%Ae}p9 z^u0gFych;i9Xb)C;y{u{dcQHH5Ne!xq`mi3SKoyDy7YlO1D6-Q_q#3{74G7N)j;GE zdFfyJ#pSjC_z#wM-Y!jHnwx0}w%VG@)KuNSDlVd~NxEXQdU)ay_3C%wyGi2Y`^uG3 zRcnXa>TLU7ge>I>q*VI|nYaDWnC0kkn1$ar({Qe3@3pV!XqSjy7q;hce6}sA>pOgH z{125zjYmW-TZ~{@U=+ISV9PuZA<^*`BGqSA^(ba~SPa6wLTVp;u)ln&=6xwf@Jr9W zw7d$$w{OiphC2_=Etg{mZ+-A?=ES*7=4V&%qOGWAuLKp>4;yJr7jr)E zUw^zjReD5!KQ-7v+8aGjwWQbPr4O=mA4kadQt2<8elDh3P}BJJ<@{_AZ;3+&bH)^i357FTyWvmxwLyrn*B+!j6B>L3o$4!M zbT;;oZ=C6+rybBt1fkJXbw86Cexobj5i^{GfAH8CK@^uKfr#kQo8Rb+pcF6_GkNy> zPP-vLoOMou!m{YvXm(k?@y*wlpZ@vJS9a;psCbG50CEhWskkeGdzdbTo0O%@sF`1O9kaxKl_k^?HMb zP<>SXj~PHr)5<;b`l_#SrHvd5^jI~MQ&4l7C8$j#`CbD%1gqT*=t1qcl?r;hbhWY} zN|gn6{c1jbtJ_8}tJR8a2UhR)S1coN-Ncy}`W zPUN2u^M4{Az(G-tk2Y_21a?|UCW(`C9>mCI&%6dk9E01ti)j$bxshhLolnC)fIBgd zjTr6jPA4+we|dEOz1rJ!Y0P3bE%K=oPc84?eZT0+Q(34n+SdJZeLlEW_;oS!)+g9` z_fF=(<1`6>U;VwW<)YYMzWg`R1Q6tjaNwyIGdbSwHyni%-JRbsxPU3-h&!y^$9zYO z1`Jam)oGsD{!hY-O&ZM5MjcNe;;gh?v~(xQjCNe`a!sgcPkEwoT^xci1$RH9*%e(? zce>xJ9Xxa4#I5C4XnHsQ&9P!gY|;OXZ@jtuG$%eGq7H`$L!4q< z5R|3C!xSp3L7uhwz8etf<6LE4rNhbke+5UKseQQS)KNKRHq_gzQQhxEB1`~nM)mYtvb@#QCf?YEZy>_5NTO6WWJVwBl{*Xe?N z#0XI5cVZSKKZhW?wVl<<93b1x>~}TjRQ)0J`T$-$seN-?SI!><9r;9@)b`0y9eE9s zLN|*dyi+r4Z7nKQnQh-t%;Go*g&%K+oI4?`aNNfs?D#3t>=4-;qg|Sha_l{39t6i` z!6z3YlC7ler!z60$vgO^OrKx;>}Mu)cjNX4%i*1CX~%a8ki49K>~_c2H(j}Tx3HcN zw)ULrFb=Cp_d8Vew2e1Hg@e0oesZf_k0a)-aK&LMqAG3n`5@`Fn{U8u$YGNBN&R$R z1ZInUFx7KuVA{T&^gbUGdiULTNA-VvtOaWA3belw&bCTWzudidJ)%#eNozb4?wpIj zXjQi}Pfo_@?lpU}n|aHZy_Wf3@$4qH+Yzu;G_Uxdr_6-D&fD;%tG~MI`AAu4A5R8}s!I$%U zKT~cC^MD*{I`P|$E`++nYjl7)nnf`!nHH`RLMv#(}vr{bDgz}duIV*-Gf8W2f0qER8Ds)JV{VEs)x z`ha*f+|-KnGyw=Zm|v!~J+DC;qaiwmy+suoNgbwmng%izgOHU~v6FUG;C_Q+-uJ;1 z0|7fGMQcIG;86yyJX0#YH%&?7P_Os;6vd4hQ~IL~6Grt{u5WIpN=P}&Ea_Lp-T$?J z^=~cTf9pT!#Pj`3M1eij@<)Yop9>@_^k@LpS@IR}`?ET`julF*Si8ZFDNe|Kh z@LW6oB6yOYlhB+y2cI+Q$6-`+olNsl_Uhf5j33e{c1!zI%>7hTZ^!Zr>_pfXQs*zW zYEsy*gGq(#9*WgZLaFa@4C>wXZ`g|QJr#J*BqhIe_QLX9Q=*rO=DTw4bY^o%A5njJ z>sm1oZH1mndl(X8V>TT*RUnpNrBlu3JWf;j=-Tz=WDEH3}E1PEEd4r1tv)&68QAqd+!yYes&n)+wZ*7ke;3Q_nHg8o3l+? zCIkkt6~5k2E8WStw-cd#6fQ~gI2e_;A3;6|=V@lpP$>4jJni?x{j%#K>jjPM`YiSMXTaVvq)#8Pd%Ne%!Q+57XM17_)G1w8g zwH&E^vs$_S9bW<*o&76HQyT%WML@2FBzk~z0E4?|gf>p#y1bWWX4%T?eGJ`Noy!-B z4GI0xG=ULSPnN6i=LEbLf;<&HnRRI&K>{z`v8{?rvteHK%9V&F!jQXx9-~lYY;O^E zBqXVb8LWxiv;;qb6D&x5fFk(jqo)yTE(l;!7c;@(G;ma&%0lQk99Xg6Sr~Mbo+A%D z^3o;%OTPslX5sc8XT!?;l002M$NklvQo+ z%agCZk<>njXwJ4|HI#~Z9j}hv%Dn&hR;ERPEw%qaYt{9Q@MccGAZvKibaZ)3qJtksK=3LBtj4Mpt;mYx(`EKnKNAW0)VQcSHOB8P9SBSu33fr}H zKcf5So41zxXY;?bQt?)^K*w5~bLLW0a<4qSoO!8PiqqkZY420rv%aBzgI_VA5sI~O z1duRbbT!&%IORfP1SfhrQ%Ax<_(pSxp3KZ_GEL2H%oJm0NMpvAgRcl2vgJDze&=fc z6wbd~?rD#Jc3-Z*W%Db)c;PF@IO(YVr1pf-_JpelV$|(^AX*!6bdt>g#;G<}Kp|RO z4RM)nlNoUhAOTF}9%55&zlXSv%3O0gA;O3Y448yGz7fsp#)L2mK^;sCFbmc&7tAq8 zCD(GR!w@m;<pEl?W?LG^!{FMA&<>*3-TLD}8q;1X=&g`G ztZkq|;L_pChv{xx!MRmD!C~RDXOiBBO(pI$y|hzdxvC1f*$$L*g#kZY{;O-P49tAl z%YNN1$m54Xh#xr<1870btLIKFpGysYF4g}BlDOwH$zF)q&t*2e6k@*ET>78O-T%{1 zpI)AAO7BNH)8Xmbc_tXo$H3nYagN7K9!Ejv^5So2J09FAroXb@efMg9t=(n`*zZjp z7nF3f(DA39>Oho`+C22}>be)+*u8fz#w-evRDKYHzI66dU2YN6DjJe1yjyx_htmRCjcHb-rM!%Muajm7vajIn7++HcX<6%yDgf!nRaseoW)h0zL17`vX~Cq#>vKe zKQ(-9`rK+M^~KtAulRzDwHYG^vYu}T;(IyicAGL5rTMsi+lYDXMeq-+gH3%Vg0w~h z%I|KqZ1-;Y+KO!dc23$BFzwykT;BZZ`^)3B!n@y2JGoYy_rt~Qn1aoAi0Q`ZFm?Q} zF*?=>(6cX^Z-f@pocRma%s$OZ*}lydP;@KERvmYw$;kXPXUyDzDE|KJJLg zU<6btA&|5YS_CzoEd@rzbUPw0NM_6c^$HHMWK($<8r4Z#Z3LpT*=^?Ot!(7WK)iL_ z$^_}DgU6ZE`y;|$wWap!BWm8Q*O%pIe{lI9{V#rL`H%ir|6sZCR<>_(`DY`bo2?(% z*uCF+k38A6^)NGJzv;1?ckWenwyU;fw%|p^e>WnxTg*<cOaVR%+JvaVQKcgQs`5wKvz%nl6nx{U-)0sfB9@N&DbKB><2Wy4PQE*2 zlgh13zn$FcvSrqkPcpk2yV(kjnNvD96`sRSSgyav)Tu7B0Z-)vco0LU-f|j($@5{3 zY^Kk4gyRO6TYd8V*$b@h$N3u6N#@LsN_-+QW+ww_!+vng$-^|0U^p3ba)Pe|U%_T+ z@b>1)!MPpI@3y5Ejb<|md+$aJDF^#c4UUCtqt^8ePMdA8q~13|v^bqfD`1UNk_Le$>jOJU?Y1|98OHr!xt~Uq zzB=a7Dt|xJQ@gj8&wVDT*$+;z9z#r+0HSgsY=73+Q8N$>!Y%VWr9*h6wZBcX0aB;i z~ndI28W6=I1o7&X&Juh2zhjXdP#B~{86E502NB( zoWG9Ih%2`xluiSqMfMHg)TK>`UZhz?&rUJCvMvlzhto)OjJ*0~684~BtW=y8{7Hf3 z{kPjF<)!7n^YCYuul?Hhmf!l-w{k??YL5Ow4zk*l^m1b5L#D|=%V%T0o7rNIMJ1*w zuuFIO7=rI?p7M>Pn@vZzV|r(UlQRD#TmF3QJJsP4kL#!J1-x(NKMg&JK3t&I>hDtsq^v0ByoK^gxvV@ z+&_tFKh-{u2ol1dOS0ZeB}H@y2Z5hTEq;=jz&>{&ydmD3E61I5d*wII%Gqr$9YYXc zH1ejtwKe(7TGS@aKIJPHEXwi(-?Ps@mm0rfEVGI-n809sXcder3~fJ#IVb%>4|Rn* z$^ZxL%U0Vv!LgAr<7@EU1&)>Tw&~OG_HpGLOK5DT={>P3FRgfdCsCIiE0`OL>R_hq zHcpT8tDMOG-^%O0)%HA_X~jF??f$inf4knQ&1aqDb8C6wmB-7a=h6s1`)spb2_x$r z_^3j)!QHzFEgCgCh|!NJ03YqxMW7Led3q0HzQ-X^^hK};K^ly}HBvc3GOr16J^)hL z(OMAQHb$5NE{197=;`drS1zvAt5NAO3X-A$940{{nCX%dU_{cJGxb!SCrP4QE^n|E zqB~j!MnHQ3Q&r8aUa(IvN9By!ARo9h9h9e2UGgAUz+n_j5$XJ`9;(0cXhWk##6;{f zhiUxGmzK}{_~({SfBv5=|KR`j&)O#OY8jL2EqvLJSskPX?^O489`h$niydZW?4)t9 z^H=NipX8-pZTGxG>M%uGKTlJ5x-}a#!k3 zDi^^cZcog{-|~Y8V^9u10K(_IljmO3WC)ijK~iG`;F4E*d0m`@%9U3>-?R&%(=-tH zVK}*xhn3NI(+uE--F;g-IZjO41{`^q>Bb8`%%t%#n=zzOe>ld?X}>26Z?>T8M81su zf^eSR=_Kjv?=SBZUvTG*N6V%6TD{p)=;P-%m*;-q)rdVNz^0!_#%QUn;bvn*5xJ@P z4P5xXVzxBx*>D7X1QPr;D&~M5L)zBViFE=fvD4 zKnw^0($~3j>6wscMOb}A8}fOfQ>e;*Vhq%2+!-TKhO+#2AzF7GK7?m_5m8I4uGFgW zin3n}9ykY%{zee(jXwDrz!eczPJ_L&{{hf<0T%*UXd#U2265_;hw&CNjZb4>)!tyx zVd{5N&HauTr*k2KcG6g;!@=2ae`^ax{Fc1(RqfyY#pRcO{zsR)|M>Tp|K#ug(elps zo2ui&C|-b_yIFag2e+2X5#(m!wdYz7aI(Z9kbrc=lpZbCyG2YMf{` z{1jJSTuy!Gjcn`tvxn)qY~{!I50^_p@tK{cmcwfw%@p=d{p~Q2^X)-*D_i$4q&$_{ zdG(FACLDL``u*i>ehJM$2ndXHCbiw{beaI^?l%HLlnBs;z$Un;Z?=kilv7!N(VRZi z_nnArFS~m?TXvQRHwK7VS;uo;DNnr(=&0%l%R4tXFb{S38xd3I*Ji=(d{S!y_8#nF z888Lf1K8y;%}R|Q#?aV%D#NXAut+z)fyTrWjF|>ZEz9%%u~&Tf0uh%5zwl3N*!r z4F=2rKSW&~(vovSx$E)~iysKR!5&8&M`RLV2#IZOMnPZ@FFJDot)WPafk}aYEhO@r zWS#(^V8Q98J&3<_7yxFa4G3G+Iyd`wv;prh5ir?}#)YYA=lUAr?|ZL@sW95P@Bg!F%bVZ&Xu0`L_G7C5-6G)5W}}^H zP5b#&^_QPI-acE#XOpYMFRcW>Nj34N+|NN5M6i|sM^DE0qb8rq#CD568y z2m&!71ZuRHa>PPneo_*z2@E0Vf~^R8tG+)=#jn53NKmz@*~fEej@lnCAqZt7u)XRe zsoB=I(p0u{VkuXB;P51|*Y&TMMMKtm%nv-?t>KbKdA_S}C!AE5wqat*(l+gKTYF(y z^{IrFJZ)w;7Y#xX*C#aveh&Tt_CGU5S;`l#eEZIaVH!V1^ZhxE#d%m4vYCdZy=Pi* zXOmXUF#b7a2kqxV()?jF8?T&sHRt7x^}W3sO-tXfn2L67ZaciOzNL|g z7^u-~X_5?qLa_cEY1+?Pbo8w>{&va7`GAU|n|}0;r0r>`A$1-jp~9P&XM-ayKOaHZ)_qkssn>ik0rih2ACqe^)3XDxv50QA#jX^ zxSI|GlXU(5*uF14CZQa8nH=C%mdlo7)6vTFzIRNfQ%|oLpZ)1SUbWTt&6t8f7U$6a z^cR*_{>tw13ZKN)^4|%T-~Dbxow<}G9*wEfUzb}kyjNZ>6#K14v9dqBy86Z|Y%8{l zF_(9)EpN0v`8%BtU+T_>i$Hrmm1p>DTbAAJX!5gdUiwk9B!!tIkt`IV3Kn-S@-uJ! zg92@Cwwh5y9hLD^#OLN%vp9%@3qiTA#e@-%xqe->Utc|9%{#x>`$yd)4Ko?; zhSdDMh){XY2D9}MBc)@i2$sFQ7qK%PyhnhfJ*i4sA~3&4iiex>c!v>r??znSJt_~4 z;&J6YjMyKhmV*TWV-N^^?I7FazRKJ}j!T7><`NEXQAaDZFa{;N#F{=qPrFRtOmzTiS{l zai<2G1wiZlA5-qTMM|3qeY6w~U`Y`}sMEK(NIVxufx#bQtwIDr&A6E=LQ?yN5Z1)9 zmi)61K!w{@Y$SFC(*$S;2!4pTd~9*V<@Prs!6fonhY{5mx4L(GMY!Nm7mjCIPZ@Pu zSmT41MKtBt&tmWqsbi7aFmpCG;n2f`m=5yj9cl!#A`ZQpr>$IRLX~NgSp|&2Z~lbo z7aiX_Z61cvw`{!8Jnlz^%`Y#fo(YDYG=sdc1zlG&>RmGcX@B^!Qs91 z59AGY#OekgnB6X#jrS{Nj}4Z*oAZvDfdM!%d96vvKkcY&8U^XEzO^}oVaanV=61(y zj<}D)kOf9Pwmo+aa8Le(`>Fr8#oL63d=%j2q*RCc?zOky*2N5!A{ieQ$@nNOVgGh( z7Vam>Z=YUH-@33o`rhX9NB_llmz%%#?QkbNIEau>w*U$4Fj0l!6K=71yFWp7?KPeU z4T90NN8|`~T06m<;7UKKgR?(}h_)gybKVFoNif8yn;?HHy9U`SaHN004^gVrjVYL) ztW##rlJ~wOvzLH*cjd4>tg7S4Fo%vC!CapDET*w>QUk|6=dGVQWG%mXaWM5@8g=AM zU6S^s`K3C;ROUom)lgcjlq%Z*Cn^yRoi?MvSHe9Rhu7mflFSjEc;lJgqdxWzT zpH$cECJi3v?7P<>Ki(@A;M|otI?TNA@#g%WJia>;>U2kDAKS|xQ9sB5F*- zGg?pPq-qDBDMcp4ZC?AvYGy0)MV&5S>Ey#R%kevH>6YZb+uZ*BqWT_X#6Qd#co?JH zZ0q&ihi8}T-?+Pc^*{aka{D*l3Lhf;t>?R+4zO^=z8*b4{3u^fXm_$c-%7BG)C^mf zJB|v=B%STfXxBN+g<6GtM^ddp{W#dC4(~&W`;HJtCGNL5b6P`#G}-{woo$a`gg@d) zA>~ZVIq}qODg%?~|K()mIn7Y{B9jC%@Mx0U4ll6um?ymrHfC3z)~BzY6-uIi6fxQS z6BFsg*qP$%F}{c-)JS;D0_?DBU_n4JFj~lpT7olRhOoh00f+CD_T8Srsv~zIxQ=Q$1|yh|M@GEz3sNujkj z9ibo?$a(eZ)v>on`lZqzPxBf_UIa?&(*&enOFmMUeeNPzltH@Py?tlilWgZAQmU?1 z3nwa1x=%{&$HBfAK2W{gvvFyu)k9jJNW(+a2%I^oO!d3eciha(jGGk?D@|nGCTWyO zwbv%_%R5r=h#;kPt-sS;T;7}CM+&=XDPUO}nzcNAi;rJxXO0TWeQyi4FahvH2XUB6NK3DJ7?C{%i%$dHU zDhsedoRA*-8Yf!>H&^f07k29=1t)zU7&H~yi^2c-rfOOS>0hVKlj^P9arOl~$J!8Q z2u`9y^fmJ#Ifh6gqO#9kZw8U3Qpe0i!|*pB2BIUy1tKvQvT;Y%M})$SXEvbx^8|10 z1)tq&l8otQ^;C6&Z{Vmr{b1_QQwS_1q~hBIl`1ww4yNGN7s?AU%256O)<^v|ttn6H z`10PHOZ$nf7}Lo#fEQm}mT!NjcUM+pp*@(w+t*^44K8NUs=MXo=VQDvoNUV*x7!Q8 zS{`MVsLgWvQ^CRpd^!R?h**UkBeKT@XdO%1UB7;Pi1o_zO(SL|oNNC7;gQXIqBZ(g z-+4FNHV=AQ8HTVX6v)c!Zi;X>iS{r_HpiRCT+XB|oJ?bU819@5VNHus&8f@K_(Ud% zJZ=Qx+jg679_F~*D}LjgV5V$WJ0yz|+>dD2=@5-u{fK$ZOc1B{b|9X!27J$Klg2lf z@5jS~$7w=_(i)15-kGJKQ7~PO*FW3Y>KMn&?>B8tVjF*iu3s&p(r&jjYcoQOgm-?g znUs1l2JlYbB{;!+Hj`LiFgGw+Oao)k&Vvq8If(Oc5*~Ie-ElM!lY0_F*{$w}X%&~x zTxdEsr)IknT}pGa-@#k&ytRD)?)R6CZ@jr|zSu5RpL-=6@VR7eD?^hRt+^UG`6PTx z*DVBw&eWYS$~EVUR>=E3ZrS^$YdRDeKk7a|>dLxa1iycX|_?NJ)ickJUh zs!H_SFoeo79wY}XB712hAXT1NR1acXmy=_n3GePQQNisamv?+_CuOE`O%OwWj;pia zmPV?y)cs&;fJY-iga%8UdD&-1z=^*cl>ic?1P1;9>1^15i!z>=BL&=y`2ZHP8<-;} zqQynjdTk`px9i&D5o(;dQSI-_0T1K1N2`*jK+=qAnWM zx4*rT@Ccm=#*fkhnC1D1UZ51FqU@H~GRf{Iy@E0xvr_+0#b|b`%YdJV3GNquyCza9 zyko&Qs5VmN*|Z26kx*MPEwp&ccfJEHyZ<~&6CB>-j@`2$&u=(JAws2f`!wb-YMU6q<-bEV+6zr zTx=}GOCW4zyQGuHtxvq4cZ~r?@F=*A59Y8Pp4skAe&r#4bWsuIZ{{JQwI>2|=s~N# z4mx`6?AE2M%KH7<#`4zhzQ5f5b~tn+hJVkta!{zX9{pkaHW00FA>1aUpm~glTeTk{ zx48O2>WFz%`Vh@@8WVx);1&np|5E@Cq98922*P9nsELz-1Av;&=5Ij9(dOtT0BAs$ zzl$R-h1hwj69v`Vcj`Fe>Hkshz@t8PF%7^p-Rwaaj3jjG(j3;Ay*f`0M7tQP9BrpeB zfPOX}d@IjAyD|rEC->{Kk6Pny1P&thpc$v%F2q-NeE?ahu85E=Pc?*qywD{0+1Br0 zxUgDYd#35&n{8b+5-CKL*L^ZA>Otej+fT9g9Wp~|63jV&B%V4Rre&-N4$)&2zA0zz ztn*u5YQ90*40gor=yKDB;s^LwrIE*_>>;{vMEPKFDa$3TcZf%sc3Hx3oTCXufBFYSdm!e1^9MY8rM$uW(#tV|7z*i&*kAwN$_xkz=li8g zt8bzfaAr0J9genwlfkAveN5lS%QY zDOJ&H+i5nlR=&PO01(!67khcT@$~n4I?xG3f_(19$d9F29M68n2&maxCxI9l3#c09LVQTO&hlu!2S_tOAoB^M$(5UvxMVM>b z9IOZw+=$C2rqq6Iopix4a2&ydLAVjD?{0mftt%yNHTCyN1^_1QQ+9<7L*WBJ+M*bL-~Da_hZE%k}qio+>IcdCT;8cyAO=qYtzh3a&1iqtky)2_Q}mJ*EIBOTY&0 z05n_!;RM`_Ac@^T8`!aHNqSB)YBXDWG=s{tP{Dx?cF$R>+N3G1z%(0fMC)j!)-ahm zV_=z{ul`AswB2?CK0-99$^O@h25UuR@*#!ZfgRzA>SL2TU_k(d-va|KB57ri#M-@4 z`NtxzC-sG%6RKrC-_EHP3@LL=qxz;<^}_bL5nb&!&%gUje}W|#Xe-lx@B|l?h$hni z%kO=4mF}4c{-u{zjBl7$jOCqo>bqch>#h1N0>@zA|DZ)um95{6N9y;K_1b4@Zw{`C zoNI`z+R~OF5y@;yZU#PrwIBwvk;)g_obqJ0Im9z}ej{^((~AvEimjb!4|49Ap2H~E z@-9ShB6tx!Tm4alD+T~TCnFn947K2+h?6~jFZJG>Ii_J=-mtC-P8sqd_}lkBs;4p$ z8ZYo8)^&XXS)^SPEv>-ch-!#DKf+DI9T((@yzlU+ZIp+5Sgwo*qoFfoysA{E@&NQYS< z{PlQ|p8~1OvJe{`+7(vS8TbQf=pxA=oRX;LB#DuW^ESGxdu9P5 z4m*wdmKr&_3#qR|NxC+G7}XhJ(CK)wF$3!V3s0vK_Zx%ZOgo*b&E}nX^qyh?%o;#? z(id#(Z4LFi0U9Z7YiVgUPlM)t-w+|G&s^Zwz|{QBR8XgKNoDOsJaZo1+ogMJm5wi9 z_xb9GDVScd@W=7vHh5^Whh;z5JB?@RID9v);CgUX79RuYx^J^0@$ zJ;o7%jk$6qMsV%J_)5Py{SZG-zdYKsb^u~_V5b%r3vlDwwWiWarZmXh(*+qxL24yL zgK#7HlFsaZ?-37sl|Any9JVy1MOY#4`Qe`Bzf4lU`!)1gB9W0IY*j@)ZNlxRG0=+ z1q0KaTM+_+w|S{=;8cb>5SDicOJ42e;}G1V-Jzhp=^1k-|J5#vYEYpH%3A35h#1jv)bZpD`9mi z=4wXZK^o)Z+GCsW-P*YuEPIXD7?4J zb;4;IQ04^b>q^QjZ5=g4TE~tPkTdRND!2$a#LRTyskD(tNZ0l&5oyGMz#tjtGM zTegefy$eZ4t!4&LeGnBD-i5J&l~eKFjehrRa7$2|gAcJQvw`WL5}a*ZIH(?l8@mO(+z#<X@*>>sv|nGNqVAT#$w)$VF$Ipl`|a5KsB;Dp z)<#p$PhG0c;_l(~gWK1Ook}$h7aoQum>)**B(09~QCz&f87E|Tbr9ighnsc}VmevT zsBbqggW6?Nk^_skx}CO+aggGBIVYb~_HOvg{6ft92sU*&))?A+1^%1iI31G(2hD@> z>LA!zq!awJda=IWsZ3j~+Zvvm4Vpeamc|tH78_xr=i6V=dXSm9sJ{JnqI!}kcsGXd zc64|AObj=?d$hk(oI&JSm)#G?A8!=I)vVTG{rzJ<{8}bt&SV>dWUF_xMn@SVh*G#NQa1fVl&iftjyBfxKpFMLRdCFV&vWSx4JMJ#KoTH z@M3pc0z6ZC!K4*7|A6og`T*DDL$sw;KIbChHUDYwFfGi$LLXNhKSWOQUktUl?8BFtc1&4z0=vp}+vyriUgZ+S^O_gaM~S*1Z#zVj=Pu;#>R6$plgqO1?-(;?HnD#1 z8>#KLZ`$xET2LNSkyBJaktaUjJ$O!}C7sF%Yuj~OzRk98;Sji$C5^w`%3o_HYv!RG z?VRYz`kaCi_g36yJJYCAiwwmELua%b(;aU6Fs=1u=G3tN z!6HZ<(jo|V4H&hV>P&(oDhP&95yS+kR4gRmz|)P01W{0{wH{G;&(;Y%m&wP8T&P*|y%PWvk#3K_N|Pf}}`6NaQc8mm6yO+5pg z_qEgC?E0~XYL_+z_T*EK{FoEQrwt^tJ~7C{-Aki&d&Csy*h4ziB6;1Os)L z0i7L#XzqQcCNDj=ELUR$VjwOj?Qb+FdFY3^(NKi_e$YGp8F5Vz5mP9qoqBbR)(rB@ zvr~jplM$52wA0okbO}7USHRWmj~CICZiwFw#&=pXK*hg#<5tbcp%wyS1mflQ5pjQ; z<45G?@e|ndJZE0Jzt0tTM7naq?Zk-L;pYpwI$oQo=ufq~&co_+vF(`@=H_|-ZMC)9 z0k~n#2}wG#uf-o=2xDVcE<(dR5V5ri>cs#pATsXO6tG0u=o50>7|Vl*)`YKZ2*?vL z@xqJB!$2^SwaSj5sp;~%q#x#jafDBQLeQ%@eEoeornCXif{%u@er{HV-%#tmrMzPr zM)cvEosJIDeEdxVG6iUVLhxdo?t#5BL=fJOX`iV7_-rsnFoE?-&SIL#lT7J{37VVt zT41KWR(L)c^E*>1%_po{He#Y}jRcqkahgC{h8V_1F$n;0cLmQyW#$w_1PGu3n%~|b zazvC06}GJqUV6tSr7jIZBS=Wg(~SWbEGjmsjF5B-bzWJVkml<}BAUiiuJq~!6Xt=i z7^#K6yd5CA1V_%IIU1W8{t z0|z1mkEg*Np1ML313r`?CYx5od;zZ+3h@RXMzAFrryd)hHiU6vvf1)e6j*I?f@m?~ z5N;De#fw9ABJ^k0O4mv{?n;AUHx~%9tz1jMownBRZdEsLgib;o#$ZeAHM!V1G9Z zGy+e16)+_*4vbC2-&WcxwcdL$VQ`+Dmygn5mFxE!-nAy+3AXibQ;z25$pu2iHg2L7 z&Fgz|Zn`lTWf?I(7Yv&Jg$Xnayukz~JaU4xcPj?aAk+G~NPGRwd3!(qifO}Jo#u5r z0hpI#dB6DI?-q*wUTHs!**vT~Gbx+VMxtcdfBovR{QB=M%eUUiPZw@nyI~xw)TI&t zu76W~Nn*qbu@F%ee=OCbjltZ}a{|(?Iy2=EfiMLS_ps#>A|#9^uO;N2nF3BfOo9_I z+ts}Gc=GccPZ$%1;>LhH<<&u8BGC~rTgJqx=}XiAW@%^?h}aEwm-_vdXY!RUi8F+Y zjKGN6l`&U;V+Nz49F^<6@|8LLR2qGTvvbGsd0+xF6&SoUmUkoIcRnbPC(cXLc>2<^ zeE-{-Juw2aBTi*{=3?pIE`McUz=D3BezxD0P0bLg=^ZDc@hA{zhf}O3Y2EOgp1PY< zC2^_IH;#7XK}h!GgQz!KuQ7o?O0Es0GUfnT#nAf(`8mSOBy7is?<6G=nJvW;u6%-6 zhVZL%KkX2*uGLhCuDsn$IxF1Vt1iP_w5gqWf=PO1VLo>1vB3y4z;vLCii^?6LK}Hd zI_VL|`ne`X?ZD_Z!y_A>>KTj+yxKzv=GY`X)$JX|vYv4Oi~499%JsaPg6la_KjeFynFqNc8iInPk3w$ww_*Uww6W(_njDEo!t)czLoGFp zNaBgm(+8Y&;dsQBx}MnG>6^0?`6wuSZh39>y(U0)ubBvfg8XZu_S>yaMD8*haW8Gr zJNfL^V@F|uHY7bM?|xEP?1`LKni_<>(vaNVk2%nLlA3C*eUKe-uZe^-b+4s2A}ft;7qh;U|=lG{4O1MsOA`hPV;?C2*0a;7-@g%Lkx@kMZ3A0-F2tuwTN1vQ5!fc zCsW95AoBHZRz_*pPSY1(TsZ(C2gYR!Cnf{OX-Kd<7gJy+T#m6eJD}@b)aGi4V9m?7 zLay=Pbys~5=M8SYxN@l3Y~%~3BqPGTnAO=ip@+3yIiybrY~GsDyV z(#n9iq$C8T-uj#NFo&O{lGtNkGdRSk##Y_kk8wd<4B~Nh?=;R1D0OxoiAGMJOv24Z zoH22~Et|ED9}ys$VE~PlCq{Nqe%cPA5)y0%1Jg!4F6qGL2|f}Q!2!+^;@MQBHpsu0 z@+*Q|nFPZOB4GKoQyOr%F(3WlZznr($TTYR-?5m5ZOxe&={chYAPI=*D$H5F z(19Tp7=Y1DTH*+=wCVJj07vMgHyfQ34mwDb6sWe%@9*b^9B0AmP=)N5BrJ>nUS>lcW$IF^EGzT%>AXOlyVyI%!yN5sA-F zvA&hBK5dg9GjLJs)sIP#_S$7ikMDsWz&tcFfL=9-QUZCj2N%FRAeVdE*Y5^#G!Ow% z=G*S*;PPLOaIYrqJ?+?OiPjtM*QSWtEP=V2E1z1H@4c}yjW_`L1k|_gE91h+dcAxc zv7TZo;_RW_ICOQd%Z|au>r9#i;$=%C5{@-<^`5udTIx7$r}nXr1+aY7u0Ut<1M!-# zBj6Aa1cV+pa~{o7;Kqt3q1^GfcQOxkU3`G3G~V!?h|%qDyw?s#AT^D%ZOq_+T4Nr9R=%^ldGzrN?wg{-KfR#2EuLC+}!k%2B4Y>b}_y zPSrf+UB3jadcrmr{GF$y&1t@jR{@t+s)d2uXp>=z_A9Q6wBv6^>PRj zUh>mj4<|oN5EV(=+y??_q@1ztb}A56&OsrZRDBvR`@J%lq$K&=IeZ2?1j$&~Vgd5~ zfV1_!IUtMj`(Rc%AV!PwJ!%+r)gAouse_5i77aEbKAoqX+*sE z;KiH_h}i}+BHDrxU@XocIFmHBj{*&V?rDAAW@&{ffAw2EOKTwzRlc+I?!Wo}rRqJP z>`c%5zGwQK-UovL1{jn@*~MZzms?0`NiHRlE6EZiMTatNDUl_|j+Df5BKyRS9G6&; zk8>>hIF@C}vTTL1MXFIO66=z?%Pqt%wxBKQ0GQr;xzqCdJs&Tl+=CDI&b?oG%TxYO zd7t+VW6L#Ei{(Pv$hGsgf&86?rk|&e<_QcB7v@txeV7@G+^KowE^|P?>HNN*%a1Fe}JR+jZq5 zg3$K0_w+N2XhO17ni|_dB%L6DW*QFjfR<_@_C^S{83t+My#}<^MPgVrT)o1&eCV6v zB_I~!{%h=KP@*zw4Zp47=P>p^H9JLcg|geA50i#}_p`r=5cH7yJXZ{Q_ z-n%_kEEh&wWR8IEw5P9|n*KDPgy3EWeK1-iegMBAG(%!)AqiEDrPlVPHeJUYQnRW3 z)k2yCY8?+leR}uRP--+akk?o4(~e(96d;`Te3{DR6g9kdtS_~C1sdxps|C50`EG%{ z4z;jR0X4F4*ZZWV=ROj=TF&d2$az=-wTWt4h=OR6*dA9bE;6<&_@t^aN7bsXZLp0E zXv5rA4D=*axBnH*?lTJbY<3_Sk38FDzFRPFBT#sg6`socU1(_rKMUzgd5=n;R!{(q z(4XUBJJjTkiTA7^7{BvK1j4;5G5pFHIySb`!A{dEUMC3T74}kmpMaK8DzbP2dh#Bv9 zA=4=1p*cW>oW&bVluUm0O3{7?TA`~j(4jX!OS|cVew7|q9ZyG z{p@%4Nr6)cQ3VX`Oov}#7C6pAeb1B8{ku5#yM6{xh2hl(+GSDHRob=+gL}S*2*c}4 zgtB;R2|hFg>UYif0h)XJ;yQ?M6e3vn-X4&M%T*ZJI4-(0)Gu|&kRq%{8)Wtr!8}qp?z0s*K3lD=u(4e-oLW|IyAvl<*nYJ-KcxrWGEYjTBy^se z&qE;D4pGfKn%V1YivqxB2=N){6fS(ltkX7ymwl$cwq0T3^SzI4ajblKAI$>@E!*12 zKu_@uq5n8g+p4ZiaE_g3iJYt1#ckjKX6OVAe>=+VAK*BO97F-OZTK$F;aNsuMn(`0 zmBF(c?G_>Yc_5&I8h>~)m`<{^_aRsTUnamnU=p@a-qc(jZdTG! zKv?zG&mJxrcFjkx!qONo7XFHH$2Fc(8{ zPgfk8Ga*y@QXr_oJsdZmTWEa97y7mDg^(=vIH=czrB6F3vQC7bt11n>>U?cPv3R{n zS<96B+QM|Gf!B;oY%n0Bm|<5LKU5C3KWLEgHT%ykDp4o#$WiFe5_T#_b8|!k(ikMo zEJNVh(P}pdvjVM!srm;P09soxf`n+tm2M`uxB`%m%oM~8WECmMtP(E`mIw1Z?SC0w zMpQG)>|w9C}~>oEbr;P9en4-^Fu=O@!mUDh%$qGQRirH~!^AGM>$E-qJ(In!X9`-b*(k z?o8lo!0xpVhEpwVt}yYOJGTh~9_GIEeSDH@8Edls>TM^CnT&pLu;`Zpb%4(bAqY6H z7y4m3l<1SA(8*!jaAC$I^#GT#MH^sbiFAwcRTOBe>&@SR_zfG$ps8W#tD!O#?Dg^aIV_c%D5g@36>FMTbkQD@{F*=%Mp>OC)^lya3E^P^2Jf+-lZ zSV1V0X;R3LzO?IQbf+>?b{#_w6K0HxWKt4iw+)S{gDMPdR-+k?E~&Plib79hp&*pr z9ZTyEc)0RVgd!8W647H5#+LDYDFhWRW)C7y^WA&dMun4eg!th()-ZtfI8XBLM$`A< zcy$M&U3gG>sYAg|ao@`bfMxsx>x_d@A?qtbEpdwbpY2Bpt1$J|N!#+Oyuct>5dkKI z$ZQD@%pW9r8J|CsIb~2)h_UR1;&en5Afq#QDIhFmZTL*>>mhLy3Q7p?edtu)QwEmc z<>`#|ZgdrwyFXEfRipb>Q4X(CK(H8gSz(;u`HPK}nsa1_RI2v;m#$eeLVqld(4Y z$JfczF#x1f!rN-;ggeC)^Rt3*YKAb(ZZl3V5u4Nlc_QNrP11pbO{UJA03slB*yS>Q zW!bCRQcW$fdc92LtD@D6KbH}*Mc_svSJM{-N4!pk@Lb{U_n3(LsFl5^0?2o_0XI)x z;@4i68NE(ut@qm`qY^?B{fPoAP>Pj>O$m1YJ`a(Kklx*W))~)j=tFc=2o3XCN!HbD62IECr>BP+B>Ref_-tNF5P(GKdVK|f zFxNC%2~VxAFtA*fx%p})&rP9$;8Q?I=$5&bZn9n=*ZI55ERjpxtvEF;@Tmq@i*C?9 znZP8VMZ%9ViYdq@!5C@SqhA8i7CtvKFKTpumw}9_D+Cl?z4XW7&e%VK&UFDYkHVpy zRdR)GuP`vwCG+m)#-cmW>MAIyiz?FE`P%#DLb!qE9?^&`pz|!%cD9M?$Yf>#ibB>< zetPS(ULu0Vv1vq!*w>0svCsC=Yt{+3dA`3Z%p8k~5XiyjKLw-xcWgVz4AuM)6|{r0 zOrk17Q>)BzGxmEECJPji_pc*L)j$-^o=spb-38V|eJt)n5JHRISAhw&#WQD4GC%MT zkrcv1m;msh{5k=Rvi77p(^2)TtBvVc61v2mnv^@pOf|OqD5X#kJsGFBhTrWG%$V6&`_8+3&p9}v@;o#CEb?*UWbtfGAnf?`{%44h zHeI<=I1KHpkiVPXeU68(TelDt2XnsMEcBj(g9-H{F2`e@Q)WmGk?1_sKoXg@e%kyn ztxT1LKOhtd)8CDcw^cF`eU18$NT%cuL{%qSz%Fu;%dcl zgqGhK0T3Zbq~5cw9pL~IuHdi}7F!ud=<9P64l||}8x_r{xlIaE>nnJ(8ZwVIH-2hj zGN)05le_-VKHF7khkf+fj)|XjikkE!ly{wj_PuKv8lf%WdXvsl(Gq>}aLX@+p2EQH zdT*l%%_yr4-bY48QXo3Uj)Tvw9QLJxYBZnx?AZ9d<4_q3`=>J5McFRk7|xRAKA&+} zW(<9mbdZC*&1N)uwhFIKK+-$V^eXdVX*C_5a8T?cRC@ZMGbkSTMH5xtIB^`>lZW6% z1UteJ&NVqeW=ajmiBxP&1`{?|r$4;~e6QBkM)yYiNqi!0*94(Dyr@XRM)I_hbBv|x zahEZL*D}2mC-b_;fSOe(FU(f^sp)Nin!b~3h2CC6mDD2ZkMCtBlVBVu9brPZ%&F)5 zE#Pt;*H}1I6{5-FFjRA${sx)euhjGmdNQ%gac!gJ#&lqs+hF^Qawwc2ibNE))z31e zs6#04geYiaOxxu+)^k5yvCE9XD9*J37+!Q!MZ1po!hX7}$+IoY=N_o10ff_p8PLRfk~*sA7zT73vmFFXF4<*F zs?eZq?261L3Xl$Iw<=7O;0gk4hxV^i;i|Kz9p+>yFN@ULM8oC}plPw>lQv4wSsnQz zq66tsKY@qk)2!*ZNaSe7JrzKkW=rbC+#+wKnS7C^GV{+Etda6vE^Ws2`XrXQf7ixK z><)FEah+f;78svqmIe2)_XHFYPY=+JcIa<*6n;VRn45AJ3aN!+C@Tgb(>gV@+FnKw z`m*t07PwU7NtAU=oE2|P1X}ba{EpOTb2119!k1Bn{xX%?QbCf?n1_iJkaBrwK$$DR zM5Z%BzXAy-i3+rNk+ymGTp3#*fahwW2(Qi8_s~SpN8odW^+Ir&(abwLC{rq;WqPHg z?NVUq_;4~bLG)#$7UBLz9!vld4TyYXa-Xk|qsjU5o(cy|0iAehUhrAQ7roeKk%Jjl zQ!uu|NApFGK9F?lB1pi#h=7cWm{TDVP-x)1K^+zCGd}BhnGxu`*Z})CGs$ylJE!OG z-usQ$`K=shAG|*|1m)M%81xJ%xC=3{VvY9IllZd+VKshNpcw_Qz=qmMB2#0jjr9Io zKS29djih$+ck=>_3V41+dsP@Q?N;VN7_CE4f^P!4OThl-VZOeOEEfj3USMK!EL|K5 z1_g{-PxC?TD#DP7HCuePFTYn~+fLi!E7$YG+L{C{jA;p^XM0U8PBQ^ovJaK(DwuQtkQOewwOgo1GC4r zaCnYw^tm3=r)=VQJFcxDS|br_2wMx5_#Ax`8}OEgXr~t1+#&OIi=zr&yFEPGcd`lOnNp{Rp;R|6c!3B884ZSlf9<`rIL3br^vKE+y1EbeMhpOR>S9c%6 z0hA(wnwlw|KRBcz?(9Vj@%ifY!h}#5zJdL(re`zHn4jc&?lC$-d+C?$Rp|C%R$q*lRix~*<26k+jonz~$J&-%o(!L!`vMrcdFY-JNMm@<6l?+aVN(lS zgTzQjnA>Kl{Yd4^t+|amtRY*}_`aL++sJPgxX`v=m~aNTBafwF1=BJTGLl+5H7xh@ zo)tl03)G4V5Vfb@JKE+Km<=dXYC@=$UF_q#(A;;sm5w<{o%<+2AoOHD%?w~(YGD(L zWcow_e3l|a03r~Dnqg1l^m^Vk5xfU>U20l2xk5ofm1u^0D$r~rm*({;d^pGRY)3&| zj1kvfyH$*Z0#2mC1m(RB4e~v|>(y8IXhLXq?T5jybIp;XoJU*w5fV>)fJvLj&c}S% zhj}o1w}=jiI-KDH2T+T&T_1^c50Ul{9O_JmPZfzvM6y{Iz7y}x;0FNJZJ=pf)u-3o z?Z{%;#ZPuM+lCi~3r_ zq*x)D%9Vz*bJKa9*9yOVhKqrki5k>kLTLBPAYLnK@cEwOA@l1ikcngnX(ka2eb6tE zg-Z|$wq4CCBipYQY;y%cJwkk$bYZ>qNQ=CW=h<(WU#;z-Gt){(E^~IZT?GaCe1(vR zBz-5gftN`~3c*T!z^n-X;?xIGx!&h|mr7DN7VG~DpGw|?KDkm;6tqY>yaLqETt?|M z>n#wPL@904?i?G!>6#pSn6(q2-5ZS6DCbV`djpE185*i$j(WKHU-|IU(dII9O>E{m zhDT5)(2bEM5wpo^cdZ8?qtHP{8)A}qAT-`G%T5Nh!!$GrT4vVKB{2)pB}f?|ujM;` z_gs76%XeY8_XDn^O)qnKc%Cp1n8i#KjAK<{HMz{MIpW{yHksD=x+~{+Y%;sr-a`SQ zZ^A4m^YLZS3{i3(_G~V*wXOC=O-nP2Oh?-4-wFu(?@Oi?a;y1myQY9s=$Lp<-z$P4 z`^Gu;Ujb#VgNu?318PnH9o3Mlmo60>dypuT!coQ@V={&Y(+iv9X-e}5jBYz^%Payw z?Tj~j@6ua^V8AJ~dgBhC6&g1%Attyl0%GEBobK<4(=?CP0odwtx1d`v;5=UKNntHx zDg&(ZolGSISLz4Ka1vJoz7KN&wiqW5wY{Eb-zCg;9Ia|tcLCBzpq9$jZ1i2NDsy^Y z^8nh2Raai^c>8s<8BI`g>V%W26b8Oz-1HIP+)}H{WWs*iDbvYBwoxYZ8NzVC^BOk?upQ*mp$*jlMxCXlX~I3-e-A@ z_c5*6#XmA?rJ9k?bqq{PHs`^^-yIJZOL@40(#V0r%>GE{rZNjE{aX#MQ1qqWV>A21 zI3Z{(j%tKM$KNtSVGITH>d1K9=G=M6e~$APP(alnjwbr~5Nvhw*x^EXfTFT^ugJX% z=C`;l+r-BlZ5YOvkaoEm(OPxzLvE7c0v)~QYUK0-@UDBw%`JtVbST;&K79kiQO37m zSU#xPeOSer#=vQ7Fe_Y}z2!bUsKP*EB^fPYKKGF!k?x^6pjqI;92Sq{v$X4FV70RM zV~}$MWm=_?9aIqL>G!}@9w?CQ>ey=d| zW&87)oTO>sxo&p40s~t>6q`GSPDFg?^=N+hAp{@eX87*d(LyNcL`p{|-wW-tq9q7y zWTbeG2w>m-^p#YZO^MUZoPlQd4R949WxO&HpfF(*(qmLveqbLp;b=xJ+JG4f#BEG6 zu8Tm^7ND&QRaOxI)%0H`Py@CxRu%i!YCKEAOy<^ZTz8W(9xd>&G zjmprAOMn$7PDhdYi>74`w$SJ<%Qc^VmGH2s$8`v;dNiYEhh|C@*SmYJZMWSrsY1l| zmk?6g?OB`?W*3zxusl~}VVf#~#|pge3$Vp+TeM3wuuEHwELbw#Hk&S8X`@L`_NBP4 z@K5+mg{}SSKme6+L|W5fhw)apD3}_!&efA!H~{zXq3j`C#~IJpK~>|7(+0m+L)TR( z!xjWS6c!&k1nqP|4KA|NtTkdLil2f8C!wu1c+P55glLHZNZj5D;#D%y;ba6gy>MQw z1W~2!sf3X5^BhMULU#$R49wu=buwpq{$VJ!A-tAMV(D-uJ{x4Cn3%l3jH-jn`ukQK zHeOlTddug^TnZ84i%hB?z+@g7*$O0m1LyCk2_$<8S-yI4G|D?BQ7Cm00K>#;X?+1A0#S_Sjf1Tu zHiW}vIW9&sZd^m7BS?*=OpW5w$7i6ha-6IPB!gHJKtwXhd+z|k0u#!bfK>O*NXRYj zG0+!hsWgQ6V^tpZDa5A`xI2r3h&gS+Lte#rTSlRgRi5i;8a1yq{45FP>OZxZA*6N) zOl>W}CIK^^9h7i~4YL_0773SSxGW*YkXTB*6G5fBuUlb^K*7;oE8@@z6uwhLx5i_mu3unyLrY+?s5xw?*L zY93Vb2lOXonJvcv0gwxqpzj(wp?|^@W*J+7qu)VhSL2#bkXj$QQ5eXul>{P%LPdzs zfdrMVzi_|B4NMyI_toB(H+OZSWb7UTW?I@OEmE0ZA!KONyoWky)VbNB9Y#W`L9}jP zE}yJm#;rhqOZ4IG`RTX@Dqu%2+M_d5lCt_zIqu;(PwhvUH}d5Hc&?raQh)E~vk7p6 zhnO&aXH}(6bz(R$j>$CI^U!)OkL`l^gyQ~f>FTUK&C?ptRy-)kOv?T2-Lp@oLh_`j z6Z+a2VU16c%uzlEk$)E}+nH>TN>ZRpInyK|G*YVtRqR}n}It2+`%XWE z1N$C008`U<*9~}`PSS&iitBLdrQc)9G2(YLMk0$-ClM@7aiqH=Z9iqEaFx5p*z7dR z5h(0I|Cd+UavP#C6<0z|`<=NmyG$Z4maLMB${e}Sr2^?IM6ULe34G-{%<3{>onO`@ zG~~8Odaf{7=3c-rprcETFOgeGNaifUfW^WYYyFEbDhpq1hv>vMxm;OhOsS&)<#zXPTO1gO`+D^-J5;}+an{}R?&x=+ePR2&D-w=K5Ackhwoj^M4dNpnlr zTNU^88qeE;{$-OcO1SUv{S-`l5#Csa^!7B-xbGfjf#yRy-+kanTMQkCj+STP2k2@6 z+S8Hjmi0RS48fb0KF{A*--f!Y__7Mk+t7j3fbost%gEG%&v4)lCk8~<3c)Up!E_Ko zQy+xBA0q8#f*@=iEY`vAH`vCB@*YD7R?$FZ-;i9ovI1U7%VFw`m z!)P|W^Yygh@Iw$Z+D+}=00Z>!+-8J>JNCNMkYNpl()dlTp=oa5vBN#wTHItGE#QSa zoX_*?`F@w@mKcyaI<6xVjaP&%xi69>MOM&6|D?A@LL zbj>A&l5u)9*c}FRoX>0ArF6Jtj4s3LU9Eye4 zZbiI9yI;S=Ib5&5!(t}~hYF{lkZvDiNh~_+2zn1uh9(kBeotvTKEiZx218X7vp8$d zBV?~{F2pw5L(jl8wK$Jf%C&KQcq+O|ECHtNYs9#>(H2V^bI5j`NvtVg--cS8!vI@| z8BL0?5za5etS#gPSaoO%O;TR{lI>m6#?SDRT0>ANdX!*?~V5VqSmJ(O&A zpIK^ZXtk`2pq=e9*Hu=!WqB)*QWHqw@csj7*Y4pkt3pJ z38%KLhkjao|x!B*PH%%Q)Q z`HVk?keoI9(Dym<^1UdT+t4ZWybX`F-viA+ckadosl@V4zK5pU2jQiY=y;hOr%P-?u)mJA=)tCV+mvD~Suw zYT+CyBxemwAmJ!|8i*>_sh~_ ze75MQ;XM{(&>6nxXBZ+=n-v`A-{kqUS&c3JPUhsZeL@4SV6(TF+po|-gT*5c6>)r5 z2I?&Q=8Mts%zM%%i}GN^&=B{dz4`_`mgbB0x=`y5LgK#rXaKLVF}yt|5D-(yvfX(9 z*?Z%UzIKVtruSkF!d;@B!#Ia-Q!AAJd&)Rp{oB%I zt|&A#W^zxOD41==v|Suy_)G1sUENCGoxUCVW6gavtLBT(GW^zLP6@9>Hy{N=jJ8uP zt5eZ-=I;=s5Zrq%gEVXdozE&thB)!5>4hm<}A`OT*LfDgF{s4^^)RS%74; zzM2(+7ssaJEzsW{^tjA8)KNx%1AD%eQ~Mz2=N>v0aqLjUC<=!NOwta*FvH|Vo!AT} z1`gvyzRh>=8rK8SQ?l% zOXy~|BVN0_%2xKkP3~%JbasY)(2)t2hgx(OOD4}3^NG|?_Da<4-$o+4;T26}qk%>$ zP7WN1q25Dr`L!GI_8U_)^EQKm((Ea*Dyp8Oxw_~XM8bDf$9sSHBN3<1aAQs|JZYAb zi*aN~?Q@yQZM`$Pz;E72O=>~VjB0>36n4HUW`Ints^HW#2dqw{IpU_EI`tr$^mWX0 ztu_9Tp9$TWC0&D#a(SqDjm&{%YGff_?^+Y zcp4(kMKe^DX{H)+9h!a?^RJmfZy-Z$er^ilWi0^0TjyCTj4U&G6wNIb>j-5f>?Zml zTvtOnpVq6FF(ht33z;SU1=`v_Fpx~A7aX=l%c7-45ZJvkhYrXV{6IToeb#^wcJcF6 zQ|FSHdCGrGpkEy*fwoGd7 zf+a8?n9`7JG8*zImx#;!s57_3L2U=S%-r@+2xBG?A|Dj786vsrRiBJOSaw9nW_ z{f7mFpP@y!4);6#433cqMIq$7WT8gc0K#jsG&Z!Rb-T`P*zE{I*Lzem7q>_bdI9F2 z^Vx&UpA;uLs_M?f=Vh!z>9(48hO=#A_M z2uM3hMgUhpsK3!OVNGG3c%5h`KE^oT&uqr^Z@wJYu1v$?6 z7Ef?b9n=bK34-=AK+q+BZ0WeePv9Upz`jzNBaQXCRvE*eWhK)XgIU{^rL1;#M&sdq zqw&nsN8-819zd}IQwgQbQ5LKUh;;KN`O)+H&{>K{daC2x6atujt)WHk0xMre zm>k`IFuG~t0*gS}$qlb&m*WL|E$uBxTZY5zvu5%jTxL&uobN>) zI&%HJ2@ko~W`$iz00p3sadDKn4PNi>W+{5UjPJ{5*#97{x(UJQ-;RR}?)QFHc-Hdl zIuxsP?|uDF0jlt{{YFf@{{qXU_u$1&=3`-DjeR?)-AL8vW&B24OIw?243<}|c0cQ@krLFkFe#%~aAM9b5o1%!mxKNXLNPg4!kkdcO< z4G{?EwXwTsrJDx4em<_ebTwXmbu{jb?#2t}$d=+(78)$mxh)uDo6ed>U&0x&PO5M{ zT4k5-TuoNTuDo`1ftq-=>|>tms~G_vokT+-Cm$F{-q`wRazrQ&b??&sp4Klfyz{Y`!lwEzP% ze?X~Zu0T2vVM+)nnP19>K~iEVB(zr?82XU@0?|MU1-`evINCBT*-mIa2-9|ud=>${ zi?nTn!FB2}nIzGp-9ZeR41xBi07Y2LV~Q6%+G%#VrWPeS_lY*#P~SO z%!Fw|j3B{%j+%Is&$a_X%r^QvFg6jwJ=}xcUfzq(d2m zw6zeIC&+<9aKT(zuOEUoGtd}$6JcKkKr$hdq2sR>WnfOfUU}=`Rl;C0h8jx&kc5o( zt>g?aILx_>;L=!^u(}>!?Z>c4EBx*1KV$6{4=rqM+5z48Y?(v|>?*_^772;U46S7$ z66UhF;8C9XfOfa{^(8Psn?@^3QZ(+i5LA_gD1Uj;)_wUQ@Gy;EpgveI7d-1U-}ZhC4>awXzm07 zyOk)CEZ(^hx85F!b8n2stFO<-g=^3jLT;zTVBo;pGF->83_KD&8PeEg`{LDI(lpU1 zTNI{L`U$CAO=S_t+*|^!pcBSjVUgwkKPj4a8ifs;_d1z?wP*$2yQpUyn_GUYgYj=q zFUGAYG#U=87WxQ_#i#%53%QZy3}{?4e+HhxNQ!s>;+FAgYVID?2$Kl6Gm{SHR(t2+ zIWUtanqT1gw2_O7@BECQ%kTLnLxu`UPk~TUTA7?gI;G<`ec^8Oo;l5c!I0R(aq1My zc`qO|(6DM~8T`nRLgOpx9()kBi)Wf;(zRzfU(de@Ko32_bxfeh03t{M;5~e4A`Ed4 z1%W8y11AY3-at6eFZ(w#!TRs+_yh$yU%NOCgKfnWj608W>MgYW{iXfU1yR^79a;?_ z7?X6=MtbiXAkr$vqJ;S;lW4nZKl{6fjF&P52e0 zGuO=fOhbc)+=S-d+eJ&h`*J;j&$Z}=n{!sQaX|}ACGAiU_zdsmOAT$^M2n#MIkh)G znab-9$kmw5ey7&=cWK(s-pjlQg^ON&r&HnPc=?;>IHqQ1Hp1xEGT0%aMPf7K)|>TY z-)7InmoOz38LADG^p=q(c&`>GaRWl=VcPdMKKLFc^g7Z2^AbJlwl4%9uo~drJBPvoJ6a6+UyHAN{d|1=#f`WPopCzgfC3Ml)Fj)%^9jtV ztuu)N*a5u}HHD!`Da8?^&9B$iJuvuW_&Y&N6|!oI~4$_hfrz)aB?s2%4$lx1pnf9fm69nt}ZwwR2n#a??jMj$r6H z7SEC^{+|#4!q^2UU=t>N6L@{#&dqq7kWdT$o*lL;s|6jH^fE)fKpV{GtVo~|rSB58 z%w#P<P*)>FQ{f#YHwHyu^`X08BZVS6+&mJyO|R8I04nihuqT-_&=4fY<$D!djUbv2;7BDs*K z-tFD#StDx}DokqoD+_eCWmq*0imVo=nzWg83-c?uj53HK>JcDj=T&xFGWn+# zZ6?^2OS?GMTv*h^;H*I)I!o$dV)tFsPE>-{L2xPY7!+STmd<}Si6XgAK9k_E5s~)0 zz^O)H)GCeMU>WV+1`aEDrgfLx?goSq-~HEr{W(mFmH55SJ|8dNf+vxrMl!FxaV6gK zGatjYIRF~C6#vUF{ku2;kNl+{d2h6T^uuTynOY|x9IUfMEa5ibI`*6q9U}?=?MfL@ zj!0p}py-g3DIrukrzArA-lp>m)*8%dacZ8Ud<{;klpcP8-#5T-p#U4ku~Hznp*&1k8xdSMiO&)R#;z0B<)^A)HL zKLGP7AM|Ot1VTnT~z0HdL&#}x9hXhRTB%AeR zUGg0mV+|Weh;OHaX1RqD&`cXCJ*=GTA_)(sKM!laTLZq832R}(`=}*w5cu6kn6b$c zMktwS?3|T)yu_F%+dy0sELv&%$pZ~>vh!yA!(V()E`nKRA=3jNXp7#{T(nKcKq~+G zAN)IFuaxE=F2&#b)mP#-KlzVi>~}t2FnBEtwmk#mj_O5W_W7GW#tMVB%0Mj z&%DKT=0E7cIkF!I;Xdv=fpCBU4|9%*O-B!KKTMam&Y|V|IhXs~reC5P1%T#+2;!}` zknY_3o?{de8Kz_aZD`}U4YTpro;)2-!vww9|Fz87Xb35_E$Q0{{Ft-rO zkYFsJB^n50t>86A1Z0fcveVtf{5!}zw7RT&4b(B85^Z~T7d~b({0OZ!@59E0gAQ13 z8h_8yOmf?iA6wPkWW6c~mtMF?BK;5raZ2`ASgX5%)C)W#YD`{C*&2{p6*(}wK=~S=w_#V!yIOohZM0dtzY(FZ1460?xouH% zqnWVm7Kn5d`q<o61 z7=KX*2n?vnQHH7YpLmFlO?gx)DGk%+g86Hd@_*r=kTRAhab~97qlu)MW+t?S*k&t5 z6DsfR@Ir@^4`OeHaXUDt6@oHdLx`&yo)#E<-hY!VtNw0s%1Z$@vyqA6O=yP82{IAosa+X<9{cv{?_LhfEE~T2xg*# z^$>3jj9A0pTgMRuFx?&lYUX1#7k9y6)i7MkK^Tzpea<51*WHg=9giBFYh8RNW9t29 z{4z#=jO#=eXs%57kwLBM%Tmrn3QA;a7%VXfUZYbF!I+0JewZxN*!M(tu@ zzehLE_L^zVH@&!(XAF>jmLr=)APr}sS8Ro*UUI*(#T0l1yIyKE%r=eOpGy4!GCJTucRA9|42TltSM)MuD z%L2~gd=Zf={oD@RTK%p>%zXnu84EKrndH-mkle&^R`>#-UvARG^QS>}X8D<%s`^dQap08`~j0`7*v)QV=8qC#(_T)j~w|z)g=5jNF9uZscf{@%g=-&z%rr zCz8CA!S(1O@umj{VrK(6=}4~*2BnR2o3KyKG}cVg5md*(tT9kEIAUJEvKRmGpP!3= z^PkR<;MdMhs8#XRTSO|}LPKN5TzX+X-okJ?_wG1ZZ&t+ZkF`2-)mwXFVm9J;e(Ux4 z?VtPg82$GzGB_~u>;wac88-u!B`3hkwP6rTIRb7 zW6Hp4UtzVecw_7R2rwfEdh}&_D-(I1W=+MbzrYxo38=koSv28zC7<8VA{ssRO{{R- z1S#n_T_A;=pdGxV`6^wIyTFiD@9k9D}pS7t6pZHxDIM0l0>AM}lq9KvyYwKyeH*K%Rk?@e|tfeLYgGfyr!u za6L-=)`%1p>Nhb-L#R0b(Y18+7evKCYWiFrouuPc@zlcy!b-?4T$x$gj<>E)un*@b&LP&L z6DvJ(x-Zt}8spRd=4-M0jdQ?n1`}szGJn9hE7xeyINvdSh}3a1f(SN8|hi@&RF>mgl{LVx1YDc$a&brH02kwC8oi9Ap|t5dsiOAo+`R z19VpBALE*k&f=j8SfuyvHSa-(^fAc%M~|ihF$KVpp+YEFM?oP&__5yAhArF&^X7y5 z+u|XbS&z50jrssp4CWMT!Cwd7ejSI z?J7ZS-Hmhxf*_`A>oT{R8VLl8;HN;ax!+(dnpc684nAnC@@&sl7?{U^ z2ugusdkrfx;ykPH>bqdakmMdj=K>z*Hn$n0y`qoGA|joX`Vp){=$I*>6ilX0d!7iU z9z<1QHR~?s$R=aDh3a=ZvMT!42x96Zacn>7deE%dB&afiNNMEHMoWGpU0b`wk` z1_2QP`$nxvPz$o zex>U-^S4~7D_UT#YexwWpzu?Jn*bxMmk8DJj?gX;$iKDSebL>b<9?lgdnVEniDRyJ zo##Tgp6}AsI*8Z<#g3MtD+&$H=Rus~-(1f%Wy}H@UR1%IM55F6Y<6?FU9lk5NxVPSTCA|(pGTu^15Fx1P z?P0L_?b2)U6CZj{>^=WRTpXE;J9x8exd*|rxC|lI;&uMMmYO(-rdeAg9#1Bo$2=y2 zPPzsJ{~pYkTW-UQO%QPncC1?5t+o%)AM@{Javf)jXwHgakQrn=8Bnb)+?H8nJ{jHf z{45lg`6~!Pjhrv&P_69-82XZO%{||lK_`SaGGTa6)M2&bb;cakgn%PfPBo@A9M*;i zx9UtDg2o`q^v;tFnqfrd(|eco)$q!SLol*A|C$mNkG*ZQ-L_xnqe!CJXw^ig5=ZV11bl8nIN7cZ=5vqs*2$oJ-kbp;5whDrLCrO9$1Qw9FW@J6nLQO3 z$a-7_jV$4#Fkz}4^SqsWibrVYul&$cWap#3m+qoSprIL*63>W(gE97n7h~Y;L(p7D zv>k(XR2Yn{@qPs+rIcX`{x-J+|AmD@(9y<4i`7adz0tg!m;(Bs@0qBC_zofrDwAhr z8BVlgC|nM;zkl;Q&-WZYOKcu>aJJ{jtRjUpAJ7oKyChRsSyHMa;_Kj6MM@LSw{@f- zvxOwx#+igxVUn#()7)lu7R4@cwDjXyp1DzuZu(`qf0^eTx&K%U_P-kwtQc%^5e3}4 zJaZv_;paaVpZ)v`as4XcmimbpIy?}ACk9wUG#CH(ul;r$KGYQtK7Kkz@o4|aXRc>~ z-v%9aT|hsjC4~OX7bfE3^H<|jPjALiv{SW3JIPZ>cyBH?gT8}3Xn*qtE>L>lEWZ!O z+WB{)=_AjADsCctHVbCHdbLOy)}yZ{-eMZqwuu5n6F~Hw6hnTOFY}rEYSZ1g&cHii zOe}Tw;`9*)0HNXHrGvD?3@tMgDGK>tJ$YYz;rCxaoA4Dvcm;-9(W?v^>1TW&Iy6N3 z!W_18Cpy5|c;pD^ph9a{LQG_5xNZ+^x&QD`qJdf%v59lWC`>{Nl8FniwtLhU^#YQy z(TEBRA-frZ#{1i7zl_Cyd6-x44n5YrQ_~hD!)Pn_apRA4_L;g1yk0C`Q=EUBfPzDI_nN=Sc&jPBwiGX>4XcLk#YN+F~vqo9zXWn`lO3cTEw9ESCK z1%P2lb0ti5SEGnhL@O5g6h+ixvdBLQ8-7T^;93{Ue62Dk?QB+>MOEnY6gK@P{iz{V zzs0W66ZGfHWN2Q1Bqrg<@)llyoXO4P-|TY#I+!076Tke-eNlZ9Ccf|rbOy1zl-wPD zYMK9mBQapeYYf4_cy3LMMco4`3NEx54StzmUq=R)FL)gQEr3jGa~D@Me=VMNS&yOP zSGozEIY6~EPR1`7S6!@zhfx`bI=G)c=5>Tn^BTh#62B|H3N%ol0)SI8yT6+gka+<- z2^qkDc=`Dt6l`RG#2CBy23*XsmJPz?u@Ipx(rE#XxUT5NNk2?k-$ zE{{j+d(LFezy`|o@l&0cJSXC-pZj*q41Y1c|6>nRHtDVS+*g)h_^Q}JDn@1P}juvg1FF0rYJWA`18UF_RGxH=hUx*Fr-A7CJtuaS5- z$@-)BB3&=KGFgFYW{D~MED$oSlC&P-CAve}*ctoKhPVU}^kQrvP=0(geu4}K3AEBAs z(8f!Q=OXh{=9zb~Z(rro{bSQ{7Dwz+LPZ#gXBv-TgrF70Jk(fQfKEyG2J+Nj3eU9=S81jN16>h zBLy>>Ub0h}Xx5fsbZJY}V93yHKX*ztt=U6up5`u^-Mj!*oxhtXl@4+4Q7yv2uVR&7 z6RJeSRm}Sa3+r^?t#6_6U9n0jx;-@VF7vy9cE30=9dA!B#Tzi{OcPvFR`5Dz6a?YE zd(_4_$rJzXV~69(vqSWkdAD#c5rRzHuYsX89YvBlg$+UTs#6={v&oX+Dpv)@EtJNQ zqaaoyK5t$GfKm`-qlFH(SZSrmQC0$K*BACYa6Flu_&e`LwM;5%WOhq%ek0%M_-KX_ zRFth~msY;l;RjE0p3XmD(07j#+Ny!?8bBNk*qtSqyn$=#un!s;tY!|}Kv_9E?+F4x z(eModidBExlt*sH#x0{o^rkMN!FH%jT)RWI8394fq8m4%a;oig4-`)E+U1$(+*^td zzW;DK%3u84l{h)@o;Y-vq>bIhc=er?_`^TD8()5PA-;KmbZ4MU@6m2-Hzsp#Jq~sD z#gUG_s3&IK44P;~>ufgf#aQP?ys|wX<4b66mh;x0JX6?Q!?$5p2p#POWfndG+Ph@V zfK`C*@b01_SN5s78TUAPnAyjMaRX6^YV#@+m6M5pfH;xF#XZq9ab&0v2BL&Ra5dj; zyUI)S0CdlN27s1v`+77zau3gMp?y>FBM;phpE}bYUqxs&F5*$ z$og^&6AGLJIc)&f=5YY7A#Am$wX+Wo9)xMg21GEK)h7dW;Se;l&cdN;)e5s&xz|I7 zpLVgPpY;W0wk_JMb{AG_)9Y7g!}-_%Er|s5Id~sq=xTvR@rSF?0%&ZX--XaBW2Z#0|h;jj4dbWHaSgMKC!ILn0h3i)Yau(yz0Qq=*t>)nY*qI#gYcL#T;5;`rH-b~;nAxfld3 zbhUOp%-aZq*YB~wgHCk7w0i7&vDv#}+)gw}8!^gOg2hxa$H@Vsu+pxhhL9eMI!X|d z5UNTi2wu)=ByC+i@Qj3<4c6GNuHzjg*{5a&lK|*Bj5Wl8XeYJg)t4yT|6IcC-) z>0nE97|6)Nn#f0IAhfc+wunqbz!Ue@$Iy+rvqZ9u0z$#Qf&W4GM;#6{HMd7EPOnYI zu>pE(#N=y$iS|(wq6;SSXn`KBAhp81tv9j9IqT#MG>{8l^p$6+1JyV z2)3PtV)Zz3H-Xx7v)EyhqNrlO&VybE@?&-`Ru|Hc>NPrq_0{`?|0 zgt)u=(E#(4apjeFVq=27qG-p*mg0}Eo{ND)?J>w=lOq5CKmbWZK~#)HJiw~CaR!s< zWAucPW*7s7zxMk1n1HWXH5)far(&pfGah^JR9qRp z8LwYhj-UUrvoY6MA6-BGSlnx9X7?5yrIRHDR1=K2jO5wR=WRaE-r#Qrxmjt==QEFE z61-HvwUbQNm%kjXCyznQgpmIDPoilMD5I$`G7^msV?sDdI(D?>JP@vwz(oM?u0ZHT zXVAwz%#W&vABNz7)b2<$bO<4`5>NM0*kg1f{)mDrGOR1(%GlA@FeCnS>^AE;n&a7{ zgV7CqZ&)9P352DrR->}afMFz3<}FaWFZ~kw0Zh5o?C@FMPx$X4 z63_|gb7Al7r`K$`un5|1MwpgyGV1H7Lx^`HNZWw*TNGGX164IsM{^F}%(uzHb8W_3 zjL`xDWWCmGMnaLG+D$U0O7P92gdm^oYl)AXf^JqPp%>=755`9!>g7-LXGF@R9uyf% z5>un|A9MyU7&aC?BDf}ByBhm+P@6*{N<4E0IzkxG_Szaa5!Ocz5w=Oq4}D2v3}7BG zg})1Fs>sSQGt1(Mdg^r)?jddU#MZMO-8J|1EQ~2(zmR*CIRl$z77r0}zzWQarvWpd zVCE9ix|-PFI>Kd*#S|?Y-Nchw6AiqZHtP^&g1iG&K>sMwE;7TMgcjDaZ`E$Z@STyk zdhx|L@#KkkrnMoycIkYgpKrbTR!sfU--yRp8~tm~_EBr&Qat~yaq8sL=QY-+Q$6M; z%v5&HA%Y`)U75zZg_dZ+eqF>fIz_e-W^qQezzvu)eA`J{dAIs zShxh!f&pc+k!~gvK~zmbIKzK9qH=|xgsh5GQw?!HdCl~}j$vlr!I%p;ZiVhX ze;SC|O|+wv9fWdMV2HH}`ZBpl&AP-A*b)qDErJDvn7iaC6r?ZL;^$65YFi#+RU*xZ zMzpj7L*_Rd)f9A*pg9L-{8^i?Vb`iau4e(sBH*fk>U#fIA+imSmx90oC59&(zbj-6 zC(7swT!o~IrW8U71%KOu_VXN24lIEHAblOvZk}Tt#w zLSg6tbkzf+Blu}*I^lF8W1_9GLsJui#4YOf-P4a!fUdeYS9l2mNv9y@Rc$j)7Sb1z z>0w#ssbGA+)8@xg#4Cy~0Om%fqBWUpesc>YolovV=E|;Jh)+VO0+MY4fp|>%2>O}K z7FOg5<;r~Tz^ot;CeR$?&?Zh@p31q^GXYAE4?ox%53#E5oHFFY4Nb>((^ z{-6CWJ~Af(9vtk7Kff^re65YKsdcjEj>QJfzVYjm_!S^Z?6Dr~;}sa*u+9)#ZJo(K zwU4}RCU^(OPBocPU4&;4M?mVi7`t*Vjt#aE6<|^lT0p?>8;ZMBG`e$VE}j|(6)Z4u zJxq}G3ObmSW*E9er?(Ic-7pC?{L0vcj(f)VP50fuAIHWGAT`?h$dN)2zz}d-3V?>e zLB_{>0t20JLyS1g>K-}EJrMvSXot?jiAbg>Io8Mb1NWbb=dNCfwFwXwkD5fN)FBaB z&VoI<9)E^se}Q7L_ahVrNa>xOx*gS|YU=w~z%;8S99sqBo1xY~n(ivO1a7!lgKb$! zwCN_>(KJ~#Y71DtKasWw3y%|P76q?M6nT*HWZ>J9t(*-)T}$@Fxy3w7AJ=bzD?W>cME zcykqU%QMXu4qqxB5rOUU9NWH0<>!ijLw?Evs3_27G_p&A5iiiS{DkS-&=ymCe{Om{ zUgNp95D;Zd3G4Q{$^NL};ny_d#dVGiZL!LBOPvP|475_o3u+Itt{k6ROi5$4COSQF{1G zblTXzT7L&<;b%2KHIQeAx?|gTcvskUEA^8Z+r@EI1*db z-h2LqF|-1GsOiFaij8juA_UOTRvU*{PCLzYhIOj&x^JRRHdzBp&l(s>w?@Rda zZS5k6hFPZ@_^V?jQmfo4&;~4CTI@JR>H? zp&97qIA}rP1>K}`SQ@d2>@-|G3vIZN%zjNzfdJ?Pyo{rwxUV+9LO|wM(;M;=_BS9E zw*3KT#~xA)?8iC!37|uqB)v@}WE^Ov0(LKxu@l2A1i5u#1=zAR-a|Nf zP8Sl-Re3_B)QH0{1|9BYKqy_b@ki%l_~uG{^V`?r<#QylfCAP@-)&%U*0#xwpIl%v z7-O=uF5iXNDD}k((M;o9MewbX9=VDG&?c4mzIu1u6a|1rHIr4%8jC#=0&C;!vHCdH zQy2Gjv1>1N5)R>0Sge_frM4 zS%lydI0AdOTX76humfUoEA34*cs)X{nlX3vsHM0J!<%eXJ0%w#0Mlv}UO4K<(%m-jXz-dj+K94BE)jY#U1{8-f1G1wPwf%iYEp3uH6%JM&_ULQrOTl2feA(9T znF;(|M#(iZW*sQjZbET&H7#+4`711wapq zL?p%d8ivlewn~w)PlQRXL~W~@Ux>!Rzhr9US`L(*l*t{mb?n0u9dNNn8GDH_L9)X5 zIH+2!H;zgj!7dp-H<#-W_R)^_J=PH)_&ydbJa8ado`K*=<{@(q#oKuMHg+f?iN;(ci3Har zM1kgF!0UOgsjTZb&>E;g;GT9f{N2iO&AXGNl#Xo1pI*Anf~JT^_qW7DL!EKvXj7cN z?}0dQ-)>xdcOuTcFdV;eX@n5lYJBQ%|8@F#9)ZOK-M$;!Fn#UQ5Wx626GVR=eYB9` zPM8P)f~_@=Te21EO8`1_n3tGEXbAD}qfk$aQ~2%;2+8p>0px2haUKFf4RP@@T**C& zqI~2AK90ks_>VXSUqNn^S$(-7v7zy&Ai^)rOvW38r&bOPWC<|^_CY+%dk7kHHf2`J zb8VwBWhgi2kP$mz%q0Yt45|}O4Qg1-Fx@&(-dX^fQ%ex7`5!!x&-0)JmX5(>t7!Kw z*0AqjYcE5*U6@W~1iu@MddTPskVe9(8^pUS0!2h&Qjn;^P@kTAQ+Ewr`kgE9mXP2n zj4*4rnaifyHiS~KH-J6`O%u=cFoDQ()9z=k3!zpNBlWWg!R^6KUfO{XX%!Wjn_>(< z*cb8Ae+M&knEUN82FMh~3L%RhpcbW82g2;3nO@;>hwBvrLVw!IU_c+x zkm#$Sz9?f{BH6|%%`83mEx8`n#>HLr_rWS9zEwwc=6!X;CH zWK<#c9*p4UR{S7l`pHZ(nR^8~-WJ#B#vD>JTa8Ddq6c2!=jNl974z8M?#wj`<`~GV z%V<0|<}eY+=Sb%!D(!3*y9^uQGS|TDL9quU>RPfPb2m<&u z;l9Yy^UMX%5q#i4L08@nUr}uv(%1U!S=XEi+@{iwgS}2!Ve7V)a1ySOAX37y)r4cM z3wUi2gURvbxQfQqcXe52EAY%MdS#G^v%{z{4nkYn5%WAuAXw z%qr4di03)+R<6H?+0|rfe~7@iEbez_o$F~d>$MXhkolFiZx`q7GDrxplzOYx@oPW! zKgOSY8Qx)%W^hW@k`G>5CC>c(M1qHQRv&GXnbk~*@x}(?%G5&at7mZ$gSOH-MNWmL zOp)_GGYiwVW1S&%T$C~o(x5miW6{DAPgcqiJ6|Pqvj-xW$5U;+{9Ooy@;7+S?O%0# zb7U#Lb9XQHzqJsL4mHG+Cy&I3`gh{ux30yf&o9L%pZ#Hc0{;)=1rwV$aQPx*g9JA* z=)Y3G z_$WAVoUKo>X8-hK3_WprrlK8WcD8jk{^#TS;#Y8loF5e{-cRzWce2IqgCyow#rRSigrQBKm_g=RH3)~SSbb$UBKzqS=qpYL@PnjNZ% zNmL+?-2`INmUTmi>odyur0^ad`{Vs68$+uNY3i@hXo?VHv z(B#kl{F(T~&;B@Rtpi9Y8sU0!iOv_^6!Ubiz9IpVYqpL0zm?dPal0-OKM2nwyy~zc zSK{csLus#9Q7NQa=Om$~dcrMx*y$VPaqL@^F>t($18kI)dupXJn!W?CEZ`Q6Lm4XW z*99T>A8d=NW6i+NWfUrr#cNlt z#v{il=J4q8m{`6MpZ(9j8b5aC3E)~klY}svW2}fDXUeC8hD~l9n{8_fp@>i@<1?uj z*6I8iN9`P9ydmtg?~VTp5n=Z5^BZqOt#t@sCQFDnGWqEgLaVi(8bXLLeywG~gDvry z#~+OU<4fO)-$8iqfU;cWW~%aPx$w|mW?jV!F!BbD-ZPyoq(kq6pm$=PWz!ol{1W4u z`wK9IdkZ-B@Txb>qE*MsvH#RzbiyJ9N{fYED{J_PFxMVopNAFFa3$!p-F4ETc>LQ+ zJZfK+l4!t}jO@arGyxj>kef59?R5su;`kE6`+0&iX-x$;De=BuUvWB`IVj@LL1>t} z6?$JLM!rqRx6JcuHbDd=u9UVg8J0n7IAgakdFRP%n4G_h;MxF0?PD{}rg$4u^A)nz zt^tWI5QW%L(NV$5f;Zb$_(CvoExEP?3Xq}^)$tD=y*EC3-%-%p6?g+?WxasBOQ%vx z*CVtw^XeaZh&hJF5gKg|oIwyE3G@^B?jhV);TRe|e5Bwrc}-K`;>Cz3pN`$JF@!KY z$Xw*hadeFByQ!H?l<}$oZdb!FErcwp(WsjcHcBq;JJl02K=7$hcs;?TjkaOE8e zEue|EM`LvY89!)-yyqXkkBNZy^$WE}qAF^BhBM8!64)@pI=Qh9DYCCym{V z2D1l`_rxbyJ5Ytg?{jF)%h1spbI!1Fvu0is`` z#p*;jHn)K5CY?8;nD&)Aql*kZ*X%D*XMe*;5EgzHbUc8mwvN(ycWEu|qVZpVp{@{7 z7$&hwrR+|_YaoVIw{PcViLW`He~>mk!?Qos-Vh)CFwDF-2~wV6K90hiWXNLLB;(S? zWtO(LirD6E#QKE`(fF~C6{rq$lJ)&G=f=lDe#{^J(mAimIQ6Y}SZ)k*_B-t#`kCQl z%n!1q3ZdcmFu%g1w$Yd$1ZAN?qe&ZUtYwD)_I34QLafH{yQ@^>U602eX^8*f@BTN@ z{{Cb1kim6*tadl~@VQ5y#+e>TZFVLpncO+x-ry@VR~Mb*T`ewiGZ6g9XNXbT#Os02 zi3}uAy_sqsrJN`Lp3nO-=@@Yw++1e~iz}kBHzxo1Yw^cl2EwsD*(TbyZ6E6tI&r*R zem!r0@5xRk1gBJMKfr`!6DF(%A=FPK;?C_`adPN^IDhU;CHVU=t1pr<3_Qol4A(-WbD_? zMwym0vGAjuIouFuo;e$jy?H%;{jKqMo#d{i6;gfC@O6Yi8Qz!%?7cz7n5i1Z-238m z?7`ueQpZy~1Lkj}-)-LWP=IB*qq_OGxN@ zD%6qi`NX%^IV2=B-AF0Bo8TlHD6M^#)L}YAaCY}ldbqEeshEQW6~`4>BL{zl)?qh^ z-{0ikMiA&L;0OCDGFSnRcLCKGS#xc#GIrBxW-)>{wL&Ss4DkHTFI;3#*q@zgB;*3? z#*vD@3&2JQ07k&{qcnD6Ur`!~`wtz4;e@t?x3d1^4lBf`91yY5Jm)!l+?n{rA3ZJ1lDm{eJ4#Vs^=QC5BYN46gBj#rgZsW{;qe8lLk z#cCs1(6bCxxrh5e_z_n_1xvYf3(6)}As->r(slyN`XTbkLvN2bE(VDu^B#NnXc7y7 z`$X^o3owi|XrDUOg0P;k39OGv^63ZIN0JgPfj_vqM-KxC9-W29m}P%Y%c4Fu_n%?9 z*)#_#=|bi^dGD^WE5-VZA^_C-YnEP(?_9O$bC+2U)(RKJ4IXhq7s8VHFJe*1P|_bp z_(bx%pV9kn&e7f?(i#^t8^5I_fD-9g!~EC{_8 zmzP`{F$n^Jf)XT3?ph}-=;2yAZ-BJ(ENDGMor%93+-RR!24129YDx>!1gSk)-SFAK zcBb7TY&5CqzyaR!-%>E*<)u=?{^29d+jKL=?Si_>R zk1)2}fK3|9X3?(Ab(zYK%8cc6Hdi2EeXT{xi?JM_A8#e+wt)*`R+1WOOHkh$3@`tbe0tC%9th2&VihGJsDY+l_=Hr?a1l1k z&#uZG(`pE%m&GBXmKz}P#X>nNK_!rqK$wLN&`^4nGTR&&$Tgg((|LGl!L@igkFbW` zL7Nz)r9S!mOY>`1LP17FVU6<+wiUleDsQ5N*HOEREB)5ljVV{kwRSuc!E#GG6>rJeY(VAp(NJ=GY2$$MNzd4qlu^c$?jWxXsz@e_`)+!*`vWx+&%L2^GLmAl-lGhP!SqV)o@z~ z$~eS-gS`5G{)c~Kzx~`%E2~1VPRt>h*>E(NOY~{OFO(%j$!@e3f>4up7|K=t6{0yX z1s!50*J*fw^>K?pDj9SPab=JN&=U^RBY zSDC}`#%UNj8wSl>L?gqrEARzE#mJ3u>EZOWa3MS03&Kg!ov8=oR^h-u+#_7V%=UJd zA|8MEn0cR#&@Ou{#R$OY6vWu1OZZYXt?N3aW1v6t)j}VfQ$j>of&_>DNg?phUrM>C znkcL?H*9MBpt|c1|Kw?_{YNiwA_!x0=)k|!38XDlY@=|5iuGA6&=Qzm$FNEgDInq4 zaJ-M?(Sr#GP`hg~w;Y3ufvFbcO@1N_(0tCkR|1RL_Xs9&u^!$RULeV*MPUP-$*O}T zJankcCXN~Gd?*=+ydXIRjibhs72z_#8hW&&DdK3QD_u(fnKX>>hpo1SzUrjeu2TA{ z{U{`?vWHKn65~f*2d$km&^{puCX5BJgvpwTA4di{;5rj-@u~0M9knAz8ZC%$sVE|i zRhWT?1f#qqTgEItdZYwXHH8Hb1;U6}*W+os{@`8LAo9Y=!vr>V=*36QHUds*(IYcU zw#E`9a3LsUB}bp36K_4RZ+!#eiNcK21NeI}LOqYj^&h69@Dju{Zz~|e`6nTcDc+MW zFG~RzWfxa2Ltuw;1O%I-2}J}M^>qkr1gd~1VFopvb7BmF(swC!9vVhVvYODB^>nh| z`ATZA&prIW@~9MyQ6+edH4|p1W*F)tDR5D~?;->|i$ypEa2F==sFoq0wf)s~Jo$Dsyp-x4rM^Ns;K_e2{h7*b|E+(}NX&@Xx=h1b94NyIxHjlQRmu2Hv zW7E`Uj51qdIu@6o1NkK!hS=plJeX$k38`23Pv$k|o$VsYVMcf3LfFLE@>7T|98B!xy8`rNX`o>o z;Y#c4`*JOu--5t_o_UjF^%?IIh8q|jbVi4VEWY)OH3lqDXFZL1&gN|6Un{hk0?{Us zlhWcfcYAZ|ORmZ2Cg$1(pI1Wk)MJnbPFg7ltZKMDXtO=Wd>6-*7(%yXuz)DQ;tGT{ zKSyCyE(0~li{C@&wA9quNdF*YK++H)pv9S#J$SHUEgi+QyUw&%&tG{xQf+eY;ir5MefD3N_Y-D!%B#?=YR_-6bn<89jmc7&PD7E@&Nt@r1jf#d-e#;pG0$}VCa4D7$3sj zuP`0&t%Hb`Ya1mcn9H>$@6{! z{>dd!{lF$>`W(>-4dhskGlLI+vOB?m9IQ}3Nl(jISl9|qFR3%q4hl}XY6mMw*^rou z<{|uCNy>)@D)-TY|2>#yr+HjclJ$N!?9fZgYUjhA_-Cf$?BLqr|y85zz7JIvdH{II@0Nr zzj=CTY!=Wlq>>6WVTfWM_qV2Wh}l)puWp?J^3=&hLP3HytTLJk$D2VbB&hnzsGVtTo&M6a9FNi4}=KXRCV$xaL z%qaBHMk_{NLef(?k(!6}O%7WB7{tnLmy}f6dshdU|Ix-U&KX*k_1H_l-fs6thwOZP zfXIM{8Uc?D^~_oo$s=h5%jI)5HUke5WFX;@SUibUzPt*1b9KtrZ_U~t|JIA#FKT&n z7NXKaL~Xuz$ckV73<8qJy4PjiGsn!h2hc_ct(}u0fCc^t8{ko$)-Nu{l!Q1X7{uJ_ zIUu2+!Xegczn8+Q<~BaZq#dEtt-8+=+H8IObV;WBm)3?9v8_=0$Ub25No(Es>Sfp1a*qu2YzxIHi?OE zks z)>8cogwQYG4ye>u4P~TQ%a0%M)8H@`8a)tb1`<*N9WIn7sy*NsfcZpMuSLL(+2NFX} zE(ttD?fB#A?NTQ zWLOX;Aj zbH2iv|#5rmdo;C{V?K21qLvdZSUIbzlh8kg|K zYT==YTtHN6IzrvU_|*Z6ef9GQj4|uGwP4NPI0$rGZoAfvf;P1=zQI08}6O zB5`8l-3S8`Dy@{7`Fvc&8lndE3&i80n?0DP%nOK%C9tziokfZ^S%Ao1LdHpruP4vm ziz3=Y>8ybTDXTCCEFwUuwtBAYQPH^#NS!2EW>KxaaQ();8#c7IV@uHYIQk<6olOzO zTVr`v*-uJ+sL&&Vgc-k!FQ3HnRGP24C=)mY-KVq}liv%NsYIRtn!AQ&evw>&I&iG7 zfBq?mgc^d!kDyT)54RvIgOk$>fQAdSXPdLE<~pR!gsn}^TF%qYvfeN|I27%9oT-oh zb6^4BmS|MF8}2DEP+D6`RHi4MUk?pe z+~jDU?|+2`)h!76my6*){Clr+;>QQf$0oX1u{1Zy74D=e#%GK$HZ&I&gCa%b$Fryo z!3W4W@a4Gxdqb-k9+=lB>K2pG2>_wd`nQk3&{Q1vApFcKgqgE3lelTQ48j-mkD74@ zZmF@KfDWbpB9&?L!aZ<*zI^-m-~ZqC-M@a|LM}=2^^-8&J~3P`8cn6C9we*!wkz*C zmunofEGG{e;UM!;0!8hs)puR}+r{3>43nG*@QeYZML<3(%vlh{&jq_s`|CZ^+@fwE zK|g!i7?KMxP2Apt#`Pp=;||6Yn%>70x5$hXsw>X#2YI2(8`7ERUT=tc_8K? zT(2V9U1vcH9+2(Yr3H)-GWF2p<75=7hCsma#NsMio>AjS>Kp{eGO5ABp97fjYcS~w zLd8$GI<9&G63y1Q{w5l6nPp2tLkYCyCiD`(tw|xQG$1EQ*{)ni0%6MKad`(aMl%6~ z3nBH;GT}_Dof@o%SDK6LGtV5i)vJy$nc32yjn@3rL4(H)J(q=SQkzm>xn;>}5!nNz6`1xL>1%#nWI{9B)NE;E2 zqzXQsYbGJY9ZBlUdHI-ZnltgmlrFs&CKoCszh2*?^2wQ%FcMsN@Dg&bbVV(QKtg9d z@HgO_3nilY$p2)qpfuU5q7;P;VQdE=3iVXhpQe?R)Y_Fl`ZN0%f7H!j5R!8ULngrw zWSqDL6AoPDTo8=Pm6e_wU&3tUl#)@%30G^RpY9(&g|SGOLUNo3Ekxfu2?@2v&cOZI z=DO9`lNIFq7v==)3rCMy)z>h|)+GqW?d|s&r9QZ6fBfBUcgk~z^6gY>nRT2jwne1= z21N#G!d=tczKo;l;ugDq{k~m3ea7DY=swI6wFjMJgeGZKm0Y!7d7{m(-0rq6(snnn zVn%uVFEaxDKfQj$ni=l6+;@XS71m6Xjc!2SrBs7=noL8Z{o%Y13F+A!J;_| z46+PWLWK3%_WX%Ldw#3f;+XP3?i#RnX4b3+jXTEW$XRqIH3l<Zm69?*S#AaQ zBAAzH3uxvUaD+@4TVe5t{BQQl12}jOVUR)2f|9g?q^NsgUeb7A5Ck8VjR(5jM%yjH zzydJ6(9o_dSWI4xo!@2uy(qC&EDPnBsR_*nuIL^cvB~$)8v?1Nb z=fx04HBV8(T703f+MesEwao2F%e{PvlTBDb1qT}yP^}!Gl!Ol3Duu2z-L)}Us_v$ zO$uVv3L}Y(`DPAil$ol=crceMja9r;R)jKX95Zt+<-AW#u()kD_TT^3{^&n+TRv+r zzChE3Y|?oVJ}UI^a=btu3^Q|?WQ{F*>Rf@f zW4d=w&fy&qnu0l|VUq7pPuQ=ums!=9o@9L>BAdpIWSZQ@Lw+x&<$J=aF#j~O5Wj-; zWDl_frsoTb3mgTcL!f&# zy#*RrB<(ob+CZFo4lTz1%E~B&5&Z~^$^y%+t+p?%&)SoP5u4AK36wd~TRtn2KDuIM$Z>0PM3btEAUvLNDsSr!i!xeCN)l zSvv^{yy+|ym79>vgwf+Xe{SK4Bogt2m=+eX6rU|XU*y_Z~_3_>7h9N zI=GORv|{$#p9Nn?^WrKv#srm*sw7)=90-?9e0bHJh~9-cIS`Nt#m3LxwXo2Cn0=|U z&kE^Atul9K84M4zH00q3W#xCm* z0f^{i1;pSaWVpCH<{Ft8Y&I*ZL>rmzBy}jIkRop$f(xl&LB6_bX+e2inW9dLc_fB% zE`oR-lSS2C?w^tz^bcv268b2q=ys2X79+~ErTo(hrSUnI>@e3PQ(zx{wDYBEkc7-G z|8L*=z?C*Hth3>0=L{w45dOmR$18zI^gL{(YGYCWIEXe32P^^JQ&>;}N`p-$06cOT zWN|?IkjsG-MngzgsJJtYJCsH+$W?GIFzZL?7hVpKr;}kfKTMeC$_>&lKep#DmSesb z*}wTe&R7qn!GC|X(>@y5vk3}IV!0dESdCvsUBx0%jq5+{wij?0_R>psq@lom`2I&O z_~FGRiZ4vsgZ@bhP*V2dnI?PhX20#g00CUEx4Zdn&s#1FyW>!{H2~5bfBtF90U%P5 zNad$tZ26QkQ#?F-D*+B>QhpMGlcvYN^f8Ucr4JXxSo=xVlzru`6$m)Ly`J~TgTN$> z#*VSa^R`FYv{%Jkqx}}Gs|GASYNSE3#OzrUzsrhCasp{VlAF9aNsFCmr97d)>XLw< zSX1*9*1SJHZST_{f-3?FvWEO5+uH5PG(M~c?WNG4XebGtNYiVCxh59vIEw&(9H!Ai zc~Ke?;+o{+!J^C{?7Yvhn#7ZW!P5v!m6RMz-(`QHuN|$Fr;lYg$%mIp81;y>CBh_2URY0ka+T zMAv%Y9~iN?)cn=8C_gE@lugo&sq)eZ9+>+?kvZ!VH!u#} z5J-y`)|7G(LLGM?SW|Og4N#b*6m<7Kmciotw=8;^rNdbDbR3;zXX9qg-nGx4pzgCN$TpU1WOUW zT_>)gh4YXAvlic?bh#0$1E~i?YF3+v#oor1@G_A!i9fo!GL2kdb6`W|ZmVWHv8J|6 z2f_KB%gE6w1FiDecO3StWZuo(zaUV#Y+5n%T1 zLwr*Cd-!lpi$lnJSOJRM1|TlBncpbZ;JH)@mKHJbxq=dSOnMh!iRwP`3^oxEntJPe zU_O(^+9Sbdk8_4)ieff$gfr(;Akfo9H2(9oY5ViHiK?(~Pqr4=OXLZ3lr!$>{)`R7 zW5Yc($b|6figL-}N!v$PM=bw^3Q%aWF1Z)Y7#SR&WO7lvjZw%U(6YU*#$u&DY6;MQ zaOoP)UsG6b4fxFKcUSCxyf+3;kLXC=pvJ2Mjul$igM7rInN>{*Teyx z9BRgVy=wk5C(W*1vlZgV`G-2p?qZ2xkytB>J=tpohl;JBsnweLXRLB*(0;%92*qRx zd#iWYrojmAg1%3)Bg!q?fT1-n0Hu$z#DZf-(yTSh0Mk+*fDm_m{jng^?2V=cNm!|C zP-2hFK4?lpMZFuOT=Y|&uGJ!J-vN3k2x9vAoEgK+8KF zA}%$sE-=`KV+b~#6vme(DQaQeNExQ1v!{j|U4|HC{dlpO5ZoIKScz;QfHmoNnygT8 z2-;WEn*(8~BAwbg^C*`?>)23hRrP!ya8K_Oxk6}3&WY39yC2IrhGl}Fk>rswS9c-S1q%Qs zmoEtj;%deP#*Ghf0hV%R4vUco0kKUGlG_Ar&Zn+Cf@D&+7BO0YA`dTBeAj!IDUStY zA!mM(pX!tf=7Kg}r-;Z2L5G3JRgw7>B7UisQm(-R%|X)uK_QOQKX>mVFfRGnIWUHZ zLkS)pq6NMJ3N!$$vO$GAc;>87+!hdEDJ%?WdnF60;er_7kNGZ>lE1iDAU*_y^Y5S! zZryz*9^Z#6ClO|{7;pvMONkyVJ*=-LXxsq5d6?z4!}*00t6jRn>^`gmADT7~mmv@9 zB2buZ^KA5YZ_pT%UV(Mw?-xcJEuFb%QzUh$M7D2|v}1&QK3w}ZZzFW^J;RuHS(ylz z08?*g5RhA>pJHA0dxsH>WH{2OB+BUTBK9s9A^k6Y`2w2w1T_#pv|(sqCace-nhB0ka>kvivE&r^rnW6K(;J{nC87dp7<42Sm7z zU|~>Q2wI=@Oq-qfsMB(nLe}u<*X-kWKd`%#3zja8Sr7H;LxezAp&JG2R(TFQc*HDp z#0!#^;$}a%6feyh&_DEwD_fTU*aatY^=Is*K*InEMPNMQ%#D&qq**PGcCn_)Dv9r( zZLGsB=LE~6xG+VWlwH3y#!n7JDXyaQ^8jvzYi%tO79HU;`XGAb5((298^$xHsH>q0 zjYPI%DVmzzkw`@Rc$hW@GvY3XN?0pdWPrQ-AeA006vvnn3M3>sYgksB(za##OF5C$ zV<*oLR9FP-;(aiK%F15uh5N0kvCJ9vP`*CGAwD`ohxr)7nR^mm1_-)C{=h=#kX63% zGW*Pf&{)@fx2Fs1ntN<(<1usi!rUOGgO8o|6ym!;B~pUkZ$TDA12&Z~kw;789Fl?- z<~ObP(JW<}bd)8rfsm|oL6V{gXhqOKVWAj$kDr(_G)UYHQAP_uA|FTow}wn^Ac-C5 zfhi{`pAJXu@eqVS6?$wH{#`@Z(?`p# zHI`>*p1w@=FIk_*$SgZk2{%C;8Z?np#e=Xe@+mduj07eNKvYp}D%xO0UnV?BI7PM0VMux!oF zzt&>sr%j1s_`~u)2QnP84d4#noB&?SMp3qWSOwwcVfmT*Ne0i14fB*Ygm){N^70Bgx z_0GV`om|?qn5>cIdG3XMQnL@zyZ1E=wvO*g8s59@t`lUSNTnDPAk_7i7)N;3*E?%6 zs>u8R06+jqL_t&|36>N9DD~Ul{hW=W)D+Y=zOhK&0o{Ih=xjA|pi#my!!aHVF4zbI zGSpIBDFFh4%gc$+R%Te+VVX@cfi{b2xTWR!7Sg;jwVNWd%2^cTw!5f+Oc*60XyNFF z;@T7|%0L|0B@tb@ZtwnJ!S3}WY?<5wI&8W2=UY4%{W>uXVIl;}KH4W637aBeBL~nh zNz8W>HzCRSRcfp8Qi=$`&$u7tpcD2{zV|vCL@K*u+908(EeI@=I*1a$xhw{e#KOCF zrZsBMJbBc{Mthtalfi@j^V%bJrUMO|7wB6p#?wOXGVAC_8`? zC3fU;uztMb18B(*E`fkz#{d^~kL(GAq&C(vT96(5ha?mTU@-&c=ko}gKQuW`?nk!G zapUa`HE5cY^$$#19f!}5L0A)Lx4D}mR&uTxLZsE!+gB{}$zv2MMXe>YWM32Lhx>K) z0X_c+7e2Z%Yd?8wns4{G5OYgY8I5QC*498#&czTBK^C3(`^@SPUO|G;?4pf=Rn$Cj z3g3j*VDC)X@y}j_IW{eU6;t}D=ON-zV1z#NDt7=Lwt+RUh(`_w-)y@t!?p;TRRm(#$kB?)FW%^by{fdd>4S8#f46>E%j zL`en|!Q=Y=nR}h&4-nslX~VhIOc&a<9(>Dby4cLl%;QI~udxW0%(gxELY*}L?pPg$ zlq4gIi>=Lq@PG>A$(|O5Kqa257(_EW_d-HIWvPmTx{_cPg=s0y7g3rp3~MjFY|pli z+p}@bN4e$L)HD|Am<_;nvR(vohCP_jr2J)53AczANWG9(~0yhN>_dZNjpUq#5)iEFgjA*<+>lrOzB?Vb*MAWSCCmWn`cg z;tfsO@4x<>Wp4e@u6EPo4Ig}kne0LQ*AT6_w-dNAFuFS5is2>G6vo=;9yDaqho9=> zr~1 zr{B&oHKydmA+tu953?@wTrHXkqLx`$3Gpdegi5(f0B@JhU1Y!fw)h@kV_}JP(kt!C z{XzQunZ5ZFmK<8DLOImaEVZNMur*Z_SP7AeLQSGoiYi2VJdf){xEV4hQ&jcY7Q|Xs zfy==fD0I9?xReaP5cHymQEFkv{D-)|O`3WDczN3qT`pk-S`lE3rgkaG%6TV^IXSZd zR)@4F4_^XpWp2yzDHj%eKYQb`m44+je8!vsq7nAgB{bbV+v**%%zA_jS&7BlGY|jiLojadFB43MSzjYSCxCMtEOBp~@WnC|d^oOoW`pb~}-orVyQpqoopy;Eh?ndj{}Nzg@I zGsEWJylX%D37bxG&>qJv3v9#;*Th*hicr#zIv9HhJt`SUebbg<&=rK_9%icskfuPE zHFcE{7X&g=bldXSS6)49m)kRWqz4EIpPjpW8I!%re)iUN`}SW=+Gk$<*uL@gSMB#+ zdBNWO58twX^6zdCG1~zjC4vCdyhp~Q5|7m9U5_NjcAA~PluV?20+)rz-x?{UxHOA` zivi4q+ag>-{o^;;EFcv397+1$7iLGuA4rgTOgnCfsRXmN5Ue0Y!qgtmJ%vA6A#ICg zus%MBWdwmCj5jF!2rIY0jJ@kc8?CTcOrNnkH?XwXms90-`9a(+A3lX3FSX<#rOF82 z+k=zV^Uc6Kh&9X3Od;96y1cS0C3oL5U5=4Lz zpQN=}z{A==Zdpp19O3;m^tUy0U}d09bFng9=nm%EB^u*2H?B_tS}KsgJ~|2zy~S8{ zlwfZUN|X8$q4am4O=Vk3b7P@-71?siFOJI#UrErhry82#LgFZgoJ!U~8Mnmg_tEMe z6c-18C2@}kJ6T~_?75;8E)`#eK*9h({tN2ad%0vy%@)PVb@~4!Z~1jjX`&j6eV`Gi z#T)7n0@DZuc#$X+CNKh$?ARMO9-?7Mb|_~M#HDsC)f_U$VOx*`<*THTN{R%4dfEa~ zPGyYRcy*zXLoL-TdR9LGg$#FD6q$C!x?Q@i7D;9VKE0_jO)>unFFl09ND4{GQc!aQ zKtI=A6HP}KPac|uQZrFK%&|tx9vm?$a!IVBhMzM^GiGtBJ(>Dk1CO6ef#rjG0;3UA zKVW{Y%q^1six$p>NJtJfA7DvS%6t>^}llSb#1jsY3u@>;zGmzQ>44r+?%?Vqj#U%Hl&?I@} zAcm?ZRPzdj9W^I@n@!urWsv|#0o*8QDU*biMZlD7{OBz>qJR?MTw?k2F!3S@IqI0B z*?Q}^Kpw4|w8SQL^O0hnlJ&*s3ztDWD*oAm@fEIZZDM^nPLe;!x+>vEksUu;h%(^7 z{vxHjeyjW1(~K#*4~;N-9pZT#0I!cJNqcnL`QR^y?C*FcmY?cVALefWa52k{9;%>N zDud)CqVAZvlK^~`@;Sq$Ha)&=fNm7?+Z-lInrz(ukY;%$fdh3Z>Gj{-G{9PeJXn3$W;^%_x=oOZ5ZhrRnCBli1^cK6R|ssm(@U(b=g%=X5zgG(tRP?T?CLU zfG}X5GP(9;ZlQ5C=y93#=1wv1$V=zAJPK7Z0QoWiZm^#13`snBj52ve<55iPf%4(VKI8t?ktPJ@ApAlB$pcIPcYr3Dn^;fk zAk3rAnj)|os7lS!Kx<@-cq1We(hKi61e&mDspn;Oydv(20_A*SUIjp zrG)EY)=q#`RCOS%(1L6I#INN(kWIMfrQ%I>+w@r2@4Ai9<=Lpe@Rhb72sS^Azd6RU zK|AWIBdsfqiS~2?Qpsz$iH^1gx?x}#j`4HhCk{i496vE-v1T?}_c1m`P6f{_vmW49 zKD!&`4zsU6?z8MtgbAp>qh|>#lE239@_j`;aMw0bIysLYQ?bdtMOpW`IfoNOC{K1m zB9e_>WF4frd4**y@SaS1DK^oH+>H&^CM=5~rvmSJol;}&2LUnJOE~rEawil@gqt+C z;zrF_g|Y>8|IkW)4(Eeclm;A-OCjNb5Gj-*Xlnrr8O2x`wX##p1!(@w7q}t|1lo+f zjOJi5smgNZhD>-#BQ=oFPiMM7!0Ij{bWtZ(-GGzG+>D5u?z%3JtM3q7c8x9N0?3>Z z!7(k>)ly=9yR=`J%7s8wCoM!X%{|B@N+KAbRksO21O(N1R-WtX@+T;t4A9FxM8_Ww zuD}M}WBtVMBnXl$lJ-81FGlo|&jI7qSOV7|fcdYguyXp_RTbvqoow428n~pq#D0Er z&EEc?ixg3A6-_z4#ds|)|L_0V^Oo7fA`U`0NbD4~;u9lO=+W7yr7S|-IKAmS6s2G# z?P1bsytxCUG zf$;jNx5z1^T^5!^2os;N6Ick!LEr>*HL`cE!W?Y4hqZn82FNvpKqaD0&JdP@U}c5z zyE8RJY^;g0=zLqN>?@5)>PIzS4DRan_1Z7*$BWGXq0}Z^_=1R90O{ z-!0C=y%lra(IKlk3}b@32o%f_N-o-P=5l_U@C7k_Bzyp% zNh726Q$0QKEB7ou5QC#|ToMT8e+>7?2@JG$PBEXKKJ9pA_s%WLC_V{&GiiEwo~%%w z0s7ozy>mvmcey@_vT?qQPkqXT)?LOe?s2#oW))wtce#fTAza`G@Q2C^{4T&xnqL;h zDEE$n9KYRXv9nMtZq@u0ilYU7p`d8w@x0X%^3-{khWjzg~bJ!k%iVv=%ySdX&J@&lpqx0gtWzST3bRuf?(&m-m8to3=;h1r43He01;cYKa!73=g6 z2xy=L1J5uqR)+Y;SxJw~t5JllDXQ!-xI$uxHn9A<&UyE|;Su zYuHK=)FlYUCl~gug8mIj*0f%kr=piBuHSqVH{m|bRW~fs!F_%7F?7i1Rou(`Gz<=% zG}m!Ybo`|YPpAzh;;W@}wQ}wy`78MjH3;yj|NZ{*EVRZZU=dxyL#4ee+9Y-*n@5rLh&hldrC3c7&x41`0w*3OR`JCH1u0?12k@E6-V`31&AG z#qa!FALZf`{LOikO#@?C77wEQS!I+N#|wb)@)Gf?x=+NY^C*Kucme-+aXf9HA_v+i z{Z4IkB^hcTyxRqEi9ZjzndE`@F>g}{uzkvmH(=dWe5{Fi7L@bk!BIt+$wlA~0Zha! zw4Y-nfRzbckNc#*4gwPG(-}z#KG{55AudZ5Ot%MsHFvwmqNM#svP&^eNai7FWtwEE zF*a_DGS(%kOEwW`ib|Ii>TmNr{(;AE@&SDfIazTvsjCd> zR4+K0pCdyM69=hq1S`W~IS6$R;iPS%4Spr)%uQ0-L0GE20|M@&oORf|F^KC<7su~l z{`3ArDiyh3|D}sq5~IY@sV4jE3uw?egb}^}TI)DJm;`u3tTbY!2$RbQ_Sea$_}ZnT zR)i2pqrKVZN64a^^aQv?`>)acC>GTWtFy*D6FO2Wnnb}&P|{oY5X|9Qf>~XP zc!tG02P~v06sFq3ooS22D!C6BSXfAX4fFzC%}?Qvum&>wsd@LPVJ22v5~SQ$QO%-5 zr&uN=Q1F7TDK^@N0X;E38y_`4$!=D~?7IfCz|( zuE%QG6_BE$9a~T!q%=KY+@PXcvc9O=3lPO&00Vr(Gd%1(016?1`GIjlzVHs_`yRlh zM~FQNzyprpt*GE02(inOuEZ=u=tx=6iV7ZtP{&3OguAS|G4@*FBodCMZa)AY*s&~Ki_b_SCJTsj^H2f@ zwTNo9!k;dBAn;FEfirioFa*FHA~m)V!s#Xc4}oTaPpGYvD#doNHuf=_GGG|(tB@k`o3lKeM+~l?LjLto2k+_kPd?YtU^Xd5v?-z?5berfz5UrfpS+Qn{hfa6YnWv78L>l{a?KT({pl_dC za?jDDF8oJ0e?cNdVq}b^ph4_Qis`t?=@O#tn3?Z^8ISADsd}zn04m?|uXN^P+0uxwRbkqq2 ztI^}h@{WP0y5H3)S^9yYKP^zq<*v}T*7XZz{0 z)C!TnaKbwzC>Z{ca{}Qh`a=kL>A&E*Q!({8P9BUG|2=|GP4U9fL^!gxoewN3W@TR# zfhdY_6A~-~gI0Tt^%Jgv!cic`Szidx3VKB-dLR>jm(Mbs!r)=~ePMLUihe0Z6(}J; zDJm?s2$_ox13-8;oaR~d4^38cya`K>&u~eE?Za*Dm$`^H|?T3L|ZmD(r_~#duo~k6LagHn#xw zhzz(ZfV^YPWb$0u6hdub!6f( z0)Wim*d44dO&d}<>^{#FCVm_sspl}c=D&3G2sIL1U3;VoN`#*a^GWk4POP;e{;N*7 z*;3Ikoy$-Yrt;G_v6gwijPI%Gr=l*$&TtH)$)&BNJ%mED=aCb~of+&byoxHW!$PP= z{TC^)ZYr_ppo4QQ_u4EjQE7adJp|p>gX!4WxD{2^!I%gD)@^5l1S&vNY49h{Hkj2w z%m|6+eY2KX*x>rRtQu&tRWIo!#0B}H$zso$_9_j-SUPQxI+r;oA zEPfbx2OI{hC%jKAopkfuSSc7b&#(jr(8Rqe6;_dp;tH$~6ql@3l*;-HVOyD*?CQ!O z1zuuM{QDBhqZQH6*mu8td(k_9-zM~!aq%L&#=h~d=x958Pg7KCK786kfucH{15Xg9 z2-uJ!Z6zpAl-JhHo0fU`NiIjz8VMq34}aLU!7W1I0alV_!&8r}?wO;O@yZ2+E$hwK zO}K}we7PkEG;SB})s2HVbJs{ljzW7nt3ZA|CXw^KW%6rYxKy;Xhg7s~UW6y^&_@x7 zKm;UhFTlw$4#%URRiI;WE~xVa*$g@7fOyUtz@K%@kDmnBax6qjkp|Tyd@g~)vG_#J zJ-TD1;Bt6vJi1-sm0mFixIuAt*vTPX+E4s81DHDvc%;g;eXIaA zo>bpBH7k~3w8#0B6to98q#OV(X<9-+KUpl{k%3=W0SAJE54fK+_nl&cLI8dl2PPDhU0vb)Mro&j z^35yxnbH=R%IMj*1o-Ou){P8X z_j`lZ{sr!XlJxW<3r>Me1?R3<>$X=r_-?G$mZ^mwKnp&AX&#gK)lCk;$KzxkLK;Pp zG0UYJQx!P~MYxcsS)Z+mQLDvDJeI}xXBT)uclq2q*HAX4qbpjQhiG-~253xeT9sn` zLih3W);?r_2)QCuULkGhxa%xY=p6d#xjPDg*1-DE>K!`Dp!`}o%NyZlgp(m4xdo@4 z9iL20ur|<8Rh8`$9Z~ytr4IYR44iO;hnCU81+pICgOx>^o=O0Zli9~LqUG!t`x}Bc z_F;UFEOXX8v$hs`;Yuxy>>JO=yh^?N%9A(}vyZP_wZkv9n5ke4Ovb3-WughZ2mi?( z1;27k%XNL<{H)nLs%Y!FS|psN@m9R;Ly4<6IAA97@xu)s+x@eJ;Tu zH$_;5u9e_{sZr02(}m|IaeX#ym+LF@%ELXcW5H}=Sj(^`6(bNJur!GpGC=qg0kCHU{Qlw-tu*z>XYkG>zCV?GB%{RW^pV19 zNFt0h(pv4L#LP-I;%6wlW0 ztl!qys0HYIFQS~j(PxpCTJ{Hep_t68YBTm@}WSUw+vdu~;sX;PWddnynP!yhn}z z#tg+lNqdZv_z9U?j{tQ0!LhC}iv1xOq=RYp6}DW9FWM%}Lo>l7_EyL`1f>rsgorir z_`_Z+I)2ElS9*Bbvc=?t_g3yaww#7~n6}C`-uVFFo@XuP-tqfm>Nb=r3@n)mAk4l0vRPQ=N_e?B|3#`5a+BKHLYu$rR$6h*kZ;7cB$4%4Nxs zRqLAZ$ko#eYZrf*edU^%R8SWWD%9q>_P@6y!JoK_JPCPwrfbJqQzX-MI}?vl0fB*~$!bs+TFN&b^R?L+U!= zgITQ0xD!(MEksj!0!M%IHM{fpFKCY^=F|PaP!OY1Y&ZFRh5K3K6ltu85Us`1NbMrR zGNBF7RB(Zo+I+@1t=Q-yw;Nh_&m6=A4PfrC^Ajp13QFY}gFr%<1=@3AVKU|;{9TKd z$jhRoG=#VShM*jR4SaVEs7T@SAF#LX`k)3tk**YLmSSTA2q-n1R7g-FV-FzGkD$7G%seU@4acA?|x)$5NHTNAz;e+ z`}K|FFEB_H4V`h~h_m!EvJvv-5V6*Nl1hBQKi@?d?@kXF=HX;APfXc9E`&$fYH(PB zs=SD%7DFGqSa%sZKPotrSh<@&&kVfs3(!$9`>ZgETs87EthnCtcW-lEK8wU`y&uq! z=getl|7XWZ16fAE@hrX9NlxT@C%Lbi%y@t#9EgQ>3^A?OlpM4(WA(*sCT-R12C+&`zo~NNM$WhWpg$J~}t4 zDT&Lhl_gE>hZ8Qs{3N7h!6AU50x=;QC-6onu96It0i`UbE#8xMk?BEsu=}ShJ%LWn z9N|KuYG;?Tkxoz`@nrr}x(I^Ag7EPs=aKJ$->mL5t{5W#p^4xq@G%(6loew^?D}#Dt1>Y*F-kH{0w9^x zTlu%iV@rr0A0Ok!EFmFqyz}H%8=2#wq|aY z2b_ZWt76#L#rW-vR6h)Hi;zb~xG4Xm^MeEMQ31!!a8;R0HeJyl+ZiouEyPE%c$ zSI761IMnl)J@8mwLkskSl~Pt>feMbHo3hdIrkLCgo5yNc&$XaHMim5s#E(?J9} znexZ0S@(%?=sRdxr%qz_!xPY6`)T$7JVv-&2EeM^Zncn~1>7sx0t93rYvko$wQ71u z*r^(>$vudV<%Z)HxlUdzqa(tq3J4dzQ3RljeO2>Nta2Ks!K|`JWm-!RU|Kt7Q_V9b zjq55(xvbMF0ykvFi%9fxXJ+FdX4i;7)&hC<2Y$Z@mG7(folHpiyfXR8q;n?y>KN~H zlbR)fMs`du(?r5WmIwFhEC+F&v>I__CYJIYUJ6H2lW(^k+T;g!?NV#N{@)ugB?x#n zjI)bmrhT8VA?KH8z_0>rbI5~ECbQ^EN7#+nYJ_@)JR%w=t1IophmR@5foZ`3$RWfc z5Xw*HAh)rUAP<0qBjqa5&=%oAwjmZ!(8suGe6VI{yokR?Vf-0l&nuMRs?(Z!6{zb_ z7Wowz^H>J7wjwllfcmTXC1OJP3#_{^1YrpR3w@j`z}WH(+@!n>&ATT*fFi7?F2bxz zJZGes?ZP}{BRUHylZ*Y@X_nYyH3+Mk;bGRuZ)0y?AszfQ=yQjS{mEZg)u}QP?KsC6 z3`W!-xCBwjHzX{WM}GzU9R6OhUK)Y4RB|0m>;8YZZ?)hD0m6*aKmHKo0m6~#J~u(y z0gS9r14>EyF=^Rcti za(?{{EBo8Owb06*J^jU3(a6I#H8KW7(QZFQ;1AQ%j2i=Uqhxpv?Sj`Dgj{rpnlTAMH~Ujltrfpj#BipL&8f2mWUrtR$yXq ztk074JGe=sPPm>&1H@r-WQMDGr~o_}B(AxLM-5G&F)p9RNX33RzqcrZ~Lgp6~8KBYUR*Mu;}I_6x5YPgzf(6 zGp?b+Al-~Q3?mg)f7NSG`>oLVu4!t)da-P?Wk~# z5h0eK^mQY`DDCDVnlI*nvn@K)Um0PlD1yv+3mu|zatnH)v5Br1k!6{|wHSYX_v*Br zsN}v_kMX`murio+a1239K~**9iuaPR0`p(0;huR;T%A*2A_7_Y#{yyVp;4A=bwl z1jRCHd6kaul4FH~hBM<9=Guk0pZO{JNZ^|0JzZ)md4(vmS=())4Ltb6NwQ^`N$cYJ zbyBd<`or#FgqT_T7oWGn*M1A!q!%BbwUiY_10PFcx@^qyJgj#iYsOrR46HaB=CD(l0-NNp%I8oH0BM)*rT9EWN2lzIZt>C{;h4prsG z#eEdXIBh2ZAQOj67$daPn5S71N@xQgW`C|fPHsTd-XGec$2>F&j-e8q09q;!Ef|Cl zcFE4lp$H@{AB)rb@#h9e5$)@vAp~?K{%~oU;(EvYyX-Hb1bx<)7BOl}#5o`kteFGn{N+z$F0me*Z;yta%M%HE;Y(-j`VZftdB}u)`d7}HXPU&M z;j3<_6B9gO)LF8C0i-ne0{=Pn-Xl^uGEuU1w*Q!{NQKZ+Xz4YYS_lx_9)aN2?ZoTV z&=y1s)5V@{;F=IG(Sd!LtFmZH%ORZ}KIa)Q(KiQXh?XU-^rbS}9wgnCHL|&7+r`h$ zkULsl$KRY!X~UHOe8>X5Z^_axV4rR;xVZjVR3{EYYqTSDG4k?STLemdWlOUf|MO4fKZxQonTy_ ze@-Co$V4J(W5oC(PBFgJtAgEPR!JMDvBVuZKkq42sP{yqwDVTxn*%SLHKLpT#RJMD zO-U$mE@?RtF(BRK&AZI1pf4bn3G76*SOQ>Vj=J<#TOAlMYII1~#>B#O%0r6^oJ?b( z*KFGYvK5S(RbQBm7H53GT~HDQ9a;2%`tb2iEYo4dOdB$ z7tSLcm=@*iRdRjE>~r-2TQ-SBTP-V>W}r77tl3$XPkjZteteHELl`5M&VpYgsEt6g zzj(wvi!;`iUbQxXvl7ZX+`9yYekXxwlKhHQ#tj9)M{e~F+BiH}O$T4qmMjcyGUb|F z07zcr%EY@$SPWwcWq2knhOlj`jZz7^fm`DN1E(5QWz_;fr|oh=g+JtTXDo0upNV0$uzNg^hhubi zu%AQOgoY}VC^QrROm91?)%U}^#K>}LUF3kz^mO6~sZ>HWj*=CYPz+V}ld?Rd;Y8rl zYJ^@=lrp+L>!fQcn@+R;WNB;&-DU488w;(Utg}a>FgsuOu^3q_Mge~D7CvE+OuBhW zL6upj=AjvA*)$l#N4JOIA!%y91oJ3jnZmqQ1)nni6olA7sKxQ+w*i@^7UDDyS!Y-! zv_Nfp;Uu-%(AWyrilz#~e$K=3>l|1Ss!{ObH;~7{KVr~4mi9>rLCOPA1VF_?DHz>@ zFp-6@Px!J3{6cNZHm7Nf$vwRM@)N8JnO!t#V30C^m~CHFB! z56jxx1G?@ED>a$D)BR8`7z4csxiaDnc=#NNv}=K{i%R^{@lLg@}$ z>7^PItI#M?+U@DWYGe(0Hi6k^kHR?BykOQt_53X78l2kJIgglXp8AICkBu&9_G~G| z;DCO8eSM@5l;cj)=^0CU6Hu@WI*oqik~Oc?!IT{Q*_YW6J_Zpne<3ruxdV7?yw-OFs z=!1Q5iF;T-&Rth2zGAKL00MwTRgJWgYxvdW35!T%PN>36D3}<3otxteyvd8dl_wzS z=+r!a!=&z?D!2p`ig|_JYu11S0mSYupfI9}gp?+%x_`8&`%ELibbS$_ED61IU6mpu zP5ebMKI41bM=2R*L!FE?A6F|Tj~zMHZiDc}TTG#0%pL{{-DTjk2v);B%n;z5ac}|; zNlR%qLnElyiQNy3ELmQCF%hmpI`SU7%6R5CCfT%b?Yb>kCxmoTs{LcF0v$|4WfnmZ5 zbVI^)RPL&MaGHUiXld7SSOBhK`T}(2W=UY^Z+o$#exaH%6N*Zd(!Ma+G_)JXwY_ao5){Mk@ANKgl+# z+-u@PeAL>G3*Z8y(Br68U2f+2pqNhik`UBGbB)rwj#0^H&K0C#mT*kXg%nPT}O^I-Dg(L<@wqI6rJ2;T~838LKoM zB}V|Thmhx#-Mg`3$D5&Xl+N}N<-#?!TyN3l`e@2}p`NG;173dT3Ce=c+Jis*W2hg0)$exnus zVWZ`a43f;$O92%%{yhULb_-!SvzWHN2MbOXsWEVQ(XdrgIa$eEjXK)Doj6u$MTg6c zVHA9rdq6YUNIVHfc-FH&^i`%Zvf;ev{C*l1I#WxGSiucy;gq2yf|J;xc*2-L?&A3n zzLPAnX`L40JIa=kiW4=HeZ>JhD4Rh#t+NW`qp;WxAQXX#K*b3H3uWGhxK=hBA}~vt zh=lFj=duKps;gdvFi-0Jn>NaVEXS$e-^sIg9?sZXoe5i~Fh}zN@(5iPGht|p&a^>7 zi<=0m4X_5_JsyP%aw=KW7eHb&(X>Cieb>h5Fr4DKHT;v1ysHxkQ)rY0LlhGciIbR; zPsY{`0%8LJR*=1CM@fE(kfhU1kGR=Y=F4FLiB}>Bu%ME8(C{IMYMK$mlibS^S}V=@ z$SReT#;osT(w+%ZQ|2Go5*b(g4XrCYOD=#@ z!~)j^-c=w#lyVHc;>&_xKo2y!wa3HOMYZAm#|!pxcic^FQy)yeMQS%+ixL=OxlWvj zSz}A7#V(&9e8{=h7tQnLm#g;MTWBb7t{Mi*wZHq9Kd>9u6LcaX_Dx^^Xp{~*^nQzB z%G9=&v+!(i6_SJ)s+;KRE98dRd=>KmFgZe$w$pR!Ar(Rtk}rQt0z;ubC58wDRH#YB zD?yM|zzwkgl!|gOR0WJrwx1h6CdOB!KyE=^F~SPM9aylwANe^+-N>}h%(^!@F=z3+QZ_e{?! zjb&{k$+9ikCXy{<8yj%UT4e}JNJtS9NPtj=0!j%~Du$$3vlJBkkqT9zijZJ}Z5%*_ zZETRN&9Y>Vq}hAl_r3e(`#E>6V)C25^IP8ceU|f_{ha4Kdz0>;d*$qMHvhmawsG?y z6xigV_S^Ch+9>l-0#P>l#ys>ZX)pVWE*vW+w3_<%Mq|G&Upq&t&~NdF5!9PXD3Jqx z>hjIpD4kH-93Ev(ZOqR>-KTw%(3sBVkYHc_iX*LWo6*`Blz@76-|~YWdH?ckcHg0j zV6I4|4HV0+;PArnq%t@*Ip!CC2f)jxkd(#58fPr#P$!tUtV&SeXQ5J;r!WkGGs0)) z+Pap&+2qAs6Xls#zd_UqErpcYQ@v;4))<=iFm>~y!D+)J$L8!gs;)Ftd-x@#Jn{6} zSU>-TMBWRHX~%NqpLX<_+7Zvab9wzI{za#atuDv+Kee1JtMO{i#|?d7&C@Mtv?Kr1 z_KjPYldqo5nv`p>y)53_)h!G>-}>Tm=IkrWg&6+(|Kfk%I10&r`WKfCFTJnu=gH-t z|D#XkIDW1TKM$9m`>D68S6W%`$KNUe002M$NklUpvSX!-MA~r47+C%h+r@@7`6zrs*Q)B2klek2Zw=tjFw%4L%93 z(Mn29x0`Z8nF^br4e@)$c?j9I{*D>=K{o#CF>`^K$yH=U`b zel_^EY}i{eR;8Dp-rjs~Er04y{FdeT@Az1psMh7K1H!t^DGI<7P0~(cNZ3p*6-f{R zB*hTEQHza2NbgJv#pT+YKaQ#+F@;p9^pq&ODF`X$K)U+Nzm`zTW)6vWlpJ*Nc1Lqv z-n{(8|MP|A-+ZalbZa_6(k6;+;|RRVe$NR)Jy$HWUEkqzWuCI}HzWlm;%rR~yWXM+ z{~@qVkT;@Oi9>IV`E2U%L0ZVhR0Hb#N>XB9(UC)(JaG%TIkP1=PTfJ`Wj+TpT2R+8 zhzoJ_>k(yphuY)RPXGKav4Z>r%`GYRFgtf^eeJE?!|h5`pmakf)ZNQfjEQl6__<8+ zZ+&t(_4=vFe?Q(?bVQAL88-DN)aZ!K3N%8_fq zIW=m&adyp`2^;y+zJpmm-(ybWsI_JyKay^&F-?Gv0FycNMEAwyX>K$OW?&3+m=W#4 z1|{q0+Pw7DXTpK`DCY#D;g`_gjee8Y&?6(&ZB4fuPw3OD^xPq=VNQ+l&G4tl(EW%K zkC6JYmaAXAUj@-QW0!%cnk{6z%b?nH;wQh{Ggr zRlLJKZ<`+P1o#IL;N#TjdoA+4#`C!QGAdd#IsA1S?AhjhELS~(C(TpgoGCB!8G>yt z$!AY~lD#d|;}$zZBRnmWFBNoi5^s0J;5y~J(-eUuzDKJ|&T_H3LafrC~)HIMf zYfCKXGec-8n1bIiRQ?IXMrFn*)(Nt$(rWI{UIustpyuE{CX=@Ku}an%0vm^Q_epVA zctNFVhoP90@!jEK9?^n{NXnInv~XdAThj~xo@tDcfCIl5 zM&Y`7fRh)kZJjw{m>7!r*~|q#7_jINdg*QU{9`g|4v6Mxm+N^YxH6&nY$X5K0?Q9V zvryUgKy8%}w&Yy=mH+$G%Rl?n*Oq_%N0qC`8I3P?mV3WIfWo;rHj7ClEr&>0z zSrA-ny!`>wJC!w9QC-ge>hw z^YLC%|5lF3TdDr_FfiCa()OAZ;QFu~8BS~nakr(_$R>5(pej5YV}Lt17hY@|?Qi+W zvivuH?GLmtJ%9dWrr6UVSf88QEVOW+j7vK$9k^0FdO33`^RcK$(Va`h#W9I(f(&u&dk-<@rJ?5V>g_=P}xK~5k&6jG?a_c)~6-@C&y z*f0<357Cf}UWgMSLuy1I#7jzXG|sg?Ltb2pZ;3N;cB|DcV31}s0QRs5)ag@e1P-Aw z80Lz0yv8@tb2JT74}nSOZ6-Ys7UNlvyp6qfCLQKgzNj5Tm z+YS#MT)XC3J}r_OV?mrEGoCZ1d&wqps^w;c@jw3DziQ{WU-;!Rtxhjr&Vg|zMrzE5 z+f1@=f41|*4)oldhxI!@+Sv9kUw-+k%dh<87nWc6!nrm$b-K&74o0l~L#37;%9-P2 z;>&3jkJ|yYwUExEIJEizk3tR%d#n}QCcqocy&P4KvJJZw!=aXEU;3}b>8@T8r_YJv z6=sfkbY)Zao$ni3ppEsB?$A9_dkZ1;{xCCUV@P^G>3=sBnF)@e(frnCI3Gq6;8WOM z4TL*63LnHM6#yMIHiqyZ3F&N=7n;lGexW$^zp3cu54*= zPCbWovWF@Pxg7zeksYbMBbgiTV$Y9{pmjqGY*TY9#C5AZ?#_jj=6gGz$Y;Os@^Z5C z4=4f@gng-slOxe0ZOgZD?5QVXxXp3Ho6Etx=f|FG-v9}Y*t~3YEsj4028!zsk%&mZ zQ7UiInNhWsorCO>&M*#RBK;A~)T(UE5W7MhYi48L5z-)epNHx7&QB!Gm{~%F=j~2| z8m)pO`Nrw`X)^ji)b{=}TMWo`S`78u0$@VhYX=c?IOKzg`j5L)g(^my?Fh@7tOxr`BI= zAV%CcQma-=j8QenVI1=QH(89XHR54#N_CBP&^;r_JeyjuDaN7nx6tY~?>&p?@8%a^ zLdfl>71AvpU^2pfI8OwzAp)fxtZ{-qwDv0@-=>~(3_2%PX+^_8?uE3p>ODC?4@S_d z?cM*#OS_l-Hp5&$8?fqx%696j0pOVyx6h;__=~%h^*C!l)r53>HT)VRS8u~4ocP3p zE>#yJ+4@bDjH~bndw14xWqAj9*+oV|+u>A(-<;=n`s(uPt8XOOtBh2h{GlDsFIPTy zVformf2p;I`LzcAJZ{Qp{izcxX~9yQ4QWdZ&t|En)6(q@HMUiQfZ4-rGla={Hr8G> zHP4~NbwRr$FX`sA2pZI_vh5rMyJKf*%5{1tq7la3p823=q_sh*H>x0XDAzdc>QWBp z{h5$^gTcnu=2Ryz0~L@>7H128s7h9+V~RG}J^SpzDL1at($`)&lWDc5?7ahP7Evkm zZxoh(_q&VU6jpv1BiF~>1mG6CMycyDB(T^_a!bs@z#4DaJ;D1ctxH5JIj-l2359#Z zBr235Jtqf8Yuh~t6I1EmFh%||W(-&{1M==qp2%n>&Aai>Ih|BX$zgAXKjkg(g>2fE zElWMQUi^8bWQCo1VRtSaU(_P3guMG!4&*euFw)>|rMYa2eX@Gu?>HPYc}fIidT^BvBSC;m`dq^?KAub001zjtIb z=FAk!={xlccXa>00QpSbaLo34?LOKTQWiVDl?Jn+jGbL^QhpgZOn2K7xe}5&cKd#S zN6wG)Ymf}9X#)=%tC&2=*Ol=v@bas`v&v!CYwZ9}8G8|Cm}-~a<6uPwuCzE?y4Uty zWtz97F0SS{eVB&8aah(tT3sz;ARz*c!fRUY;~W(>30VD-18_sl5$avi#Ve`yEO22$od46+Ag4II*nsw z=4^>ZQU;E3^og>lu~1I>s3=S_uz8ztjXDf*cC^;~HBW_rC4xQw_(=EktT5u;oy!SQ z#Tj@PGnJ?CuaG*yAX%U~fujNDblaTx_2u{OB5x#_SEss_Eyt8c13va<#E0rWj-0uv3rL_ z5dWx``ZVfJBa({_DX1w)ofHT7&z(`8IfRMsIeHDBLSt1D11ztU>fv%)=#~88& zSF+u2gh;EYp!F7^2ajIV<;gT`ytO=QvR)n7dw{9E%$(ZWQc;qX!lO*N$4Q9$jfpex ziRSZTfA+Ul7^9AFf4N28m)g|AcM8z*=yGiY*F6yd>0WB?5EjOaKzwJHV$v9LN1Tra z!X80Ym|{PTI=gN|VjdXB`aKvro0ECh?HxQAVra90;kKPMpkPN*qcOGK`ojn|_YD8f zsQg`%)x^ovoTk1RuP9(D-zDaTVUg4k?DE0q*Hi!t^{wx!y~Yop-ZsI;HlHyzHY$Z& zlXJC&igrI247wjPh&Xp_cw*Tx%)aXp@-XuqMZVZHX_|9ZUySOB1kq$srfu2VKTf+I zdGGD_HpXBJ4z!`3J9s5ir>o;TMXf$ukN533!7#zXKS8%@jRr_HQgfmVN8fvdd zP1L-$2K~$@@u=+9``ve^YuB%qYMcZ4%}nvlg*z2RrD^>9r_L^Ko{nGE{Lyy0ZY-4f zP-(S1t{X%0&1ncb((txbuVA%ZrK@EGZjDi7ToxpHGC|)xg~~Vl;pg{8=PS$Q$_eku zb$lWf`h_NPAg7*?{MVBBZ$-avJh;*ZhmbSjBROV!8{>cEd!JqY<4-)hY&{s1a3Mgz ziAU6@+CX^^u$L;Qt9_+J6;K)Z$&J->i0fd zD{i{KEu&k7>sm}v6Xn0}WRpE?vG2E_8?zPdrGJ$3ggw6+@q+OqHrU+`!iYhvrUl$A zQA9bshdJ@?a;5~#t+Ds}0q&tZ{?tIE>y^bn@AElARm1O5i88`=n_Bc8Eh_FFo2dPE zgM~nxjb7{0e!jf@zqQT4YhQjn16cpOF`|H98ynju+lUKUD6sQVH1X&E*mo>1{^5^? zByTjZkR4&l7DB+JuF}}{S5SZKRV5KgTttB}BQka-j&5G;TUtTz97!scO}z(P1cqq& z2x@LLym_ws*|FM&*q8tyGxhY95E&*vS)a9`9YjK%b-%8S19L&J7ZV(EsK;p;+%PY; zHy|J=_q3IGn2zvo(;LtCfVjqH<66YSXMpIPcgWl%QfZrMi!%mc9sQf1EI$#2HYtsN z00Y4o#Pg}tq=)ryKfs(GgozlB5aig`!3pea$Pv8)w^EL0oCgoB!I$0A8V)V+#mqj8 z+7wYRXX_za(JiwcRDPxzhp1<@Pt7-f^OB{Dp~%cVd$w`)nK?oK=hYW&qZP#{CZsDG zQjW!H%wt2e@mA{oYFoFjW@o)t?!(?3u2*fkYwfqx*QOO^jZ#H7r;^;tmOI!9upfB# zx#i|pUT>{ZQ!0mb_VR@Ypm=j$$W3v3((lIA`^)D)^ZN4h+bXxTH-$IVWJ@aY#<(Mg z8C#L+yiz{?O5A5-NV~OI?}mZZ_=2_BQWo()(@kJzZXQ zNGr4TP8*OeWNX@eYK;fKYyEZ$doQV67$bW;Mi4$a8l1L60MCY~KH77By^TI|@>&~} zHkK7f&?I9UTh76?+lvtN36l0&Ct?KZb9zMojUh3NNZ%5S{4xbU;nLd-U>fLuYZAVv zv;nbt>FeKpX*I@{{r{+t+Rpa4ujcVxiD~}!AADx{bHC@q%gVPu*MJb_6KjZZCj`9{ zGVxGv2nlb5+;S`sj<`PG3w2uv32}NE8wZ)`K}?B2#)P;ft&rRauOS1 zgAFN%*&{F^LHiCE@(2M@imCHn(?AL%uY~}+#x%r0{EirzhT)PX3b zKg>>|3l+T+p=Vc{{%I8c?1zvyVyqm7hzEd~5}9K&u5}Ga1t~-&GC-3s4$M+EA;yQW z5;d3~G zslOW$A{Zadl&bR5%tP241E6Ub`)wqoN{KdxCIL5SY9cXulgTda~ ze7-TSc4QuyBoIz)|4`%Iwmcn!e*cBW06@a>);(slY4a>>i6nXV@3lbJFW+8n9k`Sk zQCaA2znbd&)Q&vq&F^N&^dJJdk#q`Qr!w-r2dV1CJlQv!s**sRdPL&Jb=bL^Qe{`N zo9-0`dK7bblqqri`Mi(6a?=s;N%Pd~q%mga9QOx>ldj#!hQ7b2FzB9`>-osE5M_?P zOL@sx*8lWfhq)jPrtk*vLWBYv@8#5EdRzfrlR0e?fA=sW4jaRqrvRm8-Z zCDcZOkBnb^3f3p7i zT{05bK~9>Cu%2<6`-Fs^9g#WrZeX-HqRxc^=+?Ert!|7>A!T6UKwe1fc=uQ2&PcMxzlTT{#|} zG`Hrs^8Q^LK6$S}TusG!m{THw-=VU*mgiy;Tau2Kv(v8CaLIO=@OjPqqf+n8ZR;qg zZ+>Rqaw2ZHF#z!QIVD$N3p?D{&g`v6ajo4Ake$3SWV@67_?gdV@5V*$cmIYs=@3sS(vX%QTj=ec)cO-B5)B9JK_r7!A z@@?;buIf=&(jFcyFTL}*<-}7@rwv`LVpJJ;Au5Zn+klf($u zpVNHMFaqA_LxkaIZ;WCjdcTdoV6LZyH14^r|E;&N*3R1R)b{`X^_Hu4yTAWd8pALX zdP*UN3rWCV``npMlc_+)2jk*Owb>2WT93qG3NVqX7_$}!4>3n=3^AqJLM#a?JjRfX z)Cm8EIba4HZG1Hnb09mSaRX^fL!fbBFeF8969cEJl1`)$qCnuDp#fY7NvW_H4iyApx23LHxu?ng;Awye_(B)a6FTeAhb&w(k* zf|&=2Xzp?o02`r@xL{#yfCnbV<$Zi4t~Ut3t%v_c6*r!UN|;SM=D_(#V$%>veP)pP z9gd(#^Lehm5N7K=Ou{5ZWQ6802xGwH*!~!xsv(#u{|JA9zAzrYV~*!9^f{PWLs^36 zgW$m*!?%|9WjrDvYOkZ%144hnU^RH8O}7=Xd%8YMvuk`v1VrFldB^UppS&GmK8(|C z%ky`uEw4w4qkZ^?KHhq4U(TF7w{*;&4Z+pelbI%*R@*&uGrQu6eRr2<+m5&r;;?f! zQpv*@sjh1;lTPYp!%GL)lBBq|x^?-*U%ITSR0%Q>e0JgOLRt^w#?oyc#MQFWv+t|h zwln*s-0ZF8yF1zUUg6}iF8AWdr(+nmQ-|)$^2$xRA;}gPGkY+t2#yCDCN{UJ*XO+j1j-R; z7BfQdeFHP$*S~_}JlD@~?i+uD-R8dbT4xHNq6JLl|9cN!F-=F3U5}Asew$lxn$c_R zZhImx`E&1R(QK{eRkw3fJh3&NiLei;{v+9%nL!{h(h6cDViKA((kWXP@*xz@kot&> zwAVgDhs2)Ojy52%>41gnh@KYVM?{CX{igl(rAHKq4Dpco?0?@qKjv2NkeFnnQj(4s zkntgGM|bZ#)L0`#+jLD95s~;H7*6T2#0p&2CjbCyDM0ex@sO>>> zfaClLV29bIEp56MG4iah=2*K_<(p%*`9^8Gul?&^T{b5@w{7h(>dy0XRQ0B~-lI%_ z)i&{LtN;ez$c^mJyBXULvRk*dQ%pH4Ww9UkM;qHz+hYnOZEppP?L`kBwy!|G!ljtbpsx@#w0)NYiczkF`_?hPTGII&!Oy;pc*&C{L~A`uY*q&7fE$N}9n zBFj03F;GJ-K4gZZ{|;mB2g5LU#_V<_xq5?dG#L(vE1 zW(PAx6#O)OYPV+~s-lXJo~k^iM04SXqT3JgG-jbIiDk4Ggl9YmNf^tCJe<26o*E~~ z2R8MA0lAM9wO+<;$D)aw4@a9m`7f9k#$=qB8YW>;F*}519Tk0W-%}?dq}t@~;LJX- zzZ+}cHSbJ;eGxO}Wvrf+_k!>-jf1Vvg$uR&cE9>KltyD5%o-gl8j2zCfspjZM(S^^ z4-B7wf(oyF3_+imiO3D7qcUL|YRi#en3tFJpxN1@057_2n5RC$rhYbEO9HP3xQ%&E zHr>b3^HnrgSN_~kRl(h9#6VRu-=rVas7hyQlk$CiqX?92;( z^WwGYxM#~Z?%(j82Rfhe#pT#hAJNdD4}Ptd?ShIFs%sG=eniQiy#iy%%CiK$5CDui9WDkiax9 za6yo~+0t-_C?iPyyN_g4F7D!~o^CxkF3o2oX731E8?-bg9LM1}{CX!b5jTlQdcz0n zqg`|9-w=(BLl{N{JBPri&tO6#_xQ{yZFfGik;gIE!^c8W(IN~Aqru4BFE!H(;Zf5i z*wAD!Hl~5`QsDkMG^C!t(|+W0B!GuOU^w7BFLQ`- z$e!{tCWMaWFcBsK)zCP=b4)-Cps&_@*0c4tD=iIS(~SstQ+6>^Vq4gcAztpSzIVCgy5AB9y`M^TGpWo0eDs|mYij%R?|5O^_aA+Cjh7AFhE?Ngqij2n*Y{L z0QLk@UweH++Pjf->fSYG(05JE%jcMHBiNS2-jXBpPK7&mt?XX*bg+Fb$Z&Y0O_DAvp3N@3eZxf0? zh$R$9>JRhixltdz_Z&C~7h)(YMtDEISpo;}^h+Kyp5m$ZIw2(#&3&jsinR;dLiw^%Q~ z*)vU2Y3K(HTEU5cc(mFBfB$cPe%aMe@9MT6{OGpj&-}snE`Rbb|L}5rd3kyM*){?F z=|A40UT8vbMak&$+|@p@o!A-L)I;gW2lg&cCn4`7`R}$b;c?^LoO<)Hjrp7Nq~6W( zbn?Q(?m1C3a?XIWI+0DZ-}ykjl>TTQHmTeL1hWb z{@Rd3sQkY?_C-4~qgKl);Kz;iq{9c_Q&pEwd`n|)zMMrHL!`0>morzWPknEZrIJx6 z`2{R|;~ee4m?1F_w^%w!sFa(v;n2cNx@CyPbK>NXY}D$$OEF~|bH>(VWZ1T^cqv8{`n1rD53qj8TwQm_&t5MysMxOw7N&WhWYg@mC^z%&uLn+xhxH3#1!3k;oWiRj0=X z!=w_qLBL+d!dq{gA~<{veH_MtQPFys3xQ%8w4;wd z_o<&=w*A~cUOx20KeQ|#`)EiJvfxZdUx+IOBwKgItf_)ab>0r#S^nOC`%ICL_bhwA zmFg#)>kO+_ZgVZ)aWJUw-=MPR~))k2ZJUW7FOl*MBmJ`_6Y(l=1wzF$?ab z#_lNr$=P`~t75x8^urCEB?u{6Fk6&YL$=4fu$d&gV+`9; zw^w3?lu-piT!uqv&O+x&w+@*~l@x84o#H_Fo5Q%R`ux$i|B4!Hg3cfBta zAlPh*;2qW)10G^aNGy-e^)TeD5tkv>#&7Z(Vrk*20pGO5Xp4JQ| zW0Fi4$jS*Ryk@&5$%jz%X}y^<5FK$NWKKs-SRmSmcZEqvL0Soo>p*h~S;5Yy0(ed8FMcrwN*!Gvn9EsT@kp-C7I#w6q>4*~%rXqu3*8{?6q z{jZlkWe&m@HaKBQI{Oo1#8`|6vHJ5o4Pl73j?G^J!aP&M#w@I89+==HIkmoayXj-h zjOlsp8NM3h$CRv_O3J}))BaSh{W-#3*m`OC_{X1Fe)kW3>+;!`zqb6Bf1?`vNwVGL zBj2R{^zTAdboZ9SefQeqN)1&pD6akk9h3As|I#06uo3*3Gt0mE`~Q5o_0#`y`NW_7 z6IJ$kM}TiJNCKf9p_(Te1zYa!Ys)i#9oj?602;p(k3;qkrYk{ybv_$2+$eBspc^=`je<=O2A z+tM2`JbO6X`oo8o_sFrnv32?4Yj>6}zjnS7&Smt4tQ?*zC0jUb^jf=>w#QWw>S~K4 z&p|GD)d9lS5`l8q-A}b@Ir4v%As8~tPhMkn=@^WJ z=rS|bkfcVahgRKjMqr0HWV!7QsqI)(x(Lxo2X;++hyZf{Q;14!*$%uXJ>Nj&;?32%-BBkr3SAR)nb3mUQ0Z&#cV_TopT! z9<06Dk=NY88Ig^4+jGi*QS$MzZ zUwXBIgJ1t@_WjOE7~fhhmWF%tPO28q(&G9e(rQa3@i+gQ3 zpS`kcdFtISEW0mOf#mP}AIo!Mkk1?q=mAfW0^Ce8xvVY|K*%o_T8Jc*HKoNe(}xa(?9W*gjylV z)hg$lz0+3WJMAO5wtUa`zHfQ%U@Cjs%xaZ12b? zN!QIG^VW!JTh5nFIWD(_xck#Wp3AfUVvfykd3JUAmKT=g`+vis<;VZ)A6dTV4}EOe z^}$0S;~OD;@XbM(+Ek|@zgRl&Hk(td7oqfZ>h+OmY&_BfXGmii0)aSV@3sciR|qiI zdhg#iJp80J6%x{swA5&mL&SVKhz;T*7^*mArP6k)&OC?N1ITC!^{H`$D@rh9>iZzL zG1L~uLtDT=M%ukSPW}oENO+Tkmo`ioXp1H@Qonu>7DmS$VOO)eF$Jo$lv(p0yz4&w zdDeUoC#?V+Ff-B{b0FO@FKx1+5i1`Ad}R*k7jYs~h88_z#yq^e4Y>+598l9|E6hks5>p z7KM!fQ{JuZEeD}v*T8{kacjwf2HGsv{+c#rjRQ$XgxyalQwB3FTc4-fVWs9qc}cb2P>v zYt0`OQ0b{Vm~6L=@1=GSA!h(7%zi{12piIna0rj8jo_`_Z0)XXQUgJYbt4XvnJ+-D zyPba|5y|AmY(Vfl*5f2=d}G@-rXC$h#a>3Vepp9B(nSZE=YR)@tKJIv(H5z zjD_}v=xG7whA=<@`7JfE+{5y|7yS#a!di58tFBjVrf1!et_FaH+ z&9KU5tM;+rd9+GBAO5!ImQQ@o`<87VJl;H;Ut2-vEVkBYq>9ZORJV~Nbp|=a>Cb#U zpm)vQZ?zT0x-AwsQ&YheBJ$pDdAm)W+yCw48*yiwf{e;$dW28)A6v6UBMor=vA6q9 zvLCE%wl1eChn&f~F7KVRL|Ar$I@%i3Hck?3Jm!6dh$Ijq$4oFMpE;+9!Fi^3sGv*% zgoPPFR4NqYMJyAYXl&GIn|JoVP%}WK=TO=Z5N+Doks9?lJMmbBD1ZEq{pOBFZ=#o9Zc$6(DHCba^GDYt+WS9T zi0Z-ecYgohSpMLTe$%q)M}8o$=-E7sUk&&nZf)GVU+zIt`{Bl?mcRKw|EtN5_`nC> z(cX#E-FiFxO(H+)ocC10Gt816dl(?@$22(LHWZ6p$+0B_NC>UQMK`yoJ1T+wxI++6 zRrmlphDrm=qs?2G@q0eFZ~5K7=>z3>1oRI)6*2TS>e2B?r7RXN(*9Qk9WVPu9!BMT*p(Gyj3?T|XL3YSS zix@Ma`^*8OfrxBuJ_b%QgvF1*iH2E7fwhGkBd9{ujF(zG45vPfV?5)vM{<*L#*64U zHjM>S(vNzBUNg78udCAQ_BW60+0zke>xY59`dVvwEXmz>>OQl_xDhah;CUJ>CV{a) zzKM(^&^RGS0)ute^>6o}sEynW>j3r$lU8F*I9S=~=H`A(Q5%CN-REBObIsrE0_=X{ zw=S5j`MRG!MEk~!&cJoOEv6dWl)_xpYr~_zbz=3aFTa&~|3tX{#PYL0`-RfFH{`T> za{0BdpUw<;+<_OtvG#7K8eV_c*4u2G!*RpI_2nd}j(1=7M<{y=^{te-^V%Ed+Q(3Z z+Ysjyzv%~Z$lhB1hrj$2%YXI9e{ebR?8fDlU;FudeK%&mz}Cn6mselEv;5Pa?|83Z z^wbM`3n|}D+CP}^Z3i~TB%5#}ae9uEa__UF+j#OgVD9WFWVFR8y|tsr&v#IQ90`v! z4a1rFST}Ba>x%PZ2yKa>Ny0FPy>M`&Rtui841IdMXc^2K_F~}Mus@V=h?T$CoyHh zOSR8LgIwlK+8MUCL-%-{UR1{ zVYXnv){My`FC76P&dWVd{XQOI)4r^i=MEk2V{rWjJbPB2fcC-laxkzz04%Mu`+W8c z8y^#O{}Z*tA!$6c7uSt_q;vfpKOSSh6l}Uj+u%Fmy0^i*CQXx}b$Qm>u8$KlzI-|S z>K>+*9h!&0w|1F2d@A~Zx6O6K>MwlX>ca|0Zc3GY^w!1Y)GM#I`Q~UE`nDW_?IG^y z=QGXg3tv84#o5{nCqDXt1Iusz#0SbSENk$M&r~Sl}!XVo1hx(xffw1*AoSkU{@KjBXH;yBK^{mBfZO0BI*(6$M3!(t9&=bTb z0U;TQH)?p#ck2)uV#Vo6IzWRokPxT0-E-RN_d%$BGad_$7~IQLSnq=>uE@l(XKFi% ztCl=_4f7%e+14ca)Wd5GR7Os~)er&E3McXoo4kAheI5!3PXyed>RhO%E|6 zg_yNzU5tGqCH)Tm=5AcH1PsgAaGpKJ-e(fBud`kxx$ANj_(;~ZpA#q6z9Xt(RBz80 zUbs*DV-B=7`WzFaIU_X8ldY{S_n1H2=#dc)7%$wRt#r$*6Ui(E8RJ@qDHtGN{sm^5 zv04N0#Q|w3dyn=VjNIGrp0_Sjub|J?3zHcWw0p+s+P;(3SN=dcbif8YNNAv?6t2Gf z_2o=?^k?6?nmJaXj?{oFsW>0{_HS8^{owaB+3t5Vv4aT39#x<{dAbFO5LIM#YFD(xQm@$AAOn&u2K zm8P;N06KNt8=MhZtbQ5|V6wToA3+YnyUaUmo4LrlnSj9z`5h2zAsfZFtKoe(B_lgY9#1OQ0tDf^bMpj+p8h$LLf-sNCI z?9^_^gfT$cbppd2M+#o)-rC_n^D++`Y9Nj|PrN+Dq9K_pt;f7{h;R`k27+lo#0lkg zZ&RKB37?ri2``MLnh-w$BG;zpA-4H5FN_cT5e#)6p;}X~QT4lKoe-h9AucxbrSKBd zbPxC-qQL`e(F^>o|C?{bJcA+ldiC?X^f#JIZ4KTuu66t|Cgz>>gSgt|G<84eJZo5c z+5`jdn6c|J0Ij140NBt#jra1!wefg<1W9db$6T%Vx;n2NJ_eFjZq0fIAY8PrgcY-Z zGZ({DdD$5F-N4Q|cHde-dS7r4NF|(-TDal(@#T1lG{?_gYLEf*tuwjK-&d&aiBy@l zmbHp54RUjXW^-6{1EsELPaZSiRMuv5g+-55m;66``laPVU+g&TE34JTn(g8+efm{|J@j)I)C;kqB{rL_+Y+tq+z>R$c>-s~GRDKRAb|EIgc|{e4 z7zl|bK$S$m78pXgk194Vzje=O0zE^r>U({!IijQq@HaHIK?-Abzdj(D#ka8Rb$$3w zd%?gCHs9m@sq%uE+5OcK)H9|YA4#iSDmLdGt-)(l{4P+_!RCqMYyDGGIj5-lRBzH8 z<1(KyFM@;nArhuw{3SJwlpf^kw_$L>!Io1R4um5OA)xQt6OkbK#LF=dDlGp4Vj5;- zzO{=X2*J`=Fci3kS%3p(h0(*k?I8?C#%F@iX=MB!U{8B7FUe8EtZGjmuHllHsDvF1 zPWuRtN)NuYi_x6AA8t%oujk~nfD?^_M&`TR4640xW5ni(XlV}MY#huZY9kynPLE++ zPG}~bsg-K@dwv8SLRGc2TG^8Z`6fofRRbZjwqO%zS zftA%a_6Dx(eySgJu!NiCtv8nC%$v)t3#XTz$6El&8t?k%`itoZ4RSb1z_8d=XI0aE)W8agr(|++6RS1IB{00Re^-nvza{xBh$! z33&zqAXuMC7)YcqgbG2u8xO=XX5Y=(cMF36*uWN)hu`m*)4KX^fgp)-PY9`g4Pub3 z_7UE?#-I!~3ECsG;J2H*N#wdMJ_(L^8vZbN^T6mw%c^sX*SHZ0#B-zTh;MA~x8Y_z;SRzF8}kBp!UBTA3#xxV zPngD-RN%oZN2Lv?wCg_7@j`ep45)cp597fYNj+gYngF3TW?XHMvhH8^mSa96m3ww1 zZTDMG>qSd~_iw$`cp59l06w#>2H1VpSzGXsaDq>B-I~+}X|CN1NpvASc;baA_=AJi z$=LO=C;Y~IFa_WFSr9zUZ=MOx@QDUsoS2j6F%s9ud;zQaUdKJ};DSNQ<^)Rs*3R1u zutREVKqlCYHzTBOwu6gQldLf1|=$QDxF zdJ$3E*r`o#^@cac(>UA&p2e5^lm& zjXAm>t%YOaUJK#)S@Ri26_Rj(kSLG<@*yPZWpJ7QSwtKjQc2V&&Dh@5InQZ|hQXml z%iwXtXqXv*3y1)2E)#$E-tQ03F~fa*(;{da?Hd`mn7eyuxU?MW5AMuAFoG{Y=N_NOSD`LOFd8*N z9ACyB0;!bNa2}H$)uje`Wlx=cn@C!)Q*Sk(?iJg#K+20~czfS%3b}f&HZH9JR2gg8 zd;lvc$DsuFwu8UgYolWK3^f}^U4uaPca2nlu%2@ra#7Ey(xd9t=4cj8R@o?F6`t7Y z7z|Jmi9hqAVycV+IK~V~00)=EBxpR^B9S1JWDN+!)`FPEJB!%!!bSt|expwDW?Lx8 z^tHy28?eQOZ^z*FfFicEFuevtg-(DX_JAL-~a#;5=lfs zRDl{_ZSiZEKSp-3b!mym<|gsy%(uo!(vF?nT);rz)O2GRGpc9&Ug&woiGhGW=1yx^ zm!jrzA>73P!3<+CCwNNbud89!7z&tFPHzYe12A@G;zW*`GkAJ|tvO=gV;*(^+-Q1d z&#$dLrrXx~nU~C0sqst(b1){3%#$bkT)#9JCq&Z}I4GZavQD~p@U$@#JWP=hNWB|B z+#F`oJ0aHn?TTsPwm)5CsTx)39d9Ad0-4wq6L#oc;TJoaAfUnQJJLjJ#3Dfm9aD>G z3A^fCj{3$1>{`18%c4ozKNVN3jU7Ep)#j<5U48YzjtjP}!K5*`t=cP%KQ@|%SxfmXt{ zk-DDiI-&$ni#;n`Tbwx%hBHA~e zb%z!P!sbIj%p(Y-AIu8lvNqP!qT9g2CM0o5497v4FUe{@fi>0-a{z%dpAZn~NDCkd zNjopkkJRWseH$l6f;fy1Qh9#h*k`}<+`9$_{s@X8xSm6F0HMu>gvRG|8;s3S=A>SZh3ys{{KGWhw6(S#gqZa(K4yyWD*WKg9Vcw_oY zoxj+C>fE+f$A3}Rct`Jx`R)4Jz#M1Irsch$e>O4iOWJNoNrdK zao~aRmkCM39}&m2rl1q{#v;I{53ftei$ zm-K2_Lp&HAsfjsQ?2-8W#>s_&SX_v~F{BOmQeF8Ku7nWUGG1z;cWORn*yVXqogpI0 zOLB3J^>2upMke$G87%6A1Dm%hw1|N{xV!l?N312Sjmk^|LgXaEL@xRbGU}5rpz2G* zz%dmJ67uRBlJb#YC>T2?XfUI?_t7|7g1L7>R*l7dm;^YjM=l12jS*}R7mb9&8R2;z zhbPGpt7pu^I+&9+_PfZBVj%iHU|x;!#=XQ9V7s}T+Zanl_khTUqMc)JhxdlRu$lPUZ^YaS(3zSA`i{EJs7j?w&U4H+Jf+ug|>#)JW4gyw}=!dK=KhGSjKWm7QFzl~saufcm;qSt*tn74Ht=Fq%sP722>(_16B z{;01_mU_{;4EF#)R0L2soNyqn;vSrAY}lS>bK^eLD(c`2xMv^_V33FqeB$YSHV}@V z@rc(ZE=P66sZ0pLl2})oI3xuKDUBG!cwTE)b`Y-UfJ_K;Omk2Lxod9;?1efDhuuFB$s0^oM`bwewxRQ+AqOr zOrOyr8Z*M7^3oD)h?$JTkOf?51*69G43!n}nw#uJlAy~2V?>Nm3dtgL*>lzn!@(dB zD{nbsg!I-bm2=B;&DlE8xJVjI81=e7YX>QNo}H{e?OJ1N!`8+uX$Kf0jnXr;DgEVB zy7J4vyzGDPd)IK_!i5la|5|@EDz@+T;M)?<+%PQoL{lBJND)o1uir8ZAQxzha2VVFVVLD2qOpOA5F znj3tz%MN3}h-fhGAs9*QhB^IF_4R>K9;}~3&2=_g_5E05V=9WKS$hJLUx$WkPR0tx z+5>azvMz{a4c6Q+cQBMX4oB9LqG&n%Tw~_;EG@u(lE%`e_XZ0{q@RaL77M~745=WY z&ecb2P9udod1R!kXE?*C*_Y0@a7~_FvaWIf5|zvZv^5;Gdj=7jwBH7(L8#k?qfY#` zK6Nl2_y$aRh)m$F!+845U^uC$bYlNZ1CoJ=6JMD%T0Z|T8JJ) zJjOxe5@o=6tF<_q4lM}05Hu;m+*2ut$9uv}Z;ws1$n#*XPxtaGqz&yqbf`9)t9cyg zGiR%CBfMebdRbfR0Y8K{&6S`rE^v@$N(Fbw1x?NP&xeBu&sf0Fy>KuTn0w5d#!0(_ zJO-N(^hsr~!_WBvt~O4bb%CdI+=$>`5~FlEdcL3^iRc=pt(m`aRLvK$(u6`Vd( z|Mkm{V4ee$#!bV8H{noWz&=<|9LyqUP|*n71|ShN$5YP@AP^VF8&zE627%B#2MKv$ z0SPiuH!MDipdqX;x@Xw3fPv8NMu?yl}_yZDuGu`z7woiRS8P^-hZ@bdG+h(tN*__D=4I7`(un_6TgiFq@7Yt;JeOnsk>8_I=GQim@;Au zB!hI2nk2y(As!|HImdJfKGqI`&#Sijy7m{G5ijje93Np};&5W!nMNBkCfRIbqc={+ zR+0Xb4`AIJ3+ER}g&Q)9^l!~xcp(NG(?CT01Mth*7%yZVq11Cshl>}Jl!qFBV_nBF z2tpDXu){8CH+)jGDooSM{csHasIf<~6A;>_F;L&7HFHwK9W?{>N8R@fqJjJ7tu^_}aIJyS0a8bx26OEmrBfn3AQ0+1w*lXG`5kT`9^7xz3PCp`0 z2#g5XhHNj$q#X+mDHV6bB)ky#)P-wo=T7y##bW~S1-MU$gnBp$FSTp&EDSS7`Y^TG zyzMl5z2}$x%flq)y=>bXanDm{F0?K?TaU*@oo+S7-OGiv0YhVUd-Q%1Z$snXe|+n* zXWQOzuZu0v)}+Fn=A!KQ_Rd&ve70Sbn>);du;}!{)}a%Xl7Kg|$6tHxYTE7U@*X~q zL%{*lvQ?M!$zp*S#HLBnjjhi};pXca?%wzYDcFg)yZKTnJ0Y@ZUbHfHIVK}SMS_Yj5CC;G zzX624L=T>RE`|`{a1hHa86neklS4Qpy(+ty0TaNc92yBY%+AQ>`1%{E@=vez1<&B| zc+l%pzrxvG;Iuw2-`&If#IP_1jB;D;YGcxf4Y=2(OO3Z{U_$bbGp_r_yWhP-l)b|- z1m2JapWz#gmw%>0Fdf3VcJFF%fv@Irsplj+;7uj@XaXeu0J-t%!yMaibS_o?%9VQw-p)Nt z`ra=}u`SQ@gCqst)SV>s*7oM_Iamdi7}(8->q?P{-AR_+F~AEK&$jlJD3610%%Qm} z&BlGVla|#KP666f0_2XaJGZ_w7J~Qnv?Zrz4;Vr?<*v7f=aKe2a6tS!%A-jEw{%hUuH zHZ8S&OtrW1f}|FOVIzAEa$(L)DQ)PJwg)-k5{Wa?r{8Um;bcWj&pxx(He$D4G#^@z zXQ-Q=kx;h%SaYZi#AuG-FwDGr;Gq6Tj7inlm|*-(;NZ`Ehe1Jr*)RP=}0%H+`R%n95~ zq*vVx?sKMdBnr>k;AGtXXfg0r|Gp1H?6bDa9WgT#JqNb>$JCh36WZ_ltZVloK)8S= zz__(`5P6M-Oa{$H^G+oqxuqiNL}O!f^qGBV*BLJ(360a+e~X)W7VE=B0n=bz15_Lw zV=)QpK19>l(Y9rWqza5jf5u;K+;c8nY9dqV*@rTXb$-;Zu zG<7#ZK70Dwa<|P**NPfEs4DPN6SgzvV6^q{8bXSUj7r)3z>A}jjf`G6@~DPjO7+E1 zWj2l^?dvduo}=zsd}}~zP~|aA4lzzs<^lsC-G?v2z1>U$3ZvRU41~h zbIK&F8NhNjnDwkpY|K_dj)`^d^{zJ#FdqTib-uMR z?|K*AA%SH0!3%gl%%Hw8N!p@5Cm*K!X}7eUhP1|rYY(iLR%p$bwcnEJ^Zy0H$n3E} SCMk~q0000 Date: Fri, 28 Apr 2023 23:24:33 -0400 Subject: [PATCH 297/377] fix banner file name --- .../banner-772 \303\227 250.jpg" | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename "assets/banner-772 \303\227 250 px).jpg" => "assets/banner-772 \303\227 250.jpg" (100%) diff --git "a/assets/banner-772 \303\227 250 px).jpg" "b/assets/banner-772 \303\227 250.jpg" similarity index 100% rename from "assets/banner-772 \303\227 250 px).jpg" rename to "assets/banner-772 \303\227 250.jpg" From 8f67c239e36ac14115a55da2648f8129b8e90f0f Mon Sep 17 00:00:00 2001 From: majick777 Date: Sun, 30 Apr 2023 17:05:18 +1000 Subject: [PATCH 298/377] widget coundown fix --- CHANGELOG.md | 3 +++ js/radio-station-countdown.js | 10 +++++----- readme.md | 2 +- readme.txt | 7 +++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b62e22..e48a235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.1 = +* Fixed: Widget Countdown Timer Display Bug + = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) * Updated: Freemius SDK (2.5.7) diff --git a/js/radio-station-countdown.js b/js/radio-station-countdown.js index 89b273b..8df4cc3 100644 --- a/js/radio-station-countdown.js +++ b/js/radio-station-countdown.js @@ -29,7 +29,7 @@ function radio_countdown() { } if (diff < 1) {countdown = radio.labels.showended; jQuery(this).removeClass('current-show-end');} else {countdown = radio_countdown_display(diff, radio.labels.timeremaining);} - jQuery(this).parent().find('.rs-countdown').html(countdown); + jQuery(this).closest('.current-show-list').find('.rs-countdown').html(countdown); }); /* Upcoming Show Countdown */ @@ -47,22 +47,22 @@ function radio_countdown() { if (diffb < 1) {countdown = radio.labels.showended; jQuery(this).removeClass('upcoming-show-times');} else {countdown = radio.labels.showstarted;} } else {countdown = radio_countdown_display(diffa, radio.labels.timecommencing);} - jQuery(this).parent().find('.rs-countdown').html(countdown); + jQuery(this).closest('.upcoming-shows-list').find('.rs-countdown').html(countdown); }); /* Current Playlist Countdown */ - jQuery('.current-playlist-end').each(function() { + jQuery('current-playlist.countdown .current-playlist-end').each(function() { diff = parseInt(jQuery(this).val()) - radio.time.current; if (radio.clock_debug) {console.log('Current Playlist Ends in: '+diff);} if (diff < 1) {countdown = radio.labels.playlistended; jQuery(this).removeClass('current-playlist-end');} else {countdown = radio_countdown_display(diff, radio.labels.timeremaining);} - jQuery(this).parent().find('.rs-countdown').html(countdown); + jQuery(this).closest('.current-playlist').find('.rs-countdown').html(countdown); }); /* Continue Countdown */ if ( jQuery('.current-show-list.countdown .current-show-end') || jQuery('.upcoming-shows-list.countdown .upcoming-show-times') - || jQuery('.current-playlist-end') ) { + || jQuery('.current-playlist.countdown .current-playlist-end') ) { setTimeout('radio_countdown();', 1000); } } diff --git a/readme.md b/readme.md index b7ec27a..277ae73 100644 --- a/readme.md +++ b/readme.md @@ -442,7 +442,7 @@ We recommend you test these on a Staging site (or a development copy of your liv #### 2.4.0 * Radio Station Stream Player Widget! -* https://netmix.com/radio-station-2-4-0-release-with-stream-player/ +* https://radiostation.pro/radio-station-update-version-2-4-0-with-streaming-audio-player/ #### 2.3.3.9 * Multiple Dates and Times for Schedule Overrides! diff --git a/readme.txt b/readme.txt index 1e0d4ce..713c932 100644 --- a/readme.txt +++ b/readme.txt @@ -400,6 +400,9 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.1 = +* Fixed: Widget Countdown Timer Display Bug + = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) * Updated: Freemius SDK (2.5.7) @@ -984,7 +987,7 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.0 = * Radio Station Blocks for Gutenberg Block Editor! -* https://netmix.com/radio-station-2-5-0-release-with-blocks/ +* https://radiostation.pro/radio-station-2-5-0-release-with-blocks/ * Refactored Schedule Engine Class * Improved translations and sanitization * Numerous bugfixes and improvements @@ -992,7 +995,7 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.4.0 = * Radio Station Stream Player Widget! -* https://netmix.com/radio-station-2-4-0-release-with-stream-player/ +* https://radiostation.pro/radio-station-update-version-2-4-0-with-streaming-audio-player/ = 2.3.3.9 = * Multiple Dates and Times for Schedule Overrides! From b6cbcd3b2609e7e4a8d15135aabbc05750636d8d Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 3 May 2023 19:13:20 +1000 Subject: [PATCH 299/377] bugfixes for 2.5.1 --- CHANGELOG.md | 1 + docs/FAQ.md | 62 +++++++------- docs/Shortcodes.md | 16 ++-- docs/Widgets.md | 2 +- player/compat.php | 152 ++++++++++++++++++++++++++++++++++ player/radio-player.php | 3 + radio-station.php | 15 +++- readme.txt | 3 +- scheduler/schedule-engine.php | 10 +-- 9 files changed, 216 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e48a235..3a5cc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.1 = * Fixed: Widget Countdown Timer Display Bug +* Fixed: Pro Player Backwards Compatibility = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) diff --git a/docs/FAQ.md b/docs/FAQ.md index 3194dce..5ef2ece 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -3,65 +3,65 @@ *** ### Getting Started -##### How do I get started with Radio Station (Free or PRO)? +#### How do I get started with Radio Station (Free or PRO)? Read the [Quickstart Guide](./index.md#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. -##### Where can I find the full Radio Station documentation (Free or PRO)? +#### Where can I find the full Radio Station documentation (Free or PRO)? The latest documentation [can be found online here](https://radiostation.pro/docs/). Documentation is also included with the currently installed version via the Radio Station Help menu item located under the Radio Station admin menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. -##### How do I get support for Radio Station (Free or PRO)? +#### How do I get support for Radio Station (Free or PRO)? For Radio Station customers using the free, open-source version of our plugin, you can contact us via [our support channel in the WordPress support forums here](https://wordpress.org/plugins/support/radio-station). If you have any bug reports or feature suggestions please [open an issue on our Github repository](https://github.com/netmix/radio-station/) For Radio Station PRO subscribers, you can email us at support@radiostation.pro and someone will respond to your inquiry within 12 to 124 hours. All support inquiries will be handled in the order they are received. Before contacting support or opening an issue, make sure you check for conflicts by disabling all of your plugins and re-enabling them one at a time to ascertain which plugin is conflicting with Radio Station. Note that Radio Station PRO works as an addon to Radio Station, so deactivating it will disable the PRO features until you reactivate it. -##### Can I try Radio Station PRO before I purchase the plugin? +#### Can I try Radio Station PRO before I purchase the plugin? Yes, you can trial Radio Station PRO for up to 14 days. You are required to set a credit or debit card when you sign up for the free trial and can cancel any time before the trial ends. The credit or debit card on file will be charged automatically once the trial expires 14 days from the date your free trial began. [Click here to start your trial.](https://radiostation.pro/pricing) ### Account and Billing -##### How do I access my Radio Station PRO account? +#### How do I access my Radio Station PRO account? We have partnered with Freemius who provide an integrated subscription and upgrade system for WordPress plugin developers. When you purchase Radio Station PRO, you will receive email instructions to create your account, which contain a link to the [Freemius User Dashboard here](https://users.freemius.com/login). While you can see your account details by navigating to WordPress Dashboard > Radio Station > Account, logging into the Freemius Dashboard will give you full control over your account. -##### Can I request a refund for Radio Station PRO? +#### Can I request a refund for Radio Station PRO? We offer a 30 day, moneyback guarantee. If you are not satisfied with Radio Station PRO within the 30 day period, we will issue a full refund, no questions asked. Once the 30 day period is exhausted refunds are not available. -##### How do I cancel my Radio Station PRO subscription? +#### How do I cancel my Radio Station PRO subscription? Login to your [Freemius User Dashboard](https://users.freemius.com/login) and navigate to Renewals & Billing to cancel your Radio Station PRO subscription. When you cancel your subscription, your account will stay active for the remainder of the billing period. Once your subscription is cancelled, you will lose access to PRO customer support and any future upgrades, bug fixes and feature additions. Radio Station (free) will continue to operate normally. ### Plugin Usage -##### How do I schedule a Show? +#### How do I schedule a Show? Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. In order to schedule a Show, a Show must be added and available to accept schedule entries. If this is your first time using Radio Station, create a new show via the Add Show item in the Radio Station menu in your WordPress Admin screen. You can assign Shift timeslots to your new Show or pre-existing Show on the Show edit page, as well as add a Show description and other meta fields, including Show images. -##### How do I display a full schedule of my Station's shows? +#### How do I display a full schedule of my Station's shows? Navigate to the plugin Settings page via the Radio Station menu in your WordPress Admin screen and then click the Pages tab. There you can select the Page on which to automatically display the schedule, as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use additional shortcode attributes to control what is displayed in your Schedule (see [Master Schedule Shortcode Docs](./Shortcodes.md#master-schedule-shortcode) ) -##### What if I want to schedule a special event or one-off schedule change? +#### What if I want to schedule a special event or one-off schedule change? If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by navigating to WordPress Dashboard > Radio Station > Schedule Overrides > Add New. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. You can also link an Override to an existing Show, and partially update any Show information to be overridden. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) In the Free version, if an Override needs to apply to multiple dates, you must schedule each time slot individually. In the PRO version, you can repeat the Override via day periods or monthly recurrences. To enable recurring overrides, [Upgrade to Pro here](https://radiostation.pro/pricing/) -##### How do I change the Show Image displayed in the widgets and schedule? +#### How do I change the Show Image displayed in the widgets and schedule? The schedule, widgets and show page will display whichever avatar is assigned to the show on the Show Edit screen. Navigate in the WordPress Dashboard to Radio Station -> Shows and locate the Show you want to add/edit the Avatar for in the Shows list. Edit the Show then simply set a new image for the Show. -##### How do I style how the plugin displays content on the front end of my site? +#### How do I style how the plugin displays content on the front end of my site? The default styles for Radio Station have intentionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own CSS styles to suit your site's look and feel. You can add these styles via Theme Customizer's Additional CSS setting. You can also add your own `rs-custom.css` file to your Child Theme's directory, and Radio Station will automatically detect the presence of this file and enqueue it. Either way you can add more specific selectors with rules that modify or override the existing styles. You can find the base styles in the `/css/` directory of the plugin. ### Widgets, Blocks and Shortcodes -##### What Widgets/Blocks are available with this plugin? +#### What Widgets/Blocks are available with this plugin? The following Widgets are available to add via the WordPress Appearance -> Widgets page: @@ -73,13 +73,13 @@ The following Widgets are available to add via the WordPress Appearance -> Widge Since 2.5.0, these widgets are now also available as Gutenberg Blocks. See the [Widget Documentation](./Widgets.md) for more details on these Widgets. -##### Do the Widgets reload automatically? +#### Do the Widgets reload automatically? In the free version of Radio Station, the Current Show, Upcoming Shows, and Current Playlist widgets can display a countdown, but do not refresh automatically. To enable auto-reloading of these widgets, so that they refresh exactly at a Show’s changeover time, [upgrade to Radio Station PRO](https://radiostation.pro/pricing/). Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in the Free version of Radio Station. This functionality is only available in our Pro version so widgets refresh exactly at a Show's changeover time. To enable s refresh exactly at Show changeover times. To enable auto-refresh widgets [upgrade to Radio Station PRO](https://radiostation.pro) -##### What Shortcodes are available in Radio Station? +#### What Shortcodes are available in Radio Station? See the [Shortcode Documentation](./Shortcodes.md) for more details and a full list of possible Attributes for these Shortcodes: @@ -95,29 +95,29 @@ See the [Shortcode Documentation](./Shortcodes.md) for more details and a full l (Note old shortcode aliases will still work in current and future versions to prevent breakage.) -##### Do you include Page Builder support? +#### Do you include Page Builder support? The free version of Radio Station includes support for classic Widgets and Gutenberg Blocks. The same widgets are available as modules for Elementor and Beaver Builder in the Pro version, along with additional styling options for each module. To enable these page builder modules, [upgrade to PRO here](https://radiostation.pro/pricing/). ### User Plugin Roles -##### How do I assign a User as the Host or Producer to one or more Shows? +#### How do I assign a User as the Host or Producer to one or more Shows? You can to assign the Host or Producer roles to any WordPress User by accessing the User editor located under WordPress Dashboard -> Users. Search for or navigate to the User you want to assign as a Host or Producer, then click Edit to open the Edit screen for that User. Find the Roles dropdown menu and select from the Role options provided. Choosing either Host or Producer grants the User that role. You can then assign that User to single or multiple Shows via the Show Edit page. A Host or Producer only has permissions to Edit the Show(s) they are assigned to. The Pro version includes an additional Role Editor interface where you can assign the plugin Roles to any number of users at once. To enable the Role Editor interface, [upgrade to PRO here](https://radiostation.pro/pricing/). -##### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? +#### How do I grant users other than Administrator and DJ roles permission to edit Shows and Playlists? There are a number of different options depending on what your goals are. Assigning a user as a Host or Producer to a Show will allow that User to edit it (and it's Playlists.) You could also assign a single User as the Author of a Show/Playlist. If you’d like to give a user that isn't a site Administrator permissions for all Radio Station records, you can assign them the Show Editor role that was created for this purpose. This may help keep clear lines of separation for editorial responsibility over your content. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/Roles/). -##### How do I use a different image from the Gravatar for a Host/Producer? +#### How do I use a different image from the Gravatar for a Host/Producer? If you prefer not to use WordPress-owned, Gravatar.com for your User profile images, you'll need to install a plugin that allows you to add a different image to your Host/Producer's user account. You can search for a free plugin in the WordPress plugin repository at WordPress.org. As there are a number of plugins that do this already, it's mostly out of the scope of this plugin. However, in our Pro version, you can create separate Profile pages to showcase each of your Hosts and Producers, to which you can assign profile images that appear on those. To enable Profile pages, [upgrade to PRO here](https://radiostation.pro/pricing/). ### Languages and Translations -##### What languages other than English is the plugin available in? +#### What languages other than English is the plugin available in? As of April 1st, 2023, known languages include the following: @@ -131,52 +131,52 @@ As of April 1st, 2023, known languages include the following: * Spanish (es_ES) * Catalan (ca) -##### Can Radio Station be translated into my language? +#### Can Radio Station be translated into my language? You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. ### Troubleshooting -##### Why aren't all my Shows displaying in the Schedule? +#### Why aren't all my Shows displaying in the Schedule? Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. -##### I'm seeing a 404 Not Found error when I click on the link for a Show! +#### I'm seeing a 404 Not Found error when I click on the link for a Show! Try re-saving your site's permalink settings via Settings > Permalinks. WordPress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin to regenerate your site's permalinks. -##### Where is my data stored? Can I export my data? +#### Where is my data stored? Can I export my data? Radio Station is stores your site's settings and all post type data in your WordPress MySQL database on your webhost. You can export your data using WordPress Dashboard -> Tools -> Export feature, or use Radio Station PRO’s Export feature located at WordPress Dashboard -> Import/Export. Our import/export feature works with YML and not XML, which is the standard WordPress format. -##### Why can't Show Hosts or Producers can't edit a Show page? +#### Why can't Show Hosts or Producers can't edit a Show page? The only Hosts and Producers that may Edit a Show are the ones assigned as Host(s) or Producer(s) to that specific Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Shows managed by different Hosts/Producers without permission. If you need a user other than Administrator to be able to edit all Shows you can assign them a Show Editor role. ### Integrations -##### Can I use this plugin for Podcasts? +#### Can I use this plugin for Podcasts? While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. -##### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? +#### Can I use this plugin for TwitchTV, Facebook Live, YouTube or Clubhouse shows? Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. -##### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? +#### I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. -##### Can I import Show data from Pro.Radio or the JOAN (Jock on Air Now) plugin? +#### Can I import Show data from Pro.Radio or the JOAN (Jock on Air Now) plugin? We do not have a method of importing data directly from JOAN or Pro.Radio ### Development Versions -##### How do I install the latest Development version for testing? +#### How do I install the latest Development version for testing? If you are having issues with the plugin, we may recommend you install the development version for further bugfix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: @@ -194,7 +194,7 @@ You can now visit your site to make sure nothing is broken. If you experience is Alternatively, if you want to do this from your WordPress Plugin area, you can upload the development Zip file from your Plugins -> Upload page. This will install it to `/wp-content/plugins/radio-station-develop/`. You can then deactivate the existing Radio Station plugin from you Plugins page and then activate the development version. (You can tell them apart on the plugins page via their version numbers. Official releases are 2.x.x, only development releases have the extra digit 2.x.x.x) Again, if you experience issues, you can deactivate the development version and reactivate the old version. -##### What about Pro Beta Version Testing? +#### What about Pro Beta Version Testing? We are constantly improving and adding new features to [Radio Station Pro](https://radiostation.pro/pricing/). Periodically we will release a Beta version to test out a new feature (or fix) out before it is officially released. If you have a Pro license, you can access these cutting edge Pro Beta version releases in two ways: diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 2e9ae87..b9883f2 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -27,11 +27,11 @@ Note that Divs and Legacy Views are considered deprecated as they do not honour The following attributes are available for the shortcode: -* ** Layout Display Options ** +* **Layout Display Options** * *view* : Which View to use for display output. 'table', 'tabs', 'list', 'divs', 'legacy', 'grid', 'calendar'. Default 'table'. * *time* : Display time format you with to use. 12 and 24. Default is Plugin Setting. * *clock* : Display Radio Clock above schedule. (See clock shortcode.) 0 or 1. Default is Plugin Setting. -* ** Show Display Attributes ** +* **Show Display Attributes** * *show_times* : Whether to display the show shift's start and end times. 0 or 1. Default 1. * *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. * *show_image* : Whether the display the show's avatar. 0 or 1. Default 0 (1 for Tabbed View.) @@ -41,7 +41,7 @@ The following attributes are available for the shortcode: * *show_genres* : Whether to display a list of show genres. 0 or 1. Default 0 (1 for Tabbed View.) * *show_encore* : Whether to display 'encore airing' for a show shift. 0 or 1. Default 1. * *show_file* : Whether to add a link to the latest audio file. 0 or 1. Default 0. -* ** Time Display Attributes ** +* **Time Display Attributes** * *days* : Display for single day or multiple days (string or 0-6, comma separated.) Default all. * *start_day* : day of the week to start schedule (string or 0-6, or 'today') Defaults to WordPress setting. * *display_day* : Full or short day heading ('full' or 'short') Default short for Table, full for Tabs/List. @@ -52,7 +52,7 @@ The following attributes are available for the shortcode: * *hide_past_shows* : Tabs View: Hide shows that are finished in the Schedule. 0 or 1. Default 0. * *gridwidth* : [Pro] Grid View: Set the width, in pixels of the grid columns. Default 150. * *divheight* : [Legacy] Divs View: Set the height, in pixels, of the individual divs. Default 45. -* 'time_spaced* : [Pro] Grid View: Enabled time spacing with background images. 0 or 1. Default 0. +* *time_spaced* : [Pro] Grid View: Enabled time spacing with background images. 0 or 1. Default 0. * *weeks* : Number of weeks to display in calendar. For Pro 'calendar' view only. Default 4. * *previous_weeks* : Number of past weeks to display in calendar. For Pro 'calendar' view only. Default 1. @@ -186,11 +186,11 @@ The following attributes are available for this shortcode: * *genres* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) * *link_genres* : Link Genre titles to term pages. 0 or 1. Default 1. -* *genre_desc' : Display Genre term description. 0 or 1. Default 1. +* *genre_desc* : Display Genre term description. 0 or 1. Default 1. * *genre_images' : [Pro] Display Genre images. 0 or 1. Default 1. * *view* : Layout display view. 'list' or 'grid'. Default 'grid'. -* *image_width' : [Pro] Set a width style in pixels for Genre images. Default is 100. -* *hide_empty' : No output if no records to display for Genre. 0 or 1. Default 1. +* *image_width* : [Pro] Set a width style in pixels for Genre images. Default is 100. +* *hide_empty* : No output if no records to display for Genre. 0 or 1. Default 1. * *status* : Query for Show status. Default 'publish'. * *perpage* : Query for number of Shows. Default -1 (all) * *offset* : Query for Show offset. Default '' (no offset) @@ -212,7 +212,7 @@ The following attributes are available for this shortcode: * *languages* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) * *link_languages* : Link Genre titles to term pages. 0 or 1. Default 1. -* *language_desc' : Display Genre term description. 0 or 1. Default 1. +* *language_desc* : Display Genre term description. 0 or 1. Default 1. * *view* : Layout display view. 'list' or 'grid'. Default 'grid'. * *hide_empty' : No output if no records to display for Genre. 0 or 1. Default 1. * *status* : Query for Show status. Default 'publish'. diff --git a/docs/Widgets.md b/docs/Widgets.md index a8ddea5..4bfd130 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -75,7 +75,7 @@ The following attribute options are available for this shortcode: * *dynamic* : [Pro] Whether to dynamically reload on show changeover time. 0 or 1. Default 1. * *title* : The title you would like to appear over the Upcoming Shows. * *no_shows* : The text displayed when no upcoming show is scheduled. Default empty. -* *hide_empty*: Hide the content of the widget/shortcode if there is no current show. 0 or 1. Default 0. +* *hide_empty* : Hide the content of the widget/shortcode if there is no current show. 0 or 1. Default 0. * **Show Display Attributes** * *show_link* : Link the Show title to the Show's page. 0 or 1. Default is 0. * *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. diff --git a/player/compat.php b/player/compat.php index 8c1cece..779172b 100644 --- a/player/compat.php +++ b/player/compat.php @@ -10,7 +10,159 @@ function radio_station_player_enqueue_styles() { } } +if ( !function_exists( 'radio_station_player_output' ) ) { + function radio_station_player_output( $args = array(), $echo = false ) { + return radio_player_output( $player ); + } +} + +if ( !function_exists( 'radio_station_player_instance_args' ) ) { + function radio_station_player_instance_args( $args, $instance ) { + return radio_player_instance_args( $args, $instance ); + } +} + +if ( !function_exists( 'radio_station_player_shortcode' ) ) { + function radio_station_player_shortcode() { + return radio_player_shortcode( $atts ); + } +} + +if ( !function_exists( 'radio_station_player_default_colors' ) ) { + function radio_station_player_default_colors( $atts ) { + return radio_player_default_colors( $atts ); + } +} + +if ( !function_exists( 'radio_station_player_ajax' ) ) { + function radio_station_player_ajax() { + radio_player_ajax(); + } +} + +if ( !function_exists( 'radio_station_player_sanitize_shortcode_values' ) ) { + function radio_station_player_sanitize_shortcode_values() { + return radio_player_sanitize_shortcode_values(); + } +} + +if ( !function_exists( 'radio_station_player_core_scripts' ) ) { + function radio_station_player_core_scripts() { + radio_player_core_scripts(); + } +} +if ( !function_exists( 'radio_station_player_enqueue_script' ) ) { + function radio_station_player_enqueue_script() { + radio_player_enqueue_script( $script ); + } +} + +if ( !function_exists( 'radio_station_player_load_script_fallbacks' ) ) { + function radio_station_player_load_script_fallbacks( $js ) { + return radio_player_load_script_fallbacks( $js ); + } +} +if ( !function_exists( 'radio_station_player_enqueue_amplitude' ) ) { + function radio_station_player_enqueue_amplitude( $infooter ) { + radio_player_enqueue_amplitude( $infooter ); + } +} +if ( !function_exists( 'radio_station_player_enqueue_jplayer' ) ) { + function radio_station_player_enqueue_jplayer( $infooter ) { + radio_player_enqueue_jplayer( $infooter ); + } +} + +if ( !function_exists( 'radio_station_player_enqueue_howler' ) ) { + function radio_station_player_enqueue_howler( $infooter ) { + radio_player_enqueue_howler( $infooter ); + } +} + +if ( !function_exists( 'radio_player_script' ) ) { + function radio_player_script() { + radio_player_script(); + } +} + +if ( !function_exists( 'radio_station_player_get_settings' ) ) { + function radio_station_player_get_settings() { + return radio_player_get_settings(); + } +} + +if ( !function_exists( 'radio_station_player_iframe' ) ) { + function radio_station_player_iframe() { + radio_player_iframe(); + } +} + +if ( !function_exists( 'radio_station_player_state' ) ) { + function radio_station_player_state() { + radio_player_state(); + } +} + +if ( !function_exists( 'radio_station_player_script_amplitude' ) ) { + function radio_station_player_script_amplitude() { + return radio_player_script_amplitude(); + } +} + +if ( !function_exists( 'radio_station_player_script_howler' ) ) { + function radio_station_player_script_howler() { + return radio_player_script_howler(); + } +} + +if ( !function_exists( 'radio_station_player_script_jplayer' ) ) { + function radio_station_player_script_jplayer() { + return radio_player_script_jplayer(); + } +} + +if ( !function_exists( 'radio_station_player_script_mediaelements' ) ) { + function radio_station_player_script_mediaelements() { + return radio_player_script_mediaelements(); + } +} + +if ( !function_exists( 'radio_station_player_get_default_script' ) ) { + function radio_station_player_get_default_script() { + return radio_player_get_default_script(); + } +} + +if ( !function_exists( 'radio_station_player_enqueue_styles' ) ) { + function radio_station_player_enqueue_styles( $script = false, $skin = false ) { + radio_player_enqueue_styles( $script, $skin ); + } +} + +if ( !function_exists( 'radio_station_player_control_styles' ) ) { + function radio_station_player_control_styles( $instance ) { + return radio_player_control_styles( $instance ); + } +} + +if ( !function_exists( 'radio_station_player_script_tag' ) ) { + function radio_station_player_script_tag( $url, $version ) { + return radio_player_script_tag( $url, $version ); + } +} + +if ( !function_exists( 'radio_station_player_style_tag' ) ) { + function radio_station_player_style_tag( $id, $url, $version ) { + return radio_player_style_tag( $id, $url, $version ); + } +} + +if ( !function_exists( 'radio_station_player_validate_boolean' ) ) { + function radio_station_player_validate_boolean( $var ) { + return radio_player_validate_boolean( $var ); + } +} diff --git a/player/radio-player.php b/player/radio-player.php index d8eae9b..3abbbdb 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -3091,6 +3091,9 @@ function esc_url( $url ) { } } +// ------------------- +// Sanitize Text Field +// ------------------- if ( !function_exists( 'sanitize_text_field' ) ) { function sanitize_text_field( $text ) { return $text; diff --git a/radio-station.php b/radio-station.php index 1c16f67..969f47b 100644 --- a/radio-station.php +++ b/radio-station.php @@ -2,7 +2,7 @@ /** * @package Radio Station - * @version 2.5.0 + * @version 2.5.1 */ /* @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.0 +Version: 2.5.1 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -194,6 +194,17 @@ } } +// --- Backwards Compatible Player --- +// 2.5.1: add player backwards compatible functions +add_action( 'plugins_loaded', 'radio_station_back_compat_player' ); +function radio_station_back_compat_player() { + $back_compat = apply_filters( 'radio_station_back_compat_player', true ); + if ( $back_compat ) { + include RADIO_STATION_DIR . '/player/compat.php'; + } +} + + // --------------------------- // Plugin Options and Defaults // --------------------------- diff --git a/readme.txt b/readme.txt index 713c932..aadf301 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 6.2 -Stable tag: 2.5.0 +Stable tag: 2.5.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -402,6 +402,7 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.1 = * Fixed: Widget Countdown Timer Display Bug +* Fixed: Pro Player Backwards Compatibility = 2.5.0 = * Added: Radio Station Blocks! (converted Widgets) diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index 82b94ba..daff0c9 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -2242,11 +2242,11 @@ public function check_shift( $record_id, $shift, $scope, $all_shifts, $times = f if ( isset( $day_shift ) ) { // 2.3.3.6: added check to not last check shift against itself - // TODO: check possible re-occurrence of undefined index 'start' warning here ? - if ( ( $day_shift['show'] != $record_id ) - || ( $day_shift['day'] != $shift['day'] ) - || ( $day_shift['start'] != $shift['start'] ) - || ( $day_shift['end'] != $shift['end'] ) ) { + // 2.5.1: check all indexes exist to avoid undefined index warnings + if ( ( isset( $day_shift['show'] ) && ( $day_shift['show'] != $record_id ) ) + || ( isset( $day_shift['day'] ) && ( $day_shift['day'] != $shift['day'] ) ) + || ( isset( $day_shift['start'] ) && ( $day_shift['start'] != $shift['start'] ) ) + || ( isset( $day_shift['end'] ) && ( $day_shift['end'] != $shift['end'] ) ) ) { // --- check for new shift overlap using next week --- $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); From 20115efd9837868d21fb85d06e767961f416d638 Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 3 May 2023 19:20:45 +1000 Subject: [PATCH 300/377] remove spaces from banner filename --- .../banner-772\303\227250.jpg" | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename "assets/banner-772 \303\227 250.jpg" => "assets/banner-772\303\227250.jpg" (100%) diff --git "a/assets/banner-772 \303\227 250.jpg" "b/assets/banner-772\303\227250.jpg" similarity index 100% rename from "assets/banner-772 \303\227 250.jpg" rename to "assets/banner-772\303\227250.jpg" From ad2e5a8ef1ff03f9822b32707f144c6916193742 Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 3 May 2023 19:21:55 +1000 Subject: [PATCH 301/377] sync assets --- assets/banner-1544x500.jpg | Bin 0 -> 94397 bytes "assets/banner-772\303\227250.jpg" | Bin 0 -> 35982 bytes assets/icon-128x128.png | Bin 21132 -> 8587 bytes assets/icon-256x256.png | Bin 164305 -> 16255 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/banner-1544x500.jpg create mode 100644 "assets/banner-772\303\227250.jpg" diff --git a/assets/banner-1544x500.jpg b/assets/banner-1544x500.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0698ec7cddd4b9e31e0d8b483f9a696954aad3a3 GIT binary patch literal 94397 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 literal 0 HcmV?d00001 diff --git a/assets/icon-128x128.png b/assets/icon-128x128.png index b54d8f6ebde7070f51e586a88cc1f854325f71d4..727503920ee1f70b1e41f7da77db9f23579415f1 100644 GIT binary patch literal 8587 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 XKs5M(+^5TYw;4W_N$NzeZBPH%N$ip- diff --git a/assets/icon-256x256.png b/assets/icon-256x256.png index 6688faa3bff1610fcdc4cc00b85729998bc1ce86..888e20b52bae09951f7aaa6666eda0ace864a854 100644 GIT binary patch literal 16255 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 G@K>k(Cz*aP5wI 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 SCMk~q0000 Date: Wed, 10 May 2023 17:15:03 +1000 Subject: [PATCH 302/377] bugfix updates --- CHANGELOG.md | 3 +++ includes/schedules.php | 3 ++- radio-station.php | 6 +----- readme.txt | 5 ++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a5cc86..52e72d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.2 = +* Fixed: Bug retrieving show data for linked overrides + = 2.5.1 = * Fixed: Widget Countdown Timer Display Bug * Fixed: Pro Player Backwards Compatibility diff --git a/includes/schedules.php b/includes/schedules.php index 861e30e..64d4d55 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -159,7 +159,8 @@ function radio_station_get_all_overrides( $start_date = false, $end_date = false $linked_id = get_post_meta( $override_id, 'linked_show_id', true ); if ( $linked_id ) { $override_data[$i]['linked_show'] = get_post( $linked_id ); - $linked_fields = get_post_meta( $override['ID'], 'linked_show_fields', true ); + // 2.5.2: fix to use override property not array key + $linked_fields = get_post_meta( $override_id, 'linked_show_fields', true ); if ( !isset( $linked_fields['show_title'] ) || !$linked_fields['show_title'] ) { $data['title'] = $linked_show->post_title; } diff --git a/radio-station.php b/radio-station.php index 969f47b..33a166d 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1,16 +1,12 @@ Date: Mon, 15 May 2023 23:05:36 +1000 Subject: [PATCH 303/377] fix to admin override list --- CHANGELOG.md | 3 +++ includes/post-types-admin.php | 3 ++- radio-station.php | 2 +- readme.txt | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e72d0..17e2494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.3 = +* Fixed: Bug in Admin Override Timeslot List + = 2.5.2 = * Fixed: Bug retrieving show data for linked overrides diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index c7b05a7..07496b9 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -3524,7 +3524,8 @@ function radio_station_schedule_override_metabox() { if ( '' != $table['html'] ) { // 2.5.0: use wp_kses on table output $allowed = radio_station_allowed_html( 'content', 'settings' ); - echo wp_kses( $table['list'], $allowed ); + // 2.5.3: fix to incorrect table output key + echo wp_kses( $table['html'], $allowed ); } echo '' . "\n"; diff --git a/radio-station.php b/radio-station.php index 33a166d..bc0e943 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.2 +Version: 2.5.3 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 1305256..36bd236 100644 --- a/readme.txt +++ b/readme.txt @@ -400,6 +400,9 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.3 = +* Fixed: Bug in Admin Override Timeslot List + = 2.5.2 = * Fixed: Bug retrieving show data for linked overrides From 621f49a659bc649621ec3d4ca0a2ff0bdf868d6c Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 22 May 2023 13:29:45 +1000 Subject: [PATCH 304/377] fix for radio player plugin compatibility --- player/compat.php | 2 +- player/radio-player.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/player/compat.php b/player/compat.php index 779172b..b7195f4 100644 --- a/player/compat.php +++ b/player/compat.php @@ -90,7 +90,7 @@ function radio_player_script() { if ( !function_exists( 'radio_station_player_get_settings' ) ) { function radio_station_player_get_settings() { - return radio_player_get_settings(); + return radio_player_get_player_settings(); } } diff --git a/player/radio-player.php b/player/radio-player.php index 3abbbdb..9169f6c 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -1331,7 +1331,7 @@ function radio_player_core_scripts() { // --- add radio player settings (once only) --- // note: intentionally here after player scripts are set if ( !isset( $radio_player['enqeued_player'] ) ) { - $js = radio_player_get_settings(); + $js = radio_player_get_player_settings(); if ( '' != $js ) { if ( function_exists( 'wp_add_inline_script' ) ) { // 2.5.0: added check if script already done @@ -1615,7 +1615,7 @@ function radio_player_script() { // ------------------- // Get Player Settings // ------------------- -function radio_player_get_settings() { +function radio_player_get_player_settings() { global $radio_player; From 3ab83840c8f12ce0d7de0dc7e73f3a2ed7777572 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 13 Jul 2023 22:15:03 +1000 Subject: [PATCH 305/377] development updates --- .idea/php.xml | 3 + .idea/radio-station.iml | 8 +- .idea/workspace.xml | 43 +- CHANGELOG.md | 7 + blocks/archive.js | 146 +++---- blocks/clock.js | 66 +-- blocks/current-playlist.js | 116 +++--- blocks/current-show.js | 182 ++++----- blocks/editor.js | 1 - blocks/player.js | 382 +++++++++--------- blocks/schedule.js | 240 +++++------ blocks/upcoming-shows.js | 170 ++++---- freemius/includes/class-freemius.php | 17 +- freemius/start.php | 2 +- freemius/templates/account.php | 4 - freemius/templates/connect.php | 33 +- freemius/templates/forms/affiliation.php | 6 + freemius/templates/gdpr-optin-js.php | 5 +- .../templates/plugin-info/description.php | 5 - .../templates/plugin-info/screenshots.php | 5 - freemius/templates/tabs-capture-js.php | 5 +- includes/extra-strings.php | 42 +- includes/post-types-admin.php | 4 +- player/compat.php | 9 +- radio-station.php | 2 +- readme.md | 4 +- readme.txt | 11 +- 27 files changed, 795 insertions(+), 723 deletions(-) diff --git a/.idea/php.xml b/.idea/php.xml index 03d51a6..4c27ae1 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -3,6 +3,9 @@ + + + \ No newline at end of file diff --git a/.idea/radio-station.iml b/.idea/radio-station.iml index c956989..80ac2e3 100644 --- a/.idea/radio-station.iml +++ b/.idea/radio-station.iml @@ -1,7 +1,13 @@ - + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b47346a..48cd435 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -7,7 +7,8 @@
  • - +
  • -
  •  • 
  • - +
  • - - + + get_unique_affix() . '_sync_license' ) ?> @@ -508,9 +508,9 @@ class="dashicons dashicons-image-rotate"> -
  • - +
    @@ -380,9 +380,9 @@ class="button">, method : 'POST', data : { - action : 'get_ajax_action( 'update_billing' ) ?>', - security : 'get_ajax_security( 'update_billing' ) ?>', - module_id: 'get_id() ?>', + action : get_ajax_action( 'update_billing' ) ) ?>, + security : get_ajax_security( 'update_billing' ) ) ?>, + module_id: get_id() ) ?>, billing : billing }, success: function (resultObj) { diff --git a/freemius/templates/admin-notice.php b/freemius/templates/admin-notice.php index eb3b2ea..2bccae5 100644 --- a/freemius/templates/admin-notice.php +++ b/freemius/templates/admin-notice.php @@ -10,6 +10,10 @@ exit; } + /** + * @var array $VARS + */ + $dismiss_text = fs_text_x_inline( 'Dismiss', 'as close a window', 'dismiss' ); $slug = ''; @@ -35,45 +39,74 @@ } } } + + $attributes = array(); + if ( ! empty( $VARS['id'] ) ) { + $attributes['data-id'] = $VARS['id']; + } + if ( ! empty( $VARS['manager_id'] ) ) { + $attributes['data-manager-id'] = $VARS['manager_id']; + } + if ( ! empty( $slug ) ) { + $attributes['data-slug'] = $slug; + } + if ( ! empty( $type ) ) { + $attributes['data-type'] = $type; + } + + $classes = array( 'fs-notice' ); + switch ( $VARS['type'] ) { + case 'error': + $classes[] = 'error'; + $classes[] = 'form-invalid'; + break; + case 'promotion': + $classes[] = 'updated'; + $classes[] = 'promotion'; + break; + case 'warn': + $classes[] = 'notice'; + $classes[] = 'notice-warning'; + break; + case 'update': + case 'success': + default: + $classes[] = 'updated'; + $classes[] = 'success'; + break; + } + if ( ! empty( $VARS['sticky'] ) ) { + $classes[] = 'fs-sticky'; + } + if ( ! empty( $VARS['plugin'] ) ) { + $classes[] = 'fs-has-title'; + } + if ( ! empty( $slug ) ) { + $classes[] = "fs-slug-{$slug}"; + } + if ( ! empty( $type ) ) { + $classes[] = "fs-type-{$type}"; + } ?> - data-id="" data-manager-id="" data-slug="" data-type="" - class=" fs-notice"> - +
    > + + + -
    +
    + +
    +
    - - + + + + +
    diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 5ffc023..3bf4ca3 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -365,8 +365,8 @@ class="button button-secondary" tabindex="2"> - get_public_key() ) ?> + value="get_unique_affix() . '_activate_existing' ) ?>"> + get_unique_affix() . '_activate_existing' ) ?>
    - Powered by Freemius get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) ?> + Powered by Freemius get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) ) ?>   -   diff --git a/freemius/templates/forms/optout.php b/freemius/templates/forms/optout.php index 70d6fcb..8f5d56f 100644 --- a/freemius/templates/forms/optout.php +++ b/freemius/templates/forms/optout.php @@ -118,11 +118,11 @@ $form_id = "fs_opt_out_{$fs->get_id()}"; ?> -
    .. - +
    '; @@ -2101,7 +2105,7 @@ public function settings_page() { $namespace = $this->namespace; // --- open page wrapper --- - echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; do_action( $namespace . '_admin_page_top' ); @@ -2602,7 +2606,13 @@ public function setting_row( $option ) { $results = $wpdb->get_results( $query, ARRAY_A ); // 1.2.7: fix by moving page/post options variable here - $pageoptions = $postoptions = array( '' => '' ); + // 1.3.0: check separately to avoid overwriting existing options + if ( !isset( $pageoptions ) ) { + $pageoptions = array( '' => '' ); + } + if ( !isset( $postoptions ) ) { + $postoptions = array( '' => '' ); + } if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { if ( strlen( $result['post_title'] ) > 35 ) { @@ -3172,10 +3182,10 @@ public function setting_styles() { // --- filter and output styles --- $namespace = $this->namespace; $styles = apply_filters( $namespace . '_admin_page_styles', $styles ); - echo ""; + // 1.3.0: use wp_kses_post on styles output + // echo wp_strip_all_tags( implode( "\n", $styles ) ); + echo ""; } @@ -3495,6 +3505,7 @@ function radio_station_settings_resources( $media, $color_picker ) { // ========= // == 1.3.0 == +// - fix for possible page/post options conflict // - added explicit email option field type // - added fallback to text option firld type // - add check if pro slug data is a string diff --git a/options.php b/options.php index 93c89e4..d3430d4 100644 --- a/options.php +++ b/options.php @@ -131,7 +131,7 @@ // --- Station Phone Number --- // 2.3.3.6: added station phone number option - 'station_phone' => array( + 'station_phone' => array( 'type' => 'text', 'options' => 'PHONE', 'label' => __( 'Station Phone', 'radio-station' ), @@ -243,7 +243,7 @@ // --- Defaults Note --- // 2.5.0: added note about defaults being overrideable in widgets - 'player_bar_note' => array( + 'player_defaults_note' => array( 'type' => 'note', 'label' => __( 'Player Defaults Note', 'radio-station' ), 'helper' => __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ), @@ -312,8 +312,8 @@ 'label' => __( 'Default Player Theme', 'radio-station' ), 'default' => 'light', 'options' => array( - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), ), 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), 'tab' => 'player', @@ -481,9 +481,9 @@ 'label' => __( 'Sitewide Player Bar', 'radio-station' ), 'default' => 'off', 'options' => array( - 'off' => __( 'No Player Bar', 'radio-station' ), - 'top' => __( 'Top Player Bar', 'radio-station' ), - 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), + 'off' => __( 'No Player Bar', 'radio-station' ), + 'top' => __( 'Top Player Bar', 'radio-station' ), + 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), ), 'tab' => 'player', 'section' => 'bar', @@ -625,7 +625,7 @@ 'tab' => 'player', 'section' => 'bar', 'helper' => __( 'How to animate the currently playing track display.', 'radio-station' ), - 'pro' => true, + 'pro' => true, ), // --- [Pro/Player] Metadata URL --- @@ -642,7 +642,7 @@ ), // --- [Pro/Player] Store Track Metadata --- - // 2.5.6: added option to store stream + // 2.5.6: added option to store stream 'player_store_metadata' => array( 'type' => 'checkbox', 'label' => __( 'Store Track Metadata?', 'radio-station' ), @@ -652,8 +652,8 @@ 'section' => 'bar', 'helper' => __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ), 'pro' => true, - ); - + ), + // === Master Schedule Page === // --- Schedule Page --- @@ -729,8 +729,8 @@ 'label' => __( 'Available Views', 'radio-station' ), // note: unstyled list view not included in defaults 'default' => array( 'table', 'calendar' ), - 'value' => 'yes', - 'options' => array( + 'value' => 'yes', + 'options' => array( 'table' => __( 'Table View', 'radio-station' ), 'tabs' => __( 'Tabbed View', 'radio-station' ), 'list' => __( 'List View', 'radio-station' ), @@ -749,7 +749,7 @@ 'type' => 'checkbox', 'label' => __( 'Time Spaced Grid', 'radio-station' ), 'default' => '', - 'value' => 'yes', + 'value' => 'yes', 'helper' => __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ), 'tab' => 'pages', 'section' => 'schedule', diff --git a/phpcs.xml b/phpcs.xml index e34dad7..999f4c8 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -30,10 +30,13 @@ - - + + + + + @@ -70,6 +73,21 @@ + + + + + + + + + + + + + + + diff --git a/player/compat.php b/player/compat.php index 3be6aeb..f66e890 100644 --- a/player/compat.php +++ b/player/compat.php @@ -36,7 +36,7 @@ function radio_station_player_default_colors( $atts ) { } } -if ( !function_exists( 'radio_station_player_ajax' ) ) { +if ( !function_exists( 'radio_station_player_ajax' ) ) { function radio_station_player_ajax() { radio_player_ajax(); } @@ -164,8 +164,7 @@ function radio_station_player_style_tag( $id, $url, $version ) { } if ( !function_exists( 'radio_station_player_validate_boolean' ) ) { - function radio_station_player_validate_boolean( $var ) { - return radio_player_validate_boolean( $var ); + function radio_station_player_validate_boolean( $value ) { + return radio_player_validate_boolean( $value ); } } - diff --git a/player/css/radio-player.css b/player/css/radio-player.css index f688e54..b339966 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -13,8 +13,8 @@ } .rp-player audio { - width: 0px; - height: 0px; + width: 0; + height: 0; } .rp-audio button::-moz-focus-inner, .rp-audio-stream button::-moz-focus-inner { @@ -274,7 +274,7 @@ background-image: url("../images/volume-controls-dark.png?v=3"); } -.rp-mute {background-position: 0px 0px;} +.rp-mute {background-position: 0 0;} .muted .rp-mute, .rp-mute:hover {background-position: -18px -18px;} .rp-volume-max {background-position: 0 -36px;} .maxed .rp-volume-max, .rp-volume-max:focus, .rp-volume-max:hover {background-position: -18px -36px;} diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 45e0fec..a4c44fa 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -651,7 +651,7 @@ function radio_player_set_state(key, value) { /* --- store player instance data */ function radio_player_set_data_state(script, instance, data, start) { - url = data.url; format = data.format; fallback = data.fallback; fformat = data.fallback; + url = data.url; format = data.format; fallback = data.fallback; fformat = data.fformat; /* 2.5.6: fix to fallback format */ if (typeof radio_data.data[instance] != 'undefined') { cdata = radio_data.data[instance]; if ( (cdata.script != script) || (cdata.url != url) || (cdata.format != format) || (cdata.fallback != fallback) || (cdata.fformat != fformat) || (cdata.start != start) ) { diff --git a/player/radio-player.php b/player/radio-player.php index 9169f6c..9437c34 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -11,6 +11,8 @@ // - Store Player Instance Args // - Player Shortcode // - Player AJAX Display +// - Add Inline Styles +// - Print Footer Styles // - Sanitize Shortcode Values // - Sanitize Values // - Media Elements Interface @@ -186,7 +188,7 @@ function radio_player_output( $args = array(), $echo = false ) { global $radio_player; // --- maybe debug output arguments --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Passed Radio Player Output Arguments: '; echo print_r( $args, true ) . ''; } @@ -235,7 +237,7 @@ function radio_player_output( $args = array(), $echo = false ) { } } $radio_player['instances'][] = $instance; - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Player Instance: ' . esc_html( $instance ) . ' - Instances: ' . esc_html( print_r( $radio_player['instances'], true ) ) . ''; } @@ -247,7 +249,7 @@ function radio_player_output( $args = array(), $echo = false ) { } // --- maybe debug output arguments --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { echo 'Parsed Radio Player Output Arguments: ' . esc_html( print_r( $args, true ) ) . ''; } @@ -386,7 +388,7 @@ function radio_player_output( $args = array(), $echo = false ) { $html['volume'] .= '
    ' . "\n"; // --- dropdown script switcher for testing --- - if ( isset( $_REQUEST['player-debug'] ) && ( '1' == sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { + if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) { $html['switcher'] = '
    ' . "\n"; $html['switcher'] .= '
    *
    '; $html['switcher'] .= '' . "\n"; + echo '' . "\n"; echo '
    '; echo '' . "\n"; @@ -6107,29 +6108,35 @@ function radio_station_playlist_column_data( $column, $post_id ) { } elseif ( 'trackcount' == $column ) { - echo count( $tracks ) . "\n"; + // 2.5.8: added check that tracks is an array + if ( is_array( $tracks ) ) { + echo count( $tracks ) . "\n"; + } } elseif ( 'tracklist' == $column ) { - echo ''; - echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
    ' . "\n"; - echo '' . "\n"; + // 2.5.8: added check that tracks is an array + if ( is_array( $tracks ) ) { + echo ''; + echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
    ' . "\n"; + echo '' . "\n"; + } } } diff --git a/includes/templates.php b/includes/templates.php index 61731bc..52c82d1 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -510,9 +510,20 @@ function radio_station_show_content_template_excerpt( $content ) { if ( !is_singular( RADIO_STATION_SHOW_SLUG ) ) { return $content; } + + // 2.5.8: pass unprocesed content as description to content template + remove_filter( 'the_content', 'radio_station_show_content_template', 11 ); + ob_start(); + the_content(); + $content = ob_get_contents(); + ob_end_clean(); + add_filter( 'the_content', 'radio_station_show_content_template', 11 ); + remove_filter( 'the_excerpt', 'radio_station_show_content_template', 11 ); $output = radio_station_single_content_template( $content, RADIO_STATION_SHOW_SLUG ); add_filter( 'the_excerpt', 'radio_station_show_content_template', 11 ); + // add_filter( 'the_content', 'radio_station_deduplicate_content', 99 ); + return $output; } @@ -543,11 +554,20 @@ function radio_station_playlist_content_template_excerpt( $content ) { return $content; } remove_filter( 'the_excerpt', 'radio_station_playlist_content_template_excerpt', 11 ); + remove_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); $output = radio_station_single_content_template( $content, RADIO_STATION_PLAYLIST_SLUG ); add_filter( 'the_excerpt', 'radio_station_playlist_content_template_excerpt', 11 ); + // add_filter( 'the_content', 'radio_station_deduplicate_content', 99 ); return $output; } +// -------------------------- +// Deduplicate Content Filter +// -------------------------- +function radio_station_deduplicate_content( $content ) { + return ''; +} + // -------------------------------- // Override Content Template Filter // -------------------------------- @@ -574,9 +594,19 @@ function radio_station_override_content_template_excerpt( $content ) { if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { return $content; } + + // 2.5.8: pass unprocesed content as description to content template + remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); + ob_start(); + the_content(); + $content = ob_get_contents(); + ob_end_clean(); + add_filter( 'the_content', 'radio_station_override_content_template', 11 ); + remove_filter( 'the_excerpt', 'radio_station_override_content_template_excerpt', 11 ); $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); add_filter( 'the_excerpt', 'radio_station_override_content_template_excerpt', 11 ); + // add_filter( 'the_content', 'radio_station_deduplicate_content', 99 ); return $output; } diff --git a/options.php b/options.php index ace7f26..9b6b159 100644 --- a/options.php +++ b/options.php @@ -954,6 +954,17 @@ 'pro' => true, ), + // --- [Pro] Use Latest Episode --- + 'episode_use_latest' => array( + 'type' => 'checkbox', + 'label' => __( 'Use Latest Episode', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), // ==== Post Type Archives === // 2.4.0.6: move archives to separate tab diff --git a/radio-station.php b/radio-station.php index 0fbb2e0..dfc7afb 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.7.4 +Version: 2.5.7.5 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 478f2ee..4923cef 100644 --- a/readme.txt +++ b/readme.txt @@ -406,6 +406,8 @@ We recommend you test these on a Staging site (or a development copy of your liv * Fixed: Save multiple Show related post values * Fixed: Count bug on Override Archive shortcode * Fixed: Date/time display on Override Archive shortcode +* Fixed: Show file Disable Download checkbox saving +* Added: Use automatic Embeds on external Show file URLs * Changed: Removed player state saving iframe = 2.5.7 = diff --git a/templates/single-playlist-content.php b/templates/single-playlist-content.php index a0ac61f..35c2050 100644 --- a/templates/single-playlist-content.php +++ b/templates/single-playlist-content.php @@ -32,11 +32,21 @@ if ( $playlist ) { // 2.4.0.3: added check for played tracks - $found = false; + $found = $found_album = $found_label = $found_comments = false; foreach ( $playlist as $i => $track ) { if ( 'played' == $track['playlist_entry_status'] ) { $found = true; } + // 2.5.8: add check for album, label and comments columns + if ( '' != $track['playlist_entry_label'] ) { + $found_album = true; + } + if ( '' != $track['playlist_entry_label'] ) { + $found_label = true; + } + if ( '' != $track['playlist_entry_comments'] ) { + $found_comments = true; + } } if ( $found ) { @@ -47,9 +57,15 @@ echo '
    #' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Label', 'radio-station' ) ) . '' . esc_html( __( 'Comments', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Label', 'radio-station' ) ) . '' . esc_html( __( 'Comments', 'radio-station' ) ) . '
    ' . esc_attr( $count ) . '' . esc_html( $entry['playlist_entry_artist'] ) . '' . esc_html( $entry['playlist_entry_song'] ) . '' . esc_html( $entry['playlist_entry_album'] ) . '' . esc_html( $entry['playlist_entry_label'] ) . '' . esc_html( $entry['playlist_entry_comments'] ) . '' . esc_html( $entry['playlist_entry_album'] ) . '' . esc_html( $entry['playlist_entry_label'] ) . '' . esc_html( $entry['playlist_entry_comments'] ) . '
    - +
    ' . "\n"; @@ -2094,12 +2155,12 @@ public function settings_header() { // --- maybe output welcome message --- // 1.0.5: use sanitize_title on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['welcome'] ) && ( 'true' == sanitize_text_field( $_REQUEST['welcome'] ) ) ) { + if ( isset( $_REQUEST['welcome'] ) && ( 'true' == sanitize_text_field( wp_unslash( $_REQUEST['welcome'] ) ) ) ) { // 1.2.3: skip output if welcome message argument is empty if ( isset( $args['welcome'] ) && ( '' != $args['welcome'] ) ) { echo '
    '; - // 1.2.5: use direct echo option for message box - $this->message_box( $args['welcome'], true ); + // 1.2.5: use direct echo option for message box + $this->message_box( $args['welcome'], true ); echo '
    ' . "\n"; // 1.2.5: remove reset onclick attribute - $buttons .= '' . "\n"; + $buttons .= '' . "\n"; $buttons .= '' . "\n"; - $buttons .= '' . "\n"; + $buttons .= '' . "\n"; $buttons .= '
    ' . $option['label'] . "\n"; if ( 'multiselect' == $type ) { - $row .= '
    ' . esc_html( __( 'Use Ctrl and Click to Select' ) ) . '' . "\n"; + $row .= '
    ' . esc_html( __( 'Use Ctrl and Click to Select', 'radio-station' ) ) . '' . "\n"; } $row .= '
    \n"; $text .= "\n"; $text .= "\n"; - foreach ($headers as $n => $header) - $text .= " ".$this->runSpanGamut(trim($header))."\n"; + foreach ( $headers as $n => $header ) { + $text .= " " . $this->runSpanGamut( trim( $header ) ) . "\n"; + } $text .= "\n"; $text .= "\n"; # Split content by row. - $rows = explode("\n", trim($content, "\n")); + $rows = explode( "\n", trim( $content, "\n" ) ); $text .= "\n"; - foreach ($rows as $row) { + foreach ( $rows as $row ) { # Parsing span elements, including code spans, character escapes, # and inline HTML tags, so that pipes inside those gets ignored. - $row = $this->parseSpan($row); + $row = $this->parseSpan( $row ); # Split row by cell. - $row_cells = preg_split('/ *[|] */', $row, $col_count); - $row_cells = array_pad($row_cells, $col_count, ''); + $row_cells = preg_split( '/ *[|] */', $row, $col_count ); + $row_cells = array_pad( $row_cells, $col_count, '' ); $text .= "\n"; - foreach ($row_cells as $n => $cell) - $text .= " ".$this->runSpanGamut(trim($cell))."\n"; + foreach ( $row_cells as $n => $cell ) { + $text .= " " . $this->runSpanGamut( trim( $cell ) ) . "\n"; + } $text .= "\n"; } $text .= "\n"; $text .= "
    "; - return $this->hashBlock($text) . "\n"; + return $this->hashBlock( $text ) . "\n"; } - - function doDefLists($text) { - # # Form HTML definition lists. - # + function doDefLists( $text ) { + $less_than_tab = $this->tab_width - 1; # Re-usable pattern to match any entire dl list: $whole_list_re = '(?> ( # $1 = whole list ( # $2 - [ ]{0,'.$less_than_tab.'} + [ ]{0,' . $less_than_tab . '} ((?>.*\S.*\n)+) # $3 = defined term \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) (?s:.+?) ( # $4 @@ -2714,53 +2700,54 @@ function doDefLists($text) { \n{2,} (?=\S) (?! # Negative lookahead for another term - [ ]{0,'.$less_than_tab.'} + [ ]{0,' . $less_than_tab . '} (?: \S.*\n )+? # defined term \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) (?! # Negative lookahead for another definition - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) ) ) )'; // mx - $text = preg_replace_callback('{ + $text = preg_replace_callback( '{ (?>\A\n?|(?<=\n\n)) - '.$whole_list_re.' + ' . $whole_list_re . ' }mx', - array(&$this, '_doDefLists_callback'), $text); + array( &$this, '_doDefLists_callback' ), + $text + ); return $text; } - function _doDefLists_callback($matches) { + + function _doDefLists_callback( $matches ) { # Re-usable patterns to match list item bullets and number markers: $list = $matches[1]; # Turn double returns into triple returns, so that we can make a # paragraph for the last item in a list, if necessary: - $result = trim($this->processDefListItems($list)); + $result = trim( $this->processDefListItems( $list ) ); $result = "
    \n" . $result . "\n
    "; - return $this->hashBlock($result) . "\n\n"; + return $this->hashBlock( $result ) . "\n\n"; } - - function processDefListItems($list_str) { - # # Process the contents of a single definition list, splitting it # into individual term and definition list items. - # + function processDefListItems( $list_str ) { + $less_than_tab = $this->tab_width - 1; # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); + $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str ); # Process definition terms. - $list_str = preg_replace_callback('{ + $list_str = preg_replace_callback( '{ (?>\A\n?|\n\n+) # leading line ( # definition terms = $1 - [ ]{0,'.$less_than_tab.'} # leading whitespace + [ ]{0,' . $less_than_tab . '} # leading whitespace (?!\:[ ]|[ ]) # negative lookahead for a definition # mark (colon) or more whitespace. (?> \S.* \n)+? # actual term (not whitespace). @@ -2768,67 +2755,69 @@ function processDefListItems($list_str) { (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed # with a definition mark. }xm', - array(&$this, '_processDefListItems_callback_dt'), $list_str); + array( &$this, '_processDefListItems_callback_dt' ), + $list_str + ); # Process actual definitions. - $list_str = preg_replace_callback('{ + $list_str = preg_replace_callback( '{ \n(\n+)? # leading line = $1 ( # marker space = $2 - [ ]{0,'.$less_than_tab.'} # whitespace before colon + [ ]{0,' . $less_than_tab . '} # whitespace before colon \:[ ]+ # definition mark (colon) ) ((?s:.+?)) # definition text = $3 (?= \n+ # stop at next definition mark, (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} \:[ ] | + [ ]{0,' . $less_than_tab . '} \:[ ] |
    | \z ) ) }xm', - array(&$this, '_processDefListItems_callback_dd'), $list_str); + array( &$this, '_processDefListItems_callback_dd' ), + $list_str + ); return $list_str; } - function _processDefListItems_callback_dt($matches) { - $terms = explode("\n", trim($matches[1])); + + function _processDefListItems_callback_dt( $matches ) { + $terms = explode( "\n", trim( $matches[1] ) ); $text = ''; - foreach ($terms as $term) { - $term = $this->runSpanGamut(trim($term)); + foreach ( $terms as $term ) { + $term = $this->runSpanGamut( trim( $term ) ); $text .= "\n
    " . $term . "
    "; } return $text . "\n"; } - function _processDefListItems_callback_dd($matches) { + + function _processDefListItems_callback_dd( $matches ) { $leading_line = $matches[1]; $marker_space = $matches[2]; $def = $matches[3]; - if ($leading_line || preg_match('/\n{2,}/', $def)) { + if ( $leading_line || preg_match( '/\n{2,}/', $def ) ) { # Replace marker with the appropriate whitespace indentation - $def = str_repeat(' ', strlen($marker_space)) . $def; - $def = $this->runBlockGamut($this->outdent($def . "\n\n")); - $def = "\n". $def ."\n"; - } - else { - $def = rtrim($def); - $def = $this->runSpanGamut($this->outdent($def)); + $def = str_repeat( ' ', strlen( $marker_space)) . $def; + $def = $this->runBlockGamut( $this->outdent( $def . "\n\n" ) ); + $def = "\n" . $def ."\n"; + } else { + $def = rtrim( $def ); + $def = $this->runSpanGamut( $this->outdent( $def ) ); } return "\n
    " . $def . "
    \n"; } - - function doFencedCodeBlocks($text) { - # # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # + function doFencedCodeBlocks( $text ) { + + # ~~~ + # Code block + # ~~~ $less_than_tab = $this->tab_width; - $text = preg_replace_callback('{ + $text = preg_replace_callback( '{ (?:\n|\A) # 1: Opening marker ( @@ -2838,7 +2827,7 @@ function doFencedCodeBlocks($text) { (?: \.?([-_:a-zA-Z0-9]+) # 2: standalone class name | - '.$this->id_class_attr_catch_re.' # 3: Extra attributes + ' . $this->id_class_attr_catch_re . ' # 3: Extra attributes )? [ ]* \n # Whitespace and newline following marker. @@ -2853,106 +2842,99 @@ function doFencedCodeBlocks($text) { # Closing marker. \1 [ ]* (?= \n ) }xm', - array(&$this, '_doFencedCodeBlocks_callback'), $text); + array( &$this, '_doFencedCodeBlocks_callback' ), + $text + ); return $text; } - function _doFencedCodeBlocks_callback($matches) { + + function _doFencedCodeBlocks_callback( $matches ) { $classname =& $matches[2]; $attrs =& $matches[3]; $codeblock = $matches[4]; - $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - $codeblock = preg_replace_callback('/^\n+/', - array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); - - if ($classname != "") { - if ($classname[0] == '.') - $classname = substr($classname, 1); - $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; + $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES ); + $codeblock = preg_replace_callback( '/^\n+/', array( &$this, '_doFencedCodeBlocks_newlines' ), $codeblock ); + + if ( $classname != "" ) { + if ( $classname[0] == '.' ) { + $classname = substr($classname, 1 ); + } + $attr_str = ' class="' . $this->code_class_prefix.$classname . '"'; } else { - $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs); + $attr_str = $this->doExtraAttributes( $this->code_attr_on_pre ? "pre" : "code", $attrs ); } $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; - $codeblock = "$codeblock"; + $codeblock = "" . $codeblock . "
    "; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - function _doFencedCodeBlocks_newlines($matches) { - return str_repeat("empty_element_suffix", - strlen($matches[0])); + return "\n\n".$this->hashBlock( $codeblock ) . "\n\n"; } + function _doFencedCodeBlocks_newlines( $matches ) { + return str_repeat( "empty_element_suffix, strlen( $matches[0] ) ); + } - # # Redefining emphasis markers so that emphasis by underscore does not # work in the middle of a word. - # public $em_relist = array( '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? tags + function formParagraphs( $text ) { - function formParagraphs($text) { - # - # Params: - # $text - string to process with html

    tags - # # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); + $text = preg_replace( '/\A\n+|\n+\z/', '', $text ); - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); + $grafs = preg_split( '/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY ); - # # Wrap

    tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - $value = trim($this->runSpanGamut($value)); + foreach ( $grafs as $key => $value ) { + $value = trim( $this->runSpanGamut( $value ) ); # Check if this should be enclosed in a paragraph. # Clean tag hashes & block tag hashes are left alone. - $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); + $is_p = !preg_match( '/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value ); - if ($is_p) { + if ( $is_p ) { $value = "

    $value

    "; } $grafs[$key] = $value; } # Join grafs in one text, then unhash HTML tags. - $text = implode("\n\n", $grafs); + $text = implode( "\n\n", $grafs ); # Finish by removing any tag hashes still present in $text. - $text = $this->unhash($text); + $text = $this->unhash( $text ); return $text; } - ### Footnotes - function stripFootnotes($text) { - # # Strips link definitions from text, stores the URLs and titles in # hash references. - # + function stripFootnotes( $text ) { + $less_than_tab = $this->tab_width - 1; # Link defs are in the form: [^id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 + $text = preg_replace_callback( '{ + ^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?: # note_id = $1 [ ]* \n? # maybe *one* newline ( # text = $2 (no blank lines allowed) @@ -2966,84 +2948,81 @@ function stripFootnotes($text) { )* ) }xm', - array(&$this, '_stripFootnotes_callback'), - $text); + array( &$this, '_stripFootnotes_callback' ), + $text + ); return $text; } - function _stripFootnotes_callback($matches) { + + function _stripFootnotes_callback( $matches ) { $note_id = $this->fn_id_prefix . $matches[1]; - $this->footnotes[$note_id] = $this->outdent($matches[2]); + $this->footnotes[$note_id] = $this->outdent( $matches[2] ); return ''; # String that will replace the block } - - function doFootnotes($text) { - # # Replace footnote references in $text [^id] with a special text-token # which will be replaced by the actual footnote marker in appendFootnotes. - # - if (!$this->in_anchor) { - $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); + function doFootnotes( $text ) { + + if ( !$this->in_anchor ) { + $text = preg_replace( '{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text ); } return $text; } - function appendFootnotes($text) { - # # Append footnote list to text. - # - $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $text); + function appendFootnotes( $text ) { - if (!empty($this->footnotes_ordered)) { + $text = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', array( &$this, '_appendFootnotes_callback' ), $text ); + + if ( !empty( $this->footnotes_ordered ) ) { $text .= "\n\n"; $text .= "
    \n"; - $text .= "empty_element_suffix ."\n"; + $text .= "empty_element_suffix ."\n"; $text .= "
      \n\n"; $attr = ""; - if ($this->fn_backlink_class != "") { + if ( $this->fn_backlink_class != "" ) { $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; + $class = $this->encodeAttribute( $class ); + $attr .= " class=\"" . $class . "\""; } - if ($this->fn_backlink_title != "") { + if ( $this->fn_backlink_title != "" ) { $title = $this->fn_backlink_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; + $title = $this->encodeAttribute( $title ); + $attr .= " title=\"" . $title . "\""; } $num = 0; - while (!empty($this->footnotes_ordered)) { - $footnote = reset($this->footnotes_ordered); - $note_id = key($this->footnotes_ordered); - unset($this->footnotes_ordered[$note_id]); + while ( !empty( $this->footnotes_ordered ) ) { + $footnote = reset( $this->footnotes_ordered ); + $note_id = key( $this->footnotes_ordered ); + unset( $this->footnotes_ordered[$note_id] ); $ref_count = $this->footnotes_ref_count[$note_id]; - unset($this->footnotes_ref_count[$note_id]); - unset($this->footnotes[$note_id]); + unset( $this->footnotes_ref_count[$note_id] ); + unset( $this->footnotes[$note_id] ); $footnote .= "\n"; # Need to append newline before parsing. - $footnote = $this->runBlockGamut("$footnote\n"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $footnote); + $footnote = $this->runBlockGamut( $footnote . "\n"); + $footnote = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', array( &$this, '_appendFootnotes_callback' ), $footnote ); - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); + $attr = str_replace( "%%", ++$num, $attr ); + $note_id = $this->encodeAttribute( $note_id ); # Prepare backlink, multiple backlinks if multiple references - $backlink = ""; - for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { - $backlink .= " "; + $backlink = ""; + for ( $ref_num = 2; $ref_num <= $ref_count; ++$ref_num ) { + $backlink .= " "; } # Add backlink to last paragraph; create new paragraph if needed. - if (preg_match('{

      $}', $footnote)) { - $footnote = substr($footnote, 0, -4) . " $backlink

      "; + if ( preg_match( '{

      $}', $footnote ) ) { + $footnote = substr( $footnote, 0, -4 ) . " " . $backlink . "

      "; } else { - $footnote .= "\n\n

      $backlink

      "; + $footnote .= "\n\n

      " . $backlink . "

      "; } - $text .= "
    1. \n"; + $text .= "
    2. \n"; $text .= $footnote . "\n"; $text .= "
    3. \n\n"; } @@ -3053,14 +3032,15 @@ function appendFootnotes($text) { } return $text; } - function _appendFootnotes_callback($matches) { + + function _appendFootnotes_callback( $matches ) { $node_id = $this->fn_id_prefix . $matches[1]; # Create footnote marker only if it has a corresponding footnote *and* # the footnote hasn't been used by another marker. - if (isset($this->footnotes[$node_id])) { + if ( isset( $this->footnotes[$node_id] ) ) { $num =& $this->footnotes_numbers[$node_id]; - if (!isset($num)) { + if ( !isset( $num ) ) { # Transfer footnote content to the ordered list and give it its # number $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; @@ -3072,83 +3052,85 @@ function _appendFootnotes_callback($matches) { } $attr = ""; - if ($this->fn_link_class != "") { + if ( $this->fn_link_class != "" ) { $class = $this->fn_link_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; + $class = $this->encodeAttribute( $class ); + $attr .= " class=\"" . $class . "\""; } - if ($this->fn_link_title != "") { + if ( $this->fn_link_title != "" ) { $title = $this->fn_link_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; + $title = $this->encodeAttribute( $title ); + $attr .= " title=\"" . $title . "\""; } - $attr = str_replace("%%", $num, $attr); - $node_id = $this->encodeAttribute($node_id); + $attr = str_replace( "%%", $num, $attr ); + $node_id = $this->encodeAttribute( $node_id ); return - "". - "$num". + "" . + "" . $num . "" . ""; } - return "[^".$matches[1]."]"; + return "[^" . $matches[1] . "]"; } - ### Abbreviations ### - function stripAbbreviations($text) { - # # Strips abbreviations from text, stores titles in hash references. - # + function stripAbbreviations( $text ) { + $less_than_tab = $this->tab_width - 1; # Link defs are in the form: [id]*: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 + $text = preg_replace_callback( '{ + ^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?: # abbr_id = $1 (.*) # text = $2 (no blank lines allowed) }xm', - array(&$this, '_stripAbbreviations_callback'), - $text); + array( &$this, '_stripAbbreviations_callback' ), + $text + ); return $text; } - function _stripAbbreviations_callback($matches) { + + function _stripAbbreviations_callback( $matches ) { $abbr_word = $matches[1]; $abbr_desc = $matches[2]; - if ($this->abbr_word_re) + if ( $this->abbr_word_re ) { $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); + } + $this->abbr_word_re .= preg_quote( $abbr_word ); + $this->abbr_desciptions[$abbr_word] = trim( $abbr_desc ); return ''; # String that will replace the block } - - function doAbbreviations($text) { - # # Find defined abbreviations in text and wrap them in elements. - # - if ($this->abbr_word_re) { + function doAbbreviations( $text ) { + + if ( $this->abbr_word_re ) { // cannot use the /x modifier because abbr_word_re may // contain significant spaces: - $text = preg_replace_callback('{'. + $text = preg_replace_callback( '{'. '(?abbr_word_re.')'. + '(?:' . $this->abbr_word_re . ')'. '(?![\w\x1A])'. '}', - array(&$this, '_doAbbreviations_callback'), $text); + array( &$this, '_doAbbreviations_callback' ), + $text + ); } return $text; } - function _doAbbreviations_callback($matches) { + + function _doAbbreviations_callback( $matches ) { $abbr = $matches[0]; - if (isset($this->abbr_desciptions[$abbr])) { + if ( isset( $this->abbr_desciptions[$abbr] ) ) { $desc = $this->abbr_desciptions[$abbr]; - if (empty($desc)) { - return $this->hashPart("$abbr"); + if ( empty( $desc ) ) { + return $this->hashPart( "" . $abbr . ""); } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("$abbr"); + $desc = $this->encodeAttribute( $desc ); + return $this->hashPart( "" . $abbr . "" ); } } else { return $matches[0]; @@ -3251,328 +3233,341 @@ function _doAbbreviations_callback($matches) { // WordPress Readme Parser // ----------------------- -if (!class_exists('WordPress_Readme_Parser')) { - Class WordPress_Readme_Parser { +// if ( !class_exists( 'WordPress_Readme_Parser' ) ) { +if ( !class_exists( 'radio_station_readme_parser' ) ) { + class radio_station_readme_parser { - function __construct() { - // This space intentially blank - } - - function parse_readme( $file ) { - $file_contents = @implode('', @file($file)); - return $this->parse_readme_contents( $file_contents ); - } + function __construct() { + // This space intentially blank + } - function parse_readme_contents( $file_contents ) { - $file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents); - $file_contents = trim($file_contents); - if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) ) - $file_contents = substr( $file_contents, 3 ); + function parse_readme( $file ) { + $file_contents = @implode( '', @file( $file ) ); + return $this->parse_readme_contents( $file_contents ); + } - // Markdown transformations - $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents ); - $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents ); - $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents ); + function parse_readme_contents( $file_contents ) { + $file_contents = str_replace( array( "\r\n", "\r" ), "\n", $file_contents ); + $file_contents = trim( $file_contents ); + if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) ) { + $file_contents = substr( $file_contents, 3 ); + } - // === Plugin Name === - // Must be the very first thing. - if ( !preg_match('|^===(.*)===|', $file_contents, $_name) ) - return array(); // require a name - $name = trim($_name[1], '='); - $name = $this->sanitize_text( $name ); + // Markdown transformations + $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1=' . "\n", $file_contents ); + $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1==' . "\n", $file_contents ); + $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1===' . "\n", $file_contents ); - $file_contents = $this->chop_string( $file_contents, $_name[0] ); + // === Plugin Name === + // Must be the very first thing. + if ( !preg_match('|^===(.*)===|', $file_contents, $_name ) ) { + return array(); // require a name + } + $name = trim( $_name[1], '=' ); + $name = $this->sanitize_text( $name ); + $file_contents = $this->chop_string( $file_contents, $_name[0] ); - // Requires at least: 1.5 - if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) ) - $requires_at_least = $this->sanitize_text($_requires_at_least[1]); - else - $requires_at_least = NULL; + // Requires at least: 1.5 + if ( preg_match( '|Requires at least:(.*)|i', $file_contents, $_requires_at_least ) ) { + $requires_at_least = $this->sanitize_text($_requires_at_least[1]); + } else { + $requires_at_least = NULL; + } + // Tested up to: 2.1 + if ( preg_match( '|Tested up to:(.*)|i', $file_contents, $_tested_up_to ) ) { + $tested_up_to = $this->sanitize_text( $_tested_up_to[1] ); + } else { + $tested_up_to = NULL; + } - // Tested up to: 2.1 - if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) ) - $tested_up_to = $this->sanitize_text( $_tested_up_to[1] ); - else - $tested_up_to = NULL; + // Stable tag: 10.4-ride-the-fire-eagle-danger-day + if ( preg_match( '|Stable tag:(.*)|i', $file_contents, $_stable_tag ) ) { + $stable_tag = $this->sanitize_text( $_stable_tag[1] ); + } else { + $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk + } + // Tags: some tag, another tag, we like tags + if ( preg_match( '|Tags:(.*)|i', $file_contents, $_tags ) ) { + $tags = preg_split('|,[\s]*?|', trim( $_tags[1] ) ); + foreach ( array_keys( $tags ) as $t ) { + $tags[$t] = $this->sanitize_text( $tags[$t] ); + } + } else { + $tags = array(); + } - // Stable tag: 10.4-ride-the-fire-eagle-danger-day - if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) ) - $stable_tag = $this->sanitize_text( $_stable_tag[1] ); - else - $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk + // Contributors: markjaquith, mdawaffe, zefrank + $contributors = array(); + if ( preg_match( '|Contributors:(.*)|i', $file_contents, $_contributors ) ) { + $temp_contributors = preg_split( '|,[\s]*|', trim( $_contributors[1] ) ); + foreach ( array_keys( $temp_contributors ) as $c ) { + $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] ); + if ( strlen( trim( $tmp_sanitized ) ) > 0 ) { + $contributors[$c] = $tmp_sanitized; + } + unset( $tmp_sanitized ); + } + } + // Donate Link: URL + if ( preg_match( '|Donate link:(.*)|i', $file_contents, $_donate_link ) ) { + $donate_link = esc_url( $_donate_link[1] ); + } else { + $donate_link = NULL; + } - // Tags: some tag, another tag, we like tags - if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) { - $tags = preg_split('|,[\s]*?|', trim($_tags[1])); - foreach ( array_keys($tags) as $t ) - $tags[$t] = $this->sanitize_text( $tags[$t] ); - } else { - $tags = array(); - } + // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values. + foreach ( array( 'tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) { + if ( $$chop ) { + $_chop = '_' . $chop; + $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] ); + } + } + $file_contents = trim( $file_contents ); - // Contributors: markjaquith, mdawaffe, zefrank - $contributors = array(); - if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) { - $temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1])); - foreach ( array_keys($temp_contributors) as $c ) { - $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] ); - if ( strlen(trim($tmp_sanitized)) > 0 ) - $contributors[$c] = $tmp_sanitized; - unset($tmp_sanitized); + // short-description fu + if ( !preg_match( '/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description ) ) { + $_short_description = array( 1 => &$file_contents, 2 => &$file_contents ); + } + $short_desc_filtered = $this->sanitize_text( $_short_description[2] ); + $short_desc_length = strlen( $short_desc_filtered ); + $short_description = substr( $short_desc_filtered, 0, 150 ); + if ( $short_desc_length > strlen( $short_description ) ) { + $truncated = true; + } else { + $truncated = false; + } + if ( $_short_description[1] ) { + $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional } - } - - // Donate Link: URL - if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) ) - $donate_link = esc_url( $_donate_link[1] ); - else - $donate_link = NULL; + // == Section == + // Break into sections + // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section + // the array alternates from there: title2, content2, title3, content3... and so forth + $_sections = preg_split( '/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY ); + + $sections = array(); + for ( $i = 1; $i <= count($_sections); $i +=2 ) { + $_sections[$i] = preg_replace( '/^[\s]*=[\s]+(.+?)[\s]+=/m', '

      $1

      ', $_sections[$i] ); + $_sections[$i] = $this->filter_text( $_sections[$i], true ); + $title = $this->sanitize_text( $_sections[$i-1] ); + $sections[str_replace( ' ', '_', strtolower( $title ) )] = array( 'title' => $title, 'content' => $_sections[$i] ); + } - // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values. - foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) { - if ( $$chop ) { - $_chop = '_' . $chop; - $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] ); + // Special sections + // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed + // upgrade_notice is not a section, but parse it like it is for now + $final_sections = array(); + foreach ( array( 'description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice', 'extra_notes') as $special_section ) { + if ( isset( $sections[$special_section] ) ) { + $final_sections[$special_section] = $sections[$special_section]['content']; + unset( $sections[$special_section] ); + } + } + if ( isset( $final_sections['change_log'] ) && empty( $final_sections['changelog'] ) ) { + $final_sections['changelog'] = $final_sections['change_log']; } - } - $file_contents = trim($file_contents); + $final_screenshots = array(); + if ( isset( $final_sections['screenshots'] ) ) { + preg_match_all( '|
    4. (.*?)
    5. |s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER ); + if ( $screenshots ) { + foreach ( (array) $screenshots as $ss ) { + $final_screenshots[] = $ss[1]; + } + } + } + // Parse the upgrade_notice section specially: + // 1.0 => blah, 1.1 => fnord + if ( isset( $final_sections['upgrade_notice'] ) ) { + $upgrade_notice = array(); + $split = preg_split( '#

      (.*?)

      #', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); + for ( $i = 0; $i < count( $split ); $i += 2 ) { + // modified: use filter_text instead of sanitize text to maintain markup + $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->filter_text( $split[$i + 1] ), 0, 1000 ); + } + unset( $final_sections['upgrade_notice'] ); + } - // short-description fu - if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) ) - $_short_description = array( 1 => &$file_contents, 2 => &$file_contents ); - $short_desc_filtered = $this->sanitize_text( $_short_description[2] ); - $short_desc_length = strlen($short_desc_filtered); - $short_description = substr($short_desc_filtered, 0, 150); - if ( $short_desc_length > strlen($short_description) ) - $truncated = true; - else - $truncated = false; - if ( $_short_description[1] ) - $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional + // No description? + // No problem... we'll just fall back to the old style of description + // We'll even let you use markup this time! + $excerpt = false; + if ( !isset( $final_sections['description'] ) ) { + $final_sections = array_merge( array( 'description' => $this->filter_text( $_short_description[2], true ) ), $final_sections ); + $excerpt = true; + } - // == Section == - // Break into sections - // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section - // the array alternates from there: title2, content2, title3, content3... and so forth - $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); + // dump the non-special sections into $remaining_content + // their order will be determined by their original order in the readme.txt + $remaining_content = ''; + foreach ( $sections as $s_name => $s_data ) { + $remaining_content .= "\n

      " . $s_data['title'] . "

      \n" . $s_data['content']; + } + $remaining_content = trim( $remaining_content ); + + // All done! + // $r['tags'] and $r['contributors'] are simple arrays + // $r['sections'] is an array with named elements + $r = array( + 'name' => $name, + 'tags' => $tags, + 'requires_at_least' => $requires_at_least, + 'tested_up_to' => $tested_up_to, + 'stable_tag' => $stable_tag, + 'contributors' => $contributors, + 'donate_link' => $donate_link, + 'short_description' => $short_description, + 'screenshots' => $final_screenshots, + 'is_excerpt' => $excerpt, + 'is_truncated' => $truncated, + 'sections' => $final_sections, + 'remaining_content' => $remaining_content, + 'upgrade_notice' => $upgrade_notice + ); - $sections = array(); - for ( $i=1; $i <= count($_sections); $i +=2 ) { - $_sections[$i] = preg_replace('/^[\s]*=[\s]+(.+?)[\s]+=/m', '

      $1

      ', $_sections[$i]); - $_sections[$i] = $this->filter_text( $_sections[$i], true ); - $title = $this->sanitize_text( $_sections[$i-1] ); - $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $_sections[$i]); + return $r; } - - // Special sections - // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed - // upgrade_notice is not a section, but parse it like it is for now - $final_sections = array(); - foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice', 'extra_notes') as $special_section ) { - if ( isset($sections[$special_section]) ) { - $final_sections[$special_section] = $sections[$special_section]['content']; - unset($sections[$special_section]); + function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos + if ( $_string = strstr( $string, $chop ) ) { + $_string = substr( $_string, strlen( $chop ) ); + return trim( $_string ); + } else { + return trim( $string ); } } - if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) ) - $final_sections['changelog'] = $final_sections['change_log']; + function user_sanitize( $text, $strict = false ) { // whitelisted chars + // if ( function_exists( 'user_sanitize' ) ) { // bbPress native + // return user_sanitize( $text, $strict ); + // } - $final_screenshots = array(); - if ( isset($final_sections['screenshots']) ) { - preg_match_all('|
    6. (.*?)
    7. |s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER); - if ( $screenshots ) { - foreach ( (array) $screenshots as $ss ) - $final_screenshots[] = $ss[1]; + if ( $strict ) { + $text = preg_replace('/[^a-z0-9-]/i', '', $text ); + $text = preg_replace('|-+|', '-', $text ); + } else { + $text = preg_replace('/[^a-z0-9_-]/i', '', $text ); } + return $text; } - // Parse the upgrade_notice section specially: - // 1.0 => blah, 1.1 => fnord - if ( isset($final_sections['upgrade_notice']) ) { - $upgrade_notice = array(); - $split = preg_split( '#

      (.*?)

      #', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); - for ( $i = 0; $i < count( $split ); $i += 2 ) - // modified: use filter_text instead of sanitize text to maintain markup - $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->filter_text( $split[$i + 1] ), 0, 1000 ); - unset( $final_sections['upgrade_notice'] ); - } - - // No description? - // No problem... we'll just fall back to the old style of description - // We'll even let you use markup this time! - $excerpt = false; - if ( !isset($final_sections['description']) ) { - $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections); - $excerpt = true; - } - - - // dump the non-special sections into $remaining_content - // their order will be determined by their original order in the readme.txt - $remaining_content = ''; - foreach ( $sections as $s_name => $s_data ) { - $remaining_content .= "\n

      {$s_data['title']}

      \n{$s_data['content']}"; + function sanitize_text( $text ) { // not fancy + // $text = strip_tags( $text ); + $text = wp_strip_all_tags( $text ); + $text = esc_html( $text ); + $text = trim( $text ); + return $text; } - $remaining_content = trim($remaining_content); + function filter_text( $text, $markdown = false ) { // fancy, Markdown + $text = trim( $text ); - // All done! - // $r['tags'] and $r['contributors'] are simple arrays - // $r['sections'] is an array with named elements - $r = array( - 'name' => $name, - 'tags' => $tags, - 'requires_at_least' => $requires_at_least, - 'tested_up_to' => $tested_up_to, - 'stable_tag' => $stable_tag, - 'contributors' => $contributors, - 'donate_link' => $donate_link, - 'short_description' => $short_description, - 'screenshots' => $final_screenshots, - 'is_excerpt' => $excerpt, - 'is_truncated' => $truncated, - 'sections' => $final_sections, - 'remaining_content' => $remaining_content, - 'upgrade_notice' => $upgrade_notice - ); + $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE - return $r; - } + if ( $markdown ) { // Parse markdown. + // if ( !function_exists( 'Markdown' ) ) + // require WORDPRESS_README_MARKDOWN; + // $text = Markdown( $text ); + $text = radio_station_markdown( $text ); + } - function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos - if ( $_string = strstr($string, $chop) ) { - $_string = substr($_string, strlen($chop)); - return trim($_string); - } else { - return trim($string); - } - } + $allowed = array( + 'a' => array( + 'href' => array(), + 'title' => array(), + 'rel' => array()), + 'blockquote' => array('cite' => array()), + 'br' => array(), + 'p' => array(), + 'code' => array(), + 'pre' => array(), + 'em' => array(), + 'strong' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array(), + 'h3' => array(), + 'h4' => array() + ); - function user_sanitize( $text, $strict = false ) { // whitelisted chars - if ( function_exists('user_sanitize') ) // bbPress native - return user_sanitize( $text, $strict ); + $text = balanceTags( $text ); - if ( $strict ) { - $text = preg_replace('/[^a-z0-9-]/i', '', $text); - $text = preg_replace('|-+|', '-', $text); - } else { - $text = preg_replace('/[^a-z0-9_-]/i', '', $text); + $text = wp_kses( $text, $allowed ); + $text = trim( $text ); + return $text; } - return $text; - } - - function sanitize_text( $text ) { // not fancy - $text = strip_tags($text); - $text = esc_html($text); - $text = trim($text); - return $text; - } - - function filter_text( $text, $markdown = false ) { // fancy, Markdown - $text = trim($text); - - $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE - - if ( $markdown ) { // Parse markdown. - if ( !function_exists('Markdown') ) - require WORDPRESS_README_MARKDOWN; - $text = Markdown($text); - } - - $allowed = array( - 'a' => array( - 'href' => array(), - 'title' => array(), - 'rel' => array()), - 'blockquote' => array('cite' => array()), - 'br' => array(), - 'p' => array(), - 'code' => array(), - 'pre' => array(), - 'em' => array(), - 'strong' => array(), - 'ul' => array(), - 'ol' => array(), - 'li' => array(), - 'h3' => array(), - 'h4' => array() - ); - - $text = balanceTags($text); - $text = wp_kses( $text, $allowed ); - $text = trim($text); - return $text; - } + function code_trick( $text, $markdown ) { + // Don't use bbPress native function - it's incompatible with Markdown + // If doing markdown, first take any user formatted code blocks and turn them into backticks so that + // markdown will preserve things like underscores in code blocks + if ( $markdown ) { + $text = preg_replace_callback( "!(
      |)(.*?)(
      |)!s", array( __CLASS__,'decodeit' ), $text ); + } - function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown - // If doing markdown, first take any user formatted code blocks and turn them into backticks so that - // markdown will preserve things like underscores in code blocks - if ( $markdown ) - $text = preg_replace_callback("!(
      |)(.*?)(
      |)!s", array( __CLASS__,'decodeit'), $text); - - $text = str_replace(array("\r\n", "\r"), "\n", $text); - if ( !$markdown ) { - // This gets the "inline" code blocks, but can't be used with Markdown. - $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text); - // This gets the "block level" code blocks and converts them to PRE CODE - $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text); - } else { - // Markdown can do inline code, we convert bbPress style block level code to Markdown style - $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text); + $text = str_replace(array("\r\n", "\r"), "\n", $text); + if ( !$markdown ) { + // This gets the "inline" code blocks, but can't be used with Markdown. + $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text); + // This gets the "block level" code blocks and converts them to PRE CODE + $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text); + } else { + // Markdown can do inline code, we convert bbPress style block level code to Markdown style + $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text); + } + return $text; } - return $text; - } - function indent( $matches ) { - $text = $matches[3]; - $text = preg_replace('|^|m', $matches[2] . ' ', $text); - return $matches[1] . $text; - } + function indent( $matches ) { + $text = $matches[3]; + $text = preg_replace('|^|m', $matches[2] . ' ', $text); + return $matches[1] . $text; + } - function encodeit( $matches ) { - if ( function_exists('encodeit') ) // bbPress native - return encodeit( $matches ); - - $text = trim($matches[2]); - $text = htmlspecialchars($text, ENT_QUOTES); - $text = str_replace(array("\r\n", "\r"), "\n", $text); - $text = preg_replace("|\n\n\n+|", "\n\n", $text); - $text = str_replace('&lt;', '<', $text); - $text = str_replace('&gt;', '>', $text); - $text = "$text"; - if ( "`" != $matches[1] ) - $text = "
      $text
      "; - return $text; - } + function encodeit( $matches ) { + // if ( function_exists('encodeit') ) { // bbPress native + // return encodeit( $matches ); + // } - function decodeit( $matches ) { - if ( function_exists('decodeit') ) // bbPress native - return decodeit( $matches ); - - $text = $matches[2]; - $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES)); - $text = strtr($text, $trans_table); - $text = str_replace('
      ', '', $text); - $text = str_replace('&', '&', $text); - $text = str_replace(''', "'", $text); - if ( '
      ' == $matches[1] )
      -			$text = "\n$text\n";
      -		return "`$text`";
      -	}
      +			$text = trim( $matches[2] );
      +			$text = htmlspecialchars( $text, ENT_QUOTES );
      +			$text = str_replace(array( "\r\n", "\r"), "\n", $text );
      +			$text = preg_replace("|\n\n\n+|", "\n\n", $text );
      +			$text = str_replace( '&lt;', '<', $text );
      +			$text = str_replace( '&gt;', '>', $text );
      +			$text = "" . $text . "";
      +			if ( "`" != $matches[1] ) {
      +				$text = "
      " . $text . "
      "; + } + return $text; + } + + function decodeit( $matches ) { + // if ( function_exists( 'decodeit' ) ) { // bbPress native + // return decodeit( $matches ); + // } + + $text = $matches[2]; + $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) ); + $text = strtr( $text, $trans_table ); + $text = str_replace( '
      ', '', $text ); + $text = str_replace( '&', '&', $text ); + $text = str_replace( ''', "'", $text ); + if ( '
      ' == $matches[1] ) {
      +				$text = "\n" . $text . "\n";
      +			}
      +			return "`" . $text . "`";
      +		}
       
      - } // end class
      +	} // end class
       }
       
       /**
      @@ -3585,13 +3580,14 @@ function decodeit( $matches ) {
        * Add a few extras from GitHub's Markdown implementation. Must be used in a WordPress environment.
        */
       
      -if (!class_exists('GHF_Markdown_Parser')) {
      - class GHF_Markdown_Parser extends MarkdownExtra_Parser {
      +// if ( !class_exists( 'GHF_Markdown_Parser' ) ) {
      +if ( !class_exists( 'radio_station_github_markdown_parser' ) ) {
      + class radio_station_github_markdown_parser extends radio_station_markdown_extra_parser {
       
       	/**
       	 * Hooray somewhat arbitrary numbers that are fearful of 1.0.x.
       	 */
      -	const GHF_MARDOWN_VERSION = '0.9.0';
      +	// const GHF_MARDOWN_VERSION = '0.9.0';
       
       	/**
       	 * Use a [code] shortcode when encountering a fenced code block
      @@ -3892,7 +3888,7 @@ protected function get_shortcode_regex() {
       		// don't match markdown link anchors that could be mistaken for shortcodes.
       		$pattern .= '(?!\()';
       
      -		return "/$pattern/s";
      +		return "/" . $pattern . "/s";
       	}
       
       	/**
      @@ -3912,7 +3908,8 @@ public function doFencedCodeBlocks( $text ) {
       		// If we're at least at 1.2.8, native fenced code blocks are in.
       		// Below is just copied from it in case we somehow got loaded on
       		// top of someone else's Markdown Extra
      -		if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) )
      +		
      +		// if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) )
       			return parent::doFencedCodeBlocks( $text );
       
       		#
      @@ -3924,7 +3921,7 @@ public function doFencedCodeBlocks( $text ) {
       		#
       		$less_than_tab = $this->tab_width;
       
      -		$text = preg_replace_callback('{
      +		$text = preg_replace_callback( '{
       				(?:\n|\A)
       				# 1: Opening marker
       				(
      @@ -3934,7 +3931,7 @@ public function doFencedCodeBlocks( $text ) {
       				(?:
       					\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
       				|
      -					'.$this->id_class_attr_catch_re.' # 3: Extra attributes
      +					' . $this->id_class_attr_catch_re . ' # 3: Extra attributes
       				)?
       				[ ]* \n # Whitespace and newline following marker.
       
      @@ -3949,7 +3946,9 @@ public function doFencedCodeBlocks( $text ) {
       				# Closing marker.
       				\1 [ ]* (?= \n )
       			}xm',
      -			array($this, '_doFencedCodeBlocks_callback'), $text);
      +			array( $this, '_doFencedCodeBlocks_callback' ),
      +			$text
      +		);
       
       		return $text;
       	}
      @@ -3961,8 +3960,9 @@ public function doFencedCodeBlocks( $text ) {
       	 * @return string    possibly escaped start of line hash
       	 */
       	public function _doEscapeForHashWithoutSpacing( $m ) {
      -		if ( ! isset( $m[1] ) )
      +		if ( !isset( $m[1] ) ) {
       			$m[0] = '\\' . $m[0];
      +		}
       		return $m[0];
       	}
       
      @@ -3973,7 +3973,7 @@ public function _doFencedCodeBlocks_callback( $matches ) {
       		// in case we have some escaped leading hashes right at the start of the block
       		$matches[4] = $this->restore_leading_hash( $matches[4] );
       		// just MarkdownExtra_Parser if we're not going ultra-deluxe
      -		if ( ! $this->use_code_shortcode ) {
      +		if ( !$this->use_code_shortcode ) {
       			return parent::_doFencedCodeBlocks_callback( $matches );
       		}
       
      @@ -3985,8 +3985,9 @@ public function _doFencedCodeBlocks_callback( $matches ) {
       		$classname =& $matches[2];
       		$codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] );
       
      -		if ( $classname[0] == '.' )
      +		if ( $classname[0] == '.' ) {
       			$classname = substr( $classname, 1 );
      +		}
       
       		$codeblock = esc_html( $codeblock );
       		$codeblock = sprintf( $this->shortcode_start, $classname ) . "\n{$codeblock}" . $this->shortcode_end;
      @@ -3995,3 +3996,23 @@ public function _doFencedCodeBlocks_callback( $matches ) {
       
        }
       }
      +
      +
      +// =========
      +// CHANGELOG
      +// =========
      +
      +// == 1.3.1 ==
      +// - Updated: Prefixed all classes and functions
      +// - Cleaned: Coding (WPCS) and comment formatting
      +
      +// == 1.1.8 ==
      +// - Added: Github Flavoured Reademe Parser
      +// - Added: class_exists wrapper checks
      +
      +// == 1.0,7 ==
      +// - Added: function_exists and already defined checks
      +// - Changed: filename from readme.php to reader.PHP
      +
      +
      +
      diff --git a/readme.md b/readme.md
      index 123338a..415749a 100644
      --- a/readme.md
      +++ b/readme.md
      @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting
       
       Requires at least: 3.3.1
       
      -Tested up to: 6.4.2
      +Tested up to: 6.6.1
       
      -Stable tag: 2.5.7
      +Stable tag: 2.5.9
       
       License: GPLv2 or later
       
      diff --git a/readme.txt b/readme.txt
      index d2cb010..c8e210e 100644
      --- a/readme.txt
      +++ b/readme.txt
      @@ -2,11 +2,11 @@
       Contributors: tonyzeoli, majick
       Donate link: https://netmix.co/donate
       Tags: radio station, radio shows, radio station schedule, radio broadcasting, streaming radio player
      -Requires at least: 3.3.1
      -Tested up to: 6.4.3
      -Stable tag: 2.5.9
       License: GPLv2 or later
       License URI: http://www.gnu.org/licenses/gpl-2.0.html
      +Requires at least: 3.3.1
      +Tested up to: 6.6.1
      +Stable tag: 2.5.9
       
       Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. 
       
      @@ -405,7 +405,12 @@ We recommend you test these on a Staging site (or a development copy of your liv
       == Changelog ==
       
       = 2.5.10 =
      -* Fixed: Dynamic reloading without needing countdown display
      +* Updated: Freemius SDK (2.8.0)
      +* Updated: reader.php with prefixed reader functions
      +* Updated: Plugin Panel (1.3.1) with new reader function
      +* Updated: Color Picker Alpha Library (3.0.4)
      +* Updated: Howler Library (2.2.4)
      +* Improved: use wp_kses on player widget output
       
       = 2.5.9 =
       * Fixed: Missing use of prepare method on some database queries
      diff --git a/widgets/class-radio-player-widget.php b/widgets/class-radio-player-widget.php
      index 6dc6b84..e0add08 100644
      --- a/widgets/class-radio-player-widget.php
      +++ b/widgets/class-radio-player-widget.php
      @@ -382,13 +382,14 @@ public function widget( $args, $instance ) {
       		// --- maybe debug widget attributes --
       		// 2.5.0: added for debugging widget attributes
       		// 2.5.6: added sanitize_text_field wrapper
      -		if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( $_REQUEST['player-debug'] ) ) ) {
      +		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
      +		if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) ) {
       			echo 'Radio Player Widget Attributes: ';
       			echo esc_html( print_r( $atts, true ) ) . '';
       		}
       
       		// 2.5.0: get context filtered allowed HTML
      -		$allowed = radio_station_allowed_html( 'widget', 'radio-player' );
      +		$allowed = radio_station_allowed_html( 'widget', 'player' );
       
       		// --- before widget ---
       		// 2.5.0: use wp_kses on output
      @@ -414,10 +415,10 @@ public function widget( $args, $instance ) {
       			$output = apply_filters( 'radio_station_player_widget_override', $output, $args, $atts );
       
       			// --- output widget display ---
      -			// TODO: test wp_kses on widget output ?
      -			// wp_kses( $output, $allowed );
      +			// 2.5.10: use wp_kses with allowed HTML on output
       			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
      -			echo $output;
      +			// echo $output;
      +			echo wp_kses( $output, $allowed );
       
       		echo '
    ' . "\n"; From a21083c7dea591f7fed4533bad88e29e95b62c17 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 28 Nov 2024 00:41:47 -0500 Subject: [PATCH 344/377] Test version update to 6.7.1 Update the readme.txt and the readme.txt to testing notice on. .org to 6.7.1 --- readme.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 123338a..8c50246 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 6.4.2 +Tested up to: 6.7.1 Stable tag: 2.5.7 diff --git a/readme.txt b/readme.txt index 5363f5e..e288e11 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: radio station, radio shows, radio station schedule, radio broadcasting, streaming radio player Requires at least: 3.3.1 -Tested up to: 6.4.3 +Tested up to: 6.7.1 Stable tag: 2.5.9 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 1c46922c9175ae05bdf12572474499c56722bbb4 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 13 Feb 2025 23:08:08 +1000 Subject: [PATCH 345/377] 2.5.9.7 dev updates --- CHANGELOG.md | 4 +- freemius/assets/css/admin/checkout.css | 2 +- freemius/assets/css/admin/dialog-boxes.css | 2 +- freemius/assets/js/jquery.form.js | 1 + .../14fb1bd5b7c41648488b06147f50a0dc.svg | 227 ++++ .../178afa6030e76635dbe835e111d2c507.png | Bin 0 -> 6020 bytes .../27b5a722a5553d9de0170325267fccec.png | Bin 0 -> 6501 bytes .../4375c4a3ddc6f637c2ab9a2d7220f91e.png | Bin 0 -> 9380 bytes .../4529cac82a2d1f300d3c4702b7b5e8f3.svg | 227 ++++ .../5480ed23b199531a8cbc05924f26952b.png | Bin 0 -> 7262 bytes .../b4f3b958f4a019862d81b15f3f8eee3a.svg | 402 +++++++ .../c03f665db27af43971565560adfba594.png | Bin 0 -> 6208 bytes .../cb5fc4f6ec7ada72e986f6e7dde365bf.png | Bin 0 -> 23473 bytes .../dd89563360f0272635c8f0ab7d7f1402.png | Bin 0 -> 12087 bytes .../e366d70661d8ad2493bd6afbd779f125.png | Bin 0 -> 11124 bytes .../f18006f6535a1a6e9c6bfbffafe6f18a.svg | 227 ++++ .../f3aac72a8e63997d6bb888f816457e9b.png | Bin 0 -> 6040 bytes .../f928f1be99776af83e8e6be4baf8ffe7.svg | 227 ++++ .../fde48e4609a6ddc11d639fc2421f2afd.png | Bin 0 -> 11237 bytes .../assets/js/pricing/freemius-pricing.js | 2 + .../pricing/freemius-pricing.js.LICENSE.txt | 45 + freemius/includes/class-freemius.php | 203 +++- freemius/includes/class-fs-api.php | 4 +- freemius/includes/class-fs-security.php | 18 + .../managers/class-fs-checkout-manager.php | 242 ++++ .../managers/class-fs-clone-manager.php | 2 +- .../class-fs-contact-form-manager.php | 84 ++ .../managers/class-fs-debug-manager.php | 4 +- freemius/languages/freemius-de_DE.mo | Bin 69290 -> 69310 bytes freemius/languages/freemius.pot | 663 +++++------ freemius/require.php | 2 + freemius/start.php | 2 +- freemius/templates/account.php | 9 - freemius/templates/add-ons.php | 9 - freemius/templates/checkout.php | 337 +----- freemius/templates/checkout/frame.php | 182 +++ .../templates/checkout/process-redirect.php | 105 ++ freemius/templates/checkout/redirect.php | 102 ++ freemius/templates/contact.php | 42 +- freemius/templates/forms/affiliation.php | 8 - freemius/templates/powered-by.php | 61 - freemius/templates/pricing.php | 110 +- includes/master-schedule.php | 2 +- includes/onboarding.php | 1034 +++++++++++++++++ includes/schedules.php | 2 +- includes/shortcodes.php | 41 +- includes/support-functions.php | 21 +- loader.php | 305 +++-- options.php | 3 + player/js/jquery.jplayer.swf | Bin 13714 -> 0 bytes player/js/radio-player.js | 22 +- player/radio-player.php | 296 ++--- radio-station.php | 93 +- readme.md | 2 +- readme.txt | 8 +- templates/master-schedule-list.php | 3 + templates/single-show-content.php | 41 +- 57 files changed, 4151 insertions(+), 1277 deletions(-) create mode 100644 freemius/assets/js/jquery.form.js create mode 100644 freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg create mode 100644 freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png create mode 100644 freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png create mode 100644 freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png create mode 100644 freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg create mode 100644 freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png create mode 100644 freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg create mode 100644 freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png create mode 100644 freemius/assets/js/pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png create mode 100644 freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png create mode 100644 freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png create mode 100644 freemius/assets/js/pricing/f18006f6535a1a6e9c6bfbffafe6f18a.svg create mode 100644 freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png create mode 100644 freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg create mode 100644 freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png create mode 100644 freemius/assets/js/pricing/freemius-pricing.js create mode 100644 freemius/assets/js/pricing/freemius-pricing.js.LICENSE.txt create mode 100644 freemius/includes/managers/class-fs-checkout-manager.php create mode 100644 freemius/includes/managers/class-fs-contact-form-manager.php create mode 100644 freemius/templates/checkout/frame.php create mode 100644 freemius/templates/checkout/process-redirect.php create mode 100644 freemius/templates/checkout/redirect.php delete mode 100644 freemius/templates/powered-by.php create mode 100644 includes/onboarding.php delete mode 100644 player/js/jquery.jplayer.swf diff --git a/CHANGELOG.md b/CHANGELOG.md index e5214ac..2cf66fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.10 = * Updated: Freemius SDK (2.8.0) * Updated: reader.php with prefixed reader functions -* Updated: Plugin Panel (1.3.1) with new reader function +* Updated: Plugin Panel (1.3.6) with new reader function * Updated: Color Picker Alpha Library (3.0.4) * Updated: Howler Library (2.2.4) * Improved: use wp_kses on player widget output +* Improved: player volume slider background sync/hover +* Fixed: Freemius optin image URL path = 2.5.9 = * Fixed: Missing use of prepare method on some database queries diff --git a/freemius/assets/css/admin/checkout.css b/freemius/assets/css/admin/checkout.css index 26d74ca..cd5e065 100644 --- a/freemius/assets/css/admin/checkout.css +++ b/freemius/assets/css/admin/checkout.css @@ -1 +1 @@ -@media screen and (max-width:782px){#wpbody-content{padding-bottom:0!important}} \ No newline at end of file +.fs-ajax-loader{height:20px;margin:auto;position:relative;width:170px}.fs-ajax-loader .fs-ajax-loader-bar{animation-direction:normal;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:bounce_ajaxLoader;background-color:#fff;height:20px;position:absolute;top:0;transform:scale(.3);width:20px}.fs-ajax-loader .fs-ajax-loader-bar-1{animation-delay:.6s;left:0}.fs-ajax-loader .fs-ajax-loader-bar-2{animation-delay:.75s;left:19px}.fs-ajax-loader .fs-ajax-loader-bar-3{animation-delay:.9s;left:38px}.fs-ajax-loader .fs-ajax-loader-bar-4{animation-delay:1.05s;left:57px}.fs-ajax-loader .fs-ajax-loader-bar-5{animation-delay:1.2s;left:76px}.fs-ajax-loader .fs-ajax-loader-bar-6{animation-delay:1.35s;left:95px}.fs-ajax-loader .fs-ajax-loader-bar-7{animation-delay:1.5s;left:114px}.fs-ajax-loader .fs-ajax-loader-bar-8{animation-delay:1.65s;left:133px}@keyframes bounce_ajaxLoader{0%{background-color:#0074a3;transform:scale(1)}to{background-color:#fff;transform:scale(.3)}}@media screen and (max-width:782px){#wpbody-content{padding-bottom:0!important}}.fs-checkout-process-redirect{padding:40px;text-align:center} \ No newline at end of file diff --git a/freemius/assets/css/admin/dialog-boxes.css b/freemius/assets/css/admin/dialog-boxes.css index 562b7bb..9f47ce6 100644 --- a/freemius/assets/css/admin/dialog-boxes.css +++ b/freemius/assets/css/admin/dialog-boxes.css @@ -1 +1 @@ -.fs-modal{background:rgba(0,0,0,.6);display:none;height:100%;overflow:auto;position:fixed;top:0;width:100%;z-index:100000}@media(min-width:961px){.fs-modal{padding-left:160px}.rtl .fs-modal{padding-left:0;padding-right:160px}}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;left:50%;margin-left:-298px;padding-bottom:30px;position:absolute;top:-100%;width:596px;z-index:100001}@media(max-width:650px){.fs-modal .fs-modal-dialog{box-sizing:border-box;margin-left:-50%;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active,.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{background:#fefefe;border:0;padding:20px}.fs-modal .fs-modal-header{background:#fbfbfb;border-bottom:1px solid #eee;margin-bottom:-10px;padding:15px 20px;position:relative}.fs-modal .fs-modal-header h4{color:#cacaca;font-size:1.2em;font-weight:700;letter-spacing:.6px;margin:0;padding:0;text-shadow:1px 1px 1px #fff;text-transform:uppercase;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{border-radius:20px;color:#bbb;cursor:pointer;padding:3px;position:absolute;right:10px;top:12px;transition:all .2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{background:#aaa;color:#fff}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-size:20px;font-weight:700;margin-top:0}.fs-modal .fs-modal-footer{border-top:1px solid #eee;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:last-of-type{margin:0}.fs-modal .fs-modal-panel>.notice.inline{display:none;margin:0}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{left:20px;right:auto}.rtl .fs-modal .fs-modal-footer{text-align:left}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .internal-message,.fs-modal.fs-modal-deactivation-feedback .reason-input{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea,.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;display:none;padding:7px}@media(max-width:650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:left;line-height:30px}.rtl .fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.rtl .fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:right}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0!important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{font-size:16px;line-height:1.5em;margin-bottom:0;margin-top:10px}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:700;margin-bottom:0;padding:0 25px}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;position:relative;top:5px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-license-options-container table,.fs-license-options-container table .fs-available-license-key,.fs-license-options-container table select,.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{float:left;margin-right:5px;position:relative;top:6px}.fs-license-options-container table .fs-other-license-key-container div{display:block;height:30px;overflow:hidden;position:relative;top:2px;width:auto}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{border-collapse:collapse;width:100%}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type=radio]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{padding-left:3px;padding-right:3px;width:1%}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key,.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-multisite-options-container{border:1px solid #ccc;margin-top:20px;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:700}.fs-multisite-options-container.fs-apply-on-all-sites{border:0;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media(max-width:650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-left:0;margin-right:7px}a.show-license-resend-modal{display:inline-block;margin-top:4px}.fs-modal.fs-modal-email-address-update .fs-modal-body input[type=text]{width:100%}.fs-modal.fs-modal-email-address-update p{margin-bottom:0}.fs-modal.fs-modal-email-address-update ul{margin:1em .5em}.fs-modal.fs-modal-email-address-update ul li label span{float:left;margin-top:0}.fs-modal.fs-modal-email-address-update ul li label span:last-child{display:block;float:none;margin-left:20px}.fs-ajax-loader{height:20px;margin:auto;position:relative;width:170px}.fs-ajax-loader .fs-ajax-loader-bar{animation-direction:normal;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:bounce_ajaxLoader;background-color:#0074a3;height:20px;position:absolute;top:0;transform:scale(.3);width:20px}.fs-ajax-loader .fs-ajax-loader-bar-1{animation-delay:.6s;left:0}.fs-ajax-loader .fs-ajax-loader-bar-2{animation-delay:.75s;left:19px}.fs-ajax-loader .fs-ajax-loader-bar-3{animation-delay:.9s;left:38px}.fs-ajax-loader .fs-ajax-loader-bar-4{animation-delay:1.05s;left:57px}.fs-ajax-loader .fs-ajax-loader-bar-5{animation-delay:1.2s;left:76px}.fs-ajax-loader .fs-ajax-loader-bar-6{animation-delay:1.35s;left:95px}.fs-ajax-loader .fs-ajax-loader-bar-7{animation-delay:1.5s;left:114px}.fs-ajax-loader .fs-ajax-loader-bar-8{animation-delay:1.65s;left:133px}@keyframes bounce_ajaxLoader{0%{background-color:#0074a3;transform:scale(1)}to{background-color:#fff;transform:scale(.3)}}.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons,.fs-modal-auto-install #request-filesystem-credentials-form h2{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;max-width:100%;padding:10px 10px 5px;width:300px}.fs-modal-auto-install #request-filesystem-credentials-form fieldset,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form>div{display:block;margin:0 auto;max-width:100%;width:300px}.button-primary.warn{background:#f56a48;border-color:#ec6544 #d2593c #d2593c;box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{background:#e76444!important;border-color:#d85e40!important;color:#f5b3a1!important;text-shadow:0 -1px 0 rgba(0,0,0,.1)!important} \ No newline at end of file +.fs-modal{background:rgba(0,0,0,.6);display:none;height:100%;overflow:auto;position:fixed;top:0;width:100%;z-index:100000}@media(min-width:961px){.fs-modal{padding-left:160px}.rtl .fs-modal{padding-left:0;padding-right:160px}}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;left:50%;margin-left:-298px;padding-bottom:30px;position:absolute;top:-100%;width:596px;z-index:100001}@media(max-width:650px){.fs-modal .fs-modal-dialog{box-sizing:border-box;margin-left:-50%;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active,.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{background:#fefefe;border:0;padding:20px}.fs-modal .fs-modal-header{background:#fbfbfb;border-bottom:1px solid #eee;margin-bottom:-10px;padding:15px 20px;position:relative}.fs-modal .fs-modal-header h4{color:#cacaca;font-size:1.2em;font-weight:700;letter-spacing:.6px;margin:0;padding:0;text-shadow:1px 1px 1px #fff;text-transform:uppercase;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{border-radius:20px;color:#bbb;cursor:pointer;padding:3px;position:absolute;right:10px;top:12px;transition:all .2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{background:#aaa;color:#fff}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-size:20px;font-weight:700;margin-top:0}.fs-modal .fs-modal-footer{border-top:1px solid #eee;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:last-of-type{margin:0}.fs-modal .fs-modal-panel>.notice.inline{display:none;margin:0}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{left:20px;right:auto}.rtl .fs-modal .fs-modal-footer{text-align:left}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .internal-message,.fs-modal.fs-modal-deactivation-feedback .reason-input{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea,.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;display:none;padding:7px}@media(max-width:650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:left;line-height:30px}.rtl .fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.rtl .fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:right}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0!important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{font-size:16px;line-height:1.5em;margin-bottom:0;margin-top:10px}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:700;margin-bottom:0;padding:0 25px}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;position:relative;top:5px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-license-options-container table,.fs-license-options-container table .fs-available-license-key,.fs-license-options-container table select,.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{float:left;margin-right:5px;position:relative;top:6px}.fs-license-options-container table .fs-other-license-key-container div{display:block;height:30px;overflow:hidden;position:relative;top:2px;width:auto}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{border-collapse:collapse;width:100%}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type=radio]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{padding-left:3px;padding-right:3px;width:1%}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key,.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-multisite-options-container{border:1px solid #ccc;margin-top:20px;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:700}.fs-multisite-options-container.fs-apply-on-all-sites{border:0;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media(max-width:650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-left:0;margin-right:7px}a.show-license-resend-modal{display:inline-block;margin-top:4px}.fs-modal.fs-modal-email-address-update .fs-modal-body input[type=text]{width:100%}.fs-modal.fs-modal-email-address-update p{margin-bottom:0}.fs-modal.fs-modal-email-address-update ul{margin:1em .5em}.fs-modal.fs-modal-email-address-update ul li label span{float:left;margin-top:0}.fs-modal.fs-modal-email-address-update ul li label span:last-child{display:block;float:none;margin-left:20px}.fs-ajax-loader{height:20px;margin:auto;position:relative;width:170px}.fs-ajax-loader .fs-ajax-loader-bar{animation-direction:normal;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:bounce_ajaxLoader;background-color:#fff;height:20px;position:absolute;top:0;transform:scale(.3);width:20px}.fs-ajax-loader .fs-ajax-loader-bar-1{animation-delay:.6s;left:0}.fs-ajax-loader .fs-ajax-loader-bar-2{animation-delay:.75s;left:19px}.fs-ajax-loader .fs-ajax-loader-bar-3{animation-delay:.9s;left:38px}.fs-ajax-loader .fs-ajax-loader-bar-4{animation-delay:1.05s;left:57px}.fs-ajax-loader .fs-ajax-loader-bar-5{animation-delay:1.2s;left:76px}.fs-ajax-loader .fs-ajax-loader-bar-6{animation-delay:1.35s;left:95px}.fs-ajax-loader .fs-ajax-loader-bar-7{animation-delay:1.5s;left:114px}.fs-ajax-loader .fs-ajax-loader-bar-8{animation-delay:1.65s;left:133px}@keyframes bounce_ajaxLoader{0%{background-color:#0074a3;transform:scale(1)}to{background-color:#fff;transform:scale(.3)}}.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons,.fs-modal-auto-install #request-filesystem-credentials-form h2{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;max-width:100%;padding:10px 10px 5px;width:300px}.fs-modal-auto-install #request-filesystem-credentials-form fieldset,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form>div{display:block;margin:0 auto;max-width:100%;width:300px}.button-primary.warn{background:#f56a48;border-color:#ec6544 #d2593c #d2593c;box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{background:#e76444!important;border-color:#d85e40!important;color:#f5b3a1!important;text-shadow:0 -1px 0 rgba(0,0,0,.1)!important} \ No newline at end of file diff --git a/freemius/assets/js/jquery.form.js b/freemius/assets/js/jquery.form.js new file mode 100644 index 0000000..dcbcbb5 --- /dev/null +++ b/freemius/assets/js/jquery.form.js @@ -0,0 +1 @@ +!function(n){n.extend({form:function(r,e,t){null==t&&(t="POST"),null==e&&(e={});var o=n("").attr({method:t,action:r}).css({display:"none"}),a=function(r,e){if(n.isArray(e))for(var t=0;t").attr({type:"hidden",name:String(r),value:String(e)}))};for(var i in e)e.hasOwnProperty(i)&&a(i,e[i]);return o.appendTo("body")}})}(jQuery); \ No newline at end of file diff --git a/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg b/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg new file mode 100644 index 0000000..2dfccd6 --- /dev/null +++ b/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png b/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png new file mode 100644 index 0000000000000000000000000000000000000000..d5debf2b75c672c2f8c7296ef12f952fe9f0c122 GIT binary patch literal 6020 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 literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png b/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png new file mode 100644 index 0000000000000000000000000000000000000000..943dad7b163e2f0915fa2561f41a4c748e46859d GIT binary patch literal 6501 zcmV-r8JgyaP)|qK>vYwY7`=yJ$Ps_V4CQYt@-*tN+w#?P|wPt&Z*3YSp?>#0?Mu5s@9)_kBrN zLkJN5H(b45lS^_FLTBuFp8N3JBFrp9J5Ve{&ms*B4CnADG&J=fUKBqj6w zuxOY`eM63stD}dtrHz3;2OkFp`&&DjYwD|n!$bJUX0a`~R$OyS+*4OaZ(L)%f1t0t zx^z%5pa!rU?VR=X3~=D?J$?MCgN4rM>*<@Ba2eJ&H?=^0bc_jDzMcU-9)6mdnv~1A zIK16`+d5h_HCa{`)>`a)`e?WOI>&KaPbcEoi#o_4xNJ8mS0&gG?-U%qrIbbOgwfU#D<{P z3GpjIrqyycS4VdsHDP`7V14SFYbUD-bV@1;lhe)`8W@p&a9UzRH5LS7lE35QQ@<60 ziV*)WoQ#Z)RMeD7AiI?Zl>}3#Ruhkl>l&(WRTUd@jN_vh8|WD%rJlJzz$&dMA_Lkf zmRbJcL;$F*r48>c5{Zlqjj2c>f`P0-;t*HP3W%_=w1s?SEF)s z7*Pk1n!4ti?@oS0B838j`sufS^>pzj4T`6$4}2D3HGY#tF<5Li+tJSDSmO7xKXvlj z^GkDG9NZ<+r=vU5!w*yto=8he+s?*8si5RH0v7E3;LrdJD($ZzHKxX+OC zE|@%LdlzuX=+M~z@Xni?a$$U}xYjHdOFdv|YG}$KmS8`cEEdhnwLDexeO*ncKW90S7za~=v0j<&Y2@%0K!h+n}(4;1q(_+}%Hv7v$SfPf~C?&&&H zJT?|NYtGLXj)B)W+q)rT@9pCkSLB06j*X4}=KutI3erY{X4%szndfOUGfIFpIyTzW*05^HV~39Km;E&>_ey|wNMTvt)tr?0$VHMIu8qTI z|NPL!-VG=-k9BY%LOJ*fjJafXM^{gqtxZHQVCN&FqLGmiScCq7ehkT|GW-rVoE;E8 zBpjMyV_#WY=HlqyKhWFB>o|Vy2w5kmAj`ql*~i^4J1;|?`3?_?QTV)Weph&KjG=*H zc}=O*Pd$D78%5bS3bXWd^`2d`5&gWBk&I_Ah)H0v*sbl&O!@g0}*7YGI2 zJ)K~@@U~U;742QE{J!o%!2p!q!pzdm*(1OwsI8-wIqkHynxU(s3;Te--=F$+(VPT4 zCBO7Wb$!LX$*lf4Ve#^5BcpH!ErW_ zUR{!ya+1x~3ib`nzL}vQjW-bwfNB6}!I0pCFWzO+nAJ z9F+@h{E^yu-VyOMnB z!cXuiKP4T(BNO6QfJ1ciJ85qPZ{RS)*>c0}Y+_{6+R@V3Qs3IqOpDs^@UV^Lj3cKH%?^kN3yS*Jse|J2@hjIaNmYxQ9oyO6 zuFgOjw?jh%eL}=yF-XI`LCNrsXAk29EsKpRN4W9q5Ip~=0Qgx z8dB05=p#vH!Z1@|@^bZYbN0meBMd;;By|Z46EO$H#33L`g;66v!hc=3`U?&#uFc}P z%i^OKwRg6aR1}hs!G^%1&x=@i=JJUP8A;?cSIq3VRZG^wUBG$(%G9ij(1yEh*3u3Q ziYzWKp!!FqU>O-2d%F2t%}Kp-{USOa5f~j791U)jlzKWT^%S*c@|80nU-P4vWZp=F zXMp$d^9rbMuKVP`?#9;o$#fhr0R<*=(co^hi!eI6+YX=n$MJKVW$_QkM=x${t}7}p zfa?hH4~2ySp#e-(IAA3)q9V~SJRq66Ad#9I5pQm4VPS3s6(LhSTFhO$fj;M<*yW4o zE@iRVkX|^K{=q)l{+~-b)6>_pddcI1LxWJdZ+`fqOva5$<;x{LUZ2AVEVh=Ml@Peq z(ZugAUQJrF{OO0{ma{Z9i^}u&9@q`*LDm7Sd34!Ruwr}mzxV9#{}2-zS5%hY+}21s z1jHUd3g_r!Y~%tGFQ#Di^zu(7A2ZN5fVA65X2UJhvM-*wcmk%1R(-DJrS}i?fU=&t za7^LSB@ie=(-{t%!et}8-tK+heGHF0G&CqXPQ#*MV?&cS*S}lSP?b}d1!H1w>p*}7 zq<|eY7eL^Zo_(3$%Tvz4$Arzp5aku;q-33kKgNybhQ-f|T$Gl5IrZ9ksmdjlML5(> z!~vF2$cSXY&q~L+21Y9u{vO1A_rC4Vt=R~NgV>PA?}9NUk&=yj2)wRbyQCagPyzms z0RHZ&N1it~1x2#z>E*#UEuFUlE$i)9aXr`Igc|BHSiV5`0Vdi?mzC3f<(bVO^nTt! zDVfRpzTX?-8@eR!cM!DN#_GwCqFAx{%7K-Vc>xLt4k;3es_QCp@-vAgXRysrKl1!% zhdxvdEJ9DcTzwlAW%xWK3}bvirFDBRKJlu=|D>I@;#!9WMu9Hyd%K5+g+O0EQY2oJ zf`#B_c2Hz)VK$YvU`Ppxl`1fhi({9C2SsP+XQ(!>0Y9*C&eFoNn;nxgLwJ}cRz3^n zshVU}H&nuCU|id~+HmM->lhoELRVlh6(S|i8{!sGp|J=@7Q`$;bWRHpimz8dU1JR# zKO$T3)rjCZs(}UnaP4L~VyQpA^ylL8!Y;b?Qzph{f!@K05HoYrRhx@UynGTiYfjj_ z%bCgW=Asc19>mgQMTO2~n5U!%3w#Vn5gZpGA?QXyX|9x0bFz1t>EZ9;;+>P91u=&G zg4I;nY#;mOd$c|#be?2heq548Bc@L@?#!NOYO)}G>DMoxzW9^MPdS=+80`!VjLgbS zm-t^zR^;qCwpMmbeT(cyCm#<#Kd(TkFQMLe0J(4h8nGyLsrH@R9#rGY!yi@Fl}$Bo z%VL=sbJ=Vb?VJU;=+x)8VQ}BsxU;sQs;0gQNX@XeSHi!^BsA=N=~f_`nQ#}(Nq`W6 z3d16X2S;mDvYPDOP{z;IXgnqj`s##>(=V5?+4jtdGLdyFtSdb}1%gSEg+SyV%4K#ha3+Q1|+8GtWf}K*z)R8X#;IVxg_h-3~ zo=AHZ%qIfvW{f z3bQogaWOdE$cRXi{~YW`Y*gD=nO~YKmAzvwTqPBSK{IDDC8bx66{^5u7!qBQb#*y9IGCByri=5$Fu*GKetR4$YyVU4-wl<7g#)g7sJqhVN60o z{7R{nveQ?f$o^hIex8A8*%wco`+>x)mk%91Ruvy6?HPn!HibCxQw@#T5bWwR76=MD{spc23De?Fig zSrpfok(;LQ=91XU<96(2Z{y(Y<~L!*hPHqF7HxXIlh?8B<2T8vW?0F++xBmNbvG4s zg}b6^8>*(uW|dYc;IXz=_Eg7YV`)o-kapG%QUn@W!|&r;np>Hfn3MH4tbLW&(>*30 z!}o-W?fY&o`R0PTVe#R?k~L+Jtjn3ngqi#N(1)8}+#yZs?d|KKeK2W*CIgF{r`i4GwqU=|!drQolPa|yNjPak z=-WNfnzSzi0(x@mVCxteG8YyWKG)E|Xd1whtW1-zP1uX2@ec!+gX;%g7ftZAoiX?42dcFOComY6_x>Xyfy9n)V9Op+rL|fC> z^Y$cWTW3pB>ScgC@&E^|ze78=Zu)>AS?JhJnYCn7$x^-k z{1#e0jf+?)2Pwpo1X5@fWQrE4kr5GkBxK;_m6ojf=Gwn~y_4v`Z2t&!0925wLB0Jw zf8P5?a87)=?Uh|hy*GwQ_NKC~jL3P|SlW3wd&$X3AZA1)1m3`AYi)mZm#Hz;L63vP z!j|O}W)Z9B;j9+VU8ZKRs_QGjyrr1o3y*K28UkKV*N)G&057z1%gZ|~%&ati^#Wnp zIblQ5hOL|4H>X5AAnX75;q$6{x1%hGN%-A@Rccm4Sp}?VtlF~YHR7FKL_y2YJ{@gc z;I-q^cbKGA)mtF`@BZx#AO+tig%oH6S}TRr1i1a`w^_7lmI(v=L0NSP6|eW+*kf&B z3kSddU!VTt$1kR7N4zXYJ(_qJh63Y+r@#059x763;fMQo(C%6h3<=b4v+yKdZ&!7F zW$Lx_Ei#+FUS9X6r;E>B5XZ&IX=jOD1;nn8xQGQ(87sBHG$P8pnSL_)2VyD=&srS2 zjC{`@tpOouS4`-WC;D%hHYS&)M_yKM@}Ck*N+7F z1TTnQ;$Z8#a3plNAuO3l8IQ;op8hMjvgk)W+aV9xN{}@64lNB>7~EOW=s0|dlOTBM*K{)7_hoId4>i= zxHz~Y3?KtQW_EKJF=<_M&5goqptXc3;f4tG65>`+0|TOT^E-EZ_6~LO6;FKo4?D=5 z44Tx_#{=k|t~2H2bCd%M0+Mx8k}WVZk)TV^^j-lL-|f4aIzdbNiJ0-PU;SAMNBL${>Fp>|-(1VfSiWGj5yu$zQkH_j4_+_GT>tnc zh0CYWf0!*C%wH@lr?hvq?mzO`WII!d#{78ZukG)$zdrc@4C zM5(B~^}!cgVe%8^t@LsCRm%4 zwYM}T-t(yNa`c4QpX_Ak5*aeb!^PX&#DdLMaoNb2cnqGdvbGEkTFxr~r3&%p9NH;J zg=|?luw*HW#>BhPU`>^ZmeDt|dxi#vzFtAD4sKjCD>$hM6RxABrA2bZiPe6iB9TZS z6!iAPWp>s!R!Me25B8}5F_G_XZf~T?izza!%7HamRv&E0gbqiBDc0<~t2ln07~5fn zlO*`Buay1C-quN-U`bI2YCc5jReV#)^96W^_>UL2wQ@(~VEP@!E55 zgO{rOAU!?($>xbIxmIemkh6rx?*7|J>Uj;#tO1BA;MYFBg&r|V^I;Fv~ zrkgq?R#lkYNpdkP?oOT|zOz-fK8UU#TfR<7-XJj)Ep#Nym%%^9P#P>FI>nZ}sYg7f z>`dUi6>g?mF-W(B>h!_t->+#4x$T{|hhx=zO?EHl|9e00000 LNkvXXu0mjfQZ~4F literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png b/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png new file mode 100644 index 0000000000000000000000000000000000000000..33902b4a99ec9a3199804151277daabfc610406b GIT binary patch literal 9380 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg b/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg new file mode 100644 index 0000000..a7cc12b --- /dev/null +++ b/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png b/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png new file mode 100644 index 0000000000000000000000000000000000000000..eda6d6fb12df205fd9fd23ff0ff63d1d57d2ec97 GIT binary patch literal 7262 zcmaKRWl$X5+AZ!R1b2qPeFhsexFxtl@L_Nm7zTIO03n9EyM;h-Cjo*6cTI2z5S&ZS zci!{eANSm@>gxUMT5CPBS5TzcwEO zDx`uTQU`8>^s+?2P-Lv(Rxp5yv!yLe8)j+klz{r)l|ixaA#i2zZhO` zXV*veqnMPpt0mMCh6Gr_Z0%hnfk!PJK!ClqB+x)e4W#BO2eY$R_C>&SeARWKzK&2) zYoOF~fP}Z$qkuCEX$kOlc5-nS^OgkuOIPf1{db!W2>2HS=_m>OpP~%aGy!sO1Pma= z3*v!-gh2pdFfUj@5CVa41NcE;5Fd!256sU4hKLF9i-ACZe_z1IYzS)`F>QIpf9HCP zB!PBFq^lSopO=>xua^KX9AV1`78Mo!tHICD^N8Sa_i;g5dh@urGyS6=4|9hi>|K%e za2LQ|MN2EV2T~IFSn2eBPF>d|+PC-@aCUvnEDuB2d%~<05pZX~zgiZv z|L?U3{a3wzbFKe-EyDkm%lB9e-`|7%zYhAJtw;C#UH-?mkHLS84|92RJL0dc^W6zi zP$(x<k*VL5wQ5*z4KxsueA{`y$quQmJ$DRFpj$_pr+= z8Y^}Z0fwGz28~Yr#p{P72s%v^Ue8Jp(amgX2k7?ZH)`)KzI)FVL-r^dSxtz?{bdiX z?lC6wcQhRfjNZtgpf+@xn}^YvX6ffl%4mRK%JJ?HRL&EIxwsA)w0Kn~>8^)|B$lV> zbKJxG-=sU-`B4J8JA1$3C*M3xZdlVX>C+S$h|YDb!tQz6o=R}eg-&VVd2hQl0lmDr zL0!~Nf;>FjVb#7pSqV^<2Hm5scMwQOoLYE=FjEEHT@tu4zs2e4c;F_;mRd$BHN|r0 z+2~kdkh{P2?nNm$LcJoErX?hjp1p0RkLJn28$%T!dI)nfOk%$8A;B3dR~_ywv0cY5 zzk4|1>FMammTtU{z~UV|ehabv|Qy z6=u)M&~tsA4pKVAvd2;y_*mUR`Q1#S_xNT<3CrLUJ(dE83zk{c0|8%lg#~;1M(^?Q zcUdf|XINN_?bS6%HwJQxF6maRZF3ua!pIf#MD)2^f`<^5wTG1>f{KiJf}jTp)GHct z!3>aXbp-Y2wUwh$f{u<%tW(2IdlJ&g)pzH2oT?jECG!j@s6Wd-Byr|}Zbxo7Db%WQ zIL-tya1jUn3S<56(}i73N`{0L;yxIU@t3^z(gIjwaYp~_*SlJb#%r%hR_=L0!MgbB zd#1p^QHI2%URBR+Mylw;p8vz22c0tA?JsgM?2Z&1vn=f+S-?6~Bgp{$wU*JnY>0j= zk8dsQGO7WgIu%R#bt2-oZ1Sub=GSMPjgI!p(9|#@DIq&;TCVq_ST4}kyV1u~=8O|V zNr5Rz>WV?D80XQI<``f&ni1>4UdYkvoCV>cH@ev_@hhe7Kg?O}YCZVc(wDfEiRq$_ z#0~~c{Q;hJc+lT^l;(pL9%s4BnP&6-+lh%yf0z>?ZGUPntFW4v$PU@_ER+@QbDA4H zDfaI0y{G0Po5`}?@Mha8jV*wqiXvZ$h>)hFT=gp_#4J|rR`eSdid6$c&{V04Zbhgf zRO~Z`_Nyd!=}KqhKHa=v+EG3u{A_7=E$nM%qA=*WEFLY&@8on~$j(HCUG!}(EBC}> z1f-h1fV;aI%Xg;xI`sILCScwAJcBYqR+MRnD5BX~B(2rV@##7wd?8x)-L%JRW%c)Y zH7-dYFyZfhkm+y@Z{LOpkTIcSa>smH9DQK~lx{qI!6n}WC-#T0ddAIeX~}X#u+15P z6Jiy;>e>q=*cCK>oA2eGJwU{Mql>s_;&l3?MhHJsu)NGp<#zs1)o3>rYwY-mQSm{s%1M64jovh{^|D929p48S4X+k`R0J!IZV;8tA*jJe@?}_F>=}ypf z7cxYTWK?c@!*yfjqlGfl5B2kcK)aU$B>AP`nxx{n+t(iOQKoTQVT P2NY+Q0x0a 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 CHc1fx literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg b/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg new file mode 100644 index 0000000..930d34e --- /dev/null +++ b/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png b/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png new file mode 100644 index 0000000000000000000000000000000000000000..1d0ebff74ad4ebfdd89a508a2880c787f11412ab GIT binary patch literal 6208 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 R=YKv_QP7lsEo%|*{{e1;dy)VE literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png b/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png new file mode 100644 index 0000000000000000000000000000000000000000..e2bd9b32658e1479a6589d5467cdff16f1c2bce0 GIT binary patch literal 12087 zcmbVyb95%pwrD)DZChV#+xEm?Y&)6Q&cwDcv2A-|O+2yr@;m3A``#aSy|>ouwR&~! z>h0RQyJ}ZOC@DxHz~aJ!fPf%KONps`Jw3i|7HB9C5YU}GCWS8m$3|m;W|nGNE?V+(JSO(Gj7I<9FnZWJd{Kjd@CkZ2 z7@1fDU5Jf==9YH+q?a8%q{Nn{{G^)f^33uMqCg8vDK96Ws+WSAiI=qrw<)Qh05P8j z&ldw*poA(2$e6|1SW+Em27mABDKk2`f(vnvq7PWT*607$_`fdFFKJGuW;`ll68|OZ3*slW zaB*?qVPbN3cV~2GWwduPX993@bN|DGg@xe@gTdL;&c(=s!OofNKODq>&L&Ql4lb7V zcEta1G%~h#b>S!d()8aZ*gD9||2Jbh=l^olm&=$uj2xH%jLb~7w*T1mFKlNQ72y9? z_5a`zWcum`Umk#8y-a`%dbH(vJta4akT~7xk!ug zlYYHnG_^G4VP@fC1^_tOm^oRPIk*4-5pHI7ZUC1!3!AtI7mEn%e|Y>)w44Ao5fOF{ zW>ywq001D)$;~CsCd@7&#>UDj$|}Js`X5?pJ7*UoI}_l4dF<~{2HDenK3qz0h7j{uMH;$m9BGdhWFr>4#wl=gV{@24O!8a6ggF2DCMGN<@+Pu8J(=w%NMHkURI zc2T1iHkbbdi_eF1uCVBAZl8R2&hC3$7`$BcB)s5Dg>>xU%N zk~z+2kI(aspn#HR@U9>l8QK2fx+cl=^UFS3+Trf&OmANwsM*n>0(32Udiqa1CRmnmDdw-FeK!x^_v)YZmV_$J)lUC`eQZvNd^D3Q7`?Ps z8v=i6XEciJ*%FwYot+RA0!n4J2LyRe10@c?7FLFg%orRLi+H@;UjMt+WWAK&oQaK& z>b}?b{3@7-)fOmoI>_iX(mNi^Tk-K>e&sncR+l2J zrgrxUMQIW$8ioiElg2j+&w7sCeLN+FqVUrUg}zsZ@2nb!zt-XH&A36R1*}e}u_mW) zY>~bs?NW-Gn?HVfnkRi2cyuBVB6KHdT}!CW2}4P#K|(?@Zy2SI)ZNbG*PRfAO<|kX zm<9Z_WJulR&Y9l6#5zU8OieYJk;y(f{Z8H zV2d-bb;Q8+J~=gc)j1R@3|oZ*;rIFRjx}nQn3!mM-bMl|7ugDa-bfBUKu7@v6#$Zz zEN_9Pj2@zBQxZgivallrEiz}Pl28>HF6EF-+()Dbtqd!I5lqYuCo5d%mhXttCtpTl zI2h9^rKHEEl%fKK8!d$ZAdz9=u&f-8ZnE^tI?JY6{*EPpZFV@-VSmB<*fGM!(VS8+ zuq#B3j%&w}DSPR}Jvm4yY>2Cn%@5PFjijzFkEW{0%=Lo$$$F=-v6G~YQ-_g2HU5VM zUEEQf7NeaHE@N8v_|$pU0=03@cJ>LM`r{8W!mEv%fj+C)Pixji3*Q1wnc!t!S6qLD z_GwKP|7mKmsC~OJMk^b?lKw|5biV=TE+TYaUgJRnodYqJ^k{AM%uUE692Y-Jtx6D5 zbN9S-P;a2USqM#BeT40k~up;cPU>*^kRtRBng4(OP*I%rf5@5(oI zvC0)FcW`<$D#4Xz3}$b>oPpmqnC}IlZVQnIL%C^8{KH$D3MPHe39m}s7*};U0q;BZ zcQC=cBkY65fP+@@Kw@Il7}69Ok_4&Vv-<)qdfXChXo$NPGHiIe&#U3i6Qb|!Xk9aZ zz8#V?h3ncD3mrT0yTmm}2e=58BBlwvfZDaJLr0J7&&IH3E8soJBtqoQeAE8v`VETC zGVeQWN*)nCKH~miAR0mS^xD<3|JdR;d!Z#Oj37})KHbP(OM?E0cdRS*?;)FZxyHs+ z?xb##rOjYb96QjJzOUF2ZBg z_@ys5+MIb=A1D30ow)+SvyU$>phCdKQj!{wkO`L;UOA;)viCepe9<}`)6;m?dQ=-d zNRU`OpSt7X>CaA1cH!1-I3R+AIhbeT=IK^;?>oahb;q?QV*fJZ#$@VrRwIND__Hd= z&(UMTEsP8kkD!F4yjbvrIR*#Hs4aGiguW1}xYzVmKP0y?iOrLkGo*hQNAwvAsi>gp z+Aj7jmkl`7VJMWs%~a?Yc9#>LHG?o65{Yz}L*u4{q7_9aQ4$SPL1wWciIlE|qm>}} z+zdo08pZU)yiF0L8G`x81tN5qFy%8hxdZyY0eA57t2!sI8!IO4$3+a&jOtiB2Qt5n zBp7sqzJoGpq?>xEG6-MiAk8wCI#lR}df0CU?0Sdbbc7fo(r zSqyc|#MQ4j;6$liJNVOzpBcJxeafYPPbTI>pMMjz@Z%nHKBR`(RpuB#SV5s*##UPJ zBy@@ifG@{G8fihTN`Z*9SK~Q`hV9ybW65wxgj1B*N()H#!4=Llg1&Lgi8D{yqFG#C zZrChUKNn&3xTxpUe`6p5Zh;c@==j7;Exmx-;-{+iFh*cMiQ2+eP?m&~z7dus=n!Kg z8_KX>`+a?y?8~QjtEB*Q8+M%brN5OlG@v>*r!NbWp@J#R4{H)Fn$})l6X8fwP;6m3 z!HUX!4X!8p+cCi21fXKf2rqT3HMsB*yT|J50nyRXS4Zfvte=i;dQwtSLd3|(mG~cW zjOvO=Uq3p%`dKeiQ znfT1isK7w5qT*r_CMF~ZqO>V94?eo0P7)*{&QN=(75XEX4Ag{wJ(m2GaiXiFs_Jf})0-()6CB2t z3l4Vd7F8&9b~Q94ZnpjXV_*c4lzJ@m#L?=Q3MKy$rEg;^Dic!U%}s`zip4^B>XjSM z3sNM;RD-Y5_u}G?5m##nIwyDdDaS3Lv+BT^m)U1ieztKx^SChrmQi zo%=+}09XCQuEi*!TrjfO3=zi|px)ukM@s^#IJbPI5@xB1$UKkEf=w$j+Ks#4qJqkJBAmUUiTQ{Y9+{eprW?6>Ppa#~mNwE|gWe zPXtuASQ$NGkwxJt9^Urf?z6q%6sHq~8*0IwjSur03Q#M96#7m(BXGJSw1D(Qap%M8 zw=mS?xb%n{;RidLtD2wmr1x~Q+_TaB!(vX6L8M$DDs;?cWKwF_@ytNhpAbi+EtC+~ zp;Kvp9#QYM7RE?w_X;bdXX#?%Bh>Gv5$>kHull>ffX-9bABhv4L$;0DaLm|RT9$Kr zKj6!pa+q9u;fdpgr(6y%sOsrlXuTu4JZ_6i=#iLnBJni;oC%itq?25(*k>tHe1(F; z8r*MQk(S}bQKT_)`O(*<)@cBWyRC_BkT<;fi`Prp^%2DGaXJ3_@qN%rW8tofB6T8v zmOBDA{HcKZQxXL;%;umgS%pXrF5I`QUJvxXF>$uoRWg8T0T#QNH*cPrd4_7BFt1I( zJlW{acuII|ti;z0tQ12H_fiQfXd1tALz}idAO}NQvTAR8(~3 zTg+~~oL5u?0RsbLGD@WDRrNQ|#r!3*k2FZ$E$%NRc8r9nscG~iMu{wOHv!)R*^q}Y zE(XR>gUHe4<+ktBY~0h6A?6xpazC@U(1RkW%T|GO1bZ1Yu)BIHusU}%E2gHn(N2wI zmiFG_7iT}!9TYZ(wRJHd)!gJ&*L!vexJ$Ih9gEN1A0sa-JJ0}}sB^#6k6owtnv9Q( zgk4-(a>zA|E-efj6uTYe zq5Mt2z%|RKgIQ_mL?`<<11>eeeis7F5dhXAN(7sNNWm3EW`<9KCC>P1qI z%CZahs97aR^<@X#d=evaF;lfaRH_hzamc`Ii;EgDqk$-!xOogUR2|RyRW|CrG=%Uu zNl;Iwq-wJ4Wh5+J|K9x2mi%nPFoluj_Vu8_)sC{sOv|VFJO(YoXHcl~u13PYMV$;7 zxdyq)5?a(=gPq1&N(CxgA*Za#Qze4m@gEjEO}kUQD~|+%8cbPmc$)$(o(}iJ=WDM7 zqNkS??ly^S@EU_gVN9KHl4U5WAql`JA@cl3t@7*hQ6m~nJUpO{=RU=XHxiQh0{*-+ zfHW-=qTjoQ^!VPahWw#GA~OVyxKV@x9OLIB!srd!t(3N+&ws|M2sk z>3Yn%_1$h8sAEH8(ASR5p+^7^?}GGU>lDtBSE?m%f+F~CsT&yh$6y{RLe%_7^NUYbC_$Wj#`ceb0PjOA-w2YQVP|?uD5AaR1h*S($rG!P5)zv8i zzm%Jq^<8n+gDzLhN>vrse+1ySf>r zQlq9%$dIDoP!_p;9xmTX6Z<8)to998k|`sf32`tq#-8{&!kdLcu`VT6dN`eVJJ#SuX~M+ za=M{=A8@%9s(rma%T`*xcwK%Neu=-IlhlQf&#*Ks^iOAjE z*pAX4&~S)Cx{Bay5hU0Mq(7nzg)A}k33H(D;6-JfiE6ehdO0tn#cf3)q3&2rQ`$x;bL&3@iCHAX=YKw!&vxQRz%XZbk$Z|&ggyK<)W3~(O4+Yk&!*Mm%YsMn!=@G^yP2{o0aqR|{&PZ}( zeW1#{Gm-fLgl6T0_7cl5h!YG_DZw$<3j1P7yoqD8yT9cQ{@RkECi~eWa|OgoC-T)` zd;v=2qOm3GV19oLeP#}FumU9O3@88ei>s-V4Ub{(A6X%2idi}?NL#MND+tru{GL!3 z=QkMm4a?#cIS+8(Lc$#W#w!;ayTrbldW*7L^-M6_rYH^$NUdtM_BgY~(xL}9j{7Ui zzT5j3UCm8jRKbc9ECNj7DklVbN>0yaHSV#*?;BI9;qgmH9e*2|Zd4#NW%JIg938CH z)(@SNuuV?N{M?)te<+mf!yaSFNX)QqSyAzko_e}sguWbUi)A>{>#9r~Dmfn0Is}#B zqQ-2d`m6N&7b0e01YutvE?bl3+$PV4mYwPd6Dq}-9VwbqdJNNTQDf6dcy$>{hGB0) z5>Y}A_(Y&!h|CF<6h2;M@_KV-aJNlCA9IMR%+f4QBeYA@@b-iT^7UnJ>OIkF^Y+fA z8$#nO?3#f)ai3sP&e_@Ow1kEPIitmSH|3o@XB(6JveH9>&Q+uYLKD?BD0h&1wZ@sI zhDPX48i8HwMB7M<^RLPEi_bME=;RP4xk&ycaasAYkz4>oeWkbK z!@^Ab$Jx*<-Z~_?761_Uq3zIoBabadpoQ<4+}n-12|mTv8rEn9+PdP1ldE1UX?w(Z zFz{|%sY4^h2tV?k+vbY4gY-A8;CDtHrmQ7Ov{0ipTSM76t?2 zvJZU8q^6;z&5-$Vb~@In>Y#mtG)SpEE9xPtYvx);pf_#mOS4FYvwpZP*3)0Xm%QBr z))ZCSI=wj-`uIfX@ua1!Lofd4!jkLdnnbUuJEo%@taU;!M5h0b{7+xLN^5*4j!wb(ZR|kNrAllB{pq%=CpgC3?V5j6&;u)YmIPXEd zwR|P&V*SRRU6}I`997z-=SAAR=p6D za)}kt`?(ht@wPh`#rQ&taHJ}NF7XEXxq^``*YC^QJQag z1bGojPY;yBy5|5D{6Hs1#y?-Zo)Kuz=doOEHkCmE`vC9eY392SF6>2k^y zTL==a$NfOT2uEu!@oK=HKr$_ioQpq__<5??3>pw`ZzNP3 zy6+ZqQh4`=VqR1in4s6s<`xHN*!hE;9!L1O{IREW|2t~sAqdWGfmY=lYx{(LEy5pn zL$|>w(@^$Xo!YaqHS7($ax|W@H#^yu6!wlsO9YgA@bgL%VGkIG13;fR=d=6`evx3U z;cl9vZ%T|cg>-jASMcaowY{07wyccAw~Y|r2r| zfT^(2!7>Z1J=~R0!tG&H?)=@45k^1iJ*98V=5~hqDz5L4QMiG7)V=X{fV|%2@Ouw> zVql_KSz8Yc`Azvg3tl1$>>G3DTpoEp0OA{4`sY{?4OqtY zRzFpz7VLHf0;m^u(|AsDD-2re^|{mQ6t4b$Nh0d^^S#NS(oTLS0qStu($O}40Rcb2 z^Zi9^Z0yzWutdTq7rVnn5wWK1`&Qu@`G?|~KZM26cwtdh&D&*VMNL;nrI&%QHbiC@ z1+hr9Bsc8A0TMdCvZ3A;wU-3`ovCqFT5!^w1Qx-?iwzVOD z4%i%lMw5})cQCTJ2&9;Kt@!{J#v^BKesZ-U|Kf_y7H7_nr`M@b0+c1o-CQhvUr0s< zUug-~*a%JBh&Q)^oh<#%Ttw7S=9z2;{m;A^UF(!F;3SLVo)|DnTFs|-2$X_Cj~HCi zCpg|p#JMo z37)lY0wvn>)Yy}zJ|kPwCT+lqgPh;GefWOTGfp{v~zN4m{Ty*h+1fiTq6 zA4$~y>efq=MXyT0$02oO=|h3qeOnysW4-*kgO!$DG%=4M>^<;K@-`QRzTS2i7>skQ>G3&<*G8)MhpW2 z969MJp^W&8m&HELYJJ#GwY69#^t!yEe2YZ1b~T8Khi&E7Fl#G^qeSl=cB0|C*MU)n zxI(sCYzoK_R7!~*xW+=dbF?%9EY?Yuq;NG5VbU5b+@-{1;k0PXWK+dZgGPZsw=`QX zslRoRZEgp$YDXfaQ~@)%iUaF71J~6GY(1F~RDK_<8x6;1*8{? zRm7w3U`%XhjmCqdf03fwD|7n~0hiz%bQzV`OVfsthSo5mR4`0m^Mj_;s0G&-Jr?<> z$e>4jWMH1o)f!U4RQK5)<5c&#I(A~R)Dj-!{|3noFr)}p^OpwT;o*IQ(FB=wzVXCL zpjSRuRTPN9($@<+LAoojk->iU)PQGV6qO_YJ-0v`P=Pc@SXKduVtJDr{6;3sPV>`; z&*@zuxr2%r7jD0tfO99<9jT!qAGHGq(bBE=k4vSThpIIdu5Ra5W153Q47kY#YyCx= z0gad~dz%uP2Tm>fR=$iQx=oui?oW+Q_*5WQ?8^t-J-_N^6*gwf?|`xA zmI7|Am@fGq%zP+jicgN{iFZN;_sgMB#U3iOi;=|0RA!pXOECwdw|p~DMDLbLh?PIw zoQBtbnELI;hsrPbwY-mcU=p1jB!{fAe{zGf=Wr@-)=NVe_p4JpPIOt8S$QMYX0>WA z*^mEmUzUi0`ZYh~vyLq{Zcs+QZk_uc6m40p#Q|rB+l8nKXD2CpY|{2vRj?vI`8_=1 z8E$s=mLw<1@nM)3N*nNeod@mVwhFOLjGnFk!bC%(&9rmESfG(K7LM^K7#z3mZaB?K za)3FctY;(IJP@Mc-<7sRO}feR$-Gf){`6I*>#7+~Ni%XWd~W$H3+6?(j0&y)iFR{h z+6F&oRtce|T#B@rE^u&g*qi=GB6}0*m5jgacf+^sE+c{Es!t4w82kvpa;AeScU|!Z zV)~U_dh#~)Znw{yg3s(t;0Eu0sTZ(sie9@Wcpdu*`b^*>7MiJuZHP(7E&Y{;{dX)p z#zp?9fW9RT`&G^L`jycc6gn?uUaw!eMTsWNQ;~lveW<_&*QumT>+8CH@1co%KP|2p z7#CgWz_5*2y>DUz`apyE&PRkdGG^4Z&ApsUXzcM!9LLUD1CgQFgWS25UKAZL8r{zM zDn9yF2c<@H%GUdsTB4&?9k1TvN8k^{BG%#qx|C@F4+dfS#L=7LWO#VFkcJp+z#ids z%Y+pqimLY~aEZyG?9cZ}FRtLg28c*mxjHR5i`%CB5hC$ARVASgeQF}wMVDR5L`;;6 zT;}|P&>ydonS#9u%a2B5v#EriKhu>kx4d~=6FtLLqFRX$8=T;7ZqUlKkb^yq88m91 zy4TPT;F^u1ZAogz4M+&2R#TLVGzIrS)h&sg^#PZ!-^F5~4bf;Fbzz0hrv&vf@(TR~?=tI+b7J()1hU965<)VIh;hNbEg=S}k_s`h zT~N2SyA-FbrCtl)>fosP^tK?)R1J2@=AyE;5%Ct!bbr;jC;{0~VrQ6SD;zS<+YTE{ zaK@Y;r2g9|JS7dklVI(J8vECmoaHm@3PcO0nm)Ro%4`ei<1zo}wh>FVo9&MmbSR%@ zk(;g3*N_L72(dpj2~cfsv!~5E2%y*eIG+v5*uEs946su0_I|a0{JUN{*>|}t5O>iD zp5~?)r*APPO`GT-sNf|WgB8GJ!~A3;9mZ+^tb{NYBsm!OS&BR01hl2WO3iu+hR zw--y+(^pBQ(Z3tpf3j@YM6vs5VPOv$d)b(0@>=X+cHd*XSf)HWMLX>M;Xi!Q?2mS9 z5zi@G7EjM_l+^t$pEuP@N$R^lGyADX?O*t-qv@ARa27=kgzfCl_98F4islL~5$rYl z8~8mr-8nBob8BfIkWS%U_=VJ&Ynz0JL}}z(i=aN{AWIRm48Cl_#xc2{`WUKsgwO$8 z7hF?u0cB@VKTWpMBxg=-&!_}PqF#xH6c4+?>cuq;)VP$$Kc>;0qvGsT8@;t$NiwH! z8^VShc}~PtMOkaI0N;gjh@2;Kdjlsl{V7BXm8wL0*WEg|pBllw!`d3G<~sR%hDmEY zIe=<~Kxo59c(sE!V44_DBzZ~CIheIw^N#R&19X+@v_r;DA`;ScHuD8O#e0$$>{5<~ zT^P*;Q|D&|F(Vr9-fxU2q z)zPWAIrA|f-0GDIDp&pRr3Dow9&hJZs2*fK5Bl0|&m6kK|`bp_o>hBU!ss+2L6#)n=w%SWuQOF5$?{DXNv~v{E);)61@wt&6Y9L+@^t}Jt7@!8dMMvxw z^gldE6w^~zFaF4J^+^n-=2MZR9vva{mMb=wmfp3y!l2VAdzHhzCd_TJTB4<}vFq6@ zNMpDDx#lV7hvYTwOhaqmb&p9)M+Y$KhhT@|VAN;Kx|;yvgnlb|!^n(f?C*PE zFd#obo3AME3oGtoB49N6Gs-|2M%nNQv8IDHhUR4h!o6LxsZir}s%Yr3_n8!_h;^gk-_`SX{UPb?9?H z7CteTEB_6TP%n9WC*g5mtW=|T!CXbG?D_Vnk`|X~SzKGBs98Du?Lw&?!C%q9L63QI zKdHDs3NF#KYJViI^4cQ94(In{*4KLZm`e^W*IJhEX zm~gG=*9s6aH#vnj5nRV*T?E2!B`Bw`!*I$fSlx#hDO)C7u-SBoVstyIvd-lyC`rd( z>#k?}_v}UOfMWCau;5p6r<>L#hH|$+Nb>268_Kzboq+X62Kr9d{0wSXW!nsyeUV~D z7Q+(x%o1$-qO+x?B^sk&@xu9zH`LS_b4!()mAU1`^i@|bPg?KRNDDsjY z0Hs1DyW=Tz_vzd17OBR6Y#PzCgl6cCo{IA5c#S)mv+)uB)i zT>H(uAa=WW^MoI~*(??hC)R)5e^Hb}C?uU*uFP&^4c0x`OiueM`IrJ4HnneV0H)@^ zD2L6u-_=;dBXE+7@k)9csNIZu#S0txXz?+g7888&^{q z>2j3bao|s+_{%R!<5?Nf++ue|;(ywFEZ$~7KL|g0pMA4rOsSX&$I{0Q0~##|h5P$~ zJ{pK&T#g1zEkYsYO{~P&`9oi_*N$AoE*04Nf)R?%(qpFG@1J1*8s5vOAa}e_0aG{v zt#AnQc8}%}q4e1AqE=1u1MkcUma&MgpBW0A@PltD6zct{Ty2xoM_;lYSW#n+BFA>} z`q^E81qZ*im1KcHD9*K9ok$?choXE#=5&@)_V&x{G@W1vi*#qPNn@>x#y~7-_|D)b z8CL20((28aqVmFhZluL2CYm)>(y~NR8h*HL7~yn+)M*Jf;nLzmpb{~Sp|n!1;^Z$& z`a?~=z)DHRWJpv|d$uwSUiLmSw>^g$N*n8TgJPr}je~6R3^7%t5Y{WA``@cxZQrWv z)c`**4@e>Sh%HcVy6&jNve6sCs5!pfd>0S50>!t(5?sHd6u8ys!>Sh5ySSbW`ynQ+ zvSI2g3s(h%64KaZS!6iZ$G7xy%fm{;-k$I3 z(>=NOvq{hLeNY5fo6|mCN@U}@6ileriboXogA~-@*wK6fvWOmQ)IVm*%v(aJ*K~N2 z(4OiP@AO4j3~D$Hb4OQvVh6SHz~yT4oA4NAVTF6iECW`Uu{G2iG=w)YoS5T_2bvCj zHf$@h+8uZf+0I-5n7*o?%D_spi#y8s9Fup>CS&>&4Sc_|lhZil*cMHo0d4*gbRfV^ z%F5EZf0rzm8D47KHwJg+@|V(#1W5x5D4H@R21cdg_2d2gr`bvF_4=6ov6Fx187U&Y zSP=Ansf}2i+`IGy>W@@7=1lTjvSJLWiMc-Ra`&q-^U_A0xkJ$#?U5o?+K0Epmvw_r Za48O6_8h{j_kWJEON%Rr)rc4d{x3#FmGJ-o literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png b/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb194450232df293d4269448de456ea27c6f5d4 GIT binary patch literal 11124 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png b/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png new file mode 100644 index 0000000000000000000000000000000000000000..5a0f067d32bd70597dd496eee56e504b9b9bd056 GIT binary patch literal 6040 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 S1TbR&0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png b/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png new file mode 100644 index 0000000000000000000000000000000000000000..045d4b25f9727fada65dcfd66cf0a0a3d167ee84 GIT binary patch literal 11237 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

    {^6Retfv z6e)+Y86 z&-OAhn&%B~c$`zOH~YnViV(=PdD(}Fcr6gb z@qCJTX)KfxzL`hqdq$pyR{#-Ot7VpvXTX&Lf;o10Uz!M^YMI*SFN%7P`VNK(D>QTdyCP-Z<%CK(t*T4j;W{3$D2mM!AexlA- zUb<}1Dx;~3vaWm)z9em_tc|P?cssQ$Hj)Y6^M;Y^!#S7DAf(wTVsi4od$#co(U{KZ4KkEm$NX_fw^6flAOA1wX{X>{{THMY+L{U literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/freemius-pricing.js b/freemius/assets/js/pricing/freemius-pricing.js new file mode 100644 index 0000000..a72ff03 --- /dev/null +++ b/freemius/assets/js/pricing/freemius-pricing.js @@ -0,0 +1,2 @@ +/*! For license information please see freemius-pricing.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Freemius=t():e.Freemius=t()}(self,(function(){return(()=>{var e={487:e=>{var t={utf8:{stringToBytes:function(e){return t.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(t.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n{var t,n;t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n={rotl:function(e,t){return e<>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&n.rotl(e,8)|4278255360&n.rotl(e,24);for(var t=0;t0;e--)t.push(Math.floor(256*Math.random()));return t},bytesToWords:function(e){for(var t=[],n=0,a=0;n>>5]|=e[n]<<24-a%32;return t},wordsToBytes:function(e){for(var t=[],n=0;n<32*e.length;n+=8)t.push(e[n>>>5]>>>24-n%32&255);return t},bytesToHex:function(e){for(var t=[],n=0;n>>4).toString(16)),t.push((15&e[n]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],n=0;n>>6*(3-i)&63)):n.push("=");return n.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var n=[],a=0,r=0;a>>6-2*r);return n}},e.exports=n},477:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,':root{--fs-ds-blue-10: #f0f6fc;--fs-ds-blue-50: #c5d9ed;--fs-ds-blue-100: #9ec2e6;--fs-ds-blue-200: #72aee6;--fs-ds-blue-300: #4f94d4;--fs-ds-blue-400: #3582c4;--fs-ds-blue-500: #2271b1;--fs-ds-blue-600: #135e96;--fs-ds-blue-700: #0a4b78;--fs-ds-blue-800: #043959;--fs-ds-blue-900: #01263a;--fs-ds-neutral-10: #f0f0f1;--fs-ds-neutral-50: #dcdcde;--fs-ds-neutral-100: #c3c4c7;--fs-ds-neutral-200: #a7aaad;--fs-ds-neutral-300: #8c8f94;--fs-ds-neutral-400: #787c82;--fs-ds-neutral-500: #646970;--fs-ds-neutral-600: #50575e;--fs-ds-neutral-700: #3c434a;--fs-ds-neutral-800: #2c3338;--fs-ds-neutral-900: #1d2327;--fs-ds-neutral-900-fade-60: rgba(29, 35, 39, .6);--fs-ds-neutral-900-fade-92: rgba(29, 35, 39, .08);--fs-ds-green-10: #b8e6bf;--fs-ds-green-100: #68de7c;--fs-ds-green-200: #1ed14b;--fs-ds-green-300: #00ba37;--fs-ds-green-400: #00a32a;--fs-ds-green-500: #008a20;--fs-ds-green-600: #007017;--fs-ds-green-700: #005c12;--fs-ds-green-800: #00450c;--fs-ds-green-900: #003008;--fs-ds-red-10: #facfd2;--fs-ds-red-100: #ffabaf;--fs-ds-red-200: #ff8085;--fs-ds-red-300: #f86368;--fs-ds-red-400: #e65054;--fs-ds-red-500: #d63638;--fs-ds-red-600: #b32d2e;--fs-ds-red-700: #8a2424;--fs-ds-red-800: #691c1c;--fs-ds-red-900: #451313;--fs-ds-yellow-10: #fcf9e8;--fs-ds-yellow-100: #f2d675;--fs-ds-yellow-200: #f0c33c;--fs-ds-yellow-300: #dba617;--fs-ds-yellow-400: #bd8600;--fs-ds-yellow-500: #996800;--fs-ds-yellow-600: #755100;--fs-ds-yellow-700: #614200;--fs-ds-yellow-800: #4a3200;--fs-ds-yellow-900: #362400;--fs-ds-white-10: #ffffff}#fs_pricing_app,#fs_pricing_wrapper{--fs-ds-theme-primary-accent-color: var(--fs-ds-blue-500);--fs-ds-theme-primary-accent-color-hover: var(--fs-ds-blue-600);--fs-ds-theme-primary-green-color: var(--fs-ds-green-500);--fs-ds-theme-primary-red-color: var(--fs-ds-red-500);--fs-ds-theme-primary-yellow-color: var(--fs-ds-yellow-500);--fs-ds-theme-error-color: var(--fs-ds-theme-primary-red-color);--fs-ds-theme-success-color: var(--fs-ds-theme-primary-green-color);--fs-ds-theme-warn-color: var(--fs-ds-theme-primary-yellow-color);--fs-ds-theme-background-color: var(--fs-ds-white-10);--fs-ds-theme-background-shade: var(--fs-ds-neutral-10);--fs-ds-theme-background-accented: var(--fs-ds-neutral-50);--fs-ds-theme-background-hover: var(--fs-ds-neutral-200);--fs-ds-theme-background-overlay: var(--fs-ds-neutral-900-fade-60);--fs-ds-theme-background-dark: var(--fs-ds-neutral-800);--fs-ds-theme-background-darkest: var(--fs-ds-neutral-900);--fs-ds-theme-text-color: var(--fs-ds-neutral-900);--fs-ds-theme-heading-text-color: var(--fs-ds-neutral-800);--fs-ds-theme-muted-text-color: var(--fs-ds-neutral-600);--fs-ds-theme-dark-background-text-color: var(--fs-ds-white-10);--fs-ds-theme-dark-background-muted-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-divider-color: var(--fs-ds-theme-background-accented);--fs-ds-theme-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-background-hover-color: var(--fs-ds-neutral-200);--fs-ds-theme-button-text-color: var(--fs-ds-theme-heading-text-color);--fs-ds-theme-button-border-color: var(--fs-ds-neutral-300);--fs-ds-theme-button-border-hover-color: var(--fs-ds-neutral-600);--fs-ds-theme-button-border-focus-color: var(--fs-ds-blue-400);--fs-ds-theme-button-primary-background-color: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-button-primary-background-hover-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-button-primary-text-color: var(--fs-ds-white-10);--fs-ds-theme-button-primary-border-color: var(--fs-ds-blue-800);--fs-ds-theme-button-primary-border-hover-color: var(--fs-ds-blue-900);--fs-ds-theme-button-primary-border-focus-color: var(--fs-ds-blue-100);--fs-ds-theme-button-disabled-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-disabled-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-disabled-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-notice-warn-background: var(--fs-ds-yellow-10);--fs-ds-theme-notice-warn-color: var(--fs-ds-yellow-900);--fs-ds-theme-notice-warn-border: var(--fs-ds-theme-warn-color);--fs-ds-theme-notice-info-background: var(--fs-ds-theme-background-shade);--fs-ds-theme-notice-info-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-notice-info-border: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-package-popular-background: var(--fs-ds-blue-200);--fs-ds-theme-testimonial-star-color: var(--fs-ds-yellow-300)}#fs_pricing.fs-full-size-wrapper{margin-top:0}#root,#fs_pricing_app{background:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-text-color);height:auto;line-height:normal;font-size:13px;margin:0}#root h1,#root h2,#root h3,#root h4,#root ul,#root blockquote,#fs_pricing_app h1,#fs_pricing_app h2,#fs_pricing_app h3,#fs_pricing_app h4,#fs_pricing_app ul,#fs_pricing_app blockquote{margin:0;padding:0;text-align:center;color:var(--fs-ds-theme-heading-text-color)}#root h1,#fs_pricing_app h1{font-size:2.5em}#root h2,#fs_pricing_app h2{font-size:1.5em}#root h3,#fs_pricing_app h3{font-size:1.2em}#root ul,#fs_pricing_app ul{list-style-type:none}#root p,#fs_pricing_app p{font-size:.9em}#root p,#root blockquote,#fs_pricing_app p,#fs_pricing_app blockquote{color:var(--fs-ds-theme-text-color)}#root strong,#fs_pricing_app strong{font-weight:700}#root li,#root dd,#fs_pricing_app li,#fs_pricing_app dd{margin:0}#root .fs-app-header .fs-page-title,#fs_pricing_app .fs-app-header .fs-page-title{margin:0 0 15px;text-align:left;display:flex;flex-flow:row wrap;gap:10px;align-items:center;padding:20px 15px 10px}#root .fs-app-header .fs-page-title h1,#fs_pricing_app .fs-app-header .fs-page-title h1{font-size:18px;margin:0}#root .fs-app-header .fs-page-title h3,#fs_pricing_app .fs-app-header .fs-page-title h3{margin:0;font-size:14px;padding:4px 8px;font-weight:400;border-radius:4px;background-color:var(--fs-ds-theme-background-accented);color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-header .fs-plugin-title-and-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo{margin:0 15px;background:var(--fs-ds-theme-background-color);padding:12px 0;border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;text-align:center}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#root .fs-app-header .fs-plugin-title-and-logo h1,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo h1{display:inline-block;vertical-align:middle;margin:0 10px}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:48px;height:48px;border-radius:4px}@media screen and (min-width: 601px){#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:64px;height:64px}}#root .fs-trial-message,#fs_pricing_app .fs-trial-message{padding:20px;background:var(--fs-ds-theme-notice-warn-background);color:var(--fs-ds-theme-notice-warn-color);font-weight:700;text-align:center;border-top:1px solid var(--fs-ds-theme-notice-warn-border);border-bottom:1px solid var(--fs-ds-theme-notice-warn-border);font-size:1.2em;box-sizing:border-box;margin:0 0 5px}#root .fs-app-main,#fs_pricing_app .fs-app-main{text-align:center}#root .fs-app-main .fs-section,#fs_pricing_app .fs-app-main .fs-section{margin:auto;display:block}#root .fs-app-main .fs-section .fs-section-header,#fs_pricing_app .fs-app-main .fs-section .fs-section-header{font-weight:700}#root .fs-app-main>.fs-section,#fs_pricing_app .fs-app-main>.fs-section{padding:20px;margin:4em auto 0}#root .fs-app-main>.fs-section:nth-child(even),#fs_pricing_app .fs-app-main>.fs-section:nth-child(even){background:var(--fs-ds-theme-background-color)}#root .fs-app-main>.fs-section>header,#fs_pricing_app .fs-app-main>.fs-section>header{margin:0 0 3em}#root .fs-app-main>.fs-section>header h2,#fs_pricing_app .fs-app-main>.fs-section>header h2{margin:0;font-size:2.5em}#root .fs-app-main .fs-section--plans-and-pricing,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing{padding:20px;margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section{margin:1.5em auto 0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child{margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount{font-weight:700;font-size:small}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header{text-align:center;background:var(--fs-ds-theme-background-color);padding:20px;border-radius:5px;box-sizing:border-box;max-width:945px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2{margin-bottom:10px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4{font-weight:400}#root .fs-app-main .fs-section--plans-and-pricing .fs-currencies,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-currencies{border-color:var(--fs-ds-theme-button-border-color)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles{display:inline-block;vertical-align:middle;padding:0 10px;width:auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles{overflow:hidden}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li{border:1px solid var(--fs-ds-theme-border-color);border-right-width:0;display:inline-block;font-weight:700;margin:0;padding:10px;cursor:pointer}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child{border-radius:20px 0 0 20px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child{border-radius:0 20px 20px 0;border-right-width:1px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation{padding:15px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;box-sizing:border-box;max-width:945px;margin:0 auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2{margin-bottom:10px;font-weight:700}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p{font-size:small;margin:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee{max-width:857px;margin:30px auto;position:relative}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title{color:var(--fs-ds-theme-heading-text-color);font-weight:700;margin-bottom:15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message{font-size:small;line-height:20px;margin-bottom:15px;padding:0 15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img{position:absolute;width:90px;top:50%;right:0;margin-top:-45px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge{display:inline-block;vertical-align:middle;position:relative;box-shadow:none;background:transparent}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge{margin-left:20px;margin-top:13px}#root .fs-app-main .fs-section--testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials{border-top:1px solid var(--fs-ds-theme-border-color);border-bottom:1px solid var(--fs-ds-theme-border-color);padding:3em 4em 4em}#root .fs-app-main .fs-section--testimonials .fs-section-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-section-header{margin-left:-30px;margin-right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav{margin:auto;display:block;width:auto;position:relative}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{top:50%;border:1px solid var(--fs-ds-theme-border-color);border-radius:14px;cursor:pointer;margin-top:11px;position:absolute}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon{display:inline-block;height:1em;width:1em;line-height:1em;color:var(--fs-ds-theme-muted-text-color);padding:5px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev{margin-left:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track{margin:auto;overflow:hidden;position:relative;display:block;padding-top:45px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials{width:10000px;display:block;position:relative;transition:left .5s ease,right .5s ease}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{float:left;font-size:small;position:relative;width:340px;box-sizing:border-box;margin:0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{box-sizing:border-box}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating{color:var(--fs-ds-theme-testimonial-star-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{background:var(--fs-ds-theme-background-color);padding:10px;margin:0 2em;border:1px solid var(--fs-ds-theme-divider-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{border-radius:0 0 8px 8px;border-top:0 none}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header{border-bottom:0 none;border-radius:8px 8px 0 0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo{border:1px solid var(--fs-ds-theme-divider-color);border-radius:44px;padding:5px;background:var(--fs-ds-theme-background-color);width:76px;height:76px;position:relative;margin-top:-54px;left:50%;margin-left:-44px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img{max-width:100%;border-radius:40px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4{margin:15px 0 6px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote{color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message{line-height:18px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author{margin-top:30px;margin-bottom:10px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name{font-weight:700;margin-bottom:2px;color:var(--fs-ds-theme-text-color)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{margin:4em 0 0;position:relative}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li{position:relative;display:inline-block;margin:0 8px}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button{cursor:pointer;border:1px solid var(--fs-ds-theme-border-color);vertical-align:middle;display:inline-block;line-height:0;width:8px;height:8px;padding:0;color:transparent;outline:none;border-radius:4px;overflow:hidden}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span{display:inline-block;width:100%;height:100%;background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button{border:0 none}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span{background:var(--fs-ds-theme-background-accented)}#root .fs-app-main .fs-section--faq,#fs_pricing_app .fs-app-main .fs-section--faq{background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{max-width:945px;margin:0 auto;box-sizing:border-box;text-align:left;columns:2;column-gap:20px}@media only screen and (max-width: 600px){#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{columns:1}}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item{width:100%;display:inline-block;vertical-align:top;margin:0 0 20px;overflow:hidden}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{margin:0;text-align:left}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3{background:var(--fs-ds-theme-background-dark);color:var(--fs-ds-theme-dark-background-text-color);padding:15px;font-weight:700;border:1px solid var(--fs-ds-theme-background-darkest);border-bottom:0 none;border-radius:4px 4px 0 0}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{background:var(--fs-ds-theme-background-color);font-size:small;padding:15px;line-height:20px;border:1px solid var(--fs-ds-theme-border-color);border-top:0 none;border-radius:0 0 4px 4px}#root .fs-button,#fs_pricing_app .fs-button{background:var(--fs-ds-theme-button-background-color);color:var(--fs-ds-theme-button-text-color);padding:12px 10px;display:inline-block;text-transform:uppercase;font-weight:700;font-size:18px;width:100%;border-radius:4px;border:0 none;cursor:pointer;transition:background .2s ease-out,border-bottom-color .2s ease-out}#root .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled){box-shadow:0 0 0 1px var(--fs-ds-theme-button-border-focus-color)}#root .fs-button:hover:not(:disabled),#root .fs-button:focus:not(:disabled),#root .fs-button:active:not(:disabled),#fs_pricing_app .fs-button:hover:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:active:not(:disabled){will-change:background,border;background:var(--fs-ds-theme-button-background-hover-color)}#root .fs-button.fs-button--outline,#fs_pricing_app .fs-button.fs-button--outline{padding-top:11px;padding-bottom:11px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-button-border-color)}#root .fs-button.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:focus:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-focus-color)}#root .fs-button.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:active:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-hover-color)}#root .fs-button.fs-button--type-primary,#fs_pricing_app .fs-button.fs-button--type-primary{background-color:var(--fs-ds-theme-button-primary-background-color);color:var(--fs-ds-theme-button-primary-text-color);border-color:var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary:focus:not(:disabled),#root .fs-button.fs-button--type-primary:hover:not(:disabled),#root .fs-button.fs-button--type-primary:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:active:not(:disabled){background-color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-button-primary-border-hover-color)}#root .fs-button.fs-button--type-primary.fs-button--outline,#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline{background-color:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color);border:1px solid var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled){background-color:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-button:disabled,#fs_pricing_app .fs-button:disabled{cursor:not-allowed;background-color:var(--fs-ds-theme-button-disabled-background-color);color:var(--fs-ds-theme-button-disabled-text-color);border-color:var(--fs-ds-theme-button-disabled-border-color)}#root .fs-button.fs-button--size-small,#fs_pricing_app .fs-button.fs-button--size-small{font-size:14px;width:auto}#root .fs-placeholder:before,#fs_pricing_app .fs-placeholder:before{content:"";display:inline-block}@media only screen and (max-width: 768px){#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{display:none!important}#root .fs-app-main .fs-section>header h2,#fs_pricing_app .fs-app-main .fs-section>header h2{font-size:1.5em}}@media only screen and (max-width: 455px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}#root .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span,#fs_pricing_app .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span{display:none}}@media only screen and (max-width: 375px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}}\n',""]);const s=o},333:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,"#fs_pricing_app .fs-modal,#fs_pricing_wrapper .fs-modal,#fs_pricing_wrapper #fs_pricing_app .fs-modal{position:fixed;inset:0;z-index:1000;zoom:1;text-align:left;display:block!important}#fs_pricing_app .fs-modal .fs-modal-content-container,#fs_pricing_wrapper .fs-modal .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container{display:block;position:absolute;left:50%;background:var(--fs-ds-theme-background-color);box-shadow:0 0 8px 2px #0000004d}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header{background:var(--fs-ds-theme-primary-accent-color);padding:15px}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close{color:var(--fs-ds-theme-background-color)}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content{font-size:1.2em}#fs_pricing_app .fs-modal--loading,#fs_pricing_wrapper .fs-modal--loading,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading{background-color:#0000004d}#fs_pricing_app .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container{width:220px;margin-left:-126px;padding:15px;border:1px solid var(--fs-ds-theme-divider-color);text-align:center;top:50%}#fs_pricing_app .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container span{display:block;font-weight:700;font-size:16px;text-align:center;color:var(--fs-ds-theme-primary-accent-color);margin-bottom:10px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader{width:160px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container i{display:block;width:128px;margin:0 auto;height:15px;background:url(//img.freemius.com/blue-loader.gif)}#fs_pricing_app .fs-modal--refund-policy,#fs_pricing_app .fs-modal--trial-confirmation,#fs_pricing_wrapper .fs-modal--refund-policy,#fs_pricing_wrapper .fs-modal--trial-confirmation,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation{background:rgba(0,0,0,.7)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container{width:510px;margin-left:-255px;top:20%}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close{line-height:24px;font-size:24px;position:absolute;top:-12px;right:-12px;cursor:pointer}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content{height:100%;padding:1px 15px}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer{padding:10px;text-align:right;border-top:1px solid var(--fs-ds-theme-border-color);background:var(--fs-ds-theme-background-shade)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial{margin:0 7px}#fs_pricing_app .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button{width:auto;font-size:13px}\n",""]);const s=o},267:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-package,#fs_pricing_app .fs-package{display:inline-block;vertical-align:top;background:var(--fs-ds-theme-dark-background-text-color);border-bottom:3px solid var(--fs-ds-theme-border-color);width:315px;box-sizing:border-box}#root .fs-package:first-child,#root .fs-package+.fs-package,#fs_pricing_app .fs-package:first-child,#fs_pricing_app .fs-package+.fs-package{border-left:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:last-child,#fs_pricing_app .fs-package:last-child{border-right:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:not(.fs-featured-plan):first-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child{border-top-left-radius:10px}#root .fs-package:not(.fs-featured-plan):first-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child .fs-plan-title{border-top-left-radius:9px}#root .fs-package:not(.fs-featured-plan):last-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child{border-top-right-radius:10px}#root .fs-package:not(.fs-featured-plan):last-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child .fs-plan-title{border-top-right-radius:9px}#root .fs-package .fs-package-content,#fs_pricing_app .fs-package .fs-package-content{vertical-align:middle;padding-bottom:30px}#root .fs-package .fs-plan-title,#fs_pricing_app .fs-package .fs-plan-title{padding:10px 0;background:var(--fs-ds-theme-background-shade);text-transform:uppercase;border-bottom:1px solid var(--fs-ds-theme-divider-color);border-top:1px solid var(--fs-ds-theme-divider-color);width:100%;text-align:center}#root .fs-package .fs-plan-title:last-child,#fs_pricing_app .fs-package .fs-plan-title:last-child{border-right:none}#root .fs-package .fs-plan-description,#root .fs-package .fs-undiscounted-price,#root .fs-package .fs-licenses,#root .fs-package .fs-upgrade-button,#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-licenses,#fs_pricing_app .fs-package .fs-upgrade-button,#fs_pricing_app .fs-package .fs-plan-features{margin-top:10px}#root .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-plan-description{text-transform:uppercase}#root .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-undiscounted-price{margin:auto;position:relative;display:inline-block;color:var(--fs-ds-theme-muted-text-color);top:6px}#root .fs-package .fs-undiscounted-price:after,#fs_pricing_app .fs-package .fs-undiscounted-price:after{display:block;content:"";position:absolute;height:1px;background-color:var(--fs-ds-theme-error-color);left:-4px;right:-4px;top:50%;transform:translateY(-50%) skewY(1deg)}#root .fs-package .fs-selected-pricing-amount,#fs_pricing_app .fs-package .fs-selected-pricing-amount{margin:5px 0}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol{font-size:39px}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer{font-size:58px;margin:0 5px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{display:inline-block;vertical-align:middle}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer){line-height:18px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{display:block;font-size:12px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction{vertical-align:top}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{vertical-align:bottom}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-amount-free,#fs_pricing_app .fs-package .fs-selected-pricing-amount-free{font-size:48px}#root .fs-package .fs-selected-pricing-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-cycle{margin-bottom:5px;text-transform:uppercase;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity .fs-tooltip,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity .fs-tooltip{margin-left:5px}#root .fs-package .fs-upgrade-button-container,#fs_pricing_app .fs-package .fs-upgrade-button-container{padding:0 13px;display:block}#root .fs-package .fs-upgrade-button-container .fs-upgrade-button,#fs_pricing_app .fs-package .fs-upgrade-button-container .fs-upgrade-button{margin-top:20px;margin-bottom:5px}#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-features{text-align:left;margin-left:13px}#root .fs-package .fs-plan-features li,#fs_pricing_app .fs-package .fs-plan-features li{font-size:16px;display:flex;margin-bottom:8px}#root .fs-package .fs-plan-features li:not(:first-child),#fs_pricing_app .fs-package .fs-plan-features li:not(:first-child){margin-top:8px}#root .fs-package .fs-plan-features li>span,#root .fs-package .fs-plan-features li .fs-tooltip,#fs_pricing_app .fs-package .fs-plan-features li>span,#fs_pricing_app .fs-package .fs-plan-features li .fs-tooltip{font-size:small;vertical-align:middle;display:inline-block}#root .fs-package .fs-plan-features li .fs-feature-title,#fs_pricing_app .fs-package .fs-plan-features li .fs-feature-title{margin:0 5px;color:var(--fs-ds-theme-muted-text-color);max-width:260px;overflow-wrap:break-word}#root .fs-package .fs-support-and-main-features,#fs_pricing_app .fs-package .fs-support-and-main-features{margin-top:12px;padding-top:18px;padding-bottom:18px;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-support-and-main-features .fs-plan-support,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-support{margin-bottom:15px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li{font-size:small}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title{margin:0 2px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child),#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child){margin-top:5px}#root .fs-package .fs-plan-features-with-value,#fs_pricing_app .fs-package .fs-plan-features-with-value{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities{border-collapse:collapse;position:relative;width:100%}#root .fs-package .fs-license-quantities,#root .fs-package .fs-license-quantities input,#fs_pricing_app .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities input{cursor:pointer}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span{background-color:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-primary-accent-color);display:inline;padding:4px 8px;border-radius:4px;font-weight:700;margin:0 5px;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount{visibility:hidden}#root .fs-package .fs-license-quantities .fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container{line-height:30px;border-top:1px solid var(--fs-ds-theme-background-shade);font-size:small;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child{border-bottom:1px solid var(--fs-ds-theme-background-shade)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected{border-bottom-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected{background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-divider-color);color:var(--fs-ds-theme-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price),#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price){text-align:left}#root .fs-package .fs-license-quantities .fs-license-quantity,#root .fs-package .fs-license-quantities .fs-license-quantity-discount,#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{vertical-align:middle}#root .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity{position:relative;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity input,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity input{position:relative;margin-top:-1px;margin-left:7px;margin-right:7px}#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{position:relative;margin-right:auto;padding-right:7px;white-space:nowrap;font-variant-numeric:tabular-nums;text-align:right}#root .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child),#fs_pricing_app .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child){border-color:transparent}#root .fs-package .fs-most-popular,#fs_pricing_app .fs-package .fs-most-popular{display:none}#root .fs-package.fs-featured-plan .fs-most-popular,#fs_pricing_app .fs-package.fs-featured-plan .fs-most-popular{display:block;line-height:2.8em;margin-top:-2.8em;border-radius:10px 10px 0 0;color:var(--fs-ds-theme-text-color);background:var(--fs-ds-theme-package-popular-background);text-transform:uppercase;font-size:14px}#root .fs-package.fs-featured-plan .fs-plan-title,#fs_pricing_app .fs-package.fs-featured-plan .fs-plan-title{color:var(--fs-ds-theme-dark-background-text-color);background:var(--fs-ds-theme-primary-accent-color);border-top-color:var(--fs-ds-theme-primary-accent-color);border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantity-discount span{background:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected{background:var(--fs-ds-theme-primary-accent-color);border-color:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child{border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}\n',""]);const s=o},700:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-section--packages,#fs_pricing_app .fs-section--packages{display:inline-block;width:100%;position:relative}#root .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--packages .fs-packages-menu{display:none;flex-wrap:wrap;justify-content:center}#root .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--packages .fs-packages-tab{display:none}#root .fs-section--packages .fs-package-tab,#fs_pricing_app .fs-section--packages .fs-package-tab{display:inline-block;flex:1}#root .fs-section--packages .fs-package-tab a,#fs_pricing_app .fs-section--packages .fs-package-tab a{display:block;padding:4px 10px 7px;border-bottom:2px solid transparent;color:#000;text-align:center;text-decoration:none}#root .fs-section--packages .fs-package-tab.fs-package-tab--selected a,#fs_pricing_app .fs-section--packages .fs-package-tab.fs-package-tab--selected a{border-color:#0085ba}#root .fs-section--packages .fs-packages-nav,#fs_pricing_app .fs-section--packages .fs-packages-nav{position:relative;overflow:hidden;margin:auto}#root .fs-section--packages .fs-packages-nav:before,#root .fs-section--packages .fs-packages-nav:after,#fs_pricing_app .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:after{position:absolute;top:0;bottom:0;width:60px;margin-bottom:32px}#root .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:before{z-index:1}#root .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before{content:"";left:0;background:linear-gradient(to right,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-next-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-next-plan:after{content:"";right:0;background:linear-gradient(to left,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after{top:2.8em}#root .fs-section--packages .fs-prev-package,#root .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-next-package{position:absolute;top:50%;margin-top:-11px;cursor:pointer;font-size:48px;z-index:1}#root .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-prev-package{visibility:hidden;z-index:2}#root .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:2.8em}#root .fs-section--packages .fs-packages,#fs_pricing_app .fs-section--packages .fs-packages{width:auto;display:flex;flex-direction:row;margin-left:auto;margin-right:auto;margin-bottom:30px;border-top-right-radius:10px;position:relative;transition:left .5s ease,right .5s ease;padding-top:5px}#root .fs-section--packages .fs-packages:before,#fs_pricing_app .fs-section--packages .fs-packages:before{content:"";position:absolute;top:0;right:0;bottom:0;width:100px;height:100px}@media only screen and (max-width: 768px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#root .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu{display:block;font-size:24px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab{display:flex;font-size:18px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#root .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:0}}@media only screen and (max-width: 455px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}#root .fs-section--plans-and-pricing,#fs_pricing_app .fs-section--plans-and-pricing{padding:10px}}@media only screen and (max-width: 375px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}}\n',""]);const s=o},302:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-tooltip,#fs_pricing_app .fs-tooltip{cursor:help;position:relative;color:inherit}#root .fs-tooltip .fs-tooltip-message,#fs_pricing_app .fs-tooltip .fs-tooltip-message{position:absolute;width:200px;background:var(--fs-ds-theme-background-darkest);z-index:1;display:none;border-radius:4px;color:var(--fs-ds-theme-dark-background-text-color);padding:8px;text-align:left;line-height:18px}#root .fs-tooltip .fs-tooltip-message:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message:before{content:"";position:absolute;z-index:1}#root .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none),#fs_pricing_app .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none){display:block}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right{transform:translateY(-50%);left:30px;top:8px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before{left:-8px;top:50%;margin-top:-6px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top{left:50%;bottom:30px;transform:translate(-50%)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before{left:50%;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right{right:-10px;bottom:30px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before{right:10px;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}\n',""]);const s=o},645:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",a=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),a&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),a&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,a,r,i){"string"==typeof e&&(e=[[null,e,void 0]]);var o={};if(a)for(var s=0;s0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=i),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),r&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=r):u[4]="".concat(r)),t.push(u))}},t}},81:e=>{"use strict";e.exports=function(e){return e[1]}},867:(e,t,n)=>{let a=document.getElementById("fs_pricing_wrapper");a&&a.dataset&&a.dataset.publicUrl&&(n.p=a.dataset.publicUrl)},738:e=>{function t(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}e.exports=function(e){return null!=e&&(t(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&t(e.slice(0,0))}(e)||!!e._isBuffer)}},568:(e,t,n)=>{var a,r,i,o,s;a=n(12),r=n(487).utf8,i=n(738),o=n(487).bin,(s=function(e,t){e.constructor==String?e=t&&"binary"===t.encoding?o.stringToBytes(e):r.stringToBytes(e):i(e)?e=Array.prototype.slice.call(e,0):Array.isArray(e)||e.constructor===Uint8Array||(e=e.toString());for(var n=a.bytesToWords(e),l=8*e.length,c=1732584193,u=-271733879,f=-1732584194,p=271733878,d=0;d>>24)|4278255360&(n[d]<<24|n[d]>>>8);n[l>>>5]|=128<>>9<<4)]=l;var m=s._ff,g=s._gg,h=s._hh,b=s._ii;for(d=0;d>>0,u=u+v>>>0,f=f+k>>>0,p=p+_>>>0}return a.endian([c,u,f,p])})._ff=function(e,t,n,a,r,i,o){var s=e+(t&n|~t&a)+(r>>>0)+o;return(s<>>32-i)+t},s._gg=function(e,t,n,a,r,i,o){var s=e+(t&a|n&~a)+(r>>>0)+o;return(s<>>32-i)+t},s._hh=function(e,t,n,a,r,i,o){var s=e+(t^n^a)+(r>>>0)+o;return(s<>>32-i)+t},s._ii=function(e,t,n,a,r,i,o){var s=e+(n^(t|~a))+(r>>>0)+o;return(s<>>32-i)+t},s._blocksize=16,s._digestsize=16,e.exports=function(e,t){if(null==e)throw new Error("Illegal argument "+e);var n=a.wordsToBytes(s(e,t));return t&&t.asBytes?n:t&&t.asString?o.bytesToString(n):a.bytesToHex(n)}},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function r(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach((function(e){a[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},a)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var o,s,l=r(e),c=1;c{"use strict";var a=n(414);function r(){}function i(){}i.resetWarningCache=r,e.exports=function(){function e(e,t,n,r,i,o){if(o!==a){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:r};return n.PropTypes=n,n}},697:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},448:(e,t,n)=>{"use strict";var a=n(294),r=n(418),i=n(840);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n

    -
    - -
    \ No newline at end of file diff --git a/freemius/templates/checkout/frame.php b/freemius/templates/checkout/frame.php new file mode 100644 index 0000000..22379dd --- /dev/null +++ b/freemius/templates/checkout/frame.php @@ -0,0 +1,182 @@ +get_slug(); + + $fs_checkout = FS_Checkout_Manager::instance(); + + $plugin_id = fs_request_get( 'plugin_id' ); + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + $plugin_id = $fs->get_id(); + } + + $plan_id = fs_request_get( 'plan_id' ); + $licenses = fs_request_get( 'licenses' ); + + $query_params = $fs_checkout->get_query_params( + $fs, + $plugin_id, + $plan_id, + $licenses + ); + + $return_url = $fs->_get_sync_license_url( $plugin_id ); + $query_params['return_url'] = $return_url; + + $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); + if ( false !== $xdebug_session ) { + $query_params['XDEBUG_SESSION'] = $xdebug_session; + } + + $view_params = array( + 'id' => $VARS['id'], + 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), + ); + fs_require_once_template('secure-https-header.php', $view_params); +?> +
    +
    + +
    \ No newline at end of file diff --git a/freemius/templates/checkout/process-redirect.php b/freemius/templates/checkout/process-redirect.php new file mode 100644 index 0000000..4cb62a0 --- /dev/null +++ b/freemius/templates/checkout/process-redirect.php @@ -0,0 +1,105 @@ +get_id(); + } + + $fs_checkout->verify_checkout_redirect_nonce( $fs ); + + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'json2' ); + fs_enqueue_local_script( 'fs-form', 'jquery.form.js', array( 'jquery' ) ); + + $action = fs_request_get( '_fs_checkout_action' ); + $data = json_decode( fs_request_get_raw( '_fs_checkout_data' ) ); +?> +
    +
    + +
    + +
    +

    + +

    +
    +
    + + diff --git a/freemius/templates/checkout/redirect.php b/freemius/templates/checkout/redirect.php new file mode 100644 index 0000000..7094d9f --- /dev/null +++ b/freemius/templates/checkout/redirect.php @@ -0,0 +1,102 @@ +get_id(); + } + + $plan_id = fs_request_get( 'plan_id' ); + $licenses = fs_request_get( 'licenses' ); + + $query_params = $fs_checkout->get_query_params( + $fs, + $plugin_id, + $plan_id, + $licenses + ); + + // The return URL is a special page which will process the result. + $return_url = $fs_checkout->get_checkout_redirect_return_url( $fs ); + $query_params['return_url'] = $return_url; + + // Add the cancel URL to the same pricing page the request originated from. + $query_params['cancel_url'] = $fs->pricing_url( + fs_request_get( 'billing_cycle', 'annual' ), + fs_request_get_bool( 'trial' ) + ); + + if ( has_site_icon() ) { + $query_params['cancel_icon'] = get_site_icon_url(); + } + + // If the user didn't connect his account with Freemius, + // once he accepts the Terms of Service and Privacy Policy, + // and then click the purchase button, the context information + // of the user will be shared with Freemius in order to complete the + // purchase workflow and activate the license for the right user. + $install_data = array_merge( + $fs->get_opt_in_params(), + array( + 'activation_url' => fs_nonce_url( + $fs->_get_admin_page_url( + '', + array( + 'fs_action' => $fs->get_unique_affix() . '_activate_new', + 'plugin_id' => $plugin_id, + ) + ), + $fs->get_unique_affix() . '_activate_new' + ), + ) + ); + $query_params['install_data'] = json_encode( $install_data ); + + $query_params['_fs_dashboard_independent'] = true; + + $redirect_url = $fs_checkout->get_full_checkout_url( $query_params ); + + if ( ! fs_redirect( $redirect_url ) ) { + // The Header was sent, so the server redirect failed. Rely on JS instead. + ?> +
    +
    + +
    + +
    +

    + click here if you\'re stuck...' ), + esc_url( $redirect_url ) + ), + array( 'a' => array( 'href' => true ) ) + ); ?> +

    +
    +
    + + get_slug(); - $context_params = array( - 'plugin_id' => $fs->get_id(), - 'plugin_public_key' => $fs->get_public_key(), - 'plugin_version' => $fs->get_plugin_version(), - ); - - - // Get site context secure params. - if ( $fs->is_registered() ) { - $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( - $fs->get_site(), - time(), - 'contact' - ) ); - } - - $query_params = array_merge( $_GET, array_merge( $context_params, array( - 'plugin_version' => $fs->get_plugin_version(), - 'wp_login_url' => wp_login_url(), - 'site_url' => Freemius::get_unfiltered_site_url(), -// 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", - ) ) ); + $query_params = FS_Contact_Form_Manager::instance()->get_query_params( $fs ); $view_params = array( 'id' => $VARS['id'], @@ -117,12 +102,3 @@ if ( $has_tabs ) { $fs->_add_tabs_after_content(); } - - $params = array( - 'page' => 'contact', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/forms/affiliation.php b/freemius/templates/forms/affiliation.php index a053d2d..428691a 100644 --- a/freemius/templates/forms/affiliation.php +++ b/freemius/templates/forms/affiliation.php @@ -508,11 +508,3 @@ function showErrorMessage(message) { if ( $has_tabs ) { $fs->_add_tabs_after_content(); } - - $params = array( - 'page' => 'affiliation', - 'module_id' => $module_id, - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); diff --git a/freemius/templates/powered-by.php b/freemius/templates/powered-by.php deleted file mode 100644 index e925b0c..0000000 --- a/freemius/templates/powered-by.php +++ /dev/null @@ -1,61 +0,0 @@ -is_whitelabeled() && ! $fs->apply_filters( 'hide_freemius_powered_by', false ) ) { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'json2' ); - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.js' ); - fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); - ?> -
    - - diff --git a/freemius/templates/pricing.php b/freemius/templates/pricing.php index 05879a5..5c4d243 100644 --- a/freemius/templates/pricing.php +++ b/freemius/templates/pricing.php @@ -6,33 +6,6 @@ * @since 1.0.3 */ - /** - * Note for WordPress.org Theme/Plugin reviewer: - * Freemius is an SDK for plugin and theme developers. Since the core - * of the SDK is relevant both for plugins and themes, for obvious reasons, - * we only develop and maintain one code base. - * - * This code (and page) will not run for wp.org themes (only plugins). - * - * In addition, this page loads an i-frame. We intentionally named it 'frame' - * so it will pass the "Theme Check" that is looking for the string "i" . "frame". - * - * UPDATE: - * After ongoing conversations with the WordPress.org TRT we received - * an official approval for including i-frames in the theme's WP Admin setting's - * page tab (the SDK will never add any i-frames on the sitefront). i-frames - * were never against the guidelines, but we wanted to get the team's blessings - * before we move forward. For the record, I got the final approval from - * Ulrich Pogson (@grapplerulrich), a team lead at the TRT during WordCamp - * Europe 2017 (June 16th, 2017). - * - * If you have any questions or need clarifications, please don't hesitate - * pinging me on slack, my username is @svovaf. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - */ - if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -92,25 +65,9 @@ 'discounts_model' => $fs->apply_filters( 'pricing/discounts_model', 'absolute' ), ) ); - $use_external_pricing = $fs->should_use_external_pricing(); + $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); - if ( ! $use_external_pricing ) { - $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); - wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); - } else { - if ( ! $fs->is_registered() ) { - $template_data = array( - 'id' => $fs->get_id(), - ); - fs_require_template( 'forms/trial-start.php', $template_data); - } - - $view_params = array( - 'id' => $VARS['id'], - 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), - ); - fs_require_once_template('secure-https-header.php', $view_params); - } + wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); $has_tabs = $fs->_add_tabs_before_content(); @@ -119,7 +76,6 @@ } ?>
    -
    '#fs_pricing_wrapper', 'unique_affix' => $fs->get_unique_affix(), 'show_annual_in_monthly' => $fs->apply_filters( 'pricing/show_annual_in_monthly', true ), + 'license' => $fs->has_active_valid_license() ? $fs->_get_license() : null, ), $query_params ); wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); ?> - -
    - - - - - - - - - - -
    _add_tabs_after_content(); } - - $params = array( - 'page' => 'pricing', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 9f80d26..18bf071 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -690,7 +690,7 @@ function radio_station_ajax_schedule_loader() { } else { $views = array( $atts['view'] ); } - echo "Views: " . esc_html( print_r( $views, true ) ) . "\n"; + // echo "Views: " . esc_html( print_r( $views, true ) ) . "\n"; foreach ( $views as $view ) { $view = trim( $view ); diff --git a/includes/onboarding.php b/includes/onboarding.php new file mode 100644 index 0000000..7f1018e --- /dev/null +++ b/includes/onboarding.php @@ -0,0 +1,1034 @@ +' . esc_html( 'Quick Links', 'radio-station' ) . '
    ' . "\n"; + + // --- quicklinks menu --- + echo '' . "\n"; + +} + +// ------------------------- +// Statistics Progress Panel +// ------------------------- +function radio_station_statistics_panel() { + + global $wpdb; + + // --- content progress --- + $progress_items = $content_notdone = $content_progress = 0; + + // --- get show counts --- + $show_count = $shift_count = $active_count = $publish_count = $draft_count = $active_show_count = 0; + $shows_host_count = $genre_show_count = $language_show_count = 0; + $query = "SELECT ID,post_status FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_SHOW_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && count( $results ) > 0 ) { + $show_count = count( $results ); + foreach( $results as $result ) { + + // --- show counts --- + $active = get_post_meta( $result['ID'], 'show_active', true ); + if ( 'on' == $active ) { + $active_count++; + } + if ( 'publish' == $result['post_status'] ) { + $publish_count++; + } + if ( 'draft' == $result['post_status'] ) { + $draft_count++; + } + if ( ( 'on' == $active ) && ( 'publish' == $result['post_status'] ) ) { + $active_show_count++; + } + + // --- shift count --- + $shifts = get_post_meta( $result['ID'], 'show_sched', true ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $shift ) { + if ( !isset( $shift['disabled'] ) || ( 'yes' != $shift['disabled'] ) ) { + $shift_count++; + } + } + } + + // --- show host count --- + $hosts = get_post_meta( $result['ID'], 'show_user_list', true ); + if ( $hosts && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + $shows_host_count++; + } + + // --- show genres count --- + if ( has_term( '', RADIO_STATION_GENRES_SLUG, $result['ID'] ) ) { + $genre_show_count++; + } + + // --- show languages count --- + if ( has_term( '', RADIO_STATION_LANGUAGES_SLUG, $result['ID'] ) ) { + $language_show_count++; + } + + } + } + + // --- check for empty content --- + $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s AND post_status = 'publish' AND post_content = ''"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_SHOW_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && count( $results ) > 0 ) { + $show_empty_desc = count( $results ); + } else { + $show_empty_desc = 0; + } + + // --- get override count --- + $override_count = 0; + $query = "SELECT ID,post_status FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_OVERRIDE_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && count( $results ) > 0 ) { + foreach ( $results as $result ) { + if ( 'publish' == $result['post_status'] ) { + // --- single overrides --- + $overrides = get_post_meta( $result['ID'], 'show_override_sched', true ); + if ( $overrides && is_array( $overrides ) ) { + foreach ( $overrides as $override ) { + if ( !isset( $override['disabled'] ) || ( 'yes' != $override['disabled'] ) ) { + $override_count++; + } + } + } + // --- recurring overrides --- + $recurring_overrides = get_post_meta( $result['ID'], 'show_recurring_sched', true ); + if ( $recurring_overrides && is_array( $recurring_overrides ) ) { + foreach ( $recurring_overrides as $override ) { + if ( !isset( $override['disabled'] ) || ( 'yes' != $override['disabled'] ) ) { + $override_count++; + } + } + } + } + } + } + + // --- get playlist count --- + $playlist_count = $playlist_show_count = 0; + $playlist_show_ids = array(); + $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s AND post_status = 'publish'"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_PLAYLIST_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && count( $results ) > 0 ) { + foreach ( $results as $result ) { + $playlist_show_id = get_post_meta( $result['ID'], 'playlist_show_id', true ); + if ( $playlist_show_id && !in_array( $playlist_show_id, $playlist_show_ids ) ) { + $playlist_show_count++; + } + } + } + $playlist_show_count = count( $playlist_show_ids ); + + // --- get_users with host role for host count --- + $hosts = get_users( array( 'role__in' => array( 'dj' ) ) ); + $host_count = count( $hosts ); + $producers = get_users( array( 'role__in' => array( 'producer' ) ) ); + $producer_count = count( $producers ); + + // --- get term counts --- + $genre_count = wp_count_terms( array( 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'hide_empty' => false ) ); + $language_count = wp_count_terms( array( 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, 'hide_empty' => false ) ); + + // --- get episode counts --- + $episode_count = $episode_show_count = $episode_playlist_count = 0; + $episode_show_ids = $episode_playlist_ids = array(); + $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_EPISODE_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $episode_show_id = get_post_meta( $result['ID'], 'episode_show_id', true ); + if ( !in_array( $episode_show_id, $episode_show_ids ) ) { + $episode_show_ids[] = $episode_show_id; + } + $episode_playlist_id = get_post_meta( $result['ID'], 'episode_playlist', true ); + if ( $episode_playlist_id && !in_array( $episode_playlist_id, $episode_playlist_ids ) ) { + $episode_playlist_ids[] = $episode_playlist_id; + } + } + } + $episode_show_count = count( $episode_show_ids ); + $episode_playlist_count = count( $episode_playlist_ids ); + + // --- Content heading --- + echo '
    ' . esc_html( 'Station Content', 'radio-station' ) . '
    ' . "\n"; + + // --- Shows --- + $progress_items++; + $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show draft count --- + $drafts_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $drafts_link = add_query_arg( 'post_status', 'draft', $drafts_link ); + if ( $draft_count > 0 ) { + echo ' '; + echo esc_html( $draft_count ) . esc_html( __( 'Drafts', 'radio-station' ) ) . ' '; + echo ''; + echo ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } + + // --- show active percentage --- + // TODO: filter show post link for inactive shows + $inactive_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $inactive_link = add_query_arg( 'post_status', 'inactive', $inactive_link ); + echo ' ' . esc_html( $active_percent ) . '% '; + if ( $active_percent < 100 ) { + echo ''; + } + echo esc_html( __( 'Active', 'radio-station' ) ); + if ( $active_percent < 100 ) { + echo ''; + } + echo '.'; + + if ( 0 == $show_empty_desc ) { + echo ' 100% ' . esc_html( 'Description coverage', 'radio-station' ) . '.'; + } else { + $empty_desc_percent = round( ( $show_empty_desc / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $desc_percent = 100 - $empty_desc_percent; + echo ' ' . esc_html( $desc_percent ) . '% '; + // TODO: link to Show list - filtered by lack of description + // $show_desc_url = admin_url( '' ); + // echo ''; + echo esc_html( 'Description coverage', 'radio-station' ) . '.'; + // echo ''; + } + + echo '
    ' . "\n"; + + // --- add show icon --- + $add_show_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'post-new.php' ) ); + echo ''; + echo ''; + echo ''; + echo ''; + echo '
    ' . esc_html( __( 'Show', 'radio-station' ) ) . '
    '; + echo '
    '; + + echo '
    '; + + // --- Shifts --- + $progress_items++; + // TODO: calculate % schedule coverage! + $schedule_percent = 100; // ??? + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $shift_count ) . ''; + echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + + echo ' ' . esc_html( $override_count ) . ''; + echo ' ' . esc_html( __( 'Overrides', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show active percentage --- + echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); + echo '.' . "\n"; + + echo '
    ' . "\n"; + + // --- add shift icon --- + $add_shift_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Shift', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Hosts --- + $progress_items++; + $host_percent = round( ( $shows_host_count / $active_show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $host_count ) . ''; + echo ' ' . esc_html( __( 'Hosts', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $active_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show active percentage --- + // TODO: link to shows list - without hosts filter + echo ' ' . esc_html( $host_percent ) . '% '; + if ( $host_percent < 100 ) { + $show_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + echo ''; + } + echo esc_html( __( 'Host coverage', 'radio-station' ) ); + if ( $host_percent < 100 ) { + echo ''; + } + echo '.' . "\n"; + + echo '
    ' . "\n"; + + // --- add host icon --- + $add_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'users.php' ) ); + if ( defined( 'RADIO_STATION_PRO' ) ) { + $add_host_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $add_host_url = add_query_arg( 'tab', 'roles', $add_host_url ); + } + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Host', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Genres --- + $progress_items++; + $genre_percent = round( ( $genre_show_count / $active_show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $genre_count ) . ''; + echo ' ' . esc_html( __( 'Genres', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $active_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show active percentage --- + echo ' ' . esc_html( $genre_percent ) . '% '; + if ( $genre_percent < 100 ) { + // TODO: link to shows list - without genres filter + $show_genres_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $show_genres_url = add_query_arg( 'filter', 'no-genres', $show_genres_url ); + echo ''; + } + echo esc_html( __( 'Genre coverage', 'radio-station' ) ); + if ( $genre_percent < 100 ) { + echo ''; + } + echo '.' . "\n"; + + echo '
    ' . "\n"; + + // --- add genre icon --- + $add_genre_url = add_query_arg( 'taxonomy', RADIO_STATION_GENRES_SLUG, admin_url( 'edit-tags.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Genre', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Playlists --- + $progress_items++; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $playlist_count ) . ''; + echo ' ' . esc_html( __( 'Playlists', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $playlist_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + + echo ' ' . esc_html( $episode_playlist_count ) . ''; + echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . '.'; + + echo '
    ' . "\n"; + + // --- add playlist icon --- + $add_playlist_url = add_query_arg( 'post_type', RADIO_STATION_PLAYLIST_SLUG, admin_url( 'post-new.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Playlist', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Episodes --- + $progress_items++; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- episode count --- + echo '' . esc_html( $episode_count ) . ''; + echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . ' ' . esc_html( __( 'published for', 'radio-station' ) ); + + // --- episode show count --- + echo ' ' . esc_html( $episode_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . '.'; + + echo '
    ' . "\n"; + + // --- add episode icon --- + if ( defined( 'RADIO_STATION_PRO' ) ) { + + $add_episode_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'post-new.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Episode', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + } else { + // --- upgrade to Pro / feature link --- + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + } + + echo '
    ' . "\n"; + + // --- content progress bar --- + radio_station_progress_bar( $content_progress, $content_notdone, $progress_items ); + +} + +// ----------------------- +// Settings Progress Panel +// ----------------------- +function radio_station_settings_panel() { + + global $current_screen; + $context = ( 'dashboard' == $current_screen->base ) ? 'dashboard' : 'settings'; + $settings = radio_station_get_settings( false ); + + // --- settings progress --- + $progress_items = $settings_notdone = $settings_progress = 0; + + // --- settings checks --- + echo '
    ' . esc_html( 'Station Settings', 'radio-station' ) . '
    ' . "\n"; + + // --- Stream URLs --- + $progress_items++; + $settings_tab = 'general'; + $settings_section = 'broadcast'; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- stream URL --- + echo esc_html( __( 'Stream URL', 'radio-station' ) ) . ' '; + if ( '' != $settings['streaming_url'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; + } + + // --- fallback URL --- + echo ' ' . esc_html( __( 'Fallback stream URL', 'radio-station' ) ) . ' '; + if ( '' != $settings['fallback_url'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; + } + + echo '
    ' . "\n"; + + // --- fix setting icon --- + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo '' . "\n"; + echo ''; + if ( ( '' == $settings['streaming_url'] ) || ( '' == $settings['fallback_url'] ) ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Player Bar --- + $progress_items++; + $settings_tab = 'player'; + $settings_section = 'bar'; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- player bar --- + if ( !defined( 'RADIO_STATION_PRO' ) ) { + echo esc_html( __( 'Player Bar with continuous playback available in Pro version.', 'radio-station' ) ); + } else { + + echo esc_html( __( 'Player Bar', 'radio-station' ) ) . ' '; + if ( 'off' == $settings['player_bar'] ) { + echo esc_html( __( 'not enabled', 'radio-station' ) ) . '. '; + } elseif ( 'top' == $settings['player_bar'] ) { + echo esc_html( __( 'enabled at page top', 'radio-station' ) ) . '. '; + } elseif ( 'bottom' == $settings['player_bar'] ) { + echo esc_html( __( 'enabled at page bottom', 'radio-station' ) ) . '. '; + } + + // --- continuous playback --- + echo esc_html( __( 'Continuous playback', 'radio-station' ) ) . ' '; + if ( 'yes' == $settings['player_bar_continuous'] ) { + echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'disabled', 'radio-station' ) ) . '. '; + } + } + + echo '
    ' . "\n"; + + // --- fix setting icon --- + if ( !defined( 'RADIO_STATION_PRO' ) ) { + + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + } else { + + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + + echo ''; + echo ''; + echo '' . "\n"; + echo ''; + if ( ( 'off' == $settings['player_bar'] ) || ( 'yes' != $settings['player_bar_continuous'] ) ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    ' . "\n"; + } + + echo '
    ' . "\n"; + + // --- Schedule Page --- + $progress_items++; + $settings_tab = 'pages'; + $settings_section = 'schedule'; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- stream URL --- + echo ' ' . esc_html( __( 'Master Schedule Page', 'radio-station' ) ) . ' '; + if ( '' != $settings['schedule_page'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . ' '; + + // --- automatic schedule display --- + if ( 'yes' == $settings['schedule_auto'] ) { + echo esc_html( __( 'to display automatically', 'radio-station' ) ) . '.'; + } else { + // TODO: check for shortcode on schedule page ? + echo esc_html( __( 'to manual display', 'radio-station' ) ) . '.'; + } + } else { + echo esc_html( __( 'not set', 'radio-station' ) ) . '.'; + } + + echo '
    ' . "\n"; + + // --- fix setting icon --- + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ( '' == $settings['schedule_page'] ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    '; + + echo '
    '; + + // --- Station Info --- + $progress_items++; + $infos = array( 'station_title', 'station_image', 'station_phone', 'station_email' ); + $infos_count = count( $infos ); + $info_count = 0; + foreach ( $infos as $info ) { + if ( '' != $settings[$info] ) { + $info_count++; + } + } + $info_percent = round( ( $info_count / $infos_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + echo esc_html( $info_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $infos_count ); + echo ' ' . esc_html( __( 'Station information fields set', 'radio-station' ) ) . '. '; + + echo '
    ' . "\n"; + + // --- fix setting icon --- + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ( 100 == $info_percent ) { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } + echo '
    '; + + echo '
    '; + + + // --- Archive Pages --- + $progress_items++; + $settings_tab = 'archives'; + $settings_section = 'post-types'; + $archives = array( 'show', 'override', 'playlist', 'genre', 'language' ); + $pro_archives = array( 'episode', 'team' ); + if ( defined( 'RADIO_STATION_PRO' ) ) { + $archives = array_merge( $archives, $pro_archives ); + } + $archives_count = count( $archives ); + $archive_count = 0; + foreach ( $archives as $archive ) { + $key = $archive . '_archive_page'; + if ( '' != $settings[$key] ) { + $archive_count++; + } + } + $archive_percent = round( ( $archive_count / $archives_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + echo esc_html( $archive_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $archives_count ); + echo ' ' . esc_html( __( 'Archive pages set', 'radio-station' ) ) . '. '; + echo esc_html( $archive_percent ) . '% ' . esc_html( __( 'Archive coverage', 'radio-station' ) ) . '.'; + + echo '
    ' . "\n"; + + // --- fix setting icon --- + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ( 100 == $archive_percent ) { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } + echo '
    '; + + echo '
    '; + + // --- Pro Archives Pages --- + if ( !defined( 'RADIO_STATION_PRO' ) ) { + + $progress_items++; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + echo esc_html( __( 'Episode and Team Archive pages available in Pro version.' ) ); + echo '
    ' . "\n"; + + // --- upgrade to Pro / feature link --- + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    '; + } + + + // ... + + + // --- settings progress bar --- + radio_station_progress_bar( $settings_progress, $settings_notdone, $progress_items ); + +} + + +// ------------ +// Progress Bar +// ------------ +function radio_station_progress_bar( $done, $undone, $items ) { + $bar_width = 395; + $progress_percent = round( ( $done / $items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $progress_px = round( ( $progress_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); + $undone_percent = round( ( $undone / $items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $undone_px = round( ( $undone_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); + $remainder_percent = 100 - $progress_percent - $undone_percent; + $remainder_px = round( ( $remainder_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + if ( $undone_percent > 0 ) { + echo '' . "\n"; + } + echo '' . esc_html( $progress_percent ) . '%
    ' . "\n"; +} + +// ------------------------- +// Enqueue Onboarding Styles +// ------------------------- +add_action( 'admin_init', 'radio_station_enqueue_onboarding_styles' ); +function radio_station_enqueue_onboarding_styles() { + + global $current_screen; + echo 'Current Screen' . print_r( $current_screen, true ) . ''; + + // --- add onboarding styles --- + if ( ( 'dashboard' == $current_screen->base ) + || ( isset( $_REQUEST['page'] ) && ( 'radio-station' == $_REQUEST['page'] ) ) ) { + radio_station_enqueue_style( 'admin' ); + $css = radio_station_onboarding_styles(); + radio_station_add_inline_style( 'rs-admin', $css ); + } +} + +// ----------------- +// Onboarding Styles +// ----------------- +function radio_station_onboarding_styles() { + $css = ".progress-heading {text-align:center; font-size:15px; font-variant:small-caps; letter-spacing:5px; margin-bottom:5px;} + .progress-list {margin-bottom:7px;} + .progress-list-item {display:inline-block; margin-right:25px;} + .progress-list-item:last-child {margin-right:0;} + .progress-list-item a {text-decoration:none;} + .progress-list-item a:hover {text-decoration:underline; font-weight:bold;} + .progress-line {font-size:13px;} + .progress-bar {width:96%; height:7px; margin-top:7px; margin-bottom:15px; padding:0;} + .progress-bar-label {font-size:13px; font-weight:bold; margin-top:7px; text-indent:15px; line-height:7px;} + /* .progress-bar, .progress-bar-done, .progress-bar-remainder, .progress-bar-label {display:inline-block; vertical-align:middle;} */ + .progress-bar-done {background-color:#00AA77; height:7px; border:1px solid #555; border-right:0;} + .progress-bar-remainder {background-color:#FF9900; height:7px; border:1px solid #555;} + .progress-bar-undone {background-color:#EE0000; height:7px; border:1px solid #555; border-left:0; } + .progress-count {font-weight:bold; font-size:14px; background-color:#CCC; border-radius:10px;} + .progress-icon {width:25px; height:25px;} + .progress-icon.progress-tick, .progress-icon.progress-quickstart {color:#00AA00;} + .progress-icon.progress-cross {color:#EE0000;} + .progress-icon.progress-alert {color:#FF9900;} + .progress-icon.progress-add, .progress-icon.progress-settings {color:#0077CC;} + .progress-icon.progress-upgrade {color:#AA00AA; font-size:22px;} + .progress-icon.progress-docs {color:#CC7700;} + .progress-text {width:350px;} + .progress-icon, .progress-text, .progress-icon-link, .progress-label-link {display:inline-block; vertical-align:middle;} + .progress-text a, .progress-icon-link, .progress-label-link {text-decoration:none;} + .progress-text a:hover, .progress-label-link:hover {text-decoration:underline;} + .progress-label {font-size:11px; margin-top:-5px;} + "; + + return $css; +} \ No newline at end of file diff --git a/includes/schedules.php b/includes/schedules.php index 3acfe53..e9a3170 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -390,7 +390,7 @@ function radio_station_get_current_show( $time = false ) { $current_show = apply_filters( 'radio_station_current_show', $current_show, $time, $show_shifts, $channel ); if ( RADIO_STATION_DEBUG ) { - echo 'Current Show (Set): ' . esc_html( print_r( $current_show, true ) ) . PHP_EOL; + echo 'Current Show (Set): ' . esc_html( print_r( $current_show, true ) ) . '' . PHP_EOL; } // --- set to global data --- diff --git a/includes/shortcodes.php b/includes/shortcodes.php index f3f10c3..8deffa3 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1204,10 +1204,12 @@ function radio_station_genre_archive_list( $atts ) { } // --- genre title --- - $list .= '

    ' . "\n"; + // 2.5.10: change div class to genre-title-wrapper + $list .= '

    ' . "\n"; if ( $atts['link_genres'] ) { - $list .= ''; - $list .= esc_html( $genre['name'] ) . "\n"; + // 2.5.10: added genre-link class + $list .= ''; + $list .= esc_html( $genre['name'] ); $list .= '' . "\n"; } else { $list .= esc_html( $genre['name'] ) . "\n"; @@ -1237,10 +1239,12 @@ function radio_station_genre_archive_list( $atts ) { } // --- genre title --- - $heading .= '

    ' . "\n"; + // 2.5.10: change div class to genre-title-wrapper + $heading .= '

    ' . "\n"; if ( $atts['link_genres'] ) { - $heading .= ''; - $heading .= esc_html( $genre['name'] ) . "\n"; + // 2.5.10: added genre-link class + $heading .= ''; + $heading .= esc_html( $genre['name'] ); $heading .= '' . "\n"; } else { $heading .= esc_html( $genre['name'] ) . "\n"; @@ -2479,6 +2483,11 @@ function radio_station_current_show_shortcode( $atts ) { // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + + // 2.6.7: filter to enqueue= reloader script in current window + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-show', $atts, false ); + } return $html; } @@ -2515,10 +2524,12 @@ function radio_station_current_show_shortcode( $atts ) { if ( $atts['for_time'] ) { $current_shift = radio_station_get_current_show( $atts['for_time'] ); $time = radio_station_get_time( 'datetime', $atts['for_time'] ); - echo '' . "\n"; - echo 'Current Shift For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n"; - echo esc_html( print_r( $current_shift, true ) ) . "\n"; - echo '' . "\n"; + if ( RADIO_STATION_DEBUG ) { + echo '' . "\n"; + echo 'Current Shift For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n"; + echo esc_html( print_r( $current_shift, true ) ) . "\n"; + echo '' . "\n"; + } } else { $current_shift = radio_station_get_current_show(); } @@ -3308,6 +3319,11 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // 2.6.7: filter to enqueue reloader script in current window + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'upcoming-shows', $atts, false ); + } + // --- filter and return --- $html = apply_filters( 'radio_station_upcoming_shows_shortcode_ajax', $html, $atts, $instance ); return $html; @@ -3961,6 +3977,11 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); + // 2.6.7: filter to enqueue= reloader script in current window + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-playlist', $atts, false ); + } + // 2.5.0: added filter for shortcode output $html = apply_filters( 'radio_station_current_playlist_shortcode_ajax', $html, $atts, $instance ); return $html; diff --git a/includes/support-functions.php b/includes/support-functions.php index d0f803f..5e74e10 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1363,9 +1363,12 @@ function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = a function radio_station_get_stream_url() { $streaming_url = ''; $stream = radio_station_get_setting( 'streaming_url' ); - if ( RADIO_STATION_DEBUG ) { - echo 'Stream URL Setting: ' . esc_html( $stream ) . ''; - } + + // commented out: this breaks player settings + // if ( RADIO_STATION_DEBUG ) { + // echo 'Stream URL Setting: ' . esc_html( $stream ) . ''; + // } + if ( $stream && ( '' != $stream ) ) { $streaming_url = $stream; } @@ -1384,9 +1387,12 @@ function radio_station_get_fallback_url() { if ( $fallback && ( '' != $fallback ) ) { $fallback_url = $fallback; } - if ( RADIO_STATION_DEBUG ) { - echo 'Fallback URL Setting: ' . esc_html( $fallback_url ) . ''; - } + + // commented out: this breaks player settings + // if ( RADIO_STATION_DEBUG ) { + // echo 'Fallback URL Setting: ' . esc_html( $fallback_url ) . ''; + // } + $fallback_url = apply_filters( 'radio_station_fallback_url', $fallback_url ); return $fallback_url; @@ -2007,7 +2013,8 @@ function radio_station_sanitize_values( $data, $keys ) { // echo 'Sanitize Keys: '; print_r( $keys ); // echo 'Sanitize Data: '; print_r( $data ); // echo 'Sanitized: '; print_r( $sanitized ); - + // 2.5.10: add filter for sanitized values (in case fixes needed) + $sanitized = apply_filters( 'radio_station_sanitized_values', $sanitized, $data, $keys ); return $sanitized; } diff --git a/loader.php b/loader.php index 4719092..9a768b8 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // ================================= // ------------- -// Loader v1.3.2 +// Loader v1.3.4 // ------------- // Note: Changelog at end of file. @@ -546,6 +546,10 @@ public function update_settings() { // $noncecheck = wp_verify_nonce( sanitize_text_field( $_POST['_wpnonce'] ), $args['slug'] . '_update_settings' ); check_admin_referer( $args['slug'] . '_update_settings' ); + // --- debug posted values --- + // 1.3.?: move debug output to after check_admin_referer + $this->debug_posted( $settings ); + // --- get plugin options and default settings --- // 1.0.9: allow filtering of plugin options (eg. for Pro/Add Ons) $options = $this->options; @@ -1026,6 +1030,35 @@ public function update_settings() { } + // -------------------------- + // Debug Output Posted Values + // -------------------------- + function debug_posted( $settings ) { + if ( $this->debug ) { + echo '
    Current Settings:
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo esc_html( print_r( $settings, true ) ); + echo '

    ' . "\n"; + + echo '
    Plugin Options:
    '; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo esc_html( print_r( $this->options, true ) ); + echo '

    ' . "\n"; + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( isset( $_POST ) ) { + echo '
    Posted Values:
    '; + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $post_keys = array_keys( $_POST ); + foreach ( $post_keys as $post_key ) { + $value = sanitize_text_field( $_POST[$post_key] ); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions + echo esc_html( $post_key ) . ': ' . esc_html( print_r( $value, true ) ) . '
    ' . "\n"; + } + } + } + } + // ----------------------- // Validate Plugin Setting // ----------------------- @@ -1305,8 +1338,10 @@ public function add_actions() { // --- maybe delete settings on deactivation --- register_deactivation_hook( $args['file'], array( $this, 'delete_settings' ) ); - // --- maybe load thickbox --- + // --- maybe enqueue scripts / thickbox --- add_action( 'admin_enqueue_scripts', array( $this, 'maybe_load_thickbox' ) ); + // 1.3.4: add earlier enqueue settings page resources check + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_resources' ) ); // --- AJAX readme viewer --- add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) ); @@ -1543,21 +1578,11 @@ public function load_freemius() { // 1.0.5: use sanitize_text_field on request variable // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) { - if ( !function_exists( 'wp_redirect' ) ) { - include ABSPATH . WPINC . '/pluggable.php'; - } + // 1.0.7: fix support URL undefined variable warning if ( isset( $args['support'] ) ) { - // changes the support forum slug for premium based on the pro plugin file slug - // 1.0.7: fix support URL undefined variable warning - $support_url = $args['support']; - // 1.2.1: removed in favour of filtering via Pro - // if ( $premium && isset( $args['proslug'] ) ) { - // $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); - // } - $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] ); - // phpcs:ignore WordPress.Security.SafeRedirect - wp_redirect( $support_url ); - exit; + // 1.3.6: add action and bug out on redirect + add_action( 'admin_init', array( $this, 'support_redirect' ) ); + return; } } @@ -1683,6 +1708,28 @@ public function freemius_connect() { } } + // ---------------- + // Support Redirect + // ---------------- + // 1.3.6: enqueued on admin_init for slightly later execution + function support_redirect() { + + $args = $this->args; + $support_url = $args['support']; + + // changes the support forum slug for premium based on the pro plugin file slug + // 1.2.1: removed in favour of filtering via Pro + // if ( $premium && isset( $args['proslug'] ) ) { + // $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); + // } + $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] ); + + // 1.3.6: removed conditional include of pluggable (no longer necessary) + // phpcs:ignore WordPress.Security.SafeRedirect + wp_redirect( $support_url ); + exit; + } + // ------------------------ // Freemius Connect Message // ------------------------ @@ -1719,6 +1766,58 @@ public function freemius_update_message( $message, $user_first_name, $plugin_tit // --- Plugin Admin --- // ==================== + // -------------------------------- + // Enequeue Settings Page Resources + // -------------------------------- + // 1.3.4: added enqueue resources function + public function enqueue_resources() { + + $args = $this->args; + $namespace = $this->namespace; + + if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] ) ) { + + // --- get plugin options and default settings --- + // 1.1.2: fix for filtering of plugin options + $options = $this->options; + $options = apply_filters( $namespace . '_options', $options ); + + // --- maybe enqueue media scripts --- + // 1.1.7: added media gallery script enqueueing for image field + // 1.1.7: added color picker and color picker alpha script enqueueing + $enqueued_media = $enqueued_color_picker = $enqueue_color_picker = $enqueue_color_picker_alpha = false; + foreach ( $options as $option ) { + if ( ( 'image' == $option['type'] ) && !$enqueued_media ) { + wp_enqueue_media(); + $enqueued_media = true; + } elseif ( 'color' == $option['type'] ) { + $enqueue_color_picker = true; + } elseif ( 'coloralpha' == $option['type'] ) { + $enqueue_color_picker_alpha = true; + } + } + + // 1.2.5: moved out of + if ( $enqueue_color_picker_alpha ) { + wp_enqueue_style( 'wp-color-picker' ); + $suffix = '.min'; + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + $suffix = ''; + } + $url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', $args['file'] ); + wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true ); + $enqueued_color_picker = true; + } elseif ( $enqueue_color_picker ) { + wp_enqueue_style( 'wp-color-picker' ); + wp_enqueue_script( 'wp-color-picker' ); + $enqueued_color_picker = true; + } + + // --- enqueue print of settings scripts / styles --- + $this->settings_resources( $enqueued_media, $enqueued_color_picker ); + } + } + // ----------------- // Add Settings Menu // ----------------- @@ -1891,26 +1990,8 @@ public function notice_boxer() { echo '' . "\n"; echo '

    ' . "\n"; - // --- toggle notice box script --- - echo ""; + // 1.3.6: move notice boxer scripts to setting_scripts + $this->scripts[] = 'notice_boxer'; } @@ -1923,30 +2004,6 @@ public function settings_header() { $namespace = $this->namespace; $settings = $GLOBALS[$namespace]; - // --- output debug values --- - if ( $this->debug ) { - echo '
    Current Settings:
    '; - // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo esc_html( print_r( $settings, true ) ); - echo '

    ' . "\n"; - - echo '
    Plugin Options:
    '; - // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo esc_html( print_r( $this->options, true ) ); - echo '

    ' . "\n"; - - // phpcs:ignore WordPress.Security.NonceVerification.Missing - if ( isset( $_POST ) ) { - echo '
    Posted Values:
    '; - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $posted = array_map( 'sanitize_text_field', $_POST ); - foreach ( $posted as $key => $value ) { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions - echo esc_html( $key ) . ': ' . esc_html( print_r( $value, true ) ) . '
    ' . "\n"; - } - } - } - // --- check for animated gif icon with fallback to normal icon --- // 1.0.9: fix to check if PNG file exists $icon_url = false; @@ -2212,37 +2269,6 @@ public function settings_table() { $options = $this->options; $options = apply_filters( $namespace . '_options', $options ); - // --- maybe enqueue media scripts --- - // 1.1.7: added media gallery script enqueueing for image field - // 1.1.7: added color picker and color picker alpha script enqueueing - $enqueued_media = $enqueued_color_picker = $enqueue_color_picker = $enqueue_color_picker_alpha = false; - foreach ( $options as $option ) { - if ( ( 'image' == $option['type'] ) && !$enqueued_media ) { - wp_enqueue_media(); - $enqueued_media = true; - } elseif ( 'color' == $option['type'] ) { - $enqueue_color_picker = true; - } elseif ( 'coloralpha' == $option['type'] ) { - $enqueue_color_picker_alpha = true; - } - } - - // 1.2.5: moved out of - if ( $enqueue_color_picker_alpha ) { - wp_enqueue_style( 'wp-color-picker' ); - $suffix = '.min'; - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $suffix = ''; - } - $url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', $args['file'] ); - wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true ); - $enqueued_color_picker = true; - } elseif ( $enqueue_color_picker ) { - wp_enqueue_style( 'wp-color-picker' ); - wp_enqueue_script( 'wp-color-picker' ); - $enqueued_color_picker = true; - } - $defaults = $this->default_settings(); $settings = $this->get_settings( false ); @@ -2259,7 +2285,10 @@ public function settings_table() { $sections = $this->sections; $currenttab = ''; - if ( isset( $settings['settingstab'] ) ) { + // 1.3.4: allow for switching setting tab via querystring + if ( isset( $_REQUEST['tab'] ) ) { + $currenttab = sanitize_text_field( wp_unslash( $_REQUEST['tab'] ) ); + } elseif ( isset( $settings['settingstab'] ) ) { $currenttab = $settings['settingstab']; } @@ -2442,7 +2471,7 @@ public function settings_table() { echo '' . "\n"; // --- enqueue settings resources --- - $this->settings_resources( $enqueued_media, $enqueued_color_picker ); + // 1.3.4: moved settings resources enqueue to admin_enqueue_scripts } // --------------------- @@ -2532,10 +2561,19 @@ public function settings_resources( $media = true, $color_picker = true ) { } // --- enqueue settings scripts --- - add_action( 'admin_footer', array( $this, 'setting_scripts' ) ); + // 1.3.4: change from admin_footer hook + // 1.3.5: change back to admin_footer hook (for jQuery!) + // 1.3.6: enqueue and append to dummy admin script + // add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + // add_action( 'admin_footer', array( $this, 'setting_scripts' ) ); + $this->enqueue_scripts(); // --- enqueue settings styles --- - add_action( 'admin_footer', array( $this, 'setting_styles' ) ); + // 1.3.4: change from admin_footer hook + // 1.3.6: enqueue and append to dummy admin style + // add_action( 'admin_enqueue_styles', array( $this, 'enqueue_styles' ) ); + // add_action( 'admin_print_styles', array( $this, 'setting_styles' ) ); + $this->enqueue_styles(); } @@ -3024,6 +3062,20 @@ public function setting_row( $option ) { return $row; } + // --------------- + // Enqueue Scripts + // --------------- + // 1.3.6: enqueue scripts inline via dummy script + public function enqueue_scripts() { + + $version = $this->plugin_version(); + wp_register_script( 'plugin-admin-settings', null, array( 'jquery' ), $version, true ); + wp_enqueue_script( 'plugin-admin-settings' ); + $js = $this->setting_scripts(); + wp_add_inline_script( 'plugin-admin-settings', $js, 'after' ); + + } + // --------------- // Setting Scripts // --------------- @@ -3032,8 +3084,12 @@ public function setting_scripts() { $args = $this->args; $scripts = $this->scripts; + if ( count( $scripts ) > 0 ) { - echo ""; } } + + // -------------- + // Enqueue Styles + // -------------- + // 1.3.6: enqueue styles inline via dummy stylesheet + public function enqueue_styles() { + + $version = $this->plugin_version(); + wp_register_style( 'plugin-admin-settings', null, array(), $version, 'all' ); + wp_enqueue_style( 'plugin-admin-settings' ); + $css = $this->setting_styles(); + wp_add_inline_style( 'plugin-admin-settings', $css ); + + } // -------------- // Setting Styles @@ -3247,11 +3343,13 @@ public function setting_styles() { // --- filter and output styles --- $namespace = $this->namespace; $styles = apply_filters( $namespace . '_admin_page_styles', $styles ); + $styles_string = implode( "\n", $styles ); + // 1.2.5: added wp_strip_all_tags to styles output // 1.3.0: use wp_kses_post on styles output - // echo wp_strip_all_tags( implode( "\n", $styles ) ); - echo ""; - + // 1.3.6: return style string instead of echo + // echo wp_strip_all_tags( istyles_string ) ); + return $styles_string; } } @@ -3525,6 +3623,13 @@ function radio_station_settings_resources( $media, $color_picker ) { // CHANGELOG // ========= +// == 1.3.4 == +// - switch to settings tab via querystring +// - enqueue settings page resources earlier + +// == 1.3.3 == +// - move post debug output to after check_admin_referer + // == 1.3.2 == // - added isset and wp_unslash to $_REQUEST inputs diff --git a/options.php b/options.php index 9b6b159..0fe33a6 100644 --- a/options.php +++ b/options.php @@ -1197,6 +1197,9 @@ // 'section' => 'taxonomies', // ), + // TODO: guest archive pages + + // === Single Templates === // --- Templates Change Note --- diff --git a/player/js/jquery.jplayer.swf b/player/js/jquery.jplayer.swf deleted file mode 100644 index 340f7f98d0d363bafa787e2bcc7422e15fb7f41c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13714 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 Kh=yOW5&svT>H|gq diff --git a/player/js/radio-player.js b/player/js/radio-player.js index b2fb513..b2f1e5b 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -463,12 +463,14 @@ function radio_player_volume_slider(instance, volume) { sliderbg = jQuery('#radio_container_'+instance+' .rp-volume-slider-bg'); thumb = jQuery('#radio_container_'+instance+' .rp-volume-thumb'); if (slider.length) { - sliderbg.hide(); /* .css('border','inherit'); */ - slider.val(volume); swidth = slider.width(); + sliderbg.hide(); slider.val(volume); swidth = slider.width(); thumb.show(); twidth = thumb.width(); thumb.hide(); - bgwidth = (swidth - (twidth / 2)) * (volume / 100) * 0.98; - sliderbg.attr('style', 'width: '+bgwidth+'px !important;').show(); /* border:inherit; */ - if (radio_player.debug) {newwidth = parseInt(sliderbg.css('width')); console.log('Volume Slider: '+swidth+' : '+twidth+' : '+bgwidth+' : '+newwidth);} + mwidth = parseInt(sliderbg.css('margin-left').replace('px','')); + bgwidth = parseInt((swidth - twidth) * (volume / 100)) - mwidth; + sliderbg.attr('style', 'width: '+bgwidth+'px !important;').show(); + if (radio_player.debug) { + newwidth = parseInt(sliderbg.css('width')); console.log('Volume Slider BF: Slider '+swidth+' : Thumb '+twidth+' : Margin '+mwidth+' : BG '+bgwidth+' : Now '+newwidth); + } if (volume == 100) {container.addClass('maxed');} else {container.removeClass('maxed');} } } @@ -1190,6 +1192,14 @@ jQuery(document).ready(function() { } }); + /* --- bind volume slider background changes --- */ + jQuery('.rp-volume-slider').on('mousemove', function() { + container = jQuery(this).parents('.radio-container') + instance = container.attr('id').replace('radio_container_',''); + volume = parseInt(jQuery(this).val()); + radio_player_volume_slider(instance, volume); + }); + /* --- bind volume slider changes --- */ jQuery('.rp-volume-slider').on('change', function() { container = jQuery(this).parents('.radio-container') @@ -1209,7 +1219,7 @@ jQuery(document).ready(function() { jQuery('.rp-mute').on('click', function() { container = jQuery(this).parents('.radio-container'); instance = container.attr('id').replace('radio_container_',''); - console.log('mute click '+instance); + if (radio_player.debug) {console.log('mute click '+instance);} if (container.hasClass('muted')) {mute = false;} else {mute = true;} if (typeof radio_player_data.players[instance] != 'undefined') { if (radio_player.debug) {console.log('Mute/Unmute Player '+instance);} diff --git a/player/radio-player.php b/player/radio-player.php index 7905e79..35367d2 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -185,7 +185,7 @@ function radio_player_set_debug_mode() { $debug = apply_filters( 'radio_station_player_debug', $debug ); $debug = apply_filters( 'radio_player_debug', $debug ); } - define( 'RADIO_PLAYER_DEBUG', true ); + define( 'RADIO_PLAYER_DEBUG', $debug ); } @@ -961,49 +961,6 @@ function radio_player_ajax() { unset( $_REQUEST['theme'] ); } - // --- open HTML and head --- - // 2.5.0: buffer head content to maybe replace window title tag - // note: do not remove these span tags, they magically "fix" broken output buffering!? - // 2.5.10: set title with javascript instead to avoid output buffering entirely - echo '' . "\n"; - // echo ''; - // ob_start(); - // echo ''; - wp_head(); - // echo ''; - // $head = ob_get_contents(); - // echo ''; - // ob_end_clean(); - if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { - /* if ( stristr( $head, '' ) + 1; - $chunks = str_split( $head, $posa ); - unset( $chunks[0] ); - $head = implode( '', $chunks ); - $posb = stripos( $head, '' ) + strlen( '' ); - $chunks = str_split( $head, $posb ); - unset( $chunks[0] ); - $after = implode( '', $chunks ); - $head = $before . "\n" . '' . esc_html( $atts['title'] ) . '' . "\n" . $after; - } else { - $head .= '' . esc_html( $atts['title'] ) . '' . "\n"; - } - */ - // 2.5.10: set document title with javascript - echo "" . "\n"; - } - - // echo $head . "\n"; - echo '' . "\n"; - - // --- open body --- - echo '' . "\n"; - // 2.5.0: check for popup attribute $popup = ( isset( $atts['popup'] ) && $atts['popup'] ) ? true : false; // 2.5.0: clear widget/block/popup attributes @@ -1040,6 +997,79 @@ function radio_player_ajax() { $background_color = apply_filters( 'radio_player_background_color', $background_color ); } + // --- maybe add text color --- + // 2.5.0: added for matching with background color + // 2.5.6: fix for undefined variable css + // 2.5.10: moved up so that inline style can be in header + $css = ''; + if ( '' != $text_color ) { + if ( ( 'rgb' != substr( $text_color, 0, 3 ) ) && ( '#' != substr( $text_color, 0, 1 ) ) ) { + $text_color = '#' . $text_color; + } + $css .= '#player-contents {color: ' . esc_attr( $text_color ) . ';}' . "\n"; + } + + // --- maybe add background color --- + if ( '' != $background_color ) { + if ( ( 'rgb' != substr( $background_color, 0, 3 ) ) && ( '#' != substr( $background_color, 0, 1 ) ) ) { + $background_color = '#' . $background_color; + } + $css .= 'body {background: ' . esc_attr( $background_color ) . ';}' . "\n"; + } + + // --- output extra player styles --- + $css = apply_filters( 'radio_station_player_ajax_styles', $css, $atts ); + $css = apply_filters( 'radio_player_ajax_styles', $css, $atts ); + // 2.5.6: use wp_kses_post instead of wp_strip_all_tags + // 2.5.6: use radio_player_add_inline_style (with fallback) + radio_player_add_inline_style( $css ); + + // 2.5.10: set document title with javascript + if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { + $js = "document.title = '" . esc_js( $atts['title'] ) . "';" . "\n"; + radio_player_add_inline_script( $js ); + } + + // --- open HTML and head --- + // 2.5.0: buffer head content to maybe replace window title tag + // note: do not remove these span tags, they magically "fix" broken output buffering!? + // 2.5.10: set title with javascript instead to avoid output buffering entirely + echo '' . "\n"; + // echo ''; + // ob_start(); + // echo ''; + wp_head(); + // echo ''; + // $head = ob_get_contents(); + // echo ''; + // ob_end_clean(); + /* if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { + if ( stristr( $head, '' ) + 1; + $chunks = str_split( $head, $posa ); + unset( $chunks[0] ); + $head = implode( '', $chunks ); + $posb = stripos( $head, '' ) + strlen( '' ); + $chunks = str_split( $head, $posb ); + unset( $chunks[0] ); + $after = implode( '', $chunks ); + $head = $before . "\n" . '' . esc_html( $atts['title'] ) . '' . "\n" . $after; + } else { + $head .= '' . esc_html( $atts['title'] ) . '' . "\n"; + } + } */ + + // echo $head . "\n"; + echo '' . "\n"; + + // --- open body --- + echo '' . "\n"; + // --- debug shortcode attributes --- if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { echo 'Radio Player Shortcode Attributes: ' . esc_html( print_r( $atts, true ) ) . ''; @@ -1062,33 +1092,6 @@ function radio_player_ajax() { // --- call wp_footer actions --- wp_footer(); - // --- maybe add text color --- - // 2.5.0: added for matching with background color - // 2.5.6: fix for undefined variable css - $css = ''; - if ( '' != $text_color ) { - if ( ( 'rgb' != substr( $text_color, 0, 3 ) ) && ( '#' != substr( $text_color, 0, 1 ) ) ) { - $text_color = '#' . $text_color; - } - $css .= '#player-contents {color: ' . esc_attr( $text_color ) . ';}' . "\n"; - } - - // --- maybe add background color --- - if ( '' != $background_color ) { - if ( ( 'rgb' != substr( $background_color, 0, 3 ) ) && ( '#' != substr( $background_color, 0, 1 ) ) ) { - $background_color = '#' . $background_color; - } - $css .= 'body {background: ' . esc_attr( $background_color ) . ';}' . "\n"; - } - - // --- output extra player styles --- - $css = apply_filters( 'radio_station_player_ajax_styles', $css, $atts ); - $css = apply_filters( 'radio_player_ajax_styles', $css, $atts ); - // 2.5.6: use wp_kses_post instead of wp_strip_all_tags - // echo ''; - // 2.5.6: use radio_player_add_inline_style (with fallback) - radio_player_add_inline_style( 'radio-player', $css ); - // --- close footer --- echo '

    ' . "\n"; @@ -1105,18 +1108,28 @@ function radio_player_ajax() { // 2.5.6: added for possible missed inline styles (via shortcodes) function radio_player_add_inline_style( $css ) { + // TODO: find a way to fix this? it does NOT enqueue inline // --- add check if style is already done --- - if ( !wp_style_is( 'radio-player', 'done' ) ) { + if ( wp_style_is( 'radio-player', 'registered' ) && !wp_style_is( 'radio-player', 'done' ) ) { + // --- add styles as normal --- wp_add_inline_style( 'radio-player', $css ); + } else { + + $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; + wp_register_style( 'radio-player-footer', null, array(), $version, 'all' ); + wp_enqueue_style( 'radio-player-footer' ); + wp_add_inline_style( 'radio-player-footer', $css ); + // --- fallback: store extra styles for later output --- - global $radio_player_styles; - add_action( 'wp_print_footer_scripts', 'radio_player_print_footer_styles', 20 ); - if ( !isset( $radio_player_styles[$handle] ) ) { - $radio_player_styles = ''; - } - $radio_player_styles .= $css; + // global $radio_player_styles; + // add_action( 'wp_print_footer_scripts', 'radio_player_print_footer_styles', 20 ); + // if ( !isset( $radio_player_styles ) ) { + // $radio_player_styles = ''; + // } + // $radio_player_styles .= $css; + } } @@ -1124,10 +1137,7 @@ function radio_player_add_inline_style( $css ) { // Print Footer Styles // ------------------- // 2.5.6: added for possible missed inline styles (via shortcode) -function radio_player_print_footer_styles() { - global $radio_player_styles; - echo ''; -} +// 2.5.10: removed in favour of enqueueing inline style to dummy // ------------------------- // Sanitize Shortcode Values @@ -1280,7 +1290,7 @@ function radio_player_sanitize_values( $keys ) { // --- open audio element --- $html = ''; if ( 1 === $instance ) { - $html .= "\n"; + $html .= "\n"; } $html .= sprintf( '
    + \ No newline at end of file diff --git a/freemius/templates/debug/scheduled-crons.php b/freemius/templates/debug/scheduled-crons.php index 47a715e..0f5248e 100644 --- a/freemius/templates/debug/scheduled-crons.php +++ b/freemius/templates/debug/scheduled-crons.php @@ -13,6 +13,8 @@ $fs_options = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); $scheduled_crons = array(); + $is_fs_debug_page = ( isset( $VARS['is_fs_debug_page'] ) && $VARS['is_fs_debug_page'] ); + $module_types = array( WP_FS__MODULE_TYPE_PLUGIN, WP_FS__MODULE_TYPE_THEME @@ -73,8 +75,17 @@ $sec_text = fs_text_x_inline( 'sec', 'seconds' ); ?> + +

    + + +

    +

    - + +
    diff --git a/freemius/templates/forms/license-activation.php b/freemius/templates/forms/license-activation.php index 6e81937..2e7ae26 100644 --- a/freemius/templates/forms/license-activation.php +++ b/freemius/templates/forms/license-activation.php @@ -309,6 +309,7 @@ class="fs-available-license-key" */ afterLicenseUserDataLoaded = function () { if ( + false !== otherLicenseOwnerID && null !== otherLicenseOwnerID && otherLicenseOwnerID != is_registered() ? $fs->get_user()->id : 'null' ?> ) { diff --git a/includes/support-functions.php b/includes/support-functions.php index 6ae6a87..a974d36 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -2570,6 +2570,19 @@ function radio_station_widget_player_allowed_html( $allowed, $type, $context ) { 'aria-label' => array(), ); + // --- select --- + // 2.5.13: add select tag + $allowed['select'] = array( + 'id' => array(), + 'class' => array(), + 'name' => array(), + 'value' => array(), + 'type' => array(), + 'multiselect' => array(), + 'style' => array(), + 'onchange' => array(), + ); + // --- styles --- $allowed['style'] = array(); diff --git a/player/radio-player.php b/player/radio-player.php index b2a8cbf..ab30219 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -278,7 +278,11 @@ function radio_player_output( $args = array(), $echo = false ) { if ( RADIO_PLAYER_DEBUG ) { echo 'Parsed Radio Player Output Arguments: ' . esc_html( print_r( $args, true ) ) . ''; } - + + // --- set allowed tags for KSES --- + // 2.5.13: added for output filtering + $allowed = function_exists( 'radio_station_allowed_html' ) ? radio_station_allowed_html( 'widget', 'player' ) : 'post'; + // --- set instanced container IDs --- $player_id = 'radio_player_' . $instance; $container_id = 'radio_container_' . $instance; @@ -366,27 +370,27 @@ function radio_player_output( $args = array(), $echo = false ) { $html['station'] .= '
    ' . "\n"; // --- station title --- - $station_text_html = '
    '; if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { - $station_text_html .= esc_html( $args['title'] ); + $title_display .= esc_html( $args['title'] ); } - $station_text_html .= '
    ' . "\n"; + $title_display = apply_filters( 'radio_player_station_display', $title_display, $args, $instance ); + $station_text_html .= '
    ' . wp_kses( $title_display, $allowed ) . '
    ' . "\n"; // --- station timezone / location / frequency --- // 2.5.0: add filters for timezone / frequency / location display // TODO: add timezone / frequency / location attributes ? $timezone_display = isset( $args['timezone'] ) ? $args['timezone'] : ''; $timezone_display = apply_filters( 'radio_player_timezone_display', $timezone_display, $args, $instance ); - $station_text_html .= '
    ' . esc_html( $timezone_display ) . '
    ' . "\n"; + $station_text_html .= '
    ' . wp_kses( $timezone_display, $allowed ) . '
    ' . "\n"; $frequency_display = isset( $args['frequency'] ) ? $args['frequency'] : ''; $frequency_display = apply_filters( 'radio_player_frequency_display', $frequency_display, $args, $instance ); - $station_text_html .= '
    ' . "\n"; + $station_text_html .= '
    ' . wp_kses( $frequency_display, $allowed ) . '
    ' . "\n"; // 2.5.0: fix to mismatched location variable and class $location_display = isset( $args['location'] ) ? $args['location'] : ''; $location_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); - $station_text_html .= '
    ' . "\n"; + $station_text_html .= '
    ' . wp_kses( $location_display, $allowed ) . '
    ' . "\n"; $html['station'] .= $station_text_html; @@ -579,9 +583,9 @@ function radio_player_output( $args = array(), $echo = false ) { } // 2.5.10: added direct output option - // note: wp_kses_post will disable switcher control in popup/ajax call + // 2.5.13: use wp_kses not wp_kses_post if ( $echo ) { - echo wp_kses_post( $player ); + echo wp_kses( $player, $allowed ); } return $player; @@ -2053,12 +2057,18 @@ function radio_player_get_player_settings( $echo = false ) { // 2.5.7: set currently playing data only if script supported $set_data = false; foreach ( $data as $key => $value ) { - if ( ( 'script' == $key ) && in_array( $value, array( 'jplayer', 'amplitude' ) ) ) { + if ( ( 'script' == $key ) && in_array( $value, array( 'jplayer', 'amplitude', 'howler' ) ) ) { $set_data = true; } } if ( $set_data ) { foreach ( $data as $key => $value ) { + // TODO: attempt auto-fix for secure https stream? + /* if ( ( 'url' == $key ) || ( 'fallback' == $key ) ) { + if ( is_ssl() && strstr( $value, 'http://' ) ) { + $value = str_replace( 'http://', 'https://', $value ); + } + } */ echo "radio_player_data.state.data['" . esc_js( $key ) . "'] = '" . esc_js( $value ) . "';" . "\n"; } } diff --git a/radio-station-admin.php b/radio-station-admin.php index c8ac660..b39a9d4 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -1310,8 +1310,12 @@ function radio_station_launch_offer_notice( $rspage = false ) { // 2.3.1: added free directory listing offer content function radio_station_listing_offer_content( $dismissable = true ) { - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_listing_offer_dismiss' ); + // 2.5.13: add nonce to dismiss action + $nonce = wp_create_nonce( 'offer_dismiss' ); + $dismiss_url = add_query_arg( 'action', 'radio_station_listing_offer_dismiss', admin_url( 'admin-ajax.php' ) ); + $dismiss_url = add_query_arg( '_wpnonce', $nonce, $dismiss_url ); $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); + echo '
      ' . "\n"; // --- directory logo image --- @@ -1385,7 +1389,10 @@ function radio_station_listing_offer_content( $dismissable = true ) { // 2.3.3.9: added for Pro launch discount function radio_station_launch_offer_content( $dismissable = true, $prelaunch = false ) { - $dismiss_url = admin_url( 'admin-ajax.php?action=radio_station_launch_offer_dismiss' ); + // 2.5.13: added offer dismiss nonce + $nonce = wp_create_nonce( 'offer_dismiss' ); + $dismiss_url = add_query_arg( 'action', 'radio_station_launch_offer_dismiss', admin_url( 'admin-ajax.php' ) ); + $dismiss_url = add_query_arg( '_wpnonce', $nonce, $dismiss_url ); $accept_dismiss_url = add_query_arg( 'accepted', '1', $dismiss_url ); echo '
        ' . PHP_EOL; @@ -1479,14 +1486,21 @@ function radio_station_listing_offer_dismiss() { exit; } - // --- set option to dismissed --- - update_option( 'radio_station_listing_offer_dismissed', true ); - if ( isset( $_REQUEST['accept'] ) && ( '1' === sanitize_text_field( $_REQUEST['accept'] ) ) ) { - update_option( 'radio_station_listing_offer_accepted', true ); - } + // 2.5.13: added nonce check + $nonce = sanitize_text_field( $_REQUEST['_wpnonce'] ); + $checknonce = wp_verify_nonce( $nonce, 'offer_dismiss' ); + if ( $checknonce ) { - // --- hide the announcement in parent frame --- - echo "" . "\n"; + // --- set option to dismissed --- + update_option( 'radio_station_listing_offer_dismissed', true ); + if ( isset( $_REQUEST['accept'] ) && ( '1' === sanitize_text_field( $_REQUEST['accept'] ) ) ) { + update_option( 'radio_station_listing_offer_accepted', true ); + } + + // --- hide the announcement in parent frame --- + echo "" . "\n"; + + } exit; } @@ -1502,31 +1516,38 @@ function radio_station_launch_offer_dismiss() { exit; } - // --- get current user ID --- - $user_id = get_current_user_id(); + // 2.5.13: added nonce check + $nonce = sanitize_text_field( $_REQUEST['_wpnonce'] ); + $checknonce = wp_verify_nonce( $nonce, 'offer_dismiss' ); + if ( $checknonce ) { - // --- set option to dismissed --- - $user_ids = get_option( 'radio_station_launch_offer_dismissed' ); - if ( !$user_ids || !is_array( $user_ids ) ) { - $user_ids = array( $user_id ); - } elseif ( !in_array( $user_id, $user_ids ) ) { - $user_ids[] = $user_id; - } - update_option( 'radio_station_launch_offer_dismissed', $user_ids ); + // --- get current user ID --- + $user_id = get_current_user_id(); - // --- maybe set option for accepted --- - if ( isset( $_REQUEST['accept'] ) && ( '1' === sanitize_text_field( $_REQUEST['accepted'] ) ) ) { - $user_ids = get_option( 'radio_station_launch_offer_accepted' ); + // --- set option to dismissed --- + $user_ids = get_option( 'radio_station_launch_offer_dismissed' ); if ( !$user_ids || !is_array( $user_ids ) ) { $user_ids = array( $user_id ); } elseif ( !in_array( $user_id, $user_ids ) ) { $user_ids[] = $user_id; } - update_option( 'radio_station_launch_offer_accepted', $user_ids ); + update_option( 'radio_station_launch_offer_dismissed', $user_ids ); + + // --- maybe set option for accepted --- + if ( isset( $_REQUEST['accept'] ) && ( '1' === sanitize_text_field( $_REQUEST['accepted'] ) ) ) { + $user_ids = get_option( 'radio_station_launch_offer_accepted' ); + if ( !$user_ids || !is_array( $user_ids ) ) { + $user_ids = array( $user_id ); + } elseif ( !in_array( $user_id, $user_ids ) ) { + $user_ids[] = $user_id; + } + update_option( 'radio_station_launch_offer_accepted', $user_ids ); + } + + // --- hide the announcement in parent frame --- + echo "" . "\n"; } - // --- hide the announcement in parent frame --- - echo "" . "\n"; exit; } diff --git a/radio-station.php b/radio-station.php index 38602d8..7eac1f3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.12 +Version: 2.5.13 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -326,7 +326,8 @@ function radio_station_freemius_bundle_config( $settings ) { // ----------------------- // Load Plugin Text Domain // ----------------------- -add_action( 'plugins_loaded', 'radio_station_init' ); +// 2.5.13: change plugins_loaded hook for load_plugin_textdomain +add_action( 'init', 'radio_station_init' ); function radio_station_init() { // 2.3.0: use RADIO_STATION_DIR constant load_plugin_textdomain( 'radio-station', false, RADIO_STATION_DIR . '/languages' ); diff --git a/readme.md b/readme.md index 6168497..79aebf0 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3 Tested up to: 6.8.1 -Stable tag: 2.5.12 +Stable tag: 2.5.13 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 2e38b15..b13f5e1 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 Tested up to: 6.8.1 -Stable tag: 2.5.12 +Stable tag: 2.5.13 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. @@ -404,6 +404,11 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.13 = +* Updated: Freemius SDK (2.12.0) +* Fixed: load text domain too early notice +* Fixed: added nonce checks to offer dismissals + = 2.5.12 = * Updated: Block translation and element functions * Fixed: preload player settings when Block editing From 0e8b52eaa826995dc41fa23671b5200f34e40ad9 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 9 Jul 2025 08:59:16 +1000 Subject: [PATCH 351/377] dev updates --- CHANGELOG.md | 5 +++ includes/master-schedule.php | 51 ++++++++++++++---------------- includes/shortcodes.php | 10 +++--- options.php | 21 ++++++++++-- player/js/radio-player.js | 34 ++++++++++++++------ player/radio-player.php | 16 ++++++---- readme.md | 2 +- readme.txt | 7 +++- templates/master-schedule-list.php | 3 +- templates/master-schedule-tabs.php | 3 +- 10 files changed, 96 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cdc5d3..7f5e8a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.14 = +* Fixed: Player resume (play/pause/play) glitch for Amplitude +* Fixed: Keep active day when changing schedule weeks on Grid view +* Fixed: jPlayer old fallback SWF path to empty string + = 2.5.13 = * Updated: Freemius SDK (2.12.0) * Fixed: load text domain too early notice diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 6257a51..756530f 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -314,6 +314,7 @@ function radio_station_master_schedule( $atts ) { $now = radio_station_get_now(); $start_date = radio_station_get_time( 'date', $now ); } + $output .= ''; $active_date = $start_date; if ( isset( $atts['active_date'] ) && $atts['active_date'] ) { @@ -323,8 +324,11 @@ function radio_station_master_schedule( $atts ) { } } $output .= ''; + // 2.3.3.9: also added schedule start day input - $start_day = ( $atts['start_day'] ) ? $atts['start_day'] : ''; + // 2.5.13: ensure start day string is lowercase + $start_day = ( $atts['start_day'] ) ? strtolower( $atts['start_day'] ) : ''; + $output .= ''; $output .= ''; // --- enqueue schedule loader script --- @@ -667,6 +671,7 @@ function radio_station_master_schedule_loader_js( $atts ) { startday = document.getElementById('schedule-start-day').value; startdate = document.getElementById('schedule-start-date').value; activedate = document.getElementById('schedule-active-date').value; + activeday = document.getElementById('schedule-active-day').value; if (!view) { if ((0 == instance) || (false === instance)) {id = '';} else {id = '-'+instance;} if (jQuery('.master-schedule-view-tab.current-view').length) { @@ -696,6 +701,7 @@ function radio_station_master_schedule_loader_js( $atts ) { } $js .= "&view='+view+'&instance='+instance+'×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; if (startday != '') {url += '&start_day='+startday;} + if (activeday != '') {url += '&active_day='+activeday;} if (clear) {url += '&clear=1';} if (radio.debug) {url += '&rs-debug=1'; console.log('Reload View URL: '+url);} /* if (document.getElementById('schedule-'+view+'-loader').src != url) { @@ -754,7 +760,8 @@ function radio_station_master_schedule_loader_js( $atts ) { } // --- filter and return --- - $js = apply_filters( 'radio_station_master_schedule_loader_js', $js ); + // 2.5.14: added missing attributes argument to filter + $js = apply_filters( 'radio_station_master_schedule_loader_js', $js, $atts ); return $js; } @@ -772,12 +779,14 @@ function radio_station_ajax_schedule_loader() { // 2.5.0: get schedule instance ID $instance = absint( $_REQUEST['instance'] ); - + + // 2.5.13 use the start day if set $active_date = sanitize_text_field( $_REQUEST['active_date'] ); - $day = strtolower( date( 'l', strtotime( $active_date ) ) ); + $active_day = sanitize_text_field( $_REQUEST['active_day'] ); + $day = ( '' != $active_day ) ? $active_day : strtolower( date( 'l', strtotime( $active_date ) ) ); // --- sanitize shortcode attributes --- - $debug = true; + $debug = false; // $debug = true; $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); if ( RADIO_STATION_DEBUG || $debug ) { echo "Full Request Inputs: " . esc_html( print_r( array_map( 'sanitize_text_field', $_REQUEST ), true ) ); @@ -1055,6 +1064,7 @@ function radio_station_master_schedule_table_js() { function radio_table_initialize() { radio_table_responsive(false,false); radio_table_highlight(); + if (typeof radio_table_start_hours != 'undefined') {radio_table_start_hours();} radio_table_init = true; } @@ -1228,37 +1238,17 @@ function radio_station_master_schedule_tabs_js() { // --- tab switching function --- // 2.3.2: added fallback if current day is not viewed - // TODO: check current server time for onload display ? - /* date = new Date(); dayweek = date.getDay(); day = radio_get_weekday(dayweek); - if (jQuery('#master-schedule-tabs-header-'+day).length) { - id = jQuery('.master-schedule-tabs-day.selected-day').first().attr('id'); - day = id.replace('master-schedule-tabs-header-',''); - jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); - jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); - } else { - jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); - jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); - } */ - // 2.3.3.6: allow for clicking on date to change days // 2.3.3.8: make entire heading label div clickable to change tabs // 2.3.3.9: make into function and add to document ready code block - /* $js = "function radio_tabs_clicks() { - jQuery('.master-schedule-tabs-headings').bind('click', function (event) { - headerID = jQuery(event.target).closest('li').attr('id'); - panelID = headerID.replace('header', 'day'); - jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); - jQuery('#'+headerID).addClass('active-day-tab'); - jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); - jQuery('#'+panelID).addClass('active-day-panel'); - }); - }" . "\n"; */ // 2.5.0: use relative traversal from click target instead of IDs + // 2.5.13: change active day input value on tab change $js = "function radio_tabs_clicks() { if (radio.debug) {console.log('Binding Tabbed Schedule Tab Clicks');} jQuery('.master-schedule-tabs-headings').bind('click', function (event) { if (jQuery(event.target).hasClass('master-schedule-tabs-headings')) {day = jQuery(event.target).attr('data-href');} else {day = jQuery(event.target).closest('.master-schedule-tabs-headings').attr('data-href');} + document.getElementById('schedule-active-day').value = day; schedule = jQuery(event.target).closest('.master-schedule-tabs'); panels = schedule.parent().find('.master-schedule-tab-panels'); schedule.find('.master-schedule-tabs-day').removeClass('active-day-tab'); @@ -1277,6 +1267,7 @@ function radio_station_master_schedule_tabs_js() { // 2.3.3.9: check for required elements before executing functions // 2.3.3.9: fix to check before and after current time not show // 2.3.3.9: adjust responsive tabs for possible loader control presence + // 2.5.13: set active tab to active day value if set $js .= "/* Initialize Tabs */ var radio_tabs_init = false; jQuery(document).ready(function() { @@ -1292,6 +1283,7 @@ function radio_tabs_initialize() { radio_tabs_clicks(); radio_tabs_responsive(false,false); radio_tabs_show_highlight(); + if (typeof radio_tabs_start_hours != 'undefined') {radio_tabs_start_hours();} } /* Set Day Tab on Load */ @@ -1325,7 +1317,9 @@ function radio_tabs_show_highlight() { if ((start < radio.offset_time) && (end > radio.offset_time)) { jQuery(this).addClass('current-day'); day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - radio_tabs_active_tab(day,scheduleid); + active_day = document.getElementById('schedule-active-day').value; + if (active_day != '') {radio_tabs_active_tab(active_day,scheduleid);} + else {radio_tabs_active_tab(day,scheduleid);} } else {jQuery(this).removeClass('current-day');} }); radio_tabs_active_tab(false,scheduleid); /* fallback */ @@ -1508,6 +1502,7 @@ function radio_station_master_schedule_list_js() { jQuery(document).ready(function() { radio_list_highlight(); var radio_list_highlighting = setInterval(radio_list_highlight, 60000); + if (typeof radio_list_start_hours != 'undefined') {radio_list_start_hours();} }); /* Current Show Highlighting */ diff --git a/includes/shortcodes.php b/includes/shortcodes.php index b86e102..28dfdaf 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -3996,10 +3996,12 @@ function radio_station_current_playlist_shortcode( $atts ) { if ( $atts['for_time'] ) { $playlist = radio_station_get_now_playing( $atts['for_time'] ); $time = radio_station_get_time( 'datetime', $atts['for_time'] ); - echo ''; - echo 'Current Playlist For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n";; - echo esc_html( print_r( $playlist, true ) ); - echo ''; + if ( RADIO_STATION_DEBUG ) { + echo ''; + echo 'Current Playlist For Time: ' . esc_html( $atts['for_time'] ) . ' : ' . esc_html( $time ) . "\n";; + echo esc_html( print_r( $playlist, true ) ); + echo ''; + } } else { $playlist = radio_station_get_now_playing(); } diff --git a/options.php b/options.php index e19e11f..1992a65 100644 --- a/options.php +++ b/options.php @@ -57,6 +57,23 @@ 'section' => 'broadcast', ), + // --- [Player] Stream GeoBlocking --- + 'stream_geo_blocking' => array( + 'label' => __( 'GeoIP Stream Blocking', 'radio-station' ), + 'type' => 'select', + 'options' => array( + '' => __( 'No GeoIP Blocking', 'radio-station' ), + 'live365' => __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ), + // 'blacklist' => __( 'Custom Country Blacklist', 'radio-station' ), + // 'whitelist' => __( 'Custom Country Whitelist', 'radio-station' ), + ), + 'default' => '', + 'helper' => __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + 'pro' => true, + ), + // --- Main Radio Language --- 'radio_language' => array( 'type' => 'select', @@ -294,9 +311,9 @@ 'label' => __( 'Player Script', 'radio-station' ), 'default' => 'jplayer', 'options' => array( + 'amplitude' => __( 'Amplitude', 'radio-station' ), 'jplayer' => __( 'jPlayer', 'radio-station' ), // 'howler' => __( 'Howler', 'radio-station' ), - 'amplitude' => __( 'Amplitude', 'radio-station' ), ), 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), 'tab' => 'player', @@ -312,9 +329,9 @@ 'label' => __( 'Fallback Scripts', 'radio-station' ), 'default' => array( 'amplitude', 'howler', 'jplayer' ), 'options' => array( + 'amplitude' => __( 'Amplitude', 'radio-station' ), 'jplayer' => __( 'jPlayer', 'radio-station' ), // 'howler' => __( 'Howler', 'radio-station' ), - 'amplitude' => __( 'Amplitude', 'radio-station' ), ), 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), 'tab' => 'player', diff --git a/player/js/radio-player.js b/player/js/radio-player.js index b2f1e5b..b7f4f36 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -90,19 +90,19 @@ function radio_player_check_format(data) { if (!script) { if ((format in radio_player.formats.amplitude) && ('amplitude' in scripts)) {script = 'amplitude';} else if ((format in radio_player.formats.jplayer) && ('jplayer' in scripts)) {script = 'jplayer';} - /* else if ((format in radio_player.formats.howler) && ('howler' in scripts)) {script = 'howler';} */ + else if ((format in radio_player.formats.howler) && ('howler' in scripts)) {script = 'howler';} /* else if ((format in radio_player.formats.mediaelements) && ('mediaelements' in scripts)) {script = 'mediaelements';} */ if (!script) { if ((fformat in radio_player.formats.amplitude) && ('amplitude' in scripts)) {script = 'amplitude';} - else if ((fformat in radio_player.formats.jplayer) && ('howler' in scripts)) {script = 'jplayer';} - /* else if ((fformat in radio_player.formats.howler) && ('jplayer' in scripts)) {script = 'howler';} */ + else if ((fformat in radio_player.formats.jplayer) && ('jplayer' in scripts)) {script = 'jplayer';} + else if ((fformat in radio_player.formats.howler) && ('howler' in scripts)) {script = 'howler';} /* else if ((fformat in radio_player.formats.mediaelements) && ('mediaelements' in scripts)) {script = 'mediaelements';} */ if (script) {a = url; b = format; url = fallback; format = fformat; fallback = a; fformat = b;} } if (!script) { if ('amplitude' in scripts) {script = 'amplitude';} else if ('jplayer' in scripts) {script = 'jplayer';} - /* else if ('howler' in scripts) {script = 'howler';} */ + else if ('howler' in scripts) {script = 'howler';} } } @@ -129,7 +129,9 @@ function radio_player_load_station(instance, station, data, start) { data = radio_player_check_format(data); script = data.script; player = radio_player_load_audio(script, instance, data, start); - if (player && start) {radio_player_play_on_load(player, script, instance);} + if (player && start) { + setTimeout(function() {radio_player_play_on_load(player, script, instance);}, 250); + } } /* --- load a stream --- */ @@ -331,12 +333,22 @@ function radio_player_switch_script(instance, script) { /* === Player Functions and Event Callbacks === */ /* --- play player instance --- */ +// 2.5.13: add retry cycle for missed +var radio_player_retry = {} function radio_player_play_instance(instance) { radio_player.loading = true; player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; - if ((script == 'amplitude') || (script == 'howler')) {player.play();} + console.log(player); console.log(script); + if (script == 'amplitude') { + player.play(); radio_player_retry.player = player; + radio_player_retry.cycle = setInterval(function() { + player = radio_player_retry.player; + if (player.getPlayerState() == 'stopped') {player.play();} + else {clearInterval(radio_player_retry.cycle); radio_player_retry = {};} + }, 250); + } else if (script == 'howler') {player.play();} else if (script == 'jplayer') {player.jPlayer('play');} - if (radio_player.debug) {console.log('Playing '+script+' Player Instance '+instance); radio_player_is_playing(instance);} + if (radio_player.debug) {console.log('Playing '+script+' Player Instance '+instance); console.log(radio_player_is_playing(instance));} radio_player_custom_event('rp-play', {player: player, script: script, instance: instance}); } @@ -345,6 +357,7 @@ function radio_player_pause_instance(instance) { radio_player.loading = false; player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; if (radio_player.debug) {console.log('Pausing '+script+' Player Instance '+instance); radio_player_is_playing(instance);} + if (radio_player_retry.hasOwnProperty('cycle')) {clearInterval(radio_player_retry.cycle); radio_player_retry = {};} if ((script == 'amplitude') || (script == 'howler')) {player.pause();} else if (script == 'jplayer') {player.jPlayer('pause');} radio_player_custom_event('rp-pause', {player:player, script:script, instance: instance}); @@ -355,6 +368,7 @@ function radio_player_stop_instance(instance, fadeout) { radio_player.loading = false; player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; if (radio_player.debug) {console.log('Stopping '+script+' Player Instance '+instance); radio_player_is_playing(instance);} + if (radio_player_retry.hasOwnProperty('cycle')) {clearInterval(radio_player_retry.cycle); radio_player_retry = {};} if (fadeout) {radio_player_fade_volume(instance, fadeout, 0, 'stop');} else { if (script == 'amplitude') { @@ -527,8 +541,7 @@ function radio_player_pause_others(instance) { /* TODO: if the stream is the same, maybe swap-fade players ? */ if (radio_player.debug) {console.log('Pausing Player Instance '+i);} /* temporarily disabled as conflicting with multiple instances usage */ - /* radio_player_pause_instance(instance); */ - /* player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; + /* player = radio_player_data.players[i]; script = radio_player_data.scripts[i]; if ((script == 'amplitude') || (script == 'howler')) {player.pause();} else if (script == 'jplayer') {player.jPlayer('pause');} */ } @@ -703,6 +716,7 @@ function radio_player_save_user_state() { /* --- volume change audio test --- */ /* ref: https://stackoverflow.com/a/62094756/5240159 */ /* 2.5.6: fix radio.debug to radio_player.debug */ +/* 2.5.13: append testaudio to document body for more robust test */ function radio_player_volume_test() { isIOS = ['iPad Simulator','iPhone Simulator','iPod Simulator','iPad','iPhone','iPod'].includes(navigator.platform); if (radio_player.debug && isIOS) {console.log('iOS Mobile Device Detected. ');} @@ -710,7 +724,7 @@ function radio_player_volume_test() { if (radio_player.debug && isAppleDevice) {console.log('Apple Device Detected.');} isTouchScreen = navigator.maxTouchPoints >= 1; if (radio_player.debug && isTouchScreen) {console.log('Touch Screen Detected. ');} - iosAudioFailure = false; testaudio = new Audio(); + iosAudioFailure = false; testaudio = new Audio(); document.body.appendChild(testaudio); try {testaudio.volume = 0.5;} catch(e) {if (radio_player.debug) {console.log('Caught Volume Change Error.');} iosAudioFailure = true;} if (testaudio.volume === 1) {if (radio_player.debug) {console.log('Volume could not be changed.');} iosAudioFailure = true;} return isIOS || (isAppleDevice && (isTouchScreen || iosAudioFailure)); diff --git a/player/radio-player.php b/player/radio-player.php index ab30219..3e9c3c0 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -174,13 +174,13 @@ function radio_player_set_debug_mode() { return; } $debug = false; + if ( function_exists( 'radio_station_get_setting' ) ) { + $debug = radio_station_get_setting( 'player_debug' ); + } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) ) { $debug = true; } - if ( function_exists( 'radio_station_get_setting' ) ) { - $debug = radio_station_get_setting( 'player_debug' ); - } if ( function_exists( 'apply_filters' ) ) { $debug = apply_filters( 'radio_station_player_debug', $debug ); $debug = apply_filters( 'radio_player_debug', $debug ); @@ -370,6 +370,7 @@ function radio_player_output( $args = array(), $echo = false ) { $html['station'] .= '
        ' . "\n"; // --- station title --- + $title_display = ''; if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { $title_display .= esc_html( $args['title'] ); } @@ -1898,11 +1899,12 @@ function radio_player_get_player_settings( $echo = false ) { // --- set radio player settings --- // 2.5.7: disable swf fallback support + // 2.5.13: set swf_path to empty string to prevent jplayer error echo "player_settings = {"; echo "'ajaxurl': '" . esc_url( $admin_ajax ) . "', "; echo "'saveinterval':" . esc_js( $save_interval ) . ", "; // echo "'swf_path': '" . esc_url( $swf_path ) . "', "; - echo "'swf_path': false, "; + echo "'swf_path': '', "; echo "'script': '" . esc_js( $player_script ). "', "; echo "'title': '" . esc_js( $player_title ) . "', "; echo "'image': '" . esc_url( $player_image ) . "', "; @@ -1985,9 +1987,9 @@ function radio_player_get_player_settings( $echo = false ) { // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv // 2.5.7: disable Howler format list echo "formats = {"; - // echo "'howler': ['mp3','opus','ogg','oga','wav','aac','m4a','mp4','webm','weba','flac'], "; - echo "'jplayer': ['mp3','m4a','webm','oga','rtmpa','wav','flac'], "; echo "'amplitude': ['mp3','aac'], "; + echo "'jplayer': ['mp3','m4a','webm','oga','rtmpa','wav','flac'], "; + echo "'howler': ['mp3','opus','ogg','oga','wav','aac','m4a','mp4','webm','weba','flac'], "; // $js .= "'mediaelements': ['mp3','wma','wav'], "; echo "}" . "\n"; @@ -2063,7 +2065,7 @@ function radio_player_get_player_settings( $echo = false ) { } if ( $set_data ) { foreach ( $data as $key => $value ) { - // TODO: attempt auto-fix for secure https stream? + // TODO: attempt auto-fix for secure https stream ? /* if ( ( 'url' == $key ) || ( 'fallback' == $key ) ) { if ( is_ssl() && strstr( $value, 'http://' ) ) { $value = str_replace( 'http://', 'https://', $value ); diff --git a/readme.md b/readme.md index 79aebf0..5a20c60 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3 Tested up to: 6.8.1 -Stable tag: 2.5.13 +Stable tag: 2.5.14 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index b13f5e1..54f36fe 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 Tested up to: 6.8.1 -Stable tag: 2.5.13 +Stable tag: 2.5.14 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. @@ -404,6 +404,11 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.14 = +* Fixed: Player resume (play/pause/play) glitch for Amplitude +* Fixed: Keep active day when changing schedule weeks on Grid view +* Fixed: jPlayer old fallback SWF path to empty string + = 2.5.13 = * Updated: Freemius SDK (2.12.0) * Fixed: load text domain too early notice diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 95f7c33..79d2a8b 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -3,7 +3,7 @@ * Template for master schedule shortcode list style. */ -echo 'LIST ATTS: ' . print_r( $atts, true ) . ''; +// echo 'LIST ATTS: ' . print_r( $atts, true ) . ''; // --- get all the required info --- $hours = radio_station_get_hours(); @@ -340,7 +340,6 @@ } } } - echo '' . $show_hosts . ''; // 2.3.3.5: fix to incorrect context value (tabs) $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'list' ); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 5bc9fb7..2969903 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -345,7 +345,8 @@ // --- open list item --- $classlist = implode( ' ', $classes ); - $panels .= '
      • ' . "\n"; + $hour = date( 'H', strtotime( $shift['start'] ) ); + $panels .= '
      • ' . "\n"; // --- Show Image --- // (defaults to display on) From 58c63e5fb5db7bcbf1fd0749568b7102b7e2462b Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 9 Jul 2025 16:22:15 +1000 Subject: [PATCH 352/377] fix schedule tabs click --- includes/master-schedule.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 756530f..3de42d1 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -888,11 +888,12 @@ function radio_station_ajax_schedule_loader() { } elseif ( 'tabs' == $view ) { $init_js .= "if (typeof parent.radio_tabs_initialize == 'function') {" . "\n"; $init_js .= "parent.radio_tabs_init = false;" . "\n"; - $schedule_id = 'master-schedule-tabs'; - if ( $instance > 0 ) { - $schedule_id .= '-' . $instance; - } - $init_js .= "parent.radio_tabs_active_tab('" . esc_js( $day ) . "','" . esc_js( $schedule_id ) . "');" . "\n"; + // 2.5.14: removed duplicate function as already called via initialize + // $schedule_id = 'master-schedule-tabs'; + // if ( $instance > 0 ) { + // $schedule_id .= '-' . $instance; + // } + // $init_js .= "parent.radio_tabs_active_tab('" . esc_js( $day ) . "','" . esc_js( $schedule_id ) . "');" . "\n"; $init_js .= "parent.radio_tabs_initialize();" . "\n"; $init_js .= "}" . "\n"; } elseif ( 'list' == $view ) { @@ -1311,18 +1312,19 @@ function radio_tabs_show_highlight() { radio.offset_time = radio.current_time + radio.timezone.offset; if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + day = false; + active_day = document.getElementById('schedule-active-day').value; + if (active_day != '') {day = active_day;} jQuery(this).find('.master-schedule-tabs-day').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if ((start < radio.offset_time) && (end > radio.offset_time)) { jQuery(this).addClass('current-day'); - day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - active_day = document.getElementById('schedule-active-day').value; - if (active_day != '') {radio_tabs_active_tab(active_day,scheduleid);} - else {radio_tabs_active_tab(day,scheduleid);} + tabday = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); + if (!day) {day = tabday;} } else {jQuery(this).removeClass('current-day');} }); - radio_tabs_active_tab(false,scheduleid); /* fallback */ + radio_tabs_active_tab(day,scheduleid); var radio_tabs_split = false; jQuery(this).parent().find('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); From 795432d961e9d4e77a1aca8c85bd89e1c66a6dc0 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 9 Jul 2025 16:45:11 +1000 Subject: [PATCH 353/377] tab click once only event --- includes/master-schedule.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 3de42d1..0583d01 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -1246,9 +1246,11 @@ function radio_station_master_schedule_tabs_js() { // 2.5.13: change active day input value on tab change $js = "function radio_tabs_clicks() { if (radio.debug) {console.log('Binding Tabbed Schedule Tab Clicks');} - jQuery('.master-schedule-tabs-headings').bind('click', function (event) { + jQuery('.master-schedule-tabs-headings').on('click', function (event) { + event.preventDefault(); if (jQuery(event.target).hasClass('master-schedule-tabs-headings')) {day = jQuery(event.target).attr('data-href');} else {day = jQuery(event.target).closest('.master-schedule-tabs-headings').attr('data-href');} + if (radio.debug) {console.log('Switching Tab to Day: '+day);} document.getElementById('schedule-active-day').value = day; schedule = jQuery(event.target).closest('.master-schedule-tabs'); panels = schedule.parent().find('.master-schedule-tab-panels'); @@ -1256,6 +1258,7 @@ function radio_station_master_schedule_tabs_js() { schedule.find('.master-schedule-tabs-day-'+day).addClass('active-day-tab'); panels.find('.master-schedule-tabs-panel').removeClass('active-day-panel'); panels.find('.master-schedule-tabs-panel-'+day).addClass('active-day-panel'); + return false; }); }" . "\n"; @@ -1285,6 +1288,7 @@ function radio_tabs_initialize() { radio_tabs_responsive(false,false); radio_tabs_show_highlight(); if (typeof radio_tabs_start_hours != 'undefined') {radio_tabs_start_hours();} + radio_tabs_init = true; } /* Set Day Tab on Load */ @@ -1300,7 +1304,6 @@ function radio_tabs_active_tab(day,scheduleid) { jQuery('#'+scheduleid+' .master-schedule-tabs-day').first().addClass('active-day-tab'); jQuery('#'+scheduleid+' .master-schedule-tabs-panel').first().addClass('active-day-panel'); } - radio_tabs_init = true; } /* Current Show Highlighting */ From 8ac7a9b81bd2aae07f2e22d462c5b42d9daf7a87 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 10 Jul 2025 16:58:38 +1000 Subject: [PATCH 354/377] fix schedule override column date display --- CHANGELOG.md | 3 ++- includes/master-schedule.php | 11 ++++++----- includes/post-types-admin.php | 15 ++++++++++----- radio-station.php | 2 +- readme.txt | 3 ++- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5e8a5..08f0fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.14 = * Fixed: Player resume (play/pause/play) glitch for Amplitude * Fixed: Keep active day when changing schedule weeks on Grid view -* Fixed: jPlayer old fallback SWF path to empty string +* Fixed: Schedule Override list Override Date column display +* Fixed: jPlayer change old fallback SWF path to empty string = 2.5.13 = * Updated: Freemius SDK (2.12.0) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 0583d01..dc5c508 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -780,7 +780,7 @@ function radio_station_ajax_schedule_loader() { // 2.5.0: get schedule instance ID $instance = absint( $_REQUEST['instance'] ); - // 2.5.13 use the start day if set + // 2.5.13 use the active day if set $active_date = sanitize_text_field( $_REQUEST['active_date'] ); $active_day = sanitize_text_field( $_REQUEST['active_day'] ); $day = ( '' != $active_day ) ? $active_day : strtolower( date( 'l', strtotime( $active_date ) ) ); @@ -1245,7 +1245,8 @@ function radio_station_master_schedule_tabs_js() { // 2.5.0: use relative traversal from click target instead of IDs // 2.5.13: change active day input value on tab change $js = "function radio_tabs_clicks() { - if (radio.debug) {console.log('Binding Tabbed Schedule Tab Clicks');} + if (radio_tabs_init) {return;} + if (radio.debug) {console.log('Adding Tabbed Schedule Tab Clicks');} jQuery('.master-schedule-tabs-headings').on('click', function (event) { event.preventDefault(); if (jQuery(event.target).hasClass('master-schedule-tabs-headings')) {day = jQuery(event.target).attr('data-href');} @@ -1288,7 +1289,6 @@ function radio_tabs_initialize() { radio_tabs_responsive(false,false); radio_tabs_show_highlight(); if (typeof radio_tabs_start_hours != 'undefined') {radio_tabs_start_hours();} - radio_tabs_init = true; } /* Set Day Tab on Load */ @@ -1304,6 +1304,7 @@ function radio_tabs_active_tab(day,scheduleid) { jQuery('#'+scheduleid+' .master-schedule-tabs-day').first().addClass('active-day-tab'); jQuery('#'+scheduleid+' .master-schedule-tabs-panel').first().addClass('active-day-panel'); } + radio_tabs_init = true; } /* Current Show Highlighting */ @@ -1324,10 +1325,10 @@ function radio_tabs_show_highlight() { if ((start < radio.offset_time) && (end > radio.offset_time)) { jQuery(this).addClass('current-day'); tabday = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - if (!day) {day = tabday;} + radio_tabs_active_tab(tabday,scheduleid); } else {jQuery(this).removeClass('current-day');} }); - radio_tabs_active_tab(day,scheduleid); + radio_tabs_active_tab(day,scheduleid); /* fallback */ var radio_tabs_split = false; jQuery(this).parent().find('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 14ee491..678bc25 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -4825,12 +4825,17 @@ function radio_station_override_column_data( $column, $post_id ) { // 2.3.2: no need to apply timezone conversions here // 2.5.6: use radio_station_get_time instead of date - $datetime = strtotime( $override['date'] ); - $month = radio_station_get_time( 'F', $datetime ); - $month = radio_station_translate_month( $month, true ); - $weekday = radio_station_get_time( 'l', $datetime ); + // 2.5.14: fix to convert full override date and time not just date + $start = $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; + $start = radio_station_convert_shift_time( $start ); + $timestamp = radio_station_to_time( $override['date'] . ' ' . $start ); + $date = radio_station_get_time( 'j', $timestamp ); + $weekday = radio_station_get_time( 'l', $timestamp ); $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( radio_station_get_time( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( radio_station_get_time( 'Y', $datetime ) ) . "\n"; + $month = radio_station_get_time( 'F', $timestamp ); + $month = radio_station_translate_month( $month, true ); + $year = radio_station_get_time( 'Y', $timestamp ); + echo esc_html( $weekday ) . ' ' . esc_html( $date ) . ' ' . esc_html( $month ) . ' ' . esc_html( $year ) . "\n"; echo '
        ' . "\n"; // 2.3.3.9: merge override times into this columns diff --git a/radio-station.php b/radio-station.php index 7eac1f3..4069e1c 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.13 +Version: 2.5.13.1 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 54f36fe..d1b520b 100644 --- a/readme.txt +++ b/readme.txt @@ -407,7 +407,8 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.14 = * Fixed: Player resume (play/pause/play) glitch for Amplitude * Fixed: Keep active day when changing schedule weeks on Grid view -* Fixed: jPlayer old fallback SWF path to empty string +* Fixed: Schedule Override list Override Date column display +* Fixed: jPlayer change old fallback SWF path to empty string = 2.5.13 = * Updated: Freemius SDK (2.12.0) From 90a407251d55776d443c06d613705de5b90102c2 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 17 Jul 2025 21:35:04 +1000 Subject: [PATCH 355/377] 2.5.14 updates --- includes/master-schedule.php | 17 +++++++++++++---- radio-station.php | 2 +- readme.md | 2 +- readme.txt | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index dc5c508..e726417 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -897,8 +897,10 @@ function radio_station_ajax_schedule_loader() { $init_js .= "parent.radio_tabs_initialize();" . "\n"; $init_js .= "}" . "\n"; } elseif ( 'list' == $view ) { - $init_js .= "if (typeof parent.radio_list_highlight == 'function') {" . "\n"; - $init_js .= "parent.radio_list_highlight();" . "\n"; + // 2.5.14: use radio list initialize for consistency + $init_js .= "if (typeof parent.radio_list_initialize == 'function') {" . "\n"; + $init_js .= "parent.radio_list_init= false;" . "\n"; + $init_js .= "parent.radio_list_initialize();" . "\n"; $init_js .= "}" . "\n"; } @@ -1505,12 +1507,19 @@ function radio_station_master_schedule_list_js() { // 2.3.3.9: check for required elements before executing functions // 2.3.3.9: fix to check before and after current time not show $js = "/* Initialize List */ + var radio_list_init = false; jQuery(document).ready(function() { - radio_list_highlight(); + radio_list_initialize(); var radio_list_highlighting = setInterval(radio_list_highlight, 60000); - if (typeof radio_list_start_hours != 'undefined') {radio_list_start_hours();} }); + /* Initialize List */ + function radio_list_initialize() { + radio_list_init = true; + radio_list_highlight(); + if (typeof radio_list_start_hours != 'undefined') {radio_list_start_hours();} + } + /* Current Show Highlighting */ function radio_list_highlight() { if (!jQuery('.master-list-day').length) {return;} diff --git a/radio-station.php b/radio-station.php index 4069e1c..64a5492 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.13.1 +Version: 2.5.14 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index 5a20c60..2977b7f 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3 -Tested up to: 6.8.1 +Tested up to: 6.8.2 Stable tag: 2.5.14 diff --git a/readme.txt b/readme.txt index d1b520b..18074b3 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Tags: radio station, radio shows, radio station schedule, radio broadcasting, st License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 -Tested up to: 6.8.1 +Tested up to: 6.8.2 Stable tag: 2.5.14 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. From 4770a9e1c38cc897f1538f4666dd75d4fdcefa17 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 2 Aug 2025 10:12:35 +1000 Subject: [PATCH 356/377] 2.5.15 updates --- options.php | 74 +++++++++++++++++++++++++++++-- player/css/radio-player.css | 9 ++++ player/css/radio-player.min.css | 2 +- player/js/radio-player.js | 32 +++++++------ player/radio-player.php | 38 +++++++++------- templates/single-show-content.php | 52 +++++++++++++++------- 6 files changed, 157 insertions(+), 50 deletions(-) diff --git a/options.php b/options.php index 1992a65..fc2a488 100644 --- a/options.php +++ b/options.php @@ -353,10 +353,11 @@ ), // --- [Player] Player Buttons --- + // 2.5.15: change default to circular 'player_buttons' => array( 'type' => 'select', 'label' => __( 'Default Player Buttons', 'radio-station' ), - 'default' => 'rounded', + 'default' => 'circular', 'options' => array( 'circular' => __( 'Circular Buttons', 'radio-station' ), 'rounded' => __( 'Rounded Buttons', 'radio-station' ), @@ -471,11 +472,12 @@ ), // --- [Pro/Player] Player Autoresume --- + // 2.5.15: change autoresume default to off so activated manually 'player_autoresume' => array( 'type' => 'checkbox', 'label' => __( 'Autoresume Playback', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', + 'default' => '', + 'value' => 'on', 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', @@ -524,6 +526,7 @@ ), // --- [Pro/Player] Player Bar Height --- + // 2.5.15: add px suffix 'player_bar_height' => array( 'type' => 'number', 'min' => 40, @@ -531,6 +534,7 @@ 'step' => 1, 'label' => __( 'Player Bar Height', 'radio-station' ), 'default' => 80, + 'suffix' => 'px', 'tab' => 'player', 'section' => 'bar', 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), @@ -831,6 +835,37 @@ 'section' => 'show', ), + // ---- Show Player --- + // 2.5.15: added show player selection + 'show_player' => array( + 'type' => 'select', + 'label' => __( 'Latest Show Player', 'radio-station' ), + 'options' => array( + 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), + 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + ), + 'default' => 'media_elements', + 'helper' => __( 'Which player to use on the Show pages for the latest show recording.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Player Theme --- + // 2.5.15: added show player theme selection + 'show_player_theme' => array( + 'type' => 'select', + 'label' => __( 'Show Player Theme', 'radio-station' ), + 'default' => '', + 'options' => array( + '' => __( 'Player Default', 'radio-station' ), + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Show Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + // --- Show Header Image --- // 2.3.2: added plural to option label 'show_header_image' => array( @@ -983,6 +1018,39 @@ 'pro' => true, ), + // ---- [Pro] Episode Player --- + // 2.5.15: added episode player selection + 'episode_player' => array( + 'type' => 'select', + 'label' => __( 'Episode Player', 'radio-station' ), + 'options' => array( + 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), + 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + ), + 'default' => 'radio_player', + 'helper' => __( 'Which player to use on the Episode pages for the Episode recording.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // --- [Pro] Episode Player Theme --- + // 2.5.15: added episode player theme selection + 'episode_player_theme' => array( + 'type' => 'select', + 'label' => __( 'Episode Player Theme', 'radio-station' ), + 'default' => '', + 'options' => array( + '' => __( 'Player Default', 'radio-station' ), + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Episode Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + // --- [Pro] Use Latest Episode --- 'episode_use_latest' => array( 'type' => 'checkbox', diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 2862120..3cf4679 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -38,7 +38,11 @@ .rp-interface { position: relative; background-color: transparent; +} + +.rp-audio-stream .rp-interface { width: 35%; + display: inline-block; } .rp-station-info, .rp-interface, .rp-volume-controls, .rp-controls-holder , .rp-script-switcher, .rp-show-info { @@ -52,7 +56,12 @@ } .rp-station-info, .rp-show-info { + display: none; +} + +.rp-audio-stream .rp-station-info, .rp-audio-stream .rp-show-info { width: 32%; + display: inline-block; } .rp-station-text-alt, .rp-show-text-alt { diff --git a/player/css/radio-player.min.css b/player/css/radio-player.min.css index 98fa0b0..e47eec5 100644 --- a/player/css/radio-player.min.css +++ b/player/css/radio-player.min.css @@ -1 +1 @@ -.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent;width:35%}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{width:32%}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file +.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent}.rp-audio-stream .rp-interface{width:35%;display:inline-block}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{display:none}.rp-audio-stream .rp-station-info,.rp-audio-stream .rp-show-info{width:32%;display:inline-block}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file diff --git a/player/js/radio-player.js b/player/js/radio-player.js index b7f4f36..277ea73 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -1,6 +1,6 @@ /* =============================== */ /* === Radio Player Javascript === */ -/* --------- Version 1.0.2 ------- */ +/* --------- Version 1.0.3 ------- */ /* =============================== */ /* === Debounce Delay Callback === */ @@ -40,7 +40,7 @@ radio_player_cookie = { return null; }, delete: function(name) { - setCookie('radio_player_' + name, '', -1); + radio_player_cookie.set(name,'',-1); } } @@ -88,15 +88,15 @@ function radio_player_check_format(data) { /* check formats against available scripts */ if (!script) { - if ((format in radio_player.formats.amplitude) && ('amplitude' in scripts)) {script = 'amplitude';} - else if ((format in radio_player.formats.jplayer) && ('jplayer' in scripts)) {script = 'jplayer';} - else if ((format in radio_player.formats.howler) && ('howler' in scripts)) {script = 'howler';} - /* else if ((format in radio_player.formats.mediaelements) && ('mediaelements' in scripts)) {script = 'mediaelements';} */ + if (('amplitude' in scripts) && (format in radio_player.formats.amplitude)) {script = 'amplitude';} + else if (('jplayer' in scripts) && (format in radio_player.formats.jplayer)) {script = 'jplayer';} + else if (('howler' in scripts) && (format in radio_player.formats.howler)) {script = 'howler';} + /* else if (('mediaelements' in scripts) && (format in radio_player.formats.mediaelements)) {script = 'mediaelements';} */ if (!script) { - if ((fformat in radio_player.formats.amplitude) && ('amplitude' in scripts)) {script = 'amplitude';} - else if ((fformat in radio_player.formats.jplayer) && ('jplayer' in scripts)) {script = 'jplayer';} - else if ((fformat in radio_player.formats.howler) && ('howler' in scripts)) {script = 'howler';} - /* else if ((fformat in radio_player.formats.mediaelements) && ('mediaelements' in scripts)) {script = 'mediaelements';} */ + if (('amplitude' in scripts) && (fformat in radio_player.formats.amplitude)) {script = 'amplitude';} + else if (('jplayer' in scripts) && (fformat in radio_player.formats.jplayer)) {script = 'jplayer';} + else if (('howler' in scripts) && (fformat in radio_player.formats.howler)) {script = 'howler';} + /* else if (('mediaelements' in scripts) && (fformat in radio_player.formats.mediaelements)) {script = 'mediaelements';} */ if (script) {a = url; b = format; url = fallback; format = fformat; fallback = a; fformat = b;} } if (!script) { @@ -139,11 +139,13 @@ function radio_player_load_stream(script, instance, data, start) { /* set channel ID for instance */ if (!instance) {instance = radio_player_default_instance();} - radio_player_data.types[instance] = 'stream'; radio_player_data.channels[instance] = data; + radio_player_data.types[instance] = 'stream'; + radio_player_data.channels[instance] = data; if (radio_player.debug) {console.log('Set Stream Data '+data+' on Instance '+instance);} /* load the audio stream */ data = radio_player_check_format(data); script = data.script; + /* console.log('Script: '+script+' - Instance: '+instance+' URL: '+data.url+' Format: '+data.format+' Start:'+start); */ player = radio_player_load_audio(script, instance, data, start); if (player && start) {radio_player_play_on_load(player, script, instance);} } @@ -276,7 +278,7 @@ function radio_player_check_script(script) { /* --- player failed fallback to another script --- */ function radio_player_player_fallback(instance, script) { - if (typeof radio_player.delayer_player != 'undefined') {clearInterval(radio_player.delayed_player);} + if (typeof radio_player.delayed_player != 'undefined') {clearInterval(radio_player.delayed_player);} if (typeof radio_player_data.failed[instance] != 'undefined') {j = radio_player_data.failed[instance].length;} else {radio_player_data.failed[instance] = new Array(); j = 0;} if (!(script in radio_player_data.failed[instance])) {radio_player_data.failed[instance][j] = script;} @@ -300,6 +302,7 @@ function radio_player_player_fallback(instance, script) { if (radio_player.debug) {console.log('Exhausted All Player Script Type Attempts');} radio_player_data.failed = new Array(); /* reset */ /* maybe swap to fallback stream data to retry */ + data = radio_player_data.data[instance]; if (data.fallback != '') { if (radio_player.debug) {console.log('Switching to Fallback Stream');} tmpa = data.url; data.url = data.fallback; data.fallback = tmpa; @@ -336,6 +339,7 @@ function radio_player_switch_script(instance, script) { // 2.5.13: add retry cycle for missed var radio_player_retry = {} function radio_player_play_instance(instance) { + if (typeof radio_player_autoresume == 'object') {radio_player_autoresume.cancelled = true;} radio_player.loading = true; player = radio_player_data.players[instance]; script = radio_player_data.scripts[instance]; console.log(player); console.log(script); @@ -670,7 +674,7 @@ function radio_player_set_state(key, value) { /* --- store player instance data */ function radio_player_set_data_state(script, instance, data, start) { - url = data.url; format = data.format; fallback = data.fallback; fformat = data.fformat; /* 2.5.6: fix to fallback format */ + url = data.url; format = data.format; fallback = data.fallback; fformat = data.fformat; if (typeof radio_player_data.data[instance] != 'undefined') { cdata = radio_player_data.data[instance]; if ( (cdata.script != script) || (cdata.url != url) || (cdata.format != format) || (cdata.fallback != fallback) || (cdata.fformat != fformat) || (cdata.start != start) ) { @@ -893,7 +897,7 @@ function radio_player_amplitude(instance, url, format, fallback, fformat) { audio.addEventListener('error', function(e) { instance = radio_player_event_instance(e, 'Error', 'amplitude'); if (radio_player.debug) {console.log(e);} - radio_player_event_handler('error', {instance:instance, script:'amplitude'}); + /* radio_player_event_handler('error', {instance:instance, script:'amplitude'}); */ radio_player_player_fallback(instance, 'amplitude', 'Amplitude Error'); }, false); diff --git a/player/radio-player.php b/player/radio-player.php index 3e9c3c0..c177abf 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -319,6 +319,8 @@ function radio_player_output( $args = array(), $echo = false ) { $html['player_open'] .= '' . "\n"; } $classes[] = 'rp-audio-stream'; + } elseif ( 'file' == $args['media'] ) { + $classes[] = 'rp-media-file'; } // 2.5.0: added filter for radio container class @@ -375,7 +377,7 @@ function radio_player_output( $args = array(), $echo = false ) { $title_display .= esc_html( $args['title'] ); } $title_display = apply_filters( 'radio_player_station_display', $title_display, $args, $instance ); - $station_text_html .= '
        ' . wp_kses( $title_display, $allowed ) . '
        ' . "\n"; + $station_text_html = '
        ' . wp_kses( $title_display, $allowed ) . '
        ' . "\n"; // --- station timezone / location / frequency --- // 2.5.0: add filters for timezone / frequency / location display @@ -983,7 +985,8 @@ function radio_player_ajax() { $text_color = $atts['text']; unset( $atts['text_color'] ); } elseif ( function_exists( 'apply_filters' ) ) { - $text_color = apply_filters( 'radio_player_text_color', $text_color ); + // 2.5.14: add instance argument for consistency + $text_color = apply_filters( 'radio_player_text_color', $text_color, false ); } // 2.5.0: strip background color attribute (applied to window body) @@ -998,37 +1001,33 @@ function radio_player_ajax() { $background_color = $atts['background']; unset( $atts['background'] ); } elseif ( function_exists( 'apply_filters' ) ) { - // 2.5.0: fallaback to apply_filters - $background_color = apply_filters( 'radio_player_background_color', $background_color ); + // 2.5.0: fallback to apply_filters + // 2.5.14: add instance argument for consistency + $background_color = apply_filters( 'radio_player_background_color', $background_color, false ); } // --- maybe add text color --- // 2.5.0: added for matching with background color // 2.5.6: fix for undefined variable css // 2.5.10: moved up so that inline style can be in header + // 2.5.14: add body prefix to selector $css = ''; if ( '' != $text_color ) { if ( ( 'rgb' != substr( $text_color, 0, 3 ) ) && ( '#' != substr( $text_color, 0, 1 ) ) ) { $text_color = '#' . $text_color; } - $css .= '#player-contents {color: ' . esc_attr( $text_color ) . ';}' . "\n"; + $css .= 'body #player-contents {color: ' . esc_attr( $text_color ) . ';}' . "\n"; } // --- maybe add background color --- + // 2.5.14: add body prefix to selector if ( '' != $background_color ) { if ( ( 'rgb' != substr( $background_color, 0, 3 ) ) && ( '#' != substr( $background_color, 0, 1 ) ) ) { $background_color = '#' . $background_color; } - $css .= 'body {background: ' . esc_attr( $background_color ) . ';}' . "\n"; + $css .= 'body, body #player-contents {background-color: ' . esc_attr( $background_color ) . ';}' . "\n"; } - // --- output extra player styles --- - $css = apply_filters( 'radio_station_player_ajax_styles', $css, $atts ); - $css = apply_filters( 'radio_player_ajax_styles', $css, $atts ); - // 2.5.6: use wp_kses_post instead of wp_strip_all_tags - // 2.5.6: use radio_player_add_inline_style (with fallback) - radio_player_add_inline_style( $css ); - // 2.5.10: set document title with javascript if ( isset( $atts['title'] ) && $atts['title'] && ( '' != $atts['title'] ) ) { $js = "document.title = '" . esc_js( $atts['title'] ) . "';" . "\n"; @@ -1095,6 +1094,14 @@ function radio_player_ajax() { // --- output (hidden) footer for scripts --- echo '
        ' . "\n"; + // --- output extra player styles --- + // 2.5.14: move add inline style to before footer + $css = apply_filters( 'radio_station_player_ajax_styles', $css, $atts ); + $css = apply_filters( 'radio_player_ajax_styles', $css, $atts ); + // 2.5.6: use wp_kses_post instead of wp_strip_all_tags + // 2.5.6: use radio_player_add_inline_style (with fallback) + radio_player_add_inline_style( $css ); + // --- call wp_footer actions --- wp_footer(); @@ -1550,7 +1557,7 @@ function radio_player_enqueue_script( $script ) { // --- set specific script as enqueued --- $radio_player['enqeued_' . $script] = true; - if ( isset( $radio_player['enqueue_inline_scripts'] ) && $radio_player['enqueue_inline_scripts'] ) { + if ( isset( $radio_player['enqueued_inline_scripts'] ) && $radio_player['enqueued_inline_scripts'] ) { return; } @@ -1582,7 +1589,8 @@ function radio_player_enqueue_script( $script ) { } // --- set specific script as enqueued --- - $radio_player['enqeued_inline_scripts'] = true; + // 2.5.15: fix to mismatching enqueued flag + $radio_player['enqueued_inline_scripts'] = true; } diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 0c507f2..cbeb82e 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -268,19 +268,41 @@ $image_blocks['player'] .= '' . esc_html( $label ) . '
        '; } - // 2.5.8: run embed shortcode to embed external audio URLs - $wp_embed = $GLOBALS['wp_embed']; - $embed = '[embed]' . $show_file . '[/embed]'; - add_filter( 'embed_maybe_make_link', '__return_false' ); - $player_embed = $wp_embed->run_shortcode( $embed ); - remove_filter( 'embed_maybe_make_link', '__return_false' ); - - $embedded = false; - if ( $player_embed && !stristr( $player_embed, '[audio' ) ) { - $embedded = true; - } else { - $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; - $player_embed = do_shortcode( $shortcode ); + // 2.5.15: maybe use radio player instead of media elements + $show_player = radio_station_get_setting( 'show_player' ); + if ( 'radio_player' == $show_player ) { + $player_theme = radio_station_get_setting( 'show_player_theme' ); + if ( '' == $player_theme ) { + $player_theme = radio_station_get_setting( 'player_theme' ); + } + $player_buttons = radio_station_get_setting( 'player_buttons' ); + $atts = array( + 'media' => 'file', + 'url' => $show_file, + 'layout' => 'horizontal', + 'theme' => $player_theme, + 'buttons' => $player_buttons, + 'volumes' => array( 'slider', 'mute' ), + 'default' => false, // TODO: test true here + ); + $atts = apply_filters( 'radio_station_show_player_atts', $atts ); + $player_embed = radio_player_shortcode( $atts ); + } elseif ( 'media_elements' == $show_player ) { + // 2.5.8: run embed shortcode to embed external audio URLs + global $wp_embed; + $embed = '[embed]' . $show_file . '[/embed]'; + add_filter( 'embed_maybe_make_link', '__return_false' ); + $player_embed = $wp_embed->run_shortcode( $embed ); + remove_filter( 'embed_maybe_make_link', '__return_false' ); + + if ( $player_embed && !stristr( $player_embed, '[audio' ) ) { + // 2.5.8: full width and height fix for embeds + // 2.5.15: move style next to embedded player + $player_embed .= '' . "\n"; + } else { + $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; + $player_embed = do_shortcode( $shortcode ); + } } $image_blocks['player'] .= '
        ' . "\n"; @@ -302,10 +324,6 @@ $image_blocks['player'] .= '
        ' . "\n"; - // 2.5.8: full width and height fix for embeds - if ( $embedded ) { - $image_blocks['player'] .= '' . "\n"; - } } // 2.3.3.6: allow subblock order to be changed From a498470c4507f47d0b9ed8d38269e66fe267508a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 8 Aug 2025 10:38:44 +1000 Subject: [PATCH 357/377] block player control style loading fix --- CHANGELOG.md | 8 +++ blocks/player.js | 46 ++++++++++++-- includes/blocks.php | 2 +- options.php | 3 +- player/radio-player.php | 135 +++++++++++++++++++++++++++++++++++++--- radio-station.php | 2 +- readme.md | 2 +- readme.txt | 10 ++- 8 files changed, 190 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f0fa9..8f7f795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.16 = +* Fixed: Player Block control color style preview / loading + += 2.5.15 = +* Improved: AJAX player style output moved to footer +* Option: Use Stream Player for latest Show file playback +* Option: Use Stream Player for Episode file playback (Pro) + = 2.5.14 = * Fixed: Player resume (play/pause/play) glitch for Amplitude * Fixed: Keep active day when changing schedule weeks on Grid view diff --git a/blocks/player.js b/blocks/player.js index 3569d85..c38a3df 100644 --- a/blocks/player.js +++ b/blocks/player.js @@ -7,10 +7,9 @@ const rs_el = window.wp.element.createElement; const rs__ = window.wp.i18n.__; const { serverSideRender: ServerSideRender } = window.wp; - const { registerBlockType } = window.wp.blocks; - const { getBlockType } = window.wp.blocks; - const { InspectorControls } = window.wp.blockEditor; - const { Fragment } = window.wp.element; + const { registerBlockType, getBlockType } = window.wp.blocks; + const { InspectorControls, useBlockProps } = window.wp.blockEditor; + const { Fragment, useEffect, createRef, useRef } = window.wp.element; const { BaseControl, TextControl, SelectControl, RadioControl, RangeControl, ToggleControl, ColorPicker, Dropdown, Button, Panel, PanelBody, PanelRow } = window.wp.components; /* --- Register Block --- */ @@ -47,6 +46,15 @@ */ edit: (props) => { const atts = props.attributes; + let blockRef = createRef(null); + const blockProps = useBlockProps({ ref: blockRef }); + + /* load control colors on render */ + useEffect(() => { + /* console.log('Player block rendered.', atts, blockProps); */ + radio_player_control_colors(blockProps.id, atts); + }, [atts.text_color, atts.background_color, atts.playing_color, atts.button_color, atts.track_color, atts.thumb_color]); + return ( rs_el( Fragment, {}, rs_el( ServerSideRender, { block: 'radio-station/player', className: 'radio-player-block', attributes: atts } ), @@ -630,3 +638,33 @@ }); } })(); + +/* Load Player Controls Colors */ +function radio_player_control_colors(id, atts) { + container = jQuery('#'+id+' .radio-container'); /* console.log(container); */ + if (!container.length) { setTimeout(function() {radio_player_control_colors(id,atts);}, 1000); return; } + instance = container.attr('id').replace('radio_container_',''); + url = radio_player.settings.ajaxurl+'?action=player_control_styles&instance='+instance+'&text='+encodeURIComponent(atts.text_color)+'&background='+encodeURIComponent(atts.background_color)+'&playing='+encodeURIComponent(atts.playing_color)+'&buttons='+encodeURIComponent(atts.buttons_color)+'&track='+encodeURIComponent(atts.track_color)+'&thumb='+encodeURIComponent(atts.thumb_color); + jQuery.ajax({ + type: 'GET', + url: url, + data: {'action':'player_control_styles', 'instance':instance, 'text':atts.text_color, 'background':atts.background_color, 'playing':atts.playing_color, 'buttons':atts.buttons_color, 'track':atts.track_color, 'thumb':atts.thumb_color}, + processData: false, + beforeSend: function(request, settings) { + request._data = settings.data; + }, + success: function(data, success, request) { + if (data.success) { + console.log('Load Control Styles Success: '+data.message); + if (jQuery('#radio-player-control-styles-'+data.instance).length) {jQuery('#radio-player-control-styles-'+data.instance).remove();} + jQuery('body').append(''); + } else {console.log('Load Control Styles Failed: '+data.message); console.log(request);} + }, + fail: function(request, textStatus, errorThrown) { + console.log(request); console.log(textStatus); console.log(errorThrown); + } + }).catch(function(error) { + console.log(error); console.log(jQuery(this)); + }); +} + diff --git a/includes/blocks.php b/includes/blocks.php index 7d8a106..4ff3780 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -58,7 +58,7 @@ function radio_station_get_block_callbacks() { // 'timezone' => 'radio_station_timezone_shortcode', 'clock' => 'radio_station_clock_shortcode', // --- Stream Player --- - 'player' => 'radio_player_shortcode', + 'player' => 'radio_player_block_shortcode', // --- Master Schedule --- 'schedule' => 'radio_station_master_schedule', // --- Archive Lists --- diff --git a/options.php b/options.php index fc2a488..7dbd837 100644 --- a/options.php +++ b/options.php @@ -473,12 +473,13 @@ // --- [Pro/Player] Player Autoresume --- // 2.5.15: change autoresume default to off so activated manually + // 2.5.16: updated helper text 'player_autoresume' => array( 'type' => 'checkbox', 'label' => __( 'Autoresume Playback', 'radio-station' ), 'default' => '', 'value' => 'on', - 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), + 'helper' => __( 'On return to site or page reload, ask the user to resume stream playback if they were playing the stream previously, using a popup a modal dialogue box.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', 'pro' => true, diff --git a/player/radio-player.php b/player/radio-player.php index c177abf..6ad5325 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -13,7 +13,7 @@ // - Player Output // - Store Player Instance Args // - Player Shortcode -// - Player AJAX Display +// - AJAX: Player Display // - Add Inline Styles // - Print Footer Styles // - Sanitize Shortcode Values @@ -30,7 +30,7 @@ // - Dynamic Load Script via AJAX // - Get Player Settings // - User State Iframe -// - AJAX Update User State +// - AJAX: Update User State // - Load Amplitude Function // - Load JPlayer Function // - Load Howler Function @@ -38,6 +38,7 @@ // - Get Default Player Script // - Enqueue Player Styles // - Player Control Styles +// - AJAX: Control Styles // === Standalone Compatibility === // x Output Script Tag // x Output Style Tag @@ -622,6 +623,75 @@ function radio_player_instance_args( $args, $instance ) { function radio_player_shortcode_output( $atts, $content, $tag ) { return radio_player_shortcode( $atts ); } +// 2.5.16: add shortcode block function with wrapper +function radio_player_block_shortcode( $atts, $content ) { + + global $radio_player; + + // --- get block output with wrapper --- + $block = '
        '+data.css+''); + } else {console.log('Load Control Styles Failed: '+data.message); console.log(request);} + }, + fail: function(request, textStatus, errorThrown) { + console.log(request); console.log(textStatus); console.log(errorThrown); + } + }).catch(function(error) { + console.log(error); console.log(jQuery(this)); + }); + } + }); + });"; + radio_player_inline_script( $js ); + $radio_player['control_styles_script'] = true; + } + + return $block; +} // 2.5.10: added optional echo argument function radio_player_shortcode( $atts, $echo = false ) { @@ -941,9 +1011,9 @@ function radio_player_default_colors( $atts ) { return $atts; } -// ------------------- -// Player AJAX Display -// ------------------- +// -------------------- +// AJAX: Player Display +// -------------------- add_action( 'wp_ajax_radio_player', 'radio_player_ajax' ); add_action( 'wp_ajax_nopriv_radio_player', 'radio_player_ajax' ); function radio_player_ajax() { @@ -2124,9 +2194,9 @@ function radio_player_get_player_settings( $echo = false ) { // } // } -// ---------------------- -// AJAX Update User State -// ---------------------- +// ----------------------- +// AJAX: Update User State +// ----------------------- // note: only triggered for WordPress logged in users if ( function_exists( 'add_action' ) ) { add_action( 'wp_ajax_radio_player_state', 'radio_player_state' ); @@ -3013,7 +3083,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { // --------------------- // Player Control Styles // --------------------- -function radio_player_control_styles( $instance ) { +// 2.5.16: add optional attribute overrides argument +function radio_player_control_styles( $instance, $atts = false ) { global $radio_player; @@ -3035,6 +3106,14 @@ function radio_player_control_styles( $instance ) { $colors['thumb'] = radio_station_get_setting( 'player_thumb_color' ); $colors['track'] = radio_station_get_setting( 'player_range_color' ); } + + // 2.5.16: maybe apply override attributes + if ( $atts ) { + foreach ( $atts as $key => $value ) { + $colors[$key] = $value; + } + } + if ( function_exists( 'apply_filters' ) ) { $colors['text'] = apply_filters( 'radio_station_player_text_color', $colors['text'], $instance ); $colors['text'] = apply_filters( 'radio_player_text_color', $colors['text'], $instance ); @@ -3230,6 +3309,44 @@ function radio_player_control_styles( $instance ) { return $css; } +// -------------------- +// AJAX: Control Styles +// -------------------- +// 2.5.16: added separate control styles load for block editor +add_action( 'wp_ajax_player_control_styles', 'radio_player_control_styles_ajax' ); +add_action( 'wp_ajax_nopriv_player_control_styles', 'radio_player_control_styles_ajax' ); +function radio_player_control_styles_ajax() { + + // --- get provided parameters --- + $instance = absint( $_REQUEST['instance'] ); + $atts['text'] = sanitize_text_field( wp_unslash( $_REQUEST['text'] ) ); + $atts['background'] = sanitize_text_field( wp_unslash( $_REQUEST['background'] ) ); + $atts['playing'] = sanitize_text_field( wp_unslash( $_REQUEST['playing'] ) ); + $atts['buttons'] = sanitize_text_field( wp_unslash( $_REQUEST['buttons'] ) ); + $atts['thumb'] = sanitize_text_field( wp_unslash( $_REQUEST['thumb'] ) ); + $atts['track'] = sanitize_text_field( wp_unslash( $_REQUEST['track'] ) ); + + // --- generate control styles --- + $css = radio_player_control_styles( $instance, $atts ); + + // --- return result --- + if ( '' != $css ) { + $data = array( + 'success' => 1, + 'message' => 'Generated Control Colors for Player Instance ' . $instance, + 'instance' => $instance, + 'css' => $css + ); + } else { + $data = array( + 'success' => 0, + 'message' => 'Empty Styles for Player Instance ' . $instance, + 'instance' => $instance, + ); + } + wp_send_json( $data ); +} + // ------------------ // Debug Skin Loading // ------------------ diff --git a/radio-station.php b/radio-station.php index 64a5492..2f830ed 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.14 +Version: 2.5.15 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index 2977b7f..a4deb56 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3 Tested up to: 6.8.2 -Stable tag: 2.5.14 +Stable tag: 2.5.16 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 18074b3..aeabfa3 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 Tested up to: 6.8.2 -Stable tag: 2.5.14 +Stable tag: 2.5.16 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. @@ -404,6 +404,14 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.16 = +* Fixed: Player Block control color style preview / loading + += 2.5.15 = +* Improved: AJAX player style output moved to footer +* Option: Use Stream Player for latest Show file playback +* Option: Use Stream Player for Episode file playback (Pro) + = 2.5.14 = * Fixed: Player resume (play/pause/play) glitch for Amplitude * Fixed: Keep active day when changing schedule weeks on Grid view From 9f3243761c0f34f80768bc79cc13a554c2313cd9 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 10 Aug 2025 14:07:41 +1000 Subject: [PATCH 358/377] version bump fix --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index 2f830ed..a6b6a71 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.15 +Version: 2.5.16 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From 3a04e76893dc1fdd95d9e05d789f2b7bc91a6a5a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 10 Aug 2025 14:09:37 +1000 Subject: [PATCH 359/377] version bump fix --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index 2f830ed..a6b6a71 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.15 +Version: 2.5.16 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages From f71b9b8275d350c911f9f034570328cbd032d6cf Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 6 Dec 2025 15:02:40 +1000 Subject: [PATCH 360/377] 2.5.18 development updates --- CHANGELOG.md | 13 + css/rs-shortcodes.css | 17 +- css/rs-templates.css | 18 +- docs/FAQ.md | 17 +- includes/blocks.php | 6 +- includes/post-types-admin.php | 623 ++++-- includes/post-types.php | 27 +- includes/shortcodes.php | 198 +- includes/support-functions.php | 27 +- includes/user-roles.php | 281 ++- loader.php | 66 +- options.php | 3092 +++++++++++++++-------------- player/compat.php | 7 + player/radio-player.php | 114 +- radio-station-admin.php | 3 +- radio-station.php | 19 +- readme.md | 4 +- readme.txt | 17 +- templates/single-show-content.php | 107 +- 19 files changed, 2699 insertions(+), 1957 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7f795..154177d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). += 2.5.18 = +* Changed: display label of Overrides to Specials +* Changed: handle of radio-player assets to stream-player +* Improved: Related Show and Linked Show select Show ordering +* Improved: handle Special/Override meta in Archive shortcode +* Improved: edit Special/Override permissions for linked Shows +* Added: Station Frequency and Location options +* Added: hidden Channel inputs for Shifts and Overrides +* Fixed: Radio Player plugin conflicts + += 2.5.17 = +* Fixed: Current Show widget reloading when Shift display is off + = 2.5.16 = * Fixed: Player Block control color style preview / loading diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index d92f752..04cb08a 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -144,30 +144,35 @@ float: none; } +.show-archive-item-description, .override-archive-item-description { + overflow-wrap: break-word; word-break: normal; +} + /* Show Subarchive Shortcodes */ /* -------------------------- */ -.show-post, .show-playlist, .show-host, .show-producer, .show-episode { +.show-post, .show-override, .show-playlist, .show-host, .show-producer, .show-episode { padding-top: 10px; padding-bottom: 10px; } -.show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail, -.show-post-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { +.show-post-thumbnail, .show-override-thumbnail, show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail, +.show-post-info, .show-override-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { display: table-cell; vertical-align: top; } -.show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail { +.show-post-thumbnail, .show-override-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail { padding-right: 30px; margin-top: 10px; min-width: 150px; } -.show-post-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { +.show-post-info, .show-override-info, .show-playlist-info, .show-host-info, .show-producer-info, .show-episode-info { max-width: 600px; } -.show-post-title, .show-playlist-title, .show-host-title, .show-producer-title, .show-episode-title { +.show-post-title, .show-override-title, .show-playlist-title, .show-host-title, .show-producer-title, .show-episode-title, +.show-post-meta, .show-override-meta, .show-playlist-meta, .show-host-meta, .show-producer-meta, .show-episode-meta { margin-bottom: 10px; } diff --git a/css/rs-templates.css b/css/rs-templates.css index 631d49a..0041de2 100644 --- a/css/rs-templates.css +++ b/css/rs-templates.css @@ -195,7 +195,21 @@ display: none; } -#show-content #show-more-overlay { +#show-content .show-description { + -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 6em), rgba(0,0,0,0.5) calc(100% - 3em), transparent 100%); + mask-image: linear-gradient(to bottom, black calc(100% - 6em), rgba(0,0,0,0.5) calc(100% - 3em), transparent 100%); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; +} + +#show-content .show-description.expanded { + -webkit-mask-image: none; + mask-image: none; +} + +/* #show-content #show-more-overlay { position: absolute; bottom: 0; width: 100%; @@ -209,7 +223,7 @@ #show-content .show-description.expanded #show-more-overlay { display: none; -} +} */ /* Show Tabs */ diff --git a/docs/FAQ.md b/docs/FAQ.md index 5ef2ece..e3663f0 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -131,9 +131,24 @@ As of April 1st, 2023, known languages include the following: * Spanish (es_ES) * Catalan (ca) +Note that for ease of translation the Free version contains any extra text strings from the PRO version. The auto-generated `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. + #### Can Radio Station be translated into my language? -You may translate the plugin into any other language supported by the WordPress translation engine. Please visit our [Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for all translations and for further instructions. Note that for ease of translation the Free version contains any extra text strings from the PRO version. The `radio-station.pot` file is located in the `/languages` directory of the plugin. If you do add a translation for your preferred language, please send or notify us of the completed translation to `info@netmix.com`. We'd love to include it. +You may translate the plugin into any other language supported by the WordPress translation engine (GlotPress.) Here are some instructions on how to do that: + +- Go to https://translate.wordpress.org/ and sign in with a WordPress.org account (or create one). +- In the search box, enter "Radio Station" plugin name or `radio-station` text domain, and click the project result to open its translation page. Here's the link: [Radio Station Translate Project Page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) +- On the project page, choose the language from the language list. +- Choose the latest stable branch version number (note this may change as the plugin is updated so always select the latest version as older translations will automatically be transferred to it.) +- Use the filter/search to find a string. +- Click a string to open the editor. +- In the "Translation" box enter the German text (keep placeholders like %s, %d, intact). +- If unsure, add a comment in the discussion box for other translators/reviewers. +- For contributors without commit rights: click "Suggest new translation". +- If a translation is uncertain, mark it as "Needs work" or leave a note; do not publish incomplete translations. +- Check the right‑hand panel for translation suggestions and previous translations to stay consistent. +- Reviews are handled by language moderators. If the customer has reviewer role, they can approve suggestions from others. ### Troubleshooting diff --git a/includes/blocks.php b/includes/blocks.php index 4ff3780..af1fede 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -382,12 +382,14 @@ function radio_station_block_editor_assets() { $style_path = RADIO_STATION_DIR . 'player/css/radio-player' . $suffix . '.css'; $style_url = plugins_url( '/player/css/radio-player' . $suffix . '.css', RADIO_STATION_FILE ); $version = filemtime( $style_path ); - wp_enqueue_style( 'radio-player', $style_url, array(), $version, 'all' ); + // 2.5.18: change radio-player style handle to stream-player + wp_enqueue_style( 'stream-player', $style_url, array(), $version, 'all' ); // --- enqueue player control styles inline --- $control_styles = radio_player_control_styles( false ); // 2.5.0: use radio_station_add_inline_style - radio_station_add_inline_style( 'radio-player', $control_styles ); + // 2.5.18: change radio-player style handle to stream-player + radio_station_add_inline_style( 'stream-player', $control_styles ); } } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 678bc25..9012b83 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1109,8 +1109,9 @@ function radio_station_shift_edit_script() { // --- add new shift --- // 2.3.2: separate function for onclick // 2.5.0: shorten value object + // 2.5.18: added empty channel value $js .= "function radio_shift_new() { - values = {day:'', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', encore:'', disabled:''}; + values = {channel:'', day:'', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', encore:'', disabled:''}; radio_shift_add(values); }" . "\n"; @@ -1129,6 +1130,7 @@ function radio_station_shift_edit_script() { $js .= "function radio_shift_duplicate(el) { shiftid = el.id.replace('shift-','').replace('-duplicate',''); values = {}; + values.channel = jQuery('#shift-'+shiftid+'-channel').val(); values.day = jQuery('#shift-'+shiftid+'-day').val(); values.start_hour = jQuery('#shift-'+shiftid+'-start-hour').val(); values.start_min = jQuery('#shift-'+shiftid+'-start-min').val(); @@ -1148,9 +1150,33 @@ function radio_station_shift_edit_script() { // 2.3.3: add input IDs so new shifts can be duplicated $js .= "function radio_shift_add(values) {" . "\n"; $js .= "var count = jQuery('#new-shifts').children().length + 1;" . "\n"; - $js .= "output = '
        ';" . "\n"; - $js .= "output += '
          ';" . "\n"; - $js .= "output += '
        • ';" . "\n"; + $js .= "output = '
          ';" . "\n"; + $js .= "output += '
            ';" . "\n"; + + // --- shift channel --- + // 2.5.18: add channel list item for multichannel support + $channels = apply_filters( 'radio_station_channel_options', array() ); + if ( count( $channels ) > 0 ) { + $js .= "output += '
          • ';" . "\n"; + $js .= "output += ': ';" . "\n"; + $js .= "output += ''" . "\n"; + $js .= "output += '
          • ';" . "\n"; + } else { + $js .= "output += '';" . "\n"; + } + + // --- shift day --- + $js .= "output += '
          • ';" . "\n"; $js .= "output += ': ';" . "\n"; $js .= "output += '' . "\n"; + foreach ( $channels as $channel ) { + $list .= '' . "\n"; + } + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '
          • ' . "\n"; + } else { + $list .= '' . "\n"; + } + // --- shift day selection --- - $list .= '
          • ' . "\n"; + $list .= '
          • ' . "\n"; $list .= ': ' . "\n"; $class = ( '' == $shift['day'] ) ? 'incomplete' : ''; $list .= '' . "\n"; echo '
          ' . "\n"; echo '
          ' . "\n"; @@ -3103,7 +3147,8 @@ function radio_station_override_show_metabox() { // --- title --- echo '
    ' . "\n"; } - } } else { foreach ( $taboptions[$tab]['general'] as $key => $option ) { @@ -2509,6 +2507,9 @@ public function allowed_html( $option = false ) { 'value' => array(), 'type' => array(), 'placeholder' => array(), + // 1.3.6: add rows and cols + 'rows' => array(), + 'cols' => array(), ); // --- select --- @@ -2586,7 +2587,7 @@ public function settings_resources( $media = true, $color_picker = true ) { // ----------- // 1.0.9: added for automatic Settings table generation public function setting_row( $option ) { - + // --- prepare setting keys --- $args = $this->args; $namespace = $this->namespace; @@ -2637,6 +2638,7 @@ public function setting_row( $option ) { $type = 'multiselect'; $option['options'] = 'POSTIDS'; } + // TODO: password and multitoggle // --- prepare row output --- $row = '' . "\n"; @@ -2926,11 +2928,8 @@ public function setting_row( $option ) { if ( 'text' != $type ) { $class .= ' setting-' . $type; } - if ( isset( $option['placeholder'] ) ) { - $placeholder = $option['placeholder']; - } else { - $placeholder = ''; - } + $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : ''; + // 1.1.7: fix to attribute quoting output $row .= '' . "\n"; if ( isset( $option['suffix'] ) ) { @@ -2940,43 +2939,22 @@ public function setting_row( $option ) { } elseif ( 'textarea' == $type ) { // --- textarea input --- - if ( isset( $option['rows'] ) ) { - $rows = $option['rows']; - } else { - $rows = '6'; - } - if ( isset( $option['placeholder'] ) ) { - $placeholder = $option['placeholder']; - } else { - $placeholder = ''; - } + $rows = isset( $option['rows'] ) ? $option['rows'] : '6'; + $cols = isset( $option['cols'] ) ? $option['cols'] : '80'; + $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : ''; + // 1.2.4: added missing esc_textarea on value - $row .= '' . "\n"; + // 1.3.6: fixed rows attribute, added cols attribute + $row .= '' . "\n"; } elseif ( ( 'numeric' == $type ) || ( 'number' == $type ) ) { // --- numeric text input --- // note: step key is only used for controls, not for validation - if ( isset( $option['placeholder'] ) ) { - $placeholder = $option['placeholder']; - } else { - $placeholder = ''; - } - if ( isset( $option['min'] ) ) { - $min = $option['min']; - } else { - $min = 'false'; - } - if ( isset( $option['max'] ) ) { - $max = $option['max']; - } else { - $max = 'false'; - } - if ( isset( $option['step'] ) ) { - $step = $option['step']; - } else { - $step = 1; - } + $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : ''; + $min = isset( $option['min'] ) ? $option['min'] : 'false'; + $max = isset( $option['max'] ) ? $option['max'] : 'false'; + $step = isset( $option['step'] ) ? $option['step'] : 1; // 1.1.7: remove esc_js from onclick attributes // $onclickdown = "plugin_panel_number_step('down', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . "\n"; @@ -3593,11 +3571,13 @@ function radio_station_settings_table() { // Settings Row // ------------ // 1.0.9: added for standalone setting row output - if ( !function_exists( 'radio_station_settings_row' ) ) { - function radio_station_settings_row( $option, $setting ) { + // 1.3.6: fix to incorrect plural function name _settings_row + if ( !function_exists( 'radio_station_setting_row' ) ) { + function radio_station_setting_row( $option, $setting ) { $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; - $instance->settings_row( $option, $setting ); + // 1.3.6: fix to incorrect plural function name settings_row + $instance->setting_row( $option, $setting ); } } diff --git a/options.php b/options.php index 7dbd837..abde7c2 100644 --- a/options.php +++ b/options.php @@ -4,1533 +4,1571 @@ // === Radio Station Options === // ============================= -if ( !defined( 'ABSPATH' ) ) exit; - // ------------------ // Set Plugin Options // ------------------ -$options = array( - - // === Broadcast === - - // --- [Player] Streaming URL --- - 'streaming_url' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Streaming URL', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- [Player] Stream Format --- - 'streaming_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Streaming Format', 'radio-station' ), - 'default' => 'aac', - 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- [Player] Fallback Stream URL --- - 'fallback_url' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Fallback Stream URL', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- [Player] Fallback Stream Format --- - 'fallback_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Fallback Format', 'radio-station' ), - 'default' => 'ogg', - 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- [Player] Stream GeoBlocking --- - 'stream_geo_blocking' => array( - 'label' => __( 'GeoIP Stream Blocking', 'radio-station' ), - 'type' => 'select', - 'options' => array( - '' => __( 'No GeoIP Blocking', 'radio-station' ), - 'live365' => __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ), - // 'blacklist' => __( 'Custom Country Blacklist', 'radio-station' ), - // 'whitelist' => __( 'Custom Country Whitelist', 'radio-station' ), - ), - 'default' => '', - 'helper' => __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - 'pro' => true, - ), - - // --- Main Radio Language --- - 'radio_language' => array( - 'type' => 'select', - 'options' => $languages, - 'label' => __( 'Main Broadcast Language', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select the main language used on your Radio Station.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- Ping Netmix Directory --- - // note: disabled by default for WordPress.org repository compliance - // 2.5.0: moved from feeds to broadcast section - 'ping_netmix_directory' => array( - 'type' => 'checkbox', - 'label' => __( 'Ping Netmix Directory', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // === Station === - - // --- [Player] Station Title --- - // 2.3.3.8: added station title field - 'station_title' => array( - 'type' => 'text', - 'label' => __( 'Station Title', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- [Player] Station Image --- - // 2.3.3.8: added station logo image field - 'station_image' => array( - 'type' => 'image', - 'label' => __( 'Station Logo Image', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Timezone Location --- - 'timezone_location' => array( - 'type' => 'select', - 'options' => $timezones, - 'label' => __( 'Location Timezone', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Clock Time Format --- - 'clock_time_format' => array( - 'type' => 'select', - 'options' => array( - '12' => __( '12 Hour Format', 'radio-station' ), - '24' => __( '24 Hour Format', 'radio-station' ), - ), - 'label' => __( 'Clock Time Format', 'radio-station' ), - 'default' => '12', - 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Phone Number --- - // 2.3.3.6: added station phone number option - 'station_phone' => array( - 'type' => 'text', - 'options' => 'PHONE', - 'label' => __( 'Station Phone', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Phone for Shows --- - // 2.3.3.6: added default to station phone option - 'shows_phone' => array( - 'type' => 'checkbox', - 'default' => '', - 'value' => 'yes', - 'label' => __( 'Show Phone Display', 'radio-station' ), - 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Email Address --- - // 2.3.3.8: added station email address option - 'station_email' => array( - 'type' => 'email', - 'default' => '', - 'label' => __( 'Station Email', 'radio-station' ), - 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Email for Shows --- - // 2.3.3.8: added default to email address option - 'shows_email' => array( - 'type' => 'checkbox', - 'default' => '', - 'value' => 'yes', - 'label' => __( 'Show Email Display', 'radio-station' ), - 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // === Feeds === - - // --- REST Data Routes --- - 'enable_data_routes' => array( - 'type' => 'checkbox', - 'label' => __( 'Enable Data Routes', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // --- Data Feed Links --- - 'enable_data_feeds' => array( - 'type' => 'checkbox', - 'label' => __( 'Enable Data Feeds', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // === Performance === - // 2.4.0.6: separated performance section - - // --- Shift Conflict Checker --- - // 2.5.6: added setting for conflict checker - 'conflict_checker' => array( - 'type' => 'checkbox', - 'label' => __( 'Shift Conflict Checker', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Check for Shift conflicts when saving Show shift times.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'performance', - ), - - // --- Disable Transients --- - // 2.4.0.6: change label from Clear Transients - 'clear_transients' => array( - 'type' => 'checkbox', - 'label' => __( 'Disable Transients', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'performance', - ), - - // --- Transient Caching --- - 'transient_caching' => array( - 'type' => 'checkbox', - 'label' => __( 'Show Transients', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'performance', - 'pro' => true, - ), - - // --- Show Shift Feeds --- - /* 'show_shift_feeds' => array( - 'type' => 'checkbox', - 'label' => __( 'Show Shift Feeds', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - 'pro' => true, - ), */ - - // === Basic Stream Player === - - // --- Defaults Note --- - // 2.5.0: added note about defaults being overrideable in widgets - 'player_defaults_note' => array( - 'type' => 'note', - 'label' => __( 'Player Defaults Note', 'radio-station' ), - 'helper' => __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Title --- - 'player_title' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Station Title', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Image --- - 'player_image' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Station Image', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Script --- - // 2.4.0.3: change script default to jplayer - // 2.5.7: disable howler script (browser incompatibilities) - 'player_script' => array( - 'type' => 'select', - 'label' => __( 'Player Script', 'radio-station' ), - 'default' => 'jplayer', - 'options' => array( - 'amplitude' => __( 'Amplitude', 'radio-station' ), - 'jplayer' => __( 'jPlayer', 'radio-station' ), - // 'howler' => __( 'Howler', 'radio-station' ), - ), - 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Fallback Scripts --- - // 2.4.0.3: added fallback enable/disable switching - // 2.4.0.3: fixed option label from Player Script - // 2.5.7: disable howler script (browser incompatibilities) - 'player_fallbacks' => array( - 'type' => 'multicheck', - 'label' => __( 'Fallback Scripts', 'radio-station' ), - 'default' => array( 'amplitude', 'howler', 'jplayer' ), - 'options' => array( - 'amplitude' => __( 'Amplitude', 'radio-station' ), - 'jplayer' => __( 'jPlayer', 'radio-station' ), - // 'howler' => __( 'Howler', 'radio-station' ), - ), - 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Theme --- - 'player_theme' => array( - 'type' => 'select', - 'label' => __( 'Default Player Theme', 'radio-station' ), - 'default' => 'light', - 'options' => array( - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), - ), - 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Buttons --- - // 2.5.15: change default to circular - 'player_buttons' => array( - 'type' => 'select', - 'label' => __( 'Default Player Buttons', 'radio-station' ), - 'default' => 'circular', - 'options' => array( - 'circular' => __( 'Circular Buttons', 'radio-station' ), - 'rounded' => __( 'Rounded Buttons', 'radio-station' ), - 'square' => __( 'Square Buttons', 'radio-station' ), - ), - 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Volume Controls --- - // 2.4.0.3: added enable/disable volume controls option - // 2.5.0: default to volume slider only - 'player_volumes' => array( - 'type' => 'multicheck', - 'label' => __( 'Volume Controls', 'radio-station' ), - 'default' => array( 'slider' ), - 'options' => array( - 'slider' => __( 'Volume Slider', 'radio-station' ), - 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), - 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), - 'max' => __( 'Maximize Volume', 'radio-station' ), - ), - 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- [Player] Player Debug Mode --- - 'player_debug' => array( - 'type' => 'checkbox', - 'label' => __( 'Player Debug Mode', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // === Player Colours === - - // --- [Pro/Player] Playing Highlight Color --- - 'player_playing_color' => array( - 'type' => 'color', - 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), - 'default' => '#70E070', - 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Control Icons Highlight Color --- - 'player_buttons_color' => array( - 'type' => 'color', - 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), - 'default' => '#00A0E0', - 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Volume Knob Color --- - 'player_thumb_color' => array( - 'type' => 'color', - 'label' => __( 'Volume Knob Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro/Player] Volume Track Color --- - 'player_range_color' => array( - 'type' => 'coloralpha', - 'label' => __( 'Volume Track Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // === Advanced Stream Player === - - // --- [Player] Player Volume --- - 'player_volume' => array( - 'type' => 'number', - 'label' => __( 'Player Start Volume', 'radio-station' ), - 'default' => 77, - 'min' => 0, - 'step' => 1, - 'max' => 100, - 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- [Player] Single Player --- - 'player_single' => array( - 'type' => 'checkbox', - 'label' => __( 'Single Player at Once', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Stop any existing Player instances on the page or in other windows or tabs when a Player is started.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- [Pro/Player] Player Autoresume --- - // 2.5.15: change autoresume default to off so activated manually - // 2.5.16: updated helper text - 'player_autoresume' => array( - 'type' => 'checkbox', - 'label' => __( 'Autoresume Playback', 'radio-station' ), - 'default' => '', - 'value' => 'on', - 'helper' => __( 'On return to site or page reload, ask the user to resume stream playback if they were playing the stream previously, using a popup a modal dialogue box.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => true, - ), - - // --- [Pro/Player] Popup Player Button --- - // 2.5.0: enabled popup player button - 'player_popup' => array( - 'type' => 'checkbox', - 'label' => __( 'Popup Player Button', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Add button to open Popup Player in separate window.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => true, - ), - - // === Sitewide Player Bar === - - // --- Player Bar Note --- - 'player_bar_note' => array( - 'type' => 'note', - 'label' => __( 'Bar Defaults Note', 'radio-station' ), - 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) - . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - ), - - // --- [Pro/Player] Sitewide Player Bar --- - 'player_bar' => array( - 'type' => 'select', - 'label' => __( 'Sitewide Player Bar', 'radio-station' ), - 'default' => 'off', - 'options' => array( - 'off' => __( 'No Player Bar', 'radio-station' ), - 'top' => __( 'Top Player Bar', 'radio-station' ), - 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), - ), - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Player Bar Height --- - // 2.5.15: add px suffix - 'player_bar_height' => array( - 'type' => 'number', - 'min' => 40, - 'max' => 400, - 'step' => 1, - 'label' => __( 'Player Bar Height', 'radio-station' ), - 'default' => 80, - 'suffix' => 'px', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Fade In Player Bar --- - 'player_bar_fadein' => array( - 'type' => 'number', - 'label' => __( 'Fade In Player Bar', 'radio-station' ), - 'default' => 2500, - 'min' => 0, - 'step' => 100, - 'max' => 10000, - 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Continuous Playback --- - // 2.4.0.1: fix for missing value field - 'player_bar_continuous' => array( - 'type' => 'checkbox', - 'label' => __( 'Continuous Playback', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ) - . ' ' . __( 'Click here for setup notes.', 'radio-station' ) . '', - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Player Page Fade --- - 'player_bar_pagefade' => array( - 'type' => 'number', - 'label' => __( 'Page Fade Time', 'radio-station' ), - 'default' => 2000, - 'min' => 0, - 'step' => 100, - 'max' => 10000, - 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Page Load Timeout --- - // 2.4.0.3: add page load timeout option - 'player_bar_timeout' => array( - 'type' => 'number', - 'label' => __( 'Page Load Timeout', 'teleporter' ), - 'default' => 7000, - 'min' => 0, - 'step' => 500, - 'max' => 20000, - 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Bar Player Text Color --- - 'player_bar_text' => array( - 'type' => 'color', - 'label' => __( 'Bar Player Text Color', 'radio-station' ), - 'default' => '#FFFFFF', - 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Bar Player Background Color --- - 'player_bar_background' => array( - 'type' => 'coloralpha', - 'label' => __( 'Bar Player Background Color', 'radio-station' ), - 'default' => 'rgba(0,0,0,255)', - 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro/Player] Display Current Show --- - // 2.4.0.3: added for current show display - 'player_bar_currentshow' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Current Show', 'radio-station' ), - 'value' => 'yes', - 'default' => 'yes', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Display Metadata --- - // 2.4.0.3: added for now playing metadata display - 'player_bar_nowplaying' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Now Playing', 'radio-station' ), - 'value' => 'yes', - 'default' => 'yes', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Track Animation --- - // 2.5.0: added track animation option - 'player_bar_track_animation' => array( - 'type' => 'select', - 'label' => __( 'Track Animation', 'radio-station' ), - 'default' => 'backandforth', - 'options' => array( - 'none' => __( 'No Animation', 'radio-station' ), - 'lefttoright' => __( 'Left to Right Ticker', 'radio-station' ), - 'righttoleft' => __( 'Right to Left Ticker', 'radio-station' ), - 'backandforth' => __( 'Back and Forth', 'radio-station' ), - ), - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'How to animate the currently playing track display.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Metadata URL --- - // 2.4.0.3: added for alternative stream metadata URL - 'player_bar_metadata' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Metadata URL', 'radio-station' ), - 'default' => '', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro/Player] Store Track Metadata --- - // 2.5.6: added option to store stream - 'player_store_metadata' => array( - 'type' => 'checkbox', - 'label' => __( 'Store Track Metadata?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ), - 'pro' => true, - ), - - // === Master Schedule Page === - - // --- Schedule Page --- - 'schedule_page' => array( - 'type' => 'select', - 'options' => 'PAGEID', - 'label' => __( 'Master Schedule Page', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Automatic Schedule Display --- - 'schedule_auto' => array( - 'type' => 'checkbox', - 'label' => __( 'Automatic Display', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]', - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Default Schedule View --- - 'schedule_view' => array( - 'type' => 'select', - 'label' => __( 'Schedule View Default', 'radio-station' ), - 'default' => 'table', - 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'div' => __( 'Divs View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'default' => __( 'Legacy Table', 'radio-station' ), - ), - 'helper' => __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Schedule Clock Display --- - 'schedule_clock' => array( - 'type' => 'select', - 'label' => __( 'Schedule Clock?', 'radio-station' ), - 'default' => 'clock', - 'options' => array( - '' => __( 'None', 'radio-station' ), - 'clock' => __( 'Clock', 'radio-station' ), - 'timezone' => __( 'Timezone', 'radio-station' ), - ), - 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Schedule AJAX Load --- - // 2.5.10.1: added schedule AJAX load default - 'schedule_ajax' => array( - 'type' => 'checkbox', - 'label' => __( 'AJAX Load?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Whether to load schedule display via AJAX by default.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- [Pro/Plus] Schedule Switcher --- - 'schedule_switcher' => array( - 'type' => 'checkbox', - 'label' => __( 'View Switching', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - 'pro' => true, - ), - - // --- [Pro/Plus] Available Views --- - // 2.3.2: added additional views option - 'schedule_views' => array( - 'type' => 'multicheck', - 'label' => __( 'Available Views', 'radio-station' ), - // note: unstyled list view not included in defaults - 'default' => array( 'table', 'calendar' ), - 'value' => 'yes', - 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'grid' => __( 'Grid View', 'radio-station' ), - 'calendar' => __( 'Calendar View', 'radio-station' ), - ), - 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - 'pro' => true, - ), - - // --- [Pro/Plus] Time Spaced Grid View --- - // 2.4.0.4: added grid view time spacing option - 'schedule_timegrid' => array( - 'type' => 'checkbox', - 'label' => __( 'Time Spaced Grid', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - 'pro' => true, - ), - - // === Show Pages === - - // --- Show Blocks Position --- - 'show_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // ---- Show Section Layout --- - 'show_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Show Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // ---- Show Player --- - // 2.5.15: added show player selection - 'show_player' => array( - 'type' => 'select', - 'label' => __( 'Latest Show Player', 'radio-station' ), - 'options' => array( - 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), - 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), - ), - 'default' => 'media_elements', - 'helper' => __( 'Which player to use on the Show pages for the latest show recording.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Show Player Theme --- - // 2.5.15: added show player theme selection - 'show_player_theme' => array( - 'type' => 'select', - 'label' => __( 'Show Player Theme', 'radio-station' ), - 'default' => '', - 'options' => array( - '' => __( 'Player Default', 'radio-station' ), - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), - ), - 'helper' => __( 'Show Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Show Header Image --- - // 2.3.2: added plural to option label - 'show_header_image' => array( - 'type' => 'checkbox', - 'label' => __( 'Content Header Images', 'radio-station' ), - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Latest Show Posts --- - // 'show_latest_posts' => array( - // 'type' => 'numeric', - // 'label' => __( 'Latest Show Posts', 'radio-station' ), - // 'step' => 1, - // 'min' => 0, - // 'max' => 100, - // 'default' => 3, - // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'show', - // ), - - // --- Show Posts Per Page --- - 'show_posts_per_page' => array( - 'type' => 'numeric', - 'label' => __( 'Posts per Page', 'radio-station' ), - 'step' => 1, - 'min' => 0, - 'max' => 1000, - 'default' => 10, - 'helper' => __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Show Playlists per Page --- - 'show_playlists_per_page' => array( - 'type' => 'numeric', - 'step' => 1, - 'min' => 0, - 'max' => 1000, - 'label' => __( 'Playlists per Page', 'radio-station' ), - 'default' => 10, - 'helper' => __( 'Playlists per page on the Show Page tab/display', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- [Pro] Show Episodes per Page --- - 'show_episodes_per_page' => array( - 'type' => 'number', - 'label' => __( 'Episodes per Page', 'radio-station' ), - 'step' => 1, - 'min' => 1, - 'max' => 1000, - 'default' => 10, - 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - 'pro' => true, - ), - - // --- [Pro] Combined Team Tab --- - // 2.4.0.7: added combined team grid option - // 2.5.7: updated to add option to remove team display - 'combined_team_tab' => array( - 'type' => 'select', - 'label' => __( 'Team Tab Display', 'radio-station' ), - 'default' => 'yes', - 'options' => array( - 'off' => __( 'No Team Display', 'radio-station' ), - '' => __( 'Separate Role Tabs', 'radio-station' ), - 'yes' => __( 'Combined Team List', 'radio-station' ), - // 'grid' => __( 'Combined Team Grid', 'radio-station' ), - ), - 'helper' => __( 'How to display Show team members (eg. hosts, producers) on a Show page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - 'pro' => true, - ), - - // === Profile Pages === - // 2.3.3.9: added proflie page settings - - // --- [Pro/Plus] Profile Blocks Position --- - 'profile_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'profile', - 'pro' => true, - ), - - // ---- [Pro/Plus] Profile Section Layout --- - 'profile_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Profile Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'profile', - 'pro' => true, - ), - - // === Episode Pages === - // 2.3.3.9: added episode page settings - - // --- [Pro] Episode Blocks Position --- - 'episode_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // ---- [Pro] Episode Section Layout --- - 'episode_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Episode Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // ---- [Pro] Episode Player --- - // 2.5.15: added episode player selection - 'episode_player' => array( - 'type' => 'select', - 'label' => __( 'Episode Player', 'radio-station' ), - 'options' => array( - 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), - 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), - ), - 'default' => 'radio_player', - 'helper' => __( 'Which player to use on the Episode pages for the Episode recording.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // --- [Pro] Episode Player Theme --- - // 2.5.15: added episode player theme selection - 'episode_player_theme' => array( - 'type' => 'select', - 'label' => __( 'Episode Player Theme', 'radio-station' ), - 'default' => '', - 'options' => array( - '' => __( 'Player Default', 'radio-station' ), - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), - ), - 'helper' => __( 'Episode Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // --- [Pro] Use Latest Episode --- - 'episode_use_latest' => array( - 'type' => 'checkbox', - 'label' => __( 'Use Latest Episode', 'radio-station' ), - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // ==== Post Type Archives === - // 2.4.0.6: move archives to separate tab - // 2.4.0.6: added post type archives section - - // --- Shows Archive Page --- - 'show_archive_page' => array( - 'label' => __( 'Shows Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // --- Automatic Display --- - 'show_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // ? --- Redirect Shows Archive --- ? - // 'show_archive_override' => array( - // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), - // 'tab' => 'archives', - // 'section' => 'posttypes', - // ), - - // --- Overrides Archive Page --- - 'override_archive_page' => array( - 'label' => __( 'Overrides Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // --- Automatic Display --- - 'override_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // ? --- Redirect Overrides Archive --- ? - // 'override_archive_override' => array( - // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), - // 'tab' => 'archives', - // 'section' => 'posttypes', - // ), - - // --- Playlists Archive Page --- - 'playlist_archive_page' => array( - 'label' => __( 'Playlists Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // --- Automatic Display --- - 'playlist_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', - 'tab' => 'archives', - 'section' => 'posttypes', - ), - - // ? --- Redirect Playlists Archive --- ? - // 'playlist_archive_override' => array( - // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), - // 'tab' => 'archives', - // 'section' => 'posttypes', - // ), - - // --- [Pro] Episodes Archive Page --- - // 2.5.8: added episodes archive page option - 'episode_archive_page' => array( - 'label' => __( 'Episodes Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Episode archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'posttypes', - 'pro' => true, - ), - - // --- [Pro] Automatic Display --- - // 2.5.8: added episodes archive automatic display option - 'episode_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [episodes-archive]', - 'tab' => 'archives', - 'section' => 'posttypes', - 'pro' => true, - ), - - // --- [Pro] Team Archive Page --- - // 2.4.0.6: added option for team archive page - 'team_archive_page' => array( - 'label' => __( 'Team Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'posttypes', - 'pro' => true, - ), - - // --- [Pro] Automatic Display --- - // 2.4.0.6: added option for team archive page - 'team_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'select', - 'options' => array( - '' => __( 'Off', 'radio-station' ), - 'yes' => __( 'List', 'radio-station' ), - 'grid' => __( 'Grid', 'radio-station' ), - ), - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', - 'tab' => 'archives', - 'section' => 'posttypes', - 'pro' => true, - ), - - // === Taxonomy Archives === - // 2.4.0.6: added taxonomy archives section - - // --- Genres Archive Page --- - 'genre_archive_page' => array( - 'label' => __( 'Genres Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'taxonomies', - ), - - // --- Automatic Display --- - 'genre_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', - 'tab' => 'archives', - 'section' => 'taxonomies', - ), - - // ? --- Redirect Genres Archives --- ? - // 'genre_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), - // 'tab' => 'archives', - // 'section' => 'taxonomies', - // ), - - // --- Languages Archive Page --- - // 2.3.3.9: added language archive page - 'language_archive_page' => array( - 'label' => __( 'Languages Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), - 'tab' => 'archives', - 'section' => 'taxonomies', - ), - - // --- Automatic Display --- - // 2.3.3.9: added language archive automatic page - 'language_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', - 'tab' => 'archives', - 'section' => 'taxonomies', - ), - - // ? --- Redirect Languages Archives --- ? - // 'language_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), - // 'tab' => 'archives', - // 'section' => 'taxonomies', - // ), - - // TODO: guest archive pages - - - // === Single Templates === - - // --- Templates Change Note --- - 'templates_change_note' => array( - 'type' => 'note', - 'label' => __( 'Templates Change Note', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) - . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) - . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Show Template --- - 'show_template' => array( - 'label' => __( 'Show Template', 'radio-station' ), - 'type' => 'select', - 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), - ), - 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Combined Template Method --- - 'show_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Playlist Template --- - // 2.3.3.8: added missing singular.php option to match show_template - 'playlist_template' => array( - 'label' => __( 'Playlist Template', 'radio-station' ), - 'type' => 'select', - 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), - ), - 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Combined Template Method --- - 'playlist_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // === Widgets === - - // --- AJAX Loading --- - // 2.3.3: fix to value of value key - 'ajax_widgets' => array( - 'type' => 'checkbox', - 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - ), - - // --- [Pro/Plus] Dynamic Reloading --- - 'dynamic_reload' => array( - 'type' => 'checkbox', - 'label' => __( 'Dynamic Reloading?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // --- [Pro/Plus] Translate User Times --- - 'convert_show_times' => array( - 'type' => 'checkbox', - 'label' => __( 'Convert Show Times', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // --- [Pro/Plus] Timezone Switching --- - 'timezone_switching' => array( - 'type' => 'checkbox', - 'label' => __( 'User Timezone Switching', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow visitors to select their Timezone manually for Show time conversions.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // === Roles / Capabilities / Permissions === - // 2.3.0: added new capability and role options - - // --- Show Editing Permission Note --- - // 2.4.0.3: added role to show assignment note - 'permissions_show_role_note' => array( - 'type' => 'note', - 'label' => __( 'Show Editing Permissions', 'radio-station' ), - 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) - . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Playlist Editing Role Note --- - // 2.4.0.3: added role to playlist assignment note - 'permissions_playlist_role_note' => array( - 'type' => 'note', - 'label' => __( 'Playlist Permissions', 'radio-station' ), - 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Show Editor Role Note --- - 'show_editor_role_note' => array( - 'type' => 'note', - 'label' => __( 'Show Editor Role', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) - . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) - . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Author Role Capabilities --- - 'add_author_capabilities' => array( - 'type' => 'checkbox', - 'label' => __( 'Add to Author Capabilities', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Editor Role Capabilities --- - 'add_editor_capabilities' => array( - 'type' => 'checkbox', - 'label' => __( 'Add to Editor Capabilities', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // ? --- Disallow Shift Changes --- ? - // 'disallow_shift_changes' => array( - // 'type' => 'checkbox', - // 'label' => __( 'Disallow Shift Changes', 'radio-station' ), - // 'default' => array(), - // 'options' => array( - // 'authors' => __( 'WordPress Authors', 'radio-station' ), - // 'editors' => __( 'WorddPress Editors', 'radio-station' ), - // 'hosts' => __( 'Assigned DJs / Hosts', 'radio-station' ), - // 'producers' => __( 'Assigned Producers', 'radio-station' ), - // ), - // 'helper' => __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ), - // 'tab' => 'roles', - // 'section' => 'permissions', - // 'pro' => true, - // ), - - // === Tabs and Sections === - - // --- Tab Labels --- - // 2.3.2: add widget options tab - // 2.3.3.8: added player options tab - // 2.3.3.8: move templates section onto pages tab - // 2.4.0.6: added separate archives tab - 'tabs' => array( - 'general' => __( 'General', 'radio-station' ), - 'player' => __( 'Player', 'radio-station' ), - 'pages' => __( 'Pages', 'radio-station' ), - 'archives' => __( 'Archives', 'radio-station' ), - // 'templates' => __( 'Templates', 'radio-station' ), - 'widgets' => __( 'Widgets', 'radio-station' ), - 'roles' => __( 'Roles', 'radio-station' ), - ), - - // --- Section Labels --- - // 2.3.2: add widget loading section - // 2.3.3.9: added profile pages section - // 2.4.0.6: added performance section - // 2.4.0.6: added posttypes and taxonomies archive sections - 'sections' => array( - 'broadcast' => __( 'Broadcast', 'radio-station' ), - 'station' => __( 'Station', 'radio-station' ), - 'feeds' => __( 'Feeds', 'radio-station' ), - 'performance' => __( 'Performance', 'radio-station' ), - 'basic' => __( 'Basic Defaults', 'radio-station' ), - 'advanced' => __( 'Advanced Defaults', 'radio-station' ), - 'colors' => __( 'Player Colors', 'radio-station' ), - 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), - 'schedule' => __( 'Schedule Page', 'radio-station' ), - 'single' => __( 'Single Templates', 'radio-station' ), - // 'archive' => __( 'Archive Templates', 'radio-station' ), - 'show' => __( 'Show Pages', 'radio-station' ), - 'profile' => __( 'Profile Pages', 'radio-station' ), - 'episode' => __( 'Episode Pages', 'radio-station' ), - 'archives' => __( 'Archives', 'radio-station' ), - 'posttypes' => __( 'Post Types', 'radio-station' ), - 'taxonomies' => __( 'Taxonomies', 'radio-station' ), - 'loading' => __( 'Widget Loading', 'radio-station' ), - 'permissions' => __( 'Permissions', 'radio-station' ), - ), -); +function radio_station_plugin_options() { + + $options = array( + + // === Stream === + + // --- [Player] Streaming URL --- + 'streaming_url' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Streaming URL', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'stream', + ), + + // --- [Player] Stream Format --- + 'streaming_format' => array( + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Streaming Format', 'radio-station' ), + 'default' => 'aac', + 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'stream', + ), + + // --- [Player] Fallback Stream URL --- + 'fallback_url' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Fallback Stream URL', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'stream', + ), + + // --- [Player] Fallback Stream Format --- + 'fallback_format' => array( + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Fallback Format', 'radio-station' ), + 'default' => 'ogg', + 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'stream', + ), + + // --- [Player] Stream GeoBlocking --- + 'stream_geo_blocking' => array( + 'label' => __( 'GeoIP Stream Blocking', 'radio-station' ), + 'type' => 'select', + 'options' => array( + '' => __( 'No GeoIP Blocking', 'radio-station' ), + 'live365' => __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ), + // 'blacklist' => __( 'Custom Country Blacklist', 'radio-station' ), + // 'whitelist' => __( 'Custom Country Whitelist', 'radio-station' ), + ), + 'default' => '', + 'helper' => __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'stream', + 'pro' => true, + ), + + // === Broadcast === + + // --- Main Radio Language --- + 'radio_language' => array( + 'type' => 'select', + 'options' => $languages, + 'label' => __( 'Main Broadcast Language', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select the main language used on your Radio Station.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Timezone Location --- + 'timezone_location' => array( + 'type' => 'select', + 'options' => $timezones, + 'label' => __( 'Location Timezone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Clock Time Format --- + 'clock_time_format' => array( + 'type' => 'select', + 'options' => array( + '12' => __( '12 Hour Format', 'radio-station' ), + '24' => __( '24 Hour Format', 'radio-station' ), + ), + 'label' => __( 'Clock Time Format', 'radio-station' ), + 'default' => '12', + 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // === Station === + + // --- Station Title --- + // 2.3.3.8: added station title field + 'station_title' => array( + 'type' => 'text', + 'label' => __( 'Station Title', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Image --- + // 2.3.3.8: added station logo image field + 'station_image' => array( + 'type' => 'image', + 'label' => __( 'Station Logo Image', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Frequency --- + // 2.5.18: added station frequency option + 'station_frequency' => array( + 'type' => 'text', + 'label' => __( 'Station Frequency', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Text display to inform users of your main station frequency.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Location --- + // 2.5.18: added station location option + 'station_location' => array( + 'type' => 'text', + 'label' => __( 'Station Location', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Text display to inform users of your main station location.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Phone Number --- + // 2.3.3.6: added station phone number option + 'station_phone' => array( + 'type' => 'text', + 'options' => 'PHONE', + 'label' => __( 'Station Phone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Phone for Shows --- + // 2.3.3.6: added default to station phone option + 'shows_phone' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Phone Display', 'radio-station' ), + 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Email Address --- + // 2.3.3.8: added station email address option + 'station_email' => array( + 'type' => 'email', + 'default' => '', + 'label' => __( 'Station Email', 'radio-station' ), + 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Email for Shows --- + // 2.3.3.8: added default to email address option + 'shows_email' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Email Display', 'radio-station' ), + 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // === Feeds === + + // --- REST Data Routes --- + 'enable_data_routes' => array( + 'type' => 'checkbox', + 'label' => __( 'Enable Data Routes', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Data Feed Links --- + 'enable_data_feeds' => array( + 'type' => 'checkbox', + 'label' => __( 'Enable Data Feeds', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Ping Netmix Directory --- + // note: disabled by default for WordPress.org repository compliance + 'ping_netmix_directory' => array( + 'type' => 'checkbox', + 'label' => __( 'Ping Netmix Directory', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // === Performance === + // 2.4.0.6: separated performance section + + // --- Shift Conflict Checker --- + // 2.5.6: added setting for conflict checker + 'conflict_checker' => array( + 'type' => 'checkbox', + 'label' => __( 'Shift Conflict Checker', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Check for Shift conflicts when saving Show shift times.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'performance', + ), + + // --- Disable Transients --- + // 2.4.0.6: change label from Clear Transients + 'clear_transients' => array( + 'type' => 'checkbox', + 'label' => __( 'Disable Transients', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'performance', + ), + + // --- Transient Caching --- + 'transient_caching' => array( + 'type' => 'checkbox', + 'label' => __( 'Show Transients', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'performance', + 'pro' => true, + ), + + // --- Show Shift Feeds --- + /* 'show_shift_feeds' => array( + 'type' => 'checkbox', + 'label' => __( 'Show Shift Feeds', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + 'pro' => true, + ), */ + + // === Basic Stream Player === + + // --- Defaults Note --- + // 2.5.0: added note about defaults being overrideable in widgets + 'player_defaults_note' => array( + 'type' => 'note', + 'label' => __( 'Player Defaults Note', 'radio-station' ), + 'helper' => __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Title --- + 'player_title' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Station Title', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Image --- + 'player_image' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Station Image', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Script --- + // 2.4.0.3: change script default to jplayer + // 2.5.7: disable howler script (browser incompatibilities) + 'player_script' => array( + 'type' => 'select', + 'label' => __( 'Player Script', 'radio-station' ), + 'default' => 'jplayer', + 'options' => array( + 'amplitude' => __( 'Amplitude', 'radio-station' ), + 'jplayer' => __( 'jPlayer', 'radio-station' ), + // 'howler' => __( 'Howler', 'radio-station' ), + ), + 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Fallback Scripts --- + // 2.4.0.3: added fallback enable/disable switching + // 2.4.0.3: fixed option label from Player Script + // 2.5.7: disable howler script (browser incompatibilities) + 'player_fallbacks' => array( + 'type' => 'multicheck', + 'label' => __( 'Fallback Scripts', 'radio-station' ), + 'default' => array( 'amplitude', 'howler', 'jplayer' ), + 'options' => array( + 'amplitude' => __( 'Amplitude', 'radio-station' ), + 'jplayer' => __( 'jPlayer', 'radio-station' ), + // 'howler' => __( 'Howler', 'radio-station' ), + ), + 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Theme --- + 'player_theme' => array( + 'type' => 'select', + 'label' => __( 'Default Player Theme', 'radio-station' ), + 'default' => 'light', + 'options' => array( + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Buttons --- + // 2.5.15: change default to circular + 'player_buttons' => array( + 'type' => 'select', + 'label' => __( 'Default Player Buttons', 'radio-station' ), + 'default' => 'circular', + 'options' => array( + 'circular' => __( 'Circular Buttons', 'radio-station' ), + 'rounded' => __( 'Rounded Buttons', 'radio-station' ), + 'square' => __( 'Square Buttons', 'radio-station' ), + ), + 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Volume Controls --- + // 2.4.0.3: added enable/disable volume controls option + // 2.5.0: default to volume slider only + 'player_volumes' => array( + 'type' => 'multicheck', + 'label' => __( 'Volume Controls', 'radio-station' ), + 'default' => array( 'slider' ), + 'options' => array( + 'slider' => __( 'Volume Slider', 'radio-station' ), + 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), + 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), + 'max' => __( 'Maximize Volume', 'radio-station' ), + ), + 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- [Player] Player Debug Mode --- + 'player_debug' => array( + 'type' => 'checkbox', + 'label' => __( 'Player Debug Mode', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // === Player Colours === + + // --- [Pro/Player] Playing Highlight Color --- + 'player_playing_color' => array( + 'type' => 'color', + 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), + 'default' => '#70E070', + 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Control Icons Highlight Color --- + 'player_buttons_color' => array( + 'type' => 'color', + 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), + 'default' => '#00A0E0', + 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Volume Knob Color --- + 'player_thumb_color' => array( + 'type' => 'color', + 'label' => __( 'Volume Knob Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro/Player] Volume Track Color --- + 'player_range_color' => array( + 'type' => 'coloralpha', + 'label' => __( 'Volume Track Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // === Advanced Stream Player === + + // --- [Player] Player Volume --- + 'player_volume' => array( + 'type' => 'number', + 'label' => __( 'Player Start Volume', 'radio-station' ), + 'default' => 77, + 'min' => 0, + 'step' => 1, + 'max' => 100, + 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- [Player] Single Player --- + 'player_single' => array( + 'type' => 'checkbox', + 'label' => __( 'Single Player at Once', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Stop any existing Player instances on the page or in other windows or tabs when a Player is started.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- [Pro/Player] Player Autoresume --- + // 2.5.15: change autoresume default to off so activated manually + // 2.5.16: updated helper text + 'player_autoresume' => array( + 'type' => 'checkbox', + 'label' => __( 'Autoresume Playback', 'radio-station' ), + 'default' => '', + 'value' => 'on', + 'helper' => __( 'On return to site or page reload, ask the user to resume stream playback if they were playing the stream previously, using a popup a modal dialogue box.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), + + // --- [Pro/Player] Popup Player Button --- + // 2.5.0: enabled popup player button + 'player_popup' => array( + 'type' => 'checkbox', + 'label' => __( 'Popup Player Button', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Add button to open Popup Player in separate window.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), + + // === Sitewide Player Bar === + + // --- Player Bar Note --- + 'player_bar_note' => array( + 'type' => 'note', + 'label' => __( 'Bar Defaults Note', 'radio-station' ), + 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) + . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + ), + + // --- [Pro/Player] Sitewide Player Bar --- + 'player_bar' => array( + 'type' => 'select', + 'label' => __( 'Sitewide Player Bar', 'radio-station' ), + 'default' => 'off', + 'options' => array( + 'off' => __( 'No Player Bar', 'radio-station' ), + 'top' => __( 'Top Player Bar', 'radio-station' ), + 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), + ), + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Player Bar Height --- + // 2.5.15: add px suffix + 'player_bar_height' => array( + 'type' => 'number', + 'min' => 40, + 'max' => 400, + 'step' => 1, + 'label' => __( 'Player Bar Height', 'radio-station' ), + 'default' => 80, + 'suffix' => 'px', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Fade In Player Bar --- + 'player_bar_fadein' => array( + 'type' => 'number', + 'label' => __( 'Fade In Player Bar', 'radio-station' ), + 'default' => 2500, + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Continuous Playback --- + // 2.4.0.1: fix for missing value field + 'player_bar_continuous' => array( + 'type' => 'checkbox', + 'label' => __( 'Continuous Playback', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ) + . ' ' . __( 'Click here for setup notes.', 'radio-station' ) . '', + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Player Page Fade --- + 'player_bar_pagefade' => array( + 'type' => 'number', + 'label' => __( 'Page Fade Time', 'radio-station' ), + 'default' => 2000, + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Page Load Timeout --- + // 2.4.0.3: add page load timeout option + 'player_bar_timeout' => array( + 'type' => 'number', + 'label' => __( 'Page Load Timeout', 'teleporter' ), + 'default' => 7000, + 'min' => 0, + 'step' => 500, + 'max' => 20000, + 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Bar Player Text Color --- + 'player_bar_text' => array( + 'type' => 'color', + 'label' => __( 'Bar Player Text Color', 'radio-station' ), + 'default' => '#FFFFFF', + 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Bar Player Background Color --- + 'player_bar_background' => array( + 'type' => 'coloralpha', + 'label' => __( 'Bar Player Background Color', 'radio-station' ), + 'default' => 'rgba(0,0,0,255)', + 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Display Current Show --- + // 2.4.0.3: added for current show display + 'player_bar_currentshow' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Current Show', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Display Metadata --- + // 2.4.0.3: added for now playing metadata display + 'player_bar_nowplaying' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Now Playing', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Track Animation --- + // 2.5.0: added track animation option + 'player_bar_track_animation' => array( + 'type' => 'select', + 'label' => __( 'Track Animation', 'radio-station' ), + 'default' => 'backandforth', + 'options' => array( + 'none' => __( 'No Animation', 'radio-station' ), + 'lefttoright' => __( 'Left to Right Ticker', 'radio-station' ), + 'righttoleft' => __( 'Right to Left Ticker', 'radio-station' ), + 'backandforth' => __( 'Back and Forth', 'radio-station' ), + ), + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'How to animate the currently playing track display.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Metadata URL --- + // 2.4.0.3: added for alternative stream metadata URL + 'player_bar_metadata' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Metadata URL', 'radio-station' ), + 'default' => '', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro/Player] Store Track Metadata --- + // 2.5.6: added option to store stream + 'player_store_metadata' => array( + 'type' => 'checkbox', + 'label' => __( 'Store Track Metadata?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ), + 'pro' => true, + ), + + // === Master Schedule Page === + + // --- Schedule Page --- + 'schedule_page' => array( + 'type' => 'select', + 'options' => 'PAGEID', + 'label' => __( 'Master Schedule Page', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Automatic Schedule Display --- + 'schedule_auto' => array( + 'type' => 'checkbox', + 'label' => __( 'Automatic Display', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]', + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Default Schedule View --- + 'schedule_view' => array( + 'type' => 'select', + 'label' => __( 'Schedule View Default', 'radio-station' ), + 'default' => 'table', + 'options' => array( + 'table' => __( 'Table View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + 'div' => __( 'Divs View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'default' => __( 'Legacy Table', 'radio-station' ), + ), + 'helper' => __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Schedule Clock Display --- + 'schedule_clock' => array( + 'type' => 'select', + 'label' => __( 'Schedule Clock?', 'radio-station' ), + 'default' => 'clock', + 'options' => array( + '' => __( 'None', 'radio-station' ), + 'clock' => __( 'Clock', 'radio-station' ), + 'timezone' => __( 'Timezone', 'radio-station' ), + ), + 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Schedule AJAX Load --- + // 2.5.10.1: added schedule AJAX load default + 'schedule_ajax' => array( + 'type' => 'checkbox', + 'label' => __( 'AJAX Load?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Whether to load schedule display via AJAX by default.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- [Pro/Plus] Schedule Switcher --- + 'schedule_switcher' => array( + 'type' => 'checkbox', + 'label' => __( 'View Switching', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + + // --- [Pro/Plus] Available Views --- + // 2.3.2: added additional views option + 'schedule_views' => array( + 'type' => 'multicheck', + 'label' => __( 'Available Views', 'radio-station' ), + // note: unstyled list view not included in defaults + 'default' => array( 'table', 'calendar' ), + 'value' => 'yes', + 'options' => array( + 'table' => __( 'Table View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + 'grid' => __( 'Grid View', 'radio-station' ), + 'calendar' => __( 'Calendar View', 'radio-station' ), + ), + 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + + // --- [Pro/Plus] Time Spaced Grid View --- + // 2.4.0.4: added grid view time spacing option + 'schedule_timegrid' => array( + 'type' => 'checkbox', + 'label' => __( 'Time Spaced Grid', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + + // === Show Pages === + + // --- Show Blocks Position --- + 'show_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // ---- Show Section Layout --- + 'show_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Show Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // ---- Show Player --- + // 2.5.15: added show player selection + 'show_player' => array( + 'type' => 'select', + 'label' => __( 'Latest Show Player', 'radio-station' ), + 'options' => array( + 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), + 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + ), + 'default' => 'media_elements', + 'helper' => __( 'Which player to use on the Show pages for the latest show recording.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Player Theme --- + // 2.5.15: added show player theme selection + 'show_player_theme' => array( + 'type' => 'select', + 'label' => __( 'Show Player Theme', 'radio-station' ), + 'default' => '', + 'options' => array( + '' => __( 'Player Default', 'radio-station' ), + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Show Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Header Image --- + // 2.3.2: added plural to option label + 'show_header_image' => array( + 'type' => 'checkbox', + 'label' => __( 'Content Header Images', 'radio-station' ), + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Latest Show Posts --- + // 'show_latest_posts' => array( + // 'type' => 'numeric', + // 'label' => __( 'Latest Show Posts', 'radio-station' ), + // 'step' => 1, + // 'min' => 0, + // 'max' => 100, + // 'default' => 3, + // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'show', + // ), + + // --- Show Posts Per Page --- + 'show_posts_per_page' => array( + 'type' => 'numeric', + 'label' => __( 'Posts per Page', 'radio-station' ), + 'step' => 1, + 'min' => 0, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Playlists per Page --- + 'show_playlists_per_page' => array( + 'type' => 'numeric', + 'step' => 1, + 'min' => 0, + 'max' => 1000, + 'label' => __( 'Playlists per Page', 'radio-station' ), + 'default' => 10, + 'helper' => __( 'Playlists per page on the Show Page tab/display', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- [Pro] Show Episodes per Page --- + 'show_episodes_per_page' => array( + 'type' => 'number', + 'label' => __( 'Episodes per Page', 'radio-station' ), + 'step' => 1, + 'min' => 1, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, + ), + + // --- [Pro] Combined Team Tab --- + // 2.4.0.7: added combined team grid option + // 2.5.7: updated to add option to remove team display + 'combined_team_tab' => array( + 'type' => 'select', + 'label' => __( 'Team Tab Display', 'radio-station' ), + 'default' => 'yes', + 'options' => array( + 'off' => __( 'No Team Display', 'radio-station' ), + '' => __( 'Separate Role Tabs', 'radio-station' ), + 'yes' => __( 'Combined Team List', 'radio-station' ), + // 'grid' => __( 'Combined Team Grid', 'radio-station' ), + ), + 'helper' => __( 'How to display Show team members (eg. hosts, producers) on a Show page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, + ), + + // === Profile Pages === + // 2.3.3.9: added proflie page settings + + // --- [Pro/Plus] Profile Blocks Position --- + 'profile_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // ---- [Pro/Plus] Profile Section Layout --- + 'profile_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Profile Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // === Episode Pages === + // 2.3.3.9: added episode page settings + + // --- [Pro] Episode Blocks Position --- + 'episode_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ---- [Pro] Episode Section Layout --- + 'episode_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Episode Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ---- [Pro] Episode Player --- + // 2.5.15: added episode player selection + 'episode_player' => array( + 'type' => 'select', + 'label' => __( 'Episode Player', 'radio-station' ), + 'options' => array( + 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), + 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + ), + 'default' => 'radio_player', + 'helper' => __( 'Which player to use on the Episode pages for the Episode recording.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // --- [Pro] Episode Player Theme --- + // 2.5.15: added episode player theme selection + 'episode_player_theme' => array( + 'type' => 'select', + 'label' => __( 'Episode Player Theme', 'radio-station' ), + 'default' => '', + 'options' => array( + '' => __( 'Player Default', 'radio-station' ), + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Episode Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // --- [Pro] Use Latest Episode --- + 'episode_use_latest' => array( + 'type' => 'checkbox', + 'label' => __( 'Use Latest Episode', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ==== Post Type Archives === + // 2.4.0.6: move archives to separate tab + // 2.4.0.6: added post type archives section + + // --- Shows Archive Page --- + 'show_archive_page' => array( + 'label' => __( 'Shows Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // --- Automatic Display --- + 'show_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // ? --- Redirect Shows Archive --- ? + // 'show_archive_override' => array( + // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), + // 'tab' => 'archives', + // 'section' => 'posttypes', + // ), + + // --- Overrides Archive Page --- + 'override_archive_page' => array( + 'label' => __( 'Overrides Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // --- Automatic Display --- + 'override_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // ? --- Redirect Overrides Archive --- ? + // 'override_archive_override' => array( + // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), + // 'tab' => 'archives', + // 'section' => 'posttypes', + // ), + + // --- Playlists Archive Page --- + 'playlist_archive_page' => array( + 'label' => __( 'Playlists Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // --- Automatic Display --- + 'playlist_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + ), + + // ? --- Redirect Playlists Archive --- ? + // 'playlist_archive_override' => array( + // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), + // 'tab' => 'archives', + // 'section' => 'posttypes', + // ), + + // --- [Pro] Episodes Archive Page --- + // 2.5.8: added episodes archive page option + 'episode_archive_page' => array( + 'label' => __( 'Episodes Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Episode archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // --- [Pro] Automatic Display --- + // 2.5.8: added episodes archive automatic display option + 'episode_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [episodes-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // --- [Pro] Team Archive Page --- + // 2.4.0.6: added option for team archive page + 'team_archive_page' => array( + 'label' => __( 'Team Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // --- [Pro] Automatic Display --- + // 2.4.0.6: added option for team archive page + 'team_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'select', + 'options' => array( + '' => __( 'Off', 'radio-station' ), + 'yes' => __( 'List', 'radio-station' ), + 'grid' => __( 'Grid', 'radio-station' ), + ), + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', + 'tab' => 'archives', + 'section' => 'posttypes', + 'pro' => true, + ), + + // === Taxonomy Archives === + // 2.4.0.6: added taxonomy archives section + + // --- Genres Archive Page --- + 'genre_archive_page' => array( + 'label' => __( 'Genres Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'taxonomies', + ), + + // --- Automatic Display --- + 'genre_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', + 'tab' => 'archives', + 'section' => 'taxonomies', + ), + + // ? --- Redirect Genres Archives --- ? + // 'genre_archive_override' => array( + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), + // 'tab' => 'archives', + // 'section' => 'taxonomies', + // ), + + // --- Languages Archive Page --- + // 2.3.3.9: added language archive page + 'language_archive_page' => array( + 'label' => __( 'Languages Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), + 'tab' => 'archives', + 'section' => 'taxonomies', + ), + + // --- Automatic Display --- + // 2.3.3.9: added language archive automatic page + 'language_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', + 'tab' => 'archives', + 'section' => 'taxonomies', + ), + + // ? --- Redirect Languages Archives --- ? + // 'language_archive_override' => array( + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), + // 'tab' => 'archives', + // 'section' => 'taxonomies', + // ), + + // TODO: guest archive pages + + + // === Single Templates === + + // --- Templates Change Note --- + 'templates_change_note' => array( + 'type' => 'note', + 'label' => __( 'Templates Change Note', 'radio-station' ), + 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) + . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) + . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Show Template --- + 'show_template' => array( + 'label' => __( 'Show Template', 'radio-station' ), + 'type' => 'select', + 'options' => array( + 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), + 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), + 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), + 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + ), + 'default' => 'page', + 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Combined Template Method --- + 'show_template_combined' => array( + 'label' => __( 'Combined Method', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Playlist Template --- + // 2.3.3.8: added missing singular.php option to match show_template + 'playlist_template' => array( + 'label' => __( 'Playlist Template', 'radio-station' ), + 'type' => 'select', + 'options' => array( + 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), + 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), + 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), + 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + ), + 'default' => 'page', + 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Combined Template Method --- + 'playlist_template_combined' => array( + 'label' => __( 'Combined Method', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // === Widgets === + + // --- AJAX Loading --- + // 2.3.3: fix to value of value key + 'ajax_widgets' => array( + 'type' => 'checkbox', + 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + ), + + // --- [Pro/Plus] Dynamic Reloading --- + 'dynamic_reload' => array( + 'type' => 'checkbox', + 'label' => __( 'Dynamic Reloading?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // --- [Pro/Plus] Translate User Times --- + 'convert_show_times' => array( + 'type' => 'checkbox', + 'label' => __( 'Convert Show Times', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // --- [Pro/Plus] Timezone Switching --- + 'timezone_switching' => array( + 'type' => 'checkbox', + 'label' => __( 'User Timezone Switching', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow visitors to select their Timezone manually for Show time conversions.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // === Roles / Capabilities / Permissions === + // 2.3.0: added new capability and role options + + // --- Show Editing Permission Note --- + // 2.4.0.3: added role to show assignment note + 'permissions_show_role_note' => array( + 'type' => 'note', + 'label' => __( 'Show Editing Permissions', 'radio-station' ), + 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) + . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Playlist Editing Role Note --- + // 2.4.0.3: added role to playlist assignment note + 'permissions_playlist_role_note' => array( + 'type' => 'note', + 'label' => __( 'Playlist Permissions', 'radio-station' ), + 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Show Editor Role Note --- + 'show_editor_role_note' => array( + 'type' => 'note', + 'label' => __( 'Show Editor Role', 'radio-station' ), + 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) + . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) + . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Author Role Capabilities --- + 'add_author_capabilities' => array( + 'type' => 'checkbox', + 'label' => __( 'Add to Author Capabilities', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Editor Role Capabilities --- + 'add_editor_capabilities' => array( + 'type' => 'checkbox', + 'label' => __( 'Add to Editor Capabilities', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // ? --- Disallow Shift Changes --- ? + // 'disallow_shift_changes' => array( + // 'type' => 'checkbox', + // 'label' => __( 'Disallow Shift Changes', 'radio-station' ), + // 'default' => array(), + // 'options' => array( + // 'authors' => __( 'WordPress Authors', 'radio-station' ), + // 'editors' => __( 'WorddPress Editors', 'radio-station' ), + // 'hosts' => __( 'Assigned DJs / Hosts', 'radio-station' ), + // 'producers' => __( 'Assigned Producers', 'radio-station' ), + // ), + // 'helper' => __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ), + // 'tab' => 'roles', + // 'section' => 'permissions', + // 'pro' => true, + // ), + + // === Tabs and Sections === + + // --- Tab Labels --- + // 2.3.2: add widget options tab + // 2.3.3.8: added player options tab + // 2.3.3.8: move templates section onto pages tab + // 2.4.0.6: added separate archives tab + // 2.7.0: add subscribe and app tabs + 'tabs' => array( + 'general' => __( 'General', 'radio-station' ), + 'player' => __( 'Player', 'radio-station' ), + 'subscribe' => __( 'Subscribe', 'radio-station' ), + 'pages' => __( 'Pages', 'radio-station' ), + 'archives' => __( 'Archives', 'radio-station' ), + // 'templates' => __( 'Templates', 'radio-station' ), + 'widgets' => __( 'Widgets', 'radio-station' ), + 'roles' => __( 'Roles', 'radio-station' ), + 'app' => __( 'App', 'radio-station' ), + ), + + // --- Section Labels --- + // 2.3.2: add widget loading section + // 2.3.3.9: added profile pages section + // 2.4.0.6: added performance section + // 2.4.0.6: added posttypes and taxonomies archive sections + // 2.7.0: add subscribe sections + 'sections' => array( + // --- general --- + 'stream' => __( 'Stream', 'radio-station' ), + 'broadcast' => __( 'Broadcast', 'radio-station' ), + 'station' => __( 'Station', 'radio-station' ), + 'feeds' => __( 'Feeds', 'radio-station' ), + 'performance' => __( 'Performance', 'radio-station' ), + // --- player --- + 'basic' => __( 'Basic Defaults', 'radio-station' ), + 'advanced' => __( 'Advanced Defaults', 'radio-station' ), + 'colors' => __( 'Player Colors', 'radio-station' ), + 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), + // --- pages --- + 'schedule' => __( 'Schedule Page', 'radio-station' ), + 'single' => __( 'Single Templates', 'radio-station' ), + // 'archive' => __( 'Archive Templates', 'radio-station' ), + 'show' => __( 'Show Pages', 'radio-station' ), + 'profile' => __( 'Profile Pages', 'radio-station' ), + 'episode' => __( 'Episode Pages', 'radio-station' ), + // --- archives --- + 'archives' => __( 'Archives', 'radio-station' ), + 'posttypes' => __( 'Post Types', 'radio-station' ), + 'taxonomies' => __( 'Taxonomies', 'radio-station' ), + // --- widgets --- + 'loading' => __( 'Widget Loading', 'radio-station' ), + // --- roles --- + 'permissions' => __( 'Permissions', 'radio-station' ), + ), + ); + + return $options; +} + diff --git a/player/compat.php b/player/compat.php index e04fe91..b113e36 100644 --- a/player/compat.php +++ b/player/compat.php @@ -40,6 +40,13 @@ function sanitize_text_field( $text ) { } } +// ---------------------------- +// Backwards Compatible Defines +// ---------------------------- +// 2.5.18: added for Radio Player no-conflict backwards compatibility +if ( !defined( 'RADIO_PLAYER_URL' ) && defined( 'RADIO_PLAYER_DIR_URL' ) ) { + define( 'RADIO_PLAYER_URL', RADIO_PLAYER_DIR_URL ); +} // ---------------------------------------- // Player Backwards Compatibility Functions diff --git a/player/radio-player.php b/player/radio-player.php index 6ad5325..5b21714 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -85,9 +85,10 @@ // ---------------------- // // --- player resource URL --- -// RADIO_PLAYER_URL - define player URL path for standalone compatible version +// 2.5.18: change constant name from RADIO_PLAYER_DIR_URL to avoid conflict +// RADIO_PLAYER_DIR_URL - define player URL path for standalone compatible version // (note: should have a trailing slash!) eg. to use as a WordPress mu-plugins dropin: -// define( 'RADIO_PLAYER_URL', 'https://example.com/wp-content/mu-plugins/player/'); +// define( 'RADIO_PLAYER_DIR_URL', 'https://example.com/wp-content/mu-plugins/player/'); // (then include /mu-plugins/player/radio-player.php from a file in /mu-plugins/) // --- player script and skin --- @@ -289,7 +290,8 @@ function radio_player_output( $args = array(), $echo = false ) { $container_id = 'radio_container_' . $instance; // --- set Player div --- - $classes = array( 'radio-player', 'rp-player', 'script-' . $args['script'] ); + // 2.5.18: add stream-player class for cross-compatibility + $classes = array( 'stream-player', 'radio-player', 'rp-player', 'script-' . $args['script'] ); if ( $args['default'] ) { $classes[] = 'default-player'; } @@ -857,7 +859,9 @@ function radio_player_shortcode( $atts, $echo = false ) { // --- merge attribute values --- if ( function_exists( 'shortcode_atts' ) ) { + // 2.5.18: add stream-player context for cross-compatibility $atts = shortcode_atts( $defaults, $atts, 'radio-player' ); + $atts = shortcode_atts( $defaults, $atts, 'stream-player' ); } foreach ( $defaults as $key => $value ) { if ( !isset( $atts[$key] ) ) { @@ -1193,10 +1197,11 @@ function radio_player_add_inline_style( $css ) { // TODO: find a way to fix this? it does NOT enqueue inline // --- add check if style is already done --- - if ( wp_style_is( 'radio-player', 'registered' ) && !wp_style_is( 'radio-player', 'done' ) ) { + // 2.5.18: change radio-player style handle to stream-player + if ( wp_style_is( 'stream-player', 'registered' ) && !wp_style_is( 'stream-player', 'done' ) ) { // --- add styles as normal --- - wp_add_inline_style( 'radio-player', $css ); + wp_add_inline_style( 'stream-player', $css ); } else { @@ -1414,8 +1419,8 @@ function radio_player_core_scripts() { if ( function_exists( 'wp_enqueue_script' ) ) { // --- enqueue player script --- - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/sysend.js'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/sysend.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/sysend.js', RADIO_STATION_FILE ); } else { @@ -1427,8 +1432,8 @@ function radio_player_core_scripts() { // --- output script tag directly --- $url = 'js/sysend.js'; - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . $url; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . $url; } radio_player_script_tag( $url, $version ); $radio_player['printed_sysend'] = true; @@ -1449,14 +1454,16 @@ function radio_player_core_scripts() { if ( function_exists( 'wp_enqueue_script' ) ) { // --- enqueue player script --- - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/radio-player' . $suffix . '.js'; + // 2.5.18: change RADIO_PLAYER_DIR_URL to RADIO_PLAYER_DIR_URL + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/radio-player' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/radio-player' . $suffix . '.js', RADIO_STATION_FILE ); } else { $url = plugins_url( 'js/radio-player' . $suffix . '.js', __FILE__ ); } - wp_enqueue_script( 'radio-player', $url, array( 'jquery' ), $version, true ); + // 2.5.18: change radio-player script handle to stream-player + wp_enqueue_script( 'stream-player', $url, array( 'jquery' ), $version, true ); } /* elseif ( !isset( $radio_player['printed_player'] ) ) { @@ -1464,8 +1471,8 @@ function radio_player_core_scripts() { // --- output script tag directly --- $url = 'js/radio-player' . $suffix . '.js'; - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . $url; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . $url; } radio_player_script_tag( $url, $version ); $radio_player['printed_player'] = true; @@ -1482,8 +1489,8 @@ function radio_player_core_scripts() { // --- set amplitude script --- $path = dirname( __FILE__ ) . '/js/amplitude' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/amplitude' . $suffix . '.js'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/amplitude' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/amplitude' . $suffix . '.js', RADIO_STATION_FILE ); } else { @@ -1492,8 +1499,8 @@ function radio_player_core_scripts() { } /* else { $url = 'js/amplitude' . $suffix . '.js'; - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . $url; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . $url; } } */ $radio_player['amplitude_script'] = array( 'version' => '5.3.2', 'url' => $url, 'path' => $path ); @@ -1501,8 +1508,8 @@ function radio_player_core_scripts() { // --- set jplayer script --- $path = dirname( __FILE__ ) . '/js/jplayer' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/jplayer' . $suffix . '.js'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/jplayer' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/jplayer' . $suffix . '.js', RADIO_STATION_FILE ); } else { @@ -1517,8 +1524,8 @@ function radio_player_core_scripts() { // --- set howler script --- $path = dirname( __FILE__ ) . '/js/howler' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/howler' . $suffix . '.js'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/howler' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/howler' . $suffix . '.js', RADIO_STATION_FILE ); } else { @@ -1526,8 +1533,8 @@ function radio_player_core_scripts() { } } /* else { $url = 'js/howler' . $suffix . '.js'; - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . $url; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . $url; } } */ $radio_player['howler_script'] = array( 'version' => '2.2.3', 'url' => $url, 'path' => $path ); @@ -1536,14 +1543,14 @@ function radio_player_core_scripts() { /* $version = '4.2.6'; // as of WP 4.9 $version = filemtime( dirname( __FILE__ ) . '/js/mediaelement-and-player' . $suffix . '.js' ); $url = 'js/mediaelement-and-player' . $suffix . '.js'; - if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} $radio_player['media_script'] = array( 'version' => $version, 'url' => $url, 'path' => $path ); // --- set media elements player script --- $path = dirname( __FILE__ ) . '/js/rp-mediaelement' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'js/rp-mediaelement.js'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'js/rp-mediaelement.js'; $version = filemtime( dirname( __FILE__ ) . '/js/rp-mediaelement.js' ); } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/rp-mediaelement.js', RADIO_STATION_FILE ); @@ -1556,7 +1563,7 @@ function radio_player_core_scripts() { // note: no minified version here yet ? // $version = filemtime( dirname( __FILE__ ) . '/js/rp-mediaelement.js' ); // $url = 'js/rp-mediaelement.js'; - // if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} } $radio_player['elements_script'] = array( 'version' => $version, 'url' => $url, 'path' => $path ); */ @@ -1568,9 +1575,10 @@ function radio_player_core_scripts() { if ( '' != $js ) { if ( function_exists( 'wp_add_inline_script' ) ) { // 2.5.0: added check if script already done - if ( !wp_script_is( 'radio-player', 'done' ) ) { + // 2.5.18: change radio-player script handle to stream-player + if ( !wp_script_is( 'stream-player', 'done' ) ) { // --- add inline script --- - wp_add_inline_script( 'radio-player', $js, 'before' ); + wp_add_inline_script( 'stream-player', $js, 'before' ); } else { // --- print settings directly --- // 2.5.7: use direct echo arg for player settings @@ -1671,8 +1679,9 @@ function radio_player_enqueue_script( $script ) { function radio_player_inline_script( $js, $position = 'after' ) { if ( function_exists( 'wp_add_inline_script' ) ) { - if ( !wp_script_is( 'radio-player', 'done' ) ) { - wp_add_inline_script( 'radio-player', $js, $position ); + // 2.5.18: change radio-player script handle to stream-player + if ( !wp_script_is( 'stream-player', 'done' ) ) { + wp_add_inline_script( 'stream-player', $js, $position ); } else { $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; wp_register_script( 'radio-player-footer', null, array( 'jquery' ), $version, true ); @@ -1891,8 +1900,8 @@ function radio_player_get_player_settings( $echo = false ) { // --- set jPlayer Flash path --- // 2.5.7: disable swf fallback support - /* if ( defined( 'RADIO_PLAYER_URL' ) ) { - $swf_path = RADIO_PLAYER_URL . 'js'; + /* if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $swf_path = RADIO_PLAYER_DIR_URL . 'js'; } elseif ( function_exists( 'plugins_url' ) ) { if ( defined( 'RADIO_STATION_FILE' ) ) { $swf_path = plugins_url( 'player/js', RADIO_STATION_FILE ); @@ -2900,14 +2909,15 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { if ( file_exists( $path ) ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'css/radio-player' . $suffix. '.css'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'css/radio-player' . $suffix. '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/radio-player' . $suffix . '.css', RADIO_STATION_FILE ); } else { $url = plugins_url( 'css/radio-player.css', __FILE__ ); } - wp_enqueue_style( 'radio-player', $url, array(), $version, 'all' ); + // 2.5.18: change radio-player script handle to stream-player + wp_enqueue_style( 'stream-player', $url, array(), $version, 'all' ); // --- enqueue player control styles inline --- $control_styles = radio_player_control_styles( false ); @@ -2919,10 +2929,10 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } /* else { // --- output style tag directly --- $url = 'css/radio-player' . $suffix . '.css'; - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . $url; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . $url; } - radio_player_style_tag( 'radio-player', $url, $version ); + radio_player_style_tag( 'stream-player', $url, $version ); // 2.5.0: added missing non-WP control style output $control_styles = radio_player_control_styles( false ); @@ -2953,8 +2963,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { if ( file_exists( $path ) ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'css/jplayer' . $suffix. '.css'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'css/jplayer' . $suffix. '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/jplayer' . $suffix . '.css', RADIO_STATION_FILE ); } else { @@ -2964,7 +2974,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/jplayer' . $suffix . '.css'; - // if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} // radio_player_style_tag( 'rp-jplayer', $url, $version ); } @@ -2989,8 +2999,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { if ( file_exists( $path ) ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'css/jplayer' . $skin_ref . $suffix . '.css'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'css/jplayer' . $skin_ref . $suffix . '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/jplayer' . $skin_ref . $suffix . '.css', RADIO_STATION_FILE ); } else { @@ -3004,7 +3014,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/jplayer' . $skin_ref . $suffix . '.css'; - // if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} // radio_player_style_tag( 'rp-jplayer-skin', $url, $version ); } @@ -3035,8 +3045,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ){ - $url = RADIO_PLAYER_URL . 'css/rp-mediaelement.css'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ){ + $url = RADIO_PLAYER_DIR_URL . 'css/rp-mediaelement.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/rp-mediaelement.css', RADIO_STATION_FILE ); } else { @@ -3046,7 +3056,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/rp-mediaelement.css'; - // if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} // radio_player_style_tag( 'rp-mediaelement', $url, $version ); } @@ -3061,8 +3071,8 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { - if ( defined( 'RADIO_PLAYER_URL' ) ) { - $url = RADIO_PLAYER_URL . 'css/mediaelement.css'; + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { + $url = RADIO_PLAYER_DIR_URL . 'css/mediaelement.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/mediaelement.css', RADIO_STATION_FILE ); } else { @@ -3072,7 +3082,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/mediaelement.css'; - // if ( defined( 'RADIO_PLAYER_URL' ) ) {$url = RADIO_PLAYER_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} // radio_player_style_tag( 'rp-mediaelement', $url, $version ); } } diff --git a/radio-station-admin.php b/radio-station-admin.php index b39a9d4..bc6eaa3 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -222,7 +222,8 @@ function radio_station_add_admin_menus() { add_submenu_page( 'radio-station', $rs . ' ' . __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); add_submenu_page( 'radio-station', $rs . ' ' . __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Schedule Overrides', 'radio-station' ), __( 'Schedule Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); + // 2.5.18: change label from Schedule Overrides + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Special Overrides', 'radio-station' ), __( 'Special Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); do_action( 'radio_station_admin_submenu_middle' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Hosts', 'radio-station' ), __( 'Hosts', 'radio-station' ), 'edit_hosts', 'hosts' ); diff --git a/radio-station.php b/radio-station.php index a6b6a71..5c49119 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.16 +Version: 2.5.18 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -211,11 +211,13 @@ function radio_station_back_compat_player() { // 2.3.0: added plugin options // 2.4.0.8: moved options array to separate file // 2.5.0: move plan options check to separate function +// 2.5.18: get options via function +require RADIO_STATION_DIR . '/options.php'; $timezones = radio_station_get_timezone_options( true ); $languages = radio_station_get_language_options( true ); $formats = radio_station_get_stream_formats(); $plan_options = radio_station_check_plan_options(); -require RADIO_STATION_DIR . '/options.php'; +$options = radio_station_plugin_options(); // ---------------------- // Plugin Loader Settings @@ -580,9 +582,16 @@ function radio_station_enqueue_plugin_scripts() { // 2.3.0: added for automatic custom style loading radio_station_enqueue_style( 'custom' ); + // --- register moment js --- + // 2.5.0: move to separate function for reusability + // 2.5.18: move earlier to add as radio station dependency + radio_station_register_moment(); + wp_enqueue_script( 'moment' ); + // --- enqueue plugin script --- // 2.3.0: added jquery dependency for inline script fragments - radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); + // 2.5.18: add moment as a dependency to prevent pageload conflicts + radio_station_enqueue_script( 'radio-station', array( 'jquery', 'moment' ), true ); // --- set script suffix --- $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; @@ -593,10 +602,6 @@ function radio_station_enqueue_plugin_scripts() { $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); - // --- register moment js --- - // 2.5.0: move to separate function for reusability - radio_station_register_moment(); - wp_enqueue_script( 'moment' ); } // ------------------ diff --git a/readme.md b/readme.md index a4deb56..6e06cff 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3 -Tested up to: 6.8.2 +Tested up to: 6.9 -Stable tag: 2.5.16 +Stable tag: 2.5.18 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index aeabfa3..45aeeaa 100644 --- a/readme.txt +++ b/readme.txt @@ -5,8 +5,8 @@ Tags: radio station, radio shows, radio station schedule, radio broadcasting, st License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 -Tested up to: 6.8.2 -Stable tag: 2.5.16 +Tested up to: 6.9 +Stable tag: 2.5.18 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. @@ -404,6 +404,19 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == += 2.5.18 = +* Changed: display label of Overrides to Specials +* Changed: handle of radio-player assets to stream-player +* Improved: Related Show and Linked Show select Show ordering +* Improved: handle Special/Override meta in Archive shortcode +* Improved: edit Special/Override permissions for linked Shows +* Added: Station Frequency and Location options +* Added: Channel inputs for Shifts and Overrides +* Fixed: Radio Player plugin conflicts + += 2.5.17 = +* Fixed: Current Show widget reloading when Shift display is off + = 2.5.16 = * Fixed: Player Block control color style preview / loading diff --git a/templates/single-show-content.php b/templates/single-show-content.php index cbeb82e..045de9b 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -73,11 +73,7 @@ // --- get data format --- // 2.3.2: set filterable time formats // 2.3.3.9: moved out of show times block -if ( 24 == (int) $time_format ) { - $start_data_format = $end_data_format = 'H:i'; -} else { - $start_data_format = $end_data_format = 'g:i a'; -} +$start_data_format = $end_data_format = ( 24 == (int) $time_format ) ? 'H:i' : 'g:i a'; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'show-template', $post_id ); $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'show-template', $post_id ); @@ -456,7 +452,7 @@ $genre_links = array(); foreach ( $genres as $genre ) { $genre_link = get_term_link( $genre ); - $genre_links[] = '' . esc_html( $genre->name ) . '' . "\n"; + $genre_links[] = '' . esc_html( $genre->name ) . ''; } $meta_blocks['genres'] .= implode( ', ', $genre_links ) . "\n"; $meta_blocks['genres'] .= '' . "\n"; @@ -547,6 +543,11 @@ $label = __( 'Not Currently Scheduled.', 'radio-station' ); $label = apply_filters( 'radio_station_show_no_shifts_label', $label, $post_id ); $blocks['show_times'] .= esc_html( $label ); + + // 2.5.18: clear show times block for override display + if ( $post_type == RADIO_STATION_OVERRIDE_SLUG ) { + $blocks['show_times'] = ''; + } } else { @@ -704,14 +705,14 @@ // 2.3.3.9: maybe output linked override times $date_format = apply_filters( 'radio_station_override_date_format', 'j F' ); $show_past_dates = apply_filters( 'radio_station_override_show_past_dates', false ); -$overrides = radio_station_get_linked_override_times( $post_id ); +$overrides = radio_station_get_linked_override_times( $post_id, $show_past_dates ); $scheduled = ''; if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { if ( RADIO_STATION_DEBUG ) { echo 'LINKED OVERRIDES: ' . esc_html( print_r( $overrides, true ) ) . '' . "\n"; } $now = radio_station_get_now(); - foreach ( $overrides as $override ) { + foreach ( $overrides as $i => $override ) { // 2.5.8: added isset check for override disabled key if ( !isset( $override['disabled'] ) || ( 'yes' != $override['disabled'] ) ) { // 2.5.6: added check that override keys are not empty @@ -725,41 +726,49 @@ $override_end_time = $override_end_time + ( 24 * 60 * 60 ); } - // --- maybe filter out past scheduled dates --- - if ( $show_past_dates || ( $override_end_time > $now ) ) { - - $start_display = radio_station_get_time( $start_data_format, $override_start_time ); - $end_display = radio_station_get_time( $end_data_format, $override_end_time ); - $start_display = radio_station_translate_time( $start_display ); - $end_display = radio_station_translate_time( $end_display ); - $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); - $date = radio_station_get_time( $date_format, $date_time ); - - // 2.4.0.6: use filtered shift separator - $separator = ' - '; - $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); - - $scheduled .= '
    ' . "\n"; - $scheduled .= '' . esc_html( $date ) . '' . "\n"; - $scheduled .= '' . esc_html( $start_display ) . '' . "\n"; - $scheduled .= '' . esc_html( $separator ) . '' . "\n"; - $scheduled .= '' . esc_html( $end_display ) . '' . "\n"; - $scheduled .= '
    ' . "\n"; - - $scheduled .= '
    ' . "\n"; - $scheduled .= '[' . "\n"; - $scheduled .= '' . "\n"; - $scheduled .= '' . esc_html( $separator ) . '' . "\n"; - $scheduled .= ']' . "\n"; - $scheduled .= '
    ' . "\n"; - } + // --- maybe filter out past scheduled dates + // 2.5.18: moved past date check to within radio_station_get_linked_override_times + + $start_display = radio_station_get_time( $start_data_format, $override_start_time ); + $end_display = radio_station_get_time( $end_data_format, $override_end_time ); + $start_display = radio_station_translate_time( $start_display ); + $end_display = radio_station_translate_time( $end_display ); + $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); + $date = radio_station_get_time( $date_format, $date_time ); + + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); + + // TODO: link time to override in list ? + $schedule = '
    ' . "\n"; + $schedule .= '' . esc_html( $date ) . '' . "\n"; + $schedule .= '' . esc_html( $start_display ) . '' . "\n"; + $schedule .= '' . esc_html( $separator ) . '' . "\n"; + $schedule .= '' . esc_html( $end_display ) . '' . "\n"; + $schedule .= '
    ' . "\n"; + + $schedule .= '
    ' . "\n"; + $schedule .= '[' . "\n"; + $schedule .= '' . "\n"; + $schedule .= '' . esc_html( $separator ) . '' . "\n"; + $schedule .= ']' . "\n"; + $schedule .= '
    ' . "\n"; + + // 2.5.18: added show override display filter + $schedule = apply_filters( 'radio_station_show_override_display', $schedule, $override, $show_id ); + $scheduled .= $schedule; } } } } if ( '' != $scheduled ) { - $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    ' . "\n"; + // 2.5.18: change test from scheduled dates to special dates + // 2.5.18: change h5 to h4 for display consistency + $blocks['show_times'] .= '

    ' . esc_html( __( 'Special Dates', 'radio-station' ) ) . '

    ' . "\n"; $blocks['show_times'] .= $scheduled . '
    ' . "\n"; +} else { + $overrides = false; } // --- maybe add link to full schedule page --- @@ -842,6 +851,26 @@ $sections['about']['content'] .= '' . "\n"; } +// --- Show Overrides (Specials) --- +// 2.5.18: add linked overrides section (but do not duplicate on override pages) +if ( $overrides && ( $post_type != RADIO_STATION_OVERRIDE_SLUG ) ) { + $override_type = get_post_type_object( RADIO_STATION_OVERRIDE_SLUG ); + $override_label = $override_type->labels->name; + $sections['overrides']['heading'] = ''; + $label = $show_label . ' ' . $override_label; + $label = apply_filters( 'radio_station_show_overrides_label', $label, $post_id ); + $sections['overrides']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . "\n"; + $anchor = apply_filters( 'radio_station_show_overrides_anchor', $override_label, $post_id ); + $sections['overrides']['anchor'] = $anchor; + + // $radio_station_data['show-overrides'] = $overrides; // do NOT do this + $sections['overrides']['content'] = '

    ' . "\n"; + $shortcode = '[show-overrides-archive per_page="' . $posts_per_page . '" show="' . $post_id . '"]'; + $shortcode = apply_filters( 'radio_station_show_page_overrides_shortcode', $shortcode, $post_id ); + $sections['overrides']['content'] .= do_shortcode( $shortcode ); + $sections['overrides']['content'] .= '
    ' . "\n"; +} + // --- Show Episodes Tab --- // 2.3.3.4: added filter for show episodes label and anchor // 2.3.3.9: show episodes tab added via sections filter @@ -989,9 +1018,11 @@ } // --- filter section order --- - $section_order = array( 'about', 'episodes', 'posts', 'playlists', 'hosts', 'producers' ); + $section_order = array( 'about', 'overrides', 'posts', 'episodes', 'playlists', 'hosts', 'producers' ); $section_order = apply_filters( 'radio_station_show_page_section_order', $section_order, $post_id ); + echo '
    SECTION ORDER: ' . print_r( $section_order, true ) . '
    ' . "\n"; + // --- Display Show Sections --- // 2.3.0: filter show sections for display if ( ( is_array( $sections ) && ( count( $sections ) > 0 ) ) From 1b446916cfb490d032b9d6e850cffb50876869b6 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 10 Dec 2025 12:34:38 +1000 Subject: [PATCH 361/377] 2.5.18 development updates --- CHANGELOG.md | 1 + includes/onboarding.php | 377 +++++++++++++----- includes/support-functions.php | 5 +- includes/times.php | 6 +- includes/user-roles.php | 151 ++++--- loader.php | 60 ++- options.php | 704 +++++++++++++++++---------------- radio-station.php | 23 +- readme.txt | 1 + scheduler/schedule-engine.php | 6 + 10 files changed, 784 insertions(+), 550 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 154177d..6382f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). = 2.5.18 = +* Updated: Plugin Panel (1.3.7) for delayed translations * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player * Improved: Related Show and Linked Show select Show ordering diff --git a/includes/onboarding.php b/includes/onboarding.php index 7f1018e..6072422 100644 --- a/includes/onboarding.php +++ b/includes/onboarding.php @@ -20,8 +20,16 @@ function radio_station_add_dashboard_widget() { // --- add dashboard widget --- - wp_add_dashboard_widget( 'radio-station-stats', __( 'Radio Station', 'radio-station' ), 'radio_station_dashboard_widget' ); - + // wp_add_dashboard_widget( 'radio-station-stats', __( 'Radio Station', 'radio-station' ), 'radio_station_dashboard_widget' ); + $label = defined( 'RADIO_STATION_PRO_NAME' ) ? RADIO_STATION_PRO_NAME : __( 'Radio Station', 'radio-station' ); + add_meta_box( + 'radio-station-stats', + $label, + 'radio_station_dashboard_widget', + 'dashboard', + 'normal', + 'high' + ); } // ---------------- @@ -43,7 +51,14 @@ function radio_station_quicklinks_panel() { // --- quicklinks menu --- echo '
    ' . "\n"; + // --- post types menu --- + echo '' . "\n"; + + // --- post types menu --- + echo '
      ' . "\n"; + + // --- pro features --- + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + + // --- episodes --- + $episodes_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'edit.php' ) ); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '' . esc_html( __( 'Episodes', 'radio-station' ) ) . '' . "\n"; + echo '
    • ' . "\n"; + + // --- hosts --- + $hosts_url = add_query_arg( 'post_type', RADIO_STATION_HOST_SLUG, admin_url( 'edit.php' ) ); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '' . esc_html( __( 'Hosts', 'radio-station' ) ) . '' . "\n"; + echo '
    • ' . "\n"; + + // --- producers --- + $producers_url = add_query_arg( 'post_type', RADIO_STATION_PRODUCER_SLUG, admin_url( 'edit.php' ) ); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '' . esc_html( __( 'Producers', 'radio-station' ) ) . '' . "\n"; + echo '
    • ' . "\n"; + + } else { + + // --- pro feature message --- + echo '
    • ' . "\n"; + echo esc_html( __( 'Episodes, Host and Producer Profiles available in Pro.', 'radio-station' ) ); + echo '
    • ' . "\n"; + + // --- upgrade link --- + $upgrade_url = radio_station_get_upgrade_url(); + echo '
    • ' . "\n"; + echo '' . "\n"; + echo '' . esc_html( __( 'Go PRO', 'radio-station' ) ) . '' . "\n"; + echo '
    • ' . "\n"; + } + + echo '
    ' . "\n"; } // ------------------------- @@ -146,24 +251,35 @@ function radio_station_statistics_panel() { $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s AND post_status = 'publish' AND post_content = ''"; $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_SHOW_SLUG ), ARRAY_A ); if ( $results && is_array( $results ) && count( $results ) > 0 ) { - $show_empty_desc = count( $results ); + $show_desc_empty = count( $results ); } else { - $show_empty_desc = 0; + $show_desc_empty = 0; } // --- get override count --- - $override_count = 0; - $query = "SELECT ID,post_status FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; + $override_count = $specials_count = $override_desc_empty = 0; + $query = "SELECT ID,post_status,post_content FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_OVERRIDE_SLUG ), ARRAY_A ); if ( $results && is_array( $results ) && count( $results ) > 0 ) { foreach ( $results as $result ) { if ( 'publish' == $result['post_status'] ) { + + $override_count++; + + // TODO: check for post content ? + $linked_fields = get_post_meta( $result['ID'], 'linked_show_fields', true ); + if ( !isset( $linked_fields['show_content'] ) || !$linked_fields['show_content'] ) { + if ( '' == $result['post_content'] ) { + $override_desc_empty++; + } + } + // --- single overrides --- $overrides = get_post_meta( $result['ID'], 'show_override_sched', true ); if ( $overrides && is_array( $overrides ) ) { foreach ( $overrides as $override ) { if ( !isset( $override['disabled'] ) || ( 'yes' != $override['disabled'] ) ) { - $override_count++; + $specials_count++; } } } @@ -172,7 +288,7 @@ function radio_station_statistics_panel() { if ( $recurring_overrides && is_array( $recurring_overrides ) ) { foreach ( $recurring_overrides as $override ) { if ( !isset( $override['disabled'] ) || ( 'yes' != $override['disabled'] ) ) { - $override_count++; + $specials_count++; } } } @@ -230,8 +346,9 @@ function radio_station_statistics_panel() { // --- Shows --- $progress_items++; - $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + // $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + // $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $active_publish_percent = round( ( $active_count / $publish_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; echo '
    ' . "\n"; // --- progress icon --- @@ -239,7 +356,7 @@ function radio_station_statistics_panel() { if ( 0 == $show_count ) { echo 'progress-cross dashicons-dismiss'; $content_notdone++; - } elseif ( 100 == $publish_percent ) { + } elseif ( 100 == $active_publish_percent ) { echo 'progress-tick dashicons-yes-alt'; $content_progress++; } else { @@ -251,45 +368,46 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- show count --- - echo '' . esc_html( $show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + echo '' . esc_html( $publish_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); // --- show draft count --- $drafts_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); $drafts_link = add_query_arg( 'post_status', 'draft', $drafts_link ); if ( $draft_count > 0 ) { - echo ' '; - echo esc_html( $draft_count ) . esc_html( __( 'Drafts', 'radio-station' ) ) . ' '; - echo ''; - echo ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + echo ' (' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + echo ''; + echo esc_html( $draft_count ) . ' ' . esc_html( __( 'Drafts', 'radio-station' ) ); + echo ') '; } // --- show active percentage --- // TODO: filter show post link for inactive shows $inactive_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); $inactive_link = add_query_arg( 'post_status', 'inactive', $inactive_link ); - echo ' ' . esc_html( $active_percent ) . '% '; - if ( $active_percent < 100 ) { + echo ' ' . esc_html( __( 'with', 'radio-station' ) ) . ' '; + if ( $active_publish_percent < 100 ) { echo ''; } + echo ' ' . esc_html( $active_publish_percent ) . '% '; echo esc_html( __( 'Active', 'radio-station' ) ); - if ( $active_percent < 100 ) { + if ( $active_publish_percent < 100 ) { echo ''; } - echo '.'; + echo '.' . "\n"; - if ( 0 == $show_empty_desc ) { - echo ' 100% ' . esc_html( 'Description coverage', 'radio-station' ) . '.'; + /* if ( 0 == $show_desc_empty ) { + $desc_percent = 100; } else { - $empty_desc_percent = round( ( $show_empty_desc / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $empty_desc_percent = round( ( $show_desc_empty / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; $desc_percent = 100 - $empty_desc_percent; - echo ' ' . esc_html( $desc_percent ) . '% '; - // TODO: link to Show list - filtered by lack of description - // $show_desc_url = admin_url( '' ); - // echo ''; - echo esc_html( 'Description coverage', 'radio-station' ) . '.'; - // echo ''; } + echo ' ' . esc_html( $desc_percent ) . '% '; + // TODO: link to Show list - filtered by lack of description + // $show_desc_url = admin_url( '' ); + // echo ''; + echo esc_html( 'Description coverage', 'radio-station' ) . '.'; + // echo ''; */ echo '
    ' . "\n"; @@ -300,14 +418,77 @@ function radio_station_statistics_panel() { echo ''; echo ''; echo '
    ' . esc_html( __( 'Show', 'radio-station' ) ) . '
    '; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- Descriptions --- + $progress_items++; + + // --- check shows and override description percentage --- + if ( ( 0 == $show_desc_empty ) && ( 0 == $override_desc_empty ) ) { + $desc_percent = 100; + } else { + $empty_count = $show_desc_empty + $override_desc_empty; + $total_count = $show_count + $override_count; + $empty_desc_percent = round( ( $empty_count / $total_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $desc_percent = 100 - $empty_desc_percent; + } + + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); + if ( $override_count > 0 ) { + echo ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + echo '' . esc_html( $override_count ) . ''; + echo ' ' . esc_html( __( 'Overrides', 'radio-station' ) ); + } + + echo ' ' . esc_html( __( 'with',' radio-station' ) ) . ' '; + echo ' ' . esc_html( $desc_percent ) . '% '; + // TODO: link to Show list - filtered by lack of description + // $show_desc_url = admin_url( '' ); + // echo ''; + echo esc_html( 'Description coverage', 'radio-station' ) . '.'; + // echo ''; + + echo '
    ' . "\n"; + + // --- add show icon --- + $add_override_url = add_query_arg( 'post_type', RADIO_STATION_OVERRIDE_SLUG, admin_url( 'post-new.php' ) ); + echo ''; + echo ''; echo ''; + echo ''; + echo '
    ' . esc_html( __( 'Special', 'radio-station' ) ) . '
    '; + echo '
    ' . "\n"; - echo '
    '; - + echo '' . "\n"; + // --- Shifts --- $progress_items++; // TODO: calculate % schedule coverage! $schedule_percent = 100; // ??? + + echo '
    ' . "\n"; // --- progress icon --- @@ -330,10 +511,11 @@ function radio_station_statistics_panel() { echo '' . esc_html( $shift_count ) . ''; echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); - echo ' ' . esc_html( $override_count ) . ''; - echo ' ' . esc_html( __( 'Overrides', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + echo ' ' . esc_html( $specials_count ) . ''; + echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); // --- show active percentage --- + // TODO: add schedule editor link echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); echo '.' . "\n"; @@ -343,7 +525,7 @@ function radio_station_statistics_panel() { $add_shift_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); echo '' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; echo '' . "\n"; echo '
    ' . esc_html( __( 'Shift', 'radio-station' ) ) . '
    ' . "\n"; echo '
    ' . "\n"; @@ -352,7 +534,7 @@ function radio_station_statistics_panel() { // --- Hosts --- $progress_items++; - $host_percent = round( ( $shows_host_count / $active_show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $host_percent = round( ( $shows_host_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; echo '
    ' . "\n"; // --- progress icon --- @@ -395,7 +577,7 @@ function radio_station_statistics_panel() { // --- add host icon --- $add_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'users.php' ) ); - if ( defined( 'RADIO_STATION_PRO' ) ) { + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { $add_host_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); $add_host_url = add_query_arg( 'tab', 'roles', $add_host_url ); } @@ -410,7 +592,7 @@ function radio_station_statistics_panel() { // --- Genres --- $progress_items++; - $genre_percent = round( ( $genre_show_count / $active_show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $genre_percent = round( ( $genre_show_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; echo '
    ' . "\n"; // --- progress icon --- @@ -456,7 +638,7 @@ function radio_station_statistics_panel() { $add_genre_url = add_query_arg( 'taxonomy', RADIO_STATION_GENRES_SLUG, admin_url( 'edit-tags.php' ) ); echo '' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; echo '' . "\n"; echo '
    ' . esc_html( __( 'Genre', 'radio-station' ) ) . '
    ' . "\n"; echo '
    ' . "\n"; @@ -496,7 +678,7 @@ function radio_station_statistics_panel() { $add_playlist_url = add_query_arg( 'post_type', RADIO_STATION_PLAYLIST_SLUG, admin_url( 'post-new.php' ) ); echo '' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; echo '' . "\n"; echo '
    ' . esc_html( __( 'Playlist', 'radio-station' ) ) . '
    ' . "\n"; echo '
    ' . "\n"; @@ -531,7 +713,7 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- add episode icon --- - if ( defined( 'RADIO_STATION_PRO' ) ) { + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { $add_episode_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'post-new.php' ) ); echo '' . "\n"; @@ -641,13 +823,11 @@ function radio_station_settings_panel() { // --- Player Bar --- $progress_items++; - $settings_tab = 'player'; - $settings_section = 'bar'; echo '
    ' . "\n"; // --- progress icon --- echo '
    ' . "\n"; // --- player bar --- - if ( !defined( 'RADIO_STATION_PRO' ) ) { + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { echo esc_html( __( 'Player Bar with continuous playback available in Pro version.', 'radio-station' ) ); } else { + if ( 'top' == $settings['player_bar'] ) { + echo esc_html( __( 'Header', 'radio-station' ) ) . ' '; + } elseif ( 'bottom' == $settings['player_bar'] ) { + echo esc_html( __( 'Footer', 'radio-station' ) ) . ' '; + } echo esc_html( __( 'Player Bar', 'radio-station' ) ) . ' '; if ( 'off' == $settings['player_bar'] ) { echo esc_html( __( 'not enabled', 'radio-station' ) ) . '. '; - } elseif ( 'top' == $settings['player_bar'] ) { - echo esc_html( __( 'enabled at page top', 'radio-station' ) ) . '. '; - } elseif ( 'bottom' == $settings['player_bar'] ) { - echo esc_html( __( 'enabled at page bottom', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; } // --- continuous playback --- @@ -692,18 +875,20 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- fix setting icon --- - if ( !defined( 'RADIO_STATION_PRO' ) ) { + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { $upgrade_url = radio_station_get_upgrade_url(); echo '
    ' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; echo '' . "\n"; echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; echo '
    ' . "\n"; } else { + $settings_tab = 'player'; + $settings_section = 'bar'; if ( 'dashboard' == $context ) { $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); @@ -717,7 +902,7 @@ function radio_station_settings_panel() { echo ' onclick="jQuery(\'#' . esc_attr( $settings_tab ) . '-tab-button\').click();"'; } echo '>'; - echo ''; + echo ''; echo '' . "\n"; echo ''; if ( ( 'off' == $settings['player_bar'] ) || ( 'yes' != $settings['player_bar_continuous'] ) ) { @@ -732,8 +917,6 @@ function radio_station_settings_panel() { // --- Schedule Page --- $progress_items++; - $settings_tab = 'pages'; - $settings_section = 'schedule'; echo '
    ' . "\n"; // --- progress icon --- @@ -771,6 +954,8 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- fix setting icon --- + $settings_tab = 'pages'; + $settings_section = 'schedule'; if ( 'dashboard' == $context ) { $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); @@ -791,17 +976,17 @@ function radio_station_settings_panel() { } else { echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; } - echo '
    '; + echo '' . "\n"; - echo '
    '; + echo '
    ' . "\n"; // --- Station Info --- $progress_items++; - $infos = array( 'station_title', 'station_image', 'station_phone', 'station_email' ); + $infos = array( 'station_title', 'station_image', 'station_frequency', 'station_location', 'station_phone', 'station_email' ); $infos_count = count( $infos ); $info_count = 0; foreach ( $infos as $info ) { - if ( '' != $settings[$info] ) { + if ( isset( $settings[$info] ) && ( '' != $settings[$info] ) ) { $info_count++; } } @@ -814,7 +999,7 @@ function radio_station_settings_panel() { if ( 100 == $info_percent ) { echo 'progress-tick dashicons-yes-alt'; $settings_progress++; - } elseif ( $info_percent > 50 ) { + } elseif ( $info_percent > 49 ) { echo 'progress-alert dashicons-warning'; $settings_progress = $settings_progress + 0.5; } else { @@ -831,6 +1016,8 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- fix setting icon --- + $settings_tab = 'general'; + $settings_section = 'broadcast'; if ( 'dashboard' == $context ) { $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); @@ -843,7 +1030,7 @@ function radio_station_settings_panel() { echo ' onclick="jQuery(\'#' . esc_attr( $settings_tab ) . '-tab-button\').click();"'; } echo '>'; - echo ''; + echo ''; echo ''; echo ''; if ( 100 == $info_percent ) { @@ -858,11 +1045,9 @@ function radio_station_settings_panel() { // --- Archive Pages --- $progress_items++; - $settings_tab = 'archives'; - $settings_section = 'post-types'; $archives = array( 'show', 'override', 'playlist', 'genre', 'language' ); $pro_archives = array( 'episode', 'team' ); - if ( defined( 'RADIO_STATION_PRO' ) ) { + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { $archives = array_merge( $archives, $pro_archives ); } $archives_count = count( $archives ); @@ -882,7 +1067,7 @@ function radio_station_settings_panel() { if ( 100 == $archive_percent ) { echo 'progress-tick dashicons-yes-alt'; $settings_progress++; - } elseif ( $archive_percent > 50 ) { + } elseif ( $archive_percent > 49 ) { echo 'progress-alert dashicons-warning'; $settings_progress = $settings_progress + 0.5; } else { @@ -900,6 +1085,8 @@ function radio_station_settings_panel() { echo '' . "\n"; // --- fix setting icon --- + $settings_tab = 'archives'; + $settings_section = 'post-types'; if ( 'dashboard' == $context ) { $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); @@ -913,19 +1100,19 @@ function radio_station_settings_panel() { } echo '>'; echo ''; - echo ''; + echo '' . "\n"; echo ''; if ( 100 == $archive_percent ) { - echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    ' . "\n"; } else { - echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    ' . "\n"; } - echo '
    '; + echo '' . "\n"; - echo ''; + echo '' . "\n"; // --- Pro Archives Pages --- - if ( !defined( 'RADIO_STATION_PRO' ) ) { + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { $progress_items++; echo '
    ' . "\n"; @@ -934,25 +1121,23 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; echo '
    ' . "\n"; - echo esc_html( __( 'Episode and Team Archive pages available in Pro version.' ) ); + echo esc_html( __( 'Episode and Team Archive pages available in Pro version.', 'radio-station' ) ); echo '
    ' . "\n"; // --- upgrade to Pro / feature link --- $upgrade_url = radio_station_get_upgrade_url(); echo '' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; echo '' . "\n"; echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; echo '
    ' . "\n"; - echo '
    '; + echo '' . "\n"; } - // ... - // --- settings progress bar --- radio_station_progress_bar( $settings_progress, $settings_notdone, $progress_items ); @@ -970,14 +1155,16 @@ function radio_station_progress_bar( $done, $undone, $items ) { $undone_px = round( ( $undone_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); $remainder_percent = 100 - $progress_percent - $undone_percent; $remainder_px = round( ( $remainder_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); - echo '
    ' . "\n"; echo ' ' . PHP_EOL; @@ -3381,53 +3426,57 @@ function radio_station_override_show_metabox() { echo '
    ' . "\n"; // --- inside show metaboxes --- - $inside_metaboxes = array( - 'hosts' => array( + $inside_metaboxes = array(); + // 2.5.18: add permissions check for changing hosts / producers + if ( ( $author_id == $current_user_id ) || current_user_can( 'edit_others_overrides' ) ) { + $inside_metaboxes['hosts'] = array( 'title' => __( 'Override DJ(s) / Host(s)', 'radio-station' ), 'callback' => 'radio_station_show_hosts_metabox', - ), - 'producers' => array( + ); + $inside_metaboxes['producers'] = array( 'title' => __( 'Override Producer(s)', 'radio-station' ), 'callback' => 'radio_station_show_producers_metabox', - ), - ); + ); + } // --- display inside metaboxes --- - echo '
    '; - $i = 1; - foreach ( $inside_metaboxes as $key => $metabox ) { - - $classes = array( 'postbox' ); - if ( 1 == $i ) { - $classes[] = 'first'; - } elseif ( count( $inside_metaboxes ) == $i ) { - $classes[] = 'last'; - } - $class = implode( ' ', $classes ); + if ( count( $inside_metaboxes ) > 0 ) { + echo '
    '; + $i = 1; + foreach ( $inside_metaboxes as $key => $metabox ) { - // --- open inside metabox - echo '' . "\n"; + $i++; + } echo '
    ' . "\n"; - $i++; } - echo '
    ' . "\n"; // --- input list field styles --- // 2.3.3.9: add styles for table to list conversion @@ -3717,6 +3766,7 @@ function radio_station_overrides_table( $post_id ) { // --- set empty override time array --- $times = array( + 'channel' => 0, 'date' => '', 'start_hour' => '', 'start_min' => '', @@ -3731,6 +3781,13 @@ function radio_station_overrides_table( $post_id ) { // --- check and sanitize possibly posted times --- // 2.3.3.9: for adding new override time by querystring + // 2.5.18: add channel ID key/value + if ( isset( $_REQUEST['channel'] ) ) { + $times['channel'] = absint( $_REQUEST['channel'] ); + if ( $times['channel'] < 0 ) { + $times['channel'] = 0; + } + } if ( isset( $_REQUEST['date'] ) ) { $times['date'] = sanitize_text_field( $_REQUEST['date'] ); // 2.5.6: use radio_station_get_time instead of date @@ -3790,6 +3847,9 @@ function radio_station_overrides_table( $post_id ) { $overrides = array( $times ); } + // 2.5.18: add filter for channel options + $channels = apply_filters( 'radio_station_channel_options', array() ); + // 2.3.3.9: loop possible multiple overrides $list = ''; foreach ( $overrides as $i => $override ) { @@ -3803,8 +3863,30 @@ function radio_station_overrides_table( $post_id ) { // 2.3.3.9: add hidden input for unique override time ID $list .= '' . "\n"; + // --- Override Channel --- + // 2.5.18: added for multichannel support + if ( count( $channels ) > 0 ) { + $list .= '
  • ' . "\n"; + $list .= ': ' . "\n"; + $class = ( '' == $override['channel'] ) ? 'incomplete' : ''; + $list .= '' . "\n"; + $list .= '' . "\n"; + $list .= '
  • ' . "\n"; + } else { + $list .= '' . "\n"; + } + // --- Override (Start) Date --- - $list .= '
  • ' . "\n"; + $list .= '
  • ' . "\n"; $list .= ': '; $date = ( !empty( $override['date'] ) ) ? trim( $override['date'] ) : ''; @@ -4113,9 +4195,10 @@ function radio_station_override_edit_script() { // --- add new override --- // 2.5.0: shorten value object // 2.5.6: use radio_station_get_time instead of date + // 2.5.18: add channel ID to values $todate = radio_station_get_time( 'Y-m-d', time() ); $js .= "function radio_override_new() { - values = {date:'" . esc_js( $todate ) . "', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', multiday:'', end_date:'', disabled:''}; + values = {channel:0, date:'" . esc_js( $todate ) . "', start_hour:'', start_min:'', start_meridian:'', end_hour:'', end_min:'', end_meridian:'', multiday:'', end_date:'', disabled:''}; radio_override_add(values); }" . "\n"; @@ -4128,10 +4211,12 @@ function radio_station_override_edit_script() { }" . "\n"; // --- duplicate shift --- + // 2.5.18: add channel id to values $js .= "function radio_override_duplicate(el) { overrideid = el.id.replace('override-','').replace('-duplicate',''); console.log('Override ID: '+overrideid); values = {}; + values.channel = jQuery('#override-'+overrideid+'-channel').val(); values.date = jQuery('#override-'+overrideid+'-date').val(); values.start_hour = jQuery('#override-'+overrideid+'-start-hour').val(); values.start_min = jQuery('#override-'+overrideid+'-start-min').val(); @@ -4139,10 +4224,8 @@ function radio_station_override_edit_script() { values.end_hour = jQuery('#override-'+overrideid+'-end-hour').val(); values.end_min = jQuery('#override-'+overrideid+'-end-min').val(); values.end_meridian = jQuery('#override-'+overrideid+'-end-meridian').val(); - values.multiday = ''; - /* if (jQuery('#override-'+overrideid+'-multiday').prop('checked')) {values.multiday = 'yes';} */ - values.end_date = ''; - /* values.end_date = jQuery('#override-'+overrideid+'-end-date'); */ + /* values.multiday = ''; if (jQuery('#override-'+overrideid+'-multiday').prop('checked')) {values.multiday = 'yes';} + values.end_date = ''; values.end_date = jQuery('#override-'+overrideid+'-end-date'); */ values.disabled = 'yes'; radio_override_add(values); }" . "\n"; @@ -4153,8 +4236,29 @@ function radio_station_override_edit_script() { $js .= "output = '
    ';" . "\n"; $js .= "output += '
      ';" . "\n"; + // --- channel selection --- + $channels = apply_filters( 'radio_station_channel_options', array() ); + if ( count( $channels ) > 0 ) { + $js .= "output += '
    • ';" . "\n"; + $js .= "output += ': ';" . "\n"; + $js .= "output += ''" . "\n"; + $js .= "output += '
    • ';" . "\n"; + } else { + $js .= "output += '';" . "\n"; + } + // --- start date --- - $js .= "output += '
    • ';" . "\n"; + $js .= "output += '
    • ';" . "\n"; $js .= "output += '" . esc_js( __( 'Start Date', 'radio-station' ) ) . ": ';" . "\n"; $js .= "output += '';" . "\n"; $js .= "output += '
    • ';" . "\n"; @@ -4267,8 +4371,8 @@ function radio_station_override_edit_script() { // --- append new override list item --- $js .= "jQuery('#new-overrides').append(output);" . "\n"; - $js .= "jQuery('#override-new-' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; - // $js .= "jQuery('#override-new-' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + $js .= "jQuery('#override-new-'+count+'-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; + // $js .= "jQuery('#override-new-'+count+'-end-date').datepicker({dateFormat : 'yy-mm-dd'});" . "\n"; $js .= "return false;" . "\n"; $js .= "}" . "\n"; @@ -4414,7 +4518,8 @@ function radio_station_override_save_data( $post_id ) { // --- update linked show fields --- $linked_show_fields = array(); $show_fields = array( 'title', 'content', 'excerpt', 'avatar', 'image', 'user_list', 'producer_list', 'link', 'email', 'phone', 'file', 'download', 'patreon' ); - $non_input_fields = array( 'show_title', 'show_content', 'show_excerpt' ); + // 2.5.18: added missing show_avatar and show_image non-input fields + $non_input_fields = array( 'show_title', 'show_content', 'show_excerpt', 'show_avatar', 'show_image' ); foreach ( $show_fields as $field ) { $show_field = 'show_' . $field; $linked = false; @@ -4465,6 +4570,7 @@ function radio_station_override_save_data( $post_id ) { // 2.2.2: added to set default keys // 2.3.3.9: just set new schedule array here $new_sched = array( + 'channel' => 0, 'id' => '', 'date' => '', 'start_hour' => '', @@ -4486,7 +4592,13 @@ function radio_station_override_save_data( $post_id ) { $isvalid = false; // --- validate according to key --- - if ( ( 'date' === $key ) || ( 'end_date' == $key ) ) { + // 2.5.18: add channel key/value + if ( 'channel' == $key ) { + $channel = absint( $value ); + if ( $channel > -1 ) { + $isvalid = true; + } + } elseif ( ( 'date' === $key ) || ( 'end_date' == $key ) ) { // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) $parts = explode( '-', $value ); // 2.3.3.9: added extra check for date parts @@ -4621,6 +4733,9 @@ function radio_station_override_save_data( $post_id ) { if ( $sched_changed ) { update_post_meta( $post_id, 'show_override_sched', $new_scheds ); + // 2.5.18: add filter for override saving + $new_scheds = apply_filters( 'radio_station_save_override_shifts', $new_scheds, $post_id ); + // 2.3.3.9: clear out old unique shift IDs from prev_scheds // 2.5.6: added isset check for prev_scheds $new_ids = array(); @@ -5689,7 +5804,7 @@ function radio_station_playlist_show_metabox() { 'orderby' => 'post_title', 'order' => 'ASC', 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => array( 'publish', 'draft' ), + 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), 'include' => implode( ',', $allowed_shows ), ); @@ -5719,10 +5834,19 @@ function radio_station_playlist_show_metabox() { $metabox .= '
      ' . "\n"; $metabox .= '' . esc_html( __( 'Assign to Show', 'radio-station' ) ) . '
      ' . "\n"; $metabox .= '' . "\n"; $metabox .= '

      ' . "\n"; @@ -5842,7 +5966,8 @@ function radio_station_playlist_show_shift_select( $show_id, $current_shift ) { $select .= '' . esc_html( __( 'Assign to Show Shift', 'radio-station' ) ) . '
      ' . "\n"; $select .= '' . "\n"; echo '' . "\n"; - foreach ( $shows as $show ) { + + // 2.5.18: use show select options function + $allowed = radio_station_allowed_html( 'content', 'settings' ); + $html = radio_station_show_select_options( 'playlist', $selected ); + echo wp_kses( $html, $allowed ); + + /* foreach ( $shows as $show ) { $post = $show; echo '' . "\n"; - } + } */ + echo '' . "\n"; - } else { + // } else { // --- no shows message --- - echo esc_html( __( 'No Shows available to Select.', 'radio-station' ) ) . "\n"; - } + // echo esc_html( __( 'No Shows available to Select.', 'radio-station' ) ) . "\n"; + // } echo '' . "\n"; echo '
    ' . "\n"; echo '' . "\n"; @@ -6278,7 +6410,7 @@ function radio_station_quick_edit_playlist( $column_name, $post_type ) { radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: restore stored post object - $post = $stored_post; + // $post = $stored_post; // --- set flag to prevent duplication --- $radio_station_data['playlist-quick-edit'] = true; @@ -6376,7 +6508,7 @@ function radio_station_post_show_metabox() { // 2.3.3.6: store current post global global $post; - $stored_post = $post; + // $stored_post = $post; // 2.3.3.9: filter meta key according to post type $post_type = $post->post_type; @@ -6386,15 +6518,15 @@ function radio_station_post_show_metabox() { wp_nonce_field( 'radio-station', 'post_show_nonce' ); // 2.3.3.9: allow for post assignment to draft Shows - $args = array( + /* $args = array( 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => array( 'publish', 'draft' ), + 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), ); - $shows = get_posts( $args ); + $shows = get_posts( $args ); */ // --- get current selection --- // 2.3.3.4: convert possible existing selection to array @@ -6418,16 +6550,31 @@ function radio_station_post_show_metabox() { // 2.3.3.9: move meta_inner ID to class echo '
    ' . "\n"; - if ( count( $shows ) > 0 ) { + // if ( count( $shows ) > 0 ) { // --- select related show input --- // 2.2.3.4: allow for multiple selections // 2.3.3.9: use metakey for post type - echo '' . "\n"; echo '' . "\n"; + // --- loop shows for selection options --- + // 2.5.18: group linked show options by day and status + $allowed = radio_station_allowed_html( 'content', 'settings' ); + $html = radio_station_show_select_options( 'post', $selected ); + echo wp_kses( $html, $allowed ); + // --- loop shows for selection options --- // 2.3.3.4: check for multiple selections + /* $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), + ); + $shows = get_posts( $args ); foreach ( $shows as $show ) { // 2.3.3.6: check capability of user to edit each Show @@ -6450,12 +6597,13 @@ function radio_station_post_show_metabox() { echo ' (' . esc_html( __( 'Draft', 'radio-station' ) ) . ')'; } echo '' . "\n"; - } + } */ + echo '' . "\n"; - } else { + // } else { // --- no shows message --- - echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ) . "\n"; - } + // echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ) . "\n"; + // } echo '
    ' . "\n"; @@ -6464,13 +6612,15 @@ function radio_station_post_show_metabox() { // 2.5.0: added missing style filter // 2.5.0: use wp_kses_post on style output // 2.5.6: use radio_station_add_inline_style - $css = ".pre-selected {background-color:#BBB;}"; + // 2.5.18L add extra height to show select + $css = ".pre-selected {background-color:#BBB;}" . "\n"; + $css .= ".inside select.show-select {min-height: 150px;"; $css = apply_filters( 'radio_station_post_show_box_styles', $css ); // echo '' . "\n"; radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: revert current post global - $post = $stored_post; + // $post = $stored_post; } // -------------------- @@ -6571,7 +6721,7 @@ function radio_station_post_save_data( $post_id ) { function radio_station_quick_edit_post( $column_name, $post_type ) { global $post, $radio_station_data; - $stored_post = $post; + // $stored_post = $post; // 2.3.3.5: added fix for post type context if ( 'post' != $post_type ) { @@ -6585,7 +6735,7 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { // --- get all shows --- // 2.3.3.9: allow selection shows with draft status - $args = array( + /* $args = array( 'numberposts' => -1, 'offset' => 0, 'orderby' => 'post_title', @@ -6593,16 +6743,43 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { 'post_type' => RADIO_STATION_SHOW_SLUG, 'post_status' => array( 'publish', 'draft' ), ); - $shows = get_posts( $args ); + $shows = get_posts( $args ); */ + + // 2.3.3.9: filter meta key according to post type + $post_type = $post->post_type; + $metakey = apply_filters( 'radio_station_related_show_meta_key', 'post_showblog_id', $post_type ); + + $selected = get_post_meta( $post->ID, $metakey, true ); + if ( !$selected ) { + $selected = array(); + } elseif ( !is_array( $selected ) ) { + $selected = array( $selected ); + } + + // 2.3.3.6: remove possible saved zero value + // 2.3.3.9: fix to duplicate use of selected variable + if ( count( $selected ) > 0 ) { + foreach ( $selected as $i => $value ) { + if ( 0 == $value ) { + unset( $selected[$i] ); + } + } + } echo '' . "\n"; @@ -6629,7 +6806,7 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { radio_station_add_inline_style( 'rs-admin', $css ); // 2.3.3.6: restore stored post object - $post = $stored_post; + // $post = $stored_post; // 2.4.0.6: add fix for duplicate related show box $radio_station_data['related-post-quick-edit'] = true; @@ -6941,7 +7118,7 @@ function radio_station_posts_list_styles() { // --- post list styles --- // 2.3.3.9: fix to column-show type (oclumn-show) $css = ".wp-list-table .posts .column-show {max-width: 100px;} - .inline-edit-col .select-show {min-width: 200px; min-height: 100px;} + .inline-edit-col .select-show {min-width: 200px; min-height: 140px;} .bulkactions .column-show .title {display: none;}"; // 2.3.3.9: added posts list styles filter @@ -7185,3 +7362,171 @@ function radio_station_relogin_message() { exit; } +// -------------------------- +// Show Select Options output +// -------------------------- +function radio_station_show_select_options( $type, $selected, $shows = false ) { + + global $radio_show_select_options, $post; + if ( isset( $radio_show_select_options ) ) { + return $radio_show_select_options; + } + $stored_post = $post; + + // --- check for full permissions --- + // 2.5.18: changed to check permissions based on type + $user = wp_get_current_user(); + $full_permissions = false; + if ( in_array( 'administrator', $user->roles ) || in_array( 'show-editor', $user->roles ) ) { + $full_permissions = true; + } elseif ( current_user_can( 'edit_others_' . $type . 's' ) ) { + $full_permissions = true; + } + + // --- get all shows --- + // 2.4.0.4: fix to remove show limit in query + // 2.4.0.4: added pending and future post statuses + if ( !$shows ) { + $args = array( + 'numberposts' => -1, + 'offset' => 0, + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), + 'orderby' => 'post_title', + 'order' => 'ASC', + ); + $shows = get_posts( $args ); + } + + // --- loop shows to check shifts --- + $html = ''; + $sundays = $mondays = $tuesdays = $wednesdays = $thursdays = $fridays = $saturdays = $shiftless = $inactives = array(); + foreach ( $shows as $i => $show ) { + $show_id = $show->ID; + $active = get_post_meta( $show_id, 'show_active', true ); + if ( 'on' != $active ) { + $inactives[$show_id] =array( 'show' => $show, 'shift' => $shift, 'inactive' => true ); + } else { + $found = false; + $shifts = get_post_meta( $show_id, 'show_sched', true ); + foreach ( $shifts as $shift_id => $shift ) { + if ( !isset( $shift['disabled'] ) || ( 'yes' != $shift['disabled'] ) ) { + $time = intval( $shift['start_hour'] . $shift['start_min'] ); + if ( 'pm' == $shift['start_meridian'] ) { + $time = $time + 1200; + } + if ( ( 'Sunday' == $shift['day'] ) && !array_key_exists( $show_id, $sundays ) ) { + $sundays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Monday' == $shift['day'] ) && !array_key_exists( $show_id, $mondays ) ) { + $mondays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Tuesday' == $shift['day'] ) && !array_key_exists( $show_id, $tuesdays ) ) { + $tuesdays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Wednesday' == $shift['day'] ) && !array_key_exists( $show_id, $wednesdays ) ) { + $wednesdays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Thursday' == $shift['day'] ) && !array_key_exists( $show_id, $thursdays ) ) { + $thursdays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Friday' == $shift['day'] ) && !array_key_exists( $show_id, $fridays ) ) { + $fridays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } elseif ( ( 'Saturday' == $shift['day'] ) && !array_key_exists( $show_id, $saturdays ) ) { + $saturdays[$time] = array( 'show' => $show, 'shift' => $shift ); + $found = true; + } + } + } + if ( !$found ) { + $shiftless[$show_id] = array( 'show' => $show, 'shift' => '' ); + } + } + } + + // --- sort groups by times --- + ksort( $sundays ); + ksort( $mondays ); + ksort( $tuesdays ); + ksort( $wednesdays ); + ksort( $thursdays ); + ksort( $fridays ); + ksort( $saturdays ); + ksort( $shiftless ); + ksort( $inactives ); + + // --- loop groups to get options --- + $groups = array( + __( 'Sunday', 'radio-station' ) => $sundays, + __( 'Monday', 'radio-station' ) => $mondays, + __( 'Tuesday', 'radio-station' ) => $tuesdays, + __( 'Wednesday', 'radio-station' ) => $wednesdays, + __( 'Thursday', 'radio-station' ) => $thursdays, + __( 'Friday', 'radio-station' ) => $fridays, + __( 'Saturday', 'radio-station' ) => $saturdays, + __( 'Not Scheduled', 'radio-station' ) => $shiftless, + __( 'Inactive', 'radio-station' ) => $inactives, + ); + foreach ( $groups as $label => $shows ) { + $html .= '' . "\n"; + if ( count( $shows ) > 0 ) { + foreach ( $shows as $time => $show_data ) { + + $show = $show_data['show']; + $show_id = $show->ID; + $shift = $show_data['shift']; + $title = $show->post_title; + $status = $show->post_status; + + // 2.3.3.6: check capability of user to edit each Show + // (override global post object temporarily to do this) + $post = $show; + + // 2.5.18: check for permission on individual show + $permission = $full_permissions ? true : current_user_can( 'edit_show', $show_id ); + + // if ( ( 'override' != $type ) || $permission ) { + $html .= '
  • ' . "\n"; - echo '' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - if ( $undone_percent > 0 ) { - echo '' . "\n"; - } - echo '' . esc_html( $progress_percent ) . '%
    ' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + if ( $undone_percent > 0 ) { + echo '' . "\n"; + } + echo '' . esc_html( $progress_percent ) . '%
    ' . "\n"; } // ------------------------- @@ -987,7 +1174,7 @@ function radio_station_progress_bar( $done, $undone, $items ) { function radio_station_enqueue_onboarding_styles() { global $current_screen; - echo 'Current Screen' . print_r( $current_screen, true ) . ''; + // echo 'Current Screen' . print_r( $current_screen, true ) . ''; // --- add onboarding styles --- if ( ( 'dashboard' == $current_screen->base ) @@ -1002,9 +1189,9 @@ function radio_station_enqueue_onboarding_styles() { // Onboarding Styles // ----------------- function radio_station_onboarding_styles() { - $css = ".progress-heading {text-align:center; font-size:15px; font-variant:small-caps; letter-spacing:5px; margin-bottom:5px;} + $css = ".progress-heading {text-align:center; font-size:16px; font-variant:small-caps; letter-spacing:3px; margin-bottom:5px;} .progress-list {margin-bottom:7px;} - .progress-list-item {display:inline-block; margin-right:25px;} + .progress-list-item {display:inline-block; margin-right:20px;} .progress-list-item:last-child {margin-right:0;} .progress-list-item a {text-decoration:none;} .progress-list-item a:hover {text-decoration:underline; font-weight:bold;} @@ -1014,7 +1201,7 @@ function radio_station_onboarding_styles() { /* .progress-bar, .progress-bar-done, .progress-bar-remainder, .progress-bar-label {display:inline-block; vertical-align:middle;} */ .progress-bar-done {background-color:#00AA77; height:7px; border:1px solid #555; border-right:0;} .progress-bar-remainder {background-color:#FF9900; height:7px; border:1px solid #555;} - .progress-bar-undone {background-color:#EE0000; height:7px; border:1px solid #555; border-left:0; } + .progress-bar-undone {background-color:#EE0000; height:7px; border:1px solid #555; border-left:0;} .progress-count {font-weight:bold; font-size:14px; background-color:#CCC; border-radius:10px;} .progress-icon {width:25px; height:25px;} .progress-icon.progress-tick, .progress-icon.progress-quickstart {color:#00AA00;} @@ -1024,11 +1211,13 @@ function radio_station_onboarding_styles() { .progress-icon.progress-upgrade {color:#AA00AA; font-size:22px;} .progress-icon.progress-docs {color:#CC7700;} .progress-text {width:350px;} - .progress-icon, .progress-text, .progress-icon-link, .progress-label-link {display:inline-block; vertical-align:middle;} + .progress-icon, .progress-text, .progress-icon-link, .progress-label-link {display:inline-block; vertical-align:middle; line-height:25px;} + .progress-label-link {margin-left:5px; margin-top:5px; width:40px; text-align:center;} .progress-text a, .progress-icon-link, .progress-label-link {text-decoration:none;} .progress-text a:hover, .progress-label-link:hover {text-decoration:underline;} .progress-label {font-size:11px; margin-top:-5px;} "; return $css; -} \ No newline at end of file +} + diff --git a/includes/support-functions.php b/includes/support-functions.php index 1139561..c7e6271 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1858,7 +1858,8 @@ function radio_station_get_languages() { // -------------------- // Get Language Options // -------------------- -function radio_station_get_language_options( $include_wp_default = false ) { +// 2.5.18: add admin argument for optional translation +function radio_station_get_language_options( $include_wp_default = false, $admin = false ) { // --- maybe get stored timezone options --- $languages = get_transient( 'radio-station-language-options' ); @@ -1877,7 +1878,7 @@ function radio_station_get_language_options( $include_wp_default = false ) { // --- maybe include WordPress default language --- if ( $include_wp_default ) { // 2.3.3.6: fix to array for WordPress language setting - $wp_language = array( '' => __( 'WordPress Setting', 'radio-station' ) ); + $wp_language = array( '' => $admin ? __( 'WordPress Setting', 'radio-station' ) : '' ); $languages = array_merge( $wp_language, $languages ); } diff --git a/includes/times.php b/includes/times.php index 9c709f2..7744354 100644 --- a/includes/times.php +++ b/includes/times.php @@ -131,7 +131,8 @@ function radio_station_get_timezone_code( $timezone ) { // Get Timezone Options // -------------------- // ref: (based on) https://stackoverflow.com/a/17355238/5240159 -function radio_station_get_timezone_options( $include_wp_timezone = false ) { +// 2.5.18: add admin argument for optional translation +function radio_station_get_timezone_options( $include_wp_timezone = false, $admin = false ) { global $rs_se; @@ -145,7 +146,8 @@ function radio_station_get_timezone_options( $include_wp_timezone = false ) { // --- maybe add WordPress timezone (default) option --- if ( $include_wp_timezone ) { - $wp_timezone = array( '' => __( 'WordPress Timezone', 'radio-station' ) ); + // 2.5.18: add operator for optional translation + $wp_timezone = array( '' => $admin ? __( 'WordPress Timezone', 'radio-station' ) : '' ); $options = array_merge( $wp_timezone, $options ); } diff --git a/includes/user-roles.php b/includes/user-roles.php index fe45e4c..0c75a7e 100644 --- a/includes/user-roles.php +++ b/includes/user-roles.php @@ -349,60 +349,59 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { } } - // --- show capabilities check --- - // 2.5.18: moved check for show slug outside - if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { - - // --- get roles with edit shows capability --- - $edit_show_roles = $edit_others_shows_roles = array(); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach ( $wp_roles->roles as $name => $role ) { - // 2.3.0: fix to skip roles with no capabilities assigned - if ( isset( $role['capabilities'] ) ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - // 2.3.0: change publish_shows cap check to edit_shows - if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_show_roles ) ) { - $edit_show_roles[] = $name; + // 2.3.0: added object and property_exists check to be safe + if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + + // --- show capabilities check --- + // 2.5.18: moved check for show slug outside + if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { + + // --- get roles with edit shows capability --- + $edit_show_roles = $edit_others_shows_roles = array(); + if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { + foreach ( $wp_roles->roles as $name => $role ) { + // 2.3.0: fix to skip roles with no capabilities assigned + if ( isset( $role['capabilities'] ) ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + // 2.3.0: change publish_shows cap check to edit_shows + if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_show_roles ) ) { + $edit_show_roles[] = $name; + } } - } - // 2.3.3.6: add check for edit_others_shows capability - if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_others_shows_roles ) ) { - $edit_others_shows_roles[] = $name; + // 2.3.3.6: add check for edit_others_shows capability + if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_others_shows_roles ) ) { + $edit_others_shows_roles[] = $name; + } } } } } } - } - // 2.5.18: fix to move false before edit others show check - $found = false; - // 2.3.3.6: preserve if user has edit_others_shows capability - foreach ( $edit_others_shows_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - // 2.4.0.4: do not automatically assume capability match - // return $allcaps; - $found = true; - } - } - if ( !$found ) { - // 2.2.8: remove strict in_array checking - foreach ( $edit_show_roles as $role ) { + // 2.5.18: fix to move false before edit others show check + $found = false; + // 2.3.3.6: preserve if user has edit_others_shows capability + foreach ( $edit_others_shows_roles as $role ) { if ( in_array( $role, $user->roles ) ) { + // 2.4.0.4: do not automatically assume capability match + // return $allcaps; $found = true; } } - } - - // --- maybe revoke edit show capability for post --- - // 2.3.3.6: fix to incorrect logic for removing edit show capability - if ( $found ) { + if ( !$found ) { + // 2.2.8: remove strict in_array checking + foreach ( $edit_show_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + $found = true; + } + } + } - // --- limit this to published shows --- - // 2.3.0: added object and property_exists check to be safe - if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + // --- maybe revoke edit show capability for post --- + // 2.3.3.6: fix to incorrect logic for removing edit show capability + if ( $found ) { // 2.3.0: removed is_admin check (so works with frontend edit show link) @@ -449,55 +448,51 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { } } - } - } + } - // --- override capabilites check --- - // 2.5.18: added override post type handling - if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - - // --- get roles with edit shows capability --- - $edit_show_roles = $edit_others_shows_roles = array(); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach ( $wp_roles->roles as $name => $role ) { - if ( isset( $role['capabilities'] ) ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - if ( ( 'edit_overrides' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_override_roles ) ) { - $edit_override_roles[] = $name; + // --- override capabilites check --- + // 2.5.18: added override post type handling + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + + // --- get roles with edit shows capability --- + $edit_show_roles = $edit_others_shows_roles = array(); + if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { + foreach ( $wp_roles->roles as $name => $role ) { + if ( isset( $role['capabilities'] ) ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + if ( ( 'edit_overrides' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_override_roles ) ) { + $edit_override_roles[] = $name; + } } - } - if ( ( 'edit_others_overrides' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_others_overrides_roles ) ) { - $edit_others_overrides_roles[] = $name; + if ( ( 'edit_others_overrides' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_others_overrides_roles ) ) { + $edit_others_overrides_roles[] = $name; + } } } } } } - } - // --- check if capability found --- - $found = false; - foreach ( $edit_others_overrides_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - $found = true; - } - } - if ( !$found ) { - foreach ( $edit_override_roles as $role ) { + // --- check if capability found --- + $found = false; + foreach ( $edit_others_overrides_roles as $role ) { if ( in_array( $role, $user->roles ) ) { $found = true; } } - } - - // --- maybe revoke edit override capability for post --- - if ( $found ) { + if ( !$found ) { + foreach ( $edit_override_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + $found = true; + } + } + } - // --- limit this to published shows --- - if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + // --- maybe revoke edit override capability for post --- + if ( $found ) { $show_id = get_post_meta( $post->ID, 'linked_show_id', true ); $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); @@ -548,8 +543,8 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { } } - } + } } return $allcaps; diff --git a/loader.php b/loader.php index 5f28cf9..dc367ef 100644 --- a/loader.php +++ b/loader.php @@ -187,17 +187,6 @@ public function __construct( $args ) { // --- set plugin options --- // 1.0.6: added options filter $args['options'] = apply_filters( $args['namespace'] . '_options', $args['options'] ); - // 1.0.9: maybe get tabs and sections from options array - if ( isset( $args['options']['tabs'] ) ) { - $this->tabs = $args['options']['tabs']; - unset( $args['options']['tabs'] ); - } - if ( isset( $args['options']['sections'] ) ) { - $this->sections = $args['options']['sections']; - unset( $args['options']['sections'] ); - } - $this->options = $args['options']; - unset( $args['options'] ); // --- set plugin args and namespace --- // 1.1.9: filter all arguments @@ -224,6 +213,32 @@ public function __construct( $args ) { $GLOBALS[$args['namespace'] . '_instance'] = $this; } + // ------------- + // Admin Options + // ------------- + // 1.3.7: added method for loading delayed translation strings + function admin_options() { + + $namespace = $this->namespace; + $args = $this->args; + $args = apply_filters( $args['namespace'] . '_admin_args', $args ); + $this->args = $args; + + $options = $this->options; + $options = apply_filters( $namespace . '_options', $options ); + + // 1.0.9: maybe get tabs and sections from options array + if ( isset( $options['tabs'] ) ) { + $this->tabs = $options['tabs']; + unset( $options['tabs'] ); + } + if ( isset( $options['sections'] ) ) { + $this->sections = $options['sections']; + unset( $options['sections'] ); + } + $this->options = $options; + } + // ------------ // Setup Plugin // ------------ @@ -347,7 +362,7 @@ public function default_settings( $dkey = false ) { // 1.1.2: fix to apply options filter $namespace = $this->namespace; $options = $this->options; - $options = apply_filters( $namespace . '_options', $options ); + $options = apply_filters( $namespace . '_plugin_options', $options ); $defaults = array(); foreach ( $options as $key => $values ) { // 1.0.9: set default to null if default value not set @@ -1326,6 +1341,9 @@ public function add_actions() { // --- add settings on activation --- register_activation_hook( $args['file'], array( $this, 'add_settings' ) ); + // 1.3.7: added for admin options filtering + add_action( 'init', array( $this, 'admin_options' ) ); + // --- always check for update and reset of settings --- add_action( 'admin_init', array( $this, 'update_settings' ) ); add_action( 'admin_init', array( $this, 'reset_settings' ) ); @@ -1781,20 +1799,22 @@ public function enqueue_resources() { // --- get plugin options and default settings --- // 1.1.2: fix for filtering of plugin options $options = $this->options; - $options = apply_filters( $namespace . '_options', $options ); + $options = apply_filters( $namespace . '_plugin_options', $options ); // --- maybe enqueue media scripts --- // 1.1.7: added media gallery script enqueueing for image field // 1.1.7: added color picker and color picker alpha script enqueueing $enqueued_media = $enqueued_color_picker = $enqueue_color_picker = $enqueue_color_picker_alpha = false; foreach ( $options as $option ) { - if ( ( 'image' == $option['type'] ) && !$enqueued_media ) { - wp_enqueue_media(); - $enqueued_media = true; - } elseif ( 'color' == $option['type'] ) { - $enqueue_color_picker = true; - } elseif ( 'coloralpha' == $option['type'] ) { - $enqueue_color_picker_alpha = true; + if ( isset( $option['type'] ) ) { + if ( ( 'image' == $option['type'] ) && !$enqueued_media ) { + wp_enqueue_media(); + $enqueued_media = true; + } elseif ( 'color' == $option['type'] ) { + $enqueue_color_picker = true; + } elseif ( 'coloralpha' == $option['type'] ) { + $enqueue_color_picker_alpha = true; + } } } diff --git a/options.php b/options.php index abde7c2..e650c86 100644 --- a/options.php +++ b/options.php @@ -4,10 +4,24 @@ // === Radio Station Options === // ============================= +// --------------------------- +// Plugin Admin Options Filter +// --------------------------- +// 2.5.18: added filter for admin options +add_filter( 'radio_station_options', 'radio_station_admin_options' ); +function radio_station_admin_options( $options ) { + $options = radio_station_plugin_options( true ); + return $options; +} + // ------------------ // Set Plugin Options // ------------------ -function radio_station_plugin_options() { +function radio_station_plugin_options( $admin = false ) { + + $timezones = radio_station_get_timezone_options( true, $admin ); + $languages = radio_station_get_language_options( true, $admin ); + $formats = radio_station_get_stream_formats(); $options = array( @@ -17,9 +31,9 @@ function radio_station_plugin_options() { 'streaming_url' => array( 'type' => 'text', 'options' => 'URL', - 'label' => __( 'Streaming URL', 'radio-station' ), + 'label' => $admin ? __( 'Streaming URL', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds.', 'radio-station' ), + 'helper' => $admin ? __( 'Enter the Streaming URL for your Radio Station. This is used in the Radio Player and discoverable via Data Feeds.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'stream', ), @@ -28,9 +42,9 @@ function radio_station_plugin_options() { 'streaming_format' => array( 'type' => 'select', 'options' => $formats, - 'label' => __( 'Streaming Format', 'radio-station' ), + 'label' => $admin ? __( 'Streaming Format', 'radio-station' ) : '', 'default' => 'aac', - 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), + 'helper' => $admin ? __( 'Select streaming format for streaming URL.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'stream', ), @@ -39,9 +53,9 @@ function radio_station_plugin_options() { 'fallback_url' => array( 'type' => 'text', 'options' => 'URL', - 'label' => __( 'Fallback Stream URL', 'radio-station' ), + 'label' => $admin ? __( 'Fallback Stream URL', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + 'helper' => $admin ? __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'stream', ), @@ -50,25 +64,25 @@ function radio_station_plugin_options() { 'fallback_format' => array( 'type' => 'select', 'options' => $formats, - 'label' => __( 'Fallback Format', 'radio-station' ), + 'label' => $admin ? __( 'Fallback Format', 'radio-station' ) : '', 'default' => 'ogg', - 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), + 'helper' => $admin ? __( 'Select streaming fallback for fallback URL.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'stream', ), // --- [Player] Stream GeoBlocking --- 'stream_geo_blocking' => array( - 'label' => __( 'GeoIP Stream Blocking', 'radio-station' ), + 'label' => $admin ? __( 'GeoIP Stream Blocking', 'radio-station' ) : '', 'type' => 'select', 'options' => array( - '' => __( 'No GeoIP Blocking', 'radio-station' ), - 'live365' => __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ), - // 'blacklist' => __( 'Custom Country Blacklist', 'radio-station' ), - // 'whitelist' => __( 'Custom Country Whitelist', 'radio-station' ), + '' => $admin ? __( 'No GeoIP Blocking', 'radio-station' ) : '', + 'live365' => $admin ? __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ) : '', + // 'blacklist' => $admin ? __( 'Custom Country Blacklist', 'radio-station' ) : '', + // 'whitelist' => $admin ? __( 'Custom Country Whitelist', 'radio-station' ) : '', ), 'default' => '', - 'helper' => __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ), + 'helper' => $admin ? __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'stream', 'pro' => true, @@ -80,9 +94,9 @@ function radio_station_plugin_options() { 'radio_language' => array( 'type' => 'select', 'options' => $languages, - 'label' => __( 'Main Broadcast Language', 'radio-station' ), + 'label' => $admin ? __( 'Main Broadcast Language', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Select the main language used on your Radio Station.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the main language used on your Radio Station.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'broadcast', ), @@ -91,9 +105,9 @@ function radio_station_plugin_options() { 'timezone_location' => array( 'type' => 'select', 'options' => $timezones, - 'label' => __( 'Location Timezone', 'radio-station' ), + 'label' => $admin ? __( 'Location Timezone', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), + 'helper' => $admin ? __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'broadcast', ), @@ -102,12 +116,12 @@ function radio_station_plugin_options() { 'clock_time_format' => array( 'type' => 'select', 'options' => array( - '12' => __( '12 Hour Format', 'radio-station' ), - '24' => __( '24 Hour Format', 'radio-station' ), + '12' => $admin ? __( '12 Hour Format', 'radio-station' ) : '', + '24' => $admin ? __( '24 Hour Format', 'radio-station' ) : '', ), - 'label' => __( 'Clock Time Format', 'radio-station' ), + 'label' => $admin ? __( 'Clock Time Format', 'radio-station' ) : '', 'default' => '12', - 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), + 'helper' => $admin ? __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'broadcast', ), @@ -118,9 +132,9 @@ function radio_station_plugin_options() { // 2.3.3.8: added station title field 'station_title' => array( 'type' => 'text', - 'label' => __( 'Station Title', 'radio-station' ), + 'label' => $admin ? __( 'Station Title', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), + 'helper' => $admin ? __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -129,9 +143,9 @@ function radio_station_plugin_options() { // 2.3.3.8: added station logo image field 'station_image' => array( 'type' => 'image', - 'label' => __( 'Station Logo Image', 'radio-station' ), + 'label' => $admin ? __( 'Station Logo Image', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), + 'helper' => $admin ? __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -140,9 +154,9 @@ function radio_station_plugin_options() { // 2.5.18: added station frequency option 'station_frequency' => array( 'type' => 'text', - 'label' => __( 'Station Frequency', 'radio-station' ), + 'label' => $admin ? __( 'Station Frequency', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Text display to inform users of your main station frequency.', 'radio-station' ), + 'helper' => $admin ? __( 'Text display to inform users of your main station frequency.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -151,9 +165,9 @@ function radio_station_plugin_options() { // 2.5.18: added station location option 'station_location' => array( 'type' => 'text', - 'label' => __( 'Station Location', 'radio-station' ), + 'label' => $admin ? __( 'Station Location', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Text display to inform users of your main station location.', 'radio-station' ), + 'helper' => $admin ? __( 'Text display to inform users of your main station location.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -163,9 +177,9 @@ function radio_station_plugin_options() { 'station_phone' => array( 'type' => 'text', 'options' => 'PHONE', - 'label' => __( 'Station Phone', 'radio-station' ), + 'label' => $admin ? __( 'Station Phone', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), + 'helper' => $admin ? __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -176,8 +190,8 @@ function radio_station_plugin_options() { 'type' => 'checkbox', 'default' => '', 'value' => 'yes', - 'label' => __( 'Show Phone Display', 'radio-station' ), - 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), + 'label' => $admin ? __( 'Show Phone Display', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -187,8 +201,8 @@ function radio_station_plugin_options() { 'station_email' => array( 'type' => 'email', 'default' => '', - 'label' => __( 'Station Email', 'radio-station' ), - 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), + 'label' => $admin ? __( 'Station Email', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Main email address for the Station (for requests etc.)', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -199,8 +213,8 @@ function radio_station_plugin_options() { 'type' => 'checkbox', 'default' => '', 'value' => 'yes', - 'label' => __( 'Show Email Display', 'radio-station' ), - 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), + 'label' => $admin ? __( 'Show Email Display', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -210,10 +224,10 @@ function radio_station_plugin_options() { // --- REST Data Routes --- 'enable_data_routes' => array( 'type' => 'checkbox', - 'label' => __( 'Enable Data Routes', 'radio-station' ), + 'label' => $admin ? __( 'Enable Data Routes', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ), + 'helper' => $admin ? __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'feeds', ), @@ -221,10 +235,10 @@ function radio_station_plugin_options() { // --- Data Feed Links --- 'enable_data_feeds' => array( 'type' => 'checkbox', - 'label' => __( 'Enable Data Feeds', 'radio-station' ), + 'label' => $admin ? __( 'Enable Data Feeds', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ), + 'helper' => $admin ? __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'feeds', ), @@ -233,10 +247,10 @@ function radio_station_plugin_options() { // note: disabled by default for WordPress.org repository compliance 'ping_netmix_directory' => array( 'type' => 'checkbox', - 'label' => __( 'Ping Netmix Directory', 'radio-station' ), + 'label' => $admin ? __( 'Ping Netmix Directory', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ), + 'helper' => $admin ? __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'broadcast', ), @@ -248,10 +262,10 @@ function radio_station_plugin_options() { // 2.5.6: added setting for conflict checker 'conflict_checker' => array( 'type' => 'checkbox', - 'label' => __( 'Shift Conflict Checker', 'radio-station' ), + 'label' => $admin ? __( 'Shift Conflict Checker', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Check for Shift conflicts when saving Show shift times.', 'radio-station' ), + 'helper' => $admin ? __( 'Check for Shift conflicts when saving Show shift times.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'performance', ), @@ -260,10 +274,10 @@ function radio_station_plugin_options() { // 2.4.0.6: change label from Clear Transients 'clear_transients' => array( 'type' => 'checkbox', - 'label' => __( 'Disable Transients', 'radio-station' ), + 'label' => $admin ? __( 'Disable Transients', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), + 'helper' => $admin ? __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'performance', ), @@ -271,10 +285,10 @@ function radio_station_plugin_options() { // --- Transient Caching --- 'transient_caching' => array( 'type' => 'checkbox', - 'label' => __( 'Show Transients', 'radio-station' ), + 'label' => $admin ? __( 'Show Transients', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), + 'helper' => $admin ? __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'performance', 'pro' => true, @@ -283,10 +297,10 @@ function radio_station_plugin_options() { // --- Show Shift Feeds --- /* 'show_shift_feeds' => array( 'type' => 'checkbox', - 'label' => __( 'Show Shift Feeds', 'radio-station' ), + 'label' => $admin ? __( 'Show Shift Feeds', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ), + 'helper' => $admin ? __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'feeds', 'pro' => true, @@ -298,8 +312,8 @@ function radio_station_plugin_options() { // 2.5.0: added note about defaults being overrideable in widgets 'player_defaults_note' => array( 'type' => 'note', - 'label' => __( 'Player Defaults Note', 'radio-station' ), - 'helper' => __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ), + 'label' => $admin ? __( 'Player Defaults Note', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -307,10 +321,10 @@ function radio_station_plugin_options() { // --- [Player] Player Title --- 'player_title' => array( 'type' => 'checkbox', - 'label' => __( 'Display Station Title', 'radio-station' ), + 'label' => $admin ? __( 'Display Station Title', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), + 'helper' => $admin ? __( 'Display your Radio Station Title in Player by default.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -318,10 +332,10 @@ function radio_station_plugin_options() { // --- [Player] Player Image --- 'player_image' => array( 'type' => 'checkbox', - 'label' => __( 'Display Station Image', 'radio-station' ), + 'label' => $admin ? __( 'Display Station Image', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), + 'helper' => $admin ? __( 'Display your Radio Station Image in Player by default.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -331,14 +345,14 @@ function radio_station_plugin_options() { // 2.5.7: disable howler script (browser incompatibilities) 'player_script' => array( 'type' => 'select', - 'label' => __( 'Player Script', 'radio-station' ), + 'label' => $admin ? __( 'Player Script', 'radio-station' ) : '', 'default' => 'jplayer', 'options' => array( - 'amplitude' => __( 'Amplitude', 'radio-station' ), - 'jplayer' => __( 'jPlayer', 'radio-station' ), - // 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => $admin ? __( 'Amplitude', 'radio-station' ) : '', + 'jplayer' => $admin ? __( 'jPlayer', 'radio-station' ) : '', + // 'howler' => $admin ? __( 'Howler', 'radio-station' ) : '', ), - 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), + 'helper' => $admin ? __( 'Default audio script to use for playback in the Player.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -349,14 +363,14 @@ function radio_station_plugin_options() { // 2.5.7: disable howler script (browser incompatibilities) 'player_fallbacks' => array( 'type' => 'multicheck', - 'label' => __( 'Fallback Scripts', 'radio-station' ), + 'label' => $admin ? __( 'Fallback Scripts', 'radio-station' ) : '', 'default' => array( 'amplitude', 'howler', 'jplayer' ), 'options' => array( - 'amplitude' => __( 'Amplitude', 'radio-station' ), - 'jplayer' => __( 'jPlayer', 'radio-station' ), - // 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => $admin ? __( 'Amplitude', 'radio-station' ) : '', + 'jplayer' => $admin ? __( 'jPlayer', 'radio-station' ) : '', + // 'howler' => $admin ? __( 'Howler', 'radio-station' ) : '', ), - 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), + 'helper' => $admin ? __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -364,13 +378,13 @@ function radio_station_plugin_options() { // --- [Player] Player Theme --- 'player_theme' => array( 'type' => 'select', - 'label' => __( 'Default Player Theme', 'radio-station' ), + 'label' => $admin ? __( 'Default Player Theme', 'radio-station' ) : '', 'default' => 'light', 'options' => array( - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), + 'light' => $admin ? __( 'Light', 'radio-station' ) : '', + 'dark' => $admin ? __( 'Dark', 'radio-station' ) : '', ), - 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), + 'helper' => $admin ? __( 'Default Player Controls theme style.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -379,14 +393,14 @@ function radio_station_plugin_options() { // 2.5.15: change default to circular 'player_buttons' => array( 'type' => 'select', - 'label' => __( 'Default Player Buttons', 'radio-station' ), + 'label' => $admin ? __( 'Default Player Buttons', 'radio-station' ) : '', 'default' => 'circular', 'options' => array( - 'circular' => __( 'Circular Buttons', 'radio-station' ), - 'rounded' => __( 'Rounded Buttons', 'radio-station' ), - 'square' => __( 'Square Buttons', 'radio-station' ), + 'circular' => $admin ? __( 'Circular Buttons', 'radio-station' ) : '', + 'rounded' => $admin ? __( 'Rounded Buttons', 'radio-station' ) : '', + 'square' => $admin ? __( 'Square Buttons', 'radio-station' ) : '', ), - 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), + 'helper' => $admin ? __( 'Default Player Buttons shape style.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -396,15 +410,15 @@ function radio_station_plugin_options() { // 2.5.0: default to volume slider only 'player_volumes' => array( 'type' => 'multicheck', - 'label' => __( 'Volume Controls', 'radio-station' ), + 'label' => $admin ? __( 'Volume Controls', 'radio-station' ) : '', 'default' => array( 'slider' ), 'options' => array( - 'slider' => __( 'Volume Slider', 'radio-station' ), - 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), - 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), - 'max' => __( 'Maximize Volume', 'radio-station' ), + 'slider' => $admin ? __( 'Volume Slider', 'radio-station' ) : '', + 'updown' => $admin ? __( 'Volume Plus / Minus', 'radio-station' ) : '', + 'mute' => $admin ? __( 'Mute Volume Toggle', 'radio-station' ) : '', + 'max' => $admin ? __( 'Maximize Volume', 'radio-station' ) : '', ), - 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), + 'helper' => $admin ? __( 'Which volume controls to display in the Player by default.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -412,10 +426,10 @@ function radio_station_plugin_options() { // --- [Player] Player Debug Mode --- 'player_debug' => array( 'type' => 'checkbox', - 'label' => __( 'Player Debug Mode', 'radio-station' ), + 'label' => $admin ? __( 'Player Debug Mode', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), + 'helper' => $admin ? __( 'Output player debug information in browser javascript console.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -425,9 +439,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Playing Highlight Color --- 'player_playing_color' => array( 'type' => 'color', - 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), + 'label' => $admin ? __( 'Playing Icon Highlight Color', 'radio-station' ) : '', 'default' => '#70E070', - 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), + 'helper' => $admin ? __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'colors', 'pro' => true, @@ -436,9 +450,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Control Icons Highlight Color --- 'player_buttons_color' => array( 'type' => 'color', - 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), + 'label' => $admin ? __( 'Control Icons Highlight Color', 'radio-station' ) : '', 'default' => '#00A0E0', - 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), + 'helper' => $admin ? __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'colors', 'pro' => true, @@ -447,9 +461,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Volume Knob Color --- 'player_thumb_color' => array( 'type' => 'color', - 'label' => __( 'Volume Knob Color', 'radio-station' ), + 'label' => $admin ? __( 'Volume Knob Color', 'radio-station' ) : '', 'default' => '#80C080', - 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), + 'helper' => $admin ? __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'colors', 'pro' => true, @@ -458,9 +472,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Volume Track Color --- 'player_range_color' => array( 'type' => 'coloralpha', - 'label' => __( 'Volume Track Color', 'radio-station' ), + 'label' => $admin ? __( 'Volume Track Color', 'radio-station' ) : '', 'default' => '#80C080', - 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), + 'helper' => $admin ? __( 'Default Track Color for Player Volume Slider.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'colors', 'pro' => true, @@ -471,12 +485,12 @@ function radio_station_plugin_options() { // --- [Player] Player Volume --- 'player_volume' => array( 'type' => 'number', - 'label' => __( 'Player Start Volume', 'radio-station' ), + 'label' => $admin ? __( 'Player Start Volume', 'radio-station' ) : '', 'default' => 77, 'min' => 0, 'step' => 1, 'max' => 100, - 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), + 'helper' => $admin ? __( 'Initial volume for when the Player starts playback.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'advanced', 'pro' => false, @@ -485,10 +499,10 @@ function radio_station_plugin_options() { // --- [Player] Single Player --- 'player_single' => array( 'type' => 'checkbox', - 'label' => __( 'Single Player at Once', 'radio-station' ), + 'label' => $admin ? __( 'Single Player at Once', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Stop any existing Player instances on the page or in other windows or tabs when a Player is started.', 'radio-station' ), + 'helper' => $admin ? __( 'Stop any existing Player instances on the page or in other windows or tabs when a Player is started.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'advanced', 'pro' => false, @@ -499,10 +513,10 @@ function radio_station_plugin_options() { // 2.5.16: updated helper text 'player_autoresume' => array( 'type' => 'checkbox', - 'label' => __( 'Autoresume Playback', 'radio-station' ), + 'label' => $admin ? __( 'Autoresume Playback', 'radio-station' ) : '', 'default' => '', 'value' => 'on', - 'helper' => __( 'On return to site or page reload, ask the user to resume stream playback if they were playing the stream previously, using a popup a modal dialogue box.', 'radio-station' ), + 'helper' => $admin ? __( 'On return to site or page reload, ask the user to resume stream playback if they were playing the stream previously, using a popup a modal dialogue box.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'advanced', 'pro' => true, @@ -512,10 +526,10 @@ function radio_station_plugin_options() { // 2.5.0: enabled popup player button 'player_popup' => array( 'type' => 'checkbox', - 'label' => __( 'Popup Player Button', 'radio-station' ), + 'label' => $admin ? __( 'Popup Player Button', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'Add button to open Popup Player in separate window.', 'radio-station' ), + 'helper' => $admin ? __( 'Add button to open Popup Player in separate window.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'advanced', 'pro' => true, @@ -526,9 +540,9 @@ function radio_station_plugin_options() { // --- Player Bar Note --- 'player_bar_note' => array( 'type' => 'note', - 'label' => __( 'Bar Defaults Note', 'radio-station' ), - 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) - . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), + 'label' => $admin ? __( 'Bar Defaults Note', 'radio-station' ) : '', + 'helper' => $admin ? __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) + . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', ), @@ -536,16 +550,16 @@ function radio_station_plugin_options() { // --- [Pro/Player] Sitewide Player Bar --- 'player_bar' => array( 'type' => 'select', - 'label' => __( 'Sitewide Player Bar', 'radio-station' ), + 'label' => $admin ? __( 'Sitewide Player Bar', 'radio-station' ) : '', 'default' => 'off', 'options' => array( - 'off' => __( 'No Player Bar', 'radio-station' ), - 'top' => __( 'Top Player Bar', 'radio-station' ), - 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), + 'off' => $admin ? __( 'No Player Bar', 'radio-station' ) : '', + 'top' => $admin ? __( 'Top Player Bar', 'radio-station' ) : '', + 'bottom' => $admin ? __( 'Bottom Player Bar', 'radio-station' ) : '', ), 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), + 'helper' => $admin ? __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ) : '', 'pro' => true, ), @@ -556,24 +570,24 @@ function radio_station_plugin_options() { 'min' => 40, 'max' => 400, 'step' => 1, - 'label' => __( 'Player Bar Height', 'radio-station' ), + 'label' => $admin ? __( 'Player Bar Height', 'radio-station' ) : '', 'default' => 80, 'suffix' => 'px', 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), + 'helper' => $admin ? __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ) : '', 'pro' => true, ), // --- [Pro/Player] Fade In Player Bar --- 'player_bar_fadein' => array( 'type' => 'number', - 'label' => __( 'Fade In Player Bar', 'radio-station' ), + 'label' => $admin ? __( 'Fade In Player Bar', 'radio-station' ) : '', 'default' => 2500, 'min' => 0, 'step' => 100, 'max' => 10000, - 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'helper' => $admin ? __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -583,11 +597,10 @@ function radio_station_plugin_options() { // 2.4.0.1: fix for missing value field 'player_bar_continuous' => array( 'type' => 'checkbox', - 'label' => __( 'Continuous Playback', 'radio-station' ), + 'label' => $admin ? __( 'Continuous Playback', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ) - . ' ' . __( 'Click here for setup notes.', 'radio-station' ) . '', + 'helper' => $admin ? __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ) . ' ' . __( 'Click here for setup notes.', 'radio-station' ) . '' : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -596,12 +609,12 @@ function radio_station_plugin_options() { // --- [Pro/Player] Player Page Fade --- 'player_bar_pagefade' => array( 'type' => 'number', - 'label' => __( 'Page Fade Time', 'radio-station' ), + 'label' => $admin ? __( 'Page Fade Time', 'radio-station' ) : '', 'default' => 2000, 'min' => 0, 'step' => 100, 'max' => 10000, - 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), + 'helper' => $admin ? __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -611,12 +624,12 @@ function radio_station_plugin_options() { // 2.4.0.3: add page load timeout option 'player_bar_timeout' => array( 'type' => 'number', - 'label' => __( 'Page Load Timeout', 'teleporter' ), + 'label' => $admin ? __( 'Page Load Timeout', 'teleporter' ) : '', 'default' => 7000, 'min' => 0, 'step' => 500, 'max' => 20000, - 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), + 'helper' => $admin ? __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -625,9 +638,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Bar Player Text Color --- 'player_bar_text' => array( 'type' => 'color', - 'label' => __( 'Bar Player Text Color', 'radio-station' ), + 'label' => $admin ? __( 'Bar Player Text Color', 'radio-station' ) : '', 'default' => '#FFFFFF', - 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'helper' => $admin ? __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -636,9 +649,9 @@ function radio_station_plugin_options() { // --- [Pro/Player] Bar Player Background Color --- 'player_bar_background' => array( 'type' => 'coloralpha', - 'label' => __( 'Bar Player Background Color', 'radio-station' ), + 'label' => $admin ? __( 'Bar Player Background Color', 'radio-station' ) : '', 'default' => 'rgba(0,0,0,255)', - 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'helper' => $admin ? __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -648,12 +661,12 @@ function radio_station_plugin_options() { // 2.4.0.3: added for current show display 'player_bar_currentshow' => array( 'type' => 'checkbox', - 'label' => __( 'Display Current Show', 'radio-station' ), + 'label' => $admin ? __( 'Display Current Show', 'radio-station' ) : '', 'value' => 'yes', 'default' => 'yes', 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), + 'helper' => $admin ? __( 'Display the Current Show in the Player Bar.', 'radio-station' ) : '', 'pro' => true, ), @@ -661,12 +674,12 @@ function radio_station_plugin_options() { // 2.4.0.3: added for now playing metadata display 'player_bar_nowplaying' => array( 'type' => 'checkbox', - 'label' => __( 'Display Now Playing', 'radio-station' ), + 'label' => $admin ? __( 'Display Now Playing', 'radio-station' ) : '', 'value' => 'yes', 'default' => 'yes', 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), + 'helper' => $admin ? __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ) : '', 'pro' => true, ), @@ -674,17 +687,17 @@ function radio_station_plugin_options() { // 2.5.0: added track animation option 'player_bar_track_animation' => array( 'type' => 'select', - 'label' => __( 'Track Animation', 'radio-station' ), + 'label' => $admin ? __( 'Track Animation', 'radio-station' ) : '', 'default' => 'backandforth', 'options' => array( - 'none' => __( 'No Animation', 'radio-station' ), - 'lefttoright' => __( 'Left to Right Ticker', 'radio-station' ), - 'righttoleft' => __( 'Right to Left Ticker', 'radio-station' ), - 'backandforth' => __( 'Back and Forth', 'radio-station' ), + 'none' => $admin ? __( 'No Animation', 'radio-station' ) : '', + 'lefttoright' => $admin ? __( 'Left to Right Ticker', 'radio-station' ) : '', + 'righttoleft' => $admin ? __( 'Right to Left Ticker', 'radio-station' ) : '', + 'backandforth' => $admin ? __( 'Back and Forth', 'radio-station' ) : '', ), 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'How to animate the currently playing track display.', 'radio-station' ), + 'helper' => $admin ? __( 'How to animate the currently playing track display.', 'radio-station' ) : '', 'pro' => true, ), @@ -693,11 +706,11 @@ function radio_station_plugin_options() { 'player_bar_metadata' => array( 'type' => 'text', 'options' => 'URL', - 'label' => __( 'Metadata URL', 'radio-station' ), + 'label' => $admin ? __( 'Metadata URL', 'radio-station' ) : '', 'default' => '', 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), + 'helper' => $admin ? __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ) : '', 'pro' => true, ), @@ -705,12 +718,12 @@ function radio_station_plugin_options() { // 2.5.6: added option to store stream 'player_store_metadata' => array( 'type' => 'checkbox', - 'label' => __( 'Store Track Metadata?', 'radio-station' ), + 'label' => $admin ? __( 'Store Track Metadata?', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', 'tab' => 'player', 'section' => 'bar', - 'helper' => __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ), + 'helper' => $admin ? __( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ) : '', 'pro' => true, ), @@ -720,9 +733,9 @@ function radio_station_plugin_options() { 'schedule_page' => array( 'type' => 'select', 'options' => 'PAGEID', - 'label' => __( 'Master Schedule Page', 'radio-station' ), + 'label' => $admin ? __( 'Master Schedule Page', 'radio-station' ) : '', 'default' => '', - 'helper' => __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', ), @@ -730,10 +743,10 @@ function radio_station_plugin_options() { // --- Automatic Schedule Display --- 'schedule_auto' => array( 'type' => 'checkbox', - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]', + 'helper' => $admin ? __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]' : '', 'tab' => 'pages', 'section' => 'schedule', ), @@ -741,16 +754,16 @@ function radio_station_plugin_options() { // --- Default Schedule View --- 'schedule_view' => array( 'type' => 'select', - 'label' => __( 'Schedule View Default', 'radio-station' ), + 'label' => $admin ? __( 'Schedule View Default', 'radio-station' ) : '', 'default' => 'table', 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'div' => __( 'Divs View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'default' => __( 'Legacy Table', 'radio-station' ), + 'table' => $admin ? __( 'Table View', 'radio-station' ) : '', + 'list' => $admin ? __( 'List View', 'radio-station' ) : '', + 'div' => $admin ? __( 'Divs View', 'radio-station' ) : '', + 'tabs' => $admin ? __( 'Tabbed View', 'radio-station' ) : '', + 'default' => $admin ? __( 'Legacy Table', 'radio-station' ) : '', ), - 'helper' => __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ), + 'helper' => $admin ? __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', ), @@ -758,14 +771,14 @@ function radio_station_plugin_options() { // --- Schedule Clock Display --- 'schedule_clock' => array( 'type' => 'select', - 'label' => __( 'Schedule Clock?', 'radio-station' ), + 'label' => $admin ? __( 'Schedule Clock?', 'radio-station' ) : '', 'default' => 'clock', 'options' => array( - '' => __( 'None', 'radio-station' ), - 'clock' => __( 'Clock', 'radio-station' ), - 'timezone' => __( 'Timezone', 'radio-station' ), + '' => $admin ? __( 'None', 'radio-station' ) : '', + 'clock' => $admin ? __( 'Clock', 'radio-station' ) : '', + 'timezone' => $admin ? __( 'Timezone', 'radio-station' ) : '', ), - 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), + 'helper' => $admin ? __( 'Radio Time section display above program Schedule.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', ), @@ -774,10 +787,10 @@ function radio_station_plugin_options() { // 2.5.10.1: added schedule AJAX load default 'schedule_ajax' => array( 'type' => 'checkbox', - 'label' => __( 'AJAX Load?', 'radio-station' ), + 'label' => $admin ? __( 'AJAX Load?', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Whether to load schedule display via AJAX by default.', 'radio-station' ), + 'helper' => $admin ? __( 'Whether to load schedule display via AJAX by default.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', ), @@ -785,10 +798,10 @@ function radio_station_plugin_options() { // --- [Pro/Plus] Schedule Switcher --- 'schedule_switcher' => array( 'type' => 'checkbox', - 'label' => __( 'View Switching', 'radio-station' ), + 'label' => $admin ? __( 'View Switching', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), + 'helper' => $admin ? __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', 'pro' => true, @@ -798,18 +811,18 @@ function radio_station_plugin_options() { // 2.3.2: added additional views option 'schedule_views' => array( 'type' => 'multicheck', - 'label' => __( 'Available Views', 'radio-station' ), + 'label' => $admin ? __( 'Available Views', 'radio-station' ) : '', // note: unstyled list view not included in defaults 'default' => array( 'table', 'calendar' ), 'value' => 'yes', 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'grid' => __( 'Grid View', 'radio-station' ), - 'calendar' => __( 'Calendar View', 'radio-station' ), + 'table' => $admin ? __( 'Table View', 'radio-station' ) : '', + 'tabs' => $admin ? __( 'Tabbed View', 'radio-station' ) : '', + 'list' => $admin ? __( 'List View', 'radio-station' ) : '', + 'grid' => $admin ? __( 'Grid View', 'radio-station' ) : '', + 'calendar' => $admin ? __( 'Calendar View', 'radio-station' ) : '', ), - 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), + 'helper' => $admin ? __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', 'pro' => true, @@ -819,10 +832,10 @@ function radio_station_plugin_options() { // 2.4.0.4: added grid view time spacing option 'schedule_timegrid' => array( 'type' => 'checkbox', - 'label' => __( 'Time Spaced Grid', 'radio-station' ), + 'label' => $admin ? __( 'Time Spaced Grid', 'radio-station' ) : '', 'default' => '', 'value' => 'yes', - 'helper' => __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ), + 'helper' => $admin ? __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'schedule', 'pro' => true, @@ -833,14 +846,14 @@ function radio_station_plugin_options() { // --- Show Blocks Position --- 'show_block_position' => array( 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'label' => $admin ? __( 'Info Blocks Position', 'radio-station' ) : '', 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), + 'left' => $admin ? __( 'Float Left', 'radio-station' ) : '', + 'right' => $admin ? __( 'Float Right', 'radio-station' ) : '', + 'top' => $admin ? __( 'Float Top', 'radio-station' ) : '', ), 'default' => 'left', - 'helper' => __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ), + 'helper' => $admin ? __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -848,13 +861,13 @@ function radio_station_plugin_options() { // ---- Show Section Layout --- 'show_section_layout' => array( 'type' => 'select', - 'label' => __( 'Show Content Layout', 'radio-station' ), + 'label' => $admin ? __( 'Show Content Layout', 'radio-station' ) : '', 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), + 'tabbed' => $admin ? __( 'Tabbed', 'radio-station' ) : '', + 'standard' => $admin ? __( 'Standard', 'radio-station' ) : '', ), 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ), + 'helper' => $admin ? __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -863,13 +876,13 @@ function radio_station_plugin_options() { // 2.5.15: added show player selection 'show_player' => array( 'type' => 'select', - 'label' => __( 'Latest Show Player', 'radio-station' ), + 'label' => $admin ? __( 'Latest Show Player', 'radio-station' ) : '', 'options' => array( - 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), - 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + 'radio_player' => $admin ? __( 'Radio Station Stream Player', 'radio-station' ) : '', + 'media_elements' => $admin ? __( 'MediaElements (WordPress)', 'radio-station' ) : '', ), 'default' => 'media_elements', - 'helper' => __( 'Which player to use on the Show pages for the latest show recording.', 'radio-station' ), + 'helper' => $admin ? __( 'Which player to use on the Show pages for the latest show recording.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -878,14 +891,14 @@ function radio_station_plugin_options() { // 2.5.15: added show player theme selection 'show_player_theme' => array( 'type' => 'select', - 'label' => __( 'Show Player Theme', 'radio-station' ), + 'label' => $admin ? __( 'Show Player Theme', 'radio-station' ) : '', 'default' => '', 'options' => array( - '' => __( 'Player Default', 'radio-station' ), - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), + '' => $admin ? __( 'Player Default', 'radio-station' ) : '', + 'light' => $admin ? __( 'Light', 'radio-station' ) : '', + 'dark' => $admin ? __( 'Dark', 'radio-station' ) : '', ), - 'helper' => __( 'Show Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'helper' => $admin ? __( 'Show Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -894,10 +907,10 @@ function radio_station_plugin_options() { // 2.3.2: added plural to option label 'show_header_image' => array( 'type' => 'checkbox', - 'label' => __( 'Content Header Images', 'radio-station' ), + 'label' => $admin ? __( 'Content Header Images', 'radio-station' ) : '', 'value' => 'yes', 'default' => '', - 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), + 'helper' => $admin ? __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -905,12 +918,12 @@ function radio_station_plugin_options() { // --- Latest Show Posts --- // 'show_latest_posts' => array( // 'type' => 'numeric', - // 'label' => __( 'Latest Show Posts', 'radio-station' ), + // 'label' => $admin ? __( 'Latest Show Posts', 'radio-station' ) : '', // 'step' => 1, // 'min' => 0, // 'max' => 100, // 'default' => 3, - // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), + // 'helper' => $admin ? __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ) : '', // 'tab' => 'pages', // 'section' => 'show', // ), @@ -918,12 +931,12 @@ function radio_station_plugin_options() { // --- Show Posts Per Page --- 'show_posts_per_page' => array( 'type' => 'numeric', - 'label' => __( 'Posts per Page', 'radio-station' ), + 'label' => $admin ? __( 'Posts per Page', 'radio-station' ) : '', 'step' => 1, 'min' => 0, 'max' => 1000, 'default' => 10, - 'helper' => __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ), + 'helper' => $admin ? __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -934,9 +947,9 @@ function radio_station_plugin_options() { 'step' => 1, 'min' => 0, 'max' => 1000, - 'label' => __( 'Playlists per Page', 'radio-station' ), + 'label' => $admin ? __( 'Playlists per Page', 'radio-station' ) : '', 'default' => 10, - 'helper' => __( 'Playlists per page on the Show Page tab/display', 'radio-station' ), + 'helper' => $admin ? __( 'Playlists per page on the Show Page tab/display', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), @@ -944,12 +957,12 @@ function radio_station_plugin_options() { // --- [Pro] Show Episodes per Page --- 'show_episodes_per_page' => array( 'type' => 'number', - 'label' => __( 'Episodes per Page', 'radio-station' ), + 'label' => $admin ? __( 'Episodes per Page', 'radio-station' ) : '', 'step' => 1, 'min' => 1, 'max' => 1000, 'default' => 10, - 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), + 'helper' => $admin ? __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', 'pro' => true, @@ -960,15 +973,15 @@ function radio_station_plugin_options() { // 2.5.7: updated to add option to remove team display 'combined_team_tab' => array( 'type' => 'select', - 'label' => __( 'Team Tab Display', 'radio-station' ), + 'label' => $admin ? __( 'Team Tab Display', 'radio-station' ) : '', 'default' => 'yes', 'options' => array( - 'off' => __( 'No Team Display', 'radio-station' ), - '' => __( 'Separate Role Tabs', 'radio-station' ), - 'yes' => __( 'Combined Team List', 'radio-station' ), - // 'grid' => __( 'Combined Team Grid', 'radio-station' ), + 'off' => $admin ? __( 'No Team Display', 'radio-station' ) : '', + '' => $admin ? __( 'Separate Role Tabs', 'radio-station' ) : '', + 'yes' => $admin ? __( 'Combined Team List', 'radio-station' ) : '', + // 'grid' => $admin ? __( 'Combined Team Grid', 'radio-station' ) : '', ), - 'helper' => __( 'How to display Show team members (eg. hosts, producers) on a Show page.', 'radio-station' ), + 'helper' => $admin ? __( 'How to display Show team members (eg. hosts, producers) on a Show page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', 'pro' => true, @@ -980,14 +993,14 @@ function radio_station_plugin_options() { // --- [Pro/Plus] Profile Blocks Position --- 'profile_block_position' => array( 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'label' => $admin ? __( 'Info Blocks Position', 'radio-station' ) : '', 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), + 'left' => $admin ? __( 'Float Left', 'radio-station' ) : '', + 'right' => $admin ? __( 'Float Right', 'radio-station' ) : '', + 'top' => $admin ? __( 'Float Top', 'radio-station' ) : '', ), 'default' => 'left', - 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), + 'helper' => $admin ? __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'profile', 'pro' => true, @@ -996,13 +1009,13 @@ function radio_station_plugin_options() { // ---- [Pro/Plus] Profile Section Layout --- 'profile_section_layout' => array( 'type' => 'select', - 'label' => __( 'Profile Content Layout', 'radio-station' ), + 'label' => $admin ? __( 'Profile Content Layout', 'radio-station' ) : '', 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), + 'tabbed' => $admin ? __( 'Tabbed', 'radio-station' ) : '', + 'standard' => $admin ? __( 'Standard', 'radio-station' ) : '', ), 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), + 'helper' => $admin ? __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'profile', 'pro' => true, @@ -1014,14 +1027,14 @@ function radio_station_plugin_options() { // --- [Pro] Episode Blocks Position --- 'episode_block_position' => array( 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'label' => $admin ? __( 'Info Blocks Position', 'radio-station' ) : '', 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), + 'left' => $admin ? __( 'Float Left', 'radio-station' ) : '', + 'right' => $admin ? __( 'Float Right', 'radio-station' ) : '', + 'top' => $admin ? __( 'Float Top', 'radio-station' ) : '', ), 'default' => 'left', - 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), + 'helper' => $admin ? __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'episode', 'pro' => true, @@ -1030,13 +1043,13 @@ function radio_station_plugin_options() { // ---- [Pro] Episode Section Layout --- 'episode_section_layout' => array( 'type' => 'select', - 'label' => __( 'Episode Content Layout', 'radio-station' ), + 'label' => $admin ? __( 'Episode Content Layout', 'radio-station' ) : '', 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), + 'tabbed' => $admin ? __( 'Tabbed', 'radio-station' ) : '', + 'standard' => $admin ? __( 'Standard', 'radio-station' ) : '', ), 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), + 'helper' => $admin ? __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'episode', 'pro' => true, @@ -1046,13 +1059,13 @@ function radio_station_plugin_options() { // 2.5.15: added episode player selection 'episode_player' => array( 'type' => 'select', - 'label' => __( 'Episode Player', 'radio-station' ), + 'label' => $admin ? __( 'Episode Player', 'radio-station' ) : '', 'options' => array( - 'radio_player' => __( 'Radio Station Stream Player', 'radio-station' ), - 'media_elements' => __( 'MediaElements (WordPress)', 'radio-station' ), + 'radio_player' => $admin ? __( 'Radio Station Stream Player', 'radio-station' ) : '', + 'media_elements' => $admin ? __( 'MediaElements (WordPress)', 'radio-station' ) : '', ), 'default' => 'radio_player', - 'helper' => __( 'Which player to use on the Episode pages for the Episode recording.', 'radio-station' ), + 'helper' => $admin ? __( 'Which player to use on the Episode pages for the Episode recording.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'episode', 'pro' => true, @@ -1062,14 +1075,14 @@ function radio_station_plugin_options() { // 2.5.15: added episode player theme selection 'episode_player_theme' => array( 'type' => 'select', - 'label' => __( 'Episode Player Theme', 'radio-station' ), + 'label' => $admin ? __( 'Episode Player Theme', 'radio-station' ) : '', 'default' => '', 'options' => array( - '' => __( 'Player Default', 'radio-station' ), - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), + '' => $admin ? __( 'Player Default', 'radio-station' ) : '', + 'light' => $admin ? __( 'Light', 'radio-station' ) : '', + 'dark' => $admin ? __( 'Dark', 'radio-station' ) : '', ), - 'helper' => __( 'Episode Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ), + 'helper' => $admin ? __( 'Episode Player Controls theme style (Radio Station Stream Player only,)', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'episode', 'pro' => true, @@ -1078,10 +1091,10 @@ function radio_station_plugin_options() { // --- [Pro] Use Latest Episode --- 'episode_use_latest' => array( 'type' => 'checkbox', - 'label' => __( 'Use Latest Episode', 'radio-station' ), + 'label' => $admin ? __( 'Use Latest Episode', 'radio-station' ) : '', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ), + 'helper' => $admin ? __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'episode', 'pro' => true, @@ -1093,99 +1106,99 @@ function radio_station_plugin_options() { // --- Shows Archive Page --- 'show_archive_page' => array( - 'label' => __( 'Shows Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Shows Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Show archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'posttypes', ), // --- Automatic Display --- 'show_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]' : '', 'tab' => 'archives', 'section' => 'posttypes', ), // ? --- Redirect Shows Archive --- ? // 'show_archive_override' => array( - // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), + // 'label' => $admin ? __( 'Redirect Shows Archive', 'radio-station' ) : '', // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), + // 'helper' => $admin ? __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ) : '', // 'tab' => 'archives', // 'section' => 'posttypes', // ), // --- Overrides Archive Page --- 'override_archive_page' => array( - 'label' => __( 'Overrides Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Overrides Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Override archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'posttypes', ), // --- Automatic Display --- 'override_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]' : '', 'tab' => 'archives', 'section' => 'posttypes', ), // ? --- Redirect Overrides Archive --- ? // 'override_archive_override' => array( - // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), + // 'label' => $admin ? __( 'Redirect Overrides Archive', 'radio-station' ) : '', // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), + // 'helper' => $admin ? __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ) : '', // 'tab' => 'archives', // 'section' => 'posttypes', // ), // --- Playlists Archive Page --- 'playlist_archive_page' => array( - 'label' => __( 'Playlists Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Playlists Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'posttypes', ), // --- Automatic Display --- 'playlist_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]' : '', 'tab' => 'archives', 'section' => 'posttypes', ), // ? --- Redirect Playlists Archive --- ? // 'playlist_archive_override' => array( - // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), + // 'label' => $admin ? __( 'Redirect Playlists Archive', 'radio-station' ) : '', // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), + // 'helper' => $admin ? __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ) : '', // 'tab' => 'archives', // 'section' => 'posttypes', // ), @@ -1193,11 +1206,11 @@ function radio_station_plugin_options() { // --- [Pro] Episodes Archive Page --- // 2.5.8: added episodes archive page option 'episode_archive_page' => array( - 'label' => __( 'Episodes Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Episodes Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Episode archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Episode archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'posttypes', 'pro' => true, @@ -1206,11 +1219,11 @@ function radio_station_plugin_options() { // --- [Pro] Automatic Display --- // 2.5.8: added episodes archive automatic display option 'episode_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [episodes-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [episodes-archive]' : '', 'tab' => 'archives', 'section' => 'posttypes', 'pro' => true, @@ -1219,11 +1232,11 @@ function radio_station_plugin_options() { // --- [Pro] Team Archive Page --- // 2.4.0.6: added option for team archive page 'team_archive_page' => array( - 'label' => __( 'Team Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Team Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Team archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'posttypes', 'pro' => true, @@ -1232,16 +1245,16 @@ function radio_station_plugin_options() { // --- [Pro] Automatic Display --- // 2.4.0.6: added option for team archive page 'team_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'select', 'options' => array( - '' => __( 'Off', 'radio-station' ), - 'yes' => __( 'List', 'radio-station' ), - 'grid' => __( 'Grid', 'radio-station' ), + '' => $admin ? __( 'Off', 'radio-station' ) : '', + 'yes' => $admin ? __( 'List', 'radio-station' ) : '', + 'grid' => $admin ? __( 'Grid', 'radio-station' ) : '', ), 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]' : '', 'tab' => 'archives', 'section' => 'posttypes', 'pro' => true, @@ -1252,33 +1265,33 @@ function radio_station_plugin_options() { // --- Genres Archive Page --- 'genre_archive_page' => array( - 'label' => __( 'Genres Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Genres Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'taxonomies', ), // --- Automatic Display --- 'genre_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]' : '', 'tab' => 'archives', 'section' => 'taxonomies', ), // ? --- Redirect Genres Archives --- ? // 'genre_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'label' => $admin ? __( 'Redirect Genres Archive', 'radio-station' ) : '', // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), + // 'helper' => $admin ? __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ) : '', // 'tab' => 'archives', // 'section' => 'taxonomies', // ), @@ -1286,11 +1299,11 @@ function radio_station_plugin_options() { // --- Languages Archive Page --- // 2.3.3.9: added language archive page 'language_archive_page' => array( - 'label' => __( 'Languages Archive Page', 'radio-station' ), + 'label' => $admin ? __( 'Languages Archive Page', 'radio-station' ) : '', 'type' => 'select', 'options' => 'PAGEID', 'default' => '', - 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), + 'helper' => $admin ? __( 'Select the Page for displaying the Language archive list.', 'radio-station' ) : '', 'tab' => 'archives', 'section' => 'taxonomies', ), @@ -1298,22 +1311,22 @@ function radio_station_plugin_options() { // --- Automatic Display --- // 2.3.3.9: added language archive automatic page 'language_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), + 'label' => $admin ? __( 'Automatic Display', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', + 'helper' => $admin ? __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]' : '', 'tab' => 'archives', 'section' => 'taxonomies', ), // ? --- Redirect Languages Archives --- ? // 'language_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'label' => $admin ? __( 'Redirect Genres Archive', 'radio-station' ) : '', // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), + // 'helper' => $admin ? __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ) : '', // 'tab' => 'archives', // 'section' => 'taxonomies', // ), @@ -1326,37 +1339,37 @@ function radio_station_plugin_options() { // --- Templates Change Note --- 'templates_change_note' => array( 'type' => 'note', - 'label' => __( 'Templates Change Note', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) + 'label' => $admin ? __( 'Templates Change Note', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) - . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', + . ' ' . __( 'Templates Documentation', 'radio-station' ) . '' : '', 'tab' => 'pages', 'section' => 'single', ), // --- Show Template --- 'show_template' => array( - 'label' => __( 'Show Template', 'radio-station' ), + 'label' => $admin ? __( 'Show Template', 'radio-station' ) : '', 'type' => 'select', 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + 'page' => $admin ? __( 'Theme Page Template (page.php)', 'radio-station' ) : '', + 'post' => $admin ? __( 'Theme Post Template (single.php)', 'radio-station' ) : '', + 'singular' => $admin ? __( 'Theme Singular Template (singular.php)', 'radio-station' ) : '', + 'legacy' => $admin ? __( 'Legacy Plugin Template', 'radio-station' ) : '', ), 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), + 'helper' => $admin ? __( 'Which template to use for displaying Show content.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'single', ), // --- Combined Template Method --- 'show_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), + 'label' => $admin ? __( 'Combined Method', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), + 'helper' => $admin ? __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'single', ), @@ -1364,27 +1377,27 @@ function radio_station_plugin_options() { // --- Playlist Template --- // 2.3.3.8: added missing singular.php option to match show_template 'playlist_template' => array( - 'label' => __( 'Playlist Template', 'radio-station' ), + 'label' => $admin ? __( 'Playlist Template', 'radio-station' ) : '', 'type' => 'select', 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + 'page' => $admin ? __( 'Theme Page Template (page.php)', 'radio-station' ) : '', + 'post' => $admin ? __( 'Theme Post Template (single.php)', 'radio-station' ) : '', + 'singular' => $admin ? __( 'Theme Singular Template (singular.php)', 'radio-station' ) : '', + 'legacy' => $admin ? __( 'Legacy Plugin Template', 'radio-station' ) : '', ), 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), + 'helper' => $admin ? __( 'Which template to use for displaying Playlist content.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'single', ), // --- Combined Template Method --- 'playlist_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), + 'label' => $admin ? __( 'Combined Method', 'radio-station' ) : '', 'type' => 'checkbox', 'value' => 'yes', 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), + 'helper' => $admin ? __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'single', ), @@ -1395,10 +1408,10 @@ function radio_station_plugin_options() { // 2.3.3: fix to value of value key 'ajax_widgets' => array( 'type' => 'checkbox', - 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), + 'label' => $admin ? __( 'AJAX Load Widgets?', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), + 'helper' => $admin ? __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ) : '', 'tab' => 'widgets', 'section' => 'loading', ), @@ -1406,10 +1419,10 @@ function radio_station_plugin_options() { // --- [Pro/Plus] Dynamic Reloading --- 'dynamic_reload' => array( 'type' => 'checkbox', - 'label' => __( 'Dynamic Reloading?', 'radio-station' ), + 'label' => $admin ? __( 'Dynamic Reloading?', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), + 'helper' => $admin ? __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ) : '', 'tab' => 'widgets', 'section' => 'loading', 'pro' => true, @@ -1418,10 +1431,10 @@ function radio_station_plugin_options() { // --- [Pro/Plus] Translate User Times --- 'convert_show_times' => array( 'type' => 'checkbox', - 'label' => __( 'Convert Show Times', 'radio-station' ), + 'label' => $admin ? __( 'Convert Show Times', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), + 'helper' => $admin ? __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ) : '', 'tab' => 'widgets', 'section' => 'loading', 'pro' => true, @@ -1430,10 +1443,10 @@ function radio_station_plugin_options() { // --- [Pro/Plus] Timezone Switching --- 'timezone_switching' => array( 'type' => 'checkbox', - 'label' => __( 'User Timezone Switching', 'radio-station' ), + 'label' => $admin ? __( 'User Timezone Switching', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Allow visitors to select their Timezone manually for Show time conversions.', 'radio-station' ), + 'helper' => $admin ? __( 'Allow visitors to select their Timezone manually for Show time conversions.', 'radio-station' ) : '', 'tab' => 'widgets', 'section' => 'loading', 'pro' => true, @@ -1446,9 +1459,8 @@ function radio_station_plugin_options() { // 2.4.0.3: added role to show assignment note 'permissions_show_role_note' => array( 'type' => 'note', - 'label' => __( 'Show Editing Permissions', 'radio-station' ), - 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) - . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), + 'label' => $admin ? __( 'Show Editing Permissions', 'radio-station' ) : '', + 'helper' => $admin ? __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ) : '', 'tab' => 'roles', 'section' => 'permissions', ), @@ -1457,8 +1469,8 @@ function radio_station_plugin_options() { // 2.4.0.3: added role to playlist assignment note 'permissions_playlist_role_note' => array( 'type' => 'note', - 'label' => __( 'Playlist Permissions', 'radio-station' ), - 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), + 'label' => $admin ? __( 'Playlist Permissions', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ) : '', 'tab' => 'roles', 'section' => 'permissions', ), @@ -1466,10 +1478,8 @@ function radio_station_plugin_options() { // --- Show Editor Role Note --- 'show_editor_role_note' => array( 'type' => 'note', - 'label' => __( 'Show Editor Role', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) - . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) - . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), + 'label' => $admin ? __( 'Show Editor Role', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ) : '', 'tab' => 'roles', 'section' => 'permissions', ), @@ -1477,10 +1487,10 @@ function radio_station_plugin_options() { // --- Author Role Capabilities --- 'add_author_capabilities' => array( 'type' => 'checkbox', - 'label' => __( 'Add to Author Capabilities', 'radio-station' ), + 'label' => $admin ? __( 'Add to Author Capabilities', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ), + 'helper' => $admin ? __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ) : '', 'tab' => 'roles', 'section' => 'permissions', ), @@ -1488,10 +1498,10 @@ function radio_station_plugin_options() { // --- Editor Role Capabilities --- 'add_editor_capabilities' => array( 'type' => 'checkbox', - 'label' => __( 'Add to Editor Capabilities', 'radio-station' ), + 'label' => $admin ? __( 'Add to Editor Capabilities', 'radio-station' ) : '', 'default' => 'yes', 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ), + 'helper' => $admin ? __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ) : '', 'tab' => 'roles', 'section' => 'permissions', ), @@ -1499,15 +1509,15 @@ function radio_station_plugin_options() { // ? --- Disallow Shift Changes --- ? // 'disallow_shift_changes' => array( // 'type' => 'checkbox', - // 'label' => __( 'Disallow Shift Changes', 'radio-station' ), + // 'label' => $admin ? __( 'Disallow Shift Changes', 'radio-station' ) : '', // 'default' => array(), // 'options' => array( - // 'authors' => __( 'WordPress Authors', 'radio-station' ), - // 'editors' => __( 'WorddPress Editors', 'radio-station' ), - // 'hosts' => __( 'Assigned DJs / Hosts', 'radio-station' ), - // 'producers' => __( 'Assigned Producers', 'radio-station' ), + // 'authors' => $admin ? __( 'WordPress Authors', 'radio-station' ) : '', + // 'editors' => $admin ? __( 'WorddPress Editors', 'radio-station' ) : '', + // 'hosts' => $admin ? __( 'Assigned DJs / Hosts', 'radio-station' ) : '', + // 'producers' => $admin ? __( 'Assigned Producers', 'radio-station' ) : '', // ), - // 'helper' => __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ), + // 'helper' => $admin ? __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ) : '', // 'tab' => 'roles', // 'section' => 'permissions', // 'pro' => true, @@ -1522,15 +1532,15 @@ function radio_station_plugin_options() { // 2.4.0.6: added separate archives tab // 2.7.0: add subscribe and app tabs 'tabs' => array( - 'general' => __( 'General', 'radio-station' ), - 'player' => __( 'Player', 'radio-station' ), - 'subscribe' => __( 'Subscribe', 'radio-station' ), - 'pages' => __( 'Pages', 'radio-station' ), - 'archives' => __( 'Archives', 'radio-station' ), - // 'templates' => __( 'Templates', 'radio-station' ), - 'widgets' => __( 'Widgets', 'radio-station' ), - 'roles' => __( 'Roles', 'radio-station' ), - 'app' => __( 'App', 'radio-station' ), + 'general' => $admin ? __( 'General', 'radio-station' ) : '', + 'player' => $admin ? __( 'Player', 'radio-station' ) : '', + 'subscribe' => $admin ? __( 'Subscribe', 'radio-station' ) : '', + 'pages' => $admin ? __( 'Pages', 'radio-station' ) : '', + 'archives' => $admin ? __( 'Archives', 'radio-station' ) : '', + // 'templates' => $admin ? __( 'Templates', 'radio-station' ) : '', + 'widgets' => $admin ? __( 'Widgets', 'radio-station' ) : '', + 'roles' => $admin ? __( 'Roles', 'radio-station' ) : '', + 'app' => $admin ? __( 'App', 'radio-station' ) : '', ), // --- Section Labels --- @@ -1541,31 +1551,31 @@ function radio_station_plugin_options() { // 2.7.0: add subscribe sections 'sections' => array( // --- general --- - 'stream' => __( 'Stream', 'radio-station' ), - 'broadcast' => __( 'Broadcast', 'radio-station' ), - 'station' => __( 'Station', 'radio-station' ), - 'feeds' => __( 'Feeds', 'radio-station' ), - 'performance' => __( 'Performance', 'radio-station' ), + 'stream' => $admin ? __( 'Stream', 'radio-station' ) : '', + 'broadcast' => $admin ? __( 'Broadcast', 'radio-station' ) : '', + 'station' => $admin ? __( 'Station', 'radio-station' ) : '', + 'feeds' => $admin ? __( 'Feeds', 'radio-station' ) : '', + 'performance' => $admin ? __( 'Performance', 'radio-station' ) : '', // --- player --- - 'basic' => __( 'Basic Defaults', 'radio-station' ), - 'advanced' => __( 'Advanced Defaults', 'radio-station' ), - 'colors' => __( 'Player Colors', 'radio-station' ), - 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), + 'basic' => $admin ? __( 'Basic Defaults', 'radio-station' ) : '', + 'advanced' => $admin ? __( 'Advanced Defaults', 'radio-station' ) : '', + 'colors' => $admin ? __( 'Player Colors', 'radio-station' ) : '', + 'bar' => $admin ? __( 'Sitewide Bar Player', 'radio-station' ) : '', // --- pages --- - 'schedule' => __( 'Schedule Page', 'radio-station' ), - 'single' => __( 'Single Templates', 'radio-station' ), - // 'archive' => __( 'Archive Templates', 'radio-station' ), - 'show' => __( 'Show Pages', 'radio-station' ), - 'profile' => __( 'Profile Pages', 'radio-station' ), - 'episode' => __( 'Episode Pages', 'radio-station' ), + 'schedule' => $admin ? __( 'Schedule Page', 'radio-station' ) : '', + 'single' => $admin ? __( 'Single Templates', 'radio-station' ) : '', + // 'archive' => $admin ? __( 'Archive Templates', 'radio-station' ) : '', + 'show' => $admin ? __( 'Show Pages', 'radio-station' ) : '', + 'profile' => $admin ? __( 'Profile Pages', 'radio-station' ) : '', + 'episode' => $admin ? __( 'Episode Pages', 'radio-station' ) : '', // --- archives --- - 'archives' => __( 'Archives', 'radio-station' ), - 'posttypes' => __( 'Post Types', 'radio-station' ), - 'taxonomies' => __( 'Taxonomies', 'radio-station' ), + 'archives' => $admin ? __( 'Archives', 'radio-station' ) : '', + 'posttypes' => $admin ? __( 'Post Types', 'radio-station' ) : '', + 'taxonomies' => $admin ? __( 'Taxonomies', 'radio-station' ) : '', // --- widgets --- - 'loading' => __( 'Widget Loading', 'radio-station' ), + 'loading' => $admin ? __( 'Widget Loading', 'radio-station' ) : '', // --- roles --- - 'permissions' => __( 'Permissions', 'radio-station' ), + 'permissions' => $admin ? __( 'Permissions', 'radio-station' ) : '', ), ); diff --git a/radio-station.php b/radio-station.php index 5c49119..ec2b102 100644 --- a/radio-station.php +++ b/radio-station.php @@ -213,9 +213,6 @@ function radio_station_back_compat_player() { // 2.5.0: move plan options check to separate function // 2.5.18: get options via function require RADIO_STATION_DIR . '/options.php'; -$timezones = radio_station_get_timezone_options( true ); -$languages = radio_station_get_language_options( true ); -$formats = radio_station_get_stream_formats(); $plan_options = radio_station_check_plan_options(); $options = radio_station_plugin_options(); @@ -238,11 +235,8 @@ function radio_station_back_compat_player() { 'home' => RADIO_STATION_HOME_URL, 'docs' => RADIO_STATION_DOCS_URL, 'support' => 'https://github.com/netmix/radio-station/issues/', - 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), 'share' => RADIO_STATION_HOME_URL . '#share', - 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), 'donate' => 'https://patreon.com/radiostation', - 'donatetext' => __( 'Support this Plugin', 'radio-station' ), 'readme' => false, 'settingsmenu' => false, @@ -275,6 +269,21 @@ function radio_station_back_compat_player() { ); +// --------------------- +// Settings Text Filters +// --------------------- +// 2.5.18: added to avoid translations loading early +add_filter( 'radio_station_admin_args', 'radio_station_settings_texts' ); +function radio_station_settings_texts( $args ) { + $texts = array( + 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), + 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), + 'donatetext' => __( 'Support this Plugin', 'radio-station' ), + ); + $args = array_merge( $args, $texts ); + return $args; +} + // --------------------------- // Bundle Plan Settings Filter // --------------------------- @@ -315,8 +324,8 @@ function radio_station_freemius_bundle_config( $settings ) { // --- Admin Includes --- require RADIO_STATION_DIR . '/radio-station-admin.php'; - // require RADIO_STATION_DIR . '/includes/onboarding.php'; require RADIO_STATION_DIR . '/includes/post-types-admin.php'; + require RADIO_STATION_DIR . '/includes/onboarding.php'; // --- Contextual Help --- // 2.3.0: maybe load contextual help config diff --git a/readme.txt b/readme.txt index 45aeeaa..d02e0c1 100644 --- a/readme.txt +++ b/readme.txt @@ -405,6 +405,7 @@ We recommend you test these on a Staging site (or a development copy of your liv == Changelog == = 2.5.18 = +* Updated: Plugin Panel (1.3.7) for delayed translations * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player * Improved: Related Show and Linked Show select Show ordering diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index d7c2431..7332771 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -3451,6 +3451,12 @@ public function translate_meridiem( $meridiem ) { // --- get translated meridiem --- global $wp_locale; + if ( !is_object( $wp_locale ) ) { + if ( !class_exists( 'WP_Locale' ) ) { + require_once ABSPATH . WPINC . '/locale.php'; + } + $wp_locale = new WP_Locale(); + } $meridiem = $wp_locale->get_meridiem( $meridiem ); // --- maybe switch back to original locale --- From 74b40fd8af0b263b1466939e1959bcb43d6a3a82 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 26 Dec 2025 14:26:51 +1000 Subject: [PATCH 362/377] development updated incl #385 --- CHANGELOG.md | 3 +- freemius/README.md | 15 +- freemius/assets/css/admin/account.css | 2 +- .../4529cac82a2d1f300d3c4702b7b5e8f3.svg | 227 -------- .../45da596e2b512ffc3bb638baaf0fdc4e.png | Bin 0 -> 4538 bytes .../5480ed23b199531a8cbc05924f26952b.png | Bin 7262 -> 0 bytes .../a34e046aee1702a5690679750a7f4d0f.svg | 1 + .../b09d0b38b627c2fa564d050f79f2f064.svg | 1 + .../b4f3b958f4a019862d81b15f3f8eee3a.svg | 402 -------------- .../d65812c447b4523b42d59018e1c0bb53.png | Bin 0 -> 36952 bytes .../dd89563360f0272635c8f0ab7d7f1402.png | Bin 12087 -> 0 bytes .../e366d70661d8ad2493bd6afbd779f125.png | Bin 11124 -> 0 bytes .../f18006f6535a1a6e9c6bfbffafe6f18a.svg | 227 -------- .../f928f1be99776af83e8e6be4baf8ffe7.svg | 227 -------- .../assets/js/pricing/freemius-pricing.js | 2 +- .../pricing/freemius-pricing.js.LICENSE.txt | 3 +- freemius/includes/class-freemius.php | 172 +++--- freemius/includes/class-fs-hook-snapshot.php | 45 ++ freemius/includes/class-fs-plugin-updater.php | 19 +- .../class-fs-customizer-upsell-control.php | 1 - .../includes/entities/class-fs-payment.php | 5 +- .../entities/class-fs-plugin-plan.php | 11 +- .../includes/entities/class-fs-plugin-tag.php | 4 + freemius/includes/entities/class-fs-site.php | 4 +- .../managers/class-fs-checkout-manager.php | 40 +- freemius/languages/freemius-cs_CZ.mo | Bin 36388 -> 36159 bytes freemius/languages/freemius-de_DE.mo | Bin 69310 -> 68982 bytes freemius/languages/freemius-es_ES.mo | Bin 52957 -> 52694 bytes freemius/languages/freemius-fr_FR.mo | Bin 46836 -> 47083 bytes freemius/languages/freemius-hu_HU.mo | Bin 24994 -> 24740 bytes freemius/languages/freemius-it_IT.mo | Bin 58452 -> 58196 bytes freemius/languages/freemius-nl_NL.mo | Bin 45298 -> 45539 bytes freemius/languages/freemius-ta.mo | Bin 74201 -> 73818 bytes freemius/languages/freemius-zh_CN.mo | Bin 62314 -> 62040 bytes freemius/languages/freemius.pot | 510 +++++++++--------- freemius/require.php | 1 + freemius/start.php | 5 +- freemius/templates/debug.php | 10 +- .../forms/subscription-cancellation.php | 16 +- freemius/templates/js/style-premium-theme.php | 6 +- freemius/templates/plugin-info/features.php | 1 - includes/onboarding.php | 19 +- includes/schedules.php | 3 +- includes/support-functions.php | 20 +- js/radio-station.js | 4 + loader.php | 38 +- readme.txt | 1 + 47 files changed, 580 insertions(+), 1465 deletions(-) delete mode 100644 freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg create mode 100644 freemius/assets/js/pricing/45da596e2b512ffc3bb638baaf0fdc4e.png delete mode 100644 freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png create mode 100644 freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg create mode 100644 freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg delete mode 100644 freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg create mode 100644 freemius/assets/js/pricing/d65812c447b4523b42d59018e1c0bb53.png delete mode 100644 freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png delete mode 100644 freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png delete mode 100644 freemius/assets/js/pricing/f18006f6535a1a6e9c6bfbffafe6f18a.svg delete mode 100644 freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg create mode 100644 freemius/includes/class-fs-hook-snapshot.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6382f04..efeee7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). = 2.5.18 = +* Updated: Freemius SDK (2.13.0) * Updated: Plugin Panel (1.3.7) for delayed translations * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player @@ -15,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: handle Special/Override meta in Archive shortcode * Improved: edit Special/Override permissions for linked Shows * Added: Station Frequency and Location options -* Added: hidden Channel inputs for Shifts and Overrides +* Added: Channel inputs for Shifts and Overrides * Fixed: Radio Player plugin conflicts = 2.5.17 = diff --git a/freemius/README.md b/freemius/README.md index cd292dd..1b42646 100644 --- a/freemius/README.md +++ b/freemius/README.md @@ -93,7 +93,7 @@ if ( ! function_exists( 'my_prefix_fs' ) ) { ## Usage example -You can call anySDK methods by prefixing them with the shortcode function for your particular plugin/theme (specified when completing the SDK integration form in the Developer Dashboard): +You can call any SDK methods by prefixing them with the shortcode function for your particular plugin/theme (specified when completing the SDK integration form in the Developer Dashboard): ```php get_upgrade_url(); ?> @@ -110,9 +110,9 @@ Or when calling Freemius multiple times in a scope, it's recommended to use it w There are many other SDK methods available that you can use to enhance the functionality of your WordPress product. Some of the more common use-cases are covered in the [Freemius SDK Gists](https://freemius.com/help/documentation/wordpress-sdk/gists/) documentation. -## Adding license based logic examples +## Adding license-based logic examples -Add marketing content to encourage your users to upgrade for your paid version: +Add marketing content that encourages your users to upgrade to a paid version: ```php ``` -To add a function which will only be available in your premium plugin version, simply add __premium_only as the suffix of the function name. Just make sure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: +To add a function which will only be available in your premium plugin version, add `__premium_only` as the suffix of the function name. Ensure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: ```php All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers. diff --git a/freemius/assets/css/admin/account.css b/freemius/assets/css/admin/account.css index 6e0feb4..104bc2b 100644 --- a/freemius/assets/css/admin/account.css +++ b/freemius/assets/css/admin/account.css @@ -1 +1 @@ -label.fs-tag,span.fs-tag{background:#ffba00;border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id=license_not_whitelabeled].success,.fs-notice[data-id=license_whitelabeled].success{border-left-color:#00a0d2;color:inherit}.fs-notice[data-id=license_not_whitelabeled].success label.fs-plugin-title,.fs-notice[data-id=license_whitelabeled].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{border-bottom:1px solid #f1f1f1;font-size:1.3em;line-height:1.4;margin:0 0 12px;padding:12px 15px}#fs_account h3 .dashicons{font-size:1.3em;height:26px;width:26px}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{font-size:.9em;position:absolute;right:15px;top:17px}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:700}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table code,.fs-key-value-table input[type=text],.fs-key-value-table var{background:none;color:#0073aa;font-size:16px}.fs-key-value-table input[type=text]{font-weight:700;width:100%}.fs-field-beta_program label{margin-left:7px}label.fs-tag{border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag,label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{border:1px solid #e5e5e5;max-height:200px;overflow:auto}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none!important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{color:gray;text-align:right;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{white-space:nowrap;width:1px}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{font-weight:700;text-align:left}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:700}#fs_billing_address{width:100%}#fs_billing_address tr td{padding:5px;width:50%}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:700}#fs_billing_address input,#fs_billing_address select{display:block;margin-top:5px;width:100%}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent}#fs_billing_address input::placeholder,#fs_billing_address select::placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::placeholder,#fs_billing_address.fs-read-mode select::placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%}@media screen and (max-width:639px){#fs_account .fs-header-actions{margin:0 0 12px;padding:0 15px 12px;position:static}#fs_account .fs-header-actions li{display:inline-block;float:none}#fs_account #fs_account_details,#fs_account #fs_account_details tbody,#fs_account #fs_account_details td,#fs_account #fs_account_details th,#fs_account #fs_account_details tr{display:block}#fs_account #fs_account_details tr td:first-child{text-align:left}#fs_account #fs_account_details tr td:nth-child(2){padding:0 12px}#fs_account #fs_account_details tr td:nth-child(2) code{margin:0;padding:0}#fs_account #fs_account_details tr td:nth-child(2) label{margin-left:0}#fs_account #fs_account_details tr td:nth-child(3){text-align:left}#fs_account #fs_account_details tr.fs-field-plan td:nth-child(2) .button-group{float:none;margin:12px 0}} \ No newline at end of file +label.fs-tag,span.fs-tag{background:#ffba00;border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id=license_not_whitelabeled].success,.fs-notice[data-id=license_whitelabeled].success{border-left-color:#00a0d2;color:inherit}.fs-notice[data-id=license_not_whitelabeled].success label.fs-plugin-title,.fs-notice[data-id=license_whitelabeled].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{border-bottom:1px solid #f1f1f1;font-size:1.3em;line-height:1.4;margin:0 0 12px;padding:12px 15px}#fs_account h3 .dashicons{font-size:1.3em;height:26px;width:26px}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{font-size:.9em;position:absolute;right:15px;top:17px}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:none}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:700}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table code,.fs-key-value-table input[type=text],.fs-key-value-table var{background:none;color:#0073aa;font-size:16px}.fs-key-value-table input[type=text]{font-weight:700;width:100%}.fs-field-beta_program label{margin-left:7px}label.fs-tag{border-radius:3px;color:#fff;display:inline-block;font-size:11px;line-height:11px;padding:5px;vertical-align:baseline}label.fs-tag,label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{border:1px solid #e5e5e5;max-height:200px;overflow:auto}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none!important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{color:gray;text-align:right;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{white-space:nowrap;width:1px}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{font-weight:700;text-align:left}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:700}#fs_billing_address{width:100%}#fs_billing_address tr td{padding:5px;width:50%}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:700}#fs_billing_address input,#fs_billing_address select{display:block;margin-top:5px;width:100%}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent}#fs_billing_address input::placeholder,#fs_billing_address select::placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{background:none;border-color:transparent;border-bottom:1px dashed #ccc;color:#777;padding-left:0}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::placeholder,#fs_billing_address.fs-read-mode select::placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%}@media screen and (max-width:639px){#fs_account .fs-header-actions{margin:0 0 12px;padding:0 15px 12px;position:static}#fs_account .fs-header-actions li{display:inline-block;float:none}#fs_account #fs_account_details,#fs_account #fs_account_details tbody,#fs_account #fs_account_details td,#fs_account #fs_account_details th,#fs_account #fs_account_details tr{display:block}#fs_account #fs_account_details tr td:first-child{text-align:left}#fs_account #fs_account_details tr td:nth-child(2){padding:0 12px}#fs_account #fs_account_details tr td:nth-child(2) code{margin:0;padding:0}#fs_account #fs_account_details tr td:nth-child(2) label{margin-left:0}#fs_account #fs_account_details tr td:nth-child(3){text-align:left}#fs_account #fs_account_details tr.fs-field-plan td:nth-child(2) .button-group{float:none;margin:12px 0}} \ No newline at end of file diff --git a/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg b/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg deleted file mode 100644 index a7cc12b..0000000 --- a/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/freemius/assets/js/pricing/45da596e2b512ffc3bb638baaf0fdc4e.png b/freemius/assets/js/pricing/45da596e2b512ffc3bb638baaf0fdc4e.png new file mode 100644 index 0000000000000000000000000000000000000000..3386bb4a90529c1790d33bd5f91a01c4f2b4edf1 GIT binary patch literal 4538 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 GqW%jRKEH1O literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png b/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png deleted file mode 100644 index eda6d6fb12df205fd9fd23ff0ff63d1d57d2ec97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7262 zcmaKRWl$X5+AZ!R1b2qPeFhsexFxtl@L_Nm7zTIO03n9EyM;h-Cjo*6cTI2z5S&ZS zci!{eANSm@>gxUMT5CPBS5TzcwEO zDx`uTQU`8>^s+?2P-Lv(Rxp5yv!yLe8)j+klz{r)l|ixaA#i2zZhO` zXV*veqnMPpt0mMCh6Gr_Z0%hnfk!PJK!ClqB+x)e4W#BO2eY$R_C>&SeARWKzK&2) zYoOF~fP}Z$qkuCEX$kOlc5-nS^OgkuOIPf1{db!W2>2HS=_m>OpP~%aGy!sO1Pma= z3*v!-gh2pdFfUj@5CVa41NcE;5Fd!256sU4hKLF9i-ACZe_z1IYzS)`F>QIpf9HCP zB!PBFq^lSopO=>xua^KX9AV1`78Mo!tHICD^N8Sa_i;g5dh@urGyS6=4|9hi>|K%e za2LQ|MN2EV2T~IFSn2eBPF>d|+PC-@aCUvnEDuB2d%~<05pZX~zgiZv z|L?U3{a3wzbFKe-EyDkm%lB9e-`|7%zYhAJtw;C#UH-?mkHLS84|92RJL0dc^W6zi zP$(x<k*VL5wQ5*z4KxsueA{`y$quQmJ$DRFpj$_pr+= z8Y^}Z0fwGz28~Yr#p{P72s%v^Ue8Jp(amgX2k7?ZH)`)KzI)FVL-r^dSxtz?{bdiX z?lC6wcQhRfjNZtgpf+@xn}^YvX6ffl%4mRK%JJ?HRL&EIxwsA)w0Kn~>8^)|B$lV> zbKJxG-=sU-`B4J8JA1$3C*M3xZdlVX>C+S$h|YDb!tQz6o=R}eg-&VVd2hQl0lmDr zL0!~Nf;>FjVb#7pSqV^<2Hm5scMwQOoLYE=FjEEHT@tu4zs2e4c;F_;mRd$BHN|r0 z+2~kdkh{P2?nNm$LcJoErX?hjp1p0RkLJn28$%T!dI)nfOk%$8A;B3dR~_ywv0cY5 zzk4|1>FMammTtU{z~UV|ehabv|Qy z6=u)M&~tsA4pKVAvd2;y_*mUR`Q1#S_xNT<3CrLUJ(dE83zk{c0|8%lg#~;1M(^?Q zcUdf|XINN_?bS6%HwJQxF6maRZF3ua!pIf#MD)2^f`<^5wTG1>f{KiJf}jTp)GHct z!3>aXbp-Y2wUwh$f{u<%tW(2IdlJ&g)pzH2oT?jECG!j@s6Wd-Byr|}Zbxo7Db%WQ zIL-tya1jUn3S<56(}i73N`{0L;yxIU@t3^z(gIjwaYp~_*SlJb#%r%hR_=L0!MgbB zd#1p^QHI2%URBR+Mylw;p8vz22c0tA?JsgM?2Z&1vn=f+S-?6~Bgp{$wU*JnY>0j= zk8dsQGO7WgIu%R#bt2-oZ1Sub=GSMPjgI!p(9|#@DIq&;TCVq_ST4}kyV1u~=8O|V zNr5Rz>WV?D80XQI<``f&ni1>4UdYkvoCV>cH@ev_@hhe7Kg?O}YCZVc(wDfEiRq$_ z#0~~c{Q;hJc+lT^l;(pL9%s4BnP&6-+lh%yf0z>?ZGUPntFW4v$PU@_ER+@QbDA4H zDfaI0y{G0Po5`}?@Mha8jV*wqiXvZ$h>)hFT=gp_#4J|rR`eSdid6$c&{V04Zbhgf zRO~Z`_Nyd!=}KqhKHa=v+EG3u{A_7=E$nM%qA=*WEFLY&@8on~$j(HCUG!}(EBC}> z1f-h1fV;aI%Xg;xI`sILCScwAJcBYqR+MRnD5BX~B(2rV@##7wd?8x)-L%JRW%c)Y zH7-dYFyZfhkm+y@Z{LOpkTIcSa>smH9DQK~lx{qI!6n}WC-#T0ddAIeX~}X#u+15P z6Jiy;>e>q=*cCK>oA2eGJwU{Mql>s_;&l3?MhHJsu)NGp<#zs1)o3>rYwY-mQSm{s%1M64jovh{^|D929p48S4X+k`R0J!IZV;8tA*jJe@?}_F>=}ypf z7cxYTWK?c@!*yfjqlGfl5B2kcK)aU$B>AP`nxx{n+t(iOQKoTQVT P2NY+Q0x0a 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 CHc1fx diff --git a/freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg b/freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg new file mode 100644 index 0000000..7a58302 --- /dev/null +++ b/freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg b/freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg new file mode 100644 index 0000000..f82f0b2 --- /dev/null +++ b/freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg b/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg deleted file mode 100644 index 930d34e..0000000 --- a/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/freemius/assets/js/pricing/d65812c447b4523b42d59018e1c0bb53.png b/freemius/assets/js/pricing/d65812c447b4523b42d59018e1c0bb53.png new file mode 100644 index 0000000000000000000000000000000000000000..5b464324849c5ef1adf7c034e241ed5c4efaa574 GIT binary patch literal 36952 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 M07*qoM6N<$f-b(d{{R30 literal 0 HcmV?d00001 diff --git a/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png b/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png deleted file mode 100644 index e2bd9b32658e1479a6589d5467cdff16f1c2bce0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12087 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 Za48O6_8h{j_kWJEON%Rr)rc4d{x3#FmGJ-o diff --git a/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png b/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png deleted file mode 100644 index 2fb194450232df293d4269448de456ea27c6f5d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11124 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg b/freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg deleted file mode 100644 index 12c3508..0000000 --- a/freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/freemius/assets/js/pricing/freemius-pricing.js b/freemius/assets/js/pricing/freemius-pricing.js index 3af76e6..cafa6c8 100644 --- a/freemius/assets/js/pricing/freemius-pricing.js +++ b/freemius/assets/js/pricing/freemius-pricing.js @@ -1,2 +1,2 @@ /*! For license information please see freemius-pricing.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Freemius=t():e.Freemius=t()}(self,(function(){return(()=>{var e={487:e=>{var t={utf8:{stringToBytes:function(e){return t.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(t.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n{var t,n;t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n={rotl:function(e,t){return e<>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&n.rotl(e,8)|4278255360&n.rotl(e,24);for(var t=0;t0;e--)t.push(Math.floor(256*Math.random()));return t},bytesToWords:function(e){for(var t=[],n=0,a=0;n>>5]|=e[n]<<24-a%32;return t},wordsToBytes:function(e){for(var t=[],n=0;n<32*e.length;n+=8)t.push(e[n>>>5]>>>24-n%32&255);return t},bytesToHex:function(e){for(var t=[],n=0;n>>4).toString(16)),t.push((15&e[n]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],n=0;n>>6*(3-i)&63)):n.push("=");return n.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var n=[],a=0,r=0;a>>6-2*r);return n}},e.exports=n},477:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,':root{--fs-ds-blue-10: #f0f6fc;--fs-ds-blue-50: #c5d9ed;--fs-ds-blue-100: #9ec2e6;--fs-ds-blue-200: #72aee6;--fs-ds-blue-300: #4f94d4;--fs-ds-blue-400: #3582c4;--fs-ds-blue-500: #2271b1;--fs-ds-blue-600: #135e96;--fs-ds-blue-700: #0a4b78;--fs-ds-blue-800: #043959;--fs-ds-blue-900: #01263a;--fs-ds-neutral-10: #f0f0f1;--fs-ds-neutral-50: #dcdcde;--fs-ds-neutral-100: #c3c4c7;--fs-ds-neutral-200: #a7aaad;--fs-ds-neutral-300: #8c8f94;--fs-ds-neutral-400: #787c82;--fs-ds-neutral-500: #646970;--fs-ds-neutral-600: #50575e;--fs-ds-neutral-700: #3c434a;--fs-ds-neutral-800: #2c3338;--fs-ds-neutral-900: #1d2327;--fs-ds-neutral-900-fade-60: rgba(29, 35, 39, .6);--fs-ds-neutral-900-fade-92: rgba(29, 35, 39, .08);--fs-ds-green-10: #b8e6bf;--fs-ds-green-100: #68de7c;--fs-ds-green-200: #1ed14b;--fs-ds-green-300: #00ba37;--fs-ds-green-400: #00a32a;--fs-ds-green-500: #008a20;--fs-ds-green-600: #007017;--fs-ds-green-700: #005c12;--fs-ds-green-800: #00450c;--fs-ds-green-900: #003008;--fs-ds-red-10: #facfd2;--fs-ds-red-100: #ffabaf;--fs-ds-red-200: #ff8085;--fs-ds-red-300: #f86368;--fs-ds-red-400: #e65054;--fs-ds-red-500: #d63638;--fs-ds-red-600: #b32d2e;--fs-ds-red-700: #8a2424;--fs-ds-red-800: #691c1c;--fs-ds-red-900: #451313;--fs-ds-yellow-10: #fcf9e8;--fs-ds-yellow-100: #f2d675;--fs-ds-yellow-200: #f0c33c;--fs-ds-yellow-300: #dba617;--fs-ds-yellow-400: #bd8600;--fs-ds-yellow-500: #996800;--fs-ds-yellow-600: #755100;--fs-ds-yellow-700: #614200;--fs-ds-yellow-800: #4a3200;--fs-ds-yellow-900: #362400;--fs-ds-white-10: #ffffff}#fs_pricing_app,#fs_pricing_wrapper{--fs-ds-theme-primary-accent-color: var(--fs-ds-blue-500);--fs-ds-theme-primary-accent-color-hover: var(--fs-ds-blue-600);--fs-ds-theme-primary-green-color: var(--fs-ds-green-500);--fs-ds-theme-primary-red-color: var(--fs-ds-red-500);--fs-ds-theme-primary-yellow-color: var(--fs-ds-yellow-500);--fs-ds-theme-error-color: var(--fs-ds-theme-primary-red-color);--fs-ds-theme-success-color: var(--fs-ds-theme-primary-green-color);--fs-ds-theme-warn-color: var(--fs-ds-theme-primary-yellow-color);--fs-ds-theme-background-color: var(--fs-ds-white-10);--fs-ds-theme-background-shade: var(--fs-ds-neutral-10);--fs-ds-theme-background-accented: var(--fs-ds-neutral-50);--fs-ds-theme-background-hover: var(--fs-ds-neutral-200);--fs-ds-theme-background-overlay: var(--fs-ds-neutral-900-fade-60);--fs-ds-theme-background-dark: var(--fs-ds-neutral-800);--fs-ds-theme-background-darkest: var(--fs-ds-neutral-900);--fs-ds-theme-text-color: var(--fs-ds-neutral-900);--fs-ds-theme-heading-text-color: var(--fs-ds-neutral-800);--fs-ds-theme-muted-text-color: var(--fs-ds-neutral-600);--fs-ds-theme-dark-background-text-color: var(--fs-ds-white-10);--fs-ds-theme-dark-background-muted-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-divider-color: var(--fs-ds-theme-background-accented);--fs-ds-theme-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-background-hover-color: var(--fs-ds-neutral-200);--fs-ds-theme-button-text-color: var(--fs-ds-theme-heading-text-color);--fs-ds-theme-button-border-color: var(--fs-ds-neutral-300);--fs-ds-theme-button-border-hover-color: var(--fs-ds-neutral-600);--fs-ds-theme-button-border-focus-color: var(--fs-ds-blue-400);--fs-ds-theme-button-primary-background-color: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-button-primary-background-hover-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-button-primary-text-color: var(--fs-ds-white-10);--fs-ds-theme-button-primary-border-color: var(--fs-ds-blue-800);--fs-ds-theme-button-primary-border-hover-color: var(--fs-ds-blue-900);--fs-ds-theme-button-primary-border-focus-color: var(--fs-ds-blue-100);--fs-ds-theme-button-disabled-border-color: var(--fs-ds-neutral-100);--fs-ds-theme-button-disabled-background-color: var(--fs-ds-neutral-50);--fs-ds-theme-button-disabled-text-color: var(--fs-ds-neutral-300);--fs-ds-theme-notice-warn-background: var(--fs-ds-yellow-10);--fs-ds-theme-notice-warn-color: var(--fs-ds-yellow-900);--fs-ds-theme-notice-warn-border: var(--fs-ds-theme-warn-color);--fs-ds-theme-notice-info-background: var(--fs-ds-theme-background-shade);--fs-ds-theme-notice-info-color: var(--fs-ds-theme-primary-accent-color-hover);--fs-ds-theme-notice-info-border: var(--fs-ds-theme-primary-accent-color);--fs-ds-theme-package-popular-background: var(--fs-ds-blue-200);--fs-ds-theme-testimonial-star-color: var(--fs-ds-yellow-300)}#fs_pricing.fs-full-size-wrapper{margin-top:0}#root,#fs_pricing_app{background:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-text-color);height:auto;line-height:normal;font-size:13px;margin:0}#root h1,#root h2,#root h3,#root h4,#root ul,#root blockquote,#fs_pricing_app h1,#fs_pricing_app h2,#fs_pricing_app h3,#fs_pricing_app h4,#fs_pricing_app ul,#fs_pricing_app blockquote{margin:0;padding:0;text-align:center;color:var(--fs-ds-theme-heading-text-color)}#root h1,#fs_pricing_app h1{font-size:2.5em}#root h2,#fs_pricing_app h2{font-size:1.5em}#root h3,#fs_pricing_app h3{font-size:1.2em}#root ul,#fs_pricing_app ul{list-style-type:none}#root p,#fs_pricing_app p{font-size:.9em}#root p,#root blockquote,#fs_pricing_app p,#fs_pricing_app blockquote{color:var(--fs-ds-theme-text-color)}#root strong,#fs_pricing_app strong{font-weight:700}#root li,#root dd,#fs_pricing_app li,#fs_pricing_app dd{margin:0}#root .fs-app-header .fs-page-title,#fs_pricing_app .fs-app-header .fs-page-title{margin:0 0 15px;text-align:left;display:flex;flex-flow:row wrap;gap:10px;align-items:center;padding:20px 15px 10px}#root .fs-app-header .fs-page-title h1,#fs_pricing_app .fs-app-header .fs-page-title h1{font-size:18px;margin:0}#root .fs-app-header .fs-page-title h3,#fs_pricing_app .fs-app-header .fs-page-title h3{margin:0;font-size:14px;padding:4px 8px;font-weight:400;border-radius:4px;background-color:var(--fs-ds-theme-background-accented);color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-header .fs-plugin-title-and-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo{margin:0 15px;background:var(--fs-ds-theme-background-color);padding:12px 0;border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;text-align:center}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#root .fs-app-header .fs-plugin-title-and-logo h1,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo h1{display:inline-block;vertical-align:middle;margin:0 10px}#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:48px;height:48px;border-radius:4px}@media screen and (min-width: 601px){#root .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo,#fs_pricing_app .fs-app-header .fs-plugin-title-and-logo .fs-plugin-logo{width:64px;height:64px}}#root .fs-trial-message,#fs_pricing_app .fs-trial-message{padding:20px;background:var(--fs-ds-theme-notice-warn-background);color:var(--fs-ds-theme-notice-warn-color);font-weight:700;text-align:center;border-top:1px solid var(--fs-ds-theme-notice-warn-border);border-bottom:1px solid var(--fs-ds-theme-notice-warn-border);font-size:1.2em;box-sizing:border-box;margin:0 0 5px}#root .fs-app-main,#fs_pricing_app .fs-app-main{text-align:center}#root .fs-app-main .fs-section,#fs_pricing_app .fs-app-main .fs-section{margin:auto;display:block}#root .fs-app-main .fs-section .fs-section-header,#fs_pricing_app .fs-app-main .fs-section .fs-section-header{font-weight:700}#root .fs-app-main>.fs-section,#fs_pricing_app .fs-app-main>.fs-section{padding:20px;margin:4em auto 0}#root .fs-app-main>.fs-section:nth-child(even),#fs_pricing_app .fs-app-main>.fs-section:nth-child(even){background:var(--fs-ds-theme-background-color)}#root .fs-app-main>.fs-section>header,#fs_pricing_app .fs-app-main>.fs-section>header{margin:0 0 3em}#root .fs-app-main>.fs-section>header h2,#fs_pricing_app .fs-app-main>.fs-section>header h2{margin:0;font-size:2.5em}#root .fs-app-main .fs-section--plans-and-pricing,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing{padding:20px;margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section{margin:1.5em auto 0}#root .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing>.fs-section:first-child{margin-top:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-annual-discount{font-weight:700;font-size:small}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header{text-align:center;background:var(--fs-ds-theme-background-color);padding:20px;border-radius:5px;box-sizing:border-box;max-width:945px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h2{margin-bottom:10px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--trial-header h4{font-weight:400}#root .fs-app-main .fs-section--plans-and-pricing .fs-currencies,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-currencies{border-color:var(--fs-ds-theme-button-border-color)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles{display:inline-block;vertical-align:middle;padding:0 10px;width:auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles{overflow:hidden}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li{border:1px solid var(--fs-ds-theme-border-color);border-right-width:0;display:inline-block;font-weight:700;margin:0;padding:10px;cursor:pointer}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:first-child{border-radius:20px 0 0 20px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li:last-child{border-radius:0 20px 20px 0;border-right-width:1px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--billing-cycles .fs-billing-cycles li.fs-selected-billing-cycle{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation{padding:15px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-divider-color);border-radius:4px;box-sizing:border-box;max-width:945px;margin:0 auto}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation h2{margin-bottom:10px;font-weight:700}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--custom-implementation p{font-size:small;margin:0}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee{max-width:857px;margin:30px auto;position:relative}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-title{color:var(--fs-ds-theme-heading-text-color);font-weight:700;margin-bottom:15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee .fs-money-back-guarantee-message{font-size:small;line-height:20px;margin-bottom:15px;padding:0 15px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--money-back-guarantee img{position:absolute;width:90px;top:50%;right:0;margin-top:-45px}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge{display:inline-block;vertical-align:middle;position:relative;box-shadow:none;background:transparent}#root .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge,#fs_pricing_app .fs-app-main .fs-section--plans-and-pricing .fs-section--badges .fs-badge+.fs-badge{margin-left:20px;margin-top:13px}#root .fs-app-main .fs-section--testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials{border-top:1px solid var(--fs-ds-theme-border-color);border-bottom:1px solid var(--fs-ds-theme-border-color);padding:3em 4em 4em}#root .fs-app-main .fs-section--testimonials .fs-section-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-section-header{margin-left:-30px;margin-right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav{margin:auto;display:block;width:auto;position:relative}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{top:50%;border:1px solid var(--fs-ds-theme-border-color);border-radius:14px;cursor:pointer;margin-top:11px;position:absolute}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev .fs-icon,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next .fs-icon{display:inline-block;height:1em;width:1em;line-height:1em;color:var(--fs-ds-theme-muted-text-color);padding:5px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-prev{margin-left:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-nav.fs-nav-next{right:-30px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials-track{margin:auto;overflow:hidden;position:relative;display:block;padding-top:45px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials{width:10000px;display:block;position:relative;transition:left .5s ease,right .5s ease}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{float:left;font-size:small;position:relative;width:340px;box-sizing:border-box;margin:0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{box-sizing:border-box}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-rating{color:var(--fs-ds-theme-testimonial-star-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{background:var(--fs-ds-theme-background-color);padding:10px;margin:0 2em;border:1px solid var(--fs-ds-theme-divider-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial>section{border-radius:0 0 8px 8px;border-top:0 none}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header{border-bottom:0 none;border-radius:8px 8px 0 0}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo{border:1px solid var(--fs-ds-theme-divider-color);border-radius:44px;padding:5px;background:var(--fs-ds-theme-background-color);width:76px;height:76px;position:relative;margin-top:-54px;left:50%;margin-left:-44px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo object,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header .fs-testimonial-logo img{max-width:100%;border-radius:40px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-header h4{margin:15px 0 6px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-icon-quote{color:var(--fs-ds-theme-muted-text-color)}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-message{line-height:18px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author{margin-top:30px;margin-bottom:10px}#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial .fs-testimonial-author .fs-testimonial-author-name{font-weight:700;margin-bottom:2px;color:var(--fs-ds-theme-text-color)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{margin:4em 0 0;position:relative}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li{position:relative;display:inline-block;margin:0 8px}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button{cursor:pointer;border:1px solid var(--fs-ds-theme-border-color);vertical-align:middle;display:inline-block;line-height:0;width:8px;height:8px;padding:0;color:transparent;outline:none;border-radius:4px;overflow:hidden}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li button.fs-round-button span{display:inline-block;width:100%;height:100%;background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button{border:0 none}#root .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination li.selected button.fs-round-button span{background:var(--fs-ds-theme-background-accented)}#root .fs-app-main .fs-section--faq,#fs_pricing_app .fs-app-main .fs-section--faq{background:var(--fs-ds-theme-background-shade)}#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{max-width:945px;margin:0 auto;box-sizing:border-box;text-align:left;columns:2;column-gap:20px}@media only screen and (max-width: 600px){#root .fs-app-main .fs-section--faq .fs-section--faq-items,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items{columns:1}}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item{width:100%;display:inline-block;vertical-align:top;margin:0 0 20px;overflow:hidden}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{margin:0;text-align:left}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item h3{background:var(--fs-ds-theme-background-dark);color:var(--fs-ds-theme-dark-background-text-color);padding:15px;font-weight:700;border:1px solid var(--fs-ds-theme-background-darkest);border-bottom:0 none;border-radius:4px 4px 0 0}#root .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p,#fs_pricing_app .fs-app-main .fs-section--faq .fs-section--faq-items .fs-section--faq-item p{background:var(--fs-ds-theme-background-color);font-size:small;padding:15px;line-height:20px;border:1px solid var(--fs-ds-theme-border-color);border-top:0 none;border-radius:0 0 4px 4px}#root .fs-button,#fs_pricing_app .fs-button{background:var(--fs-ds-theme-button-background-color);color:var(--fs-ds-theme-button-text-color);padding:12px 10px;display:inline-block;text-transform:uppercase;font-weight:700;font-size:18px;width:100%;border-radius:4px;border:0 none;cursor:pointer;transition:background .2s ease-out,border-bottom-color .2s ease-out}#root .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled){box-shadow:0 0 0 1px var(--fs-ds-theme-button-border-focus-color)}#root .fs-button:hover:not(:disabled),#root .fs-button:focus:not(:disabled),#root .fs-button:active:not(:disabled),#fs_pricing_app .fs-button:hover:not(:disabled),#fs_pricing_app .fs-button:focus:not(:disabled),#fs_pricing_app .fs-button:active:not(:disabled){will-change:background,border;background:var(--fs-ds-theme-button-background-hover-color)}#root .fs-button.fs-button--outline,#fs_pricing_app .fs-button.fs-button--outline{padding-top:11px;padding-bottom:11px;background:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-button-border-color)}#root .fs-button.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:focus:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-focus-color)}#root .fs-button.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--outline:active:not(:disabled){background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-button-border-hover-color)}#root .fs-button.fs-button--type-primary,#fs_pricing_app .fs-button.fs-button--type-primary{background-color:var(--fs-ds-theme-button-primary-background-color);color:var(--fs-ds-theme-button-primary-text-color);border-color:var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary:focus:not(:disabled),#root .fs-button.fs-button--type-primary:hover:not(:disabled),#root .fs-button.fs-button--type-primary:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary:active:not(:disabled){background-color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-button-primary-border-hover-color)}#root .fs-button.fs-button--type-primary.fs-button--outline,#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline{background-color:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color);border:1px solid var(--fs-ds-theme-button-primary-border-color)}#root .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#root .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:focus:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:hover:not(:disabled),#fs_pricing_app .fs-button.fs-button--type-primary.fs-button--outline:active:not(:disabled){background-color:var(--fs-ds-theme-background-shade);color:var(--fs-ds-theme-button-primary-background-hover-color);border-color:var(--fs-ds-theme-primary-accent-color-hover)}#root .fs-button:disabled,#fs_pricing_app .fs-button:disabled{cursor:not-allowed;background-color:var(--fs-ds-theme-button-disabled-background-color);color:var(--fs-ds-theme-button-disabled-text-color);border-color:var(--fs-ds-theme-button-disabled-border-color)}#root .fs-button.fs-button--size-small,#fs_pricing_app .fs-button.fs-button--size-small{font-size:14px;width:auto}#root .fs-placeholder:before,#fs_pricing_app .fs-placeholder:before{content:"";display:inline-block}@media only screen and (max-width: 768px){#root .fs-app-main .fs-section--testimonials .fs-nav-pagination,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-nav-pagination{display:none!important}#root .fs-app-main .fs-section>header h2,#fs_pricing_app .fs-app-main .fs-section>header h2{font-size:1.5em}}@media only screen and (max-width: 455px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}#root .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span,#fs_pricing_app .fs-app-main .fs-section--billing-cycles .fs-billing-cycles li.fs-period--annual span{display:none}}@media only screen and (max-width: 375px){#root .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial,#fs_pricing_app .fs-app-main .fs-section--testimonials .fs-testimonials-nav .fs-testimonials .fs-testimonial{width:auto}}\n',""]);const s=o},333:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,"#fs_pricing_app .fs-modal,#fs_pricing_wrapper .fs-modal,#fs_pricing_wrapper #fs_pricing_app .fs-modal{position:fixed;inset:0;z-index:1000;zoom:1;text-align:left;display:block!important}#fs_pricing_app .fs-modal .fs-modal-content-container,#fs_pricing_wrapper .fs-modal .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container{display:block;position:absolute;left:50%;background:var(--fs-ds-theme-background-color);box-shadow:0 0 8px 2px #0000004d}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header{background:var(--fs-ds-theme-primary-accent-color);padding:15px}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header h3,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-header .fs-modal-close{color:var(--fs-ds-theme-background-color)}#fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal .fs-modal-content-container .fs-modal-content{font-size:1.2em}#fs_pricing_app .fs-modal--loading,#fs_pricing_wrapper .fs-modal--loading,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading{background-color:#0000004d}#fs_pricing_app .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container{width:220px;margin-left:-126px;padding:15px;border:1px solid var(--fs-ds-theme-divider-color);text-align:center;top:50%}#fs_pricing_app .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container span,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container span{display:block;font-weight:700;font-size:16px;text-align:center;color:var(--fs-ds-theme-primary-accent-color);margin-bottom:10px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container .fs-ajax-loader,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container .fs-ajax-loader{width:160px}#fs_pricing_app .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper .fs-modal--loading .fs-modal-content-container i,#fs_pricing_wrapper #fs_pricing_app .fs-modal--loading .fs-modal-content-container i{display:block;width:128px;margin:0 auto;height:15px;background:url(//img.freemius.com/blue-loader.gif)}#fs_pricing_app .fs-modal--refund-policy,#fs_pricing_app .fs-modal--trial-confirmation,#fs_pricing_wrapper .fs-modal--refund-policy,#fs_pricing_wrapper .fs-modal--trial-confirmation,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation{background:rgba(0,0,0,.7)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container{width:510px;margin-left:-255px;top:20%}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-header .fs-modal-close,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-header .fs-modal-close{line-height:24px;font-size:24px;position:absolute;top:-12px;right:-12px;cursor:pointer}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-content,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-content{height:100%;padding:1px 15px}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer{padding:10px;text-align:right;border-top:1px solid var(--fs-ds-theme-border-color);background:var(--fs-ds-theme-background-shade)}#fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--refund-policy .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-modal-content-container .fs-modal-footer .fs-button--approve-trial{margin:0 7px}#fs_pricing_app .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper .fs-modal--trial-confirmation .fs-button,#fs_pricing_wrapper #fs_pricing_app .fs-modal--trial-confirmation .fs-button{width:auto;font-size:13px}\n",""]);const s=o},267:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-package,#fs_pricing_app .fs-package{display:inline-block;vertical-align:top;background:var(--fs-ds-theme-dark-background-text-color);border-bottom:3px solid var(--fs-ds-theme-border-color);width:315px;box-sizing:border-box}#root .fs-package:first-child,#root .fs-package+.fs-package,#fs_pricing_app .fs-package:first-child,#fs_pricing_app .fs-package+.fs-package{border-left:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:last-child,#fs_pricing_app .fs-package:last-child{border-right:1px solid var(--fs-ds-theme-divider-color)}#root .fs-package:not(.fs-featured-plan):first-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child{border-top-left-radius:10px}#root .fs-package:not(.fs-featured-plan):first-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):first-child .fs-plan-title{border-top-left-radius:9px}#root .fs-package:not(.fs-featured-plan):last-child,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child{border-top-right-radius:10px}#root .fs-package:not(.fs-featured-plan):last-child .fs-plan-title,#fs_pricing_app .fs-package:not(.fs-featured-plan):last-child .fs-plan-title{border-top-right-radius:9px}#root .fs-package .fs-package-content,#fs_pricing_app .fs-package .fs-package-content{vertical-align:middle;padding-bottom:30px}#root .fs-package .fs-plan-title,#fs_pricing_app .fs-package .fs-plan-title{padding:10px 0;background:var(--fs-ds-theme-background-shade);text-transform:uppercase;border-bottom:1px solid var(--fs-ds-theme-divider-color);border-top:1px solid var(--fs-ds-theme-divider-color);width:100%;text-align:center}#root .fs-package .fs-plan-title:last-child,#fs_pricing_app .fs-package .fs-plan-title:last-child{border-right:none}#root .fs-package .fs-plan-description,#root .fs-package .fs-undiscounted-price,#root .fs-package .fs-licenses,#root .fs-package .fs-upgrade-button,#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-licenses,#fs_pricing_app .fs-package .fs-upgrade-button,#fs_pricing_app .fs-package .fs-plan-features{margin-top:10px}#root .fs-package .fs-plan-description,#fs_pricing_app .fs-package .fs-plan-description{text-transform:uppercase}#root .fs-package .fs-undiscounted-price,#fs_pricing_app .fs-package .fs-undiscounted-price{margin:auto;position:relative;display:inline-block;color:var(--fs-ds-theme-muted-text-color);top:6px}#root .fs-package .fs-undiscounted-price:after,#fs_pricing_app .fs-package .fs-undiscounted-price:after{display:block;content:"";position:absolute;height:1px;background-color:var(--fs-ds-theme-error-color);left:-4px;right:-4px;top:50%;transform:translateY(-50%) skewY(1deg)}#root .fs-package .fs-selected-pricing-amount,#fs_pricing_app .fs-package .fs-selected-pricing-amount{margin:5px 0}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol{font-size:39px}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer{font-size:58px;margin:0 5px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{display:inline-block;vertical-align:middle}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer:not(.fs-selected-pricing-amount-integer),#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container:not(.fs-selected-pricing-amount-integer){line-height:18px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{display:block;font-size:12px}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-fraction,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-fraction{vertical-align:top}#root .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-currency-symbol .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-integer .fs-selected-pricing-amount-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container .fs-selected-pricing-amount-cycle{vertical-align:bottom}#root .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container,#fs_pricing_app .fs-package .fs-selected-pricing-amount .fs-selected-pricing-amount-fraction-container{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-amount-free,#fs_pricing_app .fs-package .fs-selected-pricing-amount-free{font-size:48px}#root .fs-package .fs-selected-pricing-cycle,#fs_pricing_app .fs-package .fs-selected-pricing-cycle{margin-bottom:5px;text-transform:uppercase;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-selected-pricing-license-quantity .fs-tooltip,#fs_pricing_app .fs-package .fs-selected-pricing-license-quantity .fs-tooltip{margin-left:5px}#root .fs-package .fs-upgrade-button-container,#fs_pricing_app .fs-package .fs-upgrade-button-container{padding:0 13px;display:block}#root .fs-package .fs-upgrade-button-container .fs-upgrade-button,#fs_pricing_app .fs-package .fs-upgrade-button-container .fs-upgrade-button{margin-top:20px;margin-bottom:5px}#root .fs-package .fs-plan-features,#fs_pricing_app .fs-package .fs-plan-features{text-align:left;margin-left:13px}#root .fs-package .fs-plan-features li,#fs_pricing_app .fs-package .fs-plan-features li{font-size:16px;display:flex;margin-bottom:8px}#root .fs-package .fs-plan-features li:not(:first-child),#fs_pricing_app .fs-package .fs-plan-features li:not(:first-child){margin-top:8px}#root .fs-package .fs-plan-features li>span,#root .fs-package .fs-plan-features li .fs-tooltip,#fs_pricing_app .fs-package .fs-plan-features li>span,#fs_pricing_app .fs-package .fs-plan-features li .fs-tooltip{font-size:small;vertical-align:middle;display:inline-block}#root .fs-package .fs-plan-features li .fs-feature-title,#fs_pricing_app .fs-package .fs-plan-features li .fs-feature-title{margin:0 5px;color:var(--fs-ds-theme-muted-text-color);max-width:260px;overflow-wrap:break-word}#root .fs-package .fs-support-and-main-features,#fs_pricing_app .fs-package .fs-support-and-main-features{margin-top:12px;padding-top:18px;padding-bottom:18px;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-support-and-main-features .fs-plan-support,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-support{margin-bottom:15px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li{font-size:small}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title,#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li .fs-feature-title{margin:0 2px}#root .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child),#fs_pricing_app .fs-package .fs-support-and-main-features .fs-plan-features-with-value li:not(:first-child){margin-top:5px}#root .fs-package .fs-plan-features-with-value,#fs_pricing_app .fs-package .fs-plan-features-with-value{color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities{border-collapse:collapse;position:relative;width:100%}#root .fs-package .fs-license-quantities,#root .fs-package .fs-license-quantities input,#fs_pricing_app .fs-package .fs-license-quantities,#fs_pricing_app .fs-package .fs-license-quantities input{cursor:pointer}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span{background-color:var(--fs-ds-theme-background-color);border:1px solid var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-primary-accent-color);display:inline;padding:4px 8px;border-radius:4px;font-weight:700;margin:0 5px;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount span.fs-license-quantity-no-discount{visibility:hidden}#root .fs-package .fs-license-quantities .fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container{line-height:30px;border-top:1px solid var(--fs-ds-theme-background-shade);font-size:small;color:var(--fs-ds-theme-muted-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child{border-bottom:1px solid var(--fs-ds-theme-background-shade)}#root .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container:last-child.fs-license-quantity-selected{border-bottom-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected{background:var(--fs-ds-theme-background-shade);border-color:var(--fs-ds-theme-divider-color);color:var(--fs-ds-theme-text-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container.fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-divider-color)}#root .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price),#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-container>td:not(.fs-license-quantity-discount):not(.fs-license-quantity-price){text-align:left}#root .fs-package .fs-license-quantities .fs-license-quantity,#root .fs-package .fs-license-quantities .fs-license-quantity-discount,#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-discount,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{vertical-align:middle}#root .fs-package .fs-license-quantities .fs-license-quantity,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity{position:relative;white-space:nowrap}#root .fs-package .fs-license-quantities .fs-license-quantity input,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity input{position:relative;margin-top:-1px;margin-left:7px;margin-right:7px}#root .fs-package .fs-license-quantities .fs-license-quantity-price,#fs_pricing_app .fs-package .fs-license-quantities .fs-license-quantity-price{position:relative;margin-right:auto;padding-right:7px;white-space:nowrap;font-variant-numeric:tabular-nums;text-align:right}#root .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child),#fs_pricing_app .fs-package.fs-free-plan .fs-license-quantity-container:not(:last-child){border-color:transparent}#root .fs-package .fs-most-popular,#fs_pricing_app .fs-package .fs-most-popular{display:none}#root .fs-package.fs-featured-plan .fs-most-popular,#fs_pricing_app .fs-package.fs-featured-plan .fs-most-popular{display:block;line-height:2.8em;margin-top:-2.8em;border-radius:10px 10px 0 0;color:var(--fs-ds-theme-text-color);background:var(--fs-ds-theme-package-popular-background);text-transform:uppercase;font-size:14px}#root .fs-package.fs-featured-plan .fs-plan-title,#fs_pricing_app .fs-package.fs-featured-plan .fs-plan-title{color:var(--fs-ds-theme-dark-background-text-color);background:var(--fs-ds-theme-primary-accent-color);border-top-color:var(--fs-ds-theme-primary-accent-color);border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity,#fs_pricing_app .fs-package.fs-featured-plan .fs-selected-pricing-license-quantity{color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantity-discount span{background:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected{background:var(--fs-ds-theme-primary-accent-color);border-color:var(--fs-ds-theme-primary-accent-color);color:var(--fs-ds-theme-dark-background-text-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected+.fs-license-quantity-container{border-top-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected:last-child{border-bottom-color:var(--fs-ds-theme-primary-accent-color)}#root .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span,#fs_pricing_app .fs-package.fs-featured-plan .fs-license-quantities .fs-license-quantity-selected .fs-license-quantity-discount span{background:var(--fs-ds-theme-background-color);color:var(--fs-ds-theme-primary-accent-color-hover)}\n',""]);const s=o},700:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-section--packages,#fs_pricing_app .fs-section--packages{display:inline-block;width:100%;position:relative}#root .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--packages .fs-packages-menu{display:none;flex-wrap:wrap;justify-content:center}#root .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--packages .fs-packages-tab{display:none}#root .fs-section--packages .fs-package-tab,#fs_pricing_app .fs-section--packages .fs-package-tab{display:inline-block;flex:1}#root .fs-section--packages .fs-package-tab a,#fs_pricing_app .fs-section--packages .fs-package-tab a{display:block;padding:4px 10px 7px;border-bottom:2px solid transparent;color:#000;text-align:center;text-decoration:none}#root .fs-section--packages .fs-package-tab.fs-package-tab--selected a,#fs_pricing_app .fs-section--packages .fs-package-tab.fs-package-tab--selected a{border-color:#0085ba}#root .fs-section--packages .fs-packages-nav,#fs_pricing_app .fs-section--packages .fs-packages-nav{position:relative;overflow:hidden;margin:auto}#root .fs-section--packages .fs-packages-nav:before,#root .fs-section--packages .fs-packages-nav:after,#fs_pricing_app .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:after{position:absolute;top:0;bottom:0;width:60px;margin-bottom:32px}#root .fs-section--packages .fs-packages-nav:before,#fs_pricing_app .fs-section--packages .fs-packages-nav:before{z-index:1}#root .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-previous-plan:before{content:"";left:0;background:linear-gradient(to right,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-next-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-next-plan:after{content:"";right:0;background:linear-gradient(to left,#cccccc96,transparent)}#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#root .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:before,#fs_pricing_app .fs-section--packages .fs-packages-nav.fs-has-featured-plan:after{top:2.8em}#root .fs-section--packages .fs-prev-package,#root .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-next-package{position:absolute;top:50%;margin-top:-11px;cursor:pointer;font-size:48px;z-index:1}#root .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--packages .fs-prev-package{visibility:hidden;z-index:2}#root .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:2.8em}#root .fs-section--packages .fs-packages,#fs_pricing_app .fs-section--packages .fs-packages{width:auto;display:flex;flex-direction:row;margin-left:auto;margin-right:auto;margin-bottom:30px;border-top-right-radius:10px;position:relative;transition:left .5s ease,right .5s ease;padding-top:5px}#root .fs-section--packages .fs-packages:before,#fs_pricing_app .fs-section--packages .fs-packages:before{content:"";position:absolute;top:0;right:0;bottom:0;width:100px;height:100px}@media only screen and (max-width: 768px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#root .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-next-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-prev-package{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-menu{display:block;font-size:24px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages-tab{display:flex;font-size:18px;margin:0 auto 10px}#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#root .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-most-popular,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-package .fs-most-popular{display:none}#root .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-has-featured-plan .fs-packages{margin-top:0}}@media only screen and (max-width: 455px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}#root .fs-section--plans-and-pricing,#fs_pricing_app .fs-section--plans-and-pricing{padding:10px}}@media only screen and (max-width: 375px){#root .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package,#fs_pricing_app .fs-section--plans-and-pricing .fs-section--packages .fs-packages .fs-package{width:100%}}\n',""]);const s=o},302:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s});var a=n(81),r=n.n(a),i=n(645),o=n.n(i)()(r());o.push([e.id,'#root .fs-tooltip,#fs_pricing_app .fs-tooltip{cursor:help;position:relative;color:inherit}#root .fs-tooltip .fs-tooltip-message,#fs_pricing_app .fs-tooltip .fs-tooltip-message{position:absolute;width:200px;background:var(--fs-ds-theme-background-darkest);z-index:1;display:none;border-radius:4px;color:var(--fs-ds-theme-dark-background-text-color);padding:8px;text-align:left;line-height:18px}#root .fs-tooltip .fs-tooltip-message:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message:before{content:"";position:absolute;z-index:1}#root .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none),#fs_pricing_app .fs-tooltip .fs-tooltip-message:not(.fs-tooltip-message--position-none){display:block}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right{transform:translateY(-50%);left:30px;top:8px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-right:before{left:-8px;top:50%;margin-top:-6px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top{left:50%;bottom:30px;transform:translate(-50%)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top:before{left:50%;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right{right:-10px;bottom:30px}#root .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before,#fs_pricing_app .fs-tooltip .fs-tooltip-message.fs-tooltip-message--position-top-right:before{right:10px;bottom:-8px;margin-left:-6px;border-right:6px solid transparent;border-left:6px solid transparent;border-top:8px solid var(--fs-ds-theme-background-darkest)}\n',""]);const s=o},645:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",a=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),a&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),a&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,a,r,i){"string"==typeof e&&(e=[[null,e,void 0]]);var o={};if(a)for(var s=0;s0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=i),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),r&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=r):u[4]="".concat(r)),t.push(u))}},t}},81:e=>{"use strict";e.exports=function(e){return e[1]}},867:(e,t,n)=>{let a=document.getElementById("fs_pricing_wrapper");a&&a.dataset&&a.dataset.publicUrl&&(n.p=a.dataset.publicUrl)},738:e=>{function t(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}e.exports=function(e){return null!=e&&(t(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&t(e.slice(0,0))}(e)||!!e._isBuffer)}},568:(e,t,n)=>{var a,r,i,o,s;a=n(12),r=n(487).utf8,i=n(738),o=n(487).bin,(s=function(e,t){e.constructor==String?e=t&&"binary"===t.encoding?o.stringToBytes(e):r.stringToBytes(e):i(e)?e=Array.prototype.slice.call(e,0):Array.isArray(e)||e.constructor===Uint8Array||(e=e.toString());for(var n=a.bytesToWords(e),l=8*e.length,c=1732584193,u=-271733879,f=-1732584194,p=271733878,d=0;d>>24)|4278255360&(n[d]<<24|n[d]>>>8);n[l>>>5]|=128<>>9<<4)]=l;var m=s._ff,g=s._gg,h=s._hh,b=s._ii;for(d=0;d>>0,u=u+v>>>0,f=f+k>>>0,p=p+_>>>0}return a.endian([c,u,f,p])})._ff=function(e,t,n,a,r,i,o){var s=e+(t&n|~t&a)+(r>>>0)+o;return(s<>>32-i)+t},s._gg=function(e,t,n,a,r,i,o){var s=e+(t&a|n&~a)+(r>>>0)+o;return(s<>>32-i)+t},s._hh=function(e,t,n,a,r,i,o){var s=e+(t^n^a)+(r>>>0)+o;return(s<>>32-i)+t},s._ii=function(e,t,n,a,r,i,o){var s=e+(n^(t|~a))+(r>>>0)+o;return(s<>>32-i)+t},s._blocksize=16,s._digestsize=16,e.exports=function(e,t){if(null==e)throw new Error("Illegal argument "+e);var n=a.wordsToBytes(s(e,t));return t&&t.asBytes?n:t&&t.asString?o.bytesToString(n):a.bytesToHex(n)}},418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function r(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach((function(e){a[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},a)).join("")}catch(e){return!1}}()?Object.assign:function(e,i){for(var o,s,l=r(e),c=1;c{"use strict";var a=n(414);function r(){}function i(){}i.resetWarningCache=r,e.exports=function(){function e(e,t,n,r,i,o){if(o!==a){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:r};return n.PropTypes=n,n}},697:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},448:(e,t,n)=>{"use strict";var a=n(294),r=n(418),i=n(840);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n

    ' . "\n"; @@ -1179,6 +1180,7 @@ function radio_station_enqueue_onboarding_styles() { // --- add onboarding styles --- if ( ( 'dashboard' == $current_screen->base ) + || ( isset( $_REQUEST['page'] ) && ( 'radio-content' == $_REQUEST['page'] ) ) || ( isset( $_REQUEST['page'] ) && ( 'radio-station' == $_REQUEST['page'] ) ) ) { radio_station_enqueue_style( 'admin' ); $css = radio_station_onboarding_styles(); diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 9012b83..5ce0a2c 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -7218,7 +7218,7 @@ function radio_station_columns_query_filter( $query ) { if ( RADIO_STATION_OVERRIDE_SLUG === $query->get( 'post_type' ) ) { // unless order by published date is explicitly chosen - if ( 'date' !== $query->get( 'orderby' ) ) { + // if ( 'date' !== $query->get( 'orderby' ) ) { // need to loop and sync a separate meta key to enable orderby sorting // (not really efficient but at least it makes it possible!) @@ -7245,11 +7245,6 @@ function radio_station_columns_query_filter( $query ) { } } - // --- now we can set the orderby meta query to the synced key --- - $query->set( 'orderby', 'meta_value' ); - $query->set( 'meta_key', 'show_override_date' ); - $query->set( 'meta_type', 'date' ); - // --- apply override year/month filtering --- if ( isset( $_GET['month'] ) && ( '0' != sanitize_text_field( $_GET['month'] ) ) ) { $yearmonth = sanitize_text_field( $_GET['month'] ); @@ -7263,6 +7258,11 @@ function radio_station_columns_query_filter( $query ) { 'type' => 'DATE', ) ); $query->set( 'meta_query', $meta_query ); + + // --- now we can set the orderby meta query to the synced key --- + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_override_date' ); + $query->set( 'meta_type', 'date' ); } // --- meta query for past / future overrides filter --- @@ -7305,9 +7305,14 @@ function radio_station_columns_query_filter( $query ) { $meta_query = array( $pastfuture_query ); } $query->set( 'meta_query', $meta_query ); + + // --- now we can set the orderby meta query to the synced key --- + $query->set( 'orderby', 'meta_value' ); + $query->set( 'meta_key', 'show_override_date' ); + $query->set( 'meta_type', 'date' ); } - } + // } } } diff --git a/radio-station-admin.php b/radio-station-admin.php index bc6eaa3..35f5144 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -10,9 +10,11 @@ // - Enqueue Admin Scripts // - Admin Style Fixes // - Filter Plugin Action Links +// - Content Dashboard Admin Page // === Admin Menu === // - Setting Page Capability Check // - Add Admin Menu and Submenu Items +// - Content Dashboard Admin Page // - Fix to Expand Main Menu for Submenu Items // - Taxonomy Submenu Item Fix // - Fix to Redirect Plugin Settings Menu Link @@ -88,8 +90,10 @@ function radio_station_enqueue_admin_scripts() { function radio_station_admin_styles() { // --- hide first admin submenu item to prevent duplicate of main menu item --- - $css = '#toplevel_page_radio-station .wp-first-item {display: none;}' . "\n"; - $css .= '#toplevel_page_radio-station-pro .wp-first-item {display: none;}' . "\n"; + // 2.5.18: add hide first item for radio-content menu + $css = '#toplevel_page_radio-content .wp-first-item {display: none;}' . "\n"; + // $css .= '#toplevel_page_radio-station .wp-first-item {display: none;}' . "\n"; + // $css .= '#toplevel_page_radio-station-pro .wp-first-item {display: none;}' . "\n"; // --- reduce the height of the playlist editor area --- // 2.3.0: also reduce height of override editor area @@ -172,6 +176,20 @@ function radio_station_plugin_page_links( $links, $file ) { return $links; } +// ---------------------------- +// Content Dashboard Admin Page +// ---------------------------- +function radio_station_content_page() { + + echo '
    ' . "\n"; + + radio_station_quicklinks_panel(); + radio_station_statistics_panel(); + radio_station_settings_panel(); + + echo '
    ' . "\n"; +} + // ------------------ // === Admin Menu === @@ -184,8 +202,8 @@ function radio_station_plugin_page_links( $links, $file ) { add_action( 'admin_init', 'radio_station_settings_cap_check' ); function radio_station_settings_cap_check() { if ( isset( $_REQUEST['page'] ) && ( RADIO_STATION_SLUG == sanitize_text_field( $_REQUEST['page'] ) ) ) { - $settingscap = apply_filters( 'radio_station_settings_capability', 'manage_options' ); - if ( !current_user_can( $settingscap ) ) { + $settings_cap = apply_filters( 'radio_station_settings_capability', 'manage_options' ); + if ( !current_user_can( $settings_cap ) ) { wp_die( esc_html( __( 'You do not have permissions to access that page.', 'radio-station' ) ) ); } } @@ -197,42 +215,55 @@ function radio_station_settings_cap_check() { add_action( 'admin_menu', 'radio_station_add_admin_menus' ); function radio_station_add_admin_menus() { - $icon = plugins_url( 'images/radio-station-icon.png', RADIO_STATION_FILE ); - $position = apply_filters( 'radio_station_menu_position', 5 ); - $settingscap = apply_filters( 'radio_station_manage_options_capability', 'manage_options' ); + // 2.5.18: added split content and settings menus $rs = __( 'Radio Station', 'radio-station' ); - - // ---- main menu item ---- + $settings_icon = plugins_url( 'images/radio-station-icon.png', RADIO_STATION_FILE ); + $content_icon = apply_filters( 'radio_station_content_menu_icon', 'dashicons-format-audio' ); + $content_position = apply_filters( 'radio_station_content_menu_position', 4 ); + $settings_position = apply_filters( 'radio_station_menu_position', 5 ); + $settings_cap = apply_filters( 'radio_station_manage_options_capability', 'manage_options' ); + + // ---- main menu items ---- + // 2.5.18: split settings and content menus // 2.3.0: set to new plugin admin page (via plugin loader class) // (added with publish_playlists capability so that other submenu items remain accessible) - add_menu_page( $rs . ' ' . __( 'Settings', 'radio-station' ), $rs, 'publish_playlists', 'radio-station', 'radio_station_settings_page', $icon, $position ); + add_menu_page( $rs . ' ' . __( 'Settings', 'radio-station' ), $rs, 'publish_playlists', 'radio-station', 'radio_station_settings_page', $settings_icon, $settings_position ); + add_menu_page( $rs . ' ' . __( 'Content', 'radio-station' ), __( 'Station Content', 'radio-station' ), 'publish_playlists', 'radio-content', 'radio_station_content_page', $content_icon, $content_position ); // --- settings submenu item --- - // 2.3.0: added for ease of access to plugin settings - add_options_page( $rs . ' ' . __( 'Settings', 'radio-station' ), $rs, $settingscap, 'radio-station', 'radio_station_settings_page' ); + // 2.3.0: added for ease of access via plugin settings menu + add_options_page( $rs . ' ' . __( 'Settings', 'radio-station' ), $rs, $settings_cap, 'radio-station', 'radio_station_settings_page' ); - // --- submenu items --- + // --- content submenu items --- // 2.3.0: prefix plugin name on page titles (but not menu items) // 2.3.0: remove add playlist and add override to reduce clutter // 2.3.0: added actions for adding of other plugin submenu items in position // 2.5.0: disabled Add Show submenu item to reduce clutter - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); + do_action( 'radio_station_admin_submenu_content_top' ); + add_submenu_page( 'radio-content', $rs . ' ' . __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); - do_action( 'radio_station_admin_submenu_top' ); - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Playlist', 'radio-station' ), __( 'Add Playlist', 'radio-station' ), 'publish_playlists', 'add-playlist' ); - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); // 2.5.18: change label from Schedule Overrides - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Special Overrides', 'radio-station' ), __( 'Special Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Override', 'radio-station' ), __( 'Add Override', 'radio-station' ), 'publish_shows', 'add-override' ); - do_action( 'radio_station_admin_submenu_middle' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Hosts', 'radio-station' ), __( 'Hosts', 'radio-station' ), 'edit_hosts', 'hosts' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Producers', 'radio-station' ), __( 'Producers', 'radio-station' ), 'edit_producers', 'producers' ); + add_submenu_page( 'radio-content', $rs . ' ' . __( 'Special Overrides', 'radio-station' ), __( 'Special Overrides', 'radio-station' ), 'edit_shows', 'schedule-overrides' ); - // 2.3.2: as temporarily disabled, allow enabling export playlists via filter + do_action( 'radio_station_admin_submenu_content_middle' ); + + add_submenu_page( 'radio-content', $rs . ' ' . __( 'Playlists', 'radio-station' ), __( 'Playlists', 'radio-station' ), 'edit_playlists', 'playlists' ); + add_submenu_page( 'radio-content', $rs . ' ' . __( 'Genres', 'radio-station' ), __( 'Genres', 'radio-station' ), 'publish_playlists', 'genres' ); + // add_submenu_page( 'radio-content', $rs . ' ' . __( 'Languages', 'radio-station' ), __( 'Languages', 'radio-station' ), 'publish_playlists', 'languages' ); + do_action( 'radio_station_admin_submenu_content_bottom' ); + + // --- settings submenu items --- + do_action( 'radio_station_admin_submenu_settings_top' ); + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settings_cap, 'radio-station', 'radio_station_settings_page' ); + // 2.3.0: rename Help page to Documentation + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Documentation', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-docs', 'radio_station_plugin_docs_page' ); + + do_action( 'radio_station_admin_submenu_settings_middle' ); + + // 2.3.2: temporarily disabled, allows enabling old export playlists feature via filter $export_playlists = apply_filters( 'radio_station_export_playlists', false ); if ( $export_playlists ) { - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settingscap, 'playlist-export', 'radio_station_playlist_export_page' ); + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settings_cap, 'playlist-export', 'radio_station_playlist_export_page' ); } // --- import / export shows feature --- @@ -241,16 +272,13 @@ function radio_station_add_admin_menus() { add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Shows', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); } - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settingscap, 'radio-station', 'radio_station_settings_page' ); - // 2.3.0: rename Help page to Documentation - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Documentation', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-docs', 'radio_station_plugin_docs_page' ); - - do_action( 'radio_station_admin_submenu_bottom' ); + do_action( 'radio_station_admin_submenu_settings_bottom' ); + // --- hack the submenu global to add post type add/edit URLs --- global $submenu; foreach ( $submenu as $i => $menu ) { - if ( 'radio-station' === $i ) { + if ( ( 'radio-station' === $i ) || ( 'radio-content' === $i ) ) { foreach ( $menu as $j => $item ) { switch ( $item[2] ) { case 'add-show': @@ -301,12 +329,13 @@ function radio_station_add_admin_menus() { function radio_station_fix_genre_parent( $parent_file = '' ) { global $pagenow, $post; $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + // 2.3.0: also apply to language taxonomy $taxonomies = array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ); + // 2.5.18: change parent file slug to radio-content if ( ( 'edit-tags.php' === $pagenow ) && isset( $_GET['taxonomy'] ) && in_array( sanitize_text_field( $_GET['taxonomy'] ), $taxonomies ) ) { - // 2.3.0: also apply to language taxonomy - $parent_file = 'radio-station'; + $parent_file = 'radio-content'; } elseif ( ( 'post.php' === $pagenow ) && in_array( $post->post_type, $post_types ) ) { - $parent_file = 'radio-station'; + $parent_file = 'radio-content'; } return $parent_file; @@ -325,7 +354,8 @@ function radio_station_taxonomy_submenu_fix() { $js = ''; if ( RADIO_STATION_GENRES_SLUG == sanitize_text_field( $_GET['taxonomy'] ) ) { - $js = "jQuery('#toplevel_page_radio-station ul li').each(function() { + // 2.5.18: change top level page slug to radio-content + $js = "jQuery('#toplevel_page_radio-content ul li').each(function() { if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . esc_js( RADIO_STATION_GENRES_SLUG ) . "') { jQuery(this).addClass('current').find('a').addClass('current').attr('aria-current', 'page'); } @@ -334,7 +364,8 @@ function radio_station_taxonomy_submenu_fix() { // 2.3.0: add fix for language taxonomy also if ( RADIO_STATION_LANGUAGES_SLUG == sanitize_text_field( $_GET['taxonomy'] ) ) { - $js = "jQuery('#toplevel_page_radio-station ul li').each(function() { + // 2.5.18: change top level page slug to radio-content + $js = "jQuery('#toplevel_page_radio-content ul li').each(function() { if (jQuery(this).find('a').attr('href') == 'edit-tags.php?taxonomy=" . esc_js( RADIO_STATION_LANGUAGES_SLUG ) . "') { jQuery(this).addClass('current').find('a').addClass('current').attr('aria-current', 'page'); } diff --git a/radio-station.php b/radio-station.php index ec2b102..3f55190 100644 --- a/radio-station.php +++ b/radio-station.php @@ -42,6 +42,8 @@ // - Start Plugin Loader Instance // - Include Plugin Admin Files // - Load Plugin Text Domain +// x Add Pricing Page Path Filter +// x Freemius Pricing Page Path Filter // === Plugin Functions === // - Check Plan Options // - Check Plugin Version @@ -348,22 +350,24 @@ function radio_station_init() { // Add Pricing Page Path Filter // ---------------------------- // 2.5.0: added for Freemius Pricing Page v2 -add_action( 'radio_station_loaded', 'radio_station_add_pricing_path_filter' ); -function radio_station_add_pricing_path_filter() { - global $radio_station_freemius; - if ( method_exists( $radio_station_freemius, 'add_filter' ) ) { - $radio_station_freemius->add_filter( 'freemius_pricing_js_path', 'radio_station_pricing_page_path' ); - } -} +// 2.5.18: disabled as since included in Freemius by default +// add_action( 'radio_station_loaded', 'radio_station_add_pricing_path_filter' ); +// function radio_station_add_pricing_path_filter() { +// global $radio_station_freemius; +// if ( method_exists( $radio_station_freemius, 'add_filter' ) ) { +// $radio_station_freemius->add_filter( 'freemius_pricing_js_path', 'radio_station_pricing_page_path' ); +// } +// } // --------------------------------- // Freemius Pricing Page Path Filter // --------------------------------- // 2.5.0: added for Freemius Pricing Page v2 -function radio_station_pricing_page_path( $default_pricing_js_path ) { - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || RADIO_STATION_DEBUG ? '' : '.min'; - return RADIO_STATION_DIR . '/freemius-pricing/freemius-pricing' . $suffix . '.js'; -} +// 2.5.18: disabled as since included in Freemius by default +// function radio_station_pricing_page_path( $default_pricing_js_path ) { +// $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || RADIO_STATION_DEBUG ? '' : '.min'; +// return RADIO_STATION_DIR . '/freemius-pricing/freemius-pricing' . $suffix . '.js'; +// } // ------------------------ diff --git a/readme.txt b/readme.txt index 6c86461..944e00c 100644 --- a/readme.txt +++ b/readme.txt @@ -407,14 +407,19 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.18 = * Updated: Freemius SDK (2.13.0) * Updated: Plugin Panel (1.3.7) for delayed translations +* Changed: split station content and settings Admin menus * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player * Improved: Related Show and Linked Show select Show ordering * Improved: handle Special/Override meta in Archive shortcode * Improved: edit Special/Override permissions for linked Shows +* Added: Dashboard overview widget and Content Dashboard page * Added: Station Frequency and Location options * Added: Channel inputs for Shifts and Overrides +* Fixed: Special/Override (non-linked) Host/Producer data * Fixed: Radio Player plugin conflicts +* Fixed: Override Drafts without dates not listing in Admin +* Removed: Freemius Pricing v2 filter (merged in Freemius) = 2.5.17 = * Fixed: Current Show widget reloading when Shift display is off From ed44e7a56847968d054ca8c25637a703f4d8c7fe Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 25 Jan 2026 16:26:04 +1000 Subject: [PATCH 364/377] dev updates --- CHANGELOG.md | 4 + css/rs-schedule.css | 1 + includes/blocks.php | 12 +- includes/extra-strings.php | 335 +++++++++++++++++++++++++++++++-- includes/onboarding.php | 217 +++++++++++++-------- includes/post-types-admin.php | 6 +- includes/schedules.php | 50 +++-- includes/shortcodes.php | 158 +++++++++++----- includes/support-functions.php | 7 +- includes/templates.php | 42 ++++- js/radio-station-clock.js | 78 +++++--- js/radio-station.js | 123 ++++++++---- loader.php | 2 +- readme.txt | 4 + scheduler/schedule-engine.php | 33 ++++ 15 files changed, 833 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17808a1..c2a17d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: Related Show and Linked Show select Show ordering * Improved: handle Special/Override meta in Archive shortcode * Improved: edit Special/Override permissions for linked Shows +* Improved: Timezone and Clock Timezone display formatting * Added: Dashboard overview widget and Content Dashboard page * Added: Station Frequency and Location options * Added: Channel inputs for Shifts and Overrides +* Added: Link main language term to filtered show archive * Fixed: Special/Override (non-linked) Host/Producer data * Fixed: Radio Player plugin conflicts * Fixed: Override Drafts without dates not listing in Admin +* Fixed: Block asset paths for script/style file versions +* Fixed: Display user timezone for timezone shortcode * Removed: Freemius Pricing v2 filter (merged in Freemius) = 2.5.17 = diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 221cc43..305512c 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -10,6 +10,7 @@ .show-user-time { display: none; font-style: italic; + font-size: 0.95em; } /* Edit / Add Links */ diff --git a/includes/blocks.php b/includes/blocks.php index af1fede..6b575cc 100644 --- a/includes/blocks.php +++ b/includes/blocks.php @@ -330,7 +330,8 @@ function radio_station_block_editor_assets() { // --- enqueue admin script for localized variables --- $script_url = plugins_url( '/js/radio-station-admin.js', RADIO_STATION_FILE ); - $script_path = RADIO_STATION_DIR . 'js/radio-station-admin.js'; + // 2.5.18: fix to add missing / after dir path + $script_path = RADIO_STATION_DIR . '/js/radio-station-admin.js'; $version = filemtime( $script_path ); wp_enqueue_script( 'radio-station-admin', $script_url, $deps, $version, true ); $js = radio_station_localization_script(); @@ -339,7 +340,8 @@ function radio_station_block_editor_assets() { // --- block editor support for conditional loading --- $script_url = plugins_url( '/blocks/editor.js', RADIO_STATION_FILE ); - $script_path = RADIO_STATION_DIR . 'blocks/editor.js'; + // 2.5.18: fix to add missing / after dir path + $script_path = RADIO_STATION_DIR . '/blocks/editor.js'; $version = filemtime( $script_path ); wp_enqueue_script( 'radio-blockedit-js', $script_url, $deps, $version, true ); @@ -351,7 +353,8 @@ function radio_station_block_editor_assets() { // $deps = array( 'wp-edit-blocks' ); $stylesheets = array( 'shortcodes', 'schedule' ); // 'block-editor', 'blocks' foreach ( $stylesheets as $stylekey ) { - $style_path = RADIO_STATION_DIR . 'css/rs-' . $stylekey . '.css'; + // 2.5.18: fix to add missing / after dir path + $style_path = RADIO_STATION_DIR . '/css/rs-' . $stylekey . '.css'; $style_url = plugins_url( 'css/rs-' . $stylekey . '.css', RADIO_STATION_FILE ); $version = filemtime( $style_path ); wp_enqueue_style( 'rs-' . $stylekey, $style_url, array(), $version, 'all' ); @@ -379,7 +382,8 @@ function radio_station_block_editor_assets() { // --- enqueue radio player styles --- if ( array_key_exists( 'player', $callbacks ) ) { $suffix = ''; // dev temp - $style_path = RADIO_STATION_DIR . 'player/css/radio-player' . $suffix . '.css'; + // 2.5.18: fix to add missing / after dir path + $style_path = RADIO_STATION_DIR . '/player/css/radio-player' . $suffix . '.css'; $style_url = plugins_url( '/player/css/radio-player' . $suffix . '.css', RADIO_STATION_FILE ); $version = filemtime( $style_path ); // 2.5.18: change radio-player style handle to stream-player diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 5cbf87c..87842ca 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -11,22 +11,53 @@ __( 'Blue', 'radio-station' ); __( 'Purple', 'radio-station' ); __( 'Magenta', 'radio-station' ); +__( 'GeoIP Stream Blocking', 'radio-station' ); +__( 'No GeoIP Blocking', 'radio-station' ); +__( 'Live365 (only US, UK, Canada, Mexico', 'radio-station' ); +__( 'Custom Country Blacklist', 'radio-station' ); +__( 'Custom Country Whitelist', 'radio-station' ); +__( 'Block streaming according to country, detected by user IP address.', 'radio-station' ); +__( 'Broadcast Frequency', 'radio-station' ); +__( 'Text display to inform users of your main station frequency.', 'radio-station' ); +__( 'Display Start Hour', 'radio-station' ); +__( 'Midnight', 'radio-station' ); +__( 'Noon', 'radio-station' ); +__( 'Schedule displays will start from this hour instead of midnight.', 'radio-station' ); +__( 'Episodes Archive Page', 'radio-station' ); +__( 'Select the Page for displaying the Episode archive list.', 'radio-station' ); +__( 'Automatic Display', 'radio-station' ); +__( 'Replaces selected page content with default Episode Archive. Alternatively customize display using the shortcode:', 'radio-station' ); __( 'Team Archive Page', 'radio-station' ); __( 'Select the Page for displaying the Team archive list.', 'radio-station' ); -__( 'Automatic Display', 'radio-station' ); __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ); -__( 'Combined Team Tab', 'radio-station' ); -__( 'Do Not Combine', 'radio-station' ); -__( 'Combined List', 'radio-station' ); -__( 'Combined Grid', 'radio-station' ); -__( 'Combine team members (eg. hosts, producers into a single display tab.', 'radio-station' ); +__( 'Team Tab Display', 'radio-station' ); +__( 'No Team Display', 'radio-station' ); +__( 'Separate Role Tabs', 'radio-station' ); +__( 'Combined Team List', 'radio-station' ); +__( 'Combined Team Grid', 'radio-station' ); +__( 'How to display Show team members (eg. hosts, producers on a Show page.', 'radio-station' ); __( 'Time Spaced Grid', 'radio-station' ); __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ); +__( 'Episode Player', 'radio-station' ); +__( 'Radio Station Stream Player', 'radio-station' ); +__( 'MediaElements (WordPress', 'radio-station' ); +__( 'Which player to use on Episode pages for the latest show recording.', 'radio-station' ); +__( 'Player Default', 'radio-station' ); +__( 'Light', 'radio-station' ); +__( 'Dark', 'radio-station' ); +__( 'Episode Player Theme', 'radio-station' ); +__( 'Episode Player Controls theme style (Radio Station Stream Player only,', 'radio-station' ); +__( 'Use Latest Episode', 'radio-station' ); +__( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ); +__( 'Popup Player Button', 'radio-station' ); +__( 'Add button to open Popup Player in separate window.', 'radio-station' ); __( 'Player Bar Height', 'radio-station' ); __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ); __( 'Display Current Show', 'radio-station' ); __( 'Display the Current Show in the Player Bar.', 'radio-station' ); -__( 'Display Now Playing', 'radio-station' ); +__( 'Page Load Timeout', 'radio-station' ); +__( 'Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display.', 'radio-station' ); +__( 'Display Playing Track', 'radio-station' ); __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist', 'radio-station' ); __( 'Track Animation', 'radio-station' ); __( 'No Animation', 'radio-station' ); @@ -36,10 +67,8 @@ __( 'How to animate the currently playing track display.', 'radio-station' ); __( 'Metadata URL', 'radio-station' ); __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ); -__( 'Page Load Timeout', 'teleporter' ); -__( 'Number of milliseconds to wait for new Page to load before fading in anyway. Use 0 for instant display.', 'teleporter' ); -__( 'Popup Player Button', 'radio-station' ); -__( 'Add button to open Popup Player in separate window.', 'radio-station' ); +__( 'Store Track Metadata?', 'radio-station' ); +__( 'Save now playing track metadata in a separate database table for later use.', 'radio-station' ); __( 'Radio Station needs to be installed and activated to use %s.', 'radio-station' ); __( 'Activate Radio Station', 'radio-station' ); __( 'Install Radio Station', 'radio-station' ); @@ -102,6 +131,8 @@ __( 'Use an existing URL or add to Media Library.', 'radio-station' ); __( 'Enter the absolute URL to the media source.', 'radio-station' ); __( 'Opens the Media Library for uploading.', 'radio-station' ); +__( 'Disable Download', 'radio-station' ); +__( 'Enable this to hide the Download icon next to the embedded player.', 'radio-station' ); __( 'Episode Number', 'radio-station' ); __( 'This Episode number is unique.', 'radio-station' ); __( 'This Episode number is in use!', 'radio-station' ); @@ -115,6 +146,7 @@ __( 'The time that the Episode aired', 'radio-station' ); __( 'Episode Length', 'radio-station' ); __( 'minutees', 'radio-station' ); +__( '', 'radio-station' ); __( 'Linked Playlist', 'radio-station' ); __( 'Select Playlist', 'radio-station' ); __( 'No Playlists to Select.', 'radio-station' ); @@ -165,7 +197,9 @@ __( 'New Guest Name', 'radio-station' ); __( 'Guest', 'radio-station' ); __( 'Show %s', 'radio-station' ); +__( 'and', 'radio-station' ); __( 'Aired on', 'radio-station' ); +__( 'at', 'radio-station' ); __( 'Image', 'radio-station' ); __( 'Add Genre Image', 'radio-station' ); __( 'Remove Genre Image', 'radio-station' ); @@ -187,13 +221,13 @@ __( 'Image tgz file', 'radio-station' ); __( 'Download one of the image files and the data file. See help for details on how to stage an import including images.', 'radio-station' ); __( 'Show data file (YAML', 'radio-station' ); -__( 'Images not exported. See help for details on how to include images.', 'radio-station' ); +__( 'Images not exported. See help for details on how to include images.', 'radio-station' ); __( 'YAML import error. See below for details.', 'radio-station' ); __( 'Failed to create export folder', 'radio-station' ); __( 'No image exported for', 'radio-station' ); __( 'Failed to copy file. See below for details', 'radio-station' ); __( 'Failed to create show_images.zip', 'radio-station' ); -__( 'Each show in the YAML file must define at minimum the following keys: ' , 'radio-station' ); +__( 'Each show in the YAML file must define at minimum the following keys: ', 'radio-station' ); __( 'show-title: may not be null.', 'radio-station' ); __( 'show-description: may not be null.', 'radio-station' ); __( 'show-image: must be a URL reference to an existing image.', 'radio-station' ); @@ -232,6 +266,9 @@ __( 'Override: Metadata URL', 'radio-station' ); __( 'Override: Current Playlist', 'radio-station' ); __( 'Metadata Override URL', 'radio-station' ); +__( 'Sorry, this stream is unavailable in your country.', 'radio-station' ); +__( 'Station', 'radio-station' ); +__( 'Player', 'radio-station' ); __( 'Color Options', 'radio-station' ); __( 'Player Text Color', 'radio-station' ); __( 'Player Background Color', 'radio-station' ); @@ -249,6 +286,10 @@ __( 'Custom Theme', 'radio-station' ); __( 'Popup Player to separate window.', 'radio-station' ); __( 'Popup Player', 'radio-station' ); +__( 'Start Audio Playback?', 'radio-station' ); +__( 'Yes', 'radio-station' ); +__( 'No', 'radio-station' ); +__( 'Resume Audio Playback?', 'radio-station' ); __( 'Edit Your Host Profile', 'radio-station' ); __( 'Host Profile', 'radio-station' ); __( 'Edit Your Producer Profile', 'radio-station' ); @@ -314,8 +355,10 @@ __( 'Error! No saved recurrences were found.', 'radio-station' ); __( 'Cannot list recurring times without a valid start time.', 'radio-station' ); __( 'Cannot list recurring times without a valid end time.', 'radio-station' ); +__( 'Cannot list recurring times when end date is before start date.', 'radio-station' ); __( 'Override Date', 'radio-station' ); __( 'Shows Affected', 'radio-station' ); +__( 'Error. Could not generate shift preview list for times specified. Check your entries and try again.', 'radio-station' ); __( 'DJs / Hosts', 'radio-station' ); __( 'Show Editors', 'radio-station' ); __( 'Role Assignment Interface', 'radio-station' ); @@ -327,6 +370,8 @@ __( 'You have unsaved role changes.', 'radio-station' ); __( 'role added to user', 'radio-station' ); __( 'role removed from user', 'radio-station' ); +__( 'Schedule', 'radio-station' ); +__( 'Schedule Editor', 'radio-station' ); __( 'Visual Schedule Editor', 'radio-station' ); __( 'Table', 'radio-station' ); __( 'Tabs', 'radio-station' ); @@ -428,7 +473,6 @@ __( 'Automatic Changeover Reloading', 'radio-station' ); __( 'to', 'radio-station' ); __( 'with', 'radio-station' ); -__( 'and', 'radio-station' ); __( 'encore airing', 'radio-station' ); __( 'Audio File', 'radio-station' ); __( 'Genres', 'radio-station' ); @@ -448,7 +492,6 @@ __( 'Show More', 'radio-station' ); __( 'Show Less', 'radio-station' ); __( 'Listen to this %s', 'radio-station' ); -__( 'Player', 'radio-station' ); __( 'Download this Episode', 'radio-station' ); __( 'About this %s', 'radio-station' ); __( 'About', 'radio-station' ); @@ -478,8 +521,6 @@ __( 'Records Per Page', 'radio-station' ); __( 'Use 0 for all records.', 'radio-station' ); __( 'Display Pagination?', 'radio-station' ); -__( 'No', 'radio-station' ); -__( 'Yes', 'radio-station' ); __( 'Hide if Empty?', 'radio-station' ); __( 'Archive Query', 'radio-station' ); __( 'Order By', 'radio-station' ); @@ -607,8 +648,6 @@ __( 'Vertical (Stacked', 'radio-station' ); __( 'Horizontal (Inline', 'radio-station' ); __( 'Player Theme', 'radio-station' ); -__( 'Light', 'radio-station' ); -__( 'Dark', 'radio-station' ); __( 'Player Buttons', 'radio-station' ); __( 'Circular', 'radio-station' ); __( 'Rounded', 'radio-station' ); @@ -711,3 +750,261 @@ __( 'Schedule Typography', 'radio-station' ); __( 'Radio Upcoming Shows', 'radio-station' ); __( 'Upcoming Shows Styling', 'radio-station' ); +__( 'Broadcast Frequency', 'radio-station' ); +__( 'Text display to inform users of your main station frequency.', 'radio-station' ); +__( 'Show Page Button', 'radio-station' ); +__( 'Automatically add Subscibe button to Show pages.', 'radio-station' ); +__( 'Show Archives Buttons', 'radio-station' ); +__( 'Automatically add Subsscibe buttons to Show list archives.', 'radio-station' ); +__( 'Table View', 'radio-station' ); +__( 'Tabbed View', 'radio-station' ); +__( 'List View', 'radio-station' ); +__( 'Grid View', 'radio-station' ); +__( 'Calendar View', 'radio-station' ); +__( 'Schedule View Buttons', 'radio-station' ); +__( 'Automatically add Subscribe buttons to selected Schedule views by default.', 'radio-station' ); +__( 'Magic Login', 'radio-station' ); +__( 'Enable passwordless magic Login email for users to manage their subscriptions.', 'radio-station' ); +__( 'Magic Code', 'radio-station' ); +__( 'Allow user to enter magic code from email rather than clicking magic link.', 'radio-station' ); +__( 'Hide Subscriber Bar', 'radio-station' ); +__( 'Do not display the WordPress Admin Bar for logged in Subscribers.', 'radio-station' ); +__( 'Radio Clock', 'radio-station' ); +__( 'Radio Timezone', 'radio-station' ); +__( 'Times Display', 'radio-station' ); +__( 'Display radio clock or just timezone in Subscribe box.', 'radio-station' ); +__( 'Email', 'radio-station' ); +__( 'Alt Email', 'radio-station' ); +__( 'Calendar', 'radio-station' ); +__( 'RSS', 'radio-staiton' ); +__( 'Enabled Methods', 'radio-station' ); +__( 'Show reminder methods available to all subscribers. (See OneSignal section for additional methods.', 'radio-station' ); +__( 'Reminder Logging', 'radio-station' ); +__( 'Log reminder successes and failures for debugging purposes.', 'radio-station' ); +__( 'From Email Address', 'radio-station' ); +__( 'Set the email address from which Subscriber Login and Reminder emails are sent.', 'radio-station' ); +__( 'Email From Name', 'radio-station' ); +__( 'Set the name from which Subscriber Login and Reminder emails are sent.', 'radio-station' ); +__( 'Calendar Subscriptions', 'radio-station' ); +__( 'Email subject template used for sending show start alerts by email.', 'radio-station' ); +__( 'Reminder Email Body', 'radio-station' ); +__( 'Email body template used whennn sending show start alerts by Email.', 'radio-station' ); +__( 'OneSignal Integration', 'radio-station' ); +__( 'Enable OneSignal subscription reminder integration.', 'radio-station' ); +__( 'OneSignal App ID', 'radio-station' ); +__( 'OneSignal App ID, required for subscribing users to OneSignal service.', 'radio-station' ); +__( 'OneSignal API Key', 'radio-station' ); +__( 'OneSignal API key, required for sending out reminder notifications.', 'radio-station' ); +__( 'Phone Push', 'radio-station' ); +__( 'Enable OneSignal phone push app alerts. Requires Radio Your Way App.', 'radio-station' ); +__( 'Browser Push', 'radio-station' ); +__( 'Enable OneSignal web browser show alerts.', 'radio-station' ); +__( 'SMS Alerts', 'radio-station' ); +__( 'Enable SMS text messages via OneSignal.', 'radio-station' ); +__( 'Push Alert Title', 'radio-station' ); +__( 'Title template for Push Notification Alerts.', 'radio-station' ); +__( 'Starting Now Title', 'radio-station' ); +__( 'Title template for Push Notifications when prewarning minutes are 0.', 'radio-station' ); +__( 'Message template for Push Notifications Alerts.', 'radio-station' ); +__( 'Enable Tap Pages', 'radio-station' ); +__( 'Enable responsive Mobile App Menu via the included Tap Pages plugin.', 'radio-station' ); +__( '', 'radio-station' ); +__( 'Configure via Tap Pages settings page.', 'radio-station' ); +__( 'Enable Integration', 'radio-station' ); +__( 'Enable integration with Radio Your Way app.', 'radio-station' ); +__( 'App Scheme', 'radio-station' ); +__( 'Optional custom app scheme prefix to use for app redirections.', 'radio-station' ); +__( 'Latest Android Version', 'radio-station' ); +__( 'Your latest released app version in the Google Store.', 'radio-station' ); +__( 'Package ID', 'radio-station' ); +__( 'Google Play package ID used to generate app download and update links. eg. myapp.host.com', 'radio-station' ); +__( 'Store Country', 'radio-station' ); +__( 'Google Play Store country for app linking.', 'radio-station' ); +__( 'Latest Apple Version', 'radio-station' ); +__( 'Your latest release app version in the Apple Store.', 'radio-station' ); +__( 'Bundle ID', 'radio-station' ); +__( 'Apple Connect bundle ID used to generate app download and update links. eg. myapp.host.com', 'radio-station' ); +__( 'Apple Store country for app linking.', 'radio-station' ); +__( 'General', 'radio-station' ); +__( 'Player', 'radio-station' ); +__( 'Subscribe', 'radio-station' ); +__( 'Pages', 'radio-station' ); +__( 'Archives', 'radio-station' ); +__( 'Widgets', 'radio-station' ); +__( 'Roles', 'radio-station' ); +__( 'App', 'radio-station' ); +__( 'Subscribe Buttons', 'radio-station' ); +__( 'Login Options', 'radio-station' ); +__( 'Subscription Methods', 'radio-station' ); +__( 'Reminder Emails', 'radio-station' ); +__( 'Mobile App Menu', 'radio-station' ); +__( 'Radio Your Way App', 'radio-station' ); +__( 'Android App Details', 'radio-station' ); +__( 'Apple App Details', 'radio-station' ); +__( 'Radio Station needs to be installed and activated to use %s.', 'radio-station' ); +__( 'Activate Radio Station', 'radio-station' ); +__( 'Install Radio Station', 'radio-station' ); +__( 'Your Radio Station plugin needs to be updated to work with %s.', 'radio-station' ); +__( 'The required minimum version of the Radio Station plugin is:', 'radio-station' ); +__( 'Update to Radio Station', 'radio-station' ); +__( 'Development Version', 'radio-station' ); +__( 'Sorry, you are not allowed to update plugins for this site.', 'radio-station' ); +__( 'Update Plugin via Github', 'radio-station' ); +__( 'Error. Could not connect to filesystem. Please install plugin update manually.', 'radio-station' ); +__( 'Error. Downloading the plugin package failed. Please try again.', 'radio-station' ); +__( 'Downloaded plugin package from:', 'radio-station' ); +__( 'Package unzipped successfully.', 'radio-station' ); +__( 'Package contents moved to plugin directory.', 'radio-station' ); +__( 'The plugin has been updated, but could not be reactivated. Please reactivate it manually.', 'radio-station' ); +__( 'The previous version of the plugin has been backed up if you need to restore it.', 'radio-station' ); +__( 'Plugin reactivated successfully.', 'radio-station' ); +__( 'Radio Station Settings', 'radio-station' ); +__( 'Back to Plugins page.', 'radio-station' ); +__( 'Radio Station Subscribers Addon', 'radio-station' ); +__( 'Your session has timed out. Please login again!', 'prototasq' ); +__( 'Android App', 'radio-station' ); +__( 'Installed', 'radio-station' ); +__( 'Latest', 'radio-station' ); +__( 'Apple App', 'radio-station' ); +__( 'Upgrade Now', 'radio-station' ); +__( 'Your Show %s starts in %d minutes.', 'radio-station' ); +__( 'your dial', 'radio-station' ); +__( 'Enter Your Login Email', 'radio-station' ); +__( 'Password', 'radio-station' ); +__( 'Reset', 'radio-station' ); +__( 'Login with Password', 'radio-station' ); +__( 'Passwordless Login', 'radio-station' ); +__( 'Send Magic Login Link', 'radio-station' ); +__( 'Email address not found. Please recheck for typos.', 'radio-station' ); +__( 'Login failed: ', 'radio-station' ); +__( 'Successfully logged in. Refreshing page.', 'radio-station' ); +__( 'Error. Email address not found.', 'radio-station' ); +__( 'Error. Email could not be sent. Try again?', 'radio-station' ); +__( 'Email sent. Click on the link to verify your address.', 'radio-station' ); +__( 'Error. Please recheck your email entry.', 'radio-station' ); +__( 'A password reset was requested for the following account:', 'radio-station' ); +__( 'Site Name: %s', 'radio-station' ); +__( 'Username: %s', 'radio-station' ); +__( 'To reset your password, visit the following address:', 'radio-station' ); +__( 'If this was a mistake, ignore this email and nothing will happen.', 'radio-station' ); +__( 'This password reset request originated from the IP address %s.', 'radio-station' ); +__( '[%s] Password Reset Request', 'radio-station' ); +__( 'Error', 'radio-station' ); +__( 'Password reset email sent. Please check your inbox.', 'radio-station' ); +__( 'Oops! Magic login email failed to send. Please try again.', 'radio-station' ); +__( 'Sent! Click magic link in email or enter code to login.', 'radio-station' ); +__( 'Sent! Click magic link in email to login.', 'radio-station' ); +__( 'You are already logged in.', 'radio-station' ); +__( 'You are already logged in and verified.', 'radio-station' ); +__( 'Please recheck your email address for typos.', 'radio-station' ); +__( 'That magic login code has expired.', 'radio-station' ); +__( 'Success. You are now logged in.', 'radio-station' ); +__( 'Attempts exceeded. Request new link.', 'radio-station' ); +__( 'Magic login code mismatch.', 'radio-station' ); +__( 'Phone', 'radio-station' ); +__( 'Browser', 'radio-station' ); +__( 'SMS', 'radio-station' ); +__( 'Phone Alert Setup', 'radio-station' ); +__( 'App not detected on this device.', 'radio-station' ); +__( 'Phone Notification Permissions granted.', 'radio-station' ); +__( 'Enable Phone Notifications', 'radio-station' ); +__( 'Oops! Cannot Request Permissions', 'radio-station' ); +__( "Please check your device's App installation.", 'radio-station' ); +__( 'Browser Alert Setup', 'radio-station' ); +__( 'Failed to load required script for browser notifications.', 'radio-station' ); +__( 'Try disabling script blockers for this site.', 'radio-station' ); +__( 'App detected. Use phone alerts for app notifications instead.', 'radio-station' ); +__( 'Browser Notifications Permissions granted.', 'radio-station' ); +__( 'To ensure notifications are received, please make sure to select the "Forever" timeframe when granting browser permissions.', 'radio-station' ); +__( 'You can enable or disable all browser alerts at any time via this panel without needing to adjust browser settings.', 'radio-station' ); +__( 'Enable Browser Notifications', 'radio-station' ); +__( "Please check your browser's permission settings.", 'radio-station' ); +__( 'SMS Text Message Setup', 'radio-station' ); +__( 'Remove Phone Number', 'radio-station' ); +__( 'Enter Phone Number', 'radio-station' ); +__( 'Add Phone Number', 'radio-station' ); +__( 'Every Minute', 'radio-station' ); +__( 'Subscribe to %s', 'radio-station' ); +__( 'Subscribe to Shows.', 'radio-station' ); +__( 'Manage Your Show Subscriptions', 'radio-station' ); +__( 'Manage Subscriptions', 'radio-station' ); +__( 'Subscribe to Show', 'radio-station' ); +__( 'Subscribe to this Show', 'radio-station' ); +__( 'Subscribe to a Show', 'radio-station' ); +__( 'Login', 'radio-station' ); +__( 'Manage', 'radio-station' ); +__( 'Upcoming', 'radio-station' ); +__( 'Settings', 'radio-station' ); +__( 'Already a Subscriber?' , 'radio-station' ); +__( 'Click here to Login', 'radio-station' ); +__( 'Your Email', 'radio-station' ); +__( 'Verified', 'radio-station' ); +__( 'Unverified', 'radio-station' ); +__( 'Prewarning Time', 'radio-station' ); +__( 'minutes', 'radio-station' ); +__( 'Reminder Methods', 'radio-station' ); +__( 'Prewarn', 'radio-station' ); +__( 'Update Settings', 'radio-station' ); +__( 'Show', 'radio-station' ); +__( 'Select Show...', 'radio-station' ); +__( 'Specials', 'radio-station' ); +__( 'Alphabetical', 'radio-station' ); +__( 'No Day Scheduled', 'radio-station' ); +__( 'Monthly or Irregular', 'radio-station' ); +__( 'Not Currently Scheduled', 'radio-station' ); +__( 'Select Show Shift(s', 'radio-station' ); +__( 'After subscribing, you can receive Show start alerts by', 'radio-station' ); +__( 'or', 'radio-station' ); +__( 'Timeslot', 'radio-station' ); +__( 'Alerts', 'radio-station' ); +__( 'No Show Shifts found to display.', 'radio-station' ); +__( 'No upcoming shows to display.', 'radio-station' ); +__( 'Update Subscriptions', 'radio-station' ); +__( 'No Show Subscriptions yet.', 'radio-station' ); +__( 'Pause', 'radio-station' ); +__( 'Default', 'radio-station' ); +__( 'Custom', 'radio-station' ); +__( 'RSS', 'radio-station' ); +__( 'Email Notification Instructions', 'radio-station' ); +__( 'Your email is verified and setup complete.', 'radio-station' ); +__( 'Your email is not yet verified.', 'radio-station' ); +__( 'To complete set up for receiving email alerts,', 'radio-station' ); +__( 'click on the link in your verification email.', 'radio-station' ); +__( 'Resend Verification Email', 'radio-station' ); +__( 'Calendar Sync Instructions', 'radio-station' ); +__( 'Your personal Calendar URL is:', 'radio-station' ); +__( 'Copy to Clipboard', 'radio-station' ); +__( 'Calendar Client Instructions', 'radio-station' ); +__( 'Google Calendar', 'radio-station' ); +__( 'Apple Calendar (macOS', 'radio-station' ); +__( 'Apple Calendar (iOS', 'radio-station' ); +__( 'Outlook.Com', 'radio-station' ); +__( 'Outlook (Desktop', 'radio-station' ); +__( 'Thunderbird', 'radio-station' ); +__( 'RSS Notification Instructions', 'radio-station' ); +__( 'All Show Timeslots', 'radio-station' ); +__( 'to', 'radio-station' ); +__( 'Are you sure you want to remove all subscriptions for this Show?', 'radio-station' ); +__( 'Error! Show ID must be numeric.', 'radio-station' ); +__( 'Error! Show ID not found!', 'radio-station' ); +__( 'Subscription Settings Updated.', 'radio-station' ); +__( 'Oops! Provided email address is empty. Please try again!', 'radio-station' ); +__( 'Oops! Provided email address is invalid. Please try again!', 'radio-station' ); +__( 'That email is already registered. Please login instead.', 'radio-station' ); +__( 'Error. User could not be created. Please try again.', 'radio-station' ); +__( 'Error. Verification email could not be sent. Please try again.', 'radio-station' ); +__( 'Subscription created. Please click the link in your email to verify your address to continue.', 'radio-station' ); +__( 'Subscriptions Updated.', 'radio-station' ); +__( 'Error! Station ID must be numeric.', 'radio-station' ); +__( 'Error! Channel ID must be numeric.', 'radio-station' ); +__( 'Show Subscription Removed.', 'radio-station' ); +__( 'Timezone', 'radio-station' ); +__( 'Your Zone', 'radio-station' ); +__( 'Change Timezone', 'radio-station' ); +__( 'Time', 'radio-station' ); +__( 'You', 'radio-station' ); +__( 'Show times display in your timezone.', 'radio-station' ); +__( 'Select Region...', 'radio-station' ); +__( 'Clear', 'radio-station' ); +__( 'Cancel', 'radio-station' ); +__( 'Select Timezone...', 'radio-station' ); diff --git a/includes/onboarding.php b/includes/onboarding.php index 460d8f7..59028cc 100644 --- a/includes/onboarding.php +++ b/includes/onboarding.php @@ -16,9 +16,14 @@ // -------------------- // Add Dashboard Widget // -------------------- -add_action( 'wp_dashboard_setup', 'radio_station_add_dashboard_widget' ); +add_action( 'wp_dashboard_setup', 'radio_station_add_dashboard_widget', 0 ); function radio_station_add_dashboard_widget() { + if ( isset( $_REQUEST['reset-dashboard-widgets'] ) && ( '1' == sanitize_text_field( wp_unslash( $_REQUEST['reset-dashboard-widgets'] ) ) ) ) { + $current_user_id = get_current_user_id(); + delete_user_meta( $current_user_id, 'meta-box-order_dashboard' ); + } + // --- add dashboard widget --- // wp_add_dashboard_widget( 'radio-station-stats', __( 'Radio Station', 'radio-station' ), 'radio_station_dashboard_widget' ); $label = defined( 'RADIO_STATION_PRO_NAME' ) ? RADIO_STATION_PRO_NAME : __( 'Radio Station', 'radio-station' ); @@ -30,6 +35,15 @@ function radio_station_add_dashboard_widget() { 'normal', 'high' ); + + // --- move to top --- + /* global $wp_meta_boxes; + $widgets = $wp_meta_boxes['dashboard']['normal']['high']; + $widget = array( 'radio-station-stats' => $widgets['radio-station-stats'] ); + unset( $widgets['radio-station-stats'] ); + $widgets = array_merge( $widget, $widgets ); + $wp_meta_boxes['dashboard']['normal']['high'] = $widgets; */ + } // ---------------- @@ -60,10 +74,11 @@ function radio_station_quicklinks_panel() { echo '' . "\n"; // --- quickstart --- - // TODO: quickstart link / dropdown ? - $quickstart_url = ''; + // TODO: quickstart dropdown ? + $quickstart_url = add_query_arg( 'page', 'radio-station-docs', admin_url( 'admin.php' ) ); + $quickstart_url .= '#quickstart-guide'; echo '
  • '; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Quickstart', 'radio-station' ) ) . ''; echo '
  • ' . "\n"; @@ -139,7 +154,7 @@ function radio_station_quicklinks_panel() { if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { // --- schedule editor --- - // TODO: improve link to schedule editor page + // TODO: link to schedule editor page $schedule_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; echo '' . "\n"; @@ -192,8 +207,11 @@ function radio_station_statistics_panel() { global $wpdb; + // --- schedule progress --- + $schedule_items = $schedule_notdone = $schedule_progress = 0; + // --- content progress --- - $progress_items = $content_notdone = $content_progress = 0; + $content_items = $content_notdone = $content_progress = 0; // --- get show counts --- $show_count = $shift_count = $active_count = $publish_count = $draft_count = $active_show_count = 0; @@ -342,11 +360,101 @@ function radio_station_statistics_panel() { $episode_show_count = count( $episode_show_ids ); $episode_playlist_count = count( $episode_playlist_ids ); + // --- Schedule heading --- + echo '
    ' . esc_html( 'Station Schedule', 'radio-station' ) . '
    ' . "\n"; + + // --- Schedule percentage line --- + $schedule_percent = $total_times = 0; + $schedule = radio_station_get_current_schedule(); + if ( $schedule && is_array( $schedule ) && ( count( $schedule ) > 0 ) ) { + // print_r( $schedule ); + foreach ( $schedule as $day => $shifts ) { + foreach ( $shifts as $time => $shift ) { + $start_time = strtotime( $shift['date'] . ' ' . $shift['start'] ); + $end_time = strtotime( $shift['date'] . ' ' . $shift['end'] ); + if ( $start_time > $end_time ) { + $end_time = $end_time + 86400; + } + $shift_length = $end_time - $start_time; + // echo $shift['start'] . ' - ' . $shift['end'] . ' - ' . $start_time . ' - ' . $end_time . ' - '; + // echo $shift_length . '
    '; + $total_times = $total_times + $shift_length; + } + } + $week_in_seconds = 86400 * 7; + // echo 'TOTAL:' . $total_times . ' of ' . $week_in_seconds . '
    '; + $schedule_percent = round( ( $total_times / $week_in_seconds ), 2, PHP_ROUND_HALF_DOWN ) * 100; + } + + echo '
    ' . "\n"; + + // --- progress icon --- + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $shift_count ) . ''; + echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + + echo ' ' . esc_html( $specials_count ) . ''; + echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); + + // --- coverage percentage --- + echo ' ' . esc_html( __( 'with', 'radio-station' ) ); + echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); + echo '.' . "\n"; + + echo '
    ' . "\n"; + + // --- schedule editor --- + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + $schedule_url = add_query_arg( 'page', 'radio-schedule', admin_url( 'admin.php' ) ); + $schedule_title = __( 'Visual Schedule Editor', 'radio-station' ); + $schedule_text = __( 'Schedule', 'radio-station' ); + } else { + $schedule_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $schedule_title = __( 'Edit Shows', 'radio-station' ); + $schedule_text = __( 'Shows', 'radio-station' ); + } + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( $schedule_text ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '
    ' . "\n"; + + // --- shift conflicts line --- + /* $conflict_percent = 0; // TEMP + + $conflicts = radio_station_check_shifts(); + echo 'Conflicts: ' . print_r( $conflicts, true ) . '
    '; + + // TODO: check for shift conflicts and convert to % + $schedule_percent = $schedule_percent - $conflict_percent; + + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- schedule progress bar --- + radio_station_progress_bar( $schedule_percent, $conflict_percent ); + */ + // --- Content heading --- echo '
    ' . esc_html( 'Station Content', 'radio-station' ) . '
    ' . "\n"; // --- Shows --- - $progress_items++; + $content_items++; // $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; // $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; $active_publish_percent = round( ( $active_count / $publish_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; @@ -424,7 +532,7 @@ function radio_station_statistics_panel() { echo '
  • ' . "\n"; // --- Descriptions --- - $progress_items++; + $content_items++; // --- check shows and override description percentage --- if ( ( 0 == $show_desc_empty ) && ( 0 == $override_desc_empty ) ) { @@ -483,59 +591,9 @@ function radio_station_statistics_panel() { echo '' . "\n"; echo '
    ' . "\n"; - - // --- Shifts --- - $progress_items++; - // TODO: calculate % schedule coverage! - $schedule_percent = 100; // ??? - - - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; - - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $shift_count ) . ''; - echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); - - echo ' ' . esc_html( $specials_count ) . ''; - echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); - - // --- show active percentage --- - // TODO: maybe add schedule editor link - // echo ' ' . esc_html( __( 'with', 'radio-station' ) ); - // echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); - // echo '.' . "\n"; - - echo '
    ' . "\n"; - - // --- add shift icon --- - $add_shift_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Shift', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - echo '
    ' . "\n"; // --- Hosts --- - $progress_items++; + $content_items++; $host_percent = round( ( $shows_host_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; echo '
    ' . "\n"; @@ -593,7 +651,7 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- Genres --- - $progress_items++; + $content_items++; $genre_percent = round( ( $genre_show_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; echo '
    ' . "\n"; @@ -648,7 +706,7 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- Playlists --- - $progress_items++; + $content_items++; echo '
    ' . "\n"; // --- progress icon --- @@ -688,7 +746,7 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- Episodes --- - $progress_items++; + $content_items++; echo '
    ' . "\n"; // --- progress icon --- @@ -739,7 +797,9 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; // --- content progress bar --- - radio_station_progress_bar( $content_progress, $content_notdone, $progress_items ); + $progress_percent = round( ( $content_progress / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $undone_percent = round( ( $content_notdone / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + radio_station_progress_bar( $progress_percent, $undone_percent ); } @@ -753,13 +813,13 @@ function radio_station_settings_panel() { $settings = radio_station_get_settings( false ); // --- settings progress --- - $progress_items = $settings_notdone = $settings_progress = 0; + $settings_items = $settings_notdone = $settings_progress = 0; // --- settings checks --- echo '
    ' . esc_html( 'Station Settings', 'radio-station' ) . '
    ' . "\n"; // --- Stream URLs --- - $progress_items++; + $settings_items++; $settings_tab = 'general'; $settings_section = 'broadcast'; echo '
    ' . "\n"; @@ -824,7 +884,7 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- Player Bar --- - $progress_items++; + $settings_items++; echo '
    ' . "\n"; // --- progress icon --- @@ -918,7 +978,7 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- Schedule Page --- - $progress_items++; + $settings_items++; echo '
    ' . "\n"; // --- progress icon --- @@ -983,7 +1043,7 @@ function radio_station_settings_panel() { echo '
    ' . "\n"; // --- Station Info --- - $progress_items++; + $settings_items++; $infos = array( 'station_title', 'station_image', 'station_frequency', 'station_location', 'station_phone', 'station_email' ); $infos_count = count( $infos ); $info_count = 0; @@ -1046,7 +1106,7 @@ function radio_station_settings_panel() { // --- Archive Pages --- - $progress_items++; + $settings_items++; $archives = array( 'show', 'override', 'playlist', 'genre', 'language' ); $pro_archives = array( 'episode', 'team' ); if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { @@ -1116,7 +1176,7 @@ function radio_station_settings_panel() { // --- Pro Archives Pages --- if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { - $progress_items++; + $settings_items++; echo '
    ' . "\n"; // --- progress icon --- @@ -1141,7 +1201,9 @@ function radio_station_settings_panel() { // ... // --- settings progress bar --- - radio_station_progress_bar( $settings_progress, $settings_notdone, $progress_items ); + $progress_percent = round( ( $settings_progress / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $undone_percent = round( ( $settings_notdone / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + radio_station_progress_bar( $progress_percent, $undone_percent ); } @@ -1149,15 +1211,16 @@ function radio_station_settings_panel() { // ------------ // Progress Bar // ------------ -function radio_station_progress_bar( $done, $undone, $items ) { +// function radio_station_progress_bar( $done, $undone, $items ) { +function radio_station_progress_bar( $progress_percent, $undone_percent ) { + + $remainder_percent = 100 - $progress_percent - $undone_percent; $bar_width = 395; - $progress_percent = round( ( $done / $items ), 2, PHP_ROUND_HALF_DOWN ) * 100; $progress_px = round( ( $progress_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); - $undone_percent = round( ( $undone / $items ), 2, PHP_ROUND_HALF_DOWN ) * 100; $undone_px = round( ( $undone_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); - $remainder_percent = 100 - $progress_percent - $undone_percent; $remainder_px = round( ( $remainder_percent / 100 * $bar_width ), 0, PHP_ROUND_HALF_DOWN ); - echo '' . "\n"; + + echo '
    ' . "\n"; echo '' . "\n"; echo '' . "\n"; $buttons .= '' . "\n"; @@ -3163,7 +3188,8 @@ public function setting_scripts() { // 1.2.5: changed to jQuery click function to remove onclick button attribute $confirmreset = __( 'Are you sure you want to reset to default settings?', 'radio-station' ); // echo "function plugin_panel_reset_defaults() {" . "\n"; - echo "jQuery('#settingsresetbutton').on('click', function() {" . "\n"; + // 1.3.7: fix to use class not ID + echo "jQuery('.reset-button').on('click', function() {" . "\n"; echo " agree = confirm('" . esc_js( $confirmreset ) . "');" . "\n"; echo " if (!agree) {return false;}" . "\n"; echo " document.getElementById('settings-action').value = 'reset';" . "\n"; diff --git a/options.php b/options.php index e650c86..e54a41c 100644 --- a/options.php +++ b/options.php @@ -8,15 +8,20 @@ // Plugin Admin Options Filter // --------------------------- // 2.5.18: added filter for admin options -add_filter( 'radio_station_options', 'radio_station_admin_options' ); -function radio_station_admin_options( $options ) { - $options = radio_station_plugin_options( true ); - return $options; +add_action( 'plugins_loaded', 'radio_station_load_admin_options' ); +function radio_station_load_admin_options() { + add_filter( 'radio_station_options', 'radio_station_admin_options' ); + function radio_station_admin_options( $options ) { + $admin = is_admin(); + $options = radio_station_plugin_options( $admin ); + return $options; + } } // ------------------ // Set Plugin Options // ------------------ +add_filter( 'radio_station_plugin_options', 'radio_station_plugin_options' ); function radio_station_plugin_options( $admin = false ) { $timezones = radio_station_get_timezone_options( true, $admin ); @@ -139,6 +144,28 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'station', ), + // --- Station Callsign --- + // 2.5.18: added station callsign field + 'station_callsign' => array( + 'type' => 'text', + 'label' => $admin ? __( 'Station Callsign', 'radio-station' ) : '', + 'default' => '', + 'helper' => $admin ? __( '', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Tagline --- + // 2.5.18: added station tagline + 'station_tagline' => array( + 'type' => 'text', + 'label' => $admin ? __( 'Station Tagline', 'radio-station' ) : '', + 'default' => '', + 'helper' => $admin ? __( '', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'station', + ), + // --- Station Image --- // 2.3.3.8: added station logo image field 'station_image' => array( @@ -156,10 +183,26 @@ function radio_station_plugin_options( $admin = false ) { 'type' => 'text', 'label' => $admin ? __( 'Station Frequency', 'radio-station' ) : '', 'default' => '', - 'helper' => $admin ? __( 'Text display to inform users of your main station frequency.', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Your station frequency as a number.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), + + // --- Station Band --- + // 2.5.18: addde station band option + 'station_band' => array( + 'type' => 'select', + 'label' => $admin ? __( 'Frequency Band', 'radio-station' ) : '', + 'options' => array( + '' => __( 'n/a', 'radio-station' ), + 'fm' => __( 'FM', 'radio-station' ), + 'am' => __( 'AM', 'radio-station' ), + ), + 'default' => '', + 'helper' => $admin ? __( 'Your station frequency band identifier.', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'broadcast', + ), // --- Station Location --- // 2.5.18: added station location option @@ -167,7 +210,7 @@ function radio_station_plugin_options( $admin = false ) { 'type' => 'text', 'label' => $admin ? __( 'Station Location', 'radio-station' ) : '', 'default' => '', - 'helper' => $admin ? __( 'Text display to inform users of your main station location.', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Text display to inform users of your station location.', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -313,7 +356,7 @@ function radio_station_plugin_options( $admin = false ) { 'player_defaults_note' => array( 'type' => 'note', 'label' => $admin ? __( 'Player Defaults Note', 'radio-station' ) : '', - 'helper' => $admin ? __( 'Note that you can override these defaults in specific Player Widgets.', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Note that you can override these defaults in specific Player Shortcodes or Widgets.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', ), @@ -340,6 +383,24 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'basic', ), + // --- [Player] Player Meta --- + // 2.5.18: added station meta display options + 'player_meta' => array( + 'type' => 'multicheck', + 'label' => $admin ? __( 'Display Station Meta', 'radio-station' ) : '', + 'options' => array( + // 'tagline' => $admin ? __( 'Tagline', 'radio-station' ) : '', + 'frequency' => $admin ? __( 'Frequency', 'radio-station' ) : '', + 'location' => $admin ? __( 'Location', 'radio-station' ) : '', + 'timezone' => $admin ? __( 'Timezone', 'radio-station' ) : '', + // 'phone' => $admin ? __( 'Phone', 'radio-station' ) : '', + ), + 'default' => array( 'frequency', 'location' ), + 'helper' => $admin ? __( 'Display your Radio Station Meta in Player by default.', 'radio-station' ) : '', + 'tab' => 'player', + 'section' => 'basic', + ), + // --- [Player] Player Script --- // 2.4.0.3: change script default to jplayer // 2.5.7: disable howler script (browser incompatibilities) @@ -364,7 +425,7 @@ function radio_station_plugin_options( $admin = false ) { 'player_fallbacks' => array( 'type' => 'multicheck', 'label' => $admin ? __( 'Fallback Scripts', 'radio-station' ) : '', - 'default' => array( 'amplitude', 'howler', 'jplayer' ), + 'default' => array( 'amplitude', 'jplayer' ), 'options' => array( 'amplitude' => $admin ? __( 'Amplitude', 'radio-station' ) : '', 'jplayer' => $admin ? __( 'jPlayer', 'radio-station' ) : '', @@ -440,7 +501,7 @@ function radio_station_plugin_options( $admin = false ) { 'player_playing_color' => array( 'type' => 'color', 'label' => $admin ? __( 'Playing Icon Highlight Color', 'radio-station' ) : '', - 'default' => '#70E070', + 'default' => '#70D070', 'helper' => $admin ? __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'colors', @@ -624,12 +685,31 @@ function radio_station_plugin_options( $admin = false ) { // 2.4.0.3: add page load timeout option 'player_bar_timeout' => array( 'type' => 'number', - 'label' => $admin ? __( 'Page Load Timeout', 'teleporter' ) : '', + 'label' => $admin ? __( 'Page Load Timeout', 'radio-station' ) : '', 'default' => 7000, 'min' => 0, 'step' => 500, 'max' => 20000, - 'helper' => $admin ? __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Number of milliseconds to wait for new Page to load before fading in anyway or prompting (if continuous playback is enabled.)', 'radio-station' ) : '', + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro/Player] Page Load Prompt --- + // 2.5.18: add timeout/error prompt + 'player_bar_prompt' => array( + 'type' => 'select', + 'label' => $admin ? __( 'Load Fail Prompt', 'radio-station' ) : '', + 'default' => '404', + 'options' => array( + '' => $admin ? __( 'Off', 'radio-station' ) : '', + 'yes' => $admin ? __( 'Prompt on Timeout only', 'radio-station' ) : '', + '404' => $admin ? __( 'Prompt on 404 Not Found only', 'radio-station' ) : '', + '404t' => $admin ? __( 'Prompt on 404 or Timeout', 'radio-station' ) : '', + 'all' => $admin ? __( 'Prompt on Timeout or any Error', 'radio-station' ) : '', + ), + 'helper' => $admin ? __( 'Whether to show a user prompt on page timeout and/or when there is an error (if continuous playback is enabled.)', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -647,10 +727,11 @@ function radio_station_plugin_options( $admin = false ) { ), // --- [Pro/Player] Bar Player Background Color --- + // 2.5.18: fix default alpha value from 255 to 1 'player_bar_background' => array( 'type' => 'coloralpha', 'label' => $admin ? __( 'Bar Player Background Color', 'radio-station' ) : '', - 'default' => 'rgba(0,0,0,255)', + 'default' => 'rgba(0,0,0,1)', 'helper' => $admin ? __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', diff --git a/player/compat.php b/player/compat.php index bc62a7e..955d093 100644 --- a/player/compat.php +++ b/player/compat.php @@ -229,3 +229,10 @@ function radio_station_player_validate_boolean( $value ) { return (bool) $value; } } + +// 2.5.18: back compat for radio_player_inline_script name change +if ( !function_exists( 'radio_player_inline_script' ) ) { + function radio_player_inline_script( $js ) { + radio_player_add_inline_script( $js ); + } +} diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 3cf4679..50a0529 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -9,12 +9,11 @@ */ .rp-player { - background-color: transparent; + background-color: transparent; } .rp-player audio { - width: 0; - height: 0; + width: 0; height: 0; } .rp-audio button::-moz-focus-inner, .rp-audio-stream button::-moz-focus-inner { @@ -23,26 +22,20 @@ } .rp-audio, .rp-audio-stream { - font-size: 16px; - font-family: Verdana, Arial, sans-serif; - line-height: 1.6; - background-color: transparent; + font-size: 16px; font-family: Verdana, Arial, sans-serif; line-height: 1.6; background-color: transparent; } .rp-audio *:focus, .rp-audio-stream *:focus { /* Disable the browser focus highlighting. */ - outline: none; - box-shadow: none; + outline: none; box-shadow: none; } .rp-interface { - position: relative; - background-color: transparent; + position: relative; background-color: transparent; } .rp-audio-stream .rp-interface { - width: 35%; - display: inline-block; + width: 35%; display: inline-block; } .rp-station-info, .rp-interface, .rp-volume-controls, .rp-controls-holder , .rp-script-switcher, .rp-show-info { @@ -50,9 +43,7 @@ } .rp-station-info, .rp-interface, .rp-show-info { - text-align: center; - vertical-align: top; - margin-top: 7px; + text-align: center; vertical-align: top; margin-top: 7px; } .rp-station-info, .rp-show-info { @@ -60,8 +51,7 @@ } .rp-audio-stream .rp-station-info, .rp-audio-stream .rp-show-info { - width: 32%; - display: inline-block; + width: 32%; display: inline-block; } .rp-station-text-alt, .rp-show-text-alt { @@ -75,20 +65,15 @@ .radio-container.vertical .rp-station-info, .radio-container.vertical .rp-interface, .radio-container.vertical .rp-show-info { - display: block; - width: 100%; - vertical-align: top; + display: block; width: 100%; vertical-align: top; } .rp-controls, .rp-volume-controls, .rp-script-switcher { - display: inline-block; - vertical-align: middle; - /* margin-top: -10px; */ + display: inline-block; vertical-align: middle; /* margin-top: -10px; */ } .rp-volume-controls button, .rp-volume-slider-container, .rp-volume-bar { - display: inline-block; - vertical-align: middle; + display: inline-block; vertical-align: middle; } .rp-popup { @@ -96,24 +81,17 @@ } .rp-audio .rp-interface, .rp-audio-stream .rp-interface { - height: 80px; - padding-top: 5px; + height: 80px; padding-top: 5px; } /* @group CONTROLS */ .rp-interface .rp-controls { - margin: 0; - padding: 0; - overflow: hidden; + margin: 0; padding: 0; overflow: hidden; } .rp-controls button { - display: block; - float: left; - overflow: hidden; - text-indent: -9999px; - border: none; - cursor: pointer; + display: block; float: left; overflow: hidden; + text-indent: -9999px; border: none; cursor: pointer; } /* @group Control Buttons */ @@ -128,9 +106,7 @@ .dark .rp-play-pause-button-bg {background-color: rgba(64, 64, 64, 0.5);} .rp-play-pause-button-bg, .rp-play-pause-button { - width: 40px; - height: 40px; - background-size: 40px 40px; + width: 40px; height: 40px; background-size: 40px 40px; } .radio-container.circular .rp-play-pause-button-bg { @@ -142,13 +118,11 @@ } .rp-play-pause-button { - opacity: 0.9; - cursor: pointer; + opacity: 0.9; cursor: pointer; } .rp-play-pause-button:hover, .rp-play-pause-button:focus { - opacity: 1; - scale: 1.1; + opacity: 1; scale: 1.1; } .radio-container.light.circular .rp-play-pause-button { @@ -233,9 +207,7 @@ /* @group Volume Controls */ .rp-volume-slider-container { - position: relative; - opacity: 0.85; - height: 30px; + position: relative; opacity: 0.85; height: 30px; } .rp-volume-slider-container:hover, .rp-volume-slider-container:focus { @@ -243,17 +215,12 @@ } .radio-container .rp-volume-controls button, .radio-container .rp-popup button { - overflow: hidden; - text-indent: -9999px; - border: none; - cursor: pointer; - opacity: 0.9; + overflow: hidden; text-indent: -9999px; border: none; cursor: pointer; opacity: 0.9; } .radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus, .radio-container .rp-popup button:hover, .radio-container .rp-popup button:focus { - opacity: 0.99; - scale: 1.1; + opacity: 0.99; scale: 1.1; } .radio-container.circular .rp-volume-controls button, .radio-container.circular .rp-popup button { @@ -293,6 +260,7 @@ .rp-volume-down:focus, .rp-volume-down:hover {background-position: -18px -72px;} .rp-popup-button {background-position: 0 -90px;} .rp-popup-button:focus, .rp-popup-button:hover {background-position: -18px -90px;} +.rp-script-select {display: none;} /* @end */ /* @roup Now Playing Info */ @@ -300,39 +268,29 @@ font-size: 14px; } .rp-now-playing-title, .rp-now-playing-artist, .rp-now-playing-album { - display: inline-block; - padding: 0 10px; + display: inline-block; padding: 0 10px; } /* @group Station and Show Info */ .rp-station-image, .rp-station-text, .rp-show-text, .rp-show-image { - display: inline-block; - vertical-align: top; + display: inline-block; vertical-align: top; } .rp-station-info .rp-station-image, .rp-show-info .rp-show-image { - width: 64px; - height: 64px; - margin: 0; - padding: 0; - background-size: 100% 100%; + width: 64px; height: 64px; margin: 0; padding: 0; background-size: 100% 100%; } .rp-station-info .rp-station-image {margin-right: 16px;} .rp-station-info .rp-station-image.no-image {width: 0; height: 0; margin-right: 0; display: none;} .rp-station-info .rp-station-image.no-image.new-image {width: 64px; height: 64px; margin-right: 16px;} .rp-station-info .rp-station-image.new-image img {display: none;} -.rp-show-info .rp-show-image {margin-left: 16px;} +.rp-show-info .rp-show-image {margin-left: 16px; border: 0;} .rp-show-info .rp-show-image.no-image {width: 0; height: 0; margin-left: 0; display: none;} +.rp-show-info .rp-show-image-link {text-decoration: none;} .rp-show-info .rp-station-text {max-width: calc(100% - 90px);} /* @end */ /* @group No Solution error */ .rp-no-solution { - padding: 5px; - font-size: .8em; - background-color: #eee; - border: 2px solid #009be3; - color: #000; - display: none; + padding: 5px; font-size: .8em; background-color: #eee; border: 2px solid #009be3; color: #000; display: none; } .rp-no-solution a { @@ -340,9 +298,6 @@ } .rp-no-solution span { - font-size: 1em; - display: block; - text-align: center; - font-weight: bold; + font-size: 1em; display: block; text-align: center; font-weight: bold; } /* @end */ \ No newline at end of file diff --git a/player/css/radio-player.min.css b/player/css/radio-player.min.css index e47eec5..7edd6c9 100644 --- a/player/css/radio-player.min.css +++ b/player/css/radio-player.min.css @@ -1 +1 @@ -.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent}.rp-audio-stream .rp-interface{width:35%;display:inline-block}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{display:none}.rp-audio-stream .rp-station-info,.rp-audio-stream .rp-show-info{width:32%;display:inline-block}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file +.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent}.rp-audio-stream .rp-interface{width:35%;display:inline-block}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{display:none}.rp-audio-stream .rp-station-info,.rp-audio-stream .rp-show-info{width:32%;display:inline-block}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-script-select{display:none}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px;border:0}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-show-image-link{text-decoration:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file diff --git a/player/js/audio5.js b/player/js/audio5.js new file mode 100644 index 0000000..a542982 --- /dev/null +++ b/player/js/audio5.js @@ -0,0 +1,1114 @@ +/*! + * Audio5js: HTML5 Audio Compatibility Layer + * https://github.com/zohararad/audio5js + * License MIT (c) Zohar Arad 2013 + */ +(function ($win, ns, factory) { + "use strict"; + /*global define */ + /*global swfobject */ + + if (typeof (module) !== 'undefined' && module.exports) { // CommonJS + module.exports = factory(ns, $win); + } else if (typeof (define) === 'function' && define.amd) { // AMD + define(function () { + return factory(ns, $win); + }); + } else { // '; } } else { // --- print settings directly --- // 2.5.7: use direct echo arg for player settings + // 2.5.18: wrap direct echo in script tags + echo ''; } } $radio_player['enqueued_player'] = true; @@ -1627,11 +1723,16 @@ function radio_player_enqueue_script( $script ) { radio_player_enqueue_jplayer( true ); - } elseif ( ( 'howler' == $script ) && !isset( $radio_player['enqeued_howler'] ) ) { + } elseif ( ( 'audio5' == $script ) && !isset( $radio_player['enqeued_audio5'] ) ) { + + radio_player_enqueue_audio5( true ); + + } /* elseif ( ( 'howler' == $script ) && !isset( $radio_player['enqeued_howler'] ) ) { radio_player_enqueue_howler( true ); - } + } */ + // elseif ( ( 'mediaelements' == $script ) && !isset( $radio_player['enqeued_mediaelements'] ) ) { // radio_player_enqueue_mediaelements( true ); // $js = radio_player_script_mediaelements(); @@ -1668,7 +1769,7 @@ function radio_player_enqueue_script( $script ) { // --- output script tag --- if ( '' != $js ) { - radio_player_inline_script( $js ); + radio_player_add_inline_script( $js ); } // --- set specific script as enqueued --- @@ -1681,22 +1782,37 @@ function radio_player_enqueue_script( $script ) { // Add Inline Script // ----------------- // 2.5.10: added for fallback printing of scripts in footer -function radio_player_inline_script( $js, $position = 'after' ) { +// 2.5.18: renamed from radio_player_inline_script +function radio_player_add_inline_script( $js, $position = 'after' ) { if ( function_exists( 'wp_add_inline_script' ) ) { // 2.5.18: change radio-player script handle to stream-player if ( !wp_script_is( 'stream-player', 'done' ) ) { wp_add_inline_script( 'stream-player', $js, $position ); } else { - $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; - wp_register_script( 'radio-player-footer', null, array( 'jquery' ), $version, true ); - wp_enqueue_script( 'radio-player-footer' ); + // 2.5.18: check if footer scripts are registered + if ( !wp_script_is( 'radio-player-footer', 'registered' ) ) { + $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; + wp_register_script( 'radio-player-footer', null, array( 'jquery' ), $version, true ); + // wp_enqueue_script( 'radio-player-footer' ); + } wp_add_inline_script( 'radio-player-footer', $js, $position ); + + // 2.5.18: enqueue inline scripts in footer + if ( !has_action( 'wp_footer', 'radio_player_enqueue_footer_scripts', 9 ) ) { + add_action( 'wp_footer', 'radio_player_enqueue_footer_scripts', 9 ); + } } } - } +// ---------------------- +// Enqueue Footer Scripts +// ---------------------- +// 2.5.18: added to ensure catching inline scripts +function radio_player_enqueue_footer_scripts() { + wp_enqueue_script( 'radio-player-footer' ); +} // -------------------------------- // Lazy Load Audio Script Fallbacks @@ -1709,7 +1825,8 @@ function radio_player_load_script_fallbacks( $js ) { global $radio_player; // 2.4.0.3: check for fallback selection (default all) - $fallbacks = array( 'jplayer', 'howler', 'amplitude' ); + // 2.5.18: added audio5 script and removed howler + $fallbacks = array( 'jplayer', 'amplitude', 'audio5' ); // 'howler' if ( function_exists( 'radio_station_get_setting' ) ) { $fallbacks = radio_station_get_setting( 'player_fallbacks' ); } elseif ( function_exists( 'apply_filters' ) ) { @@ -1721,18 +1838,22 @@ function radio_player_load_script_fallbacks( $js ) { } // --- load fallback audio scripts --- - if ( count( $fallbacks ) > 0 ) { + // 2.5.18: added extra fallback check + if ( $fallbacks && ( count( $fallbacks ) > 0 ) ) { $js .= "head = document.getElementsByTagName('head')[0]; "; - if ( !isset( $radio_player['enqueued_howler'] ) && in_array( 'howler', $fallbacks ) ) { - $js .= "el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el);"; + if ( !isset( $radio_player['enqueued_amplitude'] ) && in_array( 'amplitude', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el);"; } if ( !isset( $radio_player['enqueued_jplayer'] ) && in_array( 'jplayer', $fallbacks ) ) { $js .= "el = document.createElement('script'); el.src = radio_player.scripts.jplayer; head.appendChild(el);"; } - if ( !isset( $radio_player['enqueued_amplitude'] ) && in_array( 'amplitude', $fallbacks ) ) { - $js .= "el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el);"; + if ( !isset( $radio_player['enqueued_audio5'] ) && in_array( 'audio5', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.audio5; head.appendChild(el);"; } - $js .= PHP_EOL; + if ( !isset( $radio_player['enqueued_howler'] ) && in_array( 'howler', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el);"; + } + $js .= "\n"; } return $js; @@ -1746,10 +1867,7 @@ function radio_player_enqueue_amplitude( $infooter ) { if ( function_exists( 'wp_enqueue_script' ) ) { // note: jquery dependency not required wp_enqueue_script( 'amplitude', $radio_player['amplitude_script']['url'], array(), $radio_player['amplitude_script']['version'], $infooter ); - } /* elseif ( !isset( $radio_player['printed_amplitude'] ) ) { - radio_player_script_tag( $radio_player['amplitude_script']['url'], $radio_player['amplitude_script']['version'] ); - $radio_player['printed_amplitude'] = true; - } */ + } } // -------------------------- @@ -1759,10 +1877,17 @@ function radio_player_enqueue_jplayer( $infooter ) { global $radio_player; if ( function_exists( 'wp_enqueue_script' ) ) { wp_enqueue_script( 'jplayer', $radio_player['jplayer_script']['url'], array( 'jquery' ), $radio_player['jplayer_script']['version'], $infooter ); - } /* elseif ( !isset( $radio_player['printed_jplayer'] ) ) { - radio_player_script_tag( $radio_player['jplayer_script']['url'], $radio_player['jplayer_script']['version'] ); - $radio_player['printed_jplayer'] = true; - } */ + } +} + +// ------------------------- +// Enqueue Audio5 Javascript +// ------------------------- +function radio_player_enqueue_audio5( $infooter ) { + global $radio_player; + if ( function_exists( 'wp_enqueue_script' ) ) { + wp_enqueue_script( 'audio5', $radio_player['audio5_script']['url'], array( 'jquery' ), $radio_player['audio5_script']['version'], $infooter ); + } } // ------------------------- @@ -1773,10 +1898,7 @@ function radio_player_enqueue_howler( $infooter ) { global $radio_player; if ( function_exists( 'wp_enqueue_script' ) ) { wp_enqueue_script( 'howler', $radio_player['howler_script']['url'], array( 'jquery' ), $radio_player['howler_script']['version'], $infooter ); - } /* elseif ( !isset( $radio_player['printed_howler'] ) ) { - radio_player_script_tag( $radio_player['howler_script']['url'], $radio_player['howler_script']['version'] ); - $radio_player['printed_howler'] = true; - } */ + } } // -------------------------------- @@ -1875,11 +1997,6 @@ function radio_player_get_player_settings( $echo = false ) { global $radio_player; - /* if ( isset( $radio_player['localized-script'] ) ) { - return ''; - } - $radio_player['localized-script'] = true; */ - $js = ''; // ---- set AJAX URL --- @@ -2005,7 +2122,7 @@ function radio_player_get_player_settings( $echo = false ) { echo "}" . "\n"; // --- maybe limit available scripts for testing purposes --- - $valid_scripts = array( 'amplitude', 'howler', 'jplayer' ); + $valid_scripts = array( 'amplitude', 'jplayer', 'howler', 'audio5' ); // 2.4.0.3: set single script override only // 2.5.0: fix to typo in $_REQUST['player-script'] // phpcs:ignore WordPress.Security.NonceVerification.Recommended @@ -2021,7 +2138,8 @@ function radio_player_get_player_settings( $echo = false ) { // --- set script URL ouput --- // 2.4.0.3: check for fallback script settings // 2.5.7: remove howler from script fallback list - $fallbacks = array( 'jplayer', 'amplitude' ); // 'howler' + // 2.5.18: added audio5 to script fallback list + $fallbacks = array( 'jplayer', 'amplitude', 'audio5' ); // 'howler' if ( function_exists( 'radio_station_get_setting' ) ) { $fallbacks = radio_station_get_setting( 'player_fallbacks' ); } elseif ( function_exists( 'apply_filters' ) ) { @@ -2058,36 +2176,52 @@ function radio_player_get_player_settings( $echo = false ) { if ( in_array( 'amplitude', $scripts ) ) { echo "'amplitude': '" . esc_url( $radio_player['amplitude_script']['url'] ) . '?version=' . esc_js( $radio_player['amplitude_script']['version'] ) . "', "; } - if ( in_array( 'howler', $scripts ) ) { - echo "'howler': '" . esc_url( $radio_player['howler_script']['url'] ) . '?version=' . esc_js( $radio_player['howler_script']['version'] ) . "', "; - } if ( in_array( 'jplayer', $scripts ) ) { echo "'jplayer': '" . esc_url( $radio_player['jplayer_script']['url'] ) . '?version=' . esc_js( $radio_player['jplayer_script']['version'] ) . "', "; } + if ( in_array( 'audio5', $scripts ) ) { + echo "'audio5': '" . esc_url( $radio_player['audio5_script']['url'] ) . '?version=' . esc_js( $radio_player['audio5_script']['version'] ) . "', "; + } + if ( in_array( 'howler', $scripts ) ) { + echo "'howler': '" . esc_url( $radio_player['howler_script']['url'] ) . '?version=' . esc_js( $radio_player['howler_script']['version'] ) . "', "; + } // echo "'media': '" . $radio_player['media_script']['url'] . '?version=' . $radio_player['media_script']['version'] . "', " // echo "'elements': '" . $radio_player['elements_script']['url'] . '?version=' . $radio_player['elements_script']['version'] . "', "; echo "}" . "\n"; // --- set player script supported formats --- // TODO: recheck supported amplitude formats ? + // [Amplitude] HTML5 Support - mp3, aac ...? + // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers // [JPlayer] Audio: mp3, m4a - Video: m4v // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv + // [Audio5] mp3, ogg, webm, mp4, wav + // ref: https://github.com/zohararad/audio5js // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm // +mpeg, oga, caf, weba, webm, dolby, flac - // [Amplitude] HTML5 Support - mp3, aac ...? - // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv // 2.5.7: disable Howler format list echo "formats = {"; echo "'amplitude': ['mp3','aac'], "; echo "'jplayer': ['mp3','m4a','webm','oga','rtmpa','wav','flac'], "; + echo "'audio5': ['mp3', 'ogg', 'webm', 'mp4', 'wav' ], "; echo "'howler': ['mp3','opus','ogg','oga','wav','aac','m4a','mp4','webm','weba','flac'], "; // $js .= "'mediaelements': ['mp3','wma','wav'], "; echo "}" . "\n"; // --- set debug mode --- // 2.5.10: moved conditions to radio_player_set_debug_mode + // 2.5.18: added sync debug switch mode $debug = RADIO_PLAYER_DEBUG ? 'true' : 'false'; + $sync_debug = 'false'; + if ( isset( $_REQUEST['sync-debug'] ) ) { + $switch = sanitize_text_field( wp_unslash( $_REQUEST['sync-debug'] ) ); + if ( ( '1' == $switch ) || ( 'on' == $switch ) ) { + $sync_debug = 'on'; + } elseif ( ( '0' == $switch ) || ( 'off' == $switch ) ) { + $sync_debug = 'off'; + } + } // 2.5.6: added explicit touchscreen detection setting echo "matchmedia = window.matchMedia || window.msMatchMedia;" . "\n"; @@ -2095,7 +2229,8 @@ function radio_player_get_player_settings( $echo = false ) { // --- set radio player settings and radio data objects --- // (with empty arrays for instances, script types, failbacks, audio targets and stream data) - echo "var radio_player = {settings:player_settings, scripts:scripts, formats:formats, loading:false, touchscreen:touchscreen, debug:" . esc_js( $debug ) . "}" . "\n"; + // 2.5.18: added sync debug switch + echo "var radio_player = {settings:player_settings, scripts:scripts, formats:formats, loading:false, touchscreen:touchscreen, debug:" . esc_js( $debug ) . ", sync_debug:" . esc_js( $sync_debug ) . "}" . "\n"; echo "var radio_player_data = {state:{}, players:[], scripts:[], failed:[], data:[], types:[], channels:[], faders:[]}" . "\n"; // --- logged in / user state settings --- @@ -2116,11 +2251,15 @@ function radio_player_get_player_settings( $echo = false ) { if ( isset( $state ) && isset( $state['station'] ) ) { $station = abs( intval( $state['station'] ) ); } - if ( isset( $station ) && $station && ( $station > 0 ) ) { - echo "radio_player_data.state.station = " . esc_js( $station ) . ";" . "\n"; - } else { - echo "radio_player_data.state.station = 0;" . "\n"; + $station = ( isset( $station ) && $station && ( $station > 0 ) ) ? $station : 0; + echo "radio_player_data.state.station = " . esc_js( $station ) . ";" . "\n"; + + // --- maybe set channel ID --- + if ( isset( $state ) && isset( $state['channel'] ) ) { + $station = abs( intval( $state['channel'] ) ); } + $channel = ( isset( $channel ) && $channel && ( $channel > 0 ) ) ? $channel : 0; + echo "radio_player_data.state.channel = " . esc_js( $channel ) . ";" . "\n"; // --- maybe set user volume --- // note: default already set above @@ -2139,7 +2278,6 @@ function radio_player_get_player_settings( $echo = false ) { // --- set main radio stream data --- echo "radio_player_data.state.data = {};" . "\n"; if ( function_exists( 'apply_filters' ) ) { - $station = ( isset( $state['station'] ) ) ? $state['station'] : 0; // note: this is the main stream data filter hooked into by Radio Station plugin // 2.4.0.3: fix for uninitialized string offset $data = apply_filters( 'radio_station_player_data', false, $station ); @@ -2151,7 +2289,7 @@ function radio_player_get_player_settings( $echo = false ) { // 2.5.7: set currently playing data only if script supported $set_data = false; foreach ( $data as $key => $value ) { - if ( ( 'script' == $key ) && in_array( $value, array( 'jplayer', 'amplitude', 'howler' ) ) ) { + if ( ( 'script' == $key ) && in_array( $value, array( 'jplayer', 'amplitude', 'howler', 'audio5' ) ) ) { $set_data = true; } } @@ -2166,7 +2304,9 @@ function radio_player_get_player_settings( $echo = false ) { echo "radio_player_data.state.data['" . esc_js( $key ) . "'] = '" . esc_js( $value ) . "';" . "\n"; } } + // 2.5.18: set default channel data echo "radio_player.stream_data = radio_player_data.state.data;" . "\n"; + echo "radio_player_data.channels[0] = radio_player_data.state.data;" . "\n"; } // --- attempt to set player state from cookies --- @@ -2235,6 +2375,7 @@ function radio_player_state() { // --- get user state values --- // 2.5.9.4: combined with value sanitizing + // 2.5.18: added channel state saving // phpcs:ignore WordPress.Security.NonceVerification.Recommended $playing = isset( $_REQUEST['playing'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['playing'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended @@ -2242,6 +2383,8 @@ function radio_player_state() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $station = isset( $_REQUEST['station'] ) ? absint( wp_unslash( $_REQUEST['station'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $channel = isset( $_REQUEST['channel'] ) ? absint( wp_unslash( $_REQUEST['channel'] ) ) : false; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $mute = isset( $_REQUEST['mute'] ) ? (bool) sanitize_text_field( wp_unslash( $_REQUEST['mute'] ) ) : false; // --- check user state values --- @@ -2253,12 +2396,16 @@ function radio_player_state() { if ( $station < 1 ) { $station = false; } + if ( $channel < 0 ) { + $station = false; + } // --- save player state to user meta --- $state = array( 'playing' => $playing, 'volume' => $volume, 'station' => $station, + 'channel' => $channel, 'mute' => $mute, ); update_user_meta( $user_id, 'radio_player_state', $state ); @@ -2275,6 +2422,7 @@ function radio_player_state() { // Load Amplitude Function // ----------------------- // "mp3" "aac" ... (+HTML5 Browser Supported Sources) +// deprecated: moved to radio-player.js function radio_player_script_amplitude() { // $method = 'callbacks'; @@ -2473,122 +2621,6 @@ function radio_player_script_amplitude() { return $js; } -// -------------------- -// Load Howler Function -// -------------------- -// Howler Note: "A live stream can only be played through HTML5 Audio." -// "mp3", "opus", "ogg", "wav", "aac", "m4a", "mp4", "webm" -function radio_player_script_howler() { - - // --- load howler player --- - $js = "function radio_player_howler(instance, url, format, fallback, fformat) { - - player_id = 'radio_player_'+instance; - container_id = 'radio_container_'+instance; - if (url == '') {url = radio_player.settings.url;} - if (url == '') {return;} - if (!format || (format == '')) {format = 'aac';} - if (fallback == '') {fallback = radio_player.settings.fallback;} - if (!fallback || !fformat || (fformat == '')) {fallback = ''; fformat = '';} - - /* check if already loaded with same source - if ( (typeof radio_player_data.scripts[instance] != 'undefined') && (radio_player_data.scripts[instance] == 'howler') - && (typeof radio_player.previous_data != 'undefined') ) { - pdata = radio_player.previous_data; - if ( (pdata.url == url) && (pdata.format == format) && (pdata.fallback == fallback) && (pdata.fformat == fformat) ) { - if (radio_player.debug) {console.log('Player already loaded with this stream data.');} - return radio_player_data.players[instance]; - } - } */ - - /* set sources */ - sources = new Array(); formats = new Array(); - sources[0] = url; /* if (fallback != '') {sources[1] = fallback;} */ - formats[0] = format; /* if ((fallback != '') && (fformat != '')) {formats[1] = fformat;} */ - - /* set volume */ - if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { - volume = jQuery('#'+container_id+' .rp-volume-slider').val(); - } else if (typeof radio_player_data.state.volume != 'undefined') {volume = radio_player_data.state.volume;} - else {volume = radio_player.settings.volume;} - radio_player_volume_slider(instance, volume); - volume = parseFloat(volume / 100); - if (radio_player.debug) {console.log('Howler init Volume: '+volume);} - - /* intialize player */ - if (radio_player.debug) {console.log('Init Howler: '+instance+' : '+url+' : '+format+' : '+fallback+' : '+fformat);} - radio_player_instance = new Howl({ - src: sources, - format: formats, - html5: false, - autoplay: false, - preload: false, - volume: volume, - onload: function(e) { - /* possible bug: maybe not always being triggered ? */ - radio_player.loading = false; - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('loaded', {instance:instance, script:'howler'}); - - channel = radio_player_data.channels[instance]; - if (channel) {radio_player_set_state('channel', channel);} - station = jQuery('#radio_player_'+instance).attr('station-id'); - if (station) {radio_player_set_state('station', station);} - }, - onplay: function(e) { - radio_player.loading = false; - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('playing', {instance:instance, script:'howler'}); - radio_player_pause_others(instance); - }, - onpause: function(e) { - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('paused', {instance:instance, script:'howler'}); - }, - onstop: function(e) { - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('stopped', {instance:instance, script:'howler'}); - }, - onvolume: function(e) { - instance = radio_player_match_instance(this, e, 'howler'); - if (instance && (radio_player_data.scripts[instance] == 'howler')) { - volume = this.volume() * 100; - if (volume > 100) {volume = 100;} - radio_player_player_volume(instance, 'howler', volume); - } - }, - onloaderror: function(id,e) { - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('error', {instance:instance, script:'howler'}); - if (radio_player.debug) {console.log('Load Error, Howler Instance: '+instance+', Sound ID: '+id);} - radio_player_player_fallback(instance, 'howler', 'Howler Load Error'); - }, - onplayerror: function(id,e) { - instance = radio_player_match_instance(this, e, 'howler'); - radio_player_event_handler('error', {instance:instance, script:'howler'}); - if (radio_player.debug) {console.log('Play Error, Howler Instance: '+instance+', Sound ID: '+id);} - radio_player_player_fallback(instance, 'howler', 'Howler Play Error'); - }, - }); - radio_player_data.players[instance] = radio_player_instance; - radio_player_data.scripts[instance] = 'howler'; - - /* match script select dropdown value */ - if (jQuery('#'+container_id+' .rp-script-select').length) { - jQuery('#'+container_id+' .rp-script-select').val('howler'); - } - - return radio_player_instance; - }"; - - // --- filter and return --- - if ( function_exists( 'apply_filters' ) ) { - $js = apply_filters( 'radio_station_player_script_howler', $js ); - $js = apply_filters( 'radio_player_script_howler', $js ); - } - return $js; -} - // --------------------- // Load JPlayer Function // --------------------- @@ -2597,6 +2629,7 @@ function radio_player_script_howler() { // Extra formats: // audio: webma, oga, wav, fla, rtmpa // video: webmv, ogv, flv, rtmpv +// deprecated: moved to radio-player.js function radio_player_script_jplayer() { // --- load jplayer --- @@ -2763,6 +2796,123 @@ function radio_player_script_jplayer() { return $js; } +// -------------------- +// Load Howler Function +// -------------------- +// Howler Note: "A live stream can only be played through HTML5 Audio." +// "mp3", "opus", "ogg", "wav", "aac", "m4a", "mp4", "webm" +// deprecated: moved to radio-player.js +function radio_player_script_howler() { + + // --- load howler player --- + $js = "function radio_player_howler(instance, url, format, fallback, fformat) { + + player_id = 'radio_player_'+instance; + container_id = 'radio_container_'+instance; + if (url == '') {url = radio_player.settings.url;} + if (url == '') {return;} + if (!format || (format == '')) {format = 'aac';} + if (fallback == '') {fallback = radio_player.settings.fallback;} + if (!fallback || !fformat || (fformat == '')) {fallback = ''; fformat = '';} + + /* check if already loaded with same source + if ( (typeof radio_player_data.scripts[instance] != 'undefined') && (radio_player_data.scripts[instance] == 'howler') + && (typeof radio_player.previous_data != 'undefined') ) { + pdata = radio_player.previous_data; + if ( (pdata.url == url) && (pdata.format == format) && (pdata.fallback == fallback) && (pdata.fformat == fformat) ) { + if (radio_player.debug) {console.log('Player already loaded with this stream data.');} + return radio_player_data.players[instance]; + } + } */ + + /* set sources */ + sources = new Array(); formats = new Array(); + sources[0] = url; /* if (fallback != '') {sources[1] = fallback;} */ + formats[0] = format; /* if ((fallback != '') && (fformat != '')) {formats[1] = fformat;} */ + + /* set volume */ + if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { + volume = jQuery('#'+container_id+' .rp-volume-slider').val(); + } else if (typeof radio_player_data.state.volume != 'undefined') {volume = radio_player_data.state.volume;} + else {volume = radio_player.settings.volume;} + radio_player_volume_slider(instance, volume); + volume = parseFloat(volume / 100); + if (radio_player.debug) {console.log('Howler init Volume: '+volume);} + + /* intialize player */ + if (radio_player.debug) {console.log('Init Howler: '+instance+' : '+url+' : '+format+' : '+fallback+' : '+fformat);} + radio_player_instance = new Howl({ + src: sources, + format: formats, + html5: false, + autoplay: false, + preload: false, + volume: volume, + onload: function(e) { + /* possible bug: maybe not always being triggered ? */ + radio_player.loading = false; + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('loaded', {instance:instance, script:'howler'}); + + channel = radio_player_data.channels[instance]; + if (channel) {radio_player_set_state('channel', channel);} + station = jQuery('#radio_player_'+instance).attr('station-id'); + if (station) {radio_player_set_state('station', station);} + }, + onplay: function(e) { + radio_player.loading = false; + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('playing', {instance:instance, script:'howler'}); + radio_player_pause_others(instance); + }, + onpause: function(e) { + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('paused', {instance:instance, script:'howler'}); + }, + onstop: function(e) { + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('stopped', {instance:instance, script:'howler'}); + }, + onvolume: function(e) { + instance = radio_player_match_instance(this, e, 'howler'); + if (instance && (radio_player_data.scripts[instance] == 'howler')) { + volume = this.volume() * 100; + if (volume > 100) {volume = 100;} + radio_player_player_volume(instance, 'howler', volume); + } + }, + onloaderror: function(id,e) { + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('error', {instance:instance, script:'howler'}); + if (radio_player.debug) {console.log('Load Error, Howler Instance: '+instance+', Sound ID: '+id);} + radio_player_player_fallback(instance, 'howler', 'Howler Load Error'); + }, + onplayerror: function(id,e) { + instance = radio_player_match_instance(this, e, 'howler'); + radio_player_event_handler('error', {instance:instance, script:'howler'}); + if (radio_player.debug) {console.log('Play Error, Howler Instance: '+instance+', Sound ID: '+id);} + radio_player_player_fallback(instance, 'howler', 'Howler Play Error'); + }, + }); + radio_player_data.players[instance] = radio_player_instance; + radio_player_data.scripts[instance] = 'howler'; + + /* match script select dropdown value */ + if (jQuery('#'+container_id+' .rp-script-select').length) { + jQuery('#'+container_id+' .rp-script-select').val('howler'); + } + + return radio_player_instance; + }"; + + // --- filter and return --- + if ( function_exists( 'apply_filters' ) ) { + $js = apply_filters( 'radio_station_player_script_howler', $js ); + $js = apply_filters( 'radio_player_script_howler', $js ); + } + return $js; +} + // --------------------------- // Load Media Element Function // --------------------------- @@ -2770,6 +2920,7 @@ function radio_player_script_jplayer() { // API ref: https://github.com/mediaelement/mediaelement/blob/master/docs/api.md // Audio support: MP3, WMA, WAV // Video support: MP4, Ogg, WebM, WMV +// deprecated: not implemented function radio_player_script_mediaelements() { // --- load media elements --- @@ -3105,10 +3256,11 @@ function radio_player_control_styles( $instance, $atts = false ) { // --- set default control colors --- // 2.5.0: added empty text and background color styles + // 2.5.18: adjust playing default from #70E070 $colors = array( 'text' => '', 'background' => '', - 'playing' => '#70E070', + 'playing' => '#70D070', 'buttons' => '#00A0E0', 'track' => '#80C080', 'thumb' => '#80C080', diff --git a/radio-station.php b/radio-station.php index 3f55190..a5d6aa6 100644 --- a/radio-station.php +++ b/radio-station.php @@ -58,12 +58,12 @@ // - Register Moment JS // - Enqueue Plugin Script // - Add Inline Script -// - Print Footer Scripts -// - Print Admin Footer Scripts +// - Enqueue Footer Scripts +// x Print Admin Footer Scripts // - Enqueue Plugin Stylesheet // - Add Inline Style -// - Print Footer Styles -// - Print Admin Footer Styles +// - Enqueue Footer Styles +// x Print Admin Footer Styles // - Enqueue Datepicker // - Enqueue Localized Script Values // - Localization Script @@ -674,12 +674,6 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = // 2.5.0: added for missed inline scripts (via shortcodes) function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { - // --- maybe enqueue footer dummy script --- - /* if ( ( 'rs-footer' == $handle ) && !wp_script_is( 'rs-footer', 'enqueued' ) ) { - $script_url = plugins_url( '/js/rs-footer.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'rs-footer', $script_url, array(), '1.0.0', true ); - } */ - // --- add check if script is already done --- if ( wp_script_is( $handle, 'registered' ) && !wp_script_is( $handle, 'done' ) ) { @@ -691,37 +685,31 @@ function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { // 2.5.7: enqueue dummy javascript file to output in footer // 2.5.10: fix slug from rp-footer to rs-footer // 2.5.10: change from register_script to enqueue_script - /* if ( !wp_script_is( 'rs-footer', 'enqueued' ) ) { - $script_url = plugins_url( '/js/rs-footer.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'rs-footer', $script_url, array(), '1.0.0', true ); - } - wp_add_inline_script( 'rs-footer', $js, $position ); */ - - // 2.5.7: enqueue dummy javascript file to output in footer + // 2.5.18: register here but enqueue later if ( !wp_script_is( 'radio-station-footer', 'registered' ) ) { - $version = functIon_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; + $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; wp_register_script( 'radio-station-footer', null, array( 'jquery' ), $version, true ); - wp_enqueue_script( 'radio-station-footer' ); + // wp_enqueue_script( 'radio-station-footer' ); } wp_add_inline_script( 'radio-station-footer', $js, $position ); + + // 2.5.18: enqueue in footer + if ( !has_action( 'wp_footer', 'radio_station_enqueue_footer_scripts', 9 ) ) { + add_action( 'wp_footer', 'radio_station_enqueue_footer_scripts', 9 ); + } } } -// -------------------- -// Print Footer Scripts -// -------------------- -// 2.5.0: added for missed inline scripts -// 2.5.7: deprecated in favour of adding inline to dummy script -/* function radio_station_print_footer_scripts() { - global $radio_station_scripts; - if ( is_array( $radio_station_scripts ) && ( count( $radio_station_scripts ) > 0 ) ) { - foreach ( $radio_station_scripts as $handle => $js ) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo ''; - } - } -} */ +// ---------------------- +// Enqueue Footer Scripts +// ---------------------- +// 2.5.0: added print footer scripts for missed inline scripts +// 2.5.7: deprecated print script in favour of adding inline to dummy script +// 2.5.18: repurpose to enqueue footer scripts +function radio_station_enqueue_footer_scripts() { + wp_enqueue_script( 'radio-station-footer' ); +} // -------------------------- // Print Admin Footer Scripts @@ -794,43 +782,28 @@ function radio_station_add_inline_style( $handle, $css ) { wp_add_inline_style( $handle, $css ); } else { - // --- store extra styles for later output --- - /* if ( !strstr( $handle, '-admin' ) ) { - global $radio_station_styles; - add_action( 'wp_print_footer_scripts', 'radio_station_print_footer_styles', 20 ); - if ( !isset( $radio_station_styles[$handle] ) ) { - $radio_station_styles[$handle] = ''; - } - $radio_station_styles[$handle] .= $css; - } else { - global $radio_station_admin_styles; - add_action( 'admin_print_footer_scripts', 'radio_station_admin_print_footer_styles', 20 ); - if ( !isset( $radio_station_admin_styles[$handle] ) ) { - $radio_station_admin_styles[$handle] = ''; - } - $radio_station_admin_styles[$handle] .= $css; - } */ + // 2.5.18: register inline style but enqueue later if ( !wp_style_is( 'radio-station-footer', 'registered' ) ) { $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; wp_register_style( 'radio-station-footer', null, array(), $version, 'all' ); - wp_enqueue_style( 'radio-station-footer' ); + // wp_enqueue_style( 'radio-station-footer' ); } wp_add_inline_style( 'radio-station-footer', $css ); + + if ( !has_action( 'wp_footer', 'radio_station_enqueue_footer_styles', 9 ) ) { + add_action( 'wp_footer', 'radio_station_enqueue_footer_styles', 9 ); + } } } -// ------------------- -// Print Footer Styles -// ------------------- -// 2.5.0: added for missed inline styles -/* function radio_station_print_footer_styles() { - global $radio_station_styles; - if ( is_array( $radio_station_styles ) && ( count( $radio_station_styles ) > 0 ) ) { - foreach ( $radio_station_styles as $handle => $css ) { - echo ''; - } - } -} */ +// --------------------- +// Enqueue Footer Styles +// --------------------- +// 2.5.0: added print in footer for missed inline styles +// 2.5.18: repurpose to enqueue footer Styles +function radio_station_enqueue_footer_styles() { + wp_enqueue_style( 'radio-station-footer' ); +} // ------------------------- // Print Admin Footer Styles diff --git a/readme.txt b/readme.txt index a8d71fa..fa8ab0b 100644 --- a/readme.txt +++ b/readme.txt @@ -407,6 +407,7 @@ We recommend you test these on a Staging site (or a development copy of your liv = 2.5.18 = * Updated: Freemius SDK (2.13.0) * Updated: Plugin Panel (1.3.7) for delayed translations +* Updated: Radio Player (1.0.4) * Changed: split station content and settings Admin menus * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player @@ -414,12 +415,15 @@ We recommend you test these on a Staging site (or a development copy of your liv * Improved: handle Special/Override meta in Archive shortcode * Improved: edit Special/Override permissions for linked Shows * Improved: Timezone and Clock Timezone display formatting +* Improved: Special override interface change highlighting * Added: Dashboard overview widget and Content Dashboard page * Added: Station Frequency and Location options * Added: Channel inputs for Shifts and Overrides +* Added: Special Overrides data routes/feed to Data API * Added: Link main language term to filtered show archive +* Fixed: Player Amplitude script pause/unpause source bug * Fixed: Special/Override (non-linked) Host/Producer data -* Fixed: Radio Player plugin conflicts +* Fixed: Radio Player (other developer) plugin conflicts * Fixed: Override Drafts without dates not listing in Admin * Fixed: Block asset paths for script/style file versions * Fixed: Display user timezone for timezone shortcode diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index 48ce162..dd17319 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -653,6 +653,18 @@ public function process_overrides( $overrides, $start_date = false, $end_date = $channel = $this->channel; $context = $this->context; + if ( !$timezone ) { + $timezone = $this->get_timezone(); + } + + // 2.5.18: add missing range time definitions + if ( $start_date ) { + $range_start_time = $this->to_time( $start_date, $timezone ); + } + if ( $end_date ) { + $range_end_time = $this->to_time( $end_date, $timezone ); + } + // --- loop overrides and get data --- $override_list = array(); foreach ( $overrides as $i => $override ) { @@ -661,9 +673,6 @@ public function process_overrides( $overrides, $start_date = false, $end_date = $override_shifts = $override['shifts']; $show = $override['show']; - // if ( !isset( $show['title'] ) ) { - // echo 'Show! ' . print_r( $show, true ) . ''; - // } if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { @@ -688,7 +697,7 @@ public function process_overrides( $overrides, $start_date = false, $end_date = // --- check if in specified date range --- if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) - || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { + || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { $inrange = false; } @@ -719,12 +728,13 @@ public function process_overrides( $overrides, $start_date = false, $end_date = } } */ + $recurs = isset( $data['recurs'] ) ? $data['recurs'] : ''; if ( $override_start_time < $override_end_time ) { // --- add the override as is --- $override_data = array( - 'override' => $show['id'], + 'show' => $show['id'], 'id' => $data['id'], 'name' => $show['title'], 'slug' => $show['slug'], @@ -732,6 +742,7 @@ public function process_overrides( $overrides, $start_date = false, $end_date = 'day' => $day, 'start' => $start, 'end' => $end, + 'recurs' => $recurs, 'url' => get_permalink( $show['id'] ), 'split' => false, ); @@ -751,6 +762,7 @@ public function process_overrides( $overrides, $start_date = false, $end_date = 'start' => $start, 'end' => '11:59:59 pm', 'real_end' => $end, + 'recurs' => $recurs, 'url' => get_permalink( $show['id'] ), 'split' => true, ); @@ -776,6 +788,7 @@ public function process_overrides( $overrides, $start_date = false, $end_date = 'real_start' => $start, 'start' => '00:00 am', 'end' => $end, + 'recurs' => $recurs, 'url' => get_permalink( $show['id'] ), 'split' => true, ); diff --git a/widgets/class-current-playlist-widget.php b/widgets/class-current-playlist-widget.php index 7f1c454..97aaa47 100644 --- a/widgets/class-current-playlist-widget.php +++ b/widgets/class-current-playlist-widget.php @@ -206,6 +206,13 @@ public function update( $new_instance, $old_instance ) { $instance['label'] = isset( $new_instance['label'] ) ? 1 : 0; $instance['comments'] = isset( $new_instance['comments'] ) ? 1 : 0; + // 2.5.18: save (or preserve) channel setting + if ( isset( $new_instance['channel'] ) ) { + $instance['channel'] = $new_instance['channel']; + } elseif ( isset( $old_instance['channel'] ) ) { + $instance['channel'] = $old_instance['channel']; + } + // 2.3.0: apply filters to widget instance update $instance = apply_filters( 'radio_station_playlist_widget_update', $instance, $new_instance, $old_instance ); return $instance; diff --git a/widgets/class-current-show-widget.php b/widgets/class-current-show-widget.php index ed4ce06..5805e26 100644 --- a/widgets/class-current-show-widget.php +++ b/widgets/class-current-show-widget.php @@ -303,6 +303,13 @@ public function update( $new_instance, $old_instance ) { $instance['show_playlist'] = isset( $new_instance['show_playlist'] ) ? 1 : 0; $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; + // 2.5.18: save (or preserve) channel setting + if ( isset( $new_instance['channel'] ) ) { + $instance['channel'] = $new_instance['channel']; + } elseif ( isset( $old_instance['channel'] ) ) { + $instance['channel'] = $old_instance['channel']; + } + // 2.3.0: filter widget update instance $instance = apply_filters( 'radio_station_current_show_widget_update', $instance, $new_instance, $old_instance ); return $instance; diff --git a/widgets/class-radio-clock-widget.php b/widgets/class-radio-clock-widget.php index b0f68c3..5fe3bc6 100644 --- a/widgets/class-radio-clock-widget.php +++ b/widgets/class-radio-clock-widget.php @@ -125,6 +125,13 @@ public function update( $new_instance, $old_instance ) { $instance['seconds'] = isset( $new_instance['seconds'] ) ? 1 : 0; $instance['zone'] = isset( $new_instance['zone'] ) ? 1 : 0; + // 2.5.18: save (or preserve) channel setting + if ( isset( $new_instance['channel'] ) ) { + $instance['channel'] = $new_instance['channel']; + } elseif ( isset( $old_instance['channel'] ) ) { + $instance['channel'] = $old_instance['channel']; + } + // 2.5.0: filter widget update instance $instance = apply_filters( 'radio_station_clock_widget_update', $instance, $new_instance, $old_instance ); return $instance; @@ -157,8 +164,9 @@ public function widget( $args, $instance ) { // --- set shortcode attributes for display --- $atts = array( + // 2.5.18: change time key to time_format // --- clock options --- - 'time' => $time, + 'time_format' => $time, 'day' => $day, 'date' => $date, 'month' => $month, @@ -168,6 +176,13 @@ public function widget( $args, $instance ) { 'widget' => 1, 'id' => $id, ); + + // TODO: added attributes fields + /* 'timezone' => $timezone, + 'code' => 1, + 'region' => 1, + 'location' => 1, + 'offset' => 1, */ // 2.3.3.9: add missing filter for clock widget attributes $atts = apply_filters( 'radio_station_clock_widget_atts', $atts, $instance ); diff --git a/widgets/class-radio-player-widget.php b/widgets/class-radio-player-widget.php index e0add08..56b211d 100644 --- a/widgets/class-radio-player-widget.php +++ b/widgets/class-radio-player-widget.php @@ -296,6 +296,13 @@ public function update( $new_instance, $old_instance ) { } $instance['volumes'] = implode( ',', $volumes ); + // 2.5.18: save (or preserve) channel setting + if ( isset( $new_instance['channel'] ) ) { + $instance['channel'] = $new_instance['channel']; + } elseif ( isset( $old_instance['channel'] ) ) { + $instance['channel'] = $old_instance['channel']; + } + // --- additional displays --- // $instance['show_display'] = isset( $new_instance['show_display'] ) ? 1 : 0; // $instance['show_hosts'] = isset( $new_instance['show_hosts'] ) ? 1 : 0; @@ -375,7 +382,7 @@ public function widget( $args, $instance ) { 'widget' => 1, 'id' => $id, ); - + // 2.5.0: add filter for default widget attributes $atts = apply_filters( 'radio_station_player_widget_atts', $atts, $instance ); diff --git a/widgets/class-upcoming-shows-widget.php b/widgets/class-upcoming-shows-widget.php index 2426d02..0d6e9d4 100644 --- a/widgets/class-upcoming-shows-widget.php +++ b/widgets/class-upcoming-shows-widget.php @@ -278,6 +278,13 @@ public function update( $new_instance, $old_instance ) { $instance['link_djs'] = isset( $new_instance['link_djs'] ) ? 1 : 0; $instance['show_encore'] = isset( $new_instance['show_encore'] ) ? 1 : 0; + // 2.5.18: save (or preserve) channel setting + if ( isset( $new_instance['channel'] ) ) { + $instance['channel'] = $new_instance['channel']; + } elseif ( isset( $old_instance['channel'] ) ) { + $instance['channel'] = $old_instance['channel']; + } + // 2.3.0: added widget filter instance to update $instance = apply_filters( 'radio_station_upcoming_shows_widget_update', $instance, $new_instance, $old_instance ); return $instance; From 422ddff755bd3aa59730c6fa9f805d1097e1b33f Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 10 Mar 2026 22:16:34 +1000 Subject: [PATCH 370/377] dev updates --- CHANGELOG.md | 9 +- includes/extra-strings.php | 7 +- options.php | 253 +++++++++++++++++++++++++------------ readme.txt | 5 +- 4 files changed, 185 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beed7b0..2be3bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 = 2.5.18 = * Updated: Freemius SDK (2.13.0) * Updated: Plugin Panel (1.3.7) for delayed translations +* Updated: Radio Player (1.0.4) * Changed: split station content and settings Admin menus * Changed: display label of Overrides to Specials * Changed: handle of radio-player assets to stream-player @@ -19,12 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improved: Timezone and Clock Timezone display formatting * Improved: Special override interface change highlighting * Added: Dashboard overview widget and Content Dashboard page -* Added: Station Frequency and Location options -* Added: Channel inputs for Shifts and Overrides +* Added: Station Tagline, Callsign, Frequency, Location options +* Added: Default Player display for Station Meta +* Added: Special Overrides data routes/feed to Data API * Added: Link main language term to filtered show archive +* Added: Channel input support for Shifts and Overrides * Fixed: Player Amplitude script pause/unpause source bug * Fixed: Special/Override (non-linked) Host/Producer data -* Fixed: Radio Player plugin conflicts +* Fixed: Radio Player (other developer) plugin conflicts * Fixed: Override Drafts without dates not listing in Admin * Fixed: Block asset paths for script/style file versions * Fixed: Display user timezone for timezone shortcode diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 07f10b4..f84c12b 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -17,6 +17,9 @@ __( 'Custom Country Blacklist', 'radio-station' ); __( 'Custom Country Whitelist', 'radio-station' ); __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ); +__( 'Station Tagline', 'radio-station' ); +__( '', 'radio-station' ); +__( 'Station Callsign', 'radio-station' ); __( 'Broadcast Frequency', 'radio-station' ); __( 'Text display to inform users of your main station frequency.', 'radio-station' ); __( 'Frequency Band', 'radio-station' ); @@ -24,6 +27,8 @@ __( 'FM', 'radio-station' ); __( 'AM', 'radio-station' ); __( 'Your station frequency band identifier.', 'radio-station' ); +__( 'Station Text', 'radio-station' ); +__( 'Text line phone number for the Station (for requests etc.', 'radio-station' ); __( 'Display Start Hour', 'radio-station' ); __( 'Midnight', 'radio-station' ); __( 'Noon', 'radio-station' ); @@ -158,7 +163,6 @@ __( 'The time that the Episode aired', 'radio-station' ); __( 'Episode Length', 'radio-station' ); __( 'minutees', 'radio-station' ); -__( '', 'radio-station' ); __( 'Linked Playlist', 'radio-station' ); __( 'Select Playlist', 'radio-station' ); __( 'No Playlists to Select.', 'radio-station' ); @@ -295,6 +299,7 @@ __( 'Now Playing Track Display', 'radio-station' ); __( 'Leave blank to use stream URL.', 'radio-station' ); __( 'Custom Theme', 'radio-station' ); +__( 'Expand', 'radio-station' ); __( 'Popup Player to separate window.', 'radio-station' ); __( 'Popup Player', 'radio-station' ); __( 'Start Audio Playback?', 'radio-station' ); diff --git a/options.php b/options.php index e54a41c..90630e4 100644 --- a/options.php +++ b/options.php @@ -28,6 +28,12 @@ function radio_station_plugin_options( $admin = false ) { $languages = radio_station_get_language_options( true, $admin ); $formats = radio_station_get_stream_formats(); + // 2.6.16: add am/pm translations for schedule_start_hour + if ( $admin ) { + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + } + $options = array( // === Stream === @@ -77,6 +83,7 @@ function radio_station_plugin_options( $admin = false ) { ), // --- [Player] Stream GeoBlocking --- + // TODO: conditional display of blacklist/whitelist fields 'stream_geo_blocking' => array( 'label' => $admin ? __( 'GeoIP Stream Blocking', 'radio-station' ) : '', 'type' => 'select', @@ -85,6 +92,7 @@ function radio_station_plugin_options( $admin = false ) { 'live365' => $admin ? __( 'Live365 (only US, UK, Canada, Mexico)', 'radio-station' ) : '', // 'blacklist' => $admin ? __( 'Custom Country Blacklist', 'radio-station' ) : '', // 'whitelist' => $admin ? __( 'Custom Country Whitelist', 'radio-station' ) : '', + // 'both' => $admin ? __( 'Blacklist and Whitelist', 'radio-station' ) : '', ), 'default' => '', 'helper' => $admin ? __( 'Block streaming according to country, detected by user IP address.', 'radio-station' ) : '', @@ -130,6 +138,56 @@ function radio_station_plugin_options( $admin = false ) { 'tab' => 'general', 'section' => 'broadcast', ), + + // --- Station Frequency --- + // 2.5.18: added station frequency option + 'station_frequency' => array( + 'type' => 'text', + 'label' => $admin ? __( 'Station Frequency', 'radio-station' ) : '', + 'default' => '', + 'helper' => $admin ? __( 'Your station frequency as a number.', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Band --- + // 2.5.18: addde station band option + 'station_band' => array( + 'type' => 'select', + 'label' => $admin ? __( 'Frequency Band', 'radio-station' ) : '', + 'options' => array( + '' => __( 'n/a', 'radio-station' ), + 'fm' => __( 'FM', 'radio-station' ), + 'am' => __( 'AM', 'radio-station' ), + ), + 'default' => '', + 'helper' => $admin ? __( 'Your station frequency band identifier.', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Service Identifier --- + // TODO: service identifier for RadioDNS + /* 'service_identifier' => array( + 'type' => 'text', + 'label' => $admin ? __( 'Service Identifier', 'radio-station' ) : '', + 'default' => '', + 'helper' => $admin ? __( 'RadioDNS Service Identifier.', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'station', + ), */ + + // --- Ping Netmix Directory --- + // note: disabled by default for WordPress.org repository compliance + 'ping_netmix_directory' => array( + 'type' => 'checkbox', + 'label' => $admin ? __( 'Ping Netmix Directory', 'radio-station' ) : '', + 'default' => '', + 'value' => 'yes', + 'helper' => $admin ? __( 'Enable this to ping the Netmix Directory whenever you update your schedule.', 'radio-station' ) : '', + 'tab' => 'general', + 'section' => 'broadcast', + ), // === Station === @@ -144,24 +202,24 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'station', ), - // --- Station Callsign --- - // 2.5.18: added station callsign field - 'station_callsign' => array( + // --- Station Tagline --- + // 2.5.18: added station tagline + 'station_tagline' => array( 'type' => 'text', - 'label' => $admin ? __( 'Station Callsign', 'radio-station' ) : '', + 'label' => $admin ? __( 'Station Tagline', 'radio-station' ) : '', 'default' => '', - 'helper' => $admin ? __( '', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Tagline for your Radio Station. (eg. 24/7 Greatest Hits.)', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), - // --- Station Tagline --- - // 2.5.18: added station tagline - 'station_tagline' => array( + // --- Station Callsign --- + // 2.5.18: added station callsign field + 'station_callsign' => array( 'type' => 'text', - 'label' => $admin ? __( 'Station Tagline', 'radio-station' ) : '', + 'label' => $admin ? __( 'Station Callsign', 'radio-station' ) : '', 'default' => '', - 'helper' => $admin ? __( '', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Short callsign for your Radio Station. (eg. TOP.)', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), @@ -177,33 +235,6 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'station', ), - // --- Station Frequency --- - // 2.5.18: added station frequency option - 'station_frequency' => array( - 'type' => 'text', - 'label' => $admin ? __( 'Station Frequency', 'radio-station' ) : '', - 'default' => '', - 'helper' => $admin ? __( 'Your station frequency as a number.', 'radio-station' ) : '', - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Band --- - // 2.5.18: addde station band option - 'station_band' => array( - 'type' => 'select', - 'label' => $admin ? __( 'Frequency Band', 'radio-station' ) : '', - 'options' => array( - '' => __( 'n/a', 'radio-station' ), - 'fm' => __( 'FM', 'radio-station' ), - 'am' => __( 'AM', 'radio-station' ), - ), - 'default' => '', - 'helper' => $admin ? __( 'Your station frequency band identifier.', 'radio-station' ) : '', - 'tab' => 'general', - 'section' => 'broadcast', - ), - // --- Station Location --- // 2.5.18: added station location option 'station_location' => array( @@ -227,37 +258,27 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'station', ), - // --- Phone for Shows --- - // 2.3.3.6: added default to station phone option - 'shows_phone' => array( - 'type' => 'checkbox', + // --- Station Text Number --- + // 2.5.18: added station text in number + 'station_text' => array( + 'type' => 'text', + 'options' => 'PHONE', + 'label' => $admin ? __( 'Station Text', 'radio-station' ) : '', 'default' => '', - 'value' => 'yes', - 'label' => $admin ? __( 'Show Phone Display', 'radio-station' ) : '', - 'helper' => $admin ? __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Text line phone number for the Station (for requests etc.)', 'radio-station' ) : '', 'tab' => 'general', 'section' => 'station', ), // --- Station Email Address --- // 2.3.3.8: added station email address option + // TODO: allow for contact page URL instead ? 'station_email' => array( 'type' => 'email', 'default' => '', 'label' => $admin ? __( 'Station Email', 'radio-station' ) : '', 'helper' => $admin ? __( 'Main email address for the Station (for requests etc.)', 'radio-station' ) : '', - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Email for Shows --- - // 2.3.3.8: added default to email address option - 'shows_email' => array( - 'type' => 'checkbox', - 'default' => '', - 'value' => 'yes', - 'label' => $admin ? __( 'Show Email Display', 'radio-station' ) : '', - 'helper' => $admin ? __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ) : '', + // . ' ' . __( 'Alternatively, enter the URL for your contact page.' , 'radio-station' ) 'tab' => 'general', 'section' => 'station', ), @@ -286,17 +307,17 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'feeds', ), - // --- Ping Netmix Directory --- - // note: disabled by default for WordPress.org repository compliance - 'ping_netmix_directory' => array( + // --- Show Shift Feeds --- + /* 'show_shift_feeds' => array( 'type' => 'checkbox', - 'label' => $admin ? __( 'Ping Netmix Directory', 'radio-station' ) : '', - 'default' => '', + 'label' => $admin ? __( 'Show Shift Feeds', 'radio-station' ) : '', + 'default' => 'yes', 'value' => 'yes', - 'helper' => $admin ? __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ) : '', 'tab' => 'general', - 'section' => 'broadcast', - ), + 'section' => 'feeds', + 'pro' => true, + ), */ // === Performance === // 2.4.0.6: separated performance section @@ -337,18 +358,6 @@ function radio_station_plugin_options( $admin = false ) { 'pro' => true, ), - // --- Show Shift Feeds --- - /* 'show_shift_feeds' => array( - 'type' => 'checkbox', - 'label' => $admin ? __( 'Show Shift Feeds', 'radio-station' ) : '', - 'default' => 'yes', - 'value' => 'yes', - 'helper' => $admin ? __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ) : '', - 'tab' => 'general', - 'section' => 'feeds', - 'pro' => true, - ), */ - // === Basic Stream Player === // --- Defaults Note --- @@ -389,11 +398,12 @@ function radio_station_plugin_options( $admin = false ) { 'type' => 'multicheck', 'label' => $admin ? __( 'Display Station Meta', 'radio-station' ) : '', 'options' => array( - // 'tagline' => $admin ? __( 'Tagline', 'radio-station' ) : '', + 'tagline' => $admin ? __( 'Tagline', 'radio-station' ) : '', 'frequency' => $admin ? __( 'Frequency', 'radio-station' ) : '', 'location' => $admin ? __( 'Location', 'radio-station' ) : '', 'timezone' => $admin ? __( 'Timezone', 'radio-station' ) : '', - // 'phone' => $admin ? __( 'Phone', 'radio-station' ) : '', + // 'phone' => $admin ? __( 'Phone Number', 'radio-station' ) : '', + // 'text' => $admin ? __( 'Text Number', 'radio-station' ) : '', ), 'default' => array( 'frequency', 'location' ), 'helper' => $admin ? __( 'Display your Radio Station Meta in Player by default.', 'radio-station' ) : '', @@ -922,6 +932,43 @@ function radio_station_plugin_options( $admin = false ) { 'pro' => true, ), + // --- schedule start hour --- + 'schedule_start_hour' => array( + 'label' => $admin ? __( 'Display Start Hour', 'radio-station' ) : '', + 'type' => 'select', + 'options' => array( + 0 => $admin ? __( 'Midnight', 'radio-station' ) : 'Midnight', + 1 => $admin ? '1 ' . $am : '1 am', + 2 => $admin ? '2 ' . $am : '2 am', + 3 => $admin ? '3 ' . $am : '3 am', + 4 => $admin ? '4 ' . $am : '4 am', + 5 => $admin ? '5 ' . $am : '5 am', + 6 => $admin ? '6 ' . $am : '6 am', + 7 => $admin ? '7 ' . $am : '7 am', + 8 => $admin ? '8 ' . $am : '8 am', + 9 => $admin ? '9 ' . $am : '9 am', + 10 => $admin ? '10 ' . $am : '10 am', + 11 => $admin ? '11 ' . $am : '11 am', + 12 => $admin ? __( 'Noon', 'radio-station' ) : 'Noon', + 13 => $admin ? '1 ' . $pm : '1 pm', + 14 => $admin ? '2 ' . $pm : '2 pm', + 15 => $admin ? '3 ' . $pm : '3 pm', + 16 => $admin ? '4 ' . $pm : '4 pm', + 17 => $admin ? '5 ' . $pm : '5 pm', + 18 => $admin ? '6 ' . $pm : '6 pm', + 19 => $admin ? '7 ' . $pm : '7 pm', + 20 => $admin ? '8 ' . $pm : '8 pm', + 21 => $admin ? '9 ' . $pm : '9 pm', + 22 => $admin ? '10 ' . $pm : '10 pm', + 23 => $admin ? '11 ' . $pm : '11 pm', + ), + 'default' => '0', + 'helper' => $admin ? __( 'Schedule displays will start from this hour instead of midnight.', 'radio-station' ) : '', + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + // === Show Pages === // --- Show Blocks Position --- @@ -984,18 +1031,46 @@ function radio_station_plugin_options( $admin = false ) { 'section' => 'show', ), - // --- Show Header Image --- - // 2.3.2: added plural to option label - 'show_header_image' => array( + // --- Phone for Shows --- + // 2.3.3.6: added default to station phone option + // 2.5.18: moved from station section + 'shows_phone' => array( 'type' => 'checkbox', - 'label' => $admin ? __( 'Content Header Images', 'radio-station' ) : '', + 'default' => '', 'value' => 'yes', + 'label' => $admin ? __( 'Show Phone Default', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ) : '', + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Text for Shows --- + // TODO: text number display on show pages + /* 'shows_text' => array( + 'type' => 'checkbox', 'default' => '', - 'helper' => $admin ? __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ) : '', + 'value' => 'yes', + 'label' => $admin ? __( 'Show Email Default', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Display Station text number on Shows where a Show text number is not set.', 'radio-station' ) : '', + 'tab' => 'pages', + 'section' => 'show', + ), */ + + // --- Email for Shows --- + // 2.3.3.8: added default to email address option + // 2.5.18: moved from station section + 'shows_email' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => $admin ? __( 'Show Email Display', 'radio-station' ) : '', + 'helper' => $admin ? __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ) : '', 'tab' => 'pages', 'section' => 'show', ), + // TODO: Show RSS / Calendar Links ? + // --- Latest Show Posts --- // 'show_latest_posts' => array( // 'type' => 'numeric', @@ -1068,6 +1143,18 @@ function radio_station_plugin_options( $admin = false ) { 'pro' => true, ), + // --- Show Header Image --- + // 2.3.2: added plural to option label + 'show_header_image' => array( + 'type' => 'checkbox', + 'label' => $admin ? __( 'Content Header Images', 'radio-station' ) : '', + 'value' => 'yes', + 'default' => '', + 'helper' => $admin ? __( 'If your theme template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ) : '', + 'tab' => 'pages', + 'section' => 'show', + ), + // === Profile Pages === // 2.3.3.9: added proflie page settings diff --git a/readme.txt b/readme.txt index fa8ab0b..0c63f8f 100644 --- a/readme.txt +++ b/readme.txt @@ -417,10 +417,11 @@ We recommend you test these on a Staging site (or a development copy of your liv * Improved: Timezone and Clock Timezone display formatting * Improved: Special override interface change highlighting * Added: Dashboard overview widget and Content Dashboard page -* Added: Station Frequency and Location options -* Added: Channel inputs for Shifts and Overrides +* Added: Station Tagline, Callsign, Frequency, Location options +* Added: Default Player display for Station Meta * Added: Special Overrides data routes/feed to Data API * Added: Link main language term to filtered show archive +* Added: Channel input support for Shifts and Overrides * Fixed: Player Amplitude script pause/unpause source bug * Fixed: Special/Override (non-linked) Host/Producer data * Fixed: Radio Player (other developer) plugin conflicts From d6d769289ee2af1b074885c43adb17880f69d69a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 15 Mar 2026 11:35:09 +1000 Subject: [PATCH 371/377] dev updates --- includes/extra-strings.php | 6 ++++ options.php | 16 +++++++++ player/css/radio-player.css | 57 ++++++++++++++++----------------- player/css/radio-player.min.css | 2 +- player/js/radio-player.js | 2 +- player/radio-player.php | 56 +++++++++++++++++++++++++------- 6 files changed, 96 insertions(+), 43 deletions(-) diff --git a/includes/extra-strings.php b/includes/extra-strings.php index f84c12b..0dd8126 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -59,6 +59,10 @@ __( 'Episode Player Controls theme style (Radio Station Stream Player only,', 'radio-station' ); __( 'Use Latest Episode', 'radio-station' ); __( 'Automatically use the latest Episode URL for the player embed on the Show page.', 'radio-station' ); +__( 'Responsive Mode', 'radio-station' ); +__( 'Horizontal Arrows', 'radio-station' ); +__( 'Vertical Expander', 'radio-station' ); +__( 'Player bar responsive mode for mobile overflow sections.', 'radio-station' ); __( 'Popup Player Button', 'radio-station' ); __( 'Add button to open Popup Player in separate window.', 'radio-station' ); __( 'Player Bar Height', 'radio-station' ); @@ -299,7 +303,9 @@ __( 'Now Playing Track Display', 'radio-station' ); __( 'Leave blank to use stream URL.', 'radio-station' ); __( 'Custom Theme', 'radio-station' ); +__( 'Close', 'radio-station' ); __( 'Expand', 'radio-station' ); +__( 'Collapse', 'radio-station' ); __( 'Popup Player to separate window.', 'radio-station' ); __( 'Popup Player', 'radio-station' ); __( 'Start Audio Playback?', 'radio-station' ); diff --git a/options.php b/options.php index 90630e4..fc25e29 100644 --- a/options.php +++ b/options.php @@ -650,6 +650,22 @@ function radio_station_plugin_options( $admin = false ) { 'pro' => true, ), + // --- [Pro/Player] Responsive Mode --- + // 2.5.18: added option for responsive expander bar + 'player_bar_responsive' => array( + 'type' => 'select', + 'label' => $admin ? __( 'Bar Responsive Mode', 'radio-station' ) : '', + 'options' => array( + 'left-right' => $admin ? __( 'Horizontal Arrows', 'radio-station' ) : '', + 'expander' => $admin ? __( 'Vertical Expander', 'radio-station' ) : '', + ), + 'default' => 'left-right', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => $admin ? __( 'Player Bar responsive mode for mobile overflow sections.', 'radio-station' ) : '', + 'pro' => true, + ), + // --- [Pro/Player] Fade In Player Bar --- 'player_bar_fadein' => array( 'type' => 'number', diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 50a0529..3befb61 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -3,7 +3,7 @@ * https://radiostation.pro/stream-player/ * * Author: Tony Hayes - * Skin Version: 1.0.1 (Stream Player 2.5.0) + * Skin Version: 1.0.2 (Stream Player 2.5.0) * * originally based on Blue Monday Skin for jPlayer 2.9.2 ~ (c) 2009-2014 Happyworm Ltd ~ MIT License */ @@ -62,9 +62,9 @@ padding: 10px; } -.radio-container.vertical .rp-station-info, -.radio-container.vertical .rp-interface, -.radio-container.vertical .rp-show-info { +.vertical .rp-station-info, +.vertical .rp-interface, +.vertical .rp-show-info { display: block; width: 100%; vertical-align: top; } @@ -95,10 +95,10 @@ } /* @group Control Buttons */ -.radio-container.horizontal .rp-play-pause-button-bg { +.horizontal .rp-play-pause-button-bg { margin-right: 25px; } -.radio-container.vertical .rp-play-pause-button-bg { +.vertical .rp-play-pause-button-bg { margin-right: 5px; } @@ -109,11 +109,11 @@ width: 40px; height: 40px; background-size: 40px 40px; } -.radio-container.circular .rp-play-pause-button-bg { +.circular .rp-play-pause-button-bg { border-radius: 20px; } -.radio-container.rounded .rp-play-pause-button-bg { +.rounded .rp-play-pause-button-bg { border-radius: 10px; } @@ -125,51 +125,51 @@ opacity: 1; scale: 1.1; } -.radio-container.light.circular .rp-play-pause-button { +.light.circular .rp-play-pause-button { background-image: url("../images/play-light-circular.png?v=2"); } -.radio-container.light.rounded .rp-play-pause-button { +.light.rounded .rp-play-pause-button { background-image: url("../images/play-light-rounded.png?v=2"); } -.radio-container.light.square .rp-play-pause-button { +.light.square .rp-play-pause-button { background-image: url("../images/play-light-square.png?v=2"); } -.radio-container.dark.circular .rp-play-pause-button { +.dark.circular .rp-play-pause-button { background-image: url("../images/play-dark-circular.png?v=2"); } -.radio-container.dark.rounded .rp-play-pause-button { +.dark.rounded .rp-play-pause-button { background-image: url("../images/play-dark-rounded.png?v=2"); } -.radio-container.dark.square .rp-play-pause-button { +.dark.square .rp-play-pause-button { background-image: url("../images/play-dark-square.png?v=2"); } -.radio-container.playing.light.circular .rp-play-pause-button { +.playing.light.circular .rp-play-pause-button { background-image: url("../images/pause-light-circular.png?v=2"); } -.radio-container.playing.light.rounded .rp-play-pause-button { +.playing.light.rounded .rp-play-pause-button { background-image: url("../images/pause-light-rounded.png?v=2"); } -.radio-container.playing.light.square .rp-play-pause-button { +.playing.light.square .rp-play-pause-button { background-image: url("../images/pause-light-square.png?v=2"); } -.radio-container.playing.dark.circular .rp-play-pause-button { +.playing.dark.circular .rp-play-pause-button { background-image: url("../images/pause-dark-circular.png?v=2"); } -.radio-container.playing.dark.rounded .rp-play-pause-button { +.playing.dark.rounded .rp-play-pause-button { background-image: url("../images/pause-dark-rounded.png?v=2"); } -.radio-container.playing.dark.square .rp-play-pause-button { +.playing.dark.square .rp-play-pause-button { background-image: url("../images/pause-dark-square.png?v=2"); } @@ -214,24 +214,24 @@ opacity: 0.99; } -.radio-container .rp-volume-controls button, .radio-container .rp-popup button { +.rp-volume-controls button, .rp-popup button { overflow: hidden; text-indent: -9999px; border: none; cursor: pointer; opacity: 0.9; } -.radio-container .rp-volume-controls button:hover, .radio-container .rp-volume-controls button:focus, -.radio-container .rp-popup button:hover, .radio-container .rp-popup button:focus { +.rp-volume-controls button:hover, .rp-volume-controls button:focus, +.rp-popup button:hover, .rp-popup button:focus { opacity: 0.99; scale: 1.1; } -.radio-container.circular .rp-volume-controls button, .radio-container.circular .rp-popup button { +.circular .rp-volume-controls button, .circular .rp-popup button { border-radius: 9px; } -.radio-container.rounded .rp-volume-controls button, .radio-container.rounded .rp-popup button { +.rounded .rp-volume-controls button, .rounded .rp-popup button { border-radius: 6px; } -.radio-container.square .rp-volume-controls button, .radio-container.square .rp-popup button { +.square .rp-volume-controls button, .square .rp-popup button { border-radius: 1px; } @@ -264,9 +264,8 @@ /* @end */ /* @roup Now Playing Info */ -.rp-now-playing { - font-size: 14px; -} +.rp-now-playing {font-size: 14px; display: none;} +.now-playing .rp-now-playing {display: flex;} .rp-now-playing-title, .rp-now-playing-artist, .rp-now-playing-album { display: inline-block; padding: 0 10px; } diff --git a/player/css/radio-player.min.css b/player/css/radio-player.min.css index 7edd6c9..f8337fb 100644 --- a/player/css/radio-player.min.css +++ b/player/css/radio-player.min.css @@ -1 +1 @@ -.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent}.rp-audio-stream .rp-interface{width:35%;display:inline-block}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{display:none}.rp-audio-stream .rp-station-info,.rp-audio-stream .rp-show-info{width:32%;display:inline-block}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.radio-container.vertical .rp-station-info,.radio-container.vertical .rp-interface,.radio-container.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.radio-container.horizontal .rp-play-pause-button-bg{margin-right:25px}.radio-container.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.radio-container.circular .rp-play-pause-button-bg{border-radius:20px}.radio-container.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.radio-container.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.radio-container.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.radio-container.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.radio-container.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.radio-container.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.radio-container.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.radio-container.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.radio-container.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.radio-container.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.radio-container.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.radio-container.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.radio-container.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.radio-container .rp-volume-controls button,.radio-container .rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.radio-container .rp-volume-controls button:hover,.radio-container .rp-volume-controls button:focus,.radio-container .rp-popup button:hover,.radio-container .rp-popup button:focus{opacity:.99;scale:1.1}.radio-container.circular .rp-volume-controls button,.radio-container.circular .rp-popup button{border-radius:9px}.radio-container.rounded .rp-volume-controls button,.radio-container.rounded .rp-popup button{border-radius:6px}.radio-container.square .rp-volume-controls button,.radio-container.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-script-select{display:none}.rp-now-playing{font-size:14px}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px;border:0}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-show-image-link{text-decoration:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file +.rp-player{background-color:transparent}.rp-player audio{width:0;height:0}.rp-audio button::-moz-focus-inner,.rp-audio-stream button::-moz-focus-inner{border:0}.rp-audio,.rp-audio-stream{font-size:16px;font-family:Verdana,Arial,sans-serif;line-height:1.6;background-color:transparent}.rp-audio *:focus,.rp-audio-stream *:focus{outline:none;box-shadow:none}.rp-interface{position:relative;background-color:transparent}.rp-audio-stream .rp-interface{width:35%;display:inline-block}.rp-station-info,.rp-interface,.rp-volume-controls,.rp-controls-holder,.rp-script-switcher,.rp-show-info{display:inline-block}.rp-station-info,.rp-interface,.rp-show-info{text-align:center;vertical-align:top;margin-top:7px}.rp-station-info,.rp-show-info{display:none}.rp-audio-stream .rp-station-info,.rp-audio-stream .rp-show-info{width:32%;display:inline-block}.rp-station-text-alt,.rp-show-text-alt{display:none}.player-contents.popup{padding:10px}.vertical .rp-station-info,.vertical .rp-interface,.vertical .rp-show-info{display:block;width:100%;vertical-align:top}.rp-controls,.rp-volume-controls,.rp-script-switcher{display:inline-block;vertical-align:middle}.rp-volume-controls button,.rp-volume-slider-container,.rp-volume-bar{display:inline-block;vertical-align:middle}.rp-popup{display:inline-block}.rp-audio .rp-interface,.rp-audio-stream .rp-interface{height:80px;padding-top:5px}.rp-interface .rp-controls{margin:0;padding:0;overflow:hidden}.rp-controls button{display:block;float:left;overflow:hidden;text-indent:-9999px;border:none;cursor:pointer}.horizontal .rp-play-pause-button-bg{margin-right:25px}.vertical .rp-play-pause-button-bg{margin-right:5px}.light .rp-play-pause-button-bg{background-color:rgba(192,192,192,.5)}.dark .rp-play-pause-button-bg{background-color:rgba(64,64,64,.5)}.rp-play-pause-button-bg,.rp-play-pause-button{width:40px;height:40px;background-size:40px 40px}.circular .rp-play-pause-button-bg{border-radius:20px}.rounded .rp-play-pause-button-bg{border-radius:10px}.rp-play-pause-button{opacity:.9;cursor:pointer}.rp-play-pause-button:hover,.rp-play-pause-button:focus{opacity:1;scale:1.1}.light.circular .rp-play-pause-button{background-image:url(../images/play-light-circular.png?v=2)}.light.rounded .rp-play-pause-button{background-image:url(../images/play-light-rounded.png?v=2)}.light.square .rp-play-pause-button{background-image:url(../images/play-light-square.png?v=2)}.dark.circular .rp-play-pause-button{background-image:url(../images/play-dark-circular.png?v=2)}.dark.rounded .rp-play-pause-button{background-image:url(../images/play-dark-rounded.png?v=2)}.dark.square .rp-play-pause-button{background-image:url(../images/play-dark-square.png?v=2)}.playing.light.circular .rp-play-pause-button{background-image:url(../images/pause-light-circular.png?v=2)}.playing.light.rounded .rp-play-pause-button{background-image:url(../images/pause-light-rounded.png?v=2)}.playing.light.square .rp-play-pause-button{background-image:url(../images/pause-light-square.png?v=2)}.playing.dark.circular .rp-play-pause-button{background-image:url(../images/pause-dark-circular.png?v=2)}.playing.dark.rounded .rp-play-pause-button{background-image:url(../images/pause-dark-rounded.png?v=2)}.playing.dark.square .rp-play-pause-button{background-image:url(../images/pause-dark-square.png?v=2)}.rp-volume-slider-container{position:relative;opacity:.85;height:30px}.rp-volume-slider-container:hover,.rp-volume-slider-container:focus{opacity:.99}.rp-volume-controls button,.rp-popup button{overflow:hidden;text-indent:-9999px;border:none;cursor:pointer;opacity:.9}.rp-volume-controls button:hover,.rp-volume-controls button:focus,.rp-popup button:hover,.rp-popup button:focus{opacity:.99;scale:1.1}.circular .rp-volume-controls button,.circular .rp-popup button{border-radius:9px}.rounded .rp-volume-controls button,.rounded .rp-popup button{border-radius:6px}.square .rp-volume-controls button,.square .rp-popup button{border-radius:1px}.rp-volume-controls button.rp-mute,.rp-volume-controls button.rp-mute:hover,.rp-volume-controls button.rp-mute:focus,.rp-volume-controls button.rp-volume-down,.rp-volume-controls button.rp-volume-down:hover,.rp-volume-controls button.rp-volume-down:focus,.rp-volume-controls button.rp-volume-up,.rp-volume-controls button.rp-volume-up:hover,.rp-volume-controls button.rp-volume-up:focus,.rp-volume-controls button.rp-volume-max,.rp-volume-controls button.rp-volume-max:hover,.rp-volume-controls button.rp-volume-max:focus,.rp-popup button.rp-popup-button,.rp-popup button.rp-popup-button:hover,.rp-popup button.rp-popup-button:focus{width:18px;height:18px;padding:0;margin:0;background-size:36px;background-repeat:no-repeat}.light button.rp-mute,.light button.rp-volume-max,.light button.rp-volume-up,.light button.rp-volume-down,.light button.rp-popup-button{background-image:url(../images/volume-controls-light.png?v=3)}.dark button.rp-mute,.dark button.rp-volume-max,.dark button.rp-volume-up,.dark button.rp-volume-down,.dark button.rp-popup-button{background-image:url(../images/volume-controls-dark.png?v=3)}.rp-mute{background-position:0 0}.muted .rp-mute,.rp-mute:hover{background-position:-18px -18px}.rp-volume-max{background-position:0 -36px}.maxed .rp-volume-max,.rp-volume-max:focus,.rp-volume-max:hover{background-position:-18px -36px}.rp-volume-up{background-position:0 -54px}.rp-volume-up:focus,.rp-volume-up:hover{background-position:-18px -54px}.rp-volume-down{background-position:0 -72px}.rp-volume-down:focus,.rp-volume-down:hover{background-position:-18px -72px}.rp-popup-button{background-position:0 -90px}.rp-popup-button:focus,.rp-popup-button:hover{background-position:-18px -90px}.rp-script-select{display:none}.rp-now-playing{font-size:14px;display:none}.now-playing .rp-now-playing{display:flex}.rp-now-playing-title,.rp-now-playing-artist,.rp-now-playing-album{display:inline-block;padding:0 10px}.rp-station-image,.rp-station-text,.rp-show-text,.rp-show-image{display:inline-block;vertical-align:top}.rp-station-info .rp-station-image,.rp-show-info .rp-show-image{width:64px;height:64px;margin:0;padding:0;background-size:100% 100%}.rp-station-info .rp-station-image{margin-right:16px}.rp-station-info .rp-station-image.no-image{width:0;height:0;margin-right:0;display:none}.rp-station-info .rp-station-image.no-image.new-image{width:64px;height:64px;margin-right:16px}.rp-station-info .rp-station-image.new-image img{display:none}.rp-show-info .rp-show-image{margin-left:16px;border:0}.rp-show-info .rp-show-image.no-image{width:0;height:0;margin-left:0;display:none}.rp-show-info .rp-show-image-link{text-decoration:none}.rp-show-info .rp-station-text{max-width:calc(100% - 90px)}.rp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.rp-no-solution a{color:#000}.rp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 3a01a84..6451fdd 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -495,7 +495,7 @@ function radio_player_get_volume(instance) { /* --- set all volume sliders --- */ function radio_player_volume_sliders(volume) { - jQuery('.radio-container').each(function() { + jQuery('.radio-container, .rp-container').each(function() { instance = jQuery(this).attr('id').replace('radio_container_',''); console.log('Set volume on instance '+instance+' to '+volume); radio_player_volume_slider(instance, volume); diff --git a/player/radio-player.php b/player/radio-player.php index 281ec9b..e1b05d2 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -315,6 +315,14 @@ function radio_player_output( $args = array(), $echo = false ) { if ( 0 == count( $args['volumes'] ) ) { $classes[] = 'no-volume-controls'; } + + // 2.5.18: added 24 hour shift time formatting class + if ( function_exists( 'radio_station_get_setting' ) ) { + $time_format = radio_station_get_setting( 'clock_time_format' ); + if ( 24 == (int) $time_format ) { + $classes[] = 'format-24'; + } + } // 2.5.0: added preconnect/dns-prefetch link tags for URL host if ( 'stream' == $args['media'] ) { @@ -341,7 +349,7 @@ function radio_player_output( $args = array(), $echo = false ) { } $class_list = implode( ' ', $classes ); - $html['player_open'] .= '
    ' . "\n"; $html['controls'] .= '
    ' . "\n"; - // 2.5.18: preload the pause icon to prevent display glitch on play + // 2.5.18: preload the pause icon to prevent display delay glitch on play $theme = radio_station_get_setting( 'player_theme' ); $buttons = radio_station_get_setting( 'player_buttons' ); $pause_icon_url = plugins_url( '/player/images/pause-' . $theme . '-' . $buttons . '.png', RADIO_STATION_FILE ); + $pause_icon_url = apply_filters( 'radio_player_pause_preload_url', $pause_icon_url ); $html['controls'] .= '' . "\n"; $html['controls'] .= '' . "\n"; // --- Volume Controls --- + // 2.5.18: respect volumes attribute key $html['volume'] = '
    ' . "\n"; // --- Volume Mute --- - $html['volume'] .= '' . "\n"; + if ( in_array( 'mute', $args['volumes'] ) ) { + $html['volume'] .= '' . "\n"; + } // --- Volume Decrease --- // 2.5.0: fix to attribute typo area-label - $html['volume'] .= '' . "\n"; + if ( in_array( 'updown', $args['volumes'] ) ) { + $html['volume'] .= '' . "\n"; + } // --- Custom Range volume slider --- - $html['volume'] .= '
    ' . "\n"; - $html['volume'] .= '
    ' . "\n"; - $html['volume'] .= '' . "\n"; - $html['volume'] .= '
    ' . "\n"; - $html['volume'] .= '
    ' . "\n"; + if ( in_array( 'slider', $args['volumes'] ) ) { + $html['volume'] .= '
    ' . "\n"; + $html['volume'] .= '
    ' . "\n"; + $html['volume'] .= '' . "\n"; + $html['volume'] .= '
    ' . "\n"; + $html['volume'] .= '
    ' . "\n"; + } // --- Volume Increase --- - $html['volume'] .= '' . "\n"; + if ( in_array( 'updown', $args['volumes'] ) ) { + $html['volume'] .= '' . "\n"; + } // --- Volume Max --- - $html['volume'] .= '' . "\n"; + if ( in_array( 'max', $args['volumes'] ) ) { + $html['volume'] .= '' . "\n"; + } $html['volume'] .= '
    ' . "\n"; // --- dropdown script switcher for testing --- - if ( RADIO_PLAYER_DEBUG ) { + if ( isset( $_REQUEST['script-debug'] ) && ( '1' == sanitize_text_field( wp_unslash( $_REQUEST['script-debug'] ) ) ) ) { $html['switcher'] = '
    ' . "\n"; $html['switcher'] .= '
    *
    '; $html['switcher'] .= '' . "\n"; + $list .= '' . "\n"; + echo '' . "\n"; + + // --- show description --- + // 2.5.18: added show description filter selection + $desc = isset( $_GET['description'] ) ? sanitize_text_field( $_GET['description'] ) : ''; + echo '' . "\n"; + echo '' . "\n"; + + // --- show genres --- + // 2.5.18: added show genre filter selection + $g = isset( $_GET['genre'] ) ? sanitize_text_field( $_GET['genre'] ) : ''; + $genres = get_terms( array( 'taxonomy' => RADIO_STATION_GENRES_SLUG, 'hide_empty' => false ) ); + if ( $genres && ( count( $genres ) > 0 ) ) { + echo '' . "\n"; + echo '' . "\n"; + } + } @@ -5309,20 +5368,23 @@ function radio_station_override_sortable_columns( $columns ) { // Schedule Override Column Styles // ------------------------------- // 2.5.0: renamed from radio_station_override_column_styles for consistency -add_action( 'admin_footer', 'radio_station_override_admin_list_styles' ); +// 2.5.18: moved to admin_head to not miss footer enqueueing +add_action( 'admin_head', 'radio_station_override_admin_list_styles' ); function radio_station_override_admin_list_styles() { $currentscreen = get_current_screen(); if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { return; } + // 2.5.18: added active filter highlighting $css = "#title {min-width: 100px;} #override_times {width: 150px;} #shows_affected {width: 250px;} #description {width: 40px; font-size: 12px;} #taxonomy-" . RADIO_STATION_GENRES_SLUG . ", #taxonomy-" . RADIO_STATION_LANGUAGES_SLUG . " {width: 75px;} #override_image, .override_image {width: 75px;} - .override_image img {width: 100%; height: auto;}"; + .override_image img {width: 100%; height: auto;} + select.active-filter {outline: 1px solid #FF7700;}"; // 2.3.2: set override image column width to override image width // 2.3.3.9: set override times column width @@ -5373,7 +5435,11 @@ function radio_station_override_date_filter( $post_type, $which ) { // --- month override selector --- echo '' . "\n"; - echo '' . "\n"; echo '' . "\n"; if ( count( $months ) > 0 ) { foreach ( $months as $key => $data ) { @@ -5403,7 +5469,11 @@ function radio_station_override_past_future_filter( $post_type, $which ) { // --- past / future override selector --- // 2.3.3.5: added option for today filtering echo '' . "\n"; - echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; @@ -6435,7 +6505,8 @@ function radio_station_playlist_column_data( $column, $post_id ) { // Playlist List Column Styles // --------------------------- // 2.5.0: renamed function from radio_station_playlist_column_styles -add_action( 'admin_footer', 'radio_station_playlist_admin_list_styles' ); +// 2.5.18: moved to admin_head to not miss footer enqueueing +add_action( 'admin_head', 'radio_station_playlist_admin_list_styles' ); function radio_station_playlist_admin_list_styles() { $currentscreen = get_current_screen(); if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { @@ -6443,11 +6514,13 @@ function radio_station_playlist_admin_list_styles() { } // --- playlist list styles --- + // 2.5.18: added active filter highlighting $css = "#show {width: 100px;} #trackcount {width: 35px; font-size: 12px;} #tracklist {width: 250px;} .tracklist-table {width: 350px;} - .tracklist-table td {padding: 0px 10px;}"; + .tracklist-table td {padding: 0px 10px;} + select.active-filter {outline: 1px solid #FF7700;}"; // 2.3.3.9: add playlist list styles filter // 2.5.0: use wp_kses_post on style output @@ -7279,6 +7352,29 @@ function radio_station_posts_list_styles() { // === Extra Functions === // ----------------------- +// ----------------------------------- +// Show Description Query Where Filter +// ----------------------------------- +// 2.5.18: added for filtering empty show description or not +add_filter( 'posts_where', 'radio_station_columns_where_filter', 10, 2 ); +function radio_station_columns_where_filter( $where, $query ) { + + if ( !is_admin() || !$query->is_main_query() || ( RADIO_STATION_SHOW_SLUG !== $query->get( 'post_type' ) ) ) { + return $where; + } + + // --- description filter --- + if ( isset( $_GET['description'] ) ) { + $desc = sanitize_text_field( $_GET['description'] ); + if ( 'empty' == $desc ) { + $where .= " AND (post_content IS NULL OR TRIM(post_content) = '')"; + } elseif ( 'notempty' == $desc ) { + $where .= " AND (post_content IS NOT NULL AND TRIM(post_content) != '')"; + } + } + return $where; +} + // --------------------------- // Post Type List Query Filter // --------------------------- @@ -7354,7 +7450,36 @@ function radio_station_columns_query_filter( $query ) { $query->set( 'orderby', 'meta_value' ); $query->set( 'meta_key', 'show_shift_time' ); $query->set( 'meta_type', 'TIME' ); + } + + // --- Genre Filtering --- + // 2.5.18: added genre filtering + if ( isset( $_GET['genre'] ) ) { + $genre = sanitize_text_field( $_GET['genre'] ); + if ( '' != $genre ) { + if ( 'none' == $genre ) { + $genre_tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'id', + 'operator' => 'NOT EXISTS', + ), + ); + } else { + $genre_tax_query = array( + array( + 'taxonomy' => RADIO_STATION_GENRES_SLUG, + 'field' => 'id', + 'terms' => (int) $genre, + ), + ); + } + + // --- set channel taxonomy query --- + $query->set( 'tax_query', $genre_tax_query ); + } } + } // --- Order Show Overrides by Override Date --- @@ -7513,7 +7638,7 @@ function radio_station_relogin_message() { } // -------------------------- -// Show Select Options output +// Show Select Options Output // -------------------------- function radio_station_show_select_options( $type, $selected, $shows = false ) { diff --git a/loader.php b/loader.php index 6541ac5..1d7bd76 100644 --- a/loader.php +++ b/loader.php @@ -3027,12 +3027,12 @@ public function setting_row( $option ) { } $data = esc_attr( $min ) . "," . esc_attr( $max ) . "," . esc_attr( $step ); $row .= '' . "\n"; - if ( isset( $option['suffix'] ) ) { - $row .= ' ' . $option['suffix']; - } // $onclickup = "plugin_panel_number_step('up', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . "\n"; // $row .= '' . "\n"; $row .= '' . "\n"; + if ( isset( $option['suffix'] ) ) { + $row .= ' ' . $option['suffix']; + } } elseif ( 'image' == $type ) { diff --git a/options.php b/options.php index fc25e29..ff9c2bd 100644 --- a/options.php +++ b/options.php @@ -151,14 +151,15 @@ function radio_station_plugin_options( $admin = false ) { ), // --- Station Band --- - // 2.5.18: addde station band option + // 2.5.18: added station band option 'station_band' => array( 'type' => 'select', 'label' => $admin ? __( 'Frequency Band', 'radio-station' ) : '', 'options' => array( - '' => __( 'n/a', 'radio-station' ), - 'fm' => __( 'FM', 'radio-station' ), - 'am' => __( 'AM', 'radio-station' ), + '' => $admin ? __( 'n/a', 'radio-station' ) : '', + 'fm' => $admin ? __( 'FM', 'radio-station' ) : '', + 'am' => $admin ? __( 'AM', 'radio-station' ) : '', + 'dab' => $admin ? __( 'DAB', 'radio-station' ) : '', ), 'default' => '', 'helper' => $admin ? __( 'Your station frequency band identifier.', 'radio-station' ) : '', @@ -402,10 +403,8 @@ function radio_station_plugin_options( $admin = false ) { 'frequency' => $admin ? __( 'Frequency', 'radio-station' ) : '', 'location' => $admin ? __( 'Location', 'radio-station' ) : '', 'timezone' => $admin ? __( 'Timezone', 'radio-station' ) : '', - // 'phone' => $admin ? __( 'Phone Number', 'radio-station' ) : '', - // 'text' => $admin ? __( 'Text Number', 'radio-station' ) : '', ), - 'default' => array( 'frequency', 'location' ), + 'default' => array( 'frequency' ), 'helper' => $admin ? __( 'Display your Radio Station Meta in Player by default.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'basic', @@ -483,6 +482,7 @@ function radio_station_plugin_options( $admin = false ) { 'type' => 'multicheck', 'label' => $admin ? __( 'Volume Controls', 'radio-station' ) : '', 'default' => array( 'slider' ), + 'suffix' => '%', 'options' => array( 'slider' => $admin ? __( 'Volume Slider', 'radio-station' ) : '', 'updown' => $admin ? __( 'Volume Plus / Minus', 'radio-station' ) : '', @@ -674,6 +674,7 @@ function radio_station_plugin_options( $admin = false ) { 'min' => 0, 'step' => 100, 'max' => 10000, + 'suffix' => 'ms', 'helper' => $admin ? __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', @@ -701,6 +702,7 @@ function radio_station_plugin_options( $admin = false ) { 'min' => 0, 'step' => 100, 'max' => 10000, + 'suffix' => 'ms', 'helper' => $admin ? __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', @@ -716,6 +718,7 @@ function radio_station_plugin_options( $admin = false ) { 'min' => 0, 'step' => 500, 'max' => 20000, + 'suffix' => 'ms', 'helper' => $admin ? __( 'Number of milliseconds to wait for new Page to load before fading in anyway or prompting (if continuous playback is enabled.)', 'radio-station' ) : '', 'tab' => 'player', 'section' => 'bar', @@ -776,6 +779,25 @@ function radio_station_plugin_options( $admin = false ) { 'helper' => $admin ? __( 'Display the Current Show in the Player Bar.', 'radio-station' ) : '', 'pro' => true, ), + + // --- [Pro/Player] Show Meta --- + // 2.5.18: added show meta display option + 'player_bar_showmeta' => array( + 'type' => 'multicheck', + 'label' => $admin ? __( 'Show Meta Display', 'radio-station' ) : '', + 'options' => array( + 'hosts' => $admin ? __( 'Hosts', 'radio-station' ) : '', + 'producers' => $admin ? __( 'Producers', 'radio-station' ) : '', + 'shift' => $admin ? __( 'Shift Time', 'radio-station' ) : '', + // 'remaining' => $admin ? __( 'Remaining Time', 'radio-station' ) : '', + // 'description' => $admin ? __( 'Description', 'radio-station' ) : '', + ), + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => $admin ? __( 'Show meta to display in the Player Bar.', 'radio-station' ) : '', + 'pro' => true, + ), // --- [Pro/Player] Display Metadata --- // 2.4.0.3: added for now playing metadata display diff --git a/player/radio-player.php b/player/radio-player.php index e1b05d2..f974107 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -230,6 +230,7 @@ function radio_player_output( $args = array(), $echo = false ) { // --- settings defaults --- // 2.5.0: added type, metadata and channel args + // 2.5.18: added meta and showmeta display (minimal) defaults $defaults = array( 'media' => 'stream', 'url' => '', @@ -238,6 +239,8 @@ function radio_player_output( $args = array(), $echo = false ) { 'fformat' => '', 'title' => '', 'image' => '', + 'meta' => array( 'frequency' ), + 'showmeta' => array( 'hosts' ), 'script' => 'amplitude', 'layout' => 'vertical', 'theme' => 'light', @@ -249,7 +252,8 @@ function radio_player_output( $args = array(), $echo = false ) { // --- ensure all arguments are set --- foreach ( $defaults as $key => $value ) { - if ( !isset( $args[$key] ) ) { + // 2.5.18: allow setting of value to default + if ( !isset( $args[$key] ) || ( 'default' == $args[$key] ) ) { $args[$key] = $value; } } @@ -307,10 +311,7 @@ function radio_player_output( $args = array(), $echo = false ) { $html['player_open'] = '
    ' . "\n"; // --- set Player container --- - $classes = array( 'radio-container', 'rp-audio' ); - $classes[] = $args['layout']; - $classes[] = $args['theme']; - $classes[] = $args['buttons']; + $classes = array( 'radio-container', 'rp-audio', $args['layout'], $args['theme'], $args['buttons'] ); // 2.5.0: maybe add no volume controls class if ( 0 == count( $args['volumes'] ) ) { $classes[] = 'no-volume-controls'; @@ -389,39 +390,74 @@ function radio_player_output( $args = array(), $echo = false ) { $html['station'] .= $image . '
    ' . "\n"; // --- station text display --- - $html['station'] .= '
    ' . "\n"; - - // --- station title --- - $title_display = ''; - if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { - $title_display .= esc_html( $args['title'] ); + $station_meta = array(); + + // --- station title --- + $title_display = ''; + if ( ( '0' != (string)$args['title'] ) && ( 0 !== $args['title'] ) && ( '' != $args['title'] ) ) { + $title_display .= esc_html( $args['title'] ); + } + $title_display = apply_filters( 'radio_player_station_display', $title_display, $args, $instance ); + $station_meta['title'] = '
    ' . "\n"; + + // --- station tagline --- + // 2.5.18: add tagline display + $tagline_display = isset( $args['tagline'] ) ? $args['tagline'] : ''; + $tagline_display = apply_filters( 'radio_player_tagline_display', $tagline_display, $args, $instance ); + $station_meta['tagline'] = '
    ' . "\n"; + + // --- station timezone / location / frequency --- + // 2.5.0: add filters for timezone / frequency / location display + $frequency_display = isset( $args['frequency'] ) ? $args['frequency'] : ''; + $frequency_display = apply_filters( 'radio_player_frequency_display', $frequency_display, $args, $instance ); + $station_meta['frequency'] = '
    ' . "\n"; + + // --- station location --- + // 2.5.0: fix to mismatched location variable and class + $location_display = isset( $args['location'] ) ? $args['location'] : ''; + $location_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); + $station_meta['location'] = '
    ' . "\n"; + + // --- station timezone --- + $timezone_display = isset( $args['timezone'] ) ? $args['timezone'] : ''; + $timezone_display = apply_filters( 'radio_player_timezone_display', $timezone_display, $args, $instance ); + $station_meta['timezone'] = '
    ' . "\n"; + + // 2.5.18: add filtering of station meta and order + $station_meta_order = array( 'title', 'tagline', 'frequency', 'location', 'timezone' ); + $station_meta_order = apply_filters( 'radio_player_station_meta_order', $station_meta_order, $args, $instance ); + $station_meta = apply_filters( 'radio_player_station_meta', $station_meta, $args, $instance ); + $station_text_html = '
    ' . "\n"; + if ( count( $station_meta_order ) > 0 ) { + foreach ( $station_meta_order as $key ) { + if ( isset( $station_meta[$key] ) ) { + $station_text_html .= $station_meta[$key]; + } } - $title_display = apply_filters( 'radio_player_station_display', $title_display, $args, $instance ); - $station_text_html = '
    ' . wp_kses( $title_display, $allowed ) . '
    ' . "\n"; - - // 2.5.18: add tagline display - $tagline_display = isset( $args['tagline'] ) ? $args['tagline'] : ''; - $tagline_display = apply_filters( 'radio_player_tagline_display', $tagline_display, $args, $instance ); - $station_text_html .= '
    ' . wp_kses( $tagline_display, $allowed ) . '
    ' . "\n"; - - // --- station timezone / location / frequency --- - // 2.5.0: add filters for timezone / frequency / location display - $frequency_display = isset( $args['frequency'] ) ? $args['frequency'] : ''; - $frequency_display = apply_filters( 'radio_player_frequency_display', $frequency_display, $args, $instance ); - $station_text_html .= '
    ' . wp_kses( $frequency_display, $allowed ) . '
    ' . "\n"; - - // 2.5.0: fix to mismatched location variable and class - $location_display = isset( $args['location'] ) ? $args['location'] : ''; - $location_display = apply_filters( 'radio_player_location_display', $location_display, $args, $instance ); - $station_text_html .= '
    ' . wp_kses( $location_display, $allowed ) . '
    ' . "\n"; - - $timezone_display = isset( $args['timezone'] ) ? $args['timezone'] : ''; - $timezone_display = apply_filters( 'radio_player_timezone_display', $timezone_display, $args, $instance ); - $station_text_html .= '
    ' . wp_kses( $timezone_display, $allowed ) . '
    ' . "\n"; - - $html['station'] .= $station_text_html; - - $html['station'] .= '
    ' . "\n"; + } + $station_text_html .= '
    ' . "\n"; + + $html['station'] .= $station_text_html; $html['station'] .= '
    ' . "\n"; @@ -439,7 +475,7 @@ function radio_player_output( $args = array(), $echo = false ) { $buttons = radio_station_get_setting( 'player_buttons' ); $pause_icon_url = plugins_url( '/player/images/pause-' . $theme . '-' . $buttons . '.png', RADIO_STATION_FILE ); $pause_icon_url = apply_filters( 'radio_player_pause_preload_url', $pause_icon_url ); - $html['controls'] .= '' . "\n"; + $html['controls'] .= '' . "\n"; $html['controls'] .= '
    ' . "\n"; @@ -497,23 +533,72 @@ function radio_player_output( $args = array(), $echo = false ) { } // --- Current Show Texts --- - // TODO: add other show info divs ( with expander ) ? + // 2.5.18: set display according to showmeta attribute + $show_meta = array(); + + // --- show title --- + $show_meta['title'] = '
    ' . "\n"; + + // --- show hosts --- + $show_meta['hosts'] = '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; + if ( count( $show_meta_order ) > 0 ) { + foreach ( $show_meta_order as $key ) { + if ( isset( $show_meta[$key] ) ) { + $show_text_html .= $show_meta[$key]; + } + } + } $show_text_html .= '
    ' . "\n"; - // 2.5.18: make show logo clickable - $show_text_html .= '' . "\n"; - $show_text_html .= '
    ' . "\n"; - $show_text_html .= '
    ' . "\n"; - $html['show'] = '
    ' . "\n"; + $html['show'] .= $show_text_html; + + // --- show logo (placeholder) --- + // 2.5.18: make show logo clickable + $html['show'] .= '' . "\n"; + $html['show'] .= '
    ' . "\n"; + $html['show'] .= '
    ' . "\n"; + $html['show'] .= '
    ' . "\n"; // --- Playback Progress Bar --- @@ -531,6 +616,7 @@ function radio_player_output( $args = array(), $echo = false ) { $html['progress'] .= '
    ' . "\n"; */ // --- no solution section --- + // deprecated: along with flash player fallback // $html['no-solution'] = '
    ' . "\n"; // $html['no-solution'] .= '' . esc_html( __( 'Update Required', 'radio-station' ) ) . '' . "\n"; // $html['no-solution'] .= 'To play the media you will need to either update your browser to a recent version or update your Flash plugin.' . "\n"; @@ -584,13 +670,15 @@ function radio_player_output( $args = array(), $echo = false ) { // --- set alternative text sections --- // 2.4.0.2: added for alternative display methods // 2.4.0.3: added missing function_exists wrappers - $station_text_alt = '
    ' . $station_text_html . '
    ' . "\n"; + // 2.6.16: just replace class and leave contents + $station_text_alt = str_replace( 'rp-station-text', 'rp-station-text-alt', $station_text_html ); if ( function_exists( 'apply_filters' ) ) { $station_text_alt = apply_filters( 'radio_station_player_station_text_alt', $station_text_alt, $args, $instance ); $station_text_alt = apply_filters( 'radio_player_station_text_alt', $station_text_alt, $args, $instance ); } // 2.5.18: fix class from rp-station-text-alt - $show_text_alt = '
    ' . $show_text_html . '
    ' . "\n"; + // 2.6.16: just replace class and leave contents + $show_text_alt = str_replace( 'rp-show-text', 'rp-show-text-alt', $show_text_html ); if ( function_exists( 'apply_filters' ) ) { $show_text_alt = apply_filters( 'radio_station_player_show_text_alt', $show_text_alt, $args, $instance ); $show_text_alt = apply_filters( 'radio_player_show_text_alt', $show_text_alt, $args, $instance ); @@ -756,11 +844,7 @@ function radio_player_shortcode( $atts, $echo = false ) { // --- set base defaults --- $title = $image = $image_url = ''; - $meta = array(); - $script = 'amplitude'; - $layout = 'horizontal'; - $theme = 'light'; - $buttons = 'rounded'; + $meta = $showmeta = $script = $layout = $theme = $buttons = 'default'; $volume = 77; // --- set default player title --- @@ -769,7 +853,8 @@ function radio_player_shortcode( $atts, $echo = false ) { } elseif ( function_exists( 'radio_station_get_setting' ) ) { $title = radio_station_get_setting( 'player_title' ); $title = ( 'yes' == $title ) ? '' : 0; - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $title = apply_filters( 'radio_station_player_default_title_display', $title ); $title = apply_filters( 'radio_player_default_title_display', $title ); } @@ -780,7 +865,8 @@ function radio_player_shortcode( $atts, $echo = false ) { } elseif ( function_exists( 'radio_station_get_setting' ) ) { $image = radio_station_get_setting( 'player_image' ); $image = ( 'yes' == $image ) ? 1 : 0; - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $image = apply_filters( 'radio_station_player_default_image_display', $image ); $image = apply_filters( 'radio_player_default_image_display', $image ); } @@ -793,11 +879,24 @@ function radio_player_shortcode( $atts, $echo = false ) { } } elseif ( function_exists( 'radio_station_get_setting' ) ) { $meta = radio_station_get_setting( 'player_meta' ); - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $meta = apply_filters( 'radio_station_player_default_meta_display', $meta ); $meta = apply_filters( 'radio_player_default_meta_display', $meta ); } + // 2.5.18: added show meta display options + if ( defined( 'RADIO_PLAYER_SHOWMETA_DISPLAY' ) ) { + $showmeta = RADIO_PLAYER_SHOWMETA_DISPLAY; + if ( is_string( $showmeta ) ) { + $showmeta = explode( ',', $showmeta ); + } + } + if ( function_exists( 'apply_filters' ) ) { + $showmeta = apply_filters( 'radio_station_player_default_showmeta_display', $showmeta ); + $showmeta = apply_filters( 'radio_player_default_showmeta_display', $showmeta ); + } + // --- set default player script --- // 2.5.7: disable howler script for default usage // 2.5.18: added audio5 test support @@ -806,7 +905,8 @@ function radio_player_shortcode( $atts, $echo = false ) { $script = RADIO_PLAYER_SCRIPT; } elseif ( function_exists( 'radio_station_get_setting' ) ) { $script = radio_station_get_setting( 'player_script' ); - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $script = apply_filters( 'radio_station_player_default_script', $script ); $script = apply_filters( 'radio_player_default_script', $script ); } @@ -818,7 +918,8 @@ function radio_player_shortcode( $atts, $echo = false ) { // --- set default player layout --- if ( defined( 'RADIO_PLAYER_LAYOUT' ) ) { $layout = RADIO_PLAYER_LAYOUT; - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $layout = apply_filters( 'radio_station_player_default_layout', $layout ); $layout = apply_filters( 'radio_player_default_layout', $layout ); } @@ -843,13 +944,10 @@ function radio_player_shortcode( $atts, $echo = false ) { } elseif ( function_exists( 'radio_station_get_setting' ) ) { $volume_controls = radio_station_get_setting( 'player_volumes' ); if ( !is_array( $volume_controls ) ) { - $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); + $volume_controls = 'default'; } } // 2.5.6: move isset check outside of function_exists - if ( !isset( $volume_controls ) ) { - $volume_controls = array( 'slider', 'updown', 'mute', 'max' ); - } if ( function_exists( 'apply_filters' ) ) { $volume_controls = apply_filters( 'radio_station_player_volumes_display', $volume_controls ); $volume_controls = apply_filters( 'radio_player_volumes_display', $volume_controls ); @@ -860,7 +958,8 @@ function radio_player_shortcode( $atts, $echo = false ) { $theme = RADIO_PLAYER_THEME; } elseif ( function_exists( 'radio_station_get_setting' ) ) { $theme = radio_station_get_setting( 'player_theme' ); - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $theme = apply_filters( 'radio_station_player_default_theme', $theme ); $theme = apply_filters( 'radio_player_default_theme', $theme ); } @@ -870,7 +969,8 @@ function radio_player_shortcode( $atts, $echo = false ) { $buttons = RADIO_PLAYER_BUTTONS; } elseif ( function_exists( 'radio_station_get_setting' ) ) { $buttons = radio_station_get_setting( 'player_buttons' ); - } elseif ( function_exists( 'apply_filters' ) ) { + } + if ( function_exists( 'apply_filters' ) ) { $buttons = apply_filters( 'radio_station_player_default_buttons', $buttons ); $buttons = apply_filters( 'radio_player_default_buttons', $buttons ); } @@ -888,6 +988,7 @@ function radio_player_shortcode( $atts, $echo = false ) { 'title' => $title, 'image' => $image, 'meta' => $meta, + 'showmeta' => $showmeta, // --- player options --- 'script' => $script, 'layout' => $layout, @@ -1310,8 +1411,10 @@ function radio_player_add_inline_style( $css ) { wp_add_inline_style( 'stream-player-footer', $css ); // 2.5.18: enqueue late to allow for in-between additions - if ( !has_action( 'wp_footer', 'radio_player_enqueue_footer_styles', 9 ) ) { - add_action( 'wp_footer', 'radio_player_enqueue_footer_styles', 9 ); + // 2.5.18: allow for admin footer position + $hook = is_admin() ? 'admin_footer' : 'wp_footer'; + if ( !has_action( $hook, 'radio_player_enqueue_footer_styles', 9 ) ) { + add_action( $hook, 'radio_player_enqueue_footer_styles', 9 ); } } } @@ -1335,7 +1438,8 @@ function radio_player_sanitize_shortcode_values() { // 2.5.0: added alternative text attribute // 2.5.0: added block attribute // 2.5.18: added audio5 script option - // 2.5.18: added ptheme key for conflict fix + // 2.5.18: added ptheme key for theme conflict fix + // TODO: handle meta and showmeta keys ? $keys = array( 'url' => 'url', 'title' => 'text', @@ -1820,8 +1924,10 @@ function radio_player_add_inline_script( $js, $position = 'after' ) { wp_add_inline_script( 'radio-player-footer', $js, $position ); // 2.5.18: enqueue inline scripts in footer - if ( !has_action( 'wp_footer', 'radio_player_enqueue_footer_scripts', 9 ) ) { - add_action( 'wp_footer', 'radio_player_enqueue_footer_scripts', 9 ); + // 2.5.18: allow for admin hook + $hook = is_admin() ? 'admin_footer' : 'wp_footer'; + if ( !has_action( $hook, 'radio_player_enqueue_footer_scripts', 9 ) ) { + add_action( $hook, 'radio_player_enqueue_footer_scripts', 9 ); } } } diff --git a/radio-station-admin.php b/radio-station-admin.php index 2d52427..3605c8f 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -14,7 +14,6 @@ // === Admin Menu === // - Settings Page Capability Check // - Add Admin Menu and Submenu Items -// - Content Dashboard Admin Page // - Fix to Expand Main Menu for Submenu Items // - Taxonomy Submenu Item Fix // - Fix to Redirect Plugin Settings Menu Link @@ -240,7 +239,9 @@ function radio_station_add_admin_menus() { // 2.3.0: remove add playlist and add override to reduce clutter // 2.3.0: added actions for adding of other plugin submenu items in position // 2.5.0: disabled Add Show submenu item to reduce clutter + // 2.5.18: added dashboard item and do_actions do_action( 'radio_station_admin_submenu_content_top' ); + add_submenu_page( 'radio-content', __( 'Dashboard', 'radio-station' ), __( 'Dashboard', 'radio-station' ), 'publish_playlists', 'radio-content', 'radio_station_content_page' ); add_submenu_page( 'radio-content', $rs . ' ' . __( 'Shows', 'radio-station' ), __( 'Shows', 'radio-station' ), 'edit_shows', 'shows' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Add Show', 'radio-station' ), __( 'Add Show', 'radio-station' ), 'publish_shows', 'add-show' ); // 2.5.18: change label from Schedule Overrides diff --git a/radio-station.php b/radio-station.php index a5d6aa6..17cd9cc 100644 --- a/radio-station.php +++ b/radio-station.php @@ -686,16 +686,18 @@ function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { // 2.5.10: fix slug from rp-footer to rs-footer // 2.5.10: change from register_script to enqueue_script // 2.5.18: register here but enqueue later - if ( !wp_script_is( 'radio-station-footer', 'registered' ) ) { + if ( !wp_script_is( RADIO_STATION_SLUG . '-footer', 'registered' ) ) { $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; - wp_register_script( 'radio-station-footer', null, array( 'jquery' ), $version, true ); - // wp_enqueue_script( 'radio-station-footer' ); + wp_register_script( RADIO_STATION_SLUG . '-footer', null, array( 'jquery' ), $version, true ); + // wp_enqueue_script( RADIO_STATION_SLUG . '-footer' ); } - wp_add_inline_script( 'radio-station-footer', $js, $position ); + wp_add_inline_script( RADIO_STATION_SLUG . '-footer', $js, $position ); // 2.5.18: enqueue in footer - if ( !has_action( 'wp_footer', 'radio_station_enqueue_footer_scripts', 9 ) ) { - add_action( 'wp_footer', 'radio_station_enqueue_footer_scripts', 9 ); + // 2.5.18: allow for admin footer hook + $hook = is_admin() ? 'admin_footer' : 'wp_footer'; + if ( !has_action( $hook, 'radio_station_enqueue_footer_scripts', 9 ) ) { + add_action( $hook, 'radio_station_enqueue_footer_scripts', 9 ); } } @@ -708,7 +710,7 @@ function radio_station_add_inline_script( $handle, $js, $position = 'after' ) { // 2.5.7: deprecated print script in favour of adding inline to dummy script // 2.5.18: repurpose to enqueue footer scripts function radio_station_enqueue_footer_scripts() { - wp_enqueue_script( 'radio-station-footer' ); + wp_enqueue_script( RADIO_STATION_SLUG . '-footer' ); } // -------------------------- @@ -783,15 +785,17 @@ function radio_station_add_inline_style( $handle, $css ) { } else { // 2.5.18: register inline style but enqueue later - if ( !wp_style_is( 'radio-station-footer', 'registered' ) ) { + if ( !wp_style_is( RADIO_STATION_SLUG . '-footer', 'registered' ) ) { $version = function_exists( 'radio_station_plugin_version' ) ? radio_station_plugin_version() : '2.5.0'; - wp_register_style( 'radio-station-footer', null, array(), $version, 'all' ); - // wp_enqueue_style( 'radio-station-footer' ); + wp_register_style( RADIO_STATION_SLUG . '-footer', null, array(), $version, 'all' ); + // wp_enqueue_style( RADIO_STATION_SLUG . '-footer' ); } - wp_add_inline_style( 'radio-station-footer', $css ); - - if ( !has_action( 'wp_footer', 'radio_station_enqueue_footer_styles', 9 ) ) { - add_action( 'wp_footer', 'radio_station_enqueue_footer_styles', 9 ); + wp_add_inline_style( RADIO_STATION_SLUG . '-footer', $css ); + + // 2.5.18: allow for admin footer position + $hook = is_admin() ? 'admin_footer' : 'wp_footer'; + if ( !has_action( $hook, 'radio_station_enqueue_footer_styles', 9 ) ) { + add_action( $hook, 'radio_station_enqueue_footer_styles', 9 ); } } } @@ -802,7 +806,7 @@ function radio_station_add_inline_style( $handle, $css ) { // 2.5.0: added print in footer for missed inline styles // 2.5.18: repurpose to enqueue footer Styles function radio_station_enqueue_footer_styles() { - wp_enqueue_style( 'radio-station-footer' ); + wp_enqueue_style( RADIO_STATION_SLUG . '-footer' ); } // ------------------------- @@ -1016,11 +1020,13 @@ function radio_station_localization_script() { // --- set countdown labels --- // 2.5.0: moved here from countdown enqueue function + // 2.5.18: added with label for host metadata display $js .= "radio.labels.showstarted = '" . esc_js( __( 'This Show has started.', 'radio-station' ) ) . "';" . "\n"; $js .= "radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "';" . "\n"; $js .= "radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station' ) ) . "';" . "\n"; $js .= "radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "';" . "\n"; $js .= "radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "';" . "\n"; + $js .= "radio.labels.showwith = '" . esc_js( __( 'with', 'radio-station' ) ) . "';" . "\n"; // --- translated time unit strings --- $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; diff --git a/readme.txt b/readme.txt index 0c63f8f..11ea464 100644 --- a/readme.txt +++ b/readme.txt @@ -422,6 +422,7 @@ We recommend you test these on a Staging site (or a development copy of your liv * Added: Special Overrides data routes/feed to Data API * Added: Link main language term to filtered show archive * Added: Channel input support for Shifts and Overrides +* Added: Genre and Description filters for admin Show list * Fixed: Player Amplitude script pause/unpause source bug * Fixed: Special/Override (non-linked) Host/Producer data * Fixed: Radio Player (other developer) plugin conflicts From 250245f9643d28e69dd822d9ccb083441c3c34ff Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 24 Mar 2026 13:10:57 +1000 Subject: [PATCH 373/377] dev updates 2.5.18.1 --- includes/onboarding.php | 1603 ++++++++++++++++++++------------------- options.php | 5 +- player/radio-player.php | 17 +- radio-station.php | 2 +- readme.md | 2 +- readme.txt | 2 +- 6 files changed, 852 insertions(+), 779 deletions(-) diff --git a/includes/onboarding.php b/includes/onboarding.php index f8e1b7d..1ff153d 100644 --- a/includes/onboarding.php +++ b/includes/onboarding.php @@ -100,7 +100,7 @@ function radio_station_quicklinks_panel() { } else { $account_url = add_query_arg( 'page', 'radio-station-account', admin_url( 'admin.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Account', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; } @@ -113,35 +113,35 @@ function radio_station_quicklinks_panel() { // --- shows --- $shows_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Shows', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- specials --- $specials_url = add_query_arg( 'post_type', RADIO_STATION_OVERRIDE_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Specials', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- playlists --- $playlist_url = add_query_arg( 'post_type', RADIO_STATION_PLAYLIST_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Playlists', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- genres --- $genres_url = add_query_arg( 'taxonomy', RADIO_STATION_GENRES_SLUG, admin_url( 'edit-tags.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Genres', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- languages --- $languages_url = add_query_arg( 'taxonomy', RADIO_STATION_LANGUAGES_SLUG, admin_url( 'edit-tags.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Languages', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; @@ -157,28 +157,28 @@ function radio_station_quicklinks_panel() { // TODO: link to schedule editor page $schedule_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Schedule Editor', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- episodes --- $episodes_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Episodes', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- hosts --- $hosts_url = add_query_arg( 'post_type', RADIO_STATION_HOST_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Hosts', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; // --- producers --- $producers_url = add_query_arg( 'post_type', RADIO_STATION_PRODUCER_SLUG, admin_url( 'edit.php' ) ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '' . esc_html( __( 'Producers', 'radio-station' ) ) . '' . "\n"; echo '
  • ' . "\n"; @@ -343,467 +343,498 @@ function radio_station_statistics_panel() { // --- get episode counts --- $episode_count = $episode_show_count = $episode_playlist_count = 0; $episode_show_ids = $episode_playlist_ids = array(); - $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; - $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_EPISODE_SLUG ), ARRAY_A ); - if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { - foreach ( $results as $result ) { - $episode_show_id = get_post_meta( $result['ID'], 'episode_show_id', true ); - if ( !in_array( $episode_show_id, $episode_show_ids ) ) { - $episode_show_ids[] = $episode_show_id; - } - $episode_playlist_id = get_post_meta( $result['ID'], 'episode_playlist', true ); - if ( $episode_playlist_id && !in_array( $episode_playlist_id, $episode_playlist_ids ) ) { - $episode_playlist_ids[] = $episode_playlist_id; + if ( defined( 'RADIO_STATION_EPISODE_SLUG' ) ) { + $query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = %s"; + $results = $wpdb->get_results( $wpdb->prepare( $query, RADIO_STATION_EPISODE_SLUG ), ARRAY_A ); + if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { + $episode_count = count( $results ); + foreach ( $results as $result ) { + $episode_show_id = get_post_meta( $result['ID'], 'episode_show_id', true ); + if ( !in_array( $episode_show_id, $episode_show_ids ) ) { + $episode_show_ids[] = $episode_show_id; + } + $episode_playlist_id = get_post_meta( $result['ID'], 'episode_playlist', true ); + if ( $episode_playlist_id && !in_array( $episode_playlist_id, $episode_playlist_ids ) ) { + $episode_playlist_ids[] = $episode_playlist_id; + } } } + $episode_show_count = count( $episode_show_ids ); + $episode_playlist_count = count( $episode_playlist_ids ); } - $episode_show_count = count( $episode_show_ids ); - $episode_playlist_count = count( $episode_playlist_ids ); - - // --- Schedule heading --- - echo '
    ' . esc_html( 'Station Schedule', 'radio-station' ) . '
    ' . "\n"; - - // --- Schedule percentage line --- - $schedule_percent = $total_times = 0; - $schedule = radio_station_get_current_schedule(); - if ( $schedule && is_array( $schedule ) && ( count( $schedule ) > 0 ) ) { - // print_r( $schedule ); - foreach ( $schedule as $day => $shifts ) { - foreach ( $shifts as $time => $shift ) { - $start_time = strtotime( $shift['date'] . ' ' . $shift['start'] ); - $end_time = strtotime( $shift['date'] . ' ' . $shift['end'] ); - if ( $start_time > $end_time ) { - $end_time = $end_time + 86400; + + // --- open table --- + echo '
    ' . "\n"; echo '' . "\n"; @@ -1179,7 +1242,7 @@ function radio_station_enqueue_onboarding_styles() { // echo 'Current Screen' . print_r( $current_screen, true ) . ''; // --- add onboarding styles --- - if ( ( 'dashboard' == $current_screen->base ) + if ( ( !is_null( $current_screen ) && ( 'dashboard' == $current_screen->base ) ) || ( isset( $_REQUEST['page'] ) && ( 'radio-content' == $_REQUEST['page'] ) ) || ( isset( $_REQUEST['page'] ) && ( 'radio-station' == $_REQUEST['page'] ) ) ) { radio_station_enqueue_style( 'admin' ); diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 5ce0a2c..f18c6a0 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -998,12 +998,13 @@ function radio_station_shift_edit_script() { // --- save shifts via AJAX --- // 2.3.2: added form input cloning to saving show shifts + // 2.5.18: added form style to display none $ajaxurl = admin_url( 'admin-ajax.php' ); $js .= "function radio_shifts_save() { action = 'radio_station_show_save_shifts'; if (jQuery('#single-shift').length && jQuery('#single-shift').prop('checked')) {action = 'radio_station_show_save_shift';} jQuery('#shift-save-form, #shift-save-frame').remove(); - form = '
    '; + form = ''; form += '
    '; jQuery('#wpbody').append(form); if (!jQuery('#shift-save-frame').length) { @@ -4092,10 +4093,11 @@ function radio_station_override_edit_script() { // --- save shifts via AJAX --- // 2.3.2: added form input cloning to saving show shifts + // 2.5.18: added form style to display none $ajaxurl = admin_url( 'admin-ajax.php' ); $js .= "function radio_overrides_save() { jQuery('#override-save-form, #override-save-frame').remove(); - form = '
    '; + form = ''; form += '
    '; jQuery('#wpbody').append(form); if (!jQuery('#override-save-frame').length) { diff --git a/includes/schedules.php b/includes/schedules.php index 136640d..74f2c48 100644 --- a/includes/schedules.php +++ b/includes/schedules.php @@ -638,30 +638,42 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = // ------------------------- // (checks all existing show shifts for schedule) // 2.3.0: added show shift conflict checker -function radio_station_check_shifts( $all_shifts ) { +// 2.5.18: rewrite unusued conflict checker function +function radio_station_check_shifts() { global $radio_station_data, $rs_se; $channel = $rs_se->channel = $radio_station_data['channel']; - // --- get times --- - // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts - // 2.5.0: set times array for conflict checking - $now = radio_station_get_now(); - $timezone = radio_station_get_timezone(); - $today = radio_station_get_time( 'l', $now, $timezone ); - $weekdays = radio_station_get_schedule_weekdays( $today, $now, $timezone ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now, $timezone ); - $times = array( - 'now' => $now, - 'today' => $today, - 'weekdays' => $weekdays, - 'weekdates' => $weekdates, - 'timezone' => $timezone, - ); + // --- get all show data --- + $shows = radio_station_get_shows(); + + $records = array(); + if ( count( $shows ) > 0 ) { + foreach ( $shows as $show ) { + + // 2.5.0: get metadata early (maybe via cached) + if ( isset( $radio_station_data['show-' . $show->ID] ) ) { + $metadata = $radio_station_data['show-' . $show->ID]; + } else { + $metadata = radio_station_get_show_data_meta( $show ); + $radio_station_data['show-' . $show->ID] = $metadata; + } + + $shifts = $metadata['schedule']; + unset( $metadata['schedule'] ); + $records[] = array( + 'ID' => $show->ID, + 'updated' => $show->post_modified_gmt, + 'shifts' => $shifts, + 'show' => $metadata, + ); + } + } - // --- check for shift conflicts --- - $checked_shifts = $rs_se->check_shifts( $all_shifts, $times ); - return $checked_shifts; + // 2.5.18: return any conflicts found instead of shifts + $checked_shifts = $rs_se->check_all_shifts( $records ); + $conflicts = $rs_se->conflicts; + return $conflicts; } // ------------------ diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 292272f..8819ea7 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -41,29 +41,13 @@ // === Time Shortcodes === // ----------------------- -// ------------------------ -// Radio Timezone Shortcode -// ------------------------ -add_shortcode( 'radio-timezone', 'radio_station_timezone_shortcode' ); -function radio_station_timezone_shortcode( $atts = array() ) { - - global $radio_station_data; - - // 2.5.0: added shortcode_atts call for filtering - $defaults = array(); - $atts = shortcode_atts( $defaults, $atts, 'radio-timezone' ); - - // --- set shortcode instance --- - // 2.5.0: simplified instance data - if ( !isset( $radio_station_data['instances']['timezone_shortcode'] ) ) { - $radio_station_data['instances']['timezone_shortcode'] = 0; - } - $radio_station_data['instances']['timezone_shortcode']++; - $instance = $radio_station_data['instances']['timezone_shortcode']; +// -------------------- +// Get Timezone Display +// -------------------- +// 2.5.18: added for use by both clock and timezone shortcodes +function radio_station_get_timezone_display( $timezone, $atts ) { // --- get radio timezone values --- - // $timezone = radio_station_get_setting( 'timezone_location' ); - $timezone = radio_station_get_timezone(); if ( !$timezone || ( '' == $timezone ) ) { // --- fallback to WordPress timezone --- $timezone = get_option( 'timezone_string' ); @@ -116,35 +100,96 @@ function radio_station_timezone_shortcode( $atts = array() ) { } $code = radio_station_get_timezone_code( $timezone ); // 2.3.2: display full timezone location as well - $location = str_replace( '/', ', ', $timezone ); - $location = str_replace( '_', ' ', $location ); - $timezone_display = $code . ' (' . $location . ') ' . $utc_offset; + // $location = str_replace( '/', ', ', $timezone ); + $loc = explode( '/', $timezone ); + $region = str_replace( '_', ' ', $loc[0] ); + $location = str_replace( '_', ' ', $loc[1] ); + + // 2.5.18: display based on specified attributes + $timezone_display = ''; + if ( $atts['code'] ) { + $timezone_display .= $code . ' '; + } + if ( $atts['region'] || $atts['location'] ) { + if ( $atts['code'] ) { + $timezone_display .= '('; + } + if ( $atts['location'] ) { + $timezone_display .= $location; + } + if ( $atts['region'] && $atts['location'] ) { + $timezone_display .= ', '; + } + if ( $atts['region'] ) { + $timezone_display .= $region; + } + if ( $atts['code'] ) { + $timezone_display .= ')'; + } + } + if ( $atts['offset'] ) { + $timezone_display .= ' ' . $utc_offset; + } } + + return $timezone_display; +} + +// ------------------------ +// Radio Timezone Shortcode +// ------------------------ +add_shortcode( 'radio-timezone', 'radio_station_timezone_shortcode' ); +function radio_station_timezone_shortcode( $atts = array() ) { + + global $radio_station_data; + + // 2.5.0: added shortcode_atts call for filtering + $defaults = array(); + $atts = shortcode_atts( $defaults, $atts, 'radio-timezone' ); + + // --- set shortcode instance --- + // 2.5.0: simplified instance data + if ( !isset( $radio_station_data['instances']['timezone_shortcode'] ) ) { + $radio_station_data['instances']['timezone_shortcode'] = 0; + } + $radio_station_data['instances']['timezone_shortcode']++; + $instance = $radio_station_data['instances']['timezone_shortcode']; + + // 2.5.18: allow for timezone attribute override + $timezone = radio_station_get_timezone(); + $defaults = array( + 'timezone' => $timezone, + 'code' => 1, + 'region' => 1, + 'location' => 1, + 'offset' => 1, + ); + $atts = shortcode_atts( $defaults, $atts, 'radio-timezone' ); + $timezone = $atts['timezone']; + + // --- set timezone display and format --- + // 2.5.18: added for display consistency + $timezone_display = radio_station_get_timezone_display( $timezone, $atts ); + $timezone_format = (string) $atts['code'] . '-' . (string) $atts['region'] . '-' . (string) $atts['location'] . '-' . (string) $atts['offset']; // --- set shortcode output --- // 2.5.0: added instance ID to timezone div wrapper $output = '
    ' . "\n"; - // --- radio timezone --- - $output .= '
    ' . "\n"; - $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ) . "\n"; - $output .= ':
    ' . "\n"; - $output .= '
    ' . "\n"; - $output .= esc_html( $timezone_display ) . "\n"; - $output .= '

    ' . "\n"; - - // --- user timezone --- - // 2.3.3.9: change span elements to divs - $output .= '
    ' . "\n"; - $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ) . "\n"; - $output .= ':
    ' . "\n"; - $output .= '
    ' . "\n"; - - // 2.3.2 allow for timezone selector test - // $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); - // if ( '' != $select ) { - // $output .= $select; - // } + // --- radio timezone --- + $output .= '
    ' . "\n"; + $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ) . "\n"; + $output .= ':
    ' . "\n"; + $output .= '
    ' . "\n"; + $output .= esc_html( $timezone_display ) . "\n"; + $output .= '

    ' . "\n"; + + // --- user timezone --- + // 2.3.3.9: change span elements to divs + $output .= '
    ' . "\n"; + $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ) . "\n"; + $output .= ':
    ' . "\n"; + $output .= '
    ' . "\n"; $output .= '
    ' . "\n"; @@ -184,9 +229,17 @@ function radio_station_clock_shortcode( $atts = array() ) { // --- merge default attributes --- // 2.3.3: fix to incorrect setting key (clock_format) + // 2.5.18: add optional timezone attribute override + // 2.5.18: added optional timezone display attributes $time_format = radio_station_get_setting( 'clock_time_format' ); + $timezone = radio_station_get_timezone(); $defaults = array( 'time_format' => $time_format, + 'timezone' => $timezone, + 'code' => 1, + 'region' => 1, + 'location' => 1, + 'offset' => 1, 'day' => 'full', // full / short / none 'date' => 1, 'month' => 'full', // full / short / none @@ -196,6 +249,11 @@ function radio_station_clock_shortcode( $atts = array() ) { ); $atts = shortcode_atts( $defaults, $atts, 'radio-clock' ); + // --- set timezone display and format --- + // 2.5.18: added for display consistency + $timezone_display = radio_station_get_timezone_display( $timezone, $atts ); + $timezone_format = (string) $atts['code'] . '-' . (string) $atts['region'] . '-' . (string) $atts['location'] . '-' . (string) $atts['offset']; + // --- set clock display classes --- $classes = array( 'radio-station-clock' ); if ( $atts['widget'] ) { @@ -243,7 +301,7 @@ function radio_station_clock_shortcode( $atts = array() ) { $clock .= ':' . "\n"; $clock .= '
    ' . "\n"; $clock .= '
    ' . "\n"; - $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; $clock .= '' . "\n"; // --- user clock --- @@ -253,7 +311,7 @@ function radio_station_clock_shortcode( $atts = array() ) { $clock .= ':' . "\n"; $clock .= '
    ' . "\n"; $clock .= '
    ' . "\n"; - $clock .= '
    ' . "\n"; + $clock .= '
    ' . "\n"; $clock .= '' . "\n"; $clock .= '' . "\n"; @@ -1041,7 +1099,9 @@ function radio_station_genre_archive_list( $atts ) { $genre = radio_station_get_genre( $genre_slug ); if ( $genre ) { // 2.5.10: fix to get first array key - $name = array_key_first( $genre ); + // 2.5.18: make backwards compatible + reset( $genre ); + $name = key( $genre ); $genres[$name] = $genre[$name]; $genre_ids[] = $genre[$name]['id']; } @@ -1457,7 +1517,9 @@ function radio_station_language_archive_list( $atts ) { $language = radio_station_get_language( $language_slug ); if ( $language ) { // 2.5.10: fix to get first array key - $name = array_key_first( $language ); + // 2.5.18: make backwards compatible + reset( $language ); + $name = key( $language ); $languages[$name] = $language[$name]; $language_ids[] = $language[$name]['id']; } diff --git a/includes/support-functions.php b/includes/support-functions.php index 400bb1a..5603419 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1948,13 +1948,16 @@ function radio_station_get_language( $lang = false ) { $languages = radio_station_get_languages(); foreach ( $languages as $i => $lang_data ) { if ( $lang_data['language'] == $lang ) { + // 2.5.18: set show archive link with query filter + $show_archive = get_post_type_archive_link( RADIO_STATION_SHOW_SLUG ); + $show_archive = add_query_arg( 'language', 'main', $show_archive ); $language = array( 'id' => 0, 'slug' => $lang, 'name' => $lang_data['native_name'], 'description' => $lang_data['english_name'], - // TODO: set URL for main language and filter archive page results ? - // 'url' => '', + // 2.5.18: use show archive page link as main language URL + 'url' => $show_archive, ); } } diff --git a/includes/templates.php b/includes/templates.php index 348046e..f70e417 100644 --- a/includes/templates.php +++ b/includes/templates.php @@ -27,7 +27,7 @@ // - Show Posts Adjacent Links // === Query Filters === // - Playlist Archive Query Filter -// - Schedule Override Filters +// - Language Archive Query Filter // === Schedule Override Filters === // - Add Override Template Filters // - Override Show Title @@ -1120,7 +1120,8 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } // --- generate adjacent Show link --- - if ( isset( $show ) ) { + // 2.5.18: added is_array check to ignore false values + if ( isset( $show ) && is_array( $show ) ) { // 2.5.0: fix to maybe get show key data if ( isset( $show['show'] ) ) { @@ -1358,6 +1359,43 @@ function radio_station_show_playlist_query( $query ) { } } +// ----------------------------- +// Language Archive Query Filter +// ----------------------------- +// 2.5.18: added for main language link to post type archive +add_filter( 'pre_get_posts', 'radio_station_show_language_query' ); +function radio_station_show_language_query( $query ) { + + if ( !is_admin() && $query->is_main_query && is_post_type_archive( RADIO_STATION_SHOW_SLUG ) ) { + + if ( isset( $_REQUEST['language'] ) && ( 'main' == sanitize_text_field( wp_unslash( $_REQUEST['language'] ) ) ) ) { + + // --- get main language slug --- + $lang = get_option( 'WPLANG' ); + if ( !$lang ) { + $lang = 'en_US'; + } + + // --- exclude posts with any specified language terms other than main --- + $query->set( 'tax_query', array( + 'relation' => 'OR', + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'term_id', + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ), + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'slug', + 'terms' => $lang, + 'operator' => 'IN', + ), + ) ); + } + } +} + // --------------------------------- // === Schedule Override Filters === diff --git a/js/radio-station-clock.js b/js/radio-station-clock.js index 1f189ae..8ced357 100644 --- a/js/radio-station-clock.js +++ b/js/radio-station-clock.js @@ -97,34 +97,6 @@ function radio_clock_date_time() { } } - /* user timezone offset */ - userzone = userzone.replace('/',', '); - userzone = userzone.replace('_',' '); - houroffset = parseInt(useroffset); - if (houroffset == 0) {userzone += ' [UTC]';} - else { - if ((typeof radio.timezone.zonename_override != 'undefined') && radio.timezone.zonename_override) { - userzone += ' ['+radio.timezone.zonename_override+']'; - } - houroffset = houroffset / 60; - if (houroffset > 0) {userzone += ' [UTC+'+houroffset+']';} - else {userzone += ' [UTC'+houroffset+']';} - } - - /* server timezone */ - serverzone = ''; - if (radio.timezone.utczone) { - serverzone += ' [UTC'+radio.timezone.utc+']'; - } else { - serverzone = radio.timezone.location; - serverzone = serverzone.replace('/',', '); - serverzone = serverzone.replace('_',' '); - if (typeof radio.timezone.code != 'undefined') { - serverzone += ' ['+radio.timezone.code+']'; - } - serverzone += ' ['+radio.timezone.utc+']'; - } - /* loop clock instances */ clock = document.getElementsByClassName('radio-station-clock'); for (i = 0; i < clock.length; i++) { @@ -158,7 +130,27 @@ function radio_clock_date_time() { for (k = 0; k < divs.length; k++) { if ((divs[k].className == 'radio-server-time') && (divs[k].innerHTML != servertime)) {divs[k].innerHTML = servertime;} else if ((divs[k].className == 'radio-server-date') && (divs[k].innerHTML != serverdate)) {divs[k].innerHTML = serverdate;} - else if (init && zone && (divs[k].className == 'radio-server-zone') && (divs[k].innerHTML != serverzone)) {divs[k].innerHTML = serverzone;} + else if (init && zone && (divs[k].className == 'radio-server-zone')) { + /* server timezone */ + format = divs[k].getAttribute('data-format'); f = format.split('-'); + serverzone = radio.timezone.location; szone = ''; + serverzone = serverzone.replace('_',' '); parts = serverzone.split('/'); + if (typeof radio.timezone.code != 'undefined') {code = radio.timezone.code} else {code = '';} + if ((f[0] == '1') && (code != '')) {szone += code+' ';} + if ((f[3] == '1') && radio.timezone.utczone) { + szone += ' [UTC'+radio.timezone.utc+']'; + } else { + if ((f[1] == '1') || (f[2] == '1')) { + if ((f[0] == '1') && (code != '')) {szone += '(';} + if (f[2] == '1') {szone += parts[1];} + if ((f[1] == '1') && (f[2] == '1')) {szone += ', ';} + if (f[1] == '1') {szone += parts[0];} + if ((f[0] == '1') && (code != '')) {szone += ')';} + } + if (f[3] == '1') {szone += ' ['+radio.timezone.utc+']';} + } + if (divs[k].innerHTML != szone) {divs[k].innerHTML = szone;} + } } } @@ -168,7 +160,33 @@ function radio_clock_date_time() { for (k = 0; k < divs.length; k++) { if ((divs[k].className == 'radio-user-time') && (divs[k].innerHTML != usertime)) {divs[k].innerHTML = usertime;} else if ((divs[k].className == 'radio-user-date') && (divs[k].innerHTML != userdate)) {divs[k].innerHTML = userdate;} - else if (init && zone && (divs[k].className == 'radio-user-zone') && (divs[k].innerHTML != userzone)) {divs[k].innerHTML = userzone;} + else if (init && zone && (divs[k].className == 'radio-user-zone')) { + /* user timezone offset */ + format = divs[k].getAttribute('data-format'); f = format.split('-'); + code = radio_timezone_code(userzone); /* console.log(code); */ + /* console.log(radio.timezone.zonename_override); */ + if ((typeof radio.timezone.zonename_override != 'undefined') && radio.timezone.zonename_override) {code = radio.timezone.zonename_override;} + uzone = userzone.replace('_',' '); parts = uzone.split('/'); + uzone = ''; + if ((f[0] == '1') && (code != '')) {uzone += code+' ';} + if ((f[1] == '1') || (f[2] == '1')) { + if ((f[0] == '1') && (code != '')) {uzone += '(';} + if (f[2] == '1') {uzone += parts[1];} + if ((f[1] == '1') && (f[2] == '1')) {uzone += ', ';} + if (f[1] == '1') {uzone += parts[0];} + if ((f[0] == '1') && (code != '')) {uzone += ')';} + } + if (f[3] == '1') { + houroffset = parseInt(useroffset); + if (houroffset == 0) {uzone += ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {uzone += ' [UTC+'+houroffset+']';} + else {uzone += ' [UTC'+houroffset+']';} + } + } + if (divs[k].innerHTML != uzone) {divs[k].innerHTML = uzone;} + } } } } diff --git a/js/radio-station.js b/js/radio-station.js index 627c56b..fcca3aa 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -78,27 +78,78 @@ function radio_timezone_display() { override = radio_display_override(); if (override) {return;} } - if (jQuery('.radio-user-timezone').length) { + /* 2.5.18: display for both clock and timezone shortcodes */ + if (jQuery('.radio-user-zone').length || jQuery('.radio-user-timezone').length) { userdatetime = new Date(); useroffset = -(userdatetime.getTimezoneOffset()); - if ((useroffset * 60) == radio.timezone.offset) {return;} + /* if ((useroffset * 60) == radio.timezone.offset) {return;} */ if (typeof jstz == 'function') {tz = jstz.determine().name();} else {tz = Intl.DateTimeFormat().resolvedOptions().timeZone;} if (tz.indexOf('/') > -1) { - tz = tz.replace('/', ', '); tz = tz.replace('_',' '); - houroffset = parseInt(useroffset); - if (houroffset == 0) {userzone = ' [UTC]';} - else { - houroffset = houroffset / 60; - if (houroffset > 0) {tz += ' [UTC+'+houroffset+']';} - else {tz += ' [UTC'+houroffset+']';} + tz = tz.replace('_',' '); parts = tz.split('/'); + region = parts[0]; loc = parts[1]; + code = radio_timezone_code(tz); /* console.log(code); */ + /* console.log(radio.timezone.zonename_override); */ + if ((typeof radio.timezone.zonename_override != 'undefined') && radio.timezone.zonename_override) {code = radio.timezone.zonename_override;} + if (jQuery('.radio-user-zone').length) { + jQuery('.radio-user-zone-title').css('display','inline-block'); + jQuery('.radio-user-zone').each(function() { + format = jQuery(this).attr('data-format'); + formatted = radio_timezone_format(format,code,region,loc,useroffset); + jQuery(this).html(formatted).css('display','inline-block'); + }); + } + if (jQuery('.radio-user-timezone').length) { + jQuery('.radio-user-timezone-title').css('display','inline-block'); + jQuery('.radio-user-timezone').each(function() { + format = jQuery(this).attr('data-format'); + formatted = radio_timezone_format(format,code,region,loc,useroffset); + jQuery(this).html(formatted).css('display','inline-block'); + }); + } - jQuery('.radio-user-timezone').html(tz).css('display','inline-block'); - jQuery('.radio-user-timezone-title').css('display','inline-block'); } } } +/* Format Timezone Display */ +function radio_timezone_format(format,code,region,loc,offset) { + houroffset = parseInt(offset); + if (houroffset == 0) {userzone = ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {userzone = ' [UTC+'+houroffset+']';} + else {userzone = ' [UTC'+houroffset+']';} + } + f = format.split('-'); formatted = ''; + if ((f[0] == '1') && (code != '')) {formatted += code+' ';} + if ((f[1] == '1') || (f[2] == '1')) { + if ((f[0] == '1') && (code != '')) {formatted += '(';} + if (f[2] == '1') {formatted += loc;} + if ((f[1] == '1') && (f[2] == '1')) {formatted += ', ';} + if (f[1] == '1') {formatted += region;} + if ((f[0] == '1') && (code != '')) {formatted += ')';} + } + if (f[3] == '1') {formatted += ' '+userzone;} + return formatted; +} + +/* Get Timezone Code */ +function radio_timezone_code(timezone) { + datetime = new Date(); + if ((typeof moment == 'function') && (typeof moment.tz == 'function')) { + zonetimedate = moment(datetime.toISOString()).tz(timezone); + code = zonetimedate.format('z'); + } else { + locale = 'en-US'; + formatter = new Intl.DateTimeFormat(locale, {timeZone: timezone, timeZoneName: 'short'}); + timezoneparts = formatter.formatToParts(datetime).find(part => part.type === 'timeZoneName'); + if (timezoneparts) {code = timezoneparts.value;} else {code = '';} + if (code.indexOf('GMT') > -1) {code = '';} + } + return code; +} + /* Retrigger Responsive Schedules */ function radio_responsive_schedules() { if (jQuery('#master-program-schedule').length) {radio_table_responsive(false,false);} @@ -109,32 +160,34 @@ function radio_responsive_schedules() { /* Update Time Displays */ function radio_convert_times() { - if (!radio.convert_show_times) {return;} - /* schedule: .show-time; widgets: .current-shift, .upcoming-show-schedule; show page: .show-shift-time, .override-time */ - jQuery('.show-time, .current-shift, .upcoming-show-shift, .override-time, .show-shift-time').each(function() { - start = jQuery(this).find('.rs-start-time'); - end = jQuery(this).find('.rs-end-time'); - starthtml = start.html(); starttime = start.attr('data'); startformat = start.attr('data-format'); - endhtml = end.html(); endtime = end.attr('data'); endformat = end.attr('data-format'); - startdisplay = radio_user_time(starttime, startformat); - enddisplay = radio_user_time(endtime, endformat); - if ((starthtml != startdisplay) || (endhtml != enddisplay)) { - if (radio.debug) {console.log('Start: '+starthtml+' => '+startdisplay+' - End: '+endhtml+' => '+enddisplay);} - showusertime = jQuery(this).parent().find('.show-user-time').show(); - showusertime.find('.rs-start-time').html(startdisplay); - showusertime.find('.rs-end-time').html(enddisplay); - } else {jQuery(this).parent().find('.show-user-time').hide();} - if (jQuery(this).find('.rs-start-date').length) { - date = jQuery(this).find('.rs-start-date'); - datehtml = date.html(); datetime = date.attr('data'); dateformat = date.attr('data-format'); - datedisplay = radio_user_time(datetime, dateformat); - if (datehtml == datedisplay) {datedisplay = '';} - if (radio.debug) {console.log('Date: '+datehtml+' => '+datedisplay);} - showusertime = jQuery(this).parent().find('.show-user-time'); - showusertime.find('.rs-start-date').html(datedisplay); + /* schedule: .show-time; widgets: .current-shift, .upcoming-show-schedule; show page: .show-shift-time, .override-time, subscribe: .show-list-time */ + jQuery('.show-time, .current-shift, .upcoming-show-shift, .show-shift-time, .override-time, .show-list-time').each(function() { + /* console.log(jQuery(this)); */ + if (radio.convert_show_times || jQuery(this).hasClass('show-list-time')) { + start = jQuery(this).find('.rs-start-time'); + end = jQuery(this).find('.rs-end-time'); + starthtml = start.html(); starttime = start.attr('data'); startformat = start.attr('data-format'); + endhtml = end.html(); endtime = end.attr('data'); endformat = end.attr('data-format'); + startdisplay = radio_user_time(starttime, startformat); + enddisplay = radio_user_time(endtime, endformat); + if ((starthtml != startdisplay) || (endhtml != enddisplay)) { + if (radio.debug) {console.log('Start: '+starthtml+' => '+startdisplay+' - End: '+endhtml+' => '+enddisplay);} + showusertime = jQuery(this).parent().find('.show-user-time').show(); + showusertime.find('.rs-start-time').html(startdisplay); + showusertime.find('.rs-end-time').html(enddisplay); + } else {jQuery(this).parent().find('.show-user-time').hide();} + if (jQuery(this).find('.rs-start-date').length) { + date = jQuery(this).find('.rs-start-date'); + datehtml = date.html(); datetime = date.attr('data'); dateformat = date.attr('data-format'); + datedisplay = radio_user_time(datetime, dateformat); + if (datehtml == datedisplay) {datedisplay = '';} + if (radio.debug) {console.log('Date: '+datehtml+' => '+datedisplay);} + showusertime = jQuery(this).parent().find('.show-user-time'); + showusertime.find('.rs-start-date').html(datedisplay); + } } }); - radio_responsive_schedules(); + if (radio.convert_show_times && jQuery('.show-time').length) {radio_responsive_schedules();} } /* Convert To Time Display */ diff --git a/loader.php b/loader.php index be6d32a..f42862f 100644 --- a/loader.php +++ b/loader.php @@ -148,7 +148,7 @@ function radio_station_settings_texts( $args ) { // (add this to your main plugin file to run this loader) // require(dirname(__FILE__).'/loader.php'); // requires this file! // $instance = new radio_station_loader($args); // instantiates loader class -// (ie. search and replace 'radio_station_' with 'my_plugin_' function namespace) +// (ie. search and replace all 'radio_station_' with 'my_plugin_' function namespace) // and then search and replace 'text-domain' with your plugin's text domain. diff --git a/readme.txt b/readme.txt index 944e00c..a8d71fa 100644 --- a/readme.txt +++ b/readme.txt @@ -413,12 +413,16 @@ We recommend you test these on a Staging site (or a development copy of your liv * Improved: Related Show and Linked Show select Show ordering * Improved: handle Special/Override meta in Archive shortcode * Improved: edit Special/Override permissions for linked Shows +* Improved: Timezone and Clock Timezone display formatting * Added: Dashboard overview widget and Content Dashboard page * Added: Station Frequency and Location options * Added: Channel inputs for Shifts and Overrides +* Added: Link main language term to filtered show archive * Fixed: Special/Override (non-linked) Host/Producer data * Fixed: Radio Player plugin conflicts * Fixed: Override Drafts without dates not listing in Admin +* Fixed: Block asset paths for script/style file versions +* Fixed: Display user timezone for timezone shortcode * Removed: Freemius Pricing v2 filter (merged in Freemius) = 2.5.17 = diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index 7332771..48ce162 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -89,10 +89,12 @@ class radio_station_schedule_engine { // --- set default arguments --- public $channel = ''; public $context = ''; + public $locale = ''; public $now = ''; public $expires = 1800; + public $conflicts = false; public $debug = false; public $debug_log = false; @@ -1697,6 +1699,36 @@ public function get_next_shifts( $limit = 3, $scheduled_shifts = false, $time = // === Shift Checking === // ---------------------- + // ---------------- + // Check All Shifts + // ---------------- + // 2.5.18: added all shift checker + public function check_all_shifts( $records ) { + + // --- get weekdates for checking --- + $now = $this->get_now(); + $timezone = $this->get_timezone(); + $today = $this->get_time( 'l', $now, $timezone ); + $weekdays = $this->get_schedule_weekdays( $today, $now, $timezone ); + $weekdates = $this->get_schedule_weekdates( $weekdays, $now, $timezone ); + $times = array( + 'now' => $now, + 'today' => $today, + 'weekdays' => $weekdays, + 'weekdates' => $weekdates, + 'timezone' => $timezone, + ); + + // --- process records into shift data --- + $split = false; + $all_shifts = $this->get_shift_data( $records, $split, $times ); + $all_shifts = $this->sort_shifts( $all_shifts, $weekdays ); + + // --- check for shift conflicts --- + $checked_shifts = $this->check_shifts( $all_shifts, $times ); + return $checked_shifts; + } + // ------------------------- // Schedule Conflict Checker // ------------------------- @@ -2055,6 +2087,7 @@ public function check_shifts( $all_shifts, $times = false ) { } // --- do shift conflict action --- + $this->conflicts = $conflicts; do_action( 'schedule_engine_set_shift_conflicts', $conflicts, $channel, $context ); if ( ( '' != $context ) && is_string( $context ) ) { do_action( $context . '_set_shift_conflicts', $conflicts, $channel ); From 223677954bf02fb680f9e36539d62d49217f3bef Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 28 Jan 2026 09:06:44 +1000 Subject: [PATCH 365/377] image updates --- images/broadcast-icon.png | Bin 0 -> 720 bytes images/radio-content-icon.png | Bin 0 -> 1325 bytes images/radio-station-icon.png | Bin 720 -> 1471 bytes images/radio-station.png | Bin 18619 -> 179665 bytes player/compat.php | 13 +++++++++ player/radio-player.php | 50 +++++++++++++++++----------------- radio-station-admin.php | 5 ++-- 7 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 images/broadcast-icon.png create mode 100644 images/radio-content-icon.png diff --git a/images/broadcast-icon.png b/images/broadcast-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb1471162c7d73d0bfc0b7fbf29c325bea73476 GIT binary patch literal 720 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 delta 645 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

    tyq|`@eizKe#TBJH#?`OSo4$iURs3va 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 BX?Fks literal 18619 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 SOp-_d0000 '5.3.2', 'url' => $url, 'path' => $path ); @@ -1509,7 +1509,7 @@ function radio_player_core_scripts() { $path = dirname( __FILE__ ) . '/js/jplayer' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'js/jplayer' . $suffix . '.js'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'js/jplayer' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/jplayer' . $suffix . '.js', RADIO_STATION_FILE ); } else { @@ -1525,7 +1525,7 @@ function radio_player_core_scripts() { $path = dirname( __FILE__ ) . '/js/howler' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'js/howler' . $suffix . '.js'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'js/howler' . $suffix . '.js'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/howler' . $suffix . '.js', RADIO_STATION_FILE ); } else { @@ -1534,7 +1534,7 @@ function radio_player_core_scripts() { } /* else { $url = 'js/howler' . $suffix . '.js'; if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . $url; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url; } } */ $radio_player['howler_script'] = array( 'version' => '2.2.3', 'url' => $url, 'path' => $path ); @@ -1543,14 +1543,14 @@ function radio_player_core_scripts() { /* $version = '4.2.6'; // as of WP 4.9 $version = filemtime( dirname( __FILE__ ) . '/js/mediaelement-and-player' . $suffix . '.js' ); $url = 'js/mediaelement-and-player' . $suffix . '.js'; - if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} $radio_player['media_script'] = array( 'version' => $version, 'url' => $url, 'path' => $path ); // --- set media elements player script --- $path = dirname( __FILE__ ) . '/js/rp-mediaelement' . $suffix . '.js'; if ( function_exists( 'wp_enqueue_script' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'js/rp-mediaelement.js'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'js/rp-mediaelement.js'; $version = filemtime( dirname( __FILE__ ) . '/js/rp-mediaelement.js' ); } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/js/rp-mediaelement.js', RADIO_STATION_FILE ); @@ -1563,7 +1563,7 @@ function radio_player_core_scripts() { // note: no minified version here yet ? // $version = filemtime( dirname( __FILE__ ) . '/js/rp-mediaelement.js' ); // $url = 'js/rp-mediaelement.js'; - // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} } $radio_player['elements_script'] = array( 'version' => $version, 'url' => $url, 'path' => $path ); */ @@ -1901,7 +1901,7 @@ function radio_player_get_player_settings( $echo = false ) { // --- set jPlayer Flash path --- // 2.5.7: disable swf fallback support /* if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $swf_path = RADIO_PLAYER_DIR_URL . 'js'; + $swf_path = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'js'; } elseif ( function_exists( 'plugins_url' ) ) { if ( defined( 'RADIO_STATION_FILE' ) ) { $swf_path = plugins_url( 'player/js', RADIO_STATION_FILE ); @@ -2910,7 +2910,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'css/radio-player' . $suffix. '.css'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'css/radio-player' . $suffix. '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/radio-player' . $suffix . '.css', RADIO_STATION_FILE ); } else { @@ -2930,7 +2930,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { // --- output style tag directly --- $url = 'css/radio-player' . $suffix . '.css'; if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . $url; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url; } radio_player_style_tag( 'stream-player', $url, $version ); @@ -2964,7 +2964,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'css/jplayer' . $suffix. '.css'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'css/jplayer' . $suffix. '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/jplayer' . $suffix . '.css', RADIO_STATION_FILE ); } else { @@ -2974,7 +2974,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/jplayer' . $suffix . '.css'; - // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} // radio_player_style_tag( 'rp-jplayer', $url, $version ); } @@ -3000,7 +3000,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'css/jplayer' . $skin_ref . $suffix . '.css'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'css/jplayer' . $skin_ref . $suffix . '.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/jplayer' . $skin_ref . $suffix . '.css', RADIO_STATION_FILE ); } else { @@ -3014,7 +3014,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/jplayer' . $skin_ref . $suffix . '.css'; - // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} // radio_player_style_tag( 'rp-jplayer-skin', $url, $version ); } @@ -3046,7 +3046,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ){ - $url = RADIO_PLAYER_DIR_URL . 'css/rp-mediaelement.css'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'css/rp-mediaelement.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/rp-mediaelement.css', RADIO_STATION_FILE ); } else { @@ -3056,7 +3056,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/rp-mediaelement.css'; - // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} // radio_player_style_tag( 'rp-mediaelement', $url, $version ); } @@ -3072,7 +3072,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) { - $url = RADIO_PLAYER_DIR_URL . 'css/mediaelement.css'; + $url = trailingslashit( RADIO_PLAYER_DIR_URL ) . 'css/mediaelement.css'; } elseif ( defined( 'RADIO_STATION_FILE' ) ) { $url = plugins_url( 'player/css/mediaelement.css', RADIO_STATION_FILE ); } else { @@ -3082,7 +3082,7 @@ function radio_player_enqueue_styles( $script = false, $skin = false ) { } else { // --- output style tag directly --- // $url = 'css/mediaelement.css'; - // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = RADIO_PLAYER_DIR_URL . $url;} + // if ( defined( 'RADIO_PLAYER_DIR_URL' ) ) {$url = trailingslashit( RADIO_PLAYER_DIR_URL ) . $url;} // radio_player_style_tag( 'rp-mediaelement', $url, $version ); } } diff --git a/radio-station-admin.php b/radio-station-admin.php index 35f5144..19a845d 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -12,7 +12,7 @@ // - Filter Plugin Action Links // - Content Dashboard Admin Page // === Admin Menu === -// - Setting Page Capability Check +// - Settings Page Capability Check // - Add Admin Menu and Submenu Items // - Content Dashboard Admin Page // - Fix to Expand Main Menu for Submenu Items @@ -218,7 +218,8 @@ function radio_station_add_admin_menus() { // 2.5.18: added split content and settings menus $rs = __( 'Radio Station', 'radio-station' ); $settings_icon = plugins_url( 'images/radio-station-icon.png', RADIO_STATION_FILE ); - $content_icon = apply_filters( 'radio_station_content_menu_icon', 'dashicons-format-audio' ); + // $content_icon = apply_filters( 'radio_station_content_menu_icon', 'dashicons-format-audio' ); + $content_icon = plugins_url( 'images/radio-content-icon.png', RADIO_STATION_FILE ); $content_position = apply_filters( 'radio_station_content_menu_position', 4 ); $settings_position = apply_filters( 'radio_station_menu_position', 5 ); $settings_cap = apply_filters( 'radio_station_manage_options_capability', 'manage_options' ); From 76c90a929a2a752997e977565dc6035e26ec1d38 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 31 Jan 2026 13:57:29 +1000 Subject: [PATCH 366/377] dev updates --- images/radio-station-icon.png | Bin 1471 -> 1464 bytes loader.php | 1 - player/js/radio-player.js | 64 ++++++++++++++++++++++++---------- player/radio-player.php | 9 +++-- radio-station-admin.php | 2 +- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/images/radio-station-icon.png b/images/radio-station-icon.png index 5233e1f25fb5002e5af112cf02abd567dabb984f..f2a9b0e062339e245d678aafd348c6c3eda11429 100644 GIT binary patch delta 1162 zcmV;51a*;<5d5 z+e=iRT^(LzO2c@dVMjtmwTds&`2)an$4MCOc^y3<0NDFjyvii5uoQ-2Y~E$AJ+ai+ z#YlERnCC9-it@6@aq-tqYWrsU=1l%{GWVAJXyMb)fuxuL)4bNWwtQa273IZqB3rUK zvBQ1D;xZ!*5!Qd9Mv@zSOsJftm**;&yOyebyLEuClokZ&_8s-2aW6H)@gm-vGl2t6m_%F{oHmja$dhsoxD&bl0y4g1UrQzh=x%nY+ zf~*9SiT6tgpNG3{`#{0NUl+ja;ckm@fw7=J`yb!{RP2BA`OW9-lWUPuvD#~Q`RiIV z2ht3tUo-kwhdO)pM<~h*va3qp(3)qAl+SO6{DCq?9wN+;3i$VLQ`A;oVk$j;ey0+AITL1ao;hl+|+5nK!Dm}&q%qCP{yJEcu2|D2kIvB!>?d6~^>_)%y)2*=5fR zo0~QD;M90weOAoE#l};~daeDn1i{1Jze@#tI z5|h6FQ#crBZEWnvimN(Z-uTp1P`m>i&7t=oLhnT=E?&ZBvS7wIx8AAK<&TYyR$v?! cQxCvDO%1f!I$&`X00000NkvXXu0jG}f-Y4)(f|Me delta 1169 zcmV;C1aAAd3%?7nKLLMhNkl-*dFm6kbP`U36R6?D8zW@aV~ zF%qU`lVs>z7G_Auh|Yu|3oa)7F&tq!LAS(Y5jPPt_`_(Bq6`LV2H|DnrCLN_g90t| z<=QLI*Ynz4aFy+oJb7}z`+mRoydDIe!lk5qbqbx|nZiIbg)Dy-01yG(n_FPfkB!X- z-KFQ22L7R>aXc}wHMO`@%M-Un2;hIW%>>to%zBTwwzzF z(9=g*W5fkkDRO@$M@8x>a(Www%hvh8ZyHohuk;KBh3-WBk>;8om%yVz%q{nLOrSrLk z9Bp!jj^Oz@E7+$gT=acD%pADow9HZw0H*pFFaaG7ha-QE8ti9 zV6xrI>{{v_nizXE7zhB#9`*TV$D}0`j(DL6QAF8e#07?kNJbzO@*Gg=Wh?<7*|a%u zvb?ZIo7An-DSzoV^-OJf@DLXjFn81V!cZveC5TA+BljcX0%;ID!$fH4ZOeVT&*cg; zWg;Gz&rg4~7pUlKFDV%>78Gb1d@jwq?(n-H$i+Yq9s?j2${r&wkldjz8Vrk@MS01X zW#N;c$A>G$VwOKIjU9Ym!$b%I1ne%aV`lzbIht%FwSq7VdMSI1xWHk2b;`7*<-{7HrR%p zvs;dnY#Jl^7+3u*S;AEsWhH}72hpZZORFSno8=O9D+{5e{=Mlub$NNn88cJF1x7jb zpvQFIYb#mDur81!PDt13UV{+?KnM&pTJ0M^DNa~(dQ21eY6+?RnX+y3)Vmy|wzj%? zZ-#&I4DRy*@@xYRj|-+JCR-XVHdaxR9mE~%$Em0|ovYR9zOvhGJ#MF4hWorel3(Qs z@)JqBlT0I{qf3-DjvZ5`;Hx$2n=~3S`(aI85qTBI$=cNUnz|!&I&)g7Rz=qrjVb@f z6G!-Q{e>?y+Vn!ZeXS=uXWyJuDm}yDa1?)8Ia&P>3?-&2mHEbuz0$th{XbHcZFa2T z*|Sv@&*fxY69@zVb7AC73?m|;Fs8)Z7=U0njEaPa2lIlFq2bEUue4mHtTEyOt7|?u zs@AAFaIbIDJpE?{ilR6IBgu+HMH>Mkh<(NO7Wt2ycetGbgTc@u;PEqOXJ_ARywrcx zK}Gx*SXz2ay+1G40>jY5@4jw3I6OXXrC!nMKB}FE00Gz6Hz>*HK(uJo>J!T=Pya^V zW#!P_5lwr0`&_iXy(w%K+q$@DE$+WFc;K-xQXyfJA3' . "\n"; $row .= '' . "\n"; - } elseif ( 'image' == $type ) { // 1.1.7: added image attachment selection from media library diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 277ea73..bcb010c 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -130,7 +130,10 @@ function radio_player_load_station(instance, station, data, start) { data = radio_player_check_format(data); script = data.script; player = radio_player_load_audio(script, instance, data, start); if (player && start) { - setTimeout(function() {radio_player_play_on_load(player, script, instance);}, 250); + setTimeout(function() { + if (radio_player.debug) {console.log('Play on Load A');} + radio_player_play_on_load(player, script, instance); + }, 250); } } @@ -147,7 +150,10 @@ function radio_player_load_stream(script, instance, data, start) { data = radio_player_check_format(data); script = data.script; /* console.log('Script: '+script+' - Instance: '+instance+' URL: '+data.url+' Format: '+data.format+' Start:'+start); */ player = radio_player_load_audio(script, instance, data, start); - if (player && start) {radio_player_play_on_load(player, script, instance);} + if (player && start) { + if (radio_player.debug) {console.log('Play on Load B');} + radio_player_play_on_load(player, script, instance); + } } /* --- load a file --- */ @@ -161,7 +167,10 @@ function radio_player_load_file(script, instance, data, start) { /* load the audio stream */ data = radio_player_check_format(data); script = data.script; player = radio_player_load_audio(script, instance, data, start); - if (player && start) {radio_player_play_on_load(player, script, instance);} + if (player && start) { + if (radio_player.debug) {console.log('Play on Load C');} + radio_player_play_on_load(player, script, instance); + } } /* --- load audio in player --- */ @@ -171,7 +180,7 @@ function radio_player_load_audio(script, instance, data, start) { radio_player_set_data_state(script, instance, data, start); loaded = radio_player_check_script(script); if (loaded) { - if (radio_player.delayed_player) {clearInterval(radio_player.delayed_player);} + clearInterval(radio_player.delayed_player); /* initialize the player if script is already loaded */ if (script == 'amplitude') {player = radio_player_amplitude(instance, url, format, fallback, fformat);} else if (script == 'jplayer') {player = radio_player_jplayer(instance, url, format, fallback, fformat);} @@ -187,6 +196,7 @@ function radio_player_load_audio(script, instance, data, start) { /* delay initialization until script is loaded */ if (radio_player.debug) {console.log('Script not ready, initializing Delayed Player');} radio_player.delayed_data = {'time':0, 'start':start, 'script':script, 'instance':instance, 'url':url, 'format':format, 'fallback':fallback, 'fformat':fformat}; + if (radio_player.hasOwnProperty('delayed_player')) {clearInterval(radio_player.delayed_player);} radio_player.delayed_player = setInterval(function() { radio_player.delayed_data.time++; data = radio_player.delayed_data; player = false; if (data.time > 10) {console.log('Script load timed out. Please try again...'); clearInterval(radio_player.delayed_player);} @@ -207,7 +217,10 @@ function radio_player_load_audio(script, instance, data, start) { radio_player_event_handler('loading', data); radio_player_set_data_state(script, instance, data, data.start); /* note: radio_player_play_on_load must be called inside delayed function */ - if (data.start) {radio_player_play_on_load(player, data.script, data.instance);} + if (data.start) { + if (radio_player.debug) {console.log('Play on Load D');} + radio_player_play_on_load(player, data.script, data.instance); + } } }, 1000); return false; @@ -231,7 +244,8 @@ function radio_player_play_on_load(player, script, instance) { } else { /* jPlayer not ready, wait until ready to play */ if (radio_player.debug) {console.log('jPlayer is not yet ready...');} - radio_player.jplayer_load = setInterval(function() { + if (radio_player.hasOwnProperty('jplayer_load')) {clearInterval(radio_player.jplayer_load);} + radio_player.jplayer_load = setInterval(function(player) { if (radio_player.jplayer_ready) { clearInterval(radio_player.jplayer_load); if (radio_player.debug) {console.log('jPlayer is ready.');} @@ -239,7 +253,7 @@ function radio_player_play_on_load(player, script, instance) { radio_player_custom_event('rp-play', detail); } catch(e) {console.log(script+' error: could not play stream.'); console.log(e);} } - }, 250); + }(player), 250); } } /* if (!jQuery('#radio_container_'+instance).hasClass('playing')) {jQuery('#radio_container_'+instance).addClass('playing');} */ @@ -330,7 +344,10 @@ function radio_player_switch_script(instance, script) { radio_player_stop_instance(instance, false); } player = radio_player_load_audio(script, instance, data, data.start); - if (player && data.start) {radio_player_play_on_load(player, script, instance);} + if (player && data.start) { + if (radio_player.debug) {console.log('Play on Load E');} + radio_player_play_on_load(player, script, instance); + } } /* === Player Functions and Event Callbacks === */ @@ -345,6 +362,7 @@ function radio_player_play_instance(instance) { console.log(player); console.log(script); if (script == 'amplitude') { player.play(); radio_player_retry.player = player; + if (radio_player_retry.hasOwnProperty('cycle')) {clearInterval(radio_play_retry.cycle);} radio_player_retry.cycle = setInterval(function() { player = radio_player_retry.player; if (player.getPlayerState() == 'stopped') {player.play();} @@ -426,6 +444,7 @@ function radio_player_is_playing(instance) { if (radio_player.debug) {console.log(player);} if (script == 'amplitude') { state = player.getPlayerState(); + if (radio_player.debug) {console.log('Amplitude Player State for Instance '+instance+': '+state);} if (state == 'playing') {playing = true;} else {playing = false;} } else if (script == 'howler') { playing = player.playing(); @@ -761,7 +780,7 @@ function radio_player_broadcast_playing(instance) { data = channel; } windowid = radio_player_window_guid(); - if (!instance) {instance = 0;} + if (instance === false) {console.log('Player Instance Error'); return;} message = windowid+'::'+instance+'::'+type+'::'+data; console.log('Broadcast Message: '+message); sysend.broadcast('radio-play', {message: message}); @@ -1155,9 +1174,13 @@ jQuery(document).ready(function() { }); /* === Pageload Functions === */ - +var radio_player_initializing; jQuery(document).ready(function() { + /* --- prevent conflict on duplicate script load --- */ + if (radio_player_initializing) {return;} + radio_player_initializing = true; + /* --- hide all volume controls if no support (iOS) --- */ novolumesupport = radio_player_volume_test(); if (novolumesupport) { @@ -1170,23 +1193,26 @@ jQuery(document).ready(function() { } /* --- bind pause/play button clicks --- */ - jQuery('.rp-play-pause-button').on('click', function() { + jQuery('.rp-play-pause-button').on('click', function(e) { + e.stopImmediatePropagation(); e.preventDefault(); container = jQuery(this).parents('.radio-container'); instance = container.attr('id').replace('radio_container_',''); /* radio_player.debug = true; */ if (radio_player.debug) {console.log('Play/Pause Button Click:'); console.log(jQuery(this));} + /* maybe toggle currently playing radio */ + inst = instance; + if (typeof radio_player_toggle_current == 'function') { + if (radio_player.debug) {console.log('Trigger Toggle of Player. New Instance '+instance);} + done = radio_player_toggle_current(instance); + if (done) {return;} + } + instance = inst; /* <- this is a fix */ + + /* check to pause or play */ if (radio_player_is_playing(instance)) { if (radio_player.debug) {console.log('Trigger Pause of Player Instance '+instance);} radio_player_pause_instance(instance); } else { - /* maybe toggle currently playing radio */ - inst = instance; - if (typeof radio_player_toggle_current == 'function') { - if (radio_player.debug) {console.log('Trigger Toggle of Player. New Instance '+instance);} - done = radio_player_toggle_current(instance); - if (done) {return;} - } - instance = inst; /* <- this is a fix */ if (radio_player.debug) {console.log('Trigger Play of Player Instance '+instance);} if (instance in radio_player_data.players) { radio_player_play_instance(instance); diff --git a/player/radio-player.php b/player/radio-player.php index 0342a78..1982908 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -179,9 +179,14 @@ function radio_player_set_debug_mode() { if ( function_exists( 'radio_station_get_setting' ) ) { $debug = radio_station_get_setting( 'player_debug' ); } + // 2.5.18: allow for querystring to disable player debug setting // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['player-debug'] ) && ( '1' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) ) { - $debug = true; + if ( isset( $_REQUEST['player-debug'] ) ) { + if ( '1' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) { + $debug = true; + } elseif ( '0' === sanitize_text_field( wp_unslash( $_REQUEST['player-debug'] ) ) ) { + $debug = false; + } } if ( function_exists( 'apply_filters' ) ) { $debug = apply_filters( 'radio_station_player_debug', $debug ); diff --git a/radio-station-admin.php b/radio-station-admin.php index 19a845d..cb0045b 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -217,7 +217,7 @@ function radio_station_add_admin_menus() { // 2.5.18: added split content and settings menus $rs = __( 'Radio Station', 'radio-station' ); - $settings_icon = plugins_url( 'images/radio-station-icon.png', RADIO_STATION_FILE ); + $settings_icon = plugins_url( 'images/radio-station-icon.png?v=2', RADIO_STATION_FILE ); // $content_icon = apply_filters( 'radio_station_content_menu_icon', 'dashicons-format-audio' ); $content_icon = plugins_url( 'images/radio-content-icon.png', RADIO_STATION_FILE ); $content_position = apply_filters( 'radio_station_content_menu_position', 4 ); From 78733d874d06973b297a7dca1a1a058ac32a75ce Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 31 Jan 2026 14:35:34 +1000 Subject: [PATCH 367/377] honour conflict checker setting on show save --- includes/post-types-admin.php | 21 +++++++++++++++------ radio-station-admin.php | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index f18c6a0..5afb798 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2257,6 +2257,9 @@ function radio_station_show_save_data( $post_id ) { // --- check show shift nonce --- if ( isset( $_POST['show_shifts_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_shifts_nonce'] ), 'radio-station' ) ) { + // 2.5.18: get check conflicts setting + $check_conflicts = radio_station_get_setting( 'conflict_checker' ); + // --- loop posted show shift times --- // 2.3.1: added check if any shifts are set (fix undefined index warning) $prev_shifts = radio_station_get_show_schedule( $post_id ); @@ -2392,11 +2395,14 @@ function radio_station_show_save_data( $post_id ) { // 2.3.0: added show shift conflict checking if ( !$disabled ) { // 2.5.0: changed scope to other instead of shows - $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'other' ); - if ( $conflicts ) { - $disabled = true; - if ( RADIO_STATION_DEBUG ) { - echo "*Conflicting Shift Disabled*" . "\n"; + // 2.5.18: honour shift conflict setting + if ( 'yes' == $check_conflicts ) { + $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'other' ); + if ( $conflicts ) { + $disabled = true; + if ( RADIO_STATION_DEBUG ) { + echo "*Conflicting Shift Disabled*" . "\n"; + } } } } @@ -2413,7 +2419,10 @@ function radio_station_show_save_data( $post_id ) { // --- recheck for conflicts with other shifts for this show --- // 2.3.0: added new shift conflict checking - $new_shifts = radio_station_check_new_shifts( $new_shifts ); + // 2.5.18: honour shift conflict setting + if ( 'yes' == $check_conflicts ) { + $new_shifts = radio_station_check_new_shifts( $new_shifts ); + } // --- update the schedule meta entry --- // 2.3.0: check if shift times have changed before saving diff --git a/radio-station-admin.php b/radio-station-admin.php index cb0045b..6e1745c 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -1628,7 +1628,7 @@ function radio_station_announcement_content( $dismissable = true ) { // --- plugin image --- $plugin_image = plugins_url( 'images/radio-station.png', RADIO_STATION_FILE ); echo '
  • ' . "\n"; - echo '' . "\n"; + echo '' . "\n"; echo '
  • ' . "\n"; // --- takeover announcement --- From eafbf21e8dacf5d7bb827a7e08d41fd86742f574 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 1 Feb 2026 00:49:12 +1000 Subject: [PATCH 368/377] role cap fixes --- includes/onboarding.php | 7 +- includes/post-types-admin.php | 648 +++++++++++++++++++--------------- includes/user-roles.php | 299 ++++++++++++---- radio-station-admin.php | 1 + 4 files changed, 590 insertions(+), 365 deletions(-) diff --git a/includes/onboarding.php b/includes/onboarding.php index 59028cc..436041f 100644 --- a/includes/onboarding.php +++ b/includes/onboarding.php @@ -446,9 +446,11 @@ function radio_station_statistics_panel() { echo '
    ' . "\n"; echo '
    ' . "\n"; + */ + // --- schedule progress bar --- + $conflict_percent = 0; radio_station_progress_bar( $schedule_percent, $conflict_percent ); - */ // --- Content heading --- echo '
    ' . esc_html( 'Station Content', 'radio-station' ) . '
    ' . "\n"; @@ -1255,7 +1257,8 @@ function radio_station_enqueue_onboarding_styles() { // Onboarding Styles // ----------------- function radio_station_onboarding_styles() { - $css = ".progress-heading {text-align:center; font-size:16px; font-variant:small-caps; letter-spacing:3px; margin-bottom:5px;} + $css = ".progress-heading {text-align:center; font-size:16px; font-variant:small-caps; letter-spacing:3px; + margin-top:10px; margin-bottom:5px; max-width:500px;} .progress-list {margin-bottom:7px;} .progress-list-item {display:inline-block; margin-right:25px;} .progress-list-item:last-child {margin-right:0;} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 5afb798..ab53635 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -3437,20 +3437,17 @@ function radio_station_override_show_metabox() { // --- inside show metaboxes --- $inside_metaboxes = array(); - // 2.5.18: add permissions check for changing hosts / producers - if ( ( $author_id == $current_user_id ) || current_user_can( 'edit_others_overrides' ) ) { - $inside_metaboxes['hosts'] = array( - 'title' => __( 'Override DJ(s) / Host(s)', 'radio-station' ), - 'callback' => 'radio_station_show_hosts_metabox', - ); - $inside_metaboxes['producers'] = array( - 'title' => __( 'Override Producer(s)', 'radio-station' ), - 'callback' => 'radio_station_show_producers_metabox', - ); - } + $inside_metaboxes['hosts'] = array( + 'title' => __( 'Override DJ(s) / Host(s)', 'radio-station' ), + 'callback' => 'radio_station_show_hosts_metabox', + ); + $inside_metaboxes['producers'] = array( + 'title' => __( 'Override Producer(s)', 'radio-station' ), + 'callback' => 'radio_station_show_producers_metabox', + ); // --- display inside metaboxes --- - if ( count( $inside_metaboxes ) > 0 ) { + // if ( count( $inside_metaboxes ) > 0 ) { echo '
    '; $i = 1; foreach ( $inside_metaboxes as $key => $metabox ) { @@ -3486,7 +3483,7 @@ function radio_station_override_show_metabox() { $i++; } echo '
    ' . "\n"; - } + // } // --- input list field styles --- // 2.3.3.9: add styles for table to list conversion @@ -3885,10 +3882,12 @@ function radio_station_overrides_table( $post_id ) { $list .= esc_html( $channel['title'] ) . '' . "\n"; } $list .= '' . "\n"; - $list .= '' . "\n"; + // 2.5.18: fix to incorrect variable unique_id + $list .= '' . "\n"; $list .= '' . "\n"; } else { - $list .= '' . "\n"; + // 2.5.18: fix to incorrect variable unique_id + $list .= '' . "\n"; } // --- Override (Start) Date --- @@ -4129,7 +4128,7 @@ function radio_station_override_edit_script() { }); jQuery('#show_override_nonce').clone().attr('id','').appendTo('#override-save-form'); jQuery('#post_ID').clone().attr('id','').attr('name','override_id').appendTo('#override-save-form'); - jQuery('#override-saving-message').show(); + jQuery('#overrides-saving-message').show(); jQuery('#override-save-form').submit(); }" . "\n"; @@ -4428,16 +4427,20 @@ function radio_station_override_save_data( $post_id ) { if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( sanitize_text_field( $_POST['show_override_nonce'] ), 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); } elseif ( !$post ) { - $error = __( 'Failed. Invalid Override.', 'radio-station' ); + $error = __( 'Failed. Invalid Special Override.', 'radio-station' ); } elseif ( !current_user_can( 'edit_shows' ) ) { $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); + } elseif ( !current_user_can( 'edit_override', $post_id ) ) { + // 2.5.18: add permission check for editing this override + $error = __( 'Failed. You are not allowed to do that.', 'radio-station' ); } // --- send error to parent frame --- if ( $error ) { - echo ""; @@ -4456,324 +4459,354 @@ function radio_station_override_save_data( $post_id ) { // $linked_show_id = get_post_meta( $post_id, 'linked_show_id', true ); $prev_linked = get_post_meta( $post_id, 'linked_show_id', true ); $linked_id = absint( $_POST['linked_show_id'] ); - $linked_show = get_post( $linked_id ); - if ( !$linked_show ) { - delete_post_meta( $post_id, 'linked_show_id' ); - } else { - update_post_meta( $post_id, 'linked_show_id', $linked_id ); - } - if ( $linked_id != $prev_linked ) { - $meta_changed = true; - } + + // 2.5.18: check permission to save linked show value + // 2.5.18: also add missing editor capabilities + $user = wp_get_current_user(); + $editor_caps = radio_station_get_setting( 'add_editor_capabilities' ); + $full_permissions = false; + if ( in_array( 'administrator', $user->roles ) || in_array( 'show-editor', $user->roles ) ) { + $full_permissions = true; + } elseif ( ( 'yes' == $editor_caps ) && in_array( 'editor', $user->roles ) ) { + $full_permissions = true; + } + $permission = $full_permissions ? true : current_user_can( 'edit_show', $linked_id ); + + if ( $permission ) { + + $linked_show = get_post( $linked_id ); + if ( !$linked_show ) { + delete_post_meta( $post_id, 'linked_show_id' ); + } else { + update_post_meta( $post_id, 'linked_show_id', $linked_id ); + } + if ( $linked_id != $prev_linked ) { + $meta_changed = true; + } - // --- sync genres switch --- - if ( isset( $_POST['sync_genres'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_genres'] ) ) ) { + // --- sync genres switch --- + if ( isset( $_POST['sync_genres'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_genres'] ) ) ) { - update_post_meta( $post_id, 'sync_genres', 'yes' ); + update_post_meta( $post_id, 'sync_genres', 'yes' ); - if ( $linked_show ) { + if ( $linked_show ) { - // --- sync genre terms --- - $prev_term_ids = $term_ids = array(); - $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); - if ( count( $prev_terms ) > 0 ) { - foreach( $prev_terms as $prev_term ) { - $prev_term_ids[] = $prev_term->term_id; + // --- sync genre terms --- + $prev_term_ids = $term_ids = array(); + $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); + if ( count( $prev_terms ) > 0 ) { + foreach( $prev_terms as $prev_term ) { + $prev_term_ids[] = $prev_term->term_id; + } } - } - $genre_terms = wp_get_object_terms( $linked_id, RADIO_STATION_GENRES_SLUG ); - if ( count( $genre_terms ) > 0 ) { - foreach ( $genre_terms as $genre_term ) { - $term_ids[] = $genre_term->term_id; + $genre_terms = wp_get_object_terms( $linked_id, RADIO_STATION_GENRES_SLUG ); + if ( count( $genre_terms ) > 0 ) { + foreach ( $genre_terms as $genre_term ) { + $term_ids[] = $genre_term->term_id; + } + } + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_GENRES_SLUG ); + if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { + $meta_changed = true; } } - wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_GENRES_SLUG ); - if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { - $meta_changed = true; - } + } else { + delete_post_meta( $post_id, 'sync_genres' ); } - } else { - delete_post_meta( $post_id, 'sync_genres' ); - } - // --- sync languages switch --- - if ( isset( $_POST['sync_languages'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_languages'] ) ) ) { + // --- sync languages switch --- + if ( isset( $_POST['sync_languages'] ) && ( 'yes' == sanitize_text_field( $_POST['sync_languages'] ) ) ) { - if ( $linked_show ) { + if ( $linked_show ) { - update_post_meta( $post_id, 'sync_languages', 'yes' ); + update_post_meta( $post_id, 'sync_languages', 'yes' ); - // --- sync language terms --- - $prev_term_ids = $term_ids = array(); - $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_LANGUAGES_SLUG ); - if ( count( $prev_terms ) > 0 ) { - foreach( $prev_terms as $prev_term ) { - $prev_term_ids[] = $prev_term->term_id; + // --- sync language terms --- + $prev_term_ids = $term_ids = array(); + $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $prev_terms ) > 0 ) { + foreach( $prev_terms as $prev_term ) { + $prev_term_ids[] = $prev_term->term_id; + } } - } - $language_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); - if ( count( $language_terms ) > 0 ) { - foreach ( $language_terms as $language_term ) { - $term_ids[] = $language_term->term_id; + $language_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $language_terms ) > 0 ) { + foreach ( $language_terms as $language_term ) { + $term_ids[] = $language_term->term_id; + } + } + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); + if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { + $meta_changed = true; } } - wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); - if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { - $meta_changed = true; + } else { + delete_post_meta( $post_id, 'sync_languages' ); + } + + // --- update linked show fields --- + $linked_show_fields = array(); + $show_fields = array( 'title', 'content', 'excerpt', 'avatar', 'image', 'user_list', 'producer_list', 'link', 'email', 'phone', 'file', 'download', 'patreon' ); + // 2.5.18: added missing show_avatar and show_image non-input fields + $non_input_fields = array( 'show_title', 'show_content', 'show_excerpt', 'show_avatar', 'show_image' ); + foreach ( $show_fields as $field ) { + $show_field = 'show_' . $field; + $linked = false; + if ( isset( $_POST[$show_field . '_link'] ) ) { + // 2.5.0: use sanitize_text_field on posted value + $linked = sanitize_text_field( $_POST[$show_field . '_link'] ); } - } - } else { - delete_post_meta( $post_id, 'sync_languages' ); - } - - // --- update linked show fields --- - $linked_show_fields = array(); - $show_fields = array( 'title', 'content', 'excerpt', 'avatar', 'image', 'user_list', 'producer_list', 'link', 'email', 'phone', 'file', 'download', 'patreon' ); - // 2.5.18: added missing show_avatar and show_image non-input fields - $non_input_fields = array( 'show_title', 'show_content', 'show_excerpt', 'show_avatar', 'show_image' ); - foreach ( $show_fields as $field ) { - $show_field = 'show_' . $field; - $linked = false; - if ( isset( $_POST[$show_field . '_link'] ) ) { - // 2.5.0: use sanitize_text_field on posted value - $linked = sanitize_text_field( $_POST[$show_field . '_link'] ); - } - $linked_show_fields[$show_field] = ( 'yes' == $linked ) ? true : false; - if ( !in_array( $show_field, $non_input_fields ) ) { - if ( isset( $_POST[$show_field] ) ) { - $value = radio_station_sanitize_input( 'show', $field ); - update_post_meta( $post_id, $show_field, $value ); - } else { - delete_post_meta( $post_id, $show_field ); + $linked_show_fields[$show_field] = ( 'yes' == $linked ) ? true : false; + if ( !in_array( $show_field, $non_input_fields ) ) { + if ( isset( $_POST[$show_field] ) ) { + $value = radio_station_sanitize_input( 'show', $field ); + update_post_meta( $post_id, $show_field, $value ); + } else { + delete_post_meta( $post_id, $show_field ); + } } } - } - update_post_meta( $post_id, 'linked_show_fields', $linked_show_fields ); + update_post_meta( $post_id, 'linked_show_fields', $linked_show_fields ); + } } // --- verify this came from the our screen and with proper authorization --- // 2.3.3.9: reverse condition to allow for combined data/schedule processing if ( isset( $_POST['show_override_nonce'] ) && wp_verify_nonce( sanitize_text_field( $_POST['show_override_nonce'] ), 'radio-station' ) ) { - // --- get the show override data --- - $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); - if ( isset( $_POST['new_override'] ) ) { - // 2.5.0: use sanitize_text_field on posted value - $new_shift = sanitize_text_field( $_POST['new_override'] ); - $new_shift['id'] = radio_station_unique_shift_id(); - $show_sched = $current_scheds; - $show_sched[] = $new_shift; - // TODo: test array_map with sanitize_text_field here ? - $_POST['show_sched'] = $show_sched; - } else { - // TODo: test array_map with sanitize_text_field here ? - $show_sched = $_POST['show_sched']; - } - $new_scheds = array(); - if ( is_array( $show_sched ) ) { - - // 2.3.3.9: loop to save possible multiple override dates/times --- - // print_r( $show_sched ); - foreach ( $show_sched as $sched ) { - - // --- get/set current schedule for merging --- - // 2.2.2: added to set default keys - // 2.3.3.9: just set new schedule array here - $new_sched = array( - 'channel' => 0, - 'id' => '', - 'date' => '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - // 'multiday' => '', - // 'end_date' => '', - 'disabled' => '', - ); + if ( !isset( $permission ) ) { + $user = wp_get_current_user(); + $editor_caps = radio_station_get_setting( 'add_editor_capabilities' ); + $full_permissions = false; + if ( in_array( 'administrator', $user->roles ) || in_array( 'show-editor', $user->roles ) ) { + $full_permissions = true; + } elseif ( ( 'yes' == $editor_caps ) && in_array( 'editor', $user->roles ) ) { + $full_permissions = true; + } + $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); + $permission = $full_permissions ? true : current_user_can( 'edit_show', $linked_id ); + } - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $override_dates = array(); - foreach ( $sched as $key => $value ) { + if ( $permission ) { + // --- get the show override data --- + $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); + if ( isset( $_POST['new_override'] ) ) { + // 2.5.0: use sanitize_text_field on posted value + $new_shift = sanitize_text_field( $_POST['new_override'] ); + $new_shift['id'] = radio_station_unique_shift_id(); + $show_sched = $current_scheds; + $show_sched[] = $new_shift; + // TODo: test array_map with sanitize_text_field here ? + $_POST['show_sched'] = $show_sched; + } else { + // TODo: test array_map with sanitize_text_field here ? + $show_sched = $_POST['show_sched']; + } + $new_scheds = array(); + if ( is_array( $show_sched ) ) { + + // 2.3.3.9: loop to save possible multiple override dates/times --- + // print_r( $show_sched ); + foreach ( $show_sched as $sched ) { + + // --- get/set current schedule for merging --- + // 2.2.2: added to set default keys + // 2.3.3.9: just set new schedule array here + $new_sched = array( + 'channel' => 0, + 'id' => '', + 'date' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + // 'multiday' => '', + // 'end_date' => '', + 'disabled' => '', + ); - $isvalid = false; + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $override_dates = array(); + foreach ( $sched as $key => $value ) { - // --- validate according to key --- - // 2.5.18: add channel key/value - if ( 'channel' == $key ) { - $channel = absint( $value ); - if ( $channel > -1 ) { - $isvalid = true; - } - } elseif ( ( 'date' === $key ) || ( 'end_date' == $key ) ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - // 2.3.3.9: added extra check for date parts - if ( 3 == count( $parts ) ) { - if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { + $isvalid = false; + + // --- validate according to key --- + // 2.5.18: add channel key/value + if ( 'channel' == $key ) { + $channel = absint( $value ); + if ( $channel > -1 ) { + $isvalid = true; + } + } elseif ( ( 'date' === $key ) || ( 'end_date' == $key ) ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + // 2.3.3.9: added extra check for date parts + if ( 3 == count( $parts ) ) { + if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { + $isvalid = true; + // 2.2.7: sync separate meta key for override date + // (could be used to improve column sorting efficiency) + // 2.3.3.9: store all override dates for later + $override_dates[] = $value; + } + } + } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { + // 2.2.3: fix to validate 00 minute value + // 2.5.0: refix to validate 00 minute value + if ( empty( $value ) || ( '00' == $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } elseif ( ( 'disabled' == $key ) || ( 'multiday' == $key ) ) { + // note: not set again if already empty + if ( 'yes' == $value ) { + $isvalid = true; + } + } elseif ( 'id' == $key ) { + // 2.3.3.9: validate unique shift ID + if ( preg_match( '/^[a-zA-Z0-9_]+$/', $value ) ) { $isvalid = true; - // 2.2.7: sync separate meta key for override date - // (could be used to improve column sorting efficiency) - // 2.3.3.9: store all override dates for later - $override_dates[] = $value; } } - } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { - $isvalid = true; - } - } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { - // 2.2.3: fix to validate 00 minute value - // 2.5.0: refix to validate 00 minute value - if ( empty( $value ) || ( '00' == $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > -1 && absint( $value ) < 61 ) { - $isvalid = true; - } - } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { - $valid = array( '', 'am', 'pm' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; - } - } elseif ( ( 'disabled' == $key ) || ( 'multiday' == $key ) ) { - // note: not set again if already empty - if ( 'yes' == $value ) { - $isvalid = true; - } - } elseif ( 'id' == $key ) { - // 2.3.3.9: validate unique shift ID - if ( preg_match( '/^[a-zA-Z0-9_]+$/', $value ) ) { - $isvalid = true; + + // --- if valid add to new schedule setting --- + if ( $isvalid ) { + $new_sched[$key] = $value; } } - // --- if valid add to new schedule setting --- - if ( $isvalid ) { - $new_sched[$key] = $value; + // 2.3.3.9: add unique override shift ID + // 2.5.0: fix to check if empty instead of isset + if ( '' == $new_sched['id'] ) { + $new_sched['id'] = radio_station_unique_shift_id(); } - } - // 2.3.3.9: add unique override shift ID - // 2.5.0: fix to check if empty instead of isset - if ( '' == $new_sched['id'] ) { - $new_sched['id'] = radio_station_unique_shift_id(); + // --- add to new schedule array --- + // 2.3.3.9: to allow for multiple overrides + $new_scheds[] = $new_sched; } - // --- add to new schedule array --- - // 2.3.3.9: to allow for multiple overrides - $new_scheds[] = $new_sched; - } - - // --- clear and resave override dates --- - // 2.3.3.9: add individual records for each date - if ( count( $override_dates ) > 0 ) { - delete_post_meta( $post_id, 'show_override_date' ); - foreach ( $override_dates as $override_date ) { - add_post_meta( $post_id, 'show_override_date', $override_date, false ); + // --- clear and resave override dates --- + // 2.3.3.9: add individual records for each date + if ( count( $override_dates ) > 0 ) { + delete_post_meta( $post_id, 'show_override_date' ); + foreach ( $override_dates as $override_date ) { + add_post_meta( $post_id, 'show_override_date', $override_date, false ); + } } - } - // --- check if override schedule has changed --- - $sched_changed = true; - // 2.3.3.9: added check that current schedule is set - if ( $current_scheds && is_array( $current_scheds ) ) { - if ( array_key_exists( 'date', $current_scheds ) ) { - $current_scheds['id'] = radio_station_unique_shift_id(); - $current_scheds = array( $current_scheds ); - } - $prev_scheds = $current_scheds; - if ( count( $current_scheds ) == count( $new_scheds ) ) { - foreach ( $current_scheds as $i => $current_sched ) { - foreach ( $new_scheds as $j => $new_sched ) { - if ( $new_sched == $current_sched ) { - unset( $current_scheds[$i] ); + // --- check if override schedule has changed --- + $sched_changed = true; + // 2.3.3.9: added check that current schedule is set + if ( $current_scheds && is_array( $current_scheds ) ) { + if ( array_key_exists( 'date', $current_scheds ) ) { + $current_scheds['id'] = radio_station_unique_shift_id(); + $current_scheds = array( $current_scheds ); + } + $prev_scheds = $current_scheds; + if ( count( $current_scheds ) == count( $new_scheds ) ) { + foreach ( $current_scheds as $i => $current_sched ) { + foreach ( $new_scheds as $j => $new_sched ) { + if ( $new_sched == $current_sched ) { + unset( $current_scheds[$i] ); + } } } } + if ( 0 === count( $current_scheds ) ) { + $sched_changed = false; + } } - if ( 0 === count( $current_scheds ) ) { - $sched_changed = false; - } - } - // --- sort schedule overrides by date/time --- - if ( count( $new_scheds ) > 0 ) { - $date_scheds = $sorted_scheds = array(); - foreach ( $new_scheds as $new_sched ) { - $date_scheds[$new_sched['date']][] = $new_sched; - } - // --- sort date_scheds by date keys --- - ksort( $date_scheds ); - foreach ( $date_scheds as $date => $scheds ) { - $sorted_scheds = array(); - foreach( $scheds as $i => $sched ) { - // --- sort by start time --- - $start = $sched['start_hour'] . ':' . $sched['start_min'] . $sched['start_meridian']; - $time = radio_station_convert_hour( $start, 24 ); - $timestamp = radio_station_to_time( $date . ' ' . $time ); - - // --- deduplicate based on timestamp --- - if ( isset( $sorted_sheds[$timestamp] ) ) { - while( isset( $sorted_scheds[$timestamp] ) ) { - $timestamp++; + // --- sort schedule overrides by date/time --- + if ( count( $new_scheds ) > 0 ) { + $date_scheds = $sorted_scheds = array(); + foreach ( $new_scheds as $new_sched ) { + $date_scheds[$new_sched['date']][] = $new_sched; + } + // --- sort date_scheds by date keys --- + ksort( $date_scheds ); + foreach ( $date_scheds as $date => $scheds ) { + $sorted_scheds = array(); + foreach( $scheds as $i => $sched ) { + // --- sort by start time --- + $start = $sched['start_hour'] . ':' . $sched['start_min'] . $sched['start_meridian']; + $time = radio_station_convert_hour( $start, 24 ); + $timestamp = radio_station_to_time( $date . ' ' . $time ); + + // --- deduplicate based on timestamp --- + if ( isset( $sorted_sheds[$timestamp] ) ) { + while( isset( $sorted_scheds[$timestamp] ) ) { + $timestamp++; + } + $sched['disabled'] = 'yes'; } - $sched['disabled'] = 'yes'; + $sorted_scheds[$timestamp] = $sched; } - $sorted_scheds[$timestamp] = $sched; + ksort( $sorted_scheds ); + $date_scheds[$date] = $sorted_scheds; } - ksort( $sorted_scheds ); - $date_scheds[$date] = $sorted_scheds; - } - $new_scheds = array(); - foreach ( $date_scheds as $date => $scheds ) { - foreach ( $scheds as $sched ) { - $new_scheds[] = $sched; + $new_scheds = array(); + foreach ( $date_scheds as $date => $scheds ) { + foreach ( $scheds as $sched ) { + $new_scheds[] = $sched; + } } } - } - // --- save schedule setting if changed --- - // 2.3.0: check if changed before saving - if ( $sched_changed ) { - update_post_meta( $post_id, 'show_override_sched', $new_scheds ); - - // 2.5.18: add filter for override saving - $new_scheds = apply_filters( 'radio_station_save_override_shifts', $new_scheds, $post_id ); - - // 2.3.3.9: clear out old unique shift IDs from prev_scheds - // 2.5.6: added isset check for prev_scheds - $new_ids = array(); - if ( isset( $prev_scheds ) && is_array( $prev_scheds ) && ( count( $prev_scheds ) > 0 ) ) { - if ( is_array( $new_scheds ) && ( count( $new_scheds ) > 0 ) ) { - foreach ( $new_scheds as $i => $new_sched ) { - $new_ids[] = $new_sched['id']; + // --- save schedule setting if changed --- + // 2.3.0: check if changed before saving + if ( $sched_changed ) { + update_post_meta( $post_id, 'show_override_sched', $new_scheds ); + + // 2.5.18: add filter for override saving + $new_scheds = apply_filters( 'radio_station_save_override_shifts', $new_scheds, $post_id ); + + // 2.3.3.9: clear out old unique shift IDs from prev_scheds + // 2.5.6: added isset check for prev_scheds + $new_ids = array(); + if ( isset( $prev_scheds ) && is_array( $prev_scheds ) && ( count( $prev_scheds ) > 0 ) ) { + if ( is_array( $new_scheds ) && ( count( $new_scheds ) > 0 ) ) { + foreach ( $new_scheds as $i => $new_sched ) { + $new_ids[] = $new_sched['id']; + } } - } - $prev_ids = array(); - foreach ( $prev_scheds as $i => $shift ) { - if ( isset( $shift['id'] ) && !in_array( $shift['id'], $new_ids ) ) { - $prev_ids[] = $shift['id']; + $prev_ids = array(); + foreach ( $prev_scheds as $i => $shift ) { + if ( isset( $shift['id'] ) && !in_array( $shift['id'], $new_ids ) ) { + $prev_ids[] = $shift['id']; + } } - } - if ( count( $prev_ids ) > 0 ) { - $unique_ids = get_option( 'radio_station_shifts_ids' ); - foreach ( $unique_ids as $i => $unique_id ) { - if ( in_array( $unique_id, $prev_ids ) ) { - unset( $unique_ids[$i] ); + if ( count( $prev_ids ) > 0 ) { + $unique_ids = get_option( 'radio_station_shifts_ids' ); + foreach ( $unique_ids as $i => $unique_id ) { + if ( in_array( $unique_id, $prev_ids ) ) { + unset( $unique_ids[$i] ); + } } + update_option( 'radio_station_shifts_ids', $unique_ids ); } - update_option( 'radio_station_shifts_ids', $unique_ids ); } } } - } } @@ -4938,6 +4971,10 @@ function radio_station_override_column_data( $column, $post_id ) { if ( 'override_times' == $column ) { // 2.3.3.9: loop possible multiple overrides + // 2.5.18: bug out if no overrides + if ( !$overrides ) { + return; + } foreach ( $overrides as $i => $override ) { if ( count( $overrides ) > 1 ) { @@ -4981,6 +5018,11 @@ function radio_station_override_column_data( $column, $post_id ) { } elseif ( 'shows_affected' == $column ) { + // 2.5.18: bug out if no overrides + if ( !$overrides ) { + return; + } + // --- maybe get all show shifts --- if ( isset( $radio_station_show_shifts ) ) { $show_shifts = $radio_station_show_shifts; @@ -5781,7 +5823,8 @@ function radio_station_playlist_show_metabox() { // 2.2.8: remove strict argument from in_array checking // 2.3.0: added check for new Show Editor role // 2.3.0: added check for edit_others_shows capability - if ( !in_array( 'administrator', $user->roles ) && !in_array( 'show-editor', $user->roles ) && !current_user_can( 'edit_others_shows' ) ) { + // 2.5.18: added check for editor role + if ( !in_array( 'administrator', $user->roles ) && !in_array( 'show-editor', $user->roles ) && !in_array( 'editor', $user->roles ) && !current_user_can( 'edit_others_shows' ) ) { // --- get the user lists for all shows --- // 2.5.0: shorten query to one line @@ -7384,18 +7427,23 @@ function radio_station_relogin_message() { function radio_station_show_select_options( $type, $selected, $shows = false ) { global $radio_show_select_options, $post; - if ( isset( $radio_show_select_options ) ) { - return $radio_show_select_options; + if ( isset( $radio_show_select_options[$type] ) ) { + return $radio_show_select_options[$type]; } $stored_post = $post; // --- check for full permissions --- // 2.5.18: changed to check permissions based on type + // 2.5.18: also add missing editor cap check $user = wp_get_current_user(); + $editor_caps = radio_station_get_setting( 'add_editor_capabilities' ); $full_permissions = false; if ( in_array( 'administrator', $user->roles ) || in_array( 'show-editor', $user->roles ) ) { $full_permissions = true; - } elseif ( current_user_can( 'edit_others_' . $type . 's' ) ) { + } elseif ( ( 'yes' == $editor_caps ) && in_array( 'editor', $user->roles ) ) { + $full_permissions = true; + } elseif ( ( 'override' != $type ) && current_user_can( 'edit_others_' . $type . 's' ) ) { + // 2.5.1.8: remove full permissions for overrides $full_permissions = true; } @@ -7418,7 +7466,19 @@ function radio_station_show_select_options( $type, $selected, $shows = false ) { $html = ''; $sundays = $mondays = $tuesdays = $wednesdays = $thursdays = $fridays = $saturdays = $shiftless = $inactives = array(); foreach ( $shows as $i => $show ) { + $show_id = $show->ID; + + // 2.5.18: add selected options to top + if ( in_array( $show_id, $selected ) ) { + $html .= '' . "\n"; + } + $active = get_post_meta( $show_id, 'show_active', true ); if ( 'on' != $active ) { $inactives[$show_id] =array( 'show' => $show, 'shift' => $shift, 'inactive' => true ); @@ -7502,17 +7562,17 @@ function radio_station_show_select_options( $type, $selected, $shows = false ) { // 2.5.18: check for permission on individual show $permission = $full_permissions ? true : current_user_can( 'edit_show', $show_id ); - // if ( ( 'override' != $type ) || $permission ) { + // 2.5.18: move selected to top + if ( !in_array( $show_id, $selected ) ) { $html .= '

    ' . "\n"; // 1.2.5: remove reset onclick attribute - $buttons .= '' . "\n"; + $buttons .= '' . "\n"; $buttons .= '' . "\n"; $buttons .= '' . "\n"; $buttons .= '
    ' . "\n"; + + // --- Schedule heading --- + echo '' . "\n"; + + // --- Schedule percentage line --- + $schedule_percent = $total_times = 0; + $schedule = radio_station_get_current_schedule(); + if ( $schedule && is_array( $schedule ) && ( count( $schedule ) > 0 ) ) { + // print_r( $schedule ); + foreach ( $schedule as $day => $shifts ) { + foreach ( $shifts as $time => $shift ) { + $start_time = strtotime( $shift['date'] . ' ' . $shift['start'] ); + $end_time = strtotime( $shift['date'] . ' ' . $shift['end'] ); + if ( $start_time > $end_time ) { + $end_time = $end_time + 86400; + } + $shift_length = $end_time - $start_time; + // echo $shift['start'] . ' - ' . $shift['end'] . ' - ' . $start_time . ' - ' . $end_time . ' - '; + // echo $shift_length . '
    '; + $total_times = $total_times + $shift_length; } - $shift_length = $end_time - $start_time; - // echo $shift['start'] . ' - ' . $shift['end'] . ' - ' . $start_time . ' - ' . $end_time . ' - '; - // echo $shift_length . '
    '; - $total_times = $total_times + $shift_length; } + $week_in_seconds = 86400 * 7; + // echo 'TOTAL:' . $total_times . ' of ' . $week_in_seconds . '
    '; + $schedule_percent = round( ( $total_times / $week_in_seconds ), 2, PHP_ROUND_HALF_DOWN ) * 100; } - $week_in_seconds = 86400 * 7; - // echo 'TOTAL:' . $total_times . ' of ' . $week_in_seconds . '
    '; - $schedule_percent = round( ( $total_times / $week_in_seconds ), 2, PHP_ROUND_HALF_DOWN ) * 100; - } - echo '
    ' . "\n"; + echo '
    ' . "\n"; - // --- progress icon --- - echo '
    ' . "\n"; + // --- progress icon --- + echo '' . "\n"; - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $shift_count ) . ''; - echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + echo '
    ' . "\n"; - // --- schedule editor --- - if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { - $schedule_url = add_query_arg( 'page', 'radio-schedule', admin_url( 'admin.php' ) ); - $schedule_title = __( 'Visual Schedule Editor', 'radio-station' ); - $schedule_text = __( 'Schedule', 'radio-station' ); - } else { - $schedule_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - $schedule_title = __( 'Edit Shows', 'radio-station' ); - $schedule_text = __( 'Shows', 'radio-station' ); - } - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( $schedule_text ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - echo '' . "\n"; - - // --- shift conflicts line --- - /* $conflict_percent = 0; // TEMP - - $conflicts = radio_station_check_shifts(); - echo 'Conflicts: ' . print_r( $conflicts, true ) . '
    '; - - // TODO: check for shift conflicts and convert to % - $schedule_percent = $schedule_percent - $conflict_percent; - - echo '
    ' . "\n"; - echo '
    ' . "\n"; - - */ - - // --- schedule progress bar --- - $conflict_percent = 0; - radio_station_progress_bar( $schedule_percent, $conflict_percent ); - - // --- Content heading --- - echo '
    ' . esc_html( 'Station Content', 'radio-station' ) . '
    ' . "\n"; - - // --- Shows --- - $content_items++; - // $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - // $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $active_publish_percent = round( ( $active_count / $publish_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + // --- schedule editor --- + echo '
    ' . "\n"; + + echo '' . "\n"; - echo '
    ' . "\n"; + // --- shift conflicts line --- + /* $conflict_percent = 0; // TEMP - // --- show count --- - echo '' . esc_html( $publish_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); - - // --- show draft count --- - $drafts_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - $drafts_link = add_query_arg( 'post_status', 'draft', $drafts_link ); - if ( $draft_count > 0 ) { - echo ' (' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - echo ''; - echo esc_html( $draft_count ) . ' ' . esc_html( __( 'Drafts', 'radio-station' ) ); - echo ') '; - } - - // --- show active percentage --- - // TODO: filter show post link for inactive shows - $inactive_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - $inactive_link = add_query_arg( 'post_status', 'inactive', $inactive_link ); - echo ' ' . esc_html( __( 'with', 'radio-station' ) ) . ' '; - if ( $active_publish_percent < 100 ) { - echo ''; - } - echo ' ' . esc_html( $active_publish_percent ) . '% '; - echo esc_html( __( 'Active', 'radio-station' ) ); - if ( $active_publish_percent < 100 ) { - echo ''; - } - echo '.' . "\n"; - - /* if ( 0 == $show_desc_empty ) { - $desc_percent = 100; - } else { - $empty_desc_percent = round( ( $show_desc_empty / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $desc_percent = 100 - $empty_desc_percent; - } - echo ' ' . esc_html( $desc_percent ) . '% '; - // TODO: link to Show list - filtered by lack of description - // $show_desc_url = admin_url( '' ); - // echo ''; - echo esc_html( 'Description coverage', 'radio-station' ) . '.'; - // echo ''; */ + $conflicts = radio_station_check_shifts(); + echo 'Conflicts: ' . print_r( $conflicts, true ) . '
    '; + + // TODO: check for shift conflicts and convert to % + $schedule_percent = $schedule_percent - $conflict_percent; + echo '
    ' . "\n"; echo '
    ' . "\n"; - - // --- add show icon --- - $add_show_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'post-new.php' ) ); - echo ''; - echo ''; - echo ''; - echo ''; - echo '
    ' . esc_html( __( 'Show', 'radio-station' ) ) . '
    '; - echo '
    ' . "\n"; - - echo '
    ' . "\n"; - // --- Descriptions --- - $content_items++; + */ - // --- check shows and override description percentage --- - if ( ( 0 == $show_desc_empty ) && ( 0 == $override_desc_empty ) ) { - $desc_percent = 100; - } else { - $empty_count = $show_desc_empty + $override_desc_empty; - $total_count = $show_count + $override_count; - $empty_desc_percent = round( ( $empty_count / $total_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $desc_percent = 100 - $empty_desc_percent; - } + // --- schedule progress bar --- + echo '' . "\n"; - echo '
    ' . "\n"; + // --- Content heading --- + echo '
    ' . "\n"; - // --- progress icon --- - echo '
    ' . "\n"; + // --- Shows --- + $content_items++; + // $active_percent = round( ( $active_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + // $publish_percent = round( ( $publish_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $active_publish_percent = round( ( $active_count / $publish_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '' . "\n"; - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); - if ( $override_count > 0 ) { - echo ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - echo '' . esc_html( $override_count ) . ''; - echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); + // --- progress icon --- + echo '
    ' . "\n"; - echo ' ' . esc_html( __( 'with',' radio-station' ) ) . ' '; - echo ' ' . esc_html( $desc_percent ) . '% '; - // TODO: link to Show list - filtered by lack of description - $show_desc_url = admin_url( 'edit.php' ); - $show_desc_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, $show_desc_url ); - $show_desc_url = add_query_arg( 'description', 'empty', $show_desc_url ); - echo ''; - echo esc_html( 'Description coverage', 'radio-station' ) . '.'; - echo ''; + echo '' . "\n"; + + // --- add show icon --- + echo '' . "\n"; + + echo '' . "\n"; - echo '' . "\n"; - - // --- add show icon --- - $add_override_url = add_query_arg( 'post_type', RADIO_STATION_OVERRIDE_SLUG, admin_url( 'post-new.php' ) ); - echo ''; - echo ''; - echo ''; - echo ''; - echo '
    ' . esc_html( __( 'Special', 'radio-station' ) ) . '
    '; - echo '
    ' . "\n"; - - echo '' . "\n"; - - // --- Hosts --- - $content_items++; - $host_percent = round( ( $shows_host_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $host_count ) . ''; - echo ' ' . esc_html( __( 'Hosts', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); - - echo ' ' . esc_html( $active_show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); - - // --- show active percentage --- - // TODO: link to shows list - without hosts filter - echo ' ' . esc_html( $host_percent ) . '% '; - if ( $host_percent < 100 ) { - $show_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - echo ''; - } - echo esc_html( __( 'Host coverage', 'radio-station' ) ); - if ( $host_percent < 100 ) { - echo ''; + echo '
    ' . "\n"; + + // --- progress icon --- + echo '' . "\n"; - echo '' . "\n"; + echo '' . "\n"; + + // --- add show icon --- + echo '' . "\n"; + + echo '' . "\n"; + + // --- Hosts --- + $content_items++; + $host_percent = round( ( $shows_host_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '' . "\n"; + + // --- progress icon --- + echo '' . "\n"; - echo '' . "\n"; + echo '' . "\n"; - // --- Playlists --- - $content_items++; - echo '
    ' . "\n"; + // --- add host icon --- + echo '
    ' . "\n"; + + echo '' . "\n"; - // --- progress icon --- - echo '
    ' . "\n"; + // --- Genres --- + $content_items++; + $genre_percent = round( ( $genre_show_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + echo '' . "\n"; - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $playlist_count ) . ''; - echo ' ' . esc_html( __( 'Playlists', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + // --- progress icon --- + echo '
    ' . "\n"; - echo ' ' . esc_html( $playlist_show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + echo '' . "\n"; + + // --- add genre icon --- + echo '' . "\n"; + + echo '' . "\n"; - echo ' ' . esc_html( $episode_playlist_count ) . ''; - echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . '.'; + // --- Playlists --- + $content_items++; + echo '' . "\n"; - echo '' . "\n"; + // --- progress icon --- + echo '' . "\n"; - // --- add playlist icon --- - $add_playlist_url = add_query_arg( 'post_type', RADIO_STATION_PLAYLIST_SLUG, admin_url( 'post-new.php' ) ); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Playlist', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - echo '' . "\n"; + echo '' . "\n"; + + // --- add playlist icon --- + echo '' . "\n"; + + echo '' . "\n"; - // --- Episodes --- - $content_items++; - echo '
    ' . "\n"; + // --- Episodes --- + // $content_items++; + echo '
    ' . "\n"; - // --- progress icon --- - echo '
    ' . "\n"; + // --- progress icon --- + echo '' . "\n"; - echo '
    ' . "\n"; - - // --- episode count --- - echo '' . esc_html( $episode_count ) . ''; - echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . ' ' . esc_html( __( 'published for', 'radio-station' ) ); + echo '
    ' . "\n"; - // --- add episode icon --- - if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + // --- add episode icon --- + echo '' . "\n"; + + echo '' . "\n"; + + // --- content progress bar --- + echo '' . "\n"; - // --- content progress bar --- - $progress_percent = round( ( $content_progress / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $undone_percent = round( ( $content_notdone / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; - radio_station_progress_bar( $progress_percent, $undone_percent ); + // --- close table --- + echo '
    ' . "\n"; + echo '
    ' . esc_html( 'Station Schedule', 'radio-station' ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $shift_count ) . ''; + echo ' ' . esc_html( __( 'Shifts', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); - echo ' ' . esc_html( $specials_count ) . ''; - echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); + echo ' ' . esc_html( $specials_count ) . ''; + echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); - // --- coverage percentage --- - echo ' ' . esc_html( __( 'with', 'radio-station' ) ); - echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); - echo '.' . "\n"; + // --- coverage percentage --- + echo ' ' . esc_html( __( 'with', 'radio-station' ) ); + echo ' ' . esc_html( $schedule_percent ) . '% ' . esc_html( __( 'Schedule coverage', 'radio-station' ) ); + echo '.' . "\n"; - echo '' . "\n"; + echo '' . "\n"; + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + $schedule_url = add_query_arg( 'page', 'radio-schedule', admin_url( 'admin.php' ) ); + $schedule_title = __( 'Visual Schedule Editor', 'radio-station' ); + $schedule_text = __( 'Schedule', 'radio-station' ); + } else { + $schedule_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $schedule_title = __( 'Edit Shows', 'radio-station' ); + $schedule_text = __( 'Shows', 'radio-station' ); + } + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( $schedule_text ) . '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + $conflict_percent = 0; + radio_station_progress_bar( $schedule_percent, $conflict_percent ); + echo '
    ' . "\n"; + echo '
    ' . esc_html( 'Station Content', 'radio-station' ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $publish_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); + + // --- show draft count --- + $drafts_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $drafts_link = add_query_arg( 'post_status', 'draft', $drafts_link ); + if ( $draft_count > 0 ) { + echo ' (' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + echo ''; + echo esc_html( $draft_count ) . ' ' . esc_html( __( 'Drafts', 'radio-station' ) ); + echo ') '; + } + + // --- show active percentage --- + // TODO: filter show post link for inactive shows + $inactive_link = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $inactive_link = add_query_arg( 'post_status', 'inactive', $inactive_link ); + echo ' ' . esc_html( __( 'with', 'radio-station' ) ) . ' '; + if ( $active_publish_percent < 100 ) { + echo ''; + } + echo ' ' . esc_html( $active_publish_percent ) . '% '; + echo esc_html( __( 'Active', 'radio-station' ) ); + if ( $active_publish_percent < 100 ) { + echo ''; + } + echo '.' . "\n"; + + /* if ( 0 == $show_desc_empty ) { + $desc_percent = 100; + } else { + $empty_desc_percent = round( ( $show_desc_empty / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $desc_percent = 100 - $empty_desc_percent; + } + echo ' ' . esc_html( $desc_percent ) . '% '; + // TODO: link to Show list - filtered by lack of description + // $show_desc_url = admin_url( '' ); + // echo ''; + echo esc_html( 'Description coverage', 'radio-station' ) . '.'; + // echo ''; */ + + echo '' . "\n"; + $add_show_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'post-new.php' ) ); + echo ''; + echo ''; + echo ''; + echo ''; + echo '
    ' . esc_html( __( 'Show', 'radio-station' ) ) . '
    '; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ); + if ( $override_count > 0 ) { + echo ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + echo '' . esc_html( $override_count ) . ''; + echo ' ' . esc_html( __( 'Specials', 'radio-station' ) ); + } - // --- add host icon --- - $add_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'users.php' ) ); - if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { - $add_host_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $add_host_url = add_query_arg( 'tab', 'roles', $add_host_url ); - } - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    '; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Host', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - echo '' . "\n"; - - // --- Genres --- - $content_items++; - $genre_percent = round( ( $genre_show_count / $show_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + echo ' ' . esc_html( __( 'with',' radio-station' ) ) . ' '; + echo ' ' . esc_html( $desc_percent ) . '% '; + // TODO: link to Show list - filtered by lack of description + $show_desc_url = admin_url( 'edit.php' ); + $show_desc_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, $show_desc_url ); + $show_desc_url = add_query_arg( 'description', 'empty', $show_desc_url ); + echo ''; + echo esc_html( 'Description coverage', 'radio-station' ) . '.'; + echo ''; - echo '
    ' . "\n"; - - // --- show count --- - echo '' . esc_html( $genre_count ) . ''; - echo ' ' . esc_html( __( 'Genres', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); - - echo ' ' . esc_html( $active_show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); - - // --- show active percentage --- - echo ' ' . esc_html( $genre_percent ) . '% '; - if ( $genre_percent < 100 ) { - // TODO: link to shows list - without genres filter - $show_genres_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); - $show_genres_url = add_query_arg( 'genre', 'none', $show_genres_url ); - echo ''; - } - echo esc_html( __( 'Genre coverage', 'radio-station' ) ); - if ( $genre_percent < 100 ) { + echo '
    ' . "\n"; + $add_override_url = add_query_arg( 'post_type', RADIO_STATION_OVERRIDE_SLUG, admin_url( 'post-new.php' ) ); + echo ''; + echo ''; echo ''; + echo ''; + echo '
    ' . esc_html( __( 'Special', 'radio-station' ) ) . '
    '; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $host_count ) . ''; + echo ' ' . esc_html( __( 'Hosts', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $active_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show active percentage --- + // TODO: link to shows list - without hosts filter + echo ' ' . esc_html( $host_percent ) . '% '; + if ( $host_percent < 100 ) { + $show_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + echo ''; + } + echo esc_html( __( 'Host coverage', 'radio-station' ) ); + if ( $host_percent < 100 ) { + echo ''; + } + echo '.' . "\n"; - // --- add genre icon --- - $add_genre_url = add_query_arg( 'taxonomy', RADIO_STATION_GENRES_SLUG, admin_url( 'edit-tags.php' ) ); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Genre', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - echo '' . "\n"; + echo '
    ' . "\n"; + $add_host_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'users.php' ) ); + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + $add_host_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $add_host_url = add_query_arg( 'tab', 'roles', $add_host_url ); + } + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Host', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $genre_count ) . ''; + echo ' ' . esc_html( __( 'Genres', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $active_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'with', 'radio-station' ) ); + + // --- show active percentage --- + echo ' ' . esc_html( $genre_percent ) . '% '; + if ( $genre_percent < 100 ) { + // TODO: link to shows list - without genres filter + $show_genres_url = add_query_arg( 'post_type', RADIO_STATION_SHOW_SLUG, admin_url( 'edit.php' ) ); + $show_genres_url = add_query_arg( 'genre', 'none', $show_genres_url ); + echo ''; + } + echo esc_html( __( 'Genre coverage', 'radio-station' ) ); + if ( $genre_percent < 100 ) { + echo ''; + } + echo '.' . "\n"; + + echo '' . "\n"; + $add_genre_url = add_query_arg( 'taxonomy', RADIO_STATION_GENRES_SLUG, admin_url( 'edit-tags.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Genre', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- show count --- + echo '' . esc_html( $playlist_count ) . ''; + echo ' ' . esc_html( __( 'Playlists', 'radio-station' ) ) . ' ' . esc_html( __( 'assigned to', 'radio-station' ) ); + + echo ' ' . esc_html( $playlist_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . ' ' . esc_html( __( 'and', 'radio-station' ) ); + + echo ' ' . esc_html( $episode_playlist_count ) . ''; + echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . '.'; + + echo '' . "\n"; + $add_playlist_url = add_query_arg( 'post_type', RADIO_STATION_PLAYLIST_SLUG, admin_url( 'post-new.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Playlist', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- episode count --- + echo '' . esc_html( $episode_count ) . ''; + echo ' ' . esc_html( __( 'Episodes', 'radio-station' ) ) . ' ' . esc_html( __( 'published for', 'radio-station' ) ); - // --- episode show count --- - echo ' ' . esc_html( $episode_show_count ) . ''; - echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . '.'; + // --- episode show count --- + echo ' ' . esc_html( $episode_show_count ) . ''; + echo ' ' . esc_html( __( 'Shows', 'radio-station' ) ) . '.'; - echo '' . "\n"; + echo '' . "\n"; + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { - $add_episode_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'post-new.php' ) ); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    '; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Episode', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - - } else { - // --- upgrade to Pro / feature link --- - $upgrade_url = radio_station_get_upgrade_url(); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    '; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; - } - - echo '' . "\n"; + $add_episode_url = add_query_arg( 'post_type', RADIO_STATION_EPISODE_SLUG, admin_url( 'post-new.php' ) ); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Episode', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + + } else { + // --- upgrade to Pro / feature link --- + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    '; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + } + echo '
    ' . "\n"; + $progress_percent = round( ( $content_progress / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $undone_percent = round( ( $content_notdone / $content_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + radio_station_progress_bar( $progress_percent, $undone_percent ); + echo '
    ' . "\n"; } @@ -818,397 +849,414 @@ function radio_station_settings_panel() { // --- settings progress --- $settings_items = $settings_notdone = $settings_progress = 0; - - // --- settings checks --- - echo '
    ' . esc_html( 'Station Settings', 'radio-station' ) . '
    ' . "\n"; - - // --- Stream URLs --- - $settings_items++; - $settings_tab = 'general'; - $settings_section = 'broadcast'; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; - echo '
    ' . "\n"; - - // --- stream URL --- - echo esc_html( __( 'Stream URL', 'radio-station' ) ) . ' '; - if ( '' != $settings['streaming_url'] ) { - echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; - } else { - echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; - } + echo '' . "\n"; + + // --- settings checks --- + echo '' . "\n"; - // --- fallback URL --- - echo ' ' . esc_html( __( 'Fallback stream URL', 'radio-station' ) ) . ' '; - if ( '' != $settings['fallback_url'] ) { - echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; - } else { - echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; - } + // --- Stream URLs --- + $settings_items++; + $settings_tab = 'general'; + $settings_section = 'broadcast'; + echo '' . "\n"; - echo '' . "\n"; - - // --- fix setting icon --- - if ( 'dashboard' == $context ) { - $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); - $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); - } else { - $fix_setting_url = 'javascript:void(0)'; - } - echo ''; - echo ''; - echo '' . "\n"; - echo ''; - if ( ( '' == $settings['streaming_url'] ) || ( '' == $settings['fallback_url'] ) ) { - echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; - } else { - echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; - } - echo '
    ' . "\n"; - - echo '' . "\n"; - - // --- Player Bar --- - $settings_items++; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + echo '">
    ' . "\n"; - echo '
    ' . "\n"; - - // --- player bar --- - if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { - echo esc_html( __( 'Player Bar with continuous playback available in Pro version.', 'radio-station' ) ); - } else { + echo '
    ' . "\n"; + + // --- fix setting icon --- + echo '' . "\n"; + + echo '' . "\n"; - echo '' . "\n"; + // --- Player Bar --- + $settings_items++; + echo '' . "\n"; + + // --- progress icon --- + echo '' . "\n"; + + echo '' . "\n"; + + // --- fix setting icon --- + echo '' . "\n"; + + echo '' . "\n"; + + // --- Schedule Page --- + $settings_items++; + echo '' . "\n"; - echo '' . "\n"; - - // --- fix setting icon --- - $settings_tab = 'pages'; - $settings_section = 'schedule'; - if ( 'dashboard' == $context ) { - $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); - $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); - } else { - $fix_setting_url = 'javascript:void(0)'; - } - echo ''; - echo ''; - echo ''; - echo ''; + // --- progress icon --- + echo '' . "\n"; - echo '
    ' . "\n"; + echo '
    ' . "\n"; + + echo '' . "\n"; + + // --- Station Info --- + $settings_items++; + $infos = array( 'station_title', 'station_image', 'station_frequency', 'station_location', 'station_phone', 'station_email' ); + $infos_count = count( $infos ); + $info_count = 0; + foreach ( $infos as $info ) { + if ( isset( $settings[$info] ) && ( '' != $settings[$info] ) ) { + $info_count++; + } } - echo '>'; - echo ''; - echo ''; - echo ''; + $info_percent = round( ( $info_count / $infos_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + + echo '' . "\n"; + + // --- progress icon --- + echo '' . "\n"; - echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- fix setting icon --- + echo '' . "\n"; - echo esc_html( $archive_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $archives_count ); - echo ' ' . esc_html( __( 'Archive pages set', 'radio-station' ) ) . '. '; - echo esc_html( $archive_percent ) . '% ' . esc_html( __( 'Archive coverage', 'radio-station' ) ) . '.'; + echo ''; - echo '' . "\n"; - - // --- fix setting icon --- - $settings_tab = 'archives'; - $settings_section = 'post-types'; - if ( 'dashboard' == $context ) { - $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); - $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); - } else { - $fix_setting_url = 'javascript:void(0)'; + + // --- Archive Pages --- + $settings_items++; + $archives = array( 'show', 'override', 'playlist', 'genre', 'language' ); + $pro_archives = array( 'episode', 'team' ); + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + $archives = array_merge( $archives, $pro_archives ); } - echo ''; - echo ''; - echo '' . "\n"; - echo ''; + $archive_percent = round( ( $archive_count / $archives_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; + + echo '' . "\n"; + + // --- progress icon --- + echo '' . "\n"; - // --- Pro Archives Pages --- - if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { - - $settings_items++; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + echo '
    ' . "\n"; - // --- upgrade to Pro / feature link --- - $upgrade_url = radio_station_get_upgrade_url(); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; + // --- fix setting icon --- + echo '' . "\n"; - echo '' . "\n"; - } + echo '' . "\n"; + + // --- Pro Archives Pages --- + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { + + $settings_items++; + echo '' . "\n"; + + // --- progress icon --- + echo '' . "\n"; + + echo '' . "\n"; + + // --- upgrade to Pro / feature link --- + echo '' . "\n"; + + echo '' . "\n"; + } - // ... + // ... - // --- settings progress bar --- - $progress_percent = round( ( $settings_progress / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; - $undone_percent = round( ( $settings_notdone / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; - radio_station_progress_bar( $progress_percent, $undone_percent ); + // --- settings progress bar --- + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . esc_html( 'Station Settings', 'radio-station' ) . '
    ' . "\n"; + echo '
    ' . "\n"; - if ( 'top' == $settings['player_bar'] ) { - echo esc_html( __( 'Header', 'radio-station' ) ) . ' '; - } elseif ( 'bottom' == $settings['player_bar'] ) { - echo esc_html( __( 'Footer', 'radio-station' ) ) . ' '; + // --- stream URL --- + echo esc_html( __( 'Stream URL', 'radio-station' ) ) . ' '; + if ( '' != $settings['streaming_url'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; } - echo esc_html( __( 'Player Bar', 'radio-station' ) ) . ' '; - if ( 'off' == $settings['player_bar'] ) { - echo esc_html( __( 'not enabled', 'radio-station' ) ) . '. '; + + // --- fallback URL --- + echo ' ' . esc_html( __( 'Fallback stream URL', 'radio-station' ) ) . ' '; + if ( '' != $settings['fallback_url'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . '. '; } else { - echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; + echo esc_html( __( 'not set', 'radio-station' ) ) . '. '; } - // --- continuous playback --- - echo esc_html( __( 'Continuous playback', 'radio-station' ) ) . ' '; - if ( 'yes' == $settings['player_bar_continuous'] ) { - echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; + echo '' . "\n"; + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); } else { - echo esc_html( __( 'disabled', 'radio-station' ) ) . '. '; + $fix_setting_url = 'javascript:void(0)'; } - } + echo ''; + echo ''; + echo '' . "\n"; + echo ''; + if ( ( '' == $settings['streaming_url'] ) || ( '' == $settings['fallback_url'] ) ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    ' . "\n"; + echo '
    ' . "\n"; + + // --- player bar --- + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { + echo esc_html( __( 'Player Bar with continuous playback available in Pro version.', 'radio-station' ) ); + } else { - // --- fix setting icon --- - if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { + if ( 'top' == $settings['player_bar'] ) { + echo esc_html( __( 'Header', 'radio-station' ) ) . ' '; + } elseif ( 'bottom' == $settings['player_bar'] ) { + echo esc_html( __( 'Footer', 'radio-station' ) ) . ' '; + } + echo esc_html( __( 'Player Bar', 'radio-station' ) ) . ' '; + if ( 'off' == $settings['player_bar'] ) { + echo esc_html( __( 'not enabled', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; + } - $upgrade_url = radio_station_get_upgrade_url(); - echo '' . "\n"; - echo '
    ' . "\n"; - echo '
    ' . "\n"; - echo '' . "\n"; - echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; - echo '
    ' . "\n"; + // --- continuous playback --- + echo esc_html( __( 'Continuous playback', 'radio-station' ) ) . ' '; + if ( 'yes' == $settings['player_bar_continuous'] ) { + echo esc_html( __( 'enabled', 'radio-station' ) ) . '. '; + } else { + echo esc_html( __( 'disabled', 'radio-station' ) ) . '. '; + } + } - } else { + echo '
    ' . "\n"; + if ( !defined( 'RADIO_STATION_PRO_FILE' ) ) { + + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; - $settings_tab = 'player'; - $settings_section = 'bar'; - if ( 'dashboard' == $context ) { - $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); - $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); } else { - $fix_setting_url = 'javascript:void(0)'; - } - echo ''; - echo ''; - echo '' . "\n"; - echo ''; - if ( ( 'off' == $settings['player_bar'] ) || ( 'yes' != $settings['player_bar_continuous'] ) ) { - echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + $settings_tab = 'player'; + $settings_section = 'bar'; + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); } else { - echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + $fix_setting_url = 'javascript:void(0)'; } - echo '
    ' . "\n"; - } - - echo '' . "\n"; - - // --- Schedule Page --- - $settings_items++; - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; - - echo '
    ' . "\n"; - - // --- stream URL --- - echo ' ' . esc_html( __( 'Master Schedule Page', 'radio-station' ) ) . ' '; - if ( '' != $settings['schedule_page'] ) { - echo esc_html( __( 'is set', 'radio-station' ) ) . ' '; - - // --- automatic schedule display --- - if ( 'yes' == $settings['schedule_auto'] ) { - echo esc_html( __( 'to display automatically', 'radio-station' ) ) . '.'; - } else { - // TODO: check for shortcode on schedule page ? - echo esc_html( __( 'to manual display', 'radio-station' ) ) . '.'; + + echo ''; + echo ''; + echo '' . "\n"; + echo ''; + if ( ( 'off' == $settings['player_bar'] ) || ( 'yes' != $settings['player_bar_continuous'] ) ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    ' . "\n"; } + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + echo 'progress-cross dashicons-dismiss'; + $settings_notdone++; } else { - echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; - } - echo '' . "\n"; - - echo '' . "\n"; - - // --- Station Info --- - $settings_items++; - $infos = array( 'station_title', 'station_image', 'station_frequency', 'station_location', 'station_phone', 'station_email' ); - $infos_count = count( $infos ); - $info_count = 0; - foreach ( $infos as $info ) { - if ( isset( $settings[$info] ) && ( '' != $settings[$info] ) ) { - $info_count++; - } - } - $info_percent = round( ( $info_count / $infos_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + echo 'progress-tick dashicons-yes-alt'; + $settings_progress++; + } // else { + // echo 'progress-alert dashicons-warning'; + // $settings_progress = $settings_progress + 0.5; + // } + echo '">
    ' . "\n"; - echo esc_html( $info_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $infos_count ); - echo ' ' . esc_html( __( 'Station information fields set', 'radio-station' ) ) . '. '; + // --- stream URL --- + echo ' ' . esc_html( __( 'Master Schedule Page', 'radio-station' ) ) . ' '; + if ( '' != $settings['schedule_page'] ) { + echo esc_html( __( 'is set', 'radio-station' ) ) . ' '; + + // --- automatic schedule display --- + if ( 'yes' == $settings['schedule_auto'] ) { + echo esc_html( __( 'to display automatically', 'radio-station' ) ) . '.'; + } else { + // TODO: check for shortcode on schedule page ? + echo esc_html( __( 'to manual display', 'radio-station' ) ) . '.'; + } + } else { + echo esc_html( __( 'not set', 'radio-station' ) ) . '.'; + } - echo '' . "\n"; - - // --- fix setting icon --- - $settings_tab = 'general'; - $settings_section = 'broadcast'; - if ( 'dashboard' == $context ) { - $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); - $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); - } else { - $fix_setting_url = 'javascript:void(0)'; - } - echo '' . "\n"; + + // --- fix setting icon --- + echo '' . "\n"; + $settings_tab = 'pages'; + $settings_section = 'schedule'; + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ( '' == $settings['schedule_page'] ) { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } + echo '
    ' . "\n"; + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + echo 'progress-tick dashicons-yes-alt'; + $settings_progress++; + } elseif ( $info_percent > 49 ) { + echo 'progress-alert dashicons-warning'; + $settings_progress = $settings_progress + 0.5; } else { - echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + echo 'progress-cross dashicons-dismiss'; + $settings_notdone++; } - echo ''; - - echo ''; - - - // --- Archive Pages --- - $settings_items++; - $archives = array( 'show', 'override', 'playlist', 'genre', 'language' ); - $pro_archives = array( 'episode', 'team' ); - if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { - $archives = array_merge( $archives, $pro_archives ); - } - $archives_count = count( $archives ); - $archive_count = 0; - foreach ( $archives as $archive ) { - $key = $archive . '_archive_page'; - if ( '' != $settings[$key] ) { - $archive_count++; - } - } - $archive_percent = round( ( $archive_count / $archives_count ), 2, PHP_ROUND_HALF_DOWN ) * 100; - - echo '
    ' . "\n"; - - // --- progress icon --- - echo '
    ' . "\n"; + echo '">
    ' . "\n"; + echo esc_html( $info_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $infos_count ); + echo ' ' . esc_html( __( 'Station information fields set', 'radio-station' ) ) . '. '; + echo '' . "\n"; + $settings_tab = 'general'; + $settings_section = 'broadcast'; + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo ''; + echo ''; + if ( 100 == $info_percent ) { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    '; + } else { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    '; + } + echo '
    '; + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    ' . "\n"; + echo 'progress-tick dashicons-yes-alt'; + $settings_progress++; + } elseif ( $archive_percent > 49 ) { + echo 'progress-alert dashicons-warning'; + $settings_progress = $settings_progress + 0.5; } else { - echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    ' . "\n"; + echo 'progress-cross dashicons-dismiss'; + $settings_notdone++; } - echo '' . "\n"; - - echo '' . "\n"; + echo '">
    ' . "\n"; + + echo esc_html( $archive_count ) . ' ' . esc_html( __( 'out of', 'radio-station' ) ) . ' ' . esc_html( $archives_count ); + echo ' ' . esc_html( __( 'Archive pages set', 'radio-station' ) ) . '. '; + echo esc_html( $archive_percent ) . '% ' . esc_html( __( 'Archive coverage', 'radio-station' ) ) . '.'; - echo '
    ' . "\n"; - echo esc_html( __( 'Episode and Team Archive pages available in Pro version.', 'radio-station' ) ); - echo '
    ' . "\n"; + echo '
    ' . "\n"; + $settings_tab = 'archives'; + $settings_section = 'post-types'; + if ( 'dashboard' == $context ) { + $fix_setting_url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + $fix_setting_url = add_query_arg( 'tab', $settings_tab, $fix_setting_url ); + $fix_setting_url = add_query_arg( 'section', $settings_section, $fix_setting_url ); + } else { + $fix_setting_url = 'javascript:void(0)'; + } + echo ''; + echo ''; + echo '' . "\n"; + echo ''; + if ( 100 == $archive_percent ) { + echo '
    ' . esc_html( __( 'Edit', 'radio-station' ) ) . '
    ' . "\n"; + } else { + echo '
    ' . esc_html( __( 'Fix', 'radio-station' ) ) . '
    ' . "\n"; + } + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo esc_html( __( 'Episode and Team Archive pages available in Pro version.', 'radio-station' ) ); + echo '' . "\n"; + $upgrade_url = radio_station_get_upgrade_url(); + echo '' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + echo '' . "\n"; + echo '
    ' . esc_html( __( 'Go Pro', 'radio-station' ) ) . '
    ' . "\n"; + echo '
    ' . "\n"; + echo '
    ' . "\n"; + $progress_percent = round( ( $settings_progress / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + $undone_percent = round( ( $settings_notdone / $settings_items ), 2, PHP_ROUND_HALF_DOWN ) * 100; + radio_station_progress_bar( $progress_percent, $undone_percent ); + echo '
    ' . "\n"; } @@ -1259,14 +1307,16 @@ function radio_station_enqueue_onboarding_styles() { // Onboarding Styles // ----------------- function radio_station_onboarding_styles() { - $css = ".progress-heading {text-align:center; font-size:16px; font-variant:small-caps; letter-spacing:3px; + $css = ".progress-table {width: 100%;} + .progress-table td {text-align:center; vertical-align:top;} + .progress-heading {text-align:center; font-size:16px; font-variant:small-caps; letter-spacing:3px; margin-top:10px; margin-bottom:5px; max-width:500px;} .progress-list {margin-bottom:7px;} .progress-list-item {display:inline-block; margin-right:25px;} .progress-list-item:last-child {margin-right:0;} .progress-list-item a {text-decoration:none;} - .progress-list-item a:hover {text-decoration:underline; font-weight:bold;} - .progress-line {font-size:13px;} + .progress-list-item a:hover {font-weight:bold;} + .progress-table td.progress-text {text-align:left; font-size:12px;} .progress-bar {width:96%; height:7px; margin-top:7px; margin-bottom:15px; padding:0;} .progress-bar-label {font-size:13px; font-weight:bold; margin-top:7px; text-indent:15px; line-height:7px;} /* .progress-bar, .progress-bar-done, .progress-bar-remainder, .progress-bar-label {display:inline-block; vertical-align:middle;} */ @@ -1274,21 +1324,36 @@ function radio_station_onboarding_styles() { .progress-bar-remainder {background-color:#FF9900; height:7px; border:1px solid #555;} .progress-bar-undone {background-color:#EE0000; height:7px; border:1px solid #555; border-left:0;} .progress-count {font-weight:bold; font-size:14px; background-color:#CCC; border-radius:10px;} - .progress-icon {width:25px; height:25px;} + .progress-icon {width:25px; height:25px; color:#0077CC;} .progress-icon.progress-tick, .progress-icon.progress-quickstart {color:#00AA00;} .progress-icon.progress-cross {color:#EE0000;} .progress-icon.progress-alert {color:#FF9900;} - .progress-icon.progress-add, .progress-icon.progress-settings {color:#0077CC;} - .progress-icon.progress-upgrade {color:#AA00AA; font-size:22px;} + .progress-icon.progress-add {color:#0077CC;} + .progress-icon.progress-settings {color:#B91245;} .progress-icon.progress-docs {color:#CC7700;} - .progress-text {width:350px;} + .progress-icon.progress-account {color:#AA00AA;} + .progress-icon.progress-upgrade, .progress-icon.progress-docs, .progress-icon.progress-shows, .progress-icon.progress-playlists {font-size:22px; width:22px;} .progress-icon, .progress-text, .progress-icon-link, .progress-label-link {display:inline-block; vertical-align:middle; line-height:25px;} - .progress-label-link {margin-left:5px; margin-top:5px; width:40px; text-align:center;} + .progress-icon-link {margin-left:2px; margin-top:2px;} + .progress-label-link {margin-left:2px; margin-top:2px; width:40px; text-align:center;} .progress-text a, .progress-icon-link, .progress-label-link {text-decoration:none;} .progress-text a:hover, .progress-label-link:hover {text-decoration:underline;} .progress-label {font-size:11px; margin-top:-5px;} "; + if ( isset( $_REQUEST['page'] ) && ( 'radio-content' == $_REQUEST['page'] ) ) { + + $css .= ".progress-table {max-width:550px;} + .progress-heading {font-size: 20px; margin-top:20px; margin-bottom:10px;} + .progress-list-item {font-size:17px;} + .progress-list-item .progress-icon {font-size:30px; width:30px; height:30px;} + .progress-list-item .progress-icon.progress-docs, .progress-list-item .progress-icon.progress-shows, .progress-list-item .progress-icon.progress-playlists {font-size:27px; width:27px;} + .progress-text, .progress-label {font-size:15px;} + + "; + } + + return $css; } diff --git a/options.php b/options.php index ff9c2bd..33f8896 100644 --- a/options.php +++ b/options.php @@ -147,7 +147,7 @@ function radio_station_plugin_options( $admin = false ) { 'default' => '', 'helper' => $admin ? __( 'Your station frequency as a number.', 'radio-station' ) : '', 'tab' => 'general', - 'section' => 'station', + 'section' => 'broadcast', ), // --- Station Band --- @@ -372,6 +372,7 @@ function radio_station_plugin_options( $admin = false ) { ), // --- [Player] Player Title --- + // TODO: option to display callsign instead ? 'player_title' => array( 'type' => 'checkbox', 'label' => $admin ? __( 'Display Station Title', 'radio-station' ) : '', @@ -792,7 +793,7 @@ function radio_station_plugin_options( $admin = false ) { // 'remaining' => $admin ? __( 'Remaining Time', 'radio-station' ) : '', // 'description' => $admin ? __( 'Description', 'radio-station' ) : '', ), - 'default' => 'yes', + 'default' => array( 'hosts', 'shift' ), 'tab' => 'player', 'section' => 'bar', 'helper' => $admin ? __( 'Show meta to display in the Player Bar.', 'radio-station' ) : '', diff --git a/player/radio-player.php b/player/radio-player.php index f974107..dfc2b23 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -250,6 +250,17 @@ function radio_player_output( $args = array(), $echo = false ) { 'default' => false, ); + // 2.5.18: explode string attributes + if ( isset( $atts['meta'] ) && is_string( $atts['meta'] ) && ( '' != $atts['meta'] ) ) { + $atts['meta'] = explode( ',', $atts['meta'] ); + } + if ( isset( $atts['showmeta'] ) && is_string( $atts['showmeta'] ) && ( '' != $atts['meta'] ) ) { + $atts['showmeta'] = explode( ',', $atts['showmeta'] ); + } + if ( isset( $atts['volumes'] ) && is_string( $atts['volumes'] ) && ( '' != $atts['meta'] ) ) { + $atts['volumes'] = explode( ',', $atts['volumes'] ); + } + // --- ensure all arguments are set --- foreach ( $defaults as $key => $value ) { // 2.5.18: allow setting of value to default @@ -398,11 +409,7 @@ function radio_player_output( $args = array(), $echo = false ) { $title_display .= esc_html( $args['title'] ); } $title_display = apply_filters( 'radio_player_station_display', $title_display, $args, $instance ); - $station_meta['title'] = '
    ' . "\n"; + $station_meta['title'] = '
    ' . $title_display . '
    ' . "\n"; // --- station tagline --- // 2.5.18: add tagline display diff --git a/radio-station.php b/radio-station.php index 17cd9cc..afce9cb 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.18 +Version: 2.5.18.1 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index 6e06cff..e1480aa 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3 Tested up to: 6.9 -Stable tag: 2.5.18 +Stable tag: 2.5.18.1 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 11ea464..1eae837 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 Tested up to: 6.9 -Stable tag: 2.5.18 +Stable tag: 2.5.18.1 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. From 7527dd58bb51785995efe6c5e93879bac8289663 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 29 Mar 2026 12:05:55 +1000 Subject: [PATCH 374/377] dev updates 2.5.18.2 --- css/rs-schedule.css | 4 + includes/extra-strings.php | 7 + includes/master-schedule.php | 104 +++++++------- includes/support-functions.php | 2 +- js/radio-station.js | 8 +- radio-station.php | 2 +- readme.md | 2 +- readme.txt | 2 +- scheduler/schedule-engine.php | 207 ++++++++++++++-------------- templates/master-schedule-list.php | 6 +- templates/master-schedule-table.php | 14 +- templates/master-schedule-tabs.php | 13 +- 12 files changed, 199 insertions(+), 172 deletions(-) diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 305512c..ddeff56 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -67,6 +67,10 @@ font-weight: bold; } +.master-genre-list .genre-highlight, .master-genre-list .genre-highlight:hover { + text-decoration: none; +} + .master-genre-list .genre-highlight.highlighted { font-weight: bold; background-color: rgba( 235, 235, 0, 0.4 ); diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 0505d98..e1c0edb 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -30,6 +30,12 @@ __( 'Your station frequency band identifier.', 'radio-station' ); __( 'Station Text', 'radio-station' ); __( 'Text line phone number for the Station (for requests etc.', 'radio-station' ); +__( 'Display Station Meta', 'radio-station' ); +__( 'Tagline', 'radio-station' ); +__( 'Frequency', 'radio-station' ); +__( 'Location', 'radio-station' ); +__( 'Timezone', 'radio-station' ); +__( 'Display your Radio Station Meta in Player by default.', 'radio-station' ); __( 'Display Start Hour', 'radio-station' ); __( 'Midnight', 'radio-station' ); __( 'Noon', 'radio-station' ); @@ -414,6 +420,7 @@ __( 'Save Overrides', 'radio-station' ); __( 'Edit Show Shifts', 'radio-station' ); __( 'Edit Override Times', 'radio-station' ); +__( 'Edit Schedule', 'radio-station' ); __( 'Edit Show Shift', 'radio-station' ); __( 'Edit Override Time', 'radio-station' ); __( 'Add to Schedule', 'radio-station' ); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index d3f9aed..b3088c5 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -42,6 +42,9 @@ function radio_station_master_schedule( $atts ) { } $radio_station_data['schedules']['instances']++; $instances = $radio_station_data['schedules']['instances']; + // if ( RADIO_STATION_DEBUG ) { + echo 'Schedule Instance ' . esc_html( $instances ) . ''; + // } } // --- make attributes backward compatible --- @@ -374,16 +377,10 @@ function radio_station_master_schedule( $atts ) { if ( 'table' == $atts['view'] ) { // 2.5.10.1: added check for specified instance - if ( !isset( $instance ) ) { - // 2.5.0: set view instance number - if ( !isset( $radio_station_data['schedules']['table'] ) ) { - $radio_station_data['schedules']['table'] = -1; - } - $radio_station_data['schedules']['table']++; - $instance = $radio_station_data['schedules']['table']; - } + // 2.5.0: set view instance number // 2.5.6: fix for missing id definition - $id = ( 0 == $instance ) ? '' : '-' . $instance; + // 2.5.18: simplifiy use instance attribute + $id = ( 0 == $atts['instance'] ) ? '' : '-' . $atts['instance'] ; // --- load table view template --- ob_start(); @@ -409,16 +406,10 @@ function radio_station_master_schedule( $atts ) { } elseif ( 'tabs' == $atts['view'] ) { // 2.5.10.1: added check for specified instance - if ( !isset( $instance ) ) { - // 2.5.0: set view instance number - if ( !isset( $radio_station_data['schedules']['tabs'] ) ) { - $radio_station_data['schedules']['tabs'] = -1; - } - $radio_station_data['schedules']['tabs']++; - $instance = $radio_station_data['schedules']['tabs']; - } + // 2.5.0: set view instance number // 2.5.6: fix for missing id definition - $id = ( 0 == $instance ) ? '' : '-' . $instance; + // 2.5.18: simplifiy use instance attribute + $id = ( 0 == $atts['instance'] ) ? '' : '-' . $atts['instance']; // --- load tabs view template --- ob_start(); @@ -444,16 +435,10 @@ function radio_station_master_schedule( $atts ) { } elseif ( 'list' == $atts['view'] ) { // 2.5.10.1: added check for specified instance - if ( !isset( $instance ) ) { - // 2.5.0: set view instance number - if ( !isset( $radio_station_data['schedules']['list'] ) ) { - $radio_station_data['schedules']['list'] = -1; - } - $radio_station_data['schedules']['list']++; - $instance = $radio_station_data['schedules']['list']; - } + // 2.5.0: set view instance number // 2.5.6: fix for missing id definition - $id = ( 0 == $instance ) ? '' : '-' . $instance; + // 2.5.18: simplifiy use instance attribute + $id = ( 0 == $atts['instance'] ) ? '' : '-' . $atts['instance']; // --- load list view template --- ob_start(); @@ -701,21 +686,21 @@ function radio_station_master_schedule_loader_js( $atts ) { newdate = new Date(new Date(startdate).getTime() + offset).toISOString().substr(0,10); timestamp = Math.floor((new Date()).getTime() / 1000); url = '" . esc_url( $loader_url ); - // 2.6.0: add args directly to avoid double escaping - $ignore_keys = array( 'start_date', 'view', 'ajax' ); + // 2.5.0: add these args directly to avoid double escaping + // 2.5.18: ignore channel attribute to check for it explicitly + $ignore_keys = array( 'start_date', 'view', 'ajax', 'channel' ); foreach ( $atts as $key => $value ) { if ( !in_array( $key, $ignore_keys ) ) { $js .= '&' . esc_js( $key ) . '=' . esc_js( $value ); } } - $js .= "&view='+view+'&instance='+instance+'×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; + $js .= "&view='+view+'&instance='+instance+'×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate;"; + // 2.5.18: added optional channel selection value + $js .= "if (jQuery('#channel-select-'+instance).length) {url += '&channel='+jQuery('#channel-select-'+instance).val();} if (startday != '') {url += '&start_day='+startday;} if (activeday != '') {url += '&active_day='+activeday;} if (clear) {url += '&clear=1';} if (radio.debug) {url += '&rs-debug=1'; console.log('Reload View URL: '+url);} - /* if (document.getElementById('schedule-'+view+'-loader').src != url) { - document.getElementById('schedule-'+view+'-loader').src = url; - } */ iframe_id = 'master-schedule-'+view+'-'+instance+'-loader'; iframe = document.getElementById(iframe_id); if (iframe) { @@ -730,42 +715,59 @@ function radio_station_master_schedule_loader_js( $atts ) { }" . "\n"; // 2.5.10.1: added multi-schedule reloader - $js .= "function radio_load_schedules() { + // 2.6.18: added optional instance argument + $js .= "function radio_load_schedules(instance) { tables = jQuery('.master-program-schedule'); tabs = jQuery('.master-schedule-tabs'); lists = jQuery('.master-schedule-list'); grids = jQuery('.master-schedule-grid'); calendars = jQuery('.master-schedule-calendar'); if (tables.length) {tables.each(function() { - instance = jQuery(this).attr('id').replace('master-program-schedule-',''); - if (radio.debug) {console.log('Refreshing Table Instance '+instance);} - radio_load_schedule(instance,false,'table',true); + inst = jQuery(this).attr('id').replace('master-program-schedule','').replace('-',''); + console.log(instance+' - '+inst); + if (inst == '') {inst = 0;} + if (!instance || (parseInt(instance) == parseInt(inst))) { + if (radio.debug) {console.log('Refreshing Table Instance '+inst);} + radio_load_schedule(inst,false,'table',true); + } }); } if (tabs.length) {tabs.each(function() { - instance = jQuery(this).attr('id').replace('master-schedule-tabs-',''); - if (radio.debug) {console.log('Refreshing Tabs Instance '+instance);} - radio_load_schedule(instance,false,'tabs',true); + inst = jQuery(this).attr('id').replace('master-schedule-tabs','').replace('-',''); + if (inst == '') {inst = 0;} + if (!instance || (parseInt(instance) == parseInt(inst))) { + if (radio.debug) {console.log('Refreshing Tabs Instance '+inst);} + radio_load_schedule(inst,false,'tabs',true); + } }); } if (lists.length) {lists.each(function() { - instance = jQuery(this).attr('id').replace('master-schedule-list-',''); - if (radio.debug) {console.log('Refreshing List Instance '+instance);} - radio_load_schedule(instance,false,'list',true); + inst = jQuery(this).attr('id').replace('master-schedule-list','').replace('-',''); + if (inst == '') {inst = 0;} + if (!instance || (parseInt(instance) == parseInt(inst))) { + if (radio.debug) {console.log('Refreshing List Instance '+inst);} + radio_load_schedule(inst,false,'list',true); + } }); } if (grids.length) {grids.each(function() { - instance = jQuery(this).attr('id').replace('master-schedule-grid-',''); - if (radio.debug) {console.log('Refreshing Grid Instance '+instance);} - radio_load_schedule(instance,false,'grid',true); + inst = jQuery(this).attr('id').replace('master-schedule-grid','').replace('-',''); + if (inst == '') {inst = 0;} + if (!instance || (parseInt(instance) == parseInt(inst))) { + if (radio.debug) {console.log('Refreshing Grid Instance '+inst);} + radio_load_schedule(inst,false,'grid',true); + } }); } - if (calendars.length) {calendars.each(function() { - instance = jQuery(this).attr('id').replace('master-schedule-calendar-',''); - if (radio.debug) {console.log('Refreshing Calendar Instance '+instance); - radio_load_schedule(instance,false,'calendar',true);} + if (calendars.length) {calendars.each(function() { + inst = jQuery(this).attr('id').replace('master-schedule-calendar','').replace('-',''); + if (inst == '') {inst = 0;} + if (!instance || (parseInt(instance) == parseInt(inst))) { + if (radio.debug) {console.log('Refreshing Calendar Instance '+inst); + radio_load_schedule(inst,false,'calendar',true);} + } }); } }" . "\n"; // 2.5.10.1: check for AJAX attribute to refresh schedule on pageload if ( isset( $atts['ajax'] ) && $atts['ajax'] ) { - $js .= "jQuery(document).ready(function() {radio_load_schedules();}, 500);" . "\n"; + $js .= "jQuery(document).ready(function() {radio_load_schedules(false);}, 500);" . "\n"; } // --- filter and return --- diff --git a/includes/support-functions.php b/includes/support-functions.php index 85e2c1b..44c896f 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -2330,7 +2330,6 @@ function radio_station_sanitize_playlist_entry( $entry ) { // 2.5.0: updated to match changed shortcode keys function radio_station_sanitize_shortcode_values( $type, $extras = false ) { - // $atts = array(); if ( 'current-show' == $type ) { // --- current show attribute keys --- @@ -2495,6 +2494,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // 2.5.0: added filter for shortcode attribute key types $keys = apply_filters( 'radio_station_shortcode_attribute_key_types', $keys, $type ); + // echo 'Attribute Keys: ' . print_r( $keys, true ) . "\n"; // --- handle extra keys --- if ( $extras && is_array( $extras ) && ( count( $extras ) > 0 ) ) { diff --git a/js/radio-station.js b/js/radio-station.js index ecca1cc..b170a21 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -152,10 +152,10 @@ function radio_timezone_code(timezone) { /* Retrigger Responsive Schedules */ function radio_responsive_schedules() { - if (jQuery('#master-program-schedule').length) {radio_table_responsive(false,false);} - if (jQuery('#master-schedule-tabs').length) {radio_tabs_responsive(false,false);} - if (jQuery('#master-schedule-grid').length) {radio_grid_responsive(false);} - if (jQuery('#master-schedule-calendar').length) {radio_calendar_responsive(false);} + if (jQuery('.master-program-schedule').length) {radio_table_responsive(false,false);} + if (jQuery('.master-schedule-tabs').length) {radio_tabs_responsive(false,false);} + if (jQuery('.master-schedule-grid').length) {radio_grid_responsive(false);} + if (jQuery('.master-schedule-calendar').length) {radio_calendar_responsive(false);} } /* Update Time Displays */ diff --git a/radio-station.php b/radio-station.php index afce9cb..03c3ea3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -6,7 +6,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.5.18.1 +Version: 2.5.18.2 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.md b/readme.md index e1480aa..125a391 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3 Tested up to: 6.9 -Stable tag: 2.5.18.1 +Stable tag: 2.5.18.2 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 1eae837..ad60c58 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Requires at least: 3.3 Tested up to: 6.9 -Stable tag: 2.5.18.1 +Stable tag: 2.5.18.2 Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. diff --git a/scheduler/schedule-engine.php b/scheduler/schedule-engine.php index dd17319..028bbbf 100644 --- a/scheduler/schedule-engine.php +++ b/scheduler/schedule-engine.php @@ -415,53 +415,118 @@ public function get_shift_data( $records, $split, $times ) { if ( $shifts && is_array( $shifts) && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $i => $shift ) { - // 2.3.3.9: set shift ID to key - // 2.5.0: use already set key - if ( !isset( $shift['id'] ) ) { - $shift['id'] = $i; - } - - // --- make sure shift has sufficient info --- - $isdisabled = ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) ? true : false; - $shift = $this->validate_shift( $shift ); + // 2.5.18: match shift channel to specified channel + if ( ( ( '' == $channel ) && ( !isset( $shift['channel'] ) || ( '' == $shift['channel'] ) || ( '0' == $shift['channel'] ) ) ) + || ( ( '' != $channel ) && ( isset( $shift['channel'] ) && ( $channel == $shift['channel'] ) ) ) ) { + + // 2.3.3.9: set shift ID to key + // 2.5.0: use already set key + if ( !isset( $shift['id'] ) ) { + $shift['id'] = $i; + } - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + // --- make sure shift has sufficient info --- + $isdisabled = ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) ? true : false; + $shift = $this->validate_shift( $shift ); - // --- if it was not already disabled, add to shift errors --- - if ( !$isdisabled ) { - $errors[$id][] = $shift; - } + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - } else { + // --- if it was not already disabled, add to shift errors --- + if ( !$isdisabled ) { + $errors[$id][] = $shift; + } - // --- shift is valid so continue checking --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first - // # midnight calculation - $day = $shift['day']; - $thisdate = $weekdates[$day]; - $midnight = $this->to_time( $thisdate . ' 23:59:59', $timezone ) + 1; - $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - $start_time = $this->convert_shift_time( $start ); - $start_time = $this->to_time( $thisdate . ' ' . $start_time, $timezone ); - if ( ( '11:59:59 pm' == $end ) || ( '12:00 am' == $end ) ) { - // 2.3.2: simplify using existing midnight time - $end_time = $midnight; } else { - $end_time = $this->convert_shift_time( $end ); - $end_time = $this->to_time( $thisdate . ' ' . $end, $timezone ); - } - $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; - $updated = $record['updated']; - if ( $split ) { + // --- shift is valid so continue checking --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first + // # midnight calculation + $day = $shift['day']; + $thisdate = $weekdates[$day]; + $midnight = $this->to_time( $thisdate . ' 23:59:59', $timezone ) + 1; + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $start_time = $this->convert_shift_time( $start ); + $start_time = $this->to_time( $thisdate . ' ' . $start_time, $timezone ); + if ( ( '11:59:59 pm' == $end ) || ( '12:00 am' == $end ) ) { + // 2.3.2: simplify using existing midnight time + $end_time = $midnight; + } else { + $end_time = $this->convert_shift_time( $end ); + $end_time = $this->to_time( $thisdate . ' ' . $end, $timezone ); + } + $encore = ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) ? true : false; + $updated = $record['updated']; + + if ( $split ) { + + // --- check if show goes over midnight --- + if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { + + // --- set the shift time as is --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $id] = array( + 'ID' => $id, + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => $end, + // 'show' => $id, + 'encore' => $encore, + 'split' => false, + 'updated' => $updated, + 'shift' => $shift, + 'override' => false, + ); + + } else { + + // --- split shift for this day --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $id] = array( + 'ID' => $id, + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => '11:59:59 pm', // midnight + // 'show' => $id, + 'split' => true, + 'encore' => $encore, + 'updated' => $updated, + 'shift' => $shift, + 'real_end' => $end, + 'override' => false, + ); + + // --- split shift for next day --- + // 2.3.2: added date data for next day + $nextday = $this->get_next_day( $day ); + $nextdate = $weekdates[$nextday]; + // 2.3.2: fix midnight timestamp for sorting + if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { + $midnight = $this->to_time( $nextdate . ' 00:00:00', $timezone ); + } + $all_shifts[$nextday][$midnight . '.' . $id] = array( + 'ID' => $id, + 'day' => $nextday, + 'date' => $nextdate, + 'start' => '00:00 am', // midnight + 'end' => $end, + // 'show' => $id, + 'encore' => $encore, + 'split' => true, + 'updated' => $updated, + 'shift' => $shift, + 'real_start' => $start, + 'override' => false, + ); + } - // --- check if show goes over midnight --- - if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { + } else { // --- set the shift time as is --- - // 2.3.2: added date data + // 2.3.2: added for non-split argument $all_shifts[$day][$start_time . '.' . $id] = array( 'ID' => $id, 'day' => $day, @@ -476,67 +541,7 @@ public function get_shift_data( $records, $split, $times ) { 'override' => false, ); - } else { - - // --- split shift for this day --- - // 2.3.2: added date data - $all_shifts[$day][$start_time . '.' . $id] = array( - 'ID' => $id, - 'day' => $day, - 'date' => $thisdate, - 'start' => $start, - 'end' => '11:59:59 pm', // midnight - // 'show' => $id, - 'split' => true, - 'encore' => $encore, - 'updated' => $updated, - 'shift' => $shift, - 'real_end' => $end, - 'override' => false, - ); - - // --- split shift for next day --- - // 2.3.2: added date data for next day - $nextday = $this->get_next_day( $day ); - $nextdate = $weekdates[$nextday]; - // 2.3.2: fix midnight timestamp for sorting - if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { - $midnight = $this->to_time( $nextdate . ' 00:00:00', $timezone ); - } - $all_shifts[$nextday][$midnight . '.' . $id] = array( - 'ID' => $id, - 'day' => $nextday, - 'date' => $nextdate, - 'start' => '00:00 am', // midnight - 'end' => $end, - // 'show' => $id, - 'encore' => $encore, - 'split' => true, - 'updated' => $updated, - 'shift' => $shift, - 'real_start' => $start, - 'override' => false, - ); } - - } else { - - // --- set the shift time as is --- - // 2.3.2: added for non-split argument - $all_shifts[$day][$start_time . '.' . $id] = array( - 'ID' => $id, - 'day' => $day, - 'date' => $thisdate, - 'start' => $start, - 'end' => $end, - // 'show' => $id, - 'encore' => $encore, - 'split' => false, - 'updated' => $updated, - 'shift' => $shift, - 'override' => false, - ); - } } } @@ -1222,9 +1227,11 @@ public function process_shifts( $show_shifts, $times ) { // --- add show data back to shift --- // 2.5.6: use apply_filters instead of prefixed functions - $show = apply_filters( 'schedule_engine_schedule_show_data_meta', $shift['ID'], $shift['id'], $context ); + // 2.5.18: fix fo possibly missing show ID + $show_id = isset( $shift['ID'] ) ? $shift['ID'] : ''; + $show = apply_filters( 'schedule_engine_schedule_show_data_meta', $show_id, $shift['id'], $context ); if ( ( '' != $context ) && is_string( $context ) ) { - $show = apply_filters( $context . '_schedule_show_data_meta', $shift['ID'], $shift['id'] ); + $show = apply_filters( $context . '_schedule_show_data_meta', $show_id, $shift['id'] ); } unset( $show['schedule'] ); $shift['show'] = $show_shifts[$day][$start]['show'] = $show; diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 79d2a8b..251537c 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -74,9 +74,13 @@ $infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'excerpt', 'custom' ); $infokeys = apply_filters( 'radio_station_schedule_list_info_order', $infokeys ); +// --- clear floats --- +echo '
    ' . "\n"; + // --- start list schedule output --- // 2.5.0: append instance to element ID -$list = '
    '; + echo '
    '; // --- output widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped diff --git a/includes/class-current-show-widget.php b/includes/class-current-show-widget.php index dbc14a9..731e3ae 100644 --- a/includes/class-current-show-widget.php +++ b/includes/class-current-show-widget.php @@ -1,7 +1,7 @@ '' ) ); + $title = $instance['title']; $display_djs = isset( $instance['show_desc'] ) ? $instance['display_djs'] : false; $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; @@ -34,15 +35,29 @@ public function form( $instance ) { $show_desc = isset( $instance['show_desc'] ) ? $instance['show_desc'] : false; // 2.2.4: added title position, avatar width and DJ link options + // 2.3.0: added countdown display option + // 2.3.2: added AJAX load option $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'below'; $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : ''; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - // 2.3.0: added countdown display option $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : false; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; // 2.3.0: convert template style code to strings + // 2.3.2: added AJAX load option field $fields = '

    + +

    + +

    '; + echo '
    '; // --- widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped diff --git a/includes/class-radio-clock-widget.php b/includes/class-radio-clock-widget.php new file mode 100644 index 0000000..650dccd --- /dev/null +++ b/includes/class-radio-clock-widget.php @@ -0,0 +1,186 @@ + 'Radio_Clock_Widget', + 'description' => __( 'Display current radio and user times.', 'radio-station' ), + ); + $widget_display_name = __( '(Radio Station) Radio Clock', 'radio-station' ); + parent::__construct( 'Radio_Clock_Widget', $widget_display_name, $widget_ops ); + } + + // --- widget instance form --- + public function form( $instance ) { + + $instance = wp_parse_args( (array) $instance, array( 'title' => __( 'Radio Clock', 'radio-station' ) ) ); + + $title = isset( $instance['title'] ) ? $instance['title'] : ''; + $time = isset( $instance['time'] ) ? $instance['time'] : 12; + $seconds = isset( $instance['seconds'] ) ? $instance['seconds'] : 0; + $day = isset( $instance['day'] ) ? $instance['day'] : 'full'; + $date = isset( $instance['date'] ) ? $instance['date'] : 1; + $month = isset( $instance['month'] ) ? $instance['month'] : 'full'; + $zone = isset( $instance['zone'] ) ? $instance['zone'] : 1; + + // --- widget options form --- + echo ' +

    + +

    + +

    + +
    + ' . esc_html( __( 'Choose time format for displayed schedules', 'radio-station' ) ) . ' +

    + +

    + +

    + +

    + +
    + ' . esc_html( __( 'Display day with clock times.', 'radio-station' ) ) . ' +

    + +

    + +

    + +

    + +
    + ' . esc_html( __( 'Display month with clock times.', 'radio-station' ) ) . ' +

    + +

    + +

    '; + + } + + // --- update widget instance --- + public function update( $new_instance, $old_instance ) { + + $instance = $old_instance; + $instance['title'] = $new_instance['title']; + + // --- update widget options --- + $instance['time'] = isset( $new_instance['time'] ) ? $new_instance['time'] : 12; + $instance['seconds'] = isset( $new_instance['seconds'] ) ? 1 : 0; + $instance['day'] = isset( $new_instance['day'] ) ? $new_instance['day'] : 'full'; + $instance['date'] = isset( $new_instance['date'] ) ? 1 : 0; + $instance['month'] = isset( $new_instance['month'] ) ? $new_instance['month'] : 'full'; + $instance['zone'] = isset( $new_instance['zone'] ) ? 1 : 0; + + return $instance; + } + + // --- output widget display --- + public function widget( $args, $instance ) { + + // 2.3.0: added hide widget if empty option + $title = empty( $instance['title'] ) ? '' : $instance['title']; + $title = apply_filters( 'widget_title', $title ); + + $time = $instance['time']; + $seconds = $instance['seconds']; + $day = $instance['day']; + $date = $instance['date']; + $zone = $instance['zone']; + $month = $instance['month']; + + // --- set shortcode attributes for display --- + $atts = array( + 'time' => $time, + 'seconds' => $seconds, + 'day' => $day, + 'date' => $date, + 'month' => $month, + 'zone' => $zone, + 'widget' => 1, + ); + + // --- get default display output --- + $output = radio_station_clock_shortcode( $atts ); + + // --- check for widget output override --- + $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); + + // --- before widget --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $args['before_widget']; + + // --- open widget container --- + echo '
    '; + + // --- output widget title --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $args['before_title']; + if ( !empty( $title ) ) { + echo esc_html( $title ); + } + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $args['after_title']; + + // --- output widget display --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; + + // --- close widget container --- + echo '
    '; + + // --- after widget --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $args['after_widget']; + + // --- enqueue widget stylesheet in footer --- + // (this means it will only load if widget is on page) + radio_station_enqueue_style( 'widgets' ); + + } +} + +// --- register the widget --- +add_action( 'widgets_init', 'radio_station_register_radio_clock_widget' ); +function radio_station_register_radio_clock_widget() { + register_widget( 'Radio_Clock_Widget' ); +} diff --git a/includes/class-upcoming-shows-widget.php b/includes/class-upcoming-shows-widget.php index 450cfc5..0174ea7 100644 --- a/includes/class-upcoming-shows-widget.php +++ b/includes/class-upcoming-shows-widget.php @@ -1,6 +1,6 @@ '' ) ); + $title = $instance['title']; $display_djs = isset( $instance['display_djs'] ) ? $instance['display_djs'] : false; $djavatar = isset( $instance['djavatar'] ) ? $instance['djavatar'] : false; @@ -32,15 +33,29 @@ public function form( $instance ) { $show_sched = isset( $instance['show_sched'] ) ? $instance['show_sched'] : false; // 2.2.4: added title position, avatar width and DJ link options + // 2.3.0: added countdown field option + // 2.3.2: added AJAX load option $title_position = isset( $instance['title_position'] ) ? $instance['title_position'] : 'right'; $avatar_width = isset( $instance['avatar_width'] ) ? $instance['avatar_width'] : '75'; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; - // 2.3.0: added countdown field option $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : ''; + $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : ''; // 2.3.0: convert template style code to straight php echo + // 2.3.2: added AJAX load option field $fields = '

    + +

    + +

    '; + echo '
    '; // --- output widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 0fe7330..86c86a6 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -131,22 +131,39 @@ function radio_station_get_station_data() { // --- get station data --- $stream_url = radio_station_get_stream_url(); + // $stream_format = radio_station_get_stream_format(); + // $fallback_url = radio_station_get_fallback_url(); + // $fallback_format = radio_station_get_fallback_format(); + $station_url = radio_station_get_station_url(); $schedule_url = radio_station_get_schedule_url(); $language = radio_station_get_language(); - $now = strtotime( current_time( 'mysql' ) ); - $date_time = date( 'Y-m-d H:i:s', $now ); + // 2.3.2: use get date function with timezone + $now = radio_station_get_now(); + $date_time = radio_station_get_time( 'datetime', $now ); + + // 2.3.2: get schedule last updated time + $updated = get_option( 'radio_station_schedule_updated' ); + if ( !$updated ) { + $updated = time(); + update_option( 'radio_station_schedule_updated', $updated ); + } // --- set station data array --- + // 2.3.2: added schedule updated timestamp $station_data = array( 'timezone' => $timezone, 'stream_url' => $stream_url, + // 'stream_format' => $stream_format, + // 'fallback_url', => $fallback_url, + // 'fallback_format', => $fallback_format, 'station_url' => $station_url, 'schedule_url' => $schedule_url, 'language' => $language['slug'], 'timestamp' => $now, 'date_time' => $date_time, + 'updated' => $updated, 'success' => true, ); $station_data = apply_filters( 'radio_station_station_data', $station_data ); @@ -168,11 +185,13 @@ function radio_station_add_station_data( $data ) { // ------------------ function radio_station_get_broadcast_data() { - // --- get broadcast info --- + // --- get current show --- $current_show = radio_station_get_current_show(); // print_r( $current_show ); $current_show = radio_station_convert_show_shift( $current_show ); // print_r( $current_show ); + + // --- get next show --- $next_show = radio_station_get_next_show(); // print_r( $next_show ); $next_show = radio_station_convert_show_shift( $next_show ); @@ -181,7 +200,7 @@ function radio_station_get_broadcast_data() { // TODO: maybe get now playing playlist ? // $current_playlist = radio_station_current_playlist(); - // --- return current and next show info --- + // --- return broadcast info --- $broadcast = array( 'current_show' => $current_show, 'next_show' => $next_show, diff --git a/includes/legacy.php b/includes/legacy.php index bc34823..37ec003 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -385,11 +385,17 @@ function array_replace() { // --- get the most recently entered song --- // (used in DJ Widget, dj-widget shortcode, Playlist Widget, now-playing shortcode) -function radio_station_get_now_playing() { +// 2.3.2: added optional time argument +function radio_station_get_now_playing( $time = false ) { // --- get the currently playing show --- // 2.3.0: added to prevent playlist/show mismatch! - $current_show = radio_station_get_current_show(); + // 2.3.2: check current show using optional time + if ( !$time ) { + $current_show = radio_station_get_current_show(); + } else { + $current_show = radio_station_get_current_show( $time ); + } if ( !$current_show ) { return false; } @@ -399,7 +405,7 @@ function radio_station_get_now_playing() { } $show_id = $current_show['show']['id']; - // TODO: match current playlist to show shift ? + // TODO: match current playlist to assigned show shift ? if ( isset( $current_show['shifts'] ) ) { $shifts = $current_show['shifts']; } @@ -464,7 +470,8 @@ function radio_station_get_now_playing() { } // --- filter and return tracks --- - $playlist = apply_filters( 'radio_station_show_now_playing', $playlist, $show_id ); + // 2.3.2: added time argument to filter + $playlist = apply_filters( 'radio_station_show_now_playing', $playlist, $show_id, $time ); return $playlist; } diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 5a7ecc7..6d248f0 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -9,7 +9,7 @@ add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { - // --- make list attribute backward compatible --- + // --- make attributes backward compatible --- // 2.3.0: convert old list attribute to view if ( !isset( $atts['view'] ) && isset( $atts['list'] ) ) { if ( 1 === (int) $atts['list'] ) { @@ -28,12 +28,15 @@ function radio_station_master_schedule( $atts ) { $atts['show_times'] = $atts['display_show_time']; unset( $atts['display_show_time'] ); } - // 2.3.0: convert single_day to days + // 2.3.0: convert single_day attribute to days if ( !isset( $atts['days'] ) && isset( $atts['single_day'] ) ) { $atts['days'] = $atts['single_day']; unset( $atts['single_day'] ); } + // --- get default clock display setting --- + $clock = radio_station_get_setting( 'schedule_clock' ); + // --- merge shortcode attributes with defaults --- // 2.3.0: added show_desc (default off) // 2.3.0: added show_hosts (alias of show_djs) @@ -44,21 +47,34 @@ function radio_station_master_schedule( $atts ) { // 2.3.0: added link_hosts attribute (default off) // 2.3.0: set default time format according to plugin setting // 2.3.0: set default table display to new table formatting + // 2.3.2: added start_day attribute (for use width days) + // 2.3.2: added display_day, display_date and display_month attributes $time_format = (int) radio_station_get_setting( 'clock_time_format' ); $defaults = array( + + // --- control display options --- + 'selector' => 1, + 'clock' => $clock, + 'timezone' => 1, + + // --- schedule display options --- 'time' => $time_format, 'show_times' => 1, 'show_link' => 1, 'view' => 'table', 'days' => false, + 'start_day' => false, + 'display_day' => 'short', + 'display_date' => 'jS', + 'display_month' => 'short', 'divheight' => 45, - // 'list' => 0, // converted above / deprecated - // 'show_djs' => 0, // converted above / deprecated - // 'display_show_time' => 1, // converted above / deprecated - // --- new display options --- - 'selector' => 1, - 'clock' => 1, + // --- converted and deprecated --- + // 'list' => 0, + // 'show_djs' => 0, + // 'display_show_time' => 1, + + // --- show display options --- 'show_image' => 0, 'show_desc' => 0, 'show_hosts' => 0, @@ -68,19 +84,30 @@ function radio_station_master_schedule( $atts ) { 'show_file' => 0, ); // 2.3.0: change some defaults for tabbed and list view + // 2.3.2: check for comma separated view list if ( isset( $atts['view'] ) ) { - if ( 'tabs' == $atts['view'] ) { + // 2.3.2: view value to lowercase to be case insensitive + $atts['view'] = strtolower( $atts['view'] ); + $views = explode( ',', $atts['view'] ); + if ( ( 'tabs' == $atts['view'] ) || in_array( 'tabs', $views ) ) { + // 2.3.2: add show descriptions default for tabbed view + // 2.3.2: add display_ and display_date attributes $defaults['show_image'] = 1; $defaults['show_hosts'] = 1; $defaults['show_genres'] = 1; - } elseif ( 'list' == $atts['view'] ) { + $defaults['show_desc'] = 1; + $defaults['display_day'] = 'full'; + $defaults['display_date'] = false; + } elseif ( ( 'list' == $atts['view'] ) || in_array( 'list', $views ) ) { + // 2.3.2: add display date attribute $defaults['show_genres'] = 1; + $defaults['display_date'] = false; } } // --- merge attributes with defaults --- $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); - + // --- enqueue schedule stylesheet --- // 2.3.0: use abstracted method for enqueueing widget styles radio_station_enqueue_style( 'schedule' ); @@ -94,39 +121,64 @@ function radio_station_master_schedule( $atts ) { $atts['clock'] = 0; } - // --- get show selection links and clock display --- - // 2.3.0: added server/user clock display - if ( $atts['selector'] ) { - $selector = radio_station_master_schedule_selector(); - } - if ( $atts['clock'] ) { - $clock = radio_station_clock_shortcode(); - } - // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views + // 2.3.2: moved shortcode calls inside and added filters $output .= '
    '; - if ( $atts['clock'] ) { - // --- display radio clock --- - $output .= '
    '; - $output .= $clock; - $output .= '
    '; - } else { - // --- display radio timezone --- - $output .= '
    '; - $output .= radio_station_timezone_shortcode(); - $output .= '
    '; - } + $controls = array(); + + // --- display radio clock or timezone (or neither) + if ( $atts['clock'] ) { - if ( $atts['selector'] ) { - // --- display genre selector --- - $output .= '
    '; - $output .= $selector; - $output .= '

    '; - } + // --- radio clock --- + $controls['clock'] = '
    '; + $clock_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); + $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); + $controls['clock'] .= '
    '; + + } elseif ( $atts['timezone'] ) { + + // --- radio timezone --- + $controls['timezone'] = '
    '; + $timezone_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); + $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); + $controls['timezone'] .= '
    '; + + } + + // --- genre selector --- + if ( $atts['selector'] ) { + $controls['selector'] = '
    '; + $controls['selector'] .= radio_station_master_schedule_selector(); + $controls['selector'] .= '
    '; + } - $output .= '

    '; + // 2.3.1: add filters for control order + $control_order = array( 'clock', 'timezone', 'selector' ); + $control_order = apply_filters( 'radio_station_schedule_control_order', $control_order, $atts ); + + // 2.3.1: add filter for controls HTML + $controls = apply_filters( 'radio_station_schedule_controls', $controls, $atts ); + + // --- add ordered controls to output --- + if ( is_array( $control_order ) && ( count( $control_order ) > 0 ) ) { + foreach ( $control_order as $control ) { + if ( isset( $controls[$control] ) && ( '' != $control ) ) { + $output .= $controls[$control]; + } + } + } + + $output .= '

    '; + + // --- schedule display override --- + // 2.3.1: add full schedule override filter + $override = apply_filters( 'radio_station_schedule_override', '', $atts ); + if ( ( '' != $override ) && strstr( $override, '' ) ) { + $override = str_replace( '', '', $override ); + return $output . $override; + } // ------------------------- // New Master Schedule Views @@ -141,6 +193,7 @@ function radio_station_master_schedule( $atts ) { $template = radio_station_get_template( 'file', 'master-schedule-table.php' ); require $template; + $output = apply_filters( 'master_schedule_table_view', $output, $atts ); return $output; } elseif ( 'tabs' == $atts['view'] ) { // 2.2.7: add tabbed view javascript to footer @@ -148,11 +201,14 @@ function radio_station_master_schedule( $atts ) { $template = radio_station_get_template( 'file', 'master-schedule-tabs.php' ); require $template; + $output = apply_filters( 'master_schedule_tabs_view', $output, $atts ); return $output; } elseif ( 'list' == $atts['view'] ) { + add_action( 'wp_footer', 'radio_station_master_schedule_list_js' ); $template = radio_station_get_template( 'file', 'master-schedule-list.php' ); require $template; + $output = apply_filters( 'master_schedule_list_view', $output, $atts ); return $output; } @@ -318,10 +374,6 @@ function radio_station_master_schedule( $atts ) { // ---------------------- function radio_station_master_schedule_selector() { - // --- open genre highlighter div --- - $html = '
    '; - $html .= '' . esc_html( __( 'Genres', 'radio-station' ) ) . ': '; - // --- get genres --- $args = array( 'hide_empty' => true, @@ -329,13 +381,21 @@ function radio_station_master_schedule_selector() { 'order' => 'ASC', ); $genres = get_terms( RADIO_STATION_GENRES_SLUG, $args ); + // 2.3.2: bug out if there are no genre terms + if ( !$genres || !is_array( $genres ) ) { + return ''; + } + + // --- open genre highlighter div --- + $html = '
    '; + $html .= '' . esc_html( __( 'Genres', 'radio-station' ) ) . ': '; // --- genre highlight links --- // 2.3.0: fix by imploding with genre link spacer $genre_links = array(); foreach ( $genres as $i => $genre ) { $slug = sanitize_title_with_dashes( $genre->name ); - $javascript = 'javascript:radio_show_highlight(\'' . $slug . '\')'; + $javascript = 'javascript:radio_genre_highlight(\'' . $slug . '\')'; $title = __( 'Click to toggle Highlight of Shows with this Genre.', 'radio-station' ); $genre_link = ''; $genre_link .= esc_html( $genre->name ) . ''; @@ -349,7 +409,7 @@ function radio_station_master_schedule_selector() { // 2.3.0: improved to highlight / unhighlight multiple genres // 2.3.0: improved to work with table, tabs or list view $js = "var highlighted_genres = new Array(); - function radio_show_highlight(genre) { + function radio_genre_highlight(genre) { if (jQuery('#genre-highlight-'+genre).hasClass('highlighted')) { jQuery('#genre-highlight-'+genre).removeClass('highlighted'); @@ -388,12 +448,54 @@ function radio_show_highlight(genre) { // 2.3.0: added for table responsiveness function radio_station_master_schedule_table_js() { - $js = "/* Trigger Responsive Table */ - jQuery(document).ready(function() {radio_table_responsive();} ); + // 2.3.2: added current show highlighting cycle + // 2.3.2: fix to currenthour substr + $js = "/* Initialize Table */ + jQuery(document).ready(function() { + radio_table_responsive(); + radio_times_highlight(); + setTimeout(radio_times_highlight, 60000); + }); jQuery(window).resize(function () { radio_resize_debounce(radio_table_responsive, 500, 'scheduletable'); }); + /* Current Time Highlighting */ + function radio_times_highlight() { + radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); + radio.offset_time = radio.current_time + radio.timezone.offset; + if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} + if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + jQuery('.master-program-day').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + if (start < radio.offset_time) { + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (end > radio.offset_time) {jQuery(this).addClass('current-day');} + else {jQuery(this).removeClass('current-day');} + } else {jQuery(this).removeClass('current-day');} + }); + jQuery('.master-program-hour').each(function() { + hour = parseInt(jQuery(this).find('.master-program-server-hour').attr('data')); + offset_time = radio.current_time + radio.timezone.offset; + current = new Date(offset_time * 1000).toISOString(); + currenthour = current.substr(11, 2); + if (currenthour.substr(0,1) == '0') {currenthour = currenthour.substr(1,1);} + if (hour == currenthour) {jQuery(this).addClass('current-hour');} + else {jQuery(this).removeClass('current-hour');} + }); + jQuery('.master-show-entry').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + if (radio.debug) {console.log(start);} + if (start < radio.offset_time) { + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (end > radio.offset_time) { + jQuery(this).addClass('nowplaying'); + if (radio.debug) {console.log('^^^^^^^');} + } else {jQuery(this).removeClass('nowplaying');} + } else {jQuery(this).removeClass('nowplaying');} + }); + } + /* Make Table Responsive */ function radio_table_responsive() { tablewidth = jQuery('#master-program-schedule').width(); @@ -407,27 +509,20 @@ function radio_table_responsive() { columns = 0; firstcolumn = -1; for (i = 0; i < 7; i++) { - jQuery('.master-program-day.day-'+i).removeClass('first-column'); - jQuery('.master-program-day.day-'+i).removeClass('last-column'); + jQuery('.master-program-day.day-'+i).removeClass('first-column').removeClass('last-column'); if ( ((i + 1) > startcolumn) && (columns < daycolumns) ) { - jQuery('.master-program-day.day-'+i).show(); - jQuery('.show-info.day-'+i).show(); + jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).show(); if (firstcolumn < 0) { + if (i > 0) {jQuery('.master-program-day.day-'+i).addClass('first-column');} firstcolumn = 0; - if (i > 0) { - jQuery('.master-program-day.day-'+i).addClass('first-column'); - } } lastcolumn = i; columns++; } else { - jQuery('.master-program-day.day-'+i).hide(); - jQuery('.show-info.day-'+i).hide(); + jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).hide(); } } + if (lastcolumn < 6) {jQuery('.master-program-day.day-'+lastcolumn).addClass('last-column');} - if (lastcolumn < 6) { - jQuery('.master-program-day.day-'+lastcolumn).addClass('last-column'); - } if (radio.debug) { console.log('Day Columns:' +daycolumns); console.log('Selected Column: '+selected); @@ -465,17 +560,19 @@ function radio_shift_day(leftright) { function radio_station_master_schedule_tabs_js() { // --- tab switching function --- - $js = "jQuery(document).ready(function() { - dayweek = new Date().getDay(); - if (dayweek == '0') {day = 'sunday';} - if (dayweek == '1') {day = 'monday';} - if (dayweek == '2') {day = 'tuesday';} - if (dayweek == '3') {day = 'wednesday';} - if (dayweek == '4') {day = 'thursday';} - if (dayweek == '5') {day = 'friday';} - if (dayweek == '6') {day = 'saturday';} + // 2.3.2: added fallback if current day is not viewed + // TODO: check current server time for onload display + /* date = new Date(); dayweek = date.getDay(); day = radio_get_weekday(dayweek); + if (jQuery('#master-schedule-tabs-header-'+day).length) { + id = jQuery('.master-schedule-tabs-day.selected-day').first().attr('id'); + day = id.replace('master-schedule-tabs-header-',''); jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); + } else { + jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); + jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); + } */ + $js = "jQuery(document).ready(function() { jQuery('.master-schedule-tabs-day-name').bind('click', function (event) { headerID = jQuery(event.target).closest('li').attr('id'); panelID = headerID.replace('header', 'day'); @@ -488,24 +585,79 @@ function radio_station_master_schedule_tabs_js() { // --- tabbed view responsiveness --- // 2.3.0: added for tabbed responsiveness - $js .= "/* Trigger Responsive Tabs */ - jQuery(document).ready(function() {radio_tabs_responsive();} ); + // 2.3.2: display selected day message if outside view + $js .= "/* Initialize Tabs */ + jQuery(document).ready(function() { + radio.schedule_tabinit = false; + radio_tabs_responsive(); + radio_show_highlight(); + setTimeout(radio_show_highlight, 60000); + }); jQuery(window).resize(function () { radio_resize_debounce(radio_tabs_responsive, 500, 'scheduletabs'); }); + /* Set Day Tab on Load */ + function radio_set_active_tab(day) { + if (radio.schedule_tabinit) {return;} + jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); + jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); + if (!day) { + jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); + jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); + } else { + jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); + jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); + } + radio.schedule_tabinit = true; + } + + /* Current Show Highlighting */ + function radio_show_highlight() { + radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); + radio.offset_time = radio.current_time + radio.timezone.offset; + if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} + if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + jQuery('.master-schedule-tabs-day').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + if (start < radio.offset_time) { + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (end > radio.offset_time) { + jQuery(this).addClass('current-day'); + day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); + radio_set_active_tab(day); + } else {jQuery(this).removeClass('current-day');} + } else {jQuery(this).removeClass('current-day');} + }); + radio_set_active_tab(false); /* fallback */ + jQuery('.master-schedule-tabs-show').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + if (radio.debug) {console.log(start);} + if (start < radio.offset_time) { + if (radio.debug) {console.log('^^^^^^^');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (end > radio.offset_time) {jQuery(this).addClass('nowplaying');} + else {jQuery(this).removeClass('nowplaying');} + } else {jQuery(this).removeClass('nowplaying');} + }); + } + /* Make Tabs Responsive */ function radio_tabs_responsive() { + + jQuery('.master-schedule-tabs-selected').hide(); tabswidth = jQuery('#master-schedule-tabs').width(); daycolumns = Math.floor(tabswidth / 125); if (daycolumns < 1) {daycolumns = 1;} else if (daycolumns > 7) {daycolumns = 7;} + fallback = false; selected = false; for (i = 0; i < 7; i++) { + if (!fallback && jQuery('.master-schedule-tabs-day.day-'+i)) {fallback = i;} if (jQuery('.master-schedule-tabs-day.day-'+i).hasClass('selected-day')) {selected = i;} } - startcolumn = selected; + if (selected) {startcolumn = selected;} else {selected = startcolumn = fallback;} if ((selected + daycolumns) > 6) {startcolumn = 7 - daycolumns;} - columns = 0; firstcolumn = -1; + activeday = false; columns = 0; firstcolumn = -1; for (i = 0; i < 7; i++) { if (jQuery('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) { activeday = i; @@ -537,13 +689,11 @@ function radio_tabs_responsive() { console.log('Last Column: '+lastcolumn); } - /* maybe change active clicked day if outside view */ - if (activeday > lastcolumn) { - jQuery('.master-schedule-tabs-day.day-'+lastcolumn+' .master-schedule-tabs-day-name').click(); - } else if (activeday < startcolumn ) { - jQuery('.master-schedule-tabs-day.day-'+startcolumn+' .master-schedule-tabs-day-name').click(); - } - + /* display selected day message if outside view */ + if ( activeday && ( (activeday > lastcolumn) || (activeday < startcolumn ) ) ) { + weekday = radio_get_weekday(activeday); + jQuery('#master-schedule-tabs-selected-'+weekday).show(); + } } /* Shift Day Left / Right */ @@ -575,3 +725,43 @@ function radio_shift_tab(leftright) { // 2.3.0: enqueue instead of echoing wp_add_inline_script( 'radio-station', $js ); } + +// -------------------- +// List View Javascript +// -------------------- +// 2.3.2: added for list schedule view +function radio_station_master_schedule_list_js() { + + // --- list view javascript --- + $js = "/* Initialize List */ + jQuery(document).ready(function() { + radio_list_highlight(); + setTimeout(radio_list_highlight, 60000); + }); + /* Current Show Highlighting */ + function radio_list_highlight() { + radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); + radio.offset_time = radio.current_time + radio.timezone.offset; + if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} + jQuery('.master-list-day').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').first().attr('data')); + if (start < radio.offset_time) { + end = parseInt(jQuery(this).find('.rs-end-time').first().attr('data')); + if (end > radio.offset_time) {jQuery(this).addClass('current-day');} + else {jQuery(this).removeClass('current-day');} + } else {jQuery(this).removeClass('current-day');} + }); + jQuery('.master-list-day-item').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + if (start < radio.offset_time) { + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (end > radio.offset_time) {jQuery(this).addClass('nowplaying');} + else {jQuery(this).removeClass('nowplaying');} + } else {jQuery(this).removeClass('nowplaying');} + }); + }"; + + // --- enqueue script inline --- + wp_add_inline_script( 'radio-station', $js ); +} + \ No newline at end of file diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 4c243a6..ed8b80a 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -34,12 +34,14 @@ // - Assign Producers to Show Metabox // - Add Show Shifts Metabox // - Show Shifts Metabox +// - Show Shift Table // - Add Show Description Helper Metabox // - Show Description Helper Metabox // - Rename Show Featured Image Metabox // - Add Show Images Metabox // - Show Images Metabox // - Update Show Metadata +// - Relogin AJAX Message // - Add Show List Columns // - Show List Column Data // - Show List Column Styles @@ -65,18 +67,26 @@ // (shows metaboxes above Editor area for Radio Station CPTs) add_action( 'edit_form_after_title', 'radio_station_top_meta_boxes' ); function radio_station_top_meta_boxes() { - global $post; + global $post, $wp_meta_boxes; $current_screen = get_current_screen(); if ( RADIO_STATION_DEBUG ) { echo ""; + echo ""; echo ""; - $hidden_metaboxes = get_hidden_meta_boxes( $current_screen ); + $metabox_order = get_user_option( 'meta-box-order_' . $current_screen->id ); + $hidden_metaboxes = get_user_option( 'metaboxhidden_' . $current_screen->id ); + $screen_layout = get_user_option( 'screen_layout_' . $current_screen->id ); + echo ""; echo ""; + echo ""; } // --- top metabox output --- - do_meta_boxes( $current_screen, 'rs-top', $post ); + // 2.3.2: change metabox ID from rs-top + // (- is not supported in metabox ID for sort order saving) + // (causing bug where sorted metaboxes disappear completely!) + do_meta_boxes( $current_screen, 'rstop', $post ); if ( RADIO_STATION_DEBUG ) { echo ""; @@ -88,7 +98,7 @@ function radio_station_top_meta_boxes() { // --------------------------------- // 2.3.0: also apply to override post type // 2.3.0: remove default languages metabox from shows -add_action( 'add_meta_boxes', 'radio_station_modify_taxonomy_metabox_positions' ); +add_action( 'add_meta_boxes', 'radio_station_modify_taxonomy_metabox_positions', 11 ); function radio_station_modify_taxonomy_metabox_positions() { global $wp_meta_boxes; @@ -118,7 +128,9 @@ function radio_station_modify_taxonomy_metabox_positions() { unset( $wp_meta_boxes[RADIO_STATION_OVERRIDE_SLUG]['side']['core']['tagsdiv-' . RADIO_STATION_LANGUAGES_SLUG] ); } - // echo ""; + // if ( RADIO_STATION_DEBUG ) { + // echo ""; + // } } @@ -133,11 +145,12 @@ function radio_station_modify_taxonomy_metabox_positions() { add_action( 'add_meta_boxes', 'radio_station_add_show_language_metabox' ); function radio_station_add_show_language_metabox() { // note: only added to overrides as moved into show info metabox for shows + // 2.3.2: removed unnecessary array wrapper from post type argument add_meta_box( RADIO_STATION_LANGUAGES_SLUG . 'div', __( 'Show Language', 'radio-station' ), 'radio_station_show_language_metabox', - array( RADIO_STATION_OVERRIDE_SLUG ), + RADIO_STATION_OVERRIDE_SLUG, 'side', 'high' ); @@ -360,12 +373,14 @@ function radio_station_language_term_filter( $post_id ) { add_action( 'add_meta_boxes', 'radio_station_add_playlist_metabox' ); function radio_station_add_playlist_metabox() { // 2.2.2: change context to show at top of edit screen + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'playlist' ); add_meta_box( 'radio-station-playlist-metabox', __( 'Playlist Entries', 'radio-station' ), 'radio_station_playlist_metabox', RADIO_STATION_PLAYLIST_SLUG, - 'rs-top', // shift to top + $position, 'high' ); } @@ -375,168 +390,366 @@ function radio_station_add_playlist_metabox() { // --------------------- function radio_station_playlist_metabox() { - global $post; + global $post, $current_screen; // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'playlist_tracks_nonce' ); // --- get the saved meta as an array --- - $entries = get_post_meta( $post->ID, 'playlist', false ); - $c = 1; + // 2.3.2: set single argument to true + $entries = get_post_meta( $post->ID, 'playlist', true ); + + // --- set button titles --- + // 2.3.2: added titles for button icons + $move_up_title = __( 'Move Track Up', 'radio-station' ); + $move_down_title = __( 'Move Track Down', 'radio-station' ); + $duplicate_title = __( 'Duplicate Track', 'radio-station' ); + $remove_title = __( 'Remove Track', 'radio-station' ); echo '
    '; - echo ''; + // 2.3.2: separate track list table + echo radio_station_playlist_track_table( $entries ); + + // --- track save/add buttons --- + // 2.3.2: change track save from button-primary to button-secondary + // 2.3.2: added playlist AJAX save button (for existing posts only) + // 2.3.2: added playlist tracks clear button + // 2.3.2: added table and track saved message + echo '
    '; + echo '
    '; + echo ''; + echo ''; + if ( 'add' != $current_screen->action ) { + echo ''; + } + echo ''; + echo ''; + echo '
    '; + echo ''; + echo ''; + echo ''; + echo '
    '; + + echo '
    '; + + // --- move new tracks message --- + // 2.3.2: added new track move message + echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    '; + + // --- clear all tracks function --- + $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); + $js = "function radio_tracks_clear() { + if (jQuery('#track-table tr').length) { + var agree = confirm('" . esc_js( $confirm_clear ) . "'); + if (!agree) {return false;} + jQuery('#track-table tr').remove(); + trackcount = 1; + } + }" . PHP_EOL; + + // --- save tracks via AJAX --- + // 2.3.2: added form input cloning to save playlist tracks + $ajaxurl = admin_url( 'admin-ajax.php' ); + $js .= "function radio_tracks_save() { + jQuery('#track-save-form, #track-save-frame').remove(); + form = '
    '; + form += '
    '; + jQuery('#wpbody').append(form); + if (!jQuery('#track-save-frame').length) { + frame = ''; + jQuery('#wpbody').append(frame); + } + /* copy tracklist input fields and nonce */ + jQuery('#track-table input').each(function() {jQuery(this).clone().appendTo('#track-save-form');}); + jQuery('#track-table select').each(function() { + name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); + jQuery('').appendTo('#track-save-form'); + }); + jQuery('#playlist_tracks_nonce').clone().attr('id','').appendTo('#track-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','playlist_id').appendTo('#track-save-form'); + jQuery('#tracks-saving-message').show(); + jQuery('#track-save-form').submit(); + }" . PHP_EOL; + + // --- move track up or down --- + // 2.3.2: added move track function + $js .= "function radio_track_move(updown, n) { + /* swap track rows */ + if (updown == 'up') { + m = n - 1; + jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ + } + if (updown == 'down') { + m = n + 1; + jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ + } + /* reset track classes */ + radio_track_classes(); + + /* swap track count */ + jQuery('#track-'+n+'-rowa .track-count').html(m); + jQuery('#track-'+m+'-rowa .track-count').html(n); + + /* swap input name keys */ + jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); + }); + jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); + }); + + /* swap button actions */ + jQuery('#track-'+n+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+m+');'); + jQuery('#track-'+n+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+m+');'); + jQuery('#track-'+m+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+n+');'); + jQuery('#track-'+m+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+n+');'); + jQuery('#track-'+n+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+m+');'); + jQuery('#track-'+n+'-rowb .track-remove').attr('onclick','radio_track_remove('+m+');'); + jQuery('#track-'+m+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+n+');'); + jQuery('#track-'+m+'-rowb .track-remove').attr('onclick','radio_track_remove('+n+');'); + + /* swap row IDs */ + jQuery('#track-'+m+'-rowa').attr('id', 'track-0-rowa'); + jQuery('#track-'+m+'-rowb').attr('id', 'track-0-rowb'); + jQuery('#track-'+n+'-rowa').attr('id', 'track-'+m+'-rowa'); + jQuery('#track-'+n+'-rowb').attr('id', 'track-'+m+'-rowb'); + jQuery('#track-0-rowa').attr('id', 'track-'+n+'-rowa'); + jQuery('#track-0-rowb').attr('id', 'track-'+n+'-rowb'); + }" . PHP_EOL; + + // --- reset first and last track classes --- + $js .= "function radio_track_classes() { + jQuery('.track-rowa, .track-rowb, .track-rowc').removeClass('first-track').removeClass('last-track'); + jQuery('.track-rowa').first().addClass('first-track'); jQuery('.track-rowa').last().addClass('last-track'); + jQuery('.track-rowb').first().addClass('first-track'); jQuery('.track-rowb').last().addClass('last-track'); + /* jQuery('.track-rowc').first().addClass('first-track'); jQuery('.track-rowc').last().addClass('last-track'); */ + }" . PHP_EOL; + + // --- add track function --- + // 2.3.0: set javascript as string to enqueue + // 2.3.2: added missing track-meta cell class + // 2.3.2: added track move arrows + // 2.3.2: added first and last row classes + // 2.3.2: set to standalone onclick function + $js .= "function radio_track_add() { + if (trackcount == 1) {classes = 'first-track last-track';} else {classes = 'last-track';} + output = ''; + output += ''+trackcount+''; + output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += '" . esc_js( __( 'Comments', 'radio-station' ) ) . ": '; + output += '
    " . esc_js( __( 'New', 'radio-station' ) ) . ":
    '; + output += '
    '; + output += '
    " . esc_js( __( 'Status', 'radio-station' ) ) . ":
    '; + output += '
    '; + output += ''; + output += '
    " . esc_js( __( 'Move', 'radio-station') ) . "
    : '; + output += '
    '; + output += '
    '; + output += '
    '; + output += '
    '; + output += ''; + output += ''; + + /* output += ''; + output += ''; */ + + jQuery('#track-table').append(output); + trackcount++; + radio_track_classes(); + return false; + }" . PHP_EOL; + + // --- duplicate track function --- + $js .= "function radio_track_duplicate(id) { + var i; var nextid = id + 1; + /* shift rows down */ + for (i = trackcount; i > id; i--) { + jQuery('#track-'+i+'-rowa, #track-'+i+'-rowb').each(function() { + jQuery(this).attr('id', jQuery(this).attr('id').replace(i, (i+1))); + jQuery(this).find('.track-count').html(i+1); + jQuery(this).find('input, select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+i+']', '['+(i+1)+']')); + }); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); + }); + } + /* add duplicate row */ + jQuery('#track-'+id+'-rowa').clone().attr('id','track-'+nextid+'-rowa').insertAfter('#track-'+id+'-rowb'); + jQuery('#track-'+id+'-rowb').clone().attr('id','track-'+nextid+'-rowb').insertAfter('#track-'+nextid+'-rowa'); + jQuery('#track-'+nextid+'-rowa .track-count').html(nextid); + jQuery('#track-'+nextid+'-rowa, #track-'+nextid+'-rowb').each(function() { + jQuery(this).find('input, select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+id+']', '['+nextid+']')); + }); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\", '+nextid+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\", '+nextid+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+nextid+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+nextid+');'); + }); + radio_track_classes(); + trackcount++; + }" . PHP_EOL; + + // --- remove track function --- + // 2.3.2: reset first and last classes on remove + // 2.3.2: set to standalone onclick function + $js .= "function radio_track_remove(id) { + jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb, #track-'+id+'-rowc').remove(); + radio_track_classes(); trackcount--; + + /* renumber track count */ + var tcount = 1; + jQuery('.track-rowa').each(function() { + jQuery(this).find('.track-count').html(tcount); tcount++; + }); + }" . PHP_EOL; + + // --- set track count --- + // 2.3.2: set count from row count length + // 2.3.2: removed document ready wrapper + $js .= "var trackcount = jQuery('.track-rowa').length + 1;"; + + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + + // --- track list styles --- + // 2.3.0: added track meta style fix + // 2.3.2: added track meta select font size fix + // 2.3.2: added track move arrow styles + // 2.3.2: added table buttons styling + // 2.3.2: added track save message styling + echo ''; + + // --- close meta inner --- + echo '
    '; + + // 2.3.2: removed publish button duplication + // (replaced with track save AJAX button) +} + +// ---------------- +// Track List Table +// ---------------- +// 2.3.2: separated tracklist table (for AJAX) +function radio_station_playlist_track_table( $entries ) { + + // --- open track table --- + echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; - // echo ""; - // echo ""; - // echo ""; - // echo ""; echo ''; - if ( isset( $entries[0] ) && !empty( $entries[0] ) ) { + // --- set button titles --- + // 2.3.2: added titles for icon buttons + $move_up_title = __( 'Move Track Up', 'radio-station' ); + $move_down_title = __( 'Move Track Down', 'radio-station' ); + $duplicate_title = __( 'Duplicate Track', 'radio-station' ); + $remove_title = __( 'Remove Track', 'radio-station' ); - foreach ( $entries[0] as $track ) { + // 2.3.2: removed [0] array key + $c = 1; + if ( isset( $entries ) && !empty( $entries ) ) { + + foreach ( $entries as $track ) { if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) || isset( $track['playlist_entry_status'] ) ) { - echo ''; - echo ''; + // --- track row a --- + $class = ''; + if ( 1 == $c ) { + $class = 'first-track'; + } elseif ( $c == count( $entries ) ) { + $class = 'last-track'; + } + echo ''; + + // --- track count --- + echo ''; + + // --- track entry inputs --- echo ''; echo ''; echo ''; echo ''; + echo ''; + + // --- track row b --- + echo ''; - echo ''; - + // --- track comments --- echo ''; + // --- track meta --- echo ''; - echo ''; + // 2.3.2: added move track arrows + echo ''; echo ''; + + // --- track row c --- + // TODO: add track time / start / end input fields ? + // echo ''; + // echo ''; + $c ++; } } } echo '
    ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '" . esc_html( __( 'DJ Comments', 'radio-station' ) ) . "" . esc_html( __( 'New', 'radio-station' ) ) . "" . esc_html( __( 'Status', 'radio-station') ) . "" . esc_html( __( 'Remove', 'radio-station') ) . "
    ' . esc_html( $c ) . '
    ' . esc_html( $c ) . '
    ' . esc_html__( 'Comments', 'radio-station' ) . ' '; echo ''; echo '
    ' . esc_html( __( 'New', 'radio-station' ) ) . ':
    '; - $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; - echo '
    '; - echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    '; + // 2.3.2: remove new value checking as now used and cleared on save + // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + // ' . checked( $track['playlist_entry_new'] ) . ' + echo '
    '; + echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    '; echo '
    ' . esc_html( __( 'Remove', 'radio-station' ) ) . ''; + echo '
    ' . esc_html( __( 'Move', 'radio-station') ) . ':
    '; + echo '
    '; + echo '
    '; + + // --- remove track button --- + echo '
    '; + echo '
    '; + echo '
    '; - - // 2.3.0: added track meta style fix - echo ''; - - echo '
    '; - echo '' . esc_html( __( 'Add Track', 'radio-station' ) ) . ''; - echo '
    '; - - echo '
    '; - - // 2.3.0: set javascript as string to enqueue - $js = " - jQuery(document).ready(function() { - var count = " . esc_js( $c ) . "; - jQuery('.add-track').click(function() { - - output = ''+count+''; - output += ''; - output += ''; - output += ''; - output += ''; - output += ''; - output += '" . esc_js( __( 'Comments', 'radio-station' ) ) . ": '; - output += '
    " . esc_js( __( 'New', 'radio-station' ) ) . ":
    '; - output += '
    '; - output += '
    " . esc_js( __( 'Status', 'radio-station' ) ) . ":
    '; - output += '
    '; - output += '" . esc_js( __( 'Remove', 'radio-station' ) ) . "'; - output += ''; - - jQuery('#here').append(output); - count = count + 1; - return false; - }); - jQuery('.remove-track').live('click', function() { - rowid = jQuery(this).attr('id'); - jQuery('#'+rowid+'-rowa').remove(); - jQuery('#'+rowid+'-rowb').remove(); - }); - }); - "; - - // --- enqueue inline script --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); - - echo '
    '; - - echo '
    '; - echo '

    '; - - $can_publish = current_user_can( 'publish_playlists' ); - // borrowed from wp-admin/includes/meta-boxes.php - // 2.2.8: remove strict in_array checking - if ( !in_array( $post->post_status, array( 'publish', 'future', 'private' ) ) || 0 === $post->ID ) { - if ( $can_publish ) { - if ( !empty( $post->post_date_gmt ) && time() < strtotime( $post->post_date_gmt . ' +0000' ) ) { - echo ''; - submit_button( - __( 'Schedule', 'radio-station' ), - 'primary', - 'publish', - false, - array( - 'tabindex' => '50', - 'accesskey' => 'o', - ) - ); - } else { - echo ''; - submit_button( - __( 'Publish' ), - 'primary', - 'publish', - false, - array( - 'tabindex' => '50', - 'accesskey' => 'o', - ) - ); - } - } else { - echo ''; - submit_button( - __( 'Update Playlist' ), - 'primary', - 'publish', - false, - array( - 'tabindex' => '50', - 'accesskey' => 'o', - ) - ); - } - } else { - echo ''; - echo ''; - } - echo '
    '; } // ----------------------------------- @@ -655,6 +868,8 @@ function radio_station_playlist_show_metabox() { // Update Playlist Data // -------------------- // --- When a playlist is saved, saves our custom data --- +// 2.3.2: added action for AJAX save of tracks +add_action( 'wp_ajax_radio_station_playlist_save_tracks', 'radio_station_playlist_save_data' ); add_action( 'save_post', 'radio_station_playlist_save_data' ); function radio_station_playlist_save_data( $post_id ) { @@ -663,20 +878,56 @@ function radio_station_playlist_save_data( $post_id ) { return; } + // --- make sure we have a post ID for AJAX save --- + // 2.3.2: added AJAX track saving checks + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { + exit; + } + $post_id = absint( $_POST['playlist_id'] ); + $post = get_post( $post_id ); + + $error = false; + if ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !$post ) { + $error = __( 'Failed. Invalid Playlist ID.', 'radio-station' ); + } elseif ( !current_user_can( 'edit_playlists' ) ) { + $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); + } + + // --- send error message to parent window --- + if ( $error ) { + echo ""; + + exit; + } + } + // --- save playlist tracks --- if ( isset( $_POST['playlist'] ) ) { // --- verify playlist nonce --- + // 2.3.2: fix OR condition to AND condition if ( isset( $_POST['playlist_tracks_nonce'] ) - || wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); // move songs that are still queued to the end of the list so that order is maintained foreach ( $playlist as $i => $song ) { - if ( 'queued' === $song['playlist_entry_status'] ) { - $playlist[] = $song; + // 2.3.2: move songs marked as new to the end instead of queued + // if ( 'queued' === $song['playlist_entry_status'] ) { + if ( $song['playlist_entry_new'] ) { + // 2.3.2: unset before adding to maintain (now ordered) track count + // 2.3.2: unset new flag from track record now it has been moved unset( $playlist[$i] ); + unset( $song['playlist_entry_new'] ); + $playlist[] = $song; } } update_post_meta( $post_id, 'playlist', $playlist ); @@ -717,7 +968,54 @@ function radio_station_playlist_save_data( $post_id ) { } } } + + // --- AJAX saving --- + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == $_POST['action'] ) ) { + + // --- display tracks saved message --- + $playlist_tracks_nonce = wp_create_nonce( 'radio-station' ); + echo ""; + + // --- refresh track list table --- + $entries = get_post_meta( $post_id, 'playlist', true ); + echo radio_station_playlist_track_table( $entries ); + echo ""; + + exit; + } + } +} + +// -------------------- +// Relogin AJAX Message +// -------------------- +// 2.3.2: added for show shifts and playlist tracks AJAX +add_action( 'wp_ajax_nopriv_radio_station_show_save_shifts', 'radio_station_relogin_message' ); +add_action( 'wp_ajax_nopriv_radio_station_playlist_save_tracks', 'radio_station_relogin_message' ); +function radio_station_relogin_message() { + + if ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { + $type = 'shift'; + } elseif ( 'station_playlist_save_tracks' == $_REQUEST['action'] ) { + $type = 'track'; + } + + // --- send relogin message + $error = __( 'Failed. Relogin in try again.', 'radio-station' ); + echo ""; + exit; } // ------------------------- @@ -933,12 +1231,14 @@ function radio_station_post_save_data( $post_id ) { add_action( 'add_meta_boxes', 'radio_station_add_show_info_metabox' ); function radio_station_add_show_info_metabox() { // 2.2.2: change context to show at top of edit screen + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'shows' ); add_meta_box( 'radio-station-show-info-metabox', __( 'Show Information', 'radio-station' ), 'radio_station_show_info_metabox', RADIO_STATION_SHOW_SLUG, - 'rs-top', // shift to top + $position, 'high' ); } @@ -954,7 +1254,9 @@ function radio_station_show_info_metabox() { wp_nonce_field( 'radio-station', 'show_meta_nonce' ); // --- get show meta --- + // 2.3.2: added show download disable switch $file = get_post_meta( $post->ID, 'show_file', true ); + $download = get_post_meta( $post->ID, 'show_download', true ); $email = get_post_meta( $post->ID, 'show_email', true ); $active = get_post_meta( $post->ID, 'show_active', true ); $link = get_post_meta( $post->ID, 'show_link', true ); @@ -962,23 +1264,28 @@ function radio_station_show_info_metabox() { // added max-width to prevent metabox overflows // 2.3.0: removed new lines between labels and fields and changed widths + // 2.3.2: increase label width to 120px for disable download field label echo '
    '; - echo '

    + echo '

    ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

    '; - echo '

    + echo '

    '; - echo '

    + echo '

    '; - echo '

    + echo '

    '; + // 2.3.2: added show download disable field + echo '

    +

    '; + // 2.3.0: added patreon page field - echo '

    '; + echo '

    '; echo ' https://patreon.com/

    '; echo '
    '; @@ -1021,7 +1328,8 @@ function radio_station_show_info_metabox() { // echo ''; // echo ''; - echo '

    ' . esc_html( $metabox['title'] ) . '

    '; + // 2.3.2: remove class="hndle" to prevent box sorting + echo '

    ' . esc_html( $metabox['title'] ) . '

    '; echo '
    '; call_user_func( $metabox['callback'] ); echo "
    "; @@ -1194,12 +1502,14 @@ function radio_station_show_producers_metabox() { add_action( 'add_meta_boxes', 'radio_station_add_show_shifts_metabox' ); function radio_station_add_show_shifts_metabox() { // 2.2.2: change context to show at top of edit screen + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'shifts' ); add_meta_box( 'radio-station-show-shifts-metabox', __( 'Show Schedule', 'radio-station' ), 'radio_station_show_shifts_metabox', RADIO_STATION_SHOW_SLUG, - 'rs-top', // shift to top + $position, 'low' ); } @@ -1209,25 +1519,358 @@ function radio_station_add_show_shifts_metabox() { // ------------------- function radio_station_show_shifts_metabox() { - global $post; + global $post, $current_screen; - // --- edit show link --- - $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); + // --- set days, hours and minutes arrays --- + $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $hours = $mins = array(); + for ( $i = 1; $i <= 12; $i ++ ) { + $hours[$i] = $i; + } + for ( $i = 0; $i < 60; $i ++ ) { + if ( $i < 10 ) { + $min = '0' . $i; + } else { + $min = $i; + } + $mins[$i] = $min; + } // 2.2.7: added meridiem translations $am = radio_station_translate_meridiem( 'am' ); $pm = radio_station_translate_meridiem( 'pm' ); + + // --- hidden debug fields --- + // 2.3.2: added save debugging field + if ( RADIO_STATION_DEBUG ) { + echo ''; + } + if ( RADIO_STATION_SAVE_DEBUG ) { + echo ''; + } // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); echo '
    '; + echo '
    '; - // --- hidden debug field --- - if ( RADIO_STATION_DEBUG ) { - echo ''; + // 2.3.2: added to bypass shift check on add (for debugging) + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + echo ''; } + // --- output show shifts table --- + // 2.3.2: separated table function (for AJAX saving) + $table = radio_station_show_shifts_table( $post->ID ); + + // --- show inactive message --- + // 2.3.0: added show inactive reminder message + if ( !$table['active'] ) { + echo '
    '; + echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; + echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); + echo '
    '; + } + + // --- shift conflicts message --- + // 2.3.0: added instructions for fixing shift conflicts + if ( $table['conflicts'] ) { + radio_station_shifts_conflict_message(); + } + + // --- output shift list --- + if ( '' != $table['list'] ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; + } + echo "
    "; + + // 2.3.0: center add shift button + // 2.3.2: fix centering by removing span wrapper + // 2.3.2: change from button-primary to button-secondary + echo '
    '; + // echo '' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; + + // --- shift save/add buttons --- + // 2.3.2: added show shifts AJAX save button (for existing posts only) + // 2.3.2: added show shifts clear button + // 2.3.2: added table and shifts saved message + echo ''; + echo '
    '; + echo ''; + echo ''; + if ( 'add' != $current_screen->action ) { + echo ''; + } + echo ''; + echo ''; + echo '
    '; + echo ''; + echo ''; + echo ''; + echo '
    '; + echo '
    '; + + // --- show shifts scripts --- + // 2.3.0: added confirmation to remove shift button + // 2.3.2: removed document ready functions wrapper + $c = 0; + $confirm_remove = __( 'Are you sure you want to remove this shift?', 'radio-station' ); + $confirm_clear = __( 'Are you sure you want to clear the shift list?', 'radio-station' ); + // $js = "var count = " . esc_attr( $c ) . ";"; + + // --- clear all shifts function --- + $js = "function radio_shifts_clear() { + if (jQuery('#shifts-list').children().length) { + var agree = confirm('" . esc_js( $confirm_clear ) . "'); + if (!agree) {return false;} + jQuery('#shifts-list').children().remove(); + jQuery('
    ').appendTo('#shifts-list'); + } + }" . PHP_EOL; + + // --- save shifts via AJAX --- + // 2.3.2: added form input cloning to saving show shifts + $ajaxurl = admin_url( 'admin-ajax.php' ); + $js .= "function radio_shifts_save() { + jQuery('#shift-save-form, #shift-save-frame').remove(); + form = '
    '; + form += '
    '; + jQuery('#wpbody').append(form); + if (!jQuery('#shift-save-frame').length) { + frame = ''; + jQuery('#wpbody').append(frame); + } + /* copy shifts input fields and nonce */ + jQuery('#shifts-list input').each(function() {jQuery(this).clone().appendTo('#shift-save-form');}); + jQuery('#shifts-list select').each(function() { + name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); + jQuery('').appendTo('#shift-save-form'); + }); + jQuery('#show_shifts_nonce').clone().attr('id','').appendTo('#shift-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','show_id').appendTo('#shift-save-form'); + jQuery('#shifts-saving-message').show(); + jQuery('#shift-save-form').submit(); + }" . PHP_EOL; + + // --- add new shift --- + // 2.3.2: separate function for onclick + $js .= "function radio_shift_new() { + values = {}; + values.day = ''; + values.start_hour = ''; + values.start_min = ''; + values.start_meridian = ''; + values.end_hour = ''; + values.end_min = ''; + values.end_meridian = ''; + values.encore = ''; + values.disabled = ''; + radio_shift_add(values); + }" . PHP_EOL; + + // --- remove shift ---- + // 2.3.2: separate function for onclick + $js .= "function radio_shift_remove(id) { + agree = confirm('" . esc_js( $confirm_remove ) . "'); + if (!agree) {return false;} + console.log(id); console.log(jQuery(id)); + console.log(jQuery(id).closest('.shift-wrapper')); + jQuery(id).closest('.shift-wrapper').remove(); + }" . PHP_EOL; + + // --- duplicate shift --- + // 2.3.2: separate function for onclick + $js .= "function radio_shift_duplicate(id) { + shiftid = jQuery(id).attr('id').replace('shift-',''); + values = {}; + values.day = jQuery('#shift-'+shiftid+'-day').val(); + values.start_hour = jQuery('#shift-'+shiftid+'-start-hour').val(); + values.start_min = jQuery('#shift-'+shiftid+'-start-min').val(); + values.start_meridian = jQuery('#shift-'+shiftid+'-start-meridian').val(); + values.end_hour = jQuery('#shift-'+shiftid+'-end-hour').val(); + values.end_min = jQuery('#shift-'+shiftid+'-end-min').val(); + values.end_meridian = jQuery('#shift-'+shiftid+'-end-meridian').val(); + values.encore = ''; + if (jQuery('#shift-'+shiftid+'-encore').prop('checked')) {values.encore = 'on';} + values.disabled = 'yes'; + radio_shift_add(values); + }" . PHP_EOL; + + // --- add shift function --- + // 2.3.2: added missing shift wrapper class + // 2.3.2: set new count based on new shift children + $js .= "/* Add Show Shift */ + function radio_shift_add(values) { + var count = jQuery('#new-shifts').children().length + 1; + console.log(count); + output = '
      '; + output += '
    • '; + output += '" . esc_js( __( 'Day', 'radio-station' ) ) . ": '; + output += '';"; - - // --- shift day --- - // 2.3.0: simplify by looping days and add translation - foreach ( $days as $day ) { - $js .= "output += '
    • '; - - output += '
    • '; - output += ''; - output += '
    • '; - - output += '
    '; - - jQuery('#here').append(output); - return false; - }"; - - // --- enqueue inline script --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); - - // --- shift display styles --- - echo ''; - - echo '
    '; + // --- set return data --- + // 2.3.2: added for separated function + $table = array( + 'list' => $list, + 'active' => $active, + 'conflicts' => $has_conflicts, + ); + + return $table; } // ----------------------------------- @@ -1699,12 +2131,14 @@ function radio_add_shift(values) { // 2.3.0: added metabox for show description helper text add_action( 'add_meta_boxes', 'radio_station_add_show_helper_box' ); function radio_station_add_show_helper_box() { + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'helper' ); add_meta_box( 'radio-station-show-helper-box', __( 'Show Description', 'radio-station' ), 'radio_station_show_helper_box', RADIO_STATION_SHOW_SLUG, - 'rs-top', + $position, 'low' ); } @@ -1998,6 +2432,8 @@ function radio_station_show_images_save() { // -------------------- // Update Show Metadata // -------------------- +// 2.3.2: added AJAX show save action +add_action( 'wp_ajax_radio_station_show_save_shifts', 'radio_station_show_save_data' ); add_action( 'save_post', 'radio_station_show_save_data' ); function radio_station_show_save_data( $post_id ) { @@ -2006,6 +2442,37 @@ function radio_station_show_save_data( $post_id ) { return; } + // --- make sure we have a post ID for AJAX save --- + // 2.3.2: added AJAX shift saving checks + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( !isset( $_POST['show_id'] ) || ( '' == $_POST['show_id'] ) ) { + exit; + } + $post_id = absint( $_POST['show_id'] ); + $post = get_post( $post_id ); + + // --- check for errors --- + $error = false; + if ( !isset( $_POST['show_shifts_nonce'] ) || !wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !$post ) { + $error = __( 'Failed. Invalid Show.', 'radio-station' ); + } elseif ( !current_user_can( 'edit_shows' ) ) { + $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); + } + + // --- send error to parent frame --- + if ( $error ) { + echo ""; + + exit; + } + } + // --- set show meta changed flags --- $show_meta_changed = $show_shifts_changed = false; @@ -2070,29 +2537,45 @@ function radio_station_show_save_data( $post_id ) { // 2.2.3: added show metadata value sanitization $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); $email = sanitize_email( trim( $_POST['show_email'] ) ); - $active = $_POST['show_active']; + $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); + $patreon_id = sanitize_title( $_POST['show_patreon'] ); + // 2.2.8: removed strict in_array checking + // 2.3.2: fix for unchecked boxes index warning + $active = $download = ''; + if ( isset( $_POST['show_active'] ) ) { + $active = $_POST['show_active']; + } if ( !in_array( $active, array( '', 'on' ) ) ) { $active = ''; } - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - $patreon_id = sanitize_title( $_POST['show_patreon'] ); + // 2.3.2: added download disable switch + if ( isset( $_POST['show_download'] ) ) { + $download = $_POST['show_download']; + } + if ( !in_array( $download, array( '', 'on' ) ) ) { + $download = ''; + } // --- get existing values and check if changed --- // 2.3.0: added check against previous values + // 2.3.2: added download disable switch $prev_file = get_post_meta( $post_id, 'show_file', true ); + $prev_download = get_post_meta( $post_id, 'show_download', true ); $prev_email = get_post_meta( $post_id, 'show_email', true ); $prev_active = get_post_meta( $post_id, 'show_active', true ); $prev_link = get_post_meta( $post_id, 'show_link', true ); $prev_patreon_id = get_post_meta( $post_id, 'show_patreon', true ); if ( ( $prev_file != $file ) || ( $prev_email != $email ) || ( $prev_active != $active ) || ( $prev_link != $link ) - || ( $prev_patreon_id != $patreon_id ) ) { + || ( $prev_download != $download ) || ( $prev_patreon_id != $patreon_id ) ) { $show_meta_changed = true; } // --- update the show metadata --- + // 2.3.2: added download disable switch update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_download', $download ); update_post_meta( $post_id, 'show_email', $email ); update_post_meta( $post_id, 'show_active', $active ); update_post_meta( $post_id, 'show_link', $link ); @@ -2243,13 +2726,20 @@ function radio_station_show_save_data( $post_id ) { $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'shows' ); if ( $conflicts ) { $disabled = true; + if ( RADIO_STATION_DEBUG ) { + echo "*Conflicting Shift Disabled*"; + } } } // --- disable if incomplete data or shift conflicts --- if ( $disabled ) { $new_shifts[$i]['disabled'] = 'yes'; + if ( RADIO_STATION_DEBUG ) { + echo "*Shift Disabled*"; + } } + } // --- recheck for conflicts with other shifts for this show --- @@ -2278,17 +2768,95 @@ function radio_station_show_save_data( $post_id ) { do_action( 'radio_station_clear_data', 'show', $post_id ); do_action( 'radio_station_clear_data', 'show_meta', $post_id ); + // --- set last updated schedule time --- + // 2.3.2: added for data API use + update_option( 'radio_station_schedule_updated', time() ); + // --- maybe send directory ping --- // 2.3.1: added directory update ping option - radio_station_send_directory_ping(); + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); } - if ( RADIO_STATION_DEBUG ) { - exit; + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( isset( $_POST['action'] ) && ( 'radio_station_show_save_shifts' == $_POST['action'] ) ) { + + print_r( $_POST['show_sched'] ); + print_r( $new_shifts ); + + // --- display shifts saved message --- + $show_shifts_nonce = wp_create_nonce( 'radio-station' ); + echo ""; + + // --- output new show shifts list --- + echo '
    '; + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + echo ''; + } + $table = radio_station_show_shifts_table( $post_id ); + if ( $table['conflicts'] ) { + radio_station_shifts_conflict_message(); + } + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; + echo '
    '; + echo '
    '; + + // --- refresh show shifts list --- + echo ""; + + // --- alert on conflicts --- + if ( $table['conflicts'] ) { + $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); + echo ""; + } + + exit; + } } } +// ----------------- +// Save Output Debug +// ----------------- +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 0 ); +function radio_station_save_debug_start( $post_id ) { + if ( !RADIO_STATION_SAVE_DEBUG ) { + return; + } + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } + ob_start(); +} +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 9999 ); +function radio_station_save_debug_end( $post_id ) { + if ( !RADIO_STATION_SAVE_DEBUG ) { + return; + } + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } + $contents = ob_get_contents(); + ob_end_clean(); + if ( strlen( $contents ) > 0 ) { + echo "Output Detected During Save (preventing redirect):
    "; + echo ''; + exit; + } +} + // --------------------- // Add Show List Columns // --------------------- @@ -2335,13 +2903,16 @@ function radio_station_show_columns( $columns ) { function radio_station_show_column_data( $column, $post_id ) { if ( 'active' == $column ) { + $active = get_post_meta( $post_id, 'show_active', true ); if ( 'on' == $active ) { echo esc_html( __( 'Yes', 'radio-station' ) ); } else { echo esc_html( __( 'No', 'radio-station' ) ); } + } elseif ( 'description' == $column ) { + // 2.3.0: added show description indicator global $wpdb; $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; @@ -2352,22 +2923,35 @@ function radio_station_show_column_data( $column, $post_id ) { } else { echo esc_html( __( 'Yes', 'radio-station' ) ); } + } elseif ( 'shifts' == $column ) { + $active = get_post_meta( $post_id, 'show_active', true ); if ( 'on' == $active ) { $active = true; } // 2.3.0: check using dates for reliability - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); $shifts = get_post_meta( $post_id, 'show_sched', true ); - if ( $shifts && ( count( $shifts ) > 0 ) ) { + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + + $sorted_shifts = $dayless_shifts = array(); foreach ( $shifts as $shift ) { - $timestamp = strtotime( $weekdates[$shift['day']] . ' ' . $shift['start_hour'] . ":" . $shift['start_min'] . " " . $shift['start_meridian'] ); - $sortedshifts[$timestamp] = $shift; + // 2.3.2: added check that shift day is not empty + if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { + // 2.3.2: fix to convert shift time to 24 hour format + $shift_time = $shift['start_hour'] . ":" . $shift['start_min'] . ' ' . $shift['start_meridian']; + $shift_time = radio_station_convert_shift_time( $shift_time ); + $shift_time = $weekdates[$shift['day']] . $shift_time; + $timestamp = radio_station_to_time( $shift_time ); + $sortedshifts[$timestamp] = $shift; + } else { + $dayless_shifts[] = $shift; + } } ksort( $sortedshifts ); @@ -2394,6 +2978,7 @@ function radio_station_show_column_data( $column, $post_id ) { $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); } } + // 2.3.0: also highlight if the show is not active if ( !$active ) { if ( !in_array( 'disabled', $classes ) ) { @@ -2406,10 +2991,13 @@ function radio_station_show_column_data( $column, $post_id ) { echo "
    "; // --- get shift start and end times --- + // 2.3.2: fix to convert to 24 hour time $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - $start_time = strtotime( $weekdates[$shift['day']] . ' ' . $start ); - $end_time = strtotime( $weekdates[$shift['day']] . ' ' . $end ); + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); // --- make weekday filter selections bold --- // 2.3.0: fix to bolding only if weekday isset @@ -2431,8 +3019,23 @@ function radio_station_show_column_data( $column, $post_id ) { } echo "
    "; } - } + + // --- dayless shifts --- + // 2.3.2: added separate display of dayless shifts + if ( count( $dayless_shifts ) > 0 ) { + foreach ( $dayless_shifts as $shift ) { + $title = __( 'This shift is disabled as no day is set.', 'radio-station' ); + echo "
    "; + $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + echo esc_html( $start ) . " - " . esc_html( $end ); + echo "
    "; + } + } + } + } elseif ( 'hosts' == $column ) { + $hosts = get_post_meta( $post_id, 'show_user_list', true ); if ( $hosts && ( count( $hosts ) > 0 ) ) { foreach ( $hosts as $host ) { @@ -2440,7 +3043,9 @@ function radio_station_show_column_data( $column, $post_id ) { echo esc_html( $user_info->display_name ) . "
    "; } } + } elseif ( 'producers' == $column ) { + // 2.3.0: added column for Producers $producers = get_post_meta( $post_id, 'show_producer_list', true ); if ( $producers && ( count( $producers ) > 0 ) ) { @@ -2449,12 +3054,15 @@ function radio_station_show_column_data( $column, $post_id ) { echo esc_html( $user_info->display_name ) . "
    "; } } + } elseif ( 'show_image' == $column ) { + // 2.3.0: get show avatar (with fallback to thumbnail) $image_url = radio_station_get_show_avatar_url( $post_id ); if ( $image_url ) { echo "
    " . esc_html( __(
    "; } + } } @@ -2518,12 +3126,14 @@ function radio_station_add_schedule_override_metabox() { // 2.2.2: add high priority to show at top of edit screen // 2.3.0: set position to top to be above editor box // 2.3.0: update meta box ID for consistency + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'overrides' ); add_meta_box( 'radio-station-override-metabox', __( 'Override Schedule', 'radio-station' ), 'radio_station_schedule_override_metabox', RADIO_STATION_OVERRIDE_SLUG, - 'rs-top', // shift to top + $position, 'high' ); } @@ -2648,8 +3258,8 @@ function radio_station_schedule_override_metabox() { // ------------------------ // Update Schedule Override // ------------------------ -add_action( 'save_post', 'radio_station_master_override_save_showpostdata' ); -function radio_station_master_override_save_showpostdata( $post_id ) { +add_action( 'save_post', 'radio_station_override_save_data' ); +function radio_station_override_save_data( $post_id ) { // --- verify if this is an auto save routine --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { @@ -2739,9 +3349,14 @@ function radio_station_master_override_save_showpostdata( $post_id ) { delete_transient( 'radio_station_current_show' ); delete_transient( 'radio_station_next_show' ); + // --- set last updated schedule time --- + // 2.3.2: added for data API use + update_option( 'radio_station_schedule_updated', time() ); + // --- maybe send directory ping --- // 2.3.1: added directory update ping option - radio_station_send_directory_ping(); + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); } } @@ -2761,13 +3376,14 @@ function radio_station_override_columns( $columns ) { $date = $columns['date']; unset( $columns['date'] ); - $columns['override_date'] = esc_attr( __( 'Override Date', 'radio-station' ) ); + $columns['override_date'] = esc_attr( __( 'Date', 'radio-station' ) ); $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station' ) ); // 2.3.0: added description indicator column - $columns['description'] = esc_attr( __( 'Description', 'radio-station' ) ); - $columns['override_image'] = esc_attr( __( 'Override Image' ) ); + $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); + // 2.3.2: added missing translation text domain + $columns['override_image'] = esc_attr( __( 'Image', 'radio-station' ) ); $columns['date'] = $date; return $columns; @@ -2784,16 +3400,24 @@ function radio_station_override_column_data( $column, $post_id ) { $override = get_post_meta( $post_id, 'show_override_sched', true ); if ( 'override_date' == $column ) { + + // 2.3.2: no need to apply timezone conversions here $datetime = strtotime( $override['date'] ); $month = date( 'F', $datetime ); $month = radio_station_translate_month( $month ); $weekday = date( 'l', $datetime ); $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); + echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' '; + echo esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); + } elseif ( 'start_time' == $column ) { + echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . ' ' . esc_html( $override['start_meridian'] ); + } elseif ( 'end_time' == $column ) { + echo esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . ' ' . esc_html( $override['end_meridian'] ); + } elseif ( 'shows_affected' == $column ) { // --- maybe get all show shifts --- @@ -2801,7 +3425,7 @@ function radio_station_override_column_data( $column, $post_id ) { $show_shifts = $radio_station_show_shifts; } else { global $wpdb; - $query = "SELECT posts.post_title, meta.post_id, meta.meta_value FROM " . $wpdb->prefix . "postmeta} AS meta + $query = "SELECT posts.post_title, meta.post_id, meta.meta_value FROM " . $wpdb->prefix . "postmeta AS meta JOIN " . $wpdb->prefix . "posts as posts ON posts.ID = meta.post_id WHERE meta.meta_key = 'show_sched' AND posts.post_status = 'publish'"; // 2.3.0: get results as an array @@ -2812,58 +3436,90 @@ function radio_station_override_column_data( $column, $post_id ) { return; } - // --- get the override weekday and convert to 24 hour time --- - $datetime = strtotime( $override['date'] ); - $weekday = date( 'l', $datetime ); + // --- get the override weekday --- + // 2.3.2: remove date time and get day from date directly + $weekday = date( 'l', strtotime( $override['date'] ) ); // --- get start and end override times --- - $override_start = strtotime( $override['date'] . ' ' . $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian'] ); - $override_end = strtotime( $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian'] ); + // 2.3.2: fix to convert to 24 hour format first + $start = $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; + $end = $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; + $start = radio_station_convert_shift_time( $start ); + $end = radio_station_convert_shift_time( $end ); + $override_start = radio_station_to_time( $override['date'] . ' ' . $start ); + $override_end = radio_station_to_time( $override['date'] . ' ' . $end ); // (if the end time is less than start time, adjust end to next day) if ( $override_end <= $override_start ) { - $override_end = $override_end + 86400; + $override_end = $override_end + ( 24 * 60 * 60 ); } // --- loop show shifts --- foreach ( $show_shifts as $show_shift ) { - $shift = maybe_unserialize( $show_shift['meta_value'] ); - if ( !is_array( $shift ) ) { - $shift = array(); + $shifts = maybe_unserialize( $show_shift['meta_value'] ); + if ( !is_array( $shifts ) ) { + $shifts = array(); } - foreach ( $shift as $time ) { - if ( isset( $time['day'] ) && ( $time['day'] == $weekday ) ) { + foreach ( $shifts as $shift ) { + if ( isset( $shift['day'] ) && ( $shift['day'] == $weekday ) ) { // --- get start and end shift times --- // 2.3.0: validate shift time to check if complete - $time = radio_station_validate_shift( $time ); - $shift_start = strtotime( $override['date'] . ' ' . $time['start_hour'] . ':' . $time['start_min'] . ' ' . $time['start_meridian'] ); - $shift_end = strtotime( $override['date'] . ' ' . $time['end_hour'] . ':' . $time['end_min'] . ' ' . $time['end_meridian'] ); + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $time = radio_station_validate_shift( $shift ); + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $start = radio_station_convert_shift_time( $start ); + $end = radio_station_convert_shift_time( $end ); + $shift_start = radio_station_to_time( $override['date'] . ' ' . $start ); + $shift_end = radio_station_to_time( $override['date'] . ' ' . $end ); if ( ( $shift_start == $shift_end ) || ( $shift_start > $shift_end ) ) { - $shift_end = $shift_end + 86400; + $shift_end = $shift_end + ( 24 * 60 * 60 ); + } + + if ( RADIO_STATION_DEBUG ) { + echo $weekday . ': ' . $start . ' to ' . $end . '
    ' . PHP_EOL; + echo $override['date'] . ': ' . $shift_start . ' to ' . $shift_end . '
    ' . PHP_EOL; + echo $override['date'] . ': ' . $override_start . ' to ' . $override_end . '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; } // --- compare override time overlaps to get affected shows --- - if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) - || ( ( $override_start >= $shift_start ) && ( $override_end < $shift_end ) ) ) { + // 2.3.2: fix to override overlap checking logic + if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_start ) ) + || ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) + || ( $override_start == $shift_start ) + || ( ( $override_start > $shift_start ) && ( $override_end < $shift_end ) ) + || ( ( $override_start > $shift_start ) && ( $override_start < $shift_end ) ) ) { + // 2.3.0: adjust cell display to two line (to allow for long show titles) - $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); - if ( 'on' != $active ) { - echo "[" . esc_html( __( 'Inactive Show', 'radio-station' ) ) . "] "; + // 2.3.2: deduplicate show check (if same show as last show displayed) + if ( !isset( $last_show ) || ( $last_show != $show_shift['post_id'] ) ) { + $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); + if ( 'on' != $active ) { + echo "[" . esc_html( __( 'Inactive', 'radio-station' ) ) . "] "; + } + echo '' . $show_shift['post_title'] . "
    "; } - echo $show_shift['post_title'] . "
    "; - if ( $time['disabled'] ) { - echo "[" . esc_html( __( 'Disabled Shift', 'radio-station' ) ) . "] "; + + if ( isset( $shift['disabled'] ) && $shift['disabled'] ) { + echo "[" . esc_html( __( 'Disabled', 'radio-station' ) ) . "] "; } - echo radio_station_translate_weekday( $time['day'] ); - echo " " . esc_html( $time['start_hour'] ) . ":" . esc_html( $time['start_min'] ) . esc_html( $time['start_meridian'] ); - echo " - " . esc_html( $time['end_hour'] ) . ":" . esc_html( $time['end_min'] ) . esc_html( $time['end_meridian'] ); + echo radio_station_translate_weekday( $shift['day'] ); + echo " " . esc_html( $shift['start_hour'] ) . ":" . esc_html( $shift['start_min'] ) . esc_html( $shift['start_meridian'] ); + echo " - " . esc_html( $shift['end_hour'] ) . ":" . esc_html( $shift['end_min'] ) . esc_html( $shift['end_meridian'] ); echo "
    "; + + // 2.3.2: store last show displayed + $last_show = $show_shift['post_id']; } } } } + } elseif ( 'description' == $column ) { + // 2.3.0: added override description indicator global $wpdb; $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; @@ -2874,6 +3530,7 @@ function radio_station_override_column_data( $column, $post_id ) { } else { echo esc_html( __( 'Yes', 'radio-station' ) ); } + } elseif ( 'override_image' == $column ) { $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); if ( $thumbnail_url ) { @@ -2901,8 +3558,11 @@ function radio_station_override_column_styles() { if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { return; } - echo ""; + + // 2.3.2: set override image column width to override image width + echo ""; } // ---------------------------------- @@ -2916,7 +3576,7 @@ function radio_station_override_date_filter( $post_type, $which ) { if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { return; } - + // --- get all show override months / years --- global $wpdb; $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; @@ -2926,9 +3586,9 @@ function radio_station_override_date_filter( $post_type, $which ) { foreach ( $results as $result ) { $post_id = $result['ID']; $override = get_post_meta( $post_id, 'show_override_date', true ); - $datetime = strtotime( $override ); - $month = date( 'm', $datetime ); - $year = date( 'Y', $datetime ); + $datetime = radio_station_to_time( $override ); + $month = radio_station_get_time( 'm', $datetime ); + $year = radio_station_get_time( 'Y', $datetime ); $months[$year . $month]['year'] = $year; $months[$year . $month]['month'] = $month; } @@ -2961,10 +3621,11 @@ function radio_station_override_date_filter( $post_type, $which ) { // 2.2.7: added filter for custom column sorting add_action( 'pre_get_posts', 'radio_station_columns_query_filter' ); function radio_station_columns_query_filter( $query ) { + if ( !is_admin() || !$query->is_main_query() ) { return; } - + // --- Shows by Shift Days Filtering --- if ( RADIO_STATION_SHOW_SLUG === $query->get( 'post_type' ) ) { @@ -3000,11 +3661,15 @@ function radio_station_columns_query_filter( $query ) { // 2.3.0: replace old with new 24 hour conversion // $shiftstart = $shifttime['start_hour'] . ':' . $shifttime['start_min'] . ":00"; // $shiftstart = radio_station_convert_schedule_to_24hour( $shift ); + // 2.3.2: replace strtotime with to_time for timezones $shiftstart = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; - $shifttime = strtotime( $weekday . ' ' . $shiftstart ); + $shifttime = radio_station_convert_shift_time( $shiftstart ); + $shifttime = radio_station_to_time( $weekday . ' ' . $shiftstart ); // 2.3.0: check for earliest shift for that day if ( !$prevtime || ( $shifttime < $prevtime ) ) { - $shiftstart = radio_station_convert_shift_time( $shiftstart, 24 ) . ':00'; + // 2.3.2: adjust as already converted to 24 hour + // $shiftstart = radio_station_convert_shift_time( $shiftstart, 24 ) . ':00'; + $shiftstart .= ':00'; $prevtime = $shifttime; } } @@ -3070,6 +3735,7 @@ function radio_station_columns_query_filter( $query ) { // --- apply override year/month filtering --- if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { $yearmonth = $_GET['month']; + // TODO: adjust for timezone..? $start_date = date( $yearmonth . '01' ); $end_date = date( $yearmonth . 't' ); $meta_query = array( diff --git a/includes/post-types.php b/includes/post-types.php index 154b703..f0ced94 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -39,22 +39,27 @@ function radio_station_create_post_types() { // $icon = WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__)) . 'images/show-menu-icon.png'; // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( - 'labels' => array( - 'name' => __( 'Shows', 'radio-station' ), - 'singular_name' => __( 'Show', 'radio-station' ), - 'add_new' => __( 'Add Show', 'radio-station' ), - 'add_new_item' => __( 'Add Show', 'radio-station' ), - 'edit_item' => __( 'Edit Show', 'radio-station' ), - 'new_item' => __( 'New Show', 'radio-station' ), - 'view_item' => __( 'View Show', 'radio-station' ), + 'labels' => array( + 'name' => __( 'Shows', 'radio-station' ), + 'singular_name' => __( 'Show', 'radio-station' ), + 'add_new' => __( 'Add Show', 'radio-station' ), + 'add_new_item' => __( 'Add Show', 'radio-station' ), + 'edit_item' => __( 'Edit Show', 'radio-station' ), + 'new_item' => __( 'New Show', 'radio-station' ), + 'view_item' => __( 'View Show', 'radio-station' ), // 2.3.0: added archive title label - 'archive_title' => __( 'Shows', 'radio-station' ), + 'archive_title' => __( 'Shows', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Shows', 'radio-station' ), + 'not_found' => __( 'No Shows found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Shows found in Trash', 'radio-station' ), + 'all_items' => __( 'All Shows', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Show descriptions', 'radio-station' ), + 'description' => __( 'Post type for Shows', 'radio-station' ), 'public' => true, 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), 'hierarchical' => false, @@ -82,21 +87,26 @@ function radio_station_create_post_types() { // $icon = plugins_url( 'images/playlist-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( 'labels' => array( - 'name' => __( 'Playlists', 'radio-station' ), - 'singular_name' => __( 'Playlist', 'radio-station' ), - 'add_new' => __( 'Add Playlist', 'radio-station' ), - 'add_new_item' => __( 'Add Playlist', 'radio-station' ), - 'edit_item' => __( 'Edit Playlist', 'radio-station' ), - 'new_item' => __( 'New Playlist', 'radio-station' ), - 'view_item' => __( 'View Playlist', 'radio-station' ), + 'name' => __( 'Playlists', 'radio-station' ), + 'singular_name' => __( 'Playlist', 'radio-station' ), + 'add_new' => __( 'Add Playlist', 'radio-station' ), + 'add_new_item' => __( 'Add Playlist', 'radio-station' ), + 'edit_item' => __( 'Edit Playlist', 'radio-station' ), + 'new_item' => __( 'New Playlist', 'radio-station' ), + 'view_item' => __( 'View Playlist', 'radio-station' ), // 2.3.0: added archive title label - 'archive_title' => __( 'Playlists', 'radio-station' ), + 'archive_title' => __( 'Playlists', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Playlists', 'radio-station' ), + 'not_found' => __( 'No Playlists found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Playlists found in Trash', 'radio-station' ), + 'all_items' => __( 'All Playlists', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Playlist descriptions', 'radio-station' ), + 'description' => __( 'Post type for Playlists', 'radio-station' ), 'public' => true, 'hierarchical' => false, // 2.3.0: added thumbnail, custom field and revision support @@ -123,19 +133,25 @@ function radio_station_create_post_types() { // $icon = plugins_url( 'images/show-menu-icon.png', RADIO_STATION_FILE ); $post_type = array( 'labels' => array( - 'name' => __( 'Schedule Overrides', 'radio-station' ), - 'singular_name' => __( 'Schedule Override', 'radio-station' ), - 'add_new' => __( 'Add Schedule Override', 'radio-station' ), - 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), - 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), - 'new_item' => __( 'New Schedule Override', 'radio-station' ), - 'view_item' => __( 'View Schedule Override', 'radio-station' ), + 'name' => __( 'Schedule Overrides', 'radio-station' ), + 'singular_name' => __( 'Schedule Override', 'radio-station' ), + 'add_new' => __( 'Add Schedule Override', 'radio-station' ), + 'add_new_item' => __( 'Add Schedule Override', 'radio-station' ), + 'edit_item' => __( 'Edit Schedule Override', 'radio-station' ), + 'new_item' => __( 'New Schedule Override', 'radio-station' ), + 'view_item' => __( 'View Schedule Override', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Overrides', 'radio-station' ), + 'not_found' => __( 'No Overrides found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Overrides found in Trash', 'radio-station' ), + 'all_items' => __( 'All Overrides', 'radio-station' ), + 'archive_title' => __( 'Overrides', 'radio-station' ), ), 'show_ui' => true, 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Schedule Override', 'radio-station' ), + 'description' => __( 'Post type for Schedule Overrides', 'radio-station' ), 'public' => true, // 2.3.0: added taxonomies to overrides 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), @@ -165,14 +181,19 @@ function radio_station_create_post_types() { $ui = apply_filters( 'radio_station_host_interface', false ); $post_type = array( 'labels' => array( - 'name' => __( 'Host Profiles', 'radio-station' ), - 'singular_name' => __( 'Host Profile', 'radio-station' ), - 'add_new' => __( 'New Host Profile', 'radio-station' ), - 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), - 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), - 'new_item' => __( 'New Host Profile', 'radio-station' ), - 'view_item' => __( 'View Host Profile', 'radio-station' ), - 'archive_title' => __( 'Show Hosts', 'radio-station' ), + 'name' => __( 'Host Profiles', 'radio-station' ), + 'singular_name' => __( 'Host Profile', 'radio-station' ), + 'add_new' => __( 'New Host Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), + 'new_item' => __( 'New Host Profile', 'radio-station' ), + 'view_item' => __( 'View Host Profile', 'radio-station' ), + 'archive_title' => __( 'Show Hosts', 'radio-station' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Hosts', 'radio-station' ), + 'not_found' => __( 'No Hosts found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Hosts found in Trash', 'radio-station' ), + 'all_items' => __( 'All Hosts', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -206,14 +227,19 @@ function radio_station_create_post_types() { $ui = apply_filters( 'radio_station_producer_interface', false ); $post_type = array( 'labels' => array( - 'name' => __( 'Producer Profiles', 'radio-station' ), - 'singular_name' => __( 'Producer Profile', 'radio-station' ), - 'add_new' => __( 'New Producer Profile', 'radio-station' ), - 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), - 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), - 'new_item' => __( 'New Producer Profile', 'radio-station' ), - 'view_item' => __( 'View Producer Profile', 'radio-station' ), - 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), + 'name' => __( 'Producer Profiles', 'radio-station' ), + 'singular_name' => __( 'Producer Profile', 'radio-station' ), + 'add_new' => __( 'New Producer Profile', 'radio-station' ), + 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), + 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), + 'new_item' => __( 'New Producer Profile', 'radio-station' ), + 'view_item' => __( 'View Producer Profile', 'radio-station' ), + 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), + // 2.3.2: added missing post type labels + 'search_items' => __( 'Search Producers', 'radio-station' ), + 'not_found' => __( 'No Producers found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Producers found in Trash', 'radio-station' ), + 'all_items' => __( 'All Producers', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -253,7 +279,14 @@ function radio_station_create_post_types() { add_filter( 'gutenberg_can_edit_post_type', 'radio_station_post_type_editor', 20, 2 ); add_filter( 'use_block_editor_for_post_type', 'radio_station_post_type_editor', 20, 2 ); function radio_station_post_type_editor( $can_edit, $post_type ) { - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + // 2.3.2: added host and producer slugs + $post_types = array( + RADIO_STATION_SHOW_SLUG, + RADIO_STATION_PLAYLIST_SLUG, + RADIO_STATION_OVERRIDE_SLUG, + RADIO_STATION_HOST_SLUG. + RADIO_STATION_PRODUCER_SLUG, + ); // 2.2.8: removed strict in_array checking if ( in_array( $post_type, $post_types ) ) { return false; @@ -273,11 +306,18 @@ function radio_station_add_featured_image_support() { // 2.3.0: add override thumbnail to theme support declaration $supported_types = get_theme_support( 'post-thumbnails' ); if ( false === $supported_types ) { - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + $post_types = array( + RADIO_STATION_SHOW_SLUG, + RADIO_STATION_OVERRIDE_SLUG, + RADIO_STATION_HOST_SLUG, + RADIO_STATION_PRODUCER_SLUG, + ); add_theme_support( 'post-thumbnails', $post_types ); } elseif ( is_array( $supported_types ) ) { $supported_types[0][] = RADIO_STATION_SHOW_SLUG; $supported_types[0][] = RADIO_STATION_OVERRIDE_SLUG; + $supported_types[0][] = RADIO_STATION_HOST_SLUG; + $supported_types[0][] = RADIO_STATION_PRODUCER_SLUG; add_theme_support( 'post-thumbnails', $supported_types[0] ); } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index f092fc0..c989296 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -8,6 +8,7 @@ // === Time Shortcodes === // - Radio Timezone Shortcode +// - Radio Clock Shortcode // === Archive Shortcodes === // - Archive List Shortcode Abstract // - Show Archive Shortcode @@ -40,6 +41,15 @@ add_shortcode( 'radio-timezone', 'radio_station_timezone_shortcode' ); function radio_station_timezone_shortcode( $atts = array() ) { + global $radio_station_data; + + // --- set shortcode instance --- + if ( isset( $radio_station_data['timezone_instance'] ) ) { + $instance = $radio_station_data['timezone_instance']; + } else { + $instance = $radio_station_data['timezone_instance'] = 0; + } + // --- get radio timezone values --- $timezone = radio_station_get_setting( 'timezone_location' ); if ( !$timezone || ( '' == $timezone ) ) { @@ -77,28 +87,157 @@ function radio_station_timezone_shortcode( $atts = array() ) { } } if ( $offset > 0 ) { - $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' +' . $offset . ']'; + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . '+' . $offset . ']'; } else { - $utc_offset = '[' . __( 'UTC', 'radio-station' ) . ' ' . $offset . ']'; + $utc_offset = '[' . __( 'UTC', 'radio-station' ) . $offset . ']'; } } $code = radio_station_get_timezone_code( $timezone ); - $timezone_display = $code . ' ' . $utc_offset; + // 2.3.2: display full timezone location as well + $location = str_replace( '/', ', ', $timezone ); + $location = str_replace( '_', ' ', $location ); + $timezone_display = $code . ' (' . $location . ') ' . $utc_offset; } // --- set shortcode output --- $output = '
    '; - $output .= ''; + + // --- radio timezone --- + $output .= '
    '; $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); + $output .= '
    : '; + $output .= '
    ' . esc_html( $timezone_display ) . '
    '; + + // --- user timezone --- + $output .= ''; + $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ); $output .= ': '; - $output .= '' . esc_html( $timezone_display ) . ''; + $output .= ''; + + // 2.3.2 allow for timezone selector test + $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); + if ( '' != $select ) { + $output .= $select; + } + $output .= '
    '; + // --- enqueue shortcode styles --- + // 2.3.2: added for timezone shortcode styles + radio_station_enqueue_style( 'shortcodes' ); + // --- filter and return --- $output = apply_filters( 'radio_station_timezone_shortcode', $output, $atts ); return $output; } +// --------------------- +// Radio Clock Shortcode +// --------------------- +add_shortcode( 'radio-clock', 'radio_station_clock_shortcode' ); +function radio_station_clock_shortcode( $atts = array() ) { + + global $radio_station_data; + + // --- set shortcode instance --- + if ( isset( $radio_station_data['clock_instance'] ) ) { + $instance = $radio_station_data['clock_instance']; + } else { + $instance = $radio_station_data['clock_instance'] = 0; + } + + $clock_format = radio_station_get_setting( 'clock_format' ); + $defaults = array( + 'time' => $clock_format, + 'seconds' => 1, + 'day' => 'full', // full / short / none + 'date' => 1, + 'month' => 'full', // full / short / none + 'zone' => 1, + 'widget' => 0, + ); + $atts = shortcode_atts( $defaults, $atts, 'radio-clock' ); + + // --- set clock display classes --- + $classes = array( 'radio-station-clock' ); + if ( $atts['widget'] ) { + $classes[] = 'radio-station-clock-widget'; + } else { + $classes[] = 'radio-station-clock-shortcode'; + } + if ( 24 == $atts['time'] ) { + $classes[] = 'format-24'; + } else { + $classes[] = 'format-12'; + } + if ( $atts['seconds'] ) { + $classes[] = 'seconds'; + } + if ( $atts['day'] ) { + if ( 'full' == $atts['day'] ) { + $classes[] = 'day'; + } elseif ( 'short' == $atts['day'] ) { + $classes[] = 'day-short'; + } + } + if ( $atts['date'] ) { + $classes[] = 'date'; + } + if ( $atts['month'] ) { + if ( 'full' == $atts['month'] ) { + $classes[] = 'month'; + } elseif ( 'short' == $atts['month'] ) { + $classes[] = 'month-short'; + } + } + if ( $atts['zone'] ) { + $classes[] = 'zone'; + } + + // -- open clock div --- + $classlist = implode( ' ', $classes ); + $clock = '
    '; + + // --- server clock --- + $clock .= '
    '; + $clock .= '
    '; + $clock .= esc_html( __( 'Radio Time', 'radio-station' ) ); + $clock .= ':
    '; + $clock .= '
    '; + $clock .= '
    '; + $clock .= '
    '; + $clock .= '
    '; + + // --- user clock --- + $clock .= '
    '; + $clock .= '
    '; + $clock .= esc_html( __( 'Your Time', 'radio-station' ) ); + $clock .= ':
    '; + $clock .= '
    '; + $clock .= '
    '; + $clock .= '
    '; + $clock .= '
    '; + + // 2.3.2 allow for timezone selector test + // $select = radio_station_timezone_select( 'radio-station-clock-' + $instance ); + $select = apply_filters( 'radio_station_clock_timezone_select', '', 'radio-station-clock-' . $instance, $atts ); + if ( '' != $select ) { + $clock .= $select; + } + + $clock .= '
    '; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + // --- enqueue clock javascript --- + radio_station_enqueue_script( 'radio-station-clock', array(), true ); + + // --- filter and return --- + $clock = apply_filters( 'radio_station_clock', $clock, $atts ); + + return $clock; +} // -------------------------- // === Archive Shortcodes === @@ -305,13 +444,18 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } } - // --- get meridiem conversions --- - // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); + // --- set time data formats --- + // 2.3.0: added once-off meridiem pre-conversions + // 2.3.2: replaced meridiem conversions with data formats + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; } - + $start_data_format = 'j, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $type . '-archive', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $type . '-archive', $atts ); + // --- check for results --- $list = '
    '; if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { @@ -378,34 +522,32 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $list .= "
    "; // --- convert date info --- - $day = date( 'l', strtotime( $datetime['date'] ) ); - $display_day = radio_station_translate_weekday( $day ); + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $date_time = radio_station_to_time( $datetime['date'] ); + // $day = radio_station_get_time( 'day', $date_time ); + // $display_day = radio_station_translate_weekday( $day ); $start = $datetime['start_hour'] . ':' . $datetime['start_min'] . ' ' . $datetime['start_meridian']; $end = $datetime['end_hour'] . ':' . $datetime['end_min'] . ' ' . $datetime['end_meridian']; - $shift_start_time = strtotime( $datetime['day'] . ' ' . $start ); - $shift_end_time = strtotime( $datetime['day'] . ' ' . $end ); + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $shift_start_time = radio_station_to_time( $datetime['day'] . ' ' . $start_time ); + $shift_end_time = radio_station_to_time( $datetime['day'] . ' ' . $end_time ); if ( $shift_start_time > $shift_end_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } // --- convert shift times --- - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $start, 24 ); - $end = radio_station_convert_shift_time( $end, 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - } else { - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $start ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $end ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - } - + // 2.3.2: use time formats with translations + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); + // 2.3.1: fix to append not echo override date to archive list - $list .= ''; - $list .= esc_html( $display_day ) . ', ' . $start . ''; + $list .= '' . esc_html( $start ) . ''; $list .= ' - '; - $list .= '' . $end . ''; + $list .= '' . esc_html( $end ) . ''; $list .= "
    "; } @@ -1057,11 +1199,25 @@ function radio_station_current_show_shortcode( $atts ) { global $radio_station_data; + // --- set widget instance ID --- + // 2.3.2: added for AJAX loading + if ( !isset( $radio_station_data['current_show_instance'] ) ) { + $radio_station_data['current_show_instance'] = 0; + } + $radio_station_data['current_show_instance']++; + $output = ''; + // 2.3.2: get default AJAX load settings + $ajax = radio_station_get_setting( 'ajax_widgets' ); + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + $dynamic = apply_filters( 'radio_station_current_show_dynamic', 0, $atts ); + // --- get shortcode attributes --- // 2.3.0: set default default_name text // 2.3.0: set default time format to plugin setting + // 2.3.2: added AJAX load attribute + // 2.3.2: added for_time attribute $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( // --- legacy options --- @@ -1081,9 +1237,11 @@ function radio_station_current_show_shortcode( $atts ) { 'title_position' => 'right', 'link_hosts' => 0, 'countdown' => 0, - 'dynamic' => 0, + 'ajax' => $ajax, + 'dynamic' => $dynamic, 'widget' => 0, 'id' => '', + 'for_time' => 0, ); // 2.3.0: convert old attributes for DJs to hosts if ( isset( $atts['display_djs'] ) && !isset( $atts['display_hosts'] ) ) { @@ -1097,6 +1255,44 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: renamed shortcode identifier to current-show $atts = shortcode_atts( $defaults, $atts, 'current-show' ); + // 2.3.2: enqueue countdown script earlier + if ( $atts['countdown'] ) { + do_action( 'radio_station_countdown_enqueue' ); + } + + // --- maybe do AJAX load --- + // 2.3.2 added widget AJAX loading + $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'current-show', $atts['widget'] ); + if ( 'on' == $atts['ajax'] ) { + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + + // --- AJAX load via iframe --- + $ajax_url = admin_url( 'admin-ajax.php' ); + $instance = $radio_station_data['current_show_instance']; + $html = '
    '; + $html .= ''; + $html .= ""; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + return $html; + } + } + // 2.3.0: maybe set float class and avatar width style $widthstyle = $floatclass = ''; if ( !empty( $atts['avatar_width'] ) ) { @@ -1108,13 +1304,6 @@ function radio_station_current_show_shortcode( $atts ) { $floatclass = ' float-right'; } - // --- get meridiem conversions --- - // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); - } - // --- maybe filter excerpt values --- // 2.3.0: added context specific excerpt value filtering if ( $atts['show_desc'] ) { @@ -1128,20 +1317,27 @@ function radio_station_current_show_shortcode( $atts ) { } // --- get current show --- + // note: current show is not split shift // 2.3.0: use new get current show function - $current_shift = radio_station_get_current_show(); - + // 2.3.2: added attribute to pass time argument + if ( $atts['for_time'] ) { + $current_shift = radio_station_get_current_show( $atts['for_time'] ); + } else { + $current_shift = radio_station_get_current_show(); + } + // --- open shortcode div wrapper --- if ( !$atts['widget'] ) { // 2.3.0: add unique id to widget shortcode + // 2.3.2: add shortcode wrap class if ( !isset( $radio_station_data['widgets']['current-show'] ) ) { $radio_station_data['widgets']['current-show'] = 0; } else { $radio_station_data['widgets']['current-show']++; } $id = 'current-show-widget-' . $radio_station_data['widgets']['current-show']; - $output .= '
    '; + $output .= '
    '; // --- shortcode only title --- if ( !empty( $atts['title'] ) ) { @@ -1171,6 +1367,17 @@ function radio_station_current_show_shortcode( $atts ) { } else { + // --- get time formats --- + // 2.3.2: moved out to get once + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; + } + $start_data_format = 'l, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'current-show', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'current-show', $atts ); + // --- set html output --- // 2.3.1: store all HTML to allow section re-ordering $html = array( 'title' => '' ); @@ -1190,7 +1397,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- check show schedule --- // 2.3.1: check early for later display if ( $atts['show_sched'] || $atts['show_all_sched'] ) { - + $shift_display = '
    '; // --- show times subheading --- @@ -1203,7 +1410,8 @@ function radio_station_current_show_shortcode( $atts ) { // --- maybe show all shifts --- // (only if not a schedule override) - if ( !isset( $show['override'] ) && $atts['show_all_sched'] ) { + // 2.3.2: fix to override variable key check + if ( !isset( $current_shift['override'] ) && $atts['show_all_sched'] ) { $shifts = radio_station_get_show_schedule( $show['id'] ); } else { $shifts = array( $current_shift ); @@ -1211,7 +1419,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- get weekdates --- // 2.3.0: use dates for reliability - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); @@ -1220,7 +1428,8 @@ function radio_station_current_show_shortcode( $atts ) { // --- convert shift info --- // 2.2.2: translate weekday for display // 2.3.0: use dates for reliability - $display_day = radio_station_translate_weekday( $shift['day'] ); + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first $weekdate = $weekdates[$shift['day']]; if ( isset( $shift['start'] ) ) { $start = $shift['start']; @@ -1228,25 +1437,22 @@ function radio_station_current_show_shortcode( $atts ) { } else { $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - } - $shift_start_time = strtotime( $weekdates[$shift['day']] . ' ' . $start ); - $shift_end_time = strtotime( $weekdates[$shift['day']] . ' ' . $end ); + } + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); if ( $shift_start_time > $shift_end_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - // --- convert shift times --- - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $start, 24 ); - $end = radio_station_convert_shift_time( $end, 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - } else { - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $start ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $end ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - } + // --- get shift display times --- + // 2.3.2: use time formats with translations + // $display_day = radio_station_translate_weekday( $shift['day'] ); + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); // --- set shift classes --- $classes = array( 'current-show-shifts', 'on-air-dj-sched' ); @@ -1257,10 +1463,9 @@ function radio_station_current_show_shortcode( $atts ) { $class = implode( ' ', $classes ); $current_shift_display = '
    '; - $current_shift_display .= ''; - $current_shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $current_shift_display .= '' . esc_html( $start ) . ''; $current_shift_display .= ' - '; - $current_shift_display .= '' . $end . ''; + $current_shift_display .= '' . esc_html( $end ) . ''; $current_shift_display .= '
    '; } $class = implode( ' ', $classes ); @@ -1270,10 +1475,9 @@ function radio_station_current_show_shortcode( $atts ) { if ( in_array( 'current-shift', $classes ) ) { $shift_display .= '
    • '; } - $shift_display .= ''; - $shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; - $shift_display .= '' . $end . ''; + $shift_display .= '' . esc_html( $end ) . ''; if ( in_array( 'current-shift', $classes ) ) { $shift_display .= '
    '; } @@ -1293,12 +1497,6 @@ function radio_station_current_show_shortcode( $atts ) { $title .= '
    '; $html['title'] = $title; - // --- show title (above only) --- - // if ( 'above' == $atts['title_position'] ) { - // $html['title'] .= $title; // already escaped - // $output .= $html['title']; - // } - // --- show avatar --- if ( $atts['show_avatar'] ) { @@ -1308,7 +1506,7 @@ function radio_station_current_show_shortcode( $atts ) { $show_avatar = radio_station_get_show_avatar( $show['id'] ); $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show['id'], $atts ); if ( $show_avatar ) { - $html['avatar'] .= '
    '; + $html['avatar'] = '
    '; if ( $show_link ) { $html['avatar'] .= '' . $show_avatar . ''; } else { @@ -1318,12 +1516,6 @@ function radio_station_current_show_shortcode( $atts ) { } } - // --- show title (all other positions) --- - // if ( 'above' != $atts['title_position'] ) { - // $html['title'] .= $title; // already escaped - // $output .= $html['title']; - // } - // --- show DJs / hosts --- if ( $atts['display_hosts'] ) { @@ -1370,14 +1562,12 @@ function radio_station_current_show_shortcode( $atts ) { } } $html['hosts'] .= '
    '; - // $output .= $html['hosts']; } } // --- output current shift display --- if ( $atts['show_sched'] && isset( $current_shift_display ) ) { $html['shift'] = $current_shift_display; - // $output .= $html['shift']; } // --- encore presentation --- @@ -1386,7 +1576,6 @@ function radio_station_current_show_shortcode( $atts ) { $html['encore'] = '
    '; $html['encore'] .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); $html['encore'] .= '
    '; - // $output .= $html['encore']; } $html['clear'] = ''; @@ -1411,7 +1600,6 @@ function radio_station_current_show_shortcode( $atts ) { // --- countdown timer display --- if ( isset( $current_shift_end ) && $atts['countdown'] ) { $html['countdown'] = '
    '; - do_action( 'radio_station_countdown_enqueue' ); } // --- show description --- @@ -1429,6 +1617,8 @@ function radio_station_current_show_shortcode( $atts ) { $excerpt .= ' ' . $more . ''; } else { $excerpt = ""; + $excerpt .= ""; + $excerpt .= ""; $excerpt .= radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } @@ -1446,10 +1636,9 @@ function radio_station_current_show_shortcode( $atts ) { $html['description'] .= '
    '; } - // $html['clear'] = ''; - // --- output full show schedule --- - if ( $atts['show_all_sched'] ) { + // 2.3.2: do not display all shifts for overrides + if ( $atts['show_all_sched'] && !isset( $current_shift['override'] ) ) { $html['schedule'] = $shift_display; } @@ -1509,6 +1698,71 @@ function radio_station_current_show_shortcode( $atts ) { return $output; } +// ------------------------ +// AJAX Current Show Loader +// ------------------------ +// 2.3.2: added AJAX current show loader +add_action( 'wp_ajax_radio_station_current_show', 'radio_station_current_show' ); +add_action( 'wp_ajax_nopriv_radio_station_current_show', 'radio_station_current_show' ); +function radio_station_current_show() { + + // --- sanitize shortcode attributes --- + $atts = radio_station_sanitize_shortcode_values( 'current-show' ); + + if ( RADIO_STATION_DEBUG ) { + echo "Current Show Transient 1: " . PHP_EOL; + if ( !isset( $atts['for_time'] ) || !$atts['for_time'] ) { + print_r( get_transient( 'radio_station_current_show' ) ); + } else { + print_r( get_transient( 'radio_station_current_show_' . $atts['for_time'] ) ); + } + } + + // if ( !isset( $atts['for_time'] ) || !$atts['for_time'] ) { + // delete_transient( 'radio_station_current_show' ); + // } else { + // delete_transient( 'radio_station_current_show_' . $atts['for_time'] ); + // } + + // --- output widget contents --- + echo '
    '; + echo radio_station_current_show_shortcode( $atts ); + echo '
    '; + + if ( RADIO_STATION_DEBUG ) { + echo "Current Show Transient 2: " . PHP_EOL; + if ( !isset( $atts['for_time'] ) || !$atts['for_time'] ) { + print_r( get_transient( 'radio_station_current_show' ) ); + } else { + print_r( get_transient( 'radio_station_current_show_' . $atts['for_time'] ) ); + } + } + + $js = ''; + if ( isset( $atts['instance'] ) ) { + + // --- send to parent window --- + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; + $js .= "parent.document.getElementById('rs-current-show-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + + // --- maybe restart countdowns --- + if ( $atts['countdown'] ) { + $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; + } + + } + + // --- filter load script --- + $js = apply_filters( 'radio_station_current_show_load_script', $js, $atts ); + + // --- output javascript + if ( '' != $js ) { + echo ""; + } + + exit; +} + // ------------------------ // Upcoming Shows Shortcode // ------------------------ @@ -1522,9 +1776,23 @@ function radio_station_upcoming_shows_shortcode( $atts ) { global $radio_station_data; + // --- set widget instance ID --- + // 2.3.2: added for AJAX loading + if ( !isset( $radio_station_data['upcoming_shows_instance'] ) ) { + $radio_station_data['upcoming_shows_instance'] = 0; + } + $radio_station_data['upcoming_shows_instance']++; + $output = ''; + // 2.3.2: get default AJAX load settings + $ajax = radio_station_get_setting( 'ajax_widgets' ); + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + $dynamic = apply_filters( 'radio_station_upcoming_shows_dynamic', 0, $atts ); + // 2.3.0: set default time format to plugin setting + // 2.3.2: added AJAX load attribute + // 2.3.2: added for_time attribute $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( // --- legacy options --- @@ -1543,9 +1811,11 @@ function radio_station_upcoming_shows_shortcode( $atts ) { 'avatar_width' => '', 'title_position' => 'right', 'countdown' => 0, - 'dynamic' => 0, + 'ajax' => $ajax, + 'dynamic' => $dynamic, 'widget' => 0, 'id' => '', + 'for_time' => 0, ); // 2.3.0: convert old attributes for DJs to hosts if ( isset( $atts['display_djs'] ) && !isset( $atts['display_hosts'] ) ) { @@ -1559,6 +1829,44 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: renamed shortcode identifier to upcoming-shows $atts = shortcode_atts( $defaults, $atts, 'upcoming-shows' ); + // 2.3.2: enqueue countdown script earlier + if ( $atts['countdown'] ) { + do_action( 'radio_station_countdown_enqueue' ); + } + + // --- maybe do AJAX load --- + // 2.3.2 added widget AJAX loading + $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'upcoming-shows', $atts['widget'] ); + if ( 'on' == $atts['ajax'] ) { + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + + // --- AJAX load via iframe --- + $ajax_url = admin_url( 'admin-ajax.php' ); + $instance = $radio_station_data['upcoming_shows_instance']; + $html = '
    '; + $html .= ''; + $html .= ""; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + return $html; + } + } + // 2.2.4: maybe set float class and avatar width style // 2.3.0: moved here from upcoming widget class $width_style = $float_class = ''; @@ -1571,31 +1879,30 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $float_class = ' float-right'; } - // --- get meridiem conversions --- - // 2.3.0: added once-off pre-conversions - if ( 12 == (int) $atts['time'] ) { - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); - } - // --- get the upcoming shows --- + // note: upcoming shows are not split shift // 2.3.0: use new get next shows function - $shows = radio_station_get_next_shows( $atts['limit'] ); + if ( $atts['for_time'] ) { + $shows = radio_station_get_next_shows( $atts['limit'], false, $atts['for_time'] ); + } else { + $shows = radio_station_get_next_shows( $atts['limit'] ); + } if ( RADIO_STATION_DEBUG ) { - $output .= '' . print_r( $shows, true ) . ''; + $output .= ''; } // --- open shortcode only wrapper --- if ( !$atts['widget'] ) { // 2.3.0: add unique id to widget shortcode + // 2.3.2: add shortcode wrap class if ( !isset( $radio_station_data['widgets']['upcoming-shows'] ) ) { $radio_station_data['widgets']['upcoming-shows'] = 0; } else { $radio_station_data['widgets']['upcoming-shows']++; } $id = 'upcoming-shows-widget-' . $radio_station_data['widgets']['upcoming-shows']; - $output .= '
    '; + $output .= '
    '; // --- maybe output shortcode title --- if ( !empty( $atts['title'] ) ) { @@ -1624,6 +1931,18 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } else { + // --- set shift display data formats --- + // 2.2.7: fix to convert time to integer + // 2.3.2: moved outside shift loop + if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; + } else { + $start_data_format = $end_data_format = 'g:i a'; + } + $start_data_format = 'l, ' . $start_data_format; + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'upcoming-shows', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'upcoming-shows', $atts ); + // --- loop upcoming shows --- foreach ( $shows as $i => $shift ) { @@ -1660,15 +1979,20 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- convert dates --- // 2.3.0: use weekdates for reliability - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- convert shift info --- // 2.2.2: fix to weekday value to be translated - $display_day = radio_station_translate_weekday( $shift['day'] ); - $shift_start_time = strtotime ( $weekdates[$shift['day']] . ' ' . $shift['start'] ); - $shift_end_time = strtotime( $weekdates[$shift['day']] . ' ' . $shift['end'] ); + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: use exact shift date in time calculations + // 2.3.2: fix to convert to 24 hour format first + // $display_day = radio_station_translate_weekday( $shift['day'] ); + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $shift_start_time = radio_station_to_time( $shift['date'] . ' ' . $shift_start ); + $shift_end_time = radio_station_to_time( $shift['date'] . ' ' . $shift_end ); if ( $shift_end_time < $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -1686,30 +2010,18 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } $class = implode( ' ', $classes ); - // --- maybe convert times --- - // 2.2.7: fix to convert time to integer - if ( 24 == (int) $atts['time'] ) { - - // --- convert start/end time to 24 hours --- - $start = radio_station_convert_shift_time( $shift['start'], 24 ); - $end = radio_station_convert_shift_time( $shift['end'], 24 ); - $data_format = 'j, H:i'; - $data_format2 = 'H:i'; - - } else { - - $start = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['start'] ); - $end = str_replace( array( 'am', 'pm' ), array( $am, $pm ), $shift['end'] ); - $data_format = 'j, g:i a'; - $data_format2 = 'g:i a'; - - } + // --- get shift display times --- + // 2.3.2: use time formats with translations + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); + // --- set shift display output --- $shift_display .= '
    '; - $shift_display .= ''; - $shift_display .= esc_html( $display_day ) . ', ' . $start . ''; + $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; - $shift_display .= '' . $end . ''; + $shift_display .= '' . esc_html( $end ) . ''; $shift_display .= '
    '; $shift_display .= '
    '; @@ -1725,12 +2037,6 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $title .= '
    '; $html['title'] = $title; - // --- show title (above position only) --- - // (for above position only) - // if ( 'above' == $atts['title_position'] ) { - // $output .= $title; // already escaped - // } - // --- show avatar --- if ( $atts['show_avatar'] ) { @@ -1752,12 +2058,6 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } } - // --- show title (all other positions) --- - // (for all positions except above) - // if ( 'above' != $atts['title_position'] ) { - // $output .= $title; // already escaped - // } - // $output .= ''; // --- DJ / Host names --- @@ -1854,9 +2154,6 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: added for countdowns if ( isset( $next_start_time ) && ( $atts['countdown'] || $atts['dynamic'] ) ) { - // --- enqueue countdown javascript --- - do_action( 'radio_station_countdown_enqueue' ); - // --- hidden input for next start time --- $output .= ''; if ( RADIO_STATION_DEBUG ) { @@ -1888,6 +2185,53 @@ function radio_station_upcoming_shows_shortcode( $atts ) { return $output; } +// -------------------------- +// AJAX Upcoming Shows Loader +// -------------------------- +// 2.3.2: added AJAX upcoming shows loader +add_action( 'wp_ajax_radio_station_upcoming_shows', 'radio_station_upcoming_shows' ); +add_action( 'wp_ajax_nopriv_radio_station_upcoming_shows', 'radio_station_upcoming_shows' ); +function radio_station_upcoming_shows() { + + // --- sanitize shortcode attributes --- + $atts = radio_station_sanitize_shortcode_values( 'upcoming-shows' ); + + // if ( !isset( $atts['for_time'] ) || !$atts['for_time'] ) { + // delete_transient( 'radio_station_next_shows' ); + // } else { + // delete_transient( 'radio_station_next_shows_' . $atts['for_time'] ); + // } + + // --- output widget contents --- + echo '
    '; + echo radio_station_upcoming_shows_shortcode( $atts ); + echo '
    '; + + $js = ''; + if ( isset( $atts['instance'] ) ) { + + // --- send to parent window --- + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; + $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + + // --- restart countdowns --- + if ( $atts['countdown'] ) { + $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; + } + + } + + // --- filter load script --- + $js = apply_filters( 'radio_station_upcoming_shows_load_script', $js, $atts ); + + // --- output javascript + if ( '' != $js ) { + echo ""; + } + + exit; +} + // --------------------- // Now Playing Shortcode // --------------------- @@ -1899,9 +2243,23 @@ function radio_station_current_playlist_shortcode( $atts ) { global $radio_station_data; + // --- set widget instance ID --- + // 2.3.2: added for AJAX loading + if ( !isset( $radio_station_data['current_playlist_instance'] ) ) { + $radio_station_data['current_playlist_instance'] = 0; + } + $radio_station_data['current_playlist_instance']++; + $output = ''; + // 2.3.2: get default AJAX load settings + $ajax = radio_station_get_setting( 'ajax_widgets' ); + $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; + $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', 0, $atts ); + // --- get shortcode attributes --- + // 2.3.2: added AJAX load attribute + // 2.3.2: added for_time attribute $defaults = array( // --- legacy options --- 'title' => '', @@ -1912,27 +2270,70 @@ function radio_station_current_playlist_shortcode( $atts ) { 'comments' => 0, // --- new options --- 'countdown' => 0, - 'dynamic' => 0, + 'ajax' => $ajax, + 'dynamic' => $dynamic, 'widget' => 0, 'id' => '', + 'for_time' => 0, ); // 2.3.0: renamed shortcode identifier to current-playlist $atts = shortcode_atts( $defaults, $atts, 'current-playlist' ); + // 2.3.2: enqueue countdown script earlier + if ( $atts['countdown'] ) { + do_action( 'radio_station_countdown_enqueue' ); + } + + // --- maybe do AJAX load --- + // 2.3.2 added widget AJAX loading + $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'current-playlist', $atts['widget'] ); + if ( 'on' == $atts['ajax'] ) { + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + + // --- AJAX load via iframe --- + $ajax_url = admin_url( 'admin-ajax.php' ); + $instance = $radio_station_data['current_playlist_instance']; + $html = '
    '; + $html .= ''; + $html .= ""; + + // --- enqueue shortcode styles --- + radio_station_enqueue_style( 'shortcodes' ); + + return $html; + } + } + // --- fetch the current playlist --- - $playlist = radio_station_get_now_playing(); + if ( $atts['for_time'] ) { + $playlist = radio_station_get_now_playing( $atts['for_time'] ); + } else { + $playlist = radio_station_get_now_playing(); + } // --- shortcode only wrapper --- if ( !$atts['widget'] ) { // 2.3.0: add unique id to widget shortcode + // 2.3.2: fix to shortcode classes + // 2.3.2: add shortcode wrap class if ( !isset( $radio_station_data['widgets']['current-playlist'] ) ) { $radio_station_data['widgets']['current-playlist'] = 0; } else { $radio_station_data['widgets']['current-playlist']++; } $id = 'show-playlist-widget-' . $radio_station_data['widgets']['current-playlist']; - $output .= '
    '; + $output .= '
    '; // --- shortcode title --- if ( !empty( $atts['title'] ) ) { @@ -2026,16 +2427,20 @@ function radio_station_current_playlist_shortcode( $atts ) { foreach ( $playlist['shifts'] as $shift ) { // --- convert shift info --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - $shift_start_time = strtotime( $start ); - $shift_end_time = strtotime( $end ); + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $shift_start_time = radio_station_to_time( $start_time ); + $shift_end_time = radio_station_to_time( $end_time ); if ( $start > $end ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } // --- check currently playing show time --- - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { // --- hidden input for playlist end time --- @@ -2044,7 +2449,6 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- for countdown timer display --- if ( $atts['countdown'] ) { $html['countdown'] .= '
    '; - do_action( 'radio_station_countdown_enqueue' ); } // --- for dynamic reloading --- @@ -2091,6 +2495,48 @@ function radio_station_current_playlist_shortcode( $atts ) { return $output; } +// ---------------------------- +// AJAX Current Playlist Loader +// ---------------------------- +// 2.3.2: added AJAX current playlist loader +add_action( 'wp_ajax_radio_station_current_playlist', 'radio_station_current_playlist' ); +add_action( 'wp_ajax_nopriv_radio_station_current_playlist', 'radio_station_current_playlist' ); +function radio_station_current_playlist() { + + // --- sanitize shortcode attributes --- + $atts = radio_station_sanitize_shortcode_values( 'current-playlist' ); + + // --- output widget contents --- + echo '
    '; + echo radio_station_current_playlist_shortcode( $atts ); + echo '
    '; + + $js = ''; + if ( isset( $atts['instance'] ) ) { + + // --- send to parent window --- + $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; + $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; + + // --- restart countdowns --- + if ( $atts['countdown'] ) { + $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; + } + + } + + // --- filter load script --- + $js = apply_filters( 'radio_station_current_playlist_load_script', $js, $atts ); + + // --- output javascript + if ( '' != $js ) { + echo ""; + } + + exit; +} + + // ---------------- // Countdown Script // ---------------- @@ -2102,11 +2548,11 @@ function radio_station_countdown_enqueue() { radio_station_enqueue_script( 'radio-station-countdown', array( 'radio-station' ), true ); // --- set countdown labels --- - $js = "radio.label_showstarted = '" . esc_js( __( 'This Show has started.', 'radio-station' ) ) . "'; - radio.label_showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "'; - radio.label_playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "'; - radio.label_timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "'; - radio.label_timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "'; + $js = "radio.labels.showstarted = '" . esc_js( __( 'This Show has started.', 'radio-station' ) ) . "'; + radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "'; + radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "'; + radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "'; + radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "'; "; // --- add script inline --- diff --git a/includes/support-functions.php b/includes/support-functions.php index bc59d10..b4173bd 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -47,12 +47,22 @@ // - Patreon Supporter Button // - Patreon Button Styles // - Send Directory Ping -// === Time Conversions -// - Get Timezones Options +// === Time Conversions === +// - Get Now +// - Get Timezone // - Get Timezone Code +// - Get Date Time Object +// - String To Time +// - Get Time +// - Get Timezone Options +// - Get Weekday(s) +// - Get Month(s) // - Get Schedule Weekdays +// - Get Schedule Weekdates // - Get Next Day // - Get Previous Day +// - Get Next Date +// - Get Previous Date // - Get All Hours // - Convert Hour to Time Format // - Convert Shift to Time Format @@ -67,11 +77,15 @@ // - Trim Excerpt // - Shorten String // - Sanitize Values +// - Sanitize Shortcode Values // === Translations === // - Translate Weekday +// - Replace Weekdays // - Translate Month +// - Replace Months // - Translate Meridiem - +// - Replace Meridiems +// - Translate Time String // ---------------------- // === Data Functions === @@ -195,11 +209,29 @@ function radio_station_unique_shift_id() { return $unique_id; } +// -------------------- +// Generate Hashed GUID +// -------------------- +// 2.3.2: add hashing function for show GUID +function radio_station_get_show_guid( $show_id ) { + + global $wpdb; + $query = "SELECT guid FROM " . $wpdb->posts . " WHERE ID = %d"; + $guid = $wpdb->get_var( $query ); + if ( !$guid ) { + $guid = get_permalink( $show_id ); + } + $hash = md5( $guid ); + + return $hash; +} + // --------------- // Get Show Shifts // --------------- // 2.3.0: added get show shifts data grabber -function radio_station_get_show_shifts( $check_conflicts = true ) { +// 2.3.2: added second argument to get non-split shifts +function radio_station_get_show_shifts( $check_conflicts = true, $split = true ) { // --- get all shows --- $errors = array(); @@ -215,7 +247,7 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { } // --- get weekdates for checking --- - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); @@ -247,18 +279,22 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { } else { // --- shift is valid so continue checking --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first $day = $shift['day']; $thisdate = $weekdates[$day]; - $nextday = date( 'Y-m-d', ( strtotime( $thisdate ) + ( 24 * 60 * 60 ) ) ); - $midnight = strtotime( $thisdate . ' 11:59:59 am' ) + 1; + $midnight = radio_station_to_time( $thisdate . ' 23:59:59' ) + 1; $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $start_time = radio_station_convert_shift_time( $start ); + $start_time = radio_station_to_time( $thisdate . ' ' . $start_time ); if ( ( '11:59:59 pm' == $end ) || ( '12:00 am' == $end ) ) { - $end_time = strtotime( $thisdate . ' 11:59:59 pm' ) + 1; + // 2.3.2: simplify using existing midnight time + $end_time = $midnight; } else { - $end_time = strtotime( $thisdate . ' ' . $end ); + $end_time = radio_station_convert_shift_time( $end ); + $end_time = radio_station_to_time( $thisdate . ' ' . $end ); } - $start_time = strtotime( $thisdate . ' ' . $start ); if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { $encore = true; } else { @@ -266,12 +302,74 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { } $updated = $show->post_modified_gmt; - // --- check if show goes over midnight --- - if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { - + if ( $split ) { + + // --- check if show goes over midnight --- + if ( ( $end_time > $start_time ) || ( $end_time == $midnight ) ) { + + // --- set the shift time as is --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $show->ID] = array( + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => $end, + 'show' => $show->ID, + 'encore' => $encore, + 'split' => false, + 'updated' => $updated, + 'shift' => $shift, + 'override' => false, + ); + + } else { + + // --- split shift for this day --- + // 2.3.2: added date data + $all_shifts[$day][$start_time . '.' . $show->ID] = array( + 'day' => $day, + 'date' => $thisdate, + 'start' => $start, + 'end' => '11:59:59 pm', // midnight + 'show' => $show->ID, + 'split' => true, + 'encore' => $encore, + 'updated' => $updated, + 'shift' => $shift, + 'real_end' => $end, + 'override' => false, + ); + + // --- split shift for next day --- + // 2.3.2: added date data for next day + $nextday = radio_station_get_next_day( $day ); + $nextdate = $weekdates[$nextday]; + // 2.3.2: fix midnight timestamp for sorting + if ( strtotime( $nextdate ) < strtotime( $thisdate ) ) { + $midnight = radio_station_to_time( $nextdate . ' 00:00:00' ); + } + $all_shifts[$nextday][$midnight . '.' . $show->ID] = array( + 'day' => $nextday, + 'date' => $nextdate, + 'start' => '00:00 am', // midnight + 'end' => $end, + 'show' => $show->ID, + 'encore' => $encore, + 'split' => true, + 'updated' => $updated, + 'shift' => $shift, + 'real_start' => $start, + 'override' => false, + ); + } + + } else { + // --- set the shift time as is --- + // 2.3.2: added for non-split argument $all_shifts[$day][$start_time . '.' . $show->ID] = array( 'day' => $day, + 'date' => $thisdate, 'start' => $start, 'end' => $end, 'show' => $show->ID, @@ -281,35 +379,7 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { 'shift' => $shift, 'override' => false, ); - } else { - // --- split shift for this day --- - $all_shifts[$day][$start_time . '.' . $show->ID] = array( - 'day' => $day, - 'start' => $start, - 'end' => '11:59:59 pm', // midnight - 'show' => $show->ID, - 'split' => true, - 'encore' => $encore, - 'updated' => $updated, - 'shift' => $shift, - 'real_end' => $end, - 'override' => false, - ); - // --- split shift for next day --- - $nextday = radio_station_get_next_day( $day ); - $all_shifts[$nextday][$midnight . '.' . $show->ID] = array( - 'day' => $nextday, - 'start' => '00:00 am', // midnight - 'end' => $end, - 'show' => $show->ID, - 'encore' => $encore, - 'split' => true, - 'updated' => $updated, - 'shift' => $shift, - 'real_start' => $start, - 'override' => false, - ); } } } @@ -341,9 +411,19 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { } } + // --- reorder by weekdays --- + // 2.3.2: added for passing to shift checker + $sorted_shifts = array(); + foreach ( $weekdays as $weekday ) { + if ( isset( $all_shifts[$weekday] ) ) { + $sorted_shifts[$weekday] = $all_shifts[$weekday]; + } + } + $all_shifts = $sorted_shifts; + // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = "Sorted Shifts" . PHP_EOL . PHP_EOL . print_r( $all_shifts, true ); + $debug = "Sorted Shifts" . PHP_EOL . PHP_EOL . print_r( $sorted_shifts, true ); radio_station_debug( $debug ); } @@ -362,7 +442,9 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { } // --- shuffle shift days so today is first day --- - $today = date( 'l' ); + // 2.3.2: use get time function for day with timezone + // $today = date( 'l' ); + $today = radio_station_get_time( 'day' ); $day_shifts = array(); for ( $i = 0; $i < 7; $i ++ ) { if ( 0 == $i ) { @@ -381,24 +463,32 @@ function radio_station_get_show_shifts( $check_conflicts = true ) { return $day_shifts; } + + + // ---------------------- // Get Schedule Overrides // ---------------------- // 2.3.0: added get schedule overrides data grabber function radio_station_get_overrides( $start_date = false, $end_date = false ) { - // --- convert dates to times for checking + // --- convert dates to times for checking --- + // (we allow an extra day either way for overflows) + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to variable conflict with start_time/end_time if ( $start_date ) { - $start_time = strtotime( $start_date ); + // 2.3.2: added missing backwards day allowance + $range_start_time = radio_station_to_time( $start_date ) - ( 24 * 60 * 60 ) + 1; } if ( $end_date ) { - $end_time = strtotime( $end_date ) + ( 24 * 60 * 60 ) - 1; + $range_end_time = radio_station_to_time( $end_date ) + ( 24 * 60 * 60 ) - 1 ; } // --- get all override IDs --- global $wpdb; - $query = "SELECT ID,post_title,post_name FROM " . $wpdb->posts - . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "' AND post_status = 'publish'"; + $query = "SELECT ID,post_title,post_name FROM " . $wpdb->posts; + $query .= " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; + $query .= " AND post_status = 'publish'"; $overrides = $wpdb->get_results( $query, ARRAY_A ); if ( !$overrides || !is_array( $overrides ) || ( count( $overrides ) < 1 ) ) { return false; @@ -408,31 +498,40 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { $override_list = array(); foreach ( $overrides as $i => $override ) { $data = get_post_meta( $override['ID'], 'show_override_sched', true ); + if ( $data ) { $date = $data['date']; if ( '' != $date ) { - - $date_time = strtotime( $date ); + + // 2.3.2: replace strtotime with to_time for timezones + $date_time = radio_station_to_time( $date ); $inrange = true; // --- check if in specified date range --- - if ( ( isset( $start_time ) && ( $date_time < $start_time ) ) - || ( isset( $end_time ) && ( $date_time > $end_time ) ) ) { + if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) + || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { $inrange = false; } // --- add the override data --- if ( $inrange ) { - $thisdate = date( 'Y-m-d', $date_time ); - $thisday = date( 'l', $date_time ); + // 2.3.2: get day from date directly + // $thisday = date( 'l', $date_time ); + $day = date( 'l', strtotime( $date ) ); + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; - $override_start_time = strtotime( $thisdate . ' ' . $start ); - $override_end_time = strtotime( $thisdate . ' ' . $end ); - // if ( $override_end_time < $override_start_time ) { - // $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - // } + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $override_start_time = radio_station_to_time( $date . ' ' . $start_time ); + $override_end_time = radio_station_to_time( $date . ' ' . $end_time ); + // 2.3.2: fix for overrides ending at midnight + if ( '12:00 am' == $end ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } if ( $override_start_time < $override_end_time ) { @@ -441,8 +540,8 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { 'override' => $override['ID'], 'name' => $override['post_title'], 'slug' => $override['post_name'], - 'date' => $thisdate, - 'day' => $thisday, + 'date' => $date, + 'day' => $day, 'start' => $start, 'end' => $end, 'url' => get_permalink( $override['ID'] ), @@ -457,18 +556,24 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { 'override' => $override['ID'], 'name' => $override['post_title'], 'slug' => $override['post_name'], - 'date' => $thisdate, - 'day' => $thisday, + 'date' => $date, + 'day' => $day, 'start' => $start, - 'end' => '11:59 pm', + 'end' => '11:59:59 pm', 'real_end' => $end, 'url' => get_permalink( $override['ID'] ), 'split' => true, ); $override_list[$date][] = $override_data; - $nextdate = date( 'Y-m-d', $date_time + ( 24 * 60 * 60 ) ); - $nextday = date( 'l', $date_time + ( 24 * 60 * 60 ) ); + // --- set the next day split shift --- + // note: these should not wrap around to start of week + // 2.3.2: use get next date/day functions + // $nextday = date( 'l', $next_date_time ); + // $nextdate = date( 'Y-m-d', $next_date_time ); + $nextdate = radio_station_get_next_date( $date ); + $nextday = radio_station_get_next_day( $day ); + $override_data = array( 'override' => $override['ID'], 'name' => $override['post_title'], @@ -609,10 +714,13 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // --- maybe get additional data --- // TODO: maybe get additional data for each data type ? // if ( $args['data'] && $results && is_array( $results ) && ( count( $results ) > 0 ) ) { - // if ( 'posts' == $datatype ) { - // } elseif ( 'playlists' == $datatype ) { - // } elseif ( 'episodes' == $datatype ) { - // } + // if ( 'posts' == $datatype ) { + // + // } elseif ( 'playlists' == $datatype ) { + // + // } elseif ( 'episodes' == $datatype ) { + // + // } // } // --- maybe cache default show data --- @@ -654,6 +762,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { } // --- get show data --- + // note: show email intentionally not included // $show_email = get_post_meta( $show->ID, 'show_email', true ); $show_link = get_post_meta( $show->ID, 'show_link', true ); $show_file = get_post_meta( $show->ID, 'show_file', true ); @@ -723,7 +832,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { 'url' => get_permalink( $show->ID ), 'latest' => $show_file, 'website' => $show_link, - // note: left out intentionally to avoid spam scrapers + // note: left out intentionally to avoid spam scraping // 'email' => $show_email, 'hosts' => $hosts, 'producers' => $producers, @@ -858,16 +967,29 @@ function radio_station_get_override_data_meta( $override ) { // -------------------- // Get Current Schedule // -------------------- -function radio_station_get_current_schedule() { +// 2.3.2: added optional time argument +function radio_station_get_current_schedule( $time = false ) { global $radio_station_data; // --- maybe get cached schedule --- - $schedule = get_transient( 'radio_station_current_schedule' ); - if ( $schedule ) { - $schedule = apply_filters( 'radio_station_current_schedule', $schedule ); + if ( !$time ) { + $schedule = get_transient( 'radio_station_current_schedule' ); + if ( $schedule ) { + $schedule = apply_filters( 'radio_station_current_schedule', $schedule, false ); + if ( $schedule ) { + return $schedule; + } + } + } else { + // --- get schedule for time --- + // 2.3.2: added transient for time schedule + $schedule = get_transient( 'radio_station_current_schedule_' . $time ); if ( $schedule ) { - return $schedule; + $schedule = apply_filters( 'radio_station_current_schedule', $schedule, $time ); + if ( $schedule ) { + return $schedule; + } } } @@ -875,7 +997,11 @@ function radio_station_get_current_schedule() { $show_shifts = radio_station_get_show_shifts(); // --- get weekdates --- - $now = strtotime( current_time( 'mysql' ) ); + if ( !$time ) { + $now = radio_station_get_now(); + } else { + $now = $time; + } $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // 2.3.1: add empty keys to ensure overrides are checked @@ -894,7 +1020,9 @@ function radio_station_get_current_schedule() { // --- get show overrides --- // (from 12am this morning, for one week ahead and back) // 2.3.1: get start and end dates from weekdays - $date = date( 'd-m-Y', $now ); + // 2.3.2: use get time function with timezone + $date = radio_station_get_time( 'date' ); + // $start_time = strtotime( '12am ' . $date ); // $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; // $start_time = $start_time - ( 7 * 24 * 60 * 60 ) - 1; @@ -913,7 +1041,10 @@ function radio_station_get_current_schedule() { } // --- apply overrides to the schedule --- - $debugday = 'Tuesday'; + $debugday = 'Monday'; + if ( isset( $_REQUEST['debug-day'] ) ) { + $debugday = $_REQUEST['debug-day']; + } $done_overrides = array(); if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { foreach ( $show_shifts as $day => $shifts ) { @@ -922,7 +1053,9 @@ function radio_station_get_current_schedule() { if ( RADIO_STATION_DEBUG ) { echo "Override Date: " . $date . PHP_EOL; } - + + // 2.3.2: reset overrides for loop + $overrides = array(); if ( isset( $override_list[$date] ) ) { $overrides = $override_list[$date]; @@ -938,24 +1071,38 @@ function radio_station_get_current_schedule() { if ( $date == $override['date'] ) { if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { - // $show_shifts[$day][$override['start']] = $override; - $override_start_time = strtotime( $date . ' ' . $override['start'] ); - $override_end_time = strtotime( $date . ' ' . $override['end'] ); - if ( isset( $override['split'] ) && $override['split'] && ( '11:59 pm' == $override['end'] ) ) { - $override_end_time = $override_end_time + 60; + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $override_start = radio_station_convert_shift_time( $override['start'] ); + $override_end = radio_station_convert_shift_time( $override['end'] ); + $override_start_time = radio_station_to_time( $date . ' ' . $override_start ); + $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); + if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { + $override_end_time = $override_end_time + 1; } + // 2.3.2: fix for non-split overrides ending on midnight + if ( $override_end_time < $override_start_time ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } // --- check for overlapped shift (if any) --- // 2.3.1 added check for shift count if ( count( $shifts ) > 0 ) { foreach ( $shifts as $start => $shift ) { - $start_time = strtotime( $date . ' ' . $shift['start'] ); - $end_time = strtotime( $date . ' ' . $shift['end'] ); - if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59 pm' == $shift['end'] ) ) { - $end_time = $end_time + 60; + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $start_time = radio_station_to_time( $date . ' ' . $shift_start ); + $end_time = radio_station_to_time( $date . ' ' . $shift_end ); + if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { + $end_time = $end_time + 1; + } + // 2.3.2: fix for non-split shifts ending on midnight + if ( $end_time < $start_time ) { + $end_time = $end_time + ( 24 * 60 * 60 ); } - if ( RADIO_STATION_DEBUG && ( $day == $debugday ) ) { echo $shift['start'] . ': ' . $start_time . ' - ' . $shift['end'] . ': ' . $end_time . PHP_EOL; @@ -985,16 +1132,14 @@ function radio_station_get_current_schedule() { $show_shifts[$day][$override['end']] = $shift; } - } elseif ( $override_start_time == $start_time ) { // --- same start so overwrite the existing shift --- - // 2.3.1: track done instead of unsetting override + // 2.3.1: set override done instead of unsetting override $done_overrides[] = $date . '--' . $i; $show_shifts[$day][$start] = $override; - // check if there is remainder of existing show - // ...this should hopefully not happen! + // --- check if there is remainder of existing show --- if ( $override_end_time < $end_time ) { $shift['start'] = $override['end']; $shift['trimmed'] = 'start'; @@ -1040,7 +1185,8 @@ function radio_station_get_current_schedule() { // --- sort the shifts using 24 hour time --- $shifts = $show_shifts[$day]; if ( count( $shifts ) > 0 ) { - $new_shifts = array(); + // 2.3.2: fix to clear shift keys between days + $new_shifts = $shift_keys = array(); $keys = array_keys( $shifts ); foreach ( $keys as $i => $key ) { $converted = radio_station_convert_shift_time( $key, 24 ); @@ -1048,10 +1194,11 @@ function radio_station_get_current_schedule() { $keys[$key] = $shift_keys[$key] = $converted; } sort( $shift_keys ); - print_r( $shift_keys ); foreach ( $shift_keys as $shift_key ) { - $key = array_search( $shift_key, $keys ); - $new_shifts[$key] = $shifts[$key]; + if ( in_array( $shift_key, $keys ) ) { + $key = array_search( $shift_key, $keys ); + $new_shifts[$key] = $shifts[$key]; + } } $shifts = $show_shifts[$day] = $new_shifts; } @@ -1059,6 +1206,7 @@ function radio_station_get_current_schedule() { if ( RADIO_STATION_DEBUG ) { echo "Shift Keys: " . print_r( $keys, true ) . PHP_EOL; echo "Sorted Keys: " . print_r( $shift_keys, true ) . PHP_EOL; + echo "Sorted Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; } // --- add directly any remaining overrides --- @@ -1077,7 +1225,7 @@ function radio_station_get_current_schedule() { $shifts = $show_shifts[$day]; // ksort( $shifts ); if ( RADIO_STATION_DEBUG ) { - echo "Shifts: " . print_r( $shifts, true ) . PHP_EOL; + echo "New Day Shifts: " . print_r( $shifts, true ) . PHP_EOL; // echo "Done Overrides: " . print_r( $done_overrides, true ) . PHP_EOL; } } @@ -1089,6 +1237,7 @@ function radio_station_get_current_schedule() { } // --- loop all shifts to add show data --- + $set_prev_shift = $prev_shift_end = false; foreach ( $show_shifts as $day => $shifts ) { // 2.3.1: added check for shift count if ( count( $shifts ) > 0 ) { @@ -1096,15 +1245,15 @@ function radio_station_get_current_schedule() { // --- check if shift is an override --- if ( isset( $shift['override'] ) && $shift['override'] ) { + // ---- add the override data --- $override = radio_station_get_override_data_meta( $shift['override'] ); $shift['show'] = $show_shifts[$day][$start]['show'] = $override; } else { - $show_id = $shift['show']; - // --- get (or get stored) show data --- + $show_id = $shift['show']; if ( isset( $radio_station_data['show-' . $show_id] ) ) { $show = $radio_station_data['show-' . $show_id]; } else { @@ -1120,33 +1269,59 @@ function radio_station_get_current_schedule() { if ( !isset( $current_show ) ) { // --- get this shift start and end times --- - $shift_start = $weekdates[$day] . ' ' . $shift['start']; - $shift_end = $weekdates[$day] . ' ' . $shift['end']; - // if ( isset( $shift['split'] ) && $shift['split'] ) { - // $thisdate = date( 'Y-m-d', strtotime( $weekdates[$day] ) ); - // $shift_end = $thisdate . ' ' . $shift['real_end']; + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); + $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); + + // if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) { + // $nextdate = radio_station_get_time( 'date', $shift_end_time + ( 23 * 60 * 60 ) ); + // $shift_end = $nextdate[$day] . ' ' . $shift['real_end']; // } - $shift_start_time = strtotime( $shift_start ); - $shift_end_time = strtotime( $shift_end ); - // adjust for shifts ending past midnight + + // - adjust for shifts ending past midnight - if ( $shift_start_time > $shift_end_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - + // --- check if this is the currently scheduled show --- if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { + + if ( isset( $maybe_next_show ) ) { + unset( $maybe_next_show ); + } $shift['day'] = $day; $current_show = $shift; $expires = $shift_end_time - $now - 1; + // - cache for one hour max - if ( $expires > 3600 ) { $expires = 3600; - } // cache for one hour max - set_transient( 'radio_station_current_show', $current_show, $expires ); + } + // 2.3.2: set temporary transient if time is specified + if ( !$time ) { + set_transient( 'radio_station_current_show', $current_show, $expires ); + } else { + set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); + } + + } elseif ( $now > $shift_end_time ) { + + // 2.3.2: set previous shift flag + $set_prev_shift = true; + + } elseif ( ( $now < $shift_start_time ) && !isset( $maybe_next_show ) ) { + + // 2.3.2: set maybe next show + $maybe_next_show = $shift; + } // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = 'Now: ' . date( 'm-d H:i:s', $now ) . ' (' . $now . '])' . PHP_EOL; + $debug = 'Now: ' . $now . PHP_EOL; + $debug .= 'Date: ' . date( 'm-d H:i:s', $now ) . PHP_EOL; $debug .= 'Shift Start: ' . $shift_start . ' (' . $shift_start_time . ')' . PHP_EOL; $debug .= 'Shift End: ' . $shift_end . ' (' . $shift_end_time . ')' . PHP_EOL . PHP_EOL; if ( isset ( $current_show ) ) { @@ -1156,53 +1331,125 @@ function radio_station_get_current_schedule() { $debug .= PHP_EOL; if ( $now >= $shift_start_time ) {$debug .= "!A!";} if ( $now < $shift_end_time ) {$debug .= "!B!";} - radio_station_debug( $debug ); + echo $debug; + // radio_station_debug( $debug ); } } elseif ( isset( $current_show['split'] ) && $current_show['split'] ) { // --- skip second part of split shift for current shift --- + // (so that it is not set as the next show) unset( $current_show['split'] ); - // $current_show['end'] = $shift['end']; - // set_transient( 'radio_station_current_show', $current_show, $expires ); - - } elseif ( !isset( $next_show ) ) { - - // --- set next show transient --- - $shift['day'] = $day; - $next_show = $shift; - $shift_end_time = strtotime( $weekdates[$day] . ' ' . $shift['end'] ); - $next_expires = $shift_end_time - $now - 1; - if ( $next_expires > ( $expires + 3600 ) ) { - $next_expires = $expires + 3600; + + } + + // 2.3.2: change to logic to allow for no current show found + if ( !isset( $next_show ) ) { + + // --- get shift times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_time = radio_station_convert_shift_time( $shift['start'] ); + $end_time = radio_station_convert_shift_time( $shift['end'] ); + $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); + $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); + if ( $shift_start_time > $shift_end_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - set_transient( 'radio_station_next_show', $next_show, $next_expires ); - } elseif ( isset( $next_show['split'] ) && $next_show['split'] ) { + if ( isset( $current_show ) + || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { - // --- recombine split next shift --- - $next_show['end'] = $shift['end']; - unset( $next_show['split'] ); - set_transient( 'radio_station_next_show', $next_show, $next_expires ); + // --- set next show --- + // 2.3.2: set date for widget + $next_show['date'] = $weekdates[$day]; + $next_show = $shift; + } } + + // 2.3.2: maybe set previous shift end value + if ( $set_prev_shift ) { + $prev_shift_end = $shift_end_time; + } + } } } - // --- get next show if we did not find a current one --- + // --- maybe set next show transient --- + // 2.3.2: check for (possibly first) next show found + if ( !isset( $next_show ) && isset( $maybe_next_show ) ) { + $next_show = $maybe_next_show; + } + if ( isset( $next_show ) ) { + + // 2.3.2: recombine split shift end times + $shift = $next_show; + if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { + $next_show['end'] = $shift['real_end']; + unset( $next_show['split'] ); + } + + // 2.3.2: added check that expires is set + $next_expires = $shift_end_time - $now - 1; + if ( isset( $expires ) && ( $next_expires > ( $expires + 3600 ) ) ) { + $next_expires = $expires + 3600; + } + // 2.3.2: set temporary transient if time is specified + if ( !$time ) { + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } else { + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); + } + } + + if ( RADIO_STATION_DEBUG ) { + if ( !isset( $current_show ) ) { + $current_show = 'None'; + } + echo 'Current Show: ' . print_r( $current_show, true ) . ''; + } + + // --- get next show if we did not find one --- if ( !isset( $next_show ) ) { + + if ( RADIO_STATION_DEBUG ) { + echo "No Next Show Found. Rechecking..."; + } + // --- pass calculated shifts with limit of 1 --- - $next_shows = radio_station_get_next_shows( 1, $show_shifts ); - if ( count( $next_shows ) > 0 ) { + // 2.3.2: added time argument + $next_shows = radio_station_get_next_shows( 1, $show_shifts, $time ); + + // 2.3.2: set next show transient within next shows function + /* if ( count( $next_shows ) > 0 ) { + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first $next_show = $next_shows[0]; - $shift_end_time = strtotime( $weekdates[$next_show['day']] . ' ' . $next_show['end'] ); + $next_show_start = radio_station_convert_shift_time( $next_show['start'] ); + $next_show_end = radio_station_convert_shift_time( $next_show['end'] ); + $shift_start_time = radio_station_to_time( $weekdates[$next_show['day']] . ' ' . $next_show_start ); + $shift_end_time = radio_station_to_time( $weekdates[$next_show['day']] . ' ' . $next_show_end ); + + // 2.3.2: adjust for midnight - + if ( $shift_end_time < $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + $next_expires = $shift_end_time - $now - 1; - set_transient( 'radio_station_next_show', $next_show, $next_expires ); - } + + // 2.3.2: set temporary transient if time is specified + if ( !$time ) { + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } else { + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); + } + } */ } - // TODO: edge case where current show or next show is split + // TODO: handle possible edge case where current show or next show is split // ...but actually unfinished due to end of schedule week ? // --- debug point --- @@ -1223,11 +1470,17 @@ function radio_station_get_current_schedule() { // --- cache current schedule data --- if ( isset( $current_show ) ) { - set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); + // 2.3.2: set temporary transient if time is specified + if ( !$time ) { + set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); + } else { + set_transient( 'radio_station_current_schedule_' . $time, $show_shifts, $expires ); + } } // --- filter and return --- - $show_shifts = apply_filters( 'radio_station_current_schedule', $show_shifts ); + // 2.3.2: added time argument to filter + $show_shifts = apply_filters( 'radio_station_current_schedule', $show_shifts, $time ); return $show_shifts; } @@ -1236,19 +1489,34 @@ function radio_station_get_current_schedule() { // Get Current Show // ---------------- // 2.3.0: added new get current show function -function radio_station_get_current_show() { +// 2.3.2: added optional time argument +function radio_station_get_current_show( $time = false ) { + + $current_show = false; // --- get cached current show value --- - $current_show = get_transient( 'radio_station_current_show' ); + if ( !$time ) { + $current_show = get_transient( 'radio_station_current_show' ); + } else { + $current_show = get_transient( 'radio_station_current_show_' . $time ); + } // --- if not set it has expired so recheck schedule --- + // 2.3.2: fix for trailing space in transient name string if ( !$current_show ) { - $schedule = radio_station_get_current_schedule(); - $current_show = get_transient( 'radio_station_current_show ' ); + + if ( !$time ) { + $schedule = radio_station_get_current_schedule(); + $current_show = get_transient( 'radio_station_current_show' ); + } else { + $schedule = radio_station_get_current_schedule( $time ); + $current_show = get_transient( 'radio_station_current_show_' . $time ); + } } // --- filter and return --- - $current_show = apply_filters( 'radio_station_current_show', $current_show ); + // 2.3.2: added time argument to filter + $current_show = apply_filters( 'radio_station_current_show', $current_show, $time ); return $current_show; } @@ -1257,19 +1525,33 @@ function radio_station_get_current_show() { // Get Next Show // ------------- // 2.3.0: added new get next show function -function radio_station_get_next_show() { +// 2.3.2: added optional time argument +function radio_station_get_next_show( $time = false ) { + + $next_show = false; // --- get cached current show value --- - $next_show = get_transient( 'radio_station_next_show' ); + if ( !$time ) { + $next_show = get_transient( 'radio_station_next_show' ); + } else { + $next_show = get_transient( 'radio_station_next_show_' . $time ); + } // --- if not set it has expired so recheck schedule --- if ( !$next_show ) { - $schedule = radio_station_get_current_schedule(); - $next_show = get_transient( 'radio_station_next_show' ); + + if ( !$time ) { + $schedule = radio_station_get_current_schedule(); + $next_show = get_transient( 'radio_station_next_show' ); + } else { + $schedule = radio_station_get_current_schedule( $time ); + $next_show = get_transient( 'radio_station_next_show_' . $time ); + } } // --- filter and return --- - $next_show = apply_filters( 'radio_station_next_show', $next_show ); + // 2.3.2: added time argument to filter + $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); return $next_show; } @@ -1278,21 +1560,40 @@ function radio_station_get_next_show() { // Get Next Shows // -------------- // 2.3.0: added new get next shows function -function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { +// 2.3.2: added optional time argument +function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = false ) { // --- get all show shifts --- // (this check is needed to prevent an endless loop!) if ( !$show_shifts ) { - $show_shifts = radio_station_get_current_schedule(); + if ( !$time ) { + $show_shifts = radio_station_get_current_schedule(); + } else { + $show_shifts = radio_station_get_current_schedule( $time ); + } } // --- loop (remaining) shifts to add show data --- $next_shows = array(); - $current_split = false; - $now = strtotime( current_time( 'mysql' ) ); - $today = date( 'w', $now ); // numerical + // 2.3.2: maybe set provided time as now + if ( $time ) { + $now = $time; + } else { + $now = radio_station_get_now(); + } + + // 2.3.2: use get time function with timezone + // 2.3.2: fix to pass week day start as numerical (w) + $today = radio_station_get_time( 'w', $now ); $weekdays = radio_station_get_schedule_weekdays( $today ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + + if ( RADIO_STATION_DEBUG ) { + echo ''; + echo "***" . $today . "***"; + print_r( $weekdays ); + echo ''; + } $current_split = false; foreach ( $weekdays as $day ) { @@ -1301,10 +1602,43 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { foreach ( $shifts as $start => $shift ) { // --- get this shift start and end times --- - $shift_start = $weekdates[$day] . ' ' . $shift['start']; - $shift_end = $weekdates[$day] . ' ' . $shift['end']; - $shift_start_time = strtotime( $shift_start ); - $shift_end_time = strtotime( $shift_end ); + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); + $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); + + if ( RADIO_STATION_DEBUG ) { + echo ''; + echo $now . ' - ' . $shift_start_time . ' - ' . $shift_end_time . PHP_EOL; + echo print_r( $shift, true ) . PHP_EOL; + } + + // --- set current show --- + // 2.3.2: + if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + if ( !isset( $current_show ) ) { + // --- recombine possible split shift to set current show --- + $current_show = $shift; + if ( isset( $current_show['split'] ) && $current_show['split'] ) { + if ( isset( $current_show['real_start'] ) ) { + $current_show['start'] = $current_show['real_start']; + } elseif ( isset( $current_show['real_end'] ) ) { + $current_show['end'] = $current_show['real_end']; + } + } + $expires = $shift_end_time - $now - 1; + if ( $expires > 3600 ) { + $expires = 3600; + } + if ( !$time ) { + set_transient( 'radio_station_current_show', $current_show, $expires ); + } else { + set_transient( 'radio_station_current_show_' . $time, $current_show, $expires ); + } + } + } // --- check if show is upcoming --- if ( $now < $shift_start_time ) { @@ -1320,18 +1654,14 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { } elseif ( isset( $shift['split'] ) && $shift['split'] ) { // --- dedupe for shifts split overnight --- + if ( isset( $shift['real_end'] ) ) { + $shift['end'] = $shift['real_end']; + $current_split = true; + } + // (note: probably unnecessary) if ( isset( $shift['real_start'] ) ) { - - // --- get real start day and time --- $shift['day'] = radio_station_get_previous_day( $day ); $shift['start'] = $shift['real_start']; - - } elseif ( isset( $shift['real_end'] ) ) { - - // --- get real end time --- - $shift['end'] = $shift['real_end']; - $current_split = true; - } } else { @@ -1341,7 +1671,20 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { if ( !$skip ) { + // --- maybe set next show transient --- + if ( !isset( $next_show ) ) { + $next_show = $shift; + $next_expires = $shift_end_time - $now - 1; + if ( !$time ) { + set_transient( 'radio_station_next_show', $next_show, $next_expires ); + } else { + set_transient( 'radio_station_next_show_' . $time, $next_show, $next_expires ); + } + } + // --- add to next shows data --- + // 2.3.2: set date for widget display + $shift['date'] = $weekdates[$day]; $next_shows[] = $shift; // --- return if we have reached limit --- @@ -1350,8 +1693,11 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false ) { return $next_shows; } } - - } + } + + if ( RADIO_STATION_DEBUG ) { + echo ''; + } } } } @@ -1547,177 +1893,305 @@ function radio_station_check_shifts( $all_shifts ) { // TODO: check for start of week and end of week shift conflicts? - $conflicts = array(); + $now = radio_station_get_now(); + $weekdays = radio_station_get_schedule_weekdays(); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + + $conflicts = $checked_shifts = array(); if ( count( $all_shifts ) > 0 ) { - foreach ( $all_shifts as $day => $shifts ) { + $prev_shift = $prev_prev_shift = false; + // foreach ( $all_shifts as $day => $shifts ) { + foreach ( $weekdays as $day ) { - // --- get previous and next days for comparisons --- - $now = strtotime( current_time( 'mysql' ) ); - $thisdate = date( 'Y-m-d', strtotime( $now ) ); - $prevdate = date( 'Y-m-d', strtotime( $thisdate ) - ( 24 * 60 * 60 ) ); - $nextdate = date( 'Y-m-d', strtotime( $thisdate ) + ( 24 * 60 * 60 ) ); - - // --- check for conflicts (overlaps) --- - $checked_shifts = array(); - $prev_shift = false; - foreach ( $shifts as $key => $shift ) { - - // --- reset shift switches --- - $set_shift = true; - $conflict = $disabled = false; - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $disabled = true; - } + if ( isset( $all_shifts[$day] ) ) { + $shifts = $all_shifts[$day]; - // --- account for midnight times --- - if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { - $start_time = strtotime( $thisdate . ' 12:00 am' ); - } else { - $start_time = strtotime( $thisdate . ' ' . $shift['start'] ); - } - if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { - $end_time = strtotime( $thisdate . ' 11:59:59 pm' ) + 1; - } else { - $end_time = strtotime( $thisdate . ' ' . $shift['end'] ); - } + // --- get previous and next days for comparisons --- + // 2.3.2: fix to use week date schedule + $thisdate = $weekdates[$day]; + $date_time = radio_station_to_time( $weekdates[$day] . ' 00:00' ); - if ( false != $prev_shift ) { - - // note: previous shift start and end times set in previous loop iteration - - // --- detect shift conflicts --- - // (and maybe *attempt* to fix them up) - if ( isset( $prev_start_time ) && ( $start_time == $prev_start_time ) ) { - if ( $shift['split'] || $prev_shift['split'] ) { - $conflict = 'overlap'; - if ( $shift['split'] && $prev_shift['split'] ) { - // need to compare start times on previous day - $data = $shift['shift']; - $prevdata = $prev_shift['shift']; - $real_start_time = strtotime( $prevdate . ' ' . $data['real_start'] ); - $prev_real_start_time = strtotime( $prevdate . ' ' . $prevdata['real_start'] ); - if ( $real_start_time > $prev_real_start_time ) { - // current shift started later (overwrite from midnight) - $set_shift = true; - } elseif ( $real_start_time == $prev_real_start_time ) { - $conflict = false; // do not duplicate, already recorded - // total overlap, check last updated post time - $updated = strtotime( $shift['updated'] ); - $prev_updated = strtotime( $prev_shift['updated'] ); - if ( $updated < $prev_updated ) { - $set_shift = false; + // --- check for conflicts (overlaps) --- + foreach ( $shifts as $key => $shift ) { + + // --- set first shift checked --- + // 2.3.2: added for checking against last shift + if ( !isset( $first_shift ) ) { + $first_shift = $shift; + } + + // --- reset shift switches --- + $set_shift = true; + $conflict = $disabled = false; + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $disabled = true; + } + + // --- account for split midnight times --- + // 2.3.2: replace strtotime with to_time for timezones + if ( ( '00:00 am' == $shift['start'] ) || ( '12:00 am' == $shift['start'] ) ) { + $start_time = radio_station_to_time( $thisdate . ' 00:00' ); + } else { + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $start_time = radio_station_to_time( $thisdate . ' ' . $shift_start ); + } + if ( ( '11:59:59 pm' == $shift['end'] ) || ( '12:00 am' == $shift['end'] ) ) { + $end_time = radio_station_to_time( $thisdate . ' 11:59:59' ) + 1; + } else { + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $end_time = radio_station_to_time( $thisdate . ' ' . $shift_end ); + } + + if ( false != $prev_shift ) { + + // note: previous shift start and end times set in previous loop iteration + if ( RADIO_STATION_DEBUG ) { + echo "Shift Date: " . $thisdate . " - Day: " . $day . " - Time: " . $date_time . PHP_EOL; + $prevdata = $prev_shift['shift']; + $prevday = $prev_shift['day']; + $prevdate = $prev_shift['date']; + echo "Previous Shift Date: " . $prevdate . " - Shift Day: " . $prevday . PHP_EOL; + echo "Shift: " . print_r( $shift, true ); + echo "Previous Shift: " . print_r( $prev_shift, true ); + } + + // --- detect shift conflicts --- + // (and maybe *attempt* to fix them up) + if ( isset( $prev_start_time ) && ( $start_time == $prev_start_time ) ) { + if ( $shift['split'] || $prev_shift['split'] ) { + $conflict = 'overlap'; + if ( $shift['split'] && $prev_shift['split'] ) { + // - need to compare start times on previous day - + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $data = $shift['shift']; + $real_start = radio_station_convert_shift_time( $data['real_start'] ); + $shiftdate = radio_station_get_previous_date( $thisdate ); + // $real_start_time = radio_station_to_time( $prevdate . ' ' . $real_start ); + $real_start_time = radio_station_to_time( $shiftdate . ' ' . $real_start ); + + // 2.3.2: fix to calculation of previous shift day start time + $prevdata = $prev_shift['shift']; + // $prevday = $prevdata['day']; + // $prevdate = radio_station_get_previous_date( $thisdate, $prevday ); + $prevdate = $prev_shift['date']; + $prev_real_start = radio_station_convert_shift_time( $prevdata['real_start'] ); + $prev_real_start_time = radio_station_to_time( $prevdate . ' ' . $prev_real_start ); + + // --- compare start times --- + if ( $real_start_time > $prev_real_start_time ) { + // - current shift started later (overwrite from midnight) - + $set_shift = true; + } elseif ( $real_start_time == $prev_real_start_time ) { + // - do not duplicate, already recorded - + $conflict = false; + // - total overlap, check last updated post time - + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; + } } + } elseif ( $shift['split'] ) { + // - the current shift has been split overnight - + // assume previous shift is correct (ignore new shift time) + $set_shift = false; + } elseif ( $prev_shift['split'] ) { + // the previous shift has been split overnight + // so we will assume the new shift start is correct + // (overwrites previous shift from midnight key) + $set_shift = true; + } + } else { + $conflict = 'same_start'; + // - we do not know which of these is correct - + // no solution here, so check most recent last updated time + // we will assume (without certainty) most recent is correct + $updated = strtotime( $shift['updated'] ); + $prev_updated = strtotime( $prev_shift['updated'] ); + if ( $updated < $prev_updated ) { + $set_shift = false; } - } elseif ( $shift['split'] ) { - // the current shift has been split overnight - // assume previous shift is correct (ignore new shift time) - $set_shift = false; - } elseif ( $prev_shift['split'] ) { - // the previous shift has been split overnight - // so we will assume the new shift start is correct - // (overwrites previous shift from midnight key) - $set_shift = true; - } - } else { - $conflict = 'same_start'; - // we do not know which of these is correct - // no solution here, so check most recent last updated time - // we will assume (without certainty) most recent is correct - $updated = strtotime( $shift['updated'] ); - $prev_updated = strtotime( $prev_shift['updated'] ); - if ( $updated < $prev_updated ) { - $set_shift = false; } - } - } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { + } elseif ( isset( $prev_end_time ) && ( $start_time < $prev_end_time ) ) { - // --- set the previous shift end time to current shift start --- - $conflict = 'overlap'; + if ( ( $end_time > $prev_start_time ) || ( $end_time > prev_end_time ) ) { - // --- modify only if this shift is not disabled --- - if ( !$disabled ) { - $checked_shift[$prev_shift['start']]['end'] = $shift['start']; - $checked_shift[$prev_shift['start']]['trimmed'] = true; - } + // --- set the previous shift end time to current shift start --- + $conflict = 'overlap'; - // --- conflict debug output --- - if ( RADIO_STATION_DEBUG ) { - $debug = "This Date: " . $thisdate . " - Next Date: " . $nextdate . " - Prev Date: " . $prevdate . PHP_EOL; - $debug .= "(Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; - $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; - $debug .= "Shift: " . print_r( $shift, true ); - $debug .= "Previous Shift: " . print_r( $prev_shift, true ); - radio_station_debug( $debug ); + // --- modify only if this shift is not disabled --- + if ( !$disabled ) { + // 2.3.2: variable type fix (from checked_shift) + // 2.3.2: fix for midnight starting aplit shifts + // 2.3.2: set checked shifts with day key directly + if ( '00:00 am' == $prev_shift['start'] ) { + $prev_shift['start'] = '12:00 am'; + } + $checked_shifts[$day][$prev_shift['start']]['end'] = $shift['start']; + $checked_shifts[$day][$prev_shift['start']]['trimmed'] = true; + + if ( RADIO_STATION_DEBUG ) { + echo "Previous Previous Shift: " . print_r( $prev_prev_shift, true ); + } + + // --- fix for real end of first part of previous split shift --- + if ( isset( $prev_shift['split'] ) && $prev_shift['split'] && isset( $prev_shift['real_start'] ) ) { + if ( isset( $prev_prev_shift ) && isset( $prev_prev_shift['split'] ) && $prev_prev_shift['split'] ) { + $checked_shifts[$prev_prev_shift['start']]['real_end'] = $shift['start']; + $checked_shifts[$prev_prev_shift['start']]['trimmed'] = true; + } + } + } + + // --- conflict debug output --- + if ( RADIO_STATION_DEBUG ) { + $debug = "Conflicting Start Time: " . date( "m-d l H:i", $start_time ) . " (" . $start_time . ")" . PHP_EOL; + $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $start_time ) . ' ]'; + $debug .= "Overlaps previous End Time: " . date( "m-d l H:i", $prev_end_time ) . " (" . $prev_end_time . ")" . PHP_EOL; + $debug .= '[ ' . radio_station_get_time( "m-d l H:i", $prev_end_time ) . ' ]'; + // $debug .= "Shift: " . print_r( $shift, true ); + // $debug .= "Previous Shift: " . print_r( $prev_shift, true ); + radio_station_debug( $debug ); + } + } } } - } - - // --- maybe store shift conflict data --- - if ( $conflict ) { - - // ---- set short shift time data --- - $shift_start = $shift['shift']['start_hour'] . ':' . $shift['shift']['start_min'] . $shift['shift']['start_meridian']; - $shift_end = $shift['shift']['end_hour'] . ':' . $shift['shift']['end_min'] . $shift['shift']['end_meridian']; - $prev_shift_start = $prev_shift['shift']['start_hour'] . ':' . $prev_shift['shift']['start_min'] . $prev_shift['shift']['start_meridian']; - $prev_shift_end = $prev_shift['shift']['end_hour'] . ':' . $prev_shift['shift']['end_min'] . $prev_shift['shift']['end_meridian']; - - // --- store conflict for this shift --- - $conflicts[$shift['show']][] = array( - 'show' => $shift['show'], - 'day' => $shift['shift']['day'], - 'start' => $shift_start, - 'end' => $shift_end, - 'disabled' => $disabled, - 'with_show' => $prev_shift['show'], - 'with_day' => $prev_shift['shift']['day'], - 'with_start' => $prev_shift_start, - 'with_end' => $prev_shift_end, - 'with_disabled' => $prev_disabled, - 'conflict' => $conflict, - 'duplicate' => false, - ); - // --- store for previous shift only if a different show --- - if ( $shift['show'] != $prev_shift['show'] ) { - $conflicts[$prev_shift['show']][] = array( - 'show' => $prev_shift['show'], - 'day' => $prev_shift['shift']['day'], - 'start' => $prev_shift_start, - 'end' => $prev_shift_end, - 'disabled' => $prev_disabled, - 'with_show' => $shift['show'], - 'with_day' => $shift['shift']['day'], - 'with_start' => $shift_start, - 'with_end' => $shift_end, - 'with_disabled' => $disabled, + // --- maybe store shift conflict data --- + if ( $conflict ) { + + // ---- set short shift time data --- + $shift_start = $shift['shift']['start_hour'] . ':' . $shift['shift']['start_min'] . $shift['shift']['start_meridian']; + $shift_end = $shift['shift']['end_hour'] . ':' . $shift['shift']['end_min'] . $shift['shift']['end_meridian']; + $prev_shift_start = $prev_shift['shift']['start_hour'] . ':' . $prev_shift['shift']['start_min'] . $prev_shift['shift']['start_meridian']; + $prev_shift_end = $prev_shift['shift']['end_hour'] . ':' . $prev_shift['shift']['end_min'] . $prev_shift['shift']['end_meridian']; + + // --- store conflict for this shift --- + $conflicts[$shift['show']][] = array( + 'show' => $shift['show'], + 'day' => $shift['shift']['day'], + 'start' => $shift_start, + 'end' => $shift_end, + 'disabled' => $disabled, + 'with_show' => $prev_shift['show'], + 'with_day' => $prev_shift['shift']['day'], + 'with_start' => $prev_shift_start, + 'with_end' => $prev_shift_end, + 'with_disabled' => $prev_disabled, 'conflict' => $conflict, - 'duplicate' => true, + 'duplicate' => false, ); + + // --- store for previous shift only if a different show --- + if ( $shift['show'] != $prev_shift['show'] ) { + $conflicts[$prev_shift['show']][] = array( + 'show' => $prev_shift['show'], + 'day' => $prev_shift['shift']['day'], + 'start' => $prev_shift_start, + 'end' => $prev_shift_end, + 'disabled' => $prev_disabled, + 'with_show' => $shift['show'], + 'with_day' => $shift['shift']['day'], + 'with_start' => $shift_start, + 'with_end' => $shift_end, + 'with_disabled' => $disabled, + 'conflict' => $conflict, + 'duplicate' => true, + ); + } } - } - // --- set current shift to previous for next iteration --- - $prev_start_time = $start_time; - $prev_end_time = $end_time; - $prev_shift = $shift; - $prev_disabled = $disabled; - - // --- set the now checked shift data --- - // (...but only if not disabled!) - if ( $set_shift && !$disabled ) { - // --- no longer need shift and post updated times --- - unset( $shift['shift'] ); - unset( $shift['updated'] ); - if ( '00:00 am' == $shift['start'] ) { - $shift['start'] = '12:00 am'; + // --- set current shift to previous for next iteration --- + $prev_start_time = $start_time; + $prev_end_time = $end_time; + if ( $prev_shift ) { + $prev_prev_shift = $prev_shift; + } + $prev_shift = $shift; + $prev_disabled = $disabled; + + // --- set the now checked shift data --- + // (...but only if not disabled!) + if ( $set_shift && !$disabled ) { + // - no longer need shift and post updated times - + unset( $shift['shift'] ); + unset( $shift['updated'] ); + if ( '00:00 am' == $shift['start'] ) { + $shift['start'] = '12:00 am'; + } + // 2.3.2: set checked shifts with day key directly + $checked_shifts[$day][$shift['start']] = $shift; } - $checked_shifts[$shift['start']] = $shift; } + } + + // --- set checked shifts for day --- + // 2.3.2: set checked shifts with day key directly + // $all_shifts[$day] = $checked_shifts; + } + } + // --- check last shift against first shift --- + // 2.3.2: added for possible overlap (split by weekly schedule dates) + if ( isset( $shift ) && ( $shift != $first_shift ) ) { + + // --- use days for next week to compare --- + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $last_shift_start = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_start ); + $last_shift_end = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_end ); + $shift_start = radio_station_convert_shift_time( $first_shift['start'] ); + $shift_end = radio_station_convert_shift_time( $first_shift['end'] ); + $first_shift_start = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_start ); + $first_shift_end = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_end ); + + if ( RADIO_STATION_DEBUG ) { + echo 'Last Shift Start: ' . $shift['day'] . ' ' . $shift_start . ' - (' . $last_shift_start . ')' . PHP_EOL; + echo 'First Shift End: ' . $first_shift['day'] . ' ' . $shift_end . ' - (' . $first_shift_end . ')' . PHP_EOL; + } + + if ( $last_shift_start < $first_shift_end ) { + + // --- record a conflict --- + if ( RADIO_STATION_DEBUG ) { + echo "First/Last Shift Overlap Conflict" . PHP_EOL; } + + /* + // --- store conflict for this shift --- + $conflicts[$first_shift['show']][] = array( + 'show' => $first_shift['show'], + 'day' => $first_shift['shift']['day'], + 'start' => $first_shift_start, + 'end' => $first_shift_end, + 'disabled' => $first_shift['shift']['disabled'], + 'with_show' => $last_shift['show'], + 'with_day' => $last_shift['shift']['day'], + 'with_start' => $last_shift_start, + 'with_end' => $last_shift_end, + 'with_disabled' => $last_shift['shift']['disabled'], + 'conflict' => 'overlap', + 'duplicate' => false, + ); - // --- set checked shifts for day --- - $all_shifts[$day] = $checked_shifts; + // --- store for other shift if different show --- + if ( $first_shift['show'] != $last_shift['show'] ) { + $conflicts[$last_shift['show']][] = array( + 'show' => $last_shift['show'], + 'day' => $last_shift['shift']['day'], + 'start' => $last_shift_start, + 'end' => $last_shift_end, + 'disabled' => $last_shift['shift']['disabled'], + 'with_show' => $first_shift['show'], + 'with_day' => $first_shift['shift']['day'], + 'with_start' => $first_shift_start, + 'with_end' => $first_shift_end, + 'with_disabled' => $first_shift['shift']['disabled'], + 'conflict' => 'overlap', + 'duplicate' => true, + ); + } */ } } @@ -1738,7 +2212,7 @@ function radio_station_check_shifts( $all_shifts ) { delete_option( 'radio_station_schedule_conflicts' ); } - return $all_shifts; + return $checked_shifts; } // ------------------ @@ -1749,18 +2223,33 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { global $radio_station_data; + // 2.3.2: bug out if day is empty + if ( '' == $shift['day'] ) { + return false; + } + + // 2.3.2: manual bypass of shift checking + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + return false; + } + // --- get all show shift times --- if ( isset( $radio_station_data['all-shifts'] ) ) { // --- get stored data --- $all_shifts = $radio_station_data['all-shifts']; } else { // (with conflict checking off as we are doing that now) - $all_shifts = radio_station_get_show_shifts( false ); + $all_shifts = radio_station_get_show_shifts( false, false ); // --- store this data for efficiency --- $radio_station_data['all-shifts'] = $all_shifts; } + // --- convert days to dates for checking --- + $now = radio_station_get_now(); + $weekdays = radio_station_get_schedule_weekdays(); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + // --- get shows to check against via context --- $check_shifts = array(); if ( 'all' == $context ) { @@ -1777,16 +2266,29 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } } - // --- convert days to dates for checking --- - $now = strtotime( current_time( 'mysql' ) ); - $weekdays = radio_station_get_schedule_weekdays(); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + // 2.3.2: to doubly ensure shifts are set in schedule order + $sorted_shifts = array(); + foreach ( $weekdays as $weekday ) { + if ( isset( $check_shifts[$weekday] ) ) { + $sorted_shifts[$weekday] = $check_shifts[$weekday]; + } + } + $check_shifts = $sorted_shifts; // --- get shift start and end time --- - $shift_start_time = strtotime( $weekdates[$shift['day']] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian'] ); - $shift_end_time = strtotime( $weekdates[$shift['day']] . ' ' . $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian'] ); + // 2.3.2: fix to convert to 24 hour times first + $start_time = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + $end_time = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; + $start_time = radio_station_convert_shift_time( $start_time ); + $end_time = radio_station_convert_shift_time( $end_time ); + + // 2.3.2: use next week day instead of date + $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + // $shift_start_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $start_time ); + // $shift_end_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $end_time ); if ( $shift_end_time < $shift_start_time ) { - $shift_end_time = $shift_end_time + 86400; + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } if ( RADIO_STATION_DEBUG ) { @@ -1800,29 +2302,58 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // --- check for conflicts with other show shifts --- $conflicts = array(); foreach ( $check_shifts as $day => $day_shifts ) { - if ( $day == $shift['day'] ) { + // 2.3.2: removed day match check + // if ( $day == $shift['day'] ) { foreach ( $day_shifts as $i => $day_shift ) { - // note: no need to adjust times for midnight as shifts are already split - $day_shift_start_time = strtotime( $weekdates[$day] . ' ' . $day_shift['start'] ); - $day_shift_end_time = strtotime( $weekdates[$day] . ' ' . $day_shift['end'] ); + if ( !isset( $first_shift ) ) { + $first_shift = $day_shift; + } + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour times first + $shift_start = radio_station_convert_shift_time( $day_shift['start'] ); + $shift_end = radio_station_convert_shift_time( $day_shift['end'] ); - if ( RADIO_STATION_DEBUG ) { - echo "with Shift for Show " . $day_shift['show'] . ": "; - echo $day . " - " . $weekdates[$day] . " - " . $day_shift['start'] . "(" . $day_shift_start_time . ")"; - echo " to " . $day_shift['end'] . "(" . $day_shift_end_time . ")" . PHP_EOL; + // 2.3.2: use next week day instead of date + $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_start ); + $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_end ); + // $day_shift_start_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_start ); + // $day_shift_end_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_end ); + // 2.3.2: adjust for midnight with change to use non-split shifts + if ( $day_shift_end_time < $day_shift_start_time ) { + $day_shift_end_time = $day_shift_end_time + ( 24 * 60 * 60 ); } // --- ignore if this is the same shift we are checking --- - if ( $day_shift['show'] != $show_id ) { - - // if the new shift starts before existing shift but finishes after existing shift starts - // - or new shift starts at the same time as the existing shift - // - of the existing shift starts before the new shift and finishes after new shift starts - // ...then there is a shift overlap conflict + $check_shift = true; + if ( $day_shift['show'] == $show_id ) { + // ? only ignore same shift not same show ? + // if ( ( $day_shift_start_time == $shift_start_time ) && ( $day_shift_end_time == $shift_end_time ) ) { + $check_shift = false; + // } + } + + if ( $check_shift ) { + + if ( RADIO_STATION_DEBUG ) { + echo "with Shift for Show " . $day_shift['show'] . ": "; + echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; + echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; + } + + // 2.3.2: improved shift checking logic + // [overlap] if the new shift starts before existing shift but end after existing shift starts + // [external] or starts before but ends after the existing shift end time + // [equal] or new shift starts at the same time as the existing shift + // [internal] or if the new shift starts after existing shift and ends before it ends + // [overlap] of the new shift starts after the existing shift but before it ends + // ...then there is a shift overlap conflict if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) + || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) || ( $shift_start_time == $day_shift_start_time ) - || ( ( $day_shift_start_time < $shift_start_time ) && ( $day_shift_end_time > $shift_start_time ) ) ) { + || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) + || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) ) { $conflicts[] = $day_shift; if ( RADIO_STATION_DEBUG ) { echo "^^^ CONFLICT ^^^" . PHP_EOL; @@ -1830,13 +2361,69 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } } } + // } + } + + // --- recheck for first shift overlaps --- + // (not implemented as not needed) + /* if ( isset( $first_shift ) ) { + // --- check for first shift overlap using next week --- + $shift_start = radio_station_convert_shift_time( $first_shift['start'] ); + $shift_end = radio_station_convert_shift_time( $first_shift['end'] ); + $first_shift_start_time = radio_station_to_time( $first_shift['date'] . ' ' . $shift_start ) + ( 7 * 24 * 60 * 60 ); + $first_shift_end_time = radio_station_to_time( $first_shift['date'] . ' ' . $shift_end ) + ( 7 * 24 * 60 * 60 ); + + if ( RADIO_STATION_DEBUG ) { + echo "with First Shift for Show " . $first_shift['show'] . ": "; + echo $first_shift['day'] . " - " . $first_shift['date'] . " - " . $first_shift['start'] . " (" . $first_shift_start_time . ")"; + echo " to " . $first_shift['end'] . " (" . $first_shift_end_time . ")" . PHP_EOL; + } + + if ( ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_start_time ) ) + || ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_end_time ) ) + || ( $shift_start_time == $first_shift_start_time ) + || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_end_time < $first_shift_end_time ) ) + || ( ( $shift_start_time > $first_shift_start_time ) && ( $shift_start_time < $first_shift_end_time ) ) ) { + $conflicts[] = $first_shift; + if ( RADIO_STATION_DEBUG ) { + echo "^^^ CONFLICT ^^^" . PHP_EOL; + } + } + } */ + + // --- recheck for last shift --- + // (for date based schedule overflow rechecking) + if ( isset( $day_shift ) ) { + + // --- check for new shift overlap using next week --- + $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); + $shift_end_time = $shift_end_time + ( 7 * 24 * 60 * 60 ); + + if ( RADIO_STATION_DEBUG ) { + echo "with Last Shift (using next week):" . PHP_EOL; + echo radio_station_get_time( 'date', $shift_start_time ) . " - " . $shift['start'] . " (" . $shift_start_time . ")"; + echo " to " . $shift['end'] . " (" . $shift_end_time . ")" . PHP_EOL; + echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; + echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; } + + if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) + || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) + || ( $shift_start_time == $day_shift_start_time ) + || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) + || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) ) { + $conflicts[] = $day_shift; + if ( RADIO_STATION_DEBUG ) { + echo "^^^ CONFLICT ^^^" . PHP_EOL; + } + } } if ( count( $conflicts ) == 0 ) { - $conflicts = false; + return false; } - return $conflicts; + + return $conflicts; } // ------------------ @@ -1845,6 +2432,13 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // (checks show shifts for conflicts with same show) function radio_station_check_new_shifts( $new_shifts ) { + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + if ( RADIO_STATION_DEBUG ) { + echo "New Shift Checking Bypassed." . PHP_EOL; + } + return $new_shifts; + } + // --- debug point --- if ( RADIO_STATION_DEBUG ) { $debug = "New Shifts: " . print_r( $new_shifts, true ); @@ -1852,62 +2446,106 @@ function radio_station_check_new_shifts( $new_shifts ) { } // --- convert days to dates for checking --- - $now = strtotime( current_time( 'mysql' ) ); + $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- double loop shifts to check against others --- foreach ( $new_shifts as $i => $shift_a ) { - // --- get shift A start and end times --- - $shift_a_start_time = strtotime( $weekdates[$shift_a['day']] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] ); - $shift_a_end_time = strtotime( $weekdates[$shift_a['day']] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] ); - if ( $shift_a_end_time < $shift_a_start_time ) { - $shift_a_end_time = $shift_a_end_time + 86400; - } - - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; - $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; - $debug = "Shift A Start: " . $a_start . PHP_EOL . 'Shift A End: ' . $a_end . PHP_EOL; - radio_station_debug( $debug ); - } + if ( '' != $shift_a['day'] ) { + + // --- get shift A start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_a_start = $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian']; + $shift_a_end = $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian']; + $shift_a_start = radio_station_convert_shift_time( $shift_a_start ); + $shift_a_end = radio_station_convert_shift_time( $shift_a_end ); + + // 2.3.2: use next week day instead of date + $shift_a_start_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_start ); + $shift_a_end_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); + // $shift_a_start_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_start ); + // $shift_a_end_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_end ); + if ( $shift_a_end_time < $shift_a_start_time ) { + $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); + } - foreach ( $new_shifts as $j => $shift_b ) { - if ( $i != $j ) { + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $a_start = $shift_a['day'] . ' ' . $shift_a['start_hour'] . ':' . $shift_a['start_min'] . $shift_a['start_meridian'] . ' (' . $shift_a_start_time . ')'; + $a_end = $shift_a['day'] . ' ' . $shift_a['end_hour'] . ':' . $shift_a['end_min'] . $shift_a['end_meridian'] . ' (' . $shift_a_end_time . ')'; + $debug = "Shift A Start: " . $a_start . PHP_EOL . 'Shift A End: ' . $a_end . PHP_EOL; + radio_station_debug( $debug ); + } - // --- get shift B start and end times --- - $shift_b_start_time = strtotime( $weekdates[$shift_b['day']] . ' ' . $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian'] ); - $shift_b_end_time = strtotime( $weekdates[$shift_b['day']] . ' ' . $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian'] ); - if ( $shift_b_end_time < $shift_b_start_time ) { - $shift_b_end_time = $shift_b_end_time + 86400; - } + foreach ( $new_shifts as $j => $shift_b ) { - // --- debug point --- - if ( RADIO_STATION_DEBUG ) { - $b_start = $shift_b['day'] . ' ' . $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian'] . ' (' . $shift_b_start_time . ')'; - $b_end = $shift_b['day'] . ' ' . $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian'] . ' (' . $shift_b_end_time . ')'; - $debug = "with Shift B Start: " . $b_start . PHP_EOL . 'Shift B End: ' . $b_end . PHP_EOL; - radio_station_debug( $debug, false, 'show-shift-save.log' ); - } - - // --- compare shift A and B times --- - if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) - || ( $shift_a_start_time == $shift_b_start_time ) - || ( ( $shift_b_start_time < $shift_a_start_time ) && ( $shift_b_end_time > $shift_a_start_time ) ) ) { + if ( $i != $j ) { + + if ( RADIO_STATION_DEBUG ) { + echo $i . ' ::: ' . $j . PHP_EOL; + } - // --- maybe disable shift B --- - if ( ( 'yes' != $new_shifts[$i]['disabled'] ) - && ( 'yes' != $new_shifts[$j]['disabled'] ) ) { + if ( '' != $shift_b['day'] ) { + // --- get shift B start and end times --- + // 2.3.2: replace strtotime with to_time for timezones + $shift_b_start = $shift_b['start_hour'] . ':' . $shift_b['start_min'] . $shift_b['start_meridian']; + $shift_b_end = $shift_b['end_hour'] . ':' . $shift_b['end_min'] . $shift_b['end_meridian']; + $shift_b_start = radio_station_convert_shift_time( $shift_b_start ); + $shift_b_end = radio_station_convert_shift_time( $shift_b_end ); + + // 2.3.2: use next week day instead of date + $shift_b_start_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_start ); + $shift_b_end_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end ); + // $shift_b_start_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_start ); + // $shift_b_end_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_end ); + if ( $shift_b_end_time < $shift_b_start_time ) { + $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); + } // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = "!Conflict Found! New Shift (B) Disabled!" . PHP_EOL; + $b_start = $shift_b['day'] . ' ' . $shift_b_start . ' (' . $shift_b_start_time . ')'; + $b_end = $shift_b['day'] . ' ' . $shift_b_end . ' (' . $shift_b_end_time . ')'; + $debug = "with Shift B Start: " . $b_start . ' - Shift B End: ' . $b_end . PHP_EOL; + // radio_station_debug( $debug, false, 'show-shift-save.log' ); radio_station_debug( $debug ); } - $new_shifts[$j]['disabled'] = 'yes'; + // --- compare shift A and B times --- + if ( ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) + || ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) + || ( $shift_a_start_time == $shift_b_start_time ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) + || ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) ) { + + // --- maybe disable shift B --- + // 2.3.2: added check for isset on disabled key + if ( ( !isset( $new_shifts[$i]['disabled'] ) || ( 'yes' != $new_shifts[$i]['disabled'] ) ) + && ( !isset( $new_shifts[$j]['disabled'] ) || ( 'yes' != $new_shifts[$j]['disabled'] ) ) ) { + + // --- debug point --- + if ( RADIO_STATION_DEBUG ) { + $debug = PHP_EOL . "* Conflict Found! New Shift (B) Disabled "; + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_start_time ) ) {$debug .= "[A]";} + if ( ( $shift_a_start_time < $shift_b_start_time ) && ( $shift_a_end_time > $shift_b_end_time ) ) {$debug .= "[B]";} + if ( $shift_a_start_time == $shift_b_start_time ) {$debug .= "[C]";} + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_end_time < $shift_b_end_time ) ) {$debug .= "[D]";} + if ( ( $shift_a_start_time > $shift_b_start_time ) && ( $shift_a_start_time < $shift_b_end_time ) ) {$debug .= "[E]";} + $debug .= "*" . PHP_EOL; + radio_station_debug( $debug ); + } + + $new_shifts[$j]['disabled'] = 'yes'; + + } else { + if ( RADIO_STATION_DEBUG ) { + echo "[Conflict with disabled shift.]" . PHP_EOL; + } + } + } } } } @@ -1956,7 +2594,7 @@ function radio_station_validate_shift( $shift ) { // ------------------ // Update Show Avatar // ------------------ -// 2.3.0: trigger show avatar update when editing +// 2.3.0: trigger show avatar check/update when editing add_action( 'replace_editor', 'radio_station_update_show_avatar', 10, 2 ); function radio_station_update_show_avatar( $replace_editor, $post ) { $show_id = $post->ID; @@ -2253,6 +2891,15 @@ function radio_station_patreon_button_styles() { #radio-station-patreon-button:hover {opacity:1 !important;}'; } +// -------------------- +// Queue Directory Ping +// -------------------- +// 2.3.2: queue directory ping on saving +function radio_station_queue_directory_ping() { + $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); + if ( 'yes' != $do_ping ) {return;} + update_option( 'radio_station_ping_directory', '1' ); +} // ------------------- // Send Directory Ping @@ -2263,21 +2910,224 @@ function radio_station_send_directory_ping() { if ( 'yes' != $do_ping ) {return;} // --- set the URL to ping --- + // 2.3.2: fix url_encode to urlencode $site_url = site_url(); $url = add_query_arg( 'ping', 'directory', RADIO_STATION_NETMIX_DIR ); - $url = add_query_arg( 'station-url', url_encode( $site_url ), $url ); + $url = add_query_arg( 'station-url', urlencode( $site_url ), $url ); $url = add_query_arg( 'timestamp', time(), $url ); - + // --- send the ping --- - $args = array( 'timeout' => 3 ); - wp_remote_get( $url, $args ); + $args = array( 'timeout' => 10 ); + if ( !function_exists( 'wp_remote_get' ) ) { + include_once ABSPATH . WPINC . '/http.php'; + } + $response = wp_remote_get( $url, $args ); + if ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + echo 'Directory Ping Response:'; + echo ''; + } + return $response; } +// ------------------- +// Check and Send Ping +// ------------------- +// 2.3.2: send queued directory ping +add_action( 'admin_footer', 'radio_station_check_directory_ping', 99 ); +function radio_station_check_directory_ping() { + $ping = get_option( 'radio_station_ping_directory' ); + if ( $ping ) { + $response = radio_station_send_directory_ping(); + if ( !is_wp_error( $response ) && isset( $response['response']['code'] ) && ( 200 == $response['response']['code'] ) ) { + delete_option( 'radio_station_ping_directory' ); + } + } elseif ( isset( $_GET['rs-test-ping'] ) && ( '1' == $_GET['rs-test-ping'] ) ) { + $response = radio_station_send_directory_ping(); + } +} // ------------------------ // === Time Conversions === // ------------------------ +// ------- +// Get Now +// ------- +// 2.3.2: added for current time consistency +function radio_station_get_now( $gmt = true ) { + + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $now = strtotime( current_time( 'mysql' ) ); + } else { + $datetime = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); + $now = $datetime->format( 'U' ); + } + + return $now; +} + +// ------------ +// Get Timezone +// ------------ +// 2.3.2: added get timezone with fallback +function radio_station_get_timezone() { + + $timezone = radio_station_get_setting( 'timezone_location' ); + if ( !$timezone || ( '' == $timezone ) ) { + + // --- fallback to WordPress timezone --- + $timezone = get_option( 'timezone_string' ); + if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { + $timezone = ''; + } + + } + + if ( '' == $timezone ) { + $offset = get_option( 'gmt_offset' ); + $timezone = 'UTC' . $offset; + } + + return $timezone; +} + +// ----------------- +// Get Timezone Code +// ----------------- +// note: this should only be used for display purposes +// (as the actual code used is based on timezone/location) +function radio_station_get_timezone_code( $timezone ) { + $datetime = new DateTime( 'now', new DateTimeZone( $timezone ) ); + return $datetime->format( 'T' ); +} + +// -------------------- +// Get Date Time Object +// -------------------- +// 2.3.2: added for consistent timezone conversions +function radio_station_get_date_time( $timestring, $timezone ) { + + if ( 'UTC' == $timezone ) { + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + } elseif ( strstr( $timezone, 'UTC' ) ) { + $offset = str_replace( 'UTC', '', $timezone ); + $offset = (int)$offset * 60 * 60; + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + $timestamp = $datetime->format( 'U' ); + $timestamp = $timestamp + $offset; + $datetime->setTimestamp( $timestamp ); + } else { + $datetime = new DateTime( $timestring, new DateTimeZone( $timezone ) ); + // ...fix to set timestamp again just in case + // echo "A: " . print_r( $datetime, true ) . PHP_EOL; + // if ( '@' == substr( $timestring, 0, 1 ) ) { + // $timestamp = substr( $timestring, 1, strlen( $timestring ) ); + // $datetime->setTimestamp( $timestamp ); + // } + // echo "B: " . print_r( $datetime, true ) . PHP_EOL; + } + + return $datetime; +} + +// -------------- +// String To Time +// -------------- +// 2.3.2: added for timezone handling +function radio_station_to_time( $timestring ) { + + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $time = strtotime( $timestring ); + } else { + $timezone = radio_station_get_timezone(); + if ( strstr( $timezone, 'UTC' ) && ( 'UTC' != $timezone ) ) { + // --- fallback for UTC offsets --- + $offset = str_replace( 'UTC', '', $timezone ); + $offset = (int)$offset * 60 * 60; + $utc = new DateTimeZone( 'UTC' ); + $datetime = new DateTime( $timestring, $utc ); + $timestamp = $datetime->getTimestamp(); + $timestamp = $timestamp - $offset; + $datetime->setTimestamp( $timestamp ); + } else { + $datetime = radio_station_get_date_time( $timestring, $timezone ); + } + $time = $datetime->format( 'U' ); + + } + + return $time; +} + +// -------- +// Get Time +// -------- +// 2.3.2: added for timezone adjustments +function radio_station_get_time( $key = false, $time = false ) { + + // --- get offset time and date --- + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + + if ( !$time ) { + $time = radio_station_get_now(); + } + $day = date( 'l', $time ); + $date = date( 'Y-m-d', $time ); + $date_time = date( 'Y-m-d H:i:s', $time ); + $timestamp = date( 'U', $time ); + + } else { + + if ( !$time ) { + $timestring = 'now'; + } else { + $timestring = '@' . $time; + } + + // --- get timezone --- + $timezone = radio_station_get_timezone(); + if ( strstr( $timezone, 'UTC' ) ) { + $datetime = radio_station_get_date_time( $timestring, $timezone ); + } else { + // ...and refix for location timezones + $datetime = new DateTime( $timestring, new DateTimeZone( 'UTC' ) ); + $datetime->setTimezone( new DateTimeZone( $timezone ) ); + } + + // --- set formatted strings --- + $day = $datetime->format( 'l' ); + $date = $datetime->format( 'Y-m-d' ); + $date_time = $datetime->format( 'Y-m-d H:i:s' ); + $timestamp = $datetime->format( 'U' ); + + } + + $times = array( + 'day' => $day, + 'date' => $date, + 'datetime' => $date_time, + 'timestamp' => $timestamp, + ); + + if ( $key ) { + if ( array_key_exists( $key, $times ) ) { + $time = $times[$key]; + } elseif ( isset( $datetime ) ) { + $time = $datetime->format( $key ); + } else { + $time = date( $key, $time ); + } + } elseif ( RADIO_STATION_DEBUG ) { + echo 'Time key not found: ' . $key . PHP_EOL; + echo 'Times: ' . print_r( $times, true ) . ''; + } + + return $time; +} + // -------------------- // Get Timezone Options // -------------------- @@ -2347,15 +3197,52 @@ function radio_station_get_timezone_options( $include_wp_timezone = false ) { return $options; } -// ----------------- -// Get Timezone Code -// ----------------- -// note: this should only be used for display in the "now" -// (as the actual code to be used is based on location) -function radio_station_get_timezone_code( $timezone ) { - $date_time = new DateTime(); - $date_time->setTimeZone( new DateTimeZone( $timezone ) ); - return $date_time->format( 'T' ); +// -------------- +// Get Weekday(s) +// -------------- +// 2.3.2: added get weekday from number helper +function radio_station_get_weekday( $day_number = null ) { + + $weekdays = array( + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ); + + if ( !is_null( $day_number ) ) { + return $weekdays[$day_number]; + } + return $weekdays; +} + +// ------------ +// Get Month(s) +// ------------ +// 2.3.2: added get weekday from number helper +function radio_station_get_month( $month_number = null ) { + + $months = array( + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ); + if ( !is_null( $month_number ) ) { + return $months[$month_number]; + } + return $months; } // --------------------- @@ -2371,7 +3258,6 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { $weekstart = apply_filters( 'radio_station_schedule_weekday_start', $weekstart ); } - // --- loop weekdays and reorder from start day --- $weekdays = array( 'Sunday', 'Monday', @@ -2381,9 +3267,21 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { 'Friday', 'Saturday', ); + + // 2.3.2: accept string format for weekstart + if ( is_string( $weekstart ) ) { + foreach ( $weekdays as $i => $weekday ) { + if ( strtolower( $weekday ) == strtolower( $weekstart ) ) { + $weekstart = $i; + } + } + } + + // --- loop weekdays and reorder from start day --- $start = $before = $after = array(); foreach ( $weekdays as $i => $weekday ) { - if ( $i == $weekstart ) { + // 2.3.2: allow matching of numerical index or weekday name + if ( ( $i == $weekstart ) || ( $weekday == $weekstart ) ) { $start[] = $weekday; } elseif ( $i > $weekstart ) { $after[] = $weekday; @@ -2403,10 +3301,22 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { // Get Schedule Weekdates // ---------------------- // 2.3.0: added for date based calculations -function radio_station_get_schedule_weekdates( $weekdays, $now ) { +function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { - $today = date( 'l', $now ); - $date = date( 'Y-m-d', $now ); + if ( !$time ) { + $time = radio_station_get_now(); + } + + // 2.3.2: use timezone setting to get offset date + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $today = date( 'l', $time ); + } else { + $timezone = radio_station_get_timezone(); + $datetime = radio_station_get_date_time( '@' . $time, $timezone ); + $today = $datetime->format( 'l' ); + } + + // --- get weekday index for today --- $weekdates = array(); foreach ( $weekdays as $i => $weekday ) { if ( $weekday == $today ) { @@ -2415,9 +3325,27 @@ function radio_station_get_schedule_weekdates( $weekdays, $now ) { } foreach ( $weekdays as $i => $weekday ) { $diff = $index - $i; - $weekdate = date( 'Y-m-d', ( strtotime( $date ) - ( $diff * 24 * 60 * 60 ) ) ); + $weekdate_time = $time - ( $diff * 24 * 60 * 60 ); + // 2.3.2: include timezone adjustment + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $weekdate = date( 'Y-m-d', $weekdate_time ); + } else { + $weekdatetime = radio_station_get_date_time( '@' . $weekdate_time, $timezone ); + $weekdate = $weekdatetime->format( 'Y-m-d' ); + } $weekdates[$weekday] = $weekdate; } + + if ( RADIO_STATION_DEBUG ) { + echo ''; + echo 'Time: ' . $time . PHP_EOL; + echo 'Today: ' . $today . PHP_EOL; + echo 'Today Object: ' . print_r( $datetime, true ); + echo 'Weekdays: ' . print_r( $weekdays, true ); + echo 'Weekdates: ' . print_r( $weekdates, true ); + echo ''; + } + return $weekdates; } @@ -2485,6 +3413,66 @@ function radio_station_get_previous_day( $day ) { return ''; } +// ------------- +// Get Next Date +// ------------- +// 2.3.2: added for more reliable calculations +function radio_station_get_next_date( $date, $weekday = false ) { + + // note: this is used internally so timezone not used + $timedate = strtotime( $date ); + $timedate = $timedate + ( 24 * 60 * 60 ); + if ( $weekday ) { + $day = date( 'l', $timedate ); + if ( $day != $weekday ) { + $i = 0; + while ( $day != $weekday ) { + $timedate = $timedate + ( 24 * 60 * 60 ); + $day = strtotime( 'l', $timedate ); + if ( 8 == $i ) { + // - failback for while failure - + $timedate = strtotime( $date ); + $next_date = date( 'next ' . $weekday, $timedate ); + return $next_date; + } + $i++; + } + } + } + $next_date = date( 'Y-m-d', $timedate ); + return $next_date; +} + +// ----------------- +// Get Previous Date +// ----------------- +// 2.3.2: added for more reliable calculations +function radio_station_get_previous_date( $date, $weekday = false ) { + + // note: this is used internally so timezone not used + $timedate = strtotime( $date ); + $timedate = $timedate - ( 24 * 60 * 60 ); + if ( $weekday ) { + $day = date( 'l', $timedate ); + if ( $day != $weekday ) { + $i = 0; + while ( $day != $weekday ) { + $timedate = $timedate - ( 24 * 60 * 60 ); + $day = strtotime( 'l', $timedate ); + if ( 8 == $i ) { + // - failback for while failure - + $timedate = strtotime( $date ); + $previous_date = date( 'previous ' . $weekday, $timedate ); + return $previous_date; + } + $i++; + } + } + } + $previous_date = date( 'Y-m-d', $timedate ); + return $previous_date; +} + // ------------- // Get All Hours // ------------- @@ -2557,6 +3545,8 @@ function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { } } } + // 2.3.2: fix for possible double spacing + $hour = str_replace( ' ', ' ', $hour ); return $hour; } @@ -2567,6 +3557,7 @@ function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { // 2.3.0: added to convert shift time to 24 hours (or back) function radio_station_convert_shift_time( $time, $timeformat = 24 ) { + // note: timezone can be ignored here as just getting hours and minutes $timestamp = strtotime( date( 'Y-m-d' ) . $time ); if ( 12 == (int) $timeformat ) { $time = date( 'g:i a', $timestamp ); @@ -2585,6 +3576,7 @@ function radio_station_convert_shift_time( $time, $timeformat = 24 ) { // 2.3.0: 24 format shift for broadcast data endpoint function radio_station_convert_show_shift( $shift ) { + // note: timezone can be ignored here as getting hours and minutes if ( isset( $shift['start'] ) ) { $shift['start'] = date( 'H:i', strtotime( $shift['start'] ) ); } @@ -2644,6 +3636,16 @@ function radio_station_convert_schedule_shifts( $schedule ) { // === Helper Functions === // ------------------------ +// -------------------- +// Encode URI Component +// -------------------- +// 2.3.2: added PHP equivalent of javascript encodeURIComponent +// ref: https://stackoverflow.com/a/1734255/5240159 +function radio_station_encode_uri_component( $string ) { + $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ); + return strtr( rawurlencode( $string ), $revert); +} + // -------------- // Get Profile ID // -------------- @@ -2827,30 +3829,40 @@ function radio_station_get_language( $lang = false ) { // 2.3.0: added permalink argument function radio_station_trim_excerpt( $content, $length = false, $more = false, $permalink = false ) { + $excerpt = ''; $raw_content = $content; - $content = strip_shortcodes( $content ); - // TODO: check for Gutenberg plugin-only equivalent ? - if ( function_exists( 'excerpt_remove_blocks' ) ) { - $content = excerpt_remove_blocks( $content ); - } - $content = apply_filters( 'the_content', $content ); - $content = str_replace( ']]>', ']]>', $content ); - - if ( !$length ) { - $length = 35; - // $length = apply_filters( 'radio_station_excerpt_length', $length ); - $length = (int) apply_filters( 'radio_station_excerpt_length', $length ); - } - if ( !$more ) { - $more = ' […]'; - // $more = apply_filters( 'excerpt_more', $more); - $more = apply_filters( 'radio_station_excerpt_more', ' […]' ); - } - // 2.3.0: added link wrapper - if ( $permalink ) { - $more = ' ' . $more . ''; + + // 2.3.2: added check for content + if ( '' != trim( $content ) ) { + + $content = strip_shortcodes( $content ); + + if ( function_exists( 'excerpt_remove_blocks' ) ) { + $content = excerpt_remove_blocks( $content ); + } + // TODO: check for Gutenberg plugin-only equivalent ? + // elseif ( function_exists( 'gutenberg_remove_blocks' ) { + // $content = gutenberg_remove_blocks( $content ); + // } + $content = apply_filters( 'the_content', $content ); + $content = str_replace( ']]>', ']]>', $content ); + + if ( !$length ) { + $length = 35; + // $length = apply_filters( 'radio_station_excerpt_length', $length ); + $length = (int) apply_filters( 'radio_station_excerpt_length', $length ); + } + if ( !$more ) { + $more = ' […]'; + // $more = apply_filters( 'excerpt_more', $more); + $more = apply_filters( 'radio_station_excerpt_more', ' […]' ); + } + // 2.3.0: added link wrapper + if ( $permalink ) { + $more = ' ' . $more . ''; + } + $excerpt = wp_trim_words( $content, $length, $more ); } - $excerpt = wp_trim_words( $content, $length, $more ); $excerpt = apply_filters( 'radio_station_trim_excerpt', $excerpt, $raw_content, $length, $more, $permalink ); return $excerpt; @@ -2884,6 +3896,92 @@ function radio_station_sanitize_values( $data, $keys ) { return $sanitized; } +// ------------------------- +// Sanitize Shortcode Values +// ------------------------- +// 2.3.2: added for AJAX widget loading +function radio_station_sanitize_shortcode_values( $type, $extras = false ) { + + $atts = array(); + + if ( 'current-show' == $type ) { + + // --- current show attribute keys --- + $keys = array( + 'title' => 'text', + 'limit' => 'integer', + 'show_avatar' => 'boolean', + 'show_link' => 'boolean', + 'show_sched' => 'boolean', + 'show_playlist' => 'boolean', + 'show_all_sched' => 'boolean', + 'show_desc' => 'boolean', + 'time' => 'integer', + 'default_name' => 'text', + 'display_hosts' => 'boolean', + 'link_hosts' => 'boolean', + 'avatar_width' => 'integer', + 'title_position' => 'slug', + 'ajax' => 'boolean', + 'countdown' => 'boolean', + 'dynamic' => 'boolean', + 'widget' => 'boolean', + 'id' => 'integer', + 'instance' => 'integer', + ); + + } elseif ( 'upcoming-shows' == $type ) { + + // --- upcoming shows attribute keys --- + $keys = array( + 'title' => 'text', + 'limit' => 'integer', + 'show_avatar' => 'boolean', + 'show_link' => 'boolean', + 'time' => 'integer', + 'show_sched' => 'boolean', + 'default_name' => 'string', + 'display_hosts' => 'boolean', + 'link_hosts' => 'boolean', + 'avatar_width' => 'integer', + 'title_position' => 'slug', + 'ajax' => 'boolean', + 'countdown' => 'boolean', + 'dynamic' => 'boolean', + 'widget' => 'boolean', + 'id' => 'integer', + 'instance' => 'integer', + ); + + } elseif ( 'current-playlist' == $type ) { + + // --- current playlist attribute keys --- + $keys = array( + 'title' => 'text', + 'artist' => 'boolean', + 'song' => 'boolean', + 'album' => 'boolean', + 'label' => 'boolean', + 'comments' => 'boolean', + 'ajax' => 'boolean', + 'countdown' => 'boolean', + 'dynamic' => 'boolean', + 'widget' => 'boolean', + 'id' => 'integer', + 'instance' => 'integer', + ); + } + + // --- handle extra keys --- + if ( $extras && is_array( $extras ) && ( count( $extras ) > 0 ) ) { + $keys = array_merge( $keys, $extras ); + } + + // --- sanitize values by key type --- + $atts = radio_station_sanitize_values( $_REQUEST, $keys ); + return $atts; +} + // -------------------- // === Translations === @@ -2895,7 +3993,8 @@ function radio_station_sanitize_values( $data, $keys ) { // important note: translated individually as cannot translate a variable // 2.2.7: use wp locale class to translate weekdays // 2.3.0: allow for abbreviated and long version changeovers -function radio_station_translate_weekday( $weekday, $short = false ) { +// 2.3.2: default short to null for more flexibility +function radio_station_translate_weekday( $weekday, $short = null ) { // 2.3.0: return empty for empty select option if ( empty( $weekday ) ) { @@ -2904,63 +4003,64 @@ function radio_station_translate_weekday( $weekday, $short = false ) { global $wp_locale; - if ( $short ) { - - // --- translate abbreviated weekday --- - if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 0 ) ); - } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 1 ) ); - } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 2 ) ); - } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 3 ) ); - } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 4 ) ); - } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 5 ) ); - } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( 6 ) ); - } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { - // 2.3.0: add support for numeric weekday value - $weekday = $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $weekday ) ); + $days = radio_station_get_weekday(); + + // --- translate weekday --- + // 2.3.2: optimized weekday translations + foreach ( $days as $i => $day ) { + $abbr = substr( $day, 0, 3 ); + if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) + || ( is_null( $short ) && ( $weekday == $day ) ) ) { + return $wp_locale->get_weekday( $i ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); + } } + } - } else { - - // --- translate full weekday --- - // 2.2.7: fix to typo for Tuesday - // 2.3.0: fix to typo for Thursday (jeez!) - if ( ( 'Sunday' == $weekday ) || ( 'Sun' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 0 ); - } elseif ( ( 'Monday' == $weekday ) || ( 'Mon' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 1 ); - } elseif ( ( 'Tuesday' == $weekday ) || ( 'Tue' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 2 ); - } elseif ( ( 'Wednesday' == $weekday ) || ( 'Wed' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 3 ); - } elseif ( ( 'Thursday' == $weekday ) || ( 'Thu' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 4 ); - } elseif ( ( 'Friday' == $weekday ) || ( 'Fri' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 5 ); - } elseif ( ( 'Saturday' == $weekday ) || ( 'Sat' == $weekday ) ) { - $weekday = $wp_locale->get_weekday( 6 ); - } elseif ( ( intval( $weekday ) > - 1 ) && ( intval( $weekday ) < 7 ) ) { - // 2.3.0: add support for numeric weekday value - $weekday = $wp_locale->get_weekday( $weekday ); + // --- fallback if day number supplied --- + // 2.3.2: optimized day number fallback + $daynum = intval( $weekday ); + if ( ( $daynum > -1 ) && ( $daynum < 7 ) ) { + if ( $short ) { + return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $daynum ) ); + } else { + return $wp_locale->get_weekday( $daynum ); } - } - + return $weekday; } +// ---------------- +// Replace Weekdays +// ---------------- +// 2.3.2: to replace with translated weekdays in a time string +function radio_station_replace_weekday( $string ) { + + $days = radio_station_get_weekday(); + foreach( $days as $day ) { + $abbr = substr( $day, 0, 3 ); + if ( strstr( $string, $day ) ) { + $translated = radio_station_translate_weekday( $day ); + $string = str_replace( $day, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = radio_station_translate_weekday( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } + } + + return $string; +} + // --------------- // Translate Month // --------------- // important note: translated individually as cannot translate a variable // 2.2.7: use wp locale class to translate months -function radio_station_translate_month( $month, $short = false ) { +// 2.3.2: default short to null for more flexibility +function radio_station_translate_month( $month, $short = null ) { // 2.3.0: return empty for empty select option if ( empty( $month ) ) { @@ -2968,72 +4068,56 @@ function radio_station_translate_month( $month, $short = false ) { } global $wp_locale; - if ( $short ) { - - // --- translate abbreviated month --- - if ( 'Jan' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 1 ) ); - } elseif ( 'Feb' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 2 ) ); - } elseif ( 'Mar' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 3 ) ); - } elseif ( 'Apr' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 4 ) ); - } elseif ( 'May' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 5 ) ); - } elseif ( 'Jun' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 6 ) ); - } elseif ( 'Jul' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 7 ) ); - } elseif ( 'Aug' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 8 ) ); - } elseif ( 'Sep' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 9 ) ); - } elseif ( 'Oct' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 10 ) ); - } elseif ( 'Nov' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 11 ) ); - } elseif ( 'Dec' === $month ) { - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( 12 ) ); - } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { - // 2.3.0: add support for numeric month value - $month = $wp_locale->get_month_abbrev( $wp_locale->get_month( $month ) ); + + $months = radio_station_get_month(); + + // --- translate month --- + // 2.3.2: optimized month translations + foreach ( $months as $i => $fullmonth ) { + $abbr = substr( $fullmonth, 0, 3 ); + if ( ( $month == $fullmonth ) || ( $month == $abbr ) ) { + if ( ( !$short && !is_null( $short ) ) + || ( is_null( $short ) && ( $month == $fullmonth ) ) ) { + return $wp_locale->get_month( ( $i + 1 ) ); + } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { + return $wp_locale->get_month_abbrev( $wp_locale->get_month( ( $i + 1 ) ) ); + } } - } else { + } - // --- translate full month --- - if ( 'January' === $month ) { - $month = $wp_locale->get_month( 1 ); - } elseif ( 'February' === $month ) { - $month = $wp_locale->get_month( 2 ); - } elseif ( 'March' === $month ) { - $month = $wp_locale->get_month( 3 ); - } elseif ( 'April' === $month ) { - $month = $wp_locale->get_month( 4 ); - } elseif ( 'May' === $month ) { - $month = $wp_locale->get_month( 5 ); - } elseif ( 'June' === $month ) { - $month = $wp_locale->get_month( 6 ); - } elseif ( 'July' === $month ) { - $month = $wp_locale->get_month( 7 ); - } elseif ( 'August' === $month ) { - $month = $wp_locale->get_month( 8 ); - } elseif ( 'September' === $month ) { - $month = $wp_locale->get_month( 9 ); - } elseif ( 'October' === $month ) { - $month = $wp_locale->get_month( 10 ); - } elseif ( 'November' === $month ) { - $month = $wp_locale->get_month( 11 ); - } elseif ( 'December' === $month ) { - $month = $wp_locale->get_month( 12 ); - } elseif ( ( intval( $month ) > 0 ) && ( intval( $month ) < 13 ) ) { - // 2.3.0: add support for numeric month value - $month = $wp_locale->get_month( $month ); + // --- fallback if month number supplied --- + // 2.3.2: optimized month number fallback + $monthnum = intval( $month ); + if ( ( $monthnum > 0 ) && ( $monthnum < 13 ) ) { + if ( $short ) { + return $wp_locale->get_month_abbrev( $wp_locale->get_month( $monthnum ) ); + } else { + return $wp_locale->get_month( $monthnum ); } + } + + return $month; +} +// -------------- +// Replace Months +// -------------- +// 2.3.2: to replace with translated months in a time string +function radio_station_replace_month( $string ) { + + $months = radio_station_get_month(); + foreach( $months as $month ) { + $abbr = substr( $month, 0, 3 ); + if ( strstr( $string, $month ) ) { + $translated = radio_station_translate_month( $month ); + $string = str_replace( $month, $translated, $string ); + } elseif ( strstr( $string, $abbr ) ) { + $translated = radio_station_translate_month( $abbr ); + $string = str_replace( $abbr, $translated, $string ); + } } - return $month; + return $string; } // ------------------ @@ -3046,3 +4130,51 @@ function radio_station_translate_meridiem( $meridiem ) { return $wp_locale->get_meridiem( $meridiem ); } +// ---------------- +// Replace Meridiem +// ---------------- +// 2.3.2: added optimized meridiem replacement +function radio_station_replace_meridiem( $string ) { + + global $radio_station_data; + if ( isset( $radio_station_data['meridiems'] ) ) { + $meridiems = $radio_station_data['meridiems']; + } else { + $meridiems = array( + 'am' => radio_station_translate_meridiem( 'am' ), + 'pm' => radio_station_translate_meridiem( 'pm' ), + 'AM' => radio_station_translate_meridiem( 'AM' ), + 'PM' => radio_station_translate_meridiem( 'PM' ), + ); + $radio_station_data['meridiems'] = $meridiems; + } + + + if ( strstr( $string, 'am' ) ) { + $string = str_replace( 'am', $meridiems['am'], $string ); + } + if ( strstr( $string, 'pm' ) ) { + $string = str_replace( 'pm', $meridiems['pm'], $string ); + } + if ( strstr( $string, 'AM' ) ) { + $string = str_replace( 'AM', $meridiems['AM'], $string ); + } + if ( strstr( $string, 'PM' ) ) { + $string = str_replace( 'PM', $meridiems['PM'], $string ); + } + + return $string; +} + +// --------------------- +// Translate Time String +// --------------------- +// 2.3.2: replace with translated month, day and meridiem in a string +function radio_station_translate_time( $string ) { + + $string = radio_station_replace_meridiem( $string ); + $string = radio_station_replace_weekday( $string ); + $string = radio_station_replace_month( $string ); + + return $string; +} diff --git a/js/jstz.js b/js/jstz.js new file mode 100644 index 0000000..f849c57 --- /dev/null +++ b/js/jstz.js @@ -0,0 +1,1462 @@ +(function (root) {/*global exports, Intl*/ +/** + * This script gives you the zone info key representing your device's time zone setting. + * + * @name jsTimezoneDetect + * @version 1.0.6 + * @author Jon Nylander + * @license MIT License - https://bitbucket.org/pellepim/jstimezonedetect/src/default/LICENCE.txt + * + * For usage and examples, visit: + * http://pellepim.bitbucket.org/jstz/ + * + * Copyright (c) Jon Nylander + */ + + +/** + * Namespace to hold all the code for timezone detection. + */ +var jstz = (function () { + 'use strict'; + var HEMISPHERE_SOUTH = 's', + + consts = { + DAY: 86400000, + HOUR: 3600000, + MINUTE: 60000, + SECOND: 1000, + BASELINE_YEAR: 2014, + MAX_SCORE: 864000000, // 10 days + AMBIGUITIES: { + 'America/Denver': ['America/Mazatlan'], + 'America/Chicago': ['America/Mexico_City'], + 'America/Asuncion': ['America/Campo_Grande', 'America/Santiago'], + 'America/Montevideo': ['America/Sao_Paulo', 'America/Santiago'], + 'Asia/Beirut': ['Asia/Amman', 'Asia/Jerusalem', 'Europe/Helsinki', 'Asia/Damascus', 'Africa/Cairo', 'Asia/Gaza', 'Europe/Minsk', 'Africa/Windhoek'], + 'Pacific/Auckland': ['Pacific/Fiji'], + 'America/Los_Angeles': ['America/Santa_Isabel'], + 'America/New_York': ['America/Havana'], + 'America/Halifax': ['America/Goose_Bay'], + 'America/Godthab': ['America/Miquelon'], + 'Asia/Dubai': ['Asia/Yerevan'], + 'Asia/Jakarta': ['Asia/Krasnoyarsk'], + 'Asia/Shanghai': ['Asia/Irkutsk', 'Australia/Perth'], + 'Australia/Sydney': ['Australia/Lord_Howe'], + 'Asia/Tokyo': ['Asia/Yakutsk'], + 'Asia/Dhaka': ['Asia/Omsk'], + 'Asia/Baku': ['Asia/Yerevan'], + 'Australia/Brisbane': ['Asia/Vladivostok'], + 'Pacific/Noumea': ['Asia/Vladivostok'], + 'Pacific/Majuro': ['Asia/Kamchatka', 'Pacific/Fiji'], + 'Pacific/Tongatapu': ['Pacific/Apia'], + 'Asia/Baghdad': ['Europe/Minsk', 'Europe/Moscow'], + 'Asia/Karachi': ['Asia/Yekaterinburg'], + 'Africa/Johannesburg': ['Asia/Gaza', 'Africa/Cairo'] + } + }, + + /** + * Gets the offset in minutes from UTC for a certain date. + * @param {Date} date + * @returns {Number} + */ + get_date_offset = function get_date_offset(date) { + var offset = -date.getTimezoneOffset(); + return (offset !== null ? offset : 0); + }, + + + get_offsets = function get_offsets() { + var offsets = []; + + for (var month = 0; month <= 11; month++) { + for (var date = 1; date <= 28; date++) { + var currentOffset = get_date_offset(new Date(consts.BASELINE_YEAR, month, date)); + if (!offsets) { + offsets.push(); + } else if (offsets && offsets[offsets.length-1] !== currentOffset) { + offsets.push(currentOffset); + } + } + } + + return offsets; + }, + + /** + * This function does some basic calculations to create information about + * the user's timezone. It uses REFERENCE_YEAR as a solid year for which + * the script has been tested rather than depend on the year set by the + * client device. + * + * Returns a key that can be used to do lookups in jstz.olson.timezones. + * eg: "720,1,2". + * + * @returns {String} + */ + lookup_key = function lookup_key() { + var diff = 0; + var offsets = get_offsets(); + + if (offsets.length > 1) { + diff = offsets[0] - offsets[1]; + } + + if (offsets.length > 3) { + return offsets[0] + ",1,weird"; + } else if (diff < 0) { + return offsets[0] + ",1"; + } else if (diff > 0) { + return offsets[1] + ",1," + HEMISPHERE_SOUTH; + } + + return offsets[0] + ",0"; + }, + + + /** + * Tries to get the time zone key directly from the operating system for those + * environments that support the ECMAScript Internationalization API. + */ + get_from_internationalization_api = function get_from_internationalization_api() { + var format, timezone; + if (!Intl || typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") { + return; + } + + format = Intl.DateTimeFormat(); + + if (typeof format === "undefined" || typeof format.resolvedOptions === "undefined") { + return; + } + + timezone = format.resolvedOptions().timeZone; + + if (timezone && (timezone.indexOf("/") > -1 || timezone === 'UTC')) { + return timezone; + } + + }, + + /** + * Starting point for getting all the DST rules for a specific year + * for the current timezone (as described by the client system). + * + * Returns an object with start and end attributes, or false if no + * DST rules were found for the year. + * + * @param year + * @returns {Object} || {Boolean} + */ + dst_dates = function dst_dates(year) { + var yearstart = new Date(year, 0, 1, 0, 0, 1, 0).getTime(); + var yearend = new Date(year, 12, 31, 23, 59, 59).getTime(); + var current = yearstart; + var offset = (new Date(current)).getTimezoneOffset(); + var dst_start = null; + var dst_end = null; + + while (current < yearend - 86400000) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + if (dateToCheckOffset < offset) { + dst_start = dateToCheck; + } + if (dateToCheckOffset > offset) { + dst_end = dateToCheck; + } + offset = dateToCheckOffset; + } + + current += 86400000; + } + + if (dst_start && dst_end) { + return { + s: find_dst_fold(dst_start).getTime(), + e: find_dst_fold(dst_end).getTime() + }; + } + + return false; + }, + + /** + * Probably completely unnecessary function that recursively finds the + * exact (to the second) time when a DST rule was changed. + * + * @param a_date - The candidate Date. + * @param padding - integer specifying the padding to allow around the candidate + * date for finding the fold. + * @param iterator - integer specifying how many milliseconds to iterate while + * searching for the fold. + * + * @returns {Date} + */ + find_dst_fold = function find_dst_fold(a_date, padding, iterator) { + if (typeof padding === 'undefined') { + padding = consts.DAY; + iterator = consts.HOUR; + } + + var date_start = new Date(a_date.getTime() - padding).getTime(); + var date_end = a_date.getTime() + padding; + var offset = new Date(date_start).getTimezoneOffset(); + + var current = date_start; + + var dst_change = null; + while (current < date_end - iterator) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + dst_change = dateToCheck; + break; + } + current += iterator; + } + + if (padding === consts.DAY) { + return find_dst_fold(dst_change, consts.HOUR, consts.MINUTE); + } + + if (padding === consts.HOUR) { + return find_dst_fold(dst_change, consts.MINUTE, consts.SECOND); + } + + return dst_change; + }, + + windows7_adaptations = function windows7_adaptions(rule_list, preliminary_timezone, score, sample) { + if (score !== 'N/A') { + return score; + } + if (preliminary_timezone === 'Asia/Beirut') { + if (sample.name === 'Africa/Cairo') { + if (rule_list[6].s === 1398376800000 && rule_list[6].e === 1411678800000) { + return 0; + } + } + if (sample.name === 'Asia/Jerusalem') { + if (rule_list[6].s === 1395964800000 && rule_list[6].e === 1411858800000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Santiago') { + if (sample.name === 'America/Asuncion') { + if (rule_list[6].s === 1412481600000 && rule_list[6].e === 1397358000000) { + return 0; + } + } + if (sample.name === 'America/Campo_Grande') { + if (rule_list[6].s === 1413691200000 && rule_list[6].e === 1392519600000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Montevideo') { + if (sample.name === 'America/Sao_Paulo') { + if (rule_list[6].s === 1413687600000 && rule_list[6].e === 1392516000000) { + return 0; + } + } + } else if (preliminary_timezone === 'Pacific/Auckland') { + if (sample.name === 'Pacific/Fiji') { + if (rule_list[6].s === 1414245600000 && rule_list[6].e === 1396101600000) { + return 0; + } + } + } + + return score; + }, + + /** + * Takes the DST rules for the current timezone, and proceeds to find matches + * in the jstz.olson.dst_rules.zones array. + * + * Compares samples to the current timezone on a scoring basis. + * + * Candidates are ruled immediately if either the candidate or the current zone + * has a DST rule where the other does not. + * + * Candidates are ruled out immediately if the current zone has a rule that is + * outside the DST scope of the candidate. + * + * Candidates are included for scoring if the current zones rules fall within the + * span of the samples rules. + * + * Low score is best, the score is calculated by summing up the differences in DST + * rules and if the consts.MAX_SCORE is overreached the candidate is ruled out. + * + * Yah follow? :) + * + * @param rule_list + * @param preliminary_timezone + * @returns {*} + */ + best_dst_match = function best_dst_match(rule_list, preliminary_timezone) { + var score_sample = function score_sample(sample) { + var score = 0; + + for (var j = 0; j < rule_list.length; j++) { + + // Both sample and current time zone report DST during the year. + if (!!sample.rules[j] && !!rule_list[j]) { + + // The current time zone's DST rules are inside the sample's. Include. + if (rule_list[j].s >= sample.rules[j].s && rule_list[j].e <= sample.rules[j].e) { + score = 0; + score += Math.abs(rule_list[j].s - sample.rules[j].s); + score += Math.abs(sample.rules[j].e - rule_list[j].e); + + // The current time zone's DST rules are outside the sample's. Discard. + } else { + score = 'N/A'; + break; + } + + // The max score has been reached. Discard. + if (score > consts.MAX_SCORE) { + score = 'N/A'; + break; + } + } + } + + score = windows7_adaptations(rule_list, preliminary_timezone, score, sample); + + return score; + }; + var scoreboard = {}; + var dst_zones = jstz.olson.dst_rules.zones; + var dst_zones_length = dst_zones.length; + var ambiguities = consts.AMBIGUITIES[preliminary_timezone]; + + for (var i = 0; i < dst_zones_length; i++) { + var sample = dst_zones[i]; + var score = score_sample(dst_zones[i]); + + if (score !== 'N/A') { + scoreboard[sample.name] = score; + } + } + + for (var tz in scoreboard) { + if (scoreboard.hasOwnProperty(tz)) { + for (var j = 0; j < ambiguities.length; j++) { + if (ambiguities[j] === tz) { + return tz; + } + } + } + } + + return preliminary_timezone; + }, + + /** + * Takes the preliminary_timezone as detected by lookup_key(). + * + * Builds up the current timezones DST rules for the years defined + * in the jstz.olson.dst_rules.years array. + * + * If there are no DST occurences for those years, immediately returns + * the preliminary timezone. Otherwise proceeds and tries to solve + * ambiguities. + * + * @param preliminary_timezone + * @returns {String} timezone_name + */ + get_by_dst = function get_by_dst(preliminary_timezone) { + var get_rules = function get_rules() { + var rule_list = []; + for (var i = 0; i < jstz.olson.dst_rules.years.length; i++) { + var year_rules = dst_dates(jstz.olson.dst_rules.years[i]); + rule_list.push(year_rules); + } + return rule_list; + }; + var check_has_dst = function check_has_dst(rules) { + for (var i = 0; i < rules.length; i++) { + if (rules[i] !== false) { + return true; + } + } + return false; + }; + var rules = get_rules(); + var has_dst = check_has_dst(rules); + + if (has_dst) { + return best_dst_match(rules, preliminary_timezone); + } + + return preliminary_timezone; + }, + + /** + * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary. + * + * Returns an object with one function ".name()" + * + * @returns Object + */ + determine = function determine(using_intl) { + var preliminary_tz = false; + var needle = lookup_key(); + if (using_intl || typeof using_intl === 'undefined') { + preliminary_tz = get_from_internationalization_api(); + } + + if (!preliminary_tz) { + preliminary_tz = jstz.olson.timezones[needle]; + + if (typeof consts.AMBIGUITIES[preliminary_tz] !== 'undefined') { + preliminary_tz = get_by_dst(preliminary_tz); + } + } + + return { + name: function () { + return preliminary_tz; + }, + using_intl: using_intl || typeof using_intl === 'undefined', + needle: needle, + offsets: get_offsets() + }; + }; + + return { + determine: determine + }; +}()); + + +jstz.olson = jstz.olson || {}; + +/** + * The keys in this dictionary are comma separated as such: + * + * First the offset compared to UTC time in minutes. + * + * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it + * does. + * + * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere, + * only interesting for timezones with DST. + * + * The mapped arrays is used for constructing the jstz.TimeZone object from within + * jstz.determine(); + */ +jstz.olson.timezones = { + '-720,0': 'Etc/GMT+12', + '-660,0': 'Pacific/Pago_Pago', + '-660,1,s': 'Pacific/Apia', // Why? Because windows... cry! + '-600,1': 'America/Adak', + '-600,0': 'Pacific/Honolulu', + '-570,0': 'Pacific/Marquesas', + '-540,0': 'Pacific/Gambier', + '-540,1': 'America/Anchorage', + '-480,1': 'America/Los_Angeles', + '-480,0': 'Pacific/Pitcairn', + '-420,0': 'America/Phoenix', + '-420,1': 'America/Denver', + '-360,0': 'America/Guatemala', + '-360,1': 'America/Chicago', + '-360,1,s': 'Pacific/Easter', + '-300,0': 'America/Bogota', + '-300,1': 'America/New_York', + '-270,0': 'America/Caracas', + '-240,1': 'America/Halifax', + '-240,0': 'America/Santo_Domingo', + '-240,1,s': 'America/Asuncion', + '-210,1': 'America/St_Johns', + '-180,1': 'America/Godthab', + '-180,0': 'America/Buenos_Aires', + '-180,1,s': 'America/Montevideo', + '-120,0': 'America/Noronha', + '-120,1': 'America/Noronha', + '-60,1': 'Atlantic/Azores', + '-60,0': 'Atlantic/Cape_Verde', + '0,0': 'UTC', + '0,1': 'Europe/London', + '0,1,weird': 'Africa/Casablanca', + '60,1': 'Europe/Berlin', + '60,0': 'Africa/Lagos', + '60,1,weird': 'Africa/Casablanca', + '120,1': 'Asia/Beirut', + '120,1,weird': 'Africa/Cairo', + '120,0': 'Africa/Johannesburg', + '180,0': 'Asia/Baghdad', + '180,1': 'Europe/Moscow', + '210,1': 'Asia/Tehran', + '240,0': 'Asia/Dubai', + '240,1': 'Asia/Baku', + '270,0': 'Asia/Kabul', + '300,1': 'Asia/Yekaterinburg', + '300,0': 'Asia/Karachi', + '330,0': 'Asia/Calcutta', + '345,0': 'Asia/Katmandu', + '360,0': 'Asia/Dhaka', + '360,1': 'Asia/Omsk', + '390,0': 'Asia/Rangoon', + '420,1': 'Asia/Krasnoyarsk', + '420,0': 'Asia/Jakarta', + '480,0': 'Asia/Shanghai', + '480,1': 'Asia/Irkutsk', + '525,0': 'Australia/Eucla', + '525,1,s': 'Australia/Eucla', + '540,1': 'Asia/Yakutsk', + '540,0': 'Asia/Tokyo', + '570,0': 'Australia/Darwin', + '570,1,s': 'Australia/Adelaide', + '600,0': 'Australia/Brisbane', + '600,1': 'Asia/Vladivostok', + '600,1,s': 'Australia/Sydney', + '630,1,s': 'Australia/Lord_Howe', + '660,1': 'Asia/Kamchatka', + '660,0': 'Pacific/Noumea', + '690,0': 'Pacific/Norfolk', + '720,1,s': 'Pacific/Auckland', + '720,0': 'Pacific/Majuro', + '765,1,s': 'Pacific/Chatham', + '780,0': 'Pacific/Tongatapu', + '780,1,s': 'Pacific/Apia', + '840,0': 'Pacific/Kiritimati' +}; + +/* Build time: 2019-09-09 11:29:41Z Build by invoking python utilities/dst.py generate */ +jstz.olson.dst_rules = { + "years": [ + 2008, + 2009, + 2010, + 2011, + 2012, + 2013, + 2014 + ], + "zones": [ + { + "name": "Africa/Cairo", + "rules": [ + { + "e": 1219957200000, + "s": 1209074400000 + }, + { + "e": 1250802000000, + "s": 1240524000000 + }, + { + "e": 1285880400000, + "s": 1284069600000 + }, + false, + false, + false, + { + "e": 1411678800000, + "s": 1406844000000 + } + ] + }, + { + "name": "America/Asuncion", + "rules": [ + { + "e": 1205031600000, + "s": 1224388800000 + }, + { + "e": 1236481200000, + "s": 1255838400000 + }, + { + "e": 1270954800000, + "s": 1286078400000 + }, + { + "e": 1302404400000, + "s": 1317528000000 + }, + { + "e": 1333854000000, + "s": 1349582400000 + }, + { + "e": 1364094000000, + "s": 1381032000000 + }, + { + "e": 1395543600000, + "s": 1412481600000 + } + ] + }, + { + "name": "America/Campo_Grande", + "rules": [ + { + "e": 1203217200000, + "s": 1224388800000 + }, + { + "e": 1234666800000, + "s": 1255838400000 + }, + { + "e": 1266721200000, + "s": 1287288000000 + }, + { + "e": 1298170800000, + "s": 1318737600000 + }, + { + "e": 1330225200000, + "s": 1350792000000 + }, + { + "e": 1361070000000, + "s": 1382241600000 + }, + { + "e": 1392519600000, + "s": 1413691200000 + } + ] + }, + { + "name": "America/Goose_Bay", + "rules": [ + { + "e": 1225594860000, + "s": 1205035260000 + }, + { + "e": 1257044460000, + "s": 1236484860000 + }, + { + "e": 1289098860000, + "s": 1268539260000 + }, + { + "e": 1320555600000, + "s": 1299988860000 + }, + { + "e": 1352005200000, + "s": 1331445600000 + }, + { + "e": 1383454800000, + "s": 1362895200000 + }, + { + "e": 1414904400000, + "s": 1394344800000 + } + ] + }, + { + "name": "America/Havana", + "rules": [ + { + "e": 1224997200000, + "s": 1205643600000 + }, + { + "e": 1256446800000, + "s": 1236488400000 + }, + { + "e": 1288501200000, + "s": 1268542800000 + }, + { + "e": 1321160400000, + "s": 1300597200000 + }, + { + "e": 1352005200000, + "s": 1333256400000 + }, + { + "e": 1383454800000, + "s": 1362891600000 + }, + { + "e": 1414904400000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Mazatlan", + "rules": [ + { + "e": 1225008000000, + "s": 1207472400000 + }, + { + "e": 1256457600000, + "s": 1238922000000 + }, + { + "e": 1288512000000, + "s": 1270371600000 + }, + { + "e": 1319961600000, + "s": 1301821200000 + }, + { + "e": 1351411200000, + "s": 1333270800000 + }, + { + "e": 1382860800000, + "s": 1365325200000 + }, + { + "e": 1414310400000, + "s": 1396774800000 + } + ] + }, + { + "name": "America/Mexico_City", + "rules": [ + { + "e": 1225004400000, + "s": 1207468800000 + }, + { + "e": 1256454000000, + "s": 1238918400000 + }, + { + "e": 1288508400000, + "s": 1270368000000 + }, + { + "e": 1319958000000, + "s": 1301817600000 + }, + { + "e": 1351407600000, + "s": 1333267200000 + }, + { + "e": 1382857200000, + "s": 1365321600000 + }, + { + "e": 1414306800000, + "s": 1396771200000 + } + ] + }, + { + "name": "America/Miquelon", + "rules": [ + { + "e": 1225598400000, + "s": 1205038800000 + }, + { + "e": 1257048000000, + "s": 1236488400000 + }, + { + "e": 1289102400000, + "s": 1268542800000 + }, + { + "e": 1320552000000, + "s": 1299992400000 + }, + { + "e": 1352001600000, + "s": 1331442000000 + }, + { + "e": 1383451200000, + "s": 1362891600000 + }, + { + "e": 1414900800000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Santa_Isabel", + "rules": [ + { + "e": 1225011600000, + "s": 1207476000000 + }, + { + "e": 1256461200000, + "s": 1238925600000 + }, + { + "e": 1289120400000, + "s": 1268560800000 + }, + { + "e": 1320570000000, + "s": 1300010400000 + }, + { + "e": 1352019600000, + "s": 1331460000000 + }, + { + "e": 1383469200000, + "s": 1362909600000 + }, + { + "e": 1414918800000, + "s": 1394359200000 + } + ] + }, + { + "name": "America/Santiago", + "rules": [ + { + "e": 1206846000000, + "s": 1223784000000 + }, + { + "e": 1237086000000, + "s": 1255233600000 + }, + { + "e": 1270350000000, + "s": 1286683200000 + }, + { + "e": 1304823600000, + "s": 1313899200000 + }, + { + "e": 1335668400000, + "s": 1346558400000 + }, + { + "e": 1367118000000, + "s": 1378612800000 + }, + { + "e": 1398567600000, + "s": 1410062400000 + } + ] + }, + { + "name": "America/Sao_Paulo", + "rules": [ + { + "e": 1203213600000, + "s": 1224385200000 + }, + { + "e": 1234663200000, + "s": 1255834800000 + }, + { + "e": 1266717600000, + "s": 1287284400000 + }, + { + "e": 1298167200000, + "s": 1318734000000 + }, + { + "e": 1330221600000, + "s": 1350788400000 + }, + { + "e": 1361066400000, + "s": 1382238000000 + }, + { + "e": 1392516000000, + "s": 1413687600000 + } + ] + }, + { + "name": "Asia/Amman", + "rules": [ + { + "e": 1225404000000, + "s": 1206655200000 + }, + { + "e": 1256853600000, + "s": 1238104800000 + }, + { + "e": 1288303200000, + "s": 1269554400000 + }, + { + "e": 1319752800000, + "s": 1301608800000 + }, + false, + false, + { + "e": 1414706400000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Damascus", + "rules": [ + { + "e": 1225486800000, + "s": 1207260000000 + }, + { + "e": 1256850000000, + "s": 1238104800000 + }, + { + "e": 1288299600000, + "s": 1270159200000 + }, + { + "e": 1319749200000, + "s": 1301608800000 + }, + { + "e": 1351198800000, + "s": 1333058400000 + }, + { + "e": 1382648400000, + "s": 1364508000000 + }, + { + "e": 1414702800000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Dubai", + "rules": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Gaza", + "rules": [ + { + "e": 1219957200000, + "s": 1206655200000 + }, + { + "e": 1252015200000, + "s": 1238104800000 + }, + { + "e": 1281474000000, + "s": 1269640860000 + }, + { + "e": 1312146000000, + "s": 1301608860000 + }, + { + "e": 1348178400000, + "s": 1333058400000 + }, + { + "e": 1380229200000, + "s": 1364508000000 + }, + { + "e": 1414098000000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Irkutsk", + "rules": [ + { + "e": 1224957600000, + "s": 1206813600000 + }, + { + "e": 1256407200000, + "s": 1238263200000 + }, + { + "e": 1288461600000, + "s": 1269712800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Jerusalem", + "rules": [ + { + "e": 1223161200000, + "s": 1206662400000 + }, + { + "e": 1254006000000, + "s": 1238112000000 + }, + { + "e": 1284246000000, + "s": 1269561600000 + }, + { + "e": 1317510000000, + "s": 1301616000000 + }, + { + "e": 1348354800000, + "s": 1333065600000 + }, + { + "e": 1382828400000, + "s": 1364515200000 + }, + { + "e": 1414278000000, + "s": 1395964800000 + } + ] + }, + { + "name": "Asia/Kamchatka", + "rules": [ + { + "e": 1224943200000, + "s": 1206799200000 + }, + { + "e": 1256392800000, + "s": 1238248800000 + }, + { + "e": 1288450800000, + "s": 1269698400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Krasnoyarsk", + "rules": [ + { + "e": 1224961200000, + "s": 1206817200000 + }, + { + "e": 1256410800000, + "s": 1238266800000 + }, + { + "e": 1288465200000, + "s": 1269716400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Omsk", + "rules": [ + { + "e": 1224964800000, + "s": 1206820800000 + }, + { + "e": 1256414400000, + "s": 1238270400000 + }, + { + "e": 1288468800000, + "s": 1269720000000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Vladivostok", + "rules": [ + { + "e": 1224950400000, + "s": 1206806400000 + }, + { + "e": 1256400000000, + "s": 1238256000000 + }, + { + "e": 1288454400000, + "s": 1269705600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yakutsk", + "rules": [ + { + "e": 1224954000000, + "s": 1206810000000 + }, + { + "e": 1256403600000, + "s": 1238259600000 + }, + { + "e": 1288458000000, + "s": 1269709200000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yekaterinburg", + "rules": [ + { + "e": 1224968400000, + "s": 1206824400000 + }, + { + "e": 1256418000000, + "s": 1238274000000 + }, + { + "e": 1288472400000, + "s": 1269723600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yerevan", + "rules": [ + { + "e": 1224972000000, + "s": 1206828000000 + }, + { + "e": 1256421600000, + "s": 1238277600000 + }, + { + "e": 1288476000000, + "s": 1269727200000 + }, + { + "e": 1319925600000, + "s": 1301176800000 + }, + false, + false, + false + ] + }, + { + "name": "Australia/Lord_Howe", + "rules": [ + { + "e": 1207407600000, + "s": 1223134200000 + }, + { + "e": 1238857200000, + "s": 1254583800000 + }, + { + "e": 1270306800000, + "s": 1286033400000 + }, + { + "e": 1301756400000, + "s": 1317483000000 + }, + { + "e": 1333206000000, + "s": 1349537400000 + }, + { + "e": 1365260400000, + "s": 1380987000000 + }, + { + "e": 1396710000000, + "s": 1412436600000 + } + ] + }, + { + "name": "Australia/Perth", + "rules": [ + { + "e": 1206813600000, + "s": 1224957600000 + }, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Helsinki", + "rules": [ + { + "e": 1224982800000, + "s": 1206838800000 + }, + { + "e": 1256432400000, + "s": 1238288400000 + }, + { + "e": 1288486800000, + "s": 1269738000000 + }, + { + "e": 1319936400000, + "s": 1301187600000 + }, + { + "e": 1351386000000, + "s": 1332637200000 + }, + { + "e": 1382835600000, + "s": 1364691600000 + }, + { + "e": 1414285200000, + "s": 1396141200000 + } + ] + }, + { + "name": "Europe/Minsk", + "rules": [ + { + "e": 1224979200000, + "s": 1206835200000 + }, + { + "e": 1256428800000, + "s": 1238284800000 + }, + { + "e": 1288483200000, + "s": 1269734400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Moscow", + "rules": [ + { + "e": 1224975600000, + "s": 1206831600000 + }, + { + "e": 1256425200000, + "s": 1238281200000 + }, + { + "e": 1288479600000, + "s": 1269730800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Pacific/Apia", + "rules": [ + false, + false, + false, + { + "e": 1301752800000, + "s": 1316872800000 + }, + { + "e": 1333202400000, + "s": 1348927200000 + }, + { + "e": 1365256800000, + "s": 1380376800000 + }, + { + "e": 1396706400000, + "s": 1411826400000 + } + ] + }, + { + "name": "Pacific/Fiji", + "rules": [ + false, + false, + { + "e": 1269698400000, + "s": 1287842400000 + }, + { + "e": 1327154400000, + "s": 1319292000000 + }, + { + "e": 1358604000000, + "s": 1350741600000 + }, + { + "e": 1390050000000, + "s": 1382796000000 + }, + { + "e": 1421503200000, + "s": 1414850400000 + } + ] + }, + { + "name": "Europe/London", + "rules": [ + { + "e": 1224982800000, + "s": 1206838800000 + }, + { + "e": 1256432400000, + "s": 1238288400000 + }, + { + "e": 1288486800000, + "s": 1269738000000 + }, + { + "e": 1319936400000, + "s": 1301187600000 + }, + { + "e": 1351386000000, + "s": 1332637200000 + }, + { + "e": 1382835600000, + "s": 1364691600000 + }, + { + "e": 1414285200000, + "s": 1396141200000 + } + ] + }, + { + "name": "Africa/Windhoek", + "rules": [ + { + "e": 1220749200000, + "s": 1207440000000 + }, + { + "e": 1252198800000, + "s": 1238889600000 + }, + { + "e": 1283648400000, + "s": 1270339200000 + }, + { + "e": 1315098000000, + "s": 1301788800000 + }, + { + "e": 1346547600000, + "s": 1333238400000 + }, + { + "e": 1377997200000, + "s": 1365292800000 + }, + { + "e": 1410051600000, + "s": 1396742400000 + } + ] + } + ] +}; +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = jstz; +} else if ((typeof define !== 'undefined' && define !== null) && (define.amd != null)) { + define([], function() { + return jstz; + }); +} else { + if (typeof root === 'undefined') { + window.jstz = jstz; + } else { + root.jstz = jstz; + } +} +}()); diff --git a/js/jstz.min.js b/js/jstz.min.js new file mode 100644 index 0000000..a05b7e8 --- /dev/null +++ b/js/jstz.min.js @@ -0,0 +1,2 @@ +/* jstz.min.js Version: 1.0.6 Build date: 2019-09-09 */ +!function(e){var a=function(){"use strict";var e="s",s={DAY:864e5,HOUR:36e5,MINUTE:6e4,SECOND:1e3,BASELINE_YEAR:2014,MAX_SCORE:864e6,AMBIGUITIES:{"America/Denver":["America/Mazatlan"],"America/Chicago":["America/Mexico_City"],"America/Asuncion":["America/Campo_Grande","America/Santiago"],"America/Montevideo":["America/Sao_Paulo","America/Santiago"],"Asia/Beirut":["Asia/Amman","Asia/Jerusalem","Europe/Helsinki","Asia/Damascus","Africa/Cairo","Asia/Gaza","Europe/Minsk","Africa/Windhoek"],"Pacific/Auckland":["Pacific/Fiji"],"America/Los_Angeles":["America/Santa_Isabel"],"America/New_York":["America/Havana"],"America/Halifax":["America/Goose_Bay"],"America/Godthab":["America/Miquelon"],"Asia/Dubai":["Asia/Yerevan"],"Asia/Jakarta":["Asia/Krasnoyarsk"],"Asia/Shanghai":["Asia/Irkutsk","Australia/Perth"],"Australia/Sydney":["Australia/Lord_Howe"],"Asia/Tokyo":["Asia/Yakutsk"],"Asia/Dhaka":["Asia/Omsk"],"Asia/Baku":["Asia/Yerevan"],"Australia/Brisbane":["Asia/Vladivostok"],"Pacific/Noumea":["Asia/Vladivostok"],"Pacific/Majuro":["Asia/Kamchatka","Pacific/Fiji"],"Pacific/Tongatapu":["Pacific/Apia"],"Asia/Baghdad":["Europe/Minsk","Europe/Moscow"],"Asia/Karachi":["Asia/Yekaterinburg"],"Africa/Johannesburg":["Asia/Gaza","Africa/Cairo"]}},i=function(e){var a=-e.getTimezoneOffset();return null!==a?a:0},r=function(){for(var e=[],a=0;a<=11;a++)for(var r=1;r<=28;r++){var n=i(new Date(s.BASELINE_YEAR,a,r));e?e&&e[e.length-1]!==n&&e.push(n):e.push()}return e},n=function(){var a=0,s=r();return s.length>1&&(a=s[0]-s[1]),s.length>3?s[0]+",1,weird":a<0?s[0]+",1":a>0?s[1]+",1,"+e:s[0]+",0"},o=function(){var e,a;if(Intl&&"undefined"!=typeof Intl&&"undefined"!=typeof Intl.DateTimeFormat&&(e=Intl.DateTimeFormat(),"undefined"!=typeof e&&"undefined"!=typeof e.resolvedOptions))return a=e.resolvedOptions().timeZone,a&&(a.indexOf("/")>-1||"UTC"===a)?a:void 0},t=function(e){for(var a=new Date(e,0,1,0,0,1,0).getTime(),s=new Date(e,12,31,23,59,59).getTime(),i=a,r=new Date(i).getTimezoneOffset(),n=null,o=null;ir&&(o=t),r=A),i+=864e5}return!(!n||!o)&&{s:u(n).getTime(),e:u(o).getTime()}},u=function f(e,a,i){"undefined"==typeof a&&(a=s.DAY,i=s.HOUR);for(var r=new Date(e.getTime()-a).getTime(),n=e.getTime()+a,o=new Date(r).getTimezoneOffset(),t=r,u=null;t=a.rules[n].s&&e[n].e<=a.rules[n].e)){r="N/A";break}if(r=0,r+=Math.abs(e[n].s-a.rules[n].s),r+=Math.abs(a.rules[n].e-e[n].e),r>s.MAX_SCORE){r="N/A";break}}return r=A(e,i,r,a)},n={},o=a.olson.dst_rules.zones,t=o.length,u=s.AMBIGUITIES[i],c=0;c 11 ) {mer = radio.units.pm;} + if ( h > 12 ) {h = h - 12;} + } else { + mer = ''; + if ( h < 10 ) {h = '0'+h;} + } + + timestring = ''+h+''; + timestring += ':'; + timestring += ''+m+''; + if (seconds) { + timestring += ':'; + timestring += ''+s+''; + } + if (mer != '') {timestring += ' '+mer+'';} + return timestring; +} + +/* Convert Date Time to Date String */ +function radio_date_string(datetime, day, date, month) { + datestring = ''; + if (day != '') { + d = datetime.getDay(); + if (day == 'short') {datestring = radio.labels.sdays[d];} + else {datestring += radio.labels.days[d];} + } + if (date) { + datestring += ' '+datetime.getDate(); + } + if (month != '') { + m = datetime.getMonth(); + if (month == 'short') {datestring += ' '+radio.labels.smonths[m];} + else {datestring += ' '+radio.labels.months[m];} + } + return datestring; +} + +/* Update Current Time Clock */ +function radio_clock_date_time(init) { + + /* user datetime / timezone */ + userdatetime = new Date(); + if (typeof jstz == 'function') {userzone = jstz.determine().name();} + else {userzone = Intl.DateTimeFormat().resolvedOptions().timeZone;} + userzone = userzone.replace('/',', '); + userzone = userzone.replace('_',' '); + + /* user timezone offset */ + useroffset = -(userdatetime.getTimezoneOffset()); + houroffset = parseInt(useroffset); + if (houroffset == 0) {userzone += ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {userzone += ' [UTC+'+houroffset+']';} + else {userzone += ' [UTC'+houroffset+']';} + } + + /* server datetime / offset */ + serverdatetime = new Date(); + serveroffset = ( -(useroffset) * 60) + radio.timezone.offset; + serverdatetime.setTime(userdatetime.getTime() + (serveroffset * 1000) ); + + /* server timezone */ + serverzone = ''; + if (typeof radio.timezone.location != 'undefined') { + serverzone = radio.timezone.location; + serverzone = serverzone.replace('/',', '); + serverzone = serverzone.replace('_',' '); + } + if (typeof radio.timezone.code != 'undefined') { + serverzone += ' ['+radio.timezone.code+']'; + } + + /* loop clock instances */ + clock = document.getElementsByClassName('radio-station-clock'); + for (i = 0; i < clock.length; i++) { + if (clock[i]) { + classes = clock[i].className; + seconds = false; day = ''; date = false; month = ''; zone = false; + if (classes.indexOf('format-24') > -1) {format = 24;} else {format = 12;} + if (classes.indexOf('seconds') > -1) {seconds = true;} + if (classes.indexOf('day') > -1) { + if (classes.indexOf('day-short') > -1) {day = 'short';} else {day = 'full';} + } + if (classes.indexOf('date') > -1) {date = true;} + if (classes.indexOf('month') > -1) { + if (classes.indexOf('month-short') > -1) {month = 'short';} else {month = 'full';} + } + if (classes.indexOf('zone') > -1) {zone = true;} + servertime = radio_time_string(serverdatetime, format, seconds); + serverdate = radio_date_string(serverdatetime, day, date, month); + usertime = radio_time_string(userdatetime, format, seconds); + userdate = radio_date_string(userdatetime, day, date, month); + + /* loop server / user clocks */ + clocks = clock[i].children; + for (j = 0; j < clocks.length; j++) { + if (clocks[j]) { + classes = clocks[j].className; + + /* update server clock */ + if (classes.indexOf('radio-station-server-clock') > -1) { + divs = clocks[j].children; + for (k = 0; k < divs.length; k++) { + if (divs[k].className == 'radio-server-time') {divs[k].innerHTML = servertime;} + if (divs[k].className == 'radio-server-date') {divs[k].innerHTML = serverdate;} + if (init && zone && (divs[k].className == 'radio-server-zone') ) {divs[k].innerHTML = serverzone;} + } + } + + /* update user clock */ + if (classes.indexOf('radio-station-user-clock') > -1) { + divs = clocks[j].children; + for (k = 0; k < divs.length; k++) { + if (divs[k].className == 'radio-user-time') {divs[k].innerHTML = usertime;} + if (divs[k].className == 'radio-user-date') {divs[k].innerHTML = userdate;} + if (init && zone && (divs[k].className == 'radio-user-zone') ) {divs[k].innerHTML = userzone;} + } + } + } + } + } + } + + /* clock loop */ + setTimeout('radio_clock_date_time();', 1000); + return true; +} + +/* Start the Clock */ +setTimeout('radio_clock_date_time(true);', 1000); \ No newline at end of file diff --git a/js/radio-station-countdown.js b/js/radio-station-countdown.js index 6248ec7..97ae84c 100644 --- a/js/radio-station-countdown.js +++ b/js/radio-station-countdown.js @@ -5,14 +5,15 @@ /* Countdown Loop */ function radio_countdown() { - radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); - radio.user_time = radio.current_time + radio.user_offset; - radio.server_time = radio.current_time + radio.timezone_offset; + radio.time.current = Math.floor( (new Date()).getTime() / 1000 ); + radio.time.user = radio.time.current - radio.timezone.useroffset; + radio.time.server = radio.time.current + radio.timezone.offset; + if (radio.debug) { - console.log('Current Time: ' + (new Date()).toISOString() + '(' + radio.current_time + ')'); - console.log('User Offset: ' + radio.user_offset + ' - Server Offset: ' + radio.timezone_offset); - userdatetime = new Date(radio.user_time * 1000); - serverdatetime = new Date(radio.server_time * 1000); + console.log('Current Time: ' + (new Date()).toISOString() + '(' + radio.time.current + ')'); + console.log('User Offset: ' + radio.timezone.useroffset + ' - Server Offset: ' + radio.timezone.offset); + userdatetime = new Date(radio.time.user * 1000); + serverdatetime = new Date(radio.time.server * 1000); console.log('User Time: ' + userdatetime.toISOString() + '(' + userdatetime.getDate() + ')'); console.log('Server Time: '+ serverdatetime.toISOString() + '(' + serverdatetime.getDate() + ')'); } @@ -20,22 +21,22 @@ function radio_countdown() { /* Current Show Countdown */ jQuery('.current-show-end').each(function() { showendtime = parseInt(jQuery(this).val()); - diff = showendtime - radio.server_time; + diff = showendtime - radio.time.current; if (radio.debug) { showend = new Date(showendtime * 1000); - console.log('Show End: ' + showend.toISOString() + '(' + showend.getTime() + ')'); + console.log('Show End: ' + showendtime + ' : ' + showend.toISOString() + '(' + showend.getTime() + ')'); console.log('Current Show Ends in: '+diff+'s'); } - if (diff < 1) {countdown = radio.label_showended; jQuery(this).removeClass('current-show-end');} - else {countdown = radio_countdown_display(diff, radio.label_timeremaining);} + if (diff < 1) {countdown = radio.labels.showended; jQuery(this).removeClass('current-show-end');} + else {countdown = radio_countdown_display(diff, radio.labels.timeremaining);} jQuery(this).parent().find('.rs-countdown').html(countdown); }); /* Upcoming Show Countdown */ jQuery('.upcoming-show-times').each(function() { times = jQuery(this).val().split('-'); - times[0] = parseInt(times[0]); diffa = times[0] - radio.server_time; - times[1] = parseInt(times[1]); diffb = times[1] - radio.server_time; + times[0] = parseInt(times[0]); diffa = times[0] - radio.time.current; + times[1] = parseInt(times[1]); diffb = times[1] - radio.time.current; if (radio.debug) { nextstart = new Date( times[0] * 1000 ); nextend = new Date( times[1] * 1000 ); console.log('Next Show Start: ' + nextstart.toISOString() + '(' + nextstart.getTime() + ')'); @@ -43,18 +44,18 @@ function radio_countdown() { console.log('Next Show Start in: '+diffa+'s - Next Show End in:'+diffb+'s'); } if (diffa < 1) { - if (diffb < 1) {countdown = radio.label_showended; jQuery(this).removeClass('upcoming-show-times');} - else {countdown = radio.label_showstarted;} - } else {countdown = radio_countdown_display(diffa, radio.label_timecommencing);} + if (diffb < 1) {countdown = radio.labels.showended; jQuery(this).removeClass('upcoming-show-times');} + else {countdown = radio.labels.showstarted;} + } else {countdown = radio_countdown_display(diffa, radio.labels.timecommencing);} jQuery(this).parent().find('.rs-countdown').html(countdown); }); /* Current Playlist Countdown */ jQuery('.current-playlist-end').each(function() { - diff = parseInt(jQuery(this).val()) - radio.server_time; + diff = parseInt(jQuery(this).val()) - radio.time.current; if (radio.debug) {console.log('Current Playlist Ends in: '+diff);} if (diff < 1) {countdown = playlistended; jQuery(this).removeClass('current-playlist-end');} - else {countdown = radio_countdown_display(diff, radio.label_timeremaining);} + else {countdown = radio_countdown_display(diff, radio.labels.timeremaining);} jQuery(this).parent().find('.rs-countdown').html(countdown); }); @@ -66,11 +67,13 @@ function radio_countdown() { /* Get Countdown Display */ function radio_countdown_display(diff, label) { - countdown = new Date(diff * 1000).toISOString(); - hours = countdown.substr(11, 2); - if (hours.substr(0,1) == '0') {hours = hours.substr(1,1);} - minutes = countdown.substr(14, 2); - seconds = countdown.substr(17, 2); + hours = Math.floor( diff / 3600 ); + if (hours > 0) {diff = diff - (hours * 3600);} + minutes = Math.floor( diff / 60 ); + if (minutes > 0) {diff = diff - (minutes * 60);} + if (minutes < 10) {minutes = '0'+minutes;} + seconds = diff; + if (seconds < 10) {seconds = '0'+seconds;} display = ''+label+': '+hours+':'; display += ''+minutes+':'+seconds+''; return display; diff --git a/js/radio-station.js b/js/radio-station.js index 03cbd30..72a59ce 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -14,6 +14,47 @@ function radio_scroll_to(id) { } else {elem.lastjump = null;} } +/* Get Day of Week */ +function radio_get_weekday(dayweek) { + if (dayweek == '0') {day = 'sunday';} + if (dayweek == '1') {day = 'monday';} + if (dayweek == '2') {day = 'tuesday';} + if (dayweek == '3') {day = 'wednesday';} + if (dayweek == '4') {day = 'thursday';} + if (dayweek == '5') {day = 'friday';} + if (dayweek == '6') {day = 'saturday';} + return day; +} + +/* Cookie Value Function */ +/* since @2.3.2 */ +radio_cookie = { + set: function (name, value, days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + var expires = '; expires=' + date.toUTCString(); + } else {var expires = '';} + document.cookie = 'radio_' + name + '=' + JSON.stringify(value) + expires + '; path=/'; + }, + get : function(name) { + var nameeq = 'radio_' + name + '=', ca = document.cookie.split(';'); + for(var i=0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1,c.length); + if (c.indexOf(nameeq) == 0) { + return JSON.parse(c.substring(nameeq.length, c.length)); + } + } + } + return null; + }, + delete : function(name) { + setCookie('radio_' + name, "", -1); + } +} + /* Debounce Delay Callback */ var radio_resize_debounce = (function () { var debounce_timers = {}; @@ -24,3 +65,27 @@ var radio_resize_debounce = (function () { }; })(); +/* User Timezone Display */ +if (typeof jQuery == 'function') { + jQuery(document).ready(function() { + if (jQuery('.radio-user-timezone').length) { + userdatetime = new Date(); + useroffset = -(userdatetime.getTimezoneOffset()); + if ((useroffset * 60) == radio.timezone.offset) {return;} + if (typeof jstz == 'function') {tz = jstz.determine().name();} + else {tz = Intl.DateTimeFormat().resolvedOptions().timeZone;} + if (tz.indexOf('/') > -1) { + tz = tz.replace('/', ', '); tz = tz.replace('_',' '); + houroffset = parseInt(useroffset); + if (houroffset == 0) {userzone = ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {tz += ' [UTC+'+houroffset+']';} + else {tz += ' [UTC'+houroffset+']';} + } + jQuery('.radio-user-timezone').html(tz); + jQuery('.radio-user-timezone-title').show(); + } + } + }); +} diff --git a/languages/radio-station-nl_NL.po b/languages/radio-station-nl_NL.po index 15879ee..f782f8d 100644 --- a/languages/radio-station-nl_NL.po +++ b/languages/radio-station-nl_NL.po @@ -23,19 +23,19 @@ msgstr "" #: includes/dj-on-air.php:90 includes/dj-on-air.php:664 #: includes/playlist.php:22 includes/playlist.php:359 includes/playlist.php:546 msgid "View Playlist" -msgstr "Bekijk de playlist" +msgstr "Bekijk afspeellijst" #: includes/dj-on-air.php:480 msgid "None Upcoming" -msgstr "Geen Volgende" +msgstr "Geen volgende" #: includes/dj-on-air.php:495 msgid "The current on-air DJ." -msgstr "De huidige on-air DJ." +msgstr "DJ die nu on air is." #: includes/dj-on-air.php:496 msgid "Radio Station: Show/DJ On-Air" -msgstr "" +msgstr "Radiostation: programma/DJ on air" #: includes/dj-on-air.php:511 includes/dj-on-air.php:738 #: includes/playlist.php:436 @@ -44,61 +44,59 @@ msgstr "Titel" #: includes/dj-on-air.php:519 includes/dj-on-air.php:746 msgid "Show Avatars" -msgstr "Laat avatar zien" +msgstr "Toon avatars" #: includes/dj-on-air.php:526 msgid "Link to the Show/DJ's profile" -msgstr "Link naar de Show/DJ's profiel" +msgstr "Link naar het profiel van programma/DJ" #: includes/dj-on-air.php:533 includes/dj-on-air.php:767 msgid "Display schedule info for this show" -msgstr "Laat de geschedulde informatie van de show zien" +msgstr "Toon de planning van dit programma" #: includes/dj-on-air.php:540 msgid "Display link to show's playlist" -msgstr "Laat de link van de playlist van de show zien" +msgstr "Toon de link van de afspeellijst van dit programma" #: includes/dj-on-air.php:545 msgid "Default DJ Name" -msgstr "Standaard DJ naam" +msgstr "Standaard-DJ-naam" #: includes/dj-on-air.php:548 includes/dj-on-air.php:761 -msgid "" -"If no Show/DJ is scheduled for the current hour, display this name/text." -msgstr "" -"ALs er geen show is gescheduled voor dit uur, laat deze naam/tekst zien." +msgid "If no Show/DJ is scheduled for the current hour, display this name/text." +msgstr "ALs er geen programma/DJ is gepland, toon dan deze naam/tekst." #: includes/dj-on-air.php:552 includes/dj-on-air.php:779 msgid "Time Format" -msgstr "Tijdsformaat" +msgstr "Tijdformaat" #: includes/dj-on-air.php:554 includes/dj-on-air.php:781 msgid "12-hour" -msgstr "12-uur" +msgstr "12-uurs" #: includes/dj-on-air.php:555 includes/dj-on-air.php:782 msgid "24-hour" -msgstr "24-uur" +msgstr "24-uurs" #: includes/dj-on-air.php:558 msgid "Choose time format for displayed schedules" -msgstr "Kies het tijdsformaat voor de getoonde schedules" +msgstr "Kies het tijdformaat voor de weergegeven planningen" #: includes/dj-on-air.php:722 msgid "The upcoming DJs/Shows." -msgstr "De volgende DJ's/Shows" +msgstr "De volgende DJ's/programma's" #: includes/dj-on-air.php:723 msgid "Radio Station: Upcoming DJ On-Air" -msgstr "Radio Station: De volgende DJ On-Air" +msgstr "Radiostation: de volgende onair-DJ" #: includes/dj-on-air.php:753 msgid "Link to Show/DJ's user profile" -msgstr "Link naar Show/DJ's gebruikers profiel" +msgstr "Link naar gebruikersprofiel van programma/DJ" #: includes/dj-on-air.php:758 msgid "No Additional Schedules" -msgstr "Geen Extra Schedules" +msgstr "Geen extra planningen" #: includes/dj-on-air.php:772 msgid "Limit" @@ -106,39 +104,39 @@ msgstr "Limiet" #: includes/dj-on-air.php:775 msgid "Number of upcoming DJs/Shows to display." -msgstr "Aantal volgende DJ's/Shows om te tonen" +msgstr "Aantal volgende DJ's/Shows om weer te geven" #: includes/dj-on-air.php:785 msgid "Choose time format for displayed schedules." -msgstr "Kies het tijdsformaat voor de getoonde schedules" +msgstr "Kies het tijdformaat voor de getoonde planningen" #: includes/master_schedule.php:21 includes/master_schedule.php:22 msgid "Schedule Override" -msgstr "Schedule Override" +msgstr "Planning-override" #: includes/master_schedule.php:23 includes/master_schedule.php:24 msgid "Add Schedule Override" -msgstr "Maak Schedule Override" +msgstr "Voeg planning-override toe" #: includes/master_schedule.php:25 msgid "Edit Schedule Override" -msgstr "Edit Schedule Override" +msgstr "Bewerk planning-override" #: includes/master_schedule.php:26 msgid "New Schedule Override" -msgstr "Nieuwe Schedule Override" +msgstr "Nieuwe planning-override" #: includes/master_schedule.php:27 msgid "View Schedule Override" -msgstr "Bekijk Schedule Override" +msgstr "Bekijk planning-override" #: includes/master_schedule.php:30 msgid "Post type for Schedule Override" -msgstr "Post het type voorde Schedule Override" +msgstr "Post het type voor de planning-override" #: includes/master_schedule.php:49 msgid "Override Schedule" -msgstr "Override Schedule" +msgstr "Overrideplanning" #: includes/master_schedule.php:79 msgid "Date" @@ -147,48 +145,48 @@ msgstr "Datum" #: includes/master_schedule.php:82 includes/playlist.php:756 #: includes/playlist.php:835 msgid "Start Time" -msgstr "Start tijd" +msgstr "Starttijd" #: includes/master_schedule.php:108 includes/playlist.php:782 #: includes/playlist.php:861 msgid "End Time" -msgstr "Eind tijd" +msgstr "Eindtijd" #: includes/master_schedule.php:432 includes/master_schedule.php:595 msgid "encore airing" -msgstr "Nogmaals On Air" +msgstr "herhaling" #: includes/master_schedule.php:437 includes/master_schedule.php:600 msgid "Audio File" -msgstr "Audio File" +msgstr "Audiobestand" #: includes/master_schedule.php:454 msgid "Sun" -msgstr "Zon" +msgstr "Zo" #: includes/master_schedule.php:454 msgid "Mon" -msgstr "Maan" +msgstr "Ma" #: includes/master_schedule.php:454 msgid "Tue" -msgstr "Dins" +msgstr "Di" #: includes/master_schedule.php:454 msgid "Wed" -msgstr "Woe" +msgstr "Wo" #: includes/master_schedule.php:454 msgid "Thu" -msgstr "Don" +msgstr "Do" #: includes/master_schedule.php:454 msgid "Fri" -msgstr "Vrij" +msgstr "Vr" #: includes/master_schedule.php:454 msgid "Sat" -msgstr "Zat" +msgstr "Za" #: includes/master_schedule.php:619 includes/playlist.php:572 msgid "Genres" @@ -196,59 +194,59 @@ msgstr "Genres" #: includes/playlist.php:16 templates/single-show.php:136 msgid "Playlists" -msgstr "Playlists" +msgstr "Afspeellijsten" #: includes/playlist.php:17 msgid "Playlist" -msgstr "Playlist" +msgstr "Afspeellijst" #: includes/playlist.php:18 includes/playlist.php:19 msgid "Add Playlist" -msgstr "Maak Playlist" +msgstr "Voeg afspeellijst toe" #: includes/playlist.php:20 msgid "Edit Playlist" -msgstr "Edit Playlist" +msgstr "Bewerk afspeellijst" #: includes/playlist.php:21 msgid "New Playlist" -msgstr "Nieuwe Playlist" +msgstr "Nieuwe afspeellijst" #: includes/playlist.php:25 msgid "Post type for Playlist descriptions" -msgstr "Post type voor Playlist omschrijving" +msgstr "Plaats type voor omschrijvingen van de afspeellijsten" #: includes/playlist.php:42 msgid "Shows" -msgstr "Shows" +msgstr "Programma's" #: includes/playlist.php:43 includes/playlist.php:191 msgid "Show" -msgstr "Show" +msgstr "Programma" #: includes/playlist.php:44 includes/playlist.php:45 msgid "Add Show" -msgstr "Maak Show Aan" +msgstr "Voeg programma toe" #: includes/playlist.php:46 msgid "Edit Show" -msgstr "Edit Show" +msgstr "Bewerk programma" #: includes/playlist.php:47 msgid "New Show" -msgstr "Nieuwe Show" +msgstr "Nieuw programma" #: includes/playlist.php:48 msgid "View Show" -msgstr "Bekijk Show" +msgstr "Bekijk programma" #: includes/playlist.php:51 msgid "Post type for Show descriptions" -msgstr "Post type voor Show omschrijving" +msgstr "Plaats type voor de programma-omschrijvingen" #: includes/playlist.php:71 msgid "Playlist Entries" -msgstr "Playlist Entries" +msgstr "Afspeellijst-invoer" #: includes/playlist.php:93 templates/single-playlist.php:43 msgid "Artist" @@ -268,7 +266,7 @@ msgstr "Platenlabel" #: includes/playlist.php:93 templates/single-playlist.php:47 msgid "DJ Comments" -msgstr "DJ commentaar" +msgstr "DJ-commentaar" #: includes/playlist.php:93 msgid "New" @@ -285,64 +283,64 @@ msgstr "Verwijder" #: includes/playlist.php:119 includes/playlist.php:144 msgid "Queued" -msgstr "Queued" +msgstr "In de wachtrij" #: includes/playlist.php:123 includes/playlist.php:144 msgid "Played" -msgstr "Gespeeld" +msgstr "Afgespeeld" #: includes/playlist.php:136 msgid "Add Entry" -msgstr "Maak Entry Aan" +msgstr "Voeg invoer toe" #: includes/playlist.php:163 includes/playlist.php:164 #: templates/single-show.php:99 msgid "Schedule" -msgstr "Uitzenddag" +msgstr "Planning" #: includes/playlist.php:166 includes/playlist.php:167 msgid "Publish" -msgstr "Publish" +msgstr "Publiceren" #: includes/playlist.php:170 msgid "Submit for Review" -msgstr "Submit voor Review" +msgstr "Opsturen voor controle" #: includes/playlist.php:171 includes/playlist.php:176 msgid "Update Playlist" -msgstr "Update Playlist" +msgstr "Afspeellijst bijwerken" #: includes/playlist.php:175 msgid "Update" -msgstr "Update" +msgstr "Bijwerken" #: includes/playlist.php:420 msgid "Display the current song." -msgstr "Laat de huidige song zien" +msgstr "Toon het huidige nummer." #: includes/playlist.php:421 msgid "Radio Station: Now Playing" -msgstr "Radio Station: Now Playing" +msgstr "Radiostation: nu speelt" #: includes/playlist.php:444 msgid "Show Artist Name" -msgstr "Laat Artiest Naam Zien" +msgstr "Toon naam van artiest" #: includes/playlist.php:451 msgid "Show Song Title" -msgstr "Laat Titel Zien" +msgstr "Toon titel van nummer" #: includes/playlist.php:458 msgid "Show Album Name" -msgstr "Laat Album Zien" +msgstr "Toon titel van album" #: includes/playlist.php:465 msgid "Show Record Label Name" -msgstr "Laat Record Label Zien" +msgstr "Toon platenlabel" #: includes/playlist.php:472 msgid "Show DJ Comments" -msgstr "Show DJ Commentaar" +msgstr "Toon DJ-commentaar" #: includes/playlist.php:573 templates/single-show.php:60 msgid "Genre" @@ -357,32 +355,28 @@ msgid "Active" msgstr "Actief" #: includes/playlist.php:614 -msgid "" -"Check this box if show is currently active (Show will not appear on " -"programming schedule if unchecked)" -msgstr "" -"Vink deze box aan als de huidige show actief is (De show zal niet " -"verschijnenin de programmering indien niet aangevinkt)" +msgid "Check this box if show is currently active (Show will not appear on programming schedule if unchecked)" +msgstr "Vink deze box aan als het huidige programma actief is (Het programma zal niet verschijnen in de programmaplanning indien niet aangevinkt)" #: includes/playlist.php:616 msgid "Current Audio File" -msgstr "Huidige Audio File" +msgstr "Huidig audiobestand" #: includes/playlist.php:619 msgid "DJ Email" -msgstr "DJ Email" +msgstr "E-mail DJ" #: includes/playlist.php:622 msgid "Website Link" -msgstr "Website Link" +msgstr "Website-link" #: includes/playlist.php:633 msgid "DJs" -msgstr "DJs" +msgstr "DJ's" #: includes/playlist.php:719 msgid "Schedules" -msgstr "Schedules" +msgstr "Planningen" #: includes/playlist.php:744 includes/playlist.php:824 msgid "Day" @@ -418,57 +412,53 @@ msgstr "Zondag" #: includes/playlist.php:806 includes/playlist.php:886 msgid "Encore Presentation" -msgstr "Nogmaals Gepresenteerd" +msgstr "Herhaling" #: includes/playlist.php:817 msgid "Add Shift" -msgstr "Voeg Shift Toe" +msgstr "Voeg tijdblok toe" #: includes/playlist.php:980 msgid "More Playlists" -msgstr "Meer Playlists" +msgstr "Meer afspeellijsten" #: includes/playlist.php:1041 msgid "More Blog Posts" -msgstr "Meer Blog Posts" +msgstr "Meer blogmeldingen" #: templates/archive-playlist.php:15 msgid "Playlist Archive for" -msgstr "Playlist Archief voor" +msgstr "Archief van afspeellijst voor" #: templates/archive-playlist.php:50 templates/single-playlist.php:13 #: templates/single-show.php:13 msgid "Post navigation" -msgstr "Post Navigatie" +msgstr "Blognavigatie" #: templates/archive-playlist.php:51 msgid "Older posts" -msgstr "Oudere posts" +msgstr "Oudere meldingen" #: templates/archive-playlist.php:52 msgid "Newer posts" -msgstr "Nieuwe posts" +msgstr "Nieuwe meldingen" #: templates/archive-playlist.php:61 templates/author.php:93 #: templates/playlist-archive-template.php:63 #: templates/show-blog-archive-template.php:71 msgid "Nothing Found" -msgstr "Niets Gevonden" +msgstr "Niets gevonden" #: templates/archive-playlist.php:65 templates/author.php:97 #: templates/playlist-archive-template.php:67 #: templates/show-blog-archive-template.php:75 -msgid "" -"Apologies, but no results were found for the requested archive. Perhaps " -"searching will help find a related post." -msgstr "" -"Excuses, maar er is niets gevonden in het archief. Misschien helpt de search " -"functie om een post te vinden." +msgid "Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post." +msgstr "Excuses, maar er is niets gevonden in het archief. Misschien helpt de zoekfunctie om een melding te vinden." #: templates/author.php:51 #, php-format msgid "Author Archives: %s" -msgstr "Auteur Archieven: %s" +msgstr "Auteurarchieven: %s" #: templates/author.php:70 #, php-format @@ -477,15 +467,15 @@ msgstr "Over: %s" #: templates/playlist-archive-template.php:16 msgid "Playlist Archive" -msgstr "Playlist Archief" +msgstr "Archief afspeellijsten" #: templates/show-blog-archive-template.php:16 msgid "Blog Archive" -msgstr "Blog Archief" +msgstr "Blogarchief" #: templates/show-blog-archive-template.php:46 msgid "Posted by" -msgstr "Gepost door" +msgstr "Geplaatst door" #: templates/single-playlist.php:14 templates/single-show.php:14 msgid "Previous" @@ -497,11 +487,11 @@ msgstr "Volgende" #: templates/single-playlist.php:32 templates/single-show.php:144 msgid "Pages:" -msgstr "Paginas:" +msgstr "Pagina's:" #: templates/single-playlist.php:65 msgid "No entries for this playlist" -msgstr "Niets gevonden voor deze playlist" +msgstr "Niets gevonden voor deze afspeellijst" #: templates/single-show.php:32 msgid "Hosted by" @@ -509,23 +499,23 @@ msgstr "Gepresenteerd door" #: templates/single-show.php:84 msgid "Email the DJ" -msgstr "Email de DJ" +msgstr "E-mail de DJ" #: templates/single-show.php:88 msgid "Show Website" -msgstr "Laat De Website Zien" +msgstr "Toon website" #: templates/single-show.php:95 msgid "Most recent broadcast" -msgstr "Laatste Uitzending" +msgstr "Laatste uitzending" #: templates/single-show.php:140 msgid "Blog Posts" -msgstr "Blog Posts" +msgstr "Blogmeldingen" #: radio-station.php:244 radio-station.php:330 msgid "Export Playlists" -msgstr "Exporteer Playlist" +msgstr "Exporteer afspeellijsten" #: radio-station.php:244 radio-station.php:451 msgid "Export" @@ -533,16 +523,16 @@ msgstr "Exporteer" #: radio-station.php:323 msgid "Right-click and download this file to save your export" -msgstr "Klik met rechts en download deze file om deze export op te slaan" +msgstr "Klik met rechts en download deze file om de export op te slaan" #: radio-station.php:340 msgid "Start Date" -msgstr "Start Datum" +msgstr "Startdatum" #: radio-station.php:394 msgid "End Date" -msgstr "Eind Datum" +msgstr "Einddatum" #: radio-station.php:466 msgid "Related to Show" -msgstr "Gerelateerd aan de Show" +msgstr "Verwant aan het programma" \ No newline at end of file diff --git a/loader.php b/loader.php index 3ea5008..0266ea6 100644 --- a/loader.php +++ b/loader.php @@ -5,9 +5,9 @@ // =========================== // // -------------- -// Version: 1.1.0 +// Version: 1.1.2 // -------------- -// * changelog at end of file! * +// Note: Changelog and structure at end of file. // // ============ // Loader Usage @@ -110,11 +110,28 @@ class radio_station_loader { public $sections = array(); public $scripts = array(); + // 1.1.2: added menu added switch + // 1.1.2: added debug switch + public $menu_added = false; + public $debug = false; + // ----------------- // Initialize Loader // ----------------- public function __construct( $args ) { + // --- set debug switch --- + // 1.1.2: added debug switch check + $prefix = ''; + if ( $args['settings'] ) { + $prefix = '-' . $args['settings']; + } + if ( isset( $_REQUEST[$prefix . 'debug'] ) ) { + if ( ( '1' == $_REQUEST[$prefix . 'debug'] ) || ( 'yes' == $_REQUEST[$prefix . 'debug'] ) ) { + $this->debug = true; + } + } + // --- set plugin options --- // 1.0.6: added options filter $args['options'] = apply_filters( $args['namespace'] . '_options', $args['options'] ); @@ -229,6 +246,15 @@ public function plugin_data( $key ) { return $value; } + + // ------------------ + // Get Plugin Version + // ------------------ + // 1.1.2: added get plugin version function + public function plugin_version() { + $args = $this->args; + return $args['version']; + } // ----------------- // Set Pro Namespace @@ -258,7 +284,10 @@ public function default_settings( $dkey = false ) { } // --- filter and store the plugin default settings --- + // 1.1.2: fix to apply options filter + $namespace = $this->namespace; $options = $this->options; + $options = apply_filters( $namespace . '_options', $options ); $defaults = array(); foreach ( $options as $key => $values ) { // 1.0.9: set default to null if default value not set @@ -268,7 +297,6 @@ public function default_settings( $dkey = false ) { $defaults[$key] = null; } } - $namespace = $this->namespace; $defaults = apply_filters( $namespace . '_default_settings', $defaults ); $this->defaults = $defaults; if ( $dkey && isset( $defaults[$dkey] ) ) { @@ -458,7 +486,7 @@ public function update_settings() { // --- get plugin options and default settings --- // 1.0.9: allow filtering of plugin options (eg. for Pro/Add Ons) $options = $this->options; - $options = apply_filters( $namespace . '_plugin_options', $options ); + $options = apply_filters( $namespace . '_options', $options ); $defaults = $this->default_settings(); // --- maybe use custom method or function --- @@ -539,7 +567,7 @@ public function update_settings() { } } - if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + if ( $this->debug ) { echo 'Saving Setting Key ' . $key . ' (' . $postkey . ': ' . print_r( $posted, true ) . '
    '; echo 'Type: ' . $type . ' - Valid Options ' . $key . ': ' . print_r( $valid, true ) . '
    '; } @@ -609,9 +637,13 @@ public function update_settings() { $posted = array(); foreach ( $valid as $option => $label ) { $optionkey = $args['settings'] . '_' . $key . '-' . $option; - if ( isset( $_POST[$optionkey] ) && ( 'yes' == $_POST[$optionkey] ) ) { - // 1.1.0: fixed to save only array of key values - $posted[] = $option; + if ( isset( $_POST[$optionkey] ) ) { + // 1.1.2: check for value if specified + if ( ( isset( $values['value'] ) && ( $values['value'] == $_POST[$optionkey] ) ) + || ( !isset( $values['value'] ) && ( 'yes' == $_POST[$optionkey] ) ) ) { + // 1.1.0: fixed to save only array of key values + $posted[] = $option; + } } } $settings[$key] = $posted; @@ -659,7 +691,7 @@ public function update_settings() { } - if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + if ( $this->debug ) { echo 'New Settings for Key ' . $key . ': '; if ( $newsettings ) { echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; @@ -694,7 +726,7 @@ public function update_settings() { } } - if ( isset( $_REQUEST['debug'] ) && ( 'yes' == $_REQUEST['debug'] ) ) { + if ( $this->debug ) { echo 'Valid Options for Key ' . $key . ': ' . print_r( $valid, true ) . '
    '; echo 'Validated Settings for Key ' . $key . ': ' . print_r( $settings[$key], true ) . '
    '; } @@ -901,22 +933,25 @@ public function validate_setting( $posted, $valid, $args ) { // Delete Settings // --------------- public function delete_settings() { - // TODO: check for plugin settings flag to delete settings data? + + // TODO: check for plugin settings flag to delete settings ? // $delete_settings = $this->get_setting( 'delete_settings' ); // if ( $delete_settings ) { // $args = $this->args; // delete_option( $args['option'] ); // } + + // TODO: check for plugin settings flag to delete data? // $delete_data = $this->get_setting( 'delete_data' ); // if ( $delete_data ) { - // do_action( $this->namespace.'_delete_data' ); + // do_action( $this->namespace . '_delete_data' ); // } } - // =============== - // --- Loading --- - // =============== + // ====================== + // --- Plugin Loading --- + // ====================== // -------------------- // Load Plugin Settings @@ -1042,9 +1077,9 @@ public function maybe_load_thickbox() { } } - // ------------- - // Readme Viewer - // ------------- + // ------------------ + // Readme Viewer AJAX + // ------------------ public function readme_viewer() { $args = $this->args; @@ -1309,9 +1344,9 @@ public function freemius_update_message( $message, $user_first_name, $plugin_tit } - // ============= - // --- Admin --- - // ============= + // ==================== + // --- Plugin Admin --- + // ==================== // ----------------- // Add Settings Menu @@ -1336,11 +1371,14 @@ public function settings_menu() { // return true from filter function to not add a submenu item in admin Settings menu // 1.0.8: change from function exists check // 1.0.9: change to filter usage to check if menu is manually added - $menuadded = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args ); + $menu_added = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args ); + + // 1.1.1: record to admin menu added switch + $this->menu_added = $menu_added; // --- maybe auto-add standalone options page --- - if ( !$menuadded ) { - // 1.0.8: check settingsmenu switch that disables automatic settings menu + if ( !$menu_added ) { + // 1.0.8: check settingsmenu switch that disables automatic settings menu adding if ( !isset( $args['settingsmenu'] ) || ( false !== $args['settingsmenu'] ) ) { add_options_page( $args['pagetitle'], $args['menutitle'], $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' ); } @@ -1349,7 +1387,7 @@ public function settings_menu() { // 1.1.0: add admin notices boxer add_action( 'all_admin_notices', array( $this, 'notice_boxer' ), 999 ); } - + // ----------------- // Plugin Page Links // ----------------- @@ -1359,12 +1397,16 @@ public function plugin_links( $links, $file ) { if ( plugin_basename( $args['file'] ) == $file ) { // --- add settings link --- - $settings_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); + // 1.1.1: fix to settings page link URL + // (depending on whether top level menu or Settings submenu item) + $page = 'options-general.php'; + if ( $this->menu_added ) {$page = 'admin.php';} + $settings_url = add_query_arg( 'page', $args['slug'], admin_url( $page ) ); $settings_link = "" . esc_html( __( 'Settings' ) ) . ""; array_unshift( $links, $settings_link ); // --- maybe add Pro upgrade link --- - // TODO: check for correct upgrade/addon URLs + // TODO: check Freemius upgrade/addon URLs // if ( isset( $args['hasplans'] && $args['hasplans'] ) { // // TODO: check if premium is already installed // $upgrade_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); @@ -1373,12 +1415,11 @@ public function plugin_links( $links, $file ) { // } // --- maybe add Addons link --- - //if ( isset($args['hasaddons'] && $args['hasaddons' ) { + // if ( isset($args['hasaddons'] && $args['hasaddons' ) { // $addons_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); // $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; // array_unshift( $links, $addons_link ); // } - } return $links; @@ -1463,22 +1504,19 @@ public function settings_header() { $settings = $GLOBALS[$namespace]; // --- output debug values --- - if ( isset( $_REQUEST['debug'] ) ) { - if ( ( 'yes' == $_REQUEST['debug'] ) || ( '1' == $_REQUEST['debug'] ) ) { - - echo "
    Current Settings:
    "; - print_r( $settings ); - echo "

    "; - - echo "
    Plugin Options:
    "; - print_r( $this->options ); - echo "

    "; - - if ( isset( $_POST ) ) { - echo "
    Posted Values:
    "; - foreach ( $_POST as $key => $value ) { - echo esc_attr( $key ) . ': ' . print_r( $value, true ) . '
    '; - } + if ( $this->debug ) { + echo "
    Current Settings:
    "; + print_r( $settings ); + echo "

    "; + + echo "
    Plugin Options:
    "; + print_r( $this->options ); + echo "

    "; + + if ( isset( $_POST ) ) { + echo "
    Posted Values:
    "; + foreach ( $_POST as $key => $value ) { + echo esc_attr( $key ) . ': ' . print_r( $value, true ) . '
    '; } } } @@ -1723,6 +1761,12 @@ public function settings_table() { $args = $this->args; $namespace = $this->namespace; $options = $this->options; + + // --- get plugin options and default settings --- + // 1.1.2: fix for filtering of plugin options + $options = $this->options; + $options = apply_filters( $namespace . '_options', $options ); + $defaults = $this->default_settings(); $settings = $this->get_settings( false ); @@ -1812,10 +1856,8 @@ public function settings_table() { wp_nonce_field( $args['slug'] . '_update_settings' ); // --- maybe set hidden debug input --- - if ( isset( $_REQUEST['debug'] ) ) { - if ( ( 'yes' == $_REQUEST['debug'] ) || ( '1' == $_REQUEST['debug'] ) ) { - echo ""; - } + if ( $this->debug ) { + echo ""; } // ---- open wrapbox --- @@ -1949,9 +1991,9 @@ public function settings_table() { } - // ------------ + // ----------- // Setting Row - // ------------ + // ----------- // 1.0.9: added for automatic Settings table generation public function setting_row( $option ) { @@ -2356,9 +2398,9 @@ public function setting_row( $option ) { return $row; } - // ---------------- - // Settings Scripts - // ---------------- + // --------------- + // Setting Scripts + // --------------- // 1.0.9: added settings page scripts public function setting_scripts() { @@ -2373,9 +2415,9 @@ public function setting_scripts() { } } - // --------------- - // Settings Styles - // --------------- + // -------------- + // Setting Styles + // -------------- public function setting_styles() { $styles = array(); @@ -2481,9 +2523,9 @@ function radio_station_loader_instance() { } } - // ------------------- - // Get Loader Instance - // ------------------- + // --------------------- + // Get Freemius Instance + // --------------------- // 2.3.0: added function for getting Freemius class instance if ( !function_exists( 'radio_station_freemius_instance' ) ) { function radio_station_freemius_instance() { @@ -2496,7 +2538,7 @@ function radio_station_freemius_instance() { // --------------- // Get Plugin Data // --------------- - // 2.3.0: added function for getting plugin data + // 1.1.1: added function for getting plugin data if ( !function_exists( 'radio_station_plugin_data' ) ) { function radio_station_plugin_data() { $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); @@ -2506,6 +2548,34 @@ function radio_station_plugin_data() { } } + // ------------------ + // Get Plugin Version + // ------------------ + // 1.1.2: added function for getting plugin version + if ( !function_exists( 'radio_station_plugin_version' ) ) { + function radio_station_plugin_version() { + $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $instance = $GLOBALS[$namespace . '_instance']; + + return $instance->plugin_version(); + } + } + + // ----------------- + // Set Pro Namespace + // ----------------- + if ( !function_exists( 'radio_station_pro_namespace' ) ) { + function radio_station_pro_namespace( $pronamespace ) { + $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $instance = $GLOBALS[$namespace . '_instance']; + $instance->pro_namespace( $pronamespace ); + } + } + + // =============== + // Plugin Settings + // =============== + // ------------ // Add Settings // ------------ @@ -2598,17 +2668,8 @@ function radio_station_delete_settings() { $instance->delete_settings(); } } - - // ----------------- - // Set Pro Namespace - // ----------------- - if ( !function_exists( 'radio_station_pro_namespace' ) ) { - function radio_station_pro_namespace( $pronamespace ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); - $instance = $GLOBALS[$namespace . '_instance']; - $instance->pro_namespace( $pronamespace ); - } - } + + // ----------- // Message Box @@ -2675,10 +2736,60 @@ function radio_station_settings_row( $option, $setting ) { // ------------ +// ========= +// STRUCTURE +// ========= +// +// === Loader Class === +// - Initialize Loader +// - Setup Plugin +// - Get Plugin Data +// - Get Plugin Version +// - Set Pro Namespace +// === Plugin Settings === +// - Get Default Settings +// - Add Settings +// - Maybe Transfer Settings +// - Get All Plugin Settings +// - Get Plugin Setting +// - Reset Plugin Settings +// - Update Plugin Settings +// - Validate Plugin Setting +// === Plugin Loading === +// - Load Plugin Settings +// - Add Actions +// - Load Helper Libraries +// - Maybe Load Thickbox +// - Readme Viewer AJAX +// === Freemius Loading === +// - Load Freemius +// - Filter Freemius Connect +// - Freemius Connect Message +// - Connect Update Message +// === Plugin Admin === +// - Add Settings Menu +// - Plugin Page Links +// - Message Box +// - Notice Boxer +// - Plugin Page Header +// - Settings Page +// - Settings Table +// - Setting Row +// - Settings Scripts +// - Settings Styles +// === Namespaced Functions === + + // ========= // CHANGELOG // ========= +// == 1.1.2 == +// - fix for filtering of plugin options +// - fix for plugin page link URL +// - added menu added and debug switches +// - added get plugin version function + // == 1.1.1 == // - remove admin_url wrapper on Freemius first-path value @@ -2749,3 +2860,10 @@ function radio_station_settings_row( $option, $setting ) { // == 0.9.0 == // - Development Version + + +// ----------------- +// Development TODOs +// ----------------- +// - check Freemius upgrade/addon URLs +// - use sanitize text field on textarea ? diff --git a/radio-station-admin.php b/radio-station-admin.php index e296f21..4ed3b6e 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -160,11 +160,17 @@ function radio_station_add_admin_menus() { do_action( 'radio_station_admin_submenu_middle' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Hosts', 'radio-station' ), __( 'Hosts', 'radio-station' ), 'edit_hosts', 'hosts' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Producers', 'radio-station' ), __( 'Producers', 'radio-station' ), 'edit_producers', 'producers' ); - // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settingscap, 'playlist-export', 'radio_station_playlist_export_page' ); + + // 2.3.2: as temporarily disabled, allow enabling export playlists via filter + $export_playlists = apply_filters( 'radio_station_export_playlists', false ); + if ( $export_playlists ) { + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Export Playlists', 'radio-station' ), __( 'Export Playlists', 'radio-station' ), $settingscap, 'playlist-export', 'radio_station_playlist_export_page' ); + } - // --- import / export feature --- + // --- import / export shows feature --- + // note: not yet implemented in free version if ( file_exists( RADIO_STATION_DIR . '/includes/import-export.php' ) ) { - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Show Data', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Shows', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); } add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settingscap, 'radio-station', 'radio_station_settings_page' ); @@ -281,6 +287,14 @@ function radio_station_taxonomy_submenu_fix() { // 2.3.0: added section for upcoming (Pro) role editor feature add_action( 'radio_station_admin_page_section_permissions_bottom', 'radio_station_role_editor' ); function radio_station_role_editor() { + + // 2.3.2: add filter role editor message + $display = apply_filters( 'radio_station_role_editor_message', true ); + if ( !$display ) { + return; + } + + // --- role assignment message --- echo "

    " . esc_html( __( 'Role Assignments', 'radio-station' ) ) . "

    "; echo esc_html( __( 'You can assign a Radio Station role to users through the WordPress User editor.', 'radio-station' ) ); @@ -305,18 +319,14 @@ function radio_station_import_export_show_page() { $importexport = RADIO_STATION_DIR . '/templates/import-export-shows.php'; if ( file_exists( $importexport ) ) { - // --- enqueue semantic/ui styles --- - $suffix = '.min'; - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $suffix = ''; - } - $semantic_css_url = plugins_url( 'vendor/semantic/ui/dist/semantic' . $suffix . '.css', RADIO_STATION_FILE ); - wp_enqueue_style( 'semantic-ui-style', $semantic_css_url, array(), '2.4.1', 'all' ); - $semantic_js_url = plugins_url( 'vendor/semantic/ui/dist/semantic' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'semantic-ui-script', $semantic_js_url, array(), '2.4.1', true ); - // --- display the import/export page --- include $importexport; + + // --- enqueue Semenatic UI --- + // 2.3.2: conditional enqueue to be safe + if ( function_exists( 'radio_station_enqueue_semantic' ) ) { + radio_station_enqueue_semantic(); + } } } @@ -362,7 +372,7 @@ function radio_station_plugin_docs_page() { document.getElementById('doc-page-'+id).style.display = 'block'; if (hash != '') { anchor = document.getElementById(hash); - atop = anchor.offsetTop; /* do not use 'top' */ + atop = anchor.offsetTop; /* do not use 'top'! */ window.scrollTo(0, (atop-20)); } }"; @@ -997,7 +1007,7 @@ function radio_station_settings_page_top() { // --- free directory listing offer --- // 2.3.1: added offer to top of admin settings $now = time(); - $offer_end = strtotime( '2020-07-01 00:01' ); + $offer_end = strtotime( '2020-08-01 00:01' ); if ( $now < $offer_end ) { if ( !get_option( 'radio_station_listing_offer_accepted' ) ) { echo radio_station_listing_offer_content( false ); @@ -1029,7 +1039,7 @@ function radio_station_listing_offer_notice() { // --- bug out if offer expired --- $now = time(); - $offer_end = strtotime( '2020-07-01 00:01' ); + $offer_end = strtotime( '2020-08-01 00:01' ); if ( $now > $offer_end ) { return; } @@ -1108,7 +1118,7 @@ function radio_station_listing_offer_content( $dismissable = true ) { $blurb .= '
    '; $blurb .= '
    '; - $blurb .= esc_html( __( 'Offer valid until end of June 2020.', 'radio-station' ) ) . '
    '; + $blurb .= esc_html( __( 'Offer valid until end of July 2020.', 'radio-station' ) ) . '
    '; $blurb .= esc_html( __( 'Activate your 30 days before it ends!', 'radio-station' ) ); $blurb .= '
    '; diff --git a/radio-station.php b/radio-station.php index edecc1d..a4cafa3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -2,7 +2,7 @@ /** * @package Radio Station - * @version 2.3.1 + * @version 2.3.2 */ /* @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.1 +Version: 2.3.2 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -98,6 +98,7 @@ // --- check and define CPT slugs --- // TODO: prefix original slugs and update post/taxonomy data +// ... and then add new slugs to template hierarchy ... if ( get_option( 'radio_show_cpts_prefixed' ) ) { define( 'RADIO_STATION_SHOW_SLUG', 'rs-show' ); define( 'RADIO_STATION_PLAYLIST_SLUG', 'rs-playlist' ); @@ -132,10 +133,19 @@ require RADIO_STATION_DIR . '/includes/class-current-show-widget.php'; require RADIO_STATION_DIR . '/includes/class-upcoming-shows-widget.php'; require RADIO_STATION_DIR . '/includes/class-current-playlist-widget.php'; +require RADIO_STATION_DIR . '/includes/class-radio-clock-widget.php'; + +// --- Player --- +// 2.3.1.1: load radio player prototype (if present) +$player_file = RADIO_STATION_DIR . '/player/radio-player.php'; +if ( file_exists( $player_file ) ) { + require $player_file; +} // --- Feature Development --- // 2.3.0: add feature branch development includes -$features = array( 'class-radio-clock-widget', 'import-export' ); +// 2.3.1: added radio player widget file +$features = array( 'class-radio-player-widget', 'import-export' ); foreach ( $features as $feature ) { $filepath = RADIO_STATION_DIR . '/includes/' . $feature . '.php'; if ( file_exists ( $filepath ) ) { @@ -165,15 +175,37 @@ 'section' => 'broadcast', ), - // ? --- Alternative Stream URL --- ? - // 'streaming_alt' => array( + // --- Fallback Stream Format --- + // 'fallback_url' => array( + // 'type' => 'select', + // 'options' => array( 'mp3', 'aac' ), // ... + // 'label' => __( 'Streaming Format', 'radio-station' ), + // 'default' => 'mp3', + // 'helper' => __( 'Select streaming fallback for streaming URL.', 'radio-station' ), + // 'tab' => 'general', + // 'section' => 'broadcast', + // ), + + // --- Fallback Stream URL --- + // 'fallback_url' => array( // 'type' => 'text', // 'options' => 'URL', // 'label' => __( 'Fallback Stream URL', 'radio-station' ), - // 'default' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + // 'default' => '', + // 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + // 'tab' => 'general', + // 'section' => 'broadcast', + // ), + + // --- Fallback Stream Format --- + // 'fallback_url' => array( + // 'type' => 'text', + // 'options' => array( 'mp3', 'aac' ), // ... + // 'label' => __( 'Fallback Format', 'radio-station' ), + // 'default' => 'mp3', + // 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), // 'tab' => 'general', // 'section' => 'broadcast', - // 'pro' => true, // ), // --- Main Radio Language --- @@ -324,6 +356,21 @@ 'section' => 'schedule', ), + // --- Schedule Clock Display --- + 'schedule_clock' => array( + 'type' => 'select', + 'label' => __( 'Schedule Clock?', 'radio-station' ), + 'default' => 'clock', + 'options' => array( + '' => __( 'None', 'radio-station' ), + 'clock' => __( 'Clock', 'radio-station' ), + 'timezone' => __( 'Timezone', 'radio-station' ), + ), + 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + // --- [Pro] Schedule Switcher --- 'schedule_switcher' => array( 'type' => 'checkbox', @@ -336,6 +383,27 @@ 'pro' => true, ), + // --- [Pro] Available Views === + // 2.3.2: added additional views option + 'schedule_views' => array( + 'type' => 'multicheck', + 'label' => __( 'Available Views', 'radio-station' ), + // note: unstyled list view not included in defaults + 'default' => array( 'table', 'tabs' ), + 'value' => 'yes', + 'options' => array( + 'table' => __( 'Table View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + // 'grid' => __( 'Grid View', 'radio-station' ), + // 'calendar' => __( 'Calendar View', 'radio-station' ); + ), + 'helper' => __( 'Switcher Views available on Master Schedule.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + // === Show Page === // --- Show Blocks Position --- @@ -368,9 +436,10 @@ ), // --- Show Header Image --- + // 2.3.2: added plural to option label 'show_header_image' => array( 'type' => 'checkbox', - 'label' => __( 'Content Header Image', 'radio-station' ), + 'label' => __( 'Content Header Images', 'radio-station' ), 'value' => 'yes', 'default' => '', 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), @@ -455,13 +524,13 @@ 'section' => 'archives', ), - // ? --- Override Shows Archive --- ? + // ? --- Redirect Shows Archive --- ? // 'show_archive_override' => array( - // 'label' => __( 'Override Shows Archive', 'radio-station' ), + // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( '', 'radio-station' ), + // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), // 'tab' => 'pages', // 'section' => 'archives', // ), @@ -488,13 +557,13 @@ 'section' => 'archives', ), - // ? --- Override Overrides Archive --- ? + // ? --- Redirect Overrides Archive --- ? // 'override_archive_override' => array( - // 'label' => __( 'Override Overrides Archive', 'radio-station' ), + // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( '', 'radio-station' ), + // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), // 'tab' => 'pages', // 'section' => 'archives', // ), @@ -521,13 +590,13 @@ 'section' => 'archives', ), - // ? --- Override Playlists Archive --- ? + // ? --- Redirect Playlists Archive --- ? // 'playlist_archive_override' => array( - // 'label' => __( 'Override Playlists Archive', 'radio-station' ), + // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( '', 'radio-station' ), + // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), // 'tab' => 'pages', // 'section' => 'archives', // ), @@ -554,13 +623,13 @@ 'section' => 'archives', ), - // ? --- Override Genres Archives --- ? + // ? --- Redirect Genres Archives --- ? // 'genre_archive_override' => array( - // 'label' => __( 'Override Genres Archive', 'radio-station' ), + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), // 'type' => 'checkbox', // 'value' => 'yes', // 'default' => '', - // 'helper' => __( '', 'radio-station' ), + // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), // 'tab' => 'pages', // 'section' => 'archives', // ), @@ -631,6 +700,31 @@ 'section' => 'single', ), + // === Widgets === + + // --- AJAX Loading --- + 'ajax_widgets' => array( + 'type' => 'checkbox', + 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), + 'default' => 'yes', + 'value' => '', + 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + ), + + // --- Dynamic Reloading --- + 'dynamic_reload' => array( + 'type' => 'checkbox', + 'label' => __( 'Dynamic Reloading?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + // === Roles / Capabilities / Permissions === // 2.3.0: added new capability and role options @@ -687,14 +781,17 @@ // === Tabs and Sections === // --- Tab Labels --- + // 2.3.2: add widget options tab 'tabs' => array( 'general' => __( 'General', 'radio-station' ), 'pages' => __( 'Pages', 'radio-station' ), 'templates' => __( 'Templates', 'radio-station' ), + 'widgets' => __( 'Widgets', 'radio-station' ), 'roles' => __( 'Roles', 'radio-station' ), ), // --- Section Labels --- + // 2.3.2: add widget loading section 'sections' => array( 'station' => __( 'Station', 'radio-station' ), 'broadcast' => __( 'Broadcast', 'radio-station' ), @@ -705,6 +802,7 @@ 'schedule' => __( 'Schedule Page', 'radio-station' ), 'show' => __( 'Show Pages', 'radio-station' ), 'archives' => __( 'Archives', 'radio-station' ), + 'loading' => __( 'Widget Loading', 'radio-station' ), 'permissions' => __( 'Permissions', 'radio-station' ), ), ); @@ -799,7 +897,8 @@ function radio_station_init() { function radio_station_check_version() { // --- get current and stored versions --- - $version = radio_station_loader_instance()->version; + // 2.3.2: use plugin version function + $version = radio_station_plugin_version(); $stored_version = get_option( 'radio_station_version', false ); // --- check current against stored version --- @@ -856,6 +955,14 @@ function radio_station_enqueue_scripts() { // --- enqueue plugin script --- // 2.3.0: added jquery dependency for inline script fragments radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); + + // -- enqueue javascript timezone script --- + // $suffix = '.min'; + // if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + // $suffix = ''; + // } + // $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); + // wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); } // --------------------- @@ -870,7 +977,14 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = $template = radio_station_get_template( 'both', $filename, 'js' ); if ( $template ) { - $version = filemtime( $template['file'] ); + // 2.3.2: use plugin version for releases + $plugin_version = radio_station_plugin_version(); + if ( 5 == strlen( $plugin_version ) ) { + $version = $plugin_version; + } else { + $version = filemtime( $template['file'] ); + } + $url = $template['url']; // --- enqueue script --- @@ -901,7 +1015,13 @@ function radio_station_enqueue_style( $stylekey ) { if ( $template ) { // --- use found template values --- - $version = filemtime( $template['file'] ); + // 2.3.2: use plugin version for releases + $plugin_version = radio_station_plugin_version(); + if ( 5 == strlen( $plugin_version ) ) { + $version = $plugin_version; + } else { + $version = filemtime( $template['file'] ); + } $url = $template['url']; // --- enqueue styles in footer --- @@ -919,114 +1039,168 @@ function radio_station_enqueue_style( $stylekey ) { add_action( 'wp_enqueue_scripts', 'radio_station_localize_script' ); function radio_station_localize_script() { - // --- create settings object --- - $js = "var radio = {}; "; + // --- create settings objects --- + $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; + // --- set AJAX URL --- + // 2.3.2: add admin AJAX URL + $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; + // --- clock time format --- $clock_format = radio_station_get_setting( 'clock_time_format' ); - $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "'; " . PHP_EOL; + $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; // --- detect touchscreens --- // ref: https://stackoverflow.com/a/52855084/5240159 - $js .= "if (window.matchMedia('(pointer: coarse)').matches) {radio.touchscreen = true;} else {radio.touchscreen = false;} " . PHP_EOL; + $js .= "if (window.matchMedia('(pointer: coarse)').matches) {radio.touchscreen = true;} else {radio.touchscreen = false;}" . PHP_EOL; - // --- radio timezone --- - $timezone = radio_station_get_setting( 'timezone_location' ); - if ( !$timezone || ( '' == $timezone ) ) { - // --- fallback to WordPress timezone --- - $timezone = get_option( 'timezone_string' ); - if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { - $timezone = ''; - } - if ( '' == $timezone ) { - $offset = get_option( 'gmt_offset' ); - } + // --- set debug flag --- + if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { + $js .= "radio.debug = true;" . PHP_EOL; + } else { + $js .= "radio.debug = false;" . PHP_EOL; } - if ( isset( $offset ) ) { - if ( !$offset ) { - $offset = 0; + + // --- radio timezone --- + // 2.3.2: added get timezone function + $timezone = radio_station_get_timezone(); + + // if ( isset( $offset ) ) { + if ( stristr( $timezone, 'UTC' ) ) { + + if ( 'UTC' == $timezone ) { + $offset = '0'; + } else { + $offset = str_replace( 'UTC', '', $timezone ); } - // $offset = intval( $offset ) * 60 * 60 * 1000; - $js .= "radio.timezone_offset = " . esc_js( $offset ) . "; "; - if ( 0 == $offset ) { + $js .= "radio.timezone.offset = " . esc_js( $offset * 60 * 60 ) . "; "; + if ( '0' == $offset ) { $offset = ''; } elseif ( $offset > 0 ) { $offset = '+' . $offset; } - $js .= "radio.timezone_code = 'UTC" . esc_js( $offset ) . "'; "; - $js .= "radio.timezone_utc = '" . esc_js( $offset ) . "'; "; + $js .= "radio.timezone.code = 'UTC" . esc_js( $offset ) . "'; "; + $js .= "radio.timezone.utc = '" . esc_js( $offset ) . "'; "; + $js .= "radio.timezone.utczone = true; "; + } else { + // --- get offset and code from timezone location --- $datetimezone = new DateTimeZone( $timezone ); $offset = $datetimezone->getOffset( new DateTime() ); + $offset_hours = $offset / ( 60 * 60 ); if ( 0 == $offset ) { $utc_offset = ''; } elseif ( $offset > 0 ) { - $utc_offset = '+' . $offset; + $utc_offset = '+' . $offset_hours; } else { - $utc_offset = $offset; + $utc_offset = $offset_hours; } $utc_offset = 'UTC' . $utc_offset; $code = radio_station_get_timezone_code( $timezone ); - $js .= "radio.timezone_location = '" . esc_js( $timezone ) . "'; "; - $js .= "radio.timezone_offset = " . esc_js( $offset ) . "; "; - $js .= "radio.timezone_code = '" . esc_js( $timezone ) . "'; "; - $js .= "radio.timezone_utc = '" . esc_js( $utc_offset ) . "'; "; - } + $js .= "radio.timezone.location = '" . esc_js( $timezone ) . "'; "; + $js .= "radio.timezone.offset = " . esc_js( $offset ) . "; "; + $js .= "radio.timezone.code = '" . esc_js( $code ) . "'; "; + $js .= "radio.timezone.utc = '" . esc_js( $utc_offset ) . "'; "; + $js .= "radio.timezone.utczone = false; "; + } + + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $js .= "radio.timezone.adjusted = false; "; + } else { + $js .= "radio.timezone.adjusted = true; "; + } + // --- set user timezone offset --- - $js .= "radio.user_offset = (new Date()).getTimezoneOffset() * 60; "; + // (and convert offset minutes to seconds) + $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . PHP_EOL; // --- translated months array --- - $js .= "radio.months = new Array("; + // 2.3.2: also translate short month labels + $js .= "radio.labels.months = new Array("; + $short = "radio.labels.smonths = new Array("; $months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ); foreach ( $months as $i => $month ) { $month = radio_station_translate_month( $month ); + $short_month = radio_station_translate_month( $month, true ); $month = str_replace( "'", "", $month ); + $short_month = str_replace( "'", "", $short_month ); $js .= "'" . esc_js( $month ) . "'"; - if ( $i < count( $months ) ) { + $short .= "'" . esc_js( $short_month ) . "'"; + if ( $i < ( count( $months ) - 1 ) ) { $js .= ", "; + $short .= ", "; } } $js .= ");" . PHP_EOL; + $js .= $short . ");" . PHP_EOL; // --- translated days array --- - $js .= "radio.days = new Array("; + // 2.3.2: also translate short day labels + $js .= "radio.labels.days = new Array("; + $short = "radio.labels.sdays = new Array("; $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); foreach ( $days as $i => $day ) { $day = radio_station_translate_weekday( $day ); + $short_day = radio_station_translate_weekday( $day, true ); $day = str_replace( "'", "", $day ); + $short_day = str_replace( "'", "", $short_day ); $js .= "'" . esc_js( $day ) . "'"; - if ( $i < count( $days ) ) { + $short .= "'" . esc_js( $short_day ) . "'"; + if ( $i < ( count( $days ) - 1 ) ) { $js .= ", "; + $short .= ", "; } } $js .= ");" . PHP_EOL; + $js .= $short . ");" . PHP_EOL; // --- translated time unit strings --- - $js .= "radio.units_am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; " . PHP_EOL; - $js .= "radio.units_pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; " . PHP_EOL; - $js .= "radio.units_second = '" . esc_js( __( 'Second', 'radio-station' ) ) . "'; " . PHP_EOL; - $js .= "radio.units_seconds = '" . esc_js( __( 'Seconds', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_minute = '" . esc_js( __( 'Minute', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_minutes = '" . esc_js( __( 'Minutes', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "';" . PHP_EOL; - $js .= "radio.units_days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "';" . PHP_EOL; - - // --- set debug flag --- - if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { - $js .= "radio.debug = true; " . PHP_EOL; - } else { - $js .= "radio.debug = false; " . PHP_EOL; - } + // TODO: convert units to single object + $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; + $js .= "radio.units.pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; "; + $js .= "radio.units.second = '" . esc_js( __( 'Second', 'radio-station' ) ) . "'; "; + $js .= "radio.units.seconds = '" . esc_js( __( 'Seconds', 'radio-station' ) ) . "'; "; + $js .= "radio.units.minute = '" . esc_js( __( 'Minute', 'radio-station' ) ) . "'; "; + $js .= "radio.units.minutes = '" . esc_js( __( 'Minutes', 'radio-station' ) ) . "'; "; + $js .= "radio.units.hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "'; "; + $js .= "radio.units.hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "'; "; + $js .= "radio.units.day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; "; + $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; "; // --- add inline script --- wp_add_inline_script( 'radio-station', $js ); } +// ----------------------------------------- +// Fix to Redirect Plugin Settings Menu Link +// ----------------------------------------- +// 2.3.2: added settings submenu page redirection fix +add_action( 'init', 'radio_station_settings_page_redirect' ); +function radio_station_settings_page_redirect() { + + // --- bug out if not admin page --- + if ( !is_admin() ) { + return; + } + + // --- but out if not plugin settings page --- + if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != $_REQUEST['page'] ) ) { + return; + } + + // --- check if link is for options-general.php --- + if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { + + // --- redirect to plugin settings page (admin.php) --- + $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + wp_redirect( $url ); + exit; + } +} + // ----------------- // === Templates === @@ -1878,9 +2052,13 @@ function radio_station_set_roles() { // 2.3.0: check/add profile capabilities to hosts $wp_roles->add_role( 'dj', __( 'DJ / Host', 'radio-station' ), $caps ); $role_caps = $wp_roles->roles['dj']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } $host_caps = array( 'edit_hosts', 'edit_published_hosts', 'delete_hosts', 'read_hosts', 'publish_hosts' ); foreach ( $host_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || ( !$role_caps[$cap] ) ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { $wp_roles->add_cap( 'dj', $cap, true ); } } @@ -1889,9 +2067,13 @@ function radio_station_set_roles() { // 2.3.0: add equivalent capability role for Show Producer $wp_roles->add_role( 'producer', __( 'Show Producer', 'radio-station' ), $caps ); $role_caps = $wp_roles->roles['producer']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } $producer_caps = array( 'edit_producers', 'edit_published_producers', 'delete_producers', 'read_producers', 'publish_producers' ); foreach ( $producer_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || ( !$role_caps[$cap] ) ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { $wp_roles->add_cap( 'producer', $cap, true ); } } @@ -1964,6 +2146,10 @@ function radio_station_set_roles() { // --- grant show edit capabilities to author users --- $author_caps = $wp_roles->roles['author']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $author_caps ) ) { + $author_caps = array(); + } $extra_caps = array( 'edit_shows', 'edit_published_shows', @@ -2045,6 +2231,10 @@ function radio_station_set_roles() { // --- grant show edit capabilities to editor users --- $editor_caps = $wp_roles->roles['editor']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $editor_caps ) ) { + $editor_caps = array(); + } foreach ( $edit_caps as $cap ) { if ( !array_key_exists( $cap, $editor_caps ) || ( !$editor_caps[$cap] ) ) { $wp_roles->add_cap( 'editor', $cap, true ); @@ -2054,6 +2244,10 @@ function radio_station_set_roles() { // --- grant all plugin capabilities to admin users --- $admin_caps = $wp_roles->roles['administrator']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $admin_caps ) ) { + $admin_caps = array(); + } foreach ( $edit_caps as $cap ) { if ( !array_key_exists( $cap, $admin_caps ) || ( !$admin_caps[$cap] ) ) { $wp_roles->add_cap( 'administrator', $cap, true ); @@ -2072,7 +2266,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { global $post, $wp_roles; // --- check if super admin --- - // 2.3.1: fix to not revoke edit caps from super admin + // ? fix to not revoke edit caps from super admin ? // (not implemented, causing connection reset error) // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { // return $allcaps; @@ -2157,6 +2351,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { // Set Debug Mode Constant // ----------------------- // 2.3.0: added debug mode constant +// 2.3.2: added saving debug mode constant if ( !defined( 'RADIO_STATION_DEBUG' ) ) { $rs_debug = false; if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { @@ -2164,6 +2359,13 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { } define( 'RADIO_STATION_DEBUG', $rs_debug ); } +if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { + $rs_save_debug = false; + if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { + $rs_save_debug = true; + } + define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); +} // -------------------------- // maybe Clear Transient Data @@ -2173,10 +2375,14 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { // 2.3.1: check clear show transients option add_action( 'init', 'radio_station_clear_transients' ); function radio_station_clear_transients() { - if ( RADIO_STATION_DEBUG || ( 'yes' == radio_station_get_setting( 'clear_transients' ) ) ) { - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_current_show' ); - delete_transient( 'radio_station_next_show' ); + $clear_transients = radio_station_get_setting( 'clear_transients' ); + if ( RADIO_STATION_DEBUG || ( 'yes' == $clear_transients ) ) { + // 2.3.2: do not clear on AJAX calls + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_next_show' ); + } } } @@ -2189,7 +2395,8 @@ function radio_station_debug( $data, $echo = true, $file = false ) { // --- maybe output debug info --- if ( $echo ) { // 2.3.0: added span wrap for hidden display - echo ''; + // 2.3.1.1: added class for page source searches + echo '' . PHP_EOL; diff --git a/readme.txt b/readme.txt index 95cb17f..ddea30b 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ === Radio Station === -Contributors: tonyzeoli, majick, nourma +Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 @@ -184,6 +184,35 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == += 2.3.2 = +* Update: Plugin Loader (1.1.2) with settings link fix +* Improved: use plugin timezone setting for all times +* Improved: show shift conflict checker logic +* Added: Radio Clock Widget for user/server time display +* Added: AJAX widget load option (to bypass page caches) +* Added: automated show schedule highlighting (table/tabs/list) +* Added: playlist track arrows for re-ordering tracks +* Added: AJAX save of show shifts and playlist tracks +* Added: post type editing metabox position filtering +* Added: more display attributes to Master Schedule shortcode +* Added: time format filters for time output displays +* Added: javascript user timezone display on Master Schedule +* Fixed: handling of UTC only timezone settings +* Fixed: added check for empty role capabilities +* Fixed: added settings submenu redirection fix +* Fixed: show and override midnight end conflict +* Fixed: calculate next shows at end of schedule week +* Fixed: metaboxes disappearing on position sorting +* Fixed: move tracks marked New to end of Playlist on update +* Fixed: override shift array output showing above schedule +* Fixed: master schedule specify days attribute bug +* Fixed: display real end time of overnight split shifts +* Fixed: master schedule display with days attribute +* Fixed: logic for Affected Shifts in override list +* Fixed: removed auto-tab selection change on tab view resize +* Fixed: Current Show widget schedule/countdown for Overrides +* Fixed: multiple overrides in schedule range variable conflict + = 2.3.1 = * Update: Plugin Loader (1.1.1) with Freemius first path fix * Fixed: conditions for Schedule Override time calculations @@ -541,6 +570,14 @@ You may translate the plugin into another language. Please visit our [WordPress == Upgrade Notice == += 2.3.1 = +* Improved Times, AJAX Loading and Bugfix Update +* https://netmix.com/radio-station-2-3-2-release/ +* Radio Clock Widget and Widget AJAX Loading +* AJAX Saving of Show Shifts and Playlist Tracks +* Automated current Show schedule highlighting +* Improved timezones, overrides, shift checking and more + = 2.3.1 = * Bugfix Update and Announcing New Netmix Station Directory! * https://netmix.com/announcing-new-netmix-directory/ diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php index c61b51d..42d2cc4 100644 --- a/templates/master-schedule-div.php +++ b/templates/master-schedule-div.php @@ -3,6 +3,28 @@ * Template for master schedule shortcode div style. */ +$now = radio_station_get_now(); +$date = radio_station_get_time( 'date', $now ); + +// --- set shift time formats --- +// 2.3.2: set time formats early +if ( 24 == (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'H:i'; +} else { + $start_data_format = $end_data_format = 'g:i a'; +} +$start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'schedule-div', $atts ); +$end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'schedule-div', $atts ); + +// --- get schedule days and dates --- +// 2.3.2: allow for start day attibute +if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { + $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); +} else { + $weekdays = radio_station_get_schedule_weekdays(); +} +$weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + // --- filter show avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'div' ); @@ -10,7 +32,6 @@ $output .= '"; +} + + +// ------------- +// === Shows === +// ------------- + // --------------------- // Add Show Info Metabox // --------------------- @@ -2872,11 +3175,17 @@ function radio_station_show_save_data( $post_id ) { // --- maybe clear transient data --- // 2.3.0: added to clear transients if any meta has changed + // 2.3.3: remove current show transient if ( $show_meta_changed || $show_shifts_changed ) { delete_transient( 'radio_station_current_schedule' ); delete_transient( 'radio_station_next_show' ); - // 2.3.3: remove current show transient - // delete_transient( 'radio_station_current_show' ); + delete_transient( 'radio_station_previous_show' ); + + // 2.3.4: delete all prefixed transients (for times) + radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); + radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); + radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); + do_action( 'radio_station_clear_data', 'show', $post_id ); do_action( 'radio_station_clear_data', 'show_meta', $post_id ); @@ -3305,8 +3614,14 @@ function radio_station_schedule_override_metabox() { echo ''; } echo ''; + + // 2.3.4: add common start minutes to top of options echo ''; echo ''; + // 2.3.4: add common end minutes to top of options echo '
  • '; echo esc_html( __( 'End Time', 'radio-station' ) ) . ':'; echo ''; echo ''; - echo ''; + echo ''; if ( count( $months ) > 0 ) { foreach ( $months as $key => $data ) { $label = $wp_locale->get_month( $data['month'] ) . ' ' . $data['year']; @@ -3727,6 +4053,30 @@ function radio_station_override_date_filter( $post_type, $which ) { } +// ------------------------------- +// Add Schedule Past Future Filter +// ------------------------------- +// 2.3.3: added past future filter prototype code +add_action( 'restrict_manage_posts', 'radio_station_override_past_future_filter', 10, 2 ); +function radio_station_override_past_future_filter( $post_type, $which ) { + + if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { + return; + } + + // --- set past future selection / default --- + $pastfuture = isset( $_GET['pastfuture'] ) ? $_GET['pastfuture'] : ''; + $pastfuture = apply_filters( 'radio_station_overrides_past_future_default', $pastfuture ); + + // --- past / future override selector --- + echo ''; + echo ''; + +} // ----------------------------------- // === Post Type List Query Filter === @@ -3798,14 +4148,6 @@ function radio_station_columns_query_filter( $query ) { } } - // --- set the meta query for filtering --- - // this is not working?! but does not need to as using orderby fixes it - // $meta_query = array( - // 'key' => 'show_shift_time', - // 'compare' => 'EXISTS', - // ); - // $query->set( 'meta_query', $meta_query ); - // --- order by show time start --- // only need to set the orderby query and exists check is automatically done! $query->set( 'orderby', 'meta_value' ); @@ -3848,7 +4190,6 @@ function radio_station_columns_query_filter( $query ) { // --- apply override year/month filtering --- if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { $yearmonth = $_GET['month']; - // TODO: adjust for timezone..? $start_date = date( $yearmonth . '01' ); $end_date = date( $yearmonth . 't' ); $meta_query = array( @@ -3860,6 +4201,36 @@ function radio_station_columns_query_filter( $query ) { $query->set( 'meta_query', $meta_query ); } + // --- meta query for past / future overrides filter --- + // 2.3.3: added past future query prototype code + $valid = array( 'past', 'future' ); + if ( isset( $_GET['pastfuture'] ) && in_array( $_GET['pastfuture'], $valid ) ) { + $pastfuture = $_GET['pastfuture']; + if ( 'past' == $pastfuture ) { + $compare = '<'; + } elseif ( 'future' == $pastfuture ) { + $compare = '>='; + } + $date = date( 'Y-m-d', time() ); + $pastfuture_query = array( + 'key' => 'show_override_date', + 'value' => $date, + 'compare' => $compare, + 'type' => 'DATE', + ); + if ( isset( $meta_query ) ) { + $combined_query = array( + 'relation' => 'AND', + $meta_query, + $pastfuture_query, + ); + $meta_query = $combined_query; + } else { + $meta_query = $pastfuture_query; + } + $query->set( 'meta_query', $meta_query ); + } + } } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 49d4fa5..5992555 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -18,7 +18,7 @@ // * Language Archive Shortcode // === Show Related Shortcodes === // - Show List Shortcode Abstract -// - Show Posts List Shortcode +// - Show Posts Archive Shortcode // - Show Playlists List Shortcode // - Show Lists Pagination Javascript // === Widget Shortcodes === @@ -1117,9 +1117,9 @@ function radio_station_show_list_shortcode( $type, $atts ) { return $list; } -// ------------------------- +// ---------------------------- // Show Posts Archive Shortcode -// ------------------------- +// ---------------------------- // requires: show shortcode attribute, eg. [show-posts-list show="1"] add_shortcode( 'show-posts-archive', 'radio_station_show_posts_archive' ); add_shortcode( 'show-post-archive', 'radio_station_show_posts_archive' ); diff --git a/includes/support-functions.php b/includes/support-functions.php index effb0c9..fa9dd2e 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -18,6 +18,7 @@ // - Get Override Metadata // - Get Current Schedule // - Get Current Show +// - Get Previous Show // - Get Next Show // - Get Next Shows // - Get Blog Posts for Show @@ -691,20 +692,49 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } } - // --- get episodes with associated show ID --- + // --- get records with associated show ID --- + global $wpdb; - $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; - $query = $wpdb->prepare( $query, $show_id ); - $post_metas = $wpdb->get_results( $query, ARRAY_A ); - if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { - return false; + if ( 'posts' == $datatype ) { + + // 2.3.3.4: handle possible multiple show post values + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = '" . $metakey . "' AND meta_value LIKE '%" . $show_id . "%'"; + $results = $wpdb->get_results( $query, ARRAY_A ); + // echo "Results: "; print_r( $results ); + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { + return false; + } + + // --- get/check post IDs in post meta --- + $post_ids = array(); + foreach ( $results as $result ) { + // TODO: check raw result is serialized or array ? + $show_ids = maybe_unserialize( $result['meta_value'] ); + if ( $show_id == $result['meta_value'] || in_array( $show_id, $show_ids ) ) { + $post_ids[] = $result['post_id']; + } + } + // echo "Post IDs: "; print_r( $post_ids ); + } else { + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; + $query = $wpdb->prepare( $query, $show_id ); + $post_metas = $wpdb->get_results( $query, ARRAY_A ); + if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { + return false; + } + + // --- get post IDs from post meta --- + $post_ids = array(); + foreach ( $post_metas as $post_meta ) { + $post_ids[] = $post_meta['post_id']; + } } - // --- get post IDs from post meta --- - $post_ids = array(); - foreach ( $post_metas as $post_meta ) { - $post_ids[] = $post_meta['post_id']; + // --- check for post IDs --- + if ( count( $post_ids ) < 1 ) { + return false; } // --- get posts from post IDs --- @@ -1246,7 +1276,7 @@ function radio_station_get_current_schedule( $time = false ) { } // --- loop all shifts to add show data --- - $set_prev_shift = $prev_shift_end = false; + $prev_shift = $set_prev_shift = $prev_shift_end = false; foreach ( $show_shifts as $day => $shifts ) { // 2.3.1: added check for shift count if ( count( $shifts ) > 0 ) { @@ -1304,11 +1334,23 @@ function radio_station_get_current_schedule( $time = false ) { $shift['day'] = $day; $current_show = $shift; - // 2.3.3: set current show global + // 2.3.3: set current show to global data + // 2.3.4: set previous show shift to global and transient if ( !$time ) { + $expires = $shift_end_time - $now - 1; $radio_station_data['current_show'] = $current_show; + if ( $prev_shift ) { + $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + $radio_station_data['previous_show'] = $prev_show; + set_transient( 'radio_station_previous_show', $prev_show, $expires ); + } } else { $radio_station_data['current_show_' . $time ] = $current_show; + if ( $prev_shift ) { + $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + $radio_station_data['previous_show_' . $time] = $prev_show; + set_transient( 'radio_station_previous_show_' . $time, $prev_show, $expires ); + } } $expires = $shift_end_time - $now - 1; if ( $expires > 3600 ) { @@ -1316,7 +1358,7 @@ function radio_station_get_current_schedule( $time = false ) { } // 2.3.2: set temporary transient if time is specified - // 2.3.3: remove current show transient + // 2.3.3: remove current show transient (being unreliable) /* if ( !$time ) { set_transient( 'radio_station_current_show', $current_show, $expires ); } else { @@ -1388,6 +1430,9 @@ function radio_station_get_current_schedule( $time = false ) { $prev_shift_end = $shift_end_time; } + // 2.3.4: set previous shift value + $prev_shift = $shift; + } } } @@ -1493,15 +1538,11 @@ function radio_station_get_current_show( $time = false ) { // --- get cached current show value --- // 2.3.3: remove current show transient // 2.3.3: check for existing global data first - /* if ( !$time ) { - if ( isset( $radio_station_data['current_show'] ) ) { - return $radio_station_data['current_show']; - } - } else { - if ( isset( $radio_station_data['current_show_' . $time] ) ) { - return $radio_station_data['current_show_' . $time]; - } - } */ + if ( !$time && isset( $radio_station_data['current_show'] ) ) { + return $radio_station_data['current_show']; + } elseif ( isset( $radio_station_data['current_show_' . $time] ) ) { + return $radio_station_data['current_show_' . $time]; + } // --- get all show shifts --- if ( !$time ) { @@ -1534,7 +1575,7 @@ function radio_station_get_current_show( $time = false ) { } // --- loop shifts to get current show --- - $current_split = false; + $current_split = $prev_show = false; foreach ( $weekdays as $day ) { if ( isset( $show_shifts[$day] ) ) { $shifts = $show_shifts[$day]; @@ -1564,6 +1605,20 @@ function radio_station_get_current_show( $time = false ) { } // --- recombine possible split shift to set current show --- $current_show = $shift; + + // 2.3.4: also set previous shift data + if ( $prev_shift ) { + $expires = $shift_end_time - $now - 1; + $previous_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); + if ( !$time ) { + $radio_station_data['previous_show'] = $previous_show; + set_transient( 'radio_station_previous_show', $previous_show, $expires ); + } else { + $radio_station_data['previous_show_' . $time] = $previous_show; + set_transient( 'radio_station_previous_show_' . $time, $previous_show, $expires ); + } + } + /* if ( isset( $current_show['split'] ) && $current_show['split'] ) { if ( isset( $current_show['real_start'] ) ) { // 2.3.3: second shift half so set to previous day and date @@ -1579,18 +1634,71 @@ function radio_station_get_current_show( $time = false ) { if ( RADIO_STATION_DEBUG ) { echo '' . PHP_EOL; } + + // 2.3.4: store previous shift + $prev_shift = $shift; } } } - // --- filter and return --- + // --- filter current show --- // 2.3.2: added time argument to filter $current_show = apply_filters( 'radio_station_current_show', $current_show, $time ); - $radio_station_data['current_show'] = $current_show; + + // --- set to global data --- + if ( !$time ) { + $radio_station_data['current_show'] = $current_show; + } else { + $radio_station_data['current_show_' . $time] = $current_show; + } return $current_show; } +// ----------------- +// Get Previous Show +// ----------------- +// 2.3.3: added get previous show function +function radio_station_get_previous_show( $time = false ) { + + global $radio_station_data; + + $prev_show = false; + + // --- get cached current show value --- + if ( !$time ) { + if ( isset ( $radio_station_data['previous_show'] ) ) { + $prev_show = $radio_station_data['previous_show']; + } else { + $prev_show = get_transient( 'radio_station_previous_show' ); + } + } else { + if ( isset ( $radio_station_data['previous_show_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $time]; + } else { + $prev_show = get_transient( 'radio_station_previous_show_' . $time ); + } + } + + // --- if not set it has expired so recheck schedule --- + if ( !$prev_show ) { + if ( !$time ) { + $schedule = radio_station_get_current_schedule(); + if ( isset( $radio_station_data['previous_show'] ) ) { + $prev_show = $radio_station_data['previous_show']; + } + } else { + $schedule = radio_station_get_current_schedule( $time ); + if ( isset( $radio_station_data['previous_show_' . $time] ) ) { + $prev_show = $radio_station_data['previous_show_' . $time]; + } + } + } + + // note: already filtered when set + return $prev_show; +} + // ------------- // Get Next Show // ------------- @@ -1635,9 +1743,9 @@ function radio_station_get_next_show( $time = false ) { } } - // --- filter and return --- // 2.3.2: added time argument to filter - $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); + // 2.3.4: moved filter to where data is set so only applied once + // $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); } return $next_show; @@ -1746,10 +1854,13 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = // --- maybe set next show transient --- // 2.3.3: also set global data key + // 2.3.4: moved next show filter here (before setting data) if ( !isset( $next_show ) ) { $next_show = $shift; $next_expires = $shift_end_time - $now - 1; - if ( !$time ) { + + $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); + if ( !$time ) { $radio_station_data['next_show'] = $next_show; set_transient( 'radio_station_next_show', $next_show, $next_expires ); } else { @@ -4066,6 +4177,26 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { return $atts; } +// -------------------------- +// Delete Prefixed Transients +// -------------------------- +// 2.3.4: added helper for clearing transient data +function radio_station_delete_transients_with_prefix( $prefix ) { + global $wpdb; + + $prefix = $wpdb->esc_like( '_transient_' . $prefix ); + $sql = "SELECT `option_name` FROM $wpdb->options WHERE `option_name` LIKE '%s'"; + $results = $wpdb->get_results( $wpdb->prepare( $sql, $prefix . '%' ), ARRAY_A ); + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { + return; + } + + foreach ( $results as $option ) { + $key = ltrim( $option['option_name'], '_transient_' ); + delete_transient( $key ); + } +} + // -------------------- // === Translations === diff --git a/js/radio-station-show-page.js b/js/radio-station-show-page.js index 8e3e32f..8f1005d 100644 --- a/js/radio-station-show-page.js +++ b/js/radio-station-show-page.js @@ -40,6 +40,19 @@ function radio_show_responsive() { if (showcontent.offsetWidth < 500) {showcontent.classList.add('narrow');} else {showcontent.classList.remove('narrow');} } + + /* Maybe Match Heights for Info and Description */ + if (document.getElementById('show-content').className.indexOf('top-blocks') < 0) { + info = document.getElementById('show-info'); + desc = document.getElementById('show-description'); + about = document.getElementById('show-section-about'); + if (info && desc) { + descheight = info.offsetHeight - 30; + if (about) {descheight = descheight - about.offsetHeight;} + if (descheight < desc.style.minHeight) {desc.style.maxHeight = 'none';} + else {desc.style.maxHeight = descheight+'px';} + } + } /* Maybe Display Show More Button */ descstate = document.getElementById('show-desc-state'); diff --git a/radio-station-admin.php b/radio-station-admin.php index 7c8a028..9068948 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -1363,6 +1363,7 @@ function radio_station_mailchimp_form() { // --- bug out if already subscribed --- // 2.3.0: added to hide for existing subscribers + // note: there is a typo in this option not worth fixing $subscribed = get_option( 'radio_station_subcribed' ); if ( $subscribed && is_array( $subscribed ) && in_array( $user_email, $subscribed ) ) { return; @@ -1452,6 +1453,7 @@ function radio_station_clear_plugin_options() { if ( !current_user_can( 'manage_options' ) ) {return;} if ( isset( $_GET['option'] ) && ( 'subscribed' == $_GET['option'] ) ) { + // note: there is a typo in this option not worth fixing delete_option( 'radio_station_subcribed' ); } if ( isset( $_GET['option'] ) && ( 'notices' == $_GET['option'] ) ) { diff --git a/radio-station.php b/radio-station.php index 42a0616..ecb1084 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.3 +Version: 2.3.3.4 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -1236,12 +1236,12 @@ function radio_station_get_template( $type, $template, $paths = false ) { if ( isset( $radio_station_data['style-dirs'] ) ) { $dirs = $radio_station_data['style-dirs']; } - $paths = array( 'css', 'styles' ); + $paths = array( 'css', 'styles', '' ); } elseif ( 'js' == $paths ) { if ( isset( $radio_station_data['script-dirs'] ) ) { $dirs = $radio_station_data['script-dirs']; } - $paths = array( 'js', 'scripts' ); + $paths = array( 'js', 'scripts', '' ); } } @@ -1287,8 +1287,9 @@ function radio_station_get_template( $type, $template, $paths = false ) { // --- loop directory hierarchy to find first template --- foreach ( $dirs as $dir ) { - $template_path = $dir['path'] . '/' . $template; - $template_url = $dir['urlpath'] . '/' . $template; + // 2.3.4: use trailingslashit to account for empty paths + $template_path = trailingslashit( $dir['path'] ) . $template; + $template_url = trailingslashit( $dir['urlpath'] ) . $template; if ( file_exists( $template_path ) ) { if ( 'file' == (string) $type ) { @@ -1872,89 +1873,134 @@ function radio_station_add_show_links( $content ) { add_filter( 'previous_post_link', 'radio_station_get_show_post_link', 11, 5 ); function radio_station_get_show_post_link( $output, $format, $link, $adjacent_post, $adjacent ) { - global $post; - - // --- filter to allow disabling --- - $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); - if ( !$link_show_posts ) { + global $radio_station_data, $post; + + // --- filter next and previous Show links --- + // 2.3.4: add filtering for adjacent show links + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( in_array( $post->post_type, $post_types ) ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + // TODO: get next/previous for override time/date + } else { + $shifts = get_post_meta( $post->id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + if ( count( $shifts ) < 1 ) { + // TODO: get the most recent show shift ? + } + if ( 1 == count( $shifts ) ) { + $shift = $shifts[0]; + $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $time = radio_station_get_time( ( $shift_start + 1 ) ); + if ( 'next' == $adjacent ) { + $rel = 'next'; + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $rel = 'prev'; + $show = radio_station_get_previous_show( $time ); + } + } else { + // TODO: method for multiple shifts ? + return $output; + } + } + } + + // --- generate adjacent post link --- + if ( isset( $show ) ) { + $adjacent_post = get_post( $show['id'] ); + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + } + return $output; } // --- filter to allow related post types --- - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - if ( !in_array( $post->post_type, $post_types ) ) { - return $output; - } + $show_post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + if ( in_array( $post->post_type, $show_post_types ) ) { + + // --- filter to allow disabling --- + $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); + if ( !$link_show_posts ) { + return $output; + } - // --- get related show --- - $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); - if ( !$related_show ) { - return $output; - } + // --- get related show --- + $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); + if ( !$related_show ) { + return $output; + } - // --- get adjacent post query --- - $args = array( - 'post_type' => $post->post_type, - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $related_show, - 'compare' => '=', + // --- get adjacent post query --- + $args = array( + 'post_type' => $post->post_type, + 'meta_query' => array( + array( + 'key' => 'post_showblog_id', + 'value' => $related_show, + 'compare' => '=', + ), ), - ), - 'order_by' => 'post_date', - ); - - // --- setup previous or next post --- - $post_type_object = get_post_type_object( $post->post_type ); - if ( 'previous' == $adjacent ) { - $rel = 'prev'; - $args['order'] = 'DESC'; - $title = __( 'Previous Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - $show_posts = get_posts( $args ); - } elseif ( 'next' == $adjacent ) { - $rel = 'next'; - $args['order'] = 'ASC'; - $title = __( 'Next Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - $show_posts = get_posts( $args ); - } + 'order_by' => 'post_date', + ); - // --- loop posts to get adjacent post --- - $found_current_post = $adjacent_post = false; - if ( $show_posts && is_array( $show_posts ) && ( count( $show_posts ) > 0 ) ) { - foreach ( $show_posts as $show_post ) { - if ( $found_current_post ) { - $related_id = get_post_meta( $show_post->ID, 'post_showblog_id', true ); - if ( $related_id == $related_show ) { - $adjacent_post = $show_post; - break; - } - } - if ( $show_post->ID == $post->ID ) { - $found_current_post = true; - } + // --- setup previous or next post --- + $post_type_object = get_post_type_object( $post->post_type ); + if ( 'previous' == $adjacent ) { + $rel = 'prev'; + $args['order'] = 'DESC'; + $title = __( 'Previous Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + $show_posts = get_posts( $args ); + } elseif ( 'next' == $adjacent ) { + $rel = 'next'; + $args['order'] = 'ASC'; + $title = __( 'Next Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + $show_posts = get_posts( $args ); } - - if ( $adjacent_post ) { - // --- adjacent post title --- - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; + // --- loop posts to get adjacent post --- + $found_current_post = $adjacent_post = false; + if ( $show_posts && is_array( $show_posts ) && ( count( $show_posts ) > 0 ) ) { + foreach ( $show_posts as $show_post ) { + if ( $found_current_post ) { + $related_id = get_post_meta( $show_post->ID, 'post_showblog_id', true ); + // 2.3.3.4: handle possible multiple show post values + if ( ( is_array( $related_id ) && in_array( $related_show, $related_id ) ) + || ( !is_array( $related_id ) && ( $related_id == $related_show ) ) ) { + $adjacent_post = $show_post; + break; + } + } + if ( $show_post->ID == $post->ID ) { + $found_current_post = true; + } } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - // --- adjacent post link --- - // (from get_adjacent_post_link) - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); + if ( $adjacent_post ) { - } + // --- adjacent post title --- + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + // --- adjacent post link --- + // (from get_adjacent_post_link) + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + } + + } } return $output; diff --git a/readme.md b/readme.md index c5b0e8f..f78690d 100644 --- a/readme.md +++ b/readme.md @@ -194,9 +194,6 @@ You may translate the plugin into another language. Please visit our [WordPress #### 2.3.3.2 * Minor Bugfix Update -#### 2.3.3.1 -* Minor Bugfix Update - #### 2.3.3 = * Important Bugfix Update * Fix to conflict with plugins using AJAX save_post calls diff --git a/readme.txt b/readme.txt index da895f8..69a78f1 100644 --- a/readme.txt +++ b/readme.txt @@ -184,6 +184,15 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == += 2.3.3.4 = +* Improved: auto-match show description to info height on Show pages +* Improved: allow multiple Related Show selection for single post +* Improved: ability to assign Post to relate to multiple Shows +* Added: Related Show Post List column and Quick Edit field +* Added: Related Show selection Bulk Edit Action for Post List +* Added: filters for label texts and title attributes on Show Page +* Added: filter for label text above Show Player (default empty) + = 2.3.3.3 = * Fixed: improved Current Show and Upcoming Shows calculations * (Display showtimes when show starts before and ends after midnight) diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 729db97..124e596 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -33,7 +33,6 @@ $show_link = get_post_meta( $post_id, 'show_link', true ); $show_email = get_post_meta( $post_id, 'show_email', true ); $show_patreon = get_post_meta( $post_id, 'show_patreon', true ); -$patreon_title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; // $show_rss = get_post_meta( $post_id, 'show_rss', true ); $show_rss = false; // TEMP @@ -62,7 +61,6 @@ $show_link = apply_filters( 'radio_station_show_link', $show_link, $post_id ); $show_email = apply_filters( 'radio_station_show_email', $show_email, $post_id ); $show_patreon = apply_filters( 'radio_station_show_patreon', $show_patreon, $post_id ); -$patreon_title = apply_filters( 'radio_station_show_patreon_title', $patreon_title, $post_id ); $show_rss = apply_filters( 'radio_station_show_rss', $show_rss, $post_id ); // --- create show icon display early --- @@ -70,37 +68,43 @@ $show_icons = array(); // --- show home link icon --- +// 2.3.3.4: added filter for title attribute if ( $show_link ) { - $title = esc_attr( __( 'Show Website', 'radio-station' ) ); + $title = __( 'Show Website', 'radio-station' ); + $title = apply_filters( 'radio_station_show_website_title', $title, $post_id ); $icon = ''; $icon = apply_filters( 'radio_station_show_home_icon', $icon, $post_id ); $show_icons['home'] = ''; } // --- email DJ / host icon --- +// 2.3.3.4: added filter for title attribute if ( $show_email ) { - $title = esc_attr( __( 'Email Show Host', 'radio-station' ) ); + $title = __( 'Email Show Host', 'radio-station' ); + $title = apply_filters( 'radio_station_show_email_title', $title, $post_id ); $icon = ''; $icon = apply_filters( 'radio_station_show_email_icon', $icon, $post_id ); $show_icons['email'] = ''; } // --- show RSS feed icon --- +// 2.3.3.4: added filter for title attribute if ( $show_rss ) { $feed_url = radio_station_get_show_rss_url( $post_id ); - $title = esc_attr( __( 'Show RSS Feed', 'radio-station' ) ); + $title = __( 'Show RSS Feed', 'radio-station' ); + $title = apply_filters( 'radio_station_show_rss_title', $title, $post_id ); $icon = ''; $icon = apply_filters( 'radio_station_show_rss_icon', $icon, $post_id ); $show_icons['rss'] = ''; @@ -192,7 +196,9 @@ $show_patreon_button = ''; if ( $show_patreon ) { $show_patreon_button .= '
    '; - $show_patreon_button .= radio_station_patreon_button( $show_patreon, $patreon_title ); + $title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; + $title = apply_filters( 'radio_station_show_patreon_title', $title, $post_id ); + $show_patreon_button .= radio_station_patreon_button( $show_patreon, $title ); $show_patreon_button .= '
    '; } // 2.3.1: added filter for patreon button @@ -201,9 +207,15 @@ // --- Show Player --- // 2.3.0: embed latest broadcast audio player + // 2.3.3.4: add filter for optional title above Show Player (default empty) + // 2.3.3.4: add filter for title text on Show Download link icon if ( $show_file ) { $blocks['show_images'] .= '
    '; + $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); + if ( $label && ( '' != $label ) ) { + $blocks['show_images'] .= '' . esc_html( $label ) . '
    '; + } $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; $player_embed = do_shortcode( $shortcode ); $blocks['show_images'] .= '
    '; @@ -214,6 +226,7 @@ // 2.3.2: check show download switch if ( $show_download ) { $title = __( 'Download Latest Broadcast', 'radio-station' ); + $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); $blocks['show_images'] .= '
    '; $blocks['show_images'] .= ''; $blocks['show_images'] .= ''; @@ -233,12 +246,20 @@ if ( $hosts || $producers || $genres || $languages ) { // --- show meta title --- - $blocks['show_meta'] = '

    ' . esc_html( __( 'Show Info', 'radio-station' ) ) . '

    '; + // 2.3.3.4: added filter for show info label + // 2.3.3.4: added class to show info label tag + $label = __( 'Show Info', 'radio-station' ); + $label = apply_filters( 'radio_station_show_info_label', $label, $post_id ); + $blocks['show_meta'] = '

    ' . esc_html( $label ) . '

    '; // --- Show DJs / Hosts --- - if ( $hosts ) { + // 2.3.3.4: added filter for hosted by label + // 2.3.3.4: replace bold title tag with span and class + if ( $hosts ) { $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( __( 'Hosted by', 'radio-station' ) ) . ': '; + $label = __( 'Hosted by', 'radio-station' ); + $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); + $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; $count = 0; $host_count = count( $hosts ); foreach ( $hosts as $host ) { @@ -267,9 +288,13 @@ // --- Show Producers --- // 2.3.0: added assigned producer display + // 2.3.3.4: added filter for produced by label + // 2.3.3.4: replace bold title tag with span and class if ( $producers ) { $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( __( 'Produced by', 'radio-station' ) ) . ': '; + $label = __( 'Produced by', 'radio-station' ); + $label = apply_filters( 'radio_station_show_producers_label', $label, $post_id ); + $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; $count = 0; $producer_count = count( $producers ); foreach ( $producers as $producer ) { @@ -298,6 +323,8 @@ // --- Show Genre(s) --- // 2.3.0: only display if genre assigned + // 2.3.3.4: added filter for genres label + // 2.3.3.4: replace bold title tag with span and class if ( $genres ) { $tax_object = get_taxonomy( RADIO_STATION_GENRES_SLUG ); if ( count( $genres ) == 1 ) { @@ -305,8 +332,9 @@ } else { $label = $tax_object->labels->name; } + $label = apply_filters( 'radio_station_show_genres_label', $label, $post_id ); $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; $genre_links = array(); foreach ( $genres as $genre ) { $genre_link = get_term_link( $genre ); @@ -318,6 +346,8 @@ // --- Show Language(s) --- // 2.3.0: only display if language is assigned + // 2.3.3.4: added filter for languages label + // 2.3.3.4: replace bold title tag with span and class if ( $languages ) { $tax_object = get_taxonomy( RADIO_STATION_LANGUAGES_SLUG ); if ( count( $languages ) == 1 ) { @@ -325,9 +355,9 @@ } else { $label = $tax_object->labels->name; } - + $label = apply_filters( 'radio_station_show_languages_label', $label, $post_id ); $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; $language_links = array(); foreach ( $languages as $language ) { $lang_label = $language->name; @@ -359,12 +389,16 @@ } // --- show times title --- -$blocks['show_times'] = '

    ' . esc_html( __( 'Show Times', 'radio-station' ) ) . '

    '; +$label = __( 'Show Times', 'radio-station' ); +$label = apply_filters( 'radio_station_show_times_label', $label, $post_id ); +$blocks['show_times'] = '

    ' . esc_html( $label ) . '

    '; // --- check if show is active and has shifts --- if ( !$active || !$shifts ) { - $blocks['show_times'] .= esc_html( __( 'Not Currently Scheduled.', 'radio-station' ) ); + $label = __( 'Not Currently Scheduled.', 'radio-station' ); + $label = apply_filters( 'radio_station_show_no_shifts_label', $label, $post_id ); + $blocks['show_times'] .= esc_html( $label ); } else { @@ -399,7 +433,10 @@ } // --- display timezone --- - $blocks['show_times'] .= '' . esc_html( __( 'Timezone', 'radio-station' ) ) . ': '; + // 2.3.3.4: added filter for timezone label + $label = __( 'Timezone', 'radio-station' ); + $label = apply_filters( 'radio_station_show_timezone_label', $label, $post_id ); + $blocks['show_times'] .= '' . esc_html( $label ) . ': '; if ( !isset( $timezone_code ) ) { $blocks['show_times'] .= esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset; } else { @@ -461,8 +498,9 @@ $class = implode( ' ', $classes ); // --- set show time output --- + // 2.3.4: fix to start data_format attribute $show_time = '
    '; - $show_time .= '' . esc_html( $start ) . ''; + $show_time .= '' . esc_html( $start ) . ''; $show_time .= ' - '; $show_time .= '' . esc_html( $end ) . ''; if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { @@ -491,11 +529,14 @@ } // --- * encore note --- + // 2.3.3.4: added filter for encore label if ( $found_encore ) { $blocks['show_times'] .= ''; $blocks['show_times'] .= '* '; $blocks['show_times'] .= ''; - $blocks['show_times'] .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); + $label = __( 'Encore Presentation', 'radio-station' ); + $label = apply_filters( 'radio_station_show_encore_label', $label, $post_id ); + $blocks['show_times'] .= esc_html( $label ); $blocks['show_times'] .= ''; $blocks['show_times'] .= ''; } @@ -504,12 +545,17 @@ } // --- maybe add link to full schedule page --- +// 2.3.3.4: added filters for schedule link title and anchor $schedule_page = radio_station_get_setting( 'schedule_page' ); if ( $schedule_page && !empty( $schedule_page ) ) { $schedule_link = get_permalink( $schedule_page ); $blocks['show_times'] .= ''; } @@ -529,8 +575,12 @@ $show_description = '
    ' . $content . '
    '; $show_description .= '
    '; $show_desc_buttons = '
    '; - $show_desc_buttons .= ' '; - $show_desc_buttons .= ' '; + $label = __( 'Show More', 'radio-station' ); + $label = apply_filters( 'radio_station_show_more_label', $label, $post_id ); + $show_desc_buttons .= ' '; + $label = __( 'Show Less', 'radio-station' ); + $label = apply_filters( 'radio_station_show_less_label', $label, $post_id ); + $show_desc_buttons .= ' '; $show_desc_buttons .= ' '; $show_desc_buttons .= '
    '; } @@ -541,12 +591,17 @@ if ( ( strlen( trim( $content ) ) > 0 ) || $show_posts || $show_playlists || $show_episodes ) { // --- About Show Tab (Post Content) --- + // 2.3.3.4: added filter for show description label and anchor $i = 0; if ( $show_description ) { $sections['about']['heading'] = ''; - $sections['about']['heading'] .= '

    ' . esc_html( __( 'About the Show', 'radio-station' ) ) . '

    '; - $sections['about']['anchor'] = __( 'About', 'radio-station' ); + $label = __( 'About the Show', 'radio-station' ); + $label = apply_filters( 'radio_station_show_description_label', $label, $post_id ); + $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $anchor = __( 'About', 'radio-station' ); + $anchor = apply_filters( 'radio_station_show_description_anchor', $anchor, $post_id ); + $sections['about']['anchor'] = $anchor; $sections['about']['content'] = '

    '; $sections['about']['content'] .= '
    '; @@ -558,11 +613,16 @@ } // --- Show Episodes Tab --- + // 2.3.3.4: added filter for show epiodes label and anchor if ( $show_episodes ) { $sections['episodes']['heading'] = ''; - $sections['episodes']['heading'] .= '

    ' . esc_html( __( 'Show Episodes', 'radio-station' ) ) . '

    '; - $sections['episodes']['anchor'] = __( 'Episodes', 'radio-station' ); + $label = __( 'Show Episodes', 'radio-station' ); + $label = apply_filters( 'radio_station_show_episodes_label', $label, $post_id ); + $sections['episodes']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $anchor = __( 'Episodes', 'radio-station' ); + $anchor = apply_filters( 'radio_station_show_episodes_anchor', $anchor, $post_id ); + $sections['episodes']['anchor'] = $anchor; $sections['episodes']['content'] = '

    '; $radio_station_data['show-episodes'] = $show_posts; @@ -574,11 +634,16 @@ } // --- Show Blog Posts Tab --- + // 2.3.3.4: added filter for show posts label and anchor if ( $show_posts ) { $sections['posts']['heading'] = ''; - $sections['posts']['heading'] .= '

    ' . esc_html( __( 'Show Posts', 'radio-station' ) ) . '

    '; - $sections['posts']['anchor'] = __( 'Posts', 'radio-station' ); + $label = __( 'Show Posts', 'radio-station' ); + $label = apply_filters( 'radio_station_show_posts_label', $label, $post_id ); + $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $anchor = __( 'Posts', 'radio-station' ); + $anchor = apply_filters( 'radio_station_show_posts_anchor', $anchor, $post_id ); + $sections['posts']['anchor'] = $anchor; $sections['posts']['content'] = '

    '; $radio_station_data['show-posts'] = $show_posts; @@ -590,11 +655,16 @@ } // --- Show Playlists Tab --- + // 2.3.3.4: added filter for show playlists label and anchor if ( $show_playlists ) { $sections['playlists']['heading'] = ''; - $sections['playlists']['heading'] .= '

    ' . esc_html( __( 'Show Playlists', 'radio-station' ) ) . '

    '; - $sections['playlists']['anchor'] = __( 'Playlists', 'radio-station' ); + $label = __( 'Show Playlists', 'radio-station' ); + $label = apply_filters( 'radio_station-show_playlists_label', $label, $post_id ); + $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $anchor = __( 'Playlists', 'radio-station' ); + $anchor = apply_filters( 'radio_station_show_playlists_anchor', $anchor, $post_id ); + $sections['playlists']['anchor'] = $anchor; $sections['playlists']['content'] = '

    '; $radio_station_data['show-playlists'] = $show_playlists; @@ -605,7 +675,7 @@ $i ++; } } -$sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id ); +$sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id, $post_id ); // --------------- @@ -647,9 +717,10 @@ } // --- Show Info Blocks --- + // 2.3.3.4: add show-info element ID to div tag ?> -
    +
    + $label = __( 'Latest Show Posts', 'radio-station' ); + $label = apply_filters( 'radio_station_show_latest_posts_label', $label, $post_id ); + ?>
    - +
    @@ -747,7 +820,7 @@ if ( ( $i + 1 ) < count( $sections ) ) { echo '
     
    '; } - $i ++; + $i++; } } ?> From 430061b39247b68a4af339bd2590ba25a0500e9d Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 29 Sep 2020 17:24:06 +1000 Subject: [PATCH 149/377] dev update 2.3.3.5 #257, #260, #269 --- CHANGELOG.md | 5 + css/rs-schedule.css | 1 + docs/Shortcodes.md | 2 +- includes/master-schedule.php | 182 ++++++++++++++++++---------- includes/post-types-admin.php | 40 ++++-- includes/support-functions.php | 6 +- loader.php | 1 + radio-station.php | 2 +- readme.txt | 5 + templates/master-schedule-list.php | 8 +- templates/master-schedule-table.php | 4 +- templates/master-schedule-tabs.php | 10 +- 12 files changed, 184 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6236628..488fd7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.3.5 +* Fixed: day left/right shifting on Schedule table/tab mobile views +* Added: past/today/future filter for Schedule Override List +* Added: filter for Schedule display start day (and to accept today) + ### 2.3.3.4 * Improved: auto-match show description to info height on Show pages * Improved: allow multiple Related Show selection for single post diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 6b330d9..c2b4c03 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -297,6 +297,7 @@ border-bottom: 0; } +#master-schedule-tabs .master-schedule-tabs-day.start-tab, #master-schedule-tabs .master-schedule-tabs-day.first-tab { margin-left: 0; } diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index a9e43a9..08fd784 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -37,7 +37,7 @@ The following attributes are available for the shortcode: * *show_encore* : Whether to display 'encore airing' for a show shift. 0 or 1. Default 1. * *show_file* : Whether to add a link to the latest audio file. 0 or 1. Default 0. * *days* : Display for single day or multiple days (string or 0-6, comma separated.) Default all. -* *start_day* : day of the week to start schedule (string or 0-6.) Default WordPress setting. +* *start_day* : day of the week to start schedule (string or 0-6, or 'today') Default WordPress setting. * *display_day* : Full or short day heading ('full' or 'short') Default short for Table, full for Tabs/List. * *display_date* : Date format for date subheading. 0 for none. Default 'jS' for Table/List, 0 for Tabs. * *display_month* : Full or short month subheading ('full', 'short') Default 'short'. diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 8a4140b..bc7628e 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -450,6 +450,7 @@ function radio_station_master_schedule_table_js() { // 2.3.2: added current show highlighting cycle // 2.3.2: fix to currenthour substr + // 2.3.3.5: change selected day and arrow logic (to single day shifting) $js = "/* Initialize Table */ jQuery(document).ready(function() { radio_table_responsive(false); @@ -499,17 +500,33 @@ function radio_times_highlight() { /* Make Table Responsive */ function radio_table_responsive(leftright) { - fallback = -1; selected = -1; - for (i = 0; i < 7; i++) { - if ((fallback < 0) && jQuery('.master-program-day.day-'+i).length) {fallback = i;} - if (jQuery('.master-program-day.day-'+i).hasClass('selected-day')) {selected = i;} + fallback = -1; selected = -1; foundtab = false; + if (!leftright || (leftright == 'left')) { + if (jQuery('.master-program-day.first-column').length) { + start = jQuery('.master-program-day.first-column'); + } else {start = jQuery('.master-program-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery('.master-program-day.last-column').length) { + end = jQuery('.master-program-day.last-column'); + } else {end = jQuery('.master-program-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); + } + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} } if (selected < 0) {selected = fallback;} + if (radio.debug) {console.log('Current Column: '+selected);} if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - jQuery('.master-program-day').removeClass('selected-day'); - jQuery('.master-program-day.day-'+selected).addClass('selected-day'); + if (!jQuery('.master-program-day.day-'+selected).length) { + while (!foundtab) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery('.master-program-day.day-'+selected).length) {foundtab = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} + } + } if (radio.debug) {console.log('Selected Column: '+selected);} totalwidth = jQuery('#master-program-hour-heading').width(); @@ -518,27 +535,47 @@ function radio_table_responsive(leftright) { tablewidth = jQuery('#master-program-schedule').width(); jQuery('#master-program-schedule').css('width','auto'); columns = 0; firstcolumn = -1; lastcolumn = 7; endtable = false; - for (i = 0; i < 7; i++) { - if ( !endtable && ((i + 1) > selected) ) { - if (firstcolumn < 0) { - if (i > 0) {jQuery('.master-program-day.day-'+i).addClass('first-column');} - firstcolumn = 0; - } else if (i < 6) {jQuery('.master-program-day.day-'+i).addClass('last-column');} + for (i = selected; i < 7; i++) { + if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} + else if (i < 6) {jQuery('.master-program-day.day-'+i).addClass('last-column');} jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).show(); colwidth = jQuery('.master-program-day.day-'+i).width(); totalwidth = totalwidth + colwidth; if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} jQuery('.master-program-day.day-'+i).removeClass('last-column'); if (totalwidth > tablewidth) { + if (radio.debug) {console.log('Hiding Column '+i);} jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).hide(); endtable = true; } else { - cwidth = jQuery('.master-program-day.day-'+i).width(); - totalwidth = totalwidth - (colwidth - cwidth); lastcolumn = i; columns++; + jQuery('.master-program-day.day-'+i).removeClass('last-column'); + totalwidth = totalwidth - colwidth + jQuery('.master-program-day.day-'+i).width(); + lastcolumn = i; columns++; } } } if (lastcolumn < 6) {jQuery('.master-program-day.day-'+lastcolumn).addClass('last-column');} + + if (leftright == 'right') { + for (i = (selected - 1); i > -1; i--) { + if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { + jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).show(); + colwidth = jQuery('.master-program-day.day-'+i).width(); + totalwidth = totalwidth + colwidth; + if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} + if (totalwidth > tablewidth) { + if (radio.debug) {console.log('Hiding Column '+i);} + jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).hide(); endtable = true; + } else { + jQuery('.master-program-day').removeClass('first-column'); + jQuery('.master-program-day.day-'+i).addClass('first-column'); + if (radio.debug) {console.log('Showing Tab '+i);} + columns++; + } + } + } + } jQuery('#master-program-schedule').css('width','100%'); } @@ -584,6 +621,7 @@ function radio_station_master_schedule_tabs_js() { // --- tabbed view responsiveness --- // 2.3.0: added for tabbed responsiveness // 2.3.2: display selected day message if outside view + // 2.3.3.5: change selected day and arrow logic (to single day shifting) $js .= "/* Initialize Tabs */ jQuery(document).ready(function() { radio.schedule_tabinit = false; @@ -643,66 +681,82 @@ function radio_show_highlight() { /* Make Tabs Responsive */ function radio_tabs_responsive(leftright) { - fallback = -1; selected = -1; - for (i = 0; i < 7; i++) { - if ((fallback < 0) && jQuery('.master-schedule-tabs-day.day-'+i).length) {fallback = i;} - if (jQuery('.master-schedule-tabs-day.day-'+i).hasClass('selected-day')) {selected = i;} + fallback = -1; selected = -1; foundtab = false; + if (!leftright || (leftright == 'left')) { + if (jQuery('.master-schedule-tabs-day.first-tab').length) { + start = jQuery('.master-schedule-tabs-day.first-tab'); + } else {start = jQuery('.master-schedule-tabs-day').first(); fallback = 0;} + classes = start.attr('class').split(' '); + } else if (leftright == 'right') { + if (jQuery('.master-schedule-tabs-day.last-tab').length) { + end = jQuery('.master-schedule-tabs-day.last-tab'); + } else {end = jQuery('.master-schedule-tabs-day').last(); fallback = 6;} + classes = end.attr('class').split(' '); + } + for (i = 0; i < classes.length; i++) { + if (classes[i].indexOf('day-') === 0) {selected = parseInt(classes[i].replace('day-',''));} } if (selected < 0) {selected = fallback;} - + if (radio.debug) {console.log('Current Tab: '+selected);} + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} - jQuery('.master-schedule-tabs-day').removeClass('selected-day'); - jQuery('.master-schedule-tabs-day.day-'+selected).addClass('selected-day'); + if (!jQuery('.master-schedule-tabs-day.day-'+selected).length) { + while (!foundtab) { + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (jQuery('.master-schedule-tabs-day.day-'+selected).length) {foundtab = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} + } + } + if (radio.debug) {console.log('Selected Tab: '+selected);} jQuery('#master-schedule-tabs').css('width','100%'); tabswidth = jQuery('#master-schedule-tabs').width(); jQuery('#master-schedule-tabs').css('width','auto'); - totalwidth = 0; columns = 0; firstcolumn = -1; lastcolumn = 7; endtabs = false; jQuery('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); - if (!leftright || (leftright == 'left')) { - for (i = 0; i < 7; i++) { - if ( !endtabs && ((i + 1) > selected) ) { - if (firstcolumn < 0) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firstcolumn = 0;} - else if (i < 6) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} - jQuery('.master-schedule-tabs-day.day-'+i).show(); - tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).width(); - marginleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); - marginright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); - totalwidth = totalwidth + tabwidth + marginleft + marginright; - if (radio.debug) {console.log(tabwidth+' - ('+marginleft+'/'+marginright+') - '+totalwidth+' / '+tabswidth);} + + totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; + for (i = selected; i < 7; i++) { + if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { + if ((i > 0) && (i == selected)) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} + else if (i < 6) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('last-tab');} + tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).show().width(); + mleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); + mright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); + totalwidth = totalwidth + tabwidth + mleft + mright; + if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} + if (totalwidth > tabswidth) { + if (radio.debug) {console.log('Hiding Tab '+i);} + jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; + } else { jQuery('.master-schedule-tabs-day.day-'+i).removeClass('last-tab'); - if (totalwidth > tabswidth) {jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true;} - else { - twidth = jQuery('.master-schedule-tabs-day.day-'+i).width(); - totalwidth = totalwidth - (tabwidth - twidth); lastcolumn = i; columns++; - } + totalwidth = totalwidth - tabwidth + jQuery('.master-schedule-tabs-day.day-'+i).width(); + if (radio.debug) {console.log('Showing Tab '+i);} + lasttab = i; tabs++; } - } - if (lastcolumn < 6) {jQuery('.master-schedule-tabs-day.day-'+lastcolumn).addClass('last-tab');} - } + } + } + if (lasttab < 6) {jQuery('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} + if (leftright == 'right') { - for (i = 6; i > -1; i--) { - if ( !endtabs && ((i + 1) > selected) ) { - if (jQuery('.master-schedule-tabs-day').length) { - if (lastcolumn > 6) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('last-tab'); lastcolumn = i;} - else if (i > 1) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab');} - jQuery('.master-schedule-tabs-day.day-'+i).show(); - tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).width(); - marginleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); - marginright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); - totalwidth = totalwidth + tabwidth + marginleft + marginright; - if (radio.debug) {console.log(tabwidth+' - ('+marginleft+'/'+marginright+') - '+totalwidth+' / '+tabswidth);} - jQuery('.master-schedule-tabs-day.day-'+i).removeClass('first-tab'); - if (totalwidth > tabswidth) {jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true;} - else { - twidth = jQuery('.master-schedule-tabs-day.day-'+i).width(); - totalwidth = totalwidth - (tabwidth - twidth); firstcolumn = i; columns++; - } - } + for (i = (selected - 1); i > -1; i--) { + if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { + tabwidth = jQuery('.master-schedule-tabs-day.day-'+i).show().width(); + mleft = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-left').replace('px','')); + mright = parseInt(jQuery('.master-schedule-tabs-day.day-'+i).css('margin-right').replace('px','')); + totalwidth = totalwidth + tabwidth + mleft + mright; + if (radio.debug) {console.log(tabwidth+' - ('+mleft+'/'+mright+') - '+totalwidth+' / '+tabswidth);} + if (totalwidth > tabswidth) { + if (radio.debug) {console.log('Hiding Tab '+i);} + jQuery('.master-schedule-tabs-day.day-'+i).hide(); endtabs = true; + } else { + jQuery('.master-schedule-tabs-day').removeClass('first-tab'); + jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); + if (radio.debug) {console.log('Showing Tab '+i);} + tabs++; + } } } - if (firstcolumn > 0) {jQuery('.master-schedule-tabs-day.day-'+firstcolumn).addClass('first-tab');} } jQuery('#master-schedule-tabs').css('width','100%'); @@ -714,7 +768,7 @@ function radio_tabs_responsive(leftright) { } } jQuery('.master-schedule-tabs-selected').hide(); - if ( activeday && ( (activeday > lastcolumn) || (activeday < firstcolumn ) ) ) { + if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { jQuery('#master-schedule-tabs-selected-'+activeday).show(); } @@ -722,9 +776,9 @@ function radio_tabs_responsive(leftright) { console.log('Active Day: '+activeday); console.log('Selected: '+selected); console.log('Fallback: '+fallback); - console.log('First Column: '+firstcolumn); - console.log('Last Column: '+lastcolumn); - console.log('Visible Columns: '+columns); + console.log('First Tab: '+firsttab); + console.log('Last Tab: '+lasttab); + console.log('Visible Tabs: '+tabs); } } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index c604c62..fb153b8 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1287,6 +1287,11 @@ function radio_station_post_save_data( $post_id ) { add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_post', 10, 2 ); function radio_station_quick_edit_post( $column_name, $post_type ) { + // 2.3.3.5: added fix for post type context + if ( $post_type != 'post' ) { + return; + } + $args = array( 'numberposts' => - 1, 'offset' => 0, @@ -4028,8 +4033,7 @@ function radio_station_override_date_filter( $post_type, $which ) { $datetime = radio_station_to_time( $override ); $month = radio_station_get_time( 'm', $datetime ); $year = radio_station_get_time( 'Y', $datetime ); - $months[$year . $month]['year'] = $year; - $months[$year . $month]['month'] = $month; + $months[$year . '-' . $month] = array( 'year' => $year, 'month' => $month ); } } else { return; @@ -4069,10 +4073,12 @@ function radio_station_override_past_future_filter( $post_type, $which ) { $pastfuture = apply_filters( 'radio_station_overrides_past_future_default', $pastfuture ); // --- past / future override selector --- + // 2.3.3.5: added option for today filtering echo ''; echo ''; @@ -4167,8 +4173,10 @@ function radio_station_columns_query_filter( $query ) { // need to loop and sync a separate meta key to enable orderby sorting // (not really efficient but at least it makes it possible!) // ...but could be improved by checking against postmeta table + // 2.3.3.5: use wpdb prepare method on query global $wpdb; - $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; + $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = %s"; + $overridequery = $wpdb->prepare( $overridequery, RADIO_STATION_OVERRIDE_SLUG ); $results = $wpdb->get_results( $overridequery, ARRAY_A ); if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { @@ -4190,8 +4198,8 @@ function radio_station_columns_query_filter( $query ) { // --- apply override year/month filtering --- if ( isset( $_GET['month'] ) && ( '0' != $_GET['month'] ) ) { $yearmonth = $_GET['month']; - $start_date = date( $yearmonth . '01' ); - $end_date = date( $yearmonth . 't' ); + $start_date = date( $yearmonth . '-01' ); + $end_date = date( $yearmonth . '-t' ); $meta_query = array( 'key' => 'show_override_date', 'value' => array( $start_date, $end_date ), @@ -4203,18 +4211,28 @@ function radio_station_columns_query_filter( $query ) { // --- meta query for past / future overrides filter --- // 2.3.3: added past future query prototype code - $valid = array( 'past', 'future' ); + // 2.3.3.5: added option for today selection + $valid = array( 'past', 'today', 'future' ); if ( isset( $_GET['pastfuture'] ) && in_array( $_GET['pastfuture'], $valid ) ) { + + $date = date( 'Y-m-d', time() ); + $yesterday = date( 'Y-m-d', time() - ( 24 * 60 * 60 ) ); + $tomorrow = date( 'Y-m-d', time() + ( 24 * 60 * 60 ) ); $pastfuture = $_GET['pastfuture']; - if ( 'past' == $pastfuture ) { + if ( 'today' == $pastfuture ) { + $compare = 'BETWEEN'; + $value = array( $yesterday, $tomorrow ); + } elseif ( 'past' == $pastfuture ) { $compare = '<'; + $value = $date; } elseif ( 'future' == $pastfuture ) { - $compare = '>='; + $compare = '>'; + $value = $date; } - $date = date( 'Y-m-d', time() ); + $pastfuture_query = array( 'key' => 'show_override_date', - 'value' => $date, + 'value' => $value, 'compare' => $compare, 'type' => 'DATE', ); diff --git a/includes/support-functions.php b/includes/support-functions.php index fa9dd2e..32546ca 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -3457,8 +3457,12 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { 'Saturday', ); - // 2.3.2: accept string format for weekstart + // 2.3.2: also accept string format for weekstart if ( is_string( $weekstart ) ) { + // 2.3.3.5: accept today as valid week start + if ( 'today' == $weekstart ) { + $today = radio_station_get_time( 'day' ); + } foreach ( $weekdays as $i => $weekday ) { if ( strtolower( $weekday ) == strtolower( $weekstart ) ) { $weekstart = $i; diff --git a/loader.php b/loader.php index 5987e71..99c21e2 100644 --- a/loader.php +++ b/loader.php @@ -2789,6 +2789,7 @@ function radio_station_settings_row( $option, $setting ) { // ========= // == 1.1.4 == +// - fix for debug prefix key dash // - fix for weird get_settings glitch bug (isset failing?!) // == 1.1.3 == diff --git a/radio-station.php b/radio-station.php index ecb1084..7d4c8b3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.4 +Version: 2.3.3.5 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station diff --git a/readme.txt b/readme.txt index 69a78f1..74b36d8 100644 --- a/readme.txt +++ b/readme.txt @@ -184,6 +184,11 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == += 2.3.3.5 = +* Fixed: day left/right shifting on Schedule table/tab mobile views +* Added: past/today/future filter for Schedule Override List +* Added: filter for Schedule display start day (and to accept today) + = 2.3.3.4 = * Improved: auto-match show description to info height on Show pages * Improved: allow multiple Related Show selection for single post diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index d15d20f..fbc7438 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -26,12 +26,15 @@ if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); } else { + // 2.3.3.5: add filter for changing start day (to accept 'today') + $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'list' ); $weekdays = radio_station_get_schedule_weekdays(); } $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- filter show avatar size --- -$avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'tabs' ); +// 2.3.3.5: fix to incorrect context value (tabs) +$avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'list' ); // --- filter excerpt length and more --- if ( $atts['show_desc'] ) { @@ -262,7 +265,8 @@ } } - $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'tabs' ); + // 2.3.3.5: fix to incorrect context value (tabs) + $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'list' ); if ( $hosts ) { $output .= '
    '; $output .= $hosts; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 2c68761..ea34aca 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -27,7 +27,9 @@ if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); } else { - $weekdays = radio_station_get_schedule_weekdays(); + // 2.3.3.5: add filter for changing start day (to accept 'today') + $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'table' ); + $weekdays = radio_station_get_schedule_weekdays( $start_day ); } $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index a581d1c..856b431 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -28,7 +28,9 @@ if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); } else { - $weekdays = radio_station_get_schedule_weekdays(); + // 2.3.3.5: add filter for changing start day (to accept 'today') + $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'tabs' ); + $weekdays = radio_station_get_schedule_weekdays( $start_day ); } $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); @@ -46,6 +48,7 @@ $panels = ''; $tcount = 0; +$start_tab = false; // 2.3.0: loop weekdays instead of legacy master list foreach ( $weekdays as $i => $weekday ) { @@ -110,6 +113,11 @@ // --- set tab classes --- $weekdate = $weekdates[$weekday]; $classes = array( 'master-schedule-tabs-day', 'day-' . $i ); + // 2.3.3.5: add extra class for starting tab + if ( !$start_tab ) { + $classes[] = 'start-tab'; + $start_tab = true; + } if ( $weekdate == $date ) { // $classes[] = 'selected-day'; $classes[] = 'current-day'; From 865897b51689d241bca66e4bc9944f4138ddb0c7 Mon Sep 17 00:00:00 2001 From: majick777 Date: Wed, 30 Sep 2020 11:22:52 +1000 Subject: [PATCH 150/377] today start hotfix #257 --- includes/support-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index 32546ca..12d52ed 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -3461,7 +3461,7 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { if ( is_string( $weekstart ) ) { // 2.3.3.5: accept today as valid week start if ( 'today' == $weekstart ) { - $today = radio_station_get_time( 'day' ); + $weekstart = radio_station_get_time( 'day' ); } foreach ( $weekdays as $i => $weekday ) { if ( strtolower( $weekday ) == strtolower( $weekstart ) ) { From 7305afb3a2f8b679fb4f9588c529d63d0cc11af9 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 19 Oct 2020 12:56:19 +1000 Subject: [PATCH 151/377] override + start_day hotfix --- CHANGELOG.md | 2 + includes/data-feeds.php | 43 +++-- includes/shortcodes.php | 118 ++++++------ includes/support-functions.php | 279 +++++++++++++++++----------- readme.txt | 2 + templates/master-schedule-list.php | 22 ++- templates/master-schedule-table.php | 34 ++-- templates/master-schedule-tabs.php | 33 ++-- 8 files changed, 315 insertions(+), 218 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 488fd7c..bed46eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.3.3.5 +* Fixed: use schedule based on start_day if specified for Schedule view * Fixed: day left/right shifting on Schedule table/tab mobile views * Added: past/today/future filter for Schedule Override List * Added: filter for Schedule display start day (and to accept today) +* Added: current playlist (if any) to Broadcast Data endpoint ### 2.3.3.4 * Improved: auto-match show description to info height on Show pages diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 86c86a6..9869718 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -108,7 +108,7 @@ function radio_station_add_feed_query_vars( $query_vars ) { $query_vars[] = $var; } } - + return $query_vars; } @@ -134,7 +134,7 @@ function radio_station_get_station_data() { // $stream_format = radio_station_get_stream_format(); // $fallback_url = radio_station_get_fallback_url(); // $fallback_format = radio_station_get_fallback_format(); - + $station_url = radio_station_get_station_url(); $schedule_url = radio_station_get_schedule_url(); $language = radio_station_get_language(); @@ -142,7 +142,7 @@ function radio_station_get_station_data() { // 2.3.2: use get date function with timezone $now = radio_station_get_now(); $date_time = radio_station_get_time( 'datetime', $now ); - + // 2.3.2: get schedule last updated time $updated = get_option( 'radio_station_schedule_updated' ); if ( !$updated ) { @@ -190,21 +190,28 @@ function radio_station_get_broadcast_data() { // print_r( $current_show ); $current_show = radio_station_convert_show_shift( $current_show ); // print_r( $current_show ); - + // --- get next show --- $next_show = radio_station_get_next_show(); // print_r( $next_show ); $next_show = radio_station_convert_show_shift( $next_show ); // print_r( $next_show ); - // TODO: maybe get now playing playlist ? - // $current_playlist = radio_station_current_playlist(); + // 2.3.3.5: just in case transients are the same + if ( $current_show == $next_show ) { + $now = radio_station_get_time(); + $next_show = radio_station_get_next_show( $now ); + $next_show = radio_station_convert_show_shift( $next_show ); + } + + // 2.3.3.5: added current playlist to broadcast data + $current_playlist = radio_station_get_current_playlist(); // --- return broadcast info --- $broadcast = array( - 'current_show' => $current_show, - 'next_show' => $next_show, - // 'current_playlist' => $current_playlist, + 'current_show' => $current_show, + 'next_show' => $next_show, + 'current_playlist' => $current_playlist, ); $broadcast = apply_filters( 'radio_station_broadcast_data', $broadcast ); @@ -546,7 +553,7 @@ function radio_station_route_broadcast( $request ) { echo "Broadcast: " . print_r( $broadcast_data, true ); } - // --- set broadcast output --- + // --- set broadcast output --- $broadcast = array( 'broadcast' => $broadcast_data ); $broadcast = radio_station_add_station_data( $broadcast ); $broadcast['endpoints'] = radio_station_get_route_urls(); @@ -572,9 +579,9 @@ function radio_station_route_schedule( $request ) { $weekdays = array(); $weekday = $singular = $multiple = false; if ( isset( $_GET['weekday'] ) ) { - + $weekday = $_GET['weekday']; - + if ( strstr( $_GET['weekday'], ',' ) ) { $multiple = true; $weekdays = explode( ',', $weekday ); @@ -872,7 +879,7 @@ function radio_station_add_feeds() { || !array_key_exists( $feedrule, $rewrite_rules ) ) { flush_rewrite_rules( false ); } - + } // ------------- @@ -926,7 +933,7 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { $radio = array( 'success' => true ); $radio['namespace'] = $base; $radio['endpoints'] = radio_station_get_feed_urls(); - + // --- reflect route format used in REST API --- $routes = array(); foreach ( $radio['endpoints'] as $endpoint => $url ) { @@ -1001,7 +1008,7 @@ function radio_station_feed_broadcast( $comment_feed, $feed_name ) { if ( RADIO_STATION_DEBUG ) { echo "Broadcast: " . print_r( $broadcast, true ); } - + $broadcast = array( 'broadcast', $broadcast ); $broadcast = radio_station_add_station_data( $broadcast ); $broadcast['endpoints'] = radio_station_get_feed_urls(); @@ -1035,9 +1042,9 @@ function radio_station_feed_schedule( $comment_feed, $feed_name ) { $weekdays = array(); $weekday = $singular = $multiple = false; if ( isset( $_GET['weekday'] ) ) { - + $weekday = $_GET['weekday']; - + if ( strstr( $_GET['weekday'], ',' ) ) { $multiple = true; $weekdays = explode( ',', $weekday ); @@ -1313,7 +1320,7 @@ function radio_station_format_xml( $data ) { // -------------------- // TODO: fix unworking array to XML conversion? function radio_station_array_to_xml( SimpleXMLElement $object, $data ) { - + foreach ( $data as $key => $value ) { if ( is_array( $value ) ) { $newobject = $object->addChild( $key ); diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 5992555..075cdd3 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -42,14 +42,14 @@ function radio_station_timezone_shortcode( $atts = array() ) { global $radio_station_data; - + // --- set shortcode instance --- if ( isset( $radio_station_data['timezone_instance'] ) ) { $instance = $radio_station_data['timezone_instance']; } else { $instance = $radio_station_data['timezone_instance'] = 0; } - + // --- get radio timezone values --- $timezone = radio_station_get_setting( 'timezone_location' ); if ( !$timezone || ( '' == $timezone ) ) { @@ -119,7 +119,7 @@ function radio_station_timezone_shortcode( $atts = array() ) { if ( '' != $select ) { $output .= $select; } - + $output .= '
    '; // --- enqueue shortcode styles --- @@ -201,7 +201,7 @@ function radio_station_clock_shortcode( $atts = array() ) { $classes[] = 'zone'; } - // -- open clock div --- + // -- open clock div --- $classlist = implode( ' ', $classes ); $clock = '
    '; @@ -231,11 +231,11 @@ function radio_station_clock_shortcode( $atts = array() ) { if ( '' != $select ) { $clock .= $select; } - + $clock .= '
    '; - + // --- enqueue shortcode styles --- - radio_station_enqueue_style( 'shortcodes' ); + radio_station_enqueue_style( 'shortcodes' ); // --- enqueue clock javascript --- radio_station_enqueue_script( 'radio-station-clock', array(), true ); @@ -391,7 +391,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- process playlist taxonomy query --- if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { - + // --- check assigned show has a specified genre term --- if ( !empty( $atts['genre'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { if ( is_array( $atts['genre'] ) ) {$genres = $atts['genre'];} @@ -406,7 +406,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { if ( in_array( $show_genre->term_id, $genres ) || in_array( $show_genre->slug, $genres) ) { $found_genre = true; } - } + } } } if ( !$found_genre ) { @@ -414,7 +414,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } } } - + // --- check assigned show has a specified language term --- if ( !empty( $atts['language'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { if ( is_array( $atts['language'] ) ) {$languages = $atts['language'];} @@ -446,7 +446,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $datetime = get_post_meta( $archive_post->ID, 'show_override_sched', true ); if ( $datetime || !is_array( $datetime ) || ( count( $datetime ) == 0 ) ) { unset( $archive_posts[$i] ); - } + } } } } @@ -462,7 +462,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $start_data_format = 'j, ' . $start_data_format; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $type . '-archive', $atts ); $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $type . '-archive', $atts ); - + // --- check for results --- $list = '
    '; if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { @@ -524,7 +524,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- display Override date(s) --- if ( ( RADIO_STATION_OVERRIDE_SLUG == $type ) && ( $atts['show_dates'] ) ) { $datetime = get_post_meta( $archive_post->ID, 'show_override_sched', true ); - + // 2.3.1: fix to append not echo override date to archive list $list .= "
    "; @@ -574,24 +574,24 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // $languages = wp_get_post_terms( $archive_post->ID, RADIO_STATION_LANGUAGES_SLUG ); // } // } - + // TODO: playlist tracks / track count ? // if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { // $tracks = get_post_meta( $archive_post->ID, 'playlist', true ); // $track_count = count( $tracks ); // } - + // TODO: display episode / host / producer meta ? // if ( defined( 'RADIO_STATION_PRO_EPISODE_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_episode_archive_meta', '' ); - // } + // $list .= apply_filters( 'radio_station_episode_archive_meta', '' ); + // } // if ( defined( 'RADIO_STATION_PRO_HOST_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_host_archive_meta', '' ); + // $list .= apply_filters( 'radio_station_host_archive_meta', '' ); // } // if ( defined( 'RADIO_STATION_PRO_PRODUCER_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_producer_archive_meta', '' ); + // $list .= apply_filters( 'radio_station_producer_archive_meta', '' ); // } - + // --- description --- if ( 'none' == $atts['description'] ) { @@ -807,7 +807,7 @@ function radio_station_genre_archive_list( $atts ) { $list .= '
    '; $list .= $genre_image; $list .= '
    '; - } + } // --- genre title --- $list .= '

    '; @@ -874,7 +874,7 @@ function radio_station_genre_archive_list( $atts ) { // TODO: show description // if ( $atts['show_desc' ] ) { // - // } + // } $list .= '

  • '; } @@ -1318,7 +1318,7 @@ function radio_station_current_show_shortcode( $atts ) { foreach ( $atts as $key => $value ) { $value = radio_station_encode_uri_component( $value ); $html .= "&" . esc_js( $key ) . "=" . esc_js( $value ); - } + } $html .= "'; "; $html .= "document.getElementById('rs-current-show-" . esc_attr( $instance ) ."-loader').src = url;"; $html .= ""; @@ -1362,7 +1362,7 @@ function radio_station_current_show_shortcode( $atts ) { } else { $current_shift = radio_station_get_current_show(); } - + // --- open shortcode div wrapper --- if ( !$atts['widget'] ) { @@ -1434,7 +1434,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- check show schedule --- // 2.3.1: check early for later display if ( $atts['show_sched'] || $atts['show_all_sched'] ) { - + $shift_display = '
    '; // --- show times subheading --- @@ -1488,7 +1488,7 @@ function radio_station_current_show_shortcode( $atts ) { // 2.2.2: translate weekday for display // 2.3.0: use dates for reliability // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first + // 2.3.2: fix to conver to 24 hour format first $start_time = radio_station_convert_shift_time( $start ); $end_time = radio_station_convert_shift_time( $end ); if ( isset( $shift['real_start'] ) ) { @@ -1512,7 +1512,7 @@ function radio_station_current_show_shortcode( $atts ) { if ( RADIO_STATION_DEBUG ) { echo ''; - + echo ''; } @@ -1523,7 +1523,7 @@ function radio_station_current_show_shortcode( $atts ) { $current_shift_end = $shift_end_time; $classes[] = 'current-shift'; $class = implode( ' ', $classes ); - + $current_shift_display = '
    '; $current_shift_display .= '' . esc_html( $start ) . ''; $current_shift_display .= ' - '; @@ -1548,7 +1548,7 @@ function radio_station_current_show_shortcode( $atts ) { $shift_display .= '
    '; } - + // --- set show title output --- $title = '
    '; if ( $show_link ) { @@ -1609,9 +1609,14 @@ function radio_station_current_show_shortcode( $atts ) { $host_link = radio_station_get_host_url( $host ); $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); - $html['hosts'] .= ''; + // 2.3.3.5: only wrap with tags if there is a link + if ( $host_link ) { + $html['hosts'] .= ''; + } $html['hosts'] .= esc_html( $user->display_name ); - $html['hosts'] .= ''; + if ( $host_link ) { + $html['hosts'] .= ''; + } } else { $html['hosts'] .= esc_html( $user->display_name ); } @@ -1730,7 +1735,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- hidden inputs for current shift time --- $output .= ''; - + if ( RADIO_STATION_DEBUG ) { $output .= ''; $output .= 'Now: ' . date( 'Y-m-d H:i:s', $now ) . ' (' . esc_attr( $now ) . ')' . PHP_EOL; @@ -1786,12 +1791,12 @@ function radio_station_current_show() { // --- send to parent window --- $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; $js .= "parent.document.getElementById('rs-current-show-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; - + // --- maybe restart countdowns --- if ( $atts['countdown'] ) { $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; } - + } // --- filter load script --- @@ -1801,7 +1806,7 @@ function radio_station_current_show() { if ( '' != $js ) { echo ""; } - + exit; } @@ -1909,7 +1914,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'upcoming-shows', $atts['widget'] ); if ( 'on' == $atts['ajax'] ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { - + // --- AJAX load via iframe --- $ajax_url = admin_url( 'admin-ajax.php' ); $instance = $radio_station_data['upcoming_shows_instance']; @@ -1925,7 +1930,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { foreach ( $atts as $key => $value ) { $value = radio_station_encode_uri_component( $value ); $html .= "&" . esc_js( $key ) . "=" . esc_js( $value ); - } + } $html .= "'; "; $html .= "document.getElementById('rs-upcoming-shows-" . esc_attr( $instance ) ."-loader').src = url;"; $html .= ""; @@ -2007,7 +2012,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { if ( 24 == (int) $atts['time'] ) { $start_data_format = $end_data_format = 'H:i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g:i a'; } $start_data_format = 'l, ' . $start_data_format; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'upcoming-shows', $atts ); @@ -2061,7 +2066,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $start = $shift['start']; } else { $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - } + } if ( isset( $shift['real_end'] ) ) { $end = $shift['real_end']; } elseif ( isset( $shift['end'] ) ) { @@ -2182,9 +2187,14 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $host_link = radio_station_get_host_url( $host ); $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); - $html['hosts'] .= ''; + // 2.3.3.5: only wrap with tags if there is a link + if ( $host_link ) { + $html['hosts'] .= ''; + } $html['hosts'] .= esc_html( $user->display_name ); - $html['hosts'] .= ''; + if ( $host_link ) { + $html['hosts'] .= ''; + } } else { $html['hosts'] .= esc_html( $user->display_name ); } @@ -2212,7 +2222,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- countdown timer display --- if ( ( 0 == $i ) && isset( $next_start_time ) && $atts['countdown'] ) { - $html['countdown'] = '
    '; + $html['countdown'] = '
    '; } // --- output show schedule --- @@ -2290,7 +2300,7 @@ function radio_station_upcoming_shows() { if ( RADIO_STATION_DEBUG ) { print_r( $atts ); } - + // --- output widget contents --- echo '
    '; echo radio_station_upcoming_shows_shortcode( $atts ); @@ -2302,7 +2312,7 @@ function radio_station_upcoming_shows() { // --- send to parent window --- $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; $js .= "parent.document.getElementById('rs-upcoming-shows-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; - + // --- restart countdowns --- if ( $atts['countdown'] ) { $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; @@ -2317,7 +2327,7 @@ function radio_station_upcoming_shows() { if ( '' != $js ) { echo ""; } - + exit; } @@ -2345,7 +2355,7 @@ function radio_station_current_playlist_shortcode( $atts ) { $ajax = radio_station_get_setting( 'ajax_widgets' ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', 0, $atts ); - + // --- get shortcode attributes --- // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute @@ -2401,7 +2411,7 @@ function radio_station_current_playlist_shortcode( $atts ) { $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'current-playlist', $atts['widget'] ); if ( 'on' == $atts['ajax'] ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { - + // --- AJAX load via iframe --- $ajax_url = admin_url( 'admin-ajax.php' ); $instance = $radio_station_data['current_playlist_instance']; @@ -2414,7 +2424,7 @@ function radio_station_current_playlist_shortcode( $atts ) { foreach ( $atts as $key => $value ) { $value = radio_station_encode_uri_component( $value ); $html .= "&" . esc_js( $key ) . "=" . esc_js( $value ); - } + } $html .= "'; "; $html .= "document.getElementById('rs-current-playlist-" . esc_attr( $instance ) ."-loader').src = url;"; $html .= ""; @@ -2640,7 +2650,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); - + return $output; } @@ -2654,19 +2664,19 @@ function radio_station_current_playlist() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-playlist' ); - + // --- output widget contents --- echo '
    '; echo radio_station_current_playlist_shortcode( $atts ); echo '
    '; - + $js = ''; if ( isset( $atts['instance'] ) ) { // --- send to parent window --- $js .= "widget = document.getElementById('widget-contents').innerHTML;" . PHP_EOL; $js .= "parent.document.getElementById('rs-current-playlist-" . esc_js( $atts['instance'] ) . "').innerHTML = widget;" . PHP_EOL; - + // --- restart countdowns --- if ( $atts['countdown'] ) { $js .= "setTimeout(function() {parent.radio_countdown();}, 2000);" . PHP_EOL; @@ -2681,7 +2691,7 @@ function radio_station_current_playlist() { if ( '' != $js ) { echo ""; } - + exit; } @@ -2701,7 +2711,7 @@ function radio_station_countdown_enqueue() { radio.labels.showended = '" . esc_js( __( 'This Show has ended.', 'radio-station' ) ) . "'; radio.labels.playlistended = '" . esc_js( __( 'This Playlist has ended.', 'radio-station') ) . "'; radio.labels.timecommencing = '" . esc_js( __( 'Commencing in', 'radio-station' ) ) . "'; - radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "'; + radio.labels.timeremaining = '" . esc_js( __( 'Remaining Time', 'radio-station' ) ) . "'; "; // --- add script inline --- diff --git a/includes/support-functions.php b/includes/support-functions.php index 12d52ed..66835a9 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -21,6 +21,7 @@ // - Get Previous Show // - Get Next Show // - Get Next Shows +// - Get Current Playlist // - Get Blog Posts for Show // - Get Playlists for Show // - Get Genre @@ -166,10 +167,10 @@ function radio_station_get_show_schedule( $show_id ) { if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { $changed = false; foreach ( $shifts as $i => $shift ) { - + // --- check for unique ID length --- if ( strlen( $i ) != 8 ) { - + // --- generate unique shift ID --- unset( $shifts[$i] ); $unique_id = radio_station_unique_shift_id(); @@ -177,14 +178,14 @@ function radio_station_get_show_schedule( $show_id ) { $changed = true; } } - + // --- update shifts to save unique ID indexes --- if ( $changed ) { - update_post_meta( $show_id, 'show_sched', $shifts ); + update_post_meta( $show_id, 'show_sched', $shifts ); } } - - return $shifts; + + return $shifts; } // ------------------------ @@ -199,14 +200,14 @@ function radio_station_unique_shift_id() { $unique_id = wp_generate_password( 8, false, false ); if ( in_array( $unique_id, $shift_ids ) ) { while ( in_array( $unique_id, $shift_ids ) ) { - $unique_id = wp_generate_password( 8, false, false ); + $unique_id = wp_generate_password( 8, false, false ); } $shift_ids[] = $unique_id; } // --- store the unique shift ID --- update_option( 'radio_station_shifts_ids', $shift_ids ); - + return $unique_id; } @@ -264,7 +265,7 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, foreach ( $shows as $show ) { $shifts = radio_station_get_show_schedule( $show->ID ); - + if ( $shifts && is_array( $shifts) && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $i => $shift ) { @@ -369,9 +370,9 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, 'override' => false, ); } - + } else { - + // --- set the shift time as is --- // 2.3.2: added for non-split argument $all_shifts[$day][$start_time . '.' . $show->ID] = array( @@ -427,7 +428,7 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, } } $all_shifts = $sorted_shifts; - + // --- debug point --- if ( RADIO_STATION_DEBUG ) { $debug = "Sorted Shifts" . PHP_EOL . PHP_EOL . print_r( $sorted_shifts, true ); @@ -505,7 +506,7 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { $override_list = array(); foreach ( $overrides as $i => $override ) { $data = get_post_meta( $override['ID'], 'show_override_sched', true ); - + if ( $data ) { $date = $data['date']; if ( '' != $date ) { @@ -522,7 +523,7 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { // --- add the override data --- if ( $inrange ) { - + // 2.3.2: get day from date directly // $thisday = date( 'l', $date_time ); $day = date( 'l', strtotime( $date ) ); @@ -580,7 +581,7 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { // $nextdate = date( 'Y-m-d', $next_date_time ); $nextdate = radio_station_get_next_date( $date ); $nextday = radio_station_get_next_day( $day ); - + $override_data = array( 'override' => $override['ID'], 'name' => $override['post_title'], @@ -724,7 +725,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { return false; } - + // --- get post IDs from post meta --- $post_ids = array(); foreach ( $post_metas as $post_meta ) { @@ -751,7 +752,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // TODO: maybe get additional data for each data type ? // if ( $args['data'] && $results && is_array( $results ) && ( count( $results ) > 0 ) ) { // if ( 'posts' == $datatype ) { - // + // // } elseif ( 'playlists' == $datatype ) { // // } elseif ( 'episodes' == $datatype ) { @@ -858,7 +859,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' ); $thumbnail_url = $thumbnail[0]; } - + // --- create array and return --- // 2.3.1: added show avatar and image URLs $show_data = array( @@ -978,7 +979,7 @@ function radio_station_get_override_data_meta( $override ) { $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' ); $thumbnail_url = $thumbnail[0]; } - + // --- create array and return --- // 2.3.1: added show avatar and image URLs $override_data = array( @@ -1004,7 +1005,8 @@ function radio_station_get_override_data_meta( $override ) { // Get Current Schedule // -------------------- // 2.3.2: added optional time argument -function radio_station_get_current_schedule( $time = false ) { +// 2.3.3.5: added optional weekstart argument +function radio_station_get_current_schedule( $time = false, $weekstart = false ) { global $radio_station_data; @@ -1041,7 +1043,8 @@ function radio_station_get_current_schedule( $time = false ) { } else { $now = $time; } - $weekdays = radio_station_get_schedule_weekdays(); + // 2.3.3.5: add passthrough of optional week start argument + $weekdays = radio_station_get_schedule_weekdays( $weekstart ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // 2.3.1: add empty keys to ensure overrides are checked foreach ( $weekdays as $weekday ) { @@ -1118,7 +1121,7 @@ function radio_station_get_current_schedule( $time = false ) { $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { $override_end_time = $override_end_time + 1; - } + } // 2.3.2: fix for non-split overrides ending on midnight if ( $override_end_time < $override_start_time ) { $override_end_time = $override_end_time + ( 24 * 60 * 60 ); @@ -1126,7 +1129,7 @@ function radio_station_get_current_schedule( $time = false ) { // --- check for overlapped shift (if any) --- // 2.3.1 added check for shift count - if ( count( $shifts ) > 0 ) { + if ( count( $shifts ) > 0 ) { foreach ( $shifts as $start => $shift ) { // 2.3.2: replace strtotime with to_time for timezones @@ -1155,13 +1158,13 @@ function radio_station_get_current_schedule( $time = false ) { if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { // 2.3.1: track done overrides - $done_overrides[] = $override['date'] . '--' . $i; + $done_overrides[] = $override['date'] . '--' . $i; $show_shifts[$day][$override['start']] = $override; } // --- check when the shift ends --- - if ( ( $override_end_time > $end_time ) + if ( ( $override_end_time > $end_time ) || ( $override_end_time == $end_time ) ) { unset( $show_shifts[$day][$start] ); } elseif ( $override_end_time > $start_time ) { @@ -1183,7 +1186,7 @@ function radio_station_get_current_schedule( $time = false ) { $shift['start'] = $override['end']; $shift['trimmed'] = 'start'; $show_shifts[$day][$override['end']] = $shift; - } + } // elseif ( $override_end_time == $end_time ) { // --- remove exact override --- // do nothing, already overridden @@ -1206,7 +1209,7 @@ function radio_station_get_current_schedule( $time = false ) { $done_overrides[] = $date . '--' . $i; // --- partial shift after override ---- - if ( $override_end_time < $end_time ) { + if ( $override_end_time < $end_time ) { $shift['start'] = $override['end']; $shift['end'] = $end; $shift['trimmed'] = 'start'; @@ -1277,7 +1280,7 @@ function radio_station_get_current_schedule( $time = false ) { // --- loop all shifts to add show data --- $prev_shift = $set_prev_shift = $prev_shift_end = false; - foreach ( $show_shifts as $day => $shifts ) { + foreach ( $show_shifts as $day => $shifts ) { // 2.3.1: added check for shift count if ( count( $shifts ) > 0 ) { foreach ( $shifts as $start => $shift ) { @@ -1350,13 +1353,13 @@ function radio_station_get_current_schedule( $time = false ) { $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); $radio_station_data['previous_show_' . $time] = $prev_show; set_transient( 'radio_station_previous_show_' . $time, $prev_show, $expires ); - } + } } $expires = $shift_end_time - $now - 1; if ( $expires > 3600 ) { $expires = 3600; } - + // 2.3.2: set temporary transient if time is specified // 2.3.3: remove current show transient (being unreliable) /* if ( !$time ) { @@ -1398,7 +1401,7 @@ function radio_station_get_current_schedule( $time = false ) { // (so that it is not set as the next show) unset( $current_show['split'] ); - } + } // 2.3.2: change to logic to allow for no current show found if ( !isset( $next_show ) ) { @@ -1414,7 +1417,7 @@ function radio_station_get_current_schedule( $time = false ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - if ( isset( $current_show ) + if ( isset( $current_show ) || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { // --- set next show --- @@ -1551,7 +1554,7 @@ function radio_station_get_current_show( $time = false ) { $show_shifts = radio_station_get_current_schedule( $time ); } - // --- get current time --- + // --- get current time --- if ( $time ) { $now = $time; } else { @@ -1560,7 +1563,7 @@ function radio_station_get_current_show( $time = false ) { // --- get schedule for time --- // 2.3.3: use weekday name instead of number (w) - // 2.3.3: add fix to start from previous day + // 2.3.3: add fix to start from previous day $today = radio_station_get_time( 'l', $now ); $yesterday = radio_station_get_previous_day( $today ); $weekdays = radio_station_get_schedule_weekdays( $yesterday ); @@ -1605,7 +1608,7 @@ function radio_station_get_current_show( $time = false ) { } // --- recombine possible split shift to set current show --- $current_show = $shift; - + // 2.3.4: also set previous shift data if ( $prev_shift ) { $expires = $shift_end_time - $now - 1; @@ -1627,14 +1630,14 @@ function radio_station_get_current_show( $time = false ) { $current_show['start'] = $current_show['real_start']; } elseif ( isset( $current_show['real_end'] ) ) { $current_show['end'] = $current_show['real_end']; - } + } } */ } if ( RADIO_STATION_DEBUG ) { echo '' . PHP_EOL; } - + // 2.3.4: store previous shift $prev_shift = $shift; } @@ -1778,7 +1781,7 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = } else { $now = radio_station_get_now(); } - + // 2.3.2: use get time function with timezone // 2.3.2: fix to pass week day start as numerical (w) // 2.3.3: revert to passing week day start as day (l) @@ -1810,7 +1813,7 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = $shift_end = radio_station_convert_shift_time( $shift['end'] ); $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - + if ( RADIO_STATION_DEBUG ) { echo ''; echo 'Next? ' . $now . ' - ' . $shift_start_time . ' - ' . $shift_end_time . PHP_EOL; @@ -1827,24 +1830,24 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = // --- reset skip flag --- $skip = false; - + if ( $current_split ) { - + $skip = true; $current_split = false; - + } elseif ( isset( $shift['split'] ) && $shift['split'] ) { - + // --- dedupe for shifts split overnight --- if ( isset( $shift['real_end'] ) ) { $shift['end'] = $shift['real_end']; - $current_split = true; + $current_split = true; } elseif ( isset( $shift['real_start'] ) ) { // 2.3.3: skip this shift instead of setting // (because second half of current show!) $skip = true; } - + } else { // --- reset split shift flag --- $current_split = false; @@ -1858,9 +1861,9 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = if ( !isset( $next_show ) ) { $next_show = $shift; $next_expires = $shift_end_time - $now - 1; - + $next_show = apply_filters( 'radio_station_next_show', $next_show, $time ); - if ( !$time ) { + if ( !$time ) { $radio_station_data['next_show'] = $next_show; set_transient( 'radio_station_next_show', $next_show, $next_expires ); } else { @@ -1897,6 +1900,56 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = return $next_shows; } +// -------------------- +// Get Current Playlist +// -------------------- +// 2.3.3.5: added get current playlist function +function radio_station_get_current_playlist() { + + $current_show = radio_station_get_current_show(); + $show_id = $current_show['show']['id']; + $playlists = radio_station_get_show_playlists( $show_id ); + if ( !$playlists || !is_array( $playlists ) || ( count ( $playlists ) < 1 ) ) { + return false; + } + + $playlist_id = $playlists[0]['ID']; + $tracks = get_post_meta( $playlist_id, 'playlist', true ); + if ( !$tracks || !is_array( $tracks ) || ( count( $tracks ) < 1 ) ) { + return false; + } + + // --- split off tracks marked as queued --- + $entries = $queued = $played = array(); + foreach ( $tracks as $i => $track ) { + foreach ( $track as $key => $value ) { + unset( $track[$key] ); + $key = str_replace( 'playlist_entry_', '', $key ); + $track[$key] = $value; + $entries[$i] = $track; + } + if ( 'queued' == $entry['status'] ) { + $queued[] = $entry; + } elseif ( 'played' == $entry['status'] ) { + $played[] = $entry; + } + } + + // --- get the track list for display --- + $playlist = array( + 'tracks' => $entries, + 'queued' => $queued, + 'played' => $played, + 'latest' => array_pop( $entries ), + 'id' => $playlist_id, + 'url' => get_permalink( $playlist_id ), + 'show' => $show_id, + 'show_url' => get_permalink( $show_id ), + ); + + return $playlist; +} + // ----------------------- // Get Blog Posts for Show // ----------------------- @@ -2085,7 +2138,7 @@ function radio_station_check_shifts( $all_shifts ) { $now = radio_station_get_now(); $weekdays = radio_station_get_schedule_weekdays(); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - + $conflicts = $checked_shifts = array(); if ( count( $all_shifts ) > 0 ) { $prev_shift = $prev_prev_shift = false; @@ -2093,7 +2146,7 @@ function radio_station_check_shifts( $all_shifts ) { foreach ( $weekdays as $day ) { if ( isset( $all_shifts[$day] ) ) { - $shifts = $all_shifts[$day]; + $shifts = $all_shifts[$day]; // --- get previous and next days for comparisons --- // 2.3.2: fix to use week date schedule @@ -2173,7 +2226,7 @@ function radio_station_check_shifts( $all_shifts ) { $set_shift = true; } elseif ( $real_start_time == $prev_real_start_time ) { // - do not duplicate, already recorded - - $conflict = false; + $conflict = false; // - total overlap, check last updated post time - $updated = strtotime( $shift['updated'] ); $prev_updated = strtotime( $prev_shift['updated'] ); @@ -2314,7 +2367,7 @@ function radio_station_check_shifts( $all_shifts ) { } } } - + // --- set checked shifts for day --- // 2.3.2: set checked shifts with day key directly // $all_shifts[$day] = $checked_shifts; @@ -2324,7 +2377,7 @@ function radio_station_check_shifts( $all_shifts ) { // --- check last shift against first shift --- // 2.3.2: added for possible overlap (split by weekly schedule dates) if ( isset( $shift ) && ( $shift != $first_shift ) ) { - + // --- use days for next week to compare --- $shift_start = radio_station_convert_shift_time( $shift['start'] ); $shift_end = radio_station_convert_shift_time( $shift['end'] ); @@ -2335,19 +2388,19 @@ function radio_station_check_shifts( $all_shifts ) { $first_shift_start = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_start ); $first_shift_end = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_end ); - if ( RADIO_STATION_DEBUG ) { + if ( RADIO_STATION_DEBUG ) { echo 'Last Shift Start: ' . $shift['day'] . ' ' . $shift_start . ' - (' . $last_shift_start . ')' . PHP_EOL; echo 'First Shift End: ' . $first_shift['day'] . ' ' . $shift_end . ' - (' . $first_shift_end . ')' . PHP_EOL; } - + if ( $last_shift_start < $first_shift_end ) { // --- record a conflict --- if ( RADIO_STATION_DEBUG ) { echo "First/Last Shift Overlap Conflict" . PHP_EOL; } - - /* + + /* // --- store conflict for this shift --- $conflicts[$first_shift['show']][] = array( 'show' => $first_shift['show'], @@ -2470,7 +2523,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $end_time = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; $start_time = radio_station_convert_shift_time( $start_time ); $end_time = radio_station_convert_shift_time( $end_time ); - + // 2.3.2: use next week day instead of date $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); @@ -2479,7 +2532,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { if ( $shift_end_time < $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - + if ( RADIO_STATION_DEBUG ) { echo "Checking Shift for Show " . $show_id . ": "; echo $shift['day'] . " - " . $weekdates[$shift['day']] . " - " . $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; @@ -2503,7 +2556,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // 2.3.2: fix to convert to 24 hour times first $shift_start = radio_station_convert_shift_time( $day_shift['start'] ); $shift_end = radio_station_convert_shift_time( $day_shift['end'] ); - + // 2.3.2: use next week day instead of date $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_start ); $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_end ); @@ -2522,9 +2575,9 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $check_shift = false; // } } - + if ( $check_shift ) { - + if ( RADIO_STATION_DEBUG ) { echo "with Shift for Show " . $day_shift['show'] . ": "; echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; @@ -2552,7 +2605,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } // } } - + // --- recheck for first shift overlaps --- // (not implemented as not needed) /* if ( isset( $first_shift ) ) { @@ -2566,7 +2619,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { echo "with First Shift for Show " . $first_shift['show'] . ": "; echo $first_shift['day'] . " - " . $first_shift['date'] . " - " . $first_shift['start'] . " (" . $first_shift_start_time . ")"; echo " to " . $first_shift['end'] . " (" . $first_shift_end_time . ")" . PHP_EOL; - } + } if ( ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_start_time ) ) || ( ( $shift_start_time < $first_shift_start_time ) && ( $shift_end_time > $first_shift_end_time ) ) @@ -2579,7 +2632,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } } } */ - + // --- recheck for last shift --- // (for date based schedule overflow rechecking) if ( isset( $day_shift ) ) { @@ -2595,7 +2648,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; } - + if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) || ( $shift_start_time == $day_shift_start_time ) @@ -2605,7 +2658,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { if ( RADIO_STATION_DEBUG ) { echo "^^^ CONFLICT ^^^" . PHP_EOL; } - } + } } if ( count( $conflicts ) == 0 ) { @@ -2672,7 +2725,7 @@ function radio_station_check_new_shifts( $new_shifts ) { foreach ( $new_shifts as $j => $shift_b ) { if ( $i != $j ) { - + if ( RADIO_STATION_DEBUG ) { echo $i . ' ::: ' . $j . PHP_EOL; } @@ -2729,7 +2782,7 @@ function radio_station_check_new_shifts( $new_shifts ) { $new_shifts[$j]['disabled'] = 'yes'; - } else { + } else { if ( RADIO_STATION_DEBUG ) { echo "[Conflict with disabled shift.]" . PHP_EOL; } @@ -2938,12 +2991,12 @@ function radio_station_get_api_url() { function radio_station_get_route_url( $route ) { global $radio_station_routes; - + // --- maybe return cached route URL --- if ( isset( $radio_station_routes[$route] ) ) { return $radio_station_routes[$route]; } - + /// --- get route URL --- $base = apply_filters( 'radio_station_route_slug_base', 'radio' ); if ( '' != $route ) { @@ -2970,18 +3023,18 @@ function radio_station_get_route_url( $route ) { function radio_station_get_feed_url( $feedname ) { global $radio_station_feeds; - + // --- maybe return cached feed URL --- if ( isset( $radio_station_feeds[$feedname] ) ) { return $radio_station_feeds[$feedname]; } - + // --- get feed URL --- $feedname = apply_filters( 'radio_station_feed_slug_' . $feedname, $feedname ); if ( !$feedname ) { return false; } - + // --- cache feed URL --- $radio_station_feeds[$feedname] = $feed_url = get_feed_link( $feedname ); @@ -3042,7 +3095,7 @@ function radio_station_get_upgrade_url() { // $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); $upgrade_url = RADIO_STATION_PRO_URL; - + return $upgrade_url; } @@ -3097,7 +3150,7 @@ function radio_station_queue_directory_ping() { function radio_station_send_directory_ping() { $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); if ( 'yes' != $do_ping ) {return;} - + // --- set the URL to ping --- // 2.3.2: fix url_encode to urlencode $site_url = site_url(); @@ -3152,7 +3205,7 @@ function radio_station_get_now( $gmt = true ) { $datetime = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); $now = $datetime->format( 'U' ); } - + return $now; } @@ -3164,18 +3217,18 @@ function radio_station_get_timezone() { $timezone = radio_station_get_setting( 'timezone_location' ); if ( !$timezone || ( '' == $timezone ) ) { - + // --- fallback to WordPress timezone --- $timezone = get_option( 'timezone_string' ); if ( false !== strpos( $timezone, 'Etc/GMT' ) ) { $timezone = ''; } - + } if ( '' == $timezone ) { $offset = get_option( 'gmt_offset' ); - $timezone = 'UTC' . $offset; + $timezone = 'UTC' . $offset; } return $timezone; @@ -3214,11 +3267,11 @@ function radio_station_get_date_time( $timestring, $timezone ) { // echo "A: " . print_r( $datetime, true ) . PHP_EOL; // if ( '@' == substr( $timestring, 0, 1 ) ) { // $timestamp = substr( $timestring, 1, strlen( $timestring ) ); - // $datetime->setTimestamp( $timestamp ); + // $datetime->setTimestamp( $timestamp ); // } // echo "B: " . print_r( $datetime, true ) . PHP_EOL; } - + return $datetime; } @@ -3230,7 +3283,7 @@ function radio_station_to_time( $timestring ) { if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $time = strtotime( $timestring ); - } else { + } else { $timezone = radio_station_get_timezone(); if ( strstr( $timezone, 'UTC' ) && ( 'UTC' != $timezone ) ) { // --- fallback for UTC offsets --- @@ -3240,17 +3293,17 @@ function radio_station_to_time( $timestring ) { $datetime = new DateTime( $timestring, $utc ); $timestamp = $datetime->getTimestamp(); $timestamp = $timestamp - $offset; - $datetime->setTimestamp( $timestamp ); + $datetime->setTimestamp( $timestamp ); } else { $datetime = radio_station_get_date_time( $timestring, $timezone ); } $time = $datetime->format( 'U' ); - + } return $time; } - + // -------- // Get Time // -------- @@ -3300,12 +3353,12 @@ function radio_station_get_time( $key = false, $time = false ) { 'datetime' => $date_time, 'timestamp' => $timestamp, ); - + if ( $key ) { if ( array_key_exists( $key, $times ) ) { $time = $times[$key]; } elseif ( isset( $datetime ) ) { - $time = $datetime->format( $key ); + $time = $datetime->format( $key ); } else { $time = date( $key, $time ); } @@ -3313,7 +3366,7 @@ function radio_station_get_time( $key = false, $time = false ) { echo 'Time key not found: ' . $key . PHP_EOL; echo 'Times: ' . print_r( $times, true ) . ''; } - + return $time; } @@ -3456,7 +3509,7 @@ function radio_station_get_schedule_weekdays( $weekstart = false ) { 'Friday', 'Saturday', ); - + // 2.3.2: also accept string format for weekstart if ( is_string( $weekstart ) ) { // 2.3.3.5: accept today as valid week start @@ -3499,7 +3552,7 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { if ( !$time ) { $time = radio_station_get_now(); } - + // 2.3.2: use timezone setting to get offset date if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $today = date( 'l', $time ); @@ -3528,7 +3581,7 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { } $weekdates[$weekday] = $weekdate; } - + if ( RADIO_STATION_DEBUG ) { echo ''; echo 'Time: ' . $time . PHP_EOL; @@ -3749,7 +3802,7 @@ function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { // ---------------------------- // 2.3.0: added to convert shift time to 24 hours (or back) function radio_station_convert_shift_time( $time, $timeformat = 24 ) { - + // note: timezone can be ignored here as just getting hours and minutes $timestamp = strtotime( date( 'Y-m-d' ) . $time ); if ( 12 == (int) $timeformat ) { @@ -4024,15 +4077,15 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ $excerpt = ''; $raw_content = $content; - + // 2.3.2: added check for content if ( '' != trim( $content ) ) { - + $content = strip_shortcodes( $content ); if ( function_exists( 'excerpt_remove_blocks' ) ) { $content = excerpt_remove_blocks( $content ); - } + } // TODO: check for Gutenberg plugin-only equivalent ? // elseif ( function_exists( 'gutenberg_remove_blocks' ) { // $content = gutenberg_remove_blocks( $content ); @@ -4096,7 +4149,7 @@ function radio_station_sanitize_values( $data, $keys ) { function radio_station_sanitize_shortcode_values( $type, $extras = false ) { $atts = array(); - + if ( 'current-show' == $type ) { // --- current show attribute keys --- @@ -4124,7 +4177,7 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'instance' => 'integer', 'for_time' => 'integer', ); - + } elseif ( 'upcoming-shows' == $type ) { // --- upcoming shows attribute keys --- @@ -4149,9 +4202,9 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'instance' => 'integer', 'for_time' => 'integer', ); - + } elseif ( 'current-playlist' == $type ) { - + // --- current playlist attribute keys --- // 2.3.3: added for_time value $keys = array( @@ -4168,9 +4221,9 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'id' => 'integer', 'instance' => 'integer', 'for_time' => 'integer', - ); + ); } - + // --- handle extra keys --- if ( $extras && is_array( $extras ) && ( count( $extras ) > 0 ) ) { $keys = array_merge( $keys, $extras ); @@ -4198,7 +4251,7 @@ function radio_station_delete_transients_with_prefix( $prefix ) { foreach ( $results as $option ) { $key = ltrim( $option['option_name'], '_transient_' ); delete_transient( $key ); - } + } } @@ -4229,7 +4282,7 @@ function radio_station_translate_weekday( $weekday, $short = null ) { foreach ( $days as $i => $day ) { $abbr = substr( $day, 0, 3 ); if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { - if ( ( !$short && !is_null( $short ) ) + if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $weekday == $day ) ) ) { return $wp_locale->get_weekday( $i ); } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { @@ -4245,10 +4298,10 @@ function radio_station_translate_weekday( $weekday, $short = null ) { if ( $short ) { return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $daynum ) ); } else { - return $wp_locale->get_weekday( $daynum ); + return $wp_locale->get_weekday( $daynum ); } } - + return $weekday; } @@ -4263,12 +4316,12 @@ function radio_station_replace_weekday( $string ) { $abbr = substr( $day, 0, 3 ); if ( strstr( $string, $day ) ) { $translated = radio_station_translate_weekday( $day ); - $string = str_replace( $day, $translated, $string ); + $string = str_replace( $day, $translated, $string ); } elseif ( strstr( $string, $abbr ) ) { $translated = radio_station_translate_weekday( $abbr ); $string = str_replace( $abbr, $translated, $string ); } - } + } return $string; } @@ -4295,7 +4348,7 @@ function radio_station_translate_month( $month, $short = null ) { foreach ( $months as $i => $fullmonth ) { $abbr = substr( $fullmonth, 0, 3 ); if ( ( $month == $fullmonth ) || ( $month == $abbr ) ) { - if ( ( !$short && !is_null( $short ) ) + if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $month == $fullmonth ) ) ) { return $wp_locale->get_month( ( $i + 1 ) ); } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { @@ -4311,10 +4364,10 @@ function radio_station_translate_month( $month, $short = null ) { if ( $short ) { return $wp_locale->get_month_abbrev( $wp_locale->get_month( $monthnum ) ); } else { - return $wp_locale->get_month( $monthnum ); + return $wp_locale->get_month( $monthnum ); } } - + return $month; } @@ -4329,7 +4382,7 @@ function radio_station_replace_month( $string ) { $abbr = substr( $month, 0, 3 ); if ( strstr( $string, $month ) ) { $translated = radio_station_translate_month( $month ); - $string = str_replace( $month, $translated, $string ); + $string = str_replace( $month, $translated, $string ); } elseif ( strstr( $string, $abbr ) ) { $translated = radio_station_translate_month( $abbr ); $string = str_replace( $abbr, $translated, $string ); @@ -4367,8 +4420,8 @@ function radio_station_replace_meridiem( $string ) { ); $radio_station_data['meridiems'] = $meridiems; } - - + + if ( strstr( $string, 'am' ) ) { $string = str_replace( 'am', $meridiems['am'], $string ); } diff --git a/readme.txt b/readme.txt index 74b36d8..dd1eb6c 100644 --- a/readme.txt +++ b/readme.txt @@ -185,9 +185,11 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == = 2.3.3.5 = +* Fixed: use schedule based on start_day if specified for Schedule view * Fixed: day left/right shifting on Schedule table/tab mobile views * Added: past/today/future filter for Schedule Override List * Added: filter for Schedule display start day (and to accept today) +* Added: current playlist (if any) to Broadcast Data endpoint = 2.3.3.4 = * Improved: auto-match show description to info height on Show pages diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index fbc7438..b512223 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -4,7 +4,6 @@ */ // --- get all the required info --- -$schedule = radio_station_get_current_schedule(); $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); @@ -23,13 +22,20 @@ // --- get schedule days and dates --- // 2.3.2: allow for start day attibute +// 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { - $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); + $start_day = $atts['start_day']; + $schedule = radio_station_get_current_schedule( $now , $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'list' ); - $weekdays = radio_station_get_schedule_weekdays(); + if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $now , $start_day ); + } else { + $schedule = radio_station_get_current_schedule(); + } } +$weekdays = radio_station_get_schedule_weekdays( $start_day ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- filter show avatar size --- @@ -69,7 +75,7 @@ $skip_day = true; } } - + if ( !$skip_day ) { // 2.3.2: move up time calculations for optional date display @@ -105,12 +111,12 @@ $date_subheading .= ' ' . radio_station_translate_month( $month, true ); } - // 2.3.2: add classes + // 2.3.2: add classes $classes = array( 'master-list-day' ); $weekdate = $weekdates[$weekday]; if ( $weekdate == $date ) { $classes[] = 'current-day'; - } + } $classlist = implode( ' ', $classes ); // 2.2.2: use translate function for weekday string @@ -125,7 +131,7 @@ if ( $atts['display_date'] ) { $output .= ' ' . esc_html( $date_subheading ) . ''; } - + // 2.3.2: add output of day start and end times // 2.3.2: replace strtotime with to_time for timezones $output .= ''; @@ -302,7 +308,7 @@ $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['end'] ); } } */ - + // --- get start and end times --- // 2.3.2: maybe use real start and end times if ( $real_shift_start ) { diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index ea34aca..d71b7f4 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -4,7 +4,6 @@ */ // --- get all the required info --- -$schedule = radio_station_get_current_schedule(); $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); @@ -24,13 +23,20 @@ // --- get schedule days and dates --- // 2.3.2: allow for start day attibute +// 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { - $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); + $start_day = $atts['start_day']; + $schedule = radio_station_get_current_schedule( $now, $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'table' ); - $weekdays = radio_station_get_schedule_weekdays( $start_day ); + if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $now , $start_day ); + } else { + $schedule = radio_station_get_current_schedule(); + } } +$weekdays = radio_station_get_schedule_weekdays( $start_day ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- filter avatar size --- @@ -77,7 +83,7 @@ } if ( !$skip_day ) { - + // --- set day column heading --- // 2.3.2: added check for short/long day display attribute if ( !in_array( $atts['display_day'], array( 'short', 'full', 'long' ) ) ) { @@ -96,7 +102,7 @@ $weekdate = $weekdates[$weekday]; $day_start_time = radio_station_to_time( $weekdate . ' 00:00:00' ); $day_end_time = $day_start_time + ( 24 * 60 * 60 ); - + // 2.3.2: add attribute for date subheading format (see PHP date() format) // $subheading = date( 'jS M', strtotime( $weekdate ) ); if ( $atts['display_date'] ) { @@ -151,10 +157,10 @@ // 2.3.2: add day start and end time date $output .= ''; $output .= ''; - + $output .= ''; - - } + + } } $output .= ''; @@ -201,7 +207,7 @@ $output .= 'Hour End' . $hour_end . '(' . date( 'H:i', $hour_end ) . ')
    '; $output .= '
    '; } - + $output .= '
    '; $output .= esc_html( $hour_display ); $output .= '
    '; @@ -303,7 +309,7 @@ } $display = $showcontinued = true; $cellshifts ++; - } elseif ( ( $shift_start_time == $hour_start ) + } elseif ( ( $shift_start_time == $hour_start ) || ( ( $shift_start_time > $hour_start ) && ( $shift_start_time < $next_hour_start ) ) ) { // - start display of shift - $started = $shift['started'] = true; @@ -390,7 +396,7 @@ // --- display empty div (for highlighting) --- $cell .= ' '; - + // 2.3.2: set shift times for highlighting $cell .= ''; $cell .= ''; @@ -408,7 +414,7 @@ // --- show logo / thumbnail --- // 2.3.0: filter show avatar via show ID and context $show_avatar = false; - if ( $atts['show_image'] ) { + if ( $atts['show_image'] ) { $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); } $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'table' ); @@ -480,7 +486,7 @@ if ( '11:59:59 pm' == $shift['end'] ) { $shift['end'] = '12:00 am'; } */ - + /* if ( 24 == (int) $atts['time'] ) { $start = radio_station_convert_shift_time( $shift['start'], 24 ); // 2.3.2: display real end of split shift @@ -498,7 +504,7 @@ $end = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['end'] ); } } */ - + // --- get start and end times --- // 2.3.2: maybe use real start and end times if ( $real_shift_start ) { diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 856b431..fe5ada2 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -5,7 +5,6 @@ */ // --- get all the required info --- -$schedule = radio_station_get_current_schedule(); $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); @@ -25,13 +24,20 @@ // --- get schedule days and dates --- // 2.3.2: allow for start day attibute +// 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { - $weekdays = radio_station_get_schedule_weekdays( $atts['start_day'] ); + $start_day = $atts['start_day']; + $schedule = radio_station_get_current_schedule( $now , $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'tabs' ); - $weekdays = radio_station_get_schedule_weekdays( $start_day ); + if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $now , $start_day ); + } else { + $schedule = radio_station_get_current_schedule(); + } } +$weekdays = radio_station_get_schedule_weekdays( $start_day ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- filter show avatar size --- @@ -94,7 +100,12 @@ // 2.3.2: add attribute for date subheading format (see PHP date() format) // $subheading = date( 'jS M', strtotime( $weekdate ) ); if ( $atts['display_date'] ) { - $date_subheading = radio_station_get_time( $atts['display_date'], $day_start_time ); + // 2.3.3.5: allow for attribute to be set to 1 for default display + if ( '1' == $atts['display_date'] ) { + $date_subheading = radio_station_get_time( 'jS', $day_start_time ); + } else { + $date_subheading = radio_station_get_time( $atts['display_date'], $day_start_time ); + } } else { $date_subheading = radio_station_get_time( 'j', $day_start_time ); } @@ -110,7 +121,7 @@ $date_subheading .= ' ' . radio_station_translate_month( $month, true ); } - // --- set tab classes --- + // --- set tab classes --- $weekdate = $weekdates[$weekday]; $classes = array( 'master-schedule-tabs-day', 'day-' . $i ); // 2.3.3.5: add extra class for starting tab @@ -133,7 +144,7 @@ $output .= '
    '; $output .= '' . $arrows['left'] . ''; $output .= '
    '; - + // 2.3.2: added optional display_date attribute and subheading $output .= '
    '; $output .= '
    '; } $output .= '
    '; - + $output .= '
    '; $output .= '' . $arrows['right'] . ''; $output .= '
    '; @@ -256,8 +267,8 @@ } if ( $j == count( $shifts ) ) { $classes[] = 'last-show'; - } - + } + // 2.3.2: check for now playing shift if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { $classes[] = 'nowplaying'; @@ -396,7 +407,7 @@ $tcount ++; } else { - + // 2.3.2: added for now playing check $panels .= ''; $panels .= ''; @@ -426,7 +437,7 @@ // 2.3.3: fix to incorrect filter name $show_file = get_post_meta( $show['id'], 'show_file', true ); $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'tabs' ); - $disable_download = get_post_meta( $show['id'], 'show_download', true ); + $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { $panels .= '
    '; $panels .= ''; From a87d9eea5423638fc38c83100e7eec05e6d17117 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 22 Oct 2020 08:44:04 +1000 Subject: [PATCH 152/377] readme release updates --- readme.md | 6 ++++++ readme.txt | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/readme.md b/readme.md index f78690d..c7a6ad7 100644 --- a/readme.md +++ b/readme.md @@ -188,6 +188,12 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices +#### 2.3.3.5 +* Ability to assign Post to relate to multiple Shows +* Added Admin Filtering, Bulk Edit and Quick Edit interfaces +* Fixes for Schedule display left/right shifting on mobiles +* Fixes for starting Schedule display on different day + #### 2.3.3.3 * Current and Upcoming Shows Widget Fix diff --git a/readme.txt b/readme.txt index dd1eb6c..c913ea2 100644 --- a/readme.txt +++ b/readme.txt @@ -608,6 +608,12 @@ You may translate the plugin into another language. Please visit our [WordPress == Upgrade Notice == += 2.3.3.5 = +* Ability to assign Post to relate to multiple Shows +* Added Admin Filtering, Bulk Edit and Quick Edit interfaces +* Fixes for Schedule display left/right shifting on mobiles +* Fixes for starting Schedule display on different day + = 2.3.3.3 = * Current and Upcoming Shows Widget Fix From 488b4ad6229e0a1fba6f4953499b7e3ed5ebf8e8 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 30 Oct 2020 22:27:22 -0400 Subject: [PATCH 153/377] update readme.txt for wordpress to 5.5.3 compatibility text change --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index c913ea2..3c49b06 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.4.1 +Tested up to: 5.5.3 Stable tag: trunk License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 8793b328feb53df1a718ba861c450ddadf392aa1 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 12 Nov 2020 18:12:19 +1000 Subject: [PATCH 154/377] dev updates #276 #277 #278 --- CHANGELOG.md | 5 + includes/data-feeds.php | 4 - includes/master-schedule.php | 67 ++--- includes/post-types-admin.php | 393 ++++++++++++++++++++---------- includes/support-functions.php | 15 +- radio-station.php | 87 ++++--- readme.txt | 5 + templates/single-show-content.php | 26 +- 8 files changed, 397 insertions(+), 205 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed46eb..58e900f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.3.6 +* Fixed: Edit permissions checks for Related to Show post assignments +* Fixed: Main Language option value for WordPress Setting +* Fixed: make Date on Tab clickable on Tabbed Schedule View + ### 2.3.3.5 * Fixed: use schedule based on start_day if specified for Schedule view * Fixed: day left/right shifting on Schedule table/tab mobile views diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 9869718..f365031 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -187,15 +187,11 @@ function radio_station_get_broadcast_data() { // --- get current show --- $current_show = radio_station_get_current_show(); - // print_r( $current_show ); $current_show = radio_station_convert_show_shift( $current_show ); - // print_r( $current_show ); // --- get next show --- $next_show = radio_station_get_next_show(); - // print_r( $next_show ); $next_show = radio_station_convert_show_shift( $next_show ); - // print_r( $next_show ); // 2.3.3.5: just in case transients are the same if ( $current_show == $next_show ) { diff --git a/includes/master-schedule.php b/includes/master-schedule.php index bc7628e..80bba7e 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -56,7 +56,7 @@ function radio_station_master_schedule( $atts ) { 'selector' => 1, 'clock' => $clock, 'timezone' => 1, - + // --- schedule display options --- 'time' => $time_format, 'show_times' => 1, @@ -70,7 +70,7 @@ function radio_station_master_schedule( $atts ) { 'divheight' => 45, // --- converted and deprecated --- - // 'list' => 0, + // 'list' => 0, // 'show_djs' => 0, // 'display_show_time' => 1, @@ -107,7 +107,7 @@ function radio_station_master_schedule( $atts ) { // --- merge attributes with defaults --- $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); - + // --- enqueue schedule stylesheet --- // 2.3.0: use abstracted method for enqueueing widget styles radio_station_enqueue_style( 'schedule' ); @@ -127,7 +127,7 @@ function radio_station_master_schedule( $atts ) { $output .= '
    '; $controls = array(); - + // --- display radio clock or timezone (or neither) if ( $atts['clock'] ) { @@ -157,11 +157,11 @@ function radio_station_master_schedule( $atts ) { // 2.3.1: add filters for control order $control_order = array( 'clock', 'timezone', 'selector' ); $control_order = apply_filters( 'radio_station_schedule_control_order', $control_order, $atts ); - + // 2.3.1: add filter for controls HTML $controls = apply_filters( 'radio_station_schedule_controls', $controls, $atts ); - // --- add ordered controls to output --- + // --- add ordered controls to output --- if ( is_array( $control_order ) && ( count( $control_order ) > 0 ) ) { foreach ( $control_order as $control ) { if ( isset( $controls[$control] ) && ( '' != $control ) ) { @@ -169,7 +169,7 @@ function radio_station_master_schedule( $atts ) { } } } - + $output .= '

    '; // --- schedule display override --- @@ -473,7 +473,7 @@ function radio_times_highlight() { end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (end > radio.offset_time) {jQuery(this).addClass('current-day');} else {jQuery(this).removeClass('current-day');} - } else {jQuery(this).removeClass('current-day');} + } else {jQuery(this).removeClass('current-day');} }); jQuery('.master-program-hour').each(function() { hour = parseInt(jQuery(this).find('.master-program-server-hour').attr('data')); @@ -499,7 +499,7 @@ function radio_times_highlight() { /* Make Table Responsive */ function radio_table_responsive(leftright) { - + fallback = -1; selected = -1; foundtab = false; if (!leftright || (leftright == 'left')) { if (jQuery('.master-program-day.first-column').length) { @@ -518,7 +518,7 @@ classes = end.attr('class').split(' '); if (selected < 0) {selected = fallback;} if (radio.debug) {console.log('Current Column: '+selected);} - if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} if (!jQuery('.master-program-day.day-'+selected).length) { while (!foundtab) { @@ -572,13 +572,13 @@ classes = end.attr('class').split(' '); jQuery('.master-program-day.day-'+i).addClass('first-column'); if (radio.debug) {console.log('Showing Tab '+i);} columns++; - } + } } - } + } } jQuery('#master-program-schedule').css('width','100%'); } - + /* Shift Day Left / Right */ function radio_shift_day(leftright) { radio_table_responsive(leftright); return false; @@ -606,9 +606,11 @@ function radio_station_master_schedule_tabs_js() { } else { jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); - } */ + } */ + + // 2.3.3.6: allow for clicking on date to change days $js = "jQuery(document).ready(function() { - jQuery('.master-schedule-tabs-day-name').bind('click', function (event) { + jQuery('.master-schedule-tabs-day-name, .master-schedule-tabs-date').bind('click', function (event) { headerID = jQuery(event.target).closest('li').attr('id'); panelID = headerID.replace('header', 'day'); jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); @@ -640,7 +642,7 @@ function radio_set_active_tab(day) { jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); if (!day) { jQuery('.master-schedule-tabs-day').first().addClass('active-day-tab'); - jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); + jQuery('.master-schedule-tabs-panel').first().addClass('active-day-panel'); } else { jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); @@ -663,7 +665,7 @@ function radio_show_highlight() { day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); radio_set_active_tab(day); } else {jQuery(this).removeClass('current-day');} - } else {jQuery(this).removeClass('current-day');} + } else {jQuery(this).removeClass('current-day');} }); radio_set_active_tab(false); /* fallback */ jQuery('.master-schedule-tabs-show').each(function() { @@ -677,7 +679,7 @@ function radio_show_highlight() { } else {jQuery(this).removeClass('nowplaying');} }); } - + /* Make Tabs Responsive */ function radio_tabs_responsive(leftright) { @@ -698,7 +700,7 @@ classes = end.attr('class').split(' '); } if (selected < 0) {selected = fallback;} if (radio.debug) {console.log('Current Tab: '+selected);} - + if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} if (!jQuery('.master-schedule-tabs-day.day-'+selected).length) { @@ -714,8 +716,8 @@ classes = end.attr('class').split(' '); tabswidth = jQuery('#master-schedule-tabs').width(); jQuery('#master-schedule-tabs').css('width','auto'); jQuery('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); - - totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; + + totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; for (i = selected; i < 7; i++) { if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { if ((i > 0) && (i == selected)) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} @@ -735,7 +737,7 @@ classes = end.attr('class').split(' '); lasttab = i; tabs++; } } - } + } if (lasttab < 6) {jQuery('.master-schedule-tabs-day.day-'+lasttab).addClass('last-tab');} if (leftright == 'right') { @@ -754,14 +756,14 @@ classes = end.attr('class').split(' '); jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); if (radio.debug) {console.log('Showing Tab '+i);} tabs++; - } + } } - } + } } jQuery('#master-schedule-tabs').css('width','100%'); /* display selected day message if outside view */ - activeday = false; + activeday = false; for (i = 0; i < 7; i++) { if (jQuery('.master-schedule-tabs-day.day-'+i).length) { if (jQuery('.master-schedule-tabs-day.day-'+i).hasClass('active-day-tab')) {activeday = i;} @@ -770,7 +772,7 @@ classes = end.attr('class').split(' '); jQuery('.master-schedule-tabs-selected').hide(); if ( activeday && ( (activeday > lasttab) || (activeday < firsttab ) ) ) { jQuery('#master-schedule-tabs-selected-'+activeday).show(); - } + } if (radio.debug) { console.log('Active Day: '+activeday); @@ -781,7 +783,7 @@ classes = end.attr('class').split(' '); console.log('Visible Tabs: '+tabs); } } - + /* Shift Day Left / Right */ function radio_shift_tab(leftright) { radio_tabs_responsive(leftright); @@ -816,19 +818,18 @@ function radio_list_highlight() { end = parseInt(jQuery(this).find('.rs-end-time').first().attr('data')); if (end > radio.offset_time) {jQuery(this).addClass('current-day');} else {jQuery(this).removeClass('current-day');} - } else {jQuery(this).removeClass('current-day');} - }); + } else {jQuery(this).removeClass('current-day');} + }); jQuery('.master-list-day-item').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (start < radio.offset_time) { + if (start < radio.offset_time) { end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (end > radio.offset_time) {jQuery(this).addClass('nowplaying');} else {jQuery(this).removeClass('nowplaying');} } else {jQuery(this).removeClass('nowplaying');} }); }"; - + // --- enqueue script inline --- wp_add_inline_script( 'radio-station', $js ); -} - \ No newline at end of file +} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index fb153b8..caad0bc 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -79,7 +79,7 @@ function radio_station_top_meta_boxes() { global $post, $wp_meta_boxes; $current_screen = get_current_screen(); - + if ( RADIO_STATION_DEBUG ) { echo ""; echo ""; @@ -89,15 +89,15 @@ function radio_station_top_meta_boxes() { $screen_layout = get_user_option( 'screen_layout_' . $current_screen->id ); echo ""; echo ""; - echo ""; + echo ""; } - + // --- top metabox output --- - // 2.3.2: change metabox ID from rs-top + // 2.3.2: change metabox ID from rs-top // (- is not supported in metabox ID for sort order saving) // (causing bug where sorted metaboxes disappear completely!) do_meta_boxes( $current_screen, 'rstop', $post ); - + if ( RADIO_STATION_DEBUG ) { echo ""; } @@ -293,7 +293,7 @@ function radio_remove_language(term) { /* re-enable language select option */ select = document.getElementById('rs-add-language-selection'); - options = select.options; + options = select.options; for (i = 0; i < options.length; i++) { if (options[i].value == term) { options[i].removeAttribute('disabled'); @@ -440,9 +440,9 @@ function radio_station_playlist_metabox() { echo ''; echo ''; echo ''; - + echo '
    '; - + // --- move new tracks message --- // 2.3.2: added new track move message echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    '; @@ -457,7 +457,7 @@ function radio_station_playlist_metabox() { trackcount = 1; } }" . PHP_EOL; - + // --- save tracks via AJAX --- // 2.3.2: added form input cloning to save playlist tracks $ajaxurl = admin_url( 'admin-ajax.php' ); @@ -493,7 +493,7 @@ function radio_station_playlist_metabox() { /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ } if (updown == 'down') { - m = n + 1; + m = n + 1; jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ @@ -504,7 +504,7 @@ function radio_station_playlist_metabox() { /* swap track count */ jQuery('#track-'+n+'-rowa .track-count').html(m); jQuery('#track-'+m+'-rowa .track-count').html(n); - + /* swap input name keys */ jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); @@ -512,7 +512,7 @@ function radio_station_playlist_metabox() { jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); }); - + /* swap button actions */ jQuery('#track-'+n+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+m+');'); jQuery('#track-'+n+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+m+');'); @@ -531,7 +531,7 @@ function radio_station_playlist_metabox() { jQuery('#track-0-rowa').attr('id', 'track-'+n+'-rowa'); jQuery('#track-0-rowb').attr('id', 'track-'+n+'-rowb'); }" . PHP_EOL; - + // --- reset first and last track classes --- $js .= "function radio_track_classes() { jQuery('.track-rowa, .track-rowb, .track-rowc').removeClass('first-track').removeClass('last-track'); @@ -572,7 +572,7 @@ function radio_station_playlist_metabox() { output += '
    '; output += ''; output += ''; - + /* output += ''; output += ''; */ @@ -596,7 +596,7 @@ function radio_station_playlist_metabox() { jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); - jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); }); } /* add duplicate row */ @@ -659,7 +659,7 @@ function radio_station_playlist_metabox() { #tracks-saving-message, #tracks-saved-message { background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; width: 170px; padding: 5px 0;} '; - + // --- close meta inner --- echo '
    '; @@ -707,17 +707,17 @@ function radio_station_playlist_track_table( $entries ) { $class = 'last-track'; } echo ''; - + // --- track count --- echo '' . esc_html( $c ) . ''; - + // --- track entry inputs --- echo ''; echo ''; echo ''; echo ''; echo ''; - + // --- track row b --- echo ''; @@ -749,12 +749,12 @@ function radio_station_playlist_track_table( $entries ) { echo '
    '; echo ''; echo ''; - + // --- track row c --- // TODO: add track time / start / end input fields ? // echo ''; // echo ''; - + $c ++; } } @@ -900,7 +900,7 @@ function radio_station_playlist_save_data( $post_id ) { } $post_id = absint( $_POST['playlist_id'] ); $post = get_post( $post_id ); - + $error = false; if ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); @@ -917,9 +917,9 @@ function radio_station_playlist_save_data( $post_id ) { parent.document.getElementById('tracks-error-message').innerHTML = '" . esc_js( $error ) . "'; form = parent.document.getElementById('track-save-form'); form.parentNode.removeChild(form); "; - + exit; - } + } } // --- save playlist tracks --- @@ -990,7 +990,7 @@ function radio_station_playlist_save_data( $post_id ) { } } } - + // --- AJAX saving --- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == $_POST['action'] ) ) { @@ -1076,7 +1076,7 @@ function radio_station_playlist_column_data( $column, $post_id ) { $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); $post = get_post( $show_id ); echo "
    " . esc_html( $post->post_title ) . ""; - } elseif ( 'trackcount' == $column ) { + } elseif ( 'trackcount' == $column ) { echo count( $tracks ); } elseif ( 'tracklist' == $column ) { echo ''; @@ -1109,11 +1109,11 @@ function radio_station_playlist_column_styles() { if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { return; } - + // --- playlist list styles --- - echo ""; @@ -1163,6 +1163,9 @@ function radio_station_post_show_metabox() { global $post; + // 2.3.3.6: store current post global + $stored_post = $post; + // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'post_show_nonce' ); @@ -1175,6 +1178,8 @@ function radio_station_post_show_metabox() { 'post_status' => 'publish', // ??? ); $shows = get_posts( $args ); + + // --- get current selection --- $selected = get_post_meta( $post->ID, 'post_showblog_id', true ); // 2.3.3.4: convert existing selection to array if ( !$selected ) { @@ -1186,6 +1191,7 @@ function radio_station_post_show_metabox() { echo '
    '; if ( count( $shows ) > 0 ) { + // --- select related show input --- // 2.2.3.4: allow for multiple selections echo ''; @@ -1207,6 +1224,13 @@ function radio_station_post_show_metabox() { echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); } echo '
    '; + + // --- related shows post box styles --- + // 2.3.3.6: add style for pre-selected option + echo ""; + + // 2.3.3.6: revert current post global + $post = $stored_post; } // ------------------- @@ -1220,47 +1244,65 @@ function radio_station_post_save_data( $post_id ) { return; } + // 2.3.3.6: store post for capability checking + global $post; + $stored_post = $post; + // --- check related show field is set --- // 2.3.0: added check if changed if ( isset( $_POST['post_showblog_id'] ) ) { // --- verify field save nonce --- - if ( !isset( $_POST['post_show_nonce'] ) - || !wp_verify_nonce( $_POST['post_show_nonce'], 'radio-station' ) ) { + if ( !isset( $_POST['post_show_nonce'] ) || !wp_verify_nonce( $_POST['post_show_nonce'], 'radio-station' ) ) { return; } // --- get the related show ID --- $changed = false; - $prev_show = get_post_meta( $post_id, 'post_showblog_id', true ); - $show = $_POST['post_showblog_id']; - - if ( empty( $show ) ) { - // --- remove show from post --- - delete_post_meta( $post_id, 'post_showblog_id' ); - if ( $prev_show ) { - $changed = true; + $current_shows = get_post_meta( $post_id, 'post_showblog_id', true ); + $show_ids = $_POST['post_showblog_id']; + + // 2.3.3.6: maybe add existing (uneditable) Show IDs + $new_show_ids = array(); + if ( $current_shows && is_array( $current_shows ) && ( count( $current_shows ) > 0 ) ) { + foreach ( $current_shows as $current_show ) { + if ( $current_show > 0 ) { + $post = get_post( $current_show ); + if ( $post && !current_user_can( 'edit_shows' ) ) { + $new_show_ids[] = $current_show; + } + } } - } else { + } + + if ( !empty( $show_ids ) ) { // --- sanitize to numeric before updating --- // 2.3.3.4: maybe sanitize multiple array values - if ( is_array( $show ) ) { - foreach ( $show as $i => $s ) { - if ( '' == $s ) { - unset( $show[$i] ); - } else { - $show[$i] = absint( trim( $s ) ); - } - } - } else { - $show = absint( trim( $show ) ); + if ( !is_array( $show_ids ) ) { + $show_ids = array( $show_ids ); } - if ( ( $show != -1 ) && ( $show != $prev_show ) ) { - update_post_meta( $post_id, 'post_showblog_id', $show ); - $changed = true; + foreach ( $show_ids as $i => $show_id ) { + $show_id = absint( trim( $show_id ) ); + if ( $show_id > -1 ) { + // 2.3.3.6: check edit Show capability before adding + $post = get_post( $show_id ); + if ( $post && current_user_can( 'edit_shows' ) && !in_array( $show_id, $new_show_ids ) ) { + $new_show_ids[] = $show_id; + } + } } } + // --- delete or update Show IDs for post --- + // 2.3.3.6: check existing versus new show ID values + if ( 0 == count( $new_show_ids ) ) { + delete_post_meta( $post_id, 'post_showblog_id' ); + $changed = true; + } elseif ( $new_show_ids != $current_shows ) { + update_post_meta( $post_id, 'post_showblog_id', $new_show_ids ); + $changed = true; + } + // 2.3.0: clear cached data to be safe // 2.3.3: remove current show transient // 2.3.4: add previous show transient @@ -1278,6 +1320,8 @@ function radio_station_post_save_data( $post_id ) { } } + // 2.3.3.6 restore stored post object + $post = $stored_post; } // ------------------------------------ @@ -1287,11 +1331,15 @@ function radio_station_post_save_data( $post_id ) { add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_post', 10, 2 ); function radio_station_quick_edit_post( $column_name, $post_type ) { + global $post; + $stored_post = $post; + // 2.3.3.5: added fix for post type context if ( $post_type != 'post' ) { return; } + // --- get all shows --- $args = array( 'numberposts' => - 1, 'offset' => 0, @@ -1311,16 +1359,29 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { echo ''; } else { // --- no shows message --- - echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); + echo esc_html( __( 'No Shows available to Select.', 'radio-station' ) ); } echo ''; echo '
    '; echo ''; + + // --- related shows post box styles --- + // 2.3.3.6: add style for pre-selected option + echo ""; + + // 2.3.3.6: restore stored post object + $post = $stored_post; } // --------------------------------- @@ -1340,9 +1401,15 @@ function radio_station_post_columns( $columns ) { add_action( 'manage_post_posts_custom_column', 'radio_station_post_column_data', 5, 2 ); function radio_station_post_column_data( $column, $post_id ) { if ( 'show' == $column ) { + + // 2.3.3.6: store global post object while capability checking + global $post; + $stored_post = $post; + + // --- get Shows linked to Post --- $data = ''; - $show_ids = array(); - $show_id = get_post_meta( $post_id, 'post_showblog_id', true ); + $show_ids = $disabled = array(); + $show_id = get_post_meta( $post_id, 'post_showblog_id', true ); if ( $show_id ) { if ( is_array( $show_id ) ) { $show_ids = $show_id; @@ -1352,17 +1419,32 @@ function radio_station_post_column_data( $column, $post_id ) { $data = $show_id; } } + + // --- display Shows linked to post --- if ( count( $show_ids ) > 0 ) { foreach ( $show_ids as $show_id ) { $show = get_post( trim( $show_id ) ); if ( $show ) { - echo ''; - echo esc_html( $show->post_title ) . '
    '; - echo '
    '; + // 2.3.3.6: only link to Shows user can edit + $post = $show; + if ( current_user_can( 'edit_shows' ) ) { + echo ''; + } else { + // 2.3.3.6: set disabled (uneditable) data + $disabled[] = $show_id; + } + echo esc_html( $show->post_title ) . '
    '; + if ( current_user_can( 'edit_shows' ) ) { + echo '
    '; + } } } } echo ''; + echo ''; + + // --- restore global post object --- + $post = $stored_post; } } @@ -1371,6 +1453,7 @@ function radio_station_post_column_data( $column, $post_id ) { // ------------------------------ // 2.3.3.4: added Related Show Quick Edit value population script // ref: https://codex.wordpress.org/Plugin_API/Action_Reference/quick_edit_custom_box +// 2.3.3.6: disable uneditable Show select options add_action( 'admin_enqueue_scripts', 'radio_station_posts_quick_edit_script' ); function radio_station_posts_quick_edit_script( $hook ) { @@ -1379,11 +1462,11 @@ function radio_station_posts_quick_edit_script( $hook ) { } if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { - $js = "(function($) { + $js = "(function($) { var \$wp_inline_edit = inlineEditPost.edit; inlineEditPost.edit = function( id ) { \$wp_inline_edit.apply(this, arguments); - var post_id = 0; + var post_id = 0; var disabled_ids; if (typeof(id) == 'object') {post_id = parseInt(this.getId(id));} if (post_id > 0) { var show_ids = \$('#post-'+post_id+' .column-show .show-ids').text(); @@ -1391,14 +1474,28 @@ function radio_station_posts_quick_edit_script( $hook ) { if (show_ids.indexOf(',') > -1) {ids = show_ids.split(',');} else {ids = new Array(); ids[0] = show_ids;} for (i = 0; i < ids.length; i++) { - var thisshowid = ids[i]; + var thisshowid = ids[i]; \$('#edit-'+post_id+' .select-show option').each(function() { if (\$(this).val() == thisshowid) {\$(this).attr('selected','selected');} }); } + /* disable uneditable options */ + disabled = \$('#post-'+post_id+' .column-show .disabled-ids').text(); + if (disabled != '') { + if (disabled.indexOf(',') > -1) {disabled_ids = disabled.split(',');} + else {disabled_ids = new Array(); disabled_ids[0] = disabled;} + \$('#edit-'+post_id+' .select-show option').each(function() { + for (j = 0; j < disabled_ids.length; j++) { + if (\$(this).val() == disabled_ids[j]) { + \$(this).attr('disabled','disabled'); + if (\$(this).attr('selected') == 'selected') {\$(this).addClass('pre-selected');} + } + } + }); + } } } - }; + }; })(jQuery);"; wp_add_inline_script( 'radio-station-admin', $js ); @@ -1437,7 +1534,7 @@ function radio_station_show_posts_bulk_edit_script( $hook ) { } else { \$(this).find('.related-show-field').remove(); } - }); + }); });"; wp_add_inline_script( 'radio-station-admin', $js ); @@ -1450,18 +1547,50 @@ function radio_station_show_posts_bulk_edit_script( $hook ) { // 2.3.3.4: add handler for bulk edit action add_filter( 'handle_bulk_actions-edit-post', 'radio_station_posts_bulk_edit_handler', 10, 3 ); function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids ) { + + global $post; + $stored_post = $post; + if ( 'related_show' !== $action ) { return $redirect_to; - } else if ( !isset($_REQUEST['post_showblog_id'] ) || ( '' == $_REQUEST['post_showblog_id'] ) ) { + } elseif ( !isset($_REQUEST['post_showblog_id'] ) || ( '' == $_REQUEST['post_showblog_id'] ) ) { return $redirect_to; } $show_ids = $_REQUEST['post_showblog_id']; + + // 2.3.3.6: check that user can edit specified Shows + $posted_show_ids = array(); + if ( count( $show_ids ) > 0 ) { + foreach ( $show_ids as $show_id ) { + $post = get_post( $show_id ); + if ( current_user_can( 'edit_shows' ) ) { + $posted_show_ids[] = $show_id; + } + } + } + + // --- loop post IDs to update --- $updated_post_ids = $failed_post_ids = array(); foreach ( $post_ids as $post_id ) { $post = get_post( $post_id ); if ( $post ) { - update_post_meta( $post_id, 'post_showblog_id', $show_ids ); + + // 2.3.3.6: keep existing (non-editable) related Shows for post + $existing_show_ids = array(); + $current_ids = get_post_meta( $post_id, 'post_showblog_id', true ); + if ( $current_ids && is_array( $current_ids ) && ( count( $current_ids ) > 0 ) ) { + foreach ( $current_ids as $i => $current_id ) { + $post = get_post( $current_id ); + if ( !current_user_can( 'edit_shows' ) ) { + $existing_show_ids[] = $current_id; + } + } + } + $new_show_ids = array_merge( $posted_show_ids, $existing_show_ids ); + + // --- update to new show IDs --- + update_post_meta( $post_id, 'post_showblog_id', $new_show_ids ); $updated_post_ids[] = $post_id; } else { $failed_post_ids[] = $post_id; @@ -1475,6 +1604,9 @@ function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids $redirect_to = add_query_arg( 'radio_station_related_show_failed', count( $failed_post_ids ), $redirect_to ); } + // --- restore stored post --- + $post = $stored_post; + return $redirect_to; } @@ -1526,7 +1658,7 @@ function radio_station_posts_list_styles() { if ( 'edit-post' !== $currentscreen->id ) { return; } - + // --- post list styles --- echo ""; } @@ -4020,7 +4163,7 @@ function radio_station_override_date_filter( $post_type, $which ) { if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { return; } - + // --- get all show override months / years --- global $wpdb; $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; @@ -4094,7 +4237,7 @@ function radio_station_columns_query_filter( $query ) { if ( !is_admin() || !$query->is_main_query() ) { return; } - + // --- Shows by Shift Days Filtering --- if ( RADIO_STATION_SHOW_SLUG === $query->get( 'post_type' ) ) { @@ -4118,7 +4261,7 @@ function radio_station_columns_query_filter( $query ) { } if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { - + $post_id = $result['ID']; $shifts = radio_station_get_show_schedule( $post_id ); @@ -4214,7 +4357,7 @@ function radio_station_columns_query_filter( $query ) { // 2.3.3.5: added option for today selection $valid = array( 'past', 'today', 'future' ); if ( isset( $_GET['pastfuture'] ) && in_array( $_GET['pastfuture'], $valid ) ) { - + $date = date( 'Y-m-d', time() ); $yesterday = date( 'Y-m-d', time() - ( 24 * 60 * 60 ) ); $tomorrow = date( 'Y-m-d', time() + ( 24 * 60 * 60 ) ); @@ -4229,7 +4372,7 @@ function radio_station_columns_query_filter( $query ) { $compare = '>'; $value = $date; } - + $pastfuture_query = array( 'key' => 'show_override_date', 'value' => $value, diff --git a/includes/support-functions.php b/includes/support-functions.php index 66835a9..a8cf4a2 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1601,7 +1601,7 @@ function radio_station_get_current_show( $time = false ) { } // --- set current show --- - // 2.3.3: get current show directly amd remove transient + // 2.3.3: get current show directly and remove transient if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { if ( RADIO_STATION_DEBUG ) { echo '^^^ Current ^^^' . PHP_EOL; @@ -3996,11 +3996,13 @@ function radio_station_get_language_options( $include_wp_default = false ) { $languages[$lang] = $translation['native_name']; } } + // set_transient( 'radio-station-language-options', $languages, 24 * 60 * 60 ); } // --- maybe include WordPress default language --- if ( $include_wp_default ) { - $wp_language = array( '', __( 'WordPress Setting', 'radio-station' ) ); + // 2.3.3.6: fix to array for WordPress language setting + $wp_language = array( '' => __( 'WordPress Setting', 'radio-station' ) ); $languages = array_merge( $wp_language, $languages ); } @@ -4020,13 +4022,17 @@ function radio_station_get_language( $lang = false ) { if ( !$lang ) { $main = true; $lang = radio_station_get_setting( 'radio_language' ); - if ( !$lang || ( '' == $lang ) ) { + // 2.3.3.6: add fallback for value of 1 due to language options bug + if ( !$lang || ( '' == $lang ) || ( '0' == $lang ) || ( '1' == $lang ) ) { $lang = get_option( 'WPLANG' ); if ( !$lang ) { $lang = 'en_US'; } } } + if ( isset( $_REQUEST['lang-debug'] ) && ( '1' == $_REQUEST['lang-debug'] ) ) { + echo PHP_EOL . "LANG: " . print_r( $lang, true ) . PHP_EOL; + } // --- get the specified language term --- $term = get_term_by( 'slug', $lang, RADIO_STATION_LANGUAGES_SLUG ); @@ -4064,6 +4070,9 @@ function radio_station_get_language( $lang = false ) { $language = false; } } + if ( isset( $_REQUEST['lang-debug'] ) && ( '1' == $_REQUEST['lang-debug'] ) ) { + echo PHP_EOL . "LANGUAGE: " . print_r( $language, true ) . PHP_EOL; + } return $language; } diff --git a/radio-station.php b/radio-station.php index 7d4c8b3..50c5818 100644 --- a/radio-station.php +++ b/radio-station.php @@ -713,7 +713,7 @@ 'tab' => 'widgets', 'section' => 'loading', ), - + // --- Dynamic Reloading --- 'dynamic_reload' => array( 'type' => 'checkbox', @@ -933,14 +933,14 @@ function radio_station_check_version() { // ----------------- // Plugin Activation // ----------------- -// (run on plugin activation, and thus also after a plugin update) +// (run on plugin activation, and thus also after a plugin update) // 2.2.8: fix for mismatched flag function name register_activation_hook( __FILE__, 'radio_station_plugin_activation' ); function radio_station_plugin_activation() { // --- flag to flush rewrite rules --- // 2.2.3: added this for custom post types rewrite flushing add_option( 'radio_station_flush_rewrite_rules', true ); - + // --- clear schedule transients --- // 2.3.3: added clear transients on (re)activation delete_transient( 'radio_station_current_schedule' ); @@ -966,7 +966,7 @@ function radio_station_enqueue_scripts() { // --- enqueue plugin script --- // 2.3.0: added jquery dependency for inline script fragments radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); - + // -- enqueue javascript timezone script --- // $suffix = '.min'; // if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { @@ -988,14 +988,14 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = $template = radio_station_get_template( 'both', $filename, 'js' ); if ( $template ) { - // 2.3.2: use plugin version for releases + // 2.3.2: use plugin version for releases $plugin_version = radio_station_plugin_version(); if ( 5 == strlen( $plugin_version ) ) { $version = $plugin_version; } else { $version = filemtime( $template['file'] ); } - + $url = $template['url']; // --- enqueue script --- @@ -1056,7 +1056,7 @@ function radio_station_localize_script() { // --- set AJAX URL --- // 2.3.2: add admin AJAX URL $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; - + // --- clock time format --- $clock_format = radio_station_get_setting( 'clock_time_format' ); $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; @@ -1075,7 +1075,7 @@ function radio_station_localize_script() { // --- radio timezone --- // 2.3.2: added get timezone function $timezone = radio_station_get_timezone(); - + // if ( isset( $offset ) ) { if ( stristr( $timezone, 'UTC' ) ) { @@ -1116,13 +1116,13 @@ function radio_station_localize_script() { $js .= "radio.timezone.utczone = false; "; } - + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $js .= "radio.timezone.adjusted = false; "; } else { $js .= "radio.timezone.adjusted = true; "; } - + // --- set user timezone offset --- // (and convert offset minutes to seconds) $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . PHP_EOL; @@ -1191,7 +1191,7 @@ function radio_station_localize_script() { // 2.3.2: added settings submenu page redirection fix add_action( 'init', 'radio_station_settings_page_redirect' ); function radio_station_settings_page_redirect() { - + // --- bug out if not admin page --- if ( !is_admin() ) { return; @@ -1204,8 +1204,8 @@ function radio_station_settings_page_redirect() { // --- check if link is for options-general.php --- if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { - - // --- redirect to plugin settings page (admin.php) --- + + // --- redirect to plugin settings page (admin.php) --- $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); wp_redirect( $url ); exit; @@ -1812,13 +1812,13 @@ function radio_station_add_show_links( $content ) { // --- filter to allow related post types --- $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - + if ( in_array( $post->post_type, $post_types ) ) { // --- link show posts --- $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); if ( $related_show ) { - $positions = array( 'after' ); + $positions = array( 'after' ); $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post->post_type ); if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { @@ -1852,16 +1852,16 @@ function radio_station_add_show_links( $content ) { $content = $before . $content . $after; } } - + // --- adjacent post links debug test --- if ( RADIO_STATION_DEBUG ) { $content .= "Previous Link: " . get_previous_post_link() . "
    "; $content .= "Next Link: " . get_next_post_link() . "
    "; } } - - } - + + } + return $content; } @@ -1880,7 +1880,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); if ( in_array( $post->post_type, $post_types ) ) { if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - // TODO: get next/previous for override time/date + // TODO: get next/previous for override time/date } else { $shifts = get_post_meta( $post->id, 'show_sched', true ); if ( $shifts && is_array( $shifts ) ) { @@ -1904,7 +1904,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } } } - + // --- generate adjacent post link --- if ( isset( $show ) ) { $adjacent_post = get_post( $show['id'] ); @@ -1928,7 +1928,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po if ( !$link_show_posts ) { return $output; } - + // --- get related show --- $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); if ( !$related_show ) { @@ -2128,7 +2128,13 @@ function radio_station_set_roles() { if ( !is_array( $role_caps ) ) { $role_caps = array(); } - $producer_caps = array( 'edit_producers', 'edit_published_producers', 'delete_producers', 'read_producers', 'publish_producers' ); + $producer_caps = array( + 'edit_producers', + 'edit_published_producers', + 'delete_producers', + 'read_producers', + 'publish_producers', + ); foreach ( $producer_caps as $cap ) { if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { $wp_roles->add_cap( 'producer', $cap, true ); @@ -2317,23 +2323,38 @@ function radio_station_set_roles() { // maybe Revoke Edit Show Capability // --------------------------------- // (revoke ability to edit show if user is not assigned to it) -add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 3 ); -function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { +add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 4 ); +function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { global $post, $wp_roles; + // --- get the current user --- + // 2.3.3.6: get user object from fourth argument instead + // $user = wp_get_current_user(); + // --- check if super admin --- // ? fix to not revoke edit caps from super admin ? - // (not implemented, causing connection reset error) + // (not implemented, as causing a connection reset error) // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { // return $allcaps; // } - // --- get the current user --- - $user = wp_get_current_user(); + // --- debug passed capability arguments --- + if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { + echo 'Cap Args: ' . print_r( $args, true ) . ''; + } + + // --- check for editor + // 2.3.3.6: check editor roles first separately + $editor_roles = array( 'administrator', 'editor', 'show-editor' ); + foreach ( $editor_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + return $allcaps; + } + } - // --- get roles with publish shows capability --- - $edit_show_roles = array( 'administrator' ); + // --- get roles with edit shows capability --- + $edit_show_roles = array(); if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { foreach ( $wp_roles->roles as $name => $role ) { // 2.3.0: fix to skip roles with no capabilities assigned @@ -2349,8 +2370,6 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { } } } - - // --- check if current user has any of these roles --- // 2.2.8: remove strict in_array checking $found = false; foreach ( $edit_show_roles as $role ) { @@ -2359,7 +2378,9 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args ) { } } - if ( !$found ) { + // --- maybe revoked edit show capability for post --- + // 2.3.3.6: fix to incorrect logic for removing edit show capability + if ( $found ) { // --- limit this to published shows --- // 2.3.0: added object and property_exists check to be safe diff --git a/readme.txt b/readme.txt index c913ea2..0b7ac28 100644 --- a/readme.txt +++ b/readme.txt @@ -184,6 +184,11 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == += 2.3.3.6 = +* Fixed: Edit permissions checks for Related to Show post assignments +* Fixed: Main Language option value for WordPress Setting +* Fixed: make Date on Tab clickable on Tabbed Schedule View + = 2.3.3.5 = * Fixed: use schedule based on start_day if specified for Schedule view * Fixed: day left/right shifting on Schedule table/tab mobile views diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 124e596..89a42a8 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -62,6 +62,7 @@ $show_email = apply_filters( 'radio_station_show_email', $show_email, $post_id ); $show_patreon = apply_filters( 'radio_station_show_patreon', $show_patreon, $post_id ); $show_rss = apply_filters( 'radio_station_show_rss', $show_rss, $post_id ); +$social_icons = apply_filters( 'radio_station_show_social_icons', false, $post_id ); // --- create show icon display early --- // 2.3.0: converted show links to icons @@ -181,7 +182,8 @@ } // --- show controls --- - if ( ( count( $show_icons ) > 0 ) || ( $show_file ) ) { + // 2.3.3.6: modify check to include social icons or patreon only + if ( ( count( $show_icons ) > 0 ) || $social_icons || $show_patreon || $show_file ) { $blocks['show_images'] .= '
    '; @@ -191,6 +193,16 @@ $blocks['show_images'] .= implode( "\n", $show_icons ); $blocks['show_images'] .= '
    '; } + // --- Social Icons --- + // 2.3.3.6: added filter for social icon display output + if ( $social_icons ) { + $social_icons = apply_filters( 'radio_station_show_social_icons_display', '' ); + if ( '' != $social_icons ) { + $blocks['show_images'] .= ''; + } + } // --- Show Patreon Button --- $show_patreon_button = ''; @@ -210,7 +222,7 @@ // 2.3.3.4: add filter for optional title above Show Player (default empty) // 2.3.3.4: add filter for title text on Show Download link icon if ( $show_file ) { - + $blocks['show_images'] .= '
    '; $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); if ( $label && ( '' != $label ) ) { @@ -233,7 +245,7 @@ $blocks['show_images'] .= ''; $blocks['show_images'] .= '
    '; } - + $blocks['show_images'] .= '
    '; } @@ -255,7 +267,7 @@ // --- Show DJs / Hosts --- // 2.3.3.4: added filter for hosted by label // 2.3.3.4: replace bold title tag with span and class - if ( $hosts ) { + if ( $hosts ) { $blocks['show_meta'] .= '
    '; $label = __( 'Hosted by', 'radio-station' ); $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); @@ -423,7 +435,7 @@ } } } - + if ( ( 0 == $offset ) || ( '' == $offset ) ) { $utc_offset = ''; } elseif ( $offset > 0 ) { @@ -488,7 +500,7 @@ $start = radio_station_get_time( $start_data_format, $shift_start_time ); $end = radio_station_get_time( $end_data_format, $shift_end_time ); $start = radio_station_translate_time( $start ); - $end = radio_station_translate_time( $end ); + $end = radio_station_translate_time( $end ); // --- check if current shift --- $classes = array( 'show-shift-time' ); @@ -906,6 +918,6 @@ radio_show_tab('about');} } } - }, 500);"; + }, 500);"; wp_add_inline_script( 'radio-station-show-page', $js ); } From da6d56cc348a870b2417a54608e119b1cdc9d811 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 14 Nov 2020 14:04:30 +1000 Subject: [PATCH 155/377] dev updates #280 --- CHANGELOG.md | 2 + freemius/assets/css/admin/account.css | 2 +- freemius/config.php | 5 +- freemius/includes/class-freemius.php | 113 +++- freemius/includes/class-fs-plugin-updater.php | 55 +- freemius/includes/fs-plugin-info-dialog.php | 26 +- freemius/includes/i18n.php | 2 + freemius/includes/sdk/FreemiusWordPress.php | 6 +- freemius/languages/freemius-cs_CZ.mo | Bin 55871 -> 59930 bytes freemius/languages/freemius-da_DK.mo | Bin 57521 -> 58864 bytes freemius/languages/freemius-en.mo | Bin 56845 -> 58177 bytes freemius/languages/freemius-es_ES.mo | Bin 60134 -> 61970 bytes freemius/languages/freemius-fr_FR.mo | Bin 61231 -> 62562 bytes freemius/languages/freemius-he_IL.mo | Bin 59889 -> 61224 bytes freemius/languages/freemius-hu_HU.mo | Bin 57975 -> 59307 bytes freemius/languages/freemius-it_IT.mo | Bin 59295 -> 60682 bytes freemius/languages/freemius-ja.mo | Bin 65079 -> 66410 bytes freemius/languages/freemius-nl_NL.mo | Bin 59339 -> 60674 bytes freemius/languages/freemius-ru_RU.mo | Bin 73391 -> 74731 bytes freemius/languages/freemius-ta.mo | Bin 91112 -> 92468 bytes freemius/languages/freemius-zh_CN.mo | Bin 52980 -> 55922 bytes freemius/languages/freemius.pot | 576 +++++++++--------- freemius/start.php | 2 +- freemius/templates/account.php | 23 + freemius/templates/forms/affiliation.php | 2 + loader.php | 119 +++- readme.txt | 2 + 27 files changed, 605 insertions(+), 330 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e900f..5fe3eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.3.3.6 +* Update: Freemius SDK (2.4.0) +* Update: Plugin Loader (1.1.5) with CSV validation fix * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View diff --git a/freemius/assets/css/admin/account.css b/freemius/assets/css/admin/account.css index 0e0c5a6..5582a0a 100644 --- a/freemius/assets/css/admin/account.css +++ b/freemius/assets/css/admin/account.css @@ -1 +1 @@ -label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #F1F1F1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:0.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type="text"]{color:#0073AA;font-size:16px;background:none}.fs-key-value-table input[type="text"]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} +label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id="license_not_whitelabeled"].success,.fs-notice[data-id="license_whitelabeled"].success{color:inherit;border-left-color:#00a0d2}.fs-notice[data-id="license_not_whitelabeled"].success label.fs-plugin-title,.fs-notice[data-id="license_whitelabeled"].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #F1F1F1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:0.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type="text"]{color:#0073AA;font-size:16px;background:none}.fs-key-value-table input[type="text"]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} diff --git a/freemius/config.php b/freemius/config.php index 1c6011b..f51eb40 100644 --- a/freemius/config.php +++ b/freemius/config.php @@ -385,4 +385,7 @@ if ( ! defined( 'WP_FS__DEMO_MODE' ) ) { define( 'WP_FS__DEMO_MODE', false ); - } \ No newline at end of file + } + if ( ! defined( 'FS_SDK__SSLVERIFY' ) ) { + define( 'FS_SDK__SSLVERIFY', false ); + } \ No newline at end of file diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index a905ba6..0536aaf 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -1674,6 +1674,7 @@ private function register_constructor_hooks() { $this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) ); $this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) ); $this->add_ajax_action( 'set_data_debug_mode', array( &$this, '_set_data_debug_mode' ) ); + $this->add_ajax_action( 'toggle_whitelabel_mode', array( &$this, '_toggle_whitelabel_mode_ajax_handler' ) ); if ( $this->_is_network_active && fs_is_network_admin() ) { $this->add_ajax_action( 'network_activate', array( &$this, '_network_activate_ajax_action' ) ); @@ -7736,7 +7737,10 @@ function _activate_plugin_event_hook() { * @author Leo Fajardo (@leorw) * @since 1.2.2 */ - if ( is_plugin_active( $other_version_basename ) ) { + if ( + is_plugin_active( $other_version_basename ) && + $this->apply_filters( 'deactivate_on_activation', true ) + ) { deactivate_plugins( $other_version_basename ); } } @@ -9021,12 +9025,25 @@ private function get_plugins_data_for_api() { 'is_uninstalled' => false, ); - $plugins_update_data[] = $new_plugin; $network_plugins_cache->plugins[ $basename ] = $new_plugin; + $is_site_level_active = ( + isset( $site_active_plugins[ $basename ] ) && + $site_active_plugins[ $basename ]['is_active'] + ); + + /** + * If not network active, set the activity status based on the site-level plugin status. + */ + if ( ! $new_plugin['is_active'] ) { + $new_plugin['is_active'] = $is_site_level_active; + } + + $plugins_update_data[] = $new_plugin; + if ( isset( $site_active_plugins[ $basename ] ) ) { $site_active_plugins_cache->plugins[ $basename ] = $new_plugin; - $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; + $site_active_plugins_cache->plugins[ $basename ]['is_active'] = $is_site_level_active; } } } @@ -13130,6 +13147,61 @@ function _add_premium_version_upgrade_selection() { } } + /** + * @author Edgar Melkonyan + * @since 2.4.1 + * + * @throws Freemius_Exception + */ + function _toggle_whitelabel_mode_ajax_handler() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'toggle_whitelabel_mode' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $license = $this->get_api_user_scope()->call( + "/licenses/{$this->_site->license_id}.json", + 'put', + array( 'is_whitelabeled' => ! $this->_license->is_whitelabeled ) + ); + + if ( ! $this->is_api_result_entity( $license ) ) { + self::shoot_ajax_failure( + FS_Api::is_api_error_object( $license ) ? + $license->error->message : + fs_text_inline( "An unknown error has occurred while trying to toggle the license's white-label mode.", 'unknown-error-occurred', $this->get_slug() ) + ); + } + + $this->_license->is_whitelabeled = $license->is_whitelabeled; + $this->_store_licenses(); + + $this->_sync_license(); + + if ( ! $license->is_whitelabeled ) { + $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); + } else { + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( + 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.', + 'license_whitelabeled' + ), + "{$this->get_plugin_title()}", + sprintf( '%s', $this->get_text_inline( 'User Dashboard', 'user-dashboard' ) ), + sprintf( '%s', $this->get_text_inline( 'revert it now', 'revert-it-now' ) ) + ), + 'license_whitelabeled' + ); + } + + self::shoot_ajax_response( array( 'success' => true ) ); + } + /** * @author Leo Fajardo (@leorw) * @since 2.3.0 @@ -20679,6 +20751,13 @@ private function _sync_plugin_license( } if ( 'none' !== $plan_change ) { + if ( + ! is_object( $this->_license ) || + ! $this->_license->is_whitelabeled + ) { + $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); + } + $this->do_action( 'after_license_change', $plan_change, $this->get_plan() ); } } @@ -22293,6 +22372,26 @@ function fs_addons_body_class( $classes ) { $this->_handle_account_edits(); + if ( + is_object( $this->_license ) && + $this->_license->user_id == $this->_user->id && + ! $this->is_whitelabeled( true ) + ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.", 'license_not_whitelabeled' ), + sprintf( + '%s', + $this->get_text_inline( 'Click here', 'click-here' ) + ) + ), + '', + 'success', + false, + 'license_not_whitelabeled' + ); + } + $this->do_action( 'account_page_load_before_departure' ); } @@ -23453,10 +23552,12 @@ function get_after_plugin_activation_redirect_url() { * @since 1.0.3 */ function _redirect_on_activation_hook() { - $url = $this->get_after_plugin_activation_redirect_url(); + if ( $this->apply_filters( 'redirect_on_activation', true ) ) { + $url = $this->get_after_plugin_activation_redirect_url(); - if ( is_string( $url ) ) { - fs_redirect( $url ); + if ( is_string( $url ) ) { + fs_redirect( $url ); + } } } diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php index 5a0c3dd..8f6b996 100644 --- a/freemius/includes/class-fs-plugin-updater.php +++ b/freemius/includes/class-fs-plugin-updater.php @@ -542,23 +542,66 @@ function pre_set_site_transient_update_plugins_filter( $transient_data ) { } } + // Alias. + $basename = $this->_fs->premium_plugin_basename(); + if ( is_object( $this->_update_details ) ) { + if ( isset( $transient_data->no_update ) ) { + unset( $transient_data->no_update[ $basename ] ); + } + if ( ! isset( $transient_data->response ) ) { $transient_data->response = array(); } // Add plugin to transient data. - $transient_data->response[ $this->_fs->premium_plugin_basename() ] = $this->_fs->is_plugin() ? + $transient_data->response[ $basename ] = $this->_fs->is_plugin() ? $this->_update_details : (array) $this->_update_details; - } else if ( isset( $transient_data->response ) ) { + } else { + if ( isset( $transient_data->response ) ) { + /** + * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + unset( $transient_data->response[ $basename ] ); + } + + if ( ! isset( $transient_data->no_update ) ) { + $transient_data->no_update = array(); + } + /** - * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. + * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI. * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 + * @since 2.4.1 + * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/ */ - unset( $transient_data->response[ $this->_fs->premium_plugin_basename() ] ); + $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ? + (object) array( + 'id' => $basename, + 'slug' => $this->_fs->get_slug(), + 'plugin' => $basename, + 'new_version' => $this->_fs->get_plugin_version(), + 'url' => '', + 'package' => '', + 'icons' => array(), + 'banners' => array(), + 'banners_rtl' => array(), + 'tested' => '', + 'requires_php' => '', + 'compatibility' => new stdClass(), + ) : + array( + 'theme' => $basename, + 'new_version' => $this->_fs->get_plugin_version(), + 'url' => '', + 'package' => '', + 'requires' => '', + 'requires_php' => '', + ); } $slug = $this->_fs->get_slug(); diff --git a/freemius/includes/fs-plugin-info-dialog.php b/freemius/includes/fs-plugin-info-dialog.php index 2e51e89..bcfce99 100644 --- a/freemius/includes/fs-plugin-info-dialog.php +++ b/freemius/includes/fs-plugin-info-dialog.php @@ -260,14 +260,20 @@ function _get_addon_info_filter( $data, $action = '', $args = null ) { if ( $has_valid_blog_id ) { restore_current_blog(); } + } - if ( is_object( $fs_addon ) ) { - $data->has_purchased_license = $fs_addon->has_active_valid_license(); - } else { - $account_addons = $this->_fs->get_account_addons(); - if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { - $data->has_purchased_license = true; - } + /** + * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.1 + */ + if ( is_object( $fs_addon ) ) { + $data->has_purchased_license = $fs_addon->has_active_valid_license(); + } else { + $account_addons = $this->_fs->get_account_addons(); + if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { + $data->has_purchased_license = true; } } @@ -577,7 +583,7 @@ private function get_plugin_actions( $api ) { $has_installed_version = ( 'install' !== $this->status['status'] ); - if ( ! $api->has_paid_plan ) { + if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { /** * Free-only add-on. * @@ -1203,7 +1209,7 @@ function install_plugin_information() { $(document).ready(function () { var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=id ?>]'); - $plan.find('input[type=radio]').live('click', function () { + $plan.find('input[type=radio]').on('click', function () { _updateCtaUrl( $plan.attr('data-plan-id'), $(this).val(), @@ -1635,4 +1641,4 @@ function toggleDropdown( $dropdown, state ) { iframe_footer(); exit; } - } \ No newline at end of file + } diff --git a/freemius/includes/i18n.php b/freemius/includes/i18n.php index 09f2546..34270f7 100644 --- a/freemius/includes/i18n.php +++ b/freemius/includes/i18n.php @@ -98,6 +98,8 @@ 'error' => _fs_text( 'Error' ), 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), 'learn-more' => _fs_text( 'Learn more' ), + 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."), + 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'), #region Affiliation 'affiliation' => _fs_text( 'Affiliation' ), diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index 9c9340c..0b80912 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -59,6 +59,10 @@ } } + if ( ! defined( 'FS_SDK__SSLVERIFY' ) ) { + define( 'FS_SDK__SSLVERIFY', false ); + } + $curl_version = FS_SDK__HAS_CURL ? curl_version() : array( 'version' => '7.37' ); @@ -401,7 +405,7 @@ private static function MakeStaticRequest( } if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { - $pWPRemoteArgs['sslverify'] = false; + $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; } if ( false !== $pBeforeExecutionFunction && diff --git a/freemius/languages/freemius-cs_CZ.mo b/freemius/languages/freemius-cs_CZ.mo index d841088676a239cd19c17a1ad6847da57ba6cf08..8452f9fa9ee5001a711e7cb70df10f39ab310da2 100644 GIT binary patch delta 14222 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 L4@WLNaN@rJ%s4+d delta 10127 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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index 29ce0d294973cceb49bd1e40a942bef8c094ea27..fd44d1b7108c9ab874f7f74afe5c37f9a09de764 100644 GIT binary patch delta 11110 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 diff --git a/freemius/languages/freemius-en.mo b/freemius/languages/freemius-en.mo index 306f7f86bd9133f90c5fa1b2a0581a46c006513c..01e52c1b6f0722c1a8630c9e492956308bb87cf4 100644 GIT binary patch delta 10963 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ħ?X^kxF2=j*Qg0x z!c6=bJ#_v%%r<}B>WZmcc!8=!@*MNmtqfFx-v2P?JQO2{>!bEYI@ZFm7>=7U1M{&u z#?3X~dgCY|`%0lI5QM%M ziT+s2_P>Std7&+~z(uIUZ{h2o|Nlcn1AELjf87efV#J$K{d=ruQ6>EmRnp&313p7P zEV95-LruDYevWE&`iETt?@b?&aQGdho7o{Wq};j$C49J{wCBFGu}xYa6nioO7R=1iHJHn!j%KLA?-epfdc! z>b=aYc{u7p)lk5euW(A6hMK_Q&&+SV8!?Tz+8R@#i5Ntj zjjG^I4AJ>NL!&Akcd#z{tu=43W~c{_!uB{5wYEQC6?}$z4}`5Vf0#(dR>Ucofb+30 zp24A5d%aoWwOEAsAdX>t=Qs^z*6?#v;!dbzG77c%vQQZ>vhgOYM0^M<;BE9oj~r8} z;;7RSgfZA0+u#_~cwb`@HrqfvhQ>@9`aqE{Oack00Xty^4n{Z3$7H;OHL&zXv!+c@ z$E&F|4a*bvLQQNU7Q;``1J|Mw-nfx`bnJ5N4JXi>_`J0MmB>9Tim$K^dTug%r2%Sh zv_K`8h8mzVYJi?L?uQ}7ndpr(Z2zK7)I%N1?S&1fW4IIDF&|6gW%R-O);}?ixX5Pn z*R6ASoA`q-&0n{=ZZQenMNQy7hGU_v=G9vfRguOn8XBkt7RI(1fbCJ6q#tUnhhTMF zfZBwI(HF0w61#)3_!w1*irdWESHZ!=9dQC4NBwcD$#zpI*UTNJRP(SbH!MXBybD#C z{ixFCp)$UJ8n^&;YJNd|nFQ=K_r+ly;`*qGWnw&zz$p9zwKSLTj`rgX8Y=P8UFO(b zM;$xQ-RAt3#W#qDp-P^EafB*IZ9EPAi05D+u0rjhUH1BU)G@qn9W~HO48a^!DUYElblP4&hf4e^>cKyt61$D6*dM3_3mr6x zmBM)9P#lgO4zm7(XynmR8RHL`OuM0GFc6j4c+`tzhII{UkL*V!a2`YP8kWUpsDw%# zHftV?+Kd%Z<3wRgY;)LUz816TcwL$!CUY-qAS#h?)b(1Z64ymlqzP)K%~5-$BPyZy zP>Byj?TIn=`W)2rvT+M;cF|Z!BmF4rijOfCXB;!H;;&Gf<~!8D?#GQKup4m*HpNjm z6%S(ork*gHxErc6nb-tpqY}D^Vd%O?<0BeHPnzF&W@9w*V=RK7D=l`+o_|tSauP}uh-M%)*tQqQCa0zNN zZA5L-1E^hn+4>V!BYuRQSpKw$D`5d~TVy~d`i!|=3yU(oQ=f)1ZDMVP?-RFqeFMM0 zq1N^QY7?He@lUA4en$=HcGmoy?~NUZ8(@E&i#la5aR|2m#{ArH6dN+WbDKsMMxHZY zuQ{kU-hI?sM&z5#lz`eiDX5Zm#AqCgf^3n%RC_%Kwa2!h_R4N7hKF1ey z`t?ISI2!ffc+_(nqc5f)U;9pbRH7YG&vm8K(2aep15pDHMIRiCdhj%ReU9y4fO^ns zRN^_P0XCsGZb3iXhx+_k>tzfeE>5fXUA8Me%HXeq$ zZzSrz2{xW?{RgV#OHhfeKqa&qmEd;t(fL0}LpPp5J@A6{I%>e1SPySw6Ab&_B=jEY zdOy@s3_?|GJSw4&F$CwMp8utF2dZ*kp_ew*5gMA|8S8m_!xdCQ1*i)BY_I=jeS~_@ zGt_;rZ0zyR++vmEy>e&ITVEtMCB9dY+@tloyXDqd-J#h0wwr=;-)vSlGIz@0Cq?I< w8yA$@|9Y@T?!FI)c;qHbDO~*j`Ouz{xepJv^83G^Tv2p>qbbwoXWv@)UuHANHUIzs diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo index 2fdcd2c6cfb77202cb102b2f3f3febfafc2abb6c..dbcaa7ba5ec26a6e0d11cf16f46ea8ad32b2ccbb 100644 GIT binary patch delta 13289 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 diff --git a/freemius/languages/freemius-fr_FR.mo b/freemius/languages/freemius-fr_FR.mo index 64b4514af980e06b6b9103889a70f7c6a6c75d31..49f3dde638b48ffa07e99f4a94e680ee7163f5f5 100644 GIT binary patch delta 11034 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 O`LDW#oTr~JDfEAl$Km7v diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 81d33ee29b096e4e7a2e3be5373c15ce75c7a3a4..6b979f72aa3ebf41a1d9b219277811f0fcb496aa 100644 GIT binary patch delta 11028 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 OFZ6}>C5tbomibS^+=SBr delta 9832 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 Uu6<)fmBrVmXS&b)Qc&gp0CeWr-T(jq diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index 92e0aaba51a078cdecb25f583b4ba5b7137dbafb..45f0a636024c3ef84a5b2cf92e064c78bd8e96b3 100644 GIT binary patch delta 11036 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 BX`cW9 delta 9834 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 R79;eY>g>;kS8q9U_OCg|knsQj delta 9992 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 diff --git a/freemius/languages/freemius-ja.mo b/freemius/languages/freemius-ja.mo index d23880bd55ba36837187c8b166fe231cc52bf55b..26b71883610b680d562667f1390b57c6afa42fce 100644 GIT binary patch delta 11015 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 delta 9839 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 diff --git a/freemius/languages/freemius-nl_NL.mo b/freemius/languages/freemius-nl_NL.mo index 4c975e95a37186af1c4a69655acfa761424c5e58..36632c53fd562feb43326492aeaaeffc2c08b734 100644 GIT binary patch delta 11079 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 P3qv38;oWeldA0ulY|eN1 delta 9845 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 diff --git a/freemius/languages/freemius-ru_RU.mo b/freemius/languages/freemius-ru_RU.mo index 812c2d7c5195821403c37d00cbb5839cd7166f2f..e07a3d018cdccad9d20d584b094e153c1ac82230 100644 GIT binary patch delta 11019 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 delta 9823 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 YIGB5V@4_3~H)}^8oR0TBekT7v0C7u5LI3~& delta 10226 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 'toggle_whitelabel_mode' ) ?>', + security : 'get_ajax_security( 'toggle_whitelabel_mode' ) ?>', + module_id: get_id() ?> + }, + beforeSend: function () { + $toggleLink.parent().text( '' + '...' ); + }, + complete: function () { + location.reload(); + } + } ); + }); })(jQuery);

    +
    Referrer Program's terms & conditions.
    +
    diff --git a/loader.php b/loader.php index 99c21e2..f8c0b42 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.4 +// Version: 1.1.5 // -------------- // Note: Changelog and structure at end of file. // @@ -114,7 +114,7 @@ class radio_station_loader { // 1.1.2: added debug switch public $menu_added = false; public $debug = false; - + // ----------------- // Initialize Loader // ----------------- @@ -122,7 +122,7 @@ public function __construct( $args ) { // --- set debug switch --- // 1.1.2: added debug switch check - $prefix = ''; + $prefix = ''; if ( $args['settings'] ) { // 1.1.4: fix to debug prefix key $prefix = $args['settings'] . '-'; @@ -247,7 +247,7 @@ public function plugin_data( $key ) { return $value; } - + // ------------------ // Get Plugin Version // ------------------ @@ -288,7 +288,7 @@ public function default_settings( $dkey = false ) { // 1.1.2: fix to apply options filter $namespace = $this->namespace; $options = $this->options; - $options = apply_filters( $namespace . '_options', $options ); + $options = apply_filters( $namespace . '_options', $options ); $defaults = array(); foreach ( $options as $key => $values ) { // 1.0.9: set default to null if default value not set @@ -313,7 +313,7 @@ public function default_settings( $dkey = false ) { public function add_settings() { // --- add the default plugin settings --- - $args = $this->args; + $args = $this->args; $defaults = $this->default_settings(); $added = add_option( $args['option'], $defaults ); @@ -323,7 +323,7 @@ public function add_settings() { foreach ( $defaults as $key => $value ) { $GLOBALS[$namespace][$key] = $value; } - + // --- record first installed version --- // 1.1.0: added record for tracking first install version add_option( $args['option'] . '_first_install', $args['version'] ); @@ -572,7 +572,7 @@ public function update_settings() { } if ( $this->debug ) { - echo 'Saving Setting Key ' . $key . ' (' . $postkey . ': ' . print_r( $posted, true ) . '
    '; + echo 'Saving Setting Key ' . $key . ' (' . $postkey . '): ' . print_r( $posted, true ) . '
    '; echo 'Type: ' . $type . ' - Valid Options ' . $key . ': ' . print_r( $valid, true ) . '
    '; } @@ -643,7 +643,7 @@ public function update_settings() { $optionkey = $args['settings'] . '_' . $key . '-' . $option; if ( isset( $_POST[$optionkey] ) ) { // 1.1.2: check for value if specified - if ( ( isset( $values['value'] ) && ( $values['value'] == $_POST[$optionkey] ) ) + if ( ( isset( $values['value'] ) && ( $values['value'] == $_POST[$optionkey] ) ) || ( !isset( $values['value'] ) && ( 'yes' == $_POST[$optionkey] ) ) ) { // 1.1.0: fixed to save only array of key values $posted[] = $option; @@ -656,7 +656,6 @@ public function update_settings() { // -- comma separated values --- // 1.0.4: added comma separated values option - $values = array(); if ( strstr( $posted, ',' ) ) { $posted = explode( ',', $posted ); } else { @@ -698,9 +697,9 @@ public function update_settings() { if ( $this->debug ) { echo 'New Settings for Key ' . $key . ': '; if ( $newsettings ) { - echo '(to-validate) ' . var_dump( $newsettings, true ) . '
    '; + echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; } else { - echo '(validated) ' . var_dump( $settings[$key], true ) . '
    '; + echo '(validated) ' . print_r( $settings[$key], true ) . '
    '; } } @@ -723,10 +722,22 @@ public function update_settings() { $settings[$key] = $newsettings; } } else { + // --- validate single setting --- - $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args ); - if ( $newsetting ) { - $settings[$key] = $newsetting; + if ( 'csv' == $type ) { + // 1.1.5: fix to validate each of multiple CSV values + $values = explode( ',', $newsettings ); + $newvalues = array(); + foreach ( $values as $value ) { + $newvalues[] = $this->validate_setting( $value, $valid, $validate_args ); + } + $newsettings = implode( ',', $newvalues ); + $settings[$key] = $newsettings; + } else { + $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args ); + if ( $newsetting ) { + $settings[$key] = $newsetting; + } } } @@ -798,6 +809,7 @@ public function update_settings() { // ----------------------- // Validate Plugin Setting // ----------------------- + // 1.1.5: plural options now use extra validation public function validate_setting( $posted, $valid, $args ) { // --- allow for clearing of a field --- @@ -828,7 +840,7 @@ public function validate_setting( $posted, $valid, $args ) { // --- number (numeric text) --- // note: step key is only used for controls, not for validation - // 1.0.9: cast to integer not absolute integer + // 1.0.9: cast to integer - not absolute integer // TODO: validate step value match ? $posted = floatval( trim( $posted ) ); if ( isset( $args['min'] ) && ( $posted < $args['min'] ) ) { @@ -863,7 +875,6 @@ public function validate_setting( $posted, $valid, $args ) { if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) { $posted = ''; } - return $posted; } elseif ( in_array( $valid, array( 'EMAIL', 'EMAILS' ) ) ) { @@ -876,32 +887,65 @@ public function validate_setting( $posted, $valid, $args ) { } else { $posted = ''; } + return $posted; + + } elseif ( in_array( $valid, array( 'USEREMAIL', 'USEREMAILS' ) ) ) { + // --- email address with user validation --- + // 1.1.5: added option for validated user emails + $email = sanitize_email( trim( $posted ) ); + if ( !$email ) { + return ''; + } + $user = get_user_by( 'email', $email ); + if ( $user ) { + $posted = $username; + } else { + $posted = ''; + } return $posted; - } elseif ( in_array( $valid, array( 'USERNAME', 'USERNAMES' ) ) ) { + } elseif ( 'USERNAME' == $valid ) { // --- username --- $username = sanitize_user( trim( $posted ) ); if ( !$username ) { return ''; } + return $username; + + } elseif ( 'USERNAMES' == $valid ) { + + // --- username with check for existing user --- + // 1.1.5: separated check from singular + $username = sanitize_user( trim( $posted ) ); + if ( !$username ) { + return ''; + } $user = get_user_by( 'login', $username ); if ( $user ) { $posted = $username; } else { $posted = ''; } - return $posted; - } elseif ( in_array( $valid, array( 'USERID', 'USERIDS' ) ) ) { + } elseif ( 'USERID' == $valid ) { // --- user ID --- $userid = intval( trim( $posted ) ); if ( 0 === $userid ) { return ''; } + + } elseif ( 'USERIDS' == $valid ) { + + // --- user ID with check for existing user --- + // 1.1.5: separated check from singular + $userid = intval( trim( $posted ) ); + if ( 0 === $userid ) { + return ''; + } $user = get_user_by( 'ID', $userid ); if ( $user ) { $posted = $userid; @@ -918,7 +962,15 @@ public function validate_setting( $posted, $valid, $args ) { return $posted; - } elseif ( in_array( $valid, array( 'PAGEID', 'PAGEIDS', 'POSTID', 'POSTIDS' ) ) ) { + } elseif ( in_array( $valid, array( 'PAGEID', 'POSTID' ) ) ) { + + $posted = intval( trim( $posted ) ); + if ( 0 === $posted ) { + return ''; + } + return $posted; + + } elseif ( in_array( $valid, array( 'PAGEIDS', 'POSTIDS' ) ) ) { $posted = intval( trim( $posted ) ); if ( 0 === $posted ) { @@ -937,14 +989,14 @@ public function validate_setting( $posted, $valid, $args ) { // Delete Settings // --------------- public function delete_settings() { - + // TODO: check for plugin settings flag to delete settings ? // $delete_settings = $this->get_setting( 'delete_settings' ); // if ( $delete_settings ) { // $args = $this->args; // delete_option( $args['option'] ); // } - + // TODO: check for plugin settings flag to delete data? // $delete_data = $this->get_setting( 'delete_data' ); // if ( $delete_data ) { @@ -1376,7 +1428,7 @@ public function settings_menu() { // 1.0.8: change from function exists check // 1.0.9: change to filter usage to check if menu is manually added $menu_added = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args ); - + // 1.1.1: record to admin menu added switch $this->menu_added = $menu_added; @@ -1391,7 +1443,7 @@ public function settings_menu() { // 1.1.0: add admin notices boxer add_action( 'all_admin_notices', array( $this, 'notice_boxer' ), 999 ); } - + // ----------------- // Plugin Page Links // ----------------- @@ -1401,7 +1453,7 @@ public function plugin_links( $links, $file ) { if ( plugin_basename( $args['file'] ) == $file ) { // --- add settings link --- - // 1.1.1: fix to settings page link URL + // 1.1.1: fix to settings page link URL // (depending on whether top level menu or Settings submenu item) $page = 'options-general.php'; if ( $this->menu_added ) {$page = 'admin.php';} @@ -1483,9 +1535,9 @@ public function notice_boxer() { } else { document.getElementById('admin-notices-wrap').style.display = ''; document.getElementById('admin-notices-arrow').innerHTML= '▾'; - } + } } "; - + // --- modified from /wp-admin/js/common.js to move notices --- echo "jQuery(document).ready(function() { setTimeout(function() { @@ -1765,12 +1817,12 @@ public function settings_table() { $args = $this->args; $namespace = $this->namespace; $options = $this->options; - + // --- get plugin options and default settings --- // 1.1.2: fix for filtering of plugin options $options = $this->options; $options = apply_filters( $namespace . '_options', $options ); - + $defaults = $this->default_settings(); $settings = $this->get_settings( false ); @@ -2672,8 +2724,8 @@ function radio_station_delete_settings() { $instance->delete_settings(); } } - - + + // ----------- // Message Box @@ -2788,6 +2840,9 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.1.5 == +// - fix to validate multiple CSV values + // == 1.1.4 == // - fix for debug prefix key dash // - fix for weird get_settings glitch bug (isset failing?!) diff --git a/readme.txt b/readme.txt index 0b7ac28..a9e7128 100644 --- a/readme.txt +++ b/readme.txt @@ -185,6 +185,8 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == = 2.3.3.6 = +* Update: Freemius SDK (2.4.0) +* Update: Plugin Loader (1.1.5) with CSV validation fix * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View From db5d50f1d74484a748ac84af91a07ea508565096 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sat, 14 Nov 2020 14:06:26 +1000 Subject: [PATCH 156/377] readme fix --- CHANGELOG.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe3eb3..f1ae07e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.3.3.6 -* Update: Freemius SDK (2.4.0) +* Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.5) with CSV validation fix * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting diff --git a/readme.txt b/readme.txt index a9e7128..f337492 100644 --- a/readme.txt +++ b/readme.txt @@ -185,7 +185,7 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == = 2.3.3.6 = -* Update: Freemius SDK (2.4.0) +* Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.5) with CSV validation fix * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting From afb6e945c2e23139c746e22fe66e288ecbe305ea Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 16 Nov 2020 17:20:58 +1000 Subject: [PATCH 157/377] dev updates #281 --- CHANGELOG.md | 2 + includes/post-types-admin.php | 24 +++++++++- includes/support-functions.php | 88 ++++++++++++++++++++++------------ readme.txt | 2 + 4 files changed, 83 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ae07e..513807a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View +* Fixed: prevent possible conflicts with changes not saved reload message +* Fixed: do not conflict check Shift against self for last shift check ### 2.3.3.5 * Fixed: use schedule based on start_day if specified for Schedule view diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index caad0bc..03062a8 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2143,10 +2143,22 @@ function radio_station_show_shifts_metabox() { radio_check_shifts(); }" . PHP_EOL; + // 2.3.3.6: store possible existing onbeforeunload function + // (to help prevent conflicts with other plugins using this event) + $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . PHP_EOL; $js .= "function radio_check_shifts() { if (jQuery('.show-shift.changed').length) { - window.onbeforeunload = function() {return true;} - } else {window.onbeforeunload = null;} + if (!onbeforeunloadset) { + storedonbeforeunload = window.onbeforeunload; + window.onbeforeunload = function() {return true;} + onbeforeunloadset = true; + } + } else { + if (onbeforeunloadset) { + window.onbeforeunload = storedonbeforeunload; + onbeforeunloadset = false; + } + } }" . PHP_EOL; // --- add new shift --- @@ -3371,8 +3383,10 @@ function radio_station_show_save_data( $post_id ) { if ( $table['conflicts'] ) { radio_station_shifts_conflict_message(); } + // phpcs:ignore WordPress.Security.OutputNotEscaped echo $table['list']; + echo '
    '; echo '
    '; @@ -3380,6 +3394,12 @@ function radio_station_show_save_data( $post_id ) { echo ""; + // 2.3.3.6: clear changes may not have been saved window reload message + echo ""; + // --- alert on conflicts --- if ( $table['conflicts'] ) { $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); diff --git a/includes/support-functions.php b/includes/support-functions.php index a8cf4a2..0233aae 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -2579,26 +2579,35 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { if ( $check_shift ) { if ( RADIO_STATION_DEBUG ) { - echo "with Shift for Show " . $day_shift['show'] . ": "; + echo "...with Shift for Show " . $day_shift['show'] . ": "; echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; } // 2.3.2: improved shift checking logic - // [overlap] if the new shift starts before existing shift but end after existing shift starts - // [external] or starts before but ends after the existing shift end time - // [equal] or new shift starts at the same time as the existing shift - // [internal] or if the new shift starts after existing shift and ends before it ends - // [overlap] of the new shift starts after the existing shift but before it ends - // ...then there is a shift overlap conflict - if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) - || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) - || ( $shift_start_time == $day_shift_start_time ) - || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) - || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) ) { + // 2.3.3.6: separated logic for conflict match code + $conflict = false; + if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { + // if the new shift starts before existing shift but ends after existing shift starts + $conflict = 'start overlap'; + } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { + // ...or starts before but ends after the existing shift end time + $conflict = 'blockout overlap'; + } elseif ( ( $shift_start_time == $day_shift_start_time ) ) { + // ...or new shift starts at the same time as the existing shift + $conflict = 'equal start time'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { + // ...or if the new shift starts after existing shift and ends before it ends + $conflict = 'internal overlap'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { + // ...or the new shift starts after the existing shift but before it ends + $conflict = 'end overlap'; + } + if ( $conflict ) { + // --- if there is a shift overlap conflict --- $conflicts[] = $day_shift; if ( RADIO_STATION_DEBUG ) { - echo "^^^ CONFLICT ^^^" . PHP_EOL; + echo '^^^ CONFLICT ( ' . $conflict . ' ) ^^^' . PHP_EOL; } } } @@ -2616,7 +2625,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $first_shift_end_time = radio_station_to_time( $first_shift['date'] . ' ' . $shift_end ) + ( 7 * 24 * 60 * 60 ); if ( RADIO_STATION_DEBUG ) { - echo "with First Shift for Show " . $first_shift['show'] . ": "; + echo "...with First Shift for Show " . $first_shift['show'] . ": "; echo $first_shift['day'] . " - " . $first_shift['date'] . " - " . $first_shift['start'] . " (" . $first_shift_start_time . ")"; echo " to " . $first_shift['end'] . " (" . $first_shift_end_time . ")" . PHP_EOL; } @@ -2637,26 +2646,43 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // (for date based schedule overflow rechecking) if ( isset( $day_shift ) ) { - // --- check for new shift overlap using next week --- - $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); - $shift_end_time = $shift_end_time + ( 7 * 24 * 60 * 60 ); + // 2.3.3.6: added check to not last check shift against itself + if ( ( $day_shift['show'] != $show_id ) + || ( $day_shift['day'] != $shift['day'] ) + || ( $day_shift['start'] != $shift['start'] ) + || ( $day_shift['end'] != $shift['end'] ) ) { - if ( RADIO_STATION_DEBUG ) { - echo "with Last Shift (using next week):" . PHP_EOL; - echo radio_station_get_time( 'date', $shift_start_time ) . " - " . $shift['start'] . " (" . $shift_start_time . ")"; - echo " to " . $shift['end'] . " (" . $shift_end_time . ")" . PHP_EOL; - echo $day_shift['day'] . " - " . $day_shift['date'] . " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; - echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; - } + // --- check for new shift overlap using next week --- + $shift_start_time = $shift_start_time + ( 7 * 24 * 60 * 60 ); + $shift_end_time = $shift_end_time + ( 7 * 24 * 60 * 60 ); - if ( ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) - || ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) - || ( $shift_start_time == $day_shift_start_time ) - || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) - || ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) ) { - $conflicts[] = $day_shift; if ( RADIO_STATION_DEBUG ) { - echo "^^^ CONFLICT ^^^" . PHP_EOL; + echo "...with Last Shift (using next week):" . PHP_EOL; + // echo radio_station_get_time( 'date', $shift_start_time ) . " - " . $shift['start'] . " (" . $shift_start_time . ")"; + // echo " to " . $shift['end'] . " (" . $shift_end_time . ")" . PHP_EOL; + echo $day_shift['day'] . " - " . radio_station_get_time( 'date', $day_shift_start_time ); + echo " - " . $day_shift['start'] . " (" . $day_shift_start_time . ")"; + echo " to " . $day_shift['end'] . " (" . $day_shift_end_time . ")" . PHP_EOL; + } + + // 2.3.3.6: separated logic for conflict match code + $conflict = false; + if ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_start_time ) ) { + $conflict = 'start overlap'; + } elseif ( ( $shift_start_time < $day_shift_start_time ) && ( $shift_end_time > $day_shift_end_time ) ) { + $conflict = 'blockout overlap'; + } elseif ( $shift_start_time == $day_shift_start_time ) { + $conflict = 'equal start time'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_end_time < $day_shift_end_time ) ) { + $conflict = 'internal overlap'; + } elseif ( ( $shift_start_time > $day_shift_start_time ) && ( $shift_start_time < $day_shift_end_time ) ) { + $conflict = 'end overlap'; + } + if ( $conflict ) { + $conflicts[] = $day_shift; + if ( RADIO_STATION_DEBUG ) { + echo '^^^ CONFLICT ( ' . $conflict . ') ^^^' . PHP_EOL; + } } } } diff --git a/readme.txt b/readme.txt index f337492..9280c3d 100644 --- a/readme.txt +++ b/readme.txt @@ -190,6 +190,8 @@ You may translate the plugin into another language. Please visit our [WordPress * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View +* Fixed: prevent possible conflicts with changes not saved reload message +* Fixed: do not conflict check Shift against self for last shift check = 2.3.3.5 = * Fixed: use schedule based on start_day if specified for Schedule view From 75b9588a7e156542791c0ea060972136e98828c6 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 24 Nov 2020 18:15:55 +1000 Subject: [PATCH 158/377] dev updates #282 #259 --- CHANGELOG.md | 4 +- radio-station.php | 335 +++++++++++++++++++++++++++++++++------------- readme.txt | 4 +- 3 files changed, 248 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 513807a..a03d3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View * Fixed: prevent possible conflicts with changes not saved reload message -* Fixed: do not conflict check Shift against self for last shift check +* Fixed: do not conflict check Shift against itself for last shift check +* Fixed: link back to Show posts for related Show posts (allow multiple) +* Fixed: filter next/previous post link for (multiple) related Show posts ### 2.3.3.5 * Fixed: use schedule based on start_day if specified for Schedule view diff --git a/radio-station.php b/radio-station.php index 50c5818..3e608c8 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1803,6 +1803,7 @@ function radio_station_post_type_archive_template( $archive_template, $type, $te // Add Links to Back to Show // ------------------------- // 2.3.0: add links to show from show posts and playlists +// 2.3.3.6: allow for multiple related show post assignments add_filter( 'the_content', 'radio_station_add_show_links', 20 ); function radio_station_add_show_links( $content ) { @@ -1811,57 +1812,79 @@ function radio_station_add_show_links( $content ) { // note: playlists are linked via single-playlist-content.php template // --- filter to allow related post types --- - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + $post_types = array( 'post' ); + $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); if ( in_array( $post->post_type, $post_types ) ) { // --- link show posts --- - $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); - if ( $related_show ) { + $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); + if ( $related_shows ) { + + // 2.3.3.6: convert string value if not multiple + if ( !is_array( $related_shows ) ) { + $related_shows = array( $related_shows ); + } + $positions = array( 'after' ); - $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post->post_type ); + $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post->post_type, $post ); if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { + + // --- set related shows link(s) --- + // 2.3.3.6: get all related show links + $show_links = ''; + $hash_ref = '#show-' . str_replace( 'rs-', '', $post->post_type ) . 's'; + foreach ( $related_shows as $related_show ) { + $show = get_post( $related_show ); + $title = $show->post_title; + $permalink = get_permalink( $show->ID ) . $hash_ref; + if ( '' != $show_links ) { + $show_links .= ', '; + } + $show_links .= '' . esc_html( $title ) . ''; + } + + // --- set post type labels --- $before = $after = ''; - $show = get_post( $related_show ); $post_type_object = get_post_type_object( $post->post_type ); $singular = $post_type_object->labels->singular_name; $plural = $post_type_object->labels->name; - $permalink = get_permalink( $show->ID ); + + // --- before content links --- if ( in_array( 'before', $positions ) ) { - $show_link_before = sprintf( __( '%s for Show', 'radio-station' ), $singular ); - $show_link_before .= ': ' . $show->post_title . ''; - $before = $show_link_before . '

    '; - $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $show ); + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( '%s for Shows', 'radio-station' ), $singular ); + } else { + $label = sprintf( __( '%s for Show', 'radio-station' ), $singular ); + } + $before = $label . ': ' . $show_links . '

    '; + $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $related_shows ); } + + // --- after content links --- if ( in_array( 'after', $positions ) ) { - $post_type_sections = array( 'post' ); - if ( defined( 'RADIO_STATION_EPISODE_SLUG' ) ) { - $post_type_sections[] = RADIO_STATION_EPISODE_SLUG; - } - if ( in_array( $post->post_type, $post_type_sections ) ) { - $post_type_ref = '#show-' . str_replace( 'rs-', '', $post->post_type ) . 's'; - $anchor = sprintf( __( 'All %s for Show %s', 'radio-station' ), $plural, $show->post_title ); - $show_link_after = '← ' . $anchor . ''; - } - if ( isset( $show_link_after ) ) { - $after = '
    ' . $show_link_after; + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( 'More %s for Shows', 'radio-station' ), $plural ); + } else { + $label = sprintf( __( 'More %s for Show', 'radio-station' ), $plural ); } - $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $show ); + $after = '
    ' . $label . ': ' . $show_links; + $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $related_shows ); } $content = $before . $content . $after; } } - - // --- adjacent post links debug test --- - if ( RADIO_STATION_DEBUG ) { - $content .= "Previous Link: " . get_previous_post_link() . "
    "; - $content .= "Next Link: " . get_next_post_link() . "
    "; - } } } + // --- adjacent post links debug output --- + if ( RADIO_STATION_DEBUG ) { + $content .= 'Previous Post Link: ' . get_previous_post_link() . '' . PHP_EOL; + $content .= 'Next Post Link: ' . get_next_post_link() . '' . PHP_EOL; + } + return $content; } @@ -1880,33 +1903,103 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); if ( in_array( $post->post_type, $post_types ) ) { if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - // TODO: get next/previous for override time/date + // 2.3.3.6: get next/previous Show for override date/time + $sched = get_post_meta( $post->ID, 'show_override_sched', true ); + $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; + $time = radio_station_get_time( ( $override_start + 1 ) ); + if ( 'next' == $adjacent ) { + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $show = radio_station_get_previous_show( $time ); + } } else { - $shifts = get_post_meta( $post->id, 'show_sched', true ); + $shifts = get_post_meta( $post->ID, 'show_sched', true ); if ( $shifts && is_array( $shifts ) ) { if ( count( $shifts ) < 1 ) { - // TODO: get the most recent show shift ? + // 2.3.3.6: default to standard adjacent post link + return $output; } if ( 1 == count( $shifts ) ) { $shift = $shifts[0]; $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; $time = radio_station_get_time( ( $shift_start + 1 ) ); if ( 'next' == $adjacent ) { - $rel = 'next'; $show = radio_station_get_next_show( $time ); } elseif ( 'previous' == $adjacent ) { - $rel = 'prev'; $show = radio_station_get_previous_show( $time ); } } else { - // TODO: method for multiple shifts ? - return $output; + // 2.3.3.6: added method for Show with multiple shifts + $now = radio_station_get_now(); + $show_shifts = radio_station_get_current_schedule(); + if ( !$show_shifts ) { + return $output; + } + + // --- get upcoming shift for Show --- + $next_shift = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !$next_shift && ( $day_shift['show']['id'] == $post->ID ) ) { + if ( !isset( $last_shift ) ) { + $last_shift = $day_shift; + } + $start = $day_shift['date'] . ' ' . $day_shift['start']; + $start_time = radio_station_to_time( $start ); + $end = $day_shift['date'] . ' ' . $day_shift['end']; + $end_time = radio_station_to_time( $end ); + if ( ( $start_time > $now ) || ( $now < $end_time ) ) { + $next_shift = $day_shift; + } + } + } + } + if ( !$next_shift ) { + $next_shift = $last_shift; + } + // echo "Next Show Shift: " . print_r( $next_shift, true ); + + // --- reverse order for finding previous show shift --- + if ( 'previous' == $adjacent ) { + foreach ( $show_shifts as $day => $day_shifts ) { + $show_shifts[$day] = array_reverse( $day_shifts, true ); + } + $show_shifts = array_reverse( $show_shifts, true ); + } + + // --- loop shifts to find adjacent shift's Show --- + $found = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !isset( $first_shift ) && ( $day_shift['show']['id'] != $post->ID ) ) { + $first_shift = $day_shift; + } + // echo "Shift: " . print_r( $day_shift, true ) . PHP_EOL; + if ( !isset( $show ) ) { + if ( $found && ( $day_shift['show']['id'] != $post->ID ) ) { + $show = $day_shift['show']; + } elseif ( !$found ) { + if ( $next_shift == $day_shift ) { + $found = true; + } + } + } + } + } + if ( !isset( $show ) && isset( $first_shift ) ) { + $show = $first_shift['show']; + } } } } - // --- generate adjacent post link --- + // --- generate adjacent Show link --- if ( isset( $show ) ) { + if ( 'next' == $adjacent ) { + $rel = 'next'; + } elseif ( 'previous' == $adjacent ) { + $rel = 'prev'; + } $adjacent_post = get_post( $show['id'] ); $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); $string = ''; @@ -1920,8 +2013,9 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } // --- filter to allow related post types --- - $show_post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); - if ( in_array( $post->post_type, $show_post_types ) ) { + $related_post_types = array( 'post' ); + $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); + if ( in_array( $post->post_type, $related_post_types ) ) { // --- filter to allow disabling --- $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); @@ -1930,77 +2024,117 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } // --- get related show --- - $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); + $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); if ( !$related_show ) { return $output; } + if ( is_array( $related_show ) ) { + $related_shows = $related_show; + } else { + $related_shows = array( $related_show ); + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows A: ' . print_r( $related_shows, true ) . ''; + } + + // --- get more Shows related to this related Post --- + // 2.3.3.6: allow for multiple related posts + global $wpdb; + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%" . $related_shows[0] . "%'"; + if ( count( $related_shows ) > 1 ) { + foreach ( $related_show as $i => $show_id ) { + if ( $i > 0 ) { + $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%" . $show_id . "%'"; + } + } + } + $results = $wpdb->get_results( $query, ARRAY_A ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows B: ' . print_r( $results, true ) . ''; + } + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { + return $output; + } + $related_posts = array(); + foreach ( $results as $result ) { + $values = maybe_unserialize( $result['meta_value'] ); + if ( RADIO_STATION_DEBUG ) { + echo 'Post ' . $result['post_id'] . ' Related Show Values : ' . print_r( $values, true ) . ''; + } + // --- double check Show ID is actually a match --- + if ( ( $result['meta_value'] == $related_show ) || ( is_array( $values ) && array_intersect( $related_shows, $values ) ) ) { + // --- recheck post is of the same post type --- + $query = "SELECT post_type FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $result['post_id'] ); + $related_post_type = $wpdb->get_var( $query ); + if ( $related_post_type == $post->post_type ) { + $related_posts[] = $result['post_id']; + } + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts B: ' . print_r( $related_posts, true ) . ''; + } + if ( 0 == count( $related_posts ) ) { + return $output; + } // --- get adjacent post query --- + // 2.3.3.6: use post__in related post array instead of meta_query $args = array( - 'post_type' => $post->post_type, - 'meta_query' => array( - array( - 'key' => 'post_showblog_id', - 'value' => $related_show, - 'compare' => '=', - ), - ), - 'order_by' => 'post_date', + 'post_type' => $post->post_type, + 'posts_per_page' => 1, + 'orderby' => 'post_modified', + 'post__in' => $related_posts, + 'ignore_sticky_posts' => true, ); - // --- setup previous or next post --- + // --- setup for previous or next post --- + // 2.3.3.6: set date_query instead of meta_query $post_type_object = get_post_type_object( $post->post_type ); if ( 'previous' == $adjacent ) { - $rel = 'prev'; $args['order'] = 'DESC'; - $title = __( 'Previous Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - $show_posts = get_posts( $args ); + $args['date_query'] = array( array( 'before' => $post->post_date ) ); + $rel = 'prev'; + $title = __( 'Previous Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; } elseif ( 'next' == $adjacent ) { - $rel = 'next'; $args['order'] = 'ASC'; - $title = __( 'Next Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - $show_posts = get_posts( $args ); - } - - // --- loop posts to get adjacent post --- - $found_current_post = $adjacent_post = false; - if ( $show_posts && is_array( $show_posts ) && ( count( $show_posts ) > 0 ) ) { - foreach ( $show_posts as $show_post ) { - if ( $found_current_post ) { - $related_id = get_post_meta( $show_post->ID, 'post_showblog_id', true ); - // 2.3.3.4: handle possible multiple show post values - if ( ( is_array( $related_id ) && in_array( $related_show, $related_id ) ) - || ( !is_array( $related_id ) && ( $related_id == $related_show ) ) ) { - $adjacent_post = $show_post; - break; - } - } - if ( $show_post->ID == $post->ID ) { - $found_current_post = true; - } - } - - if ( $adjacent_post ) { + $args['date_query'] = array( array( 'after' => $post->post_date ) ); + $rel = 'next'; + $title = __( 'Next Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + } - // --- adjacent post title --- - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; - } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + // --- get the adjacent post --- + // 2.3.3.6: use date_query instead of looping posts + $show_posts = get_posts( $args ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts Args: ' . print_r( $args, true ) . ''; + } + if ( 0 == count( $show_posts ) ) { + return $output; + } + $adjacent_post = $show_posts[0]; + if ( RADIO_STATION_DEBUG ) { + echo 'Realted Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; + } - // --- adjacent post link --- - // (from get_adjacent_post_link) - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); + // --- adjacent post title --- + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - } + // --- adjacent post link --- + // (from function get_adjacent_post_link) + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); - } } return $output; @@ -2340,6 +2474,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // } // --- debug passed capability arguments --- + // TODO: get post object from args instead of global ? if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { echo 'Cap Args: ' . print_r( $args, true ) . ''; } @@ -2354,7 +2489,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { } // --- get roles with edit shows capability --- - $edit_show_roles = array(); + $edit_show_roles = $edit_others_shows_roles = array(); if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { foreach ( $wp_roles->roles as $name => $role ) { // 2.3.0: fix to skip roles with no capabilities assigned @@ -2366,10 +2501,24 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { $edit_show_roles[] = $name; } } + // 2.3.3.6: add check for edit-others_shows capability + if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_others_shows_roles ) ) { + $edit_others_shows_roles[] = $name; + } + } } } } } + + // 2.3.3.6: preserve if user has edit_others_shows capability + foreach ( $edit_others_shows_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + return $allcaps; + } + } + // 2.2.8: remove strict in_array checking $found = false; foreach ( $edit_show_roles as $role ) { diff --git a/readme.txt b/readme.txt index 9280c3d..cbf1165 100644 --- a/readme.txt +++ b/readme.txt @@ -191,7 +191,9 @@ You may translate the plugin into another language. Please visit our [WordPress * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View * Fixed: prevent possible conflicts with changes not saved reload message -* Fixed: do not conflict check Shift against self for last shift check +* Fixed: do not conflict check Shift against itself for last shift check +* Fixed: link back to Show posts for related Show posts (allow multiple) +* Fixed: filter next/previous post link for (multiple) related Show posts = 2.3.3.5 = * Fixed: use schedule based on start_day if specified for Schedule view From 48e1880ecac70140293fe88c09d34f75953c7356 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 26 Nov 2020 09:37:49 +1000 Subject: [PATCH 159/377] dev updates #282 --- includes/post-types-admin.php | 37 +++++++++++++++---- radio-station.php | 24 +++++++++--- .../legacy/show-blog-archive-template.php | 8 ++-- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 03062a8..a1ef95a 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1187,6 +1187,14 @@ function radio_station_post_show_metabox() { } elseif ( !is_array( $selected ) ) { $selected = array( $selected ); } + // 2.3.3.6: remove possible saved zero value + if ( count( $selected ) > 0 ) { + foreach ( $selected as $i => $selected ) { + if ( 0 == $selected ) { + unset( $selected[$i] ); + } + } + } echo '
    '; @@ -1283,7 +1291,8 @@ function radio_station_post_save_data( $post_id ) { } foreach ( $show_ids as $i => $show_id ) { $show_id = absint( trim( $show_id ) ); - if ( $show_id > -1 ) { + // 2.3.3.6: check show ID value is above zero not -1 + if ( $show_id > 0 ) { // 2.3.3.6: check edit Show capability before adding $post = get_post( $show_id ); if ( $post && current_user_can( 'edit_shows' ) && !in_array( $show_id, $new_show_ids ) ) { @@ -1411,10 +1420,16 @@ function radio_station_post_column_data( $column, $post_id ) { $show_ids = $disabled = array(); $show_id = get_post_meta( $post_id, 'post_showblog_id', true ); if ( $show_id ) { + // 2.3.3.6: add check to ignore possible saved zero value if ( is_array( $show_id ) ) { $show_ids = $show_id; + foreach ( $show_ids as $i => $show_id ) { + if ( 0 == $show_id ) { + unset( $show_ids[$i] ); + } + } $data = implode( ',', $show_id ); - } else { + } elseif ( $show_id > 0 ) { $show_ids = array( $show_id ); $data = $show_id; } @@ -1563,9 +1578,12 @@ function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids $posted_show_ids = array(); if ( count( $show_ids ) > 0 ) { foreach ( $show_ids as $show_id ) { - $post = get_post( $show_id ); - if ( current_user_can( 'edit_shows' ) ) { - $posted_show_ids[] = $show_id; + // 2.3.3.6: added check to ignore zero values + if ( 0 != $show_id ) { + $post = get_post( $show_id ); + if ( current_user_can( 'edit_shows' ) ) { + $posted_show_ids[] = $show_id; + } } } } @@ -1581,9 +1599,12 @@ function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids $current_ids = get_post_meta( $post_id, 'post_showblog_id', true ); if ( $current_ids && is_array( $current_ids ) && ( count( $current_ids ) > 0 ) ) { foreach ( $current_ids as $i => $current_id ) { - $post = get_post( $current_id ); - if ( !current_user_can( 'edit_shows' ) ) { - $existing_show_ids[] = $current_id; + // 2.3.3.6: added check to ignore possible zero values + if ( 0 != $current_id ) { + $post = get_post( $current_id ); + if ( !current_user_can( 'edit_shows' ) ) { + $existing_show_ids[] = $current_id; + } } } } diff --git a/radio-station.php b/radio-station.php index 3e608c8..10c4e0f 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1819,12 +1819,17 @@ function radio_station_add_show_links( $content ) { // --- link show posts --- $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); - if ( $related_shows ) { - - // 2.3.3.6: convert string value if not multiple - if ( !is_array( $related_shows ) ) { - $related_shows = array( $related_shows ); + // 2.3.3.6: convert string value if not multiple + if ( $related_shows && !is_array( $related_shows ) ) { + $related_shows = array( $related_shows ); + } + // 2.3.3.6: remove possible zero values + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); } + } + if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { $positions = array( 'after' ); $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post->post_type, $post ); @@ -2033,6 +2038,15 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } else { $related_shows = array( $related_show ); } + // 2.3.3.6: remove possible saved zero value + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } + } + if ( 0 == count( $related_shows ) ) { + return $output; + } if ( RADIO_STATION_DEBUG ) { echo 'Related Shows A: ' . print_r( $related_shows, true ) . ''; } diff --git a/templates/legacy/show-blog-archive-template.php b/templates/legacy/show-blog-archive-template.php index f9456de..c64fa3f 100644 --- a/templates/legacy/show-blog-archive-template.php +++ b/templates/legacy/show-blog-archive-template.php @@ -19,13 +19,13 @@ - $post_types, 'posts_per_page' => 10, @@ -33,13 +33,15 @@ 'order' => 'desc', 'meta_query' => array( array( - 'key' => 'post_showblog_id', - 'value' => $show_id, + 'key' => 'post_showblog_id', + 'value' => '"' . $show_id . '"', + 'compare' => 'LIKE', ), ), 'paged' => $paged, ); $archive_query = new WP_Query( $args ); + while ( $archive_query->have_posts() ) : $archive_query->the_post(); ?> From c53ff52b45e93d0439e0dc3f775329a95ae115f7 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 26 Nov 2020 11:15:49 +1000 Subject: [PATCH 160/377] dev updates #279 --- CHANGELOG.md | 1 + includes/master-schedule.php | 22 +++-- radio-station.php | 59 +++++++------- readme.txt | 1 + templates/master-schedule-list.php | 72 ++++++++--------- templates/master-schedule-table.php | 120 ++++++++++++++-------------- templates/master-schedule-tabs.php | 111 +++++++++++++------------ 7 files changed, 195 insertions(+), 191 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a03d3ed..c3b0d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: do not conflict check Shift against itself for last shift check * Fixed: link back to Show posts for related Show posts (allow multiple) * Fixed: filter next/previous post link for (multiple) related Show posts +* Fixed: automatic pages conflict where themes filter the_content early ### 2.3.3.5 * Fixed: use schedule based on start_day if specified for Schedule view diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 80bba7e..21b35d6 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -121,10 +121,16 @@ function radio_station_master_schedule( $atts ) { $atts['clock'] = 0; } + // 2.3.3.6: set new line for easier debug viewing + $newline = ''; + if ( RADIO_STATION_DEBUG ) { + $newline = "\n"; + } + // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views // 2.3.2: moved shortcode calls inside and added filters - $output .= '
    '; + $output .= '
    ' . $newline; $controls = array(); @@ -132,26 +138,26 @@ function radio_station_master_schedule( $atts ) { if ( $atts['clock'] ) { // --- radio clock --- - $controls['clock'] = '
    '; + $controls['clock'] = '
    ' . $newline; $clock_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); $controls['clock'] .= radio_station_clock_shortcode( $clock_atts ); - $controls['clock'] .= '
    '; + $controls['clock'] .= PHP_EOL . '
    ' . $newline; } elseif ( $atts['timezone'] ) { // --- radio timezone --- - $controls['timezone'] = '
    '; + $controls['timezone'] = '
    ' . $newline; $timezone_atts = apply_filters( 'radio_station_schedule_clock', array(), $atts ); $controls['timezone'] .= radio_station_timezone_shortcode( $timezone_atts ); - $controls['timezone'] .= '
    '; + $controls['timezone'] .= PHP_EOL . '
    ' . $newline; } // --- genre selector --- if ( $atts['selector'] ) { - $controls['selector'] = '
    '; + $controls['selector'] = '
    ' . $newline; $controls['selector'] .= radio_station_master_schedule_selector(); - $controls['selector'] .= '
    '; + $controls['selector'] .= PHP_EOL . '
    ' . $newline; } // 2.3.1: add filters for control order @@ -170,7 +176,7 @@ function radio_station_master_schedule( $atts ) { } } - $output .= '

    '; + $output .= '

    ' . $newline; // --- schedule display override --- // 2.3.1: add full schedule override filter diff --git a/radio-station.php b/radio-station.php index 10c4e0f..d77cc58 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1310,10 +1310,12 @@ function radio_station_get_template( $type, $template, $paths = false ) { // ------------------------------ // 2.3.0: standalone filter for automatic page content // 2.3.1: re-add filter so the_content can be processed multuple times -add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); -function radio_station_automatic_pages_content( $content ) { +// 2.3.3.6: set automatic content early and clear existing content +add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); +function radio_station_automatic_pages_content_set( $content ) { + + global $radio_station_data; - // global $radio_station_data; // if ( isset( $radio_station_data['doing_excerpt'] ) && $radio_station_data['doing_excerpt'] ) { // return $content; // } @@ -1334,11 +1336,6 @@ function radio_station_automatic_pages_content( $content ) { } } $shortcode = '[master-schedule' . $atts_string . ']'; - remove_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - $content = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - return $content; } } } @@ -1361,11 +1358,6 @@ function radio_station_automatic_pages_content( $content ) { } } $shortcode = '[shows-archive' . $atts_string . ']'; - remove_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - $content = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - return $content; } } } @@ -1388,11 +1380,6 @@ function radio_station_automatic_pages_content( $content ) { } } $shortcode = '[overrides-archive' . $atts_string . ']'; - remove_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - $content = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - return $content; } } } @@ -1415,11 +1402,6 @@ function radio_station_automatic_pages_content( $content ) { } } $shortcode = '[playlists-archive' . $atts_string . ']'; - remove_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - $content = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - return $content; } } } @@ -1442,18 +1424,39 @@ function radio_station_automatic_pages_content( $content ) { } } $shortcode = '[genres-archive' . $atts_string. ']'; - remove_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - $content = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content', 11 ); - return $content; } } } + // 2.3.3.6: moved out to reduce repetitive code + if ( isset( $shortcode ) ) { + remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); + // 2.3.1: re-add filter so the_content may be processed multuple times + add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + // 2.3.3.6: clear existing content to allow for interim filters + $content = ''; + } + + return $content; +} + +// ---------------------------------- +// Automatic Pages Content Set Filter +// ---------------------------------- +// 2.3.3.6: append existing automatic page content to allow for interim filters +add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); +function radio_station_automatic_pages_content_get( $content ) { + global $radio_station_data; + if ( isset( $radio_station_data['automatic_content'] ) ) { + $content .= $radio_station_data['automatic_content']; + } return $content; } + // ------------------------------ // Single Content Template Filter // ------------------------------ diff --git a/readme.txt b/readme.txt index cbf1165..6cb7243 100644 --- a/readme.txt +++ b/readme.txt @@ -194,6 +194,7 @@ You may translate the plugin into another language. Please visit our [WordPress * Fixed: do not conflict check Shift against itself for last shift check * Fixed: link back to Show posts for related Show posts (allow multiple) * Fixed: filter next/previous post link for (multiple) related Show posts +* Fixed: automatic pages conflict where themes filter the_content early = 2.3.3.5 = * Fixed: use schedule based on start_day if specified for Schedule view diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index b512223..289b873 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -7,8 +7,6 @@ $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); -// $am = str_replace( ' ', '', radio_station_translate_meridiem( 'am' ) ); -// $pm = str_replace( ' ', '', radio_station_translate_meridiem( 'pm' ) ); // --- set shift time formats --- // 2.3.2: set time formats early @@ -49,7 +47,7 @@ } // --- start list schedule output --- -$output .= '
      '; +$output .= '
        ' . $newline; $tcount = 0; // 2.3.0: loop weekdays instead of legacy master list @@ -122,23 +120,23 @@ // 2.2.2: use translate function for weekday string // 2.3.2: added optional display-date attribute $display_day = radio_station_translate_weekday( $weekday ); - $output .= '
      • '; + $output .= '
      • ' . $newline; $output .= ''; + $output .= '>' . esc_html( $display_day ) . '' . $newline; if ( $atts['display_date'] ) { - $output .= ' ' . esc_html( $date_subheading ) . ''; + $output .= ' ' . esc_html( $date_subheading ) . '' . $newline; } // 2.3.2: add output of day start and end times // 2.3.2: replace strtotime with to_time for timezones - $output .= ''; - $output .= ''; + $output .= '' . $newline; + $output .= '' . $newline; // --- open day list --- - $output .= '
          '; + $output .= '
            ' . $newline; // --- get shifts for this day --- if ( isset( $schedule[$weekday] ) ) { @@ -211,7 +209,7 @@ // --- open show list item --- $classlist = implode( ' ', $classes ); - $output .= '
          • '; + $output .= '
          • ' . $newline; // --- show avatar --- if ( $atts['show_image'] ) { @@ -219,13 +217,13 @@ $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'list' ); if ( $show_avatar ) { - $output .= '
            '; + $output .= '
            ' . $newline; if ( $show_link ) { $output .= '' . $show_avatar . ''; } else { $output .= $show_avatar; } - $output .= '
            '; + $output .= '
            ' . $newline; } } @@ -235,9 +233,9 @@ } else { $show_title = esc_html( $show['name'] ); } - $output .= ''; + $output .= '' . $newline; $output .= $show_title; - $output .= ''; + $output .= '' . $newline; // --- show hosts --- // 2.3.0: changed from show_djs @@ -251,13 +249,13 @@ $hosts .= ''; $hosts .= esc_html( __( 'with', 'radio-station' ) ); // 2.3.2: fix variable to close span tag - $hosts .= ' '; + $hosts .= ' ' . $newline; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . ''; + $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { $hosts .= esc_html( $host['name'] ); } @@ -274,9 +272,9 @@ // 2.3.3.5: fix to incorrect context value (tabs) $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'list' ); if ( $hosts ) { - $output .= '
            '; + $output .= '
            ' . $newline; $output .= $hosts; - $output .= '
            '; + $output .= '
            ' . $newline; } } @@ -325,13 +323,13 @@ $end = radio_station_translate_time( $end ); // 2.3.0: filter show time by show and context - $show_time = '' . esc_html( $start ) . ''; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' '; - $show_time .= '' . esc_html( $end ) . ''; + $show_time = '' . esc_html( $start ) . '' . $newline; + $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= '' . esc_html( $end ) . '' . $newline; $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'list' ); - $output .= '
            ' . $show_time . '
            '; - $output .= '
            '; + $output .= '
            ' . $show_time . '
            ' . $newline; + $output .= '
            ' . $newline; $tcount ++; } @@ -348,7 +346,7 @@ if ( 'on' == $show_encore ) { $output .= '
            '; $output .= esc_html( __( 'encore airing', 'radio-station' ) ); - $output .= '
            '; + $output .= '
    ' . $newline; } } @@ -361,11 +359,11 @@ $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'list' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $output .= '
    '; + $output .= ''; + $output .= '' . $newline; + $output .= '
    ' . $newline; } } @@ -373,16 +371,16 @@ // (defaults to on) // 2.3.0: add genres to list view if ( $atts['show_genres'] ) { - $output .= '
    '; + $output .= '
    ' . $newline; $genres = array(); if ( count( $terms ) > 0 ) { foreach ( $terms as $term ) { - $genres[] = '' . esc_html( $term->name ) . ''; + $genres[] = '' . esc_html( $term->name ) . '' . $newline; } $genre_display = implode( ', ', $genres ); $output .= esc_html( __( 'Genres', 'radio-station' ) ) . ': ' . $genre_display; } - $output .= '
    '; + $output .= '
    ' . $newline; } // --- show description --- @@ -403,21 +401,21 @@ $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'list' ); // --- output excerpt --- - $output .= '
    '; - $output .= $excerpt; - $output .= '
    '; + $output .= '
    ' . $newline; + $output .= $excerpt . $newline; + $output .= '
    ' . $newline; } - $output .= ''; + $output .= '' . $newline; } } - $output .= ''; + $output .= '' . $newline; // --- close master list day item --- - $output .= ''; + $output .= '' . $newline; } } // --- close master list --- -$output .= ''; +$output .= '' . $newline; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index d71b7f4..fe8103d 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -8,8 +8,6 @@ $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); $today = radio_station_get_time( 'day', $now ); -// $am = str_replace( ' ', '', radio_station_translate_meridiem( 'am' ) ); -// $pm = str_replace( ' ', '', radio_station_translate_meridiem( 'pm' ) ); // --- set shift time formats --- // 2.3.2: set time formats early @@ -49,15 +47,15 @@ } // --- clear floats --- -$output .= '
    '; +$output .= '
    ' . $newline; // --- start master program table --- -$output .= ''; +$output .= '
    ' . $newline; // --- weekday table headings row --- // 2.3.2: added hour column heading id -$output .= ''; -$output .= ''; +$output .= '' . $newline; +$output .= '' . $newline; foreach ( $weekdays as $i => $weekday ) { @@ -137,32 +135,32 @@ // 2.3.2: added check for optional display_date attribute $arrows = array( 'right' => '►', 'left' => '◄' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'table' ); - $output .= ''; + $output .= '' . $newline; } } -$output .= ''; +$output .= '' . $newline; // --- loop schedule hours --- $tcount = 0; @@ -176,7 +174,7 @@ } // --- start hour row --- - $output .= ''; + $output .= '' . $newline; // --- set data format for timezone conversions --- if ( 24 == (int) $atts['time'] ) { @@ -198,7 +196,7 @@ $class = implode( ' ', $classes ); // --- hour heading --- - $output .= ''; + $output .= '' . $newline; + $output .= '
    ' . $newline; + $output .= '
    ' . $newline; + $output .= '' . $newline; foreach ( $weekdays as $i => $weekday ) { @@ -390,7 +388,7 @@ if ( !isset( $cell ) ) { $cell = ''; } - $cell .= '
    '; + $cell .= '
    ' . $newline; if ( $showcontinued ) { @@ -398,8 +396,8 @@ $cell .= ' '; // 2.3.2: set shift times for highlighting - $cell .= ''; - $cell .= ''; + $cell .= '' . $newline; + $cell .= '' . $newline; } else { @@ -419,23 +417,23 @@ } $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'table' ); if ( $show_avatar ) { - $cell .= '
    '; + $cell .= '
    ' . $newline; if ( $show_link ) { - $cell .= '' . $show_avatar . ''; + $cell .= '' . $show_avatar . '' . $newline; } else { - $cell .= $show_avatar; + $cell .= $show_avatar . $newline; } - $cell .= '
    '; + $cell .= '
    ' . $newline; } // --- show title --- - $cell .= '
    '; + $cell .= '
    ' . $newline; if ( $show_link ) { - $cell .= '' . esc_html( $show['name'] ) . ''; + $cell .= '' . esc_html( $show['name'] ) . '' . $newline; } else { - $cell .= esc_html( $show['name'] ); + $cell .= esc_html( $show['name'] ) . $newline; } - $cell .= '
    '; + $cell .= '
    ' . $newline; // --- show DJs / hosts --- if ( $atts['show_hosts'] ) { @@ -445,7 +443,7 @@ $hosts .= ' '; $hosts .= esc_html( __( 'with', 'radio-station' ) ); - $hosts .= ' '; + $hosts .= ' ' . $newline; $count = 0; $hostcount = count( $show['hosts'] ); @@ -453,7 +451,7 @@ $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . ''; + $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { $hosts .= esc_html( $host['name'] ); } @@ -469,9 +467,9 @@ $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'table' ); if ( $hosts ) { - $cell .= '
    '; + $cell .= '
    ' . $newline; $cell .= $hosts; - $cell .= '
    '; + $cell .= '
    ' . $newline; } } @@ -521,14 +519,14 @@ $end = radio_station_translate_time( $end ); // --- set show time output --- - $show_time = '' . esc_html( $start ) . ''; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' '; - $show_time .= '' . esc_html( $end ) . ''; + $show_time = '' . esc_html( $start ) . '' . $newline; + $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= '' . esc_html( $end ) . '' . $newline; $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'table' ); // --- add show time to cell --- - $cell .= '
    ' . $show_time . '
    '; - $cell .= '
    '; + $cell .= '
    ' . $show_time . '
    ' . $newline; + $cell .= '
    ' . $newline; $tcount ++; } @@ -543,7 +541,7 @@ if ( 'on' == $show_encore ) { $cell .= '
    '; $cell .= esc_html( __( 'encore airing', 'radio-station' ) ); - $cell .= '
    '; + $cell .= '
    ' . $newline; } // --- show file --- @@ -555,11 +553,11 @@ // 2.3.2: check disable download meta $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $cell .= '
    '; + $cell .= ''; + $cell .= '' . $newline; + $cell .= '
    ' . $newline; } // --- show description --- @@ -580,14 +578,14 @@ $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'table' ); // --- output excerpt --- - $cell .= '
    '; - $cell .= $excerpt; - $cell .= '
    '; + $cell .= '
    ' . $newline; + $cell .= $excerpt . $newline; + $cell .= '
    ' . $newline; } } - $cell .= '
    '; + $cell .= '' . $newline; // 2.3.1: fix to ensure reset showcontinued flag in cell if ( isset( $shift['finished'] ) && $shift['finished'] ) { @@ -612,21 +610,21 @@ $cellclasses[] = 'no-shifts'; } $cellclass = implode( ' ', $cellclasses ); - $output .= ''; + $output .= '' . $newline; + $output .= '' . $newline; } } // --- close hour row --- - $output .= ''; + $output .= '' . $newline; } -$output .= '
    '; - $output .= '
    '; - $output .= '' . $arrows['left'] . ''; - $output .= '
    '; - $output .= '
    '; + $output .= '
    ' . $newline; + $output .= '
    ' . $newline; + $output .= '' . $arrows['left'] . '' . $newline; + $output .= '
    ' . $newline; + $output .= '
    ' . $newline; $output .= '
    '; + $output .= '>' . esc_html( $display_day ) . '
    ' . $newline; if ( $atts['display_date'] ) { - $output .= '
    ' . esc_html( $date_subheading ) . '
    '; + $output .= '
    ' . esc_html( $date_subheading ) . '
    ' . $newline; } - $output .= '
    '; - $output .= '
    '; - $output .= '' . $arrows['right'] . ''; - $output .= '
    '; + $output .= '' . $newline; + $output .= '
    ' . $newline; + $output .= '' . $arrows['right'] . '' . $newline; + $output .= '
    ' . $newline; // 2.3.2: add day start and end time date - $output .= ''; - $output .= ''; + $output .= '' . $newline; + $output .= '' . $newline; - $output .= '
    '; + $output .= '' . $newline; if ( isset( $_GET['hourdebug'] ) && ( '1' == $_GET['hourdebug'] ) ) { $output .= ''; @@ -210,10 +208,10 @@ $output .= '
    '; $output .= esc_html( $hour_display ); - $output .= '
    '; - $output .= '
    '; - $output .= '
    '; - $output .= '
    '; - $output .= "
    "; + $output .= '
    ' . $newline; + $output .= '
    ' . $newline; if ( isset( $cell ) ) { $output .= $cell; } - $output .= "
    "; - $output .= '
    '; +$output .= '' . $newline; if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { $output .= $shiftdebug; diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index fe5ada2..986f2fa 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -9,8 +9,6 @@ $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); $today = radio_station_get_time( 'day', $now ); -// $am = str_replace( ' ', '', radio_station_translate_meridiem( 'am' ) ); -// $pm = str_replace( ' ', '', radio_station_translate_meridiem( 'pm' ) ); // --- set shift time formats --- // 2.3.2: set time formats early @@ -50,7 +48,7 @@ } // --- start tabbed schedule output --- -$output .= '
      '; +$output .= '
        ' . $newline; $panels = ''; $tcount = 0; @@ -140,30 +138,30 @@ // 2.3.1: added (negative) return to arrow onclick functions $arrows = array( 'right' => '►', 'left' => '◄' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'tabs' ); - $output .= '
      • '; - $output .= '
        '; - $output .= '' . $arrows['left'] . ''; - $output .= '
        '; + $output .= '
      • ' . $newline; + $output .= '
        ' . $newline; + $output .= '' . $arrows['left'] . '' . $newline; + $output .= '
        ' . $newline; // 2.3.2: added optional display_date attribute and subheading - $output .= '
        '; + $output .= '
        ' . $newline; $output .= '
        '; + $output .= '>' . esc_html( $display_day ) . '
        ' . $newline; if ( $atts['display_date'] ) { - $output .= '
        ' . esc_html( $date_subheading ) . '
        '; + $output .= '
        ' . esc_html( $date_subheading ) . '
        ' . $newline; } - $output .= '
        '; + $output .= '
        ' . $newline; - $output .= '
        '; - $output .= '' . $arrows['right'] . ''; - $output .= '
        '; - $output .= '
        '; + $output .= '
        ' . $newline; + $output .= '' . $arrows['right'] . '' . $newline; + $output .= '
        ' . $newline; + $output .= '
        ' . $newline; // 2.3.2: add start and end day times for automatic highlighting - $output .= ''; - $output .= ''; + $output .= '' . $newline; + $output .= '' . $newline; $output .= '
      • '; // 2.2.7: separate headings from panels for tab view @@ -173,12 +171,12 @@ $classes[] = 'active-day-panel'; } $classlist = implode( ' ', $classes ); - $panels .= '
          '; + $panels .= '
            ' . $newline; // 2.3.2: added extra current day display // 2.3.3: use numeric day index for ID instead of weekday name $display_day = radio_station_translate_weekday( $weekday, false ); - $panels .= '
            '; + $panels .= '
            ' . $newline; $panels .= __( 'Viewing', 'radio-station' ) . ': ' . esc_html( $display_day ) . '
            '; // --- get shifts for this day --- @@ -276,7 +274,7 @@ // --- open list item --- $classlist = implode( ' ' , $classes ); - $panels .= '
          • '; + $panels .= '
          • ' . $newline; // --- Show Image --- // (defaults to display on) @@ -286,20 +284,20 @@ $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'tabs' ); if ( $show_avatar ) { - $panels .= '
            '; + $panels .= '
            ' . $newline; if ( $show_link ) { - $panels .= '' . $show_avatar . ''; + $panels .= '' . $show_avatar . '' . $newline; } else { $panels .= $show_avatar; } - $panels .= '
            '; + $panels .= '
            ' . $newline; } else { - $panels .= '
            '; + $panels .= '
            ' . $newline; } } // --- Show Information --- - $panels .= '
            '; + $panels .= '
            ' . $newline; // --- show title --- if ( $show_link ) { @@ -307,9 +305,9 @@ } else { $show_title = esc_html( $show['name'] ); } - $panels .= '
            '; - $panels .= $show_title; - $panels .= '
            '; + $panels .= '
            ' . $newline; + $panels .= $show_title . $newline; + $panels .= '
            ' . $newline; // --- show hosts --- if ( $atts['show_hosts'] ) { @@ -321,13 +319,13 @@ $host_count = count( $show['hosts'] ); $hosts .= ' '; $hosts .= esc_html( __( 'with', 'radio-station' ) ); - $hosts .= ' '; + $hosts .= ' ' . $newline; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . ''; + $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { $hosts .= esc_html( $host['name'] ); } @@ -343,10 +341,9 @@ $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'tabs' ); if ( $hosts ) { - $panels .= '
            '; - // phpcs:ignore WordPress.Security.OutputNotEscaped + $panels .= '
            ' . $newline; $panels .= $hosts; - $panels .= '
            '; + $panels .= '
            ' . $newline; } } @@ -397,20 +394,20 @@ $end = radio_station_translate_time( $end ); // 2.3.0: filter show time by show and context - $show_time = '' . esc_html( $start ) . ''; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' '; - $show_time .= '' . esc_html( $end ) . ''; + $show_time = '' . esc_html( $start ) . '' . $newline; + $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= '' . esc_html( $end ) . '' . $newline; $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'tabs' ); - $panels .= '
            ' . $show_time . '
            '; - $panels .= '
            '; + $panels .= '
            ' . $show_time . '
            ' . $newline; + $panels .= '
            ' . $newline; $tcount ++; } else { // 2.3.2: added for now playing check - $panels .= ''; - $panels .= ''; + $panels .= '' . $newline; + $panels .= '' . $newline; } @@ -426,7 +423,7 @@ if ( 'on' == $show_encore ) { $panels .= '
            '; $panels .= esc_html( __( 'encore airing', 'radio-station' ) ); - $panels .= '
            '; + $panels .= '
            ' . $newline; } } @@ -439,30 +436,30 @@ $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'tabs' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $panels .= '
            '; + $panels .= ''; + $panels .= '' . $newline; + $panels .= '
            ' . $newline; } } // --- show genres --- // (defaults to display on) if ( $atts['show_genres'] ) { - $panels .= '
            '; + $panels .= '
            ' . $newline; $genres = array(); if ( count( $terms ) > 0 ) { foreach ( $terms as $term ) { - $genres[] = '' . esc_html( $term->name ) . ''; + $genres[] = '' . esc_html( $term->name ) . '' . $newline; } $genre_display = implode( ', ', $genres ); $panels .= esc_html( __( 'Genres', 'radio-station' ) ) . ': ' . $genre_display; } - $panels .= '
            '; + $panels .= '
            ' . $newline; } - $panels .= '
            '; + $panels .= '
          • ' . $newline; // --- show description --- if ( $atts['show_desc'] ) { @@ -482,9 +479,9 @@ $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'tabs' ); // --- output excerpt --- - $panels .= '
            '; - $panels .= $excerpt; - $panels .= '
            '; + $panels .= '
            ' . $newline; + $panels .= $excerpt . $newline; + $panels .= '
            ' . $newline; } @@ -494,20 +491,20 @@ if ( !$foundshows ) { // 2.3.2: added no shows class - $panels .= '
          • '; + $panels .= '
          • ' . $newline; $panels .= esc_html( __( 'No Shows scheduled for this day.', 'radio-station' ) ); - $panels .= '
          • '; + $panels .= '' . $newline; } } - $panels .= '
          '; + $panels .= '
        ' . $newline; } -$output .= '
      '; +$output .= '
    ' . $newline; -$output .= '
    '; +$output .= '
    ' . $newline; $output .= $panels; -$output .= '
    '; +$output .= '
    ' . $newline; if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { $output .= $shiftdebug; From 86abcaf877099feebb39ff340a9c12488496c29d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 27 Nov 2020 12:50:37 +1000 Subject: [PATCH 161/377] dev updates #255 --- CHANGELOG.md | 1 + includes/master-schedule.php | 87 ++++++++++++++++++----------- readme.txt | 1 + templates/master-schedule-list.php | 13 +++++ templates/master-schedule-table.php | 11 +++- templates/master-schedule-tabs.php | 15 ++++- 6 files changed, 93 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b0d8f..334cdae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.6 * Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.5) with CSV validation fix +* Improved: current Show highlighting on Schedule for overnight shifts * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 21b35d6..09b7fed 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -457,6 +457,7 @@ function radio_station_master_schedule_table_js() { // 2.3.2: added current show highlighting cycle // 2.3.2: fix to currenthour substr // 2.3.3.5: change selected day and arrow logic (to single day shifting) + // 2.3.3.6: also highlight split shift via matching shift class $js = "/* Initialize Table */ jQuery(document).ready(function() { radio_table_responsive(false); @@ -475,10 +476,9 @@ function radio_times_highlight() { if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} jQuery('.master-program-day').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (start < radio.offset_time) { - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (end > radio.offset_time) {jQuery(this).addClass('current-day');} - else {jQuery(this).removeClass('current-day');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + jQuery(this).addClass('current-day'); } else {jQuery(this).removeClass('current-day');} }); jQuery('.master-program-hour').each(function() { @@ -490,16 +490,23 @@ function radio_times_highlight() { if (hour == currenthour) {jQuery(this).addClass('current-hour');} else {jQuery(this).removeClass('current-hour');} }); + var radio_active_shift = false; jQuery('.master-show-entry').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (radio.debug) {console.log(start);} - if (start < radio.offset_time) { - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (end > radio.offset_time) { - jQuery(this).addClass('nowplaying'); - if (radio.debug) {console.log('^^^^^^^');} - } else {jQuery(this).removeClass('nowplaying');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (radio.debug) {console.log(start+' - '+end);} + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + jQuery(this).addClass('nowplaying'); + if (radio.debug) {console.log('^ Now Playing ^');} + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_active_shift = classes[i];} + } + } } else {jQuery(this).removeClass('nowplaying');} + if (radio_active_shift) {jQuery('.'+radio_active_shift).addClass('nowplaying');} }); } @@ -630,6 +637,7 @@ function radio_station_master_schedule_tabs_js() { // 2.3.0: added for tabbed responsiveness // 2.3.2: display selected day message if outside view // 2.3.3.5: change selected day and arrow logic (to single day shifting) + // 2.3.3.6: also highlight split shift via matching shift class $js .= "/* Initialize Tabs */ jQuery(document).ready(function() { radio.schedule_tabinit = false; @@ -664,26 +672,32 @@ function radio_show_highlight() { if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} jQuery('.master-schedule-tabs-day').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (start < radio.offset_time) { - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (end > radio.offset_time) { - jQuery(this).addClass('current-day'); - day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - radio_set_active_tab(day); - } else {jQuery(this).removeClass('current-day');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + jQuery(this).addClass('current-day'); + day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); + radio_set_active_tab(day); } else {jQuery(this).removeClass('current-day');} }); radio_set_active_tab(false); /* fallback */ + var radio_active_split = false; jQuery('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (radio.debug) {console.log(start);} - if (start < radio.offset_time) { - if (radio.debug) {console.log('^^^^^^^');} - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (end > radio.offset_time) {jQuery(this).addClass('nowplaying');} - else {jQuery(this).removeClass('nowplaying');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (radio.debug) {console.log(start+' - '+end);} + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + if (radio.debug) {console.log('^ Now Playing ^');} + jQuery(this).addClass('nowplaying'); + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_active_split = classes[i];} + } + } } else {jQuery(this).removeClass('nowplaying');} }); + if (radio_active_split) {jQuery('.'+radio_active_split).addClass('nowplaying');} } /* Make Tabs Responsive */ @@ -808,6 +822,7 @@ function radio_shift_tab(leftright) { function radio_station_master_schedule_list_js() { // --- list view javascript --- + // 2.3.3.6: also highlight split shift via matching shift class $js = "/* Initialize List */ jQuery(document).ready(function() { radio_list_highlight(); @@ -820,20 +835,28 @@ function radio_list_highlight() { if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} jQuery('.master-list-day').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').first().attr('data')); - if (start < radio.offset_time) { - end = parseInt(jQuery(this).find('.rs-end-time').first().attr('data')); - if (end > radio.offset_time) {jQuery(this).addClass('current-day');} - else {jQuery(this).removeClass('current-day');} + end = parseInt(jQuery(this).find('.rs-end-time').first().attr('data')); + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + jQuery(this).addClass('current-day'); } else {jQuery(this).removeClass('current-day');} }); + var radio_active_list; jQuery('.master-list-day-item').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - if (start < radio.offset_time) { - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (end > radio.offset_time) {jQuery(this).addClass('nowplaying');} - else {jQuery(this).removeClass('nowplaying');} + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + if (radio.debug) {console.log('^ Now Playing ^');} + jQuery(this).addClass('nowplaying'); + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_active_list = classes[i];} + } + } } else {jQuery(this).removeClass('nowplaying');} }); + if (radio_active_list) {jQuery('.'+radio_active_list).addClass('nowplaying');} }"; // --- enqueue script inline --- diff --git a/readme.txt b/readme.txt index 6cb7243..33b3364 100644 --- a/readme.txt +++ b/readme.txt @@ -187,6 +187,7 @@ You may translate the plugin into another language. Please visit our [WordPress = 2.3.3.6 = * Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.5) with CSV validation fix +* Improved: current Show highlighting on Schedule for overnight shifts * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 289b873..9ce162b 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -76,6 +76,10 @@ if ( !$skip_day ) { + // 2.3.3.6: set next and previous day for split shift IDs + $nextday = radio_station_get_next_day( $weekday ); + $prevday = radio_station_get_previous_day( $weekday ); + // 2.3.2: move up time calculations for optional date display $day_start_time = radio_station_to_time( $weekdates[$weekday] . ' 00:00' ); $day_end_time = $day_start_time + ( 24 * 60 * 60 ); @@ -150,6 +154,7 @@ foreach ( $shifts as $shift ) { $show = $shift['show']; + $split_id = false; // --- convert shift time data --- // 2.3.2: replace strtotime with to_time for timezones @@ -182,9 +187,11 @@ if ( isset( $shift['real_start'] ) ) { $real_shift_start = radio_station_convert_shift_time( $shift['real_start'] ); $real_shift_start = radio_station_to_time( $weekdate . ' ' . $real_shift_start ) - ( 24 * 60 * 60 ); + $split_id = strtolower( $prevday . '-' . $weekday ); } elseif ( isset( $shift['real_end'] ) ) { $real_shift_end = radio_station_convert_shift_time( $shift['real_end'] ); $real_shift_end = radio_station_to_time( $weekdate . ' ' . $real_shift_end ) + ( 24 * 60 * 60 ); + $split_id = strtolower( $weekday . '-' . $nextday ); } } @@ -194,6 +201,7 @@ $show_link = apply_filters( 'radio_station_schedule_show_link', $show['url'], $show['id'], 'list' ); } + // --- list item classes --- // 2.3.0: add genre classes for highlighting $classes = array( 'master-list-day-item' ); $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); @@ -206,6 +214,11 @@ if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { $classes[] = 'nowplaying'; } + // 2.3.3.6: add overnight split ID for highlighting + if ( $split_id ) { + $classes[] = 'overnight'; + $classes[] = 'split-' . $split_id; + } // --- open show list item --- $classlist = implode( ' ', $classes ); diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index fe8103d..ced4061 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -252,6 +252,7 @@ $shifts = array(); } $nextday = radio_station_get_next_day( $weekday ); + $prevday = radio_station_get_previous_day( $weekday ); // --- get weekdates --- $weekdate = $weekdates[$weekday]; @@ -267,6 +268,8 @@ // --- loop the shifts for this day --- foreach ( $shifts as $shift ) { + $split_id = false; + if ( !isset( $shift['finished'] ) || !$shift['finished'] ) { // --- get shift start and end times --- @@ -293,9 +296,11 @@ if ( isset( $shift['real_start'] ) ) { $real_shift_start = radio_station_convert_shift_time( $shift['real_start'] ); $real_shift_start = radio_station_to_time( $weekdate . ' ' . $real_shift_start ) - ( 24 * 60 * 60 ); + $split_id = strtolower( $prevday . '-' . $weekday ); } elseif ( isset( $shift['real_end'] ) ) { $real_shift_end = radio_station_convert_shift_time( $shift['real_end'] ); $real_shift_end = radio_station_to_time( $weekdate . ' ' . $real_shift_end ) + ( 24 * 60 * 60 ); + $split_id = strtolower( $weekday . '-' . $nextday ); } } @@ -382,6 +387,10 @@ $divclasses[] = sanitize_title_with_dashes( $genre ); } } + if ( $split_id ) { + $divclasses[] = 'overnight'; + $divclasses[] = 'split-' . $split_id; + } $divclass = implode( ' ', $divclasses ); // --- start the cell contents --- @@ -627,5 +636,5 @@ $output .= '' . $newline; if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - $output .= $shiftdebug; + $output .= '
    Shift Debug Info:
    ' . $shiftdebug . '
    '; } diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 986f2fa..8aba71c 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -79,6 +79,10 @@ if ( !$skip_day ) { + // 2.3.3.6: set next and previous day for split shift IDs + $nextday = radio_station_get_next_day( $weekday ); + $prevday = radio_station_get_previous_day( $weekday ); + // 2.3.2: set day start and end times // 2.3.2: replace strtotime with to_time for timezones $day_start_time = radio_station_to_time( $weekdates[$weekday] . ' 00:00' ); @@ -198,6 +202,7 @@ $j++; $show = $shift['show']; + $split_id = false; $show_link = false; if ( $atts['show_link'] ) { @@ -236,9 +241,11 @@ if ( isset( $shift['real_start'] ) ) { $real_shift_start = radio_station_convert_shift_time( $shift['real_start'] ); $real_shift_start = radio_station_to_time( $weekdate . ' ' . $real_shift_start ) - ( 24 * 60 * 60 ); + $split_id = strtolower( $prevday . '-' . $weekday ); } elseif ( isset( $shift['real_end'] ) ) { $real_shift_end = radio_station_convert_shift_time( $shift['real_end'] ); $real_shift_end = radio_station_to_time( $weekdate . ' ' . $real_shift_end ) + ( 24 * 60 * 60 ); + $split_id = strtolower( $weekday . '-' . $nextday ); } } @@ -266,11 +273,15 @@ if ( $j == count( $shifts ) ) { $classes[] = 'last-show'; } - // 2.3.2: check for now playing shift if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { $classes[] = 'nowplaying'; } + // 2.3.3.6: add overnight split ID for highlighting + if ( $split_id ) { + $classes[] = 'overnight'; + $classes[] = 'split-' . $split_id; + } // --- open list item --- $classlist = implode( ' ' , $classes ); @@ -507,5 +518,5 @@ $output .= '
    ' . $newline; if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - $output .= $shiftdebug; + $output .= '
    Shift Debug Info:
    ' . $shiftdebug . '
    '; } From 8076eda13fe295bbd1db11b7f78c2109bc581e53 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 1 Dec 2020 11:40:41 +1000 Subject: [PATCH 162/377] dev updates #212 #283 --- .idea/2.3.0.iml | 18 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/php.xml | 11 + .idea/workspace.xml | 207 ++++++++++++++++ CHANGELOG.md | 5 +- css/rs-templates.css | 9 + includes/master-schedule.php | 76 ++++-- includes/post-types-admin.php | 41 ++- loader.php | 26 +- radio-station.php | 52 +++- readme.txt | 5 +- templates/single-show-content.php | 358 +++++++++++++++++---------- 14 files changed, 653 insertions(+), 174 deletions(-) create mode 100644 .idea/2.3.0.iml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/2.3.0.iml b/.idea/2.3.0.iml new file mode 100644 index 0000000..eff601d --- /dev/null +++ b/.idea/2.3.0.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9cd4114 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..cdd202d --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..f116fb0 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,207 @@ + + + + + + + + + $PROJECT_DIR$/composer.json + + + + + + + + + + + + + + + + + + + + 1575948187082 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C:\!WP\InstantWP_4.5\iwpserver\htdocs\wordpress + + + + + + file://$PROJECT_DIR$/radio-station.php + 487 + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 334cdae..e805c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.6 * Update: Freemius SDK (2.4.1) -* Update: Plugin Loader (1.1.5) with CSV validation fix +* Update: Plugin Loader (1.1.6) with phone number and CSV validation +* Added: Station phone number setting with default display option +* Added: Schedule classes for Shows before and after current Show * Improved: current Show highlighting on Schedule for overnight shifts +* Improved: info section reordering filters on single Show template * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View diff --git a/css/rs-templates.css b/css/rs-templates.css index 485b39e..7c709a9 100644 --- a/css/rs-templates.css +++ b/css/rs-templates.css @@ -80,6 +80,15 @@ margin-top: 5px; } +#show-content .show-icon span:hover, #show-content .show-download span:hover { + opacity: 0.8; + text-decoration: none; +} + +#show-content .show-icon .show-download a, #show-content .show-icon .show-download a:visited { + text-decoration: none; +} + #show-content .show-embed .mejs-container { border-radius: 15px; overflow: hidden; diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 09b7fed..5e59fdf 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -490,24 +490,36 @@ function radio_times_highlight() { if (hour == currenthour) {jQuery(this).addClass('current-hour');} else {jQuery(this).removeClass('current-hour');} }); - var radio_active_shift = false; - jQuery('.master-show-entry').each(function() { - start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); - end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); - if (radio.debug) {console.log(start+' - '+end);} - if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - jQuery(this).addClass('nowplaying'); - if (radio.debug) {console.log('^ Now Playing ^');} - /* also highlight split shift via matching shift class */ - if (jQuery(this).hasClass('overnight')) { - classes = jQuery(this).attr('class').split(/\s+/); - for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_active_shift = classes[i];} + var radio_active_shift = false; var radio_table_current = false; + for (i = 0; i < 7; i++) { + jQuery('#master-program-schedule .day-'+i).each(function() { + jQuery(this).find('.master-show-entry').each(function() { + start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); + end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); + if (radio.debug) {console.log(start+' - '+end);} + if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + if (radio.debug) {console.log('^ Now Playing ^');} + radio_table_current = true; + jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + /* also highlight split shift via matching shift class */ + if (jQuery(this).hasClass('overnight')) { + classes = jQuery(this).attr('class').split(/\s+/); + for (i = 0; i < classes.length; i++) { + if (classes[i].substr(0,6) == 'split-') {radio_active_shift = classes[i];} + } + } + } else { + jQuery(this).removeClass('nowplaying'); + if (radio_table_current) {jQuery(this).removeClass('before-current').addClass('after-current');} + else {jQuery(this).addClass('before-current').removeClass('after-current');} } - } - } else {jQuery(this).removeClass('nowplaying');} - if (radio_active_shift) {jQuery('.'+radio_active_shift).addClass('nowplaying');} - }); + if (radio_active_shift) { + jQuery('.'+radio_active_shift).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + } + }); + }); + } + if (!radio_table_current) {jQuery('.master-show-entry').removeClass('before-current');} } /* Make Table Responsive */ @@ -680,14 +692,15 @@ function radio_show_highlight() { } else {jQuery(this).removeClass('current-day');} }); radio_set_active_tab(false); /* fallback */ - var radio_active_split = false; + var radio_active_split = false; var radio_tabs_current = false; jQuery('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (radio.debug) {console.log(start+' - '+end);} if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + radio_tabs_current = true; if (radio.debug) {console.log('^ Now Playing ^');} - jQuery(this).addClass('nowplaying'); + jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); /* also highlight split shift via matching shift class */ if (jQuery(this).hasClass('overnight')) { classes = jQuery(this).attr('class').split(/\s+/); @@ -695,9 +708,16 @@ classes = jQuery(this).attr('class').split(/\s+/); if (classes[i].substr(0,6) == 'split-') {radio_active_split = classes[i];} } } - } else {jQuery(this).removeClass('nowplaying');} + } else { + jQuery(this).removeClass('nowplaying'); + if (radio_tabs_current) {jQuery(this).removeClass('before-current').addClass('after-current');} + else {jQuery(this).addClass('before-current').removeClass('after-current');} + } }); - if (radio_active_split) {jQuery('.'+radio_active_split).addClass('nowplaying');} + if (radio_active_split) { + jQuery('.'+radio_active_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + } + if (!radio_tabs_current) {jQuery('.master-schedule-tabs-show').removeClass('before-current');} } /* Make Tabs Responsive */ @@ -840,11 +860,12 @@ function radio_list_highlight() { jQuery(this).addClass('current-day'); } else {jQuery(this).removeClass('current-day');} }); - var radio_active_list; + var radio_active_list = false; var radio_list_current = false; jQuery('.master-list-day-item').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if ( (start < radio.offset_time) && (end > radio.offset_time) ) { + radio_list_current = true; if (radio.debug) {console.log('^ Now Playing ^');} jQuery(this).addClass('nowplaying'); /* also highlight split shift via matching shift class */ @@ -854,9 +875,16 @@ classes = jQuery(this).attr('class').split(/\s+/); if (classes[i].substr(0,6) == 'split-') {radio_active_list = classes[i];} } } - } else {jQuery(this).removeClass('nowplaying');} + } else { + jQuery(this).removeClass('nowplaying'); + if (radio_list_current) {jQuery(this).removeClass('before-current').addClass('after-current');} + else {jQuery(this).addClass('before-current').removeClass('after-current');} + } }); - if (radio_active_list) {jQuery('.'+radio_active_list).addClass('nowplaying');} + if (radio_active_list) { + jQuery('.'+radio_active_list).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + } + if (!radio_list_current) {jQuery('.master-list-day-item').removeClass('before-current');} }"; // --- enqueue script inline --- diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index a1ef95a..4a57a7e 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1722,11 +1722,12 @@ function radio_station_show_info_metabox() { // --- get show meta --- // 2.3.2: added show download disable switch - $file = get_post_meta( $post->ID, 'show_file', true ); - $download = get_post_meta( $post->ID, 'show_download', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); $active = get_post_meta( $post->ID, 'show_active', true ); $link = get_post_meta( $post->ID, 'show_link', true ); + $email = get_post_meta( $post->ID, 'show_email', true ); + $phone = get_post_meta( $post->ID, 'show_phone', true ); + $file = get_post_meta( $post->ID, 'show_file', true ); + $download = get_post_meta( $post->ID, 'show_download', true ); $patreon_id = get_post_meta( $post->ID, 'show_patreon', true ); // added max-width to prevent metabox overflows @@ -1741,9 +1742,14 @@ function radio_station_show_info_metabox() { echo '

    '; - echo '

    + // 2.3.3.6: change text string from DJ / Host email (as maybe multiple hosts) + echo '

    '; + // 2.3.3.6: added Show phone number input field + echo '

    +

    '; + echo '

    '; @@ -3135,6 +3141,18 @@ function radio_station_show_save_data( $post_id ) { $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); $patreon_id = sanitize_title( $_POST['show_patreon'] ); + // 2.3.3.6: added phone number with character filter validation + $phone = trim( $_POST['show_phone'] ); + if ( strlen( $phone ) > 0 ) { + $phone = str_split( $phone, 1 ); + $phone = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $phone ); + if ( count( $phone ) > 0 ) { + $phone = implode( '', $phone ); + } else { + $phone = ''; + } + } + // 2.2.8: removed strict in_array checking // 2.3.2: fix for unchecked boxes index warning $active = $download = ''; @@ -3155,15 +3173,18 @@ function radio_station_show_save_data( $post_id ) { // --- get existing values and check if changed --- // 2.3.0: added check against previous values // 2.3.2: added download disable switch + // 2.3.3.6: added phone number field saving $prev_file = get_post_meta( $post_id, 'show_file', true ); $prev_download = get_post_meta( $post_id, 'show_download', true ); $prev_email = get_post_meta( $post_id, 'show_email', true ); + $prev_phone = get_post_meta( $post_id, 'show_phone', true ); $prev_active = get_post_meta( $post_id, 'show_active', true ); $prev_link = get_post_meta( $post_id, 'show_link', true ); $prev_patreon_id = get_post_meta( $post_id, 'show_patreon', true ); - if ( ( $prev_file != $file ) || ( $prev_email != $email ) - || ( $prev_active != $active ) || ( $prev_link != $link ) - || ( $prev_download != $download ) || ( $prev_patreon_id != $patreon_id ) ) { + if ( ( $prev_active != $active ) || ( $prev_link != $link ) + || ( $prev_email != $email ) || ( $prev_phone != $phone ) + || ( $prev_file != $file ) || ( $prev_download != $download ) + || ( $prev_patreon_id != $patreon_id ) ) { $show_meta_changed = true; } @@ -3172,6 +3193,7 @@ function radio_station_show_save_data( $post_id ) { update_post_meta( $post_id, 'show_file', $file ); update_post_meta( $post_id, 'show_download', $download ); update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_phone', $phone ); update_post_meta( $post_id, 'show_active', $active ); update_post_meta( $post_id, 'show_link', $link ); update_post_meta( $post_id, 'show_patreon', $patreon_id ); @@ -3383,8 +3405,9 @@ function radio_station_show_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { if ( isset( $_POST['action'] ) && ( 'radio_station_show_save_shifts' == $_POST['action'] ) ) { - print_r( $_POST['show_sched'] ); - print_r( $new_shifts ); + // --- (hidden) debug information --- + echo "Posted Shifsts: " . print_r( $_POST['show_sched'], true ) . PHP_EOL; + echo "New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; // --- display shifts saved message --- $show_shifts_nonce = wp_create_nonce( 'radio-station' ); diff --git a/loader.php b/loader.php index f8c0b42..2f0f9b9 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.5 +// Version: 1.1.6 // -------------- // Note: Changelog and structure at end of file. // @@ -721,6 +721,7 @@ public function update_settings() { } else { $settings[$key] = $newsettings; } + } else { // --- validate single setting --- @@ -863,6 +864,25 @@ public function validate_setting( $posted, $valid, $args ) { return $posted; } + } elseif ( 'PHONE' == $valid ) { + + // --- phone number characters only --- + $posted = trim( $posted ); + // $checkposted = preg_match( '/^[0-9+\(\)#\.\s\-]+$/', $posted ); + // if ( $checkposted ) { + // return $posted; + // } + if ( strlen( $posted ) > 0 ) { + $posted = str_split( $posted, 1 ); + $posted = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $posted ); + if ( count( $posted ) > 0 ) { + $posted = implode( '', $posted ); + return $posted; + } else { + return ''; + } + } + } elseif ( in_array( $valid, array( 'URL', 'URLS' ) ) ) { // --- URL address --- @@ -982,6 +1002,7 @@ public function validate_setting( $posted, $valid, $args ) { } } + // TODO: return validation error ? return false; } @@ -2840,6 +2861,9 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.1.6 == +// - added phone number character validation + // == 1.1.5 == // - fix to validate multiple CSV values diff --git a/radio-station.php b/radio-station.php index d77cc58..5e1f451 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.5 +Version: 2.3.3.6 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -47,6 +47,8 @@ // - Enqueue Plugin Stylesheet // - Localize Time Strings // === Template Filters === +// - Get Template +// - Station Phone Number Filter // - Automatic Pages Content Filter // - Single Content Template Filter // - Show Content Template Filter @@ -219,6 +221,31 @@ 'section' => 'broadcast', ), + // --- Station Phone Number --- + // 2.3.3.6: added station phone number option + 'station_phone' => array( + 'type' => 'text', + 'options' => 'PHONE', + 'label' => __( 'Station Phone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Phone for Shows --- + // 2.3.3.6: added default to station phone option + 'shows_phone' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Phone Display', 'radio-station' ), + 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // === Times === // --- Timezone Location --- @@ -1213,9 +1240,9 @@ function radio_station_settings_page_redirect() { } -// ----------------- -// === Templates === -// ----------------- +// ------------------------ +// === Template Filters === +// ------------------------ // ------------ // Get Template @@ -1305,6 +1332,23 @@ function radio_station_get_template( $type, $template, $paths = false ) { return false; } +// --------------------------- +// Station Phone Number Filter +// --------------------------- +// 2.3.3.6: added to return station phone for all Shows (if not set for Show) +add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); +function radio_station_phone_number( $phone, $post_id ) { + if ( $phone ) { + return $phone; + } + $shows_phone = radio_station_get_setting( 'shows_phone' ); + if ( 'yes' == $shows_phone ) { + $phone = radio_station_get_setting( 'station_phone' ); + return $phone; + } + return false; +} + // ------------------------------ // Automatic Pages Content Filter // ------------------------------ diff --git a/readme.txt b/readme.txt index 33b3364..0ead639 100644 --- a/readme.txt +++ b/readme.txt @@ -186,8 +186,11 @@ You may translate the plugin into another language. Please visit our [WordPress = 2.3.3.6 = * Update: Freemius SDK (2.4.1) -* Update: Plugin Loader (1.1.5) with CSV validation fix +* Update: Plugin Loader (1.1.6) with phone number and CSV validation +* Added: Station phone number setting with default display option +* Added: Schedule classes for Shows before and after current Show * Improved: current Show highlighting on Schedule for overnight shifts +* Improved: info section reordering filters on single Show template * Fixed: Edit permissions checks for Related to Show post assignments * Fixed: Main Language option value for WordPress Setting * Fixed: make Date on Tab clickable on Tabbed Schedule View diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 89a42a8..d7c44d8 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -13,6 +13,12 @@ global $radio_station_data, $post; $post_id = $radio_station_data['show-id'] = $post->ID; +// 2.3.3.6: set new line for easier debug viewing +$newline = ''; +if ( RADIO_STATION_DEBUG ) { + $newline = "\n"; +} + // --- get schedule time format --- $time_format = (int) radio_station_get_setting( 'clock_time_format', $post_id ); @@ -29,9 +35,11 @@ $shifts = radio_station_get_show_schedule( $post_id ); // --- get show icon / button data --- +// 2.3.3.6: added Show phone display $show_file = get_post_meta( $post_id, 'show_file', true ); $show_link = get_post_meta( $post_id, 'show_link', true ); $show_email = get_post_meta( $post_id, 'show_email', true ); +$show_phone = get_post_meta( $post_id, 'show_phone', true ); $show_patreon = get_post_meta( $post_id, 'show_patreon', true ); // $show_rss = get_post_meta( $post_id, 'show_rss', true ); $show_rss = false; // TEMP @@ -40,7 +48,7 @@ $show_download = true; $download = get_post_meta( $post_id, 'show_download', true ); if ( 'on' == $download ) { - // --- on = disabled --- + // note: on = disabled $show_download = false; } @@ -60,6 +68,7 @@ $show_download = apply_filters( 'radio_station_show_download', $show_download, $post_id ); $show_link = apply_filters( 'radio_station_show_link', $show_link, $post_id ); $show_email = apply_filters( 'radio_station_show_email', $show_email, $post_id ); +$show_phone = apply_filters( 'radio_station_show_phone', $show_phone, $post_id ); $show_patreon = apply_filters( 'radio_station_show_patreon', $show_patreon, $post_id ); $show_rss = apply_filters( 'radio_station_show_rss', $show_rss, $post_id ); $social_icons = apply_filters( 'radio_station_show_social_icons', false, $post_id ); @@ -67,19 +76,39 @@ // --- create show icon display early --- // 2.3.0: converted show links to icons $show_icons = array(); +$icon_colors = array( + 'website' => '#A44B73', + 'email' => '#0086CC', + 'phone' => '#008000', + 'rss' => '#FF6E01', +); // --- show home link icon --- // 2.3.3.4: added filter for title attribute if ( $show_link ) { - $title = __( 'Show Website', 'radio-station' ); + $title = __( 'Visit Show Website', 'radio-station' ); $title = apply_filters( 'radio_station_show_website_title', $title, $post_id ); - $icon = ''; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_home_icon', $icon, $post_id ); - $show_icons['home'] = '
    '; - $show_icons['home'] .= ''; + $show_icons['home'] = ''; + $show_icons['home'] .= '' . $newline; + $show_icons['home'] .= '
    ' . $newline; +} + +// --- phone number icon --- +// 2.3.3.6: added show phone icon +if ( $show_phone ) { + $title = __( 'Call in Phone Number', 'radio-station' ); + $title = apply_filters( 'radio_station_show_phone_title', $title, $post_id ); + $icon = '' . $newline; + $icon = apply_filters( 'radio_station_show_phone_icon', $icon, $post_id ); + $show_icons['phone'] = '
    ' . $newline; + $show_icons['phone'] .= '' . $newline; + $show_icons['phone'] .= $icon . $newline; + $show_icons['phone'] .= '' . $newline; + $show_icons['phone'] .= '
    ' . $newline; } // --- email DJ / host icon --- @@ -87,13 +116,13 @@ if ( $show_email ) { $title = __( 'Email Show Host', 'radio-station' ); $title = apply_filters( 'radio_station_show_email_title', $title, $post_id ); - $icon = ''; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_email_icon', $icon, $post_id ); - $show_icons['email'] = '
    '; - $show_icons['email'] .= ''; - $show_icons['email'] .= $icon; - $show_icons['email'] .= ''; - $show_icons['email'] .= '
    '; + $show_icons['email'] = '
    ' . $newline; + $show_icons['email'] .= '' . $newline; + $show_icons['email'] .= $icon . $newline; + $show_icons['email'] .= '' . $newline; + $show_icons['email'] .= '
    ' . $newline; } // --- show RSS feed icon --- @@ -102,13 +131,13 @@ $feed_url = radio_station_get_show_rss_url( $post_id ); $title = __( 'Show RSS Feed', 'radio-station' ); $title = apply_filters( 'radio_station_show_rss_title', $title, $post_id ); - $icon = ''; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_rss_icon', $icon, $post_id ); - $show_icons['rss'] = '
    '; - $show_icons['rss'] .= ''; - $show_icons['rss'] .= $icon; - $show_icons['rss'] .= ''; - $show_icons['rss'] .= '
    '; + $show_icons['rss'] = '
    ' . $newline; + $show_icons['rss'] .= '' . $newline; + $show_icons['rss'] .= $icon . $newline; + $show_icons['rss'] .= '' . $newline; + $show_icons['rss'] .= '
    ' . $newline; } // --- filter show icons --- @@ -149,17 +178,20 @@ $jump_links = apply_filters( 'radio_station_show_jump_links', 'yes', $post_id ); -// ------------------ -// Set Blocks Content -// ------------------ +// -------------------------- +// === Set Blocks Content === +// -------------------------- // --- set empty blocks --- $blocks = array( 'show_images' => '', 'show_meta' => '', 'show_schedule' => '' ); +// ----------------- // Show Images Block // ----------------- if ( ( $avatar_id || $thumbnail_id ) || ( count( $show_icons ) > 0 ) || ( $show_file ) ) { + $image_blocks = array(); + // --- Show Avatar --- if ( $avatar_id || $thumbnail_id ) { // --- get show avatar (with thumbnail fallback) --- @@ -175,47 +207,48 @@ } else { $class = ''; } - $blocks['show_images'] .= '
    '; + $blocks['show_images'] = '
    '; $blocks['show_images'] .= $show_avatar; $blocks['show_images'] .= '
    '; } } - // --- show controls --- + // --- Show controls --- // 2.3.3.6: modify check to include social icons or patreon only if ( ( count( $show_icons ) > 0 ) || $social_icons || $show_patreon || $show_file ) { - $blocks['show_images'] .= '
    '; - // --- Show Icons --- if ( count( $show_icons ) > 0 ) { - $blocks['show_images'] .= '
    '; - $blocks['show_images'] .= implode( "\n", $show_icons ); - $blocks['show_images'] .= '
    '; + $image_blocks['icons'] = '
    '; + $image_blocks['icons'] .= implode( "\n", $show_icons ); + $image_blocks['icons'] .= '
    '; } + // --- Social Icons --- // 2.3.3.6: added filter for social icon display output if ( $social_icons ) { $social_icons = apply_filters( 'radio_station_show_social_icons_display', '' ); if ( '' != $social_icons ) { - $blocks['show_images'] .= ''; + $image_blocks['social'] = ''; } } // --- Show Patreon Button --- - $show_patreon_button = ''; + $patreon_button = ''; if ( $show_patreon ) { - $show_patreon_button .= '
    '; + $patreon_button .= '
    '; $title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; $title = apply_filters( 'radio_station_show_patreon_title', $title, $post_id ); - $show_patreon_button .= radio_station_patreon_button( $show_patreon, $title ); - $show_patreon_button .= '
    '; + $patreon_button .= radio_station_patreon_button( $show_patreon, $title ); + $patreon_button .= '
    '; } // 2.3.1: added filter for patreon button - $show_patreon_button = apply_filters( 'radio_station_show_patreon_button', $show_patreon_button, $post_id ); - $blocks['show_images'] .= $show_patreon_button; + $patreon_button = apply_filters( 'radio_station_show_patreon_button', $patreon_button, $post_id ); + if ( '' != $patreon_button ) { + $image_blocks['patreon'] = $patreon_button; + } // --- Show Player --- // 2.3.0: embed latest broadcast audio player @@ -223,79 +256,103 @@ // 2.3.3.4: add filter for title text on Show Download link icon if ( $show_file ) { - $blocks['show_images'] .= '
    '; + $image_blocks['player'] = '
    ' . $newline; $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); if ( $label && ( '' != $label ) ) { - $blocks['show_images'] .= '' . esc_html( $label ) . '
    '; + $image_blocks['player'] .= '' . esc_html( $label ) . '
    '; } $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; $player_embed = do_shortcode( $shortcode ); - $blocks['show_images'] .= '
    '; - $blocks['show_images'] .= $player_embed; - $blocks['show_images'] .= '
    '; + $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= $player_embed . $newline; + $image_blocks['player'] .= '
    ' . $newline; // --- Download Audio Icon --- // 2.3.2: check show download switch if ( $show_download ) { $title = __( 'Download Latest Broadcast', 'radio-station' ); $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); - $blocks['show_images'] .= '
    '; - $blocks['show_images'] .= ''; - $blocks['show_images'] .= ''; - $blocks['show_images'] .= ''; - $blocks['show_images'] .= '
    '; + $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '
    ' . $newline; } - $blocks['show_images'] .= '
    '; + $image_blocks['player'] .= '
    ' . $newline; + } + + // 2.3.3.6: allow subblock order to be changed + $image_blocks = apply_filters( 'radio_station_show_images_blocks', $image_blocks, $post_id ); + $image_block_order = array( 'icons', 'social', 'patreon', 'player' ); + $image_block_order = apply_filters( 'radio_station_show_image_block_order', $image_block_order, $post_id ); + if ( RADIO_STATION_DEBUG ) { + echo 'Image Block Order: ' . print_r( $image_block_order, true ) . ''; + echo ''; + } + + // --- combine image blocks to show images block --- + if ( is_array( $image_blocks ) && ( count( $image_blocks ) > 0 ) + && is_array( $image_block_order ) && ( count( $image_block_order ) > 0 ) ) { + $blocks['show_images'] .= '
    '; + foreach ( $image_block_order as $image_block ) { + if ( isset( $image_blocks[$image_block] ) ) { + $blocks['show_images'] .= $image_blocks[$image_block]; + } + } + $blocks['show_images'] .= '
    ' . $newline; } - $blocks['show_images'] .= '
    '; } } +// --------------- // Show Meta Block // --------------- -if ( $hosts || $producers || $genres || $languages ) { +// 2.3.3.6: added Show phone display section +if ( $show_phone || $hosts || $producers || $genres || $languages ) { + + $meta_blocks = array(); // --- show meta title --- // 2.3.3.4: added filter for show info label // 2.3.3.4: added class to show info label tag $label = __( 'Show Info', 'radio-station' ); $label = apply_filters( 'radio_station_show_info_label', $label, $post_id ); - $blocks['show_meta'] = '

    ' . esc_html( $label ) . '

    '; + $blocks['show_meta'] = '

    ' . esc_html( $label ) . '

    ' . $newline; // --- Show DJs / Hosts --- // 2.3.3.4: added filter for hosted by label // 2.3.3.4: replace bold title tag with span and class if ( $hosts ) { - $blocks['show_meta'] .= '
    '; + $meta_blocks['hosts'] = '
    ' . $newline; $label = __( 'Hosted by', 'radio-station' ); $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; $host_count = count( $hosts ); foreach ( $hosts as $host ) { $count ++; $user_info = get_userdata( $host ); - // --- DJ / Host URL / display--- + // --- DJ / Host URL and/or display --- $host_url = radio_station_get_host_url( $host ); if ( $host_url ) { - $blocks['show_meta'] .= ''; + $meta_blocks['hosts'] .= ''; } - $blocks['show_meta'] .= esc_html( $user_info->display_name ); + $meta_blocks['hosts'] .= esc_html( $user_info->display_name ); if ( $host_url ) { - $blocks['show_meta'] .= ''; + $meta_blocks['hosts'] .= '' . $newline; } if ( ( ( 1 === $count ) && ( 2 === $host_count ) ) || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) === $count ) ) ) { - $blocks['show_meta'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $meta_blocks['hosts'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( count( $hosts ) > 2 ) && ( $count < count( $hosts ) ) ) { - $blocks['show_meta'] .= ', '; + $meta_blocks['hosts'] .= ', '; } } - $blocks['show_meta'] .= '
    '; + $meta_blocks['hosts'] .= '
    ' . $newline; } // --- Show Producers --- @@ -303,10 +360,10 @@ // 2.3.3.4: added filter for produced by label // 2.3.3.4: replace bold title tag with span and class if ( $producers ) { - $blocks['show_meta'] .= '
    '; + $meta_blocks['producers'] = '
    ' . $newline; $label = __( 'Produced by', 'radio-station' ); $label = apply_filters( 'radio_station_show_producers_label', $label, $post_id ); - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; $producer_count = count( $producers ); foreach ( $producers as $producer ) { @@ -316,21 +373,21 @@ // --- Producer URL / display --- $producer_url = radio_station_get_producer_url( $producer ); if ( $producer_url ) { - $blocks['show_meta'] .= ''; + $meta_blocks['producers'] .= ''; } - $blocks['show_meta'] .= esc_html( $user_info->display_name ); + $meta_blocks['producers'] .= esc_html( $user_info->display_name ); if ( $producer_url ) { - $blocks['show_meta'] .= ''; + $meta_blocks['producers'] .= '' . $newline; } if ( ( ( 1 === $count ) && ( 2 === $producer_count ) ) || ( ( $producer_count > 2 ) && ( ( $producer_count - 1 ) === $count ) ) ) { - $blocks['show_meta'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $meta_blocks['producers'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( count( $producers ) > 2 ) && ( $count < count( $producers ) ) ) { - $blocks['show_meta'] .= ', '; + $meta_blocks['producers'] .= ', '; } } - $blocks['show_meta'] .= '
    '; + $meta_blocks['producers'] .= '
    ' . $newline; } // --- Show Genre(s) --- @@ -345,15 +402,15 @@ $label = $tax_object->labels->name; } $label = apply_filters( 'radio_station_show_genres_label', $label, $post_id ); - $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $meta_blocks['genres'] = '
    ' . $newline; + $meta_blocks['genres'] .= '' . esc_html( $label ) . ': ' . $newline; $genre_links = array(); foreach ( $genres as $genre ) { $genre_link = get_term_link( $genre ); - $genre_links[] = '' . esc_html( $genre->name ) . ''; + $genre_links[] = '' . esc_html( $genre->name ) . '' . $newline; } - $blocks['show_meta'] .= implode( ', ', $genre_links ); - $blocks['show_meta'] .= '
    '; + $meta_blocks['genres'] .= implode( ', ', $genre_links ) . $newline; + $meta_blocks['genres'] .= '
    ' . $newline; } // --- Show Language(s) --- @@ -368,8 +425,8 @@ $label = $tax_object->labels->name; } $label = apply_filters( 'radio_station_show_languages_label', $label, $post_id ); - $blocks['show_meta'] .= '
    '; - $blocks['show_meta'] .= '' . esc_html( $label ) . ': '; + $meta_blocks['languages'] = '
    ' . $newline; + $meta_blocks['languages'] .= '' . esc_html( $label ) . ': ' . $newline; $language_links = array(); foreach ( $languages as $language ) { $lang_label = $language->name; @@ -377,13 +434,44 @@ $lang_label .= ' (' . $language->description . ')'; } $language_link = get_term_link( $language ); - $language_links[] = '' . esc_html( $lang_label ) . ''; + $language_links[] = '' . esc_html( $lang_label ) . '' . $newline; + } + $meta_blocks['languages'] .= implode( ', ', $language_links ) . $newline; + $meta_blocks['languages'] .= '
    ' . $newline; + } + + // --- Show Phone --- + if ( $show_phone ) { + $meta_blocks['phone'] = '
    '; + $label = __( 'Call in', 'radio-station' ); + $label = apply_filters( 'radio_station_show_phone_label', $label, $post_id ); + $meta_blocks['phone'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['phone'] .= '' . esc_html( $show_phone ) . ''; + $meta_blocks['phone'] .= '
    '; + } + + // --- filter meta blocks and order --- + // 2.3.3.6: allow subblock order to be changed + $meta_blocks = apply_filters( 'radio_station_show_meta_blocks', $meta_blocks, $post_id ); + $meta_block_order = array( 'hosts', 'producers', 'genres', 'languages', 'phone' ); + $meta_block_order = apply_filters( 'radio_station_show_meta_block_order', $meta_block_order, $post_id ); + if ( RADIO_STATION_DEBUG ) { + echo 'Meta Block Order: ' . print_r( $meta_block_order, true ) . ''; + echo ''; + } + + // --- combine meta blocks to show meta block --- + if ( is_array( $meta_blocks ) && ( count( $meta_blocks ) > 0 ) + && is_array( $meta_block_order ) && ( count( $meta_block_order ) > 0 ) ) { + foreach ( $meta_block_order as $meta_block ) { + if ( isset( $meta_blocks[$meta_block] ) ) { + $blocks['show_meta'] .= $meta_blocks[$meta_block]; + } } - $blocks['show_meta'] .= implode( ', ', $language_links ); - $blocks['show_meta'] .= '
    '; } } +// ---------------- // Show Times Block // ---------------- @@ -403,7 +491,7 @@ // --- show times title --- $label = __( 'Show Times', 'radio-station' ); $label = apply_filters( 'radio_station_show_times_label', $label, $post_id ); -$blocks['show_times'] = '

    ' . esc_html( $label ) . '

    '; +$blocks['show_times'] = '

    ' . esc_html( $label ) . '

    ' . $newline; // --- check if show is active and has shifts --- if ( !$active || !$shifts ) { @@ -448,20 +536,20 @@ // 2.3.3.4: added filter for timezone label $label = __( 'Timezone', 'radio-station' ); $label = apply_filters( 'radio_station_show_timezone_label', $label, $post_id ); - $blocks['show_times'] .= '' . esc_html( $label ) . ': '; + $blocks['show_times'] .= '' . esc_html( $label ) . ': ' . $newline; if ( !isset( $timezone_code ) ) { $blocks['show_times'] .= esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset; } else { $blocks['show_times'] .= esc_html( $timezone_code ); $blocks['show_times'] .= ''; $blocks['show_times'] .= ' [' . esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset . ']'; - $blocks['show_times'] .= ''; + $blocks['show_times'] .= '' . $newline; } // TODO: --- display user timezone --- // $block['show_times'] .= ... - $blocks['show_times'] .= ''; + $blocks['show_times'] .= '
    ' . $newline; $found_encore = false; @@ -511,49 +599,49 @@ // --- set show time output --- // 2.3.4: fix to start data_format attribute - $show_time = '
    '; - $show_time .= '' . esc_html( $start ) . ''; - $show_time .= ' - '; - $show_time .= '' . esc_html( $end ) . ''; + $show_time = '
    ' . $newline; + $show_time .= '' . esc_html( $start ) . '' . $newline; + $show_time .= ' - ' . $newline; + $show_time .= '' . esc_html( $end ) . '' . $newline; if ( isset( $shift['encore'] ) && ( 'on' == $shift['encore'] ) ) { $found_encore = true; - $show_time .= '*'; + $show_time .= '*' . $newline; } - $show_time .= '
    '; + $show_time .= '
    ' . $newline; $show_times[] = $show_time; } } } $show_times_count = count( $show_times ); if ( $show_times_count > 0 ) { - $blocks['show_times'] .= ''; + $blocks['show_times'] .= '' . $newline; } } // --- * encore note --- // 2.3.3.4: added filter for encore label if ( $found_encore ) { - $blocks['show_times'] .= ''; + $blocks['show_times'] .= '' . $newline; + $blocks['show_times'] .= '' . $newline; } - $blocks['show_times'] .= '
    '; + $blocks['show_times'] .= '' . $newline; $weekday = radio_station_translate_weekday( $day, true ); - $blocks['show_times'] .= '' . esc_html( $weekday ) . ': '; - $blocks['show_times'] .= ''; + $blocks['show_times'] .= '' . esc_html( $weekday ) . ': ' . $newline; + $blocks['show_times'] .= '' . $newline; foreach ( $show_times as $i => $show_time ) { $blocks['show_times'] .= '' . $show_time . ''; // if ( $i < ( $show_times_count - 1 ) ) { // $blocks['show_times'] .= '
    '; // } } - $blocks['show_times'] .= '
    '; - $blocks['show_times'] .= '* '; + $blocks['show_times'] .= '
    ' . $newline; + $blocks['show_times'] .= '* ' . $newline; $blocks['show_times'] .= ''; $label = __( 'Encore Presentation', 'radio-station' ); $label = apply_filters( 'radio_station_show_encore_label', $label, $post_id ); $blocks['show_times'] .= esc_html( $label ); - $blocks['show_times'] .= ''; - $blocks['show_times'] .= '
    '; + $blocks['show_times'] .= '' . $newline; } // --- maybe add link to full schedule page --- @@ -561,42 +649,44 @@ $schedule_page = radio_station_get_setting( 'schedule_page' ); if ( $schedule_page && !empty( $schedule_page ) ) { $schedule_link = get_permalink( $schedule_page ); - $blocks['show_times'] .= '' . $newline; } -// --- filter show info blocks --- +// --- filter all show info blocks --- $blocks = apply_filters( 'radio_station_show_page_blocks', $blocks, $post_id ); -// ----------------- -// Set Show Sections -// ----------------- +// ------------------------- +// === Set Show Sections === +// ------------------------- // 2.3.0: add show information sections +// -------------------- // Set Show Description // -------------------- $show_description = false; if ( strlen( trim( $content ) ) > 0 ) { - $show_description = '
    ' . $content . '
    '; - $show_description .= '
    '; - $show_desc_buttons = '
    '; + $show_description = '
    ' . $content . '
    ' . $newline; + $show_description .= '
    ' . $newline; + $show_desc_buttons = '
    ' . $newline; $label = __( 'Show More', 'radio-station' ); $label = apply_filters( 'radio_station_show_more_label', $label, $post_id ); - $show_desc_buttons .= ' '; + $show_desc_buttons .= ' ' . $newline; $label = __( 'Show Less', 'radio-station' ); $label = apply_filters( 'radio_station_show_less_label', $label, $post_id ); - $show_desc_buttons .= ' '; - $show_desc_buttons .= ' '; - $show_desc_buttons .= '
    '; + $show_desc_buttons .= ' ' . $newline; + $show_desc_buttons .= ' ' . $newline; + $show_desc_buttons .= '
    ' . $newline; } +// ------------- // Show Sections // ------------- $sections = array(); @@ -607,20 +697,20 @@ $i = 0; if ( $show_description ) { - $sections['about']['heading'] = ''; + $sections['about']['heading'] = '' . $newline; $label = __( 'About the Show', 'radio-station' ); $label = apply_filters( 'radio_station_show_description_label', $label, $post_id ); - $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; $anchor = __( 'About', 'radio-station' ); $anchor = apply_filters( 'radio_station_show_description_anchor', $anchor, $post_id ); $sections['about']['anchor'] = $anchor; - $sections['about']['content'] = '

    '; - $sections['about']['content'] .= '
    '; + $sections['about']['content'] = '

    ' . $newline; + $sections['about']['content'] .= '
    ' . $newline; $sections['about']['content'] .= $show_description; - $sections['about']['content'] .= '
    '; + $sections['about']['content'] .= '
    ' . $newline; $sections['about']['content'] .= $show_desc_buttons; - $sections['about']['content'] .= '
    '; + $sections['about']['content'] .= '
    ' . $newline; $i ++; } @@ -628,20 +718,20 @@ // 2.3.3.4: added filter for show epiodes label and anchor if ( $show_episodes ) { - $sections['episodes']['heading'] = ''; + $sections['episodes']['heading'] = '' . $newline; $label = __( 'Show Episodes', 'radio-station' ); $label = apply_filters( 'radio_station_show_episodes_label', $label, $post_id ); - $sections['episodes']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $sections['episodes']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; $anchor = __( 'Episodes', 'radio-station' ); $anchor = apply_filters( 'radio_station_show_episodes_anchor', $anchor, $post_id ); $sections['episodes']['anchor'] = $anchor; - $sections['episodes']['content'] = '

    '; + $sections['episodes']['content'] = '

    ' . $newline; $radio_station_data['show-episodes'] = $show_posts; $shortcode = '[show-episodes-archive per_page="' . $episodes_per_page . '"]'; $shortcode = apply_filters( 'radio_station_show_page_episodes_shortcode', $shortcode, $post_id ); $sections['episodes']['content'] .= do_shortcode( $shortcode ); - $sections['episodes']['content'] .= '
    '; + $sections['episodes']['content'] .= '
    ' . $newline; $i ++; } @@ -649,20 +739,20 @@ // 2.3.3.4: added filter for show posts label and anchor if ( $show_posts ) { - $sections['posts']['heading'] = ''; + $sections['posts']['heading'] = '' . $newline; $label = __( 'Show Posts', 'radio-station' ); $label = apply_filters( 'radio_station_show_posts_label', $label, $post_id ); - $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; $anchor = __( 'Posts', 'radio-station' ); $anchor = apply_filters( 'radio_station_show_posts_anchor', $anchor, $post_id ); $sections['posts']['anchor'] = $anchor; - $sections['posts']['content'] = '

    '; + $sections['posts']['content'] = '

    ' . $newline; $radio_station_data['show-posts'] = $show_posts; $shortcode = '[show-posts-archive per_page="' . $posts_per_page . '"]'; $shortcode = apply_filters( 'radio_station_show_page_posts_shortcode', $shortcode, $post_id ); $sections['posts']['content'] .= do_shortcode( $shortcode ); - $sections['posts']['content'] .= '
    '; + $sections['posts']['content'] .= '
    ' . $newline; $i ++; } @@ -673,26 +763,26 @@ $sections['playlists']['heading'] = ''; $label = __( 'Show Playlists', 'radio-station' ); $label = apply_filters( 'radio_station-show_playlists_label', $label, $post_id ); - $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    '; + $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; $anchor = __( 'Playlists', 'radio-station' ); $anchor = apply_filters( 'radio_station_show_playlists_anchor', $anchor, $post_id ); $sections['playlists']['anchor'] = $anchor; - $sections['playlists']['content'] = '

    '; + $sections['playlists']['content'] = '

    ' . $newline; $radio_station_data['show-playlists'] = $show_playlists; $shortcode = '[show-playlists-archive per_page="' . $playlists_per_page . '"]'; $shortcode = apply_filters( 'radio_station_show_page_playlists_shortcode', $shortcode, $post_id ); $sections['playlists']['content'] .= do_shortcode( $shortcode ); - $sections['playlists']['content'] .= '
    '; + $sections['playlists']['content'] .= '
    ' . $newline; $i ++; } } $sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id, $post_id ); -// --------------- -// Template Output -// --------------- +// ----------------------- +// === Template Output === +// ----------------------- // --- set content classes --- $classes = array(); From dddb6b3c2eb5a6aae80450a92d56e27d39c60a8a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 7 Dec 2020 11:49:46 +1000 Subject: [PATCH 163/377] dev update add upgrade notice --- readme.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/readme.txt b/readme.txt index 0ead639..e8ec31e 100644 --- a/readme.txt +++ b/readme.txt @@ -624,6 +624,13 @@ You may translate the plugin into another language. Please visit our [WordPress == Upgrade Notice == += 2.3.3.6 = +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + = 2.3.3.5 = * Ability to assign Post to relate to multiple Shows * Added Admin Filtering, Bulk Edit and Quick Edit interfaces From e22f73528ade46a8bb40b7053c497a0873a933e2 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 7 Dec 2020 11:58:32 +1000 Subject: [PATCH 164/377] dev updated tested up to --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index e8ec31e..63e0b9e 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.4.1 +Tested up to: 5.5.3 Stable tag: trunk License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From de20d6e4ca766ff02db6eba605af6407835b70ed Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 9 Dec 2020 16:24:40 -0500 Subject: [PATCH 165/377] update upgrade notification in readme.md --- readme.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/readme.md b/readme.md index c7a6ad7..13c3a87 100644 --- a/readme.md +++ b/readme.md @@ -188,6 +188,21 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices +#### 2.3.3.6 +* Added: Station phone number setting with default display option +* Added: Schedule classes for Shows before and after current Show +* Improved: current Show highlighting on Schedule for overnight shifts +* Improved: info section reordering filters on single Show template +* Fixed: Edit permissions checks for Related to Show post assignments +* Fixed: Main Language option value for WordPress Setting +* Fixed: make Date on Tab clickable on Tabbed Schedule View +* Fixed: prevent possible conflicts with changes not saved reload message +* Fixed: do not conflict check Shift against itself for last shift check +* Fixed: link back to Show posts for related Show posts (allow multiple) +* Fixed: filter next/previous post link for (multiple) related Show posts +* Fixed: automatic pages conflict where themes filter the_content early + + #### 2.3.3.5 * Ability to assign Post to relate to multiple Shows * Added Admin Filtering, Bulk Edit and Quick Edit interfaces From 324da559a567d14d8622495b55f4ba410b476839 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 10 Dec 2020 19:17:48 -0500 Subject: [PATCH 166/377] Update readme.md Was missing two lines of text --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 13c3a87..c12bc5e 100644 --- a/readme.md +++ b/readme.md @@ -189,6 +189,8 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices #### 2.3.3.6 +* Update: Freemius SDK (2.4.1) +* Update: Plugin Loader (1.1.6) with phone number and CSV validation * Added: Station phone number setting with default display option * Added: Schedule classes for Shows before and after current Show * Improved: current Show highlighting on Schedule for overnight shifts From 7a4b461eda1aefa34c7cbd632c7cb69a3190fb4a Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 11 Dec 2020 11:32:46 +1000 Subject: [PATCH 167/377] dev updates #285 #291 #292 --- CHANGELOG.md | 5 +++++ includes/post-types-admin.php | 31 ++++++++++++++----------- includes/support-functions.php | 33 +++++++++++++++++++++++++++ radio-station.php | 41 +++++++++++++++++++++++++--------- reader.php | 40 ++++++++++++++++----------------- readme.txt | 5 +++++ 6 files changed, 111 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e805c20..9eb433e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.3.7 +* Fixed: Bulk Edit field repetition and possible jQuery conflict +* Fixed: Related Posts check producing error output +* Fixed: WordPress Readme Parser deprecated errors for PHP7 + ### 2.3.3.6 * Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.6) with phone number and CSV validation diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 4a57a7e..cfcdfa2 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1476,6 +1476,7 @@ function radio_station_posts_quick_edit_script( $hook ) { return; } + // 2.3.3.7: use jQuery instead of \$ for better compatibility if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { $js = "(function($) { var \$wp_inline_edit = inlineEditPost.edit; @@ -1484,26 +1485,26 @@ function radio_station_posts_quick_edit_script( $hook ) { var post_id = 0; var disabled_ids; if (typeof(id) == 'object') {post_id = parseInt(this.getId(id));} if (post_id > 0) { - var show_ids = \$('#post-'+post_id+' .column-show .show-ids').text(); + var show_ids = jQuery('#post-'+post_id+' .column-show .show-ids').text(); if (show_ids != '') { if (show_ids.indexOf(',') > -1) {ids = show_ids.split(',');} else {ids = new Array(); ids[0] = show_ids;} for (i = 0; i < ids.length; i++) { var thisshowid = ids[i]; - \$('#edit-'+post_id+' .select-show option').each(function() { - if (\$(this).val() == thisshowid) {\$(this).attr('selected','selected');} + jQuery('#edit-'+post_id+' .select-show option').each(function() { + if (jQuery(this).val() == thisshowid) {jQuery(this).attr('selected','selected');} }); } /* disable uneditable options */ - disabled = \$('#post-'+post_id+' .column-show .disabled-ids').text(); + disabled = jQuery('#post-'+post_id+' .column-show .disabled-ids').text(); if (disabled != '') { if (disabled.indexOf(',') > -1) {disabled_ids = disabled.split(',');} else {disabled_ids = new Array(); disabled_ids[0] = disabled;} - \$('#edit-'+post_id+' .select-show option').each(function() { + jQuery('#edit-'+post_id+' .select-show option').each(function() { for (j = 0; j < disabled_ids.length; j++) { - if (\$(this).val() == disabled_ids[j]) { - \$(this).attr('disabled','disabled'); - if (\$(this).attr('selected') == 'selected') {\$(this).addClass('pre-selected');} + if (jQuery(this).val() == disabled_ids[j]) { + jQuery(this).attr('disabled','disabled'); + if (jQuery(this).attr('selected') == 'selected') {jQuery(this).addClass('pre-selected');} } } }); @@ -1540,14 +1541,18 @@ function radio_station_show_posts_bulk_edit_script( $hook ) { return; } + // 2.3.3.7: use jQuery instead of \$ for better compatibility + // 2.3.3.7: do not reclone the show field if it already exists if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { - $js = "\$(document).ready(function() { - $('#bulk-action-selector-top, #bulk-action-selector-bottom').on('change', function(e) { - if ( \$(this).val() == 'related_show' ) { + $js = "jQuery(document).ready(function() { + jQuery('#bulk-action-selector-top, #bulk-action-selector-bottom').on('change', function(e) { + if (jQuery(this).val() == 'related_show') { /* clone the Quick Edit fieldset to after bulk action selector */ - \$('.related-show-field').first().clone().insertAfter(\$(this)); + if (!jQuery(this).parent().find('.related-show-field').length) { + jQuery('.related-show-field').first().clone().insertAfter(jQuery(this)); + } } else { - \$(this).find('.related-show-field').remove(); + jQuery(this).find('.related-show-field').remove(); } }); });"; diff --git a/includes/support-functions.php b/includes/support-functions.php index 0233aae..b7d4449 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -39,6 +39,7 @@ // - Get Show Avatar // === URL Functions === // - Get Streaming URL +// - Get Stream Formats // - Get Master Schedule Page URL // - Get Radio Station API URL // - Get Route URL @@ -2964,6 +2965,38 @@ function radio_station_get_stream_url() { return $streaming_url; } +// Get Stream Formats +// ------------------ +// 2.3.3.7: added streaming format options +function radio_station_get_stream_formats() { + + // TODO: recheck amplitude formats ? + // [Amplitude] HTML5 Support - mp3, aac ...? + // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers + // [JPlayer] Audio: mp3, m4a - Video: m4v + // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv + // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm + // +mpeg, oga, caf, weba, webm, dolby, flac + // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv + + $formats = array( + 'aac' => 'AAC (A/H)', + 'mp3' => 'MP3 (A/J/H)', + 'm4a' => 'M4A (J/H)', + 'ogg' => 'OGG (H)', + 'oga' => 'OGA (J/H)', + 'webm' => 'WebM (J/H)', + 'rtmpa' => 'RTMPA (J)', + 'opus' => 'OPUS (H)', + 'wav' => 'WAV (J/H)', + 'flac' => 'FLAC (J/H)', + ); + + // --- filter and return --- + $formats = apply_filters( 'radio_station_stream_formats', $formats ); + return $formats; +} + // --------------- // Get Station URL // --------------- diff --git a/radio-station.php b/radio-station.php index 5e1f451..a352d18 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.6 +Version: 2.3.3.7 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -162,6 +162,7 @@ // 2.3.0: added plugin options $timezones = radio_station_get_timezone_options( true ); $languages = radio_station_get_language_options( true ); +$formats = radio_station_get_stream_formats(); $options = array( // === Broadcast === @@ -177,12 +178,12 @@ 'section' => 'broadcast', ), - // --- Fallback Stream Format --- - // 'fallback_url' => array( + // --- Stream Format --- + // 'streaming_format' => array( // 'type' => 'select', - // 'options' => array( 'mp3', 'aac' ), // ... + // 'options' => $formats, // 'label' => __( 'Streaming Format', 'radio-station' ), - // 'default' => 'mp3', + // 'default' => 'aac', // 'helper' => __( 'Select streaming fallback for streaming URL.', 'radio-station' ), // 'tab' => 'general', // 'section' => 'broadcast', @@ -200,11 +201,11 @@ // ), // --- Fallback Stream Format --- - // 'fallback_url' => array( + // 'fallback_format' => array( // 'type' => 'text', - // 'options' => array( 'mp3', 'aac' ), // ... + // 'options' => $formats, // 'label' => __( 'Fallback Format', 'radio-station' ), - // 'default' => 'mp3', + // 'default' => 'oga', // 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), // 'tab' => 'general', // 'section' => 'broadcast', @@ -1212,6 +1213,21 @@ function radio_station_localize_script() { } +// ------------------------- +// Filter for Streaming Data +// ------------------------- +// 2.3.3.7: added streaming data filter for player integration +add_filter( 'radio_station_player_data', 'radio_station_streaming_data' ); +function radio_station_streaming_data( $data ) { + $data = array( + 'url' => radio_station_get_stream_url(), + 'format' => radio_station_get_setting( 'streaming_format' ), + 'fallback' => radio_station_get_setting( 'fallback_url' ), + 'fformat' => radio_station_get_setting( 'fallback_format' ), + ); + return $data; +} + // ----------------------------------------- // Fix to Redirect Plugin Settings Menu Link // ----------------------------------------- @@ -1871,9 +1887,12 @@ function radio_station_add_show_links( $content ) { $related_shows = array( $related_shows ); } // 2.3.3.6: remove possible zero values - foreach ( $related_shows as $i => $related_show ) { - if ( 0 == $related_show ) { - unset( $related_shows[$i] ); + // 2.3.3.7: added count check for before looping + if ( $related_shows && ( count( $related_shows ) > 0 ) ) { + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } } } if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { diff --git a/reader.php b/reader.php index cba030d..3d7f91c 100644 --- a/reader.php +++ b/reader.php @@ -133,7 +133,7 @@ class Markdown_Parser { var $escape_chars_re; - function Markdown_Parser() { + function __construct() { # # Constructor function. Initialize appropriate member variables. # @@ -1123,7 +1123,7 @@ function doItalicsAndBold($text) { } else { # Other closing marker: close one em or strong and # change current token state to match the other - $token_stack[0] = str_repeat($token{0}, 3-$token_len); + $token_stack[0] = str_repeat($token[0], 3-$token_len); $tag = $token_len == 2 ? "strong" : "em"; $span = $text_stack[0]; $span = $this->runSpanGamut($span); @@ -1148,7 +1148,7 @@ function doItalicsAndBold($text) { } else { # Reached opening three-char emphasis marker. Push on token # stack; will be handled by the special condition above. - $em = $token{0}; + $em = $token[0]; $strong = "$em$em"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); @@ -1488,9 +1488,9 @@ function handleSpanToken($token, &$str) { # Handle $token provided by parseSpan by determining its nature and # returning the corresponding value that should replace it. # - switch ($token{0}) { + switch ($token[0]) { case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); + return $this->hashPart("&#". ord($token[1]). ";"); case "`": # Search for end marker in remaining text. if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', @@ -1608,7 +1608,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { ### Parser Implementation ### - function MarkdownExtra_Parser() { + function __construct() { # # Constructor function. Initialize the parser object. # @@ -1634,7 +1634,7 @@ function MarkdownExtra_Parser() { "doAbbreviations" => 70, ); - parent::Markdown_Parser(); + parent::__construct(); } @@ -1711,9 +1711,9 @@ function doExtraAttributes($tag_name, $attr) { $classes = array(); $id = false; foreach ($elements as $element) { - if ($element{0} == '.') { + if ($element[0] == '.') { $classes[] = substr($element, 1); - } else if ($element{0} == '#') { + } else if ($element[0] == '#') { if ($id === false) $id = substr($element, 1); } } @@ -1976,7 +1976,7 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, # # Check for: Indented code block. # - else if ($tag{0} == "\n" || $tag{0} == " ") { + else if ($tag[0] == "\n" || $tag[0] == " ") { # Indented code block: pass it unchanged, will be handled # later. $parsed .= $tag; @@ -1985,7 +1985,7 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, # Check for: Code span marker # Note: need to check this after backtick fenced code blocks # - else if ($tag{0} == "`") { + else if ($tag[0] == "`") { # Find corresponding end marker. $tag_re = preg_quote($tag); if (preg_match('{^(?>.+?|\n(?!\n))*?(?clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + $tag[1] == '!' || $tag[1] == '?') { # Need to parse tag and following text using the HTML parser. # (don't check for markdown attribute) @@ -2042,8 +2042,8 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, # # Increase/decrease nested tag count. # - if ($tag{1} == '/') $depth--; - else if ($tag{strlen($tag)-2} != '/') $depth++; + if ($tag[1] == '/') $depth--; + else if ($tag[strlen($tag)-2] != '/') $depth++; if ($depth < 0) { # @@ -2147,7 +2147,7 @@ function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { # first character as filtered to prevent an infinite loop in the # parent function. # - return array($original_text{0}, substr($original_text, 1)); + return array($original_text[0], substr($original_text, 1)); } $block_text .= $parts[0]; # Text before current tag. @@ -2159,7 +2159,7 @@ function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { # Comments and Processing Instructions. # if (preg_match('{^auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + $tag[1] == '!' || $tag[1] == '?') { # Just add the tag to the block as if it was text. $block_text .= $tag; @@ -2170,8 +2170,8 @@ function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { # the tag's name match base tag's. # if (preg_match('{^doExtraAttributes("h$level", $dummy =& $matches[2]); $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; @@ -2847,7 +2847,7 @@ function _doFencedCodeBlocks_callback($matches) { array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); if ($classname != "") { - if ($classname{0} == '.') + if ($classname[0] == '.') $classname = substr($classname, 1); $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; } else { diff --git a/readme.txt b/readme.txt index 63e0b9e..49453ae 100644 --- a/readme.txt +++ b/readme.txt @@ -184,6 +184,11 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == += 2.3.3.7 = +* Fixed: Bulk Edit field repetition and possible jQuery conflict +* Fixed: Related Posts check producing error output +* Fixed: WordPress Readme Parser deprecated errors for PHP7 + = 2.3.3.6 = * Update: Freemius SDK (2.4.1) * Update: Plugin Loader (1.1.6) with phone number and CSV validation From 2534cf433acca17a4070d57f2d5ab6d2f076c485 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 11 Dec 2020 12:43:58 +1000 Subject: [PATCH 168/377] bump tested up to WP 5.6 --- readme.md | 9 ++++++++- readme.txt | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index c7a6ad7..05d5f71 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.3.2 +Tested up to: 5.6 Stable tag: trunk @@ -188,6 +188,13 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices +#### 2.3.3.6 +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + #### 2.3.3.5 * Ability to assign Post to relate to multiple Shows * Added Admin Filtering, Bulk Edit and Quick Edit interfaces diff --git a/readme.txt b/readme.txt index 49453ae..bbfc4f1 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.5.3 +Tested up to: 5.6 Stable tag: trunk License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 89b9f66b682681bf87e0ba99d916e65eb90bf13b Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 17 Dec 2020 10:43:47 +1000 Subject: [PATCH 169/377] dev updates #290 --- CHANGELOG.md | 1 + includes/support-functions.php | 51 ++++++++++++++++++++++------------ readme.txt | 1 + 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb433e..2642671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.3.3.7 +* Fixed: Schedule Overrides overlapping multiple Show shifts * Fixed: Bulk Edit field repetition and possible jQuery conflict * Fixed: Related Posts check producing error output * Fixed: WordPress Readme Parser deprecated errors for PHP7 diff --git a/includes/support-functions.php b/includes/support-functions.php index b7d4449..e33b755 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -556,7 +556,8 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { 'url' => get_permalink( $override['ID'] ), 'split' => false, ); - $override_list[$date][] = $override_data; + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; } else { @@ -573,7 +574,8 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { 'url' => get_permalink( $override['ID'] ), 'split' => true, ); - $override_list[$date][] = $override_data; + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; // --- set the next day split shift --- // note: these should not wrap around to start of week @@ -595,13 +597,22 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { 'url' => get_permalink( $override['ID'] ), 'split' => true, ); - $override_list[$nextdate][] = $override_data; + // 2.3.3.7: set array order by start time + $override_list[$nextdate][$override_start_time] = $override_data; } } } } } + // 2.3.3.7: reorder overrides by sequential times + if ( count( $override_list ) > 0 ) { + foreach ( $override_list as $day => $overrides ) { + ksort( $overrides ); + $override_list[$day] = $overrides; + } + } + // --- filter and return --- $override_list = apply_filters( 'radio_station_get_overrides', $override_list, $start_date, $end_date ); @@ -1088,7 +1099,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( isset( $_REQUEST['debug-day'] ) ) { $debugday = $_REQUEST['debug-day']; } - $done_overrides = array(); + // 2.3.3.7: remove check if override already done + // $done_overrides = array(); if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { foreach ( $show_shifts as $day => $shifts ) { @@ -1110,9 +1122,10 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( count( $overrides ) > 0 ) { foreach ( $overrides as $i => $override ) { - // 2.3.1: added check if override already done if ( $date == $override['date'] ) { - if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { + // 2.3.1: added check if override already done + // 2.3.3.7: remove check if override already done + // if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first @@ -1156,13 +1169,13 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( $override_start_time < $start_time ) { // --- maybe add the override --- - if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { - + // 2.3.3.7: remove check if override already done + // if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { // 2.3.1: track done overrides - $done_overrides[] = $override['date'] . '--' . $i; + // 2.3.3.7: remove check if override already done + // $done_overrides[] = $override['date'] . '--' . $i; $show_shifts[$day][$override['start']] = $override; - - } + // } // --- check when the shift ends --- if ( ( $override_end_time > $end_time ) @@ -1179,7 +1192,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // --- same start so overwrite the existing shift --- // 2.3.1: set override done instead of unsetting override - $done_overrides[] = $date . '--' . $i; + // 2.3.3.7: remove check if override already done + // $done_overrides[] = $date . '--' . $i; $show_shifts[$day][$start] = $override; // --- check if there is remainder of existing show --- @@ -1207,7 +1221,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // --- add the override --- $show_shifts[$day][$override['start']] = $override; // 2.3.1: track done instead of unsetting - $done_overrides[] = $date . '--' . $i; + // 2.3.3.7: remove check if override already done + // $done_overrides[] = $date . '--' . $i; // --- partial shift after override ---- if ( $override_end_time < $end_time ) { @@ -1219,7 +1234,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } } } - } + // 2.3.3.7: remove check if override already done + // } } } } @@ -1257,10 +1273,11 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( count( $overrides ) > 0 ) { foreach ( $overrides as $i => $override ) { if ( $date == $override['date'] ) { - if ( !in_array( $date . '--' . $i, $done_overrides ) ) { + // 2.3.3.7: remove check if override already done + // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { $show_shifts[$day][$override['start']] = $override; - $done_overrides[] = $date . '--' . $i; - } + // $done_overrides[] = $date . '--' . $i; + // } } } } diff --git a/readme.txt b/readme.txt index bbfc4f1..730bacb 100644 --- a/readme.txt +++ b/readme.txt @@ -185,6 +185,7 @@ You may translate the plugin into another language. Please visit our [WordPress == Changelog == = 2.3.3.7 = +* Fixed: Schedule Overrides overlapping multiple Show shifts * Fixed: Bulk Edit field repetition and possible jQuery conflict * Fixed: Related Posts check producing error output * Fixed: WordPress Readme Parser deprecated errors for PHP7 From 8eed6bc6de9d8efe3e084ee2c966710985627e6d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 18 Dec 2020 11:10:57 +1000 Subject: [PATCH 170/377] dev updates #290 --- includes/support-functions.php | 229 +++++++++++++++++++-------------- 1 file changed, 129 insertions(+), 100 deletions(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index e33b755..260627b 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1099,8 +1099,7 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( isset( $_REQUEST['debug-day'] ) ) { $debugday = $_REQUEST['debug-day']; } - // 2.3.3.7: remove check if override already done - // $done_overrides = array(); + $done_overrides = array(); if ( $override_list && is_array( $override_list ) && ( count( $override_list ) > 0 ) ) { foreach ( $show_shifts as $day => $shifts ) { @@ -1124,118 +1123,145 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( $date == $override['date'] ) { // 2.3.1: added check if override already done - // 2.3.3.7: remove check if override already done - // if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + // 2.3.3.7: remove check if override already done from here + + $override_start = radio_station_convert_shift_time( $override['start'] ); + $override_end = radio_station_convert_shift_time( $override['end'] ); + $override_start_time = radio_station_to_time( $date . ' ' . $override_start ); + $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); + if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { + $override_end_time = $override_end_time + 1; + } + // 2.3.2: fix for non-split overrides ending on midnight + if ( $override_end_time < $override_start_time ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $override_start = radio_station_convert_shift_time( $override['start'] ); - $override_end = radio_station_convert_shift_time( $override['end'] ); - $override_start_time = radio_station_to_time( $date . ' ' . $override_start ); - $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); - if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { - $override_end_time = $override_end_time + 1; - } - // 2.3.2: fix for non-split overrides ending on midnight - if ( $override_end_time < $override_start_time ) { - $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - } + // --- check for overlapped shift (if any) --- + // 2.3.1 added check for shift count + if ( count( $shifts ) > 0 ) { + // 2.3.3.7: change shifts variable in loop not just show_shifts + foreach ( $shifts as $start => $shift ) { + + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $shift_start = radio_station_convert_shift_time( $shift['start'] ); + $shift_end = radio_station_convert_shift_time( $shift['end'] ); + $start_time = radio_station_to_time( $date . ' ' . $shift_start ); + $end_time = radio_station_to_time( $date . ' ' . $shift_end ); + if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { + $end_time = $end_time + 1; + } + // 2.3.2: fix for non-split shifts ending on midnight + if ( $end_time < $start_time ) { + $end_time = $end_time + ( 24 * 60 * 60 ); + } - // --- check for overlapped shift (if any) --- - // 2.3.1 added check for shift count - if ( count( $shifts ) > 0 ) { - foreach ( $shifts as $start => $shift ) { - - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $start_time = radio_station_to_time( $date . ' ' . $shift_start ); - $end_time = radio_station_to_time( $date . ' ' . $shift_end ); - if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { - $end_time = $end_time + 1; - } - // 2.3.2: fix for non-split shifts ending on midnight - if ( $end_time < $start_time ) { - $end_time = $end_time + ( 24 * 60 * 60 ); + if ( $day == $debugday ) { + $debugshifts .= $day . ' Show from ' . $shift['start'] . ': ' . $start_time . ' to ' . $shift['end'] . ': ' . $end_time . PHP_EOL; + $debugshifts .= $day . ' Override from ' . $override['start'] . ': ' . $override_start_time . ' to ' . $override['end'] . ': ' . $override_end_time . PHP_EOL; + } + + // --- check if the override starts earlier than shift --- + if ( $override_start_time < $start_time ) { + + // --- check when the shift ends --- + if ( ( $override_end_time > $end_time ) + || ( $override_end_time == $end_time ) ) { + // --- overlaps so remove shift --- + if ( $day == $debugday ) { + $debugshifts .= "Removed Shift: " . print_r( $shift, true ) . PHP_EOL; + } + unset( $show_shifts[$day][$start] ); + unset( $shifts[$start] ); + } elseif ( $override_end_time > $start_time ) { + // --- add trimmed shift remainder --- + if ( $day == $debugday ) { + $debugshifts .= "Trimmed Start of Shift to " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; + } + unset( $show_shifts[$day][$start] ); + unset( $shifts[$start] ); + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; } - if ( RADIO_STATION_DEBUG && ( $day == $debugday ) ) { - echo $shift['start'] . ': ' . $start_time . ' - ' . $shift['end'] . ': ' . $end_time . PHP_EOL; - echo $override['start'] . ': ' . $override_start_time . ' - ' . $override['end'] . ': ' . $override_end_time . PHP_EOL; + // --- add the override if not already added --- + if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { + // 2.3.1: track done overrides + $done_overrides[] = $override['date'] . '--' . $i; + if ( $day == $debugday ) { + $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; + } + $shifts[$override['start']] = $override; + $show_shifts[$day] = $shifts; } - // --- check if the override starts earlier than shift --- - if ( $override_start_time < $start_time ) { - - // --- maybe add the override --- - // 2.3.3.7: remove check if override already done - // if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { - // 2.3.1: track done overrides - // 2.3.3.7: remove check if override already done - // $done_overrides[] = $override['date'] . '--' . $i; - $show_shifts[$day][$override['start']] = $override; - // } - - // --- check when the shift ends --- - if ( ( $override_end_time > $end_time ) - || ( $override_end_time == $end_time ) ) { - unset( $show_shifts[$day][$start] ); - } elseif ( $override_end_time > $start_time ) { - unset( $show_shifts[$day][$start] ); - $shift['start'] = $override['end']; - $shift['trimmed'] = 'start'; - $show_shifts[$day][$override['end']] = $shift; + } elseif ( $override_start_time == $start_time ) { + + // --- same start so overwrite the existing shift --- + // 2.3.1: set override done instead of unsetting override + // 2.3.3.7: remove check if override already done + // $done_overrides[] = $date . '--' . $i; + if ( $day == $debugday ) { + $debugshifts .= "Replaced Shift with Override: " . print_r( $show_shifts[$day][$start], true ) . PHP_EOL; + } + $shifts[$start] = $override; + $show_shifts[$day] = $shifts; + + // --- check if there is remainder of existing show --- + if ( $override_end_time < $end_time ) { + $shift['start'] = $override['end']; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; + if ( $day == $debugday ) { + $debugshifts .= "And trimmed Shift Start to " . $override['end'] . PHP_EOL; } + } + // elseif ( $override_end_time == $end_time ) { + // --- remove exact override --- + // do nothing, already overridden + // } - } elseif ( $override_start_time == $start_time ) { + } elseif ( ( $override_start_time > $start_time ) + && ( $override_start_time < $end_time ) ) { - // --- same start so overwrite the existing shift --- - // 2.3.1: set override done instead of unsetting override - // 2.3.3.7: remove check if override already done - // $done_overrides[] = $date . '--' . $i; - $show_shifts[$day][$start] = $override; + $end = $shift['end']; - // --- check if there is remainder of existing show --- - if ( $override_end_time < $end_time ) { - $shift['start'] = $override['end']; - $shift['trimmed'] = 'start'; - $show_shifts[$day][$override['end']] = $shift; - } - // elseif ( $override_end_time == $end_time ) { - // --- remove exact override --- - // do nothing, already overridden - // } - - } elseif ( ( $override_start_time > $start_time ) - && ( $override_start_time < $end_time ) ) { - - $end = $shift['end']; - - // --- partial shift before override --- - $shift['start'] = $start; - $shift['end'] = $override['start']; - $shift['trimmed'] = 'end'; - $show_shifts[$day][$start] = $shift; - - // --- add the override --- - $show_shifts[$day][$override['start']] = $override; - // 2.3.1: track done instead of unsetting - // 2.3.3.7: remove check if override already done - // $done_overrides[] = $date . '--' . $i; - - // --- partial shift after override ---- - if ( $override_end_time < $end_time ) { - $shift['start'] = $override['end']; - $shift['end'] = $end; - $shift['trimmed'] = 'start'; - $show_shifts[$day][$override['end']] = $shift; + // --- partial shift before override --- + if ( $day == $debugday ) { + $debugshifts .= "Trimmed Shift End to " . $override['start'] . ": " . print_r( $shift, true ) . PHP_EOL; + } + $shift['start'] = $start; + $shift['end'] = $override['start']; + $shift['trimmed'] = 'end'; + $shifts[$start] = $shift; + $show_shifts[$day] = $shifts; + + // --- add the override --- + $show_shifts[$day][$override['start']] = $override; + // 2.3.1: track done instead of unsetting + // 2.3.3.7: remove check if override already done here + // $done_overrides[] = $date . '--' . $i; + + // --- partial shift after override ---- + if ( $override_end_time < $end_time ) { + if ( $day == $debugday ) { + $debugshifts .= "And added partial Shift after " . $override['end'] . ": " . print_r( $shift, true ) . PHP_EOL; } + $shift['start'] = $override['end']; + $shift['end'] = $end; + $shift['trimmed'] = 'start'; + $shifts[$override['end']] = $shift; + $show_shifts[$day] = $shifts; } } } - // 2.3.3.7: remove check if override already done - // } + } } } } @@ -1263,6 +1289,9 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } if ( RADIO_STATION_DEBUG ) { + if ( isset( $debugshifts ) ) { + echo "Day Debug: " . $debugshifts . PHP_EOL; + } echo "Shift Keys: " . print_r( $keys, true ) . PHP_EOL; echo "Sorted Keys: " . print_r( $shift_keys, true ) . PHP_EOL; echo "Sorted Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; From 53e12f1b295182797150dddc6e331fdb965df536 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 18 Dec 2020 22:37:50 -0500 Subject: [PATCH 171/377] Update Upgrade Notices in readme.md Update upgrade notice to 2.3.3.7 with a link to the changelog instead of listing out the changes. --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index c12bc5e..2b85687 100644 --- a/readme.md +++ b/readme.md @@ -187,6 +187,8 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices +#### 2.3.3.7 +* Various bug fixes. Please [view the Changelog](./CHANGELOG.md) for details. #### 2.3.3.6 * Update: Freemius SDK (2.4.1) From d3f675465a8b2009fce8476aef4df2b093831131 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 18 Dec 2020 23:20:50 -0500 Subject: [PATCH 172/377] update radio-station.php Package version number updated the package version number, was 2.3.3 to 2.3.3.7 --- radio-station.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio-station.php b/radio-station.php index a352d18..6ffc9ec 100644 --- a/radio-station.php +++ b/radio-station.php @@ -2,7 +2,7 @@ /** * @package Radio Station - * @version 2.3.3 + * @version 2.3.3.7 */ /* From ff805fd3f4e49f69ecc2ba7264b60d728e409ca5 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 6 Jan 2021 20:59:19 -0500 Subject: [PATCH 173/377] Updates to readme files Updates to readme files - added text about usage and some faqs. --- readme.md | 16 ++++++++++++++-- readme.txt | 20 +++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 88f1ab2..8957252 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html ## Description -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. +Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. @@ -77,7 +77,7 @@ This plugin is under active development and we are continuously working to enhan ### Upgrading to Radio Station Pro -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. ## Installation @@ -162,6 +162,18 @@ The only Hosts and Producers that can edit a show are the ones listed as being H Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account and edit your author.php theme file accordingly. That's a little out of the scope of this plugin. I recommend [Cimy User Extra Fields](http://wordpress.org/extend/plugins/cimy-user-extra-fields/) +#### Can I use this plugin for Podcasts? + +While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. + +#### Can I use this plugin for TWitchTV, Facebook Live, or Clubhouse shows? + +Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. + +#### Can I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? + +We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. + #### What languages other than English is the plugin available in? Right now: diff --git a/readme.txt b/readme.txt index 730bacb..4d484b4 100644 --- a/readme.txt +++ b/readme.txt @@ -12,7 +12,7 @@ Radio Station let's you build and manage a Show Schedule for a radio station or == Description == -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. +Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. @@ -22,7 +22,7 @@ The plugin contains a widget to display the on-air Current Show linked to the Sh As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://netmix.com/radio-station/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostationdemo.com). -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. +We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. Enter your [email address on the Radio Station Pro website] (https://radiostation.pro) to add yourself to the cue to be able to download the be notified when PRO is released. = Updating from Prior to 2.3.0 = @@ -66,7 +66,7 @@ This plugin is under active development and we are continuously working to enhan = Upgrading to Radio Station Pro = -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. == Installation == @@ -172,6 +172,20 @@ Right now: You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/locale/en-gb/default/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. += Can I use this plugin for Podcasts? = + +While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. + += Can I use this plugin for TWitchTV, Facebook Live, or Clubhouse shows? = + +Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. + += I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? = + +We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. + + + == Screenshots == 1. Table Schedule View 2. Tabbed Schedule View From e564bda8860768abd476c590a94e3b836cc6843d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 21 Jan 2021 09:52:18 +1000 Subject: [PATCH 174/377] dev updates #283, #299, #304, #306, #307 --- CHANGELOG.md | 15 + css/rs-schedule.css | 64 ++- docs/Shortcodes.md | 4 +- docs/Widgets.md | 7 +- includes/class-current-playlist-widget.php | 18 +- includes/class-current-show-widget.php | 28 +- includes/class-upcoming-shows-widget.php | 12 +- includes/data-feeds.php | 60 +- includes/master-schedule.php | 29 +- includes/post-types-admin.php | 2 +- includes/shortcodes.php | 593 +++++++++++-------- includes/support-functions.php | 103 +++- js/wp-color-picker-alpha.js | 635 +++++++++++++++++++++ js/wp-color-picker-alpha.min.js | 11 + loader.php | 325 ++++++++--- radio-station.php | 403 +++++++++++-- readme.md | 48 +- readme.txt | 31 +- templates/master-schedule-list.php | 215 ++++--- templates/master-schedule-table.php | 258 +++++---- templates/master-schedule-tabs.php | 289 ++++++---- 21 files changed, 2366 insertions(+), 784 deletions(-) create mode 100644 js/wp-color-picker-alpha.js create mode 100644 js/wp-color-picker-alpha.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2642671..8a65ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.3.8 +* Update: Plugin Panel (1.1.7) with Image and Color Picker fields +* Added: Stream Format selection setting +* Added: Station Email Address setting with default display option +* Added: Section order filtering for Master Schedule Views +* Added: Section display filtering for Master Schedule Views +* Added: Section display filtering for Widget sections +* Added: Show image alignment attribute to Schedule Tabs View +* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) +* Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Fixed: Display Widget Countdown when no Current Show/Playlist +* Fixed: Check for explicit singular.php template usage setting +* Fixed: Access to Shows Data via querystring of Show ID/name +* Fixed: Shows Data for Genres/Languages querystring of ID/name + ### 2.3.3.7 * Fixed: Schedule Overrides overlapping multiple Show shifts * Fixed: Bulk Edit field repetition and possible jQuery conflict diff --git a/css/rs-schedule.css b/css/rs-schedule.css index c2b4c03..1e95ec7 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -191,7 +191,11 @@ border: 1px dashed orange; background-color: rgba(235,235,0,0.4); } - + +#master-program-schedule .master-show-entry.before-current { + opacity: 0.8; +} + #master-program-schedule span.show-title, #master-program-schedule span.show-file, #master-program-schedule span.show-time, @@ -336,14 +340,18 @@ } #master-schedule-tabs .master-schedule-tabs-day.active-day-tab .master-schedule-tab-bottom { - position: absolute; - display: block; + position: absolute; + display: block; bottom: 0; - height: 1px; - width: 100%; - background-color: #FFF; - margin-bottom: -1px; - z-index: 999; + height: 1px; + width: 100%; + background-color: #FFF; + margin-bottom: -1px; + z-index: 999; +} + +#master-schedule-tab-panels { + min-width: 300px; } #master-schedule-tab-panels .master-schedule-tabs-selected { @@ -394,6 +402,14 @@ border-radius: 0 7px 7px 7px; } +#master-schedule-tab-panels .master-schedule-tabs-show.before-current { + opacity: 0.8; +} + +#master-schedule-tab-panels.hide-past-shows .master-show-entry.before-current { + display: none; +} + #master-schedule-tab-panels .master-schedule-tabs-show .show-info, #master-schedule-tab-panels .master-schedule-tabs-show .show-image, #master-schedule-tab-panels .master-schedule-tabs-show .show-desc { @@ -408,10 +424,27 @@ text-align: center; } +#master-schedule-tab-panels .master-schedule-tabs-show .show-image.right-image { + margin-right: 0; + margin-left: 10px; +} + #master-schedule-tab-panels .master-schedule-tabs-show .show-info { + min-width: 260px; +} + +#master-schedule-tab-panels .master-schedule-tabs-show .show-info.has-show-desc { width: 260px; } +#master-schedule-tab-panels .master-schedule-tabs-show .show-info.left-image { + text-align: left; +} + +#master-schedule-tab-panels .master-schedule-tabs-show .show-info.right-image { + text-align: right; +} + #master-schedule-tab-panels .master-schedule-tabs-show .show-desc { max-width: 400px; } @@ -496,3 +529,18 @@ width: 50px; z-index: 5; } + +/* Media Queries */ +@media screen and (max-width: 782px) { + #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 120px;} +} + +@media screen and (max-width: 599px) { + #master-program-schedule .show-image, + #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 90px;} + #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 75px;} +} + +@media screen and (max-width: 299px) { + #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 50px;} +} \ No newline at end of file diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 08fd784..a880276 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -41,7 +41,9 @@ The following attributes are available for the shortcode: * *display_day* : Full or short day heading ('full' or 'short') Default short for Table, full for Tabs/List. * *display_date* : Date format for date subheading. 0 for none. Default 'jS' for Table/List, 0 for Tabs. * *display_month* : Full or short month subheading ('full', 'short') Default 'short'. -* *divheight* : Set the height, in pixels, of the individual divs. For 'divs' view only. Default 45. +* *image_position* : Show image position for Tabs view. 'right', 'left' or 'alternate'. Default 'left'. +* *hide_past_shows* : Hide shows that are finished in the Schedule for Tabs view. 0 or 1. Default 0. +* *divheight* : Set the height, in pixels, of the individual divs. For legacy 'divs' view only. Default 45. Example: Display the schedule in 24-hour time format, use `[master-schedule time="24"]`. diff --git a/docs/Widgets.md b/docs/Widgets.md index c3dba82..bf75c5f 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -13,7 +13,7 @@ Note Widgets are displayed via their corresponding Shortcodes to prevent code du Displays the currently playing Show - if there is one scheduled to play right now. ### Current Show Shortcode -`[current-show]` (legacy supported name `[dj-widget]`) +`[current-show]` (legacy supported name: `[dj-widget]`) The following attributes are available for this shortcode: @@ -28,6 +28,7 @@ The following attributes are available for this shortcode: * *show_playlist* : Display a link to the show's current playlist, if any. 0 or 1. Default is 1. * *show_all_sched* : Displays all schedules for a show if it airs on multiple days. 0 or 1. Default is 0. * *show_desc* : Display an excerpt of the show's description. 0 or 1. Default is 0. +* *show_encore* : Display encore presentation text (when set for Show). 0 or 1. Default is 1. * *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. * *avatar_width* : Set a width style in pixels for Show Avatars. Default is not to set. * *countdown* : Display a Countdown until the current Show ends. 0 or 1. Default is 0. @@ -41,7 +42,7 @@ Displays a limited list of Upcoming Shows - if there are Shows scheduled. ### Upcoming Shows Shortcode -`[upcoming-shows]` (legacy supported name `[dj-coming-up-widget]`) +`[upcoming-shows]` (legacy supported name: `[dj-coming-up-widget]`) The following attributes are available for this shortcode: @@ -53,6 +54,7 @@ The following attributes are available for this shortcode: * *link_hosts* : Link Show hosts to their profile pages. 0 or 1. Default is 0. * *time* : The time format used for displaying schedules. 12 or 24. Default is global Plugin Setting. * *show_sched* : Display the show's schedules. 0 or 1. Default is 1. +* *show_encore* : Display encore presentation text (when set for Show). 0 or 1. Default is 1. * *default_name* : The text you would like to display when no upcoming show is scheduled. Default is none. * *title_position* : Relative to Avatar. 'above', 'below', 'left' or 'right'. Default is 'right'. * *avatar_width* : Set a width style in pixels for Show Avatars. Default is not to set. @@ -71,6 +73,7 @@ Displays the Playlist assigned to the currently playing Show - if there is one a The following attributes are available for this shortcode: * *title* : The title you would like to appear over the Playlist display block +* *link* : Link to the Playlist's page. 0 or 1. Default is 1. * *artist* : Display artist name. 0 or 1 Default is 1. * *song* : Display song name. 0 or 1. Default is 1. * *album* : Display album name. 0 or 1. Default is 0. diff --git a/includes/class-current-playlist-widget.php b/includes/class-current-playlist-widget.php index 1199ac9..4deb523 100644 --- a/includes/class-current-playlist-widget.php +++ b/includes/class-current-playlist-widget.php @@ -26,7 +26,9 @@ public function form( $instance ) { // 2.3.0: added hide widget if empty option // 2.3.0: added countdown display option // 2.3.2: added AJAX load option + // 2.3.3.8: added playlist link option $title = $instance['title']; + $link = isset( $instance['link'] ) ? $instance['link'] : true; $artist = isset( $instance['artist'] ) ? $instance['artist'] : true; $song = isset( $instance['song'] ) ? $instance['song'] : true; $album = isset( $instance['album'] ) ? $instance['album'] : false; @@ -38,6 +40,7 @@ public function form( $instance ) { // 2.3.0: convert template style code to strings // 2.3.2: added AJAX load option field + // 2.3.3.8: added playlist link field $fields = '

    +

    + +

    +

    - +

    - +

    -

    @@ -134,6 +136,13 @@ public function form( $instance ) {

    +

    + +

    +

    - +

    - +

    - +

      '; - if ( !$current_shift ) { - - // --- default output if no current shift --- - $output .= '
    • '; - if ( !empty( $atts['default_name'] ) ) { - $no_current_show = esc_html( $atts['default_name'] ); - } else { - $no_current_show = esc_html( __( 'No Show currently scheduled.', 'radio-station') ); - } - // 2.3.1: add filter for no current shows text - $no_current_show = apply_filters( 'radio_station_no_current_show_text', $no_current_show, $atts ); - $output .= $no_current_show; - $output .= '
    • '; - - } else { + // --- current shift display --- + if ( $current_shift ) { // --- get time formats --- // 2.3.2: moved out to get once @@ -1422,8 +1411,6 @@ function radio_station_current_show_shortcode( $atts ) { // --- set current show data --- $show = $current_shift['show']; - $output .= '
    • '; - // --- get show link --- $show_link = false; if ( $atts['show_link'] ) { @@ -1549,6 +1536,9 @@ function radio_station_current_show_shortcode( $atts ) { $shift_display .= '
    '; } + // --- set clear div --- + $html['clear'] = ''; + // --- set show title output --- $title = '
    '; if ( $show_link ) { @@ -1557,7 +1547,11 @@ function radio_station_current_show_shortcode( $atts ) { $title .= esc_html( $show['name'] ); } $title .= '
    '; - $html['title'] = $title; + // 2.3.3.8: added current show title filter + $title = apply_filters( 'radio_station_current_show_title_display', $title, $show['id'], $atts ); + if ( ( '' != $title ) && is_string( $title ) ) { + $html['title'] = $title; + } // --- show avatar --- if ( $atts['show_avatar'] ) { @@ -1568,30 +1562,36 @@ function radio_station_current_show_shortcode( $atts ) { $show_avatar = radio_station_get_show_avatar( $show['id'] ); $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show['id'], $atts ); if ( $show_avatar ) { - $html['avatar'] = '
    '; + $avatar = '
    '; if ( $show_link ) { - $html['avatar'] .= '' . $show_avatar . ''; + $avatar .= '' . $show_avatar . ''; } else { - $html['avatar'] .= $show_avatar; + $avatar .= $show_avatar; + } + $avatar .= '
    '; + + // 2.3.3.8: added avatar display filter + $avatar = apply_filters( 'radio_station_current_show_avatar_display', $avatar, $show['id'], $atts ); + if ( ( '' != $avatar ) && is_string( $avatar ) ) { + $html['avatar'] = $avatar; } - $html['avatar'] .= '
    '; } } // --- show DJs / hosts --- if ( $atts['display_hosts'] ) { - $hosts = get_post_meta( $show['id'], 'show_user_list', true ); - - if ( $hosts && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + $hosts = ''; + $show_hosts = get_post_meta( $show['id'], 'show_user_list', true ); + if ( $show_hosts && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $html['hosts'] = '
    '; + $hosts = '
    '; - $html['hosts'] .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; $host_count = count( $hosts ); - foreach ( $hosts as $host ) { + foreach ( $show_hosts as $host ) { $count ++; @@ -1611,24 +1611,28 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.3.5: only wrap with tags if there is a link if ( $host_link ) { - $html['hosts'] .= ''; + $hosts .= ''; } - $html['hosts'] .= esc_html( $user->display_name ); + $hosts .= esc_html( $user->display_name ); if ( $host_link ) { - $html['hosts'] .= ''; + $hosts .= ''; } } else { - $html['hosts'] .= esc_html( $user->display_name ); + $hosts .= esc_html( $user->display_name ); } if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) == $count ) ) ) { - $html['hosts'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $html['hosts'] .= ', '; + $hosts .= ', '; } } - $html['hosts'] .= '
    '; + $hosts .= '
    '; + } + $hosts = apply_filters( 'radio_station_current_show_hosts_display', $hosts, $show['id'], $atts ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $html['hosts'] = $hosts; } } @@ -1639,28 +1643,40 @@ function radio_station_current_show_shortcode( $atts ) { // --- encore presentation --- // 2.3.0: added encore presentation display - if ( isset( $show['encore'] ) && ( $show['encore'] ) ) { - $html['encore'] = '
    '; - $html['encore'] .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); - $html['encore'] .= '
    '; + // 2.3.3.8: added shortcode attribute check (with default 1) + if ( $atts['show_encore'] ) { + $encore = ''; + if ( isset( $show['encore'] ) && ( $show['encore'] ) ) { + $encore = '
    '; + $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); + $encore .= '
    '; + } + // 2.3.3.8: added encore display filter + $encore = apply_filters( 'radio_station_current_show_encore_display', $encore, $show['id'], $atts ); + if ( ( '' != $encore ) && is_string( $encore ) ) { + $html['encore'] = $encore; + } } - $html['clear'] = ''; - // --- current show playlist --- // 2.3.0: convert span to div tags for consistency if ( $atts['show_playlist'] ) { // 2.3.0: use new function to get current playlist - $playlist = radio_station_get_now_playing(); + $current_playlist = radio_station_get_now_playing(); if ( RADIO_STATION_DEBUG ) { - $output .= '' . print_r( $playlist, true ) . ''; + $output .= 'Current Playlist: ' . print_r( $current_playlist, true ) . ''; + } + if ( $current_playlist && isset( $current_playlist['playlist_url'] ) ) { + $playlist = ''; } - if ( $playlist && isset( $playlist['playlist_url'] ) ) { - $html['playlist'] = ''; + // 2.3.3.8: added playlist diplay filter + $playlist = apply_filters( 'radio_station_current_show_playlist_display', $playlist, $show['id'], $atts ); + if ( ( '' != $playlist ) && is_string( $playlist ) ) { + $html['playlist'] = $playlist; } } @@ -1692,42 +1708,81 @@ function radio_station_current_show_shortcode( $atts ) { // --- filter excerpt by context --- // 2.3.0: added contextual filtering if ( $atts['widget'] ) { - $excerpt = apply_filters( 'radio_station_current_show_widget_excerpt', $excerpt, $show['id'] ); + $excerpt = apply_filters( 'radio_station_current_show_widget_excerpt', $excerpt, $show['id'], $atts ); } else { - $excerpt = apply_filters( 'radio_station_current_show_shortcode_excerpt', $excerpt, $show['id'] ); + $excerpt = apply_filters( 'radio_station_current_show_shortcode_excerpt', $excerpt, $show['id'], $atts ); } - // --- output excerpt --- - $html['description'] = '
    '; - $html['description'] .= $excerpt; - $html['description'] .= '
    '; + // --- set description --- + $description = ''; + if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { + $description = '
    '; + $description .= $excerpt; + $description .= '
    '; + } + $description = apply_filters( 'radio_station_current_show_description_display', $description, $show['id'], $atts ); + if ( ( '' != $description ) && is_string( $description ) ) { + $html['description'] = $description; + } } // --- output full show schedule --- // 2.3.2: do not display all shifts for overrides if ( $atts['show_all_sched'] && !isset( $current_shift['override'] ) ) { - $html['schedule'] = $shift_display; + $schedule = apply_filters( 'radio_station_current_show_shifts_display', $shift_display, $show['id'], $atts ); + if ( ( '' != $schedule ) && is_string( $schedule ) ) { + $html['schedule'] = $schedule; + } } + // --- custom HTML section --- + // 2.3.3.8: added custom HTML section + $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show['id'] ); + + // --- open current show list item --- + $output .= '
  • '; + // --- filter display section order --- // 2.3.1: added filter for section order display if ( 'above' == $atts['title_position'] ) { - $order = array( 'title', 'avatar', 'hosts', 'shift', 'encore', 'clear', 'playlist', 'countdown', 'description', 'clear', 'schedule' ); + $order = array( 'title', 'avatar', 'hosts', 'shift', 'encore', 'clear', 'playlist', 'countdown', 'description', 'clear', 'schedule', 'custom' ); } else { - $order = array( 'avatar', 'title', 'hosts', 'shift', 'encore', 'clear', 'playlist', 'countdown', 'description', 'clear', 'schedule' ); + $order = array( 'avatar', 'title', 'hosts', 'shift', 'encore', 'clear', 'playlist', 'countdown', 'description', 'clear', 'schedule', 'custom' ); } $order = apply_filters( 'radio_station_current_show_section_order', $order, $atts ); foreach ( $order as $section ) { - if ( isset( $html[$section] ) ) { + if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { $output .= $html[$section]; } } + // --- close current show list item --- $output .= '
  • '; + } else { + + // --- no current show shift display --- + $output .= '
  • '; + if ( !empty( $atts['default_name'] ) ) { + $no_current_show = esc_html( $atts['default_name'] ); + } else { + $no_current_show = esc_html( __( 'No Show currently scheduled.', 'radio-station') ); + } + // 2.3.1: add filter for no current shows text + $no_current_show = apply_filters( 'radio_station_no_current_show_text', $no_current_show, $atts ); + $output .= $no_current_show; + $output .= '
  • '; + + // --- countdown timer display --- + // 2.3.3.8: add countdown timer div regardless of no current show + // (so timer can update when a current show starts) + if ( $atts['countdown'] ) { + $output .= '
  • '; + } + } - // --- close show list --- + // --- close current show list --- $output .= ''; // --- countdown timers --- @@ -1845,6 +1900,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: set default time format to plugin setting // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute + // 2.3.3.8: added show_encore attribute (default 1) $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( // --- legacy options --- @@ -1859,6 +1915,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 'display_producers' => 0, // 'show_desc' => 0, 'display_hosts' => 0, + 'show_encore' => 1, 'link_hosts' => 0, 'avatar_width' => '', 'title_position' => 'right', @@ -1990,21 +2047,18 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- open upcoming show list --- $output .= '
      '; - // --- no shows upcoming output --- - if ( !$shows ) { + // --- shows upcoming output --- + if ( $shows ) { - $output .= '
    • '; - if ( ! empty( $atts['default_name'] ) ) { - $no_upcoming_shows = esc_html( $atts['default_name'] ); + // --- filter display section order --- + // 2.3.1: added filter for section order display + // 2.3.3.8: moved section order filter outside of show shift loop + if ( 'above' == $atts['title_position'] ) { + $order = array( 'title', 'avatar', 'hosts', 'shift', 'clear', 'countdown', 'encore', 'custom' ); } else { - $no_upcoming_shows = esc_html( __( 'No Upcoming Shows Scheduled.', 'radio-station' ) ); + $order = array( 'avatar', 'title', 'hosts', 'shift', 'clear', 'countdown', 'encore', 'custom' ); } - // 2.3.1: add filter for no current shows text - $no_upcoming_shows = apply_filters( 'radio_station_no_upcoming_shows_text', $no_upcoming_shows, $atts ); - $output .= $no_upcoming_shows; - $output .= '
    • '; - - } else { + $order = apply_filters( 'radio_station_upcoming_shows_section_order', $order, $atts ); // --- set shift display data formats --- // 2.2.7: fix to convert time to integer @@ -2047,18 +2101,10 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $show_link = apply_filters( 'radio_station_upcoming_show_link', $show_link, $show['id'], $atts ); } - $output .= '
    • '; - // --- check show schedule --- // 2.3.1: check earlier for later display if ( $atts['show_sched'] || $atts['countdown'] || $atts['dynamic'] ) { - $shift_display = '
      '; - - if ( RADIO_STATION_DEBUG ) { - $shift_display .= "Upcoming Shift: " . print_r( $shift, true ) . ""; - } - // --- set shift start and end --- if ( isset( $shift['real_start'] ) ) { $start = $shift['real_start']; @@ -2115,16 +2161,22 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $end = radio_station_translate_time( $end ); // --- set shift display output --- + $shift_display = '
      '; $shift_display .= '
      '; $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; $shift_display .= '' . esc_html( $end ) . ''; $shift_display .= '
      '; - $shift_display .= '
      '; + if ( RADIO_STATION_DEBUG ) { + $shift_display .= "Upcoming Shift: " . print_r( $shift, true ) . ""; + } } - // --- set show title output --- + // --- set clear div --- + $html['clear'] = ''; + + // --- set show title --- $title = '
      '; if ( $show_link ) { $title .= '' . esc_html( $show['name'] ) . ''; @@ -2132,44 +2184,50 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $title .= esc_html( $show['name'] ); } $title .= '
      '; - $html['title'] = $title; + $title = apply_filters( 'radio_station_upcoming_show_title_display', $title, $show['id'], $atts ); + if ( ( '' != $title ) && is_string( $title ) ) { + $html['title'] = $title; + } - // --- show avatar --- + // --- set show avatar --- if ( $atts['show_avatar'] ) { // 2.3.0: get show avatar (with thumbnail fallback) // 2.3.0: filter show avatar by context // 2.3.0: maybe link avatar to show + $avatar = ''; $show_avatar = radio_station_get_show_avatar( $show['id'] ); $show_avatar = apply_filters( 'radio_station_upcoming_show_avatar', $show_avatar, $show['id'], $atts ); if ( $show_avatar ) { - $html['avatar'] = '
      '; + $avatar = '
      '; if ( $atts['show_link'] ) { - $html['avatar'] .= ''; + $avatar .= ''; } - $html['avatar'] .= $show_avatar; + $avatar .= $show_avatar; if ( $atts['show_link'] ) { - $html['avatar'] .= ''; + $avatar .= ''; } - $html['avatar'] .= '
      '; + $avatar .= '
      '; + } + $avatar = apply_filters( 'radio_station_upcoming_show_avatar_display', $avatar, $show['id'], $atts ); + if ( ( '' != $avatar ) && is_string( $avatar ) ) { + $html['avatar'] = $avatar; } } - // $output .= ''; - - // --- DJ / Host names --- + // --- set DJ / Host names --- if ( $atts['display_hosts'] ) { - $hosts = get_post_meta( $show['id'], 'show_user_list', true ); - if ( isset( $hosts ) && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { - - $html['hosts'] = '
      '; + $hosts = ''; + $show_hosts = get_post_meta( $show['id'], 'show_user_list', true ); + if ( isset( $show_hosts ) && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $html['hosts'] .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + $hosts = '
      '; + $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; $host_count = count( $hosts ); - foreach ( $hosts as $host ) { + foreach ( $show_hosts as $host ) { $count ++; @@ -2189,64 +2247,94 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.3.5: only wrap with tags if there is a link if ( $host_link ) { - $html['hosts'] .= ''; + $hosts .= ''; } - $html['hosts'] .= esc_html( $user->display_name ); + $hosts .= esc_html( $user->display_name ); if ( $host_link ) { - $html['hosts'] .= ''; + $hosts .= ''; } } else { - $html['hosts'] .= esc_html( $user->display_name ); + $hosts .= esc_html( $user->display_name ); } if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) || ( ( $host_count > 2 ) && ( $count == ( $host_count - 1 ) ) ) ) { - $html['hosts'] .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $html['hosts'] .= ', '; + $hosts .= ', '; } } - $html['hosts'] .= '
      '; + $hosts .= '
      '; + } + $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show['id'], $atts ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $html['hosts'] = $hosts; } } - // --- encore presentation --- + // --- set encore presentation --- // 2.2.4: added encore presentation display - if ( isset( $show['encore'] ) && ( 'on' == $show['encore'] ) ) { - $html['encore'] = '
      '; - $html['encore'] .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); - $html['encore'] .= '
      '; + // 2.3.3.8: added shortcode attribute for encore display (default 1) + if ( $atts['show_encore'] ) { + $encore = ''; + if ( isset( $show['encore'] ) && ( 'on' == $show['encore'] ) ) { + $encore = '
      '; + $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); + $encore .= '
      '; + } + $encore = apply_filters( 'radio_station_upcoming_show_encore_display', $encore, $show['id'], $atts ); + if ( ( '' != $encore ) && is_string( $encore ) ) { + $html['encore'] = $encore; + } } - $html['clear'] = ''; - - // --- countdown timer display --- + // --- set countdown timer --- if ( ( 0 == $i ) && isset( $next_start_time ) && $atts['countdown'] ) { $html['countdown'] = '
      '; } - // --- output show schedule --- + // --- set show schedule --- if ( $atts['show_sched'] ) { - $html['shift'] = $shift_display; + $schedule = apply_filters( 'radio_station_upcoming_show_shifts_display', $shift_display, $show['id'], $atts ); + if ( ( '' != $schedule ) && is_string( $schedule ) ) { + $html['shift'] = $schedule; + } } - // --- filter display section order --- - // 2.3.1: added filter for section order display - if ( 'above' == $atts['title_position'] ) { - $order = array( 'title', 'avatar', 'hosts', 'shift', 'clear', 'countdown', 'encore' ); - } else { - $order = array( 'avatar', 'title', 'hosts', 'shift', 'clear', 'countdown', 'encore' ); - } - $order = apply_filters( 'radio_station_upcoming_shows_section_order', $order, $atts ); + // --- custom HTML section --- + // 2.3.3.8: added custom HTML section + $html['custom'] = apply_filters( 'radio_station_upcoming_show_custom_display', '', $show['id'], $atts ); + + // --- open upcoming show list item --- + $output .= '
    • '; + + // --- add output according to section order --- + // 2.3.3.8: moved section order filter out of show shift loop foreach ( $order as $section ) { - if ( isset( $html[$section] ) ) { + if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { $output .= $html[$section]; } } + // --- close upcoming show list item --- $output .= '
    • '; } + } else { + + // --- no shows upcoming --- + // note: no countdown display added as no upcoming shows found + $output .= '
    • '; + if ( ! empty( $atts['default_name'] ) ) { + $no_upcoming_shows = esc_html( $atts['default_name'] ); + } else { + $no_upcoming_shows = esc_html( __( 'No Upcoming Shows Scheduled.', 'radio-station' ) ); + } + // 2.3.1: add filter for no current shows text + $no_upcoming_shows = apply_filters( 'radio_station_no_upcoming_shows_text', $no_upcoming_shows, $atts ); + $output .= $no_upcoming_shows; + $output .= '
    • '; + } // --- close upcoming shows list --- @@ -2368,6 +2456,7 @@ function radio_station_current_playlist_shortcode( $atts ) { 'label' => 0, 'comments' => 0, // --- new options --- + 'link' => 1, 'countdown' => 0, 'ajax' => $ajax, 'dynamic' => $dynamic, @@ -2464,11 +2553,97 @@ function radio_station_current_playlist_shortcode( $atts ) { } } + // --- set empty HTML array --- + $html = array(); + + // --- countdown timer --- + // 2.3.0: added for countdown changeovers + // 2.3.3.8: moved outside of current playlist check + if ( $atts['countdown'] || $atts['dynamic'] ) { + + // 2.3.1: added check for playlist shifts value + if ( isset( $playlist['shifts'] ) && is_array( $playlist['shifts'] ) && ( count( $playlist['shifts'] ) > 0 ) ) { + + // --- convert dates --- + // 2.3.0: use weekdates for reliability + if ( $atts['for_time'] ) { + $now = $atts['for_time']; + } else { + $now = radio_station_get_now(); + } + $today = radio_station_get_time( 'l', $now ); + $yesterday = radio_station_get_previous_day( $today ); + $weekdays = radio_station_get_schedule_weekdays( $yesterday ); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + + // --- loop shifts --- + foreach ( $playlist['shifts'] as $shift ) { + + // --- set shift start and end --- + if ( isset( $shift['real_start'] ) ) { + $start = $shift['real_start']; + } elseif ( isset( $shift['start'] ) ) { + $start = $shift['start']; + } else { + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + } + if ( isset( $shift['real_end'] ) ) { + $end = $shift['real_end']; + } elseif ( isset( $shift['end'] ) ) { + $end = $shift['end']; + } else { + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + } + + // --- convert shift info --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + if ( isset( $shift['real_start'] ) ) { + $prevday = radio_station_get_previous_day( $shift['day'] ); + $shift_start_time = radio_station_to_time( $weekdates[$prevday] . ' ' . $start_time ); + } else { + $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + } + $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + if ( $start > $end ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } + + // --- check currently playing show time --- + if ( $atts['for_time'] ) { + $now = $atts['for_time']; + } else { + $now = radio_station_get_now(); + } + if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + + // --- hidden input for playlist end time --- + $html['countdown'] = ''; + + // --- for countdown timer display --- + if ( $atts['countdown'] ) { + $html['countdown'] .= '
      '; + } + + // --- for dynamic reloading --- + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current_playlist', $atts, $shift_end_time ); + if ( $dynamic ) { + $html['countdown'] .= $dynamic; + } + } + } + } + } + } + // 2.3.0: use updated code from now playing widget if ( $playlist ) { // 2.3.0: split div wrapper from track wrapper - $output .= '
      '; + $tracks = '
      '; // --- loop playlist tracks --- // 2.3.0: loop all instead of just latest @@ -2487,138 +2662,72 @@ function radio_station_current_playlist_shortcode( $atts ) { $class .= ' played'; } - $html['tracks'] = '
      '; + $tracks .= '
      '; // 2.2.3: convert span tags to div tags // 2.2.4: check value keys are set before outputting if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { - $html['tracks'] .= '
      '; - $html['tracks'] .= esc_html( __( 'Song', 'radio-station' ) ); - $html['tracks'] .= ': ' . esc_html( $track['playlist_entry_song'] ); - $html['tracks'] .= '
      '; + $tracks .= '
      '; + $tracks .= esc_html( __( 'Song', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ); + $tracks .= '
      '; } // 2.2.7: add label prefixes to now playing data if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { - $html['tracks'] .= '
      '; - $html['tracks'] .= esc_html( __( 'Artist', 'radio-station' ) ); - $html['tracks'] .= ': ' . esc_html( $track['playlist_entry_artist'] ); - $html['tracks'] .= '
      '; + $tracks .= '
      '; + $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ); + $tracks .= '
      '; } if ( $atts['album'] && !empty( $track['playlist_entry_album'] ) ) { - $html['tracks'] .= '
      '; - $html['tracks'] .= esc_html( __( 'Album', 'radio-station' ) ); - $html['tracks'] .= ': ' . esc_html( $track['playlist_entry_album'] ); - $html['tracks'] .= '
      '; + $tracks .= '
      '; + $tracks .= esc_html( __( 'Album', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ); + $tracks .= '
      '; } if ( $atts['label'] && !empty( $track['playlist_entry_label'] ) ) { - $html['tracks'] .= '
      '; - $html['tracks'] .= esc_html( __( 'Label', 'radio-station' ) ); - $html['tracks'] .= ': ' . esc_html( $track['playlist_entry_label'] ); - $html['tracks'] .= '
      '; + $tracks .= '
      '; + $tracks .= esc_html( __( 'Label', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ); + $tracks .= '
      '; } if ( $atts['comments'] && !empty( $track['playlist_entry_comments'] ) ) { - $html['tracks'] .= '
      '; - $html['tracks'] .= esc_html( __( 'Comments', 'radio-station' ) ); - $html['tracks'] .= ': ' . esc_html( $track['playlist_entry_comments'] ); - $html['tracks'] .= '
      '; + $tracks .= '
      '; + $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ); + $tracks .= '
      '; } - $html['tracks'] .= '
      '; + $tracks .= '
      '; } } - // --- playlist permalink --- - if ( isset( $playlist['playlist_permalink'] ) ) { - $html['link'] = ''; + // 2.3.3.8 added track display filter + $tracks .= '
      '; + $tracks = apply_filters( 'radio_station_current_playlist_tracks_display', $tracks, $playlist, $atts ); + if ( ( '' != $tracks ) && is_string( $tracks ) ) { + $html['tracks'] = $tracks; } - // --- countdown timer --- - // 2.3.0: added for countdown changeovers - if ( $atts['countdown'] || $atts['dynamic'] ) { - - // 2.3.1: added check for playlist shifts value - if ( isset( $playlist['shifts'] ) && is_array( $playlist['shifts'] ) && ( count( $playlist['shifts'] ) > 0 ) ) { - - // --- convert dates --- - // 2.3.0: use weekdates for reliability - if ( $atts['for_time'] ) { - $now = $atts['for_time']; - } else { - $now = radio_station_get_now(); - } - $today = radio_station_get_time( 'l', $now ); - $yesterday = radio_station_get_previous_day( $today ); - $weekdays = radio_station_get_schedule_weekdays( $yesterday ); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - // --- loop shifts --- - foreach ( $playlist['shifts'] as $shift ) { - - // --- set shift start and end --- - if ( isset( $shift['real_start'] ) ) { - $start = $shift['real_start']; - } elseif ( isset( $shift['start'] ) ) { - $start = $shift['start']; - } else { - $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - } - if ( isset( $shift['real_end'] ) ) { - $end = $shift['real_end']; - } elseif ( isset( $shift['end'] ) ) { - $end = $shift['end']; - } else { - $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - } - - // --- convert shift info --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - if ( isset( $shift['real_start'] ) ) { - $prevday = radio_station_get_previous_day( $shift['day'] ); - $shift_start_time = radio_station_to_time( $weekdates[$prevday] . ' ' . $start_time ); - } else { - $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - } - $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - if ( $start > $end ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } - - // --- check currently playing show time --- - if ( $atts['for_time'] ) { - $now = $atts['for_time']; - } else { - $now = radio_station_get_now(); - } - if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { - - // --- hidden input for playlist end time --- - $html['countdown'] = ''; - - // --- for countdown timer display --- - if ( $atts['countdown'] ) { - $html['countdown'] .= '
      '; - } - - // --- for dynamic reloading --- - if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current_playlist', $atts, $shift_end_time ); - if ( $dynamic ) { - $html['countdown'] .= $dynamic; - } - } - } - } + // --- playlist permalink --- + // 2.3.3.8 added playlist_link shortcode attribute (default 1) + if ( $atts['link'] ) { + $link = ''; + if ( isset( $playlist['playlist_permalink'] ) ) { + $link = ''; + } + // 2.3.3.8: added playlist link display filter + $link = apply_filters( 'radio_station_current_playlist_link_display', $link, $playlist, $atts ); + if ( ( '' != $link ) && is_string( $link ) ) { + $html['link'] = $link; } } @@ -2633,14 +2742,26 @@ function radio_station_current_playlist_shortcode( $atts ) { } } else { + // 2.2.3: added missing translation wrapper // 2.3.0: added no playlist class // 2.3.1: add filter for no playlist text - $output .= '
      '; $no_current_playlist = esc_html( __( 'No Current Playlist available.', 'radio-station' ) ); $no_current_playlist = apply_filters( 'radio_station_no_current_playlist_text', $no_current_playlist, $atts ); - $output .= $no_current_playlist; - $output .= '
      '; + $no_playlist .= $no_current_playlist; + $no_playlist .= '
      '; + // 2.3.3.8: added no playlist display filter + $no_playlist = apply_filters( 'radio_station_current_playlist_no_playlist_display', $no_playlist, $atts ); + if ( ( '' != $no_playlist ) && is_string( $no_playlist ) ) { + $output .= $no_playlist; + } + + // TODO: display countdown even if no current playlist + // if ( $atts['countdown'] ) { + // $output .= $html['countdown']; + // } } // --- close shortcode only wrapper --- diff --git a/includes/support-functions.php b/includes/support-functions.php index 260627b..5d5604c 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -923,6 +923,35 @@ function radio_station_get_show_data_meta( $show, $single = false ) { return $show_data; } +// -------------------- +// Get Show Description +// -------------------- +// 2.3.3.8: added for show data API feed +function radio_station_get_show_description( $show_data ) { + + // --- get description and excerpt --- + $show_id = $show_data['id']; + $show_post = get_post( $show_id ); + $description = $show_post->post_content; + if ( !empty( $show_post->post_excerpt ) ) { + $excerpt = $show_post->post_excerpt; + } else { + $length = apply_filters( 'radio_station_show_data_excerpt_length', false ); + $more = apply_filters( 'radio_station_show_data_excerpt_more', '' ); + $excerpt = radio_station_trim_excerpt( $description, $length, $more, false ); + } + + // --- filter description and excerpt --- + $description = apply_filters( 'radio_station_show_data_description', $description, $show_id ); + $excerpt = apply_filters( 'radio_station_show_data_excerpt', $excerpt, $show_id ); + + // --- add to existing show data --- + $show_data['description'] = $description; + $show_data['excerpt'] = $excerpt; + + return $show_data; +} + // ---------------------- // Get Override Data Meta // ---------------------- @@ -2018,17 +2047,22 @@ function radio_station_get_show_playlists( $show_id = false, $args = array() ) { // --------- // 2.3.0: added genre data grabber function radio_station_get_genre( $genre ) { - $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); - if ( !$term ) { - $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); - } - if ( !$term ) { + // 2.3.3.8: explicitly check for numberic genre term ID + $id = absint( $genre ); + if ( $id < 1 ) { + // $genre = sanitize_title( $genre ); + $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); + if ( !$term ) { + $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); + } + } else { $term = get_term_by( 'id', $genre, RADIO_STATION_GENRES_SLUG ); } if ( !$term ) { return false; } - $genre[$term->name] = array( + $genre_data = array(); + $genre_data[$term->name] = array( 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, @@ -2036,7 +2070,7 @@ function radio_station_get_genre( $genre ) { 'url' => get_term_link( $term, RADIO_STATION_GENRES_SLUG ), ); - return $genre; + return $genre_data; } // ---------- @@ -3019,23 +3053,20 @@ function radio_station_get_stream_formats() { // TODO: recheck amplitude formats ? // [Amplitude] HTML5 Support - mp3, aac ...? // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers - // [JPlayer] Audio: mp3, m4a - Video: m4v - // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm // +mpeg, oga, caf, weba, webm, dolby, flac + // [JPlayer] Audio: mp3, m4a - Video: m4v + // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv $formats = array( - 'aac' => 'AAC (A/H)', - 'mp3' => 'MP3 (A/J/H)', - 'm4a' => 'M4A (J/H)', - 'ogg' => 'OGG (H)', - 'oga' => 'OGA (J/H)', - 'webm' => 'WebM (J/H)', - 'rtmpa' => 'RTMPA (J)', - 'opus' => 'OPUS (H)', - 'wav' => 'WAV (J/H)', - 'flac' => 'FLAC (J/H)', + 'aac' => 'AAC/M4A', // A/H/J + 'mp3' => 'MP3', // A/H/J + 'ogg' => 'OGG', // H + 'oga' => 'OGA', // H/J + 'webm' => 'WebM', // H/J + 'rtmpa' => 'RTMPA', // J + 'opus' => 'OPUS', // H ); // --- filter and return --- @@ -3057,6 +3088,22 @@ function radio_station_get_station_url() { return $station_url; } +// --------------------- +// Get Station Image URL +// --------------------- +// 2.3.3.8: added get station logo image URL +function radio_station_get_station_image_url() { + $station_image = ''; + $attachment_id = radio_station_get_setting( 'station_image' ); + $image = wp_get_attachment_image_src( $attachment_id, 'full' ); + if ( is_array( $image ) ) { + $station_image = $image[0]; + } + $station_image = apply_filters( 'radio_station_station_image_url', $station_image ); + + return $station_image; +} + // ---------------------------- // Get Master Schedule Page URL // ---------------------------- @@ -4140,13 +4187,18 @@ function radio_station_get_language( $lang = false ) { } // --- get the specified language term --- - $term = get_term_by( 'slug', $lang, RADIO_STATION_LANGUAGES_SLUG ); - if ( !$term ) { - $term = get_term_by( 'name', $lang, RADIO_STATION_LANGUAGES_SLUG ); - } - if ( !$term ) { + // 2.3.3.8: explicitly check for numberic language term ID + $id = absint( $lang ); + if ( $id < 1 ) { + $term = get_term_by( 'slug', $lang, RADIO_STATION_LANGUAGES_SLUG ); + if ( !$term ) { + $term = get_term_by( 'name', $lang, RADIO_STATION_LANGUAGES_SLUG ); + } + } else { $term = get_term_by( 'id', $lang, RADIO_STATION_LANGUAGES_SLUG ); } + + // --- set language from term --- if ( $term ) { $language = array( 'id' => $term->term_id, @@ -4167,7 +4219,7 @@ function radio_station_get_language( $lang = false ) { 'name' => $lang_data['native_name'], 'description' => $lang_data['english_name'], // TODO: set URL for main language and filter archive page results ? - // 'url' => '', + // 'url' => '', ); } } @@ -4209,7 +4261,6 @@ function radio_station_trim_excerpt( $content, $length = false, $more = false, $ if ( !$length ) { $length = 35; - // $length = apply_filters( 'radio_station_excerpt_length', $length ); $length = (int) apply_filters( 'radio_station_excerpt_length', $length ); } if ( !$more ) { diff --git a/js/wp-color-picker-alpha.js b/js/wp-color-picker-alpha.js new file mode 100644 index 0000000..a3573d0 --- /dev/null +++ b/js/wp-color-picker-alpha.js @@ -0,0 +1,635 @@ +/**! + * wp-color-picker-alpha + * + * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker + * Only run in input and is defined data alpha in true + * + * Version: 3.0.0 + * https://github.com/kallookoo/wp-color-picker-alpha + * Licensed under the GPLv2 license or later. + */ + +( function( $, undef ) { + + var wpColorPickerAlpha = { + 'version' : 300 + }; + + // Always try to use the last version of this script. + if ( 'wpColorPickerAlpha' in window && 'version' in window.wpColorPickerAlpha ) { + var version = parseInt( window.wpColorPickerAlpha.version, 10 ); + if ( ! isNaN( version ) && version >= wpColorPickerAlpha.version ) { + return; + } + } + + // Prevent multiple initiations + if ( Color.fn.hasOwnProperty( 'to_s' ) ) { + return; + } + + // Create new method to replace the `Color.toString()` inside the scripts. + Color.fn.to_s = function( type ) { + type = ( type || 'hex' ); + // Change hex to rgba to return the correct color. + if ( 'hex' === type && this._alpha < 1 ) { + type = 'rgba'; + } + + var color = ''; + if ( 'hex' === type ) { + color = this.toString(); + } else if ( ! this.error ) { + color = this.toCSS( type ).replace( /\(\s+/, '(' ).replace( /\s+\)/, ')' ); + } + return color; + } + + // Register the global variable. + window.wpColorPickerAlpha = wpColorPickerAlpha; + + // Background image encoded + var backgroundImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg=='; + + /** + * Iris + */ + $.widget( 'a8c.iris', $.a8c.iris, { + /** + * Alpha options + * + * @since 3.0.0 + * + * @type {Object} + */ + alphaOptions: { + alphaEnabled: false, + }, + /** + * Get the current color or the new color. + * + * @since 3.0.0 + * @access private + * + * @param {Object|*} The color instance if not defined return the cuurent color. + * + * @return {string} The element's color. + */ + _getColor: function( color ) { + if ( color === undef ) { + color = this._color; + } + + if ( this.alphaOptions.alphaEnabled ) { + color = color.to_s( this.alphaOptions.alphaColorType ); + if ( ! this.alphaOptions.alphaColorWithSpace ) { + color = color.replace( /\s+/g, '' ); + } + return color; + } + return color.toString(); + }, + /** + * Create widget + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _create: function() { + try { + // Try to get the wpColorPicker alpha options. + this.alphaOptions = this.element.wpColorPicker( 'instance' ).alphaOptions; + } catch( e ) {} + + // We make sure there are all options + $.extend( {}, this.alphaOptions, { + alphaEnabled: false, + alphaCustomWidth: 130, + alphaReset: false, + alphaColorType: 'hex', + alphaColorWithSpace: false, + } ); + + this._super(); + }, + /** + * Binds event listeners to the Iris. + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _addInputListeners: function( input ) { + var self = this, + debounceTimeout = 100, + callback = function( event ){ + var val = input.val(), + color = new Color( val ), + val = val.replace( /^(#|(rgb|hsl)a?)/, '' ), + type = self.alphaOptions.alphaColorType; + + input.removeClass( 'iris-error' ); + + if ( ! color.error ) { + // let's not do this on keyup for hex shortcodes + if ( 'hex' !== type || ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { + // Compare color ( #AARRGGBB ) + if ( color.toIEOctoHex() !== self._color.toIEOctoHex() ) { + self._setOption( 'color', self._getColor( color ) ); + } + } + } else if ( val !== '' ) { + input.addClass( 'iris-error' ); + } + }; + + input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); + + // If we initialized hidden, show on first focus. The rest is up to you. + if ( self.options.hide ) { + input.one( 'focus', function() { + self.show(); + }); + } + }, + /** + * Init Controls + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _initControls: function() { + this._super(); + + if ( this.alphaOptions.alphaEnabled ) { + // Create Alpha controls + var self = this, + stripAlpha = self.controls.strip.clone(false, false), + stripAlphaSlider = stripAlpha.find( '.iris-slider-offset' ), + controls = { + stripAlpha : stripAlpha, + stripAlphaSlider : stripAlphaSlider + }; + + stripAlpha.addClass( 'iris-strip-alpha' ); + stripAlphaSlider.addClass( 'iris-slider-offset-alpha' ); + stripAlpha.appendTo( self.picker.find( '.iris-picker-inner' ) ); + + // Push new controls + $.each( controls, function( k, v ) { + self.controls[k] = v; + } ); + + // Create slider + self.controls.stripAlphaSlider.slider( { + orientation : 'vertical', + min : 0, + max : 100, + step : 1, + value : parseInt( self._color._alpha * 100 ), + slide : function( event, ui ) { + self.active = 'strip'; + // Update alpha value + self._color._alpha = parseFloat( ui.value / 100 ); + self._change.apply( self, arguments ); + } + } ); + } + }, + /** + * Create the controls sizes + * + * @since 3.0.0 + * @access private + * + * @param {bool} reset Set to True for recreate the controls sizes. + * + * @return {void} + */ + _dimensions: function( reset ) { + this._super( reset ); + + if ( this.alphaOptions.alphaEnabled ) { + var self = this, + opts = self.options, + controls = self.controls, + square = controls.square, + strip = self.picker.find( '.iris-strip' ), + innerWidth, squareWidth, stripWidth, stripMargin, totalWidth; + + /** + * I use Math.round() to avoid possible size errors, + * this function returns the value of a number rounded + * to the nearest integer. + * + * The width to append all widgets, + * if border is enabled, 22 is subtracted. + * 20 for css left and right property + * 2 for css border + */ + innerWidth = Math.round( self.picker.outerWidth( true ) - ( opts.border ? 22 : 0 ) ); + // The width of the draggable, aka square. + squareWidth = Math.round( square.outerWidth() ); + // The width for the sliders + stripWidth = Math.round( ( innerWidth - squareWidth ) / 2 ); + // The margin for the sliders + stripMargin = Math.round( stripWidth / 2 ); + // The total width of the elements. + totalWidth = Math.round( squareWidth + ( stripWidth * 2 ) + ( stripMargin * 2 ) ); + + // Check and change if necessary. + while ( totalWidth > innerWidth ) { + stripWidth = Math.round( stripWidth - 2 ); + stripMargin = Math.round( stripMargin - 1 ); + totalWidth = Math.round( squareWidth + ( stripWidth * 2 ) + ( stripMargin * 2 ) ); + } + + + square.css( 'margin', '0' ); + strip.width( stripWidth ).css( 'margin-left', stripMargin + 'px' ); + } + }, + /** + * Callback to update the controls and the current color. + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _change: function() { + var self = this, + active = self.active; + + self._super(); + + if ( self.alphaOptions.alphaEnabled ) { + var controls = self.controls, + alpha = parseInt( self._color._alpha * 100 ), + color = self._color.toRgb(), + gradient = [ + 'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%', + 'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%' + ], + target = self.picker.closest( '.wp-picker-container' ).find( '.wp-color-result' ); + + self.options.color = self._getColor(); + // Generate background slider alpha, only for CSS3. + controls.stripAlpha.css( { 'background' : 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + backgroundImage + ')' } ); + // Update alpha value + if ( active ) { + controls.stripAlphaSlider.slider( 'value', alpha ); + } + + if ( ! self._color.error ) { + self.element.removeClass( 'iris-error' ).val( self.options.color ); + } + + self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() { + var color = $( this ).data( 'color' ); + if ( self.alphaOptions.alphaReset ) { + self._color._alpha = 1; + color = self._getColor(); + } + self._setOption( 'color', color ); + } ); + } + }, + /** + * Paint dimensions. + * + * @since 3.0.0 + * @access private + * + * @param {string} origin Origin (position). + * @param {string} control Type of the control, + * + * @return {void} + */ + _paintDimension: function( origin, control ) { + var self = this, + color = false; + + // Fix for slider hue opacity. + if ( self.alphaOptions.alphaEnabled && 'strip' === control ) { + color = self._color; + self._color = new Color( color.toString() ); + self.hue = self._color.h(); + } + + self._super( origin, control ); + + // Restore the color after paint. + if ( color ) { + self._color = color; + } + }, + /** + * To update the options, see original source to view the available options. + * + * @since 3.0.0 + * + * @param {string} key The Option name. + * @param {mixed} value The Option value to update. + * + * @return {void} + */ + _setOption: function( key, value ) { + var self = this; + if ( 'color' === key && self.alphaOptions.alphaEnabled ) { + // cast to string in case we have a number + value = '' + value; + newColor = new Color( value ).setHSpace( self.options.mode ); + // Check if error && Check the color to prevent callbacks with the same color. + if ( ! newColor.error && self._getColor( newColor ) !== self._getColor() ) { + self._color = newColor; + self.options.color = self._getColor(); + self.active = 'external'; + self._change(); + } + } else { + return self._super( key, value ); + } + }, + /** + * Returns the iris object if no new color is provided. If a new color is provided, it sets the new color. + * + * @param newColor {string|*} The new color to use. Can be undefined. + * + * @since 3.0.0 + * + * @return {string} The element's color. + */ + color: function( newColor ) { + if ( newColor === true ) { + return this._color.clone(); + } + if ( newColor === undef ) { + return this._getColor(); + } + this.option( 'color', newColor ); + }, + } ); + + /** + * wpColorPicker + */ + $.widget( 'wp.wpColorPicker', $.wp.wpColorPicker, { + /** + * Alpha options + * + * @since 3.0.0 + * + * @type {Object} + */ + alphaOptions: { + alphaEnabled: false, + }, + /** + * Get the alpha options. + * + * @since 3.0.0 + * @access private + * + * @return {object} The current alpha options. + */ + _getAlphaOptions: function() { + var el = this.element, + type = ( el.data( 'type' ) || this.options.type ), + color = ( el.data( 'defaultColor' ) || el.val() ), + options = { + alphaEnabled: ( el.data( 'alphaEnabled' ) || false ), + alphaCustomWidth: 130, + alphaReset: false, + alphaColorType: 'rgb', + alphaColorWithSpace: false, + }; + + if ( options.alphaEnabled ) { + options.alphaEnabled = ( el.is( 'input' ) && 'full' === type ); + } + + if ( ! options.alphaEnabled ) { + return options; + } + + options.alphaColorWithSpace = ( color && color.match( /\s/ ) ); + + $.each( options, function( name, defaultValue ) { + var value = ( el.data( name ) || defaultValue ); + switch ( name ) { + case 'alphaCustomWidth': + value = ( value ? parseInt( value, 10 ) : 0 ); + value = ( isNaN( value ) ? defaultValue : value ); + break; + case 'alphaColorType': + if ( ! value.match( /^(hex|(rgb|hsl)a?)$/ ) ) { + if ( color && color.match( /^#/ ) ) { + value = 'hex'; + } else if ( color && color.match( /^hsla?/ ) ) { + value = 'hsl'; + } else { + value = defaultValue; + } + } + break; + default: + value = !!value; + break; + } + options[name] = value; + } ); + + return options; + }, + /** + * Create widget + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _create: function() { + // Return early if Iris support is missing. + if ( ! $.support.iris ) { + return; + } + + // Set the alpha options for the current instance. + this.alphaOptions = this._getAlphaOptions(); + + // Create widget. + this._super(); + }, + /** + * Binds event listeners to the color picker and create options, etc... + * + * @since 3.0.0 + * @access private + * + * @return {void} + */ + _addListeners: function() { + if ( ! this.alphaOptions.alphaEnabled ) { + return this._super(); + } + + var self = this, + el = self.element, + isDeprecated = self.toggler.is( 'a' ); + + this.alphaOptions.defaultWidth = el.width(); + if ( this.alphaOptions.alphaCustomWidth ) { + el.width( parseInt( this.alphaOptions.defaultWidth + this.alphaOptions.alphaCustomWidth, 10 ) ); + } + + self.toggler.css( { + 'position': 'relative', + 'background-image' : 'url(' + backgroundImage + ')' + } ); + + if ( isDeprecated ) { + self.toggler.html( '' ); + } else { + self.toggler.append( '' ); + } + + self.colorAlpha = self.toggler.find( 'span.color-alpha' ).css( { + 'width' : '30px', + 'height' : '100%', + 'position' : 'absolute', + 'top' : 0, + 'background-color' : el.val(), + } ); + + // Define the correct position for ltr or rtl direction. + if ( 'ltr' === self.colorAlpha.css( 'direction' ) ) { + self.colorAlpha.css( { + 'border-bottom-left-radius' : '2px', + 'border-top-left-radius' : '2px', + 'left' : 0 + } ); + } else { + self.colorAlpha.css( { + 'border-bottom-right-radius' : '2px', + 'border-top-right-radius' : '2px', + 'right' : 0 + } ); + } + + + el.iris( { + /** + * @summary Handles the onChange event if one has been defined in the options. + * + * Handles the onChange event if one has been defined in the options and additionally + * sets the background color for the toggler element. + * + * @since 3.0.0 + * + * @param {Event} event The event that's being called. + * @param {HTMLElement} ui The HTMLElement containing the color picker. + * + * @returns {void} + */ + change: function( event, ui ) { + self.colorAlpha.css( { 'background-color': ui.color.to_s( self.alphaOptions.alphaColorType ) } ); + + // fire change callback if we have one + if ( $.isFunction( self.options.change ) ) { + self.options.change.call( this, event, ui ); + } + } + } ); + + + /** + * Prevent any clicks inside this widget from leaking to the top and closing it. + * + * @since 3.0.0 + * + * @param {Event} event The event that's being called. + * + * @return {void} + */ + self.wrap.on( 'click.wpcolorpicker', function( event ) { + event.stopPropagation(); + }); + + /** + * Open or close the color picker depending on the class. + * + * @since 3.0.0 + */ + self.toggler.click( function() { + if ( self.toggler.hasClass( 'wp-picker-open' ) ) { + self.close(); + } else { + self.open(); + } + }); + + /** + * Checks if value is empty when changing the color in the color picker. + * If so, the background color is cleared. + * + * @since 3.0.0 + * + * @param {Event} event The event that's being called. + * + * @return {void} + */ + el.change( function( event ) { + var val = $( this ).val(); + + if ( el.hasClass( 'iris-error' ) || val === '' || val.match( /^(#|(rgb|hsl)a?)$/ ) ) { + if ( isDeprecated ) { + self.toggler.removeAttr( 'style' ); + } + + self.colorAlpha.css( 'background-color', '' ); + + // fire clear callback if we have one + if ( $.isFunction( self.options.clear ) ) { + self.options.clear.call( this, event ); + } + } + } ); + + /** + * Enables the user to either clear the color in the color picker or revert back to the default color. + * + * @since 3.0.0 + * + * @param {Event} event The event that's being called. + * + * @return {void} + */ + self.button.click( function( event ) { + if ( $( this ).hasClass( 'wp-picker-default' ) ) { + el.val( self.options.defaultColor ).change(); + } else if ( $( this ).hasClass( 'wp-picker-clear' ) ) { + el.val( '' ); + if ( isDeprecated ) { + self.toggler.removeAttr( 'style' ); + } + + self.colorAlpha.css( 'background-color', '' ); + + // fire clear callback if we have one + if ( $.isFunction( self.options.clear ) ) { + self.options.clear.call( this, event ); + } + + el.trigger( 'change' ); + } + } ); + }, + } ); +} ( jQuery ) ); \ No newline at end of file diff --git a/js/wp-color-picker-alpha.min.js b/js/wp-color-picker-alpha.min.js new file mode 100644 index 0000000..1aeb1f2 --- /dev/null +++ b/js/wp-color-picker-alpha.min.js @@ -0,0 +1,11 @@ +/**! + * wp-color-picker-alpha + * + * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker + * Only run in input and is defined data alpha in true + * + * Version: 3.0.0 + * https://github.com/kallookoo/wp-color-picker-alpha + * Licensed under the GPLv2 license or later. + */ +!function(e,a){var l,o={version:300};if("wpColorPickerAlpha"in window&&"version"in window.wpColorPickerAlpha){var t=parseInt(window.wpColorPickerAlpha.version,10);if(!isNaN(t)&&o.version<=t)return}Color.fn.hasOwnProperty("to_s")||(Color.fn.to_s=function(o){"hex"===(o=o||"hex")&&this._alpha<1&&(o="rgba");var a="";return"hex"===o?a=this.toString():this.error||(a=this.toCSS(o).replace(/\(\s+/,"(").replace(/\s+\)/,")")),a},window.wpColorPickerAlpha=o,l="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==",e.widget("a8c.iris",e.a8c.iris,{alphaOptions:{alphaEnabled:!1},_getColor:function(o){return o===a&&(o=this._color),this.alphaOptions.alphaEnabled?(o=o.to_s(this.alphaOptions.alphaColorType),this.alphaOptions.alphaColorWithSpace||(o=o.replace(/\s+/g,"")),o):o.toString()},_create:function(){try{this.alphaOptions=this.element.wpColorPicker("instance").alphaOptions}catch(o){}e.extend({},this.alphaOptions,{alphaEnabled:!1,alphaCustomWidth:130,alphaReset:!1,alphaColorType:"hex",alphaColorWithSpace:!1}),this._super()},_addInputListeners:function(i){function o(o){var a=i.val(),t=new Color(a),a=a.replace(/^(#|(rgb|hsl)a?)/,""),r=l.alphaOptions.alphaColorType;i.removeClass("iris-error"),t.error?""!==a&&i.addClass("iris-error"):"hex"===r&&"keyup"===o.type&&a.match(/^[0-9a-fA-F]{3}$/)||t.toIEOctoHex()!==l._color.toIEOctoHex()&&l._setOption("color",l._getColor(t))}var l=this;i.on("change",o).on("keyup",l._debounce(o,100)),l.options.hide&&i.one("focus",function(){l.show()})},_initControls:function(){var t,o,a,r;this._super(),this.alphaOptions.alphaEnabled&&(a=(o=(t=this).controls.strip.clone(!1,!1)).find(".iris-slider-offset"),r={stripAlpha:o,stripAlphaSlider:a},o.addClass("iris-strip-alpha"),a.addClass("iris-slider-offset-alpha"),o.appendTo(t.picker.find(".iris-picker-inner")),e.each(r,function(o,a){t.controls[o]=a}),t.controls.stripAlphaSlider.slider({orientation:"vertical",min:0,max:100,step:1,value:parseInt(100*t._color._alpha),slide:function(o,a){t.active="strip",t._color._alpha=parseFloat(a.value/100),t._change.apply(t,arguments)}}))},_dimensions:function(o){if(this._super(o),this.alphaOptions.alphaEnabled){for(var a=this,t=a.options,r=a.controls.square,o=a.picker.find(".iris-strip"),i=Math.round(a.picker.outerWidth(!0)-(t.border?22:0)),l=Math.round(r.outerWidth()),e=Math.round((i-l)/2),s=Math.round(e/2),n=Math.round(l+2*e+2*s);i'):t.toggler.append(''),t.colorAlpha=t.toggler.find("span.color-alpha").css({width:"30px",height:"100%",position:"absolute",top:0,"background-color":r.val()}),"ltr"===t.colorAlpha.css("direction")?t.colorAlpha.css({"border-bottom-left-radius":"2px","border-top-left-radius":"2px",left:0}):t.colorAlpha.css({"border-bottom-right-radius":"2px","border-top-right-radius":"2px",right:0}),r.iris({change:function(o,a){t.colorAlpha.css({"background-color":a.color.to_s(t.alphaOptions.alphaColorType)}),e.isFunction(t.options.change)&&t.options.change.call(this,o,a)}}),t.wrap.on("click.wpcolorpicker",function(o){o.stopPropagation()}),t.toggler.click(function(){t.toggler.hasClass("wp-picker-open")?t.close():t.open()}),r.change(function(o){var a=e(this).val();(r.hasClass("iris-error")||""===a||a.match(/^(#|(rgb|hsl)a?)$/))&&(i&&t.toggler.removeAttr("style"),t.colorAlpha.css("background-color",""),e.isFunction(t.options.clear)&&t.options.clear.call(this,o))}),t.button.click(function(o){e(this).hasClass("wp-picker-default")?r.val(t.options.defaultColor).change():e(this).hasClass("wp-picker-clear")&&(r.val(""),i&&t.toggler.removeAttr("style"),t.colorAlpha.css("background-color",""),e.isFunction(t.options.clear)&&t.options.clear.call(this,o),r.trigger("change"))})}}))}(jQuery); \ No newline at end of file diff --git a/loader.php b/loader.php index 2f0f9b9..a280b56 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.6 +// Version: 1.1.7 // -------------- // Note: Changelog and structure at end of file. // @@ -692,6 +692,24 @@ public function update_settings() { // 1.0.9: added multiselect value saving $newsettings = array_values( $posted ); + } elseif ( 'image' == $type ) { + + // --- check attachment ID value --- + // 1.1.7: add image attachment ID saving + if ( '' != $posted ) { + $attachment = wp_get_attachment_image_src( $posted, 'full' ); + if ( is_array( $attachment ) ) { + $settings[$key] = $posted; + } + } + + } elseif ( ( 'color' == $type ) || ( 'coloralpha' == $type ) ) { + + // --- color or color alpha setting --- + // 1.1.7: added color picker value saving + // TODO: validate this setting ? + $settings[$key] = $posted; + } if ( $this->debug ) { @@ -1844,6 +1862,30 @@ public function settings_table() { $options = $this->options; $options = apply_filters( $namespace . '_options', $options ); + // --- maybe enqueue media scripts --- + // 1.1.7: added media gallery script enqueueing for image field + // 1.1.7: added color picker and color picker alpha script enqueueing + $enqueued_media = $enqueued_color_picker = $enqueued_color_picker_alpha = false; + foreach ( $options as $option ) { + if ( ( 'image' == $option['type'] ) && !$enqueued_media ) { + wp_enqueue_media(); + $enqueued_media = true; + } elseif ( ( 'color' == $option['type'] ) && !$enqueued_color_picker ) { + wp_enqueue_style( 'wp-color-picker' ); + wp_enqueue_script( 'wp-color-picker' ); + $enqueued_color_picker = true; + } elseif ( ( 'coloralpha' == $option['type'] ) && !$enqueued_color_picker_alpha ) { + wp_enqueue_style( 'wp-color-picker' ); + $suffix = '.min'; + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + $suffix = ''; + } + $url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', $args['file'] ); + wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true ); + $enqueued_color_picker = $enqueued_color_picker_alpha = true; + } + } + $defaults = $this->default_settings(); $settings = $this->get_settings( false ); @@ -1874,6 +1916,14 @@ public function settings_table() { } } + // --- remove unused tabs automatically --- + // 1.1.7: added for cleaner interface while developing + foreach ( $tabs as $tab => $tablabel ) { + if ( !isset( $taboptions[$tab] ) ) { + unset( $tabs[$tab] ); + } + } + // --- maybe push general section to top of tab --- foreach ( $taboptions as $tab => $tabsections ) { if ( isset( $tabsections['general'] ) && ( count( $tabsections ) > 1 ) ) { @@ -1920,10 +1970,10 @@ public function settings_table() { // 1.0.9: add to settings scripts $confirmreset = __( 'Are you sure you want to reset to default settings?' ); $script = "function settings_reset_defaults() { - agree = confirm('" . esc_js( $confirmreset ) . "'); if (!agree) {return false;} - document.getElementById('settings-action').value = 'reset'; - document.getElementById('settings-form').submit(); - }"; + agree = confirm('" . esc_js( $confirmreset ) . "'); if (!agree) {return false;} + document.getElementById('settings-action').value = 'reset'; + document.getElementById('settings-form').submit(); + }"; $this->scripts[] = $script; // --- start settings form --- @@ -2050,16 +2100,76 @@ public function settings_table() { // --- number input step script --- // 1.0.9: added to script array $script = "function settings_number_step(updown, id, min, max, step) { - if (updown == 'up') {multiplier = 1;} - if (updown == 'down') {multiplier = -1;} - current = parseInt(document.getElementById(id).value); - newvalue = current + (multiplier * parseInt(step)); - if (newvalue < parseInt(min)) {newvalue = min;} - if (newvalue > parseInt(max)) {newvalue = max;} - document.getElementById(id).value = newvalue; - }"; + if (updown == 'up') {multiplier = 1;} else if (updown == 'down') {multiplier = -1;} + current = parseInt(document.getElementById(id).value); + newvalue = current + (multiplier * parseInt(step)); + if (newvalue < parseInt(min)) {newvalue = min;} + if (newvalue > parseInt(max)) {newvalue = max;} + document.getElementById(id).value = newvalue; + }"; $this->scripts[] = $script; + // --- image selection script --- + // 1.1.7: added for image field type + if ( $enqueued_media ) { + $confirm_remove = __( 'Are you sure you want to remove this image?' ); + $script = "jQuery(function(){ + + var mediaframe, parentdiv; + + /* Add Image on Click */ + jQuery('.upload-custom-image').on( 'click', function( event ) { + + event.preventDefault(); + parentdiv = jQuery(this).parent().parent(); + + if (mediaframe) {mediaframe.open(); return;} + mediaframe = wp.media({ + title: 'Select or Upload Image', + button: {text: 'Use this Image'}, + multiple: false + }); + + mediaframe.on( 'select', function() { + var attachment = mediaframe.state().get('selection').first().toJSON(); + image = '\"\"'; + parentdiv.find('.custom-image-container').append(image); + parentdiv.find('.custom-image-id').val(attachment.id); + parentdiv.find('.upload-custom-image').addClass('hidden'); + parentdiv.find('.delete-custom-image').removeClass('hidden'); + }); + + mediaframe.open(); + jQuery('.media-modal-close').on( 'click', function() { + console.log('close click detected'); + mediaframe.close(); + }); + }); + + /* Delete Image on Click */ + jQuery('.delete-custom-image').on( 'click', function( event ) { + event.preventDefault(); + agree = confirm('" . esc_js( $confirm_remove ) . "'); + if (!agree) {return;} + parentdiv = jQuery(this).parent().parent(); + parentdiv.find('.custom-image-container').html(''); + parentdiv.find('.custom-image-id').val(''); + parentdiv.find('.upload-custom-image').removeClass('hidden'); + parentdiv.find('.delete-custom-image').addClass('hidden'); + }); + + });"; + $this->scripts[] = $script; + } + + // --- color picker script --- + if ( $enqueued_color_picker ) { + $script = "jQuery(document).ready(function(){ + if (jQuery('.color-picker').length) {jQuery('.color-picker').wpColorPicker();} + });"; + $this->scripts[] = $script; + } + // --- enqueue settings scripts --- add_action( 'admin_footer', array( $this, 'setting_scripts' ) ); @@ -2126,22 +2236,22 @@ public function setting_row( $option ) { } // --- prepare row output --- - $row = ""; + $row = ''; - $row .= "" . $option['label']; + $row .= '' . $option['label']; if ( 'multiselect' == $type ) { - $row .= "
      " . esc_html( __( 'Use Ctrl and Click to Select' ) ) . ""; + $row .= '
      ' . esc_html( __( 'Use Ctrl and Click to Select' ) ) . ''; } - $row .= ""; + $row .= ''; // 1.0.9: added multiple cell spanning note type if ( ( 'note' == $type ) || ( 'info' == $type ) || ( 'helper' == $type ) ) { - $row .= ""; + $row .= ''; if ( isset( $option['helper'] ) ) { $row .= $option['helper']; } - $row .= ""; + $row .= ''; } else { @@ -2149,26 +2259,26 @@ public function setting_row( $option ) { if ( isset( $option['pro'] ) && $option['pro'] ) { // --- Pro version setting (teaser) --- - $row .= ""; + $row .= ''; $upgrade_link = false; if ( $args['hasplans'] || $args['hasaddons'] ) { $upgrade_link = add_query_arg( 'page=', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); $target = ''; } elseif ( isset( $args['upgrade_link'] ) ) { $upgrade_link = $args['upgrade_link']; - $target = " target='_blank'"; + $target = ' target="_blank"'; } if ( $upgrade_link ) { $row .= __( 'Available in Pro Version.' ) . '
      '; - $row .= "" . esc_html( __( 'Click Here to Upgrade!' ) ) . ""; + $row .= '' . esc_html( __( 'Click Here to Upgrade!' ) ) . ''; } else { $row .= esc_html( __( 'Coming soon in Pro version!' ) ); } - $row .= ""; + $row .= ''; } else { - $row .= ""; + $row .= ''; // --- maybe prepare special options --- if ( isset( $option['options'] ) && is_string( $option['options'] ) ) { @@ -2292,14 +2402,14 @@ public function setting_row( $option ) { // 1.0.9: add toggle input (styled checkbox) $checked = ''; if ( $setting == $option['value'] ) { - $checked = " checked='checked'"; + $checked = ' checked="checked"'; } - $row .= ""; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } } elseif ( 'checkbox' == $type ) { @@ -2307,11 +2417,11 @@ public function setting_row( $option ) { // --- checkbox --- $checked = ''; if ( $setting == $option['value'] ) { - $checked = " checked='checked'"; + $checked = ' checked="checked"'; } - $row .= ""; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } } elseif ( 'multicheck' == $type ) { @@ -2321,11 +2431,11 @@ public function setting_row( $option ) { foreach ( $option['options'] as $key => $label ) { $checked = ''; if ( is_array( $setting ) && in_array( $key, $setting ) ) { - $checked = " checked='checked'"; + $checked = ' checked="checked"'; } - $checkboxes[] = " " . esc_html( $label ); + $checkboxes[] = ' ' . esc_html( $label ); } - $row .= implode( "
      ", $checkboxes ); + $row .= implode( '
      ', $checkboxes ); } elseif ( 'radio' == $type ) { @@ -2334,57 +2444,57 @@ public function setting_row( $option ) { foreach ( $option['options'] as $value => $label ) { $checked = ''; if ( $setting == $value ) { - $checked = " checked='checked'"; + $checked = ' checked="checked"'; } - $radios[] = " " . esc_html( $label ); + $radios[] = ' ' . esc_html( $label ); } $row .= implode( '
      ', $radios ); } elseif ( 'select' == $type ) { // --- select dropdown --- - $row .= "'; foreach ( $option['options'] as $value => $label ) { // 1.0.9: support option grouping (set unique key containing OPTGROUP-) if ( strstr( $value, '*OPTGROUP*' ) ) { - $row .= "" . esc_html( $label ) . ''; + $row .= '' . esc_html( $label ) . ''; } else { // 1.1.3: remove strict value checking if ( $setting == $value ) { - $selected = " selected='selected'"; + $selected = ' selected="selected"'; } else { $selected = ''; } - $row .= ""; + $row .= ''; } } - $row .= ""; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } } elseif ( 'multiselect' == $type ) { // --- multiselect dropdown --- - $row .= "'; foreach ( $option['options'] as $value => $label ) { if ( '' != $value ) { // 1.1.3: check for OPTGROUP instead of *OPTGROUP* if ( strstr( $value, 'OPTGROUP' ) ) { - $row .= ""; + $row .= ''; } else { if ( is_array( $setting ) && in_array( $value, $setting ) ) { - $selected = " selected='selected'"; + $selected = ' selected="selected"'; } else { $selected = ''; } - $row .= ""; + $row .= ''; } } } - $row .= ""; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } } elseif ( 'text' == $type ) { @@ -2399,9 +2509,9 @@ public function setting_row( $option ) { } else { $placeholder = ''; } - $row .= ""; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } } elseif ( 'textarea' == $type ) { @@ -2417,7 +2527,7 @@ public function setting_row( $option ) { } else { $placeholder = ''; } - $row .= "'; } elseif ( ( 'numeric' == $type ) || ( 'number' == $type ) ) { @@ -2443,31 +2553,79 @@ public function setting_row( $option ) { } else { $step = 1; } - $onclickup = 'settings_number_step("up", "' . esc_attr( $name ) . '", ' . esc_attr( $min ) . ', ' . esc_attr( $max ) . ', ' . esc_attr( $step ) . ');'; - $onclickdown = 'settings_number_step("down", "' . esc_attr( $name ) . '", ' . esc_attr( $min ) . ', ' . esc_attr( $max ) . ', ' . esc_attr( $step ) . ');'; - $row .= ""; - $row .= ""; - $row .= ""; + $onclickup = "settings_number_step('up', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");"; + $onclickdown = "settings_number_step('down', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");"; + $row .= ''; + $row .= ''; + $row .= ''; if ( isset( $option['suffix'] ) ) { - $row .= " " . $option['suffix']; + $row .= ' ' . $option['suffix']; } + } elseif ( 'image' == $type ) { + + // 1.1.7: added image attachment selection from media library + + // --- get current image --- + $image = wp_get_attachment_image_src( $setting, 'full' ); + $has_image = is_array( $image ); + + // --- image container --- + $row .= '
      '; + if ( $has_image ) { + $row .= ''; + } + $row .= '
      '; + + // --- add and remove links --- + $upload_link = get_upload_iframe_src( 'image' ); + $row .= '

      '; + $hidden = ''; + if ( $has_image ) { + $hidden = ' hidden'; + } + $row .= ''; + $row .= esc_html( __( 'Add Image' ) ); + $row .= ''; + + $hidden = ''; + if ( !$has_image ) { + $hidden = ' hidden'; + } + $row .= ''; + $row .= esc_html( __( 'Remove Image' ) ); + $row .= ''; + $row .= '

      '; + + // --- hidden input for image ID --- + $row .= ''; + + } elseif ( 'color' == $type ) { + + // 1.1.7: added color picker field + $row .= ''; + + } elseif ( 'coloralpha' == $type ) { + + // 1.1.7: added color picker alpha field + $row .= ''; + } - $row .= ""; + $row .= ''; } // --- setting helper text --- if ( isset( $option['helper'] ) ) { - $row .= ""; - $row .= "" . esc_html( $option['helper'] ) . ""; + $row .= ''; + $row .= '' . esc_html( $option['helper'] ) . ''; } } - $row .= ""; + $row .= ''; // --- settings row spacer --- - $row .= " "; + $row .= ' '; // --- filter and return setting row --- $row = apply_filters( $namespace . '_setting_row', $row, $option ); @@ -2530,23 +2688,23 @@ public function setting_styles() { // --- toggle input styles --- // Ref: https://www.w3schools.com/howto/howto_css_switch.asp $styles[] = ' - .setting-toggle {position:relative; display:inline-block; width:30px; height:17px;} - .setting-toggle input {opacity:0; width:0; height:0;} - .setting-slider {position:absolute; cursor:pointer; - top:0; left:0; right:0; bottom:0; background-color:#ccc; - -webkit-transition:.4s; transition:.4s; - } - .setting-slider:before {position:absolute; content:""; height:13px; width:13px; - left:2px; bottom:2px; background-color:white; -webkit-transition:.4s; transition:.4s; - } - input:checked + .setting-slider {background-color: #2196F3;} - input:focus + .setting-slider {box-shadow: 0 0 1px #2196F3;} - input:checked + .setting-slider:before { - -webkit-transform:translateX(13px); -ms-transform:translateX(13px); transform:translateX(13px); - } - .setting-slider.round {border-radius: 17px;} - .setting-slider.round:before {border-radius: 50%;} - '; + .setting-toggle {position:relative; display:inline-block; width:30px; height:17px;} + .setting-toggle input {opacity:0; width:0; height:0;} + .setting-slider {position:absolute; cursor:pointer; + top:0; left:0; right:0; bottom:0; background-color:#ccc; + -webkit-transition:.4s; transition:.4s; + } + .setting-slider:before {position:absolute; content:""; height:13px; width:13px; + left:2px; bottom:2px; background-color:white; -webkit-transition:.4s; transition:.4s; + } + input:checked + .setting-slider {background-color: #2196F3;} + input:focus + .setting-slider {box-shadow: 0 0 1px #2196F3;} + input:checked + .setting-slider:before { + -webkit-transform:translateX(13px); -ms-transform:translateX(13px); transform:translateX(13px); + } + .setting-slider.round {border-radius: 17px;} + .setting-slider.round:before {border-radius: 50%;} + '; // --- filter and output styles --- $namespace = $this->namespace; @@ -2861,6 +3019,11 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.1.7 == +// - added media library upload image field type +// - added color picker and color picker alpha field types +// - automatically remove unused settings tabs + // == 1.1.6 == // - added phone number character validation diff --git a/radio-station.php b/radio-station.php index a352d18..7d4d8ec 100644 --- a/radio-station.php +++ b/radio-station.php @@ -179,37 +179,37 @@ ), // --- Stream Format --- - // 'streaming_format' => array( - // 'type' => 'select', - // 'options' => $formats, - // 'label' => __( 'Streaming Format', 'radio-station' ), - // 'default' => 'aac', - // 'helper' => __( 'Select streaming fallback for streaming URL.', 'radio-station' ), - // 'tab' => 'general', - // 'section' => 'broadcast', - // ), + 'streaming_format' => array( + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Streaming Format', 'radio-station' ), + 'default' => 'aac', + 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), // --- Fallback Stream URL --- - // 'fallback_url' => array( - // 'type' => 'text', - // 'options' => 'URL', - // 'label' => __( 'Fallback Stream URL', 'radio-station' ), - // 'default' => '', - // 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), - // 'tab' => 'general', - // 'section' => 'broadcast', - // ), + 'fallback_url' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Fallback Stream URL', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), // --- Fallback Stream Format --- - // 'fallback_format' => array( - // 'type' => 'text', - // 'options' => $formats, - // 'label' => __( 'Fallback Format', 'radio-station' ), - // 'default' => 'oga', - // 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), - // 'tab' => 'general', - // 'section' => 'broadcast', - // ), + 'fallback_format' => array( + 'type' => 'text', + 'options' => $formats, + 'label' => __( 'Fallback Format', 'radio-station' ), + 'default' => 'ogg', + 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), // --- Main Radio Language --- 'radio_language' => array( @@ -222,6 +222,44 @@ 'section' => 'broadcast', ), + // === Station === + + // --- Station Image --- + // 2.3.3.8: added station logo image field + 'station_image' => array( + 'type' => 'image', + 'label' => __( 'Station Logo Image', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Timezone Location --- + 'timezone_location' => array( + 'type' => 'select', + 'options' => $timezones, + 'label' => __( 'Location Timezone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Clock Time Format --- + 'clock_time_format' => array( + 'type' => 'select', + 'options' => array( + '12' => __( '12 Hour Format', 'radio-station' ), + '24' => __( '24 Hour Format', 'radio-station' ), + ), + 'label' => __( 'Clock Time Format', 'radio-station' ), + 'default' => '12', + 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + // --- Station Phone Number --- // 2.3.3.6: added station phone number option 'station_phone' => array( @@ -231,7 +269,7 @@ 'default' => '', 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), 'tab' => 'general', - 'section' => 'broadcast', + 'section' => 'station', ), // --- Phone for Shows --- @@ -243,37 +281,34 @@ 'label' => __( 'Show Phone Display', 'radio-station' ), 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), 'tab' => 'general', - 'section' => 'broadcast', + 'section' => 'station', ), - - // === Times === - - // --- Timezone Location --- - 'timezone_location' => array( - 'type' => 'select', - 'options' => $timezones, - 'label' => __( 'Location Timezone', 'radio-station' ), + // --- Station Email Address --- + // 2.3.3.8: added station email address option + 'station_email' => array( + 'type' => 'email', 'default' => '', - 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), + 'label' => __( 'Station Email', 'radio-station' ), + 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), 'tab' => 'general', - 'section' => 'times', + 'section' => 'station', ), - // --- Clock Time Format --- - 'clock_time_format' => array( - 'type' => 'select', - 'options' => array( - '12' => __( '12 Hour Format', 'radio-station' ), - '24' => __( '24 Hour Format', 'radio-station' ), - ), - 'label' => __( 'Clock Time Format', 'radio-station' ), - 'default' => '12', - 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), + // --- Email for Shows --- + // 2.3.3.8: added default to email address option + 'shows_email' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Email Display', 'radio-station' ), + 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), 'tab' => 'general', - 'section' => 'times', + 'section' => 'station', ), + // === Feeds === + // --- REST Data Routes --- 'enable_data_routes' => array( 'type' => 'checkbox', @@ -343,6 +378,224 @@ 'pro' => true, ), */ + // === Basic Stream Player === + + // TODO: add note about these defaults being overrideable in widgets + + // --- Player Title --- + /* 'player_title' => array ( + 'type' => 'text', + 'label' => __( 'Player Title Text', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Default Text to display along with Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), */ + + // --- Player Image --- + /* 'player_image' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Station Image', 'radio-station' ), + 'default' => 'yes', + 'helper' => __( 'Display your Radio Station logo image in Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), */ + + // --- Player Script --- + /* 'player_script' => array( + 'type' => 'select', + 'label' => __( 'Player Script', 'radio-station' ), + 'default' => 'amplitude', + 'options' => array( + 'amplitude' => __( 'Amplitude', 'radio-station' ), + 'howler' => __( 'Howler', 'radio-station' ), + 'jplayer' => __( 'jPlayer', 'radio-station' ), + ), + 'helper' => __( 'Default Script to use for Radio Streaming Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), */ + + // --- Player Theme --- + /* 'player_theme' => array( + 'type' => 'select', + 'label' => __( 'Default Player Theme', 'radio-station' ), + 'default' => 'light-rounded', + 'options' => array( + 'light-rounded' => 'Light on Dark, Rounded Buttons', 'radio-station' ), + 'light-square' => 'Light on Dark, Square Buttons', 'radio-station' ), + 'dark-rounded' => 'Dark on Light, Rounded Buttons', 'radio-station' ), + 'dark-square' => 'Dark on Light, Square Buttons', 'radio-station' ), + ), + 'helper' => __( '', 'radio-station' + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), */ + + // --- [Pro] Playing Highlight Color --- + /* 'playing_button_color' => array( + 'type' => 'color', + 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), */ + + // --- [Pro] Control Icons Highlight Color --- + /* 'control_buttons_color' => array( + 'type' => 'color', + 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Default highlight color to use for Control buttons when active.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), */ + + // --- [Pro] Volume Knob Color --- + /* 'volume_thumb_color' => array( + 'type' => 'color', + 'label' => __( 'Volume Knob Color', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), */ + + // --- [Pro] Volume Track Color --- + /* 'volume_track_color' => array( + 'type' => 'coloralpha', + 'label' => __( 'Volume Track Color', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), */ + + // TODO: additional CSS input field ? + + // === Advanced Stream Player === + + // --- Player Volume --- + /* 'player_volume' => array( + 'type' => 'number', + 'label' => __( 'Player Start Volume', 'radio-station' ), + 'default' => 77, + 'min' => 0, + 'step' => 1, + 'max' => 100, + 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), */ + + // --- Single Player --- + /* 'player_single' => array( + 'type' => 'checkbox', + 'label' => __( 'Single Player at Once', 'radio-station' ), + 'default' => 'yes', + 'helper' => __( 'Stop any existing Players in other windows or tabs when a Player is started.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), */ + + // --- [Pro] Player Autoresume --- + /* 'player_autoresume' => array( + 'type' => 'checkbox', + 'label' => __( 'Autoresume Playback', 'radio-station' ), + 'default' => 'yes', + 'helper' => __( 'Attempt to resume playback if user was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), */ + + // --- [Pro] Popup Player Window --- + /* 'player_popup' => array( + 'type' => 'checkbox', + 'label' => __( 'Popup Player Window', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Add a popup icon to your Player to open it in a separate window.', 'radio-station' ), + 'pro' => true, + ), */ + + // === Sitewide Player Bar === + + // --- [Pro] Sitewide Player Bar --- + /* 'player_bar' => array( + 'type' => 'select', + 'label' => __( 'Sitewide Player Bar', 'radio-station' ), + 'default' => 'off', + 'options' => array( + 'off' => __( 'No Player Bar', 'radio-station + 'top' => __( 'Top Player Bar', 'radio-station' ), + 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), + + ), + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), + 'pro' => true, + ), */ + + // --- [Pro] Continuous Playback --- + /* 'player_continuous' => array( + 'type' => 'checkbox', + 'label' => __( 'Continuous Playback', 'radio-station' ), + 'default' => 'yes', + 'helper' => __( 'Uninterrupted sitewide bar playback while user is navigating between pages! Requires additional Plugin.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), */ + + // --- [Pro] Fade In Player Bar --- + /* 'player_fadein' => array( + 'type' => 'number', + 'label' => __( 'Fade In Player Bar', 'radio-station' ), + 'default' => '2500', + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), */ + + // --- [Pro] Bar Player Text Color --- + /* 'player_bar_text' => array( + 'type' => 'color', + 'label' => __( 'Sitewide Player Text Color', 'radio-station' ), + 'default' => '#FFFFFF', + 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), */ + + // --- [Pro] Bar Player Background Color --- + /* 'player_bar_background' => array( + 'type' => 'coloralpha', + 'label' => __( 'Sitewide Player Background Color', 'radio-station' ), + 'default' => 'rgba(0,0,0,1)', + 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), */ + // === Master Schedule Page === // --- Schedule Page --- @@ -703,13 +956,15 @@ ), // --- Playlist Template --- + // 2.3.3.8: added missing singular.php option to match show_template 'playlist_template' => array( 'label' => __( 'Playlist Template', 'radio-station' ), 'type' => 'select', 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), + 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), + 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), + 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), ), 'default' => 'page', 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), @@ -811,8 +1066,10 @@ // --- Tab Labels --- // 2.3.2: add widget options tab + // 2.3.3.8: added player options tab 'tabs' => array( 'general' => __( 'General', 'radio-station' ), + 'player' => __( 'Player', 'radio-station' ), 'pages' => __( 'Pages', 'radio-station' ), 'templates' => __( 'Templates', 'radio-station' ), 'widgets' => __( 'Widgets', 'radio-station' ), @@ -822,10 +1079,12 @@ // --- Section Labels --- // 2.3.2: add widget loading section 'sections' => array( - 'station' => __( 'Station', 'radio-station' ), 'broadcast' => __( 'Broadcast', 'radio-station' ), - 'times' => __( 'Times', 'radio-station' ), + 'station' => __( 'Station', 'radio-station' ), 'feeds' => __( 'Feeds', 'radio-station' ), + 'basic' => __( 'Basic Defaults', 'radio-station' ), + 'advanced' => __( 'Advanced Defaults', 'radio-station' ), + 'colors' => __( 'Player Colors', 'radio-station' ), 'single' => __( 'Single Templates', 'radio-station' ), 'archive' => __( 'Archive Templates', 'radio-station' ), 'schedule' => __( 'Schedule Page', 'radio-station' ), @@ -1218,8 +1477,10 @@ function radio_station_localize_script() { // ------------------------- // 2.3.3.7: added streaming data filter for player integration add_filter( 'radio_station_player_data', 'radio_station_streaming_data' ); -function radio_station_streaming_data( $data ) { +function radio_station_streaming_data( $data, $station = false ) { $data = array( + 'script' => radio_station_get_setting( 'player_script' ), + 'instance' => 0, 'url' => radio_station_get_stream_url(), 'format' => radio_station_get_setting( 'streaming_format' ), 'fallback' => radio_station_get_setting( 'fallback_url' ), @@ -1348,9 +1609,9 @@ function radio_station_get_template( $type, $template, $paths = false ) { return false; } -// --------------------------- -// Station Phone Number Filter -// --------------------------- +// ------------------------------------- +// Station Phone Number for Shows Filter +// ------------------------------------- // 2.3.3.6: added to return station phone for all Shows (if not set for Show) add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); function radio_station_phone_number( $phone, $post_id ) { @@ -1365,6 +1626,23 @@ function radio_station_phone_number( $phone, $post_id ) { return false; } +// -------------------------------------- +// Station Email Address for Shows Filter +// -------------------------------------- +// 2.3.3.8: added to return station email for all Shows (if not set for Show) +add_filter( 'radio_station_show_email', 'radio_station_email_address', 10, 2 ); +function radio_station_email_address( $email, $post_id ) { + if ( $email ) { + return $email; + } + $shows_email = radio_station_get_setting( 'shows_email' ); + if ( 'yes' == $shows_email ) { + $email = radio_station_get_setting( 'station_email' ); + return $email; + } + return false; +} + // ------------------------------ // Automatic Pages Content Filter // ------------------------------ @@ -1799,14 +2077,17 @@ function radio_station_load_template( $single_template, $type, $templates ) { } // --- use post or page template --- + // 2.3.3.8: added missing singular.php template setting if ( 'post' == $show_template ) { $templates = array( 'single.php' ); } elseif ( 'page' == $show_template ) { $templates = array( 'page.php' ); + } elseif ( 'singular' == $show_template ) { + $template = array( 'singular.php' ); } - // --- add standard fallbacks to singular and index --- - $templates[] = 'singular.php'; + // --- add standard fallbacks to index --- + // 2.3.3.8: remove singular fallback as it is explicitly chosen $templates[] = 'index.php'; $single_template = get_query_template( $post_type, $templates ); } diff --git a/readme.md b/readme.md index 8957252..145b345 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html ## Description -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. +Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. @@ -77,7 +77,7 @@ This plugin is under active development and we are continuously working to enhan ### Upgrading to Radio Station Pro -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). ## Installation @@ -162,18 +162,6 @@ The only Hosts and Producers that can edit a show are the ones listed as being H Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account and edit your author.php theme file accordingly. That's a little out of the scope of this plugin. I recommend [Cimy User Extra Fields](http://wordpress.org/extend/plugins/cimy-user-extra-fields/) -#### Can I use this plugin for Podcasts? - -While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. - -#### Can I use this plugin for TWitchTV, Facebook Live, or Clubhouse shows? - -Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. - -#### Can I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? - -We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. - #### What languages other than English is the plugin available in? Right now: @@ -200,30 +188,26 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices +#### 2.3.3.8 +* Updated Plugin Panel Library +* Added Stream Format selection setting +* Added Station email address setting with default display option +* Added Section order filtering for Master Schedules and Widgets +* Added Show image alignment attribute to Schedule Tabs View + +#### 2.3.3.7 +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + #### 2.3.3.6 -<<<<<<< HEAD -* Update: Freemius SDK (2.4.1) -* Update: Plugin Loader (1.1.6) with phone number and CSV validation -* Added: Station phone number setting with default display option -* Added: Schedule classes for Shows before and after current Show -* Improved: current Show highlighting on Schedule for overnight shifts -* Improved: info section reordering filters on single Show template -* Fixed: Edit permissions checks for Related to Show post assignments -* Fixed: Main Language option value for WordPress Setting -* Fixed: make Date on Tab clickable on Tabbed Schedule View -* Fixed: prevent possible conflicts with changes not saved reload message -* Fixed: do not conflict check Shift against itself for last shift check -* Fixed: link back to Show posts for related Show posts (allow multiple) -* Fixed: filter next/previous post link for (multiple) related Show posts -* Fixed: automatic pages conflict where themes filter the_content early - -======= * Updated Freemius SDK and Plugin Loader libraries * Added Station phone number setting with default display option * Added Schedule classes for Shows before and after current Show * Multiple Related Show Post assignment edit and link fixes * Bugfixes for permissions, main language and shift checker ->>>>>>> release/2.3.3.7 #### 2.3.3.5 * Ability to assign Post to relate to multiple Shows diff --git a/readme.txt b/readme.txt index 4d484b4..8f9c87a 100644 --- a/readme.txt +++ b/readme.txt @@ -198,6 +198,21 @@ We haven't built an interface between Google Calendar and Radio Station just yet == Changelog == += 2.3.3.8 = +* Update: Plugin Panel (1.1.7) with Image and Color Picker fields +* Added: Stream Format and Fallback/Format selection setting +* Added: Station Email Address setting with default display option +* Added: Section order filtering for Master Schedule Views +* Added: Section display filtering for Master Schedule Views +* Added: Section display filtering for Widget sections +* Added: Show image alignment attribute to Schedule Tabs View +* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) +* Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Fixed: Display Widget Countdown when no Current Show/Playlist +* Fixed: Check for explicit singular.php template usage setting +* Fixed: Access to Shows Data via querystring of Show ID/name +* Fixed: Shows Data for Genres/Languages querystring of ID/name + = 2.3.3.7 = * Fixed: Schedule Overrides overlapping multiple Show shifts * Fixed: Bulk Edit field repetition and possible jQuery conflict @@ -349,7 +364,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Fix to not 404 author pages for DJs without blog posts = 2.2.7 = -* Dutch translation added (Thank you to André Dortmont for the file!) +* Dutch translation added (Thank you to André Dortmont for the file!) * Added Tabbed Display for Master Schedule Shortcode (via Tutorial) * Add Show list columns with active, shift, DJs and show image displays * Add Schedule Override list columns with date sorting and filtering @@ -644,6 +659,20 @@ We haven't built an interface between Google Calendar and Radio Station just yet == Upgrade Notice == += 2.3.3.8 = +* Updated Plugin Panel Library +* Added Stream Format selection setting +* Added Station email address setting with default display option +* Added Section order filtering for Master Schedules and Widgets +* Added Show image alignment attribute to Schedule Tabs View + += 2.3.3.7 = +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + = 2.3.3.6 = * Updated Freemius SDK and Plugin Loader libraries * Added Station phone number setting with default display option diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 9ce162b..b035cfe 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -46,8 +46,13 @@ $more = apply_filters( 'radio_station_schedule_list_excerpt_more', '[…]' ); } +// --- set list info key order --- +// 2.3.3.8: added for possible info rearrangement +$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'excerpt' ); +$infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); + // --- start list schedule output --- -$output .= '
        ' . $newline; +$list .= '
          ' . $newline; $tcount = 0; // 2.3.0: loop weekdays instead of legacy master list @@ -124,23 +129,23 @@ // 2.2.2: use translate function for weekday string // 2.3.2: added optional display-date attribute $display_day = radio_station_translate_weekday( $weekday ); - $output .= '
        • ' . $newline; - $output .= '' . $newline; + $list .= '' . $newline; + $list .= '>' . esc_html( $display_day ) . '' . $newline; if ( $atts['display_date'] ) { - $output .= ' ' . esc_html( $date_subheading ) . '' . $newline; + $list .= ' ' . esc_html( $date_subheading ) . '' . $newline; } // 2.3.2: add output of day start and end times // 2.3.2: replace strtotime with to_time for timezones - $output .= '' . $newline; - $output .= '' . $newline; + $list .= '' . $newline; + $list .= '' . $newline; // --- open day list --- - $output .= '
            ' . $newline; + $list .= '
              ' . $newline; // --- get shifts for this day --- if ( isset( $schedule[$weekday] ) ) { @@ -153,7 +158,9 @@ if ( count( $shifts ) > 0 ) { foreach ( $shifts as $shift ) { + // 2.3.3.8: reset info array $show = $shift['show']; + $info = array(); $split_id = false; // --- convert shift time data --- @@ -222,7 +229,7 @@ // --- open show list item --- $classlist = implode( ' ', $classes ); - $output .= '
            • ' . $newline; + $list .= '
            • ' . $newline; // --- show avatar --- if ( $atts['show_image'] ) { @@ -230,13 +237,18 @@ $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'list' ); if ( $show_avatar ) { - $output .= '
              ' . $newline; + $avatar = '
              ' . $newline; if ( $show_link ) { - $output .= '' . $show_avatar . ''; + $avatar .= '' . $show_avatar . ''; } else { - $output .= $show_avatar; + $avatar .= $show_avatar; + } + $avatar .= '
              ' . $newline; + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'list' ); + if ( ( '' != $avatar ) && is_string( $avatar ) ) { + $info['avatar'] = $avatar; + // $list .= $avatar; } - $output .= '
              ' . $newline; } } @@ -246,80 +258,64 @@ } else { $show_title = esc_html( $show['name'] ); } - $output .= '' . $newline; - $output .= $show_title; - $output .= '' . $newline; + $title = '' . $newline; + $title .= $show_title; + $title .= '' . $newline; + $title = apply_filters( 'radio_station_schedule_show_title', $title, $show['id'], 'list' ); + if ( ( '' != $title ) && is_string( $title ) ) { + $info['title'] = $title; + // $list .= $title; + } // --- show hosts --- // 2.3.0: changed from show_djs if ( $atts['show_hosts'] ) { - $hosts = ''; + $show_hosts = ''; if ( $show['hosts'] && is_array( $show['hosts'] ) && ( count( $show['hosts'] ) > 0 ) ) { $count = 0; $host_count = count( $show['hosts'] ); - $hosts .= ''; - $hosts .= esc_html( __( 'with', 'radio-station' ) ); + $show_hosts .= ''; + $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); // 2.3.2: fix variable to close span tag - $hosts .= ' ' . $newline; + $show_hosts .= ' ' . $newline; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { - $hosts .= esc_html( $host['name'] ); + $show_hosts .= esc_html( $host['name'] ); } if ( ( ( 1 === $count ) && ( 2 === $host_count ) ) || ( ( $host_count > 2 ) && ( ( $count === $host_count - 1 ) ) ) ) { - $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $show_hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $hosts .= ', '; + $show_hosts .= ', '; } } } // 2.3.3.5: fix to incorrect context value (tabs) - $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'list' ); - if ( $hosts ) { - $output .= '
              ' . $newline; - $output .= $hosts; - $output .= '
              ' . $newline; + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'list' ); + if ( $show_hosts ) { + $hosts = '
              ' . $newline; + $hosts .= $show_hosts; + $hosts .= '
              ' . $newline; + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'list' ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $info['hosts'] = $hosts; + // $list .= $hosts; + } } } // --- show time --- if ( $atts['show_times'] ) { - // --- convert shift time for display --- - // 2.3.0: updated to use new schedule data - /* if ( '00:00 am' == $shift['start'] ) { - $shift['start'] = '12:00 am'; - } - if ( '11:59:59 pm' == $shift['end'] ) { - $shift['end'] = '12:00 am'; - } - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $shift['start'], 24 ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = radio_station_convert_shift_time( $shift['real_end'], 24 ); - } else { - $end = radio_station_convert_shift_time( $shift['end'], 24 ); - } - } else { - $start = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm), $shift['start'] ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['real_end'] ); - } else { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['end'] ); - } - } */ - // --- get start and end times --- // 2.3.2: maybe use real start and end times if ( $real_shift_start ) { @@ -339,13 +335,28 @@ $show_time = '' . esc_html( $start ) . '' . $newline; $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'list' ); - $output .= '
              ' . $show_time . '
              ' . $newline; - $output .= '
              ' . $newline; - $tcount ++; + } else { + + // 2.3.3.8: added for now playing check + $show_time = '' . $newline; + $show_time .= '' . $newline; + + } + // 2.3.3.8: moved filter out and added display filter + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'list', $shift ); + $times = '
              ' . $newline; + $times .= '
              ' . $newline; + $info['times'] = $times; + // $list .= $times; + $tcount ++; // --- encore --- if ( $atts['show_encore'] ) { @@ -357,9 +368,14 @@ } $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show['id'], 'list' ); if ( 'on' == $show_encore ) { - $output .= '
              '; - $output .= esc_html( __( 'encore airing', 'radio-station' ) ); - $output .= '
              ' . $newline; + $encore = '
              '; + $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); + $encore .= '
              ' . $newline; + $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show['id'], 'list' ); + if ( ( '' != $encore ) && is_string( $encore ) ) { + $info['encore'] = $encore; + // $list .= $encore; + } } } @@ -368,15 +384,21 @@ // 2.3.0: filter show file by show and context // 2.3.2: check disable download meta // 2.3.3: fix to incorrect filter name + // 2.3.3.8: add filter for show file link $show_file = get_post_meta( $show['id'], 'show_file', true ); $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'list' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $output .= '
              ' . $newline; - $output .= ''; - $output .= esc_html( __( 'Audio File', 'radio-station' ) ); - $output .= '' . $newline; - $output .= '
              ' . $newline; + $file = '
              ' . $newline; + $file .= ''; + $file .= esc_html( __( 'Audio File', 'radio-station' ) ); + $file .= '' . $newline; + $file .= '
              ' . $newline; + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'list' ); + if ( ( '' != $file ) && is_string( $file ) ) { + $info['file'] = $file; + // $list .= $file; + } } } @@ -384,16 +406,21 @@ // (defaults to on) // 2.3.0: add genres to list view if ( $atts['show_genres'] ) { - $output .= '
              ' . $newline; - $genres = array(); + $genres = '
              ' . $newline; + $show_genres = array(); if ( count( $terms ) > 0 ) { + $genres .= esc_html( __( 'Genres', 'radio-station' ) ) . ': '; foreach ( $terms as $term ) { - $genres[] = '' . esc_html( $term->name ) . '' . $newline; + $show_genres[] = '' . esc_html( $term->name ) . '' . $newline; } - $genre_display = implode( ', ', $genres ); - $output .= esc_html( __( 'Genres', 'radio-station' ) ) . ': ' . $genre_display; + $genres .= implode( ', ', $show_genres ); + } + $genres .= '
              ' . $newline; + $genres = apply_filters( 'radio_station_schedule_show_genres_display', $genres, $show['id'], 'list' ); + if ( ( '' != $genres ) && is_string( $genres ) ) { + $info['genres'] = $genres; + // $list .= $genres; } - $output .= '
              ' . $newline; } // --- show description --- @@ -404,31 +431,41 @@ // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { - $excerpt = $show_post->post_excerpt; - $excerpt .= ' ' . $more . ''; + $show_excerpt = $show_post->post_excerpt; + $show_excerpt .= ' ' . $more . ''; } else { - $excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); + $show_excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'list' ); + + // --- set excerpt display --- + $excerpt = '
              ' . $newline; + $excerpt .= $show_excerpt . $newline; + $excerpt .= '
              ' . $newline; + if ( ( '' != 'excerpt' ) && is_string( $excerpt ) ) { + $info['excerpt'] = $excerpt; + // $list .= $excerpt; + } + } - // --- filter excerpt by context --- - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'list' ); - - // --- output excerpt --- - $output .= '
              ' . $newline; - $output .= $excerpt . $newline; - $output .= '
              ' . $newline; - + // --- add item info according to key order --- + // 2.3.3.8: added for possible order rearrangement + foreach ( $infokeys as $infokey ) { + if ( isset( $info[$infokey] ) ) { + $list .= $info[$infokey]; + } } - $output .= '
            • ' . $newline; + $list .= '' . $newline; } } - $output .= '
            ' . $newline; + $list .= '
          ' . $newline; // --- close master list day item --- - $output .= '
        • ' . $newline; + $list .= '' . $newline; } } // --- close master list --- -$output .= '
        ' . $newline; +$list .= '
      ' . $newline; +$output = $list; \ No newline at end of file diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index ced4061..097d877 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -46,16 +46,21 @@ $more = apply_filters( 'radio_station_schedule_table_excerpt_more', '[…]' ); } +// --- set cell info key order --- +// 2.3.3.8: added for possible info rearrangement +$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'excerpt' ); +$infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); + // --- clear floats --- -$output .= '
      ' . $newline; +$table = '
      ' . $newline; // --- start master program table --- -$output .= '' . $newline; +$table .= '
      ' . $newline; // --- weekday table headings row --- // 2.3.2: added hour column heading id -$output .= '' . $newline; -$output .= '' . $newline; +$table .= '' . $newline; +$table .= '' . $newline; foreach ( $weekdays as $i => $weekday ) { @@ -135,32 +140,32 @@ // 2.3.2: added check for optional display_date attribute $arrows = array( 'right' => '►', 'left' => '◄' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'table' ); - $output .= '' . $newline; + $table .= '' . $newline; } } -$output .= '' . $newline; +$table .= '' . $newline; // --- loop schedule hours --- $tcount = 0; @@ -174,7 +179,7 @@ } // --- start hour row --- - $output .= '' . $newline; + $table .= '' . $newline; // --- set data format for timezone conversions --- if ( 24 == (int) $atts['time'] ) { @@ -196,22 +201,22 @@ $class = implode( ' ', $classes ); // --- hour heading --- - $output .= '' . $newline; + $table .= '
      '; + $table .= esc_html( $hour_display ); + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '' . $newline; foreach ( $weekdays as $i => $weekday ) { @@ -366,6 +371,8 @@ // --- maybe add shift display to the cell --- if ( $display ) { + // 2.3.3.8: reset info array for each cell + $info = array(); $show = $shift['show']; // --- set the show div classes --- @@ -426,33 +433,43 @@ } $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'table' ); if ( $show_avatar ) { - $cell .= '
      ' . $newline; + $avatar = '
      ' . $newline; if ( $show_link ) { - $cell .= '' . $show_avatar . '' . $newline; + $avatar .= '' . $show_avatar . '' . $newline; } else { - $cell .= $show_avatar . $newline; + $avatar .= $show_avatar . $newline; + } + $avatar .= '
      ' . $newline; + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'table' ); + if ( ( '' != $avatar ) && is_string( $avatar ) ) { + $info['avatar'] = $avatar; + // $cell .= $avatar; } - $cell .= '
      ' . $newline; } // --- show title --- - $cell .= '
      ' . $newline; + $title = '
      ' . $newline; if ( $show_link ) { - $cell .= '' . esc_html( $show['name'] ) . '' . $newline; + $title .= '' . esc_html( $show['name'] ) . '' . $newline; } else { - $cell .= esc_html( $show['name'] ) . $newline; + $title .= esc_html( $show['name'] ) . $newline; + } + $title .= '
      ' . $newline; + $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show['id'], 'table' ); + if ( ( '' != $title ) && is_string( $title ) ) { + $info['title'] = $title; + // $cell .= $title; } - $cell .= '
      ' . $newline; // --- show DJs / hosts --- if ( $atts['show_hosts'] ) { - $hosts = ''; + $show_hosts = ''; if ( $show['hosts'] && is_array( $show['hosts'] ) && ( count( $show['hosts'] ) > 0 ) ) { - $hosts .= ' '; - $hosts .= esc_html( __( 'with', 'radio-station' ) ); - $hosts .= ' ' . $newline; + $show_hosts .= ' '; + $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); + $show_hosts .= ' ' . $newline; $count = 0; $hostcount = count( $show['hosts'] ); @@ -460,58 +477,36 @@ $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { - $hosts .= esc_html( $host['name'] ); + $show_hosts .= esc_html( $host['name'] ); } if ( ( ( 1 === $count ) && ( 2 === $hostcount ) ) || ( ( $hostcount > 2 ) && ( ( $hostcount - 1 ) === $count ) ) ) { - $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $show_hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( $count < $hostcount ) && ( $hostcount > 2 ) ) { - $hosts .= ', '; + $show_hosts .= ', '; } } } - $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'table' ); - if ( $hosts ) { - $cell .= '
      ' . $newline; - $cell .= $hosts; - $cell .= '
      ' . $newline; + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'table' ); + if ( $show_hosts ) { + $hosts = '
      ' . $newline; + $hosts .= $show_hosts; + $hosts .= '
      ' . $newline; + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'table' ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $info['hosts'] = $hosts; + // $cell .= $hosts; + } } } // --- show time --- if ( $atts['show_times'] ) { - // --- convert shift time for display --- - // 2.3.2: removed duplicate calculation of shift times - /* if ( '00:00 am' == $shift['start'] ) { - $shift['start'] = '12:00 am'; - } - if ( '11:59:59 pm' == $shift['end'] ) { - $shift['end'] = '12:00 am'; - } */ - - /* if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $shift['start'], 24 ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = radio_station_convert_shift_time( $shift['real_end'], 24 ); - } else { - $end = radio_station_convert_shift_time( $shift['end'], 24 ); - } - } else { - $start = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['start'] ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['real_end'] ); - } else { - $end = str_replace( array( 'am', 'pm'), array( $am, $pm ), $shift['end'] ); - } - } */ - // --- get start and end times --- // 2.3.2: maybe use real start and end times if ( $real_shift_start ) { @@ -531,15 +526,29 @@ $show_time = '' . esc_html( $start ) . '' . $newline; $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'table' ); - // --- add show time to cell --- - $cell .= '
      ' . $show_time . '
      ' . $newline; - $cell .= '
      ' . $newline; - $tcount ++; + } else { + + // 2.3.3.8: added for now playing check + $show_time = '' . $newline; + $show_time .= '' . $newline; } + // --- add show time to cell --- + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'table', $shift ); + $times = '
      ' . $newline; + $times .= '
      ' . $newline; + $info['times'] = $times; + // $cell .= $times; + $tcount ++; + // --- encore airing --- // 2.3.1: added isset check for encore switch $show_encore = false; @@ -548,25 +557,36 @@ } $show_encore = apply_filters( 'radio_station_schedule_show_encore', $shift['encore'], $show['id'], 'table' ); if ( 'on' == $show_encore ) { - $cell .= '
      '; - $cell .= esc_html( __( 'encore airing', 'radio-station' ) ); - $cell .= '
      ' . $newline; + $encore = '
      '; + $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); + $encore .= '
      ' . $newline; + $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show['id'], 'table' ); + if ( ( '' != $encore ) && is_string( $encore ) ) { + $info['encore'] = $encore; + // $cell .= $encore; + } } // --- show file --- + // 2.3.2: check disable download meta + // 2.3.3.8: add filter for show file link $show_file = false; if ( $atts['show_file'] ) { $show_file = get_post_meta( $show['id'], 'show_file', true ); } $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'table' ); - // 2.3.2: check disable download meta $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $cell .= '
      ' . $newline; - $cell .= ''; - $cell .= esc_html( __( 'Audio File', 'radio-station' ) ); - $cell .= '' . $newline; - $cell .= '
      ' . $newline; + $file = '
      ' . $newline; + $file .= ''; + $file .= esc_html( __( 'Audio File', 'radio-station' ) ); + $file .= '' . $newline; + $file .= '
      ' . $newline; + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show['id'], 'table' ); + if ( ( '' != $file ) && is_string( $file ) ) { + $info['file'] = $file; + // $cell .= $link; + } } // --- show description --- @@ -577,23 +597,36 @@ // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { - $excerpt = $show_post->post_excerpt; - $excerpt .= ' ' . $more . ''; + $show_excerpt = $show_post->post_excerpt; + $show_excerpt .= ' ' . $more . ''; } else { - $excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); + $show_excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } // --- filter excerpt by context --- - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'table' ); + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'table' ); // --- output excerpt --- - $cell .= '
      ' . $newline; - $cell .= $excerpt . $newline; - $cell .= '
      ' . $newline; - + $excerpt = '
      ' . $newline; + $excerpt .= $show_excerpt . $newline; + $excerpt .= '
      ' . $newline; + $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpy, $show['id'], 'table' ); + if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { + $info['excerpt'] = $excerpt; + // $cell .= $excerpt; + } } } + + // --- add cell info according to key order --- + // 2.3.3.8: added for possible order rearrangement + foreach ( $infokeys as $infokey ) { + if ( isset( $info[$infokey] ) ) { + $cell .= $info[$infokey]; + } + } + $cell .= '
      ' . $newline; // 2.3.1: fix to ensure reset showcontinued flag in cell @@ -619,22 +652,23 @@ $cellclasses[] = 'no-shifts'; } $cellclass = implode( ' ', $cellclasses ); - $output .= '' . $newline; + $table .= '' . $newline; + $table .= '' . $newline; } } // --- close hour row --- - $output .= '' . $newline; + $table .= '' . $newline; } -$output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '' . $arrows['left'] . '' . $newline; - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '' . $arrows['left'] . '' . $newline; + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '>' . esc_html( $display_day ) . '
      ' . $newline; if ( $atts['display_date'] ) { - $output .= '
      ' . esc_html( $date_subheading ) . '
      ' . $newline; + $table .= '
      ' . esc_html( $date_subheading ) . '
      ' . $newline; } - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '' . $arrows['right'] . '' . $newline; - $output .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '' . $arrows['right'] . '' . $newline; + $table .= '
      ' . $newline; // 2.3.2: add day start and end time date - $output .= '' . $newline; - $output .= '' . $newline; + $table .= '' . $newline; + $table .= '' . $newline; - $output .= '
      ' . $newline; + $table .= '' . $newline; if ( isset( $_GET['hourdebug'] ) && ( '1' == $_GET['hourdebug'] ) ) { - $output .= ''; - $output .= 'Now' . $now . '(' . date( 'H:i', $now ) . ')
      '; - $output .= 'Hour Start' . $hour_start . '(' . date( 'H:i', $hour_start ) . ')
      '; - $output .= 'Hour End' . $hour_end . '(' . date( 'H:i', $hour_end ) . ')
      '; - $output .= '
      '; + $table .= ''; + $table .= 'Now' . $now . '(' . date( 'H:i', $now ) . ')
      '; + $table .= 'Hour Start' . $hour_start . '(' . date( 'H:i', $hour_start ) . ')
      '; + $table .= 'Hour End' . $hour_end . '(' . date( 'H:i', $hour_end ) . ')
      '; + $table .= '
      '; } - $output .= '
      '; - $output .= esc_html( $hour_display ); - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; + $table .= '
      ' . $newline; + $table .= '
      ' . $newline; if ( isset( $cell ) ) { - $output .= $cell; + $table .= $cell; } - $output .= '
      ' . $newline; - $output .= '
      ' . $newline; +$table .= '' . $newline; if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { - $output .= '
      Shift Debug Info:
      ' . $shiftdebug . '
      '; + $table .= '
      Shift Debug Info:
      ' . $shiftdebug . '
      '; } +$output = $table; \ No newline at end of file diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 8aba71c..8b044df 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -47,9 +47,12 @@ $more = apply_filters( 'radio_station_schedule_tabs_excerpt_more', '[…]' ); } -// --- start tabbed schedule output --- -$output .= '
        ' . $newline; +// --- set cell info key order --- +// 2.3.3.8: added for possible info rearrangement +$infokeys = array( 'title', 'hosts', 'times', 'encore', 'file', 'genres' ); +$infokeys = apply_filters( 'radio_station_schedule_tabs_info_order', $infokeys ); +// --- start tabbed schedule output --- $panels = ''; $tcount = 0; $start_tab = false; @@ -142,31 +145,31 @@ // 2.3.1: added (negative) return to arrow onclick functions $arrows = array( 'right' => '►', 'left' => '◄' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'tabs' ); - $output .= '
      • ' . $newline; - $output .= '
        ' . $newline; - $output .= '' . $arrows['left'] . '' . $newline; - $output .= '
        ' . $newline; + $tabs .= '
      • ' . $newline; + $tabs .= '
        ' . $newline; + $tabs .= '' . $arrows['left'] . '' . $newline; + $tabs .= '
        ' . $newline; // 2.3.2: added optional display_date attribute and subheading - $output .= '
        ' . $newline; - $output .= '
        ' . $newline; + $tabs .= '>' . esc_html( $display_day ) . '
        ' . $newline; if ( $atts['display_date'] ) { - $output .= '
        ' . esc_html( $date_subheading ) . '
        ' . $newline; + $tabs .= '
        ' . esc_html( $date_subheading ) . '
        ' . $newline; } - $output .= '
        ' . $newline; + $tabs .= '
    ' . $newline; - $output .= '
    ' . $newline; - $output .= '' . $arrows['right'] . '' . $newline; - $output .= '
    ' . $newline; - $output .= '
    ' . $newline; + $tabs .= '
    ' . $newline; + $tabs .= '' . $arrows['right'] . '' . $newline; + $tabs .= '
    ' . $newline; + $tabs .= '
    ' . $newline; // 2.3.2: add start and end day times for automatic highlighting - $output .= '' . $newline; - $output .= '' . $newline; - $output .= ''; + $tabs .= '' . $newline; + $tabs .= '' . $newline; + $tabs .= ''; // 2.2.7: separate headings from panels for tab view $classes = array( 'master-schedule-tabs-panel' ); @@ -197,11 +200,21 @@ $foundshows = true; + // --- maybe set start avatar position --- + // 2.3.3.8: added for possible alternating avatar positions + $avatar_position = false; + if ( 'alternate' == $atts['image_position'] ) { + $avatar_position = 'left'; + $avatar_position = apply_filters( 'radio_station_schedule_tabs_avatar_position_start', $avatar_position ); + } + $j = 0; foreach ( $shifts as $shift ) { + // 2.3.3.8: reset info array $j++; $show = $shift['show']; + $info = array(); $split_id = false; $show_link = false; @@ -289,106 +302,100 @@ // --- Show Image --- // (defaults to display on) + $avatar = ''; if ( $atts['show_image'] ) { + + // --- get show avatar image --- // 2.3.0: filter show avatar by show and context // 2.3.0: maybe link avatar to show $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'tabs' ); + + // --- set show image classes --- + $classes = array( 'show-image' ); + if ( ( 'left' == $atts['image_position'] ) || ( 'left' == $avatar_position ) ) { + $classes[] = 'left-image'; + } elseif ( ( 'right' == $atts['image_position'] ) || ( 'right' == $avatar_position ) ) { + $classes[] = 'right-image'; + } + $classlist = implode( ' ', $classes ); + if ( $show_avatar ) { - $panels .= '
    ' . $newline; + $avatar = '
    ' . $newline; if ( $show_link ) { - $panels .= '' . $show_avatar . '' . $newline; + $avatar .= '' . $show_avatar . '' . $newline; } else { - $panels .= $show_avatar; + $avatar .= $show_avatar; } - $panels .= '
    ' . $newline; + $avatar .= '
    ' . $newline; } else { - $panels .= '
    ' . $newline; + $avatar = '
    ' . $newline; } + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'tabs' ); + // $panels .= $avatar; } - // --- Show Information --- - $panels .= '
    ' . $newline; - // --- show title --- if ( $show_link ) { $show_title = '' . esc_html( $show['name'] ) . ''; } else { $show_title = esc_html( $show['name'] ); } - $panels .= '
    ' . $newline; - $panels .= $show_title . $newline; - $panels .= '
    ' . $newline; + $title = '
    ' . $newline; + $title .= $show_title . $newline; + $title .= '
    ' . $newline; + $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show['id'], 'tabs' ); + if ( ( '' != $title ) && is_string( $title ) ) { + $info['title'] = $title; + // $panels .= $title; + } // --- show hosts --- if ( $atts['show_hosts'] ) { - $hosts = ''; + $show_hosts = ''; if ( $show['hosts'] && is_array( $show['hosts'] ) && ( count( $show['hosts'] ) > 0 ) ) { $count = 0; $host_count = count( $show['hosts'] ); - $hosts .= ' '; - $hosts .= esc_html( __( 'with', 'radio-station' ) ); - $hosts .= ' ' . $newline; + $show_hosts .= ' '; + $show_hosts .= esc_html( __( 'with', 'radio-station' ) ); + $show_hosts .= ' ' . $newline; foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check if ( $atts['link_hosts'] && !empty( $host['url'] ) ) { - $hosts .= '' . esc_html( $host['name'] ) . '' . $newline; + $show_hosts .= '' . esc_html( $host['name'] ) . '' . $newline; } else { - $hosts .= esc_html( $host['name'] ); + $show_hosts .= esc_html( $host['name'] ); } if ( ( ( 1 === $count ) && ( 2 === $host_count ) ) || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) === $count ) ) ) { - $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + $show_hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $hosts .= ', '; + $show_hosts .= ', '; } } } - $hosts = apply_filters( 'radio_station_schedule_show_hosts', $hosts, $show['id'], 'tabs' ); - if ( $hosts ) { - $panels .= '
    ' . $newline; - $panels .= $hosts; - $panels .= '
    ' . $newline; + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'tabs' ); + if ( $show_hosts ) { + $hosts = '
    ' . $newline; + $hosts .= $show_hosts; + $hosts .= '
    ' . $newline; + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'tabs' ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $info['hosts'] = $hosts; + // $panels .= $hosts; + } } } // --- show times --- if ( $atts['show_times'] ) { - // --- convert shift time for display --- - // 2.3.0: updated to use new schedule data - /* if ( '00:00 am' == $shift['start'] ) { - $shift['start'] = '12:00 am'; - } - if ( '11:59:59 pm' == $shift['end'] ) { - $shift['end'] = '12:00 am'; - } - if ( 24 == (int) $atts['time'] ) { - $start = radio_station_convert_shift_time( $shift['start'], 24 ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = radio_station_convert_shift_time( $shift['real_end'], 24 ); - } else { - $end = radio_station_convert_shift_time( $shift['end'], 24 ); - } - $data_format = 'H:i'; - } else { - $start = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm), $shift['start'] ); - // 2.3.2: display real end of split shift - if ( isset( $shift['split'] ) && $shift['split'] && isset( $shift['real_end'] ) ) { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['real_end'] ); - } else { - $end = str_replace( array( 'am', 'pm'), array( ' ' . $am, ' ' . $pm ), $shift['end'] ); - } - $data_format = 'g:i a'; - } */ - // --- get start and end times --- // 2.3.2: maybe use real start and end times if ( $real_shift_start ) { @@ -408,19 +415,28 @@ $show_time = '' . esc_html( $start ) . '' . $newline; $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'tabs' ); - - $panels .= '
    ' . $show_time . '
    ' . $newline; - $panels .= '
    ' . $newline; - $tcount ++; } else { // 2.3.2: added for now playing check - $panels .= '' . $newline; - $panels .= '' . $newline; + // 2.3.3.8: moved for better conditional logic + $show_time = '' . $newline; + $show_time .= '' . $newline; + + } + // 2.3.3.8: moved show time filter out and added display filter + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'tabs', $shift ); + $times = '
    ' . $newline; + $times .= '
    ' . $newline; + $info['times'] = $times; + $tcount ++; // --- encore --- // 2.3.0: filter encore switch by show and context @@ -432,9 +448,14 @@ } $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show['id'], 'tabs' ); if ( 'on' == $show_encore ) { - $panels .= '
    '; - $panels .= esc_html( __( 'encore airing', 'radio-station' ) ); - $panels .= '
    ' . $newline; + $encore = '
    '; + $encore .= esc_html( __( 'encore airing', 'radio-station' ) ); + $encore .= '
    ' . $newline; + $encore = apply_filters( 'radio_station_schedule_show_encore_display', $encore, $show['id'], 'tabs' ); + if ( ( '' != $encore ) && is_string( $encore ) ) { + $info['encore'] = $encore; + // $panel .= $encore; + } } } @@ -443,35 +464,90 @@ // 2.3.0: filter audio file by show and context // 2.3.2: check show download disable meta // 2.3.3: fix to incorrect filter name + // 2.3.3.8: add filter for show file link div $show_file = get_post_meta( $show['id'], 'show_file', true ); $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'tabs' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { - $panels .= '
    ' . $newline; - $panels .= ''; - $panels .= esc_html( __( 'Audio File', 'radio-station' ) ); - $panels .= '' . $newline; - $panels .= '
    ' . $newline; + $file = '
    ' . $newline; + $file .= ''; + $file .= esc_html( __( 'Audio File', 'radio-station' ) ); + $file .= '' . $newline; + $file .= '
    ' . $newline; + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'tabs' ); + if ( ( '' != $file ) && is_string( $file ) ) { + $info['file'] = $file; + // $panels .= $link; + } } } // --- show genres --- // (defaults to display on) if ( $atts['show_genres'] ) { - $panels .= '
    ' . $newline; - $genres = array(); + $genres = '
    ' . $newline; + $show_genres = array(); if ( count( $terms ) > 0 ) { + $genres .= esc_html( __( 'Genres', 'radio-station' ) ) . ': '; foreach ( $terms as $term ) { - $genres[] = '' . esc_html( $term->name ) . '' . $newline; + $show_genres[] = '' . esc_html( $term->name ) . '' . $newline; } - $genre_display = implode( ', ', $genres ); - $panels .= esc_html( __( 'Genres', 'radio-station' ) ) . ': ' . $genre_display; + $genres .= implode( ', ', $show_genres ); + } + $genres .= '
    ' . $newline; + $genres = apply_filters( 'radio_station_schedule_show_genres', $genres, $show['id'], 'tabs' ); + if ( ( '' != $genres ) && is_string( $genres ) ) { + $info['genres'] = $genres; + // $panels .= $genres; } - $panels .= '
    ' . $newline; } + // --- left aligned show avatar --- + // 2.3.3.8: add image according to position + if ( $avatar ) { + if ( ( 'left' == $atts['image_position'] ) || ( 'left' == $avatar_position ) ) { + $panels .= $avatar; + } + } + + // --- Show Information --- + // 2.3.3.8: add avatar classes to show info for style targeting + $classes = array( 'show-info' ); + if ( $atts['show_desc'] ) { + $classes[] = 'has-show-desc'; + } + if ( ( 'left' == $atts['image_position'] ) || ( 'left' == $avatar_position ) ) { + $classes[] = 'left-image'; + } elseif ( ( 'right' == $atts['image_position'] ) || ( 'right' == $avatar_position ) ) { + $classes[] = 'right-image'; + } + $classlist = implode( ' ', $classes ); + $panels .= '
    ' . $newline; + + // --- add item info according to key order --- + // 2.3.3.8: added for possible order rearrangement + foreach ( $infokeys as $infokey ) { + if ( isset( $info[$infokey] ) ) { + $panels .= $info[$infokey]; + } + } + + // --- close show info section --- $panels .= '
    ' . $newline; + // --- right aligned show avatar --- + // 2.3.3.8: add image according to position + if ( $avatar ) { + if ( 'right' == $atts['image_position'] ) { + $panels .= $avatar; + } elseif ( 'right' == $avatar_position ) { + $panels .= $avatar; + $avatar_position = 'left'; + } elseif ( 'left' == $avatar_position ) { + $avatar_position = 'right'; + } + } + // --- show description --- if ( $atts['show_desc'] ) { @@ -480,19 +556,23 @@ // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { - $excerpt = $show_post->post_excerpt; - $excerpt .= ' ' . $more . ''; + $show_excerpt = $show_post->post_excerpt; + $show_excerpt .= ' ' . $more . ''; } else { - $excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); + $show_excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } // --- filter excerpt by context --- - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $excerpt, $show['id'], 'tabs' ); + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'tabs' ); // --- output excerpt --- - $panels .= '
    ' . $newline; - $panels .= $excerpt . $newline; - $panels .= '
    ' . $newline; + $excerpt = '
    ' . $newline; + $excerpt .= $show_excerpt . $newline; + $excerpt .= '
    ' . $newline; + $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpt, $show['id'], 'tabs' ); + if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { + $panels .= $excerpt; + } } @@ -511,9 +591,18 @@ $panels .= '' . $newline; } +// --- add day tabs to output --- +$output = '
      ' . $newline; +$output .= $tabs; $output .= '
    ' . $newline; -$output .= '
    ' . $newline; +// --- add day panels to output --- +// 2.3.3.8: check for hide past shows attribute +$output .= '
    Date: Thu, 21 Jan 2021 10:06:28 +1000 Subject: [PATCH 175/377] dev updates #306 --- templates/master-schedule-list.php | 9 ++++++++- templates/master-schedule-table.php | 9 ++++++++- templates/master-schedule-tabs.php | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index b035cfe..6958581 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -48,7 +48,7 @@ // --- set list info key order --- // 2.3.3.8: added for possible info rearrangement -$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'excerpt' ); +$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'excerpt', 'custom' ); $infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); // --- start list schedule output --- @@ -448,6 +448,13 @@ } } + // --- custom info section --- + // 2.3.3.8: allow for custom HTML to be added + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'list' ); + if ( ( '' != $custom ) && is_string( $custom ) ) { + $info['custom'] = $custom; + } + // --- add item info according to key order --- // 2.3.3.8: added for possible order rearrangement foreach ( $infokeys as $infokey ) { diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 097d877..cabdfa5 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -48,7 +48,7 @@ // --- set cell info key order --- // 2.3.3.8: added for possible info rearrangement -$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'excerpt' ); +$infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'excerpt', 'custom' ); $infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); // --- clear floats --- @@ -617,6 +617,13 @@ } } + // --- custom info section --- + // 2.3.3.8: allow for custom HTML to be added + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'table' ); + if ( ( '' != $custom ) && is_string( $custom ) ) { + $info['custom'] = $custom; + } + } // --- add cell info according to key order --- diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 8b044df..78f88c8 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -49,7 +49,7 @@ // --- set cell info key order --- // 2.3.3.8: added for possible info rearrangement -$infokeys = array( 'title', 'hosts', 'times', 'encore', 'file', 'genres' ); +$infokeys = array( 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'custom' ); $infokeys = apply_filters( 'radio_station_schedule_tabs_info_order', $infokeys ); // --- start tabbed schedule output --- @@ -502,6 +502,13 @@ } } + // --- custom info section --- + // 2.3.3.8: allow for custom HTML to be added + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'tabs' ); + if ( ( '' != $custom ) && is_string( $custom ) ) { + $info['custom'] = $custom; + } + // --- left aligned show avatar --- // 2.3.3.8: add image according to position if ( $avatar ) { From 9f0870889733e604932155d9d900912f43bde82f Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 21 Jan 2021 15:40:41 +1000 Subject: [PATCH 176/377] dev update fixes --- includes/post-types-admin.php | 3 ++- includes/shortcodes.php | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 8b6e70a..88e2e30 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1428,7 +1428,8 @@ function radio_station_post_column_data( $column, $post_id ) { unset( $show_ids[$i] ); } } - $data = implode( ',', $show_id ); + // 2.3.3.8: fix to implode show_ids not show_id + $data = implode( ',', $show_ids ); } elseif ( $show_id > 0 ) { $show_ids = array( $show_id ); $data = $show_id; diff --git a/includes/shortcodes.php b/includes/shortcodes.php index dada4da..2871f08 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1737,7 +1737,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- custom HTML section --- // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show['id'] ); + $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show['id'], $atts ); // --- open current show list item --- $output .= '
  • '; @@ -2731,12 +2731,16 @@ function radio_station_current_playlist_shortcode( $atts ) { } } + // --- custom HTML section --- + // 2.3.3.8: added custom HTML section + $html['custom'] = apply_filters( 'radio_station_upcoming_show_custom_display', '', $playlist, $atts ); + // --- filter display section order --- // 2.3.1: added filter for section order display - $order = array( 'tracks', 'link', 'countdown' ); + $order = array( 'tracks', 'link', 'countdown', 'custom' ); $order = apply_filters( 'radio_station_current_playlist_section_order', $order, $atts ); foreach ( $order as $section ) { - if ( isset( $html[$section] ) ) { + if ( isset( $html[$section] ) && ( '' != $html['section'] ) ) { $output .= $html[$section]; } } From 4665dde9af52482e118a15c5efe6302c616b517d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 26 Jan 2021 11:49:01 +1000 Subject: [PATCH 177/377] dev updates #313 --- CHANGELOG.md | 1 + includes/support-functions.php | 12 +++---- radio-station.php | 56 +++++++++++++++++++++-------- readme.txt | 1 + templates/master-schedule-list.php | 7 ++-- templates/master-schedule-table.php | 5 ++- templates/master-schedule-tabs.php | 5 ++- templates/single-show-content.php | 25 +++++++------ 8 files changed, 78 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a65ebd..f169a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added: Show image alignment attribute to Schedule Tabs View * Added: Show Description/Excerpt to Show Data Endpoint (via querystring) * Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Added: Screen Reader text for Show icons on Show Page * Fixed: Display Widget Countdown when no Current Show/Playlist * Fixed: Check for explicit singular.php template usage setting * Fixed: Access to Shows Data via querystring of Show ID/name diff --git a/includes/support-functions.php b/includes/support-functions.php index 5d5604c..3ac552e 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1415,8 +1415,12 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // 2.3.3: set current show to global data // 2.3.4: set previous show shift to global and transient + // 2.3.3.8: move expires declaration earlier + $expires = $shift_end_time - $now - 1; + if ( $expires > 3600 ) { + $expires = 3600; + } if ( !$time ) { - $expires = $shift_end_time - $now - 1; $radio_station_data['current_show'] = $current_show; if ( $prev_shift ) { $prev_show = apply_filters( 'radio_station_previous_show', $prev_shift, $time ); @@ -1431,13 +1435,9 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) set_transient( 'radio_station_previous_show_' . $time, $prev_show, $expires ); } } - $expires = $shift_end_time - $now - 1; - if ( $expires > 3600 ) { - $expires = 3600; - } // 2.3.2: set temporary transient if time is specified - // 2.3.3: remove current show transient (being unreliable) + // 2.3.3: remove current show transient (as being unreliable) /* if ( !$time ) { set_transient( 'radio_station_current_show', $current_show, $expires ); } else { diff --git a/radio-station.php b/radio-station.php index 7d4d8ec..9280ed4 100644 --- a/radio-station.php +++ b/radio-station.php @@ -424,14 +424,28 @@ /* 'player_theme' => array( 'type' => 'select', 'label' => __( 'Default Player Theme', 'radio-station' ), - 'default' => 'light-rounded', + 'default' => 'light', 'options' => array( - 'light-rounded' => 'Light on Dark, Rounded Buttons', 'radio-station' ), - 'light-square' => 'Light on Dark, Square Buttons', 'radio-station' ), - 'dark-rounded' => 'Dark on Light, Rounded Buttons', 'radio-station' ), - 'dark-square' => 'Dark on Light, Square Buttons', 'radio-station' ), + 'light' => 'Light', 'radio-station' ), + 'dark' => 'Dark', 'radio-station' ), ), - 'helper' => __( '', 'radio-station' + 'helper' => __( 'Default Player Controls theme style.', 'radio-station', + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), */ + + // --- Player Buttons --- + /* 'player_buttons' => array( + 'type' => 'select', + 'label' => __( 'Default Player Buttons', 'radio-station' ), + 'default' => 'light', + 'options' => array( + 'circular' => 'Circular Buttons', 'radio-station' ), + 'rounded' => 'Rounded Buttons', 'radio-station' ), + 'square' => 'Square Buttons', 'radio-station' ), + ), + 'helper' => __( 'Default Player Buttons shape style.', 'radio-station', 'tab' => 'player', 'section' => 'basic', 'pro' => false, @@ -481,8 +495,6 @@ 'pro' => true, ), */ - // TODO: additional CSS input field ? - // === Advanced Stream Player === // --- Player Volume --- @@ -549,30 +561,44 @@ 'pro' => true, ), */ + // --- [Pro] Fade In Player Bar --- + /* 'player_fadein' => array( + 'type' => 'number', + 'label' => __( 'Fade In Player Bar', 'radio-station' ), + 'default' => 2500, + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), */ + // --- [Pro] Continuous Playback --- /* 'player_continuous' => array( 'type' => 'checkbox', 'label' => __( 'Continuous Playback', 'radio-station' ), 'default' => 'yes', - 'helper' => __( 'Uninterrupted sitewide bar playback while user is navigating between pages! Requires additional Plugin.', 'radio-station' ), + 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', 'pro' => true, ), */ - // --- [Pro] Fade In Player Bar --- - /* 'player_fadein' => array( + // --- [Pro] Player Page Fade --- + /* 'player_pagefade' => array( 'type' => 'number', 'label' => __( 'Fade In Player Bar', 'radio-station' ), - 'default' => '2500', + 'default' => 2000, 'min' => 0, 'step' => 100, 'max' => 10000, - 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'helper' => __( 'Number of milliseconds over which to fade in new Pages when continuous playback is enabled. Use 0 for instant display.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', 'pro' => true, - ), */ + */ // --- [Pro] Bar Player Text Color --- /* 'player_bar_text' => array( @@ -596,6 +622,8 @@ 'pro' => true, ), */ + // TODO: additional CSS input field ? + // === Master Schedule Page === // --- Schedule Page --- diff --git a/readme.txt b/readme.txt index 8f9c87a..c9fade4 100644 --- a/readme.txt +++ b/readme.txt @@ -208,6 +208,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Added: Show image alignment attribute to Schedule Tabs View * Added: Show Description/Excerpt to Show Data Endpoint (via querystring) * Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Added: Screen Reader text for Show icons on Show Page * Fixed: Display Widget Countdown when no Current Show/Playlist * Fixed: Check for explicit singular.php template usage setting * Fixed: Access to Shows Data via querystring of Show ID/name diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 6958581..48eb5c6 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -52,7 +52,7 @@ $infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); // --- start list schedule output --- -$list .= '
      ' . $newline; +$list = '
        ' . $newline; $tcount = 0; // 2.3.0: loop weekdays instead of legacy master list @@ -385,13 +385,16 @@ // 2.3.2: check disable download meta // 2.3.3: fix to incorrect filter name // 2.3.3.8: add filter for show file link + // 2.3.3.8: add filter for show file anchor $show_file = get_post_meta( $show['id'], 'show_file', true ); $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'list' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { + $anchor = __( 'Audio File', 'radio-station' ); + $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show['id'], 'tabs' ); $file = '' . $newline; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'list' ); diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index cabdfa5..526293a 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -570,6 +570,7 @@ // --- show file --- // 2.3.2: check disable download meta // 2.3.3.8: add filter for show file link + // 2.3.3.8: added filter for show file anchor $show_file = false; if ( $atts['show_file'] ) { $show_file = get_post_meta( $show['id'], 'show_file', true ); @@ -577,9 +578,11 @@ $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'table' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { + $anchor = __( 'Audio File', 'radio-station' ); + $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show['id'], 'tabs' ); $file = '' . $newline; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show['id'], 'table' ); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 78f88c8..bc3cc27 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -465,13 +465,16 @@ // 2.3.2: check show download disable meta // 2.3.3: fix to incorrect filter name // 2.3.3.8: add filter for show file link div + // 2.3.3.8: added filter for show file anchor $show_file = get_post_meta( $show['id'], 'show_file', true ); $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'tabs' ); $disable_download = get_post_meta( $show['id'], 'show_download', true ); if ( $show_file && !empty( $show_file ) && !$disable_download ) { + $anchor = __( 'Audio File', 'radio-station' ); + $anchor = apply_filters( 'radio_station_schedule_show_file_anchor', $anchor, $show['id'], 'tabs' ); $file = '' . $newline; $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'tabs' ); diff --git a/templates/single-show-content.php b/templates/single-show-content.php index d7c44d8..ef91e22 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -85,13 +85,14 @@ // --- show home link icon --- // 2.3.3.4: added filter for title attribute +// 2.3.3.8: added alt text to span for screen readers if ( $show_link ) { $title = __( 'Visit Show Website', 'radio-station' ); $title = apply_filters( 'radio_station_show_website_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_home_icon', $icon, $post_id ); $show_icons['home'] = '
        ' . $newline; - $show_icons['home'] .= '' . $newline; + $show_icons['home'] .= '' . $newline; $show_icons['home'] .= $icon; $show_icons['home'] .= '' . $newline; $show_icons['home'] .= '
        ' . $newline; @@ -99,13 +100,14 @@ // --- phone number icon --- // 2.3.3.6: added show phone icon +// 2.3.3.8: added aria label to link and hidden to span icon if ( $show_phone ) { $title = __( 'Call in Phone Number', 'radio-station' ); $title = apply_filters( 'radio_station_show_phone_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_phone_icon', $icon, $post_id ); $show_icons['phone'] = '' . $newline; @@ -113,13 +115,14 @@ // --- email DJ / host icon --- // 2.3.3.4: added filter for title attribute +// 2.3.3.8: added aria label to link and hidden to span icon if ( $show_email ) { $title = __( 'Email Show Host', 'radio-station' ); $title = apply_filters( 'radio_station_show_email_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_email_icon', $icon, $post_id ); $show_icons['email'] = '' . $newline; @@ -127,14 +130,15 @@ // --- show RSS feed icon --- // 2.3.3.4: added filter for title attribute +// 2.3.3.8: added aria label to link and hidden to span icon if ( $show_rss ) { $feed_url = radio_station_get_show_rss_url( $post_id ); $title = __( 'Show RSS Feed', 'radio-station' ); $title = apply_filters( 'radio_station_show_rss_title', $title, $post_id ); - $icon = '' . $newline; + $icon = '' . $newline; $icon = apply_filters( 'radio_station_show_rss_icon', $icon, $post_id ); $show_icons['rss'] = '' . $newline; @@ -269,12 +273,13 @@ // --- Download Audio Icon --- // 2.3.2: check show download switch + // 2.3.3.8: added aria label to link and hidden to span icon if ( $show_download ) { $title = __( 'Download Latest Broadcast', 'radio-station' ); $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); $image_blocks['player'] .= '' . $newline; } From c694b78d40680faac6d8f0887da942534309679f Mon Sep 17 00:00:00 2001 From: Michael Torbert Date: Mon, 1 Feb 2021 11:32:56 -0500 Subject: [PATCH 178/377] stable tag should be current version issue #314 --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index c9fade4..30c7950 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.6 -Stable tag: trunk +Stable tag: 2.3.3.7 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -365,7 +365,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Fix to not 404 author pages for DJs without blog posts = 2.2.7 = -* Dutch translation added (Thank you to André Dortmont for the file!) +* Dutch translation added (Thank you to André Dortmont for the file!) * Added Tabbed Display for Master Schedule Shortcode (via Tutorial) * Add Show list columns with active, shift, DJs and show image displays * Add Schedule Override list columns with date sorting and filtering From 07d2b7b2b06a8a318b0c971004e493957f5c68da Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 11 Feb 2021 21:44:02 +1000 Subject: [PATCH 179/377] dev updates including #290 --- includes/data-feeds.php | 955 ++++++++++++++++----------------- includes/shortcodes.php | 2 +- includes/support-functions.php | 17 +- loader.php | 55 +- radio-station.php | 233 ++++---- readme.txt | 2 +- 6 files changed, 670 insertions(+), 594 deletions(-) diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 17c68e2..0bb3a93 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -16,6 +16,13 @@ // - Get Broadcast Data // - Get Shows Data // - Get Genres Data +// === Data Endpoints === +// - Station Data Endpoint +// - Broadcast Data Endpoint +// - Schedule Data Endpoint +// - Shows Data Endpoint +// - Genres Data Endpoint +// - Languages Data Endpoint // === REST Routes === // - Register Rest Routes // - Get Route URLs @@ -401,6 +408,304 @@ function radio_station_get_languages_data( $language = false ) { return $languages_data; } +// ---------------------- +// === Data Endpoints === +// ---------------------- + +// --------------------- +// Station Data Endpoint +// --------------------- +function radio_station_station_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + $station = array(); + + // --- broadcast --- + $broadcast = radio_station_get_broadcast_data(); + $station['broadcast'] = $broadcast; + + // --- schedule --- + $schedule = radio_station_get_current_schedule(); + $schedule = radio_station_convert_schedule_shifts( $schedule ); + if ( count( $schedule ) > 0 ) { + $station['schedule'] = $schedule; + } else { + $station['schedule'] = array(); + } + + // --- shows --- + $shows = radio_station_get_shows_data(); + if ( count( $shows ) > 0 ) { + $station['shows'] = $shows; + } else { + $station['shows'] = array(); + } + + // --- genres --- + $genres = radio_station_get_genres_data(); + if ( count( $genres ) > 0 ) { + $station['genres'] = $genres; + } else { + $station['genres'] = array(); + } + + // --- languages --- + $languages = radio_station_get_languages_data(); + if ( count( $languages ) > 0 ) { + $station['languages'] = $languages; + } else { + $station['languages'] = array(); + } + + return $station; +} + +// ----------------------- +// Broadcast Data Endpoint +// ----------------------- +function radio_station_broadcast_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + $broadcast = radio_station_get_broadcast_data(); + if ( RADIO_STATION_DEBUG ) { + echo "Broadcast: " . print_r( $broadcast, true ); + } + return $broadcast; +} + +// ---------------------- +// Schedule Data Endpoint +// ---------------------- +function radio_station_schedule_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + // --- get current schedule --- + $schedule = radio_station_get_current_schedule(); + $schedule = radio_station_convert_schedule_shifts( $schedule ); + + // --- check for weekday query --- + $weekdays = array(); + $weekday = $singular = $multiple = false; + if ( isset( $_GET['weekday'] ) ) { + + $weekday = $_GET['weekday']; + + if ( strstr( $_GET['weekday'], ',' ) ) { + $multiple = true; + $weekdays = explode( ',', $weekday ); + } else { + $singular = true; + $weekdays = array( $weekday ); + } + + // --- remove all shifts not on specified weekdays --- + foreach ( $weekdays as $i => $day ) { + $weekdays[$i] = strtolower( trim( $day ) ); + } + $shiftcount = 0; + if ( count( $schedule ) > 0 ) { + foreach ( $schedule as $day => $shifts ) { + if ( !in_array( strtolower( $day ), $weekdays ) ) { + unset( $schedule[$day] ); + } else { + $shiftcount = $shiftcount + count( $shifts ); + } + } + } + + // --- set no shifts error --- + if ( ( 0 == count( $schedule ) ) || ( 0 == $shiftcount ) ) { + $code = 'no_scheduled_shifts'; + $message = 'No Show shifts were found for the days specified.'; + $error = new WP_Error( $code, $message, array( 'status' => 400 ) ); + } + + } elseif ( isset( $_GET['date'] ) ) { + + // TODO: get schedule for specific date ? + + } else { + + // --- check there are any shifts --- + $shiftcount = 0; + if ( count( $schedule ) > 0 ) { + foreach ( $schedule as $day => $shifts ) { + $shiftcount = $shiftcount + count( $shifts ); + } + } + + // --- set no shifts error --- + if ( ( 0 == count( $schedule ) ) || ( 0 == $shiftcount ) ) { + $code = 'no_schedule'; + $message = 'No Show shifts were found in the Schedule.'; + $error = new WP_Error( $code, $message, array( 'status' => 400 ) ); + } + + } + + // --- maybe set request error --- + if ( isset( $error ) ) { + $schedule = $error; + } + + // --- maybe output debug info --- + if ( RADIO_STATION_DEBUG ) { + echo "Weekday: " . $weekday . PHP_EOL; + echo "Weekdays: " . print_r( $weekdays, true ) . PHP_EOL; + echo "Schedule: " . print_r( $schedule, true ); + } + + return $schedule; +} + +// ------------------- +// Shows Data Endpoint +// ------------------- +function radio_station_shows_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + // --- get show query parameter --- + $show = $singular = $multiple = false; + if ( isset( $_GET['show'] ) ) { + $show = $_GET['show']; + if ( strstr( $show, ',' ) ) { + $multiple = true; + } else { + $singular = true; + } + } + + // --- get show list data --- + $shows = radio_station_get_shows_data( $show ); + + // --- maybe set request error --- + if ( 0 === count( $shows ) ) { + if ( $singular ) { + $code = 'show_not_found'; + $message = 'Requested Show was not found.'; + } elseif ( $multiple ) { + $code = 'shows_not_found'; + $message = 'No Requested Shows were found.'; + } else { + $code = 'no_shows'; + $message = 'No Shows were found.'; + } + $shows = new WP_Error( $code, $message, array( 'status' => 400 ) ); + } + + // --- maybe output debug info --- + if ( RADIO_STATION_DEBUG ) { + echo "Show: " . $show . PHP_EOL; + echo "Shows: " . print_r( $shows, true ); + } + + return $shows; +} + +// -------------------- +// Genres Data Endpoint +// -------------------- +function radio_station_genres_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + // --- get genre query parameter --- + $genre = $singular = $multiple = false; + if ( isset( $_GET['genre'] ) ) { + $genre = $_GET['genre']; + if ( strstr( $genre, ',' ) ) { + $multiple = true; + } else { + $singular = true; + } + } + + // --- get genre list data --- + $genres = radio_station_get_genres_data( $genre ); + + // --- maybe set request error --- + if ( 0 === count( $genres ) ) { + if ( $singular ) { + $code = 'genre_not_found'; + $message = 'Requested Genre was not found.'; + } elseif ( $multiple ) { + $code = 'genres_not_found'; + $message = 'No Requested Genres were found.'; + } else { + $code = 'no_genres'; + $message = 'No Genres were found.'; + } + $genres = new WP_Error( $code, $message, array( 'status' => 400 ) ); + } + + // --- maybe output debug info --- + if ( RADIO_STATION_DEBUG ) { + echo "Genre: " . $genre . PHP_EOL; + echo "Genres: " . print_r( $genres, true ); + } + + return $genres; +} + +// ----------------------- +// Languages Data Endpoint +// ----------------------- +function radio_station_languages_endpoint() { + + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + } + + // --- get language query parameter --- + $language = $singular = $multiple = false; + if ( isset( $_GET['language'] ) ) { + $language = $_GET['language']; + if ( strstr( $language, ',' ) ) { + $multiple = true; + } else { + $singular = true; + } + } + + // --- get language list data --- + $languages = radio_station_get_languages_data( $language ); + if ( RADIO_STATION_DEBUG ) { + echo "Language: " . $language . PHP_EOL; + echo "Languages: " . print_r( $languages, true ); + } + + // --- maybe return route error --- + if ( 0 === count( $languages ) ) { + if ( $singular ) { + $code = 'language_not_found'; + $message = 'Requested Language was not found.'; + } elseif ( $multiple ) { + $code = 'languages_not_found'; + $message = 'No Requested Languages were found.'; + } else { + $code = 'no_languages'; + $message = 'No Languages were found.'; + } + $languages = new WP_Error( $code, $message, array( 'status' => 400 ) ); + } + + return $languages; +} + // ------------------- // === REST Routes === @@ -551,22 +856,19 @@ function radio_station_route_radio( $response, $handler, $request ) { // (combined data from all routes) function radio_station_route_station( $request ) { - $station = array(); - $broadcast = radio_station_get_broadcast_data(); - $station['broadcast'] = $broadcast; - $schedule = radio_station_get_current_schedule(); - $schedule = radio_station_convert_schedule_shifts( $schedule ); - $station['schedule'] = $schedule; - $shows_data = radio_station_get_shows_data(); - $station['shows'] = $shows_data; - $genres_data = radio_station_get_genres_data(); - $station['genres'] = $genres_data; - $languages_data = radio_station_get_languages_data(); - $station['languages'] = $languages_data; + // --- get station endpoint data --- + $station = radio_station_station_endpoint(); $station = radio_station_add_station_data( $station ); $station['endpoints'] = radio_station_get_route_urls(); $station = apply_filters( 'radio_station_route_station', $station, $request ); + // --- maybe output debug display --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $station, true ) . PHP_EOL; + exit; + } + return $station; } @@ -575,22 +877,20 @@ function radio_station_route_station( $request ) { // ----------------------- function radio_station_route_broadcast( $request ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get broadcast data --- - $broadcast_data = radio_station_get_broadcast_data(); - if ( RADIO_STATION_DEBUG ) { - echo "Broadcast: " . print_r( $broadcast_data, true ); - } - - // --- set broadcast output --- - $broadcast = array( 'broadcast' => $broadcast_data ); + // --- get broadcast endpoint data --- + $broadcast = radio_station_broadcast_endpoint(); + $broadcast = array( 'broadcast' => $broadcast ); $broadcast = radio_station_add_station_data( $broadcast ); $broadcast['endpoints'] = radio_station_get_route_urls(); $broadcast = apply_filters( 'radio_station_route_broadcast', $broadcast, $request ); + // --- maybe output debug display --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $broadcast, true ) . PHP_EOL; + exit; + } + return $broadcast; } @@ -599,54 +899,20 @@ function radio_station_route_broadcast( $request ) { // ------------------- function radio_station_route_schedule( $request ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get current schedule --- - $schedule_data = radio_station_get_current_schedule(); - $schedule_data = radio_station_convert_schedule_shifts( $schedule_data ); - - // --- check for weekday query --- - $weekdays = array(); - $weekday = $singular = $multiple = false; - if ( isset( $_GET['weekday'] ) ) { - - $weekday = $_GET['weekday']; - - if ( strstr( $_GET['weekday'], ',' ) ) { - $multiple = true; - $weekdays = explode( ',', $weekday ); - } else { - $singular = true; - $weekdays = array( $weekday ); - } - - // --- remove all shifts not on specified weekdays --- - foreach ( $weekdays as $i => $day ) { - $weekdays[$i] = strtolower( trim( $day ) ); - } - if ( count( $schedule_data ) > 0 ) { - foreach ( $schedule_data as $day => $shifts ) { - if ( !in_array( strtolower( $day ), $weekdays ) ) { - unset( $schedule_data[$day] ); - } - } - } - } - - if ( RADIO_STATION_DEBUG ) { - echo "Weekday: " . $weekday . PHP_EOL; - echo "Weekdays: " . print_r( $weekdays, true ) . PHP_EOL; - echo "Schedule: " . print_r( $schedule_data, true ); - } - - // --- set schedule output --- - $schedule = array( 'schedule' => $schedule_data ); + // --- get schedule endpoint data --- + $schedule = radio_station_schedule_endpoint(); + $schedule = array( 'schedule' => $schedule ); $schedule = radio_station_add_station_data( $schedule ); $schedule['endpoints'] = radio_station_get_route_urls(); $schedule = apply_filters( 'radio_station_route_schedule', $schedule, $request ); + // --- maybe output debug display --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $schedule, true ) . PHP_EOL; + exit; + } + return $schedule; } @@ -655,102 +921,46 @@ function radio_station_route_schedule( $request ) { // --------------- function radio_station_route_shows( $request ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get show query parameter --- - $show = $singular = $multiple = false; - if ( isset( $_GET['show'] ) ) { - $show = $_GET['show']; - if ( strstr( $show, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } + // --- get shows endpoint data --- + $show_list = radio_station_shows_endpoint(); + if ( !is_wp_error( $show_list ) ) { + $show_list = array( 'shows' => $show_list ); + $show_list = radio_station_add_station_data( $show_list ); + $show_list['endpoints'] = radio_station_get_route_urls(); } - - // --- get show list data --- - $shows_data = radio_station_get_shows_data( $show ); - if ( RADIO_STATION_DEBUG ) { - echo "Show: " . $show . PHP_EOL; - echo "Shows: " . print_r( $shows_data, true ); - } - - // --- maybe return route error --- - if ( count( $shows_data ) === 0 ) { - if ( $singular ) { - $code = 'show_not_found'; - $message = 'Requested Show was not found.'; - } elseif ( $multiple ) { - $code = 'shows_not_found'; - $message = 'No Requested Shows were found.'; - } else { - $code = 'no_shows'; - $message = 'No Shows were found.'; - } - - return new WP_Error( $code, $message, array( 'status' => 404 ) ); - } - - // --- return show list --- - $show_list = array( 'shows' => $shows_data ); - $show_list = radio_station_add_station_data( $show_list ); - $show_list['endpoints'] = radio_station_get_route_urls(); $show_list = apply_filters( 'radio_station_route_shows', $show_list, $request ); - return $show_list; -} - -// ---------------- -// Genre List Route -// ---------------- -function radio_station_route_genres( $request ) { - - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get genre query parameter --- - $genre = $singular = $multiple = false; - if ( isset( $_GET['genre'] ) ) { - $genre = $_GET['genre']; - if ( strstr( $genre, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } - } - - // --- get genre list data --- - $genres_data = radio_station_get_genres_data( $genre ); + // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - echo "Genre: " . $genre . PHP_EOL; - echo "Genres: " . print_r( $genres_data, true ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $show_list, true ) . PHP_EOL; + exit; } - // --- maybe return route error --- - if ( count( $genres_data ) === 0 ) { - if ( $singular ) { - $code = 'genre_not_found'; - $message = 'Requested Genre was not found.'; - } elseif ( $multiple ) { - $code = 'genres_not_found'; - $message = 'No Requested Genres were found.'; - } else { - $code = 'no_genres'; - $message = 'No Genres were found.'; - } + return $show_list; +} - return new WP_Error( $code, $message, array( 'status' => 404 ) ); - } +// ---------------- +// Genre List Route +// ---------------- +function radio_station_route_genres( $request ) { // --- return genre list --- - $genre_list = array( 'genres' => $genres_data ); - $genre_list = radio_station_add_station_data( $genre_list ); - $genre_list['endpoints'] = radio_station_get_route_urls(); + $genre_list = radio_station_genres_endpoint(); + if ( !is_wp_error( $genre_list ) ) { + $genre_list = array( 'genres' => $genre_list ); + $genre_list = radio_station_add_station_data( $genre_list ); + $genre_list['endpoints'] = radio_station_get_route_urls(); + } $genre_list = apply_filters( 'radio_station_route_genres', $genre_list, $request ); + // --- maybe output debug display --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $genre_list, true ) . PHP_EOL; + exit; + } + return $genre_list; } @@ -759,64 +969,25 @@ function radio_station_route_genres( $request ) { // ------------------- function radio_station_route_languages( $request ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get language query parameter --- - $language = $singular = $multiple = false; - if ( isset( $_GET['language'] ) ) { - $language = $_GET['language']; - if ( strstr( $language, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } + // --- return language list --- + $language_list = radio_station_languages_endpoint(); + if ( !is_wp_error( $language_list ) ) { + $language_list = array( 'languages' => $language_list ); + $language_list = radio_station_add_station_data( $language_list ); + $language_list['endpoints'] = radio_station_get_route_urls(); } + $language_list = apply_filters( 'radio_station_route_languages', $language_list, $request ); - // --- get language list data --- - $languages_data = radio_station_get_languages_data( $language ); + // --- maybe output debug display --- if ( RADIO_STATION_DEBUG ) { - echo "Language: " . $language . PHP_EOL; - echo "Languages: " . print_r( $languages_data, true ); - } - - // --- maybe return route error --- - if ( count( $languages_data ) === 0 ) { - if ( $singular ) { - $code = 'language_not_found'; - $message = 'Requested Language was not found.'; - } elseif ( $multiple ) { - $code = 'languages_not_found'; - $message = 'No Requested Languages were found.'; - } else { - $code = 'no_languages'; - $message = 'No Languages were found.'; - } - - return new WP_Error( $code, $message, array( 'status' => 404 ) ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $language_list, true ) . PHP_EOL; + exit; } - - // --- return language list --- - $language_list = array( 'languages' => $languages_data ); - $language_list = radio_station_add_station_data( $language_list ); - $language_list['endpoints'] = radio_station_get_route_urls(); - $language_list = apply_filters( 'radio_station_route_languages', $language_list, $request ); - + return $language_list; } -// ------------------------ -// Check for Genre Callback -// ------------------------ -// function radio_station_check_genre( $genre ) { -// $term = get_term_by( 'slug', $genre, RADIO_STATION_GENRES_SLUG ); -// if ( $term ) {return true;} -// $term = get_term_by( 'name', $genre, RADIO_STATION_GENRES_SLUG ); -// if ( $term ) {return true;} -// return false; -// } - // ============= // --- Feeds --- @@ -957,10 +1128,6 @@ function radio_station_get_feed_urls() { // -------------------- function radio_station_feed_radio( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - $base = apply_filters( 'radio_station_feed_slug_base', 'radio' ); $radio = array( 'success' => true ); $radio['namespace'] = $base; @@ -973,8 +1140,8 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { $routes[$key] = array( 'namespace' => $base, 'methods' => array ( 'GET' ), - // 'endpoints' => array(), - // 'url' => $url, + // 'endpoints' => array(), + // 'url' => $url, '_links' => array( 'self' => $url, ), @@ -983,15 +1150,15 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { $radio['routes'] = $routes; $radio = apply_filters( 'radio_station_feed_radio', $radio ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $radio ); - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + header( 'Content-Type: text/plain' ); + echo "Output: " . print_r( $radio, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $radio ); - // } + } } // ------------ @@ -1000,31 +1167,21 @@ function radio_station_feed_radio( $comment_feed, $feed_name ) { // (combined data from all feeds) function radio_station_feed_station( $comment_feed, $feed_name ) { - $data = array(); - $broadcast = radio_station_get_broadcast_data(); - $station['broadcast'] = $broadcast; - $schedule = radio_station_get_current_schedule(); - $schedule = radio_station_convert_schedule_shifts( $schedule ); - $station['schedule'] = $schedule; - $shows_data = radio_station_get_shows_data(); - $station['shows'] = $shows_data; - $genres_data = radio_station_get_genres_data(); - $station['genres'] = $genres_data; - $languages_data = radio_station_get_languages_data(); - $station['languages'] = $languages_data; + // --- get station endpoint data --- + $station = radio_station_station_endpoint(); $station = radio_station_add_station_data( $station ); $station['endpoints'] = radio_station_get_feed_urls(); $station = apply_filters( 'radio_station_feed_station', $station ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $station ); - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $station, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $station ); - // } + } } // ---------------------- @@ -1032,29 +1189,22 @@ function radio_station_feed_station( $comment_feed, $feed_name ) { // ---------------------- function radio_station_feed_broadcast( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - $broadcast = radio_station_get_broadcast_data(); - if ( RADIO_STATION_DEBUG ) { - echo "Broadcast: " . print_r( $broadcast, true ); - } - - $broadcast = array( 'broadcast', $broadcast ); + // --- get broadcast endpoint data --- + // 2.3.3.8: fix to broadcast array nesting + $broadcast = radio_station_broadcast_endpoint(); + $broadcast = array( 'broadcast' => $broadcast ); $broadcast = radio_station_add_station_data( $broadcast ); $broadcast['endpoints'] = radio_station_get_feed_urls(); $broadcast = apply_filters( 'radio_station_feed_broadcast', $broadcast ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $broadcast ); - // } else { - header( 'Content-Type: application/json' ); + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $broadcast, true ) . PHP_EOL; + } else { + header( 'Content-Type: application/json' ); echo json_encode( $broadcast ); - // } + } } // ------------------ @@ -1062,63 +1212,22 @@ function radio_station_feed_broadcast( $comment_feed, $feed_name ) { // ------------------ function radio_station_feed_schedule( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- get current schedule --- - $schedule_data = radio_station_get_current_schedule(); - $schedule_data = radio_station_convert_schedule_shifts( $schedule_data ); - - // --- check for weekday query --- - $weekdays = array(); - $weekday = $singular = $multiple = false; - if ( isset( $_GET['weekday'] ) ) { - - $weekday = $_GET['weekday']; - - if ( strstr( $_GET['weekday'], ',' ) ) { - $multiple = true; - $weekdays = explode( ',', $weekday ); - } else { - $singular = true; - $weekdays = array( $weekday ); - } - - // --- remove all shifts not on specified weekdays --- - foreach ( $weekdays as $i => $day ) { - $weekdays[$i] = strtolower( trim( $day ) ); - } - if ( count( $schedule_data ) > 0 ) { - foreach ( $schedule_data as $day => $shifts ) { - if ( !in_array( strtolower( $day ), $weekdays ) ) { - unset( $schedule_data[$day] ); - } - } - } - } - - if ( RADIO_STATION_DEBUG ) { - echo "Weekday: " . $weekday . PHP_EOL; - echo "Weekdays: " . print_r( $weekdays, true ) . PHP_EOL; - echo "Schedule: " . print_r( $schedule_data, true ); - } - - // --- set schedule output --- - $schedule = array( 'schedule' => $schedule_data ); + // --- get schedule endpoint data --- + $schedule = radio_station_schedule_endpoint(); + $schedule = array( 'schedule' => $schedule ); $schedule = radio_station_add_station_data( $schedule ); $schedule['endpoints'] = radio_station_get_feed_urls(); $schedule = apply_filters( 'radio_station_feed_schedule', $schedule ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $schedule ); - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $schedule, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $schedule ); - // } + } } // -------------- @@ -1126,58 +1235,26 @@ function radio_station_feed_schedule( $comment_feed, $feed_name ) { // -------------- function radio_station_feed_shows( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- check for show query --- - $show = $singular = $multiple = false; - if ( isset( $_GET['show'] ) ) { - $show = $_GET['show']; - if ( strstr( $show, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } - } - - // --- get show list data --- - $shows_data = radio_station_get_shows_data( $show ); - - // --- maybe output feed error message --- - if ( count( $shows_data ) === 0 ) { - if ( $singular ) { - $details = __( 'Requested Show was not found.', 'radio-station' ); - } elseif ( $multiple ) { - $details = __( 'No Requested Shows were found.', 'radio-station' ); - } else { - $details = __( 'No Shows were found.', 'radio-station' ); - } - radio_station_feed_not_found( $details ); - - return; - } - - $show_list = array( 'shows' => $shows_data ); - if ( RADIO_STATION_DEBUG ) { - echo "Show: " . $show . PHP_EOL; - echo "Shows: " . print_r( $show_list, true ); + // --- get shows data endpoint --- + $show_list = radio_station_shows_endpoint(); + if ( is_wp_error( $show_list ) ) { + $show_list = radio_station_feed_not_found( $show_list ); + } else { + $show_list = array( 'shows' => $show_list ); + $show_list = radio_station_add_station_data( $show_list ); + $show_list['endpoints'] = radio_station_get_feed_urls(); } - - // --- output encoded show list --- - $show_list = radio_station_add_station_data( $show_list ); - $show_list['endpoints'] = radio_station_get_feed_urls(); $show_list = apply_filters( 'radio_station_feed_shows', $show_list ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // echo radio_station_format_xml( $show_list ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $show_list, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $show_list ); - // } + } } // --------------- @@ -1185,58 +1262,27 @@ function radio_station_feed_shows( $comment_feed, $feed_name ) { // --------------- function radio_station_feed_genres( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- check for genre query --- - $genre = $singular = $multiple = false; - if ( isset( $_GET['genre'] ) ) { - $genre = $_GET['genre']; - if ( strstr( $genre, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } - } - - // --- get genre list data --- - $genres_data = radio_station_get_genres_data( $genre ); - - // --- maybe output feed error message --- - if ( count( $genres_data ) === 0 ) { - if ( $singular ) { - $details = __( 'Requested Genre was not found.', 'radio-station' ); - } elseif ( $multiple ) { - $details = __( 'No Requested Genres were found.', 'radio-station' ); - } else { - $details = __( 'No Genres were found.', 'radio-station' ); - } - radio_station_feed_not_found( $details ); - - return; - } - - // --- output encoded genre list --- - $genre_list = array( 'genres' => $genres_data ); - if ( RADIO_STATION_DEBUG ) { - echo "Genre: " . $genre . PHP_EOL; - echo "Genres: " . print_r( $genre_list, true ); + // --- get genres data endpoint --- + $genre_list = radio_station_genres_endpoint(); + if ( is_wp_error( $genre_list ) ) { + $genre_list = radio_station_feed_not_found( $genre_list ); + } else { + $genre_list = array( 'genres' => $genre_list ); + $genre_list = radio_station_add_station_data( $genre_list ); + $genre_list['endpoints'] = radio_station_get_feed_urls(); } - - $genre_list = radio_station_add_station_data( $genre_list ); - $genre_list['endpoints'] = radio_station_get_feed_urls(); $genre_list = apply_filters( 'radio_station_feed_genres', $genre_list ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $genre_list ); - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $genre_list, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $genre_list ); - // } + } + } // ------------------- @@ -1244,122 +1290,55 @@ function radio_station_feed_genres( $comment_feed, $feed_name ) { // ------------------- function radio_station_feed_languages( $comment_feed, $feed_name ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); - } - - // --- check for single language query --- - $language = $singular = $multiple = false; - if ( isset( $_GET['language'] ) ) { - $language = $_GET['language']; - if ( strstr( $language, ',' ) ) { - $multiple = true; - } else { - $singular = true; - } - } - - // --- get genre list data --- - $languages_data = radio_station_get_languages_data( $language ); - - // --- maybe output feed error message --- - if ( count( $languages_data ) === 0 ) { - if ( $singular ) { - $details = __( 'Requested Language was not found.', 'radio-station' ); - } elseif ( $multiple ) { - $details = __( 'No Requested Languages were found.', 'radio-station' ); - } else { - $details = __( 'No Languages were found.', 'radio-station' ); - } - radio_station_feed_not_found( $details ); - - return; - } - - // --- output encoded language list --- - $language_list = array( 'languages' => $languages_data ); - if ( RADIO_STATION_DEBUG ) { - echo "Language: " . $language; - echo "Languages: " . print_r( $language_list, true ); + $language_list = radio_station_languages_endpoint(); + if ( is_wp_error( $language_list ) ) { + $language_list = radio_station_feed_not_found( $language_list ); + } else { + $language_list = array( 'languages' => $language_list ); + $language_list = radio_station_add_station_data( $language_list ); + $language_list['endpoints'] = radio_station_get_feed_urls(); } - - // --- set languages list output --- - $language_list = radio_station_add_station_data( $language_list ); - $language_list['endpoints'] = radio_station_get_feed_urls(); $language_list = apply_filters( 'radio_station_feed_languages', $language_list ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $language_list ); - // } else { + // --- output debug display or feed data --- + if ( RADIO_STATION_DEBUG ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo "Output: " . print_r( $language_list, true ) . PHP_EOL; + } else { header( 'Content-Type: application/json' ); // phpcs:ignore WordPress.Security.OutputNotEscaped echo json_encode( $language_list ); - // } + } } + // -------------------- // Not Found Feed Error // -------------------- -function radio_station_feed_not_found( $details ) { +// 2.3.3.8: reformat WP Error instead of using message only +function radio_station_feed_not_found( $error ) { - if ( RADIO_STATION_DEBUG ) { - header( 'Content-Type: text/plain' ); + if ( is_wp_error( $error ) ) { + $code = $error->get_error_code(); + $message = $error->get_error_message(); + } else { + $code = str_replace( ' ', '-', strtolower( $error ) ); + $message = $error; } + // 2.3.3.8: change status code to 400 bad request from 404 not found $error = array( 'success' => false, 'errors' => array( - 'status' => 404, - 'code' => 404, - 'title' => __( 'Error 404 Not Found', 'radio-station' ), + 'status' => 400, + 'code' => $code, + 'title' => __( 'Error 400 No Requested Data', 'radio-station' ), 'message' => __( 'The requested data could not be found.', 'radio-station' ), - 'detail' => $details, + 'detail' => $message, ), ); - // if ( isset( $_GET['format'] ) && ( 'xml' == $_GET['format'] ) ) { - // header( 'Content-Type: application/rss+xml' ); - // // phpcs:ignore WordPress.Security.OutputNotEscaped - // echo radio_station_format_xml( $error ); - // } else { - header( 'Content-Type: application/json' ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo json_encode( $error ); - // } -} - -// ------------------ -// Format Data to XML -// ------------------ -function radio_station_format_xml( $data ) { - - $xml = new SimpleXMLElement( '' ); - $xml = radio_station_array_to_xml( $xml, $data ); - $output = $xml->asXML(); - - $dom = new DOMDocument(); - // $dom->formatOutput = true; - $dom->loadXML( $output ); - $output = $dom->saveXML(); - - return $output; + return $error; } -// -------------------- -// Convert Array to XML -// -------------------- -// TODO: fix unworking array to XML conversion? -function radio_station_array_to_xml( SimpleXMLElement $object, $data ) { - - foreach ( $data as $key => $value ) { - if ( is_array( $value ) ) { - $newobject = $object->addChild( $key ); - radio_station_array_to_xml( $newobject, $value ); - } else { - $object->addChild( $key, htmlspecialchars( $value ) ); - } - } -} diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 2871f08..86b5c2c 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -2740,7 +2740,7 @@ function radio_station_current_playlist_shortcode( $atts ) { $order = array( 'tracks', 'link', 'countdown', 'custom' ); $order = apply_filters( 'radio_station_current_playlist_section_order', $order, $atts ); foreach ( $order as $section ) { - if ( isset( $html[$section] ) && ( '' != $html['section'] ) ) { + if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { $output .= $html[$section]; } } diff --git a/includes/support-functions.php b/includes/support-functions.php index 3ac552e..aebb311 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1161,7 +1161,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $override_start_time = radio_station_to_time( $date . ' ' . $override_start ); $override_end_time = radio_station_to_time( $date . ' ' . $override_end ); if ( isset( $override['split'] ) && $override['split'] && ( '11:59:59 pm' == $override['end'] ) ) { - $override_end_time = $override_end_time + 1; + // 2.3.3.8: fix to add 60 seconds instead of 1 + $override_end_time = $override_end_time + 60; } // 2.3.2: fix for non-split overrides ending on midnight if ( $override_end_time < $override_start_time ) { @@ -1181,7 +1182,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $start_time = radio_station_to_time( $date . ' ' . $shift_start ); $end_time = radio_station_to_time( $date . ' ' . $shift_end ); if ( isset( $shift['split'] ) && $shift['split'] && ( '11:59:59 pm' == $shift['end'] ) ) { - $end_time = $end_time + 1; + // 2.3.3.8: fix to add 60 seconds instead of 1 + $end_time = $end_time + 60; } // 2.3.2: fix for non-split shifts ending on midnight if ( $end_time < $start_time ) { @@ -1219,15 +1221,15 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } // --- add the override if not already added --- - if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { - // 2.3.1: track done overrides + // 2.3.3.8: removed adding of overrides here + /* if ( !in_array( $override['date'] . '--' . $i, $done_overrides ) ) { $done_overrides[] = $override['date'] . '--' . $i; if ( $day == $debugday ) { $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; } $shifts[$override['start']] = $override; $show_shifts[$day] = $shifts; - } + } */ } elseif ( $override_start_time == $start_time ) { @@ -1333,8 +1335,11 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( $date == $override['date'] ) { // 2.3.3.7: remove check if override already done // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { - $show_shifts[$day][$override['start']] = $override; // $done_overrides[] = $date . '--' . $i; + $show_shifts[$day][$override['start']] = $override; + if ( $day == $debugday ) { + $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; + } // } } } diff --git a/loader.php b/loader.php index a280b56..e08123b 100644 --- a/loader.php +++ b/loader.php @@ -707,7 +707,7 @@ public function update_settings() { // --- color or color alpha setting --- // 1.1.7: added color picker value saving - // TODO: validate this setting ? + // TODO: maybe validate this setting ? $settings[$key] = $posted; } @@ -717,7 +717,12 @@ public function update_settings() { if ( $newsettings ) { echo '(to-validate) ' . print_r( $newsettings, true ) . '
        '; } else { - echo '(validated) ' . print_r( $settings[$key], true ) . '
        '; + // 1.1.7 handle if (new) key not set yet + if ( isset( $settings[$key] ) ) { + echo '(validated) ' . print_r( $settings[$key], true ) . '
        '; + } else { + echo 'No setting yet for key ' . $key . '
        '; + } } } @@ -783,6 +788,13 @@ public function update_settings() { $settings = call_user_func( $funcname, $settings ); } + // --- output new settings --- + if ( $this->debug ) { + echo "
        All New Settings:
        "; + print_r( $settings ); + echo "

        "; + } + if ( $settings && is_array( $settings ) ) { // --- loop default keys to remove others --- @@ -908,11 +920,18 @@ public function validate_setting( $posted, $valid, $args ) { // 1.0.4: added validated URL option // 1.0.6: fix to posted variable type (vposted) // 1.0.9: remove check for http prefix to allow other protocols + // 1.1.7: use FILTER_SANITIZE_URL not FILTER_SANITIZE_STRING $posted = trim( $posted ); - $url = filter_var( $posted, FILTER_SANITIZE_STRING ); - if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) { - $posted = ''; + $posted = filter_var( $posted, FILTER_SANITIZE_URL ); + + // 1.1.7: remove FILTER_VALIDATE_URL check - not working!? + if ( $this->debug ) { + $check = filter_var( $url, FILTER_VALIDATE_URL ); + echo 'Validated URL: ' . print_r( $check, true ) . '
        '; } + // if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) { + // $posted = ''; + // } return $posted; } elseif ( in_array( $valid, array( 'EMAIL', 'EMAILS' ) ) ) { @@ -1889,6 +1908,13 @@ public function settings_table() { $defaults = $this->default_settings(); $settings = $this->get_settings( false ); + // --- output saved settings --- + if ( $this->debug ) { + echo "
        Saved Settings:
        "; + print_r( $settings ); + echo "

        "; + } + // --- get option tabs and sections --- $tabs = $this->tabs; $sections = $this->sections; @@ -2400,7 +2426,11 @@ public function setting_row( $option ) { // --- toggle --- // 1.0.9: add toggle input (styled checkbox) + // 1.1.7: set default option value if not set $checked = ''; + if ( !isset( $option['value'] ) ) { + $option['value'] = '1'; + } if ( $setting == $option['value'] ) { $checked = ' checked="checked"'; } @@ -2415,7 +2445,11 @@ public function setting_row( $option ) { } elseif ( 'checkbox' == $type ) { // --- checkbox --- + // 1.1.7: set default option value if not set $checked = ''; + if ( !isset( $option['value'] ) ) { + $option['value'] = '1'; + } if ( $setting == $option['value'] ) { $checked = ' checked="checked"'; } @@ -2509,7 +2543,8 @@ public function setting_row( $option ) { } else { $placeholder = ''; } - $row .= ''; + // 1.1.7: fix to attribute quoting output + $row .= ''; if ( isset( $option['suffix'] ) ) { $row .= ' ' . $option['suffix']; } @@ -2553,11 +2588,12 @@ public function setting_row( $option ) { } else { $step = 1; } + // 1.1.7: remove esc_js from onclick attributes $onclickup = "settings_number_step('up', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");"; $onclickdown = "settings_number_step('down', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");"; - $row .= ''; + $row .= ''; $row .= ''; - $row .= ''; + $row .= ''; if ( isset( $option['suffix'] ) ) { $row .= ' ' . $option['suffix']; } @@ -3023,6 +3059,9 @@ function radio_station_settings_row( $option, $setting ) { // - added media library upload image field type // - added color picker and color picker alpha field types // - automatically remove unused settings tabs +// - fix to text field attribute quoting +// - fix to not escape number step button function +// - remove FILTER_VALIDATE_URL from URL saving (not working) // == 1.1.6 == // - added phone number character validation diff --git a/radio-station.php b/radio-station.php index 9280ed4..fd23203 100644 --- a/radio-station.php +++ b/radio-station.php @@ -137,13 +137,6 @@ require RADIO_STATION_DIR . '/includes/class-current-playlist-widget.php'; require RADIO_STATION_DIR . '/includes/class-radio-clock-widget.php'; -// --- Player --- -// 2.3.1.1: load radio player prototype (if present) -$player_file = RADIO_STATION_DIR . '/player/radio-player.php'; -if ( file_exists( $player_file ) ) { - require $player_file; -} - // --- Feature Development --- // 2.3.0: add feature branch development includes // 2.3.1: added radio player widget file @@ -155,6 +148,13 @@ } } +// --- Player --- +// 2.3.1.1: load radio player prototype (if present) +$player_file = RADIO_STATION_DIR . '/player/radio-player.php'; +if ( file_exists( $player_file ) ) { + require $player_file; +} + // --------------------------- // Plugin Options and Defaults @@ -202,7 +202,7 @@ // --- Fallback Stream Format --- 'fallback_format' => array( - 'type' => 'text', + 'type' => 'select', 'options' => $formats, 'label' => __( 'Fallback Format', 'radio-station' ), 'default' => 'ogg', @@ -224,6 +224,17 @@ // === Station === + // --- Station Title --- + // 2.3.3.8: added station title field + 'station_title' => array( + 'type' => 'text', + 'label' => __( 'Station Title', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + // --- Station Image --- // 2.3.3.8: added station logo image field 'station_image' => array( @@ -383,29 +394,31 @@ // TODO: add note about these defaults being overrideable in widgets // --- Player Title --- - /* 'player_title' => array ( - 'type' => 'text', - 'label' => __( 'Player Title Text', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Default Text to display along with Player.', 'radio-station' ), + 'player_title' => array ( + 'type' => 'checkbox', + 'label' => __( 'Display Station Title', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', 'pro' => false, - ), */ + ), // --- Player Image --- - /* 'player_image' => array( + 'player_image' => array( 'type' => 'checkbox', 'label' => __( 'Display Station Image', 'radio-station' ), 'default' => 'yes', - 'helper' => __( 'Display your Radio Station logo image in Player by default.', 'radio-station' ), + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', 'pro' => false, - ), */ + ), // --- Player Script --- - /* 'player_script' => array( + 'player_script' => array( 'type' => 'select', 'label' => __( 'Player Script', 'radio-station' ), 'default' => 'amplitude', @@ -418,87 +431,101 @@ 'tab' => 'player', 'section' => 'basic', 'pro' => false, - ), */ + ), // --- Player Theme --- - /* 'player_theme' => array( - 'type' => 'select', - 'label' => __( 'Default Player Theme', 'radio-station' ), - 'default' => 'light', - 'options' => array( - 'light' => 'Light', 'radio-station' ), - 'dark' => 'Dark', 'radio-station' ), - ), - 'helper' => __( 'Default Player Controls theme style.', 'radio-station', - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), */ + 'player_theme' => array( + 'type' => 'select', + 'label' => __( 'Default Player Theme', 'radio-station' ), + 'default' => 'light', + 'options' => array( + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), // --- Player Buttons --- - /* 'player_buttons' => array( - 'type' => 'select', - 'label' => __( 'Default Player Buttons', 'radio-station' ), - 'default' => 'light', - 'options' => array( - 'circular' => 'Circular Buttons', 'radio-station' ), - 'rounded' => 'Rounded Buttons', 'radio-station' ), - 'square' => 'Square Buttons', 'radio-station' ), - ), - 'helper' => __( 'Default Player Buttons shape style.', 'radio-station', - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), */ + 'player_buttons' => array( + 'type' => 'select', + 'label' => __( 'Default Player Buttons', 'radio-station' ), + 'default' => 'rounded', + 'options' => array( + 'circular' => __( 'Circular Buttons', 'radio-station' ), + 'rounded' => __( 'Rounded Buttons', 'radio-station' ), + 'square' => __( 'Square Buttons', 'radio-station' ), + ), + 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + // --- Player Debug Mode --- + 'player_debug' => array( + 'type' => 'checkbox', + 'label' => __( 'Player Debug Mode', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Output player debug information in browser console.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // === Player Colours === + // --- [Pro] Playing Highlight Color --- - /* 'playing_button_color' => array( + 'player_playing_color' => array( 'type' => 'color', 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), - 'default' => '', + 'default' => '#70E070', 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), 'tab' => 'player', 'section' => 'colors', 'pro' => true, - ), */ + ), // --- [Pro] Control Icons Highlight Color --- - /* 'control_buttons_color' => array( + 'player_buttons_color' => array( 'type' => 'color', 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), - 'default' => '', + 'default' => '#00A0E0', 'helper' => __( 'Default highlight color to use for Control buttons when active.', 'radio-station' ), 'tab' => 'player', 'section' => 'colors', 'pro' => true, - ), */ + ), // --- [Pro] Volume Knob Color --- - /* 'volume_thumb_color' => array( + 'player_thumb_color' => array( 'type' => 'color', 'label' => __( 'Volume Knob Color', 'radio-station' ), - 'default' => '', + 'default' => '#80C080', 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), 'tab' => 'player', 'section' => 'colors', 'pro' => true, - ), */ + ), // --- [Pro] Volume Track Color --- - /* 'volume_track_color' => array( + 'player_range_color' => array( 'type' => 'coloralpha', 'label' => __( 'Volume Track Color', 'radio-station' ), - 'default' => '', + 'default' => '#80C080', 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), 'tab' => 'player', 'section' => 'colors', 'pro' => true, - ), */ + ), // === Advanced Stream Player === // --- Player Volume --- - /* 'player_volume' => array( + 'player_volume' => array( 'type' => 'number', 'label' => __( 'Player Start Volume', 'radio-station' ), 'default' => 77, @@ -509,60 +536,75 @@ 'tab' => 'player', 'section' => 'advanced', 'pro' => false, - ), */ + ), // --- Single Player --- - /* 'player_single' => array( + 'player_single' => array( 'type' => 'checkbox', 'label' => __( 'Single Player at Once', 'radio-station' ), 'default' => 'yes', - 'helper' => __( 'Stop any existing Players in other windows or tabs when a Player is started.', 'radio-station' ), + 'value' => 'yes', + 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', 'pro' => false, - ), */ + ), // --- [Pro] Player Autoresume --- - /* 'player_autoresume' => array( + 'player_autoresume' => array( 'type' => 'checkbox', 'label' => __( 'Autoresume Playback', 'radio-station' ), 'default' => 'yes', - 'helper' => __( 'Attempt to resume playback if user was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), + 'value' => 'yes', + 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), 'tab' => 'player', 'section' => 'advanced', 'pro' => true, - ), */ + ), // --- [Pro] Popup Player Window --- /* 'player_popup' => array( 'type' => 'checkbox', 'label' => __( 'Popup Player Window', 'radio-station' ), 'default' => '', + 'value' => 'yes', 'helper' => __( 'Add a popup icon to your Player to open it in a separate window.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', 'pro' => true, ), */ // === Sitewide Player Bar === + // --- Player Bar Note --- + 'player_bar_note' => array( + 'type' => 'note', + 'label' => __( 'Bar Defaults Note', 'radio-station' ), + 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) + . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + // 'pro' => true, + ), + // --- [Pro] Sitewide Player Bar --- - /* 'player_bar' => array( + 'player_bar' => array( 'type' => 'select', 'label' => __( 'Sitewide Player Bar', 'radio-station' ), 'default' => 'off', 'options' => array( - 'off' => __( 'No Player Bar', 'radio-station + 'off' => __( 'No Player Bar', 'radio-station' ), 'top' => __( 'Top Player Bar', 'radio-station' ), 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), - ), 'tab' => 'player', 'section' => 'bar', 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), 'pro' => true, - ), */ + ), // --- [Pro] Fade In Player Bar --- - /* 'player_fadein' => array( + 'player_bar_fadein' => array( 'type' => 'number', 'label' => __( 'Fade In Player Bar', 'radio-station' ), 'default' => 2500, @@ -573,10 +615,10 @@ 'tab' => 'player', 'section' => 'bar', 'pro' => true, - ), */ + ), // --- [Pro] Continuous Playback --- - /* 'player_continuous' => array( + 'player_bar_continuous' => array( 'type' => 'checkbox', 'label' => __( 'Continuous Playback', 'radio-station' ), 'default' => 'yes', @@ -584,10 +626,10 @@ 'tab' => 'player', 'section' => 'bar', 'pro' => true, - ), */ + ), // --- [Pro] Player Page Fade --- - /* 'player_pagefade' => array( + 'player_bar_pagefade' => array( 'type' => 'number', 'label' => __( 'Fade In Player Bar', 'radio-station' ), 'default' => 2000, @@ -598,29 +640,29 @@ 'tab' => 'player', 'section' => 'bar', 'pro' => true, - */ + ), // --- [Pro] Bar Player Text Color --- - /* 'player_bar_text' => array( + 'player_bar_text' => array( 'type' => 'color', - 'label' => __( 'Sitewide Player Text Color', 'radio-station' ), + 'label' => __( 'Bar Player Text Color', 'radio-station' ), 'default' => '#FFFFFF', 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', 'pro' => true, - ), */ + ), // --- [Pro] Bar Player Background Color --- - /* 'player_bar_background' => array( + 'player_bar_background' => array( 'type' => 'coloralpha', - 'label' => __( 'Sitewide Player Background Color', 'radio-station' ), - 'default' => 'rgba(0,0,0,1)', + 'label' => __( 'Bar Player Background Color', 'radio-station' ), + 'default' => 'rgba(0,0,0,255)', 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', 'pro' => true, - ), */ + ), // TODO: additional CSS input field ? @@ -943,7 +985,7 @@ // 'section' => 'archives', // ), - // === Templates === + // === Single Templates === // --- Templates Change Note --- 'templates_change_note' => array( @@ -952,7 +994,7 @@ 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', - 'tab' => 'templates', + 'tab' => 'pages', 'section' => 'single', ), @@ -968,7 +1010,7 @@ ), 'default' => 'page', 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), - 'tab' => 'templates', + 'tab' => 'pages', 'section' => 'single', ), @@ -979,7 +1021,7 @@ 'value' => 'yes', 'default' => '', 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'templates', + 'tab' => 'pages', 'section' => 'single', ), @@ -996,7 +1038,7 @@ ), 'default' => 'page', 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), - 'tab' => 'templates', + 'tab' => 'pages', 'section' => 'single', ), @@ -1007,7 +1049,7 @@ 'value' => 'yes', 'default' => '', 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'templates', + 'tab' => 'pages', 'section' => 'single', ), @@ -1095,11 +1137,12 @@ // --- Tab Labels --- // 2.3.2: add widget options tab // 2.3.3.8: added player options tab + // 2.3.3.8: move templates section onto pages tab 'tabs' => array( 'general' => __( 'General', 'radio-station' ), 'player' => __( 'Player', 'radio-station' ), 'pages' => __( 'Pages', 'radio-station' ), - 'templates' => __( 'Templates', 'radio-station' ), + // 'templates' => __( 'Templates', 'radio-station' ), 'widgets' => __( 'Widgets', 'radio-station' ), 'roles' => __( 'Roles', 'radio-station' ), ), @@ -1113,6 +1156,7 @@ 'basic' => __( 'Basic Defaults', 'radio-station' ), 'advanced' => __( 'Advanced Defaults', 'radio-station' ), 'colors' => __( 'Player Colors', 'radio-station' ), + 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), 'single' => __( 'Single Templates', 'radio-station' ), 'archive' => __( 'Archive Templates', 'radio-station' ), 'schedule' => __( 'Schedule Page', 'radio-station' ), @@ -1123,6 +1167,15 @@ ), ); +// 2.3.3.8: [temp] remove player options if player not present +if ( !file_exists( $player_file ) ) { + foreach ( $options as $key => $option ) { + if ( 'player_' == substr( $key, 0, 7 ) ) { + unset( $options[$key] ); + } + } +} + // ---------------------- // Plugin Loader Settings // ---------------------- diff --git a/readme.txt b/readme.txt index 30c7950..d42086e 100644 --- a/readme.txt +++ b/readme.txt @@ -365,7 +365,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Fix to not 404 author pages for DJs without blog posts = 2.2.7 = -* Dutch translation added (Thank you to André Dortmont for the file!) +* Dutch translation added (Thank you to André Dortmont for the file!) * Added Tabbed Display for Master Schedule Shortcode (via Tutorial) * Add Show list columns with active, shift, DJs and show image displays * Add Schedule Override list columns with date sorting and filtering From 419456103d5995a985800860d410556236c1e24b Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 16 Feb 2021 15:45:47 +1000 Subject: [PATCH 180/377] dev updates #290 --- includes/shortcodes.php | 4 ++-- includes/support-functions.php | 35 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 86b5c2c..6a1e757 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -2303,7 +2303,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- custom HTML section --- // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_upcoming_show_custom_display', '', $show['id'], $atts ); + $html['custom'] = apply_filters( 'radio_station_upcoming_shows_custom_display', '', $show['id'], $atts ); // --- open upcoming show list item --- $output .= '
      • '; @@ -2733,7 +2733,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- custom HTML section --- // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_upcoming_show_custom_display', '', $playlist, $atts ); + $html['custom'] = apply_filters( 'radio_station_current_playlist_custom_display', '', $playlist, $atts ); // --- filter display section order --- // 2.3.1: added filter for section order display diff --git a/includes/support-functions.php b/includes/support-functions.php index aebb311..c96c97d 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1298,6 +1298,24 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } } + // --- add directly any remaining overrides --- + // 2.3.1: fix to include standalone overrides on days + // 2.3.3.8: moved override adding to fix shift order + if ( count( $overrides ) > 0 ) { + foreach ( $overrides as $i => $override ) { + if ( $date == $override['date'] ) { + // 2.3.3.7: remove check if override already done + // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { + // $done_overrides[] = $date . '--' . $i; + $show_shifts[$day][$override['start']] = $override; + if ( $day == $debugday ) { + $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; + } + // } + } + } + } + // --- sort the shifts using 24 hour time --- $shifts = $show_shifts[$day]; if ( count( $shifts ) > 0 ) { @@ -1328,23 +1346,6 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) echo "Sorted Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; } - // --- add directly any remaining overrides --- - // 2.3.1: fix to include standalone overrides on days - if ( count( $overrides ) > 0 ) { - foreach ( $overrides as $i => $override ) { - if ( $date == $override['date'] ) { - // 2.3.3.7: remove check if override already done - // if ( !in_array( $date . '--' . $i, $done_overrides ) ) { - // $done_overrides[] = $date . '--' . $i; - $show_shifts[$day][$override['start']] = $override; - if ( $day == $debugday ) { - $debugshifts .= "Added Override: " . print_r( $override, true ) . PHP_EOL; - } - // } - } - } - } - $shifts = $show_shifts[$day]; // ksort( $shifts ); if ( RADIO_STATION_DEBUG ) { From a6f563237d90568e340ce777b50c4de76d8f7bbb Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 21 Feb 2021 09:02:12 +1000 Subject: [PATCH 181/377] dev updates for 2.3.3.8 release --- CHANGELOG.md | 5 +- docs/Data.md | 10 - docs/Filters.md | 443 ++++++++++++++++++++++++++++ docs/index.md | 2 +- includes/post-types-admin.php | 3 +- includes/shortcodes.php | 110 ++++--- includes/support-functions.php | 8 +- radio-station.php | 16 +- readme.md | 2 +- templates/master-schedule-list.php | 48 +-- templates/master-schedule-table.php | 43 +-- templates/master-schedule-tabs.php | 47 +-- templates/single-show-content.php | 3 +- 13 files changed, 602 insertions(+), 138 deletions(-) create mode 100644 docs/Filters.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f169a26..28d8770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.8 * Update: Plugin Panel (1.1.7) with Image and Color Picker fields -* Added: Stream Format selection setting +& Documentation: Full Plugin Filter List added to docs/Filters.md +* Added: Stream Format and Fallback/Format selection setting +* Added: Station Image and Station Title for future Player Display * Added: Station Email Address setting with default display option * Added: Section order filtering for Master Schedule Views * Added: Section display filtering for Master Schedule Views @@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Check for explicit singular.php template usage setting * Fixed: Access to Shows Data via querystring of Show ID/name * Fixed: Shows Data for Genres/Languages querystring of ID/name +* Fixed: Override Display order output for Tab/List Views ### 2.3.3.7 * Fixed: Schedule Overrides overlapping multiple Show shifts diff --git a/docs/Data.md b/docs/Data.md index e1de5ff..0441428 100644 --- a/docs/Data.md +++ b/docs/Data.md @@ -54,13 +54,3 @@ A flexible taxonomy allowing for the addition of Genre terms that can be assigne A fixed taxonomy allowing for assigning of Language terms to a Show (or Override.) In a Show does not have a Language assigned, it is assumed to be in the main Language selected in the Plugin Settings. Languages are displayed on a Show's page, and discoverable via the plugin's data [API](./API.md) Languages Endpoint, and in future will be displayable in widgets and other shortcodes also. There will also be an addition of a Languages Archive Shortcode that will work similar to the Genre Archive Shortcode. - -## Data Filters - -There are filters throughout the plugin that allow you to override data values and plugin output. We employ the practice of adding as many of these as possible to allow users of the plugin to customize it's behaviour without needing to modify the plugin's code - as these kind of modifications are overwritten with plugin updates. - -You can add your own custom filters via a Code Snippets plugin (which has the advantage of checking syntax for you), or in your Child Theme's `functions.php`, or in a file with a PHP extension in your `/wp-content/mu-plugins/` directory. - -### Filter List - -Currently you can find these filters by searching the plugin code for `apply_filters( 'radio_station'`. Af full list of available filters will be gradually added here in future plugin documentation. diff --git a/docs/Filters.md b/docs/Filters.md new file mode 100644 index 0000000..b851cab --- /dev/null +++ b/docs/Filters.md @@ -0,0 +1,443 @@ +# Radio Station Plugin Filters + +*** + +## Data Filters + +There are filters throughout the plugin that allow you to override data values and plugin output. We employ the practice of adding as many of these as possible to allow users of the plugin to customize it's behaviour without needing to modify the plugin's code - as these kind of modifications are overwritten with plugin updates. + +You can add your own custom filters via a Code Snippets plugin (which has the advantage of checking syntax for you), or in your Child Theme's `functions.php`, or in any file with a PHP extension in your `/wp-content/mu-plugins/` directory. + +## Finding Filters + +You can find these filters by searching any of PHP plugin files for `apply_filters( 'radio_`. + +## Filter Values and Arguments + +Note the first argument passed to `apply_filters` is the name of the filter, the second argument is the value to be filtered. Additional arguments may also be provided to the filter so that you can match changes to specific contexts. + +## Filter Examples + +You can find many examples and tutorials of how to use WordPress filters online. Here is a generic filter example to help you get started with filters. This one will add custom HTML to the bottom of the Current Show Widget, regardless of which Show is playing: + +``` +add_filter( 'radio_station_current_show_custom_display', 'my_custom_function_name' ); +function my_custom_function_name( $html ) { + $html .= "
        Now taking phone requests!
        "; + return $html; +} +``` + +Note if a filter has additional arguments, and you wish to check them, you need to specify the number of arguments. To do this you must also include a filter priority. Here `10` is the (default) priority of when to run the filter and `3` is the number of arguments passed to the filter function. This example will add custom HTML to the bottom of the Current Show widget only if the Show ID is 20: + +``` +add_filter( 'radio_station_current_show_custom_display', 'my_custom_function_name', 10, 3 ); +function my_custom_function_name( $html, $show_id, $atts ) { + if ( 20 == $show_id ) { + $html .= "
        Welcoming our newest DJ!
        "; + } + return $html; +} +``` + +## Filter List + +Here is a full list of available filters within the plugin, grouped by file and function for ease of reference. + +| File / *Function* | Filter | Value | Extra Args | +| - | - | +|**radio-station.php**|||| +|*radio_station_get_template*|`radio_station_template_dir_hierarchy` | ` $dirs` | `$template`, `$paths`| +|*radio_station_automatic_pages_content_set*|`radio_station_automatic_schedule_atts` | ` $atts` | | +| |`radio_station_automatic_show_archive_atts` | ` $atts` | | +| |`radio_station_automatic_override_archive_atts` | ` $atts` | | +| |`radio_station_automatic_playlist_archive_atts` | ` $atts` | | +| |`radio_station_automatic_genre_archive_atts` | ` $atts` | | +|*radio_station_single_content_template*|`radio_station_'.$post_type.'_content_templates` | ` $templates` | `$post_type`| +| |`radio_station_content_'.$post_type` | ` $output` | `$post_id`| +|*radio_station_get_host_template*|`radio_station_host_templates` | ` $templates` | | +|*radio_station_get_producer_template*|`radio_station_producer_templates` | ` $templates` | | +|*radio_station_add_show_links*|`radio_station_show_related_post_types` | ` $post_types` | | +| |`radio_station_link_to_show_positions` | ` $positions` | `$post_type`, `$post`| +| |`radio_station_link_to_show_before` | ` $before` | `$post`, `$related_shows`| +| |`radio_station_link_to_show_after` | ` $after` | `$post`, `$related_shows`| +|*radio_station_get_show_post_link*|`radio_station_show_related_post_types` | ` $related_post_types` | | +| |`radio_station_link_show_posts` | ` true` | `$post`| +|**radio-station-admin.php**|||| +|*radio_station_settings_cap_check*|`radio_station_settings_capability` | ` 'manage_options'` | | +|*radio_station_add_admin_menus*|`radio_station_menu_position` | ` 5` | | +| |`radio_station_manage_options_capability` | ` 'manage_options'` | | +| |`radio_station_export_playlists` | ` false` | | +|*radio_station_role_editor*|`radio_station_role_editor_message` | ` true` | | +|**includes/data-feeds.php**|||| +|*radio_station_api_link_header*|`radio_station_api_discovery_header` | ` $header` | | +|*radio_station_api_discovery_link*|`radio_station_api_discovery_link` | ` $link` | | +|*radio_station_api_discovery_rsd*|`radio_station_api_discovery_rsd` | ` $link` | | +|*radio_station_get_station_data*|`radio_station_station_data` | ` $station_data` | | +|*radio_station_get_broadcast_data*|`radio_station_broadcast_data` | ` $broadcast` | | +|*radio_station_get_shows_data*|`radio_station_shows_data` | ` $shows` | `$show`| +|*radio_station_get_genres_data*|`radio_station_genres_data` | ` $genres` | `$genre`| +|*radio_station_get_languages_data*|`radio_station_languages_data` | ` $languages_data` | `$language`| +|*radio_station_register_rest_routes*|`radio_station_route_slug_base` | ` 'radio'` | | +| |`radio_station_route_slug_station` | ` 'station'` | | +| |`radio_station_route_slug_broadcast` | ` 'broadcast'` | | +| |`radio_station_route_slug_schedule` | ` 'schedule'` | | +| |`radio_station_route_slug_shows` | ` 'shows'` | | +| |`radio_station_route_slug_genres` | ` 'genres'` | | +| |`radio_station_route_slug_languages` | ` 'languages'` | | +|*radio_station_get_route_urls*|`radio_station_route_urls` | ` $routes` | | +|*radio_station_route_radio*|`radio_station_route_slug_base` | ` 'radio'` | | +|*radio_station_route_station*|`radio_station_route_station` | ` $station` | `$request`| +|*radio_station_route_broadcast*|`radio_station_route_broadcast` | ` $broadcast` | `$request`| +|*radio_station_route_schedule*|`radio_station_route_schedule` | ` $schedule` | `$request`| +|*radio_station_route_shows*|`radio_station_route_shows` | ` $show_list` | `$request`| +|*radio_station_route_genres*|`radio_station_route_genres` | ` $genre_list` | `$request`| +|*radio_station_route_languages*|`radio_station_route_languages` | ` $language_list` | `$request`| +|*radio_station_add_feeds*|`radio_station_feed_slug_base` | ` 'radio'` | | +| |`radio_station_feed_slug_station` | ` 'station'` | | +| |`radio_station_feed_slug_broadcast` | ` 'broadcast'` | | +| |`radio_station_feed_slug_schedule` | ` 'schedule'` | | +| |`radio_station_feed_slug_shows` | ` 'shows'` | | +| |`radio_station_feed_slug_genres` | ` 'genres'` | | +| |`radio_station_feed_slug_languages` | ` 'languages'` | | +| |`radio_station_feed_slugs` | ` $feeds` | | +|*radio_station_get_feed_urls*|`radio_station_feed_urls` | ` $feeds` | | +|*radio_station_feed_radio*|`radio_station_feed_slug_base` | ` 'radio'` | | +| |`radio_station_feed_radio` | ` $radio` | | +|*radio_station_feed_station*|`radio_station_feed_station` | ` $station` | | +|*radio_station_feed_broadcast*|`radio_station_feed_broadcast` | ` $broadcast` | | +|*radio_station_feed_schedule*|`radio_station_feed_schedule` | ` $schedule` | | +|*radio_station_feed_shows*|`radio_station_feed_shows` | ` $show_list` | | +|*radio_station_feed_genres*|`radio_station_feed_genres` | ` $genre_list` | | +|*radio_station_feed_languages*|`radio_station_feed_languages` | ` $language_list` | | +|**includes/master-schedule.php**|||| +|*radio_station_master_schedule*|`radio_station_schedule_clock` | ` array()` | `$atts`| +| |`radio_station_schedule_clock` | ` array()` | `$atts`| +| |`radio_station_schedule_control_order` | ` $control_order` | `$atts`| +| |`radio_station_schedule_controls` | ` $controls` | `$atts`| +| |`radio_station_schedule_override` | ` ''` | `$atts`| +| |`master_schedule_table_view` | ` $output` | `$atts`| +| |`master_schedule_tabs_view` | ` $output` | `$atts`| +| |`master_schedule_list_view` | ` $output` | `$atts`| +|**includes/post-types.php**|||| +|*radio_station_create_post_types*|`radio_station_post_type_show` | ` $post_type` | | +| |`radio_station_post_type_playlist` | ` $post_type` | | +| |`radio_station_post_type_override` | ` $post_type` | | +| |`radio_station_host_interface` | ` false` | | +| |`radio_station_post_type_host` | ` $post_type` | | +| |`radio_station_producer_interface` | ` false` | | +| |`radio_station_post_type_producer` | ` $post_type` | | +|*radio_station_register_show_taxonomies*|`radio_station_genre_taxonomy_args` | ` $args` | | +| |`radio_station_language_taxonomy_args` | ` $args` | | +|**includes/post-types-admin.php**|||| +|*radio_station_add_playlist_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'playlist'`| +|*radio_station_add_post_show_metabox*|`radio_station_show_related_post_types` | ` $post_types` | | +|*radio_station_add_show_info_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'shows'`| +|*radio_station_add_show_shifts_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'shifts'`| +|*radio_station_add_show_helper_box*|`radio_station_metabox_position` | ` 'rstop'` | `'helper'`| +|*radio_station_add_schedule_override_metabox*|`radio_station_metabox_position` | ` 'rstop'` | `'overrides'`| +|*radio_station_override_past_future_filter*|`radio_station_overrides_past_future_default` | ` $pastfuture` | | +|**includes/shortcodes.php**|||| +|*radio_station_timezone_shortcode*|`radio_station_timezone_select` | ` ''` | `'radio-station-timezone-'.$instance`, `$atts`| +| |`radio_station_timezone_shortcode` | ` $output` | `$atts`| +|*radio_station_clock_shortcode*|`radio_station_clock_timezone_select` | ` ''` | `'radio-station-clock-'.$instance`, `$atts`| +| |`radio_station_clock` | ` $clock` | `$atts`| +|*radio_station_archive_list_shortcode*|`radio_station_'.$type.'_archive_post_args` | ` $args` | | +| |`radio_station_'.$type.'_archive_posts` | ` $archive_posts` | | +| |`radio_station_time_format_start` | ` $start_data_format` | `$type.'-archive'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `$type.'-archive'`, `$atts`| +| |`radio_station_archive_'.$type.'_list_excerpt_length` | ` false` | | +| |`radio_station_archive_'.$type.'_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_episode_archive_meta` | ` ''` | | +| |`radio_station_host_archive_meta` | ` ''` | | +| |`radio_station_producer_archive_meta` | ` ''` | | +| |`radio_station_'.$type.'_archive_content` | ` $post_content` | `$post_id`| +| |`radio_station_'.$type.'_archive_excerpt` | ` $excerpt` | `$post_id`| +| |`radio_station_'.$type.'_archive_list` | ` $list` | `$atts`| +|*radio_station_genre_archive_list*|`radio_station_genre_archive_post_args` | ` $args` | | +| |`radio_station_genre_archive_posts` | ` $posts` | | +| |`radio_station_genre_image` | ` false` | `$genre`| +| |`radio_station_genre_archive_list` | ` $list` | `$atts`| +|*radio_station_language_archive_list*|`radio_station_language_archive_list` | ` $list` | `$atts`| +|*radio_station_show_list_shortcode*|`radio_station_get_show_episodes` | ` false` | `$show_id`, `$args`| +| |`radio_station_show_'.$type.'_list_excerpt_length` | ` false` | | +| |`radio_station_show_'.$type.'_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_show_'.$type.'_content` | ` $post_content` | `$post_id`| +| |`radio_station_show_'.$type.'_excerpt` | ` $excerpt` | `$post_id`| +| |`radio_station_show_'.$type.'_list` | ` $list` | `$atts`| +|*radio_station_current_show_shortcode*|`radio_station_current_show_dynamic` | ` 0` | `$atts`| +| |`radio_station_widgets_ajax_override` | ` $ajax` | `'current-show'`, `$widget`| +| |`radio_station_current_show_widget_excerpt_length` | ` false` | | +| |`radio_station_current_show_widget_excerpt_more` | ` '[…]'` | | +| |`radio_station_current_show_shortcode_excerpt_length` | ` false` | | +| |`radio_station_current_show_shortcode_excerpt_more` | ` '[…]'` | | +| |`radio_station_time_format_start` | ` $start_data_format` | `'current-show'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'current-show'`, `$atts`| +| |`radio_station_current_show_link` | ` $show_link` | `$show_id`, `$atts`| +| |`radio_station_current_show_title_display` | ` $title` | `$show_id`, `$atts`| +| |`radio_station_current_show_avatar` | ` $show_avatar` | `$show_id`, `$atts`| +| |`radio_station_current_show_avatar_display` | ` $avatar` | `$show_id`, `$atts`| +| |`radio_station_dj_link` | ` $host_link` | `$host`| +| |`radio_station_current_show_hosts_display` | ` $hosts` | `$show_id`, `$atts`| +| |`radio_station_current_show_encore_display` | ` $encore` | `$show_id`, `$atts`| +| |`radio_station_current_show_playlist_display` | ` $playlist` | `$show_id`, `$atts`| +| |`radio_station_current_show_widget_excerpt` | ` $excerpt` | `$show_id`, `$atts`| +| |`radio_station_current_show_shortcode_excerpt` | ` $excerpt` | `$show_id`, `$atts`| +| |`radio_station_current_show_description_display` | ` $description` | `$show_id`, `$atts`| +| |`radio_station_current_show_shifts_display` | ` $shift_display` | `$show_id`, `$atts`| +| |`radio_station_current_show_custom_display` | ` ''` | `$show_id`, `$atts`| +| |`radio_station_current_show_section_order` | ` $order` | `$atts`| +| |`radio_station_no_current_show_text` | ` $no_current_show` | `$atts`| +| |`radio_station_countdown_dynamic` | ` false` | `'current-show'`, `$atts`, `$current_shift_end`| +|*radio_station_current_show*|`radio_station_current_show_load_script` | ` $js` | `$atts`| +|*radio_station_upcoming_shows_shortcode*|`radio_station_upcoming_shows_dynamic` | ` 0` | `$atts`| +| |`radio_station_widgets_ajax_override` | ` $ajax` | `'upcoming-shows'`, `$widget`| +| |`radio_station_upcoming_shows_section_order` | ` $order` | `$atts`| +| |`radio_station_time_format_start` | ` $start_data_format` | `'upcoming-shows'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'upcoming-shows'`, `$atts`| +| |`radio_station_upcoming_show_link` | ` $show_link` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_title_display` | ` $title` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_avatar` | ` $show_avatar` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_avatar_display` | ` $avatar` | `$show_id`, `$atts`| +| |`radio_station_dj_link` | ` $host_link` | `$host`| +| |`radio_station_upcoming_show_hosts_display` | ` $hosts` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_encore_display` | ` $encore` | `$show_id`, `$atts`| +| |`radio_station_upcoming_show_shifts_display` | ` $shift_display` | `$show_id`, `$atts`| +| |`radio_station_upcoming_shows_custom_display` | ` ''` | `$show_id`, `$atts`| +| |`radio_station_no_upcoming_shows_text` | ` $no_upcoming_shows` | `$atts`| +| |`radio_station_countdown_dynamic` | ` false` | `'upcoming-shows'`, `$atts`, `$next_start_time`| +|*radio_station_upcoming_shows*|`radio_station_upcoming_shows_load_script` | ` $js` | `$atts`| +|*radio_station_current_playlist_shortcode*|`radio_station_current_playlist_dynamic` | ` 0` | `$atts`| +| |`radio_station_widgets_ajax_override` | ` $ajax` | `'current-playlist'`, `$widget`| +| |`radio_station_countdown_dynamic` | ` false` | `'current-playlist'`, `$atts`, `$shift_end_time`| +| |`radio_station_current_playlist_tracks_display` | ` $tracks` | `$playlist`, `$atts`| +| |`radio_station_current_playlist_link_display` | ` $link` | `$playlist`, `$atts`| +| |`radio_station_current_playlist_custom_display` | ` ''` | `$playlist`, `$atts`| +| |`radio_station_current_playlist_section_order` | ` $order` | `$atts`| +| |`radio_station_no_current_playlist_text` | ` $no_current_playlist` | `$atts`| +| |`radio_station_current_playlist_no_playlist_display` | ` $no_playlist` | `$atts`| +|*radio_station_current_playlist*|`radio_station_current_playlist_load_script` | ` $js` | `$atts`| +|**includes/support-functions.php**|||| +|*radio_station_get_shows*|`radio_station_get_shows` | ` $shows` | `$defaults`| +|*radio_station_get_show_shifts*|`radio_station_show_shifts` | ` $day_shifts` | | +|*radio_station_get_overrides*|`radio_station_get_overrides` | ` $override_list` | `$start_date`, `$end_date`| +|*radio_station_get_show_data*|`radio_station_cached_data` | ` false` | `$datatype`, `$show_id`| +| |`radio_station_show_'.$datatype` | ` $results` | `$show_id`, `$args`| +|*radio_station_get_show_data_meta*|`radio_station_show_data_meta` | ` $show_data` | `$show_id`| +|*radio_station_get_show_description*|`radio_station_show_data_excerpt_length` | ` false` | | +| |`radio_station_show_data_excerpt_more` | ` ''` | | +| |`radio_station_show_data_description` | ` $description` | `$show_id`| +| |`radio_station_show_data_excerpt` | ` $excerpt` | `$show_id`| +|*radio_station_get_override_data_meta*|`radio_station_override_data` | ` $override_data` | `$override_id`| +|*radio_station_get_current_schedule*|`radio_station_previous_show` | ` $prev_shift` | `$time`| +| |`radio_station_previous_show` | ` $prev_shift` | `$time`| +| |`radio_station_current_schedule` | ` $show_shifts` | `$time`| +|*radio_station_get_current_show*|`radio_station_previous_show` | ` $prev_shift` | `$time`| +| |`radio_station_current_show` | ` $current_show` | `$time`| +|*radio_station_get_next_show*|`radio_station_next_show` | ` $next_show` | `$time`| +|*radio_station_get_next_shows*|`radio_station_next_show` | ` $next_show` | `$time`| +| |`radio_station_next_shows` | ` $next_shows` | `$limit`, `$show_shifts`| +| |`radio_station_next_shows` | ` $next_shows` | `$limit`, `$show_shifts`| +|*radio_station_get_genres*|`radio_station_get_genres` | ` $genres` | `$args`| +|*radio_station_get_genre_shows*|`radio_station_show_genres_query_args` | ` $args` | `$genre`| +|*radio_station_get_language_shows*|`radio_station_show_languages_query_args` | ` $args` | `$language`| +|*radio_station_get_show_avatar_id*|`radio_station_show_avatar_id` | ` $avatar_id` | `$show_id`| +|*radio_station_get_show_avatar_url*|`radio_station_show_avatar_url` | ` $avatar_url` | `$show_id`| +|*radio_station_get_show_avatar*|`radio_station_show_avatar` | ` $avatar` | `$show_id`| +|*radio_station_get_stream_url*|`radio_station_stream_url` | ` $streaming_url` | | +|*radio_station_get_stream_formats*|`radio_station_stream_formats` | ` $formats` | | +|*radio_station_get_station_url*|`radio_station_station_url` | ` $station_url` | | +|*radio_station_get_station_image_url*|`radio_station_station_image_url` | ` $station_image` | | +|*radio_station_get_schedule_url*|`radio_station_schedule_url` | ` $schedule_url` | | +|*radio_station_get_api_url*|`radio_station_api_url` | ` $api_url` | | +|*radio_station_get_route_url*|`radio_station_route_slug_base` | ` 'radio'` | | +| |`radio_station_route_slug_'.$route` | ` $route` | | +|*radio_station_get_feed_url*|`radio_station_feed_slug_'.$feedname` | ` $feedname` | | +|*radio_station_get_host_url*|`radio_station_host_url` | ` $host_url` | `$host_id`| +|*radio_station_get_producer_url*|`radio_station_producer_url` | ` $producer_url` | `$producer_id`| +|*radio_station_patreon_button*|`radio_station_patreon_button` | ` $button` | `$page`| +|*radio_station_get_timezone_options*|`radio_station_get_timezone_options` | ` $options` | `$include_wp_timezone`| +|*radio_station_get_schedule_weekdays*|`radio_station_schedule_weekday_start` | ` $weekstart` | | +|*radio_station_get_languages*|`radio_station_get_languages` | ` $translations` | | +|*radio_station_get_language_options*|`radio_station_get_language_options` | ` $languages` | `$include_wp_default`| +|*radio_station_trim_excerpt*|`radio_station_excerpt_length` | ` $length` | | +| |`radio_station_excerpt_more` | ` ' […]'` | | +| |`radio_station_trim_excerpt` | ` $excerpt` | `$raw_content`, `$length`, `$more`, `$permalink`| +|**includes/class-current-show-widget.php**|||| +|*form*|`radio_station_current_show_widget_fields` | ` $fields` | `$this`, `$instance`| +|*update*|`radio_station_current_show_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +|*widget*|`radio_station_current_show_widget_override` | ` $output` | `$args`, `$atts`| +|**includes/class-upcoming-shows-widget.php**|||| +|*form*|`radio_station_upcoming_shows_widget_fields` | ` $fields` | `$this`, `$instance`| +|*update*|`radio_station_upcoming_shows_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +|*widget*|`radio_station_upcoming_shows_widget_override` | ` $output` | `$args`, `$atts`| +|**includes/class-current-playlist-widget.php**|||| +|*form*|`radio_station_playlist_widget_fields` | ` $fields` | `$this`, `$instance`| +|*update*|`radio_station_playlist_widget_update` | ` $instance` | `$new_instance`, `$old_instance`| +|*widget*|`radio_station_current_playlist_widget_override` | ` $output` | `$args`, `$atts`| +|**includes/class-radio-clock-widget.php**|||| +| |`radio_station_radio_clock_widget_override` | ` $output` | `$args`, `$atts`| +|**templates/master-schedule-table.php**|||| +| |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-table'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-table'`, `$atts`| +| |`radio_station_schedule_start_day` | ` false` | `'table'`| +| |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'table'`| +| |`radio_station_schedule_table_excerpt_length` | ` false` | | +| |`radio_station_schedule_table_excerpt_more` | ` '[…]'` | | +| |`radio_station_schedule_table_info_order` | ` $infokeys` | | +| |`radio_station_schedule_arrows` | ` $arrows` | `'table'`| +| |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'table'`, `$shift`| +| |`radio_station_schedule_show_time_display` | ` true` | `$show_id`, `'table'`, `$shift`| +| |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_file_anchor` | ` $anchor` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_file_display` | ` $file` | `$show_file`, `$show_id`, `'table'`| +| |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_excerpt_display` | ` $excerpy` | `$show_id`, `'table'`| +| |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'table'`| +|**templates/master-schedule-tabs.php**|||| +| |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-tabs'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-tabs'`, `$atts`| +| |`radio_station_schedule_start_day` | ` false` | `'tabs'`| +| |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'tabs'`| +| |`radio_station_schedule_tabs_excerpt_length` | ` false` | | +| |`radio_station_schedule_tabs_excerpt_more` | ` '[…]'` | | +| |`radio_station_schedule_tabs_info_order` | ` $infokeys` | | +| |`radio_station_schedule_arrows` | ` $arrows` | `'tabs'`| +| |`radio_station_schedule_tabs_avatar_position_start` | ` $avatar_position` | | +| |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_title_display` | ` $title` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'tabs'`, `$shift`| +| |`radio_station_schedule_show_times_display` | ` true` | `$show_id`, `'tabs'`, `$shift`| +| |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_file_anchor` | ` $anchor` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_file_display` | ` $file` | `$show_file`, `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_genres` | ` $genres` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'tabs'`| +| |`radio_station_schedule_show_excerpt_display` | ` $excerpt` | `$show_id`, `'tabs'`| +|**templates/master-schedule-legacy.php**|||| +| |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'legacy'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show['id']`, `'legacy'`| +| |`radio_station_schedule_show_link` | ` $show_link` | `$show['id']`, `'legacy'`| +| |`radio_station_schedule_show_time` | ` $times` | `$show['id']`, `'legacy'`| +| |`radio_station_schedule_show_encore` | ` $encore` | `$show['id']`, `'legacy'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show['id']`, `'legacy'`| +|**templates/master-schedule-list.php**|||| +| |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-list'`, `$atts`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-list'`, `$atts`| +| |`radio_station_schedule_start_day` | ` false` | `'list'`| +| |`radio_station_schedule_show_avatar_size` | ` 'thumbnail'` | `'list'`| +| |`radio_station_schedule_list_excerpt_length` | ` false` | | +| |`radio_station_schedule_list_excerpt_more` | ` '[…]'` | | +| |`radio_station_schedule_list_info_order` | ` $infokeys` | | +| |`radio_station_schedule_show_link` | ` $show_link` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_avatar` | ` $show_avatar` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_avatar_display` | ` $avatar` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_title` | ` $title` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_hosts` | ` $show_hosts` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_hosts_display` | ` $hosts` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_time` | ` $show_time` | `$show_id`, `'list'`, `$shift`| +| |`radio_station_schedule_show_time_display` | ` true` | `$show_id`, `'list'`, `$shift`| +| |`radio_station_schedule_show_encore` | ` $show_encore` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_encore_display` | ` $encore` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_file` | ` $show_file` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_file_anchor` | ` $anchor` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_file_display` | ` $file` | `$show_file`, `$show_id`, `'list'`| +| |`radio_station_schedule_show_genres_display` | ` $genres` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_excerpt` | ` $show_excerpt` | `$show_id`, `'list'`| +| |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'list'`| +|**templates/single-playlist-content.php**|||| +| |`radio_station_link_playlist_to_show_before` | ` $before` | `$post`, `$show`| +| |`radio_station_link_playlist_to_show_before` | ` $after` | `$post`, `$show`| +|**templates/single-show-content.php**|||| +| |`radio_station_show_title` | ` $show_title` | `$post_id`| +| |`radio_station_show_header` | ` $header_id` | `$post_id`| +| |`radio_station_show_avatar` | ` $avatar_id` | `$post_id`| +| |`radio_station_show_thumbnail` | ` $thumbnail_id` | `$post_id`| +| |`radio_station_show_genres` | ` $genres` | `$post_id`| +| |`radio_station_show_languages` | ` $languages` | `$post_id`| +| |`radio_station_show_hosts` | ` $hosts` | `$post_id`| +| |`radio_station_show_producers` | ` $producers` | `$post_id`| +| |`radio_station_show_active` | ` $active` | `$post_id`| +| |`radio_station_show_shifts` | ` $shifts` | `$post_id`| +| |`radio_station_show_file` | ` $show_file` | `$post_id`| +| |`radio_station_show_download` | ` $show_download` | `$post_id`| +| |`radio_station_show_link` | ` $show_link` | `$post_id`| +| |`radio_station_show_email` | ` $show_email` | `$post_id`| +| |`radio_station_show_phone` | ` $show_phone` | `$post_id`| +| |`radio_station_show_patreon` | ` $show_patreon` | `$post_id`| +| |`radio_station_show_rss` | ` $show_rss` | `$post_id`| +| |`radio_station_show_social_icons` | ` false` | `$post_id`| +| |`radio_station_show_website_title` | ` $title` | `$post_id`| +| |`radio_station_show_home_icon` | ` $icon` | `$post_id`| +| |`radio_station_show_phone_title` | ` $title` | `$post_id`| +| |`radio_station_show_phone_icon` | ` $icon` | `$post_id`| +| |`radio_station_show_email_title` | ` $title` | `$post_id`| +| |`radio_station_show_email_icon` | ` $icon` | `$post_id`| +| |`radio_station_show_rss_title` | ` $title` | `$post_id`| +| |`radio_station_show_rss_icon` | ` $icon` | `$post_id`| +| |`radio_station_show_page_icons` | ` $show_icons` | `$post_id`| +| |`radio_station_show_page_latest_limit` | ` $latest_limit` | `$post_id`| +| |`radio_station_show_page_posts_limit` | ` false` | `$post_id`| +| |`radio_station_show_page_playlist_limit` | ` false` | `$post_id`| +| |`radio_station_show_page_episodes` | ` false` | `$post_id`| +| |`radio_station_show_jump_links` | ` 'yes'` | `$post_id`| +| |`radio_station_show_avatar_size` | ` 'medium'` | `$post_id`, `'show-page'`| +| |`radio_station_show_social_icons_display` | ` ''` | | +| |`radio_station_show_patreon_title` | ` $title` | `$post_id`| +| |`radio_station_show_patreon_button` | ` $patreon_button` | `$post_id`| +| |`radio_station_show_player_label` | ` ''` | `$post_id`| +| |`radio_station_show_download_title` | ` $title` | `$post_id`| +| |`radio_station_show_images_blocks` | ` $image_blocks` | `$post_id`| +| |`radio_station_show_image_block_order` | ` $image_block_order` | `$post_id`| +| |`radio_station_show_info_label` | ` $label` | `$post_id`| +| |`radio_station_show_hosts_label` | ` $label` | `$post_id`| +| |`radio_station_show_producers_label` | ` $label` | `$post_id`| +| |`radio_station_show_genres_label` | ` $label` | `$post_id`| +| |`radio_station_show_languages_label` | ` $label` | `$post_id`| +| |`radio_station_show_phone_label` | ` $label` | `$post_id`| +| |`radio_station_show_meta_blocks` | ` $meta_blocks` | `$post_id`| +| |`radio_station_show_meta_block_order` | ` $meta_block_order` | `$post_id`| +| |`radio_station_show_times_label` | ` $label` | `$post_id`| +| |`radio_station_show_no_shifts_label` | ` $label` | `$post_id`| +| |`radio_station_show_timezone_label` | ` $label` | `$post_id`| +| |`radio_station_time_format_start` | ` $start_data_format` | `'show-template'`, `$post_id`| +| |`radio_station_time_format_end` | ` $end_data_format` | `'show-template'`, `$post_id`| +| |`radio_station_show_encore_label` | ` $label` | `$post_id`| +| |`radio_station_show_schedule_link_title` | ` $title` | `$post_id`| +| |`radio_station_show_schedule_link_anchor` | ` $label` | `$post_id`| +| |`radio_station_show_page_blocks` | ` $blocks` | `$post_id`| +| |`radio_station_show_more_label` | ` $label` | `$post_id`| +| |`radio_station_show_less_label` | ` $label` | `$post_id`| +| |`radio_station_show_description_label` | ` $label` | `$post_id`| +| |`radio_station_show_description_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_episodes_label` | ` $label` | `$post_id`| +| |`radio_station_show_episodes_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_page_episodes_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station_show_posts_label` | ` $label` | `$post_id`| +| |`radio_station_show_posts_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_page_posts_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station-show_playlists_label` | ` $label` | `$post_id`| +| |`radio_station_show_playlists_anchor` | ` $anchor` | `$post_id`| +| |`radio_station_show_page_playlists_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station_show_page_sections` | ` $sections` | `$post_id`| +| |`radio_station_show_header_size` | ` 'full'` | `$post_id`| +| |`radio_station_show_page_header_image` | ` $header_image` | `$post_id`| +| |`radio_station_show_page_block_order` | ` $block_order` | `$post_id`| +| |`radio_station_show_latest_posts_label` | ` $label` | `$post_id`| +| |`radio_station_show_page_latest_shortcode` | ` $shortcode` | `$post_id`| +| |`radio_station_show_page_section_order` | ` $section_order` | `$post_id`| diff --git a/docs/index.md b/docs/index.md index cd681de..3f642a9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,7 +32,7 @@ There are a few [Widgets](./Widgets.md) you can add via your Appearance -> Widge Then there are also a number of other [Shortcodes](./Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. -Radio Station has several in-built [Data](./Data.md) types. These include [Custom Post Types](./Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](./Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](./Data.md#data-filters) throughout the plugin. We have also incorporated an [API](./API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. +Radio Station has several in-built [Data](./Data.md) types. These include [Custom Post Types](./Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](./Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via [Custom Filters](./Filters.md) throughout the plugin. We have also incorporated an [API](./API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://netmix.com/radio-station-pro/). Check out the [Roadmap](./Roadmap.md) if you are interested in seeing what is coming up next! diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 88e2e30..37c20d4 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1144,7 +1144,8 @@ function radio_station_add_post_show_metabox() { // 2.3.0: moved check for shows inside metabox // ---- add a filter for which post types to show metabox on --- - $post_types = apply_filters( 'radio_station_show_related_post_types', array( 'post' ) ); + $post_types = array( 'post' ); + $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); // --- add the metabox to post types --- add_meta_box( diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 6a1e757..030c238 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -594,23 +594,25 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- description --- + $post_content = $archive_post->post_content; + $post_id = $archive_post->ID; if ( 'none' == $atts['description'] ) { $list .= ''; } elseif ( 'full' == $atts['description'] ) { $list .= '
        '; - $content = apply_filters( 'radio_station_' . $type . '_archive_content', $archive_post->post_content, $archive_post->ID ); + $content = apply_filters( 'radio_station_' . $type . '_archive_content', $post_content, $post_id ); $list .= $content; $list .= '
        '; } else { $list .= '
        '; - $permalink = get_permalink( $archive_post->ID ); + $permalink = get_permalink( $post_id ); if ( !empty( $archive_post->post_excerpt ) ) { $excerpt = $archive_post->post_excerpt; $excerpt .= ' ' . $more . ''; } else { - $excerpt = radio_station_trim_excerpt( $archive_post->post_content, $length, $more, $permalink ); + $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); } - $excerpt = apply_filters( 'radio_station_' . $type . '_archive_excerpt', $excerpt, $archive_post->ID ); + $excerpt = apply_filters( 'radio_station_' . $type . '_archive_excerpt', $excerpt, $post_id ); $list .= $excerpt; $list .= '
        '; } @@ -1043,11 +1045,13 @@ function radio_station_show_list_shortcode( $type, $atts ) { $list .= '
  • '; // --- post excerpt --- + $post_content = $post['post_content']; + $post_id = $post['ID']; if ( 'none' == $atts['content'] ) { $list .= ''; } elseif ( 'full' == $atts['content'] ) { $list .= '
    '; - $content = apply_filters( 'radio_station_show_' . $type . '_content', $post['post_content'], $post['ID'] ); + $content = apply_filters( 'radio_station_show_' . $type . '_content', $post_content, $post_id ); // $list .= $content; $list .= '
    '; } else { @@ -1057,9 +1061,9 @@ function radio_station_show_list_shortcode( $type, $atts ) { $excerpt = $post['post_excerpt']; $excerpt .= ' ' . $more . ''; } else { - $excerpt = radio_station_trim_excerpt( $post['post_content'], $length, $more, $permalink ); + $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); } - $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post['ID'] ); + $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post_id ); $list .= $excerpt; $list .= '
    '; } @@ -1301,8 +1305,10 @@ function radio_station_current_show_shortcode( $atts ) { // --- maybe do AJAX load --- // 2.3.2 added widget AJAX loading - $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'current-show', $atts['widget'] ); - if ( 'on' == $atts['ajax'] ) { + $ajax = $atts['ajax']; + $widget = $atts['widget']; + $ajax = apply_filters( 'radio_station_widgets_ajax_override', $ajax, 'current-show', $widget ); + if ( 'on' == $ajax ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { // --- AJAX load via iframe --- @@ -1410,12 +1416,13 @@ function radio_station_current_show_shortcode( $atts ) { // --- set current show data --- $show = $current_shift['show']; + $show_id = $show['id']; // --- get show link --- $show_link = false; if ( $atts['show_link'] ) { - $show_link = get_permalink( $show['id'] ); - $show_link = apply_filters( 'radio_station_current_show_link', $show_link, $show['id'], $atts ); + $show_link = get_permalink( $show_id ); + $show_link = apply_filters( 'radio_station_current_show_link', $show_link, $show_id, $atts ); } // --- check show schedule --- @@ -1436,7 +1443,7 @@ function radio_station_current_show_shortcode( $atts ) { // (only if not a schedule override) // 2.3.2: fix to override variable key check if ( !isset( $current_shift['override'] ) && $atts['show_all_sched'] ) { - $shifts = radio_station_get_show_schedule( $show['id'] ); + $shifts = radio_station_get_show_schedule( $show_id ); } else { $shifts = array( $current_shift ); } @@ -1548,7 +1555,7 @@ function radio_station_current_show_shortcode( $atts ) { } $title .= '
    '; // 2.3.3.8: added current show title filter - $title = apply_filters( 'radio_station_current_show_title_display', $title, $show['id'], $atts ); + $title = apply_filters( 'radio_station_current_show_title_display', $title, $show_id, $atts ); if ( ( '' != $title ) && is_string( $title ) ) { $html['title'] = $title; } @@ -1559,8 +1566,8 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: get show avatar (with thumbnail fallback) // 2.3.0: filter show avatar via display context // 2.3.0: maybe add link from avatar to show - $show_avatar = radio_station_get_show_avatar( $show['id'] ); - $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show['id'], $atts ); + $show_avatar = radio_station_get_show_avatar( $show_id ); + $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { $avatar = '
    '; if ( $show_link ) { @@ -1571,7 +1578,7 @@ function radio_station_current_show_shortcode( $atts ) { $avatar .= '
    '; // 2.3.3.8: added avatar display filter - $avatar = apply_filters( 'radio_station_current_show_avatar_display', $avatar, $show['id'], $atts ); + $avatar = apply_filters( 'radio_station_current_show_avatar_display', $avatar, $show_id, $atts ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $html['avatar'] = $avatar; } @@ -1582,7 +1589,7 @@ function radio_station_current_show_shortcode( $atts ) { if ( $atts['display_hosts'] ) { $hosts = ''; - $show_hosts = get_post_meta( $show['id'], 'show_user_list', true ); + $show_hosts = get_post_meta( $show_id, 'show_user_list', true ); if ( $show_hosts && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { $hosts = '
    '; @@ -1630,7 +1637,7 @@ function radio_station_current_show_shortcode( $atts ) { } $hosts .= '
    '; } - $hosts = apply_filters( 'radio_station_current_show_hosts_display', $hosts, $show['id'], $atts ); + $hosts = apply_filters( 'radio_station_current_show_hosts_display', $hosts, $show_id, $atts ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $html['hosts'] = $hosts; } @@ -1652,7 +1659,7 @@ function radio_station_current_show_shortcode( $atts ) { $encore .= '
    '; } // 2.3.3.8: added encore display filter - $encore = apply_filters( 'radio_station_current_show_encore_display', $encore, $show['id'], $atts ); + $encore = apply_filters( 'radio_station_current_show_encore_display', $encore, $show_id, $atts ); if ( ( '' != $encore ) && is_string( $encore ) ) { $html['encore'] = $encore; } @@ -1674,7 +1681,7 @@ function radio_station_current_show_shortcode( $atts ) { $playlist .= '
    '; } // 2.3.3.8: added playlist diplay filter - $playlist = apply_filters( 'radio_station_current_show_playlist_display', $playlist, $show['id'], $atts ); + $playlist = apply_filters( 'radio_station_current_show_playlist_display', $playlist, $show_id, $atts ); if ( ( '' != $playlist ) && is_string( $playlist ) ) { $html['playlist'] = $playlist; } @@ -1690,8 +1697,8 @@ function radio_station_current_show_shortcode( $atts ) { if ( $atts['show_desc'] ) { // --- get show post --- - $show_post = get_post( $show['id'] ); - $permalink = get_permalink( $show_post->ID ); + $show_post = get_post( $show_id ); + $permalink = get_permalink( $show_id ); // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { @@ -1708,9 +1715,9 @@ function radio_station_current_show_shortcode( $atts ) { // --- filter excerpt by context --- // 2.3.0: added contextual filtering if ( $atts['widget'] ) { - $excerpt = apply_filters( 'radio_station_current_show_widget_excerpt', $excerpt, $show['id'], $atts ); + $excerpt = apply_filters( 'radio_station_current_show_widget_excerpt', $excerpt, $show_id, $atts ); } else { - $excerpt = apply_filters( 'radio_station_current_show_shortcode_excerpt', $excerpt, $show['id'], $atts ); + $excerpt = apply_filters( 'radio_station_current_show_shortcode_excerpt', $excerpt, $show_id, $atts ); } // --- set description --- @@ -1720,7 +1727,7 @@ function radio_station_current_show_shortcode( $atts ) { $description .= $excerpt; $description .= '
    '; } - $description = apply_filters( 'radio_station_current_show_description_display', $description, $show['id'], $atts ); + $description = apply_filters( 'radio_station_current_show_description_display', $description, $show_id, $atts ); if ( ( '' != $description ) && is_string( $description ) ) { $html['description'] = $description; } @@ -1729,7 +1736,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- output full show schedule --- // 2.3.2: do not display all shifts for overrides if ( $atts['show_all_sched'] && !isset( $current_shift['override'] ) ) { - $schedule = apply_filters( 'radio_station_current_show_shifts_display', $shift_display, $show['id'], $atts ); + $schedule = apply_filters( 'radio_station_current_show_shifts_display', $shift_display, $show_id, $atts ); if ( ( '' != $schedule ) && is_string( $schedule ) ) { $html['schedule'] = $schedule; } @@ -1737,7 +1744,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- custom HTML section --- // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show['id'], $atts ); + $html['custom'] = apply_filters( 'radio_station_current_show_custom_display', '', $show_id, $atts ); // --- open current show list item --- $output .= '
  • '; @@ -1802,7 +1809,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- for dynamic reloading --- if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current_show', $atts, $current_shift_end ); + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-show', $atts, $current_shift_end ); if ( $dynamic ) { $output .= $dynamic; } @@ -1832,7 +1839,7 @@ function radio_station_current_show() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-show' ); if ( RADIO_STATION_DEBUG ) { - print_r( $atts ); + echo "Current Show Shortcode Attributes: " . print_r( $atts, true ); } // --- output widget contents --- @@ -1968,8 +1975,10 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- maybe do AJAX load --- // 2.3.2 added widget AJAX loading - $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'upcoming-shows', $atts['widget'] ); - if ( 'on' == $atts['ajax'] ) { + $ajax = $atts['ajax']; + $widget = $atts['widget']; + $ajax = apply_filters( 'radio_station_widgets_ajax_override', $ajax, 'upcoming-shows', $widget ); + if ( 'on' == $ajax ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { // --- AJAX load via iframe --- @@ -2093,12 +2102,13 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- get show data --- $show = $shift['show']; + $show_id = $show['id']; // --- set show link --- $show_link = false; if ( $atts['show_link'] ) { - $show_link = get_permalink( $show['id'] ); - $show_link = apply_filters( 'radio_station_upcoming_show_link', $show_link, $show['id'], $atts ); + $show_link = get_permalink( $show_id ); + $show_link = apply_filters( 'radio_station_upcoming_show_link', $show_link, $show_id, $atts ); } // --- check show schedule --- @@ -2184,7 +2194,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $title .= esc_html( $show['name'] ); } $title .= '
  • '; - $title = apply_filters( 'radio_station_upcoming_show_title_display', $title, $show['id'], $atts ); + $title = apply_filters( 'radio_station_upcoming_show_title_display', $title, $show_id, $atts ); if ( ( '' != $title ) && is_string( $title ) ) { $html['title'] = $title; } @@ -2196,8 +2206,8 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: filter show avatar by context // 2.3.0: maybe link avatar to show $avatar = ''; - $show_avatar = radio_station_get_show_avatar( $show['id'] ); - $show_avatar = apply_filters( 'radio_station_upcoming_show_avatar', $show_avatar, $show['id'], $atts ); + $show_avatar = radio_station_get_show_avatar( $show_id ); + $show_avatar = apply_filters( 'radio_station_upcoming_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { $avatar = '
    '; if ( $atts['show_link'] ) { @@ -2209,7 +2219,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } $avatar .= '
    '; } - $avatar = apply_filters( 'radio_station_upcoming_show_avatar_display', $avatar, $show['id'], $atts ); + $avatar = apply_filters( 'radio_station_upcoming_show_avatar_display', $avatar, $show_id, $atts ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $html['avatar'] = $avatar; } @@ -2219,7 +2229,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { if ( $atts['display_hosts'] ) { $hosts = ''; - $show_hosts = get_post_meta( $show['id'], 'show_user_list', true ); + $show_hosts = get_post_meta( $show_id, 'show_user_list', true ); if ( isset( $show_hosts ) && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { $hosts = '
    '; @@ -2266,7 +2276,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { } $hosts .= '
    '; } - $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show['id'], $atts ); + $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show_id, $atts ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $html['hosts'] = $hosts; } @@ -2282,7 +2292,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $encore .= esc_html( __( 'Encore Presentation', 'radio-station' ) ); $encore .= '
    '; } - $encore = apply_filters( 'radio_station_upcoming_show_encore_display', $encore, $show['id'], $atts ); + $encore = apply_filters( 'radio_station_upcoming_show_encore_display', $encore, $show_id, $atts ); if ( ( '' != $encore ) && is_string( $encore ) ) { $html['encore'] = $encore; } @@ -2295,7 +2305,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- set show schedule --- if ( $atts['show_sched'] ) { - $schedule = apply_filters( 'radio_station_upcoming_show_shifts_display', $shift_display, $show['id'], $atts ); + $schedule = apply_filters( 'radio_station_upcoming_show_shifts_display', $shift_display, $show_id, $atts ); if ( ( '' != $schedule ) && is_string( $schedule ) ) { $html['shift'] = $schedule; } @@ -2303,7 +2313,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- custom HTML section --- // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_upcoming_shows_custom_display', '', $show['id'], $atts ); + $html['custom'] = apply_filters( 'radio_station_upcoming_shows_custom_display', '', $show_id, $atts ); // --- open upcoming show list item --- $output .= '
  • '; @@ -2357,7 +2367,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- for dynamic reloading --- if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'upcoming_shows', $atts, $next_start_time ); + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'upcoming-shows', $atts, $next_start_time ); if ( $dynamic ) { $output .= $dynamic; } @@ -2386,7 +2396,7 @@ function radio_station_upcoming_shows() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'upcoming-shows' ); if ( RADIO_STATION_DEBUG ) { - print_r( $atts ); + echo "Upcoming Shows Shortcode Attributes: " . print_r( $atts, true ); } // --- output widget contents --- @@ -2497,8 +2507,10 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- maybe do AJAX load --- // 2.3.2 added widget AJAX loading - $atts['ajax'] = apply_filters( 'radio_station_widgets_ajax_override', $atts['ajax'], 'current-playlist', $atts['widget'] ); - if ( 'on' == $atts['ajax'] ) { + $ajax = $atts['ajax']; + $widget = $atts['widget']; + $ajax = apply_filters( 'radio_station_widgets_ajax_override', $ajax, 'current-playlist', $widget ); + if ( 'on' == $ajax ) { if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { // --- AJAX load via iframe --- @@ -2629,7 +2641,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- for dynamic reloading --- if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current_playlist', $atts, $shift_end_time ); + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-playlist', $atts, $shift_end_time ); if ( $dynamic ) { $html['countdown'] .= $dynamic; } @@ -2756,6 +2768,7 @@ function radio_station_current_playlist_shortcode( $atts ) { $no_current_playlist = apply_filters( 'radio_station_no_current_playlist_text', $no_current_playlist, $atts ); $no_playlist .= $no_current_playlist; $no_playlist .= '
  • '; + // 2.3.3.8: added no playlist display filter $no_playlist = apply_filters( 'radio_station_current_playlist_no_playlist_display', $no_playlist, $atts ); if ( ( '' != $no_playlist ) && is_string( $no_playlist ) ) { @@ -2789,6 +2802,9 @@ function radio_station_current_playlist() { // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'current-playlist' ); + if ( RADIO_STATION_DEBUG ) { + echo "Current Playlist Shortcode Attributes: " . print_r( $atts, true ); + } // --- output widget contents --- echo '
    '; diff --git a/includes/support-functions.php b/includes/support-functions.php index c96c97d..3888d37 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -905,6 +905,7 @@ function radio_station_get_show_data_meta( $show, $single = false ) { } // --- add extra data for single show route/feed --- + $show_id = $show->ID; if ( $single ) { // --- add show posts --- @@ -914,11 +915,11 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $show_data['playlists'] = radio_station_get_show_playlists( $show->ID ); // --- filter to maybe add more data --- - $show_data = apply_filters( 'radio_station_show_data_meta', $show_data, $show->ID ); + $show_data = apply_filters( 'radio_station_show_data_meta', $show_data, $show_id ); } // --- maybe cache Show meta data --- - do_action( 'radio_station_cache_data', 'show_meta', $show->ID, $show_data ); + do_action( 'radio_station_cache_data', 'show_meta', $show_id, $show_data ); return $show_data; } @@ -1037,7 +1038,8 @@ function radio_station_get_override_data_meta( $override ) { ); // --- filter and return --- - $override_data = apply_filters( 'radio_station_override_data', $override_data, $override->ID ); + $override_id = $override->ID; + $override_data = apply_filters( 'radio_station_override_data', $override_data, $override_id ); return $override_data; } diff --git a/radio-station.php b/radio-station.php index fd23203..27e2363 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.7 +Version: 2.3.3.8 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -1728,7 +1728,7 @@ function radio_station_email_address( $email, $post_id ) { // Automatic Pages Content Filter // ------------------------------ // 2.3.0: standalone filter for automatic page content -// 2.3.1: re-add filter so the_content can be processed multuple times +// 2.3.1: re-add filter so the_content can be processed multiple times // 2.3.3.6: set automatic content early and clear existing content add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); function radio_station_automatic_pages_content_set( $content ) { @@ -1926,7 +1926,8 @@ function radio_station_single_content_template( $content, $post_type ) { // --- filter and return buffered content --- $output = str_replace( '', $content, $output ); - $output = apply_filters( 'radio_station_content_' . $post_type, $output, get_the_ID() ); + $post_id = get_the_ID(); + $output = apply_filters( 'radio_station_content_' . $post_type, $output, $post_id ); return $output; } @@ -2237,10 +2238,11 @@ function radio_station_add_show_links( $content ) { // note: playlists are linked via single-playlist-content.php template // --- filter to allow related post types --- + $post_type = $post->post_type; $post_types = array( 'post' ); $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); - if ( in_array( $post->post_type, $post_types ) ) { + if ( in_array( $post_type, $post_types ) ) { // --- link show posts --- $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); @@ -2260,14 +2262,14 @@ function radio_station_add_show_links( $content ) { if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { $positions = array( 'after' ); - $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post->post_type, $post ); + $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post_type, $post ); if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { // --- set related shows link(s) --- // 2.3.3.6: get all related show links $show_links = ''; - $hash_ref = '#show-' . str_replace( 'rs-', '', $post->post_type ) . 's'; + $hash_ref = '#show-' . str_replace( 'rs-', '', $post_type ) . 's'; foreach ( $related_shows as $related_show ) { $show = get_post( $related_show ); $title = $show->post_title; @@ -2280,7 +2282,7 @@ function radio_station_add_show_links( $content ) { // --- set post type labels --- $before = $after = ''; - $post_type_object = get_post_type_object( $post->post_type ); + $post_type_object = get_post_type_object( $post_type ); $singular = $post_type_object->labels->singular_name; $plural = $post_type_object->labels->name; diff --git a/readme.md b/readme.md index 145b345..eab6259 100644 --- a/readme.md +++ b/readme.md @@ -190,7 +190,7 @@ You may translate the plugin into another language. Please visit our [WordPress #### 2.3.3.8 * Updated Plugin Panel Library -* Added Stream Format selection setting +* Added Stream Format, Station Name and Image settings * Added Station email address setting with default display option * Added Section order filtering for Master Schedules and Widgets * Added Show image alignment attribute to Schedule Tabs View diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 48eb5c6..7983b64 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -49,7 +49,7 @@ // --- set list info key order --- // 2.3.3.8: added for possible info rearrangement $infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'excerpt', 'custom' ); -$infokeys = apply_filters( 'radio_station_schedule_table_info_order', $infokeys ); +$infokeys = apply_filters( 'radio_station_schedule_list_info_order', $infokeys ); // --- start list schedule output --- $list = '
      ' . $newline; @@ -160,6 +160,7 @@ // 2.3.3.8: reset info array $show = $shift['show']; + $show_id = $show['id']; $info = array(); $split_id = false; @@ -205,13 +206,14 @@ // 2.3.0: filter show link by show and context $show_link = false; if ( $atts['show_link'] ) { - $show_link = apply_filters( 'radio_station_schedule_show_link', $show['url'], $show['id'], 'list' ); + $show_link = $show['url']; } + $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show_id, 'list' ); // --- list item classes --- // 2.3.0: add genre classes for highlighting $classes = array( 'master-list-day-item' ); - $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); + $terms = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG, array() ); if ( $terms && ( count( $terms ) > 0 ) ) { foreach ( $terms as $term ) { $classes[] = strtolower( $term->slug ); @@ -234,8 +236,8 @@ // --- show avatar --- if ( $atts['show_image'] ) { // 2.3.0: filter show avatar via show ID and context - $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); - $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'list' ); + $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); + $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show_id, 'list' ); if ( $show_avatar ) { $avatar = '
      ' . $newline; if ( $show_link ) { @@ -244,7 +246,7 @@ $avatar .= $show_avatar; } $avatar .= '
      ' . $newline; - $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'list' ); + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'list' ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $info['avatar'] = $avatar; // $list .= $avatar; @@ -261,7 +263,7 @@ $title = '' . $newline; $title .= $show_title; $title .= '' . $newline; - $title = apply_filters( 'radio_station_schedule_show_title', $title, $show['id'], 'list' ); + $title = apply_filters( 'radio_station_schedule_show_title', $title, $show_id, 'list' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; // $list .= $title; @@ -300,12 +302,12 @@ } // 2.3.3.5: fix to incorrect context value (tabs) - $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'list' ); + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'list' ); if ( $show_hosts ) { $hosts = '
      ' . $newline; $hosts .= $show_hosts; $hosts .= '
      ' . $newline; - $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'list' ); + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'list' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; // $list .= $hosts; @@ -345,10 +347,10 @@ } // 2.3.3.8: moved filter out and added display filter - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'list', $shift ); + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'list', $shift ); $times = '
      '; $file .= esc_html( $anchor ); $file .= '' . $newline; $file .= '
      ' . $newline; - $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'list' ); + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'list' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; // $list .= $file; @@ -419,7 +421,7 @@ $genres .= implode( ', ', $show_genres ); } $genres .= '
    ' . $newline; - $genres = apply_filters( 'radio_station_schedule_show_genres_display', $genres, $show['id'], 'list' ); + $genres = apply_filters( 'radio_station_schedule_show_genres_display', $genres, $show_id, 'list' ); if ( ( '' != $genres ) && is_string( $genres ) ) { $info['genres'] = $genres; // $list .= $genres; @@ -429,8 +431,8 @@ // --- show description --- if ( $atts['show_desc'] ) { - $show_post = get_post( $show['id'] ); - $permalink = get_permalink( $show_post->ID ); + $show_post = get_post( $show_id ); + $permalink = get_permalink( $show_id ); // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { @@ -439,7 +441,7 @@ } else { $show_excerpt = radio_station_trim_excerpt( $show_post->post_content, $length, $more, $permalink ); } - $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'list' ); + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'list' ); // --- set excerpt display --- $excerpt = '
    ' . $newline; @@ -453,7 +455,7 @@ // --- custom info section --- // 2.3.3.8: allow for custom HTML to be added - $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'list' ); + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show_id, 'list' ); if ( ( '' != $custom ) && is_string( $custom ) ) { $info['custom'] = $custom; } diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 526293a..a641f51 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -374,9 +374,10 @@ // 2.3.3.8: reset info array for each cell $info = array(); $show = $shift['show']; + $show_id = $show['id']; // --- set the show div classes --- - $divclasses = array( 'master-show-entry', 'show-id-' . $show['id'], $show['slug'] ); + $divclasses = array( 'master-show-entry', 'show-id-' . $show_id, $show['slug'] ); if ( $nowplaying ) { $divclasses[] = 'nowplaying'; } @@ -423,15 +424,15 @@ if ( $atts['show_link'] ) { $show_link = $show['url']; } - $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'table' ); + $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show_id, 'table' ); // --- show logo / thumbnail --- // 2.3.0: filter show avatar via show ID and context $show_avatar = false; if ( $atts['show_image'] ) { - $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); + $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); } - $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'table' ); + $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show_id, 'table' ); if ( $show_avatar ) { $avatar = '
    ' . $newline; if ( $show_link ) { @@ -440,7 +441,7 @@ $avatar .= $show_avatar . $newline; } $avatar .= '
    ' . $newline; - $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'table' ); + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'table' ); if ( ( '' != $avatar ) && is_string( $avatar ) ) { $info['avatar'] = $avatar; // $cell .= $avatar; @@ -455,7 +456,7 @@ $title .= esc_html( $show['name'] ) . $newline; } $title .= '
    ' . $newline; - $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show['id'], 'table' ); + $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'table' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; // $cell .= $title; @@ -491,12 +492,12 @@ } } - $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'table' ); + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'table' ); if ( $show_hosts ) { $hosts = '
    ' . $newline; $hosts .= $show_hosts; $hosts .= '
    ' . $newline; - $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'table' ); + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'table' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; // $cell .= $hosts; @@ -536,10 +537,10 @@ } // --- add show time to cell --- - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'table', $shift ); + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'table', $shift ); $times = '
    '; $file .= esc_html( $anchor ); $file .= '' . $newline; $file .= '
    ' . $newline; - $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show['id'], 'table' ); + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'table' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; // $cell .= $link; @@ -607,13 +610,13 @@ } // --- filter excerpt by context --- - $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'table' ); + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'table' ); // --- output excerpt --- $excerpt = '
    ' . $newline; $excerpt .= $show_excerpt . $newline; $excerpt .= '
    ' . $newline; - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpy, $show['id'], 'table' ); + $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpy, $show_id, 'table' ); if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { $info['excerpt'] = $excerpt; // $cell .= $excerpt; @@ -622,7 +625,7 @@ // --- custom info section --- // 2.3.3.8: allow for custom HTML to be added - $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'table' ); + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show_id, 'table' ); if ( ( '' != $custom ) && is_string( $custom ) ) { $info['custom'] = $custom; } diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index bc3cc27..9556b82 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -218,10 +218,11 @@ $split_id = false; $show_link = false; + $show_id = $show['id']; if ( $atts['show_link'] ) { $show_link = $show['url']; } - $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'tabs' ); + $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show_id, 'tabs' ); // --- convert shift time data --- // 2.3.2: replace strtotime with to_time for timezones @@ -273,7 +274,7 @@ // 2.3.0: add genre classes for highlighting $classes = array( 'master-schedule-tabs-show' ); - $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); + $terms = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG, array() ); if ( $terms && ( count( $terms ) > 0 ) ) { foreach ( $terms as $term ) { $classes[] = strtolower( $term->slug ); @@ -308,8 +309,8 @@ // --- get show avatar image --- // 2.3.0: filter show avatar by show and context // 2.3.0: maybe link avatar to show - $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); - $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'tabs' ); + $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); + $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show_id, 'tabs' ); // --- set show image classes --- $classes = array( 'show-image' ); @@ -331,7 +332,7 @@ } else { $avatar = '
    ' . $newline; } - $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show['id'], 'tabs' ); + $avatar = apply_filters( 'radio_station_schedule_show_avatar_display', $avatar, $show_id, 'tabs' ); // $panels .= $avatar; } @@ -344,7 +345,7 @@ $title = '
    ' . $newline; $title .= $show_title . $newline; $title .= '
    ' . $newline; - $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show['id'], 'tabs' ); + $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'tabs' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; // $panels .= $title; @@ -380,12 +381,12 @@ } } - $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show['id'], 'tabs' ); + $show_hosts = apply_filters( 'radio_station_schedule_show_hosts', $show_hosts, $show_id, 'tabs' ); if ( $show_hosts ) { $hosts = '
    ' . $newline; $hosts .= $show_hosts; $hosts .= '
    ' . $newline; - $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show['id'], 'tabs' ); + $hosts = apply_filters( 'radio_station_schedule_show_hosts_display', $hosts, $show_id, 'tabs' ); if ( ( '' != $hosts ) && is_string( $hosts ) ) { $info['hosts'] = $hosts; // $panels .= $hosts; @@ -426,10 +427,10 @@ } // 2.3.3.8: moved show time filter out and added display filter - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'tabs', $shift ); + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'tabs', $shift ); $times = '
    '; $file .= esc_html( $anchor ); $file .= '' . $newline; $file .= '
    ' . $newline; - $file = apply_filters( 'radio_station_schedule_show_file_display', $file, show_file, $show['id'], 'tabs' ); + $file = apply_filters( 'radio_station_schedule_show_file_display', $file, $show_file, $show_id, 'tabs' ); if ( ( '' != $file ) && is_string( $file ) ) { $info['file'] = $file; // $panels .= $link; @@ -498,7 +499,7 @@ $genres .= implode( ', ', $show_genres ); } $genres .= '
    ' . $newline; - $genres = apply_filters( 'radio_station_schedule_show_genres', $genres, $show['id'], 'tabs' ); + $genres = apply_filters( 'radio_station_schedule_show_genres', $genres, $show_id, 'tabs' ); if ( ( '' != $genres ) && is_string( $genres ) ) { $info['genres'] = $genres; // $panels .= $genres; @@ -507,7 +508,7 @@ // --- custom info section --- // 2.3.3.8: allow for custom HTML to be added - $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show['id'], 'tabs' ); + $custom = apply_filters( 'radio_station_schedule_show_custom_display', '', $show_id, 'tabs' ); if ( ( '' != $custom ) && is_string( $custom ) ) { $info['custom'] = $custom; } @@ -561,8 +562,8 @@ // --- show description --- if ( $atts['show_desc'] ) { - $show_post = get_post( $show['id'] ); - $permalink = get_permalink( $show_post->ID ); + $show_post = get_post( $show_id ); + $permalink = get_permalink( $show_id ); // --- get show excerpt --- if ( !empty( $show_post->post_excerpt ) ) { @@ -573,13 +574,13 @@ } // --- filter excerpt by context --- - $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show['id'], 'tabs' ); + $show_excerpt = apply_filters( 'radio_station_schedule_show_excerpt', $show_excerpt, $show_id, 'tabs' ); // --- output excerpt --- $excerpt = '
    ' . $newline; $excerpt .= $show_excerpt . $newline; $excerpt .= '
    ' . $newline; - $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpt, $show['id'], 'tabs' ); + $excerpt = apply_filters( 'radio_station_schedule_show_excerpt_display', $excerpt, $show_id, 'tabs' ); if ( ( '' != $excerpt ) && is_string( $excerpt ) ) { $panels .= $excerpt; } diff --git a/templates/single-show-content.php b/templates/single-show-content.php index ef91e22..8e6e639 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -782,7 +782,8 @@ $i ++; } } -$sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id, $post_id ); +// 2.3.3.8: remove duplicate post_id filter argument +$sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id ); // ----------------------- From 5149c69437db139b1cf36615862f5c6a2b1d700d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 22 Feb 2021 15:19:07 +1000 Subject: [PATCH 182/377] add Filters.md to documentation table --- docs/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 3f642a9..4fe6a1a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,8 @@ | [Roles](./Roles.md) | Plugin Roles and related Capabilities | | [Widgets](./Widgets.md) | Current Show, Upcoming Shows, Playlist, Clock, Player | | [Shortcodes](./Shortcodes.md) | Data List Archives and Master Schedule Views | -| [Data](./Data.md) | Post Types, Taxonomies and Data Filters | +| [Data](./Data.md) | Post Types and Taxonomies | +| [Filters](./Filters.md) | Custom Development Value Filters | | [API](./API.md) | Data API via REST and Feed Endpoints | [Roadmap](./Roadmap.md) | Feature Roadmap for Free and Pro Versions | | [Changelog](../CHANGELOG.md) | Log of Changes for each Release | From 4e711dc78621fbf3f30bae640cd72f4acc74ed2d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 22 Feb 2021 15:41:18 +1000 Subject: [PATCH 183/377] schedule tab click fix --- includes/master-schedule.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 3cb5c5a..adf6dd3 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -647,8 +647,9 @@ function radio_station_master_schedule_tabs_js() { } */ // 2.3.3.6: allow for clicking on date to change days + // 2.3.3.8: make entire heading label div clickable to change tabs $js = "jQuery(document).ready(function() { - jQuery('.master-schedule-tabs-day-name, .master-schedule-tabs-date').bind('click', function (event) { + jQuery('.master-schedule-tabs-headings').bind('click', function (event) { headerID = jQuery(event.target).closest('li').attr('id'); panelID = headerID.replace('header', 'day'); jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); From dbdae8ee83ef2d24ad15dd38f77002d6a1475202 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 24 Feb 2021 23:54:46 -0500 Subject: [PATCH 184/377] Update Stable tag in readme.txt Update stable tag to 2.3.3.8, needed for language translation fix --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index d42086e..5bf235d 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.6 -Stable tag: 2.3.3.7 +Stable tag: 2.3.3.8 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 596b8c6e6afeb3fb844794c53178910dcc984cd3 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Thu, 25 Feb 2021 00:02:16 -0500 Subject: [PATCH 185/377] changes to readme files Updated readme.md to include all readme.txt changes in 2.3.3.8. Fixed stable tag to version number instead of trunk to fix issues with translations caught by Michael Torbert --- readme.md | 20 +++++++++++++++----- readme.txt | 1 + 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index eab6259..57d03d0 100644 --- a/readme.md +++ b/readme.md @@ -189,11 +189,21 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices #### 2.3.3.8 -* Updated Plugin Panel Library -* Added Stream Format, Station Name and Image settings -* Added Station email address setting with default display option -* Added Section order filtering for Master Schedules and Widgets -* Added Show image alignment attribute to Schedule Tabs View +* Update: Plugin Panel (1.1.7) with Image and Color Picker fields +* Added: Stream Format and Fallback/Format selection setting +* Added: Station Email Address setting with default display option +* Added: Section order filtering for Master Schedule Views +* Added: Section display filtering for Master Schedule Views +* Added: Section display filtering for Widget sections +* Added: Show image alignment attribute to Schedule Tabs View +* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) +* Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Added: Screen Reader text for Show icons on Show Page +* Fixed: Display Widget Countdown when no Current Show/Playlist +* Fixed: Check for explicit singular.php template usage setting +* Fixed: Access to Shows Data via querystring of Show ID/name +* Fixed: Shows Data for Genres/Languages querystring of ID/name +* Fixed: Changed stable tag from trunk to version number to fix translations issue #### 2.3.3.7 * Updated Freemius SDK and Plugin Loader libraries diff --git a/readme.txt b/readme.txt index 5bf235d..7fa0538 100644 --- a/readme.txt +++ b/readme.txt @@ -213,6 +213,7 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Fixed: Check for explicit singular.php template usage setting * Fixed: Access to Shows Data via querystring of Show ID/name * Fixed: Shows Data for Genres/Languages querystring of ID/name +* Fixed: Changed stable tag from trunk to version number to fix translations issue = 2.3.3.7 = * Fixed: Schedule Overrides overlapping multiple Show shifts From bb328013a37812b93049d383cca6923814ea3376 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 3 Mar 2021 15:57:01 +1000 Subject: [PATCH 186/377] readme fixes --- readme.md | 24 +++++++----------------- readme.txt | 2 +- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/readme.md b/readme.md index 57d03d0..a3da43f 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.6 +Tested up to: 5.6.2 -Stable tag: trunk +Stable tag: 2.3.3.8 License: GPLv2 or later @@ -189,21 +189,11 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices #### 2.3.3.8 -* Update: Plugin Panel (1.1.7) with Image and Color Picker fields -* Added: Stream Format and Fallback/Format selection setting -* Added: Station Email Address setting with default display option -* Added: Section order filtering for Master Schedule Views -* Added: Section display filtering for Master Schedule Views -* Added: Section display filtering for Widget sections -* Added: Show image alignment attribute to Schedule Tabs View -* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) -* Added: Reduced opacity for past Shows on Schedule Tab/Table Views -* Added: Screen Reader text for Show icons on Show Page -* Fixed: Display Widget Countdown when no Current Show/Playlist -* Fixed: Check for explicit singular.php template usage setting -* Fixed: Access to Shows Data via querystring of Show ID/name -* Fixed: Shows Data for Genres/Languages querystring of ID/name -* Fixed: Changed stable tag from trunk to version number to fix translations issue +* Updated Plugin Panel Library +* Added Stream Format selection setting +* Added Station email address setting with default display option +* Added Section order filtering for Master Schedules and Widgets +* Added Show image alignment attribute to Schedule Tabs View #### 2.3.3.7 * Updated Freemius SDK and Plugin Loader libraries diff --git a/readme.txt b/readme.txt index 7fa0538..865bfb0 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.6 +Tested up to: 5.6.2 Stable tag: 2.3.3.8 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 1c71ddb400791f7927a0ca32c9f3f7f5b55aa08c Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 3 Mar 2021 16:01:31 +1000 Subject: [PATCH 187/377] dev updates including #322 --- CHANGELOG.md | 4 ++++ includes/support-functions.php | 8 +++----- loader.php | 14 +++++++++----- radio-station.php | 13 +++++++------ readme.md | 24 +++++++----------------- readme.txt | 2 +- 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d8770..b080352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.3.3.9 +* Update: Plugin Panel (1.1.8) with Number Step Min/Max fix +* Fixed: 24 Schedule Time format in Shift Scheduel for Shows Data Endpoint + ### 2.3.3.8 * Update: Plugin Panel (1.1.7) with Image and Color Picker fields & Documentation: Full Plugin Filter List added to docs/Filters.md diff --git a/includes/support-functions.php b/includes/support-functions.php index 3888d37..998871f 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -472,9 +472,6 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, return $day_shifts; } - - - // ---------------------- // Get Schedule Overrides // ---------------------- @@ -4001,8 +3998,9 @@ function radio_station_convert_show_shifts( $show ) { if ( isset( $show['schedule'] ) ) { $schedule = $show['schedule']; foreach ( $schedule as $i => $shift ) { - $start_hour = substr( radio_station_convert_hour( $shift['start_hour'] . $shift['start_meridian'] ), 0, 2 ); - $end_hour = substr( radio_station_convert_hour( $shift['end_hour'] . $shift['end_meridian'] ), 0, 2 ); + // 2.3.3.9: fixed to not use radio_station_convert_hour + $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); + $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); $schedule[$i] = array( 'day' => $shift['day'], 'start' => $start_hour . ':' . $shift['start_min'], diff --git a/loader.php b/loader.php index e08123b..d2cbbed 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.7 +// Version: 1.1.8 // -------------- // Note: Changelog and structure at end of file. // @@ -2125,12 +2125,13 @@ public function settings_table() { // --- number input step script --- // 1.0.9: added to script array + // 1.1.8: fix to check for no mix or max value $script = "function settings_number_step(updown, id, min, max, step) { if (updown == 'up') {multiplier = 1;} else if (updown == 'down') {multiplier = -1;} current = parseInt(document.getElementById(id).value); newvalue = current + (multiplier * parseInt(step)); - if (newvalue < parseInt(min)) {newvalue = min;} - if (newvalue > parseInt(max)) {newvalue = max;} + if ((min !== false) && (newvalue < parseInt(min))) {newvalue = min;} + if ((max !== false) && (newvalue > parseInt(max))) {newvalue = max;} document.getElementById(id).value = newvalue; }"; $this->scripts[] = $script; @@ -2576,12 +2577,12 @@ public function setting_row( $option ) { if ( isset( $option['min'] ) ) { $min = $option['min']; } else { - $min = false; + $min = 'false'; } if ( isset( $option['max'] ) ) { $max = $option['max']; } else { - $max = false; + $max = 'false'; } if ( isset( $option['step'] ) ) { $step = $option['step']; @@ -3055,6 +3056,9 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.1.8 == +// - fix to number step if no min or max value + // == 1.1.7 == // - added media library upload image field type // - added color picker and color picker alpha field types diff --git a/radio-station.php b/radio-station.php index ad08b2a..172d724 100644 --- a/radio-station.php +++ b/radio-station.php @@ -2,7 +2,7 @@ /** * @package Radio Station - * @version 2.3.3.7 + * @version 2.3.3 */ /* @@ -3049,12 +3049,13 @@ function radio_station_clear_transients() { $clear_transients = radio_station_get_setting( 'clear_transients' ); if ( RADIO_STATION_DEBUG || ( 'yes' == $clear_transients ) ) { // 2.3.2: do not clear on AJAX calls - if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - // 2.3.3: remove current show transient - // delete_transient( 'radio_station_current_show' ); + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return; } + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_next_show' ); + // 2.3.3: remove current show transient + // delete_transient( 'radio_station_current_show' ); } } diff --git a/readme.md b/readme.md index 57d03d0..a3da43f 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.6 +Tested up to: 5.6.2 -Stable tag: trunk +Stable tag: 2.3.3.8 License: GPLv2 or later @@ -189,21 +189,11 @@ You may translate the plugin into another language. Please visit our [WordPress ## Upgrade Notices #### 2.3.3.8 -* Update: Plugin Panel (1.1.7) with Image and Color Picker fields -* Added: Stream Format and Fallback/Format selection setting -* Added: Station Email Address setting with default display option -* Added: Section order filtering for Master Schedule Views -* Added: Section display filtering for Master Schedule Views -* Added: Section display filtering for Widget sections -* Added: Show image alignment attribute to Schedule Tabs View -* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) -* Added: Reduced opacity for past Shows on Schedule Tab/Table Views -* Added: Screen Reader text for Show icons on Show Page -* Fixed: Display Widget Countdown when no Current Show/Playlist -* Fixed: Check for explicit singular.php template usage setting -* Fixed: Access to Shows Data via querystring of Show ID/name -* Fixed: Shows Data for Genres/Languages querystring of ID/name -* Fixed: Changed stable tag from trunk to version number to fix translations issue +* Updated Plugin Panel Library +* Added Stream Format selection setting +* Added Station email address setting with default display option +* Added Section order filtering for Master Schedules and Widgets +* Added Show image alignment attribute to Schedule Tabs View #### 2.3.3.7 * Updated Freemius SDK and Plugin Loader libraries diff --git a/readme.txt b/readme.txt index 7fa0538..865bfb0 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.6 +Tested up to: 5.6.2 Stable tag: 2.3.3.8 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From 28da0c6c46ba589451b6c6e351acb262ba8d3c2e Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 4 Mar 2021 11:41:20 +1000 Subject: [PATCH 188/377] legacy view day heading fix --- includes/master-schedule.php | 16 ++++++++-------- templates/master-schedule-legacy.php | 9 +++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/includes/master-schedule.php b/includes/master-schedule.php index adf6dd3..aef212d 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -55,18 +55,18 @@ function radio_station_master_schedule( $atts ) { // --- control display options --- 'selector' => 1, 'clock' => $clock, - 'timezone' => 1, + 'timezone' => 1, // --- schedule display options --- 'time' => $time_format, - 'show_times' => 1, + 'show_times' => 1, 'show_link' => 1, 'view' => 'table', - 'days' => false, - 'start_day' => false, - 'display_day' => 'short', - 'display_date' => 'jS', - 'display_month' => 'short', + 'days' => false, + 'start_day' => false, + 'display_day' => 'short', + 'display_date' => 'jS', + 'display_month' => 'short', // --- converted and deprecated --- // 'list' => 0, @@ -75,7 +75,7 @@ function radio_station_master_schedule( $atts ) { // --- show display options --- 'show_image' => 0, - 'show_desc' => 0, + 'show_desc' => 0, 'show_hosts' => 0, 'link_hosts' => 0, 'show_genres' => 0, diff --git a/templates/master-schedule-legacy.php b/templates/master-schedule-legacy.php index ac5c7a9..7461eb2 100644 --- a/templates/master-schedule-legacy.php +++ b/templates/master-schedule-legacy.php @@ -16,8 +16,13 @@ $output .= ' '; foreach ( $days_of_the_week as $weekday => $info ) { // 2.2.2: fix to translate incorrect variable (heading) - $heading = substr( $weekday, 0, 3 ); - $heading = radio_station_translate_weekday( $heading ); + // 2.3.3.8: remove abbreviation and add support for display_day attribute + // $heading = substr( $weekday, 0, 3 ); + if ( 'short' == $atts['display_day'] ) { + $heading = radio_station_translate_weekday( $weekday, true ); + } else { + $heading = radio_station_translate_weekday( $weekday ); + } $output .= '' . $heading . ''; } $output .= ''; From 71eda7514c8f66aeadb107dfc4da991e538fb334 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 5 Mar 2021 09:42:32 +1000 Subject: [PATCH 189/377] dev update fix #319 --- includes/shortcodes.php | 6 ++++-- templates/master-schedule-table.php | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 030c238..fd1dd0b 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1597,7 +1597,8 @@ function radio_station_current_show_shortcode( $atts ) { $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; - $host_count = count( $hosts ); + // 2.3.3.9: fix to host count + $host_count = count( $show_hosts ); foreach ( $show_hosts as $host ) { $count ++; @@ -2236,7 +2237,8 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; $count = 0; - $host_count = count( $hosts ); + // 2.3.3.9: fix to host count + $host_count = count( $show_hosts ); foreach ( $show_hosts as $host ) { $count ++; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index a641f51..346168e 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -473,7 +473,7 @@ $show_hosts .= ' ' . $newline; $count = 0; - $hostcount = count( $show['hosts'] ); + $host_count = count( $show['hosts'] ); foreach ( $show['hosts'] as $host ) { $count ++; // 2.3.0: added link_hosts attribute check @@ -483,10 +483,10 @@ $show_hosts .= esc_html( $host['name'] ); } - if ( ( ( 1 === $count ) && ( 2 === $hostcount ) ) - || ( ( $hostcount > 2 ) && ( ( $hostcount - 1 ) === $count ) ) ) { + if ( ( ( 1 === $count ) && ( 2 === $host_count ) ) + || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) === $count ) ) ) { $show_hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - } elseif ( ( $count < $hostcount ) && ( $hostcount > 2 ) ) { + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { $show_hosts .= ', '; } } From ba640bb1e62b921c3038ee001e92cf58ef1902ce Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 9 Mar 2021 12:16:34 +1000 Subject: [PATCH 190/377] dev updates including #322 --- CHANGELOG.md | 3 ++- docs/Shortcodes.md | 16 +++++++++++++ includes/data-feeds.php | 35 ++++++++++++++++------------- includes/master-schedule.php | 17 +++++++------- includes/support-functions.php | 21 +++++++++++++++-- radio-station.php | 2 +- templates/master-schedule-table.php | 6 +++-- templates/master-schedule-tabs.php | 6 +++-- 8 files changed, 73 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b080352..02e6ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.9 * Update: Plugin Panel (1.1.8) with Number Step Min/Max fix -* Fixed: 24 Schedule Time format in Shift Scheduel for Shows Data Endpoint +* Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch +* Fixed: Multiple host separator display in Current Show Widget ### 2.3.3.8 * Update: Plugin Panel (1.1.7) with Image and Color Picker fields diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index a880276..d0bc15d 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -47,6 +47,22 @@ The following attributes are available for the shortcode: Example: Display the schedule in 24-hour time format, use `[master-schedule time="24"]`. +##### [Pro] Multi-View Switching + +In Radio Station Pro, you can display multiple views which the user can switch between. This gives your visitors better access to your schedule in the way that is better suited to them. You can set the available views in the plugin settings for the automatic schedule page as well as the default view. Alternatively you can use the Master Schedule Shortcode as normal on a page and provide a comma-separated list of views (the first value provided will be used as the default view.) eg. `[master-schedule view="table,tabs"]` + +Further, if you wish to customize the attributes for each particular view, you can use the `radio_station_pro_multiview_attributes` filter. By adding a view prefix to the attribute you wish to change, this will override the attribute for that view. eg. To display the full day heading in Tabs view, but short day headings in Table view: + +``` +add_filter( 'radio_station_pro_multiview_atts', 'my_custom_multiview_attributes ); +function my_custom_multiview_attributes( $atts ) { + $atts['table_display_day'] = 'short'; + $atts['tabs_display_day'] = 'long'; + return $atts; +} +`` + + #### Radio Timezone Shortcode `[radio-timezone]` diff --git a/includes/data-feeds.php b/includes/data-feeds.php index 0bb3a93..e8cb141 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -136,12 +136,14 @@ function radio_station_get_station_data() { } } - // --- get station data --- + // --- get stream data --- + // 2.3.3.9: enabled format and fallback data $stream_url = radio_station_get_stream_url(); - // $stream_format = radio_station_get_stream_format(); - // $fallback_url = radio_station_get_fallback_url(); - // $fallback_format = radio_station_get_fallback_format(); + $stream_format = radio_station_get_setting( 'streaming_format' ); + $fallback_url = radio_station_get_fallback_url(); + $fallback_format = radio_station_get_setting( 'fallback_format' ); + // --- get station data --- $station_url = radio_station_get_station_url(); $schedule_url = radio_station_get_schedule_url(); $language = radio_station_get_language(); @@ -159,19 +161,20 @@ function radio_station_get_station_data() { // --- set station data array --- // 2.3.2: added schedule updated timestamp + // 2.3.3.9: enabled format and fallback data $station_data = array( - 'timezone' => $timezone, - 'stream_url' => $stream_url, - // 'stream_format' => $stream_format, - // 'fallback_url', => $fallback_url, - // 'fallback_format', => $fallback_format, - 'station_url' => $station_url, - 'schedule_url' => $schedule_url, - 'language' => $language['slug'], - 'timestamp' => $now, - 'date_time' => $date_time, - 'updated' => $updated, - 'success' => true, + 'timezone' => $timezone, + 'stream_url' => $stream_url, + 'stream_format' => $stream_format, + 'fallback_url' => $fallback_url, + 'fallback_format' => $fallback_format, + 'station_url' => $station_url, + 'schedule_url' => $schedule_url, + 'language' => $language['slug'], + 'timestamp' => $now, + 'date_time' => $date_time, + 'updated' => $updated, + 'success' => true, ); $station_data = apply_filters( 'radio_station_station_data', $station_data ); diff --git a/includes/master-schedule.php b/includes/master-schedule.php index aef212d..d25e2d9 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -105,9 +105,13 @@ function radio_station_master_schedule( $atts ) { // 2.3.2: add display date attribute $defaults['show_genres'] = 1; $defaults['display_date'] = false; - } elseif ( 'divs' == $atts['view'] ) { + } elseif ( ( 'divs' == $atts['view'] ) || in_array( 'divs', $views ) ) { // 2.3.3.8: moved divs view only default here + // 2.3.3.9: added check if divs in views array $defaults['divheight'] = 45; + } elseif ( ( 'grid' == $atts['grid'] ) || in_array( 'grid', $views ) ) { + // 2.3.3.9: add default for grid view width + $defaults['gridwidth'] = 150; } } @@ -121,12 +125,7 @@ function radio_station_master_schedule( $atts ) { // --- set initial empty output string --- $output = ''; - // --- disable clock if feature is not present --- - // (temporarily while clock is in development) - if ( !function_exists( 'radio_station_clock_shortcode' ) ) { - $atts['clock'] = 0; - } - + // 2.3.3.9: remove check if clock shortcode present // 2.3.3.6: set new line for easier debug viewing $newline = ''; if ( RADIO_STATION_DEBUG ) { @@ -140,7 +139,7 @@ function radio_station_master_schedule( $atts ) { $controls = array(); - // --- display radio clock or timezone (or neither) + // --- display radio clock or timezone (or neither) --- if ( $atts['clock'] ) { // --- radio clock --- @@ -838,7 +837,7 @@ classes = end.attr('class').split(' '); } } - /* Shift Day Left / Right */ + /* Shift Day Left / Right */ function radio_shift_tab(leftright) { radio_tabs_responsive(leftright); return false; diff --git a/includes/support-functions.php b/includes/support-functions.php index 998871f..5f1ae4f 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -3050,6 +3050,21 @@ function radio_station_get_stream_url() { return $streaming_url; } +// ----------------- +// Get Streaming URL +// ----------------- +// 2.3.3.9: added get fallback URL helper +function radio_station_get_fallback_url() { + $fallback_url = ''; + $fallback = radio_station_get_setting( 'fallback_url' ); + if ( $fallback && ( '' != $fallback ) ) { + $fallback_url = $fallback; + } + $fallback_url = apply_filters( 'radio_station_fallback_url', $fallback_url ); + + return $fallback_url; +} + // Get Stream Formats // ------------------ // 2.3.3.7: added streaming format options @@ -4001,10 +4016,13 @@ function radio_station_convert_show_shifts( $show ) { // 2.3.3.9: fixed to not use radio_station_convert_hour $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); + // 2.3.3.9: added missing shift encore designation + $encore = ( 'on' == $shift['encore'] ) ? 1 : 0; $schedule[$i] = array( 'day' => $shift['day'], 'start' => $start_hour . ':' . $shift['start_min'], 'end' => $end_hour . ':' . $shift['end_min'], + 'encore' => $encore, ); } $show['schedule'] = $schedule; @@ -4453,8 +4471,7 @@ function radio_station_translate_weekday( $weekday, $short = null ) { foreach ( $days as $i => $day ) { $abbr = substr( $day, 0, 3 ); if ( ( $weekday == $day ) || ( $weekday == $abbr ) ) { - if ( ( !$short && !is_null( $short ) ) - || ( is_null( $short ) && ( $weekday == $day ) ) ) { + if ( ( !$short && !is_null( $short ) ) || ( is_null( $short ) && ( $weekday == $day ) ) ) { return $wp_locale->get_weekday( $i ); } elseif ( $short || ( is_null( $short ) && ( weekday == $abbr ) ) ) { return $wp_locale->get_weekday_abbrev( $wp_locale->get_weekday( $i ) ); diff --git a/radio-station.php b/radio-station.php index 172d724..e651218 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1564,7 +1564,7 @@ function radio_station_streaming_data( $data, $station = false ) { 'instance' => 0, 'url' => radio_station_get_stream_url(), 'format' => radio_station_get_setting( 'streaming_format' ), - 'fallback' => radio_station_get_setting( 'fallback_url' ), + 'fallback' => radio_station_get_fallback_url(), 'fformat' => radio_station_get_setting( 'fallback_format' ), ); return $data; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 346168e..fa7e829 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -46,6 +46,10 @@ $more = apply_filters( 'radio_station_schedule_table_excerpt_more', '[…]' ); } +// --- filter arrows --- +$arrows = array( 'right' => '►', 'left' => '◄' ); +$arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'table' ); + // --- set cell info key order --- // 2.3.3.8: added for possible info rearrangement $infokeys = array( 'avatar', 'title', 'hosts', 'times', 'encore', 'file', 'excerpt', 'custom' ); @@ -138,8 +142,6 @@ // 2.3.0: added left/right arrow responsive controls // 2.3.1: added (negative) return to arrow onclick functions // 2.3.2: added check for optional display_date attribute - $arrows = array( 'right' => '►', 'left' => '◄' ); - $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'table' ); $table .= '' . $newline; $table .= ''; + // --- add archive_pagination --- + // TODO: add genre archive list pagination + // if ( $atts['pagination'] ) { + // $list .= radio_station_archive_pagination( 'genre', $atts, $post_count ); + // } + + // --- enqueue pagination javascript --- + // add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); + // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -900,12 +1053,243 @@ function radio_station_genre_archive_list( $atts ) { // -------------------------- // Language Archive Shortcode // -------------------------- -// TODO: Languages Archive Shortcode -// add_shortcode( 'language-archive', 'radio_station_language_archive_list' ); -// add_shortcode( 'languages-archive', 'radio_station_language_archive_list' ); +// 2.3.3.9: add Languages Archive Shortcode +add_shortcode( 'language-archive', 'radio_station_language_archive_list' ); +add_shortcode( 'languages-archive', 'radio_station_language_archive_list' ); function radio_station_language_archive_list( $atts ) { - $list = ''; + $defaults = array( + // --- language display options --- + 'languages' => '', + 'link_languages' => 1, + 'language_desc' => 1, + 'hide_empty' => 1, + 'pagination' => 1, + // --- query args --- + 'status' => 'publish', + 'perpage' => - 1, + 'offset' => 0, + 'orderby' => 'title', + 'order' => 'ASC', + 'with_shifts' => 1, + // --- show display options --- + 'show_avatars' => 1, + 'thumbnails' => 0, + 'avatar_width' => 75, + 'show_desc' => 1, + ); + + // --- handle possible pagination offset --- + if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { + $page = absint( get_query_var( 'page' ) ); + if ( $page > - 1 ) { + $atts['offset'] = (int) $atts['perpage'] * $page; + } + } + $atts = shortcode_atts( $defaults, $atts, 'language-archive' ); + + // --- maybe get specified language(s) --- + if ( !empty( $atts['languages'] ) ) { + $languages = explode( ',', $atts['languages'] ); + foreach ( $languages as $i => $language ) { + $language = trim( $language ); + $language = radio_station_get_language( $language ); + if ( $language ) { + $languages[$i] = $language; + } else { + unset( $language[$i] ); + } + } + } else { + // --- get all languages --- + $args = array(); + if ( !$atts['hide_empty'] ) { + $args['hide_empty'] = false; + } + $languages = radio_station_get_language_terms( $args ); + } + + // --- check if we have languages --- + if ( !$languages || ( count( $languages ) == 0 ) ) { + if ( $atts['hide_empty'] ) { + return ''; + } else { + $list = '
    '; + $list .= esc_html( __( 'No Languages were found to display.', 'radio-station' ) ); + $list .= '
    '; + + return $list; + } + } + + $list = '
    '; + + // --- loop languages --- + foreach ( $languages as $name => $language ) { + + // --- get published shows --- + // TODO: also display Overrides in archive list ? + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'numberposts' => $atts['perpage'], + 'offset' => $atts['offset'], + 'orderby' => $atts['orderby'], + 'order' => $atts['order'], + 'post_status' => $atts['status'], + ); + + if ( $atts['with_shifts'] ) { + + // --- active shows with shifts --- + $args['meta_query'] = array( + 'relation' => 'AND', + array( + 'key' => 'show_sched', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ); + + } else { + + // --- just active shows --- + $args['meta_query'] = array( + array( + 'key' => 'show_active', + 'value' => 'on', + 'compare' => '=', + ), + ); + } + + // --- set language taxonomy query --- + $args['tax_query'] = array( + array( + 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, + 'field' => 'slug', + 'terms' => $language['slug'], + ), + ); + + // --- get shows in language --- + $args = apply_filters( 'radio_station_language_archive_post_args', $args ); + $posts = get_posts( $args ); + $posts = apply_filters( 'radio_station_language_archive_posts', $posts ); + + $list .= '
    '; + + if ( $posts || ( count( $posts ) > 0 ) ) { + $has_posts = true; + } else { + $has_posts = false; + } + if ( $has_posts || ( !$has_posts && !$atts['hide_empty'] ) ) { + + // --- language title --- + $list .= '

    '; + if ( $atts['link_languages'] ) { + $list .= '' . $language['name'] . ''; + } else { + $list .= $language['name']; + } + $list .= '

    '; + + // --- language description --- + if ( $atts['language_desc'] && !empty( $language['language_desc'] ) ) { + $list .= '
    '; + $list .= $language['description']; + $list .= '
    '; + } + + } + + if ( !$has_posts ) { + + // --- no shows messages ---- + if ( !$atts['hide_empty'] ) { + $list .= esc_html( __( 'No Shows in this Language.', 'radio-station' ) ); + } + + } else { + + // --- filter excerpt length and more --- + $length = apply_filters( 'radio_station_language_archive_excerpt_length', false ); + $more = apply_filters( 'radio_station_language_archive_excerpt_more', '[…]' ); + + // --- show archive list --- + $list .= '
      '; + + foreach ( $posts as $post ) { + $list .= '
    • '; + + // --- show avatar or thumbnail fallback --- + $width_style = ''; + if ( absint( $atts['avatar_width'] ) > 0 ) { + $width_styles = ' style="width: ' . esc_attr( absint( $atts['avatar_width'] ) ) . 'px"'; + } + $list .= '
      '; + $show_avatar = false; + if ( $atts['show_avatars'] ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $show_avatar = radio_station_get_show_avatar( $post->ID, 'thumbnail', $attr ); + } + if ( $show_avatar ) { + $list .= $show_avatar; + } elseif ( $atts['thumbnails'] ) { + if ( has_post_thumbnail( $post->ID ) ) { + $attr = array( 'class' => 'show-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post->ID, 'thumbnail', $attr ); + $list .= $thumbnail; + } + } + $list .= '
      '; + + // --- show title ---- + $permalink = get_permalink( $post->ID ); + $list .= ''; + + // --- show excerpt --- + if ( $atts['show_desc' ] ) { + if ( !empty( $post->post_excerpt ) ) { + $excerpt = $post->post_excerpt; + $excerpt .= ' ' . $more . ''; + } else { + $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); + } + $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); + + if ( '' != $excerpt ) { + $list .= '
      '; + $list .= $excerpt; + $list .= '
      '; + } + } + + $list .= '
    • '; + } + $list .= '
    '; + } + + $list .= '
    '; + } + + $list .= '
    '; + + // --- add archive_pagination --- + // TODO: add language archive list pagination + // if ( $atts['pagination'] ) { + // $list .= radio_station_archive_pagination( 'language', $atts, $post_count ); + // } + + // --- enqueue pagination javascript --- + // add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -916,6 +1300,95 @@ function radio_station_language_archive_list( $atts ) { return $list; } +// ------------------ +// Archive Pagination +// ------------------ +function radio_station_archive_pagination( $type, $atts, $post_count ) { + + global $post; + $permalink = get_permalink( $post->ID ); + $post_pages = ceil( $post_count / $atts['perpage'] ); + if ( $atts['offset'] > 0 ) { + $current_page = $atts['offset'] / $atts['perpage']; + } else { + $current_page = 0; + } + $prev_page = $current_page - 1; + $next_page = $current_page + 1; + + $pagi = '

    '; + $pagi .= '
    '; + $url = add_query_arg( 'page', $prev_page, $permalink ); + $pagi .= '
    '; + if ( $prev_page > 0 ) { + $pagi .= ''; + } + $pagi .= '
    '; + for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { + if ( $current_page == $pagenum ) { + $active = ' active'; + } else { + $active = ''; + } + $pagi .= '
    '; + $url = add_query_arg( 'page', $pagenum, $permalink ); + $pagi .= ''; + $pagi .= esc_html( $pagenum ); + $pagi .= ''; + $pagi .= '
    '; + } + $url = add_query_arg( 'page', $next_page, $permalink ); + $pagi .= '
    '; + $pagi .= ''; + $pagi .= '
    '; + $pagi .= '
    '; + + return $pagi; +} + +// ----------------------------- +// Archive Pagination Javascript +// ----------------------------- +// 2.3.3.9: renamed function to distinguish from list pagination +function radio_station_archive_pagination_javascript() { + + // TEMP + return; + + // --- fade out current page and fade in selected page --- + $js = "function radio_archive_page(id, types, pagenum) { + currentpage = document.getElementById('archive-'+id+'-'+types+'-current-page').value; + if (pagenum == 'next') { + pagenum = parseInt(currentpage) + 1; + pagecount = document.getElementById('archive-'+id+'-'+types+'-page-count').value; + if (pagenum > pagecount) {return;} + } + if (pagenum == 'prev') { + pagenum = parseInt(currentpage) - 1; + if (pagenum < 1) {return;} + } + if (typeof jQuery == 'function') { + jQuery('.archive-'+id+'-'+types+'-page').fadeOut(500); + jQuery('#archive-'+id+'-'+types+'-page-'+pagenum).fadeIn(1000); + jQuery('.archive-'+id+'-'+types+'-page-button').removeClass('active'); + jQuery('#archive-'+id+'-'+types+'-page-button-'+pagenum).addClass('active'); + jQuery('#archive-'+id+'-'+types+'-current-page').val(pagenum); + } else { + pages = document.getElementsByClassName('archive-'+id+'-'+types+'-page'); + for (i = 0; i < pages.length; i++) {pages[i].style.display = 'none';} + document.getElementById('archive-'+id+'-'+types+'-page-'+pagenum).style.display = ''; + buttons = document.getElementsByClassName('archive-'+id+'-'+types+'-page-button'); + for (i = 0; i < buttons.length; i++) {buttons[i].classList.remove('active');} + document.getElementById('archive-'+id+'-'+types+'-page-button-'+pagenum).classList.add('active'); + document.getElementById('archive-'+id+'-'+types+'-current-page').value = pagenum; + } + }"; + + // --- enqueue script inline --- + wp_add_inline_script( 'radio-station', $js ); +} + + // ------------------------------- // === Show Related Shortcodes === // ------------------------------- @@ -978,7 +1451,10 @@ function radio_station_show_list_shortcode( $type, $atts ) { } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { $posts = radio_station_get_show_playlists( $show_id, $args ); } elseif ( defined( 'RADIO_STATION_EPISODE_SLUG' ) && ( RADIO_STATION_EPISODE_SLUG == $type ) ) { - $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args ); + $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args ); + } + if ( RADIO_STATION_DEBUG ) { + echo 'Show Posts (' . $type . '):' . print_r( $posts, true ) . ''; } } if ( !isset( $posts ) || !$posts || !is_array( $posts ) || ( count( $posts ) == 0 ) ) {return '';} @@ -1082,7 +1558,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { if ( $atts['pagination'] && ( $post_pages > 1 ) ) { $list .= '

    '; $list .= '
    '; - $list .= '
    '; + $list .= '
    '; $list .= ''; $list .= '
    '; for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { @@ -1091,14 +1567,14 @@ function radio_station_show_list_shortcode( $type, $atts ) { } else { $active = ''; } - $onclick = 'radio_show_page(' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; + $onclick = 'radio_list_page(' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; $list .= ''; } - $list .= '
    '; + $list .= '
    '; $list .= ''; $list .= '
    '; $list .= ''; @@ -1113,7 +1589,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { radio_station_enqueue_style( 'shortcodes' ); // --- enqueue pagination javascript --- - add_action( 'wp_footer', 'radio_station_pagination_javascript' ); + add_action( 'wp_footer', 'radio_station_list_pagination_javascript' ); // --- filter and return --- $list = apply_filters( 'radio_station_show_' . $type . '_list', $list, $atts ); @@ -1156,10 +1632,11 @@ function radio_station_show_playlists_archive( $atts ) { // -------------------------------- // Show Lists Pagination Javascript // -------------------------------- -function radio_station_pagination_javascript() { +// 2.3.3.9: renamed function to distinguish from archive pagination +function radio_station_list_pagination_javascript() { // --- fade out current page and fade in selected page --- - $js = "function radio_show_page(id, types, pagenum) { + $js = "function radio_list_page(id, types, pagenum) { currentpage = document.getElementById('show-'+id+'-'+types+'-current-page').value; if (pagenum == 'next') { pagenum = parseInt(currentpage) + 1; @@ -1224,7 +1701,8 @@ function radio_station_current_show_shortcode( $atts ) { $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; // --- apply filters for dynamic reload value --- - $dynamic = apply_filters( 'radio_station_current_show_dynamic', 0, $atts ); + $dynamic = apply_filters( 'radio_station_current_show_dynamic', false, $atts ); + $dynamic = $dynamic ? 1 : 0; // 2.3.3: use plugin setting if time format attribute is empty if ( isset( $atts['time'] ) && ( '' == $atts['time'] ) ) { @@ -1237,6 +1715,7 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.2: added AJAX load attribute // 2.3.2: added for_time attribute // 2.3.3.8: added show_encore attribute (default 1) + // 2.3.3.9: added avatar_size attribute (default thumbnail) $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( // --- legacy options --- @@ -1253,6 +1732,7 @@ function radio_station_current_show_shortcode( $atts ) { // --- new options --- // 'display_producers' => 0, 'avatar_width' => '', + 'avatar_size' => 'thumbnail', 'title_position' => 'right', 'link_hosts' => 0, 'show_encore' => 1, @@ -1367,6 +1847,11 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.2: added attribute to pass time argument if ( $atts['for_time'] ) { $current_shift = radio_station_get_current_show( $atts['for_time'] ); + $time = radio_station_get_time( 'datetime', $atts['for_time'] ); + echo ''; + echo 'Current Shift For Time: ' . $atts['for_time'] . ' : ' . $time . PHP_EOL; + echo print_r( $current_shift, true ) . PHP_EOL; + echo ''; } else { $current_shift = radio_station_get_current_show(); } @@ -1504,12 +1989,6 @@ function radio_station_current_show_shortcode( $atts ) { $start = radio_station_translate_time( $start ); $end = radio_station_translate_time( $end ); - if ( RADIO_STATION_DEBUG ) { - echo ''; - - echo ''; - } - // --- set shift classes --- $classes = array( 'current-show-shifts', 'on-air-dj-sched' ); if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { @@ -1523,17 +2002,34 @@ function radio_station_current_show_shortcode( $atts ) { $current_shift_display .= ' - '; $current_shift_display .= '' . esc_html( $end ) . ''; $current_shift_display .= '
    '; + + // 2.3.3.9: add show user time div + $current_shift_display .= '
    '; + $current_shift_display .= '['; + $current_shift_display .= ' - '; + $current_shift_display .= ']'; + $current_shift_display .= '
    '; + } $class = implode( ' ', $classes ); // --- shift display output --- $shift_display .= '
    '; if ( in_array( 'current-shift', $classes ) ) { + // (this highlights the current shift item in the full schedule list) $shift_display .= '
    • '; } $shift_display .= '' . esc_html( $start ) . ''; $shift_display .= ' - '; $shift_display .= '' . esc_html( $end ) . ''; + + // 2.3.3.9: add show user time div + $shift_display .= '
      '; + $shift_display .= '['; + $shift_display .= ' - '; + $shift_display .= ']'; + $shift_display .= '
      '; + if ( in_array( 'current-shift', $classes ) ) { $shift_display .= '
    '; } @@ -1566,7 +2062,10 @@ function radio_station_current_show_shortcode( $atts ) { // 2.3.0: get show avatar (with thumbnail fallback) // 2.3.0: filter show avatar via display context // 2.3.0: maybe add link from avatar to show - $show_avatar = radio_station_get_show_avatar( $show_id ); + // 2.3.3.9: allow for possible avatar size attribute/filter + $avatar = ''; + $avatar_size = apply_filters( 'radio_station_current_show_avatar_size', $atts['avatar_size'], $show_id ); + $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); $show_avatar = apply_filters( 'radio_station_current_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { $avatar = '
    '; @@ -1576,12 +2075,12 @@ function radio_station_current_show_shortcode( $atts ) { $avatar .= $show_avatar; } $avatar .= '
    '; - - // 2.3.3.8: added avatar display filter - $avatar = apply_filters( 'radio_station_current_show_avatar_display', $avatar, $show_id, $atts ); - if ( ( '' != $avatar ) && is_string( $avatar ) ) { - $html['avatar'] = $avatar; - } + } + // 2.3.3.8: added avatar display filter + // 2.3.3.9: moved filter outside of conditional + $avatar = apply_filters( 'radio_station_current_show_avatar_display', $avatar, $show_id, $atts ); + if ( ( '' != $avatar ) && is_string( $avatar ) ) { + $html['avatar'] = $avatar; } } @@ -1796,6 +2295,11 @@ function radio_station_current_show_shortcode( $atts ) { // --- countdown timers --- if ( isset( $current_shift_end ) && ( $atts['countdown'] || $atts['dynamic'] ) ) { + // 2.3.3.9: output current time override + if ( isset( $atts['for_time'] ) ) { + $output .= ''; + } + // --- hidden inputs for current shift time --- $output .= ''; @@ -1898,7 +2402,8 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.2: get default AJAX load settings $ajax = radio_station_get_setting( 'ajax_widgets' ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - $dynamic = apply_filters( 'radio_station_upcoming_shows_dynamic', 0, $atts ); + $dynamic = apply_filters( 'radio_station_upcomins_shows_dynamic', false, $atts ); + $dynamic = $dynamic ? 1 : 0; // 2.3.3: use plugin setting if time format attribute is empty if ( isset( $atts['time'] ) && ( '' == $atts['time'] ) ) { @@ -1926,6 +2431,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { 'show_encore' => 1, 'link_hosts' => 0, 'avatar_width' => '', + 'avatar_size' => 'thumbnail', 'title_position' => 'right', 'countdown' => 0, 'ajax' => $ajax, @@ -1999,7 +2505,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $html .= "&" . esc_js( $key ) . "=" . esc_js( $value ); } $html .= "'; "; - $html .= "document.getElementById('rs-upcoming-shows-" . esc_attr( $instance ) ."-loader').src = url;"; + $html .= "document.getElementById('rs-upcoming-shows-" . esc_attr( $instance ) . "-loader').src = url;"; $html .= ""; // --- enqueue shortcode styles --- @@ -2030,7 +2536,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $shows = radio_station_get_next_shows( $atts['limit'] ); } if ( RADIO_STATION_DEBUG ) { - $output .= ''; + $output .= 'Upcoming Shows: ' . print_r( $shows, true ) . ''; } // --- open shortcode only wrapper --- @@ -2178,9 +2684,15 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $shift_display .= ' - '; $shift_display .= '' . esc_html( $end ) . ''; $shift_display .= '
    '; + // 2.3.3.9: add empty user time div + $shift_display .= '
    '; + $shift_display .= '['; + $shift_display .= ' - '; + $shift_display .= ']'; + $shift_display .= '
    '; $shift_display .= '
    '; if ( RADIO_STATION_DEBUG ) { - $shift_display .= "Upcoming Shift: " . print_r( $shift, true ) . ""; + $shift_display .= 'Upcoming Shift: ' . print_r( $shift, true ) . ''; } } @@ -2206,8 +2718,10 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: get show avatar (with thumbnail fallback) // 2.3.0: filter show avatar by context // 2.3.0: maybe link avatar to show + // 2.3.3.9: add filter for avatar image display size $avatar = ''; - $show_avatar = radio_station_get_show_avatar( $show_id ); + $avatar_size = apply_filters( 'radio_station_upcoming_show_avatar_size', $atts['avatar_size'], $show_id ); + $show_avatar = radio_station_get_show_avatar( $show_id, $avatar_size ); $show_avatar = apply_filters( 'radio_station_upcoming_show_avatar', $show_avatar, $show_id, $atts ); if ( $show_avatar ) { $avatar = '
    '; @@ -2356,6 +2870,11 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.3.0: added for countdowns if ( isset( $next_start_time ) && ( $atts['countdown'] || $atts['dynamic'] ) ) { + // 2.3.3.9: output current time override + if ( isset( $atts['for_time'] ) ) { + $output .= ''; + } + // --- hidden input for next start time --- $output .= ''; if ( RADIO_STATION_DEBUG ) { @@ -2454,7 +2973,8 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.2: get default AJAX load settings $ajax = radio_station_get_setting( 'ajax_widgets' ); $ajax = ( 'yes' == $ajax ) ? 'on' : 'off'; - $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', 0, $atts ); + $dynamic = apply_filters( 'radio_station_current_playlist_dynamic', false, $atts ); + $dynamic = $dynamic ? 1 : 0; // --- get shortcode attributes --- // 2.3.2: added AJAX load attribute @@ -2476,6 +2996,7 @@ function radio_station_current_playlist_shortcode( $atts ) { 'id' => '', 'for_time' => 0, ); + // 2.3.0: renamed shortcode identifier to current-playlist $atts = shortcode_atts( $defaults, $atts, 'current-playlist' ); @@ -2542,6 +3063,11 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- fetch the current playlist --- if ( $atts['for_time'] ) { $playlist = radio_station_get_now_playing( $atts['for_time'] ); + $time = radio_station_get_time( 'datetime', $atts['for_time'] ); + echo ''; + echo 'Current Playlist For Time: ' . $atts['for_time'] . ' : ' . $time . PHP_EOL; + echo print_r( $playlist, true ); + echo ''; } else { $playlist = radio_station_get_now_playing(); } @@ -2575,6 +3101,11 @@ function radio_station_current_playlist_shortcode( $atts ) { // 2.3.3.8: moved outside of current playlist check if ( $atts['countdown'] || $atts['dynamic'] ) { + $html['countdown'] = ''; + // if ( RADIO_STATION_DEBUG ) { + echo 'Playlist: ' . print_r( $playlist, true ) . ''; + // } + // 2.3.1: added check for playlist shifts value if ( isset( $playlist['shifts'] ) && is_array( $playlist['shifts'] ) && ( count( $playlist['shifts'] ) > 0 ) ) { @@ -2589,9 +3120,10 @@ function radio_station_current_playlist_shortcode( $atts ) { $yesterday = radio_station_get_previous_day( $today ); $weekdays = radio_station_get_schedule_weekdays( $yesterday ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + // echo 'Now: ' . $now . ' : ' . radio_station_get_time( 'datetime', $now ) . '
    '; // --- loop shifts --- - foreach ( $playlist['shifts'] as $shift ) { + foreach ( $playlist['shifts'] as $shift_id => $shift ) { // --- set shift start and end --- if ( isset( $shift['real_start'] ) ) { @@ -2612,6 +3144,7 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- convert shift info --- // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour format first + // TODO: check/test possible undefined index for $shift['day'] ? $start_time = radio_station_convert_shift_time( $start ); $end_time = radio_station_convert_shift_time( $end ); if ( isset( $shift['real_start'] ) ) { @@ -2621,20 +3154,28 @@ function radio_station_current_playlist_shortcode( $atts ) { $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); } $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - if ( $start > $end ) { + // 2.3.3.9: fix to overnight check variables + if ( $shift_start_time > $shift_end_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } // --- check currently playing show time --- if ( $atts['for_time'] ) { $now = $atts['for_time']; + $html['countdown'] .= ''; } else { $now = radio_station_get_now(); } - if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { - + // echo 'Shift Start: ' . $shift_start_time . '(' . radio_station_get_time( 'datetime', $shift_start_time ) . ')
    '; + // echo 'Shift End: ' . $shift_end_time . '(' . radio_station_get_time( 'datetime', $shift_end_time ) . ')
    '; + + if ( ( ( $now > $shift_start_time ) || ( $now == $shift_start_time ) ) && ( $now < $shift_end_time ) ) { + + // print_r( $shift ); + // echo "^^^ NOW PLAYING ^^^"; + // --- hidden input for playlist end time --- - $html['countdown'] = ''; + $html['countdown'] .= ''; // --- for countdown timer display --- if ( $atts['countdown'] ) { @@ -2654,78 +3195,73 @@ function radio_station_current_playlist_shortcode( $atts ) { } // 2.3.0: use updated code from now playing widget - if ( $playlist ) { + // 2.3.3.9: move check for playlist tracks here + $tracks = ''; + if ( $playlist && isset( $playlist['tracks'] ) && is_array( $playlist['tracks'] ) && ( count( $playlist['tracks'] ) > 0 ) ) { // 2.3.0: split div wrapper from track wrapper - $tracks = '
    '; + $tracks .= '
    '; // --- loop playlist tracks --- // 2.3.0: loop all instead of just latest // 2.3.1: added check for playlist tracks - if ( isset( $playlist['tracks'] ) && is_array( $playlist['tracks'] ) && ( count( $playlist['tracks'] ) > 0 ) ) { - foreach ( $playlist['tracks'] as $track ) { - - $class = ''; - if ( isset( $track['playlist_entry_new'] ) && ( 'on' === $track['playlist_entry_new'] ) ) { - $class .= ' new'; - } - // 2.3.0: added check for latest track since looping - if ( $track == $playlist['latest'] ) { - $class .= ' latest'; - } else { - $class .= ' played'; - } + // 2.3.3.9: moved up check for playlist tracks + foreach ( $playlist['tracks'] as $track ) { - $tracks .= '
    '; + $class = ''; + if ( isset( $track['playlist_entry_new'] ) && ( 'on' === $track['playlist_entry_new'] ) ) { + $class .= ' new'; + } + // 2.3.0: added check for latest track since looping + if ( $track == $playlist['latest'] ) { + $class .= ' latest'; + } else { + $class .= ' played'; + } - // 2.2.3: convert span tags to div tags - // 2.2.4: check value keys are set before outputting - if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Song', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ); - $tracks .= '
    '; - } + $tracks .= '
    '; - // 2.2.7: add label prefixes to now playing data - if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ); - $tracks .= '
    '; - } + // 2.2.3: convert span tags to div tags + // 2.2.4: check value keys are set before outputting + if ( $atts['song'] && isset( $track['playlist_entry_song'] ) ) { + $tracks .= '
    '; + $tracks .= esc_html( __( 'Song', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_song'] ); + $tracks .= '
    '; + } - if ( $atts['album'] && !empty( $track['playlist_entry_album'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Album', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ); - $tracks .= '
    '; - } + // 2.2.7: add label prefixes to now playing data + if ( $atts['artist'] && isset( $track['playlist_entry_artist'] ) ) { + $tracks .= '
    '; + $tracks .= esc_html( __( 'Artist', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_artist'] ); + $tracks .= '
    '; + } - if ( $atts['label'] && !empty( $track['playlist_entry_label'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Label', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ); - $tracks .= '
    '; - } + if ( $atts['album'] && !empty( $track['playlist_entry_album'] ) ) { + $tracks .= '
    '; + $tracks .= esc_html( __( 'Album', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_album'] ); + $tracks .= '
    '; + } - if ( $atts['comments'] && !empty( $track['playlist_entry_comments'] ) ) { - $tracks .= '
    '; - $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); - $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ); - $tracks .= '
    '; - } + if ( $atts['label'] && !empty( $track['playlist_entry_label'] ) ) { + $tracks .= '
    '; + $tracks .= esc_html( __( 'Label', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_label'] ); + $tracks .= '
    '; + } + if ( $atts['comments'] && !empty( $track['playlist_entry_comments'] ) ) { + $tracks .= '
    '; + $tracks .= esc_html( __( 'Comments', 'radio-station' ) ); + $tracks .= ': ' . esc_html( $track['playlist_entry_comments'] ); $tracks .= '
    '; } - } - // 2.3.3.8 added track display filter - $tracks .= '
    '; - $tracks = apply_filters( 'radio_station_current_playlist_tracks_display', $tracks, $playlist, $atts ); - if ( ( '' != $tracks ) && is_string( $tracks ) ) { - $html['tracks'] = $tracks; + $tracks .= '
    '; } + $tracks .= '
    '; // --- playlist permalink --- // 2.3.3.8 added playlist_link shortcode attribute (default 1) @@ -2745,20 +3281,6 @@ function radio_station_current_playlist_shortcode( $atts ) { } } - // --- custom HTML section --- - // 2.3.3.8: added custom HTML section - $html['custom'] = apply_filters( 'radio_station_current_playlist_custom_display', '', $playlist, $atts ); - - // --- filter display section order --- - // 2.3.1: added filter for section order display - $order = array( 'tracks', 'link', 'countdown', 'custom' ); - $order = apply_filters( 'radio_station_current_playlist_section_order', $order, $atts ); - foreach ( $order as $section ) { - if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { - $output .= $html[$section]; - } - } - } else { // 2.2.3: added missing translation wrapper @@ -2772,17 +3294,37 @@ function radio_station_current_playlist_shortcode( $atts ) { $no_playlist .= '
    '; // 2.3.3.8: added no playlist display filter + // 2.3.3.9: assign to tracks key for possible output re-ordering $no_playlist = apply_filters( 'radio_station_current_playlist_no_playlist_display', $no_playlist, $atts ); if ( ( '' != $no_playlist ) && is_string( $no_playlist ) ) { - $output .= $no_playlist; + $html['tracks'] = $no_playlist; } + } - // TODO: display countdown even if no current playlist - // if ( $atts['countdown'] ) { - // $output .= $html['countdown']; - // } + // 2.3.3.8: added track display filter + // 2.3.3.9: moved outside of playlist check + $tracks = apply_filters( 'radio_station_current_playlist_tracks_display', $tracks, $playlist, $atts ); + if ( ( '' != $tracks ) && is_string( $tracks ) ) { + $html['tracks'] = $tracks; } + // --- custom HTML section --- + // 2.3.3.8: added custom HTML section + // 2.3.3.9: move outside of playlist check + $html['custom'] = apply_filters( 'radio_station_current_playlist_custom_display', '', $playlist, $atts ); + + // --- filter display section order --- + // 2.3.1: added filter for section order display + // 2.3.3.9: moved outside of check for current playlist + $order = array( 'tracks', 'link', 'countdown', 'custom' ); + $order = apply_filters( 'radio_station_current_playlist_section_order', $order, $atts ); + // $output .= print_r( array_keys( $html ), true ); + foreach ( $order as $section ) { + if ( isset( $html[$section] ) && ( '' != $html[$section] ) ) { + $output .= $html[$section]; + } + } + // --- close shortcode only wrapper --- if ( !$atts['widget'] ) { $output .= '
    '; @@ -2845,7 +3387,13 @@ function radio_station_current_playlist() { // 2.3.0: added shortcode/widget countdown script add_action( 'radio_station_countdown_enqueue', 'radio_station_countdown_enqueue' ); function radio_station_countdown_enqueue() { - + + // 2.3.3.9: check if script is enqueued + global $radio_station_data; + if ( isset( $radio_station_data['countdown-script'] ) ) { + return; + } + // --- enqueue countdown script --- radio_station_enqueue_script( 'radio-station-countdown', array( 'radio-station' ), true ); @@ -2860,6 +3408,9 @@ function radio_station_countdown_enqueue() { // --- add script inline --- wp_add_inline_script( 'radio-station-countdown', $js ); + // 2.3.3.9: flag script as enqueued + $radio_station_data['countdown-script'] = true; + } diff --git a/includes/support-functions.php b/includes/support-functions.php index 065b65d..d6fe087 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -16,6 +16,7 @@ // - Get Show Data // - Get Show Metadata // - Get Override Metadata +// - Get Show Override Value // - Get Current Schedule // - Get Current Show // - Get Previous Show @@ -80,6 +81,7 @@ // - Trim Excerpt // - Shorten String // - Sanitize Values +// - Sanitize Input Values // - Sanitize Shortcode Values // === Translations === // - Translate Weekday @@ -266,10 +268,16 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, foreach ( $shows as $show ) { $shifts = radio_station_get_show_schedule( $show->ID ); + if ( RADIO_STATION_DEBUG ) { + echo 'Shifts for Show ' . $show->ID . ': ' . print_r( $shifts, true ) . ''; + } if ( $shifts && is_array( $shifts) && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $i => $shift ) { + // 2.3.3.9: set shift ID to key + $shift['id'] = $i; + // --- make sure shift has sufficient info --- if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { $isdisabled = true; @@ -467,7 +475,8 @@ function radio_station_get_show_shifts( $check_conflicts = true, $split = true, } // --- filter and return --- - $day_shifts = apply_filters( 'radio_station_show_shifts', $day_shifts ); + // 2.3.3.9: changed conflicting filter name from radio_station_show_shifts + $day_shifts = apply_filters( 'radio_station_show_day_shifts', $day_shifts ); return $day_shifts; } @@ -503,99 +512,131 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { // --- loop overrides and get data --- $override_list = array(); foreach ( $overrides as $i => $override ) { - $data = get_post_meta( $override['ID'], 'show_override_sched', true ); - if ( $data ) { - $date = $data['date']; - if ( '' != $date ) { + // 2.3.3.9: allow for usage of linked show title + $title = $override['post_title']; + $linked_id = get_post_meta( $override['ID'], 'linked_show_id', true ); + if ( $linked_id ) { + $linked_show = get_post( $linked_id ); + $linked_fields = get_post_meta( $override['ID'], 'linked_show_fields', true ); + if ( !isset( $linked_fields['show_title'] ) || !$linked_fields['show_title'] ) { + $title = $linked_show->post_title; + } + } + + // 2.3.3.9: get possible array of override shifts + $override_shifts = get_post_meta( $override['ID'], 'show_override_sched', true ); + if ( array_key_exists( 'date', $override_shifts ) ) { + $override_shifts = array( $override_shifts ); + } + + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { - // 2.3.2: replace strtotime with to_time for timezones - $date_time = radio_station_to_time( $date ); - $inrange = true; + // --- loop override shifts --- + // 2.3.3.9: loop to allow for multiple overrides + foreach ( $override_shifts as $j => $data ) { - // --- check if in specified date range --- - if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) - || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { - $inrange = false; - } + // 2.3.3.9: ignore disabled overrides + if ( !isset( $data['disabled'] ) || ( 'yes' != $data['disabled'] ) ) { - // --- add the override data --- - if ( $inrange ) { + $date = $data['date']; + if ( '' != $date ) { - // 2.3.2: get day from date directly - // $thisday = date( 'l', $date_time ); - $day = date( 'l', strtotime( $date ) ); + // 2.3.2: replace strtotime with to_time for timezones + $date_time = radio_station_to_time( $date ); + $inrange = true; - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to conver to 24 hour format first - $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; - $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - $override_start_time = radio_station_to_time( $date . ' ' . $start_time ); - $override_end_time = radio_station_to_time( $date . ' ' . $end_time ); - // 2.3.2: fix for overrides ending at midnight - if ( '12:00 am' == $end ) { - $override_end_time = $override_end_time + ( 24 * 60 * 60 ); - } + // --- check if in specified date range --- + if ( ( isset( $range_start_time ) && ( $date_time < $range_start_time ) ) + || ( isset( $range_end_time ) && ( $date_time > $range_end_time ) ) ) { + $inrange = false; + } - if ( $override_start_time < $override_end_time ) { - - // --- add the override as is --- - $override_data = array( - 'override' => $override['ID'], - 'name' => $override['post_title'], - 'slug' => $override['post_name'], - 'date' => $date, - 'day' => $day, - 'start' => $start, - 'end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => false, - ); - // 2.3.3.7: set array order by start time - $override_list[$date][$override_start_time] = $override_data; + // --- add the override data --- + if ( $inrange ) { - } else { + // 2.3.2: get day from date directly + // $thisday = date( 'l', $date_time ); + $day = date( 'l', strtotime( $date ) ); - // --- split the override overnight --- - $override_data = array( - 'override' => $override['ID'], - 'name' => $override['post_title'], - 'slug' => $override['post_name'], - 'date' => $date, - 'day' => $day, - 'start' => $start, - 'end' => '11:59:59 pm', - 'real_end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => true, - ); - // 2.3.3.7: set array order by start time - $override_list[$date][$override_start_time] = $override_data; - - // --- set the next day split shift --- - // note: these should not wrap around to start of week - // 2.3.2: use get next date/day functions - // $nextday = date( 'l', $next_date_time ); - // $nextdate = date( 'Y-m-d', $next_date_time ); - $nextdate = radio_station_get_next_date( $date ); - $nextday = radio_station_get_next_day( $day ); - - $override_data = array( - 'override' => $override['ID'], - 'name' => $override['post_title'], - 'slug' => $override['post_name'], - 'date' => $nextdate, - 'day' => $nextday, - 'real_start' => $start, - 'start' => '00:00 am', - 'end' => $end, - 'url' => get_permalink( $override['ID'] ), - 'split' => true, - ); - // 2.3.3.7: set array order by start time - $override_list[$nextdate][$override_start_time] = $override_data; + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to conver to 24 hour format first + $start = $data['start_hour'] . ':' . $data['start_min'] . ' ' . $data['start_meridian']; + $end = $data['end_hour'] . ':' . $data['end_min'] . ' ' . $data['end_meridian']; + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $override_start_time = radio_station_to_time( $date . ' ' . $start_time ); + $override_end_time = radio_station_to_time( $date . ' ' . $end_time ); + // 2.3.2: fix for overrides ending at midnight + if ( '12:00 am' == $end ) { + $override_end_time = $override_end_time + ( 24 * 60 * 60 ); + } + // TODO: allow for multiday overrides ? + /* if ( isset( $data['multiday'] ) && ( 'yes' == $data['multiday'] ) ) { + if ( isset( $data['enddate'] ) && ( '' != $data['enddate'] ) ) { + + } + } */ + + if ( $override_start_time < $override_end_time ) { + + // --- add the override as is --- + $override_data = array( + 'override' => $override['ID'], + 'name' => $title, + 'slug' => $override['post_name'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => $end, + 'url' => get_permalink( $override['ID'] ), + 'split' => false, + ); + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; + + } else { + + // --- split the override overnight --- + $override_data = array( + 'override' => $override['ID'], + 'name' => $title, + 'slug' => $override['post_name'], + 'date' => $date, + 'day' => $day, + 'start' => $start, + 'end' => '11:59:59 pm', + 'real_end' => $end, + 'url' => get_permalink( $override['ID'] ), + 'split' => true, + ); + // 2.3.3.7: set array order by start time + $override_list[$date][$override_start_time] = $override_data; + + // --- set the next day split shift --- + // note: these should not wrap around to start of week + // 2.3.2: use get next date/day functions + // $nextday = date( 'l', $next_date_time ); + // $nextdate = date( 'Y-m-d', $next_date_time ); + $nextdate = radio_station_get_next_date( $date ); + $nextday = radio_station_get_next_day( $day ); + + $override_data = array( + 'override' => $override['ID'], + 'name' => $title, + 'slug' => $override['post_name'], + 'date' => $nextdate, + 'day' => $nextday, + 'real_start' => $start, + 'start' => '00:00 am', + 'end' => $end, + 'url' => get_permalink( $override['ID'] ), + 'split' => true, + ); + // 2.3.3.7: set array order by start time + $override_list[$nextdate][$override_start_time] = $override_data; + } + } } } } @@ -648,11 +689,6 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } elseif ( false !== $args['limit'] ) { $default = false; } - if ( !isset( $args['data'] ) ) { - $args['data'] = true; - } elseif ( true !== $args['data'] ) { - $default = false; - } if ( !isset( $args['columns'] ) || !is_array( $args['columns'] ) || ( count( $args['columns'] ) < 1 ) ) { $columns = 'posts.ID, posts.post_title, posts.post_content, posts.post_excerpt, posts.post_date'; } else { @@ -703,15 +739,18 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } // --- get records with associated show ID --- - global $wpdb; if ( 'posts' == $datatype ) { // 2.3.3.4: handle possible multiple show post values + // 2.3.3.9: added 'i:' prefix to LIKE match value $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = '" . $metakey . "' AND meta_value LIKE '%" . $show_id . "%'"; + . " WHERE meta_key = '" . $metakey . "' AND meta_value LIKE '%i:" . $show_id . "%'"; $results = $wpdb->get_results( $query, ARRAY_A ); - // echo "Results: "; print_r( $results ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Query: ' . $query . ''; + echo 'Related Results: ' . print_r( $results, true ) . ''; + } if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { return false; } @@ -719,18 +758,22 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { // --- get/check post IDs in post meta --- $post_ids = array(); foreach ( $results as $result ) { - // TODO: check raw result is serialized or array ? + // TODO: check if raw result is serialized or array ? $show_ids = maybe_unserialize( $result['meta_value'] ); if ( $show_id == $result['meta_value'] || in_array( $show_id, $show_ids ) ) { $post_ids[] = $result['post_id']; } } - // echo "Post IDs: "; print_r( $post_ids ); + } else { $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; $query = $wpdb->prepare( $query, $show_id ); $post_metas = $wpdb->get_results( $query, ARRAY_A ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Query: ' . $query . ''; + echo 'Related Results: ' . print_r( $post_metas, true ) . ''; + } if ( !$post_metas || !is_array( $post_metas ) || ( count( $post_metas ) < 1 ) ) { return false; } @@ -757,18 +800,6 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } $results = $wpdb->get_results( $query, ARRAY_A ); - // --- maybe get additional data --- - // TODO: maybe get additional data for each data type ? - // if ( $args['data'] && $results && is_array( $results ) && ( count( $results ) > 0 ) ) { - // if ( 'posts' == $datatype ) { - // - // } elseif ( 'playlists' == $datatype ) { - // - // } elseif ( 'episodes' == $datatype ) { - // - // } - // } - // --- maybe cache default show data --- if ( $default ) { do_action( 'radio_station_cache_data', $datatype, $show_id, $results ); @@ -835,10 +866,13 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $user = get_user_by( 'ID', $host ); $radio_station_data['user-' . $host] = $user; } - $hosts[] = array( - 'name' => $user->display_name, - 'url' => radio_station_get_host_url( $host ), - ); + // 2.3.3.9: added check user still exists + if ( $user ) { + $hosts[] = array( + 'name' => $user->display_name, + 'url' => radio_station_get_host_url( $host ), + ); + } } } if ( is_array( $show_producers ) && ( count( $show_producers ) > 0 ) ) { @@ -849,10 +883,13 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $user = get_user_by( 'ID', $producer ); $radio_station_data['user-' . $producer] = $user; } - $producers[] = array( - 'name' => $user->display_name, - 'url' => radio_station_get_producer_url( $producer ), - ); + // 2.3.3.9: added check user still exists + if ( $user ) { + $producers[] = array( + 'name' => $user->display_name, + 'url' => radio_station_get_producer_url( $producer ), + ); + } } } @@ -961,16 +998,17 @@ function radio_station_get_override_data_meta( $override ) { if ( !is_object( $override ) ) { $override = get_post( $override ); } + $override_id = $override->ID; // --- get override terms --- $genre_list = $language_list = array(); - $genres = wp_get_post_terms( $override->ID, RADIO_STATION_GENRES_SLUG ); + $genres = wp_get_post_terms( $override_id, RADIO_STATION_GENRES_SLUG ); if ( $genres ) { foreach ( $genres as $genre ) { $genre_list[] = $genre->name; } } - $languages = wp_get_post_terms( $override->ID, RADIO_STATION_LANGUAGES_SLUG ); + $languages = wp_get_post_terms( $override_id, RADIO_STATION_LANGUAGES_SLUG ); if ( $languages ) { foreach ( $languages as $language ) { $language_list[] = $language->name; @@ -978,8 +1016,8 @@ function radio_station_get_override_data_meta( $override ) { } // --- get override user data --- - $override_hosts = get_post_meta( $override->ID, 'override_user_list', true ); - $override_producers = get_post_meta( $override->ID, 'override_producer_list', true ); + $override_hosts = get_post_meta( $override_id, 'override_user_list', true ); + $override_producers = get_post_meta( $override_id, 'override_producer_list', true ); $hosts = $producers = array(); if ( is_array( $override_hosts ) && ( count( $override_hosts ) > 0 ) ) { foreach ( $override_hosts as $host ) { @@ -1008,12 +1046,12 @@ function radio_station_get_override_data_meta( $override ) { // --- get avatar and thumbnail URL --- // 2.3.1: added show avatar and image URLs - $avatar_url = radio_station_get_show_avatar_url( $override->ID ); + $avatar_url = radio_station_get_show_avatar_url( $override_id ); if ( !$avatar_url ) { $avatar_url = ''; } $thumbnail_url = ''; - $thumbnail_id = get_post_meta( $override->ID, '_thumbnail_id', true ); + $thumbnail_id = get_post_meta( $override_id, '_thumbnail_id', true ); if ( $thumbnail_id ) { $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' ); $thumbnail_url = $thumbnail[0]; @@ -1022,10 +1060,10 @@ function radio_station_get_override_data_meta( $override ) { // --- create array and return --- // 2.3.1: added show avatar and image URLs $override_data = array( - 'id' => $override->ID, + 'id' => $override_id, 'name' => $override->post_title, 'slug' => $override->post_name, - 'url' => get_permalink( $override->ID ), + 'url' => get_permalink( $override_id ), 'genres' => $genre_list, 'languages' => $language_list, 'hosts' => $hosts, @@ -1034,13 +1072,125 @@ function radio_station_get_override_data_meta( $override ) { 'image_url' => $thumbnail_url, ); + // --- linked Show ID --- + // 2.3.3.9: maybe use linked show data + $linked_id = get_post_meta( $override_id, 'linked_show_id', true ); + if ( $linked_id ) { + + // --- use linked show data --- + $show_data = radio_station_get_show_data_meta( $linked_id ); + $show_fields = get_post_meta( $override_id, 'linked_show_fields', true ); + + // --- map info keys to meta keys --- + $fields = array( + 'name' => 'show_title', + 'hosts' => 'show_user_list', + 'producers' => 'show_producer_list', + 'avatar_url' => 'show_avatar', + // 'image_url' => 'show_thumbnail', + ); + + // --- apply selected show data to override --- + foreach ( $fields as $key => $meta_key ) { + if ( isset( $show_fields[$meta_key] ) && $show_fields[$meta_key] ) { + $override_data[$key] = $show_data[$key]; + } + } + } + // --- filter and return --- - $override_id = $override->ID; $override_data = apply_filters( 'radio_station_override_data', $override_data, $override_id ); return $override_data; } +// ----------------------- +// Get Show Override Value +// ----------------------- +// 2.3.3.9: added to check linked show field by key +function radio_station_get_show_override( $override_id, $meta_key ) { + + global $radio_station_data; + + // --- check/cache linked show values --- + if ( isset( $radio_station_data['linked-show-' . $override_id] ) ) { + $linked_id = $radio_station_data['linked-show-' . $override_id]; + if ( isset( $radio_station_data['linked-fields-' . $override_id] ) ) { + $linked_fields = $radio_station_data['linked-fields-' . $override_id]; + } + } else { + $linked_id = get_post_meta( $override_id, 'linked_show_id', true ); + $radio_station_data['linked-show-' . $override_id] = $linked_id; + if ( $linked_id ) { + $linked_fields = get_post_meta( $override_id, 'linked_show_fields', true ); + $radio_station_data['linked-fields-' . $override_id] = $linked_fields; + } + } + + // --- return the show field value for this key --- + // echo "Override: " . $override_id . " - Meta Key: " . $meta_key; + if ( $linked_id ) { + // echo " - Linked Show: " . $linked_id; + if ( !isset( $linked_fields[$meta_key] ) || empty( $linked_fields[$meta_key] ) ) { + if ( 'show_title' == $meta_key ) { + $post = get_post( $linked_id ); + $value = $post->post_title; + } elseif ( 'show_image' == $meta_key ) { + $value = get_post_meta( $linked_id, '_thumbnail_id', true ); + } else { + $value = get_post_meta( $linked_id, $meta_key, true ); + // echo " - Value: " . $value . "
    " . PHP_EOL; + } + if ( $value ) { + return $value; + } else { + return ''; + } + } + } + return false; +} + +// ----------------------------- +// Get Linked Overrides for Show +// ----------------------------- +// 2.3.3.9: added for show page display +function radio_station_get_linked_overrides( $post_id ) { + + // -- get show ID for override or show post --- + $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); + if ( $linked_id ) { + $show_id = $linked_id; + } else { + $show_id = $post_id; + } + + // --- get linked overrides via show ID --- + global $wpdb; + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = 'linked_show_id' AND meta_value = %d"; + $query = $wpdb->prepare( $query, $show_id ); + $results = $wpdb->get_results( $query, ARRAY_A ); + $overrides = false; + if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $override_id = $result['post_id']; + $schedule = get_post_meta( $override_id, 'show_override_sched', true ); + if ( $schedule ) { + if ( !is_array( $overrides ) ) { + $override = array(); + } + if ( !is_array( $schedule ) ) { + $schedule = array( $schedule ); + } + foreach ( $schedule as $override ) { + $overrides[] = $override; + } + } + } + } + return $overrides; +} + // -------------------- // Get Current Schedule // -------------------- @@ -1103,7 +1253,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // (from 12am this morning, for one week ahead and back) // 2.3.1: get start and end dates from weekdays // 2.3.2: use get time function with timezone - $date = radio_station_get_time( 'date' ); + // 2.3.3.9: pass second argument as time may not be now + $date = radio_station_get_time( 'date', $now ); // $start_time = strtotime( '12am ' . $date ); // $end_time = $start_time + ( 7 * 24 * 60 * 60 ) + 1; @@ -1498,8 +1649,7 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } - if ( isset( $current_show ) - || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { + if ( isset( $current_show ) || ( $prev_shift_end && ( $now > $prev_shift_end ) && ( $now < $shift_start_time ) ) ) { // --- set next show --- // 2.3.2: set date for widget @@ -1677,7 +1827,7 @@ function radio_station_get_current_show( $time = false ) { if ( RADIO_STATION_DEBUG ) { echo ''; - echo 'Current? ' . $now . ' - ' . $shift_start_time . ' - ' . $shift_end_time . PHP_EOL; + echo 'Now: ' . $now . ' - Shift Start: ' . $shift_start_time . ' - Shift End: ' . $shift_end_time . PHP_EOL; echo print_r( $shift, true ) . PHP_EOL; } @@ -1729,6 +1879,10 @@ function radio_station_get_current_show( $time = false ) { // 2.3.2: added time argument to filter $current_show = apply_filters( 'radio_station_current_show', $current_show, $time ); + if ( RADIO_STATION_DEBUG ) { + echo 'Current Show: ' . print_r( $current_show, true ) . PHP_EOL; + } + // --- set to global data --- if ( !$time ) { $radio_station_data['current_show'] = $current_show; @@ -2443,6 +2597,8 @@ function radio_station_check_shifts( $all_shifts ) { // (...but only if not disabled!) if ( $set_shift && !$disabled ) { // - no longer need shift and post updated times - + // 2.3.3.9: keep shift ID with schedule data + $shift['id'] = $shift['shift']['id']; unset( $shift['shift'] ); unset( $shift['updated'] ); if ( '00:00 am' == $shift['start'] ) { @@ -2605,10 +2761,11 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // --- get shift start and end time --- // 2.3.2: fix to convert to 24 hour times first - $start_time = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; - $end_time = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; - $start_time = radio_station_convert_shift_time( $start_time ); - $end_time = radio_station_convert_shift_time( $end_time ); + // 2.3.3.9: set shift start and end to prevent undefined index warning later + $shift['start'] = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + $shift['end'] = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; + $start_time = radio_station_convert_shift_time( $shift['start'] ); + $end_time = radio_station_convert_shift_time( $shift['end'] ); // 2.3.2: use next week day instead of date $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); @@ -2733,6 +2890,7 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { if ( isset( $day_shift ) ) { // 2.3.3.6: added check to not last check shift against itself + // TODO: check possible re-occurrence undefined index 'start' warning here ? if ( ( $day_shift['show'] != $show_id ) || ( $day_shift['day'] != $shift['day'] ) || ( $day_shift['start'] != $shift['start'] ) @@ -2951,8 +3109,18 @@ function radio_station_validate_shift( $shift ) { // 2.3.0: trigger show avatar check/update when editing add_action( 'replace_editor', 'radio_station_update_show_avatar', 10, 2 ); function radio_station_update_show_avatar( $replace_editor, $post ) { - $show_id = $post->ID; - radio_station_get_show_avatar_id( $show_id ); + + // 2.3.3.9: fix to only apply to image-specific post types + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + $post_types = apply_filters( 'radio_station_show_avatar_post_types', $post_types ); + if ( in_array( $post->post_type, $post_types ) ) { + $show_id = $post->ID; + radio_station_get_show_avatar_id( $show_id ); + } else { + // 2.3.3.9: add cleanup for non-intended post types + delete_post_meta( $post->ID, 'show_avatar' ); + delete_post_meta( $post->ID, '_rs_image_updated' ); + } return $replace_editor; } @@ -3026,7 +3194,8 @@ function radio_station_get_show_avatar( $show_id, $size = 'thumbnail', $attr = a } // --- filter and return --- - $avatar = apply_filters( 'radio_station_show_avatar', $avatar, $show_id ); + // 2.3.3.9: change conflicting (duplicate) filter name for show avatar + $avatar = apply_filters( 'radio_station_show_avatar_output', $avatar, $show_id ); return $avatar; } @@ -4106,6 +4275,38 @@ function radio_station_get_profile_id( $type, $user_id ) { return false; } +// ------------------ +// Get Language Terms +// ------------------ +// 2.3.3.9: added for language archive shortcode +function radio_station_get_language_terms( $args = false ) { + + $defaults = array( 'taxonomy' => RADIO_STATION_LANGUAGES_SLUG, 'orderby' => 'name', 'hide_empty' => true ); + if ( $args && is_array( $args ) ) { + foreach ( $args as $key => $value ) { + $defaults[$key] = $value; + } + } + $terms = get_terms( $defaults ); + $languages = array(); + if ( $terms ) { + foreach ( $terms as $term ) { + $languages[$term->name] = array( + 'id' => $term->term_id, + 'name' => $term->name, + 'slug' => $term->slug, + 'description' => $term->description, + 'url' => get_term_link( $term, RADIO_STATION_LANGUAGES_SLUG ), + ); + } + } + + // --- filter and return --- + $languages = apply_filters( 'radio_station_get_language_terms', $languages, $args ); + + return $languages; +} + // ------------- // Get Languages // ------------- @@ -4331,6 +4532,75 @@ function radio_station_sanitize_values( $data, $keys ) { return $sanitized; } +// -------------------- +// Sanitize Input Value +// -------------------- +// 2.3.3.9: added for combined show/override input saving +function radio_station_sanitize_input( $prefix, $key ) { + + $postkey = $prefix . '_' . $key; + $numeric_keys = array( 'avatar', 'image' ); + $checkbox_keys = array( 'active', 'download' ); + $user_keys = array( 'user_list', 'producer_list' ); + if ( 'file' == $key ) { + $value = wp_strip_all_tags( trim( $_POST[$postkey] ) ); + } elseif ( 'email' == $key ) { + $value = sanitize_email( trim( $_POST[$postkey] ) ); + } elseif ( 'link' == $key ) { + $value = filter_var( trim( $_POST[$postkey] ), FILTER_SANITIZE_URL ); + } elseif ( 'patreon' == $key ) { + $value = sanitize_title( $_POST[$postkey] ); + } elseif ( 'phone' == $key ) { + // 2.3.3.6: added phone number with character filter validation + $value = trim( $_POST[$postkey] ); + if ( strlen( $value ) > 0 ) { + $value = str_split( $value, 1 ); + $value = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $value ); + if ( count( $value ) > 0 ) { + $value = implode( '', $value ); + } else { + $value = ''; + } + } + } elseif ( in_array( $key, $numeric_keys ) ) { + $value = absint( $_POST[$postkey] ); + if ( $value < 0 ) { + $value = ''; + } + } elseif ( in_array( $key, $checkbox_keys ) ) { + + // 2.2.8: removed strict in_array checking + // 2.3.2: fix for unchecked boxes index warning + $value = ''; + if ( isset( $_POST[$postkey] ) ) { + $value = $_POST[$postkey]; + } + if ( !in_array( $value, array( '', 'on' ) ) ) { + $value = ''; + } + } elseif ( in_array( $key, $user_keys ) ) { + + if ( isset( $_POST[$postkey] ) ) { + $value = $_POST[$postkey]; + } + if ( !isset( $value ) || !is_array( $value ) ) { + $value = array(); + } else { + foreach ( $value as $i => $userid ) { + if ( !empty( $userid ) ) { + $user = get_user_by( 'ID', $userid ); + if ( !$user ) { + unset( $value[$i] ); + } + } + } + } + } + + + return $value; +} + // ------------------------- // Sanitize Shortcode Values // ------------------------- @@ -4411,6 +4681,41 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'instance' => 'integer', 'for_time' => 'integer', ); + } elseif ( 'master-schedule' == $type ) { + + // --- master schedule attribute keys --- + // 2.3.3.9: added for AJAX schedule loading + $keys = array( + + // --- schedule display options --- + 'time' => 'text', + 'show_times' => 'boolean', + 'show_link' => 'boolean', + 'view' => 'text', + 'days' => 'text', + 'start_day' => 'text', + 'start_date' => 'text', + 'display_day' => 'text', + 'display_date' => 'text', + 'display_month' => 'text', + + // --- show display options --- + 'show_image' => 'boolean', + 'show_desc' => 'boolean', + 'show_hosts' => 'boolean', + 'link_hosts' => 'boolean', + 'show_genres' => 'boolean', + 'show_encore' => 'boolean', + 'show_file' => 'boolean', + + // --- view specific options --- + 'divheight' => 'integer', + 'gridwidth' => 'integer', + 'hide_past_shows' => 'boolean', + 'image_position' => 'text', + + ); + } // --- handle extra keys --- @@ -4443,6 +4748,42 @@ function radio_station_delete_transients_with_prefix( $prefix ) { } } +// ----------------- +// Clear Cached Data +// ----------------- +// 2.3.3.9: made into separate function +function radio_station_clear_cached_data( $post_id = false ) { + + // --- clear main schedule transients --- + // 2.3.3: remove current show transient + // 2.3.4: add previous show transient + delete_transient( 'radio_station_current_schedule' ); + delete_transient( 'radio_station_next_show' ); + delete_transient( 'radio_station_previous_show' ); + + // --- clear time-based schedule transients --- + // 2.3.4: delete all prefixed transients (for times) + radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); + radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); + radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); + + // --- maybe clear show meta data --- + if ( $post_id ) { + do_action( 'radio_station_clear_data', 'show', $post_id ); + do_action( 'radio_station_clear_data', 'show_meta', $post_id ); + } + + // --- maybe send directory ping --- + // 2.3.1: added directory update ping option + // 2.3.2: queue directory ping + radio_station_queue_directory_ping(); + + // --- set last updated schedule time --- + // 2.3.2: added for data API use + update_option( 'radio_station_schedule_updated', time() ); + +} + // -------------------- // === Translations === @@ -4586,7 +4927,6 @@ function radio_station_replace_month( $string ) { // 2.2.7: added meridiem translation function function radio_station_translate_meridiem( $meridiem ) { global $wp_locale; - return $wp_locale->get_meridiem( $meridiem ); } diff --git a/js/moment.js b/js/moment.js new file mode 100644 index 0000000..1484d6c --- /dev/null +++ b/js/moment.js @@ -0,0 +1,5670 @@ +//! moment.js +//! version : 2.29.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, (function () { 'use strict'; + + var hookCallback; + + function hooks() { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback(callback) { + hookCallback = callback; + } + + function isArray(input) { + return ( + input instanceof Array || + Object.prototype.toString.call(input) === '[object Array]' + ); + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return ( + input != null && + Object.prototype.toString.call(input) === '[object Object]' + ); + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return Object.getOwnPropertyNames(obj).length === 0; + } else { + var k; + for (k in obj) { + if (hasOwnProp(obj, k)) { + return false; + } + } + return true; + } + } + + function isUndefined(input) { + return input === void 0; + } + + function isNumber(input) { + return ( + typeof input === 'number' || + Object.prototype.toString.call(input) === '[object Number]' + ); + } + + function isDate(input) { + return ( + input instanceof Date || + Object.prototype.toString.call(input) === '[object Date]' + ); + } + + function map(arr, fn) { + var res = [], + i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function createUTC(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty: false, + unusedTokens: [], + unusedInput: [], + overflow: -2, + charsLeftOver: 0, + nullInput: false, + invalidEra: null, + invalidMonth: null, + invalidFormat: false, + userInvalidated: false, + iso: false, + parsedDateParts: [], + era: null, + meridiem: null, + rfc2822: false, + weekdayMismatch: false, + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this), + len = t.length >>> 0, + i; + + for (i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m), + parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }), + isNowValid = + !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidEra && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = + isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } else { + return isNowValid; + } + } + return m._isValid; + } + + function createInvalid(flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = (hooks.momentProperties = []), + updateInProgress = false; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment(obj) { + return ( + obj instanceof Moment || (obj != null && obj._isAMomentObject != null) + ); + } + + function warn(msg) { + if ( + hooks.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && + console.warn + ) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = [], + arg, + i, + key; + for (i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (key in arguments[0]) { + if (hasOwnProp(arguments[0], key)) { + arg += key + ': ' + arguments[0][key] + ', '; + } + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn( + msg + + '\nArguments: ' + + Array.prototype.slice.call(args).join('') + + '\n' + + new Error().stack + ); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; + + function isFunction(input) { + return ( + (typeof Function !== 'undefined' && input instanceof Function) || + Object.prototype.toString.call(input) === '[object Function]' + ); + } + + function set(config) { + var prop, i; + for (i in config) { + if (hasOwnProp(config, i)) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + + /\d{1,2}/.source + ); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), + prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if ( + hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop]) + ) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, + res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay: '[Today at] LT', + nextDay: '[Tomorrow at] LT', + nextWeek: 'dddd [at] LT', + lastDay: '[Yesterday at] LT', + lastWeek: '[Last] dddd [at] LT', + sameElse: 'L', + }; + + function calendar(key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return ( + (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + + absNumber + ); + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, + formatFunctions = {}, + formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken(token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal( + func.apply(this, arguments), + token + ); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), + i, + length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', + i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) + ? array[i].call(mom, format) + : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = + formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace( + localFormattingTokens, + replaceLongDateFormatTokens + ); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var defaultLongDateFormat = { + LTS: 'h:mm:ss A', + LT: 'h:mm A', + L: 'MM/DD/YYYY', + LL: 'MMMM D, YYYY', + LLL: 'MMMM D, YYYY h:mm A', + LLLL: 'dddd, MMMM D, YYYY h:mm A', + }; + + function longDateFormat(key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper + .match(formattingTokens) + .map(function (tok) { + if ( + tok === 'MMMM' || + tok === 'MM' || + tok === 'DD' || + tok === 'dddd' + ) { + return tok.slice(1); + } + return tok; + }) + .join(''); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate() { + return this._invalidDate; + } + + var defaultOrdinal = '%d', + defaultDayOfMonthOrdinalParse = /\d{1,2}/; + + function ordinal(number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + w: 'a week', + ww: '%d weeks', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years', + }; + + function relativeTime(number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return isFunction(output) + ? output(number, withoutSuffix, string, isFuture) + : output.replace(/%d/i, number); + } + + function pastFuture(diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias(unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' + ? aliases[units] || aliases[units.toLowerCase()] + : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = [], + u; + for (u in unitsObj) { + if (hasOwnProp(unitsObj, u)) { + units.push({ unit: u, priority: priorities[u] }); + } + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function absFloor(number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + function makeGetSet(unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + + function get(mom, unit) { + return mom.isValid() + ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() + : NaN; + } + + function set$1(mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if ( + unit === 'FullYear' && + isLeapYear(mom.year()) && + mom.month() === 1 && + mom.date() === 29 + ) { + value = toInt(value); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit]( + value, + mom.month(), + daysInMonth(value, mom.month()) + ); + } else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } + + // MOMENTS + + function stringGet(units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + function stringSet(units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units), + i; + for (i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + var match1 = /\d/, // 0 - 9 + match2 = /\d\d/, // 00 - 99 + match3 = /\d{3}/, // 000 - 999 + match4 = /\d{4}/, // 0000 - 9999 + match6 = /[+-]?\d{6}/, // -999999 - 999999 + match1to2 = /\d\d?/, // 0 - 99 + match3to4 = /\d\d\d\d?/, // 999 - 9999 + match5to6 = /\d\d\d\d\d\d?/, // 99999 - 999999 + match1to3 = /\d{1,3}/, // 0 - 999 + match1to4 = /\d{1,4}/, // 0 - 9999 + match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999 + matchUnsigned = /\d+/, // 0 - inf + matchSigned = /[+-]?\d+/, // -inf - inf + matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z + matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, + regexes; + + regexes = {}; + + function addRegexToken(token, regex, strictRegex) { + regexes[token] = isFunction(regex) + ? regex + : function (isStrict, localeData) { + return isStrict && strictRegex ? strictRegex : regex; + }; + } + + function getParseRegexForToken(token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape( + s + .replace('\\', '') + .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function ( + matched, + p1, + p2, + p3, + p4 + ) { + return p1 || p2 || p3 || p4; + }) + ); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken(token, callback) { + var i, + func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken(token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + WEEK = 7, + WEEKDAY = 8; + + function mod(n, x) { + return ((n % x) + x) % x; + } + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 + ? isLeapYear(year) + ? 29 + : 28 + : 31 - ((modMonth % 7) % 2); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split( + '_' + ), + defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split( + '_' + ), + MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, + defaultMonthsShortRegex = matchWord, + defaultMonthsRegex = matchWord; + + function localeMonths(m, format) { + if (!m) { + return isArray(this._months) + ? this._months + : this._months['standalone']; + } + return isArray(this._months) + ? this._months[m.month()] + : this._months[ + (this._months.isFormat || MONTHS_IN_FORMAT).test(format) + ? 'format' + : 'standalone' + ][m.month()]; + } + + function localeMonthsShort(m, format) { + if (!m) { + return isArray(this._monthsShort) + ? this._monthsShort + : this._monthsShort['standalone']; + } + return isArray(this._monthsShort) + ? this._monthsShort[m.month()] + : this._monthsShort[ + MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone' + ][m.month()]; + } + + function handleStrictParse(monthName, format, strict) { + var i, + ii, + mom, + llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort( + mom, + '' + ).toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse(monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp( + '^' + this.months(mom, '').replace('.', '') + '$', + 'i' + ); + this._shortMonthsParse[i] = new RegExp( + '^' + this.monthsShort(mom, '').replace('.', '') + '$', + 'i' + ); + } + if (!strict && !this._monthsParse[i]) { + regex = + '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'MMMM' && + this._longMonthsParse[i].test(monthName) + ) { + return i; + } else if ( + strict && + format === 'MMM' && + this._shortMonthsParse[i].test(monthName) + ) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth(mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth(value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } + + function getDaysInMonth() { + return daysInMonth(this.year(), this.month()); + } + + function monthsShortRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict + ? this._monthsShortStrictRegex + : this._monthsShortRegex; + } + } + + function monthsRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict + ? this._monthsStrictRegex + : this._monthsRegex; + } + } + + function computeMonthsParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._monthsShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? zeroFill(y, 4) : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = + input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + // HOOKS + + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear() { + return isLeapYear(this.year()); + } + + function createDate(y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date; + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } + + return date; + } + + function createUTCDate(y) { + var date, args; + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + args = Array.prototype.slice.call(arguments); + // preserve leap years using a full 400 year cycle, then reset + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } + + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, + resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear, + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, + resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear, + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function ( + input, + week, + config, + token + ) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek(mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 6th is the first week of the year. + }; + + function localeFirstDayOfWeek() { + return this._week.dow; + } + + function localeFirstDayOfYear() { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek(input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek(input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + function shiftWeekdays(ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split( + '_' + ), + defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + defaultWeekdaysRegex = matchWord, + defaultWeekdaysShortRegex = matchWord, + defaultWeekdaysMinRegex = matchWord; + + function localeWeekdays(m, format) { + var weekdays = isArray(this._weekdays) + ? this._weekdays + : this._weekdays[ + m && m !== true && this._weekdays.isFormat.test(format) + ? 'format' + : 'standalone' + ]; + return m === true + ? shiftWeekdays(weekdays, this._week.dow) + : m + ? weekdays[m.day()] + : weekdays; + } + + function localeWeekdaysShort(m) { + return m === true + ? shiftWeekdays(this._weekdaysShort, this._week.dow) + : m + ? this._weekdaysShort[m.day()] + : this._weekdaysShort; + } + + function localeWeekdaysMin(m) { + return m === true + ? shiftWeekdays(this._weekdaysMin, this._week.dow) + : m + ? this._weekdaysMin[m.day()] + : this._weekdaysMin; + } + + function handleStrictParse$1(weekdayName, format, strict) { + var i, + ii, + mom, + llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin( + mom, + '' + ).toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort( + mom, + '' + ).toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse(weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp( + '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._shortWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._minWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + } + if (!this._weekdaysParse[i]) { + regex = + '^' + + this.weekdays(mom, '') + + '|^' + + this.weekdaysShort(mom, '') + + '|^' + + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'dddd' && + this._fullWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'ddd' && + this._shortWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'dd' && + this._minWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + function weekdaysRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict + ? this._weekdaysStrictRegex + : this._weekdaysRegex; + } + } + + function weekdaysShortRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict + ? this._weekdaysShortStrictRegex + : this._weekdaysShortRegex; + } + } + + function weekdaysMinRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict + ? this._weekdaysMinStrictRegex + : this._weekdaysMinRegex; + } + } + + function computeWeekdaysParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], + shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom, + minp, + shortp, + longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = regexEscape(this.weekdaysMin(mom, '')); + shortp = regexEscape(this.weekdaysShort(mom, '')); + longp = regexEscape(this.weekdays(mom, '')); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._weekdaysShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + this._weekdaysMinStrictRegex = new RegExp( + '^(' + minPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return ( + '' + + hFormat.apply(this) + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return ( + '' + + this.hours() + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + function meridiem(token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem( + this.hours(), + this.minutes(), + lowercase + ); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem(isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM(input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return (input + '').toLowerCase().charAt(0) === 'p'; + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i, + // Setting the hour should keep the time, because the user explicitly + // specified which hour they want. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + getSetHour = makeGetSet('Hours', true); + + function localeMeridiem(hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse, + }; + + // internal storage for locale config files + var locales = {}, + localeFamilies = {}, + globalLocale; + + function commonPrefix(arr1, arr2) { + var i, + minl = Math.min(arr1.length, arr2.length); + for (i = 0; i < minl; i += 1) { + if (arr1[i] !== arr2[i]) { + return i; + } + } + return minl; + } + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, + j, + next, + locale, + split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if ( + next && + next.length >= j && + commonPrefix(split, next) >= j - 1 + ) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return globalLocale; + } + + function loadLocale(name) { + var oldLocale = null, + aliasedRequire; + // TODO: Find a better way to register and load all the locales in Node + if ( + locales[name] === undefined && + typeof module !== 'undefined' && + module && + module.exports + ) { + try { + oldLocale = globalLocale._abbr; + aliasedRequire = require; + aliasedRequire('./locale/' + name); + getSetGlobalLocale(oldLocale); + } catch (e) { + // mark as not found to avoid repeating expensive file require call causing high CPU + // when trying to find en-US, en_US, en-us for every format call + locales[name] = null; // null means not found + } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale(key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } else { + if (typeof console !== 'undefined' && console.warn) { + //warn user if arguments are passed but the locale could not be set + console.warn( + 'Locale ' + key + ' not found. Did you forget to load it?' + ); + } + } + } + + return globalLocale._abbr; + } + + function defineLocale(name, config) { + if (config !== null) { + var locale, + parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple( + 'defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.' + ); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale = loadLocale(config.parentLocale); + if (locale != null) { + parentConfig = locale._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config, + }); + return null; + } + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, + tmpLocale, + parentConfig = baseConfig; + + if (locales[name] != null && locales[name].parentLocale != null) { + // Update existing child locale in-place to avoid memory-leaks + locales[name].set(mergeConfigs(locales[name]._config, config)); + } else { + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + if (tmpLocale == null) { + // updateLocale is called for creating a new locale + // Set abbr so it will have a name (getters return + // undefined otherwise). + config.abbr = name; + } + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + } + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + if (name === getSetGlobalLocale()) { + getSetGlobalLocale(name); + } + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function getLocale(key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function listLocales() { + return keys(locales); + } + + function checkOverflow(m) { + var overflow, + a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 + ? MONTH + : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) + ? DATE + : a[HOUR] < 0 || + a[HOUR] > 24 || + (a[HOUR] === 24 && + (a[MINUTE] !== 0 || + a[SECOND] !== 0 || + a[MILLISECOND] !== 0)) + ? HOUR + : a[MINUTE] < 0 || a[MINUTE] > 59 + ? MINUTE + : a[SECOND] < 0 || a[SECOND] > 59 + ? SECOND + : a[MILLISECOND] < 0 || a[MILLISECOND] > 999 + ? MILLISECOND + : -1; + + if ( + getParsingFlags(m)._overflowDayOfYear && + (overflow < YEAR || overflow > DATE) + ) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/], + ['YYYYMM', /\d{6}/, false], + ['YYYY', /\d{4}/, false], + ], + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/], + ], + aspNetJsonRegex = /^\/?Date\((-?\d+)/i, + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, + obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60, + }; + + // date from iso format + function configFromISO(config) { + var i, + l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, + dateFormat, + timeFormat, + tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + function extractFromRFC2822Strings( + yearStr, + monthStr, + dayStr, + hourStr, + minuteStr, + secondStr + ) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10), + ]; + + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + + return result; + } + + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s + .replace(/\([^)]*\)|[\n\t]/g, ' ') + .replace(/(\s\s+)/g, ' ') + .replace(/^\s\s*/, '') + .replace(/\s\s*$/, ''); + } + + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an independent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date( + parsedInput[0], + parsedInput[1], + parsedInput[2] + ).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10), + m = hm % 100, + h = (hm - m) / 100; + return h * 60 + m; + } + } + + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)), + parsedArray; + if (match) { + parsedArray = extractFromRFC2822Strings( + match[4], + match[3], + match[2], + match[5], + match[6], + match[7] + ); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + + // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + if (config._strict) { + config._isValid = false; + } else { + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } + } + + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [ + nowValue.getUTCFullYear(), + nowValue.getUTCMonth(), + nowValue.getUTCDate(), + ]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray(config) { + var i, + date, + input = [], + currentDate, + expectedWeekday, + yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if ( + config._dayOfYear > daysInYear(yearToUse) || + config._dayOfYear === 0 + ) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = + config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if ( + config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0 + ) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply( + null, + input + ); + expectedWeekday = config._useUTC + ? config._d.getUTCDay() + : config._d.getDay(); + + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + + // check for mismatching day of week + if ( + config._w && + typeof config._w.d !== 'undefined' && + config._w.d !== expectedWeekday + ) { + getParsingFlags(config).weekdayMismatch = true; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults( + w.GG, + config._a[YEAR], + weekOfYear(createLocal(), 1, 4).year + ); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from beginning of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to beginning of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // constant that refers to the ISO standard + hooks.ISO_8601 = function () {}; + + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () {}; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, + parsedInput, + tokens, + token, + skipped, + stringLength = string.length, + totalParsedInputLength = 0, + era; + + tokens = + expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || + [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice( + string.indexOf(parsedInput) + parsedInput.length + ); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = + stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if ( + config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0 + ) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap( + config._locale, + config._a[HOUR], + config._meridiem + ); + + // handle era + era = getParsingFlags(config).era; + if (era !== null) { + config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]); + } + + configFromArray(config); + checkOverflow(config); + } + + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + scoreToBeat, + i, + currentScore, + validFormatFound, + bestFormatIsValid = false; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + validFormatFound = false; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (isValid(tempConfig)) { + validFormatFound = true; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (!bestFormatIsValid) { + if ( + scoreToBeat == null || + currentScore < scoreToBeat || + validFormatFound + ) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + if (validFormatFound) { + bestFormatIsValid = true; + } + } + } else { + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i), + dayOrDate = i.day === undefined ? i.date : i.day; + config._a = map( + [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond], + function (obj) { + return obj && parseInt(obj, 10); + } + ); + + configFromArray(config); + } + + function createFromConfig(config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig(config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({ nullInput: true }); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC(input, format, locale, strict, isUTC) { + var c = {}; + + if (format === true || format === false) { + strict = format; + format = undefined; + } + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ( + (isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0) + ) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function createLocal(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ), + prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min() { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max() { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +new Date(); + }; + + var ordering = [ + 'year', + 'quarter', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', + ]; + + function isDurationValid(m) { + var key, + unitHasDecimal = false, + i; + for (key in m) { + if ( + hasOwnProp(m, key) && + !( + indexOf.call(ordering, key) !== -1 && + (m[key] == null || !isNaN(m[key])) + ) + ) { + return false; + } + } + + for (i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; + } + + function isValid$1() { + return this._isValid; + } + + function createInvalid$1() { + return createDuration(NaN); + } + + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || normalizedInput.isoWeek || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = + +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + quarters * 3 + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); + } + + function isDuration(obj) { + return obj instanceof Duration; + } + + function absRound(number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ( + (dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i])) + ) { + diffs++; + } + } + return diffs + lengthDiff; + } + + // FORMATTING + + function offset(token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(), + sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return ( + sign + + zeroFill(~~(offset / 60), 2) + + separator + + zeroFill(~~offset % 60, 2) + ); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher), + chunk, + parts, + minutes; + + if (matches === null) { + return null; + } + + chunk = matches[matches.length - 1] || []; + parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = + (isMoment(input) || isDate(input) + ? input.valueOf() + : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + + function getDateOffset(m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset()); + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset(input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract( + this, + createDuration(input - offset, 'm'), + 1, + false + ); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone(input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC(keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal(keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset() { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } else { + this.utcOffset(0, true); + } + } + return this; + } + + function hasAlignedHourOffset(input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime() { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted() { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}, + other; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = + this.isValid() && compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal() { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset() { + return this.isValid() ? this._isUTC : false; + } + + function isUtc() { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + + function createDuration(input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months, + }; + } else if (isNumber(input) || !isNaN(+input)) { + duration = {}; + if (key) { + duration[key] = +input; + } else { + duration.milliseconds = +input; + } + } else if ((match = aspNetRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match + }; + } else if ((match = isoRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: parseIso(match[2], sign), + M: parseIso(match[3], sign), + w: parseIso(match[4], sign), + d: parseIso(match[5], sign), + h: parseIso(match[6], sign), + m: parseIso(match[7], sign), + s: parseIso(match[8], sign), + }; + } else if (duration == null) { + // checks for null or undefined + duration = {}; + } else if ( + typeof duration === 'object' && + ('from' in duration || 'to' in duration) + ) { + diffRes = momentsDifference( + createLocal(duration.from), + createLocal(duration.to) + ); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + if (isDuration(input) && hasOwnProp(input, '_isValid')) { + ret._isValid = input._isValid; + } + + return ret; + } + + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso(inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {}; + + res.months = + other.month() - base.month() + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +base.clone().add(res.months, 'M'); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return { milliseconds: 0, months: 0 }; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple( + name, + 'moment().' + + name + + '(period, number) is deprecated. Please use moment().' + + name + + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.' + ); + tmp = val; + val = period; + period = tmp; + } + + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + + function addSubtract(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } + + var add = createAdder(1, 'add'), + subtract = createAdder(-1, 'subtract'); + + function isString(input) { + return typeof input === 'string' || input instanceof String; + } + + // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined + function isMomentInput(input) { + return ( + isMoment(input) || + isDate(input) || + isString(input) || + isNumber(input) || + isNumberOrStringArray(input) || + isMomentInputObject(input) || + input === null || + input === undefined + ); + } + + function isMomentInputObject(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'years', + 'year', + 'y', + 'months', + 'month', + 'M', + 'days', + 'day', + 'd', + 'dates', + 'date', + 'D', + 'hours', + 'hour', + 'h', + 'minutes', + 'minute', + 'm', + 'seconds', + 'second', + 's', + 'milliseconds', + 'millisecond', + 'ms', + ], + i, + property; + + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function isNumberOrStringArray(input) { + var arrayTest = isArray(input), + dataTypeTest = false; + if (arrayTest) { + dataTypeTest = + input.filter(function (item) { + return !isNumber(item) && isString(input); + }).length === 0; + } + return arrayTest && dataTypeTest; + } + + function isCalendarSpec(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'sameDay', + 'nextDay', + 'lastDay', + 'nextWeek', + 'lastWeek', + 'sameElse', + ], + i, + property; + + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 + ? 'sameElse' + : diff < -1 + ? 'lastWeek' + : diff < 0 + ? 'lastDay' + : diff < 1 + ? 'sameDay' + : diff < 2 + ? 'nextDay' + : diff < 7 + ? 'nextWeek' + : 'sameElse'; + } + + function calendar$1(time, formats) { + // Support for single parameter, formats only overload to the calendar function + if (arguments.length === 1) { + if (!arguments[0]) { + time = undefined; + formats = undefined; + } else if (isMomentInput(arguments[0])) { + time = arguments[0]; + formats = undefined; + } else if (isCalendarSpec(arguments[0])) { + formats = arguments[0]; + time = undefined; + } + } + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse', + output = + formats && + (isFunction(formats[format]) + ? formats[format].call(this, now) + : formats[format]); + + return this.format( + output || this.localeData().calendar(format, this, createLocal(now)) + ); + } + + function clone() { + return new Moment(this); + } + + function isAfter(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween(from, to, units, inclusivity) { + var localFrom = isMoment(from) ? from : createLocal(from), + localTo = isMoment(to) ? to : createLocal(to); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; + } + inclusivity = inclusivity || '()'; + return ( + (inclusivity[0] === '(' + ? this.isAfter(localFrom, units) + : !this.isBefore(localFrom, units)) && + (inclusivity[1] === ')' + ? this.isBefore(localTo, units) + : !this.isAfter(localTo, units)) + ); + } + + function isSame(input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return ( + this.clone().startOf(units).valueOf() <= inputMs && + inputMs <= this.clone().endOf(units).valueOf() + ); + } + } + + function isSameOrAfter(input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + + function isSameOrBefore(input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + + function diff(input, units, asFloat) { + var that, zoneDelta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + switch (units) { + case 'year': + output = monthDiff(this, that) / 12; + break; + case 'month': + output = monthDiff(this, that); + break; + case 'quarter': + output = monthDiff(this, that) / 3; + break; + case 'second': + output = (this - that) / 1e3; + break; // 1000 + case 'minute': + output = (this - that) / 6e4; + break; // 1000 * 60 + case 'hour': + output = (this - that) / 36e5; + break; // 1000 * 60 * 60 + case 'day': + output = (this - that - zoneDelta) / 864e5; + break; // 1000 * 60 * 60 * 24, negate dst + case 'week': + output = (this - that - zoneDelta) / 6048e5; + break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: + output = this - that; + } + + return asFloat ? output : absFloor(output); + } + + function monthDiff(a, b) { + if (a.date() < b.date()) { + // end-of-month calculations work correct when the start month has more + // days than the end month. + return -monthDiff(b, a); + } + // difference in months + var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, + adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString() { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true, + m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment( + m, + utc + ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' + : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1000) + .toISOString() + .replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment( + m, + utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect() { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment', + zone = '', + prefix, + year, + datetime, + suffix; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + prefix = '[' + func + '("]'; + year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY'; + datetime = '-MM-DD[T]HH:mm:ss.SSS'; + suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); + } + + function format(inputString) { + if (!inputString) { + inputString = this.isUtc() + ? hooks.defaultFormatUtc + : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ to: this, from: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow(withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + + function to(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ from: this, to: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow(withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale(key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData() { + return this._locale; + } + + var MS_PER_SECOND = 1000, + MS_PER_MINUTE = 60 * MS_PER_SECOND, + MS_PER_HOUR = 60 * MS_PER_MINUTE, + MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; + + // actual modulo - handles negative numbers (for dates before 1970): + function mod$1(dividend, divisor) { + return ((dividend % divisor) + divisor) % divisor; + } + + function localStartOfDate(y, m, d) { + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } + + function utcStartOfDate(y, m, d) { + // Date.UTC remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } + + function startOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year(), 0, 1); + break; + case 'quarter': + time = startOfDate( + this.year(), + this.month() - (this.month() % 3), + 1 + ); + break; + case 'month': + time = startOfDate(this.year(), this.month(), 1); + break; + case 'week': + time = startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + ); + break; + case 'isoWeek': + time = startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + ); + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date()); + break; + case 'hour': + time = this._d.valueOf(); + time -= mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ); + break; + case 'minute': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case 'second': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function endOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case 'quarter': + time = + startOfDate( + this.year(), + this.month() - (this.month() % 3) + 3, + 1 + ) - 1; + break; + case 'month': + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case 'week': + time = + startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + 7 + ) - 1; + break; + case 'isoWeek': + time = + startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + 7 + ) - 1; + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case 'hour': + time = this._d.valueOf(); + time += + MS_PER_HOUR - + mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ) - + 1; + break; + case 'minute': + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case 'second': + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function valueOf() { + return this._d.valueOf() - (this._offset || 0) * 60000; + } + + function unix() { + return Math.floor(this.valueOf() / 1000); + } + + function toDate() { + return new Date(this.valueOf()); + } + + function toArray() { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hour(), + m.minute(), + m.second(), + m.millisecond(), + ]; + } + + function toObject() { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds(), + }; + } + + function toJSON() { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function isValid$2() { + return isValid(this); + } + + function parsingFlags() { + return extend({}, getParsingFlags(this)); + } + + function invalidAt() { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict, + }; + } + + addFormatToken('N', 0, 0, 'eraAbbr'); + addFormatToken('NN', 0, 0, 'eraAbbr'); + addFormatToken('NNN', 0, 0, 'eraAbbr'); + addFormatToken('NNNN', 0, 0, 'eraName'); + addFormatToken('NNNNN', 0, 0, 'eraNarrow'); + + addFormatToken('y', ['y', 1], 'yo', 'eraYear'); + addFormatToken('y', ['yy', 2], 0, 'eraYear'); + addFormatToken('y', ['yyy', 3], 0, 'eraYear'); + addFormatToken('y', ['yyyy', 4], 0, 'eraYear'); + + addRegexToken('N', matchEraAbbr); + addRegexToken('NN', matchEraAbbr); + addRegexToken('NNN', matchEraAbbr); + addRegexToken('NNNN', matchEraName); + addRegexToken('NNNNN', matchEraNarrow); + + addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function ( + input, + array, + config, + token + ) { + var era = config._locale.erasParse(input, token, config._strict); + if (era) { + getParsingFlags(config).era = era; + } else { + getParsingFlags(config).invalidEra = input; + } + }); + + addRegexToken('y', matchUnsigned); + addRegexToken('yy', matchUnsigned); + addRegexToken('yyy', matchUnsigned); + addRegexToken('yyyy', matchUnsigned); + addRegexToken('yo', matchEraYearOrdinal); + + addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR); + addParseToken(['yo'], function (input, array, config, token) { + var match; + if (config._locale._eraYearOrdinalRegex) { + match = input.match(config._locale._eraYearOrdinalRegex); + } + + if (config._locale.eraYearOrdinalParse) { + array[YEAR] = config._locale.eraYearOrdinalParse(input, match); + } else { + array[YEAR] = parseInt(input, 10); + } + }); + + function localeEras(m, format) { + var i, + l, + date, + eras = this._eras || getLocale('en')._eras; + for (i = 0, l = eras.length; i < l; ++i) { + switch (typeof eras[i].since) { + case 'string': + // truncate time + date = hooks(eras[i].since).startOf('day'); + eras[i].since = date.valueOf(); + break; + } + + switch (typeof eras[i].until) { + case 'undefined': + eras[i].until = +Infinity; + break; + case 'string': + // truncate time + date = hooks(eras[i].until).startOf('day').valueOf(); + eras[i].until = date.valueOf(); + break; + } + } + return eras; + } + + function localeErasParse(eraName, format, strict) { + var i, + l, + eras = this.eras(), + name, + abbr, + narrow; + eraName = eraName.toUpperCase(); + + for (i = 0, l = eras.length; i < l; ++i) { + name = eras[i].name.toUpperCase(); + abbr = eras[i].abbr.toUpperCase(); + narrow = eras[i].narrow.toUpperCase(); + + if (strict) { + switch (format) { + case 'N': + case 'NN': + case 'NNN': + if (abbr === eraName) { + return eras[i]; + } + break; + + case 'NNNN': + if (name === eraName) { + return eras[i]; + } + break; + + case 'NNNNN': + if (narrow === eraName) { + return eras[i]; + } + break; + } + } else if ([name, abbr, narrow].indexOf(eraName) >= 0) { + return eras[i]; + } + } + } + + function localeErasConvertYear(era, year) { + var dir = era.since <= era.until ? +1 : -1; + if (year === undefined) { + return hooks(era.since).year(); + } else { + return hooks(era.since).year() + (year - era.offset) * dir; + } + } + + function getEraName() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].name; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].name; + } + } + + return ''; + } + + function getEraNarrow() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].narrow; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].narrow; + } + } + + return ''; + } + + function getEraAbbr() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].abbr; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].abbr; + } + } + + return ''; + } + + function getEraYear() { + var i, + l, + dir, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + dir = eras[i].since <= eras[i].until ? +1 : -1; + + // truncate time + val = this.clone().startOf('day').valueOf(); + + if ( + (eras[i].since <= val && val <= eras[i].until) || + (eras[i].until <= val && val <= eras[i].since) + ) { + return ( + (this.year() - hooks(eras[i].since).year()) * dir + + eras[i].offset + ); + } + } + + return this.year(); + } + + function erasNameRegex(isStrict) { + if (!hasOwnProp(this, '_erasNameRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNameRegex : this._erasRegex; + } + + function erasAbbrRegex(isStrict) { + if (!hasOwnProp(this, '_erasAbbrRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasAbbrRegex : this._erasRegex; + } + + function erasNarrowRegex(isStrict) { + if (!hasOwnProp(this, '_erasNarrowRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNarrowRegex : this._erasRegex; + } + + function matchEraAbbr(isStrict, locale) { + return locale.erasAbbrRegex(isStrict); + } + + function matchEraName(isStrict, locale) { + return locale.erasNameRegex(isStrict); + } + + function matchEraNarrow(isStrict, locale) { + return locale.erasNarrowRegex(isStrict); + } + + function matchEraYearOrdinal(isStrict, locale) { + return locale._eraYearOrdinalRegex || matchUnsigned; + } + + function computeErasParse() { + var abbrPieces = [], + namePieces = [], + narrowPieces = [], + mixedPieces = [], + i, + l, + eras = this.eras(); + + for (i = 0, l = eras.length; i < l; ++i) { + namePieces.push(regexEscape(eras[i].name)); + abbrPieces.push(regexEscape(eras[i].abbr)); + narrowPieces.push(regexEscape(eras[i].narrow)); + + mixedPieces.push(regexEscape(eras[i].name)); + mixedPieces.push(regexEscape(eras[i].abbr)); + mixedPieces.push(regexEscape(eras[i].narrow)); + } + + this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i'); + this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i'); + this._erasNarrowRegex = new RegExp( + '^(' + narrowPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken(token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function ( + input, + week, + config, + token + ) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy + ); + } + + function getSetISOWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.isoWeek(), + this.isoWeekday(), + 1, + 4 + ); + } + + function getISOWeeksInYear() { + return weeksInYear(this.year(), 1, 4); + } + + function getISOWeeksInISOWeekYear() { + return weeksInYear(this.isoWeekYear(), 1, 4); + } + + function getWeeksInYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getWeeksInWeekYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter(input) { + return input == null + ? Math.ceil((this.month() + 1) / 3) + : this.month((input - 1) * 3 + (this.month() % 3)); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIORITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict + ? locale._dayOfMonthOrdinalParse || locale._ordinalParse + : locale._dayOfMonthOrdinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear(input) { + var dayOfYear = + Math.round( + (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5 + ) + 1; + return input == null ? dayOfYear : this.add(input - dayOfYear, 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token, getSetMillisecond; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + + getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr() { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName() { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + if (typeof Symbol !== 'undefined' && Symbol.for != null) { + proto[Symbol.for('nodejs.util.inspect.custom')] = function () { + return 'Moment<' + this.format() + '>'; + }; + } + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.eraName = getEraName; + proto.eraNarrow = getEraNarrow; + proto.eraAbbr = getEraAbbr; + proto.eraYear = getEraYear; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.weeksInWeekYear = getWeeksInWeekYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate( + 'dates accessor is deprecated. Use date instead.', + getSetDayOfMonth + ); + proto.months = deprecate( + 'months accessor is deprecated. Use month instead', + getSetMonth + ); + proto.years = deprecate( + 'years accessor is deprecated. Use year instead', + getSetYear + ); + proto.zone = deprecate( + 'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', + getSetZone + ); + proto.isDSTShifted = deprecate( + 'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', + isDaylightSavingTimeShifted + ); + + function createUnix(input) { + return createLocal(input * 1000); + } + + function createInZone() { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat(string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + proto$1.eras = localeEras; + proto$1.erasParse = localeErasParse; + proto$1.erasConvertYear = localeErasConvertYear; + proto$1.erasAbbrRegex = erasAbbrRegex; + proto$1.erasNameRegex = erasNameRegex; + proto$1.erasNarrowRegex = erasNarrowRegex; + + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1(format, index, field, setter) { + var locale = getLocale(), + utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl(format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i, + out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl(localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0, + i, + out = []; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function listMonths(format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function listMonthsShort(format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function listWeekdays(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function listWeekdaysShort(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function listWeekdaysMin(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + getSetGlobalLocale('en', { + eras: [ + { + since: '0001-01-01', + until: +Infinity, + offset: 1, + name: 'Anno Domini', + narrow: 'AD', + abbr: 'AD', + }, + { + since: '0000-12-31', + until: -Infinity, + offset: 1, + name: 'Before Christ', + narrow: 'BC', + abbr: 'BC', + }, + ], + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal: function (number) { + var b = number % 10, + output = + toInt((number % 100) / 10) === 1 + ? 'th' + : b === 1 + ? 'st' + : b === 2 + ? 'nd' + : b === 3 + ? 'rd' + : 'th'; + return number + output; + }, + }); + + // Side effect imports + + hooks.lang = deprecate( + 'moment.lang is deprecated. Use moment.locale instead.', + getSetGlobalLocale + ); + hooks.langData = deprecate( + 'moment.langData is deprecated. Use moment.localeData instead.', + getLocale + ); + + var mathAbs = Math.abs; + + function abs() { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function addSubtract$1(duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function add$1(input, value) { + return addSubtract$1(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1(input, value) { + return addSubtract$1(this, input, value, -1); + } + + function absCeil(number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble() { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, + minutes, + hours, + years, + monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if ( + !( + (milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0) + ) + ) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths(days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return (days * 4800) / 146097; + } + + function monthsToDays(months) { + // the reverse of daysToMonths + return (months * 146097) / 4800; + } + + function as(units) { + if (!this.isValid()) { + return NaN; + } + var days, + months, + milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'quarter' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + switch (units) { + case 'month': + return months; + case 'quarter': + return months / 3; + case 'year': + return months / 12; + } + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week': + return days / 7 + milliseconds / 6048e5; + case 'day': + return days + milliseconds / 864e5; + case 'hour': + return days * 24 + milliseconds / 36e5; + case 'minute': + return days * 1440 + milliseconds / 6e4; + case 'second': + return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': + return Math.floor(days * 864e5) + milliseconds; + default: + throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function valueOf$1() { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs(alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'), + asSeconds = makeAs('s'), + asMinutes = makeAs('m'), + asHours = makeAs('h'), + asDays = makeAs('d'), + asWeeks = makeAs('w'), + asMonths = makeAs('M'), + asQuarters = makeAs('Q'), + asYears = makeAs('y'); + + function clone$1() { + return createDuration(this); + } + + function get$2(units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'), + seconds = makeGetter('seconds'), + minutes = makeGetter('minutes'), + hours = makeGetter('hours'), + days = makeGetter('days'), + months = makeGetter('months'), + years = makeGetter('years'); + + function weeks() { + return absFloor(this.days() / 7); + } + + var round = Math.round, + thresholds = { + ss: 44, // a few seconds to seconds + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month/week + w: null, // weeks to month + M: 11, // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) { + var duration = createDuration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + weeks = round(duration.as('w')), + years = round(duration.as('y')), + a = + (seconds <= thresholds.ss && ['s', seconds]) || + (seconds < thresholds.s && ['ss', seconds]) || + (minutes <= 1 && ['m']) || + (minutes < thresholds.m && ['mm', minutes]) || + (hours <= 1 && ['h']) || + (hours < thresholds.h && ['hh', hours]) || + (days <= 1 && ['d']) || + (days < thresholds.d && ['dd', days]); + + if (thresholds.w != null) { + a = + a || + (weeks <= 1 && ['w']) || + (weeks < thresholds.w && ['ww', weeks]); + } + a = a || + (months <= 1 && ['M']) || + (months < thresholds.M && ['MM', months]) || + (years <= 1 && ['y']) || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding(roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof roundingFunction === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold(threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize(argWithSuffix, argThresholds) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var withSuffix = false, + th = thresholds, + locale, + output; + + if (typeof argWithSuffix === 'object') { + argThresholds = argWithSuffix; + argWithSuffix = false; + } + if (typeof argWithSuffix === 'boolean') { + withSuffix = argWithSuffix; + } + if (typeof argThresholds === 'object') { + th = Object.assign({}, thresholds, argThresholds); + if (argThresholds.s != null && argThresholds.ss == null) { + th.ss = argThresholds.s - 1; + } + } + + locale = this.localeData(); + output = relativeTime$1(this, !withSuffix, th, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return (x > 0) - (x < 0) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000, + days = abs$1(this._days), + months = abs$1(this._months), + minutes, + hours, + years, + s, + total = this.asSeconds(), + totalSign, + ymSign, + daysSign, + hmsSign; + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + + totalSign = total < 0 ? '-' : ''; + ymSign = sign(this._months) !== sign(total) ? '-' : ''; + daysSign = sign(this._days) !== sign(total) ? '-' : ''; + hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return ( + totalSign + + 'P' + + (years ? ymSign + years + 'Y' : '') + + (months ? ymSign + months + 'M' : '') + + (days ? daysSign + days + 'D' : '') + + (hours || minutes || seconds ? 'T' : '') + + (hours ? hmsSign + hours + 'H' : '') + + (minutes ? hmsSign + minutes + 'M' : '') + + (seconds ? hmsSign + s + 'S' : '') + ); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + proto$2.toIsoString = deprecate( + 'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', + toISOString$1 + ); + proto$2.lang = lang; + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + //! moment.js + + hooks.version = '2.29.1'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // + DATE: 'YYYY-MM-DD', // + TIME: 'HH:mm', // + TIME_SECONDS: 'HH:mm:ss', // + TIME_MS: 'HH:mm:ss.SSS', // + WEEK: 'GGGG-[W]WW', // + MONTH: 'YYYY-MM', // + }; + + return hooks; + +}))); diff --git a/js/moment.min.js b/js/moment.min.js new file mode 100644 index 0000000..57cd2d4 --- /dev/null +++ b/js/moment.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function f(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(m(e,t))return;return 1}function r(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function a(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function d(e,t){for(var n=[],s=0;s>>0,s=0;sFe(e)?(r=e+1,a-Fe(e)):(r=e,a);return{year:r,dayOfYear:o}}function Ae(e,t,n){var s,i,r=Ge(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+je(i=e.year()-1,t,n):a>je(e.year(),t,n)?(s=a-je(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function je(e,t,n){var s=Ge(e,t,n),i=Ge(e+1,t,n);return(Fe(e)-s+i)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),A("week",5),A("isoWeek",5),ce("w",te),ce("ww",te,Q),ce("W",te),ce("WW",te,Q),ge(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=Z(e)});function Ie(e,t){return e.slice(t,7).concat(e.slice(0,t))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),A("day",11),A("weekday",11),A("isoWeekday",11),ce("d",te),ce("e",te),ce("E",te),ce("dd",function(e,t){return t.weekdaysMinRegex(e)}),ce("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ce("dddd",function(e,t){return t.weekdaysRegex(e)}),ge(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:y(n).invalidWeekday=e}),ge(["d","e","E"],function(e,t,n,s){t[s]=Z(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),$e="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),qe=de,Be=de,Je=de;function Qe(){function e(e,t){return t.length-e.length}for(var t,n,s,i,r=[],a=[],o=[],u=[],l=0;l<7;l++)t=_([2e3,1]).day(l),n=me(this.weekdaysMin(t,"")),s=me(this.weekdaysShort(t,"")),i=me(this.weekdays(t,"")),r.push(n),a.push(s),o.push(i),u.push(n),u.push(s),u.push(i);r.sort(e),a.sort(e),o.sort(e),u.sort(e),this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Xe),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),L("hour","h"),A("hour",13),ce("a",et),ce("A",et),ce("H",te),ce("h",te),ce("k",te),ce("HH",te,Q),ce("hh",te,Q),ce("kk",te,Q),ce("hmm",ne),ce("hmmss",se),ce("Hmm",ne),ce("Hmmss",se),ye(["H","HH"],Me),ye(["k","kk"],function(e,t,n){var s=Z(e);t[Me]=24===s?0:s}),ye(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ye(["h","hh"],function(e,t,n){t[Me]=Z(e),y(n).bigHour=!0}),ye("hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s)),y(n).bigHour=!0}),ye("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i)),y(n).bigHour=!0}),ye("Hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s))}),ye("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i))});var tt=z("Hours",!0);var nt,st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Te,monthsShort:Ne,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return nt}function ut(t){var e;if(void 0===it[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=nt._abbr,require("./locale/"+t),lt(e)}catch(e){it[t]=null}return it[t]}function lt(e,t){var n;return e&&((n=r(t)?dt(e):ht(e,t))?nt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),nt._abbr}function ht(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])Y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ut(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new x(b(s,t)),rt[e]&&rt[e].forEach(function(e){ht(e.name,e.config)}),lt(e),it[e]}function dt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return nt;if(!o(e)){if(t=ut(e))return t;e=[e]}return ot(e)}function ct(e){var t,n=e._a;return n&&-2===y(e).overflow&&(t=n[ve]<0||11xe(n[pe],n[ve])?ke:n[Me]<0||24je(n,r,a)?y(e)._overflowWeeks=!0:null!=u?y(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[pe]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=St(e._a[pe],s[pe]),(e._dayOfYear>Fe(r)||0===e._dayOfYear)&&(y(e)._overflowDayOfYear=!0),n=Ve(r,0,e._dayOfYear),e._a[ve]=n.getUTCMonth(),e._a[ke]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=u[t]=s[t];for(;t<7;t++)e._a[t]=u[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[Me]&&0===e._a[De]&&0===e._a[Se]&&0===e._a[Ye]&&(e._nextDay=!0,e._a[Me]=0),e._d=(e._useUTC?Ve:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,u),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Me]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(y(e).weekdayMismatch=!0)}}function Ot(e){if(e._f!==f.ISO_8601)if(e._f!==f.RFC_2822){e._a=[],y(e).empty=!0;for(var t,n,s,i,r,a,o,u=""+e._i,l=u.length,h=0,d=H(e._f,e._locale).match(N)||[],c=0;cn.valueOf():n.valueOf()"}),pn.toJSON=function(){return this.isValid()?this.toISOString():null},pn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},pn.unix=function(){return Math.floor(this.valueOf()/1e3)},pn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},pn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},pn.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},pn.isLocal=function(){return!!this.isValid()&&!this._isUTC},pn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},pn.isUtc=At,pn.isUTC=At,pn.zoneAbbr=function(){return this._isUTC?"UTC":""},pn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},pn.dates=n("dates accessor is deprecated. Use date instead.",fn),pn.months=n("months accessor is deprecated. Use month instead",Ue),pn.years=n("years accessor is deprecated. Use year instead",Le),pn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),pn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!r(this._isDSTShifted))return this._isDSTShifted;var e,t={};return v(t,this),(t=bt(t))._a?(e=(t._isUTC?_:Tt)(t._a),this._isDSTShifted=this.isValid()&&0 11 ) {mer = radio.units.pm;} + if ( h == 0 ) {h = '12'; mer = radio.units.am;} if ( h > 12 ) {h = h - 12;} } else { mer = ''; @@ -36,18 +43,24 @@ function radio_time_string(datetime, format, seconds) { } /* Convert Date Time to Date String */ -function radio_date_string(datetime, day, date, month) { +function radio_date_string(datetime, day, date, month, override) { + if (override) { + isostring = datetime.toISOString(); + m = parseInt(isostring.substr(5,2)) - 1; + dd = parseInt(isostring.substr(8,2)); + d = datetime.getDay(); /* TODO */ + } else { + d = datetime.getDay(); + m = datetime.getMonth(); + dd = datetime.getDate(); + } datestring = ''; if (day != '') { - d = datetime.getDay(); if (day == 'short') {datestring = radio.labels.sdays[d];} else {datestring += radio.labels.days[d];} } - if (date) { - datestring += ' '+datetime.getDate(); - } + if (date) {datestring += ' '+dd;} if (month != '') { - m = datetime.getMonth(); if (month == 'short') {datestring += ' '+radio.labels.smonths[m];} else {datestring += ' '+radio.labels.months[m];} } @@ -59,35 +72,53 @@ function radio_clock_date_time(init) { /* user datetime / timezone */ userdatetime = new Date(); + useroffset = -(userdatetime.getTimezoneOffset()); if (typeof jstz == 'function') {userzone = jstz.determine().name();} else {userzone = Intl.DateTimeFormat().resolvedOptions().timeZone;} - userzone = userzone.replace('/',', '); - userzone = userzone.replace('_',' '); + + /* server datetime / offset */ + serverdatetime = new Date(); + serveroffset = ( -(useroffset) * 60) + radio.timezone.offset; + serverdatetime.setTime(userdatetime.getTime() + (serveroffset * 1000)); + + /* get timezone override */ + if (typeof radio_timezone_override == 'function') { + override = radio_timezone_override(); + if (radio.debug) {console.log('User Timezone Selection Override: '+override);} + if (override) { + userzone = override; + offset = radio_offset_override(false); + userdatetime.setTime(userdatetime.getTime() + (offset * 60 * 1000)); + useroffset = offset; + } + } /* user timezone offset */ - useroffset = -(userdatetime.getTimezoneOffset()); + userzone = userzone.replace('/',', '); + userzone = userzone.replace('_',' '); houroffset = parseInt(useroffset); if (houroffset == 0) {userzone += ' [UTC]';} else { + if ((typeof radio.timezone.zonename_override != 'undefined') && radio.timezone.zonename_override) { + userzone += ' ['+radio.timezone.zonename_override+']'; + } houroffset = houroffset / 60; if (houroffset > 0) {userzone += ' [UTC+'+houroffset+']';} else {userzone += ' [UTC'+houroffset+']';} } - /* server datetime / offset */ - serverdatetime = new Date(); - serveroffset = ( -(useroffset) * 60) + radio.timezone.offset; - serverdatetime.setTime(userdatetime.getTime() + (serveroffset * 1000) ); - /* server timezone */ serverzone = ''; - if (typeof radio.timezone.location != 'undefined') { + if (radio.timezone.utczone) { + serverzone += ' [UTC'+radio.timezone.utc+']'; + } else { serverzone = radio.timezone.location; serverzone = serverzone.replace('/',', '); serverzone = serverzone.replace('_',' '); - } - if (typeof radio.timezone.code != 'undefined') { - serverzone += ' ['+radio.timezone.code+']'; + if (typeof radio.timezone.code != 'undefined') { + serverzone += ' ['+radio.timezone.code+']'; + } + serverzone += ' ['+radio.timezone.utc+']'; } /* loop clock instances */ @@ -96,7 +127,7 @@ function radio_clock_date_time(init) { if (clock[i]) { classes = clock[i].className; seconds = false; day = ''; date = false; month = ''; zone = false; - if (classes.indexOf('format-24') > -1) {format = 24;} else {format = 12;} + if (classes.indexOf('format-24') > -1) {hours = 24;} else {hours = 12;} if (classes.indexOf('seconds') > -1) {seconds = true;} if (classes.indexOf('day') > -1) { if (classes.indexOf('day-short') > -1) {day = 'short';} else {day = 'full';} @@ -106,10 +137,10 @@ function radio_clock_date_time(init) { if (classes.indexOf('month-short') > -1) {month = 'short';} else {month = 'full';} } if (classes.indexOf('zone') > -1) {zone = true;} - servertime = radio_time_string(serverdatetime, format, seconds); - serverdate = radio_date_string(serverdatetime, day, date, month); - usertime = radio_time_string(userdatetime, format, seconds); - userdate = radio_date_string(userdatetime, day, date, month); + servertime = radio_time_string(serverdatetime, hours, seconds, false); + serverdate = radio_date_string(serverdatetime, day, date, month, false); + usertime = radio_time_string(userdatetime, hours, seconds, override); + userdate = radio_date_string(userdatetime, day, date, month, override); /* loop server / user clocks */ clocks = clock[i].children; @@ -142,7 +173,7 @@ function radio_clock_date_time(init) { } /* clock loop */ - setTimeout('radio_clock_date_time();', 1000); + setTimeout(radio_clock_date_time, 1000); return true; } diff --git a/js/radio-station-countdown.js b/js/radio-station-countdown.js index 97ae84c..47578c8 100644 --- a/js/radio-station-countdown.js +++ b/js/radio-station-countdown.js @@ -54,7 +54,7 @@ function radio_countdown() { jQuery('.current-playlist-end').each(function() { diff = parseInt(jQuery(this).val()) - radio.time.current; if (radio.debug) {console.log('Current Playlist Ends in: '+diff);} - if (diff < 1) {countdown = playlistended; jQuery(this).removeClass('current-playlist-end');} + if (diff < 1) {countdown = radio.labels.playlistended; jQuery(this).removeClass('current-playlist-end');} else {countdown = radio_countdown_display(diff, radio.labels.timeremaining);} jQuery(this).parent().find('.rs-countdown').html(countdown); }); diff --git a/js/radio-station.js b/js/radio-station.js index 72a59ce..03a2397 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -15,14 +15,14 @@ function radio_scroll_to(id) { } /* Get Day of Week */ -function radio_get_weekday(dayweek) { - if (dayweek == '0') {day = 'sunday';} - if (dayweek == '1') {day = 'monday';} - if (dayweek == '2') {day = 'tuesday';} - if (dayweek == '3') {day = 'wednesday';} - if (dayweek == '4') {day = 'thursday';} - if (dayweek == '5') {day = 'friday';} - if (dayweek == '6') {day = 'saturday';} +function radio_get_weekday(x) { + if (x == '0') {day = 'sunday';} + else if (x == '1') {day = 'monday';} + else if (x == '2') {day = 'tuesday';} + else if (x == '3') {day = 'wednesday';} + else if (x == '4') {day = 'thursday';} + else if (x == '5') {day = 'friday';} + else if (x == '6') {day = 'saturday';} return day; } @@ -47,11 +47,11 @@ radio_cookie = { return JSON.parse(c.substring(nameeq.length, c.length)); } } - } + } return null; }, delete : function(name) { - setCookie('radio_' + name, "", -1); + document.cookie = 'radio_' + name + '=; expires=-1; path=/'; } } @@ -66,26 +66,120 @@ var radio_resize_debounce = (function () { })(); /* User Timezone Display */ -if (typeof jQuery == 'function') { - jQuery(document).ready(function() { - if (jQuery('.radio-user-timezone').length) { - userdatetime = new Date(); - useroffset = -(userdatetime.getTimezoneOffset()); - if ((useroffset * 60) == radio.timezone.offset) {return;} - if (typeof jstz == 'function') {tz = jstz.determine().name();} - else {tz = Intl.DateTimeFormat().resolvedOptions().timeZone;} - if (tz.indexOf('/') > -1) { - tz = tz.replace('/', ', '); tz = tz.replace('_',' '); - houroffset = parseInt(useroffset); - if (houroffset == 0) {userzone = ' [UTC]';} - else { - houroffset = houroffset / 60; - if (houroffset > 0) {tz += ' [UTC+'+houroffset+']';} - else {tz += ' [UTC'+houroffset+']';} - } - jQuery('.radio-user-timezone').html(tz); - jQuery('.radio-user-timezone-title').show(); +function radio_timezone_display() { + if (typeof radio_display_override == 'function') { + override = radio_display_override(); + if (override) {return;} + } + if (jQuery('.radio-user-timezone').length) { + userdatetime = new Date(); + useroffset = -(userdatetime.getTimezoneOffset()); + if ((useroffset * 60) == radio.timezone.offset) {return;} + if (typeof jstz == 'function') {tz = jstz.determine().name();} + else {tz = Intl.DateTimeFormat().resolvedOptions().timeZone;} + if (tz.indexOf('/') > -1) { + tz = tz.replace('/', ', '); tz = tz.replace('_',' '); + houroffset = parseInt(useroffset); + if (houroffset == 0) {userzone = ' [UTC]';} + else { + houroffset = houroffset / 60; + if (houroffset > 0) {tz += ' [UTC+'+houroffset+']';} + else {tz += ' [UTC'+houroffset+']';} } + jQuery('.radio-user-timezone').html(tz).css('display','inline-block'); + jQuery('.radio-user-timezone-title').css('display','inline-block'); + } + } +} + +/* Retrigger Responsive Schedules */ +function radio_responsive_schedules() { + if (jQuery('#master-program-schedule').length) {radio_table_responsive(false);} + if (jQuery('#master-schedule-tabs').length) {radio_tabs_responsive(false);} + if (jQuery('#master-schedule-grid').length) {radio_grid_responsive(false);} + if (jQuery('#master-schedule-calendar').length) {radio_calendar_responsive(false);} +} + +/* Update Time Displays */ +function radio_convert_times() { + if (!radio.convert_show_times) {return;} + /* schedule: .show-time; widgets: .current-shift, .upcoming-show-schedule; show page: .show-shift-time, .override-time */ + jQuery('.show-time, .current-shift, .upcoming-show-shift, .override-time, .show-shift-time').each(function() { + start = jQuery(this).find('.rs-start-time'); + end = jQuery(this).find('.rs-end-time'); + starthtml = start.html(); starttime = start.attr('data'); startformat = start.attr('data-format'); + endhtml = end.html(); endtime = end.attr('data'); endformat = end.attr('data-format'); + startdisplay = radio_user_time(starttime, startformat); + enddisplay = radio_user_time(endtime, endformat); + if ((starthtml != startdisplay) || (endhtml != enddisplay)) { + if (radio.debug) {console.log('Start Display: '+startdisplay+' - End Display: '+enddisplay);} + showusertime = jQuery(this).parent().find('.show-user-time').show(); + showusertime.find('.rs-start-time').html(startdisplay); + showusertime.find('.rs-end-time').html(enddisplay); + } else {jQuery(this).parent().find('.show-user-time').hide();} + if (jQuery(this).find('.rs-start-date').length) { + date = jQuery(this).find('.rs-start-date'); + datehtml = date.html(); datetime = date.attr('data'); dateformat = date.attr('data-format'); + datedisplay = radio_user_time(datetime, dateformat); + if (datehtml == datedisplay) {datedisplay = '';} + showusertime = jQuery(this).parent().find('.show-user-time'); + showusertime.find('.rs-start-date').html(datedisplay); } }); + radio_responsive_schedules(); +} + +/* Convert To Time Display */ +function radio_user_time(time, format) { + if (typeof radio_user_override == 'function') { + override = radio_user_override(time, format); + if (override) {return override;} + } + datetime = new Date(time * 1000); + zonetime = moment(datetime.toISOString()); + formatted = radio_convert_time(zonetime, format); + return formatted; +} + +/* Convert Time to Formatted Time */ +function radio_convert_time(zonetime, format) { + formatted = ''; + for (var i = 0; i < format.length; i++) { + k = format.charAt(i); + v = radio_format_key(zonetime, k); + formatted += v; + } + return formatted; +} + +/* Format Time Key */ +function radio_format_key(zonetime, key) { + v = key; + for (i in radio.moment_map) { + if (i == key) { + k = radio.moment_map[i]; + v = zonetime.format(k); + if (((i == 'd') || (i == 'm') || (i == 'h') || (i == 'H') || (i == 'i') || (i == 's')) && (v < 10)) {v = '0'+v;} + else if (i == 'D') {v = radio.labels.sdays[v];} + else if (i == 'l') {v = radio.labels.days[v];} + else if (i == 'N') {v++;} + else if (i == 'S') {d = zonetime.format('D'); v.replace(d, '');} + else if (i == 'F') {v = radio.labels.months[v];} + else if (i == 'M') {v = radio.labels.smonths[v];} + else if ((i == 'a') || (i == 'A')) { + if (v.substr(0,1) == 'a') {v = radio.units.am;} + if (v.substr(0,1) == 'p') {v = radio.units.pm;} + if (i == 'A') {v = v.toUpperCase();} + } + } + } + return v; +} + +/* Pageload Function */ +if (typeof jQuery == 'function') { + jQuery(document).ready(function() { + radio_timezone_display(); + radio_convert_times(); + }); } diff --git a/loader.php b/loader.php index d2cbbed..4c19a0a 100644 --- a/loader.php +++ b/loader.php @@ -1390,8 +1390,8 @@ public function load_freemius() { 'support' => $args['support'], 'account' => $args['account'], ), - ); - + ); + // --- maybe add plugin submenu to parent menu --- if ( isset( $args['parentmenu'] ) ) { $settings['menu']['parent'] = array( 'slug' => $args['parentmenu'] ); @@ -1414,6 +1414,9 @@ public function load_freemius() { // --- add Freemius connect message filter --- $this->freemius_connect(); + + // --- fire Freemius loaded action --- + do_action( $args['namespace'] . '_loaded' ); } } diff --git a/radio-station-admin.php b/radio-station-admin.php index 9068948..45bd3ab 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -60,28 +60,6 @@ function radio_station_enqueue_admin_scripts() { } -// ------------------ -// Enqueue Datepicker -// ------------------ -// 2.3.0: enqueued separately by override post type only -function radio_station_enqueue_datepicker() { - - // --- enqueue jquery datepicker --- - wp_enqueue_script( 'jquery-ui-datepicker' ); - - // --- enqueue jquery datepicker styles --- - // 2.3.0: update theme styles from 1.8.2 to 1.12.1 - // 2.3.0: use local datepicker styles instead of via Google - // $protocol = 'http'; - // if ( is_ssl() ) {$protocol .= 's';} - // $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'; - // wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.12.1' ); - $style = radio_station_get_template( 'both', 'jquery-ui.css', 'css' ); - wp_enqueue_style( 'jquery-ui-smoothness', $style['url'], array(), '1.12.1', 'all' ); - -} - - // ----------------- // Admin Style Fixes // ----------------- @@ -212,12 +190,12 @@ function radio_station_add_admin_menus() { case 'add-override': $submenu[$i][$j][2] = 'post-new.php?post_type=' . RADIO_STATION_OVERRIDE_SLUG; break; - // case 'hosts': - // $submenu[$i][$j][2] = 'post-new.php?post_type=' . RADIO_STATION_HOST_SLUG; - // break; - // case 'producers': - // $submenu[$i][$j][2] = 'post-new.php?post_type=' . RADIO_STATION_PRODUCER_SLUG; - // break; + case 'hosts': + $submenu[$i][$j][2] = 'edit.php?post_type=' . RADIO_STATION_HOST_SLUG; + break; + case 'producers': + $submenu[$i][$j][2] = 'edit.php?post_type=' . RADIO_STATION_PRODUCER_SLUG; + break; } } } @@ -1092,13 +1070,14 @@ function radio_station_listing_offer_content( $dismissable = true ) { $blurb .= '

    '; $blurb .= esc_html( __( 'We are excited to announce the opening of the new', 'radio-station' ) ); $blurb .= ' Netmix Station Directory!!!
    '; - $blurb .= esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...' ) ) . '
    '; + $blurb .= esc_html( __( 'Allowing listeners to newly discover Stations and Shows - which can include yours...', 'radio-station' ) ) . '
    '; - $blurb .= esc_html( __( 'Because while launching,') ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!' ) ) . '
    '; - $blurb .= esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?' ) ); + $blurb .= esc_html( __( 'Because while launching,') ) . ' ' . esc_html( __( 'we are offering 30 days free listing to all Radio Station users!', 'radio-station' ) ) . '
    '; + $blurb .= esc_html( __( 'Interested in more exposure and listeners for your Radio Station, for free?', 'radio-station' ) ); $blurb .= '

    '; // --- accept / decline offer button links --- + // 2.3.3.9: added missing string text domains $blurb .= '
  • '; $blurb .= '
    '; $onclick = ''; @@ -1106,14 +1085,14 @@ function radio_station_listing_offer_content( $dismissable = true ) { $onclick = ' onclick="radio_display_dismiss_link();"'; } $blurb .= ''; $blurb .= '
    '; $blurb .= ''; $blurb .= '

    '; @@ -1220,6 +1199,8 @@ function radio_station_announcement_content( $dismissable = true ) { $blurb .= '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . ' '; $blurb .= esc_html( __( ' plugin development has been actively taken over by', 'radio-station' ) ); $blurb .= ' Netmix.
    '; + // 2.3.3.9: add updated text after 2000 user milestone + $blurb .= esc_html( __( 'And due to our continued efforts we now have a community of over two thousand active stations!', 'radio-station' ) ) . '
    '; $blurb .= esc_html( __( 'We invite you to', 'radio-station' ) ); $blurb .= ' '; $blurb .= esc_html( __( 'Become a Radio Station Patreon Supporter', 'radio-station' ) ); diff --git a/radio-station.php b/radio-station.php index e651218..2d88301 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.3.3.8 +Version: 2.3.3.9 Text Domain: radio-station Domain Path: /languages Author URI: https://netmix.com/radio-station @@ -45,7 +45,9 @@ // - Flush Rewrite Rules // - Enqueue Plugin Script // - Enqueue Plugin Stylesheet -// - Localize Time Strings +// - Enqueue Datepicker +// - Enqueue Localized Script Values +// - Localization Script // === Template Filters === // - Get Template // - Station Phone Number Filter @@ -65,6 +67,7 @@ // - Show Posts Adjacent Links // === Query Filters === // - Playlist Archive Query Filter +// - Schedule Override Filters // === User Roles === // - Set Roles and Capabilities // - maybe Revoke Edit Show Capability @@ -728,7 +731,7 @@ 'label' => __( 'View Switching', 'radio-station' ), 'default' => '', 'value' => 'yes', - 'helper' => __( 'Enable View Switching on the Master Schedule.', 'radio-station' ), + 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), 'tab' => 'pages', 'section' => 'schedule', 'pro' => true, @@ -746,10 +749,10 @@ 'table' => __( 'Table View', 'radio-station' ), 'tabs' => __( 'Tabbed View', 'radio-station' ), 'list' => __( 'List View', 'radio-station' ), - // 'grid' => __( 'Grid View', 'radio-station' ), + 'grid' => __( 'Grid View', 'radio-station' ), // 'calendar' => __( 'Calendar View', 'radio-station' ); ), - 'helper' => __( 'Switcher Views available on Master Schedule.', 'radio-station' ), + 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), 'tab' => 'pages', 'section' => 'schedule', 'pro' => true, @@ -838,18 +841,18 @@ ), // --- [Pro] Show Episodes per Page --- - // 'show_episodes_per_page' => array( - // 'type' => 'number', - // 'label' => __( 'Episodes per Page', 'radio-station' ), - // 'step' => 1, - // 'min' => 1, - // 'max' => 1000, - // 'default' => 10, - // 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'show', - // 'pro' => true, - // ), + 'show_episodes_per_page' => array( + 'type' => 'number', + 'label' => __( 'Episodes per Page', 'radio-station' ), + 'step' => 1, + 'min' => 1, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, + ), // ==== Archives === @@ -1067,7 +1070,7 @@ 'section' => 'loading', ), - // --- Dynamic Reloading --- + // --- [Pro] Dynamic Reloading --- 'dynamic_reload' => array( 'type' => 'checkbox', 'label' => __( 'Dynamic Reloading?', 'radio-station' ), @@ -1079,6 +1082,29 @@ 'pro' => true, ), + // --- Translate User Times --- + 'convert_show_times' => array( + 'type' => 'checkbox', + 'label' => __( 'Convert Show Times', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // --- [Pro] Timezone Switching --- + 'timezone_switching' => array( + 'type' => 'checkbox', + 'label' => __( 'User Timezone Switching', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow visitors to select their Timezone manually for Show time translations.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), // === Roles / Capabilities / Permissions === // 2.3.0: added new capability and role options @@ -1303,7 +1329,7 @@ function radio_station_check_version() { // ----------------- // (run on plugin activation, and thus also after a plugin update) // 2.2.8: fix for mismatched flag function name -register_activation_hook( __FILE__, 'radio_station_plugin_activation' ); +register_activation_hook( RADIO_STATION_FILE, 'radio_station_plugin_activation' ); function radio_station_plugin_activation() { // --- flag to flush rewrite rules --- // 2.2.3: added this for custom post types rewrite flushing @@ -1311,14 +1337,14 @@ function radio_station_plugin_activation() { // --- clear schedule transients --- // 2.3.3: added clear transients on (re)activation - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); + // 2.3.3.9: just use clear cached data function + radio_station_clear_cached_data( false ); } // ------------------- // Flush Rewrite Rules // ------------------- -register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); +register_deactivation_hook( RADIO_STATION_FILE, 'flush_rewrite_rules' ); // ---------------------- // Enqueue Plugin Scripts @@ -1335,13 +1361,21 @@ function radio_station_enqueue_scripts() { // 2.3.0: added jquery dependency for inline script fragments radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); - // -- enqueue javascript timezone script --- - // $suffix = '.min'; - // if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - // $suffix = ''; - // } - // $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); - // wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); + // --- set script suffix --- + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + $suffix = ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) ? '' : $suffix; + + // -- enqueue javascript timezone detection script --- + // 2.3.3.9: activated for improved timezone detection + $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); + + // --- Moment.js --- + // ref: https://momentjs.com + // 2.3.3.9: added for improved time format display + $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'momentjs', $moment_url, array(), '2.29.1', false ); + } // --------------------- @@ -1358,7 +1392,10 @@ function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = // 2.3.2: use plugin version for releases $plugin_version = radio_station_plugin_version(); - if ( 5 == strlen( $plugin_version ) ) { + $version_length = strlen( $plugin_version ); + // TODO: maybe allow for minor version release numbers + // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { + if ( 5 == $version_length ) { $version = $plugin_version; } else { $version = filemtime( $template['file'] ); @@ -1396,7 +1433,10 @@ function radio_station_enqueue_style( $stylekey ) { // --- use found template values --- // 2.3.2: use plugin version for releases $plugin_version = radio_station_plugin_version(); - if ( 5 == strlen( $plugin_version ) ) { + $version_length = strlen( $plugin_version ); + // TODO: maybe allow for minor version release numbers ? + // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { + if ( 5 == $version_length ) { $version = $plugin_version; } else { $version = filemtime( $template['file'] ); @@ -1412,12 +1452,44 @@ function radio_station_enqueue_style( $stylekey ) { } } -// --------------------- -// Localize Time Strings -// --------------------- +// ------------------ +// Enqueue Datepicker +// ------------------ +// 2.3.0: enqueued separately by override post type only +// 2.3.3.9: moved here from radio-station-admin.php +function radio_station_enqueue_datepicker() { + + // --- enqueue jquery datepicker --- + wp_enqueue_script( 'jquery-ui-datepicker' ); + + // --- enqueue jquery datepicker styles --- + // 2.3.0: update theme styles from 1.8.2 to 1.12.1 + // 2.3.0: use local datepicker styles instead of via Google + // $protocol = 'http'; + // if ( is_ssl() ) {$protocol .= 's';} + // $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'; + // wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.12.1' ); + $style = radio_station_get_template( 'both', 'jquery-ui.css', 'css' ); + wp_enqueue_style( 'jquery-ui-smoothness', $style['url'], array(), '1.12.1', 'all' ); + +} + +// ------------------------------- +// Enqueue Localized Script Values +// ------------------------------- add_action( 'wp_enqueue_scripts', 'radio_station_localize_script' ); function radio_station_localize_script() { + $js = radio_station_localization_script(); + wp_add_inline_script( 'radio-station', $js ); +} + +// ------------------- +// Localization Script +// ------------------- +// 2.3.3.9: separated script from enqueueing +function radio_station_localization_script() { + // --- create settings objects --- $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; @@ -1426,6 +1498,8 @@ function radio_station_localize_script() { $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; // --- clock time format --- + // TODO: maybe set time format ? + // ref: https://devhints.io/wip/intl-datetime $clock_format = radio_station_get_setting( 'clock_time_format' ); $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; @@ -1444,7 +1518,6 @@ function radio_station_localize_script() { // 2.3.2: added get timezone function $timezone = radio_station_get_timezone(); - // if ( isset( $offset ) ) { if ( stristr( $timezone, 'UTC' ) ) { if ( 'UTC' == $timezone ) { @@ -1536,7 +1609,6 @@ function radio_station_localize_script() { $js .= $short . ");" . PHP_EOL; // --- translated time unit strings --- - // TODO: convert units to single object $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; $js .= "radio.units.pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; "; $js .= "radio.units.second = '" . esc_js( __( 'Second', 'radio-station' ) ) . "'; "; @@ -1546,10 +1618,27 @@ function radio_station_localize_script() { $js .= "radio.units.hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "'; "; $js .= "radio.units.hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "'; "; $js .= "radio.units.day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; "; - $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; "; + $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; " . PHP_EOL; + + // --- time key map --- + // 2.3.3.9: added for PHP Date Format to MomentJS conversions + // (object of approximate 'PHP date() key':'moment format() key' conversions) + $js .= "radio.moment_map = {'d':'D', 'j':'D', 'w':'e', 'D':'e', 'l':'e', 'N':'e', 'S':'Do', "; + $js .= "'F':'M', 'm':'M', 'n':'M', 'M':'M', 'Y':'YYYY', 'y':'YY',"; + $js .= "'a':'a', 'A':'a', 'g':'h', 'G':'H', 'g':'h', 'H':'H', 'i':'m', 's':'s'}" . PHP_EOL; + + // --- convert show times --- + // 2.3.3.9: + $usertimes = radio_station_get_setting( 'convert_show_times' ); + if ( 'yes' == $usertimes ) { + $js .= "radio.convert_show_times = true;" . PHP_EOL; + } else { + $js .= "radio.convert_show_times = false;" . PHP_EOL; + } // --- add inline script --- - wp_add_inline_script( 'radio-station', $js ); + $js = apply_filters( 'radio_station_localization_script', $js ); + return $js; } @@ -1597,11 +1686,43 @@ function radio_station_settings_page_redirect() { } } +// ------------------------------------ +// Set Allowed Origins for Radio Player +// ------------------------------------ +// 2.3.3.9: added for embedded radio player control +add_filter( 'allowed_http_origins', 'radio_station_allowed_player_origins' ); +function radio_station_allowed_player_origins( $origins ) { + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + return $origins; + } + if ( !isset( $_REQUEST['action'] ) || ( 'radio_player' != $_REQUEST['action'] ) ) { + return $origins; + } + $allowed = array( 'https://netmix.com' ); + $allowed = apply_filters( 'radio_station_player_allowed_origins', $allowed ); + foreach ( $allowed as $allow ) { + $origins[] = $allow; + } + return $origins; +} + // ------------------------ // === Template Filters === // ------------------------ +// -------------------- +// Doing Template Check +// -------------------- +// 2.3.3.9: added to help distinguish filter contexts +function radio_station_doing_template() { + global $radio_station_data; + if ( isset( $radio_station_data['doing-template'] ) && $radio_station_data['doing-template'] ) { + return true; + } + return false; +} + // ------------ // Get Template // ------------ @@ -1846,6 +1967,8 @@ function radio_station_automatic_pages_content_set( $content ) { } } } + + // TODO: languages archive page ? // 2.3.3.6: moved out to reduce repetitive code if ( isset( $shortcode ) ) { @@ -1918,12 +2041,20 @@ function radio_station_single_content_template( $content, $post_type ) { // --- enqueue dashicons for frontend --- wp_enqueue_style( 'dashicons' ); + // --- filter post before including template --- + global $post; + $original_post = $post; + $post = apply_filters( 'radio_station_single_template_post_data', $post, $post_type ); + // --- start buffer and include content template --- ob_start(); include $content_template; $output = ob_get_contents(); ob_end_clean(); + // --- restore post global to be safe --- + $post = $original_post; + // --- filter and return buffered content --- $output = str_replace( '', $content, $output ); $post_id = get_the_ID(); @@ -1932,6 +2063,38 @@ function radio_station_single_content_template( $content, $post_type ) { return $output; } +// ------------------------------------ +// Filter for Override Show Linked Data +// ------------------------------------ +add_filter( 'radio_station_single_template_post_data', 'radio_station_override_linked_show_data', 10, 2 ); +function radio_station_override_linked_show_data( $post, $post_type ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); + if ( $linked_id ) { + $show_post = get_post( $linked_id ); + if ( $show_post ) { + $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); + if ( $linked_fields ) { + foreach ( $linked_fields as $key => $switch ) { + if ( !$switch ) { + // echo $key . ' - '; + if ( 'show_title' == $key ) { + $post->post_title = $show_post->post_title; + } elseif ( 'show_excerpt' == $key ) { + $post->post_excerpt = $show_post->post_excerpt; + } elseif ( 'show_content' == $key ) { + $post->post_content = $show_post->post_content; + // print_r( $post->post_content ); + } + } + } + } + } + } + } + return $post; +} + // ---------------------------- // Show Content Template Filter // ---------------------------- @@ -1966,11 +2129,32 @@ function radio_station_playlist_content_template( $content ) { function radio_station_override_content_template( $content ) { remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multuple times + // 2.3.1: re-add filter so the_content can be processed multiple times add_filter( 'the_content', 'radio_station_override_content_template', 11 ); return $output; } +// ---------------------------------- +// Override Content with Show Content +// ---------------------------------- +// 2.3.3.9: maybe use show content for override content +add_filter( 'the_content', 'radio_station_override_content', 0 ); +function radio_station_override_content( $content ) { + if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { + return $content; + } + remove_filter( 'the_content', 'radio_station_override_content', 0 ); + global $post; + $override = radio_station_get_show_override( $post->ID, 'show_content' ); + if ( false !== $override ) { + $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); + // print_r( $override ); + $content = $override->post_content; + } + add_filter( 'the_content', 'radio_station_override_content', 0 ); + return $content; +} + // --------------------------------- // DJ / Host / Producer Template Fix // --------------------------------- @@ -2339,9 +2523,21 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po if ( in_array( $post->post_type, $post_types ) ) { if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { // 2.3.3.6: get next/previous Show for override date/time - $sched = get_post_meta( $post->ID, 'show_override_sched', true ); - $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; - $time = radio_station_get_time( ( $override_start + 1 ) ); + // 2.3.3.9: modified to handle multiple override times + $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); + if ( array_key_exists( 'date', $scheds ) ) { + $sched = array( $scheds ); + } + $now = time(); + foreach ( $scheds as $sched ) { + $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; + $override_time = radio_station_get_time( ( $override_start + 1 ) ); + if ( !isset( $time ) ) { + $time = $override_time; + } elseif ( ( $time < $now ) && ( $override_time > $now ) ) { + $time = $override_time; + } + } if ( 'next' == $adjacent ) { $show = radio_station_get_next_show( $time ); } elseif ( 'previous' == $adjacent ) { @@ -2357,7 +2553,8 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po if ( 1 == count( $shifts ) ) { $shift = $shifts[0]; $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - $time = radio_station_get_time( ( $shift_start + 1 ) ); + // 2.3.3.9: fix to put addition outside bracket + $time = radio_station_get_time( $shift_start ) + 1; if ( 'next' == $adjacent ) { $show = radio_station_get_next_show( $time ); } elseif ( 'previous' == $adjacent ) { @@ -2483,13 +2680,14 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- get more Shows related to this related Post --- // 2.3.3.6: allow for multiple related posts + // 2.3.3.9: added 'i:' prefix to LIKE vlaue matches global $wpdb; $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%" . $related_shows[0] . "%'"; + . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; if ( count( $related_shows ) > 1 ) { foreach ( $related_show as $i => $show_id ) { if ( $i > 0 ) { - $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%" . $show_id . "%'"; + $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $show_id . "%'"; } } } @@ -2647,9 +2845,9 @@ function radio_station_show_playlist_query( $query ) { // === User Roles === // ------------------ -// ---------------------------- -// Set DJ Role and Capabilities -// ---------------------------- +// -------------------------- +// Set Roles and Capabilities +// -------------------------- if ( is_multisite() ) { add_action( 'init', 'radio_station_set_roles', 10, 0 ); // 2.3.1: added possible fix for roles not being set on multisite @@ -2971,7 +3169,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { } } - // --- maybe revoked edit show capability for post --- + // --- maybe revoke edit show capability for post --- // 2.3.3.6: fix to incorrect logic for removing edit show capability if ( $found ) { @@ -3014,6 +3212,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { return $allcaps; } + // ================= // --- Debugging --- // ================= @@ -3052,10 +3251,8 @@ function radio_station_clear_transients() { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return; } - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - // 2.3.3: remove current show transient - // delete_transient( 'radio_station_current_show' ); + // 2.3.3.9: just use clear cached data function + radio_station_clear_cached_data( false ); } } diff --git a/reader.php b/reader.php index 3d7f91c..0c96bd7 100644 --- a/reader.php +++ b/reader.php @@ -827,7 +827,7 @@ function _doHeaders_callback_setext($matches) { if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) return $matches[0]; - $level = $matches[2]{0} == '=' ? 1 : 2; + $level = $matches[2][0] == '=' ? 1 : 2; $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } diff --git a/readme.md b/readme.md index a3da43f..4a2ea3a 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Requires at least: 3.3.1 Tested up to: 5.6.2 -Stable tag: 2.3.3.8 +Stable tag: 2.3.3.9 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 865bfb0..436daf0 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.6.2 -Stable tag: 2.3.3.8 +Stable tag: 2.3.3.9 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -198,6 +198,19 @@ We haven't built an interface between Google Calendar and Radio Station just yet == Changelog == += 2.3.3.9 = +* Update: Plugin Panel (1.1.8) with Number Step Min/Max fix +* Update: Freemius SDK (2.4.2) +* Improved: Allow for Multiple Override Times (with AJAX Saving) +* Added: Link Override to Show Data with selectable Show Fields +* Added: Language Archive Shortcode (similar to Genre Archive) +* Fixed: Current Show highlighting timer interval cycling +* Fixed: Before and After Show classes when no current Show +* Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch +* Fixed: Multiple host separator display in Current Show Widget +* Fixed: Playlist Widget playlist ended label when no next playlist +* Fixed: Conflicting duplicate filter name for Show Avatar + = 2.3.3.8 = * Update: Plugin Panel (1.1.7) with Image and Color Picker fields * Added: Stream Format and Fallback/Format selection setting @@ -661,6 +674,13 @@ We haven't built an interface between Google Calendar and Radio Station just yet == Upgrade Notice == += 2.3.3.9 = +* Multiple Dates and Times for Schedule Overrides! +* Link Override to Show with Selective Fields +* Language Archive Shortcode for Shows +* Various Bugfixes and Improvements +* Updated Freemius SDK and Plugin Loader + = 2.3.3.8 = * Updated Plugin Panel Library * Added Stream Format selection setting diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 7983b64..68b6753 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -7,6 +7,20 @@ $hours = radio_station_get_hours(); $now = radio_station_get_now(); $date = radio_station_get_time( 'date', $now ); +$today = radio_station_get_time( 'day', $now ); + +// --- check if start date is set --- +// 2.3.3.9: added for non-now schedule displays +if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { + $start_date = $atts['start_date']; + $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); + // --- force display of date and month --- + $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; + $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; +} else { + $start_time = $now; +} +$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'list' ); // --- set shift time formats --- // 2.3.2: set time formats early @@ -23,18 +37,20 @@ // 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $start_day = $atts['start_day']; - $schedule = radio_station_get_current_schedule( $now , $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'list' ); - if ( $start_day ) { - $schedule = radio_station_get_current_schedule( $now , $start_day ); - } else { - $schedule = radio_station_get_current_schedule(); - } +} +if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $start_time , $start_day ); +} elseif ( $start_time != $now ) { + // 2.3.3.9: load current or time-specific schedule + $schedule = radio_station_get_current_schedule( $start_time ); +} else { + $schedule = radio_station_get_current_schedule(); } $weekdays = radio_station_get_schedule_weekdays( $start_day ); -$weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); +$weekdates = radio_station_get_schedule_weekdates( $weekdays, $start_time ); // --- filter show avatar size --- // 2.3.3.5: fix to incorrect context value (tabs) @@ -266,7 +282,15 @@ $title = apply_filters( 'radio_station_schedule_show_title', $title, $show_id, 'list' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; - // $list .= $title; + } + // 2.3.3.9: allow for admin edit link + $edit_link = apply_filters( 'radio_station_show_edit_link', '', $show_id, $shift['id'], 'list' ); + if ( '' != $edit_link ) { + if ( isset( $info['title'] ) ) { + $info['title'] .= $edit_link; + } else { + $info['title'] = $edit_link; + } } // --- show hosts --- @@ -347,7 +371,8 @@ } // 2.3.3.8: moved filter out and added display filter - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'list', $shift ); + // 2.3.3.9: added tcount argument to filter + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'list', $shift, $tcount ); $times = '
    ' . $newline; - $times .= '
    ' . $newline; + // 2.3.3.9: added internal spans for user time + $times .= '
    '; + $times .= '[' . $newline; + $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $times .= ']' . $newline; + $times .= '
    ' . $newline; $info['times'] = $times; - // $list .= $times; $tcount ++; // --- encore --- @@ -480,4 +509,8 @@ // --- close master list --- $list .= '' . $newline; -$output = $list; \ No newline at end of file + +// --- hidden iframe for schedule reloading --- +$list .= '' . $newline; + +echo $list; \ No newline at end of file diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index fa7e829..6293e41 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -9,6 +9,19 @@ $date = radio_station_get_time( 'date', $now ); $today = radio_station_get_time( 'day', $now ); +// --- check if start date is set --- +// 2.3.3.9: added for non-now schedule displays +if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { + $start_date = $atts['start_date']; + $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); + // --- force display of date and month --- + $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; + $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; +} else { + $start_time = $now; +} +$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'table' ); + // --- set shift time formats --- // 2.3.2: set time formats early if ( 24 == (int) $atts['time'] ) { @@ -24,18 +37,20 @@ // 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $start_day = $atts['start_day']; - $schedule = radio_station_get_current_schedule( $now, $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'table' ); - if ( $start_day ) { - $schedule = radio_station_get_current_schedule( $now , $start_day ); - } else { - $schedule = radio_station_get_current_schedule(); - } +} +if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $start_time, $start_day ); +} elseif ( $start_time != $now ) { + // 2.3.3.9: load current or time-specific schedule + $schedule = radio_station_get_current_schedule( $start_time ); +} else { + $schedule = radio_station_get_current_schedule(); } $weekdays = radio_station_get_schedule_weekdays( $start_day ); -$weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); +$weekdates = radio_station_get_schedule_weekdates( $weekdays, $start_time ); // --- filter avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'table' ); @@ -47,7 +62,7 @@ } // --- filter arrows --- -$arrows = array( 'right' => '►', 'left' => '◄' ); +$arrows = array( 'left' => '‹', 'right' => '›', 'doubleleft' => '«', 'doubleright' => '»' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'table' ); // --- set cell info key order --- @@ -461,7 +476,15 @@ $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'table' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; - // $cell .= $title; + } + // 2.3.3.9: allow for admin edit link + $edit_link = apply_filters( 'radio_station_show_edit_link', '', $show_id, $shift['id'], 'table' ); + if ( '' != $edit_link ) { + if ( isset( $info['title'] ) ) { + $info['title'] .= $edit_link; + } else { + $info['title'] = $edit_link; + } } // --- show DJs / hosts --- @@ -539,7 +562,8 @@ } // --- add show time to cell --- - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'table', $shift ); + // 2.3.3.9: added tcount argument to filter + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'table', $shift, $tcount ); $times = '
    ' . $newline; - $times .= '
    ' . $newline; + // 2.3.3.9: added internal spans for user time + $times .= '
    ' . $newline; + $times .= '[' . $newline; + $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $times .= ']' . $newline; + $times .= '
    ' . $newline; $info['times'] = $times; - // $cell .= $times; $tcount ++; // --- encore airing --- @@ -665,6 +693,14 @@ } else { // 2.3.2: add no-shifts class $cellclasses[] = 'no-shifts'; + $times = array( 'date' => $weekdate, 'day' => $weekday, 'hour' => $hour, 'mins' => 0 ); + $add_link = apply_filters( 'radio_station_schedule_add_link', '', $times, 'table' ); + if ( '' != $add_link ) { + if ( !isset( $cell ) ) { + $cell = ''; + } + $cell .= $add_link; + } } $cellclass = implode( ' ', $cellclasses ); $table .= '' . $newline; @@ -683,7 +719,11 @@ $table .= '' . $newline; +// --- hidden iframe for schedule reloading --- +$table .= '' . $newline; + if ( isset( $_GET['rs-shift-debug'] ) && ( '1' == $_GET['rs-shift-debug'] ) ) { $table .= '
    Shift Debug Info:
    ' . $shiftdebug . '
    '; } -$output = $table; \ No newline at end of file + +echo $table; \ No newline at end of file diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 87b7313..0d2914a 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -10,6 +10,19 @@ $date = radio_station_get_time( 'date', $now ); $today = radio_station_get_time( 'day', $now ); +// --- check if start date is set --- +// 2.3.3.9: added for non-now schedule displays +if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { + $start_date = $atts['start_date']; + $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); + // --- force display of date and month --- + $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; + $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; +} else { + $start_time = $now; +} +$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'tabs' ); + // --- set shift time formats --- // 2.3.2: set time formats early if ( 24 == (int) $atts['time'] ) { @@ -25,18 +38,20 @@ // 2.3.3.5: use the start_day value for getting the current schedule if ( isset( $atts['start_day'] ) && $atts['start_day'] ) { $start_day = $atts['start_day']; - $schedule = radio_station_get_current_schedule( $now , $start_day ); } else { // 2.3.3.5: add filter for changing start day (to accept 'today') $start_day = apply_filters( 'radio_station_schedule_start_day', false, 'tabs' ); - if ( $start_day ) { - $schedule = radio_station_get_current_schedule( $now , $start_day ); - } else { - $schedule = radio_station_get_current_schedule(); - } +} +if ( $start_day ) { + $schedule = radio_station_get_current_schedule( $start_time, $start_day ); +} elseif ( $start_time != $now ) { + // 2.3.3.9: load current or time-specific schedule + $schedule = radio_station_get_current_schedule( $start_time ); +} else { + $schedule = radio_station_get_current_schedule(); } $weekdays = radio_station_get_schedule_weekdays( $start_day ); -$weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); +$weekdates = radio_station_get_schedule_weekdates( $weekdays, $start_time ); // --- filter show avatar size --- $avatar_size = apply_filters( 'radio_station_schedule_show_avatar_size', 'thumbnail', 'tabs' ); @@ -48,7 +63,7 @@ } // --- filter arrows --- -$arrows = array( 'right' => '►', 'left' => '◄' ); +$arrows = array( 'left' => '‹', 'right' => '›', 'doubleleft' => '«', 'doubleright' => '»' ); $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'tabs' ); // --- set cell info key order --- @@ -57,7 +72,7 @@ $infokeys = apply_filters( 'radio_station_schedule_tabs_info_order', $infokeys ); // --- start tabbed schedule output --- -$panels = ''; +$tabs = $panels = ''; $tcount = 0; $start_tab = false; // 2.3.0: loop weekdays instead of legacy master list @@ -350,7 +365,15 @@ $title = apply_filters( 'radio_station_schedule_show_title_display', $title, $show_id, 'tabs' ); if ( ( '' != $title ) && is_string( $title ) ) { $info['title'] = $title; - // $panels .= $title; + } + // 2.3.3.9: allow for admin edit link + $edit_link = apply_filters( 'radio_station_show_edit_link', '', $show_id, $shift['id'], 'tabs' ); + if ( '' != $edit_link ) { + if ( isset( $info['title'] ) ) { + $info['title'] .= $edit_link; + } else { + $info['title'] = $edit_link; + } } // --- show hosts --- @@ -429,7 +452,8 @@ } // 2.3.3.8: moved show time filter out and added display filter - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'tabs', $shift ); + // 2.3.3.9: added tcount argument to filter + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show_id, 'tabs', $shift, $tcount ); $times = '
    ' . $newline; - $times .= '
    ' . $newline; + // 2.3.3.9: added internal spans for user time + $times .= '
    ' . $newline; + $times .= '[' . $newline; + $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $times .= ']' . $newline; + $times .= '
    ' . $newline; $info['times'] = $times; $tcount ++; @@ -605,20 +634,25 @@ } // --- add day tabs to output --- -$output = '
      ' . $newline; -$output .= $tabs; -$output .= '
    ' . $newline; +$html = '
      ' . $newline; +$html .= $tabs; +$html .= '
    ' . $newline; // --- add day panels to output --- // 2.3.3.8: check for hide past shows attribute -$output .= '
    @@ -914,9 +971,11 @@
     
    '; + } ?>
    @@ -1017,3 +1080,6 @@ }, 500);"; wp_add_inline_script( 'radio-station-show-page', $js ); } + +// 2.3.3.9: turn off doing template flag +$radio_station_data['doing-template'] = false; From c4100922108b6524124693a3dc9d60d60634493d Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 27 Apr 2021 09:47:40 +1000 Subject: [PATCH 194/377] dev update for sync override taxonomies --- includes/post-types-admin.php | 77 ++++++++++++++++++++++--------- includes/support-functions.php | 53 ++++++++++++++------- templates/single-show-content.php | 2 +- 3 files changed, 94 insertions(+), 38 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index d58fc78..9c19ecc 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1794,9 +1794,10 @@ function radio_station_show_images_save() { // -------------------- // 2.3.2: added AJAX show save shifts action // 2.3.3.9: added AJAX show save shift action +// 2.3.3.9: change save action priority (to be after taxonomy updates) add_action( 'wp_ajax_radio_station_show_save_shift', 'radio_station_show_save_data' ); add_action( 'wp_ajax_radio_station_show_save_shifts', 'radio_station_show_save_data' ); -add_action( 'save_post', 'radio_station_show_save_data' ); +add_action( 'save_post', 'radio_station_show_save_data', 11 ); function radio_station_show_save_data( $post_id ) { // --- verify if this is an auto save routine --- @@ -2108,6 +2109,40 @@ function radio_station_show_save_data( $post_id ) { } } + // 2.3.3.9: maybe sync to linked override taxonomies + $overrides = radio_station_get_linked_overrides( $post_id ); + if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { + + // --- get genre and language terms --- + $genre_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); + if ( count( $genre_terms ) > 0 ) { + foreach ( $genre_terms as $genre_term ) { + $genre_term_ids[] = $genre_term->term_id; + } + } + $language_terms = wp_get_object_terms( $post_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $language_terms ) > 0 ) { + foreach ( $language_terms as $language_term ) { + $language_term_ids[] = $language_term->term_id; + } + } + + // --- maybe set these terms to linked overrides --- + foreach ( $overrides as $override_id ) { + $sync_genres = get_post_meta( $override_id, 'sync_genres', true ); + if ( 'yes' == $sync_genres ) { + wp_set_post_terms( $override_id, $genre_term_ids, RADIO_STATION_GENRES_SLUG ); + } + $sync_languages = get_post_meta( $override_id, 'sync_languages', true ); + if ( 'yes' == $sync_languages ) { + wp_set_post_terms( $override_id, $language_term_ids, RADIO_STATION_LANGUAGES_SLUG ); + } + if ( ( 'yes' == $sync_genres ) || ( 'yes' == $sync_languages ) ) { + radio_station_clear_cached_data( $override_id ); + } + } + } + // --- maybe clear transient data --- // 2.3.0: added to clear transients if any meta has changed // 2.3.3: remove current show transient @@ -3794,14 +3829,13 @@ function radio_station_override_save_data( $post_id ) { } } $genre_terms = wp_get_object_terms( $linked_id, RADIO_STATION_GENRES_SLUG ); - print_r( $genre_terms ); if ( count( $genre_terms ) > 0 ) { foreach ( $genre_terms as $genre_term ) { $term_ids[] = $genre_term->term_id; } } wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_GENRES_SLUG ); - if ( array_diff( $prev_term_ids, $term_ids ) === array_diff( $term_ids, $prev_term_ids ) ) { + if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { $meta_changed = true; } } @@ -3813,28 +3847,29 @@ function radio_station_override_save_data( $post_id ) { $sync_languages = false; if ( isset( $_POST['sync_languages'] ) && ( 'yes' == $_POST['sync_languages'] ) ) { - update_post_meta( $post_id, 'sync_languages', 'yes' ); + if ( $linked_show ) { + + update_post_meta( $post_id, 'sync_languages', 'yes' ); - // --- sync language terms --- - $prev_term_ids = $term_ids = array(); - $prev_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); - if ( count( $prev_terms ) > 0 ) { - foreach( $prev_terms as $prev_term ) { - $prev_term_ids[] = $prev_term->term_id; + // --- sync language terms --- + $prev_term_ids = $term_ids = array(); + $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $prev_terms ) > 0 ) { + foreach( $prev_terms as $prev_term ) { + $prev_term_ids[] = $prev_term->term_id; + } } - } - $language_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); - print_r( $language_terms ); - if ( count( $language_terms ) > 0 ) { - foreach ( $language_terms as $language_term ) { - $term_ids[] = $language_term->term_id; + $language_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $language_terms ) > 0 ) { + foreach ( $language_terms as $language_term ) { + $term_ids[] = $language_term->term_id; + } + } + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); + if ( array_diff( $prev_term_ids, $term_ids ) != array_diff( $term_ids, $prev_term_ids ) ) { + $meta_changed = true; } } - wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); - if ( array_diff( $prev_term_ids, $term_ids ) === array_diff( $term_ids, $prev_term_ids ) ) { - $meta_changed = true; - } - } else { delete_post_meta( $post_id, 'sync_languages' ); } diff --git a/includes/support-functions.php b/includes/support-functions.php index d6fe087..8ab0fed 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1154,7 +1154,7 @@ function radio_station_get_show_override( $override_id, $meta_key ) { // ----------------------------- // Get Linked Overrides for Show // ----------------------------- -// 2.3.3.9: added for show page display +// 2.3.3.9: added for getting linked show overrides function radio_station_get_linked_overrides( $post_id ) { // -- get show ID for override or show post --- @@ -1165,32 +1165,53 @@ function radio_station_get_linked_overrides( $post_id ) { $show_id = $post_id; } - // --- get linked overrides via show ID --- + // --- get linked override IDs via show ID --- global $wpdb; $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta WHERE meta_key = 'linked_show_id' AND meta_value = %d"; $query = $wpdb->prepare( $query, $show_id ); $results = $wpdb->get_results( $query, ARRAY_A ); - $overrides = false; + $override_ids = array(); if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { $override_id = $result['post_id']; - $schedule = get_post_meta( $override_id, 'show_override_sched', true ); - if ( $schedule ) { - if ( !is_array( $overrides ) ) { - $override = array(); - } - if ( !is_array( $schedule ) ) { - $schedule = array( $schedule ); - } - foreach ( $schedule as $override ) { - $overrides[] = $override; - } - } + $override_ids[] = $override_id; } - } + } + + // --- filter and return --- + $override_ids = apply_filters( 'radio_station_linked_overrides', $override_ids, $post_id ); + return $override_ids; +} + +// ------------------------- +// Get Linked Override Times +// ------------------------- +// 2.3.3.9: added for show page display +function radio_station_get_linked_override_times( $post_id ) { + + $override_ids = radio_station_get_linked_overrides( $post_id ); + $overrides = array(); + foreach ( $override_ids as $override_id ) { + $schedule = get_post_meta( $override_id, 'show_override_sched', true ); + if ( $schedule ) { + if ( !is_array( $overrides ) ) { + $override = array(); + } + if ( !is_array( $schedule ) ) { + $schedule = array( $schedule ); + } + foreach ( $schedule as $override ) { + $overrides[] = $override; + } + } + } + + // --- filter and return --- + $overrides = apply_filters( 'radio_station_linked_override_times', $overrides, $post_id ); return $overrides; } + // -------------------- // Get Current Schedule // -------------------- diff --git a/templates/single-show-content.php b/templates/single-show-content.php index f9eddd7..b2a9d7f 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -668,7 +668,7 @@ // 2.3.3.9: maybe output linked override times $date_format = apply_filters( 'radio_station_override_date_format', 'j F' ); -$overrides = radio_station_get_linked_overrides( $post_id ); +$overrides = radio_station_get_linked_override_times( $post_id ); if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { $blocks['show_times'] .= '
    ' . esc_html( __( 'Special Times', 'radio-station' ) ) . '
    '; foreach ( $overrides as $override ) { From 3ec77297ecaa22333a7b682caef1b17d4d761a62 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 27 Apr 2021 10:35:34 +1000 Subject: [PATCH 195/377] dev update fix #300 --- includes/shortcodes.php | 12 ++++++++---- includes/support-functions.php | 30 ++++++++++++++++++++---------- js/radio-station.js | 3 ++- templates/single-show-content.php | 7 ++++--- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 7b38b52..e5fa3f6 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -644,7 +644,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $end_time = radio_station_convert_shift_time( $end ); $shift_start_time = radio_station_to_time( $override_time['day'] . ' ' . $start_time ); $shift_end_time = radio_station_to_time( $override_time['day'] . ' ' . $end_time ); - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -1977,7 +1978,8 @@ function radio_station_current_show_shortcode( $atts ) { $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); } $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -2653,7 +2655,8 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $shift_start ); } $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $shift_end ); - if ( $shift_end_time < $shift_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -3155,7 +3158,8 @@ function radio_station_current_playlist_shortcode( $atts ) { } $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); // 2.3.3.9: fix to overnight check variables - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } diff --git a/includes/support-functions.php b/includes/support-functions.php index 8ab0fed..e5f139d 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -568,7 +568,8 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { $override_start_time = radio_station_to_time( $date . ' ' . $start_time ); $override_end_time = radio_station_to_time( $date . ' ' . $end_time ); // 2.3.2: fix for overrides ending at midnight - if ( '12:00 am' == $end ) { + // 2.3.3.9: fix to use standardized operator check + if ( $override_end_time <= $override_start_time ) { $override_end_time = $override_end_time + ( 24 * 60 * 60 ); } // TODO: allow for multiday overrides ? @@ -1336,7 +1337,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $override_end_time = $override_end_time + 60; } // 2.3.2: fix for non-split overrides ending on midnight - if ( $override_end_time < $override_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $override_end_time <= $override_start_time ) { $override_end_time = $override_end_time + ( 24 * 60 * 60 ); } @@ -1357,7 +1359,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $end_time = $end_time + 60; } // 2.3.2: fix for non-split shifts ending on midnight - if ( $end_time < $start_time ) { + // 2.3.3.9: added or equals to operator + if ( $end_time <= $start_time ) { $end_time = $end_time + ( 24 * 60 * 60 ); } @@ -1577,7 +1580,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) // } // - adjust for shifts ending past midnight - - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -1666,7 +1670,8 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) $end_time = radio_station_convert_shift_time( $shift['end'] ); $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -1842,7 +1847,8 @@ function radio_station_get_current_show( $time = false ) { $shift_start_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_start ); $shift_end_time = radio_station_to_time( $weekdates[$day] . ' ' . $shift_end ); // 2.3.3: fix for shifts split over midnight - if ( $shift_start_time > $shift_end_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -2793,7 +2799,8 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); // $shift_start_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $start_time ); // $shift_end_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $end_time ); - if ( $shift_end_time < $shift_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -2827,7 +2834,8 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // $day_shift_start_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_start ); // $day_shift_end_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_end ); // 2.3.2: adjust for midnight with change to use non-split shifts - if ( $day_shift_end_time < $day_shift_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $day_shift_end_time <= $day_shift_start_time ) { $day_shift_end_time = $day_shift_end_time + ( 24 * 60 * 60 ); } @@ -3001,7 +3009,8 @@ function radio_station_check_new_shifts( $new_shifts ) { $shift_a_end_time = radio_station_to_time( $weekdates[$shift_a['day']] . ' ' . $shift_a_end ); // $shift_a_start_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_start ); // $shift_a_end_time = radio_station_to_time( '+2 weeks ' . $shift_a['day'] . ' ' . $shift_a_end ); - if ( $shift_a_end_time < $shift_a_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_a_end_time <= $shift_a_start_time ) { $shift_a_end_time = $shift_a_end_time + ( 24 * 60 * 60 ); } @@ -3034,7 +3043,8 @@ function radio_station_check_new_shifts( $new_shifts ) { $shift_b_end_time = radio_station_to_time( $weekdates[$shift_b['day']] . ' ' . $shift_b_end ); // $shift_b_start_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_start ); // $shift_b_end_time = radio_station_to_time( '+2 weeks ' . $shift_b['day'] . ' ' . $shift_b_end ); - if ( $shift_b_end_time < $shift_b_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_b_end_time <= $shift_b_start_time ) { $shift_b_end_time = $shift_b_end_time + ( 24 * 60 * 60 ); } diff --git a/js/radio-station.js b/js/radio-station.js index 03a2397..2994e53 100644 --- a/js/radio-station.js +++ b/js/radio-station.js @@ -112,7 +112,7 @@ function radio_convert_times() { startdisplay = radio_user_time(starttime, startformat); enddisplay = radio_user_time(endtime, endformat); if ((starthtml != startdisplay) || (endhtml != enddisplay)) { - if (radio.debug) {console.log('Start Display: '+startdisplay+' - End Display: '+enddisplay);} + if (radio.debug) {console.log('Start: '+starthtml+' => '+startdisplay+' - End: '+endhtml+' => '+enddisplay);} showusertime = jQuery(this).parent().find('.show-user-time').show(); showusertime.find('.rs-start-time').html(startdisplay); showusertime.find('.rs-end-time').html(enddisplay); @@ -122,6 +122,7 @@ function radio_convert_times() { datehtml = date.html(); datetime = date.attr('data'); dateformat = date.attr('data-format'); datedisplay = radio_user_time(datetime, dateformat); if (datehtml == datedisplay) {datedisplay = '';} + if (radio.debug) {console.log('Date: '+datehtml+' => '+datedisplay);} showusertime = jQuery(this).parent().find('.show-user-time'); showusertime.find('.rs-start-date').html(datedisplay); } diff --git a/templates/single-show-content.php b/templates/single-show-content.php index b2a9d7f..574e343 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -594,7 +594,8 @@ $end_time = radio_station_convert_shift_time( $end ); $shift_start_time = radio_station_to_time( $start_time ); $shift_end_time = radio_station_to_time( $end_time ); - if ( $shift_end_time < $shift_start_time ) { + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); } @@ -670,7 +671,7 @@ $date_format = apply_filters( 'radio_station_override_date_format', 'j F' ); $overrides = radio_station_get_linked_override_times( $post_id ); if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { - $blocks['show_times'] .= '
    ' . esc_html( __( 'Special Times', 'radio-station' ) ) . '
    '; + $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    '; foreach ( $overrides as $override ) { if ( 'yes' != $override['disabled'] ) { @@ -678,7 +679,7 @@ $end = $override['date'] . ' ' . $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; $override_start_time = radio_station_to_time( $start ); $override_end_time = radio_station_to_time( $end ); - if ( $override_end_time < $override_start_time ) { + if ( $override_end_time <= $override_start_time ) { $override_end_time = $override_end_time + ( 24 * 60 * 60 ); } $start_display = radio_station_get_time( $start_data_format, $override_start_time ); From ed48c5415692b5e615d5042b34eae76ebc011dea Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 28 Apr 2021 11:26:03 +1000 Subject: [PATCH 196/377] dev update override time IDs --- CHANGELOG.md | 8 +++ docs/Display.md | 13 +++- includes/post-types-admin.php | 117 ++++++++++++++++++++++++++++++++- includes/support-functions.php | 26 +++++++- readme.md | 7 ++ readme.txt | 1 + 6 files changed, 168 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ce7b0..d735d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.9 * Update: Plugin Panel (1.1.8) with Number Step Min/Max fix * Update: Freemius SDK (2.4.2) +* Improved: Allow for Multiple Override Times (with AJAX Saving) +* Added: Link Override to Show Data with selectable Show Fields +* Added: Language Archive Shortcode (similar to Genre Archive) +* Fixed: Current Show highlighting timer interval cycling +* Fixed: Before and After Show classes when no current Show * Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch * Fixed: Multiple host separator display in Current Show Widget +* Fixed: Playlist Widget playlist ended label when no next playlist +* Fixed: Conflicting duplicate filter name for Show Avatar +* Fixed: time conversions where start/finish Show/Override is equal ### 2.3.3.8 * Update: Plugin Panel (1.1.7) with Image and Color Picker fields diff --git a/docs/Display.md b/docs/Display.md index cddbe3d..425466a 100644 --- a/docs/Display.md +++ b/docs/Display.md @@ -15,6 +15,17 @@ You can find the base styles in the `/css` directory of the plugin, prefixed wit You may wish to add your own styles to suit your site's look and feel. One easy way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. This is preferable to modifying the base style files, as your changes will be overwritten in a plugin update. +## Timezone Conversions + +#### Automatic Display + +Radio Station will now display a shift time converted to the users timezone under every shift time displayed. This conversion is based on the selected Timezone in your plugin settings and the default timezone detected in the users browser. This feature is on by default but can be disabled in the Plugin Settings. + +#### [Pro] User Timezone Switching + +In [Radio Station Pro](https://radiostation.pro), there is an additional interface displayed in the Clock/Timezone shortcode (automatically displayed above schedules and also in the Radio Clock widget.) This allows your visitors/listeners to select a specific timezone other than what is in their browser. This can be useful for travellers especially, for keeping up with their home Shows, or tuning into a new local station without needing to update their computer clock. + + ## Automatic Pages ### Master Schedule Page @@ -122,7 +133,7 @@ This means you do not have to retranslate common strings for days of the week, m #### Using a Translation Plugin - +If you wish to use a translation plugin instead, you can find the relevant language filed in the /languages/ directory of the plugin. Follow the translation plugin documentation on how to use these files. We welcome submissions of updated language files. Please contact us via email or submit a pull request via the [Github repository](https://github.com/netmix/radio-station/). diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 9c19ecc..88b1767 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1972,6 +1972,7 @@ function radio_station_show_save_data( $post_id ) { $shifts = $_POST['show_sched']; } $prev_shifts = radio_station_get_show_schedule( $post_id ); + $new_ids = array(); $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { foreach ( $shifts as $i => $shift ) { @@ -1984,6 +1985,7 @@ function radio_station_show_save_data( $post_id ) { if ( 'new-' == substr( $i, 0, 4 ) ) { $i = radio_station_unique_shift_id(); } + $new_ids[] = $i; // --- loop shift keys --- foreach ( $shift as $key => $value ) { @@ -2107,6 +2109,25 @@ function radio_station_show_save_data( $post_id ) { delete_post_meta( $post_id, 'show_sched' ); $show_shifts_changed = true; } + + // 2.3.3.9: clear out old unique shift IDs from prev_shifts + if ( $show_shifts_changed && is_array( $prev_shifts ) && ( count( $prev_shifts ) > 0 ) ) { + $prev_ids = array(); + foreach ( $prev_shifts as $i => $shift ) { + if ( !in_array( $i, $new_ids ) ) { + $prev_ids[] = $i; + } + } + if ( count( $prev_ids ) > 0 ) { + $unique_ids = get_option( 'radio_station_shifts_ids' ); + foreach ( $unique_ids as $i => $unique_id ) { + if ( in_array( $unique_id, $prev_ids ) ) { + unset( $unique_ids[$i] ); + } + } + update_option( 'radio_station_shifts_ids', $unique_ids ); + } + } } // 2.3.3.9: maybe sync to linked override taxonomies @@ -2258,6 +2279,37 @@ function radio_station_show_delete( $post_id, $post ) { // --- clear all cached schedule data --- radio_station_clear_cached_data( $post_id ); + + // --- clear from unique shift IDs list --- + // 2.3.3.9: added to keep unique ID list from bloating over time + $shift_ids = get_option( 'radio_station_shifts_ids' ); + $ids_changed = false; + if ( $shift_ids && is_array( $shift_ids ) && ( count( $shift_ids ) > 0 ) ) { + if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $id => $shift ) { + if ( 8 == strlen( $id ) ) { + $key = array_search( $id, $shift_ids ); + unset( $shift_ids[$key] ); + } + } + } + } elseif ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + $shifts = get_post_meta( $post_id, 'show_override_sched', true ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $shift ) { + if ( isset( $shift['id'] ) && ( 8 == strlen( $shift['id'] ) ) ) { + $key = array_search( $shift['id'], $shift_ids ); + unset( $shift_ids[$key] ); + } + } + } + } + } + if ( $ids_changed ) { + update_option( 'radio_station_shift_ids', $shift_ids ); + } } @@ -3197,7 +3249,24 @@ function radio_station_overrides_table( $post_id ) { $overrides = get_post_meta( $post_id, 'show_override_sched', true ); if ( array_key_exists( 'date', $overrides ) ) { $overrides = array( $overrides ); + update_post_meta( $post_id, 'show_override_sched', $overrides ); } + + // 2.2.3.9: loop to add unique shift IDs and maybe resave + if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { + $update_overrides = false; + foreach ( $overrides as $j => $data ) { + if ( !isset( $data['id'] ) ) { + $data['id'] = radio_station_unique_shift_id(); + $overrides[$j] = $data; + $update_overrides = true; + } + } + if ( $update_overrides ) { + update_post_meta( $post_id, 'show_override_sched', $overrides ); + } + } + echo 'Current Overrides: ' . print_r( $overrides, true ) . ''; if ( !$overrides ) { @@ -3279,9 +3348,14 @@ function radio_station_overrides_table( $post_id ) { $list = ''; foreach ( $overrides as $i => $override ) { - $list .= '
    '; + // 2.3.3.9: use override shift ID for override wrapper ID --- + $id = $override['id']; + $list .= '
    '; $list .= '
    '; + // --- multiple selection helper text --- + // 2.3.0: added multiple selection helper text + echo '
    ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
    ' . PHP_EOL; + + echo '
    ' . PHP_EOL; } // ------------------------------------ @@ -638,16 +728,18 @@ function radio_station_show_producers_metabox() { // --- move any selected DJs to the top of the list --- foreach ( $producers as $i => $producer ) { if ( in_array( $producer->ID, $current ) ) { - unset( $producers[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $producers, $producer ); // prepend the user to the beginning of the array + // unset first, or prepending will change the index numbers and cause you to delete the wrong item + unset( $producers[$i] ); + // prepend the user to the beginning of the array + array_unshift( $producers, $producer ); } } // --- Producer Selection Input --- // 2.3.3.9: move meta_inner ID to class - echo '
    '; - echo '' . PHP_EOL; + echo '' . PHP_EOL; foreach ( $producers as $producer ) { $display_name = $producer->display_name; if ( $producer->display_name !== $producer->user_login ) { @@ -657,12 +749,15 @@ function radio_station_show_producers_metabox() { if ( in_array( $producer->ID, $current ) ) { echo ' selected="selected"'; } - echo '>' . esc_html( $display_name ) . ''; + echo '>' . esc_html( $display_name ) . '' . PHP_EOL; } - echo ''; + echo '' . PHP_EOL; // --- multiple selection helper text --- - echo '
    ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
    '; + echo '
    '; + esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ); + echo '
    ' . PHP_EOL; + echo '
    '; } @@ -705,13 +800,13 @@ function radio_station_show_shifts_metabox() { wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); // 2.3.3.9: move meta_inner ID to class - echo '
    '; + echo '
    ' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; // 2.3.2: added to bypass shift check on add (for debugging) if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - echo ''; + echo '' . PHP_EOL; } // --- output show shifts table --- @@ -721,10 +816,11 @@ function radio_station_show_shifts_metabox() { // --- show inactive message --- // 2.3.0: added show inactive reminder message if ( !$table['active'] ) { - echo '
    '; + // 2.3.3.9: change to mismatched div class + echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); - echo '
    '; + echo '
    ' . PHP_EOL; } // --- shift conflicts message --- @@ -739,7 +835,7 @@ function radio_station_show_shifts_metabox() { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $table['list']; } - echo '
    '; + echo '
    ' . PHP_EOL; // --- shift save/add buttons --- // 2.3.0: center add shift button @@ -748,25 +844,25 @@ function radio_station_show_shifts_metabox() { // 2.3.2: added table and shifts saved message // 2.3.2: fix centering by removing span wrapper // 2.3.2: change from button-primary to button-secondary + // 2.3.3.9: change shift-table-buttons ID to shift-buttons class // echo '
    ' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; - echo '
    '; - echo '
    '; - echo ''; - echo ''; + echo '
    ' . PHP_EOL; + echo ''; + echo '' . PHP_EOL; // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { - echo ''; + echo '' . PHP_EOL; } - echo ''; - echo ''; - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    '; - echo ''; - echo ''; - echo ''; - echo '
    '; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- get shift edit javascript --- // 2.3.3.9: moved to separate function @@ -778,7 +874,22 @@ function radio_station_show_shifts_metabox() { // --- shift display styles --- // 2.3.2: added dashed border to new shift - echo '' . PHP_EOL; + + // --- close meta inner --- + echo '
    ' . PHP_EOL; +} + +// ------------------ +// Shifts List Styles +// ------------------ +// 2.3.3.9: moved to separate function +function radio_station_shifts_list_styles() { + + // 2.3.3.9: change shift-table-buttons ID to shift-buttons class + $css = "#new-shifts .show-shift {border: 2px dashed green; background-color: #FFFFDD;} .show-shift {list-style: none; margin-bottom: 10px; border: 2px solid green; background-color: #EEFFEE;} .show-shift select.changed, .show-shift input[checkbox].changed {background-color: #FFFFCC;} .show-shift select option.original {font-weight: bold;} @@ -791,15 +902,15 @@ function radio_station_show_shifts_metabox() { .show-shift.conflicts {outline: 2px solid red;} .show-shift.disabled.conflicts {border: 2px dashed red; outline: none;} .show-shift select.incomplete {border: 2px solid orange;} - #shifts-table-buttons .shifts-clear, #shifts-table-buttons .shifts-save, #shifts-table-buttons .shift-add { + .shift-buttons .shifts-clear, .shift-buttons .shifts-save, .shift-buttons .shift-add { cursor: pointer; display:block; width: 150px; padding: 8px; text-align: center; line-height: 1em;} .shift-duplicate, .shift-remove {cursor: pointer;} #shifts-saving-message, #shifts-saved-message { - background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;} - '; + background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}" . PHP_EOL; - // --- close meta inner --- - echo '
    '; + // 2.3.3.9: added shift edit styles filter + $css = apply_filters( 'radio_station_shift_list_edit_styles', $css ); + return $css; } // ----------------- @@ -981,8 +1092,8 @@ function radio_station_shift_edit_script() { var count = jQuery('#new-shifts').children().length + 1; output = '
    '; output += '
      '; - output += '
    • '; - output += '" . esc_js( __( 'Day', 'radio-station' ) ) . ": '; + output += '
    • '; + output += ': '; output += ''; - output += '
    • '; + output += '';" . PHP_EOL; - output += '
    • '; - output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": '; - output += '';"; // - start hour - foreach ( $hours as $hour ) { @@ -1026,10 +1138,10 @@ function radio_station_shift_edit_script() { if (values.start_meridian == '" . esc_js( $pm ) . "') {output += ' selected=\"selected\"';} output += '>" . esc_js( $pm ) . "'; output += ' '; - output += '
    • ';"; + output += '';" . PHP_EOL; - $js .= "output += '
    • '; - output += '" . esc_js( __( 'End Time', 'radio-station' ) ) . ": '; + $js .= "output += '
    • '; + output += ': '; output += ' ';"; + $js .= "output += ' ';" . PHP_EOL; // - end min - @@ -1049,7 +1161,7 @@ function radio_station_shift_edit_script() { if (values.end_min == '" . esc_js( $min ) . "') {output += ' selected=\"selected\"';} output += '>" . esc_js( $min ) . "';"; } - $js .= "output += ' ';"; + $js .= "output += ' ';" . PHP_EOL; // - end meridian - $js .= "output += ' '; - output += '
    • ';"; + output += '';" . PHP_EOL; // - encore - - $js .= "output += '
    • '; + $js .= "output += '
    • '; output += ''; + output += '
    • ';" . PHP_EOL; // - disabled shift - - $js .= "output += '
    • '; + $js .= "output += '
    • '; output += ''; + output += '
    • ';" . PHP_EOL; // - duplicate shift - - $js .= "output += '
    • '; + $js .= "output += '
    • '; output += ''; - output += '
    • ';"; + output += '';" . PHP_EOL; // - remove shift - - $js .= "output += '
    • '; + $js .= "output += '
    • '; output += ''; - output += '
    • ';"; + output += '';" . PHP_EOL; // --- append new shift list item --- $js .= "output += '
    '; @@ -1099,14 +1211,16 @@ function radio_station_shift_edit_script() { // ----------------------- // Shifts Oonflict Message // ----------------------- -function radio_station_shifts_conflict_message() { - echo '
    '; - echo '' . esc_html( __( 'Warning! Show Shift Conflicts were detected!', 'radio-station' ) ) . '
    '; - echo esc_html( __( 'Please note that Shifts with conflicts are automatically disabled upon saving.', 'radio-station' ) ) . '
    '; - echo esc_html( __( 'Fix the Shift and/or the Shift on the conflicting Show and Update them both.', 'radio-station' ) ) . '
    '; - echo esc_html( __( 'Then you can uncheck the shift Disable box and Update to re-enable the Shift.', 'radio-station' ) ) . '
    '; +// 2.3.3.9: add hidden argument +function radio_station_shifts_conflict_message( $hidden = false ) { + + echo '
    '; } @@ -1299,145 +1413,149 @@ function radio_station_show_shifts_table( $post_id ) { } $classlist = implode( " ", $classes ); - $list .= '
    '; - $list .= '
      '; + $list .= '
      ' . PHP_EOL; + + $list .= '
        ' . PHP_EOL; // --- shift day selection --- - $list .= '
      • '; - $list .= esc_html( __( 'Day', 'radio-station' ) ) . ': '; + $list .= '
      • ' . PHP_EOL; + $list .= ': '; $class = ( '' == $shift['day'] ) ? 'incomplete' : ''; - $list .= ''; - $list .= ''; - $list .= '
      • '; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- shift start time --- - $list .= '
      • '; - $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ': '; + $list .= '
      • ' . PHP_EOL; + + $list .= ': ' . PHP_EOL; // --- start hour selection --- $class = ( '' == $shift['start_hour'] ) ? 'incomplete' : ''; - $list .= '' . PHP_EOL; foreach ( $hours as $hour ) { $class = ( $hour == $shift['start_hour'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- start minute selection --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; foreach ( $mins as $min ) { $class = ( $min == $shift['start_min'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- start meridiem selection --- $class = ( '' == $shift['start_meridian'] ) ? 'incomplete' : ''; - $list .= '' . PHP_EOL; $class = ( 'am' == $shift['start_meridian'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; $class = ( 'pm' == $shift['start_meridian'] ) ? 'original' : ''; - $list .= ''; - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; - $list .= '
      • '; + $list .= '' . PHP_EOL; // --- shift end time --- - $list .= '
      • '; - $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ': '; + $list .= '
      • ' . PHP_EOL; + + $list .= ': '; // --- end hour selection --- $class = ( '' == $shift['end_hour'] ) ? 'incomplete' : ''; - $list .= '' . PHP_EOL; foreach ( $hours as $hour ) { $class = ( $hour == $shift['end_hour'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- end minute selection --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; foreach ( $mins as $min ) { $class = ( $min == $shift['end_min'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- end meridiem selection --- $class = ( '' == $shift['end_meridian'] ) ? 'incomplete' : ''; - $list .= '' . PHP_EOL; $class = ( 'am' == $shift['end_meridian'] ) ? 'original' : ''; - $list .= ''; + $list .= '' . PHP_EOL; $class = ( 'pm' == $shift['end_meridian'] ) ? 'original' : ''; - $list .= ''; - $list .= ''; - $list .= ''; - $list .= '
      • '; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- encore presentation --- if ( !isset( $shift['encore'] ) ) { $shift['encore'] = ''; } - $list .= '
      • '; - $list .= ''; - $list .= ' ' . esc_html( __( 'Encore', 'radio-station' ) ); - $list .= ''; - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= ' ' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '
      • ' . PHP_EOL; // --- shift disabled --- // 2.3.0: added disabled checkbox to shift row if ( !isset( $shift['disabled'] ) ) { $shift['disabled'] = ''; } - $list .= '
      • '; - $list .= ''; - $list .= ' ' . esc_html( __( 'Disabled', 'radio-station' ) ); - $list .= ''; - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= ' ' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '
      • ' . PHP_EOL; // --- duplicate shift icon --- // 2.3.0: added duplicate shift icon - $list .= '
      • '; - $title = __( 'Duplicate Shift', 'radio-station' ); - $list .= ''; - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; + $title = __( 'Duplicate Shift', 'radio-station' ) . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '
      • ' . PHP_EOL; // --- remove shift icon --- // 2.3.0: change remove button to icon - $list .= '
      • '; - $title = __( 'Remove Shift', 'radio-station' ); - $list .= ''; - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; + $title = __( 'Remove Shift', 'radio-station' ) . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '
      • ' . PHP_EOL; - $list .= '
      '; + $list .= '
    ' . PHP_EOL; // --- output any shift conflicts found --- if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { - $list .= '
    '; - $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': '; + + $list .= '
    ' . PHP_EOL; + $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': ' . PHP_EOL; foreach ( $conflicts as $j => $conflict ) { if ( $j > 0 ) { $list .= ', '; @@ -1453,18 +1571,22 @@ function radio_station_show_shifts_table( $post_id ) { $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; } - $list .= '

    '; + $list .= '

    ' . PHP_EOL; } // --- close shift wrapper --- - $list .= '
    '; + $list .= '
    ' . PHP_EOL; } } // 2.3.2: moved into function and changed ID // 2.3.3.9: change element from span to div - $list .= '
    '; + $list .= '
    ' . PHP_EOL; + + if ( RADIO_STATION_DEBUG ) { + $list .= 'Conflict List: ' . print_r( $conflict_list, true ) . '
    ' . PHP_EOL; + } // --- set return data --- // 2.3.2: added for separated function @@ -1502,14 +1624,15 @@ function radio_station_add_show_helper_box() { function radio_station_show_helper_box() { // --- show description helper text --- - echo '

    '; - echo esc_html( __( "The text field below is for your Show Description. It will display in the About section of your Show page.", 'radio-station' ) ); - echo ' ' . esc_html( __( "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.", 'radio-station' ) ); - echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . "
    "; - echo esc_html( __( "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.", 'radio-station' ) ); - echo ' ' . esc_html( __( "You can then assign them to a relevant Post Category for display on your site also.", 'radio-station' ) ); - echo '

    '; - + echo '

    ' . PHP_EOL; + echo esc_html( __( 'The text field below is for your Show Description. It will display in the About section of your Show page.', 'radio-station' ) ); + echo ' ' . esc_html( __( 'It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.', 'radio-station' ) ); + echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . '
    '; + echo esc_html( __( 'We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.', 'radio-station' ) ); + echo ' ' . esc_html( __( 'This way you can then assign them to a relevant Post Category for display on your site also.', 'radio-station' ) ); + echo '

    ' . PHP_EOL; + + // [Pro Blurb] // TODO: upgrade to Pro for upcoming Show Episodes blurb // echo '
    ' . esc_html( 'In future, Radio Station Pro will include an Episodes post type', 'radio-station' ) ); // TODO: change this text/link when Pro Episodes become available @@ -1580,41 +1703,42 @@ function radio_station_show_images_metabox() { $has_show_avatar = is_array( $show_avatar_src ); // --- show avatar image --- - echo '
    '; + echo '
    ' . PHP_EOL; // --- image container --- - echo '
    '; + echo '
    ' . PHP_EOL; if ( $has_show_avatar ) { - echo ''; + echo '' . PHP_EOL; } - echo '
    '; + echo '
    ' . PHP_EOL; // --- add and remove links --- - echo '

    '; + echo '

    ' . PHP_EOL; $hidden = ''; if ( $has_show_avatar ) { $hidden = ' hidden'; } - echo ''; - echo esc_html( __( 'Set Show Avatar Image', 'radio-station' ) ); - echo ''; + echo '' . PHP_EOL; + echo esc_html( __( 'Set Show Avatar Image', 'radio-station' ) ) . PHP_EOL; + echo '' . PHP_EOL; $hidden = ''; if ( !$has_show_avatar ) { $hidden = ' hidden'; } - echo ''; - echo esc_html( __( 'Remove Show Avatar Image', 'radio-station' ) ); - echo ''; - echo '

    '; + echo '' . PHP_EOL; + echo esc_html( __( 'Remove Show Avatar Image', 'radio-station' ) ) . PHP_EOL; + echo '' . PHP_EOL; + echo '

    ' . PHP_EOL; // --- hidden input for image ID --- - echo ''; + echo '' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; // --- check if show content header image is enabled --- + // 2.3.3.9: fix to match yes string value $header_image = radio_station_get_setting( 'show_header_image' ); - if ( $header_image ) { + if ( 'yes' == $header_image ) { // --- get show header image info $show_header = get_post_meta( $post->ID, 'show_header', true ); @@ -1622,107 +1746,115 @@ function radio_station_show_images_metabox() { $has_show_header = is_array( $show_header_src ); // --- show header image --- - echo '
    '; + echo '
    ' . PHP_EOL; // --- image container --- - echo '
    '; + echo '
    ' . PHP_EOL; if ( $has_show_header ) { - echo ''; + echo '' . PHP_EOL; } - echo '
    '; + echo '
    ' . PHP_EOL; // --- add and remove links --- - echo '

    '; + echo '

    ' . PHP_EOL; $hidden = ''; if ( $has_show_header ) { $hidden = ' hidden'; } - echo ''; - echo esc_html( __( 'Set Show Header Image', 'radio-station' ) ); - echo ''; + echo '' . PHP_EOL; + echo esc_html( __( 'Set Show Header Image', 'radio-station' ) ) . PHP_EOL; + echo '' . PHP_EOL; $hidden = ''; if ( !$has_show_header ) { $hidden = ' hidden'; } - echo ''; - echo esc_html( __( 'Remove Show Header Image', 'radio-station' ) ); - echo ''; - echo '

    '; + echo '' . PHP_EOL; + echo esc_html( __( 'Remove Show Header Image', 'radio-station' ) ) . PHP_EOL; + echo '' . PHP_EOL; + echo '

    ' . PHP_EOL; // --- hidden input for image ID --- - echo ''; + echo '' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; } // --- set images autosave nonce and iframe --- $images_autosave_nonce = wp_create_nonce( 'show-images-autosave' ); - echo ''; - echo ''; - - // --- image selection script --- - $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); - $js = "jQuery(function(){ - - var mediaframe, parentdiv, - imagesmetabox = jQuery('#radio-station-show-images-metabox'), - addimagelink = imagesmetabox.find('.upload-custom-image'), - deleteimagelink = imagesmetabox.find('.delete-custom-image'); + echo '' . PHP_EOL; + echo '' . PHP_EOL; - /* Add Image on Click */ - addimagelink.on( 'click', function( event ) { + // --- script text strings --- + $image_confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); + $media_title_text = __( 'Select or Upload Image' ,'radio-station' ); + $media_button_text = __( 'Use this Image', 'radio-station' ); - event.preventDefault(); - parentdiv = jQuery(this).parent().parent(); - - if (mediaframe) {mediaframe.open(); return;} - mediaframe = wp.media({ - title: 'Select or Upload Image', - button: {text: 'Use this Image'}, - multiple: false - }); - - mediaframe.on( 'select', function() { - var attachment = mediaframe.state().get('selection').first().toJSON(); - image = '\"\"'; - parentdiv.find('.custom-image-container').append(image); - parentdiv.find('.custom-image-id').val(attachment.id); - parentdiv.find('.upload-custom-image').addClass('hidden'); - parentdiv.find('.delete-custom-image').removeClass('hidden'); - - /* auto-save image via AJAX */ - postid = '" . $post->ID . "'; imgid = attachment.id; - if (parentdiv.attr('id') == 'show-avatar-image') {imagetype = 'avatar';} - if (parentdiv.attr('id') == 'show-header-image') {imagetype = 'header';} - imagessavenonce = jQuery('#show-images-save-nonce').val(); - framesrc = ajaxurl+'?action=radio_station_show_images_save'; - framesrc += '&post_id='+postid+'&image_type='+imagetype; - framesrc += '&image_id='+imgid+'&_wpnonce='+imagessavenonce; - jQuery('#show-images-save-frame').attr('src', framesrc); - }); - - mediaframe.open(); + // --- image selection script --- + // 2.3.3.9: add library argument to wp.media load + $js = " var mediaframe, parentdiv, + imagesmetabox = jQuery('#radio-station-show-images-metabox'), + addimagelink = imagesmetabox.find('.upload-custom-image'), + deleteimagelink = imagesmetabox.find('.delete-custom-image'), + imageconfirmremove = '" . esc_js( $image_confirm_remove ) . "', + wpmediatitletext = '" . esc_js( $media_title_text ). "', + wpmediabuttontext = '" . esc_js( $media_button_text ) . "'; + + /* Add Image on Click */ + addimagelink.on( 'click', function( event ) { + + event.preventDefault(); + parentdiv = jQuery(this).parent().parent(); + + if (mediaframe) {mediaframe.open(); return;} + mediaframe = wp.media({ + title: wpmediatitletext, + button: {text: wpmediabuttontext}, + library: {order: 'DESC', orderby: 'date', type: 'image', search: null, uploadedTo: null}, + multiple: false }); - /* Delete Image on Click */ - deleteimagelink.on( 'click', function( event ) { - event.preventDefault(); - agree = confirm('" . esc_js( $confirm_remove ) . "'); - if (!agree) {return;} - parentdiv = jQuery(this).parent().parent(); - parentdiv.find('.custom-image-container').html(''); - parentdiv.find('.custom-image-id').val(''); - parentdiv.find('.upload-custom-image').removeClass('hidden'); - parentdiv.find('.delete-custom-image').addClass('hidden'); + mediaframe.on( 'select', function() { + var attachment = mediaframe.state().get('selection').first().toJSON(); + image = '\"\"'; + parentdiv.find('.custom-image-container').append(image); + parentdiv.find('.custom-image-id').val(attachment.id); + parentdiv.find('.upload-custom-image').addClass('hidden'); + parentdiv.find('.delete-custom-image').removeClass('hidden'); + + /* auto-save image via AJAX */ + postid = jQuery('#post_ID').val(); imgid = attachment.id; + if (parentdiv.attr('id') == 'show-avatar-image') {imagetype = 'avatar';} + if (parentdiv.attr('id') == 'show-header-image') {imagetype = 'header';} + imagessavenonce = jQuery('#show-images-save-nonce').val(); + framesrc = ajaxurl+'?action=radio_station_show_images_save'; + framesrc += '&post_id='+postid+'&image_type='+imagetype; + framesrc += '&image_id='+imgid+'&nonce='+imagessavenonce; + jQuery('#show-images-save-frame').attr('src', framesrc); }); - });"; + mediaframe.open(); + }); + + /* Delete Image on Click */ + deleteimagelink.on( 'click', function( event ) { + event.preventDefault(); + agree = confirm(imageconfirmremove); + if (!agree) {return;} + parentdiv = jQuery(this).parent().parent(); + parentdiv.find('.custom-image-container').html(''); + parentdiv.find('.custom-image-id').val(''); + parentdiv.find('.upload-custom-image').removeClass('hidden'); + parentdiv.find('.delete-custom-image').addClass('hidden'); + }); + " . PHP_EOL; // --- enqueue script inline --- // 2.3.0: enqueue instead of echoing wp_add_inline_script( 'radio-station-admin', $js ); + // 2.3.3.9: add media modal close button style fix + echo ''; } // --------------------------------- @@ -1753,7 +1885,7 @@ function radio_station_show_images_save() { } // --- verify nonce value --- - if ( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( $_GET['_wpnonce'], 'show-images-autosave' ) ) { + if ( !isset( $_GET['nonce'] ) || !wp_verify_nonce( $_GET['nonce'], 'show-images-autosave' ) ) { exit; } @@ -1808,19 +1940,20 @@ function radio_station_show_save_data( $post_id ) { // --- make sure we have a post ID for AJAX save --- // 2.3.2: added AJAX shift saving checks if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( !isset( $_REQUEST['action'] ) ) { + return; + } // 2.3.3: added double check for AJAX action match - if ( isset( $_REQUEST['action'] ) ) { - // 2.3.3.9: check for single or multiple shift save action - if ( 'radio_station_show_save_shift' == $_REQUEST['action'] ) { - $selection = 'single'; - if ( preg_match( '/^[a-zA-Z0-9_]+$/', $_REQUEST['shift_id'] ) ) { - $shift_id = $_REQUEST['shift_id']; - } - } elseif ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { - $selection = 'multiple'; - } else { - return; - } + // 2.3.3.9: check for single or multiple shift save action + if ( 'radio_station_show_save_shift' == $_REQUEST['action'] ) { + $selection = 'single'; + if ( preg_match( '/^[a-zA-Z0-9_]+$/', $_REQUEST['shift_id'] ) ) { + $shift_id = $_REQUEST['shift_id']; + } + } elseif ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { + $selection = 'multiple'; + } elseif ( 'radio_station_add_show_shift' == $_REQUEST['action'] ) { + $selection = 'add'; } else { return; } @@ -1847,7 +1980,9 @@ function radio_station_show_save_data( $post_id ) { echo ""; exit; } @@ -1967,11 +2102,20 @@ function radio_station_show_save_data( $post_id ) { // --- loop posted show shift times --- // 2.3.1: added check if any shifts are set (fix undefined index warning) + $prev_shifts = radio_station_get_show_schedule( $post_id ); $shifts = $new_shifts = array(); - if ( isset( $_POST['show_sched'] ) ) { + // 2.3.3.9: allow for posting of just new shift + if ( isset( $_POST['new_shift'] ) ) { + $new_shift = $_POST['new_shift']; + // print_r( $new_shift ); + $new_id = $shift_id = radio_station_unique_shift_id(); + $shifts = $prev_shifts; + $shifts[$new_id] = $new_shift; + $_POST['show_sched'] = $shifts; + } else { $shifts = $_POST['show_sched']; } - $prev_shifts = radio_station_get_show_schedule( $post_id ); + $new_ids = array(); $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { @@ -2176,7 +2320,7 @@ function radio_station_show_save_data( $post_id ) { // 2.3.3.9: removed check of AJAX action as done earlier - // --- (hidden) debug information --- + // --- debug information --- echo "Posted Shifts: " . print_r( $_POST['show_sched'], true ) . PHP_EOL; echo "New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; @@ -2187,7 +2331,7 @@ function radio_station_show_save_data( $post_id ) { parent.document.getElementById('shifts-saved-message').style.display = ''; if (typeof parent.jQuery == 'function') {parent.jQuery('#shifts-saved-message').fadeOut(3000);} else {setTimeout(function() {parent.document.getElementById('shifts-saved-message').style.display = 'none';}, 3000);} - form = parent.document.getElementById('shift-save-form'); form.parentNode.removeChild(form); + /* form = parent.document.getElementById('shift-save-form'); form.parentNode.removeChild(form); */ parent.document.getElementById('show_shifts_nonce').value = '" . esc_js( $show_shifts_nonce ) . "'; "; @@ -2200,61 +2344,71 @@ function radio_station_show_save_data( $post_id ) { echo ''; } $table = radio_station_show_shifts_table( $post_id ); + + // --- check for shift conflicts --- + $display_warning = false; // 2.3.3.9: check conflict count instead of boolean test if ( count( $table['conflicts'] ) > 0 ) { - radio_station_shifts_conflict_message(); + // 2.3.3.9: maybe skip conflict message if single shift save has no conflict + $display_warning = true; + if ( isset( $selection ) && ( 'multiple' != $selection ) && isset( $shift_id ) ) { + $display_warning = $found_conflict = false; + foreach ( $table['conflicts'] as $unique_id => $conflict ) { + if ( $shift_id == $unique_id ) { + $found_conflict = true; + } + } + if ( $found_conflict ) { + $display_warning = true; + } + } + $hidden = !$display_warning; + radio_station_shifts_conflict_message( $hidden ); } // phpcs:ignore WordPress.Security.OutputNotEscaped echo $table['list']; - echo '
    '; + // echo '
    '; echo '
    '; // --- refresh show shifts list --- - echo ""; + $js = "shiftslist = parent.document.getElementById('shifts-list'); + shiftslist.innerHTML = document.getElementById('shifts-list').innerHTML; + shiftslist.style.display = '';" . PHP_EOL; - // 2.3.3.9: trigger action for single or multiple shift save - if ( isset( $selection ) ) { - if ( 'single' == $selection ) { - do_action( 'radio_station_show_save_shift' ); - } else { - do_action( 'radio_station_show_save_shifts' ); - } - } + // --- reload the current schedule view --- + $js .= "parent.radio_load_schedule(false,false);" . PHP_EOL; // 2.3.3.6: clear changes may not have been saved window reload message - echo ""; + }" . PHP_EOL; // --- alert on conflicts --- - // 2.3.3.9: check conflict count instead of boolean test - if ( count( $table['conflicts'] ) > 0 ) { - // 2.3.3.9: maybe skip conflict message if single shift save has no conflict - $display_warning = true; - if ( isset( $selection ) && ( 'single' == $selection ) && isset( $shift_id ) ) { - $display_warning = $found_conflict = false; - foreach ( $table['conflicts'] as $unique_id => $conflict ) { - if ( $shift_id == $unique_id ) { - $found_conflict = true; - } - } - if ( $found_conflict ) { - $display_warning = true; - } - } - if ( $display_warning ) { - $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); - echo ""; + if ( $display_warning ) { + $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); + $js .= "alert('" . esc_js( $warning ) . "');" . PHP_EOL; + } + + // --- output the scripts --- + echo ''; + + // 2.3.3.9: trigger action for single or multiple shift save + if ( isset( $selection ) ) { + if ( 'single' == $selection ) { + do_action( 'radio_station_show_save_shift', $shift_id ); + } else { + do_action( 'radio_station_show_save_shifts' ); } } - - // --- reload the current schedule view --- - echo ""; + } + // --- return early when adding single shift --- + // 2.3.3.9: added for single shift action + if ( 'radio_station_add_show_shift' == $_REQUEST['action'] ) { + return; } exit; @@ -2410,9 +2564,9 @@ function radio_station_show_column_data( $column, $post_id ) { $content = $wpdb->get_var( $query ); if ( !$content || ( trim( $content ) == '' ) ) { // 2.3.3.9: change bold emphasis to italics - echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + echo '' . esc_html( __( 'No', 'radio-station' ) ) . '' . PHP_EOL; } else { - echo esc_html( __( 'Yes', 'radio-station' ) ); + echo esc_html( __( 'Yes', 'radio-station' ) ) . PHP_EOL; } } elseif ( 'shifts' == $column ) { @@ -2479,7 +2633,7 @@ function radio_station_show_column_data( $column, $post_id ) { } $classlist = implode( ' ', $classes ); - echo "
    "; + echo '
    ' . PHP_EOL; // --- get shift start and end times --- // 2.3.2: fix to convert to 24 hour time @@ -2498,17 +2652,17 @@ function radio_station_show_column_data( $column, $post_id ) { $nextday = radio_station_get_next_day( $weekday ); // 2.3.0: handle shifts that go overnight for weekday filter if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { - echo ""; + echo ''; $bold = true; } } echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); - echo " " . esc_html( $start ) . " - " . esc_html( $end ); + echo ' ' . esc_html( $start ) . ' - ' . esc_html( $end ); if ( $bold ) { - echo ""; + echo ''; } - echo "
    "; + echo '
    '; } // --- dayless shifts --- @@ -2516,11 +2670,11 @@ function radio_station_show_column_data( $column, $post_id ) { if ( count( $dayless_shifts ) > 0 ) { foreach ( $dayless_shifts as $shift ) { $title = __( 'This shift is disabled as no day is set.', 'radio-station' ); - echo "
    "; - $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; - $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - echo esc_html( $start ) . " - " . esc_html( $end ); - echo "
    "; + echo '
    ' . PHP_EOL; + $start = $shift['start_hour'] . ':' . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . $shift['end_meridian']; + echo esc_html( $start ) . ' - ' . esc_html( $end ); + echo '
    '; } } } @@ -2531,7 +2685,7 @@ function radio_station_show_column_data( $column, $post_id ) { if ( $hosts && ( count( $hosts ) > 0 ) ) { foreach ( $hosts as $host ) { $user_info = get_userdata( $host ); - echo esc_html( $user_info->display_name ) . "
    "; + echo esc_html( $user_info->display_name ) . '
    '; } } @@ -2542,7 +2696,7 @@ function radio_station_show_column_data( $column, $post_id ) { if ( $producers && ( count( $producers ) > 0 ) ) { foreach ( $producers as $producer ) { $user_info = get_userdata( $producer ); - echo esc_html( $user_info->display_name ) . "
    "; + echo esc_html( $user_info->display_name ) . '
    '; } } @@ -2551,7 +2705,8 @@ function radio_station_show_column_data( $column, $post_id ) { // 2.3.0: get show avatar (with fallback to thumbnail) $image_url = radio_station_get_show_avatar_url( $post_id ); if ( $image_url ) { - echo "
    " . esc_html( __(
    "; + // 2.3.3.9: fix to use esc_attr instead of esc_attr + echo '
    ' . esc_attr( __( 'Show Avatar', 'radio-station' ) ) . '
    '; } } @@ -2563,6 +2718,7 @@ function radio_station_show_column_data( $column, $post_id ) { // 2.2.7: added show column styles add_action( 'admin_footer', 'radio_station_show_column_styles' ); function radio_station_show_column_styles() { + $current_screen = get_current_screen(); if ( 'edit-' . RADIO_STATION_SHOW_SLUG !== $current_screen->id ) { return; @@ -2592,15 +2748,15 @@ function radio_station_show_day_filter( $post_type, $which ) { // --- show day selector --- $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); - echo ''; - echo '' . PHP_EOL; + echo '' . PHP_EOL; foreach ( $days as $day ) { $label = esc_attr( radio_station_translate_weekday( $day ) ); - echo ''; + echo '' . PHP_EOL; } - echo ''; + echo '' . PHP_EOL; } @@ -2646,11 +2802,14 @@ function radio_station_override_show_metabox() { ); $shows = get_posts( $args ); - // --- link to Show field --- + // --- link to Show fields --- $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; // --- sync genre taxonomy --- $sync_genres = get_post_meta( $post_id, 'sync_genres', true ); - echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; // --- sync language taxonomy --- $sync_languages = get_post_meta( $post_id, 'sync_languages', true ); echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + + echo '' . PHP_EOL; + echo ''; + echo '
    ' . PHP_EOL; echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'If selected, Override data will be used from the Linked Show.', 'radio-station' ) ) . '' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '' . esc_html( 'Sync Genres?', 'radio-station' ) . '' . PHP_EOL; - echo '' . PHP_EOL; + echo '
  • ' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; echo '' . PHP_EOL; - echo '
  • ' . PHP_EOL; + echo '>' . PHP_EOL; + echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'If checked, assigned Genre terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . PHP_EOL; - echo '
    ' . PHP_EOL; - echo '' . esc_html( 'Sync Languages?', 'radio-station' ) . '' . PHP_EOL; - echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; echo '' . PHP_EOL; - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'If checked, assigned Language terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . PHP_EOL; - echo '

    '; + // --- close linked show list --- + echo '
    ' . PHP_EOL; // --- get show meta --- // $active = get_post_meta( $post->ID, 'show_active', true ); @@ -2720,19 +2888,19 @@ function radio_station_override_show_metabox() { $patreon_id = get_post_meta( $post_id, 'show_patreon', true ); $linked_fields = get_post_meta( $post_id, 'linked_show_fields', true ); - echo '
    '; + echo '
    ' . PHP_EOL; - echo '
    '; - echo '' . esc_html( __( 'Usage Note', 'radio-station' ) ) . ''; - echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ); - echo '

    '; + echo '
    ' . PHP_EOL; + echo '' . esc_html( __( 'Usage Note', 'radio-station' ) ) . '' . PHP_EOL; + echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ) . PHP_EOL; + echo '

    ' . PHP_EOL; // --- table headings --- - echo ''; + echo '
    '; - echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . ''; - echo ''; - echo '' . esc_html( __( 'Override Data', 'radio-station' ) ) . ''; - echo '
    ' . PHP_EOL; // TODO: show active ? // echo '

    @@ -2745,14 +2913,14 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_content'] ) && $linked_fields['show_title'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- content --- @@ -2761,14 +2929,14 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_content'] ) && $linked_fields['show_content'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- excerpt --- @@ -2777,14 +2945,14 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_excerpt'] ) && $linked_fields['show_excerpt'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- avatar --- @@ -2793,19 +2961,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_avatar'] ) && $linked_fields['show_avatar'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- featured image --- @@ -2814,19 +2982,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_image'] ) && $linked_fields['show_image'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- hosts --- @@ -2842,12 +3010,12 @@ function radio_station_override_show_metabox() { echo ' style="display:none;"'; } echo '>' . esc_html( __( 'Use Host assignment box below.', 'radio-station' ) ) . '' . PHP_EOL; - echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '' . PHP_EOL; // --- producers --- @@ -2856,19 +3024,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_producer_list'] ) && $linked_fields['show_producer_list'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- website --- @@ -2889,7 +3057,7 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_link'] ) && $linked_fields['show_link'] ) { echo ' style="display:none;"'; } - echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; // --- email --- @@ -2898,19 +3066,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_email'] ) && $linked_fields['show_email'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- phone --- @@ -2919,19 +3087,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_phone'] ) && $linked_fields['show_phone'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- audio file --- @@ -2940,19 +3108,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_file'] ) && $linked_fields['show_file'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- disable download --- @@ -2961,20 +3129,20 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_download'] ) && $linked_fields['show_download'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; // --- patreon --- @@ -2983,19 +3151,19 @@ function radio_station_override_show_metabox() { if ( isset( $linked_fields['show_patreon'] ) && $linked_fields['show_patreon'] ) { echo ' checked="checked"'; } - echo '> '; + echo '> ' . PHP_EOL; echo '' . PHP_EOL; do_action( 'radio_station_show_fields', $post_id, 'override' ); @@ -3032,6 +3200,7 @@ function radio_station_override_show_metabox() { } $class = implode( ' ', $classes ); + // --- open inside metabox echo '
    ' . PHP_EOL; + // --- inside metabox contents --- $widget_title = $metabox['title']; echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . PHP_EOL; echo '
    ' . PHP_EOL; call_user_func( $metabox['callback'] ); echo '
    ' . PHP_EOL; + + // --- close inside metabox --- echo '
    ' . PHP_EOL; $i ++; } - echo ''; + echo '' . PHP_EOL; + + // --- input list field styles --- + // 2.3.3.9: add styles for table to list conversion + $css = '.input-label, .input-field, .input-helper {display: inline-block;} + .input-field {max-width: 120px;} + .input-field, .input-helper {margin-left: 20px;} + ' . PHP_EOL; // --- output inside metabox styles --- - echo ""; + #show-inside-metaboxes .postbox select {max-width: 200px;}'; + + // 2.3.3.9: filter + $css = apply_filters( 'radio_station_override_edit_styles', $css ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo ''; // --- get override show script --- $js = radio_station_override_show_script(); @@ -3177,39 +3361,30 @@ function radio_station_schedule_override_metabox() { // --- override save/add buttons --- // 2.3.3.9: added for AJAX save and multiple overrides - echo '
    ' . PHP_EOL; + echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . esc_html( __( 'Override Data', 'radio-station' ) ) . '' . PHP_EOL; + echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo ' ' . esc_html( __( 'Check to Disable', 'radio-station' ) ) . '' . PHP_EOL; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ''; + echo '> https://patreon.com/' . PHP_EOL; + echo '' . PHP_EOL; echo '' . PHP_EOL; echo '
    '; - echo ''; - echo ''; + // 2.3.3.9: change override-table-buttons to override-buttons + echo '
    '; + echo '' . PHP_EOL; // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { - echo ''; + echo '' . PHP_EOL; } - echo ''; - echo ''; - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    '; - echo ''; - echo ''; - echo ''; - echo '
    '; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- override list styles --- // 2.3.0: added datepicker z-index to fix conflict with editor buttons // 2.3.3.9: apply class styles to override list - echo ""; + // 2.3.3.9: moved styles to separate function + $css = radio_station_overrides_list_styles(); + echo ''; // --- enqueue datepicker script and styles --- // 2.3.0: enqueue for override post type only @@ -3235,6 +3410,31 @@ function radio_station_schedule_override_metabox() { } +// --------------------- +// Overrides List Styles +// --------------------- +// 2.3.3.9: moved to separate function +function radio_station_overrides_list_styles() { + + // 2.3.3.9: change override-table-buttons to override-buttons + $css = "body.post-type-override #ui-datepicker-div {z-index: 1001 !important;} + .override-list {list-style: none;} + .override-list .override-item {display: inline-block; margin-left: 20px;} + .override-list .override-item.first-item {margin-left: 10px;} + .override-list .override-item.last-item {margin-right: 10px;} + .override-date {width: 100px; text-align: center;} + .override-select {min-width:35px;} + .override-buttons .overrides-clear, .override-buttons .overrides-save, .override-buttons .override-add { + cursor: pointer; display:block; width: 150px; padding: 8px; text-align: center; line-height: 1em;} + #overrides-saving-message, #overrides-saved-message { + background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}" . PHP_EOL; + + // 2.3.3.9: added override edit styles filter + $css = apply_filters( 'radio_station_override_list_edit_styles', $css ); + return $css; +} + + // -------------------- // Overrides Table List // -------------------- @@ -3267,7 +3467,7 @@ function radio_station_overrides_table( $post_id ) { } } - echo 'Current Overrides: ' . print_r( $overrides, true ) . ''; + echo 'Current Overrides: ' . print_r( $overrides, true ) . '' . PHP_EOL; if ( !$overrides ) { @@ -3350,165 +3550,166 @@ function radio_station_overrides_table( $post_id ) { // 2.3.3.9: use override shift ID for override wrapper ID --- $id = $override['id']; - $list .= '
    '; - $list .= '
      '; + $list .= '
      ' . PHP_EOL; + + $list .= '
        ' . PHP_EOL; // 2.3.3.9: add hidden input for unique override time ID - $list .= ''; + $list .= '' . PHP_EOL; // --- Override (Start) Date --- - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; - $list .= esc_html( __( 'Start Date', 'radio-station' ) ) . ':'; + $list .= ':' . PHP_EOL; $date = ( !empty( $override['date'] ) ) ? trim( $override['date'] ) : ''; - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; - $list .= '
      • '; + $list .= '' . PHP_EOL; // --- Override Start Time --- - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; // --- start time label -- - $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ':'; + $list .= ':' . PHP_EOL; // --- start hour --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; for ( $j = 1; $j <= 12; $j ++ ) { - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- start minute --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; for ( $j = 0; $j < 60; $j++ ) { $min = ( $j < 10 ) ? '0' . $j : $j; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- start meridian --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; - $list .= '
      • '; + $list .= '' . PHP_EOL; // --- Override End Time --- // 2.3.4: add common end minutes to top of options - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; // --- end time label --- - $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ':'; + $list .= ':' . PHP_EOL; // --- end hour --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; for ( $j = 1; $j <= 12; $j ++ ) { - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- end minutes --- - $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; for ( $j = 0; $j < 60; $j++ ) { $min = ( $j < 10 ) ? '0' . $j : $j; - $list .= ''; + $list .= '' . PHP_EOL; } - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- end meridian --- - $list .= ''; - $list .= ''; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; - $list .= '
      • '; + $list .= '' . PHP_EOL; // - multiday switch - // 2.3.3.9: added multiday checkbox prototype - $list .= ''; + $list .= '>' . PHP_EOL; + $list .= ': ' . PHP_EOL; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; */ // --- disabled --- // 2.3.3.9: added disabled override checkox - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; $list .= '' . PHP_EOL; $list .= ''; - $list .= '
      • '; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; // --- remove shift icon --- - $list .= '
      • '; + $list .= '
      • ' . PHP_EOL; $title = __( 'Remove Override', 'radio-station' ); - $list .= ''; - $list .= '
      • '; + $list .= '' . PHP_EOL; + $list .= '' . PHP_EOL; - $list .= '
      '; - $list .= '
      '; + $list .= '
    ' . PHP_EOL; + $list .= '
    ' . PHP_EOL; } // --- new overrides div --- - $list .= '
    '; + $list .= '
    ' . PHP_EOL; // --- set table output to return --- $table = array( @@ -3832,7 +4033,12 @@ function radio_station_override_save_data( $post_id ) { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_override_save' != $_REQUEST['action'] ) ) { + if ( !isset( $_REQUEST['action'] ) ) { + return; + } + // 2.3.3.9: match to whitelisted actions + $actions = array( 'radio_station_override_save', 'radio_station_add_override_time' ); + if ( !in_array( $_REQUEST['action'], $actions ) ) { return; } @@ -3977,11 +4183,22 @@ function radio_station_override_save_data( $post_id ) { if ( isset( $_POST['show_override_nonce'] ) && wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { // --- get the show override data --- - $show_sched = $_POST['show_sched']; + $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); + if ( isset( $_POST['new_override'] ) ) { + $new_shift = $_POST['new_override']; + $new_shift['id'] = radio_station_unique_shift_id(); + $show_sched = $current_scheds; + $show_sched[] = $new_shift; + $_POST['show_sched'] = $show_sched; + } else { + $show_sched = $_POST['show_sched']; + } + $new_scheds = array(); if ( is_array( $show_sched ) ) { // 2.3.3.9: loop to save possible multiple override dates/times --- - $new_scheds = array(); + print_r( $show_sched ); + foreach ( $show_sched as $sched ) { // --- get/set current schedule for merging --- @@ -4079,7 +4296,6 @@ function radio_station_override_save_data( $post_id ) { // --- check if override schedule has changed --- $sched_changed = true; - $current_scheds = get_post_meta( $post_id, 'show_override_sched', true ); if ( array_key_exists( 'date', $current_scheds ) ) { $current_scheds['id'] = radio_station_unique_shift_id(); $current_scheds = array( $current_scheds ); @@ -4176,67 +4392,83 @@ function radio_station_override_save_data( $post_id ) { // --- update overrides table when AJAX saving --- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - if ( isset( $_POST['action'] ) && ( 'radio_station_override_save' == $_POST['action'] ) ) { - - // --- (hidden) debug information --- - echo "Previous Overrides: " . print_r( $current_scheds, true ) . PHP_EOL; - echo "New Overrides: " . print_r( $new_scheds, true ) . PHP_EOL; - - // --- display shifts saved message --- - // 2.3.3.9: fade out overrides saved message - $show_override_nonce = wp_create_nonce( 'radio-station' ); - echo ""; - // 2.3.3.9: added check if override schedule changed - if ( $sched_changed ) { + // 2.3.3.9: remove duplicate action check - // --- output new show shifts list --- - echo '
    '; - $table = radio_station_overrides_table( $post_id ); - // if ( $table['conflicts'] ) { - // radio_station_overrides_conflict_message(); - // } + // --- (hidden) debug information --- + echo "Previous Overrides: " . print_r( $current_scheds, true ) . PHP_EOL; + echo "New Overrides: " . print_r( $new_scheds, true ) . PHP_EOL; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $table['list']; + // --- display shifts saved message --- + // 2.3.3.9: fade out overrides saved message + $show_override_nonce = wp_create_nonce( 'radio-station' ); + echo ""; - echo '
    '; + // 2.3.3.9: added check if override schedule changed + if ( $sched_changed ) { - echo '
    '; + // --- output new show shifts list --- + echo '
    '; + $table = radio_station_overrides_table( $post_id ); + // if ( $table['conflicts'] ) { + // radio_station_overrides_conflict_message(); + // } + + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; + + // echo '
    '; + echo '
    '; - // --- refresh show shifts list --- - echo ""; + // --- refresh show shifts list --- + $js = ""; + // --- reload the current schedule view --- + $js .= "console.log('? OH YEAH ?');"; + $js .= "parent.radio_load_schedule(false,false);" . PHP_EOL; - // --- alert on conflicts --- - // if ( $table['conflicts'] ) { - // $warning = __( 'Warning! Overide conflicts detected.', 'radio-station' ); - // echo ""; - // } + // $js .= "if (parent.window.onbeforeunloadset) { + // parent.window.onbeforeunload = parent.storedonbeforeunload; + // parent.window.onbeforeunloadset = false; + // }"; + + // --- alert on conflicts --- + // if ( $table['conflicts'] ) { + // $warning = __( 'Warning! Override conflicts detected.', 'radio-station' ); + // $js .= "alert('" . esc_js( $warning ) . "');"; + // } - // --- re-initialize datepicker fields in parent window --- - $js .= "parent.jQuery('.override-date').each(function() { - jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); - });" . PHP_EOL; - - // --- reload the current schedule view --- - echo ""; + // --- re-initialize datepicker fields in parent window --- + $js .= "parent.jQuery('.override-date').each(function() { + jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); + });" . PHP_EOL; + + // --- output the scripts --- + echo ''; + // 2.3.3.9: trigger action for save or add override time + if ( 'radio_station_override_save' == $_REQUEST['action'] ) { + do_action( 'radio_station_override_save_time', $shift_id ); + } elseif ( 'radio_station_add_override_time' == $_REQUEST['action'] ) { + do_action( 'radio_station_override_add_time' ); } + } - exit; + // --- return early when adding single override --- + // 2.3.3.9: added for single override action + if ( 'radio_station_add_override_time' == $_REQUEST['action'] ) { + return; } + + exit; } } @@ -4301,12 +4533,12 @@ function radio_station_override_column_data( $column, $post_id ) { foreach ( $overrides as $i => $override ) { if ( count( $overrides ) > 1 ) { - echo '' . ( $i + 1 ) . ': '; + echo '' . ( $i + 1 ) . ': ' . PHP_EOL; } // 2.3.3.9: maybe display disabled for this override time if ( isset( $override['disabled'] ) && ( 'yes' == $override['disabled'] ) ) { - echo '[Disabled]
    '; + echo '[Disabled]
    ' . PHP_EOL; } // 2.3.2: no need to apply timezone conversions here @@ -4315,22 +4547,22 @@ function radio_station_override_column_data( $column, $post_id ) { $month = radio_station_translate_month( $month, true ); $weekday = date( 'l', $datetime ); $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); - echo '
    '; + echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ) . PHP_EOL; + echo '
    ' . PHP_EOL; // 2.3.3.9: merge override times into this columns // 2.3.3.9: display according to selected time format $time_format = radio_station_get_setting( 'clock_time_format' ); if ( 12 == $time_format ) { echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . esc_html( $override['start_meridian'] ); - echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ); + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ) . PHP_EOL; } elseif ( 24 == $time_format ) { $start_hour = radio_station_convert_hour( $override['start_hour'] . ' ' . $override['start_meridian'] ); $end_hour = radio_station_convert_hour( $override['end_hour'] . ' ' . $override['end_meridian'] ); echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ); - echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ); + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . PHP_EOL; } - echo '
    '; + echo '
    ' . PHP_EOL; } } elseif ( 'shows_affected' == $column ) { @@ -4423,13 +4655,13 @@ function radio_station_override_column_data( $column, $post_id ) { if ( !isset( $last_show ) || ( $last_show != $show_shift['post_id'] ) ) { $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); if ( 'on' != $active ) { - $affected .= "[" . esc_html( __( 'Inactive', 'radio-station' ) ) . "] "; + $affected .= '[' . esc_html( __( 'Inactive', 'radio-station' ) ) . '] '; } - $affected .= '' . $show_shift['post_title'] . "
    "; + $affected .= '' . $show_shift['post_title'] . '
    '; } if ( isset( $shift['disabled'] ) && $shift['disabled'] ) { - $affected .= "[" . esc_html( __( 'Disabled', 'radio-station' ) ) . "] "; + $affected .= '[' . esc_html( __( 'Disabled', 'radio-station' ) ) . '] '; } $affected .= radio_station_translate_weekday( $shift['day'] ) . ' '; // 2.3.3.9: display according to time format setting @@ -4443,7 +4675,7 @@ function radio_station_override_column_data( $column, $post_id ) { $affected .= esc_html( $start_hour ) . ':' . esc_html( $shift['start_min'] ); $affected .= ' - ' . esc_html( $end_hour ) . ':' . esc_html( $shift['end_min'] ); } - $affected .= "
    "; + $affected .= '
    '; // 2.3.3.9: store affected shows by override index $affected_shows[$i] = $affected; @@ -4507,7 +4739,7 @@ function radio_station_override_column_data( $column, $post_id ) { $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); $thumbnail_url = apply_filters( 'radio_station_show_avatar', $thumbnail_url, $post_id ); if ( $thumbnail_url ) { - echo "
    " . esc_attr( __(
    "; + echo '
    ' . esc_attr( __( 'Override Logo', 'radio-station' ) ) . '
    ' . PHP_EOL; } } } @@ -4581,16 +4813,16 @@ function radio_station_override_date_filter( $post_type, $which ) { $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; // --- month override selector --- - echo ''; - echo '' . PHP_EOL; + echo '' . PHP_EOL; if ( count( $months ) > 0 ) { foreach ( $months as $key => $data ) { $label = $wp_locale->get_month( $data['month'] ) . ' ' . $data['year']; - echo "\n"; + echo '' . PHP_EOL; } } - echo ''; + echo '' . PHP_EOL; } @@ -4611,13 +4843,13 @@ function radio_station_override_past_future_filter( $post_type, $which ) { // --- past / future override selector --- // 2.3.3.5: added option for today filtering - echo ''; - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; } @@ -4634,6 +4866,7 @@ function radio_station_override_past_future_filter( $post_type, $which ) { // Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 add_action( 'add_meta_boxes', 'radio_station_add_playlist_metabox' ); function radio_station_add_playlist_metabox() { + // 2.2.2: change context to show at top of edit screen // 2.3.2: filter top metabox position $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'playlist' ); @@ -4657,10 +4890,6 @@ function radio_station_playlist_metabox() { // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'playlist_tracks_nonce' ); - // --- get the saved meta as an array --- - // 2.3.2: set single argument to true - $entries = get_post_meta( $post->ID, 'playlist', true ); - // --- set button titles --- // 2.3.2: added titles for button icons $move_up_title = __( 'Move Track Up', 'radio-station' ); @@ -4669,38 +4898,38 @@ function radio_station_playlist_metabox() { $remove_title = __( 'Remove Track', 'radio-station' ); // 2.3.3.9: move meta_inner ID to class - echo '
    '; + echo '
    ' . PHP_EOL; // 2.3.2: separate track list table - echo radio_station_playlist_track_table( $entries ); + echo radio_station_playlist_track_table( $post->ID ); // --- track save/add buttons --- // 2.3.2: change track save from button-primary to button-secondary // 2.3.2: added playlist AJAX save button (for existing posts only) // 2.3.2: added playlist tracks clear button // 2.3.2: added table and track saved message - echo '
    '; - echo ''; - echo ''; + echo ''; + echo '' . PHP_EOL; // 2.3.3.9: change to single cell spanning 3 columns - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; if ( 'add' != $current_screen->action ) { - echo ''; + echo '' . PHP_EOL; } - echo ''; - echo ''; - echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    '; - echo ''; - echo ''; - echo ''; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '
    ' . PHP_EOL; - echo '
    '; + echo '
    ' . PHP_EOL; // --- move new tracks message --- // 2.3.2: added new track move message - echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    '; + echo '
    ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
    ' . PHP_EOL; // --- clear all tracks function --- $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); @@ -4745,13 +4974,10 @@ function radio_station_playlist_metabox() { m = n - 1; jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ - } - if (updown == 'down') { + } else if (updown == 'down') { m = n + 1; jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ } /* reset track classes */ radio_track_classes(); @@ -4789,10 +5015,9 @@ function radio_station_playlist_metabox() { // --- reset first and last track classes --- $js .= "function radio_track_classes() { - jQuery('.track-rowa, .track-rowb, .track-rowc').removeClass('first-track').removeClass('last-track'); + jQuery('.track-rowa, .track-rowb').removeClass('first-track').removeClass('last-track'); jQuery('.track-rowa').first().addClass('first-track'); jQuery('.track-rowa').last().addClass('last-track'); jQuery('.track-rowb').first().addClass('first-track'); jQuery('.track-rowb').last().addClass('last-track'); - /* jQuery('.track-rowc').first().addClass('first-track'); jQuery('.track-rowc').last().addClass('last-track'); */ }" . PHP_EOL; // --- add track function --- @@ -4801,6 +5026,7 @@ function radio_station_playlist_metabox() { // 2.3.2: added track move arrows // 2.3.2: added first and last row classes // 2.3.2: set to standalone onclick function + // 2.3.3.9: added track length select inputs $js .= "function radio_track_add() { if (trackcount == 1) {classes = 'first-track last-track';} else {classes = 'last-track';} output = ''; @@ -4811,7 +5037,15 @@ function radio_station_playlist_metabox() { output += ''; output += ''; output += ''; - output += '" . esc_js( __( 'Comments', 'radio-station' ) ) . ": '; + output += '
    " . esc_js( __( 'Length', 'radio-station' ) ) . ": '; + output += 'm : '; + output += 's
    '; + output += '
    " . esc_js( __( 'Comments', 'radio-station' ) ) . ":
    '; output += '
    " . esc_js( __( 'New', 'radio-station' ) ) . ":
    '; output += '
    '; output += '
    " . esc_js( __( 'Status', 'radio-station' ) ) . ":
    '; @@ -4828,9 +5062,6 @@ function radio_station_playlist_metabox() { output += ''; output += ''; - /* output += ''; - output += ''; */ - jQuery('#track-table').append(output); trackcount++; radio_track_classes(); @@ -4875,9 +5106,9 @@ function radio_station_playlist_metabox() { // 2.3.2: reset first and last classes on remove // 2.3.2: set to standalone onclick function $js .= "function radio_track_remove(id) { - jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb, #track-'+id+'-rowc').remove(); - radio_track_classes(); trackcount--; - + jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb').remove(); + radio_track_classes(); + trackcount--; /* renumber track count */ var tcount = 1; jQuery('.track-rowa').each(function() { @@ -4900,9 +5131,15 @@ function radio_station_playlist_metabox() { // 2.3.2: added track move arrow styles // 2.3.2: added table buttons styling // 2.3.2: added track save message styling - echo ''; + background-color: lightYellow; border: 1px solid #E6DB55; margin-top: 10px; font-weight: bold; max-width: 300px; padding: 5px 0;}' . PHP_EOL; + + // --- filter and output --- + // 2.3.3.9: added track list style filter + $css = apply_filters( 'radio_station_tracks_list_styles', $css ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo ''; // --- close meta inner --- echo '
    '; @@ -4926,16 +5168,21 @@ function radio_station_playlist_metabox() { // Track List Table // ---------------- // 2.3.2: separated tracklist table (for AJAX) -function radio_station_playlist_track_table( $entries ) { +function radio_station_playlist_track_table( $playlist_id ) { + + // --- get the saved meta as an array --- + // 2.3.2: set single argument to true + $entries = get_post_meta( $playlist_id, 'playlist', true ); // --- open track table --- - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; + // TODO: convert track table to list ? + echo '
    ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; // --- set button titles --- // 2.3.2: added titles for icon buttons @@ -4951,64 +5198,96 @@ function radio_station_playlist_track_table( $entries ) { foreach ( $entries as $track ) { if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_minutes'] ) || isset( $track['playlist_entry_seconds'] ) || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) || isset( $track['playlist_entry_status'] ) ) { - // --- track row a --- + // 2.3.3.9: set any unset keys to empty + $entry_keys = array( + 'playlist_entry_artist', + 'playlist_entry_song', + 'playlist_entry_album', + 'playlist_entry_label', + 'playlist_entry_minutes', + 'playlist_entry_seconds', + 'playlist_entry_comments', + 'playlist_entry_new', + 'playlist_entry_status', + ); + foreach ( $entry_keys as $entry_key ) { + if ( !isset( $track[$entry_key] ) ) { + $track[$entry_key] = ''; + } + } + + // --- track row A --- $class = ''; if ( 1 == $c ) { $class = 'first-track'; } elseif ( $c == count( $entries ) ) { $class = 'last-track'; } - echo ''; + echo '' . PHP_EOL; // --- track count --- - echo ''; + echo '' . PHP_EOL; // --- track entry inputs --- - echo ''; - echo ''; - echo ''; - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; echo ''; - // --- track row b --- - echo ''; + // --- track row B --- + echo '' . PHP_EOL; + + // --- track length --- + // 2.3.3.9: added track length inputs + // 2.3.3.9: add unit translation wrappers with notes + echo ''; + echo '
    ' . esc_html( __( 'Comments', 'radio-station' ) ) . ': ' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- track meta --- - echo ''; + echo '
    ' . PHP_EOL; + echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; // 2.3.2: added move track arrows - echo ''; - echo ''; - - // --- track row c --- - // TODO: add track time / start / end input fields ? - // echo ''; - // echo ''; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '' . PHP_EOL; $c ++; } @@ -5045,13 +5324,14 @@ function radio_station_playlist_show_metabox() { // --- check that we have at least one show --- // 2.3.0: moved up to check for any shows + // 2.3.3.9: allow assignment to draft ahows $args = array( 'numberposts' => - 1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', + 'post_status' => array( 'publish', 'draft' ) ); $shows = get_posts( $args ); if ( count( $shows ) > 0 ) { @@ -5093,13 +5373,14 @@ function radio_station_playlist_show_metabox() { } } + // 2.3.3.9: allow assignment to draft shows $args = array( 'numberposts' => - 1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'aSC', 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', + 'post_status' => array( 'publish', 'draft' ), 'include' => implode( ',', $allowed_shows ), ); @@ -5107,27 +5388,27 @@ function radio_station_playlist_show_metabox() { } // 2.3.3.9: move meta_inner ID to class - echo '
    '; + echo '
    ' . PHP_EOL; if ( !$have_shows ) { - echo esc_html( __( 'No Shows were found.', 'radio-station' ) ); + echo esc_html( __( 'No Shows were found.', 'radio-station' ) ) . PHP_EOL; } else { if ( count( $shows ) < 1 ) { - echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ); + echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ) . PHP_EOL; } else { // --- add nonce field for verification --- wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); // --- select show to assign playlist to --- $current = get_post_meta( $post->ID, 'playlist_show_id', true ); - echo '' . PHP_EOL; + echo '' . PHP_EOL; foreach ( $shows as $show ) { - echo ''; + echo '' . PHP_EOL; } - echo ''; + echo '' . PHP_EOL; } } - echo '
    '; + echo '
    ' . PHP_EOL; } // -------------------- @@ -5158,7 +5439,7 @@ function radio_station_playlist_save_data( $post_id ) { } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { - $error = __( 'Failed. Playlist ID provided.', 'radio-station' ); + $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); } else { $post_id = absint( $_POST['playlist_id'] ); $post = get_post( $post_id ); @@ -5190,18 +5471,26 @@ function radio_station_playlist_save_data( $post_id ) { $prev_playlist = get_post_meta( $post_id, 'playlist', true ); $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); - // move songs that are still queued to the end of the list so that order is maintained - foreach ( $playlist as $i => $song ) { - // 2.3.2: move songs marked as new to the end instead of queued - // if ( 'queued' === $song['playlist_entry_status'] ) { - if ( $song['playlist_entry_new'] ) { - // 2.3.2: unset before adding to maintain (now ordered) track count - // 2.3.2: unset new flag from track record now it has been moved - unset( $playlist[$i] ); - unset( $song['playlist_entry_new'] ); - $playlist[] = $song; + if ( count( $playlist ) > 0 ) { + // move songs that are still queued to the end of the list so that order is maintained + foreach ( $playlist as $i => $entry ) { + + // 2.3.3.9: sanitize entry key values + $entry = radio_station_sanitize_playlist_entry( $entry ); + + // 2.3.2: move songs marked as new to the end instead of queued + // if ( 'queued' === $song['playlist_entry_status'] ) { + if ( $entry['playlist_entry_new'] ) { + // 2.3.2: unset before adding to maintain (now ordered) track count + // 2.3.2: unset new flag from track record now it has been moved + unset( $playlist[$i] ); + unset( $entry['playlist_entry_new'] ); + $playlist[] = $entry; + } } } + + // --- check if playlist is changed --- if ( $prev_playlist != $playlist ) { update_post_meta( $post_id, 'playlist', $playlist ); $playlist_changed = true; @@ -5261,8 +5550,7 @@ function radio_station_playlist_save_data( $post_id ) { // --- refresh track list table --- // 2.3.3.9: added check if playlist changed if ( $playlist_changed ) { - $entries = get_post_meta( $post_id, 'playlist', true ); - echo radio_station_playlist_track_table( $entries ); + echo radio_station_playlist_track_table( $post_id ); echo ""; } @@ -5307,28 +5595,28 @@ function radio_station_playlist_column_data( $column, $post_id ) { if ( 'show' == $column ) { $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); $post = get_post( $show_id ); - echo "" . esc_html( $post->post_title ) . ""; + echo '' . esc_html( $post->post_title ) . '' . PHP_EOL; } elseif ( 'trackcount' == $column ) { - echo count( $tracks ); + echo count( $tracks ) . PHP_EOL; } elseif ( 'tracklist' == $column ) { - echo ''; - echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . "
    "; - echo '
    ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '
    ' . esc_html( $c ) . '' . esc_html( $c ) . '
    ' . esc_html( __( 'Length', 'radio-station' ) ) . ': ' . PHP_EOL; + echo ''; + echo _x( 'm', 'minutes unit', 'radio-station' ) . ' : ' . PHP_EOL; + echo '' . _x( 's', 'seconds unit', 'radio-station' ) . '
    ' . PHP_EOL; // --- track comments --- - echo '
    ' . esc_html__( 'Comments', 'radio-station' ) . ' '; - echo ''; - echo '
    ' . esc_html( __( 'New', 'radio-station' ) ) . ':
    '; + echo '
    ' . PHP_EOL; + echo '
    ' . esc_html( __( 'New', 'radio-station' ) ) . ':
    ' . PHP_EOL; // 2.3.2: remove new value checking as now used and cleared on save // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; // ' . checked( $track['playlist_entry_new'] ) . ' - echo '
    '; - echo '
    ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
    '; - echo '
    '; - echo '
    ' . esc_html( __( 'Move', 'radio-station') ) . ':
    '; - echo '
    '; - echo '
    '; + echo '
    ' . PHP_EOL; + echo '
    ' . esc_html( __( 'Move', 'radio-station') ) . ':
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; + echo '
    ' . PHP_EOL; // --- remove track button --- - echo '
    '; - echo '
    '; - echo '
    '; - echo ''; - echo ''; - echo ''; - echo ''; + echo '' . PHP_EOL; + echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . '
    ' . PHP_EOL; + echo '
    #' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Status', 'radio-station' ) ) . '
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; foreach ( $tracks as $i => $track ) { - echo ''; - echo ''; - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; $status = $track['playlist_entry_status']; $status = strtoupper( substr( $status, 0, 1 ) ) . substr( $status, 1, strlen( $status ) ); - echo ''; - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; } - echo '
    #' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Status', 'radio-station' ) ) . '
    ' . $i . '' . esc_html( $track['playlist_entry_song'] ) . '' . esc_html( $track['playlist_entry_artist'] ) . '
    ' . esc_html( $i ) . '' . esc_html( $track['playlist_entry_song'] ) . '' . esc_html( $track['playlist_entry_artist'] ) . '' . esc_html( $status ) . '
    ' . esc_html( $status ) . '
    '; + echo '
    ' . PHP_EOL; } } @@ -5343,11 +5631,15 @@ function radio_station_playlist_column_styles() { } // --- playlist list styles --- - echo ""; + .tracklist-table td {padding: 0px 10px;}"; + + // 2.3.3.9: add playlist list styles filter + $css = apply_filters( 'radio_station_playlist_list_styles', $css ); + echo ''; // --- expand/collapse tracklist data --- $js = "function showhidetracklist(postid) { @@ -5358,6 +5650,8 @@ function radio_station_playlist_column_styles() { // --- enqueue script inline --- // 2.3.0: enqueue instead of echo + // 2.3.3.9: filter playlist list script + $js = apply_filters( 'radio_station_playlist_list_script', $css ); wp_add_inline_script( 'radio-station-admin', $js ); } @@ -5439,15 +5733,15 @@ function radio_station_post_show_metabox() { } // 2.3.3.9: move meta_inner ID to class - echo '
    '; + echo '
    ' . PHP_EOL; if ( count( $shows ) > 0 ) { // --- select related show input --- // 2.2.3.4: allow for multiple selections // 2.3.3.9: use metakey for post type - echo '' . PHP_EOL; + echo '' . PHP_EOL; // --- loop shows for selection options --- // 2.3.3.4: check for multiple selections @@ -5472,14 +5766,14 @@ function radio_station_post_show_metabox() { if ( 'draft' == $show->post_status ) { echo ' (' . esc_html( __( 'Draft', 'radio-station' ) ) . ')'; } - echo ''; + echo '' . PHP_EOL; } - echo ''; + echo '' . PHP_EOL; } else { // --- no shows message --- - echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); + echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ) . PHP_EOL; } - echo '
    '; + echo '
    ' . PHP_EOL; // --- related shows post box styles --- // 2.3.3.6: add style for pre-selected option @@ -5594,24 +5888,24 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { } // --- get all shows --- + // 2.3.3.9: allow selection shows with draft status $args = array( 'numberposts' => - 1, 'offset' => 0, 'orderby' => 'post_title', 'order' => 'ASC', 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', // ??? + 'post_status' => array( 'publish', 'draft' ), ); $shows = get_posts( $args ); - echo '' . PHP_EOL; // --- related shows post box styles --- // 2.3.3.6: add style for pre-selected option - echo ""; + echo "" . PHP_EOL; // 2.3.3.6: restore stored post object $post = $stored_post; @@ -5689,20 +5983,20 @@ function radio_station_post_column_data( $column, $post_id ) { // 2.3.3.6: only link to Shows user can edit $post = $show; if ( current_user_can( 'edit_shows' ) ) { - echo ''; + echo '' . PHP_EOL; } else { // 2.3.3.6: set disabled (uneditable) data $disabled[] = $show_id; } echo esc_html( $show->post_title ) . '
    '; if ( current_user_can( 'edit_shows' ) ) { - echo '
    '; + echo '' . PHP_EOL; } } } } - echo ''; - echo ''; + echo '' . PHP_EOL; + echo '' . PHP_EOL; // --- restore global post object --- $post = $stored_post; @@ -5888,35 +6182,33 @@ function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids // 2.3.3.4: add notice for bulk edit result add_action( 'admin_notices', 'radio_station_posts_bulk_edit_notice' ); function radio_station_posts_bulk_edit_notice() { + $updated = $failed = false; - if ( isset( $_REQUEST['radio_station_related_show_updated'] ) ) { - $updated_ = intval( $_REQUEST['radio_station_related_show_updated'] ); - } - if ( isset( $_REQUEST['radio_station_related_show_failed'] ) ) { - $failed = intval( $_REQUEST['radio_station_related_show_failed'] ); + if ( isset( $_REQUEST['radio_station_related_show_updated'] ) ) { + $updated_ = intval( $_REQUEST['radio_station_related_show_updated'] ); + } + if ( isset( $_REQUEST['radio_station_related_show_failed'] ) ) { + $failed = intval( $_REQUEST['radio_station_related_show_failed'] ); } if ( $updated || $failed ) { - echo '
    '; + $class = ( $updated_products_count > 0 ) ? 'updated' : 'error'; + echo '
    ' . PHP_EOL; if ( $updated > 0 ) { // --- number of posts updated message --- - echo '

    '; - $message = __( 'Updated Related Shows for %d Posts.', 'radio_station' ); - $message = sprintf( $message, $updated ); - echo esc_html( $message ); - echo '

    '; + $message = __( 'Updated Related Shows for %d Posts.', 'radio_station' ); + $message = sprintf( $message, $updated ); + echo '

    ' . esc_html( $message ) . '

    ' . PHP_EOL; } if ( $failed > 0 ) { // --- number of posts failed messsage --- - echo '

    '; - $message = __( 'Failed to Update Related Shows for %d Posts.', 'radio-station' ); - $message = sprintf( $message, $failed ); - esc_html( $message ); - echo '

    '; + $message = __( 'Failed to Update Related Shows for %d Posts.', 'radio-station' ); + $message = sprintf( $message, $failed ); + echo '

    ' . esc_html( $message ) . '

    ' . PHP_EOL; } - echo '
    '; + echo '
    ' . PHP_EOL; } } @@ -5933,10 +6225,13 @@ function radio_station_posts_list_styles() { // --- post list styles --- // 2.3.3.9: fix to column-show type (oclumn-show) - echo ""; + .bulkactions .column-show .title {display: none;}"; + + // 2.3.3.9: added posts list styles filter + $css = apply_filters( 'radio_station_posts_list_styles', $css ); + echo ''; } @@ -6131,26 +6426,41 @@ function radio_station_columns_query_filter( $query ) { add_action( 'wp_ajax_nopriv_radio_station_playlist_save_tracks', 'radio_station_relogin_message' ); function radio_station_relogin_message() { + // --- interim login thickbox --- + // 2.3.3.9: maybe close existing thickbox + $js = "if (parent && parent.tb_remove) {try {parent.tb_remove();} catch(e) {} }" . PHP_EOL; + // 2.3.3.9: trigger WP interim login screen thickbox + $js .= "if (parent) {parent.jQuery(document).trigger('heartbeat-tick.wp-auth-check', [{'wp-auth-check': false}]);}" . PHP_EOL; + // 2.3.3.9: fix to playlist action name prefix // 2.3.3.9: added support for override times // 2.3.3.9: add check for single shift action + $action = $_REQUEST['action']; $save_shift_actions = array( 'radio_station_show_save_shift', 'radio_station_show_save_shifts' ); - if ( in_array( $_REQUEST['action'], $save_shift_actions ) ) { + if ( in_array( $action, $save_shift_actions ) ) { $type = 'shift'; - } elseif ( 'radio_station_override_save' == $_REQUEST['action'] ) { + } elseif ( 'radio_station_override_save' == $action ) { $type = 'override'; - } elseif ( 'radio_station_playlist_save_tracks' == $_REQUEST['action'] ) { + } elseif ( 'radio_station_playlist_save_tracks' == $action ) { $type = 'track'; } - // --- send relogin message --- - $error = __( 'Failed. Please relogin and try again.', 'radio-station' ); - echo ""; + // TODO: maybe cache the posted data so it can be restored ? + // --- send relogin message --- + if ( isset( $type ) ) { + $error = __( 'Failed. Please relogin and try again.', 'radio-station' ); + $js .- "parent.document.getElementById('" . $type . "s-saving-message').style.display = 'none'; + parent.document.getElementById('" . $type . "s-error-message').style.display = ''; + parent.document.getElementById('" . $type . "s-error-message').innerHTML = '" . esc_js( $error ) . "'; + form = parent.document.getElementById('" . $type . "-save-form'); + form.parentNode.removeChild(form);" . PHP_EOL; + } + + // --- filter and output --- + $js = apply_filters( 'radio_station_relogin_script', $js ); + echo ''; exit; + } diff --git a/includes/post-types.php b/includes/post-types.php index fea44af..c897b3b 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -184,20 +184,21 @@ function radio_station_create_post_types() { // (so that rewrite rules and query vars are added for it) $ui = apply_filters( 'radio_station_host_interface', false ); $post_type = array( + // 2.3.3.9: fix to labels for template output 'labels' => array( - 'name' => __( 'Host Profiles', 'radio-station' ), - 'singular_name' => __( 'Host Profile', 'radio-station' ), - 'add_new' => __( 'New Host Profile', 'radio-station' ), + 'name' => __( 'Hosts', 'radio-station' ), + 'singular_name' => __( 'Host', 'radio-station' ), + 'add_new' => __( 'Add New Host Profile', 'radio-station' ), 'add_new_item' => __( 'Add Host Profile', 'radio-station' ), 'edit_item' => __( 'Edit Host Profile', 'radio-station' ), 'new_item' => __( 'New Host Profile', 'radio-station' ), 'view_item' => __( 'View Host Profile', 'radio-station' ), 'archive_title' => __( 'Show Hosts', 'radio-station' ), // 2.3.2: added missing post type labels - 'search_items' => __( 'Search Hosts', 'radio-station' ), - 'not_found' => __( 'No Hosts found', 'radio-station' ), - 'not_found_in_trash' => __( 'No Hosts found in Trash', 'radio-station' ), - 'all_items' => __( 'All Hosts', 'radio-station' ), + 'search_items' => __( 'Search Host Profiles', 'radio-station' ), + 'not_found' => __( 'No Host Profiles found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Host Profiles found in Trash', 'radio-station' ), + 'all_items' => __( 'All Host Profiles', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -233,20 +234,21 @@ function radio_station_create_post_types() { // (so that rewrite rules and query vars are added for it) $ui = apply_filters( 'radio_station_producer_interface', false ); $post_type = array( + // 2.3.3.9: fix to labels for template output 'labels' => array( - 'name' => __( 'Producer Profiles', 'radio-station' ), - 'singular_name' => __( 'Producer Profile', 'radio-station' ), - 'add_new' => __( 'New Producer Profile', 'radio-station' ), + 'name' => __( 'Producers', 'radio-station' ), + 'singular_name' => __( 'Producer', 'radio-station' ), + 'add_new' => __( 'Add New Producer Profile', 'radio-station' ), 'add_new_item' => __( 'Add Producer Profile', 'radio-station' ), 'edit_item' => __( 'Edit Producer Profile', 'radio-station' ), 'new_item' => __( 'New Producer Profile', 'radio-station' ), 'view_item' => __( 'View Producer Profile', 'radio-station' ), 'archive_title' => __( 'Show Producers Profile', 'Hosts' ), // 2.3.2: added missing post type labels - 'search_items' => __( 'Search Producers', 'radio-station' ), - 'not_found' => __( 'No Producers found', 'radio-station' ), - 'not_found_in_trash' => __( 'No Producers found in Trash', 'radio-station' ), - 'all_items' => __( 'All Producers', 'radio-station' ), + 'search_items' => __( 'Search Producer Profiles', 'radio-station' ), + 'not_found' => __( 'No Producer Profiles found', 'radio-station' ), + 'not_found_in_trash' => __( 'No Producer Profiles found in Trash', 'radio-station' ), + 'all_items' => __( 'All Producer Profiles', 'radio-station' ), ), 'show_ui' => $ui, 'show_in_menu' => false, @@ -342,10 +344,15 @@ function radio_station_add_featured_image_support() { add_action( 'admin_bar_menu', 'radio_station_modify_admin_bar_menu', 71 ); function radio_station_modify_admin_bar_menu( $wp_admin_bar ) { - // 2.3.0: loop post types to add post type items + // 2.3.3.9: add filter for admin bar post types $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + $post_types = apply_filters( 'radio_station_admin_bar_post_types', $post_types, 'new' ); + + // 2.3.0: loop post types to add post type items foreach ( $post_types as $post_type ) { - if ( current_user_can( 'publish_' . $post_type . 's' ) ) { + // 2.3.3.9: strip post type prefix for permission check + $type = str_replace( 'rs-', '', $post_type ); + if ( current_user_can( 'publish_' . $type . 's' ) ) { $post_type_object = get_post_type_object( $post_type ); $args = array( 'id' => 'new-' . $post_type, @@ -367,14 +374,19 @@ function radio_station_modify_admin_bar_menu( $wp_admin_bar ) { add_action( 'admin_bar_menu', 'radio_station_admin_bar_view_edit_links', 81 ); function radio_station_admin_bar_view_edit_links( $wp_admin_bar ) { + global $pagenow, $post; $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG ); // --- loop to check for plugin post types --- - if ( ! is_admin() && is_singular() ) { - foreach ( $post_types as $post_type ) { + if ( !is_admin() && is_singular() ) { + // 2.3.3.9: added filter for admin bar post types + $edit_post_types = apply_filters( 'radio_station_admin_bar_post_types', $post_types, 'edit' ); + foreach ( $edit_post_types as $post_type ) { if ( is_singular( $post_type ) ) { // --- add post type edit link --- - if ( current_user_can( 'edit_' . $post_type . 's' ) ) { + // 2.3.3.9: strip post type prefix for permission check + $type = str_replace( 'rs-', '', $post_type ); + if ( current_user_can( 'edit_' . $type . 's' ) ) { $post_type_object = get_post_type_object( $post_type ); $post_id = get_the_ID(); $args = array( @@ -390,10 +402,9 @@ function radio_station_admin_bar_view_edit_links( $wp_admin_bar ) { // --- check edit post match for view link --- // 2.3.0: add view links for admin - global $pagenow; if ( is_admin() && ( 'post.php' == $pagenow ) ) { - global $post; - foreach ( $post_types as $post_type ) { + $view_post_types = apply_filters( 'radio_station_admin_bar_post_types', $post_types, 'view' ); + foreach ( $view_post_types as $post_type ) { if ( $post->post_type == $post_type ) { $post_type_object = get_post_type_object( $post_type ); if ( 'draft' == $post->post_status ) { diff --git a/includes/shortcodes.php b/includes/shortcodes.php index e5fa3f6..9b9c61d 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -259,8 +259,11 @@ function radio_station_clock_shortcode( $atts = array() ) { // (handles Shows, Overrides, Playlists etc.) function radio_station_archive_list_shortcode( $type, $atts ) { - // --- merge defaults with passed attributes --- + // --- get clock time format --- $time_format = radio_station_get_setting( 'clock_time_format' ); + + // --- merge defaults with passed attributes --- + // 2.3.3.9: add atts for specific posts $defaults = array( // --- shortcode display ---- 'description' => 'excerpt', @@ -288,6 +291,10 @@ function radio_station_archive_list_shortcode( $type, $atts ) { 'thumbnails' => 0, // --- for playlists --- // 'track_count' => 0, + // --- specific posts --- + 'show' => false, + 'override' => false, + 'playlist' => false, ); // --- handle possible pagination offset --- @@ -389,10 +396,24 @@ function radio_station_archive_list_shortcode( $type, $atts ) { ); } + // 2.3.3.9: allow for selective post specifications + if ( ( RADIO_STATION_SHOW_SLUG == $type ) && isset( $atts['show'] ) ) { + $args['include'] = explode( ',', $atts['show'] ); + } elseif ( ( RADIO_STATION_OVERRIDE_SLUG == $type ) && isset( $atts['override'] ) ) { + $args['include'] = explode( ',', $atts['override'] ); + } elseif ( ( RADIO_STATION_PLAYLIST_SLUG == $type ) && isset( $atts['playlist'] ) ) { + $args['include'] = explode( ',', $atts['playlist'] ); + } + // --- get posts via query --- $args = apply_filters( 'radio_station_' . $type . '_archive_post_args', $args ); $archive_posts = get_posts( $args ); $archive_posts = apply_filters( 'radio_station_' . $type . '_archive_posts', $archive_posts ); + if ( RADIO_STATION_DEBUG ) { + echo 'Archive Shortcode: ' . PHP_EOL; + echo 'Args: ' . print_r( $args, true ) . PHP_EOL; + echo 'Posts: ' . print_r( $archive_posts, true ) . ''; + } // --- process playlist taxonomy query --- if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { @@ -501,16 +522,20 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- manually apply offset and perpage limit --- // 2.3.3.9: added to enable pagination count - if ( $post_count > $atts['offset'] ) { - $offset_posts = array(); - foreach ( $archive_posts as $i => $archive_post ) { - if ( ( $i > $atts['offset'] ) && ( count( $offset_posts ) < $atts['perpage'] ) ) { - $offset_posts[] = $archive_post; + if ( $atts['offset'] && ( $atts['perpage'] > 0 ) ) { + if ( $post_count > $atts['offset'] ) { + $offset_posts = array(); + foreach ( $archive_posts as $i => $archive_post ) { + if ( ( $i > $atts['offset'] ) && ( count( $offset_posts ) < $atts['perpage'] ) ) { + $offset_posts[] = $archive_post; + } } + $archive_posts = $offset_posts; + $post_count = count( $archive_posts ); + } else { + $archive_posts = array(); + $post_count = 0; } - $archive_posts = $offset_posts; - } else { - $archive_posts = array(); } } @@ -740,8 +765,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $list .= '
    '; // --- add archive_pagination --- - if ( $atts['pagination'] && ( $archive_count > 0 ) ) { - if ( $archive_count > $atts['perpage'] ) { + if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { + if ( $post_count > $atts['perpage'] ) { $list .= radio_station_archive_pagination( $type, $atts, $post_count ); } } @@ -1443,6 +1468,7 @@ function radio_station_show_list_shortcode( $type, $atts ) { } // --- get related to show posts --- + // 2.3.3.9: also handle host or producer lists $args = array(); if ( isset( $atts['limit'] ) ) { $args['limit'] = $atts['limit']; @@ -1451,17 +1477,27 @@ function radio_station_show_list_shortcode( $type, $atts ) { $posts = radio_station_get_show_posts( $show_id, $args ); } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { $posts = radio_station_get_show_playlists( $show_id, $args ); + $type = 'playlist'; + } elseif ( RADIO_STATION_HOST_SLUG == $type ) { + $posts = apply_filters( 'radio_station_get_show_hosts', false, $show_id, $args ); + $type = 'host'; + } elseif ( RADIO_STATION_PRODUCER_SLUG == $type ) { + $posts = apply_filters( 'radio_station_get_show_producers', false, $show_id, $args ); + $type = 'producer'; } elseif ( defined( 'RADIO_STATION_EPISODE_SLUG' ) && ( RADIO_STATION_EPISODE_SLUG == $type ) ) { $posts = apply_filters( 'radio_station_get_show_episodes', false, $show_id, $args ); + $type = 'episode'; } if ( RADIO_STATION_DEBUG ) { echo 'Show Posts (' . $type . '):' . print_r( $posts, true ) . ''; } } - if ( !isset( $posts ) || !$posts || !is_array( $posts ) || ( count( $posts ) == 0 ) ) {return '';} + if ( !isset( $posts ) || !$posts || !is_array( $posts ) || ( count( $posts ) == 0 ) ) { + return ''; + } // --- filter excerpt length and more --- - $length = apply_filters( 'radio_station_show_' . $type. '_list_excerpt_length', false ); + $length = apply_filters( 'radio_station_show_' . $type . '_list_excerpt_length', false ); $more = apply_filters( 'radio_station_show_' . $type . '_list_excerpt_more', '[…]' ); // --- show list div --- @@ -1497,56 +1533,104 @@ function radio_station_show_list_shortcode( $type, $atts ) { $class = implode( ' ', $classes ); $list .= '
    '; - // --- post thumbnail --- - if ( $atts['thumbnails'] ) { - $has_thumbnail = has_post_thumbnail( $post['ID'] ); - if ( $has_thumbnail ) { - $attr = array( 'class' => 'show-' . esc_attr( $type ) . '-thumbnail-image' ); - $thumbnail = get_the_post_thumbnail( $post['ID'], 'thumbnail', $attr ); + // 2.3.3.9: check if this object is instance of WP_User class + if ( is_a( $post, 'WP_User' ) ) { + + // --- this is a user without a profile post --- + $user = $post; + $user_id = $user->data->ID; + + // TODO: check for user avatar ? + + $list .= '
    '; + + // --- link to author page --- + $list .= '
    '; + $permalink = get_author_posts_url( $user_id ); + $title = __( 'View all posts by %s', 'radio-station' ); + $title = sprintf( $title, $user->display_name ); + $list .= ''; + $list .= esc_attr( $user->display_name ); + $list .= ''; + $list .= '
    '; + + // --- author bio/excerpt --- + $userdata = get_user_meta( $user_id ); + $bio_content = $userdata->description[0]; + if ( 'none' == $atts['content'] ) { + $list .= ''; + } elseif ( 'full' == $atts['content'] ) { + $list .= '
    '; + $content = apply_filters( 'radio_station_show_' . $type . '_content', $bio_content, $user_id ); + $list .= $bio_content; + $list .= '
    '; + } else { + $list .= '
    '; + $permalink = get_author_posts_url( $user_id ); + $excerpt = radio_station_trim_excerpt( $bio_content, $length, $more, $permalink ); + $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $user_id ); + $list .= $excerpt; + $list .= '
    '; + } + + + $list .= '
    '; + + } else { + + // --- post thumbnail --- + if ( $atts['thumbnails'] ) { + $thumbnail = false; + $has_thumbnail = has_post_thumbnail( $post['ID'] ); + if ( $has_thumbnail ) { + $attr = array( 'class' => 'show-' . esc_attr( $type ) . '-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $post['ID'], 'thumbnail', $attr ); + } + $thumbnail = apply_filters( 'radio_station_show_list_archive_avatar', $thumbnail, $post['ID'], $type ); if ( $thumbnail ) { $list .= '
    ' . $thumbnail . '
    '; } } - } - $list .= '
    '; + $list .= '
    '; - // --- link to post --- - $list .= '
    '; - $permalink = get_permalink( $post['ID'] ); - $timestamp = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); - $title = __( 'Published on ', 'radio-station' ) . $timestamp; - $list .= ''; - $list .= esc_attr( $post['post_title'] ); - $list .= ''; - $list .= '
    '; - - // --- post excerpt --- - $post_content = $post['post_content']; - $post_id = $post['ID']; - if ( 'none' == $atts['content'] ) { - $list .= ''; - } elseif ( 'full' == $atts['content'] ) { - $list .= '
    '; - $content = apply_filters( 'radio_station_show_' . $type . '_content', $post_content, $post_id ); - // $list .= $content; - $list .= '
    '; - } else { - $list .= '
    '; + // --- link to post --- + $list .= '
    '; $permalink = get_permalink( $post['ID'] ); - if ( !empty( $post['post_excerpt'] ) ) { - $excerpt = $post['post_excerpt']; - $excerpt .= ' ' . $more . ''; + $timestamp = mysql2date( $dateformat . ' ' . $timeformat, $post['post_date'], false ); + $title = __( 'Published on ', 'radio-station' ) . $timestamp; + $list .= ''; + $list .= esc_attr( $post['post_title'] ); + $list .= ''; + $list .= '
    '; + + // --- post excerpt --- + $post_content = $post['post_content']; + $post_id = $post['ID']; + if ( 'none' == $atts['content'] ) { + $list .= ''; + } elseif ( 'full' == $atts['content'] ) { + $list .= '
    '; + $content = apply_filters( 'radio_station_show_' . $type . '_content', $post_content, $post_id ); + $list .= $content; + $list .= '
    '; } else { - $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); + $list .= '
    '; + $permalink = get_permalink( $post['ID'] ); + if ( !empty( $post['post_excerpt'] ) ) { + $excerpt = $post['post_excerpt']; + $excerpt .= ' ' . $more . ''; + } else { + $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); + } + $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post_id ); + $list .= $excerpt; + $list .= '
    '; } - $excerpt = apply_filters( 'radio_station_show_' . $type . '_excerpt', $excerpt, $post_id ); - $list .= $excerpt; + $list .= '
    '; } - $list .= '
    '; - // --- close item div --- $list .= '
    '; $j ++; @@ -1556,10 +1640,11 @@ function radio_station_show_list_shortcode( $type, $atts ) { $list .= '
    '; // --- list pagination --- + // 2.3.3.9: fix to hide left arrow display on load if ( $atts['pagination'] && ( $post_pages > 1 ) ) { $list .= '

    '; $list .= '
    '; - $list .= '
    '; + $list .= ''; for ( $pagenum = 1; $pagenum < ( $post_pages + 1 ); $pagenum ++ ) { @@ -1568,14 +1653,14 @@ function radio_station_show_list_shortcode( $type, $atts ) { } else { $active = ''; } - $onclick = 'radio_list_page(' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; + $onclick = 'radio_list_page(\'show\', ' . esc_js( $show_id ) . ', \'' . esc_js( $type ) . 's\', ' . esc_js( $pagenum ) . ');'; $list .= ''; } - $list .= '
    '; + $list .= '
    '; $list .= ''; $list .= '
    '; $list .= ''; @@ -1637,11 +1722,13 @@ function radio_station_show_playlists_archive( $atts ) { function radio_station_list_pagination_javascript() { // --- fade out current page and fade in selected page --- - $js = "function radio_list_page(id, types, pagenum) { - currentpage = document.getElementById('show-'+id+'-'+types+'-current-page').value; + // 2.3.3.9: added selector prefix as argument + // 2.3.3.9: fix to conditional arrow displays + $js = "function radio_list_page(prefix, id, types, pagenum) { + currentpage = document.getElementById(prefix+'-'+id+'-'+types+'-current-page').value; + pagecount = document.getElementById(prefix+'-'+id+'-'+types+'-page-count').value; if (pagenum == 'next') { - pagenum = parseInt(currentpage) + 1; - pagecount = document.getElementById('show-'+id+'-'+types+'-page-count').value; + pagenum = parseInt(currentpage) + 1; if (pagenum > pagecount) {return;} } if (pagenum == 'prev') { @@ -1649,20 +1736,26 @@ function radio_station_list_pagination_javascript() { if (pagenum < 1) {return;} } if (typeof jQuery == 'function') { - jQuery('.show-'+id+'-'+types+'-page').fadeOut(500); - jQuery('#show-'+id+'-'+types+'-page-'+pagenum).fadeIn(1000); - jQuery('.show-'+id+'-'+types+'-page-button').removeClass('active'); - jQuery('#show-'+id+'-'+types+'-page-button-'+pagenum).addClass('active'); - jQuery('#show-'+id+'-'+types+'-current-page').val(pagenum); + jQuery('.'+prefix+'-'+id+'-'+types+'-page').fadeOut(500); + jQuery('#'+prefix+'-'+id+'-'+types+'-page-'+pagenum).fadeIn(1000); + jQuery('.'+prefix+'-'+id+'-'+types+'-page-button').removeClass('active'); + jQuery('#'+prefix+'-'+id+'-'+types+'-page-button-'+pagenum).addClass('active'); + jQuery('#'+prefix+'-'+id+'-'+types+'-current-page').val(pagenum); } else { - pages = document.getElementsByClassName('show-'+id+'-'+types+'-page'); + pages = document.getElementsByClassName(prefix+'-'+id+'-'+types+'-page'); for (i = 0; i < pages.length; i++) {pages[i].style.display = 'none';} - document.getElementById('show-'+id+'-'+types+'-page-'+pagenum).style.display = ''; - buttons = document.getElementsByClassName('show-'+id+'-'+types+'-page-button'); + document.getElementById(prefix+'-'+id+'-'+types+'-page-'+pagenum).style.display = ''; + buttons = document.getElementsByClassName(prefix+'-'+id+'-'+types+'-page-button'); for (i = 0; i < buttons.length; i++) {buttons[i].classList.remove('active');} - document.getElementById('show-'+id+'-'+types+'-page-button-'+pagenum).classList.add('active'); - document.getElementById('show-'+id+'-'+types+'-current-page').value = pagenum; - } + document.getElementById(prefix+'-'+id+'-'+types+'-page-button-'+pagenum).classList.add('active'); + document.getElementById(prefix+'-'+id+'-'+types+'-current-page').value = pagenum; + } + larrow = document.getElementById(prefix+'-'+types+'-pagination-button-left'); + rarrow = document.getElementById(prefix+'-'+types+'-pagination-button-right'); + larrow.style.display = ''; rarrow.style.display = ''; + if (pagenum == 1) {larrow.style.display = 'none';} + else if (pagenum == pagecount) {rarrow.style.display = 'none';} + console.log(pagenum+' - '+pagecount); }"; // --- enqueue script inline --- @@ -3128,69 +3221,73 @@ function radio_station_current_playlist_shortcode( $atts ) { // --- loop shifts --- foreach ( $playlist['shifts'] as $shift_id => $shift ) { - // --- set shift start and end --- - if ( isset( $shift['real_start'] ) ) { - $start = $shift['real_start']; - } elseif ( isset( $shift['start'] ) ) { - $start = $shift['start']; - } else { - $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - } - if ( isset( $shift['real_end'] ) ) { - $end = $shift['real_end']; - } elseif ( isset( $shift['end'] ) ) { - $end = $shift['end']; - } else { - $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - } - - // --- convert shift info --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - // TODO: check/test possible undefined index for $shift['day'] ? - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - if ( isset( $shift['real_start'] ) ) { - $prevday = radio_station_get_previous_day( $shift['day'] ); - $shift_start_time = radio_station_to_time( $weekdates[$prevday] . ' ' . $start_time ); - } else { - $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - } - $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - // 2.3.3.9: fix to overnight check variables - // 2.3.3.9: added or equals to operator - if ( $shift_end_time <= $shift_start_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } + // 2.3.3.9: added check that shift day is set + if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { - // --- check currently playing show time --- - if ( $atts['for_time'] ) { - $now = $atts['for_time']; - $html['countdown'] .= ''; - } else { - $now = radio_station_get_now(); - } - // echo 'Shift Start: ' . $shift_start_time . '(' . radio_station_get_time( 'datetime', $shift_start_time ) . ')
    '; - // echo 'Shift End: ' . $shift_end_time . '(' . radio_station_get_time( 'datetime', $shift_end_time ) . ')
    '; + // --- set shift start and end --- + if ( isset( $shift['real_start'] ) ) { + $start = $shift['real_start']; + } elseif ( isset( $shift['start'] ) ) { + $start = $shift['start']; + } else { + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + } + if ( isset( $shift['real_end'] ) ) { + $end = $shift['real_end']; + } elseif ( isset( $shift['end'] ) ) { + $end = $shift['end']; + } else { + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + } - if ( ( ( $now > $shift_start_time ) || ( $now == $shift_start_time ) ) && ( $now < $shift_end_time ) ) { - - // print_r( $shift ); - // echo "^^^ NOW PLAYING ^^^"; - - // --- hidden input for playlist end time --- - $html['countdown'] .= ''; + // --- convert shift info --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + // TODO: check/test possible undefined index for $shift['day'] ? + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + if ( isset( $shift['real_start'] ) ) { + $prevday = radio_station_get_previous_day( $shift['day'] ); + $shift_start_time = radio_station_to_time( $weekdates[$prevday] . ' ' . $start_time ); + } else { + $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + } + $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + // 2.3.3.9: fix to overnight check variables + // 2.3.3.9: added or equals to operator + if ( $shift_end_time <= $shift_start_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } - // --- for countdown timer display --- - if ( $atts['countdown'] ) { - $html['countdown'] .= '
    '; + // --- check currently playing show time --- + if ( $atts['for_time'] ) { + $now = $atts['for_time']; + $html['countdown'] .= ''; + } else { + $now = radio_station_get_now(); } + // echo 'Shift Start: ' . $shift_start_time . '(' . radio_station_get_time( 'datetime', $shift_start_time ) . ')
    '; + // echo 'Shift End: ' . $shift_end_time . '(' . radio_station_get_time( 'datetime', $shift_end_time ) . ')
    '; - // --- for dynamic reloading --- - if ( $atts['dynamic'] ) { - $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-playlist', $atts, $shift_end_time ); - if ( $dynamic ) { - $html['countdown'] .= $dynamic; + if ( ( ( $now > $shift_start_time ) || ( $now == $shift_start_time ) ) && ( $now < $shift_end_time ) ) { + + // print_r( $shift ); + // echo "^^^ NOW PLAYING ^^^"; + + // --- hidden input for playlist end time --- + $html['countdown'] .= ''; + + // --- for countdown timer display --- + if ( $atts['countdown'] ) { + $html['countdown'] .= '
    '; + } + + // --- for dynamic reloading --- + if ( $atts['dynamic'] ) { + $dynamic = apply_filters( 'radio_station_countdown_dynamic', false, 'current-playlist', $atts, $shift_end_time ); + if ( $dynamic ) { + $html['countdown'] .= $dynamic; + } } } } diff --git a/includes/support-functions.php b/includes/support-functions.php index 4b67b67..9dcfe37 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -74,7 +74,8 @@ // - Convert Show Shifts // - Convert Schedule Shifts // === Helper Functions === -// - Get Profile ID +// - Get Icon Colors +// - Encode URI Component // - Get Languages // - Get Language Options // - Get Language @@ -697,6 +698,12 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { $metakey = 'playlist_show_id'; } elseif ( 'episodes' == $datatype ) { $metakey = 'episode_show_id'; + } elseif ( 'hosts' == $datatype ) { + $metakey = 'show_user_list'; + $userkey = 'host_user_id'; + } elseif ( 'producers' == $datatype ) { + $metakey = 'show_producer_list'; + $userkey = 'producer_user_id'; } else { return false; } @@ -784,7 +791,29 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } } + } elseif ( ( 'hosts' == $datatype ) || ( 'producers' == $datatype ) ) { + + // 2.3.3.9; added get host/producer profile posts + $user_ids = get_post_meta( $show_id, $metakey, true ); + if ( !$user_ids ) { + return false; + } + $post_ids = $no_profile_ids = array(); + foreach ( $user_ids as $user_id ) { + $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = '" . $userkey . "' AND meta_value = %d"; + $query = $wpdb->prepare( $query, $user_id ); + $profile_id = $wpdb->get_var( $query ); + if ( $profile_id ) { + $post_ids[] = $profile_id; + } else { + $no_profile_ids[] = $user_id; + } + } + } else { + + // --- other types (episodes/playlists) --- $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta" . " WHERE meta_key = '" . $metakey . "' AND meta_value = %d"; $query = $wpdb->prepare( $query, $show_id ); @@ -819,6 +848,14 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } $results = $wpdb->get_results( $query, ARRAY_A ); + // --- non-post (user) IDS --- + if ( isset( $no_profile_ids ) && ( count( $no_profile_ids ) > 0 ) ) { + foreach ( $no_profile_ids as $user_id ) { + $user = get_user_by( 'ID', $user_id ); + $results[] = $user; + } + } + // --- maybe cache default show data --- if ( $default ) { do_action( 'radio_station_cache_data', $datatype, $show_id, $results ); @@ -2427,8 +2464,10 @@ function radio_station_check_shifts( $all_shifts ) { // TODO: check for start of week and end of week shift conflicts? + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts $now = radio_station_get_now(); - $weekdays = radio_station_get_schedule_weekdays(); + $today = radio_station_get_time( 'l', $now ); + $weekdays = radio_station_get_schedule_weekdays( $today ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); $conflicts = $checked_shifts = array(); @@ -2453,6 +2492,7 @@ function radio_station_check_shifts( $all_shifts ) { if ( !isset( $first_shift ) ) { $first_shift = $shift; } + $last_shift = $shift; // --- reset shift switches --- $set_shift = true; @@ -2670,28 +2710,40 @@ function radio_station_check_shifts( $all_shifts ) { // --- check last shift against first shift --- // 2.3.2: added for possible overlap (split by weekly schedule dates) - if ( isset( $shift ) && ( $shift != $first_shift ) ) { + if ( isset( $last_shift ) && ( $last_shift != $first_shift ) ) { - // --- use days for next week to compare --- - $shift_start = radio_station_convert_shift_time( $shift['start'] ); - $shift_end = radio_station_convert_shift_time( $shift['end'] ); - $last_shift_start = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_start ); - $last_shift_end = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_end ); - $shift_start = radio_station_convert_shift_time( $first_shift['start'] ); - $shift_end = radio_station_convert_shift_time( $first_shift['end'] ); - $first_shift_start = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_start ); - $first_shift_end = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $shift_end ); + // --- use days for different weeks to compare --- + $l_shift_start = radio_station_convert_shift_time( $last_shift['start'] ); + $l_shift_end = radio_station_convert_shift_time( $last_shift['end'] ); + $last_shift_start = radio_station_to_time( $last_shift['day'] . ' ' . $l_shift_start ); + $last_shift_end = radio_station_to_time( $last_shift['day'] . ' ' . $l_shift_end ); + if ( $last_shift_end < $last_shift_start ) { + $last_shift_end = $last_shift_end + ( 24 * 60 * 60 ); + } + + $f_shift_start = radio_station_convert_shift_time( $first_shift['start'] ); + $f_shift_end = radio_station_convert_shift_time( $first_shift['end'] ); + $first_shift_start = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_start ); + $first_shift_end = radio_station_to_time( 'next ' . $first_shift['day'] . ' ' . $f_shift_end ); + if ( $first_shift_end < $first_shift_start ) { + $first_shift_end = $first_shift_end + ( 24 * 60 * 60 ); + } if ( RADIO_STATION_DEBUG ) { - echo 'Last Shift Start: ' . $shift['day'] . ' ' . $shift_start . ' - (' . $last_shift_start . ')' . PHP_EOL; - echo 'First Shift End: ' . $first_shift['day'] . ' ' . $shift_end . ' - (' . $first_shift_end . ')' . PHP_EOL; + echo 'Last Shift End: ' . $last_shift['day'] . ' ' . $l_shift_end . ' - (' . $last_shift_end . ')' . PHP_EOL; + echo 'First Shift Start: ' . $first_shift['day'] . ' ' . $f_shift_start . ' - (' . $first_shift_start . ')' . PHP_EOL; } - if ( $last_shift_start < $first_shift_end ) { + // --- end of the week overlap check --- + // 2.3.3.9: fix to incorrect overlap logic + if ( $last_shift_end > $first_shift_start ) { + // if ( $last_shift_start < $first_shift_end ) { // --- record a conflict --- if ( RADIO_STATION_DEBUG ) { echo "First/Last Shift Overlap Conflict" . PHP_EOL; + echo "First Shift: " . print_r( $first_shift, true ); + echo "Last Shift: " . print_r( $last_shift, true ); } /* @@ -2782,8 +2834,10 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { } // --- convert days to dates for checking --- + // 2.3.3.9: fix weekday/dates code to match radio_station_get_show_shifts $now = radio_station_get_now(); - $weekdays = radio_station_get_schedule_weekdays(); + $today = radio_station_get_time( 'l', $now ); + $weekdays = radio_station_get_schedule_weekdays( $today ); $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); // --- get shows to check against via context --- @@ -2822,8 +2876,6 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // 2.3.2: use next week day instead of date $shift_start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); $shift_end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - // $shift_start_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $start_time ); - // $shift_end_time = radio_station_to_time( '+2 weeks ' . $shift['day'] . ' ' . $end_time ); // 2.3.3.9: added or equals to operator if ( $shift_end_time <= $shift_start_time ) { $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); @@ -2850,14 +2902,12 @@ function radio_station_check_shift( $show_id, $shift, $context = 'all' ) { // 2.3.2: replace strtotime with to_time for timezones // 2.3.2: fix to convert to 24 hour times first - $shift_start = radio_station_convert_shift_time( $day_shift['start'] ); - $shift_end = radio_station_convert_shift_time( $day_shift['end'] ); + $day_shift_start = radio_station_convert_shift_time( $day_shift['start'] ); + $day_shift_end = radio_station_convert_shift_time( $day_shift['end'] ); // 2.3.2: use next week day instead of date - $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_start ); - $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $shift_end ); - // $day_shift_start_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_start ); - // $day_shift_end_time = radio_station_to_time( '+2 weeks ' . $day_shift['day'] . ' ' . $shift_end ); + $day_shift_start_time = radio_station_to_time( $day_shift['date'] . ' ' . $day_shift_start ); + $day_shift_end_time = radio_station_to_time( $day_shift['date'] . ' ' . $day_shift_end ); // 2.3.2: adjust for midnight with change to use non-split shifts // 2.3.3.9: added or equals to operator if ( $day_shift_end_time <= $day_shift_start_time ) { @@ -3452,32 +3502,22 @@ function radio_station_get_show_rss_url( $show_id ) { // ------------------------- // Get DJ / Host Profile URL // ------------------------- -// 2.3.0: added to get DJ / Host profile permalink +// 2.3.0: added to get DJ / Host author/profile permalink +// 2.3.3.9: moved get possible profile ID to Pro filter function radio_station_get_host_url( $host_id ) { - $post_id = radio_station_get_profile_id( RADIO_STATION_HOST_SLUG, $host_id ); - if ( $post_id ) { - $host_url = get_permalink( $post_id ); - } else { - $host_url = get_author_posts_url( $host_id ); - } + $host_url = get_author_posts_url( $host_id ); $host_url = apply_filters( 'radio_station_host_url', $host_url, $host_id ); - return $host_url; } // ------------------------ // Get Producer Profile URL // ------------------------ -// 2.3.0: added to get Producer profile permalink +// 2.3.0: added to get Producer author/profile permalink +// 2.3.3.9: moved get possible profile ID to Pro filter function radio_station_get_producer_url( $producer_id ) { - $post_id = radio_station_get_profile_id( RADIO_STATION_PRODUCER_SLUG, $producer_id ); - if ( $post_id ) { - $producer_url = get_permalink( $post_id ); - } else { - $producer_url = get_author_posts_url( $producer_id ); - } + $producer_url = get_author_posts_url( $producer_id ); $producer_url = apply_filters( 'radio_station_producer_url', $producer_url, $producer_id ); - return $producer_url; } @@ -3535,13 +3575,14 @@ function radio_station_patreon_button_styles() { // -------------------- // 2.3.2: queue directory ping on saving function radio_station_queue_directory_ping() { - // 2.3.3.9: maybe bug out on activation + // 2.3.3.9: fix to bug out during plugin activation if ( !function_exists( 'radio_station_get_setting' ) ) { return; } - $do_ping = radio_station_get_setting( 'ping_netmix_directory' ); - if ( 'yes' != $do_ping ) {return;} - update_option( 'radio_station_ping_directory', '1' ); + $queue_ping = radio_station_get_setting( 'ping_netmix_directory' ); + if ( 'yes' == $queue_ping ) { + update_option( 'radio_station_ping_directory', '1' ); + } } // ------------------- @@ -4205,7 +4246,8 @@ function radio_station_convert_hour( $hour, $timeformat = 24, $suffix = true ) { function radio_station_convert_shift_time( $time, $timeformat = 24 ) { // note: timezone can be ignored here as just getting hours and minutes - $timestamp = strtotime( date( 'Y-m-d' ) . $time ); + // 2.3.3.9: added space between date and time + $timestamp = strtotime( date( 'Y-m-d' ) . ' ' . $time ); if ( 12 == (int) $timeformat ) { $time = date( 'g:i a', $timestamp ); str_replace( 'am', radio_station_translate_meridiem( 'am' ), $time ); @@ -4287,6 +4329,22 @@ function radio_station_convert_schedule_shifts( $schedule ) { // === Helper Functions === // ------------------------ +// --------------- +// Get Icon Colors +// --------------- +// 2.3.3.9: moved out from single-show-content.php template +function radio_station_get_icon_colors( $context = false ) { + $icon_colors = array( + 'website' => '#A44B73', + 'email' => '#0086CC', + 'phone' => '#008000', + 'download' => '#7DBB00', + 'rss' => '#FF6E01', + ); + $icon_colors = apply_filters( 'radio_station_icon_colors', $icon_colors, $context ); + return $icon_colors; +} + // -------------------- // Encode URI Component // -------------------- @@ -4297,44 +4355,6 @@ function radio_station_encode_uri_component( $string ) { return strtr( rawurlencode( $string ), $revert); } -// -------------- -// Get Profile ID -// -------------- -// 2.3.0: added to get host or producer profile post ID -function radio_station_get_profile_id( $type, $user_id ) { - - global $radio_station_data; - - if ( isset( $radio_station_data[$type . '-' . $user_id] ) ) { - $post_id = $radio_station_data[$type . '-' . $user_id]; - return $post_id; - } - - // --- get the post ID(s) for the profile --- - global $wpdb; - $query = "SELECT post_id FROM " . $wpdb->prefix . "postmeta - WHERE meta_key = '" . $type . "_user_id' AND meta_value = %d"; - $query = $wpdb->prepare( $query, $user_id ); - $results = $wpdb->get_results( $query, ARRAY_A ); - - // --- check for and return published profile ID --- - if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { - foreach ( $results as $result ) { - $query = "SELECT ID FROM " . $wpdb->prefix . "posts - WHERE post_status = 'publish' AND post_id = %d"; - $query = $wpdb->prepare( $query, $result['ID'] ); - $post_id = $wpdb->get_var( $query ); - if ( $post_id ) { - $radio_station_data[$type . '-' . $user_id] = $post_id; - - return $post_id; - } - } - } - - return false; -} - // ------------------ // Get Language Terms // ------------------ @@ -4671,6 +4691,39 @@ function radio_station_sanitize_input( $prefix, $key ) { } } + } elseif ( in_array( $key, $types['hour'] ) ) { + + // --- hours (24) --- + $value = absint( $_POST[$postkey] ); + if ( ( $value < 0 ) || ( $value > 23 ) ) { + $value = '00'; + } elseif ( $value < 10 ) { + $value = '0' . $value; + } else { + $value = (string) $value; + } + + } elseif ( in_array( $key, $types['mins'] ) ) { + + // --- minutes (or seconds) --- + $value = absint( $_POST[$postkey] ); + if ( ( $value < 0 ) || ( $value > 60 ) ) { + $value = '00'; + } elseif ( $value < 10 ) { + $value = '0' . $value; + } else { + $value = (string) $value; + } + + } elseif ( in_array( $key, $types['meridiem'] ) ) { + + // --- meridiems --- + $valid = array( '', 'am', 'pm' ); + $value = $_POST[$postkey]; + if ( !in_array( $value, $valid ) ) { + $value = ''; + } + } return $value; @@ -4683,7 +4736,7 @@ function radio_station_sanitize_input( $prefix, $key ) { function radio_station_get_meta_input_types() { $types = array( - 'numeric' => array( 'avatar', 'image' ), + 'numeric' => array( 'avatar', 'image', 'number' ), 'checkbox' => array( 'active', 'download' ), 'user' => array( 'user_list', 'producer_list' ), 'file' => array( 'file' ), @@ -4692,6 +4745,9 @@ function radio_station_get_meta_input_types() { 'slug' => array( 'slug', 'patreon' ), 'phone' => array( 'phone' ), 'date' => array( 'date' ), + 'hour' => array( 'hour' ), + 'mins' => array( 'mins', 'minutes', 'seconds' ), + 'meridiem' => array( 'meridian', 'meridiem' ), ); // --- filter and return --- @@ -4699,6 +4755,50 @@ function radio_station_get_meta_input_types() { return $types; } +// ----------------------- +// Sanitize Playlist Entry +// ----------------------- +// 2.3.3.9: added for entry validation +function radio_station_sanitize_playlist_entry( $entry ) { + + // --- set playlist entry keys --- + $entry_keys = array( + 'playlist_entry_artist', + 'playlist_entry_song', + 'playlist_entry_album', + 'playlist_entry_label', + 'playlist_entry_minutes', + 'playlist_entry_seconds', + 'playlist_entry_comments', + 'playlist_entry_new', + 'playlist_entry_status', + ); + $text_keys = array( 'artist', 'song', 'album', 'label', 'comments' ); + $numeric_keys = array( 'minutes', 'seconds' ); + foreach ( $entry_keys as $entry_key ) { + if ( isset( $entry[$entry_key] ) ) { + $value = $entry[$entry_key]; + $key = str_replace( 'playlist_entry_', '', $entry_key ); + if ( in_array( $key, $text_keys ) ) { + $value = sanitize_text_field( $value ); + } elseif ( in_array( $key, $numeric_keys ) ) { + $value = absint( $value ); + if ( ( 'seconds' == $key ) && ( $value < 10 ) ) { + + } + } elseif ( 'status' == $key ) { + if ( $value != 'played' ) { + $value = 'queued'; + } + } + $entry[$entry_key] = $value; + } + } + + return $entry; +} + + // ------------------------- // Sanitize Shortcode Values // ------------------------- diff --git a/js/radio-station-page.js b/js/radio-station-page.js new file mode 100644 index 0000000..d9f3dfd --- /dev/null +++ b/js/radio-station-page.js @@ -0,0 +1,130 @@ +/* ------------------------- */ +/* Radio Station Page Script */ +/* ------------------------- */ + +/* Get Page Type */ +function radio_page_type() { + if (!document.getElementById('radio-page-type')) {return 'show';} + return document.getElementById('radio-page-type').value; +} + +/* Show/Hide Audio Player */ +function radio_show_player() { + prefix = radio_page_type(); + if (typeof jQuery == 'function') {jQuery('#'+prefix+'-player').fadeIn(1000);} + else {document.getElementById(prefix+'-player').style.display = 'block';} +} + +/* Switch Section Tabs */ +function radio_show_tab(tab) { + prefix = radio_page_type(); + if ( (typeof jQuery == 'function') && jQuery('#'+prefix+'-'+tab+'-tab') ) { + jQuery('.'+prefix+'-tab').removeClass('tab-active').addClass('tab-inactive'); + jQuery('#'+prefix+'-'+tab+'-tab').removeClass('tab-inactive').addClass('tab-active'); + jQuery('#'+prefix+'-'+tab).removeClass('tab-inactive').addClass('tab-active'); + } else if (document.getElementById(prefix+'-'+tab+'-tab')) { + tabs = document.getElementsByClassName(prefix+'-tab'); + for (i = 0; i < tabs.length; i++) { + tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); + } + button = document.getElementById(prefix+'-'+tab+'-tab'); + button.className = button.className.replace('-tab-inactive', '-tab-active'); + content = document.getElementById(prefix+'-'+tab); + content.className = content.className.replace('-tab-inactive', '-tab-active'); + } +} + +/* Responsive Page */ +function radio_page_responsive() { + prefix = radio_page_type(); + + /* Check to Add Narrow Class */ + if (typeof jQuery == 'function') { + content = jQuery('#'+prefix+'-content'); + if (content.width() < 500) {content.addClass('narrow');} + else {content.removeClass('narrow');} + } else { + content = document.getElementById(prefix+'-content'); + if (content.offsetWidth < 500) {content.classList.add('narrow');} + else {content.classList.remove('narrow');} + } + + /* Maybe Match Heights for Info and Description */ + if (document.getElementById(prefix+'-content').className.indexOf('top-blocks') < 0) { + info = document.getElementById(prefix+'-info'); + desc = document.getElementById(prefix+'-description'); + about = document.getElementById(prefix+'-section-about'); + if (info && desc) { + descheight = info.offsetHeight - 30; + if (about) {descheight = descheight - about.offsetHeight;} + if (descheight < desc.style.minHeight) {desc.style.maxHeight = 'none';} + else {desc.style.maxHeight = descheight+'px';} + } + } + + /* Maybe Display Show More Button */ + descstate = document.getElementById('show-desc-state'); + if ( descstate && (descstate.value != 'expanded') ) { + desc = document.getElementsByClassName(prefix+'-description')[0]; + if (desc.offsetHeight < desc.scrollHeight) { + document.getElementById('show-more-overlay').style.display = 'block'; + document.getElementById('show-desc-buttons').style.display = 'block'; + desc.style.paddingBottom = '0'; + } else { + document.getElementById('show-more-overlay').style.display = 'none'; + document.getElementById('show-desc-buttons').style.display = 'none'; + desc.style.paddingBottom = '30px'; + } + } +} + +/* Description Show More/Less */ +function radio_show_desc(moreless) { + prefix = radio_page_type(); + if (moreless == 'more') { + if (typeof jQuery == 'function') {jQuery('#'+prefix+'-description').addClass('expanded');} + else {document.getElementById(prefix+'-description').classList.add('expanded');} + document.getElementById('show-more-overlay').style.display = 'none'; + document.getElementById('show-desc-more').style.display = 'none'; + document.getElementById('show-desc-less').style.display = 'inline-block'; + } + if (moreless == 'less') { + if (typeof jQuery == 'function') {jQuery('.show-description').removeClass('expanded');} + else {document.getElementById(prefix+'-description').classList.remove('expanded');} + document.getElementById('show-more-overlay').style.display = 'block'; + document.getElementById('show-desc-less').style.display = 'none'; + document.getElementById('show-desc-more').style.display = ''; + radio_scroll_to(prefix+'-section-about'); + } +} + +/* Section Scroll Link */ +function radio_scroll_link(id) { + prefix = radio_page_type(); + if (typeof jQuery == 'function') { + section = jQuery('#'+prefix+'-section-'+id); + scrolltop = section.offset().top - section.height() - 40; + jQuery('html, body').animate({ 'scrollTop': scrolltop }, 1000); + } else { + radio_scroll_to(prefix+'-section-'+id); + } +} + +/* Responsive Load and Resizing */ +if (typeof jQuery == 'function') { + jQuery(document).ready(function() { + prefix = radio_page_type(); + radio_page_responsive(); + jQuery(window).resize(function () { + radio_resize_debounce(radio_page_responsive, 500, prefix+'page'); + }); + } ); +} else { + if (window.addEventListener) { + document.body[addEventListener]('load', radio_page_responsive, false); + document.body[addEventListener]('resize', radio_page_responsive, false); + } else { + document.body[attachEvent]('onload', radio_page_responsive, false); + document.body[attachEvent]('onresize', radio_page_responsive, false); + } +} diff --git a/js/radio-station-show-page.js b/js/radio-station-show-page.js deleted file mode 100644 index 8f1005d..0000000 --- a/js/radio-station-show-page.js +++ /dev/null @@ -1,117 +0,0 @@ -/* ------------------------------ */ -/* Radio Station Show Page Script */ -/* ------------------------------ */ - -/* Show/Hide Audio Player */ -function radio_show_player() { - if (typeof jQuery == 'function') {jQuery('#show-player').fadeIn(1000);} - else {document.getElementById('show-player').style.display = 'block';} -} - -/* Switch Section Tabs */ -function radio_show_tab(tab) { - if ( (typeof jQuery == 'function') && jQuery('#show-'+tab+'-tab') ) { - jQuery('.show-tab').removeClass('tab-active').addClass('tab-inactive'); - jQuery('#show-'+tab+'-tab').removeClass('tab-inactive').addClass('tab-active'); - jQuery('#show-'+tab).removeClass('tab-inactive').addClass('tab-active'); - } else if (document.getElementById('show-'+tab+'-tab')) { - tabs = document.getElementsByClassName('show-tab'); - for (i = 0; i < tabs.length; i++) { - tabs[i].className = tabs[i].className.replace('-tab-active', '-tab-inactive'); - } - button = document.getElementById('show-'+tab+'-tab'); - button.className = button.className.replace('-tab-inactive', '-tab-active'); - content = document.getElementById('show-'+tab); - content.className = content.className.replace('-tab-inactive', '-tab-active'); - } -} - -/* Responsive Page */ -function radio_show_responsive() { - - /* Check to Add Narrow Class */ - if (typeof jQuery == 'function') { - showcontent = jQuery('#show-content'); - if (showcontent.width() < 500) {showcontent.addClass('narrow');} - else {showcontent.removeClass('narrow');} - - } else { - showcontent = document.getElementById('show-content'); - if (showcontent.offsetWidth < 500) {showcontent.classList.add('narrow');} - else {showcontent.classList.remove('narrow');} - } - - /* Maybe Match Heights for Info and Description */ - if (document.getElementById('show-content').className.indexOf('top-blocks') < 0) { - info = document.getElementById('show-info'); - desc = document.getElementById('show-description'); - about = document.getElementById('show-section-about'); - if (info && desc) { - descheight = info.offsetHeight - 30; - if (about) {descheight = descheight - about.offsetHeight;} - if (descheight < desc.style.minHeight) {desc.style.maxHeight = 'none';} - else {desc.style.maxHeight = descheight+'px';} - } - } - - /* Maybe Display Show More Button */ - descstate = document.getElementById('show-desc-state'); - if ( descstate && (descstate.value != 'expanded') ) { - showdesc = document.getElementsByClassName('show-description')[0]; - if (showdesc.offsetHeight < showdesc.scrollHeight) { - document.getElementById('show-more-overlay').style.display = 'block'; - document.getElementById('show-desc-buttons').style.display = 'block'; - showdesc.style.paddingBottom = '0'; - } else { - document.getElementById('show-more-overlay').style.display = 'none'; - document.getElementById('show-desc-buttons').style.display = 'none'; - showdesc.style.paddingBottom = '30px'; - } - } -} - -/* Description Show More/Less */ -function radio_show_desc(moreless) { - if (moreless == 'more') { - if (typeof jQuery == 'function') {jQuery('#show-description').addClass('expanded');} - else {document.getElementById('show-description').classList.add('expanded');} - document.getElementById('show-more-overlay').style.display = 'none'; - document.getElementById('show-desc-more').style.display = 'none'; - document.getElementById('show-desc-less').style.display = 'inline-block'; - } - if (moreless == 'less') { - if (typeof jQuery == 'function') {jQuery('.show-description').removeClass('expanded');} - else {document.getElementById('show-description').classList.remove('expanded');} - document.getElementById('show-more-overlay').style.display = 'block'; - document.getElementById('show-desc-less').style.display = 'none'; - document.getElementById('show-desc-more').style.display = ''; - radio_scroll_to('show-section-about'); - } -} - -/* Section Scroll Link */ -function radio_scroll_link(id) { - if (typeof jQuery == 'function') { - section = jQuery('#show-section-'+id); - scrolltop = section.offset().top - section.height() - 40; - jQuery('html, body').animate({ 'scrollTop': scrolltop }, 1000); - } else { - radio_scroll_to('show-section-'+id); - } -} - -/* Responsive Load and Resizing */ -if (typeof jQuery == 'function') { - jQuery(document).ready(function() {radio_show_responsive();} ); - jQuery(window).resize(function () { - radio_resize_debounce(radio_show_responsive, 500, 'showpage'); - }); -} else { - if (window.addEventListener) { - document.body[addEventListener]('load', radio_show_responsive, false); - document.body[addEventListener]('resize', radio_show_responsive, false); - } else { - document.body[attachEvent]('onload', radio_show_responsive, false); - document.body[attachEvent]('onresize', radio_show_responsive, false); - } -} diff --git a/radio-station-admin.php b/radio-station-admin.php index a46b8d0..63384b1 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -281,12 +281,13 @@ function radio_station_role_editor() { echo '
    ' . esc_html( __( 'Radio Station Pro will include a Role Assignment Interface so you can easily assign Radio Station roles to any user.', 'radio-station' ) ) . '
    '; // --- find out about Pro link --- - // TODO: change this text/link when Pro version becomes available and remove target blank + // [Pro Blurb] + // TODO: add go Por link when Pro available $upgrade_url = radio_station_get_upgrade_url(); - echo "
    "; + echo '
    '; // echo esc_html( __( 'Upgrade to Radio Station Pro', 'radio-station' ) ); echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); - echo " →."; + echo ' →.'; } // ------------------------------- @@ -1275,7 +1276,8 @@ function radio_station_shift_conflict_notice() { echo '
      '; - echo '
    • '; + // 2.3.3.9: remove unnecessary left margin on first list item + echo '
    • '; echo '' . esc_html( __( 'Radio Station', 'radio-station' ) ) . '
      '; echo esc_html( __( 'has detected', 'radio-station' ) ) . '
      '; echo esc_html( __( 'Schedule conflicts!', 'radio-station' ) ); diff --git a/radio-station.php b/radio-station.php index 2d88301..eb46aa1 100644 --- a/radio-station.php +++ b/radio-station.php @@ -70,6 +70,7 @@ // - Schedule Override Filters // === User Roles === // - Set Roles and Capabilities +// - Admin Fix for DJ / Host Role Label // - maybe Revoke Edit Show Capability // === Debugging === // - Set Debug Mode Constant @@ -89,9 +90,9 @@ define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); define( 'RADIO_STATION_BASENAME', plugin_basename( __FILE__ ) ); define( 'RADIO_STATION_HOME_URL', 'https://netmix.com/radio-station/' ); -define( 'RADIO_STATION_DOCS_URL', 'https://netmix.com/radio-station/docs/' ); -define( 'RADIO_STATION_API_DOCS_URL', 'https://netmix.com/radio-station/docs/api/' ); -define( 'RADIO_STATION_PRO_URL', 'https://netmix.com/radio-station-pro/' ); +define( 'RADIO_STATION_DOCS_URL', 'https://radiostation.pro/docs/' ); +define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); +define( 'RADIO_STATION_PRO_URL', 'https://radiostation.pro/' ); define( 'RADIO_STATION_NETMIX_DIR', 'https://netmix.com/' ); // ------------------------ @@ -103,7 +104,6 @@ // --- check and define CPT slugs --- // TODO: prefix original slugs and update post/taxonomy data -// ... and then add new slugs to template hierarchy ... if ( get_option( 'radio_show_cpts_prefixed' ) ) { define( 'RADIO_STATION_SHOW_SLUG', 'rs-show' ); define( 'RADIO_STATION_PLAYLIST_SLUG', 'rs-playlist' ); @@ -743,14 +743,14 @@ 'type' => 'multicheck', 'label' => __( 'Available Views', 'radio-station' ), // note: unstyled list view not included in defaults - 'default' => array( 'table', 'tabs' ), + 'default' => array( 'table', 'calendar' ), 'value' => 'yes', 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'grid' => __( 'Grid View', 'radio-station' ), - // 'calendar' => __( 'Calendar View', 'radio-station' ); + 'table' => __( 'Table View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + 'grid' => __( 'Grid View', 'radio-station' ), + 'calendar' => __( 'Calendar View', 'radio-station' ), ), 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), 'tab' => 'pages', @@ -758,7 +758,7 @@ 'pro' => true, ), - // === Show Page === + // === Show Pages === // --- Show Blocks Position --- 'show_block_position' => array( @@ -809,7 +809,7 @@ // 'min' => 0, // 'max' => 100, // 'default' => 3, - // 'helper' => __( 'Number of Latest Blog Posts to Show above Show Page tabs.', 'radio-station' ), + // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), // 'tab' => 'pages', // 'section' => 'show', // ), @@ -854,6 +854,75 @@ 'pro' => true, ), + // === Profile Pages === + // 2.3.3.9: added proflie page settings + + // --- [Pro] Profile Blocks Position --- + 'profile_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // ---- [Pro] Profile Section Layout --- + 'profile_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Profile Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // === Episode Pages === + // 2.3.3.9: added proflie page settings + + // --- [Pro] Profile Blocks Position --- + 'episode_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ---- [Pro] Profile Section Layout --- + 'episode_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Episode Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ==== Archives === // --- Shows Archive Page --- @@ -988,6 +1057,41 @@ // 'section' => 'archives', // ), + // --- Languages Archive Page --- + // 2.3.3.9: added language archive page + 'language_archive_page' => array( + 'label' => __( 'Languages Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + // 2.3.3.9: added language archive automatic page + 'language_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Languages Archives --- ? + // 'language_archive_override' => array( + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + // === Single Templates === // --- Templates Change Note --- @@ -1166,8 +1270,8 @@ // 2.3.3.8: move templates section onto pages tab 'tabs' => array( 'general' => __( 'General', 'radio-station' ), - 'player' => __( 'Player', 'radio-station' ), 'pages' => __( 'Pages', 'radio-station' ), + 'player' => __( 'Player', 'radio-station' ), // 'templates' => __( 'Templates', 'radio-station' ), 'widgets' => __( 'Widgets', 'radio-station' ), 'roles' => __( 'Roles', 'radio-station' ), @@ -1175,6 +1279,7 @@ // --- Section Labels --- // 2.3.2: add widget loading section + // 2.3.3.9: added profile pages section 'sections' => array( 'broadcast' => __( 'Broadcast', 'radio-station' ), 'station' => __( 'Station', 'radio-station' ), @@ -1187,6 +1292,8 @@ 'archive' => __( 'Archive Templates', 'radio-station' ), 'schedule' => __( 'Schedule Page', 'radio-station' ), 'show' => __( 'Show Pages', 'radio-station' ), + 'profile' => __( 'Profile Pages', 'radio-station' ), + 'episode' => __( 'Episode Pages', 'radio-station' ), 'archives' => __( 'Archives', 'radio-station' ), 'loading' => __( 'Widget Loading', 'radio-station' ), 'permissions' => __( 'Permissions', 'radio-station' ), @@ -1217,7 +1324,7 @@ 'title' => 'Radio Station', 'parentmenu' => 'radio-station', 'home' => RADIO_STATION_HOME_URL, - 'docs' => RADIO_STATION_DOCS_URL, + 'docs' => RADIO_STATION_DOCS_URL, 'support' => 'https://github.com/netmix/radio-station/issues/', 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), 'share' => RADIO_STATION_HOME_URL . '?share', @@ -1888,8 +1995,10 @@ function radio_station_automatic_pages_content_set( $content ) { $automatic = radio_station_get_setting( 'show_archive_auto' ); if ( 'yes' === (string) $automatic ) { $atts = array(); - $view = radio_station_get_setting( 'show_archive_view' ); - if ( $view ) {$atts['view'] = $view;} + // $view = radio_station_get_setting( 'show_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } $atts = apply_filters( 'radio_station_automatic_show_archive_atts', $atts ); $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { @@ -1910,8 +2019,10 @@ function radio_station_automatic_pages_content_set( $content ) { $automatic = radio_station_get_setting( 'override_archive_auto' ); if ( 'yes' === (string) $automatic ) { $atts = array(); - $view = radio_station_get_setting( 'override_archive_view' ); - if ( $view ) {$atts['view'] = $view;} + // $view = radio_station_get_setting( 'override_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } $atts = apply_filters( 'radio_station_automatic_override_archive_atts', $atts ); $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { @@ -1932,8 +2043,10 @@ function radio_station_automatic_pages_content_set( $content ) { $automatic = radio_station_get_setting( 'playlist_archive_auto' ); if ( 'yes' == $automatic ) { $atts = array(); - $view = radio_station_get_setting( 'playlist_archive_view' ); - if ( $view ) {$atts['view'] = $view;} + // $view = radio_station_get_setting( 'playlist_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } $atts = apply_filters( 'radio_station_automatic_playlist_archive_atts', $atts ); $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { @@ -1954,8 +2067,10 @@ function radio_station_automatic_pages_content_set( $content ) { $automatic = radio_station_get_setting( 'genre_archive_auto' ); if ( 'yes' === (string) $automatic ) { $atts = array(); - $view = radio_station_get_setting( 'genre_archive_view' ); - if ( $view ) {$atts['view'] = $view;} + // $view = radio_station_get_setting( 'genre_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } $atts = apply_filters( 'radio_station_automatic_genre_archive_atts', $atts ); $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { @@ -1968,7 +2083,29 @@ function radio_station_automatic_pages_content_set( $content ) { } } - // TODO: languages archive page ? + // --- languages archive page --- + // 2.3.3.9: added automatic display of language archive page + $language_archive_page = radio_station_get_setting( '' ); + if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { + if ( is_page( $language_archive_page ) ) { + $automatic = radio_station_get_setting( 'language_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'language_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_languagee_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[languages-archive' . $atts_string. ']'; + } + } + } // 2.3.3.6: moved out to reduce repetitive code if ( isset( $shortcode ) ) { @@ -2012,17 +2149,27 @@ function radio_station_single_content_template( $content, $post_type ) { } // --- check for user content templates --- + // 2.3.3.9: allow for prefixed and unprefixed post types $theme_dir = get_stylesheet_directory(); - $templates = array( - $theme_dir . '/templates/single-' . $post_type . '-content.php', - $theme_dir . '/single-' . $post_type . '-content.php', - RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php', - ); + $templates = array(); + $templates[] = $theme_dir . '/templates/single-' . $post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php'; + $unprefixed_post_type = str_replace( 'rs-', '', $post_type ); + if ( $post_type != $unprefixed_post_type ) { + $templates[] = $theme_dir . '/templates/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $unprefixed_post_type . '-content.php'; + } + // 2.3.0: fallback to show content template for overrides if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - $templates[] = $theme_dir . '/templates/single-' . RADIO_STATION_SHOW_SLUG . '-content.php'; - $templates[] = $theme_dir . '/single-' . RADIO_STATION_SHOW_SLUG . '-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-' . RADIO_STATION_SHOW_SLUG . '-content.php'; + // $templates[] = $theme_dir . '/templates/single-rs-show-content.php'; + // $templates[] = $theme_dir . '/single-rs-show-content.php'; + // $templates[] = RADIO_STATION_DIR . '/templates/single-rs-show-content.php'; + $templates[] = $theme_dir . '/templates/single-show-content.php'; + $templates[] = $theme_dir . '/single-show-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; } $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); foreach ( $templates as $template ) { @@ -2036,7 +2183,13 @@ function radio_station_single_content_template( $content, $post_type ) { } // --- enqueue template styles --- - radio_station_enqueue_style( 'templates' ); + // 2.3.3.9: check post type for page template style enqueue + $page_templates = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); + if ( in_array( $post_type, $page_templates ) ) { + radio_station_enqueue_style( 'templates' ); + } + // 2.3.3.9: fire action for enqueueing other template styles + do_action( 'radio_station_enqueue_template_styles', $post_type ); // --- enqueue dashicons for frontend --- wp_enqueue_style( 'dashicons' ); @@ -2077,14 +2230,12 @@ function radio_station_override_linked_show_data( $post, $post_type ) { if ( $linked_fields ) { foreach ( $linked_fields as $key => $switch ) { if ( !$switch ) { - // echo $key . ' - '; if ( 'show_title' == $key ) { $post->post_title = $show_post->post_title; } elseif ( 'show_excerpt' == $key ) { $post->post_excerpt = $show_post->post_excerpt; } elseif ( 'show_content' == $key ) { $post->post_content = $show_post->post_content; - // print_r( $post->post_content ); } } } @@ -2148,7 +2299,6 @@ function radio_station_override_content( $content ) { $override = radio_station_get_show_override( $post->ID, 'show_content' ); if ( false !== $override ) { $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); - // print_r( $override ); $content = $override->post_content; } add_filter( 'the_content', 'radio_station_override_content', 0 ); @@ -2895,6 +3045,9 @@ function radio_station_set_roles() { $wp_roles->add_cap( 'dj', $cap, true ); } } + // 2.3.3.9: fix for existing DJ role old name + $wp_roles->roles['dj']['name'] = __( 'DJ / Host', 'radio_station' ); + $wp_roles->role_names['dj'] = __( 'DJ / Host', 'radio_station' ); // --- add Show Producer role --- // 2.3.0: add equivalent capability role for Show Producer @@ -3095,6 +3248,19 @@ function radio_station_set_roles() { } +// ---------------------------------- +// Admin Fix for DJ / Host Role Label +// ---------------------------------- +// 2.3.3.9: added for user edit screen crackliness +add_filter( 'editable_roles', 'radio_station_role_check_test', 9 ); +function radio_station_role_check_test( $roles ) { + if ( RADIO_STATION_DEBUG && is_admin() ) { + echo "DJ Role: " . print_r( $roles['dj'], true ); + } + $roles['dj']['name'] = __( 'DJ / Host', 'radio-station' ); + return $roles; +} + // --------------------------------- // maybe Revoke Edit Show Capability // --------------------------------- diff --git a/readme.txt b/readme.txt index b26ab5f..0849f14 100644 --- a/readme.txt +++ b/readme.txt @@ -204,13 +204,15 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Improved: Allow for Multiple Override Times (with AJAX Saving) * Added: Link Override to Show Data with selectable Show Fields * Added: Language Archive Shortcode (similar to Genre Archive) +* Added: Display Linked Override Date List on Show Pages * Fixed: Current Show highlighting timer interval cycling * Fixed: Before and After Show classes when no current Show * Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch * Fixed: Multiple host separator display in Current Show Widget * Fixed: Playlist Widget playlist ended label when no next playlist * Fixed: Conflicting duplicate filter name for Show Avatar -* Fixed: time conversions where start/finish Show/Override is equal +* Fixed: Time conversions where start/finish Show/Override is equal +* Fixed: Show page subarchive lists pagination button arrow display = 2.3.3.8 = * Update: Plugin Panel (1.1.7) with Image and Color Picker fields diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 68b6753..f470d47 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -511,6 +511,6 @@ $list .= '
    ' . $newline; // --- hidden iframe for schedule reloading --- -$list .= '' . $newline; +$list .= '' . $newline; echo $list; \ No newline at end of file diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 574e343..4be31ea 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -17,10 +17,7 @@ $post_type = $radio_station_data['show-type'] = $post->post_type; // 2.3.3.6: set new line for easier debug viewing -$newline = ''; -if ( RADIO_STATION_DEBUG ) { - $newline = "\n"; -} +$newline = RADIO_STATION_DEBUG ? "\n" : ''; // --- get schedule time format --- $time_format = (int) radio_station_get_setting( 'clock_time_format', $post_id ); @@ -89,13 +86,10 @@ // --- create show icon display early --- // 2.3.0: converted show links to icons +// 2.3.3.9: add download icon color +// 2.3.3.9: filter icon colors $show_icons = array(); -$icon_colors = array( - 'website' => '#A44B73', - 'email' => '#0086CC', - 'phone' => '#008000', - 'rss' => '#FF6E01', -); +$icon_colors = radio_station_get_icon_colors( 'show-page' ); // --- show home link icon --- // 2.3.3.4: added filter for title attribute @@ -162,7 +156,7 @@ $show_icons = apply_filters( 'radio_station_show_page_icons', $show_icons, $post_id ); // --- set show related defaults --- -$show_latest = $show_posts = $show_playlists = $show_episodes = false; +$show_latest = $show_posts = $show_playlists = false; // --- check for latest show blog posts --- // $latest_limit = radio_station_get_setting( 'show_latest_posts' ); @@ -186,10 +180,6 @@ $show_playlists = radio_station_get_show_playlists( $post_id, array( 'limit' => $limit ) ); } -// --- check for show episodes --- -$episodes_per_page = radio_station_get_setting( 'show_episodes_per_page' ); -$show_episodes = apply_filters( 'radio_station_show_page_episodes', false, $post_id ); - // --- get layout display settings ---- $block_position = radio_station_get_setting( 'show_block_position' ); $section_layout = radio_station_get_setting( 'show_section_layout' ); @@ -206,125 +196,118 @@ // ----------------- // Show Images Block // ----------------- -if ( ( $avatar_id || $thumbnail_id ) || ( count( $show_icons ) > 0 ) || ( $show_file ) ) { - - $image_blocks = array(); - - // --- Show Avatar --- - if ( $avatar_id || $thumbnail_id ) { - // --- get show avatar (with thumbnail fallback) --- - $size = apply_filters( 'radio_station_show_avatar_size', 'medium', $post_id, 'show-page' ); - $attr = array( 'class' => 'show-image' ); - if ( $show_title ) { - $attr['alt'] = $attr['title'] = $show_title; - } - $show_avatar = radio_station_get_show_avatar( $post_id, $size, $attr ); - if ( $show_avatar ) { - if ( $header_id ) { - $class = ' has-header-image'; - } else { - $class = ''; - } - $blocks['show_images'] = '
    '; - $blocks['show_images'] .= $show_avatar; - $blocks['show_images'] .= '
    '; +$image_blocks = array(); + +// --- Show Avatar --- +if ( $avatar_id || $thumbnail_id ) { + // --- get show avatar (with thumbnail fallback) --- + $size = apply_filters( 'radio_station_show_avatar_size', 'medium', $post_id, 'show-page' ); + $attr = array( 'class' => 'show-image' ); + if ( $show_title ) { + $attr['alt'] = $attr['title'] = $show_title; + } + $show_avatar = radio_station_get_show_avatar( $post_id, $size, $attr ); + if ( $show_avatar ) { + if ( $header_id ) { + $class = ' has-header-image'; + } else { + $class = ''; } + $blocks['show_images'] = '
    ' . $newline; + $blocks['show_images'] .= $show_avatar; + $blocks['show_images'] .= '
    ' . $newline; } +} - // --- Show controls --- - // 2.3.3.6: modify check to include social icons or patreon only - if ( ( count( $show_icons ) > 0 ) || $social_icons || $show_patreon || $show_file ) { - - // --- Show Icons --- - if ( count( $show_icons ) > 0 ) { - $image_blocks['icons'] = '
    '; - $image_blocks['icons'] .= implode( "\n", $show_icons ); - $image_blocks['icons'] .= '
    '; - } +// --- Show Icons --- +// 2.3.3.9: remove unnecessary image block condition check +if ( count( $show_icons ) > 0 ) { + $image_blocks['icons'] = '
    ' . $newline; + $image_blocks['icons'] .= implode( $newline, $show_icons ); + $image_blocks['icons'] .= '
    ' . $newline; +} - // --- Social Icons --- - // 2.3.3.6: added filter for social icon display output - if ( $social_icons ) { - $social_icons = apply_filters( 'radio_station_show_social_icons_display', '' ); - if ( '' != $social_icons ) { - $image_blocks['social'] = ''; - } - } +// --- Social Icons --- +// 2.3.3.6: added filter for social icon display output +if ( $social_icons ) { + $social_icons = apply_filters( 'radio_station_show_social_icons_display', '' ); + if ( '' != $social_icons ) { + $image_blocks['social'] = '' . $newline; + } +} - // --- Show Patreon Button --- - $patreon_button = ''; - if ( $show_patreon ) { - $patreon_button .= '
    '; - $title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; - $title = apply_filters( 'radio_station_show_patreon_title', $title, $post_id ); - $patreon_button .= radio_station_patreon_button( $show_patreon, $title ); - $patreon_button .= '
    '; - } - // 2.3.1: added filter for patreon button - $patreon_button = apply_filters( 'radio_station_show_patreon_button', $patreon_button, $post_id ); - if ( '' != $patreon_button ) { - $image_blocks['patreon'] = $patreon_button; - } +// --- Show Patreon Button --- +$patreon_button = ''; +if ( $show_patreon ) { + $patreon_button .= '
    ' . $newline; + $title = __( 'Become a Supporter for', 'radio-station' ) . ' ' . $show_title; + $title = apply_filters( 'radio_station_show_patreon_title', $title, $post_id ); + $patreon_button .= radio_station_patreon_button( $show_patreon, $title ); + $patreon_button .= '
    ' . $newline; +} +// 2.3.1: added filter for patreon button +$patreon_button = apply_filters( 'radio_station_show_patreon_button', $patreon_button, $post_id ); +if ( '' != $patreon_button ) { + $image_blocks['patreon'] = $patreon_button; +} - // --- Show Player --- - // 2.3.0: embed latest broadcast audio player - // 2.3.3.4: add filter for optional title above Show Player (default empty) - // 2.3.3.4: add filter for title text on Show Download link icon - if ( $show_file ) { +// --- Show Player --- +// 2.3.0: embed latest broadcast audio player +// 2.3.3.4: add filter for optional title above Show Player (default empty) +// 2.3.3.4: add filter for title text on Show Download link icon +if ( $show_file ) { - $image_blocks['player'] = '
    ' . $newline; - $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); - if ( $label && ( '' != $label ) ) { - $image_blocks['player'] .= '' . esc_html( $label ) . '
    '; - } - $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; - $player_embed = do_shortcode( $shortcode ); - $image_blocks['player'] .= '
    ' . $newline; - $image_blocks['player'] .= $player_embed . $newline; - $image_blocks['player'] .= '
    ' . $newline; - - // --- Download Audio Icon --- - // 2.3.2: check show download switch - // 2.3.3.8: added aria label to link and hidden to span icon - if ( $show_download ) { - $title = __( 'Download Latest Broadcast', 'radio-station' ); - $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); - $image_blocks['player'] .= '
    ' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '' . $newline; - $image_blocks['player'] .= '
    ' . $newline; - } + $image_blocks['player'] = '
    ' . $newline; + $label = apply_filters( 'radio_station_show_player_label', '', $post_id ); + if ( $label && ( '' != $label ) ) { + $image_blocks['player'] .= '' . esc_html( $label ) . '
    '; + } + $shortcode = '[audio src="' . $show_file . '" preload="metadata"]'; + $player_embed = do_shortcode( $shortcode ); + $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= $player_embed . $newline; + $image_blocks['player'] .= '
    ' . $newline; + + // --- Download Audio Icon --- + // 2.3.2: check show download switch + // 2.3.3.8: added aria label to link and hidden to span icon + if ( $show_download ) { + $title = __( 'Download Latest Broadcast', 'radio-station' ); + $title = apply_filters( 'radio_station_show_download_title', $title, $post_id ); + $image_blocks['player'] .= '
    ' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '' . $newline; + $image_blocks['player'] .= '
    ' . $newline; + } - $image_blocks['player'] .= '
    ' . $newline; - } + $image_blocks['player'] .= '
    ' . $newline; +} - // 2.3.3.6: allow subblock order to be changed - $image_blocks = apply_filters( 'radio_station_show_images_blocks', $image_blocks, $post_id ); - $image_block_order = array( 'icons', 'social', 'patreon', 'player' ); - $image_block_order = apply_filters( 'radio_station_show_image_block_order', $image_block_order, $post_id ); - if ( RADIO_STATION_DEBUG ) { - echo 'Image Block Order: ' . print_r( $image_block_order, true ) . ''; - echo ''; - } +// 2.3.3.6: allow subblock order to be changed +$image_blocks = apply_filters( 'radio_station_show_images_blocks', $image_blocks, $post_id ); +$image_block_order = array( 'icons', 'social', 'patreon', 'player' ); +$image_block_order = apply_filters( 'radio_station_show_image_block_order', $image_block_order, $post_id ); +if ( RADIO_STATION_DEBUG ) { + echo 'Image Block Order: ' . print_r( $image_block_order, true ) . ''; + echo ''; +} - // --- combine image blocks to show images block --- - if ( is_array( $image_blocks ) && ( count( $image_blocks ) > 0 ) - && is_array( $image_block_order ) && ( count( $image_block_order ) > 0 ) ) { - $blocks['show_images'] .= '
    '; - foreach ( $image_block_order as $image_block ) { - if ( isset( $image_blocks[$image_block] ) ) { - $blocks['show_images'] .= $image_blocks[$image_block]; - } - } - $blocks['show_images'] .= '
    ' . $newline; +// --- combine image blocks to show images block --- +if ( is_array( $image_blocks ) && ( count( $image_blocks ) > 0 ) + && is_array( $image_block_order ) && ( count( $image_block_order ) > 0 ) ) { + $blocks['show_images'] .= '
    '; + foreach ( $image_block_order as $image_block ) { + if ( isset( $image_blocks[$image_block] ) ) { + $blocks['show_images'] .= $image_blocks[$image_block]; } - } + $blocks['show_images'] .= '
    ' . $newline; } + // --------------- // Show Meta Block // --------------- @@ -347,7 +330,7 @@ $meta_blocks['hosts'] = '
    ' . $newline; $label = __( 'Hosted by', 'radio-station' ); $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); - $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; $host_count = count( $hosts ); foreach ( $hosts as $host ) { @@ -382,7 +365,7 @@ $meta_blocks['producers'] = '
    ' . $newline; $label = __( 'Produced by', 'radio-station' ); $label = apply_filters( 'radio_station_show_producers_label', $label, $post_id ); - $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; $producer_count = count( $producers ); foreach ( $producers as $producer ) { @@ -422,7 +405,7 @@ } $label = apply_filters( 'radio_station_show_genres_label', $label, $post_id ); $meta_blocks['genres'] = '
    ' . $newline; - $meta_blocks['genres'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['genres'] .= '' . esc_html( $label ) . ': ' . $newline; $genre_links = array(); foreach ( $genres as $genre ) { $genre_link = get_term_link( $genre ); @@ -445,7 +428,7 @@ } $label = apply_filters( 'radio_station_show_languages_label', $label, $post_id ); $meta_blocks['languages'] = '
    ' . $newline; - $meta_blocks['languages'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['languages'] .= '' . esc_html( $label ) . ': ' . $newline; $language_links = array(); foreach ( $languages as $language ) { $lang_label = $language->name; @@ -464,7 +447,7 @@ $meta_blocks['phone'] = '
    '; $label = __( 'Call in', 'radio-station' ); $label = apply_filters( 'radio_station_show_phone_label', $label, $post_id ); - $meta_blocks['phone'] .= '' . esc_html( $label ) . ': ' . $newline; + $meta_blocks['phone'] .= '' . esc_html( $label ) . ': ' . $newline; $meta_blocks['phone'] .= '' . esc_html( $show_phone ) . ''; $meta_blocks['phone'] .= '
    '; } @@ -556,7 +539,7 @@ // 2.3.3.9: added span wrap with class for actual timezone $label = __( 'Timezone', 'radio-station' ); $label = apply_filters( 'radio_station_show_timezone_label', $label, $post_id ); - $blocks['show_times'] .= '' . esc_html( $label ) . ': ' . $newline; + $blocks['show_times'] .= '' . esc_html( $label ) . ': ' . $newline; if ( !isset( $timezone_code ) ) { $blocks['show_times'] .= ''; $blocks['show_times'] .= esc_html( __( 'UTC', 'radio-station' ) ) . $utc_offset; @@ -576,7 +559,6 @@ $blocks['show_times'] .= '' . $newline; $found_encore = false; - $weekdays = radio_station_get_schedule_weekdays(); $now = radio_station_get_now(); foreach ( $weekdays as $day ) { @@ -625,7 +607,7 @@ $show_time .= '' . $newline; // 2.3.3.9: add user show time div - $show_time .= '
    ' . $newline; + $show_time .= '
    ' . $newline; $show_time .= '[' . $newline; $show_time .= ' - ' . $newline; $show_time .= ']' . $newline; @@ -669,9 +651,11 @@ // 2.3.3.9: maybe output linked override times $date_format = apply_filters( 'radio_station_override_date_format', 'j F' ); +$show_past_dates = apply_filters( 'radio_station_override_show_past_dates', false ); $overrides = radio_station_get_linked_override_times( $post_id ); +$scheduled = ''; if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { - $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    '; + $now = radio_station_get_now(); foreach ( $overrides as $override ) { if ( 'yes' != $override['disabled'] ) { @@ -682,30 +666,38 @@ if ( $override_end_time <= $override_start_time ) { $override_end_time = $override_end_time + ( 24 * 60 * 60 ); } - $start_display = radio_station_get_time( $start_data_format, $override_start_time ); - $end_display = radio_station_get_time( $end_data_format, $override_end_time ); - $start_display = radio_station_translate_time( $start_display ); - $end_display = radio_station_translate_time( $end_display ); - $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); - $date = radio_station_get_time( $date_format, $date_time ); - - $blocks['show_times'] .= '
    ' . $newline; - $blocks['show_times'] .= '' . esc_html( $date ) . '' . $newline; - $blocks['show_times'] .= '' . esc_html( $start_display ) . '' . $newline; - $blocks['show_times'] .= ' - ' . $newline; - $blocks['show_times'] .= '' . esc_html( $end_display ) . '' . $newline; - $blocks['show_times'] .= '
    ' . $newline; - - $blocks['show_times'] .= '
    ' . $newline; - $blocks['show_times'] .= '[' . $newline; - $blocks['show_times'] .= '' . $newline; - $blocks['show_times'] .= ' - ' . $newline; - $blocks['show_times'] .= ']' . $newline; - $blocks['show_times'] .= '
    ' . $newline; + // --- maybe filter out past scheduled dates --- + if ( $show_past_dates || ( $override_end_time > $now ) ) { + + $start_display = radio_station_get_time( $start_data_format, $override_start_time ); + $end_display = radio_station_get_time( $end_data_format, $override_end_time ); + $start_display = radio_station_translate_time( $start_display ); + $end_display = radio_station_translate_time( $end_display ); + $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); + $date = radio_station_get_time( $date_format, $date_time ); + + $scheduled .= '
    ' . $newline; + $scheduled .= '' . esc_html( $date ) . '' . $newline; + $scheduled .= '' . esc_html( $start_display ) . '' . $newline; + $scheduled .= ' - ' . $newline; + $scheduled .= '' . esc_html( $end_display ) . '' . $newline; + $scheduled .= '
    ' . $newline; + + $scheduled .= '
    ' . $newline; + $scheduled .= '[' . $newline; + $scheduled .= '' . $newline; + $scheduled .= ' - ' . $newline; + $scheduled .= ']' . $newline; + $scheduled .= '
    ' . $newline; + } } } } +if ( '' != $scheduled ) { + $blocks['show_times'] .= '
    ' . esc_html( __( 'Scheduled Dates', 'radio-station' ) ) . '
    '; + $blocks['show_times'] .= $scheduled . '
    '; +} // --- maybe add link to full schedule page --- // 2.3.3.4: added filters for schedule link title and anchor @@ -753,93 +745,81 @@ // Show Sections // ------------- $sections = array(); -if ( ( strlen( trim( $content ) ) > 0 ) || $show_posts || $show_playlists || $show_episodes ) { - - // --- About Show Tab (Post Content) --- - // 2.3.3.4: added filter for show description label and anchor - $i = 0; - if ( $show_description ) { - - $sections['about']['heading'] = '' . $newline; - $label = __( 'About the Show', 'radio-station' ); - $label = apply_filters( 'radio_station_show_description_label', $label, $post_id ); - $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; - $anchor = __( 'About', 'radio-station' ); - $anchor = apply_filters( 'radio_station_show_description_anchor', $anchor, $post_id ); - $sections['about']['anchor'] = $anchor; - - $sections['about']['content'] = '

    ' . $newline; - $sections['about']['content'] .= '
    ' . $newline; - $sections['about']['content'] .= $show_description; - $sections['about']['content'] .= '
    ' . $newline; - $sections['about']['content'] .= $show_desc_buttons; - $sections['about']['content'] .= '
    ' . $newline; - $i ++; - } - // --- Show Episodes Tab --- - // 2.3.3.4: added filter for show episodes label and anchor - if ( $show_episodes ) { - - $sections['episodes']['heading'] = '' . $newline; - $label = __( 'Show Episodes', 'radio-station' ); - $label = apply_filters( 'radio_station_show_episodes_label', $label, $post_id ); - $sections['episodes']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; - $anchor = __( 'Episodes', 'radio-station' ); - $anchor = apply_filters( 'radio_station_show_episodes_anchor', $anchor, $post_id ); - $sections['episodes']['anchor'] = $anchor; - - $sections['episodes']['content'] = '

    ' . $newline; - $radio_station_data['show-episodes'] = $show_posts; - $shortcode = '[show-episodes-archive per_page="' . $episodes_per_page . '" show="' . $post_id . '"]'; - $shortcode = apply_filters( 'radio_station_show_page_episodes_shortcode', $shortcode, $post_id ); - $sections['episodes']['content'] .= do_shortcode( $shortcode ); - $sections['episodes']['content'] .= '
    ' . $newline; - $i ++; - } +// 2.3.3.9: get label from post type object +$show_post_type = get_post_type_object( RADIO_STATION_SHOW_SLUG ); +$show_label = $show_post_type->labels->singular_name; + +// --- About Show Tab (Post Content) --- +// 2.3.3.4: added filter for show description label and anchor +if ( $show_description ) { + + $sections['about']['heading'] = '' . $newline; + $label = __( 'About the %s', 'radio-station' ); + $label = sprintf( $label, $show_label ); + $label = apply_filters( 'radio_station_show_description_label', $label, $post_id ); + $sections['about']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $anchor = __( 'About', 'radio-station' ); + $anchor = apply_filters( 'radio_station_show_description_anchor', $anchor, $post_id ); + $sections['about']['anchor'] = $anchor; + + $sections['about']['content'] = '

    ' . $newline; + $sections['about']['content'] .= '
    ' . $newline; + $sections['about']['content'] .= $show_description; + $sections['about']['content'] .= '
    ' . $newline; + $sections['about']['content'] .= $show_desc_buttons; + $sections['about']['content'] .= '
    ' . $newline; +} - // --- Show Blog Posts Tab --- - // 2.3.3.4: added filter for show posts label and anchor - if ( $show_posts ) { - - $sections['posts']['heading'] = '' . $newline; - $label = __( 'Show Posts', 'radio-station' ); - $label = apply_filters( 'radio_station_show_posts_label', $label, $post_id ); - $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; - $anchor = __( 'Posts', 'radio-station' ); - $anchor = apply_filters( 'radio_station_show_posts_anchor', $anchor, $post_id ); - $sections['posts']['anchor'] = $anchor; - - $sections['posts']['content'] = '

    ' . $newline; - $radio_station_data['show-posts'] = $show_posts; - $shortcode = '[show-posts-archive per_page="' . $posts_per_page . '" show="' . $post_id . '"]'; - $shortcode = apply_filters( 'radio_station_show_page_posts_shortcode', $shortcode, $post_id ); - $sections['posts']['content'] .= do_shortcode( $shortcode ); - $sections['posts']['content'] .= '
    ' . $newline; - $i ++; - } +// --- Show Episodes Tab --- +// 2.3.3.4: added filter for show episodes label and anchor +// 2.3.3.9: show episodes tab added via sections filter + +// --- Show Blog Posts Tab --- +// 2.3.3.4: added filter for show posts label and anchor +if ( $show_posts ) { + + // 2.3.3.9: get label from post type object + $posts_type = get_post_type_object( 'post' ); + $posts_label = $posts_type->labels->name; + $sections['posts']['heading'] = '' . $newline; + $label = $label = $show_label . ' ' . $posts_label; + $label = apply_filters( 'radio_station_show_posts_label', $label, $post_id ); + $sections['posts']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $anchor = apply_filters( 'radio_station_show_posts_anchor', $posts_label, $post_id ); + $sections['posts']['anchor'] = $anchor; + + $radio_station_data['show-posts'] = $show_posts; + $sections['posts']['content'] = '

    ' . $newline; + $shortcode = '[show-posts-archive per_page="' . $posts_per_page . '" show="' . $post_id . '"]'; + $shortcode = apply_filters( 'radio_station_show_page_posts_shortcode', $shortcode, $post_id ); + $sections['posts']['content'] .= do_shortcode( $shortcode ); + $sections['posts']['content'] .= '
    ' . $newline; +} - // --- Show Playlists Tab --- - // 2.3.3.4: added filter for show playlists label and anchor - if ( $show_playlists ) { - - $sections['playlists']['heading'] = ''; - $label = __( 'Show Playlists', 'radio-station' ); - $label = apply_filters( 'radio_station-show_playlists_label', $label, $post_id ); - $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; - $anchor = __( 'Playlists', 'radio-station' ); - $anchor = apply_filters( 'radio_station_show_playlists_anchor', $anchor, $post_id ); - $sections['playlists']['anchor'] = $anchor; - - $sections['playlists']['content'] = '

    ' . $newline; - $radio_station_data['show-playlists'] = $show_playlists; - $shortcode = '[show-playlists-archive per_page="' . $playlists_per_page . '" show="' . $post_id . '"]'; - $shortcode = apply_filters( 'radio_station_show_page_playlists_shortcode', $shortcode, $post_id ); - $sections['playlists']['content'] .= do_shortcode( $shortcode ); - $sections['playlists']['content'] .= '
    ' . $newline; - $i ++; - } +// --- Show Playlists Tab --- +// 2.3.3.4: added filter for show playlists label and anchor +if ( $show_playlists ) { + + // 2.3.3.9: get label from post type object + $playlists_type = get_post_type_object( RADIO_STATION_PLAYLIST_SLUG ); + $playlist_label = $playlists_type->labels->name; + $sections['playlists']['heading'] = '
    '; + $label = $show_label . ' ' . $playlist_label; + // 2.3.3.9: fix to filter name (replace dash with underscore) + $label = apply_filters( 'radio_station_show_playlists_label', $label, $post_id ); + $sections['playlists']['heading'] .= '

    ' . esc_html( $label ) . '

    ' . $newline; + $anchor = apply_filters( 'radio_station_show_playlists_anchor', $playlists_label, $post_id ); + $sections['playlists']['anchor'] = $anchor; + + $radio_station_data['show-playlists'] = $show_playlists; + $sections['playlists']['content'] = '

    ' . $newline; + $shortcode = '[show-playlists-archive per_page="' . $playlists_per_page . '" show="' . $post_id . '"]'; + $shortcode = apply_filters( 'radio_station_show_page_playlists_shortcode', $shortcode, $post_id ); + $sections['playlists']['content'] .= do_shortcode( $shortcode ); + $sections['playlists']['content'] .= '
    ' . $newline; } + // 2.3.3.8: remove duplicate post_id filter argument $sections = apply_filters( 'radio_station_show_page_sections', $sections, $post_id ); @@ -848,239 +828,216 @@ // === Template Output === // ----------------------- -// --- set content classes --- -$classes = array(); -if ( 'right' == $block_position ) { - $classes[] = 'right-blocks'; -} elseif ( 'top' == $block_position ) { - $classes[] = 'top-blocks'; -} else { - $classes[] = 'left-blocks'; +// --- set content class --- +// 2.3.3.9: simplify content class code +$class = 'left-blocks'; +if ( in_array( $block_position, array( 'left', 'right', 'top' ) ) ) { + $class = $block_position . '-blocks'; } -$class = implode( ' ', $classes ); - -?> - -
    - - '; - $header_image .= ''; - $header_image .= '

    '; - $header_image = apply_filters( 'radio_station_show_page_header_image', $header_image, $post_id ); - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $header_image; - } - - // --- Show Info Blocks --- - // 2.3.3.4: add show-info element ID to div tag - ?> - -
    - 0 ) ) { - foreach ( $block_order as $i => $block ) { - if ( isset( $blocks[$block] ) && ( '' != trim( $blocks[$block] ) ) ) { +echo '' . $newline; +echo '
    ' . $newline; +echo '' . $newline; + + // --- Show Header --- + // 2.3.0: added new optional show header display + $header = radio_station_get_setting( 'show_header_image' ); + if ( $header && $header_id ) { + $size = apply_filters( 'radio_station_show_header_size', 'full', $post_id ); + $header_src = wp_get_attachment_image_src( $header_id, $size ); + $header_url = $header_src[0]; + $header_width = $header_src[1]; + $header_height = $header_src[2]; + $header_image = '
    '; + $header_image .= ''; + $header_image .= '

    '; + $header_image = apply_filters( 'radio_station_show_page_header_image', $header_image, $post_id ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $header_image; + } - // --- set block classes --- - $classes = array( 'show-block' ); - $classes[] = str_replace( '_', '-', $block ); - if ( 0 == $i ) { - $classes[] = 'first-block'; - } elseif ( count( $block_order ) == ( $i + 1 ) ) { - $classes[] = 'last-block'; - } - $class = implode( ' ', $classes ); + // --- Show Info Blocks --- + // 2.3.3.4: add show-info element ID to div tag + echo '
    ' . $newline; + + // --- filter block order --- + $block_order = array( 'show_images', 'show_meta', 'show_times' ); + $block_order = apply_filters( 'radio_station_show_page_block_order', $block_order, $post_id ); + + // --- loop blocks --- + if ( is_array( $block_order ) && ( count( $block_order ) > 0 ) ) { + foreach ( $block_order as $i => $block ) { + if ( isset( $blocks[$block] ) && ( '' != trim( $blocks[$block] ) ) ) { + + // --- set block classes --- + $classes = array( 'show-block' ); + $classes[] = str_replace( '_', '-', $block ); + if ( 0 == $i ) { + $classes[] = 'first-block'; + } elseif ( count( $block_order ) == ( $i + 1 ) ) { + $classes[] = 'last-block'; + } + $class = implode( ' ', $classes ); - // --- output blocks --- - echo '
    '; - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $blocks[$block]; - echo '
    '; + // --- output blocks --- + echo '
    ' . $newline; + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $blocks[$block] . $newline; + echo '
    ' . $newline; - $first = ''; - } - } + $first = ''; } - ?> + } + } -
    + echo '
    ' . $newline; -
    + echo '
    ' . $newline; - + echo '
    ' . $newline; + echo '
    ' . $newline; + echo '' . esc_html( $label ) . '' . $newline; + echo '
    ' . $newline; -
    -
    - -
    - -
    + $radio_station_data['show-latests'] = $show_latest; + $shortcode = '[show-latest-archive show="' . $post_id . '" thumbnails="0" pagination="0" content="none"]'; + $shortcode = apply_filters( 'radio_station_show_page_latest_shortcode', $shortcode, $post_id ); + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo do_shortcode( $shortcode ); + echo '
    ' . $newline; - 0 ) ) - && is_array( $section_order ) && ( count( $section_order ) > 0 ) ) { + // --- Display Show Sections --- + // 2.3.0: filter show sections for display + if ( ( is_array( $sections ) && ( count( $sections ) > 0 ) ) + && is_array( $section_order ) && ( count( $section_order ) > 0 ) ) { - // --- tabs for tabbed layout --- - if ( 'tabbed' == $section_layout ) { + // --- tabs for tabbed layout --- + if ( 'tabbed' == $section_layout ) { - // --- output first section as non-tabbed --- - if ( isset( $sections[$section_order[0]] ) ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $sections[$section_order[0]]['heading']; - echo $sections[$section_order[0]]['content']; + // --- output first section as non-tabbed --- + if ( isset( $sections[$section_order[0]] ) ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $sections[$section_order[0]]['heading']; + echo $sections[$section_order[0]]['content']; + } + unset( $section_order[0] ); + + echo '
    ' . $newline; + + $i = 0; + $found_section = false; + foreach ( $section_order as $section ) { + if ( isset( $sections[$section] ) ) { + $found_section = true; + if ( 0 == $i ) { + $class = "tab-active"; + } else { + $class = "tab-inactive"; } - unset( $section_order[0] ); - - ?> - -
    - '; - echo esc_html( $sections[$section]['anchor'] ); - echo '
    '; - if ( ( $i + 1 ) < count( $sections ) ) { - echo '
     
    '; - } - $i++; - } - } - // 2.3.3.9: add end tab right spacer - if ( $found_section ) { - echo '
     
    '; - } - ?> -
    - - -
    '; - } - - } else { + echo '
    ' . $newline; + echo esc_html( $sections[$section]['anchor'] ) . $newline; + echo '
    ' . $newline; + if ( ( $i + 1 ) < count( $sections ) ) { + echo '
     
    ' . $newline; + } + $i++; + } + } + // 2.3.3.9: add end tab right spacer + if ( $found_section ) { + echo '
     
    ' . $newline; + } + echo '
    ' . $newline; + } - // --- add tab classes to section --- - $classes = array( 'show-tab' ); - if ( 0 == $i ) { - $classes[] = 'tab-active'; - } else { - $classes[] = 'tab-inactive'; + echo '
    ' . $newline; + $i = 0; + foreach ( $section_order as $section ) { + if ( isset( $sections[$section] ) ) { + + if ( 'tabbed' != $section_layout ) { + + // --- section heading --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $sections[$section]['heading']; + + // --- section jump links --- + if ( 'yes' == $jump_links ) { + echo '' . $newline; } - ?> -
    + } else { - + // --- add tab classes to section --- + $classes = array( 'show-tab' ); + if ( 0 == $i ) { + $classes[] = 'tab-active'; + } else { + $classes[] = 'tab-inactive'; + } + $class = implode( ' ', $classes ); + $sections[$section]['content'] = str_replace( 'class="show-section-content"', 'class="' . esc_attr( $class ) . '"', $sections[$section]['content'] ); -
    + } -
    - + // --- section content --- + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $sections[$section]['content'] . $newline; -' . $newline; + + } + echo '
    ' . $newline; +echo '
    ' . $newline; +echo '' . $newline; // --- enqueue show page script --- // 2.3.0: enqueue script instead of echoing -radio_station_enqueue_script( 'radio-station-show-page', array( 'radio-station' ), true ); +radio_station_enqueue_script( 'radio-station-page', array( 'radio-station' ), true ); // --- maybe detect and switch to # tab --- -if ( 'tabbed' == 'section_layout' ) { - $js = "setTimeout(function() { - if (window.location.hash) { - hash = window.location.hash.substring(1); - if (hash.indexOf('show-') > -1) { - tab = hash.replace('show-', ''); - radio_show_tab('about');} - } - } - }, 500);"; - wp_add_inline_script( 'radio-station-show-page', $js ); +// 2.3.3.9: fix to match variable not string +if ( 'tabbed' == $section_layout ) { + $js = "setTimeout(function() {"; + $js .= " if (window.location.hash) {"; + $js .= " hash = window.location.hash.substring(1);"; + $js .= " if (hash.indexOf('show-') > -1) {"; + $js .= " tab = hash.replace('show-', '');"; + $js .= " radio_show_tab(tab);"; + $js .= " }"; + $js .= " }"; + $js .= "}, 500);"; + wp_add_inline_script( 'radio-station-page', $js ); } // 2.3.3.9: turn off doing template flag $radio_station_data['doing-template'] = false; + From a9f7deb049d7fe5f39e30443d5b2134e96e65755 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 8 Jun 2021 17:59:18 +1000 Subject: [PATCH 201/377] dev updates --- CHANGELOG.md | 3 +- css/rs-schedule.css | 20 ++++++++ css/rs-shortcodes.css | 5 -- css/rs-templates.css | 14 ++++-- docs/Manage.md | 8 +--- includes/master-schedule.php | 19 +++++++- includes/post-types-admin.php | 40 +++++++++------- includes/support-functions.php | 72 +++++++++++++++++++++-------- readme.txt | 4 +- templates/master-schedule-list.php | 6 +-- templates/master-schedule-table.php | 15 ++++-- templates/master-schedule-tabs.php | 25 ++++++++-- 12 files changed, 162 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b1c94..d735d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: Multiple host separator display in Current Show Widget * Fixed: Playlist Widget playlist ended label when no next playlist * Fixed: Conflicting duplicate filter name for Show Avatar -* Fixed: Time conversions where start/finish Show/Override is equal -* Fixed: Show page subarchive lists pagination button arrow display +* Fixed: time conversions where start/finish Show/Override is equal ### 2.3.3.8 * Update: Plugin Panel (1.1.7) with Image and Color Picker fields diff --git a/css/rs-schedule.css b/css/rs-schedule.css index ab3cbeb..73e63e1 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -120,6 +120,15 @@ text-decoration: none; } +.master-schedule-table-arrow-left, .master-schedule-table-arrow-right { + display: inline-block; + width: 50%; + line-height: 2em; + font-size: 1em; + vertical-align: top; + cursor: pointer; +} + #master-program-schedule th .shift-left-arrow { float: left; font-size: 2em; @@ -325,6 +334,17 @@ padding: 5px; } +.master-schedule-tabs-loader { + list-style: none; + display: inline-block; + margin: 0; + padding: 0 10px; + font-size: 1em; + line-height: 2em; + vertical-align: top; + cursor: pointer; +} + #master-schedule-tabs .master-schedule-tabs-day { display: inline-block; position: relative; diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index 45036d8..6675a67 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -159,11 +159,6 @@ padding: 5px; } -.show-pagination-button.arrow-button { - padding-top: 4px; - padding-bottom: 6px; -} - .show-pagination-button.active { background-color: transparent; font-weight: bold; diff --git a/css/rs-templates.css b/css/rs-templates.css index 96da9cb..cf51141 100644 --- a/css/rs-templates.css +++ b/css/rs-templates.css @@ -52,7 +52,13 @@ margin-bottom: 20px; } -.show-label { +#show-content .show-info .show-player-label, +#show-content .show-info .show-hosts-label, +#show-content .show-info .show-producers-label, +#show-content .show-info .show-genres-label, +#show-content .show-info .show-languages-label, +#show-content .show-info .show-timezone-label, +#show-content .show-info .show-latest-label { font-weight: bold; } @@ -110,13 +116,13 @@ #show-content .show-icons { vertical-align: top; - margin-top: 12px; + margin-top: 10px; } #show-content .show-icons .show-icon { display: inline-block; margin-right: 24px; - margin-bottom: 12px; + margin-bottom: 10px; } #show-content .show-icons .show-icon span, @@ -216,7 +222,7 @@ /* --------- */ #show-content .show-tabs .show-tab { display: inline-block; - min-width: 100px; + width: 100px; line-height: 40px; font-size: 18px; color: #444444; diff --git a/docs/Manage.md b/docs/Manage.md index 436296a..bd2716c 100644 --- a/docs/Manage.md +++ b/docs/Manage.md @@ -10,13 +10,9 @@ Shows, Overrides and Playlists each have their own standard list page via the Wo You can add Show Shifts from the Show's Edit page. You can click Add Shift as many times as you like to create new Shift entries. For a Shift to be valid it must have all time fields filled in. The Encore field (repeat airing) is optional. You can disable a Shift and it will be unused but still saved. Each Shift entry also has a Duplicate and Remove icon to copy that entry to a new Shift or remove it. Shifts are not altered until you save them via clicking Update for a Show. -### [Pro] Visual Schedule Editor +### [Pro] Visual Shift Editor -[Radio Station Pro](https://radiostation.pro) includes a Visual Schedule Editor to allow you to assign Shifts directly to the Schedule - whether on the Frontend or Backend! On the backend the Visual Schedule Editor is loaded at the top of the Shows and Overrides list pages. And on the Frontend it is loaded wherever the Schedule is displayed (via shortcode or automatic page setting.) - -Show Editors and Administrators can edit existing Shows and Shifts by clicking the edit pen icon next to any Show title on the Schedule, or on any shift time. This will popup a thickbox editing screen for that Show's shifts (or single shift time) where the shift time(s) can be modified and updated. - -Clicking the plus icon will load a popup thickbox screen allowing the adding of either a Show or an Override, with the times filled in based on which timeslot is clicked. Simply modify the time as desired and choose an existing Show or Override to add the timeslot to and Save. The new shift will be added and the window will revert to an edit screen for that Show/Override. +[Radio Station Pro](https://radiostation.pro) includes a Visual Shift Editor to allow you to assign Shifts directly to the Schedule. ... ## Shift Conflict Checking diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 360b5ff..0e4f3e6 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -252,6 +252,9 @@ function radio_station_master_schedule( $atts ) { } } $output .= ''; + // 2.3.3.9: also added schedule start day input + $start_day = ( $atts['start_day'] ) ? $atts['start_day'] : ''; + $output .= ''; // --- enqueue schedule loader script --- $js = radio_station_master_schedule_loader_js( $atts ); @@ -482,10 +485,11 @@ function radio_station_master_schedule_loader_js( $atts ) { } // --- schedule loader function --- - $js = "function radio_load_schedule(direction,view) { + $js = "function radio_load_schedule(direction,view,clear) { if (document.getElementById('schedule-loader-frame')) { view = radio_cookie.get('admin_schedule_view'); radio_load_view(view); return; } + startday = document.getElementById('schedule-start-day').value; startdate = document.getElementById('schedule-start-date').value; activedate = document.getElementById('schedule-active-date').value; if (!view) { @@ -508,7 +512,9 @@ function radio_station_master_schedule_loader_js( $atts ) { url = '" . $loader_url . "&view='+view; timestamp = Math.floor((new Date()).getTime() / 1000); url += '×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; - if (radio.debug) {console.log('Reload View URL: '+url);} + if (startday != '') {url += '&start_day='+startday;} + if (clear) {url += '&clear=1';} + if (radio.debug) {url += '&rs-debug=1'; console.log('Reload View URL: '+url);} if (document.getElementById('schedule-'+view+'-loader').src != url) { document.getElementById('schedule-'+view+'-loader').src = url; } @@ -526,6 +532,11 @@ function radio_station_master_schedule_loader_js( $atts ) { add_action( 'wp_ajax_nopriv_radio_station_schedule', 'radio_station_ajax_schedule_loader' ); function radio_station_ajax_schedule_loader() { + // --- maybe clear cached data --- + if ( isset( $_REQUEST['clear'] ) && ( '1' == $_REQUEST['clear'] ) ) { + radio_station_clear_cached_data( false ); + } + // --- sanitize shortcode attributes --- $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); if ( RADIO_STATION_DEBUG ) { @@ -923,6 +934,7 @@ function radio_station_master_schedule_tabs_js() { // 2.3.3.9: use setInterval instead of setTimeout for highlighting check // 2.3.3.9: check for required elements before executing functions // 2.3.3.9: fix to check before and after current time not show + // 2.3.3.9: adjust responsive tabs for possible loader control presence $js .= "/* Initialize Tabs */ var radio_tabs_init = false; jQuery(document).ready(function() { @@ -1037,6 +1049,9 @@ classes = end.attr('class').split(' '); jQuery('.master-schedule-tabs-day').removeClass('first-tab').removeClass('last-tab').hide(); totalwidth = 0; tabs = 0; firsttab = -1; lasttab = 7; endtabs = false; + if (jQuery('#master-schedule-tabs-loader-left').length) {totalwidth = totalwidth + jQuery('#master-schedule-tabs-loader-left').width();} + if (jQuery('#master-schedule-tabs-loader-right').length) {totalwidth = totalwidth + jQuery('#master-schedule-tabs-loader-right').width();} + for (i = selected; i < 7; i++) { if (!endtabs && (jQuery('.master-schedule-tabs-day.day-'+i).length)) { if ((i > 0) && (i == selected)) {jQuery('.master-schedule-tabs-day.day-'+i).addClass('first-tab'); firsttab = i;} diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 9ccd5f6..2363084 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -889,7 +889,9 @@ function radio_station_show_shifts_metabox() { function radio_station_shifts_list_styles() { // 2.3.3.9: change shift-table-buttons ID to shift-buttons class - $css = "#new-shifts .show-shift {border: 2px dashed green; background-color: #FFFFDD;} + // 2.3.3.9: added maximum width for shift lists + $css = "#shifts-list, #new-shifts {max-width: 900px;} + #new-shifts .show-shift {border: 2px dashed green; background-color: #FFFFDD;} .show-shift {list-style: none; margin-bottom: 10px; border: 2px solid green; background-color: #EEFFEE;} .show-shift select.changed, .show-shift input[checkbox].changed {background-color: #FFFFCC;} .show-shift select option.original {font-weight: bold;} @@ -1980,9 +1982,8 @@ function radio_station_show_save_data( $post_id ) { echo ""; exit; } @@ -2331,7 +2332,8 @@ function radio_station_show_save_data( $post_id ) { parent.document.getElementById('shifts-saved-message').style.display = ''; if (typeof parent.jQuery == 'function') {parent.jQuery('#shifts-saved-message').fadeOut(3000);} else {setTimeout(function() {parent.document.getElementById('shifts-saved-message').style.display = 'none';}, 3000);} - /* form = parent.document.getElementById('shift-save-form'); form.parentNode.removeChild(form); */ + /* form = parent.document.getElementById('shift-save-form'); + if (form) {form.parentNode.removeChild(form);} */ parent.document.getElementById('show_shifts_nonce').value = '" . esc_js( $show_shifts_nonce ) . "'; "; @@ -2378,7 +2380,7 @@ function radio_station_show_save_data( $post_id ) { shiftslist.style.display = '';" . PHP_EOL; // --- reload the current schedule view --- - $js .= "parent.radio_load_schedule(false,false);" . PHP_EOL; + $js .= "parent.radio_load_schedule(false,false,true);" . PHP_EOL; // 2.3.3.6: clear changes may not have been saved window reload message $js .= "if (parent.window.onbeforeunloadset) { @@ -3417,7 +3419,9 @@ function radio_station_schedule_override_metabox() { function radio_station_overrides_list_styles() { // 2.3.3.9: change override-table-buttons to override-buttons - $css = "body.post-type-override #ui-datepicker-div {z-index: 1001 !important;} + // 2.3.3.9: added maximum width for override lists + $css = "#overrides-list, #new-overrides {max-width: 900px;} + body.post-type-override #ui-datepicker-div {z-index: 1001 !important;} .override-list {list-style: none;} .override-list .override-item {display: inline-block; margin-left: 20px;} .override-list .override-item.first-item {margin-left: 10px;} @@ -4064,7 +4068,8 @@ function radio_station_override_save_data( $post_id ) { echo ""; exit; @@ -4406,7 +4411,8 @@ function radio_station_override_save_data( $post_id ) { parent.document.getElementById('overrides-saved-message').style.display = ''; if (typeof parent.jQuery == 'function') {parent.jQuery('#overrides-saved-message').fadeOut(3000);} else {setTimeout(function() {parent.document.getElementById('overrides-saved-message').style.display = 'none';}, 3000);} - form = parent.document.getElementById('override-save-form'); form.parentNode.removeChild(form); + form = parent.document.getElementById('override-save-form'); + if (form) {form.parentNode.removeChild(form);} parent.document.getElementById('show_override_nonce').value = '" . esc_js( $show_override_nonce ) . "'; "; @@ -4427,13 +4433,13 @@ function radio_station_override_save_data( $post_id ) { echo ''; // --- refresh show shifts list --- - $js = ""; exit; @@ -5543,7 +5550,8 @@ function radio_station_playlist_save_data( $post_id ) { parent.document.getElementById('tracks-saved-message').style.display = ''; if (typeof parent.jQuery == 'function') {parent.jQuery('#tracks-saved-message').fadeOut(3000);} else {setTimeout(function() {parent.document.getElementById('tracks-saved-message').style.display = 'none';}, 3000);} - form = parent.document.getElementById('track-save-form'); form.parentNode.removeChild(form); + form = parent.document.getElementById('track-save-form'); + if (form) {form.parentNode.removeChild(form);} parent.document.getElementById('playlist_tracks_nonce').value = '" . esc_js( $playlist_tracks_nonce ) . "'; "; @@ -6454,7 +6462,7 @@ function radio_station_relogin_message() { parent.document.getElementById('" . $type . "s-error-message').style.display = ''; parent.document.getElementById('" . $type . "s-error-message').innerHTML = '" . esc_js( $error ) . "'; form = parent.document.getElementById('" . $type . "-save-form'); - form.parentNode.removeChild(form);" . PHP_EOL; + if (form) {form.parentNode.removeChild(form);}" . PHP_EOL; } // --- filter and output --- diff --git a/includes/support-functions.php b/includes/support-functions.php index 9dcfe37..7091307 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1290,18 +1290,32 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) if ( !$time ) { // 2.3.3: check data global first if ( isset( $radio_station_data['current_schedule'] ) ) { + if ( RADIO_STATION_DEBUG ) { + echo "Using cached schedule pageload data for Now." . PHP_EOL; + } return $radio_station_data['current_schedule']; } else { $show_shifts = get_transient( 'radio_station_current_schedule' ); + if ( RADIO_STATION_DEBUG && $show_shifts ) { + echo "Using cached transient 'radio_station_current_schedule':" . PHP_EOL; + print_r( $show_shifts ); + } } } else { // --- get schedule for time --- // 2.3.2: added transient for time schedule // 2.3.3: check data global first if ( isset( $radio_station_data['current_schedule_' . $time ] ) ) { + if ( RADIO_STATION_DEBUG ) { + echo "Using cached schedule pageload data for '" . $time . "'" . PHP_EOL; + } return $radio_station_data['current_schedule_' . $time ]; } else { $show_shifts = get_transient( 'radio_station_current_schedule_' . $time ); + if ( RADIO_STATION_DEBUG && $show_shifts ) { + echo "Using cached transient 'radio_station_current_schedule_" . $time . "':" . PHP_EOL; + print_r( $show_shifts ); + } } } @@ -1807,9 +1821,28 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } + // --- cache current schedule data --- + // 2.3.2: set temporary transient if time is specified + // 2.3.3: also set global data for current schedule + if ( !isset( $expires ) ) { + $expires = 3600; + } + if ( !$time ) { + $radio_station_data['current_schedule'] = $show_shifts; + set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); + } else { + $radio_station_data['current_schedule_' . $time] = $show_shifts; + delete_transient( 'radio_station_current_schedule_' . $time ); + set_transient( 'radio_station_current_schedule_' . $time, $show_shifts, $expires ); + } + // --- debug point --- if ( RADIO_STATION_DEBUG ) { - $debug = "Show Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; + $debug = ''; + if ( $time ) { + $debug .= "Cached Schedule to radio_station_current_schedule_" . $time . PHP_EOL; + } + $debug .= "Show Schedule: " . print_r( $show_shifts, true ) . PHP_EOL; if ( isset( $current_show ) ) { $debug .= "Current Show: " . print_r( $current_show, true ) . PHP_EOL; } @@ -1823,20 +1856,6 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) radio_station_debug( $debug ); } - // --- cache current schedule data --- - // 2.3.2: set temporary transient if time is specified - // 2.3.3: also set global data for current schedule - if ( !isset( $expires ) ) { - $expires = 3600; - } - if ( !$time ) { - $radio_station_data['current_schedule'] = $show_shifts; - set_transient( 'radio_station_current_schedule', $show_shifts, $expires ); - } else { - $radio_station_data['current_schedule_' . $time] = $show_shifts; - set_transient( 'radio_station_current_schedule_' . $time, $show_shifts, $expires ); - } - // --- filter and return --- // 2.3.2: added time argument to filter // 2.3.3: apply filter only once @@ -4929,20 +4948,33 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { // -------------------------- // Delete Prefixed Transients // -------------------------- -// 2.3.4: added helper for clearing transient data +// 2.3.3.4: added helper for clearing transient data function radio_station_delete_transients_with_prefix( $prefix ) { global $wpdb; - $prefix = $wpdb->esc_like( '_transient_' . $prefix ); - $sql = "SELECT `option_name` FROM $wpdb->options WHERE `option_name` LIKE '%s'"; - $results = $wpdb->get_results( $wpdb->prepare( $sql, $prefix . '%' ), ARRAY_A ); + // 2.3.3.9: fix to prefix by adding trailing underscore + $prefix = $wpdb->esc_like( '_transient_' . $prefix . '_' ); + + // 2.3.3.9: fix to LIKE match and query prepare + $query = "SELECT `option_name` FROM " . $wpdb->prefix . "options WHERE `option_name` LIKE '%" . $prefix . "%'"; + $results = $wpdb->get_results( $query, ARRAY_A ); + if ( RADIO_STATION_DEBUG ) { + echo $query . PHP_EOL . '
    '; + print_r( $results ); + } if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { return; } foreach ( $results as $option ) { - $key = ltrim( $option['option_name'], '_transient_' ); + // $key = ltrim( $option['option_name'], '_transient_' ); + $key = substr( $option['option_name'], 11 ); + if ( RADIO_STATION_DEBUG ) { + echo "Deleting transient and cache object for '" . $key . "'" . PHP_EOL; + } delete_transient( $key ); + // 2.3.3.9: also delete transient cache object by key + wp_cache_delete( $key, 'transient' ); } } diff --git a/readme.txt b/readme.txt index 0849f14..b26ab5f 100644 --- a/readme.txt +++ b/readme.txt @@ -204,15 +204,13 @@ We haven't built an interface between Google Calendar and Radio Station just yet * Improved: Allow for Multiple Override Times (with AJAX Saving) * Added: Link Override to Show Data with selectable Show Fields * Added: Language Archive Shortcode (similar to Genre Archive) -* Added: Display Linked Override Date List on Show Pages * Fixed: Current Show highlighting timer interval cycling * Fixed: Before and After Show classes when no current Show * Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch * Fixed: Multiple host separator display in Current Show Widget * Fixed: Playlist Widget playlist ended label when no next playlist * Fixed: Conflicting duplicate filter name for Show Avatar -* Fixed: Time conversions where start/finish Show/Override is equal -* Fixed: Show page subarchive lists pagination button arrow display +* Fixed: time conversions where start/finish Show/Override is equal = 2.3.3.8 = * Update: Plugin Panel (1.1.7) with Image and Color Picker fields diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index f470d47..ad1ff25 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -13,14 +13,14 @@ // 2.3.3.9: added for non-now schedule displays if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { $start_date = $atts['start_date']; - $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); // --- force display of date and month --- $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; } else { - $start_time = $now; + // 2.3.3.9: set start date to current date + $start_date = $date; } -$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'list' ); +$start_time = radio_station_to_time( $start_date . ' 00:00:00' ); // --- set shift time formats --- // 2.3.2: set time formats early diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 6293e41..6172bc9 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -13,14 +13,15 @@ // 2.3.3.9: added for non-now schedule displays if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { $start_date = $atts['start_date']; - $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); // --- force display of date and month --- $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; } else { - $start_time = $now; + // 2.3.3.9: set start date to current date + $start_date = $date; } -$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'table' ); +$start_time = radio_station_to_time( $start_date . ' 00:00:00' ); +$start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'table', $atts ); // --- set shift time formats --- // 2.3.2: set time formats early @@ -79,7 +80,11 @@ // --- weekday table headings row --- // 2.3.2: added hour column heading id $table .= '
    ' . $newline; -$table .= '' . $newline; +$table .= '' . $newline; foreach ( $weekdays as $i => $weekday ) { @@ -699,7 +704,7 @@ if ( !isset( $cell ) ) { $cell = ''; } - $cell .= $add_link; + $cell .= '
    ' . $add_link . '
    '; } } $cellclass = implode( ' ', $cellclasses ); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 0d2914a..af50fc6 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -14,13 +14,14 @@ // 2.3.3.9: added for non-now schedule displays if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { $start_date = $atts['start_date']; - $start_time = radio_station_to_time( $start_date . ' 00:00:00' ); // --- force display of date and month --- $atts['display_date'] = ( !$atts['display_date'] ) ? '1' : $atts['display_date']; $atts['display_month'] = ( !$atts['display_month'] ) ? 'short' : $atts['display_month']; } else { - $start_time = $now; + // 2.3.3.9: set start date to current date + $start_date = $date; } +$start_time = radio_station_to_time( $start_date . ' 00:00:00' ); $start_time = apply_filters( 'radio_station_schedule_start_time', $start_time, 'tabs' ); // --- set shift time formats --- @@ -71,10 +72,20 @@ $infokeys = array( 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'custom' ); $infokeys = apply_filters( 'radio_station_schedule_tabs_info_order', $infokeys ); -// --- start tabbed schedule output --- +// --- reset loop variables --- $tabs = $panels = ''; $tcount = 0; $start_tab = false; + +// 2.3.3.9: added filter for week loader control +$load_prev = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'left' ); +if ( '' != $load_prev ) { + $tabs .= '
  • ' . $newline; + $tabs .= $load_prev . $newline; + $tabs .= '
  • ' . $newline; +} + +// --- start tabbed schedule output --- // 2.3.0: loop weekdays instead of legacy master list foreach ( $weekdays as $i => $weekday ) { @@ -633,6 +644,14 @@ $panels .= '' . $newline; } +// 2.3.3.9: added filter for week loader control +$load_next = apply_filters( 'radio_station_schedule_loader_control', '', 'tabs', 'right' ); +if ( '' != $load_next ) { + $tabs .= '
  • ' . $newline; + $tabs .= $load_next . $newline; + $tabs .= '
  • ' . $newline; +} + // --- add day tabs to output --- $html = '
    ' . $newline; +// 2.3.3.9: added filters for week loader controls +$table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'left' ); +$table .= apply_filters( 'radio_station_schedule_loader_control', '', 'table', 'right' ); +$table .= '
    "; // --- plugin icon --- + // 1.1.9: add filter for plugin icon url + $icon_url = apply_filters( $args['namespace'] . '_settings_page_icon_url' ); echo ""; @@ -1678,8 +1685,10 @@ public function settings_header() { echo "
    "; - if ( $icon_url ) { + if ( $icon_url ) { echo ""; } echo "
    "; // --- plugin title --- + // 1.1.9: add filter for plugin pagetitle + $title = apply_filters( $args['namespace'] '_setting_page_title', $args['title'] ); echo ""; echo ""; @@ -3059,6 +3068,9 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.1.9 == +// - fix to allow saving of zero value + // == 1.1.8 == // - fix to number step if no min or max value diff --git a/player/images/controls.pdn b/player/images/controls.pdn deleted file mode 100644 index 930b28e7ef2cd88737929bbca87ef683716b98c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12896 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 tk`OE-W|h|WpY$vWpCMf9Kwbru^bRrerV{7BD z<}1kxj!58Ul<;-p0Cxn`-Ayczh{U?DeL2S@&}akPVZgkWV-WD8Bswu0n3AZJfQ64A z4DN$Q`oVnQ7z`Tj9_Y{0Yjr9`pubM6S8(0bN>PBjgpH>$jnL3wkqQSq)CiV1h$RAg z1W@2q1Y8U_2=~>9b>IXpni|1`l5rX>f`?*&6HFz81;Yb2Nm8Q$6T@JElN4T%IF@I` z;fUIBV2CE-czi=*Ft~sTi(oPFY?3&{5G$ufCg~_a3L@abO(6)aFH9^o$HZ!3cnMZu zkjs-0kyxElCseao<_Myc?uVdI1STPq#YV#;VQd(i&Sk*JCZ3YwtUgSNfEzJTCY)yWPKr?m$FO6h(pYMU0*>=C2F2=Q6)Yu%qKOfTVhzE{NURtk@)b#hoJfX( zZH9?VMgNv=Bqh=It?C&=8LsRIUL>Sp{IV}o9 z_R}+?7+gQ7K~HDIMyb6>qUcyQfxwRN!vG3#Qh1V3Zi=Bxq#QWa%rXjzJOoQ6$LMK^ zBC-LglL)9r6~av5MG#`5QJO?jG=(pv!wm*HH8Dt#7#>AK%8=oap<*U498EW)U?^!6 z-b*M&;MFiJkEd7Dlo}e;AVcVb(dtkrC7NPl$&~`Q7A>Q)SR}JZZKgpbenBKIH9RcB zOA<8+WkOk6E7?8bSJRy)WRWyamQpV`G@(5yxmxxB>#Oh4+=p<}JC@KOeV9V*oNM8+s z6{C{lLxRK2v2vOLPlSd zS+Qy+K8YSf0HRhZ)o4&&S}Mg4o0Q0cnne-JLXA|)NuxtVNpuWejWNK3j9k45 z6)a>kj2tgAQXVc=$mK>sFib3DhN{S7s?s-3Rxqu1X&cqsK&>VB}|5r z810K=QxN7vP9&0ul)yyN5Vm>K<7i`=s4bP ze^OSm+YgU0X#rE8Z*Ncs`8Fn~O1_NR+IQ{SM479OTb2NZ!oN*cr#{^!aSAqn*Y^Qz`T8UXO zvftS7^^0f_0RM0O{M8qrbO2+)!442ZfTjf6AaK*4++;mRLO4gN(%2@1OO)UbUN4dJ z9TJ!Vg9QBg0u}-8wYLXN9|4Us$;Rf{4jUUg@D;6r4Qd9~{#Q7BP&<&s{~fOi2GKy7 zz%i|qRP7@?agg!cs2pR{Cw*^Cs=nqc0N%#Toa46E6SCNlKl5=2tC5f=c`CZrX(SMY9ahyAb%4BW*LnDYI; ziys#CZ5IIh>n8AjHo=L$0si$jpwkb4ejuPTFoo_1hyj4C$Hx2(2)xI)Q-l9904xuR zuP1H#8+haoz;Fc1l5>oLIl zfY{ql>96{_xmErMVC73!V1jdbFBtmK*zxw5Z?`E0|N-?c&mXA&~fku#CHQI zFo1wTVKsmO9S1)^d^dmt0|*!}Rs#gkaXC}p0P*kK50<1KzHV%_z{dq&Fj&juD8U;W1XQ#iB0F55P-#qfxr!$PV+U-1SM)tV z-yskXgTa9K&mje7#vmm}A^6u1DLb&H0&hG_qQ!G~GLc55SMu#tY7S4LGdWnVB3#86 zK)^4DLH{k^P!Py~@%^1|%}}em!Mpg|;kNUDJF&Cs+>j3QfdTMIJ-8TPG(5S$&Cj|y#AX@f9I!cf z06=L20khwV84Qj<8gK*J511jK2BZWlCK>41e#QKTcnMO_9}!$@MGOPifxO`cv>y<| zK@G?uRzxb$8T##%B#B)94Ym(*qCcz`U}c46fQv!4aRb^9uuM<`GLIED0_Y4P$vHYJ z9&qcBBmMC}PO{>$!BrqRxdH75yeLourkxcp8t4r99{yiZAU5 z&%0;$hXl#e3Ml{=f~@HVv>zaapax`6E2IeMe1k;&1G%F=BuKATNGZ4w}~l8|2r#07G+gKghu#RsJvO6|l6@>tK-Y{wIP2*~uPYXOT{fLO6&yTDtW= z8GF7!D9~sGd_e60fq)ch16D5Zf!NKr{(u5_{!9K{fPnl0-aZ7R6YxsEUkC)`39ByT zUv*%{TXkT9|FjNFTdNMt(w}r-3R-nw#{HxN6UnLr^X4brqQ7;Vf7OAZZCwXO@=xo) z7`5uako-|M6d(;?^6TT{nrd(D4YFx0fj9!;GtjLo>O{@l=aYw{PChvFJNN{e6X#vs z|MiCcSvmmred(Q@Q;b)j&Nn<~Ij_Cxz`r(a02Y?-v~0e+^V%VUxSADhn|kc+iGv4V zm(TyS(8GDu+I8!0t#=s2b=`A*aaYUs8v`6A6~ykK3Uvq4ShxV6Q;#m(yV4o*F6VQ| z_)R%`Avw+GFFjo1kn4GW!)edo4mi%7a*e)1G_zA(?Q@thB`O8sAvl1X>0W?X@&sRv zj82eiuO!?qn6_&Ahm{8gze`2TYFK>xm&(PS0nyzrhRcgKXRbzl42iqC`PZ}oU62~h z!BL(=&NyuxUAbU=K56Zw*`#(|oLv?|xYFePc~s5p!>QrpcO+k&@fJS+d2Bk`F>vDz z^!-mO65v@450~YqJ#X=y=icTMpHH5c?d!3=@XoE;g{hV)Uv}nx-q7}-K!#ekrtM13 z!@7b|g>{>g_VI=T`EmxO;LUYp`U=(5M;8m4#*VkFi`;VK zn90SGuUq(OXSuPjvnbF~?z^yFboydd_1%x=XxGob83yY2`nEo5lQ{Y=DQ_Ly(3ja> zci?bMN8<3g!=G(z?P=WE#>-BZUesQB3aL>>-d)o)Q8>IkX9kb`oFTKQ@@y;v?k|kZLjT4ccCn%>9RL{zTGy3 zoBekE96h@4(NmRiK6D_?`TFSnq;*^lXSzEiC?YmGdeG^SxKx)hBbbBo$6UTnBAy$O_ zp+lteXIHZJ>`ZQ4w7&e$Qzbrc-w&9uD7c$_#lNOy_Wd`T&R{}PR+cQ;gxxaz_6QsJ z%MeTL3fh(IxX~>M%&hj)mnDjd@l%fFGgs2mpFhmadvxh+{u|kKe1+rgy66aQ_vFt{ z$DM!0_jcI>o6<$df#q9x3m(ttit0`;a_dVugiB^f+eeq5Xf2(rqGaXrCU}eT=iS8C z4op6^Xm)CRS?5}(&o_7GoP1hQdyEsCmFt;TeJ3fw49!c8opY&Z@YutyTqJ;dVPixWq zh1n-y9>V!?8`|o#3v;)-n%I>c71h&hg9j!DU#irS@7KBuA}XxanXZ97iEeV zD`6Ei+kB39*tkuvIr38g+T?x@z{ZD#jy6+<5$`n?qs)FCq^H_N)9mI=3YeCKdoS zImv)>O}FFghNcGd(wH5kQF^T9P+F^DRYR?E)?Dwb&SR6#jJ2RQ{E}4}onN?NZMcH} zG?J8?_AauJey|jI@sVxE-KHHs9eL2v??--=5ks3W*X729PI>`zvA%N2lcLj6y~94t z9hrJ{R&$yAn!L0fE=ini?)FpWM=xCVq_B6fWuhnpPgkw33<-vl{lBQn^3#)hk1y=O z?(XX9I&_X!p&fpj`Eq^NrlO|i=EOjM|J!57jKPef zQmGm1n&#FJJ~#~@ezf}K;+8>m%2jK4;%=?mFDQYb9sv@6Mbs zs8TLhpIEl8ePen$EJ5M@s^u*02KRVhvV7erUP%0g1Do!3cl+Y9lwutTaHrf0pB zJ`R4*7+SeJ;*jgT=g+g#UVr&=rXF6nGA%8w`L3_7FV3N09j&BO(=fQuuH)B=4E^QK zuC9QDvw^2-jvU8M;_Nzl?3g+5`BbNi-n+k8K8^n3Ax%lP>l#q7?fsi>Pa^F7hYwd@ zyy-aM$@Mj7+~6B5N=4rcm#HUKHm~x{-;tYlncXroUVJ->8P~VU@<}k>^~J$QmYdC| zJCz-qTYNXDp8MpCifO*LFm}cmxuv8d*z#xd*3wkJ34yonT=hJiw3*fovCM9~X`2`*oqB4aE5z0Ed263vLUH*9)ppCBV@}yl>oJ6L+Fu&iniBU9V0D8FC5-0PRy^|hdNZgWBc}_WiC0_PV*iu&m9pPy?Y+(b>N{?{l29i zIw-`u_In_!T1|xugm+y$JUy}Rd1A-3PSU7~^dU!|oeO~3eug}(TeO@+wA}69b94S{ zO(%WwoE2?T^NzbY9e9T?>V0r4F1jlz`Oo6i(wVTh$-IN_2zM)IZ5u+!-ka#?=or&; zV&M}{-h}rf)8kS$jyTH5Yk*yjzWW)9su@x}9eF7$E354{=FRt8JX~Y%ICq8Jp0Vp% zN4$OQy>qGq50+jSEtt0c%J_?GHl0{gL^?)Tu(xr!b!kuClbvzK)j)^&-IgO`B3#MxMa`P0l%ps`I)81mf1oZCyQ&EP=%_K06RucMerIMVzn76`mp&#HQVPPz9(Iv>vIaf1=YR|;Z9zV?1W%Vo1kH6e#t~8u)y>z8#g>XS0d-Cq$!B519 zecF2awhjB%V&*lyJ^n;S+fCbCtMs3Iuw1J|jriEu)oXVu;9X)_mEL&}`HxEdb&mlh zx;fL2HC^1W_e;sEyVxvy??sz?`F^kxkNaU47nVYzrH z88bCS4-T{~NbSmMZ5nuopI>w?cT6!&-|M}kx@7;aPiy*q6`pKg>YxhcHuTlwoHJgh z1W$wc&nxY9?mhz@b`!sg5mmc!XHILo@RaFua`oYP4ydF9x4&HRU(@?;divP?GdeX4 zS^N2<^0UXTVK!bQ1d9KtG~Mo))r)zb*OT}GTcdaFd~qZ8F*0Ll!-P6g*^;y3{AmA6 z`|JHjpUk-m5m<9PrNbt9C`Hn#8g>4cA1k>qb^)9>y8#Pk?*(j zPu-lnw9?0N)E3M}L2>oGU0W{vy7*&5gq(ESfyZ!p|NJN<=eMD#{Z&fO(l^YHZCwLP z-sv{q6W4CIBZiK%_cmlB-}@O!m5uCV&n?pu%Y;{7@NTRw*^9PZ@HA{l{&R4u$Ly%= zp+33q^<6EB_TP`ZT1<1SUfh{#2~2hsi&oL9?H;BX zZhHp47tF1D(9*h##%q4pl{5bN#*T%qjD&NG?j6XVm>zuZ@tcFIf|uFxZ#27%NnJ&E zJ^vElKJZ~(X7Tb*XBTu5Zujh($6t8^IXOKjZohtA;iu0_{(7?Eq_VemNqPre-R zTYq2PQ#QB_TW!=DbT9hSX0bv~guBl4f}{`}GZ6K{*y&!SL+W1t0o$sm3_O$2#klS& zTkKxZyJpv`E!9EuPITe%oLfn2?+sgU&yHUW&st%gRrggo6PN|Suv8U3ZcHD6E$wcS*G2?u|HoWQRlHLuS+)aky|Zm~CK=`6G7 zq$xd1uhu&cTfj~`Av+oWQg>F@alofy+$lMH!`9OuCDDI9Lw%WBAsJuec71$xOivvz zOc?0gFxut?ch{qG?7Uj9Iaf-fyZ`v$Sr09ZzP{d4Dy=`#Q`&Z}sb|T&8zD!;ZI(5O z7AGz9@!sjdBpJDM%h`nFUmgb^4MuLUY<&Orbu(R-R*S7QRE%O5qmtK*FE4nWQg%c5 zEH&0FE!f#Hv2e=n^P7C1p7hRYTbTZiTy$fV#kV7(zIIx*Ie&@#cf%;lbK5x$uZQ%Q zEx7%^F1gsc7}ba0Eu6G_gX+$~N6|y04n0Heh^3`j0<)(GmNKQd-{S&~RW{~679}rD z74>Q!E4s=S4;xd&t~y!wG->JU>S^S$;v2cQ_t&mupF;GQyHst%rfOz(D$6IqAN)#t zKrii+oSE~);k;#fQ$^>^i4VWjy_h48sXeue*3zWfIcj!sns)8BBcHCp@+_^H_cJm@ z2OO#v%C2Gq!?zfD=1-??3|(WfyZ$`zUAcC&N5|sVNAeHeTKcplKR(N;Yi-K=B_-;% zlq=@O+6BMEyyfqfyoEoX%AaIPUN>h?)zCMcAsKl`v5&WEdbm|?Wj*Pwz37X&9H(UDv>^LuvvV=S z3~L&mR}KCIS}l4ULadn?|go%DW%8NPWMuDY!PJ0=KHd4>}+#=o49 znVV4`GojqSytrFDvZyKLLi%g^$C=&yum={QOOUv7Ok1*7%hr}r_Oudc&M!A$xe{hg z`}XPy5BGYPp71U$Hs9*mlw7OAKFO7AZNFG}_dvWZzU^HYi*@!`=;7+NoVz;-efnzS z^scr#PrqR$&aW}EnmZ+ZZx_5fM?Jh_*++D~2vubk^M|BBT~@s+Dm=S(3ueZO;p?~T+rM_kp-pykr{iry zD=0JThKt(2xL&!4wwdha?(W{1BFVgaX7(9HO2e2e@6wIOpu_GV9Jey}&3S+bXeBaM za>iXnc#ye@dsPkR<}_KJ9PB7c|NZyuqK<=sQ|$+QPHsBTOxZo+2JCv`g@P7+VA0G{ zraU31{%KqMg567=2x63t=kC{=CZ9W|ol|@!@M6^ab7S)RZYRS(`U)Hmj4-&pWORii|EGme69`F{Wl6!Px? diff --git a/player/images/player-controls-old.png b/player/images/player-controls-old.png deleted file mode 100644 index 39a93b803943e111adfb05e1d7bb086dda792e0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4823 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 diff --git a/player/images/player-controls.png b/player/images/player-controls.png deleted file mode 100644 index 708bb8ee1b4b9b7c7027aba630be76427cd95c5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2301 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2$D%eK~!i%?U-F} z6h{_@Pq%068GjIC8;2N7gta(Y;$;(QW!Pme_EQVkz+S_TY4^|Ik_-3)2oM*$5*M4q z*+j@*As~DtaqP_49-H`cW;`=Jd*14*p6;Hmo@o;$MDi<@tGl}E@#(75RqwR1zFw$P zNcE-`BExXoQMar8a7hYRH6*vzq8 zaZFJ~$lyEvH#bL5pT44JPhZGZR^2eUFZbhTtaHJCtc$;qk{lbH-=F*AjBM6&A_JIBZZSdgD1(c!MX5{zcqqk={R0MVoteW%og)G`eA#U z@`VEJ)vDy+nw01!?{&ooqO+?I#|G!p#mj+UARWgnm-h&zRN|V;&gKp+eYZr#og!6> z6?t<|evUyYavVo6LL3{MtCy}c1)~(jG49{L?+sj~QW1@>3$J-(_Q>G0!;_k(Nms7? zlTs<}$WMU`L@mUG-VX1)lWuP->&d~Vy zxCEh1!uTF1<a)*2nPHBl<9Ipbmx{Xa4x04j$urv8+K~{*y~m*rkq_gYm zKVJrt8oGSB`E^GQF~`zzjOFF!Iz|Bh9RIjnF4O${Jnio8()#+kXiM>HF+F{SYwqb2 zKviB$3{FO;#3jhF!I_?(4g}@CWqOX8N_9|YXPUaYI(b@B)Z5!bBO@b}&1R{)yIXeA zi{D_X*SK!xSm4~bb4R-l`K36KlYipG3F_(Tp~1mHF*^Q)S8|7&$D2z^EV<2Mjs?!$ zyLa`AV~7(OkZBt9(MP|fj*bpGdh}>yK#*u>LOr-Cd6FCpoO}1~#RS#h7;9_Ien_cS ztI_J}Ds66V(%9IkKo0q1!~*Ky@4}KCTXL*8PGWj2h=qQpd>sZ0;}iKh!wKacYk9L`mlu8>-XF@&=;X&=)1)-^>k-NUE)zoD#@`!9Fc>G9pj_<$5dF) z)9A=i`tJKY4G#>_ncsX&pMU;oBFD%aJAz{+c8m}H{VzIpY?w}sjni{Z^rfX0`s<(m z&co=wSw2pzV`PpkITmwl$*~eU#^HDS>HU$D)RpcMHO^C}lh`rpIFWwHaq41}ip3)F z2#RT&nz|hDL+wa%Y;dNgrbJ*-p4zX-*S=lH*h=j>M!)v$7?UW@F^2W_ebSTlTcgB| z5s8X>OtVAOxCiq0R|WEM0*HHk8M-_+ixkb8kXGTkuZSlb7#I+na6dzTC>7}SYOyWHsJ<~W$ByD8{>E6T zxQit=Q7m|w&J5*Ve@}lt{}ZQIhN{$`oG$!9C&mVbL8;%jfJ3Krrk`bHlPN_J&B-4pAIA=J z5<5niHeAOVg5&=F0b1E!rkz#yHx{HgagLGu+EJXuj`78dmtxLUpAs)peSIA?bm)lJ zCONT=kvVo0C$VEZbm))>8U@ps=42e~AL3w>IYte~ihRrBzA<9$=;HYX1Veu8)G_Mp zz$eRQ#eHLp;8@^91{CKQv1V*-ZHa3W6BF8Cpg6}E;)r0N#Ey}tTl^ygiMdBn$2v() zz%d$z$$cD=Z|I9$LU8OD*Y%`CQXiihqnS)@7?D%9e<7GRDL;BZc*-{^Edqk0%jI&S z;J7_CKY6uUV@BRa!Ew|*O0fYweE2X>_ABmdpFVwh1_j5mIa09!Vf(=Wl|9V!;f;BW#5 zIoGaT6D#$tTen0V;#hrsJ>uL8Xm)m1_#CXJY6p%J_PDXJF`As5lr3&Rv$OxT+x-Bl z^x(mRnaRoDxu5hz3k!*hCqf``!C;$E1sh{n?Jo}v`8nPQ!P_n_F4EG{QUD511Yzg* z3y|~Y&kLL4${nGNjV*BOFz5R9>tfVy-@Y9fJ;|}MS=X1x?q%#Iy&OV77cX8E$I()I zS@V0s99!iO0%DGso}k?RzpU3D!wGwU5|uCQ%HOvYCpMtb(NXbp7X`=r75d)1d2Lk9u1(m_D2bP!N0c?+Y? zF^0WS$mzB17+Y`-9Am8f9B+hlj!_8#_ytH3$Ec*?wC5O;>0opHX><@!D;)$BFQWeg XAPepgv7F#&00000NkvXXu0mjf_Yis# diff --git a/player/images/temp.png b/player/images/temp.png deleted file mode 100644 index 3fd6f10401879a686af4d1d1b58fd7fcdef77bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1532 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1(8WaK~z{r%~;D$ zBS#diZu2s4KR^hXAcF!CED`KR_=`r75+%xFB-XRYD4KkbJv)%lC@b)wfEmPsw*dn- z*zTNjtE%12*svKooOX3}S6AJ8>fU;|u6pfoOfI;+y>+jyt{m5O-_`4Nb$xxUs@1Bx zzrRNY>U@mH ztlS$8)es*>iuT(0(lg!Dn@zY~m1sG$h{j zB`~siH9^wByn~BZVJL-_L?$I61UWI_UNt&8ipmWsmd^6y6A%@T$om#HB zJ!*AASKv)s@&UTj=``nraPmKIY;3F*i^bf+!h+0JHnnP(mX_?}i(qqeQ<#!3`#_6r zZ*Ldo=H~Lt%gbwGL9BhgNeRcgAyRPiXuY&vXbf@@tz+l-KM`~jxp3DagkWe)5W(k& zU^O}*#x>Dyp*}&ow+bBnk@GGxSQC0&LAV6zg7y|Pz+5&@FNeA@0+$=!J+~{gG6=y= z|495b>jeQi=85psy&)GGgG7+E5F@vj=jTJ~#NO<}rtoHH3|h!)FQjzkC?G>CUvK{5!6$iK(YNJ7%^HLG(rSh%StrvZHLE8+Y@6j z8noLWg769IlNuBMRq%fW8b)DAnIt5sa|axPJDU*XJ}ir)kV3_0di8x_b?N=rX?k)j z4eiDLR1Xk)JXl{AG>U!FL_-!7=9Z-2qv#QwAm0!aFlTv`#xtCJm5-tjLXaDCS67!5 zL`u#;I0p1GBx1uWLeR!aEI5}UGo`4oy0ktUw;_ZeBTYeqQG%0EMwW3kK|YqCP{uKD zjWL;>ogIavo`gK)@0RY6?jUdW-G`POoOO@UntUxtT`4x-*c4kU5Agqd-YjIZSsvo^ z^Yioi*vYULLmoW1mrA9A*Pv3gqH<|1653oX0>M(2+0+rj(dZCeO6~9cTd+LHi4cMu zkob_#WHJoisQs_|pPD$lKmyturA8QgZ?6!9txgg;F^TY7&~nmgR-@JN#QG@P+uQrZ z&~LQsxPEYg{FtI-)sIQjUAb#w=D`7+R! zIUjKqi(d+rO2wn@R4SF{e88&mL4P-Z<%Y`T@-K`s9336ykQm5RD=RCj*nI@j8*?nA z5(mqZwN3~mV#;Ek-dovDl(~BOuvlMTFN}?ismaMnF%XQ!@ZVn;9_2(LVf!HwR+KqI zwOZ|G^u}d4j!`d?WGI5qJWvyM&1CtL;|$>N;W9?l>vdU#={|alvz6p%v1BrtXBlMw z37OZ_5h6(3+}t?TYBhl)-&+b}7>p!Hj5*6lh)JvhJV(&(3c)rcO_C&NEGC`C_xE@P ig(?XLA_=b+O8o}}R`kcOK1mM%0000 true, ), + // --- [Pro] Player Bar Height --- + 'player_bar_height' => array( + 'type' => 'number', + 'min' => 40, + 'max' => 400, + 'step' => 1, + 'label' => __( 'Player Bar Height', 'radio-station' ), + 'default' => 80, + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), + 'pro' => true, + ), + // --- [Pro] Fade In Player Bar --- 'player_bar_fadein' => array( 'type' => 'number', @@ -1303,13 +1318,13 @@ ); // 2.3.3.8: [temp] remove player options if player not present -if ( !file_exists( $player_file ) ) { - foreach ( $options as $key => $option ) { - if ( 'player_' == substr( $key, 0, 7 ) ) { - unset( $options[$key] ); - } - } -} +// if ( !file_exists( $player_file ) ) { +// foreach ( $options as $key => $option ) { +// if ( 'player_' == substr( $key, 0, 7 ) ) { +// unset( $options[$key] ); +// } +// } +// } // ---------------------- // Plugin Loader Settings diff --git a/readme.txt b/readme.txt index 0c9b732..3eb5450 100644 --- a/readme.txt +++ b/readme.txt @@ -214,6 +214,10 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.4.0.3 = +* Update: Plugin Panel (1.1.9) with zero value save fix +* Refix: missing fix to active day tab on pageload + = 2.4.0.2 = * Fixed: Multiple Player instance IDs * Fixed: Player loading button glow animation @@ -222,6 +226,11 @@ You can now visit your site to make sure nothing is broken. If you experience is * Added: Alternative text positions in Player * Added: Pause button graphics to Player += 2.4.0.1 = +* Fixed: Rounded player play button background corner style +* Fixed: Tabbed schedule active day tab on pageload +* Improved: Radio Clock Widget layout + = 2.4.0 = * Added: Radio Stream Player! * Fixed: Shows archive shortcode with no Shows selected From e7e433117b2cf8db92e027aae93b6a90e1e3a91c Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 5 Aug 2021 20:22:33 +1000 Subject: [PATCH 221/377] dev fix --- loader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.php b/loader.php index 53c884a..b0d5c95 100644 --- a/loader.php +++ b/loader.php @@ -1686,7 +1686,7 @@ public function settings_header() { // --- plugin title --- // 1.1.9: add filter for plugin pagetitle - $title = apply_filters( $args['namespace'] '_setting_page_title', $args['title'] ); + $title = apply_filters( $args['namespace'] . '_settings_page_title', $args['title'] ); echo ""; From e80156e3623d34a063847eacb0a74b8fd3863189 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 6 Aug 2021 09:42:28 +1000 Subject: [PATCH 222/377] dev fix loader --- loader.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/loader.php b/loader.php index 53c884a..513d6ee 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.8 +// Version: 1.1.9 // -------------- // Note: Changelog and structure at end of file. // @@ -716,7 +716,7 @@ public function update_settings() { if ( $this->debug ) { echo 'New Settings for Key ' . $key . ': '; - if ( $newsettings ) { + if ( $newsetting || ( 0 === $newsetting ) || ( '0' === $newsetting ) ) { echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; } else { // 1.1.7 handle if (new) key not set yet @@ -729,16 +729,20 @@ public function update_settings() { } // --- maybe validate new settings --- - if ( $newsettings ) { + // 1.1.9: fix to allow saving of zero value + if ( $newsettings || ( 0 === $newsettings ) || ( '0' === $newsettings ) ) { if ( is_array( $newsettings ) ) { // --- validate array of settings --- // 1.1.9: fix to allow saving of zero value foreach ( $newsettings as $newkey => $newvalue ) { $newsetting = $this->validate_setting( $newvalue, $valid, $validate_args ); + if ( $this->debug ) { + echo 'Validated Setting array value ' . $newvalue . ' to ' . $newsetting; + } if ( $newsetting && ( '' != $newsetting ) ) { $newsettings[$newkey] = $newsetting; - } elseif ( ( 0 === $newsetting ) || ( '0' == $newsetting ) ) { + } elseif ( ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $newsettings[$newkey] = $newsetting; } else { unset( $newsettings[$newkey] ); @@ -758,13 +762,21 @@ public function update_settings() { $values = explode( ',', $newsettings ); $newvalues = array(); foreach ( $values as $value ) { - $newvalues[] = $this->validate_setting( $value, $valid, $validate_args ); + $newvalue = $this->validate_setting( $value, $valid, $validate_args ); + $newvalues[] = $newvalue; + if ( $this->debug ) { + echo 'Validated Setting value ' . $value . ' to ' . $newvalue; + } } $newsettings = implode( ',', $newvalues ); $settings[$key] = $newsettings; } else { $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args ); - if ( $newsetting ) { + // 1.1.9: fix to allow saving of zero value + if ( $this->debug ) { + echo 'Validated Setting single value ' . $newsettings . ' to ' . $newsetting; + } + if ( $newsetting || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $settings[$key] = $newsetting; } } @@ -1686,7 +1698,7 @@ public function settings_header() { // --- plugin title --- // 1.1.9: add filter for plugin pagetitle - $title = apply_filters( $args['namespace'] '_setting_page_title', $args['title'] ); + $title = apply_filters( $args['namespace'] . '_settings_page_title', $args['title'] ); echo "
    "; From 649f5baec607f8dd01ef0545bd1696a4da3440c2 Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 12 Aug 2021 22:56:19 +1000 Subject: [PATCH 223/377] dev updates --- CHANGELOG.md | 1 + includes/legacy.php | 7 +- includes/support-functions.php | 20 ++++-- loader.php | 119 ++++++++++++++++++-------------- player/css/radio-player.css | 8 +++ player/js/radio-player.js | 18 ++--- player/radio-player.php | 122 +++++++++++++++++++++++++-------- radio-station.php | 66 +++++++++++++++++- readme.txt | 15 ++-- 9 files changed, 270 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43190d0..eada865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.4.0.3 * Update: Plugin Panel (1.1.9) with zero value save fix +* Improved: lazy load all fallback player scripts on pageload * Refix: missing fix to active day tab on pageload ### 2.4.0.2 diff --git a/includes/legacy.php b/includes/legacy.php index b1fc4b8..93bc975 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -461,7 +461,12 @@ function radio_station_get_now_playing( $time = false ) { $playlist['tracks'] = $tracks; $playlist['queued'] = $queued; $playlist['played'] = $played; - $playlist['latest'] = array_pop( $tracks ); + // 2.4.0.3: set latest track from queued + if ( count( $queued ) > 0 ) { + $playlist['latest'] = $queued[0]; + } else { + $playlist['latest'] = array(); + } // --- add show and playlist data --- // 2.3.0: add IDs and URLs instead of just playlist URL diff --git a/includes/support-functions.php b/includes/support-functions.php index 8a6a66f..3477e56 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -2279,19 +2279,29 @@ function radio_station_get_current_playlist() { $track[$key] = $value; $entries[$i] = $track; } - if ( 'queued' == $entry['status'] ) { - $queued[] = $entry; - } elseif ( 'played' == $entry['status'] ) { - $played[] = $entry; + // 2.4.0.3: fix for queued and played arrays + foreach ( $track as $key => $value ) { + if ( 'status' == $key ) { + if ( 'queued' == $value ) { + $queued[] = $track; + } elseif ( 'played' == $value ) { + $played[] = $track; + } + } } } + + $latest = array(); + if ( isset( $queued[0] ) ) { + $latest = $queued[0]; + } // --- get the track list for display --- $playlist = array( 'tracks' => $entries, 'queued' => $queued, 'played' => $played, - 'latest' => array_pop( $entries ), + 'latest' => $latest, 'id' => $playlist_id, 'url' => get_permalink( $playlist_id ), 'show' => $show_id, diff --git a/loader.php b/loader.php index 513d6ee..4ea234c 100644 --- a/loader.php +++ b/loader.php @@ -1499,6 +1499,9 @@ public function settings_menu() { if ( !isset( $args['menutitle'] ) ) { $args['menutitle'] = $args['title']; } + // 1.1.9: added filters for page and menu titles + $pagetitle = apply_filters( $namespace . '_settings_page_title', $args['pagetitle'] ); + $menutitle = apply_filters( $namespace . '_settings_menu_title', $args['menutitle'] ); // --- trigger filter plugin menu action --- // (can hook into this to add an admin menu manually using the provided loader args) @@ -1639,16 +1642,16 @@ public function settings_header() { // --- output debug values --- if ( $this->debug ) { - echo "
    Current Settings:
    "; + echo '
    Current Settings:
    '; print_r( $settings ); - echo "

    "; + echo '

    '; - echo "
    Plugin Options:
    "; + echo '
    Plugin Options:
    '; print_r( $this->options ); - echo "

    "; + echo '

    '; if ( isset( $_POST ) ) { - echo "
    Posted Values:
    "; + echo '
    Posted Values:
    '; foreach ( $_POST as $key => $value ) { echo esc_attr( $key ) . ': ' . print_r( $value, true ) . '
    '; } @@ -1681,42 +1684,53 @@ public function settings_header() { .readme:hover {text-decoration:underline;}"; // --- open header table --- - echo "

    "; - echo "" . esc_html( $args['title'] ) . ""; + echo "" . esc_html( $title ) . ""; echo "

    "; echo "" . esc_html( $title ) . ""; echo "

    "; echo "" . esc_html( $title ) . ""; echo "

    "; + echo '
    '; // --- plugin icon --- // 1.1.9: add filter for plugin icon url - $icon_url = apply_filters( $args['namespace'] . '_settings_page_icon_url' ); - echo ""; + echo ''; - echo "
    "; + $icon_url = apply_filters( $namespace . '_settings_page_icon_url', $icon_url ); + echo ''; if ( $icon_url ) { - echo ""; + echo ''; } - echo ""; + echo ''; - echo ""; + echo '
    '; // --- plugin title --- // 1.1.9: add filter for plugin pagetitle - $title = apply_filters( $args['namespace'] . '_settings_page_title', $args['title'] ); - echo ""; + $title = apply_filters( $namespace . '_settings_page_title', $args['title'] ); + echo ''; - echo ""; + echo ''; // --- plugin version --- - echo ""; + // 1.1.9: add filter for plugin version + $version = apply_filters( $namespace . '_settings_page_version', 'v' . $args['version'] ); + echo ''; - echo "'; + } - echo "

    "; - echo "" . esc_html( $title ) . ""; - echo "

    '; + echo '' . esc_html( $title ) . ''; + echo '

    v" . esc_html( $args['version'] ) . "

    ' . esc_html( $version ) . '

    "; + // --- subtitle --- + // 1.1.9: added optional subtitle filter display + $subtitle = apply_filters( $namespace . '_settings_page_subtitle', '' ); + if ( '' != $subtitle ) { + echo '
    '; + echo '

    ' . esc_html( $subtitle ) . '

    '; + echo '
    "; + echo '
    '; + + echo '
    '; // ---- plugin author --- // 1.0.8: check if author URL is set if ( isset( $args['author_url'] ) ) { - echo "" . esc_html( __( 'by' ) ) . " "; - echo "" . esc_html( $args['author'] ) . "

    "; + echo '' . esc_html( __( 'by' ) ) . ' '; + echo '' . esc_html( $args['author'] ) . '

    '; } // --- readme / docs / support links --- @@ -1725,20 +1739,20 @@ public function settings_header() { // 1.1.0: added title attributes to links $links = array(); if ( isset( $args['home'] ) ) { - $links[] = "" . esc_html( __( 'Home' ) ) . ""; + $links[] = '' . esc_html( __( 'Home' ) ) . ''; } if ( !isset( $args['readme'] ) || ( false !== $args['readme'] ) ) { $readme_url = add_query_arg( 'action', $namespace . '_readme_viewer', admin_url( 'admin-ajax.php' ) ); - $links[] = "" . esc_html( __( 'Readme' ) ) . ""; + $links[] = '' . esc_html( __( 'Readme' ) ) . ''; } if ( isset( $args['docs'] ) ) { - $links[] = "" . esc_html( __( 'Docs' ) ) . ""; + $links[] = '' . esc_html( __( 'Docs' ) ) . ''; } if ( isset( $args['support'] ) ) { - $links[] = "" . esc_html( __( 'Support' ) ) . ""; + $links[] = '' . esc_html( __( 'Support' ) ) . ''; } if ( isset( $args['development'] ) ) { - $links[] = "" . esc_html( __( 'Dev' ) ) . ""; + $links[] = '' . esc_html( __( 'Dev' ) ) . ''; } // 1.0.9: change filter from _plugin_links to disambiguate @@ -1750,28 +1764,28 @@ public function settings_header() { // --- author icon --- if ( $author_icon_url ) { - echo "
    "; + echo ''; // 1.0.8: check if author URL is set for link if ( isset( $args['author_url'] ) ) { - echo ""; + echo ''; } - echo ""; + echo ''; if ( isset( $args['author_url'] ) ) { - echo ""; + echo ''; } } - echo "
    "; + echo '
    '; - echo "
    "; + echo ''; - echo ""; + echo ''; // --- plugin supporter links --- // 1.0.1: set rate/share/donate links and texts // 1.0.8: added filters for rate/share/donate links - echo "
    "; + echo '
    '; // --- output updated and reset messages --- if ( isset( $_GET['updated'] ) ) { @@ -1843,23 +1857,23 @@ public function settings_header() { $message = $settings['title'] . ' ' . __( 'Settings Reset!' ); } if ( isset( $message ) ) { - echo ""; + echo ''; // phpcs:ignore WordPress.Security.OutputNotEscaped echo $this->message_box( $message, false ); - echo ""; + echo ''; } } else { // --- maybe output welcome message --- if ( isset( $_REQUEST['welcome'] ) && ( 'true' == $_REQUEST['welcome'] ) ) { if ( isset( $args['welcome'] ) ) { - echo ""; + echo ''; echo $this->message_box( $args['welcome'], false ); - echo ""; + echo ''; } } } - echo "
    "; + echo '
    '; } // ------------- @@ -1870,7 +1884,7 @@ public function settings_page() { $namespace = $this->namespace; // --- open page wrapper --- - echo "
    "; + echo '
    '; do_action( $namespace . '_admin_page_top' ); @@ -1885,7 +1899,7 @@ public function settings_page() { do_action( $namespace . '_admin_page_bottom' ); // --- close page wrapper --- - echo "
    "; + echo '
    '; } // -------------- @@ -3082,6 +3096,7 @@ function radio_station_settings_row( $option, $setting ) { // == 1.1.9 == // - fix to allow saving of zero value +// - added filters for plugin page settings header sections // == 1.1.8 == // - fix to number step if no min or max value diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 6a9b04e..5080be3 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -304,6 +304,14 @@ } /* @end */ +/* @roup Now Playing Info */ +.rp-now-playing { + font-size: 14px; +} +.rp-now-playing-title, .rp-now-playing-artist, .rp-now-playing-album { + display: inline-block; +} + /* @group Station and Show Info */ .rp-station-image, .rp-station-text, .rp-show-text, .rp-show-image { display: inline-block; diff --git a/player/js/radio-player.js b/player/js/radio-player.js index ef1b473..c448caf 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -121,7 +121,7 @@ function radio_player_load_stream(script, instance, data, start) { /* set channel ID for instance */ if (!instance) {instance = radio_player_default_instance();} radio_data.types[instance] = 'stream'; radio_data.channels[instance] = data; - if (radio_player.debug) {console.log('Set Stream Data '+channel+' on Instance '+instance);} + if (radio_player.debug) {console.log('Set Stream Data '+data+' on Instance '+instance);} /* load the audio stream */ data = radio_player_check_format(data); script = data.script; @@ -135,7 +135,7 @@ function radio_player_load_file(script, instance, data, start) { /* set channel ID for instance */ if (!instance) {instance = radio_player_default_instance();} radio_data.types[instance] = 'file'; radio_data.channels[instance] = data; - if (radio_player.debug) {console.log('Set File Data '+channel+' on Instance '+instance);} + if (radio_player.debug) {console.log('Set File Data '+data+' on Instance '+instance);} /* load the audio stream */ data = radio_player_check_format(data); script = data.script; @@ -229,31 +229,31 @@ function radio_player_play_on_load(player, script, instance) { // --- check/load a player script --- function radio_player_check_script(script) { loading = false; head = document.getElementsByTagName('head')[0]; - funcs = radio_player.settings.ajaxurl+'?action=radio_station_player_script&script='+script; + /* funcs = radio_player.settings.ajaxurl+'?action=radio_station_player_script&script='+script; */ if (script == 'amplitude') { if (typeof window.Amplitude == 'undefined') { if (radio_player.debug) {console.log('Dynamically Loading Amplitude Player Script...');} el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el); loading = true; } - if (typeof radio_player_amplitude == 'undefined') { + /* if (typeof radio_player_amplitude == 'undefined') { el = document.createElement('script'); el.src = funcs; head.appendChild(el); loading = true; - } + } */ } else if (script == 'jplayer') { if (typeof jQuery.jPlayer == 'undefined') { if (radio_player.debug) {console.log('Dynamically Loading jPlayer Script...');} el = document.createElement('script'); el.src = radio_player.scripts.jplayer; head.appendChild(el); loading = true; } - if (typeof radio_player_jplayer == 'undefined') { + /* if (typeof radio_player_jplayer == 'undefined') { el = document.createElement('script'); el.src = funcs; head.appendChild(el); loading = true; - } + } */ } else if (script == 'howler') { if (typeof window.Howl == 'undefined') { if (radio_player.debug) {console.log('Dynamically Loading Howler Player Script...');} el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el); loading = true; } - if (typeof radio_player_howler == 'undefined') { + /* if (typeof radio_player_howler == 'undefined') { el = document.createElement('script'); el.src = funcs; head.appendChild(el); loading = true; - } + } */ } /* else if ( ( script == 'mediaelement') && (typeof mejs == 'undefined' ) ) { el = document.createElement('script'); el.src = radio_player.scripts.media; head.appendChild(el); el = document.createElement('script'); el.src = radio_player.scripts.elements; head.appendChild(el); diff --git a/player/radio-player.php b/player/radio-player.php index cf96b75..f03a09a 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -8,27 +8,32 @@ // - Player Output // - Player Shortcode // - Player AJAX Display +// - Sanitize Shortcode Values +// - Sanitize Values // - Media Elements Interface // === Player Scripts === +// - Enqueue Player Javasscripts // - Enqueue Player Script -// - Enqueue Player Functions -// - Enqueue Amplitude -// - Enqueue JPlayer -// - Enqueue Howler -// - Enqueue Media Elements +// - Lazy Load Script Fallbacks +// - Enqueue Amplitude Javascript +// - Enqueue JPlayer Javascript +// - Enqueue Howler Javascript +// * Enqueue Media Element Javascript +// - Dynamic Load Script via AJAX // - Get Player Settings // - User State Iframe // - AJAX Update User State // - Load Amplitude Function // - Load JPlayer Function // - Load Howler Function -// - Load Media Element Function +// * Load Media Element Function // - Get Default Player Script // - Enqueue Player Styles // - Player Control Styles // === Standalone Compatibility === // - Output Script Tag // - Output Style Tag +// - Validate Boolean // - Escape JS // - Escape HTML // - Escape URL @@ -228,7 +233,10 @@ function radio_station_player_output( $args = array() ) { } // --- filter player output args --- - $args = apply_filters( 'radio_station_player_output_args', $args, $instance ); + // 2.4.0.3: added missing function_exists wrapper + if ( function_exists( 'apply_filters' ) ) { + $args = apply_filters( 'radio_station_player_output_args', $args, $instance ); + } // --- maybe debug output arguments --- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { @@ -271,7 +279,8 @@ function radio_station_player_output( $args = array() ) { if ( ( '0' != (string)$args['image'] ) && ( 0 !== $args['image'] ) && ( '' != $args['image'] ) ) { $image = '' . PHP_EOL; if ( function_exists( 'apply_filters' ) ) { - $image = apply_filters( 'radio_station_player_station_image_tag', $image, $args['image'], $atts, $instance ); + // 2.4.0.3: fix atts to args in third filter argument + $image = apply_filters( 'radio_station_player_station_image_tag', $image, $args['image'], $args, $instance ); } if ( !is_string( $image ) ) { $image = ''; @@ -399,11 +408,10 @@ function radio_station_player_output( $args = array() ) { // $html['no-solution'] .= '
    ' . PHP_EOL; // --- Current Track --- - // TODO: for future stream metadata display ? $html['track'] = '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; - $html['track'] .= '
    ' . PHP_EOL; + $html['track'] .= '
    ' . PHP_EOL; + $html['track'] .= '
    ' . PHP_EOL; + $html['track'] .= '
    ' . PHP_EOL; $html['track'] .= '
    ' . PHP_EOL; // --- set section order --- @@ -444,10 +452,15 @@ function radio_station_player_output( $args = array() ) { // --- set alternative text sections --- // 2.4.0.2: added for alternative display methods + // 2.4.0.3: added missing function_exists wrappers $station_text_alt = '
    ' . $station_text_html . '
    ' . PHP_EOL; - $station_text_alt = apply_filters( 'radio_station_player_station_text_alt', $station_text_alt, $args, $instance ); + if ( function_exists( 'apply_filters' ) ) { + $station_text_alt = apply_filters( 'radio_station_player_station_text_alt', $station_text_alt, $args, $instance ); + } $show_text_alt = '
    ' . $show_text_html . '
    ' . PHP_EOL; - $show_text_alt = apply_filters( 'radio_station_player_show_text_alt', $show_text_alt, $args, $instance ); + if ( function_exists( 'apply_filters' ) ) { + $show_text_alt = apply_filters( 'radio_station_player_show_text_alt', $show_text_alt, $args, $instance ); + } // --- create player from sections --- $player = $html['player_open']; @@ -456,14 +469,15 @@ function radio_station_player_output( $args = array() ) { // --- create control interface --- // 2.4.0.2: added alternative text sections + // 2.4.0.3: fix to alternative text variables $player .= $html['interface_open']; - $player .= $html['station-text-alt']; + $player .= $station_text_alt; foreach ( $control_order as $control ) { if ( isset( $html[$control] ) ) { $player .= $html[$control]; } } - $player .= $html['show-text-alt']; + $player .= $show_text_alt; $player .= $html['interface_close']; } elseif ( isset( $html[$section] ) ) { @@ -476,7 +490,10 @@ function radio_station_player_output( $args = array() ) { $player .= $html['player_close']; // --- filter and return --- - $player = apply_filters( 'radio_station_player_html', $player, $args, $instance ); + // 2.4.0.3: added missing function_exists wrappers + if ( function_exists( 'apply_filters' ) ) { + $player = apply_filters( 'radio_station_player_html', $player, $args, $instance ); + } return $player; } @@ -489,6 +506,11 @@ function radio_station_player_output( $args = array() ) { } function radio_station_player_shortcode( $atts ) { + // 2.4.0.3: fix for when no attributes passed + if ( !is_array( $atts ) ) { + $atts = array(); + } + // --- maybe debug shortcode attributes -- if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { echo 'Passed Radio Player Shortcode Attributes: '; @@ -595,7 +617,7 @@ function radio_station_player_shortcode( $atts ) { } // --- filter attributes --- - // 2.4.0.1: move fitler to before merging + // 2.4.0.1: move filter to before merging if ( function_exists( 'apply_filters' ) ) { $atts = apply_filters( 'radio_station_player_shortcode_attributes', $atts ); } @@ -610,6 +632,12 @@ function radio_station_player_shortcode( $atts ) { } } + // --- maybe debug shortcode attributes -- + if ( isset( $_REQUEST['player-debug'] ) && ( '1' == $_REQUEST['player-debug'] ) ) { + echo 'Combined Radio Player Shortcode Attributes: '; + echo print_r( $atts, true ) . ''; + } + // --- maybe get station title --- if ( '' == $atts['title'] ) { if ( defined( 'RADIO_PLAYER_TITLE' ) ) { @@ -619,14 +647,16 @@ function radio_station_player_shortcode( $atts ) { } elseif ( function_exists( 'apply_filters' ) ) { $atts['title'] = apply_filters( 'radio_station_player_default_title', '' ); } - } elseif ( '0' == (string)$atts['title'] ) { + } elseif ( ( '0' == $atts['title'] ) || ( 0 === $atts['title'] ) ) { + // --- allows disabling via 0 attribute value --- + // 2.4.0.3: allow for string or integer value match $atts['title'] = ''; } // --- maybe get station image --- if ( $atts['image'] ) { // note: converts attribute switch to URL - if ( 1 == $atts['image'] ) { + if ( ( '1' == $atts['image'] ) || ( 1 == $atts['image'] ) ) { if ( defined( 'RADIO_PLAYER_IMAGE' ) ) { $atts['image'] = RADIO_PLAYER_IMAGE; } elseif ( function_exists( 'radio_station_get_setting' ) ) { @@ -1084,17 +1114,14 @@ function radio_station_player_enqueue_script( $script ) { if ( ( 'amplitude' == $script ) && !isset( $radio_player['enqeued_amplitude'] ) ) { radio_station_player_enqueue_amplitude( true ); - $js = radio_station_player_script_amplitude(); } elseif ( ( 'jplayer' == $script ) && !isset( $radio_player['enqeued_jplayer'] ) ) { radio_station_player_enqueue_jplayer( true ); - $js = radio_station_player_script_jplayer(); } elseif ( ( 'howler' == $script ) && !isset( $radio_player['enqeued_howler'] ) ) { radio_station_player_enqueue_howler( true ); - $js = radio_station_player_script_howler(); } // elseif ( ( 'mediaelements' == $script ) && !isset( $radio_player['enqeued_mediaelements'] ) ) { @@ -1102,6 +1129,11 @@ function radio_station_player_enqueue_script( $script ) { // $js = radio_station_player_script_mediaelements(); // } + // 2.4.0.3: load all player scripts regardless + $js .= radio_station_player_script_howler(); + $js .= radio_station_player_script_jplayer(); + $js .= radio_station_player_script_amplitude(); + // --- append any pageload scripts --- if ( function_exists( 'apply_filters') ) { $pageload = apply_filters( 'radio_station_player_pageload_script', '' ); @@ -1130,6 +1162,29 @@ function radio_station_player_enqueue_script( $script ) { $radio_player['enqeued_' . $script] = true; } +// -------------------------- +// Lazy Load Script Fallbacks +// -------------------------- +// 2.4.0.3: lazy load fallback scripts on pageload to cache them +add_filter( 'radio_station_player_pageload_script', 'radio_station_player_load_script_fallbacks' ); +function radio_station_player_load_script_fallbacks( $js ) { + + global $radio_player; + $js .= "head = document.getElementsByTagName('head')[0]; "; + if ( !isset( $radio_player['enqueued_howler'] ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el);"; + } + if ( !isset( $radio_player['enqueued_jplayer'] ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.jplayer; head.appendChild(el);"; + } + if ( !isset( $radio_player['enqueued_amplitude'] ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el);"; + } + $js .= PHP_EOL; + + return $js; +} + // ---------------------------- // Enqueue Amplitude Javascript // ---------------------------- @@ -1493,9 +1548,10 @@ function radio_station_player_get_settings() { // --- set main radio stream data --- $js .= "radio_data.state.data = {};" . PHP_EOL; if ( function_exists( 'apply_filters' ) ) { - // note: this is the main stream data filter hooked into by Radio Station plugin $station = ( isset( $state['station'] ) ) ? $state['station'] : 0; - $data = apply_filters( 'radio_station_player_data', false, $state['station'] ); + // note: this is the main stream data filter hooked into by Radio Station plugin + // 2.4.0.3: fix for uninitialized string offset + $data = apply_filters( 'radio_station_player_data', false, $station ); } if ( $data && is_array( $data ) ) { foreach ( $data as $key => $value ) { @@ -2039,7 +2095,7 @@ function radio_station_player_script_jplayer() { } // --------------------------- -// Load MediaElements Function +// Load Media Element Function // --------------------------- // Usage ref: https://github.com/mediaelement/mediaelement/blob/master/docs/usage.md // API ref: https://github.com/mediaelement/mediaelement/blob/master/docs/api.md @@ -2183,7 +2239,9 @@ function radio_station_player_enqueue_styles( $script = false, $skin = false ) { } else { $path = dirname( __FILE__ ) . '/css/radio-player' . $suffix . '.css'; } - echo 'Style Path: ' . $path . ''; + if ( defined( 'RADIO_PLAYER_DEBUG' ) && RADIO_PLAYER_DEBUG ) { + echo 'Style Path: ' . $path . ''; + } if ( file_exists( $path ) ) { $version = filemtime( $path ); if ( function_exists( 'wp_enqueue_style' ) ) { @@ -2393,7 +2451,10 @@ function radio_station_player_control_styles( $instance ) { } } } - $colors = apply_filters( 'radio_station_player_control_colors', $colors, $instance ); + // 2.4.0.3: added missing function_exists wrapper + if ( function_exists( 'apply_filters' ) ) { + $colors = apply_filters( 'radio_station_player_control_colors', $colors, $instance ); + } // --- Play Button --- // 2.4.0.2: fix to glowingloading animation reference @@ -2475,7 +2536,10 @@ function radio_station_player_control_styles( $instance ) { $css .= PHP_EOL . $container . " .rp-volume-thumb {display: none; width: 18px;}" . PHP_EOL; // --- filter and return --- - $css = apply_filters( 'radio_station_player_control_styles', $css, $instance ); + // 2.4.0.3: added missing function_exists wrapper + if ( function_exists( 'apply_filters' ) ) { + $css = apply_filters( 'radio_station_player_control_styles', $css, $instance ); + } return $css; } diff --git a/radio-station.php b/radio-station.php index c2bc11f..51118a1 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.2 +Version: 2.4.0.3 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -90,7 +90,7 @@ define( 'RADIO_STATION_FILE', __FILE__ ); define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); define( 'RADIO_STATION_BASENAME', plugin_basename( __FILE__ ) ); -define( 'RADIO_STATION_HOME_URL', 'https://netmix.com/radio-station/' ); +define( 'RADIO_STATION_HOME_URL', 'https://radiostation.pro/' ); define( 'RADIO_STATION_DOCS_URL', 'https://radiostation.pro/docs/' ); define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); define( 'RADIO_STATION_PRO_URL', 'https://radiostation.pro/' ); @@ -684,6 +684,44 @@ 'pro' => true, ), + // --- [Pro] Display Current Show --- + // 2.4.0.3: added for current show display + 'player_bar_currentshow' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Current Show', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro] Display Metadata --- + // 2.4.0.3: added for now playing metadata display + 'player_bar_nowplaying' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Now Playing', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), + 'pro' => true, + ), + + // --- Metadata URL --- + // 2.4.0.3: added for alternative stream metadata URL + 'player_bar_metadata' => array( + 'type' => 'url', + 'label' => __( 'Metadata URL', 'radio-station' ), + 'default' => '', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), + 'pro' => true, + ), + // TODO: additional CSS input textarea field ? // === Master Schedule Page === @@ -1230,12 +1268,34 @@ // === Roles / Capabilities / Permissions === // 2.3.0: added new capability and role options + // --- Show Editing Permission Note --- + // 2.4.0.3: added role to show assignment note + 'permissions_show_role_note' => array( + 'type' => 'note', + 'label' => __( 'Show Editing Permissions', 'radio-station' ), + 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) + . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Playlist Editing Role Note --- + // 2.4.0.3: added role to playlist assignment note + 'permissions_playlistow_role_note' => array( + 'type' => 'note', + 'label' => __( 'Playlist Permissions', 'radio-station' ), + 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + // --- Show Editor Role Note --- 'show_editor_role_note' => array( 'type' => 'note', 'label' => __( 'Show Editor Role', 'radio-station' ), 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) - . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ), + . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) + . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), 'tab' => 'roles', 'section' => 'permissions', ), diff --git a/readme.txt b/readme.txt index 3eb5450..79574c9 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.8 -Stable tag: 2.4.0.2 +Stable tag: 2.4.0.3 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -84,11 +84,11 @@ Love Radio Station and ready for more? As the free version develops, we have als = How do I get started with Radio Station? = -Read the [Quickstart Guide](https://netmix.com/radio-station/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. +Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. = Where can I find the full plugin documentation? = -The latest documentation can be found online at [NetMix.com](https://netmix.com/radio-station/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. = How do I schedule a Show? = @@ -96,7 +96,7 @@ Simply create a new show via Add Show in the Radio Station plugin menu in the Ad = How do I display a full schedule of my Station's shows? = -In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](https://netmix.com/radio-station/docs/shortcodes/#master-schedule-shortcode) ) +In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](https://radiostation.pro/docs/shortcodes/#master-schedule-shortcode) ) = I've scheduled all my Shows, but some are not showing up on the program schedule? = @@ -117,7 +117,7 @@ The default styles for Radio Station have intionally kept fairly minimal so as t = What Widgets are available with this plugin? = The following Widgets are available to add via the WordPress Appearance -> Widgets page: -Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](https://netmix.com/radio-station/docs/widgets/) for more details on these Widgets. +Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](https://radiostation.pro/docs/widgets/) for more details on these Widgets. = Do the Widgets reload automatically? = @@ -125,7 +125,7 @@ Current Show, Upcoming Shows and Current Playlist widgets do not refresh automat = What Shortcodes are available with this plugin? = -See the [Shortcode Documentation](https://netmix.com/radio-station/docs/shortcodes/) for more details and a full list of possible Attributes for these Shortcodes: +See the [Shortcode Documentation](https://radiostation.pro/docs/shortcodes/) for more details and a full list of possible Attributes for these Shortcodes: * `[master-schedule]` - Master Program Schedule Display * `[current-show]` - Current Show Widget @@ -141,7 +141,7 @@ Note old shortcode aliases will still work in current and future versions to pre = I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? = -There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](https://netmix.com/radio-station/docs/roles/) +There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/roles/) = How do I change the Show Avatar displayed in the sidebar widget? = @@ -216,6 +216,7 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.4.0.3 = * Update: Plugin Panel (1.1.9) with zero value save fix +* Improved: lazy load all fallback player scripts on pageload * Refix: missing fix to active day tab on pageload = 2.4.0.2 = From e13d4166668ee8dac22fb076605ea354643a8819 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 20 Aug 2021 10:55:03 +1000 Subject: [PATCH 224/377] dev updates incl #372 --- CHANGELOG.md | 7 +- css/rs-shortcodes.css | 2 +- includes/post-types-admin.php | 19 ++-- includes/shortcodes.php | 4 + includes/support-functions.php | 10 +- loader.php | 179 ++++++++++++++++++++++----------- player/js/radio-player.js | 33 +++--- player/radio-player.php | 101 +++++++++++++------ radio-station.php | 68 +++++++++++-- readme.txt | 7 +- 10 files changed, 310 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eada865..c4dab74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.3 -* Update: Plugin Panel (1.1.9) with zero value save fix -* Improved: lazy load all fallback player scripts on pageload +* Update: Plugin Panel (1.2.0) with zero value save and tab fix +* Added: option to disable player audio fallback scripts +* Added: option to hide various volume controls +* Improved: lazy load player audio fallback scripts * Refix: missing fix to active day tab on pageload +* Fixed: player volume slider background position (cross-browser) ### 2.4.0.2 * Fixed: Multiple Player instance IDs diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index a6d8058..007e8e1 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -125,7 +125,7 @@ /* Show Subarchive Shortcodes */ /* -------------------------- */ -.show-post { +.show-post, .show-playlist, .show-host, .show-producer, .show-episode { padding-top: 5px; padding-bottom: 5px; } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 4d5dfc7..829d308 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2310,7 +2310,8 @@ function radio_station_show_save_data( $post_id ) { wp_set_post_terms( $override_id, $language_term_ids, RADIO_STATION_LANGUAGES_SLUG ); } if ( ( 'yes' == $sync_genres ) || ( 'yes' == $sync_languages ) ) { - radio_station_clear_cached_data( $override_id ); + // 2.4.0.3: added second argument to cache clear + radio_station_clear_cached_data( $override_id, RADIO_STATION_OVERRIDE_SLUG ); } } } @@ -2320,7 +2321,8 @@ function radio_station_show_save_data( $post_id ) { // 2.3.3: remove current show transient // 2.3.3.9: just call clear cached data function if ( $show_meta_changed || $show_shifts_changed ) { - radio_station_clear_cached_data( $post_id ); + // 2.4.0.3: added second argument to cache clear + radio_station_clear_cached_data( $post_id, RADIO_STATION_SHOW_SLUG ); } if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { @@ -2434,13 +2436,15 @@ function radio_station_show_delete( $post_id, $post ) { if ( !property_exists( $post, 'post_type' ) ) { $post = get_post( $post_id ); } - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + // 2.4.0.3: also trigger clear data for playlists + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); if ( !in_array( $post->post_type, $post_types ) ) { return; } // --- clear all cached schedule data --- - radio_station_clear_cached_data( $post_id ); + // 2.4.0.3: added second argument to cache clear + radio_station_clear_cached_data( $post_id, $post->post_type ); // --- clear from unique shift IDs list --- // 2.3.3.9: added to keep unique ID list from bloating over time @@ -4405,7 +4409,8 @@ function radio_station_override_save_data( $post_id ) { // --- clear cached schedule if changed --- // 2.3.3.9: clear cache on data/schedule change if ( $meta_changed || $sched_changed ) { - radio_station_clear_cached_data( $post_id ); + // 2.4.0.3: added second argument to cache clear + radio_station_clear_cached_data( $post_id, RADIO_STATION_OVERRIDE_SLUG ); } // --- update overrides table when AJAX saving --- @@ -5547,7 +5552,7 @@ function radio_station_playlist_save_data( $post_id ) { // 2.3.4: add previous show transient // 2.3.3.9: just call new clear cache function if ( $show_changed ) { - radio_station_clear_cached_data( $show ); + radio_station_clear_cached_data( $show, RADIO_STATION_PLAYLIST_SLUG ); } } } @@ -5885,7 +5890,7 @@ function radio_station_post_save_data( $post_id ) { // 2.3.4: add previous show transient // 2.3.3.9: just call clear cache data function if ( $changed ) { - radio_station_clear_cached_data( $post_id ); + radio_station_clear_cached_data( $post_id, 'post' ); } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index c270fc5..7463ac2 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -1458,6 +1458,10 @@ function radio_station_show_list_shortcode( $type, $atts ) { unset( $radio_station_data['show-' . $type . 's'] ); $show_id = $radio_station_data['show-id']; + if ( RADIO_STATION_DEBUG ) { + echo 'Stored Show Posts (' . $type . '):' . print_r( $posts, true ) . ''; + } + } else { // --- check for show ID (required at minimum) --- if ( !$atts['show'] ) { diff --git a/includes/support-functions.php b/includes/support-functions.php index 3477e56..49c9b0b 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -762,6 +762,9 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { $default_data = apply_filters( 'radio_station_cached_data', false, $datatype, $show_id ); if ( $default_data ) { return $default_data; + if ( RADIO_STATION_DEBUG ) { + echo 'Using Cacehed Data:' . print_r( $default_data, true ) . ''; + } } } @@ -5016,7 +5019,8 @@ function radio_station_delete_transients_with_prefix( $prefix ) { // Clear Cached Data // ----------------- // 2.3.3.9: made into separate function -function radio_station_clear_cached_data( $post_id = false ) { +// 2.4.0.3: added second argument for post type +function radio_station_clear_cached_data( $post_id = false, $post_type = false ) { // --- clear main schedule transients --- // 2.3.3: remove current show transient @@ -5033,8 +5037,8 @@ function radio_station_clear_cached_data( $post_id = false ) { // --- maybe clear show meta data --- if ( $post_id ) { - do_action( 'radio_station_clear_data', 'show', $post_id ); - do_action( 'radio_station_clear_data', 'show_meta', $post_id ); + do_action( 'radio_station_clear_data', $post_type, $post_id ); + do_action( 'radio_station_clear_data', $post_type . '_meta', $post_id ); } // --- maybe send directory ping --- diff --git a/loader.php b/loader.php index 4ea234c..3cba23f 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.1.9 +// Version: 1.2.0 // -------------- // Note: Changelog and structure at end of file. // @@ -466,16 +466,11 @@ public function update_settings() { // 1.0.2: fix to namespace key typo in isset check // 1.0.3: only use namespace not settings key // 1.0.9: check page is set and matches slug - if ( !isset( $_REQUEST['page'] ) ) { + if ( !isset( $_REQUEST['page'] ) || ( $_REQUEST['page'] != $args['slug'] ) ) { return; } - if ( $_REQUEST['page'] != $args['slug'] ) { - return; - } - if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) { - return; - } - if ( 'yes' != $_POST[$args['namespace'] . '_update_settings'] ) { + $updatekey = $args['namespace'] . '_update_settings'; + if ( !isset( $_POST[$updatekey] ) || ( 'yes' != $_POST[$args['namespace'] . '_update_settings'] ) ) { return; } @@ -668,7 +663,8 @@ public function update_settings() { } if ( is_string( $valid ) ) { $newsettings = $posted; - } elseif ( is_array( $valid ) ) { + } elseif ( is_array( $valid ) && ( count( $valid ) > 0 ) ) { + // 1.2.0: fix to check for empty valid array foreach ( $posted as $i => $value ) { if ( !in_array( $value, $valid ) ) { unset( $posted[$i] ); @@ -716,7 +712,8 @@ public function update_settings() { if ( $this->debug ) { echo 'New Settings for Key ' . $key . ': '; - if ( $newsetting || ( 0 === $newsetting ) || ( '0' === $newsetting ) ) { + // 1.2.0: added isset check for newsetting + if ( isset( $newsetting ) && ( $newsetting || ( 0 === $newsetting ) || ( '0' === $newsetting ) ) ) { echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; } else { // 1.1.7 handle if (new) key not set yet @@ -822,6 +819,28 @@ public function update_settings() { } } + // --- save settings tab --- + // 1.2.0: validate and save current settings tab + if ( isset( $_POST['settingstab'] ) ) { + $currenttab = $_POST['settingstab']; + $tabs = array(); + foreach ( $options as $key => $option ) { + if ( isset ( $option['tab'] ) && !in_array( $option['tab'], $tabs ) ) { + $tabs[] = $option['tab']; + } + } + if ( count( $tabs ) > 0 ) { + if ( in_array( $currenttab, $tabs ) ) { + $settings['settingstab'] = $currenttab; + } elseif ( in_array( 'general', $tabs ) ) { + $settings['settingstab'] = 'general'; + } else { + $settings['settingstab'] = $tabs[0]; + } + } + } + + // --- update the plugin settings --- $settings['savetime'] = time(); update_option( $args['option'], $settings ); @@ -1543,20 +1562,42 @@ public function plugin_links( $links, $file ) { array_unshift( $links, $settings_link ); // --- maybe add Pro upgrade link --- - // TODO: check Freemius upgrade/addon URLs - // if ( isset( $args['hasplans'] && $args['hasplans'] ) { - // // TODO: check if premium is already installed - // $upgrade_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); - // $upgrade_link = "" . esc_html( __('Upgrade to Pro' ) ) . ""; - // array_unshift( $links, $upgrade_link ); - // } + if ( isset( $args['hasplans'] ) && $args['hasplans'] ) { + + // -- internal upgrade link --- + // TODO: check if premium is already installed + if ( isset( $args['upgrade_link'] ) ) { + $upgrade_url = $args['upgrade_link']; + $upgrade_target = ''; + } else { + $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); + $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : ''; + } + $upgrade_link = "" . esc_html( __('Upgrade' ) ) . ""; + array_unshift( $links, $upgrade_link ); + + // --- external pro link --- + // 1.2.0: added pro link + if ( isset( $args['pro_link'] ) ) { + $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; + $pro_link = "" . esc_html( __('Pro Details' ) ) . ""; + array_unshift( $links, $pro_link ); + } + } // --- maybe add Addons link --- - // if ( isset($args['hasaddons'] && $args['hasaddons' ) { - // $addons_url = add_query_arg( 'page', $args['slug'], admin_url( 'admin.php' ) ); - // $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; - // array_unshift( $links, $addons_link ); - // } + // 1.2.0: activated add-ons link + if ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) { + if ( isset( $args['addons_link'] ) ) { + $addons_url = $args['addons_link']; + $addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : ''; + } else { + $addons_url = add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ); + $addons_target = ''; + } + $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; + array_unshift( $links, $addons_link ); + } } return $links; @@ -1679,10 +1720,6 @@ public function settings_header() { } $author_icon_url = apply_filters( $namespace . '_author_icon_url', $author_icon_url ); - // --- plugin header styles --- - echo ""; - // --- open header table --- echo ''; @@ -2041,9 +2078,10 @@ public function settings_table() { $this->scripts[] = $script; // --- start settings form --- + // 1.2.0: remove unused prefix on settings tab name attribute echo ""; echo ""; - echo ""; + echo ""; wp_nonce_field( $args['slug'] . '_update_settings' ); // --- maybe set hidden debug input --- @@ -2321,21 +2359,36 @@ public function setting_row( $option ) { } else { // TODO: add check if already Pro version ? + // (currently done by removing pro key/value pair in Pro code) if ( isset( $option['pro'] ) && $option['pro'] ) { // --- Pro version setting (teaser) --- + // 1.2.0: improved handling of upgrade links $row .= ' - - - - - - - - - - - - - - - - - - get_id(), - 'account', - 'deactivate_license', - fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), - '', - array( 'plugin_id' => $addon_id ), - false, - true - ); - - $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); - $downgrade_confirmation_message = sprintf( - $downgrade_x_confirm_text, - ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), - $plan->title, - $human_readable_license_expiration - ); - - $after_downgrade_message = ! $license->is_block_features ? - sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : - sprintf( $after_downgrade_blocking_text, $plan->title ); - - if ( ! $license->is_lifetime() && $is_active_subscription ) { - $buttons[] = fs_ui_get_action_button( - $fs->get_id(), - 'account', - 'downgrade_account', - esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), - '', - array( 'plugin_id' => $addon_id ), - false, - false, - false, - ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), - 'POST' - ); - } - } else if ( $is_paid_trial ) { - $buttons[] = fs_ui_get_action_button( - $fs->get_id(), - 'account', - 'cancel_trial', - esc_html( $cancel_trial_text ), - '', - array( 'plugin_id' => $addon_id ), - false, - false, - 'dashicons dashicons-download', - $cancel_trial_confirm_text, - 'POST' - ); - } else if ( ! $has_feature_enabled_license ) { - $premium_licenses = $fs_addon->get_available_premium_licenses(); - - if ( ! empty( $premium_licenses ) ) { - $premium_license = $premium_licenses[0]; - $has_multiple_premium_licenses = ( 1 < count( $premium_licenses ) ); - - if ( ! $has_multiple_premium_licenses ) { - $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); - $site = $fs_addon->get_site(); - - $buttons[] = fs_ui_get_action_button( - $fs->get_id(), - 'account', - 'activate_license', - esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), - ($has_multiple_premium_licenses ? - 'activate-license-trigger ' . $fs_addon->get_unique_affix() : - ''), - array( - 'plugin_id' => $addon_id, - 'license_id' => $premium_license->id, - ), - true, - true - ); - - $is_license_activation_added = true; - } - } - } - } - -// if ( 0 == count( $buttons ) ) { - if ( $fs_addon->is_premium() && ! $is_license_activation_added ) { - $fs_addon->_add_license_activation_dialog_box(); - - $buttons[] = fs_ui_get_action_button( - $fs->get_id(), - 'account', - 'activate_license', - ( ! $has_feature_enabled_license ) ? - fs_esc_html_inline( 'Activate License', 'activate-license', $slug ) : - fs_esc_html_inline( 'Change License', 'change-license', $slug ), - 'activate-license-trigger ' . $fs_addon->get_unique_affix(), - array( - 'plugin_id' => $addon_id, - ), - (! $has_feature_enabled_license), - true - ); - - $is_license_activation_added = true; - } - - if ( $fs_addon->has_paid_plan() ) { - // Add sync license only if non of the other CTAs are visible. - $buttons[] = fs_ui_get_action_button( - $fs->get_id(), - 'account', - $fs->get_unique_affix() . '_sync_license', - fs_esc_html_x_inline( 'Sync', 'as synchronize', 'sync', $slug ), - '', - array( 'plugin_id' => $addon_id ), - false, - true - ); - } -// } - } else if ( ! $show_upgrade ) { - if ( $fs->is_addon_installed( $addon_id ) ) { - $addon_file = $fs->get_addon_basename( $addon_id ); - - if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { - $buttons[] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), - fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), - $activate_text - ); - } - } else { - if ( $fs->is_allowed_to_install() ) { - $buttons[] = sprintf( - '%s', - wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), - fs_text_inline( 'Install Now', 'install-now', $slug ) - ); - } else { - $buttons[] = sprintf( - '%s', - $fs->_get_latest_download_local_url( $addon_id ), - esc_html( $download_latest_text ) - ); - } - } - } - - if ( $show_upgrade ) { - $buttons[] = sprintf( ' %s', - esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . - '&TB_iframe=true&width=600&height=550' ) ), - esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), - esc_attr( $addon_info['title'] ), - ( $fs_addon->has_free_plan() ? - $upgrade_text : - fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) - ); - } - - $buttons_count = count( $buttons ); - ?> - - - - - - - is_addon_installed( $addon_id ); - ?> - - - - - - - - - - \ No newline at end of file diff --git a/freemius/templates/account/partials/deactivate-license-button.php b/freemius/templates/account/partials/deactivate-license-button.php index 123b092..e69de29 100644 --- a/freemius/templates/account/partials/deactivate-license-button.php +++ b/freemius/templates/account/partials/deactivate-license-button.php @@ -1,36 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/freemius/templates/account/partials/index.php b/freemius/templates/account/partials/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/account/partials/index.php +++ b/freemius/templates/account/partials/index.php @@ -1,3 +0,0 @@ -get_slug(); - $site = $VARS['site']; - $main_license = $VARS['license']; - $is_data_debug_mode = $fs->is_data_debug_mode(); - $is_whitelabeled = $fs->is_whitelabeled(); - $has_paid_plan = $fs->has_paid_plan(); - $is_premium = $fs->is_premium(); - $main_user = $fs->get_user(); - $blog_id = $site['blog_id']; - - $install = $VARS['install']; - $is_registered = ! empty( $install ); - $license = null; - $trial_plan = $fs->get_trial_plan(); - $free_text = fs_text_inline( 'Free', 'free', $slug ); - - if ( $is_whitelabeled && $fs->is_delegated_connection( $blog_id ) ) { - $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id ); - } -?> - data-install-id="id ?>"> - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/freemius/templates/account/payments.php b/freemius/templates/account/payments.php index fd54c9b..e69de29 100644 --- a/freemius/templates/account/payments.php +++ b/freemius/templates/account/payments.php @@ -1,59 +0,0 @@ -get_slug(); - - $payments = $fs->_fetch_payments(); - - $show_payments = ( is_array( $payments ) && 0 < count( $payments ) ); - - if ( $show_payments ) : -?> -
    -
    -

    - -
    -
    '; $upgrade_link = false; - if ( $args['hasplans'] || $args['hasaddons'] ) { - $upgrade_link = add_query_arg( 'page=', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); - $target = ''; + if ( ( isset( $args['hasplans'] ) && $args['hasplans'] ) + || ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) ) { + $upgrade_link = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); + $upgrade_target = ''; } elseif ( isset( $args['upgrade_link'] ) ) { $upgrade_link = $args['upgrade_link']; - $target = ' target="_blank"'; + $upgrade_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : ''; } - if ( $upgrade_link ) { - $row .= __( 'Available in Pro Version.' ) . '
    '; - $row .= '' . esc_html( __( 'Click Here to Upgrade!' ) ) . ''; + if ( isset( $args['pro_link'] ) ) { + $pro_link = $args['pro_link']; + $pro_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : ''; + } + if ( $upgrade_link || $pro_link ) { + $row .= __( 'Available in Pro.' ) . '
    '; + if ( $upgrade_link ) { + $row .= '' . esc_html( __( 'Upgrade Now' ) ) . ''; + } + if ( $upgrade_link && $pro_link ) { + $row .= ' | '; + } + if ( $pro_link ) { + $row .= '' . esc_html( __( 'Pro Details' ) ) . ''; + } } else { $row .= esc_html( __( 'Coming soon in Pro version!' ) ); } @@ -2570,7 +2623,9 @@ public function setting_row( $option ) { $row .= ' ' . $option['suffix']; } - } elseif ( 'text' == $type ) { + } elseif ( ( 'text' == $type ) || ( 'csv' == $type ) ) { + + // 1.2.0: re-added missing csv field type // --- text inputs --- $class = 'setting-text'; @@ -2735,6 +2790,12 @@ public function setting_styles() { // --- page styles --- $styles[] = '#wrapbox {margin-right: 20px;}'; + // --- plugin header styles --- + // 1.2.0: moved from plugin header section + $styles[] = '.pluginlink {text-decoration:none;}'; + $styles[] = '.smalllink {font-size:11px;}'; + $styles[] = '.readme:hover {text-decoration:underline;}'; + // --- settings tab styles --- // 1.1.0: added max-width:100% to select input $styles[] = '.settings-tab-button {display:inline-block; font-size:15px; padding:7px 14px; margin-right:20px; border-radius:7px;}'; @@ -2815,8 +2876,8 @@ function radio_station_load_prefixed_functions() { // the below functions use the function name to grab and load the corresponding class method // all function name suffixes here must be two words for the magic namespace grabber to work // ie. _add_settings, because the namespace is taken from *before the second-last underscore* - if ( !function_exists( 'radio_station_get_radio_station_slug' ) ) { - function radio_station_get_radio_station_slug( $f ) { + if ( !function_exists( 'radio_station_get_namespace_from_function' ) ) { + function radio_station_get_namespace_from_function( $f ) { return substr( $f, 0, strrpos( $f, '_', ( strrpos( $f, '_' ) - strlen( $f ) - 1 ) ) ); } } @@ -2827,7 +2888,7 @@ function radio_station_get_radio_station_slug( $f ) { // 2.3.0: added function for getting loader class instance if ( !function_exists( 'radio_station_loader_instance' ) ) { function radio_station_loader_instance() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); return $GLOBALS[$namespace . '_instance']; } @@ -2839,7 +2900,7 @@ function radio_station_loader_instance() { // 2.3.0: added function for getting Freemius class instance if ( !function_exists( 'radio_station_freemius_instance' ) ) { function radio_station_freemius_instance() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); return $GLOBALS[$namespace . '_freemius']; } @@ -2851,7 +2912,7 @@ function radio_station_freemius_instance() { // 1.1.1: added function for getting plugin data if ( !function_exists( 'radio_station_plugin_data' ) ) { function radio_station_plugin_data() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->plugin_data(); @@ -2864,7 +2925,7 @@ function radio_station_plugin_data() { // 1.1.2: added function for getting plugin version if ( !function_exists( 'radio_station_plugin_version' ) ) { function radio_station_plugin_version() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->plugin_version(); @@ -2876,7 +2937,7 @@ function radio_station_plugin_version() { // ----------------- if ( !function_exists( 'radio_station_pro_namespace' ) ) { function radio_station_pro_namespace( $pronamespace ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->pro_namespace( $pronamespace ); } @@ -2891,7 +2952,7 @@ function radio_station_pro_namespace( $pronamespace ) { // ------------ if ( !function_exists( 'radio_station_add_settings' ) ) { function radio_station_add_settings() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->add_settings(); } @@ -2902,7 +2963,7 @@ function radio_station_add_settings() { // ------------ if ( !function_exists( 'radio_station_default_settings' ) ) { function radio_station_default_settings( $key = false ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->default_settings( $key ); @@ -2914,7 +2975,7 @@ function radio_station_default_settings( $key = false ) { // ----------- if ( !function_exists( 'radio_station_get_options' ) ) { function radio_station_get_options() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->options; @@ -2926,7 +2987,7 @@ function radio_station_get_options() { // ----------- if ( !function_exists( 'radio_station_get_setting' ) ) { function radio_station_get_setting( $key, $filter = true ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->get_setting( $key, $filter ); @@ -2939,7 +3000,7 @@ function radio_station_get_setting( $key, $filter = true ) { // 1.0.9: added missing get_settings prefixed function if ( !function_exists( 'radio_station_get_settings' ) ) { function radio_station_get_settings( $filter = true ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->get_settings( $filter ); @@ -2951,7 +3012,7 @@ function radio_station_get_settings( $filter = true ) { // -------------- if ( !function_exists( 'radio_station_reset_settings' ) ) { function radio_station_reset_settings() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->reset_settings(); } @@ -2962,7 +3023,7 @@ function radio_station_reset_settings() { // --------------- if ( !function_exists( 'radio_station_update_settings' ) ) { function radio_station_update_settings() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->update_settings(); } @@ -2973,7 +3034,7 @@ function radio_station_update_settings() { // --------------- if ( !function_exists( 'radio_station_delete_settings' ) ) { function radio_station_delete_settings() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->delete_settings(); } @@ -2986,7 +3047,7 @@ function radio_station_delete_settings() { // ----------- if ( !function_exists( 'radio_station_message_box' ) ) { function radio_station_message_box( $message, $echo = false ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; return $instance->message_box( $message, $echo ); @@ -2998,7 +3059,7 @@ function radio_station_message_box( $message, $echo = false ) { // --------------- if ( !function_exists( 'radio_station_settings_header' ) ) { function radio_station_settings_header() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->settings_header(); } @@ -3009,7 +3070,7 @@ function radio_station_settings_header() { // ------------- if ( !function_exists( 'radio_station_settings_page' ) ) { function radio_station_settings_page() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->settings_page(); } @@ -3021,7 +3082,7 @@ function radio_station_settings_page() { // 1.0.9: added for standalone setting table output if ( !function_exists( 'radio_station_settings_table' ) ) { function radio_station_settings_table() { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->settings_table(); } @@ -3033,7 +3094,7 @@ function radio_station_settings_table() { // 1.0.9: added for standalone setting row output if ( !function_exists( 'radio_station_settings_row' ) ) { function radio_station_settings_row( $option, $setting ) { - $namespace = radio_station_get_radio_station_slug( __FUNCTION__ ); + $namespace = radio_station_get_namespace_from_function( __FUNCTION__ ); $instance = $GLOBALS[$namespace . '_instance']; $instance->settings_row( $option, $setting ); } @@ -3094,6 +3155,11 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.2.0 == +// - fix missing CSV field type row output condition +// - fix to saving of current settings tab (if any) +// - improved handling of Pro Upgrade and Details links + // == 1.1.9 == // - fix to allow saving of zero value // - added filters for plugin page settings header sections @@ -3204,5 +3270,4 @@ function radio_station_settings_row( $option, $setting ) { // ----------------- // Development TODOs // ----------------- -// - check Freemius upgrade/addon URLs // - use sanitize text field on textarea ? diff --git a/player/js/radio-player.js b/player/js/radio-player.js index c448caf..59f68e4 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -275,19 +275,27 @@ function radio_player_player_fallback(instance, script) { /* retry different script with stored player instance data */ newscript = false; - for (k in radio_player.scripts) { - if (!newscript) { - found = false; - for (j = 0; j < radio_data.failed[instance].length; j++) { - if (radio_data.failed[instance][j] == k) {found = true;} + if (radio_player.scripts.length) { + for (k in radio_player.scripts) { + if (!newscript) { + found = false; + for (j = 0; j < radio_data.failed[instance].length; j++) { + if (radio_data.failed[instance][j] == k) {found = true;} + } + if (!found) {newscript = k;} } - if (!found) {newscript = k;} } } if (!newscript) { - console.log('Exhausted All Player Script Type Attempts'); + if (radio_player.debug) {console.log('Exhausted All Player Script Type Attempts');} radio_data.failed = new Array(); /* reset */ - /* TODO: swap to fallback stream data and retry ? */ + /* maybe swap to fallback stream data to retry */ + if (data.fallback != '') { + if (radio_player.debug) {console.log('Switching to Fallback Stream');} + tmpa = data.url; data.url = data.fallback; data.fallback = tmpa; + tmpb = data.format; data.fformat = data.format; data.fformat = tmpb; + radio_player_load_audio(script, instance, data, data.start); + } } else { radio_data.data[instance].script = newscript; data = radio_data.data[instance]; if (radio_player.debug) {console.log('Trying New Player Script: '+newscript); console.log(data);} @@ -429,11 +437,12 @@ function radio_player_change_volume(instance, volume) { } /* --- get player volume --- */ -function radio_player_get_volume(instance, volume) { +function radio_player_get_volume(instance) { player = radio_data.players[instance]; script = radio_data.scripts[instance]; - if (script == 'amplitude') {return player.getVolume();} - else if (script == 'howler') {return (player.volume() * 100);} - else if (script == 'jplayer') {return (player.jPlayer('volume') * 100);} + if (script == 'amplitude') {volume = player.getVolume();} + else if (script == 'howler') {volume = (player.volume() * 100);} + else if (script == 'jplayer') {volume = (player.jPlayer('volume') * 100);} + return volume; } /* --- set slider volume with background div width fix --- */ diff --git a/player/radio-player.php b/player/radio-player.php index f03a09a..6ee157f 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -3,6 +3,8 @@ // ==================== // === RADIO PLAYER === // ==================== +// === by Tony Hayes == +// ==================== // === Radio Player === // - Player Output @@ -14,7 +16,7 @@ // === Player Scripts === // - Enqueue Player Javasscripts // - Enqueue Player Script -// - Lazy Load Script Fallbacks +// - Lazy Load Audio Script Fallbacks // - Enqueue Amplitude Javascript // - Enqueue JPlayer Javascript // - Enqueue Howler Javascript @@ -85,7 +87,7 @@ // (then include /mu-plugins/player/radio-player.php from a file in /mu-plugins/) // --- player script and skin --- -// RADIO_PLAYER_SCRIPT - default player script (amplitude, jplayer, howler, mediaelements) +// RADIO_PLAYER_SCRIPT - default player script (amplitude, jplayer, howler) // RADIO_PLAYER_FORCE_SCRIPT - force override any use of other player script // RADIO_PLAYER_SKIN - default player skin (must match script used) // RADIO_PLAYER_FORCE_SKIN - force override any use of other player skin @@ -1162,25 +1164,40 @@ function radio_station_player_enqueue_script( $script ) { $radio_player['enqeued_' . $script] = true; } -// -------------------------- -// Lazy Load Script Fallbacks -// -------------------------- +// -------------------------------- +// Lazy Load Audio Script Fallbacks +// -------------------------------- // 2.4.0.3: lazy load fallback scripts on pageload to cache them add_filter( 'radio_station_player_pageload_script', 'radio_station_player_load_script_fallbacks' ); function radio_station_player_load_script_fallbacks( $js ) { global $radio_player; - $js .= "head = document.getElementsByTagName('head')[0]; "; - if ( !isset( $radio_player['enqueued_howler'] ) ) { - $js .= "el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el);"; + + // 2.4.0.3: check for fallback selection (default all) + $fallbacks = array( 'jplayer', 'howler', 'amplitude' ); + if ( function_exists( 'radio_station_get_setting' ) ) { + $fallbacks = radio_station_get_setting( 'player_fallbacks' ); + } elseif ( function_exists( 'apply_filters' ) ) { + $fallbacks = apply_filters( 'radio_station_player_fallbacks', $fallbacks ); } - if ( !isset( $radio_player['enqueued_jplayer'] ) ) { - $js .= "el = document.createElement('script'); el.src = radio_player.scripts.jplayer; head.appendChild(el);"; + if ( defined( 'RADIO_PLAYER_FALLBACKS' ) ) { + $fallbacks = explode( ',', RADIO_PLAYER_FALLBACKS ); } - if ( !isset( $radio_player['enqueued_amplitude'] ) ) { - $js .= "el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el);"; + + // --- load fallback audio scripts --- + if ( count( $fallbacks ) > 0 ) { + $js .= "head = document.getElementsByTagName('head')[0]; "; + if ( !isset( $radio_player['enqueued_howler'] ) && in_array( 'howler', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.howler; head.appendChild(el);"; + } + if ( !isset( $radio_player['enqueued_jplayer'] ) && in_array( 'jplayer', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.jplayer; head.appendChild(el);"; + } + if ( !isset( $radio_player['enqueued_amplitude'] ) && in_array( 'amplitude', $fallbacks ) ) { + $js .= "el = document.createElement('script'); el.src = radio_player.scripts.amplitude; head.appendChild(el);"; + } + $js .= PHP_EOL; } - $js .= PHP_EOL; return $js; } @@ -1364,18 +1381,6 @@ function radio_station_player_get_settings() { $player_script = radio_station_player_get_default_script(); if ( function_exists( 'radio_station_get_setting' ) ) { - // --- get stream settings --- - // $streaming_url = radio_station_get_setting( 'streaming_url' ); - // $fallback_url = radio_station_get_setting( 'fallback_url' ); - // $streaming_format = radio_station_get_setting( 'streaming_format' ); - // $fallback_format = radio_station_get_setting( 'fallback_format' ); - - // --- set player settings --- - // $js .= "radio_player.settings.url = '" . esc_url( $streaming_url ) . "'; " . PHP_EOL; - // $js .= "radio_player.settings.fallback = '" . esc_url( $fallback_url ) . "'; " . PHP_EOL; - // $js .= "radio_player.settings.format = '" . esc_js( $streaming_format ) . "'; " . PHP_EOL; - // $js .= "radio_player.settings.fformat = '" . esc_js( $fallback_format ) . "'; " . PHP_EOL; - // --- get player settings --- $player_script = radio_station_get_setting( 'player_script' ); $player_title = radio_station_get_setting( 'player_title' ); @@ -1451,6 +1456,16 @@ function radio_station_player_get_settings() { } // --- set script URL ouput --- + // 2.4.0.3: check for fallback script settings + $fallbacks = array( 'jplayer', 'howler', 'amplitude' ); + if ( function_exists( 'radio_station_get_setting' ) ) { + $fallbacks = radio_station_get_setting( 'player_fallbacks' ); + } elseif ( function_exists( 'apply_filters' ) ) { + $fallbacks = apply_filters( 'radio_station_player_fallbacks', $fallbacks ); + } + if ( defined( 'RADIO_PLAYER_FALLBACKS' ) ) { + $fallbacks = explode( ',', RADIO_PLAYER_FALLBACKS ); + } $js .= "scripts = {"; if ( in_array( 'amplitude', $scripts ) ) { $js .= "'amplitude': '" . $radio_player['amplitude_script']['url'] . '?version=' . $radio_player['amplitude_script']['version'] . "', "; @@ -1467,17 +1482,17 @@ function radio_station_player_get_settings() { // --- set player script supported formats --- // TODO: recheck supported amplitude formats ? - // [Amplitude] HTML5 Support - mp3, aac ...? - // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers // [JPlayer] Audio: mp3, m4a - Video: m4v // +Audio: webma, oga, wav, fla, rtmpa +Video: webmv, ogv, flv, rtmpv // [Howler] mp3, opus, ogg, wav, aac, m4a, mp4, webm // +mpeg, oga, caf, weba, webm, dolby, flac + // [Amplitude] HTML5 Support - mp3, aac ...? + // ref: https://en.wikipedia.org/wiki/HTML5_audio#Supporting_browsers // [Media Elements] Audio: mp3, wma, wav +Video: mp4, ogg, webm, wmv $js .= "formats = {"; $js .= "'howler': ['mp3','opus','ogg','oga','wav','aac','m4a','mp4','webm','weba','flac'], "; - $js .= "'amplitude': ['mp3','aac'], "; $js .= "'jplayer': ['mp3','m4a','webm','oga','rtmpa','wav','flac'], "; + $js .= "'amplitude': ['mp3','aac'], "; // $js .= "'mediaelements': ['mp3','wma','wav'], "; $js .= "}" . PHP_EOL; @@ -2493,9 +2508,10 @@ function radio_station_player_control_styles( $instance ) { " . $container . " .rp-volume-controls input[type=range]::-moz-focus-outer {outline: none; box-shadow: none;}" . PHP_EOL; // --- Range Track (synced Background Div) --- + // 2.4.0.3: add position absolute/top on slider background (cross-browser display fix) $css .= "/* Range Track */ " . $container . " .rp-volume-controls .rp-volume-slider-bg { - overflow: hidden; height: 9px; margin-left: 9px; z-index: -1; + position: absolute; top: 9px; overflow: hidden; height: 9px; margin-left: 9px; z-index: -1; border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 3px; background: rgba(128, 128, 128, 0.5); } " . $container . ".playing .rp-volume-controls .rp-volume-slider-bg {background: " . $colors['track'] . ";} @@ -2503,10 +2519,11 @@ function radio_station_player_control_styles( $instance ) { // --- Slider Range Track (Clickable Transparent) --- $css .= "/* Range Track */ -" . $container . " .rp-volume-controls input[type=range] {float: left; margin-top: -9px;} " . $container . " .rp-volume-controls input[type=range]::-webkit-slider-runnable-track {height: 9px; background: transparent; -webkit-appearance: none;} " . $container . " .rp-volume-controls input[type=range]::-moz-range-track {height: 9px; background: transparent;} " . $container . " .rp-volume-controls input[type=range]::-ms-track {height: 9px; color: transparent; background: transparent; border-color: transparent;}" . PHP_EOL; +// 2.4.0.3: remove float on range input (cross-browser display fix) +// " . $container . " .rp-volume-controls input[type=range] {float: left; margin-top: -9px;} // --- Slider Range Thumb --- $css .= "/* Range Thumb */ @@ -2535,6 +2552,30 @@ function radio_station_player_control_styles( $instance ) { // if changing the thumb width style, override this style also for volume background to match! $css .= PHP_EOL . $container . " .rp-volume-thumb {display: none; width: 18px;}" . PHP_EOL; + // --- get volume control display settings --- + // 2.4.1.4: added volume control visibility options + if ( function_exists( 'radio_station_get_setting' ) ) { + $volumes = radio_station_get_setting( 'player_volumes' ); + if ( !is_array( $volumes ) ) { + $volumes = array( 'slider', 'updown', 'mute', 'max' ); + } + } elseif ( function_exists( 'apply_filters' ) ) { + $volumes = array( 'slider', 'updown', 'mute', 'max' ); + $volumes = apply_filters( 'radio_station_player_volumes_display', $volumes ); + } + if ( !in_array( 'slider', $volumes ) ) { + $css .= PHP_EOL . $container . " .rp-volume-slider-container {display: none;}" . PHP_EOL; + } + if ( !in_array( 'updown', $volumes ) ) { + $css .= PHP_EOL . $container . " .rp-volume-up, " . $container . " .rp-volume-down {display: none;}" . PHP_EOL; + } + if ( !in_array( 'mute', $volumes ) ) { + $css .= PHP_EOL . $container . " .rp-mute {display: none;}" . PHP_EOL; + } + if ( !in_array( 'max', $volumes ) ) { + $css .= PHP_EOL . $container . " .rp-volume-max {display: none;}" . PHP_EOL; + } + // --- filter and return --- // 2.4.0.3: added missing function_exists wrapper if ( function_exists( 'apply_filters' ) ) { diff --git a/radio-station.php b/radio-station.php index 51118a1..3a706f9 100644 --- a/radio-station.php +++ b/radio-station.php @@ -422,16 +422,34 @@ ), // --- Player Script --- + // 2.4.0.3: change script default to jplayer 'player_script' => array( 'type' => 'select', 'label' => __( 'Player Script', 'radio-station' ), - 'default' => 'amplitude', + 'default' => 'jplayer', 'options' => array( - 'amplitude' => __( 'Amplitude', 'radio-station' ), + 'jplayer' => __( 'jPlayer', 'radio-station' ), 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => __( 'Amplitude', 'radio-station' ), + ), + 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Fallback Scripts --- + // 2.4.0.3: added fallback enable/disable switching + 'player_fallbacks' => array( + 'type' => 'multicheck', + 'label' => __( 'Player Script', 'radio-station' ), + 'default' => array( 'amplitude', 'howler', 'jplayer' ), + 'options' => array( 'jplayer' => __( 'jPlayer', 'radio-station' ), + 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => __( 'Amplitude', 'radio-station' ), ), - 'helper' => __( 'Default audio script to use for Radio Streaming Player.', 'radio-station' ), + 'helper' => __( 'Fallback scripts to enable for when the default Player script fails.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', 'pro' => false, @@ -468,6 +486,23 @@ 'pro' => false, ), + // --- Volume Controls --- + // 2.4.0.3: added enable/disable volume controls option + 'player_volumes' => array( + 'type' => 'multicheck', + 'label' => __( 'Volume Controls', 'radio-station' ), + 'default' => array( 'slider', 'updown', 'mute', 'max' ), + 'options' => array( + 'slider' => __( 'Volume Slider', 'radio-station' ), + 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), + 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), + 'max' => __( 'Maximize Volume', 'radio-station' ), + ), + 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + // --- Player Debug Mode --- 'player_debug' => array( 'type' => 'checkbox', @@ -656,7 +691,22 @@ 'min' => 0, 'step' => 100, 'max' => 10000, - 'helper' => __( 'Number of milliseconds over which to fade in new Pages when continuous playback is enabled. Use 0 for instant display.', 'radio-station' ), + 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Page Load Timeout --- + // 2.4.0.3: add page load timeout option + 'player_bar_timeout' => array( + 'type' => 'number', + 'label' => __( 'Page Load Timeout', 'teleporter' ), + 'default' => 7000, + 'min' => 0, + 'step' => 500, + 'max' => 20000, + 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', 'pro' => true, @@ -1424,10 +1474,16 @@ // --- Freemius --- // 2.4.0.1: turn on addons switch for Pro + // 2.4.0.3: turn on plans switch for Pro also + // 2.4.0.3: set Pro details and Upgrade links 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', - 'hasplans' => false, - 'hasaddons' => true, + 'hasplans' => true, + 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', + // 'upgrade_link' => add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ), + // 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', + // 'hasaddons' => true, + // 'addons_link' => add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ), 'plan' => 'free', ); diff --git a/readme.txt b/readme.txt index 79574c9..9cb1772 100644 --- a/readme.txt +++ b/readme.txt @@ -215,9 +215,12 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.3 = -* Update: Plugin Panel (1.1.9) with zero value save fix -* Improved: lazy load all fallback player scripts on pageload +* Update: Plugin Panel (1.2.0) with zero value save and tab fix +* Added: option to disable player audio fallback scripts +* Added: option to hide various volume controls +* Improved: lazy load player audio fallback scripts * Refix: missing fix to active day tab on pageload +* Fixed: player volume slider background position (cross-browser) = 2.4.0.2 = * Fixed: Multiple Player instance IDs From acde57a8b0707b21d6946bb033b189a95f1d00e5 Mon Sep 17 00:00:00 2001 From: majick777 Date: Tue, 24 Aug 2021 21:59:39 +1000 Subject: [PATCH 225/377] dev updates 2.4.0.3 --- CHANGELOG.md | 4 +- includes/data-feeds.php | 4 +- includes/extra-strings.php | 85 +++++++++---------- includes/post-types-admin.php | 10 ++- includes/post-types.php | 15 ++-- includes/support-functions.php | 9 ++- js/radio-station-admin.js | 2 + loader.php | 62 ++++++++------ player/css/radio-player.css | 1 + radio-station-admin.php | 20 +++-- radio-station.php | 69 +++++++++++++--- readme.txt | 4 +- templates/single-playlist-content.php | 112 +++++++++++++++++--------- 13 files changed, 253 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4dab74..fbfaa02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.3 -* Update: Plugin Panel (1.2.0) with zero value save and tab fix +* Update: Plugin Panel (1.2.1) with zero value save and tab fixes * Added: option to disable player audio fallback scripts * Added: option to hide various volume controls * Improved: lazy load player audio fallback scripts +* Improved: added author support to post types for quick edit * Refix: missing fix to active day tab on pageload * Fixed: player volume slider background position (cross-browser) +* Fixed: missing title value for adjacent post links ### 2.4.0.2 * Fixed: Multiple Player instance IDs diff --git a/includes/data-feeds.php b/includes/data-feeds.php index e8cb141..9e7e559 100644 --- a/includes/data-feeds.php +++ b/includes/data-feeds.php @@ -60,7 +60,7 @@ function radio_station_api_link_header() { return; } $api_url = radio_station_get_api_url(); - $header = 'Link: <' . esc_url_raw( $api_url ) . '>; rel="' . RADIO_STATION_API_DOCS_URL . '"'; + $header = 'Link: <' . esc_url_raw( $api_url ) . '>; rel="' . RADIO_STATION_DOCS_URL . 'api/"'; $header = apply_filters( 'radio_station_api_discovery_header', $header ); if ( $header ) { header( $header, false ); @@ -73,7 +73,7 @@ function radio_station_api_link_header() { add_action( 'wp_head', 'radio_station_api_discovery_link' ); function radio_station_api_discovery_link() { $api_url = radio_station_get_api_url(); - $link = ""; + $link = ""; $link = apply_filters( 'radio_station_api_discovery_link', $link ); if ( $link ) { // phpcs:ignore WordPress.Security.OutputNotEscaped diff --git a/includes/extra-strings.php b/includes/extra-strings.php index 4cf9e0e..309e54c 100644 --- a/includes/extra-strings.php +++ b/includes/extra-strings.php @@ -1,6 +1,18 @@ false, // 2.3.0: added custom field and revision support // 2.3.3.9: added post excerpt support - 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'comments', 'custom-fields', 'revisions' ), + // 2.4.1.4: added author support for quick edit + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'comments', 'custom-fields', 'revisions', 'author' ), 'can_export' => true, // 2.3.0: added show archives support 'has_archive' => 'shows', @@ -113,7 +114,8 @@ function radio_station_create_post_types() { 'public' => true, 'hierarchical' => false, // 2.3.0: added thumbnail, custom field and revision support - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'revisions' ), + // 2.4.1.4: added author support for quick edit + 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'revisions', 'author' ), 'can_export' => true, // 2.3.0: changed from playlists-archive 'has_archive' => 'playlists', @@ -162,7 +164,8 @@ function radio_station_create_post_types() { // 2.3.0: added editor support for override description // 2.3.0: added custom field and revision support // 2.3.3.9: added post excerpt support - 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), + // 2.4.1.4: added author support for quick edit + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions', 'author' ), 'can_export' => true, 'has_archive' => false, 'rewrite' => array( @@ -212,7 +215,8 @@ function radio_station_create_post_types() { // 2.3.3.9: set can_export true 'can_export' => true, // 2.3.3.9: added all post type supports - 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), + // 2.4.1.4: added author support for quick edit + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions', 'author' ), 'has_archive' => 'hosts', 'rewrite' => array( 'slug' => 'host', @@ -262,7 +266,8 @@ function radio_station_create_post_types() { // 2.3.3.9: set can_export true 'can_export' => true, // 2.3.3.9: added all post type supports - 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), + // 2.4.1.4: added author support for quick edit + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions', 'author' ), 'has_archive' => 'producers', 'rewrite' => array( 'slug' => 'producer', diff --git a/includes/support-functions.php b/includes/support-functions.php index 49c9b0b..80f5435 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -3382,7 +3382,7 @@ function radio_station_get_fallback_url() { $fallback_url = $fallback; } if ( RADIO_STATION_DEBUG ) { - echo 'Fallback URL Setting: '; var_dump( $stream ); echo ''; + echo 'Fallback URL Setting: '; var_dump( $fallback_url ); echo ''; } $fallback_url = apply_filters( 'radio_station_fallback_url', $fallback_url ); @@ -3580,7 +3580,7 @@ function radio_station_get_upgrade_url() { // ...maybe it is -addons instead of -pricing ??? // $upgrade_url = add_query_arg( 'page', 'radio-station-pricing', admin_url( 'admin.php' ) ); - $upgrade_url = RADIO_STATION_PRO_URL; + $upgrade_url = RADIO_STATION_PRO_URL . 'pricing/'; return $upgrade_url; } @@ -4676,6 +4676,11 @@ function radio_station_sanitize_input( $prefix, $key ) { $postkey = $prefix . '_' . $key; $types = radio_station_get_meta_input_types(); + // 2.4.0.3: bug out if post key not set + if ( !isset( $_POST[$postkey] ) ) { + return ''; + } + if ( in_array( $key, $types['file'] ) ) { $value = wp_strip_all_tags( trim( $_POST[$postkey] ) ); } elseif ( in_array( $key, $types['email'] ) ) { diff --git a/js/radio-station-admin.js b/js/radio-station-admin.js index 7db776b..f0d05ea 100644 --- a/js/radio-station-admin.js +++ b/js/radio-station-admin.js @@ -4,6 +4,8 @@ /* note: admin scripts are currently enqueued using wp_add_inline_script */ /* this file is necessary to ensure they are printed in the right place */ +var radio_admin; radio_admin = {debug:false}; + /* Cookie Value Function */ /* since @2.3.2 */ radio_cookie = { diff --git a/loader.php b/loader.php index 3cba23f..5a4f345 100644 --- a/loader.php +++ b/loader.php @@ -224,7 +224,7 @@ public function setup_plugin() { $profiles = explode( ',', $proslug ); $proslug = trim( $profiles[0] ); } - $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension + $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension $args['profiles'] = $profiles; } @@ -1170,10 +1170,10 @@ public function load_helpers() { } // --- Pro Functions --- - $plan = 'free'; // 1.0.2: added prototype auto-loading of Pro file(s) + // 1.2.1: fix overriding of plan arg to free // (to work with @fs_premium_only file list) - if ( count( $args['profiles'] ) > 0 ) { + if ( isset( $args['profiles'] ) && count( $args['profiles'] ) > 0 ) { $included = get_included_files(); foreach ( $args['profiles'] as $profile ) { // --- chech for php extension --- @@ -1189,8 +1189,11 @@ public function load_helpers() { } } } - $args['plan'] = $plan; - $this->args = $args; + // 1.2.0: only change plan setting if premium files found + if ( isset( $plan ) ) { + $args['plan'] = $plan; + $this->args = $args; + } // --- Plugin Update Checker --- // note: lack of updatechecker.php file indicates WordPress.Org SVN repo version @@ -1564,24 +1567,27 @@ public function plugin_links( $links, $file ) { // --- maybe add Pro upgrade link --- if ( isset( $args['hasplans'] ) && $args['hasplans'] ) { - // -- internal upgrade link --- - // TODO: check if premium is already installed - if ( isset( $args['upgrade_link'] ) ) { - $upgrade_url = $args['upgrade_link']; - $upgrade_target = ''; - } else { - $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); - $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : ''; - } - $upgrade_link = "" . esc_html( __('Upgrade' ) ) . ""; - array_unshift( $links, $upgrade_link ); - - // --- external pro link --- - // 1.2.0: added pro link - if ( isset( $args['pro_link'] ) ) { - $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; - $pro_link = "" . esc_html( __('Pro Details' ) ) . ""; - array_unshift( $links, $pro_link ); + // 1.2.1: add check if premium is already installed + if ( !isset( $args['plan'] ) || ( 'premium' != $args['plan'] ) ) { + + // -- internal upgrade link --- + if ( isset( $args['upgrade_link'] ) ) { + $upgrade_url = $args['upgrade_link']; + $upgrade_target = ''; + } else { + $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); + $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : ''; + } + $upgrade_link = "" . esc_html( __('Upgrade' ) ) . ""; + array_unshift( $links, $upgrade_link ); + + // --- external pro link --- + // 1.2.0: added separate pro details link + if ( isset( $args['pro_link'] ) ) { + $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; + $pro_link = "" . esc_html( __('Pro Details' ) ) . ""; + array_unshift( $links, $pro_link ); + } } } @@ -2370,10 +2376,12 @@ public function setting_row( $option ) { || ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) ) { $upgrade_link = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) ); $upgrade_target = ''; - } elseif ( isset( $args['upgrade_link'] ) ) { + } + if ( isset( $args['upgrade_link'] ) ) { $upgrade_link = $args['upgrade_link']; - $upgrade_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : ''; + $upgrade_target = !strstr( $upgrade_link, '/wp-admin/' ) ? ' target="_blank"' : ''; } + // 1.2.1: fix to check pro_link not upgrade_link if ( isset( $args['pro_link'] ) ) { $pro_link = $args['pro_link']; $pro_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : ''; @@ -3155,6 +3163,10 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.2.1 == +// - fix overriding of plan arg to free +// - add check if premium already installed + // == 1.2.0 == // - fix missing CSV field type row output condition // - fix to saving of current settings tab (if any) diff --git a/player/css/radio-player.css b/player/css/radio-player.css index 5080be3..a2f0c58 100644 --- a/player/css/radio-player.css +++ b/player/css/radio-player.css @@ -310,6 +310,7 @@ } .rp-now-playing-title, .rp-now-playing-artist, .rp-now-playing-album { display: inline-block; + padding: 0 10px; } /* @group Station and Show Info */ diff --git a/radio-station-admin.php b/radio-station-admin.php index 4271779..048e665 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -58,6 +58,11 @@ function radio_station_enqueue_admin_scripts() { $deps = array( 'jquery' ); wp_enqueue_script( 'radio-station-admin', $script['url'], $deps, $version, true ); + if ( RADIO_STATION_DEBUG ) { + $js = "radio_admin.debug = true;"; + wp_add_inline_script( 'radio-station-admin', $js ); + } + // --- enqueue admin styles --- radio_station_enqueue_style( 'admin' ); @@ -276,18 +281,17 @@ function radio_station_role_editor() { } // --- role assignment message --- - echo "

    " . esc_html( __( 'Role Assignments', 'radio-station' ) ) . "

    "; - echo esc_html( __( 'You can assign a Radio Station role to users through the WordPress User editor.', 'radio-station' ) ); + echo '

    ' . esc_html( __( 'Role Assignments', 'radio-station' ) ) . '

    '; + echo esc_html( __( 'You can assign a Radio Station role to users through the WordPress User editor.', 'radio-station' ) ) . '
    '; // --- info regarding Pro role assignment interface --- - // TODO: change this text when Pro version becomes available - echo '
    ' . esc_html( __( 'Radio Station Pro will include a Role Assignment Interface so you can easily assign Radio Station roles to any user.', 'radio-station' ) ) . '
    '; + // 2.4.0.3: change text to reflect inclusion in Pro + echo esc_html( __( 'Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user.', 'radio-station' ) ) . '
    '; - // --- find out about Pro link --- - // [Pro Blurb] - // TODO: add go Por link when Pro available + // --- Pro upgrade link --- + // TODO: maybe add picture of role editing interface ? $upgrade_url = radio_station_get_upgrade_url(); - echo '
    '; + echo ''; // echo esc_html( __( 'Upgrade to Radio Station Pro', 'radio-station' ) ); echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); echo ' →.'; diff --git a/radio-station.php b/radio-station.php index 3a706f9..483f01d 100644 --- a/radio-station.php +++ b/radio-station.php @@ -38,6 +38,7 @@ // - Define Plugin Data Slugs // - Include Plugin Files // - Plugin Options and Defaults +// - Pro Version Install Check // - Plugin Loader Settings // - Start Plugin Loader Instance // - Include Plugin Admin Files @@ -87,12 +88,14 @@ // Define Plugin Constants // ----------------------- // 2.3.1: added constant for Netmix Directory +// 2.4.0.3: remove separate constant for API docs link +// 2.4.0.3: update home URLs to radiostation.pro define( 'RADIO_STATION_FILE', __FILE__ ); define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); define( 'RADIO_STATION_BASENAME', plugin_basename( __FILE__ ) ); define( 'RADIO_STATION_HOME_URL', 'https://radiostation.pro/' ); define( 'RADIO_STATION_DOCS_URL', 'https://radiostation.pro/docs/' ); -define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); +// define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); define( 'RADIO_STATION_PRO_URL', 'https://radiostation.pro/' ); define( 'RADIO_STATION_NETMIX_DIR', 'https://netmix.com/' ); @@ -1427,20 +1430,45 @@ ), ); -// 2.3.3.8: [temp] remove player options if player not present -// if ( !file_exists( $player_file ) ) { -// foreach ( $options as $key => $option ) { -// if ( 'player_' == substr( $key, 0, 7 ) ) { -// unset( $options[$key] ); -// } -// } -// } +// ------------------------- +// Pro Version Install Check +// ------------------------- +// 2.4.0.3: added check active/installed Pro version +$plan = 'free'; +if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + // --- check for activated pro plugin --- + $plan = 'premium'; +} else { + // --- check for deactivated pro plugin --- + $plugins = wp_cache_get( 'plugins', 'plugins' ); + if ( !$plugins ) { + if ( function_exists( 'get_plugins' ) ) { + $plugins = get_plugins(); + } else { + $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; + if ( file_exists( $plugin_path ) ) { + include $plugin_path; + $plugins = get_plugins(); + } + } + } + if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { + foreach ( $plugins as $slug => $plugin ) { + if ( strstr( $slug, 'radio-station-pro.php' ) ) { + $plan = 'premium'; + break; + } + } + } +} // ---------------------- // Plugin Loader Settings // ---------------------- // 2.3.0: added plugin loader settings $slug = 'radio-station'; + +// --- settings array --- $settings = array( // --- Plugin Info --- 'slug' => $slug, @@ -1454,7 +1482,7 @@ 'docs' => RADIO_STATION_DOCS_URL, 'support' => 'https://github.com/netmix/radio-station/issues/', 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), - 'share' => RADIO_STATION_HOME_URL . '?share', + 'share' => RADIO_STATION_HOME_URL . '#share', 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), 'donate' => 'https://patreon.com/radiostation', 'donatetext' => __( 'Support this Plugin', 'radio-station' ), @@ -1484,7 +1512,7 @@ // 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', // 'hasaddons' => true, // 'addons_link' => add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ), - 'plan' => 'free', + 'plan' => $plan, ); // ------------------------- @@ -2941,6 +2969,15 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po $rel = 'prev'; } $adjacent_post = get_post( $show['id'] ); + + // --- adjacent post title --- + // 2.4.0.3: added fix for missing post title + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); $string = ''; $inlink = str_replace( '%title', $post_title, $link ); @@ -3066,7 +3103,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po } $adjacent_post = $show_posts[0]; if ( RADIO_STATION_DEBUG ) { - echo 'Realted Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; + echo 'Related Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; } // --- adjacent post title --- @@ -3197,7 +3234,13 @@ function radio_station_set_roles() { if ( !is_array( $role_caps ) ) { $role_caps = array(); } - $host_caps = array( 'edit_hosts', 'edit_published_hosts', 'delete_hosts', 'read_hosts', 'publish_hosts' ); + $host_caps = array( + 'edit_hosts', + 'edit_published_hosts', + 'delete_hosts', + 'read_hosts', + 'publish_hosts' + ); foreach ( $host_caps as $cap ) { if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { $wp_roles->add_cap( 'dj', $cap, true ); diff --git a/readme.txt b/readme.txt index 9cb1772..341e6fa 100644 --- a/readme.txt +++ b/readme.txt @@ -215,12 +215,14 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.3 = -* Update: Plugin Panel (1.2.0) with zero value save and tab fix +* Update: Plugin Panel (1.2.1) with zero value save and tab fixes * Added: option to disable player audio fallback scripts * Added: option to hide various volume controls * Improved: lazy load player audio fallback scripts +* Improved: added author support to post types for quick edit * Refix: missing fix to active day tab on pageload * Fixed: player volume slider background position (cross-browser) +* Fixed: missing title value for adjacent post links = 2.4.0.2 = * Fixed: Multiple Player instance IDs diff --git a/templates/single-playlist-content.php b/templates/single-playlist-content.php index 8f9e2f2..9ac2ece 100644 --- a/templates/single-playlist-content.php +++ b/templates/single-playlist-content.php @@ -1,5 +1,12 @@ -
    - - - - - - - - - - - > - - - - - - - - $track ) { + if ( 'played' == $track['playlist_entry_status'] ) { + $found = true; + } + } + + if ( $found ) { + + echo '
    ' . PHP_EOL; + echo '
    #
    ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + + $count = 1; + foreach ( $playlist as $entry ) { + if ( 'played' === $entry['playlist_entry_status'] ) { + + // 2.4.0.3: remove new class it behavior changed + // $new_class = ''; + // if ( isset( $entry['playlist_entry_new'] ) && 'on' === $entry['playlist_entry_new'] ) { + // $new_class = 'class="new"'; + // } + + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + $count++; + } } - ?> - -
    #' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Label', 'radio-station' ) ) . '' . esc_html( __( 'Comments', 'radio-station' ) ) . '
    ' . esc_attr( $count ) . '' . esc_html( $entry['playlist_entry_artist'] ) . '' . esc_html( $entry['playlist_entry_song'] ) . '' . esc_html( $entry['playlist_entry_album'] ) . '' . esc_html( $entry['playlist_entry_label'] ) . '' . esc_html( $entry['playlist_entry_comments'] ) . '
    -
    - -
    - -
    -' . PHP_EOL; + echo '' . PHP_EOL; + + } else { + + // 2.4.0.3: added text to indicate no played tracks + echo '
    ' . PHP_EOL; + echo esc_html( __( 'No played tracks found for this Playlist yet.', 'radio-station' ) ); + echo '
    ' . PHP_EOL; + } + +} else { + + // --- not playlist entries message --- + echo '
    ' . PHP_EOL; + echo esc_html( __( 'No entries found for this Playlist', 'radio-station' ) ) . PHP_EOL; + echo '
    ' . PHP_EOL; + +} // --- link show playlists (bottom) --- if ( $related_show ) { $show_playlists_link = '
    ← ' . __( 'All Playlists for Show', 'radio-station' ) . ': ' . $show->post_title . ''; - $after = '
    ' . $show_playlists_link; - $after = apply_filters( 'radio_station_link_playlist_to_show_before', $after, $post, $show ); + $after = '
    ' . $show_playlists_link . PHP_EOL;; + // 2.4.0.3: fix filter name to after not before + $after = apply_filters( 'radio_station_link_playlist_to_show_after', $after, $post, $show ); echo $after; } + From 7c2bd554e73f5cd4eefecde8cea69a7be0e0e398 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 25 Aug 2021 10:45:26 -0400 Subject: [PATCH 226/377] Update issue templates Adding pre-dployment items --- .github/ISSUE_TEMPLATE/developer_task.md | 2 +- .../pre-deployment-checklist.md | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/pre-deployment-checklist.md diff --git a/.github/ISSUE_TEMPLATE/developer_task.md b/.github/ISSUE_TEMPLATE/developer_task.md index b4cafa0..cf3411b 100644 --- a/.github/ISSUE_TEMPLATE/developer_task.md +++ b/.github/ISSUE_TEMPLATE/developer_task.md @@ -17,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Additional context** -Add any other context or screenshots about the feature request here. \ No newline at end of file +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md new file mode 100644 index 0000000..8ac4cd8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md @@ -0,0 +1,40 @@ +--- +name: Pre-Deployment Checklist +about: All tasks related to a deployment. +title: '' +labels: '' +assignees: '' + +--- + +Please check that the following items are completed. + +For the read.md file, please check on the following items: + +1. Does the "Requires At Least" version need to be updated? If yes, has it been updated? + +2. Does the "Tested Up To" version need to be updated? If yes, has it been updated? + +3. Does the "Stable Tag" version need to be updated? If yes, has it been updated? + +4. Has the "Upgrade Notice" for the new version been added? If no, please add the upgrade notice and confirm. + +In the readme.txt, please check on the following items: + +1. Has the changelog been updated? If no, please update and confirm + +2. Does the "Requires At Least" version need to be updated? If yes, has it been updated? + +3. Does the "Tested Up To" version need to be updated? If yes, has it been updated? + +4. Does the "Stable Tag" version need to be updated? If yes, has it been updated? + +In radiostation.php, please check the following items: + +1. Has the "@version" been updated? If not, please update. + +1. Does the "@version" need to be updated? If yes, has it been updated? + +2. Does the "Version" number need to be updated? If yes, has it been updated? + +3. Does the "Requires at least" need to be updated? If yes, has it been updated? From 57a801c1d30f6784004bdc5737a7f8aa7b1d12e7 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 25 Aug 2021 10:47:38 -0400 Subject: [PATCH 227/377] Update issue templates Add markup to headings --- .../pre-deployment-checklist.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md index 8ac4cd8..20530ff 100644 --- a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md +++ b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md @@ -7,34 +7,34 @@ assignees: '' --- -Please check that the following items are completed. +**Please check that the following items are completed.** -For the read.md file, please check on the following items: +**For the read.md file, please check on the following items:** -1. Does the "Requires At Least" version need to be updated? If yes, has it been updated? +**1. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** -2. Does the "Tested Up To" version need to be updated? If yes, has it been updated? +**2. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** -3. Does the "Stable Tag" version need to be updated? If yes, has it been updated? +**3. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** -4. Has the "Upgrade Notice" for the new version been added? If no, please add the upgrade notice and confirm. +**4. Has the "Upgrade Notice" for the new version been added? If no, please add the upgrade notice and confirm.** -In the readme.txt, please check on the following items: +**In the readme.txt, please check on the following items:** -1. Has the changelog been updated? If no, please update and confirm +**1. Has the changelog been updated? If no, please update and confirm** -2. Does the "Requires At Least" version need to be updated? If yes, has it been updated? +**2. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** -3. Does the "Tested Up To" version need to be updated? If yes, has it been updated? +**3. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** -4. Does the "Stable Tag" version need to be updated? If yes, has it been updated? +**4. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** -In radiostation.php, please check the following items: +**In radiostation.php, please check the following items:** -1. Has the "@version" been updated? If not, please update. +**1. Has the "@version" been updated? If not, please update.** -1. Does the "@version" need to be updated? If yes, has it been updated? +**1. Does the "@version" need to be updated? If yes, has it been updated?** -2. Does the "Version" number need to be updated? If yes, has it been updated? +**2. Does the "Version" number need to be updated? If yes, has it been updated?** -3. Does the "Requires at least" need to be updated? If yes, has it been updated? +**3. Does the "Requires at least" need to be updated? If yes, has it been updated?** From 6e0a8986d2bc146e7094584d474dd32fa497c709 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Wed, 25 Aug 2021 10:59:41 -0400 Subject: [PATCH 228/377] Update issue templates add spacing --- .github/ISSUE_TEMPLATE/pre-deployment-checklist.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md index 20530ff..a8c93af 100644 --- a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md +++ b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md @@ -10,31 +10,39 @@ assignees: '' **Please check that the following items are completed.** **For the read.md file, please check on the following items:** - **1. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** + **2. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** + **3. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** + **4. Has the "Upgrade Notice" for the new version been added? If no, please add the upgrade notice and confirm.** -**In the readme.txt, please check on the following items:** +**In the readme.txt, please check on the following items:** **1. Has the changelog been updated? If no, please update and confirm** + **2. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** + **3. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** + **4. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** -**In radiostation.php, please check the following items:** +**In radiostation.php, please check the following items:** **1. Has the "@version" been updated? If not, please update.** + **1. Does the "@version" need to be updated? If yes, has it been updated?** + **2. Does the "Version" number need to be updated? If yes, has it been updated?** + **3. Does the "Requires at least" need to be updated? If yes, has it been updated?** From e70e643165c994b274c6cc3dde524f62dcc606dc Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Fri, 27 Aug 2021 17:43:29 +1000 Subject: [PATCH 229/377] 2.4.0.3b dev updates --- CHANGELOG.md | 1 + loader.php | 66 +++++++++++++++++++++++----------- player/radio-player.php | 80 +++++++++++++++++++++++++---------------- radio-station-admin.php | 28 +++++++++++++++ radio-station.php | 59 +++++++++++++++--------------- readme.md | 4 +-- readme.txt | 2 ++ 7 files changed, 156 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbfaa02..f8ff045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Refix: missing fix to active day tab on pageload * Fixed: player volume slider background position (cross-browser) * Fixed: missing title value for adjacent post links +* Fixed: Fallback scripts and fallback stream URLs ### 2.4.0.2 * Fixed: Multiple Player instance IDs diff --git a/loader.php b/loader.php index 5a4f345..57ad710 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.2.0 +// Version: 1.2.1 // -------------- // Note: Changelog and structure at end of file. // @@ -332,7 +332,8 @@ public function add_settings() { } // 1.0.9: trigger add settings action - do_action( $args['nsmespace'] . '_add_settings', $args ); + // 1.2.1: fix to namespace typo (nsmespace) + do_action( $args['namespace'] . '_add_settings', $args ); } // ----------------------- @@ -521,7 +522,7 @@ public function update_settings() { if ( isset( $_POST[$postkey] ) ) { $posted = $_POST[$postkey]; } - $newsettings = false; + $newsettings = null; // --- maybe validate special options --- // 1.0.9: check for special options to prepare @@ -713,7 +714,7 @@ public function update_settings() { if ( $this->debug ) { echo 'New Settings for Key ' . $key . ': '; // 1.2.0: added isset check for newsetting - if ( isset( $newsetting ) && ( $newsetting || ( 0 === $newsetting ) || ( '0' === $newsetting ) ) ) { + if ( !is_null( $newsettings ) ) { echo '(to-validate) ' . print_r( $newsettings, true ) . '
    '; } else { // 1.1.7 handle if (new) key not set yet @@ -727,17 +728,19 @@ public function update_settings() { // --- maybe validate new settings --- // 1.1.9: fix to allow saving of zero value - if ( $newsettings || ( 0 === $newsettings ) || ( '0' === $newsettings ) ) { + // 1.2.1: fix to allow saving of empty value + if ( !is_null( $newsettings ) ) { if ( is_array( $newsettings ) ) { // --- validate array of settings --- // 1.1.9: fix to allow saving of zero value + // 1.2.1: fix to allow saving of empty value foreach ( $newsettings as $newkey => $newvalue ) { $newsetting = $this->validate_setting( $newvalue, $valid, $validate_args ); if ( $this->debug ) { echo 'Validated Setting array value ' . $newvalue . ' to ' . $newsetting; } - if ( $newsetting && ( '' != $newsetting ) ) { + if ( $newsetting || ( '' == $newsetting ) ) { $newsettings[$newkey] = $newsetting; } elseif ( ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $newsettings[$newkey] = $newsetting; @@ -751,7 +754,7 @@ public function update_settings() { $settings[$key] = $newsettings; } - } else { + } elseif ( $newsettings || ( '' == $newsettings ) || ( 0 === $newsettings ) || ( '0' === $newsettings ) ) { // --- validate single setting --- if ( 'csv' == $type ) { @@ -770,10 +773,11 @@ public function update_settings() { } else { $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args ); // 1.1.9: fix to allow saving of zero value + // 1.2.1: fix to allow saving of empty value if ( $this->debug ) { - echo 'Validated Setting single value ' . $newsettings . ' to ' . $newsetting; + echo 'Validated Setting single value ' . $newsettings . ' to ' . $newsetting . '
    '; } - if ( $newsetting || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { + if ( $newsetting || ( '' == $newsetting ) || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) { $settings[$key] = $newsetting; } } @@ -957,8 +961,11 @@ public function validate_setting( $posted, $valid, $args ) { // 1.0.6: fix to posted variable type (vposted) // 1.0.9: remove check for http prefix to allow other protocols // 1.1.7: use FILTER_SANITIZE_URL not FILTER_SANITIZE_STRING + // 1.2.1: allow for clearing URL by saving empty value $posted = trim( $posted ); - $posted = filter_var( $posted, FILTER_SANITIZE_URL ); + if ( '' != $posted ) { + $posted = filter_var( $posted, FILTER_SANITIZE_URL ); + } // 1.1.7: remove FILTER_VALIDATE_URL check - not working!? if ( $this->debug ) { @@ -1152,6 +1159,12 @@ public function add_actions() { // --- AJAX readme viewer --- add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) ); + + // --- load Freemius (requires PHP 5.4+) --- + // 1.2.1: move Freemius loading to plugins_loaded hook + if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) { + add_action( 'plugins_loaded', array( $this, 'load_freemius' ) ); + } } // --------------------- @@ -1213,10 +1226,8 @@ public function load_helpers() { // 1.0.9: added action for extra loader helpers (eg. WordQuest) do_action( $args['namespace'] . '_loader_helpers', $args ); - // --- Freemius (requires PHP 5.4+) --- - if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) { - $this->load_freemius(); - } + // --- load Freemius (requires PHP 5.4+) --- + // 1.2.1: moved to plugins_loaded action hook } @@ -1334,6 +1345,11 @@ public function readme_viewer() { // ==================== public function load_freemius() { + // 1.2.1: no need to laod if not in admin area + if ( !is_admin() ) { + return; + } + $args = $this->args; $namespace = $this->namespace; @@ -1348,6 +1364,9 @@ public function load_freemius() { $premium = false; if ( isset( $args['plan'] ) && ( 'premium' == $args['plan'] ) ) { $premium = true; + } else { + // 1.2.1: added filter for premium init + $premium = apply_filters( 'freemius_init_premium_' . $args['namespace'], $premium ); } // --- maybe redirect link to plugin support forum --- @@ -1360,9 +1379,10 @@ public function load_freemius() { // changes the support forum slug for premium based on the pro plugin file slug // 1.0.7: fix support URL undefined variable warning $support_url = $args['support']; - if ( $premium ) { - $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); - } + // 1.2.1: removed in favour of filtering via Pro + // if ( $premium && isset( $args['proslug'] ) ) { + // $support_url = str_replace( $args['slug'], $args['proslug'], $support_url ); + // } $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] ); wp_redirect( $support_url ); exit; @@ -1385,15 +1405,17 @@ public function load_freemius() { if ( !isset( $args['type'] ) ) { $args['type'] = 'plugin'; } - if ( !isset( $args['hasaddons'] ) ) { - $args['hasaddons'] = false; + if ( !isset( $args['wporg'] ) ) { + $args['wporg'] = false; } if ( !isset( $args['hasplans'] ) ) { $args['hasplans'] = false; } - if ( !isset( $args['wporg'] ) ) { - $args['wporg'] = false; + if ( !isset( $args['hasaddons'] ) ) { + $args['hasaddons'] = false; } + // 1.2.1: add filter for addons init + $args['hasaddons'] = apply_filters( 'freemius_init_addons_' . $args['namespace'], $args['hasaddons'] ); // --- set defaults for options submenu key values --- // 1.0.2: fix to isset check keys @@ -3164,8 +3186,10 @@ function radio_station_settings_row( $option, $setting ) { // ========= // == 1.2.1 == +// - added filters for premium and addons init // - fix overriding of plan arg to free // - add check if premium already installed +// - fix namespace typo in add settings action // == 1.2.0 == // - fix missing CSV field type row output condition diff --git a/player/radio-player.php b/player/radio-player.php index 6ee157f..65e616f 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -1397,23 +1397,29 @@ function radio_station_player_get_settings() { $player_single = true; if ( function_exists( 'apply_filters' ) ) { + $player_script = apply_fitlers( 'radio_station_player_script', $player_script ); $player_title = apply_filters( 'radio_station_player_title', $player_title ); $player_image = apply_filters( 'radio_station_player_image', $player_image ); $player_volume = abs( intval( apply_filters( 'radio_station_player_volume', $player_volume ) ) ); $player_single = apply_filters( 'radio_station_player_single', $player_single ); } - if ( defined( 'RADIO_PLAYER_TITLE' ) ) { - $player_title = RADIO_PLAYER_TITLE; - } - if ( defined( 'RADIO_PLAYER_IMAGE' ) ) { - $player_image = RADIO_PLAYER_IMAGE; - } - if ( defined( 'RADIO_PLAYER_VOLUME' ) ) { - $player_volume = abs( intval( RADIO_PLAYER_VOLUME ) ); - } - if ( defined( 'RADIO_PLAYER_SINGLE' ) ) { - $player_single = RADIO_PLAYER_SINGLE; - } + } + + // 2.4.0.3: move constant checks out + if ( defined( 'RADIO_PLAYER_SCRIPT' ) ) { + $player_script = RADIO_PLAYER_SCRIPT; + } + if ( defined( 'RADIO_PLAYER_TITLE' ) ) { + $player_title = RADIO_PLAYER_TITLE; + } + if ( defined( 'RADIO_PLAYER_IMAGE' ) ) { + $player_image = RADIO_PLAYER_IMAGE; + } + if ( defined( 'RADIO_PLAYER_VOLUME' ) ) { + $player_volume = abs( intval( RADIO_PLAYER_VOLUME ) ); + } + if ( defined( 'RADIO_PLAYER_SINGLE' ) ) { + $player_single = RADIO_PLAYER_SINGLE; } // --- set script suffix --- @@ -1437,23 +1443,16 @@ function radio_station_player_get_settings() { // $js .= "'suffix': '" . esc_js( $suffix ) . "', "; $js .= "}" . PHP_EOL; - // --- set player script URLs --- - $scripts = array( 'amplitude', 'howler', 'jplayer' ); - // --- maybe limit available scripts for testing purposes --- - if ( isset( $_REQUEST['scripts'] ) ) { - $limit_scripts = explode( ',', $_REQUEST['scripts'] ); - if ( is_array( $limit_scripts ) && ( count( $limit_scripts ) > 0 ) ) { - foreach ( $limit_scripts as $i => $limit_script ) { - if ( !in_array( $limit_script, $scripts ) ) { - unset( $limit_scripts[$i] ); - } - } - } - if ( is_array( $limit_scripts ) && ( count( $limit_scripts ) > 0 ) ) { - $scripts = $limit_scripts; + $valid_scripts = array( 'amplitude', 'howler', 'jplayer' ); + // 2.4.0.3: set single script override only + if ( isset( $_REQUEST['player-script'] ) && in_array( $_REQUST['player-script'], $valid_scripts ) ) { + // 2.4.0.3: only allow admin to override script for testing purposes + if ( function_exists( 'current_user_can' ) && current_user_can( 'manage_options' ) ) { + $player_script = $_REQUEST['player-script']; } } + $scripts = array( $player_script ); // --- set script URL ouput --- // 2.4.0.3: check for fallback script settings @@ -1466,6 +1465,27 @@ function radio_station_player_get_settings() { if ( defined( 'RADIO_PLAYER_FALLBACKS' ) ) { $fallbacks = explode( ',', RADIO_PLAYER_FALLBACKS ); } + // 2.4.0.3: allow for admin-only fallback script override + if ( isset( $_REQUEST['fallback-scripts'] ) ) { + if ( function_exists( 'current_user_can' ) && current_user_can( 'manage_options' ) ) { + $fallback_scripts = explode( ',', $_REQUEST['fallback-scripts'] ); + if ( count( $fallback_scripts ) > 0 ) { + foreach ( $fallback_scripts as $i => $fallback_script ) { + if ( !in_array( $fallback_script, $valid_scripts ) ) { + unset( $fallback_scripts[$i] ); + } + } + } + if ( count( $fallback_scripts ) > 0 ) { + $fallbacks = $fallback_scripts; + } + } + } + + // 2.4.0.3: merge fallbacks with current script + if ( is_array( $fallbacks ) && ( count( $fallbacks ) > 0 ) ) { + $scripts = array_merge( $scripts, $fallbacks ); + } $js .= "scripts = {"; if ( in_array( 'amplitude', $scripts ) ) { $js .= "'amplitude': '" . $radio_player['amplitude_script']['url'] . '?version=' . $radio_player['amplitude_script']['version'] . "', "; @@ -1685,7 +1705,7 @@ function radio_station_player_script_amplitude() { /* set song streams */ songs = new Array(); songs[0] = {'name': '', 'artist': '', 'album': '', 'url': url, 'cover_art_url': '', 'live': true}; - if ('' != fallback) {songs[1] = {'name': '', 'artist': '', 'album': '', 'url': fallback, 'cover_art_url': '', 'live': true};} + /* if ('' != fallback) {songs[1] = {'name': '', 'artist': '', 'album': '', 'url': fallback, 'cover_art_url': '', 'live': true};} */ /* set volume */ if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { @@ -1851,8 +1871,8 @@ function radio_station_player_script_howler() { /* set sources */ sources = new Array(); formats = new Array(); - sources[0] = url; if (fallback != '') {sources[1] = fallback;} - formats[0] = format; if ((fallback != '') && (fformat != '')) {formats[1] = fformat;} + sources[0] = url; /* if (fallback != '') {sources[1] = fallback;} */ + formats[0] = format; /* if ((fallback != '') && (fformat != '')) {formats[1] = fformat;} */ /* set volume */ if (jQuery('#'+container_id+' .rp-volume-slider').hasClass('changed')) { @@ -1978,7 +1998,7 @@ function radio_station_player_script_jplayer() { if (radio_player.debug) {console.log('jPlayer init Volume: '+volume);} media = {}; /* media.title = ''; */ media[format] = url; supplied = format; - if (fallback && fformat) {media[fformat] = fallback; supplied += ', '+fformat;} + /* if (fallback && fformat) {media[fformat] = fallback; supplied += ', '+fformat;} */ radio_player.jplayer_media = media; console.log(radio_player.jplayer_media); radio_player.jplayer_ready = false; diff --git a/radio-station-admin.php b/radio-station-admin.php index 048e665..d415a08 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -7,6 +7,7 @@ // === Admin Setup === // - Enqueue Admin Scripts // - Admin Style Fixes +// - Filter License Activation Link // === Admin Menu === // - Setting Page Capability Check // - Add Admin Menu and Submenu Items @@ -92,6 +93,33 @@ function radio_station_admin_styles() { } +// ------------------------------ +// Filter License Activation Link +// ------------------------------ +// 2.4.0.3: filter license activation link for free version on plugin page +// note: this is because Pro is a separate plugin not a replacement one +add_filter( 'plugin_action_links_' . RADIO_STATION_BASENAME, 'radio_station_license_activation_link', 20, 2 ); +// add_filter( 'network_admin_plugin_action_links_' . RADIO_STATION_BASENAME, , 'radio_station_license_activation_link', 20, 2 ); +function radio_station_license_activation_link( $links, $file ) { + foreach ( $links as $key => $link ) { + // if ( RADIO_STATION_DEBUG ) { + // echo 'Plugin Link ' . $key . ': ' . $link . '' . PHP_EOL; + // } + // --- remove activate premium license link from free version --- + if ( strstr( $key, 'activate-license' ) ) { + unset( $links[$key] ); + } + // --- remove upgrade link if Pro is already installed --- + if ( defined( 'RADIO_STATION_PRO_FILE' ) && strstr( $key, 'upgrade' ) ) { + unset( $links[$key] ); + } + } + // if ( RADIO_STATION_DEBUG ) { + // echo 'Plugin Links: ' . print_r( $links, true ) . '' . PHP_EOL; + // } + return $links; +} + // ------------------ // === Admin Menu === diff --git a/radio-station.php b/radio-station.php index 483f01d..77e8c1e 100644 --- a/radio-station.php +++ b/radio-station.php @@ -44,7 +44,7 @@ // - Include Plugin Admin Files // - Load Plugin Text Domain // - Check Plugin Version -// - Flush Rewrite Rules +// - Flush Rewrite Rules on Deactivation // - Enqueue Plugin Script // - Enqueue Plugin Stylesheet // - Enqueue Datepicker @@ -443,16 +443,17 @@ // --- Fallback Scripts --- // 2.4.0.3: added fallback enable/disable switching + // 2.4.0.3: fixed option label from Player Script 'player_fallbacks' => array( 'type' => 'multicheck', - 'label' => __( 'Player Script', 'radio-station' ), + 'label' => __( 'Fallback Scripts', 'radio-station' ), 'default' => array( 'amplitude', 'howler', 'jplayer' ), 'options' => array( 'jplayer' => __( 'jPlayer', 'radio-station' ), 'howler' => __( 'Howler', 'radio-station' ), 'amplitude' => __( 'Amplitude', 'radio-station' ), ), - 'helper' => __( 'Fallback scripts to enable for when the default Player script fails.', 'radio-station' ), + 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), 'tab' => 'player', 'section' => 'basic', 'pro' => false, @@ -1435,29 +1436,25 @@ // ------------------------- // 2.4.0.3: added check active/installed Pro version $plan = 'free'; -if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { - // --- check for activated pro plugin --- - $plan = 'premium'; -} else { - // --- check for deactivated pro plugin --- - $plugins = wp_cache_get( 'plugins', 'plugins' ); - if ( !$plugins ) { - if ( function_exists( 'get_plugins' ) ) { + +// --- check for deactivated pro plugin --- +$plugins = wp_cache_get( 'plugins', 'plugins' ); +if ( !$plugins ) { + if ( function_exists( 'get_plugins' ) ) { + $plugins = get_plugins(); + } else { + $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; + if ( file_exists( $plugin_path ) ) { + include $plugin_path; $plugins = get_plugins(); - } else { - $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; - if ( file_exists( $plugin_path ) ) { - include $plugin_path; - $plugins = get_plugins(); - } } } - if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { - foreach ( $plugins as $slug => $plugin ) { - if ( strstr( $slug, 'radio-station-pro.php' ) ) { - $plan = 'premium'; - break; - } +} +if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { + foreach ( $plugins as $slug => $plugin ) { + if ( strstr( $slug, 'radio-station-pro.php' ) ) { + $plan = 'premium'; + break; } } } @@ -1507,11 +1504,11 @@ 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', 'hasplans' => true, - 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', - // 'upgrade_link' => add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ), - // 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', - // 'hasaddons' => true, - // 'addons_link' => add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ), + // 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', + 'upgrade_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), + 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', + 'hasaddons' => false, + 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), 'plan' => $plan, ); @@ -1627,9 +1624,9 @@ function radio_station_welcome_redirect() { exit; } */ -// ------------------- -// Flush Rewrite Rules -// ------------------- +// ----------------------------------- +// Flush Rewrite Rules on Deactivation +// ----------------------------------- register_deactivation_hook( RADIO_STATION_FILE, 'flush_rewrite_rules' ); // ---------------------- diff --git a/readme.md b/readme.md index d11f7de..7b95811 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,9 @@ Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.6.2 +Tested up to: 5.8 -Stable tag: 2.3.3.9 +Stable tag: 2.4.0.3 License: GPLv2 or later diff --git a/readme.txt b/readme.txt index 341e6fa..4f02d92 100644 --- a/readme.txt +++ b/readme.txt @@ -220,9 +220,11 @@ You can now visit your site to make sure nothing is broken. If you experience is * Added: option to hide various volume controls * Improved: lazy load player audio fallback scripts * Improved: added author support to post types for quick edit +* Improved: Playlist content template and action hooks * Refix: missing fix to active day tab on pageload * Fixed: player volume slider background position (cross-browser) * Fixed: missing title value for adjacent post links +* Fixed: Fallback scripts and fallback stream URLs = 2.4.0.2 = * Fixed: Multiple Player instance IDs From 6cd16cfb396b78c39243435dde0361d0eead7be0 Mon Sep 17 00:00:00 2001 From: Tony Zeoli Date: Fri, 27 Aug 2021 13:13:16 -0400 Subject: [PATCH 230/377] Add Demo link Add Demo FAQ entry and reference to demo site in first paragraph of Description. --- readme.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 4f02d92..33a4dd6 100644 --- a/readme.txt +++ b/readme.txt @@ -48,7 +48,7 @@ If you are a WordPress developer wanting to contribute to Radio Station, please = Quickstart Guide = -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. +Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. Or, view the combined {FREE and PRO demo site](https://demo.radiostation.pro). Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://netmix.com/radio-station/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://netmix.com/radio-station/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. @@ -88,7 +88,11 @@ Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for = Where can I find the full plugin documentation? = -The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. + += Is there a demo site I can view to see how Radio Statin works? = + +Yes, visite [demo.radiostation.pro](https://demo.radiostation.pro/) to view the FREE and PRO features of Radio Station. You will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users. = How do I schedule a Show? = From d305dcdcf78723a70d5fbf08f11045703e6d3abf Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 31 Aug 2021 14:58:19 +1000 Subject: [PATCH 231/377] dev updates --- .github/ISSUE_TEMPLATE/developer_task.md | 2 +- .../pre-deployment-checklist.md | 48 - player/js/radio-player.js | 10 +- player/radio-player.php | 6 +- radio-station.php | 2 +- radio-station.php.bak | 3656 +++++++++++++++++ readme.md | 32 +- readme.txt | 31 +- readme.txt.bak | 802 ++++ templates/master-schedule-div.php | 3 +- templates/master-schedule-div.php.bak | 226 + templates/master-schedule-legacy.php | 3 +- templates/master-schedule-legacy.php.bak | 255 ++ 13 files changed, 4989 insertions(+), 87 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/pre-deployment-checklist.md create mode 100644 radio-station.php.bak create mode 100644 readme.txt.bak create mode 100644 templates/master-schedule-div.php.bak create mode 100644 templates/master-schedule-legacy.php.bak diff --git a/.github/ISSUE_TEMPLATE/developer_task.md b/.github/ISSUE_TEMPLATE/developer_task.md index cf3411b..b4cafa0 100644 --- a/.github/ISSUE_TEMPLATE/developer_task.md +++ b/.github/ISSUE_TEMPLATE/developer_task.md @@ -17,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Additional context** -Add any other context or screenshots about the feature request here. +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md b/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md deleted file mode 100644 index a8c93af..0000000 --- a/.github/ISSUE_TEMPLATE/pre-deployment-checklist.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Pre-Deployment Checklist -about: All tasks related to a deployment. -title: '' -labels: '' -assignees: '' - ---- - -**Please check that the following items are completed.** - -**For the read.md file, please check on the following items:** -**1. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** - - -**2. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** - - -**3. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** - - -**4. Has the "Upgrade Notice" for the new version been added? If no, please add the upgrade notice and confirm.** - - -**In the readme.txt, please check on the following items:** -**1. Has the changelog been updated? If no, please update and confirm** - - -**2. Does the "Requires At Least" version need to be updated? If yes, has it been updated?** - - -**3. Does the "Tested Up To" version need to be updated? If yes, has it been updated?** - - -**4. Does the "Stable Tag" version need to be updated? If yes, has it been updated?** - - -**In radiostation.php, please check the following items:** -**1. Has the "@version" been updated? If not, please update.** - - -**1. Does the "@version" need to be updated? If yes, has it been updated?** - - -**2. Does the "Version" number need to be updated? If yes, has it been updated?** - - -**3. Does the "Requires at least" need to be updated? If yes, has it been updated?** diff --git a/player/js/radio-player.js b/player/js/radio-player.js index 59f68e4..1f2cca2 100644 --- a/player/js/radio-player.js +++ b/player/js/radio-player.js @@ -451,15 +451,13 @@ function radio_player_volume_slider(instance, volume) { slider = jQuery('#radio_container_'+instance+' .rp-volume-slider'); sliderbg = jQuery('#radio_container_'+instance+' .rp-volume-slider-bg'); thumb = jQuery('#radio_container_'+instance+' .rp-volume-thumb'); - console.log(thumb); if (slider.length) { - sliderbg.hide().css('border','inherit'); + sliderbg.hide(); /* .css('border','inherit'); */ slider.val(volume); swidth = slider.width(); thumb.show(); twidth = thumb.width(); thumb.hide(); - bgwidth = (swidth - twidth) * (volume / 100); - sliderbg.attr('style', 'width: '+bgwidth+'px !important; border:inherit;').show(); - newwidth = parseInt(sliderbg.css('width')); - if (radio_player.debug) {console.log('Volume Slider: '+swidth+' : '+twidth+' : '+bgwidth+' : '+newwidth);} + bgwidth = (swidth - (twidth / 2)) * (volume / 100); + sliderbg.attr('style', 'width: '+bgwidth+'px !important;').show(); /* border:inherit; */ + if (radio_player.debug) {newwidth = parseInt(sliderbg.css('width')); console.log('Volume Slider: '+swidth+' : '+twidth+' : '+bgwidth+' : '+newwidth);} if (volume == 100) {container.addClass('maxed');} else {container.removeClass('maxed');} } } diff --git a/player/radio-player.php b/player/radio-player.php index 65e616f..7d67e85 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -2519,10 +2519,10 @@ function radio_station_player_control_styles( $instance ) { // --- Volume Range Input and Container --- // ref: http://danielstern.ca/range.css/#/ // ref: https://css-tricks.fcom/sliding-nightmare-understanding-range-input/ + // 2.4.0.4: added no border style to range input (border added on some themes) $css .= "/* Range Input */ -" . $container . " .rp-volume-controls input[type=range] { - margin: 0; background-color: transparent; vertical-align: middle; -webkit-appearance: none; -} +" . $container . " .rp-volume-controls input[type=range] {"; + $css .= "margin: 0; background-color: transparent; vertical-align: middle; -webkit-appearance: none; border: none;} " . $container . " .rp-volume-controls input[type=range]:focus {outline: none; box-shadow: none;} " . $container . " .rp-volume-controls input[type=range]::-moz-focus-inner, " . $container . " .rp-volume-controls input[type=range]::-moz-focus-outer {outline: none; box-shadow: none;}" . PHP_EOL; diff --git a/radio-station.php b/radio-station.php index 77e8c1e..e1b1453 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.3 +Version: 2.4.0.3.1 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/radio-station.php.bak b/radio-station.php.bak new file mode 100644 index 0000000..77e8c1e --- /dev/null +++ b/radio-station.php.bak @@ -0,0 +1,3656 @@ + array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Streaming URL', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Stream Format --- + 'streaming_format' => array( + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Streaming Format', 'radio-station' ), + 'default' => 'aac', + 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Fallback Stream URL --- + 'fallback_url' => array( + 'type' => 'text', + 'options' => 'URL', + 'label' => __( 'Fallback Stream URL', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Fallback Stream Format --- + 'fallback_format' => array( + 'type' => 'select', + 'options' => $formats, + 'label' => __( 'Fallback Format', 'radio-station' ), + 'default' => 'ogg', + 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // --- Main Radio Language --- + 'radio_language' => array( + 'type' => 'select', + 'options' => $languages, + 'label' => __( 'Main Broadcast Language', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select the main language used on your Radio Station.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'broadcast', + ), + + // === Station === + + // --- Station Title --- + // 2.3.3.8: added station title field + 'station_title' => array( + 'type' => 'text', + 'label' => __( 'Station Title', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Image --- + // 2.3.3.8: added station logo image field + 'station_image' => array( + 'type' => 'image', + 'label' => __( 'Station Logo Image', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Timezone Location --- + 'timezone_location' => array( + 'type' => 'select', + 'options' => $timezones, + 'label' => __( 'Location Timezone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Clock Time Format --- + 'clock_time_format' => array( + 'type' => 'select', + 'options' => array( + '12' => __( '12 Hour Format', 'radio-station' ), + '24' => __( '24 Hour Format', 'radio-station' ), + ), + 'label' => __( 'Clock Time Format', 'radio-station' ), + 'default' => '12', + 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Phone Number --- + // 2.3.3.6: added station phone number option + 'station_phone' => array( + 'type' => 'text', + 'options' => 'PHONE', + 'label' => __( 'Station Phone', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Phone for Shows --- + // 2.3.3.6: added default to station phone option + 'shows_phone' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Phone Display', 'radio-station' ), + 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Station Email Address --- + // 2.3.3.8: added station email address option + 'station_email' => array( + 'type' => 'email', + 'default' => '', + 'label' => __( 'Station Email', 'radio-station' ), + 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // --- Email for Shows --- + // 2.3.3.8: added default to email address option + 'shows_email' => array( + 'type' => 'checkbox', + 'default' => '', + 'value' => 'yes', + 'label' => __( 'Show Email Display', 'radio-station' ), + 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'station', + ), + + // === Feeds === + + // --- REST Data Routes --- + 'enable_data_routes' => array( + 'type' => 'checkbox', + 'label' => __( 'Enable Data Routes', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Data Feed Links --- + 'enable_data_feeds' => array( + 'type' => 'checkbox', + 'label' => __( 'Enable Data Feeds', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Ping Netmix Directory --- + // note: disabled by default for WordPress.org repository compliance + 'ping_netmix_directory' => array( + 'type' => 'checkbox', + 'label' => __( 'Ping Netmix Directory', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Clear Transients --- + 'clear_transients' => array( + 'type' => 'checkbox', + 'label' => __( 'Clear Transients', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + ), + + // --- Transient Caching --- + 'transient_caching' => array( + 'type' => 'checkbox', + 'label' => __( 'Show Transients', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + 'pro' => true, + ), + + // --- Show Shift Feeds --- + /* 'show_shift_feeds' => array( + 'type' => 'checkbox', + 'label' => __( 'Show Shift Feeds', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ), + 'tab' => 'general', + 'section' => 'feeds', + 'pro' => true, + ), */ + + // === Basic Stream Player === + + // TODO: add note about these defaults being overrideable in widgets + + // --- Player Title --- + 'player_title' => array ( + 'type' => 'checkbox', + 'label' => __( 'Display Station Title', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Player Image --- + 'player_image' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Station Image', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Player Script --- + // 2.4.0.3: change script default to jplayer + 'player_script' => array( + 'type' => 'select', + 'label' => __( 'Player Script', 'radio-station' ), + 'default' => 'jplayer', + 'options' => array( + 'jplayer' => __( 'jPlayer', 'radio-station' ), + 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => __( 'Amplitude', 'radio-station' ), + ), + 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Fallback Scripts --- + // 2.4.0.3: added fallback enable/disable switching + // 2.4.0.3: fixed option label from Player Script + 'player_fallbacks' => array( + 'type' => 'multicheck', + 'label' => __( 'Fallback Scripts', 'radio-station' ), + 'default' => array( 'amplitude', 'howler', 'jplayer' ), + 'options' => array( + 'jplayer' => __( 'jPlayer', 'radio-station' ), + 'howler' => __( 'Howler', 'radio-station' ), + 'amplitude' => __( 'Amplitude', 'radio-station' ), + ), + 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Player Theme --- + 'player_theme' => array( + 'type' => 'select', + 'label' => __( 'Default Player Theme', 'radio-station' ), + 'default' => 'light', + 'options' => array( + 'light' => __( 'Light', 'radio-station' ), + 'dark' => __( 'Dark', 'radio-station' ), + ), + 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Player Buttons --- + 'player_buttons' => array( + 'type' => 'select', + 'label' => __( 'Default Player Buttons', 'radio-station' ), + 'default' => 'rounded', + 'options' => array( + 'circular' => __( 'Circular Buttons', 'radio-station' ), + 'rounded' => __( 'Rounded Buttons', 'radio-station' ), + 'square' => __( 'Square Buttons', 'radio-station' ), + ), + 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // --- Volume Controls --- + // 2.4.0.3: added enable/disable volume controls option + 'player_volumes' => array( + 'type' => 'multicheck', + 'label' => __( 'Volume Controls', 'radio-station' ), + 'default' => array( 'slider', 'updown', 'mute', 'max' ), + 'options' => array( + 'slider' => __( 'Volume Slider', 'radio-station' ), + 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), + 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), + 'max' => __( 'Maximize Volume', 'radio-station' ), + ), + 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + ), + + // --- Player Debug Mode --- + 'player_debug' => array( + 'type' => 'checkbox', + 'label' => __( 'Player Debug Mode', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'basic', + 'pro' => false, + ), + + // === Player Colours === + + // --- [Pro] Playing Highlight Color --- + 'player_playing_color' => array( + 'type' => 'color', + 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), + 'default' => '#70E070', + 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro] Control Icons Highlight Color --- + 'player_buttons_color' => array( + 'type' => 'color', + 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), + 'default' => '#00A0E0', + 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro] Volume Knob Color --- + 'player_thumb_color' => array( + 'type' => 'color', + 'label' => __( 'Volume Knob Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // --- [Pro] Volume Track Color --- + 'player_range_color' => array( + 'type' => 'coloralpha', + 'label' => __( 'Volume Track Color', 'radio-station' ), + 'default' => '#80C080', + 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'colors', + 'pro' => true, + ), + + // === Advanced Stream Player === + + // --- Player Volume --- + 'player_volume' => array( + 'type' => 'number', + 'label' => __( 'Player Start Volume', 'radio-station' ), + 'default' => 77, + 'min' => 0, + 'step' => 1, + 'max' => 100, + 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- Single Player --- + 'player_single' => array( + 'type' => 'checkbox', + 'label' => __( 'Single Player at Once', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => false, + ), + + // --- [Pro] Player Autoresume --- + 'player_autoresume' => array( + 'type' => 'checkbox', + 'label' => __( 'Autoresume Playback', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), + + // --- [Pro] Popup Player Window --- + /* 'player_popup' => array( + 'type' => 'checkbox', + 'label' => __( 'Popup Player Window', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Add a popup icon to your Player to open it in a separate window.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'advanced', + 'pro' => true, + ), */ + + // === Sitewide Player Bar === + + // --- Player Bar Note --- + 'player_bar_note' => array( + 'type' => 'note', + 'label' => __( 'Bar Defaults Note', 'radio-station' ), + 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) + . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + // 'pro' => true, + ), + + // --- [Pro] Sitewide Player Bar --- + 'player_bar' => array( + 'type' => 'select', + 'label' => __( 'Sitewide Player Bar', 'radio-station' ), + 'default' => 'off', + 'options' => array( + 'off' => __( 'No Player Bar', 'radio-station' ), + 'top' => __( 'Top Player Bar', 'radio-station' ), + 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), + ), + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro] Player Bar Height --- + 'player_bar_height' => array( + 'type' => 'number', + 'min' => 40, + 'max' => 400, + 'step' => 1, + 'label' => __( 'Player Bar Height', 'radio-station' ), + 'default' => 80, + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro] Fade In Player Bar --- + 'player_bar_fadein' => array( + 'type' => 'number', + 'label' => __( 'Fade In Player Bar', 'radio-station' ), + 'default' => 2500, + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Continuous Playback --- + // 2.4.0.1: fix for missing value field + 'player_bar_continuous' => array( + 'type' => 'checkbox', + 'label' => __( 'Continuous Playback', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Player Page Fade --- + 'player_bar_pagefade' => array( + 'type' => 'number', + 'label' => __( 'Page Fade Time', 'radio-station' ), + 'default' => 2000, + 'min' => 0, + 'step' => 100, + 'max' => 10000, + 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Page Load Timeout --- + // 2.4.0.3: add page load timeout option + 'player_bar_timeout' => array( + 'type' => 'number', + 'label' => __( 'Page Load Timeout', 'teleporter' ), + 'default' => 7000, + 'min' => 0, + 'step' => 500, + 'max' => 20000, + 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Bar Player Text Color --- + 'player_bar_text' => array( + 'type' => 'color', + 'label' => __( 'Bar Player Text Color', 'radio-station' ), + 'default' => '#FFFFFF', + 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Bar Player Background Color --- + 'player_bar_background' => array( + 'type' => 'coloralpha', + 'label' => __( 'Bar Player Background Color', 'radio-station' ), + 'default' => 'rgba(0,0,0,255)', + 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), + 'tab' => 'player', + 'section' => 'bar', + 'pro' => true, + ), + + // --- [Pro] Display Current Show --- + // 2.4.0.3: added for current show display + 'player_bar_currentshow' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Current Show', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), + 'pro' => true, + ), + + // --- [Pro] Display Metadata --- + // 2.4.0.3: added for now playing metadata display + 'player_bar_nowplaying' => array( + 'type' => 'checkbox', + 'label' => __( 'Display Now Playing', 'radio-station' ), + 'value' => 'yes', + 'default' => 'yes', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), + 'pro' => true, + ), + + // --- Metadata URL --- + // 2.4.0.3: added for alternative stream metadata URL + 'player_bar_metadata' => array( + 'type' => 'url', + 'label' => __( 'Metadata URL', 'radio-station' ), + 'default' => '', + 'tab' => 'player', + 'section' => 'bar', + 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), + 'pro' => true, + ), + + // TODO: additional CSS input textarea field ? + + // === Master Schedule Page === + + // --- Schedule Page --- + 'schedule_page' => array( + 'type' => 'select', + 'options' => 'PAGEID', + 'label' => __( 'Master Schedule Page', 'radio-station' ), + 'default' => '', + 'helper' => __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Automatic Schedule Display --- + 'schedule_auto' => array( + 'type' => 'checkbox', + 'label' => __( 'Automatic Display', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]', + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Default Schedule View --- + 'schedule_view' => array( + 'type' => 'select', + 'label' => __( 'Schedule View Default', 'radio-station' ), + 'default' => 'table', + 'options' => array( + 'table' => __( 'Table View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + 'div' => __( 'Divs View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'default' => __( 'Legacy Table', 'radio-station' ), + ), + 'helper' => __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- Schedule Clock Display --- + 'schedule_clock' => array( + 'type' => 'select', + 'label' => __( 'Schedule Clock?', 'radio-station' ), + 'default' => 'clock', + 'options' => array( + '' => __( 'None', 'radio-station' ), + 'clock' => __( 'Clock', 'radio-station' ), + 'timezone' => __( 'Timezone', 'radio-station' ), + ), + 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + ), + + // --- [Pro] Schedule Switcher --- + 'schedule_switcher' => array( + 'type' => 'checkbox', + 'label' => __( 'View Switching', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + + // --- [Pro] Available Views === + // 2.3.2: added additional views option + 'schedule_views' => array( + 'type' => 'multicheck', + 'label' => __( 'Available Views', 'radio-station' ), + // note: unstyled list view not included in defaults + 'default' => array( 'table', 'calendar' ), + 'value' => 'yes', + 'options' => array( + 'table' => __( 'Table View', 'radio-station' ), + 'tabs' => __( 'Tabbed View', 'radio-station' ), + 'list' => __( 'List View', 'radio-station' ), + 'grid' => __( 'Grid View', 'radio-station' ), + 'calendar' => __( 'Calendar View', 'radio-station' ), + ), + 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + + // === Show Pages === + + // --- Show Blocks Position --- + 'show_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // ---- Show Section Layout --- + 'show_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Show Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Header Image --- + // 2.3.2: added plural to option label + 'show_header_image' => array( + 'type' => 'checkbox', + 'label' => __( 'Content Header Images', 'radio-station' ), + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Latest Show Posts --- + // 'show_latest_posts' => array( + // 'type' => 'numeric', + // 'label' => __( 'Latest Show Posts', 'radio-station' ), + // 'step' => 1, + // 'min' => 0, + // 'max' => 100, + // 'default' => 3, + // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'show', + // ), + + // --- Show Posts Per Page --- + 'show_posts_per_page' => array( + 'type' => 'numeric', + 'label' => __( 'Posts per Page', 'radio-station' ), + 'step' => 1, + 'min' => 0, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- Show Playlists per Page --- + 'show_playlists_per_page' => array( + 'type' => 'numeric', + 'step' => 1, + 'min' => 0, + 'max' => 1000, + 'label' => __( 'Playlists per Page', 'radio-station' ), + 'default' => 10, + 'helper' => __( 'Playlists per page on the Show Page tab/display', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + ), + + // --- [Pro] Show Episodes per Page --- + 'show_episodes_per_page' => array( + 'type' => 'number', + 'label' => __( 'Episodes per Page', 'radio-station' ), + 'step' => 1, + 'min' => 1, + 'max' => 1000, + 'default' => 10, + 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'show', + 'pro' => true, + ), + + // === Profile Pages === + // 2.3.3.9: added proflie page settings + + // --- [Pro] Profile Blocks Position --- + 'profile_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // ---- [Pro] Profile Section Layout --- + 'profile_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Profile Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'profile', + 'pro' => true, + ), + + // === Episode Pages === + // 2.3.3.9: added episode page settings + + // --- [Pro] Episode Blocks Position --- + 'episode_block_position' => array( + 'type' => 'select', + 'label' => __( 'Info Blocks Position', 'radio-station' ), + 'options' => array( + 'left' => __( 'Float Left', 'radio-station' ), + 'right' => __( 'Float Right', 'radio-station' ), + 'top' => __( 'Float Top', 'radio-station' ), + ), + 'default' => 'left', + 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + // ---- [Pro] Episode Section Layout --- + 'episode_section_layout' => array( + 'type' => 'select', + 'label' => __( 'Episode Content Layout', 'radio-station' ), + 'options' => array( + 'tabbed' => __( 'Tabbed', 'radio-station' ), + 'standard' => __( 'Standard', 'radio-station' ), + ), + 'default' => 'tabbed', + 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'episode', + 'pro' => true, + ), + + + // ==== Archives === + + // --- Shows Archive Page --- + 'show_archive_page' => array( + 'label' => __( 'Shows Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + 'show_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Shows Archive --- ? + // 'show_archive_override' => array( + // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + + // --- Overrides Archive Page --- + 'override_archive_page' => array( + 'label' => __( 'Overrides Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + 'override_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Overrides Archive --- ? + // 'override_archive_override' => array( + // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + + // --- Playlists Archive Page --- + 'playlist_archive_page' => array( + 'label' => __( 'Playlists Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + 'playlist_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Playlists Archive --- ? + // 'playlist_archive_override' => array( + // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + + // --- Genres Archive Page --- + 'genre_archive_page' => array( + 'label' => __( 'Genres Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + 'genre_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Genres Archives --- ? + // 'genre_archive_override' => array( + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + + // --- Languages Archive Page --- + // 2.3.3.9: added language archive page + 'language_archive_page' => array( + 'label' => __( 'Languages Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + ), + + // --- Automatic Display --- + // 2.3.3.9: added language archive automatic page + 'language_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', + 'tab' => 'pages', + 'section' => 'archives', + ), + + // ? --- Redirect Languages Archives --- ? + // 'language_archive_override' => array( + // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), + // 'type' => 'checkbox', + // 'value' => 'yes', + // 'default' => '', + // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), + // 'tab' => 'pages', + // 'section' => 'archives', + // ), + + // === Single Templates === + + // --- Templates Change Note --- + 'templates_change_note' => array( + 'type' => 'note', + 'label' => __( 'Templates Change Note', 'radio-station' ), + 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) + . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) + . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Show Template --- + 'show_template' => array( + 'label' => __( 'Show Template', 'radio-station' ), + 'type' => 'select', + 'options' => array( + 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), + 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), + 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), + 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + ), + 'default' => 'page', + 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Combined Template Method --- + 'show_template_combined' => array( + 'label' => __( 'Combined Method', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Playlist Template --- + // 2.3.3.8: added missing singular.php option to match show_template + 'playlist_template' => array( + 'label' => __( 'Playlist Template', 'radio-station' ), + 'type' => 'select', + 'options' => array( + 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), + 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), + 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), + 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), + ), + 'default' => 'page', + 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // --- Combined Template Method --- + 'playlist_template_combined' => array( + 'label' => __( 'Combined Method', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => '', + 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'single', + ), + + // === Widgets === + + // --- AJAX Loading --- + // 2.3.3: fix to value of value key + 'ajax_widgets' => array( + 'type' => 'checkbox', + 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + ), + + // --- [Pro] Dynamic Reloading --- + 'dynamic_reload' => array( + 'type' => 'checkbox', + 'label' => __( 'Dynamic Reloading?', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // --- Translate User Times --- + 'convert_show_times' => array( + 'type' => 'checkbox', + 'label' => __( 'Convert Show Times', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // --- [Pro] Timezone Switching --- + 'timezone_switching' => array( + 'type' => 'checkbox', + 'label' => __( 'User Timezone Switching', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow visitors to select their Timezone manually for Show time translations.', 'radio-station' ), + 'tab' => 'widgets', + 'section' => 'loading', + 'pro' => true, + ), + + // === Roles / Capabilities / Permissions === + // 2.3.0: added new capability and role options + + // --- Show Editing Permission Note --- + // 2.4.0.3: added role to show assignment note + 'permissions_show_role_note' => array( + 'type' => 'note', + 'label' => __( 'Show Editing Permissions', 'radio-station' ), + 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) + . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Playlist Editing Role Note --- + // 2.4.0.3: added role to playlist assignment note + 'permissions_playlistow_role_note' => array( + 'type' => 'note', + 'label' => __( 'Playlist Permissions', 'radio-station' ), + 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Show Editor Role Note --- + 'show_editor_role_note' => array( + 'type' => 'note', + 'label' => __( 'Show Editor Role', 'radio-station' ), + 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) + . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) + . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Author Role Capabilities --- + 'add_author_capabilities' => array( + 'type' => 'checkbox', + 'label' => __( 'Add to Author Capabilities', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // --- Editor Role Capabilities --- + 'add_editor_capabilities' => array( + 'type' => 'checkbox', + 'label' => __( 'Add to Editor Capabilities', 'radio-station' ), + 'default' => 'yes', + 'value' => 'yes', + 'helper' => __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ), + 'tab' => 'roles', + 'section' => 'permissions', + ), + + // ? --- Disallow Shift Changes --- ? + // 'disallow_shift_changes' => array( + // 'type' => 'checkbox', + // 'label' => __( 'Disallow Shift Changes', 'radio-station' ), + // 'default' => array(), + // 'options' => array( + // 'authors' => __( 'WordPress Authors', 'radio-station' ), + // 'editors' => __( 'WorddPress Editors', 'radio-station' ), + // 'hosts' => __( 'Assigned DJs / Hosts', 'radio-station' ), + // 'producers' => __( 'Assigned Producers', 'radio-station' ), + // ), + // 'helper' => __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ), + // 'tab' => 'roles', + // 'section' => 'permissions', + // 'pro' => true, + // ), + + // === Tabs and Sections === + + // --- Tab Labels --- + // 2.3.2: add widget options tab + // 2.3.3.8: added player options tab + // 2.3.3.8: move templates section onto pages tab + 'tabs' => array( + 'general' => __( 'General', 'radio-station' ), + 'pages' => __( 'Pages', 'radio-station' ), + 'player' => __( 'Player', 'radio-station' ), + // 'templates' => __( 'Templates', 'radio-station' ), + 'widgets' => __( 'Widgets', 'radio-station' ), + 'roles' => __( 'Roles', 'radio-station' ), + ), + + // --- Section Labels --- + // 2.3.2: add widget loading section + // 2.3.3.9: added profile pages section + 'sections' => array( + 'broadcast' => __( 'Broadcast', 'radio-station' ), + 'station' => __( 'Station', 'radio-station' ), + 'feeds' => __( 'Feeds', 'radio-station' ), + 'basic' => __( 'Basic Defaults', 'radio-station' ), + 'advanced' => __( 'Advanced Defaults', 'radio-station' ), + 'colors' => __( 'Player Colors', 'radio-station' ), + 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), + 'single' => __( 'Single Templates', 'radio-station' ), + 'archive' => __( 'Archive Templates', 'radio-station' ), + 'schedule' => __( 'Schedule Page', 'radio-station' ), + 'show' => __( 'Show Pages', 'radio-station' ), + 'profile' => __( 'Profile Pages', 'radio-station' ), + 'episode' => __( 'Episode Pages', 'radio-station' ), + 'archives' => __( 'Archives', 'radio-station' ), + 'loading' => __( 'Widget Loading', 'radio-station' ), + 'permissions' => __( 'Permissions', 'radio-station' ), + ), +); + +// ------------------------- +// Pro Version Install Check +// ------------------------- +// 2.4.0.3: added check active/installed Pro version +$plan = 'free'; + +// --- check for deactivated pro plugin --- +$plugins = wp_cache_get( 'plugins', 'plugins' ); +if ( !$plugins ) { + if ( function_exists( 'get_plugins' ) ) { + $plugins = get_plugins(); + } else { + $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; + if ( file_exists( $plugin_path ) ) { + include $plugin_path; + $plugins = get_plugins(); + } + } +} +if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { + foreach ( $plugins as $slug => $plugin ) { + if ( strstr( $slug, 'radio-station-pro.php' ) ) { + $plan = 'premium'; + break; + } + } +} + +// ---------------------- +// Plugin Loader Settings +// ---------------------- +// 2.3.0: added plugin loader settings +$slug = 'radio-station'; + +// --- settings array --- +$settings = array( + // --- Plugin Info --- + 'slug' => $slug, + 'file' => __FILE__, + 'version' => '0.0.1', + + // --- Menus and Links --- + 'title' => 'Radio Station', + 'parentmenu' => 'radio-station', + 'home' => RADIO_STATION_HOME_URL, + 'docs' => RADIO_STATION_DOCS_URL, + 'support' => 'https://github.com/netmix/radio-station/issues/', + 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), + 'share' => RADIO_STATION_HOME_URL . '#share', + 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), + 'donate' => 'https://patreon.com/radiostation', + 'donatetext' => __( 'Support this Plugin', 'radio-station' ), + 'readme' => false, + 'settingsmenu' => false, + + // --- Options --- + 'namespace' => 'radio_station', + 'settings' => 'rs', + 'option' => 'radio_station', + 'options' => $options, + + // --- WordPress.Org --- + 'wporgslug' => 'radio-station', + 'wporg' => true, + 'textdomain' => 'radio-station', + + // --- Freemius --- + // 2.4.0.1: turn on addons switch for Pro + // 2.4.0.3: turn on plans switch for Pro also + // 2.4.0.3: set Pro details and Upgrade links + 'freemius_id' => '4526', + 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', + 'hasplans' => true, + // 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', + 'upgrade_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), + 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', + 'hasaddons' => false, + 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), + 'plan' => $plan, +); + +// ------------------------- +// Set Plugin Option Globals +// ------------------------- +global $radio_station_data; +$radio_station_data['options'] = $options; +$radio_station_data['settings'] = $settings; + +// ---------------------------- +// Start Plugin Loader Instance +// ---------------------------- +require RADIO_STATION_DIR . '/loader.php'; +$instance = new radio_station_loader( $settings ); + +// -------------------------- +// Include Plugin Admin Files +// -------------------------- +// 2.2.7: added conditional load of admin includes +// 2.2.7: moved all admin functions to radio-station-admin.php +if ( is_admin() ) { + require RADIO_STATION_DIR . '/radio-station-admin.php'; + require RADIO_STATION_DIR . '/includes/post-types-admin.php'; + + // --- Contextual Help --- + // 2.3.0: maybe load contextual help config + if ( file_exists( RADIO_STATION_DIR . '/help/contextual-help-config.php' ) ) { + include RADIO_STATION_DIR . '/help/contextual-help-config.php'; + } +} + +// ----------------------- +// Load Plugin Text Domain +// ----------------------- +add_action( 'plugins_loaded', 'radio_station_init' ); +function radio_station_init() { + // 2.3.0: use RADIO_STATION_DIR constant + load_plugin_textdomain( 'radio-station', false, RADIO_STATION_DIR . '/languages' ); +} + +// -------------------- +// Check Plugin Version +// -------------------- +// 2.3.0: check plugin version for updates and announcements +add_action( 'init', 'radio_station_check_version', 9 ); +function radio_station_check_version() { + + // --- get current and stored versions --- + // 2.3.2: use plugin version function + $version = radio_station_plugin_version(); + $stored_version = get_option( 'radio_station_version', false ); + + // --- check current against stored version --- + if ( !$stored_version ) { + + // --- no stored plugin version, add it now --- + update_option( 'radio_station_version', $version ); + + if ( version_compare( $version, '2.3.0', '>=' ) ) { + // --- flush rewrite rules (for new post type and rest route rewrites) --- + // (handled separately as 2.3.0 is first version with version checking) + add_option( 'radio_station_flush_rewrite_rules', true ); + } + + } elseif ( version_compare( $version, $stored_version, '>' ) ) { + + // --- updates from before to after x.x.x --- + // (code template if/when needed for future release updates) + // if ( ( version_compare( $version, 'x.x.x', '>=' ) ) + // && ( version_compare( $stored_version, 'x.x.x', '<' ) ) ) { + // // eg. trigger a single thing to do + // add_option( 'radio_station_do_thing_once', true ); + // } + + // --- bump stored version to current version --- + update_option( 'radio_station_previous_version', $stored_version ); + update_option( 'radio_station_version', $version ); + } +} + +// ----------------- +// Plugin Activation +// ----------------- +// (run on plugin activation, and thus also after a plugin update) +// 2.2.8: fix for mismatched flag function name +register_activation_hook( RADIO_STATION_FILE, 'radio_station_plugin_activation' ); +function radio_station_plugin_activation() { + // --- flag to flush rewrite rules --- + // 2.2.3: added this for custom post types rewrite flushing + add_option( 'radio_station_flush_rewrite_rules', true ); + + // --- clear schedule transients --- + // 2.3.3: added clear transients on (re)activation + // 2.3.3.9: just use clear cached data function + radio_station_clear_cached_data( false ); + + // --- set welcome redirect transient --- + // TODO: check if handled by Freemius activation + // set_transient( 'radio_station_welcome', 1, 7 ); +} + +// --------------------------- +// Activation Welcome Redirect +// --------------------------- +/* add_action( 'admin_init', 'radio_station_welcome_redirect' ); +function radio_station_welcome_redirect() { + if ( !get_transient( 'radio_station_welcome' ) || wp_doing_ajax() || is_network_admin() || !current_user_can( 'install_plugins' ) ) { + return; + } + delete_transient( 'radio_station_welcome' ); + wp_safe_redirect( admin_url( 'admin.php?page=radio-station&welcome=1' ) ); + exit; +} */ + +// ----------------------------------- +// Flush Rewrite Rules on Deactivation +// ----------------------------------- +register_deactivation_hook( RADIO_STATION_FILE, 'flush_rewrite_rules' ); + +// ---------------------- +// Enqueue Plugin Scripts +// ---------------------- +// 2.3.0: added for enqueueing main Radio Station script +add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_scripts' ); +function radio_station_enqueue_scripts() { + + // --- enqueue custom stylesheet if found --- + // 2.3.0: added for automatic custom style loading + radio_station_enqueue_style( 'custom' ); + + // --- enqueue plugin script --- + // 2.3.0: added jquery dependency for inline script fragments + radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); + + // --- set script suffix --- + $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + $suffix = ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) ? '' : $suffix; + + // -- enqueue javascript timezone detection script --- + // 2.3.3.9: activated for improved timezone detection + $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); + + // --- Moment.js --- + // ref: https://momentjs.com + // 2.3.3.9: added for improved time format display + $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); + wp_enqueue_script( 'momentjs', $moment_url, array(), '2.29.1', false ); + +} + +// --------------------- +// Enqueue Plugin Script +// --------------------- +function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = false ) { + + // --- set stylesheet filename and child theme path --- + $filename = $scriptkey . '.js'; + + // 2.3.0: check template hierarchy for file + $template = radio_station_get_template( 'both', $filename, 'js' ); + if ( $template ) { + + // 2.3.2: use plugin version for releases + $plugin_version = radio_station_plugin_version(); + $version_length = strlen( $plugin_version ); + // TODO: maybe allow for minor version release numbers + // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { + if ( 5 == $version_length ) { + $version = $plugin_version; + } else { + $version = filemtime( $template['file'] ); + } + + $url = $template['url']; + + // --- enqueue script --- + wp_enqueue_script( $scriptkey, $url, $deps, $version, $infooter ); + } +} + +// ------------------------- +// Enqueue Plugin Stylesheet +// ------------------------- +// ?.?.?: widgets.css style conditional enqueueing moved to within widget classes +// 2.3.0: added abstracted method for enqueueing plugin stylesheets +// 2.3.0: moved master schedule style enqueueing to conditional in master-schedule.php +function radio_station_enqueue_style( $stylekey ) { + + // --- check style enqueued switch --- + global $radio_station_styles; + if ( !isset( $radio_station_styles ) ) { + $radio_station_styles = array(); + } + if ( !isset( $radio_station_styles[$stylekey] ) ) { + + // --- set stylesheet filename and child theme path --- + $filename = 'rs-' . $stylekey . '.css'; + + // 2.3.0: check template hierarchy for file + $template = radio_station_get_template( 'both', $filename, 'css' ); + if ( $template ) { + + // --- use found template values --- + // 2.3.2: use plugin version for releases + $plugin_version = radio_station_plugin_version(); + $version_length = strlen( $plugin_version ); + // TODO: maybe allow for minor version release numbers ? + // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { + if ( 5 == $version_length ) { + $version = $plugin_version; + } else { + $version = filemtime( $template['file'] ); + } + $url = $template['url']; + + // --- enqueue styles in footer --- + wp_enqueue_style( 'rs-' . $stylekey, $url, array(), $version, 'all' ); + + // --- set style enqueued switch --- + $radio_station_styles[$stylekey] = true; + } + } +} + +// ------------------ +// Enqueue Datepicker +// ------------------ +// 2.3.0: enqueued separately by override post type only +// 2.3.3.9: moved here from radio-station-admin.php +function radio_station_enqueue_datepicker() { + + // --- enqueue jquery datepicker --- + wp_enqueue_script( 'jquery-ui-datepicker' ); + + // --- enqueue jquery datepicker styles --- + // 2.3.0: update theme styles from 1.8.2 to 1.12.1 + // 2.3.0: use local datepicker styles instead of via Google + // $protocol = 'http'; + // if ( is_ssl() ) {$protocol .= 's';} + // $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'; + // wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.12.1' ); + $style = radio_station_get_template( 'both', 'jquery-ui.css', 'css' ); + wp_enqueue_style( 'jquery-ui-smoothness', $style['url'], array(), '1.12.1', 'all' ); + +} + +// ------------------------------- +// Enqueue Localized Script Values +// ------------------------------- +add_action( 'wp_enqueue_scripts', 'radio_station_localize_script' ); +function radio_station_localize_script() { + + $js = radio_station_localization_script(); + wp_add_inline_script( 'radio-station', $js ); +} + +// ------------------- +// Localization Script +// ------------------- +// 2.3.3.9: separated script from enqueueing +function radio_station_localization_script() { + + // --- create settings objects --- + $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; + + // --- set AJAX URL --- + // 2.3.2: add admin AJAX URL + $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; + + // --- clock time format --- + // TODO: maybe set time format ? + // ref: https://devhints.io/wip/intl-datetime + $clock_format = radio_station_get_setting( 'clock_time_format' ); + $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; + + // --- detect touchscreens --- + // ref: https://stackoverflow.com/a/52855084/5240159 + $js .= "if (window.matchMedia('(pointer: coarse)').matches) {radio.touchscreen = true;} else {radio.touchscreen = false;}" . PHP_EOL; + + // --- set debug flag --- + if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { + $js .= "radio.debug = true;" . PHP_EOL; + } else { + $js .= "radio.debug = false;" . PHP_EOL; + } + + // --- radio timezone --- + // 2.3.2: added get timezone function + $timezone = radio_station_get_timezone(); + + if ( stristr( $timezone, 'UTC' ) ) { + + if ( 'UTC' == $timezone ) { + $offset = '0'; + } else { + $offset = str_replace( 'UTC', '', $timezone ); + } + $js .= "radio.timezone.offset = " . esc_js( $offset * 60 * 60 ) . "; "; + if ( '0' == $offset ) { + $offset = ''; + } elseif ( $offset > 0 ) { + $offset = '+' . $offset; + } + $js .= "radio.timezone.code = 'UTC" . esc_js( $offset ) . "'; "; + $js .= "radio.timezone.utc = '" . esc_js( $offset ) . "'; "; + $js .= "radio.timezone.utczone = true; "; + + } else { + + // --- get offset and code from timezone location --- + $datetimezone = new DateTimeZone( $timezone ); + $offset = $datetimezone->getOffset( new DateTime() ); + $offset_hours = $offset / ( 60 * 60 ); + if ( 0 == $offset ) { + $utc_offset = ''; + } elseif ( $offset > 0 ) { + $utc_offset = '+' . $offset_hours; + } else { + $utc_offset = $offset_hours; + } + $utc_offset = 'UTC' . $utc_offset; + $code = radio_station_get_timezone_code( $timezone ); + $js .= "radio.timezone.location = '" . esc_js( $timezone ) . "'; "; + $js .= "radio.timezone.offset = " . esc_js( $offset ) . "; "; + $js .= "radio.timezone.code = '" . esc_js( $code ) . "'; "; + $js .= "radio.timezone.utc = '" . esc_js( $utc_offset ) . "'; "; + $js .= "radio.timezone.utczone = false; "; + + } + + if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { + $js .= "radio.timezone.adjusted = false; "; + } else { + $js .= "radio.timezone.adjusted = true; "; + } + + // --- set user timezone offset --- + // (and convert offset minutes to seconds) + $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . PHP_EOL; + + // --- translated months array --- + // 2.3.2: also translate short month labels + $js .= "radio.labels.months = new Array("; + $short = "radio.labels.smonths = new Array("; + $months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ); + foreach ( $months as $i => $month ) { + $month = radio_station_translate_month( $month ); + $short_month = radio_station_translate_month( $month, true ); + $month = str_replace( "'", "", $month ); + $short_month = str_replace( "'", "", $short_month ); + $js .= "'" . esc_js( $month ) . "'"; + $short .= "'" . esc_js( $short_month ) . "'"; + if ( $i < ( count( $months ) - 1 ) ) { + $js .= ", "; + $short .= ", "; + } + } + $js .= ");" . PHP_EOL; + $js .= $short . ");" . PHP_EOL; + + // --- translated days array --- + // 2.3.2: also translate short day labels + $js .= "radio.labels.days = new Array("; + $short = "radio.labels.sdays = new Array("; + $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + foreach ( $days as $i => $day ) { + $day = radio_station_translate_weekday( $day ); + $short_day = radio_station_translate_weekday( $day, true ); + $day = str_replace( "'", "", $day ); + $short_day = str_replace( "'", "", $short_day ); + $js .= "'" . esc_js( $day ) . "'"; + $short .= "'" . esc_js( $short_day ) . "'"; + if ( $i < ( count( $days ) - 1 ) ) { + $js .= ", "; + $short .= ", "; + } + } + $js .= ");" . PHP_EOL; + $js .= $short . ");" . PHP_EOL; + + // --- translated time unit strings --- + $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; + $js .= "radio.units.pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; "; + $js .= "radio.units.second = '" . esc_js( __( 'Second', 'radio-station' ) ) . "'; "; + $js .= "radio.units.seconds = '" . esc_js( __( 'Seconds', 'radio-station' ) ) . "'; "; + $js .= "radio.units.minute = '" . esc_js( __( 'Minute', 'radio-station' ) ) . "'; "; + $js .= "radio.units.minutes = '" . esc_js( __( 'Minutes', 'radio-station' ) ) . "'; "; + $js .= "radio.units.hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "'; "; + $js .= "radio.units.hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "'; "; + $js .= "radio.units.day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; "; + $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; " . PHP_EOL; + + // --- time key map --- + // 2.3.3.9: added for PHP Date Format to MomentJS conversions + // (object of approximate 'PHP date() key':'moment format() key' conversions) + $js .= "radio.moment_map = {'d':'D', 'j':'D', 'w':'e', 'D':'e', 'l':'e', 'N':'e', 'S':'Do', "; + $js .= "'F':'M', 'm':'M', 'n':'M', 'M':'M', 'Y':'YYYY', 'y':'YY',"; + $js .= "'a':'a', 'A':'a', 'g':'h', 'G':'H', 'g':'h', 'H':'H', 'i':'m', 's':'s'}" . PHP_EOL; + + // --- convert show times --- + // 2.3.3.9: + $usertimes = radio_station_get_setting( 'convert_show_times' ); + if ( 'yes' == $usertimes ) { + $js .= "radio.convert_show_times = true;" . PHP_EOL; + } else { + $js .= "radio.convert_show_times = false;" . PHP_EOL; + } + + // --- add inline script --- + $js = apply_filters( 'radio_station_localization_script', $js ); + return $js; + +} + +// ------------------------- +// Filter for Streaming Data +// ------------------------- +// 2.3.3.7: added streaming data filter for player integration +add_filter( 'radio_station_player_data', 'radio_station_streaming_data' ); +function radio_station_streaming_data( $data, $station = false ) { + $data = array( + 'script' => radio_station_get_setting( 'player_script' ), + 'instance' => 0, + 'url' => radio_station_get_stream_url(), + 'format' => radio_station_get_setting( 'streaming_format' ), + 'fallback' => radio_station_get_fallback_url(), + 'fformat' => radio_station_get_setting( 'fallback_format' ), + ); + if ( RADIO_STATION_DEBUG ) { + echo 'Player Stream Data: ' . print_r( $data, true ) . ''; + } + $data = apply_filters( 'radio_station_streaming_data', $data, $station ); + return $data; +} + +// ----------------------------------------- +// Fix to Redirect Plugin Settings Menu Link +// ----------------------------------------- +// 2.3.2: added settings submenu page redirection fix +add_action( 'init', 'radio_station_settings_page_redirect' ); +function radio_station_settings_page_redirect() { + + // --- bug out if not admin page --- + if ( !is_admin() ) { + return; + } + + // --- but out if not plugin settings page --- + if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != $_REQUEST['page'] ) ) { + return; + } + + // --- check if link is for options-general.php --- + if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { + + // --- redirect to plugin settings page (admin.php) --- + $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); + wp_redirect( $url ); + exit; + } +} + +// ------------------------------------ +// Set Allowed Origins for Radio Player +// ------------------------------------ +// 2.3.3.9: added for embedded radio player control +add_filter( 'allowed_http_origins', 'radio_station_allowed_player_origins' ); +function radio_station_allowed_player_origins( $origins ) { + if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { + return $origins; + } + if ( !isset( $_REQUEST['action'] ) || ( 'radio_player' != $_REQUEST['action'] ) ) { + return $origins; + } + $allowed = array( 'https://netmix.com' ); + $allowed = apply_filters( 'radio_station_player_allowed_origins', $allowed ); + foreach ( $allowed as $allow ) { + $origins[] = $allow; + } + return $origins; +} + + +// ------------------------ +// === Template Filters === +// ------------------------ + +// -------------------- +// Doing Template Check +// -------------------- +// 2.3.3.9: added to help distinguish filter contexts +function radio_station_doing_template() { + global $radio_station_data; + if ( isset( $radio_station_data['doing-template'] ) && $radio_station_data['doing-template'] ) { + return true; + } + return false; +} + +// ------------ +// Get Template +// ------------ +// 2.3.0: added for template file hierarchy +function radio_station_get_template( $type, $template, $paths = false ) { + + global $radio_station_data; + + // --- maybe set default paths --- + if ( !$paths ) { + if ( isset( $radio_station_data['template-dirs'] ) ) { + $dirs = $radio_station_data['template-dirs']; + } + $paths = array( 'templates', '' ); + } elseif ( is_string( $paths ) ) { + if ( 'css' == $paths ) { + if ( isset( $radio_station_data['style-dirs'] ) ) { + $dirs = $radio_station_data['style-dirs']; + } + $paths = array( 'css', 'styles', '' ); + } elseif ( 'js' == $paths ) { + if ( isset( $radio_station_data['script-dirs'] ) ) { + $dirs = $radio_station_data['script-dirs']; + } + $paths = array( 'js', 'scripts', '' ); + } + } + + if ( !isset( $dirs ) ) { + $dirs = array(); + $styledir = get_stylesheet_directory(); + $styledirurl = get_stylesheet_directory_uri(); + $templatedir = get_template_directory(); + $templatedirurl = get_template_directory_uri(); + + // --- maybe generate default hierarchies --- + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => $styledir . '/' . $path, + 'urlpath' => $styledirurl . '/' . $path, + ); + } + if ( $styledir != $templatedir ) { + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => $templatedir . '/' . $path, + 'urlpath' => $templatedirurl . '/' . $path, + ); + } + } + if ( defined( 'RADIO_STATION_PRO_DIR' ) ) { + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => RADIO_STATION_PRO_DIR . '/' . $path, + 'urlpath' => plugins_url( $path, RADIO_STATION_PRO_FILE ), + ); + } + } + foreach ( $paths as $path ) { + $dirs[] = array( + 'path' => RADIO_STATION_DIR . '/' . $path, + 'urlpath' => plugins_url( $path, RADIO_STATION_FILE ), + ); + } + } + $dirs = apply_filters( 'radio_station_template_dir_hierarchy', $dirs, $template, $paths ); + + // --- loop directory hierarchy to find first template --- + foreach ( $dirs as $dir ) { + + // 2.3.4: use trailingslashit to account for empty paths + $template_path = trailingslashit( $dir['path'] ) . $template; + $template_url = trailingslashit( $dir['urlpath'] ) . $template; + + if ( file_exists( $template_path ) ) { + if ( 'file' == (string) $type ) { + return $template_path; + } elseif ( 'url' === (string) $type ) { + return $template_url; + } else { + return array( 'file' => $template_path, 'url' => $template_url ); + } + } + } + + return false; +} + +// ------------------------------------- +// Station Phone Number for Shows Filter +// ------------------------------------- +// 2.3.3.6: added to return station phone for all Shows (if not set for Show) +add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); +function radio_station_phone_number( $phone, $post_id ) { + if ( $phone ) { + return $phone; + } + $shows_phone = radio_station_get_setting( 'shows_phone' ); + if ( 'yes' == $shows_phone ) { + $phone = radio_station_get_setting( 'station_phone' ); + return $phone; + } + return false; +} + +// -------------------------------------- +// Station Email Address for Shows Filter +// -------------------------------------- +// 2.3.3.8: added to return station email for all Shows (if not set for Show) +add_filter( 'radio_station_show_email', 'radio_station_email_address', 10, 2 ); +function radio_station_email_address( $email, $post_id ) { + if ( $email ) { + return $email; + } + $shows_email = radio_station_get_setting( 'shows_email' ); + if ( 'yes' == $shows_email ) { + $email = radio_station_get_setting( 'station_email' ); + return $email; + } + return false; +} + +// ------------------------------ +// Automatic Pages Content Filter +// ------------------------------ +// 2.3.0: standalone filter for automatic page content +// 2.3.1: re-add filter so the_content can be processed multiple times +// 2.3.3.6: set automatic content early and clear existing content +add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); +function radio_station_automatic_pages_content_set( $content ) { + + global $radio_station_data; + + // if ( isset( $radio_station_data['doing_excerpt'] ) && $radio_station_data['doing_excerpt'] ) { + // return $content; + // } + + // --- for automatic output on selected master schedule page --- + $schedule_page = radio_station_get_setting( 'schedule_page' ); + if ( !is_null( $schedule_page ) && !empty( $schedule_page ) ) { + if ( is_page( $schedule_page ) ) { + $automatic = radio_station_get_setting( 'schedule_auto' ); + if ( 'yes' === (string) $automatic ) { + $view = radio_station_get_setting( 'schedule_view' ); + $atts = array( 'view' => $view ); + $atts = apply_filters( 'radio_station_automatic_schedule_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[master-schedule' . $atts_string . ']'; + } + } + } + + // --- show archive page --- + // 2.3.0: added automatic display of show archive page + $show_archive_page = radio_station_get_setting( 'show_archive_page' ); + if ( !is_null( $show_archive_page ) && !empty( $show_archive_page ) ) { + if ( is_page( $show_archive_page ) ) { + $automatic = radio_station_get_setting( 'show_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'show_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_show_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[shows-archive' . $atts_string . ']'; + } + } + } + + // --- override archive page --- + // 2.3.0: added automatic display of override archive page + $override_archive_page = radio_station_get_setting( 'override_archive_page' ); + if ( !is_null( $override_archive_page ) && !empty( $override_archive_page ) ) { + if ( is_page( $override_archive_page ) ) { + $automatic = radio_station_get_setting( 'override_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'override_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_override_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[overrides-archive' . $atts_string . ']'; + } + } + } + + // --- playlist archive page --- + // 2.3.0: added automatic display of playlist archive page + $playlist_archive_page = radio_station_get_setting( 'playlist_archive_page' ); + if ( !is_null( $playlist_archive_page ) && !empty( $playlist_archive_page ) ) { + if ( is_page( $playlist_archive_page ) ) { + $automatic = radio_station_get_setting( 'playlist_archive_auto' ); + if ( 'yes' == $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'playlist_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_playlist_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[playlists-archive' . $atts_string . ']'; + } + } + } + + // --- genre archive page --- + // 2.3.0: added automatic display of genre archive page + $genre_archive_page = radio_station_get_setting( 'genre_archive_page' ); + if ( !is_null( $genre_archive_page ) && !empty( $genre_archive_page ) ) { + if ( is_page( $genre_archive_page ) ) { + $automatic = radio_station_get_setting( 'genre_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'genre_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_genre_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[genres-archive' . $atts_string. ']'; + } + } + } + + // --- languages archive page --- + // 2.3.3.9: added automatic display of language archive page + $language_archive_page = radio_station_get_setting( '' ); + if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { + if ( is_page( $language_archive_page ) ) { + $automatic = radio_station_get_setting( 'language_archive_auto' ); + if ( 'yes' === (string) $automatic ) { + $atts = array(); + // $view = radio_station_get_setting( 'language_archive_view' ); + // if ( $view ) { + // $atts['view'] = $view; + // } + $atts = apply_filters( 'radio_station_automatic_languagee_archive_atts', $atts ); + $atts_string = ''; + if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { + foreach ( $atts as $key => $value ) { + $atts_string = ' ' . $key . '="' . $value . '"'; + } + } + $shortcode = '[languages-archive' . $atts_string. ']'; + } + } + } + + // 2.3.3.6: moved out to reduce repetitive code + if ( isset( $shortcode ) ) { + remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); + // 2.3.1: re-add filter so the_content may be processed multuple times + add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); + add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); + // 2.3.3.6: clear existing content to allow for interim filters + $content = ''; + } + + return $content; +} + +// ---------------------------------- +// Automatic Pages Content Set Filter +// ---------------------------------- +// 2.3.3.6: append existing automatic page content to allow for interim filters +add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); +function radio_station_automatic_pages_content_get( $content ) { + global $radio_station_data; + if ( isset( $radio_station_data['automatic_content'] ) ) { + $content .= $radio_station_data['automatic_content']; + } + return $content; +} + + +// ------------------------------ +// Single Content Template Filter +// ------------------------------ +// 2.3.0: moved here and abstracted from templates/single-show.php +// 2.3.0: standalone filter name to allow for replacement +function radio_station_single_content_template( $content, $post_type ) { + + // --- check if single plugin post type --- + if ( !is_singular( $post_type ) ) { + return $content; + } + + // --- check for user content templates --- + // 2.3.3.9: allow for prefixed and unprefixed post types + $theme_dir = get_stylesheet_directory(); + $templates = array(); + $templates[] = $theme_dir . '/templates/single-' . $post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php'; + $unprefixed_post_type = str_replace( 'rs-', '', $post_type ); + if ( $post_type != $unprefixed_post_type ) { + $templates[] = $theme_dir . '/templates/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = $theme_dir . '/single-' . $unprefixed_post_type . '-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-' . $unprefixed_post_type . '-content.php'; + } + + // 2.3.0: fallback to show content template for overrides + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + // $templates[] = $theme_dir . '/templates/single-rs-show-content.php'; + // $templates[] = $theme_dir . '/single-rs-show-content.php'; + // $templates[] = RADIO_STATION_DIR . '/templates/single-rs-show-content.php'; + $templates[] = $theme_dir . '/templates/single-show-content.php'; + $templates[] = $theme_dir . '/single-show-content.php'; + $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; + } + $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); + foreach ( $templates as $template ) { + if ( file_exists( $template ) ) { + $content_template = $template; + break; + } + } + if ( !isset( $content_template ) ) { + return $content; + } + + // --- enqueue template styles --- + // 2.3.3.9: check post type for page template style enqueue + $page_templates = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); + if ( in_array( $post_type, $page_templates ) ) { + radio_station_enqueue_style( 'templates' ); + } + // 2.3.3.9: fire action for enqueueing other template styles + do_action( 'radio_station_enqueue_template_styles', $post_type ); + + // --- enqueue dashicons for frontend --- + wp_enqueue_style( 'dashicons' ); + + // --- filter post before including template --- + global $post; + $original_post = $post; + $post = apply_filters( 'radio_station_single_template_post_data', $post, $post_type ); + + // --- start buffer and include content template --- + ob_start(); + include $content_template; + $output = ob_get_contents(); + ob_end_clean(); + + // --- restore post global to be safe --- + $post = $original_post; + + // --- filter and return buffered content --- + $output = str_replace( '', $content, $output ); + $post_id = get_the_ID(); + $output = apply_filters( 'radio_station_content_' . $post_type, $output, $post_id ); + + return $output; +} + +// ------------------------------------ +// Filter for Override Show Linked Data +// ------------------------------------ +add_filter( 'radio_station_single_template_post_data', 'radio_station_override_linked_show_data', 10, 2 ); +function radio_station_override_linked_show_data( $post, $post_type ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); + if ( $linked_id ) { + $show_post = get_post( $linked_id ); + if ( $show_post ) { + $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); + if ( $linked_fields ) { + foreach ( $linked_fields as $key => $switch ) { + if ( !$switch ) { + if ( 'show_title' == $key ) { + $post->post_title = $show_post->post_title; + } elseif ( 'show_excerpt' == $key ) { + $post->post_excerpt = $show_post->post_excerpt; + } elseif ( 'show_content' == $key ) { + $post->post_content = $show_post->post_content; + } + } + } + } + } + } + } + return $post; +} + +// ---------------------------- +// Show Content Template Filter +// ---------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_show_content_template', 11 ); +function radio_station_show_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_show_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_SHOW_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multuple times + add_filter( 'the_content', 'radio_station_show_content_template', 11 ); + return $output; +} + +// -------------------------------- +// Playlist Content Template Filter +// -------------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); +function radio_station_playlist_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_PLAYLIST_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multuple times + add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); + return $output; +} + +// -------------------------------- +// Override Content Template Filter +// -------------------------------- +// 2.3.0: standalone filter name to allow for replacement +add_filter( 'the_content', 'radio_station_override_content_template', 11 ); +function radio_station_override_content_template( $content ) { + remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); + $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); + // 2.3.1: re-add filter so the_content can be processed multiple times + add_filter( 'the_content', 'radio_station_override_content_template', 11 ); + return $output; +} + +// ---------------------------------- +// Override Content with Show Content +// ---------------------------------- +// 2.3.3.9: maybe use show content for override content +add_filter( 'the_content', 'radio_station_override_content', 0 ); +function radio_station_override_content( $content ) { + if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { + return $content; + } + remove_filter( 'the_content', 'radio_station_override_content', 0 ); + global $post; + $override = radio_station_get_show_override( $post->ID, 'show_content' ); + if ( false !== $override ) { + $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); + $content = $override->post_content; + } + add_filter( 'the_content', 'radio_station_override_content', 0 ); + return $content; +} + +// --------------------------------- +// DJ / Host / Producer Template Fix +// --------------------------------- +// 2.2.8: temporary fix to not 404 author pages for DJs without blog posts +// Ref: https://wordpress.org/plugins/show-authors-without-posts/ +add_filter( '404_template', 'radio_station_author_host_pages' ); +function radio_station_author_host_pages( $template ) { + + global $wp_query; + if ( !is_author() ) { + + if ( get_query_var( 'host' ) ) { + + // --- get user by ID or name --- + $host = get_query_var( 'host' ); + if ( absint( $host ) > - 1 ) { + $user = get_user_by( 'ID', $host ); + } else { + $user = get_user_by( 'slug', $host ); + } + + // --- check if specified user has DJ/host role --- + if ( $user && in_array( 'dj', $user->roles ) ) { + $host_template = radio_station_get_host_template(); + if ( $host_template ) { + $template = $host_template; + } + } + + } elseif ( get_query_var( 'producer' ) ) { + + // --- get user by ID or name --- + $producer = get_query_var( 'producer' ); + if ( absint( $producer ) > - 1 ) { + $user = get_user_by( 'ID', $producer ); + } else { + $user = get_user_by( 'slug', $producer ); + } + + // --- check if specified user has producer role --- + if ( $user && in_array( 'producer', $user->roles ) ) { + $producer_template = radio_station_get_producer_template(); + if ( $producer_template ) { + $template = $producer_template; + } + } + + } elseif ( get_query_var( 'author' ) && ( 0 == $wp_query->posts->post ) ) { + + // --- get the author user --- + if ( get_query_var( 'author_name' ) ) { + $author = get_user_by( 'slug', get_query_var( 'author_name' ) ); + } else { + $author = get_userdata( get_query_var( 'author' ) ); + } + + if ( $author ) { + + // --- check if author has DJ, producer or administrator role --- + if ( in_array( 'dj', $author->roles ) + || in_array( 'producer', $author->roles ) + || in_array( 'administrator', $author->roles ) ) { + + // TODO: maybe check if user is assigned to any shows ? + $template = get_author_template(); + } + } + + } + + } + + return $template; +} + +// ---------------------- +// Get DJ / Host Template +// ---------------------- +// 2.3.0: added get DJ template function +// (modified template hierarchy from get_page_template) +function radio_station_get_host_template() { + + $templates = array(); + $hostname = get_query_var( 'host' ); + if ( $hostname ) { + $hostname_decoded = urldecode( $hostname ); + if ( $hostname_decoded !== $hostname ) { + $templates[] = 'host-' . $hostname_decoded . '.php'; + } + $templates[] = 'host-' . $hostname . '.php'; + } + $templates[] = 'single-host.php'; + + $templates = apply_filters( 'radio_station_host_templates', $templates ); + + return get_query_template( RADIO_STATION_HOST_SLUG, $templates ); +} + +// --------------------- +// Get Producer Template +// --------------------- +// 2.3.0: added get producer template function +// (modified template hierarchy from get_page_template) +function radio_station_get_producer_template() { + + $templates = array(); + $producername = get_query_var( 'producer' ); + if ( $producername ) { + $producername_decoded = urldecode( $producername ); + if ( $producername_decoded !== $producername ) { + $templates[] = 'producer-' . $producername_decoded . '.php'; + } + $templates[] = 'producer-' . $producername . '.php'; + } + $templates[] = 'single-producer.php'; + + $templates = apply_filters( 'radio_station_producer_templates', $templates ); + + return get_query_template( RADIO_STATION_PRODUCER_SLUG, $templates ); +} + +// ------------------------- +// Single Template Hierarchy +// ------------------------- +function radio_station_single_template_hierarchy( $templates ) { + + global $post; + + // --- remove single.php as the show / playlist fallback --- + // (allows for user selection of page.php or single.php later) + if ( ( RADIO_STATION_SHOW_SLUG === (string) $post->post_type ) + || ( RADIO_STATION_OVERRIDE_SLUG === (string) $post->post_type ) + || ( RADIO_STATION_PLAYLIST_SLUG === (string) $post->post_type ) ) { + $i = array_search( 'single.php', $templates ); + if ( false !== $i ) { + unset( $templates[$i] ); + } + } + + return $templates; +} + +// ----------------------- +// Single Templates Loader +// ----------------------- +add_filter( 'single_template', 'radio_station_load_template', 10, 3 ); +function radio_station_load_template( $single_template, $type, $templates ) { + + global $post; + + // --- handle single templates --- + $post_type = $post->post_type; + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); + // TODO: RADIO_STATION_EPISODE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG + if ( in_array( $post_type, $post_types ) ) { + + // --- check for existing template override --- + // note: single.php is removed from template hierarchy via filter + remove_filter( 'single_template', 'radio_station_load_template' ); + add_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); + $template = get_single_template(); + remove_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); + + // --- use legacy template --- + if ( $template ) { + + // --- use the found user template --- + $single_template = $template; + + // --- check for combined template and content filter --- + $combined = radio_station_get_setting( $post_type . '_template_combined' ); + if ( 'yes' != $combined ) { + remove_filter( 'the_content', 'radio_station_' . $post_type . '_content_template', 11 ); + } + + } else { + + // --- get template selection --- + // 2.3.0: removed default usage of single show/playlist templates (not theme agnostic) + // 2.3.0: added option for use of template hierarchy + $show_template = radio_station_get_setting( $post_type . '_template' ); + + // --- maybe use legacy template --- + if ( 'legacy' === (string) $show_template ) { + return RADIO_STATION_DIR . '/templates/legacy/single-' . $post_type . '.php'; + } + + // --- use post or page template --- + // 2.3.3.8: added missing singular.php template setting + if ( 'post' == $show_template ) { + $templates = array( 'single.php' ); + } elseif ( 'page' == $show_template ) { + $templates = array( 'page.php' ); + } elseif ( 'singular' == $show_template ) { + $template = array( 'singular.php' ); + } + + // --- add standard fallbacks to index --- + // 2.3.3.8: remove singular fallback as it is explicitly chosen + $templates[] = 'index.php'; + $single_template = get_query_template( $post_type, $templates ); + } + } + + return $single_template; +} + +// -------------------------- +// Archive Template Hierarchy +// -------------------------- +add_filter( 'archive_template_hierarchy', 'radio_station_archive_template_hierarchy' ); +function radio_station_archive_template_hierarchy( $templates ) { + + // --- add extra template search path of /templates/ --- + $post_types = array_filter( (array) get_query_var( 'post_type' ) ); + if ( count( $post_types ) == 1 ) { + $post_type = reset( $post_types ); + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); + if ( in_array( $post_type, $post_types ) ) { + $template = array( 'templates/archive-' . $post_type . '.php' ); + // 2.3.0: add fallback to show archive template for overrides + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { + $template[] = 'templates/archive-' . RADIO_STATION_SHOW_SLUG . '.php'; + } + $templates = array_merge( $template, $templates ); + } + } + + return $templates; +} + +// ------------------------ +// Archive Templates Loader +// ------------------------ +// TODO: implement standard archive page overrides via plugin settings +// add_filter( 'archive_template', 'radio_station_post_type_archive_template', 10, 3 ); +function radio_station_post_type_archive_template( $archive_template, $type, $templates ) { + global $post; + + // --- check for archive template override --- + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); + foreach ( $post_types as $post_type ) { + if ( is_post_type_archive( $post_type ) ) { + $override = radio_station_get_setting( $post_type . '_archive_override' ); + if ( 'yes' !== (string) $override ) { + $archive_template = get_page_template(); + add_filter( 'the_content', 'radio_station_' . $post_type . '_archive', 11 ); + } + } + } + + return $archive_template; +} + +// ------------------------- +// Add Links to Back to Show +// ------------------------- +// 2.3.0: add links to show from show posts and playlists +// 2.3.3.6: allow for multiple related show post assignments +add_filter( 'the_content', 'radio_station_add_show_links', 20 ); +function radio_station_add_show_links( $content ) { + + global $post; + + // note: playlists are linked via single-playlist-content.php template + + // --- filter to allow related post types --- + $post_type = $post->post_type; + $post_types = array( 'post' ); + $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); + + if ( in_array( $post_type, $post_types ) ) { + + // --- link show posts --- + $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); + // 2.3.3.6: convert string value if not multiple + if ( $related_shows && !is_array( $related_shows ) ) { + $related_shows = array( $related_shows ); + } + // 2.3.3.6: remove possible zero values + // 2.3.3.7: added count check for before looping + if ( $related_shows && ( count( $related_shows ) > 0 ) ) { + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } + } + } + if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { + + $positions = array( 'after' ); + $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post_type, $post ); + if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { + if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { + + // --- set related shows link(s) --- + // 2.3.3.6: get all related show links + $show_links = ''; + $hash_ref = '#show-' . str_replace( 'rs-', '', $post_type ) . 's'; + foreach ( $related_shows as $related_show ) { + $show = get_post( $related_show ); + $title = $show->post_title; + $permalink = get_permalink( $show->ID ) . $hash_ref; + if ( '' != $show_links ) { + $show_links .= ', '; + } + $show_links .= '' . esc_html( $title ) . ''; + } + + // --- set post type labels --- + $before = $after = ''; + $post_type_object = get_post_type_object( $post_type ); + $singular = $post_type_object->labels->singular_name; + $plural = $post_type_object->labels->name; + + // --- before content links --- + if ( in_array( 'before', $positions ) ) { + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( '%s for Shows', 'radio-station' ), $singular ); + } else { + $label = sprintf( __( '%s for Show', 'radio-station' ), $singular ); + } + $before = $label . ': ' . $show_links . '

    '; + $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $related_shows ); + } + + // --- after content links --- + if ( in_array( 'after', $positions ) ) { + if ( count( $related_shows ) > 1 ) { + $label = sprintf( __( 'More %s for Shows', 'radio-station' ), $plural ); + } else { + $label = sprintf( __( 'More %s for Show', 'radio-station' ), $plural ); + } + $after = '
    ' . $label . ': ' . $show_links; + $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $related_shows ); + } + $content = $before . $content . $after; + } + } + } + + } + + // --- adjacent post links debug output --- + if ( RADIO_STATION_DEBUG ) { + $content .= 'Previous Post Link: ' . get_previous_post_link() . '' . PHP_EOL; + $content .= 'Next Post Link: ' . get_next_post_link() . '' . PHP_EOL; + } + + return $content; +} + +// ------------------------- +// Show Posts Adjacent Links +// ------------------------- +// 2.3.0: added show post adjacent links filter +add_filter( 'next_post_link', 'radio_station_get_show_post_link', 11, 5 ); +add_filter( 'previous_post_link', 'radio_station_get_show_post_link', 11, 5 ); +function radio_station_get_show_post_link( $output, $format, $link, $adjacent_post, $adjacent ) { + + global $radio_station_data, $post; + + // --- filter next and previous Show links --- + // 2.3.4: add filtering for adjacent show links + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( in_array( $post->post_type, $post_types ) ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + // 2.3.3.6: get next/previous Show for override date/time + // 2.3.3.9: modified to handle multiple override times + // 2.3.3.9: added check that schedule key is set + $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); + if ( $scheds && is_array( $scheds ) ) { + if ( array_key_exists( 'date', $scheds ) ) { + $sched = array( $scheds ); + } + $now = time(); + foreach ( $scheds as $sched ) { + $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; + $override_time = radio_station_get_time( ( $override_start + 1 ) ); + if ( !isset( $time ) ) { + $time = $override_time; + } elseif ( ( $time < $now ) && ( $override_time > $now ) ) { + $time = $override_time; + } + } + if ( 'next' == $adjacent ) { + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $show = radio_station_get_previous_show( $time ); + } + } + } else { + $shifts = get_post_meta( $post->ID, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) ) { + if ( count( $shifts ) < 1 ) { + // 2.3.3.6: default to standard adjacent post link + return $output; + } + if ( 1 == count( $shifts ) ) { + $shift = $shifts[0]; + $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + // 2.3.3.9: fix to put addition outside bracket + $time = radio_station_get_time( $shift_start ) + 1; + if ( 'next' == $adjacent ) { + $show = radio_station_get_next_show( $time ); + } elseif ( 'previous' == $adjacent ) { + $show = radio_station_get_previous_show( $time ); + } + } else { + // 2.3.3.6: added method for Show with multiple shifts + $now = radio_station_get_now(); + $show_shifts = radio_station_get_current_schedule(); + if ( !$show_shifts ) { + return $output; + } + + // --- get upcoming shift for Show --- + $next_shift = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !$next_shift && ( $day_shift['show']['id'] == $post->ID ) ) { + if ( !isset( $last_shift ) ) { + $last_shift = $day_shift; + } + $start = $day_shift['date'] . ' ' . $day_shift['start']; + $start_time = radio_station_to_time( $start ); + $end = $day_shift['date'] . ' ' . $day_shift['end']; + $end_time = radio_station_to_time( $end ); + if ( ( $start_time > $now ) || ( $now < $end_time ) ) { + $next_shift = $day_shift; + } + } + } + } + if ( !$next_shift ) { + $next_shift = $last_shift; + } + // echo "Next Show Shift: " . print_r( $next_shift, true ); + + // --- reverse order for finding previous show shift --- + if ( 'previous' == $adjacent ) { + foreach ( $show_shifts as $day => $day_shifts ) { + $show_shifts[$day] = array_reverse( $day_shifts, true ); + } + $show_shifts = array_reverse( $show_shifts, true ); + } + + // --- loop shifts to find adjacent shift's Show --- + $found = false; + foreach ( $show_shifts as $day => $day_shifts ) { + foreach ( $day_shifts as $day_shift ) { + if ( !isset( $first_shift ) && ( $day_shift['show']['id'] != $post->ID ) ) { + $first_shift = $day_shift; + } + // echo "Shift: " . print_r( $day_shift, true ) . PHP_EOL; + if ( !isset( $show ) ) { + if ( $found && ( $day_shift['show']['id'] != $post->ID ) ) { + $show = $day_shift['show']; + } elseif ( !$found ) { + if ( $next_shift == $day_shift ) { + $found = true; + } + } + } + } + } + if ( !isset( $show ) && isset( $first_shift ) ) { + $show = $first_shift['show']; + } + } + } + } + + // --- generate adjacent Show link --- + if ( isset( $show ) ) { + if ( 'next' == $adjacent ) { + $rel = 'next'; + } elseif ( 'previous' == $adjacent ) { + $rel = 'prev'; + } + $adjacent_post = get_post( $show['id'] ); + + // --- adjacent post title --- + // 2.4.0.3: added fix for missing post title + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + } + + return $output; + } + + // --- filter to allow related post types --- + $related_post_types = array( 'post' ); + $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); + if ( in_array( $post->post_type, $related_post_types ) ) { + + // --- filter to allow disabling --- + $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); + if ( !$link_show_posts ) { + return $output; + } + + // --- get related show --- + $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); + if ( !$related_show ) { + return $output; + } + if ( is_array( $related_show ) ) { + $related_shows = $related_show; + } else { + $related_shows = array( $related_show ); + } + // 2.3.3.6: remove possible saved zero value + foreach ( $related_shows as $i => $related_show ) { + if ( 0 == $related_show ) { + unset( $related_shows[$i] ); + } + } + if ( 0 == count( $related_shows ) ) { + return $output; + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows A: ' . print_r( $related_shows, true ) . ''; + } + + // --- get more Shows related to this related Post --- + // 2.3.3.6: allow for multiple related posts + // 2.3.3.9: added 'i:' prefix to LIKE vlaue matches + global $wpdb; + $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" + . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; + if ( count( $related_shows ) > 1 ) { + foreach ( $related_show as $i => $show_id ) { + if ( $i > 0 ) { + $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $show_id . "%'"; + } + } + } + $results = $wpdb->get_results( $query, ARRAY_A ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Shows B: ' . print_r( $results, true ) . ''; + } + if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { + return $output; + } + $related_posts = array(); + foreach ( $results as $result ) { + $values = maybe_unserialize( $result['meta_value'] ); + if ( RADIO_STATION_DEBUG ) { + echo 'Post ' . $result['post_id'] . ' Related Show Values : ' . print_r( $values, true ) . ''; + } + // --- double check Show ID is actually a match --- + if ( ( $result['meta_value'] == $related_show ) || ( is_array( $values ) && array_intersect( $related_shows, $values ) ) ) { + // --- recheck post is of the same post type --- + $query = "SELECT post_type FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $result['post_id'] ); + $related_post_type = $wpdb->get_var( $query ); + if ( $related_post_type == $post->post_type ) { + $related_posts[] = $result['post_id']; + } + } + } + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts B: ' . print_r( $related_posts, true ) . ''; + } + if ( 0 == count( $related_posts ) ) { + return $output; + } + + // --- get adjacent post query --- + // 2.3.3.6: use post__in related post array instead of meta_query + $args = array( + 'post_type' => $post->post_type, + 'posts_per_page' => 1, + 'orderby' => 'post_modified', + 'post__in' => $related_posts, + 'ignore_sticky_posts' => true, + ); + + // --- setup for previous or next post --- + // 2.3.3.6: set date_query instead of meta_query + $post_type_object = get_post_type_object( $post->post_type ); + if ( 'previous' == $adjacent ) { + $args['order'] = 'DESC'; + $args['date_query'] = array( array( 'before' => $post->post_date ) ); + $rel = 'prev'; + $title = __( 'Previous Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + } elseif ( 'next' == $adjacent ) { + $args['order'] = 'ASC'; + $args['date_query'] = array( array( 'after' => $post->post_date ) ); + $rel = 'next'; + $title = __( 'Next Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; + } + + // --- get the adjacent post --- + // 2.3.3.6: use date_query instead of looping posts + $show_posts = get_posts( $args ); + if ( RADIO_STATION_DEBUG ) { + echo 'Related Posts Args: ' . print_r( $args, true ) . ''; + } + if ( 0 == count( $show_posts ) ) { + return $output; + } + $adjacent_post = $show_posts[0]; + if ( RADIO_STATION_DEBUG ) { + echo 'Related Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; + } + + // --- adjacent post title --- + $post_title = $adjacent_post->post_title; + if ( empty( $adjacent_post->post_title ) ) { + $post_title = $title; + } + $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); + + // --- adjacent post link --- + // (from function get_adjacent_post_link) + $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); + $string = ''; + $inlink = str_replace( '%title', $post_title, $link ); + $inlink = str_replace( '%date', $date, $inlink ); + $inlink = $string . $inlink . ''; + $output = str_replace( '%link', $inlink, $format ); + + } + + return $output; +} + + +// ============= +// Query Filters +// ============= + +// ----------------------------- +// Playlist Archive Query Filter +// ----------------------------- +// 2.3.0: added to replace old archive template meta query +add_filter( 'pre_get_posts', 'radio_station_show_playlist_query' ); +function radio_station_show_playlist_query( $query ) { + + if ( RADIO_STATION_PLAYLIST_SLUG == $query->get( 'post_type' ) ) { + + // --- not needed if using legacy template --- + $styledir = get_stylesheet_directory(); + if ( file_exists( $styledir . '/archive-playlist.php' ) + || file_exists( $styledir . '/templates/archive-playlist.php' ) ) { + return; + } + // 2.3.0: also check in parent theme directory + $templatedir = get_template_directory(); + if ( $templatedir != $styledir ) { + if ( file_exists( $templatedir . '/archive-playlist.php' ) + || file_exists( $templatedir . '/templates/archive-playlist.php' ) ) { + return; + } + } + + // --- check if show ID or slug is set -- + // TODO: maybe use get_query_var here ? + if ( isset( $_GET['show_id'] ) ) { + $show_id = absint( $_GET['show_id'] ); + if ( $show_id < 0 ) { + unset( $show_id ); + } + } elseif ( isset( $_GET['show'] ) ) { + $show = sanitize_title( $_GET['show'] ); + global $wpdb; + $show_query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; + $show_query = $wpdb->prepare( $show_query, $show ); + $show_id = $wpdb->get_var( $show_query ); + if ( !$show_id ) { + unset( $show_id ); + } + } + + // --- maybe add the playlist meta query --- + if ( isset( $show_id ) ) { + $meta_query = array( + 'key' => 'playlist_show_id', + 'value' => $show_id, + ); + $query->set( $meta_query ); + } + } +} + + +// ------------------ +// === User Roles === +// ------------------ + +// -------------------------- +// Set Roles and Capabilities +// -------------------------- +if ( is_multisite() ) { + add_action( 'init', 'radio_station_set_roles', 10, 0 ); + // 2.3.1: added possible fix for roles not being set on multisite + add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); +} else { + add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); +} +function radio_station_set_roles() { + + global $wp_roles; + + // --- set only necessary capabilities for DJs --- + $caps = array( + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'read_shows' => true, + 'edit_playlists' => true, + 'edit_published_playlists' => true, + // by default DJs cannot edit others playlists + // 'edit_others_playlists' => false, + 'read_playlists' => true, + 'publish_playlists' => true, + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, + ); + + // --- add the DJ role --- + // 2.3.0: translate DJ role name + // 2.3.0: change label from 'DJ' to 'DJ / Host' + // 2.3.0: check/add profile capabilities to hosts + $wp_roles->add_role( 'dj', __( 'DJ / Host', 'radio-station' ), $caps ); + $role_caps = $wp_roles->roles['dj']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } + $host_caps = array( + 'edit_hosts', + 'edit_published_hosts', + 'delete_hosts', + 'read_hosts', + 'publish_hosts' + ); + foreach ( $host_caps as $cap ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { + $wp_roles->add_cap( 'dj', $cap, true ); + } + } + // 2.3.3.9: fix for existing DJ role old name + $wp_roles->roles['dj']['name'] = __( 'DJ / Host', 'radio_station' ); + $wp_roles->role_names['dj'] = __( 'DJ / Host', 'radio_station' ); + + // --- add Show Producer role --- + // 2.3.0: add equivalent capability role for Show Producer + $wp_roles->add_role( 'producer', __( 'Show Producer', 'radio-station' ), $caps ); + $role_caps = $wp_roles->roles['producer']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $role_caps ) ) { + $role_caps = array(); + } + $producer_caps = array( + 'edit_producers', + 'edit_published_producers', + 'delete_producers', + 'read_producers', + 'publish_producers', + ); + foreach ( $producer_caps as $cap ) { + if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { + $wp_roles->add_cap( 'producer', $cap, true ); + } + } + + // --- grant all capabilities to Show Editors --- + // 2.3.0: set Show Editor role capabilities + $caps = array( + 'edit_shows' => true, + 'edit_published_shows' => true, + 'edit_others_shows' => true, + 'edit_private_shows' => true, + 'delete_shows' => true, + 'delete_published_shows' => true, + 'delete_others_shows' => true, + 'delete_private_shows' => true, + 'read_shows' => true, + 'publish_shows' => true, + + 'edit_playlists' => true, + 'edit_published_playlists' => true, + 'edit_others_playlists' => true, + 'edit_private_playlists' => true, + 'delete_playlists' => true, + 'delete_published_playlists' => true, + 'delete_others_playlists' => true, + 'delete_private_playlists' => true, + 'read_playlists' => true, + 'publish_playlists' => true, + + 'edit_overrides' => true, + 'edit_overrides_playlists' => true, + 'edit_others_overrides' => true, + 'edit_private_overrides' => true, + 'delete_overrides' => true, + 'delete_published_overrides' => true, + 'delete_others_overrides' => true, + 'delete_private_overrides' => true, + 'read_overrides' => true, + 'publish_overrides' => true, + + 'edit_hosts' => true, + 'edit_published_hosts' => true, + 'edit_others_hosts' => true, + 'delete_hosts' => true, + 'read_hosts' => true, + 'publish_hosts' => true, + + 'edit_producers' => true, + 'edit_published_producers' => true, + 'edit_others_producers' => true, + 'delete_producers' => true, + 'read_producers' => true, + 'publish_producers' => true, + + 'read' => true, + 'upload_files' => true, + 'edit_posts' => true, + 'edit_others_posts' => true, + 'edit_published_posts' => true, + 'publish_posts' => true, + 'delete_posts' => true, + ); + + // --- add the Show Editor role --- + // 2.3.0: added Show Editor role + $wp_roles->add_role( 'show-editor', __( 'Show Editor', 'radio-station' ), $caps ); + + // --- check plugin setting for authors --- + if ( radio_station_get_setting( 'add_author_capabilities' ) == 'yes' ) { + + // --- grant show edit capabilities to author users --- + $author_caps = $wp_roles->roles['author']['capabilities']; + // 2.3.1.1: added check if role caps is an array + if ( !is_array( $author_caps ) ) { + $author_caps = array(); + } + $extra_caps = array( + 'edit_shows', + 'edit_published_shows', + 'read_shows', + 'publish_shows', + + 'edit_playlists', + 'edit_published_playlists', + 'read_playlists', + 'publish_playlists', + + 'edit_overrides', + 'edit_published_overrides', + 'read_overrides', + 'publish_overrides', + ); + foreach ( $extra_caps as $cap ) { + if ( !array_key_exists( $cap, $author_caps ) || ( !$author_caps[$cap] ) ) { + $wp_roles->add_cap( 'author', $cap, true ); + } + } + } + + // --- specify edit caps (for editors and admins) --- + // 2.3.0: added show override, host and producer capabilities + $edit_caps = array( + 'edit_shows', + 'edit_published_shows', + 'edit_others_shows', + 'edit_private_shows', + 'delete_shows', + 'delete_published_shows', + 'delete_others_shows', + 'delete_private_shows', + 'read_shows', + 'publish_shows', + + 'edit_playlists', + 'edit_published_playlists', + 'edit_others_playlists', + 'edit_private_playlists', + 'delete_playlists', + 'delete_published_playlists', + 'delete_others_playlists', + 'delete_private_playlists', + 'read_playlists', + 'publish_playlists', + + 'edit_overrides', + 'edit_published_overrides', + 'edit_others_overrides', + 'edit_private_overrides', + 'delete_overrides', + 'delete_published_overrides', + 'delete_others_overrides', + 'delete_private_overrides', + 'read_overrides', + 'publish_overrides', + + 'edit_hosts', + 'edit_published_hosts', + 'edit_others_hosts', + 'delete_hosts', + 'delete_others_hosts', + 'read_hosts', + 'publish_hosts', + + 'edit_producers', + 'edit_published_producers', + 'edit_others_producers', + 'delete_producers', + 'delete_others_producers', + 'read_producers', + 'publish_producers', + ); + + // --- check plugin setting for editors --- + if ( radio_station_get_setting( 'add_editor_capabilities' ) == 'yes' ) { + + // --- grant show edit capabilities to editor users --- + $editor_caps = $wp_roles->roles['editor']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $editor_caps ) ) { + $editor_caps = array(); + } + foreach ( $edit_caps as $cap ) { + if ( !array_key_exists( $cap, $editor_caps ) || ( !$editor_caps[$cap] ) ) { + $wp_roles->add_cap( 'editor', $cap, true ); + } + } + } + + // --- grant all plugin capabilities to admin users --- + $admin_caps = $wp_roles->roles['administrator']['capabilities']; + // 2.3.1.1: added check if capabilities is an array + if ( !is_array( $admin_caps ) ) { + $admin_caps = array(); + } + foreach ( $edit_caps as $cap ) { + if ( !array_key_exists( $cap, $admin_caps ) || ( !$admin_caps[$cap] ) ) { + $wp_roles->add_cap( 'administrator', $cap, true ); + } + } + +} + +// ---------------------------------- +// Admin Fix for DJ / Host Role Label +// ---------------------------------- +// 2.3.3.9: added for user edit screen crackliness +add_filter( 'editable_roles', 'radio_station_role_check_test', 9 ); +function radio_station_role_check_test( $roles ) { + if ( RADIO_STATION_DEBUG && is_admin() ) { + echo "DJ Role: " . print_r( $roles['dj'], true ); + } + $roles['dj']['name'] = __( 'DJ / Host', 'radio-station' ); + return $roles; +} + +// --------------------------------- +// maybe Revoke Edit Show Capability +// --------------------------------- +// (revoke ability to edit show if user is not assigned to it) +add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 4 ); +function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { + + global $post, $wp_roles; + + // --- get the current user --- + // 2.3.3.6: get user object from fourth argument instead + // $user = wp_get_current_user(); + + // --- check if super admin --- + // ? fix to not revoke edit caps from super admin ? + // (not implemented, as causing a connection reset error) + // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { + // return $allcaps; + // } + + // --- debug passed capability arguments --- + // TODO: get post object from args instead of global ? + if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { + echo 'Cap Args: ' . print_r( $args, true ) . ''; + } + + // --- check for editor + // 2.3.3.6: check editor roles first separately + $editor_roles = array( 'administrator', 'editor', 'show-editor' ); + foreach ( $editor_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + return $allcaps; + } + } + + // --- get roles with edit shows capability --- + $edit_show_roles = $edit_others_shows_roles = array(); + if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { + foreach ( $wp_roles->roles as $name => $role ) { + // 2.3.0: fix to skip roles with no capabilities assigned + if ( isset( $role['capabilities'] ) ) { + foreach ( $role['capabilities'] as $capname => $capstatus ) { + // 2.3.0: change publish_shows cap check to edit_shows + if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_show_roles ) ) { + $edit_show_roles[] = $name; + } + } + // 2.3.3.6: add check for edit-others_shows capability + if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { + if ( !in_array( $name, $edit_others_shows_roles ) ) { + $edit_others_shows_roles[] = $name; + } + } + } + } + } + } + + // 2.3.3.6: preserve if user has edit_others_shows capability + foreach ( $edit_others_shows_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + return $allcaps; + } + } + + // 2.2.8: remove strict in_array checking + $found = false; + foreach ( $edit_show_roles as $role ) { + if ( in_array( $role, $user->roles ) ) { + $found = true; + } + } + + // --- maybe revoke edit show capability for post --- + // 2.3.3.6: fix to incorrect logic for removing edit show capability + if ( $found ) { + + // --- limit this to published shows --- + // 2.3.0: added object and property_exists check to be safe + if ( is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + + // 2.3.0: removed is_admin check (so works with frontend edit show link) + // 2.3.0: moved check if show is published inside + if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { + + // --- get show hosts and producers --- + $hosts = get_post_meta( $post->ID, 'show_user_list', true ); + $producers = get_post_meta( $post->ID, 'show_producer_list', true ); + + if ( !$hosts || empty( $hosts ) ) { + $hosts = array(); + } + if ( !$producers || empty( $producers ) ) { + $producers = array(); + } + + // ---- revoke editing capability if not assigned to this show --- + // 2.2.8: remove strict in_array checking + // 2.3.0: also check new Producer role + if ( !in_array( $user->ID, $hosts ) && !in_array( $user->ID, $producers ) ) { + + // --- remove the edit_shows capability --- + $allcaps['edit_shows'] = false; + + // 2.3.0: move check if show is published inside + if ( 'publish' == $post->post_status ) { + $allcaps['edit_published_shows'] = false; + } + } + } + } + } + + return $allcaps; +} + + +// ================= +// --- Debugging --- +// ================= + +// ----------------------- +// Set Debug Mode Constant +// ----------------------- +// 2.3.0: added debug mode constant +// 2.3.2: added saving debug mode constant +if ( !defined( 'RADIO_STATION_DEBUG' ) ) { + $rs_debug = false; + if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { + $rs_debug = true; + } + define( 'RADIO_STATION_DEBUG', $rs_debug ); +} +if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { + $rs_save_debug = false; + if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { + $rs_save_debug = true; + } + define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); +} + +// -------------------------- +// maybe Clear Transient Data +// -------------------------- +// 2.3.0: clear show transients if debugging +// 2.3.1: added action to init hook +// 2.3.1: check clear show transients option +add_action( 'init', 'radio_station_clear_transients' ); +function radio_station_clear_transients() { + $clear_transients = radio_station_get_setting( 'clear_transients' ); + if ( RADIO_STATION_DEBUG || ( 'yes' == $clear_transients ) ) { + // 2.3.2: do not clear on AJAX calls + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return; + } + // 2.3.3.9: just use clear cached data function + radio_station_clear_cached_data( false ); + } +} + +// ------------------------ +// Debug Output and Logging +// ------------------------ +// 2.3.0: added debugging function +function radio_station_debug( $data, $echo = true, $file = false ) { + + // --- maybe output debug info --- + if ( $echo ) { + // 2.3.0: added span wrap for hidden display + // 2.3.1.1: added class for page source searches + echo '' . PHP_EOL; + } + + // --- check for logging constant --- + if ( defined( 'RADIO_STATION_DEBUG_LOG' ) ) { + if ( !$file && RADIO_STATION_DEBUG_LOG ) { + $file = 'radio-station.log'; + } elseif ( false === RADIO_STATION_DEBUG_LOG ) { + $file = false; + } + } + + // --- write to debug file --- + if ( $file ) { + if ( !is_dir( RADIO_STATION_DIR . '/debug' ) ) { + wp_mkdir_p( RADIO_STATION_DIR . '/debug' ); + } + $file = RADIO_STATION_DIR . '/debug/' . $file; + error_log( $data, 3, $file ); + } +} diff --git a/readme.md b/readme.md index 7b95811..93b62f9 100644 --- a/readme.md +++ b/readme.md @@ -31,9 +31,9 @@ A schedule of all Shows can be generated and added to a page with a shortcode (o The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. -As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://netmix.com/radio-station/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostationdemo.com). +As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://radiostation.pro/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostation.pro). -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. +We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also continuing to develop more exciting features and functionality for [Radio Station Pro](https://radiostation.pro/). ### Updating from Prior to 2.3.0 @@ -59,25 +59,25 @@ If you are a WordPress developer wanting to contribute to Radio Station, please ### Quickstart Guide -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. +Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://radiostation.pro/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://netmix.com/radio-station/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://netmix.com/radio-station/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. +Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://radiostation.pro/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://radiostation.pro/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://netmix.com/radio-station/docs/Display.md#page-templates) used. You can also assign different [Images](https://netmix.com/radio-station/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://netmix.com/radio-station/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. +Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/Display.md#page-templates) used. You can also assign different [Images](https://radiostation.pro/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://radiostation.pro/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. -Next you may want to give some users on your site some plugin [Roles](https://netmix.com/radio-station/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. +Next you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. -There are a few [Widgets](https://netmix.com/radio-station/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. +There are a few [Widgets](https://radiostation.pro/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. -Then there are also a number of other [Shortcodes](https://netmix.com/radio-station/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. +Then there are also a number of other [Shortcodes](https://radiostation.pro/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. -Radio Station has several in-built [Data](https://netmix.com/radio-station/docs/Data.md) types. These include [Custom Post Types](https://netmix.com/radio-station/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://netmix.com/radio-station/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://netmix.com/radio-station/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. +Radio Station has several in-built [Data](https://radiostation.pro/docs/Data.md) types. These include [Custom Post Types](https://radiostation.pro/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://radiostation.pro/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://netmix.com/radio-station/docs/Roadmap.md) if you are interested in seeing what is coming up next! +This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://radiostation.pro/docs/Roadmap.md) if you are interested in seeing what is coming up next! ### Upgrading to Radio Station Pro -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://radiostation.pro/). ## Installation @@ -94,9 +94,17 @@ Love Radio Station and ready for more? As the free version develops, we have als ## Frequently Asked Questions +#### How do I get started with Radio Station? = + +Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. + #### Where can I find the full plugin documentation? -The latest documentation can be found online at [NetMix.com](https://netmix.com/radio-station/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation can be found online at [RadioStation.Pro](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. + +#### Is there a demo site I can view to see how Radio Station works? = + +Yes, visit the [Radio Station Demo Site](https://demo.radiostation.pro/) to view the Free and PRO features of Radio Station. (Note you will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users.) #### How do I schedule a Show? diff --git a/readme.txt b/readme.txt index 33a4dd6..3b1ba6d 100644 --- a/readme.txt +++ b/readme.txt @@ -20,9 +20,9 @@ A schedule of all Shows can be generated and added to a page with a shortcode (o The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. -As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://netmix.com/radio-station/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostationdemo.com). +As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://radiostation.pro/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://demo.radiostation.pro). -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. Enter your [email address on the Radio Station Pro website] (https://radiostation.pro) to add yourself to the cue to be able to download the be notified when PRO is released. +We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also continuing to develop more exciting features and functionality for [Radio Station Pro](https://radiostation.pro/). = Updating from Prior to 2.3.0 = @@ -48,25 +48,25 @@ If you are a WordPress developer wanting to contribute to Radio Station, please = Quickstart Guide = -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. Or, view the combined {FREE and PRO demo site](https://demo.radiostation.pro). +Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://radiostation.pro/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://netmix.com/radio-station/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://netmix.com/radio-station/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. +Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://radiostation.pro/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://radiostation.pro/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://netmix.com/radio-station/docs/Display.md#page-templates) used. You can also assign different [Images](https://netmix.com/radio-station/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://netmix.com/radio-station/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. +Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://radiostation.pro/docs/Display.md#page-templates) used. You can also assign different [Images](https://radiostation.pro/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://radiostation.pro/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. -Next you may want to give some users on your site some plugin [Roles](https://netmix.com/radio-station/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. +Next you may want to give some users on your site some plugin [Roles](https://radiostation.pro/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. -There are a few [Widgets](https://netmix.com/radio-station/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. +There are a few [Widgets](https://radiostation.pro/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. -Then there are also a number of other [Shortcodes](https://netmix.com/radio-station/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. +Then there are also a number of other [Shortcodes](https://radiostation.pro/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. -Radio Station has several in-built [Data](https://netmix.com/radio-station/docs/Data.md) types. These include [Custom Post Types](https://netmix.com/radio-station/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://netmix.com/radio-station/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://netmix.com/radio-station/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. +Radio Station has several in-built [Data](https://radiostation.pro/docs/Data.md) types. These include [Custom Post Types](https://radiostation.pro/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://radiostation.pro/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://radiostation.pro/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://netmix.com/radio-station/docs/Roadmap.md) if you are interested in seeing what is coming up next! +This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://radiostation.pro/docs/Roadmap.md) if you are interested in seeing what is coming up next! = Upgrading to Radio Station Pro = -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://radiostation.pro/). == Installation == @@ -88,11 +88,11 @@ Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for = Where can I find the full plugin documentation? = -The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. +The latest documentation can be found online at [RadioStation.Pro](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. -= Is there a demo site I can view to see how Radio Statin works? = += Is there a demo site I can view to see how Radio Station works? = -Yes, visite [demo.radiostation.pro](https://demo.radiostation.pro/) to view the FREE and PRO features of Radio Station. You will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users. +Yes, visit the [Radio Station Demo Site](https://demo.radiostation.pro/) to view the Free and PRO features of Radio Station. (Note you will not be able to test the Visual Schedule Editor in PRO as it's only available for logged in users.) = How do I schedule a Show? = @@ -218,6 +218,9 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.4.0.4 = +* Fixed: Fallback scripts and fallback stream URLs + = 2.4.0.3 = * Update: Plugin Panel (1.2.1) with zero value save and tab fixes * Added: option to disable player audio fallback scripts diff --git a/readme.txt.bak b/readme.txt.bak new file mode 100644 index 0000000..4f02d92 --- /dev/null +++ b/readme.txt.bak @@ -0,0 +1,802 @@ +=== Radio Station === +Contributors: tonyzeoli, majick +Donate link: https://netmix.co/donate +Tags: dj, music, playlist, radio, shows, scheduling, broadcasting +Requires at least: 3.3.1 +Tested up to: 5.8 +Stable tag: 2.4.0.3 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Radio Station let's you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. + +== Description == + +Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. + +The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. + +A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has it's own dedicated page to display all the Show details in a responsive layout. + +The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. + +As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://netmix.com/radio-station/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostationdemo.com). + +We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. Enter your [email address on the Radio Station Pro website] (https://radiostation.pro) to add yourself to the cue to be able to download the be notified when PRO is released. + += Updating from Prior to 2.3.0 = + +Since 2.3.0, the first major feature update since plugin takeover in July 2019, Radio Station has incorporated a whole bunch of enhancements (see the changelog for a full list)... but here is a shortlist of the main new features: + +* an Updated Show Page Layout (based on Content Filters not Templates) +* Responsive Schedule Views (with integrated Override support) +* Revamped Schedule calculations (with Show Shift Conflict Checking) +* Producer and Show Editor Roles (for improved Show management) +* Language Taxonomy Assignments (for Shows and Overrides) +* Admin Plugin Settings Page (with a plethora of new options) +* ...and a Radio Station Data API via the WordPress REST API! + +If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards oompatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. + += Support and Contribution = + +We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. + +For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). + +If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. + += Quickstart Guide = + +Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. + +Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://netmix.com/radio-station/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://netmix.com/radio-station/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. + +Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://netmix.com/radio-station/docs/Display.md#page-templates) used. You can also assign different [Images](https://netmix.com/radio-station/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://netmix.com/radio-station/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. + +Next you may want to give some users on your site some plugin [Roles](https://netmix.com/radio-station/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. + +There are a few [Widgets](https://netmix.com/radio-station/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. + +Then there are also a number of other [Shortcodes](https://netmix.com/radio-station/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. + +Radio Station has several in-built [Data](https://netmix.com/radio-station/docs/Data.md) types. These include [Custom Post Types](https://netmix.com/radio-station/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://netmix.com/radio-station/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://netmix.com/radio-station/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. + +This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://netmix.com/radio-station/docs/Roadmap.md) if you are interested in seeing what is coming up next! + += Upgrading to Radio Station Pro = + +Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. + + +== Installation == + +1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. +2. Activate the plugin through the 'Plugins' menu in the WordPress Admin +3. Alternatively search for Radio Station via the WordPress admin Add New plugin interface and install and activate it there. +4. Give any users who need access to the plugin the role of "Host", "Producer" or "Show Editor". Assigning These roles gives publish and edit access to the plugin's records. +5. Create Shows, add Shifts to them, and assign Images, Genres, Languages, Hosts and/or Producers. +6. Add Playlists to your Shows or assign posts to Shows as needed. +7. Go to your admin Appearance -> Widgets page to add and configure Current and Upcoming Show Widgets, and any other desired plugin widgets. +8. See the QuickStart Guide above for more detailed instructions of what else is available. + +== Frequently Asked Questions == + += How do I get started with Radio Station? = + +Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. + += Where can I find the full plugin documentation? = + +The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. + += How do I schedule a Show? = + +Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. + += How do I display a full schedule of my Station's shows? = + +In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](https://radiostation.pro/docs/shortcodes/#master-schedule-shortcode) ) + += I've scheduled all my Shows, but some are not showing up on the program schedule? = + +Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. + += What if I want to schedule a special event or one-off schedule change? = + +If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by clicking Schedule Override in the Radio Station admin menu. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) + += I'm seeing a 404 Not Found error when I click on the link for a Show! = + +Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin. + += What if I want to change or style the plugin's displays? = + +The default styles for Radio Station have intionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. + += What Widgets are available with this plugin? = + +The following Widgets are available to add via the WordPress Appearance -> Widgets page: +Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](https://radiostation.pro/docs/widgets/) for more details on these Widgets. + += Do the Widgets reload automatically? = + +Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in this free version. This capability has been added as a new feature to the [Pro version](https://radiostation.pro) however - so that the widgets refresh exactly at Show changeover times. + += What Shortcodes are available with this plugin? = + +See the [Shortcode Documentation](https://radiostation.pro/docs/shortcodes/) for more details and a full list of possible Attributes for these Shortcodes: + +* `[master-schedule]` - Master Program Schedule Display +* `[current-show]` - Current Show Widget +* `[upcoming-shows]` - Upcoming Shows Widget +* `[current-playlist]` - Current Playlist Widget +* `[shows-archive]` - Archive List of Shows +* `[genres-archive]` - Archive List of Shows sorted by Genre +* `[languages-archive]` - Archive List of Shows sorted by Language +* `[overrides-archive]` - Archive List of Schedule overrides +* `[playlists-archive]` - Archive List of Show Playlists + +Note old shortcode aliases will still work in current and future versions to prevent breakage. + += I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? = + +There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/roles/) + += How do I change the Show Avatar displayed in the sidebar widget? = + +The avatar is whatever image is assigned as the Show's Avatar. All you have to do is set a new Show Avatar on the Edit page for that Show. + += Why don't any users show up in the Hosts or Producers list on the Show edit page? = + +You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit page. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. + += My Show Hosts and Producers can't edit a Show page. What do I do? = + +The only Hosts and Producers that can edit a show are the ones listed as being Hosts or Producers for that Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Host/Producer's Shows without permission. + += I don't want to use Gravatar for my Host/Producer's image on their profile page. = + +Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. + += What languages other than English is the plugin available in? = + +Right now: + +* Albanian (sq_AL) +* Dutch (nl_NL) +* French (fr_FR) +* German (de_DE) +* Italian (it_IT) +* Russian (ru_RU) +* Serbian (sr_RS) +* Spanish (es_ES) +* Catalan (ca) + += Can the plugin be translated into my language? = + +You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. + += Can I use this plugin for Podcasts? = + +While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. + += Can I use this plugin for TwitchTV, Facebook Live, or Clubhouse shows? = + +Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. + += I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? = + +We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. + += How do I install the latest Development version for testing? = + +If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: + +1. Download the `develop` branch zip from the Github repository at: +`https://github.com/netmix/radio-station/tree/develop/` +2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` +3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` +4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` + +You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. + + +== Screenshots == +1. Table Schedule View +2. Tabbed Schedule View +3. Show Page Layout +4. Playlist Page Layout +5. Current Show Widget +6. Show Archive List Shortcode +7. Admin Settings Panel +8. Show Conflict Display + +== Changelog == + += 2.4.0.3 = +* Update: Plugin Panel (1.2.1) with zero value save and tab fixes +* Added: option to disable player audio fallback scripts +* Added: option to hide various volume controls +* Improved: lazy load player audio fallback scripts +* Improved: added author support to post types for quick edit +* Improved: Playlist content template and action hooks +* Refix: missing fix to active day tab on pageload +* Fixed: player volume slider background position (cross-browser) +* Fixed: missing title value for adjacent post links +* Fixed: Fallback scripts and fallback stream URLs + += 2.4.0.2 = +* Fixed: Multiple Player instance IDs +* Fixed: Player loading button glow animation +* Added: Enabled Pro Pricing plans page +* Added: Widget type specific classes +* Added: Alternative text positions in Player +* Added: Pause button graphics to Player + += 2.4.0.1 = +* Fixed: Rounded player play button background corner style +* Fixed: Tabbed schedule active day tab on pageload +* Improved: Radio Clock Widget layout + += 2.4.0 = +* Added: Radio Stream Player! +* Fixed: Shows archive shortcode with no Shows selected + += 2.3.3.9 = +* Update: Plugin Panel (1.1.8) with Number Step Min/Max fix +* Update: Freemius SDK (2.4.2) +* Improved: Allow for Multiple Override Times (with AJAX Saving) +* Improved: Markdown Extra Compatibility for PHP 7.4+ +* Added: Link Override to Show Data with selectable Show Fields +* Added: Language Archive Shortcode (similar to Genre Archive) +* Added: Display Linked Override Date List on Show Pages +* Added: Automatic user showtime conversion and display +* Fixed: Show Schedule sometimes starting on previous week +* Fixed: Current Show highlighting timer interval cycling +* Fixed: Before and After Show classes when no current Show +* Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch +* Fixed: Multiple host separator display in Current Show Widget +* Fixed: Playlist Widget playlist ended label when no next playlist +* Fixed: Conflicting duplicate filter name for Show Avatar +* Fixed: Time conversions where start/finish Show/Override is equal +* Fixed: Show page subarchive lists pagination button arrow display +* Fixed: Show Shifts with same start time overwriting bug + += 2.3.3.8 = +* Update: Plugin Panel (1.1.7) with Image and Color Picker fields +* Added: Stream Format and Fallback/Format selection setting +* Added: Station Email Address setting with default display option +* Added: Section order filtering for Master Schedule Views +* Added: Section display filtering for Master Schedule Views +* Added: Section display filtering for Widget sections +* Added: Show image alignment attribute to Schedule Tabs View +* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) +* Added: Reduced opacity for past Shows on Schedule Tab/Table Views +* Added: Screen Reader text for Show icons on Show Page +* Fixed: Display Widget Countdown when no Current Show/Playlist +* Fixed: Check for explicit singular.php template usage setting +* Fixed: Access to Shows Data via querystring of Show ID/name +* Fixed: Shows Data for Genres/Languages querystring of ID/name +* Fixed: Changed stable tag from trunk to version number to fix translations issue + += 2.3.3.7 = +* Fixed: Schedule Overrides overlapping multiple Show shifts +* Fixed: Bulk Edit field repetition and possible jQuery conflict +* Fixed: Related Posts check producing error output +* Fixed: WordPress Readme Parser deprecated errors for PHP7 + += 2.3.3.6 = +* Update: Freemius SDK (2.4.1) +* Update: Plugin Loader (1.1.6) with phone number and CSV validation +* Added: Station phone number setting with default display option +* Added: Schedule classes for Shows before and after current Show +* Improved: current Show highlighting on Schedule for overnight shifts +* Improved: info section reordering filters on single Show template +* Fixed: Edit permissions checks for Related to Show post assignments +* Fixed: Main Language option value for WordPress Setting +* Fixed: make Date on Tab clickable on Tabbed Schedule View +* Fixed: prevent possible conflicts with changes not saved reload message +* Fixed: do not conflict check Shift against itself for last shift check +* Fixed: link back to Show posts for related Show posts (allow multiple) +* Fixed: filter next/previous post link for (multiple) related Show posts +* Fixed: automatic pages conflict where themes filter the_content early + += 2.3.3.5 = +* Fixed: use schedule based on start_day if specified for Schedule view +* Fixed: day left/right shifting on Schedule table/tab mobile views +* Added: past/today/future filter for Schedule Override List +* Added: filter for Schedule display start day (and to accept today) +* Added: current playlist (if any) to Broadcast Data endpoint + += 2.3.3.4 = +* Improved: auto-match show description to info height on Show pages +* Improved: allow multiple Related Show selection for single post +* Improved: ability to assign Post to relate to multiple Shows +* Added: Related Show Post List column and Quick Edit field +* Added: Related Show selection Bulk Edit Action for Post List +* Added: filters for label texts and title attributes on Show Page +* Added: filter for label text above Show Player (default empty) + += 2.3.3.3 = +* Fixed: improved Current Show and Upcoming Shows calculations +* (Display showtimes when show starts before and ends after midnight) + += 2.3.3.2 = +* Update: Freemius SDK (2.4.0) +* Update: Plugin Loader (1.1.4) with weird isset glitch fix +* Fixed: Current Show for Shows ending at midnight +* Fixed: incorrect AJAX Widget plugin setting value +* Fixed: use pageload data for schedules before transients + += 2.3.3 = +* Update: Plugin Loader (1.1.3) with non-strict select match fix +* Improved: width responsiveness for table/tabbed Schedule views +* Improved: show shifts interface background colors +* Added: navigate away from page on shift change check +* Added: default time format option to Widgets +* Removed: current show transients (intermittant unreliability) +* Fixed: AJAX call causing plugin conflicts via save_post action +* Fixed: calculation of Upcoming Shows near end of the week +* Fixed: remove and duplicate actions on new shifts + += 2.3.2 = +* Update: Plugin Loader (1.1.2) with settings link fix +* Improved: use plugin timezone setting for all times +* Improved: show shift conflict checker logic +* Added: Radio Clock Widget for user/server time display +* Added: AJAX widget load option (to bypass page caches) +* Added: automated show schedule highlighting (table/tabs/list) +* Added: playlist track arrows for re-ordering tracks +* Added: AJAX save of show shifts and playlist tracks +* Added: post type editing metabox position filtering +* Added: more display attributes to Master Schedule shortcode +* Added: time format filters for time output displays +* Added: javascript user timezone display on Master Schedule +* Fixed: handling of UTC only timezone settings +* Fixed: added check for empty role capabilities +* Fixed: added settings submenu redirection fix +* Fixed: show and override midnight end conflict +* Fixed: calculate next shows at end of schedule week +* Fixed: metaboxes disappearing on position sorting +* Fixed: move tracks marked New to end of Playlist on update +* Fixed: override shift array output showing above schedule +* Fixed: master schedule specify days attribute bug +* Fixed: display real end time of overnight split shifts +* Fixed: master schedule display with days attribute +* Fixed: logic for Affected Shifts in override list +* Fixed: removed auto-tab selection change on tab view resize +* Fixed: Current Show widget schedule/countdown for Overrides +* Fixed: multiple overrides in schedule range variable conflict + += 2.3.1 = +* Update: Plugin Loader (1.1.1) with Freemius first path fix +* Fixed: conditions for Schedule Override time calculations +* Fixed: schedule table view - 12 hour format with translations +* Fixed: schedule table view hour column width style +* Fixed: javascript table/tab arrows to prevent default click +* Fixed: undefined index warning when saving show with no shifts +* Fixed: append not echo override date to shortcode archive list +* Fixed: compatibility with multiple the_content calls (Yoast) +* Fixed: reset to showcontinued flag in Schedule (table view) +* Added: show avatar and featured image URLs to Data API output +* Added: option to ping Netmix directory on show updates +* Added: option to clear transients on every pageload +* Added: filters for widget section display order + += 2.3.0 = +* Include: Plugin Loader (1.1.0) with plugin options and settings +* Include: Freemius SDK (2.3.0) and Freemius integration +* Feature: assign new Producer role to a Show for Show displays +* Feature: internal Schedule Show Shift Conflict checking +* Feature: Show Shift saving completeness and conflict checking +* Feature: added Data Endpoints API via WordPress REST and Feeds +* Feature: options to set Page and default View for Master Schedule +* Feature: post type Archive Shortcodes and Show-related Shortcodes +* Feature: display Radio Timezone on Master Schedule table view +* Feature: added Show Header image to Shows for single Show display +* Feature: added Show Language Taxonomy to Shows (and Overrides) +* Feature: added Countdown clock for Show and Playlists Widgets +* Improved: new Data Model and Schedule (with Override) Calculation +* Improved: new Show Content Template layout display method +* Improved: new Playlist Content Template layout display method +* Improved: added multiple Genre highlight selection on Master Schedule +* Improved: added Custom Field and Revision support to post types +* Improved: missing output sanitization throughout the plugin +* Improved: added file hierarchy fallbacks for CSS, JS and Templates +* Improved: enqueue conditional scripts inline instead of echoing +* Improved: Master Schedule displays enhancements and styling +* Improved: add Responsiveness to Master Schedule Table and Tab View +* Improved: add View/Edit links for editing custom post types +* Improved: load Datepicker styles locally instead of via Google +* Improved: add debug function for debug display and logging +* Improved: add links from Show Posts back to Show Page +* Improved: added Duplicate Shift button to Show Shift Editing +* Roles: new Show Producer role (same capabilities as DJ / Host) +* Roles: new Show Editor role (edit permissions but not Admin) +* Roles: Changed DJ role Label to DJ / Host (for talk show usage) +* Admin: Added Plugin Settings Admin Page (via Plugin Loader) +* Admin: Added plugin Upgrade / Updated details admin notices +* Admin: Schedule conflict notice and Show conflicts in Shift column +* Admin: Show/Override content indicator columns to Admin Show list +* Admin: Show Description helper text metabox on Show edit screen +* Admin: Fix to restore Admin Bar New/Edit links for plugin post types +* Admin: Store installed version for future updates and announcements +* Disabled: automatic loading of old templates (non theme agnostic) + += 2.2.8 = +* Fix to remove strict type checking from in_array (introduced 2.2.6) +* Fix to mismatched flush rewrite rules flag function name +* Fix to undefined index warnings for new Schedule Overrides +* Fix to not 404 author pages for DJs without blog posts + += 2.2.7 = +* Dutch translation added (Thank you to André Dortmont for the file!) +* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) +* Add Show list columns with active, shift, DJs and show image displays +* Add Schedule Override list columns with date sorting and filtering +* Add playlist track information labels to Now Playing Widget +* Added meridiem (am/pm) translations via WP Locale class +* Added star rating link to plugin announcement box +* Added update subscription form to plugin Help page +* Fix to checkbox value saving for On Air/Upcoming Widgets +* Fix 12 hour show time display in Upcoming Widget +* Fix PM 12 hour shot time display in On Air Widget +* Fix to schedule override date picker value visibility +* Fix to weekday and month translations to use WP Locale +* Fix to checkbox value saving in Upcoming Widget +* Split Plugin Admin Functions into separate file +* Split Post Type Admin Functions into separate include +* Revert anonymous function use in widget registrations + += 2.2.6 = +* Reorganize master-list shortcode into templates +* Add constant for plugin directory +* Use WP_Query instead of get_posts +* New posts_per_page and tax_query +* Fixes for undefined indexes +* Fixes for raw mysql queries +* Typecasting to support strict comparisons + += 2.2.5 = +* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) + += 2.2.4 = +* added title position and avatar width options to widgets +* added missing DJ author links as new option to widgets +* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) +* improved to show Encore Presentation in show widget displays +* fix to Show shift Encore Presentation checkbox saving + += 2.2.3 = +* added flush rewrite rules on plugin activation/deactivation +* added show_admin_column and show_in_quick_edit for Genres +* added show metadata and schedule value sanitization +* fix to 00 minute validation for Schedule Override +* convert span tags to div tags in Widgets to fix line breaks + += 2.2.2 = +* shift main playlist and show metaboxes above editor +* set plugin custom post types editor to Classic Editor +* add high priority to side metaboxes for plugin post types +* added dismissable development changeover admin notice +* added simple Patreon supporter image button and blurb +* added filter for DJ Avatar size on Author page template +* fix to Schedule Override metabox value saving +* fix to Playlist track list items overflowing metabox +* fix to shift up time row on Master Schedule table view +* fix to missing weekday headings in Master Schedule table +* fix to weekday display for Upcoming DJ Widget +* fix to user display labels on select DJ metabox +* fix to file_exists check for DJ on Air stylesheet path +* fix to make DJ multi-select input full metabox width +* fix to expand admin menu when on genre taxonomy page +* fix to expand admin menu when editing plugin post types +* fix to genre submenu item link for current page +* added GitHub URI to plugin header for GitHub updater + += 2.2.1 = +* Re-commit all missing files via SVN + += 2.2.0 = +* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) +* fixed the protocol in jQuery UI style Google URL +* reprefixed all functions for consistency (radio_station_) +* updated all the widget constructor methods +* merged the menu items into a single main menu +* updated the capability checks for the menu items +* moved the help and export pages to /templates/ +* moved all the css files to /css/ +* enqeued the djonair css from within the widget +* use plugins_url for all resource URLs +* added $wpdb->prepare to sanitize a query +* added some sanization for metabox save values +* added a week and month translation helper +* added a radio station antenna icon + += 2.1.3 = +* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). + += 2.1.2 = +* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. +* Catalan translation added (Thank you to Victor Riera for the file!) + += 2.1.1 = +* Bug fix - Fixed day of the week language translation issue in master schedule shortcode +* Bug fix - Added some error checking in the sidebar widgets +* New Feature - Added ability to give schedule overrides a featured image +* New Feature - Added built-in help page + += 2.1 = +* General code cleanup, 4.1 compatibility testing, and changes for better efficiency. +* Bug fix - Fixed issue with early morning shows spanning entire column in the programming grid shortcode +* New Feature - Master programming grid can now be displayed in div format, as well as the original table and list formats. + += 2.0.16 = +* Minor revisions to German translation. +* Fixed a bug that was resetting custom-sert role capabilities for the DJ role. + += 2.0.15 = +* German translation added (Thank you to Ian Hook for the file!) + += 2.0.14 = +* Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked +* Added ability to display DJ names on the master schedule. +* Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. +* Verified compatibility woth WordPress 4.0 + += 2.0.13 = +* Added the ability to display show avatars on the program grid. +* Added the ability to display show description in the now on-air widget and short code. + += 2.0.12 = +* Fixed a bug in the master schedule shortcode + += 2.0.11 = +* Russian translation added (Thank you to Alexander Esin for the file!) + += 2.0.10 = +* Fixed role/capability conflict with WP User Avatar plugin. +* Added the missing leading zero to 24-hour time format on the master schedule. +* Fixed dj_get_current function so that it no longer returns shows that have been moved to the trash. +* Fixed dj_get_next function so that it no longer ignores the "Active" checkbox on a show. +* Added some CSS ids and classes to the master program schedule list format to make it more useful + += 2.0.9 = +* Fixed broken upcoming show shortcode. +* Added ability to display DJ names along with the show title in the widgets. + += 2.0.8 = +* Fixed the display of schedules for upcoming shows in the widget and shortcode. +* Fixed a bug in the dj_get_next function that was causing it to ignore the beginning of the next week at the end of the current week. + += 2.0.7 = +* Fixed scheduling bug in shortcode function + += 2.0.6 = +* Master Schedule now displays days starting with the start_of_week option set in the WordPress General Settings panel. +* Fixed issue with shows that have been unplublished still showing up on the master schedule. +* Fixed missing am/pm text on shows that run overnight on the master schedule. +* Fixed an issue with shows that run overnight not spanning the correct number of hours on the second day on the master schedule. +* Fixed problem in Upcoming DJ Widget that wasn't displaying the correct upcoming shift. + += 2.0.5 = +* Fixed an issue with some shows displaying in 24 hour time on master schedule grid even though 12-hour time is specified +* Fixed a bug in the On-Air widget that was preventing shows spanning two day from displaying +* Added code to enable theme support for post-thumbnails on the "show" post-type so users don't have to add it to their theme's functions.php file anymore. + += 2.0.4 = +* Master Schedule bug for shows that start at midnight and end before the hour is up fixed. + += 2.0.3 = +* Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes + += 2.0.2 = +* Bug fix: Scheduling issue with overnight shows fixed + += 2.0.1 = +* Bug fix: Fixed PHP error in Playlist save function that was triggered during preview +* Bug fix: Fixed PHP notice in playlist template file +* Bug fix: Fixed PHP error in dj-widget shortcode + += 2.0.0 = +* Major code reorganization for better future development +* PHP warning fix +* Enabled option to add comments on Shows and Playlists +* Added option to show either single or multiple schedules in the On Air widget + += 1.6.2 = +* Minor PHP warning fixes + += 1.6.1 = +* Bug fix: Some of the code added in the previous update uses the array_replace() function that is only available in PHP 5.3+. Added a fallback for older PHP versions. + += 1.6.0 = +* Added the ability to override the weekly schedule to allow one-off events to be scheduled +* Added a list format option to the master schedule shortcode +* Added Italian translation (it_IT) (thank you to Cristofaro Giuseppe!) + += 1.5.4 = +* Fixed some PHP notices that were being generated when there were no playlist entries in the system. + += 1.5.3 = +* Added Serbian translation (sr_RS) (thank you to Miodarag Zivkovic!) + += 1.5.2.1 = +* Removed some debug code from one of the template files + += 1.5.2 = +* Fixed some localization bugs. +* Added Albanian translation (sq_AL) (thank you to Lorenc!) + += 1.5.1 = +* Fixed some localization bugs. +* Added French translation (fr_FR) (a big thank you to Dan over at BuddyPress France - http://bp-fr.net/) + += 1.5.0 = +* Plugin modified to allow for internationalization. +* Spanish translation (es_ES) added. + += 1.4.6 = +* Fixed a bug with shows that start at midnight not displaying in the on-air sidebar widget. +* Switched DJ/Show avatars in the widgets to use the featured image of the show instead of gravatar. +* Updated show template to get rid of a PHP warning that appeared if the show had no schedules. +* Fixed some other areas of the code that were generating PHP notices in WordPress 3.6 +* Added CSS classes to master program schedule output so CSS rules can be applied to specific shows +* Added new attribute to the list-shows shortcode to allow only specified genres to be displayed + += 1.4.5 = +* Fixed master-schedule shortcode bug that was preventing display of 12 hour time + += 1.4.4 = +* Compatibility fix for Wordpress 3.6 - fixed problem with giving alternative roles DJ capabilities +* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 + += 1.4.3 = +* Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time +* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 +* Added example of how to display show schedule to single-show.php template +* Added more options to the plugin's widgets +* Added new options to the master-schedule shortcode + += 1.4.2 = +* Fixed a bug in the CSS file override from theme directory + += 1.4.1 = +* Fixed issue with templates copied to the theme directory not overriding the defaults correctly +* Fixed incorrectly implemented wp_enqueue_styles() +* Removed deprecated escape_attribute() function from the plugin widgets +* Fixed some areas of the code that were generating PHP notices + += 1.4.0 = +* Compatibility fix for WordPress 3.6 + += 1.3.9 = +* Fixed a bug that was preventing sites using a non-default table prefix from seeing the list of DJs on the add/edit show pages + += 1.3.8 = +* Changes to fix the incorrect list of available shows on the Add Playlist page +* Removing Add Show links from admin menu for DJs, since they don't have permission to use them anyway. + += 1.3.7 = +* Fixed a scheduling bug in the upcoming shows widget +* By popular request, switched the order of artist and song in the now playing widget + += 1.3.6 = +* Fixed issue with shows that run overnight not showing up correctly in the sidebar widgets + += 1.3.5 = +* Fixed a time display bug in the DJ On-Air sidebar widget +* Fixed a display bug on the master schedule with overnight shows + += 1.3.4 = +* By request, added as 24-hour time format option to the master schedule and sidebar widgets. + += 1.3.3 = +* Added the ability to assign any user with the edit_shows capability as a DJ, to accomodate custom and edited roles. + += 1.3.2 = +* Fixed a bug in the DJ-on-air widget + += 1.3.1 = +* Fixed a major bug in the master schedule output + += 1.3 = +* Fixed some minor compatibility issues with WordPress 3.5 +* Fixed Shows icon in Dashboard + += 1.2 = +* Fixed thumbnail bug in sidebar widgets +* Added new widget to display upcoming shows +* Added pagination options for playlists and show blogs + += 1.1 = +* Fixed playlist edit screen so that queued songs fall to the bottom of the list to maintain play order +* Reduced the size of the content field in the playlist post type +* Some minor formatting changes to default templates +* Added genre highlighter to the master programming schedule page +* Added a second Update button on the bottom of the playlist edit page for convinience. +* Added sample template for DJ user pages +* Fixed a bug in the master schedule shortcode that messed up the table for shows that are more than two hours in duration +* Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. +* Added new field to associate blog posts with shows + += 1.0 = +* Initial release + +== Upgrade Notice == + += 2.4.0 = +* Radio Station Stream Player Widget! +* https://netmix.com/radio-station-2-4-0-release-with-stream-player/ + += 2.3.3.9 = +* Multiple Dates and Times for Schedule Overrides! +* https://netmix.com/radio-station-2-3-3-9-release-announcement/ +* Link Override to Show with Selective Fields +* Automatic Visitor Showtime Conversion and Display +* Language Archive Shortcode for Shows +* Various Bugfixes and Improvements +* Updated Freemius SDK and Plugin Loader + += 2.3.3.8 = +* Updated Plugin Panel Library +* Added Stream Format selection setting +* Added Station email address setting with default display option +* Added Section order filtering for Master Schedules and Widgets +* Added Show image alignment attribute to Schedule Tabs View + += 2.3.3.7 = +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + += 2.3.3.6 = +* Updated Freemius SDK and Plugin Loader libraries +* Added Station phone number setting with default display option +* Added Schedule classes for Shows before and after current Show +* Multiple Related Show Post assignment edit and link fixes +* Bugfixes for permissions, main language and shift checker + += 2.3.3.5 = +* Ability to assign Post to relate to multiple Shows +* Added Admin Filtering, Bulk Edit and Quick Edit interfaces +* Fixes for Schedule display left/right shifting on mobiles +* Fixes for starting Schedule display on different day + += 2.3.3.3 = +* Current and Upcoming Shows Widget Fix + += 2.3.3.2 = +* Minor Bugfix Update + += 2.3.3 = +* Important Bugfix Update +* Fix to conflict with plugins using AJAX save_post calls +* Improved accuracy for responsive table/tab Schedule views +* Added colour improvements to Show Shift interface +* Fix to calculate Current Show (transient no longer used) + += 2.3.2 = +* Improved Times, AJAX Loading and Bugfix Update +* https://netmix.com/radio-station-2-3-2-release/ +* Radio Clock Widget and Widget AJAX Loading +* AJAX Saving of Show Shifts and Playlist Tracks +* Automated current Show schedule highlighting +* Improved timezones, overrides, shift checking and more + += 2.3.1 = +* Bugfix Update and Announcing New Netmix Station Directory! +* https://netmix.com/announcing-new-netmix-directory/ +* Including minor fixes to major update release +* Option to ping Netmix Directory on show updates + += 2.3.0 = +* Major Update including many new features, enhancements and fixes! +* https://netmix.com/radio-station-2-3-0-release/ +* Revamped Templates, Master Schedule Views, Shortcodes and Widgets +* Added Admin Options, REST API Routes and Shift Conflict Checking +* Added Show Producers, Language Taxonomy, Timezones and Countdowns +* Improved User Roles, Show Images, Post Type Supports + much more! diff --git a/templates/master-schedule-div.php b/templates/master-schedule-div.php index 42d2cc4..11b6542 100644 --- a/templates/master-schedule-div.php +++ b/templates/master-schedule-div.php @@ -170,7 +170,8 @@ */ // 2.3.0: filter show time by show and context - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'div' ); + // 2.4.0.4: added missing filter arguments for Pro + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'div', false, false ); $output .= $show_time; $output .= ''; } diff --git a/templates/master-schedule-div.php.bak b/templates/master-schedule-div.php.bak new file mode 100644 index 0000000..42d2cc4 --- /dev/null +++ b/templates/master-schedule-div.php.bak @@ -0,0 +1,226 @@ +'; +for ( $i = 2; $i < 24; $i++ ) { + $rowheight = $atts['divheight'] * $i; + $output .= '#master-schedule-divs .rowspan' . $i . ' { '; + $output .= 'height: ' . ( $rowheight ) . 'px; }'; +} + +$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; +$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; +$output .= ''; + +// output the schedule +$output .= '
    '; +// $weekdays = array_keys( $days_of_the_week ); + +$output .= '
    '; +$output .= '
     
    '; +foreach ( $weekdays as $weekday ) { + $display_day = radio_station_translate_weekday( $weekday ); + $output .= '
    ' . esc_html( $display_day ) . '
    '; +} +$output .= '
    '; + +foreach ( $master_list as $hour => $days ) { + + $output .= '
    '; + + // output the hour labels + $output .= '
    '; + if ( 12 === (int) $atts['time'] ) { + // random date needed to convert time to 12-hour format + $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); + } else { + // random date needed to convert time to 24-hour format + $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); + } + $output .= '
    '; + + foreach ( $weekdays as $weekday ) { + $output .= '
    '; + if ( isset( $days[ $weekday ] ) ) { + foreach ( $days[ $weekday ] as $min => $show ) { + + // --- genre terms --- + // TODO: check term output formatting + $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); + $classes = array( 'master-show-entry', 'show-id-' . $show['id'] ); + $classes[] = sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $show['id'] ) ) ); + foreach ( $terms as $term ) { + // $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + $classes[] = $term->slug; + } + + $output .= '
    '; + + // --- show avatar --- + if ( $atts['show_image'] ) { + // 2.3.0: filter show avatar by show and context + $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); + $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'tabs' ); + if ( $show_avatar ) { + $output .= '
    ' . $show_avatar . '
    '; + } + } + + // --- show title / link --- + $show_title = get_the_title( $show['id'] ); + if ( $atts['show_link'] ) { + // 2.3.0: filter show link by show and context + $show_link = get_permalink( $show['id'] ); + $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'div' ); + if ( $show_link ) { + $show_title .= '' . $show_title . ''; + } + } + $output .= '
    '; + $output .= $show_title; + $output .= '
    '; + + // list of DJs + // 2.3.0: changed from show_djs + if ( $atts['show_hosts'] ) { + + $output .= ''; + + $show_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $show_names ) { + + $output .= ' ' . esc_html( __( 'with', 'radio-station' ) ) . ''; + + foreach ( $show_names as $name ) { + + $count++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $show_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + // --- show time --- + if ( $atts['show_times'] ) { + + $output .= ''; + + if ( 12 === (int) $atts['time'] ) { + $show_time = date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $show_time .= ' - '; + $show_time .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + } else { + $show_time = date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $show_time .= ' - '; + $show_time .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + } + + /* if ( 12 === (int) $atts['time'] ) { + $start_data_format = $end_data_format = 'g:i a'; + } else { + $start_data_format = $end_data_format = 'H:i'; + } + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, '', $atts ); + $start_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, '', $atts ); + + $start = radio_station_get_time( $shift_start_time ); + $end = radio_station_get_time( $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); + */ + + // 2.3.0: filter show time by show and context + $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'div' ); + $output .= $show_time; + $output .= ''; + } + + // --- encore --- + // 2.3.0: filter encore by show and context --- + if ( isset( $show['time']['encore'] ) ) { + $show_encore = $show['time']['encore']; + } else { + $show_encore = false; + } + $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show['id'], 'list' ); + if ( 'on' == $show_encore ) { + $output .= ' ' . esc_html( __( 'encore airing', 'radio-station' ) ) . ''; + } + + // --- show file --- + // 2.3.0: filter show file by show and context + $show_file = get_post_meta( $show['id'], 'show_file', true ); + $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'div' ); + // 2.3.2: check disable download meta + $disable_download = get_post_meta( $show['id'], 'show_download', true ); + if ( $show_file && ! empty( $show_file ) && !$disable_download ) { + $output .= ' ' . esc_html( __( 'Audio File', 'radio-station' ) ) . ''; + } + + // calculate duration of show for rowspanning + if ( isset( $show['time']['rollover'] ) ) { //show started on the previous day + $duration = $show['time']['end_hour']; + } else { + if ( $show['time']['end_hour'] >= $show['time']['start_hour'] ) { + $duration = $show['time']['end_hour'] - $show['time']['start_hour']; + } else { + $duration = 23 - $show['time']['start_hour']; + } + } + + if ( $duration >= 1 ) { + $output .= '
    '; + + if ( '00' !== $show['time']['end_min'] ) { + $output .= '
    '; + } + } + + $output .= '
    '; // end master-show-entry + } + } + $output .= '
    '; // end master-schedule-weekday + } + $output .= '
    '; // end master-schedule-hour +} +$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-schedule-legacy.php b/templates/master-schedule-legacy.php index 7461eb2..697df2f 100644 --- a/templates/master-schedule-legacy.php +++ b/templates/master-schedule-legacy.php @@ -209,7 +209,8 @@ $times .= ' - '; $times .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); } - $time = apply_filters( 'radio_station_schedule_show_time', $times, $show['id'], 'legacy' ); + // 2.4.0.4: added missing filter arguments for Pro + $time = apply_filters( 'radio_station_schedule_show_time', $times, $show['id'], 'legacy', false, false ); $output .= $time; $output .= ''; } diff --git a/templates/master-schedule-legacy.php.bak b/templates/master-schedule-legacy.php.bak new file mode 100644 index 0000000..7461eb2 --- /dev/null +++ b/templates/master-schedule-legacy.php.bak @@ -0,0 +1,255 @@ +'; + +// --- create the output in a table --- +$output .= ''; + +// --- output the headings in the correct order --- +$output .= ''; +foreach ( $days_of_the_week as $weekday => $info ) { + // 2.2.2: fix to translate incorrect variable (heading) + // 2.3.3.8: remove abbreviation and add support for display_day attribute + // $heading = substr( $weekday, 0, 3 ); + if ( 'short' == $atts['display_day'] ) { + $heading = radio_station_translate_weekday( $weekday, true ); + } else { + $heading = radio_station_translate_weekday( $weekday ); + } + $output .= ''; +} +$output .= ''; + +if ( !isset( $nextskip ) ) { + $nextskip = array(); +} + +foreach ( $master_list as $hour => $days ) { + + $output .= ''; + + $output .= ''; + + $curskip = $nextskip; + $nextskip = array(); + + foreach ( $days as $day => $min ) { + + // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours + $continue = 0; + foreach ( $curskip as $x => $skip ) { + + if ( $skip['day'] === $day ) { + if ( $skip['span'] > 1 ) { + $continue = 1; + $skip['span'] = $skip['span'] - 1; + $curskip[$x]['span'] = $skip['span']; + $nextskip = $curskip; + } + } + } + + $rowspan = 0; + foreach ( $min as $show ) { + + if ( 0 === (int) $show['time']['start_hour'] && 0 === (int) $show['time']['end_hour'] ) { ///midnight to midnight shows + if ( $show['time']['start_min'] === $show['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour + $rowspan = 24; + } + } + + if ( 0 === (int) $show['time']['end_hour'] && 0 !== (int) $show['time']['start_hour'] ) { + //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span + $rowspan = $rowspan + ( 24 - $show['time']['start_hour'] ); + } elseif ( $show['time']['start_hour'] > $show['time']['end_hour'] ) { + // show runs from before midnight night until the next morning + if ( isset( $show['time']['real_start'] ) ) { + // if we're on the second day of a show that spans two days + $rowspan = $show['time']['end_hour']; + } else { + // if we're on the first day of a show that spans two days + $rowspan = $rowspan + ( 24 - $show['time']['start_hour'] ); + } + } else { + // all other shows + $rowspan = $rowspan + ( $show['time']['end_hour'] - $show['time']['start_hour'] ); + } + } + + $span = ''; + if ( $rowspan > 1 ) { + $span = ' rowspan="' . $rowspan . '"'; + // add to both arrays + $curskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $show['id'] ), + ); + $nextskip[] = array( + 'day' => $day, + 'span' => $rowspan, + 'show' => get_the_title( $show['id'] ), + ); + } + + // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. + if ( $continue ) { + continue; + } + + $output .= ''; + + foreach ( $min as $show ) { + + $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); + $classes = ' show-id-' . $show['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $show['id'] ) ) ) . ' '; + foreach ( $terms as $show_term ) { + $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; + } + + $output .= '
    '; + + if ( $atts['show_image'] ) { + // 2.3.0: get show avatar filtered by show ID and context + $show_avatar = radio_station_get_show_avatar( $show['id'] ); + $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'legacy' ); + if ( $show_avatar ) { + $output .= '' . $show_avatar . ''; + } + } + + $output .= ''; + if ( $atts['show_link'] ) { + // 2.3.0: filter show link via show ID + $show_link = get_permalink( $show['id'] ); + $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'legacy' ); + if ( $show_link ) { + $output .= '' . get_the_title( $show['id'] ) . ''; + } else { + $output .= get_the_title( $show['id'] ); + } + } else { + $output .= get_the_title( $show['id'] ); + } + $output .= ''; + + if ( $atts['show_djs'] ) { + + $output .= ''; + + $dj_names = get_post_meta( $show['id'], 'show_user_list', true ); + $count = 0; + + if ( $dj_names ) { + + $output .= ' '; + $output .= esc_html( __( 'with', 'radio-station' ) ); + $output .= ' '; + + foreach ( $dj_names as $name ) { + $count ++; + $user_info = get_userdata( $name ); + + $output .= $user_info->display_name; + + $names_count = count( $dj_names ); + if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { + $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( $count < $names_count && $names_count > 2 ) { + $output .= ', '; + } + } + } + + $output .= ''; + } + + if ( $atts['show_times'] ) { + + $output .= ''; + + if ( 12 === (int) $atts['time'] ) { + + // 2.2.7: added meridiem translation + $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); + $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); + $times = date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); + $times .= ' - '; + $times .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); + + } else { + $times = date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); + $times .= ' - '; + $times .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); + } + $time = apply_filters( 'radio_station_schedule_show_time', $times, $show['id'], 'legacy' ); + $output .= $time; + $output .= ''; + } + + // --- encore airing --- + if ( $atts['show_encore'] ) { + // 2.3.0: filter encore switch by show ID and context + if ( isset( $show['time']['encore'] ) ) { + $encore = $show['time']['encore']; + } else { + $encore = false; + } + $encore = apply_filters( 'radio_station_schedule_show_encore', $encore, $show['id'], 'legacy' ); + if ( 'on' == $encore ) { + $output .= ''; + $output .= esc_html( __( 'encore airing', 'radio-station' ) ); + $output .= ''; + } + } + + // --- show file --- + if ( $atts['show_file'] ) { + // 2.3.0: filter show file by show ID and context + $show_file = get_post_meta( $show['id'], 'show_file', true ); + $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'legacy' ); + // 2.3.2: check disable download meta + $disable_download = get_post_meta( $show['id'], 'show_download', true ); + if ( $show_file && !empty( $show_file ) && !$disable_download ) { + // 2.3.0: added missing span close tag + $output .= ''; + $output .= esc_html( __( 'Audio File', 'radio-station' ) ) . ''; + $output .= ''; + } + } + + $output .= '
    '; + } + $output .= ''; + } + + $output .= '
    '; +} +$output .= '
    ' . $heading . '
    '; + + // 2.2.7: added meridiem translations + if ( 12 === (int) $atts['time'] ) { + if ( 0 === $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'am' ); + } elseif ( (int) $hour < 12 ) { + $output .= $hour . radio_station_translate_meridiem( 'am' ); + } elseif ( 12 === (int) $hour ) { + $output .= '12' . radio_station_translate_meridiem( 'pm' ); + } else { + $output .= ( $hour - 12 ) . radio_station_translate_meridiem( 'pm' ); + } + } else { + if ( $hour < 10 ) { + $output .= '0'; + } + $output .= $hour . ':00'; + } + + $output .= '
    '; From c00c18f24dfa4362af4bb1034fc87086ce708f0b Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 26 Sep 2021 18:35:23 +1000 Subject: [PATCH 232/377] dev updates --- css/rs-schedule.css | 15 +- docs/Shortcodes.md | 1 + includes/post-types-admin.php | 2 +- includes/shortcodes.php | 4 +- js/radio-station-page.js | 8 +- radio-station.php | 20 +- radio-station.php.bak | 3656 ---------------------- readme.txt | 2 + readme.txt.bak | 802 ----- templates/master-schedule-div.php.bak | 226 -- templates/master-schedule-legacy.php.bak | 255 -- 11 files changed, 37 insertions(+), 4954 deletions(-) delete mode 100644 radio-station.php.bak delete mode 100644 readme.txt.bak delete mode 100644 templates/master-schedule-div.php.bak delete mode 100644 templates/master-schedule-legacy.php.bak diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 73e63e1..a2530b7 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -595,15 +595,18 @@ /* Media Queries */ @media screen and (max-width: 782px) { - #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 120px;} + #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 120px;} } @media screen and (max-width: 599px) { - #master-program-schedule .show-image, - #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 90px;} - #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 75px;} + #master-program-schedule .show-image, + #master-schedule-tab-panels .master-schedule-tabs-show .show-image {width: 90px;} + #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 75px;} + #master-schedule-controls-wrapper .radio-station-server-clock, + #master-schedule-controls-wrapper .radio-station-user-clock {margin-bottom: 10px;} + #master-schedule-controls-wrapper .radio-clock-title {display: block; min-height: auto; margin-bottom: 5px;} } @media screen and (max-width: 299px) { - #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 50px;} -} \ No newline at end of file + #master-program-schedule #master-program-hour-heading, #master-program-schedule .master-program-hour-row {width: 50px;} +} diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 54b9010..37c5a55 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -48,6 +48,7 @@ The following attributes are available for the shortcode: * *hide_past_shows* : Hide shows that are finished in the Schedule for Tabs view. 0 or 1. Default 0. * *divheight* : Set the height, in pixels, of the individual divs. For legacy 'divs' view only. Default 45. * *gridheight* : Set the width, in pixels, of the grid columns. For Pro 'grid' view only. Default 150. +* 'time_spaced* : Enabled time spacing with background images. For Pro 'grid' view only. 0 or 1. Default 0. * *weeks* : Number of weeks to display in calendar. For Pro 'calendar' view only. Default 4. * *previous_weeks* : Number of past weeks to display in calendar. For Pro 'calendar' view only. Default 1. diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index da64d2f..82ba820 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -459,7 +459,7 @@ function radio_station_show_info_metabox() { echo '
    ' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; - echo '' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '' . PHP_EOL; + echo '' . esc_html( __( 'Check this box if show is currently active (Show will not appear on schedule if unchecked.)', 'radio-station' ) ) . '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . PHP_EOL; diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 7463ac2..d0c1f52 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -109,14 +109,14 @@ function radio_station_timezone_shortcode( $atts = array() ) { // --- radio timezone --- $output .= '
    '; $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); - $output .= ':
    '; + $output .= ': '; $output .= '
    ' . esc_html( $timezone_display ) . '

    '; // --- user timezone --- // 2.3.3.9: change span elements to divs $output .= '
    '; $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ); - $output .= ':
    '; + $output .= ': '; $output .= '
    '; // 2.3.2 allow for timezone selector test diff --git a/js/radio-station-page.js b/js/radio-station-page.js index d9f3dfd..21ba53f 100644 --- a/js/radio-station-page.js +++ b/js/radio-station-page.js @@ -57,8 +57,10 @@ function radio_page_responsive() { if (info && desc) { descheight = info.offsetHeight - 30; if (about) {descheight = descheight - about.offsetHeight;} - if (descheight < desc.style.minHeight) {desc.style.maxHeight = 'none';} - else {desc.style.maxHeight = descheight+'px';} + if (descheight > 30) { + if (descheight < desc.style.minHeight) {desc.style.maxHeight = '';} + else {desc.style.maxHeight = descheight+'px';} + } else {desc.style.maxHeight = '';} } } @@ -89,7 +91,7 @@ function radio_show_desc(moreless) { document.getElementById('show-desc-less').style.display = 'inline-block'; } if (moreless == 'less') { - if (typeof jQuery == 'function') {jQuery('.show-description').removeClass('expanded');} + if (typeof jQuery == 'function') {jQuery('#'+prefix+'-description').removeClass('expanded');} else {document.getElementById(prefix+'-description').classList.remove('expanded');} document.getElementById('show-more-overlay').style.display = 'block'; document.getElementById('show-desc-less').style.display = 'none'; diff --git a/radio-station.php b/radio-station.php index e1b1453..f7b58a8 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.3.1 +Version: 2.4.0.3.3 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -767,7 +767,8 @@ // --- Metadata URL --- // 2.4.0.3: added for alternative stream metadata URL 'player_bar_metadata' => array( - 'type' => 'url', + 'type' => 'text', + 'options' => 'URL', 'label' => __( 'Metadata URL', 'radio-station' ), 'default' => '', 'tab' => 'player', @@ -846,7 +847,7 @@ 'pro' => true, ), - // --- [Pro] Available Views === + // --- [Pro] Available Views --- // 2.3.2: added additional views option 'schedule_views' => array( 'type' => 'multicheck', @@ -867,6 +868,19 @@ 'pro' => true, ), + // --- [Pro] Time Spaced Grid View --- + // 2.4.0.3: added grid view time spacing option + 'schedule_timegrid' => array( + 'type' => 'checkbox', + 'label' => __( 'Time Spaced Grid', 'radio-station' ), + 'default' => '', + 'value' => 'yes', + 'helper' => __( 'Enable Grid View option for equalized time spacing and background imsges.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'schedule', + 'pro' => true, + ), + // === Show Pages === // --- Show Blocks Position --- diff --git a/radio-station.php.bak b/radio-station.php.bak deleted file mode 100644 index 77e8c1e..0000000 --- a/radio-station.php.bak +++ /dev/null @@ -1,3656 +0,0 @@ - array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Streaming URL', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- Stream Format --- - 'streaming_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Streaming Format', 'radio-station' ), - 'default' => 'aac', - 'helper' => __( 'Select streaming format for streaming URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- Fallback Stream URL --- - 'fallback_url' => array( - 'type' => 'text', - 'options' => 'URL', - 'label' => __( 'Fallback Stream URL', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Enter an alternative Streaming URL for Player fallback.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- Fallback Stream Format --- - 'fallback_format' => array( - 'type' => 'select', - 'options' => $formats, - 'label' => __( 'Fallback Format', 'radio-station' ), - 'default' => 'ogg', - 'helper' => __( 'Select streaming fallback for fallback URL.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // --- Main Radio Language --- - 'radio_language' => array( - 'type' => 'select', - 'options' => $languages, - 'label' => __( 'Main Broadcast Language', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select the main language used on your Radio Station.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'broadcast', - ), - - // === Station === - - // --- Station Title --- - // 2.3.3.8: added station title field - 'station_title' => array( - 'type' => 'text', - 'label' => __( 'Station Title', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Name of your Radio Station. For use in Stream Player and Data Feeds.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Image --- - // 2.3.3.8: added station logo image field - 'station_image' => array( - 'type' => 'image', - 'label' => __( 'Station Logo Image', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Add a logo image for your Radio Station. Please ensure image is square before uploading. Recommended size 256 x 256', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Timezone Location --- - 'timezone_location' => array( - 'type' => 'select', - 'options' => $timezones, - 'label' => __( 'Location Timezone', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select your Broadcast Location for Radio Timezone display.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Clock Time Format --- - 'clock_time_format' => array( - 'type' => 'select', - 'options' => array( - '12' => __( '12 Hour Format', 'radio-station' ), - '24' => __( '24 Hour Format', 'radio-station' ), - ), - 'label' => __( 'Clock Time Format', 'radio-station' ), - 'default' => '12', - 'helper' => __( 'Default Time Format for display output. Can be overridden in each shortcode or widget.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Phone Number --- - // 2.3.3.6: added station phone number option - 'station_phone' => array( - 'type' => 'text', - 'options' => 'PHONE', - 'label' => __( 'Station Phone', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Main call in phone number for the Station (for requests etc.)', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Phone for Shows --- - // 2.3.3.6: added default to station phone option - 'shows_phone' => array( - 'type' => 'checkbox', - 'default' => '', - 'value' => 'yes', - 'label' => __( 'Show Phone Display', 'radio-station' ), - 'helper' => __( 'Display Station phone number on Shows where a Show phone number is not set.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Station Email Address --- - // 2.3.3.8: added station email address option - 'station_email' => array( - 'type' => 'email', - 'default' => '', - 'label' => __( 'Station Email', 'radio-station' ), - 'helper' => __( 'Main email address for the Station (for requests etc.)', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // --- Email for Shows --- - // 2.3.3.8: added default to email address option - 'shows_email' => array( - 'type' => 'checkbox', - 'default' => '', - 'value' => 'yes', - 'label' => __( 'Show Email Display', 'radio-station' ), - 'helper' => __( 'Display Station email address on Shows where a Show email address is not set.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'station', - ), - - // === Feeds === - - // --- REST Data Routes --- - 'enable_data_routes' => array( - 'type' => 'checkbox', - 'label' => __( 'Enable Data Routes', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Enables Station Data Routes via WordPress REST API.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // --- Data Feed Links --- - 'enable_data_feeds' => array( - 'type' => 'checkbox', - 'label' => __( 'Enable Data Feeds', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Enable Station Data Feeds via WordPress Feed links.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // --- Ping Netmix Directory --- - // note: disabled by default for WordPress.org repository compliance - 'ping_netmix_directory' => array( - 'type' => 'checkbox', - 'label' => __( 'Ping Netmix Directory', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'If you have a Netmix Directory listing, enable this to ping the directory whenever you update your schedule.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // --- Clear Transients --- - 'clear_transients' => array( - 'type' => 'checkbox', - 'label' => __( 'Clear Transients', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Clear Schedule transients with every pageload. Less efficient but more reliable.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - ), - - // --- Transient Caching --- - 'transient_caching' => array( - 'type' => 'checkbox', - 'label' => __( 'Show Transients', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Use Show Transient Data to improve Schedule calculation performance.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - 'pro' => true, - ), - - // --- Show Shift Feeds --- - /* 'show_shift_feeds' => array( - 'type' => 'checkbox', - 'label' => __( 'Show Shift Feeds', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts.', 'radio-station' ), - 'tab' => 'general', - 'section' => 'feeds', - 'pro' => true, - ), */ - - // === Basic Stream Player === - - // TODO: add note about these defaults being overrideable in widgets - - // --- Player Title --- - 'player_title' => array ( - 'type' => 'checkbox', - 'label' => __( 'Display Station Title', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Title in Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Player Image --- - 'player_image' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Station Image', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Display your Radio Station Image in Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Player Script --- - // 2.4.0.3: change script default to jplayer - 'player_script' => array( - 'type' => 'select', - 'label' => __( 'Player Script', 'radio-station' ), - 'default' => 'jplayer', - 'options' => array( - 'jplayer' => __( 'jPlayer', 'radio-station' ), - 'howler' => __( 'Howler', 'radio-station' ), - 'amplitude' => __( 'Amplitude', 'radio-station' ), - ), - 'helper' => __( 'Default audio script to use for playback in the Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Fallback Scripts --- - // 2.4.0.3: added fallback enable/disable switching - // 2.4.0.3: fixed option label from Player Script - 'player_fallbacks' => array( - 'type' => 'multicheck', - 'label' => __( 'Fallback Scripts', 'radio-station' ), - 'default' => array( 'amplitude', 'howler', 'jplayer' ), - 'options' => array( - 'jplayer' => __( 'jPlayer', 'radio-station' ), - 'howler' => __( 'Howler', 'radio-station' ), - 'amplitude' => __( 'Amplitude', 'radio-station' ), - ), - 'helper' => __( 'Enabled fallback audio scripts to try when the default Player script fails.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Player Theme --- - 'player_theme' => array( - 'type' => 'select', - 'label' => __( 'Default Player Theme', 'radio-station' ), - 'default' => 'light', - 'options' => array( - 'light' => __( 'Light', 'radio-station' ), - 'dark' => __( 'Dark', 'radio-station' ), - ), - 'helper' => __( 'Default Player Controls theme style.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Player Buttons --- - 'player_buttons' => array( - 'type' => 'select', - 'label' => __( 'Default Player Buttons', 'radio-station' ), - 'default' => 'rounded', - 'options' => array( - 'circular' => __( 'Circular Buttons', 'radio-station' ), - 'rounded' => __( 'Rounded Buttons', 'radio-station' ), - 'square' => __( 'Square Buttons', 'radio-station' ), - ), - 'helper' => __( 'Default Player Buttons shape style.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // --- Volume Controls --- - // 2.4.0.3: added enable/disable volume controls option - 'player_volumes' => array( - 'type' => 'multicheck', - 'label' => __( 'Volume Controls', 'radio-station' ), - 'default' => array( 'slider', 'updown', 'mute', 'max' ), - 'options' => array( - 'slider' => __( 'Volume Slider', 'radio-station' ), - 'updown' => __( 'Volume Plus / Minus', 'radio-station' ), - 'mute' => __( 'Mute Volume Toggle', 'radio-station' ), - 'max' => __( 'Maximize Volume', 'radio-station' ), - ), - 'helper' => __( 'Which volume controls to display in the Player by default.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - ), - - // --- Player Debug Mode --- - 'player_debug' => array( - 'type' => 'checkbox', - 'label' => __( 'Player Debug Mode', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Output player debug information in browser javascript console.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'basic', - 'pro' => false, - ), - - // === Player Colours === - - // --- [Pro] Playing Highlight Color --- - 'player_playing_color' => array( - 'type' => 'color', - 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), - 'default' => '#70E070', - 'helper' => __( 'Default highlight color to use for Play button icon when playing.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro] Control Icons Highlight Color --- - 'player_buttons_color' => array( - 'type' => 'color', - 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), - 'default' => '#00A0E0', - 'helper' => __( 'Default highlight color to use for Control button icons when active.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro] Volume Knob Color --- - 'player_thumb_color' => array( - 'type' => 'color', - 'label' => __( 'Volume Knob Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Knob Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // --- [Pro] Volume Track Color --- - 'player_range_color' => array( - 'type' => 'coloralpha', - 'label' => __( 'Volume Track Color', 'radio-station' ), - 'default' => '#80C080', - 'helper' => __( 'Default Track Color for Player Volume Slider.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'colors', - 'pro' => true, - ), - - // === Advanced Stream Player === - - // --- Player Volume --- - 'player_volume' => array( - 'type' => 'number', - 'label' => __( 'Player Start Volume', 'radio-station' ), - 'default' => 77, - 'min' => 0, - 'step' => 1, - 'max' => 100, - 'helper' => __( 'Initial volume for when the Player starts playback.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- Single Player --- - 'player_single' => array( - 'type' => 'checkbox', - 'label' => __( 'Single Player at Once', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Stop any existing Players on the page or in other windows or tabs when a Player is started.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => false, - ), - - // --- [Pro] Player Autoresume --- - 'player_autoresume' => array( - 'type' => 'checkbox', - 'label' => __( 'Autoresume Playback', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Attempt to resume playback if visitor was playing. Only triggered when the user first interacts with the page.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => true, - ), - - // --- [Pro] Popup Player Window --- - /* 'player_popup' => array( - 'type' => 'checkbox', - 'label' => __( 'Popup Player Window', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Add a popup icon to your Player to open it in a separate window.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'advanced', - 'pro' => true, - ), */ - - // === Sitewide Player Bar === - - // --- Player Bar Note --- - 'player_bar_note' => array( - 'type' => 'note', - 'label' => __( 'Bar Defaults Note', 'radio-station' ), - 'helper' => __( 'The Bar Player uses the default configurations set above.', 'radio-station' ) - . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - // 'pro' => true, - ), - - // --- [Pro] Sitewide Player Bar --- - 'player_bar' => array( - 'type' => 'select', - 'label' => __( 'Sitewide Player Bar', 'radio-station' ), - 'default' => 'off', - 'options' => array( - 'off' => __( 'No Player Bar', 'radio-station' ), - 'top' => __( 'Top Player Bar', 'radio-station' ), - 'bottom' => __( 'Bottom Player Bar', 'radio-station' ), - ), - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Add a fixed position Player Bar which displays Sitewide.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro] Player Bar Height --- - 'player_bar_height' => array( - 'type' => 'number', - 'min' => 40, - 'max' => 400, - 'step' => 1, - 'label' => __( 'Player Bar Height', 'radio-station' ), - 'default' => 80, - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Set the height of the Sitewide Player Bar in pixels.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro] Fade In Player Bar --- - 'player_bar_fadein' => array( - 'type' => 'number', - 'label' => __( 'Fade In Player Bar', 'radio-station' ), - 'default' => 2500, - 'min' => 0, - 'step' => 100, - 'max' => 10000, - 'helper' => __( 'Number of milliseconds after Page load over which to fade in Player Bar. Use 0 for instant display.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Continuous Playback --- - // 2.4.0.1: fix for missing value field - 'player_bar_continuous' => array( - 'type' => 'checkbox', - 'label' => __( 'Continuous Playback', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Uninterrupted Sitewide Bar playback while user is navigating between pages! Pages are loaded in background and faded in while Player Bar persists.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Player Page Fade --- - 'player_bar_pagefade' => array( - 'type' => 'number', - 'label' => __( 'Page Fade Time', 'radio-station' ), - 'default' => 2000, - 'min' => 0, - 'step' => 100, - 'max' => 10000, - 'helper' => __( 'Number of milliseconds over which to fade in new Pages (when continuous playback is enabled.) Use 0 for instant display.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Page Load Timeout --- - // 2.4.0.3: add page load timeout option - 'player_bar_timeout' => array( - 'type' => 'number', - 'label' => __( 'Page Load Timeout', 'teleporter' ), - 'default' => 7000, - 'min' => 0, - 'step' => 500, - 'max' => 20000, - 'helper' => __( 'Number of milliseconds to wait for new Page to load before fading in anyway (when continuous playback is enabled.)', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Bar Player Text Color --- - 'player_bar_text' => array( - 'type' => 'color', - 'label' => __( 'Bar Player Text Color', 'radio-station' ), - 'default' => '#FFFFFF', - 'helper' => __( 'Text color for the fixed position Sitewide Bar Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Bar Player Background Color --- - 'player_bar_background' => array( - 'type' => 'coloralpha', - 'label' => __( 'Bar Player Background Color', 'radio-station' ), - 'default' => 'rgba(0,0,0,255)', - 'helper' => __( 'Background color for the fixed position Sitewide Bar Player.', 'radio-station' ), - 'tab' => 'player', - 'section' => 'bar', - 'pro' => true, - ), - - // --- [Pro] Display Current Show --- - // 2.4.0.3: added for current show display - 'player_bar_currentshow' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Current Show', 'radio-station' ), - 'value' => 'yes', - 'default' => 'yes', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Display the Current Show in the Player Bar.', 'radio-station' ), - 'pro' => true, - ), - - // --- [Pro] Display Metadata --- - // 2.4.0.3: added for now playing metadata display - 'player_bar_nowplaying' => array( - 'type' => 'checkbox', - 'label' => __( 'Display Now Playing', 'radio-station' ), - 'value' => 'yes', - 'default' => 'yes', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Display the currently playing song in the Player Bar, if a supported metadata format is available. (Icy Meta, Icecast, Shoutcast 1/2, Current Playlist)', 'radio-station' ), - 'pro' => true, - ), - - // --- Metadata URL --- - // 2.4.0.3: added for alternative stream metadata URL - 'player_bar_metadata' => array( - 'type' => 'url', - 'label' => __( 'Metadata URL', 'radio-station' ), - 'default' => '', - 'tab' => 'player', - 'section' => 'bar', - 'helper' => __( 'Now playing metadata is normally retrieved via the Stream URL. Use this setting if you need to provide an alternative metadata location.', 'radio-station' ), - 'pro' => true, - ), - - // TODO: additional CSS input textarea field ? - - // === Master Schedule Page === - - // --- Schedule Page --- - 'schedule_page' => array( - 'type' => 'select', - 'options' => 'PAGEID', - 'label' => __( 'Master Schedule Page', 'radio-station' ), - 'default' => '', - 'helper' => __( 'Select the Page you are displaying the Master Schedule on.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Automatic Schedule Display --- - 'schedule_auto' => array( - 'type' => 'checkbox', - 'label' => __( 'Automatic Display', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: ', 'radio-station' ) . ' [master-schedule]', - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Default Schedule View --- - 'schedule_view' => array( - 'type' => 'select', - 'label' => __( 'Schedule View Default', 'radio-station' ), - 'default' => 'table', - 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'div' => __( 'Divs View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'default' => __( 'Legacy Table', 'radio-station' ), - ), - 'helper' => __( 'View type to use for automatic display on Master Schedule Page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- Schedule Clock Display --- - 'schedule_clock' => array( - 'type' => 'select', - 'label' => __( 'Schedule Clock?', 'radio-station' ), - 'default' => 'clock', - 'options' => array( - '' => __( 'None', 'radio-station' ), - 'clock' => __( 'Clock', 'radio-station' ), - 'timezone' => __( 'Timezone', 'radio-station' ), - ), - 'helper' => __( 'Radio Time section display above program Schedule.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - ), - - // --- [Pro] Schedule Switcher --- - 'schedule_switcher' => array( - 'type' => 'checkbox', - 'label' => __( 'View Switching', 'radio-station' ), - 'default' => '', - 'value' => 'yes', - 'helper' => __( 'Enable View Switching on the automatic Master Schedule page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - 'pro' => true, - ), - - // --- [Pro] Available Views === - // 2.3.2: added additional views option - 'schedule_views' => array( - 'type' => 'multicheck', - 'label' => __( 'Available Views', 'radio-station' ), - // note: unstyled list view not included in defaults - 'default' => array( 'table', 'calendar' ), - 'value' => 'yes', - 'options' => array( - 'table' => __( 'Table View', 'radio-station' ), - 'tabs' => __( 'Tabbed View', 'radio-station' ), - 'list' => __( 'List View', 'radio-station' ), - 'grid' => __( 'Grid View', 'radio-station' ), - 'calendar' => __( 'Calendar View', 'radio-station' ), - ), - 'helper' => __( 'Switcher Views available on automatic Master Schedule page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'schedule', - 'pro' => true, - ), - - // === Show Pages === - - // --- Show Blocks Position --- - 'show_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Show info blocks relative to Show Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // ---- Show Section Layout --- - 'show_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Show Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Show description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Show Header Image --- - // 2.3.2: added plural to option label - 'show_header_image' => array( - 'type' => 'checkbox', - 'label' => __( 'Content Header Images', 'radio-station' ), - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Latest Show Posts --- - // 'show_latest_posts' => array( - // 'type' => 'numeric', - // 'label' => __( 'Latest Show Posts', 'radio-station' ), - // 'step' => 1, - // 'min' => 0, - // 'max' => 100, - // 'default' => 3, - // 'helper' => __( 'Number of Latest Blog Posts to display above Show Page tabs.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'show', - // ), - - // --- Show Posts Per Page --- - 'show_posts_per_page' => array( - 'type' => 'numeric', - 'label' => __( 'Posts per Page', 'radio-station' ), - 'step' => 1, - 'min' => 0, - 'max' => 1000, - 'default' => 10, - 'helper' => __( 'Linked Show Posts per page on the Show Page tab/display.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- Show Playlists per Page --- - 'show_playlists_per_page' => array( - 'type' => 'numeric', - 'step' => 1, - 'min' => 0, - 'max' => 1000, - 'label' => __( 'Playlists per Page', 'radio-station' ), - 'default' => 10, - 'helper' => __( 'Playlists per page on the Show Page tab/display', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - ), - - // --- [Pro] Show Episodes per Page --- - 'show_episodes_per_page' => array( - 'type' => 'number', - 'label' => __( 'Episodes per Page', 'radio-station' ), - 'step' => 1, - 'min' => 1, - 'max' => 1000, - 'default' => 10, - 'helper' => __( 'Number of Show Episodes per page on the Show page tab/display.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'show', - 'pro' => true, - ), - - // === Profile Pages === - // 2.3.3.9: added proflie page settings - - // --- [Pro] Profile Blocks Position --- - 'profile_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Profile info blocks relative to Profile Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'profile', - 'pro' => true, - ), - - // ---- [Pro] Profile Section Layout --- - 'profile_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Profile Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Profile description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'profile', - 'pro' => true, - ), - - // === Episode Pages === - // 2.3.3.9: added episode page settings - - // --- [Pro] Episode Blocks Position --- - 'episode_block_position' => array( - 'type' => 'select', - 'label' => __( 'Info Blocks Position', 'radio-station' ), - 'options' => array( - 'left' => __( 'Float Left', 'radio-station' ), - 'right' => __( 'Float Right', 'radio-station' ), - 'top' => __( 'Float Top', 'radio-station' ), - ), - 'default' => 'left', - 'helper' => __( 'Where to position Episode info blocks relative to Episode Page content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - // ---- [Pro] Episode Section Layout --- - 'episode_section_layout' => array( - 'type' => 'select', - 'label' => __( 'Episode Content Layout', 'radio-station' ), - 'options' => array( - 'tabbed' => __( 'Tabbed', 'radio-station' ), - 'standard' => __( 'Standard', 'radio-station' ), - ), - 'default' => 'tabbed', - 'helper' => __( 'How to display extra sections below Episode description. In content tabs or standard layout down the page.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'episode', - 'pro' => true, - ), - - - // ==== Archives === - - // --- Shows Archive Page --- - 'show_archive_page' => array( - 'label' => __( 'Shows Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Show archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - ), - - // --- Automatic Display --- - 'show_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [shows-archive]', - 'tab' => 'pages', - 'section' => 'archives', - ), - - // ? --- Redirect Shows Archive --- ? - // 'show_archive_override' => array( - // 'label' => __( 'Redirect Shows Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Shows to Shows Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', - // ), - - // --- Overrides Archive Page --- - 'override_archive_page' => array( - 'label' => __( 'Overrides Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Override archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - ), - - // --- Automatic Display --- - 'override_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Override Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [overrides-archive]', - 'tab' => 'pages', - 'section' => 'archives', - ), - - // ? --- Redirect Overrides Archive --- ? - // 'override_archive_override' => array( - // 'label' => __( 'Redirect Overrides Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Overrides to Overrides Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', - // ), - - // --- Playlists Archive Page --- - 'playlist_archive_page' => array( - 'label' => __( 'Playlists Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Playlist archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - ), - - // --- Automatic Display --- - 'playlist_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [playlists-archive]', - 'tab' => 'pages', - 'section' => 'archives', - ), - - // ? --- Redirect Playlists Archive --- ? - // 'playlist_archive_override' => array( - // 'label' => __( 'Redirect Playlists Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Custom Post Type Archive for Playlists to Playlist Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', - // ), - - // --- Genres Archive Page --- - 'genre_archive_page' => array( - 'label' => __( 'Genres Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Genre archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - ), - - // --- Automatic Display --- - 'genre_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [genres-archive]', - 'tab' => 'pages', - 'section' => 'archives', - ), - - // ? --- Redirect Genres Archives --- ? - // 'genre_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Genres to Genres Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', - // ), - - // --- Languages Archive Page --- - // 2.3.3.9: added language archive page - 'language_archive_page' => array( - 'label' => __( 'Languages Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Language archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - ), - - // --- Automatic Display --- - // 2.3.3.9: added language archive automatic page - 'language_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Language Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [languages-archive]', - 'tab' => 'pages', - 'section' => 'archives', - ), - - // ? --- Redirect Languages Archives --- ? - // 'language_archive_override' => array( - // 'label' => __( 'Redirect Genres Archive', 'radio-station' ), - // 'type' => 'checkbox', - // 'value' => 'yes', - // 'default' => '', - // 'helper' => __( 'Redirect Taxonomy Archive for Languages to Languages Archive Page.', 'radio-station' ), - // 'tab' => 'pages', - // 'section' => 'archives', - // ), - - // === Single Templates === - - // --- Templates Change Note --- - 'templates_change_note' => array( - 'type' => 'note', - 'label' => __( 'Templates Change Note', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, the way that Templates are implemented has changed.', 'radio-station' ) - . ' ' . __( 'See the Documentation for more information:', 'radio-station' ) - . ' ' . __( 'Templates Documentation', 'radio-station' ) . '', - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Show Template --- - 'show_template' => array( - 'label' => __( 'Show Template', 'radio-station' ), - 'type' => 'select', - 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), - ), - 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Show content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Combined Template Method --- - 'show_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Playlist Template --- - // 2.3.3.8: added missing singular.php option to match show_template - 'playlist_template' => array( - 'label' => __( 'Playlist Template', 'radio-station' ), - 'type' => 'select', - 'options' => array( - 'page' => __( 'Theme Page Template (page.php)', 'radio-station' ), - 'post' => __( 'Theme Post Template (single.php)', 'radio-station' ), - 'singular' => __( 'Theme Singular Template (singular.php)', 'radio-station' ), - 'legacy' => __( 'Legacy Plugin Template', 'radio-station' ), - ), - 'default' => 'page', - 'helper' => __( 'Which template to use for displaying Playlist content.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // --- Combined Template Method --- - 'playlist_template_combined' => array( - 'label' => __( 'Combined Method', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => '', - 'helper' => __( 'Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.)', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'single', - ), - - // === Widgets === - - // --- AJAX Loading --- - // 2.3.3: fix to value of value key - 'ajax_widgets' => array( - 'type' => 'checkbox', - 'label' => __( 'AJAX Load Widgets?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Defaults plugin widgets to AJAX loading. Can also be set on individual widgets.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - ), - - // --- [Pro] Dynamic Reloading --- - 'dynamic_reload' => array( - 'type' => 'checkbox', - 'label' => __( 'Dynamic Reloading?', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Automatically reload all plugin widgets on change of current Show. Can also be set on individual widgets.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // --- Translate User Times --- - 'convert_show_times' => array( - 'type' => 'checkbox', - 'label' => __( 'Convert Show Times', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Automatically display Show times converted into the visitor timezone, based on their browser setting.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // --- [Pro] Timezone Switching --- - 'timezone_switching' => array( - 'type' => 'checkbox', - 'label' => __( 'User Timezone Switching', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow visitors to select their Timezone manually for Show time translations.', 'radio-station' ), - 'tab' => 'widgets', - 'section' => 'loading', - 'pro' => true, - ), - - // === Roles / Capabilities / Permissions === - // 2.3.0: added new capability and role options - - // --- Show Editing Permission Note --- - // 2.4.0.3: added role to show assignment note - 'permissions_show_role_note' => array( - 'type' => 'note', - 'label' => __( 'Show Editing Permissions', 'radio-station' ), - 'helper' => __( 'By default, only Hosts and Producers that are assigned to a Show can edit that Show.', 'radio-station' ) - . ' ' . __( 'This means an Administrator or Show Editor must assign these users to the Show first.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Playlist Editing Role Note --- - // 2.4.0.3: added role to playlist assignment note - 'permissions_playlistow_role_note' => array( - 'type' => 'note', - 'label' => __( 'Playlist Permissions', 'radio-station' ), - 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Show Editor Role Note --- - 'show_editor_role_note' => array( - 'type' => 'note', - 'label' => __( 'Show Editor Role', 'radio-station' ), - 'helper' => __( 'Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types.', 'radio-station' ) - . ' ' . __( 'You can assign this Role to any user to give them full Station Schedule updating permissions.', 'radio-station' ) - . ' ' . __( 'This is so a manager can edit the schedule without requiring full site administration role.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Author Role Capabilities --- - 'add_author_capabilities' => array( - 'type' => 'checkbox', - 'label' => __( 'Add to Author Capabilities', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Author role to publish and edit their own Shows and Playlists.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // --- Editor Role Capabilities --- - 'add_editor_capabilities' => array( - 'type' => 'checkbox', - 'label' => __( 'Add to Editor Capabilities', 'radio-station' ), - 'default' => 'yes', - 'value' => 'yes', - 'helper' => __( 'Allow users with WordPress Editor role to edit all Radio Station post types.', 'radio-station' ), - 'tab' => 'roles', - 'section' => 'permissions', - ), - - // ? --- Disallow Shift Changes --- ? - // 'disallow_shift_changes' => array( - // 'type' => 'checkbox', - // 'label' => __( 'Disallow Shift Changes', 'radio-station' ), - // 'default' => array(), - // 'options' => array( - // 'authors' => __( 'WordPress Authors', 'radio-station' ), - // 'editors' => __( 'WorddPress Editors', 'radio-station' ), - // 'hosts' => __( 'Assigned DJs / Hosts', 'radio-station' ), - // 'producers' => __( 'Assigned Producers', 'radio-station' ), - // ), - // 'helper' => __( 'Prevents users of these Roles changing Show Shift times.', 'radio-station' ), - // 'tab' => 'roles', - // 'section' => 'permissions', - // 'pro' => true, - // ), - - // === Tabs and Sections === - - // --- Tab Labels --- - // 2.3.2: add widget options tab - // 2.3.3.8: added player options tab - // 2.3.3.8: move templates section onto pages tab - 'tabs' => array( - 'general' => __( 'General', 'radio-station' ), - 'pages' => __( 'Pages', 'radio-station' ), - 'player' => __( 'Player', 'radio-station' ), - // 'templates' => __( 'Templates', 'radio-station' ), - 'widgets' => __( 'Widgets', 'radio-station' ), - 'roles' => __( 'Roles', 'radio-station' ), - ), - - // --- Section Labels --- - // 2.3.2: add widget loading section - // 2.3.3.9: added profile pages section - 'sections' => array( - 'broadcast' => __( 'Broadcast', 'radio-station' ), - 'station' => __( 'Station', 'radio-station' ), - 'feeds' => __( 'Feeds', 'radio-station' ), - 'basic' => __( 'Basic Defaults', 'radio-station' ), - 'advanced' => __( 'Advanced Defaults', 'radio-station' ), - 'colors' => __( 'Player Colors', 'radio-station' ), - 'bar' => __( 'Sitewide Bar Player', 'radio-station' ), - 'single' => __( 'Single Templates', 'radio-station' ), - 'archive' => __( 'Archive Templates', 'radio-station' ), - 'schedule' => __( 'Schedule Page', 'radio-station' ), - 'show' => __( 'Show Pages', 'radio-station' ), - 'profile' => __( 'Profile Pages', 'radio-station' ), - 'episode' => __( 'Episode Pages', 'radio-station' ), - 'archives' => __( 'Archives', 'radio-station' ), - 'loading' => __( 'Widget Loading', 'radio-station' ), - 'permissions' => __( 'Permissions', 'radio-station' ), - ), -); - -// ------------------------- -// Pro Version Install Check -// ------------------------- -// 2.4.0.3: added check active/installed Pro version -$plan = 'free'; - -// --- check for deactivated pro plugin --- -$plugins = wp_cache_get( 'plugins', 'plugins' ); -if ( !$plugins ) { - if ( function_exists( 'get_plugins' ) ) { - $plugins = get_plugins(); - } else { - $plugin_path = ABSPATH . 'wp-admin/includes/plugin.php'; - if ( file_exists( $plugin_path ) ) { - include $plugin_path; - $plugins = get_plugins(); - } - } -} -if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { - foreach ( $plugins as $slug => $plugin ) { - if ( strstr( $slug, 'radio-station-pro.php' ) ) { - $plan = 'premium'; - break; - } - } -} - -// ---------------------- -// Plugin Loader Settings -// ---------------------- -// 2.3.0: added plugin loader settings -$slug = 'radio-station'; - -// --- settings array --- -$settings = array( - // --- Plugin Info --- - 'slug' => $slug, - 'file' => __FILE__, - 'version' => '0.0.1', - - // --- Menus and Links --- - 'title' => 'Radio Station', - 'parentmenu' => 'radio-station', - 'home' => RADIO_STATION_HOME_URL, - 'docs' => RADIO_STATION_DOCS_URL, - 'support' => 'https://github.com/netmix/radio-station/issues/', - 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), - 'share' => RADIO_STATION_HOME_URL . '#share', - 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), - 'donate' => 'https://patreon.com/radiostation', - 'donatetext' => __( 'Support this Plugin', 'radio-station' ), - 'readme' => false, - 'settingsmenu' => false, - - // --- Options --- - 'namespace' => 'radio_station', - 'settings' => 'rs', - 'option' => 'radio_station', - 'options' => $options, - - // --- WordPress.Org --- - 'wporgslug' => 'radio-station', - 'wporg' => true, - 'textdomain' => 'radio-station', - - // --- Freemius --- - // 2.4.0.1: turn on addons switch for Pro - // 2.4.0.3: turn on plans switch for Pro also - // 2.4.0.3: set Pro details and Upgrade links - 'freemius_id' => '4526', - 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', - 'hasplans' => true, - // 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', - 'upgrade_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), - 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', - 'hasaddons' => false, - 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), - 'plan' => $plan, -); - -// ------------------------- -// Set Plugin Option Globals -// ------------------------- -global $radio_station_data; -$radio_station_data['options'] = $options; -$radio_station_data['settings'] = $settings; - -// ---------------------------- -// Start Plugin Loader Instance -// ---------------------------- -require RADIO_STATION_DIR . '/loader.php'; -$instance = new radio_station_loader( $settings ); - -// -------------------------- -// Include Plugin Admin Files -// -------------------------- -// 2.2.7: added conditional load of admin includes -// 2.2.7: moved all admin functions to radio-station-admin.php -if ( is_admin() ) { - require RADIO_STATION_DIR . '/radio-station-admin.php'; - require RADIO_STATION_DIR . '/includes/post-types-admin.php'; - - // --- Contextual Help --- - // 2.3.0: maybe load contextual help config - if ( file_exists( RADIO_STATION_DIR . '/help/contextual-help-config.php' ) ) { - include RADIO_STATION_DIR . '/help/contextual-help-config.php'; - } -} - -// ----------------------- -// Load Plugin Text Domain -// ----------------------- -add_action( 'plugins_loaded', 'radio_station_init' ); -function radio_station_init() { - // 2.3.0: use RADIO_STATION_DIR constant - load_plugin_textdomain( 'radio-station', false, RADIO_STATION_DIR . '/languages' ); -} - -// -------------------- -// Check Plugin Version -// -------------------- -// 2.3.0: check plugin version for updates and announcements -add_action( 'init', 'radio_station_check_version', 9 ); -function radio_station_check_version() { - - // --- get current and stored versions --- - // 2.3.2: use plugin version function - $version = radio_station_plugin_version(); - $stored_version = get_option( 'radio_station_version', false ); - - // --- check current against stored version --- - if ( !$stored_version ) { - - // --- no stored plugin version, add it now --- - update_option( 'radio_station_version', $version ); - - if ( version_compare( $version, '2.3.0', '>=' ) ) { - // --- flush rewrite rules (for new post type and rest route rewrites) --- - // (handled separately as 2.3.0 is first version with version checking) - add_option( 'radio_station_flush_rewrite_rules', true ); - } - - } elseif ( version_compare( $version, $stored_version, '>' ) ) { - - // --- updates from before to after x.x.x --- - // (code template if/when needed for future release updates) - // if ( ( version_compare( $version, 'x.x.x', '>=' ) ) - // && ( version_compare( $stored_version, 'x.x.x', '<' ) ) ) { - // // eg. trigger a single thing to do - // add_option( 'radio_station_do_thing_once', true ); - // } - - // --- bump stored version to current version --- - update_option( 'radio_station_previous_version', $stored_version ); - update_option( 'radio_station_version', $version ); - } -} - -// ----------------- -// Plugin Activation -// ----------------- -// (run on plugin activation, and thus also after a plugin update) -// 2.2.8: fix for mismatched flag function name -register_activation_hook( RADIO_STATION_FILE, 'radio_station_plugin_activation' ); -function radio_station_plugin_activation() { - // --- flag to flush rewrite rules --- - // 2.2.3: added this for custom post types rewrite flushing - add_option( 'radio_station_flush_rewrite_rules', true ); - - // --- clear schedule transients --- - // 2.3.3: added clear transients on (re)activation - // 2.3.3.9: just use clear cached data function - radio_station_clear_cached_data( false ); - - // --- set welcome redirect transient --- - // TODO: check if handled by Freemius activation - // set_transient( 'radio_station_welcome', 1, 7 ); -} - -// --------------------------- -// Activation Welcome Redirect -// --------------------------- -/* add_action( 'admin_init', 'radio_station_welcome_redirect' ); -function radio_station_welcome_redirect() { - if ( !get_transient( 'radio_station_welcome' ) || wp_doing_ajax() || is_network_admin() || !current_user_can( 'install_plugins' ) ) { - return; - } - delete_transient( 'radio_station_welcome' ); - wp_safe_redirect( admin_url( 'admin.php?page=radio-station&welcome=1' ) ); - exit; -} */ - -// ----------------------------------- -// Flush Rewrite Rules on Deactivation -// ----------------------------------- -register_deactivation_hook( RADIO_STATION_FILE, 'flush_rewrite_rules' ); - -// ---------------------- -// Enqueue Plugin Scripts -// ---------------------- -// 2.3.0: added for enqueueing main Radio Station script -add_action( 'wp_enqueue_scripts', 'radio_station_enqueue_scripts' ); -function radio_station_enqueue_scripts() { - - // --- enqueue custom stylesheet if found --- - // 2.3.0: added for automatic custom style loading - radio_station_enqueue_style( 'custom' ); - - // --- enqueue plugin script --- - // 2.3.0: added jquery dependency for inline script fragments - radio_station_enqueue_script( 'radio-station', array( 'jquery' ), true ); - - // --- set script suffix --- - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - $suffix = ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) ? '' : $suffix; - - // -- enqueue javascript timezone detection script --- - // 2.3.3.9: activated for improved timezone detection - $jstz_url = plugins_url( 'js/jstz' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'jstz', $jstz_url, array(), '1.0.6', false ); - - // --- Moment.js --- - // ref: https://momentjs.com - // 2.3.3.9: added for improved time format display - $moment_url = plugins_url( 'js/moment' . $suffix . '.js', RADIO_STATION_FILE ); - wp_enqueue_script( 'momentjs', $moment_url, array(), '2.29.1', false ); - -} - -// --------------------- -// Enqueue Plugin Script -// --------------------- -function radio_station_enqueue_script( $scriptkey, $deps = array(), $infooter = false ) { - - // --- set stylesheet filename and child theme path --- - $filename = $scriptkey . '.js'; - - // 2.3.0: check template hierarchy for file - $template = radio_station_get_template( 'both', $filename, 'js' ); - if ( $template ) { - - // 2.3.2: use plugin version for releases - $plugin_version = radio_station_plugin_version(); - $version_length = strlen( $plugin_version ); - // TODO: maybe allow for minor version release numbers - // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { - if ( 5 == $version_length ) { - $version = $plugin_version; - } else { - $version = filemtime( $template['file'] ); - } - - $url = $template['url']; - - // --- enqueue script --- - wp_enqueue_script( $scriptkey, $url, $deps, $version, $infooter ); - } -} - -// ------------------------- -// Enqueue Plugin Stylesheet -// ------------------------- -// ?.?.?: widgets.css style conditional enqueueing moved to within widget classes -// 2.3.0: added abstracted method for enqueueing plugin stylesheets -// 2.3.0: moved master schedule style enqueueing to conditional in master-schedule.php -function radio_station_enqueue_style( $stylekey ) { - - // --- check style enqueued switch --- - global $radio_station_styles; - if ( !isset( $radio_station_styles ) ) { - $radio_station_styles = array(); - } - if ( !isset( $radio_station_styles[$stylekey] ) ) { - - // --- set stylesheet filename and child theme path --- - $filename = 'rs-' . $stylekey . '.css'; - - // 2.3.0: check template hierarchy for file - $template = radio_station_get_template( 'both', $filename, 'css' ); - if ( $template ) { - - // --- use found template values --- - // 2.3.2: use plugin version for releases - $plugin_version = radio_station_plugin_version(); - $version_length = strlen( $plugin_version ); - // TODO: maybe allow for minor version release numbers ? - // if ( ( 5 == $version_length ) || ( 7 == $version_length ) ) { - if ( 5 == $version_length ) { - $version = $plugin_version; - } else { - $version = filemtime( $template['file'] ); - } - $url = $template['url']; - - // --- enqueue styles in footer --- - wp_enqueue_style( 'rs-' . $stylekey, $url, array(), $version, 'all' ); - - // --- set style enqueued switch --- - $radio_station_styles[$stylekey] = true; - } - } -} - -// ------------------ -// Enqueue Datepicker -// ------------------ -// 2.3.0: enqueued separately by override post type only -// 2.3.3.9: moved here from radio-station-admin.php -function radio_station_enqueue_datepicker() { - - // --- enqueue jquery datepicker --- - wp_enqueue_script( 'jquery-ui-datepicker' ); - - // --- enqueue jquery datepicker styles --- - // 2.3.0: update theme styles from 1.8.2 to 1.12.1 - // 2.3.0: use local datepicker styles instead of via Google - // $protocol = 'http'; - // if ( is_ssl() ) {$protocol .= 's';} - // $url = $protocol . '://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'; - // wp_enqueue_style( 'jquery-ui-style', $url, array(), '1.12.1' ); - $style = radio_station_get_template( 'both', 'jquery-ui.css', 'css' ); - wp_enqueue_style( 'jquery-ui-smoothness', $style['url'], array(), '1.12.1', 'all' ); - -} - -// ------------------------------- -// Enqueue Localized Script Values -// ------------------------------- -add_action( 'wp_enqueue_scripts', 'radio_station_localize_script' ); -function radio_station_localize_script() { - - $js = radio_station_localization_script(); - wp_add_inline_script( 'radio-station', $js ); -} - -// ------------------- -// Localization Script -// ------------------- -// 2.3.3.9: separated script from enqueueing -function radio_station_localization_script() { - - // --- create settings objects --- - $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; - - // --- set AJAX URL --- - // 2.3.2: add admin AJAX URL - $js .= "radio.ajax_url = '" . esc_url( admin_url( 'admin-ajax.php' ) ) . "';" . PHP_EOL; - - // --- clock time format --- - // TODO: maybe set time format ? - // ref: https://devhints.io/wip/intl-datetime - $clock_format = radio_station_get_setting( 'clock_time_format' ); - $js .= "radio.clock_format = '" . esc_js( $clock_format ) . "';" . PHP_EOL; - - // --- detect touchscreens --- - // ref: https://stackoverflow.com/a/52855084/5240159 - $js .= "if (window.matchMedia('(pointer: coarse)').matches) {radio.touchscreen = true;} else {radio.touchscreen = false;}" . PHP_EOL; - - // --- set debug flag --- - if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { - $js .= "radio.debug = true;" . PHP_EOL; - } else { - $js .= "radio.debug = false;" . PHP_EOL; - } - - // --- radio timezone --- - // 2.3.2: added get timezone function - $timezone = radio_station_get_timezone(); - - if ( stristr( $timezone, 'UTC' ) ) { - - if ( 'UTC' == $timezone ) { - $offset = '0'; - } else { - $offset = str_replace( 'UTC', '', $timezone ); - } - $js .= "radio.timezone.offset = " . esc_js( $offset * 60 * 60 ) . "; "; - if ( '0' == $offset ) { - $offset = ''; - } elseif ( $offset > 0 ) { - $offset = '+' . $offset; - } - $js .= "radio.timezone.code = 'UTC" . esc_js( $offset ) . "'; "; - $js .= "radio.timezone.utc = '" . esc_js( $offset ) . "'; "; - $js .= "radio.timezone.utczone = true; "; - - } else { - - // --- get offset and code from timezone location --- - $datetimezone = new DateTimeZone( $timezone ); - $offset = $datetimezone->getOffset( new DateTime() ); - $offset_hours = $offset / ( 60 * 60 ); - if ( 0 == $offset ) { - $utc_offset = ''; - } elseif ( $offset > 0 ) { - $utc_offset = '+' . $offset_hours; - } else { - $utc_offset = $offset_hours; - } - $utc_offset = 'UTC' . $utc_offset; - $code = radio_station_get_timezone_code( $timezone ); - $js .= "radio.timezone.location = '" . esc_js( $timezone ) . "'; "; - $js .= "radio.timezone.offset = " . esc_js( $offset ) . "; "; - $js .= "radio.timezone.code = '" . esc_js( $code ) . "'; "; - $js .= "radio.timezone.utc = '" . esc_js( $utc_offset ) . "'; "; - $js .= "radio.timezone.utczone = false; "; - - } - - if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { - $js .= "radio.timezone.adjusted = false; "; - } else { - $js .= "radio.timezone.adjusted = true; "; - } - - // --- set user timezone offset --- - // (and convert offset minutes to seconds) - $js .= "radio.timezone.useroffset = (new Date()).getTimezoneOffset() * 60;" . PHP_EOL; - - // --- translated months array --- - // 2.3.2: also translate short month labels - $js .= "radio.labels.months = new Array("; - $short = "radio.labels.smonths = new Array("; - $months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ); - foreach ( $months as $i => $month ) { - $month = radio_station_translate_month( $month ); - $short_month = radio_station_translate_month( $month, true ); - $month = str_replace( "'", "", $month ); - $short_month = str_replace( "'", "", $short_month ); - $js .= "'" . esc_js( $month ) . "'"; - $short .= "'" . esc_js( $short_month ) . "'"; - if ( $i < ( count( $months ) - 1 ) ) { - $js .= ", "; - $short .= ", "; - } - } - $js .= ");" . PHP_EOL; - $js .= $short . ");" . PHP_EOL; - - // --- translated days array --- - // 2.3.2: also translate short day labels - $js .= "radio.labels.days = new Array("; - $short = "radio.labels.sdays = new Array("; - $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); - foreach ( $days as $i => $day ) { - $day = radio_station_translate_weekday( $day ); - $short_day = radio_station_translate_weekday( $day, true ); - $day = str_replace( "'", "", $day ); - $short_day = str_replace( "'", "", $short_day ); - $js .= "'" . esc_js( $day ) . "'"; - $short .= "'" . esc_js( $short_day ) . "'"; - if ( $i < ( count( $days ) - 1 ) ) { - $js .= ", "; - $short .= ", "; - } - } - $js .= ");" . PHP_EOL; - $js .= $short . ");" . PHP_EOL; - - // --- translated time unit strings --- - $js .= "radio.units.am = '" . esc_js( radio_station_translate_meridiem( 'am' ) ) . "'; "; - $js .= "radio.units.pm = '" . esc_js( radio_station_translate_meridiem( 'pm' ) ) . "'; "; - $js .= "radio.units.second = '" . esc_js( __( 'Second', 'radio-station' ) ) . "'; "; - $js .= "radio.units.seconds = '" . esc_js( __( 'Seconds', 'radio-station' ) ) . "'; "; - $js .= "radio.units.minute = '" . esc_js( __( 'Minute', 'radio-station' ) ) . "'; "; - $js .= "radio.units.minutes = '" . esc_js( __( 'Minutes', 'radio-station' ) ) . "'; "; - $js .= "radio.units.hour = '" . esc_js( __( 'Hour', 'radio-station' ) ) . "'; "; - $js .= "radio.units.hours = '" . esc_js( __( 'Hours', 'radio-station' ) ) . "'; "; - $js .= "radio.units.day = '" . esc_js( __( 'Day', 'radio-station' ) ) . "'; "; - $js .= "radio.units.days = '" . esc_js( __( 'Days', 'radio-station' ) ) . "'; " . PHP_EOL; - - // --- time key map --- - // 2.3.3.9: added for PHP Date Format to MomentJS conversions - // (object of approximate 'PHP date() key':'moment format() key' conversions) - $js .= "radio.moment_map = {'d':'D', 'j':'D', 'w':'e', 'D':'e', 'l':'e', 'N':'e', 'S':'Do', "; - $js .= "'F':'M', 'm':'M', 'n':'M', 'M':'M', 'Y':'YYYY', 'y':'YY',"; - $js .= "'a':'a', 'A':'a', 'g':'h', 'G':'H', 'g':'h', 'H':'H', 'i':'m', 's':'s'}" . PHP_EOL; - - // --- convert show times --- - // 2.3.3.9: - $usertimes = radio_station_get_setting( 'convert_show_times' ); - if ( 'yes' == $usertimes ) { - $js .= "radio.convert_show_times = true;" . PHP_EOL; - } else { - $js .= "radio.convert_show_times = false;" . PHP_EOL; - } - - // --- add inline script --- - $js = apply_filters( 'radio_station_localization_script', $js ); - return $js; - -} - -// ------------------------- -// Filter for Streaming Data -// ------------------------- -// 2.3.3.7: added streaming data filter for player integration -add_filter( 'radio_station_player_data', 'radio_station_streaming_data' ); -function radio_station_streaming_data( $data, $station = false ) { - $data = array( - 'script' => radio_station_get_setting( 'player_script' ), - 'instance' => 0, - 'url' => radio_station_get_stream_url(), - 'format' => radio_station_get_setting( 'streaming_format' ), - 'fallback' => radio_station_get_fallback_url(), - 'fformat' => radio_station_get_setting( 'fallback_format' ), - ); - if ( RADIO_STATION_DEBUG ) { - echo 'Player Stream Data: ' . print_r( $data, true ) . ''; - } - $data = apply_filters( 'radio_station_streaming_data', $data, $station ); - return $data; -} - -// ----------------------------------------- -// Fix to Redirect Plugin Settings Menu Link -// ----------------------------------------- -// 2.3.2: added settings submenu page redirection fix -add_action( 'init', 'radio_station_settings_page_redirect' ); -function radio_station_settings_page_redirect() { - - // --- bug out if not admin page --- - if ( !is_admin() ) { - return; - } - - // --- but out if not plugin settings page --- - if ( !isset( $_REQUEST['page'] ) || ( 'radio-station' != $_REQUEST['page'] ) ) { - return; - } - - // --- check if link is for options-general.php --- - if ( strstr( $_SERVER['REQUEST_URI'], '/options-general.php' ) ) { - - // --- redirect to plugin settings page (admin.php) --- - $url = add_query_arg( 'page', 'radio-station', admin_url( 'admin.php' ) ); - wp_redirect( $url ); - exit; - } -} - -// ------------------------------------ -// Set Allowed Origins for Radio Player -// ------------------------------------ -// 2.3.3.9: added for embedded radio player control -add_filter( 'allowed_http_origins', 'radio_station_allowed_player_origins' ); -function radio_station_allowed_player_origins( $origins ) { - if ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) { - return $origins; - } - if ( !isset( $_REQUEST['action'] ) || ( 'radio_player' != $_REQUEST['action'] ) ) { - return $origins; - } - $allowed = array( 'https://netmix.com' ); - $allowed = apply_filters( 'radio_station_player_allowed_origins', $allowed ); - foreach ( $allowed as $allow ) { - $origins[] = $allow; - } - return $origins; -} - - -// ------------------------ -// === Template Filters === -// ------------------------ - -// -------------------- -// Doing Template Check -// -------------------- -// 2.3.3.9: added to help distinguish filter contexts -function radio_station_doing_template() { - global $radio_station_data; - if ( isset( $radio_station_data['doing-template'] ) && $radio_station_data['doing-template'] ) { - return true; - } - return false; -} - -// ------------ -// Get Template -// ------------ -// 2.3.0: added for template file hierarchy -function radio_station_get_template( $type, $template, $paths = false ) { - - global $radio_station_data; - - // --- maybe set default paths --- - if ( !$paths ) { - if ( isset( $radio_station_data['template-dirs'] ) ) { - $dirs = $radio_station_data['template-dirs']; - } - $paths = array( 'templates', '' ); - } elseif ( is_string( $paths ) ) { - if ( 'css' == $paths ) { - if ( isset( $radio_station_data['style-dirs'] ) ) { - $dirs = $radio_station_data['style-dirs']; - } - $paths = array( 'css', 'styles', '' ); - } elseif ( 'js' == $paths ) { - if ( isset( $radio_station_data['script-dirs'] ) ) { - $dirs = $radio_station_data['script-dirs']; - } - $paths = array( 'js', 'scripts', '' ); - } - } - - if ( !isset( $dirs ) ) { - $dirs = array(); - $styledir = get_stylesheet_directory(); - $styledirurl = get_stylesheet_directory_uri(); - $templatedir = get_template_directory(); - $templatedirurl = get_template_directory_uri(); - - // --- maybe generate default hierarchies --- - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => $styledir . '/' . $path, - 'urlpath' => $styledirurl . '/' . $path, - ); - } - if ( $styledir != $templatedir ) { - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => $templatedir . '/' . $path, - 'urlpath' => $templatedirurl . '/' . $path, - ); - } - } - if ( defined( 'RADIO_STATION_PRO_DIR' ) ) { - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => RADIO_STATION_PRO_DIR . '/' . $path, - 'urlpath' => plugins_url( $path, RADIO_STATION_PRO_FILE ), - ); - } - } - foreach ( $paths as $path ) { - $dirs[] = array( - 'path' => RADIO_STATION_DIR . '/' . $path, - 'urlpath' => plugins_url( $path, RADIO_STATION_FILE ), - ); - } - } - $dirs = apply_filters( 'radio_station_template_dir_hierarchy', $dirs, $template, $paths ); - - // --- loop directory hierarchy to find first template --- - foreach ( $dirs as $dir ) { - - // 2.3.4: use trailingslashit to account for empty paths - $template_path = trailingslashit( $dir['path'] ) . $template; - $template_url = trailingslashit( $dir['urlpath'] ) . $template; - - if ( file_exists( $template_path ) ) { - if ( 'file' == (string) $type ) { - return $template_path; - } elseif ( 'url' === (string) $type ) { - return $template_url; - } else { - return array( 'file' => $template_path, 'url' => $template_url ); - } - } - } - - return false; -} - -// ------------------------------------- -// Station Phone Number for Shows Filter -// ------------------------------------- -// 2.3.3.6: added to return station phone for all Shows (if not set for Show) -add_filter( 'radio_station_show_phone', 'radio_station_phone_number', 10, 2 ); -function radio_station_phone_number( $phone, $post_id ) { - if ( $phone ) { - return $phone; - } - $shows_phone = radio_station_get_setting( 'shows_phone' ); - if ( 'yes' == $shows_phone ) { - $phone = radio_station_get_setting( 'station_phone' ); - return $phone; - } - return false; -} - -// -------------------------------------- -// Station Email Address for Shows Filter -// -------------------------------------- -// 2.3.3.8: added to return station email for all Shows (if not set for Show) -add_filter( 'radio_station_show_email', 'radio_station_email_address', 10, 2 ); -function radio_station_email_address( $email, $post_id ) { - if ( $email ) { - return $email; - } - $shows_email = radio_station_get_setting( 'shows_email' ); - if ( 'yes' == $shows_email ) { - $email = radio_station_get_setting( 'station_email' ); - return $email; - } - return false; -} - -// ------------------------------ -// Automatic Pages Content Filter -// ------------------------------ -// 2.3.0: standalone filter for automatic page content -// 2.3.1: re-add filter so the_content can be processed multiple times -// 2.3.3.6: set automatic content early and clear existing content -add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); -function radio_station_automatic_pages_content_set( $content ) { - - global $radio_station_data; - - // if ( isset( $radio_station_data['doing_excerpt'] ) && $radio_station_data['doing_excerpt'] ) { - // return $content; - // } - - // --- for automatic output on selected master schedule page --- - $schedule_page = radio_station_get_setting( 'schedule_page' ); - if ( !is_null( $schedule_page ) && !empty( $schedule_page ) ) { - if ( is_page( $schedule_page ) ) { - $automatic = radio_station_get_setting( 'schedule_auto' ); - if ( 'yes' === (string) $automatic ) { - $view = radio_station_get_setting( 'schedule_view' ); - $atts = array( 'view' => $view ); - $atts = apply_filters( 'radio_station_automatic_schedule_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[master-schedule' . $atts_string . ']'; - } - } - } - - // --- show archive page --- - // 2.3.0: added automatic display of show archive page - $show_archive_page = radio_station_get_setting( 'show_archive_page' ); - if ( !is_null( $show_archive_page ) && !empty( $show_archive_page ) ) { - if ( is_page( $show_archive_page ) ) { - $automatic = radio_station_get_setting( 'show_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'show_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_show_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[shows-archive' . $atts_string . ']'; - } - } - } - - // --- override archive page --- - // 2.3.0: added automatic display of override archive page - $override_archive_page = radio_station_get_setting( 'override_archive_page' ); - if ( !is_null( $override_archive_page ) && !empty( $override_archive_page ) ) { - if ( is_page( $override_archive_page ) ) { - $automatic = radio_station_get_setting( 'override_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'override_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_override_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[overrides-archive' . $atts_string . ']'; - } - } - } - - // --- playlist archive page --- - // 2.3.0: added automatic display of playlist archive page - $playlist_archive_page = radio_station_get_setting( 'playlist_archive_page' ); - if ( !is_null( $playlist_archive_page ) && !empty( $playlist_archive_page ) ) { - if ( is_page( $playlist_archive_page ) ) { - $automatic = radio_station_get_setting( 'playlist_archive_auto' ); - if ( 'yes' == $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'playlist_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_playlist_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[playlists-archive' . $atts_string . ']'; - } - } - } - - // --- genre archive page --- - // 2.3.0: added automatic display of genre archive page - $genre_archive_page = radio_station_get_setting( 'genre_archive_page' ); - if ( !is_null( $genre_archive_page ) && !empty( $genre_archive_page ) ) { - if ( is_page( $genre_archive_page ) ) { - $automatic = radio_station_get_setting( 'genre_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'genre_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_genre_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[genres-archive' . $atts_string. ']'; - } - } - } - - // --- languages archive page --- - // 2.3.3.9: added automatic display of language archive page - $language_archive_page = radio_station_get_setting( '' ); - if ( !is_null( $language_archive_page ) && !empty( $language_archive_page ) ) { - if ( is_page( $language_archive_page ) ) { - $automatic = radio_station_get_setting( 'language_archive_auto' ); - if ( 'yes' === (string) $automatic ) { - $atts = array(); - // $view = radio_station_get_setting( 'language_archive_view' ); - // if ( $view ) { - // $atts['view'] = $view; - // } - $atts = apply_filters( 'radio_station_automatic_languagee_archive_atts', $atts ); - $atts_string = ''; - if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { - foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; - } - } - $shortcode = '[languages-archive' . $atts_string. ']'; - } - } - } - - // 2.3.3.6: moved out to reduce repetitive code - if ( isset( $shortcode ) ) { - remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); - remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); - $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); - // 2.3.1: re-add filter so the_content may be processed multuple times - add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); - add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); - // 2.3.3.6: clear existing content to allow for interim filters - $content = ''; - } - - return $content; -} - -// ---------------------------------- -// Automatic Pages Content Set Filter -// ---------------------------------- -// 2.3.3.6: append existing automatic page content to allow for interim filters -add_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); -function radio_station_automatic_pages_content_get( $content ) { - global $radio_station_data; - if ( isset( $radio_station_data['automatic_content'] ) ) { - $content .= $radio_station_data['automatic_content']; - } - return $content; -} - - -// ------------------------------ -// Single Content Template Filter -// ------------------------------ -// 2.3.0: moved here and abstracted from templates/single-show.php -// 2.3.0: standalone filter name to allow for replacement -function radio_station_single_content_template( $content, $post_type ) { - - // --- check if single plugin post type --- - if ( !is_singular( $post_type ) ) { - return $content; - } - - // --- check for user content templates --- - // 2.3.3.9: allow for prefixed and unprefixed post types - $theme_dir = get_stylesheet_directory(); - $templates = array(); - $templates[] = $theme_dir . '/templates/single-' . $post_type . '-content.php'; - $templates[] = $theme_dir . '/single-' . $post_type . '-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-' . $post_type . '-content.php'; - $unprefixed_post_type = str_replace( 'rs-', '', $post_type ); - if ( $post_type != $unprefixed_post_type ) { - $templates[] = $theme_dir . '/templates/single-' . $unprefixed_post_type . '-content.php'; - $templates[] = $theme_dir . '/single-' . $unprefixed_post_type . '-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-' . $unprefixed_post_type . '-content.php'; - } - - // 2.3.0: fallback to show content template for overrides - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - // $templates[] = $theme_dir . '/templates/single-rs-show-content.php'; - // $templates[] = $theme_dir . '/single-rs-show-content.php'; - // $templates[] = RADIO_STATION_DIR . '/templates/single-rs-show-content.php'; - $templates[] = $theme_dir . '/templates/single-show-content.php'; - $templates[] = $theme_dir . '/single-show-content.php'; - $templates[] = RADIO_STATION_DIR . '/templates/single-show-content.php'; - } - $templates = apply_filters( 'radio_station_' . $post_type . '_content_templates', $templates, $post_type ); - foreach ( $templates as $template ) { - if ( file_exists( $template ) ) { - $content_template = $template; - break; - } - } - if ( !isset( $content_template ) ) { - return $content; - } - - // --- enqueue template styles --- - // 2.3.3.9: check post type for page template style enqueue - $page_templates = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - if ( in_array( $post_type, $page_templates ) ) { - radio_station_enqueue_style( 'templates' ); - } - // 2.3.3.9: fire action for enqueueing other template styles - do_action( 'radio_station_enqueue_template_styles', $post_type ); - - // --- enqueue dashicons for frontend --- - wp_enqueue_style( 'dashicons' ); - - // --- filter post before including template --- - global $post; - $original_post = $post; - $post = apply_filters( 'radio_station_single_template_post_data', $post, $post_type ); - - // --- start buffer and include content template --- - ob_start(); - include $content_template; - $output = ob_get_contents(); - ob_end_clean(); - - // --- restore post global to be safe --- - $post = $original_post; - - // --- filter and return buffered content --- - $output = str_replace( '', $content, $output ); - $post_id = get_the_ID(); - $output = apply_filters( 'radio_station_content_' . $post_type, $output, $post_id ); - - return $output; -} - -// ------------------------------------ -// Filter for Override Show Linked Data -// ------------------------------------ -add_filter( 'radio_station_single_template_post_data', 'radio_station_override_linked_show_data', 10, 2 ); -function radio_station_override_linked_show_data( $post, $post_type ) { - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); - if ( $linked_id ) { - $show_post = get_post( $linked_id ); - if ( $show_post ) { - $linked_fields = get_post_meta( $post->ID, 'linked_show_fields', true ); - if ( $linked_fields ) { - foreach ( $linked_fields as $key => $switch ) { - if ( !$switch ) { - if ( 'show_title' == $key ) { - $post->post_title = $show_post->post_title; - } elseif ( 'show_excerpt' == $key ) { - $post->post_excerpt = $show_post->post_excerpt; - } elseif ( 'show_content' == $key ) { - $post->post_content = $show_post->post_content; - } - } - } - } - } - } - } - return $post; -} - -// ---------------------------- -// Show Content Template Filter -// ---------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_show_content_template', 11 ); -function radio_station_show_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_show_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_SHOW_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multuple times - add_filter( 'the_content', 'radio_station_show_content_template', 11 ); - return $output; -} - -// -------------------------------- -// Playlist Content Template Filter -// -------------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); -function radio_station_playlist_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_PLAYLIST_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multuple times - add_filter( 'the_content', 'radio_station_playlist_content_template', 11 ); - return $output; -} - -// -------------------------------- -// Override Content Template Filter -// -------------------------------- -// 2.3.0: standalone filter name to allow for replacement -add_filter( 'the_content', 'radio_station_override_content_template', 11 ); -function radio_station_override_content_template( $content ) { - remove_filter( 'the_content', 'radio_station_override_content_template', 11 ); - $output = radio_station_single_content_template( $content, RADIO_STATION_OVERRIDE_SLUG ); - // 2.3.1: re-add filter so the_content can be processed multiple times - add_filter( 'the_content', 'radio_station_override_content_template', 11 ); - return $output; -} - -// ---------------------------------- -// Override Content with Show Content -// ---------------------------------- -// 2.3.3.9: maybe use show content for override content -add_filter( 'the_content', 'radio_station_override_content', 0 ); -function radio_station_override_content( $content ) { - if ( !is_singular( RADIO_STATION_OVERRIDE_SLUG ) ) { - return $content; - } - remove_filter( 'the_content', 'radio_station_override_content', 0 ); - global $post; - $override = radio_station_get_show_override( $post->ID, 'show_content' ); - if ( false !== $override ) { - $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); - $content = $override->post_content; - } - add_filter( 'the_content', 'radio_station_override_content', 0 ); - return $content; -} - -// --------------------------------- -// DJ / Host / Producer Template Fix -// --------------------------------- -// 2.2.8: temporary fix to not 404 author pages for DJs without blog posts -// Ref: https://wordpress.org/plugins/show-authors-without-posts/ -add_filter( '404_template', 'radio_station_author_host_pages' ); -function radio_station_author_host_pages( $template ) { - - global $wp_query; - if ( !is_author() ) { - - if ( get_query_var( 'host' ) ) { - - // --- get user by ID or name --- - $host = get_query_var( 'host' ); - if ( absint( $host ) > - 1 ) { - $user = get_user_by( 'ID', $host ); - } else { - $user = get_user_by( 'slug', $host ); - } - - // --- check if specified user has DJ/host role --- - if ( $user && in_array( 'dj', $user->roles ) ) { - $host_template = radio_station_get_host_template(); - if ( $host_template ) { - $template = $host_template; - } - } - - } elseif ( get_query_var( 'producer' ) ) { - - // --- get user by ID or name --- - $producer = get_query_var( 'producer' ); - if ( absint( $producer ) > - 1 ) { - $user = get_user_by( 'ID', $producer ); - } else { - $user = get_user_by( 'slug', $producer ); - } - - // --- check if specified user has producer role --- - if ( $user && in_array( 'producer', $user->roles ) ) { - $producer_template = radio_station_get_producer_template(); - if ( $producer_template ) { - $template = $producer_template; - } - } - - } elseif ( get_query_var( 'author' ) && ( 0 == $wp_query->posts->post ) ) { - - // --- get the author user --- - if ( get_query_var( 'author_name' ) ) { - $author = get_user_by( 'slug', get_query_var( 'author_name' ) ); - } else { - $author = get_userdata( get_query_var( 'author' ) ); - } - - if ( $author ) { - - // --- check if author has DJ, producer or administrator role --- - if ( in_array( 'dj', $author->roles ) - || in_array( 'producer', $author->roles ) - || in_array( 'administrator', $author->roles ) ) { - - // TODO: maybe check if user is assigned to any shows ? - $template = get_author_template(); - } - } - - } - - } - - return $template; -} - -// ---------------------- -// Get DJ / Host Template -// ---------------------- -// 2.3.0: added get DJ template function -// (modified template hierarchy from get_page_template) -function radio_station_get_host_template() { - - $templates = array(); - $hostname = get_query_var( 'host' ); - if ( $hostname ) { - $hostname_decoded = urldecode( $hostname ); - if ( $hostname_decoded !== $hostname ) { - $templates[] = 'host-' . $hostname_decoded . '.php'; - } - $templates[] = 'host-' . $hostname . '.php'; - } - $templates[] = 'single-host.php'; - - $templates = apply_filters( 'radio_station_host_templates', $templates ); - - return get_query_template( RADIO_STATION_HOST_SLUG, $templates ); -} - -// --------------------- -// Get Producer Template -// --------------------- -// 2.3.0: added get producer template function -// (modified template hierarchy from get_page_template) -function radio_station_get_producer_template() { - - $templates = array(); - $producername = get_query_var( 'producer' ); - if ( $producername ) { - $producername_decoded = urldecode( $producername ); - if ( $producername_decoded !== $producername ) { - $templates[] = 'producer-' . $producername_decoded . '.php'; - } - $templates[] = 'producer-' . $producername . '.php'; - } - $templates[] = 'single-producer.php'; - - $templates = apply_filters( 'radio_station_producer_templates', $templates ); - - return get_query_template( RADIO_STATION_PRODUCER_SLUG, $templates ); -} - -// ------------------------- -// Single Template Hierarchy -// ------------------------- -function radio_station_single_template_hierarchy( $templates ) { - - global $post; - - // --- remove single.php as the show / playlist fallback --- - // (allows for user selection of page.php or single.php later) - if ( ( RADIO_STATION_SHOW_SLUG === (string) $post->post_type ) - || ( RADIO_STATION_OVERRIDE_SLUG === (string) $post->post_type ) - || ( RADIO_STATION_PLAYLIST_SLUG === (string) $post->post_type ) ) { - $i = array_search( 'single.php', $templates ); - if ( false !== $i ) { - unset( $templates[$i] ); - } - } - - return $templates; -} - -// ----------------------- -// Single Templates Loader -// ----------------------- -add_filter( 'single_template', 'radio_station_load_template', 10, 3 ); -function radio_station_load_template( $single_template, $type, $templates ) { - - global $post; - - // --- handle single templates --- - $post_type = $post->post_type; - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_PLAYLIST_SLUG ); - // TODO: RADIO_STATION_EPISODE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG - if ( in_array( $post_type, $post_types ) ) { - - // --- check for existing template override --- - // note: single.php is removed from template hierarchy via filter - remove_filter( 'single_template', 'radio_station_load_template' ); - add_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); - $template = get_single_template(); - remove_filter( 'single_template_hierarchy', 'radio_station_single_template_hierarchy' ); - - // --- use legacy template --- - if ( $template ) { - - // --- use the found user template --- - $single_template = $template; - - // --- check for combined template and content filter --- - $combined = radio_station_get_setting( $post_type . '_template_combined' ); - if ( 'yes' != $combined ) { - remove_filter( 'the_content', 'radio_station_' . $post_type . '_content_template', 11 ); - } - - } else { - - // --- get template selection --- - // 2.3.0: removed default usage of single show/playlist templates (not theme agnostic) - // 2.3.0: added option for use of template hierarchy - $show_template = radio_station_get_setting( $post_type . '_template' ); - - // --- maybe use legacy template --- - if ( 'legacy' === (string) $show_template ) { - return RADIO_STATION_DIR . '/templates/legacy/single-' . $post_type . '.php'; - } - - // --- use post or page template --- - // 2.3.3.8: added missing singular.php template setting - if ( 'post' == $show_template ) { - $templates = array( 'single.php' ); - } elseif ( 'page' == $show_template ) { - $templates = array( 'page.php' ); - } elseif ( 'singular' == $show_template ) { - $template = array( 'singular.php' ); - } - - // --- add standard fallbacks to index --- - // 2.3.3.8: remove singular fallback as it is explicitly chosen - $templates[] = 'index.php'; - $single_template = get_query_template( $post_type, $templates ); - } - } - - return $single_template; -} - -// -------------------------- -// Archive Template Hierarchy -// -------------------------- -add_filter( 'archive_template_hierarchy', 'radio_station_archive_template_hierarchy' ); -function radio_station_archive_template_hierarchy( $templates ) { - - // --- add extra template search path of /templates/ --- - $post_types = array_filter( (array) get_query_var( 'post_type' ) ); - if ( count( $post_types ) == 1 ) { - $post_type = reset( $post_types ); - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); - if ( in_array( $post_type, $post_types ) ) { - $template = array( 'templates/archive-' . $post_type . '.php' ); - // 2.3.0: add fallback to show archive template for overrides - if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { - $template[] = 'templates/archive-' . RADIO_STATION_SHOW_SLUG . '.php'; - } - $templates = array_merge( $template, $templates ); - } - } - - return $templates; -} - -// ------------------------ -// Archive Templates Loader -// ------------------------ -// TODO: implement standard archive page overrides via plugin settings -// add_filter( 'archive_template', 'radio_station_post_type_archive_template', 10, 3 ); -function radio_station_post_type_archive_template( $archive_template, $type, $templates ) { - global $post; - - // --- check for archive template override --- - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG ); - foreach ( $post_types as $post_type ) { - if ( is_post_type_archive( $post_type ) ) { - $override = radio_station_get_setting( $post_type . '_archive_override' ); - if ( 'yes' !== (string) $override ) { - $archive_template = get_page_template(); - add_filter( 'the_content', 'radio_station_' . $post_type . '_archive', 11 ); - } - } - } - - return $archive_template; -} - -// ------------------------- -// Add Links to Back to Show -// ------------------------- -// 2.3.0: add links to show from show posts and playlists -// 2.3.3.6: allow for multiple related show post assignments -add_filter( 'the_content', 'radio_station_add_show_links', 20 ); -function radio_station_add_show_links( $content ) { - - global $post; - - // note: playlists are linked via single-playlist-content.php template - - // --- filter to allow related post types --- - $post_type = $post->post_type; - $post_types = array( 'post' ); - $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); - - if ( in_array( $post_type, $post_types ) ) { - - // --- link show posts --- - $related_shows = get_post_meta( $post->ID, 'post_showblog_id', true ); - // 2.3.3.6: convert string value if not multiple - if ( $related_shows && !is_array( $related_shows ) ) { - $related_shows = array( $related_shows ); - } - // 2.3.3.6: remove possible zero values - // 2.3.3.7: added count check for before looping - if ( $related_shows && ( count( $related_shows ) > 0 ) ) { - foreach ( $related_shows as $i => $related_show ) { - if ( 0 == $related_show ) { - unset( $related_shows[$i] ); - } - } - } - if ( $related_shows && is_array( $related_shows ) && ( count( $related_shows ) > 0 ) ) { - - $positions = array( 'after' ); - $positions = apply_filters( 'radio_station_link_to_show_positions', $positions, $post_type, $post ); - if ( $positions && is_array( $positions ) && ( count( $positions ) > 0 ) ) { - if ( in_array( 'before', $positions ) || in_array( 'after', $positions ) ) { - - // --- set related shows link(s) --- - // 2.3.3.6: get all related show links - $show_links = ''; - $hash_ref = '#show-' . str_replace( 'rs-', '', $post_type ) . 's'; - foreach ( $related_shows as $related_show ) { - $show = get_post( $related_show ); - $title = $show->post_title; - $permalink = get_permalink( $show->ID ) . $hash_ref; - if ( '' != $show_links ) { - $show_links .= ', '; - } - $show_links .= '' . esc_html( $title ) . ''; - } - - // --- set post type labels --- - $before = $after = ''; - $post_type_object = get_post_type_object( $post_type ); - $singular = $post_type_object->labels->singular_name; - $plural = $post_type_object->labels->name; - - // --- before content links --- - if ( in_array( 'before', $positions ) ) { - if ( count( $related_shows ) > 1 ) { - $label = sprintf( __( '%s for Shows', 'radio-station' ), $singular ); - } else { - $label = sprintf( __( '%s for Show', 'radio-station' ), $singular ); - } - $before = $label . ': ' . $show_links . '

    '; - $before = apply_filters( 'radio_station_link_to_show_before', $before, $post, $related_shows ); - } - - // --- after content links --- - if ( in_array( 'after', $positions ) ) { - if ( count( $related_shows ) > 1 ) { - $label = sprintf( __( 'More %s for Shows', 'radio-station' ), $plural ); - } else { - $label = sprintf( __( 'More %s for Show', 'radio-station' ), $plural ); - } - $after = '
    ' . $label . ': ' . $show_links; - $after = apply_filters( 'radio_station_link_to_show_after', $after, $post, $related_shows ); - } - $content = $before . $content . $after; - } - } - } - - } - - // --- adjacent post links debug output --- - if ( RADIO_STATION_DEBUG ) { - $content .= 'Previous Post Link: ' . get_previous_post_link() . '' . PHP_EOL; - $content .= 'Next Post Link: ' . get_next_post_link() . '' . PHP_EOL; - } - - return $content; -} - -// ------------------------- -// Show Posts Adjacent Links -// ------------------------- -// 2.3.0: added show post adjacent links filter -add_filter( 'next_post_link', 'radio_station_get_show_post_link', 11, 5 ); -add_filter( 'previous_post_link', 'radio_station_get_show_post_link', 11, 5 ); -function radio_station_get_show_post_link( $output, $format, $link, $adjacent_post, $adjacent ) { - - global $radio_station_data, $post; - - // --- filter next and previous Show links --- - // 2.3.4: add filtering for adjacent show links - $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); - if ( in_array( $post->post_type, $post_types ) ) { - if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { - // 2.3.3.6: get next/previous Show for override date/time - // 2.3.3.9: modified to handle multiple override times - // 2.3.3.9: added check that schedule key is set - $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); - if ( $scheds && is_array( $scheds ) ) { - if ( array_key_exists( 'date', $scheds ) ) { - $sched = array( $scheds ); - } - $now = time(); - foreach ( $scheds as $sched ) { - $override_start = $sched['date'] . ' ' . $sched['start_hour'] . ':' . $sched['start_min'] . ' ' . $sched['start_meridian']; - $override_time = radio_station_get_time( ( $override_start + 1 ) ); - if ( !isset( $time ) ) { - $time = $override_time; - } elseif ( ( $time < $now ) && ( $override_time > $now ) ) { - $time = $override_time; - } - } - if ( 'next' == $adjacent ) { - $show = radio_station_get_next_show( $time ); - } elseif ( 'previous' == $adjacent ) { - $show = radio_station_get_previous_show( $time ); - } - } - } else { - $shifts = get_post_meta( $post->ID, 'show_sched', true ); - if ( $shifts && is_array( $shifts ) ) { - if ( count( $shifts ) < 1 ) { - // 2.3.3.6: default to standard adjacent post link - return $output; - } - if ( 1 == count( $shifts ) ) { - $shift = $shifts[0]; - $shift_start = $shift['day'] . ' ' . $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - // 2.3.3.9: fix to put addition outside bracket - $time = radio_station_get_time( $shift_start ) + 1; - if ( 'next' == $adjacent ) { - $show = radio_station_get_next_show( $time ); - } elseif ( 'previous' == $adjacent ) { - $show = radio_station_get_previous_show( $time ); - } - } else { - // 2.3.3.6: added method for Show with multiple shifts - $now = radio_station_get_now(); - $show_shifts = radio_station_get_current_schedule(); - if ( !$show_shifts ) { - return $output; - } - - // --- get upcoming shift for Show --- - $next_shift = false; - foreach ( $show_shifts as $day => $day_shifts ) { - foreach ( $day_shifts as $day_shift ) { - if ( !$next_shift && ( $day_shift['show']['id'] == $post->ID ) ) { - if ( !isset( $last_shift ) ) { - $last_shift = $day_shift; - } - $start = $day_shift['date'] . ' ' . $day_shift['start']; - $start_time = radio_station_to_time( $start ); - $end = $day_shift['date'] . ' ' . $day_shift['end']; - $end_time = radio_station_to_time( $end ); - if ( ( $start_time > $now ) || ( $now < $end_time ) ) { - $next_shift = $day_shift; - } - } - } - } - if ( !$next_shift ) { - $next_shift = $last_shift; - } - // echo "Next Show Shift: " . print_r( $next_shift, true ); - - // --- reverse order for finding previous show shift --- - if ( 'previous' == $adjacent ) { - foreach ( $show_shifts as $day => $day_shifts ) { - $show_shifts[$day] = array_reverse( $day_shifts, true ); - } - $show_shifts = array_reverse( $show_shifts, true ); - } - - // --- loop shifts to find adjacent shift's Show --- - $found = false; - foreach ( $show_shifts as $day => $day_shifts ) { - foreach ( $day_shifts as $day_shift ) { - if ( !isset( $first_shift ) && ( $day_shift['show']['id'] != $post->ID ) ) { - $first_shift = $day_shift; - } - // echo "Shift: " . print_r( $day_shift, true ) . PHP_EOL; - if ( !isset( $show ) ) { - if ( $found && ( $day_shift['show']['id'] != $post->ID ) ) { - $show = $day_shift['show']; - } elseif ( !$found ) { - if ( $next_shift == $day_shift ) { - $found = true; - } - } - } - } - } - if ( !isset( $show ) && isset( $first_shift ) ) { - $show = $first_shift['show']; - } - } - } - } - - // --- generate adjacent Show link --- - if ( isset( $show ) ) { - if ( 'next' == $adjacent ) { - $rel = 'next'; - } elseif ( 'previous' == $adjacent ) { - $rel = 'prev'; - } - $adjacent_post = get_post( $show['id'] ); - - // --- adjacent post title --- - // 2.4.0.3: added fix for missing post title - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; - } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); - } - - return $output; - } - - // --- filter to allow related post types --- - $related_post_types = array( 'post' ); - $show_post_types = apply_filters( 'radio_station_show_related_post_types', $related_post_types ); - if ( in_array( $post->post_type, $related_post_types ) ) { - - // --- filter to allow disabling --- - $link_show_posts = apply_filters( 'radio_station_link_show_posts', true, $post ); - if ( !$link_show_posts ) { - return $output; - } - - // --- get related show --- - $related_show = get_post_meta( $post->ID, 'post_showblog_id', true ); - if ( !$related_show ) { - return $output; - } - if ( is_array( $related_show ) ) { - $related_shows = $related_show; - } else { - $related_shows = array( $related_show ); - } - // 2.3.3.6: remove possible saved zero value - foreach ( $related_shows as $i => $related_show ) { - if ( 0 == $related_show ) { - unset( $related_shows[$i] ); - } - } - if ( 0 == count( $related_shows ) ) { - return $output; - } - if ( RADIO_STATION_DEBUG ) { - echo 'Related Shows A: ' . print_r( $related_shows, true ) . ''; - } - - // --- get more Shows related to this related Post --- - // 2.3.3.6: allow for multiple related posts - // 2.3.3.9: added 'i:' prefix to LIKE vlaue matches - global $wpdb; - $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" - . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; - if ( count( $related_shows ) > 1 ) { - foreach ( $related_show as $i => $show_id ) { - if ( $i > 0 ) { - $query .= " OR meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $show_id . "%'"; - } - } - } - $results = $wpdb->get_results( $query, ARRAY_A ); - if ( RADIO_STATION_DEBUG ) { - echo 'Related Shows B: ' . print_r( $results, true ) . ''; - } - if ( !$results || !is_array( $results ) || ( count( $results ) < 1 ) ) { - return $output; - } - $related_posts = array(); - foreach ( $results as $result ) { - $values = maybe_unserialize( $result['meta_value'] ); - if ( RADIO_STATION_DEBUG ) { - echo 'Post ' . $result['post_id'] . ' Related Show Values : ' . print_r( $values, true ) . ''; - } - // --- double check Show ID is actually a match --- - if ( ( $result['meta_value'] == $related_show ) || ( is_array( $values ) && array_intersect( $related_shows, $values ) ) ) { - // --- recheck post is of the same post type --- - $query = "SELECT post_type FROM " . $wpdb->prefix . "posts WHERE ID = %d"; - $query = $wpdb->prepare( $query, $result['post_id'] ); - $related_post_type = $wpdb->get_var( $query ); - if ( $related_post_type == $post->post_type ) { - $related_posts[] = $result['post_id']; - } - } - } - if ( RADIO_STATION_DEBUG ) { - echo 'Related Posts B: ' . print_r( $related_posts, true ) . ''; - } - if ( 0 == count( $related_posts ) ) { - return $output; - } - - // --- get adjacent post query --- - // 2.3.3.6: use post__in related post array instead of meta_query - $args = array( - 'post_type' => $post->post_type, - 'posts_per_page' => 1, - 'orderby' => 'post_modified', - 'post__in' => $related_posts, - 'ignore_sticky_posts' => true, - ); - - // --- setup for previous or next post --- - // 2.3.3.6: set date_query instead of meta_query - $post_type_object = get_post_type_object( $post->post_type ); - if ( 'previous' == $adjacent ) { - $args['order'] = 'DESC'; - $args['date_query'] = array( array( 'before' => $post->post_date ) ); - $rel = 'prev'; - $title = __( 'Previous Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - } elseif ( 'next' == $adjacent ) { - $args['order'] = 'ASC'; - $args['date_query'] = array( array( 'after' => $post->post_date ) ); - $rel = 'next'; - $title = __( 'Next Related Show', 'radio-station' ) . ' ' . $post_type_object->labels->singular_name; - } - - // --- get the adjacent post --- - // 2.3.3.6: use date_query instead of looping posts - $show_posts = get_posts( $args ); - if ( RADIO_STATION_DEBUG ) { - echo 'Related Posts Args: ' . print_r( $args, true ) . ''; - } - if ( 0 == count( $show_posts ) ) { - return $output; - } - $adjacent_post = $show_posts[0]; - if ( RADIO_STATION_DEBUG ) { - echo 'Related Adjacent Post: ' . print_r( $adjacent_post, true ) . ''; - } - - // --- adjacent post title --- - $post_title = $adjacent_post->post_title; - if ( empty( $adjacent_post->post_title ) ) { - $post_title = $title; - } - $post_title = apply_filters( 'the_title', $post_title, $adjacent_post->ID ); - - // --- adjacent post link --- - // (from function get_adjacent_post_link) - $date = mysql2date( get_option( 'date_format' ), $adjacent_post->post_date ); - $string = ''; - $inlink = str_replace( '%title', $post_title, $link ); - $inlink = str_replace( '%date', $date, $inlink ); - $inlink = $string . $inlink . ''; - $output = str_replace( '%link', $inlink, $format ); - - } - - return $output; -} - - -// ============= -// Query Filters -// ============= - -// ----------------------------- -// Playlist Archive Query Filter -// ----------------------------- -// 2.3.0: added to replace old archive template meta query -add_filter( 'pre_get_posts', 'radio_station_show_playlist_query' ); -function radio_station_show_playlist_query( $query ) { - - if ( RADIO_STATION_PLAYLIST_SLUG == $query->get( 'post_type' ) ) { - - // --- not needed if using legacy template --- - $styledir = get_stylesheet_directory(); - if ( file_exists( $styledir . '/archive-playlist.php' ) - || file_exists( $styledir . '/templates/archive-playlist.php' ) ) { - return; - } - // 2.3.0: also check in parent theme directory - $templatedir = get_template_directory(); - if ( $templatedir != $styledir ) { - if ( file_exists( $templatedir . '/archive-playlist.php' ) - || file_exists( $templatedir . '/templates/archive-playlist.php' ) ) { - return; - } - } - - // --- check if show ID or slug is set -- - // TODO: maybe use get_query_var here ? - if ( isset( $_GET['show_id'] ) ) { - $show_id = absint( $_GET['show_id'] ); - if ( $show_id < 0 ) { - unset( $show_id ); - } - } elseif ( isset( $_GET['show'] ) ) { - $show = sanitize_title( $_GET['show'] ); - global $wpdb; - $show_query = "SELECT ID FROM " . $wpdb->prefix . "posts WHERE post_type = '" . RADIO_STATION_SHOW_SLUG . "' AND post_name = %s"; - $show_query = $wpdb->prepare( $show_query, $show ); - $show_id = $wpdb->get_var( $show_query ); - if ( !$show_id ) { - unset( $show_id ); - } - } - - // --- maybe add the playlist meta query --- - if ( isset( $show_id ) ) { - $meta_query = array( - 'key' => 'playlist_show_id', - 'value' => $show_id, - ); - $query->set( $meta_query ); - } - } -} - - -// ------------------ -// === User Roles === -// ------------------ - -// -------------------------- -// Set Roles and Capabilities -// -------------------------- -if ( is_multisite() ) { - add_action( 'init', 'radio_station_set_roles', 10, 0 ); - // 2.3.1: added possible fix for roles not being set on multisite - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} else { - add_action( 'admin_init', 'radio_station_set_roles', 10, 0 ); -} -function radio_station_set_roles() { - - global $wp_roles; - - // --- set only necessary capabilities for DJs --- - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'read_shows' => true, - 'edit_playlists' => true, - 'edit_published_playlists' => true, - // by default DJs cannot edit others playlists - // 'edit_others_playlists' => false, - 'read_playlists' => true, - 'publish_playlists' => true, - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true, - ); - - // --- add the DJ role --- - // 2.3.0: translate DJ role name - // 2.3.0: change label from 'DJ' to 'DJ / Host' - // 2.3.0: check/add profile capabilities to hosts - $wp_roles->add_role( 'dj', __( 'DJ / Host', 'radio-station' ), $caps ); - $role_caps = $wp_roles->roles['dj']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $role_caps ) ) { - $role_caps = array(); - } - $host_caps = array( - 'edit_hosts', - 'edit_published_hosts', - 'delete_hosts', - 'read_hosts', - 'publish_hosts' - ); - foreach ( $host_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { - $wp_roles->add_cap( 'dj', $cap, true ); - } - } - // 2.3.3.9: fix for existing DJ role old name - $wp_roles->roles['dj']['name'] = __( 'DJ / Host', 'radio_station' ); - $wp_roles->role_names['dj'] = __( 'DJ / Host', 'radio_station' ); - - // --- add Show Producer role --- - // 2.3.0: add equivalent capability role for Show Producer - $wp_roles->add_role( 'producer', __( 'Show Producer', 'radio-station' ), $caps ); - $role_caps = $wp_roles->roles['producer']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $role_caps ) ) { - $role_caps = array(); - } - $producer_caps = array( - 'edit_producers', - 'edit_published_producers', - 'delete_producers', - 'read_producers', - 'publish_producers', - ); - foreach ( $producer_caps as $cap ) { - if ( !array_key_exists( $cap, $role_caps ) || !$role_caps[$cap] ) { - $wp_roles->add_cap( 'producer', $cap, true ); - } - } - - // --- grant all capabilities to Show Editors --- - // 2.3.0: set Show Editor role capabilities - $caps = array( - 'edit_shows' => true, - 'edit_published_shows' => true, - 'edit_others_shows' => true, - 'edit_private_shows' => true, - 'delete_shows' => true, - 'delete_published_shows' => true, - 'delete_others_shows' => true, - 'delete_private_shows' => true, - 'read_shows' => true, - 'publish_shows' => true, - - 'edit_playlists' => true, - 'edit_published_playlists' => true, - 'edit_others_playlists' => true, - 'edit_private_playlists' => true, - 'delete_playlists' => true, - 'delete_published_playlists' => true, - 'delete_others_playlists' => true, - 'delete_private_playlists' => true, - 'read_playlists' => true, - 'publish_playlists' => true, - - 'edit_overrides' => true, - 'edit_overrides_playlists' => true, - 'edit_others_overrides' => true, - 'edit_private_overrides' => true, - 'delete_overrides' => true, - 'delete_published_overrides' => true, - 'delete_others_overrides' => true, - 'delete_private_overrides' => true, - 'read_overrides' => true, - 'publish_overrides' => true, - - 'edit_hosts' => true, - 'edit_published_hosts' => true, - 'edit_others_hosts' => true, - 'delete_hosts' => true, - 'read_hosts' => true, - 'publish_hosts' => true, - - 'edit_producers' => true, - 'edit_published_producers' => true, - 'edit_others_producers' => true, - 'delete_producers' => true, - 'read_producers' => true, - 'publish_producers' => true, - - 'read' => true, - 'upload_files' => true, - 'edit_posts' => true, - 'edit_others_posts' => true, - 'edit_published_posts' => true, - 'publish_posts' => true, - 'delete_posts' => true, - ); - - // --- add the Show Editor role --- - // 2.3.0: added Show Editor role - $wp_roles->add_role( 'show-editor', __( 'Show Editor', 'radio-station' ), $caps ); - - // --- check plugin setting for authors --- - if ( radio_station_get_setting( 'add_author_capabilities' ) == 'yes' ) { - - // --- grant show edit capabilities to author users --- - $author_caps = $wp_roles->roles['author']['capabilities']; - // 2.3.1.1: added check if role caps is an array - if ( !is_array( $author_caps ) ) { - $author_caps = array(); - } - $extra_caps = array( - 'edit_shows', - 'edit_published_shows', - 'read_shows', - 'publish_shows', - - 'edit_playlists', - 'edit_published_playlists', - 'read_playlists', - 'publish_playlists', - - 'edit_overrides', - 'edit_published_overrides', - 'read_overrides', - 'publish_overrides', - ); - foreach ( $extra_caps as $cap ) { - if ( !array_key_exists( $cap, $author_caps ) || ( !$author_caps[$cap] ) ) { - $wp_roles->add_cap( 'author', $cap, true ); - } - } - } - - // --- specify edit caps (for editors and admins) --- - // 2.3.0: added show override, host and producer capabilities - $edit_caps = array( - 'edit_shows', - 'edit_published_shows', - 'edit_others_shows', - 'edit_private_shows', - 'delete_shows', - 'delete_published_shows', - 'delete_others_shows', - 'delete_private_shows', - 'read_shows', - 'publish_shows', - - 'edit_playlists', - 'edit_published_playlists', - 'edit_others_playlists', - 'edit_private_playlists', - 'delete_playlists', - 'delete_published_playlists', - 'delete_others_playlists', - 'delete_private_playlists', - 'read_playlists', - 'publish_playlists', - - 'edit_overrides', - 'edit_published_overrides', - 'edit_others_overrides', - 'edit_private_overrides', - 'delete_overrides', - 'delete_published_overrides', - 'delete_others_overrides', - 'delete_private_overrides', - 'read_overrides', - 'publish_overrides', - - 'edit_hosts', - 'edit_published_hosts', - 'edit_others_hosts', - 'delete_hosts', - 'delete_others_hosts', - 'read_hosts', - 'publish_hosts', - - 'edit_producers', - 'edit_published_producers', - 'edit_others_producers', - 'delete_producers', - 'delete_others_producers', - 'read_producers', - 'publish_producers', - ); - - // --- check plugin setting for editors --- - if ( radio_station_get_setting( 'add_editor_capabilities' ) == 'yes' ) { - - // --- grant show edit capabilities to editor users --- - $editor_caps = $wp_roles->roles['editor']['capabilities']; - // 2.3.1.1: added check if capabilities is an array - if ( !is_array( $editor_caps ) ) { - $editor_caps = array(); - } - foreach ( $edit_caps as $cap ) { - if ( !array_key_exists( $cap, $editor_caps ) || ( !$editor_caps[$cap] ) ) { - $wp_roles->add_cap( 'editor', $cap, true ); - } - } - } - - // --- grant all plugin capabilities to admin users --- - $admin_caps = $wp_roles->roles['administrator']['capabilities']; - // 2.3.1.1: added check if capabilities is an array - if ( !is_array( $admin_caps ) ) { - $admin_caps = array(); - } - foreach ( $edit_caps as $cap ) { - if ( !array_key_exists( $cap, $admin_caps ) || ( !$admin_caps[$cap] ) ) { - $wp_roles->add_cap( 'administrator', $cap, true ); - } - } - -} - -// ---------------------------------- -// Admin Fix for DJ / Host Role Label -// ---------------------------------- -// 2.3.3.9: added for user edit screen crackliness -add_filter( 'editable_roles', 'radio_station_role_check_test', 9 ); -function radio_station_role_check_test( $roles ) { - if ( RADIO_STATION_DEBUG && is_admin() ) { - echo "DJ Role: " . print_r( $roles['dj'], true ); - } - $roles['dj']['name'] = __( 'DJ / Host', 'radio-station' ); - return $roles; -} - -// --------------------------------- -// maybe Revoke Edit Show Capability -// --------------------------------- -// (revoke ability to edit show if user is not assigned to it) -add_filter( 'user_has_cap', 'radio_station_revoke_show_edit_cap', 10, 4 ); -function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { - - global $post, $wp_roles; - - // --- get the current user --- - // 2.3.3.6: get user object from fourth argument instead - // $user = wp_get_current_user(); - - // --- check if super admin --- - // ? fix to not revoke edit caps from super admin ? - // (not implemented, as causing a connection reset error) - // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { - // return $allcaps; - // } - - // --- debug passed capability arguments --- - // TODO: get post object from args instead of global ? - if ( isset( $_REQUEST['cap-debug'] ) && ( '1' == $_REQUEST['cap-debug'] ) ) { - echo 'Cap Args: ' . print_r( $args, true ) . ''; - } - - // --- check for editor - // 2.3.3.6: check editor roles first separately - $editor_roles = array( 'administrator', 'editor', 'show-editor' ); - foreach ( $editor_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - return $allcaps; - } - } - - // --- get roles with edit shows capability --- - $edit_show_roles = $edit_others_shows_roles = array(); - if ( isset( $wp_roles->roles ) && is_array( $wp_roles->roles ) ) { - foreach ( $wp_roles->roles as $name => $role ) { - // 2.3.0: fix to skip roles with no capabilities assigned - if ( isset( $role['capabilities'] ) ) { - foreach ( $role['capabilities'] as $capname => $capstatus ) { - // 2.3.0: change publish_shows cap check to edit_shows - if ( ( 'edit_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_show_roles ) ) { - $edit_show_roles[] = $name; - } - } - // 2.3.3.6: add check for edit-others_shows capability - if ( ( 'edit_others_shows' === $capname ) && (bool) $capstatus ) { - if ( !in_array( $name, $edit_others_shows_roles ) ) { - $edit_others_shows_roles[] = $name; - } - } - } - } - } - } - - // 2.3.3.6: preserve if user has edit_others_shows capability - foreach ( $edit_others_shows_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - return $allcaps; - } - } - - // 2.2.8: remove strict in_array checking - $found = false; - foreach ( $edit_show_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { - $found = true; - } - } - - // --- maybe revoke edit show capability for post --- - // 2.3.3.6: fix to incorrect logic for removing edit show capability - if ( $found ) { - - // --- limit this to published shows --- - // 2.3.0: added object and property_exists check to be safe - if ( is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { - - // 2.3.0: removed is_admin check (so works with frontend edit show link) - // 2.3.0: moved check if show is published inside - if ( RADIO_STATION_SHOW_SLUG == $post->post_type ) { - - // --- get show hosts and producers --- - $hosts = get_post_meta( $post->ID, 'show_user_list', true ); - $producers = get_post_meta( $post->ID, 'show_producer_list', true ); - - if ( !$hosts || empty( $hosts ) ) { - $hosts = array(); - } - if ( !$producers || empty( $producers ) ) { - $producers = array(); - } - - // ---- revoke editing capability if not assigned to this show --- - // 2.2.8: remove strict in_array checking - // 2.3.0: also check new Producer role - if ( !in_array( $user->ID, $hosts ) && !in_array( $user->ID, $producers ) ) { - - // --- remove the edit_shows capability --- - $allcaps['edit_shows'] = false; - - // 2.3.0: move check if show is published inside - if ( 'publish' == $post->post_status ) { - $allcaps['edit_published_shows'] = false; - } - } - } - } - } - - return $allcaps; -} - - -// ================= -// --- Debugging --- -// ================= - -// ----------------------- -// Set Debug Mode Constant -// ----------------------- -// 2.3.0: added debug mode constant -// 2.3.2: added saving debug mode constant -if ( !defined( 'RADIO_STATION_DEBUG' ) ) { - $rs_debug = false; - if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { - $rs_debug = true; - } - define( 'RADIO_STATION_DEBUG', $rs_debug ); -} -if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { - $rs_save_debug = false; - if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { - $rs_save_debug = true; - } - define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); -} - -// -------------------------- -// maybe Clear Transient Data -// -------------------------- -// 2.3.0: clear show transients if debugging -// 2.3.1: added action to init hook -// 2.3.1: check clear show transients option -add_action( 'init', 'radio_station_clear_transients' ); -function radio_station_clear_transients() { - $clear_transients = radio_station_get_setting( 'clear_transients' ); - if ( RADIO_STATION_DEBUG || ( 'yes' == $clear_transients ) ) { - // 2.3.2: do not clear on AJAX calls - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - return; - } - // 2.3.3.9: just use clear cached data function - radio_station_clear_cached_data( false ); - } -} - -// ------------------------ -// Debug Output and Logging -// ------------------------ -// 2.3.0: added debugging function -function radio_station_debug( $data, $echo = true, $file = false ) { - - // --- maybe output debug info --- - if ( $echo ) { - // 2.3.0: added span wrap for hidden display - // 2.3.1.1: added class for page source searches - echo '' . PHP_EOL; - } - - // --- check for logging constant --- - if ( defined( 'RADIO_STATION_DEBUG_LOG' ) ) { - if ( !$file && RADIO_STATION_DEBUG_LOG ) { - $file = 'radio-station.log'; - } elseif ( false === RADIO_STATION_DEBUG_LOG ) { - $file = false; - } - } - - // --- write to debug file --- - if ( $file ) { - if ( !is_dir( RADIO_STATION_DIR . '/debug' ) ) { - wp_mkdir_p( RADIO_STATION_DIR . '/debug' ); - } - $file = RADIO_STATION_DIR . '/debug/' . $file; - error_log( $data, 3, $file ); - } -} diff --git a/readme.txt b/readme.txt index 3b1ba6d..bcccd52 100644 --- a/readme.txt +++ b/readme.txt @@ -220,6 +220,8 @@ You can now visit your site to make sure nothing is broken. If you experience is = 2.4.0.4 = * Fixed: Fallback scripts and fallback stream URLs +* Fixed: Radio Clock responsive width display +* Fixed: Collapse descriptions for non-show pages = 2.4.0.3 = * Update: Plugin Panel (1.2.1) with zero value save and tab fixes diff --git a/readme.txt.bak b/readme.txt.bak deleted file mode 100644 index 4f02d92..0000000 --- a/readme.txt.bak +++ /dev/null @@ -1,802 +0,0 @@ -=== Radio Station === -Contributors: tonyzeoli, majick -Donate link: https://netmix.co/donate -Tags: dj, music, playlist, radio, shows, scheduling, broadcasting -Requires at least: 3.3.1 -Tested up to: 5.8 -Stable tag: 2.4.0.3 -License: GPLv2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html - -Radio Station let's you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. - -== Description == - -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website. If you're a podcaster with scheduled releases or a Clubhouse app moderator who schedule rooms, you could use this plugin to announce your schedule on your website. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. - -The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. - -A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has it's own dedicated page to display all the Show details in a responsive layout. - -The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. - -As there is a lot you can do with Radio Station, we've made an effort to provide complete [Radio Station Plugin Documentation](https://netmix.com/radio-station/docs/). You can also find a Quickstart Guide there, as well as in the section below. You can see some example displays from the plugin via the Screenshots section, and full live examples are available on the [Radio Station Plugin Demo Site](https://radiostationdemo.com). - -We are actively seeking Radio Station partners and supporters to fund further development of the free, open source version of this plugin via [Patreon](https://www.patreon.com/radiostation) and are also in the process of developing more exciting features and functionality for a future [Radio Station Pro](https://netmix.com/radio-station-pro/) upgrade. Enter your [email address on the Radio Station Pro website] (https://radiostation.pro) to add yourself to the cue to be able to download the be notified when PRO is released. - -= Updating from Prior to 2.3.0 = - -Since 2.3.0, the first major feature update since plugin takeover in July 2019, Radio Station has incorporated a whole bunch of enhancements (see the changelog for a full list)... but here is a shortlist of the main new features: - -* an Updated Show Page Layout (based on Content Filters not Templates) -* Responsive Schedule Views (with integrated Override support) -* Revamped Schedule calculations (with Show Shift Conflict Checking) -* Producer and Show Editor Roles (for improved Show management) -* Language Taxonomy Assignments (for Shows and Overrides) -* Admin Plugin Settings Page (with a plethora of new options) -* ...and a Radio Station Data API via the WordPress REST API! - -If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards oompatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. - -= Support and Contribution = - -We are grateful to Nikki Blight for her contribution to creating and developing this plugin for as long as she could maintain the codebase. As of June 22, 2019, Radio Station is managed by [Tony Zeoli](https://profiles.wordpress.org/tonyzeoli/) with [Tony Hayes](https://profiles.wordpress.org/majick/) as lead developer and other contributing committers to the project. - -For free version plugin support, you can ask in the [Wordpress Plugin Support Forum](https://wordpress.org/support/plugin/radio-station/). Please give 24-48 hours to answer support questions. Alternatively (and preferably) you can submit bugs, enhancement and feature requests directly to [Github Repository Issues](https://github.com/netmix/radio-station/issues/). - -If you are a WordPress developer wanting to contribute to Radio Station, please join the team and follow plugin development on [Github](https://github.com/netmix/radio-station) and submit Issues and Pull Requests there. You can see the current progress via the Projects tab. Or if you would prefer to get involved even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. - -= Quickstart Guide = - -Once you have installed and activated the Radio Station Plugin on your WordPress site, your WordPress Admin area will now have a new menu item titled Radio Station with submenu page items. If you are trying to do something specific, you can check out the [FAQ](https://netmix.com/radio-station/docs/FAQ.md) for Frequently Asked Questions as you may find the answer there. - -Firstly, you can visit the Plugin Settings screen to adjust the default [Options](https://netmix.com/radio-station/docs/Options.md) to your liking. Here you can set your Radio Timezone and Streaming URL (if you have one) along with other global plugin settings. Also from this Settings page you may want to assign [Pages](https://netmix.com/radio-station/docs/Display.md#automatic-pages) and Views for your Program Schedule display and other optional Post Type Archive displays. - -Add a New Show and assign it a Shift timeslot and Publish. Then check out how it displays on a single Show page by clicking the Show Permalink. Schedule Overrides work in a similar way but are for specific date and time blocks only. Depending on your Theme, you may wish to adjust the [Templates](https://netmix.com/radio-station/docs/Display.md#page-templates) used. You can also assign different [Images](https://netmix.com/radio-station/docs/Display.md#images) to Shows (and Schedule Overrides.) Then have a look at your Program Schedule page to see the Show displayed there also. Just keep adding Shows until you have your Schedule filled in! You can further [Manage](https://netmix.com/radio-station/docs/Manage.md) your Shows and other Station data via the WordPress Admin area. - -Next you may want to give some users on your site some plugin [Roles](https://netmix.com/radio-station/docs/Roles.md). (Note that while the default interface in WordPress allows you to assign a single role to a user, it also supports multiple roles, but you need to add a plugin to get an interface for this.) Giving a Role of Host/DJ or Producer to a user will allow them to be assigned to a Show on the Show Edit Page and thus edit that particular Show also. You can also assign the Show Editor role if you have someone needs to edit all plugin records without being a site Administator. - -There are a few [Widgets](https://netmix.com/radio-station/docs/Widgets.md) you can add via your Appearance -> Widgets menu. The main one will display the currently playing Show, and another will display Upcoming Shows. There is also a Current Playlist Widget for if you have created and assigned a Playlist to a Show. - -Then there are also a number of other [Shortcodes](https://netmix.com/radio-station/docs/Shortcodes.md) you can use in your pages with different display options you can use in various places on your site also. There is the Master Schedule, Widget Shortcodes, and also Archive Shortcodes for each of the different data records. - -Radio Station has several in-built [Data](https://netmix.com/radio-station/docs/Data.md) types. These include [Custom Post Types](https://netmix.com/radio-station/docs/Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](https://netmix.com/radio-station/docs/Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via custom [Data Filters](#data-filters) throughout the plugin. We have also incorporated an [API](https://netmix.com/radio-station/docs/API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. - -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for **Radio Station Pro**. Check out the [Roadmap](https://netmix.com/radio-station/docs/Roadmap.md) if you are interested in seeing what is coming up next! - -= Upgrading to Radio Station Pro = - -Love Radio Station and ready for more? As the free version develops, we have also been working hard to introduce new features to create a Professional version that will "level up" the plugin to make your Station's site even more useable and accessible for your listeners! [Click here to learn more about Radio Station Pro](https://netmix.com/radio-station-pro/). Add your email address at [RadioStation.pro](https://radiostation.pro) to get in the cue so you can receive an invite to download the release when it's ready. - - -== Installation == - -1. Upload plugin .zip file to the `/wp-content/plugins/` directory and unzip. -2. Activate the plugin through the 'Plugins' menu in the WordPress Admin -3. Alternatively search for Radio Station via the WordPress admin Add New plugin interface and install and activate it there. -4. Give any users who need access to the plugin the role of "Host", "Producer" or "Show Editor". Assigning These roles gives publish and edit access to the plugin's records. -5. Create Shows, add Shifts to them, and assign Images, Genres, Languages, Hosts and/or Producers. -6. Add Playlists to your Shows or assign posts to Shows as needed. -7. Go to your admin Appearance -> Widgets page to add and configure Current and Upcoming Show Widgets, and any other desired plugin widgets. -8. See the QuickStart Guide above for more detailed instructions of what else is available. - -== Frequently Asked Questions == - -= How do I get started with Radio Station? = - -Read the [Quickstart Guide](https://radiostation.pro/docs/#quickstart-guide) for an introduction to the plugin, what features are available and how to set them up. - -= Where can I find the full plugin documentation? = - -The latest documentation can be found online at [NetMix.com](https://radiostation.pro/docs/). Documentation is also included with for the currently installed version via the Radio Station Help menu. You can find the Markdown-formatted files in the `/docs` folder of the [GitHub Repository](https://github.com/netmix/radio-station/docs/) and in the `/docs` folder of the plugin directory. - -= How do I schedule a Show? = - -Simply create a new show via Add Show in the Radio Station plugin menu in the Admin area. You will be able to assign Shift timeslots to it on the Show edit page, as well as add the Show description and other meta fields, including Show images. - -= How do I display a full schedule of my Station's shows? = - -In the Plugin Settings, you can select a Page on which to automatically display the schedule as well as which View to display (a Table grid by default.) Alternatively, you can use the shortcode `[master-schedule]` on any page (or post.) This option allows you to use further shortcode attributes to control the what is displayed in the Schedule (see [Master Schedule Shortcode Docs](https://radiostation.pro/docs/shortcodes/#master-schedule-shortcode) ) - -= I've scheduled all my Shows, but some are not showing up on the program schedule? = - -Did you remember to check the "Active" checkbox for each Show? If a Show is not marked active, the plugin assumes that it's not currently in production and it is not shown on the Schedule. A Show will also not be shown if it has a Draft status or has no active Shifts assigned to it. - -= What if I want to schedule a special event or one-off schedule change? = - -If you have a one-off event that you need to show up in the Schedule and Widgets, you can create a Schedule Override by clicking Schedule Override in the Radio Station admin menu. This will allow you to set aside a block of time on a specific date, and when the Schedule or Widget is displaying that date, the override will be used instead of the normally scheduled Show. (Note that Schedule Overrides will not display in the old Legacy Table/Div Views of the Master Schedule.) - -= I'm seeing a 404 Not Found error when I click on the link for a Show! = - -Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpress sometimes gets confused with a new custom post type is added. Permalink rewrites are automatically flushed on plugin activation, so you can also just deactivate and reactivate the plugin. - -= What if I want to change or style the plugin's displays? = - -The default styles for Radio Station have intionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. - -= What Widgets are available with this plugin? = - -The following Widgets are available to add via the WordPress Appearance -> Widgets page: -Current Show, Upcoming Shows, Current Playlist, Radio Clock and Streaming Player Widgets. See the [Widget Documentation](https://radiostation.pro/docs/widgets/) for more details on these Widgets. - -= Do the Widgets reload automatically? = - -Current Show, Upcoming Shows and Current Playlist widgets do not refresh automatically in this free version. This capability has been added as a new feature to the [Pro version](https://radiostation.pro) however - so that the widgets refresh exactly at Show changeover times. - -= What Shortcodes are available with this plugin? = - -See the [Shortcode Documentation](https://radiostation.pro/docs/shortcodes/) for more details and a full list of possible Attributes for these Shortcodes: - -* `[master-schedule]` - Master Program Schedule Display -* `[current-show]` - Current Show Widget -* `[upcoming-shows]` - Upcoming Shows Widget -* `[current-playlist]` - Current Playlist Widget -* `[shows-archive]` - Archive List of Shows -* `[genres-archive]` - Archive List of Shows sorted by Genre -* `[languages-archive]` - Archive List of Shows sorted by Language -* `[overrides-archive]` - Archive List of Schedule overrides -* `[playlists-archive]` - Archive List of Show Playlists - -Note old shortcode aliases will still work in current and future versions to prevent breakage. - -= I need users other than just the Administrator and DJ roles to have access to the Shows and Playlists post types. How do I do that? = - -There are a number of different options depending on what you are wanting to to do. To address this situation, we have added a Show Editor role that can edit Shows without being an Administrator. You can find more information on roles in the [Roles Documentation](https://radiostation.pro/docs/roles/) - -= How do I change the Show Avatar displayed in the sidebar widget? = - -The avatar is whatever image is assigned as the Show's Avatar. All you have to do is set a new Show Avatar on the Edit page for that Show. - -= Why don't any users show up in the Hosts or Producers list on the Show edit page? = - -You need to assign the Host or Producer role to the users you want, so that you can select these users on the Show edit page. You can assign roles by editing the User via the WordPress admin. The [Pro version](https://radiostation.pro) has an additional Role Editor interface where you can assign the plugin roles to any number of users at once. - -= My Show Hosts and Producers can't edit a Show page. What do I do? = - -The only Hosts and Producers that can edit a show are the ones listed as being Hosts or Producers for that Show in the respective user selection menus. This is to prevent Hosts/Producers from editing other Host/Producer's Shows without permission. - -= I don't want to use Gravatar for my Host/Producer's image on their profile page. = - -Then you'll need to install a plugin that lets you add a different image to your Host/Producer's user account. As there are a number of plugins that do just this, it's a little out of the scope of this plugin. However, in the [Pro version](https://radiostation.pro) you can create separate profile pages to showcase each of your Hosts and Producers, and assign profile images to these profile pages. - -= What languages other than English is the plugin available in? = - -Right now: - -* Albanian (sq_AL) -* Dutch (nl_NL) -* French (fr_FR) -* German (de_DE) -* Italian (it_IT) -* Russian (ru_RU) -* Serbian (sr_RS) -* Spanish (es_ES) -* Catalan (ca) - -= Can the plugin be translated into my language? = - -You may translate the plugin into another language. Please visit our [WordPress Translate project page](https://translate.wordpress.org/projects/wp-plugins/radio-station/) for this plugin for further instruction. The `radio-station.pot` file is located in the `/languages` directory of the plugin. Please send the finished translation to `info@netmix.com`. We'd love to include it. - -= Can I use this plugin for Podcasts? = - -While the plugin is not specifically geared toward Podcasting, which is not live programming, some podcaster's have used Radio Station to let their subscribers know when they publish new shows. - -= Can I use this plugin for TwitchTV, Facebook Live, or Clubhouse shows? = - -Sure, there's no reason why you couldn't use the plugin to display a show schedule on a WordPress site for those services. Unfortunately, we are not currently syncing events from these platforms, but may do so in the future. While there may be APIs available from the larger services, Clubhouse does not yet have a public API, so scheduled rooms can't be automated to the Radio Station show scheduling system. - -= I use Google Calendar to print a show schedule online. Can I import/sync my Google Calendar with Radio Station? = - -We haven't built an interface between Google Calendar and Radio Station just yet, but it's on our radar to do so in the foreseeable future. - -= How do I install the latest Development version for testing? = - -If you are having issues with the plugin, we may recommend you install the development version for further bugix testing, as it may contain fixes that are not yet released into the next stable WordPress version. It is recommended you do this on a staging site. Instructions: - -1. Download the `develop` branch zip from the Github repository at: -`https://github.com/netmix/radio-station/tree/develop/` -2. Unzip the downloaded file on your computer and upload it via FTP to the subdirectory of your WordPress install on your web server: `/wp-content/plugins/radio-station-dev/` -3. Rename the subdirectory `/wp-content/plugins/radio-station/` to `/wp-content/plugins/radio-station-old` -4. Rename the subdirectory `/wp-content/plugins/radio-station-dev/` to `/wp-content/plugins/radio-station/` - -You can now visit your site to make sure nothing is broken. If you experience issues you can reverse the folder renaming process to activate the old copy of the plugin. If the new development version works fine, at your convenience you can delete the `/wp-content/plugins/radio-station-old/` directory. - - -== Screenshots == -1. Table Schedule View -2. Tabbed Schedule View -3. Show Page Layout -4. Playlist Page Layout -5. Current Show Widget -6. Show Archive List Shortcode -7. Admin Settings Panel -8. Show Conflict Display - -== Changelog == - -= 2.4.0.3 = -* Update: Plugin Panel (1.2.1) with zero value save and tab fixes -* Added: option to disable player audio fallback scripts -* Added: option to hide various volume controls -* Improved: lazy load player audio fallback scripts -* Improved: added author support to post types for quick edit -* Improved: Playlist content template and action hooks -* Refix: missing fix to active day tab on pageload -* Fixed: player volume slider background position (cross-browser) -* Fixed: missing title value for adjacent post links -* Fixed: Fallback scripts and fallback stream URLs - -= 2.4.0.2 = -* Fixed: Multiple Player instance IDs -* Fixed: Player loading button glow animation -* Added: Enabled Pro Pricing plans page -* Added: Widget type specific classes -* Added: Alternative text positions in Player -* Added: Pause button graphics to Player - -= 2.4.0.1 = -* Fixed: Rounded player play button background corner style -* Fixed: Tabbed schedule active day tab on pageload -* Improved: Radio Clock Widget layout - -= 2.4.0 = -* Added: Radio Stream Player! -* Fixed: Shows archive shortcode with no Shows selected - -= 2.3.3.9 = -* Update: Plugin Panel (1.1.8) with Number Step Min/Max fix -* Update: Freemius SDK (2.4.2) -* Improved: Allow for Multiple Override Times (with AJAX Saving) -* Improved: Markdown Extra Compatibility for PHP 7.4+ -* Added: Link Override to Show Data with selectable Show Fields -* Added: Language Archive Shortcode (similar to Genre Archive) -* Added: Display Linked Override Date List on Show Pages -* Added: Automatic user showtime conversion and display -* Fixed: Show Schedule sometimes starting on previous week -* Fixed: Current Show highlighting timer interval cycling -* Fixed: Before and After Show classes when no current Show -* Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch -* Fixed: Multiple host separator display in Current Show Widget -* Fixed: Playlist Widget playlist ended label when no next playlist -* Fixed: Conflicting duplicate filter name for Show Avatar -* Fixed: Time conversions where start/finish Show/Override is equal -* Fixed: Show page subarchive lists pagination button arrow display -* Fixed: Show Shifts with same start time overwriting bug - -= 2.3.3.8 = -* Update: Plugin Panel (1.1.7) with Image and Color Picker fields -* Added: Stream Format and Fallback/Format selection setting -* Added: Station Email Address setting with default display option -* Added: Section order filtering for Master Schedule Views -* Added: Section display filtering for Master Schedule Views -* Added: Section display filtering for Widget sections -* Added: Show image alignment attribute to Schedule Tabs View -* Added: Show Description/Excerpt to Show Data Endpoint (via querystring) -* Added: Reduced opacity for past Shows on Schedule Tab/Table Views -* Added: Screen Reader text for Show icons on Show Page -* Fixed: Display Widget Countdown when no Current Show/Playlist -* Fixed: Check for explicit singular.php template usage setting -* Fixed: Access to Shows Data via querystring of Show ID/name -* Fixed: Shows Data for Genres/Languages querystring of ID/name -* Fixed: Changed stable tag from trunk to version number to fix translations issue - -= 2.3.3.7 = -* Fixed: Schedule Overrides overlapping multiple Show shifts -* Fixed: Bulk Edit field repetition and possible jQuery conflict -* Fixed: Related Posts check producing error output -* Fixed: WordPress Readme Parser deprecated errors for PHP7 - -= 2.3.3.6 = -* Update: Freemius SDK (2.4.1) -* Update: Plugin Loader (1.1.6) with phone number and CSV validation -* Added: Station phone number setting with default display option -* Added: Schedule classes for Shows before and after current Show -* Improved: current Show highlighting on Schedule for overnight shifts -* Improved: info section reordering filters on single Show template -* Fixed: Edit permissions checks for Related to Show post assignments -* Fixed: Main Language option value for WordPress Setting -* Fixed: make Date on Tab clickable on Tabbed Schedule View -* Fixed: prevent possible conflicts with changes not saved reload message -* Fixed: do not conflict check Shift against itself for last shift check -* Fixed: link back to Show posts for related Show posts (allow multiple) -* Fixed: filter next/previous post link for (multiple) related Show posts -* Fixed: automatic pages conflict where themes filter the_content early - -= 2.3.3.5 = -* Fixed: use schedule based on start_day if specified for Schedule view -* Fixed: day left/right shifting on Schedule table/tab mobile views -* Added: past/today/future filter for Schedule Override List -* Added: filter for Schedule display start day (and to accept today) -* Added: current playlist (if any) to Broadcast Data endpoint - -= 2.3.3.4 = -* Improved: auto-match show description to info height on Show pages -* Improved: allow multiple Related Show selection for single post -* Improved: ability to assign Post to relate to multiple Shows -* Added: Related Show Post List column and Quick Edit field -* Added: Related Show selection Bulk Edit Action for Post List -* Added: filters for label texts and title attributes on Show Page -* Added: filter for label text above Show Player (default empty) - -= 2.3.3.3 = -* Fixed: improved Current Show and Upcoming Shows calculations -* (Display showtimes when show starts before and ends after midnight) - -= 2.3.3.2 = -* Update: Freemius SDK (2.4.0) -* Update: Plugin Loader (1.1.4) with weird isset glitch fix -* Fixed: Current Show for Shows ending at midnight -* Fixed: incorrect AJAX Widget plugin setting value -* Fixed: use pageload data for schedules before transients - -= 2.3.3 = -* Update: Plugin Loader (1.1.3) with non-strict select match fix -* Improved: width responsiveness for table/tabbed Schedule views -* Improved: show shifts interface background colors -* Added: navigate away from page on shift change check -* Added: default time format option to Widgets -* Removed: current show transients (intermittant unreliability) -* Fixed: AJAX call causing plugin conflicts via save_post action -* Fixed: calculation of Upcoming Shows near end of the week -* Fixed: remove and duplicate actions on new shifts - -= 2.3.2 = -* Update: Plugin Loader (1.1.2) with settings link fix -* Improved: use plugin timezone setting for all times -* Improved: show shift conflict checker logic -* Added: Radio Clock Widget for user/server time display -* Added: AJAX widget load option (to bypass page caches) -* Added: automated show schedule highlighting (table/tabs/list) -* Added: playlist track arrows for re-ordering tracks -* Added: AJAX save of show shifts and playlist tracks -* Added: post type editing metabox position filtering -* Added: more display attributes to Master Schedule shortcode -* Added: time format filters for time output displays -* Added: javascript user timezone display on Master Schedule -* Fixed: handling of UTC only timezone settings -* Fixed: added check for empty role capabilities -* Fixed: added settings submenu redirection fix -* Fixed: show and override midnight end conflict -* Fixed: calculate next shows at end of schedule week -* Fixed: metaboxes disappearing on position sorting -* Fixed: move tracks marked New to end of Playlist on update -* Fixed: override shift array output showing above schedule -* Fixed: master schedule specify days attribute bug -* Fixed: display real end time of overnight split shifts -* Fixed: master schedule display with days attribute -* Fixed: logic for Affected Shifts in override list -* Fixed: removed auto-tab selection change on tab view resize -* Fixed: Current Show widget schedule/countdown for Overrides -* Fixed: multiple overrides in schedule range variable conflict - -= 2.3.1 = -* Update: Plugin Loader (1.1.1) with Freemius first path fix -* Fixed: conditions for Schedule Override time calculations -* Fixed: schedule table view - 12 hour format with translations -* Fixed: schedule table view hour column width style -* Fixed: javascript table/tab arrows to prevent default click -* Fixed: undefined index warning when saving show with no shifts -* Fixed: append not echo override date to shortcode archive list -* Fixed: compatibility with multiple the_content calls (Yoast) -* Fixed: reset to showcontinued flag in Schedule (table view) -* Added: show avatar and featured image URLs to Data API output -* Added: option to ping Netmix directory on show updates -* Added: option to clear transients on every pageload -* Added: filters for widget section display order - -= 2.3.0 = -* Include: Plugin Loader (1.1.0) with plugin options and settings -* Include: Freemius SDK (2.3.0) and Freemius integration -* Feature: assign new Producer role to a Show for Show displays -* Feature: internal Schedule Show Shift Conflict checking -* Feature: Show Shift saving completeness and conflict checking -* Feature: added Data Endpoints API via WordPress REST and Feeds -* Feature: options to set Page and default View for Master Schedule -* Feature: post type Archive Shortcodes and Show-related Shortcodes -* Feature: display Radio Timezone on Master Schedule table view -* Feature: added Show Header image to Shows for single Show display -* Feature: added Show Language Taxonomy to Shows (and Overrides) -* Feature: added Countdown clock for Show and Playlists Widgets -* Improved: new Data Model and Schedule (with Override) Calculation -* Improved: new Show Content Template layout display method -* Improved: new Playlist Content Template layout display method -* Improved: added multiple Genre highlight selection on Master Schedule -* Improved: added Custom Field and Revision support to post types -* Improved: missing output sanitization throughout the plugin -* Improved: added file hierarchy fallbacks for CSS, JS and Templates -* Improved: enqueue conditional scripts inline instead of echoing -* Improved: Master Schedule displays enhancements and styling -* Improved: add Responsiveness to Master Schedule Table and Tab View -* Improved: add View/Edit links for editing custom post types -* Improved: load Datepicker styles locally instead of via Google -* Improved: add debug function for debug display and logging -* Improved: add links from Show Posts back to Show Page -* Improved: added Duplicate Shift button to Show Shift Editing -* Roles: new Show Producer role (same capabilities as DJ / Host) -* Roles: new Show Editor role (edit permissions but not Admin) -* Roles: Changed DJ role Label to DJ / Host (for talk show usage) -* Admin: Added Plugin Settings Admin Page (via Plugin Loader) -* Admin: Added plugin Upgrade / Updated details admin notices -* Admin: Schedule conflict notice and Show conflicts in Shift column -* Admin: Show/Override content indicator columns to Admin Show list -* Admin: Show Description helper text metabox on Show edit screen -* Admin: Fix to restore Admin Bar New/Edit links for plugin post types -* Admin: Store installed version for future updates and announcements -* Disabled: automatic loading of old templates (non theme agnostic) - -= 2.2.8 = -* Fix to remove strict type checking from in_array (introduced 2.2.6) -* Fix to mismatched flush rewrite rules flag function name -* Fix to undefined index warnings for new Schedule Overrides -* Fix to not 404 author pages for DJs without blog posts - -= 2.2.7 = -* Dutch translation added (Thank you to André Dortmont for the file!) -* Added Tabbed Display for Master Schedule Shortcode (via Tutorial) -* Add Show list columns with active, shift, DJs and show image displays -* Add Schedule Override list columns with date sorting and filtering -* Add playlist track information labels to Now Playing Widget -* Added meridiem (am/pm) translations via WP Locale class -* Added star rating link to plugin announcement box -* Added update subscription form to plugin Help page -* Fix to checkbox value saving for On Air/Upcoming Widgets -* Fix 12 hour show time display in Upcoming Widget -* Fix PM 12 hour shot time display in On Air Widget -* Fix to schedule override date picker value visibility -* Fix to weekday and month translations to use WP Locale -* Fix to checkbox value saving in Upcoming Widget -* Split Plugin Admin Functions into separate file -* Split Post Type Admin Functions into separate include -* Revert anonymous function use in widget registrations - -= 2.2.6 = -* Reorganize master-list shortcode into templates -* Add constant for plugin directory -* Use WP_Query instead of get_posts -* New posts_per_page and tax_query -* Fixes for undefined indexes -* Fixes for raw mysql queries -* Typecasting to support strict comparisons - -= 2.2.5 = -* WordPress coding standards and best practices (thanks to Mike Garrett @mikengarrett) - -= 2.2.4 = -* added title position and avatar width options to widgets -* added missing DJ author links as new option to widgets -* cleanup, improve and fix enqueued Widget CSS (on air/upcoming) -* improved to show Encore Presentation in show widget displays -* fix to Show shift Encore Presentation checkbox saving - -= 2.2.3 = -* added flush rewrite rules on plugin activation/deactivation -* added show_admin_column and show_in_quick_edit for Genres -* added show metadata and schedule value sanitization -* fix to 00 minute validation for Schedule Override -* convert span tags to div tags in Widgets to fix line breaks - -= 2.2.2 = -* shift main playlist and show metaboxes above editor -* set plugin custom post types editor to Classic Editor -* add high priority to side metaboxes for plugin post types -* added dismissable development changeover admin notice -* added simple Patreon supporter image button and blurb -* added filter for DJ Avatar size on Author page template -* fix to Schedule Override metabox value saving -* fix to Playlist track list items overflowing metabox -* fix to shift up time row on Master Schedule table view -* fix to missing weekday headings in Master Schedule table -* fix to weekday display for Upcoming DJ Widget -* fix to user display labels on select DJ metabox -* fix to file_exists check for DJ on Air stylesheet path -* fix to make DJ multi-select input full metabox width -* fix to expand admin menu when on genre taxonomy page -* fix to expand admin menu when editing plugin post types -* fix to genre submenu item link for current page -* added GitHub URI to plugin header for GitHub updater - -= 2.2.1 = -* Re-commit all missing files via SVN - -= 2.2.0 = -* WordPress coding standards refactoring for WP 5 (thanks to Tony Hayes @majick777) -* fixed the protocol in jQuery UI style Google URL -* reprefixed all functions for consistency (radio_station_) -* updated all the widget constructor methods -* merged the menu items into a single main menu -* updated the capability checks for the menu items -* moved the help and export pages to /templates/ -* moved all the css files to /css/ -* enqeued the djonair css from within the widget -* use plugins_url for all resource URLs -* added $wpdb->prepare to sanitize a query -* added some sanization for metabox save values -* added a week and month translation helper -* added a radio station antenna icon - -= 2.1.3 = -* Added method for displaying schedule for only a single day (see readme section for the master-schedule shortcode for details). - -= 2.1.2 = -* Compatibility fix for Wordpress 4.3.x - Updated the widgets to use PHP5 constructors instead of the deprecated PHP4 constructors. -* Catalan translation added (Thank you to Victor Riera for the file!) - -= 2.1.1 = -* Bug fix - Fixed day of the week language translation issue in master schedule shortcode -* Bug fix - Added some error checking in the sidebar widgets -* New Feature - Added ability to give schedule overrides a featured image -* New Feature - Added built-in help page - -= 2.1 = -* General code cleanup, 4.1 compatibility testing, and changes for better efficiency. -* Bug fix - Fixed issue with early morning shows spanning entire column in the programming grid shortcode -* New Feature - Master programming grid can now be displayed in div format, as well as the original table and list formats. - -= 2.0.16 = -* Minor revisions to German translation. -* Fixed a bug that was resetting custom-sert role capabilities for the DJ role. - -= 2.0.15 = -* German translation added (Thank you to Ian Hook for the file!) - -= 2.0.14 = -* Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked -* Added ability to display DJ names on the master schedule. -* Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. -* Verified compatibility woth WordPress 4.0 - -= 2.0.13 = -* Added the ability to display show avatars on the program grid. -* Added the ability to display show description in the now on-air widget and short code. - -= 2.0.12 = -* Fixed a bug in the master schedule shortcode - -= 2.0.11 = -* Russian translation added (Thank you to Alexander Esin for the file!) - -= 2.0.10 = -* Fixed role/capability conflict with WP User Avatar plugin. -* Added the missing leading zero to 24-hour time format on the master schedule. -* Fixed dj_get_current function so that it no longer returns shows that have been moved to the trash. -* Fixed dj_get_next function so that it no longer ignores the "Active" checkbox on a show. -* Added some CSS ids and classes to the master program schedule list format to make it more useful - -= 2.0.9 = -* Fixed broken upcoming show shortcode. -* Added ability to display DJ names along with the show title in the widgets. - -= 2.0.8 = -* Fixed the display of schedules for upcoming shows in the widget and shortcode. -* Fixed a bug in the dj_get_next function that was causing it to ignore the beginning of the next week at the end of the current week. - -= 2.0.7 = -* Fixed scheduling bug in shortcode function - -= 2.0.6 = -* Master Schedule now displays days starting with the start_of_week option set in the WordPress General Settings panel. -* Fixed issue with shows that have been unplublished still showing up on the master schedule. -* Fixed missing am/pm text on shows that run overnight on the master schedule. -* Fixed an issue with shows that run overnight not spanning the correct number of hours on the second day on the master schedule. -* Fixed problem in Upcoming DJ Widget that wasn't displaying the correct upcoming shift. - -= 2.0.5 = -* Fixed an issue with some shows displaying in 24 hour time on master schedule grid even though 12-hour time is specified -* Fixed a bug in the On-Air widget that was preventing shows spanning two day from displaying -* Added code to enable theme support for post-thumbnails on the "show" post-type so users don't have to add it to their theme's functions.php file anymore. - -= 2.0.4 = -* Master Schedule bug for shows that start at midnight and end before the hour is up fixed. - -= 2.0.3 = -* Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes - -= 2.0.2 = -* Bug fix: Scheduling issue with overnight shows fixed - -= 2.0.1 = -* Bug fix: Fixed PHP error in Playlist save function that was triggered during preview -* Bug fix: Fixed PHP notice in playlist template file -* Bug fix: Fixed PHP error in dj-widget shortcode - -= 2.0.0 = -* Major code reorganization for better future development -* PHP warning fix -* Enabled option to add comments on Shows and Playlists -* Added option to show either single or multiple schedules in the On Air widget - -= 1.6.2 = -* Minor PHP warning fixes - -= 1.6.1 = -* Bug fix: Some of the code added in the previous update uses the array_replace() function that is only available in PHP 5.3+. Added a fallback for older PHP versions. - -= 1.6.0 = -* Added the ability to override the weekly schedule to allow one-off events to be scheduled -* Added a list format option to the master schedule shortcode -* Added Italian translation (it_IT) (thank you to Cristofaro Giuseppe!) - -= 1.5.4 = -* Fixed some PHP notices that were being generated when there were no playlist entries in the system. - -= 1.5.3 = -* Added Serbian translation (sr_RS) (thank you to Miodarag Zivkovic!) - -= 1.5.2.1 = -* Removed some debug code from one of the template files - -= 1.5.2 = -* Fixed some localization bugs. -* Added Albanian translation (sq_AL) (thank you to Lorenc!) - -= 1.5.1 = -* Fixed some localization bugs. -* Added French translation (fr_FR) (a big thank you to Dan over at BuddyPress France - http://bp-fr.net/) - -= 1.5.0 = -* Plugin modified to allow for internationalization. -* Spanish translation (es_ES) added. - -= 1.4.6 = -* Fixed a bug with shows that start at midnight not displaying in the on-air sidebar widget. -* Switched DJ/Show avatars in the widgets to use the featured image of the show instead of gravatar. -* Updated show template to get rid of a PHP warning that appeared if the show had no schedules. -* Fixed some other areas of the code that were generating PHP notices in WordPress 3.6 -* Added CSS classes to master program schedule output so CSS rules can be applied to specific shows -* Added new attribute to the list-shows shortcode to allow only specified genres to be displayed - -= 1.4.5 = -* Fixed master-schedule shortcode bug that was preventing display of 12 hour time - -= 1.4.4 = -* Compatibility fix for Wordpress 3.6 - fixed problem with giving alternative roles DJ capabilities -* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 - -= 1.4.3 = -* Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time -* Fixed some areas of the code that were generating PHP notices in WordPress 3.6 -* Added example of how to display show schedule to single-show.php template -* Added more options to the plugin's widgets -* Added new options to the master-schedule shortcode - -= 1.4.2 = -* Fixed a bug in the CSS file override from theme directory - -= 1.4.1 = -* Fixed issue with templates copied to the theme directory not overriding the defaults correctly -* Fixed incorrectly implemented wp_enqueue_styles() -* Removed deprecated escape_attribute() function from the plugin widgets -* Fixed some areas of the code that were generating PHP notices - -= 1.4.0 = -* Compatibility fix for WordPress 3.6 - -= 1.3.9 = -* Fixed a bug that was preventing sites using a non-default table prefix from seeing the list of DJs on the add/edit show pages - -= 1.3.8 = -* Changes to fix the incorrect list of available shows on the Add Playlist page -* Removing Add Show links from admin menu for DJs, since they don't have permission to use them anyway. - -= 1.3.7 = -* Fixed a scheduling bug in the upcoming shows widget -* By popular request, switched the order of artist and song in the now playing widget - -= 1.3.6 = -* Fixed issue with shows that run overnight not showing up correctly in the sidebar widgets - -= 1.3.5 = -* Fixed a time display bug in the DJ On-Air sidebar widget -* Fixed a display bug on the master schedule with overnight shows - -= 1.3.4 = -* By request, added as 24-hour time format option to the master schedule and sidebar widgets. - -= 1.3.3 = -* Added the ability to assign any user with the edit_shows capability as a DJ, to accomodate custom and edited roles. - -= 1.3.2 = -* Fixed a bug in the DJ-on-air widget - -= 1.3.1 = -* Fixed a major bug in the master schedule output - -= 1.3 = -* Fixed some minor compatibility issues with WordPress 3.5 -* Fixed Shows icon in Dashboard - -= 1.2 = -* Fixed thumbnail bug in sidebar widgets -* Added new widget to display upcoming shows -* Added pagination options for playlists and show blogs - -= 1.1 = -* Fixed playlist edit screen so that queued songs fall to the bottom of the list to maintain play order -* Reduced the size of the content field in the playlist post type -* Some minor formatting changes to default templates -* Added genre highlighter to the master programming schedule page -* Added a second Update button on the bottom of the playlist edit page for convinience. -* Added sample template for DJ user pages -* Fixed a bug in the master schedule shortcode that messed up the table for shows that are more than two hours in duration -* Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. -* Added new field to associate blog posts with shows - -= 1.0 = -* Initial release - -== Upgrade Notice == - -= 2.4.0 = -* Radio Station Stream Player Widget! -* https://netmix.com/radio-station-2-4-0-release-with-stream-player/ - -= 2.3.3.9 = -* Multiple Dates and Times for Schedule Overrides! -* https://netmix.com/radio-station-2-3-3-9-release-announcement/ -* Link Override to Show with Selective Fields -* Automatic Visitor Showtime Conversion and Display -* Language Archive Shortcode for Shows -* Various Bugfixes and Improvements -* Updated Freemius SDK and Plugin Loader - -= 2.3.3.8 = -* Updated Plugin Panel Library -* Added Stream Format selection setting -* Added Station email address setting with default display option -* Added Section order filtering for Master Schedules and Widgets -* Added Show image alignment attribute to Schedule Tabs View - -= 2.3.3.7 = -* Updated Freemius SDK and Plugin Loader libraries -* Added Station phone number setting with default display option -* Added Schedule classes for Shows before and after current Show -* Multiple Related Show Post assignment edit and link fixes -* Bugfixes for permissions, main language and shift checker - -= 2.3.3.6 = -* Updated Freemius SDK and Plugin Loader libraries -* Added Station phone number setting with default display option -* Added Schedule classes for Shows before and after current Show -* Multiple Related Show Post assignment edit and link fixes -* Bugfixes for permissions, main language and shift checker - -= 2.3.3.5 = -* Ability to assign Post to relate to multiple Shows -* Added Admin Filtering, Bulk Edit and Quick Edit interfaces -* Fixes for Schedule display left/right shifting on mobiles -* Fixes for starting Schedule display on different day - -= 2.3.3.3 = -* Current and Upcoming Shows Widget Fix - -= 2.3.3.2 = -* Minor Bugfix Update - -= 2.3.3 = -* Important Bugfix Update -* Fix to conflict with plugins using AJAX save_post calls -* Improved accuracy for responsive table/tab Schedule views -* Added colour improvements to Show Shift interface -* Fix to calculate Current Show (transient no longer used) - -= 2.3.2 = -* Improved Times, AJAX Loading and Bugfix Update -* https://netmix.com/radio-station-2-3-2-release/ -* Radio Clock Widget and Widget AJAX Loading -* AJAX Saving of Show Shifts and Playlist Tracks -* Automated current Show schedule highlighting -* Improved timezones, overrides, shift checking and more - -= 2.3.1 = -* Bugfix Update and Announcing New Netmix Station Directory! -* https://netmix.com/announcing-new-netmix-directory/ -* Including minor fixes to major update release -* Option to ping Netmix Directory on show updates - -= 2.3.0 = -* Major Update including many new features, enhancements and fixes! -* https://netmix.com/radio-station-2-3-0-release/ -* Revamped Templates, Master Schedule Views, Shortcodes and Widgets -* Added Admin Options, REST API Routes and Shift Conflict Checking -* Added Show Producers, Language Taxonomy, Timezones and Countdowns -* Improved User Roles, Show Images, Post Type Supports + much more! diff --git a/templates/master-schedule-div.php.bak b/templates/master-schedule-div.php.bak deleted file mode 100644 index 42d2cc4..0000000 --- a/templates/master-schedule-div.php.bak +++ /dev/null @@ -1,226 +0,0 @@ -'; -for ( $i = 2; $i < 24; $i++ ) { - $rowheight = $atts['divheight'] * $i; - $output .= '#master-schedule-divs .rowspan' . $i . ' { '; - $output .= 'height: ' . ( $rowheight ) . 'px; }'; -} - -$output .= '#master-schedule-divs .rowspan-half { height: 15px; margin-top: -7px; }'; -$output .= '#master-schedule-divs .rowspan { top: ' . $atts['divheight'] . 'px; }'; -$output .= ''; - -// output the schedule -$output .= '
    '; -// $weekdays = array_keys( $days_of_the_week ); - -$output .= '
    '; -$output .= '
     
    '; -foreach ( $weekdays as $weekday ) { - $display_day = radio_station_translate_weekday( $weekday ); - $output .= '
    ' . esc_html( $display_day ) . '
    '; -} -$output .= '
    '; - -foreach ( $master_list as $hour => $days ) { - - $output .= '
    '; - - // output the hour labels - $output .= '
    '; - if ( 12 === (int) $atts['time'] ) { - // random date needed to convert time to 12-hour format - $output .= date( 'ga', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); - } else { - // random date needed to convert time to 24-hour format - $output .= date( 'H:i', strtotime( '1981-04-28 ' . $hour . ':00:00' ) ); - } - $output .= '
    '; - - foreach ( $weekdays as $weekday ) { - $output .= '
    '; - if ( isset( $days[ $weekday ] ) ) { - foreach ( $days[ $weekday ] as $min => $show ) { - - // --- genre terms --- - // TODO: check term output formatting - $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); - $classes = array( 'master-show-entry', 'show-id-' . $show['id'] ); - $classes[] = sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $show['id'] ) ) ); - foreach ( $terms as $term ) { - // $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; - $classes[] = $term->slug; - } - - $output .= '
    '; - - // --- show avatar --- - if ( $atts['show_image'] ) { - // 2.3.0: filter show avatar by show and context - $show_avatar = radio_station_get_show_avatar( $show['id'], $avatar_size ); - $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'tabs' ); - if ( $show_avatar ) { - $output .= '
    ' . $show_avatar . '
    '; - } - } - - // --- show title / link --- - $show_title = get_the_title( $show['id'] ); - if ( $atts['show_link'] ) { - // 2.3.0: filter show link by show and context - $show_link = get_permalink( $show['id'] ); - $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'div' ); - if ( $show_link ) { - $show_title .= '' . $show_title . ''; - } - } - $output .= '
    '; - $output .= $show_title; - $output .= '
    '; - - // list of DJs - // 2.3.0: changed from show_djs - if ( $atts['show_hosts'] ) { - - $output .= ''; - - $show_names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $show_names ) { - - $output .= ' ' . esc_html( __( 'with', 'radio-station' ) ) . ''; - - foreach ( $show_names as $name ) { - - $count++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $show_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - // --- show time --- - if ( $atts['show_times'] ) { - - $output .= ''; - - if ( 12 === (int) $atts['time'] ) { - $show_time = date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $show_time .= ' - '; - $show_time .= date( 'g:i a', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - } else { - $show_time = date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $show_time .= ' - '; - $show_time .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - } - - /* if ( 12 === (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'g:i a'; - } else { - $start_data_format = $end_data_format = 'H:i'; - } - $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, '', $atts ); - $start_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, '', $atts ); - - $start = radio_station_get_time( $shift_start_time ); - $end = radio_station_get_time( $shift_end_time ); - $start = radio_station_translate_time( $start ); - $end = radio_station_translate_time( $end ); - */ - - // 2.3.0: filter show time by show and context - $show_time = apply_filters( 'radio_station_schedule_show_time', $show_time, $show['id'], 'div' ); - $output .= $show_time; - $output .= ''; - } - - // --- encore --- - // 2.3.0: filter encore by show and context --- - if ( isset( $show['time']['encore'] ) ) { - $show_encore = $show['time']['encore']; - } else { - $show_encore = false; - } - $show_encore = apply_filters( 'radio_station_schedule_show_encore', $show_encore, $show['id'], 'list' ); - if ( 'on' == $show_encore ) { - $output .= ' ' . esc_html( __( 'encore airing', 'radio-station' ) ) . ''; - } - - // --- show file --- - // 2.3.0: filter show file by show and context - $show_file = get_post_meta( $show['id'], 'show_file', true ); - $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'div' ); - // 2.3.2: check disable download meta - $disable_download = get_post_meta( $show['id'], 'show_download', true ); - if ( $show_file && ! empty( $show_file ) && !$disable_download ) { - $output .= ' ' . esc_html( __( 'Audio File', 'radio-station' ) ) . ''; - } - - // calculate duration of show for rowspanning - if ( isset( $show['time']['rollover'] ) ) { //show started on the previous day - $duration = $show['time']['end_hour']; - } else { - if ( $show['time']['end_hour'] >= $show['time']['start_hour'] ) { - $duration = $show['time']['end_hour'] - $show['time']['start_hour']; - } else { - $duration = 23 - $show['time']['start_hour']; - } - } - - if ( $duration >= 1 ) { - $output .= '
    '; - - if ( '00' !== $show['time']['end_min'] ) { - $output .= '
    '; - } - } - - $output .= '
    '; // end master-show-entry - } - } - $output .= '
    '; // end master-schedule-weekday - } - $output .= '
    '; // end master-schedule-hour -} -$output .= '
    '; // end master-schedule-divs diff --git a/templates/master-schedule-legacy.php.bak b/templates/master-schedule-legacy.php.bak deleted file mode 100644 index 7461eb2..0000000 --- a/templates/master-schedule-legacy.php.bak +++ /dev/null @@ -1,255 +0,0 @@ -'; - -// --- create the output in a table --- -$output .= ''; - -// --- output the headings in the correct order --- -$output .= ''; -foreach ( $days_of_the_week as $weekday => $info ) { - // 2.2.2: fix to translate incorrect variable (heading) - // 2.3.3.8: remove abbreviation and add support for display_day attribute - // $heading = substr( $weekday, 0, 3 ); - if ( 'short' == $atts['display_day'] ) { - $heading = radio_station_translate_weekday( $weekday, true ); - } else { - $heading = radio_station_translate_weekday( $weekday ); - } - $output .= ''; -} -$output .= ''; - -if ( !isset( $nextskip ) ) { - $nextskip = array(); -} - -foreach ( $master_list as $hour => $days ) { - - $output .= ''; - - $output .= ''; - - $curskip = $nextskip; - $nextskip = array(); - - foreach ( $days as $day => $min ) { - - // overly complex way of determining if we need to accomodate a rowspan due to a show spanning multiple hours - $continue = 0; - foreach ( $curskip as $x => $skip ) { - - if ( $skip['day'] === $day ) { - if ( $skip['span'] > 1 ) { - $continue = 1; - $skip['span'] = $skip['span'] - 1; - $curskip[$x]['span'] = $skip['span']; - $nextskip = $curskip; - } - } - } - - $rowspan = 0; - foreach ( $min as $show ) { - - if ( 0 === (int) $show['time']['start_hour'] && 0 === (int) $show['time']['end_hour'] ) { ///midnight to midnight shows - if ( $show['time']['start_min'] === $show['time']['end_min'] ) { //accomodate shows that end midway through the 12am hour - $rowspan = 24; - } - } - - if ( 0 === (int) $show['time']['end_hour'] && 0 !== (int) $show['time']['start_hour'] ) { - //fix shows that end at midnight (BUT take into account shows that start at midnight and end before the hour is up e.g. 12:00 - 12:30am), otherwise you could end up with a negative row span - $rowspan = $rowspan + ( 24 - $show['time']['start_hour'] ); - } elseif ( $show['time']['start_hour'] > $show['time']['end_hour'] ) { - // show runs from before midnight night until the next morning - if ( isset( $show['time']['real_start'] ) ) { - // if we're on the second day of a show that spans two days - $rowspan = $show['time']['end_hour']; - } else { - // if we're on the first day of a show that spans two days - $rowspan = $rowspan + ( 24 - $show['time']['start_hour'] ); - } - } else { - // all other shows - $rowspan = $rowspan + ( $show['time']['end_hour'] - $show['time']['start_hour'] ); - } - } - - $span = ''; - if ( $rowspan > 1 ) { - $span = ' rowspan="' . $rowspan . '"'; - // add to both arrays - $curskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $show['id'] ), - ); - $nextskip[] = array( - 'day' => $day, - 'span' => $rowspan, - 'show' => get_the_title( $show['id'] ), - ); - } - - // if we need to accomodate a rowspan, skip this iteration so we don't end up with an extra table cell. - if ( $continue ) { - continue; - } - - $output .= ''; - - foreach ( $min as $show ) { - - $terms = wp_get_post_terms( $show['id'], RADIO_STATION_GENRES_SLUG, array() ); - $classes = ' show-id-' . $show['id'] . ' ' . sanitize_title_with_dashes( str_replace( '_', '-', get_the_title( $show['id'] ) ) ) . ' '; - foreach ( $terms as $show_term ) { - $classes .= sanitize_title_with_dashes( $show_term->name ) . ' '; - } - - $output .= '
    '; - - if ( $atts['show_image'] ) { - // 2.3.0: get show avatar filtered by show ID and context - $show_avatar = radio_station_get_show_avatar( $show['id'] ); - $show_avatar = apply_filters( 'radio_station_schedule_show_avatar', $show_avatar, $show['id'], 'legacy' ); - if ( $show_avatar ) { - $output .= '' . $show_avatar . ''; - } - } - - $output .= ''; - if ( $atts['show_link'] ) { - // 2.3.0: filter show link via show ID - $show_link = get_permalink( $show['id'] ); - $show_link = apply_filters( 'radio_station_schedule_show_link', $show_link, $show['id'], 'legacy' ); - if ( $show_link ) { - $output .= '' . get_the_title( $show['id'] ) . ''; - } else { - $output .= get_the_title( $show['id'] ); - } - } else { - $output .= get_the_title( $show['id'] ); - } - $output .= ''; - - if ( $atts['show_djs'] ) { - - $output .= ''; - - $dj_names = get_post_meta( $show['id'], 'show_user_list', true ); - $count = 0; - - if ( $dj_names ) { - - $output .= ' '; - $output .= esc_html( __( 'with', 'radio-station' ) ); - $output .= ' '; - - foreach ( $dj_names as $name ) { - $count ++; - $user_info = get_userdata( $name ); - - $output .= $user_info->display_name; - - $names_count = count( $dj_names ); - if ( ( 1 === $count && 2 === $names_count ) || ( $names_count > 2 && $count === $names_count - 1 ) ) { - $output .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - } elseif ( $count < $names_count && $names_count > 2 ) { - $output .= ', '; - } - } - } - - $output .= ''; - } - - if ( $atts['show_times'] ) { - - $output .= ''; - - if ( 12 === (int) $atts['time'] ) { - - // 2.2.7: added meridiem translation - $starttime = strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ); - $endtime = strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ); - $times = date( 'g:i', $starttime ) . ' ' . radio_station_translate_meridiem( date( 'a', $starttime ) ); - $times .= ' - '; - $times .= date( 'g:i', $endtime ) . ' ' . radio_station_translate_meridiem( date( 'a', $endtime ) ); - - } else { - $times = date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['start_hour'] . ':' . $show['time']['start_min'] . ':00 ' ) ); - $times .= ' - '; - $times .= date( 'H:i', strtotime( '1981-04-28 ' . $show['time']['end_hour'] . ':' . $show['time']['end_min'] . ':00 ' ) ); - } - $time = apply_filters( 'radio_station_schedule_show_time', $times, $show['id'], 'legacy' ); - $output .= $time; - $output .= ''; - } - - // --- encore airing --- - if ( $atts['show_encore'] ) { - // 2.3.0: filter encore switch by show ID and context - if ( isset( $show['time']['encore'] ) ) { - $encore = $show['time']['encore']; - } else { - $encore = false; - } - $encore = apply_filters( 'radio_station_schedule_show_encore', $encore, $show['id'], 'legacy' ); - if ( 'on' == $encore ) { - $output .= ''; - $output .= esc_html( __( 'encore airing', 'radio-station' ) ); - $output .= ''; - } - } - - // --- show file --- - if ( $atts['show_file'] ) { - // 2.3.0: filter show file by show ID and context - $show_file = get_post_meta( $show['id'], 'show_file', true ); - $show_file = apply_filters( 'radio_station_schedule_show_file', $show_file, $show['id'], 'legacy' ); - // 2.3.2: check disable download meta - $disable_download = get_post_meta( $show['id'], 'show_download', true ); - if ( $show_file && !empty( $show_file ) && !$disable_download ) { - // 2.3.0: added missing span close tag - $output .= ''; - $output .= esc_html( __( 'Audio File', 'radio-station' ) ) . ''; - $output .= ''; - } - } - - $output .= '
    '; - } - $output .= ''; - } - - $output .= '
    '; -} -$output .= '
    ' . $heading . '
    '; - - // 2.2.7: added meridiem translations - if ( 12 === (int) $atts['time'] ) { - if ( 0 === $hour ) { - $output .= '12' . radio_station_translate_meridiem( 'am' ); - } elseif ( (int) $hour < 12 ) { - $output .= $hour . radio_station_translate_meridiem( 'am' ); - } elseif ( 12 === (int) $hour ) { - $output .= '12' . radio_station_translate_meridiem( 'pm' ); - } else { - $output .= ( $hour - 12 ) . radio_station_translate_meridiem( 'pm' ); - } - } else { - if ( $hour < 10 ) { - $output .= '0'; - } - $output .= $hour . ':00'; - } - - $output .= '
    '; From 148467bb2ce3de9845ff4fe927537c4a4f003a55 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 30 Sep 2021 08:23:48 +1000 Subject: [PATCH 233/377] dev updates #353 #384 --- CHANGELOG.md | 7 ++ includes/post-types-admin.php | 119 +++++++++++++++++----------------- radio-station-admin.php | 70 ++++++++++---------- radio-station.php | 59 +++++++++++------ readme.txt | 2 + 5 files changed, 143 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8ff045..db2141a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### +* Fixed: DJ / Host can edit own/others Show permissions +* Fixed: Override link to show dropdown query +* Fixed: Fallback scripts and fallback stream URLs +* Fixed: Radio Clock responsive width display +* Fixed: Collapse descriptions for non-show pages + ### 2.4.0.3 * Update: Plugin Panel (1.2.1) with zero value save and tab fixes * Added: option to disable player audio fallback scripts diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 82ba820..4061548 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -453,7 +453,7 @@ function radio_station_show_info_metabox() { // --- show active switch --- echo '
  • ' . PHP_EOL; - echo '
  • ' . PHP_EOL; @@ -528,7 +528,7 @@ function radio_station_show_info_metabox() { // 2.3.3.5: added action for further custom fields do_action( 'radio_station_show_fields', $post_id, 'show' ); - + echo '' . PHP_EOL; // --- close meta inner box --- @@ -596,7 +596,7 @@ function radio_station_show_info_metabox() { #show-inside-metaboxes .postbox.first {margin-right: 20px;} #show-inside-metaboxes .postbox.last {margin-left: 20px;} #show-inside-metaboxes .postbox select {max-width: 200px;}"; - + // --- filter and output styles --- // 2.3.3.9: added edit styles filter $css = apply_filters( 'radio_station_show_edit_styles', $css ); @@ -816,7 +816,7 @@ function radio_station_show_shifts_metabox() { // --- show inactive message --- // 2.3.0: added show inactive reminder message if ( !$table['active'] ) { - // 2.3.3.9: change to mismatched div class + // 2.3.3.9: change to mismatched div class echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); @@ -1274,7 +1274,7 @@ function radio_station_show_shifts_table( $post_id ) { 'encore' => '', 'disabled' => '', ); - + // --- check and sanitize possibly posted times --- // 2.3.3.9: for adding new show shift by querystring if ( isset( $_REQUEST['day'] ) ) { @@ -2122,7 +2122,7 @@ function radio_station_show_save_data( $post_id ) { } else { $shifts = $_POST['show_sched']; } - + $new_ids = array(); $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { @@ -2260,7 +2260,7 @@ function radio_station_show_save_data( $post_id ) { delete_post_meta( $post_id, 'show_sched' ); $show_shifts_changed = true; } - + // 2.3.3.9: clear out old unique shift IDs from prev_shifts if ( $show_shifts_changed && is_array( $prev_shifts ) && ( count( $prev_shifts ) > 0 ) ) { $prev_ids = array(); @@ -2284,7 +2284,7 @@ function radio_station_show_save_data( $post_id ) { // 2.3.3.9: maybe sync to linked override taxonomies $overrides = radio_station_get_linked_overrides( $post_id ); if ( $overrides && is_array( $overrides ) && ( count( $overrides ) > 0 ) ) { - + // --- get genre and language terms --- $genre_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); if ( count( $genre_terms ) > 0 ) { @@ -2405,7 +2405,7 @@ function radio_station_show_save_data( $post_id ) { $js .= "alert('" . esc_js( $warning ) . "');" . PHP_EOL; } - // --- output the scripts --- + // --- output the scripts --- echo ''; // 2.3.3.9: trigger action for single or multiple shift save @@ -2448,7 +2448,7 @@ function radio_station_show_delete( $post_id, $post ) { // --- clear all cached schedule data --- // 2.4.0.3: added second argument to cache clear radio_station_clear_cached_data( $post_id, $post->post_type ); - + // --- clear from unique shift IDs list --- // 2.3.3.9: added to keep unique ID list from bloating over time $shift_ids = get_option( 'radio_station_shifts_ids' ); @@ -2814,10 +2814,13 @@ function radio_station_override_show_metabox() { echo '
    '; // --- get all shows --- + // 2.4.0.4: fix to remove show limit in query + // 2.4.0.4: added pending and future post statuses $args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => array( 'publish', 'draft' ), + 'post_status' => array( 'publish', 'draft', 'pending', 'future' ), 'orderby' => 'modified', + 'numberposts' => -1, ); $shows = get_posts( $args ); @@ -2843,8 +2846,8 @@ function radio_station_override_show_metabox() { echo '
    ' . PHP_EOL; echo '' . PHP_EOL; - + // --- sync genre taxonomy --- $sync_genres = get_post_meta( $post_id, 'sync_genres', true ); echo '
  • ' . PHP_EOL; @@ -2893,10 +2896,10 @@ function radio_station_override_show_metabox() { echo '' . esc_html( __( 'If checked, assigned Language terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . PHP_EOL; echo '
  • ' . PHP_EOL; echo '' . PHP_EOL; - + // --- close linked show list --- echo '
    ' . PHP_EOL; - + // --- get show meta --- // $active = get_post_meta( $post->ID, 'show_active', true ); $link = get_post_meta( $post_id, 'show_link', true ); @@ -2914,7 +2917,7 @@ function radio_station_override_show_metabox() { echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ) . PHP_EOL; echo '
    ' . PHP_EOL; - // --- table headings --- + // --- table headings --- echo '' . PHP_EOL; - // --- excerpt --- + // --- excerpt --- echo '' . PHP_EOL; - + // --- avatar --- echo '' . PHP_EOL; - + // --- website --- echo '' . PHP_EOL; - + // 2.3.3.9: change to single cell spanning 3 columns echo '' . PHP_EOL; - + // 2.3.3.9: change to single cell spanning 3 columns echo ''; @@ -1781,7 +1785,7 @@ public function settings_header() { // --- subtitle --- // 1.1.9: added optional subtitle filter display $subtitle = apply_filters( $namespace . '_settings_page_subtitle', '' ); - if ( '' != $subtitle ) { + if ( '' != $subtitle ) { echo ''; @@ -2011,7 +2015,7 @@ public function settings_table() { $defaults = $this->default_settings(); $settings = $this->get_settings( false ); - // --- output saved settings --- + // --- output saved settings --- if ( $this->debug ) { echo "
    Saved Settings:
    "; print_r( $settings ); @@ -3207,8 +3211,8 @@ function radio_station_settings_row( $option, $setting ) { // - added media library upload image field type // - added color picker and color picker alpha field types // - automatically remove unused settings tabs -// - fix to text field attribute quoting -// - fix to not escape number step button function +// - fix to text field attribute quoting +// - fix to not escape number step button function // - remove FILTER_VALIDATE_URL from URL saving (not working) // == 1.1.6 == diff --git a/radio-station-admin.php b/radio-station-admin.php index d7a2a7e..76bf89f 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -617,23 +617,25 @@ function radio_station_get_upgrade_notice() { } if ( property_exists( $pluginupdates, 'response' ) ) { foreach ( $pluginupdates->response as $file => $update ) { - if ( $update->slug == $pluginslug ) { - if ( property_exists( $update, 'upgrade_notice' ) ) { - - // 2.3.3.9: compare new version with installed version - $new_version = $update->new_version; - $version = radio_station_plugin_version(); - if ( version_compare( $version, $new_version, '<' ) ) { - - // --- parse upgrade notice --- - $notice = $update->upgrade_notice; - $notice = radio_station_parse_upgrade_notice( $notice ); - $notice['update_id'] = str_replace( '.', '', $new_version ); - if ( property_exists( $update, 'icons' ) && isset( $update->icons['1x'] ) ) { - $notice['icon_url'] = $update->icons['1x']; + if ( is_object( $update ) && property_exists( $update, 'slug' ) ) { + if ( $update->slug == $pluginslug ) { + if ( property_exists( $update, 'upgrade_notice' ) ) { + + // 2.3.3.9: compare new version with installed version + $new_version = $update->new_version; + $version = radio_station_plugin_version(); + if ( version_compare( $version, $new_version, '<' ) ) { + + // --- parse upgrade notice --- + $notice = $update->upgrade_notice; + $notice = radio_station_parse_upgrade_notice( $notice ); + $notice['update_id'] = str_replace( '.', '', $new_version ); + if ( property_exists( $update, 'icons' ) && isset( $update->icons['1x'] ) ) { + $notice['icon_url'] = $update->icons['1x']; + } + $notice['plugin_file'] = $file; + break; } - $notice['plugin_file'] = $file; - break; } } } diff --git a/radio-station.php b/radio-station.php index 327b527..11ff360 100644 --- a/radio-station.php +++ b/radio-station.php @@ -7,14 +7,14 @@ /* Plugin Name: Radio Station -Plugin URI: https://netmix.com/radio-station +Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.3.4 +Version: 2.4.0.3.5 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages -Author URI: https://netmix.com/radio-station +Author URI: https://netmix.com/ GitHub Plugin URI: netmix/radio-station Copyright 2019 Digital Strategy Works (email : info@digitalstrategyworks.com) @@ -93,7 +93,7 @@ define( 'RADIO_STATION_FILE', __FILE__ ); define( 'RADIO_STATION_DIR', dirname( __FILE__ ) ); define( 'RADIO_STATION_BASENAME', plugin_basename( __FILE__ ) ); -define( 'RADIO_STATION_HOME_URL', 'https://radiostation.pro/' ); +define( 'RADIO_STATION_HOME_URL', 'https://radiostation.pro/radio-station/' ); define( 'RADIO_STATION_DOCS_URL', 'https://radiostation.pro/docs/' ); // define( 'RADIO_STATION_API_DOCS_URL', 'https://radiostation.pro/docs/api/' ); define( 'RADIO_STATION_PRO_URL', 'https://radiostation.pro/' ); @@ -134,20 +134,26 @@ require RADIO_STATION_DIR . '/includes/data-feeds.php'; require RADIO_STATION_DIR . '/includes/legacy.php'; +// --- Player --- +// 2.4.0.4: include player as standard +require RADIO_STATION_DIR . '/player/radio-player.php'; + // --- Shortcodes --- require RADIO_STATION_DIR . '/includes/master-schedule.php'; require RADIO_STATION_DIR . '/includes/shortcodes.php'; // --- Widgets --- +// 2.4.0.4: move player widget here require RADIO_STATION_DIR . '/includes/class-current-show-widget.php'; require RADIO_STATION_DIR . '/includes/class-upcoming-shows-widget.php'; require RADIO_STATION_DIR . '/includes/class-current-playlist-widget.php'; require RADIO_STATION_DIR . '/includes/class-radio-clock-widget.php'; +require RADIO_STATION_DIR . '/includes/class-radio-player-widget.php'; // --- Feature Development --- // 2.3.0: add feature branch development includes // 2.3.1: added radio player widget file -$features = array( 'class-radio-player-widget', 'import-export' ); +$features = array( 'import-export' ); foreach ( $features as $feature ) { $filepath = RADIO_STATION_DIR . '/includes/' . $feature . '.php'; if ( file_exists ( $filepath ) ) { @@ -155,14 +161,6 @@ } } -// --- Player --- -// 2.3.1.1: load radio player prototype (if present) -$player_file = RADIO_STATION_DIR . '/player/radio-player.php'; -if ( file_exists( $player_file ) ) { - require $player_file; -} - - // --------------------------- // Plugin Options and Defaults // --------------------------- @@ -1449,10 +1447,14 @@ // Pro Version Install Check // ------------------------- // 2.4.0.3: added check active/installed Pro version +// 2.4.0.4: add defaults for has_addons and has_plans +$has_addons = false; +$has_plans = true; $plan = 'free'; // --- check for deactivated pro plugin --- -$plugins = wp_cache_get( 'plugins', 'plugins' ); +// 2.4.0.4: remove unnecessary second argument to wp_cache_get +$plugins = wp_cache_get( 'plugins' ); if ( !$plugins ) { if ( function_exists( 'get_plugins' ) ) { $plugins = get_plugins(); @@ -1467,8 +1469,17 @@ if ( $plugins && is_array( $plugins ) && ( count( $plugins ) > 0 ) ) { foreach ( $plugins as $slug => $plugin ) { if ( strstr( $slug, 'radio-station-pro.php' ) ) { - $plan = 'premium'; - break; + // 2.4.0.4: only set premium for upgrade version + if ( isset( $plugin['Name'] ) && strstr( $plugin['Name'], '(Premium)' ) ) { + $plan = 'premium'; + break; + } else { + // 2.4.0.4: detect and force enable addon version + $plan = 'premium'; + $has_addons = true; + $has_plans = false; + break; + } } } } @@ -1515,13 +1526,13 @@ // 2.4.0.1: turn on addons switch for Pro // 2.4.0.3: turn on plans switch for Pro also // 2.4.0.3: set Pro details and Upgrade links + // 2.4.0.4: change upgrade_link to -upgrade 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', - 'hasplans' => true, - // 'upgrade_link' => RADIO_STATION_PRO_URL . 'pricing/', - 'upgrade_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), + 'hasplans' => $has_plans, + 'upgrade_link' => add_query_arg( 'page', $slug . '-upgrade', admin_url( 'admin.php' ) ), 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', - 'hasaddons' => false, + 'hasaddons' => $has_addons, 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), 'plan' => $plan, ); @@ -3036,7 +3047,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po // --- get more Shows related to this related Post --- // 2.3.3.6: allow for multiple related posts - // 2.3.3.9: added 'i:' prefix to LIKE vlaue matches + // 2.3.3.9: added 'i:' prefix to LIKE value matches global $wpdb; $query = "SELECT post_id,meta_value FROM " . $wpdb->prefix . "postmeta" . " WHERE meta_key = 'post_showblog_id' AND meta_value LIKE '%i:" . $related_shows[0] . "%'"; @@ -3567,11 +3578,16 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { $hosts = get_post_meta( $post->ID, 'show_user_list', true ); $producers = get_post_meta( $post->ID, 'show_producer_list', true ); + // 2.3.0.4: convert possible (old) non-array values if ( !$hosts || empty( $hosts ) ) { $hosts = array(); + } elseif ( !is_array( $hosts ) ) { + $hosts = array( $hosts ); } if ( !$producers || empty( $producers ) ) { $producers = array(); + } elseif ( !is_array( $producers ) ) { + $producers = array( $producers ); } // ---- revoke editing capability if not assigned to this show --- @@ -3685,3 +3701,15 @@ function radio_station_debug( $data, $echo = true, $file = false ) { error_log( $data, 3, $file ); } } + +// --------------------- +// Freemius Object Debug +// --------------------- +// 2.4.0.4: added to debug freemius instance +add_action( 'shutdown', 'radio_station_freemius_debug' ); +function radio_station_freemius_debug() { + if ( is_admin() && RADIO_STATION_DEBUG && current_user_can( 'manage_options' ) ) { + $instance = radio_station_freemius_instance(); + echo 'Freemius Object: ' . print_r( $instance, true ) . ''; + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 93b62f9..c7931d5 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # Radio Station -Radio Station let's you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. +Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. ## Plugin Details @@ -23,11 +23,11 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html ## Description -Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. It's functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. +Radio Station by NetMix is a plugin to build and manage a Show Schedule for a radio station or internet broadcaster's WordPress website, including podcasters. Its functionality was originally based on Drupal 6's Station plugin, reworked for use in Wordpress and then extended. The plugin adds a new "Show" post type, schedulable blocks of time that contain a Show description, a Show shifts repeater field, assignable images and other meta information. You can also create Playlists associated with those shows, or assign standard blog posts to relate to a Show. It also supports adding Schedule Overrides for specific dates and times, and adds the ability to associate users (given a role of "Host" or "Producer") to Shows, so they can be displayed for that Show and to give them edit access. -A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has it's own dedicated page to display all the Show details in a responsive layout. +A schedule of all Shows can be generated and added to a page with a shortcode (or simple page selection in the Plugin Settings) which has a number of Layout and display options. Shows can be categorized into Genres and a Genre highlighting filter appears on the embedded Schedule view. Each Show has its own dedicated page to display all the Show details in a responsive layout. The plugin contains a widget to display the on-air Current Show linked to the Show page, with various widget display options, and further widgets for displaying Upcoming Shows and current Playlist tracks. Shortcodes are available for these widgets, as well as for displaying archive lists of any of the plugin's custom post types. @@ -47,7 +47,7 @@ Since 2.3.0, the first major feature update since plugin takeover in July 2019, * Admin Plugin Settings Page (with a plethora of new options) * ...and a Radio Station Data API via the WordPress REST API! -If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards oompatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. +If you have been using Radio Station prior to version 2.3.0 and want to update, it is recommended that you read [the blog post for the 2.3.0 release](https://netmix.com/2-3-0-release-announcement/). As there is quite a lot of refactoring and changes in this version, you will want to check the details of the new changes with your current usage - especially if you have been using any custom page templates in your theme or other plugin-related custom code on your site. As these are probably the most significant changes that will ever be made to the plugin in a release, we have worked hard to maintain backwards compatibility and test the new features thoroughly, but it's important you know what is going and test things out yourself in the update process. ### Support and Contribution @@ -128,7 +128,7 @@ Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpr #### What if I want to change or style the plugin's displays? -The default styles for Radio Station have intionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. +The default styles for Radio Station have intentionally been kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. #### What Widgets are available with this plugin? diff --git a/readme.txt b/readme.txt index c4a90e2..3db8bb3 100644 --- a/readme.txt +++ b/readme.txt @@ -8,7 +8,7 @@ Stable tag: 2.4.0.3 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html -Radio Station let's you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. +Radio Station lets you build and manage a Show Schedule for a radio station or Internet broadcaster's WordPress website. == Description == @@ -116,7 +116,7 @@ Try re-saving your site's permalink settings via Settings -> Permalinks. Wordpr = What if I want to change or style the plugin's displays? = -The default styles for Radio Station have intionally kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. +The default styles for Radio Station have intentionally been kept fairly minimal so as to be compatible with most themes, so you may wish to add your own styles to suit your site's look and feel. The best way to do this is to add your own `rs-custom.css` to your Child Theme's directory, and add more specific style rules that modify or override the existing styles. Radio Station will automatically detect the presence of this file and enqueue it. You can find the base styles in the `/css/` directory of the plugin. = What Widgets are available with this plugin? = @@ -219,11 +219,13 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.4 = +* Improved: clear cache on show/override status transitions * Fixed: DJ / Host can edit own/others Show permissions * Fixed: Override link to show dropdown query * Fixed: Fallback scripts and fallback stream URLs * Fixed: Radio Clock responsive width display * Fixed: Collapse descriptions for non-show pages +* Fixed: adjust Tabbed schedule dates for daylight saving = 2.4.0.3 = * Update: Plugin Panel (1.2.1) with zero value save and tab fixes @@ -346,7 +348,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Improved: show shifts interface background colors * Added: navigate away from page on shift change check * Added: default time format option to Widgets -* Removed: current show transients (intermittant unreliability) +* Removed: current show transients (intermittent unreliability) * Fixed: AJAX call causing plugin conflicts via save_post action * Fixed: calculation of Upcoming Shows near end of the week * Fixed: remove and duplicate actions on new shifts @@ -447,7 +449,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Add Show list columns with active, shift, DJs and show image displays * Add Schedule Override list columns with date sorting and filtering * Add playlist track information labels to Now Playing Widget -* Added meridiem (am/pm) translations via WP Locale class +* Added meridian (am/pm) translations via WP Locale class * Added star rating link to plugin announcement box * Added update subscription form to plugin Help page * Fix to checkbox value saving for On Air/Upcoming Widgets @@ -490,7 +492,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * shift main playlist and show metaboxes above editor * set plugin custom post types editor to Classic Editor * add high priority to side metaboxes for plugin post types -* added dismissable development changeover admin notice +* added dismissible development changeover admin notice * added simple Patreon supporter image button and blurb * added filter for DJ Avatar size on Author page template * fix to Schedule Override metabox value saving @@ -554,7 +556,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed issue on the master schedule where genres containing more than one work wouldn't highlight when clicked * Added ability to display DJ names on the master schedule. * Fixed bug in the Upcoming widget. Override Schedule no longer display as upcoming when they are on-air. -* Verified compatibility woth WordPress 4.0 +* Verified compatibility with WordPress 4.0 = 2.0.13 = * Added the ability to display show avatars on the program grid. @@ -600,7 +602,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Master Schedule bug for shows that start at midnight and end before the hour is up fixed. = 2.0.3 = -* Compatibility fix: Fixed a jquery conflict in the backend that was occuring in certain themes +* Compatibility fix: Fixed a jquery conflict in the backend that was occurring in certain themes = 2.0.2 = * Bug fix: Scheduling issue with overnight shows fixed @@ -664,7 +666,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed some areas of the code that were generating PHP notices in WordPress 3.6 = 1.4.3 = -* Master schedule shortcode now displays indiviual shows in both 24 and 12 hour time +* Master schedule shortcode now displays individual shows in both 24 and 12 hour time * Fixed some areas of the code that were generating PHP notices in WordPress 3.6 * Added example of how to display show schedule to single-show.php template * Added more options to the plugin's widgets @@ -726,7 +728,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Reduced the size of the content field in the playlist post type * Some minor formatting changes to default templates * Added genre highlighter to the master programming schedule page -* Added a second Update button on the bottom of the playlist edit page for convinience. +* Added a second Update button on the bottom of the playlist edit page for convenience. * Added sample template for DJ user pages * Fixed a bug in the master schedule shortcode that messed up the table for shows that are more than two hours in duration * Fixed a bug in the master schedule shortcode to accomodate shows that run from late night into the following morning. diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 805276d..554b091 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -118,7 +118,9 @@ // 2.3.2: set day start and end times // 2.3.2: replace strtotime with to_time for timezones + // 2.4.0.4: adjust day start time for daylight saving $day_start_time = radio_station_to_time( $weekdates[$weekday] . ' 00:00' ); + $day_start_adjusted = $day_start_time + ( 2 * 60 * 60 ); $day_end_time = $day_start_time + ( 24 * 60 * 60 ); // 2.2.2: use translate function for weekday string @@ -137,16 +139,16 @@ if ( $atts['display_date'] ) { // 2.3.3.5: allow for attribute to be set to 1 for default display if ( '1' == $atts['display_date'] ) { - $date_subheading = radio_station_get_time( 'jS', $day_start_time ); + $date_subheading = radio_station_get_time( 'jS', $day_start_adjusted ); } else { - $date_subheading = radio_station_get_time( $atts['display_date'], $day_start_time ); + $date_subheading = radio_station_get_time( $atts['display_date'], $day_start_adjusted ); } } else { - $date_subheading = radio_station_get_time( 'j', $day_start_time ); + $date_subheading = radio_station_get_time( 'j', $day_start_adjusted ); } // 2.3.2: add attribute for short or long month display - $month = radio_station_get_time( 'F', $day_start_time ); + $month = radio_station_get_time( 'F', $day_start_adjusted ); if ( $atts['display_month'] && !in_array( $atts['display_month'], array( 'short', 'full', 'long' ) ) ) { $atts['display_month'] = 'short'; } @@ -476,7 +478,7 @@ $times .= '
    ' . $newline; $times .= '[' . $newline; $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; - $times .= ']' . $newline; + $times .= ']' . $newline; $times .= '
    ' . $newline; $info['times'] = $times; $tcount ++; diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 3a1a7b2..04777fd 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -332,6 +332,10 @@ $label = apply_filters( 'radio_station_show_hosts_label', $label, $post_id ); $meta_blocks['hosts'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; + // 2.4.0.4: convert possible (old) non-array values + if ( !is_array( $hosts ) ) { + $hosts = array( $hosts ); + } $host_count = count( $hosts ); foreach ( $hosts as $host ) { $count ++; @@ -367,6 +371,10 @@ $label = apply_filters( 'radio_station_show_producers_label', $label, $post_id ); $meta_blocks['producers'] .= '' . esc_html( $label ) . ': ' . $newline; $count = 0; + // 2.4.0.4: convert possible (old) non-array values + if ( !is_array( $producers ) ) { + $producers = array( $producers ); + } $producer_count = count( $producers ); foreach ( $producers as $producer ) { $count ++; From 411734681026698dd7a8d7470d45e38035c12d91 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 28 Oct 2021 17:45:56 +1000 Subject: [PATCH 236/377] function typo fix --- includes/support-functions.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index 624c37c..9754bd4 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -4093,23 +4093,6 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { $weekdates[$weekday] = $weekdate; } - // 2.4.0.4: check/fix for duplicate date crackliness (daylight saving?) - foreach ( $weekdates as $day => $date ) { - if ( isset( $prevdate ) && ( $prevdate == $date ) ) { - $weekdates[$day] = radio_station_get_next_date( $date ); - $found = false; - foreach ( $weekdates as $k => $v ) { - if ( $found ) { - $weekdates[$k] = radio_station_next_date[$v]; - } - if ( $k == $day ) { - $found = true; - } - } - } - $prevdate = $date; - } - if ( RADIO_STATION_DEBUG ) { echo ''; echo 'Time: ' . $time . PHP_EOL; From ea6892b9de2c01555268b218f872d80ac8a753f5 Mon Sep 17 00:00:00 2001 From: majick777 Date: Fri, 29 Oct 2021 22:10:33 +1000 Subject: [PATCH 237/377] 2.4.0.4 release updates --- CHANGELOG.md | 2 ++ docs/Pro.md | 4 +++- docs/Shortcodes.md | 34 ++++++++++++++++++++++++++++ includes/post-types.php | 41 +++++++++++++++++++--------------- includes/support-functions.php | 17 ++++++++++++++ radio-station.php | 2 +- readme.txt | 6 ++--- 7 files changed, 83 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b47a79b..62879ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.4 +* Improved: clear cache on show/override status transitions * Fixed: DJ / Host can edit own/others Show permissions * Fixed: Override link to show dropdown query * Fixed: Fallback scripts and fallback stream URLs * Fixed: Radio Clock responsive width display * Fixed: Collapse descriptions for non-show pages +* Fixed: Deduplicate dates in week (daylight saving fix) ### 2.4.0.3 * Update: Plugin Panel (1.2.1) with zero value save and tab fixes diff --git a/docs/Pro.md b/docs/Pro.md index f968ec7..0c14c43 100644 --- a/docs/Pro.md +++ b/docs/Pro.md @@ -38,7 +38,9 @@ Note that additional Pro Documentation will be added as new Features are release [Schedule Grid View](./Shortcodes.md#master-schedule-shortcode) [Schedule Calendar View](./Shortcodes.md#master-schedule-shortcode) [Multiple View Switching](./Shortcodes.md#pro-multiple-view-switching) -[Show Episodes Archive Shortcode](./Shortcodes.md#pro-show-episodes-archive-shortcode +[Hosts Archive Shortcode](./Shortcodes.md#pro-hosts-archive-shortcode) +[Producers Archive Shortcode](./Shortcodes.md#pro-producers-archive-shortcode) +[Show Episodes Archive Shortcode](./Shortcodes.md#pro-show-episodes-archive-shortcode) #### Widgets [Sitewide Bar Player(./Player.md#pro-sitewide-bar-player) diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 37c5a55..004107d 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -213,6 +213,40 @@ The following attributes are available for this shortcode: [Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/languages-archive/) +### [Pro] Hosts Archive Shortcode + +`[host-archive]` or `[hosts-archive]` + +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : View option. 'grid' option. Default 0 (list) +* *status* : Query for Host status. Default 'publish'. +* *perpage* : Query for number of Hosts. Default -1 (all) +* *offset* : Query for Host offset. Default '' (no offset) +* *orderby* : Query to order Host display by. Default 'title'. +* *order* : Query order for Hosts. Default 'ASC'. +* *thumbnails* : Display profile image. 0 or 1. Default 0. +* *social* : Display social profile icons. 0 or 1. Default 1. +* *shows* : Display a list of Shows assigned to Host. 0 or 1. Default 1. + +[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/hosts-archive/) + +### [Pro] Producers Archive Shortcode + +`[producer-archive]` or `[producers-archive]` + +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : View option. 'grid' option. Default 0 (list) +* *status* : Query for Producer status. Default 'publish'. +* *perpage* : Query for number of Producers. Default -1 (all) +* *offset* : Query for producer offset. Default '' (no offset) +* *orderby* : Query to order Producer display by. Default 'title'. +* *order* : Query order for Producers. Default 'ASC'. +* *thumbnails* : Display profile image. 0 or 1. Default 0. +* *social* : Display social profile icons. 0 or 1. Default 1. +* *shows* : Display a list of Shows assigned to Producer. 0 or 1. Default 1. + +[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/producers-archive/) + ### Show Posts Archive Shortcode `[show-posts-archive]` (or `[show-post-archive]`) diff --git a/includes/post-types.php b/includes/post-types.php index 3a59cda..45aba1b 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -61,7 +61,8 @@ function radio_station_create_post_types() { 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Shows', 'radio-station' ), + // 2.4.0.4: change to description (as displayed in some archive templates) + 'description' => __( 'Shows Archive', 'radio-station' ), 'public' => true, 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), 'hierarchical' => false, @@ -110,7 +111,8 @@ function radio_station_create_post_types() { 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Playlists', 'radio-station' ), + // 2.4.0.4: change to description (as displayed in some archive templates) + 'description' => __( 'Playlists Archive', 'radio-station' ), 'public' => true, 'hierarchical' => false, // 2.3.0: added thumbnail, custom field and revision support @@ -156,7 +158,8 @@ function radio_station_create_post_types() { 'show_in_menu' => false, // now added to main menu 'show_in_admin_bar' => false, // this is done manually 'show_in_rest' => true, - 'description' => __( 'Post type for Schedule Overrides', 'radio-station' ), + // 2.4.0.4: change to description (as displayed in some archive templates) + 'description' => __( 'Schedule Overrides Archive', 'radio-station' ), 'public' => true, // 2.3.0: added taxonomies to overrides 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), @@ -208,7 +211,8 @@ function radio_station_create_post_types() { 'show_in_admin_bar' => false, 'show_in_nav_menus' => false, 'show_in_rest' => true, - 'description' => __( 'Post type for DJ / Host Profiles', 'radio-station' ), + // 2.4.0.4: change to description (as displayed in some archive templates) + 'description' => __( 'Host Profiles Archive', 'radio-station' ), 'exclude_from_search' => false, 'public' => true, 'hierarchical' => false, @@ -259,11 +263,12 @@ function radio_station_create_post_types() { 'show_in_admin_bar' => false, 'show_in_nav_menus' => false, 'show_in_rest' => true, - 'description' => __( 'Post type for Producer Profiles', 'radio-station' ), + // 2.4.0.4: change to description (as displayed in some archive templates) + 'description' => __( 'Producer Profiles Archive', 'radio-station' ), 'exclude_from_search' => false, 'public' => true, 'hierarchical' => false, - // 2.3.3.9: set can_export true + // 2.3.3.9: set can_export true 'can_export' => true, // 2.3.3.9: added all post type supports // 2.4.1.4: added author support for quick edit @@ -298,8 +303,8 @@ function radio_station_create_post_types() { function radio_station_post_type_editor( $can_edit, $post_type ) { // 2.3.2: added host and producer slugs $post_types = array( - RADIO_STATION_SHOW_SLUG, - RADIO_STATION_PLAYLIST_SLUG, + RADIO_STATION_SHOW_SLUG, + RADIO_STATION_PLAYLIST_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG. RADIO_STATION_PRODUCER_SLUG, @@ -324,7 +329,7 @@ function radio_station_add_featured_image_support() { $supported_types = get_theme_support( 'post-thumbnails' ); if ( false === $supported_types ) { $post_types = array( - RADIO_STATION_SHOW_SLUG, + RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG, RADIO_STATION_HOST_SLUG, RADIO_STATION_PRODUCER_SLUG, @@ -607,7 +612,7 @@ function radio_station_override_show_avatar_id( $avatar_id, $post_id ) { if ( false !== $override ) { return $override; } - } + } return $avatar_id; } @@ -630,7 +635,7 @@ function radio_station_override_thumbnail_id( $id, $object_id, $meta_key, $singl $override = radio_station_get_show_override( $object_id, 'show_image' ); if ( false !== $override ) { return $override; - } + } } return $id; } @@ -643,7 +648,7 @@ function radio_station_override_show_hosts( $hosts, $post_id ) { } return $hosts; } - + // --- Show Producers --- function radio_station_override_show_producers( $producers, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_producer_list' ); @@ -652,7 +657,7 @@ function radio_station_override_show_producers( $producers, $post_id ) { } return $producers; } - + // --- Show Website Link --- function radio_station_override_show_link( $show_link, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_link' ); @@ -661,7 +666,7 @@ function radio_station_override_show_link( $show_link, $post_id ) { } return $show_link; } - + // --- Show Email --- function radio_station_override_show_email( $show_email, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_email' ); @@ -670,7 +675,7 @@ function radio_station_override_show_email( $show_email, $post_id ) { } return $show_email; } - + // --- Show Phone --- function radio_station_override_show_phone( $show_phone, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_phone' ); @@ -679,7 +684,7 @@ function radio_station_override_show_phone( $show_phone, $post_id ) { } return $show_phone; } - + // --- Show File --- function radio_station_override_show_file( $show_file, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_file' ); @@ -688,7 +693,7 @@ function radio_station_override_show_file( $show_file, $post_id ) { } return $show_file; } - + // --- Disable Download --- function radio_station_override_show_download( $show_download, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_download' ); @@ -697,7 +702,7 @@ function radio_station_override_show_download( $show_download, $post_id ) { } return $show_download; } - + // --- Show Patreon --- function radio_station_override_show_patreon( $show_patreon, $post_id ) { $override = radio_station_get_show_override( $post_id, 'show_patreon' ); diff --git a/includes/support-functions.php b/includes/support-functions.php index 9754bd4..6a21771 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -4093,6 +4093,23 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { $weekdates[$weekday] = $weekdate; } + // 2.4.0.4: check/fix for duplicate date crackliness (daylight saving?) + foreach ( $weekdates as $day => $date ) { + if ( isset( $prevdate ) && ( $prevdate == $date ) ) { + $weekdates[$day] = radio_station_get_next_date( $date ); + $found = false; + foreach ( $weekdates as $k => $v ) { + if ( $found ) { + $weekdates[$k] = radio_station_get_next_date( $v ); + } + if ( $k == $day ) { + $found = true; + } + } + } + $prevdate = $date; + } + if ( RADIO_STATION_DEBUG ) { echo ''; echo 'Time: ' . $time . PHP_EOL; diff --git a/radio-station.php b/radio-station.php index 11ff360..1f1aaed 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.3.5 +Version: 2.4.0.4 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 3db8bb3..146bea8 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: tonyzeoli, majick Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 -Tested up to: 5.8 -Stable tag: 2.4.0.3 +Tested up to: 5.8.1 +Stable tag: 2.4.0.4 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -225,7 +225,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed: Fallback scripts and fallback stream URLs * Fixed: Radio Clock responsive width display * Fixed: Collapse descriptions for non-show pages -* Fixed: adjust Tabbed schedule dates for daylight saving +* Fixed: Deduplicate dates in week (daylight saving fix) = 2.4.0.3 = * Update: Plugin Panel (1.2.1) with zero value save and tab fixes From c97af7b4a47f59ca192f0c6e7a1b5b2efc2a9cd1 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 1 Nov 2021 13:36:26 +1000 Subject: [PATCH 238/377] early capability check plugin conflict fix --- radio-station.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/radio-station.php b/radio-station.php index 1f1aaed..4038226 100644 --- a/radio-station.php +++ b/radio-station.php @@ -3493,6 +3493,11 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { global $post, $wp_roles; + // 2.4.0.4.1: fix for early capability check plugin conflict + if ( !function_exists( 'radio_station_get_setting' ) ) { + return $allcaps; + } + // --- check if super admin --- // 2.3.3.6: get user object from fourth argument instead // ? fix to not revoke edit caps from super admin ? From d11ea99dcd1d055d74092d4ba2fc1f9436d83b51 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 4 Nov 2021 09:55:53 +1000 Subject: [PATCH 239/377] bump version number --- CHANGELOG.md | 3 +++ radio-station.php | 2 +- readme.txt | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62879ad..ef600d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.4.0.5 +* Fixed: plugin conflicts causing fatal errors + ### 2.4.0.4 * Improved: clear cache on show/override status transitions * Fixed: DJ / Host can edit own/others Show permissions diff --git a/radio-station.php b/radio-station.php index 4038226..a5cca61 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.4 +Version: 2.4.0.5 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 8fd1d2a..9ef94b4 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://netmix.co/donate Tags: dj, music, playlist, radio, shows, scheduling, broadcasting Requires at least: 3.3.1 Tested up to: 5.8.1 -Stable tag: 2.4.0.4 +Stable tag: 2.4.0.5 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -218,6 +218,9 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.4.0.5 = +* Fixed: plugin conflicts causing fatal errors + = 2.4.0.4 = * Improved: clear cache on show/override status transitions * Fixed: DJ / Host can edit own/others Show permissions From f67bb096e140c2d48a8f72043bf903448a0c4757 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 20 Dec 2021 14:45:04 +1000 Subject: [PATCH 240/377] update demo links in docs --- docs/Display.md | 2 +- docs/Player.md | 2 +- docs/Shortcodes.md | 40 ++++++++++++++++++++-------------------- docs/Widgets.md | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/Display.md b/docs/Display.md index 425466a..327a224 100644 --- a/docs/Display.md +++ b/docs/Display.md @@ -2,7 +2,7 @@ *** -For live display examples see [Radio Station Demo Site](http://radiostationdemo.com). +For live display examples see [Radio Station Demo Site](http://demo.radiostation.pro). ## Styling diff --git a/docs/Player.md b/docs/Player.md index 8d6938c..8013db6 100644 --- a/docs/Player.md +++ b/docs/Player.md @@ -40,7 +40,7 @@ The following attributes are available for this shortcode: * *image* : Player/Station Image. URL (recommended size 256x256). Default none. * *volume* : Initial Player Volume. 0-100. Default: 77 -[Demo Site Example Output](https://radiostationdemo.com/player-shortcode/) +[Demo Site Example Output](https://demo.radiostation.pro/player-shortcode/) #### Shortcode in Templates diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 004107d..12b1e21 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -4,7 +4,7 @@ Note if you want to display a Shortcode within a custom Template, you can use the WordPress `do_shortcode` function. eg. `do_shortcode('[master-schedule]');` -Shortcode Output Examples can be seen on the [Radio Station Demo Site](http://radiostationdemo.com) +Shortcode Output Examples can be seen on the [Radio Station Demo Site](http://demo.radiostation.pro) ## Master Schedule @@ -13,13 +13,13 @@ Shortcode Output Examples can be seen on the [Radio Station Demo Site](http://ra Use the shortcode `[master-schedule]` on any page. This will generate a full-page schedule in one of five Views: -* [Table](https://radiostationdemo.com/master-schedule/table-view/) (default) - responsive program in table form -* [Tabbed](https://radiostationdemo.com/master-schedule/tabbed-view/) - responsive styled list view with day selection tabs -* [List](https://radiostationdemo.com/master-schedule/list-view/) - unstyled plain list view for custom development use -* [Divs](https://radiostationdemo.com/master-schedule/divs-view/) - (deprecated - display issues) legacy unstyled div-based view -* [Legacy](https://radiostationdemo.com/master-schedule/legacy-view/) - (deprecated) legacy table view -* [Grid](https://radiostationdemo.com/master-schedule/grid-view/) - [Pro] extra grid style view available in Pro version -* [Calendar](https://radiostationdemo.com/master-schedule/calendar-view/) - [Pro] extra calendar style view available in Pro version +* [Table](https://demo.radiostation.pro/master-schedule/table-view/) (default) - responsive program in table form +* [Tabbed](https://demo.radiostation.pro/master-schedule/tabbed-view/) - responsive styled list view with day selection tabs +* [List](https://demo.radiostation.pro/master-schedule/list-view/) - unstyled plain list view for custom development use +* [Divs](https://demo.radiostation.pro/master-schedule/divs-view/) - (deprecated - display issues) legacy unstyled div-based view +* [Legacy](https://demo.radiostation.pro/master-schedule/legacy-view/) - (deprecated) legacy table view +* [Grid](https://demo.radiostation.pro/master-schedule/grid-view/) - [Pro] extra grid style view available in Pro version +* [Calendar](https://demo.radiostation.pro/master-schedule/calendar-view/) - [Pro] extra calendar style view available in Pro version The above View names are linked to examples on the Demo Site. @@ -71,7 +71,7 @@ function my_custom_multiview_attributes( $atts ) { } ``` -[Demo Site Example Output](https://radiostationdemo.com/master-schedule/multiple-view-switching/) +[Demo Site Example Output](https://demo.radiostation.pro/master-schedule/multiple-view-switching/) #### Radio Timezone Shortcode @@ -122,7 +122,7 @@ The following attributes are available for this shortcode: * *thumbnails* : Display Show Featured image if no Show Avatar. 0 or 1. Default 0. * *with_shifts* : Only display Shows with active Shifts. 0 or 1. Default 0. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/shows-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/shows-archive/) ### Overrides Archive Shortcode @@ -145,7 +145,7 @@ The following attributes are available for this shortcode: * *thumbnails* : Display Override Featured image if no Overide Avatar. 0 or 1. Default 0. * *with_dates* : Only display Shows with Date set. 0 or 1. Default 0. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/overrides-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/overrides-archive/) ### Playlists Archive Shortcode @@ -163,7 +163,7 @@ The following attributes are available for this shortcode: * *orderby* : Query to order Playlists display by. Default 'title'. * *order* : Query order for Playlists. Default 'ASC'. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/playlists-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/playlists-archive/) ### Genres Archive Shortcode @@ -188,7 +188,7 @@ The following attributes are available for this shortcode: * *avatar_width* : * *avatar_width* : Set a width style in pixels for Show Avatars. Default is 75. * *show_desc* : Display Show Descriptions. 'none', 'full' or 'excerpt'. Default 'none'. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/genres-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/genres-archive/) ### Language Archive Shortcode @@ -211,13 +211,13 @@ The following attributes are available for this shortcode: * *avatar_width* : * *avatar_width* : Set a width style in pixels for Show Avatars. Default is 75. * *show_desc* : Display Show Descriptions. 'none', 'full' or 'excerpt'. Default 'none'. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/languages-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/languages-archive/) ### [Pro] Hosts Archive Shortcode `[host-archive]` or `[hosts-archive]` -* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *content* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. * *view* : View option. 'grid' option. Default 0 (list) * *status* : Query for Host status. Default 'publish'. * *perpage* : Query for number of Hosts. Default -1 (all) @@ -228,13 +228,13 @@ The following attributes are available for this shortcode: * *social* : Display social profile icons. 0 or 1. Default 1. * *shows* : Display a list of Shows assigned to Host. 0 or 1. Default 1. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/hosts-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/hosts-archive/) ### [Pro] Producers Archive Shortcode `[producer-archive]` or `[producers-archive]` -* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *content* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. * *view* : View option. 'grid' option. Default 0 (list) * *status* : Query for Producer status. Default 'publish'. * *perpage* : Query for number of Producers. Default -1 (all) @@ -245,7 +245,7 @@ The following attributes are available for this shortcode: * *social* : Display social profile icons. 0 or 1. Default 1. * *shows* : Display a list of Shows assigned to Producer. 0 or 1. Default 1. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/producers-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/producers-archive/) ### Show Posts Archive Shortcode @@ -259,7 +259,7 @@ The following attributes are available for this shortcode: * *thumbnails* : Display Show Post Thumbnails. 0 or 1. Default 1. * *pagination* : Paginate Show Post Display. 0 or 1. Default 1. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/show-posts-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/show-posts-archive/) ### Show Playlists Archive Shortcode @@ -272,7 +272,7 @@ The following attributes are available for this shortcode: * *content* : Playlist Content display. 'none', 'full' or 'excerpt. Default 'excerpt'. * *pagination* : Paginate Show Post Display. 0 or 1. Default 1. -[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/show-playlists-archive/) +[Demo Site Example Output](https://demo.radiostation.pro/archive-shortcodes/show-playlists-archive/) ### [Pro] Show Episodes Archive Shortcode diff --git a/docs/Widgets.md b/docs/Widgets.md index aa262d2..5a3fcca 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -6,7 +6,7 @@ You can add any of the Radio Station Plugin Widgets to your site's sidebar widge Note Widgets are displayed via their corresponding Shortcodes to prevent code duplication and maintain display consistency. (The selected Widget options are simply converted into Shortcode attributes.) This also means if you want to display a Widget within a custom Template, you can use the corresponding shortcode in your template file. eg. `do_shortcode('[current-show]');` -[Radio Station Demo Site](http://radiostationdemo.com) +[Radio Station Demo Site](http://demo.radiostation.pro) ## Radio Player Widget @@ -45,7 +45,7 @@ The following attributes are available for this shortcode: Example: `[current-show title="Now On-Air" show_avatar="1" show_link="1" show_sched="1"]` -[Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-show-widget/) +[Demo Site Example Output](https://demo.radiostation.pro/extra-shortcodes/current-show-widget/) ## Upcoming Shows Widget @@ -72,7 +72,7 @@ The following attributes are available for this shortcode: Example: `[upcoming-shows title="Coming Up On-Air" show_avatar="1" show_link="1" limit="3" time="12" schow_sched="1"]` -[Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/upcoming-shows-widget/) +[Demo Site Example Output](https://demo.radiostation.pro/extra-shortcodes/upcoming-shows-widget/) ## Current Playlist Widget @@ -94,7 +94,7 @@ The following attributes are available for this shortcode: Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" label="1" comments="0"]` -[Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-playlist-widget/) +[Demo Site Example Output](https://demo.radiostation.pro/extra-shortcodes/current-playlist-widget/) ## Radio Clock Widget From d8858bd32c50445ea72d5e1de2344fa5f4637659 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 20 Dec 2021 14:47:34 +1000 Subject: [PATCH 241/377] fix #395 --- includes/support-functions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/support-functions.php b/includes/support-functions.php index 6a21771..a8baf00 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1468,6 +1468,10 @@ function radio_station_get_current_schedule( $time = false, $weekstart = false ) } if ( $day == $debugday ) { + // 2.4.0.6: fix to undefined variable warning + if ( !isset( $debugshifts ) ) { + $debugshifts = ''; + } $debugshifts .= $day . ' Show from ' . $shift['start'] . ': ' . $start_time . ' to ' . $shift['end'] . ': ' . $end_time . PHP_EOL; $debugshifts .= $day . ' Override from ' . $override['start'] . ': ' . $override_start_time . ' to ' . $override['end'] . ': ' . $override_end_time . PHP_EOL; } From fbc5ac744f5cd42208fe10d2b8954e225c52e096 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 20 Dec 2021 15:08:20 +1000 Subject: [PATCH 242/377] fix #400 --- includes/post-types-admin.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index a3e1463..d0dcb5b 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -5948,7 +5948,7 @@ function radio_station_post_save_data( $post_id ) { add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_post', 10, 2 ); function radio_station_quick_edit_post( $column_name, $post_type ) { - global $post; + global $post, $radio_station_data; $stored_post = $post; // 2.3.3.5: added fix for post type context @@ -5956,6 +5956,11 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { return; } + // 2.4.0.6: add fix for duplicate related show box + if ( isset( $radio_station_data['related-post-quick-edit'] ) ) { + return; + } + // --- get all shows --- // 2.3.3.9: allow selection shows with draft status $args = array( @@ -5999,6 +6004,9 @@ function radio_station_quick_edit_post( $column_name, $post_type ) { // 2.3.3.6: restore stored post object $post = $stored_post; + + // 2.4.0.6: add fix for duplicate related show box + $radio_station_data['related-post-quick-edit'] = true; } // ---------------------------------- From 1f9c7d3f63888884b339144dea9b7595a793ac27 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 23 Dec 2021 15:29:24 +1000 Subject: [PATCH 243/377] fix #393 --- docs/Shortcodes.md | 11 +++++++---- includes/shortcodes.php | 11 +++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index 12b1e21..5292ae5 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -109,6 +109,7 @@ Note for ease of use either the singular or plural version of each archive short The following attributes are available for this shortcode: * *description* : Show description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *hide_empty* : Only display if Shows are found. 0 or 1. Default 0. * *time* : Display time format you with to use. Valid values are 12 and 24. Default is the Plugin Setting. * *genre* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) @@ -131,6 +132,7 @@ The following attributes are available for this shortcode: The following attributes are available for this shortcode: * *description* : Override description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *hide_empty* : Only display if Overides are found. 0 or 1. Default 0. * *show_dates* : Display the Schedule Override dates and start/end times. 0 or 1. Default 1. * *time* : Display time format you with to use. Valid values are 12 and 24. Default is the Plugin Setting. @@ -154,6 +156,7 @@ The following attributes are available for this shortcode: The following attributes are available for this shortcode: * *description* : Playlist description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *hide_empty* : Only display if Playlists are found. 0 or 1. Default 0. * *genre* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) * *language* : Languages to display (ID or slug). Separate multiple values with commas. Default empty (all) @@ -217,8 +220,8 @@ The following attributes are available for this shortcode: `[host-archive]` or `[hosts-archive]` -* *content* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. -* *view* : View option. 'grid' option. Default 0 (list) +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt', default 'none' for grid view. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *status* : Query for Host status. Default 'publish'. * *perpage* : Query for number of Hosts. Default -1 (all) * *offset* : Query for Host offset. Default '' (no offset) @@ -234,8 +237,8 @@ The following attributes are available for this shortcode: `[producer-archive]` or `[producers-archive]` -* *content* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt'. -* *view* : View option. 'grid' option. Default 0 (list) +* *description* : Profile description display. 'none', 'full' or 'excerpt'. Default 'excerpt', default 'none' for grid view. +* *view* : Display view option. 'list' or 'grid' option. Default 'list'. * *status* : Query for Producer status. Default 'publish'. * *perpage* : Query for number of Producers. Default -1 (all) * *offset* : Query for producer offset. Default '' (no offset) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index e87c5b0..2fe588d 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -271,12 +271,13 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- merge defaults with passed attributes --- // 2.3.3.9: add atts for specific posts // 2.4.0.4: added optional view attribute + // 2.4.1.8: change default view value to list $defaults = array( // --- shortcode display ---- 'description' => 'excerpt', 'hide_empty' => 0, 'time' => $time_format, - 'view' => 0, + 'view' => 'list', // --- taxonomy queries --- 'genre' => '', 'language' => '', @@ -305,6 +306,11 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { 'playlist' => false, ); + // 2.4.1.8: change default description value for grid view + if ( isset( $atts['view'] ) && ( 'grid' == $atts['view'] ) ) { + $defaults['description'] = 'none'; + } + // --- handle possible pagination offset --- if ( isset( $atts['perpage'] ) && !isset( $atts['offset'] ) && get_query_var( 'page' ) ) { $page = absint( get_query_var( 'page' ) ); @@ -750,7 +756,8 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- description --- // 2.4.0.4: remove description for grid view - if ( ( 'none' == $atts['description'] ) || ( 'grid' == $atts['view'] ) ) { + // 2.4.1.8: set different grid default earlier instead + if ( 'none' == $atts['description'] ) { $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { $info['description'] = '
    '; From 4d28cdda8fc55ff24e8223587713e56ba684c4cb Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 2 Jan 2022 17:59:10 +1000 Subject: [PATCH 244/377] fix for automatic pages shortcode atts --- CHANGELOG.md | 6 ++++++ radio-station.php | 48 +++++++++++++++++++++++++++++++++++++++++------ readme.txt | 6 ++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef600d2..31f2b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 2.4.0.6 +* Updated: documentation links to new demo site address +* Fixed: remove duplicate Related Show box in Post Quick Edit +* Fixed: multiple attributes for automatic pages shortcodes +* Fixed: undefined warning for debugshifts + ### 2.4.0.5 * Fixed: plugin conflicts causing fatal errors diff --git a/radio-station.php b/radio-station.php index a5cca61..2880e3d 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1213,6 +1213,32 @@ // 'section' => 'archives', // ), + // --- Teams Archive Page --- + // 2.4.0.6: added team archive page + 'team_archive_page' => array( + 'label' => __( 'Team Archive Page', 'radio-station' ), + 'type' => 'select', + 'options' => 'PAGEID', + 'default' => '', + 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), + 'tab' => 'pages', + 'section' => 'archives', + 'pro' => true, + ), + + // --- Automatic Display --- + // 2.4.0.6: added teams archive automatic page + 'team_archive_auto' => array( + 'label' => __( 'Automatic Display', 'radio-station' ), + 'type' => 'checkbox', + 'value' => 'yes', + 'default' => 'yes', + 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', + 'tab' => 'pages', + 'section' => 'archives', + 'pro' => true, + ), + // === Single Templates === // --- Templates Change Note --- @@ -2163,6 +2189,7 @@ function radio_station_email_address( $email, $post_id ) { // 2.3.0: standalone filter for automatic page content // 2.3.1: re-add filter so the_content can be processed multiple times // 2.3.3.6: set automatic content early and clear existing content +// 2.4.0.6: fix to concatenate multiple atts values from filtering add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); function radio_station_automatic_pages_content_set( $content ) { @@ -2184,7 +2211,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[master-schedule' . $atts_string . ']'; @@ -2208,7 +2236,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[shows-archive' . $atts_string . ']'; @@ -2232,7 +2261,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[overrides-archive' . $atts_string . ']'; @@ -2256,7 +2286,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[playlists-archive' . $atts_string . ']'; @@ -2280,7 +2311,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[genres-archive' . $atts_string. ']'; @@ -2304,7 +2336,8 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - $atts_string = ' ' . $key . '="' . $value . '"'; + // 2.4.0.6: fix to append multiple atts values + $atts_string .= ' ' . $key . '="' . $value . '"'; } } $shortcode = '[languages-archive' . $atts_string. ']'; @@ -2314,6 +2347,9 @@ function radio_station_automatic_pages_content_set( $content ) { // 2.3.3.6: moved out to reduce repetitive code if ( isset( $shortcode ) ) { + if ( RADIO_STATION_DEBUG ) { + echo 'Automatic Content Shortcode: ' . $shortcode . ''; + } remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); diff --git a/readme.txt b/readme.txt index 9ef94b4..6e58840 100644 --- a/readme.txt +++ b/readme.txt @@ -218,6 +218,12 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == += 2.4.0.6 = +* Updated: documentation links to new demo site address +* Fixed: remove duplicate Related Show box in Post Quick Edit +* Fixed: multiple attributes for automatic pages shortcodes +* Fixed: undefined warning for debugshifts + = 2.4.0.5 = * Fixed: plugin conflicts causing fatal errors From d0986365df22c4471ae898c9bc98619811381dd5 Mon Sep 17 00:00:00 2001 From: majick777 Date: Thu, 13 Jan 2022 22:04:31 +1000 Subject: [PATCH 245/377] bundle test --- includes/shortcodes.php | 6 +-- radio-station.php | 101 ++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 70 deletions(-) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 2fe588d..9f19c64 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -271,7 +271,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- merge defaults with passed attributes --- // 2.3.3.9: add atts for specific posts // 2.4.0.4: added optional view attribute - // 2.4.1.8: change default view value to list + // 2.4.0.6: change default view value to list $defaults = array( // --- shortcode display ---- 'description' => 'excerpt', @@ -306,7 +306,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { 'playlist' => false, ); - // 2.4.1.8: change default description value for grid view + // 2.4.0.6: change default description value for grid view if ( isset( $atts['view'] ) && ( 'grid' == $atts['view'] ) ) { $defaults['description'] = 'none'; } @@ -756,7 +756,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- description --- // 2.4.0.4: remove description for grid view - // 2.4.1.8: set different grid default earlier instead + // 2.4.0.6: set different grid default earlier instead if ( 'none' == $atts['description'] ) { $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { diff --git a/radio-station.php b/radio-station.php index 2880e3d..ac26d1f 100644 --- a/radio-station.php +++ b/radio-station.php @@ -519,7 +519,7 @@ // === Player Colours === - // --- [Pro] Playing Highlight Color --- + // --- [Pro/Player] Playing Highlight Color --- 'player_playing_color' => array( 'type' => 'color', 'label' => __( 'Playing Icon Highlight Color', 'radio-station' ), @@ -530,7 +530,7 @@ 'pro' => true, ), - // --- [Pro] Control Icons Highlight Color --- + // --- [Pro/Player] Control Icons Highlight Color --- 'player_buttons_color' => array( 'type' => 'color', 'label' => __( 'Control Icons Highlight Color', 'radio-station' ), @@ -541,7 +541,7 @@ 'pro' => true, ), - // --- [Pro] Volume Knob Color --- + // --- [Pro/Player] Volume Knob Color --- 'player_thumb_color' => array( 'type' => 'color', 'label' => __( 'Volume Knob Color', 'radio-station' ), @@ -552,7 +552,7 @@ 'pro' => true, ), - // --- [Pro] Volume Track Color --- + // --- [Pro/Player] Volume Track Color --- 'player_range_color' => array( 'type' => 'coloralpha', 'label' => __( 'Volume Track Color', 'radio-station' ), @@ -591,7 +591,7 @@ 'pro' => false, ), - // --- [Pro] Player Autoresume --- + // --- [Pro/Player] Player Autoresume --- 'player_autoresume' => array( 'type' => 'checkbox', 'label' => __( 'Autoresume Playback', 'radio-station' ), @@ -625,10 +625,9 @@ . ' ' . __( 'You can override these in specific Player Widgets.', 'radio-station' ), 'tab' => 'player', 'section' => 'bar', - // 'pro' => true, ), - // --- [Pro] Sitewide Player Bar --- + // --- [Pro/Player] Sitewide Player Bar --- 'player_bar' => array( 'type' => 'select', 'label' => __( 'Sitewide Player Bar', 'radio-station' ), @@ -644,7 +643,7 @@ 'pro' => true, ), - // --- [Pro] Player Bar Height --- + // --- [Pro/Player] Player Bar Height --- 'player_bar_height' => array( 'type' => 'number', 'min' => 40, @@ -658,7 +657,7 @@ 'pro' => true, ), - // --- [Pro] Fade In Player Bar --- + // --- [Pro/Player] Fade In Player Bar --- 'player_bar_fadein' => array( 'type' => 'number', 'label' => __( 'Fade In Player Bar', 'radio-station' ), @@ -672,7 +671,7 @@ 'pro' => true, ), - // --- [Pro] Continuous Playback --- + // --- [Pro/Player] Continuous Playback --- // 2.4.0.1: fix for missing value field 'player_bar_continuous' => array( 'type' => 'checkbox', @@ -685,7 +684,7 @@ 'pro' => true, ), - // --- [Pro] Player Page Fade --- + // --- [Pro/Player] Player Page Fade --- 'player_bar_pagefade' => array( 'type' => 'number', 'label' => __( 'Page Fade Time', 'radio-station' ), @@ -699,7 +698,7 @@ 'pro' => true, ), - // --- [Pro] Page Load Timeout --- + // --- [Pro/Player] Page Load Timeout --- // 2.4.0.3: add page load timeout option 'player_bar_timeout' => array( 'type' => 'number', @@ -714,7 +713,7 @@ 'pro' => true, ), - // --- [Pro] Bar Player Text Color --- + // --- [Pro/Player] Bar Player Text Color --- 'player_bar_text' => array( 'type' => 'color', 'label' => __( 'Bar Player Text Color', 'radio-station' ), @@ -725,7 +724,7 @@ 'pro' => true, ), - // --- [Pro] Bar Player Background Color --- + // --- [Pro/Player] Bar Player Background Color --- 'player_bar_background' => array( 'type' => 'coloralpha', 'label' => __( 'Bar Player Background Color', 'radio-station' ), @@ -736,7 +735,7 @@ 'pro' => true, ), - // --- [Pro] Display Current Show --- + // --- [Pro/Player] Display Current Show --- // 2.4.0.3: added for current show display 'player_bar_currentshow' => array( 'type' => 'checkbox', @@ -749,7 +748,7 @@ 'pro' => true, ), - // --- [Pro] Display Metadata --- + // --- [Pro/Player] Display Metadata --- // 2.4.0.3: added for now playing metadata display 'player_bar_nowplaying' => array( 'type' => 'checkbox', @@ -762,7 +761,7 @@ 'pro' => true, ), - // --- Metadata URL --- + // --- [Pro/Player] Metadata URL --- // 2.4.0.3: added for alternative stream metadata URL 'player_bar_metadata' => array( 'type' => 'text', @@ -833,7 +832,7 @@ 'section' => 'schedule', ), - // --- [Pro] Schedule Switcher --- + // --- [Pro/Plus] Schedule Switcher --- 'schedule_switcher' => array( 'type' => 'checkbox', 'label' => __( 'View Switching', 'radio-station' ), @@ -845,7 +844,7 @@ 'pro' => true, ), - // --- [Pro] Available Views --- + // --- [Pro/Plus] Available Views --- // 2.3.2: added additional views option 'schedule_views' => array( 'type' => 'multicheck', @@ -866,7 +865,7 @@ 'pro' => true, ), - // --- [Pro] Time Spaced Grid View --- + // --- [Pro/Plus] Time Spaced Grid View --- // 2.4.0.4: added grid view time spacing option 'schedule_timegrid' => array( 'type' => 'checkbox', @@ -978,7 +977,7 @@ // === Profile Pages === // 2.3.3.9: added proflie page settings - // --- [Pro] Profile Blocks Position --- + // --- [Pro/Plus] Profile Blocks Position --- 'profile_block_position' => array( 'type' => 'select', 'label' => __( 'Info Blocks Position', 'radio-station' ), @@ -994,7 +993,7 @@ 'pro' => true, ), - // ---- [Pro] Profile Section Layout --- + // ---- [Pro/Plus] Profile Section Layout --- 'profile_section_layout' => array( 'type' => 'select', 'label' => __( 'Profile Content Layout', 'radio-station' ), @@ -1213,32 +1212,6 @@ // 'section' => 'archives', // ), - // --- Teams Archive Page --- - // 2.4.0.6: added team archive page - 'team_archive_page' => array( - 'label' => __( 'Team Archive Page', 'radio-station' ), - 'type' => 'select', - 'options' => 'PAGEID', - 'default' => '', - 'helper' => __( 'Select the Page for displaying the Team archive list.', 'radio-station' ), - 'tab' => 'pages', - 'section' => 'archives', - 'pro' => true, - ), - - // --- Automatic Display --- - // 2.4.0.6: added teams archive automatic page - 'team_archive_auto' => array( - 'label' => __( 'Automatic Display', 'radio-station' ), - 'type' => 'checkbox', - 'value' => 'yes', - 'default' => 'yes', - 'helper' => __( 'Replaces selected page content with default Team Archive. Alternatively customize display using the shortcode:', 'radio-station' ) . ' [teams-archive]', - 'tab' => 'pages', - 'section' => 'archives', - 'pro' => true, - ), - // === Single Templates === // --- Templates Change Note --- @@ -1321,7 +1294,7 @@ 'section' => 'loading', ), - // --- [Pro] Dynamic Reloading --- + // --- [Pro/Plus] Dynamic Reloading --- 'dynamic_reload' => array( 'type' => 'checkbox', 'label' => __( 'Dynamic Reloading?', 'radio-station' ), @@ -1333,7 +1306,7 @@ 'pro' => true, ), - // --- Translate User Times --- + // --- [Pro/Plus] Translate User Times --- 'convert_show_times' => array( 'type' => 'checkbox', 'label' => __( 'Convert Show Times', 'radio-station' ), @@ -1345,7 +1318,7 @@ 'pro' => true, ), - // --- [Pro] Timezone Switching --- + // --- [Pro/Plus] Timezone Switching --- 'timezone_switching' => array( 'type' => 'checkbox', 'label' => __( 'User Timezone Switching', 'radio-station' ), @@ -1561,6 +1534,10 @@ 'hasaddons' => $has_addons, 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), 'plan' => $plan, + // 2.4.0.6: add bundles configuration + 'bundle_id' => '9521', + 'bundle_public_key' => 'pk_a2650f223ef877e87fe0fdfc4442b', + 'bundle_license_auto_activation' => true, ); // ------------------------- @@ -2189,7 +2166,6 @@ function radio_station_email_address( $email, $post_id ) { // 2.3.0: standalone filter for automatic page content // 2.3.1: re-add filter so the_content can be processed multiple times // 2.3.3.6: set automatic content early and clear existing content -// 2.4.0.6: fix to concatenate multiple atts values from filtering add_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); function radio_station_automatic_pages_content_set( $content ) { @@ -2211,8 +2187,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[master-schedule' . $atts_string . ']'; @@ -2236,8 +2211,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[shows-archive' . $atts_string . ']'; @@ -2261,8 +2235,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[overrides-archive' . $atts_string . ']'; @@ -2286,8 +2259,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[playlists-archive' . $atts_string . ']'; @@ -2311,8 +2283,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[genres-archive' . $atts_string. ']'; @@ -2336,8 +2307,7 @@ function radio_station_automatic_pages_content_set( $content ) { $atts_string = ''; if ( is_array( $atts ) && ( count( $atts ) > 0 ) ) { foreach ( $atts as $key => $value ) { - // 2.4.0.6: fix to append multiple atts values - $atts_string .= ' ' . $key . '="' . $value . '"'; + $atts_string = ' ' . $key . '="' . $value . '"'; } } $shortcode = '[languages-archive' . $atts_string. ']'; @@ -2347,9 +2317,6 @@ function radio_station_automatic_pages_content_set( $content ) { // 2.3.3.6: moved out to reduce repetitive code if ( isset( $shortcode ) ) { - if ( RADIO_STATION_DEBUG ) { - echo 'Automatic Content Shortcode: ' . $shortcode . ''; - } remove_filter( 'the_content', 'radio_station_automatic_pages_content_set', 1 ); remove_filter( 'the_content', 'radio_station_automatic_pages_content_get', 11 ); $radio_station_data['automatic_content'] = do_shortcode( $shortcode ); From 53c9e77801a79688632a3b028d1a650e3ce7ad10 Mon Sep 17 00:00:00 2001 From: majick777 Date: Sun, 16 Jan 2022 20:49:11 +1000 Subject: [PATCH 246/377] dev updates --- CHANGELOG.md | 2 ++ includes/support-functions.php | 4 ++++ loader.php | 21 ++++++++++++++++++--- radio-station.php | 20 +++++++++++++++----- readme.txt | 2 ++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f2b2a..8855052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.6 +* Updated: Plugin Panel (1.2.2) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes +* Fixed: undefined warning in current playlist when no current show * Fixed: undefined warning for debugshifts ### 2.4.0.5 diff --git a/includes/support-functions.php b/includes/support-functions.php index a8baf00..2c4f4f9 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -2281,6 +2281,10 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = function radio_station_get_current_playlist() { $current_show = radio_station_get_current_show(); + // 2.4.0.6: bug out if there is no current show + if ( !$current_show || !isset( $current_show['show'] ) || !isset( $current_show['show']['id'] ) ) { + return false; + } $show_id = $current_show['show']['id']; $playlists = radio_station_get_show_playlists( $show_id ); if ( !$playlists || !is_array( $playlists ) || ( count ( $playlists ) < 1 ) ) { diff --git a/loader.php b/loader.php index 597923e..49c1163 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.2.1 +// Version: 1.2.2 // -------------- // Note: Changelog and structure at end of file. // @@ -120,6 +120,10 @@ class radio_station_loader { // ----------------- public function __construct( $args ) { + if ( !is_array( $args ) ) { + return; + } + // --- set debug switch --- // 1.1.2: added debug switch check $prefix = ''; @@ -1663,13 +1667,18 @@ public function notice_boxer() { $args = $this->args; - // --- bug out if not on radio station pages --- + // --- bug out if not on plugin page --- if ( !isset( $_REQUEST['page'] ) ) { return; } if ( $args['slug'] != substr( $_REQUEST['page'], 0, strlen( $args['slug'] ) ) ) { return; } + + // 1.2.2: bug out if adminsanity notices are loaded + if ( isset( $GLOBALS['radio_station_data']['load']['notices'] ) && $GLOBALS['radio_station_data']['load']['notices'] ) { + return; + } // --- output notice box --- echo '
    '; @@ -1863,7 +1872,8 @@ public function settings_header() { } elseif ( isset( $args['type'] ) && ( 'theme' == $args['type'] ) ) { $rate_url = 'https://wordpress.org/support/theme/' . $args['wporgslug'] . '/reviews/#new-post'; } else { - $rate_url = 'https://wordpress.org/plugins/' . $args['wporgslug'] . '/reviews/#new-post'; + // 1.2.2: update rating URL to match new repo scheme + $rate_url = 'https://wordpress.org/support/plugins/' . $args['wporgslug'] . '/reviews/#new-post'; } if ( isset( $args['ratetext'] ) ) { $rate_text = $args['ratetext']; @@ -3189,6 +3199,11 @@ function radio_station_settings_row( $option, $setting ) { // CHANGELOG // ========= +// == 1.2.2 == +// - update plugin repository rating URL +// - remove duplication of addons link +// - no notice boxer if adminsanity notices loaded + // == 1.2.1 == // - added filters for premium and addons init // - fix overriding of plan arg to free diff --git a/radio-station.php b/radio-station.php index ac26d1f..d4a3809 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1526,19 +1526,29 @@ // 2.4.0.3: turn on plans switch for Pro also // 2.4.0.3: set Pro details and Upgrade links // 2.4.0.4: change upgrade_link to -upgrade + // 2.4.0.6: change upgrade_link to -pricing 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', 'hasplans' => $has_plans, - 'upgrade_link' => add_query_arg( 'page', $slug . '-upgrade', admin_url( 'admin.php' ) ), + 'upgrade_link' => add_query_arg( 'page', $slug . '-pricing', admin_url( 'admin.php' ) ), 'pro_link' => RADIO_STATION_PRO_URL . 'pricing/', 'hasaddons' => $has_addons, 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), 'plan' => $plan, - // 2.4.0.6: add bundles configuration - 'bundle_id' => '9521', - 'bundle_public_key' => 'pk_a2650f223ef877e87fe0fdfc4442b', - 'bundle_license_auto_activation' => true, ); +// print_r( $settings ); + +// ---------------------- +// Config Upgrade Bundles +// ---------------------- +// 2.4.0.6: add bundles configuration +add_filter( 'freemius_init_settings_radio_station', 'radio_station_bundles_init' ); +function radio_station_bundles_init( $settings ) { + $settings['bundle_id'] = '9521'; + $settings['bundle_public_key'] = 'pk_a2650f223ef877e87fe0fdfc4442b'; + $settings['bundle_license_auto_activation'] = true; + return $settings; +} // ------------------------- // Set Plugin Option Globals diff --git a/readme.txt b/readme.txt index 6e58840..641fc28 100644 --- a/readme.txt +++ b/readme.txt @@ -219,9 +219,11 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.6 = +* Updated: Plugin Panel (1.2.2) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes +* Fixed: undefined warning in current playlist when no current show * Fixed: undefined warning for debugshifts = 2.4.0.5 = From 6d090aac1c81a4c7cbfbc50552ebc633a1d9b8d8 Mon Sep 17 00:00:00 2001 From: majick777 Date: Sun, 16 Jan 2022 21:08:39 +1000 Subject: [PATCH 247/377] schedule newline fix --- CHANGELOG.md | 1 + includes/master-schedule.php | 5 +---- readme.txt | 1 + templates/master-schedule-list.php | 2 ++ templates/master-schedule-table.php | 2 ++ templates/master-schedule-tabs.php | 2 ++ 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8855052..35074bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes * Fixed: undefined warning in current playlist when no current show +* Fixed: undefined warning for newline in schedule templates * Fixed: undefined warning for debugshifts ### 2.4.0.5 diff --git a/includes/master-schedule.php b/includes/master-schedule.php index b1d22cb..9fb7071 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -176,10 +176,7 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: remove check if clock shortcode present // 2.3.3.6: set new line for easier debug viewing - $newline = ''; - if ( RADIO_STATION_DEBUG ) { - $newline = "\n"; - } + $newline = RADIO_STATION_DEBUG ? "\n" : ''; // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views diff --git a/readme.txt b/readme.txt index 641fc28..8725b55 100644 --- a/readme.txt +++ b/readme.txt @@ -224,6 +224,7 @@ You can now visit your site to make sure nothing is broken. If you experience is * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes * Fixed: undefined warning in current playlist when no current show +* Fixed: undefined warning for newline in schedule templates * Fixed: undefined warning for debugshifts = 2.4.0.5 = diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index ad1ff25..bf9241b 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -3,6 +3,8 @@ * Template for master schedule shortcode list style. */ +$newline = RADIO_STATION_DEBUG ? "\n" : ''; + // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 6172bc9..236e346 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -3,6 +3,8 @@ * Template for master schedule shortcode default (table) style. */ +$newline = RADIO_STATION_DEBUG ? "\n" : ''; + // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 554b091..e41d081 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -4,6 +4,8 @@ * ref: http://nlb-creations.com/2014/06/06/radio-station-tutorial-creating-a-tabbed-programming-schedule/ */ +$newline = RADIO_STATION_DEBUG ? "\n" : ''; + // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); From 32d9dd6586782e84d5d8fdbeb2c1bdcc9dbab420 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 20 Feb 2022 16:55:16 +1000 Subject: [PATCH 248/377] dev updates #406 #408 --- CHANGELOG.md | 6 +-- docs/Filters.md | 9 +++- includes/master-schedule.php | 5 +- includes/shortcodes.php | 36 +++++++++---- includes/support-functions.php | 7 +-- js/radio-station-clock.js | 4 +- js/radio-station-countdown.js | 4 +- loader.php | 24 +++++---- player/radio-player.php | 2 +- radio-station-admin.php | 28 ++++++++-- radio-station.php | 79 ++++++++++++++--------------- readme.txt | 6 +-- templates/master-schedule-list.php | 11 ++-- templates/master-schedule-table.php | 7 +-- templates/master-schedule-tabs.php | 7 +-- templates/single-show-content.php | 14 +++-- 16 files changed, 156 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35074bf..b7716eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.6 -* Updated: Plugin Panel (1.2.2) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes -* Fixed: undefined warning in current playlist when no current show -* Fixed: undefined warning for newline in schedule templates +* Fixed: hide inactive tab shortcode section on tab click * Fixed: undefined warning for debugshifts +* Fixed: current show in schedule when on exact start second +* Added: filters for time and date separators ### 2.4.0.5 * Fixed: plugin conflicts causing fatal errors diff --git a/docs/Filters.md b/docs/Filters.md index 81be917..5402872 100644 --- a/docs/Filters.md +++ b/docs/Filters.md @@ -282,6 +282,7 @@ Here is a full list of available filters within the plugin, grouped by file and |**includes/class-radio-clock-widget.php**|||| | |`radio_station_radio_clock_widget_override` | ` $output` | `$args`, `$atts`| |**templates/master-schedule-table.php**|||| +| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'table'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-table'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-table'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'table'`| @@ -307,6 +308,7 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_excerpt_display` | ` $excerpy` | `$show_id`, `'table'`| | |`radio_station_schedule_show_custom_display` | ` ''` | `$show_id`, `'table'`| |**templates/master-schedule-tabs.php**|||| +| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'tabs'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-tabs'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-tabs'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'tabs'`| @@ -341,6 +343,7 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_schedule_show_encore` | ` $encore` | `$show['id']`, `'legacy'`| | |`radio_station_schedule_show_file` | ` $show_file` | `$show['id']`, `'legacy'`| |**templates/master-schedule-list.php**|||| +| |`radio_station_schedule_show_time_separator` | ` $shifts_separator` | `'list'`| | |`radio_station_time_format_start` | ` $start_data_format` | `'schedule-list'`, `$atts`| | |`radio_station_time_format_end` | ` $end_data_format` | `'schedule-list'`, `$atts`| | |`radio_station_schedule_start_day` | ` false` | `'list'`| @@ -451,4 +454,8 @@ Here is a full list of available filters within the plugin, grouped by file and Below is a list of filters that are available within [Radio Station Pro](https://radiostation.pro). -*TODO* \ No newline at end of file +*TODO* + + + + diff --git a/includes/master-schedule.php b/includes/master-schedule.php index 9fb7071..b1d22cb 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -176,7 +176,10 @@ function radio_station_master_schedule( $atts ) { // 2.3.3.9: remove check if clock shortcode present // 2.3.3.6: set new line for easier debug viewing - $newline = RADIO_STATION_DEBUG ? "\n" : ''; + $newline = ''; + if ( RADIO_STATION_DEBUG ) { + $newline = "\n"; + } // --- table for selector and clock --- // 2.3.0: moved out from templates to apply to all views diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 9f19c64..3e32725 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -271,7 +271,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- merge defaults with passed attributes --- // 2.3.3.9: add atts for specific posts // 2.4.0.4: added optional view attribute - // 2.4.0.6: change default view value to list + // 2.4.1.8: change default view value to list $defaults = array( // --- shortcode display ---- 'description' => 'excerpt', @@ -306,7 +306,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { 'playlist' => false, ); - // 2.4.0.6: change default description value for grid view + // 2.4.1.8: change default description value for grid view if ( isset( $atts['view'] ) && ( 'grid' == $atts['view'] ) ) { $defaults['description'] = 'none'; } @@ -709,9 +709,13 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { $start = radio_station_translate_time( $start ); $end = radio_station_translate_time( $end ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'override' ); + // 2.3.1: fix to append not echo override date to archive list $info['meta'] .= '' . esc_html( $start ) . ''; - $info['meta'] .= ' - '; + $info['meta'] .= '' . esc_html( $separator ) . ''; $info['meta'] .= '' . esc_html( $end ) . ''; $info['meta'] .= '
    '; @@ -756,7 +760,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- description --- // 2.4.0.4: remove description for grid view - // 2.4.0.6: set different grid default earlier instead + // 2.4.1.8: set different grid default earlier instead if ( 'none' == $atts['description'] ) { $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { @@ -2130,22 +2134,30 @@ function radio_station_current_show_shortcode( $atts ) { $classes[] = 'current-shift'; $class = implode( ' ', $classes ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'override' ); + $current_shift_display = '
    '; $current_shift_display .= '' . esc_html( $start ) . ''; - $current_shift_display .= ' - '; + $current_shift_display .= '' . esc_html( $separator ) . ''; $current_shift_display .= '' . esc_html( $end ) . ''; $current_shift_display .= '
    '; // 2.3.3.9: add show user time div $current_shift_display .= '
    '; $current_shift_display .= '['; - $current_shift_display .= ' - '; + $current_shift_display .= '' . esc_html( $separator ) . ''; $current_shift_display .= ']'; $current_shift_display .= '
    '; } $class = implode( ' ', $classes ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'current-show' ); + // --- shift display output --- $shift_display .= '
    '; if ( in_array( 'current-shift', $classes ) ) { @@ -2153,13 +2165,13 @@ function radio_station_current_show_shortcode( $atts ) { $shift_display .= '
    • '; } $shift_display .= '' . esc_html( $start ) . ''; - $shift_display .= ' - '; + $shift_display .= '' . esc_html( $separator ) . ''; $shift_display .= '' . esc_html( $end ) . ''; // 2.3.3.9: add show user time div $shift_display .= '
      '; $shift_display .= '['; - $shift_display .= ' - '; + $shift_display .= '' . esc_html( $separator ) . ''; $shift_display .= ']'; $shift_display .= '
      '; @@ -2822,17 +2834,21 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $start = radio_station_translate_time( $start ); $end = radio_station_translate_time( $end ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'upcoming-shows' ); + // --- set shift display output --- $shift_display = '
      '; $shift_display .= '
      '; $shift_display .= '' . esc_html( $start ) . ''; - $shift_display .= ' - '; + $shift_display .= '' . esc_html( $separator ) . ''; $shift_display .= '' . esc_html( $end ) . ''; $shift_display .= '
      '; // 2.3.3.9: add empty user time div $shift_display .= '
      '; $shift_display .= '['; - $shift_display .= ' - '; + $shift_display .= '' . esc_html( $separator ) . ''; $shift_display .= ']'; $shift_display .= '
      '; $shift_display .= '
      '; diff --git a/includes/support-functions.php b/includes/support-functions.php index 2c4f4f9..0c54835 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -1972,7 +1972,8 @@ function radio_station_get_current_show( $time = false ) { // --- set current show --- // 2.3.3: get current show directly and remove transient - if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + // 2.4.0.6: fix to add equal to operator for start time + if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { if ( RADIO_STATION_DEBUG ) { echo '^^^ Current ^^^' . PHP_EOL; } @@ -2281,10 +2282,6 @@ function radio_station_get_next_shows( $limit = 3, $show_shifts = false, $time = function radio_station_get_current_playlist() { $current_show = radio_station_get_current_show(); - // 2.4.0.6: bug out if there is no current show - if ( !$current_show || !isset( $current_show['show'] ) || !isset( $current_show['show']['id'] ) ) { - return false; - } $show_id = $current_show['show']['id']; $playlists = radio_station_get_show_playlists( $show_id ); if ( !$playlists || !is_array( $playlists ) || ( count ( $playlists ) < 1 ) ) { diff --git a/js/radio-station-clock.js b/js/radio-station-clock.js index e9fd107..b1e146b 100644 --- a/js/radio-station-clock.js +++ b/js/radio-station-clock.js @@ -32,10 +32,10 @@ function radio_time_string(datetime, hours, seconds, override) { } timestring = ''+h+''; - timestring += ':'; + timestring += ''+radio.sep+''; timestring += ''+m+''; if (seconds) { - timestring += ':'; + timestring += ''+radio.sep+''; timestring += ''+s+''; } if (mer != '') {timestring += ' '+mer+'';} diff --git a/js/radio-station-countdown.js b/js/radio-station-countdown.js index 47578c8..1c141aa 100644 --- a/js/radio-station-countdown.js +++ b/js/radio-station-countdown.js @@ -74,8 +74,8 @@ function radio_countdown_display(diff, label) { if (minutes < 10) {minutes = '0'+minutes;} seconds = diff; if (seconds < 10) {seconds = '0'+seconds;} - display = ''+label+': '+hours+':'; - display += ''+minutes+':'+seconds+''; + display = ''+label+': '+hours+''+radio.sep+''; + display += ''+minutes+''+radio.sep+''+seconds+''; return display; } diff --git a/loader.php b/loader.php index 49c1163..b1e710b 100644 --- a/loader.php +++ b/loader.php @@ -5,7 +5,7 @@ // =========================== // // -------------- -// Version: 1.2.2 +// Version: 1.2.1 // -------------- // Note: Changelog and structure at end of file. // @@ -1350,9 +1350,10 @@ public function readme_viewer() { public function load_freemius() { // 1.2.1: no need to load if not in admin area - if ( !is_admin() ) { - return; - } + // if ( !is_admin() ) { + // return; + // } + echo 'Freemius Loading...'; $args = $this->args; $namespace = $this->namespace; @@ -1437,6 +1438,8 @@ public function load_freemius() { // --- set Freemius settings from plugin settings --- // 1.1.1: remove admin_url wrapper on Freemius first-path value + // TODO: further possible args for Freemius init (eg. bundle_id) + // ref: https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/ $first_path = add_query_arg( 'page', $args['slug'], 'admin.php' ); $first_path = add_query_arg( 'welcome', 'true', $first_path ); $settings = array( @@ -1464,9 +1467,9 @@ public function load_freemius() { // --- filter settings before initializing --- $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings ); - if ( $this->debug ) { + // if ( $this->debug ) { echo 'Freemius Settings: ' . print_r( $settings, true ) . ''; - } + // } if ( !$settings || !is_array( $settings ) ) { return; } @@ -1667,7 +1670,7 @@ public function notice_boxer() { $args = $this->args; - // --- bug out if not on plugin page --- + // --- bug out if not on radio station pages --- if ( !isset( $_REQUEST['page'] ) ) { return; } @@ -2423,7 +2426,8 @@ public function setting_row( $option ) { $pro_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : ''; } if ( $upgrade_link || $pro_link ) { - $row .= __( 'Available in Pro.' ) . '
      '; + // 1.2.2: change text from Available in Pro + $row .= __( 'Premium Feature.' ) . '
      '; if ( $upgrade_link ) { $row .= '' . esc_html( __( 'Upgrade Now' ) ) . ''; } @@ -2431,7 +2435,8 @@ public function setting_row( $option ) { $row .= ' | '; } if ( $pro_link ) { - $row .= '' . esc_html( __( 'Pro Details' ) ) . ''; + // 1.2.2: change text from Pro details + $row .= '' . esc_html( __( 'Details' ) ) . ''; } } else { $row .= esc_html( __( 'Coming soon in Pro version!' ) ); @@ -3203,6 +3208,7 @@ function radio_station_settings_row( $option, $setting ) { // - update plugin repository rating URL // - remove duplication of addons link // - no notice boxer if adminsanity notices loaded +// - change upgrade texts // == 1.2.1 == // - added filters for premium and addons init diff --git a/player/radio-player.php b/player/radio-player.php index 7d67e85..9681b40 100644 --- a/player/radio-player.php +++ b/player/radio-player.php @@ -172,7 +172,7 @@ // Accepts: $args (Array) // Array Key | Accepts // 'script' | 'amplitude' (default), 'jplayer', 'howler', // 'mediaelements' -// 'layout' | 'horizontal', 'vertical +// 'layout' | 'horizontal', 'vertical' // 'theme' | 'light', 'dark' // 'buttons' | 'circular', 'rounded', 'square' // 'skin' | // (Media Elements: 'wordpress', 'minimal'); diff --git a/radio-station-admin.php b/radio-station-admin.php index 76bf89f..9a4d07a 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -101,22 +101,44 @@ function radio_station_admin_styles() { add_filter( 'plugin_action_links_' . RADIO_STATION_BASENAME, 'radio_station_license_activation_link', 20, 2 ); // add_filter( 'network_admin_plugin_action_links_' . RADIO_STATION_BASENAME, , 'radio_station_license_activation_link', 20, 2 ); function radio_station_license_activation_link( $links, $file ) { + + if ( RADIO_STATION_DEBUG ) { + echo 'RS Plugin Links A: ' . print_r( $links, true ) . '' . PHP_EOL; + } + foreach ( $links as $key => $link ) { + // if ( RADIO_STATION_DEBUG ) { // echo 'Plugin Link ' . $key . ': ' . $link . '' . PHP_EOL; // } + + // 2.4.0.6: remove addons link from Free by default + if ( strstr( $link, '-addons' ) ) { + global $radio_station_data; + if ( !$radio_station_data['settings']['hasaddons'] ) { + unset( $links[$key] ); + } + } + // --- remove activate premium license link from free version --- if ( strstr( $key, 'activate-license' ) ) { + if ( defined( 'RADIO_STATION_PRO_FILE' ) ) { + global $radio_station_activation_link; + $radio_station_activation_link = $link; + } unset( $links[$key] ); } + // --- remove upgrade link if Pro is already installed --- if ( defined( 'RADIO_STATION_PRO_FILE' ) && strstr( $key, 'upgrade' ) ) { unset( $links[$key] ); } } - // if ( RADIO_STATION_DEBUG ) { - // echo 'Plugin Links: ' . print_r( $links, true ) . '' . PHP_EOL; - // } + + if ( RADIO_STATION_DEBUG ) { + echo 'RS Plugin Links B: ' . print_r( $links, true ) . '' . PHP_EOL; + } + return $links; } diff --git a/radio-station.php b/radio-station.php index d4a3809..8c6a8fa 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.5 +Version: 2.4.0.5.3 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -35,6 +35,7 @@ // === Setup === // - Define Plugin Constants +// - Set Debug Mode Constant // - Define Plugin Data Slugs // - Include Plugin Files // - Plugin Options and Defaults @@ -75,7 +76,6 @@ // - Admin Fix for DJ / Host Role Label // - maybe Revoke Edit Show Capability // === Debugging === -// - Set Debug Mode Constant // - maybe Clear Transient Data // - Debug Output and Logging @@ -120,6 +120,25 @@ define( 'RADIO_STATION_GENRES_SLUG', 'genres' ); } +// ----------------------- +// Set Debug Mode Constant +// ----------------------- +// 2.3.0: added debug mode constant +// 2.3.2: added saving debug mode constant +if ( !defined( 'RADIO_STATION_DEBUG' ) ) { + $rs_debug = false; + if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { + $rs_debug = true; + } + define( 'RADIO_STATION_DEBUG', $rs_debug ); +} +if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { + $rs_save_debug = false; + if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { + $rs_save_debug = true; + } + define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); +} // -------------------- // Include Plugin Files @@ -1526,7 +1545,6 @@ // 2.4.0.3: turn on plans switch for Pro also // 2.4.0.3: set Pro details and Upgrade links // 2.4.0.4: change upgrade_link to -upgrade - // 2.4.0.6: change upgrade_link to -pricing 'freemius_id' => '4526', 'freemius_key' => 'pk_aaf375c4fb42e0b5b3831e0b8476b', 'hasplans' => $has_plans, @@ -1535,20 +1553,11 @@ 'hasaddons' => $has_addons, 'addons_link' => add_query_arg( 'page', $slug . '-addons', admin_url( 'admin.php' ) ), 'plan' => $plan, + // 2.4.0.6: add bundles configuration + // 'bundle_id' => '9521', + // 'bundle_public_key' => 'pk_a2650f223ef877e87fe0fdfc4442b', + // 'bundle_license_auto_activation' => true, ); -// print_r( $settings ); - -// ---------------------- -// Config Upgrade Bundles -// ---------------------- -// 2.4.0.6: add bundles configuration -add_filter( 'freemius_init_settings_radio_station', 'radio_station_bundles_init' ); -function radio_station_bundles_init( $settings ) { - $settings['bundle_id'] = '9521'; - $settings['bundle_public_key'] = 'pk_a2650f223ef877e87fe0fdfc4442b'; - $settings['bundle_license_auto_activation'] = true; - return $settings; -} // ------------------------- // Set Plugin Option Globals @@ -1556,6 +1565,9 @@ function radio_station_bundles_init( $settings ) { global $radio_station_data; $radio_station_data['options'] = $options; $radio_station_data['settings'] = $settings; +if ( RADIO_STATION_DEBUG ) { + echo 'Radio Station Settings: ' . print_r( $settings, true ) . ''; +} // ---------------------------- // Start Plugin Loader Instance @@ -1813,6 +1825,10 @@ function radio_station_localization_script() { // --- create settings objects --- $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; + + // 2.4.0.6: add filterable time display separator + $time_separator = apply_filters( 'radio_station_time_separator', ':' ); + $js .= " radio.sep = '" . esc_js( $time_separator ) . "';"; // --- set AJAX URL --- // 2.3.2: add admin AJAX URL @@ -1829,7 +1845,7 @@ function radio_station_localization_script() { $js .= "if (window.matchMedia('(pointer: coarse)').matches) {radio.touchscreen = true;} else {radio.touchscreen = false;}" . PHP_EOL; // --- set debug flag --- - if ( defined( 'RADIO_STATION_DEBUG' ) && RADIO_STATION_DEBUG ) { + if ( RADIO_STATION_DEBUG ) { $js .= "radio.debug = true;" . PHP_EOL; } else { $js .= "radio.debug = false;" . PHP_EOL; @@ -3646,26 +3662,6 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // --- Debugging --- // ================= -// ----------------------- -// Set Debug Mode Constant -// ----------------------- -// 2.3.0: added debug mode constant -// 2.3.2: added saving debug mode constant -if ( !defined( 'RADIO_STATION_DEBUG' ) ) { - $rs_debug = false; - if ( isset( $_REQUEST['rs-debug'] ) && ( '1' == $_REQUEST['rs-debug'] ) ) { - $rs_debug = true; - } - define( 'RADIO_STATION_DEBUG', $rs_debug ); -} -if ( !defined( 'RADIO_STATION_SAVE_DEBUG' ) ) { - $rs_save_debug = false; - if ( isset( $_REQUEST['rs-save-debug'] ) && ( '1' == $_REQUEST['rs-save-debug'] ) ) { - $rs_save_debug = true; - } - define( 'RADIO_STATION_SAVE_DEBUG', $rs_save_debug ); -} - // -------------------------- // maybe Clear Transient Data // -------------------------- @@ -3727,7 +3723,10 @@ function radio_station_debug( $data, $echo = true, $file = false ) { add_action( 'shutdown', 'radio_station_freemius_debug' ); function radio_station_freemius_debug() { if ( is_admin() && RADIO_STATION_DEBUG && current_user_can( 'manage_options' ) ) { - $instance = radio_station_freemius_instance(); - echo 'Freemius Object: ' . print_r( $instance, true ) . ''; + // 2.4.0.6: check if global instance is set directly + if ( isset( $GLOBALS['radio_station_freemius'] ) ) { + $instance = $GLOBALS['radio_station_freemius']; + echo 'Freemius Object: ' . print_r( $instance, true ) . ''; + } } -} \ No newline at end of file +} diff --git a/readme.txt b/readme.txt index 8725b55..2912627 100644 --- a/readme.txt +++ b/readme.txt @@ -219,13 +219,13 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.6 = -* Updated: Plugin Panel (1.2.2) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes -* Fixed: undefined warning in current playlist when no current show -* Fixed: undefined warning for newline in schedule templates +* Fixed: hide inactive tab shortcode section on tab click * Fixed: undefined warning for debugshifts +* Fixed: current show in schedule when on exact start second +* Added: filters for time and date separators = 2.4.0.5 = * Fixed: plugin conflicts causing fatal errors diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index bf9241b..4275b7c 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -3,8 +3,6 @@ * Template for master schedule shortcode list style. */ -$newline = RADIO_STATION_DEBUG ? "\n" : ''; - // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); @@ -26,6 +24,9 @@ // --- set shift time formats --- // 2.3.2: set time formats early +// 2.4.0.6: add filter for shift times separator +$shifts_separator = __( 'to', 'radio-station' ); +$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'list' ); if ( 24 == (int) $atts['time'] ) { $start_data_format = $end_data_format = 'H:i'; } else { @@ -360,8 +361,9 @@ $end = radio_station_translate_time( $end ); // 2.3.0: filter show time by show and context + // 2.4.0.6: used filtered shift times separator $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; } else { @@ -383,9 +385,10 @@ } $times .= '>' . $show_time . '
    ' . $newline; // 2.3.3.9: added internal spans for user time + // 2.4.0.6: use filtered shift times separator $times .= '
    '; $times .= '[' . $newline; - $times .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $times .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; $times .= ']' . $newline; $times .= '
    ' . $newline; $info['times'] = $times; diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 236e346..525d04f 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -3,8 +3,6 @@ * Template for master schedule shortcode default (table) style. */ -$newline = RADIO_STATION_DEBUG ? "\n" : ''; - // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); @@ -27,6 +25,9 @@ // --- set shift time formats --- // 2.3.2: set time formats early +// 2.4.0.6: add filter for shift times separator +$shifts_separator = __( 'to', 'radio-station' ); +$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'table' ); if ( 24 == (int) $atts['time'] ) { $start_data_format = $end_data_format = 'H:i'; } else { @@ -557,7 +558,7 @@ // --- set show time output --- $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; } else { diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index e41d081..9181f37 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -4,8 +4,6 @@ * ref: http://nlb-creations.com/2014/06/06/radio-station-tutorial-creating-a-tabbed-programming-schedule/ */ -$newline = RADIO_STATION_DEBUG ? "\n" : ''; - // --- get all the required info --- $hours = radio_station_get_hours(); $now = radio_station_get_now(); @@ -28,6 +26,9 @@ // --- set shift time formats --- // 2.3.2: set time formats early +// 2.4.0.6: add filter for shift times separator +$shifts_separator = __( 'to', 'radio-station' ); +$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'tabs' ); if ( 24 == (int) $atts['time'] ) { $start_data_format = $end_data_format = 'H:i'; } else { @@ -454,7 +455,7 @@ // 2.3.0: filter show time by show and context $show_time = '' . esc_html( $start ) . '' . $newline; - $show_time .= ' ' . esc_html( __( 'to', 'radio-station' ) ) . ' ' . $newline; + $show_time .= ' ' . esc_html( $shifts_separator ) . ' ' . $newline; $show_time .= '' . esc_html( $end ) . '' . $newline; } else { diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 04777fd..8c7b963 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -602,6 +602,10 @@ } $class = implode( ' ', $classes ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'show-content' ); + // --- set show time output --- // 2.3.4: fix to start data_format attribute $show_time = '
    ' . $newline; @@ -617,7 +621,7 @@ // 2.3.3.9: add user show time div $show_time .= '
    ' . $newline; $show_time .= '[' . $newline; - $show_time .= ' - ' . $newline; + $show_time .= '' . esc_html( $separator ) . '' . $newline; $show_time .= ']' . $newline; $show_time .= '
    ' . $newline; @@ -685,17 +689,21 @@ $date_time = radio_station_to_time( $override['date'] . ' 00:00' ); $date = radio_station_get_time( $date_format, $date_time ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_times_separator', $separator, 'override-content' ); + $scheduled .= '
    ' . $newline; $scheduled .= '' . esc_html( $date ) . '' . $newline; $scheduled .= '' . esc_html( $start_display ) . '' . $newline; - $scheduled .= ' - ' . $newline; + $scheduled .= '' . esc_html( $separator ) . '' . $newline; $scheduled .= '' . esc_html( $end_display ) . '' . $newline; $scheduled .= '
    ' . $newline; $scheduled .= '
    ' . $newline; $scheduled .= '[' . $newline; $scheduled .= '' . $newline; - $scheduled .= ' - ' . $newline; + $scheduled .= '' . esc_html( $separator ) . '' . $newline; $scheduled .= ']' . $newline; $scheduled .= '
    ' . $newline; } From c73c0750a6c24d199fa9b8489f62a17aa044ee13 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Sun, 20 Feb 2022 17:38:34 +1000 Subject: [PATCH 249/377] dev updates #406 --- includes/shortcodes.php | 40 +++++++++++++++++------------ radio-station.php | 2 +- templates/master-schedule-list.php | 8 +++--- templates/master-schedule-table.php | 8 +++--- templates/master-schedule-tabs.php | 4 ++- templates/single-show-content.php | 4 +-- 6 files changed, 39 insertions(+), 27 deletions(-) diff --git a/includes/shortcodes.php b/includes/shortcodes.php index 3e32725..26d27b2 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -519,10 +519,13 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // --- set time data formats --- // 2.3.0: added once-off meridiem pre-conversions // 2.3.2: replaced meridiem conversions with data formats + // 2.4.0.6: added filter for default time format separator + $time_separator = ':'; + $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, $post_type . '-archive' ); if ( 24 == (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'H:i'; + $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; } $start_data_format = 'j, ' . $start_data_format; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $post_type . '-archive', $atts ); @@ -711,7 +714,7 @@ function radio_station_archive_list_shortcode( $post_type, $atts ) { // 2.4.0.6: use filtered shift separator $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'override' ); + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override' ); // 2.3.1: fix to append not echo override date to archive list $info['meta'] .= '' . esc_html( $start ) . ''; @@ -2022,10 +2025,13 @@ function radio_station_current_show_shortcode( $atts ) { // --- get time formats --- // 2.3.2: moved out to get once + // 2.4.0.6: added filter for default time format separator + $time_separator = ':'; + $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'current-show' ); if ( 24 == (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'H:i'; + $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; } $start_data_format = 'l, ' . $start_data_format; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'current-show', $atts ); @@ -2126,18 +2132,19 @@ function radio_station_current_show_shortcode( $atts ) { $start = radio_station_translate_time( $start ); $end = radio_station_translate_time( $end ); + // 2.4.0.6: use filtered shift separator + $separator = ' - '; + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'current-show' ); + // --- set shift classes --- + // 2.4.0.6: fix for exact current time as start time $classes = array( 'current-show-shifts', 'on-air-dj-sched' ); - if ( ( $now > $shift_start_time ) && ( $now < $shift_end_time ) ) { + if ( ( $now >= $shift_start_time ) && ( $now < $shift_end_time ) ) { $current_shift_start = $shift_start_time; $current_shift_end = $shift_end_time; $classes[] = 'current-shift'; $class = implode( ' ', $classes ); - // 2.4.0.6: use filtered shift separator - $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'override' ); - $current_shift_display = '
    '; $current_shift_display .= '' . esc_html( $start ) . ''; $current_shift_display .= '' . esc_html( $separator ) . ''; @@ -2154,10 +2161,6 @@ function radio_station_current_show_shortcode( $atts ) { } $class = implode( ' ', $classes ); - // 2.4.0.6: use filtered shift separator - $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'current-show' ); - // --- shift display output --- $shift_display .= '
    '; if ( in_array( 'current-shift', $classes ) ) { @@ -2735,10 +2738,13 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // --- set shift display data formats --- // 2.2.7: fix to convert time to integer // 2.3.2: moved outside shift loop + // 2.4.0.6: added filter for default time format separator + $time_separator = ':'; + $time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'upcoming-shows' ); if ( 24 == (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'H:i'; + $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; } $start_data_format = 'l, ' . $start_data_format; $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'upcoming-shows', $atts ); @@ -2836,7 +2842,7 @@ function radio_station_upcoming_shows_shortcode( $atts ) { // 2.4.0.6: use filtered shift separator $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'upcoming-shows' ); + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'upcoming-shows' ); // --- set shift display output --- $shift_display = '
    '; diff --git a/radio-station.php b/radio-station.php index 8c6a8fa..385857c 100644 --- a/radio-station.php +++ b/radio-station.php @@ -1827,7 +1827,7 @@ function radio_station_localization_script() { $js = "var radio = {}; radio.timezone = {}; radio.time = {}; radio.labels = {}; radio.units = {};"; // 2.4.0.6: add filterable time display separator - $time_separator = apply_filters( 'radio_station_time_separator', ':' ); + $time_separator = apply_filters( 'radio_station_time_separator', ':', 'javascript' ); $js .= " radio.sep = '" . esc_js( $time_separator ) . "';"; // --- set AJAX URL --- diff --git a/templates/master-schedule-list.php b/templates/master-schedule-list.php index 4275b7c..e237c36 100644 --- a/templates/master-schedule-list.php +++ b/templates/master-schedule-list.php @@ -26,11 +26,13 @@ // 2.3.2: set time formats early // 2.4.0.6: add filter for shift times separator $shifts_separator = __( 'to', 'radio-station' ); -$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'list' ); +$shifts_separator = apply_filters( 'radio_station_show_times_separator', $shifts_separator, 'schedule-list' ); +$time_separator = ':'; +$time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-list' ); if ( 24 == (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'H:i'; + $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; } $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'schedule-list', $atts ); $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'schedule-list', $atts ); diff --git a/templates/master-schedule-table.php b/templates/master-schedule-table.php index 525d04f..08d5e4f 100644 --- a/templates/master-schedule-table.php +++ b/templates/master-schedule-table.php @@ -27,11 +27,13 @@ // 2.3.2: set time formats early // 2.4.0.6: add filter for shift times separator $shifts_separator = __( 'to', 'radio-station' ); -$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'table' ); +$shifts_separator = apply_filters( 'radio_station_show_time_separator', $shifts_separator, 'schedule-table' ); +$time_separator = ':'; +$time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-table' ); if ( 24 == (int) $atts['time'] ) { - $start_data_format = $end_data_format = 'H:i'; + $start_data_format = $end_data_format = 'H' . $time_separator . 'i'; } else { - $start_data_format = $end_data_format = 'g:i a'; + $start_data_format = $end_data_format = 'g' . $time_separator . 'i a'; } $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, 'schedule-table', $atts ); $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, 'schedule-table', $atts ); diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 9181f37..ac61d5e 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -28,7 +28,9 @@ // 2.3.2: set time formats early // 2.4.0.6: add filter for shift times separator $shifts_separator = __( 'to', 'radio-station' ); -$shifts_separator = apply_filters( 'radio_station_schedule_show_time_separator', $shifts_separator, 'tabs' ); +$shifts_separator = apply_filters( 'radio_station_show_times_separator', $shifts_separator, 'schedule-tabs' ); +$time_separator = ':'; +$time_separator = apply_filters( 'radio_station_time_separator', $time_separator, 'schedule-tabs' ); if ( 24 == (int) $atts['time'] ) { $start_data_format = $end_data_format = 'H:i'; } else { diff --git a/templates/single-show-content.php b/templates/single-show-content.php index 8c7b963..b727c8b 100644 --- a/templates/single-show-content.php +++ b/templates/single-show-content.php @@ -604,7 +604,7 @@ // 2.4.0.6: use filtered shift separator $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'show-content' ); + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'show-content' ); // --- set show time output --- // 2.3.4: fix to start data_format attribute @@ -691,7 +691,7 @@ // 2.4.0.6: use filtered shift separator $separator = ' - '; - $separator = apply_filters( 'radio_station_times_separator', $separator, 'override-content' ); + $separator = apply_filters( 'radio_station_show_times_separator', $separator, 'override-content' ); $scheduled .= '
    ' . $newline; $scheduled .= '' . esc_html( $date ) . '' . $newline; From e836683592ad7a9f4936e9012c0a4138d6cf6bc8 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 24 Feb 2022 09:37:49 +1000 Subject: [PATCH 250/377] dev update freemius sdk --- CHANGELOG.md | 1 + freemius/LICENSE.txt | 674 - freemius/README.md | 282 - freemius/assets/css/admin/account.css | 1 - freemius/assets/css/admin/add-ons.css | 2 - freemius/assets/css/admin/affiliation.css | 1 - freemius/assets/css/admin/checkout.css | 1 - freemius/assets/css/admin/common.css | 2 - freemius/assets/css/admin/connect.css | 1 - freemius/assets/css/admin/debug.css | 1 - freemius/assets/css/admin/dialog-boxes.css | 2 - .../assets/css/admin/gdpr-optin-notice.css | 1 - freemius/assets/css/admin/index.php | 3 - freemius/assets/css/admin/plugins.css | 1 - freemius/assets/css/customizer.css | 1 - freemius/assets/css/index.php | 3 - freemius/assets/img/index.php | 3 - freemius/assets/img/plugin-icon.png | Bin 9380 -> 0 bytes freemius/assets/img/theme-icon.png | Bin 11237 -> 0 bytes freemius/assets/index.php | 3 - freemius/assets/js/index.php | 3 - freemius/assets/js/nojquery.ba-postmessage.js | 140 - .../assets/js/nojquery.ba-postmessage.min.js | 12 - freemius/assets/js/postmessage.js | 135 - freemius/config.php | 391 - freemius/includes/class-freemius-abstract.php | 597 - freemius/includes/class-freemius.php | 25364 ---------------- freemius/includes/class-fs-admin-notices.php | 321 - freemius/includes/class-fs-api.php | 664 - freemius/includes/class-fs-logger.php | 691 - freemius/includes/class-fs-options.php | 431 - freemius/includes/class-fs-plugin-updater.php | 1561 - freemius/includes/class-fs-security.php | 85 - freemius/includes/class-fs-storage.php | 532 - freemius/includes/class-fs-user-lock.php | 126 - .../class-fs-customizer-support-section.php | 102 - .../class-fs-customizer-upsell-control.php | 161 - freemius/includes/customizer/index.php | 3 - .../debug/class-fs-debug-bar-panel.php | 64 - freemius/includes/debug/debug-bar-start.php | 52 - freemius/includes/debug/index.php | 3 - .../entities/class-fs-affiliate-terms.php | 128 - .../includes/entities/class-fs-affiliate.php | 84 - .../includes/entities/class-fs-billing.php | 95 - .../includes/entities/class-fs-entity.php | 159 - .../includes/entities/class-fs-payment.php | 168 - .../entities/class-fs-plugin-info.php | 34 - .../entities/class-fs-plugin-license.php | 330 - .../entities/class-fs-plugin-plan.php | 145 - .../includes/entities/class-fs-plugin-tag.php | 60 - .../includes/entities/class-fs-plugin.php | 159 - .../includes/entities/class-fs-pricing.php | 157 - .../entities/class-fs-scope-entity.php | 29 - freemius/includes/entities/class-fs-site.php | 253 - .../entities/class-fs-subscription.php | 147 - freemius/includes/entities/class-fs-user.php | 62 - freemius/includes/entities/index.php | 3 - freemius/includes/fs-core-functions.php | 1416 - freemius/includes/fs-essential-functions.php | 500 - freemius/includes/fs-plugin-info-dialog.php | 1644 - freemius/includes/i18n.php | 605 - freemius/includes/index.php | 3 - freemius/includes/l10n.php | 48 - .../managers/class-fs-admin-menu-manager.php | 1006 - .../class-fs-admin-notice-manager.php | 472 - .../managers/class-fs-cache-manager.php | 326 - .../managers/class-fs-gdpr-manager.php | 202 - .../managers/class-fs-key-value-storage.php | 392 - .../managers/class-fs-license-manager.php | 104 - .../managers/class-fs-option-manager.php | 521 - .../managers/class-fs-plan-manager.php | 162 - .../managers/class-fs-plugin-manager.php | 220 - freemius/includes/managers/index.php | 3 - .../Exceptions/ArgumentNotExistException.php | 9 - .../sdk/Exceptions/EmptyArgumentException.php | 9 - .../includes/sdk/Exceptions/Exception.php | 74 - .../Exceptions/InvalidArgumentException.php | 8 - .../sdk/Exceptions/OAuthException.php | 12 - freemius/includes/sdk/Exceptions/index.php | 3 - freemius/includes/sdk/FreemiusBase.php | 215 - freemius/includes/sdk/FreemiusWordPress.php | 712 - freemius/includes/sdk/LICENSE.txt | 340 - freemius/includes/sdk/index.php | 3 - .../fs-essential-functions-1.1.7.1.php | 43 - .../fs-essential-functions-2.2.1.php | 45 - freemius/includes/supplements/index.php | 3 - freemius/index.php | 3 - freemius/languages/freemius-cs_CZ.mo | Bin 60931 -> 0 bytes freemius/languages/freemius-da_DK.mo | Bin 59840 -> 0 bytes freemius/languages/freemius-en.mo | Bin 59161 -> 0 bytes freemius/languages/freemius-es_ES.mo | Bin 62945 -> 0 bytes freemius/languages/freemius-fr_FR.mo | Bin 63519 -> 0 bytes freemius/languages/freemius-he_IL.mo | Bin 62213 -> 0 bytes freemius/languages/freemius-hu_HU.mo | Bin 60199 -> 0 bytes freemius/languages/freemius-it_IT.mo | Bin 61645 -> 0 bytes freemius/languages/freemius-ja.mo | Bin 67335 -> 0 bytes freemius/languages/freemius-nl_NL.mo | Bin 61628 -> 0 bytes freemius/languages/freemius-ru_RU.mo | Bin 75599 -> 0 bytes freemius/languages/freemius-ta.mo | Bin 93241 -> 0 bytes freemius/languages/freemius-zh_CN.mo | Bin 56773 -> 0 bytes freemius/languages/freemius.pot | 2556 -- freemius/languages/index.php | 3 - freemius/package.json | 27 - freemius/require.php | 49 - freemius/start.php | 530 - freemius/templates/account.php | 1098 - freemius/templates/account/billing.php | 423 - freemius/templates/account/index.php | 3 - .../partials/activate-license-button.php | 54 - freemius/templates/account/partials/addon.php | 446 - .../partials/deactivate-license-button.php | 36 - freemius/templates/account/partials/index.php | 3 - freemius/templates/account/partials/site.php | 352 - freemius/templates/account/payments.php | 59 - freemius/templates/add-ons.php | 502 - freemius/templates/add-trial-to-pricing.php | 31 - freemius/templates/admin-notice.php | 76 - freemius/templates/ajax-loader.php | 1 - freemius/templates/auto-installation.php | 249 - freemius/templates/checkout.php | 337 - freemius/templates/connect.php | 1038 - freemius/templates/contact.php | 128 - freemius/templates/debug.php | 759 - freemius/templates/debug/api-calls.php | 155 - freemius/templates/debug/index.php | 3 - freemius/templates/debug/logger.php | 66 - .../templates/debug/plugins-themes-sync.php | 76 - freemius/templates/debug/scheduled-crons.php | 136 - freemius/templates/email.php | 49 - freemius/templates/firewall-issues-js.php | 59 - freemius/templates/forms/affiliation.php | 509 - freemius/templates/forms/data-debug-mode.php | 213 - .../templates/forms/deactivation/contact.php | 23 - .../templates/forms/deactivation/form.php | 543 - .../templates/forms/deactivation/index.php | 3 - .../forms/deactivation/retry-skip.php | 24 - freemius/templates/forms/index.php | 3 - .../templates/forms/license-activation.php | 870 - freemius/templates/forms/optout.php | 336 - .../premium-versions-upgrade-handler.php | 205 - .../premium-versions-upgrade-metadata.php | 47 - freemius/templates/forms/resend-key.php | 247 - .../forms/subscription-cancellation.php | 277 - freemius/templates/forms/trial-start.php | 181 - freemius/templates/forms/user-change.php | 296 - freemius/templates/gdpr-optin-js.php | 66 - freemius/templates/index.php | 3 - freemius/templates/js/index.php | 3 - .../templates/js/jquery.content-change.php | 58 - .../templates/js/open-license-activation.php | 37 - freemius/templates/js/style-premium-theme.php | 53 - freemius/templates/partials/index.php | 2 - .../templates/partials/network-activation.php | 89 - freemius/templates/plugin-icon.php | 20 - .../templates/plugin-info/description.php | 78 - freemius/templates/plugin-info/features.php | 114 - freemius/templates/plugin-info/index.php | 3 - .../templates/plugin-info/screenshots.php | 34 - freemius/templates/powered-by.php | 61 - freemius/templates/pricing.php | 209 - freemius/templates/secure-https-header.php | 39 - freemius/templates/sticky-admin-notice-js.php | 39 - freemius/templates/tabs-capture-js.php | 63 - freemius/templates/tabs.php | 190 - radio-station.php | 2 +- readme.txt | 1 + 166 files changed, 3 insertions(+), 57779 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7716eb..34c3fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 2.4.0.6 +* Update: Freemius SDK (2.4.3) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes diff --git a/freemius/LICENSE.txt b/freemius/LICENSE.txt index 30ace6a..e69de29 100644 --- a/freemius/LICENSE.txt +++ b/freemius/LICENSE.txt @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file diff --git a/freemius/README.md b/freemius/README.md index 8260aff..e69de29 100644 --- a/freemius/README.md +++ b/freemius/README.md @@ -1,282 +0,0 @@ -Freemius WordPress SDK -====================== - -Welcome to the official repository for the Freemius SDK! Adding the SDK to your WordPress plugin, theme, or add-ons, enables all the benefits that come with using the [Freemius platform](https://freemius.com) such as: - -* [Software Licensing](https://freemius.com/wordpress/software-licensing/) -* [Secure Checkout](https://freemius.com/wordpress/checkout/) -* [Subscriptions](https://freemius.com/wordpress/recurring-payments-subscriptions/) -* [Automatic Updates](https://freemius.com/wordpress/automatic-software-updates/) -* [Seamless EU VAT](https://freemius.com/wordpress/collecting-eu-vat-europe/) -* [Cart Abandonment Recovery](https://freemius.com/wordpress/cart-abandonment-recovery/) -* [Affiliate Platform](https://freemius.com/wordpress/affiliate-platform/) -* [Analytics & Usage Tracking](https://freemius.com/wordpress/insights/) -* [User Dashboard](https://freemius.com/wordpress/user-dashboard/) - -* [Monetization](https://freemius.com/wordpress/) -* [Analytics](https://freemius.com/wordpress/insights/) -* [More...](https://freemius.com/wordpress/features-comparison/) - -Freemius truly empowers developers to create prosperous subscription-based businesses. - -If you're new to Freemius then we recommend taking a look at our [Getting Started](https://freemius.com/help/documentation/getting-started/) guide first. - -If you're a WordPress plugin or theme developer and are interested in monetizing with Freemius then you can [sign-up for a FREE account](https://dashboard.freemius.com/register/): - -https://dashboard.freemius.com/register/ - -Once you have your account setup and are familiar with how it all works you're ready to begin [integrating Freemius](https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/) into your WordPress product - -You can see some of the existing WordPress.org plugins & themes that are already utilizing the power of Freemius here: - -* https://profiles.wordpress.org/freemius/#content-plugins -* https://includewp.com/freemius/#focus - -## Code Documentation - -You can find the SDK's documentation here: -https://freemius.com/help/documentation/wordpress-sdk/ - -## Integrating & Initializing the SDK - -As part of the integration process, you'll need to [add the latest version](https://freemius.com/help/documentation/getting-started/#add_the_latest_wordpress_sdk_into_your_product) of the Freemius SDK into your WordPress project. - -Then, when you've completed the [SDK integration form](https://freemius.com/help/documentation/getting-started/#fill_out_the_sdk_integration_form) a snippet of code is generated which you'll need to copy and paste into the top of your main plugin's PHP file, right after the plugin's header comment. - -Note: For themes, this will be in the root `functions.php` file instead. - -A typical SDK snippet will look similar to the following (your particular snippet may differ slightly depending on your integration): - -```php -if ( ! function_exists( 'my_prefix_fs' ) ) { - // Create a helper function for easy SDK access. - function my_prefix_fs() { - global $my_prefix_fs; - - if ( ! isset( $my_prefix_fs ) ) { - // Include Freemius SDK. - require_once dirname(__FILE__) . '/freemius/start.php'; - - $my_prefix_fs = fs_dynamic_init( array( - 'id' => '1234', - 'slug' => 'my-new-plugin', - 'premium_slug' => 'my-new-plugin-premium', - 'type' => 'plugin', - 'public_key' => 'pk_bAEfta69seKymZzmf2xtqq8QXHz9y', - 'is_premium' => true, - // If your plugin is a serviceware, set this option to false. - 'has_premium_version' => true, - 'has_paid_plans' => true, - 'is_org_compliant' => true, - 'menu' => array( - 'slug' => 'my-new-plugin', - 'parent' => array( - 'slug' => 'options-general.php', - ), - ), - // Set the SDK to work in a sandbox mode (for development & testing). - // IMPORTANT: MAKE SURE TO REMOVE SECRET KEY BEFORE DEPLOYMENT. - 'secret_key' => 'sk_ubb4yN3mzqGR2x8#P7r5&@*xC$utE', - ) ); - } - - return $my_prefix_fs; - } - - // Init Freemius. - my_prefix_fs(); - // Signal that SDK was initiated. - do_action( 'my_prefix_fs_loaded' ); -} - -``` - -## Usage example - -You can call anySDK methods by prefixing them with the shortcode function for your particular plugin/theme (specified when completing the SDK integration form in the Developer Dashboard): - -```php -get_upgrade_url(); ?> -``` - -Or when calling Freemius multiple times in a scope, it's recommended to use it with the global variable: - -```php -get_account_url(); -?> -``` - -There are many other SDK methods available that you can use to enhance the functionality of your WordPress product. Some of the more common use-cases are covered in the [Freemius SDK Gists](https://freemius.com/help/documentation/wordpress-sdk/gists/) documentation. - -## Adding license based logic examples - -Add marketing content to encourage your users to upgrade for your paid version: - -```php -is_not_paying() ) { - echo '

    ' . esc_html__('Awesome Premium Features', 'my-plugin-slug') . '

    '; - echo '' . - esc_html__('Upgrade Now!', 'my-plugin-slug') . - ''; - echo '
    '; - } -?> -``` - -Add logic which will only be available in your premium plugin version: - -```php -is__premium_only() ) { - - // ... premium only logic ... - - } -?> -``` - -To add a function which will only be available in your premium plugin version, simply add __premium_only as the suffix of the function name. Just make sure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: - -```php -is__premium_only() ) { - // Init premium version. - $this->admin_init__premium_only(); - - add_action( 'admin_init', array( &$this, 'admin_init_hook__premium_only' ); - } - - ... - } - - // This method will be only included in the premium version. - function admin_init__premium_only() { - ... - } - - // This method will be only included in the premium version. - function admin_init_hook__premium_only() { - ... - } - } -?> -``` - -Add logic which will only be executed for customers in your 'professional' plan: - -```php -is_plan('professional', true) ) { - // .. logic related to Professional plan only ... - } -?> -``` - -Add logic which will only be executed for customers in your 'professional' plan or higher plans: - -```php -is_plan('professional') ) { - // ... logic related to Professional plan and higher plans ... - } -?> -``` - -Add logic which will only be available in your premium plugin version AND will only be executed for customers in your 'professional' plan (and higher plans): - -```php -is_plan__premium_only('professional') ) { - // ... logic related to Professional plan and higher plans ... - } -?> -``` - -Add logic only for users in trial: - -```php -is_trial() ) { - // ... logic for users in trial ... - } -?> -``` - -Add logic for specified paid plan: - -```php -is__premium_only() ) { - if ( my_prefix_fs()->is_plan( 'professional', true ) ) { - - // ... logic related to Professional plan only ... - - } else if ( my_prefix_fs()->is_plan( 'business' ) ) { - - // ... logic related to Business plan and higher plans ... - - } - } -?> -``` - -## Excluding files and folders from the free plugin version -There are [two ways](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/#excluding_files_and_folders_from_the_free_plugin_version) to exclude files from your free version. - -1. Add `__premium_only` just before the file extension. For example, functions__premium_only.php will be only included in the premium plugin version. This works for all types of files, not only PHP. -2. Add `@fs_premium_only` a special meta tag to the plugin's main PHP file header. Example: -```php - -``` -In the example plugin header above, the file `/lib/functions.php` and the directory `/premium-files/` will be removed from the free plugin version. - -# WordPress.org Compliance -Based on [WordPress.org Guidelines](https://wordpress.org/plugins/about/guidelines/) you are not allowed to submit a plugin that has premium code in it: -> All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers. - -Therefore, if you want to deploy your free plugin's version to WordPress.org, make sure you wrap all your premium code with `if ( my_prefix_fs()->{{ method }}__premium_only() )` or use [some of the other methods](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/) provided by the SDK to exclude premium features & files from the free version. - -## Deployment -Zip your Freemius product’s root folder and [upload it in the Deployment section](https://freemius.com/help/documentation/selling-with-freemius/deployment/) in the *Freemius Developer's Dashboard*. -The plugin/theme will automatically be scanned and processed by a custom-developed *PHP Processor* which will auto-generate two versions of your plugin: - -1. **Premium version**: Identical to your uploaded version, including all code (except your `secret_key`). Will be enabled for download ONLY for your paying or in trial customers. -2. **Free version**: The code stripped from all your paid features (based on the logic added wrapped in `{ method }__premium_only()`). - -The free version is the one that you should give your users to download. Therefore, download the free generated version and upload to your site. Or, if your plugin was WordPress.org compliant and you made sure to exclude all your premium code with the different provided techniques, you can deploy the downloaded free version to the .org repo. - -## License -Copyright (c) Freemius®, Inc. - -Licensed under the GNU general public license (version 3). diff --git a/freemius/assets/css/admin/account.css b/freemius/assets/css/admin/account.css index 5582a0a..e69de29 100644 --- a/freemius/assets/css/admin/account.css +++ b/freemius/assets/css/admin/account.css @@ -1 +0,0 @@ -label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id="license_not_whitelabeled"].success,.fs-notice[data-id="license_whitelabeled"].success{color:inherit;border-left-color:#00a0d2}.fs-notice[data-id="license_not_whitelabeled"].success label.fs-plugin-title,.fs-notice[data-id="license_whitelabeled"].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #F1F1F1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:0.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type="text"]{color:#0073AA;font-size:16px;background:none}.fs-key-value-table input[type="text"]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} diff --git a/freemius/assets/css/admin/add-ons.css b/freemius/assets/css/admin/add-ons.css index d2391c6..e69de29 100644 --- a/freemius/assets/css/admin/add-ons.css +++ b/freemius/assets/css/admin/add-ons.css @@ -1,2 +0,0 @@ -.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:greenyellow;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.3);box-shadow:1px 1px 1px rgba(0,0,0,0.3);text-transform:uppercase;font-size:0.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} -#TB_window,#TB_window iframe{width:821px !important}#plugin-information .fyi{width:266px !important}#plugin-information #section-holder{margin-right:299px}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description iframe{max-width:100%}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:greenyellow;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid darkgreen;padding:2px;text-align:center;font-size:0.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#F3F3F3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:greenyellow}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;width:auto;top:0;right:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-top:6px solid white;border-right:4px solid transparent;border-left:4px solid transparent;top:12px;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{position:absolute;right:-1px;top:100%;margin-left:auto;padding:3px 0;border:1px solid #bfbfbf;background-color:#fff;z-index:1;width:230px;text-align:left;-moz-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);-webkit-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{-moz-border-radius:3px 0 3px 3px;-webkit-border-radius:3px 0 3px 3px;border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{-moz-border-radius:3px 3px 0 3px;-webkit-border-radius:3px 3px 0 3px;border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:normal;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{top:auto;bottom:100%;margin-bottom:2px}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{text-align:center;display:table}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer>.button,#plugin-information-footer .fs-dropdown{position:relative;top:3px}#plugin-information-footer>.button.left,#plugin-information-footer .fs-dropdown.left{float:left}#plugin-information-footer>.right,#plugin-information-footer .fs-dropdown{float:right}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} diff --git a/freemius/assets/css/admin/affiliation.css b/freemius/assets/css/admin/affiliation.css index 003ca37..e69de29 100644 --- a/freemius/assets/css/admin/affiliation.css +++ b/freemius/assets/css/admin/affiliation.css @@ -1 +0,0 @@ -@charset "UTF-8";#fs_affiliation_content_wrapper #messages{margin-top:25px}#fs_affiliation_content_wrapper h3{font-size:24px;padding:0;margin-left:0}#fs_affiliation_content_wrapper ul li{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;list-style-type:none}#fs_affiliation_content_wrapper ul li:before{content:'✓';margin-right:10px;font-weight:bold}#fs_affiliation_content_wrapper p:not(.description),#fs_affiliation_content_wrapper li,#fs_affiliation_content_wrapper label{font-size:16px !important;line-height:26px !important}#fs_affiliation_content_wrapper .button{margin-top:20px;margin-bottom:7px;line-height:35px;height:40px;font-size:16px}#fs_affiliation_content_wrapper .button#cancel_button{margin-right:5px}#fs_affiliation_content_wrapper form .input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form .input-container .input-label{font-weight:bold;display:block;width:100%}#fs_affiliation_content_wrapper form .input-container.input-container-text label,#fs_affiliation_content_wrapper form .input-container.input-container-text input,#fs_affiliation_content_wrapper form .input-container.input-container-text textarea{display:block}#fs_affiliation_content_wrapper form .input-container #add_domain,#fs_affiliation_content_wrapper form .input-container .remove-domain{text-decoration:none;display:inline-block;margin-top:3px}#fs_affiliation_content_wrapper form .input-container #add_domain:focus,#fs_affiliation_content_wrapper form .input-container .remove-domain:focus{box-shadow:none}#fs_affiliation_content_wrapper form .input-container #add_domain.disabled,#fs_affiliation_content_wrapper form .input-container .remove-domain.disabled{color:#aaa;cursor:default}#fs_affiliation_content_wrapper form #extra_domains_container .description{margin-top:0;position:relative;top:-4px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain{display:inline-block;margin-right:5px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain:last-of-type{margin-bottom:0} diff --git a/freemius/assets/css/admin/checkout.css b/freemius/assets/css/admin/checkout.css index 56515d2..e69de29 100644 --- a/freemius/assets/css/admin/checkout.css +++ b/freemius/assets/css/admin/checkout.css @@ -1 +0,0 @@ -@media screen and (max-width: 782px){#wpbody-content{padding-bottom:0 !important}} diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css index d96aa2f..e69de29 100644 --- a/freemius/assets/css/admin/common.css +++ b/freemius/assets/css/admin/common.css @@ -1,2 +0,0 @@ -.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,0.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,0.1),inset 0 1px 3px 0 rgba(0,0,0,0.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,0.5);z-index:999;-moz-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}.fs-switch-feedback{margin-left:10px}.fs-switch-feedback.success{color:#71ae00}.rtl .fs-switch-feedback{margin-left:0;margin-right:10px}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media (max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}} -.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/assets/css/admin/connect.css b/freemius/assets/css/admin/connect.css index dff7c49..e69de29 100644 --- a/freemius/assets/css/admin/connect.css +++ b/freemius/assets/css/admin/connect.css @@ -1 +0,0 @@ -#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions>.fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li>i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li .fs-switch{float:right}#fs_connect .fs-permissions ul li .fs-permission-description{margin-left:55px}#fs_connect .fs-permissions ul li .fs-permission-description span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff !important;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li .fs-switch{float:left}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;text-align:center;font-size:0.9em;margin-top:10px} diff --git a/freemius/assets/css/admin/debug.css b/freemius/assets/css/admin/debug.css index 4b30d84..e69de29 100644 --- a/freemius/assets/css/admin/debug.css +++ b/freemius/assets/css/admin/debug.css @@ -1 +0,0 @@ -.fs-switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:bold}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac !important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be} diff --git a/freemius/assets/css/admin/dialog-boxes.css b/freemius/assets/css/admin/dialog-boxes.css index 434f346..e69de29 100644 --- a/freemius/assets/css/admin/dialog-boxes.css +++ b/freemius/assets/css/admin/dialog-boxes.css @@ -1,2 +0,0 @@ -.fs-modal{position:fixed;overflow:auto;height:100%;width:100%;top:0;z-index:100000;display:none;background:rgba(0,0,0,0.6)}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;position:absolute;left:50%;margin-left:-298px;padding-bottom:30px;top:-100%;z-index:100001;width:596px}@media (max-width: 650px){.fs-modal .fs-modal-dialog{margin-left:-50%;box-sizing:border-box;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active{display:block}.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{border:0;background:#fefefe;padding:20px}.fs-modal .fs-modal-header{border-bottom:#eeeeee solid 1px;background:#fbfbfb;padding:15px 20px;position:relative;margin-bottom:-10px}.fs-modal .fs-modal-header h4{margin:0;padding:0;text-transform:uppercase;font-size:1.2em;font-weight:bold;color:#cacaca;text-shadow:1px 1px 1px #fff;letter-spacing:0.6px;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{position:absolute;right:10px;top:12px;cursor:pointer;color:#bbb;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;padding:3px;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;-ms-transition:all 0.2s ease-in-out;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{color:#fff;background:#aaa}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-weight:bold;font-size:20px;margin-top:0}.fs-modal .fs-modal-footer{border-top:#eeeeee solid 1px;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:first-child{margin:0}.fs-modal .fs-modal-panel>.notice.inline{margin:0;display:none}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{right:auto;left:20px}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .reason-input,.fs-modal.fs-modal-deactivation-feedback .internal-message{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea,.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;padding:7px;display:none}@media (max-width: 650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label{float:left}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0 !important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{margin-top:0;line-height:1.5em}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:bold;padding:0 25px;margin-bottom:0}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;top:5px;position:relative}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table,.fs-license-options-container table select,.fs-license-options-container table .fs-available-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{position:relative;top:6px;float:left;margin-right:5px}.fs-license-options-container table .fs-other-license-key-container div{overflow:hidden;width:auto;height:30px;display:block;top:2px;position:relative}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{width:100%;border-collapse:collapse}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type="radio"]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{width:1%;padding-left:3px;padding-right:3px}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key{width:100%}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media (max-width: 650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}} -.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-right:7px;margin-left:0}a.show-license-resend-modal{margin-top:4px;display:inline-block}.fs-ajax-loader{position:relative;width:170px;height:20px;margin:auto}.fs-ajax-loader .fs-ajax-loader-bar{position:absolute;top:0;background-color:#0074a3;width:20px;height:20px;-webkit-animation-name:bounce_ajaxLoader;-moz-animation-name:bounce_ajaxLoader;-ms-animation-name:bounce_ajaxLoader;-o-animation-name:bounce_ajaxLoader;animation-name:bounce_ajaxLoader;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s;animation-duration:1.5s;animation-iteration-count:infinite;-o-animation-iteration-count:infinite;-ms-animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;-webkit-animation-direction:normal;-moz-animation-direction:normal;-ms-animation-direction:normal;-o-animation-direction:normal;animation-direction:normal;-moz-transform:0.3;-o-transform:0.3;-ms-transform:0.3;-webkit-transform:0.3;transform:0.3}.fs-ajax-loader .fs-ajax-loader-bar-1{left:0px;animation-delay:0.6s;-o-animation-delay:0.6s;-ms-animation-delay:0.6s;-webkit-animation-delay:0.6s;-moz-animation-delay:0.6s}.fs-ajax-loader .fs-ajax-loader-bar-2{left:19px;animation-delay:0.75s;-o-animation-delay:0.75s;-ms-animation-delay:0.75s;-webkit-animation-delay:0.75s;-moz-animation-delay:0.75s}.fs-ajax-loader .fs-ajax-loader-bar-3{left:38px;animation-delay:0.9s;-o-animation-delay:0.9s;-ms-animation-delay:0.9s;-webkit-animation-delay:0.9s;-moz-animation-delay:0.9s}.fs-ajax-loader .fs-ajax-loader-bar-4{left:57px;animation-delay:1.05s;-o-animation-delay:1.05s;-ms-animation-delay:1.05s;-webkit-animation-delay:1.05s;-moz-animation-delay:1.05s}.fs-ajax-loader .fs-ajax-loader-bar-5{left:76px;animation-delay:1.2s;-o-animation-delay:1.2s;-ms-animation-delay:1.2s;-webkit-animation-delay:1.2s;-moz-animation-delay:1.2s}.fs-ajax-loader .fs-ajax-loader-bar-6{left:95px;animation-delay:1.35s;-o-animation-delay:1.35s;-ms-animation-delay:1.35s;-webkit-animation-delay:1.35s;-moz-animation-delay:1.35s}.fs-ajax-loader .fs-ajax-loader-bar-7{left:114px;animation-delay:1.5s;-o-animation-delay:1.5s;-ms-animation-delay:1.5s;-webkit-animation-delay:1.5s;-moz-animation-delay:1.5s}.fs-ajax-loader .fs-ajax-loader-bar-8{left:133px;animation-delay:1.65s;-o-animation-delay:1.65s;-ms-animation-delay:1.65s;-webkit-animation-delay:1.65s;-moz-animation-delay:1.65s}@-moz-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-ms-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-o-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-webkit-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}.fs-modal-auto-install #request-filesystem-credentials-form h2,.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;padding:10px 10px 5px 10px;width:300px;max-width:100%}.fs-modal-auto-install #request-filesystem-credentials-form>div,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form fieldset{width:300px;max-width:100%;margin:0 auto;display:block}.button-primary.warn{box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c;background:#f56a48;border-color:#ec6544 #d2593c #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{color:#f5b3a1 !important;background:#e76444 !important;border-color:#d85e40 !important;text-shadow:0 -1px 0 rgba(0,0,0,0.1) !important} diff --git a/freemius/assets/css/admin/gdpr-optin-notice.css b/freemius/assets/css/admin/gdpr-optin-notice.css index 0da5146..e69de29 100644 --- a/freemius/assets/css/admin/gdpr-optin-notice.css +++ b/freemius/assets/css/admin/gdpr-optin-notice.css @@ -1 +0,0 @@ -.fs-notice[data-id^="gdpr_optin_actions"] .underlined{text-decoration:underline}.fs-notice[data-id^="gdpr_optin_actions"] ul .button,.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{vertical-align:middle}.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{display:inline-block;margin-left:3px} diff --git a/freemius/assets/css/admin/index.php b/freemius/assets/css/admin/index.php index 0316c6a..e69de29 100644 --- a/freemius/assets/css/admin/index.php +++ b/freemius/assets/css/admin/index.php @@ -1,3 +0,0 @@ -4QUc+`00KlZEA}{+QaB(jz&calwp&7Oa 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r diff --git a/freemius/assets/img/theme-icon.png b/freemius/assets/img/theme-icon.png index 045d4b25f9727fada65dcfd66cf0a0a3d167ee84..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 11237 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

    {^6Retfv z6e)+Y86 z&-OAhn&%B~c$`zOH~YnViV(=PdD(}Fcr6gb z@qCJTX)KfxzL`hqdq$pyR{#-Ot7VpvXTX&Lf;o10Uz!M^YMI*SFN%7P`VNK(D>QTdyCP-Z<%CK(t*T4j;W{3$D2mM!AexlA- zUb<}1Dx;~3vaWm)z9em_tc|P?cssQ$Hj)Y6^M;Y^!#S7DAf(wTVsi4od$#co(U{KZ4KkEm$NX_fw^6flAOA1wX{X>{{THMY+L{U diff --git a/freemius/assets/index.php b/freemius/assets/index.php index 0316c6a..e69de29 100644 --- a/freemius/assets/index.php +++ b/freemius/assets/index.php @@ -1,3 +0,0 @@ - 0) { - $window.on('scroll', function () { - for (var i = 0; i < iframes.length; i++) { - FS.PostMessage.postScroll(iframes[i]); - } - }); - } - }, - init_child : function () - { - this.init(_parent_subdomain); - - _is_child = true; - - // Post height of a child right after window is loaded. - $(window).bind('load', function () { - FS.PostMessage.postHeight(); - - // Post message that window was loaded. - FS.PostMessage.post('loaded'); - }); - }, - hasParent : function () - { - return _hasParent; - }, - postHeight : function (diff, wrapper) { - diff = diff || 0; - wrapper = wrapper || '#wrap_section'; - this.post('height', { - height: diff + $(wrapper).outerHeight(true) - }); - }, - postScroll : function (iframe) { - this.post('scroll', { - top: $window.scrollTop(), - height: ($window.height() - parseFloat($html.css('paddingTop')) - parseFloat($html.css('marginTop'))) - }, iframe); - }, - post : function (type, data, iframe) - { - console.debug('PostMessage.post', type); - - if (iframe) - { - // Post to iframe. - _postman.postMessage(JSON.stringify({ - type: type, - data: data - }), iframe.src, iframe.contentWindow); - } - else { - // Post to parent. - _postman.postMessage(JSON.stringify({ - type: type, - data: data - }), _parent_url, window.parent); - } - }, - receive: function (type, callback) - { - console.debug('PostMessage.receive', type); - - if (undef === _callbacks[type]) - _callbacks[type] = []; - - _callbacks[type].push(callback); - }, - receiveOnce: function (type, callback) - { - if (this.is_set(type)) - return; - - this.receive(type, callback); - }, - // Check if any callbacks assigned to a specified message type. - is_set: function (type) - { - return (undef != _callbacks[type]); - }, - parent_url: function () - { - return _parent_url; - }, - parent_subdomain: function () - { - return _parent_subdomain; - } - }; - }(); -})(jQuery); \ No newline at end of file diff --git a/freemius/config.php b/freemius/config.php index f51eb40..e69de29 100644 --- a/freemius/config.php +++ b/freemius/config.php @@ -1,391 +0,0 @@ -is_registered() && $fs->is_tracking_allowed()` - * - * @since 1.0.1 - * @return bool - */ - abstract function is_registered(); - - /** - * Check if the user skipped connecting the account with Freemius. - * - * @since 1.0.7 - * - * @return bool - */ - abstract function is_anonymous(); - - /** - * Check if the user currently in activation mode. - * - * @since 1.0.7 - * - * @return bool - */ - abstract function is_activation_mode(); - - #endregion - - #---------------------------------------------------------------------------------- - #region Usage Tracking - #---------------------------------------------------------------------------------- - - /** - * Returns TRUE if the user opted-in and didn't disconnect (opt-out). - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool - */ - abstract function is_tracking_allowed(); - - /** - * Returns TRUE if the user never opted-in or manually opted-out. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return bool - */ - function is_tracking_prohibited() { - return ! $this->is_registered() || ! $this->is_tracking_allowed(); - } - - /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API Result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - abstract function stop_tracking(); - - /** - * Opt-in back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - abstract function allow_tracking(); - - #endregion - - #---------------------------------------------------------------------------------- - #region Module Type - #---------------------------------------------------------------------------------- - - /** - * Checks if the plugin's type is "plugin". The other type is "theme". - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - abstract function is_plugin(); - - /** - * Checks if the module type is "theme". The other type is "plugin". - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - function is_theme() { - return ( ! $this->is_plugin() ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Permissions - #---------------------------------------------------------------------------------- - - /** - * Check if plugin must be WordPress.org compliant. - * - * @since 1.0.7 - * - * @return bool - */ - abstract function is_org_repo_compliant(); - - /** - * Check if plugin is allowed to install executable files. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return bool - */ - function is_allowed_to_install() { - return ( $this->is_premium() || ! $this->is_org_repo_compliant() ); - } - - #endregion - - /** - * Check if user in trial or in free plan (not paying). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return bool - */ - function is_not_paying() { - return ( $this->is_trial() || $this->is_free_plan() ); - } - - /** - * Check if the user has an activated and valid paid license on current plugin's install. - * - * @since 1.0.9 - * - * @return bool - */ - abstract function is_paying(); - - /** - * Check if the user is paying or in trial. - * - * @since 1.0.9 - * - * @return bool - */ - function is_paying_or_trial() { - return ( $this->is_paying() || $this->is_trial() ); - } - - /** - * Check if user in a trial or have feature enabled license. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @return bool - */ - abstract function can_use_premium_code(); - - #---------------------------------------------------------------------------------- - #region Premium Only - #---------------------------------------------------------------------------------- - - /** - * All logic wrapped in methods with "__premium_only()" suffix will be only - * included in the premium code. - * - * Example: - * if ( freemius()->is__premium_only() ) { - * ... - * } - */ - - /** - * Returns true when running premium plugin code. - * - * @since 1.0.9 - * - * @return bool - */ - function is__premium_only() { - return $this->is_premium(); - } - - /** - * Check if the user has an activated and valid paid license on current plugin's install. - * - * @since 1.0.9 - * - * @return bool - * - */ - function is_paying__premium_only() { - return ( $this->is__premium_only() && $this->is_paying() ); - } - - /** - * All code wrapped in this statement will be only included in the premium code. - * - * @since 1.0.9 - * - * @param string $plan Plan name. - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - function is_plan__premium_only( $plan, $exact = false ) { - return ( $this->is_premium() && $this->is_plan( $plan, $exact ) ); - } - - /** - * Check if plan matches active license' plan or active trial license' plan. - * - * All code wrapped in this statement will be only included in the premium code. - * - * @since 1.0.9 - * - * @param string $plan Plan name. - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - function is_plan_or_trial__premium_only( $plan, $exact = false ) { - return ( $this->is_premium() && $this->is_plan_or_trial( $plan, $exact ) ); - } - - /** - * Check if the user is paying or in trial. - * - * All code wrapped in this statement will be only included in the premium code. - * - * @since 1.0.9 - * - * @return bool - */ - function is_paying_or_trial__premium_only() { - return $this->is_premium() && $this->is_paying_or_trial(); - } - - /** - * Check if the user has an activated and valid paid license on current plugin's install. - * - * @since 1.0.4 - * - * @return bool - * - * @deprecated Method name is confusing since it's not clear from the name the code will be removed. - * @using Alias to is_paying__premium_only() - */ - function is_paying__fs__() { - return $this->is_paying__premium_only(); - } - - /** - * Check if user in a trial or have feature enabled license. - * - * All code wrapped in this statement will be only included in the premium code. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - * @return bool - */ - function can_use_premium_code__premium_only() { - return $this->is_premium() && $this->can_use_premium_code(); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Trial - #---------------------------------------------------------------------------------- - - /** - * Check if the user in a trial. - * - * @since 1.0.3 - * - * @return bool - */ - abstract function is_trial(); - - /** - * Check if trial already utilized. - * - * @since 1.0.9 - * - * @return bool - */ - abstract function is_trial_utilized(); - - #endregion - - #---------------------------------------------------------------------------------- - #region Plans - #---------------------------------------------------------------------------------- - - /** - * Check if the user is on the free plan of the product. - * - * @since 1.0.4 - * - * @return bool - */ - abstract function is_free_plan(); - - /** - * @since 1.0.2 - * - * @param string $plan Plan name. - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - abstract function is_plan( $plan, $exact = false ); - - /** - * Check if plan based on trial. If not in trial mode, should return false. - * - * @since 1.0.9 - * - * @param string $plan Plan name. - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - abstract function is_trial_plan( $plan, $exact = false ); - - /** - * Check if plan matches active license' plan or active trial license' plan. - * - * @since 1.0.9 - * - * @param string $plan Plan name. - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - function is_plan_or_trial( $plan, $exact = false ) { - return $this->is_plan( $plan, $exact ) || - $this->is_trial_plan( $plan, $exact ); - } - - /** - * Check if plugin has any paid plans. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - abstract function has_paid_plan(); - - /** - * Check if plugin has any free plan, or is it premium only. - * - * Note: If no plans configured, assume plugin is free. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - abstract function has_free_plan(); - - /** - * Check if plugin is premium only (no free plans). - * - * NOTE: is__premium_only() is very different method, don't get confused. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - * @return bool - */ - abstract function is_only_premium(); - - /** - * Check if module has a premium code version. - * - * Serviceware module might be freemium without any - * premium code version, where the paid features - * are all part of the service. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @return bool - */ - abstract function has_premium_version(); - - /** - * Check if module has any release on Freemius, - * or all plugin's code is on WordPress.org (Serviceware). - * - * @return bool - */ - function has_release_on_freemius() { - return ! $this->is_org_repo_compliant() || - $this->has_premium_version(); - } - - /** - * Checks if it's a freemium plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - * @return bool - */ - function is_freemium() { - return $this->has_paid_plan() && - $this->has_free_plan(); - } - - /** - * Check if module has only one plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @return bool - */ - abstract function is_single_plan(); - - #endregion - - /** - * Check if running payments in sandbox mode. - * - * @since 1.0.4 - * - * @return bool - */ - abstract function is_payments_sandbox(); - - /** - * Check if running test vs. live plugin. - * - * @since 1.0.5 - * - * @return bool - */ - abstract function is_live(); - - /** - * Check if running premium plugin code. - * - * @since 1.0.5 - * - * @return bool - */ - abstract function is_premium(); - - /** - * Get upgrade URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @param string $period Billing cycle. - * - * @return string - */ - abstract function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ); - - /** - * Check if Freemius was first added in a plugin update. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.5 - * - * @return bool - */ - function is_plugin_update() { - return ! $this->is_plugin_new_install(); - } - - /** - * Check if Freemius was part of the plugin when the user installed it first. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.5 - * - * @return bool - */ - abstract function is_plugin_new_install(); - - #---------------------------------------------------------------------------------- - #region Marketing - #---------------------------------------------------------------------------------- - - /** - * Check if current user purchased any other plugins before. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - abstract function has_purchased_before(); - - /** - * Check if current user classified as an agency. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - abstract function is_agency(); - - /** - * Check if current user classified as a developer. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - abstract function is_developer(); - - /** - * Check if current user classified as a business. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - abstract function is_business(); - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index f3b3428..e69de29 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -1,25364 +0,0 @@ -store_id_slug_type_path_map( $module_id, $slug ); - } - - $this->_module_id = $module_id; - $this->_slug = $this->get_slug(); - $this->_module_type = $this->get_module_type(); - - $this->_blog_id = is_multisite() ? get_current_blog_id() : null; - - $this->_storage = FS_Storage::instance( $this->_module_type, $this->_slug ); - - $this->_cache = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" ); - - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init ); - $this->_plugin_dir_path = plugin_dir_path( $this->_plugin_main_file_path ); - $this->_plugin_basename = $this->get_plugin_basename(); - $this->_free_plugin_basename = str_replace( '-premium/', '/', $this->_plugin_basename ); - - $this->_is_multisite_integrated = ( - defined( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) && - ( true === constant( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) ) - ); - - $this->_is_network_active = ( - is_multisite() && - $this->_is_multisite_integrated && - // Themes are always network activated, but the ACTUAL activation is per site. - $this->is_plugin() && - ( - is_plugin_active_for_network( $this->_plugin_basename ) || - // Plugin network level activation or uninstall. - ( fs_is_network_admin() && is_plugin_inactive( $this->_plugin_basename ) ) - ) - ); - - $this->_storage->set_network_active( - $this->_is_network_active, - $this->is_delegated_connection() - ); - - if ( ! isset( $this->_storage->is_network_activated ) ) { - $this->_storage->is_network_activated = $this->_is_network_active; - } - - if ( $this->_storage->is_network_activated != $this->_is_network_active ) { - // Update last activation level. - $this->_storage->is_network_activated = $this->_is_network_active; - - $this->maybe_adjust_storage(); - } - - #region Migration - - if ( is_multisite() ) { - /** - * If the install_timestamp exists on the site level but doesn't exist on the - * network level storage, it means that we need to process the storage with migration. - * - * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, install_timestamp will be already set in the network level storage. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - if ( false === $this->_storage->get( 'install_timestamp', false, true ) && - false !== $this->_storage->get( 'install_timestamp', false, false ) - ) { - // Initiate storage migration. - $this->_storage->migrate_to_network(); - - // Migrate module cache to network level storage. - $this->_cache->migrate_to_network(); - } - } - - #endregion - - $base_name_split = explode( '/', $this->_plugin_basename ); - $this->_plugin_dir_name = $base_name_split[0]; - - if ( $this->_logger->is_on() ) { - $this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path ); - $this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path ); - $this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename ); - $this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename ); - $this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name ); - } - - // Remember link between file to slug. - $this->store_file_slug_map(); - - // Store plugin's initial install timestamp. - if ( ! isset( $this->_storage->install_timestamp ) ) { - $this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME; - } - - if ( ! is_object( $this->_plugin ) ) { - $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get(); - } - - $this->_admin_notices = FS_Admin_Notices::instance( - $this->_slug . ( $this->is_theme() ? ':theme' : '' ), - /** - * Ensure that the admin notice will always have a title by using the stored plugin title if available and - * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - ( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ), - $this->get_unique_affix() - ); - - if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) || - fs_request_is_action( 'restart_freemius' ) - ) { - FS_Api::clear_cache(); - $this->_cache->clear(); - } - - $this->register_constructor_hooks(); - - /** - * Starting from version 2.0.0, `FS_Site` entities no longer have the `plan` property and have `plan_id` - * instead. This should be called before calling `_load_account()`, otherwise, `$this->_site` will not be - * loaded in `_load_account` for versions of SDK starting from 2.0.0. - * - * @author Leo Fajardo (@leorw) - */ - self::migrate_install_plan_to_plan_id( $this->_storage ); - - $this->_load_account(); - - $this->_version_updates_handler(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - private function maybe_adjust_storage() { - $install_timestamp = null; - $prev_is_premium = null; - - $options_to_update = array(); - - $is_network_admin = fs_is_network_admin(); - - $network_install_timestamp = $this->_storage->get( 'install_timestamp', null, true ); - - if ( ! $is_network_admin ) { - if ( is_null( $network_install_timestamp ) ) { - // Plugin was not network-activated before. - return; - } - - if ( is_null( $this->_storage->get( 'install_timestamp', null, false ) ) ) { - // Set the `install_timestamp` only if it's not yet set. - $install_timestamp = $network_install_timestamp; - } - - $prev_is_premium = $this->_storage->get( 'prev_is_premium', null, true ); - } else { - $current_wp_user = self::_get_current_wp_user(); - $current_fs_user = self::_get_user_by_email( $current_wp_user->user_email ); - $network_user_info = array(); - - $skips_count = 0; - - $sites = self::get_sites(); - $sites_count = count( $sites ); - - $blog_id_2_install_map = array(); - - $is_first_non_ignored_blog = true; - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - $blog_install_timestamp = $this->_storage->get( 'install_timestamp', null, $blog_id ); - - if ( is_null( $blog_install_timestamp ) ) { - // Plugin has not been installed on this blog. - continue; - } - - $is_earlier_install = ( - ! is_null( $install_timestamp ) && - $blog_install_timestamp < $install_timestamp - ); - - $install = $this->get_install_by_blog_id( $blog_id ); - - $update_network_user_info = false; - - if ( ! is_object( $install ) ) { - if ( ! $this->_storage->get( 'is_anonymous', false, $blog_id ) ) { - // The opt-in decision (whether to skip or opt in) is yet to be made. - continue; - } - - $skips_count ++; - } else { - $blog_id_2_install_map[ $blog_id ] = $install; - - if ( empty( $network_user_info ) ) { - // Set the network user info for the 1st time. Choose any user information whether or not it is for the current WP user. - $update_network_user_info = true; - } - - if ( ! $update_network_user_info && - is_object( $current_fs_user ) && - $network_user_info['user_id'] != $current_fs_user->id && - $install->user_id == $current_fs_user->id - ) { - // If an install that is owned by the current WP user is found, use its user information instead. - $update_network_user_info = true; - } - - if ( ! $update_network_user_info && - $is_earlier_install && - ( ! is_object( $current_fs_user ) || $current_fs_user->id == $install->user_id ) - ) { - // Update to the earliest install info if there's no install found so far that is owned by the current WP user; OR only if the found install is owned by the current WP user. - $update_network_user_info = true; - } - } - - if ( $update_network_user_info ) { - $network_user_info = array( - 'user_id' => $install->user_id, - 'blog_id' => $blog_id - ); - } - - $site_prev_is_premium = $this->_storage->get( 'prev_is_premium', null, $blog_id ); - - if ( $is_first_non_ignored_blog ) { - $prev_is_premium = $site_prev_is_premium; - - if ( is_null( $network_install_timestamp ) ) { - $install_timestamp = $blog_install_timestamp; - } - - $is_first_non_ignored_blog = false; - - continue; - } - - if ( ! is_null( $prev_is_premium ) && $prev_is_premium !== $site_prev_is_premium ) { - // If a different `$site_prev_is_premium` value is found, do not include the option in the collection of options to update. - $prev_is_premium = null; - } - - if ( $is_earlier_install ) { - // If an earlier install timestamp is found. - $install_timestamp = $blog_install_timestamp; - } - } - - $installs_count = count( $blog_id_2_install_map ); - - if ( $sites_count === ( $installs_count + $skips_count ) ) { - if ( ! empty( $network_user_info ) ) { - $options_to_update['network_user_id'] = $network_user_info['user_id']; - $options_to_update['network_install_blog_id'] = $network_user_info['blog_id']; - - foreach ( $blog_id_2_install_map as $blog_id => $install ) { - if ( $install->user_id == $network_user_info['user_id'] ) { - continue; - } - - $this->_storage->store( 'is_delegated_connection', true, $blog_id ); - } - } - - if ( $sites_count === $skips_count ) { - /** - * Assume network-level skipping as the intended action if all actions identified were only - * skipping of the connection (i.e., no opt-ins and delegated connections so far). - */ - $options_to_update['is_anonymous_ms'] = true; - } else if ( $sites_count === $installs_count ) { - /** - * Assume network-level opt-in as the intended action if all actions identified were only opt-ins - * (i.e., no delegation and skipping of the connections so far). - */ - $options_to_update['is_network_connected'] = true; - } - } - } - - if ( ! is_null( $install_timestamp ) ) { - $options_to_update['install_timestamp'] = $install_timestamp; - } - - if ( ! is_null( $prev_is_premium ) ) { - $options_to_update['prev_is_premium'] = $prev_is_premium; - } - - if ( ! empty( $options_to_update ) ) { - $this->adjust_storage( $options_to_update, $is_network_admin ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param array $options - * @param bool $is_network_admin - */ - private function adjust_storage( $options, $is_network_admin ) { - foreach ( $options as $name => $value ) { - $this->_storage->store( $name, $value, $is_network_admin ? true : null ); - } - } - - /** - * Checks whether this module has a settings menu. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - function has_settings_menu() { - return ( $this->_is_network_active && fs_is_network_admin() ) ? - $this->_menu->has_network_menu() : - $this->_menu->has_menu(); - } - - /** - * If `true` the opt-in should be shown as a modal dialog box on the themes.php page. WordPress.org themes guidelines prohibit from redirecting the user from the themes.php page after activating a theme. - * - * @author Vova Feldman (@svovaf) - * @since 2.4.5 - * - * @return bool - */ - function show_opt_in_on_themes_page() { - if ( ! $this->is_free_wp_org_theme() ) { - return false; - } - - if ( ! $this->has_settings_menu() ) { - return true; - } - - return $this->show_settings_with_tabs(); - } - - /** - * If `true` the opt-in should be shown on the product's main setting page. - * - * @author Vova Feldman (@svovaf) - * @since 2.4.5 - * - * @return bool - * - * @uses show_opt_in_on_themes_page(); - */ - function show_opt_in_on_setting_page() { - return ! $this->show_opt_in_on_themes_page(); - } - - /** - * If `true` the settings should be shown using tabs. - * - * @author Vova Feldman (@svovaf) - * @since 2.4.5 - * - * @return bool - */ - function show_settings_with_tabs() { - return ( self::NAVIGATION_TABS === $this->_navigation ); - } - - /** - * Check if the context module is free wp.org theme. - * - * This method is helpful because: - * 1. wp.org themes are limited to a single submenu item, - * and sub-submenu items are most likely not allowed (never verified). - * 2. wp.org themes are not allowed to redirect the user - * after the theme activation, therefore, the agreed UX - * is showing the opt-in as a modal dialog box after - * activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - function is_free_wp_org_theme() { - return ( - $this->is_theme() && - $this->is_org_repo_compliant() && - ! $this->is_premium() - ); - } - - /** - * Checks whether this a submenu item is visible. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.6 - * @since 1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page. - * - * @param string $slug - * @param bool $is_tabs_visibility_check This is used to decide if the associated tab should be shown or hidden. - * - * @return bool - */ - function is_submenu_item_visible( $slug, $is_tabs_visibility_check = false ) { - if ( $this->is_admin_page( $slug ) ) { - /** - * It is the current context page, so show the submenu item - * so the user will have the right context page, even if it - * was set to hidden. - */ - return true; - } - - if ( ! $this->has_settings_menu() ) { - // No menu settings at all. - return false; - } - - if ( - ! $is_tabs_visibility_check && - $this->is_org_repo_compliant() && - $this->show_settings_with_tabs() - ) { - /** - * wp.org themes are limited to a single submenu item, and - * sub-submenu items are most likely not allowed (never verified). - */ - return false; - } - - return $this->_menu->is_submenu_item_visible( $slug ); - } - - /** - * Check if a Freemius page should be accessible via the UI. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param string $slug - * - * @return bool - */ - function is_page_visible( $slug ) { - if ( $this->is_admin_page( $slug ) ) { - return true; - } - - return $this->_menu->is_submenu_item_visible( $slug, true, true ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - private function _version_updates_handler() { - if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) { - // Freemius version upgrade mode. - $this->_storage->sdk_last_version = $this->_storage->sdk_version; - $this->_storage->sdk_version = $this->version; - - if ( empty( $this->_storage->sdk_last_version ) || - version_compare( $this->_storage->sdk_last_version, $this->version, '<' ) - ) { - $this->_storage->sdk_upgrade_mode = true; - $this->_storage->sdk_downgrade_mode = false; - } else { - $this->_storage->sdk_downgrade_mode = true; - $this->_storage->sdk_upgrade_mode = false; - - } - - $this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version ); - } - - $plugin_version = $this->get_plugin_version(); - if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) { - // Plugin version upgrade mode. - $this->_storage->plugin_last_version = $this->_storage->plugin_version; - $this->_storage->plugin_version = $plugin_version; - - if ( empty( $this->_storage->plugin_last_version ) || - version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' ) - ) { - $this->_storage->plugin_upgrade_mode = true; - $this->_storage->plugin_downgrade_mode = false; - } else { - $this->_storage->plugin_downgrade_mode = true; - $this->_storage->plugin_upgrade_mode = false; - } - - if ( ! empty( $this->_storage->plugin_last_version ) ) { - // Different version of the plugin was installed before, therefore it's an update. - $this->_storage->is_plugin_new_install = false; - } - - $this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version ); - } - } - - #-------------------------------------------------------------------------------- - #region Data Migration on SDK Update - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.5 - * - * @param string $sdk_prev_version - * @param string $sdk_version - */ - function _sdk_version_update( $sdk_prev_version, $sdk_version ) { - /** - * @since 1.1.7.3 Fixed unwanted connectivity test cleanup. - */ - if ( empty( $sdk_prev_version ) ) { - return; - } - - if ( version_compare( $sdk_prev_version, '2.1.0', '<' ) && - version_compare( $sdk_version, '2.1.0', '>=' ) - ) { - $this->_storage->handle_gdpr_admin_notice = true; - } - - if ( version_compare( $sdk_prev_version, '2.0.0', '<' ) && - version_compare( $sdk_version, '2.0.0', '>=' ) - ) { - $this->migrate_to_subscriptions_collection(); - - $this->consolidate_licenses(); - - // Clear trial_plan since it's now loaded from the plans collection when needed. - $this->_storage->remove( 'trial_plan', true, false ); - } - - if ( version_compare( $sdk_prev_version, '1.2.3', '<' ) && - version_compare( $sdk_version, '1.2.3', '>=' ) - ) { - /** - * Starting from version 1.2.3, paths are stored as relative instead of absolute and some of them can be - * invalid. - * - * @author Leo Fajardo (@leorw) - */ - $this->remove_invalid_paths(); - } - - if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) && - version_compare( $sdk_version, '1.1.5', '>=' ) - ) { - // On version 1.1.5 merged connectivity and is_on data. - if ( isset( $this->_storage->connectivity_test ) ) { - if ( ! isset( $this->_storage->is_on ) ) { - unset( $this->_storage->connectivity_test ); - } else { - $connectivity_data = $this->_storage->connectivity_test; - $connectivity_data['is_active'] = $this->_storage->is_on['is_active']; - $connectivity_data['timestamp'] = $this->_storage->is_on['timestamp']; - - // Override. - $this->_storage->connectivity_test = $connectivity_data; - - // Remove previous structure. - unset( $this->_storage->is_on ); - } - - } - } - - if ( - version_compare( $sdk_prev_version, '2.2.1', '<' ) && - version_compare( $sdk_version, '2.2.1', '>=' ) - ) { - /** - * Clear the file cache without storing the previous path since it could be a wrong path. For example, - * in the versions of the SDK lower than 2.2.1, it's possible for the path of an add-on to be the same - * as the parent plugin's when the add-on was auto-installed since the relevant method names were not - * skipped in the logic that determines the right path in the `get_caller_main_file_and_type` method - * (e.g. `try_activate_plugin`). Since it was an auto-installation, the caller was the parent plugin - * and so its path was used. In case the stored path is wrong, clearing the cache will resolve issues - * related to data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - $this->clear_module_main_file_cache( false ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param \FS_Storage $storage - * @param bool|int|null $blog_id - */ - private static function migrate_install_plan_to_plan_id( FS_Storage $storage, $blog_id = null ) { - if ( empty( $storage->sdk_version ) ) { - // New installation of the plugin, no need to upgrade. - return; - } - - if ( ! version_compare( $storage->sdk_version, '2.0.0', '<' ) ) { - // Previous version is >= 2.0.0, so no need to migrate. - return; - } - - // Alias. - $module_type = $storage->get_module_type(); - $module_slug = $storage->get_module_slug(); - - $installs = self::get_all_sites( $module_type, $blog_id ); - $install = isset( $installs[ $module_slug ] ) ? $installs[ $module_slug ] : null; - - if ( ! is_object( $install ) ) { - return; - } - - if ( isset( $install->plan ) && is_object( $install->plan ) ) { - if ( isset( $install->plan->id ) && ! empty( $install->plan->id ) ) { - $install->plan_id = self::_decrypt( $install->plan->id ); - } - - unset( $install->plan ); - - $installs[ $module_slug ] = clone $install; - - self::set_account_option_by_module( - $module_type, - 'sites', - $installs, - true, - $blog_id - ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - private function migrate_to_subscriptions_collection() { - if ( ! is_object( $this->_site ) ) { - return; - } - - if ( isset( $this->_storage->subscription ) && is_object( $this->_storage->subscription ) ) { - $this->_storage->subscriptions = array( fs_get_entity( $this->_storage->subscription, FS_Subscription::get_class_name() ) ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - private function consolidate_licenses() { - $plugin_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_PLUGIN ); - if ( isset( $plugin_licenses[ $this->_slug ] ) ) { - $plugin_licenses = $plugin_licenses[ $this->_slug ]; - } else { - $plugin_licenses = array(); - } - - $theme_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_THEME ); - if ( isset( $theme_licenses[ $this->_slug ] ) ) { - $theme_licenses = $theme_licenses[ $this->_slug ]; - } else { - $theme_licenses = array(); - } - - if ( empty( $plugin_licenses ) && empty( $theme_licenses ) ) { - return; - } - - $all_licenses = array(); - $user_id_license_ids_map = array(); - - foreach ( $plugin_licenses as $user_id => $user_licenses ) { - if ( is_array( $user_licenses ) ) { - if ( ! isset( $user_license_ids[ $user_id ] ) ) { - $user_id_license_ids_map[ $user_id ] = array(); - } - - foreach ( $user_licenses as $user_license ) { - $all_licenses[] = $user_license; - $user_id_license_ids_map[ $user_id ][] = $user_license->id; - } - } - } - - foreach ( $theme_licenses as $user_id => $user_licenses ) { - if ( is_array( $user_licenses ) ) { - if ( ! isset( $user_license_ids[ $user_id ] ) ) { - $user_id_license_ids_map[ $user_id ] = array(); - } - - foreach ( $user_licenses as $user_license ) { - $all_licenses[] = $user_license; - $user_id_license_ids_map[ $user_id ][] = $user_license->id; - } - } - } - - self::store_user_id_license_ids_map( - $user_id_license_ids_map, - $this->_module_id - ); - - $this->_store_licenses( true, $this->_module_id, $all_licenses ); - } - - /** - * Remove invalid paths. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - private function remove_invalid_paths() { - // Remove invalid path that is still associated with the current slug if there's any. - $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); - foreach ( $file_slug_map as $plugin_basename => $slug ) { - if ( $slug === $this->_slug && - $plugin_basename !== $this->_plugin_basename && - ! file_exists( $this->get_absolute_path( $plugin_basename ) ) - ) { - unset( $file_slug_map[ $plugin_basename ] ); - self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); - - break; - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param string $plugin_prev_version - * @param string $plugin_version - */ - function _after_version_update( $plugin_prev_version, $plugin_version ) { - if ( $this->is_theme() ) { - // Expire the cache of the previous tabs since the theme may - // have setting updates. - $this->_cache->expire( 'tabs' ); - $this->_cache->expire( 'tabs_stylesheets' ); - } - } - - /** - * A special migration logic for the $_accounts, executed for all the plugins in the system: - * - Moves some data to the network level storage. - * - If the plugin's connection was skipped for all sites, set the plugin as if it was network skipped. - * - If the plugin's connection was ignored for all sites, don't do anything in terms of the network connection. - * - If the plugin was connected to all sites by the same super-admin, set the plugin as if was network opted-in for all sites. - * - If there's at least one site that was connected by a super-admin, find the "main super-admin" (the one that installed the majority of the plugin installs) and set the plugin as if was network activated with the main super-admin, set all the sites that were skipped or opted-in with a different user to delegated mode. Then, prompt the currently logged super-admin to choose what to do with the ignored sites. - * - If there are any sites in the network which the connection decision was not yet taken for, set this plugin into network activation mode so a super-admin can choose what to do with the rest of the sites. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private static function migrate_accounts_to_network() { - $sites = self::get_sites(); - $sites_count = count( $sites ); - $connection_status = array(); - $plugin_slugs = array(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - self::$_accounts->migrate_to_network( $blog_id ); - - /** - * Build a list of all Freemius powered plugins slugs. - */ - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array(), $blog_id ); - foreach ( $id_slug_type_path_map as $module_id => $data ) { - if ( WP_FS__MODULE_TYPE_PLUGIN === $data['type'] ) { - $plugin_slugs[ $data['slug'] ] = true; - } - } - - $installs = self::get_account_option( 'sites', WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); - - if ( is_array( $installs ) ) { - foreach ( $installs as $slug => $install ) { - if ( ! isset( $connection_status[ $slug ] ) ) { - $connection_status[ $slug ] = array(); - } - - if ( is_object( $install ) && - FS_Site::is_valid_id( $install->id ) && - FS_User::is_valid_id( $install->user_id ) - ) { - $connection_status[ $slug ][ $blog_id ] = $install->user_id; - } - } - } - } - - foreach ( $plugin_slugs as $slug => $true ) { - if ( ! isset( $connection_status[ $slug ] ) ) { - $connection_status[ $slug ] = array(); - } - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( isset( $connection_status[ $slug ][ $blog_id ] ) ) { - continue; - } - - $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); - - $is_anonymous = $storage->get( 'is_anonymous', null, $blog_id ); - - if ( ! is_null( $is_anonymous ) ) { - // Since 1.1.3 is_anonymous is an array. - if ( is_array( $is_anonymous ) && isset( $is_anonymous['is'] ) ) { - $is_anonymous = $is_anonymous['is']; - } - - if ( is_bool( $is_anonymous ) && true === $is_anonymous ) { - $connection_status[ $slug ][ $blog_id ] = 'skipped'; - } - } - - if ( ! isset( $connection_status[ $slug ][ $blog_id ] ) ) { - $connection_status[ $slug ][ $blog_id ] = 'ignored'; - } - } - } - - $super_admins = array(); - - foreach ( $connection_status as $slug => $blogs_status ) { - $skips = 0; - $ignores = 0; - $connections = 0; - $opted_in_users = array(); - $opted_in_super_admins = array(); - - $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); - - foreach ( $blogs_status as $blog_id => $status_or_user_id ) { - if ( 'skipped' === $status_or_user_id ) { - $skips ++; - } else if ( 'ignored' === $status_or_user_id ) { - $ignores ++; - } else if ( FS_User::is_valid_id( $status_or_user_id ) ) { - $connections ++; - - if ( ! isset( $opted_in_users[ $status_or_user_id ] ) ) { - $opted_in_users[ $status_or_user_id ] = array(); - } - - $opted_in_users[ $status_or_user_id ][] = $blog_id; - - if ( isset( $super_admins[ $status_or_user_id ] ) || - self::is_super_admin( $status_or_user_id ) - ) { - // Cache super-admin data. - $super_admins[ $status_or_user_id ] = true; - - // Remember opted-in super-admins for the plugin. - $opted_in_super_admins[ $status_or_user_id ] = true; - } - } - } - - $main_super_admin_user_id = null; - $all_migrated = false; - if ( $sites_count == $skips ) { - // All sites were skipped -> network skip by copying the anonymous mode from any of the sites. - $storage->is_anonymous_ms = $storage->is_anonymous; - - $all_migrated = true; - } else if ( $sites_count == $ignores ) { - // Don't do anything, still in activation mode. - - $all_migrated = true; - } else if ( 0 < count( $opted_in_super_admins ) ) { - // Find the super-admin with the majority of installs. - $max_installs_by_super_admin = 0; - foreach ( $opted_in_super_admins as $user_id => $true ) { - $installs_count = count( $opted_in_users[ $user_id ] ); - - if ( $installs_count > $max_installs_by_super_admin ) { - $max_installs_by_super_admin = $installs_count; - $main_super_admin_user_id = $user_id; - } - } - - if ( $sites_count == $connections && 1 == count( $opted_in_super_admins ) ) { - // Super-admin opted-in for all sites in the network. - $storage->is_network_connected = true; - - $all_migrated = true; - } - - // Store network user. - $storage->network_user_id = $main_super_admin_user_id; - - $storage->network_install_blog_id = ( $sites_count == $connections ) ? - // Since all sites are opted-in, associating with the main site. - get_current_blog_id() : - // Associating with the 1st found opted-in site. - $opted_in_users[ $main_super_admin_user_id ][0]; - - /** - * Make sure we migrate the plan ID of the network install, otherwise, if after the migration - * the 1st page that will be loaded is the network level WP Admin and $storage->network_install_blog_id - * is different than the main site of the network, the $this->_site will not be set since the plan_id - * will be empty. - */ - $storage->migrate_to_network(); - self::migrate_install_plan_to_plan_id( $storage, $storage->network_install_blog_id ); - } else { - // At least one opt-in. All the opt-in were created by a non-super-admin. - if ( 0 == $ignores ) { - // All sites were opted-in or skipped, all by non-super-admin. So delegate all. - $storage->store( 'is_delegated_connection', true, true ); - - $all_migrated = true; - } - } - - if ( ! $all_migrated ) { - /** - * Delegate all sites that were: - * 1) Opted-in by a user that is NOT the main-super-admin. - * 2) Skipped and non of the sites was opted-in by a super-admin. If any site was opted-in by a super-admin, there will be a main-super-admin, and we consider the skip as if it was done by that user. - */ - foreach ( $blogs_status as $blog_id => $status_or_user_id ) { - if ( $status_or_user_id == $main_super_admin_user_id ) { - continue; - } - - if ( FS_User::is_valid_id( $status_or_user_id ) || - ( 'skipped' === $status_or_user_id && is_null( $main_super_admin_user_id ) ) - ) { - $storage->store( 'is_delegated_connection', true, $blog_id ); - } - } - } - - - if ( ( $connections + $skips > 0 ) ) { - if ( $ignores > 0 ) { - /** - * If admin already opted-in or skipped in any of the network sites, and also - * have sites which the connection decision was not yet taken, set this plugin - * into network activation mode so the super-admin can choose what to do with - * the rest of the sites. - */ - self::set_network_upgrade_mode( $storage ); - } - } - } - } - - /** - * Set a module into network upgrade mode. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_Storage $storage - * - * @return bool - */ - private static function set_network_upgrade_mode( FS_Storage $storage ) { - return $storage->is_network_activation = true; - } - - /** - * Will return true after upgrading to the SDK with the network level integration, - * when the super-admin involvement is required regarding the rest of the sites. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function is_network_upgrade_mode() { - return $this->_storage->get( 'is_network_activation' ); - } - - /** - * Clear flag after the upgrade mode completion. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool True if network activation was on and now completed. - */ - private function network_upgrade_mode_completed() { - if ( fs_is_network_admin() && $this->is_network_upgrade_mode() ) { - $this->_storage->remove( 'is_network_activation' ); - - return true; - } - - return false; - } - - #endregion - - /** - * This action is connected to the 'plugins_loaded' hook and helps to determine - * if this is a new plugin installation or a plugin update. - * - * There are 3 different use-cases: - * 1) New plugin installation right with Freemius: - * 1.1 _activate_plugin_event_hook() will be executed first - * 1.2 Since $this->_storage->is_plugin_new_install is not set, - * and $this->_storage->plugin_last_version is not set, - * $this->_storage->is_plugin_new_install will be set to TRUE. - * 1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will - * be already set to TRUE. - * - * 2) Plugin update, didn't have Freemius before, and now have the SDK: - * 2.1 _activate_plugin_event_hook() will not be executed, because - * the activation hook do NOT fires on updates since WP 3.1. - * 2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will - * be empty, therefore, it will be set to FALSE. - * - * 3) Plugin update, had Freemius in prev version as well: - * 3.1 _version_updates_handler() will be executed 1st, since FS was installed - * before, $this->_storage->plugin_last_version will NOT be empty, - * therefore, $this->_storage->is_plugin_new_install will be set to FALSE. - * 3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is - * already set, therefore, it will not be modified. - * - * Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9. - * - * NOTE: - * The only fallback of this mechanism is if an admin updates a plugin based on use-case #2, - * and then, the next immediate PageView is the plugin's main settings page, it will not - * show the opt-in right away. The reason it will happen is because Freemius execution - * will be turned off till the plugin is fully loaded at least once - * (till $this->_storage->was_plugin_loaded is TRUE). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - */ - function _plugins_loaded() { - // Update flag that plugin was loaded with Freemius at least once. - $this->_storage->was_plugin_loaded = true; - - /** - * Bug fix - only set to false when it's a plugin, due to the - * execution sequence of the theme hooks and our methods, if - * this will be set for themes, Freemius will always assume - * it's a theme update. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.2 - */ - if ( $this->is_plugin() && - ! isset( $this->_storage->is_plugin_new_install ) - ) { - $this->_storage->is_plugin_new_install = ( - ! is_plugin_active( $this->_plugin_basename ) && - empty( $this->_storage->plugin_last_version ) - ); - } - } - - /** - * Add special parameter to WP admin AJAX calls so when we - * process AJAX calls we can identify its source properly. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - static function _enrich_ajax_url() { - $admin_param = is_network_admin() ? - '_fs_network_admin' : - '_fs_blog_admin'; - ?> - - - - _logger->entrance(); - - if ( is_admin() ) { - add_action( 'admin_init', array( &$this, '_hook_action_links_and_register_account_hooks' ) ); - - if ( $this->is_plugin() ) { - if ( self::is_plugin_install_page() && true !== fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) { - /** - * Unless the `fs_allow_updater_and_dialog` URL param exists and its value is `true`, make - * Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) - * so that they won't interfere with the .org plugins' functionalities on that page (e.g. - * updating of a .org plugin). - */ - add_filter( 'site_transient_update_plugins', array( 'Freemius', '_remove_fs_updates_from_plugin_install_page' ), 10, 2 ); - } else if ( self::is_plugins_page() || self::is_updates_page() ) { - /** - * On the "Plugins" and "Updates" admin pages, if there are premium or non–org-compliant plugins, modify their details dialog URLs (add a Freemius-specific param) so that the SDK can determine if the plugin information dialog should show information from Freemius. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - add_action( 'admin_footer', array( 'Freemius', '_prepend_fs_allow_updater_and_dialog_flag_url_param' ) ); - } - - $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; - - /** - * @since 1.2.2 - * - * Hook to both free and premium version activations to support - * auto deactivation on the other version activation. - */ - register_activation_hook( - $plugin_dir . $this->_free_plugin_basename, - array( &$this, '_activate_plugin_event_hook' ) - ); - - register_activation_hook( - $plugin_dir . $this->premium_plugin_basename(), - array( &$this, '_activate_plugin_event_hook' ) - ); - } else { - add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 ); - - add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) ); - } - - /** - * Part of the mechanism to identify new plugin install vs. plugin update. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - */ - if ( empty( $this->_storage->was_plugin_loaded ) ) { - /** - * During the plugin activation (not theme), 'plugins_loaded' will be already executed - * when the logic gets here since the activation logic first add the activate plugins, - * then triggers 'plugins_loaded', and only then include the code of the plugin that - * is activated. Which means that _plugins_loaded() will NOT be executed during the - * plugin activation, and that IS intentional. - * - * @author Vova Feldman (@svovaf) - */ - if ( $this->is_plugin() && - $this->is_activation_mode( false ) && - 0 == did_action( 'plugins_loaded' ) - ) { - add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) ); - } else { - // If was activated before, then it was already loaded before. - $this->_plugins_loaded(); - } - } - - if ( ! self::is_ajax() ) { - if ( ! $this->is_addon() ) { - add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY ); - } - } - - if ( $this->_storage->handle_gdpr_admin_notice ) { - add_action( 'init', array( &$this, '_maybe_show_gdpr_admin_notice' ) ); - } - - add_action( 'init', array( &$this, '_maybe_add_gdpr_optin_ajax_handler') ); - add_action( 'init', array( &$this, '_maybe_add_pricing_ajax_handler' ) ); - } - - if ( $this->is_plugin() ) { - if ( $this->_is_network_active ) { - add_action( 'wpmu_new_blog', array( $this, '_after_new_blog_callback' ), 10, 6 ); - } - - register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) ); - } - - if ( is_multisite() ) { - add_action( 'deactivate_blog', array( &$this, '_after_site_deactivated_callback' ) ); - add_action( 'archive_blog', array( &$this, '_after_site_deactivated_callback' ) ); - add_action( 'make_spam_blog', array( &$this, '_after_site_deactivated_callback' ) ); - add_action( 'deleted_blog', array( &$this, '_after_site_deleted_callback' ), 10, 2 ); - - add_action( 'activate_blog', array( &$this, '_after_site_reactivated_callback' ) ); - add_action( 'unarchive_blog', array( &$this, '_after_site_reactivated_callback' ) ); - add_action( 'make_ham_blog', array( &$this, '_after_site_reactivated_callback' ) ); - } - - if ( $this->is_theme() && - self::is_customizer() && - $this->apply_filters( 'show_customizer_upsell', true ) - ) { - // Register customizer upsell. - add_action( 'customize_register', array( &$this, '_customizer_register' ) ); - } - - add_action( 'admin_init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY ); - - if ( $this->is_theme() && ! $this->is_migration() ) { - add_action( 'admin_init', array( &$this, '_add_tracking_links' ) ); - } - - add_action( 'admin_init', array( &$this, '_add_license_activation' ) ); - add_action( 'admin_init', array( &$this, '_add_premium_version_upgrade_selection' ) ); - add_action( 'admin_init', array( &$this, '_add_beta_mode_update_handler' ) ); - add_action( 'admin_init', array( &$this, '_add_user_change_option' ) ); - - $this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) ); - $this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) ); - $this->add_ajax_action( 'set_data_debug_mode', array( &$this, '_set_data_debug_mode' ) ); - $this->add_ajax_action( 'toggle_whitelabel_mode', array( &$this, '_toggle_whitelabel_mode_ajax_handler' ) ); - - if ( $this->_is_network_active && fs_is_network_admin() ) { - $this->add_ajax_action( 'network_activate', array( &$this, '_network_activate_ajax_action' ) ); - } - - $this->add_ajax_action( 'install_premium_version', array( - &$this, - '_install_premium_version_ajax_action' - ) ); - - $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) ); - - $this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) ); - - $this->add_action( 'sdk_version_update', array( &$this, '_sdk_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 ); - - $this->add_action( - 'plugin_version_update', - array( &$this, '_after_version_update' ), - WP_FS__DEFAULT_PRIORITY, - 2 - ); - $this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) ); - - add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); - add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); - add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); - - /** - * Handle request to reset anonymous mode for `get_reconnect_url()`. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - */ - if ( fs_request_is_action( 'reset_anonymous_mode' ) && - $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) - ) { - add_action( 'admin_init', array( &$this, 'connect_again' ) ); - } - } - - /** - * Register the required hooks right after the settings parse is completed. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - */ - private function register_after_settings_parse_hooks() { - if ( is_admin() && - $this->is_theme() && - $this->is_premium() && - ! $this->has_active_valid_license() - ) { - $this->add_ajax_action( - 'delete_theme_update_data', - array( &$this, '_delete_theme_update_data_action' ) - ); - } - - if ( $this->show_settings_with_tabs() ) { - /** - * Include the required hooks to capture the theme settings' page tabs - * and cache them. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - if ( ! $this->_cache->has_valid( 'tabs' ) ) { - add_action( 'admin_footer', array( &$this, '_tabs_capture' ) ); - // Add license activation AJAX callback. - $this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) ); - - add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 ); - } - - add_action( - 'admin_footer', - array( &$this, '_add_freemius_tabs' ), - /** - * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()). - * That's why the priority is 11 while the tabs capture logic is added - * with priority 10. - * - * @author Vova Feldman (@svovaf) - */ - 11 - ); - } - - if ( ! self::is_ajax() ) { - if ( ! $this->is_addon() || $this->is_only_premium() ) { - add_action( - ( $this->_is_network_active && fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', - array( &$this, '_prepare_admin_menu' ), - WP_FS__LOWEST_PRIORITY - ); - } - } - } - - /** - * Makes Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) so that - * they won't interfere with the .org plugins' functionalities on that page (e.g. updating of a .org plugin). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - * - * @param object $updates - * @param string|null $transient - * - * @return object - */ - static function _remove_fs_updates_from_plugin_install_page( $updates, $transient = null ) { - if ( is_object( $updates ) && isset( $updates->response ) ) { - foreach ( $updates->response as $file => $plugin ) { - if ( isset( $plugin->package ) && false !== strpos( $plugin->package, 'api.freemius' ) ) { - unset( $updates->response[ $file ] ); - } - } - } - - return $updates; - } - - /** - * Prepends the `fs_allow_updater_and_dialog` param to the plugin information URLs to tell the SDK to handle - * the information that is shown on the plugin details dialog that is shown when the relevant link is clicked. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - * - * @return string - */ - static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { - $slug_basename_map = array(); - foreach ( self::$_instances as $instance ) { - if ( ! $instance->is_plugin() ) { - continue; - } - - $slug_basename_map[ $instance->get_slug() ] = $instance->premium_plugin_basename(); - } - ?> - - is_beta() ) { - $has_any_beta_version = true; - break; - } - } - - if ( $has_any_beta_version ) { - fs_enqueue_local_style( 'fs_plugins', '/admin/plugins.css' ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - static function _maybe_add_beta_label_to_plugins_and_handle_confirmation() { - $beta_data = array(); - - foreach ( self::$_instances as $instance ) { - if ( ! $instance->is_premium() ) { - continue; - } - - /** - * If there's an available beta version update, a confirmation message will be shown when the - * "Update now" link on the "Plugins" or "Themes" page is clicked. - */ - $has_beta_update = $instance->has_beta_update(); - - $is_beta = ( - // The "Beta" label is added separately for themes. - $instance->is_plugin() && - $instance->is_beta() - ); - - if ( ! $is_beta && ! $has_beta_update ) { - continue; - } - - $beta_data[ $instance->get_plugin_basename() ] = array( 'is_installed_version_beta' => $is_beta ); - - if ( ! $has_beta_update ) { - continue; - } - - $beta_data[ $instance->get_plugin_basename() ]['beta_version_update_confirmation_message'] = sprintf( - '%s %s', - sprintf( - fs_esc_attr_inline( - 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', - 'beta-version-update-caution', - $instance->get_slug() - ), - $instance->get_plugin_title() - ), - fs_esc_attr_inline( 'Would you like to proceed with the update?', 'update-confirmation', $instance->get_slug() ) - ); - } - - if ( empty( $beta_data ) ) { - return; - } - ?> - - _free_plugin_basename ] ); - unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] ); - - update_option( 'uninstall_plugins', $uninstallable_plugins ); - } - - /** - * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates. - * - * @param bool $store_prev_path - */ - private function clear_module_main_file_cache( $store_prev_path = true ) { - if ( ! isset( $this->_storage->plugin_main_file ) || - empty( $this->_storage->plugin_main_file->path ) - ) { - return; - } - - if ( ! $store_prev_path ) { - /** - * Storing the previous path is not needed when clearing the cache after an SDK version update since - * the main purpose of the cache clearing in that event is to correct a wrong plugin main file path - * which causes data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - unset( $this->_storage->plugin_main_file->path ); - } else { - $plugin_main_file = clone $this->_storage->plugin_main_file; - - // Store cached path (2nd layer cache). - $plugin_main_file->prev_path = $plugin_main_file->path; - - // Clear cached path. - unset( $plugin_main_file->path ); - - $this->_storage->plugin_main_file = $plugin_main_file; - } - - /** - * Clear global cached path. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' ); - unset( $id_slug_type_path_map[ $this->_module_id ]['path'] ); - self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - function _hook_action_links_and_register_account_hooks() { - if ( $this->is_migration() ) { - return; - } - - $this->_add_tracking_links(); - - if ( self::is_plugins_page() && $this->is_plugin() ) { - $this->hook_plugin_action_links(); - } - - $this->_register_account_hooks(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - private function _register_account_hooks() { - if ( ! is_admin() ) { - return; - } - - /** - * Always show the deactivation feedback form since we added - * automatic free version deactivation upon premium code activation. - * - * @since 1.2.1.6 - */ - $this->add_ajax_action( - 'submit_uninstall_reason', - array( &$this, '_submit_uninstall_reason_action' ) - ); - - $this->add_ajax_action( - 'cancel_subscription_or_trial', - array( &$this, 'cancel_subscription_or_trial_ajax_action' ) - ); - - if ( ! $this->is_addon() || $this->is_parent_plugin_installed() ) { - if ( ( $this->is_plugin() && self::is_plugins_page() ) || - ( $this->is_theme() && self::is_themes_page() ) - ) { - add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) ); - } - } - } - - /** - * Leverage backtrace to find caller plugin file path. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool $is_init Is initiation sequence. - * - * @return string - */ - private function _find_caller_plugin_file( $is_init = false ) { - // Try to load the cached value of the file path. - if ( isset( $this->_storage->plugin_main_file ) ) { - $plugin_main_file = $this->_storage->plugin_main_file; - if ( ! empty( $plugin_main_file->path ) ) { - $absolute_path = $this->get_absolute_path( $plugin_main_file->path ); - if ( file_exists( $absolute_path ) ) { - return $absolute_path; - } - } - } - - /** - * @since 1.2.1 - * - * `clear_module_main_file_cache()` is clearing the plugin's cached path on - * deactivation. Therefore, if any plugin/theme was initiating `Freemius` - * with that plugin's slug, it was overriding the empty plugin path with a wrong path. - * - * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path` - * when the class instantiator isn't the module. - */ - if ( ! $is_init ) { - // Fetch prev path cache. - if ( isset( $this->_storage->plugin_main_file ) && - ! empty( $this->_storage->plugin_main_file->prev_path ) - ) { - $absolute_path = $this->get_absolute_path( $this->_storage->plugin_main_file->prev_path ); - if ( file_exists( $absolute_path ) ) { - return $absolute_path; - } - } - - wp_die( - $this->get_text_inline( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', 'failed-finding-main-path' ) . - " Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";", - $this->get_text_inline( 'Error', 'error' ), - array( 'back_link' => true ) - ); - } - - /** - * @since 1.2.1 - * - * Only the original instantiator that calls dynamic_init can modify the module's path. - */ - // Find caller module. - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); - $this->_storage->plugin_main_file = (object) array( - 'path' => $id_slug_type_path_map[ $this->_module_id ]['path'], - ); - - return $this->get_absolute_path( $id_slug_type_path_map[ $this->_module_id ]['path'] ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @param string $path - * - * @return string - */ - private function get_relative_path( $path ) { - $module_root_dir = $this->get_module_root_dir_path(); - if ( 0 === strpos( $path, $module_root_dir ) ) { - $path = substr( $path, strlen( $module_root_dir ) ); - } - - return $path; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @param string $path - * @param string|bool $module_type - * - * @return string - */ - private function get_absolute_path( $path, $module_type = false ) { - $module_root_dir = $this->get_module_root_dir_path( $module_type ); - if ( 0 !== strpos( $path, $module_root_dir ) ) { - $path = fs_normalize_path( $module_root_dir . $path ); - } - - return $path; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @param string|bool $module_type - * - * @return string - */ - private function get_module_root_dir_path( $module_type = false ) { - $is_plugin = empty( $module_type ) ? - $this->is_plugin() : - ( WP_FS__MODULE_TYPE_PLUGIN === $module_type ); - - return fs_normalize_path( trailingslashit( $is_plugin ? - WP_PLUGIN_DIR : - get_theme_root( get_stylesheet() ) ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param number $module_id - * @param string $slug - * - * @since 1.2.2 - */ - private function store_id_slug_type_path_map( $module_id, $slug ) { - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); - - $store_option = false; - - if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) { - $id_slug_type_path_map[ $module_id ] = array( - 'slug' => $slug - ); - - $store_option = true; - } else if ( - isset( $id_slug_type_path_map[ $module_id ]['slug'] ) && - $slug !== $id_slug_type_path_map[ $module_id ]['slug'] - ) { - $id_slug_type_path_map[ $module_id ]['slug'] = $slug; - $store_option = true; - } - - if ( empty( $id_slug_type_path_map[ $module_id ]['path'] ) || - /** - * This verification is for cases when suddenly the same module - * is installed but with a different folder name. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - */ - ! file_exists( $this->get_absolute_path( - $id_slug_type_path_map[ $module_id ]['path'], - $id_slug_type_path_map[ $module_id ]['type'] - ) ) - ) { - $caller_main_file_and_type = $this->get_caller_main_file_and_type(); - - $id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type; - $id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path; - - $store_option = true; - } - - if ( $store_option ) { - self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); - } - } - - /** - * Identifies the caller type: plugin or theme. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when - * add-ons are relying on loading the SDK from the parent module, and also allows themes including the - * SDK an internal file instead of directly from functions.php. - * @since 1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic. - */ - private function get_caller_main_file_and_type() { - self::require_plugin_essentials(); - - $all_plugins = fs_get_plugins( true ); - $all_plugins_paths = array(); - - // Get active plugin's main files real full names (might be symlinks). - foreach ( $all_plugins as $relative_path => $data ) { - if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) { - /** - * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they - * can't really include the SDK. - * - * @author Vova Feldman - * @since 1.2.1.7 - */ - continue; - } - - $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); - } - - $caller_file_candidate = false; - $caller_map = array(); - $module_type = WP_FS__MODULE_TYPE_PLUGIN; - $themes_dir = fs_normalize_path( get_theme_root( get_stylesheet() ) ); - $plugin_dir_to_skip = false; - - for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { - if ( empty( $bt[ $i ]['file'] ) ) { - continue; - } - - if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) { - // If file same as the prev file in the stack, skip it. - continue; - } - - if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array( - 'do_action', - 'apply_filter', - // The string split is stupid, but otherwise, theme check - // throws info notices. - 'requir' . 'e_once', - 'requir' . 'e', - 'includ' . 'e_once', - 'includ' . 'e', - 'install_and_activate_plugin', - 'try_activate_plugin', - 'activate_plugin' - ) ) - ) { - if ( 'activate_plugin' === $bt[ $i ]['function'] ) { - /** - * Store the directory of the activator plugin so that any other file that starts with it - * cannot be mistakenly chosen as a candidate caller file. - * - * @author Leo Fajardo - * - * @since 2.3.0 - */ - $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); - - foreach ( $all_plugins_paths as $plugin_path ) { - $plugin_dir = fs_normalize_path( dirname( $plugin_path ) . '/' ); - if ( false !== strpos( $caller_file_path, $plugin_dir ) ) { - $plugin_dir_to_skip = $plugin_dir; - - break; - } - } - } - - // Ignore call stack hooks and files inclusion. - continue; - } - - $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); - - if ( ! empty( $plugin_dir_to_skip ) ) { - /** - * Skip if it's an activator plugin file to avoid mistakenly choosing it as a candidate caller file. - * - * @author Leo Fajardo - * - * @since 2.3.0 - */ - if ( 0 === strpos( $caller_file_path, $plugin_dir_to_skip ) ) { - continue; - } - } - - if ( 'functions.php' === basename( $caller_file_path ) ) { - /** - * 1. Assumes that theme's starting execution file is functions.php. - * 2. This complex logic fixes symlink issues (e.g. with Vargant). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.5 - */ - - if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) { - $module_type = WP_FS__MODULE_TYPE_THEME; - - /** - * Relative path of the theme, e.g.: - * `my-theme/functions.php` - * - * @author Leo Fajardo (@leorw) - */ - $caller_file_candidate = basename( dirname( $caller_file_path ) ) . - '/' . - basename( $caller_file_path ); - - continue; - } - } - - $caller_file_hash = md5( $caller_file_path ); - - if ( ! isset( $caller_map[ $caller_file_hash ] ) ) { - foreach ( $all_plugins_paths as $plugin_path ) { - if ( empty( $plugin_path ) ) { - continue; - } - - if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) { - $caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path ); - break; - } - } - } - - if ( isset( $caller_map[ $caller_file_hash ] ) ) { - $module_type = WP_FS__MODULE_TYPE_PLUGIN; - $caller_file_candidate = plugin_basename( $caller_map[ $caller_file_hash ] ); - } - } - - return (object) array( - 'module_type' => $module_type, - 'path' => $caller_file_candidate - ); - } - - #---------------------------------------------------------------------------------- - #region Deactivation Feedback Form - #---------------------------------------------------------------------------------- - - /** - * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins - * page. - * - * @author Vova Feldman (@svovaf) - * @author Leo Fajardo (@leorw) - * - * @since 1.1.2 - */ - function _add_deactivation_feedback_dialog_box() { - $subscription_cancellation_dialog_box_template_params = $this->apply_filters( 'show_deactivation_subscription_cancellation', true ) ? - $this->_get_subscription_cancellation_dialog_box_template_params() : - array(); - - /** - * @since 2.3.0 Developers can optionally hide the deactivation feedback form using the 'show_deactivation_feedback_form' filter. - */ - $show_deactivation_feedback_form = true; - if ( $this->has_filter( 'show_deactivation_feedback_form' ) ) { - $show_deactivation_feedback_form = $this->apply_filters( 'show_deactivation_feedback_form', true ); - } else if ( $this->is_addon() ) { - /** - * If the add-on's 'show_deactivation_feedback_form' is not set, try to inherit the value from the parent. - */ - $show_deactivation_feedback_form = $this->get_parent_instance()->apply_filters( 'show_deactivation_feedback_form', true ); - } - - $uninstall_confirmation_message = $this->apply_filters( 'uninstall_confirmation_message', '' ); - - if ( - empty( $subscription_cancellation_dialog_box_template_params ) && - ! $show_deactivation_feedback_form && - empty( $uninstall_confirmation_message ) - ) { - return; - } - - $vars = array( 'id' => $this->_module_id ); - - if ( $show_deactivation_feedback_form ) { - /* Check the type of user: - * 1. Long-term (long-term) - * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term). - * 3. Short-term (short-term) - */ - $is_long_term_user = true; - - // Check if the site is at least 2 days old. - $time_installed = $this->_storage->install_timestamp; - - // Difference in seconds. - $date_diff = time() - $time_installed; - - // Convert seconds to days. - $date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) ); - - if ( $date_diff_days < 2 ) { - $is_long_term_user = false; - } - - $is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user ); - - if ( $is_long_term_user ) { - $user_type = 'long-term'; - } else { - if ( ! $this->is_registered() && ! $this->is_anonymous() ) { - $user_type = 'non-registered-and-non-anonymous-short-term'; - } else { - $user_type = 'short-term'; - } - } - - $uninstall_reasons = $this->_get_uninstall_reasons( $user_type ); - - $vars['reasons'] = $uninstall_reasons; - } - - $vars['subscription_cancellation_dialog_box_template_params'] = &$subscription_cancellation_dialog_box_template_params; - $vars['show_deactivation_feedback_form'] = $show_deactivation_feedback_form; - $vars['uninstall_confirmation_message'] = $uninstall_confirmation_message; - - /** - * Load the HTML template for the deactivation feedback dialog box. - * - * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin. - */ - fs_require_template( 'forms/deactivation/form.php', $vars ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.1.2 - * - * @param string $user_type - * - * @return array The uninstall reasons for the specified user type. - */ - function _get_uninstall_reasons( $user_type = 'long-term' ) { - $module_type = $this->_module_type; - - $internal_message_template_var = array( - 'id' => $this->_module_id - ); - - $plan = $this->get_plan(); - - if ( $this->is_registered() && is_object( $plan ) && $plan->has_technical_support() ) { - $contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var ); - } else { - $contact_support_template = ''; - } - - $reason_found_better_plugin = array( - 'id' => self::REASON_FOUND_A_BETTER_PLUGIN, - 'text' => sprintf( $this->get_text_inline( 'I found a better %s', 'reason-found-a-better-plugin' ), $module_type ), - 'input_type' => 'textfield', - 'input_placeholder' => sprintf( $this->get_text_inline( "What's the %s's name?", 'placeholder-plugin-name' ), $module_type ), - ); - - $reason_temporary_deactivation = array( - 'id' => self::REASON_TEMPORARY_DEACTIVATION, - 'text' => sprintf( - $this->get_text_inline( "It's a temporary %s. I'm just debugging an issue.", 'reason-temporary-x' ), - strtolower( $this->is_plugin() ? - $this->get_text_inline( 'Deactivation', 'deactivation' ) : - $this->get_text_inline( 'Theme Switch', 'theme-switch' ) - ) - ), - 'input_type' => '', - 'input_placeholder' => '' - ); - - $reason_other = array( - 'id' => self::REASON_OTHER, - 'text' => $this->get_text_inline( 'Other', 'reason-other' ), - 'input_type' => 'textfield', - 'input_placeholder' => '' - ); - - $long_term_user_reasons = array( - array( - 'id' => self::REASON_NO_LONGER_NEEDED, - 'text' => sprintf( $this->get_text_inline( 'I no longer need the %s', 'reason-no-longer-needed' ), $module_type ), - 'input_type' => '', - 'input_placeholder' => '' - ), - $reason_found_better_plugin, - array( - 'id' => self::REASON_NEEDED_FOR_A_SHORT_PERIOD, - 'text' => sprintf( $this->get_text_inline( 'I only needed the %s for a short period', 'reason-needed-for-a-short-period' ), $module_type ), - 'input_type' => '', - 'input_placeholder' => '' - ), - array( - 'id' => self::REASON_BROKE_MY_SITE, - 'text' => sprintf( $this->get_text_inline( 'The %s broke my site', 'reason-broke-my-site' ), $module_type ), - 'input_type' => '', - 'input_placeholder' => '', - 'internal_message' => $contact_support_template - ), - array( - 'id' => self::REASON_SUDDENLY_STOPPED_WORKING, - 'text' => sprintf( $this->get_text_inline( 'The %s suddenly stopped working', 'reason-suddenly-stopped-working' ), $module_type ), - 'input_type' => '', - 'input_placeholder' => '', - 'internal_message' => $contact_support_template - ) - ); - - if ( $this->is_paying() ) { - $long_term_user_reasons[] = array( - 'id' => self::REASON_CANT_PAY_ANYMORE, - 'text' => $this->get_text_inline( "I can't pay for it anymore", 'reason-cant-pay-anymore' ), - 'input_type' => 'textfield', - 'input_placeholder' => $this->get_text_inline( 'What price would you feel comfortable paying?', 'placeholder-comfortable-price' ) - ); - } - - $reason_dont_share_info = array( - 'id' => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION, - 'text' => $this->get_text_inline( "I don't like to share my information with you", 'reason-dont-like-to-share-my-information' ), - 'input_type' => '', - 'input_placeholder' => '' - ); - - /** - * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the - * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in - * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is - * enabled and the user's account is currently not in pending activation state (similar to the way the Skip - * button in the opt-in form is shown/hidden). - */ - if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) { - $reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var ); - } - - $uninstall_reasons = array( - 'long-term' => $long_term_user_reasons, - 'non-registered-and-non-anonymous-short-term' => array( - array( - 'id' => self::REASON_DIDNT_WORK, - 'text' => sprintf( $this->get_text_inline( "The %s didn't work", 'reason-didnt-work' ), $module_type ), - 'input_type' => '', - 'input_placeholder' => '' - ), - $reason_dont_share_info, - $reason_found_better_plugin - ), - 'short-term' => array( - array( - 'id' => self::REASON_COULDNT_MAKE_IT_WORK, - 'text' => $this->get_text_inline( "I couldn't understand how to make it work", 'reason-couldnt-make-it-work' ), - 'input_type' => '', - 'input_placeholder' => '', - 'internal_message' => $contact_support_template - ), - $reason_found_better_plugin, - array( - 'id' => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE, - 'text' => sprintf( $this->get_text_inline( "The %s is great, but I need specific feature that you don't support", 'reason-great-but-need-specific-feature' ), $module_type ), - 'input_type' => 'textarea', - 'input_placeholder' => $this->get_text_inline( 'What feature?', 'placeholder-feature' ) - ), - array( - 'id' => self::REASON_NOT_WORKING, - 'text' => sprintf( $this->get_text_inline( 'The %s is not working', 'reason-not-working' ), $module_type ), - 'input_type' => 'textarea', - 'input_placeholder' => $this->get_text_inline( "Kindly share what didn't work so we can fix it for future users...", 'placeholder-share-what-didnt-work' ) - ), - array( - 'id' => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR, - 'text' => $this->get_text_inline( "It's not what I was looking for", 'reason-not-what-i-was-looking-for' ), - 'input_type' => 'textarea', - 'input_placeholder' => $this->get_text_inline( "What you've been looking for?", 'placeholder-what-youve-been-looking-for' ) - ), - array( - 'id' => self::REASON_DIDNT_WORK_AS_EXPECTED, - 'text' => sprintf( $this->get_text_inline( "The %s didn't work as expected", 'reason-didnt-work-as-expected' ), $module_type ), - 'input_type' => 'textarea', - 'input_placeholder' => $this->get_text_inline( 'What did you expect?', 'placeholder-what-did-you-expect' ) - ) - ) - ); - - // Randomize the reasons for the current user type. - shuffle( $uninstall_reasons[ $user_type ] ); - - // Keep the following reasons as the last items in the list. - $uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation; - $uninstall_reasons[ $user_type ][] = $reason_other; - - $uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons ); - - return $uninstall_reasons[ $user_type ]; - } - - /** - * Called after the user has submitted his reason for deactivating the plugin. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.2 - */ - function _submit_uninstall_reason_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'submit_uninstall_reason' ); - - $reason_id = fs_request_get( 'reason_id' ); - - // Check if the given reason ID is an unsigned integer. - if ( ! ctype_digit( $reason_id ) ) { - exit; - } - - $reason_info = trim( fs_request_get( 'reason_info', '' ) ); - if ( ! empty( $reason_info ) ) { - $reason_info = substr( $reason_info, 0, 128 ); - } - - $reason = (object) array( - 'id' => $reason_id, - 'info' => $reason_info, - 'is_anonymous' => fs_request_get_bool( 'is_anonymous' ) - ); - - $this->_storage->store( 'uninstall_reason', $reason ); - - /** - * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do - * not support uninstall hook. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( $this->is_theme() ) { - if ( $this->is_premium() && ! $this->has_active_valid_license() ) { - FS_Plugin_Updater::instance( $this )->delete_update_data(); - } - - $this->_uninstall_plugin_event( false ); - $this->remove_sdk_reference(); - } - - // Print '1' for successful operation. - echo 1; - exit; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.4 - */ - function cancel_subscription_or_trial_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'cancel_subscription_or_trial' ); - - $result = $this->cancel_subscription_or_trial( fs_request_get( 'plugin_id', $this->get_id() ), false ); - - if ( $this->is_api_error( $result ) ) { - $this->shoot_ajax_failure( $result->error->message ); - } - - $this->shoot_ajax_success(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.4 - * - * @param number $plugin_id - * - * @return object - */ - private function cancel_subscription_or_trial( $plugin_id ) { - $fs = null; - if ( $plugin_id == $this->get_id() ) { - $fs = $this; - } else if ( $this->is_addon_activated( $plugin_id ) ) { - $fs = self::get_instance_by_id( $plugin_id ); - } - - $result = null; - - if ( ! is_null( $fs ) ) { - $result = $fs->is_paid_trial() ? - $fs->_cancel_trial() : - $fs->_downgrade_site(); - } - - return $result; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - */ - function _delete_theme_update_data_action() { - FS_Plugin_Updater::instance( $this )->delete_update_data(); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Instance - #---------------------------------------------------------------------------------- - - /** - * Main singleton instance. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * - * @param number $module_id - * @param string|bool $slug - * @param bool $is_init Is initiation sequence. - * - * @return Freemius|false - */ - static function instance( $module_id, $slug = false, $is_init = false ) { - if ( empty( $module_id ) ) { - return false; - } - - /** - * Load the essential static data prior to initiating FS_Plugin_Manager since there's an essential MS network migration logic that needs to be executed prior to the initiation. - */ - self::_load_required_static(); - - if ( ! is_numeric( $module_id ) ) { - if ( ! $is_init && true === $slug ) { - $is_init = true; - } - - $slug = $module_id; - - $module = FS_Plugin_Manager::instance( $slug )->get(); - - if ( is_object( $module ) ) { - $module_id = $module->id; - } - } - - $key = 'm_' . $module_id; - - if ( ! isset( self::$_instances[ $key ] ) ) { - self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init ); - } - - return self::$_instances[ $key ]; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number $addon_id - * - * @return bool - */ - private static function has_instance( $addon_id ) { - return isset( self::$_instances[ 'm_' . $addon_id ] ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @param string|number $id_or_slug - * @param string $module_type - * - * @return number|false - */ - private static function get_module_id( $id_or_slug, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { - if ( is_numeric( $id_or_slug ) ) { - return $id_or_slug; - } - - foreach ( self::$_instances as $instance ) { - // Also check the module type since there can be a plugin and a theme with the same slug. - if ( ( $module_type === $instance->get_module_type() ) && ( $id_or_slug === $instance->get_slug() ) ) { - return $instance->get_id(); - } - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number $id - * - * @return false|Freemius - */ - static function get_instance_by_id( $id ) { - return isset ( self::$_instances[ 'm_' . $id ] ) ? - self::$_instances[ 'm_' . $id ] : - false; - } - - /** - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param string $plugin_file - * @param string $module_type - * - * @return false|Freemius - */ - static function get_instance_by_file( $plugin_file, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { - $slug = self::find_slug_by_basename( $plugin_file ); - - return ( false !== $slug ) ? - self::instance( self::get_module_id( $slug, $module_type ) ) : - false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return false|Freemius - */ - function get_parent_instance() { - return self::get_instance_by_id( $this->_plugin->parent_plugin_id ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string|number $id_or_slug - * - * @return false|Freemius - */ - function get_addon_instance( $id_or_slug ) { - $addon_id = self::get_module_id( $id_or_slug ); - - return self::instance( $addon_id ); - } - - #endregion ------------------------------------------------------------------ - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function is_parent_plugin_installed() { - $is_active = self::has_instance( $this->_plugin->parent_plugin_id ); - - if ( $is_active ) { - return true; - } - - /** - * Parent module might be a theme. If that's the case, the add-on's FS - * instance will be loaded prior to the theme's FS instance, therefore, - * we need to check if it's active with a "look ahead". - * - * @author Vova Feldman - * @since 1.2.2.3 - */ - global $fs_active_plugins; - if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) { - $active_theme = wp_get_theme(); - - foreach ( $fs_active_plugins->plugins as $sdk => $module ) { - if ( WP_FS__MODULE_TYPE_THEME === $module->type ) { - if ( $module->plugin_path == $active_theme->get_stylesheet() ) { - // Parent module is a theme and it's currently active. - return true; - } - } - } - } - - return false; - } - - /** - * Check if add-on parent plugin in activation mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - function is_parent_in_activation() { - $parent_fs = $this->get_parent_instance(); - if ( ! is_object( $parent_fs ) ) { - return false; - } - - return ( $parent_fs->is_activation_mode() ); - } - - /** - * Is plugin in activation mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param bool $and_on - * - * @return bool - */ - function is_activation_mode( $and_on = true ) { - return fs_is_network_admin() ? - $this->is_network_activation_mode( $and_on ) : - $this->is_site_activation_mode( $and_on ); - } - - /** - * Is plugin in activation mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param bool $and_on - * - * @return bool - */ - function is_site_activation_mode( $and_on = true ) { - return ( - ( $this->is_on() || ! $and_on ) && - ( - ( $this->is_premium() && true === $this->_storage->require_license_activation ) || - ( - ( ! $this->is_registered() || - ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) && - ( ! $this->is_enable_anonymous() || - ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) ) - ) - ) - ); - } - - /** - * Checks if the SDK in network activation mode. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param bool $and_on - * - * @return bool - */ - private function is_network_activation_mode( $and_on = true ) { - if ( ! $this->_is_network_active ) { - // Not network activated. - return false; - } - - if ( $this->is_network_upgrade_mode() ) { - // Special flag to enforce network activation mode to decide what to do with the sites that are not yet opted-in nor skipped. - return true; - } - - if ( ! $this->is_site_activation_mode( $and_on ) ) { - // Whether the context is single site or the network, if the plugin is no longer in activation mode then it is not in network activation mode as well. - return false; - } - - if ( $this->is_network_delegated_connection() ) { - // Super-admin delegated the connection to the site admins -> not activation mode. - return false; - } - - if ( $this->is_network_anonymous() && true !== $this->_storage->require_license_activation ) { - // Super-admin skipped the connection network wide -> not activation mode. - return false; - } - - if ( $this->is_network_registered() ) { - // Super-admin connected at least one site -> not activation mode. - return false; - } - - return true; - } - - /** - * Check if current page is the opt-in/pending-activation page. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @return bool - */ - function is_activation_page() { - if ( $this->_menu->is_activation_page( $this->show_opt_in_on_themes_page() ) ) { - return true; - } - - if ( ! $this->is_activation_mode() ) { - return false; - } - - // Check if current page is matching the activation page. - return $this->is_matching_url( $this->get_activation_url() ); - } - - /** - * Check if URL path's are matching and that all querystring - * arguments of the $sub_url exist in the $url with the same values. - * - * WARNING: - * 1. This method doesn't check if the sub/domain are matching. - * 2. Ignore case sensitivity. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $sub_url - * @param string $url If argument is not set, check if the sub_url matching the current's page URL. - * - * @return bool - */ - private function is_matching_url( $sub_url, $url = '' ) { - if ( empty( $url ) ) { - $url = $_SERVER['REQUEST_URI']; - } - - $url = strtolower( $url ); - $sub_url = strtolower( $sub_url ); - - if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) { - // Different path - DO NOT OVERRIDE PAGE. - return false; - } - - $url_params = array(); - parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params ); - - $sub_url_params = array(); - parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params ); - - foreach ( $sub_url_params as $key => $val ) { - if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) { - // Not matching query string - DO NOT OVERRIDE PAGE. - return false; - } - } - - return true; - } - - /** - * Get the basenames of all active plugins for specific blog. Including network activated plugins. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return string[] - */ - private static function get_active_plugins_basenames( $blog_id = 0 ) { - if ( is_multisite() && $blog_id > 0 ) { - $active_basenames = get_blog_option( $blog_id, 'active_plugins' ); - } else { - $active_basenames = get_option( 'active_plugins' ); - } - - if ( ! is_array( $active_basenames ) ) { - $active_basenames = array(); - } - - if ( is_multisite() ) { - $network_active_basenames = get_site_option( 'active_sitewide_plugins' ); - - if ( is_array( $network_active_basenames ) && ! empty( $network_active_basenames ) ) { - $active_basenames = array_merge( $active_basenames, array_keys( $network_active_basenames ) ); - } - } - - return $active_basenames; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param int $blog_id - * - * @return array - */ - static function get_active_plugins_directories_map( $blog_id = 0 ) { - $active_basenames = self::get_active_plugins_basenames( $blog_id ); - - $map = array(); - - foreach ( $active_basenames as $active_basename ) { - $active_basename = fs_normalize_path( $active_basename ); - - if ( false === strpos( $active_basename, '/' ) ) { - continue; - } - - $map[ dirname( $active_basename ) ] = true; - } - - return $map; - } - - /** - * Get collection of all active plugins. Including network activated plugins. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param int $blog_id Since 2.0.0 - * - * @return array[string]array - */ - private static function get_active_plugins( $blog_id = 0 ) { - self::require_plugin_essentials(); - - $active_plugin = array(); - $all_plugins = fs_get_plugins(); - $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); - - foreach ( $active_plugins_basenames as $plugin_basename ) { - $active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ]; - } - - return $active_plugin; - } - - /** - * Get collection of all site active plugins for a specified blog. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return array[string]array - */ - private static function get_site_active_plugins( $blog_id = 0 ) { - $active_basenames = ( is_multisite() && $blog_id > 0 ) ? - get_blog_option( $blog_id, 'active_plugins' ) : - get_option( 'active_plugins' ); - - $active = array(); - - if ( ! is_array( $active_basenames ) ) { - return $active; - } - - foreach ( $active_basenames as $basename ) { - $active[ $basename ] = array( - 'is_active' => true, - 'Version' => '1.0', // Dummy version. - 'slug' => self::get_plugin_slug( $basename ), - ); - } - - return $active; - } - - /** - * Get collection of all plugins with their activation status for a specified blog. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @param int $blog_id Since 2.0.0 - * - * @return array Key is the plugin file path and the value is an array of the plugin data. - */ - private static function get_all_plugins( $blog_id = 0 ) { - self::require_plugin_essentials(); - - $all_plugins = fs_get_plugins(); - - $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); - - foreach ( $all_plugins as $basename => &$data ) { - // By default set to inactive (next foreach update the active plugins). - $data['is_active'] = false; - // Enrich with plugin slug. - $data['slug'] = self::get_plugin_slug( $basename ); - } - - // Flag active plugins. - foreach ( $active_plugins_basenames as $basename ) { - if ( isset( $all_plugins[ $basename ] ) ) { - $all_plugins[ $basename ]['is_active'] = true; - } - } - - return $all_plugins; - } - - /** - * Get collection of all plugins and if they are network level activated. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return array Key is the plugin basename and the value is an array of the plugin data. - */ - private static function get_network_plugins() { - self::require_plugin_essentials(); - - $all_plugins = fs_get_plugins(); - - $network_active_basenames = is_multisite() ? - get_site_option( 'active_sitewide_plugins' ) : - array(); - - foreach ( $all_plugins as $basename => &$data ) { - // By default set to inactive (next foreach update the active plugins). - $data['is_active'] = false; - // Enrich with plugin slug. - $data['slug'] = self::get_plugin_slug( $basename ); - } - - // Flag active plugins. - foreach ( $network_active_basenames as $basename ) { - if ( isset( $all_plugins[ $basename ] ) ) { - $all_plugins[ $basename ]['is_active'] = true; - } - } - - return $all_plugins; - } - - /** - * Cached result of get_site_transient( 'update_plugins' ) - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @var object - */ - private static $_plugins_info; - - /** - * Helper function to get specified plugin's slug. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @param $basename - * - * @return string - */ - private static function get_plugin_slug( $basename ) { - if ( ! isset( self::$_plugins_info ) ) { - self::$_plugins_info = get_site_transient( 'update_plugins' ); - } - - $slug = ''; - - if ( is_object( self::$_plugins_info ) ) { - if ( isset( self::$_plugins_info->no_update ) && - isset( self::$_plugins_info->no_update[ $basename ] ) && - ! empty( self::$_plugins_info->no_update[ $basename ]->slug ) - ) { - $slug = self::$_plugins_info->no_update[ $basename ]->slug; - } else if ( isset( self::$_plugins_info->response ) && - isset( self::$_plugins_info->response[ $basename ] ) && - ! empty( self::$_plugins_info->response[ $basename ]->slug ) - ) { - $slug = self::$_plugins_info->response[ $basename ]->slug; - } - } - - if ( empty( $slug ) ) { - // Try to find slug from FS data. - $slug = self::find_slug_by_basename( $basename ); - } - - if ( empty( $slug ) ) { - // Fallback to plugin's folder name. - $slug = dirname( $basename ); - } - - return $slug; - } - - private static $_statics_loaded = false; - - /** - * Load static resources. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - */ - private static function _load_required_static() { - if ( self::$_statics_loaded ) { - return; - } - - self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - self::$_static_logger->entrance(); - - self::$_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); - - if ( is_multisite() ) { - $has_skipped_migration = ( - // 'id_slug_type_path_map' - was never stored on older versions, therefore, not exists on the site level. - null === self::$_accounts->get_option( 'id_slug_type_path_map', null, false ) && - // 'file_slug_map' stored on the site level, so it was running an SDK version before it was integrated with MS-network. - null !== self::$_accounts->get_option( 'file_slug_map', null, false ) - ); - - /** - * If the file_slug_map exists on the site level but doesn't exist on the - * network level storage, it means that we need to process the storage with migration. - * - * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, file_slug_map will be already set in the network level storage. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - if ( - ( $has_skipped_migration && true !== self::$_accounts->get_option( 'ms_migration_complete', false, true ) ) || - ( null === self::$_accounts->get_option( 'file_slug_map', null, true ) && - null !== self::$_accounts->get_option( 'file_slug_map', null, false ) ) - ) { - self::migrate_options_to_network(); - } - } - - self::$_global_admin_notices = FS_Admin_Notices::instance( 'global' ); - - if ( ! WP_FS__DEMO_MODE ) { - add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( - 'Freemius', - '_add_debug_section' - ) ); - } - - add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) ); - - self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) ); - - self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) ); - - self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) ); - - if ( 0 == did_action( 'plugins_loaded' ) ) { - add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 ); - } - - add_action( 'admin_footer', array( 'Freemius', '_enrich_ajax_url' ) ); - add_action( 'admin_footer', array( 'Freemius', '_open_support_forum_in_new_page' ) ); - - if ( self::is_plugins_page() || self::is_themes_page() ) { - add_action( 'admin_print_footer_scripts', array( 'Freemius', '_maybe_add_beta_label_styles' ), 9 ); - - /** - * Specifically use this hook so that the JS event handlers will work properly on the "Themes" - * page. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - add_action( 'admin_footer-' . self::get_current_page(), array( 'Freemius', '_maybe_add_beta_label_to_plugins_and_handle_confirmation') ); - } - - self::$_statics_loaded = true; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @since 2.1.3 - */ - private static function migrate_options_to_network() { - self::migrate_accounts_to_network(); - - // Migrate API options from site level to network level. - $api_network_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true ); - $api_network_options->migrate_to_network(); - - // Migrate API cache to network level storage. - FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME )->migrate_to_network(); - - self::$_accounts->set_option( 'ms_migration_complete', true, true ); - } - - #---------------------------------------------------------------------------------- - #region Localization - #---------------------------------------------------------------------------------- - - /** - * Load framework's text domain. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - */ - static function _load_textdomain() { - if ( ! is_admin() ) { - return; - } - - global $fs_active_plugins; - - // Works both for plugins and themes. - load_plugin_textdomain( - 'freemius', - false, - $fs_active_plugins->newest->sdk_path . '/languages/' - ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Debugging - #---------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - */ - static function _add_debug_section() { - if ( ! is_super_admin() ) { - // Add debug page only for super-admins. - return; - } - - self::$_static_logger->entrance(); - - $title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION ); - - if ( WP_FS__DEV_MODE ) { - // Add top-level debug menu item. - $hook = FS_Admin_Menu_Manager::add_page( - $title, - $title, - 'manage_options', - 'freemius', - array( 'Freemius', '_debug_page_render' ) - ); - } else { - // Add hidden debug page. - $hook = FS_Admin_Menu_Manager::add_subpage( - null, - $title, - $title, - 'manage_options', - 'freemius', - array( 'Freemius', '_debug_page_render' ) - ); - } - - if ( ! empty( $hook ) ) { - add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - static function _toggle_debug_mode() { - if ( ! is_super_admin() ) { - return; - } - - $is_on = fs_request_get( 'is_on', false, 'post' ); - - if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) { - update_option( 'fs_debug_mode', $is_on ); - - // Turn on/off storage logging. - FS_Logger::_set_storage_logging( ( 1 == $is_on ) ); - } - - exit; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - static function _get_debug_log() { - $logs = FS_Logger::load_db_logs( - fs_request_get( 'filters', false, 'post' ), - ! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200, - ! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0 - ); - - self::shoot_ajax_success( $logs ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - static function _get_db_option() { - check_admin_referer( 'fs_get_db_option' ); - - $option_name = fs_request_get( 'option_name' ); - - if ( ! is_super_admin() || - ! fs_starts_with( $option_name, 'fs_' ) - ) { - self::shoot_ajax_failure(); - } - - $value = get_option( $option_name ); - - $result = array( - 'name' => $option_name, - ); - - if ( false !== $value ) { - if ( ! is_string( $value ) ) { - $value = json_encode( $value ); - } - - $result['value'] = $value; - } - - self::shoot_ajax_success( $result ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - static function _set_db_option() { - check_admin_referer( 'fs_set_db_option' ); - - $option_name = fs_request_get( 'option_name' ); - - if ( ! is_super_admin() || - ! fs_starts_with( $option_name, 'fs_' ) - ) { - self::shoot_ajax_failure(); - } - - $option_value = fs_request_get( 'option_value' ); - - if ( ! empty( $option_value ) ) { - update_option( $option_name, $option_value ); - } - - self::shoot_ajax_success(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - */ - static function _debug_page_actions() { - self::_clean_admin_content_section(); - - if ( fs_request_is_action( 'restart_freemius' ) ) { - check_admin_referer( 'restart_freemius' ); - - if ( ! is_multisite() ) { - // Clear accounts data. - self::$_accounts->clear( null, true ); - } else { - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - self::$_accounts->clear( $blog_id, true ); - } - - // Clear network level storage. - self::$_accounts->clear( true, true ); - } - - // Clear SDK reference cache. - delete_option( 'fs_active_plugins' ); - } else if ( fs_request_is_action( 'clear_updates_data' ) ) { - check_admin_referer( 'clear_updates_data' ); - - if ( ! is_multisite() ) { - set_site_transient( 'update_plugins', null ); - set_site_transient( 'update_themes', null ); - } else { - $current_blog_id = get_current_blog_id(); - - $sites = self::get_sites(); - foreach ( $sites as $site ) { - switch_to_blog( self::get_site_blog_id( $site ) ); - - set_site_transient( 'update_plugins', null ); - set_site_transient( 'update_themes', null ); - } - - switch_to_blog( $current_blog_id ); - } - } else if ( fs_request_is_action( 'simulate_trial' ) ) { - check_admin_referer( 'simulate_trial' ); - - $fs = freemius( fs_request_get( 'module_id' ) ); - - // Update SDK install to at least 24 hours before. - $fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC ); - // Unset the trial shown timestamp. - unset( $fs->_storage->trial_promotion_shown ); - } else if ( fs_request_is_action( 'simulate_network_upgrade' ) ) { - check_admin_referer( 'simulate_network_upgrade' ); - - $fs = freemius( fs_request_get( 'module_id' ) ); - - self::set_network_upgrade_mode( $fs->_storage ); - } else if ( fs_request_is_action( 'delete_install' ) ) { - check_admin_referer( 'delete_install' ); - - self::_delete_site_by_slug( - fs_request_get( 'slug' ), - fs_request_get( 'module_type' ), - true, - fs_request_get( 'blog_id', null ) - ); - } else if ( fs_request_is_action( 'delete_user' ) ) { - check_admin_referer( 'delete_user' ); - - self::delete_user( fs_request_get( 'user_id' ) ); - } else if ( fs_request_is_action( 'download_logs' ) ) { - check_admin_referer( 'download_logs' ); - - $download_url = FS_Logger::download_db_logs( - fs_request_get( 'filters', false, 'post' ) - ); - - if ( false === $download_url ) { - wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact support@freemius.com.' ); - } - - fs_redirect( $download_url ); - } else if ( fs_request_is_action( 'migrate_options_to_network' ) ) { - check_admin_referer( 'migrate_options_to_network' ); - - self::migrate_options_to_network(); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - */ - static function _debug_page_render() { - self::$_static_logger->entrance(); - - if ( ! is_multisite() ) { - $all_plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); - $all_themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME ); - } else { - $sites = self::get_sites(); - - $all_plugins_installs = array(); - $all_themes_installs = array(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - $plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); - - foreach ( $plugins_installs as $slug => $install ) { - if ( ! isset( $all_plugins_installs[ $slug ] ) ) { - $all_plugins_installs[ $slug ] = array(); - } - - $install->blog_id = $blog_id; - - $all_plugins_installs[ $slug ][] = $install; - } - - $themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ); - - foreach ( $themes_installs as $slug => $install ) { - if ( ! isset( $all_themes_installs[ $slug ] ) ) { - $all_themes_installs[ $slug ] = array(); - } - - $install->blog_id = $blog_id; - - $all_themes_installs[ $slug ][] = $install; - } - } - } - - $licenses_by_module_type = self::get_all_licenses_by_module_type(); - - $vars = array( - 'plugin_sites' => $all_plugins_installs, - 'theme_sites' => $all_themes_installs, - 'users' => self::get_all_users(), - 'addons' => self::get_all_addons(), - 'account_addons' => self::get_all_account_addons(), - 'plugin_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_PLUGIN ], - 'theme_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_THEME ] - ); - - fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' ); - fs_require_once_template( 'debug.php', $vars ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Connectivity Issues - #---------------------------------------------------------------------------------- - - /** - * Check if Freemius should be turned on for the current plugin install. - * - * Note: - * $this->_is_on is updated in has_api_connectivity() - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_on() { - self::$_static_logger->entrance(); - - if ( isset( $this->_is_on ) ) { - return $this->_is_on; - } - - // If already installed or pending then sure it's on :) - if ( $this->is_registered() || $this->is_pending_activation() ) { - $this->_is_on = true; - - return true; - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param bool $flush_if_no_connectivity - * - * @return bool - */ - private function should_run_connectivity_test( $flush_if_no_connectivity = false ) { - if ( ! isset( $this->_storage->connectivity_test ) ) { - // Connectivity test was never executed, or cache was cleared. - return true; - } - - if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) { - if ( WP_FS__IS_HTTP_REQUEST ) { - if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) { - // Domain changed. - return true; - } - - if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) { - // Server IP changed. - return true; - } - } - } - - if ( $this->_storage->connectivity_test['is_connected'] && - $this->_storage->connectivity_test['is_active'] - ) { - // API connected and Freemius is active - no need to run connectivity check. - return false; - } - - if ( $flush_if_no_connectivity ) { - /** - * If explicitly asked to flush when no connectivity - do it only - * if at least 10 sec passed from the last API connectivity test. - */ - return ( isset( $this->_storage->connectivity_test['timestamp'] ) && - ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 ); - } - - /** - * @since 1.1.7 Don't check for connectivity on plugin downgrade. - */ - $version = $this->get_plugin_version(); - if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) { - // If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test. - return true; - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param int|null $blog_id Since 2.0.0. - * @param bool $is_gdpr_test Since 2.0.2. Perform only the GDPR test. - * - * @return object|false - */ - private function ping( $blog_id = null, $is_gdpr_test = false ) { - if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) { - return false; - } - - $version = $this->get_plugin_version(); - - $is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ); - - return $this->get_api_plugin_scope()->ping( - $this->get_anonymous_id( $blog_id ), - array( - 'is_update' => json_encode( $is_update ), - 'version' => $version, - 'sdk' => $this->version, - 'is_admin' => json_encode( is_admin() ), - 'is_ajax' => json_encode( self::is_ajax() ), - 'is_cron' => json_encode( self::is_cron() ), - 'is_gdpr_test' => $is_gdpr_test, - 'is_http' => json_encode( WP_FS__IS_HTTP_REQUEST ), - ) - ); - } - - /** - * Check if there's any connectivity issue to Freemius API. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool $flush_if_no_connectivity - * - * @return bool - */ - function has_api_connectivity( $flush_if_no_connectivity = false ) { - $this->_logger->entrance(); - - if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) { - return $this->_has_api_connection; - } - - if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY && - isset( $this->_storage->connectivity_test ) && - true === $this->_storage->connectivity_test['is_connected'] - ) { - unset( $this->_storage->connectivity_test ); - } - - if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { - $this->_has_api_connection = $this->_storage->connectivity_test['is_connected']; - /** - * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration. - * - * @since 1.2.1.5 If the user running the premium version then ignore the 'is_active' flag and turn Freemius on to enable license key activation. - */ - $this->_is_on = $this->_storage->connectivity_test['is_active'] || - $this->is_premium() || - ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF ); - - return $this->_has_api_connection; - } - - $pong = $this->ping(); - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( ! $is_connected ) { - // API failure. - $this->_add_connectivity_issue_message( $pong ); - } - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - } - - $this->store_connectivity_info( $pong, $is_connected ); - - return $this->_has_api_connection; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param object $pong - * @param bool $is_connected - */ - private function store_connectivity_info( $pong, $is_connected ) { - $this->_logger->entrance(); - - $version = $this->get_plugin_version(); - - if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { - $is_active = false; - } else { - $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); - } - - $is_active = $this->apply_filters( - 'is_on', - $is_active, - $this->is_plugin_update(), - $version - ); - - $this->_storage->connectivity_test = array( - 'is_connected' => $is_connected, - 'host' => $_SERVER['HTTP_HOST'], - 'server_ip' => WP_FS__REMOTE_ADDR, - 'is_active' => $is_active, - 'timestamp' => WP_FS__SCRIPT_START_TIME, - // Last version with connectivity attempt. - 'version' => $version, - ); - - $this->_has_api_connection = $is_connected; - $this->_is_on = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF ); - } - - /** - * Force turning Freemius on. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8.1 - * - * @return bool TRUE if successfully turned on. - */ - private function turn_on() { - $this->_logger->entrance(); - - if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) { - return false; - } - - $updated_connectivity = $this->_storage->connectivity_test; - $updated_connectivity['is_active'] = true; - $updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME; - $this->_storage->connectivity_test = $updated_connectivity; - - $this->_is_on = true; - - return true; - } - - /** - * Anonymous and unique site identifier (Hash). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.0 - * - * @param null|int $blog_id Since 2.0.0 - * - * @return string - */ - function get_anonymous_id( $blog_id = null ) { - $unique_id = self::$_accounts->get_option( 'unique_id', null, $blog_id ); - - if ( empty( $unique_id ) || ! is_string( $unique_id ) ) { - $key = fs_strip_url_protocol( get_site_url( $blog_id ) ); - - $secure_auth = SECURE_AUTH_KEY; - if ( empty( $secure_auth ) || - false !== strpos( $secure_auth, ' ' ) || - 'put your unique phrase here' === $secure_auth - ) { - // Protect against default auth key. - $secure_auth = md5( microtime() ); - } - - /** - * Base the unique identifier on the WP secure authentication key. Which - * turns the key into a secret anonymous identifier. This will help us - * to avoid duplicate installs generation on the backend upon opt-in. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - */ - $unique_id = md5( $key . $secure_auth ); - - self::$_accounts->set_option( 'unique_id', $unique_id, true, $blog_id ); - } - - $this->_logger->departure( $unique_id ); - - return $unique_id; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @return \WP_User - */ - static function _get_current_wp_user() { - self::require_pluggable_essentials(); - self::wp_cookie_constants(); - - return wp_get_current_user(); - } - - /** - * Define cookie constants which are required by Freemius::_get_current_wp_user() since - * it uses wp_get_current_user() which needs the cookie constants set. When a plugin - * is network activated the cookie constants are only configured after the network - * plugins activation, therefore, if we don't define those constants WP will throw - * PHP warnings/notices. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.1 - */ - private static function wp_cookie_constants() { - if ( defined( 'LOGGED_IN_COOKIE' ) && - ( defined( 'AUTH_COOKIE' ) || defined( 'SECURE_AUTH_COOKIE' ) ) - ) { - return; - } - - /** - * Used to guarantee unique hash cookies - * - * @since 1.5.0 - */ - if ( ! defined( 'COOKIEHASH' ) ) { - $siteurl = get_site_option( 'siteurl' ); - if ( $siteurl ) { - define( 'COOKIEHASH', md5( $siteurl ) ); - } else { - define( 'COOKIEHASH', '' ); - } - } - - if ( ! defined( 'LOGGED_IN_COOKIE' ) ) { - define( 'LOGGED_IN_COOKIE', 'wordpress_logged_in_' . COOKIEHASH ); - } - - /** - * @since 2.5.0 - */ - if ( ! defined( 'AUTH_COOKIE' ) ) { - define( 'AUTH_COOKIE', 'wordpress_' . COOKIEHASH ); - } - - /** - * @since 2.6.0 - */ - if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) { - define( 'SECURE_AUTH_COOKIE', 'wordpress_sec_' . COOKIEHASH ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return int - */ - static function get_current_wp_user_id() { - $wp_user = self::_get_current_wp_user(); - - return $wp_user->ID; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $email - * - * @return bool - */ - static function is_valid_email( $email ) { - if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { - return false; - } - - $parts = explode( '@', $email ); - - if ( 2 !== count( $parts ) || empty( $parts[1] ) ) { - return false; - } - - $blacklist = array( - 'admin.', - 'webmaster.', - 'localhost.', - 'dev.', - 'development.', - 'test.', - 'stage.', - 'staging.', - ); - - // Make sure domain is not one of the blacklisted. - foreach ( $blacklist as $invalid ) { - if ( 0 === strpos( $parts[1], $invalid ) ) { - return false; - } - } - - // Get the UTF encoded domain name. - $domain = idn_to_ascii( $parts[1] ) . '.'; - - return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); - } - - /** - * Generate API connectivity issue message. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param mixed $api_result - * @param bool $is_first_failure - */ - function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) { - if ( ! $this->is_premium() && $this->_enable_anonymous ) { - // Don't add message if it's the free version and can run anonymously. - return; - } - - if ( ! function_exists( 'wp_nonce_url' ) ) { - require_once ABSPATH . 'wp-includes/functions.php'; - } - - $current_user = self::_get_current_wp_user(); -// $admin_email = get_option( 'admin_email' ); - $admin_email = $current_user->user_email; - - // Aliases. - $deactivate_plugin_title = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' ); - $deactivate_plugin_desc = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' ); - $install_previous_title = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' ); - $install_previous_desc = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' ); - $fix_issue_title = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' ); - $fix_issue_desc = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' ); - /* translators: %s: product title (e.g. "Awesome Plugin" requires an access to...) */ - $x_requires_access_to_api = $this->esc_html_inline( '%s requires an access to our API.', 'x-requires-access-to-api' ); - $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' ); - $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' ); - - $message = false; - if ( is_object( $api_result ) && - isset( $api_result->error ) && - isset( $api_result->error->code ) - ) { - switch ( $api_result->error->code ) { - case 'curl_missing': - $missing_methods = ''; - if ( is_array( $api_result->missing_methods ) && - ! empty( $api_result->missing_methods ) - ) { - foreach ( $api_result->missing_methods as $m ) { - if ( 'curl_version' === $m ) { - continue; - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods .= ', '; - } - - $missing_methods .= sprintf( '%s', $m ); - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods = sprintf( - '

    %s %s', - $this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ), - $missing_methods - ); - } - } - - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' . - $missing_methods . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '

    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ), - ' - ' . sprintf( - $this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'cloudflare_ddos_protection': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'squid_cache_block': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s - %s', - $this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ), - sprintf( - $this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - sprintf( - $this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ), - // We use a filter since the plugin might require additional API connectivity. - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '', - $this->_module_type - ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; -// default: -// $message = $this->get_text_inline( 'connectivity-test-fails-message' ); -// break; - } - } - - $message_id = 'failed_connect_api'; - $type = 'error'; - - $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' ); - - if ( false === $message ) { - if ( $is_first_failure ) { - // First attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '

    ' . - '%s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    %s %s
    ', - sprintf( - '%s', - $this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' ) - ), - sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $this->get_text_inline( 'No - just deactivate', 'no-deactivate' ) - ) - ) - ); - - $message_id = 'failed_connect_api_first'; - $type = 'promotion'; - } else { - // Second connectivity attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
    1. %s
    2. %s
    3. %s
    ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - } - } - - $this->_admin_notices->add_sticky( - $message, - $message_id, - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - $type - ); - } - - /** - * Handle user request to resolve connectivity issue. - * This method will send an email to Freemius API technical staff for resolution. - * The email will contain server's info and installed plugins (might be caching issue). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function _email_about_firewall_issue() { - $this->_admin_notices->remove_sticky( 'failed_connect_api' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); - exit; - } - - $current_user = self::_get_current_wp_user(); - $admin_email = $current_user->user_email; - - $error_type = fs_request_get( 'error_type', 'general' ); - - switch ( $error_type ) { - case 'squid': - $title = 'Squid ACL Blocking Issue'; - break; - case 'cloudflare': - $title = 'CloudFlare Blocking Issue'; - break; - default: - $title = 'API Connectivity Issue'; - break; - } - - $custom_email_sections = array(); - - // Add 'API Error' custom email section. - $custom_email_sections['api_error'] = array( - 'title' => 'API Error', - 'rows' => array( - 'ping' => array( - 'API Error', - is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong ) - ), - ) - ); - - // Send email with technical details to resolve API connectivity issues. - $this->send_email( - 'api@freemius.com', // recipient - $title . ' [' . $this->get_plugin_name() . ']', // subject - $custom_email_sections, - array( "Reply-To: $admin_email <$admin_email>" ) // headers - ); - - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ), - '' . $admin_email . '' - ), - 'server_details_sent' - ); - - // Action was taken, tell that API connectivity troubleshooting should be off now. - - echo "1"; - exit; - } - - /** - * Handle connectivity test retry approved by the user. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - */ - function _retry_connectivity_test() { - $this->_admin_notices->remove_sticky( 'failed_connect_api_first' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); - } else { - // Add connectivity issue message after 2nd failed attempt. - $this->_add_connectivity_issue_message( $pong, false ); - - echo "1"; - } - - exit; - } - - static function _add_firewall_issues_javascript() { - $params = array(); - fs_require_once_template( 'firewall-issues-js.php', $params ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Email - #---------------------------------------------------------------------------------- - - /** - * Generates and sends an HTML email with customizable sections. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.2 - * - * @param string $to_address - * @param string $subject - * @param array $sections - * @param array $headers - * - * @return bool Whether the email contents were sent successfully. - */ - private function send_email( - $to_address, - $subject, - $sections = array(), - $headers = array() - ) { - $default_sections = $this->get_email_sections(); - - // Insert new sections or replace the default email sections. - if ( is_array( $sections ) && ! empty( $sections ) ) { - foreach ( $sections as $section_id => $custom_section ) { - if ( ! isset( $default_sections[ $section_id ] ) ) { - // If the section does not exist, add it. - $default_sections[ $section_id ] = $custom_section; - } else { - // If the section already exists, override it. - $current_section = $default_sections[ $section_id ]; - - // Replace the current section's title if a custom section title exists. - if ( isset( $custom_section['title'] ) ) { - $current_section['title'] = $custom_section['title']; - } - - // Insert new rows under the current section or replace the default rows. - if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) { - foreach ( $custom_section['rows'] as $row_id => $row ) { - $current_section['rows'][ $row_id ] = $row; - } - } - - $default_sections[ $section_id ] = $current_section; - } - } - } - - $vars = array( 'sections' => $default_sections ); - $message = fs_get_template( 'email.php', $vars ); - - // Set the type of email to HTML. - $headers[] = 'Content-type: text/html; charset=UTF-8'; - - $header_string = implode( "\r\n", $headers ); - - return wp_mail( - $to_address, - $subject, - $message, - $header_string - ); - } - - /** - * Generates the data for the sections of the email content. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.2 - * - * @return array - */ - private function get_email_sections() { - // Retrieve the current user's information so that we can get the user's email, first name, and last name below. - $current_user = self::_get_current_wp_user(); - - // Retrieve the cURL version information so that we can get the version number below. - $curl_version_information = curl_version(); - - $active_plugin = self::get_active_plugins(); - - // Generate the list of active plugins separated by new line. - $active_plugin_string = ''; - foreach ( $active_plugin as $plugin ) { - $active_plugin_string .= sprintf( - '%s [v%s]
    ', - $plugin['PluginURI'], - $plugin['Name'], - $plugin['Version'] - ); - } - - $server_ip = WP_FS__REMOTE_ADDR; - - // Add PHP info for deeper investigation. - ob_start(); - phpinfo(); - $php_info = ob_get_clean(); - - $api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 ); - - // Generate the default email sections. - $sections = array( - 'sdk' => array( - 'title' => 'SDK', - 'rows' => array( - 'fs_version' => array( 'FS Version', $this->version ), - 'curl_version' => array( 'cURL Version', $curl_version_information['version'] ) - ) - ), - 'plugin' => array( - 'title' => ucfirst( $this->get_module_type() ), - 'rows' => array( - 'name' => array( 'Name', $this->get_plugin_name() ), - 'version' => array( 'Version', $this->get_plugin_version() ) - ) - ), - 'api' => array( - 'title' => 'API Subdomain', - 'rows' => array( - 'dns' => array( - 'DNS_CNAME', - function_exists( 'dns_get_record' ) ? - var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) : - 'dns_get_record() disabled/blocked' - ), - 'ip' => array( - 'IP', - function_exists( 'gethostbyname' ) ? - gethostbyname( $api_domain ) : - 'gethostbyname() disabled/blocked' - ), - ), - ), - 'site' => array( - 'title' => 'Site', - 'rows' => array( - 'unique_id' => array( 'Unique ID', $this->get_anonymous_id() ), - 'address' => array( 'Address', site_url() ), - 'host' => array( - 'HTTP_HOST', - ( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) - ), - 'hosting' => array( - 'Hosting Company' => fs_request_has( 'hosting_company' ) ? - fs_request_get( 'hosting_company' ) : - 'Unknown', - ), - 'server_addr' => array( - 'SERVER_ADDR', - '' . $server_ip . '' - ) - ) - ), - 'user' => array( - 'title' => 'User', - 'rows' => array( - 'email' => array( 'Email', $current_user->user_email ), - 'first' => array( 'First', $current_user->user_firstname ), - 'last' => array( 'Last', $current_user->user_lastname ) - ) - ), - 'plugins' => array( - 'title' => 'Plugins', - 'rows' => array( - 'active_plugins' => array( 'Active Plugins', $active_plugin_string ) - ) - ), - 'php_info' => array( - 'title' => 'PHP Info', - 'rows' => array( - 'info' => array( $php_info ) - ), - ) - ); - - // Allow the sections to be modified by other code. - $sections = $this->apply_filters( 'email_template_sections', $sections ); - - return $sections; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Initialization - #---------------------------------------------------------------------------------- - - /** - * Init plugin's Freemius instance. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param number $id - * @param string $public_key - * @param bool $is_live - * @param bool $is_premium - */ - function init( $id, $public_key, $is_live = true, $is_premium = true ) { - $this->_logger->entrance(); - - $this->dynamic_init( array( - 'id' => $id, - 'public_key' => $public_key, - 'is_live' => $is_live, - 'is_premium' => $is_premium, - ) ); - } - - /** - * Dynamic initiator, originally created to support initiation - * with parent_id for add-ons. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param array $plugin_info - * - * @throws Freemius_Exception - */ - function dynamic_init( array $plugin_info ) { - $this->_logger->entrance(); - - $this->parse_settings( $plugin_info ); - - $this->register_after_settings_parse_hooks(); - - if ( $this->should_stop_execution() ) { - return; - } - - if ( ! $this->is_registered() ) { - if ( $this->is_anonymous() ) { - // If user skipped, no need to test connectivity. - $this->_has_api_connection = true; - $this->_is_on = true; - } else { - if ( ! $this->has_api_connectivity() ) { - if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) || - $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { - if ( ! $this->_enable_anonymous || $this->is_premium() ) { - // If anonymous mode is disabled, add firewall admin-notice message. - add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) ); - - $ajax_action_suffix = $this->_slug . ( $this->is_theme() ? ':theme' : '' ); - add_action( "wp_ajax_fs_resolve_firewall_issues_{$ajax_action_suffix}", array( - &$this, - '_email_about_firewall_issue' - ) ); - - add_action( "wp_ajax_fs_retry_connectivity_test_{$ajax_action_suffix}", array( - &$this, - '_retry_connectivity_test' - ) ); - - /** - * Currently the admin notice manager relies on the module's type and slug. The new AJAX actions manager uses module IDs, hence, consider to replace the if block above with the commented code below after adjusting the admin notices manager to work with module IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - /*$this->add_ajax_action( 'resolve_firewall_issues', array( - &$this, - '_email_about_firewall_issue' - ) ); - - $this->add_ajax_action( 'retry_connectivity_test', array( - &$this, - '_retry_connectivity_test' - ) );*/ - } - } - - return; - } else { - $this->_admin_notices->remove_sticky( array( - 'failed_connect_api_first', - 'failed_connect_api', - ) ); - - if ( $this->_anonymous_mode ) { - // Simulate anonymous mode. - $this->_is_anonymous = true; - } - } - } - } - - /** - * This should be executed even if Freemius is off for the core module, - * otherwise, the add-ons dialogbox won't work properly. This is esepcially - * relevant when the developer decided to turn FS off for existing users. - * - * @author Vova Feldman (@svovaf) - */ - if ( $this->is_user_in_admin() && - 'plugin-information' === fs_request_get( 'tab', false ) && - $this->should_use_freemius_updater_and_dialog() && - ( - ( $this->is_addon() && $this->get_slug() == fs_request_get( 'plugin', false ) ) || - ( $this->has_addons() && $this->get_id() == fs_request_get( 'parent_plugin_id', false ) ) - ) - ) { - require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php'; - - new FS_Plugin_Info_Dialog( $this->is_addon() ? $this->get_parent_instance() : $this ); - } - - // Check if Freemius is on for the current plugin. - // This MUST be executed after all the plugin variables has been loaded. - if ( ! $this->is_registered() && ! $this->is_on() ) { - return; - } - - if ( $this->has_api_connectivity() ) { - if ( self::is_cron() ) { - $this->hook_callback_to_sync_cron(); - } else if ( $this->is_user_in_admin() ) { - /** - * Schedule daily data sync cron if: - * - * 1. User opted-in (for tracking). - * 2. If skipped, but later upgraded (opted-in via upgrade). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - */ - if ( $this->is_registered() ) { - if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) { - $this->schedule_sync_cron(); - } - } - - /** - * Check if requested for manual blocking background sync. - */ - if ( fs_request_has( 'background_sync' ) ) { - $this->run_manual_sync(); - } - } - } - - if ( $this->is_registered() ) { - $this->hook_callback_to_install_sync(); - } - - if ( $this->is_addon() ) { - if ( $this->is_parent_plugin_installed() ) { - // Link to parent FS. - $this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id ); - - // Get parent plugin reference. - $this->_parent_plugin = $this->_parent->get_plugin(); - } - } - - if ( $this->is_user_in_admin() ) { - if ( $this->is_addon() ) { - if ( ! $this->is_parent_plugin_installed() ) { - $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); - - if ( isset( $plugin_info['parent'] ) ) { - $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); - } - - $this->_admin_notices->add( - ( ! empty( $parent_name ) ? - sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) : - sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() ) - ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - - return; - } else { - $is_network_admin = fs_is_network_admin(); - - if ( ! $this->_parent->is_registered() && $this->is_registered() ) { - // If add-on activated and parent not, automatically install parent for the user. - $this->activate_parent_account( $this->_parent ); - } else if ( - $this->_parent->is_registered() && - ! $this->is_registered() && - /** - * If not registered for add-on and the following conditions for the add-on are met, activate add-on account. - * * Network active and in network admin - network activate add-on account. - * * Network active and not in network admin - activate add-on account for the current blog. - * * Not network active and not in network admin - activate add-on account for the current blog. - * - * If not registered for add-on, not network active, and in network admin, do not handle the add-on activation. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - ( $this->is_network_active() || ! $is_network_admin ) - ) { - $premium_license = null; - - if ( - ! $this->has_free_plan() && - $this->is_bundle_license_auto_activation_enabled() && - $this->_parent->is_activated_with_bundle_license() - ) { - /** - * If the add-on has no free plan, try to activate the account only when there's a bundle license. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - */ - $bundle_license = $this->get_active_parent_license( $this->_parent->_get_license()->secret_key, false ); - - if ( - is_object( $bundle_license ) && - ! empty( $bundle_license->products ) && - in_array( $this->get_id(), $bundle_license->products ) - ) { - $premium_license = $bundle_license; - } - } - - if ( $this->has_free_plan() || is_object( $premium_license) ) { - // If parent plugin activated, automatically install add-on for the user. - $this->_activate_addon_account( - $this->_parent, - ( $this->is_network_active() && $is_network_admin ) ? - true : - get_current_blog_id(), - $premium_license - ); - } - } - - // @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic. - if ( $this->is_premium() ) { - // Remove add-on download admin-notice. - $this->_parent->_admin_notices->remove_sticky( array( - 'addon_plan_upgraded_' . $this->_slug, - 'no_addon_license_' . $this->_slug, - ) ); - } - -// $this->deactivate_premium_only_addon_without_license(); - } - } - - add_action( 'admin_init', array( &$this, '_admin_init_action' ) ); - -// if ( $this->is_registered() || -// $this->is_anonymous() || -// $this->is_pending_activation() -// ) { -// $this->_init_admin(); -// } - } - - /** - * Should be called outside `$this->is_user_in_admin()` scope - * because the updater has some logic that needs to be executed - * during AJAX calls. - * - * Currently we need to hook to the `http_request_host_is_external` filter. - * In the future, there might be additional logic added. - * - * @author Vova Feldman - * @since 1.2.1.6 - */ - if ( - $this->should_use_freemius_updater_and_dialog() && - ( - $this->is_premium() || - /** - * If not premium but the premium version is installed, also instantiate the updater so that the - * plugin information dialog of the premium version will have the information from the server. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) ) - ) && - $this->has_release_on_freemius() - ) { - FS_Plugin_Updater::instance( $this ); - } - - $this->do_action( 'initiated' ); - - if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) { - if ( isset( $this->_storage->prev_is_premium ) ) { - $this->apply_filters( - 'after_code_type_change', - // New code type. - $this->_plugin->is_premium - ); - } else { - // Set for code type for the first time. - $this->_storage->prev_is_premium = $this->_plugin->is_premium; - } - } - - if ( ! $this->is_addon() ) { - if ( $this->is_registered() ) { - // Fix for upgrade from versions < 1.0.9. - if ( ! isset( $this->_storage->activation_timestamp ) ) { - $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; - } - - $this->do_action( 'after_init_plugin_registered' ); - } else if ( $this->is_anonymous() ) { - $this->do_action( 'after_init_plugin_anonymous' ); - } else if ( $this->is_pending_activation() ) { - $this->do_action( 'after_init_plugin_pending_activations' ); - } - } else { - if ( $this->is_registered() ) { - $this->do_action( 'after_init_addon_registered' ); - } else if ( $this->is_anonymous() ) { - $this->do_action( 'after_init_addon_anonymous' ); - } else if ( $this->is_pending_activation() ) { - $this->do_action( 'after_init_addon_pending_activations' ); - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - * - * @return bool - */ - private function should_use_freemius_updater_and_dialog() { - return ( - /** - * Allow updater and dialog when the `fs_allow_updater_and_dialog` URL query param exists and has `true` - * value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action` - * URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts - * with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing - * plugin details from .org). - */ - ( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) || - ( - ! self::is_plugin_install_page() && - // Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected. - ( 'install-plugin' !== fs_request_get( 'action' ) ) - ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @since 1.2.1.5 - */ - function _stop_tracking_callback() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'stop_tracking' ); - - $result = $this->stop_tracking( fs_is_network_admin() ); - - if ( true === $result ) { - self::shoot_ajax_success(); - } - - $this->_logger->api_error( $result ); - - self::shoot_ajax_failure( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . - ( $this->is_api_error( $result ) && isset( $result->error ) ? - $result->error->message : - var_export( $result, true ) ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - */ - function _allow_tracking_callback() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'allow_tracking' ); - - $result = $this->allow_tracking( fs_is_network_admin() ); - - if ( true === $result ) { - self::shoot_ajax_success(); - } - - $this->_logger->api_error( $result ); - - self::shoot_ajax_failure( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . - ( $this->is_api_error( $result ) && isset( $result->error ) ? - $result->error->message : - var_export( $result, true ) ) - ); - } - - /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - function stop_site_tracking() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } - - if ( $this->is_tracking_prohibited() ) { - // Already disconnected. - return true; - } - - // Send update to FS. - $result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array( - 'is_disconnected' => true - ) ); - - if ( ! $this->is_api_result_entity( $result ) || - ! isset( $result->is_disconnected ) || - ! $result->is_disconnected - ) { - $this->_logger->api_error( $result ); - - return $result; - } - - $this->_site->is_disconnected = $result->is_disconnected; - $this->_store_site(); - - $this->clear_sync_cron(); - - // Successfully disconnected. - return true; - } - - /** - * Opt-out network from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - function stop_network_tracking() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } - - $install_id_2_blog_id = array(); - $installs_map = $this->get_blog_install_map(); - - $opt_out_all = true; - - $params = array(); - foreach ( $installs_map as $blog_id => $install ) { - if ( $install->is_tracking_prohibited() ) { - // Already opted-out. - continue; - } - - if ( $this->is_site_delegated_connection( $blog_id ) ) { - // Opt-out only from non-delegated installs. - $opt_out_all = false; - continue; - } - - $params[] = array( 'id' => $install->id ); - - $install_id_2_blog_id[ $install->id ] = $blog_id; - } - - if ( empty( $install_id_2_blog_id ) ) { - return true; - } - - $params[] = array( 'is_disconnected' => true ); - - // Send update to FS. - $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - $this->_logger->api_error( $result ); - - return $result; - } - - foreach ( $result->installs as $r_install ) { - $blog_id = $install_id_2_blog_id[ $r_install->id ]; - $install = $installs_map[ $blog_id ]; - $install->is_disconnected = $r_install->is_disconnected; - $this->_store_site( true, $blog_id, $install ); - } - - $this->clear_sync_cron( $opt_out_all ); - - // Successfully disconnected. - return true; - } - - /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @param bool $is_network_action - * - * @return bool|object - */ - function stop_tracking( $is_network_action = false ) { - $this->_logger->entrance(); - - return $is_network_action ? - $this->stop_network_tracking() : - $this->stop_site_tracking(); - } - - /** - * Opt-in back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - function allow_site_tracking() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } - - if ( $this->is_tracking_allowed() ) { - // Tracking already allowed. - return true; - } - - $result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array( - 'is_disconnected' => false - ) ); - - if ( ! $this->is_api_result_entity( $result ) || - ! isset( $result->is_disconnected ) || - $result->is_disconnected - ) { - $this->_logger->api_error( $result ); - - return $result; - } - - $this->_site->is_disconnected = $result->is_disconnected; - $this->_store_site(); - - $this->schedule_sync_cron(); - - // Successfully reconnected. - return true; - } - - /** - * Opt-in network back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - function allow_network_tracking() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } - - $install_id_2_blog_id = array(); - $installs_map = $this->get_blog_install_map(); - - $params = array(); - foreach ( $installs_map as $blog_id => $install ) { - if ( $install->is_tracking_allowed() ) { - continue; - } - - $params[] = array( 'id' => $install->id ); - - $install_id_2_blog_id[ $install->id ] = $blog_id; - } - - if ( empty( $install_id_2_blog_id ) ) { - return true; - } - - $params[] = array( 'is_disconnected' => false ); - - // Send update to FS. - $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); - - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - $this->_logger->api_error( $result ); - - return $result; - } - - foreach ( $result->installs as $r_install ) { - $blog_id = $install_id_2_blog_id[ $r_install->id ]; - $install = $installs_map[ $blog_id ]; - $install->is_disconnected = $r_install->is_disconnected; - $this->_store_site( true, $blog_id, $install ); - } - - $this->schedule_sync_cron(); - - // Successfully reconnected. - return true; - } - - /** - * Opt-in back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @param bool $is_network_action - * - * @return bool|object - */ - function allow_tracking( $is_network_action = false ) { - $this->_logger->entrance(); - - return $is_network_action ? - $this->allow_network_tracking() : - $this->allow_site_tracking(); - } - - /** - * If user opted-in and later disabled usage-tracking, - * re-allow tracking for licensing and updates. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @param bool $is_context_single_site - */ - private function reconnect_locally( $is_context_single_site = false ) { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return; - } - - if ( ! fs_is_network_admin() || $is_context_single_site ) { - if ( $this->is_tracking_prohibited() ) { - $this->_site->is_disconnected = false; - $this->_store_site(); - } - } else { - $installs_map = $this->get_blog_install_map(); - foreach ( $installs_map as $blog_id => $install ) { - /** - * @var FS_Site $install - */ - if ( $install->is_tracking_prohibited() ) { - $install->is_disconnected = false; - $this->_store_site( true, $blog_id, $install ); - } - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 - * - * @return bool - */ - function is_extensions_tracking_allowed() { - return ( true === $this->apply_filters( - 'is_extensions_tracking_allowed', - $this->_storage->get( 'is_extensions_tracking_allowed', null ) - ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 - */ - function _update_tracking_permission_callback() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'update_tracking_permission' ); - - $is_enabled = fs_request_get_bool( 'is_enabled', null ); - - if ( ! is_bool( $is_enabled ) ) { - self::shoot_ajax_failure(); - } - - $permission = fs_request_get( 'permission' ); - - switch ( $permission ) { - case 'extensions': - $this->update_extensions_tracking_flag( $is_enabled ); - break; - default: - $permission = 'no_match'; - } - - if ( 'no_match' === $permission ) { - self::shoot_ajax_failure(); - } - - self::shoot_ajax_success( array( - 'permissions' => array( - $permission => $is_enabled, - ) - ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @param bool|null $is_enabled - */ - function update_extensions_tracking_flag( $is_enabled ) { - if ( is_bool( $is_enabled ) ) { - $this->_storage->store( 'is_extensions_tracking_allowed', $is_enabled ); - } - } - - /** - * Parse plugin's settings (as defined by the plugin dev). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param array $plugin_info - * - * @throws \Freemius_Exception - */ - private function parse_settings( &$plugin_info ) { - $this->_logger->entrance(); - - $id = $this->get_numeric_option( $plugin_info, 'id', false ); - $public_key = $this->get_option( $plugin_info, 'public_key', false ); - $secret_key = $this->get_option( $plugin_info, 'secret_key', null ); - $parent_id = $this->get_numeric_option( $plugin_info, 'parent_id', null ); - $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.9 Try to pull secret key from external config. - */ - if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) { - $secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" ); - } - - if ( isset( $plugin_info['parent'] ) ) { - $parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null ); -// $parent_slug = $this->get_option( $plugin_info['parent'], 'slug', null ); -// $parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null ); -// $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); - } - - if ( false === $id ) { - throw new Freemius_Exception( array( - 'error' => array( - 'type' => 'ParameterNotSet', - 'message' => 'Plugin id parameter is not set.', - 'code' => 'plugin_id_not_set', - 'http' => 500, - ) - ) ); - } - if ( false === $public_key ) { - throw new Freemius_Exception( array( - 'error' => array( - 'type' => 'ParameterNotSet', - 'message' => 'Plugin public_key parameter is not set.', - 'code' => 'plugin_public_key_not_set', - 'http' => 500, - ) - ) ); - } - - $plugin = ( $this->_plugin instanceof FS_Plugin ) ? - $this->_plugin : - new FS_Plugin(); - - $premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' ); - - $plugin->update( array( - 'id' => $id, - 'type' => $this->get_option( $plugin_info, 'type', $this->_module_type ), - 'public_key' => $public_key, - 'slug' => $this->_slug, - 'premium_slug' => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ), - 'parent_plugin_id' => $parent_id, - 'version' => $this->get_plugin_version(), - 'title' => $this->get_plugin_name( $premium_suffix ), - 'file' => $this->_plugin_basename, - 'is_premium' => $this->get_bool_option( $plugin_info, 'is_premium', true ), - 'premium_suffix' => $premium_suffix, - 'is_live' => $this->get_bool_option( $plugin_info, 'is_live', true ), - 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ), - 'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ), - 'bundle_public_key' => $this->get_option( $plugin_info, 'bundle_public_key', null ), - ) ); - - if ( $plugin->is_updated() ) { - // Update plugin details. - $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin ); - } - // Set the secret key after storing the plugin, we don't want to store the key in the storage. - $this->_plugin->secret_key = $secret_key; - - /** - * If the product is network integrated and activated and the current view is in the network level Admin dashboard, if the product's network-level menu located differently from the sub-site level, then use the network menu details (when set). - * - * @author Vova Feldman - * @since 2.4.5 - */ - if ( $this->is_network_active() && fs_is_network_admin() ) { - if ( isset( $plugin_info['menu_network'] ) && - is_array( $plugin_info['menu_network'] ) && - ! empty( $plugin_info['menu_network'] ) - ) { - $plugin_info['menu'] = $plugin_info['menu_network']; - } - } - - if ( ! isset( $plugin_info['menu'] ) ) { - $plugin_info['menu'] = array(); - - if ( ! empty( $this->_storage->sdk_last_version ) && - version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' ) - ) { - // Backward compatibility to 1.1.2 - $plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ? - $plugin_info['menu_slug'] : - $this->_slug; - } - } - - $this->_menu = FS_Admin_Menu_Manager::instance( - $this->_module_id, - $this->_module_type, - $this->get_unique_affix() - ); - - $this->_menu->init( $plugin_info['menu'], $this->is_addon() ); - - $this->_has_addons = $this->get_bool_option( $plugin_info, 'has_addons', false ); - $this->_has_paid_plans = $this->get_bool_option( $plugin_info, 'has_paid_plans', true ); - $this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans ); - $this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false ); - $this->_is_org_compliant = $this->get_bool_option( $plugin_info, 'is_org_compliant', true ); - $this->_is_premium_only = $this->get_bool_option( $plugin_info, 'is_premium_only', false ); - if ( $this->_is_premium_only ) { - // If premium only plugin, disable anonymous mode. - $this->_enable_anonymous = false; - $this->_anonymous_mode = false; - } else { - $this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true ); - $this->_anonymous_mode = $this->get_bool_option( $plugin_info, 'anonymous_mode', false ); - } - $this->_permissions = $this->get_option( $plugin_info, 'permissions', array() ); - $this->_is_bundle_license_auto_activation_enabled = $this->get_option( $plugin_info, 'bundle_license_auto_activation', false ); - - if ( ! empty( $plugin_info['trial'] ) ) { - $this->_trial_days = $this->get_numeric_option( - $plugin_info['trial'], - 'days', - // Default to 0 - trial without days specification. - 0 - ); - - $this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false ); - } - - $this->_navigation = $this->get_option( - $plugin_info, - 'navigation', - $this->is_free_wp_org_theme() ? - self::NAVIGATION_TABS : - self::NAVIGATION_MENU - ); - } - - /** - * @param string[] $options - * @param string $key - * @param mixed $default - * - * @return bool - */ - private function get_option( &$options, $key, $default = false ) { - return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; - } - - private function get_bool_option( &$options, $key, $default = false ) { - return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; - } - - private function get_numeric_option( &$options, $key, $default = false ) { - return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default; - } - - /** - * Gate keeper. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return bool - */ - private function should_stop_execution() { - if ( empty( $this->_storage->was_plugin_loaded ) ) { - /** - * Don't execute Freemius until plugin was fully loaded at least once, - * to give the opportunity for the activation hook to run before pinging - * the API for connectivity test. This logic is relevant for the - * identification of new plugin install vs. plugin update. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - */ - return true; - } - - if ( $this->is_activation_mode() ) { - if ( ! is_admin() ) { - /** - * If in activation mode, don't execute Freemius outside of the - * admin dashboard. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - return true; - } - - if ( ! WP_FS__IS_HTTP_REQUEST ) { - /** - * If in activation and executed without HTTP context (e.g. CLI, Cronjob), - * then don't start Freemius. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6.3 - * - * @link https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli - */ - return true; - } - - if ( self::is_cron() ) { - /** - * If in activation mode, don't execute Freemius during wp crons - * (wp crons have HTTP context - called as HTTP request). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - return true; - } - - if ( self::is_ajax() && - ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) && - ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { - /** - * During activation, if running in AJAX mode, unless there's a sticky - * connectivity issue notice, don't run Freemius. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - return true; - } - } - - return false; - } - - /** - * Triggered after code type has changed. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9.1 - */ - function _after_code_type_change() { - $this->_logger->entrance(); - - if ( $this->is_theme() ) { - // Expire the cache of the previous tabs since the theme may - // have setting updates after code type has changed. - $this->_cache->expire( 'tabs' ); - $this->_cache->expire( 'tabs_stylesheets' ); - } - - if ( $this->is_registered() ) { - if ( ! $this->is_addon() ) { - add_action( - is_admin() ? 'admin_init' : 'init', - array( &$this, '_plugin_code_type_changed' ) - ); - } - - if ( $this->is_premium() ) { - // Purge cached payments after switching to the premium version. - // @todo This logic doesn't handle purging the cache for serviceware module upgrade. - $this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" ); - } - } - } - - /** - * Handles plugin's code type change (free <--> premium). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function _plugin_code_type_changed() { - $this->_logger->entrance(); - - if ( $this->is_premium() ) { - $this->reconnect_locally(); - - // Activated premium code. - $this->do_action( 'after_premium_version_activation' ); - - // Remove all sticky messages related to download of the premium version. - $this->_admin_notices->remove_sticky( array( - 'trial_started', - 'plan_upgraded', - 'plan_changed', - 'license_activated', - ) ); - - $notice = ''; - if ( ! $this->is_only_premium() ) { - $notice = sprintf( $this->get_text_inline( 'Premium %s version was successfully activated.', 'premium-activated-message' ), $this->_module_type ); - } - - $license_notice = $this->get_license_network_activation_notice(); - if ( ! empty( $license_notice ) ) { - $notice .= ' ' . $license_notice; - } - - if ( ! empty( $notice ) ) { - $this->_admin_notices->add_sticky( - trim( $notice ), - 'premium_activated', - $this->get_text_x_inline( 'W00t', - 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' - ); - } - } else { - // Remove sticky message related to premium code activation. - $this->_admin_notices->remove_sticky( 'premium_activated' ); - - // Activated free code (after had the premium before). - $this->do_action( 'after_free_version_reactivation' ); - - if ( $this->is_paying() && ! $this->is_premium() ) { - $this->_admin_notices->add_sticky( - sprintf( - /* translators: %s: License type (e.g. you have a professional license) */ - $this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ), - $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - } - } - - // Schedule code type changes event. - $this->schedule_install_sync(); - - /** - * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid - * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version - * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been - * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since - * the main file of the "free" version will be loaded first before calling the hooked callback. Since the - * free and premium versions are almost identical (same class or have same functions), a fatal error like - * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur. - */ - $this->unregister_uninstall_hook(); - - $this->clear_module_main_file_cache(); - - // Update is_premium of latest version. - $this->_storage->prev_is_premium = $this->_plugin->is_premium; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Add-ons - #---------------------------------------------------------------------------------- - - /** - * Check if add-on installed and activated on site. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string|number $id_or_slug - * @param bool|null $is_premium Since 1.2.1.7 can check for specified add-on version. - * - * @return bool - */ - function is_addon_activated( $id_or_slug, $is_premium = null ) { - $this->_logger->entrance(); - - $addon_id = self::get_module_id( $id_or_slug ); - $is_activated = self::has_instance( $addon_id ); - - if ( ! $is_activated ) { - return false; - } - - if ( is_bool( $is_premium ) ) { - // Check if the specified code version is activate. - $addon = $this->get_addon_instance( $addon_id ); - $is_activated = ( $is_premium === $addon->is_premium() ); - } - - return $is_activated; - } - - /** - * Check if add-on was connected to install - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @param string|number $id_or_slug - * - * @return bool - */ - function is_addon_connected( $id_or_slug ) { - $this->_logger->entrance(); - - $sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); - - $addon_id = self::get_module_id( $id_or_slug ); - $addon = $this->get_addon( $addon_id ); - $slug = $addon->slug; - if ( ! isset( $sites[ $slug ] ) ) { - return false; - } - - $site = $sites[ $slug ]; - - $plugin = FS_Plugin_Manager::instance( $addon_id )->get(); - - if ( $plugin->parent_plugin_id != $this->_plugin->id ) { - // The given slug do NOT belong to any of the plugin's add-ons. - return false; - } - - return ( is_object( $site ) && - is_numeric( $site->id ) && - is_numeric( $site->user_id ) && - FS_Plugin_Plan::is_valid_id( $site->plan_id ) - ); - } - - /** - * Determines if add-on installed. - * - * NOTE: This is a heuristic and only works if the folder/file named as the slug. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string|number $id_or_slug - * - * @return bool - */ - function is_addon_installed( $id_or_slug ) { - $this->_logger->entrance(); - - $addon_id = self::get_module_id( $id_or_slug ); - - return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) ); - } - - /** - * Get add-on basename. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string|number $id_or_slug - * - * @return string - */ - function get_addon_basename( $id_or_slug ) { - $addon_id = self::get_module_id( $id_or_slug ); - - if ( $this->is_addon_activated( $addon_id ) ) { - return self::instance( $addon_id )->get_plugin_basename(); - } - - $addon = $this->get_addon( $addon_id ); - $premium_basename = "{$addon->premium_slug}/{$addon->slug}.php"; - - if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) { - return $premium_basename; - } - - $all_plugins = $this->get_all_plugins(); - - foreach ( $all_plugins as $basename => $data ) { - if ( $addon->slug === $data['slug'] || - $addon->premium_slug === $data['slug'] - ) { - return $basename; - } - } - - $free_basename = "{$addon->slug}/{$addon->slug}.php"; - - return $free_basename; - } - - /** - * Get installed add-ons instances. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return Freemius[] - */ - function get_installed_addons() { - if ( $this->is_addon() ) { - // Add-on cannot have add-ons. - return array(); - } - - $installed_addons = array(); - - foreach ( self::$_instances as $instance ) { - if ( $instance->is_addon_of( $this->_plugin->id ) ) { - $installed_addons[] = $instance; - } - } - - return $installed_addons; - } - - /** - * Check if any add-ons of the plugin are installed. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.1 - * - * @return bool - */ - function has_installed_addons() { - if ( ! $this->has_addons() ) { - return false; - } - - foreach ( self::$_instances as $instance ) { - if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) { - if ( $this->_plugin->id == $instance->_parent_plugin->id ) { - return true; - } - } - } - - return false; - } - - /** - * Tell Freemius that the current plugin is an add-on. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number $parent_plugin_id The parent plugin ID - */ - function init_addon( $parent_plugin_id ) { - $this->_plugin->parent_plugin_id = $parent_plugin_id; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function is_addon() { - return ( - isset( $this->_plugin->parent_plugin_id ) && - is_numeric( $this->_plugin->parent_plugin_id ) - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 - * - * @param number $parent_product_id - * - * @return bool - */ - function is_addon_of( $parent_product_id ) { - return ( - $this->is_addon() && - $parent_product_id == $this->_plugin->parent_plugin_id - ); - } - - /** - * Deactivate add-on if it's premium only and the user does't have a valid license. - * - * @param bool $is_after_trial_cancel - * - * @return bool If add-on was deactivated. - */ - private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) { - if ( ! $this->has_free_plan() && - ! $this->has_features_enabled_license() && - ! $this->_has_premium_license() - ) { - if ( $this->is_registered() ) { - // IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons). - // if (empty($this->_storage->activation_timestamp) || - // (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30 - // ) { - /** - * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation. - * - * Retry syncing the user add-on licenses. - */ - // Sync licenses. - $this->_sync_licenses(); - // } - - // Try to activate premium license. - $this->_activate_license( true ); - } - - if ( ! $this->has_free_plan() && - ! $this->has_features_enabled_license() && - ! $this->_has_premium_license() - ) { - // @todo Check if deactivate plugins also call the deactivation hook. - - $this->_parent->_admin_notices->add_sticky( - sprintf( - ( $is_after_trial_cancel ? - $this->_parent->get_text_inline( - '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.', - 'addon-trial-cancelled-message' - ) : - $this->_parent->get_text_inline( - '%s is a premium only add-on. You have to purchase a license first before activating the plugin.', - 'addon-no-license-message' - ) - ), - '' . $this->_plugin->title . '' - ) . ' ' . sprintf( - '%s  ➜', - $this->_parent->addon_url( $this->_slug ), - esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ), - $this->_parent->get_text_inline( 'Purchase License', 'purchase-license' ) - ), - 'no_addon_license_' . $this->_slug, - ( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ), - ( $is_after_trial_cancel ? 'success' : 'error' ) - ); - - deactivate_plugins( array( $this->_plugin_basename ), true ); - - return true; - } - } - - return false; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Sandbox - #---------------------------------------------------------------------------------- - - /** - * Set Freemius into sandbox mode for debugging. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $secret_key - */ - function init_sandbox( $secret_key ) { - $this->_plugin->secret_key = $secret_key; - - // Update plugin details. - FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true ); - } - - /** - * Check if running payments in sandbox mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return bool - */ - function is_payments_sandbox() { - return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key ); - } - - #endregion - - /** - * Check if running test vs. live plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return bool - */ - function is_live() { - return $this->_plugin->is_live; - } - - /** - * Check if super-admin skipped connection for all sites in the network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function is_network_anonymous() { - if ( ! $this->_is_network_active ) { - return false; - } - - $is_anonymous_ms = $this->_storage->get( 'is_anonymous_ms' ); - - if ( empty( $is_anonymous_ms ) ) { - return false; - } - - return $is_anonymous_ms['is']; - } - - /** - * Check if super-admin opted-in for all sites in the network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function is_network_connected() { - if ( ! $this->_is_network_active ) { - return false; - } - - return $this->_storage->get( 'is_network_connected' ); - } - - /** - * Check if the user skipped connecting the account with Freemius. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - function is_anonymous() { - if ( ! isset( $this->_is_anonymous ) ) { - if ( $this->is_network_anonymous() ) { - $this->_is_anonymous = true; - } else if ( ! fs_is_network_admin() ) { - if ( ! isset( $this->_storage->is_anonymous ) ) { - // Not skipped. - $this->_is_anonymous = false; - } else if ( is_bool( $this->_storage->is_anonymous ) ) { - // For back compatibility, since the variable was boolean before. - $this->_is_anonymous = $this->_storage->is_anonymous; - - // Upgrade stored data format to 1.1.3 format. - $this->set_anonymous_mode( $this->_storage->is_anonymous ); - } else { - // Version 1.1.3 and later. - $this->_is_anonymous = $this->_storage->is_anonymous['is']; - } - } - } - - return $this->_is_anonymous; - } - - /** - * Check if the user skipped the connection of a specified site. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return bool - */ - function is_anonymous_site( $blog_id = 0 ) { - if ( $this->is_network_anonymous() ) { - return true; - } - - $is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id ); - - if ( empty( $is_anonymous ) ) { - return false; - } - - return $is_anonymous['is']; - } - - /** - * Check if user connected his account and install pending email activation. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - function is_pending_activation() { - return $this->_storage->get( 'is_pending_activation', false ); - } - - /** - * Check if plugin must be WordPress.org compliant. - * - * @since 1.0.7 - * - * @return bool - */ - function is_org_repo_compliant() { - return $this->_is_org_compliant; - } - - #-------------------------------------------------------------------------------- - #region WP Cron Common - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * - * @return object - */ - private function get_cron_data( $name ) { - $this->_logger->entrance( $name ); - - /** - * @var object $cron_data - */ - return $this->_storage->get( "{$name}_cron", null ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - */ - private function clear_cron_data( $name ) { - $this->_logger->entrance( $name ); - - $this->_storage->remove( "{$name}_cron" ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param int $cron_blog_id The cron executing blog ID. - */ - private function set_cron_data( $name, $cron_blog_id = 0 ) { - $this->_logger->entrance( $name ); - - $this->_storage->store( "{$name}_cron", (object) array( - 'version' => $this->get_plugin_version(), - 'blog_id' => $cron_blog_id, - 'sdk_version' => $this->version, - 'timestamp' => WP_FS__SCRIPT_START_TIME, - 'on' => true, - ) ); - } - - /** - * Get the cron's executing blog ID. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * - * @return int - */ - private function get_cron_blog_id( $name ) { - $this->_logger->entrance( $name ); - - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - return ( is_object( $cron_data ) && is_numeric( $cron_data->blog_id ) ) ? - $cron_data->blog_id : - 0; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * - * @return bool - */ - private function is_cron_on( $name ) { - $this->_logger->entrance( $name ); - - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - return ( ! is_null( $cron_data ) && true === $cron_data->on ); - } - - /** - * Unix timestamp for previous cron execution or false if never executed. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * - * @return int|false - */ - private function cron_last_execution( $name ) { - $this->_logger->entrance( $name ); - - return $this->_storage->get( "{$name}_timestamp" ); - } - - /** - * Set cron execution time to now. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - */ - private function set_cron_execution_timestamp( $name ) { - $this->_logger->entrance( $name ); - - $this->_storage->store( "{$name}_timestamp", time() ); - } - - /** - * Sets the keepalive time to now. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - * - * @param bool|null $use_network_level_storage - */ - private function set_keepalive_timestamp( $use_network_level_storage = null ) { - $this->_logger->entrance(); - - $this->_storage->store( 'keepalive_timestamp', time(), $use_network_level_storage ); - } - - /** - * Check if cron was executed in the last $period of seconds. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param int $period In seconds - * - * @return bool - */ - private function is_cron_executed( $name, $period = WP_FS__TIME_24_HOURS_IN_SEC ) { - $this->_logger->entrance( $name ); - - $last_execution = $this->cron_last_execution( $name ); - - if ( ! is_numeric( $last_execution ) ) { - return false; - } - - return ( $last_execution > ( WP_FS__SCRIPT_START_TIME - $period ) ); - } - - /** - * WP Cron is executed on a site level. When running in a multisite network environment - * with the network integration activated, for optimization reasons, we are consolidating - * the installs data sync cron to be executed only from a single site. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $except_blog_id Target any except the excluded blog ID. - * - * @return int - */ - private function get_cron_target_blog_id( $except_blog_id = 0 ) { - if ( ! is_multisite() ) { - return 0; - } - - if ( $this->_is_network_active && - is_numeric( $this->_storage->network_install_blog_id ) && - $except_blog_id != $this->_storage->network_install_blog_id && - self::is_site_active( $this->_storage->network_install_blog_id ) - ) { - // Try to run cron from the main network blog. - $install = $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ); - - if ( is_object( $install ) && - ( $this->is_premium() || $install->is_tracking_allowed() ) - ) { - return $this->_storage->network_install_blog_id; - } - } - - // Get first opted-in blog ID with active tracking. - $installs = $this->get_blog_install_map(); - foreach ( $installs as $blog_id => $install ) { - if ( $except_blog_id != $blog_id && - self::is_site_active( $blog_id ) && - ( $this->is_premium() || $install->is_tracking_allowed() ) - ) { - return $blog_id; - } - } - - return 0; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param string $action_tag Callback action tag. - * @param bool $is_network_clear If set to TRUE, clear sync cron even if there are installs that are still connected. - */ - private function clear_cron( $name, $action_tag = '', $is_network_clear = false ) { - $this->_logger->entrance( $name ); - - if ( ! $this->is_cron_on( $name ) ) { - return; - } - - $clear_cron = true; - if ( ! $is_network_clear && $this->_is_network_active ) { - $installs = $this->get_blog_install_map(); - - foreach ( $installs as $blog_id => $install ) { - /** - * @var FS_Site $install - */ - if ( $install->is_tracking_allowed() ) { - $clear_cron = false; - break; - } - } - } - - if ( ! $clear_cron ) { - return; - } - - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? - $cron_data->blog_id : - 0; - - $this->clear_cron_data( $name ); - - if ( 0 < $cron_blog_id ) { - switch_to_blog( $cron_blog_id ); - } - - if ( empty( $action_tag ) ) { - $action_tag = $name; - } - - wp_clear_scheduled_hook( $this->get_action_tag( $action_tag ) ); - - if ( 0 < $cron_blog_id ) { - restore_current_blog(); - } - } - - /** - * Unix timestamp for next cron execution or false if not scheduled. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param string $action_tag Callback action tag. - * - * @return int|false - */ - private function get_next_scheduled_cron( $name, $action_tag = '' ) { - $this->_logger->entrance( $name ); - - if ( ! $this->is_cron_on( $name ) ) { - return false; - } - - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? - $cron_data->blog_id : - 0; - - if ( 0 < $cron_blog_id ) { - switch_to_blog( $cron_blog_id ); - } - - if ( empty( $action_tag ) ) { - $action_tag = $name; - } - - $next_scheduled = wp_next_scheduled( $this->get_action_tag( $action_tag ) ); - - if ( 0 < $cron_blog_id ) { - restore_current_blog(); - } - - return $next_scheduled; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param string $action_tag Callback action tag. - * @param string $recurrence 'single' or 'daily'. - * @param int $start_at Defaults to now. - * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. - * @param int $except_blog_id Target any except the excluded blog ID. - */ - private function schedule_cron( - $name, - $action_tag = '', - $recurrence = 'single', - $start_at = WP_FS__SCRIPT_START_TIME, - $randomize_start = true, - $except_blog_id = 0 - ) { - $this->_logger->entrance( $name ); - - $this->clear_cron( $name, $action_tag, true ); - - $cron_blog_id = $this->get_cron_target_blog_id( $except_blog_id ); - - if ( is_multisite() && 0 == $cron_blog_id ) { - // Don't schedule cron since couldn't find a target blog. - return; - } - - if ( 0 < $cron_blog_id ) { - switch_to_blog( $cron_blog_id ); - } - - if ( 'daily' === $recurrence ) { - if ( $randomize_start ) { - // Schedule first sync with a random 12 hour time range from now. - $start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) ); - } - - // Schedule daily WP cron. - wp_schedule_event( - $start_at, - 'daily', - $this->get_action_tag( $action_tag ) - ); - } else if ( 'single' === $recurrence ) { - // Schedule single cron. - wp_schedule_single_event( - $start_at, - $this->get_action_tag( $action_tag ) - ); - } - - $this->set_cron_data( $name, $cron_blog_id ); - - if ( 0 < $cron_blog_id ) { - restore_current_blog(); - } - } - - /** - * Consolidated cron execution for performance optimization. The max number of API requests is based on the number of unique opted-in users. - * that doesn't halt page loading. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $name Cron name. - * @param callable $callable The function that should be executed. - */ - private function execute_cron( $name, $callable ) { - $this->_logger->entrance( $name ); - - // Store the last time data sync was executed. - $this->set_cron_execution_timestamp( $name ); - - // Check if API is temporary down. - if ( FS_Api::is_temporary_down() ) { - return; - } - - // @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours. - - $users_2_blog_ids = array(); - - if ( ! is_multisite() ) { - // Add dummy blog. - $users_2_blog_ids[0] = array( 0 ); - } else { - $installs = $this->get_blog_install_map(); - foreach ( $installs as $blog_id => $install ) { - if ( $this->is_premium() || $install->is_tracking_allowed() ) { - if ( ! isset( $users_2_blog_ids[ $install->user_id ] ) ) { - $users_2_blog_ids[ $install->user_id ] = array(); - } - - $users_2_blog_ids[ $install->user_id ][] = $blog_id; - } - } - } - - $current_blog_id = get_current_blog_id(); - - foreach ( $users_2_blog_ids as $user_id => $blog_ids ) { - if ( 0 < $blog_ids[0] ) { - $this->switch_to_blog( $blog_ids[0] ); - } - - call_user_func_array( $callable, array( $blog_ids, ( is_multisite() ? $current_blog_id : null ) ) ); - - foreach ( $blog_ids as $blog_id ) { - $this->do_action( "after_{$name}_cron", $blog_id ); - } - } - - if ( is_multisite() ) { - $this->switch_to_blog( $current_blog_id, fs_is_network_admin() ? $this->get_network_install() : null ); - - $this->do_action( "after_{$name}_cron_multisite" ); - } - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Daily Sync Cron - #---------------------------------------------------------------------------------- - - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - private function is_sync_cron_scheduled() { - return $this->is_cron_on( 'sync' ); - } - - /** - * Get the sync cron's executing blog ID. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return int - */ - private function get_sync_cron_blog_id() { - return $this->get_cron_blog_id( 'sync' ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - private function run_manual_sync() { - self::require_pluggable_essentials(); - - if ( ! $this->is_user_admin() ) { - return; - } - - // Run manual sync. - $this->_sync_cron(); - - // Reschedule next cron to run 24 hours from now (performance optimization). - $this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false ); - } - - /** - * Data sync cron job. Replaces the background sync non blocking HTTP request - * that doesn't halt page loading. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. - */ - function _sync_cron() { - $this->_logger->entrance(); - - $this->execute_cron( 'sync', array( &$this, '_sync_cron_method' ) ); - } - - /** - * The actual data sync cron logic. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int[] $blog_ids - * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the - * `_sync_plugin_license` method in order to switch to the previous blog when sending - * updates for a single site in case `execute_cron` has switched to a different blog. - */ - function _sync_cron_method( array $blog_ids, $current_blog_id = null ) { - if ( $this->is_registered() ) { - if ( $this->has_paid_plan() ) { - // Initiate background plan sync. - $this->_sync_license( true, false, $current_blog_id ); - - if ( $this->is_paying() ) { - // Check for premium plugin updates. - $this->check_updates( true ); - } - } else { - // Sync install(s) (only if something changed locally). - if ( 1 < count( $blog_ids ) ) { - $this->sync_installs(); - } else { - $this->sync_install(); - } - - $this->maybe_sync_install_user(); - } - } - } - - /** - * Check if sync was executed in the last $period of seconds. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param int $period In seconds - * - * @return bool - */ - private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) { - return $this->is_cron_executed( 'sync', $period ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return bool - */ - private function is_sync_cron_on() { - return $this->is_cron_on( 'sync' ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param int $start_at Defaults to now. - * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. - * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. - */ - private function schedule_sync_cron( - $start_at = WP_FS__SCRIPT_START_TIME, - $randomize_start = true, - $except_blog_id = 0 - ) { - $this->schedule_cron( - 'sync', - 'data_sync', - 'daily', - $start_at, - $randomize_start, - $except_blog_id - ); - } - - /** - * Add the actual sync function to the cron job hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - private function hook_callback_to_sync_cron() { - $this->add_action( 'data_sync', array( &$this, '_sync_cron' ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. - */ - private function clear_sync_cron( $is_network_clear = false ) { - $this->_logger->entrance(); - - $this->clear_cron( 'sync', 'data_sync', $is_network_clear ); - } - - /** - * Unix timestamp for next sync cron execution or false if not scheduled. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return int|false - */ - function next_sync_cron() { - return $this->get_next_scheduled_cron( 'sync', 'data_sync' ); - } - - /** - * Unix timestamp for previous sync cron execution or false if never executed. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return int|false - */ - function last_sync_cron() { - return $this->cron_last_execution( 'sync' ); - } - - #endregion Daily Sync Cron ------------------------------------------------------------------ - - #---------------------------------------------------------------------------------- - #region Async Install Sync - #---------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return bool - */ - private function is_install_sync_scheduled() { - return $this->is_cron_on( 'install_sync' ); - } - - /** - * Get the sync cron's executing blog ID. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return int - */ - private function get_install_sync_cron_blog_id() { - return $this->get_cron_blog_id( 'install_sync' ); - } - - /** - * Instead of running blocking install sync event, execute non blocking scheduled wp-cron. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. - */ - private function schedule_install_sync( $except_blog_id = 0 ) { - $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id ); - } - - /** - * Unix timestamp for previous install sync cron execution or false if never executed. - * - * @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return int|false - */ - function last_install_sync() { - return $this->cron_last_execution( 'install_sync' ); - } - - /** - * Unix timestamp for next install sync cron execution or false if not scheduled. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @return int|false - */ - function next_install_sync() { - return $this->get_next_scheduled_cron( 'install_sync', 'install_sync' ); - } - - /** - * Add the actual install sync function to the cron job hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - */ - private function hook_callback_to_install_sync() { - $this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. - */ - private function clear_install_sync_cron( $is_network_clear = false ) { - $this->_logger->entrance(); - - $this->clear_cron( 'install_sync', 'install_sync', $is_network_clear ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. - */ - public function _run_sync_install() { - $this->_logger->entrance(); - - $this->execute_cron( 'sync', array( &$this, '_sync_install_cron_method' ) ); - } - - /** - * The actual install(s) sync cron logic. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int[] $blog_ids - * @param int|null $current_blog_id - */ - function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { - if ( $this->is_registered() ) { - if ( 1 < count( $blog_ids ) ) { - $this->sync_installs( array(), true ); - } else { - $this->sync_install( array(), true ); - } - - $this->maybe_sync_install_user(); - } - } - - #endregion Async Install Sync ------------------------------------------------------------------ - - /** - * Show a notice that activation is currently pending. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param bool|string $email - * @param bool $is_pending_trial Since 1.2.1.5 - */ - function _add_pending_activation_notice( $email = false, $is_pending_trial = false ) { - if ( ! is_string( $email ) ) { - $current_user = self::_get_current_wp_user(); - $email = $current_user->user_email; - } - - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message' ), - '' . $this->get_plugin_name() . '', - '' . $email . '', - ( $is_pending_trial ? - $this->get_text_inline( 'start the trial', 'start-the-trial' ) : - $this->get_text_inline( 'complete the install', 'complete-the-install' ) ) - ), - 'activation_pending', - 'Thanks!' - ); - } - - /** - * Check if currently in plugin activation. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @return bool - */ - function is_plugin_activation() { - $result = get_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); - - return !empty($result); - } - - /** - * - * NOTE: admin_menu action executed before admin_init. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function _admin_init_action() { - $is_migration = $this->is_migration(); - - /** - * Automatically redirect to connect/activation page after plugin activation. - * - * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode. - */ - if ( $this->is_plugin_activation() ) { - delete_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); - - if ( isset( $_GET['activate-multi'] ) ) { - /** - * Don't redirect if activating multiple plugins at once (bulk activation). - */ - } else if ( ! $is_migration ) { - $this->_redirect_on_activation_hook(); - return; - } - } - - if ( $is_migration ) { - return; - } - - if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) { - check_admin_referer( $this->get_unique_affix() . '_skip_activation' ); - - $this->skip_connection( null, fs_is_network_admin() ); - - fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ); - } - - if ( $this->is_network_activation_mode() && - fs_request_is_action( $this->get_unique_affix() . '_delegate_activation' ) - ) { - check_admin_referer( $this->get_unique_affix() . '_delegate_activation' ); - - $this->delegate_connection(); - - fs_redirect( $this->get_after_activation_url( 'after_delegation_url' ) ); - } - - $this->_add_upgrade_action_link(); - - if ( ! ( ! $this->_is_network_active && fs_is_network_admin() ) && - ( - ( true === $this->_storage->require_license_activation ) || - // Not registered nor anonymous. - ( ! $this->is_registered() && ! $this->is_anonymous() ) || - // OR, network level and in network upgrade mode. - ( fs_is_network_admin() && $this->_is_network_active && $this->is_network_upgrade_mode() ) - ) - ) { - if ( ! $this->is_pending_activation() ) { - if ( ! $this->is_activation_page() ) { - /** - * If a user visits any other admin page before activating the premium-only theme with a valid - * license, reactivate the previous theme. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( $this->is_theme() && - ! $this->has_settings_menu() && - ! isset( $_REQUEST['fs_action'] ) && - $this->can_activate_previous_theme() - ) { - if ( $this->is_only_premium() ) { - $this->activate_previous_theme(); - return; - } - - if ( true === $this->_storage->require_license_activation ) { - $this->_storage->require_license_activation = false; - } - } - - if ( ! fs_is_network_admin() && - $this->is_network_activation_mode() && - ! $this->is_delegated_connection() - ) { - return; - } - - if ( $this->is_plugin_new_install() || $this->is_only_premium() ) { - if ( ! $this->_anonymous_mode && - ( ! $this->is_addon() || ! $this->_parent->is_anonymous() ) ) { - // Show notice for new plugin installations. - $this->_admin_notices->add( - sprintf( - $this->get_text_inline( 'You are just one step away - %s', 'you-are-step-away' ), - sprintf( '%s', - $this->get_activation_url( array(), ! $this->is_delegated_connection() ), - sprintf( $this->get_text_x_inline( 'Complete "%s" Activation Now', - '%s - plugin name. As complete "PluginX" activation now', 'activate-x-now' ), $this->get_plugin_name() ) - ) - ), - '', - 'update-nag' - ); - } - } else { - if ( $this->should_add_sticky_optin_notice() ) { - $this->add_sticky_optin_admin_notice(); - } - - if ( $this->has_filter( 'optin_pointer_element' ) ) { - // Don't show admin nag if plugin update. - wp_enqueue_script( 'wp-pointer' ); - wp_enqueue_style( 'wp-pointer' ); - - $this->_enqueue_connect_essentials(); - - add_action( 'admin_print_footer_scripts', array( - $this, - '_add_connect_pointer_script' - ) ); - } - } - } - } - - if ( $this->show_opt_in_on_themes_page() && - $this->is_activation_page() - ) { - $this->_show_theme_activation_optin_dialog(); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - private function should_add_sticky_optin_notice() { - if ( $this->is_addon() && $this->_parent->is_anonymous() ) { - return false; - } - - if ( fs_is_network_admin() ) { - if ( ! $this->_is_network_active ) { - return false; - } - - if ( ! $this->is_network_activation_mode() ) { - return false; - } - - return ! isset( $this->_storage->sticky_optin_added_ms ); - } - - if ( ! $this->is_activation_mode() ) { - return false; - } - - // If running from a blog admin and delegated the connection. - return ! isset( $this->_storage->sticky_optin_added ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - private function add_sticky_optin_admin_notice() { - if ( ! $this->_is_network_active || ! fs_is_network_admin() ) { - $this->_storage->sticky_optin_added = true; - } else { - $this->_storage->sticky_optin_added_ms = true; - } - - // Show notice for new plugin installations. - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'We made a few tweaks to the %s, %s', 'few-plugin-tweaks' ), - $this->_module_type, - sprintf( '%s', - $this->get_activation_url(), - sprintf( $this->get_text_inline( 'Opt in to make "%s" better!', 'optin-x-now' ), $this->get_plugin_name() ) - ) - ), - 'connect_account', - '', - 'update-nag' - ); - } - - /** - * Enqueue connect requires scripts and styles. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - */ - function _enqueue_connect_essentials() { - wp_enqueue_script( 'jquery' ); - wp_enqueue_script( 'json2' ); - - fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); - fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); - - fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); - } - - /** - * Add connect / opt-in pointer. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - */ - function _add_connect_pointer_script() { - $vars = array( 'id' => $this->_module_id ); - $pointer_content = fs_get_template( 'connect.php', $vars ); - ?> - - _menu->get_raw_slug() ) || - fs_is_plugin_page( $this->_slug ); - } - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - /** - * Delete site install from Database. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param bool $store - * @param int|null $blog_id Since 2.0.0 - * - * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). - */ - function _delete_site( $store = true, $blog_id = null ) { - return self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store, $blog_id ); - } - - /** - * Delete site install from Database. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param string $slug - * @param string $module_type - * @param bool $store - * @param int|null $blog_id Since 2.0.0 - * - * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). - */ - static function _delete_site_by_slug( $slug, $module_type, $store = true, $blog_id = null ) { - $sites = self::get_all_sites( $module_type, $blog_id ); - - $install_id = false; - - if ( isset( $sites[ $slug ] ) ) { - if ( is_object( $sites[ $slug ] ) ) { - $install_id = $sites[ $slug ]->id; - } - - unset( $sites[ $slug ] ); - - self::set_account_option_by_module( $module_type, 'sites', $sites, $store, $blog_id ); - } - - return $install_id; - } - - /** - * Delete user. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * @param bool $store - * - * @return false|int The user ID if deleted. Otherwise, FALSE (when install not exist). - */ - private static function delete_user( $user_id, $store = true ) { - $users = self::get_all_users(); - - if ( ! is_array( $users ) || ! isset( $users[ $user_id ] ) ) { - return false; - } - - unset( $users[ $user_id ] ); - - self::$_accounts->set_option( 'users', $users, $store ); - - return $user_id; - } - - /** - * Delete plugin's plans information. - * - * @param bool $store Flush to Database if true. - * @param bool $keep_associated_plans If set to false, delete all plans, even if a plan is associated with an install. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - private function _delete_plans( $store = true, $keep_associated_plans = true ) { - $this->_logger->entrance(); - - $plans = self::get_all_plans( $this->_module_type ); - - $plans_to_keep = array(); - - if ( $keep_associated_plans ) { - $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); - foreach ( $plans_ids_to_keep as $plan_id ) { - $plan = self::_get_plan_by_id( $plan_id ); - if ( is_object( $plan ) ) { - $plans_to_keep[] = self::_encrypt_entity( $plan ); - } - } - } - - if ( ! empty( $plans_to_keep ) ) { - $plans[ $this->_slug ] = $plans_to_keep; - } else { - unset( $plans[ $this->_slug ] ); - } - - $this->set_account_option( 'plans', $plans, $store ); - } - - /** - * Delete all plugin licenses. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool $store - */ - private function _delete_licenses( $store = true ) { - $this->_logger->entrance(); - - $all_licenses = self::get_all_licenses(); - - unset( $all_licenses[ $this->_module_id ] ); - - self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); - } - - /** - * Check if Freemius was added on new plugin installation. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.5 - * - * @return bool - */ - function is_plugin_new_install() { - return isset( $this->_storage->is_plugin_new_install ) && - $this->_storage->is_plugin_new_install; - } - - /** - * Check if it's the first plugin release that is running Freemius. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return bool - */ - function is_first_freemius_powered_version() { - return empty( $this->_storage->plugin_last_version ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool|string - */ - private function get_previous_theme_slug() { - return isset( $this->_storage->previous_theme ) ? - $this->_storage->previous_theme : - false; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return string - */ - private function can_activate_previous_theme() { - $slug = $this->get_previous_theme_slug(); - if ( false !== $slug && current_user_can( 'switch_themes' ) ) { - $theme_instance = wp_get_theme( $slug ); - - return $theme_instance->exists(); - } - - return false; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - private function activate_previous_theme() { - switch_theme( $this->get_previous_theme_slug() ); - unset( $this->_storage->previous_theme ); - - global $pagenow; - if ( 'themes.php' === $pagenow ) { - /** - * Refresh the active theme information. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - fs_redirect( $this->admin_url( $pagenow ) ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return string - */ - function get_previous_theme_activation_url() { - if ( ! $this->can_activate_previous_theme() ) { - return ''; - } - - /** - * Activation URL - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - return wp_nonce_url( - $this->admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ), - 'switch-theme_' . $this->get_previous_theme_slug() - ); - } - - /** - * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in - * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is - * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous - * theme doesn't exist, then there will be no close button. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @param string $slug_or_name Old theme's slug or name. - * @param bool|WP_Theme $old_theme WP_Theme instance of the old theme if it still exists. - */ - function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) { - $this->_storage->previous_theme = ( false !== $old_theme ) ? - $old_theme->get_stylesheet() : - $slug_or_name; - - $this->_activate_plugin_event_hook(); - } - - /** - * Plugin activated hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @uses FS_Api - */ - function _activate_plugin_event_hook() { - $this->_logger->entrance( 'slug = ' . $this->_slug ); - - if ( ! $this->is_user_admin() ) { - return; - } - - $this->unregister_uninstall_hook(); - - // Clear API cache on activation. - FS_Api::clear_cache(); - - $is_premium_version_activation = $this->is_plugin() ? - ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) ) : - $this->is_premium(); - - $this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' ); - - if ( $this->is_plugin() ) { - // This logic is relevant only to plugins since both the free and premium versions of a plugin can be active at the same time. - // 1. If running in the activation of the FREE module, get the basename of the PREMIUM. - // 2. If running in the activation of the PREMIUM module, get the basename of the FREE. - $other_version_basename = $is_premium_version_activation ? - $this->_free_plugin_basename : - $this->premium_plugin_basename(); - - if ( ! $this->_is_network_active ) { - /** - * Themes are always network activated, but the ACTUAL activation is per site. - * - * During the activation, the plugin isn't yet active, therefore, - * _is_network_active will be set to false even if it's a network level - * activation. So we need to fix that by looking at the is_network_admin() value. - * - * @author Vova Feldman - */ - $this->_is_network_active = ( - $this->_is_multisite_integrated && - fs_is_network_admin() - ); - } - - /** - * If the other module version is active, deactivate it. - * - * is_plugin_active() checks if the plugin is active on the site or the network level and - * deactivate_plugins() deactivates the plugin whether it's activated on the site or network level. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( - is_plugin_active( $other_version_basename ) && - $this->apply_filters( 'deactivate_on_activation', true ) - ) { - deactivate_plugins( $other_version_basename ); - } - } - - if ( $this->is_registered() ) { - if ( $is_premium_version_activation ) { - $this->reconnect_locally(); - } - - - // Schedule re-activation event and sync. -// $this->sync_install( array(), true ); - $this->schedule_install_sync(); - - // If activating the premium module version, add an admin notice to congratulate for an upgrade completion. - if ( $is_premium_version_activation ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'The upgrade of %s was successfully completed.', 'successful-version-upgrade-message' ), sprintf( '%s', $this->_plugin->title ) ), - $this->get_text_x_inline( 'W00t', - 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' - ); - } - } else if ( $this->is_anonymous() ) { - if ( isset( $this->_storage->is_anonymous_ms ) && $this->_storage->is_anonymous_ms['is'] ) { - $plugin_version = $this->_storage->is_anonymous_ms['version']; - $network = true; - } else { - $plugin_version = $this->_storage->is_anonymous['version']; - $network = false; - } - - /** - * Reset "skipped" click cache on the following: - * 1. Freemius DEV mode. - * 2. WordPress DEBUG mode. - * 3. If a plugin and the user skipped the exact same version before. - * - * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE). - * - * @todo 4. If explicitly asked to retry after every activation. - */ - if ( WP_FS__DEV_MODE || - ( - ( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) && - $this->get_plugin_version() == $plugin_version - ) - ) { - $this->reset_anonymous_mode( $network ); - } - } - - $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); - - if ( $this->is_addon() && ! $is_trial_or_has_features_enabled_license ) { - /** - * When activating an add-on, try to also activate a license. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - if ( ! $this->_is_network_active ) { - $this->maybe_activate_addon_license(); - } else { - $this->maybe_network_activate_addon_license(); - } - - /** - * Avoid redirecting to the license activation screen after automatically activating an add-on license. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); - - if ( $is_trial_or_has_features_enabled_license && true === $this->_storage->require_license_activation ) { - $this->_storage->require_license_activation = false; - } - } - - if ( - $is_premium_version_activation && - ( - ( ! $this->is_registered() && $this->is_anonymous() ) || - ( - $this->is_registered() && - ! $is_trial_or_has_features_enabled_license - ) - ) - ) { - $this->_storage->require_license_activation = true; - } - - if ( ! isset( $this->_storage->is_plugin_new_install ) ) { - /** - * If no previous version of plugin's version exist, it means that it's either - * the first time that the plugin installed on the site, or the plugin was installed - * before but didn't have Freemius integrated. - * - * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires - * on manual activation via the dashboard, is_plugin_activation() is TRUE - * only after immediate activation. - * - * @since 1.1.4 - * @link https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/ - */ - $this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version ); - } - - /** - * Also flush when activating the premium version so that even if Freemius was off before, the API - * connectivity test can be run again. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3.1 - */ - $has_api_connectivity = $this->has_api_connectivity( WP_FS__DEV_MODE || $is_premium_version_activation ); - - if ( ! $this->_anonymous_mode && - $has_api_connectivity && - ! $this->_isAutoInstall - ) { - // Store hint that the plugin was just activated to enable auto-redirection to settings. - set_transient( "fs_{$this->_module_type}_{$this->_slug}_activated", true, 60 ); - } - - /** - * Activation hook is executed after the plugin's main file is loaded, therefore, - * after the plugin was loaded. The logic is located at activate_plugin() - * ./wp-admin/includes/plugin.php. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - */ - $this->_storage->was_plugin_loaded = true; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - private function maybe_activate_addon_license() { - $parent_fs = $this->get_parent_instance(); - - if ( - ! is_object( $parent_fs ) || - ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) - ) { - // Try to activate a license only if the parent plugin is active and has a valid `install`. - return; - } - - $license = $this->get_active_parent_license(); - if ( ! is_object( $license ) ) { - return; - } - - if ( - $this->is_bundle_license_auto_activation_enabled() && - ! empty( $license->products ) - ) { - $this->activate_bundle_license( $license ); - - return; - } - - if ( ! $this->is_registered() ) { - // Opt in with a license key. - $this->opt_in( - $parent_fs->get_current_or_network_user()->email, - false, - false, - $license->secret_key - ); - } else { - // Activate the license. - $install = $this->get_api_site_scope()->call( - '/', - 'put', - array( 'license_key' => $this->apply_filters( 'license_key', $license->secret_key ) ) - ); - - if ( ! FS_Api::is_api_error( $install ) ) { - $this->_sync_addon_license( $this->get_id(), true ); - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param FS_Plugin_License $license - */ - private function maybe_network_activate_addon_license( $license = null ) { - $parent_fs = $this->get_parent_instance(); - if ( ! is_object( $parent_fs ) || ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) ) { - // Try to activate a license only if the parent plugin is active and has a valid `install`. - return; - } - - $license = ( ! is_null( $license ) ) ? - $license : - $this->get_active_parent_license(); - - if ( ! is_object( $license ) ) { - return; - } - - if ( - $this->is_bundle_license_auto_activation_enabled() && - ! empty( $license->products ) - ) { - $this->activate_bundle_license( $license ); - - return; - } - - if ( ! $this->is_network_registered() ) { - $sites = $this->get_sites_for_network_level_optin(); - - if ( count( $sites ) > $license->left() ) { - // If the add-on is network active, try to activate the license only if it can be activated on all sites. - return; - } - - // Opt in with a license key. - $this->opt_in( - $parent_fs->get_user()->email, - false, - false, - $license->secret_key, - false, - false, - false, - null, - $sites - ); - } else { - $blog_2_install_map = array(); - $site_ids = array(); - - $all_sites = Freemius::get_sites(); - - foreach ( $all_sites as $site ) { - $blog_id = Freemius::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { - // Skip license activation for installs that are already associated with a license. - continue; - } - - if ( is_object( $install ) ) { - $blog_2_install_map[ $blog_id ] = $install; - } else { - $site_ids[] = $blog_id; - } - } - - if ( ( count( $blog_2_install_map ) + count( $site_ids ) ) > $license->left() ) { - return; - } - - $user = $this->get_current_or_network_user(); - - if ( ! empty( $blog_2_install_map ) ) { - $result = $this->activate_license_on_many_installs( $user, $license->secret_key, $blog_2_install_map ); - - if ( true !== $result ) { - return; - } - } - - if ( ! empty( $site_ids ) ) { - $this->activate_license_on_many_sites( $user, $license->secret_key, $site_ids ); - } - } - } - - /** - * Tries to activate a bundle license for all supported products if the current product is activated with a bundle license. This is called after activating an available license (not via the license activation dialog but by clicking on a license activation button) for a product via its "Account" page. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - * - * @param FS_Plugin_License $license - * @param array $sites - * @param int $blog_id - */ - private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) { - if ( ! is_object( $license ) && $this->has_active_valid_license() ) { - $license = $this->_license; - } - - if ( ! is_object( $license ) ) { - return; - } - - $parent_license = ( ! empty( $license->products ) ) ? - $license : - $this->get_active_parent_license( $license->secret_key ); - - if ( is_object( $parent_license ) ) { - $this->activate_bundle_license( $parent_license, $sites, $blog_id ); - } - } - - /** - * Try to activate a bundle license for all the bundle products installed on the site. - * (1) If a child product install already has a license, the bundle license won't be activated. - * (2) On multi-site networks, if the attempt to activate the bundle license is triggered from the network admin, the bundle license activation will only work for non-delegated sites and only if none of them is associated with a license. Even if one of the sites has the product installed with a license key, skip the bundle license activation for the product. - * (3) On multi-site networks, if the attempt to activate the bundle license is triggered from a site-level admin, only activate the license if the product is site-level activated or delegated, and the product installation is not yet associated with a license. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - * - * @param FS_Plugin_License $license - * @param array $sites - * @param int $current_blog_id - */ - private function activate_bundle_license( $license, $sites = array(), $current_blog_id = 0 ) { - $is_network_admin = fs_is_network_admin(); - - $installs_by_blog_map = array(); - $site_info_by_blog_map = array(); - - /** - * Try to activate the license for all supported products. - * - * @author Leo Fajardo - */ - foreach ( $license->products as $product_id ) { - $fs = self::get_instance_by_id( $product_id ); - - if ( ! is_object( $fs ) ) { - continue; - } - - if ( ! $fs->has_paid_plan() ) { - continue; - } - - if ( - ! $fs->is_addon() && - ! FS_Plan_Manager::instance()->has_paid_plan( $fs->_plans ) - ) { - /** - * The parent product can be free-only but can have its `has_paid_plan` flag set to `true` when - * there is a context bundle. - */ - continue; - } - - if ( $current_blog_id > 0 ) { - $fs->switch_to_blog( $current_blog_id ); - } - - if ( $fs->has_active_valid_license() ) { - continue; - } - - if ( ! $is_network_admin || $current_blog_id > 0 ) { - if ( $fs->is_network_active() && ! $fs->is_delegated_connection( $current_blog_id ) ) { - // Do not try to activate the license in the site level if the product is network active and the connection was not delegated. - continue; - } - } else { - if ( ! $fs->is_network_active() ) { - // Do not try to activate the license in the network level if the product is not network active. - continue; - } - - if ( $fs->is_network_delegated_connection() ) { - // Do not try to activate the license in the network level if the activation has been delegated to site admins. - continue; - } - - $has_install_with_license = false; - - // Collection of sites that have an install entity that is not activated with a license or non-delegated sites that have no install entity, or both types of site. - $filtered_sites = array(); - - if ( empty( $sites ) ) { - $all_sites = self::get_sites(); - - foreach ( $all_sites as $site ) { - $sites[] = array( 'blog_id' => self::get_site_blog_id( $site ) ); - } - } else { - // Populate the map here to avoid calling `$fs->get_site_info( $site );` in the other `for` loop below. - foreach ( $sites as $site ) { - if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { - continue; - } - - $site_info_by_blog_map[ $site['blog_id'] ] = $site; - } - } - - foreach ( $sites as $site ) { - if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { - continue; - } - - $blog_id = $site['blog_id']; - - if ( ! isset( $installs_by_blog_map[ $blog_id ] ) ) { - $installs_by_blog_map[ $blog_id ] = self::get_all_sites( $fs->get_module_type(), $blog_id ); - } - - $installs = $installs_by_blog_map[ $blog_id ]; - $install = null; - - if ( isset( $installs[ $fs->get_slug() ] ) ) { - $install = $installs[ $fs->get_slug() ]; - - if ( - is_object( $install ) && - ( - ! FS_Site::is_valid_id( $install->id ) || - ! FS_User::is_valid_id( $install->user_id ) || - ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) - ) - ) { - $install = null; - } - } - - if ( - is_object( $install ) && - FS_Plugin_License::is_valid_id( $install->license_id ) - ) { - $has_install_with_license = true; - break; - } - - if ( $fs->is_site_delegated_connection( $blog_id ) ) { - // Site activation delegated, don't activate bundle license on the site in the network admin. - continue; - } - - if ( ! isset( $site_info_by_blog_map[ $blog_id ] ) ) { - $site_info_by_blog_map[ $blog_id ] = $fs->get_site_info( $site ); - } - - $filtered_sites[] = $site_info_by_blog_map[ $blog_id ]; - } - - if ( $has_install_with_license || empty( $filtered_sites ) ) { - // Do not try to activate the license at the network level if there's any install with a license or there's no site to activate the license on. - continue; - } - - $sites = $filtered_sites; - } - - $fs->activate_migrated_license( - $license->secret_key, - null, - null, - $sites, - ( $current_blog_id > 0 ? $current_blog_id : null ) - ); - } - } - - /** - * Returns a parent license that can be activated for the context product. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param string|null $license_key - * @param bool $flush - * - * @return FS_Plugin_License - */ - function get_active_parent_license( $license_key = null, $flush = true ) { - $parent_licenses_endpoint = "/plugins/{$this->get_id()}/parent_licenses.json?filter=activatable"; - - $fs = $this; - - if ( $this->is_addon() ) { - $parent_instance = $this->get_parent_instance(); - - if ( is_object( $parent_instance ) && $parent_instance->is_registered() ) { - $fs = $parent_instance; - } - } - - $foreign_licenses = $fs->get_foreign_licenses_info( - self::get_all_licenses( $this->get_parent_id() ) - ); - - if ( ! empty ( $foreign_licenses ) ) { - $foreign_licenses = array( - // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. - 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), - 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) - ); - - $parent_licenses_endpoint = add_query_arg( $foreign_licenses, $parent_licenses_endpoint ); - } - - $result = $fs->get_current_or_network_user_api_scope()->get( $parent_licenses_endpoint, $flush ); - - if ( - ! $this->is_api_result_object( $result, 'licenses' ) || - ! is_array( $result->licenses ) || - empty( $result->licenses ) - ) { - return null; - } - - $parent_license = null; - - if ( empty( $license_key ) ) { - $parent_license = $result->licenses[0]; - } else { - foreach ( $result->licenses as $license ) { - if ( $license_key === $license->secret_key ) { - $parent_license = $license; - break; - } - } - } - - if ( ! is_null( $parent_license ) ) { - $parent_license = new FS_Plugin_License( $parent_license ); - } - - return $parent_license; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return array - */ - function get_sites_for_network_level_optin() { - $sites = array(); - $all_sites = self::get_sites(); - - foreach ( $all_sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( ! $this->is_site_delegated_connection( $blog_id ) && - ! $this->is_installed_on_site( $blog_id ) - ) { - $sites[] = $this->get_site_info( $site ); - } - } - - return $sites; - } - - /** - * Delete account. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param bool $check_user Enforce checking if user have plugins activation privileges. - */ - function delete_account_event( $check_user = true ) { - $this->_logger->entrance( 'slug = ' . $this->_slug ); - - if ( $check_user && ! $this->is_user_admin() ) { - return; - } - - $this->do_action( 'before_account_delete' ); - - // Clear all admin notices. - $this->_admin_notices->clear_all_sticky( false ); - - $this->_delete_site( false ); - - $delete_network_common_data = true; - - if ( $this->_is_network_active ) { - $installs = $this->get_blog_install_map(); - - // Don't delete common network data unless no other installs left. - $delete_network_common_data = empty( $installs ); - } - - if ( $delete_network_common_data ) { - $this->_delete_plans( false ); - - $this->_delete_licenses( false ); - - // Delete add-ons related to plugin's account. - $this->_delete_account_addons( false ); - } - - // @todo Delete plans and licenses of add-ons. - - self::$_accounts->store(); - - /** - * IMPORTANT: - * Clear crons must be executed before clearing all storage. - * Otherwise, the cron will not be cleared. - */ - if ( $delete_network_common_data ) { - $this->clear_sync_cron(); - } - - $this->clear_install_sync_cron(); - - // Clear all storage data. - $this->_storage->clear_all( true, array( - 'is_delegated_connection', - 'connectivity_test', - 'is_on', - ), false ); - - // Send delete event. - $this->get_api_site_scope()->call( '/', 'delete' ); - - $this->do_action( 'after_account_delete' ); - } - - /** - * Delete network level account. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param bool $check_user Enforce checking if user have plugins activation privileges. - */ - function delete_network_account_event( $check_user = true ) { - $this->_logger->entrance( 'slug = ' . $this->_slug ); - - if ( $check_user && ! $this->is_user_admin() ) { - return; - } - - $this->do_action( 'before_network_account_delete' ); - - // Clear all admin notices. - $this->_admin_notices->clear_all_sticky(); - - $this->_delete_plans( false, false ); - - $this->_delete_licenses( false ); - - // Delete add-ons related to plugin's account. - $this->_delete_account_addons( false ); - - // @todo Delete plans and licenses of add-ons. - - self::$_accounts->store( true ); - - /** - * IMPORTANT: - * Clear crons must be executed before clearing all storage. - * Otherwise, the cron will not be cleared. - */ - $this->clear_sync_cron( true ); - $this->clear_install_sync_cron( true ); - - $sites = self::get_sites(); - - $install_ids = array(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( $this->is_site_delegated_connection( $blog_id ) ) { - continue; - } - - $install_id = $this->_delete_site( true, $blog_id ); - - // Clear all storage data. - $this->_storage->clear_all( true, array( 'connectivity_test' ), $blog_id ); - - if ( FS_Site::is_valid_id( $install_id ) ) { - $install_ids[] = $install_id; - } - - switch_to_blog( $blog_id ); - - $this->do_action( 'after_account_delete' ); - - restore_current_blog(); - } - - $this->_storage->clear_all( true, array( - 'connectivity_test', - 'is_on', - ), true ); - - // Send delete event. - if ( ! empty( $install_ids ) ) { - $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', $install_ids ), 'delete' ); - } - - $this->do_action( 'after_network_account_delete' ); - } - - /** - * Plugin deactivation hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - */ - function _deactivate_plugin_hook() { - $this->_logger->entrance( 'slug = ' . $this->_slug ); - - if ( ! $this->is_user_admin() ) { - return; - } - - $is_network_deactivation = fs_is_network_admin(); - $storage_keys_for_removal = array(); - - $this->_admin_notices->clear_all_sticky(); - - $storage_keys_for_removal[] = 'sticky_optin_added'; - if ( isset( $this->_storage->sticky_optin_added ) ) { - unset( $this->_storage->sticky_optin_added ); - } - - if ( ! isset( $this->_storage->is_plugin_new_install ) ) { - // Remember that plugin was already installed. - $this->_storage->is_plugin_new_install = false; - } - - // Hook to plugin uninstall. - register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) ); - - $this->clear_module_main_file_cache(); - $this->clear_sync_cron( $this->_is_network_active ); - $this->clear_install_sync_cron(); - - if ( $this->is_registered() ) { - if ( $this->is_premium() && ! $this->has_active_valid_license() ) { - FS_Plugin_Updater::instance( $this )->delete_update_data(); - } - - if ( $is_network_deactivation ) { - // Send deactivation event. - $this->sync_installs( array( - 'is_active' => false, - ) ); - } else { - // Send deactivation event. - $this->sync_install( array( - 'is_active' => false, - ) ); - } - } else { - if ( ! $this->has_api_connectivity() ) { - // Reset connectivity test cache. - unset( $this->_storage->connectivity_test ); - - $storage_keys_for_removal[] = 'connectivity_test'; - } - } - - if ( $is_network_deactivation ) { - if ( isset( $this->_storage->sticky_optin_added_ms ) ) { - unset( $this->_storage->sticky_optin_added_ms ); - } - - if ( ! empty( $storage_keys_for_removal ) ) { - $sites = self::get_sites(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - foreach ( $storage_keys_for_removal as $key ) { - $this->_storage->remove( $key, false, $blog_id ); - } - - $this->_storage->save( $blog_id ); - } - } - } - - // Clear API cache on deactivation. - FS_Api::clear_cache(); - - $this->remove_sdk_reference(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - */ - private function remove_sdk_reference() { - global $fs_active_plugins; - - foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { - if ( $this->_plugin_basename == $data->plugin_path ) { - unset( $fs_active_plugins->plugins[ $sdk_path ] ); - break; - } - } - - fs_fallback_to_newest_active_sdk(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param bool $is_anonymous - * @param bool|int $network_or_blog_id Since 2.0.0 - */ - private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = 0 ) { - // Store information regarding skip to try and opt-in the user - // again in the future. - $skip_info = array( - 'is' => $is_anonymous, - 'timestamp' => WP_FS__SCRIPT_START_TIME, - 'version' => $this->get_plugin_version(), - ); - - if ( true === $network_or_blog_id ) { - $this->_storage->is_anonymous_ms = $skip_info; - } else { - $this->_storage->store( 'is_anonymous', $skip_info, $network_or_blog_id ); - } - - $this->network_upgrade_mode_completed(); - - // Update anonymous mode cache. - $this->_is_anonymous = $is_anonymous; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id Site ID. - * @param int $user_id User ID. - * @param string $domain Site domain. - * @param string $path Site path. - * @param int $network_id Network ID. Only relevant on multi-network installations. - * @param array $meta Metadata. Used to set initial site options. - * - * @uses Freemius::is_license_network_active() to check if the context license was network activated by the super-admin. - * @uses Freemius::is_network_connected() to check if the super-admin network opted-in. - * @uses Freemius::is_network_anonymous() to check if the super-admin network skipped. - * @uses Freemius::is_network_delegated_connection() to check if the super-admin network delegated the connection to the site admins. - */ - function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_id, $meta ) { - $this->_logger->entrance(); - - if ( $this->is_premium() && - $this->is_network_connected() && - is_object( $this->_license ) && - $this->_license->can_activate( FS_Site::is_localhost_by_address( $domain ) ) && - $this->is_license_network_active( $blog_id ) - ) { - /** - * Running the premium version, the license was network activated, and the license can also be activated on the current site -> so try to opt-in with the license key. - */ - $current_blog_id = get_current_blog_id(); - $license = clone $this->_license; - - $this->switch_to_blog( $blog_id ); - - // Opt-in with network user. - $this->install_with_user( - $this->get_network_user(), - $license->secret_key, - false, - false, - false - ); - - if ( is_object( $this->_site ) ) { - if ( $this->_site->license_id == $license->id ) { - /** - * If the license was activated successfully, sync the license data from the remote server. - */ - $this->_license = $license; - $this->sync_site_license(); - } - } - - $this->switch_to_blog( $current_blog_id ); - - if ( is_object( $this->_site ) ) { - // Already connected (with or without a license), so no need to continue. - return; - } - } - - if ( $this->is_network_anonymous() ) { - /** - * Opt-in was network skipped so automatically skip the opt-in for the new site. - */ - $this->skip_site_connection( $blog_id ); - } else if ( $this->is_network_delegated_connection() ) { - /** - * Opt-in was network delegated so automatically delegate the opt-in for the new site's admin. - */ - $this->delegate_site_connection( $blog_id ); - } else if ( $this->is_network_connected() ) { - /** - * Opt-in was network activated so automatically opt-in with the network user and new site admin. - */ - $current_blog_id = get_current_blog_id(); - - $this->switch_to_blog( $blog_id ); - - // Opt-in with network user. - $this->install_with_user( - $this->get_network_user(), - false, - false, - false, - false - ); - - $this->switch_to_blog( $current_blog_id ); - } else { - /** - * If the super-admin mixed different options (connect, skip, delegated): - * a) If at least one site connection was delegated, then automatically delegate connection. - * b) Otherwise, it means that at least one site was skipped and at least one site was connected. For a simplified UX in the initial release of the multisite network integration, skip the connection for the newly created site. If the super-admin will want to opt-in they can still do that from the network level Account page. - */ - $has_delegated_site = false; - - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( $this->is_site_delegated_connection( $blog_id ) ) { - $has_delegated_site = true; - break; - } - } - - if ( $has_delegated_site ) { - $this->delegate_site_connection( $blog_id ); - } else { - $this->skip_site_connection( $blog_id ); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param bool|int $network_or_blog_id Since 2.0.0. - */ - private function reset_anonymous_mode( $network_or_blog_id = 0 ) { - if ( true === $network_or_blog_id ) { - unset( $this->_storage->is_anonymous_ms ); - } else { - $this->_storage->remove( 'is_anonymous', true, $network_or_blog_id ); - } - - /** - * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module - * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click - * on the admin notice that contains the opt-in link in order to trigger the opt-in popup). - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( ! $this->_is_network_active || - 0 === $network_or_blog_id || - get_current_blog_id() == $network_or_blog_id || - ( true === $network_or_blog_id && fs_is_network_admin() ) - ) { - $this->_is_anonymous = null; - } - } - - /** - * This is used to ensure that before redirecting to the opt-in page after resetting the anonymous mode or - * deleting the account in the network level, the URL of the page to redirect to is correct. - * - * @author Leo Fajardo (@leorw) - * - * @since 2.1.3 - */ - private function maybe_set_slug_and_network_menu_exists_flag() { - if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { - $this->_menu->set_slug_and_network_menu_exists_flag( $this->_menu->has_menu() ? - $this->_menu->get_slug() : - $this->_slug - ); - } - } - - /** - * Clears the anonymous mode and redirects to the opt-in screen. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - */ - function connect_again() { - if ( ! $this->is_anonymous() ) { - return; - } - - $this->reset_anonymous_mode( fs_is_network_admin() ); - - $this->maybe_set_slug_and_network_menu_exists_flag(); - - fs_redirect( $this->get_activation_url() ); - } - - /** - * Skip account connect, and set anonymous mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @param array|null $sites Since 2.0.0. Specific sites. - * @param bool $skip_all_network Since 2.0.0. If true, skip connection for all sites. - */ - function skip_connection( $sites = null, $skip_all_network = false ) { - $this->_logger->entrance(); - - $this->_admin_notices->remove_sticky( 'connect_account' ); - - if ( $skip_all_network ) { - $this->set_anonymous_mode( true, true ); - } - - if ( ! $skip_all_network && empty( $sites ) ) { - $this->skip_site_connection(); - } else { - $uids = array(); - - if ( $skip_all_network ) { - $this->set_anonymous_mode( true, true ); - - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $this->skip_site_connection( $blog_id, false ); - $uids[] = $this->get_anonymous_id( $blog_id ); - } - } else if ( ! empty( $sites ) ) { - foreach ( $sites as $site ) { - $uids[] = $site['uid']; - $this->skip_site_connection( $site['blog_id'], false ); - } - } - - // Send anonymous skip event. - // No user identified info nor any tracking will be sent after the user skips the opt-in. - $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( - 'uids' => $uids, - ) ); - } - - $this->network_upgrade_mode_completed(); - } - - /** - * Skip connection for specific site in the network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int|null $blog_id - * @param bool $send_skip - */ - private function skip_site_connection( $blog_id = null, $send_skip = true ) { - $this->_logger->entrance(); - - $this->_admin_notices->remove_sticky( 'connect_account', $blog_id ); - - $this->set_anonymous_mode( true, $blog_id ); - - if ( $send_skip ) { - $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( - 'uids' => array( $this->get_anonymous_id( $blog_id ) ), - ) ); - } - } - - /** - * Plugin version update hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - */ - private function update_plugin_version_event() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return; - } - - $this->schedule_install_sync(); -// $this->sync_install( array(), true ); - } - - /** - * Generate an MD5 signature of a plugins collection. - * This helper methods used to identify changes in a plugins collection. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param array [string]array $plugins - * - * @return string - */ - private function get_plugins_thumbprint( $plugins ) { - ksort( $plugins ); - - $thumbprint = ''; - foreach ( $plugins as $basename => $data ) { - $thumbprint .= $data['slug'] . ',' . - $data['Version'] . ',' . - ( $data['is_active'] ? '1' : '0' ) . ';'; - } - - return md5( $thumbprint ); - } - - /** - * Return a list of modified plugins since the last sync. - * - * Note: - * There's no point to store a plugins counter since even if the number of - * plugins didn't change, we still need to check if the versions are all the - * same and the activity state is similar. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return array|false - */ - private function get_plugins_data_for_api() { - // Alias. - $site_active_plugins_option_name = 'active_plugins'; - $network_plugins_option_name = 'all_plugins'; - - /** - * Collection of all site level active plugins. - */ - $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); - - if ( ! is_object( $site_active_plugins_cache ) ) { - $site_active_plugins_cache = (object) array( - 'timestamp' => '', - 'md5' => '', - 'plugins' => array(), - ); - } - - $time = time(); - - if ( ! empty( $site_active_plugins_cache->timestamp ) && - ( $time - $site_active_plugins_cache->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC - ) { - // Don't send plugin updates if last update was in the past 5 min. - return false; - } - - // Write timestamp to lock the logic. - $site_active_plugins_cache->timestamp = $time; - self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); - - // Reload options from DB. - self::$_accounts->load( true ); - $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); - - if ( $time != $site_active_plugins_cache->timestamp ) { - // If timestamp is different, then another thread captured the lock. - return false; - } - - /** - * Collection of all plugins (network level). - */ - $network_plugins_cache = self::$_accounts->get_option( $network_plugins_option_name ); - - if ( ! is_object( $network_plugins_cache ) ) { - $network_plugins_cache = (object) array( - 'timestamp' => '', - 'md5' => '', - 'plugins' => array(), - ); - } - - // Check if there's a change in plugins. - $network_plugins = self::get_network_plugins(); - $site_active_plugins = self::get_site_active_plugins(); - - $network_plugins_thumbprint = $this->get_plugins_thumbprint( $network_plugins ); - $site_active_plugins_thumbprint = $this->get_plugins_thumbprint( $site_active_plugins ); - - // Check if plugins status changed (version or active/inactive). - $network_plugins_changed = ( $network_plugins_cache->md5 !== $network_plugins_thumbprint ); - $site_active_plugins_changed = ( $site_active_plugins_cache->md5 !== $site_active_plugins_thumbprint ); - - if ( ! $network_plugins_changed && - ! $site_active_plugins_changed - ) { - // No changes. - return array(); - } - - $plugins_update_data = array(); - - foreach ( $network_plugins_cache->plugins as $basename => $data ) { - if ( ! isset( $network_plugins[ $basename ] ) ) { - // Plugin uninstalled. - $uninstalled_plugin_data = $data; - $uninstalled_plugin_data['is_active'] = false; - $uninstalled_plugin_data['is_uninstalled'] = true; - $plugins_update_data[] = $uninstalled_plugin_data; - - unset( $network_plugins[ $basename ] ); - - unset( $network_plugins_cache->plugins[ $basename ] ); - unset( $site_active_plugins_cache->plugins[ $basename ] ); - - continue; - } - - $was_active = $data['is_active'] || - ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && - true === $site_active_plugins_cache->plugins[ $basename ]['is_active'] ); - $is_active = $network_plugins[ $basename ]['is_active'] || - ( isset( $site_active_plugins[ $basename ] ) && - $site_active_plugins[ $basename ]['is_active'] ); - - if ( ! isset( $site_active_plugins_cache->plugins[ $basename ] ) && - isset( $site_active_plugins[ $basename ] ) - ) { - // Plugin was site level activated. - $site_active_plugins_cache->plugins[ $basename ] = $network_plugins[ $basename ]; - $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; - } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && - ! isset( $site_active_plugins[ $basename ] ) - ) { - // Plugin was site level deactivated. - unset( $site_active_plugins_cache->plugins[ $basename ] ); - } - - $prev_version = $data['version']; - $current_version = $network_plugins[ $basename ]['Version']; - - if ( $was_active !== $is_active || $prev_version !== $current_version ) { - // Plugin activated or deactivated, or version changed. - - if ( $was_active !== $is_active ) { - if ( $data['is_active'] != $network_plugins[ $basename ]['is_active'] ) { - $network_plugins_cache->plugins[ $basename ]['is_active'] = $data['is_active']; - } - } - - if ( $prev_version !== $current_version ) { - $network_plugins_cache->plugins[ $basename ]['Version'] = $current_version; - } - - $updated_plugin_data = $data; - $updated_plugin_data['is_active'] = $is_active; - $updated_plugin_data['version'] = $current_version; - $updated_plugin_data['title'] = $network_plugins[ $basename ]['Name']; - $plugins_update_data[] = $updated_plugin_data; - } - } - - // Find new plugins that weren't yet seen before. - foreach ( $network_plugins as $basename => $data ) { - if ( ! isset( $network_plugins_cache->plugins[ $basename ] ) ) { - // New plugin. - $new_plugin = array( - 'slug' => $data['slug'], - 'version' => $data['Version'], - 'title' => $data['Name'], - 'is_active' => $data['is_active'], - 'is_uninstalled' => false, - ); - - $network_plugins_cache->plugins[ $basename ] = $new_plugin; - - $is_site_level_active = ( - isset( $site_active_plugins[ $basename ] ) && - $site_active_plugins[ $basename ]['is_active'] - ); - - /** - * If not network active, set the activity status based on the site-level plugin status. - */ - if ( ! $new_plugin['is_active'] ) { - $new_plugin['is_active'] = $is_site_level_active; - } - - $plugins_update_data[] = $new_plugin; - - if ( isset( $site_active_plugins[ $basename ] ) ) { - $site_active_plugins_cache->plugins[ $basename ] = $new_plugin; - $site_active_plugins_cache->plugins[ $basename ]['is_active'] = $is_site_level_active; - } - } - } - - $site_active_plugins_cache->md5 = $site_active_plugins_thumbprint; - $site_active_plugins_cache->timestamp = $time; - self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); - - $network_plugins_cache->md5 = $network_plugins_thumbprint; - $network_plugins_cache->timestamp = $time; - self::$_accounts->set_option( $network_plugins_option_name, $network_plugins_cache, true ); - - return $plugins_update_data; - } - - /** - * Return a list of modified themes since the last sync. - * - * Note: - * There's no point to store a themes counter since even if the number of - * themes didn't change, we still need to check if the versions are all the - * same and the activity state is similar. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return array|false - */ - private function get_themes_data_for_api() { - // Alias. - $option_name = 'all_themes'; - - $all_cached_themes = self::$_accounts->get_option( $option_name ); - - if ( ! is_object( $all_cached_themes ) ) { - $all_cached_themes = (object) array( - 'timestamp' => '', - 'md5' => '', - 'themes' => array(), - ); - } - - $time = time(); - - if ( ! empty( $all_cached_themes->timestamp ) && - ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC - ) { - // Don't send theme updates if last update was in the past 5 min. - return false; - } - - // Write timestamp to lock the logic. - $all_cached_themes->timestamp = $time; - self::$_accounts->set_option( $option_name, $all_cached_themes, true ); - - // Reload options from DB. - self::$_accounts->load( true ); - $all_cached_themes = self::$_accounts->get_option( $option_name ); - - if ( $time != $all_cached_themes->timestamp ) { - // If timestamp is different, then another thread captured the lock. - return false; - } - - // Get active theme. - $active_theme = wp_get_theme(); - $active_theme_stylesheet = $active_theme->get_stylesheet(); - - // Check if there's a change in themes. - $all_themes = wp_get_themes(); - - // Check if themes changed. - ksort( $all_themes ); - - $themes_signature = ''; - foreach ( $all_themes as $slug => $data ) { - $is_active = ( $slug === $active_theme_stylesheet ); - $themes_signature .= $slug . ',' . - $data->version . ',' . - ( $is_active ? '1' : '0' ) . ';'; - } - - // Check if themes status changed (version or active/inactive). - $themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) ); - - $themes_update_data = array(); - - if ( $themes_changed ) { - // Change in themes, report changes. - - // Update existing themes info. - foreach ( $all_cached_themes->themes as $slug => $data ) { - $is_active = ( $slug === $active_theme_stylesheet ); - - if ( ! isset( $all_themes[ $slug ] ) ) { - // Plugin uninstalled. - $uninstalled_theme_data = $data; - $uninstalled_theme_data['is_active'] = false; - $uninstalled_theme_data['is_uninstalled'] = true; - $themes_update_data[] = $uninstalled_theme_data; - - unset( $all_themes[ $slug ] ); - unset( $all_cached_themes->themes[ $slug ] ); - } else if ( $data['is_active'] !== $is_active || - $data['version'] !== $all_themes[ $slug ]->version - ) { - // Plugin activated or deactivated, or version changed. - - $all_cached_themes->themes[ $slug ]['is_active'] = $is_active; - $all_cached_themes->themes[ $slug ]['version'] = $all_themes[ $slug ]->version; - - $themes_update_data[] = $all_cached_themes->themes[ $slug ]; - } - } - - // Find new themes that weren't yet seen before. - foreach ( $all_themes as $slug => $data ) { - if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) { - $is_active = ( $slug === $active_theme_stylesheet ); - - // New plugin. - $new_plugin = array( - 'slug' => $slug, - 'version' => $data->version, - 'title' => $data->name, - 'is_active' => $is_active, - 'is_uninstalled' => false, - ); - - $themes_update_data[] = $new_plugin; - $all_cached_themes->themes[ $slug ] = $new_plugin; - } - } - - $all_cached_themes->md5 = md5( $themes_signature ); - $all_cached_themes->timestamp = time(); - self::$_accounts->set_option( $option_name, $all_cached_themes, true ); - } - - return $themes_update_data; - } - - /** - * Get site data for API install request. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.2 - * - * @param string[] $override - * @param bool $include_plugins Since 1.1.8 by default include plugin changes. - * @param bool $include_themes Since 1.1.8 by default include plugin changes. - * @param bool $include_blog_data Since 2.3.0 by default include the current blog's data (language, charset, title, and URL). - * - * @return array - */ - private function get_install_data_for_api( - array $override, - $include_plugins = true, - $include_themes = true, - $include_blog_data = true - ) { - if ( $this->is_extensions_tracking_allowed() ) { - if ( ! defined( 'WP_FS__TRACK_PLUGINS' ) || false !== WP_FS__TRACK_PLUGINS ) { - /** - * @since 1.1.8 Also send plugin updates. - */ - if ( $include_plugins && ! isset( $override['plugins'] ) ) { - $plugins = $this->get_plugins_data_for_api(); - if ( ! empty( $plugins ) ) { - $override['plugins'] = $plugins; - } - } - } - - if ( ! defined( 'WP_FS__TRACK_THEMES' ) || false !== WP_FS__TRACK_THEMES ) { - /** - * @since 1.1.8 Also send themes updates. - */ - if ( $include_themes && ! isset( $override['themes'] ) ) { - $themes = $this->get_themes_data_for_api(); - if ( ! empty( $themes ) ) { - $override['themes'] = $themes; - } - } - } - } - - $versions = $this->get_versions(); - - $blog_data = $include_blog_data ? - array( - 'language' => get_bloginfo( 'language' ), - 'charset' => get_bloginfo( 'charset' ), - 'title' => get_bloginfo( 'name' ), - 'url' => get_site_url(), - ) : - array(); - - return array_merge( $versions, $blog_data, array( - 'version' => $this->get_plugin_version(), - 'is_premium' => $this->is_premium(), - // Special params. - 'is_active' => true, - 'is_disconnected' => $this->is_tracking_prohibited(), - 'is_uninstalled' => false, - ), $override ); - } - - /** - * Update installs details. - * - * @todo V1 of multiste network support doesn't support plugin and theme data sending. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string[] string $override - * @param bool $only_diff - * @param bool $include_plugins Since 1.1.8 by default include plugin changes. - * @param bool $include_themes Since 1.1.8 by default include plugin changes. - * - * @return array - */ - private function get_installs_data_for_api( - array $override, - $only_diff = false, - $include_plugins = true, - $include_themes = true - ) { - /** - * @since 1.1.8 Also send plugin updates. - */ -// if ( $include_plugins && ! isset( $override['plugins'] ) ) { -// $plugins = $this->get_plugins_data_for_api(); -// if ( ! empty( $plugins ) ) { -// $override['plugins'] = $plugins; -// } -// } - /** - * @since 1.1.8 Also send themes updates. - */ -// if ( $include_themes && ! isset( $override['themes'] ) ) { -// $themes = $this->get_themes_data_for_api(); -// if ( ! empty( $themes ) ) { -// $override['themes'] = $themes; -// } -// } - - // Common properties. - $versions = $this->get_versions(); - $common = array_merge( $versions, array( - 'version' => $this->get_plugin_version(), - 'is_premium' => $this->is_premium(), - ), $override ); - - - $is_common_diff_for_any_site = false; - $common_diff_union = array(); - - $installs_data = array(); - - $sites = self::get_sites(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) ) { - if ( $install->user_id != $this->_user->id ) { - // Install belongs to a different owner. - continue; - } - - if ( ! $this->is_premium() && $install->is_tracking_prohibited() ) { - // Don't send updates regarding opted-out installs. - continue; - } - - $install_data = $this->get_site_info( $site ); - - $uid = $install_data['uid']; - - unset( $install_data['blog_id'] ); - unset( $install_data['uid'] ); - - $install_data['is_disconnected'] = $install->is_disconnected; - $install_data['is_active'] = $this->is_active_for_site( $blog_id ); - $install_data['is_uninstalled'] = $install->is_uninstalled; - - $common_diff = null; - $is_common_diff = false; - if ( $only_diff ) { - $install_data = $this->get_install_diff_for_api( $install_data, $install, $override ); - $common_diff = $this->get_install_diff_for_api( $common, $install, $override ); - - $is_common_diff = ! empty( $common_diff ); - - if ( $is_common_diff ) { - foreach ( $common_diff as $k => $v ) { - if ( ! isset( $common_diff_union[ $k ] ) ) { - $common_diff_union[ $k ] = $v; - } - } - } - - $is_common_diff_for_any_site = $is_common_diff_for_any_site || $is_common_diff; - } - - if ( ! empty( $install_data ) || $is_common_diff ) { - // Add install ID and site unique ID. - $install_data['id'] = $install->id; - $install_data['uid'] = $uid; - - $installs_data[] = $install_data; - } - } - } - - restore_current_blog(); - - if ( 0 < count( $installs_data ) && ( $is_common_diff_for_any_site || ! $only_diff ) ) { - if ( ! $only_diff ) { - $installs_data[] = $common; - } else if ( ! empty( $common_diff_union ) ) { - $installs_data[] = $common_diff_union; - } - } - - foreach ( $installs_data as &$data ) { - $data = (object) $data; - } - - return $installs_data; - } - - /** - * Compare site actual data to the stored install data and return the differences for an API data sync. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param array $site - * @param FS_Site $install - * @param string[] string $override - * - * @return array - */ - private function get_install_diff_for_api( $site, $install, $override = array() ) { - $diff = array(); - $special = array(); - $special_override = false; - - foreach ( $site as $p => $v ) { - if ( property_exists( $install, $p ) ) { - if ( ( is_bool( $install->{$p} ) || ! empty( $install->{$p} ) ) && - $install->{$p} != $v - ) { - $install->{$p} = $v; - $diff[ $p ] = $v; - } - } else { - $special[ $p ] = $v; - - if ( isset( $override[ $p ] ) || - 'plugins' === $p || - 'themes' === $p - ) { - $special_override = true; - } - } - } - - if ( $special_override || 0 < count( $diff ) ) { - // Add special params only if has at least one - // standard param, or if explicitly requested to - // override a special param or a param which is not exist - // in the install object. - $diff = array_merge( $diff, $special ); - } - - return $diff; - } - - /** - * Update install only if changed. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string[] string $override - * @param bool $flush - * - * @return false|object|string - */ - private function send_install_update( $override = array(), $flush = false ) { - $this->_logger->entrance(); - - $check_properties = $this->get_install_data_for_api( $override ); - - if ( $flush ) { - $params = $check_properties; - } else { - $params = $this->get_install_diff_for_api( $check_properties, $this->_site, $override ); - } - - $keepalive_only_update = false; - if ( empty( $params ) ) { - $keepalive_only_update = $this->should_send_keepalive_update(); - - if ( ! $keepalive_only_update ) { - /** - * There are no updates to send including keepalive. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - return false; - } - } - - if ( ! $keepalive_only_update ) { - /** - * Do not update the last install sync timestamp after a keepalive-only call since there were no actual - * updates sent. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - if ( ! is_multisite() ) { - // Update last install sync timestamp. - $this->set_cron_execution_timestamp( 'install_sync' ); - } - - $params['uid'] = $this->get_anonymous_id(); - } - - $this->set_keepalive_timestamp(); - - // Send updated values to FS. - $site = $this->get_api_site_scope()->call( '/', 'put', $params ); - - if ( ! $keepalive_only_update && $this->is_api_result_entity( $site ) ) { - /** - * Do not clear scheduled sync after a keepalive-only call since there were no actual updates sent. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - if ( ! is_multisite() ) { - // I successfully sent install update, clear scheduled sync if exist. - $this->clear_install_sync_cron(); - } - } - - return $site; - } - - /** - * Update installs only if changed. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string[] string $override - * @param bool $flush - * - * @return false|object|string - */ - private function send_installs_update( $override = array(), $flush = false ) { - $this->_logger->entrance(); - - $installs_data = $this->get_installs_data_for_api( $override, ! $flush ); - - $keepalive_only_update = false; - if ( empty( $installs_data ) ) { - /** - * Pass `true` to use the network level storage since the update is for many installs. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - $keepalive_only_update = $this->should_send_keepalive_update( true ); - - if ( ! $keepalive_only_update ) { - /** - * There are no updates to send including keepalive. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - return false; - } - } - - if ( ! $keepalive_only_update ) { - // Update last install sync timestamp if there were actual updates sent (i.e., not a keepalive-only call). - $this->set_cron_execution_timestamp( 'install_sync' ); - } - - /** - * Pass `true` to use the network level storage since the update is for many installs. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - $this->set_keepalive_timestamp( true ); - - // Send updated values to FS. - $result = $this->get_api_user_scope()->call( "/plugins/{$this->_plugin->id}/installs.json", 'put', $installs_data ); - - if ( ! $keepalive_only_update && $this->is_api_result_object( $result, 'installs' ) ) { - // I successfully sent installs update (there was an actual update sent and it's not just a keepalive-only call), clear scheduled sync if exist. - $this->clear_install_sync_cron(); - } - - return $result; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param bool|null $use_network_level_storage - * - * @return bool - */ - private function should_send_keepalive_update( $use_network_level_storage = null ) { - $keepalive_timestamp = $this->_storage->get( 'keepalive_timestamp', 0, $use_network_level_storage ); - - if ( $keepalive_timestamp < ( time() - WP_FS__TIME_WEEK_IN_SEC ) ) { - // If updated more than 7 days ago, trigger a keepalive and update the time it was triggered. - return true; - } else { - // If updated 7 days ago or less, "flip a coin", if the value is 7 trigger a keepalive and update the last time it was triggered. - return ( 7 == rand( 1, 7 ) ); - } - } - - /** - * Syncs the install owner's data if needed (i.e., if the install owner is different from the loaded user). - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - private function maybe_sync_install_user() { - if ( $this->_user->id == $this->_site->user_id ) { - return; - } - - // Fetch user data and store if found. - $this->sync_user_by_current_install(); - } - - /** - * Update install only if changed. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string[] string $override - * @param bool $flush - */ - private function sync_install( $override = array(), $flush = false ) { - $this->_logger->entrance(); - - $site = $this->send_install_update( $override, $flush ); - - if ( false === $site ) { - // No sync required. - return; - } - - if ( ! $this->is_api_result_entity( $site ) ) { - // Failed to sync, don't update locally. - return; - } - - $this->_site = new FS_Site( $site ); - - $this->_store_site( true ); - } - - /** - * Update install only if changed. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string[] string $override - * @param bool $flush - */ - private function sync_installs( $override = array(), $flush = false ) { - $this->_logger->entrance(); - - $result = $this->send_installs_update( $override, $flush ); - - if ( false === $result ) { - // No sync required. - return; - } - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - // Failed to sync, don't update locally. - return; - } - - $address_to_blog_map = $this->get_address_to_blog_map(); - - foreach ( $result->installs as $install ) { - $this->_site = new FS_Site( $install ); - - $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); - $blog_id = $address_to_blog_map[ $address ]; - - $this->_store_site( true, $blog_id ); - } - } - - /** - * Track install's custom event. - * - * IMPORTANT: - * Custom event tracking is currently only supported for specific clients. - * If you are not one of them, please don't use this method. If you will, - * the API will simply ignore your request based on the plugin ID. - * - * Need custom tracking for your plugin or theme? - * If you are interested in custom event tracking please contact yo@freemius.com - * for further details. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param string $name Event name. - * @param array $properties Associative key/value array with primitive values only - * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. - * @param bool $once If true, event will be tracked only once. IMPORTANT: Still trigger the API call. - * - * @return object|false Event data or FALSE on failure. - * - * @throws \Freemius_InvalidArgumentException - */ - public function track_event( $name, $properties = array(), $process_at = false, $once = false ) { - $this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) ); - - if ( ! $this->is_registered() ) { - return false; - } - - $event = array( 'type' => $name ); - - if ( is_numeric( $process_at ) && $process_at > time() ) { - $event['process_at'] = $process_at; - } - - if ( $once ) { - $event['once'] = true; - } - - if ( ! empty( $properties ) ) { - // Verify associative array values are primitive. - foreach ( $properties as $k => $v ) { - if ( ! is_scalar( $v ) ) { - throw new Freemius_InvalidArgumentException( 'The $properties argument must be an associative key/value array with primitive values only.' ); - } - } - - $event['properties'] = $properties; - } - - $result = $this->get_api_site_scope()->call( 'events.json', 'post', $event ); - - return $this->is_api_error( $result ) ? - false : - $result; - } - - /** - * Track install's custom event only once, but it still triggers the API call. - * - * IMPORTANT: - * Custom event tracking is currently only supported for specific clients. - * If you are not one of them, please don't use this method. If you will, - * the API will simply ignore your request based on the plugin ID. - * - * Need custom tracking for your plugin or theme? - * If you are interested in custom event tracking please contact yo@freemius.com - * for further details. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param string $name Event name. - * @param array $properties Associative key/value array with primitive values only - * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. - * - * @return object|false Event data or FALSE on failure. - * - * @throws \Freemius_InvalidArgumentException - * - * @user Freemius::track_event() - */ - public function track_event_once( $name, $properties = array(), $process_at = false ) { - return $this->track_event( $name, $properties, $process_at, true ); - } - - /** - * Plugin uninstall hook. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param bool $check_user Enforce checking if user have plugins activation privileges. - */ - function _uninstall_plugin_event( $check_user = true ) { - $this->_logger->entrance( 'slug = ' . $this->_slug ); - - if ( $check_user && ! current_user_can( 'activate_plugins' ) ) { - return; - } - - $params = array(); - $uninstall_reason = null; - if ( isset( $this->_storage->uninstall_reason ) ) { - $uninstall_reason = $this->_storage->uninstall_reason; - $params['reason_id'] = $uninstall_reason->id; - $params['reason_info'] = $uninstall_reason->info; - } - - if ( ! $this->is_registered() ) { - // Send anonymous uninstall event only if user submitted a feedback. - if ( isset( $uninstall_reason ) ) { - if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) { - $this->opt_in( false, false, false, false, true ); - } else { - $params['uid'] = $this->get_anonymous_id(); - $this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params ); - } - } - } else { - $params = array_merge( $params, array( - 'is_active' => false, - 'is_uninstalled' => true, - ) ); - - if ( $this->_is_network_active ) { - // Send uninstall event. - $this->send_installs_update( $params ); - } else { - // Send uninstall event. - $this->send_install_update( $params ); - } - } - - // @todo Decide if we want to delete plugin information from db. - } - - /** - * Set the basename of the current product and hook _activate_plugin_event_hook() to the activation action. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.1 - * - * @param string $is_premium - * @param string $caller - * - * @return string - */ - function set_basename( $is_premium, $caller ) { - $basename = plugin_basename( $caller ); - - $current_basename = $is_premium ? - $this->_premium_plugin_basename : - $this->_free_plugin_basename; - - if ( $current_basename == $basename ) { - // Basename value set correctly. - return; - } - - if ( $is_premium ) { - $this->_premium_plugin_basename = $basename; - } else { - $this->_free_plugin_basename = $basename; - } - - $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; - - register_activation_hook( - $plugin_dir . $basename, - array( &$this, '_activate_plugin_event_hook' ) - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * @since 2.2.1 If the context product is in its premium version, use the current module's basename, even if it was renamed. - * - * @return string - */ - function premium_plugin_basename() { - if ( ! isset( $this->_premium_plugin_basename ) ) { - $this->_premium_plugin_basename = $this->is_premium() ? - // The product is premium, so use the current basename. - $this->_plugin_basename : - $this->get_premium_slug() . '/' . basename( $this->_free_plugin_basename ); - } - - return $this->_premium_plugin_basename; - } - - /** - * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - */ - public static function _uninstall_plugin_hook() { - self::_load_required_static(); - - self::$_static_logger->entrance(); - - if ( ! current_user_can( 'activate_plugins' ) ) { - return; - } - - $plugin_file = substr( current_filter(), strlen( 'uninstall_' ) ); - - self::$_static_logger->info( 'plugin = ' . $plugin_file ); - - define( 'WP_FS__UNINSTALL_MODE', true ); - - $fs = self::get_instance_by_file( $plugin_file ); - - if ( is_object( $fs ) ) { - $fs->remove_sdk_reference(); - - self::require_plugin_essentials(); - - if ( is_plugin_active( $fs->_free_plugin_basename ) || - is_plugin_active( $fs->premium_plugin_basename() ) - ) { - // Deleting Free or Premium plugin version while the other version still installed. - return; - } - - $fs->_uninstall_plugin_event(); - - $fs->do_action( 'after_uninstall' ); - } - } - - #---------------------------------------------------------------------------------- - #region Plugin Information - #---------------------------------------------------------------------------------- - - /** - * Load WordPress core plugin.php essential module. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - */ - private static function require_plugin_essentials() { - if ( ! function_exists( 'get_plugins' ) ) { - self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' ); - - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - } - - /** - * Load WordPress core pluggable.php module. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.2 - */ - private static function require_pluggable_essentials() { - if ( ! function_exists( 'wp_get_current_user' ) ) { - require_once ABSPATH . 'wp-includes/pluggable.php'; - } - } - - /** - * Return plugin data. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param bool $reparse_plugin_metadata - * - * @return array - */ - function get_plugin_data( $reparse_plugin_metadata = false ) { - if ( ! isset( $this->_plugin_data ) || $reparse_plugin_metadata ) { - self::require_plugin_essentials(); - - if ( $this->is_plugin() ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.0 When using get_plugin_data() do NOT translate plugin data. - * - * @link https://github.com/Freemius/wordpress-sdk/issues/77 - */ - $plugin_data = get_plugin_data( - $this->_plugin_main_file_path, - false, - false - ); - } else { - $theme_data = wp_get_theme(); - - if ( $this->_plugin_basename !== $theme_data->get_stylesheet() && is_child_theme() ) { - $parent_theme = $theme_data->parent(); - - if ( ( $parent_theme instanceof WP_Theme ) && $this->_plugin_basename === $parent_theme->get_stylesheet() ) { - $theme_data = $parent_theme; - } - } - - $plugin_data = array( - 'Name' => $theme_data->get( 'Name' ), - 'Version' => $theme_data->get( 'Version' ), - 'Author' => $theme_data->get( 'Author' ), - 'Description' => $theme_data->get( 'Description' ), - 'PluginURI' => $theme_data->get( 'ThemeURI' ), - ); - } - - $this->_plugin_data = $plugin_data; - } - - return $this->_plugin_data; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * @since 1.2.2.5 If slug not set load slug by module ID. - * - * @return string Plugin slug. - */ - function get_slug() { - if ( ! isset( $this->_slug ) ) { - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); - $this->_slug = $id_slug_type_path_map[ $this->_module_id ]['slug']; - } - - return $this->_slug; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - * - * @return string - */ - function get_premium_slug() { - return is_object( $this->_plugin ) ? - $this->_plugin->premium_slug : - "{$this->_slug}-premium"; - } - - /** - * Retrieve the desired folder name for the product. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @return string Plugin slug. - */ - function get_target_folder_name() { - return $this->can_use_premium_code() ? - $this->_plugin->premium_slug : - $this->_slug; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @return number Plugin ID. - */ - function get_id() { - return $this->_plugin->id; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - * - * @return number|null Bundle ID. - */ - function get_bundle_id() { - return ( isset( $this->_plugin->bundle_id ) && FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) ) ? - $this->_plugin->bundle_id : - null; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return string|null Bundle public key. - */ - function get_bundle_public_key() { - return isset( $this->_plugin->bundle_public_key ) ? - $this->_plugin->bundle_public_key : - null; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return string Freemius SDK version - */ - function get_sdk_version() { - return $this->version; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return number Parent plugin ID (if parent exist). - */ - function get_parent_id() { - return $this->is_addon() ? - $this->get_parent_instance()->get_id() : - $this->_plugin->id; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return string - */ - function get_usage_tracking_terms_url() { - return $this->apply_filters( - 'usage_tracking_terms_url', - "https://freemius.com/wordpress/usage-tracking/{$this->_plugin->id}/{$this->_slug}/" - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return string - */ - function get_eula_url() { - return $this->apply_filters( - 'eula_url', - "https://freemius.com/terms/{$this->_plugin->id}/{$this->_slug}/" - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @return string Plugin public key. - */ - function get_public_key() { - return $this->_plugin->public_key; - } - - /** - * Will be available only on sandbox mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return mixed Plugin secret key. - */ - function get_secret_key() { - return $this->_plugin->secret_key; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @return bool - */ - function has_secret_key() { - return ! empty( $this->_plugin->secret_key ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string|bool $premium_suffix - * - * @return string - */ - function get_plugin_name( $premium_suffix = false ) { - $this->_logger->entrance(); - - /** - * This `if-else` can be squeezed into a single `if` but I intentionally split it for code readability. - * - * @author Vova Feldman - */ - if ( ! isset( $this->_plugin_name ) ) { - // Name is not yet set. - $this->set_name( $premium_suffix ); - } else if ( - ! empty( $premium_suffix ) && - ( ! is_object( $this->_plugin ) || $this->_plugin->premium_suffix !== $premium_suffix ) - ) { - // Name is already set, but there's a change in the premium suffix. - $this->set_name( $premium_suffix ); - } - - return $this->_plugin_name; - } - - /** - * Calculates and stores the product's name. This helper function was created specifically for get_plugin_name() just to make the code clearer. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.1 - * - * @param string $premium_suffix - */ - private function set_name( $premium_suffix = '' ) { - $plugin_data = $this->get_plugin_data(); - - // Get name. - $this->_plugin_name = $plugin_data['Name']; - - if ( is_string( $premium_suffix ) ) { - $premium_suffix = trim( $premium_suffix ); - - if ( ! empty( $premium_suffix ) ) { - // Check if plugin name contains " (premium)" or a custom suffix and remove it. - $suffix = ( ' ' . strtolower( $premium_suffix ) ); - $suffix_len = strlen( $suffix ); - - if ( strlen( $plugin_data['Name'] ) > $suffix_len && - $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len ) - ) { - $this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len ); - } - } - } - - $this->_logger->departure( 'Name = ' . $this->_plugin_name ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * - * @param bool $reparse_plugin_metadata - * - * @return string - */ - function get_plugin_version( $reparse_plugin_metadata = false ) { - $this->_logger->entrance(); - - $plugin_data = $this->get_plugin_data( $reparse_plugin_metadata ); - - $this->_logger->departure( 'Version = ' . $plugin_data['Version'] ); - - return $this->apply_filters( 'plugin_version', $plugin_data['Version'] ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @return string - */ - function get_plugin_title() { - $this->_logger->entrance(); - - $title = $this->_plugin->title; - - return $this->apply_filters( 'plugin_title', $title ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param bool $lowercase - * - * @return string - */ - function get_module_label( $lowercase = false ) { - $label = $this->is_addon() ? - $this->get_text_inline( 'Add-On', 'addon' ) : - ( $this->is_plugin() ? - $this->get_text_inline( 'Plugin', 'plugin' ) : - $this->get_text_inline( 'Theme', 'theme' ) ); - - if ( $lowercase ) { - $label = strtolower( $label ); - } - - return $label; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return string - */ - function get_plugin_basename() { - if ( ! isset( $this->_plugin_basename ) ) { - if ( $this->is_plugin() ) { - $this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path ); - } else { - $this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) ); - } - } - - return $this->_plugin_basename; - } - - function get_plugin_folder_name() { - $this->_logger->entrance(); - - $plugin_folder = $this->_plugin_basename; - - while ( '.' !== dirname( $plugin_folder ) ) { - $plugin_folder = dirname( $plugin_folder ); - } - - $this->_logger->departure( 'Folder Name = ' . $plugin_folder ); - - return $plugin_folder; - } - - #endregion ------------------------------------------------------------------ - - /* Account - ------------------------------------------------------------------------------------------------------------------*/ - - /** - * Find plugin's slug by plugin's basename. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $plugin_base_name - * - * @return false|string - */ - private static function find_slug_by_basename( $plugin_base_name ) { - $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); - - if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) { - return false; - } - - return $file_slug_map[ $plugin_base_name ]; - } - - /** - * Store the map between the plugin's basename to the slug. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - private function store_file_slug_map() { - $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); - - if ( ! array( $file_slug_map ) ) { - $file_slug_map = array(); - } - - if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) || - $file_slug_map[ $this->_plugin_basename ] !== $this->_slug - ) { - $file_slug_map[ $this->_plugin_basename ] = $this->_slug; - self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); - } - } - - /** - * @return array[number]FS_User - */ - static function get_all_users() { - $users = self::maybe_get_entities_account_option( 'users', array() ); - - if ( ! is_array( $users ) ) { - $users = array(); - } - - return $users; - } - - /** - * @param string $module_type - * @param null|int $blog_id Since 2.0.0 - * - * @return array[string]FS_Site - */ - private static function get_all_sites( - $module_type = WP_FS__MODULE_TYPE_PLUGIN, - $blog_id = null - ) { - $sites = self::get_account_option( 'sites', $module_type, $blog_id ); - - if ( ! is_array( $sites ) ) { - $sites = array(); - } - - return $sites; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @since 1.2.2 - * - * @param string $option_name - * @param string $module_type - * @param null|int $network_level_or_blog_id Since 2.0.0 - * - * @return mixed - */ - private static function get_account_option( $option_name, $module_type = null, $network_level_or_blog_id = null ) { - if ( ! is_null( $module_type ) && WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { - $option_name = $module_type . '_' . $option_name; - } - - return self::maybe_get_entities_account_option( $option_name, array(), $network_level_or_blog_id ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @since 1.2.2 - * - * @param string $option_name - * @param mixed $option_value - * @param bool $store - * @param null|int $network_level_or_blog_id Since 2.0.0 - */ - private function set_account_option( $option_name, $option_value, $store, $network_level_or_blog_id = null ) { - self::set_account_option_by_module( - $this->_module_type, - $option_name, - $option_value, - $store, - $network_level_or_blog_id - ); - } - - /** - * @author Vova Feldman (@svovaf) - * - * @since 1.2.2.7 - * - * @param string $module_type - * @param string $option_name - * @param mixed $option_value - * @param bool $store - * @param null|int $network_level_or_blog_id Since 2.0.0 - */ - private static function set_account_option_by_module( - $module_type, - $option_name, - $option_value, - $store, - $network_level_or_blog_id = null - ) { - if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) { - $option_name = $module_type . '_' . $option_name; - } - - self::$_accounts->set_option( $option_name, $option_value, $store, $network_level_or_blog_id ); - } - - /** - * This method can also return non-entity or non-entities collection option like the `user_id_license_ids_map` option. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @param string $option_name - * @param mixed $default - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). - * - * @return mixed|FS_Plugin[]|FS_User[]|FS_Site[]|FS_Plugin_License[]|FS_Plugin_Plan[]|FS_Plugin_Tag[] - */ - private static function maybe_get_entities_account_option( $option_name, $default = null, $network_level_or_blog_id = null ) { - $option = self::$_accounts->get_option( $option_name, $default, $network_level_or_blog_id ); - - $class_name = ''; - - if ( fs_starts_with( $option_name, WP_FS__MODULE_TYPE_THEME . '_' ) ) { - $option_name = str_replace( WP_FS__MODULE_TYPE_THEME . '_', '', $option_name ); - } - - switch ( $option_name ) { - case 'plugins': - case 'themes': - case 'addons': - $class_name = FS_Plugin::get_class_name(); - break; - case 'users': - $class_name = FS_User::get_class_name(); - break; - case 'sites': - $class_name = FS_Site::get_class_name(); - break; - case 'licenses': - case 'all_licenses': - $class_name = FS_Plugin_License::get_class_name(); - break; - case 'plans': - $class_name = FS_Plugin_Plan::get_class_name(); - break; - case 'updates': - $class_name = FS_Plugin_Tag::get_class_name(); - break; - } - - if ( empty( $class_name ) ) { - return $option; - } - - return fs_get_entities( $option, $class_name ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number|null $module_id - * - * @return FS_Plugin_License[] - */ - private static function get_all_licenses( $module_id = null ) { - $licenses = self::get_account_option( 'all_licenses' ); - - if ( ! is_array( $licenses ) ) { - $licenses = array(); - } - - if ( is_null( $module_id ) ) { - return $licenses; - } - - $licenses = isset( $licenses[ $module_id ] ) ? - $licenses[ $module_id ] : - array(); - - return $licenses; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return array - */ - private static function get_all_licenses_by_module_type() { - $licenses = self::get_account_option( 'all_licenses' ); - - $licenses_by_module_type = array( - WP_FS__MODULE_TYPE_PLUGIN => array(), - WP_FS__MODULE_TYPE_THEME => array() - ); - - if ( ! is_array( $licenses ) ) { - return $licenses_by_module_type; - } - - foreach ( $licenses as $module_id => $module_licenses ) { - $fs = self::get_instance_by_id( $module_id ); - if ( false === $fs ) { - continue; - } - - $licenses_by_module_type[ $fs->_module_type ] = array_merge( $licenses_by_module_type[ $fs->_module_type ], $module_licenses ); - } - - return $licenses_by_module_type; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param number $module_id - * @param number|null $user_id - * - * @return array - */ - private static function get_user_id_license_ids_map( $module_id, $user_id = null ) { - $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); - - if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { - $all_modules_user_id_license_ids_map = array(); - } - - $user_id_license_ids_map = isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ? - $all_modules_user_id_license_ids_map[ $module_id ] : - array(); - - if ( FS_User::is_valid_id( $user_id ) ) { - $user_id_license_ids_map = isset( $user_id_license_ids_map[ $user_id ] ) ? - $user_id_license_ids_map[ $user_id ] : - array(); - } - - return $user_id_license_ids_map; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param array $new_user_id_license_ids_map - * @param number $module_id - * @param number|null $user_id - */ - private static function store_user_id_license_ids_map( $new_user_id_license_ids_map, $module_id, $user_id = null ) { - $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); - if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { - $all_modules_user_id_license_ids_map = array(); - } - - if ( ! isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ) { - $all_modules_user_id_license_ids_map[ $module_id ] = array(); - } - - if ( FS_User::is_valid_id( $user_id ) ) { - $all_modules_user_id_license_ids_map[ $module_id ][ $user_id ] = $new_user_id_license_ids_map; - } else { - $all_modules_user_id_license_ids_map[ $module_id ] = $new_user_id_license_ids_map; - } - - self::$_accounts->set_option( 'user_id_license_ids_map', $all_modules_user_id_license_ids_map, true ); - } - - /** - * Get a collection of the user's linked license IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * - * @return number[] - */ - private function get_user_linked_license_ids( $user_id ) { - return self::get_user_id_license_ids_map( $this->_module_id, $user_id ); - } - - /** - * Override the user's linked license IDs with a new IDs collection. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * @param number[] $license_ids - */ - private function set_user_linked_license_ids( $user_id, array $license_ids ) { - self::store_user_id_license_ids_map( $license_ids, $this->_module_id, $user_id ); - } - - /** - * Link a specified license ID to a given user. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $license_id - * @param number $user_id - */ - private function link_license_2_user( $license_id, $user_id ) { - $license_ids = $this->get_user_linked_license_ids( $user_id ); - - if ( in_array( $license_id, $license_ids ) ) { - // License already linked. - return; - } - - $license_ids[] = $license_id; - - $this->set_user_linked_license_ids( $user_id, $license_ids ); - } - - /** - * @param string|bool $module_type - * - * @return FS_Plugin_Plan[] - */ - private static function get_all_plans( $module_type = false ) { - $plans = self::get_account_option( 'plans', $module_type ); - - if ( ! is_array( $plans ) ) { - $plans = array(); - } - - return $plans; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return FS_Plugin_Tag[] - */ - private static function get_all_updates() { - $updates = self::maybe_get_entities_account_option( 'updates', array() ); - - if ( ! is_array( $updates ) ) { - $updates = array(); - } - - return $updates; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return array|false - */ - private static function get_all_addons() { - $addons = self::maybe_get_entities_account_option( 'addons', array() ); - - if ( ! is_array( $addons ) ) { - $addons = array(); - } - - return $addons; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return number[]|false - */ - private static function get_all_account_addons() { - $addons = self::$_accounts->get_option( 'account_addons', array() ); - - if ( ! is_array( $addons ) ) { - $addons = array(); - } - - return $addons; - } - - /** - * Check if user has connected his account (opted-in). - * - * Note: - * If the user opted-in and opted-out on a later stage, - * this will still return true. If you want to check if the - * user is currently opted-in, use: - * `$fs->is_registered() && $fs->is_tracking_allowed()` - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * @return bool - */ - function is_registered() { - return is_object( $this->_user ); - } - - /** - * Returns TRUE if the user opted-in and didn't disconnect (opt-out). - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool - */ - function is_tracking_allowed() { - return ( is_object( $this->_site ) && $this->_site->is_tracking_allowed() ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - * - * @return bool - */ - function is_bundle_license_auto_activation_enabled() { - return $this->is_addon() ? - ( is_object( $this->_parent ) && $this->_parent->is_bundle_license_auto_activation_enabled() ) : - $this->_is_bundle_license_auto_activation_enabled; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return FS_Plugin - */ - function get_plugin() { - return $this->_plugin; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return FS_User - */ - function get_user() { - return $this->_user; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return FS_Site - */ - function get_site() { - return $this->_site; - } - - /** - * Get plugin add-ons. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @since 1.1.7.3 If not yet loaded, fetch data from the API. - * - * @param bool $flush - * - * @return FS_Plugin[]|false - */ - function get_addons( $flush = false ) { - $this->_logger->entrance(); - - if ( ! $this->_has_addons ) { - return false; - } - - $addons = $this->sync_addons( $flush ); - - return ( ! is_array( $addons ) || empty( $addons ) ) ? - false : - $addons; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return number[]|false - */ - function get_account_addons() { - $this->_logger->entrance(); - - $addons = self::get_all_account_addons(); - - if ( ! is_array( $addons ) || - ! isset( $addons[ $this->_plugin->id ] ) || - ! is_array( $addons[ $this->_plugin->id ] ) || - 0 === count( $addons[ $this->_plugin->id ] ) - ) { - return false; - } - - return $addons[ $this->_plugin->id ]; - } - - /** - * Check if user has any - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @return bool - */ - function has_account_addons() { - $addons = $this->get_account_addons(); - - return is_array( $addons ) && ( 0 < count( $addons ) ); - } - - - /** - * Get add-on by ID (from local data). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number $id - * - * @return FS_Plugin|false - */ - function get_addon( $id ) { - $this->_logger->entrance(); - - $addons = $this->get_addons(); - - if ( is_array( $addons ) ) { - foreach ( $addons as $addon ) { - if ( $id == $addon->id ) { - return $addon; - } - } - } - - return false; - } - - /** - * Get add-on by slug (from local data). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string $slug - * - * @param bool $flush - * - * @return FS_Plugin|false - */ - function get_addon_by_slug( $slug, $flush = false ) { - $this->_logger->entrance(); - - $addons = $this->get_addons( $flush ); - - if ( is_array( $addons ) ) { - foreach ( $addons as $addon ) { - if ( $slug === $addon->slug ) { - return $addon; - } - } - } - - return false; - } - - /** - * @var array { - * @key number Add-on ID. - * @val object[] The add-on's plans and prices object. - * } - */ - private $plans_and_pricing_by_addon_id; - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return array { - * @key number Add-on ID. - * @val object[] The add-on's plans and prices object. - * } - */ - function _get_addons_plans_and_pricing_map_by_id() { - if ( ! isset( $this->plans_and_pricing_by_addon_id ) ) { - $result = $this->get_api_plugin_scope()->get( $this->add_show_pending( "/addons/pricing.json?type=visible" ) ); - - $plans_and_pricing_by_addon_id = array(); - if ( $this->is_api_result_object( $result, 'addons' ) ) { - foreach ( $result->addons as $addon ) { - $plans_and_pricing_by_addon_id[ $addon->id ] = $addon->plans; - } - } - - $this->plans_and_pricing_by_addon_id = $plans_and_pricing_by_addon_id; - } - - return $this->plans_and_pricing_by_addon_id; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param number $addon_id - * @param bool $is_installed - * - * @return array - */ - function _get_addon_info( $addon_id, $is_installed ) { - $addon = $this->get_addon( $addon_id ); - - if ( ! is_object( $addon ) ) { - // Unexpected call. - return array(); - } - - $slug = $addon->slug; - - $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); - - if ( ! fs_is_network_admin() ) { - // Get blog-level activated installations. - $sites = self::maybe_get_entities_account_option( 'sites', array() ); - } else { - $sites = null; - - if ( $this->is_addon_activated( $addon_id ) && - $this->get_addon_instance( $addon_id )->is_network_active() - ) { - if ( FS_Site::is_valid_id( $addon_storage->network_install_blog_id ) ) { - // Get network-level activated installations. - $sites = self::maybe_get_entities_account_option( - 'sites', - array(), - $addon_storage->network_install_blog_id - ); - } - } - } - - $addon_info = array( - 'is_connected' => false, - 'slug' => $slug, - 'title' => $addon->title, - 'is_whitelabeled' => $addon_storage->is_whitelabeled - ); - - if ( ! $is_installed ) { - $plans_and_pricing_by_addon_id = $this->_get_addons_plans_and_pricing_map_by_id(); - - if ( isset( $plans_and_pricing_by_addon_id[ $addon_id ] ) ) { - $has_paid_plan = false; - $plans = $plans_and_pricing_by_addon_id[ $addon_id ]; - - if ( is_array( $plans ) && count( $plans ) > 0 ) { - foreach ( $plans as $plan ) { - if ( isset( $plan->pricing ) && - is_array( $plan->pricing ) && - count( $plan->pricing ) > 0 - ) { - $has_paid_plan = true; - break; - } - } - } - - $addon_info['has_paid_plan'] = $has_paid_plan; - } - } - - if ( ! is_array( $sites ) || ! isset( $sites[ $slug ] ) ) { - return $addon_info; - } - - $site = $sites[ $slug ]; - - $addon_info['is_connected'] = ( - ( $addon->parent_plugin_id == $this->get_id() ) && - is_object( $site ) && - FS_Site::is_valid_id( $site->id ) && - FS_User::is_valid_id( $site->user_id ) && - FS_Plugin_Plan::is_valid_id( $site->plan_id ) - ); - - if ( $addon_info['is_connected'] && $is_installed ) { - return $addon_info; - } - - $addon_info['site'] = $site; - - $plugins_data = self::maybe_get_entities_account_option( WP_FS__MODULE_TYPE_PLUGIN . 's', array() ); - if ( isset( $plugins_data[ $slug ] ) ) { - $plugin_data = $plugins_data[ $slug ]; - - $addon_info['version'] = $plugin_data->version; - } - - $all_plans = self::maybe_get_entities_account_option( 'plans', array() ); - if ( isset( $all_plans[ $slug ] ) ) { - $plans = $all_plans[ $slug ]; - - foreach ( $plans as $plan ) { - if ( $site->plan_id == Freemius::_decrypt( $plan->id ) ) { - $addon_info['plan_name'] = Freemius::_decrypt( $plan->name ); - $addon_info['plan_title'] = Freemius::_decrypt( $plan->title ); - break; - } - } - } - - $licenses = self::maybe_get_entities_account_option( 'all_licenses', array() ); - if ( is_array( $licenses ) && isset( $licenses[ $addon_id ] ) ) { - foreach ( $licenses[ $addon_id ] as $license ) { - if ( $license->id == $site->license_id ) { - $addon_info['license'] = $license; - break; - } - } - } - - if ( isset( $addon_info['license'] ) ) { - if ( isset( $addon_storage->subscriptions ) && - ! empty( $addon_storage->subscriptions ) - ) { - $addon_subscriptions = fs_get_entities( $addon_storage->subscriptions, FS_Subscription::get_class_name() ); - - foreach ( $addon_subscriptions as $subscription ) { - if ( $subscription->license_id == $site->license_id ) { - $addon_info['subscription'] = $subscription; - break; - } - } - } - } - - return $addon_info; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * - * @return FS_User - */ - static function _get_user_by_id( $user_id ) { - self::$_static_logger->entrance( "user_id = {$user_id}" ); - - $users = self::get_all_users(); - - if ( is_array( $users ) ) { - if ( isset( $users[ $user_id ] ) && - $users[ $user_id ] instanceof FS_User && - $user_id == $users[ $user_id ]->id - ) { - return $users[ $user_id ]; - } - - // If user wasn't found by the key, iterate over all the users collection. - foreach ( $users as $user ) { - /** - * @var FS_User $user - */ - if ( $user_id == $user->id ) { - return $user; - } - } - } - - return null; - } - - /** - * Checks if a Freemius user_id is associated with a super-admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * - * @return bool - */ - private static function is_super_admin( $user_id ) { - $is_super_admin = false; - - $user = self::_get_user_by_id( $user_id ); - - if ( $user instanceof FS_User && ! empty( $user->email ) ) { - self::require_pluggable_essentials(); - - $wp_user = get_user_by( 'email', $user->email ); - - if ( $wp_user instanceof WP_User ) { - $super_admins = get_super_admins(); - $is_super_admin = ( is_array( $super_admins ) && in_array( $wp_user->user_login, $super_admins ) ); - } - } - - return $is_super_admin; - } - - #---------------------------------------------------------------------------------- - #region Plans & Licensing - #---------------------------------------------------------------------------------- - - /** - * Check if running premium plugin code. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return bool - */ - function is_premium() { - /** - * `$this->_plugin` will be `false` when `is_activation_mode` calls this method directly from the - * `register_constructor_hooks` method. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - return is_object( $this->_plugin ) ? - $this->_plugin->is_premium : - false; - } - - /** - * Get site's plan ID. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @return number - */ - function get_plan_id() { - return $this->_site->plan_id; - } - - /** - * Get site's plan title. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @return string - */ - function get_plan_title() { - $plan = $this->get_plan(); - - return is_object( $plan ) ? $plan->title : 'PLAN_TITLE'; - } - - /** - * Get site's plan name. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function get_plan_name() { - $plan = $this->get_plan(); - - return is_object( $plan ) ? $plan->name : 'PLAN_NAME'; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return FS_Plugin_Plan|false - */ - function get_plan() { - if ( ! is_object( $this->_site ) ) { - return false; - } - - return FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) ? - $this->_get_plan_by_id( $this->_site->plan_id ) : - false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return bool - */ - function is_trial() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() || ! is_object( $this->_site ) ) { - return false; - } - - return $this->_site->is_trial(); - } - - /** - * Check if currently in a trial with payment method (credit card or paypal). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @return bool - */ - function is_paid_trial() { - $this->_logger->entrance(); - - if ( ! $this->is_trial() ) { - return false; - } - - if ( ! $this->has_active_valid_license() ) { - return false; - } - - if ( $this->_site->trial_plan_id != $this->_license->plan_id ) { - return false; - } - - /** - * @var FS_Subscription $subscription - */ - $subscription = $this->_get_subscription( $this->_license->id ); - - return ( is_object( $subscription ) && $subscription->is_active() ); - } - - /** - * Check if trial already utilized. - * - * @since 1.0.9 - * - * @return bool - */ - function is_trial_utilized() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return false; - } - - return $this->_site->is_trial_utilized(); - } - - /** - * Get trial plan information (if in trial). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool|FS_Plugin_Plan - */ - function get_trial_plan() { - $this->_logger->entrance(); - - if ( ! $this->is_trial() ) { - return false; - } - - // Try to load plan from local cache. - $trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id ); - - if ( ! is_object( $trial_plan ) ) { - $trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id ); - - /** - * If managed to fetch the plan, add it to the plans collection. - */ - if ( $trial_plan instanceof FS_Plugin_Plan ) { - if ( ! is_array( $this->_plans ) ) { - $this->_plans = array(); - } - - $this->_plans[] = $trial_plan; - $this->_store_plans(); - } - } - - if ( $trial_plan instanceof FS_Plugin_Plan ) { - return $trial_plan; - } - - /** - * If for some reason failed to get the trial plan, fallback to a dummy name and title. - */ - $trial_plan = new FS_Plugin_Plan(); - $trial_plan->id = $this->_site->trial_plan_id; - $trial_plan->name = 'pro'; - $trial_plan->title = 'Pro'; - - return $trial_plan; - } - - /** - * Check if the user has an activate, non-expired license on current plugin's install. - * - * @since 1.0.9 - * - * @return bool - */ - function is_paying() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return false; - } - - if ( ! $this->has_paid_plan() ) { - return false; - } - - return ( - ! $this->is_trial() && - 'free' !== $this->get_plan_name() && - $this->has_active_valid_license() - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return bool - */ - function is_free_plan() { - if ( ! $this->is_registered() ) { - return true; - } - - if ( ! $this->has_paid_plan() ) { - return true; - } - - return ( - 'free' === $this->get_plan_name() || - ! $this->has_features_enabled_license() - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return bool - */ - function _has_premium_license() { - $this->_logger->entrance(); - - $premium_license = $this->_get_available_premium_license(); - - return ( false !== $premium_license ); - } - - /** - * Check if user has any licenses associated with the plugin (including expired or blocking). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param bool $including_foreign - * - * @return bool - */ - function has_any_license( $including_foreign = true ) { - if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) { - return false; - } - - if ( $including_foreign ) { - return true; - } - - foreach ( $this->_licenses as $license ) { - if ( $this->_user->id == $license->user_id ) { - return true; - } - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool|null $is_localhost - * - * @return FS_Plugin_License|false - */ - function _get_available_premium_license( $is_localhost = null ) { - $this->_logger->entrance(); - - $licenses = $this->get_available_premium_licenses( $is_localhost ); - if ( ! empty( $licenses ) ) { - return $licenses[0]; - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool|null $is_localhost - * - * @return FS_Plugin_License[] - */ - function get_available_premium_licenses( $is_localhost = null ) { - $this->_logger->entrance(); - - $licenses = array(); - if ( ! $this->has_paid_plan() ) { - return $licenses; - } - - if ( is_array( $this->_licenses ) ) { - foreach ( $this->_licenses as $license ) { - if ( ! $license->can_activate( $is_localhost ) ) { - continue; - } - - $licenses[] = $license; - } - } - - return $licenses; - } - - /** - * Sync local plugin plans with remote server. - * - * IMPORTANT: If for some reason a site is associated with deleted plan, we'll preserve the plan's information and append it as the last plan. This means that if plan is deleted, the is_plan() method will ALWAYS return true for any given argument (it becomes the most inclusive plan). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return FS_Plugin_Plan[]|object - */ - function _sync_plans() { - $plans = $this->_fetch_plugin_plans(); - - if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { - $plans_map = array(); - foreach ( $plans as $plan ) { - $plans_map[ $plan->id ] = true; - } - - $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); - - foreach ( $plans_ids_to_keep as $plan_id ) { - if ( isset( $plans_map[ $plan_id ] ) ) { - continue; - } - - $missing_plan = self::_get_plan_by_id( $plan_id ); - - if ( is_object( $missing_plan ) ) { - $plans[] = $missing_plan; - } - } - - $this->_plans = $plans; - $this->_store_plans(); - } - - $this->do_action( 'after_plans_sync', $plans ); - - return $this->_plans; - } - - /** - * Check if specified plan exists locally. If not, fetch it and store it. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $plan_id - * - * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. - */ - private function sync_plan_if_not_exist( $plan_id ) { - $plan = self::_get_plan_by_id( $plan_id ); - - if ( is_object( $plan ) ) { - // Plan already exists. - return $plan; - } - - $plan = $this->fetch_plan_by_id( $plan_id ); - - if ( $plan instanceof FS_Plugin_Plan ) { - $this->_plans[] = $plan; - $this->_store_plans(); - - return $plan; - } - - return $plan; - } - - /** - * Check if specified license exists locally. If not, fetch it and store it. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $license_id - * @param string $license_key - * - * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. - */ - private function sync_license_if_not_exist( $license_id, $license_key ) { - $license = $this->_get_license_by_id( $license_id ); - - if ( is_object( $license ) ) { - // License already exists. - return $license; - } - - $license = $this->fetch_license_by_key( $license_id, $license_key ); - - if ( $license instanceof FS_Plugin_License ) { - $this->_licenses[] = $license; - - $this->set_license( $license ); - - $this->_store_licenses(); - - return $license; - } - - return $license; - } - - /** - * Get a collection of unique plan IDs that are associated with any installs in the network. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return number[] - */ - private function get_plans_ids_associated_with_installs() { - if ( ! is_multisite() ) { - if ( ! is_object( $this->_site ) || - ! FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) - ) { - return array(); - } - - return array( $this->_site->plan_id ); - } - - $plan_ids = array(); - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( ! is_object( $install ) || - ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) - ) { - continue; - } - - $plan_ids[ $install->plan_id ] = true; - } - - return array_keys( $plan_ids ); - } - - /** - * Get a collection of unique license IDs that are associated with any installs in the network. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return number[] - */ - private function get_license_ids_associated_with_installs() { - if ( ! $this->_is_network_active ) { - if ( ! is_object( $this->_site ) || - ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) - ) { - return array(); - } - - return array( $this->_site->license_id ); - } - - $license_ids = array(); - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( ! is_object( $install ) || - ! FS_Plugin_License::is_valid_id( $install->license_id ) - ) { - continue; - } - - $license_ids[ $install->license_id ] = true; - } - - return array_keys( $license_ids ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param number $id - * - * @return FS_Plugin_Plan|false - */ - function _get_plan_by_id( $id ) { - $this->_logger->entrance(); - - if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { - $this->_sync_plans(); - } - - foreach ( $this->_plans as $plan ) { - if ( $id == $plan->id ) { - return $plan; - } - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.8.1 - * - * @param string $name - * - * @return FS_Plugin_Plan|false - */ - private function get_plan_by_name( $name ) { - $this->_logger->entrance(); - - if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { - $this->_sync_plans(); - } - - foreach ( $this->_plans as $plan ) { - if ( $name == $plan->name ) { - return $plan; - } - } - - return false; - } - - /** - * Sync local licenses with remote server. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param number|bool $site_license_id - * @param number|null $blog_id - * - * @return FS_Plugin_License[]|object - */ - function _sync_licenses( $site_license_id = false, $blog_id = null ) { - $this->_logger->entrance(); - - $is_network_admin = fs_is_network_admin(); - - if ( $is_network_admin && is_null( $blog_id ) ) { - $all_licenses = self::get_all_licenses( $this->_module_id ); - } else { - $all_licenses = $this->get_user_licenses( $this->_user->id ); - } - - $foreign_licenses = $this->get_foreign_licenses_info( $all_licenses, $site_license_id ); - - $all_licenses_map = array(); - foreach ( $all_licenses as $license ) { - $all_licenses_map[ $license->id ] = true; - } - - $licenses = $this->_fetch_licenses( false, $site_license_id, $foreign_licenses, $blog_id ); - - if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { - $licenses_map = array(); - foreach ( $licenses as $license ) { - $licenses_map[ $license->id ] = true; - } - -// $license_ids_to_keep = $this->get_license_ids_associated_with_installs(); -// foreach ( $license_ids_to_keep as $license_id ) { -// if ( isset( $licenses_map[ $license_id ] ) ) { -// continue; -// } -// -// $missing_license = self::_get_license_by_id( $license_id, false ); -// if ( is_object( $missing_license ) ) { -// $licenses[] = $missing_license; -// $licenses_map[ $missing_license->id ] = true; -// } -// } - - $user_license_ids = $this->get_user_linked_license_ids( $this->_user->id ); - - foreach ( $user_license_ids as $key => $license_id ) { - if ( ! isset( $licenses_map[ $license_id ] ) ) { - // Remove access to licenses that no longer exist. - unset( $user_license_ids[ $key ] ); - } - } - - if ( ! empty( $user_license_ids ) ) { - foreach ( $licenses_map as $license_id => $value ) { - if ( ! isset( $all_licenses_map[ $license_id ] ) ) { - // Associate new licenses with the user who triggered the license syncing. - $user_license_ids[] = $license_id; - } - } - - $user_license_ids = array_unique( $user_license_ids ); - } else { - $user_license_ids = array_keys( $licenses_map ); - } - - if ( ! $is_network_admin || ! is_null( $blog_id ) ) { - $user_licenses = array(); - foreach ( $licenses as $license ) { - if ( ! in_array( $license->id, $user_license_ids ) ) { - continue; - } - - $user_licenses[] = $license; - } - - $this->_licenses = $user_licenses; - } else { - $this->_licenses = $licenses; - } - - $this->set_user_linked_license_ids( $this->_user->id, $user_license_ids ); - - $this->_store_licenses( true, $this->_module_id, $licenses ); - } - - // Update current license. - if ( is_object( $this->_license ) ) { - $license = $this->_get_license_by_id( $this->_license->id ); - - if ( is_object( $license ) ) { - /** - * `$license` can be `false` in case a user change action has just been completed and this method - * has synced the `$this->_licenses` collection for the new user. In this case, the - * `$this->_licenses` collection may have only the newly activated license that is associated with - * the new user. `set_license` will eventually be called in the same request by the logic that - * follows outside this method which will detect that the install's license has been updated, and - * then `_update_site_license` will be called which in turn will call `set_license`. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - $this->set_license( $license ); - } - } - - return $this->_licenses; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param number $id - * @param bool $sync_licenses - * - * @return FS_Plugin_License|false - */ - function _get_license_by_id( $id, $sync_licenses = true ) { - $this->_logger->entrance(); - - if ( ! FS_Plugin_License::is_valid_id( $id ) ) { - return false; - } - - /** - * When running from the network level admin and opted-in from the network, - * check if the license exists in the network user licenses collection. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - if ( fs_is_network_admin() && - $this->is_network_registered() && - ( ! is_object( $this->_user ) || $this->_storage->network_user_id != $this->_user->id ) - ) { - $licenses = $this->get_user_licenses( $this->_storage->network_user_id ); - - foreach ( $licenses as $license ) { - if ( $id == $license->id ) { - return $license; - } - } - } - - if ( ! $this->has_any_license() && $sync_licenses ) { - $this->_sync_licenses( $id ); - } - - if ( is_array( $this->_licenses ) ) { - foreach ( $this->_licenses as $license ) { - if ( $id == $license->id ) { - return $license; - } - } - } - - return false; - } - - /** - * Get license by ID. Unlike _get_license_by_id(), this method only checks the local storage and return any license, whether it's associated with the current context user/install or not. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $id - * - * @return FS_Plugin_License - */ - private function get_license_by_id( $id ) { - $licenses = self::get_all_licenses( $this->_module_id ); - - if ( is_array( $licenses ) && ! empty( $licenses ) ) { - foreach ( $licenses as $license ) { - if ( $id == $license->id ) { - return $license; - } - } - } - - return null; - } - - /** - * Synchronize the site's context license by fetching the license form the API and updating the local data with it. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return \FS_Plugin_License|mixed - */ - private function sync_site_license() { - $api = $this->get_api_user_scope(); - - $result = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . urlencode( $this->_license->secret_key ), true ); - - if ( ! $this->is_api_result_entity( $result ) ) { - return $result; - } - - $license = $this->_update_site_license( new FS_Plugin_License( $result ) ); - $this->_store_licenses(); - - return $license; - } - - /** - * Get all user's available licenses for the current module. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * - * @return FS_Plugin_License[] - */ - private function get_user_licenses( $user_id ) { - $all_licenses = self::get_all_licenses( $this->_module_id ); - if ( empty( $all_licenses ) ) { - return array(); - } - - $user_license_ids = $this->get_user_linked_license_ids( $user_id ); - if ( empty( $user_license_ids ) ) { - return array(); - } - - $licenses = array(); - foreach ( $all_licenses as $license ) { - if ( in_array( $license->id, $user_license_ids ) ) { - $licenses[] = $license; - } - } - - return $licenses; - } - - /** - * Checks if the context license is network activated except on the given blog ID. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $except_blog_id - * - * @return bool - */ - private function is_license_network_active( $except_blog_id = 0 ) { - $this->_logger->entrance(); - - if ( ! is_object( $this->_license ) ) { - return false; - } - - $sites = self::get_sites(); - - if ( $this->_license->total_activations() < ( count( $sites ) - 1 ) ) { - // There are more sites than the number of activations, so license cannot be network activated. - return false; - } - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( $except_blog_id == $blog_id ) { - // Skip excluded blog. - continue; - } - - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) && $install->license_id != $this->_license->id ) { - return false; - } - } - - return true; - } - - /** - * Checks if license can be activated on all the network sites (opted-in or skipped) that are not yet associated with a license. If possible, try to make the activation, if not return false. - * - * Notice: On success, this method will also update the license activations counters (without updating the license in the storage). - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * @param \FS_Plugin_License $license - * - * @return bool - */ - private function try_activate_license_on_network( FS_User $user, FS_Plugin_License $license ) { - $this->_logger->entrance(); - - $result = $this->can_activate_license_on_network( $license ); - - if ( false === $result ) { - return false; - } - - $installs_without_license = $result['installs']; - if ( ! empty( $installs_without_license ) ) { - $this->activate_license_on_many_installs( $user, $license->secret_key, $installs_without_license ); - } - - $disconnected_site_ids = $result['sites']; - if ( ! empty( $disconnected_site_ids ) ) { - $this->activate_license_on_many_sites( $user, $license->secret_key, $disconnected_site_ids ); - } - - $this->link_license_2_user( $license->id, $user->id ); - - // Sync license after activations. - $license->activated += $result['production_count']; - $license->activated_local += $result['localhost_count']; - -// $this->_store_licenses() - - return true; - } - - /** - * Checks if the given license can be activated on the whole network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_Plugin_License $license - * - * @return false|array { - * @type array[int]FS_Site $installs Blog ID to install map. - * @type int[] $sites Non-connected blog IDs. - * @type int $production_count Production sites count. - * @type int $localhost_count Production sites count. - * } - */ - private function can_activate_license_on_network( FS_Plugin_License $license ) { - $sites = self::get_sites(); - - $production_count = 0; - $localhost_count = 0; - - $installs_without_license = array(); - $disconnected_site_ids = array(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) ) { - if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { - // License already activated on the install. - continue; - } - - $url = $install->url; - - $installs_without_license[ $blog_id ] = $install; - } else { - $url = is_object( $site ) ? - $site->siteurl : - get_site_url( $blog_id ); - - $disconnected_site_ids[] = $blog_id; - } - - if ( FS_Site::is_localhost_by_address( $url ) ) { - $localhost_count ++; - } else { - $production_count ++; - } - } - - if ( ! $license->can_activate_bulk( $production_count, $localhost_count ) ) { - return false; - } - - return array( - 'installs' => $installs_without_license, - 'sites' => $disconnected_site_ids, - 'production_count' => $production_count, - 'localhost_count' => $localhost_count, - ); - } - - /** - * Activate a given license on a collection of installs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * @param string $license_key - * @param array $blog_2_install_map { - * @key int Blog ID. - * @value FS_Site Blog's associated install. - * } - * - * @return mixed|true - */ - private function activate_license_on_many_installs( - FS_User $user, - $license_key, - array $blog_2_install_map - ) { - $params = array( - array( 'license_key' => $this->apply_filters( 'license_key', $license_key ) ) - ); - - $install_2_blog_map = array(); - foreach ( $blog_2_install_map as $blog_id => $install ) { - $params[] = array( 'id' => $install->id ); - - $install_2_blog_map[ $install->id ] = $blog_id; - } - - $result = $this->get_api_user_scope_by_user( $user )->call( - "plugins/{$this->_plugin->id}/installs.json", - 'PUT', - $params - ); - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - return $result; - } - - foreach ( $result->installs as $r_install ) { - $install = new FS_Site( $r_install ); - $install->is_disconnected = false; - - // Update install. - $this->_store_site( - true, - $install_2_blog_map[ $r_install->id ], - $install - ); - } - - return true; - } - - /** - * Activate a given license on a collection of blogs/sites that are not yet opted-in. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @param \FS_User $user - * @param string $license_key - * - * @return true|mixed True if successful, otherwise, the API result. - */ - private function activate_license_on_site( FS_User $user, $license_key ) { - return $this->activate_license_on_many_sites( $user, $license_key ); - } - - /** - * Activate a given license on a collection of blogs/sites that are not yet opted-in. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * @param string $license_key - * @param int[] $site_ids - * - * @return true|mixed True if successful, otherwise, the API result. - */ - private function activate_license_on_many_sites( - FS_User $user, - $license_key, - array $site_ids = array() - ) { - $sites = array(); - foreach ( $site_ids as $site_id ) { - $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); - } - - // Install the plugin. - $result = $this->create_installs_with_user( - $user, - $license_key, - false, - $sites, - false, - true - ); - - if ( ! $this->is_api_result_entity( $result ) && - ! $this->is_api_result_object( $result, 'installs' ) - ) { - return $result; - } - - $installs = array(); - - if ( $this->is_api_result_entity( $result ) ) { - $install = new FS_Site( $result ); - - $this->_user = $user; - - $this->_store_site( true, null, $install ); - - $this->_site = $install; - - $this->reset_anonymous_mode(); - } else { - foreach ( $result->installs as $install ) { - $installs[] = new FS_Site( $install ); - } - - // Map site addresses to their blog IDs. - $address_to_blog_map = $this->get_address_to_blog_map(); - - $first_blog_id = null; - - foreach ( $installs as $install ) { - $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); - $blog_id = $address_to_blog_map[ $address ]; - - $this->_store_site( true, $blog_id, $install ); - - $this->reset_anonymous_mode( $blog_id ); - - if ( is_null( $first_blog_id ) ) { - $first_blog_id = $blog_id; - } - } - - if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { - $this->_storage->network_install_blog_id = $first_blog_id; - } - } - - return true; - } - - /** - * Sync site's license with user licenses. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param FS_Plugin_License|null $new_license - * - * @return FS_Plugin_License|null - */ - function _update_site_license( $new_license ) { - $this->_logger->entrance(); - - /** - * In case this call will be removed in the future, the `_sync_licenses()` method needs to be updated - * accordingly so that it will also handle the case when an ownership change is done via license - * activation. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - $this->set_license( $new_license ); - - if ( ! is_object( $new_license ) ) { - $this->_site->license_id = null; - $this->_sync_site_subscription( null ); - - return $this->_license; - } - - $this->_site->license_id = $this->_license->id; - - if ( ! is_array( $this->_licenses ) ) { - $this->_licenses = array(); - } - - $is_license_found = false; - for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { - if ( $new_license->id == $this->_licenses[ $i ]->id ) { - $this->_licenses[ $i ] = $new_license; - - $is_license_found = true; - break; - } - } - - // If new license just append. - if ( ! $is_license_found ) { - $this->_licenses[] = $new_license; - } - - $this->_sync_site_subscription( $new_license ); - - return $this->_license; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @param \FS_Plugin_License $license - */ - private function set_license( FS_Plugin_License $license = null ) { - $this->_license = $license; - - $this->maybe_update_whitelabel_flag( $license ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @param FS_Plugin_License $license - */ - private function maybe_update_whitelabel_flag( $license ) { - $is_whitelabeled = isset( $this->_storage->is_whitelabeled ) ? - $this->_storage->is_whitelabeled : - false; - - if ( is_object( $license ) ) { - $license_user = self::_get_user_by_id( $license->user_id ); - - if ( ! is_object( $license_user ) ) { - // If foreign license, do not update the `is_whitelabeled` flag. - return; - } - - if ( $this->is_addon() ) { - /** - * Store the last license data to the parent's storage since it's needed only when showing the - * "Start Debug" dialog which is triggered from the "Account" page. This way, there's no need to - * iterate over the add-ons just to get the last license data. - */ - $this->get_parent_instance()->store_last_activated_license_data( $license, $license_user ); - } else { - $this->store_last_activated_license_data( $license ); - } - - if ( $license->is_whitelabeled ) { - // Activated a developer license, data should be hidden. - $is_whitelabeled = true; - } else if ( $this->is_registered() && $this->_user->id == $license->user_id ) { - // The account owner activated a regular license key, no need to hide the data. - $is_whitelabeled = false; - } - } - - $this->_storage->is_whitelabeled = $is_whitelabeled; - - // Reset the whitelabeled status after update. - $this->is_whitelabeled = null; - if ( $this->is_addon() ) { - $parent_fs = $this->get_parent_instance(); - - if ( is_object( $parent_fs ) ) { - $parent_fs->is_whitelabeled = null; - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @param FS_Plugin_License $license - * @param FS_User $license_user - */ - private function store_last_activated_license_data( FS_Plugin_License $license, $license_user = null ) { - if ( ! is_object( $license_user ) ) { - $this->_storage->last_license_key = md5( $license->secret_key ); - $this->_storage->last_license_user_id = null; - } else { - $this->_storage->last_license_user_key = md5( $license_user->secret_key ); - $this->_storage->last_license_user_id = $license_user->id; - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @param bool $ignore_data_debug_mode - * - * @return bool - */ - function is_whitelabeled_by_flag( $ignore_data_debug_mode = false ) { - if ( true !== $this->_storage->is_whitelabeled ) { - return false; - } else if ( $ignore_data_debug_mode ) { - return true; - } - - $fs = $this->is_addon() ? - $this->get_parent_instance() : - $this; - - return ! $fs->is_data_debug_mode(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @return number - */ - function get_last_license_user_id() { - return ( FS_User::is_valid_id( $this->_storage->last_license_user_id ) ) ? - $this->_storage->last_license_user_id : - null; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @param int $blog_id - * @param bool $ignore_data_debug_mode - * - * @return bool - */ - function is_whitelabeled( $ignore_data_debug_mode = false, $blog_id = null ) { - if ( ! is_null( $blog_id ) ) { - $this->switch_to_blog( $blog_id ); - } - - if ( ! is_null( $this->is_whitelabeled ) ) { - $is_whitelabeled = $this->is_whitelabeled; - } else { - $is_whitelabeled = false; - - $is_whitelabeled_flag = $this->is_whitelabeled_by_flag( true ); - - if ( ! $this->has_addons() ) { - $is_whitelabeled = $is_whitelabeled_flag; - } else if ( $is_whitelabeled_flag ) { - $is_whitelabeled = true; - } else { - $addon_ids = $this->get_updated_account_addons(); - $installed_addons = $this->get_installed_addons(); - foreach ( $installed_addons as $fs_addon ) { - $addon_ids[] = $fs_addon->get_id(); - } - - if ( ! empty( $addon_ids ) ) { - $addon_ids = array_unique( $addon_ids ); - - $is_network_level = ( - fs_is_network_admin() && - $this->is_network_active() - ); - - foreach ( $addon_ids as $addon_id ) { - $addon = $this->get_addon( $addon_id ); - - if ( ! is_object( $addon ) ) { - continue; - } - - $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); - $fs_addon = $this->is_addon_activated( $addon_id ) ? - self::get_addon_instance( $addon_id ) : - null; - - $was_addon_network_activated = false; - - if ( is_object( $fs_addon ) ) { - $was_addon_network_activated = $fs_addon->is_network_active(); - } else if ( $is_network_level ) { - $was_addon_network_activated = $addon_storage->get( 'was_plugin_loaded', false, true ); - } - - $network_delegated_connection = ( - $was_addon_network_activated && - $addon_storage->get( 'is_delegated_connection', false, true ) - ); - - if ( - $is_network_level && - ( ! $was_addon_network_activated || $network_delegated_connection ) - ) { - $sites = self::get_sites(); - - /** - * If in network admin area and the add-on was not network-activated or network-activated - * and network-delegated, find any add-on whose is_whitelabeled flag is true. - */ - foreach ( $sites as $site ) { - $site_info = $this->get_site_info( $site ); - - if ( $addon_storage->get( 'is_whitelabeled', false, $site_info['blog_id'] ) ) { - $is_whitelabeled = true; - break; - } - } - - if ( $is_whitelabeled ) { - break; - } - } else { - /** - * This will be executed when any of the following is met: - * 1. Add-on was network-activated, not network-delegated, and in network admin area. - * 2. Add-on was network-activated, network-delegated, and in site admin area. - * 3. Add-on was not network-activated and in site admin area. - */ - if ( true === $addon_storage->is_whitelabeled ) { - $is_whitelabeled = true; - break; - } - } - } - } - } - - $this->is_whitelabeled = $is_whitelabeled; - - if ( ! $is_whitelabeled || ! $this->is_data_debug_mode() ) { - $this->_admin_notices->remove_sticky( 'data_debug_mode_enabled' ); - } - - if ( ! is_null( $blog_id ) ) { - $this->restore_current_blog(); - } - } - - return ( - $is_whitelabeled && - ( $ignore_data_debug_mode || ! $this->is_data_debug_mode() ) - ); - } - - /** - * Sync site's subscription. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param FS_Plugin_License|null $license - * - * @return bool|\FS_Subscription - */ - private function _sync_site_subscription( $license ) { - if ( ! is_object( $license ) ) { - $this->delete_unused_subscriptions(); - - return false; - } - - // Load subscription details if not lifetime. - $subscription = $license->is_lifetime() ? - false : - $this->_fetch_site_license_subscription(); - - if ( is_object( $subscription ) && ! isset( $subscription->error ) ) { - $this->store_subscription( $subscription ); - } else { - $this->delete_unused_subscriptions(); - } - - return $subscription; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool|\FS_Plugin_License - */ - function _get_license() { - if ( ! fs_is_network_admin() || is_object( $this->_license ) ) { - return $this->_license; - } - - return $this->_get_available_premium_license(); - } - - /** - * @param number $license_id - * - * @return null|\FS_Subscription - */ - function _get_subscription( $license_id ) { - if ( ! isset( $this->_storage->subscriptions ) || - empty( $this->_storage->subscriptions ) - ) { - return null; - } - - foreach ( fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ) as $subscription ) { - if ( $subscription->license_id == $license_id ) { - return $subscription; - } - } - - return null; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param FS_Subscription $subscription - */ - function store_subscription( FS_Subscription $subscription ) { - if ( ! isset( $this->_storage->subscriptions ) ) { - $this->_storage->subscriptions = array(); - } - - if ( empty( $this->_storage->subscriptions ) || ! is_multisite() ) { - $this->_storage->subscriptions = array( $subscription ); - - return; - } - - $subscriptions = fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ); - - $updated_subscription = false; - foreach ( $subscriptions as $key => $existing_subscription ) { - if ( $existing_subscription->id == $subscription->id ) { - $subscriptions[ $key ] = $subscription; - $updated_subscription = true; - break; - } - } - - if ( ! $updated_subscription ) { - $subscriptions[] = $subscription; - } - - $this->_storage->subscriptions = $subscriptions; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - function delete_unused_subscriptions() { - if ( ! isset( $this->_storage->subscriptions ) || - empty( $this->_storage->subscriptions ) || - // Clean up only if there are already at least 3 subscriptions. - ( count( $this->_storage->subscriptions ) < 3 ) - ) { - return; - } - - if ( ! is_multisite() ) { - // If not multisite, there should only be 1 subscription, so just clear the array. - $this->_storage->subscriptions = array(); - - return; - } - - $subscriptions_to_keep_by_license_id_map = array(); - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( ! is_object( $install ) || - ! FS_Plugin_License::is_valid_id( $install->license_id ) - ) { - continue; - } - - $subscriptions_to_keep_by_license_id_map[ $install->license_id ] = true; - } - - if ( empty( $subscriptions_to_keep_by_license_id_map ) ) { - $this->_storage->subscriptions = array(); - - return; - } - - foreach ( $this->_storage->subscriptions as $key => $subscription ) { - if ( ! isset( $subscriptions_to_keep_by_license_id_map[ $subscription->license_id ] ) ) { - unset( $this->_storage->subscriptions[ $key ] ); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @param string $plan Plan name - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - function is_plan( $plan, $exact = false ) { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return false; - } - - $plan = strtolower( $plan ); - - $current_plan_name = $this->get_plan_name(); - - if ( $current_plan_name === $plan ) { - // Exact plan. - return true; - } else if ( $exact ) { - // Required exact, but plans are different. - return false; - } - - $current_plan_order = - 1; - $required_plan_order = PHP_INT_MAX; - for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { - if ( $plan === $this->_plans[ $i ]->name ) { - $required_plan_order = $i; - } else if ( $current_plan_name === $this->_plans[ $i ]->name ) { - $current_plan_order = $i; - } - } - - return ( $current_plan_order > $required_plan_order ); - } - - /** - * Check if module has only one plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param bool $double_check In some cases developers prefer to release their paid offering as premium-only, even though there is a free version. For those cases, looking at the 'is_premium_only' value isn't enough because the result will return false even when the product has only signle paid plan. - * - * @return bool - */ - function is_single_plan( $double_check = false ) { - $this->_logger->entrance(); - - if ( ! $this->is_registered() || - ! is_array( $this->_plans ) || - 0 === count( $this->_plans ) - ) { - return true; - } - - $has_free_plan = $this->has_free_plan(); - - if ( ! $has_free_plan && $double_check ) { - foreach ( $this->_plans as $plan ) { - if ( $plan->is_free() ) { - $has_free_plan = true; - break; - } - } - } - - return ( 1 === ( count( $this->_plans ) - ( $has_free_plan ? 1 : 0 ) ) ); - } - - /** - * Check if plan based on trial. If not in trial mode, should return false. - * - * @since 1.0.9 - * - * @param string $plan Plan name - * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. - * - * @return bool - */ - function is_trial_plan( $plan, $exact = false ) { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - return false; - } - - if ( ! $this->is_trial() ) { - return false; - } - - $trial_plan = $this->get_trial_plan(); - - if ( $trial_plan->name === $plan ) { - // Exact plan. - return true; - } else if ( $exact ) { - // Required exact, but plans are different. - return false; - } - - $current_plan_order = - 1; - $required_plan_order = - 1; - for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { - if ( $plan === $this->_plans[ $i ]->name ) { - $required_plan_order = $i; - } else if ( $trial_plan->name === $this->_plans[ $i ]->name ) { - $current_plan_order = $i; - } - } - - return ( $current_plan_order > $required_plan_order ); - } - - /** - * Check if plugin has any paid plans. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - function has_paid_plan() { - return $this->_has_paid_plans || - FS_Plan_Manager::instance()->has_paid_plan( $this->_plans ); - } - - /** - * Check if plugin has any plan with a trail. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function has_trial_plan() { - /** - * @author Vova Feldman(@svovaf) - * @since 1.2.1.5 - * - * Allow setting a trial from the SDK without calling the API. - * But, if the user did opt-in, continue using the real data from the API. - */ - if ( $this->_trial_days >= 0 ) { - return true; - } - - return $this->_storage->get( 'has_trial_plan', false ); - } - - /** - * Check if plugin has any free plan, or is it premium only. - * - * Note: If no plans configured, assume plugin is free. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool - */ - function has_free_plan() { - return ! $this->is_only_premium(); - } - - /** - * Displays a license activation dialog box when the user clicks on the "Activate License" - * or "Change License" link on the plugins - * page. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.9 - */ - function _add_license_activation_dialog_box() { - $vars = array( - 'id' => $this->_module_id, - ); - - fs_require_template( 'forms/license-activation.php', $vars ); - fs_require_template( 'forms/resend-key.php', $vars ); - } - - /** - * Returns a collection of IDs of installs that are associated with the context product and its add-ons, and activated with foreign licenses. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @return number[] - */ - function get_installs_ids_with_foreign_licenses() { - $installs = array(); - - if ( - is_object( $this->_license ) && - $this->_site->user_id != $this->_license->user_id - ) { - $installs[] = $this->_site->id; - } - - /** - * Also try to get foreign licenses for the context product's add-ons. - */ - $installs_by_slug_map = $this->get_parent_and_addons_installs_info(); - - foreach ( $installs_by_slug_map as $slug => $install_info ) { - if ( $slug == $this->get_slug() ) { - continue; - } - - $install = $install_info['install']; - $license = $install_info['license']; - - if ( - is_object( $license ) && - $install->user_id != $license->user_id - ) { - $installs[] = $install->id; - } - } - - return $installs; - } - - /** - * Displays the "Change User" dialog box when the user clicks on the "Change User" button on the "Account" page. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @param number[] $install_ids - */ - function _add_user_change_dialog_box( $install_ids ) { - $vars = array( - 'id' => $this->_module_id, - 'license_owners' => $this->fetch_installs_licenses_owners_data( $install_ids ) - ); - - fs_require_template( 'forms/user-change.php', $vars ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - function _add_data_debug_mode_dialog_box() { - $vars = array( - 'id' => $this->_module_id, - ); - - fs_require_template( 'forms/data-debug-mode.php', $vars ); - } - - /** - * Displays a subscription cancellation dialog box when the user clicks on the "Deactivate License" - * link on the "Account" page or deactivates a plugin and there's an active subscription that is - * either associated with a non-lifetime single-site license or non-lifetime multisite license that - * is only activated on a single production site. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - * - * @param bool $is_license_deactivation - * - * @return array - */ - function _get_subscription_cancellation_dialog_box_template_params( $is_license_deactivation = false ) { - if ( fs_is_network_admin() ) { - // Subscription cancellation dialog box is currently not supported for multisite networks. - return array(); - } - - if ( $this->is_whitelabeled() ) { - return array(); - } - - $license = $this->_get_license(); - - /** - * If the installation is associated with a non-lifetime license, which is either a single-site or only activated on a single production site (or zero), and connected to an active subscription, suggest the customer to cancel the subscription upon deactivation. - * - * @author Leo Fajardo (@leorw) (Comment added by Vova Feldman @svovaf) - * @since 2.2.1 - */ - if ( ! is_object( $license ) || - $license->is_lifetime() || - ( ! $license->is_single_site() && $license->activated > 1 ) - ) { - return array(); - } - - /** - * @var FS_Subscription $subscription - */ - $subscription = $this->_get_subscription( $license->id ); - if ( ! is_object( $subscription ) || ! $subscription->is_active() ) { - return array(); - } - - return array( - 'id' => $this->_module_id, - 'license' => $license, - 'has_trial' => $this->is_paid_trial(), - 'is_license_deactivation' => $is_license_deactivation, - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - */ - function _add_premium_version_upgrade_selection_dialog_box() { - $modules_update = get_site_transient( $this->is_theme() ? 'update_themes' : 'update_plugins' ); - if ( ! isset( $modules_update->response[ $this->_plugin_basename ] ) ) { - return; - } - - $vars = array( - 'id' => $this->_module_id, - 'new_version' => is_object( $modules_update->response[ $this->_plugin_basename ] ) ? - $modules_update->response[ $this->_plugin_basename ]->new_version : - $modules_update->response[ $this->_plugin_basename ]['new_version'] - ); - - fs_require_template( 'forms/premium-versions-upgrade-metadata.php', $vars ); - fs_require_once_template( 'forms/premium-versions-upgrade-handler.php', $vars ); - } - - /** - * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins" - * page. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - */ - function _add_optout_dialog() { - if ( $this->is_theme() ) { - $vars = null; - fs_require_once_template( '/js/jquery.content-change.php', $vars ); - } - - $vars = array( 'id' => $this->_module_id ); - fs_require_template( 'forms/optout.php', $vars ); - } - - /** - * Prepare page to include all required UI and logic for the license activation dialog. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - */ - function _add_license_activation() { - if ( $this->is_migration() ) { - return; - } - - if ( ! $this->is_user_admin() ) { - // Only admins can activate a license. - return; - } - - if ( ! $this->has_paid_plan() ) { - // Module doesn't have any paid plans. - return; - } - - if ( - $this->has_premium_version() && - ! $this->is_premium() && - /** - * Also handle the case when an upgrade was made using the free version. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - ! is_object( $this->_get_license() ) - ) { - // Only add license activation logic to the premium version, or in case of a serviceware plugin, also in the free version. - return; - } - - // Add license activation link and AJAX request handler. - if ( self::is_plugins_page() ) { - $is_network_admin = fs_is_network_admin(); - - if ( - ( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) || - ( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) ) - ) { - /** - * @since 1.2.0 Add license action link only on plugins page. - */ - $this->_add_license_action_link(); - } - } - - // Add license activation AJAX callback. - $this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) ); - - // Add resend license AJAX callback. - $this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) ); - } - - /** - * Prepares page to include all required UI and logic for the "Change User" dialog. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - function _add_user_change_option() { - if ( ! $this->should_handle_user_change() ) { - return; - } - - $installs_ids_with_foreign_licenses = $this->get_installs_ids_with_foreign_licenses(); - - if ( empty( $installs_ids_with_foreign_licenses ) ) { - // Handle user change only when the parent product or one of its add-ons is activated with a foreign license. - return; - } - - // Add user change AJAX handler. - $this->add_ajax_action( 'change_user', array( &$this, '_user_change_ajax_action' ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - function should_handle_user_change() { - if ( ! $this->is_user_admin() ) { - // Only admins can change user. - return false; - } - - if ( $this->is_addon() ) { - return false; - } - - if ( ! $this->is_registered() ) { - return false; - } - - if ( - $this->is_network_active() && - ( fs_is_network_admin() || ! $this->is_site_delegated_connection() ) - ) { - // Handle only on site-level "Account" section for now. - return false; - } - - return true; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - */ - function _add_premium_version_upgrade_selection() { - if ( ! $this->is_user_admin() ) { - return; - } - - if ( ! $this->is_premium() || $this->has_any_active_valid_license() ) { - // This is relevant only to the free versions and premium versions without an active license. - return; - } - - if ( self::is_updates_page() || ( $this->is_plugin() && self::is_plugins_page() ) ) { - $this->_add_premium_version_upgrade_selection_action(); - } - } - - /** - * @author Edgar Melkonyan - * @since 2.4.1 - * - * @throws Freemius_Exception - */ - function _toggle_whitelabel_mode_ajax_handler() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'toggle_whitelabel_mode' ); - - if ( ! $this->is_user_admin() ) { - // Only for admins. - self::shoot_ajax_failure(); - } - - $license = $this->get_api_user_scope()->call( - "/licenses/{$this->_site->license_id}.json", - 'put', - array( 'is_whitelabeled' => ! $this->_license->is_whitelabeled ) - ); - - if ( ! $this->is_api_result_entity( $license ) ) { - self::shoot_ajax_failure( - FS_Api::is_api_error_object( $license ) ? - $license->error->message : - fs_text_inline( "An unknown error has occurred while trying to toggle the license's white-label mode.", 'unknown-error-occurred', $this->get_slug() ) - ); - } - - $this->_license->is_whitelabeled = $license->is_whitelabeled; - $this->_store_licenses(); - - $this->_sync_license(); - - if ( ! $license->is_whitelabeled ) { - $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); - } else { - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( - 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.', - 'license_whitelabeled' - ), - "{$this->get_plugin_title()}", - sprintf( '%s', $this->get_text_inline( 'User Dashboard', 'user-dashboard' ) ), - sprintf( '%s', $this->get_text_inline( 'revert it now', 'revert-it-now' ) ) - ), - 'license_whitelabeled' - ); - } - - self::shoot_ajax_response( array( 'success' => true ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - function _add_beta_mode_update_handler() { - if ( ! $this->is_user_admin() ) { - return; - } - - if ( ! $this->is_premium() ) { - return; - } - - $this->add_ajax_action( 'set_beta_mode', array( &$this, '_set_beta_mode_ajax_handler' ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - function _set_beta_mode_ajax_handler() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'set_beta_mode' ); - - if ( ! $this->is_user_admin() ) { - // Only for admins. - self::shoot_ajax_failure(); - } - - $is_beta = trim( fs_request_get( 'is_beta', '', 'post' ) ); - - if ( empty( $is_beta ) || ! in_array( $is_beta, array( 'true', 'false' ) ) ) { - self::shoot_ajax_failure(); - } - - $site = $this->get_api_site_scope()->call( - '', - 'put', - array( - 'is_beta' => ( 'true' == $is_beta ), - 'fields' => 'is_beta' - ) - ); - - if ( ! $this->is_api_result_entity( $site ) ) { - self::shoot_ajax_failure( - FS_Api::is_api_error_object( $site ) ? - $site->error->message : - fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() ) - ); - } - - $this->_site->is_beta = $site->is_beta; - $this->_store_site(); - - self::shoot_ajax_response( array( 'success' => true ) ); - } - - /** - * License activation WP AJAX handler. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.9 - * - * @uses Freemius::activate_license() - */ - function _activate_license_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'activate_license' ); - - $license_key = trim( fs_request_get( 'license_key' ) ); - - if ( empty( $license_key ) ) { - exit; - } - - $sites = fs_is_network_admin() ? - fs_request_get( 'sites', array(), 'post' ) : - array(); - - $result = $this->activate_license( - $license_key, - $sites, - fs_request_get_bool( 'is_marketing_allowed', null ), - fs_request_get( 'blog_id', null ), - fs_request_get( 'module_id', null, 'post' ), - fs_request_get( 'user_id', null ), - fs_request_get_bool( 'is_extensions_tracking_allowed', null ) - ); - - if ( - $result['success'] && - $this->is_bundle_license_auto_activation_enabled() - ) { - $license = new FS_Plugin_License(); - $license->secret_key = $license_key; - - $this->maybe_activate_bundle_license( $license, $sites ); - } - - echo json_encode( $result ); - - exit; - } - - /** - * User change WP AJAX handler. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - function _user_change_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'change_user' ); - - $new_email_address = trim( fs_request_get( 'email_address', '' ) ); - $new_user_id = fs_request_get( 'user_id' ); - - if ( empty( $new_email_address ) && ! FS_User::is_valid_id( $new_user_id ) ) { - self::shoot_ajax_failure( fs_text_inline( 'Invalid new user ID or email address.', 'invalid-new-user-id-or-email', $this->get_slug() ) ); - } - - $params = array(); - - if ( ! empty( $new_email_address ) ) { - $params['user_email'] = $new_email_address; - } else { - $params['user_id'] = $new_user_id; - } - - $installs_info_by_slug_map = $this->get_parent_and_addons_installs_info(); - $install_ids = array(); - - foreach ( $installs_info_by_slug_map as $slug => $install_info ) { - $install_ids[ $slug ] = $install_info['install']->id; - } - - $params['install_ids'] = implode( ',', array_values( $install_ids ) ); - - $install = $this->get_api_site_scope()->call( $this->add_show_pending( '/' ), 'put', $params ); - - if ( FS_Api::is_api_error( $install ) ) { - $error = ''; - - if ( is_object( $install ) ) { - switch ( $install->error->code ) { - case 'user_exist': - $error = ( - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' . - $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . - sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email_address . '' ) . - sprintf( - '', - $this->get_account_url( 'change_owner', array( - 'state' => 'init', - 'candidate_email' => $new_email_address - ) ), - $this->get_text_inline( 'Change Ownership', 'change-ownership' ) - ) - ); - break; - } - } - - if ( empty( $error ) ) { - $error = FS_Api::is_api_error_object( $install ) ? - $install->error->message : - var_export( $install->error, true ); - } - - self::shoot_ajax_failure( $error ); - } else { - if ( - // If successful ownership change. - $this->get_user()->id != $install->user_id || - ! empty( $new_email_address ) - ) { - $this->complete_ownership_change_by_license( $install->user_id, $install_ids ); - } - } - - self::shoot_ajax_success(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2.14 - */ - function starting_migration() { - if ( ! empty( $this->_storage->license_migration ) ) { - // Do not overwrite the data if already set. - return; - } - - $this->_storage->license_migration = array( - 'is_migrating' => true, - 'start_timestamp' => time() - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2.14 - */ - function is_migration() { - if ( $this->is_addon() ) { - return $this->get_parent_instance()->is_migration(); - } - - if ( empty( $this->_storage->license_migration ) ) { - return false; - } - - if ( ! $this->_storage->license_migration['is_migrating'] ) { - return false; - } - - return ( - // Return `true` if the migration is within 5 minutes from the starting time. - ( time() - $this->_storage->license_migration['start_timestamp'] ) <= WP_FS__TIME_5_MIN_IN_SEC - ); - } - - /** - * - * A helper method to activate migrated licenses. If the product is network activated and integrated, the method will network activate the license. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.0 - * - * @param string $license_key - * @param null|bool $is_marketing_allowed - * @param null|number $plugin_id - * @param array $sites - * @param int $blog_id - * - * @return array { - * @var bool $success - * @var string $error - * @var string $next_page - * } - * - * @uses Freemius::activate_license() - */ - function activate_migrated_license( - $license_key, - $is_marketing_allowed = null, - $plugin_id = null, - $sites = array(), - $blog_id = null - ) { - $this->_logger->entrance(); - - $result = $this->activate_license( - $license_key, - ( empty( $sites ) && is_null( $blog_id ) && $this->is_network_active() ) ? - $this->get_sites_for_network_level_optin() : - $sites, - $is_marketing_allowed, - $blog_id, - $plugin_id - ); - - // No need to show the sticky after license activation notice after migrating a license. - $this->_admin_notices->remove_sticky( 'plan_upgraded' ); - - return $result; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @return string - */ - function get_pricing_js_path() { - if ( ! isset( $this->_pricing_js_path ) ) { - $pricing_js_path = $this->apply_filters( 'freemius_pricing_js_path', '' ); - - if ( empty( $pricing_js_path ) ) { - global $fs_active_plugins; - - foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { - if ( $data->plugin_path == $this->get_plugin_basename() ) { - $plugin_or_theme_root_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); - - $pricing_js_path = $plugin_or_theme_root_dir - . '/' - // The basename will be `plugins`, `themes`, or the basename of a custom plugins or themes directory. - . str_replace( '../' . basename( $plugin_or_theme_root_dir ) . '/', '', $sdk_path ) - . '/includes/freemius-pricing/freemius-pricing.js'; - - break; - } - } - } - - $this->_pricing_js_path = $pricing_js_path; - } - - return $this->_pricing_js_path; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @return bool - */ - function should_use_external_pricing() { - if ( is_null( $this->_use_external_pricing ) ) { - $pricing_js_path = $this->get_pricing_js_path(); - - $this->_use_external_pricing = ( empty( $pricing_js_path ) || ! file_exists( $pricing_js_path ) ); - } - - return $this->_use_external_pricing; - } - - /** - * The implementation of this method was previously in `_activate_license_ajax_action()`. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.4 - * @since 2.0.0 When a super-admin that hasn't connected before is network activating a license and excluding some of the sites for the license activation, go over the unselected sites in the network and if a site is not connected, skipped, nor delegated, if it's a freemium product then just skip the connection for the site, if it's a premium only product, delegate the connection and license activation to the site admin (Vova Feldman @svovaf). - * @param string $license_key - * @param array $sites - * @param null|bool $is_marketing_allowed - * @param null|int $blog_id - * @param null|number $plugin_id - * @param null|number $license_owner_id - * - * @return array { - * @var bool $success - * @var string $error - * @var string $next_page - * } - */ - private function activate_license( - $license_key, - $sites = array(), - $is_marketing_allowed = null, - $blog_id = null, - $plugin_id = null, - $license_owner_id = null, - $is_extensions_tracking_allowed = null - ) { - $this->_logger->entrance(); - - $license_key = trim( $license_key ); - - $is_network_activation_or_migration = ( - fs_is_network_admin() || - ( ! empty( $sites ) && $this->is_migration() ) - ); - - if ( ! $is_network_activation_or_migration ) { - // If the license activation is executed outside the context of a network admin, ignore the sites collection. - $sites = array(); - } - - $fs = ( empty($plugin_id) || $plugin_id == $this->_module_id ) ? - $this : - $this->get_addon_instance( $plugin_id ); - - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - - $error = false; - $next_page = false; - - $has_valid_blog_id = is_numeric( $blog_id ); - - $user = null; - - if ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) { - /** - * When activating an add-on's license and the parent is opted-in, activate the license with the parent's opted-in user context. - * - * @author Vova Feldman (@svovaf) - */ - $user = $fs->get_parent_instance()->get_current_or_network_user(); - } else if ( $fs->is_registered() ) { - $user = $fs->get_current_or_network_user(); - } - - if ( $has_valid_blog_id ) { - /** - * If a specific blog ID was provided, activate the license only on the specific blog that is associated with the given blog ID. - * - * @author Leo Fajardo (@leorw) - */ - $fs->switch_to_blog( $blog_id ); - } - - if ( is_object( $user ) ) { - if ( $is_network_activation_or_migration && ! $has_valid_blog_id ) { - // If no specific blog ID was provided, activate the license for all sites in the network. - $blog_2_install_map = array(); - $site_ids = array(); - - foreach ( $sites as $site ) { - if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { - continue; - } - - $install = $fs->get_install_by_blog_id( $site['blog_id'] ); - - if ( is_object( $install ) ) { - $blog_2_install_map[ $site['blog_id'] ] = $install; - } else { - $site_ids[] = $site['blog_id']; - } - } - - if ( ! empty( $blog_2_install_map ) ) { - $result = $fs->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } - } - - if ( empty( $error ) && ! empty( $site_ids ) ) { - $result = $fs->activate_license_on_many_sites( $user, $license_key, $site_ids ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } - } - } else { - if ( $fs->is_registered() ) { - $params = array( - 'license_key' => $fs->apply_filters( 'license_key', $license_key ) - ); - - $install_ids = array(); - - $change_owner = FS_User::is_valid_id( $license_owner_id ); - - if ( $change_owner ) { - $params['user_id'] = $license_owner_id; - - $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info(); - - foreach ( $installs_info_by_slug_map as $slug => $install_info ) { - $install_ids[ $slug ] = $install_info['install']->id; - } - - $params['install_ids'] = implode( ',', array_values( $install_ids ) ); - } - - $api = $fs->get_api_site_scope(); - - $install = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); - - if ( FS_Api::is_api_error( $install ) ) { - $error = FS_Api::is_api_error_object( $install ) ? - $install->error->message : - var_export( $install->error, true ); - } else { - $fs->reconnect_locally( $has_valid_blog_id ); - - if ( - $change_owner && - // If successful ownership change. - $fs->get_user()->id != $install->user_id - ) { - $fs->complete_ownership_change_by_license( $install->user_id, $install_ids ); - } - } - } else /* ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) */ { - $result = $fs->activate_license_on_site( $user, $license_key ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } - } - } - - if ( empty( $error ) ) { - $fs->network_upgrade_mode_completed(); - - $fs->_user = $user; - - if ( fs_is_network_admin() && ! $has_valid_blog_id ) { - $fs->_site = $fs->get_network_install(); - } - - $fs->_sync_license( true, $has_valid_blog_id ); - - $this->maybe_sync_install_user(); - - $next_page = $fs->is_addon() ? - $fs->get_parent_instance()->get_account_url() : - $fs->get_after_activation_url( 'after_connect_url' ); - } - } else { - $next_page = $fs->opt_in( - false, - false, - false, - $license_key, - false, - false, - false, - $is_marketing_allowed, - $sites - ); - - if ( isset( $next_page->error ) ) { - $error = $next_page->error; - } else { - if ( $is_network_activation_or_migration ) { - /** - * Get the list of sites that were just opted-in (and license activated). - * This is an optimization for the next part below saving some DB queries. - */ - $connected_sites = array(); - foreach ( $sites as $site ) { - if ( isset( $site['blog_id'] ) && is_numeric( $site['blog_id'] ) ) { - $connected_sites[ $site['blog_id'] ] = true; - } - } - - $all_sites = self::get_sites(); - $pending_sites = array(); - - /** - * Check if there are any sites that are not connected, skipped, nor delegated. For every site that falls into that category, if the product is freemium, skip the connection. If the product is premium only, delegate the connection to the site administrator. - * - * @author Vova Feldman (@svovaf) - */ - foreach ( $all_sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - if ( isset( $connected_sites[ $blog_id ] ) ) { - // Site was just connected. - continue; - } - - if ( $fs->is_installed_on_site( $blog_id ) ) { - // Site was already connected before. - continue; - } - - if ( $fs->is_site_delegated_connection( $blog_id ) ) { - // Site's connection was delegated. - continue; - } - - if ( $fs->is_anonymous_site( $blog_id ) ) { - // Site connection was already skipped. - continue; - } - - $pending_sites[] = self::get_site_info( $site ); - } - - if ( ! empty( $pending_sites ) ) { - if ( $fs->is_freemium() && $fs->is_enable_anonymous() ) { - $fs->skip_connection( $pending_sites ); - } else { - $fs->delegate_connection( $pending_sites ); - } - } - } - } - } - - if ( false === $error && true === $fs->_storage->require_license_activation ) { - $fs->_storage->require_license_activation = false; - } - - $result = array( - 'success' => ( false === $error ) - ); - - if ( false !== $error ) { - $result['error'] = $fs->apply_filters( 'opt_in_error_message', $error ); - } else { - if ( $fs->is_addon() || $fs->has_addons() ) { - /** - * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, - * an updated valid user licenses collection will be fetched from the server which is used to also - * update the account add-ons (add-ons the user has licenses for). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - $fs->purge_valid_user_licenses_cache(); - } - - $result['next_page'] = $next_page; - } - - return $result; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @return array { - * @key string Product slug. - * @value array { - * @property FS_Site $site - * @property FS_Plugin_License $license - * } - * } - */ - private function get_parent_and_addons_installs_info() { - $fs = $this->is_addon() ? - $this->get_parent_instance() : - $this; - - $installed_addons_ids = array(); - - $installed_addons_instances = $fs->get_installed_addons(); - foreach ( $installed_addons_instances as $instance ) { - $installed_addons_ids[] = $instance->get_id(); - } - - $addons_ids = array_unique( array_merge( - $installed_addons_ids, - $fs->get_updated_account_addons() - ) ); - - // Add parent product info. - $installs_info_by_slug_map = array( - $fs->get_slug() => array( - 'install' => $fs->get_site(), - 'license' => $fs->_get_license() - ) - ); - - foreach ( $addons_ids as $addon_id ) { - $is_installed = isset( $installed_addons_ids_map[ $addon_id ] ); - - $addon_info = $fs->_get_addon_info( $addon_id, $is_installed ); - - if ( ! $addon_info['is_connected'] ) { - // Add-on is not associated with an install entity. - continue; - } - - $installs_info_by_slug_map[ $addon_info['slug'] ] = array( - 'install' => $addon_info['site'], - 'license' => isset( $addon_info['license'] ) ? - $addon_info['license'] : - null - ); - } - - return $installs_info_by_slug_map; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3.1 - */ - function _network_activate_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'network_activate' ); - - $plugin_id = fs_request_get( 'module_id', '', 'post' ); - $fs = ( $plugin_id == $this->_module_id ) ? - $this : - $this->get_addon_instance( $plugin_id ); - - $error = false; - - $sites = fs_request_get( 'sites', array(), 'post' ); - if ( is_array( $sites ) && ! empty( $sites ) ) { - $sites_by_action = array( - 'allow' => array(), - 'delegate' => array(), - 'skip' => array() - ); - - foreach ( $sites as $site ) { - $sites_by_action[ $site['action'] ][] = $site; - } - - $total_sites = count( $sites ); - $total_sites_to_delegate = count( $sites_by_action['delegate'] ); - - $next_page = ''; - - $has_any_install = fs_request_get_bool( 'has_any_install' ); - - if ( $total_sites === $total_sites_to_delegate && - ! $this->is_network_upgrade_mode() && - ! $has_any_install - ) { - $this->delegate_connection(); - } else { - if ( ! empty( $sites_by_action['delegate'] ) ) { - $this->delegate_connection( $sites_by_action['delegate'] ); - } - - if ( ! empty( $sites_by_action['skip'] ) ) { - $this->skip_connection( $sites_by_action['skip'] ); - } - - if ( empty( $sites_by_action['allow'] ) ) { - if ( $has_any_install ) { - $first_install = $fs->find_first_install(); - - if ( ! is_null( $first_install ) ) { - $fs->_site = $first_install['install']; - $fs->_storage->network_install_blog_id = $first_install['blog_id']; - - $fs->_user = self::_get_user_by_id( $fs->_site->user_id ); - $fs->_storage->network_user_id = $fs->_user->id; - } - } - } else { - if ( ! $fs->is_registered() || ! $this->_is_network_active ) { - $next_page = $fs->opt_in( - false, - false, - false, - false, - false, - false, - false, - fs_request_get_bool( 'is_marketing_allowed', null ), - $sites_by_action['allow'] - ); - } else { - $next_page = $fs->install_with_user( - $this->get_network_user(), - false, - false, - false, - true, - $sites_by_action['allow'] - ); - } - - if ( is_object( $next_page ) && isset( $next_page->error ) ) { - $error = $next_page->error; - } - } - } - - if ( empty( $next_page ) ) { - $next_page = $this->get_after_activation_url( 'after_network_activation_url' ); - } - } else { - $error = $this->get_text_inline( 'Invalid site details collection.', 'invalid_site_details_collection' ); - } - - $result = array( - 'success' => ( false === $error ) - ); - - if ( false !== $error ) { - $result['error'] = $error; - } else { - $result['next_page'] = $next_page; - } - - echo json_encode( $result ); - - exit; - } - - /** - * Billing update AJAX callback. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - */ - function _update_billing_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'update_billing' ); - - if ( ! $this->is_user_admin() ) { - // Only for admins. - self::shoot_ajax_failure(); - } - - $billing = fs_request_get( 'billing' ); - - $api = $this->get_api_user_scope(); - $result = $api->call( '/billing.json', 'put', array_merge( $billing, array( - 'plugin_id' => $this->get_parent_id(), - ) ) ); - - if ( ! $this->is_api_result_entity( $result ) ) { - self::shoot_ajax_failure(); - } - - // Purge cached billing. - $this->get_api_user_scope()->purge_cache( 'billing.json' ); - - self::shoot_ajax_success(); - } - - /** - * Trial start for anonymous users (AJAX callback). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - */ - function _start_trial_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'start_trial' ); - - if ( ! $this->is_user_admin() ) { - // Only for admins. - self::shoot_ajax_failure(); - } - - $trial_data = fs_request_get( 'trial' ); - - $next_page = $this->opt_in( - false, - false, - false, - false, - false, - $trial_data['plan_id'] - ); - - if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { - self::shoot_ajax_failure( - isset( $next_page->error ) ? - $next_page->error->message : - var_export( $next_page, true ) - ); - } - - $this->shoot_ajax_success( array( - 'next_page' => $next_page, - ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.0 - */ - function _resend_license_key_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'resend_license_key' ); - - $email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) ); - - if ( empty( $email_address ) ) { - exit; - } - - $error = false; - - $api = $this->get_api_plugin_scope(); - $result = $api->call( '/licenses/resend.json', 'post', - array( - 'email' => $email_address, - 'url' => home_url(), - ) - ); - - if ( is_object( $result ) && isset( $result->error ) ) { - $error = $result->error; - - if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) { - $error = $this->get_text_inline( "We couldn't find your email address in the system, are you sure it's the right address?", 'email-not-found' ); - } else if ( 'no_license' === $error->code ) { - $error = $this->get_text_inline( "We can't see any active licenses associated with that email address, are you sure it's the right address?", 'no-active-licenses' ); - } else { - $error = $error->message; - } - } - - $licenses = array( - 'success' => ( false === $error ) - ); - - if ( false !== $error ) { - $licenses['error'] = sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), strtolower( $error ) ); - } - - echo json_encode( $licenses ); - - exit; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.8 - * - * @var string - */ - private static $_pagenow; - - /** - * Get current page or the referer if executing a WP AJAX request. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.8 - * - * @return string - */ - static function get_current_page() { - if ( ! isset( self::$_pagenow ) ) { - global $pagenow; - if ( empty( $pagenow ) && is_admin() && is_multisite() ) { - /** - * It appears that `$pagenow` is not yet initialized in some network admin pages when this method - * is called, so initialize it here using some pieces of code from `wp-includes/vars.php`. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - if ( is_network_admin() ) { - preg_match( '#/wp-admin/network/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); - } else if ( is_user_admin() ) { - preg_match( '#/wp-admin/user/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); - } else { - preg_match( '#/wp-admin/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); - } - - $pagenow = $self_matches[1]; - $pagenow = trim( $pagenow, '/' ); - $pagenow = preg_replace( '#\?.*?$#', '', $pagenow ); - if ( '' === $pagenow || 'index' === $pagenow || 'index.php' === $pagenow ) { - $pagenow = 'index.php'; - } else { - preg_match( '#(.*?)(/|$)#', $pagenow, $self_matches ); - $pagenow = strtolower( $self_matches[1] ); - if ( '.php' !== substr($pagenow, -4, 4) ) - $pagenow .= '.php'; // for Options +Multiviews: /wp-admin/themes/index.php (themes.php is queried) - } - } - - self::$_pagenow = $pagenow; - - if ( self::is_ajax() && - 'admin-ajax.php' === $pagenow - ) { - $referer = fs_get_raw_referer(); - - if ( is_string( $referer ) ) { - $parts = explode( '?', $referer ); - - self::$_pagenow = basename( $parts[0] ); - } - } - } - - return self::$_pagenow; - } - - /** - * Helper method to check if user in the plugins page. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return bool - */ - static function is_plugins_page() { - return ( 'plugins.php' === self::get_current_page() ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - * - * @return bool - */ - static function is_plugin_install_page() { - return ( 'plugin-install.php' === self::get_current_page() ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - * - * @return bool - */ - static function is_updates_page() { - return ( 'update-core.php' === self::get_current_page() ); - } - - /** - * Helper method to check if user in the themes page. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.6 - * - * @return bool - */ - static function is_themes_page() { - return ( 'themes.php' === self::get_current_page() ); - } - - #---------------------------------------------------------------------------------- - #region Affiliation - #---------------------------------------------------------------------------------- - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @return bool - */ - function has_affiliate_program() { - if ( ! is_object( $this->_plugin ) ) { - return false; - } - - return $this->_plugin->has_affiliate_program(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.4 - */ - private function fetch_affiliate_terms() { - if ( ! is_object( $this->plugin_affiliate_terms ) ) { - $plugins_api = $this->get_api_plugin_scope(); - $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', false ); - - if ( ! $this->is_api_result_entity( $affiliate_terms ) ) { - return; - } - - $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.4 - */ - private function fetch_affiliate_and_custom_terms() { - if ( ! empty( $this->_storage->affiliate_application_data ) ) { - $application_data = $this->_storage->affiliate_application_data; - $flush = ( ! isset( $application_data['status'] ) || 'pending' === $application_data['status'] ); - - $users_api = $this->get_api_user_scope(); - $result = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush ); - if ( $this->is_api_result_object( $result, 'affiliates' ) ) { - if ( ! empty( $result->affiliates ) ) { - $affiliate = new FS_Affiliate( $result->affiliates[0] ); - - if ( ! isset( $application_data['status'] ) || $application_data['status'] !== $affiliate->status ) { - $application_data['status'] = $affiliate->status; - $this->_storage->affiliate_application_data = $application_data; - } - - if ( $affiliate->is_using_custom_terms ) { - $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", $flush ); - if ( $this->is_api_result_entity( $affiliate_terms ) ) { - $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); - } - } - - $this->affiliate = $affiliate; - } - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - private function fetch_affiliate_and_terms() { - $this->_logger->entrance(); - - $this->fetch_affiliate_terms(); - $this->fetch_affiliate_and_custom_terms(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @return FS_Affiliate - */ - function get_affiliate() { - return $this->affiliate; - } - - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @return FS_AffiliateTerms - */ - function get_affiliate_terms() { - return is_object( $this->custom_affiliate_terms ) ? - $this->custom_affiliate_terms : - $this->plugin_affiliate_terms; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - function _submit_affiliate_application() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'submit_affiliate_application' ); - - if ( ! $this->is_user_admin() ) { - // Only for admins. - self::shoot_ajax_failure(); - } - - $affiliate = fs_request_get( 'affiliate' ); - - if ( empty( $affiliate['promotion_methods'] ) ) { - unset( $affiliate['promotion_methods'] ); - } - - if ( ! empty( $affiliate['additional_domains'] ) ) { - $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] ); - } - - if ( ! $this->is_registered() ) { - // Opt in but don't track usage. - $next_page = $this->opt_in( - false, - false, - false, - false, - false, - false, - true - ); - - if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { - self::shoot_ajax_failure( - isset( $next_page->error ) ? - $next_page->error->message : - var_export( $next_page, true ) - ); - } else if ( $this->is_pending_activation() ) { - self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation.', 'account-is-pending-activation' ) ); - } - } - - $this->fetch_affiliate_terms(); - - $api = $this->get_api_user_scope(); - $result = $api->call( - ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), - 'post', - $affiliate - ); - - if ( $this->is_api_error( $result ) ) { - self::shoot_ajax_failure( - isset( $result->error ) ? - $result->error->message : - var_export( $result, true ) - ); - } else { - if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { - $this->_admin_notices->remove_sticky( 'affiliate_program' ); - } - - $affiliate_application_data = array( - 'status' => 'pending', - 'stats_description' => $affiliate['stats_description'], - 'promotion_method_description' => $affiliate['promotion_method_description'], - ); - - if ( ! empty( $affiliate['promotion_methods'] ) ) { - $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods']; - } - - if ( ! empty( $affiliate['domain'] ) ) { - $affiliate_application_data['domain'] = $affiliate['domain']; - } - - if ( ! empty( $affiliate['additional_domains'] ) ) { - $affiliate_application_data['additional_domains'] = $affiliate['additional_domains']; - } - - $this->_storage->affiliate_application_data = $affiliate_application_data; - } - - // Purge cached affiliate. - $api->purge_cache( 'affiliate.json' ); - - self::shoot_ajax_success( $result ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @return array|null - */ - function get_affiliate_application_data() { - if ( empty( $this->_storage->affiliate_application_data ) ) { - return null; - } - - return $this->_storage->affiliate_application_data; - } - - #endregion Affiliation ------------------------------------------------------------ - - #---------------------------------------------------------------------------------- - #region URL Generators - #---------------------------------------------------------------------------------- - - /** - * Alias to pricing_url(). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @uses pricing_url() - * - * @param string $period Billing cycle - * @param bool $is_trial - * - * @return string - */ - function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { - return $this->pricing_url( $period, $is_trial ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @uses get_upgrade_url() - * - * @return string - */ - function get_trial_url() { - return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.4 - * - * @param string $new_version - * - * @return string - */ - function version_upgrade_checkout_link( $new_version ) { - if ( ! is_object( $this->_license ) ) { - $url = $this->pricing_url(); - - $purchase_license_text = $this->get_text_inline( 'Buy a license now', 'buy-license-now' ); - } else { - $subscription = $this->_get_subscription( $this->_license->id ); - - $url = $this->checkout_url( - is_object( $subscription ) ? - ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : - WP_FS__PERIOD_LIFETIME, - false, - array( 'licenses' => $this->_license->quota ) - ); - - $purchase_license_text = $this->get_text_inline( 'Renew your license now', 'renew-license-now' ); - } - - return sprintf( - $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), - sprintf( '%s', $url, $purchase_license_text ), - $new_version - ); - } - - /** - * Plugin's pricing URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $billing_cycle Billing cycle - * - * @param bool $is_trial - * - * @return string - */ - function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { - $this->_logger->entrance(); - - $params = array( - 'billing_cycle' => $billing_cycle - ); - - if ( $is_trial ) { - $params['trial'] = 'true'; - } - - $url = $this->is_addon() ? - $this->_parent->addon_url( $this->_slug ) : - $this->_get_admin_page_url( 'pricing', $params ); - - return $this->apply_filters( 'pricing_url', $url ); - } - - /** - * Checkout page URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string $billing_cycle Billing cycle - * @param bool $is_trial - * @param array $extra (optional) Extra parameters, override other query params. - * @param bool|null $network - * - * @return string - */ - function checkout_url( - $billing_cycle = WP_FS__PERIOD_ANNUALLY, - $is_trial = false, - $extra = array(), - $network = null - ) { - $this->_logger->entrance(); - - $params = array( - 'checkout' => 'true', - 'billing_cycle' => $billing_cycle, - ); - - if ( $is_trial ) { - $params['trial'] = 'true'; - } - - /** - * Params in extra override other params. - */ - $params = array_merge( $params, $extra ); - - return $this->_get_admin_page_url( 'pricing', $params, $network ); - } - - /** - * Add-on checkout URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @param number $addon_id - * @param number $pricing_id - * @param string $billing_cycle - * @param bool $is_trial - * @param bool|null $network - * - * @return string - */ - function addon_checkout_url( - $addon_id, - $pricing_id, - $billing_cycle = WP_FS__PERIOD_ANNUALLY, - $is_trial = false, - $network = null - ) { - return $this->checkout_url( $billing_cycle, $is_trial, array( - 'plugin_id' => $addon_id, - 'pricing_id' => $pricing_id, - ), $network ); - } - - #endregion - - #endregion ------------------------------------------------------------------ - - /** - * Check if plugin has any add-ons. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @since 1.1.7.3 Base logic only on the parameter provided by the developer in the init function. - * - * @return bool - */ - function has_addons() { - $this->_logger->entrance(); - - return $this->_has_addons; - } - - /** - * Check if plugin can work in anonymous mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - * - * @deprecated Please use is_enable_anonymous() instead. - */ - function enable_anonymous() { - return $this->_enable_anonymous; - } - - /** - * Check if plugin can work in anonymous mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - * @return bool - */ - function is_enable_anonymous() { - return $this->_enable_anonymous; - } - - /** - * Check if plugin is premium only (no free plans). - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9 - * - * @return bool - */ - function is_only_premium() { - return $this->_is_premium_only; - } - - /** - * Checks if the plugin's type is "plugin". The other type is "theme". - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - function is_plugin() { - return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return string - */ - function get_module_type() { - if ( ! isset( $this->_module_type ) ) { - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); - $this->_module_type = $id_slug_type_path_map[ $this->_module_id ]['type']; - } - - return $this->_module_type; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return string - */ - function get_plugin_main_file_path() { - return $this->_plugin_main_file_path; - } - - /** - * Check if module has a premium code version. - * - * Serviceware module might be freemium without any - * premium code version, where the paid features - * are all part of the service. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @return bool - */ - function has_premium_version() { - return $this->_has_premium_version; - } - - /** - * Check if feature supported with current site's plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @todo IMPLEMENT - * - * @param number $feature_id - * - * @throws Exception - */ - function is_feature_supported( $feature_id ) { - throw new Exception( 'not implemented' ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @return bool Is running in SSL/HTTPS - */ - function is_ssl() { - return WP_FS__IS_HTTPS; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool Is running in AJAX call. - * - * @link http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax - */ - static function is_ajax() { - return ( defined( 'DOING_AJAX' ) && DOING_AJAX ); - } - - /** - * Check if it's an AJAX call targeted for the current module. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * - * @param array|string $actions Collection of AJAX actions. - * - * @return bool - */ - function is_ajax_action( $actions ) { - // Verify it's an ajax call. - if ( ! self::is_ajax() ) { - return false; - } - - // Verify the call is relevant for the plugin. - if ( $this->_module_id != fs_request_get( 'module_id' ) ) { - return false; - } - - // Verify it's one of the specified actions. - if ( is_string( $actions ) ) { - $actions = explode( ',', $actions ); - } - - if ( is_array( $actions ) && 0 < count( $actions ) ) { - $ajax_action = fs_request_get( 'action' ); - - foreach ( $actions as $action ) { - if ( $ajax_action === $this->get_action_tag( $action ) ) { - return true; - } - } - } - - return false; - } - - /** - * Check if it's an AJAX call targeted for current request. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * - * @param array|string $actions Collection of AJAX actions. - * @param number|null $module_id - * - * @return bool - */ - static function is_ajax_action_static( $actions, $module_id = null ) { - // Verify it's an ajax call. - if ( ! self::is_ajax() ) { - return false; - } - - - if ( ! empty( $module_id ) ) { - // Verify the call is relevant for the plugin. - if ( $module_id != fs_request_get( 'module_id' ) ) { - return false; - } - } - - // Verify it's one of the specified actions. - if ( is_string( $actions ) ) { - $actions = explode( ',', $actions ); - } - - if ( is_array( $actions ) && 0 < count( $actions ) ) { - $ajax_action = fs_request_get( 'action' ); - - foreach ( $actions as $action ) { - if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) { - return true; - } - } - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @return bool - */ - static function is_cron() { - return ( defined( 'DOING_CRON' ) && DOING_CRON ); - } - - /** - * Check if a real user is visiting the admin dashboard. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @return bool - */ - function is_user_in_admin() { - return ( - is_admin() && - ! self::is_ajax() && - ! self::is_cron() && - ( 'admin-post.php' !== self::get_current_page() ) - ); - } - - /** - * Check if a real user is in the customizer view. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - static function is_customizer() { - return is_customize_preview(); - } - - /** - * Check if running in HTTPS and if site's plan matching the specified plan. - * - * @param string $plan - * @param bool $exact - * - * @return bool - */ - function is_ssl_and_plan( $plan, $exact = false ) { - return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) ); - } - - /** - * Construct plugin's settings page URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $page - * @param array $params - * @param bool|null $network - * - * @return string - */ - function _get_admin_page_url( $page = '', $params = array(), $network = null ) { - if ( is_null( $network ) ) { - $network = ( - $this->_is_network_active && - ( fs_is_network_admin() || ! $this->is_delegated_connection() ) - ); - } - - if ( 0 < count( $params ) ) { - foreach ( $params as $k => $v ) { - $params[ $k ] = urlencode( $v ); - } - } - - $page_param = $this->_menu->get_slug( $page ); - - if ( empty( $page ) && - // Show the opt-in as an overlay for free wp.org themes or themes without any settings page. - $this->show_opt_in_on_themes_page() - ) { - $params[ $this->get_unique_affix() . '_show_optin' ] = 'true'; - - return add_query_arg( - $params, - $this->admin_url( 'themes.php', 'admin', $network ) - ); - } - - if ( ! $this->has_settings_menu() ) { - if ( ! empty( $page ) ) { - // Module doesn't have a setting page, but since the request is for - // a specific Freemius page, use the admin.php path. - return add_query_arg( array_merge( $params, array( - 'page' => $page_param, - ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); - } else { - if ( $this->is_activation_mode() ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * If plugin doesn't have a settings page, create one for the opt-in screen. - */ - return add_query_arg( array_merge( $params, array( - 'page' => $this->_slug, - ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); - } else { - // Plugin without a settings page. - return add_query_arg( - $params, - $this->admin_url( 'plugins.php', 'admin', $network ) - ); - } - } - } - - // Module has a submenu settings page. - if ( ! $this->_menu->is_top_level() ) { - $parent_slug = $this->_menu->get_parent_slug(); - $menu_file = ( false !== strpos( $parent_slug, '.php' ) ) ? - $parent_slug : - 'admin.php'; - - return add_query_arg( array_merge( $params, array( - 'page' => $page_param, - ) ), $this->admin_url( $menu_file, 'admin', $network ) ); - } - - // Module has a top level CPT settings page. - if ( $this->_menu->is_cpt() ) { - if ( empty( $page ) && $this->is_activation_mode() ) { - return add_query_arg( array_merge( $params, array( - 'page' => $page_param - ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); - } else { - if ( ! empty( $page ) ) { - $params['page'] = $page_param; - } - - return add_query_arg( - $params, - $this->admin_url( $this->_menu->get_raw_slug(), 'admin', $network ) - ); - } - } - - // Module has a custom top level settings page. - return add_query_arg( array_merge( $params, array( - 'page' => $page_param, - ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); - } - - #-------------------------------------------------------------------------------- - #region Multisite - #-------------------------------------------------------------------------------- - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return bool - */ - function is_network_active() { - return $this->_is_network_active; - } - - /** - * Delegate activation for the given sites in the network (or all sites if `null`) to site admins. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param array|null $sites - */ - private function delegate_connection( $sites = null ) { - $this->_logger->entrance(); - - $this->_admin_notices->remove_sticky( 'connect_account' ); - - if ( is_null( $sites ) ) { - // All sites delegation. - $this->_storage->store( 'is_delegated_connection', true, true, true ); - } else { - // Specified sites delegation. - foreach ( $sites as $site ) { - $this->delegate_site_connection( $site['blog_id'] ); - } - } - - $this->network_upgrade_mode_completed(); - } - - /** - * Delegate specific network site conncetion to the site admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - */ - private function delegate_site_connection( $blog_id ) { - $this->_storage->store( 'is_delegated_connection', true, $blog_id, true ); - } - - /** - * Check if super-admin delegated the connection of ALL sites to the site admins. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function is_network_delegated_connection() { - if ( ! $this->_is_network_active ) { - return false; - } - - return $this->_storage->get( 'is_delegated_connection', false, true ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return bool - */ - function is_site_delegated_connection( $blog_id = 0 ) { - if ( ! $this->_is_network_active ) { - return false; - } - - if ( 0 == $blog_id ) { - $blog_id = get_current_blog_id(); - } - - return $this->_storage->get( 'is_delegated_connection', false, $blog_id ); - } - - /** - * Check if delegated the connection. When running within the the network admin, - * and haven't specified the blog ID, checks if network level delegated. If running - * within a site admin or specified a blog ID, check if delegated the connection for - * the current context site. - * - * If executed outside the the admin, check if delegated the connection - * for the current context site OR the whole network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id If set, checks if network delegated or blog specific delegated. - * - * @return bool - */ - function is_delegated_connection( $blog_id = 0 ) { - if ( ! $this->_is_network_active ) { - return false; - } - - if ( fs_is_network_admin() && 0 == $blog_id ) { - return $this->is_network_delegated_connection(); - } - - return ( - $this->is_network_delegated_connection() || - $this->is_site_delegated_connection( $blog_id ) - ); - } - - /** - * Check if the current module is active for the site. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return bool - */ - function is_active_for_site( $blog_id ) { - if ( ! is_multisite() ) { - // Not a multisite and this code is executed, means that the plugin is active. - return true; - } - - if ( $this->is_theme() ) { - // All themes are site level activated. - return true; - } - - if ( $this->_is_network_active ) { - // Plugin was network activated so it's active. - return true; - } - - return in_array( $this->_plugin_basename, (array) get_blog_option( $blog_id, 'active_plugins', array() ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return array Active & public sites collection. - */ - static function get_sites() { - if ( ! is_multisite() ) { - return array(); - } - - /** - * For consistency with get_blog_list() which only return active public sites. - * - * @author Vova Feldman (@svovaf) - */ - $args = array( - /** - * Commented out in order to handle the migration of site options whether the site is public or not. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - // 'public' => 1, - 'archived' => 0, - 'mature' => 0, - 'spam' => 0, - 'deleted' => 0, - ); - - if ( function_exists( 'get_sites' ) ) { - // For WP 4.6 and above. - return get_sites( $args ); - } else if ( function_exists( 'wp_' . 'get_sites' ) ) { - // For WP 3.7 to WP 4.5. - /** - * This is a hack suggested previously proposed by the TRT. Our SDK is compliant with older WP versions and we'd like to keep it that way. - * - * @todo Remove this hack once this false-positive error is removed from the Theme Sniffer. - * - * @since 2.3.3 - * @author Vova Feldman (@svovaf) - */ - $fn = 'wp_' . 'get_sites'; - return $fn( $args ); - } else { - // For WP 3.6 and below. - return get_blog_list( 0, 'all' ); - } - } - - /** - * Checks if a given blog is active. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param $blog_id - * - * @return bool - */ - private static function is_site_active( $blog_id ) { - global $wpdb; - - $blog_info = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d", $blog_id ) ); - - if ( ! is_object( $blog_info ) ) { - return false; - } - - return ( - true == $blog_info->public && - false == $blog_info->archived && - false == $blog_info->mature && - false == $blog_info->spam && - false == $blog_info->deleted - ); - } - - /** - * Get a mapping between the site addresses to their blog IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return array { - * @key string Site address without protocol with a trailing slash. - * @value int Site's blog ID. - * } - */ - private function get_address_to_blog_map() { - $sites = self::get_sites(); - - // Map site addresses to their blog IDs. - $address_to_blog_map = array(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $address = trailingslashit( fs_strip_url_protocol( get_site_url( $blog_id ) ) ); - $address_to_blog_map[ $address ] = $blog_id; - } - - return $address_to_blog_map; - } - - /** - * Get a mapping between the site addresses to their blog IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return array { - * @key int Site's blog ID. - * @value FS_Site Associated install. - * } - */ - function get_blog_install_map() { - $sites = self::get_sites(); - - // Map site blog ID to its install. - $install_map = array(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) ) { - $install_map[ $blog_id ] = $install; - } - } - - return $install_map; - } - - /** - * Gets a map of module IDs that the given user has opted-in to. - * - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param number $fs_user_id - * - * @return array { - * @key number $plugin_id - * @value bool Always true. - * } - */ - private static function get_user_opted_in_module_ids_map( $fs_user_id ) { - self::$_static_logger->entrance(); - - if ( ! is_multisite() ) { - $installs = array_merge( - self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ), - self::get_all_sites( WP_FS__MODULE_TYPE_THEME ) - ); - } else { - $sites = self::get_sites(); - - $installs = array(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - - $installs = array_merge( - $installs, - self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ), - self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ) - ); - } - } - - $module_ids_map = array(); - foreach ( $installs as $install ) { - if ( is_object( $install ) && - FS_Site::is_valid_id( $install->id ) && - FS_User::is_valid_id( $install->user_id ) && - ( $install->user_id == $fs_user_id ) - ) { - $module_ids_map[ $install->plugin_id ] = true; - } - } - - return $module_ids_map; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @return null|array { - * 'install' => FS_Site Module's install, - * 'blog_id' => string The associated blog ID. - * } - */ - function find_first_install() { - $sites = self::get_sites(); - - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $install = $this->get_install_by_blog_id( $blog_id ); - - if ( is_object( $install ) ) { - return array( - 'install' => $install, - 'blog_id' => $blog_id - ); - } - } - - return null; - } - - /** - * Switches the Freemius site level context to a specified blog. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * @param FS_Site $install - * - * @return bool Since 2.3.1 returns if a switch was made. - */ - function switch_to_blog( $blog_id, FS_Site $install = null ) { - if ( ! is_numeric( $blog_id ) || $blog_id == $this->_context_is_network_or_blog_id ) { - return false; - } - - switch_to_blog( $blog_id ); - $this->_context_is_network_or_blog_id = $blog_id; - - self::$_accounts->set_site_blog_context( $blog_id ); - $this->_storage->set_site_blog_context( $blog_id ); - $this->_storage->set_network_active( $this->_is_network_active, $this->is_delegated_connection( $blog_id ) ); - - $this->_site = is_object( $install ) ? - $install : - $this->get_install_by_blog_id( $blog_id ); - - $this->_user = false; - $this->_licenses = false; - $this->_license = null; - $this->is_whitelabeled = null; - - if ( is_object( $this->_site ) ) { - // Try to fetch user from install. - $this->_user = self::_get_user_by_id( $this->_site->user_id ); - - if ( ! is_object( $this->_user ) && - FS_User::is_valid_id( $this->_storage->prev_user_id ) - ) { - // Try to fetch previously saved user. - $this->_user = self::_get_user_by_id( $this->_storage->prev_user_id ); - - if ( ! is_object( $this->_user ) ) { - // Fallback to network's user. - $this->_user = $this->get_network_user(); - } - } - - $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); - - if ( ! empty( $all_plugin_licenses ) ) { - if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { - $this->_license = null; - } else { - $license_found = false; - foreach ( $all_plugin_licenses as $license ) { - if ( $license->id == $this->_site->license_id ) { - // License found. - $this->_license = $license; - $license_found = true; - break; - } - } - - if ( $license_found ) { - $this->link_license_2_user( $this->_license->id, $this->_user->id ); - } - } - - $this->_licenses = $this->get_user_licenses( $this->_user->id ); - } - } - - unset( $this->_site_api ); - unset( $this->_user_api ); - - return false; - } - - /** - * Restore the blog context to the blog that originally loaded the module. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function restore_current_blog() { - $this->switch_to_blog( $this->_blog_id ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param array|WP_Site $site - * - * @return int - */ - static function get_site_blog_id( &$site ) { - return ( $site instanceof WP_Site ) ? - $site->blog_id : - ( is_object( $site ) && isset( $site->userblog_id ) ? - $site->userblog_id : - $site['blog_id'] ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param array|WP_Site|null $site - * - * @return array - */ - function get_site_info( $site = null ) { - $this->_logger->entrance(); - - $switched = false; - - if ( is_null( $site ) ) { - $url = get_site_url(); - $name = get_bloginfo( 'name' ); - $blog_id = null; - } else { - $blog_id = self::get_site_blog_id( $site ); - - if ( get_current_blog_id() != $blog_id ) { - switch_to_blog( $blog_id ); - $switched = true; - } - - if ( $site instanceof WP_Site ) { - $url = $site->siteurl; - $name = $site->blogname; - } else { - $url = get_site_url( $blog_id ); - $name = get_bloginfo( 'name' ); - } - } - - $info = array( - 'uid' => $this->get_anonymous_id( $blog_id ), - 'url' => $url, - 'title' => $name, - 'language' => get_bloginfo( 'language' ), - 'charset' => get_bloginfo( 'charset' ), - ); - - if ( is_numeric( $blog_id ) ) { - $info['blog_id'] = $blog_id; - } - - if ( $switched ) { - restore_current_blog(); - } - - return $info; - } - - /** - * Load the module's install based on the blog ID. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int|null $blog_id - * - * @return FS_Site - */ - function get_install_by_blog_id( $blog_id = null ) { - $installs = self::get_all_sites( $this->_module_type, $blog_id ); - $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; - - if ( is_object( $install ) && - is_numeric( $install->id ) && - is_numeric( $install->user_id ) && - FS_Plugin_Plan::is_valid_id( $install->plan_id ) - ) { - // Load site. - $install = clone $install; - } - - return $install; - } - - /** - * Check if module is installed on a specified site. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int|null $blog_id - * - * @return bool - */ - function is_installed_on_site( $blog_id = null ) { - $installs = self::get_all_sites( $this->_module_type, $blog_id ); - $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; - - return ( - is_object( $install ) && - is_numeric( $install->id ) && - is_numeric( $install->user_id ) && - FS_Plugin_Plan::is_valid_id( $install->plan_id ) - ); - } - - /** - * Check if super-admin connected at least one site via the network opt-in. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function is_network_registered() { - if ( ! $this->_is_network_active ) { - return false; - } - - return FS_User::is_valid_id( $this->_storage->network_user_id ); - } - - /** - * Returns the main user associated with the network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return FS_User - */ - function get_network_user() { - if ( ! $this->_is_network_active ) { - return null; - } - - return FS_User::is_valid_id( $this->_storage->network_user_id ) ? - self::_get_user_by_id( $this->_storage->network_user_id ) : - null; - } - - /** - * Returns the current context user or the network's main user. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return FS_User - */ - function get_current_or_network_user() { - return ( $this->_user instanceof FS_User ) ? - $this->_user : - $this->get_network_user(); - } - - /** - * Returns the main install associated with the network. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return FS_Site - */ - function get_network_install() { - if ( ! $this->_is_network_active ) { - return null; - } - - return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? - $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ) : - null; - } - - /** - * Returns the blog ID that is associated with the main install. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @return int|null - */ - function get_network_install_blog_id() { - if ( ! $this->_is_network_active ) { - return null; - } - - return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? - $this->_storage->network_install_blog_id : - null; - } - - /** - * Returns the current context install or the network's main install. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return FS_Site - */ - function get_current_or_network_install() { - return ( $this->_site instanceof FS_Site ) ? - $this->_site : - $this->get_network_install(); - } - - /** - * Check if executing a site level action from the network level admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return false|int If yes, return the requested blog ID. - */ - private function is_network_level_site_specific_action() { - if ( ! $this->_is_network_active ) { - return false; - } - - if ( ! fs_is_network_admin() ) { - return false; - } - - $blog_id = fs_request_get( 'blog_id', '' ); - - return is_numeric( $blog_id ) ? $blog_id : false; - } - - /** - * Check if executing an action from the network level admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - private function is_network_level_action() { - return ( $this->_is_network_active && fs_is_network_admin() ); - } - - /** - * Needs to be executed after site deactivation, archive, deletion, or flag as spam. - * The logic updates the network level user and blog, and reschedule the crons if the cron executing site matching the site that is no longer publicly active. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $context_blog_id - */ - private function update_multisite_data_after_site_deactivation( $context_blog_id = 0 ) { - $this->_logger->entrance(); - - if ( $this->_is_network_active ) { - if ( $context_blog_id == $this->_storage->network_install_blog_id ) { - $installs_map = $this->get_blog_install_map(); - - foreach ( $installs_map as $blog_id => $install ) { - /** - * @var FS_Site $install - */ - if ( $context_blog_id == $blog_id ) { - continue; - } - - if ( $install->user_id != $this->_storage->network_user_id ) { - continue; - } - - // Switch reference to a blog that is opted-in and belong to the same super-admin. - $this->_storage->network_install_blog_id = $blog_id; - break; - } - } - } - - if ( $this->is_sync_cron_scheduled() && - $context_blog_id == $this->get_sync_cron_blog_id() - ) { - $this->schedule_sync_cron( WP_FS__SCRIPT_START_TIME, true, $context_blog_id ); - } - - if ( $this->is_install_sync_scheduled() && - $context_blog_id == $this->get_install_sync_cron_blog_id() - ) { - $this->schedule_install_sync( $context_blog_id ); - } - } - - /** - * Executed after site deactivation, archive, or flag as spam. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $context_blog_id - */ - public function _after_site_deactivated_callback( $context_blog_id = 0 ) { - $this->_logger->entrance(); - - $install = $this->get_install_by_blog_id( $context_blog_id ); - - if ( ! is_object( $install ) ) { - // Site not connected. - return; - } - - $this->update_multisite_data_after_site_deactivation( $context_blog_id ); - - $current_blog_id = get_current_blog_id(); - - $this->switch_to_blog( $context_blog_id ); - - // Send deactivation event. - $this->sync_install( array( - 'is_active' => false, - ) ); - - $this->switch_to_blog( $current_blog_id ); - } - - /** - * Executed after site deletion. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $context_blog_id - * @param bool $drop True if site's database tables should be dropped. Default is false. - */ - public function _after_site_deleted_callback( $context_blog_id = 0, $drop = false ) { - $this->_logger->entrance(); - - $install = $this->get_install_by_blog_id( $context_blog_id ); - - if ( ! is_object( $install ) ) { - // Site not connected. - return; - } - - $this->update_multisite_data_after_site_deactivation( $context_blog_id ); - - $current_blog_id = get_current_blog_id(); - - $this->switch_to_blog( $context_blog_id ); - - if ( $drop ) { - // Delete install if dropping site DB. - $this->delete_account_event(); - } else { - // Send deactivation event. - $this->sync_install( array( - 'is_active' => false, - ) ); - } - - $this->switch_to_blog( $current_blog_id ); - } - - /** - * Executed after site re-activation. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $context_blog_id - */ - public function _after_site_reactivated_callback( $context_blog_id = 0 ) { - $this->_logger->entrance(); - - $install = $this->get_install_by_blog_id( $context_blog_id ); - - if ( ! is_object( $install ) ) { - // Site not connected. - return; - } - - if ( ! self::is_site_active( $context_blog_id ) ) { - // Site not yet active (can be in spam mode, archived, deleted...). - return; - } - - $current_blog_id = get_current_blog_id(); - - $this->switch_to_blog( $context_blog_id ); - - // Send re-activation event. - $this->sync_install( array( - 'is_active' => true, - ) ); - - $this->switch_to_blog( $current_blog_id ); - } - - #endregion Multisite - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $path - * @param string $scheme - * @param bool $network - * - * @return string - */ - private function admin_url( $path = '', $scheme = 'admin', $network = true ) { - return ( $this->_is_network_active && $network ) ? - network_admin_url( $path, $scheme ) : - admin_url( $path, $scheme ); - } - - /** - * Check if currently in a specified admin page. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param string $page - * - * @return bool - */ - function is_admin_page( $page ) { - return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) ); - } - - /** - * Check if currently in the product's main admin page. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return bool - */ - function is_main_admin_page() { - return $this->is_admin_page( '' ); - } - - /** - * Get module's main admin setting page URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return string - */ - function main_menu_url() { - return $this->_menu->main_menu_url(); - } - - /** - * Check if currently on the theme's setting page or - * on any of the Freemius added pages (via tabs). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - * - * @deprecated Please use is_product_settings_page() instead; - */ - function is_theme_settings_page() { - return $this->is_product_settings_page(); - } - - /** - * Check if currently on the product's main setting page or on any of the Freemius added pages (via tabs). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - function is_product_settings_page() { - return fs_starts_with( - fs_request_get( 'page', '', 'get' ), - $this->_menu->get_slug() - ); - } - - /** - * Plugin's account page + sync license URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.9.1 - * - * @param bool|number $plugin_id - * @param bool $add_action_nonce - * @param array $params - * - * @return string - */ - function _get_sync_license_url( $plugin_id = false, $add_action_nonce = true, $params = array() ) { - if ( is_numeric( $plugin_id ) ) { - $params['plugin_id'] = $plugin_id; - } - - return $this->get_account_url( - $this->get_unique_affix() . '_sync_license', - $params, - $add_action_nonce - ); - } - - /** - * Plugin's account URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool|string $action - * @param array $params - * - * @param bool $add_action_nonce - * - * @return string - */ - function get_account_url( $action = false, $params = array(), $add_action_nonce = true ) { - if ( is_string( $action ) ) { - $params['fs_action'] = $action; - } - - self::require_pluggable_essentials(); - - return ( $add_action_nonce && is_string( $action ) ) ? - fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) : - $this->_get_admin_page_url( 'account', $params ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * - * @param string $tab - * @param bool $action - * @param array $params - * @param bool $add_action_nonce - * - * @return string - * - * @uses get_account_url() - */ - function get_account_tab_url( $tab, $action = false, $params = array(), $add_action_nonce = true ) { - $params['tab'] = $tab; - - return $this->get_account_url( $action, $params, $add_action_nonce ); - } - - /** - * Plugin's account URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool|string $topic - * @param bool|string $message - * - * @return string - */ - function contact_url( $topic = false, $message = false ) { - $params = array(); - if ( is_string( $topic ) ) { - $params['topic'] = $topic; - } - if ( is_string( $message ) ) { - $params['message'] = $message; - } - - if ( $this->is_addon() ) { - $params['addon_id'] = $this->get_id(); - - return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params ); - } else { - return $this->_get_admin_page_url( 'contact', $params ); - } - } - - /** - * Add-on direct info URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.0 - * - * @param string $slug - * - * @return string - */ - function addon_url( $slug ) { - return $this->_get_admin_page_url( 'addons', array( - 'slug' => $slug - ) ); - } - - /** - * Add-ons URL. - * - * @author Vova Feldman (@svovaf) - * @since 2.4.5 - * - * @return string - */ - function get_addons_url() { - return $this->_get_admin_page_url( 'addons' ); - } - - /* Logger - ------------------------------------------------------------------------------------------------------------------*/ - /** - * @param string $id - * @param bool $prefix_slug - * - * @return FS_Logger - */ - function get_logger( $id = '', $prefix_slug = true ) { - return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id ); - } - - /** - * Note: This method is used externally so don't delete it. - * - * @param $id - * @param bool $load_options - * @param bool $prefix_slug - * - * @return FS_Option_Manager - */ - function get_options_manager( $id, $load_options = false, $prefix_slug = true ) { - return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options ); - } - - /* Security - ------------------------------------------------------------------------------------------------------------------*/ - private static function _encrypt( $str ) { - if ( is_null( $str ) ) { - return null; - } - - /** - * The encrypt/decrypt functions are used to protect - * the user from messing up with some of the sensitive - * data stored for the module as a JSON in the database. - * - * I used the same suggested hack by the theme review team. - * For more details, look at the function `Base64UrlDecode()` - * in `./sdk/FreemiusBase.php`. - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - */ - $fn = 'base64' . '_encode'; - - return $fn( $str ); - } - - static function _decrypt( $str ) { - if ( is_null( $str ) ) { - return null; - } - - /** - * The encrypt/decrypt functions are used to protect - * the user from messing up with some of the sensitive - * data stored for the module as a JSON in the database. - * - * I used the same suggested hack by the theme review team. - * For more details, look at the function `Base64UrlDecode()` - * in `./sdk/FreemiusBase.php`. - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - */ - $fn = 'base64' . '_decode'; - - return $fn( $str ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param FS_Entity $entity - * - * @return FS_Entity Return an encrypted clone entity. - */ - private static function _encrypt_entity( FS_Entity $entity ) { - $clone = clone $entity; - $props = get_object_vars( $entity ); - - foreach ( $props as $key => $val ) { - $clone->{$key} = self::_encrypt( $val ); - } - - return $clone; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param FS_Entity $entity - * - * @return FS_Entity Return an decrypted clone entity. - */ - private static function decrypt_entity( FS_Entity $entity ) { - $clone = clone $entity; - $props = get_object_vars( $entity ); - - foreach ( $props as $key => $val ) { - $clone->{$key} = self::_decrypt( $val ); - } - - return $clone; - } - - /** - * Tries to activate account based on POST params. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @deprecated Not in use, outdated. - */ - function _activate_account() { - if ( $this->is_registered() ) { - // Already activated. - return; - } - - self::_clean_admin_content_section(); - - if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) { -// check_admin_referer( 'activate_' . $this->_plugin->public_key ); - - // Verify matching plugin details. - if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) { - return; - } - - $user = new FS_User(); - $user->id = fs_request_get( 'user_id' ); - $user->public_key = fs_request_get( 'user_public_key' ); - $user->secret_key = fs_request_get( 'user_secret_key' ); - $user->email = fs_request_get( 'user_email' ); - $user->first = fs_request_get( 'user_first' ); - $user->last = fs_request_get( 'user_last' ); - $user->is_verified = fs_request_get_bool( 'user_is_verified' ); - - $site = new FS_Site(); - $site->id = fs_request_get( 'install_id' ); - $site->public_key = fs_request_get( 'install_public_key' ); - $site->secret_key = fs_request_get( 'install_secret_key' ); - $site->plan_id = fs_request_get( 'plan_id' ); - - $plans = array(); - $plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) ); - foreach ( $plans_data as $p ) { - $plan = new FS_Plugin_Plan( $p ); - if ( $site->plan_id == $plan->id ) { - $plan->title = fs_request_get( 'plan_title' ); - $plan->name = fs_request_get( 'plan_name' ); - } - - $plans[] = $plan; - } - - $this->_set_account( $user, $site, $plans ); - - // Reload the page with the keys. - fs_redirect( $this->_get_admin_page_url() ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $email - * - * @return FS_User|false - */ - static function _get_user_by_email( $email ) { - self::$_static_logger->entrance(); - - $email = trim( strtolower( $email ) ); - - $users = self::get_all_users(); - - if ( is_array( $users ) ) { - foreach ( $users as $user ) { - if ( $email === trim( strtolower( $user->email ) ) ) { - return $user; - } - } - } - - return false; - } - - #---------------------------------------------------------------------------------- - #region Account (Loading, Updates & Activation) - #---------------------------------------------------------------------------------- - - /*** - * Load account information (user + site). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - */ - private function _load_account() { - $this->_logger->entrance(); - - $this->do_action( 'before_account_load' ); - - $users = self::get_all_users(); - $plans = self::get_all_plans( $this->_module_type ); - - if ( $this->_logger->is_on() && is_admin() ) { - $this->_logger->log( 'users = ' . var_export( $users, true ) ); - $this->_logger->log( 'plans = ' . var_export( $plans, true ) ); - } - - $site = fs_is_network_admin() ? - $this->get_network_install() : - $this->get_install_by_blog_id(); - - if ( fs_is_network_admin() && - $this->is_network_active() && - ! is_object( $site ) && - FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) - ) { - $first_install = $this->find_first_install(); - - if ( is_null( $first_install ) ) { - unset( $this->_storage->network_install_blog_id ); - } else { - $site = $first_install['install']; - $this->_storage->network_install_blog_id = $first_install['blog_id']; - } - } - - if ( is_object( $site ) && - is_numeric( $site->id ) && - is_numeric( $site->user_id ) && - FS_Plugin_Plan::is_valid_id( $site->plan_id ) - ) { - // Load site. - $this->_site = $site; - - // Load plans. - $this->_plans = $plans[ $this->_slug ]; - if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) { - $this->_sync_plans(); - } else { - for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { - if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) { - $this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] ); - } else { - unset( $this->_plans[ $i ] ); - } - } - } - } - - $user = null; - if ( fs_is_network_admin() && $this->_is_network_active ) { - $user = $this->get_network_user(); - } - - if ( is_object( $user ) ) { - $this->_user = clone $user; - } else if ( $this->_site ) { - $user = self::_get_user_by_id( $this->_site->user_id ); - - if ( ! is_object( $user ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) { - /** - * Try to load the previous owner. This recovery is used for the following use-case: - * 1. Opt-in - * 2. Cloning site1 to site2 - * 3. Ownership switch in site1 (same applies for site2) - * 4. Install data sync on site2 - * 5. Now site2's install is associated with the new owner which does not exists locally. - */ - $user = self::_get_user_by_id( $this->_storage->prev_user_id ); - } - - if ( ! is_object( $user ) ) { - /** - * This is a special fault tolerance mechanism to handle a scenario that the user data is missing. - */ - $user = $this->sync_user_by_current_install(); - } - - $this->_user = ( $user instanceof FS_User ) ? - clone $user : - null; - } - - if ( is_object( $this->_user ) ) { - // Load licenses. - $this->_licenses = $this->get_user_licenses( $this->_user->id ); - } - - if ( is_object( $this->_site ) ) { - $this->_license = $this->_get_license_by_id( $this->_site->license_id ); - - if ( $this->_site->version != $this->get_plugin_version() ) { - // If stored install version is different than current installed plugin version, - // then update plugin version event. - $this->update_plugin_version_event(); - } - } - - if ( true === $this->_storage->require_license_activation && - ! fs_request_get_bool( 'require_license', true ) - ) { - $this->_storage->require_license_activation = false; - } - - if ( $this->is_theme() ) { - $this->_register_account_hooks(); - } - } - - /** - * Special user recovery mechanism. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number|null $site_user_id - * - * @return \FS_User|mixed - */ - private function sync_user_by_current_install( $site_user_id = null ) { - $site_user_id = FS_Site::is_valid_id( $site_user_id ) ? - $site_user_id : - $this->_site->user_id; - - $api = $this->get_api_site_scope(); - - $uid = $this->get_anonymous_id(); - $request_path = "/users/{$site_user_id}.json?uid={$uid}"; - - $result = $api->get( $request_path, false, WP_FS__TIME_10_MIN_IN_SEC ); - - if ( $this->is_api_result_entity( $result ) ) { - $user = new FS_User( $result ); - $this->_user = $user; - $this->_store_user(); - - return $user; - } - - $error_code = FS_Api::get_error_code( $result ); - - if ( in_array( $error_code, array( 'invalid_unique_id', 'user_cannot_be_recovered' ) ) ) { - /** - * Those API errors will continue coming and are not recoverable with the - * current site's data. Therefore, extend the API call's cached result to 7 days. - */ - $api->update_cache_expiration( $request_path, WP_FS__TIME_WEEK_IN_SEC ); - } - - return $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param FS_User $user - * @param FS_Site $site - * @param bool|array $plans - */ - private function _set_account( FS_User $user, FS_Site $site, $plans = false ) { - $site->user_id = $user->id; - - $this->_site = $site; - $this->_user = $user; - if ( false !== $plans ) { - $this->_plans = $plans; - } - - $this->send_install_update(); - - $this->_store_account(); - - } - - /** - * Get a sanitized array with the WordPress version, SDK version, and PHP version. - * Each version is trimmed after the 16th char. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.1 - * - * @return array - */ - private function get_versions() { - $versions = array(); - $versions['platform_version'] = get_bloginfo( 'version' ); - $versions['sdk_version'] = $this->version; - $versions['programming_language_version'] = phpversion(); - - foreach ( $versions as $k => $version ) { - if ( is_string( $versions[ $k ] ) && ! empty( $versions[ $k ] ) ) { - $versions[ $k ] = substr( $versions[ $k ], 0, 16 ); - } - } - - return $versions; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return bool - */ - function has_beta_update() { - return ( - ! empty( $this->_storage->beta_data ) && - ( true === $this->_storage->beta_data['is_beta'] ) && - version_compare( $this->_storage->beta_data['version'], $this->get_plugin_version(), '>' ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return bool - */ - function is_beta() { - return ( - ! empty( $this->_storage->beta_data ) && - ( true === $this->_storage->beta_data['is_beta'] ) && - ( $this->get_plugin_version() === $this->_storage->beta_data['version'] ) - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param array $override_with - * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network. - * - * @return array - */ - function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) { - $this->_logger->entrance(); - - $current_user = self::_get_current_wp_user(); - - $activation_action = $this->get_unique_affix() . '_activate_new'; - $return_url = $this->is_anonymous() ? - // If skipped already, then return to the account page. - $this->get_account_url( $activation_action, array(), false ) : - // Return to the module's main page. - $this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) ); - - $versions = $this->get_versions(); - - $params = array_merge( $versions, array( - 'user_firstname' => $current_user->user_firstname, - 'user_lastname' => $current_user->user_lastname, - 'user_nickname' => $current_user->user_nicename, - 'user_email' => $current_user->user_email, - 'user_ip' => WP_FS__REMOTE_ADDR, - 'plugin_slug' => $this->_slug, - 'plugin_id' => $this->get_id(), - 'plugin_public_key' => $this->get_public_key(), - 'plugin_version' => $this->get_plugin_version(), - 'return_url' => fs_nonce_url( $return_url, $activation_action ), - 'account_url' => fs_nonce_url( $this->_get_admin_page_url( - 'account', - array( 'fs_action' => 'sync_user' ) - ), 'sync_user' ), - 'is_premium' => $this->is_premium(), - 'is_active' => true, - 'is_uninstalled' => false, - ) ); - - if ( $this->is_addon() ) { - $parent_fs = $this->get_parent_instance(); - - $params['parent_plugin_slug'] = $parent_fs->_slug; - $params['parent_plugin_id'] = $parent_fs->get_id(); - } - - if ( true === $network_level_or_blog_id ) { - if ( ! isset( $override_with['sites'] ) ) { - $params['sites'] = $this->get_sites_for_network_level_optin(); - } - } else { - $site = is_numeric( $network_level_or_blog_id ) ? - array( 'blog_id' => $network_level_or_blog_id ) : - null; - - $site = $this->get_site_info( $site ); - - $params = array_merge( $params, array( - 'site_uid' => $site['uid'], - 'site_url' => $site['url'], - 'site_name' => $site['title'], - 'language' => $site['language'], - 'charset' => $site['charset'], - ) ); - } - - if ( $this->is_pending_activation() && - ! empty( $this->_storage->pending_license_key ) - ) { - $params['license_key'] = $this->_storage->pending_license_key; - } - - if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) { - // Even though rand() is known for its security issues, - // the timestamp adds another layer of protection. - // It would be very hard for an attacker to get the secret key form here. - // Plus, this should never run in production since the secret should never - // be included in the production version. - $params['ts'] = WP_FS__SCRIPT_START_TIME; - $params['salt'] = md5( uniqid( rand() ) ); - $params['secure'] = md5( - $params['ts'] . - $params['salt'] . - $this->get_secret_key() - ); - } - - return array_merge( $params, $override_with ); - } - - /** - * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to. - * 2. If there was an API error, return the API result. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param string|bool $email - * @param string|bool $first - * @param string|bool $last - * @param string|bool $license_key - * @param bool $is_uninstall If "true", this means that the module is currently being uninstalled. - * In this case, the user and site info will be sent to the server but no - * data will be saved to the WP installation's database. - * @param number|bool $trial_plan_id - * @param bool $is_disconnected Whether or not to opt in without tracking. - * @param null|bool $is_marketing_allowed - * @param array $sites If network-level opt-in, an array of containing details of sites. - * - * @return string|object - * @use WP_Error - */ - function opt_in( - $email = false, - $first = false, - $last = false, - $license_key = false, - $is_uninstall = false, - $trial_plan_id = false, - $is_disconnected = false, - $is_marketing_allowed = null, - $sites = array() - ) { - $this->_logger->entrance(); - - if ( false === $email ) { - $current_user = self::_get_current_wp_user(); - $email = $current_user->user_email; - } - - /** - * @since 1.2.1 If activating with license key, ignore the context-user - * since the user will be automatically loaded from the license. - */ - if ( empty( $license_key ) ) { - // Clean up pending license if opt-ing in again. - $this->_storage->remove( 'pending_license_key' ); - - if ( ! $is_uninstall ) { - $fs_user = Freemius::_get_user_by_email( $email ); - if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) { - return $this->install_with_user( - $fs_user, - false, - $trial_plan_id, - true, - true, - $sites - ); - } - } - } - - $user_info = array(); - if ( ! empty( $email ) ) { - $user_info['user_email'] = $email; - } - if ( ! empty( $first ) ) { - $user_info['user_firstname'] = $first; - } - if ( ! empty( $last ) ) { - $user_info['user_lastname'] = $last; - } - - if ( ! empty( $sites ) ) { - $is_network = true; - - $user_info['sites'] = $sites; - } else { - $is_network = false; - } - - $params = $this->get_opt_in_params( $user_info, $is_network ); - - $filtered_license_key = false; - if ( is_string( $license_key ) ) { - $filtered_license_key = $this->apply_filters( 'license_key', $license_key ); - $params['license_key'] = $filtered_license_key; - } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { - $params['trial_plan_id'] = $trial_plan_id; - } - - if ( $is_uninstall ) { - $params['uninstall_params'] = array( - 'reason_id' => $this->_storage->uninstall_reason->id, - 'reason_info' => $this->_storage->uninstall_reason->info - ); - } - - if ( isset( $params['license_key'] ) ) { - $fs_user = Freemius::_get_user_by_email( $email ); - - if ( is_object( $fs_user ) ) { - /** - * If opting in with a context license and the context WP Admin user already opted in - * before from the current site, add the user context security params to avoid the - * unnecessary email activation when the context license is owned by the same context user. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - $params = array_merge( $params, FS_Security::instance()->get_context_params( - $fs_user, - false, - 'install_with_existing_user' - ) ); - } - } - - if ( is_bool( $is_marketing_allowed ) ) { - $params['is_marketing_allowed'] = $is_marketing_allowed; - } - - $params['is_disconnected'] = $is_disconnected; - $params['format'] = 'json'; - - $request = array( - 'method' => 'POST', - 'body' => $params, - 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, - ); - - $url = $this->add_show_pending( WP_FS__ADDRESS . '/action/service/user/install/' ); - $response = self::safe_remote_post( $url, $request ); - - if ( is_wp_error( $response ) ) { - /** - * @var WP_Error $response - */ - $result = new stdClass(); - - $error_code = $response->get_error_code(); - $error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) ); - - $result->error = (object) array( - 'type' => $error_type, - 'message' => $response->get_error_message(), - 'code' => $error_code, - 'http' => 402 - ); - - $this->maybe_modify_api_curl_error_message( $result ); - - return $result; - } - - // Module is being uninstalled, don't handle the returned data. - if ( $is_uninstall ) { - return true; - } - - /** - * When json_decode() executed on PHP 5.2 with an invalid JSON, it will throw a PHP warning. Unfortunately, the new Theme Check doesn't allow PHP silencing and the theme review team isn't open to change that, therefore, instead of using `@json_decode()` we had to use the method without the `@` directive. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * @link https://themes.trac.wordpress.org/ticket/46134#comment:5 - * @link https://themes.trac.wordpress.org/ticket/46134#comment:9 - * @link https://themes.trac.wordpress.org/ticket/46134#comment:12 - * @link https://themes.trac.wordpress.org/ticket/46134#comment:14 - */ - $decoded = is_string( $response['body'] ) ? - json_decode( $response['body'] ) : - null; - - if ( empty( $decoded ) ) { - return false; - } - - if ( ! $this->is_api_result_object( $decoded ) ) { - if ( ! empty( $params['license_key'] ) ) { - // Pass the fully entered license key to the failure handler. - $params['license_key'] = $license_key; - } - - return $is_uninstall ? - $decoded : - $this->apply_filters( 'after_install_failure', $decoded, $params ); - } else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) { - if ( $is_network ) { - $site_ids = array(); - foreach ( $sites as $site ) { - $site_ids[] = $site['blog_id']; - } - - /** - * Store the sites so that they can be installed once the user has clicked on the activation link - * in the email. - * - * @author Leo Fajardo (@leorw) - */ - $this->_storage->pending_sites_info = array( - 'blog_ids' => $site_ids, - 'license_key' => $license_key, - 'trial_plan_id' => $trial_plan_id - ); - } - - // Pending activation, add message. - return $this->set_pending_confirmation( - ( isset( $decoded->email ) ? - $decoded->email : - true ), - false, - $filtered_license_key, - ! empty( $params['trial_plan_id'] ) - ); - } else if ( isset( $decoded->install_secret_key ) ) { - return $this->install_with_new_user( - $decoded->user_id, - $decoded->user_public_key, - $decoded->user_secret_key, - ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? - $decoded->is_marketing_allowed : - null ), - ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? - $decoded->is_extensions_tracking_allowed : - null ), - $decoded->install_id, - $decoded->install_public_key, - $decoded->install_secret_key, - false - ); - } else if ( is_array( $decoded->installs ) ) { - return $this->install_many_with_new_user( - $decoded->user_id, - $decoded->user_public_key, - $decoded->user_secret_key, - ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? - $decoded->is_marketing_allowed : - null ), - ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? - $decoded->is_extensions_tracking_allowed : - null ), - $decoded->installs, - false - ); - } - - return $decoded; - } - - /** - * Set user and site identities. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param FS_User $user - * @param FS_Site $site - * @param bool $redirect - * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will - * redirect (or return a URL) to the account page with a special parameter to - * trigger the auto installation processes. - * - * @return string If redirect is `false`, returns the next page the user should be redirected to. - */ - function setup_account( - FS_User $user, - FS_Site $site, - $redirect = true, - $auto_install = false - ) { - return $this->setup_network_account( - $user, - array( $site ), - $redirect, - $auto_install, - false - ); - } - - /** - * Set user and site identities. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param FS_User $user - * @param FS_Site[] $installs - * @param bool $redirect - * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. - * @param bool $is_network_level_opt_in - * - * @return string If redirect is `false`, returns the next page the user should be redirected to. - */ - function setup_network_account( - FS_User $user, - array $installs, - $redirect = true, - $auto_install = false, - $is_network_level_opt_in = true - ) { - $first_install = $installs[0]; - - $this->_user = $user; - $this->_site = $first_install; - - $this->_sync_plans(); - - if ( $this->_storage->handle_gdpr_admin_notice && - $this->should_handle_gdpr_admin_notice() && - FS_GDPR_Manager::instance()->should_show_opt_in_notice() - ) { - /** - * Clear user lock after an opt-in. - */ - require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; - FS_User_Lock::instance()->unlock(); - } - - if ( 1 < count( $installs ) ) { - // Only network level opt-in can have more than one install. - $is_network_level_opt_in = true; - } -// $is_network_level_opt_in = self::is_ajax_action_static( 'network_activate', $this->_module_id ); - // If Freemius was OFF before, turn it on. - $this->turn_on(); - - $this->handle_account_connection( - $installs, - ( ! $this->_is_network_active || ! $is_network_level_opt_in ) - ); - - if ( is_numeric( $first_install->license_id ) ) { - $this->set_license( $this->_get_license_by_id( $first_install->license_id ) ); - } - - $this->_admin_notices->remove_sticky( 'connect_account' ); - - if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) { - // Remove pending activation sticky notice (if still exist). - $this->_admin_notices->remove_sticky( 'activation_pending' ); - - // Remove plugin from pending activation mode. - unset( $this->_storage->is_pending_activation ); - - if ( ! $this->is_paying_or_trial() ) { - $this->_admin_notices->add_sticky( - sprintf( $this->get_text_inline( '%s activation was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), - 'activation_complete' - ); - } - } - - if ( $this->is_paying_or_trial() ) { - if ( ! $this->is_premium() || - ! $this->has_premium_version() || - ! $this->has_settings_menu() - ) { - if ( $this->is_paying() ) { - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ), - $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - } else { - $trial_plan = $this->get_trial_plan(); - - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), - '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $trial_plan->title ), - 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - } - } - - $this->_admin_notices->remove_sticky( array( - 'trial_promotion', - ) ); - } - - $plugin_id = fs_request_get( 'plugin_id', false ); - - // Store activation time ONLY for plugins & themes (not add-ons). - if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) { - if ( empty( $this->_storage->activation_timestamp ) ) { - $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; - } - } - - $next_page = ''; - - $extra = array(); - if ( $auto_install ) { - $extra['auto_install'] = 'true'; - } - - if ( is_numeric( $plugin_id ) ) { - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.1.6 - * - * Also sync the license after an anonymous user subscribes. - */ - if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) { - // Add-on was installed - sync license right after install. - $next_page = $this->_get_sync_license_url( $plugin_id, true, $extra ); - } - } else { - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.9 If site installed with a valid license, sync license. - */ - if ( $this->is_paying() ) { - $this->_sync_plugin_license( - true, - // Installs data is already synced in the beginning of this method directly or via _set_account(). - false - ); - } - - // Reload the page with the keys. - $next_page = $this->is_anonymous() ? - // If user previously skipped, redirect to account page. - $this->get_account_url( false, $extra ) : - $this->get_after_activation_url( 'after_connect_url', array(), $is_network_level_opt_in ); - } - - if ( ! empty( $next_page ) && $redirect ) { - fs_redirect( $next_page ); - } - - return $next_page; - } - - /** - * Install plugin with new user information after approval. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function _install_with_new_user() { - $this->_logger->entrance(); - - if ( $this->is_registered() ) { - return; - } - - if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) || - // @todo This logic should be improved because it's executed on every load of a theme. - $this->is_theme() - ) { -// check_admin_referer( $this->_slug . '_activate_new' ); - - if ( fs_request_has( 'user_secret_key' ) ) { - if ( fs_is_network_admin() && isset( $this->_storage->pending_sites_info ) ) { - $pending_sites_info = $this->_storage->pending_sites_info; - - $this->install_many_pending_with_user( - fs_request_get( 'user_id' ), - fs_request_get( 'user_public_key' ), - fs_request_get( 'user_secret_key' ), - fs_request_get_bool( 'is_marketing_allowed', null ), - fs_request_get_bool( 'is_extensions_tracking_allowed', null ), - $pending_sites_info['blog_ids'], - $pending_sites_info['license_key'], - $pending_sites_info['trial_plan_id'] - ); - } else { - $this->install_with_new_user( - fs_request_get( 'user_id' ), - fs_request_get( 'user_public_key' ), - fs_request_get( 'user_secret_key' ), - fs_request_get_bool( 'is_marketing_allowed', null ), - fs_request_get_bool( 'is_extensions_tracking_allowed', null ), - fs_request_get( 'install_id' ), - fs_request_get( 'install_public_key' ), - fs_request_get( 'install_secret_key' ), - true, - fs_request_get_bool( 'auto_install' ) - ); - } - } else if ( fs_request_has( 'pending_activation' ) ) { - $this->set_pending_confirmation( fs_request_get( 'user_email' ), true ); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $id - * @param string $public_key - * @param string $secret_key - * - * @return \FS_User - */ - private function setup_user( $id, $public_key, $secret_key ) { - $user = self::_get_user_by_id( $id ); - - if ( is_object( $user ) ) { - $this->_user = $user; - } else { - $user = new FS_User(); - $user->id = $id; - $user->public_key = $public_key; - $user->secret_key = $secret_key; - - $this->_user = $user; - $user_result = $this->get_api_user_scope()->get(); - $user = new FS_User( $user_result ); - - $this->_user = $user; - $this->_store_user(); - } - - return $user; - } - - /** - * Install plugin with new user. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param number $user_id - * @param string $user_public_key - * @param string $user_secret_key - * @param bool|null $is_marketing_allowed - * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 - * @param number $install_id - * @param string $install_public_key - * @param string $install_secret_key - * @param bool $redirect - * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. - * - * @return string If redirect is `false`, returns the next page the user should be redirected to. - */ - private function install_with_new_user( - $user_id, - $user_public_key, - $user_secret_key, - $is_marketing_allowed, - $is_extensions_tracking_allowed, - $install_id, - $install_public_key, - $install_secret_key, - $redirect = true, - $auto_install = false - ) { - /** - * This method is also executed after opting in with a license key since the - * license can be potentially associated with a different owner. - * - * @since 2.0.0 - */ - $user = self::_get_user_by_id( $user_id ); - - if ( ! is_object( $user ) ) { - $user = new FS_User(); - $user->id = $user_id; - $user->public_key = $user_public_key; - $user->secret_key = $user_secret_key; - - $this->_user = $user; - $user_result = $this->get_api_user_scope()->get(); - $user = new FS_User( $user_result ); - } - - $this->_user = $user; - - $site = new FS_Site(); - $site->id = $install_id; - $site->public_key = $install_public_key; - $site->secret_key = $install_secret_key; - - $this->_site = $site; - $site_result = $this->get_api_site_scope()->get(); - $site = new FS_Site( $site_result ); - $this->_site = $site; - - if ( ! is_null( $is_marketing_allowed ) ) { - $this->disable_opt_in_notice_and_lock_user(); - } - - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - - return $this->setup_account( - $this->_user, - $this->_site, - $redirect, - $auto_install - ); - } - - /** - * Install plugin with user. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param number $user_id - * @param string $user_public_key - * @param string $user_secret_key - * @param bool|null $is_marketing_allowed - * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 - * @param array $site_ids - * @param bool $license_key - * @param bool $trial_plan_id - * @param bool $redirect - * - * @return string If redirect is `false`, returns the next page the user should be redirected to. - */ - private function install_many_pending_with_user( - $user_id, - $user_public_key, - $user_secret_key, - $is_marketing_allowed, - $is_extensions_tracking_allowed, - $site_ids, - $license_key = false, - $trial_plan_id = false, - $redirect = true - ) { - $user = $this->setup_user( $user_id, $user_public_key, $user_secret_key ); - - if ( ! is_null( $is_marketing_allowed ) ) { - $this->disable_opt_in_notice_and_lock_user(); - } - - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - - $sites = array(); - foreach ( $site_ids as $site_id ) { - $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); - } - - $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); - } - - /** - * Multi-site install with a new user. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $user_id - * @param string $user_public_key - * @param string $user_secret_key - * @param bool|null $is_marketing_allowed - * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 - * @param object[] $installs - * @param bool $redirect - * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. - * - * @return string If redirect is `false`, returns the next page the user should be redirected to. - */ - private function install_many_with_new_user( - $user_id, - $user_public_key, - $user_secret_key, - $is_marketing_allowed, - $is_extensions_tracking_allowed, - array $installs, - $redirect = true, - $auto_install = false - ) { - $this->setup_user( $user_id, $user_public_key, $user_secret_key ); - - if ( ! is_null( $is_marketing_allowed ) ) { - $this->disable_opt_in_notice_and_lock_user(); - } - - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - - $install_ids = array(); - - foreach ( $installs as $install ) { - $install_ids[] = $install->id; - } - - $left = count( $install_ids ); - $offset = 0; - - $installs = array(); - while ( $left > 0 ) { - $result = $this->get_api_user_scope()->get( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', array_slice( $install_ids, $offset, 25 ) ) ); - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - // @todo Handle API error. - } - - $installs = array_merge( $installs, $result->installs ); - - $left -= 25; - } - - foreach ( $installs as &$install ) { - $install = new FS_Site( $install ); - } - - return $this->setup_network_account( - $this->_user, - $installs, - $redirect, - $auto_install - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param string|bool $email - * @param bool $redirect - * @param string|bool $license_key Since 1.2.1.5 - * @param bool $is_pending_trial Since 1.2.1.5 - * - * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page. - */ - private function set_pending_confirmation( - $email = false, - $redirect = true, - $license_key = false, - $is_pending_trial = false - ) { - if ( $this->_ignore_pending_mode ) { - /** - * If explicitly asked to ignore pending mode, set to anonymous mode - * if require confirmation before finalizing the opt-in. - * - * @author Vova Feldman - * @since 1.2.1.6 - */ - $this->skip_connection( null, fs_is_network_admin() ); - } else { - // Install must be activated via email since - // user with the same email already exist. - $this->_storage->is_pending_activation = true; - $this->_add_pending_activation_notice( $email, $is_pending_trial ); - } - - if ( ! empty( $license_key ) ) { - $this->_storage->pending_license_key = $license_key; - } - - // Remove the opt-in sticky notice. - $this->_admin_notices->remove_sticky( array( - 'connect_account', - 'trial_promotion', - ) ); - - $next_page = $this->get_after_activation_url( 'after_pending_connect_url' ); - - // Reload the page with with pending activation message. - if ( $redirect ) { - fs_redirect( $next_page ); - } - - return $next_page; - } - - /** - * Install plugin with current logged WP user info. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function _install_with_current_user() { - $this->_logger->entrance(); - - if ( $this->is_registered() ) { - return; - } - - if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) { -// check_admin_referer( 'activate_existing_' . $this->_plugin->public_key ); - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.9 Add license key if given. - */ - $license_key = fs_request_get( 'license_secret_key' ); - - $this->update_extensions_tracking_flag( fs_request_get_bool( 'is_extensions_tracking_allowed', null ) ); - - $this->install_with_current_user( $license_key ); - } - } - - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @param string|bool $license_key - * @param number|bool $trial_plan_id - * @param array $sites Since 2.0.0 - * @param bool $redirect - * - * @return object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. - */ - private function install_with_current_user( - $license_key = false, - $trial_plan_id = false, - $sites = array(), - $redirect = true - ) { - // Get current logged WP user. - $current_user = self::_get_current_wp_user(); - - // Find the relevant FS user by the email. - $user = self::_get_user_by_email( $current_user->user_email ); - - return $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * @param string|bool $license_key - * @param number|bool $trial_plan_id - * @param bool $redirect - * @param bool $setup_account Since 2.0.0. When set to FALSE, executes a light installation without setting up the account as if it's the first opt-in. - * @param array $sites Since 2.0.0. If not empty, should be a collection of site details for the bulk install API request. - * - * @return \FS_Site|object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. If $setup_account is set to `false`, return the newly created install. - */ - function install_with_user( - FS_User $user, - $license_key = false, - $trial_plan_id = false, - $redirect = true, - $setup_account = true, - $sites = array() - ) { - // We have to set the user before getting user scope API handler. - $this->_user = $user; - - // Install the plugin. - $result = $this->create_installs_with_user( - $user, - $license_key, - $trial_plan_id, - $sites, - $redirect - ); - - if ( ! $this->is_api_result_entity( $result ) && - ! $this->is_api_result_object( $result, 'installs' ) - ) { - // @todo Handler potential API error of the $result - } - - if ( empty( $sites ) ) { - $site = new FS_Site( $result ); - $this->_site = $site; - - if ( ! $setup_account ) { - $this->_store_site(); - - $this->sync_plan_if_not_exist( $site->plan_id ); - - if ( ! empty( $license_key ) && FS_Plugin_License::is_valid_id( $site->license_id ) ) { - $this->sync_license_if_not_exist( $site->license_id, $license_key ); - } - - $this->_admin_notices->remove_sticky( 'connect_account', false ); - - return $site; - } - - return $this->setup_account( $this->_user, $this->_site, $redirect ); - } else { - $installs = array(); - foreach ( $result->installs as $install ) { - $installs[] = new FS_Site( $install ); - } - - return $this->setup_network_account( - $user, - $installs, - $redirect - ); - } - } - - /** - * Initiate an API request to create a collection of installs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * @param bool $license_key - * @param bool $trial_plan_id - * @param array $sites - * @param bool $redirect - * @param bool $silent - * - * @return object|mixed - */ - private function create_installs_with_user( - FS_User $user, - $license_key = false, - $trial_plan_id = false, - $sites = array(), - $redirect = false, - $silent = false - ) { - $extra_install_params = array( - 'uid' => $this->get_anonymous_id(), - 'is_disconnected' => false, - ); - - if ( ! empty( $license_key ) ) { - $extra_install_params['license_key'] = $this->apply_filters( 'license_key', $license_key ); - - if ( $silent ) { - $extra_install_params['ignore_license_owner'] = true; - } - } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { - $extra_install_params['trial_plan_id'] = $trial_plan_id; - } - - if ( ! empty( $sites ) ) { - $extra_install_params['sites'] = $sites; - } - - $args = $this->get_install_data_for_api( $extra_install_params, false, false ); - - // Install the plugin. - $result = $this->get_api_user_scope_by_user( $user )->call( - "/plugins/{$this->get_id()}/installs.json", - 'post', - $args - ); - - if ( ! $this->is_api_result_entity( $result ) && - ! $this->is_api_result_object( $result, 'installs' ) - ) { - if ( ! empty( $args['license_key'] ) ) { - // Pass the fully entered license key to the failure handler. - $args['license_key'] = $license_key; - } - - $result = $this->apply_filters( 'after_install_failure', $result, $args ); - - if ( ! $silent ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . - $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $result->error->message . '', - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - } - - if ( $redirect ) { - /** - * We set the user before getting the user scope API handler, so the user became temporarily - * registered (`is_registered() = true`). Since the API returned an error and we will redirect, - * we have to set the user to `null`, otherwise, the user will be redirected to the wrong - * activation page based on the return value of `is_registered()`. In addition, in case the - * context plugin doesn't have a settings menu and the default page is the `Plugins` page, - * misleading plugin activation errors will be shown on the `Plugins` page. - * - * @author Leo Fajardo (@leorw) - */ - $this->_user = null; - - fs_redirect( $this->get_activation_url( array( 'error' => $result->error->message ) ) ); - } - } - - return $result; - } - - /** - * Tries to activate add-on account based on parent plugin info. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param Freemius $parent_fs - * @param bool|int|null $network_level_or_blog_id True for network level opt-in and integer for opt-in for specified blog in the network. - * @param FS_Plugin_License $bundle_license Since 2.4.0. If provided, this license will be activated for the add-on. - */ - private function _activate_addon_account( - Freemius $parent_fs, - $network_level_or_blog_id = null, - FS_Plugin_License $bundle_license = null - ) { - if ( $this->is_registered() ) { - // Already activated. - return; - } - - /** - * Do not override the `uid` if network-level opt-in since the call to `get_sites_for_network_level_optin()` - * already returns the data for the current blog. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $uid_param_to_override = ( true === $network_level_or_blog_id ) ? - array() : - array( 'uid' => $this->get_anonymous_id() ); - - $params = $this->get_install_data_for_api( - $uid_param_to_override, - false, - false, - /** - * Do not include the data for the current blog if network-level opt-in since the call to `get_sites_for_network_level_optin` - * already includes the data for it. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - ( true !== $network_level_or_blog_id ) - ); - - if ( true === $network_level_or_blog_id ) { - $params['sites'] = $this->get_sites_for_network_level_optin(); - - if ( empty( $params['sites'] ) ) { - return; - } - } - - if ( is_object( $bundle_license ) ) { - $params['license_key'] = $bundle_license->secret_key; - } - - // Activate add-on with parent plugin credentials. - $result = $parent_fs->get_api_site_scope()->call( - "/addons/{$this->_plugin->id}/installs.json", - 'post', - $params - ); - - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - if ( is_object( $bundle_license ) ) { - /** - * When a license object is provided, it's an attempt by the SDK to activate a bundle license and not a user-initiated action, therefore, do not show any admin notice to avoid confusion (e.g.: the notice will show up just above the opt-in link). If the license activation fails, the admin will see an opt-in link instead. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - */ - } else { - $error_message = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - $this->get_text_inline( 'An unknown error has occurred.', 'unknown-error' ); - - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . - $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $error_message . '', - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - } - - return; - } - - $addon_installs = $result->installs; - foreach ( $addon_installs as $key => $addon_install ) { - $addon_installs[ $key ] = new FS_Site( $addon_install ); - } - - $first_install = $addon_installs[0]; - - // Get user information based on parent's plugin. - $user = $parent_fs->get_user(); - - // First of all, set site and user info - otherwise we won't - // be able to invoke API calls. - $this->_site = $first_install; - $this->_user = $user; - - // Sync add-on plans. - $this->_sync_plans(); - - $this->handle_account_connection( $addon_installs, ! fs_is_network_admin() ); - - // Get site's current plan. - //$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id ); - - // Sync licenses. - $this->_sync_licenses(); - - if ( ! fs_is_network_admin() ) { - // Try to activate premium license. - $this->_activate_license( true, $bundle_license ); - - if ( is_object( $bundle_license ) ) { - $this->maybe_activate_bundle_license( $bundle_license ); - } - } else { - if ( is_object( $bundle_license ) ) { - $premium_license = $bundle_license; - } else { - $license_id = fs_request_get( 'license_id' ); - - if ( is_object( $this->_site ) && - FS_Plugin_License::is_valid_id( $license_id ) && - $license_id == $this->_site->license_id - ) { - // License is already activated. - return; - } - - $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? - $this->_get_license_by_id( $license_id ) : - $this->_get_available_premium_license(); - } - - if ( is_object( $premium_license ) ) { - $this->maybe_network_activate_addon_license( $premium_license ); - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param FS_Site[] $installs - * @param bool $is_site_level - */ - private function handle_account_connection( $installs, $is_site_level ) { - $first_install = $installs[0]; - - if ( $is_site_level ) { - $this->_set_account( $this->_user, $first_install ); - - $this->do_action( 'after_account_connection', $this->_user, $first_install ); - } else { - $this->_store_user(); - - // Map site addresses to their blog IDs. - $address_to_blog_map = $this->get_address_to_blog_map(); - - $first_blog_id = null; - $blog_2_install_map = array(); - foreach ( $installs as $install ) { - $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); - $blog_id = $address_to_blog_map[ $address ]; - - $this->_store_site( true, $blog_id, $install ); - - if ( is_null( $first_blog_id ) ) { - $first_blog_id = $blog_id; - } - - $blog_2_install_map[ $blog_id ] = $install; - } - - if ( ! FS_User::is_valid_id( $this->_storage->network_user_id ) || - ! is_object( self::_get_user_by_id( $this->_storage->network_user_id ) ) - ) { - // Store network user. - $this->_storage->network_user_id = $this->_user->id; - } - - if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { - $this->_storage->network_install_blog_id = $first_blog_id; - } - - if ( count( $installs ) === count( $address_to_blog_map ) ) { - // Super admin opted in for all sites in the network. - $this->_storage->is_network_connected = true; - } - - $this->_store_licenses( false ); - - self::$_accounts->store(); - - // Don't sync the installs data on network upgrade - if ( ! $this->network_upgrade_mode_completed() ) { - $this->send_installs_update(); - } - - // Switch install context back to the first install. - $this->_site = $first_install; - - $current_blog = get_current_blog_id(); - - foreach ( $blog_2_install_map as $blog_id => $install ) { - $this->switch_to_blog( $blog_id ); - - $this->do_action( 'after_account_connection', $this->_user, $install ); - } - - $this->switch_to_blog( $current_blog ); - - $this->do_action( 'after_network_account_connection', $this->_user, $blog_2_install_map ); - } - } - - /** - * Tries to activate parent account based on add-on's info. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param Freemius $parent_fs - */ - private function activate_parent_account( Freemius $parent_fs ) { - if ( ! $this->is_addon() ) { - // This is not an add-on. - return; - } - - if ( $parent_fs->is_registered() ) { - // Already activated. - return; - } - - // Activate parent with add-on's user credentials. - $parent_install = $this->get_api_user_scope()->call( - "/plugins/{$parent_fs->_plugin->id}/installs.json", - 'post', - $parent_fs->get_install_data_for_api( array( - 'uid' => $parent_fs->get_anonymous_id(), - ), false, false ) - ); - - if ( isset( $parent_install->error ) ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . - $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $parent_install->error->message . '', - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - - return; - } - - $parent_fs->_admin_notices->remove_sticky( 'connect_account' ); - - if ( $parent_fs->is_pending_activation() ) { - $parent_fs->_admin_notices->remove_sticky( 'activation_pending' ); - - unset( $parent_fs->_storage->is_pending_activation ); - } - - // Get user information based on parent's plugin. - $user = $this->get_user(); - - // First of all, set site info - otherwise we won't - // be able to invoke API calls. - $parent_fs->_site = new FS_Site( $parent_install ); - $parent_fs->_user = $user; - - // Sync add-on plans. - $parent_fs->_sync_plans(); - - $parent_fs->_set_account( $user, $parent_fs->_site ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Admin Menu Items - #---------------------------------------------------------------------------------- - - private $_menu_items = array(); - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.8 - * - * @return array - */ - function get_menu_items() { - return $this->_menu_items; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return string - */ - function get_menu_slug() { - return $this->_menu->get_slug(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function _prepare_admin_menu() { -// if ( ! $this->is_on() ) { -// return; -// } - - /** - * When running from a site admin with a network activated module and the connection - * was NOT delegated and the user still haven't skipped or opted-in, then hide the - * site level settings. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - $should_hide_site_admin_settings = ( - $this->_is_network_active && - ! fs_is_network_admin() && - ! $this->is_delegated_connection() && - ! $this->is_anonymous() && - ! $this->is_registered() - ); - - $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); - - if ( ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || - $should_hide_site_admin_settings - ) { - $this->_menu->remove_menu_item( $should_hide_site_admin_settings ); - } else { - $this->do_action( fs_is_network_admin() ? - 'before_network_admin_menu_init' : - 'before_admin_menu_init' - ); - - $this->add_menu_action(); - - $this->add_network_menu_when_missing(); - - $this->add_submenu_items(); - } - } - - /** - * Admin dashboard menu items modifications. - * - * NOTE: admin_menu action executed before admin_init. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - */ - private function add_menu_action() { - if ( $this->is_activation_mode() ) { - if ( $this->show_opt_in_on_setting_page() ) { - $this->override_plugin_menu_with_activation(); - } else { - /** - * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page. - */ - if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { - add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) ); - } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) || - fs_request_get_bool( 'pending_activation' ) - ) { - add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) ); - } - } - } else { - if ( ! $this->is_registered() ) { - // If not registered try to install user. - if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { - $this->_install_with_new_user(); - } - } else if ( - fs_request_is_action( 'sync_user' ) && - ( ! $this->has_settings_menu() || $this->show_opt_in_on_themes_page() ) - ) { - $this->_handle_account_user_sync(); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - */ - function _redirect_on_clicked_menu_link() { - $this->_logger->entrance(); - - $page = fs_request_get('page'); - $page = is_string($page) ? strtolower($page) : ''; - - $this->_logger->log( 'page = ' . $page ); - - foreach ( $this->_menu_items as $priority => $items ) { - foreach ( $items as $item ) { - if ( isset( $item['url'] ) ) { - if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) { - $this->_logger->log( 'Redirecting to ' . $item['url'] ); - - fs_redirect( $item['url'] ); - } - } - } - } - } - - /** - * Remove plugin's all admin menu items & pages, and replace with activation page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - */ - private function override_plugin_menu_with_activation() { - $this->_logger->entrance(); - - $hook = false; - - if ( ! $this->has_settings_menu() ) { - // Add the opt-in page without a menu item. - $hook = FS_Admin_Menu_Manager::add_subpage( - null, - $this->get_plugin_name(), - $this->get_plugin_name(), - 'manage_options', - $this->_slug, - array( &$this, '_connect_page_render' ) - ); - } else if ( $this->_menu->is_top_level() ) { - if ( $this->_menu->is_override_exact() ) { - // Make sure the current page is matching the activation page. - if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { - return; - } - } - - $hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) ); - - if ( false === $hook ) { - // Create new menu item just for the opt-in. - $hook = FS_Admin_Menu_Manager::add_page( - $this->get_plugin_name(), - $this->get_plugin_name(), - 'manage_options', - $this->_menu->get_slug(), - array( &$this, '_connect_page_render' ) - ); - } - } else { - $menus = array( $this->_menu->get_parent_slug() ); - - if ( $this->_menu->is_override_exact() ) { - // Make sure the current page is matching the activation page. - if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { - return; - } - } - - foreach ( $menus as $parent_slug ) { - $hook = $this->_menu->override_submenu_action( - $parent_slug, - $this->_menu->get_raw_slug(), - array( &$this, '_connect_page_render' ) - ); - - if ( false !== $hook ) { - // Found plugin's submenu item. - break; - } - } - } - - if ( $this->is_activation_page() ) { - // Clean admin page from distracting content. - self::_clean_admin_content_section(); - } - - if ( false !== $hook ) { - if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { - $this->_install_with_current_user(); - } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { - $this->_install_with_new_user(); - } - } - } - - /** - * If a plugin was network activated and connected but don't have a network - * level settings, then add an artificial menu item for the Account and other - * Freemius settings. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private function add_network_menu_when_missing() { - $this->_logger->entrance(); - - if ( ! $this->_is_network_active ) { - // Plugin wasn't activated on the network level. - return; - } - - if ( ! fs_is_network_admin() ) { - // The context is not the network admin. - return; - } - - if ( $this->_menu->has_network_menu() ) { - // Plugin already has a network level menu. - return; - } - - if ( $this->is_network_activation_mode() ) { - /** - * Do not add during activation mode, otherwise, there will be duplicate menus while the opt-in - * screen is being shown. - * - * @author Leo Fajardo (@leorw) - */ - return; - } - - if ( ! WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED ) { - if ( $this->is_network_delegated_connection() ) { - // Super-admin delegated the connection to the site admins. - return; - } - } - - if ( ! $this->_menu->has_menu() || $this->_menu->is_top_level() ) { - - if ( $this->_menu->has_menu() || - ! $this->is_addon() || - $this->is_activation_mode() - ) { - $this->_dynamically_added_top_level_page_hook_name = $this->_menu->add_page_and_update( - $this->get_plugin_name(), - $this->get_plugin_name(), - 'manage_options', - $this->_menu->has_menu() ? $this->_menu->get_slug() : $this->_slug - ); - } - } else { - $this->_menu->add_subpage_and_update( - $this->_menu->get_parent_slug(), - $this->get_plugin_name(), - $this->get_plugin_name(), - 'manage_options', - $this->_menu->get_slug() - ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.1 - * - * return string - */ - function get_top_level_menu_capability() { - global $menu; - - $top_level_menu_slug = $this->get_top_level_menu_slug(); - - foreach ( $menu as $menu_info ) { - /** - * The second element in the menu info array is the capability/role that has access to the menu and the - * third element is the menu slug. - */ - if ( $menu_info[2] === $top_level_menu_slug ) { - return $menu_info[1]; - } - } - - return 'read'; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * - * @return string - */ - private function get_top_level_menu_slug() { - return ( $this->is_addon() ? - $this->get_parent_instance()->_menu->get_top_level_menu_slug() : - $this->_menu->get_top_level_menu_slug() ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return string - */ - function get_pricing_cta_label() { - $label = $this->get_text_inline( 'Upgrade', 'upgrade' ); - - if ( $this->is_in_trial_promotion() && - ! $this->is_paying_or_trial() - ) { - // If running a trial promotion, modify the pricing to load the trial. - $label = $this->get_text_inline( 'Start Trial', 'start-trial' ); - } else if ( $this->is_paying() ) { - $label = $this->get_text_inline( 'Pricing', 'pricing' ); - } - - return $label; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - function is_pricing_page_visible() { - return ( - // Has at least one paid plan. - $this->has_paid_plan() && - // Didn't ask to hide the pricing page. - $this->is_page_visible( 'pricing' ) && - // Don't have a valid active license or has more than one plan. - ( ! $this->is_paying() || ! $this->is_single_plan( true ) ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param bool $is_activation_mode - * - * @return bool - */ - private function should_add_submenu_or_action_links( $is_activation_mode ) { - if ( $this->is_addon() ) { - // No submenu items or action links for add-ons. - return false; - } - - if ( $this->show_opt_in_on_themes_page() ) { - if ( ! fs_is_network_admin() ) { - // Also add action links or submenu items when running in a free .org theme so the tabs will be visible. - return true; - } - } else if ( $is_activation_mode ) { - // Don't show submenu-items/tabs in activation mode, unless it's a wp.org theme. - return false; - } - - if ( fs_is_network_admin() ) { - /** - * Add submenu items or action links to network level when plugin was network activated and the super - * admin did NOT delegate the connection of all sites to site admins. - */ - return ( - $this->_is_network_active && - ( WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED || - ! $this->is_network_delegated_connection() ) - ); - } - - return ( ! $this->_is_network_active || $this->is_delegated_connection() ); - } - - /** - * Add default Freemius menu items. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible. - */ - private function add_submenu_items() { - $this->_logger->entrance(); - - $is_activation_mode = $this->is_activation_mode(); - - $add_submenu_items = $this->should_add_submenu_or_action_links( $is_activation_mode ); - - if ( $add_submenu_items ) { - if ( $this->has_affiliate_program() ) { - // Add affiliation page. - $this->add_submenu_item( - $this->get_text_inline( 'Affiliation', 'affiliation' ), - array( &$this, '_affiliation_page_render' ), - $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Affiliation', 'affiliation' ), - 'manage_options', - 'affiliation', - 'Freemius::_clean_admin_content_section', - WP_FS__DEFAULT_PRIORITY, - $this->is_submenu_item_visible( 'affiliation' ) - ); - } - } - - if ( $add_submenu_items || - ( $is_activation_mode && - $this->is_only_premium() && - $this->is_admin_page( 'account' ) && - fs_request_is_action( $this->get_unique_affix() . '_sync_license' ) - ) - ) { - if ( ! WP_FS__DEMO_MODE && $this->is_registered() ) { - $show_account = ( - $this->is_submenu_item_visible( 'account' ) && - /** - * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans. - */ - ( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() ) - ); - - // Add user account page. - $this->add_submenu_item( - $this->get_text_inline( 'Account', 'account' ), - array( &$this, '_account_page_render' ), - $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Account', 'account' ), - 'manage_options', - 'account', - array( &$this, '_account_page_load' ), - WP_FS__DEFAULT_PRIORITY, - ( $add_submenu_items && $show_account ) - ); - } - } - - if ( $add_submenu_items ) { - if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { - // Add contact page. - $this->add_submenu_item( - $this->get_text_inline( 'Contact Us', 'contact-us' ), - array( &$this, '_contact_page_render' ), - $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Contact Us', 'contact-us' ), - 'manage_options', - 'contact', - 'Freemius::_clean_admin_content_section', - WP_FS__DEFAULT_PRIORITY, - $this->is_submenu_item_visible( 'contact' ) - ); - } - - if ( $this->has_addons() ) { - $this->add_submenu_item( - $this->get_text_inline( 'Add-Ons', 'add-ons' ), - array( &$this, '_addons_page_render' ), - $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Add-Ons', 'add-ons' ), - 'manage_options', - 'addons', - array( &$this, '_addons_page_load' ), - WP_FS__LOWEST_PRIORITY - 1, - $this->is_submenu_item_visible( 'addons' ) - ); - } - } - - if ( $add_submenu_items || - ( $is_activation_mode && $this->is_only_premium() && $this->is_admin_page( 'pricing' ) ) - ) { - if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { - $show_pricing = ( - $this->is_submenu_item_visible( 'pricing' ) && - $this->is_pricing_page_visible() - ); - - $pricing_cta_text = $this->get_pricing_cta_label(); - $pricing_class = 'upgrade-mode'; - if ( $show_pricing ) { - if ( $this->is_in_trial_promotion() && - ! $this->is_paying_or_trial() - ) { - // If running a trial promotion, modify the pricing to load the trial. - $pricing_class = 'trial-mode'; - } else if ( $this->is_paying() ) { - $pricing_class = ''; - } - } - - // Add upgrade/pricing page. - $this->add_submenu_item( - $pricing_cta_text . '  ' . ( is_rtl() ? $this->get_text_x_inline( '←', 'ASCII arrow left icon', 'symbol_arrow-left' ) : $this->get_text_x_inline( '➤', 'ASCII arrow right icon', 'symbol_arrow-right' ) ), - array( &$this, '_pricing_page_render' ), - $this->get_plugin_name() . ' – ' . $this->get_text_x_inline( 'Pricing', 'noun', 'pricing' ), - 'manage_options', - 'pricing', - 'Freemius::_clean_admin_content_section', - WP_FS__LOWEST_PRIORITY, - ( $add_submenu_items && $show_pricing ), - $pricing_class - ); - } - } - - if ( ! $is_activation_mode || ( true !== $this->_storage->require_license_activation ) ) { - /** - * Add the other menu items if there are any when not in activation mode or license activation is not - * required (license activation is required for registered or anonymous users after activating the - * premium version when the site is not in trial mode or there's no active valid license). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - if ( 0 < count( $this->_menu_items ) ) { - if ( ! $this->_menu->is_top_level() ) { - fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); - - // Append submenu items right after the plugin's submenu item. - $this->order_sub_submenu_items(); - } else { - // Append submenu items. - $this->embed_submenu_items(); - } - } - } - } - - /** - * Moved the actual submenu item additions to a separated function, - * in order to support sub-submenu items when the plugin's settings - * only have a submenu and not top-level menu item. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - */ - private function embed_submenu_items() { - $item_template = $this->_menu->is_top_level() ? - '%s' : - '%s'; - - $top_level_menu_capability = $this->get_top_level_menu_capability(); - - ksort( $this->_menu_items ); - - $is_first_submenu_item = true; - - foreach ( $this->_menu_items as $priority => $items ) { - foreach ( $items as $item ) { - $capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability ); - - $menu_item = sprintf( - $item_template, - $this->get_unique_affix(), - $item['menu_slug'], - ! empty( $item['class'] ) ? $item['class'] : '', - $item['menu_title'] - ); - - $top_level_menu_slug = $this->get_top_level_menu_slug(); - $menu_slug = $this->_menu->get_slug( $item['menu_slug'] ); - - if ( ! isset( $item['url'] ) ) { - $hook = FS_Admin_Menu_Manager::add_subpage( - $item['show_submenu'] ? - $top_level_menu_slug : - null, - $item['page_title'], - $menu_item, - $capability, - $menu_slug, - $item['render_function'] - ); - - if ( false !== $item['before_render_function'] ) { - add_action( "load-$hook", $item['before_render_function'] ); - } - } else { - FS_Admin_Menu_Manager::add_subpage( - $item['show_submenu'] ? - $top_level_menu_slug : - null, - $item['page_title'], - $menu_item, - $capability, - $menu_slug, - array( $this, '' ) - ); - } - - if ( $item['show_submenu'] && $is_first_submenu_item ) { - if ( $this->_is_network_active && ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { - /** - * If the top-level menu has been dynamically created, remove the first submenu item that - * WordPress automatically creates when there's no submenu item whose slug matches the - * parent's. In the following example, the `Awesome Plugin` submenu item will be removed. - * - * Awesome Plugin - * - Awesome Plugin <-- we want to remove this since there's no real setting page for the top-level - * - * @author Leo Fajardo (@leorw) - */ - remove_submenu_page( $top_level_menu_slug, $top_level_menu_slug ); - } - - $is_first_submenu_item = false; - } - } - } - } - - /** - * Re-order the submenu items so all Freemius added new submenu items - * are added right after the plugin's settings submenu item. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - */ - private function order_sub_submenu_items() { - global $submenu; - - $menu_slug = $this->_menu->get_top_level_menu_slug(); - - /** - * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user - * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any - * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us, - * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission. - * - * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu - * of another plugin, only users that have the right role can access its sub-submenus since we will use the - * capability needed to access the parent menu as the capability for the submenus that we will add. - */ - if ( empty( $submenu[ $menu_slug ] ) ) { - return; - } - - $top_level_menu = &$submenu[ $menu_slug ]; - - $all_submenu_items_after = array(); - - $found_submenu_item = false; - - foreach ( $top_level_menu as $submenu_id => $meta ) { - if ( $found_submenu_item ) { - // Remove all submenu items after the plugin's submenu item. - $all_submenu_items_after[] = $meta; - unset( $top_level_menu[ $submenu_id ] ); - } - - if ( $this->_menu->get_raw_slug() === $meta[2] ) { - // Found the submenu item, put all below. - $found_submenu_item = true; - continue; - } - } - - // Embed all plugin's new submenu items. - $this->embed_submenu_items(); - - // Start with specially high number to make sure it's appended. - $i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 ); - foreach ( $all_submenu_items_after as $meta ) { - $top_level_menu[ $i ] = $meta; - $i ++; - } - - // Sort submenu items. - ksort( $top_level_menu ); - } - - /** - * Helper method to return the module's support forum URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return string - */ - function get_support_forum_url() { - return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" ); - } - - /** - * Displays the Support Forum link when enabled. - * - * Can be filtered like so: - * - * function _fs_show_support_menu( $is_visible, $menu_id ) { - * if ( 'support' === $menu_id ) { - * return _fs->is_registered(); - * } - * return $is_visible; - * } - * _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2); - * - */ - function _add_default_submenu_items() { - if ( ! $this->is_on() ) { - return; - } - - if ( ! $this->is_activation_mode() && - ( ( $this->_is_network_active && fs_is_network_admin() ) || - ( ! $this->_is_network_active && is_admin() ) ) - ) { - $this->add_submenu_link_item( - $this->apply_filters( 'support_forum_submenu', $this->get_text_inline( 'Support Forum', 'support-forum' ) ), - $this->get_support_forum_url(), - 'wp-support-forum', - null, - 50, - $this->is_submenu_item_visible( 'support' ) - ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param string $menu_title - * @param callable $render_function - * @param bool|string $page_title - * @param string $capability - * @param bool|string $menu_slug - * @param bool|callable $before_render_function - * @param int $priority - * @param bool $show_submenu - * @param string $class Since 1.2.1.5 can add custom classes to menu items. - */ - function add_submenu_item( - $menu_title, - $render_function, - $page_title = false, - $capability = 'manage_options', - $menu_slug = false, - $before_render_function = false, - $priority = WP_FS__DEFAULT_PRIORITY, - $show_submenu = true, - $class = '' - ) { - $this->_logger->entrance( 'Title = ' . $menu_title ); - - if ( $this->is_addon() ) { - $parent_fs = $this->get_parent_instance(); - - if ( is_object( $parent_fs ) ) { - $parent_fs->add_submenu_item( - $menu_title, - $render_function, - $page_title, - $capability, - $menu_slug, - $before_render_function, - $priority, - $show_submenu, - $class - ); - - return; - } - } - - if ( ! isset( $this->_menu_items[ $priority ] ) ) { - $this->_menu_items[ $priority ] = array(); - } - - $this->_menu_items[ $priority ][] = array( - 'page_title' => is_string( $page_title ) ? $page_title : $menu_title, - 'menu_title' => $menu_title, - 'capability' => $capability, - 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), - 'render_function' => $render_function, - 'before_render_function' => $before_render_function, - 'show_submenu' => $show_submenu, - 'class' => $class, - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param string $menu_title - * @param string $url - * @param bool $menu_slug - * @param string $capability - * @param int $priority - * @param bool $show_submenu - */ - function add_submenu_link_item( - $menu_title, - $url, - $menu_slug = false, - $capability = 'read', - $priority = WP_FS__DEFAULT_PRIORITY, - $show_submenu = true - ) { - $this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url ); - - if ( $this->is_addon() ) { - $parent_fs = $this->get_parent_instance(); - - if ( is_object( $parent_fs ) ) { - $parent_fs->add_submenu_link_item( - $menu_title, - $url, - $menu_slug, - $capability, - $priority, - $show_submenu - ); - - return; - } - } - - if ( ! isset( $this->_menu_items[ $priority ] ) ) { - $this->_menu_items[ $priority ] = array(); - } - - $this->_menu_items[ $priority ][] = array( - 'menu_title' => $menu_title, - 'capability' => $capability, - 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), - 'url' => $url, - 'page_title' => $menu_title, - 'render_function' => 'fs_dummy', - 'before_render_function' => '', - 'show_submenu' => $show_submenu, - ); - } - - #endregion ------------------------------------------------------------------ - - #-------------------------------------------------------------------------------- - #region Admin Notices - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @param string|string[] $ids - * @param int|null $network_level_or_blog_id - * - * @uses FS_Admin_Notices::remove_sticky() - */ - function remove_sticky( $ids, $network_level_or_blog_id = null ) { - $this->_admin_notices->remove_sticky( $ids, $network_level_or_blog_id ); - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Actions / Hooks / Filters - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @param string $tag - * - * @return string - */ - public function get_action_tag( $tag ) { - return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $tag - * @param string $slug - * @param bool $is_plugin - * - * @return string - */ - static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) { - $action = "fs_{$tag}"; - - if ( ! empty( $slug ) ) { - $action .= '_' . self::get_module_unique_affix( $slug, $is_plugin ); - } - - return $action; - } - - /** - * Returns a string that can be used to generate a unique action name, - * option name, HTML element ID, or HTML element class. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return string - */ - public function get_unique_affix() { - return self::get_module_unique_affix( $this->_slug, $this->is_plugin() ); - } - - /** - * Returns a string that can be used to generate a unique action name, - * option name, HTML element ID, or HTML element class. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.5 - * - * @param string $slug - * @param bool $is_plugin - * - * @return string - */ - static function get_module_unique_affix( $slug, $is_plugin = true ) { - $affix = $slug; - - if ( ! $is_plugin ) { - $affix .= '-' . WP_FS__MODULE_TYPE_THEME; - } - - return $affix; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are - * based on the slug for backward compatibility. - * - * @param string $tag - * - * @return string - */ - function get_ajax_action( $tag ) { - return self::get_ajax_action_static( $tag, $this->_module_id ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $tag - * - * @return string - */ - function get_ajax_security( $tag ) { - return wp_create_nonce( $this->get_ajax_action( $tag ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $tag - */ - function check_ajax_referer( $tag ) { - check_ajax_referer( $this->get_ajax_action( $tag ), 'security' ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are - * based on the slug for backward compatibility. - * - * @param string $tag - * @param number|null $module_id - * - * @return string - */ - private static function get_ajax_action_static( $tag, $module_id = null ) { - $action = "fs_{$tag}"; - - if ( ! empty( $module_id ) ) { - $action .= "_{$module_id}"; - } - - return $action; - } - - /** - * Do action, specific for the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param string $tag The name of the action to be executed. - * @param mixed $arg,... Optional. Additional arguments which are passed on to the - * functions hooked to the action. Default empty. - * - * @uses do_action() - */ - function do_action( $tag, $arg = '' ) { - $this->_logger->entrance( $tag ); - - $args = func_get_args(); - - call_user_func_array( 'do_action', array_merge( - array( $this->get_action_tag( $tag ) ), - array_slice( $args, 1 ) ) - ); - } - - /** - * Add action, specific for the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param string $tag - * @param callable $function_to_add - * @param int $priority - * @param int $accepted_args - * - * @uses add_action() - */ - function add_action( - $tag, - $function_to_add, - $priority = WP_FS__DEFAULT_PRIORITY, - $accepted_args = 1 - ) { - $this->_logger->entrance( $tag ); - - add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); - } - - /** - * Add AJAX action, specific for the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param string $tag - * @param callable $function_to_add - * @param int $priority - * - * @uses add_action() - * - * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. - */ - function add_ajax_action( - $tag, - $function_to_add, - $priority = WP_FS__DEFAULT_PRIORITY - ) { - $this->_logger->entrance( $tag ); - - return self::add_ajax_action_static( - $tag, - $function_to_add, - $priority, - $this->_module_id - ); - } - - /** - * Add AJAX action. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $tag - * @param callable $function_to_add - * @param int $priority - * @param number|null $module_id - * - * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. - * @uses add_action() - * - */ - static function add_ajax_action_static( - $tag, - $function_to_add, - $priority = WP_FS__DEFAULT_PRIORITY, - $module_id = null - ) { - self::$_static_logger->entrance( $tag ); - - if ( ! self::is_ajax_action_static( $tag, $module_id ) ) { - return false; - } - - add_action( - 'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ), - $function_to_add, - $priority, - 0 - ); - - self::$_static_logger->info( "$tag AJAX callback action added." ); - - return true; - } - - /** - * Send a JSON response back to an Ajax request. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $response - */ - static function shoot_ajax_response( $response ) { - wp_send_json( $response ); - } - - /** - * Send a JSON response back to an Ajax request, indicating success. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $data Data to encode as JSON, then print and exit. - */ - static function shoot_ajax_success( $data = null ) { - wp_send_json_success( $data ); - } - - /** - * Send a JSON response back to an Ajax request, indicating failure. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $error Optional error message. - */ - static function shoot_ajax_failure( $error = '' ) { - $result = array( 'success' => false ); - if ( ! empty( $error ) ) { - $result['error'] = $error; - } - - wp_send_json( $result ); - } - - /** - * Apply filter, specific for the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $tag The name of the filter hook. - * @param mixed $value The value on which the filters hooked to `$tag` are applied on. - * - * @return mixed The filtered value after all hooked functions are applied to it. - * - * @uses apply_filters() - */ - function apply_filters( $tag, $value ) { - $this->_logger->entrance( $tag ); - - $args = func_get_args(); - array_unshift( $args, $this->get_unique_affix() ); - - return call_user_func_array( 'fs_apply_filter', $args ); - } - - /** - * Add filter, specific for the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $tag - * @param callable $function_to_add - * @param int $priority - * @param int $accepted_args - * - * @uses add_filter() - */ - function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) { - $this->_logger->entrance( $tag ); - - add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); - } - - /** - * Check if has filter. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param string $tag - * @param callable|bool $function_to_check Optional. The callback to check for. Default false. - * - * @return false|int - * - * @uses has_filter() - */ - function has_filter( $tag, $function_to_check = false ) { - $this->_logger->entrance( $tag ); - - return has_filter( $this->get_action_tag( $tag ), $function_to_check ); - } - - #endregion - - /** - * Override default i18n text phrases. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string[] string $key_value - * - * @uses fs_override_i18n() - */ - function override_i18n( $key_value ) { - fs_override_i18n( $key_value, $this->_slug ); - } - - /* Account Page - ------------------------------------------------------------------------------------------------------------------*/ - /** - * Update site information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param bool $store Flush to Database if true. - * @param null|int $network_level_or_blog_id Since 2.0.0 - * @param \FS_Site $site Since 2.0.0 - */ - private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null ) { - $this->_logger->entrance(); - - if ( is_null( $site ) ) { - $site = $this->_site; - } - - if ( !isset( $site ) || !is_object($site) || empty( $site->id ) ) { - $this->_logger->error( "Empty install ID, can't store site." ); - - return; - } - - $site_clone = clone $site; - - $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id ); - - if ( is_object( $this->_user ) && $this->_user->id != $site->user_id ) { - $this->sync_user_by_current_install( $site->user_id ); - - $prev_stored_user_id = $this->_storage->get( 'prev_user_id', false, $network_level_or_blog_id ); - - if ( empty( $prev_stored_user_id ) && - is_object($this->_user) && $this->_user->id != $site->user_id - ) { - /** - * Store the current user ID as the previous user ID so that the previous user can be used - * as the install's owner while the new owner's details are not yet available. - * - * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original` - * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica` - * will be using the previous user until it is updated again (e.g.: until the next clone of `original` - * into `replica`. - * - * @author Leo Fajardo (@leorw) - */ - $this->_storage->store( 'prev_user_id', $sites[ $this->_slug ]->user_id, $network_level_or_blog_id ); - } - } - - $sites[ $this->_slug ] = $site_clone; - - $this->set_account_option( 'sites', $sites, $store, $network_level_or_blog_id ); - } - - /** - * Update plugin's plans information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @param bool $store Flush to Database if true. - */ - private function _store_plans( $store = true ) { - $this->_logger->entrance(); - - $plans = self::get_all_plans( $this->_module_type ); - - // Copy plans. - $encrypted_plans = array(); - for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { - $encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] ); - } - - $plans[ $this->_slug ] = $encrypted_plans; - - $this->set_account_option( 'plans', $plans, $store ); - } - - /** - * Update user's plugin licenses. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool $store - * @param number|bool $module_id - * @param FS_Plugin_License[] $licenses - */ - private function _store_licenses( $store = true, $module_id = false, $licenses = array() ) { - $this->_logger->entrance(); - - $all_licenses = self::get_all_licenses(); - - if ( ! FS_Plugin::is_valid_id( $module_id ) ) { - $module_id = $this->_module_id; - - $user_licenses = is_array( $this->_licenses ) ? - $this->_licenses : - array(); - - if ( empty( $user_licenses ) ) { - // If the context user doesn't have any license, don't update the licenses collection. - return; - } - - $new_user_licenses_map = array(); - foreach ( $user_licenses as $user_license ) { - $new_user_licenses_map[ $user_license->id ] = $user_license; - } - - self::store_user_id_license_ids_map( array_keys( $new_user_licenses_map ), $this->_module_id, $this->_user->id ); - - // Update user licenses. - $licenses_to_update_count = count( $new_user_licenses_map ); - foreach ( $all_licenses[ $module_id ] as $key => $license ) { - if ( 0 === $licenses_to_update_count ) { - break; - } - - if ( isset( $new_user_licenses_map[ $license->id ] ) ) { - // Update license. - $all_licenses[ $module_id ][ $key ] = $new_user_licenses_map[ $license->id ]; - unset( $new_user_licenses_map[ $license->id ] ); - - $licenses_to_update_count --; - } - } - - if ( ! empty( $new_user_licenses_map ) ) { - // Add new licenses. - $all_licenses[ $module_id ] = array_merge( array_values( $new_user_licenses_map ), $all_licenses[ $module_id ] ); - } - - $licenses = $all_licenses[ $module_id ]; - } - - if ( ! isset( $all_licenses[ $module_id ] ) ) { - $all_licenses[ $module_id ] = array(); - } - - $all_licenses[ $module_id ] = $licenses; - - self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); - } - - /** - * Update user information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param bool $store Flush to Database if true. - */ - private function _store_user( $store = true ) { - $this->_logger->entrance(); - - if ( empty( $this->_user->id ) ) { - $this->_logger->error( "Empty user ID, can't store user." ); - - return; - } - - $users = self::get_all_users(); - $users[ $this->_user->id ] = $this->_user; - self::$_accounts->set_option( 'users', $users, $store ); - } - - /** - * Update new updates information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param FS_Plugin_Tag|null $update - * @param bool $store Flush to Database if true. - * @param bool|number $plugin_id - */ - private function _store_update( $update, $store = true, $plugin_id = false ) { - $this->_logger->entrance(); - - if ( $update instanceof FS_Plugin_Tag ) { - $update->updated = time(); - } - - if ( ! is_numeric( $plugin_id ) ) { - $plugin_id = $this->_plugin->id; - } - - $updates = self::get_all_updates(); - $updates[ $plugin_id ] = $update; - self::$_accounts->set_option( 'updates', $updates, $store ); - } - - /** - * Update new updates information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param FS_Plugin[] $plugin_addons - * @param bool $store Flush to Database if true. - */ - private function _store_addons( $plugin_addons, $store = true ) { - $this->_logger->entrance(); - - $addons = self::get_all_addons(); - $addons[ $this->_plugin->id ] = $plugin_addons; - self::$_accounts->set_option( 'addons', $addons, $store ); - } - - /** - * Delete plugin's associated add-ons. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - * - * @param bool $store - * - * @return bool - */ - private function _delete_account_addons( $store = true ) { - $all_addons = self::get_all_account_addons(); - - if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) { - return false; - } - - unset( $all_addons[ $this->_plugin->id ] ); - - self::$_accounts->set_option( 'account_addons', $all_addons, $store ); - - return true; - } - - /** - * Update account add-ons list. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param FS_Plugin[] $addons - * @param bool $store Flush to Database if true. - */ - private function _store_account_addons( $addons, $store = true ) { - $this->_logger->entrance(); - - $all_addons = self::get_all_account_addons(); - $all_addons[ $this->_plugin->id ] = $addons; - self::$_accounts->set_option( 'account_addons', $all_addons, $store ); - } - - /** - * Purges the cache for the valid user licenses API call so that when the `Account` or `Add-Ons` page is loaded, - * the valid user licenses will be fetched again and the account add-ons may be updated. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - private function purge_valid_user_licenses_cache() { - if ( ! $this->is_registered() ) { - return; - } - - $this->get_api_user_scope()->purge_cache( $this->get_valid_user_licenses_endpoint() ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param array $all_licenses - * @param number|null $site_license_id - * @param bool $include_parent_licenses - * - * @return array - */ - private function get_foreign_licenses_info( $all_licenses, $site_license_id = null, $include_parent_licenses = false ) { - $foreign_licenses = array( - 'ids' => array(), - 'license_keys' => array() - ); - - $parent_license_ids_map = array(); - - foreach ( $all_licenses as $license ) { - if ( $license->user_id == $this->_user->id || $license->id == $site_license_id ) { - continue; - } - - $foreign_licenses['ids'][] = $license->id; - $foreign_licenses['license_keys'][] = $license->secret_key; - - if ( - $include_parent_licenses && - is_object( $this->_license ) && - FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) && - ! isset( $parent_license_ids_map[ $this->_license->parent_license_id ] ) - ) { - /** - * Include the parent license's info only if it has not been included before since child licenses - * can have the same parent license. - */ - $foreign_licenses['ids'][] = $this->_license->parent_license_id; - $foreign_licenses['license_keys'][] = $license->secret_key; - - $parent_license_ids_map[ $this->_license->parent_license_id ] = true; - } - } - - if ( empty( $foreign_licenses['ids'] ) ) { - $foreign_licenses = array(); - } - - return $foreign_licenses; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return string - */ - private function get_valid_user_licenses_endpoint() { - $user_licenses_endpoint = '/licenses.json?type=active' . - ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? '&is_enriched=true' : '' ); - - $foreign_licenses = $this->get_foreign_licenses_info( self::get_all_licenses( $this->_module_id ), null, true ); - - if ( ! empty ( $foreign_licenses ) ) { - $foreign_licenses = array( - // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. - 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), - 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) - ); - - $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); - } - - return $user_licenses_endpoint; - } - - /** - * Fetches active licenses that are enriched with product type if there's a context `bundle_id` and bundle - * licenses enriched with product IDs if there are any. From the licenses, the `get_updated_account_addons` - * method filters out non–add-on product IDs and stores the add-on IDs. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - * - * @return stdClass[] array - */ - private function fetch_valid_user_licenses() { - $this->_logger->entrance(); - - $result = $this->get_api_user_scope()->get( $this->get_valid_user_licenses_endpoint() ); - - if ( ! $this->is_api_result_object( $result, 'licenses' ) || - ! is_array( $result->licenses ) - ) { - return array(); - } - - return $result->licenses; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - * - * @return number[] Account add-on IDs. - */ - function get_updated_account_addons() { - $addons = $this->get_addons(); - if ( empty( $addons ) ) { - return array(); - } - - $account_addons = $this->get_account_addons(); - if ( ! is_array( $account_addons ) ) { - $account_addons = array(); - } - - $user_licenses = $this->is_registered() ? - $this->fetch_valid_user_licenses() : - array(); - - if ( empty( $user_licenses ) ) { - return $account_addons; - } - - $addon_ids = array(); - foreach ( $addons as $addon ) { - $addon_ids[] = $addon->id; - } - - $license_product_ids = array(); - - foreach ( $user_licenses as $license ) { - if ( isset( $license->plugin_type ) && 'bundle' === $license->plugin_type ) { - $license_product_ids = array_merge( $license_product_ids, $license->products ); - } else { - $license_product_ids[] = $license->plugin_id; - } - } - - // Filter out non–add-on IDs. - $new_account_addons = array_intersect( $addon_ids, $license_product_ids ); - if ( count( $new_account_addons ) !== count( $account_addons ) ) { - $this->_store_account_addons( array_unique( $new_account_addons ) ); - } - - return $new_account_addons; - } - - /** - * Store account params in the Database. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.1 - * - * @param null|int $blog_id Since 2.0.0 - */ - private function _store_account( $blog_id = null ) { - $this->_logger->entrance(); - - $this->_store_site( false, $blog_id ); - $this->_store_user( false ); - $this->_store_plans( false ); - $this->_store_licenses( false ); - - self::$_accounts->store( $blog_id ); - } - - /** - * Sync user's information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * @uses FS_Api - */ - private function _handle_account_user_sync() { - $this->_logger->entrance(); - - $api = $this->get_api_user_scope(); - - // Get user's information. - $user = $api->get( '/', true ); - - if ( isset( $user->id ) ) { - $this->_user->first = $user->first; - $this->_user->last = $user->last; - $this->_user->email = $user->email; - - $is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' ); - - if ( $user->is_verified && - ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) - ) { - $this->_user->is_verified = true; - - $this->do_action( 'account_email_verified', $user->email ); - - $this->_admin_notices->add( - $this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ), - $this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!', - 'success', - // Make admin sticky if account menu item is invisible, - // since the page will be auto redirected to the plugin's - // main settings page, and the non-sticky message - // will disappear. - ! $is_menu_item_account_visible, - 'email_verified' - ); - } - - // Flush user details to DB. - $this->_store_user(); - - $this->do_action( 'after_account_user_sync', $user ); - - /** - * If account menu item is hidden, redirect to plugin's main settings page. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @link https://github.com/Freemius/wordpress-sdk/issues/6 - */ - if ( ! $is_menu_item_account_visible ) { - fs_redirect( $this->_get_admin_page_url() ); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * @uses FS_Api - * - * @param number|bool $license_id - * - * @return FS_Subscription|object|bool - */ - private function _fetch_site_license_subscription( $license_id = false ) { - $this->_logger->entrance(); - $api = $this->get_api_site_scope(); - - if ( ! is_numeric( $license_id ) ) { - $license_id = FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) ? - $this->_license->parent_license_id : - $this->_license->id; - } - - $result = $api->get( "/licenses/{$license_id}/subscriptions.json", true ); - - return ! isset( $result->error ) ? - ( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ? - new FS_Subscription( $result->subscriptions[0] ) : - false - ) : - $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * @uses FS_Api - * - * @param number|bool $plan_id - * - * @return FS_Plugin_Plan|object - */ - private function _fetch_site_plan( $plan_id = false ) { - $this->_logger->entrance(); - $api = $this->get_api_site_scope(); - - if ( ! is_numeric( $plan_id ) ) { - $plan_id = $this->_site->plan_id; - } - - $plan = $api->get( "/plans/{$plan_id}.json", true ); - - return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * @uses FS_Api - * - * @return FS_Plugin_Plan[]|object - */ - private function _fetch_plugin_plans() { - $this->_logger->entrance(); - $api = $this->get_current_or_network_user_api_scope(); - - /** - * @since 1.2.3 When running in DEV mode, retrieve pending plans as well. - */ - $result = $api->get( $this->add_show_pending( "/plugins/{$this->_module_id}/plans.json" ), true ); - - if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) { - for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) { - $result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] ); - } - - $result = $result->plans; - } - - return $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $plan_id - * - * @return \FS_Plugin_Plan|object - */ - private function fetch_plan_by_id( $plan_id ) { - $this->_logger->entrance(); - $api = $this->get_current_or_network_user_api_scope(); - - $result = $api->get( "/plugins/{$this->_module_id}/plans/{$plan_id}.json", true ); - - return $this->is_api_result_entity( $result ) ? - new FS_Plugin_Plan( $result ) : - $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * @uses FS_Api - * - * @param number|bool $plugin_id - * @param number|bool $site_license_id - * @param array $foreign_licenses @since 2.0.0. This is used by network-activated plugins. - * @param number|null $blog_id - * - * @return FS_Plugin_License[]|object - */ - private function _fetch_licenses( - $plugin_id = false, - $site_license_id = false, - $foreign_licenses = array(), - $blog_id = null - ) { - $this->_logger->entrance(); - - $api = $this->get_api_user_scope(); - - if ( ! is_numeric( $plugin_id ) ) { - $plugin_id = $this->_plugin->id; - } - - $user_licenses_endpoint = "/plugins/{$plugin_id}/licenses.json?is_enriched=true"; - if ( ! empty ( $foreign_licenses ) ) { - $foreign_licenses = array( - // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. - 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), - 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) - ); - - $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); - } - - $result = $api->get( $user_licenses_endpoint, true ); - - $is_site_license_synced = false; - - $api_errors = array(); - - if ( $this->is_api_result_object( $result, 'licenses' ) && - is_array( $result->licenses ) - ) { - for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { - $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); - - if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) { - $is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id ); - } - } - - $result = $result->licenses; - } else { - $api_errors[] = $result; - $result = array(); - } - - if ( ! $is_site_license_synced ) { - if ( ! is_null( $blog_id ) ) { - /** - * If blog ID is not null, the request is for syncing of the license of a single site via the - * network-level "Account" page. - * - * @author Leo Fajardo (@leorw) - */ - $this->switch_to_blog( $blog_id ); - } - - $api = $this->get_api_site_scope(); - - if ( is_numeric( $site_license_id ) ) { - // Try to retrieve a foreign license that is linked to the install. - $api_result = $api->call( '/licenses.json?is_enriched=true' ); - - if ( $this->is_api_result_object( $api_result, 'licenses' ) && - is_array( $api_result->licenses ) - ) { - $licenses = $api_result->licenses; - - if ( ! empty( $licenses ) ) { - $result[] = new FS_Plugin_License( $licenses[0] ); - } - } else { - $api_errors[] = $api_result; - } - } else if ( - is_object( $this->_license ) && - /** - * Sync only if the license belongs to the context plugin. `$plugin_id` can be an add-on ID while - * the FS instance that does the syncing is the parent FS instance. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $this->_license->plugin_id == $plugin_id - ) { - $is_license_in_result = false; - if ( ! empty( $result ) ) { - foreach ( $result as $license ) { - if ( $license->id == $this->_license->id ) { - $is_license_in_result = true; - break; - } - } - } - - if ( ! $is_license_in_result ) { - // Fetch foreign license by ID and license key. - $license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . - urlencode( $this->_license->secret_key ) . '&is_enriched=true' ); - - if ( $this->is_api_result_entity( $license ) ) { - $result[] = new FS_Plugin_License( $license ); - } else { - $api_errors[] = $license; - } - } - } - - if ( ! is_null( $blog_id ) ) { - $this->switch_to_blog( $this->_storage->network_install_blog_id ); - } - } - - if ( is_array( $result ) && 0 < count( $result ) ) { - // If found at least one license, return license collection even if there are errors. - return $result; - } - - if ( ! empty( $api_errors ) ) { - // If found any errors and no licenses, return first error. - return $api_errors[0]; - } - - // Fallback to empty licenses list. - return $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param number $license_id - * @param string $license_key - * - * @return \FS_Plugin_License|object - */ - private function fetch_license_by_key( $license_id, $license_key ) { - $this->_logger->entrance(); - - $api = $this->get_current_or_network_user_api_scope(); - - $result = $api->get( "/licenses/{$license_id}.json?license_key=" . urlencode( $license_key ) ); - - return $this->is_api_result_entity( $result ) ? - new FS_Plugin_License( $result ) : - $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * @uses FS_Api - * - * @param number|bool $plugin_id - * @param bool $flush - * - * @return FS_Payment[]|object - */ - function _fetch_payments( $plugin_id = false, $flush = false ) { - $this->_logger->entrance(); - - $api = $this->get_api_user_scope(); - - if ( ! is_numeric( $plugin_id ) ) { - $plugin_id = $this->_plugin->id; - } - - $include_bundles = ( - is_object( $this->_plugin ) && - FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) - ); - - $result = $api->get( - "/plugins/{$plugin_id}/payments.json?include_addons=true" . ($include_bundles ? '&include_bundles=true' : ''), - $flush - ); - - if ( ! isset( $result->error ) ) { - for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) { - $result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] ); - } - $result = $result->payments; - } - - return $result; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * @uses FS_Api - * - * @param bool $flush - * - * @return \FS_Billing|mixed - */ - function _fetch_billing( $flush = false ) { - require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php'; - - $billing = $this->get_api_user_scope()->get( 'billing.json', $flush ); - - if ( $this->is_api_result_entity( $billing ) ) { - $billing = new FS_Billing( $billing ); - } - - return $billing; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param FS_Plugin_License[] $licenses - * @param number $module_id - */ - private function _update_licenses( $licenses, $module_id ) { - $this->_logger->entrance(); - - if ( is_array( $licenses ) ) { - for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) { - $licenses[ $i ]->updated = time(); - } - } - - $this->_store_licenses( true, $module_id, $licenses ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool|number $plugin_id - * @param bool $flush Since 1.1.7.3 - * @param int $expiration Since 1.2.2.7 - * @param bool|string $newer_than Since 2.2.1 - * - * @return object|false New plugin tag info if exist. - */ - private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { - $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than ); - - if ( ! is_object( $latest_tag ) ) { - return false; - } - - $plugin_version = $this->get_plugin_version(); - - // Check if version is actually newer. - $has_new_version = - // If it's an non-installed add-on then always return latest. - ( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) || - // Compare versions. - version_compare( $plugin_version, $latest_tag->version, '<' ); - - $this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' ); - - $is_latest_version_beta = ( 'beta' === $latest_tag->release_mode ); - - $this->_storage->beta_data = array( - 'is_beta' => $is_latest_version_beta, - 'version' => $latest_tag->version - ); - - return $has_new_version ? $latest_tag : false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool|number $plugin_id - * @param bool $flush Since 1.1.7.3 - * @param int $expiration Since 1.2.2.7 - * @param bool|string $newer_than Since 2.2.1 - * - * @return bool|FS_Plugin_Tag - */ - function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { - $this->_logger->entrance(); - - if ( ! is_numeric( $plugin_id ) ) { - $plugin_id = $this->_plugin->id; - } - - $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than ); - $updates = $this->get_all_updates(); - - return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false; - } - - /** - * Check if site assigned with active license. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @deprecated Please use has_active_valid_license() instead because license can be cancelled. - */ - function has_active_license() { - return ( - is_object( $this->_license ) && - is_numeric( $this->_license->id ) && - ! $this->_license->is_expired() - ); - } - - /** - * Check if site assigned with active & valid (not expired) license. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param bool $check_expiration - */ - function has_active_valid_license( $check_expiration = true ) { - return self::is_active_valid_license( $this->_license, $check_expiration ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - function is_data_debug_mode() { - if ( is_null( $this->is_whitelabeled ) || ! $this->is_whitelabeled ) { - return false; - } - - $fs = $this->is_addon() ? - $this->get_parent_instance() : - $this; - - if ( $fs->is_network_active() && fs_is_network_admin() ) { - $is_developer_license_debug_mode = get_site_transient( "fs_{$this->get_id()}_data_debug_mode" ); - } else { - $is_developer_license_debug_mode = get_transient( "fs_{$this->get_id()}_data_debug_mode" ); - } - - return ( 'true' === $is_developer_license_debug_mode ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - function _set_data_debug_mode() { - if ( ! $this->is_whitelabeled( true ) ) { - return; - } - - $license_or_user_key = fs_request_get( 'license_or_user_key' ); - - $transient_value = ( ! empty( $license_or_user_key ) ) ? - 'true' : - 'false'; - - if ( 'true' === $transient_value ) { - $stored_key = $this->_storage->get( ! FS_User::is_valid_id( $this->_storage->last_license_user_id ) ? - 'last_license_key' : - 'last_license_user_key' - ); - - if ( md5( $license_or_user_key ) !== $stored_key ) { - $this->shoot_ajax_failure( sprintf( - '%s... %s', - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), - $this->get_text_inline( - 'seems like the key you entered doesn\'t match our records.', - 'developer-or-license-not-found' - ) - ) ); - } - } - - if ( $this->is_network_active() && fs_is_network_admin() ) { - set_site_transient( - "fs_{$this->get_id()}_data_debug_mode", - $transient_value, - WP_FS__TIME_24_HOURS_IN_SEC / 24 - ); - } else { - set_transient( - "fs_{$this->get_id()}_data_debug_mode", - $transient_value, - WP_FS__TIME_24_HOURS_IN_SEC / 24 - ); - } - - if ( 'true' === $transient_value ) { - $this->_admin_notices->add_sticky( - $this->get_text_inline( - 'Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.', - 'data_debug_mode_enabled' - ), - 'data_debug_mode_enabled' - ); - } - - $this->shoot_ajax_success(); - } - - /** - * Check if a given license is active & valid (not expired). - * - * @author Vova Feldman (@svovaf) - * @since 2.1.3 - * - * @param FS_Plugin_License $license - * @param bool $check_expiration - * - * @return bool - */ - private static function is_active_valid_license( $license, $check_expiration = true ) { - return ( - is_object( $license ) && - FS_Plugin_License::is_valid_id( $license->id ) && - $license->is_active() && - ( ! $check_expiration || $license->is_valid() ) - ); - } - - /** - * Checks if there's any site that is associated with an active & valid license. - * This logic is used to determine if the admin can download the premium code base from a network level admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.3 - * - * @return bool - */ - function has_any_active_valid_license() { - if ( ! fs_is_network_admin() ) { - return $this->has_active_valid_license(); - } - - $installs = $this->get_blog_install_map(); - $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); - - foreach ( $installs as $blog_id => $install ) { - if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { - continue; - } - - foreach ( $all_plugin_licenses as $license ) { - if ( $license->id == $install->license_id ) { - if ( self::is_active_valid_license( $license ) ) { - return true; - } - } - } - } - - return false; - } - - /** - * Check if site assigned with license with enabled features. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function has_features_enabled_license() { - return ( - is_object( $this->_license ) && - is_numeric( $this->_license->id ) && - $this->_license->is_features_enabled() - ); - } - - /** - * Checks if the product is activated with a bundle license. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - * - * @return bool - */ - function is_activated_with_bundle_license() { - if ( ! $this->has_features_enabled_license() ) { - return false; - } - - return FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ); - } - - /** - * Check if user is a trial or have feature enabled license. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @return bool - */ - function can_use_premium_code() { - return $this->is_trial() || $this->has_features_enabled_license(); - } - - /** - * Checks if the current user can activate plugins or switch themes. Note that this method should only be used - * after the `init` action is triggered because it is using `current_user_can()` which is only functional after - * the context user is authenticated. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - function is_user_admin() { - /** - * Require a super-admin when network activated, running from the network level OR if - * running from the site level but not delegated the opt-in. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - if ( $this->_is_network_active && - ( fs_is_network_admin() || ! $this->is_delegated_connection() ) - ) { - return is_super_admin(); - } - - return ( $this->is_plugin() && current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) - || ( $this->is_theme() && current_user_can( 'switch_themes' ) ); - } - - /** - * Sync site's plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @uses FS_Api - * - * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by - * the admin. - * @param bool $is_context_single_site @since 2.0.0. This is used when syncing a license for a single install from the - * network-level "Account" page. - * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the - * `_sync_plugin_license` method in order to switch to the previous blog when sending - * updates for a single site in case `execute_cron` has switched to a different blog. - */ - private function _sync_license( $background = false, $is_context_single_site = false, $current_blog_id = null ) { - $this->_logger->entrance(); - - $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); - - $is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() ); - - if ( $is_addon_sync ) { - $this->_sync_addon_license( $plugin_id, $background ); - } else { - $this->_sync_plugin_license( $background, true, $is_context_single_site, $current_blog_id ); - } - - $this->do_action( 'after_account_plan_sync', $this->get_plan_name() ); - } - - /** - * Sync plugin's add-on license. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * @uses FS_Api - * - * @param number $addon_id - * @param bool $background - */ - private function _sync_addon_license( $addon_id, $background ) { - $this->_logger->entrance(); - - if ( $this->is_addon_activated( $addon_id ) ) { - // If already installed, use add-on sync. - $fs_addon = self::get_instance_by_id( $addon_id ); - - if ( - // Add-on is network activated and network integrated. - $fs_addon->is_network_active() || - // Background sync cron. - self::is_cron() || - // Add-on is not network activated or not network integrated. - ! fs_is_network_admin() - ) { - $fs_addon->_sync_license( $background ); - - return; - } - } - - // Validate add-on exists. - $addon = $this->get_addon( $addon_id ); - - if ( ! is_object( $addon ) ) { - return; - } - - // Add add-on into account add-ons. - $account_addons = $this->get_account_addons(); - if ( ! is_array( $account_addons ) ) { - $account_addons = array(); - } - $account_addons[] = $addon->id; - $account_addons = array_unique( $account_addons ); - $this->_store_account_addons( $account_addons ); - - // Load add-on licenses. - $licenses = $this->_fetch_licenses( $addon->id ); - - // Sync add-on licenses. - if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { - $this->_update_licenses( $licenses, $addon->id ); - - if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) { - $plans_result = $this->get_api_site_or_plugin_scope()->get( $this->add_show_pending( "/addons/{$addon_id}/plans.json" ) ); - - if ( ! isset( $plans_result->error ) ) { - $plans = array(); - foreach ( $plans_result->plans as $plan ) { - $plans[] = new FS_Plugin_Plan( $plan ); - } - - $this->_admin_notices->add_sticky( - sprintf( - ( FS_Plan_Manager::instance()->has_free_plan( $plans ) ? - $this->get_text_inline( 'Your %s Add-on plan was successfully upgraded.', 'addon-successfully-upgraded-message' ) : - /* translators: %s:product name, e.g. Facebook add-on was successfully... */ - $this->get_text_inline( '%s Add-on was successfully purchased.', 'addon-successfully-purchased-message' ) ), - $addon->title - ) . ' ' . $this->get_latest_download_link( - $this->get_text_inline( 'Download the latest version', 'download-latest-version' ), - $addon_id - ), - 'addon_plan_upgraded_' . $addon->slug, - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - } - } - } - } - - /** - * Sync site's plugin plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * @uses FS_Api - * - * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by the admin. - * @param bool $send_installs_update Since 2.0.0 - * @param bool $is_context_single_site Since 2.0.0. This is used when sending an update for a single install and - * syncing its license from the network-level "Account" page (e.g.: after - * activating a license only for the single install). - * @param int|null $current_blog_id Since 2.2.3. This is passed from the `execute_cron` method so that it - * can be used here to switch to the previous blog in case `execute_cron` - * has switched to a different blog. - */ - private function _sync_plugin_license( - $background = false, - $send_installs_update = true, - $is_context_single_site = false, - $current_blog_id = null - ) { - $this->_logger->entrance(); - - $plan_change = 'none'; - - $is_site_level_sync = ( $is_context_single_site || fs_is_blog_admin() || ! $this->_is_network_active ); - - if ( ! $send_installs_update ) { - $site = $this->_site; - } else { - /** - * Sync site info. - * - * @todo This line will execute install sync on a daily basis, even if running the free version (for opted-in users). The reason we want to keep it that way is for cases when the user was a paying customer, then there was a failure in subscription payment, and then after some time the payment was successful. This could be heavily optimized. For example, we can skip the $flush if the current install was never associated with a paid version. - */ - if ( $is_site_level_sync ) { - /** - * Switch to the previous blog since `execute_cron` may have switched to a different blog. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - if ( is_numeric( $current_blog_id ) ) { - $this->switch_to_blog( $current_blog_id ); - } - - $result = $this->send_install_update( array(), true ); - $is_valid = $this->is_api_result_entity( $result ); - } else { - $result = $this->send_installs_update( array(), true ); - $is_valid = $this->is_api_result_object( $result, 'installs' ); - } - - if ( ! $is_valid ) { - if ( $is_context_single_site ) { - // Switch back to the main blog so that the following logic will have the right entities. - $this->switch_to_blog( $this->_storage->network_install_blog_id ); - } - - // Show API messages only if not background sync or if paying customer. - if ( ! $background || $this->is_paying() ) { - // Try to ping API to see if not blocked. - if ( ! FS_Api::test() ) { - /** - * Failed to ping API - blocked! - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages. - */ - $api = $this->get_api_site_scope(); - - if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { - self::$_global_admin_notices->add( - sprintf( - $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', 'server-blocking-access' ), - $this->get_plugin_name(), - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '' - ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error', - $background, - 'api_blocked' - ); - } - } else { - // Authentication params are broken. - $this->_admin_notices->add( - $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - } - } - - // No reason to continue with license sync while there are API issues. - return; - } - - if ( $is_site_level_sync ) { - $site = new FS_Site( $result ); - } else { - // Map site addresses to their blog IDs. - $address_to_blog_map = $this->get_address_to_blog_map(); - - // Find the current context install. - $site = null; - foreach ( $result->installs as $install ) { - if ( $install->id == $this->_site->id ) { - $site = new FS_Site( $install ); - } else { - $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); - $blog_id = $address_to_blog_map[ $address ]; - - $this->_store_site( true, $blog_id, new FS_Site( $install ) ); - } - } - } - - // Sync plans. - $this->_sync_plans(); - } - - // Remove sticky API connectivity message. - self::$_global_admin_notices->remove_sticky( 'api_blocked' ); - - if ( ! $this->has_paid_plan() ) { - $this->_site = $site; - $this->_store_site( - true, - $is_site_level_sync ? - null : - $this->get_network_install_blog_id() - ); - } else { - $context_blog_id = 0; - - if ( $is_context_single_site ) { - $context_blog_id = get_current_blog_id(); - - // Switch back to the main blog in order to properly sync the license. - $this->switch_to_blog( $this->_storage->network_install_blog_id ); - } - - /** - * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license - * associated with that ID is not included in the user's licenses collection. - */ - $this->_sync_licenses( - $site->license_id, - ( $is_context_single_site ? - $context_blog_id : - null - ) - ); - - if ( $is_context_single_site ) { - $this->switch_to_blog( $context_blog_id ); - } - - // Check if plan / license changed. - if ( $site->plan_id != $this->_site->plan_id || - // Check if trial started. - $site->trial_plan_id != $this->_site->trial_plan_id || - $site->trial_ends != $this->_site->trial_ends || - // Check if license changed. - $site->license_id != $this->_site->license_id - ) { - if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) { - // New trial started. - $this->_site = $site; - $plan_change = 'trial_started'; - - // For trial with subscription use-case. - $new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id ); - - if ( is_object( $new_license ) && $new_license->is_valid() ) { - $this->_site = $site; - $this->_update_site_license( $new_license ); - $this->_store_licenses(); - - $this->_sync_site_subscription( $this->_license ); - } - } else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) { - // Was in trial, but now trial expired and no license ID. - // New trial started. - $this->_site = $site; - $plan_change = 'trial_expired'; - } else { - $is_free = $this->is_free_plan(); - - // Make sure license exist and not expired. - $new_license = is_null( $site->license_id ) ? - null : - $this->_get_license_by_id( $site->license_id ); - - if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) { - // License cancelled. - $this->_site = $site; - $this->_update_site_license( $new_license ); - $this->_store_licenses(); - - $plan_change = 'cancelled'; - } else if ( $is_free && ( ( ! is_object( $new_license ) || $new_license->is_expired() ) ) ) { - // The license is expired, so ignore upgrade method. - $this->_site = $site; - } else { - // License changed. - $this->_site = $site; - - /** - * IMPORTANT: - * The line below should be executed before trying to activate the license on the rest of the network, otherwise, the license' activation counters may be out of sync + there's no need to activate the license on the context site since it's already activated on it. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - $this->_update_site_license( $new_license ); - - if ( ! $is_context_single_site && - fs_is_network_admin() && - $this->_is_network_active && - $new_license->quota > 1 && - get_blog_count() > 1 - ) { - // See if license can activated on all sites. - if ( ! $this->try_activate_license_on_network( $this->_user, $new_license ) ) { - if ( ! fs_request_get_bool( 'auto_install' ) ) { - // Open the license activation dialog box on the account page. - add_action( 'admin_footer', array( - &$this, - '_open_license_activation_dialog_box' - ) ); - } - } - } - - $this->_store_licenses(); - - $plan_change = $is_free ? - ( $this->is_only_premium() ? 'activated' : 'upgraded' ) : - ( is_object( $new_license ) ? - 'changed' : - 'downgraded' ); - } - } - - // Store updated site info. - $this->_store_site( - true, - $is_site_level_sync ? - null : - $this->get_network_install_blog_id() - ); - } else { - if ( ! is_object( $this->_license ) ) { - $this->maybe_update_whitelabel_flag( - FS_Plugin_License::is_valid_id( $site->license_id ) ? - $this->get_license_by_id( $site->license_id ) : - null - ); - } else { - $this->maybe_update_whitelabel_flag( $this->_license ); - - if ( $this->_license->is_expired() ) { - if ( ! $this->has_features_enabled_license() ) { - $this->_deactivate_license(); - $plan_change = 'downgraded'; - } else { - $last_time_expired_license_notice_was_shown = $this->_storage->get( 'expired_license_notice_shown', 0 ); - - if ( time() - ( 14 * WP_FS__TIME_24_HOURS_IN_SEC ) >= $last_time_expired_license_notice_was_shown ) { - /** - * Show the expired license notice every 14 days. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - $plan_change = 'expired'; - } - } - } - } - - if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) { - $this->_sync_site_subscription( $this->_license ); - } - } - - if ( ! $this->is_addon() && - $this->_site->is_beta() !== $site->is_beta - ) { - // Beta flag updated. - $this->_site = $site; - - $this->_store_site( - true, - $is_site_level_sync ? - null : - $this->get_network_install_blog_id() - ); - } - - if ( $this->is_addon() || $this->has_addons() ) { - /** - * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, - * an updated valid user licenses collection will be fetched from the server which is used to also - * update the account add-ons (add-ons the user has licenses for). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - $this->purge_valid_user_licenses_cache(); - } - } - - $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; - - if ( $this->apply_filters( 'has_paid_plan_account', $this->has_paid_plan() ) ) { - switch ( $plan_change ) { - case 'none': - if ( ! $background && is_admin() ) { - $plan = $this->is_trial() ? - $this->get_trial_plan() : - $this->get_plan(); - - if ( $plan->is_free() ) { - $this->_admin_notices->add( - sprintf( - $this->get_text_inline( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.', 'plan-did-not-change-message' ), - '' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text_x_inline( 'Trial', 'trial period', 'trial' ) : '' ) . '' - ) . ' ' . sprintf( - '%s', - $this->contact_url( - 'bug', - sprintf( $this->get_text_inline( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.', 'plan-did-not-change-email-message' ), - strtoupper( $plan->name ) - ) - ), - $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) - ), - $hmm_text - ); - } - } - break; - case 'upgraded': - case 'activated': - $this->_admin_notices->add_sticky( - ( 'activated' === $plan_change ) ? - $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ) : - $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) . - $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - - $this->_admin_notices->remove_sticky( array( - 'trial_started', - 'trial_promotion', - 'trial_expired', - 'activation_complete', - 'license_expired', - ) ); - break; - case 'changed': - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Your plan was successfully changed to %s.', 'plan-changed-to-x-message' ), - $this->get_plan_title() - ), - 'plan_changed' - ); - - $this->_admin_notices->remove_sticky( array( - 'trial_started', - 'trial_promotion', - 'trial_expired', - 'activation_complete', - ) ); - break; - case 'downgraded': - $this->_admin_notices->add_sticky( - ($this->has_free_plan() ? - sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using the free %s forever.', 'license-expired-blocking-message' ), $this->_module_type ) : - /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ - sprintf( $this->get_text_inline( 'Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'license-expired-blocking-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true) ) ), - 'license_expired', - $hmm_text - ); - $this->_admin_notices->remove_sticky( 'plan_upgraded' ); - break; - case 'cancelled': - $this->_admin_notices->add( - $this->get_text_inline( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.', 'license-cancelled' ) . ' ' . - sprintf( - '%s', - $this->contact_url( 'bug' ), - $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) - ), - $hmm_text, - 'error' - ); - $this->_admin_notices->remove_sticky( 'plan_upgraded' ); - break; - case 'expired': - $this->_admin_notices->add_sticky( - sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.', 'license-expired-non-blocking-message' ), $this->get_plan()->title ), - 'license_expired', - $hmm_text - ); - - $this->_storage->expired_license_notice_shown = WP_FS__SCRIPT_START_TIME; - - $this->_admin_notices->remove_sticky( 'plan_upgraded' ); - break; - case 'trial_started': - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), - '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $this->get_trial_plan()->title ), - 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - - $this->_admin_notices->remove_sticky( array( - 'trial_promotion', - ) ); - break; - case 'trial_expired': - $this->_admin_notices->add_sticky( - ($this->has_free_plan() ? - $this->get_text_inline( 'Your free trial has expired. You can still continue using all our free features.', 'trial-expired-message' ) : - /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ - sprintf( $this->get_text_inline( 'Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'trial-expired-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true))), - 'trial_expired', - $hmm_text - ); - $this->_admin_notices->remove_sticky( array( - 'trial_started', - 'trial_promotion', - 'plan_upgraded', - ) ); - break; - } - } - - if ( 'none' !== $plan_change ) { - if ( - ! is_object( $this->_license ) || - ! $this->_license->is_whitelabeled - ) { - $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); - } - - $this->do_action( 'after_license_change', $plan_change, $this->get_plan() ); - } - } - - /** - * Include the required JS at the footer of the admin to trigger the license activation dialog box. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - public function _open_license_activation_dialog_box() { - $vars = array( 'license_id' => $this->_site->license_id ); - fs_require_once_template( 'js/open-license-activation.php', $vars ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool $background - * @param FS_Plugin_License|null $premium_license - */ - protected function _activate_license( $background = false, $premium_license = null ) { - $this->_logger->entrance(); - - if ( is_null( $premium_license ) ) { - $license_id = fs_request_get( 'license_id' ); - - if ( is_object( $this->_site ) && - FS_Plugin_License::is_valid_id( $license_id ) && - $license_id == $this->_site->license_id - ) { - // License is already activated. - return; - } - - $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? - $this->_get_license_by_id( $license_id ) : - $this->_get_available_premium_license(); - } - - if ( ! is_object( $premium_license ) ) { - return; - } - - if ( ! is_object( $this->_site ) ) { - // Not yet opted-in. - $user = $this->get_current_or_network_user(); - if ( ! is_object( $user ) ) { - $user = self::_get_user_by_id( $premium_license->user_id ); - } - - if ( is_object( $user ) ) { - $this->install_with_user( $user, $premium_license->secret_key, false, false, false ); - } else { - $this->opt_in( - false, - false, - false, - $premium_license->secret_key - ); - - return; - } - } - - - /** - * If the premium license is already associated with the install, just - * update the license reference (activation is not required). - * - * @since 1.1.9 - */ - if ( $premium_license->id == $this->_site->license_id ) { - // License is already activated. - $this->_update_site_license( $premium_license ); - $this->_store_account(); - - return; - } - - if ( $this->_site->user_id != $premium_license->user_id ) { - $api_request_params = array( 'license_key' => $premium_license->secret_key ); - } else { - $api_request_params = array(); - } - - $api = $this->get_api_site_scope(); - $license = $api->call( "/licenses/{$premium_license->id}.json?is_enriched=true", 'put', $api_request_params ); - - if ( ! $this->is_api_result_entity( $license ) ) { - if ( ! $background ) { - $this->_admin_notices->add( sprintf( - '%s %s', - $this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ), - ( is_object( $license ) && isset( $license->error ) ? - $license->error->message : - sprintf( '%s
    %s', - $this->get_text_inline( 'Error received from the server:', 'server-error-message' ), - var_export( $license, true ) - ) - ) - ), - $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...', - 'error' - ); - } - - return; - } - - $premium_license = new FS_Plugin_License( $license ); - - // Updated site plan. - $site = $this->get_api_site_scope()->get( '/', true ); - if ( $this->is_api_result_entity( $site ) ) { - $this->_site = new FS_Site( $site ); - } - $this->_update_site_license( $premium_license ); - - $this->_store_account(); - - if ( $this->is_addon() || $this->has_addons() ) { - /** - * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, - * an updated valid user licenses collection will be fetched from the server which is used to also - * update the account add-ons (add-ons the user has licenses for). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - $this->purge_valid_user_licenses_cache(); - } - - if ( ! $background ) { - $this->_admin_notices->add_sticky( - $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) . - $this->get_complete_upgrade_instructions(), - 'license_activated', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); - } - - $this->_admin_notices->remove_sticky( array( - 'trial_promotion', - 'license_expired', - ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param bool $show_notice - */ - protected function _deactivate_license( $show_notice = true ) { - $this->_logger->entrance(); - - $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; - - if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'It looks like your site currently doesn\'t have an active license.', 'no-active-license-message' ), $this->get_plan_title() ), - $hmm_text - ); - - return; - } - - $api = $this->get_api_site_scope(); - $license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' ); - - $this->handle_license_deactivation_result( $license, $hmm_text, $show_notice ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - * - * @param FS_Plugin_License $license - * @param bool|string $hmm_text - * @param bool $show_notice - */ - private function handle_license_deactivation_result( $license, $hmm_text = false, $show_notice = true ) { - if ( isset( $license->error ) ) { - $this->_admin_notices->add( - $this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '
    ' . - $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ), - $hmm_text, - 'error' - ); - - return; - } - - // Update license cache. - if ( is_array( $this->_licenses ) ) { - for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { - if ( $license->id == $this->_licenses[ $i ]->id ) { - $this->_licenses[ $i ] = new FS_Plugin_License( $license ); - } - } - } - - // Update site plan to default. - $this->_sync_plans(); - $this->_site->plan_id = $this->_plans[0]->id; - // Unlink license from site. - $this->_update_site_license( null ); - - $this->_store_account(); - - if ( $show_notice ) { - $this->_admin_notices->add( - sprintf( $this->is_only_premium() ? - $this->get_text_inline( 'Your %s license was successfully deactivated.', 'license-deactivation-message_premium-only' ) : - $this->get_text_inline( 'Your license was successfully deactivated, you are back to the %s plan.', 'license-deactivation-message' ), - $this->get_plan_title() - ), - $this->get_text_inline( 'O.K', 'ok' ) - ); - } - - $this->_admin_notices->remove_sticky( array( - 'plan_upgraded', - 'license_activated', - ) ); - } - - /** - * Site plan downgrade. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @return object - * - * @uses FS_Api - */ - private function _downgrade_site() { - $this->_logger->entrance(); - - $deactivate_license = fs_request_get_bool( 'deactivate_license' ); - - $api = $this->get_api_site_scope(); - $site = $api->call( 'downgrade.json', 'put', array( 'deactivate_license' => $deactivate_license ) ); - - $plan_downgraded = false; - $plan = false; - if ( $this->is_api_result_entity( $site ) ) { - $prev_plan_id = $this->_site->plan_id; - - // Update new site plan id. - $this->_site->plan_id = $site->plan_id; - - $plan = $this->get_plan(); - $subscription = $this->_sync_site_subscription( $this->_license ); - - // Plan downgraded if plan was changed or subscription was cancelled. - $plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) || - ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() ); - } else { - // handle different error cases. - $this->handle_license_deactivation_result( - $site, - $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...' - ); - } - - if ( ! $plan_downgraded ) { - return (object) array( - 'error' => (object) array( - 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.', 'subscription-cancellation-failure-message' ) - ) - ); - } - - // Remove previous sticky message about upgrade (if exist). - $this->_admin_notices->remove_sticky( 'plan_upgraded' ); - - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Your subscription was successfully cancelled. Your %s plan license will expire in %s.', 'plan-x-downgraded-message' ), - $plan->title, - human_time_diff( time(), strtotime( $this->_license->expiration ) ) - ) - ); - - // Store site updates. - $this->_store_site(); - - if ( $deactivate_license && - ! FS_Plugin_License::is_valid_id( $site->license_id ) - ) { - if ( $this->_site->is_localhost() ) { - $this->_license->activated_local = max( 0, $this->_license->activated_local - 1 ); - } else { - $this->_license->activated = max( 0, $this->_license->activated - 1 ); - } - - // Handle successful license deactivation result. - $this->handle_license_deactivation_result( $this->_license ); - } - - return $site; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.8.1 - * - * @param bool|string $plan_name - * - * @return bool If trial was successfully started. - */ - function start_trial( $plan_name = false ) { - $this->_logger->entrance(); - - // Alias. - $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; - - if ( $this->is_trial() ) { - // Already in trial mode. - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'You are already running the %s in a trial mode.', 'in-trial-mode' ), $this->_module_type ), - $oops_text, - 'error' - ); - - return false; - } - - if ( $this->_site->is_trial_utilized() ) { - // Trial was already utilized. - $this->_admin_notices->add( - $this->get_text_inline( 'You already utilized a trial before.', 'trial-utilized' ), - $oops_text, - 'error' - ); - - return false; - } - - if ( false !== $plan_name ) { - $plan = $this->get_plan_by_name( $plan_name ); - - if ( false === $plan ) { - // Plan doesn't exist. - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ), - $oops_text, - 'error' - ); - - return false; - } - - if ( ! $plan->has_trial() ) { - // Plan doesn't exist. - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ), - $oops_text, - 'error' - ); - - return false; - } - } else { - if ( ! $this->has_trial_plan() ) { - // None of the plans have a trial. - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ), - $oops_text, - 'error' - ); - - return false; - } - - $plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); - - $plan = $plans_with_trial[0]; - } - - $api = $this->get_api_site_scope(); - $plan = $api->call( "plans/{$plan->id}/trials.json", 'post' ); - - if ( ! $this->is_api_result_entity( $plan ) ) { - // Some API error while trying to start the trial. - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) - . ' ' . var_export( $plan, true ), - $oops_text, - 'error' - ); - - return false; - } - - // Sync license. - $this->_sync_license(); - - return $this->is_trial(); - } - - /** - * Cancel site trial. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return object - * - * @uses FS_Api - */ - private function _cancel_trial() { - $this->_logger->entrance(); - - if ( ! $this->is_trial() ) { - return (object) array( - 'error' => (object) array( - 'message' => $this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' ) - ) - ); - } - - $trial_plan = $this->get_trial_plan(); - - $api = $this->get_api_site_scope(); - $site = $api->call( 'trials.json', 'delete' ); - - $trial_cancelled = false; - - if ( $this->is_api_result_entity( $site ) ) { - $prev_trial_ends = $this->_site->trial_ends; - - if ( $this->is_paid_trial() ) { - $this->_license->expiration = $site->trial_ends; - $this->_license->is_cancelled = true; - $this->_update_site_license( $this->_license ); - $this->_store_licenses(); - - // Clear subscription reference. - $this->_sync_site_subscription( null ); - } - - // Update site info. - $this->_site = new FS_Site( $site ); - - $trial_cancelled = ( $prev_trial_ends != $site->trial_ends ); - } else { - // @todo handle different error cases. - } - - if ( ! $trial_cancelled ) { - return (object) array( - 'error' => (object) array( - 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' ) - ) - ); - } - - // Remove previous sticky messages about upgrade or trial (if exist). - $this->_admin_notices->remove_sticky( array( - 'trial_started', - 'trial_promotion', - 'plan_upgraded', - ) ); - - // Store site updates. - $this->_store_site(); - - if ( ! $this->is_addon() || - ! $this->deactivate_premium_only_addon_without_license( true ) - ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $trial_plan->title ) - ); - } - - return $site; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool|number $plugin_id - * - * @return bool - */ - private function _is_addon_id( $plugin_id ) { - return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id ); - } - - /** - * Check if user eligible to download premium version updates. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - private function _can_download_premium() { - return $this->has_any_active_valid_license() || - ( $this->is_trial() && ! $this->get_trial_plan()->is_free() ); - } - - /** - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool|number $addon_id - * @param string $type "json" or "zip" - * - * @return string - */ - private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) { - - $is_addon = $this->_is_addon_id( $addon_id ); - - $is_premium = null; - if ( ! $is_addon ) { - $is_premium = ( $this->is_premium() || $this->_can_download_premium() ); - } else if ( $this->is_addon_activated( $addon_id ) ) { - $fs_addon = self::get_instance_by_id( $addon_id ); - $is_premium = ( $fs_addon->is_premium() || $fs_addon->_can_download_premium() ); - } - - // If add-on, then append add-on ID. - $endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) . - '/updates/latest.' . $type; - - // If add-on and not yet activated, try to fetch based on server licensing. - if ( is_bool( $is_premium ) ) { - $endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint ); - } - - if ( $this->has_secret_key() ) { - $endpoint = add_query_arg( 'type', 'all', $endpoint ); - } else if ( is_object( $this->_site ) && $this->_site->is_beta() ) { - $endpoint = add_query_arg( 'type', 'beta', $endpoint ); - } - - return $endpoint; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool|number $addon_id - * @param bool $flush Since 1.1.7.3 - * @param int $expiration Since 1.2.2.7 - * @param bool|string $newer_than Since 2.2.1 - * @param bool|string $fetch_readme Since 2.2.1 - * - * @return object|false Plugin latest tag info. - */ - function _fetch_latest_version( - $addon_id = false, - $flush = true, - $expiration = WP_FS__TIME_24_HOURS_IN_SEC, - $newer_than = false, - $fetch_readme = true - ) { - $this->_logger->entrance(); - - $switch_to_blog_id = null; - - /** - * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in. - * @since 1.1.7.4 Also check updates for add-ons. - */ - if ( ! $this->is_registered() && - ! $this->_is_addon_id( $addon_id ) - ) { - if ( ! is_multisite() ) { - return false; - } - - $installs_map = $this->get_blog_install_map(); - - foreach ( $installs_map as $blog_id => $install ) { - /** - * @var FS_Site $install - */ - if ( $install->is_trial() ) { - $switch_to_blog_id = $blog_id; - break; - } - - if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { - $license = $this->get_license_by_id( $install->license_id ); - - if ( is_object( $license ) && $license->is_features_enabled() ) { - $switch_to_blog_id = $blog_id; - break; - } - } - } - - if ( is_null( $switch_to_blog_id ) ) { - return false; - } - } - - $current_blog_id = is_numeric( $switch_to_blog_id ) ? - get_current_blog_id() : - 0; - - if ( is_numeric( $switch_to_blog_id ) ) { - $this->switch_to_blog( $switch_to_blog_id ); - } - - $latest_version_endpoint = $this->_get_latest_version_endpoint( $addon_id, 'json' ); - - if ( ! empty( $newer_than ) ) { - $latest_version_endpoint = add_query_arg( 'newer_than', $newer_than, $latest_version_endpoint ); - } - - if ( true === $fetch_readme ) { - $latest_version_endpoint = add_query_arg( 'readme', 'true', $latest_version_endpoint ); - } - - $tag = $this->get_api_site_or_plugin_scope()->get( - $latest_version_endpoint, - $flush, - $expiration - ); - - if ( is_numeric( $switch_to_blog_id ) ) { - $this->switch_to_blog( $current_blog_id ); - } - - $latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get'; - - $this->_logger->departure( 'Latest version ' . $latest_version ); - - return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false; - } - - #---------------------------------------------------------------------------------- - #region Download Plugin - #---------------------------------------------------------------------------------- - - /** - * Download latest plugin version, based on plan. - * - * Not like _download_latest(), this will redirect the page - * to secure download url to prevent dual download (from FS to WP server, - * and then from WP server to the client / browser). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool|number $plugin_id - * - * @uses FS_Api - * @uses wp_redirect() - */ - private function download_latest_directly( $plugin_id = false ) { - $this->_logger->entrance(); - - wp_redirect( $this->get_latest_download_api_url( $plugin_id ) ); - } - - /** - * Get latest plugin FS API download URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool|number $plugin_id - * - * @return string - */ - private function get_latest_download_api_url( $plugin_id = false ) { - $this->_logger->entrance(); - - return $this->get_api_site_scope()->get_signed_url( - $this->_get_latest_version_endpoint( $plugin_id, 'zip' ) - ); - } - - /** - * Get payment invoice URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * - * @param bool|number $payment_id - * - * @return string - */ - function _get_invoice_api_url( $payment_id = false ) { - $this->_logger->entrance(); - - $url = $this->get_api_user_scope()->get_signed_url( - "/payments/{$payment_id}/invoice.pdf" - ); - - if ( ! fs_starts_with( $url, 'https://' ) ) { - // Always use HTTPS for invoices. - $url = 'https' . substr( $url, 4 ); - } - - return $url; - } - - /** - * Get latest plugin download link. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $label - * @param bool|number $plugin_id - * - * @return string - */ - private function get_latest_download_link( $label, $plugin_id = false ) { - return sprintf( - '%s', - $this->_get_latest_download_local_url( $plugin_id ), - $label - ); - } - - /** - * Get latest plugin download local URL. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool|number $plugin_id - * - * @return string - */ - function _get_latest_download_local_url( $plugin_id = false ) { - // Add timestamp to protect from caching. - $params = array( 'ts' => WP_FS__SCRIPT_START_TIME ); - - if ( ! empty( $plugin_id ) ) { - $params['plugin_id'] = $plugin_id; - } else if ( $this->is_addon() ) { - $params['plugin_id'] = $this->get_id(); - } - - $fs = $this->is_addon() ? - $this->get_parent_instance() : - $this; - - return $this->apply_filters( 'download_latest_url', $fs->get_account_url( 'download_latest', $params ) ); - } - - #endregion Download Plugin ------------------------------------------------------------------ - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @uses FS_Api - * - * @param bool $background Hints the method if it's a background updates check. If false, it means that - * was initiated by the admin. - * @param bool|number $plugin_id - * @param bool $flush Since 1.1.7.3 - * @param int $expiration Since 1.2.2.7 - * @param bool|string $newer_than Since 2.2.1 - */ - private function check_updates( - $background = false, - $plugin_id = false, - $flush = true, - $expiration = WP_FS__TIME_24_HOURS_IN_SEC, - $newer_than = false - ) { - $this->_logger->entrance(); - - // Check if there's a newer version for download. - $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than ); - - $update = null; - if ( is_object( $new_version ) ) { - $update = new FS_Plugin_Tag( $new_version ); - - if ( ! $background ) { - $this->_admin_notices->add( - sprintf( - /* translators: %s: Numeric version number (e.g. '2.1.9' */ - $this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ), - $update->version, - sprintf( - '%s', - $this->get_account_url( 'download_latest' ), - sprintf( - /* translators: %s: plan name (e.g. latest "Professional" version) */ - $this->get_text_inline( 'the latest %s version here', 'latest-x-version' ), - $this->get_plan_title() - ) - ) - ), - $this->get_text_inline( 'New', 'new' ) . '!' - ); - } - } else if ( false === $new_version && ! $background ) { - $this->_admin_notices->add( - $this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ), - $this->get_text_inline( 'You are all good!', 'you-are-good' ) - ); - } - - $this->_store_update( $update, true, $plugin_id ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool $flush Since 1.1.7.3 add 24 hour cache by default. - * - * @return FS_Plugin[] - * - * @uses FS_Api - */ - private function sync_addons( $flush = false ) { - $this->_logger->entrance(); - - $api = $this->get_api_site_or_plugin_scope(); - - $path = $this->add_show_pending( '/addons.json?enriched=true&count=50' ); - - /** - * @since 1.2.1 - * - * If there's a cached version of the add-ons and not asking - * for a flush, just use the currently stored add-ons. - */ - if ( ! $flush && $api->is_cached( $path ) ) { - $addons = self::get_all_addons(); - - return isset( $addons[ $this->_plugin->id ] ) ? - $addons[ $this->_plugin->id ] : - array(); - } - - $result = $api->get( $path, $flush ); - - $addons = array(); - if ( $this->is_api_result_object( $result, 'plugins' ) && - is_array( $result->plugins ) - ) { - for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) { - $addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] ); - } - - $this->_store_addons( $addons, true ); - } - - return $addons; - } - - /** - * Handle user email update. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * @uses FS_Api - * - * @param string $new_email - * - * @return object - */ - private function update_email( $new_email ) { - $this->_logger->entrance(); - - - $api = $this->get_api_user_scope(); - $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array( - 'email' => $new_email, - 'after_email_confirm_url' => $this->_get_admin_page_url( - 'account', - array( 'fs_action' => 'sync_user' ) - ), - ) ); - - if ( ! isset( $user->error ) ) { - $this->_user->email = $user->email; - $this->_user->is_verified = $user->is_verified; - $this->_store_user(); - } else { - // handle different error cases. - - } - - return $user; - } - - #---------------------------------------------------------------------------------- - #region API Error Handling - #---------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @param mixed $result - * - * @return bool Is API result contains an error. - */ - private function is_api_error( $result ) { - return FS_Api::is_api_error( $result ); - } - - /** - * Checks if given API result is a non-empty and not an error object. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $result - * @param string|null $required_property Optional property we want to verify that is set. - * - * @return bool - */ - function is_api_result_object( $result, $required_property = null ) { - return FS_Api::is_api_result_object( $result, $required_property ); - } - - /** - * Checks if given API result is a non-empty entity object with non-empty ID. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $result - * - * @return bool - */ - private function is_api_result_entity( $result ) { - return FS_Api::is_api_result_entity( $result ); - } - - #endregion - - /** - * Make sure a given argument is an array of a specific type. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $array - * @param string $class - * - * @return bool - */ - private function is_array_instanceof( $array, $class ) { - return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) ); - } - - /** - * Start install ownership change. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * @uses FS_Api - * - * @param string $candidate_email - * - * @return bool Is ownership change successfully initiated. - */ - private function init_change_owner( $candidate_email ) { - $this->_logger->entrance(); - - $api = $this->get_api_site_scope(); - $result = $api->call( "/users/{$this->_user->id}.json", 'put', array( - 'email' => $candidate_email, - 'after_confirm_url' => $this->_get_admin_page_url( - 'account', - array( 'fs_action' => 'change_owner' ) - ), - ) ); - - return ! $this->is_api_error( $result ); - } - - /** - * Handle install ownership change. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * @uses FS_Api - * - * @return bool Was ownership change successfully complete. - */ - private function complete_change_owner() { - $this->_logger->entrance(); - - $site_result = $this->get_api_site_scope( true )->get(); - $site = new FS_Site( $site_result ); - $this->_site = $site; - - $user = new FS_User(); - $user->id = fs_request_get( 'user_id' ); - - // Validate install's user and given user. - if ( $user->id != $this->_site->user_id ) { - return false; - } - - $user->public_key = fs_request_get( 'user_public_key' ); - $user->secret_key = fs_request_get( 'user_secret_key' ); - - // Fetch new user information. - $this->_user = $user; - $user_result = $this->get_api_user_scope( true )->get(); - $user = new FS_User( $user_result ); - $this->_user = $user; - - $this->_set_account( $user, $site ); - - return true; - } - - /** - * Completes ownership change by license. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @param number $user_id - * @param array[string]number $install_ids_by_slug_map - * - */ - private function complete_ownership_change_by_license( $user_id, $install_ids_by_slug_map ) { - $this->_logger->entrance(); - - $this->sync_user_by_current_install( $user_id ); - - $result = $this->get_api_user_scope( true )->get( - "/installs.json?install_ids=" . implode( ',', $install_ids_by_slug_map ) - ); - - if ( $this->is_api_result_object( $result, 'installs' ) ) { - $sites = self::get_all_sites( $this->get_module_type() ); - $install_ids_by_slug_map = array_flip( $install_ids_by_slug_map ); - - foreach ( $result->installs as $install ) { - $site = new FS_Site( $install ); - - $sites[ $install_ids_by_slug_map[ $site->id ] ] = clone $site; - } - - $this->set_account_option( 'sites', $sites, true ); - } - } - - /** - * Handle user name update. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * @uses FS_Api - * - * @return object - */ - private function update_user_name() { - $this->_logger->entrance(); - $name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' ); - - $api = $this->get_api_user_scope(); - $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array( - 'name' => $name, - ) ); - - if ( ! isset( $user->error ) ) { - $this->_user->first = $user->first; - $this->_user->last = $user->last; - $this->_store_user(); - } else { - // handle different error cases. - - } - - return $user; - } - - /** - * Verify user email. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * @uses FS_Api - */ - private function verify_email() { - $this->_handle_account_user_sync(); - - if ( $this->_user->is_verified() ) { - return; - } - - $api = $this->get_api_site_scope(); - $result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array( - 'after_email_confirm_url' => $this->_get_admin_page_url( - 'account', - array( 'fs_action' => 'sync_user' ) - ) - ) ); - - if ( ! isset( $result->error ) ) { - $this->_admin_notices->add( sprintf( - $this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ), - sprintf( '%2$s', esc_url( $this->_user->email ), $this->_user->email ) - ) ); - } else { - // handle different error cases. - - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.2 - * - * @param array $params - * @param bool|null $network - * - * @return string - */ - function get_activation_url( $params = array(), $network = null ) { - if ( $this->is_addon() && $this->has_free_plan() ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 Add-on's activation is the parent's module activation. - */ - return $this->get_parent_instance()->get_activation_url( $params ); - } - - return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params, $network ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param array $params - * - * @return string - */ - function get_reconnect_url( $params = array() ) { - $params['fs_action'] = 'reset_anonymous_mode'; - $params['fs_unique_affix'] = $this->get_unique_affix(); - - return $this->get_activation_url( $params ); - } - - /** - * Get the URL of the page that should be loaded after the user connect - * or skip in the opt-in screen. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string $filter Filter name. - * @param array $params Since 1.2.2.7 - * @param bool|null $network - * - * @return string - */ - function get_after_activation_url( $filter, $params = array(), $network = null ) { - if ( $this->show_opt_in_on_themes_page() && - ( fs_request_has( 'pending_activation' ) || - // For cases when the first time path is set, even though it's a WP.org theme. - fs_request_get_bool( $this->get_unique_affix() . '_show_optin' ) ) - ) { - $first_time_path = ''; - } else { - $first_time_path = $this->_menu->get_first_time_path( - fs_is_network_admin() && $this->_is_network_active - ); - } - - if ( $this->_is_network_active && - fs_is_network_admin() && - ! $this->_menu->has_network_menu() && - $this->is_network_registered() - ) { - $target_url = $this->get_account_url(); - } else { - // Default plugin's page. - $target_url = $this->_get_admin_page_url( '', array(), $network ); - } - - return add_query_arg( $params, $this->apply_filters( - $filter, - empty( $first_time_path ) ? - $target_url : - $first_time_path - ) ); - } - - /** - * Handle account page updates / edits / actions. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - */ - private function _handle_account_edits() { - if ( ! $this->is_user_admin() ) { - return; - } - - $action = fs_get_action(); - - if ( empty( $action ) ) { - return; - } - - $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); - $install_id = fs_request_get( 'install_id', '' ); - - // Alias. - $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; - - $is_network_action = $this->is_network_level_action(); - $blog_id = $this->is_network_level_site_specific_action(); - $is_parent_plugin_action = ( $plugin_id == $this->get_id() ); - - if ( is_numeric( $blog_id ) ) { - $this->switch_to_blog( $blog_id ); - } else { - $blog_id = ''; - } - - switch ( $action ) { - case 'opt_in': - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - - if ( $is_parent_plugin_action ) { - if ( $is_network_action && ! empty( $blog_id ) ) { - if ( ! $this->is_registered() ) { - $this->install_with_user( - $this->get_network_user(), - false, - false, - false, - false - ); - - $this->_admin_notices->add( - $this->get_text_inline( 'Site successfully opted in.', 'successful-opt-in' ), - $this->get_text_inline( 'Awesome', 'awesome' ) - ); - } - } - } - break; - - case 'toggle_tracking': - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - - if ( $is_parent_plugin_action ) { - if ( $is_network_action && ! empty( $blog_id ) ) { - if ( $this->is_registered() ) { - if ( $this->is_tracking_prohibited() ) { - if ( $this->allow_site_tracking() ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation' ), $this->_module_type ), - $this->get_text_inline( 'Thank you!', 'thank-you' ) - ); - } - } else { - if ( $this->stop_site_tracking() ) { - $this->_admin_notices->add( - sprintf( - $this->get_text_inline( 'We will no longer be sending any usage data of %s on %s to %s.', 'opted-out-successfully' ), - $this->get_plugin_title(), - fs_strip_url_protocol( get_site_url( $blog_id ) ), - sprintf( - '%s', - 'https://freemius.com', - 'freemius.com' - ) - ) - ); - } - } - } - } - } - - break; - - case 'delete_account': - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - - $is_network_deletion = $is_network_action && empty( $blog_id ); - - if ( $is_parent_plugin_action ) { - // Delete add-on installs if have any. - $installed_addons = $this->get_installed_addons(); - foreach ( $installed_addons as $fs_addon ) { - if ( $is_network_deletion ) { - $fs_addon->delete_network_account_event(); - } else { - $fs_addon->delete_account_event(); - } - } - - if ( $is_network_deletion ) { - $this->delete_network_account_event(); - } else { - $this->delete_account_event(); - } - - // Clear user and site. - $this->_site = null; - $this->_user = null; - - $this->maybe_set_slug_and_network_menu_exists_flag(); - - fs_redirect( $this->get_activation_url() ); - } else { - if ( $this->is_addon_activated( $plugin_id ) ) { - $fs_addon = self::get_instance_by_id( $plugin_id ); - - if ( $is_network_deletion ) { - $fs_addon->delete_network_account_event(); - } else { - $fs_addon->delete_account_event(); - } - - fs_redirect( $this->_get_admin_page_url( 'account' ) ); - } - } - - return; - - case 'downgrade_account': - if ( is_numeric( $blog_id ) ) { - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - } else { - check_admin_referer( $action ); - } - - $switch_to_network_install_blog_after_cancellation = ( - is_numeric( $blog_id ) && - $plugin_id == $this->get_id() && - ! $this->is_trial() - ); - - $result = $this->cancel_subscription_or_trial( $plugin_id ); - if ( $this->is_api_error( $result ) ) { - $this->_admin_notices->add( - $result->error->message, - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - } - - if ( $switch_to_network_install_blog_after_cancellation ) { - $this->switch_to_blog( $this->_storage->network_install_blog_id ); - } - - return; - - case 'activate_license': - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - - $fs = $this; - if ( $plugin_id != $this->get_id() ) { - $fs = $this->is_addon_activated( $plugin_id ) ? - self::get_instance_by_id( $plugin_id ) : - null; - } - - if ( is_object( $fs ) ) { - $fs->_activate_license(); - - /** - * Remove the product ID from `$_REQUEST` so that the syncing of the license for the other products will work properly. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.0 - */ - unset( $_REQUEST['plugin_id'] ); - - if ( $this->is_bundle_license_auto_activation_enabled() ) { - $fs->maybe_activate_bundle_license( null, array(), is_numeric( $blog_id ) ? $blog_id : 0 ); - } - } - - return; - - case 'deactivate_license': - check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); - - if ( $plugin_id == $this->get_id() ) { - $this->_deactivate_license(); - - if ( $this->is_only_premium() ) { - // Clear user and site. - $this->_site = null; - $this->_user = null; - - if ( ! $is_network_action ) { - fs_redirect( $this->get_activation_url() ); - } else if ( is_numeric( $blog_id ) ) { - $this->switch_to_blog( $this->_storage->network_install_blog_id ); - } - } - } else { - if ( $this->is_addon_activated( $plugin_id ) ) { - $fs_addon = self::get_instance_by_id( $plugin_id ); - $fs_addon->_deactivate_license(); - } - } - - return; - - case 'check_updates': - check_admin_referer( $action ); - $this->check_updates(); - - return; - - case 'change_owner': - $state = fs_request_get( 'state', 'init' ); - switch ( $state ) { - case 'init': - $candidate_email = fs_request_get( 'candidate_email', '' ); - - if ( $this->init_change_owner( $candidate_email ) ) { - $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '' . $this->_user->email . '' ) ); - } - break; - case 'owner_confirmed': - $candidate_email = fs_request_get( 'candidate_email', '' ); - - $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '' . $candidate_email . '' ) ); - break; - case 'candidate_confirmed': - if ( $this->complete_change_owner() ) { - $this->_admin_notices->add_sticky( - sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '' . $this->_user->email . '' ), - 'ownership_changed', - $this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!' - ); - } else { - // @todo Handle failed ownership change message. - } - break; - } - - return; - - case 'update_email': - check_admin_referer( 'update_email' ); - - $new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' ); - $result = $this->update_email( $new_email ); - - if ( isset( $result->error ) ) { - switch ( $result->error->code ) { - case 'user_exist': - $this->_admin_notices->add( - $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . - sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email . '' ) . - sprintf( - '', - $this->get_account_url( 'change_owner', array( - 'state' => 'init', - 'candidate_email' => $new_email - ) ), - $this->get_text_inline( 'Change Ownership', 'change-ownership' ) - ), - $oops_text, - 'error' - ); - break; - } - } else { - $this->_admin_notices->add( $this->get_text_inline( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.', 'email-updated-message' ) ); - } - - return; - - case 'update_user_name': - check_admin_referer( 'update_user_name' ); - - $result = $this->update_user_name(); - - if ( isset( $result->error ) ) { - $this->_admin_notices->add( - $this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ), - $oops_text, - 'error' - ); - } else { - $this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) ); - } - - return; - - #region Actions that might be called from external links (e.g. email) - - case 'cancel_trial': - $result = $this->cancel_subscription_or_trial( $plugin_id ); - if ( $this->is_api_error( $result ) ) { - $this->_admin_notices->add( - $result->error->message, - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error' - ); - } - - return; - - case 'verify_email': - $this->verify_email(); - - return; - - case 'sync_user': - $this->_handle_account_user_sync(); - - return; - - case $this->get_unique_affix() . '_sync_license': - $this->_sync_license(); - - return; - - case 'download_latest': - $this->download_latest_directly( $plugin_id ); - - return; - - #endregion - } - - if ( WP_FS__IS_POST_REQUEST ) { - $properties = array( 'site_secret_key', 'site_id', 'site_public_key' ); - foreach ( $properties as $p ) { - if ( 'update_' . $p === $action ) { - check_admin_referer( $action ); - - $this->_logger->log( $action ); - - $site_property = substr( $p, strlen( 'site_' ) ); - $site_property_value = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' ); - $this->get_site()->{$site_property} = $site_property_value; - - // Store account after modification. - $this->_store_site(); - - $this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value ); - - $this->_admin_notices->add( sprintf( - /* translators: %s: User's account property (e.g. email address, name) */ - $this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ), - '' . str_replace( '_', ' ', $p ) . '' - ) ); - - return; - } - } - } - } - - /** - * Account page resources load. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - */ - function _account_page_load() { - $this->_logger->entrance(); - - $this->_logger->info( var_export( $_REQUEST, true ) ); - - fs_enqueue_local_style( 'fs_account', '/admin/account.css' ); - - if ( $this->has_addons() ) { - wp_enqueue_script( 'plugin-install' ); - add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); - } - - if ( $this->has_paid_plan() && - ! $this->has_any_license() && - ! $this->is_sync_executed() && - $this->is_tracking_allowed() - ) { - /** - * If no licenses found and no sync job was executed during the last 24 hours, - * just execute the sync job right away (blocking execution). - * - * @since 1.1.7.3 - */ - $this->run_manual_sync(); - } - - $this->_handle_account_edits(); - - if ( - is_object( $this->_license ) && - $this->_license->user_id == $this->_user->id && - ! $this->is_whitelabeled( true ) - ) { - $this->_admin_notices->add( - sprintf( - $this->get_text_inline( "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.", 'license_not_whitelabeled' ), - sprintf( - '%s', - $this->get_text_inline( 'Click here', 'click-here' ) - ) - ), - '', - 'success', - false, - 'license_not_whitelabeled' - ); - } - - $this->do_action( 'account_page_load_before_departure' ); - } - - /** - * Renders the "Affiliation" page. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - function _affiliation_page_render() { - $this->_logger->entrance(); - - $this->fetch_affiliate_and_terms(); - - fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' ); - - $vars = array( 'id' => $this->_module_id ); - echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) ); - } - - - /** - * Render account page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - */ - function _account_page_render() { - $this->_logger->entrance(); - - $template = 'account.php'; - $vars = array( 'id' => $this->_module_id ); - - /** - * Added filter to the template to allow developers wrapping the template - * in custom HTML (e.g. within a wizard/tabs). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) ); - } - - /** - * Render account connect page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function _connect_page_render() { - $this->_logger->entrance(); - - $vars = array( 'id' => $this->_module_id ); - - /** - * Added filter to the template to allow developers wrapping the template - * in custom HTML (e.g. within a wizard/tabs). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) ); - } - - /** - * Load required resources before add-ons page render. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - */ - function _addons_page_load() { - $this->_logger->entrance(); - - fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); - - wp_enqueue_script( 'plugin-install' ); - add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); - - if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { - $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '' . $this->get_plugin_name() . '' ), - $this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ), - 'update-nag' - ); - } - } - - /** - * Render add-ons page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - */ - function _addons_page_render() { - $this->_logger->entrance(); - - $vars = array( 'id' => $this->_module_id ); - - /** - * Added filter to the template to allow developers wrapping the template - * in custom HTML (e.g. within a wizard/tabs). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) ); - } - - /* Pricing & Upgrade - ------------------------------------------------------------------------------------------------------------------*/ - /** - * Render pricing page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - */ - function _pricing_page_render() { - $this->_logger->entrance(); - - $vars = array( 'id' => $this->_module_id ); - - if ( 'true' === fs_request_get( 'checkout', false ) ) { - echo $this->apply_filters( 'templates/checkout.php', fs_get_template( 'checkout.php', $vars ) ); - } else { - echo $this->apply_filters( 'templates/pricing.php', fs_get_template( 'pricing.php', $vars ) ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - function _maybe_add_pricing_ajax_handler() { - if ( ! $this->should_use_external_pricing() ) { - $this->add_ajax_action( 'pricing_ajax_action', array( &$this, '_fs_pricing_ajax_action_handler' ) ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - function _fs_pricing_ajax_action_handler() { - $this->check_ajax_referer( 'pricing_ajax_action' ); - - $result = null; - $pricing_action = fs_request_get( 'pricing_action' ); - - switch ( $pricing_action ) { - case 'fetch_pricing_data': - $params = array( - 'is_enriched' => true, - 'trial' => fs_request_get_bool( 'trial' ), - 'sandbox' => fs_request_get( 'sandbox' ), - 's_ctx_type' => fs_request_get( 's_ctx_type' ), - 's_ctx_id' => fs_request_get( 's_ctx_id' ), - 's_ctx_ts' => fs_request_get( 's_ctx_ts' ), - 's_ctx_secure' => fs_request_get( 's_ctx_secure' ), - ); - - $bundle_id = $this->get_bundle_id(); - $bundle_public_key = $this->get_bundle_public_key(); - - $has_bundle_context = ( FS_Plugin::is_valid_id( $bundle_id ) && ! empty( $bundle_public_key ) ); - - if ( ! $has_bundle_context ) { - $api = $this->get_api_plugin_scope(); - } else { - $api = FS_Api::instance( - $bundle_id, - 'plugin', - $bundle_id, - $bundle_public_key, - ! $this->is_live(), - false, - $this->get_sdk_version() - ); - - $params['plugin_id'] = $this->get_id(); - $params['plugin_public_key'] = $this->get_public_key(); - } - - $result = $api->get( 'pricing.json?' . http_build_query( $params ) ); - break; - case 'start_trial': - $result = $this->opt_in( - false, - false, - false, - false, - false, - fs_request_get( 'plan_id' ) - ); - } - - if ( is_object( $result ) && $this->is_api_error( $result ) ) { - $this->_logger->api_error( $result ); - - self::shoot_ajax_failure( - isset( $result->error ) ? - ( is_string( $result->error ) ? $result->error : $result->error->message ) : - var_export( $result, true ) - ); - } - - $this->shoot_ajax_success( $result ); - } - - #---------------------------------------------------------------------------------- - #region Contact Us - #---------------------------------------------------------------------------------- - - /** - * Render contact-us page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - */ - function _contact_page_render() { - $this->_logger->entrance(); - - $vars = array( 'id' => $this->_module_id ); - - /** - * Added filter to the template to allow developers wrapping the template - * in custom HTML (e.g. within a wizard/tabs). - * - * @author Vova Feldman (@svovaf) - * @since 2.1.3 - */ - echo $this->apply_filters( 'templates/contact.php', fs_get_template( 'contact.php', $vars ) ); - } - - #endregion ------------------------------------------------------------------------ - - /** - * Hide all admin notices to prevent distractions. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @uses remove_all_actions() - */ - private static function _hide_admin_notices() { - remove_all_actions( 'admin_notices' ); - remove_all_actions( 'network_admin_notices' ); - remove_all_actions( 'all_admin_notices' ); - remove_all_actions( 'user_admin_notices' ); - } - - static function _clean_admin_content_section_hook() { - self::_hide_admin_notices(); - - // Hide footer. - echo ''; - } - - /** - * Attach to admin_head hook to hide all admin notices. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - */ - static function _clean_admin_content_section() { - add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' ); - } - - /* CSS & JavaScript - ------------------------------------------------------------------------------------------------------------------*/ - /* function _enqueue_script($handle, $src) { - $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); - - $this->_logger->entrance( 'script = ' . $url ); - - wp_enqueue_script( $handle, $url ); - }*/ - - /* SDK - ------------------------------------------------------------------------------------------------------------------*/ - private $_user_api; - - /** - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @param bool $flush - * - * @return FS_Api - */ - private function get_api_user_scope( $flush = false ) { - if ( ! isset( $this->_user_api ) || $flush ) { - $this->_user_api = $this->get_api_user_scope_by_user( $this->_user ); - } - - return $this->_user_api; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_User $user - * - * @return \FS_Api - */ - private function get_api_user_scope_by_user( FS_User $user ) { - return FS_Api::instance( - $this->_module_id, - 'user', - $user->id, - $user->public_key, - ! $this->is_live(), - $user->secret_key, - $this->get_sdk_version() - ); - } - - /** - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param bool $flush - * - * @return FS_Api - */ - private function get_current_or_network_user_api_scope( $flush = false ) { - if ( ! $this->_is_network_active || - ( isset( $this->_user ) && $this->_user instanceof FS_User ) - ) { - return $this->get_api_user_scope( $flush ); - } - - $user = $this->get_current_or_network_user(); - - $this->_user_api = FS_Api::instance( - $this->_module_id, - 'user', - $user->id, - $user->public_key, - ! $this->is_live(), - $user->secret_key, - $this->get_sdk_version() - ); - - return $this->_user_api; - } - - private $_site_api; - - /** - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @param bool $flush - * - * @return FS_Api - */ - private function get_api_site_scope( $flush = false ) { - if ( ! isset( $this->_site_api ) || $flush ) { - $this->_site_api = FS_Api::instance( - $this->_module_id, - 'install', - $this->_site->id, - $this->_site->public_key, - ! $this->is_live(), - $this->_site->secret_key, - $this->get_sdk_version() - ); - } - - return $this->_site_api; - } - - private $_plugin_api; - - /** - * Get plugin public API scope. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return FS_Api - */ - function get_api_plugin_scope() { - if ( ! isset( $this->_plugin_api ) ) { - $this->_plugin_api = FS_Api::instance( - $this->_module_id, - 'plugin', - $this->_plugin->id, - $this->_plugin->public_key, - ! $this->is_live(), - false, - $this->get_sdk_version() - ); - } - - return $this->_plugin_api; - } - - /** - * Get bundle public API scope. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return FS_Api - */ - function get_api_bundle_scope() { - return FS_Api::instance( - $this->get_bundle_id(), - 'plugin', - $this->get_bundle_id(), - $this->get_bundle_public_key(), - ! $this->is_live(), - false, - $this->get_sdk_version() - ); - } - - /** - * Get site API scope object (fallback to public plugin scope when not registered). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return FS_Api - */ - function get_api_site_or_plugin_scope() { - return $this->is_registered() ? - $this->get_api_site_scope() : - $this->get_api_plugin_scope(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.2.3.1 - * - * @param object $result - */ - private function maybe_modify_api_curl_error_message( $result ) { - if ( - 'cUrlMissing' !== $result->error->type && - ( 'CurlException' !== $result->error->type || CURLE_COULDNT_CONNECT != $result->error->code ) && - ( 'HttpRequestFailed' !== $result->error->type || false === strpos( $result->error->message, 'cURL error ' . CURLE_COULDNT_CONNECT ) ) - ) { - return; - } - - $result->error->message = $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . - ' ' . - $this->esc_html_inline( - sprintf( - 'Please contact your hosting provider and ask them to whitelist %s for external connection.', - implode( - ', ', - $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) - ) - ), - 'connectivity-whitelist' - ) . - ' ' . - sprintf( - $this->esc_html_inline( 'Once you are done, deactivate the %s and activate it again.', 'connectivity-reactivate-module' ), - $this->get_module_type() - ); - } - - /** - * Show trial promotional notice (if any trial exist). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param FS_Plugin_Plan[] $plans - */ - function _check_for_trial_plans( $plans ) { - /** - * For some reason core's do_action() flattens arrays when it has a single object item. Therefore, we need to restructure the array as expected. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.2 - */ - if ( ! is_array( $plans ) && is_object( $plans ) ) { - $plans = array( $plans ); - } - - if ( ! $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { - $plans = array(); - } - - $this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans ); - } - - /** - * During trial promotion the "upgrade" submenu item turns to - * "start trial" to encourage the trial. Since we want to keep - * the same menu item handler and there's no robust way to - * add new arguments to the menu item link's querystring, - * use JavaScript to find the menu item and update the href of - * the link. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - */ - function _fix_start_trial_menu_item_url() { - $template_args = array( 'id' => $this->_module_id ); - fs_require_template( 'add-trial-to-pricing.php', $template_args ); - } - - /** - * Check if module is currently in a trial promotion mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - function is_in_trial_promotion() { - return $this->_admin_notices->has_sticky( 'trial_promotion' ); - } - - /** - * Show trial promotional notice (if any trial exist). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool If trial notice added. - */ - function _add_trial_notice() { - if ( ! $this->is_user_admin() ) { - return false; - } - - if ( ! $this->is_user_in_admin() ) { - return false; - } - - if ( $this->_is_network_active ) { - if ( fs_is_network_admin() ) { - // Network level trial is disabled at the moment. - return false; - } - - if ( ! $this->is_delegated_connection() ) { - // Only delegated sites should support trials. - return false; - } - } - - // Check if trial message is already shown. - if ( $this->is_in_trial_promotion() ) { - add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) ); - - $this->_menu->add_counter_to_menu_item( 1, 'fs-trial' ); - - return false; - } - - if ( $this->is_premium() && ! WP_FS__DEV_MODE ) { - // Don't show trial if running the premium code, unless running in DEV mode. - return false; - } - - if ( ! $this->has_trial_plan() ) { - // No plans with trial. - return false; - } - - if ( ! $this->apply_filters( 'show_trial', true ) ) { - // Developer explicitly asked not to show the trial promo. - return false; - } - - if ( $this->is_registered() ) { - // Check if trial already utilized. - if ( $this->_site->is_trial_utilized() ) { - return false; - } - - if ( $this->is_paying_or_trial() ) { - // Don't show trial if paying or already in trial. - return false; - } - } - - if ( $this->is_activation_mode() || $this->is_pending_activation() ) { - // If not yet opted-in/skipped, or pending activation, don't show trial. - return false; - } - - $last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false ); - $was_promotion_shown_before = ( false !== $last_time_trial_promotion_shown ); - - // Show promotion if never shown before and 24 hours after initial activation with FS. - if ( ! $was_promotion_shown_before && - $this->_storage->install_timestamp > ( time() - $this->apply_filters( 'show_first_trial_after_n_sec', WP_FS__TIME_24_HOURS_IN_SEC ) ) - ) { - return false; - } - - // OR if promotion was shown before, try showing it every 30 days. - if ( $was_promotion_shown_before && - $this->apply_filters( 'reshow_trial_after_every_n_sec', 30 * WP_FS__TIME_24_HOURS_IN_SEC ) > time() - $last_time_trial_promotion_shown - ) { - return false; - } - - $trial_period = $this->_trial_days; - $require_payment = $this->_is_trial_require_payment; - $trial_url = $this->get_trial_url(); - $plans_string = strtolower( $this->get_text_inline( 'Awesome', 'awesome' ) ); - - if ( $this->is_registered() ) { - // If opted-in, override trial with up to date data from API. - $trial_plans = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); - $trial_plans_count = count( $trial_plans ); - - if ( 0 === $trial_plans_count ) { - // If there's no plans with a trial just exit. - return false; - } - - /** - * @var FS_Plugin_Plan $paid_plan - */ - $paid_plan = $trial_plans[0]; - $require_payment = $paid_plan->is_require_subscription; - $trial_period = $paid_plan->trial_period; - - $total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 ); - - if ( $total_paid_plans !== $trial_plans_count ) { - // Not all paid plans have a trial - generate a string of those that have it. - for ( $i = 0; $i < $trial_plans_count; $i ++ ) { - $plans_string .= sprintf( - ' %s', - $trial_url, - $trial_plans[ $i ]->title - ); - - if ( $i < $trial_plans_count - 2 ) { - $plans_string .= ', '; - } else if ( $i == $trial_plans_count - 2 ) { - $plans_string .= ' and '; - } - } - } - } - - $message = sprintf( - $this->get_text_x_inline( 'Hey', 'exclamation', 'hey' ) . '! ' . $this->get_text_inline( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.', 'trial-x-promotion-message' ), - sprintf( '%s', $this->get_plugin_name() ), - $plans_string, - $trial_period - ); - - // "No Credit-Card Required" or "No Commitment for N Days". - $cc_string = $require_payment ? - sprintf( $this->get_text_inline( 'No commitment for %s days - cancel anytime!', 'no-commitment-for-x-days' ), $trial_period ) : - $this->get_text_inline( 'No credit card required', 'no-cc-required' ) . '!'; - - - // Start trial button. - $button = ' ' . sprintf( - '', - $trial_url, - $this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' ) - ); - - $this->_admin_notices->add_sticky( - $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ), - 'trial_promotion', - '', - 'promotion' - ); - - $this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME; - - return true; - } - - /** - * Lets users/customers know that the product has an affiliate program. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2.11 - * - * @return bool Returns true if the notice has been added. - */ - function _add_affiliate_program_notice() { - if ( ! $this->is_user_admin() ) { - return false; - } - - if ( ! $this->is_user_in_admin() ) { - return false; - } - - // Check if the notice is already shown. - if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { - return false; - } - - if ( - // Product has no affiliate program. - ! $this->has_affiliate_program() || - // User has applied for an affiliate account. - ! empty( $this->_storage->affiliate_application_data ) - ) { - return false; - } - - if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) { - // Developer explicitly asked not to show the notice about the affiliate program. - return false; - } - - if ( $this->is_activation_mode() || $this->is_pending_activation() ) { - // If not yet opted in/skipped, or pending activation, don't show the notice. - return false; - } - - $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false ); - $was_notice_shown_before = ( false !== $last_time_notice_was_shown ); - - /** - * Do not show the notice if it was already shown before or less than 30 days have passed since the initial - * activation with FS. - */ - if ( $was_notice_shown_before || - $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) ) - ) { - return false; - } - - if ( ! $this->is_paying() && - FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation - ) { - // If the user is not a customer and the affiliate program is only for customers, don't show the notice. - return false; - } - - $message = sprintf( - $this->get_text_inline( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!', 'become-an-ambassador-admin-notice' ), - sprintf( '%s', $this->get_plugin_name() ), - $this->get_module_label( true ) - ); - - // HTML code for the "Learn more..." button. - $button = ' ' . sprintf( - '', - $this->_get_admin_page_url( 'affiliation' ), - $this->get_text_inline( 'Learn more', 'learn-more' ) . '...' - ); - - $this->_admin_notices->add_sticky( - $this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ), - 'affiliate_program', - '', - 'promotion' - ); - - $this->_storage->affiliate_program_notice_shown = WP_FS__SCRIPT_START_TIME; - - return true; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - */ - function _enqueue_common_css() { - if ( $this->has_paid_plan() && ! $this->is_paying() ) { - // Add basic CSS for admin-notices and menu-item colors. - fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - function _show_theme_activation_optin_dialog() { - fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); - - add_action( 'admin_footer', array( &$this, '_add_fs_theme_activation_dialog' ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - function _add_fs_theme_activation_dialog() { - global $pagenow; - - if ( 'themes.php' !== $pagenow ) { - return; - } - - $vars = array( 'id' => $this->_module_id ); - fs_require_once_template( 'connect.php', $vars ); - } - - /* Action Links - ------------------------------------------------------------------------------------------------------------------*/ - private $_action_links_hooked = false; - private $_action_links = array(); - - /** - * Hook to plugin action links filter. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - */ - private function hook_plugin_action_links() { - $this->_logger->entrance(); - - $this->_action_links_hooked = true; - - $this->_logger->log( 'Adding action links hooks.' ); - - // Add action link to settings page. - add_filter( 'plugin_action_links_' . $this->_plugin_basename, array( - &$this, - '_modify_plugin_action_links_hook' - ), WP_FS__DEFAULT_PRIORITY, 2 ); - add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array( - &$this, - '_modify_plugin_action_links_hook' - ), WP_FS__DEFAULT_PRIORITY, 2 ); - } - - /** - * Add plugin action link. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * - * @param $label - * @param $url - * @param bool $external - * @param int $priority - * @param bool $key - */ - function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) { - $this->_logger->entrance(); - - if ( ! isset( $this->_action_links[ $priority ] ) ) { - $this->_action_links[ $priority ] = array(); - } - - if ( false === $key ) { - $key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) ); - } - - $this->_action_links[ $priority ][] = array( - 'label' => $label, - 'href' => $url, - 'key' => $key, - 'external' => $external - ); - } - - /** - * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - */ - function _add_upgrade_action_link() { - $this->_logger->entrance(); - - $is_activation_mode = $this->is_activation_mode(); - - $add_action_links = $this->should_add_submenu_or_action_links( $is_activation_mode ); - - /** - * The following logic is based on the logic in `add_submenu_items()` method that decides when the "Upgrade" - * and "Add-Ons" menus should be added. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $add_upgrade_link = ( - $add_action_links || - ( $is_activation_mode && $this->is_only_premium() ) - ) && ! WP_FS__DEMO_MODE && ( ! $this->is_whitelabeled() ); - - $add_addons_link = ( $add_action_links && $this->has_addons() ); - - if ( ! $add_upgrade_link && ! $add_addons_link ) { - return; - } - - if ( - $add_upgrade_link && - $this->is_pricing_page_visible() && - $this->is_submenu_item_visible( 'pricing' ) - ) { - $this->add_plugin_action_link( - $this->get_text_inline( 'Upgrade', 'upgrade' ), - $this->get_upgrade_url(), - false, - 7, - 'upgrade' - ); - } - - if ( - $add_addons_link && - $this->has_addons() && - $this->is_submenu_item_visible( 'addons' ) - ) { - $this->add_plugin_action_link( - $this->get_text_inline( 'Add-Ons', 'add-ons' ), - $this->_get_admin_page_url( 'addons' ), - false, - 9, - 'addons' - ); - } - } - - /** - * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection. - * - * @author Leo Fajardo (@leorw) - * @since 1.1.9 - */ - function _add_license_action_link() { - $this->_logger->entrance(); - - if ( ! self::is_ajax() ) { - // Inject license activation dialog UI and client side code. - add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) ); - } - - $link_text = $this->is_free_plan() ? - $this->get_text_inline( 'Activate License', 'activate-license' ) : - $this->get_text_inline( 'Change License', 'change-license' ); - - $this->add_plugin_action_link( - $link_text, - '#', - false, - 11, - ( 'activate-license ' . $this->get_unique_affix() ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - */ - function _add_premium_version_upgrade_selection_action() { - $this->_logger->entrance(); - - if ( ! self::is_ajax() ) { - add_action( 'admin_footer', array( &$this, '_add_premium_version_upgrade_selection_dialog_box' ) ); - } - } - - /** - * Adds "Opt In" or "Opt Out" link to the main "Plugins" page link actions collection. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - */ - function _add_tracking_links() { - if ( ! current_user_can( 'manage_options' ) ) { - return; - } - - $this->_logger->entrance(); - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 Allow opting out from usage-tracking for paid products too by giving the appropriate warning letting the user know the automatic updates mechanism cannot function without an ongoing connection to the licensing and updates engine. - */ - /*if ( $this->is_premium() ) { - // Don't add opt-in/out for premium code base. - return; - }*/ - - if ( $this->is_only_premium() && $this->is_free_plan() ) { - // Don't add tracking links for premium-only products that were opted-in by relation (add-on or a parent product) before activating any license. - return; - } - - if ( - $this->is_addon() && - ! $this->is_only_premium() && - $this->_parent->is_anonymous() - ) { - return; - } - - if ( fs_is_network_admin() ) { - if ( ! $this->_is_network_active ) { - // Don't add tracking links when browsing the network WP Admin and the plugin is not network active. - return; - } else if ( $this->is_network_delegated_connection() ) { - // Don't add tracking links when browsing the network WP Admin and the activation has been delegated to site admins. - return; - } - } else { - if ( $this->_is_network_active && ! $this->is_delegated_connection() ) { - // Don't add tracking links when browsing the sub-site WP Admin, the plugin is network active, and the connection was not delegated. - return; - } - } - - if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) { - if ( ! $this->is_registered() && $this->is_anonymous() ) { - $this->connect_again(); - - return; - } - } - - if ( ( $this->is_plugin() && ! self::is_plugins_page() ) || - ( $this->is_theme() && ! self::is_themes_page() ) - ) { - // Only show tracking links on the plugins and themes pages. - return; - } - - if ( - $this->is_activation_mode() && - $this->is_premium() && - ! $this->is_registered() - ) { - // If not yet registered and running the premium code base, a license activation link will already be shown. - return; - } - - if ( $this->is_registered() && $this->is_tracking_allowed() ) { - if ( ! $this->is_premium() && ! $this->is_enable_anonymous() ) { - // If opted in and tracking is allowed, don't allow to opt out if not premium and anonymous mode is disabled. - return; - } - } - - if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) { - return; - } - - if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) { - return; - } - - if ( $this->add_ajax_action( 'update_tracking_permission', array( &$this, '_update_tracking_permission_callback' ) ) ) { - return; - } - - $link_text_id = ''; - $url = '#'; - - if ( $this->is_registered() ) { - if ( $this->is_tracking_allowed() ) { - $link_text_id = $this->get_text_inline( 'Opt Out', 'opt-out' ); - } else { - $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); - } - } else if ( $this->is_anonymous() || $this->is_activation_mode() ) { - /** - * Show opt-in link only if skipped or in activation mode. - */ - $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); - - $params = ! $this->is_anonymous() ? - array() : - array( - 'nonce' => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ), - 'fs_action' => ( $this->get_unique_affix() . '_reconnect' ), - ); - - $url = $this->get_activation_url( $params ); - } - - add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) ); - - if ( ! empty( $link_text_id ) && $this->is_plugin() && self::is_plugins_page() ) { - $this->add_plugin_action_link( - $link_text_id, - $url, - false, - 13, - "opt-in-or-opt-out {$this->_slug}" - ); - } - } - - /** - * Get the URL of the page that should be loaded right after the plugin activation. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - * - * @return string - */ - function get_after_plugin_activation_redirect_url() { - $url = false; - - if ( ! $this->is_addon() || ! $this->has_free_plan() ) { - $first_time_path = $this->_menu->get_first_time_path( - fs_is_network_admin() && $this->_is_network_active - ); - - if ( $this->is_activation_mode() ) { - $url = $this->get_activation_url(); - } else if ( ! empty( $first_time_path ) ) { - $url = $first_time_path; - } else { - $page = ''; - if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { - if ( $this->is_network_registered() ) { - $page = 'account'; - } else if ( $this->is_pending_activation() || $this->is_network_anonymous() ) { - $this->maybe_set_slug_and_network_menu_exists_flag(); - } - } - - $url = $this->_get_admin_page_url( $page ); - } - } else { - $plugin_fs = false; - - if ( $this->is_parent_plugin_installed() ) { - $plugin_fs = self::get_parent_instance(); - } - - if ( is_object( $plugin_fs ) ) { - if ( ! $plugin_fs->is_registered() ) { - // Forward to parent plugin connect when parent not registered. - $url = $plugin_fs->get_activation_url(); - } else { - // Forward to account page. - $url = $plugin_fs->_get_admin_page_url( 'account' ); - } - } - } - - return $url; - } - - /** - * Forward page to activation page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - */ - function _redirect_on_activation_hook() { - if ( $this->apply_filters( 'redirect_on_activation', true ) ) { - $url = $this->get_after_plugin_activation_redirect_url(); - - if ( is_string( $url ) ) { - fs_redirect( $url ); - } - } - } - - /** - * Modify plugin's page action links collection. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.0 - * - * @param array $links - * @param $file - * - * @return array - */ - function _modify_plugin_action_links_hook( $links, $file ) { - $this->_logger->entrance(); - - $passed_deactivate = false; - $deactivate_link = ''; - $before_deactivate = array(); - $after_deactivate = array(); - foreach ( $links as $key => $link ) { - if ( 'deactivate' === $key ) { - $deactivate_link = $link; - $passed_deactivate = true; - continue; - } - - if ( ! $passed_deactivate ) { - $before_deactivate[ $key ] = $link; - } else { - $after_deactivate[ $key ] = $link; - } - } - - ksort( $this->_action_links ); - - foreach ( $this->_action_links as $new_links ) { - foreach ( $new_links as $link ) { - $before_deactivate[ $link['key'] ] = '' . $link['label'] . ''; - } - } - - if ( ! empty( $deactivate_link ) ) { - /** - * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link. - * - * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation. - */ - $deactivate_link .= ''; - - // Append deactivation link. - $before_deactivate['deactivate'] = $deactivate_link; - } - - return array_merge( $before_deactivate, $after_deactivate ); - } - - /** - * Adds admin message. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $message - * @param string $title - * @param string $type - */ - function add_admin_message( $message, $title = '', $type = 'success' ) { - $this->_admin_notices->add( $message, $title, $type ); - } - - /** - * Adds sticky admin message. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.0 - * - * @param string $message - * @param string $id - * @param string $title - * @param string $type - */ - function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' ) { - $this->_admin_notices->add_sticky( $message, $id, $title, $type ); - } - - /** - * Check if the paid version of the module is installed. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.0 - * - * @return bool - */ - private function is_premium_version_installed() { - $premium_plugin_basename = $this->premium_plugin_basename(); - $premium_plugin = get_plugins( '/' . dirname( $premium_plugin_basename ) ); - - return ! empty( $premium_plugin ); - } - - /** - * Helper function that returns the final steps for the upgrade completion. - * - * If the module is already running the premium code, returns an empty string. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param string $plan_title - * - * @return string - */ - private function get_complete_upgrade_instructions( $plan_title = '' ) { - $this->_logger->entrance(); - - $activate_license_string = $this->get_license_network_activation_notice(); - - if ( ! $this->has_premium_version() || $this->is_premium() ) { - return '' . $activate_license_string; - } - - if ( empty( $plan_title ) ) { - $plan_title = $this->get_plan_title(); - } - - if ( $this->is_premium_version_installed() ) { - /** - * If the premium version is already installed, instead of showing the installation instructions, - * tell the current user to activate it. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - $premium_plugin_basename = $this->premium_plugin_basename(); - - return sprintf( - /* translators: %1$s: Product title; %2$s: Plan title */ - $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ), - sprintf( '%s', esc_html( $this->get_plugin_title() ) ), - $plan_title, - sprintf( - '', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_plugin_basename, 'activate-plugin_' . $premium_plugin_basename ), - esc_html( sprintf( - /* translators: %s: Plan title */ - $this->get_text_inline( 'Activate %s features', 'activate-x-features' ), - $plan_title - ) ) - ) - ); - } else { - // @since 1.2.1.5 The free version is auto deactivated. - $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ? - ( '
  • ' . $this->esc_html_inline( 'Deactivate the free version', 'deactivate-free-version' ) . '.
  • ' ) : - ''; - - return sprintf( - ' %s:
    1. %s.
    2. %s
    3. %s (%s).
    ', - $this->get_text_inline( 'Please follow these steps to complete the upgrade', 'follow-steps-to-complete-upgrade' ), - ( empty( $activate_license_string ) ? '' : $activate_license_string . '
  • ' ) . - $this->get_latest_download_link( sprintf( - /* translators: %s: Plan title */ - $this->get_text_inline( 'Download the latest %s version', 'download-latest-x-version' ), - $plan_title - ) ), - $deactivation_step, - $this->get_text_inline( 'Upload and activate the downloaded version', 'upload-and-activate' ), - $this->apply_filters( 'upload_and_install_video_url', '//bit.ly/upload-wp-' . $this->_module_type . 's' ), - $this->get_text_inline( 'How to upload and activate?', 'howto-upload-activate' ) - ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param string $url - * @param array $request - */ - private static function enrich_request_for_debug( &$url, &$request ) { - if ( WP_FS__DEBUG_SDK || isset( $_COOKIE['XDEBUG_SESSION'] ) ) { - $url = add_query_arg( 'XDEBUG_SESSION_START', rand( 0, 9999999 ), $url ); - $url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url ); - - $request['cookies'] = array( - new WP_Http_Cookie( array( - 'name' => 'XDEBUG_SESSION', - 'value' => 'PHPSTORM', - ) ) - ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param string $url - * @param array $request - * @param int $success_cache_expiration - * @param int $failure_cache_expiration - * @param bool $maybe_enrich_request_for_debug - * - * @return WP_Error|array - */ - static function safe_remote_post( - &$url, - $request, - $success_cache_expiration = 0, - $failure_cache_expiration = 0, - $maybe_enrich_request_for_debug = true - ) { - $should_cache = ($success_cache_expiration + $failure_cache_expiration > 0); - - $cache_key = $should_cache ? md5( fs_strip_url_protocol($url) . json_encode( $request ) ) : false; - - $response = (!WP_FS__DEBUG_SDK && ( false !== $cache_key )) ? - get_transient( $cache_key ) : - false; - - if ( false === $response ) { - if ( $maybe_enrich_request_for_debug ) { - self::enrich_request_for_debug( $url, $request ); - } - - $response = wp_remote_post( $url, $request ); - - if ( $response instanceof WP_Error ) { - if ( 'https://' === substr( $url, 0, 8 ) && - isset( $response->errors ) && - isset( $response->errors['http_request_failed'] ) - ) { - $http_error = strtolower( $response->errors['http_request_failed'][0] ); - - if ( false !== strpos( $http_error, 'ssl' ) || - false !== strpos( $http_error, 'curl error 35' ) - ) { - // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). - $url = 'http://' . substr( $url, 8 ); - - $request['timeout'] = 15; - $response = wp_remote_post( $url, $request ); - } - } - } - - if ( false !== $cache_key ) { - set_transient( - $cache_key, - $response, - ( ( $response instanceof WP_Error ) ? - $failure_cache_expiration : - $success_cache_expiration ) - ); - } - } - - return $response; - } - - /** - * This method is used to enrich the after upgrade notice instructions when the upgraded - * license cannot be activated network wide (license quota isn't large enough). - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - private function get_license_network_activation_notice() { - if ( ! $this->_is_network_active ) { - // Module isn't network level activated. - return ''; - } - - if ( ! fs_is_network_admin() ) { - // Not network level admin. - return ''; - } - - if ( get_blog_count() == 1 ) { - // There's only a single site in the network so if there's a context license it was already activated. - return ''; - } - - if ( ! is_object( $this->_license ) ) { - // No context license. - return ''; - } - - if ( $this->_license->is_single_site() && 0 < $this->_license->activated ) { - // License was already utilized (this is not 100% the case if all the network is localhost sites and the license can be utilized on unlimited localhost sites). - return ''; - } - - if ( $this->can_activate_license_on_network( $this->_license ) ) { - // License can be activated on all the network, so probably, the license is already activate on all the network (that's how the after upgrade sync works). - return ''; - } - - return sprintf( - $this->get_text_inline( '%sClick here%s to choose the sites where you\'d like to activate the license on.', 'network-choose-sites-for-license' ), - '', - '' - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $key - * - * @return string - */ - function get_text( $key ) { - return fs_text( $key, $this->_slug ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * - * @return string - */ - function get_text_inline( $text, $key = '' ) { - return _fs_text_inline( $text, $key, $this->_slug ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * - * @return string - */ - function get_text_x_inline( $text, $context, $key ) { - return _fs_text_x_inline( $text, $context, $key, $this->_slug ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * - * @return string - */ - function esc_html_inline( $text, $key ) { - return esc_html( _fs_text_inline( $text, $key, $this->_slug ) ); - } - - #---------------------------------------------------------------------------------- - #region Versioning - #---------------------------------------------------------------------------------- - - /** - * Check if Freemius in SDK upgrade mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_sdk_upgrade_mode() { - return isset( $this->_storage->sdk_upgrade_mode ) ? - $this->_storage->sdk_upgrade_mode : - false; - } - - /** - * Turn SDK upgrade mode off. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function set_sdk_upgrade_complete() { - $this->_storage->sdk_upgrade_mode = false; - } - - /** - * Check if plugin upgrade mode. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_plugin_upgrade_mode() { - return isset( $this->_storage->plugin_upgrade_mode ) ? - $this->_storage->plugin_upgrade_mode : - false; - } - - /** - * Turn plugin upgrade mode off. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function set_plugin_upgrade_complete() { - $this->_storage->plugin_upgrade_mode = false; - - $license_migration = ! empty( $this->_storage->license_migration ) ? - $this->_storage->license_migration : - array(); - - $license_migration['is_migrating'] = false; - - $this->_storage->license_migration = $license_migration; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Permissions - #---------------------------------------------------------------------------------- - - /** - * Check if specific permission requested. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $permission - * - * @return bool - */ - function is_permission_requested( $permission ) { - return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Auto Activation - #---------------------------------------------------------------------------------- - - /** - * Hints the SDK if running an auto-installation. - * - * @var bool - */ - private $_isAutoInstall = false; - - /** - * After upgrade callback to install and auto activate a plugin. - * This code will only be executed on explicit request from the user, - * following the practice Jetpack are using with their theme installations. - * - * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - function _install_premium_version_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'install_premium_version' ); - - if ( ! $this->is_registered() ) { - // Not registered. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Auto installation only works for opted-in users.', 'auto-install-error-not-opted-in' ), - 'code' => 'premium_installed', - ) ); - } - - $plugin_id = fs_request_get( 'target_module_id', $this->get_id() ); - - if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { - // Invalid ID. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), - 'code' => 'invalid_module_id', - ) ); - } - - if ( $plugin_id == $this->get_id() ) { - if ( $this->is_premium() ) { - // Already using the premium code version. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), - 'code' => 'premium_installed', - ) ); - } - if ( ! $this->can_use_premium_code() ) { - // Don't have access to the premium code. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'You do not have a valid license to access the premium version.', 'auto-install-error-invalid-license' ), - 'code' => 'invalid_license', - ) ); - } - if ( ! $this->has_release_on_freemius() ) { - // Plugin is a serviceware, no premium code version. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Plugin is a "Serviceware" which means it does not have a premium code version.', 'auto-install-error-serviceware' ), - 'code' => 'premium_version_missing', - ) ); - } - } else { - $addon = $this->get_addon( $plugin_id ); - - if ( ! is_object( $addon ) ) { - // Invalid add-on ID. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), - 'code' => 'invalid_module_id', - ) ); - } - - if ( $this->is_addon_activated( $plugin_id, true ) ) { - // Premium add-on version is already activated. - self::shoot_ajax_failure( array( - 'message' => $this->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ), - 'code' => 'premium_installed', - ) ); - } - } - - $this->_isAutoInstall = true; - - // Try to install and activate. - $updater = FS_Plugin_Updater::instance( $this ); - $result = $updater->install_and_activate_plugin( $plugin_id ); - - if ( is_array( $result ) && ! empty( $result['message'] ) ) { - self::shoot_ajax_failure( array( - 'message' => $result['message'], - 'code' => $result['code'], - ) ); - } - - self::shoot_ajax_success( $result ); - } - - /** - * Displays module activation dialog box after a successful upgrade - * where the user explicitly requested to auto download and install - * the premium version. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - function _add_auto_installation_dialog_box() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { - // Not registered. - return; - } - - $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); - - if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { - // Invalid module ID. - return; - } - - if ( $plugin_id == $this->get_id() ) { - if ( $this->is_premium() ) { - // Already using the premium code version. - return; - } - if ( ! $this->can_use_premium_code() ) { - // Don't have access to the premium code. - return; - } - if ( ! $this->has_release_on_freemius() ) { - // Plugin is a serviceware, no premium code version. - return; - } - } else { - $addon = $this->get_addon( $plugin_id ); - - if ( ! is_object( $addon ) ) { - // Invalid add-on ID. - return; - } - - if ( $this->is_addon_activated( $plugin_id, true ) ) { - // Premium add-on version is already activated. - return; - } - } - - $vars = array( - 'id' => $this->_module_id, - 'target_module_id' => $plugin_id, - 'slug' => $this->_slug, - ); - - fs_require_template( 'auto-installation.php', $vars ); - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Tabs Integration - #-------------------------------------------------------------------------------- - - #region Module's Original Tabs - - /** - * Inject a JavaScript logic to capture the theme tabs HTML. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - function _tabs_capture() { - $this->_logger->entrance(); - - if ( ! $this->is_product_settings_page() || - ! $this->is_matching_url( $this->main_menu_url() ) - ) { - return; - } - - $params = array( - 'id' => $this->_module_id, - ); - - fs_require_once_template( 'tabs-capture-js.php', $params ); - } - - /** - * Cache theme's tabs HTML for a week. The cache will also be set as expired - * after version and type (free/premium) changes, in addition to the week period. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - function _store_tabs_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'store_tabs' ); - - // Init filesystem if not yet initiated. - WP_Filesystem(); - - // Get POST body HTML data. - global $wp_filesystem; - $tabs_html = $wp_filesystem->get_contents( "php://input" ); - - if ( is_string( $tabs_html ) ) { - $tabs_html = trim( $tabs_html ); - } - - if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) { - self::shoot_ajax_failure(); - } - - $this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); - - self::shoot_ajax_success(); - } - - /** - * Cache theme's settings page custom styles. The cache will also be set as expired - * after version and type (free/premium) changes, in addition to the week period. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - function _store_tabs_styles() { - $this->_logger->entrance(); - - if ( ! $this->is_product_settings_page() || - ! $this->is_matching_url( $this->main_menu_url() ) - ) { - return; - } - - $wp_styles = wp_styles(); - - $theme_styles_url = get_template_directory_uri(); - - $stylesheets = array(); - foreach ( $wp_styles->queue as $handler ) { - if ( fs_starts_with( $handler, 'fs_' ) ) { - // Assume that stylesheets that their handler starts with "fs_" belong to the SDK. - continue; - } - - /** - * @var _WP_Dependency $stylesheet - */ - $stylesheet = $wp_styles->registered[ $handler ]; - - if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) { - $stylesheets[] = $stylesheet->src; - } - } - - if ( ! empty( $stylesheets ) ) { - $this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); - } - } - - /** - * Check if module's original settings page has any tabs. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - private function has_tabs() { - return $this->_cache->has( 'tabs' ); - } - - /** - * Get module's settings page HTML content, starting - * from the beginning of the
    element, - * until the tabs HTML (including). - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return string - */ - private function get_tabs_html() { - $this->_logger->entrance(); - - return $this->_cache->get( 'tabs' ); - } - - /** - * Check if page should include tabs. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool - */ - private function should_page_include_tabs() { - if ( ! $this->has_settings_menu() ) { - // Don't add tabs if no settings at all. - return false; - } - - if ( self::NAVIGATION_TABS !== $this->_navigation ) { - // Only add tabs to themes for now. - return false; - } - - if ( $this->is_theme() && ! $this->has_paid_plan() && ! $this->has_addons() ) { - // Only add tabs to monetizing themes. - return false; - } - - if ( ! $this->is_product_settings_page() ) { - // Only add tabs if browsing one of the product's setting pages. - return false; - } - - if ( $this->is_activation_mode() && $this->is_activation_page() ) { - // Don't include tabs in the activation page. - return false; - } - - if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) { - // Don't add tabs on checkout page, we want to reduce distractions - // as much as possible. - return false; - } - - return true; - } - - /** - * Add the tabs HTML before the setting's page content and - * enqueue any required stylesheets. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool If tabs were included. - */ - function _add_tabs_before_content() { - $this->_logger->entrance(); - - if ( ! $this->should_page_include_tabs() ) { - return false; - } - - /** - * Enqueue the original stylesheets that are included in the - * theme settings page. That way, if the theme settings has - * some custom _styled_ content above the tabs UI, this - * will make sure that the styling is preserved. - */ - $stylesheets = $this->_cache->get( 'tabs_stylesheets', array() ); - if ( is_array( $stylesheets ) ) { - for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) { - wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] ); - } - } - - // Cut closing
    tag. - echo substr( trim( $this->get_tabs_html() ), 0, - 6 ); - - return true; - } - - /** - * Add the tabs closing HTML after the setting's page content. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return bool If tabs closing HTML was included. - */ - function _add_tabs_after_content() { - $this->_logger->entrance(); - - if ( ! $this->should_page_include_tabs() ) { - return false; - } - - echo '
  • '; - - return true; - } - - #endregion - - /** - * Add in-page JavaScript to inject the Freemius tabs into - * the module's setting tabs section. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - function _add_freemius_tabs() { - $this->_logger->entrance(); - - if ( ! $this->should_page_include_tabs() ) { - return; - } - - $params = array( 'id' => $this->_module_id ); - fs_require_once_template( 'tabs.php', $params ); - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Customizer Integration for Themes - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param WP_Customize_Manager $customizer - */ - function _customizer_register( $customizer ) { - $this->_logger->entrance(); - - if ( $this->is_pricing_page_visible() ) { - require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php'; - - $customizer->add_section( 'freemius_upsell', array( - 'title' => '★ ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ), - 'priority' => 1, - ) ); - $customizer->add_setting( 'freemius_upsell', array( - 'sanitize_callback' => 'esc_html', - ) ); - - $customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array( - 'fs' => $this, - 'section' => 'freemius_upsell', - 'priority' => 100, - ) ) ); - } - - if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) { - require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php'; - - // Main Documentation Link In Customizer Root. - $customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array( - 'fs' => $this, - 'priority' => 1000, - ) ) ); - } - } - - #endregion - - /** - * If the theme has a paid version, add some custom - * styling to the theme's premium version (if exists) - * to highlight that it's the premium version of the - * same theme, making it easier for identification - * after the user upgrades and upload it to the site. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - function _style_premium_theme() { - $this->_logger->entrance(); - - if ( ! self::is_themes_page() ) { - // Only include in the themes page. - return; - } - - if ( ! $this->has_paid_plan() ) { - // Only include if has any paid plans. - return; - } - - $params = null; - fs_require_once_template( '/js/jquery.content-change.php', $params ); - - $params = array( - 'slug' => $this->_slug, - 'id' => $this->_module_id, - ); - - fs_require_template( '/js/style-premium-theme.php', $params ); - } - - /** - * This method will return the absolute URL of the module's local icon. - * - * When you are running your plugin or theme on a **localhost** environment, if the icon - * is not found in the local assets folder, try to fetch the icon URL from Freemius. If not set and - * it's a plugin hosted on WordPress.org, try fetching the icon URL from wordpress.org. - * If an icon is found, this method will automatically attempt to download the icon and store it - * in /freemius/assets/img/{slug}.{png|jpg|gif|svg}. - * - * It's important to mention that this method is NOT phoning home since the developer will deploy - * the product with the local icon in the assets folder. The download process just simplifies - * the process for the developer. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function get_local_icon_url() { - global $fs_active_plugins; - - /** - * @since 1.1.7.5 - */ - $local_path = $this->apply_filters( 'plugin_icon', false ); - - if ( is_string( $local_path ) ) { - $icons = array( $local_path ); - } else { - $img_dir = WP_FS__DIR_IMG; - - // Locate the main assets folder. - if ( 1 < count( $fs_active_plugins->plugins ) ) { - $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); - - foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) { - if ( $data->plugin_path == $this->get_plugin_basename() ) { - $img_dir = $plugin_or_theme_img_dir - . '/' - /** - * The basename will be `themes` or the basename of a custom themes directory. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - . str_replace( '../' . basename( $plugin_or_theme_img_dir ) . '/', '', $sdk_path ) - . '/assets/img'; - - break; - } - } - } - - // Try to locate the icon in the assets folder. - $icons = glob( fs_normalize_path( $img_dir . "/{$this->_slug}.*" ) ); - - if ( ! is_array( $icons ) || 0 === count( $icons ) ) { - if ( ! WP_FS__IS_LOCALHOST && $this->is_theme() ) { - $icons = array( - fs_normalize_path( $img_dir . '/theme-icon.png' ) - ); - } else { - $icon_found = false; - $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.png" ); - - if ( ! function_exists( 'get_filesystem_method' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - - $have_write_permissions = ( 'direct' === get_filesystem_method( array(), fs_normalize_path( $img_dir ) ) ); - - /** - * IMPORTANT: THIS CODE WILL NEVER RUN AFTER THE PLUGIN IS IN THE REPO. - * - * This code will only be executed once during the testing - * of the plugin in a local environment. The plugin icon file WILL - * already exist in the assets folder when the plugin is deployed to - * the repository. - */ - if ( WP_FS__IS_LOCALHOST && $have_write_permissions ) { - // Fetch icon from Freemius. - $icon = $this->fetch_remote_icon_url(); - - // Fetch icon from WordPress.org. - if ( empty( $icon ) && $this->is_plugin() && $this->is_org_repo_compliant() ) { - if ( ! function_exists( 'plugins_api' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; - } - - $plugin_information = plugins_api( 'plugin_information', array( - 'slug' => $this->_slug, - 'fields' => array( - 'sections' => false, - 'tags' => false, - 'icons' => true - ) - ) ); - - if ( - ! is_wp_error( $plugin_information ) - && isset( $plugin_information->icons ) - && ! empty( $plugin_information->icons ) - ) { - /** - * Get the smallest icon. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - $icon = end( $plugin_information->icons ); - } - } - - if ( ! empty( $icon ) ) { - if ( 0 !== strpos( $icon, 'http' ) ) { - $icon = 'http:' . $icon; - } - - /** - * Get a clean file extension, e.g.: "jpg" and not "jpg?rev=1305765". - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - $ext = pathinfo( strtok( $icon, '?' ), PATHINFO_EXTENSION ); - - $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.{$ext}" ); - - // Try to download the icon. - $icon_found = fs_download_image( $icon, $local_path ); - } - } - - if ( ! $icon_found ) { - // No icons found, fallback to default icon. - if ( $have_write_permissions ) { - // If have write permissions, copy default icon. - copy( fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ), $local_path ); - } else { - // If doesn't have write permissions, use default icon path. - $local_path = fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ); - } - } - - $icons = array( $local_path ); - } - } - } - - $icon_dir = dirname( $icons[0] ); - - return fs_img_url( substr( $icons[0], strlen( $icon_dir ) ), $icon_dir ); - } - - /** - * Fetch module's extended info. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return object|mixed - */ - private function fetch_module_info() { - return $this->get_api_plugin_scope()->get( 'info.json', false, WP_FS__TIME_WEEK_IN_SEC ); - } - - /** - * Fetch module's remote icon URL. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function fetch_remote_icon_url() { - $info = $this->fetch_module_info(); - - return ( $this->is_api_result_object( $info, 'icon' ) && is_string( $info->icon ) ) ? - $info->icon : - ''; - } - - #-------------------------------------------------------------------------------- - #region GDPR - #-------------------------------------------------------------------------------- - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @return bool - */ - function fetch_and_store_current_user_gdpr_anonymously() { - $pong = $this->ping( null, true ); - - if ( ! $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) { - return false; - } else { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - return $pong->is_gdpr_required; - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param array $user_plugins - * - * @return string - */ - private function get_gdpr_admin_notice_string( $user_plugins ) { - $this->_logger->entrance(); - - $addons = self::get_all_addons(); - - foreach ( $user_plugins as $user_plugin ) { - $has_addons = isset( $addons[ $user_plugin->id ] ); - - if ( WP_FS__MODULE_TYPE_PLUGIN === $user_plugin->type && ! $has_addons ) { - if ( $this->_module_id == $user_plugin->id ) { - $addons = $this->get_addons(); - $has_addons = ( ! empty( $addons ) ); - } else { - $plugin_api = FS_Api::instance( - $user_plugin->id, - 'plugin', - $user_plugin->id, - $user_plugin->public_key, - ! $user_plugin->is_live, - false, - $this->get_sdk_version() - ); - - $addons_result = $plugin_api->get( '/addons.json?enriched=true', true ); - - if ( $this->is_api_result_object( $addons_result, 'plugins' ) && - is_array( $addons_result->plugins ) && - ! empty( $addons_result->plugins ) - ) { - $has_addons = true; - } - } - } - - $user_plugin->has_addons = $has_addons; - } - - $is_single_parent_product = ( 1 === count( $user_plugins ) ); - - $multiple_products_text = ''; - - if ( $is_single_parent_product ) { - $single_parent_product = reset( $user_plugins ); - - $thank_you = sprintf( - "%s", - $single_parent_product->id, - sprintf( - $single_parent_product->has_addons ? - $this->get_text_inline( 'Thank you so much for using %s and its add-ons!', 'thank-you-for-using-product-and-its-addons' ) : - $this->get_text_inline( 'Thank you so much for using %s!', 'thank-you-for-using-product' ), - sprintf('%s', $single_parent_product->title) - ) - ); - - $already_opted_in = sprintf( - $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving the %s.", 'already-opted-in-to-product-usage-tracking' ), - ( WP_FS__MODULE_TYPE_THEME === $single_parent_product->type ) ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN - ); - } else { - $thank_you = $this->get_text_inline( 'Thank you so much for using our products!', 'thank-you-for-using-products' ); - $already_opted_in = $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving them.", 'already-opted-in-to-products-usage-tracking' ); - - $products_and_add_ons = ''; - foreach ( $user_plugins as $user_plugin ) { - if ( ! empty( $products_and_add_ons ) ) { - $products_and_add_ons .= ', '; - } - - if ( ! $user_plugin->has_addons ) { - $products_and_add_ons .= sprintf( - "%s", - $user_plugin->id, - $user_plugin->title - ); - } else { - $products_and_add_ons .= sprintf( - "%s", - $user_plugin->id, - sprintf( - $this->get_text_inline( '%s and its add-ons', 'product-and-its-addons' ), - $user_plugin->title - ) - ); - } - } - - $multiple_products_text = sprintf( - "%s: %s", - $this->get_text_inline( 'Products', 'products' ), - $products_and_add_ons - ); - } - - $actions = sprintf( - '
    • %s - %s
    • %s - %s
    ', - sprintf('', $this->get_text_inline( 'Yes', 'yes' ) ), - $this->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ), - sprintf('', $this->get_text_inline( 'No', 'no' ) ), - sprintf( - $this->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), - '', - '' - ) - ); - - return sprintf( - '%s %s %s', - $thank_you, - $already_opted_in, - sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . - '

    ' . - '' . $this->get_text_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) . '' . - $actions . - ( $is_single_parent_product ? '' : $multiple_products_text ) - ); - } - - /** - * This method is called for opted-in users to fetch the is_marketing_allowed flag of the user for all the - * plugins and themes they've opted in to. - * - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param string $user_email - * @param string $license_key - * @param array $plugin_ids - * @param string|null $license_key - * - * @return array|false - */ - private function fetch_user_marketing_flag_status_by_plugins( $user_email, $license_key, $plugin_ids ) { - $request = array( - 'method' => 'POST', - 'body' => array(), - 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, - ); - - if ( is_string( $user_email ) ) { - $request['body']['email'] = $user_email; - } else { - $request['body']['license_key'] = $license_key; - } - - $result = array(); - - $url = WP_FS__ADDRESS . '/action/service/user_plugin/'; - $total_plugin_ids = count( $plugin_ids ); - - $plugin_ids_count_per_request = 10; - for ( $i = 1; $i <= $total_plugin_ids; $i += $plugin_ids_count_per_request ) { - $plugin_ids_set = array_slice( $plugin_ids, $i - 1, $plugin_ids_count_per_request ); - - $request['body']['plugin_ids'] = $plugin_ids_set; - - $response = self::safe_remote_post( - $url, - $request, - WP_FS__TIME_24_HOURS_IN_SEC, - WP_FS__TIME_12_HOURS_IN_SEC - ); - - if ( ! is_wp_error( $response ) ) { - $decoded = is_string( $response['body'] ) ? - json_decode( $response['body'] ) : - null; - - if ( - !is_object($decoded) || - !isset($decoded->success) || - true !== $decoded->success || - !isset( $decoded->data ) || - !is_array( $decoded->data ) - ) { - return false; - } - - $result = array_merge( $result, $decoded->data ); - } - } - - return $result; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function _maybe_show_gdpr_admin_notice() { - if ( ! $this->is_user_in_admin() ) { - return; - } - - if ( ! $this->should_handle_gdpr_admin_notice() ) { - return; - } - - if ( ! $this->is_user_admin() ) { - return; - } - - require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; - - $lock = FS_User_Lock::instance(); - - /** - * Try to acquire a 60-sec lock based on the WP user and thread/process ID. - */ - if ( ! $lock->try_lock( 60 ) ) { - return; - } - - /** - * @var $current_wp_user WP_User - */ - $current_wp_user = self::_get_current_wp_user(); - - /** - * @var FS_User $current_fs_user - */ - $current_fs_user = Freemius::_get_user_by_email( $current_wp_user->user_email ); - - $ten_years_in_sec = 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC; - - if ( ! is_object( $current_fs_user ) ) { - // 10-year lock. - $lock->lock( $ten_years_in_sec ); - - return; - } - - $gdpr = FS_GDPR_Manager::instance(); - - if ( $gdpr->is_opt_in_notice_shown() ) { - // 30-day lock. - $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); - - return; - } - - if ( ! $gdpr->should_show_opt_in_notice() ) { - // 10-year lock. - $lock->lock( $ten_years_in_sec ); - - return; - } - - $last_time_notice_shown = $gdpr->last_time_notice_was_shown(); - $was_notice_shown_before = ( false !== $last_time_notice_shown ); - - if ( $was_notice_shown_before && - 30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_notice_shown - ) { - // If the notice was shown before, show it again after 30 days from the last time it was shown. - return; - } - - /** - * Find all plugin IDs that were installed by the current admin. - */ - $plugin_ids_map = self::get_user_opted_in_module_ids_map( $current_fs_user->id ); - - if ( empty( $plugin_ids_map )) { - $lock->lock( $ten_years_in_sec ); - - return; - } - - $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( - $current_fs_user->email, - null, - array_keys( $plugin_ids_map ) - ); - - if ( empty( $user_plugins ) ) { - $lock->lock( - is_array($user_plugins) ? - $ten_years_in_sec : - // Lock for 24-hours on errors. - WP_FS__TIME_24_HOURS_IN_SEC - ); - - return; - } - - $has_unset_marketing_optin = false; - - foreach ( $user_plugins as $user_plugin ) { - if ( true == $user_plugin->is_marketing_allowed ) { - unset( $plugin_ids_map[ $user_plugin->plugin_id ] ); - } - - if ( ! $has_unset_marketing_optin && is_null( $user_plugin->is_marketing_allowed ) ) { - $has_unset_marketing_optin = true; - } - } - - if ( empty( $plugin_ids_map ) || - ( $was_notice_shown_before && ! $has_unset_marketing_optin ) - ) { - $lock->lock( $ten_years_in_sec ); - - return; - } - - $modules = array_merge( - array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), - array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) - ); - - foreach ( $modules as $module ) { - if ( ! FS_Plugin::is_valid_id( $module->parent_plugin_id ) && isset( $plugin_ids_map[ $module->id ] ) ) { - $plugin_ids_map[ $module->id ] = $module; - } - } - - $plugin_title = null; - if ( 1 === count( $plugin_ids_map ) ) { - $module = reset( $plugin_ids_map ); - $plugin_title = $module->title; - } - - $gdpr->add_opt_in_sticky_notice( - $this->get_gdpr_admin_notice_string( $plugin_ids_map ), - $plugin_title - ); - - $this->add_gdpr_optin_ajax_handler_and_style(); - - $gdpr->notice_was_just_shown(); - - // 30-day lock. - $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); - } - - /** - * Prevents the GDPR opt-in admin notice from being added if the user has already chosen to allow or not allow - * marketing. - * - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - private function disable_opt_in_notice_and_lock_user() { - FS_GDPR_Manager::instance()->disable_opt_in_notice(); - - require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; - - // 10-year lock. - FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function _add_gdpr_optin_js() { - $vars = array( 'id' => $this->_module_id ); - - fs_require_once_template( 'gdpr-optin-js.php', $vars ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function enqueue_gdpr_optin_notice_style() { - fs_enqueue_local_style( 'fs_gdpr_optin_notice', '/admin/gdpr-optin-notice.css' ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function _maybe_add_gdpr_optin_ajax_handler() { - $this->add_ajax_action( 'fetch_is_marketing_required_flag_value', array( &$this, '_fetch_is_marketing_required_flag_value_ajax_action' ) ); - - if ( FS_GDPR_Manager::instance()->is_opt_in_notice_shown() ) { - $this->add_gdpr_optin_ajax_handler_and_style(); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function _fetch_is_marketing_required_flag_value_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); - - $license_key = fs_request_get( 'license_key' ); - - if ( empty($license_key) ) { - self::shoot_ajax_failure( $this->get_text_inline( 'License key is empty.', 'empty-license-key' ) ); - } - - $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( - null, - $license_key, - array( $this->_module_id ) - ); - - if ( ! is_array( $user_plugins ) || - empty($user_plugins) || - !isset($user_plugins[0]->plugin_id) || - $user_plugins[0]->plugin_id != $this->_module_id - ) { - /** - * If faced an error or if the module ID do not match to the current module, ask for GDPR opt-in. - * - * @author Vova Feldman (@svovaf) - */ - self::shoot_ajax_success( array( - 'is_marketing_allowed' => null, - 'license_owner_id' => null - ) ); - } - - self::shoot_ajax_success( array( - 'is_marketing_allowed' => $user_plugins[0]->is_marketing_allowed, - 'license_owner_id' => ( isset( $user_plugins[0]->license_owner_id ) ? $user_plugins[0]->license_owner_id : null ) - ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @param number[] $install_ids - * - * @return array { - * An array of objects containing the installs' licenses owners data. - * - * @property number $id User ID. - * @property string $email User email (can be masked email). - * } - */ - private function fetch_installs_licenses_owners_data( $install_ids ) { - $this->_logger->entrance(); - - $response = $this->get_api_user_scope()->get( - '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) - ); - - $license_owners = null; - - if ( $this->is_api_result_object( $response, 'owners' ) ) { - $license_owners = $response->owners; - } - - return $license_owners; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - private function add_gdpr_optin_ajax_handler_and_style() { - // Add GDPR action AJAX callback. - $this->add_ajax_action( 'gdpr_optin_action', array( &$this, '_gdpr_optin_ajax_action' ) ); - - add_action( 'admin_footer', array( &$this, '_add_gdpr_optin_js' ) ); - add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_gdpr_optin_notice_style' ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - function _gdpr_optin_ajax_action() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'gdpr_optin_action' ); - - if ( ! fs_request_has( 'is_marketing_allowed' ) || ! fs_request_has( 'plugin_ids' ) ) { - self::shoot_ajax_failure(); - } - - $current_wp_user = self::_get_current_wp_user(); - - $plugin_ids = fs_request_get( 'plugin_ids', array() ); - if ( ! is_array( $plugin_ids ) || empty( $plugin_ids ) ) { - self::shoot_ajax_failure(); - } - - $modules = array_merge( - array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), - array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) - ); - - foreach ( $modules as $key => $module ) { - if ( ! in_array( $module->id, $plugin_ids ) ) { - unset( $modules[ $key ] ); - } - } - - if ( empty( $modules ) ) { - self::shoot_ajax_failure(); - } - - $user_api = $this->get_api_user_scope_by_user( Freemius::_get_user_by_email( $current_wp_user->user_email ) ); - - foreach ( $modules as $module ) { - $user_api->call( "?plugin_id={$module->id}", 'put', array( - 'is_marketing_allowed' => ( true == fs_request_get_bool( 'is_marketing_allowed' ) ) - ) ); - } - - FS_GDPR_Manager::instance()->remove_opt_in_notice(); - - require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; - - // 10-year lock. - FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); - - self::shoot_ajax_success(); - } - - /** - * Checks if the GDPR admin notice should be handled. By default, this logic is off, unless the integrator adds the special 'handle_gdpr_admin_notice' filter. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return bool - */ - private function should_handle_gdpr_admin_notice() { - return $this->apply_filters( - 'handle_gdpr_admin_notice', - // Default to false. - false - ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Marketing - #---------------------------------------------------------------------------------- - - /** - * Check if current user purchased any other plugins before. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function has_purchased_before() { - // TODO: Implement has_purchased_before() method. - throw new Exception( 'not implemented' ); - } - - /** - * Check if current user classified as an agency. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_agency() { - // TODO: Implement is_agency() method. - throw new Exception( 'not implemented' ); - } - - /** - * Check if current user classified as a developer. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_developer() { - // TODO: Implement is_developer() method. - throw new Exception( 'not implemented' ); - } - - /** - * Check if current user classified as a business. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_business() { - // TODO: Implement is_business() method. - throw new Exception( 'not implemented' ); - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Helper - #---------------------------------------------------------------------------------- - - /** - * If running with a secret key, assume it's the developer and show pending plans as well. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.2 - * - * @param string $path - * - * @return string - */ - function add_show_pending( $path ) { - if ( ! $this->has_secret_key() ) { - return $path; - } - - return $path . ( false !== strpos( $path, '?' ) ? '&' : '?' ) . 'show_pending=true'; - } - - #endregion - } diff --git a/freemius/includes/class-fs-admin-notices.php b/freemius/includes/class-fs-admin-notices.php index 01b197a..e69de29 100644 --- a/freemius/includes/class-fs-admin-notices.php +++ b/freemius/includes/class-fs-admin-notices.php @@ -1,321 +0,0 @@ -_id = $id; - $this->_title = $title; - $this->_module_unique_affix = $module_unique_affix; - $this->_is_multisite = is_multisite(); - - if ( $this->_is_multisite ) { - $this->_blog_id = get_current_blog_id(); - - $this->_network_notices = FS_Admin_Notice_Manager::instance( - $id, - $title, - $module_unique_affix, - $is_network_and_blog_admins, - true - ); - } - - $this->_notices = FS_Admin_Notice_Manager::instance( - $id, - $title, - $module_unique_affix, - false, - $this->_blog_id - ); - } - - /** - * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $message - * @param string $title - * @param string $type - * @param bool $is_sticky - * @param string $id Message ID - * @param bool $store_if_sticky - * @param int|null $network_level_or_blog_id - * - * @uses add_action() - */ - function add( - $message, - $title = '', - $type = 'success', - $is_sticky = false, - $id = '', - $store_if_sticky = true, - $network_level_or_blog_id = null - ) { - if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices( $network_level_or_blog_id ); - } - - $notices->add( - $message, - $title, - $type, - $is_sticky, - $id, - $store_if_sticky - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string|string[] $ids - * @param int|null $network_level_or_blog_id - */ - function remove_sticky( $ids, $network_level_or_blog_id = null ) { - if ( ! is_array( $ids ) ) { - $ids = array( $ids ); - } - - if ( $this->should_use_network_notices( $ids[0], $network_level_or_blog_id ) ) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices( $network_level_or_blog_id ); - } - - return $notices->remove_sticky( $ids ); - } - - /** - * Check if sticky message exists by id. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $id - * @param int|null $network_level_or_blog_id - * - * @return bool - */ - function has_sticky( $id, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices( $network_level_or_blog_id ); - } - - return $notices->has_sticky( $id ); - } - - /** - * Adds sticky admin notification. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $message - * @param string $id Message ID - * @param string $title - * @param string $type - * @param int|null $network_level_or_blog_id - * @param number|null $wp_user_id - * @param string|null $plugin_title - * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and - * blog admin pages. - */ - function add_sticky( - $message, - $id, - $title = '', - $type = 'success', - $network_level_or_blog_id = null, - $wp_user_id = null, - $plugin_title = null, - $is_network_and_blog_admins = false - ) { - if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices( $network_level_or_blog_id ); - } - - $notices->add_sticky( $message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); - } - - /** - * Clear all sticky messages. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int|null $network_level_or_blog_id - */ - function clear_all_sticky( $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite || - false === $network_level_or_blog_id || - 0 == $network_level_or_blog_id || - is_null( $network_level_or_blog_id ) - ) { - $notices = $this->get_site_notices( $network_level_or_blog_id ); - $notices->clear_all_sticky(); - } - - if ( $this->_is_multisite && - ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) - ) { - $this->_network_notices->clear_all_sticky(); - } - } - - /** - * Add admin message to all admin messages queue, and hook to all_admin_notices if not yet hooked. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $message - * @param string $title - * @param string $type - * @param bool $is_sticky - * @param string $id Message ID - */ - function add_all( $message, $title = '', $type = 'success', $is_sticky = false, $id = '' ) { - $this->add( $message, $title, $type, $is_sticky, true, $id ); - } - - #-------------------------------------------------------------------------------- - #region Helper Methods - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return FS_Admin_Notice_Manager - */ - private function get_site_notices( $blog_id = 0 ) { - if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { - return $this->_notices; - } - - return FS_Admin_Notice_Manager::instance( - $this->_id, - $this->_title, - $this->_module_unique_affix, - false, - $blog_id - ); - } - - /** - * Check if the network notices should be used. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $id - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite notices (if there's a network). When `false`, use the current context blog notices. When `null`, the decision which notices manager to use (MS vs. Current S) will be handled internally and determined based on the $id and the context admin (blog admin vs. network level admin). - * - * @return bool - */ - private function should_use_network_notices( $id = '', $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite ) { - // Not a multisite environment. - return false; - } - - if ( is_numeric( $network_level_or_blog_id ) ) { - // Explicitly asked to use a specified blog storage. - return false; - } - - if ( is_bool( $network_level_or_blog_id ) ) { - // Explicitly specified whether should use the network or blog level storage. - return $network_level_or_blog_id; - } - - return fs_is_network_admin(); - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php index ec24ea2..e69de29 100644 --- a/freemius/includes/class-fs-api.php +++ b/freemius/includes/class-fs-api.php @@ -1,664 +0,0 @@ -get_option( 'api_clock_diff', 0 ); - Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); - - if ( self::$_options->get_option( 'api_force_http', false ) ) { - Freemius_Api_WordPress::SetHttp(); - } - } - - /** - * @param string $slug - * @param string $scope 'app', 'developer', 'user' or 'install'. - * @param number $id Element's id. - * @param string $public_key Public key. - * @param bool|string $secret_key Element's secret key. - * @param bool $is_sandbox - * @param null|string $sdk_version - */ - private function __construct( - $slug, - $scope, - $id, - $public_key, - $secret_key, - $is_sandbox, - $sdk_version - ) { - $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox ); - - $this->_slug = $slug; - $this->_sdk_version = $sdk_version; - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - } - - /** - * Find clock diff between server and API server, and store the diff locally. - * - * @param bool|int $diff - * - * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. - */ - private function _sync_clock_diff( $diff = false ) { - $this->_logger->entrance(); - - // Sync clock and store. - $new_clock_diff = ( false === $diff ) ? - Freemius_Api_WordPress::FindClockDiff() : - $diff; - - if ( $new_clock_diff === self::$_clock_diff ) { - return false; - } - - self::$_clock_diff = $new_clock_diff; - - // Update API clock's diff. - Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); - - // Store new clock diff in storage. - self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); - - return $new_clock_diff; - } - - /** - * Override API call to enable retry with servers' clock auto sync method. - * - * @param string $path - * @param string $method - * @param array $params - * @param bool $retry Is in retry or first call attempt. - * - * @return array|mixed|string|void - */ - private function _call( $path, $method = 'GET', $params = array(), $retry = false ) { - $this->_logger->entrance( $method . ':' . $path ); - - if ( self::is_temporary_down() ) { - $result = $this->get_temporary_unavailable_error(); - } else { - /** - * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet. - */ - if ( ! empty( $this->_sdk_version ) ) { - if ( false === strpos( $path, 'sdk_version=' ) && - ! isset( $params['sdk_version'] ) - ) { - // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY. - $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path ); - } - } - - $result = $this->_api->Api( $path, $method, $params ); - - if ( null !== $result && - isset( $result->error ) && - isset( $result->error->code ) && - 'request_expired' === $result->error->code - ) { - if ( ! $retry ) { - $diff = isset( $result->error->timestamp ) ? - ( time() - strtotime( $result->error->timestamp ) ) : - false; - - // Try to sync clock diff. - if ( false !== $this->_sync_clock_diff( $diff ) ) { - // Retry call with new synced clock. - return $this->_call( $path, $method, $params, true ); - } - } - } - } - - if ( $this->_logger->is_on() && self::is_api_error( $result ) ) { - // Log API errors. - $this->_logger->api_error( $result ); - } - - return $result; - } - - /** - * Override API call to wrap it in servers' clock sync method. - * - * @param string $path - * @param string $method - * @param array $params - * - * @return array|mixed|string|void - * @throws Freemius_Exception - */ - function call( $path, $method = 'GET', $params = array() ) { - return $this->_call( $path, $method, $params ); - } - - /** - * Get API request URL signed via query string. - * - * @param string $path - * - * @return string - */ - function get_signed_url( $path ) { - return $this->_api->GetSignedUrl( $path ); - } - - /** - * @param string $path - * @param bool $flush - * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours - * - * @return stdClass|mixed - */ - function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { - $this->_logger->entrance( $path ); - - $cache_key = $this->get_cache_key( $path ); - - // Always flush during development. - if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { - $flush = true; - } - - $cached_result = self::$_cache->get( $cache_key ); - - if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) { - $result = $this->call( $path ); - - if ( ! is_object( $result ) || isset( $result->error ) ) { - // Api returned an error. - if ( is_object( $cached_result ) && - ! isset( $cached_result->error ) - ) { - // If there was an error during a newer data fetch, - // fallback to older data version. - $result = $cached_result; - - if ( $this->_logger->is_on() ) { - $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) ); - } - } else { - if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) { - /** - * If the response code is 404, cache the result for half of the `$expiration`. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - $expiration /= 2; - } else { - // If no older data version and the response code is not 404, return result without - // caching the error. - return $result; - } - } - } - - self::$_cache->set( $cache_key, $result, $expiration ); - - $cached_result = $result; - } else { - $this->_logger->log( 'Using cached API result.' ); - } - - return $cached_result; - } - - /** - * Check if there's a cached version of the API request. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @param string $path - * @param string $method - * @param array $params - * - * @return bool - */ - function is_cached( $path, $method = 'GET', $params = array() ) { - $cache_key = $this->get_cache_key( $path, $method, $params ); - - return self::$_cache->has_valid( $cache_key ); - } - - /** - * Invalidate a cached version of the API request. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param string $path - * @param string $method - * @param array $params - */ - function purge_cache( $path, $method = 'GET', $params = array() ) { - $this->_logger->entrance( "{$method}:{$path}" ); - - $cache_key = $this->get_cache_key( $path, $method, $params ); - - self::$_cache->purge( $cache_key ); - } - - /** - * Invalidate a cached version of the API request. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $path - * @param int $expiration - * @param string $method - * @param array $params - */ - function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) { - $this->_logger->entrance( "{$method}:{$path}:{$expiration}" ); - - $cache_key = $this->get_cache_key( $path, $method, $params ); - - self::$_cache->update_expiration( $cache_key, $expiration ); - } - - /** - * @param string $path - * @param string $method - * @param array $params - * - * @return string - * @throws \Freemius_Exception - */ - private function get_cache_key( $path, $method = 'GET', $params = array() ) { - $canonized = $this->_api->CanonizePath( $path ); -// $exploded = explode('/', $canonized); -// return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); - return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); - } - - /** - * Test API connectivity. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 If fails, try to fallback to HTTP. - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if - * temporary down. - * - * @return bool True if successful connectivity to the API. - */ - static function test() { - self::_init(); - - $cache_key = 'ping_test'; - - $test = self::$_cache->get_valid( $cache_key, null ); - - if ( is_null( $test ) ) { - $test = Freemius_Api_WordPress::Test(); - - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option( 'api_force_http', true, true ); - - $test = Freemius_Api_WordPress::Test(); - - if ( false === $test ) { - /** - * API connectivity test fail also in HTTP request, therefore, - * fallback to HTTPS to keep connection secure. - * - * @since 1.1.6 - */ - self::$_options->set_option( 'api_force_http', false, true ); - } - } - - self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); - } - - return $test; - } - - /** - * Check if API is temporary down. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @return bool - */ - static function is_temporary_down() { - self::_init(); - - $test = self::$_cache->get_valid( 'ping_test', null ); - - return ( false === $test ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @return object - */ - private function get_temporary_unavailable_error() { - return (object) array( - 'error' => (object) array( - 'type' => 'TemporaryUnavailable', - 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.', - 'code' => 'temporary_unavailable', - 'http' => 503 - ) - ); - } - - /** - * Ping API for connectivity test, and return result object. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param null|string $unique_anonymous_id - * @param array $params - * - * @return object - */ - function ping( $unique_anonymous_id = null, $params = array() ) { - $this->_logger->entrance(); - - if ( self::is_temporary_down() ) { - return $this->get_temporary_unavailable_error(); - } - - $pong = is_null( $unique_anonymous_id ) ? - Freemius_Api_WordPress::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ) ) ); - - if ( $this->is_valid_ping( $pong ) ) { - return $pong; - } - - if ( self::should_try_with_http( $pong ) ) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option( 'api_force_http', true, true ); - - $pong = is_null( $unique_anonymous_id ) ? - Freemius_Api_WordPress::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ) ) ); - - if ( ! $this->is_valid_ping( $pong ) ) { - self::$_options->set_option( 'api_force_http', false, true ); - } - } - - return $pong; - } - - /** - * Check if based on the API result we should try - * to re-run the same request with HTTP instead of HTTPS. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param $result - * - * @return bool - */ - private static function should_try_with_http( $result ) { - if ( ! Freemius_Api_WordPress::IsHttps() ) { - return false; - } - - return ( ! is_object( $result ) || - ! isset( $result->error ) || - ! isset( $result->error->code ) || - ! in_array( $result->error->code, array( - 'curl_missing', - 'cloudflare_ddos_protection', - 'maintenance_mode', - 'squid_cache_block', - 'too_many_requests', - ) ) ); - - } - - /** - * Check if valid ping request result. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @param mixed $pong - * - * @return bool - */ - function is_valid_ping( $pong ) { - return Freemius_Api_WordPress::Test( $pong ); - } - - function get_url( $path = '' ) { - return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); - } - - /** - * Clear API cache. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - static function clear_cache() { - self::_init(); - - self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); - self::$_cache->clear(); - } - - #---------------------------------------------------------------------------------- - #region Error Handling - #---------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $result - * - * @return bool Is API result contains an error. - */ - static function is_api_error( $result ) { - return ( is_object( $result ) && isset( $result->error ) ) || - is_string( $result ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param mixed $result - * - * @return bool Is API result contains an error. - */ - static function is_api_error_object( $result ) { - return ( - is_object( $result ) && - isset( $result->error ) && - isset( $result->error->message ) - ); - } - - /** - * Checks if given API result is a non-empty and not an error object. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $result - * @param string|null $required_property Optional property we want to verify that is set. - * - * @return bool - */ - static function is_api_result_object( $result, $required_property = null ) { - return ( - is_object( $result ) && - ! isset( $result->error ) && - ( empty( $required_property ) || isset( $result->{$required_property} ) ) - ); - } - - /** - * Checks if given API result is a non-empty entity object with non-empty ID. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $result - * - * @return bool - */ - static function is_api_result_entity( $result ) { - return self::is_api_result_object( $result, 'id' ) && - FS_Entity::is_valid_id( $result->id ); - } - - /** - * Get API result error code. If failed to get code, returns an empty string. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param mixed $result - * - * @return string - */ - static function get_error_code( $result ) { - if ( is_object( $result ) && - isset( $result->error ) && - is_object( $result->error ) && - ! empty( $result->error->code ) - ) { - return $result->error->code; - } - - return ''; - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/class-fs-logger.php b/freemius/includes/class-fs-logger.php index 624c683..e69de29 100644 --- a/freemius/includes/class-fs-logger.php +++ b/freemius/includes/class-fs-logger.php @@ -1,691 +0,0 @@ -_id = $id; - - $bt = debug_backtrace(); - $caller = $bt[2]; - - if ( false !== strpos( $caller['file'], 'plugins' ) ) { - $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' ); - } else { - $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' ); - } - - if ( $on ) { - $this->on(); - } - if ( $echo ) { - $this->echo_on(); - } - } - - /** - * @param string $id - * @param bool $on - * @param bool $echo - * - * @return FS_Logger - */ - public static function get_logger( $id, $on = false, $echo = false ) { - $id = strtolower( $id ); - - if ( ! isset( self::$_processID ) ) { - self::init(); - } - - if ( ! isset( self::$LOGGERS[ $id ] ) ) { - self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo ); - } - - return self::$LOGGERS[ $id ]; - } - - /** - * Initialize logging global info. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - private static function init() { - self::$_ownerName = function_exists( 'get_current_user' ) ? - get_current_user() : - 'unknown'; - self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); - self::$_abspathLength = strlen( ABSPATH ); - self::$_processID = mt_rand( 0, 32000 ); - - // Process ID may be `false` on errors. - if ( ! is_numeric( self::$_processID ) ) { - self::$_processID = 0; - } - } - - private static function hook_footer() { - if ( self::$_HOOKED_FOOTER ) { - return; - } - - if ( is_admin() ) { - add_action( 'admin_footer', 'FS_Logger::dump', 100 ); - } else { - add_action( 'wp_footer', 'FS_Logger::dump', 100 ); - } - } - - function is_on() { - return $this->_on; - } - - function on() { - $this->_on = true; - - if ( ! function_exists( 'dbDelta' ) ) { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - } - - self::hook_footer(); - } - - function echo_on() { - $this->on(); - - $this->_echo = true; - } - - function is_echo_on() { - return $this->_echo; - } - - function get_id() { - return $this->_id; - } - - function get_file() { - return $this->_file_start; - } - - private function _log( &$message, $type, $wrapper = false ) { - if ( ! $this->is_on() ) { - return; - } - - $bt = debug_backtrace(); - $depth = $wrapper ? 3 : 2; - while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) { - $depth ++; - } - - $caller = $bt[ $depth ]; - - /** - * Retrieve the correct call file & line number from backtrace - * when logging from a wrapper method. - * - * @author Vova Feldman - * @since 1.2.1.6 - */ - if ( empty( $caller['line'] ) ) { - $depth --; - - while ( $depth >= 0 ) { - if ( ! empty( $bt[ $depth ]['line'] ) ) { - $caller['line'] = $bt[ $depth ]['line']; - $caller['file'] = $bt[ $depth ]['file']; - break; - } - } - } - - $log = array_merge( $caller, array( - 'cnt' => self::$CNT ++, - 'logger' => $this, - 'timestamp' => microtime( true ), - 'log_type' => $type, - 'msg' => $message, - ) ); - - if ( self::$_isStorageLoggingOn ) { - $this->db_log( $type, $message, self::$CNT, $caller ); - } - - self::$LOG[] = $log; - - if ( $this->is_echo_on() && ! Freemius::is_ajax() ) { - echo self::format_html( $log ) . "\n"; - } - } - - function log( $message, $wrapper = false ) { - $this->_log( $message, 'log', $wrapper ); - } - - function info( $message, $wrapper = false ) { - $this->_log( $message, 'info', $wrapper ); - } - - function warn( $message, $wrapper = false ) { - $this->_log( $message, 'warn', $wrapper ); - } - - function error( $message, $wrapper = false ) { - $this->_log( $message, 'error', $wrapper ); - } - - /** - * Log API error. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $api_result - * @param bool $wrapper - */ - function api_error( $api_result, $wrapper = false ) { - $message = ''; - if ( is_object( $api_result ) && - ! empty( $api_result->error ) && - ! empty( $api_result->error->message ) - ) { - $message = $api_result->error->message; - } else if ( is_object( $api_result ) ) { - $message = var_export( $api_result, true ); - } else if ( is_string( $api_result ) ) { - $message = $api_result; - } else if ( empty( $api_result ) ) { - $message = 'Empty API result.'; - } - - $message = 'API Error: ' . $message; - - $this->_log( $message, 'error', $wrapper ); - } - - function entrance( $message = '', $wrapper = false ) { - $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message; - - $this->_log( $msg, 'log', $wrapper ); - } - - function departure( $message = '', $wrapper = false ) { - $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message; - - $this->_log( $msg, 'log', $wrapper ); - } - - #-------------------------------------------------------------------------------- - #region Log Formatting - #-------------------------------------------------------------------------------- - - private static function format( $log, $show_type = true ) { - return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']'; - } - - private static function format_html( $log ) { - return '
    [' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . esc_html( $log['msg'] ) . '' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']
    '; - } - - #endregion - - static function dump() { - ?> - - - - prefix}fs_logger"; - - if ( $is_on ) { - /** - * Create logging table. - * - * NOTE: - * dbDelta must use KEY and not INDEX for indexes. - * - * @link https://core.trac.wordpress.org/ticket/2695 - */ - $result = $wpdb->query( "CREATE TABLE {$table} ( -`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, -`process_id` INT UNSIGNED NOT NULL, -`user_name` VARCHAR(64) NOT NULL, -`logger` VARCHAR(128) NOT NULL, -`log_order` INT UNSIGNED NOT NULL, -`type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log', -`message` TEXT NOT NULL, -`file` VARCHAR(256) NOT NULL, -`line` INT UNSIGNED NOT NULL, -`function` VARCHAR(256) NOT NULL, -`request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call', -`request_url` VARCHAR(1024) NOT NULL, -`created` DECIMAL(16, 6) NOT NULL, -PRIMARY KEY (`id`), -KEY `process_id` (`process_id` ASC), -KEY `process_logger` (`process_id` ASC, `logger` ASC), -KEY `function` (`function` ASC), -KEY `type` (`type` ASC))" ); - } else { - /** - * Drop logging table. - */ - $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" ); - } - - if ( false !== $result ) { - update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) ); - } - - return ( false !== $result ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $type - * @param string $message - * @param int $log_order - * @param array $caller - * - * @return false|int - */ - private function db_log( - &$type, - &$message, - &$log_order, - &$caller - ) { - global $wpdb; - - $request_type = 'call'; - if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - $request_type = 'cron'; - } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - $request_type = 'ajax'; - } - - $request_url = WP_FS__IS_HTTP_REQUEST ? - $_SERVER['REQUEST_URI'] : - ''; - - return $wpdb->insert( - "{$wpdb->prefix}fs_logger", - array( - 'process_id' => self::$_processID, - 'user_name' => self::$_ownerName, - 'logger' => $this->_id, - 'log_order' => $log_order, - 'type' => $type, - 'request_type' => $request_type, - 'request_url' => $request_url, - 'message' => $message, - 'file' => isset( $caller['file'] ) ? - substr( $caller['file'], self::$_abspathLength ) : - '', - 'line' => $caller['line'], - 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'], - 'created' => microtime( true ), - ) - ); - } - - /** - * Persistent DB logger columns. - * - * @var array - */ - private static $_log_columns = array( - 'id', - 'process_id', - 'user_name', - 'logger', - 'log_order', - 'type', - 'message', - 'file', - 'line', - 'function', - 'request_type', - 'request_url', - 'created', - ); - - /** - * Create DB logs query. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param int $limit - * @param int $offset - * @param bool $order - * @param bool $escape_eol - * - * @return string - */ - private static function build_db_logs_query( - $filters = false, - $limit = 200, - $offset = 0, - $order = false, - $escape_eol = false - ) { - global $wpdb; - - $select = '*'; - - if ( $escape_eol ) { - $select = ''; - for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { - if ( $i > 0 ) { - $select .= ', '; - } - - if ( 'message' !== self::$_log_columns[ $i ] ) { - $select .= self::$_log_columns[ $i ]; - } else { - $select .= 'REPLACE(message , \'\n\', \' \') AS message'; - } - } - } - - $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; - if ( is_array( $filters ) ) { - $criteria = array(); - - if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) { - $filters['type'] = strtolower( $filters['type'] ); - - switch ( $filters['type'] ) { - case 'warn_error': - $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); - break; - case 'error': - case 'warn': - $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); - break; - case 'info': - default: - $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); - break; - } - } - - if ( ! empty( $filters['request_type'] ) ) { - $filters['request_type'] = strtolower( $filters['request_type'] ); - - if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) { - $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); - } - } - - if ( ! empty( $filters['file'] ) ) { - $criteria[] = array( - 'col' => 'file', - 'op' => 'LIKE', - 'val' => '%' . esc_sql( $filters['file'] ), - ); - } - - if ( ! empty( $filters['function'] ) ) { - $criteria[] = array( - 'col' => 'function', - 'op' => 'LIKE', - 'val' => '%' . esc_sql( $filters['function'] ), - ); - } - - if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) { - $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); - } - - if ( ! empty( $filters['logger'] ) ) { - $criteria[] = array( - 'col' => 'logger', - 'op' => 'LIKE', - 'val' => '%' . esc_sql( $filters['logger'] ) . '%', - ); - } - - if ( ! empty( $filters['message'] ) ) { - $criteria[] = array( - 'col' => 'message', - 'op' => 'LIKE', - 'val' => '%' . esc_sql( $filters['message'] ) . '%', - ); - } - - if ( 0 < count( $criteria ) ) { - $query .= "\nWHERE\n"; - - $first = true; - foreach ( $criteria as $c ) { - if ( ! $first ) { - $query .= "AND\n"; - } - - if ( is_array( $c['val'] ) ) { - $operator = 'IN'; - - for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) { - $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'"; - } - - $val = '(' . implode( ',', $c['val'] ) . ')'; - } else { - $operator = ! empty( $c['op'] ) ? $c['op'] : '='; - $val = "'" . esc_sql( $c['val'] ) . "'"; - } - - $query .= "`{$c['col']}` {$operator} {$val}\n"; - - $first = false; - } - } - } - - if ( ! is_array( $order ) ) { - $order = array( - 'col' => 'id', - 'order' => 'desc' - ); - } - - $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; - - return $query; - } - - /** - * Load logs from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param int $limit - * @param int $offset - * @param bool $order - * - * @return object[]|null - */ - public static function load_db_logs( - $filters = false, - $limit = 200, - $offset = 0, - $order = false - ) { - global $wpdb; - - $query = self::build_db_logs_query( - $filters, - $limit, - $offset, - $order - ); - - return $wpdb->get_results( $query ); - } - - /** - * Load logs from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param string $filename - * @param int $limit - * @param int $offset - * @param bool $order - * - * @return false|string File download URL or false on failure. - */ - public static function download_db_logs( - $filters = false, - $filename = '', - $limit = 10000, - $offset = 0, - $order = false - ) { - global $wpdb; - - $query = self::build_db_logs_query( - $filters, - $limit, - $offset, - $order, - true - ); - - $upload_dir = wp_upload_dir(); - if ( empty( $filename ) ) { - $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; - } - $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}"; - - $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; - - $columns = ''; - for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { - if ( $i > 0 ) { - $columns .= ', '; - } - - $columns .= "'" . self::$_log_columns[ $i ] . "'"; - } - - $query = "SELECT {$columns} UNION ALL " . $query; - - $result = $wpdb->query( $query ); - - if ( false === $result ) { - return false; - } - - return rtrim( $upload_dir['url'], '/' ) . '/' . $filename; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $filename - * - * @return string - */ - public static function get_logs_download_url( $filename = '' ) { - $upload_dir = wp_upload_dir(); - if ( empty( $filename ) ) { - $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; - } - - return rtrim( $upload_dir['url'], '/' ) . $filename; - } - - #endregion - } diff --git a/freemius/includes/class-fs-options.php b/freemius/includes/class-fs-options.php index dcb9409..e69de29 100644 --- a/freemius/includes/class-fs-options.php +++ b/freemius/includes/class-fs-options.php @@ -1,431 +0,0 @@ -_id = $id; - $this->_is_multisite = is_multisite(); - - if ( $this->_is_multisite ) { - $this->_blog_id = get_current_blog_id(); - $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); - } - - $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); - } - - /** - * Switch the context of the site level options manager. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param $blog_id - */ - function set_site_blog_context( $blog_id ) { - $this->_blog_id = $blog_id; - - $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $option - * @param mixed $default - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). - * - * @return mixed - */ - function get_option( $option, $default = null, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { - return $this->_network_options->get_option( $option, $default ); - } - - $site_options = $this->get_site_options( $network_level_or_blog_id ); - - return $site_options->get_option( $option, $default ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param string $option - * @param mixed $value - * @param bool $flush - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). - */ - function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { - $this->_network_options->set_option( $option, $value, $flush ); - } else { - $site_options = $this->get_site_options( $network_level_or_blog_id ); - $site_options->set_option( $option, $value, $flush ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $option - * @param bool $flush - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). - */ - function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { - $this->_network_options->unset_option( $option, $flush ); - } else { - $site_options = $this->get_site_options( $network_level_or_blog_id ); - $site_options->unset_option( $option, $flush ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param bool $flush - * @param bool $network_level - */ - function load( $flush = false, $network_level = true ) { - if ( $this->_is_multisite && $network_level ) { - $this->_network_options->load( $flush ); - } else { - $this->_options->load( $flush ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - * - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage. - */ - function store( $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite || - false === $network_level_or_blog_id || - 0 == $network_level_or_blog_id || - is_null( $network_level_or_blog_id ) - ) { - $site_options = $this->get_site_options( $network_level_or_blog_id ); - $site_options->store(); - } - - if ( $this->_is_multisite && - ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) - ) { - $this->_network_options->store(); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int|null|bool $network_level_or_blog_id - * @param bool $flush - */ - function clear( $network_level_or_blog_id = null, $flush = false ) { - if ( ! $this->_is_multisite || - false === $network_level_or_blog_id || - is_null( $network_level_or_blog_id ) || - is_numeric( $network_level_or_blog_id ) - ) { - $site_options = $this->get_site_options( $network_level_or_blog_id ); - $site_options->clear( $flush ); - } - - if ( $this->_is_multisite && - ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) - ) { - $this->_network_options->clear( $flush ); - } - } - - /** - * Migration script to the new storage data structure that is network compatible. - * - * IMPORTANT: - * This method should be executed only after it is determined if this is a network - * level compatible product activation. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - */ - function migrate_to_network( $blog_id = 0 ) { - if ( ! $this->_is_multisite ) { - return; - } - - $updated = false; - - $site_options = $this->get_site_options( $blog_id ); - - $keys = $site_options->get_options_keys(); - - foreach ( $keys as $option ) { - if ( $this->is_site_option( $option ) || - // Don't move admin notices to the network storage. - in_array($option, array( - // Don't move admin notices to the network storage. - 'admin_notices', - // Don't migrate the module specific data, it will be migrated by the FS_Storage. - 'plugin_data', - 'theme_data', - )) - ) { - continue; - } - - $option_updated = false; - - // Migrate option to the network storage. - $site_option = $site_options->get_option( $option ); - - if ( ! $this->_network_options->has_option( $option ) ) { - // Option not set on the network level, so just set it. - $this->_network_options->set_option( $option, $site_option, false ); - - $option_updated = true; - } else { - // Option already set on the network level, so we need to merge it inelegantly. - $network_option = $this->_network_options->get_option( $option ); - - if ( is_array( $network_option ) && is_array( $site_option ) ) { - // Option is an array. - foreach ( $site_option as $key => $value ) { - if ( ! isset( $network_option[ $key ] ) ) { - $network_option[ $key ] = $value; - - $option_updated = true; - } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { - if ( empty( $network_option[ $key ] ) ) { - $network_option[ $key ] = $value; - - $option_updated = true; - } else if ( empty( $value ) ) { - // Do nothing. - } else { - reset($value); - $first_key = key($value); - if ( $value[$first_key] instanceof FS_Entity ) { - // Merge entities by IDs. - $network_entities_ids = array(); - foreach ( $network_option[ $key ] as $entity ) { - $network_entities_ids[ $entity->id ] = true; - } - - foreach ( $value as $entity ) { - if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { - $network_option[ $key ][] = $entity; - - $option_updated = true; - } - } - } - } - } - } - } - - if ( $option_updated ) { - $this->_network_options->set_option( $option, $network_option, false ); - } - } - - /** - * Remove the option from site level storage. - * - * IMPORTANT: - * The line below is intentionally commented since we want to preserve the option - * on the site storage level for "downgrade compatibility". Basically, if the user - * will downgrade to an older version of the plugin with the prev storage structure, - * it will continue working. - * - * @todo After a few releases we can remove this. - */ -// $site_options->unset_option($option, false); - - if ( $option_updated ) { - $updated = true; - } - } - - if ( ! $updated ) { - return; - } - - // Update network level storage. - $this->_network_options->store(); -// $site_options->store(); - } - - - #-------------------------------------------------------------------------------- - #region Helper Methods - #-------------------------------------------------------------------------------- - - /** - * We don't want to load the map right away since it's not even needed in a non-MS environment. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private static function load_site_options_map() { - self::$_SITE_OPTIONS_MAP = array( - 'sites' => true, - 'theme_sites' => true, - 'unique_id' => true, - 'active_plugins' => true, - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $option - * - * @return bool - */ - private function is_site_option( $option ) { - if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { - return false; - } - - if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { - self::load_site_options_map(); - } - - return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return FS_Option_Manager - */ - private function get_site_options( $blog_id = 0 ) { - if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { - return $this->_options; - } - - return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); - } - - /** - * Check if an option should be stored on the MS network storage. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $option - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). - * - * @return bool - */ - private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite ) { - // Not a multisite environment. - return false; - } - - if ( is_numeric( $network_level_or_blog_id ) ) { - // Explicitly asked to use a specified blog storage. - return false; - } - - if ( is_bool( $network_level_or_blog_id ) ) { - // Explicitly specified whether should use the network or blog level storage. - return $network_level_or_blog_id; - } - - // Determine which storage to use based on the option. - return ! $this->is_site_option( $option ); - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php index 8f6b996..e69de29 100644 --- a/freemius/includes/class-fs-plugin-updater.php +++ b/freemius/includes/class-fs-plugin-updater.php @@ -1,1561 +0,0 @@ -get_id(); - - if ( ! isset( self::$_INSTANCES[ $key ] ) ) { - self::$_INSTANCES[ $key ] = new self( $freemius ); - } - - return self::$_INSTANCES[ $key ]; - } - - #endregion - - private function __construct( Freemius $freemius ) { - $this->_fs = $freemius; - - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->filters(); - } - - /** - * Initiate required filters. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - */ - private function filters() { - // Override request for plugin information - add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 ); - - $this->add_transient_filters(); - - /** - * If user has the premium plugin's code but do NOT have an active license, - * encourage him to upgrade by showing that there's a new release, but instead - * of showing an update link, show upgrade link to the pricing page. - * - * @since 1.1.6 - * - */ - // WP 2.9+ - add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( - &$this, - 'catch_plugin_update_row' - ), 9 ); - add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( - &$this, - 'edit_and_echo_plugin_update_row' - ), 11, 2 ); - - if ( ! $this->_fs->has_any_active_valid_license() ) { - add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) ); - } - - if ( ! WP_FS__IS_PRODUCTION_MODE ) { - add_filter( 'http_request_host_is_external', array( - $this, - 'http_request_host_is_external_filter' - ), 10, 3 ); - } - - if ( $this->_fs->is_premium() ) { - if ( ! $this->is_correct_folder_name() ) { - add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 ); - } - - add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 ); - add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); - - if ( ! $this->_fs->has_any_active_valid_license() ) { - add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 ); - } - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.4 - */ - function catch_plugin_information_dialog_contents() { - if ( - 'plugin-information' !== fs_request_get( 'tab', false ) || - $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) - ) { - return; - } - - add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 ); - - ob_start(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.4 - * - * @param string $hook_suffix - */ - function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { - if ( - 'plugin-information' !== fs_request_get( 'tab', false ) || - $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) - ) { - return; - } - - $license = $this->_fs->_get_license(); - - $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ? - $this->_fs->_get_subscription( $license->id ) : - null; - - $contents = ob_get_clean(); - - $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); - - if ( false !== $update_button_id_attribute_pos ) { - $update_button_start_pos = strrpos( - substr( $contents, 0, $update_button_id_attribute_pos ), - '', $update_button_id_attribute_pos ) + strlen( '' ) ); - - /** - * The part of the contents without the update button. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.5 - */ - $modified_contents = substr( $contents, 0, $update_button_start_pos ); - - $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) ); - - /** - * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, - * the text will be "Renew license" and will link to the checkout page with the license's billing cycle - * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. - */ - $update_button = preg_replace( - '/(\)(.+)(\<\/a>)/is', - is_object( $license ) ? - sprintf( - '$1$3%s$5%s$7', - $this->_fs->checkout_url( - is_object( $subscription ) ? - ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : - WP_FS__PERIOD_LIFETIME, - false, - array( 'licenses' => $license->quota ) - ), - fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) - ) : - sprintf( - '$1$3%s$5%s$7', - $this->_fs->pricing_url(), - fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) - ), - $update_button - ); - - /** - * Append the modified button. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.5 - */ - $modified_contents .= $update_button; - - /** - * Append the remaining part of the contents after the update button. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.5 - */ - $modified_contents .= substr( $contents, $update_button_end_pos ); - - $contents = $modified_contents; - } - - echo $contents; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private function add_transient_filters() { - if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) { - $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' ); - - return; - } - - add_filter( 'pre_set_site_transient_update_plugins', array( - &$this, - 'pre_set_site_transient_update_plugins_filter' - ) ); - - add_filter( 'pre_set_site_transient_update_themes', array( - &$this, - 'pre_set_site_transient_update_plugins_filter' - ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private function remove_transient_filters() { - remove_filter( 'pre_set_site_transient_update_plugins', array( - &$this, - 'pre_set_site_transient_update_plugins_filter' - ) ); - - remove_filter( 'pre_set_site_transient_update_themes', array( - &$this, - 'pre_set_site_transient_update_plugins_filter' - ) ); - } - - /** - * Capture plugin update row by turning output buffering. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - */ - function catch_plugin_update_row() { - ob_start(); - } - - /** - * Overrides default update message format with "renew your license" message. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $file - * @param array $plugin_data - */ - function edit_and_echo_plugin_update_row( $file, $plugin_data ) { - $plugin_update_row = ob_get_clean(); - - $current = get_site_transient( 'update_plugins' ); - if ( ! isset( $current->response[ $file ] ) ) { - echo $plugin_update_row; - - return; - } - - $r = $current->response[ $file ]; - - $has_beta_update = $this->_fs->has_beta_update(); - - if ( $this->_fs->has_any_active_valid_license() ) { - if ( $has_beta_update ) { - /** - * Turn the "new version" text into "new Beta version". - * - * Sample input: - * There is a new version of Awesome Plugin available. update now. - * Output: - * There is a new Beta version of Awesome Plugin available. update now. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $plugin_update_row = preg_replace( - '/(\)(.+)(\.+\)/is', - ( - '$1' . - sprintf( - fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), - $has_beta_update ? - fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : - fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ), - $this->_fs->get_plugin_title() - ) . - ' ' . - '$3' . - '$6' - ), - $plugin_update_row - ); - } - } else { - /** - * Turn the "new version" text into a link that opens the plugin information dialog when clicked and - * make the "View version x details" text link to the checkout page instead of opening the plugin - * information dialog when clicked. - * - * Sample input: - * There is a new version of Awesome Plugin available. update now. - * Output: - * There is a Buy a license now to access version x.y.z security & feature updates, and support. - * OR - * There is a Buy a license now to access version x.y.z security & feature updates, and support. - * - * @author Leo Fajardo (@leorw) - */ - $plugin_update_row = preg_replace( - '/(\)(.+)(\.+\)/is', - ( - '$1' . - sprintf( - fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), - sprintf( - '%s', - '$5', - $has_beta_update ? - fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : - fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ) - ), - $this->_fs->get_plugin_title() - ) . - ' ' . - $this->_fs->version_upgrade_checkout_link( $r->new_version ) . - '$6' - ), - $plugin_update_row - ); - } - - if ( - $this->_fs->is_plugin() && - isset( $r->upgrade_notice ) && - strlen( trim( $r->upgrade_notice ) ) > 0 - ) { - $slug = $this->_fs->get_slug(); - - $upgrade_notice_html = sprintf( - '

    %3$s %4$s

    ', - $slug, - $this->_fs->get_module_type(), - fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ), - esc_html( $r->upgrade_notice ) - ); - - $plugin_update_row = str_replace( '
    ', '
    ' . $upgrade_notice_html, $plugin_update_row ); - } - - echo $plugin_update_row; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - * - * @param array $prepared_themes - * - * @return array - */ - function change_theme_update_info_html( $prepared_themes ) { - $theme_basename = $this->_fs->get_plugin_basename(); - - if ( ! isset( $prepared_themes[ $theme_basename ] ) ) { - return $prepared_themes; - } - - $themes_update = get_site_transient( 'update_themes' ); - if ( ! isset( $themes_update->response[ $theme_basename ] ) || - empty( $themes_update->response[ $theme_basename ]['package'] ) - ) { - return $prepared_themes; - } - - $prepared_themes[ $theme_basename ]['update'] = preg_replace( - '/(\)(.+)(\)/is', - '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) . - '$4', - $prepared_themes[ $theme_basename ]['update'] - ); - - // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page. - $prepared_themes[ $theme_basename ]['hasPackage'] = false; - - return $prepared_themes; - } - - /** - * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip. - * During development mode we want to be able updating plugin versions via our localhost repository. This - * filter white-list all domains including "api.freemius". - * - * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/ - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param bool $allow - * @param string $host - * @param string $url - * - * @return bool - */ - function http_request_host_is_external_filter( $allow, $host, $url ) { - return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow; - } - - /** - * Check for Updates at the defined API endpoint and modify the update array. - * - * This function dives into the update api just when WordPress creates its update array, - * then adds a custom API call and injects the custom plugin data retrieved from the API. - * It is reassembled from parts of the native WordPress plugin update code. - * See wp-includes/update.php line 121 for the original wp_update_plugins() function. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @uses FS_Api - * - * @param object $transient_data Update array build by WordPress. - * - * @return object Modified update array with custom plugin data. - */ - function pre_set_site_transient_update_plugins_filter( $transient_data ) { - $this->_logger->entrance(); - - /** - * "plugins" or "themes". - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - $module_type = $this->_fs->get_module_type() . 's'; - - /** - * Ensure that we don't mix plugins update info with themes update info. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) { - return $transient_data; - } - - if ( empty( $transient_data ) || - defined( 'WP_FS__UNINSTALL_MODE' ) - ) { - return $transient_data; - } - - global $wp_current_filter; - - $current_plugin_version = $this->_fs->get_plugin_version(); - - if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) { - if ( - is_null( $this->_update_details ) || - ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version ) - ) { - /** - * After an update, clear the stored update details and reparse the plugin's main file in order to get - * the updated version's information and prevent the previous update information from showing up on the - * updates page. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - $this->_update_details = null; - $current_plugin_version = $this->_fs->get_plugin_version( true ); - } - } - - if ( ! isset( $this->_update_details ) ) { - // Get plugin's newest update. - $new_version = $this->_fs->get_update( - false, - fs_request_get_bool( 'force-check' ), - WP_FS__TIME_24_HOURS_IN_SEC / 24, - $current_plugin_version - ); - - $this->_update_details = false; - - if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) { - $this->_logger->log( 'Found newer plugin version ' . $new_version->version ); - - /** - * Cache plugin details locally since set_site_transient( 'update_plugins' ) - * called multiple times and the non wp.org plugins are filtered after the - * call to .org. - * - * @since 1.1.8.1 - */ - $this->_update_details = $this->get_update_details( $new_version ); - } - } - - // Alias. - $basename = $this->_fs->premium_plugin_basename(); - - if ( is_object( $this->_update_details ) ) { - if ( isset( $transient_data->no_update ) ) { - unset( $transient_data->no_update[ $basename ] ); - } - - if ( ! isset( $transient_data->response ) ) { - $transient_data->response = array(); - } - - // Add plugin to transient data. - $transient_data->response[ $basename ] = $this->_fs->is_plugin() ? - $this->_update_details : - (array) $this->_update_details; - } else { - if ( isset( $transient_data->response ) ) { - /** - * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - unset( $transient_data->response[ $basename ] ); - } - - if ( ! isset( $transient_data->no_update ) ) { - $transient_data->no_update = array(); - } - - /** - * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI. - * - * @since 2.4.1 - * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/ - */ - $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ? - (object) array( - 'id' => $basename, - 'slug' => $this->_fs->get_slug(), - 'plugin' => $basename, - 'new_version' => $this->_fs->get_plugin_version(), - 'url' => '', - 'package' => '', - 'icons' => array(), - 'banners' => array(), - 'banners_rtl' => array(), - 'tested' => '', - 'requires_php' => '', - 'compatibility' => new stdClass(), - ) : - array( - 'theme' => $basename, - 'new_version' => $this->_fs->get_plugin_version(), - 'url' => '', - 'package' => '', - 'requires' => '', - 'requires_php' => '', - ); - } - - $slug = $this->_fs->get_slug(); - - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) { - if ( ! isset( $this->_translation_updates ) ) { - $this->_translation_updates = array(); - - if ( current_user_can( 'update_languages' ) ) { - $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug ); - if ( ! empty( $translation_updates ) ) { - $this->_translation_updates = $translation_updates; - } - } - } - - if ( ! empty( $this->_translation_updates ) ) { - $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ? - $transient_data->translations : - array(); - - $current_plugin_translation_updates_map = array(); - foreach ( $all_translation_updates as $key => $translation_update ) { - if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) { - $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update; - unset( $all_translation_updates[ $key ] ); - } - } - - foreach ( $this->_translation_updates as $translation_update ) { - $lang = $translation_update['language']; - if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) || - version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' ) - ) { - $current_plugin_translation_updates_map[ $lang ] = $translation_update; - } - } - - $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) ); - } - } - - return $transient_data; - } - - /** - * Get module's required data for the updates mechanism. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_Plugin_Tag $new_version - * - * @return object - */ - function get_update_details( FS_Plugin_Tag $new_version ) { - $update = new stdClass(); - $update->slug = $this->_fs->get_slug(); - $update->new_version = $new_version->version; - $update->url = WP_FS__ADDRESS; - $update->package = $new_version->url; - $update->tested = $new_version->tested_up_to_version; - $update->requires = $new_version->requires_platform_version; - - $icon = $this->_fs->get_local_icon_url(); - - if ( ! empty( $icon ) ) { - $update->icons = array( -// '1x' => $icon, -// '2x' => $icon, - 'default' => $icon, - ); - } - - if ( $this->_fs->is_premium() ) { - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false ); - - if ( - isset( $latest_tag->readme ) && - isset( $latest_tag->readme->upgrade_notice ) && - ! empty( $latest_tag->readme->upgrade_notice ) - ) { - $update->upgrade_notice = $latest_tag->readme->upgrade_notice; - } - } - - $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename(); - - return $update; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param FS_Plugin_Tag $new_version - * - * @return bool - */ - private function is_new_version_premium( FS_Plugin_Tag $new_version ) { - $query_str = parse_url( $new_version->url, PHP_URL_QUERY ); - if ( empty( $query_str ) ) { - return false; - } - - parse_str( $query_str, $params ); - - return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] ); - } - - /** - * Update the updates transient with the module's update information. - * - * This method is required for multisite environment. - * If a module is site activated (not network) and not on the main site, - * the module will NOT be executed on the network level, therefore, the - * custom updates logic will not be executed as well, so unless we force - * the injection of the update into the updates transient, premium updates - * will not work. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param \FS_Plugin_Tag $new_version - */ - function set_update_data( FS_Plugin_Tag $new_version ) { - $this->_logger->entrance(); - - if ( ! $this->is_new_version_premium( $new_version ) ) { - return; - } - - $transient_key = "update_{$this->_fs->get_module_type()}s"; - - $transient_data = get_site_transient( $transient_key ); - - $transient_data = is_object( $transient_data ) ? - $transient_data : - new stdClass(); - - // Alias. - $basename = $this->_fs->get_plugin_basename(); - $is_plugin = $this->_fs->is_plugin(); - - if ( ! isset( $transient_data->response ) || - ! is_array( $transient_data->response ) - ) { - $transient_data->response = array(); - } else if ( ! empty( $transient_data->response[ $basename ] ) ) { - $version = $is_plugin ? - ( ! empty( $transient_data->response[ $basename ]->new_version ) ? - $transient_data->response[ $basename ]->new_version : - null - ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ? - $transient_data->response[ $basename ]['new_version'] : - null - ); - - if ( $version == $new_version->version ) { - // The update data is already set. - return; - } - } - - // Remove the added filters. - $this->remove_transient_filters(); - - $this->_update_details = $this->get_update_details( $new_version ); - - // Set update data in transient. - $transient_data->response[ $basename ] = $is_plugin ? - $this->_update_details : - (array) $this->_update_details; - - if ( ! isset( $transient_data->checked ) || - ! is_array( $transient_data->checked ) - ) { - $transient_data->checked = array(); - } - - // Flag the module as if it was already checked. - $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version(); - $transient_data->last_checked = time(); - - set_site_transient( $transient_key, $transient_data ); - - $this->add_transient_filters(); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.2 - */ - function delete_update_data() { - $this->_logger->entrance(); - - $transient_key = "update_{$this->_fs->get_module_type()}s"; - - $transient_data = get_site_transient( $transient_key ); - - // Alias - $basename = $this->_fs->get_plugin_basename(); - - if ( ! is_object( $transient_data ) || - ! isset( $transient_data->response ) || - ! is_array( $transient_data->response ) || - empty( $transient_data->response[ $basename ] ) - ) { - return; - } - - unset( $transient_data->response[ $basename ] ); - - // Remove the added filters. - $this->remove_transient_filters(); - - set_site_transient( $transient_key, $transient_data ); - - $this->add_transient_filters(); - } - - /** - * Try to fetch plugin's info from .org repository. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @param string $action - * @param object $args - * - * @return bool|mixed - */ - static function _fetch_plugin_info_from_repository( $action, $args ) { - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/'; - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { - $url = set_url_scheme( $url, 'https' ); - } - - $args = array( - 'timeout' => 15, - 'body' => array( - 'action' => $action, - 'request' => serialize( $args ) - ) - ); - - $request = wp_remote_post( $url, $args ); - - if ( is_wp_error( $request ) ) { - return false; - } - - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) ); - - if ( ! is_object( $res ) && ! is_array( $res ) ) { - return false; - } - - return $res; - } - - /** - * Fetches module translation updates from wordpress.org. - * - * @author Leo Fajardo (@leorw) - * @since 2.1.2 - * - * @param string $module_type - * @param string $slug - * - * @return array|null - */ - private function fetch_wp_org_module_translation_updates( $module_type, $slug ) { - $plugin_data = $this->_fs->get_plugin_data(); - - $locales = array_values( get_available_languages() ); - $locales = apply_filters( "{$module_type}_update_check_locales", $locales ); - $locales = array_unique( $locales ); - - $plugin_basename = $this->_fs->get_plugin_basename(); - if ( 'themes' === $module_type ) { - $plugin_basename = $slug; - } - - global $wp_version; - - $request_args = array( - 'timeout' => 15, - 'body' => array( - "{$module_type}" => json_encode( - array( - "{$module_type}" => array( - $plugin_basename => array( - 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ), - 'Author' => $plugin_data['Author'], - ) - ) - ) - ), - 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ), - 'locale' => json_encode( $locales ) - ), - 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) ) - ); - - $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/"; - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { - $url = set_url_scheme( $url, 'https' ); - } - - $raw_response = Freemius::safe_remote_post( - $url, - $request_args, - WP_FS__TIME_24_HOURS_IN_SEC, - WP_FS__TIME_12_HOURS_IN_SEC, - false - ); - - if ( is_wp_error( $raw_response ) ) { - return null; - } - - $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); - - if ( ! is_array( $response ) ) { - return null; - } - - if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) { - return null; - } - - return $response['translations']; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.2 - * - * @param string $module_type - * @param string $slug - * - * @return array - */ - private function get_installed_translations( $module_type, $slug ) { - if ( function_exists( 'wp_get_installed_translations' ) ) { - return wp_get_installed_translations( $module_type ); - } - - $dir = "/{$module_type}"; - - if ( ! is_dir( WP_LANG_DIR . $dir ) ) - return array(); - - $files = scandir( WP_LANG_DIR . $dir ); - if ( ! $files ) - return array(); - - $language_data = array(); - - foreach ( $files as $file ) { - if ( 0 !== strpos( $file, $slug ) ) { - continue; - } - - if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) { - continue; - } - - if ( substr( $file, -3 ) !== '.po' ) { - continue; - } - - if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { - continue; - } - - if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { - continue; - } - - list( , $textdomain, $language ) = $match; - - if ( '' === $textdomain ) { - $textdomain = 'default'; - } - - $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" ); - } - - return $language_data; - } - - /** - * Updates information on the "View version x.x details" page with custom data. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @uses FS_Api - * - * @param object $data - * @param string $action - * @param mixed $args - * - * @return object - */ - function plugins_api_filter( $data, $action = '', $args = null ) { - $this->_logger->entrance(); - - if ( ( 'plugin_information' !== $action ) || - ! isset( $args->slug ) - ) { - return $data; - } - - $addon = false; - $is_addon = false; - $addon_version = false; - - if ( $this->_fs->get_slug() !== $args->slug ) { - $addon = $this->_fs->get_addon_by_slug( $args->slug ); - - if ( ! is_object( $addon ) ) { - return $data; - } - - if ( $this->_fs->is_addon_activated( $addon->id ) ) { - $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version(); - } else if ( $this->_fs->is_addon_installed( $addon->id ) ) { - $addon_plugin_data = get_plugin_data( - ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ), - false, - false - ); - - if ( ! empty( $addon_plugin_data ) ) { - $addon_version = $addon_plugin_data['Version']; - } - } - - $is_addon = true; - } - - $plugin_in_repo = false; - if ( ! $is_addon ) { - // Try to fetch info from .org repository. - $data = self::_fetch_plugin_info_from_repository( $action, $args ); - - $plugin_in_repo = ( false !== $data ); - } - - if ( ! $plugin_in_repo ) { - $data = $args; - - // Fetch as much as possible info from local files. - $plugin_local_data = $this->_fs->get_plugin_data(); - $data->name = $plugin_local_data['Name']; - $data->author = $plugin_local_data['Author']; - $data->sections = array( - 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.', - ); - - // @todo Store extra plugin info on Freemius or parse readme.txt markup. - /*$info = $this->_fs->get_api_site_scope()->call('/information.json'); - -if ( !isset($info->error) ) { - $data = $info; -}*/ - } - - $plugin_version = $is_addon ? - $addon_version : - $this->_fs->get_plugin_version(); - - // Get plugin's newest update. - $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version ); - - if ( ! is_object( $new_version ) || empty( $new_version->version ) ) { - $data->version = $plugin_version; - } else { - if ( $is_addon ) { - $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); - $data->slug = $addon->slug; - $data->url = WP_FS__ADDRESS; - $data->package = $new_version->url; - } - - if ( ! $plugin_in_repo ) { - $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created; - $data->requires = $new_version->requires_platform_version; - $data->tested = $new_version->tested_up_to_version; - } - - $data->version = $new_version->version; - $data->download_link = $new_version->url; - - if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) { - $new_version_readme_data = $new_version->readme; - if ( isset( $new_version_readme_data->sections ) ) { - $new_version_readme_data->sections = (array) $new_version_readme_data->sections; - } else { - $new_version_readme_data->sections = array(); - } - - if ( isset( $data->sections ) ) { - if ( isset( $data->sections['screenshots'] ) ) { - $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots']; - } - - if ( isset( $data->sections['reviews'] ) ) { - $new_version_readme_data->sections['reviews'] = $data->sections['reviews']; - } - } - - if ( isset( $new_version_readme_data->banners ) ) { - $new_version_readme_data->banners = (array) $new_version_readme_data->banners; - } else if ( isset( $data->banners ) ) { - $new_version_readme_data->banners = $data->banners; - } - - $wp_org_sections = array( - 'author', - 'author_profile', - 'rating', - 'ratings', - 'num_ratings', - 'support_threads', - 'support_threads_resolved', - 'active_installs', - 'added', - 'homepage' - ); - - foreach ( $wp_org_sections as $wp_org_section ) { - if ( isset( $data->{$wp_org_section} ) ) { - $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section}; - } - } - - $data = $new_version_readme_data; - } - } - - return $data; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param number|bool $addon_id - * @param bool|string $newer_than Since 2.2.1 - * @param bool|string $fetch_readme Since 2.2.1 - * - * @return object - */ - private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) { - return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme ); - } - - /** - * Checks if a given basename has a matching folder name - * with the current context plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @return bool - */ - private function is_correct_folder_name() { - return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) ); - } - - /** - * This is a special after upgrade handler for migrating modules - * that didn't use the '-premium' suffix folder structure before - * the migration. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $response Install response. - * @param array $hook_extra Extra arguments passed to hooked filters. - * @param array $result Installation result data. - * - * @return bool - */ - function _maybe_update_folder_name( $response, $hook_extra, $result ) { - $basename = $this->_fs->get_plugin_basename(); - - if ( true !== $response || - empty( $hook_extra ) || - empty( $hook_extra['plugin'] ) || - $basename !== $hook_extra['plugin'] - ) { - return $response; - } - - $active_plugins_basenames = get_option( 'active_plugins' ); - - foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) { - if ( $basename === $active_plugin_basename ) { - // Get filename including extension. - $filename = basename( $basename ); - - $new_basename = plugin_basename( - trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) . - $filename - ); - - // Verify that the expected correct path exists. - if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) { - // Override active plugin name. - $active_plugins_basenames[ $key ] = $new_basename; - update_option( 'active_plugins', $active_plugins_basenames ); - } - - break; - } - } - - return $response; - } - - #---------------------------------------------------------------------------------- - #region Auto Activation - #---------------------------------------------------------------------------------- - - /** - * Installs and active a plugin when explicitly requested that from a 3rd party service. - * - * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin. - * - * @link http://tgmpluginactivation.com/ - * - * @author Vova Feldman - * @since 1.2.1.7 - * - * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ - * - * @uses WP_Filesystem - * @uses WP_Error - * @uses WP_Upgrader - * @uses Plugin_Upgrader - * @uses Plugin_Installer_Skin - * @uses Plugin_Upgrader_Skin - * - * @param number|bool $plugin_id - * - * @return array - */ - function install_and_activate_plugin( $plugin_id = false ) { - if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) { - // Invalid plugin ID. - return array( - 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), - 'code' => 'invalid_module_id', - ); - } - - $is_addon = false; - if ( FS_Plugin::is_valid_id( $plugin_id ) && - $plugin_id != $this->_fs->get_id() - ) { - $addon = $this->_fs->get_addon( $plugin_id ); - - if ( ! is_object( $addon ) ) { - // Invalid add-on ID. - return array( - 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), - 'code' => 'invalid_module_id', - ); - } - - $slug = $addon->slug; - $premium_slug = $addon->premium_slug; - $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); - - $is_addon = true; - } else { - $slug = $this->_fs->get_slug(); - $premium_slug = $this->_fs->get_premium_slug(); - $title = $this->_fs->get_plugin_title() . - ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' ); - } - - if ( $this->is_premium_plugin_active( $plugin_id ) ) { - // Premium version already activated. - return array( - 'message' => $is_addon ? - $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) : - $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), - 'code' => 'premium_installed', - ); - } - - $latest_version = $this->get_latest_download_details( $plugin_id, false, false ); - $target_folder = $premium_slug; - - // Prep variables for Plugin_Installer_Skin class. - $extra = array(); - $extra['slug'] = $target_folder; - $source = $latest_version->url; - $api = null; - - $install_url = add_query_arg( - array( - 'action' => 'install-plugin', - 'plugin' => urlencode( $slug ), - ), - 'update.php' - ); - - if ( ! class_exists( 'Plugin_Upgrader', false ) ) { - // Include required resources for the installation. - require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; - } - - $skin_args = array( - 'type' => 'web', - 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ), - 'url' => esc_url_raw( $install_url ), - 'nonce' => 'install-plugin_' . $slug, - 'plugin' => '', - 'api' => $api, - 'extra' => $extra, - ); - -// $skin = new Automatic_Upgrader_Skin( $skin_args ); -// $skin = new Plugin_Installer_Skin( $skin_args ); - $skin = new WP_Ajax_Upgrader_Skin( $skin_args ); - - // Create a new instance of Plugin_Upgrader. - $upgrader = new Plugin_Upgrader( $skin ); - - // Perform the action and install the plugin from the $source urldecode(). - add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); - - $install_result = $upgrader->install( $source ); - - remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 ); - - if ( is_wp_error( $install_result ) ) { - return array( - 'message' => $install_result->get_error_message(), - 'code' => $install_result->get_error_code(), - ); - } elseif ( is_wp_error( $skin->result ) ) { - return array( - 'message' => $skin->result->get_error_message(), - 'code' => $skin->result->get_error_code(), - ); - } elseif ( $skin->get_errors()->get_error_code() ) { - return array( - 'message' => $skin->get_error_messages(), - 'code' => 'unknown', - ); - } elseif ( is_null( $install_result ) ) { - global $wp_filesystem; - - $error_code = 'unable_to_connect_to_filesystem'; - $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' ); - - // Pass through the error from WP_Filesystem if one was raised. - if ( $wp_filesystem instanceof WP_Filesystem_Base && - is_wp_error( $wp_filesystem->errors ) && - $wp_filesystem->errors->get_error_code() - ) { - $error_message = $wp_filesystem->errors->get_error_message(); - } - - return array( - 'message' => $error_message, - 'code' => $error_code, - ); - } - - // Grab the full path to the main plugin's file. - $plugin_activate = $upgrader->plugin_info(); - - // Try to activate the plugin. - $activation_result = $this->try_activate_plugin( $plugin_activate ); - - if ( is_wp_error( $activation_result ) ) { - return array( - 'message' => $activation_result->get_error_message(), - 'code' => $activation_result->get_error_code(), - ); - } - - return $skin->get_upgrade_messages(); - } - - /** - * Tries to activate a plugin. If fails, returns the error. - * - * @author Vova Feldman - * @since 1.2.1.7 - * - * @param string $file_path Path within wp-plugins/ to main plugin file. - * This determines the styling of the output messages. - * - * @return bool|WP_Error - */ - protected function try_activate_plugin( $file_path ) { - $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() ); - - return is_wp_error( $activate ) ? - $activate : - true; - } - - /** - * Check if a premium module version is already active. - * - * @author Vova Feldman - * @since 1.2.1.7 - * - * @param number|bool $plugin_id - * - * @return bool - */ - private function is_premium_plugin_active( $plugin_id = false ) { - if ( $plugin_id != $this->_fs->get_id() ) { - return $this->_fs->is_addon_activated( $plugin_id, true ); - } - - return is_plugin_active( $this->_fs->premium_plugin_basename() ); - } - - /** - * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - * - * @param bool|WP_Error $response Response. - * @param array $hook_extra Extra arguments passed to hooked filters. - * - * @return bool|WP_Error - */ - static function _store_basename_for_source_adjustment( $response, $hook_extra ) { - if ( isset( $hook_extra['plugin'] ) ) { - self::$_upgrade_basename = $hook_extra['plugin']; - } else if ( isset( $hook_extra['theme'] ) ) { - self::$_upgrade_basename = $hook_extra['theme']; - } else { - self::$_upgrade_basename = null; - } - - return $response; - } - - /** - * Adjust the plugin directory name if necessary. - * Assumes plugin has a folder (not a single file plugin). - * - * The final destination directory of a plugin is based on the subdirectory name found in the - * (un)zipped source. In some cases this subdirectory name is not the same as the expected - * slug and the plugin will not be recognized as installed. This is fixed by adjusting - * the temporary unzipped source subdirectory name to the expected plugin slug. - * - * @author Vova Feldman - * @since 1.2.1.7 - * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated. - * - * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/. - * @param string $remote_source Path to upgrade/zip-file-name.tmp. - * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin. - * - * @return string|WP_Error - */ - static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) { - if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) { - return $source; - } - - $basename = self::$_upgrade_basename; - $is_theme = false; - - // Figure out what the slug is supposed to be. - if ( isset( $upgrader->skin->options['extra'] ) ) { - // Set by the auto-install logic. - $desired_slug = $upgrader->skin->options['extra']['slug']; - } else if ( ! empty( $basename ) ) { - /** - * If it doesn't end with ".php", it's a theme. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - $is_theme = ( ! fs_ends_with( $basename, '.php' ) ); - - $desired_slug = ( ! $is_theme ) ? - dirname( $basename ) : - // Theme slug - $basename; - } else { - // Can't figure out the desired slug, stop the execution. - return $source; - } - - if ( is_multisite() ) { - /** - * If we are running in a multisite environment and the product is not network activated, - * the instance will not exist anyway. Therefore, try to update the source if necessary - * regardless if the Freemius instance of the product exists or not. - * - * @author Vova Feldman - */ - } else if ( ! empty( $basename ) ) { - $fs = Freemius::get_instance_by_file( - $basename, - $is_theme ? - WP_FS__MODULE_TYPE_THEME : - WP_FS__MODULE_TYPE_PLUGIN - ); - - if ( ! is_object( $fs ) ) { - /** - * If the Freemius instance does not exist on a non-multisite network environment, it means that: - * 1. The product is not powered by Freemius; OR - * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - return $source; - } - } - - $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) ); - - if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) { - $from_path = untrailingslashit( $source ); - $to_path = trailingslashit( $remote_source ) . $desired_slug; - - if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) { - return trailingslashit( $to_path ); - } - - return new WP_Error( - 'rename_failed', - fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ), - array( - 'found' => $subdir_name, - 'expected' => $desired_slug - ) - ); - } - - return $source; - } - - #endregion - } diff --git a/freemius/includes/class-fs-security.php b/freemius/includes/class-fs-security.php index 4535aa2..e69de29 100644 --- a/freemius/includes/class-fs-security.php +++ b/freemius/includes/class-fs-security.php @@ -1,85 +0,0 @@ -id . - $entity->secret_key . - $entity->public_key . - $action - ); - } - - /** - * @param \FS_Scope_Entity $entity - * @param int|bool $timestamp - * @param string $action - * - * @return array - */ - function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) { - if ( false === $timestamp ) { - $timestamp = time(); - } - - return array( - 's_ctx_type' => $entity->get_type(), - 's_ctx_id' => $entity->id, - 's_ctx_ts' => $timestamp, - 's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ), - ); - } - } diff --git a/freemius/includes/class-fs-storage.php b/freemius/includes/class-fs-storage.php index 187a011..e69de29 100644 --- a/freemius/includes/class-fs-storage.php +++ b/freemius/includes/class-fs-storage.php @@ -1,532 +0,0 @@ -_module_type = $module_type; - $this->_module_slug = $slug; - $this->_is_multisite = is_multisite(); - - if ( $this->_is_multisite ) { - $this->_blog_id = get_current_blog_id(); - $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true ); - } - - $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id ); - } - - /** - * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values - * are retrieved/stored from/into the storage. - * - * @author Leo Fajardo (@leorw) - * - * @param bool $is_network_active - * @param bool $is_delegated_connection - */ - function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { - $this->_is_network_active = $is_network_active; - $this->_is_delegated_connection = $is_delegated_connection; - } - - /** - * Switch the context of the site level storage manager. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - */ - function set_site_blog_context( $blog_id ) { - $this->_storage = $this->get_site_storage( $blog_id ); - $this->_blog_id = $blog_id; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $key - * @param mixed $value - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). - * @param bool $flush - */ - function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) { - if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { - $this->_network_storage->store( $key, $value, $flush ); - } else { - $storage = $this->get_site_storage( $network_level_or_blog_id ); - $storage->store( $key, $value, $flush ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param bool $store - * @param string[] $exceptions Set of keys to keep and not clear. - * @param int|null|bool $network_level_or_blog_id - */ - function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite || - false === $network_level_or_blog_id || - is_null( $network_level_or_blog_id ) || - is_numeric( $network_level_or_blog_id ) - ) { - $storage = $this->get_site_storage( $network_level_or_blog_id ); - $storage->clear_all( $store, $exceptions ); - } - - if ( $this->_is_multisite && - ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) - ) { - $this->_network_storage->clear_all( $store, $exceptions ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $key - * @param bool $store - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). - */ - function remove( $key, $store = true, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { - $this->_network_storage->remove( $key, $store ); - } else { - $storage = $this->get_site_storage( $network_level_or_blog_id ); - $storage->remove( $key, $store ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $key - * @param mixed $default - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). - * - * @return mixed - */ - function get( $key, $default = false, $network_level_or_blog_id = null ) { - if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { - return $this->_network_storage->get( $key, $default ); - } else { - $storage = $this->get_site_storage( $network_level_or_blog_id ); - - return $storage->get( $key, $default ); - } - } - - /** - * Multisite activated: - * true: Save network storage. - * int: Save site specific storage. - * false|0: Save current site storage. - * null: Save network and current site storage. - * Site level activated: - * Save site storage. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param bool|int|null $network_level_or_blog_id - */ - function save( $network_level_or_blog_id = null ) { - if ( $this->_is_network_active && - ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) - ) { - $this->_network_storage->save(); - } - - if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { - $storage = $this->get_site_storage( $network_level_or_blog_id ); - $storage->save(); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function get_module_slug() { - return $this->_module_slug; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function get_module_type() { - return $this->_module_type; - } - - /** - * Migration script to the new storage data structure that is network compatible. - * - * IMPORTANT: - * This method should be executed only after it is determined if this is a network - * level compatible product activation. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function migrate_to_network() { - if ( ! $this->_is_multisite ) { - return; - } - - $updated = false; - - if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { - self::load_network_options_map(); - } - - foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { - if ( ! $this->is_multisite_option( $option ) ) { - continue; - } - - if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { - // Migrate option to the network storage. - $this->_network_storage->store( $option, $this->_storage->{$option}, false ); - - /** - * Remove the option from site level storage. - * - * IMPORTANT: - * The line below is intentionally commented since we want to preserve the option - * on the site storage level for "downgrade compatibility". Basically, if the user - * will downgrade to an older version of the plugin with the prev storage structure, - * it will continue working. - * - * @todo After a few releases we can remove this. - */ -// $this->_storage->remove($option, false); - - $updated = true; - } - } - - if ( ! $updated ) { - return; - } - - // Update network level storage. - $this->_network_storage->save(); -// $this->_storage->save(); - } - - #-------------------------------------------------------------------------------- - #region Helper Methods - #-------------------------------------------------------------------------------- - - /** - * We don't want to load the map right away since it's not even needed in a non-MS environment. - * - * Example: - * array( - * 'option1' => 0, // Means that the option should always be stored on the network level. - * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. - * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. - * 'option3' => 3, // Means that the option should always be stored on the site level. - * ) - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - private static function load_network_options_map() { - self::$_NETWORK_OPTIONS_MAP = array( - // Network level options. - 'affiliate_application_data' => 0, - 'beta_data' => 0, - 'connectivity_test' => 0, - 'handle_gdpr_admin_notice' => 0, - 'has_trial_plan' => 0, - 'install_sync_timestamp' => 0, - 'install_sync_cron' => 0, - 'is_anonymous_ms' => 0, - 'is_network_activated' => 0, - 'is_on' => 0, - 'is_plugin_new_install' => 0, - 'network_install_blog_id' => 0, - 'pending_sites_info' => 0, - 'plugin_last_version' => 0, - 'plugin_main_file' => 0, - 'plugin_version' => 0, - 'sdk_downgrade_mode' => 0, - 'sdk_last_version' => 0, - 'sdk_upgrade_mode' => 0, - 'sdk_version' => 0, - 'sticky_optin_added_ms' => 0, - 'subscriptions' => 0, - 'sync_timestamp' => 0, - 'sync_cron' => 0, - 'was_plugin_loaded' => 0, - 'network_user_id' => 0, - 'plugin_upgrade_mode' => 0, - 'plugin_downgrade_mode' => 0, - 'is_network_connected' => 0, - /** - * Special flag that is used when a super-admin upgrades to the new version of the SDK that - * supports network level integration, when the connection decision wasn't made for all of the - * sites in the network. - */ - 'is_network_activation' => 0, - 'license_migration' => 0, - - // When network activated, then network level. - 'install_timestamp' => 1, - 'prev_is_premium' => 1, - 'require_license_activation' => 1, - - // If not network activated OR delegated, then site level. - 'activation_timestamp' => 2, - 'expired_license_notice_shown' => 2, - 'is_whitelabeled' => 2, - 'last_license_key' => 2, - 'last_license_user_id' => 2, - 'prev_user_id' => 2, - 'sticky_optin_added' => 2, - 'uninstall_reason' => 2, - 'is_pending_activation' => 2, - 'pending_license_key' => 2, - 'is_extensions_tracking_allowed' => 2, - - // Site level options. - 'is_anonymous' => 3, - ); - } - - /** - * This method will and should only be executed when is_multisite() is true. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $key - * - * @return bool|mixed - */ - private function is_multisite_option( $key ) { - if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { - self::load_network_options_map(); - } - - if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) { - // Option not found -> use site level storage. - return false; - } - - if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { - // Option found and set to always use the network level storage on a multisite. - return true; - } - - if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { - // Option found and set to always use the site level storage on a multisite. - return false; - } - - if ( ! $this->_is_network_active ) { - return false; - } - - if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { - // Network activated. - return true; - } - - if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) { - // Network activated and not delegated. - return true; - } - - return false; - } - - /** - * @author Leo Fajardo - * - * @param string $key - * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). - * - * @return bool - */ - private function should_use_network_storage( $key, $network_level_or_blog_id = null ) { - if ( ! $this->_is_multisite ) { - // Not a multisite environment. - return false; - } - - if ( is_numeric( $network_level_or_blog_id ) ) { - // Explicitly asked to use a specified blog storage. - return false; - } - - if ( is_bool( $network_level_or_blog_id ) ) { - // Explicitly specified whether should use the network or blog level storage. - return $network_level_or_blog_id; - } - - // Determine which storage to use based on the option. - return $this->is_multisite_option( $key ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $blog_id - * - * @return \FS_Key_Value_Storage - */ - private function get_site_storage( $blog_id = 0 ) { - if ( ! is_numeric( $blog_id ) || - $blog_id == $this->_blog_id || - 0 == $blog_id - ) { - return $this->_storage; - } - - return FS_Key_Value_Storage::instance( - $this->_module_type . '_data', - $this->_storage->get_secondary_id(), - $blog_id - ); - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Magic methods - #-------------------------------------------------------------------------------- - - function __set( $k, $v ) { - if ( $this->should_use_network_storage( $k ) ) { - $this->_network_storage->{$k} = $v; - } else { - $this->_storage->{$k} = $v; - } - } - - function __isset( $k ) { - return $this->should_use_network_storage( $k ) ? - isset( $this->_network_storage->{$k} ) : - isset( $this->_storage->{$k} ); - } - - function __unset( $k ) { - if ( $this->should_use_network_storage( $k ) ) { - unset( $this->_network_storage->{$k} ); - } else { - unset( $this->_storage->{$k} ); - } - } - - function __get( $k ) { - return $this->should_use_network_storage( $k ) ? - $this->_network_storage->{$k} : - $this->_storage->{$k}; - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/class-fs-user-lock.php b/freemius/includes/class-fs-user-lock.php index 842cbba..e69de29 100644 --- a/freemius/includes/class-fs-user-lock.php +++ b/freemius/includes/class-fs-user-lock.php @@ -1,126 +0,0 @@ -_wp_user_id = Freemius::get_current_wp_user_id(); - $this->_thread_id = mt_rand( 0, 32000 ); - } - - - /** - * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @param int $expiration - * - * @return bool TRUE if successfully acquired lock. - */ - function try_lock( $expiration = 0 ) { - if ( $this->is_locked() ) { - // Already locked. - return false; - } - - set_site_transient( "locked_{$this->_wp_user_id}", $this->_thread_id, $expiration ); - - if ( $this->has_lock() ) { - set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); - - return true; - } - - return false; - } - - /** - * Acquire lock regardless if it's already acquired by another locker or not. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @param int $expiration - */ - function lock( $expiration = 0 ) { - set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); - } - - /** - * Checks if lock is currently acquired. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return bool - */ - function is_locked() { - return ( false !== get_site_transient( "locked_{$this->_wp_user_id}" ) ); - } - - /** - * Unlock the lock. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - */ - function unlock() { - delete_site_transient( "locked_{$this->_wp_user_id}" ); - } - - /** - * Checks if lock is currently acquired by the current locker. - * - * @return bool - */ - private function has_lock() { - return ( $this->_thread_id == get_site_transient( "locked_{$this->_wp_user_id}" ) ); - } - } \ No newline at end of file diff --git a/freemius/includes/customizer/class-fs-customizer-support-section.php b/freemius/includes/customizer/class-fs-customizer-support-section.php index 3a9300d..e69de29 100644 --- a/freemius/includes/customizer/class-fs-customizer-support-section.php +++ b/freemius/includes/customizer/class-fs-customizer-support-section.php @@ -1,102 +0,0 @@ -register_section_type( 'FS_Customizer_Support_Section' ); - - parent::__construct( $manager, $id, $args ); - } - - /** - * The type of customize section being rendered. - * - * @since 1.0.0 - * @access public - * @var string - */ - public $type = 'freemius-support-section'; - - /** - * @var Freemius - */ - public $fs = null; - - /** - * Add custom parameters to pass to the JS via JSON. - * - * @since 1.0.0 - */ - public function json() { - $json = parent::json(); - - $is_contact_visible = $this->fs->is_page_visible( 'contact' ); - $is_support_visible = $this->fs->is_page_visible( 'support' ); - - $json['theme_title'] = $this->fs->get_plugin_name(); - - if ( $is_contact_visible && $is_support_visible ) { - $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); - } - - if ( $is_contact_visible ) { - $json['contact'] = array( - 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), - 'url' => $this->fs->contact_url(), - ); - } - - if ( $is_support_visible ) { - $json['support'] = array( - 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), - 'url' => $this->fs->get_support_forum_url() - ); - } - - return $json; - } - - /** - * Outputs the Underscore.js template. - * - * @since 1.0.0 - */ - protected function render_template() { - ?> -
  • -

    - {{ data.theme_title }} - <# if ( data.contact && data.support ) { #> -
    - <# } #> - <# if ( data.contact ) { #> - {{ data.contact.label }} - <# } #> - <# if ( data.support ) { #> - {{ data.support.label }} - <# } #> - <# if ( data.contact && data.support ) { #> -
    - <# } #> -

    -
  • - register_control_type( 'FS_Customizer_Upsell_Control' ); - - parent::__construct( $manager, $id, $args ); - } - - /** - * Enqueue resources for the control. - */ - public function enqueue() { - fs_enqueue_local_style( 'fs_customizer', 'customizer.css' ); - } - - /** - * Json conversion - */ - public function to_json() { - $pricing_cta = esc_html( $this->fs->get_pricing_cta_label() ) . '  ' . ( is_rtl() ? '←' : '➤' ); - - parent::to_json(); - - $this->json['button_text'] = $pricing_cta; - $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? - $this->fs->get_trial_url() : - $this->fs->get_upgrade_url(); - - $api = FS_Plugin::is_valid_id( $this->fs->get_bundle_id() ) ? - $this->fs->get_api_bundle_scope() : - $this->fs->get_api_plugin_scope(); - - // Load features. - $pricing = $api->get( $this->fs->add_show_pending( "pricing.json" ) ); - - if ( $this->fs->is_api_result_object( $pricing, 'plans' ) ) { - // Add support features. - if ( is_array( $pricing->plans ) && 0 < count( $pricing->plans ) ) { - $support_features = array( - 'kb' => 'Help Center', - 'forum' => 'Support Forum', - 'email' => 'Priority Email Support', - 'phone' => 'Phone Support', - 'skype' => 'Skype Support', - 'is_success_manager' => 'Personal Success Manager', - ); - - for ( $i = 0, $len = count( $pricing->plans ); $i < $len; $i ++ ) { - if ( 'free' == $pricing->plans[$i]->name ) { - continue; - } - - if ( ! isset( $pricing->plans[ $i ]->features ) || - ! is_array( $pricing->plans[ $i ]->features ) ) { - $pricing->plans[$i]->features = array(); - } - - foreach ( $support_features as $key => $label ) { - $key = ( 'is_success_manager' !== $key ) ? - "support_{$key}" : - $key; - - if ( ! empty( $pricing->plans[ $i ]->{$key} ) ) { - - $support_feature = new stdClass(); - $support_feature->title = $label; - - $pricing->plans[ $i ]->features[] = $support_feature; - } - } - } - } - } - - $this->json['plans'] = $pricing->plans; - - $this->json['strings'] = array( - 'plan' => $this->fs->get_text_x_inline( 'Plan', 'as product pricing plan', 'plan' ), - ); - } - - /** - * Control content - */ - public function content_template() { - ?> -
    - <# if ( data.plans ) { #> -
      - <# for (i in data.plans) { #> - <# if ( 'free' != data.plans[i].name && (null != data.plans[i].features && 0 < data.plans[i].features.length) ) { #> -
    • -
      - -
      - <# if ( data.plans[i].description ) { #> -

      {{ data.plans[i].description }}

      - <# } #> - <# if ( data.plans[i].features ) { #> -
        - <# for ( j in data.plans[i].features ) { #> -
      • - <# if ( data.plans[i].features[j].value ) { #>{{ data.plans[i].features[j].value }} <# } #>{{ data.plans[i].features[j].title }} - <# if ( data.plans[i].features[j].description ) { #> - {{ data.plans[i].features[j].description }} - <# } #> -
      • - <# } #> -
      - <# } #> - <# if ( 'free' != data.plans[i].name ) { #> - {{{ data.button_text }}} - <# } #> -
      -
      -
    • - <# } #> - <# } #> -
    - <# } #> -
    - title( 'Freemius' ); - } - - static function requests_count() { - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); - } - - return number_format( count( $logger ) ); - } - - static function total_time() { - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); - } - - $total_time = .0; - foreach ( $logger as $l ) { - $total_time += $l['total']; - } - - return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); - } - - function render() { - ?> -
    - -
    - -
    - -
    - -
    - commission_type ) ? - ( '$' . $this->commission ) : - ( $this->commission . '%' ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @return bool - */ - function has_lifetime_commission() { - return ( 0 !== $this->future_payments_days ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @return bool - */ - function is_session_cookie() { - return ( 0 == $this->cookie_days ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @return bool - */ - function has_renewals_commission() { - return ( is_null( $this->commission_renewals_days ) || $this->commission_renewals_days > 0 ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-affiliate.php b/freemius/includes/entities/class-fs-affiliate.php index cdae366..e69de29 100644 --- a/freemius/includes/entities/class-fs-affiliate.php +++ b/freemius/includes/entities/class-fs-affiliate.php @@ -1,84 +0,0 @@ -status ); - } - - /** - * @author Leo Fajardo - * - * @return bool - */ - function is_pending() { - return ( 'pending' === $this->status ); - } - - /** - * @author Leo Fajardo - * - * @return bool - */ - function is_suspended() { - return ( 'suspended' === $this->status ); - } - - /** - * @author Leo Fajardo - * - * @return bool - */ - function is_rejected() { - return ( 'rejected' === $this->status ); - } - - /** - * @author Leo Fajardo - * - * @return bool - */ - function is_blocked() { - return ( 'blocked' === $this->status ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-billing.php b/freemius/includes/entities/class-fs-billing.php index bf68401..e69de29 100644 --- a/freemius/includes/entities/class-fs-billing.php +++ b/freemius/includes/entities/class-fs-billing.php @@ -1,95 +0,0 @@ - $def_value ) { - $this->{$key} = isset( $entity->{$key} ) ? - $entity->{$key} : - $def_value; - } - } - - static function get_type() { - return 'type'; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param FS_Entity $entity1 - * @param FS_Entity $entity2 - * - * @return bool - */ - static function equals( $entity1, $entity2 ) { - if ( is_null( $entity1 ) && is_null( $entity2 ) ) { - return true; - } else if ( is_object( $entity1 ) && is_object( $entity2 ) ) { - return ( $entity1->id == $entity2->id ); - } else if ( is_object( $entity1 ) ) { - return is_null( $entity1->id ); - } else { - return is_null( $entity2->id ); - } - } - - private $_is_updated = false; - - /** - * Update object property. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string|array[string]mixed $key - * @param string|bool $val - * - * @return bool - */ - function update( $key, $val = false ) { - if ( ! is_array( $key ) ) { - $key = array( $key => $val ); - } - - $is_updated = false; - - foreach ( $key as $k => $v ) { - if ( $this->{$k} === $v ) { - continue; - } - - if ( ( is_string( $this->{$k} ) && is_numeric( $v ) || - ( is_numeric( $this->{$k} ) && is_string( $v ) ) ) && - $this->{$k} == $v - ) { - continue; - } - - // Update value. - $this->{$k} = $v; - - $is_updated = true; - } - - $this->_is_updated = $is_updated; - - return $is_updated; - } - - /** - * Checks if entity was updated. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_updated() { - return $this->_is_updated; - } - - /** - * @param $id - * - * @author Vova Feldman (@svovaf) - * @since 1.1.2 - * - * @return bool - */ - static function is_valid_id($id){ - return is_numeric($id); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @return string - */ - public static function get_class_name() { - return get_called_class(); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-payment.php b/freemius/includes/entities/class-fs-payment.php index 8e35766..e69de29 100644 --- a/freemius/includes/entities/class-fs-payment.php +++ b/freemius/includes/entities/class-fs-payment.php @@ -1,168 +0,0 @@ -bound_payment_id ) && 0 > $this->gross ); - } - - /** - * Checks if the payment was migrated from another platform. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.2 - * - * @return bool - */ - function is_migrated() { - return ( 0 != $this->source ); - } - - /** - * Returns the gross in this format: - * `{symbol}{amount | 2 decimal digits} {currency | uppercase}` - * - * Examples: £9.99 GBP, -£9.99 GBP. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return string - */ - function formatted_gross() - { - return ( - ( $this->gross < 0 ? '-' : '' ) . - $this->get_symbol() . - number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' . - strtoupper( $this->currency ) - ); - } - - /** - * A map between supported currencies with their symbols. - * - * @var array - */ - static $CURRENCY_2_SYMBOL; - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return string - */ - private function get_symbol() { - if ( ! isset( self::$CURRENCY_2_SYMBOL ) ) { - // Lazy load. - self::$CURRENCY_2_SYMBOL = array( - self::CURRENCY_USD => '$', - self::CURRENCY_GBP => '£', - self::CURRENCY_EUR => '€', - ); - } - - return self::$CURRENCY_2_SYMBOL[ $this->currency ]; - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-info.php b/freemius/includes/entities/class-fs-plugin-info.php index f546534..e69de29 100644 --- a/freemius/includes/entities/class-fs-plugin-info.php +++ b/freemius/includes/entities/class-fs-plugin-info.php @@ -1,34 +0,0 @@ -is_features_enabled() ) { - return 0; - } - - if ( $this->is_unlimited() ) { - return 999; - } - - return ( $this->quota - $this->activated - ( $this->is_free_localhost ? 0 : $this->activated_local ) ); - } - - /** - * Check if single site license. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8.1 - * - * @return bool - */ - function is_single_site() { - return ( is_numeric( $this->quota ) && 1 == $this->quota ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.5 - * - * @return bool - */ - function is_expired() { - return ! $this->is_lifetime() && ( strtotime( $this->expiration ) < WP_FS__SCRIPT_START_TIME ); - } - - /** - * Check if license is not expired. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @return bool - */ - function is_valid() { - return ! $this->is_expired(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function is_lifetime() { - return is_null( $this->expiration ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.0 - * - * @return bool - */ - function is_unlimited() { - return is_null( $this->quota ); - } - - /** - * Check if license is fully utilized. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool|null $is_localhost - * - * @return bool - */ - function is_utilized( $is_localhost = null ) { - if ( is_null( $is_localhost ) ) { - $is_localhost = WP_FS__IS_LOCALHOST_FOR_SERVER; - } - - if ( $this->is_unlimited() ) { - return false; - } - - return ! ( $this->is_free_localhost && $is_localhost ) && - ( $this->quota <= $this->activated + ( $this->is_free_localhost ? 0 : $this->activated_local ) ); - } - - /** - * Check if license can be activated. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param bool|null $is_localhost - * - * @return bool - */ - function can_activate( $is_localhost = null ) { - return ! $this->is_utilized( $is_localhost ) && $this->is_features_enabled(); - } - - /** - * Check if license can be activated on a given number of production and localhost sites. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param int $production_count - * @param int $localhost_count - * - * @return bool - */ - function can_activate_bulk( $production_count, $localhost_count ) { - if ( $this->is_unlimited() ) { - return true; - } - - /** - * For simplicity, the logic will work as following: when given X sites to activate the license on, if it's - * possible to activate on ALL of them, do the activation. If it's not possible to activate on ALL of them, - * do NOT activate on any of them. - */ - return ( $this->quota >= $this->activated + $production_count + ( $this->is_free_localhost ? 0 : $this->activated_local + $localhost_count ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1 - * - * @return bool - */ - function is_active() { - return ( ! $this->is_cancelled ); - } - - /** - * Check if license's plan features are enabled. - * - * - Either if plan not expired - * - If expired, based on the configuration to block features or not. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function is_features_enabled() { - return $this->is_active() && ( ! $this->is_block_features || ! $this->is_expired() ); - } - - /** - * Subscription considered to be new without any payments - * if the license expires in less than 24 hours - * from the license creation. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_first_payment_pending() { - return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->expiration ) - strtotime( $this->created ) ); - } - - /** - * @return int - */ - function total_activations() { - return ( $this->activated + $this->activated_local ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return string - */ - function get_html_escaped_masked_secret_key() { - return self::mask_secret_key_for_html( $this->secret_key ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @param string $secret_key - * - * @return string - */ - static function mask_secret_key_for_html( $secret_key ) { - return ( - // Initial 6 chars - sk_ABC - htmlspecialchars( substr( $secret_key, 0, 6 ) ) . - // Masking - str_pad( '', ( strlen( $secret_key ) - 9 ) * 6, '•' ) . - // Last 3 chars. - htmlspecialchars( substr( $secret_key, - 3 ) ) - ); - } - } diff --git a/freemius/includes/entities/class-fs-plugin-plan.php b/freemius/includes/entities/class-fs-plugin-plan.php index 00a0d74..e69de29 100644 --- a/freemius/includes/entities/class-fs-plugin-plan.php +++ b/freemius/includes/entities/class-fs-plugin-plan.php @@ -1,145 +0,0 @@ -name = strtolower( $plan->name ); - } - } - - static function get_type() { - return 'plan'; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_free() { - return ( 'free' === $this->name ); - } - - /** - * Checks if this plan supports "Technical Support". - * - * @author Leo Fajardo (leorw) - * @since 1.2.0 - * - * @return bool - */ - function has_technical_support() { - return ( ! empty( $this->support_email ) || - ! empty( $this->support_skype ) || - ! empty( $this->support_phone ) || - ! empty( $this->is_success_manager ) - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function has_trial() { - return ! $this->is_free() && - is_numeric( $this->trial_period ) && ( $this->trial_period > 0 ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-tag.php b/freemius/includes/entities/class-fs-plugin-tag.php index 739e9c8..e69de29 100644 --- a/freemius/includes/entities/class-fs-plugin-tag.php +++ b/freemius/includes/entities/class-fs-plugin-tag.php @@ -1,60 +0,0 @@ -release_mode ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin.php b/freemius/includes/entities/class-fs-plugin.php index 2bc039a..e69de29 100644 --- a/freemius/includes/entities/class-fs-plugin.php +++ b/freemius/includes/entities/class-fs-plugin.php @@ -1,159 +0,0 @@ -is_premium = false; - $this->is_live = true; - - if ( empty( $this->premium_slug ) && ! empty( $plugin->slug ) ) { - $this->premium_slug = "{$this->slug}-premium"; - } - - if ( empty( $this->premium_suffix ) ) { - $this->premium_suffix = '(Premium)'; - } - - if ( isset( $plugin->info ) && is_object( $plugin->info ) ) { - $this->info = new FS_Plugin_Info( $plugin->info ); - } - } - - /** - * Check if plugin is an add-on (has parent). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool - */ - function is_addon() { - return isset( $this->parent_plugin_id ) && is_numeric( $this->parent_plugin_id ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - * - * @return bool - */ - function has_affiliate_program() { - return ( ! empty( $this->affiliate_moderation ) ); - } - - static function get_type() { - return 'plugin'; - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-pricing.php b/freemius/includes/entities/class-fs-pricing.php index 5404fe5..e69de29 100644 --- a/freemius/includes/entities/class-fs-pricing.php +++ b/freemius/includes/entities/class-fs-pricing.php @@ -1,157 +0,0 @@ -monthly_price ) && $this->monthly_price > 0 ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return bool - */ - function has_annual() { - return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return bool - */ - function has_lifetime() { - return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); - } - - /** - * Check if unlimited licenses pricing. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return bool - */ - function is_unlimited() { - return is_null( $this->licenses ); - } - - - /** - * Check if pricing has more than one billing cycle. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return bool - */ - function is_multi_cycle() { - $cycles = 0; - if ( $this->has_monthly() ) { - $cycles ++; - } - if ( $this->has_annual() ) { - $cycles ++; - } - if ( $this->has_lifetime() ) { - $cycles ++; - } - - return $cycles > 1; - } - - /** - * Get annual over monthly discount. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return int - */ - function annual_discount_percentage() { - return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); - } - - /** - * Get annual over monthly savings. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.8 - * - * @return float - */ - function annual_savings() { - return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - * - * @return bool - */ - function is_usd() { - return ( 'usd' === $this->currency ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-scope-entity.php b/freemius/includes/entities/class-fs-scope-entity.php index 6b83107..e69de29 100644 --- a/freemius/includes/entities/class-fs-scope-entity.php +++ b/freemius/includes/entities/class-fs-scope-entity.php @@ -1,29 +0,0 @@ -plan_id = $site->plan_id; - } - - if ( ! is_bool( $this->is_disconnected ) ) { - $this->is_disconnected = false; - } - } - - static function get_type() { - return 'install'; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $url - * - * @return bool - */ - static function is_localhost_by_address( $url ) { - if ( false !== strpos( $url, '127.0.0.1' ) || - false !== strpos( $url, 'localhost' ) - ) { - return true; - } - - if ( ! fs_starts_with( $url, 'http' ) ) { - $url = 'http://' . $url; - } - - $url_parts = parse_url( $url ); - - $subdomain = $url_parts['host']; - - return ( - // Starts with. - fs_starts_with( $subdomain, 'local.' ) || - fs_starts_with( $subdomain, 'dev.' ) || - fs_starts_with( $subdomain, 'test.' ) || - fs_starts_with( $subdomain, 'stage.' ) || - fs_starts_with( $subdomain, 'staging.' ) || - - // Ends with. - fs_ends_with( $subdomain, '.dev' ) || - fs_ends_with( $subdomain, '.test' ) || - fs_ends_with( $subdomain, '.staging' ) || - fs_ends_with( $subdomain, '.local' ) || - fs_ends_with( $subdomain, '.example' ) || - fs_ends_with( $subdomain, '.invalid' ) || - // GoDaddy test/dev. - fs_ends_with( $subdomain, '.myftpupload.com' ) || - // ngrok tunneling. - fs_ends_with( $subdomain, '.ngrok.io' ) || - // wpsandbox. - fs_ends_with( $subdomain, '.wpsandbox.pro' ) || - // SiteGround staging. - fs_starts_with( $subdomain, 'staging' ) || - // WPEngine staging. - fs_ends_with( $subdomain, '.staging.wpengine.com' ) || - fs_ends_with( $subdomain, '.dev.wpengine.com' ) || - fs_ends_with( $subdomain, '.wpengine.com' ) || - // Pantheon - ( fs_ends_with( $subdomain, 'pantheonsite.io' ) && - ( fs_starts_with( $subdomain, 'test-' ) || fs_starts_with( $subdomain, 'dev-' ) ) ) || - // Cloudways - fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || - // Kinsta - ( fs_starts_with( $subdomain, 'staging-' ) && ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) ) || - // DesktopServer - fs_ends_with( $subdomain, '.dev.cc' ) || - // Pressable - fs_ends_with( $subdomain, '.mystagingwebsite.com' ) - ); - } - - function is_localhost() { - return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) ); - } - - /** - * Check if site in trial. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_trial() { - return is_numeric( $this->trial_plan_id ) && ( strtotime( $this->trial_ends ) > WP_FS__SCRIPT_START_TIME ); - } - - /** - * Check if user already utilized the trial with the current install. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_trial_utilized() { - return is_numeric( $this->trial_plan_id ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function is_tracking_allowed() { - return ( true !== $this->is_disconnected ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function is_tracking_prohibited() { - return ! $this->is_tracking_allowed(); - } - - /** - * @author Edgar Melkonyan - * - * @return bool - */ - function is_beta() { - return ( isset( $this->is_beta ) && true === $this->is_beta ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-subscription.php b/freemius/includes/entities/class-fs-subscription.php index 3556fbd..e69de29 100644 --- a/freemius/includes/entities/class-fs-subscription.php +++ b/freemius/includes/entities/class-fs-subscription.php @@ -1,147 +0,0 @@ -is_canceled() ) { - return false; - } - - return ( - ! empty( $this->next_payment ) && - strtotime( $this->next_payment ) > WP_FS__SCRIPT_START_TIME - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @return bool - */ - function is_canceled() { - return ! is_null( $this->canceled_at ); - } - - /** - * Subscription considered to be new without any payments - * if the next payment should be made within less than 24 hours - * from the subscription creation. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @return bool - */ - function is_first_payment_pending() { - return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->next_payment ) - strtotime( $this->created ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - */ - function has_trial() { - return ! is_null( $this->trial_ends ); - } - } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-user.php b/freemius/includes/entities/class-fs-user.php index a329e87..e69de29 100644 --- a/freemius/includes/entities/class-fs-user.php +++ b/freemius/includes/entities/class-fs-user.php @@ -1,62 +0,0 @@ -first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) ); - } - - function is_verified() { - return ( isset( $this->is_verified ) && true === $this->is_verified ); - } - - static function get_type() { - return 'user'; - } - } \ No newline at end of file diff --git a/freemius/includes/entities/index.php b/freemius/includes/entities/index.php index 0316c6a..e69de29 100644 --- a/freemius/includes/entities/index.php +++ b/freemius/includes/entities/index.php @@ -1,3 +0,0 @@ - ' : '' ) . $title; - - if ( is_string( $confirmation ) ) { - return sprintf( '%s%s', - freemius( $module_id )->_get_admin_page_url( $page, $params ), - $method, - $action, - wp_nonce_field( $action, '_wpnonce', true, false ), - 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), - $confirmation, - $title - ); - } else if ( 'GET' !== strtoupper( $method ) ) { - return sprintf( '
    %s%s', - freemius( $module_id )->_get_admin_page_url( $page, $params ), - $method, - $action, - wp_nonce_field( $action, '_wpnonce', true, false ), - 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), - $title - ); - } else { - return sprintf( '%s', - wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ), - 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), - $title - ); - } - } - - function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { - ?> $entities_or_entity ) { - if ( is_array( $entities_or_entity ) ) { - $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name ); - } else { - $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name ); - } - } - - return $entities; - } - } - - if ( ! function_exists( 'fs_nonce_url' ) ) { - /** - * Retrieve URL with nonce added to URL query. - * - * Originally was using `wp_nonce_url()` but the new version - * changed the return value to escaped URL, that's not the expected - * behaviour. - * - * @author Vova Feldman (@svovaf) - * @since ~1.1.3 - * - * @param string $actionurl URL to add nonce action. - * @param int|string $action Optional. Nonce action name. Default -1. - * @param string $name Optional. Nonce name. Default '_wpnonce'. - * - * @return string Escaped URL with nonce action added. - */ - function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) { - return add_query_arg( $name, wp_create_nonce( $action ), $actionurl ); - } - } - - if ( ! function_exists( 'fs_starts_with' ) ) { - /** - * Check if string starts with. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string $haystack - * @param string $needle - * - * @return bool - */ - function fs_starts_with( $haystack, $needle ) { - $length = strlen( $needle ); - - return ( substr( $haystack, 0, $length ) === $needle ); - } - } - - if ( ! function_exists( 'fs_ends_with' ) ) { - /** - * Check if string ends with. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $haystack - * @param string $needle - * - * @return bool - */ - function fs_ends_with( $haystack, $needle ) { - $length = strlen( $needle ); - $start = $length * - 1; // negative - - return ( substr( $haystack, $start ) === $needle ); - } - } - - if ( ! function_exists( 'fs_strip_url_protocol' ) ) { - function fs_strip_url_protocol( $url ) { - if ( ! fs_starts_with( $url, 'http' ) ) { - return $url; - } - - $protocol_pos = strpos( $url, '://' ); - - if ( $protocol_pos > 5 ) { - return $url; - } - - return substr( $url, $protocol_pos + 3 ); - } - } - - #region Url Canonization ------------------------------------------------------------------ - - if ( ! function_exists( 'fs_canonize_url' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string $url - * @param bool $omit_host - * @param array $ignore_params - * - * @return string - */ - function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { - $parsed_url = parse_url( strtolower( $url ) ); - -// if ( ! isset( $parsed_url['host'] ) ) { -// return $url; -// } - - $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path']; - - if ( isset( $parsed_url['query'] ) ) { - parse_str( $parsed_url['query'], $queryString ); - $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params ); - } - - return $canonical; - } - } - - if ( ! function_exists( 'fs_canonize_query_string' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param array $params - * @param array $ignore_params - * @param bool $params_prefix - * - * @return string - */ - function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) { - if ( ! is_array( $params ) || 0 === count( $params ) ) { - return ''; - } - - // Url encode both keys and values - $keys = fs_urlencode_rfc3986( array_keys( $params ) ); - $values = fs_urlencode_rfc3986( array_values( $params ) ); - $params = array_combine( $keys, $values ); - - // Parameters are sorted by name, using lexicographical byte value ordering. - // Ref: Spec: 9.1.1 (1) - uksort( $params, 'strcmp' ); - - $pairs = array(); - foreach ( $params as $parameter => $value ) { - $lower_param = strtolower( $parameter ); - - // Skip ignore params. - if ( in_array( $lower_param, $ignore_params ) || - ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) ) - ) { - continue; - } - - if ( is_array( $value ) ) { - // If two or more parameters share the same name, they are sorted by their value - // Ref: Spec: 9.1.1 (1) - natsort( $value ); - foreach ( $value as $duplicate_value ) { - $pairs[] = $lower_param . '=' . $duplicate_value; - } - } else { - $pairs[] = $lower_param . '=' . $value; - } - } - - if ( 0 === count( $pairs ) ) { - return ''; - } - - return implode( "&", $pairs ); - } - } - - if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string|string[] $input - * - * @return array|mixed|string - */ - function fs_urlencode_rfc3986( $input ) { - if ( is_array( $input ) ) { - return array_map( 'fs_urlencode_rfc3986', $input ); - } else if ( is_scalar( $input ) ) { - return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) ); - } - - return ''; - } - } - - #endregion Url Canonization ------------------------------------------------------------------ - - if ( ! function_exists( 'fs_download_image' ) ) { - /** - * @author Vova Feldman (@svovaf) - * - * @since 1.2.2 Changed to usage of WP_Filesystem_Direct. - * - * @param string $from URL - * @param string $to File path. - * - * @return bool Is successfully downloaded. - */ - function fs_download_image( $from, $to ) { - $dir = dirname( $to ); - - if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { - return false; - } - - if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { - require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; - require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; - } - - $fs = new WP_Filesystem_Direct( '' ); - $tmpfile = download_url( $from ); - - if ( $tmpfile instanceof WP_Error ) { - // Issue downloading the file. - return false; - } - - $fs->copy( $tmpfile, $to ); - $fs->delete( $tmpfile ); - - return true; - } - } - - /* General Utilities - --------------------------------------------------------------------------------------------*/ - - if ( ! function_exists( 'fs_sort_by_priority' ) ) { - /** - * Sorts an array by the value of the priority key. - * - * @author Daniel Iser (@danieliser) - * @since 1.1.7 - * - * @param $a - * @param $b - * - * @return int - */ - function fs_sort_by_priority( $a, $b ) { - - // If b has a priority and a does not, b wins. - if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) { - return 1; - } // If b has a priority and a does not, b wins. - elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { - return - 1; - } // If neither has a priority or both priorities are equal its a tie. - elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) { - return 0; - } - - // If both have priority return the winner. - return ( $a['priority'] < $b['priority'] ) ? - 1 : 1; - } - } - - #-------------------------------------------------------------------------------- - #region Localization - #-------------------------------------------------------------------------------- - - if ( ! function_exists( 'fs_text' ) ) { - /** - * Retrieve a translated text by key. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $key - * @param string $slug - * - * @return string - * - * @global $fs_text , $fs_text_overrides - */ - function fs_text( $key, $slug = 'freemius' ) { - global $fs_text, - $fs_module_info_text, - $fs_text_overrides; - - if ( isset( $fs_text_overrides[ $slug ] ) ) { - if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { - return $fs_text_overrides[ $slug ][ $key ]; - } - - $lower_key = strtolower( $key ); - if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { - return $fs_text_overrides[ $slug ][ $lower_key ]; - } - } - - if ( ! isset( $fs_text ) ) { - $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? - WP_FS__DIR_INCLUDES : - dirname( __FILE__ ); - - require_once $dir . '/i18n.php'; - } - - if ( isset( $fs_text[ $key ] ) ) { - return $fs_text[ $key ]; - } - - if ( isset( $fs_module_info_text[ $key ] ) ) { - return $fs_module_info_text[ $key ]; - } - - return $key; - } - - #region Private - - /** - * Retrieve an inline translated text by key with a context. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - * - * @global $fs_text_overrides - */ - function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); - - // Avoid misleading Theme Check warning. - $fn = 'translate_with_gettext_context'; - - return $fn( $text, $context, $text_domain ); - } - - #endregion - - /** - * Retrieve an inline translated text by key with a context. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - * - * @global $fs_text_overrides - */ - function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - return _fs_text_x_inline( $text, $context, $key, $slug ); - } - - /** - * Output a translated text by key. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $key - * @param string $slug - */ - function fs_echo( $key, $slug = 'freemius' ) { - echo fs_text( $key, $slug ); - } - - /** - * Output an inline translated text. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) { - echo _fs_text_inline( $text, $key, $slug ); - } - - /** - * Output an inline translated text with a context. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - echo _fs_text_x_inline( $text, $context, $key, $slug ); - } - } - - if ( ! function_exists( 'fs_text_override' ) ) { - /** - * Get a translatable text override if exists, or `false`. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string|false - */ - function fs_text_override( $text, $key, $slug ) { - global $fs_text_overrides; - - /** - * Check if string is overridden. - */ - if ( ! isset( $fs_text_overrides[ $slug ] ) ) { - return false; - } - - if ( empty( $key ) ) { - $key = strtolower( str_replace( ' ', '-', $text ) ); - } - - if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { - return $fs_text_overrides[ $slug ][ $key ]; - } - - $lower_key = strtolower( $key ); - if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { - return $fs_text_overrides[ $slug ][ $lower_key ]; - } - - return false; - } - } - - if ( ! function_exists( 'fs_text_and_domain' ) ) { - /** - * Get a translatable text and its text domain. - * - * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string[] - */ - function fs_text_and_domain( $text, $key, $slug ) { - $override = fs_text_override( $text, $key, $slug ); - - if ( false === $override ) { - // No override, use FS text domain. - $text_domain = 'freemius'; - } else { - // Found an override. - $text = $override; - // Use the module's text domain. - $text_domain = $slug; - } - - return array( $text, $text_domain ); - } - } - - if ( ! function_exists( '_fs_text_inline' ) ) { - /** - * Retrieve an inline translated text by key. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - * - * @global $fs_text_overrides - */ - function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) { - list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); - - // Avoid misleading Theme Check warning. - $fn = 'translate'; - - return $fn( $text, $text_domain ); - } - } - - if ( ! function_exists( 'fs_text_inline' ) ) { - /** - * Retrieve an inline translated text by key. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - * - * @global $fs_text_overrides - */ - function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { - return _fs_text_inline( $text, $key, $slug ); - } - } - - if ( ! function_exists( 'fs_esc_attr' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - * - * @return string - */ - function fs_esc_attr( $key, $slug ) { - return esc_attr( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_attr_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) { - return esc_attr( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_attr_echo' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - */ - function fs_esc_attr_echo( $key, $slug ) { - echo esc_attr( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) { - echo esc_attr( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - * - * @return string - */ - function fs_esc_js( $key, $slug ) { - return esc_js( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) { - return esc_js( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js_x_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js_echo' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - */ - function fs_esc_js_echo( $key, $slug ) { - echo esc_js( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) { - echo esc_js( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_json_encode_echo' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - */ - function fs_json_encode_echo( $key, $slug ) { - echo json_encode( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) { - echo json_encode( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - * - * @return string - */ - function fs_esc_html( $key, $slug ) { - return esc_html( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) { - return esc_html( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html_x_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - * - * @return string - */ - function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $context Context information for the translators. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { - echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html_echo' ) ) { - /** - * @author Vova Feldman - * @since 1.2.1.6 - * - * @param string $key - * @param string $slug - */ - function fs_esc_html_echo( $key, $slug ) { - echo esc_html( fs_text( $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - * - * @param string $text Translatable string. - * @param string $key String key for overrides. - * @param string $slug Module slug for overrides. - */ - function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) { - echo esc_html( _fs_text_inline( $text, $key, $slug ) ); - } - } - - if ( ! function_exists( 'fs_override_i18n' ) ) { - /** - * Override default i18n text phrases. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param array[string]string $key_value - * @param string $slug - * - * @global $fs_text_overrides - */ - function fs_override_i18n( array $key_value, $slug = 'freemius' ) { - global $fs_text_overrides; - - if ( ! isset( $fs_text_overrides[ $slug ] ) ) { - $fs_text_overrides[ $slug ] = array(); - } - - foreach ( $key_value as $key => $value ) { - $fs_text_overrides[ $slug ][ $key ] = $value; - } - } - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Multisite Network - #-------------------------------------------------------------------------------- - - if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function fs_is_plugin_uninstall() { - return ( - defined( 'WP_UNINSTALL_PLUGIN' ) || - ( 0 < did_action( 'update_option_uninstall_plugins' ) ) - ); - } - } - - if ( ! function_exists( 'fs_is_network_admin' ) ) { - /** - * Unlike is_network_admin(), this one will also work properly when - * the context execution is WP AJAX handler, and during plugin - * uninstall. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function fs_is_network_admin() { - return ( - WP_FS__IS_NETWORK_ADMIN || - ( is_multisite() && fs_is_plugin_uninstall() ) - ); - } - } - - if ( ! function_exists( 'fs_is_blog_admin' ) ) { - /** - * Unlike is_blog_admin(), this one will also work properly when - * the context execution is WP AJAX handler, and during plugin - * uninstall. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function fs_is_blog_admin() { - return ( - WP_FS__IS_BLOG_ADMIN || - ( ! is_multisite() && fs_is_plugin_uninstall() ) - ); - } - } - - #endregion - - if ( ! function_exists( 'fs_apply_filter' ) ) { - /** - * Apply filter for specific plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param string $module_unique_affix Module's unique affix. - * @param string $tag The name of the filter hook. - * @param mixed $value The value on which the filters hooked to `$tag` are applied on. - * - * @return mixed The filtered value after all hooked functions are applied to it. - * - * @uses apply_filters() - */ - function fs_apply_filter( $module_unique_affix, $tag, $value ) { - $args = func_get_args(); - - return call_user_func_array( 'apply_filters', array_merge( - array( "fs_{$tag}_{$module_unique_affix}" ), - array_slice( $args, 2 ) ) - ); - } - } \ No newline at end of file diff --git a/freemius/includes/fs-essential-functions.php b/freemius/includes/fs-essential-functions.php index 76a3ef8..e69de29 100644 --- a/freemius/includes/fs-essential-functions.php +++ b/freemius/includes/fs-essential-functions.php @@ -1,500 +0,0 @@ -add( "Freemius failed to redirect the page because the headers have been already sent from line {$line} in file {$file}. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' ); - } - - return false; - } - - if ( defined( 'DOING_AJAX' ) ) { - // Don't redirect on AJAX calls. - return false; - } - - if ( ! $location ) // allows the wp_redirect filter to cancel a redirect - { - return false; - } - - $location = fs_sanitize_redirect( $location ); - - if ( $is_IIS ) { - header( "Refresh: 0;url=$location" ); - } else { - if ( php_sapi_name() != 'cgi-fcgi' ) { - status_header( $status ); - } // This causes problems on IIS and some FastCGI setups - header( "Location: $location" ); - } - - if ( $exit ) { - exit(); - } - - return true; - } - - if ( ! function_exists( 'fs_sanitize_redirect' ) ) { - /** - * Sanitizes a URL for use in a redirect. - * - * @since 2.3 - * - * @param string $location - * - * @return string redirect-sanitized URL - */ - function fs_sanitize_redirect( $location ) { - $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location ); - $location = fs_kses_no_null( $location ); - - // remove %0d and %0a from location - $strip = array( '%0d', '%0a' ); - $found = true; - while ( $found ) { - $found = false; - foreach ( (array) $strip as $val ) { - while ( strpos( $location, $val ) !== false ) { - $found = true; - $location = str_replace( $val, '', $location ); - } - } - } - - return $location; - } - } - - if ( ! function_exists( 'fs_kses_no_null' ) ) { - /** - * Removes any NULL characters in $string. - * - * @since 1.0.0 - * - * @param string $string - * - * @return string - */ - function fs_kses_no_null( $string ) { - $string = preg_replace( '/\0+/', '', $string ); - $string = preg_replace( '/(\\\\0)+/', '', $string ); - - return $string; - } - } - } - - #endregion Core Redirect (copied from BuddyPress) ----------------------------------------- - - if ( ! function_exists( '__fs' ) ) { - global $fs_text_overrides; - - if ( ! isset( $fs_text_overrides ) ) { - $fs_text_overrides = array(); - } - - /** - * Retrieve a translated text by key. - * - * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7. - * @todo Remove this method in the future. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param string $key - * @param string $slug - * - * @return string - * - * @global $fs_text, $fs_text_overrides - */ - function __fs( $key, $slug = 'freemius' ) { - _deprecated_function( __FUNCTION__, '2.0.0', 'fs_text()' ); - - global $fs_text, - $fs_module_info_text, - $fs_text_overrides; - - if ( isset( $fs_text_overrides[ $slug ] ) ) { - if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { - return $fs_text_overrides[ $slug ][ $key ]; - } - - $lower_key = strtolower( $key ); - if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { - return $fs_text_overrides[ $slug ][ $lower_key ]; - } - } - - if ( ! isset( $fs_text ) ) { - $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? - WP_FS__DIR_INCLUDES : - dirname( __FILE__ ); - - require_once $dir . '/i18n.php'; - } - - if ( isset( $fs_text[ $key ] ) ) { - return $fs_text[ $key ]; - } - - if ( isset( $fs_module_info_text[ $key ] ) ) { - return $fs_module_info_text[ $key ]; - } - - return $key; - } - - /** - * Output a translated text by key. - * - * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`. - * - * @todo Remove this method in the future. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param string $key - * @param string $slug - */ - function _efs( $key, $slug = 'freemius' ) { - fs_echo( $key, $slug ); - } - } - - if ( ! function_exists( 'fs_get_ip' ) ) { - /** - * Get client IP. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.2 - * - * @return string|null - */ - function fs_get_ip() { - $fields = array( - 'HTTP_CF_CONNECTING_IP', - 'HTTP_CLIENT_IP', - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_FORWARDED', - 'HTTP_FORWARDED_FOR', - 'HTTP_FORWARDED', - 'REMOTE_ADDR', - ); - - foreach ( $fields as $ip_field ) { - if ( ! empty( $_SERVER[ $ip_field ] ) ) { - return $_SERVER[ $ip_field ]; - } - } - - return null; - } - } - - /** - * Leverage backtrace to find caller plugin main file path. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return string - */ - function fs_find_caller_plugin_file() { - /** - * All the code below will be executed once on activation. - * If the user changes the main plugin's file name, the file_exists() - * will catch it. - */ - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $all_plugins = fs_get_plugins( true ); - $all_plugins_paths = array(); - - // Get active plugin's main files real full names (might be symlinks). - foreach ( $all_plugins as $relative_path => $data ) { - $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); - } - - $plugin_file = null; - for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { - if ( empty( $bt[ $i ]['file'] ) ) { - continue; - } - - if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) { - $plugin_file = $bt[ $i ]['file']; - break; - } - } - - if ( is_null( $plugin_file ) ) { - // Throw an error to the developer in case of some edge case dev environment. - wp_die( - 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', - 'Error', - array( 'back_link' => true ) - ); - } - - return $plugin_file; - } - - require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php'; - - /** - * Update SDK newest version reference. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $sdk_relative_path - * @param string|bool $plugin_file - * - * @global $fs_active_plugins - */ - function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) { - /** - * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()` - * function in the older version will be used instead of this one. But since the older version is using - * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the - * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be - * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine. - * - * Future versions that will call this function will use the proper logic here instead of just relying on the - * `is_plugin_active` function to fail for themes. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - - global $fs_active_plugins; - - $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ]; - - if ( ! is_string( $plugin_file ) ) { - $plugin_file = plugin_basename( fs_find_caller_plugin_file() ); - } - - if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) { - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $in_activation = ( ! is_plugin_active( $plugin_file ) ); - } else { - $theme = wp_get_theme(); - $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet ); - } - - $fs_active_plugins->newest = (object) array( - 'plugin_path' => $plugin_file, - 'sdk_path' => $sdk_relative_path, - 'version' => $newest_sdk->version, - 'in_activation' => $in_activation, - 'timestamp' => time(), - ); - - // Update DB with latest SDK version and path. - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - - /** - * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @return bool Was plugin order changed. Return false if plugin was loaded first anyways. - * - * @global $fs_active_plugins - */ - function fs_newest_sdk_plugin_first() { - global $fs_active_plugins; - - /** - * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins). - * - * @link https://github.com/Freemius/wordpress-sdk/issues/26 - */ - - $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path; - - $active_plugins = get_option( 'active_plugins', array() ); - $updated_active_plugins = array( $newest_sdk_plugin_path ); - - $plugin_found = false; - $is_first_path = true; - - foreach ( $active_plugins as $key => $plugin_path ) { - if ( $plugin_path === $newest_sdk_plugin_path ) { - if ( $is_first_path ) { - // if it's the first plugin already, no need to continue - return false; - } - - $plugin_found = true; - - // Skip the plugin (it is already added as the 1st item of $updated_active_plugins). - continue; - } - - $updated_active_plugins[] = $plugin_path; - - if ( $is_first_path ) { - $is_first_path = false; - } - } - - if ( $plugin_found ) { - update_option( 'active_plugins', $updated_active_plugins ); - - return true; - } - - if ( is_multisite() ) { - // Plugin is network active. - $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() ); - - if ( isset( $network_active_plugins[ $newest_sdk_plugin_path ] ) ) { - reset( $network_active_plugins ); - if ( $newest_sdk_plugin_path === key( $network_active_plugins ) ) { - // Plugin is already activated first on the network level. - return false; - } else { - $time = $network_active_plugins[ $newest_sdk_plugin_path ]; - - // Remove plugin from its current position. - unset( $network_active_plugins[ $newest_sdk_plugin_path ] ); - - // Set it to be included first. - $network_active_plugins = array( $newest_sdk_plugin_path => $time ) + $network_active_plugins; - - update_site_option( 'active_sitewide_plugins', $network_active_plugins ); - - return true; - } - } - } - - return false; - } - - /** - * Go over all Freemius SDKs in the system and find and "remember" - * the newest SDK which is associated with an active plugin. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @global $fs_active_plugins - */ - function fs_fallback_to_newest_active_sdk() { - global $fs_active_plugins; - - /** - * @var object $newest_sdk_data - */ - $newest_sdk_data = null; - $newest_sdk_path = null; - - foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) { - if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' ) - ) { - // If plugin inactive or SDK starter file doesn't exist, remove SDK reference. - if ( 'plugin' === $data->type ) { - $is_module_active = is_plugin_active( $data->plugin_path ); - } else { - $active_theme = wp_get_theme(); - $is_module_active = ( $data->plugin_path === $active_theme->get_template() ); - } - - $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) ); - - if ( ! $is_module_active || ! $is_sdk_exists ) { - unset( $fs_active_plugins->plugins[ $sdk_relative_path ] ); - - // No need to store the data since it will be stored in fs_update_sdk_newest_version() - // or explicitly with update_option(). - } else { - $newest_sdk_data = $data; - $newest_sdk_path = $sdk_relative_path; - } - } - } - - if ( is_null( $newest_sdk_data ) ) { - // Couldn't find any SDK reference. - $fs_active_plugins = new stdClass(); - update_option( 'fs_active_plugins', $fs_active_plugins ); - } else { - fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path ); - } - } \ No newline at end of file diff --git a/freemius/includes/fs-plugin-info-dialog.php b/freemius/includes/fs-plugin-info-dialog.php index bcfce99..e69de29 100644 --- a/freemius/includes/fs-plugin-info-dialog.php +++ b/freemius/includes/fs-plugin-info-dialog.php @@ -1,1644 +0,0 @@ -_fs = $fs; - - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - // Remove default plugin information action. - remove_all_actions( 'install_plugins_pre_plugin-information' ); - - // Override action with custom plugins function for add-ons. - add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) ); - - // Override request for plugin information for Add-ons. - add_filter( - 'fs_plugins_api', - array( &$this, '_get_addon_info_filter' ), - WP_FS__DEFAULT_PRIORITY, 3 ); - } - - /** - * Generate add-on plugin information. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param array $data - * @param string $action - * @param object|null $args - * - * @return array|null - */ - function _get_addon_info_filter( $data, $action = '', $args = null ) { - $this->_logger->entrance(); - - $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() ); - - if ( $this->_fs->get_id() != $parent_plugin_id || - ( 'plugin_information' !== $action ) || - ! isset( $args->slug ) - ) { - return $data; - } - - // Find add-on by slug. - $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE ); - - if ( false === $selected_addon ) { - return $data; - } - - if ( ! isset( $selected_addon->info ) ) { - // Setup some default info. - $selected_addon->info = new stdClass(); - $selected_addon->info->selling_point_0 = 'Selling Point 1'; - $selected_addon->info->selling_point_1 = 'Selling Point 2'; - $selected_addon->info->selling_point_2 = 'Selling Point 3'; - $selected_addon->info->description = '

    Tell your users all about your add-on

    '; - } - - fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); - - $data = $args; - - $has_free_plan = false; - $has_paid_plan = false; - - // Load add-on pricing. - $has_pricing = false; - $has_features = false; - $plans = false; - - $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) ); - - if ( ! isset( $result->error ) ) { - $plans = $result->plans; - - if ( is_array( $plans ) ) { - for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { - $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null; - $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null; - - $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); - $plan = $plans[ $i ]; - - if ( 'free' == $plans[ $i ]->name || - ! is_array( $pricing ) || - 0 == count( $pricing ) - ) { - $has_free_plan = true; - } - - if ( is_array( $pricing ) && 0 < count( $pricing ) ) { - $filtered_pricing = array(); - - foreach ( $pricing as $prices ) { - $prices = new FS_Pricing( $prices ); - - if ( ! $prices->is_usd() ) { - /** - * Skip non-USD pricing. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - continue; - } - - if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) || - ( $prices->has_annual() && $prices->annual_price > 1.0 ) || - ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 ) - ) { - $filtered_pricing[] = $prices; - } - } - - if ( ! empty( $filtered_pricing ) ) { - $has_paid_plan = true; - - $plan->pricing = $filtered_pricing; - - $has_pricing = true; - } - } - - if ( is_array( $features ) && 0 < count( $features ) ) { - $plan->features = $features; - - $has_features = true; - } - } - } - } - - $latest = null; - - if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { - $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( - 'plugin_information', (object) array( - 'slug' => $selected_addon->slug, - 'is_ssl' => is_ssl(), - 'fields' => array( - 'banners' => true, - 'reviews' => true, - 'downloaded' => false, - 'active_installs' => true - ) - ) ); - - if ( ! empty( $repo_data ) ) { - $data = $repo_data; - $data->wp_org_missing = false; - } else { - // Couldn't find plugin on .org. - $selected_addon->is_wp_org_compliant = false; - - // Plugin is missing, not on Freemius nor WP.org. - $data->wp_org_missing = true; - } - - $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing ); - } else { - $data->has_purchased_license = false; - $data->wp_org_missing = false; - - $fs_addon = null; - $current_addon_version = false; - if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) { - $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id ); - $current_addon_version = $fs_addon->get_plugin_version(); - } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) { - $addon_plugin_data = get_plugin_data( - ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ), - false, - false - ); - - if ( ! empty( $addon_plugin_data ) ) { - $current_addon_version = $addon_plugin_data['Version']; - } - } - - // Fetch latest version from Freemius. - $latest = $this->_fs->_fetch_latest_version( - $selected_addon->id, - true, - WP_FS__TIME_24_HOURS_IN_SEC, - $current_addon_version - ); - - if ( $has_paid_plan ) { - $blog_id = fs_request_get( 'fs_blog_id' ); - $has_valid_blog_id = is_numeric( $blog_id ); - - if ( $has_valid_blog_id ) { - switch_to_blog( $blog_id ); - } - - $data->checkout_link = $this->_fs->checkout_url( - WP_FS__PERIOD_ANNUALLY, - false, - array(), - ( $has_valid_blog_id ? false : null ) - ); - - if ( $has_valid_blog_id ) { - restore_current_blog(); - } - } - - /** - * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.1 - */ - if ( is_object( $fs_addon ) ) { - $data->has_purchased_license = $fs_addon->has_active_valid_license(); - } else { - $account_addons = $this->_fs->get_account_addons(); - if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { - $data->has_purchased_license = true; - } - } - - if ( $has_free_plan || $data->has_purchased_license ) { - $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id ); - } - - $data->fs_missing = ( - false === $latest && - ( - empty( $selected_addon->premium_releases_count ) || - ! ( $selected_addon->premium_releases_count > 0 ) - ) - ); - - // Fetch as much as possible info from local files. - $plugin_local_data = $this->_fs->get_plugin_data(); - $data->author = $plugin_local_data['Author']; - - if ( ! empty( $selected_addon->info->banner_url ) ) { - $data->banners = array( - 'low' => $selected_addon->info->banner_url, - ); - } - - if ( ! empty( $selected_addon->info->screenshots ) ) { - $view_vars = array( - 'screenshots' => $selected_addon->info->screenshots, - 'plugin' => $selected_addon, - ); - $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars ); - } - - if ( is_object( $latest ) ) { - $data->version = $latest->version; - $data->last_updated = $latest->created; - $data->requires = $latest->requires_platform_version; - $data->tested = $latest->tested_up_to_version; - } else if ( ! empty( $current_addon_version ) ) { - $data->version = $current_addon_version; - } else { - // Add dummy version. - $data->version = '1.0.0'; - - // Add message to developer to deploy the plugin through Freemius. - } - } - - $data->name = $selected_addon->title; - $view_vars = array( 'plugin' => $selected_addon ); - - if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { - $latest_version_readme_data = $latest->readme; - if ( isset( $latest_version_readme_data->sections ) ) { - $data->sections = (array) $latest_version_readme_data->sections; - } else { - $data->sections = array(); - } - } - - $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars ); - - if ( $has_pricing ) { - // Add plans to data. - $data->plans = $plans; - - if ( $has_features ) { - $view_vars = array( - 'plans' => $plans, - 'plugin' => $selected_addon, - ); - $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); - } - } - - $data->has_free_plan = $has_free_plan; - $data->has_paid_plan = $has_paid_plan; - $data->is_paid = $has_paid_plan; - $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant; - $data->premium_slug = $selected_addon->premium_slug; - $data->addon_id = $selected_addon->id; - - if ( ! isset( $data->has_purchased_license ) ) { - $data->has_purchased_license = false; - } - - return $data; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @param FS_Plugin_Plan $plan - * - * @return string - */ - private function get_billing_cycle( FS_Plugin_Plan $plan ) { - $billing_cycle = null; - - if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { - $pricing = $plan->pricing[0]; - if ( isset( $pricing->annual_price ) ) { - $billing_cycle = 'annual'; - } else if ( isset( $pricing->monthly_price ) ) { - $billing_cycle = 'monthly'; - } else if ( isset( $pricing->lifetime_price ) ) { - $billing_cycle = 'lifetime'; - } - } else { - foreach ( $plan->pricing as $pricing ) { - if ( isset( $pricing->annual_price ) ) { - $billing_cycle = 'annual'; - } else if ( isset( $pricing->monthly_price ) ) { - $billing_cycle = 'monthly'; - } else if ( isset( $pricing->lifetime_price ) ) { - $billing_cycle = 'lifetime'; - } - - if ( ! is_null( $billing_cycle ) ) { - break; - } - } - } - - return $billing_cycle; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param FS_Plugin_Plan $plan - * @param FS_Pricing $pricing - * - * @return float|null|string - */ - private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) { - $price_tag = ''; - if ( isset( $pricing->annual_price ) ) { - $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' ); - } else if ( isset( $pricing->monthly_price ) ) { - $price_tag = $pricing->monthly_price . ' / mo'; - } else if ( isset( $pricing->lifetime_price ) ) { - $price_tag = $pricing->lifetime_price; - } - - return '$' . $price_tag; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param object $api - * @param FS_Plugin_Plan $plan - * - * @return string - */ - private function get_actions_dropdown( $api, $plan = null ) { - $this->actions = isset( $this->actions ) ? - $this->actions : - $this->get_plugin_actions( $api ); - - $actions = $this->actions; - - $checkout_cta = $this->get_checkout_cta( $api, $plan ); - if ( ! empty( $checkout_cta ) ) { - /** - * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in - * the actions dropdown. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - if ( ! $api->has_purchased_license ) { - array_unshift( $actions, $checkout_cta ); - } else { - $actions[] = $checkout_cta; - } - } - - if ( empty( $actions ) ) { - return ''; - } - - $total_actions = count( $actions ); - if ( 1 === $total_actions ) { - return $actions[0]; - } - - ob_start(); - - ?> -
    -
    - -
    - - -
    -
    -
    - checkout_link ) || - ! isset( $api->plans ) || - ! is_array( $api->plans ) || - 0 == count( $api->plans ) - ) { - return ''; - } - - if ( is_null( $plan ) ) { - foreach ( $api->plans as $p ) { - if ( ! empty( $p->pricing ) ) { - $plan = $p; - break; - } - } - } - - $blog_id = fs_request_get( 'fs_blog_id' ); - $has_valid_blog_id = is_numeric( $blog_id ); - - if ( $has_valid_blog_id ) { - switch_to_blog( $blog_id ); - } - - $addon_checkout_url = $this->_fs->addon_checkout_url( - $plan->plugin_id, - $plan->pricing[0]->id, - $this->get_billing_cycle( $plan ), - $plan->has_trial(), - ( $has_valid_blog_id ? false : null ) - ); - - if ( $has_valid_blog_id ) { - restore_current_blog(); - } - - return '' . - esc_html( ! $plan->has_trial() ? - ( - $api->has_purchased_license ? - fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) : - fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) - ) : - sprintf( - /* translators: %s: N-days trial */ - fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), - $this->get_trial_period( $plan ) - ) - ) . - ''; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param object $api - * - * @return string[] - */ - private function get_plugin_actions( $api ) { - $this->status = isset( $this->status ) ? - $this->status : - install_plugin_install_status( $api ); - - $is_update_available = ( 'update_available' === $this->status['status'] ); - - if ( $is_update_available && empty( $this->status['url'] ) ) { - return array(); - } - - $blog_id = fs_request_get( 'fs_blog_id' ); - - $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id ); - - $actions = array(); - - $is_addon_activated = $this->_fs->is_addon_activated( $api->slug ); - $fs_addon = null; - - $is_free_installed = null; - $is_premium_installed = null; - - $has_installed_version = ( 'install' !== $this->status['status'] ); - - if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { - /** - * Free-only add-on. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $is_free_installed = $has_installed_version; - $is_premium_installed = false; - } else if ( ! $api->has_free_plan ) { - /** - * Premium-only add-on. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $is_free_installed = false; - $is_premium_installed = $has_installed_version; - } else { - /** - * Freemium add-on. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - if ( ! $has_installed_version ) { - $is_free_installed = false; - $is_premium_installed = false; - } else { - $fs_addon = $is_addon_activated ? - $this->_fs->get_addon_instance( $api->slug ) : - null; - - if ( is_object( $fs_addon ) ) { - if ( $fs_addon->is_premium() ) { - $is_premium_installed = true; - } else { - $is_free_installed = true; - } - } - - if ( is_null( $is_free_installed ) ) { - $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) ); - if ( ! $is_free_installed ) { - /** - * Check if there's a plugin installed in a directory named `$api->slug`. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $installed_plugins = get_plugins( '/' . $api->slug ); - $is_free_installed = ( ! empty( $installed_plugins ) ); - } - } - - if ( is_null( $is_premium_installed ) ) { - $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) ); - if ( ! $is_premium_installed ) { - /** - * Check if there's a plugin installed in a directory named `$api->premium_slug`. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $installed_plugins = get_plugins( '/' . $api->premium_slug ); - $is_premium_installed = ( ! empty( $installed_plugins ) ); - } - } - } - - $has_installed_version = ( $is_free_installed || $is_premium_installed ); - } - - $this->status['is_free_installed'] = $is_free_installed; - $this->status['is_premium_installed'] = $is_premium_installed; - - $can_install_free_version = false; - $can_install_free_version_update = false; - $can_download_free_version = false; - $can_activate_free_version = false; - $can_install_premium_version = false; - $can_install_premium_version_update = false; - $can_download_premium_version = false; - $can_activate_premium_version = false; - - if ( ! $api->has_purchased_license ) { - if ( $api->has_free_plan ) { - if ( $has_installed_version ) { - if ( $is_update_available ) { - $can_install_free_version_update = true; - } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { - $can_activate_free_version = true; - } - } else { - if ( - $this->_fs->is_premium() || - ! $this->_fs->is_org_repo_compliant() || - $api->is_wp_org_compliant - ) { - $can_install_free_version = true; - } else { - $can_download_free_version = true; - } - } - } - } else { - if ( ! is_object( $fs_addon ) && $is_addon_activated ) { - $fs_addon = $this->_fs->get_addon_instance( $api->slug ); - } - - $can_download_premium_version = true; - - if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { - if ( $is_premium_installed ) { - $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() ); - } else if ( $is_free_installed ) { - $can_activate_free_version = ( ! $is_addon_activated ); - } - } - - if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) { - if ( $is_update_available ) { - $can_install_premium_version_update = true; - } else if ( ! $is_premium_installed ) { - $can_install_premium_version = true; - } - } - } - - if ( - $can_install_premium_version || - $can_install_premium_version_update - ) { - if ( is_numeric( $blog_id ) ) { - /** - * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded - * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update - * to work. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); - } - - /** - * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be - * installed/updated. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&', $this->status['url'] ); - } - - if ( $can_install_free_version_update || $can_install_premium_version_update ) { - $actions[] = $this->get_cta( - ( $can_install_free_version_update ? - fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) : - fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ), - true, - false, - $this->status['url'], - '_parent' - ); - } else if ( $can_install_free_version || $can_install_premium_version ) { - $actions[] = $this->get_cta( - ( $can_install_free_version ? - fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) : - fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ), - true, - false, - $this->status['url'], - '_parent' - ); - } - - $download_latest_action = ''; - - if ( - ! empty( $api->download_link ) && - ( $can_download_free_version || $can_download_premium_version ) - ) { - $download_latest_action = $this->get_cta( - ( $can_download_free_version ? - fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) : - fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ), - true, - false, - esc_url( $api->download_link ) - ); - } - - if ( ! $can_activate_free_version && ! $can_activate_premium_version ) { - if ( ! empty( $download_latest_action ) ) { - $actions[] = $download_latest_action; - } - } else { - $activate_action = sprintf( - '%s', - wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ), - fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ), - $can_activate_free_version ? - fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) : - fs_text_inline( 'Activate', 'activate', $api->slug ) - ); - - if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) { - $actions[] = $download_latest_action; - - $download_latest_action = ''; - } - - if ( $can_install_premium_version || $can_install_premium_version_update ) { - if ( $can_download_premium_version && ! empty( $download_latest_action ) ) { - $actions[] = $download_latest_action; - - $download_latest_action = ''; - } - - $actions[] = $activate_action; - } else { - array_unshift( $actions, $activate_action ); - } - - if ( ! empty ($download_latest_action ) ) { - $actions[] = $download_latest_action; - } - } - - return $actions; - } - - /** - * Rebuilds the status URL based on the admin URL. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @param int $blog_id - * @param string $network_status_url - * @param string $status - * - * @return string - */ - private static function get_blog_status_url( $blog_id, $network_status_url, $status ) { - if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) { - return $network_status_url; - } - - $action = ( 'install' === $status ) ? - 'install-plugin' : - 'upgrade-plugin'; - - $query = parse_url( $network_status_url, PHP_URL_QUERY ); - if ( empty( $query ) ) { - return $network_status_url; - } - - parse_str( html_entity_decode( $query ), $url_params ); - if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) { - return $network_status_url; - } - - $plugin = $url_params['plugin']; - - return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); - } - - /** - * Helper method to get a CTA button HTML. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $label - * @param bool $is_primary - * @param bool $is_disabled - * @param string $href - * @param string $target - * - * @return string - */ - private function get_cta( - $label, - $is_primary = true, - $is_disabled = false, - $href = '', - $target = '_blank' - ) { - $classes = array(); - - if ( ! $is_primary ) { - $classes[] = 'left'; - } else { - $classes[] = 'button-primary'; - $classes[] = 'right'; - } - - if ( $is_disabled ) { - $classes[] = 'disabled'; - } - - $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : ''; - - return sprintf( - '%s', - empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, - implode( ' ', $classes ), - $label - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7 - * - * @param FS_Plugin_Plan $plan - * - * @return string - */ - private function get_trial_period( $plan ) { - $trial_period = (int) $plan->trial_period; - - switch ( $trial_period ) { - case 30: - return 'month'; - case 60: - return '2 months'; - default: - return "{$plan->trial_period} days"; - } - } - - /** - * Display plugin information in dialog box form. - * - * Based on core install_plugin_information() function. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - */ - function install_plugin_information() { - global $tab; - - if ( empty( $_REQUEST['plugin'] ) ) { - return; - } - - $args = array( - 'slug' => wp_unslash( $_REQUEST['plugin'] ), - 'is_ssl' => is_ssl(), - 'fields' => array( - 'banners' => true, - 'reviews' => true, - 'downloaded' => false, - 'active_installs' => true - ) - ); - - if ( is_array( $args ) ) { - $args = (object) $args; - } - - if ( ! isset( $args->per_page ) ) { - $args->per_page = 24; - } - - if ( ! isset( $args->locale ) ) { - $args->locale = get_locale(); - } - - $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); - - if ( is_wp_error( $api ) ) { - wp_die( $api ); - } - - $plugins_allowedtags = array( - 'a' => array( - 'href' => array(), - 'title' => array(), - 'target' => array(), - // Add image style for screenshots. - 'class' => array() - ), - 'style' => array(), - 'abbr' => array( 'title' => array() ), - 'acronym' => array( 'title' => array() ), - 'code' => array(), - 'pre' => array(), - 'em' => array(), - 'strong' => array(), - 'div' => array( 'class' => array() ), - 'span' => array( 'class' => array() ), - 'p' => array(), - 'ul' => array(), - 'ol' => array(), - 'li' => array( 'class' => array() ), - 'i' => array( 'class' => array() ), - 'h1' => array(), - 'h2' => array(), - 'h3' => array(), - 'h4' => array(), - 'h5' => array(), - 'h6' => array(), - 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ), -// 'table' => array(), -// 'td' => array(), -// 'tr' => array(), -// 'th' => array(), -// 'thead' => array(), -// 'tbody' => array(), - ); - - $plugins_section_titles = array( - 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ), - 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ), - 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ), - 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ), - 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ), - 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ), - 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ), - ); - - // Sanitize HTML -// foreach ( (array) $api->sections as $section_name => $content ) { -// $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); -// } - - foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { - if ( isset( $api->$key ) ) { - $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); - } - } - - // Add after $api->slug is ready. - $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug ); - - $_tab = esc_attr( $tab ); - - $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English. - if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) { - $section_titles = array_keys( (array) $api->sections ); - $section = array_shift( $section_titles ); - } - - iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) ); - - $_with_banner = ''; - -// var_dump($api->banners); - if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) { - $_with_banner = 'with-banner'; - $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low']; - $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high']; - ?> - - '; - echo "

    {$api->name}

    "; - echo "
    \n"; - - foreach ( (array) $api->sections as $section_name => $content ) { - if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { - continue; - } - - if ( isset( $plugins_section_titles[ $section_name ] ) ) { - $title = $plugins_section_titles[ $section_name ]; - } else { - $title = ucwords( str_replace( '_', ' ', $section_name ) ); - } - - $class = ( $section_name === $section ) ? ' class="current"' : ''; - $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); - $href = esc_url( $href ); - $san_section = esc_attr( $section_name ); - echo "\t" . esc_html( $title ) . "\n"; - } - - echo "
    \n"; - - ?> -
    -
    - is_paid ) : ?> - plans ) ) : ?> -
    - plans as $plan ) : ?> - pricing ) ) { - continue; - } - - /** - * @var FS_Plugin_Plan $plan - */ - ?> - pricing[0] ?> - is_multi_cycle() ?> -
    -

    slug ), $plan->title ) ) ?>

    - has_annual() ?> - has_monthly() ?> - -
    - - pricing[0]->annual_discount_percentage() : 0 ?> - 0 ) : ?> - slug ), $annual_discount . '%' ) ?> - -
      -
    - get_actions_dropdown( $api, $plan ) ?> -
    - has_trial() ) : ?> - get_trial_period( $plan ) ?> -
      -
    • - slug ), $trial_period ) ) ?> -
    • -
    • - slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?> -
    • -
    - -
    -
    -
    - - - -
    -

    slug ) ?>

    -
      - version ) ) { ?> -
    • - slug ); ?> - : version; ?>
    • - author ) ) { - ?> -
    • - slug ); ?> - : author, '_blank' ); ?> -
    • - last_updated ) ) { - ?> -
    • slug ); ?> - : - slug ), - human_time_diff( strtotime( $api->last_updated ) ) - ) ) ?> -
    • - requires ) ) { - ?> -
    • - slug ) ?> - : slug ), $api->requires ) ) ?> -
    • - tested ) ) { - ?> -
    • - slug ); ?> - : tested; ?> -
    • - downloaded ) ) { - ?> -
    • - slug ) ?> - : downloaded ) ? - /* translators: %s: 1 or One (Number of times downloaded) */ - fs_text_inline( '%s time', 'x-time', $api->slug ) : - /* translators: %s: Number of times downloaded */ - fs_text_inline( '%s times', 'x-times', $api->slug ) - ), - number_format_i18n( $api->downloaded ) - ) ); ?> -
    • - slug ) && true == $api->is_wp_org_compliant ) { - ?> -
    • slug ) ?> - » -
    • - homepage ) ) { - ?> -
    • slug ) ?> - » -
    • - donate_link ) && empty( $api->contributors ) ) { - ?> -
    • slug ) ?> - » -
    • - -
    -
    - rating ) ) { ?> -

    slug ); ?>

    - $api->rating, - 'type' => 'percent', - 'number' => $api->num_ratings - ) ); ?> - (slug ), - sprintf( - ( ( 1 == $api->num_ratings ) ? - /* translators: %s: 1 or One */ - fs_text_inline( '%s rating', 'x-rating', $api->slug ) : - /* translators: %s: Number larger than 1 */ - fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) - ), - number_format_i18n( $api->num_ratings ) - ) ) ) ?>) - - ratings ) && array_sum( (array) $api->ratings ) > 0 ) { - foreach ( $api->ratings as $key => $ratecount ) { - // Avoid div-by-zero. - $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; - $stars_label = sprintf( - ( ( 1 == $key ) ? - /* translators: %s: 1 or One */ - fs_text_inline( '%s star', 'x-star', $api->slug ) : - /* translators: %s: Number larger than 1 */ - fs_text_inline( '%s stars', 'x-stars', $api->slug ) - ), - number_format_i18n( $key ) - ); - ?> -
    - - - - - -
    - contributors ) ) { - ?> -

    slug ); ?>

    -
      - contributors as $contrib_username => $contrib_profile ) { - if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { - continue; - } - if ( empty( $contrib_username ) ) { - $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); - } - $contrib_username = sanitize_user( $contrib_username ); - if ( empty( $contrib_profile ) ) { - echo "
    • {$contrib_username}
    • "; - } else { - echo "
    • {$contrib_username}
    • "; - } - } - ?> -
    - donate_link ) ) { ?> - slug ) ?> - » - - -
    -
    - tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { - echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; - } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { - echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; - } - - foreach ( (array) $api->sections as $section_name => $content ) { - $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); - $content = links_add_target( $content, '_blank' ); - - $san_section = esc_attr( $section_name ); - - $display = ( $section_name === $section ) ? 'block' : 'none'; - - if ( 'description' === $section_name && - ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || - ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) - ) { - $missing_notice = array( - 'type' => 'error', - 'id' => md5( microtime() ), - 'message' => $api->is_paid ? - fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : - fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), - ); - fs_require_template( 'admin-notice.php', $missing_notice ); - } - echo "\t
    \n"; - echo $content; - echo "\t
    \n"; - } - echo "
    \n"; - echo "
    \n"; - echo "
    \n"; // #plugin-information-scrollable - echo "\n"; - ?> - - 'You are just one step away - %s', - * - * We can use the filter: - * fs_override_i18n( array( - * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), - * 'skip' => __( 'Not today', '{your-text_domain}' ), - * ), '{plugin_slug}' ); - * - * Or with the Freemius instance: - * - * my_freemius->override_i18n( array( - * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), - * 'skip' => __( 'Not today', '{your-text_domain}' ), - * ) ); - */ - global $fs_text; - - $fs_text = array( - 'account' => _fs_text( 'Account' ), - 'addon' => _fs_text( 'Add-On' ), - 'contact-us' => _fs_text( 'Contact Us' ), - 'contact-support' => _fs_text( 'Contact Support' ), - 'change-ownership' => _fs_text( 'Change Ownership' ), - 'support' => _fs_text( 'Support' ), - 'support-forum' => _fs_text( 'Support Forum' ), - 'add-ons' => _fs_text( 'Add-Ons' ), - 'upgrade' => _fs_x( 'Upgrade', 'verb' ), - 'awesome' => _fs_text( 'Awesome' ), - 'pricing' => _fs_x( 'Pricing', 'noun' ), - 'price' => _fs_x( 'Price', 'noun' ), - 'unlimited-updates' => _fs_text( 'Unlimited Updates' ), - 'downgrade' => _fs_x( 'Downgrade', 'verb' ), - 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ), - 'cancel-trial' => _fs_text( 'Cancel Trial' ), - 'free-trial' => _fs_text( 'Free Trial' ), - 'start-free-x' => _fs_text( 'Start my free %s' ), - 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ), - 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ), - 'details' => _fs_text( 'Details' ), - 'account-details' => _fs_text( 'Account Details' ), - 'delete' => _fs_x( 'Delete', 'verb' ), - 'show' => _fs_x( 'Show', 'verb' ), - 'hide' => _fs_x( 'Hide', 'verb' ), - 'edit' => _fs_x( 'Edit', 'verb' ), - 'update' => _fs_x( 'Update', 'verb' ), - 'date' => _fs_text( 'Date' ), - 'amount' => _fs_text( 'Amount' ), - 'invoice' => _fs_text( 'Invoice' ), - 'billing' => _fs_text( 'Billing' ), - 'payments' => _fs_text( 'Payments' ), - 'delete-account' => _fs_text( 'Delete Account' ), - 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ), - 'plan' => _fs_x( 'Plan', 'as product pricing plan' ), - 'change-plan' => _fs_text( 'Change Plan' ), - 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ), - 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ), - 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ), - 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ), - 'new' => _fs_text( 'New' ), - 'free' => _fs_text( 'Free' ), - 'trial' => _fs_x( 'Trial', 'as trial plan' ), - 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ), - 'purchase' => _fs_x( 'Purchase', 'verb' ), - 'purchase-license' => _fs_text( 'Purchase License' ), - 'buy' => _fs_x( 'Buy', 'verb' ), - 'buy-license' => _fs_text( 'Buy License' ), - 'license-single-site' => _fs_text( 'Single Site License' ), - 'license-unlimited' => _fs_text( 'Unlimited Licenses' ), - 'license-x-sites' => _fs_text( 'Up to %s Sites' ), - 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ), - 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ), - 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ), - 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ), - 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now', - '%s - plugin name. As complete "Jetpack" activation now' ), - 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ), - 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ), - 'error' => _fs_text( 'Error' ), - 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), - 'learn-more' => _fs_text( 'Learn more' ), - 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."), - 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'), - - #region Affiliation - 'affiliation' => _fs_text( 'Affiliation' ), - 'affiliate' => _fs_text( 'Affiliate' ), - 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ), - 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ), - 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ), - 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ), - 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ), - 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ), - 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ), - 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ), - 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ), - 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ), - 'program-summary' => _fs_text( 'Program Summary' ), - 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ), - 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ), - 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ), - 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ), - 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ), - 'become-an-affiliate' => _fs_text( 'Become an affiliate' ), - 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ), - 'full-name' => _fs_text( 'Full name' ), - 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ), - 'promotion-methods' => _fs_text( 'Promotion methods' ), - 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ), - 'mobile-apps' => _fs_text( 'Mobile apps' ), - 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ), - 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ), - 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ), - 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ), - 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ), - 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ), - 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ), - 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ), - 'add-another-domain' => _fs_text( 'Add another domain' ), - 'remove' => _fs_x( 'Remove', 'Remove domain' ), - 'email-address-is-required' => _fs_text( 'Email address is required.' ), - 'domain-is-required' => _fs_text( 'Domain is required.' ), - 'invalid-domain' => _fs_text( 'Invalid domain' ), - 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ), - 'processing' => _fs_text( 'Processing...' ), - 'non-expiring' => _fs_text( 'Non-expiring' ), - 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ), - #endregion Affiliation - - #region Account - 'expiration' => _fs_x( 'Expiration', 'as expiration date' ), - 'license' => _fs_x( 'License', 'as software license' ), - 'not-verified' => _fs_text( 'not verified' ), - 'verify-email' => _fs_text( 'Verify Email' ), - 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ), - 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ), - 'no-expiration' => _fs_text( 'No expiration' ), - 'expired' => _fs_text( 'Expired' ), - 'cancelled' => _fs_text( 'Cancelled' ), - 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ), - 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ), - /* translators: %s: Version number (e.g. 4.6 or higher) */ - 'x-or-higher' => _fs_text( '%s or higher' ), - 'version' => _fs_x( 'Version', 'as plugin version' ), - 'name' => _fs_text( 'Name' ), - 'email' => _fs_text( 'Email' ), - 'email-address' => _fs_text( 'Email address' ), - 'verified' => _fs_text( 'Verified' ), - 'module' => _fs_text( 'Module' ), - 'module-type' => _fs_text( 'Module Type' ), - 'plugin' => _fs_text( 'Plugin' ), - 'plugins' => _fs_text( 'Plugins' ), - 'theme' => _fs_text( 'Theme' ), - 'themes' => _fs_text( 'Themes' ), - 'path' => _fs_x( 'Path', 'as file/folder path' ), - 'title' => _fs_text( 'Title' ), - 'free-version' => _fs_text( 'Free version' ), - 'premium-version' => _fs_text( 'Premium version' ), - 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ), - 'id' => _fs_text( 'ID' ), - 'users' => _fs_text( 'Users' ), - 'module-installs' => _fs_text( '%s Installs' ), - 'sites' => _fs_x( 'Sites', 'like websites' ), - 'user-id' => _fs_text( 'User ID' ), - 'site-id' => _fs_text( 'Site ID' ), - 'public-key' => _fs_text( 'Public Key' ), - 'secret-key' => _fs_text( 'Secret Key' ), - 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ), - 'no-id' => _fs_text( 'No ID' ), - 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ), - 'sync' => _fs_x( 'Sync', 'as synchronize' ), - 'activate-license' => _fs_text( 'Activate License' ), - 'activate-free-version' => _fs_text( 'Activate Free Version' ), - 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ), - 'activating-license' => _fs_text( 'Activating license...' ), - 'change-license' => _fs_text( 'Change License' ), - 'update-license' => _fs_text( 'Update License' ), - 'deactivate-license' => _fs_text( 'Deactivate License' ), - 'activate' => _fs_text( 'Activate' ), - 'deactivate' => _fs_text( 'Deactivate' ), - 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ), - 'skip-and-x' => _fs_text( 'Skip & %s' ), - 'no-deactivate' => _fs_text( 'No - just deactivate' ), - 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ), - 'active' => _fs_x( 'Active', 'active mode' ), - 'is-active' => _fs_x( 'Is Active', 'is active mode?' ), - 'install-now' => _fs_text( 'Install Now' ), - 'install-update-now' => _fs_text( 'Install Update Now' ), - 'more-information-about-x' => _fs_text( 'More information about %s' ), - 'localhost' => _fs_text( 'Localhost' ), - 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ), - 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ), - 'last-license' => _fs_text( 'Last license' ), - 'what-is-your-x' => _fs_text( 'What is your %s?' ), - 'activate-this-addon' => _fs_text( 'Activate this add-on' ), - 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ), - 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ), - 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ), - 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ), - 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ), - 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ), - 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ), - 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ), - #endregion Account - - 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ), - 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ), - #region Plugin Deactivation - 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ), - 'quick-feedback' => _fs_text( 'Quick feedback' ), - 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ), - 'deactivating' => _fs_text( 'deactivating' ), - 'deactivation' => _fs_text( 'Deactivation' ), - 'theme-switch' => _fs_text( 'Theme Switch' ), - 'switching' => _fs_text( 'switching' ), - 'switch' => _fs_text( 'Switch' ), - 'activate-x' => _fs_text( 'Activate %s' ), - 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ), - 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ), - 'cancel' => _fs_text( 'Cancel' ), - 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ), - 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ), - 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ), - 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ), - 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ), - 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ), - 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ), - 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ), - 'reason-other' => _fs_x( 'Other', - 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ), - 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ), - 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ), - 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ), - 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ), - 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ), - 'reason-not-working' => _fs_text( 'The %s is not working' ), - 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ), - 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ), - 'placeholder-feature' => _fs_text( 'What feature?' ), - 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ), - 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ), - 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ), - 'reason-didnt-work' => _fs_text( "The %s didn't work" ), - 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ), - 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ), - #endregion Plugin Deactivation - - #region Connect - 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ), - 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ), - 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ), - 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ), - 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ), - 'complete-the-install' => _fs_text( 'complete the install' ), - 'start-the-trial' => _fs_text( 'start the trial' ), - 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ), - 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ), - 'what-permissions' => _fs_text( 'What permissions are being granted?' ), - 'permissions-profile' => _fs_text( 'Your Profile Overview' ), - 'permissions-profile_desc' => _fs_text( 'Name and email address' ), - 'permissions-site' => _fs_text( 'Your Site Overview' ), - 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ), - 'permissions-events' => _fs_text( 'Current %s Events' ), - 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ), - 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ), - 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ), - 'permissions-admin-notices' => _fs_text( 'Admin Notices' ), - 'permissions-newsletter' => _fs_text( 'Newsletter' ), - 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ), - 'privacy-policy' => _fs_text( 'Privacy Policy' ), - 'tos' => _fs_text( 'Terms of Service' ), - 'activating' => _fs_x( 'Activating', 'as activating plugin' ), - 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ), - 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ), - 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ), - 'skip' => _fs_x( 'Skip', 'verb' ), - 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ), - 'resend-activation-email' => _fs_text( 'Re-send activation email' ), - 'license-key' => _fs_text( 'License key' ), - 'send-license-key' => _fs_text( 'Send License Key' ), - 'sending-license-key' => _fs_text( 'Sending license key' ), - 'have-license-key' => _fs_text( 'Have a license key?' ), - 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ), - 'cant-find-license-key' => _fs_text( "Can't find your license key?" ), - 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ), - 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ), - 'opt-in' => _fs_text( 'Opt In' ), - 'opt-out' => _fs_text( 'Opt Out' ), - 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ), - 'opting-out' => _fs_text( 'Opting out...' ), - 'opting-in' => _fs_text( 'Opting in...' ), - 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ), - 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ), - 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ), - 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ), - 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ), - 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ), - 'continue' => _fs_text( 'Continue' ), - 'allow' => _fs_text( 'allow' ), - 'delegate' => _fs_text( 'delegate' ), - #endregion Connect - - #region Screenshots - 'screenshots' => _fs_text( 'Screenshots' ), - 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ), - #endregion Screenshots - - #region Debug - 'freemius-debug' => _fs_text( 'Freemius Debug' ), - 'on' => _fs_x( 'On', 'as turned on' ), - 'off' => _fs_x( 'Off', 'as turned off' ), - 'debugging' => _fs_x( 'Debugging', 'as code debugging' ), - 'freemius-state' => _fs_text( 'Freemius State' ), - 'connected' => _fs_x( 'Connected', 'as connection was successful' ), - 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ), - 'api' => _fs_x( 'API', 'as application program interface' ), - 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ), - 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ), - 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ), - 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ), - 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ), - 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ), - 'actions' => _fs_text( 'Actions' ), - 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ), - 'start-fresh' => _fs_text( 'Start Fresh' ), - 'clear-api-cache' => _fs_text( 'Clear API Cache' ), - 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ), - 'scheduled-crons' => _fs_text( 'Scheduled Crons' ), - 'cron-type' => _fs_text( 'Cron Type' ), - 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ), - 'module-licenses' => _fs_text( '%s Licenses' ), - 'debug-log' => _fs_text( 'Debug Log' ), - 'all' => _fs_text( 'All' ), - 'file' => _fs_text( 'File' ), - 'function' => _fs_text( 'Function' ), - 'process-id' => _fs_text( 'Process ID' ), - 'logger' => _fs_text( 'Logger' ), - 'message' => _fs_text( 'Message' ), - 'download' => _fs_text( 'Download' ), - 'filter' => _fs_text( 'Filter' ), - 'type' => _fs_text( 'Type' ), - 'all-types' => _fs_text( 'All Types' ), - 'all-requests' => _fs_text( 'All Requests' ), - #endregion Debug - - #region Expressions - 'congrats' => _fs_x( 'Congrats', 'as congratulations' ), - 'oops' => _fs_x( 'Oops', 'exclamation' ), - 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ), - 'woot' => _fs_x( 'W00t', - '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ), - 'right-on' => _fs_x( 'Right on', 'a positive response' ), - 'hmm' => _fs_x( 'Hmm', - 'something somebody says when they are thinking about what you have just said. ' ), - 'ok' => _fs_text( 'O.K' ), - 'hey' => _fs_x( 'Hey', 'exclamation' ), - 'heads-up' => _fs_x( 'Heads up', - 'advance notice of something that will need attention.' ), - #endregion Expressions - - #region Admin Notices - 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ), - 'you-are-good' => _fs_text( 'You are all good!' ), - 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ), - 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ), - 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ), - 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ), - 'x-updated' => _fs_text( 'You have successfully updated your %s.' ), - 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ), - 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ), - 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ), - 'no-cc-required' => _fs_text( 'No credit card required' ), - 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ), - 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ), - 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ), - 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ), - 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ), - 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ), - 'download-latest-version' => _fs_text( 'Download the latest version' ), - 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ), - 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ), - 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ), - 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.', - '%s - product name, e.g. Facebook add-on was successfully...' ), - 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ), - 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ), - 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ), - 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ), - 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ), - 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ), - 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ), - 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ), - 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ), - 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ), - 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ), - 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ), - 'server-error-message' => _fs_text( 'Error received from the server:' ), - 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ), - 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ), - 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ), - 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ), - 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ), - 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ), - 'please-download-x' => _fs_text( 'Please download %s.' ), - 'latest-x-version' => _fs_x( 'the latest %s version here', - '%s - plan name, as the latest professional version here' ), - 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ), - 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ), - 'starting-trial' => _fs_text( 'Starting trial' ), - 'please-wait' => _fs_text( 'Please wait' ), - 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ), - 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ), - 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ), - 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ), - 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ), - 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ), - 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ), - 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ), - 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ), - 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ), - 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ), - 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ), - 'contact-us-here' => _fs_text( 'Please contact us here' ), - 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ), - #endregion Admin Notices - #region Connectivity Issues - 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ), - 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ), - 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ), - 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ), - 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ), - 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.', - 'as pluginX requires an access to our API' ), - 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ), - 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ), - 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), - 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ), - 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ), - 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ), - 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), - 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ), - 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ), - 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ), - 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ), - 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ), - 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ), - 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ), - 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ), - 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ), - 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ), - 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', - '%1$s - plugin title, %2$s - API domain' ), - 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ), - #endregion Connectivity Issues - #region Change Owner - 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ), - 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ), - 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ), - #endregion Change Owner - 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.', - 'addonX cannot run without pluginY' ), - 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ), - 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.', - 'pluginX activation was successfully...' ), - 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ), - 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ), - 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ), - #-------------------------------------------------------------------------------- - #region Add-On Licensing - #-------------------------------------------------------------------------------- - 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ), - 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ), - #endregion - #-------------------------------------------------------------------------------- - #region Billing Cycles - #-------------------------------------------------------------------------------- - 'monthly' => _fs_x( 'Monthly', 'as every month' ), - 'mo' => _fs_x( 'mo', 'as monthly period' ), - 'annual' => _fs_x( 'Annual', 'as once a year' ), - 'annually' => _fs_x( 'Annually', 'as once a year' ), - 'once' => _fs_x( 'Once', 'as once a year' ), - 'year' => _fs_x( 'year', 'as annual period' ), - 'lifetime' => _fs_text( 'Lifetime' ), - 'best' => _fs_x( 'Best', 'e.g. the best product' ), - 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ), - 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ), - #endregion Billing Cycles - 'view-details' => _fs_text( 'View details' ), - #-------------------------------------------------------------------------------- - #region Trial - #-------------------------------------------------------------------------------- - 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ), - /* translators: %1$s: Number of trial days; %2$s: Plan name; */ - 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ), - /* translators: %s: Link to freemius.com */ - 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ), - - #endregion - #-------------------------------------------------------------------------------- - #region Billing Details - #-------------------------------------------------------------------------------- - 'business-name' => _fs_text( 'Business name' ), - 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ), - 'address-line-n' => _fs_text( 'Address Line %d' ), - 'country' => _fs_text( 'Country' ), - 'select-country' => _fs_text( 'Select Country' ), - 'city' => _fs_text( 'City' ), - 'town' => _fs_text( 'Town' ), - 'state' => _fs_text( 'State' ), - 'province' => _fs_text( 'Province' ), - 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ), - #endregion - #-------------------------------------------------------------------------------- - #region Module Installation - #-------------------------------------------------------------------------------- - 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ), - 'auto-installation' => _fs_text( 'Automatic Installation' ), - /* translators: %s: Number of seconds */ - 'x-sec' => _fs_text( '%s sec' ), - 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ), - 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ), - 'cancel-installation' => _fs_text( 'Cancel Installation' ), - 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ), - 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ), - 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ), - 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ), - 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ), - 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ), - 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ), - #endregion - - /* translators: %s: Page name */ - 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ), - 'pci-compliant' => _fs_text( 'PCI compliant' ), - 'view-paid-features' => _fs_text( 'View paid features' ), - ); - - /** - * Localization of the strings in the plugin/theme info dialog box. - * - * $fs_module_info_text should ONLY include strings that are not located in $fs_text. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - */ - global $fs_module_info_text; - - $fs_module_info_text = array( - 'description' => _fs_x( 'Description', 'Plugin installer section title' ), - 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ), - 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ), - 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ), - 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ), - 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ), - /* translators: %s: 1 or One */ - 'x-star' => _fs_text( '%s star' ), - /* translators: %s: Number larger than 1 */ - 'x-stars' => _fs_text( '%s stars' ), - /* translators: %s: 1 or One */ - 'x-rating' => _fs_text( '%s rating' ), - /* translators: %s: Number larger than 1 */ - 'x-ratings' => _fs_text( '%s ratings' ), - /* translators: %s: 1 or One (Number of times downloaded) */ - 'x-time' => _fs_text( '%s time' ), - /* translators: %s: Number of times downloaded */ - 'x-times' => _fs_text( '%s times' ), - /* translators: %s: # of stars (e.g. 5 stars) */ - 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ), - 'last-updated:' => _fs_text( 'Last Updated' ), - 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ), - 'author:' => _fs_x( 'Author:', 'as the plugin author' ), - 'compatible-up-to:' => _fs_text( 'Compatible up to:' ), - 'downloaded:' => _fs_text( 'Downloaded:' ), - 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ), - 'plugin-homepage' => _fs_text( 'Plugin Homepage' ), - 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ), - 'average-rating' => _fs_text( 'Average Rating' ), - 'based-on-x' => _fs_text( 'based on %s' ), - 'warning:' => _fs_text( 'Warning:' ), - 'contributors' => _fs_text( 'Contributors' ), - 'plugin-install' => _fs_text( 'Plugin Install' ), - 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ), - 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ), - 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ), - 'latest-installed' => _fs_text( 'Latest Version Installed' ), - ); diff --git a/freemius/includes/index.php b/freemius/includes/index.php index 0316c6a..e69de29 100644 --- a/freemius/includes/index.php +++ b/freemius/includes/index.php @@ -1,3 +0,0 @@ - - */ - private $_default_submenu_items; - /** - * @since 1.1.3 - * - * @var string - */ - private $_first_time_path; - /** - * @since 1.2.2 - * - * @var bool - */ - private $_menu_exists; - /** - * @since 2.0.0 - * - * @var bool - */ - private $_network_menu_exists; - - #endregion Properties - - /** - * @var FS_Logger - */ - protected $_logger; - - #region Singleton - - /** - * @var FS_Admin_Menu_Manager[] - */ - private static $_instances = array(); - - /** - * @param number $module_id - * @param string $module_type - * @param string $module_unique_affix - * - * @return FS_Admin_Menu_Manager - */ - static function instance( $module_id, $module_type, $module_unique_affix ) { - $key = 'm_' . $module_id; - - if ( ! isset( self::$_instances[ $key ] ) ) { - self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); - } - - return self::$_instances[ $key ]; - } - - protected function __construct( $module_id, $module_type, $module_unique_affix ) { - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->_module_id = $module_id; - $this->_module_type = $module_type; - $this->_module_unique_affix = $module_unique_affix; - } - - #endregion Singleton - - #region Helpers - - private function get_option( &$options, $key, $default = false ) { - return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; - } - - private function get_bool_option( &$options, $key, $default = false ) { - return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; - } - - #endregion Helpers - - /** - * @param array $menu - * @param bool $is_addon - */ - function init( $menu, $is_addon = false ) { - $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); - $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); - - $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); - - $this->_default_submenu_items = array(); - // @deprecated - $this->_type = 'page'; - $this->_is_top_level = true; - $this->_is_override_exact = false; - $this->_parent_slug = false; - // @deprecated - $this->_parent_type = 'page'; - - if ( isset( $menu ) ) { - if ( ! $is_addon ) { - $this->_default_submenu_items = array( - 'contact' => $this->get_bool_option( $menu, 'contact', true ), - 'support' => $this->get_bool_option( $menu, 'support', true ), - 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), - 'account' => $this->get_bool_option( $menu, 'account', true ), - 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), - 'addons' => $this->get_bool_option( $menu, 'addons', true ), - ); - - // @deprecated - $this->_type = $this->get_option( $menu, 'type', 'page' ); - } - - $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); - - if ( isset( $menu['parent'] ) ) { - $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); - // @deprecated - $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); - - // If parent's slug is different, then it's NOT a top level menu item. - $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); - } else { - /** - * If no parent then top level if: - * - Has custom admin menu ('page') - * - CPT menu type ('cpt') - */ -// $this->_is_top_level = in_array( $this->_type, array( -// 'cpt', -// 'page' -// ) ); - } - - $first_path = $this->get_option( $menu, 'first-path', false ); - - if ( ! empty( $first_path ) && is_string( $first_path ) ) { - $this->_first_time_path = $first_path; - } - } - } - - /** - * Check if top level menu. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return bool False if submenu item. - */ - function is_top_level() { - return $this->_is_top_level; - } - - /** - * Check if the page should be override on exact URL match. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return bool False if submenu item. - */ - function is_override_exact() { - return $this->_is_override_exact; - } - - - /** - * Get the path of the page the user should be forwarded to after first activation. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param bool $is_network Since 2.4.5 - * - * @return string - */ - function get_first_time_path( $is_network = false ) { - if ( empty ( $this->_first_time_path ) ) { - return $this->_first_time_path; - } - - if ( $is_network ) { - return network_admin_url( $this->_first_time_path ); - } else { - return admin_url( $this->_first_time_path ); - } - } - - /** - * Check if plugin's menu item is part of a custom top level menu. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return bool - */ - function has_custom_parent() { - return ! $this->_is_top_level && is_string( $this->_parent_slug ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @return bool - */ - function has_menu() { - return $this->_menu_exists; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return bool - */ - function has_network_menu() { - return $this->_network_menu_exists; - } - - /** - * @author Leo Fajardo (@leorw) - * - * @param string $menu_slug - * - * @since 2.1.3 - */ - function set_slug_and_network_menu_exists_flag($menu_slug ) { - $this->_menu_slug = $menu_slug; - $this->_network_menu_exists = false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string $id - * @param bool $default - * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. - * - * @return bool - */ - function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { - if ( ! $ignore_menu_existence && ! $this->has_menu() ) { - return false; - } - - return fs_apply_filter( - $this->_module_unique_affix, - 'is_submenu_visible', - $this->get_bool_option( $this->_default_submenu_items, $id, $default ), - $id - ); - } - - /** - * Calculates admin settings menu slug. - * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @param string $page - * - * @return string - */ - function get_slug( $page = '' ) { - return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? - $this->_menu_slug : - $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_parent_slug() { - return $this->_parent_slug; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_type() { - return $this->_type; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return bool - */ - function is_cpt() { - return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || - // Back compatibility. - 'cpt' === $this->_type - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_parent_type() { - return $this->_parent_type; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_raw_slug() { - return $this->_menu_slug; - } - - /** - * Get plugin's original menu slug. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_original_menu_slug() { - if ( 'cpt' === $this->_type ) { - return add_query_arg( array( - 'post_type' => $this->_menu_slug - ), 'edit.php' ); - } - - if ( false === strpos( $this->_menu_slug, '.php?' ) ) { - return $this->_menu_slug; - } else { - return $this->_module_unique_affix; - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.3 - * - * @return string - */ - function get_top_level_menu_slug() { - return $this->has_custom_parent() ? - $this->get_parent_slug() : - $this->get_raw_slug(); - } - - /** - * Is user on plugin's admin activation page. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - * - * @param bool $show_opt_in_on_themes_page Since 2.3.1 - * - * @return bool - * - * @deprecated Please use is_activation_page() instead. - */ - function is_main_settings_page( $show_opt_in_on_themes_page = false ) { - return $this->is_activation_page( $show_opt_in_on_themes_page ); - } - - /** - * Is user on product's admin activation page. - * - * @author Vova Feldman (@svovaf) - * @since 2.3.1 - * - * @param bool $show_opt_in_on_themes_page Since 2.3.1 - * - * @return bool - */ - function is_activation_page( $show_opt_in_on_themes_page = false ) { - if ( $show_opt_in_on_themes_page ) { - /** - * In activation only when show_optin query string param is given. - * - * @since 1.2.2 - */ - return ( - ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && - Freemius::is_themes_page() && - fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ) - ); - } - - if ( $this->_menu_exists && - ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) - ) { - /** - * Module has a settings menu and the context page is the main settings page, so assume it's in - * activation (doesn't really check if already opted-in/skipped or not). - * - * @since 1.2.2 - */ - return true; - } - - return false; - } - - #region Submenu Override - - /** - * Override submenu's action. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.0 - * - * @param string $parent_slug - * @param string $menu_slug - * @param callable $function - * - * @return false|string If submenu exist, will return the hook name. - */ - function override_submenu_action( $parent_slug, $menu_slug, $function ) { - global $submenu; - - $menu_slug = plugin_basename( $menu_slug ); - $parent_slug = plugin_basename( $parent_slug ); - - if ( ! isset( $submenu[ $parent_slug ] ) ) { - // Parent menu not exist. - return false; - } - - $found_submenu_item = false; - foreach ( $submenu[ $parent_slug ] as $submenu_item ) { - if ( $menu_slug === $submenu_item[2] ) { - $found_submenu_item = $submenu_item; - break; - } - } - - if ( false === $found_submenu_item ) { - // Submenu item not found. - return false; - } - - // Remove current function. - $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); - remove_all_actions( $hookname ); - - // Attach new action. - add_action( $hookname, $function ); - - return $hookname; - } - - #endregion Submenu Override - - #region Top level menu Override - - /** - * Find plugin's admin dashboard main menu item. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @return string[]|false - */ - private function find_top_level_menu() { - global $menu; - - $position = - 1; - $found_menu = false; - - $menu_slug = $this->get_raw_slug(); - - $hook_name = get_plugin_page_hookname( $menu_slug, '' ); - foreach ( $menu as $pos => $m ) { - if ( $menu_slug === $m[2] ) { - $position = $pos; - $found_menu = $m; - break; - } - } - - if ( false === $found_menu ) { - return false; - } - - return array( - 'menu' => $found_menu, - 'position' => $position, - 'hook_name' => $hook_name - ); - } - - /** - * Find plugin's admin dashboard main submenu item. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @return array|false - */ - private function find_main_submenu() { - global $submenu; - - $top_level_menu_slug = $this->get_top_level_menu_slug(); - - if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { - return false; - } - - $submenu_slug = $this->get_raw_slug(); - - $position = - 1; - $found_submenu = false; - - $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); - - foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { - if ( $submenu_slug === $sub[2] ) { - $position = $pos; - $found_submenu = $sub; - } - } - - if ( false === $found_submenu ) { - return false; - } - - return array( - 'menu' => $found_submenu, - 'parent_slug' => $top_level_menu_slug, - 'position' => $position, - 'hook_name' => $hook_name - ); - } - - /** - * Remove all sub-menu items. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @return bool If submenu with plugin's menu slug was found. - */ - private function remove_all_submenu_items() { - global $submenu; - - $menu_slug = $this->get_raw_slug(); - - if ( ! isset( $submenu[ $menu_slug ] ) ) { - return false; - } - - /** - * This method is NOT executed for WordPress.org themes. - * Since we maintain only one version of the SDK we added this small - * hack to avoid the error from Theme Check since it's a false-positive. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - $submenu_ref = &$submenu; - $submenu_ref[ $menu_slug ] = array(); - - return true; - } - - /** - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param bool $remove_top_level_menu - * - * @return false|array[string]mixed - */ - function remove_menu_item( $remove_top_level_menu = false ) { - $this->_logger->entrance(); - - // Find main menu item. - $top_level_menu = $this->find_top_level_menu(); - - if ( false === $top_level_menu ) { - return false; - } - - // Remove it with its actions. - remove_all_actions( $top_level_menu['hook_name'] ); - - // Remove all submenu items. - $this->remove_all_submenu_items(); - - if ( $remove_top_level_menu ) { - global $menu; - unset( $menu[ $top_level_menu['position'] ] ); - } - - return $top_level_menu; - } - - /** - * Get module's main admin setting page URL. - * - * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @return string - */ - function main_menu_url() { - $this->_logger->entrance(); - - if ( $this->_is_top_level ) { - $menu = $this->find_top_level_menu(); - } else { - $menu = $this->find_main_submenu(); - } - - $parent_slug = isset( $menu['parent_slug'] ) ? - $menu['parent_slug'] : - 'admin.php'; - - return admin_url( - $parent_slug . - ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) . - 'page=' . - $menu['menu'][2] - ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param callable $function - * - * @return false|array[string]mixed - */ - function override_menu_item( $function ) { - $found_menu = $this->remove_menu_item(); - - if ( false === $found_menu ) { - return false; - } - - if ( ! $this->is_top_level() || ! $this->is_cpt() ) { - $menu_slug = plugin_basename( $this->get_slug() ); - - $hookname = get_plugin_page_hookname( $menu_slug, '' ); - - // Override menu action. - add_action( $hookname, $function ); - } else { - global $menu; - - // Remove original CPT menu. - unset( $menu[ $found_menu['position'] ] ); - - // Create new top-level menu action. - $hookname = self::add_page( - $found_menu['menu'][3], - $found_menu['menu'][0], - 'manage_options', - $this->get_slug(), - $function, - $found_menu['menu'][6], - $found_menu['position'] - ); - } - - return $hookname; - } - - /** - * Adds a counter to the module's top level menu item. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param int $counter - * @param string $class - */ - function add_counter_to_menu_item( $counter = 1, $class = '' ) { - global $menu, $submenu; - - $mask = '%s '; - - /** - * This method is NOT executed for WordPress.org themes. - * Since we maintain only one version of the SDK we added this small - * hack to avoid the error from Theme Check since it's a false-positive. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - $menu_ref = &$menu; - $submenu_ref = &$submenu; - - if ( $this->_is_top_level ) { - // Find main menu item. - $found_menu = $this->find_top_level_menu(); - - if ( false !== $found_menu ) { - // Override menu label. - $menu_ref[ $found_menu['position'] ][0] = sprintf( - $mask, - $found_menu['menu'][0], - $class, - $counter - ); - } - } else { - $found_submenu = $this->find_main_submenu(); - - if ( false !== $found_submenu ) { - // Override menu label. - $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( - $mask, - $found_submenu['menu'][0], - $class, - $counter - ); - } - } - } - - #endregion Top level menu Override - - /** - * Add a top-level menu page. - * - * Note for WordPress.org Theme/Plugin reviewer: - * - * This is a replication of `add_menu_page()` to avoid Theme Check warning. - * - * Why? - * ==== - * Freemius is an SDK for plugin and theme developers. Since the core - * of the SDK is relevant both for plugins and themes, for obvious reasons, - * we only develop and maintain one code base. - * - * This method will not run for wp.org themes (only plugins) since theme - * admin settings/options are now only allowed in the customizer. - * - * If you have any questions or need clarifications, please don't hesitate - * pinging me on slack, my username is @svovaf. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - * - * @param string $page_title The text to be displayed in the title tags of the page when the menu is - * selected. - * @param string $menu_title The text to be used for the menu. - * @param string $capability The capability required for this menu to be displayed to the user. - * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). - * @param callable|string $function The function to be called to output the content for this page. - * @param string $icon_url The URL to the icon to be used for this menu. - * * Pass a base64-encoded SVG using a data URI, which will be colored to - * match the color scheme. This should begin with - * 'data:image/svg+xml;base64,'. - * * Pass the name of a Dashicons helper class to use a font icon, - * e.g. 'dashicons-chart-pie'. - * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added - * via CSS. - * @param int $position The position in the menu order this one should appear. - * - * @return string The resulting page's hook_suffix. - */ - static function add_page( - $page_title, - $menu_title, - $capability, - $menu_slug, - $function = '', - $icon_url = '', - $position = null - ) { - $fn = 'add_menu' . '_page'; - - return $fn( - $page_title, - $menu_title, - $capability, - $menu_slug, - $function, - $icon_url, - $position - ); - } - - /** - * Add page and update menu instance settings. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $page_title - * @param string $menu_title - * @param string $capability - * @param string $menu_slug - * @param callable|string $function - * @param string $icon_url - * @param int|null $position - * - * @return string - */ - function add_page_and_update( - $page_title, - $menu_title, - $capability, - $menu_slug, - $function = '', - $icon_url = '', - $position = null - ) { - $this->_menu_slug = $menu_slug; - $this->_is_top_level = true; - $this->_menu_exists = true; - $this->_network_menu_exists = true; - - return self::add_page( - $page_title, - $menu_title, - $capability, - $menu_slug, - $function, - $icon_url, - $position - ); - } - - /** - * Add a submenu page. - * - * Note for WordPress.org Theme/Plugin reviewer: - * - * This is a replication of `add_submenu_page()` to avoid Theme Check warning. - * - * Why? - * ==== - * Freemius is an SDK for plugin and theme developers. Since the core - * of the SDK is relevant both for plugins and themes, for obvious reasons, - * we only develop and maintain one code base. - * - * This method will not run for wp.org themes (only plugins) since theme - * admin settings/options are now only allowed in the customizer. - * - * If you have any questions or need clarifications, please don't hesitate - * pinging me on slack, my username is @svovaf. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2 - * - * @param string $parent_slug The slug name for the parent menu (or the file name of a standard - * WordPress admin page). - * @param string $page_title The text to be displayed in the title tags of the page when the menu is - * selected. - * @param string $menu_title The text to be used for the menu. - * @param string $capability The capability required for this menu to be displayed to the user. - * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). - * @param callable|string $function The function to be called to output the content for this page. - * - * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability - * required. - */ - static function add_subpage( - $parent_slug, - $page_title, - $menu_title, - $capability, - $menu_slug, - $function = '' - ) { - $fn = 'add_submenu' . '_page'; - - return $fn( $parent_slug, - $page_title, - $menu_title, - $capability, - $menu_slug, - $function - ); - } - - /** - * Add sub page and update menu instance settings. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $parent_slug - * @param string $page_title - * @param string $menu_title - * @param string $capability - * @param string $menu_slug - * @param callable|string $function - * - * @return string - */ - function add_subpage_and_update( - $parent_slug, - $page_title, - $menu_title, - $capability, - $menu_slug, - $function = '' - ) { - $this->_menu_slug = $menu_slug; - $this->_parent_slug = $parent_slug; - $this->_is_top_level = false; - $this->_menu_exists = true; - $this->_network_menu_exists = true; - - return self::add_subpage( - $parent_slug, - $page_title, - $menu_title, - $capability, - $menu_slug, - $function - ); - } - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-admin-notice-manager.php b/freemius/includes/managers/class-fs-admin-notice-manager.php index cc9d7f9..e69de29 100644 --- a/freemius/includes/managers/class-fs-admin-notice-manager.php +++ b/freemius/includes/managers/class-fs-admin-notice-manager.php @@ -1,472 +0,0 @@ - 0 ) { - $key .= ":{$network_level_or_blog_id}"; - } else { - $network_level_or_blog_id = get_current_blog_id(); - - $key .= ":{$network_level_or_blog_id}"; - } - } - - if ( ! isset( self::$_instances[ $key ] ) ) { - self::$_instances[ $key ] = new FS_Admin_Notice_Manager( - $id, - $title, - $module_unique_affix, - $is_network_and_blog_admins, - $network_level_or_blog_id - ); - } - - return self::$_instances[ $key ]; - } - - /** - * @param string $id - * @param string $title - * @param string $module_unique_affix - * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and - * blog admin pages. - * @param bool|int $network_level_or_blog_id - */ - protected function __construct( - $id, - $title = '', - $module_unique_affix = '', - $is_network_and_blog_admins = false, - $network_level_or_blog_id = false - ) { - $this->_id = $id; - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - $this->_title = ! empty( $title ) ? $title : ''; - $this->_module_unique_affix = $module_unique_affix; - $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); - - if ( is_multisite() ) { - $this->_is_network_notices = ( true === $network_level_or_blog_id ); - - if ( is_numeric( $network_level_or_blog_id ) ) { - $this->_blog_id = $network_level_or_blog_id; - } - } else { - $this->_is_network_notices = false; - } - - $is_network_admin = fs_is_network_admin(); - $is_blog_admin = fs_is_blog_admin(); - - if ( ( $this->_is_network_notices && $is_network_admin ) || - ( ! $this->_is_network_notices && $is_blog_admin ) || - ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) - ) { - if ( 0 < count( $this->_sticky_storage ) ) { - $ajax_action_suffix = str_replace( ':', '-', $this->_id ); - - // If there are sticky notices for the current slug, add a callback - // to the AJAX action that handles message dismiss. - add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( - &$this, - 'dismiss_notice_ajax_callback' - ) ); - - foreach ( $this->_sticky_storage as $msg ) { - // Add admin notice. - $this->add( - $msg['message'], - $msg['title'], - $msg['type'], - true, - $msg['id'], - false, - isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, - ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, - $is_network_and_blog_admins - ); - } - } - } - } - - /** - * Remove sticky message by ID. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - */ - function dismiss_notice_ajax_callback() { - $this->_sticky_storage->remove( $_POST['message_id'] ); - wp_die(); - } - - /** - * Rendered sticky message dismiss JavaScript. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - static function _add_sticky_dismiss_javascript() { - $params = array(); - fs_require_once_template( 'sticky-admin-notice-js.php', $params ); - } - - private static $_added_sticky_javascript = false; - - /** - * Hook to the admin_footer to add sticky message dismiss JavaScript handler. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - private static function has_sticky_messages() { - if ( ! self::$_added_sticky_javascript ) { - add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); - } - } - - /** - * Handle admin_notices by printing the admin messages stacked in the queue. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - */ - function _admin_notices_hook() { - if ( function_exists( 'current_user_can' ) && - ! current_user_can( 'manage_options' ) - ) { - // Only show messages to admins. - return; - } - - - $show_admin_notices = ( ! $this->is_gutenberg_page() ); - - foreach ( $this->_notices as $id => $msg ) { - if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { - if ( get_current_user_id() != $msg['wp_user_id'] ) { - continue; - } - } - - /** - * Added a filter to control the visibility of admin notices. - * - * Usage example: - * - * /** - * * @param bool $show - * * @param array $msg { - * * @var string $message The actual message. - * * @var string $title An optional message title. - * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). - * * @var string $id The unique identifier of the message. - * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. - * * @var string $plugin The product's title. - * * @var string $wp_user_id An optional WP user ID that this admin notice is for. - * * } - * * - * * @return bool - * *\/ - * function my_custom_show_admin_notice( $show, $msg ) { - * if ('trial_promotion' != $msg['id']) { - * return false; - * } - * - * return $show; - * } - * - * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); - * - * @author Vova Feldman - * @since 2.2.0 - */ - $show_notice = call_user_func_array( 'fs_apply_filter', array( - $this->_module_unique_affix, - 'show_admin_notice', - $show_admin_notices, - $msg - ) ); - - if ( true !== $show_notice ) { - continue; - } - - fs_require_template( 'admin-notice.php', $msg ); - - if ( $msg['sticky'] ) { - self::has_sticky_messages(); - } - } - } - - /** - * Enqueue common stylesheet to style admin notice. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function _enqueue_styles() { - fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); - } - - /** - * Check if the current page is the Gutenberg block editor. - * - * @author Vova Feldman (@svovaf) - * @since 2.2.3 - * - * @return bool - */ - function is_gutenberg_page() { - if ( function_exists( 'is_gutenberg_page' ) && - is_gutenberg_page() - ) { - // The Gutenberg plugin is on. - return true; - } - - $current_screen = get_current_screen(); - - if ( method_exists( $current_screen, 'is_block_editor' ) && - $current_screen->is_block_editor() - ) { - // Gutenberg page on 5+. - return true; - } - - return false; - } - - /** - * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.4 - * - * @param string $message - * @param string $title - * @param string $type - * @param bool $is_sticky - * @param string $id Message ID - * @param bool $store_if_sticky - * @param number|null $wp_user_id - * @param string|null $plugin_title - * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network - * and blog admin pages. - * - * @uses add_action() - */ - function add( - $message, - $title = '', - $type = 'success', - $is_sticky = false, - $id = '', - $store_if_sticky = true, - $wp_user_id = null, - $plugin_title = null, - $is_network_and_blog_admins = false - ) { - $notices_type = $this->get_notices_type(); - - if ( empty( $this->_notices ) ) { - if ( ! $is_network_and_blog_admins ) { - add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); - } else { - add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); - add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); - } - - add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); - } - - if ( '' === $id ) { - $id = md5( $title . ' ' . $message . ' ' . $type ); - } - - $message_object = array( - 'message' => $message, - 'title' => $title, - 'type' => $type, - 'sticky' => $is_sticky, - 'id' => $id, - 'manager_id' => $this->_id, - 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), - 'wp_user_id' => $wp_user_id, - ); - - if ( $is_sticky && $store_if_sticky ) { - $this->_sticky_storage->{$id} = $message_object; - } - - $this->_notices[ $id ] = $message_object; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string|string[] $ids - */ - function remove_sticky( $ids ) { - if ( ! is_array( $ids ) ) { - $ids = array( $ids ); - } - - foreach ( $ids as $id ) { - // Remove from sticky storage. - $this->_sticky_storage->remove( $id ); - - if ( isset( $this->_notices[ $id ] ) ) { - unset( $this->_notices[ $id ] ); - } - } - } - - /** - * Check if sticky message exists by id. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param $id - * - * @return bool - */ - function has_sticky( $id ) { - return isset( $this->_sticky_storage[ $id ] ); - } - - /** - * Adds sticky admin notification. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $message - * @param string $id Message ID - * @param string $title - * @param string $type - * @param number|null $wp_user_id - * @param string|null $plugin_title - * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network - * and blog admin pages. - */ - function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false ) { - if ( ! empty( $this->_module_unique_affix ) ) { - $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); - $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); - } - - $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); - } - - /** - * Clear all sticky messages. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.8 - */ - function clear_all_sticky() { - $this->_sticky_storage->clear_all(); - } - - #-------------------------------------------------------------------------------- - #region Helper Method - #-------------------------------------------------------------------------------- - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - private function get_notices_type() { - return $this->_is_network_notices ? - 'network_admin_notices' : - 'admin_notices'; - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-cache-manager.php b/freemius/includes/managers/class-fs-cache-manager.php index 7f2d850..e69de29 100644 --- a/freemius/includes/managers/class-fs-cache-manager.php +++ b/freemius/includes/managers/class-fs-cache-manager.php @@ -1,326 +0,0 @@ -_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->_logger->entrance(); - $this->_logger->log( 'id = ' . $id ); - - $this->_options = FS_Option_Manager::get_manager( $id, true, true, false ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param $id - * - * @return FS_Cache_Manager - */ - static function get_manager( $id ) { - $id = strtolower( $id ); - - if ( ! isset( self::$_MANAGERS[ $id ] ) ) { - self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); - } - - return self::$_MANAGERS[ $id ]; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @return bool - */ - function is_empty() { - $this->_logger->entrance(); - - return $this->_options->is_empty(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - */ - function clear() { - $this->_logger->entrance(); - - $this->_options->clear( true ); - } - - /** - * Delete cache manager from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function delete() { - $this->_options->delete(); - } - - /** - * Check if there's a cached item. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - * - * @return bool - */ - function has( $key ) { - $cache_entry = $this->_options->get_option( $key, false ); - - return ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) - ); - } - - /** - * Check if there's a valid cached item. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - * @param null|int $expiration Since 1.2.2.7 - * - * @return bool - */ - function has_valid( $key, $expiration = null ) { - $cache_entry = $this->_options->get_option( $key, false ); - - $is_valid = ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) && - $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME - ); - - if ( $is_valid && - is_numeric( $expiration ) && - isset( $cache_entry->created ) && - is_numeric( $cache_entry->created ) && - $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME - ) { - /** - * Even if the cache is still valid, since we are checking for validity - * with an explicit expiration period, if the period has past, return - * `false` as if the cache is invalid. - * - * @since 1.2.2.7 - */ - $is_valid = false; - } - - return $is_valid; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - * @param mixed $default - * - * @return mixed - */ - function get( $key, $default = null ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = $this->_options->get_option( $key, false ); - - if ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) - ) { - return $cache_entry->result; - } - - return is_object( $default ) ? clone $default : $default; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - * @param mixed $default - * - * @return mixed - */ - function get_valid( $key, $default = null ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = $this->_options->get_option( $key, false ); - - if ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) && - $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME - ) { - return $cache_entry->result; - } - - return is_object( $default ) ? clone $default : $default; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - * @param mixed $value - * @param int $expiration - * @param int $created Since 2.0.0 Cache creation date. - */ - function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = new stdClass(); - - $cache_entry->result = $value; - $cache_entry->created = $created; - $cache_entry->timestamp = $created + $expiration; - $this->_options->set_option( $key, $cache_entry, true ); - } - - /** - * Get cached record expiration, or false if not cached or expired. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.3 - * - * @param string $key - * - * @return bool|int - */ - function get_record_expiration( $key ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = $this->_options->get_option( $key, false ); - - if ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) && - $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME - ) { - return $cache_entry->timestamp; - } - - return false; - } - - /** - * Purge cached item. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.6 - * - * @param string $key - */ - function purge( $key ) { - $this->_logger->entrance( 'key = ' . $key ); - - $this->_options->unset_option( $key, true ); - } - - /** - * Extend cached item caching period. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @param string $key - * @param int $expiration - * - * @return bool - */ - function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = $this->_options->get_option( $key, false ); - - if ( ! is_object( $cache_entry ) || - ! isset( $cache_entry->timestamp ) || - ! is_numeric( $cache_entry->timestamp ) - ) { - return false; - } - - $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); - - return true; - } - - /** - * Set cached item as expired. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - * - * @param string $key - */ - function expire( $key ) { - $this->_logger->entrance( 'key = ' . $key ); - - $cache_entry = $this->_options->get_option( $key, false ); - - if ( is_object( $cache_entry ) && - isset( $cache_entry->timestamp ) && - is_numeric( $cache_entry->timestamp ) - ) { - // Set to expired. - $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; - $this->_options->set_option( $key, $cache_entry, true ); - } - } - - #-------------------------------------------------------------------------------- - #region Migration - #-------------------------------------------------------------------------------- - - /** - * Migrate options from site level. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function migrate_to_network() { - $this->_options->migrate_to_network(); - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-gdpr-manager.php b/freemius/includes/managers/class-fs-gdpr-manager.php index a64abb0..e69de29 100644 --- a/freemius/includes/managers/class-fs-gdpr-manager.php +++ b/freemius/includes/managers/class-fs-gdpr-manager.php @@ -1,202 +0,0 @@ -_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); - $this->_wp_user_id = Freemius::get_current_wp_user_id(); - $this->_option_name = "u{$this->_wp_user_id}"; - $this->_data = $this->_storage->get_option( $this->_option_name, array() ); - $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); - - if ( ! is_array( $this->_data ) ) { - $this->_data = array(); - } - } - - /** - * Update a GDPR option for the current admin and store it. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @param string $name - * @param mixed $value - */ - private function update_option( $name, $value ) { - $this->_data[ $name ] = $value; - - $this->_storage->set_option( $this->_option_name, $this->_data, true ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @return bool|null - */ - public function is_required() { - return isset( $this->_data['required'] ) ? - $this->_data['required'] : - null; - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @param bool $is_required - */ - public function store_is_required( $is_required ) { - $this->update_option( 'required', $is_required ); - } - - /** - * Checks if the GDPR opt-in sticky notice is currently shown. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return bool - */ - public function is_opt_in_notice_shown() { - return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); - } - - /** - * Remove the GDPR opt-in sticky notice. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - */ - public function remove_opt_in_notice() { - $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); - - $this->disable_opt_in_notice(); - } - - /** - * Prevents the opt-in message from being added/shown. - * - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - */ - public function disable_opt_in_notice() { - $this->update_option( 'show_opt_in_notice', false ); - } - - /** - * Checks if a GDPR opt-in message needs to be shown to the current admin. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return bool - */ - public function should_show_opt_in_notice() { - return ( - ! isset( $this->_data['show_opt_in_notice'] ) || - true === $this->_data['show_opt_in_notice'] - ); - } - - /** - * Get the last time the GDPR opt-in notice was shown. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return false|int - */ - public function last_time_notice_was_shown() { - return isset( $this->_data['notice_shown_at'] ) ? - $this->_data['notice_shown_at'] : - false; - } - - /** - * Update the timestamp of the last time the GDPR opt-in message was shown to now. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - */ - public function notice_was_just_shown() { - $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); - } - - /** - * @param string $message - * @param string|null $plugin_title - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - */ - public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { - $this->_notices->add_sticky( - $message, - "gdpr_optin_actions_{$this->_wp_user_id}", - '', - 'promotion', - true, - $this->_wp_user_id, - $plugin_title, - true - ); - } - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-key-value-storage.php b/freemius/includes/managers/class-fs-key-value-storage.php index 713df6f..e69de29 100644 --- a/freemius/includes/managers/class-fs-key-value-storage.php +++ b/freemius/includes/managers/class-fs-key-value-storage.php @@ -1,392 +0,0 @@ - 0 ) { - $key .= ":{$network_level_or_blog_id}"; - } else { - $network_level_or_blog_id = get_current_blog_id(); - - $key .= ":{$network_level_or_blog_id}"; - } - } - - if ( ! isset( self::$_instances[ $key ] ) ) { - self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); - } - - return self::$_instances[ $key ]; - } - - protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { - $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->_id = $id; - $this->_secondary_id = $secondary_id; - - if ( is_multisite() ) { - $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); - - if ( is_numeric( $network_level_or_blog_id ) ) { - $this->_blog_id = $network_level_or_blog_id; - } - } else { - $this->_is_multisite_storage = false; - } - - $this->load(); - } - - protected function get_option_manager() { - return FS_Option_Manager::get_manager( - WP_FS__ACCOUNTS_OPTION_NAME, - true, - $this->_is_multisite_storage ? - true : - ( $this->_blog_id > 0 ? $this->_blog_id : false ) - ); - } - - protected function get_all_data() { - return $this->get_option_manager()->get_option( $this->_id, array() ); - } - - /** - * Load plugin data from local DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - */ - function load() { - $all_plugins_data = $this->get_all_data(); - $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? - $all_plugins_data[ $this->_secondary_id ] : - array(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $key - * @param mixed $value - * @param bool $flush - */ - function store( $key, $value, $flush = true ) { - if ( $this->_logger->is_on() ) { - $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); - } - - if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { - // No need to store data if the value wasn't changed. - return; - } - - $all_data = $this->get_all_data(); - - $this->_data[ $key ] = $value; - - $all_data[ $this->_secondary_id ] = $this->_data; - - $options_manager = $this->get_option_manager(); - $options_manager->set_option( $this->_id, $all_data, $flush ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function save() { - $this->get_option_manager()->store(); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param bool $store - * @param string[] $exceptions Set of keys to keep and not clear. - */ - function clear_all( $store = true, $exceptions = array() ) { - $new_data = array(); - foreach ( $exceptions as $key ) { - if ( isset( $this->_data[ $key ] ) ) { - $new_data[ $key ] = $this->_data[ $key ]; - } - } - - $this->_data = $new_data; - - if ( $store ) { - $all_data = $this->get_all_data(); - $all_data[ $this->_secondary_id ] = $this->_data; - $options_manager = $this->get_option_manager(); - $options_manager->set_option( $this->_id, $all_data, true ); - } - } - - /** - * Delete key-value storage. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function delete() { - $this->_data = array(); - - $all_data = $this->get_all_data(); - unset( $all_data[ $this->_secondary_id ] ); - $options_manager = $this->get_option_manager(); - $options_manager->set_option( $this->_id, $all_data, true ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $key - * @param bool $store - */ - function remove( $key, $store = true ) { - if ( ! array_key_exists( $key, $this->_data ) ) { - return; - } - - unset( $this->_data[ $key ] ); - - if ( $store ) { - $all_data = $this->get_all_data(); - $all_data[ $this->_secondary_id ] = $this->_data; - $options_manager = $this->get_option_manager(); - $options_manager->set_option( $this->_id, $all_data, true ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param string $key - * @param mixed $default - * - * @return bool|\FS_Plugin - */ - function get( $key, $default = false ) { - return array_key_exists( $key, $this->_data ) ? - $this->_data[ $key ] : - $default; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - function get_secondary_id() { - return $this->_secondary_id; - } - - - /* ArrayAccess + Magic Access (better for refactoring) - -----------------------------------------------------------------------------------*/ - function __set( $k, $v ) { - $this->store( $k, $v ); - } - - function __isset( $k ) { - return array_key_exists( $k, $this->_data ); - } - - function __unset( $k ) { - $this->remove( $k ); - } - - function __get( $k ) { - return $this->get( $k, null ); - } - - function offsetSet( $k, $v ) { - if ( is_null( $k ) ) { - throw new Exception( 'Can\'t append value to request params.' ); - } else { - $this->{$k} = $v; - } - } - - function offsetExists( $k ) { - return array_key_exists( $k, $this->_data ); - } - - function offsetUnset( $k ) { - unset( $this->$k ); - } - - function offsetGet( $k ) { - return $this->get( $k, null ); - } - - /** - * (PHP 5 >= 5.0.0)
    - * Return the current element - * - * @link http://php.net/manual/en/iterator.current.php - * @return mixed Can return any type. - */ - public function current() { - return current( $this->_data ); - } - - /** - * (PHP 5 >= 5.0.0)
    - * Move forward to next element - * - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. - */ - public function next() { - next( $this->_data ); - } - - /** - * (PHP 5 >= 5.0.0)
    - * Return the key of the current element - * - * @link http://php.net/manual/en/iterator.key.php - * @return mixed scalar on success, or null on failure. - */ - public function key() { - return key( $this->_data ); - } - - /** - * (PHP 5 >= 5.0.0)
    - * Checks if current position is valid - * - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean The return value will be casted to boolean and then evaluated. - * Returns true on success or false on failure. - */ - public function valid() { - $key = key( $this->_data ); - - return ( $key !== null && $key !== false ); - } - - /** - * (PHP 5 >= 5.0.0)
    - * Rewind the Iterator to the first element - * - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. - */ - public function rewind() { - reset( $this->_data ); - } - - /** - * (PHP 5 >= 5.1.0)
    - * Count elements of an object - * - * @link http://php.net/manual/en/countable.count.php - * @return int The custom count as an integer. - *

    - *

    - * The return value is cast to an integer. - */ - public function count() { - return count( $this->_data ); - } - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-license-manager.php b/freemius/includes/managers/class-fs-license-manager.php index 891ecd8..e69de29 100644 --- a/freemius/includes/managers/class-fs-license-manager.php +++ b/freemius/includes/managers/class-fs-license-manager.php @@ -1,104 +0,0 @@ -get_slug() ); -// -// if ( ! isset( self::$_instances[ $slug ] ) ) { -// self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); -// } -// -// return self::$_instances[ $slug ]; -// } -// -//// private function __construct($slug) { -//// parent::__construct($slug); -//// } -// -// function entry_id() { -// return 'licenses'; -// } -// -// function sync( $id ) { -// -// } -// -// /** -// * @author Vova Feldman (@svovaf) -// * @since 1.0.5 -// * @uses FS_Api -// * -// * @param number|bool $plugin_id -// * -// * @return FS_Plugin_License[]|stdClass Licenses or API error. -// */ -// function api_get_user_plugin_licenses( $plugin_id = false ) { -// $api = $this->_fs->get_api_user_scope(); -// -// if ( ! is_numeric( $plugin_id ) ) { -// $plugin_id = $this->_fs->get_id(); -// } -// -// $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); -// -// if ( ! isset( $result->error ) ) { -// for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { -// $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); -// } -// -// $result = $result->licenses; -// } -// -// return $result; -// } -// -// function api_get_many() { -// -// } -// -// function api_activate( $id ) { -// -// } -// -// function api_deactivate( $id ) { -// -// } - - /** - * @param FS_Plugin_License[] $licenses - * - * @return bool - */ - static function has_premium_license( $licenses ) { - if ( is_array( $licenses ) ) { - foreach ( $licenses as $license ) { - /** - * @var FS_Plugin_License $license - */ - if ( ! $license->is_utilized() && $license->is_features_enabled() ) { - return true; - } - } - } - - return false; - } - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-option-manager.php b/freemius/includes/managers/class-fs-option-manager.php index 9d59abf..e69de29 100644 --- a/freemius/includes/managers/class-fs-option-manager.php +++ b/freemius/includes/managers/class-fs-option-manager.php @@ -1,521 +0,0 @@ -_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - - $this->_logger->entrance(); - $this->_logger->log( 'id = ' . $id ); - - $this->_id = $id; - - $this->_autoload = $autoload; - - if ( is_multisite() ) { - $this->_is_network_storage = ( true === $network_level_or_blog_id ); - - if ( is_numeric( $network_level_or_blog_id ) ) { - $this->_blog_id = $network_level_or_blog_id; - } - } else { - $this->_is_network_storage = false; - } - - if ( $load ) { - $this->load(); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param string $id - * @param bool $load - * @param bool|int $network_level_or_blog_id Since 2.0.0 - * @param bool|null $autoload - * - * @return \FS_Option_Manager - */ - static function get_manager( - $id, - $load = false, - $network_level_or_blog_id = false, - $autoload = null - ) { - $key = strtolower( $id ); - - if ( is_multisite() ) { - if ( true === $network_level_or_blog_id ) { - $key .= ':ms'; - } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { - $key .= ":{$network_level_or_blog_id}"; - } else { - $network_level_or_blog_id = get_current_blog_id(); - - $key .= ":{$network_level_or_blog_id}"; - } - } - - if ( ! isset( self::$_MANAGERS[ $key ] ) ) { - self::$_MANAGERS[ $key ] = new FS_Option_Manager( - $id, - $load, - $network_level_or_blog_id, - $autoload - ); - } // If load required but not yet loaded, load. - else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { - self::$_MANAGERS[ $key ]->load(); - } - - return self::$_MANAGERS[ $key ]; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param bool $flush - */ - function load( $flush = false ) { - $this->_logger->entrance(); - - $option_name = $this->get_option_manager_name(); - - if ( $flush || ! isset( $this->_options ) ) { - if ( isset( $this->_options ) ) { - // Clear prev options. - $this->clear(); - } - - $cache_group = $this->get_cache_group(); - - if ( WP_FS__DEBUG_SDK ) { - - // Don't use cache layer in DEBUG mode. - $load_options = empty( $this->_options ); - - } else { - - $this->_options = wp_cache_get( - $option_name, - $cache_group - ); - - $load_options = ( false === $this->_options ); - } - - $cached = true; - - if ( $load_options ) { - if ( $this->_is_network_storage ) { - $this->_options = get_site_option( $option_name ); - } else if ( $this->_blog_id > 0 ) { - $this->_options = get_blog_option( $this->_blog_id, $option_name ); - } else { - $this->_options = get_option( $option_name ); - } - - if ( is_string( $this->_options ) ) { - $this->_options = json_decode( $this->_options ); - } - -// $this->_logger->info('get_option = ' . var_export($this->_options, true)); - - if ( false === $this->_options ) { - $this->clear(); - } - - $cached = false; - } - - if ( ! WP_FS__DEBUG_SDK && ! $cached ) { - // Set non encoded cache. - wp_cache_set( $option_name, $this->_options, $cache_group ); - } - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return bool - */ - function is_loaded() { - return isset( $this->_options ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return bool - */ - function is_empty() { - return ( $this->is_loaded() && false === $this->_options ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool $flush - */ - function clear( $flush = false ) { - $this->_logger->entrance(); - - $this->_options = array(); - - if ( $flush ) { - $this->store(); - } - } - - /** - * Delete options manager from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function delete() { - $option_name = $this->get_option_manager_name(); - - if ( $this->_is_network_storage ) { - delete_site_option( $option_name ); - } else if ( $this->_blog_id > 0 ) { - delete_blog_option( $this->_blog_id, $option_name ); - } else { - delete_option( $option_name ); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param string $option - * - * @return bool - */ - function has_option( $option ) { - return array_key_exists( $option, $this->_options ); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param string $option - * @param mixed $default - * - * @return mixed - */ - function get_option( $option, $default = null ) { - $this->_logger->entrance( 'option = ' . $option ); - - if ( ! $this->is_loaded() ) { - $this->load(); - } - - if ( is_array( $this->_options ) ) { - $value = isset( $this->_options[ $option ] ) ? - $this->_options[ $option ] : - $default; - } else if ( is_object( $this->_options ) ) { - $value = isset( $this->_options->{$option} ) ? - $this->_options->{$option} : - $default; - } else { - $value = $default; - } - - /** - * If it's an object, return a clone of the object, otherwise, - * external changes of the object will actually change the value - * of the object in the options manager which may lead to an unexpected - * behaviour and data integrity when a store() call is triggered. - * - * Example: - * $object1 = $options->get_option( 'object1' ); - * $object1->x = 123; - * - * $object2 = $options->get_option( 'object2' ); - * $object2->y = 'dummy'; - * - * $options->set_option( 'object2', $object2, true ); - * - * If we don't return a clone of option 'object1', setting 'object2' - * will also store the updated value of 'object1' which is quite not - * an expected behaviour. - * - * @author Vova Feldman - */ - return is_object( $value ) ? clone $value : $value; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param string $option - * @param mixed $value - * @param bool $flush - */ - function set_option( $option, $value, $flush = false ) { - $this->_logger->entrance( 'option = ' . $option ); - - if ( ! $this->is_loaded() ) { - $this->clear(); - } - - /** - * If it's an object, store a clone of the object, otherwise, - * external changes of the object will actually change the value - * of the object in the options manager which may lead to an unexpected - * behaviour and data integrity when a store() call is triggered. - * - * Example: - * $object1 = new stdClass(); - * $object1->x = 123; - * - * $options->set_option( 'object1', $object1 ); - * - * $object1->x = 456; - * - * $options->set_option( 'object2', $object2, true ); - * - * If we don't set the option as a clone of option 'object1', setting 'object2' - * will also store the updated value of 'object1' ($object1->x = 456 instead of - * $object1->x = 123) which is quite not an expected behaviour. - * - * @author Vova Feldman - */ - $copy = is_object( $value ) ? clone $value : $value; - - if ( is_array( $this->_options ) ) { - $this->_options[ $option ] = $copy; - } else if ( is_object( $this->_options ) ) { - $this->_options->{$option} = $copy; - } - - if ( $flush ) { - $this->store(); - } - } - - /** - * Unset option. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @param string $option - * @param bool $flush - */ - function unset_option( $option, $flush = false ) { - $this->_logger->entrance( 'option = ' . $option ); - - if ( is_array( $this->_options ) ) { - if ( ! isset( $this->_options[ $option ] ) ) { - return; - } - - unset( $this->_options[ $option ] ); - - } else if ( is_object( $this->_options ) ) { - if ( ! isset( $this->_options->{$option} ) ) { - return; - } - - unset( $this->_options->{$option} ); - } - - if ( $flush ) { - $this->store(); - } - } - - /** - * Dump options to database. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - */ - function store() { - $this->_logger->entrance(); - - $option_name = $this->get_option_manager_name(); - - if ( $this->_logger->is_on() ) { - $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); - } - - // Update DB. - if ( $this->_is_network_storage ) { - update_site_option( $option_name, $this->_options ); - } else if ( $this->_blog_id > 0 ) { - update_blog_option( $this->_blog_id, $option_name, $this->_options ); - } else { - update_option( $option_name, $this->_options, $this->_autoload ); - } - - if ( ! WP_FS__DEBUG_SDK ) { - wp_cache_set( $option_name, $this->_options, $this->get_cache_group() ); - } - } - - /** - * Get options keys. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.3 - * - * @return string[] - */ - function get_options_keys() { - if ( is_array( $this->_options ) ) { - return array_keys( $this->_options ); - } else if ( is_object( $this->_options ) ) { - return array_keys( get_object_vars( $this->_options ) ); - } - - return array(); - } - - #-------------------------------------------------------------------------------- - #region Migration - #-------------------------------------------------------------------------------- - - /** - * Migrate options from site level. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - function migrate_to_network() { - $site_options = FS_Option_Manager::get_manager($this->_id, true, false); - - $options = is_object( $site_options->_options ) ? - get_object_vars( $site_options->_options ) : - $site_options->_options; - - if ( ! empty( $options ) ) { - foreach ( $options as $key => $val ) { - $this->set_option( $key, $val, false ); - } - - $this->store(); - } - } - - #endregion - - #-------------------------------------------------------------------------------- - #region Helper Methods - #-------------------------------------------------------------------------------- - - /** - * @return string - */ - private function get_option_manager_name() { - return $this->_id; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - * - * @return string - */ - private function get_cache_group() { - $group = WP_FS__SLUG; - - if ( $this->_is_network_storage ) { - $group .= '_ms'; - } else if ( $this->_blog_id > 0 ) { - $group .= "_s{$this->_blog_id}"; - } - - return $group; - } - - #endregion - } diff --git a/freemius/includes/managers/class-fs-plan-manager.php b/freemius/includes/managers/class-fs-plan-manager.php index 639de43..e69de29 100644 --- a/freemius/includes/managers/class-fs-plan-manager.php +++ b/freemius/includes/managers/class-fs-plan-manager.php @@ -1,162 +0,0 @@ -is_utilized() && $license->is_features_enabled() ) { - return true; - } - } - } - - return false; - } - - /** - * Check if plugin has any paid plans. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param FS_Plugin_Plan[] $plans - * - * @return bool - */ - function has_paid_plan( $plans ) { - if ( ! is_array( $plans ) || 0 === count( $plans ) ) { - return false; - } - - /** - * @var FS_Plugin_Plan[] $plans - */ - for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { - if ( ! $plans[ $i ]->is_free() ) { - return true; - } - } - - return false; - } - - /** - * Check if plugin has any free plan, or is it premium only. - * - * Note: If no plans configured, assume plugin is free. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.7 - * - * @param FS_Plugin_Plan[] $plans - * - * @return bool - */ - function has_free_plan( $plans ) { - if ( ! is_array( $plans ) || 0 === count( $plans ) ) { - return true; - } - - /** - * @var FS_Plugin_Plan[] $plans - */ - for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { - if ( $plans[ $i ]->is_free() ) { - return true; - } - } - - return false; - } - - /** - * Find all plans that have trial. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param FS_Plugin_Plan[] $plans - * - * @return FS_Plugin_Plan[] - */ - function get_trial_plans( $plans ) { - $trial_plans = array(); - - if ( is_array( $plans ) && 0 < count( $plans ) ) { - /** - * @var FS_Plugin_Plan[] $plans - */ - for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { - if ( $plans[ $i ]->has_trial() ) { - $trial_plans[] = $plans[ $i ]; - } - } - } - - return $trial_plans; - } - - /** - * Check if plugin has any trial plan. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param FS_Plugin_Plan[] $plans - * - * @return bool - */ - function has_trial_plan( $plans ) { - if ( ! is_array( $plans ) || 0 === count( $plans ) ) { - return true; - } - - /** - * @var FS_Plugin_Plan[] $plans - */ - for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { - if ( $plans[ $i ]->has_trial() ) { - return true; - } - } - - return false; - } - } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-plugin-manager.php b/freemius/includes/managers/class-fs-plugin-manager.php index bacf160..e69de29 100644 --- a/freemius/includes/managers/class-fs-plugin-manager.php +++ b/freemius/includes/managers/class-fs-plugin-manager.php @@ -1,220 +0,0 @@ -_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - $this->_module_id = $module_id; - - $this->load(); - } - - protected function get_option_manager() { - return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - * - * @param string|bool $module_type "plugin", "theme", or "false" for all modules. - * - * @return array - */ - protected function get_all_modules( $module_type = false ) { - $option_manager = $this->get_option_manager(); - - if ( false !== $module_type ) { - return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() ); - } - - return array( - self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ), - self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ), - ); - } - - /** - * Load plugin data from local DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - */ - function load() { - $all_modules = $this->get_all_modules(); - - if ( ! is_numeric( $this->_module_id ) ) { - unset( $all_modules[ self::OPTION_NAME_THEMES ] ); - } - - foreach ( $all_modules as $modules ) { - /** - * @since 1.2.2 - * - * @var $modules FS_Plugin[] - */ - foreach ( $modules as $module ) { - $found_module = false; - - /** - * If module ID is not numeric, it must be a plugin's slug. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.2 - */ - if ( ! is_numeric( $this->_module_id ) ) { - if ( $this->_module_id === $module->slug ) { - $this->_module_id = $module->id; - $found_module = true; - } - } else if ( $this->_module_id == $module->id ) { - $found_module = true; - } - - if ( $found_module ) { - $this->_module = $module; - break; - } - } - } - } - - /** - * Store plugin on local DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool|FS_Plugin $module - * @param bool $flush - * - * @return bool|\FS_Plugin - */ - function store( $module = false, $flush = true ) { - if ( false !== $module ) { - $this->_module = $module; - } - - $all_modules = $this->get_all_modules( $this->_module->type ); - $all_modules[ $this->_module->slug ] = $this->_module; - - $options_manager = $this->get_option_manager(); - $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); - - return $this->_module; - } - - /** - * Update local plugin data if different. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param \FS_Plugin $plugin - * @param bool $store - * - * @return bool True if plugin was updated. - */ - function update( FS_Plugin $plugin, $store = true ) { - if ( ! ($this->_module instanceof FS_Plugin ) || - $this->_module->slug != $plugin->slug || - $this->_module->public_key != $plugin->public_key || - $this->_module->secret_key != $plugin->secret_key || - $this->_module->parent_plugin_id != $plugin->parent_plugin_id || - $this->_module->title != $plugin->title - ) { - $this->store( $plugin, $store ); - - return true; - } - - return false; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param FS_Plugin $plugin - * @param bool $store - */ - function set( FS_Plugin $plugin, $store = false ) { - $this->_module = $plugin; - - if ( $store ) { - $this->store(); - } - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @return bool|\FS_Plugin - */ - function get() { - return isset( $this->_module ) ? - $this->_module : - false; - } - - - } \ No newline at end of file diff --git a/freemius/includes/managers/index.php b/freemius/includes/managers/index.php index 0316c6a..e69de29 100644 --- a/freemius/includes/managers/index.php +++ b/freemius/includes/managers/index.php @@ -1,3 +0,0 @@ -_result = $result; - - $code = 0; - $message = 'Unknown error, please check GetResult().'; - $type = ''; - - if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { - if ( isset( $result['error']['code'] ) ) { - $code = $result['error']['code']; - } - if ( isset( $result['error']['message'] ) ) { - $message = $result['error']['message']; - } - if ( isset( $result['error']['type'] ) ) { - $type = $result['error']['type']; - } - } - - $this->_type = $type; - $this->_code = $code; - - parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); - } - - /** - * Return the associated result object returned by the API server. - * - * @return array The result from the API server - */ - public function getResult() { - return $this->_result; - } - - public function getStringCode() { - return $this->_code; - } - - public function getType() { - return $this->_type; - } - - /** - * To make debugging easier. - * - * @return string The string representation of the error - */ - public function __toString() { - $str = $this->getType() . ': '; - - if ( $this->code != 0 ) { - $str .= $this->getStringCode() . ': '; - } - - return $str . $this->getMessage(); - } - } - } \ No newline at end of file diff --git a/freemius/includes/sdk/Exceptions/InvalidArgumentException.php b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php index 685752a..e69de29 100644 --- a/freemius/includes/sdk/Exceptions/InvalidArgumentException.php +++ b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php @@ -1,8 +0,0 @@ -_id = $pID; - $this->_public = $pPublic; - $this->_secret = $pSecret; - $this->_scope = $pScope; - $this->_isSandbox = $pIsSandbox; - } - - public function IsSandbox() { - return $this->_isSandbox; - } - - function CanonizePath( $pPath ) { - $pPath = trim( $pPath, '/' ); - $query_pos = strpos( $pPath, '?' ); - $query = ''; - - if ( false !== $query_pos ) { - $query = substr( $pPath, $query_pos ); - $pPath = substr( $pPath, 0, $query_pos ); - } - - // Trim '.json' suffix. - $format_length = strlen( '.' . self::FORMAT ); - $start = $format_length * ( - 1 ); //negative - if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { - $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); - } - - switch ( $this->_scope ) { - case 'app': - $base = '/apps/' . $this->_id; - break; - case 'developer': - $base = '/developers/' . $this->_id; - break; - case 'user': - $base = '/users/' . $this->_id; - break; - case 'plugin': - $base = '/plugins/' . $this->_id; - break; - case 'install': - $base = '/installs/' . $this->_id; - break; - default: - throw new Freemius_Exception( 'Scope not implemented.' ); - } - - return '/v' . FS_API__VERSION . $base . - ( ! empty( $pPath ) ? '/' : '' ) . $pPath . - ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; - } - - abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); - - /** - * @param string $pPath - * @param string $pMethod - * @param array $pParams - * - * @return object[]|object|null - */ - private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { - $pMethod = strtoupper( $pMethod ); - - try { - $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); - } catch ( Freemius_Exception $e ) { - // Map to error object. - $result = (object) $e->getResult(); - } catch ( Exception $e ) { - // Map to error object. - $result = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - - return $result; - } - - public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { - return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); - } - - /** - * Base64 decoding that does not need to be urldecode()-ed. - * - * Exactly the same as PHP base64 encode except it uses - * `-` instead of `+` - * `_` instead of `/` - * No padded = - * - * @param string $input Base64UrlEncoded() string - * - * @return string - */ - protected static function Base64UrlDecode( $input ) { - /** - * IMPORTANT NOTE: - * This is a hack suggested by @otto42 and @greenshady from - * the theme's review team. The usage of base64 for API - * signature encoding was approved in a Slack meeting - * held on Tue (10/25 2016). - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @since 1.2.2 - * @author Vova Feldman (@svovaf) - */ - $fn = 'base64' . '_decode'; - return $fn( strtr( $input, '-_', '+/' ) ); - } - - /** - * Base64 encoding that does not need to be urlencode()ed. - * - * Exactly the same as base64 encode except it uses - * `-` instead of `+ - * `_` instead of `/` - * - * @param string $input string - * - * @return string Base64 encoded string - */ - protected static function Base64UrlEncode( $input ) { - /** - * IMPORTANT NOTE: - * This is a hack suggested by @otto42 and @greenshady from - * the theme's review team. The usage of base64 for API - * signature encoding was approved in a Slack meeting - * held on Tue (10/25 2016). - * - * @todo Remove this hack once the base64 error is removed from the Theme Check. - * - * @since 1.2.2 - * @author Vova Feldman (@svovaf) - */ - $fn = 'base64' . '_encode'; - $str = strtr( $fn( $input ), '+/', '-_' ); - $str = str_replace( '=', '', $str ); - - return $str; - } - } diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index 0b80912..e69de29 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -1,712 +0,0 @@ - '7.37' ); - - if ( ! defined( 'FS_API__PROTOCOL' ) ) { - define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); - } - - if ( ! defined( 'FS_API__LOGGER_ON' ) ) { - define( 'FS_API__LOGGER_ON', false ); - } - - if ( ! defined( 'FS_API__ADDRESS' ) ) { - define( 'FS_API__ADDRESS', '://api.freemius.com' ); - } - if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { - define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); - } - - if ( class_exists( 'Freemius_Api_WordPress' ) ) { - return; - } - - class Freemius_Api_WordPress extends Freemius_Api_Base { - private static $_logger = array(); - - /** - * @param string $pScope 'app', 'developer', 'user' or 'install'. - * @param number $pID Element's id. - * @param string $pPublic Public key. - * @param string|bool $pSecret Element's secret key. - * @param bool $pSandbox Whether or not to run API in sandbox mode. - */ - public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { - // If secret key not provided, use public key encryption. - if ( is_bool( $pSecret ) ) { - $pSecret = $pPublic; - } - - parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); - } - - public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { - $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); - - if ( ':' === $address[0] ) { - $address = self::$_protocol . $address; - } - - return $address . $pCanonizedPath; - } - - #---------------------------------------------------------------------------------- - #region Servers Clock Diff - #---------------------------------------------------------------------------------- - - /** - * @var int Clock diff in seconds between current server to API server. - */ - private static $_clock_diff = 0; - - /** - * Set clock diff for all API calls. - * - * @since 1.0.3 - * - * @param $pSeconds - */ - public static function SetClockDiff( $pSeconds ) { - self::$_clock_diff = $pSeconds; - } - - /** - * Find clock diff between current server to API server. - * - * @since 1.0.2 - * @return int Clock diff in seconds. - */ - public static function FindClockDiff() { - $time = time(); - $pong = self::Ping(); - - return ( $time - strtotime( $pong->timestamp ) ); - } - - #endregion - - /** - * @var string http or https - */ - private static $_protocol = FS_API__PROTOCOL; - - /** - * Set API connection protocol. - * - * @since 1.0.4 - */ - public static function SetHttp() { - self::$_protocol = 'http'; - } - - /** - * @since 1.0.4 - * - * @return bool - */ - public static function IsHttps() { - return ( 'https' === self::$_protocol ); - } - - /** - * Sign request with the following HTTP headers: - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @param string $pResourceUrl - * @param array $pWPRemoteArgs - * - * @return array - */ - function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { - $auth = $this->GenerateAuthorizationParams( - $pResourceUrl, - $pWPRemoteArgs['method'], - ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' - ); - - $pWPRemoteArgs['headers']['Date'] = $auth['date']; - $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; - - if ( ! empty( $auth['content_md5'] ) ) { - $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; - } - - return $pWPRemoteArgs; - } - - /** - * Generate Authorization request headers: - * - * Content-MD5: MD5(HTTP Request body) - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, - * {scope_entity_secret_key})) - * - * @author Vova Feldman - * - * @param string $pResourceUrl - * @param string $pMethod - * @param string $pPostParams - * - * @return array - * @throws Freemius_Exception - */ - function GenerateAuthorizationParams( - $pResourceUrl, - $pMethod = 'GET', - $pPostParams = '' - ) { - $pMethod = strtoupper( $pMethod ); - - $eol = "\n"; - $content_md5 = ''; - $content_type = ''; - $now = ( time() - self::$_clock_diff ); - $date = date( 'r', $now ); - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $content_type = 'application/json'; - - if ( ! empty( $pPostParams ) ) { - $content_md5 = md5( $pPostParams ); - } - } - - $string_to_sign = implode( $eol, array( - $pMethod, - $content_md5, - $content_type, - $date, - $pResourceUrl - ) ); - - // If secret and public keys are identical, it means that - // the signature uses public key hash encoding. - $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; - - $auth = array( - 'date' => $date, - 'authorization' => $auth_type . ' ' . $this->_id . ':' . - $this->_public . ':' . - self::Base64UrlEncode( hash_hmac( - 'sha256', $string_to_sign, $this->_secret - ) ) - ); - - if ( ! empty( $content_md5 ) ) { - $auth['content_md5'] = $content_md5; - } - - return $auth; - } - - /** - * Get API request URL signed via query string. - * - * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). - * - * @param string $pPath - * - * @throws Freemius_Exception - * - * @return string - */ - function GetSignedUrl( $pPath ) { - $resource = explode( '?', $this->CanonizePath( $pPath ) ); - $pResourceUrl = $resource[0]; - - $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); - - return Freemius_Api_WordPress::GetUrl( - $pResourceUrl . '?' . - ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . - 'authorization=' . urlencode( $auth['authorization'] ) . - '&auth_date=' . urlencode( $auth['date'] ) - , $this->_isSandbox ); - } - - /** - * @author Vova Feldman - * - * @param string $pUrl - * @param array $pWPRemoteArgs - * - * @return mixed - */ - private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { - $start = microtime( true ); - - $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); - - if ( FS_API__LOGGER_ON ) { - $end = microtime( true ); - - $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); - $is_http_error = is_wp_error( $response ); - - self::$_logger[] = array( - 'id' => count( self::$_logger ), - 'start' => $start, - 'end' => $end, - 'total' => ( $end - $start ), - 'method' => $pWPRemoteArgs['method'], - 'path' => $pUrl, - 'body' => $has_body ? $pWPRemoteArgs['body'] : null, - 'result' => ! $is_http_error ? - $response['body'] : - json_encode( $response->get_error_messages() ), - 'code' => ! $is_http_error ? $response['response']['code'] : null, - 'backtrace' => debug_backtrace(), - ); - } - - return $response; - } - - /** - * @return array - */ - static function GetLogger() { - return self::$_logger; - } - - /** - * @param string $pCanonizedPath - * @param string $pMethod - * @param array $pParams - * @param null|array $pWPRemoteArgs - * @param bool $pIsSandbox - * @param null|callable $pBeforeExecutionFunction - * - * @return object[]|object|null - * - * @throws \Freemius_Exception - */ - private static function MakeStaticRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null, - $pIsSandbox = false, - $pBeforeExecutionFunction = null - ) { - // Connectivity errors simulation. - if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { - self::ThrowCloudFlareDDoSException(); - } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { - self::ThrowSquidAclException(); - } - - if ( empty( $pWPRemoteArgs ) ) { - $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . - home_url(); - - $pWPRemoteArgs = array( - 'method' => strtoupper( $pMethod ), - 'connect_timeout' => 10, - 'timeout' => 60, - 'follow_redirects' => true, - 'redirection' => 5, - 'user-agent' => $user_agent, - 'blocking' => true, - ); - } - - if ( ! isset( $pWPRemoteArgs['headers'] ) || - ! is_array( $pWPRemoteArgs['headers'] ) - ) { - $pWPRemoteArgs['headers'] = array(); - } - - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { - $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; - - if ( is_array( $pParams ) && 0 < count( $pParams ) ) { - $pWPRemoteArgs['body'] = json_encode( $pParams ); - } - } - - $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); - - $resource = explode( '?', $pCanonizedPath ); - - if ( FS_SDK__HAS_CURL ) { - // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait - // for 2 seconds if the server does not support this header. - $pWPRemoteArgs['headers']['Expect'] = ''; - } - - if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { - $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; - } - - if ( false !== $pBeforeExecutionFunction && - is_callable( $pBeforeExecutionFunction ) - ) { - $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); - } - - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - - if ( is_wp_error( $result ) ) { - /** - * @var WP_Error $result - */ - if ( self::IsCurlError( $result ) ) { - /** - * With dual stacked DNS responses, it's possible for a server to - * have IPv6 enabled but not have IPv6 connectivity. If this is - * the case, cURL will try IPv4 first and if that fails, then it will - * fall back to IPv6 and the error EHOSTUNREACH is returned by the - * operating system. - */ - $matches = array(); - $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; - if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { - /** - * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. - * @author Vova Feldman (@svovaf) - */ - if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { - if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { -// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); - // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. - add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); - - // Re-run request. - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); - } - } - } - } - - if ( is_wp_error( $result ) ) { - self::ThrowWPRemoteException( $result ); - } - } - - $response_body = $result['body']; - - if ( empty( $response_body ) ) { - return null; - } - - $decoded = json_decode( $response_body ); - - if ( is_null( $decoded ) ) { - if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && - preg_match( '/text\/javascript/', $response_body ) - ) { - self::ThrowCloudFlareDDoSException( $response_body ); - } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && - preg_match( '/squid/', $response_body ) - ) { - self::ThrowSquidAclException( $response_body ); - } else { - $decoded = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $response_body, - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - } - - return $decoded; - } - - - /** - * Makes an HTTP request. This method can be overridden by subclasses if - * developers want to do fancier things or use something other than wp_remote_request() - * to make the request. - * - * @param string $pCanonizedPath The URL to make the request to - * @param string $pMethod HTTP method - * @param array $pParams The parameters to use for the POST body - * @param null|array $pWPRemoteArgs wp_remote_request options. - * - * @return object[]|object|null - * - * @throws Freemius_Exception - */ - public function MakeRequest( - $pCanonizedPath, - $pMethod = 'GET', - $pParams = array(), - $pWPRemoteArgs = null - ) { - $resource = explode( '?', $pCanonizedPath ); - - // Only sign request if not ping.json connectivity test. - $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); - - return self::MakeStaticRequest( - $pCanonizedPath, - $pMethod, - $pParams, - $pWPRemoteArgs, - $this->_isSandbox, - $sign_request ? array( &$this, 'SignRequest' ) : null - ); - } - - /** - * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter - * - * @param resource $handle A cURL handle returned by curl_init() - * - * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to - * CURL_IPRESOLVE_V4 - * - * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e - */ - static function CurlResolveToIPv4( $handle ) { - curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); - - return $handle; - } - - #---------------------------------------------------------------------------------- - #region Connectivity Test - #---------------------------------------------------------------------------------- - - /** - * If successful connectivity to the API endpoint using ping.json endpoint. - * - * - OR - - * - * Validate if ping result object is valid. - * - * @param mixed $pPong - * - * @return bool - */ - public static function Test( $pPong = null ) { - $pong = is_null( $pPong ) ? - self::Ping() : - $pPong; - - return ( - is_object( $pong ) && - isset( $pong->api ) && - 'pong' === $pong->api - ); - } - - /** - * Ping API to test connectivity. - * - * @return object - */ - public static function Ping() { - try { - $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); - } catch ( Freemius_Exception $e ) { - // Map to error object. - $result = (object) $e->getResult(); - } catch ( Exception $e ) { - // Map to error object. - $result = (object) array( - 'error' => (object) array( - 'type' => 'Unknown', - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', - 'code' => 'unknown', - 'http' => 402 - ) - ); - } - - return $result; - } - - #endregion - - #---------------------------------------------------------------------------------- - #region Connectivity Exceptions - #---------------------------------------------------------------------------------- - - /** - * @param \WP_Error $pError - * - * @return bool - */ - private static function IsCurlError( WP_Error $pError ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - return ( 0 === strpos( $message, 'cURL' ) ); - } - - /** - * @param WP_Error $pError - * - * @throws Freemius_Exception - */ - private static function ThrowWPRemoteException( WP_Error $pError ) { - if ( self::IsCurlError( $pError ) ) { - $message = $pError->get_error_message( 'http_request_failed' ); - - #region Check if there are any missing cURL methods. - - $curl_required_methods = array( - 'curl_version', - 'curl_exec', - 'curl_init', - 'curl_close', - 'curl_setopt', - 'curl_setopt_array', - 'curl_error', - ); - - // Find all missing methods. - $missing_methods = array(); - foreach ( $curl_required_methods as $m ) { - if ( ! function_exists( $m ) ) { - $missing_methods[] = $m; - } - } - - if ( ! empty( $missing_methods ) ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'cUrlMissing', - 'message' => $message, - 'code' => 'curl_missing', - 'http' => 402 - ), - 'missing_methods' => $missing_methods, - ) ); - } - - #endregion - - // cURL error - "cURL error {{errno}}: {{error}}". - $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); - - $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; - $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; - - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $code, - 'message' => $message, - 'type' => 'CurlException', - ), - ) ); - } else { - $e = new Freemius_Exception( array( - 'error' => (object) array( - 'code' => $pError->get_error_code(), - 'message' => $pError->get_error_message(), - 'type' => 'WPRemoteException', - ), - ) ); - } - - throw $e; - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowCloudFlareDDoSException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'CloudFlareDDoSProtection', - 'message' => $pResult, - 'code' => 'cloudflare_ddos_protection', - 'http' => 402 - ) - ) ); - } - - /** - * @param string $pResult - * - * @throws Freemius_Exception - */ - private static function ThrowSquidAclException( $pResult = '' ) { - throw new Freemius_Exception( array( - 'error' => (object) array( - 'type' => 'SquidCacheBlock', - 'message' => $pResult, - 'code' => 'squid_cache_block', - 'http' => 402 - ) - ) ); - } - - #endregion - } \ No newline at end of file diff --git a/freemius/includes/sdk/LICENSE.txt b/freemius/includes/sdk/LICENSE.txt index d6a9326..e69de29 100644 --- a/freemius/includes/sdk/LICENSE.txt +++ b/freemius/includes/sdk/LICENSE.txt @@ -1,340 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - diff --git a/freemius/includes/sdk/index.php b/freemius/includes/sdk/index.php index 0316c6a..e69de29 100644 --- a/freemius/includes/sdk/index.php +++ b/freemius/includes/sdk/index.php @@ -1,3 +0,0 @@ - $data ) { - if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) . '/' ) ) ) { - if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { - return $relative_path; - } - } - } - - return null; - } diff --git a/freemius/includes/supplements/fs-essential-functions-2.2.1.php b/freemius/includes/supplements/fs-essential-functions-2.2.1.php index 946a34d..e69de29 100644 --- a/freemius/includes/supplements/fs-essential-functions-2.2.1.php +++ b/freemius/includes/supplements/fs-essential-functions-2.2.1.php @@ -1,45 +0,0 @@ -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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index 420c0a4d649886c862c39da081ccbd6152dafa52..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 59840 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 diff --git a/freemius/languages/freemius-en.mo b/freemius/languages/freemius-en.mo index af870a5634b2dc8a67ccfab10c389a38046d0bcb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 59161 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 E0n$6m-~a#s diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo index e10ef71aa02158461e55b43881f21c6790851396..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 62945 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

    |Ux4%+3 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

    p%v>@m~5!siWoW0{3Z!{~4Ybasy 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 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index 1ad1ff4a138655df268dba88bffa8830ef28e0dd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 62213 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 Cc)wr( diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index b4e5ee1af68ff8d0bed9f2da324728fec357a589..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 60199 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 diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index 8eadcf8e0fc21d20cab4e551adc8376fcbffad68..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 61645 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 PK-&Wu6@Be?EbI!eUC*acU z^LfS0@1Ap>^DO`8zdz48=W7Rd-J0;fp{FIuk>KV-bd)65FUYZ%pKs^53jQ%T44%F) zNd~}s!9H;RMM*Lr{0;Eg;I#p71{ZUDC-_Y8bKt??AA=`@e+}*g52w(@;2(jq2;2E5M1Y8IH3#j&ck57_~;AZgY z;QPTBfgb{o20sg`-oF740{fUkW}4JQX|?JQsW(SPJJSKuDLo z8SDn{2UYH?pvLtBP~-kL@IWx7kghKU)!#Ls#(N>Ce!Uhv2^0IL7Xz#7;Ks=WunJHg%H+2Dmsp&57=D0=-6{4jXP zGVkXT;1L{8f%Cw>1I1SdEl-jYz$M^Z@M5q8UI9YV;&i@!(%kk4zB*{78 zI#B(-1vENceP z@tw|O>iPyy{d{e}OF)h1Qc&Y~6L=7KHK=*K1yn!o0~dk2K}eZ=1C%@-f{^LHQ$W>! zI(P@T5fnZD4pe*a(oQf1WyMK z2OkF2&o2c0CMbIS6jXgrd#PVP3>3dE0gnLB0oCsz@KLZ1-lprPF&6Om!9g%RJxQJc zUI%I(-wvwX-vXsKc7bZ=QSc)03!wOV;VQrHEui$tjiBgwKd5>h3HWJ{t|nguMbCrI zaDF=!{29kbfI9!eUhnVGpy+vAz!!t!pEckmU|%@i1HuBy7eLM1)6aCe900<)$zdQY zpS%q0os%SQ05y(JtWJ_Qf?otRzVp{4$rkVeP;~nU_yX{Apy>V$Q1kK=a4~o=Ld4W2 zF9o~7aZvT%1NMQBfSUJbpXK`K7;qlP=YUTGtKbIkbs(&hd;%N-e*%ty7oY9)p8}u3 z@%O+<@E<|->ymX&@2fz`(LJE}@_tbL{bV@)5;&jZzXen9x$6U;gQC|;@K$gQD0%t{ zhzd;hf0^5jQ^7ZJ{5B90C(lM{s2>|ZmH#YwH270c?H+!v^Tkq7biE0LOo(z8RE!-x-cS3aURJ z2loM|KvYojWl(%{;CVj&qd@g@A*gna2i49A;dmvedG7_)&a1=u;c#3F_?zJ2T)z&~ z_;!GYfgc1l&L;wX9{e1~e*u0FynADkd>cG_6Ep_9U+(>R6jcA80Ox{V1djxN1d1*P zpYQD)1&W{MfroNzFz}XFxcvPNDEa#`xCQ)s@MLiHMNaR_z~wrA zrH^MEl>Yr8C_VVxSG(RE1;q!~gW{)mg8PE^fCqy2f$GoW;Bnx$!Fk|wUgPzz07dUI zsOvX@3&6)grabv3X#DkBw^J)Y&GR{+_@@Nw`fq}ohpRx*>mgA6`Abmp_IIGl?SHZN z>rha~$AF^u@c~Z;Mc-af_rDB$F8IoDegstcaZq;ia!~Vh6R7dt1!`VD3cdi`2}1hh z3!uu){|%pq6`m8u@=5A2^`v|Ch?E%FH ze*iYXpMxV{qvUk`I;eg=YpeJFFi`zk2&%s)1w0QFe++>}U+_g7Uj>SP-v_EcdqByh_?2Xp)gC^~%}Tn&B~RDZfFUjJ(F8jkxxx{`bs>;})f z#K-%3P~*B1d?t7!sQTUoE(CXh8^EuEXM@LYbNV+x(d!N1G2m6;-Qc|-Q=Y6F@$0`0 zc5(bbINlYG9|PBN{&zvi-7^`S#%R%^4_*z5FK-IRzYQ9lL5<_{pz8f&a2fa) zASyT6XN=eYd<`i1`z%P)$!%!SAqWso(VSVt_L3h#lPPNWha&(Ob39y z;B&x@py>P?U^jR*cog^$sQI1(7l7XZrKb*PI-M4RPv`i2P~*G+JOsQLJQi$#8^Cvi z&jr5&E(QMu6n`!n_c&}FsQ&K;H7`E_)!u=ZqN~7@z_)_$05^k2OwcAc2KIoT3it!? zxpT0Y+kG4>CcXb9@L;Gy8H;HltmgC~RE0Z#{yy4?Bh)!+*`-U=QKz5#q5_+C)+@Gy8d_$BZN@CN}8 zyu#=CI8gLn2a5k|;ETcA!Nb5m0gnQ|3yOcAb*0lS1=YXJpxUc|&j;TGiVtoBHICl_ zRqyA(<={Vq>c@PTRpoj?(Q6B+^OK&JlV_vxVIa|~3!w}ZOx zdQkV>0ji((fs*$}!TrF`gyU&Y{rVaxI{ghOIs7@O`wqC)`SWmaHOGBm7kC?}cJ2UG z??=FW!AC*$?^ED2z^{Sg^KXHw?l*@I2&!BO zRJqZBmw{){vFpHI&VTN0u8;P+-t%UY-~i|U8Wg{t{dQsu@QtA6?NgxU?JM9J;6H$( z$Fdu||Lefla{MZAYz}@rcss|(zr*$VXF$pA{x^B~GeFU&FJM2Y`DlP@=W0;=`5y2< z@P1J9vIjf^`~&ba;C?rIebeAZj=u#y9X#=!K5s7x*b9nJUj`lk4uYDmCiq-%Hz;}g zJg9Qt0_TBB3aCHFfTw~l0VVfMZ~=HH*bROfJPG_~P;^;*tIJ0(DE=*j$AVXbqVI>p z@z+7M^Ne?4E5JqIv%qV?6ubdczwQS`hd%&S|ChiD_>Z9G>6N$nJlzF~t{(#>zn=kX z;6H)S1559wKg`brcs9rHdXMYpuYv043MTEt;Q64A{|yv9j`%I_=ZnFUIesO0A$Svb zI{01inbf=J4)>>*zLy*t=U)Y?{8zzc;GuVVy=y`Ba{xRFY=T|j&7kzpZJ^|0C#dm% z78G5+5w3q16rVryE~nGMpyqJ}sPk*VwcyLa8^PPaGr)y+`}_}shj4r;2+JgI1;t-S z?eKY81ZrM3fJ?zKQ2n?Y6#sl26u*8BJOcbO_*3vlpziyJ_c^~Ge2>ffS)j=!sQX95 z@o$2<|2j~5`W{gIcpTgceiqyg9`}BDNY}wt-xCLASz6I1cJ_bGteh!>Vdsp7?@#6a*aKAr!(C23;;9Eh>>wTc+ z=d+;b@he3)a-vGrQ@7nG9r1bct1`7)vpG4D)<)gwcw}0H-X1~0$Bpz z4~jp24AOLR==Up!C7NgBt%nU+{T99-PbZ zsi6Av8gL=l0Jnm_1)d204Agxq{>bTc0jT+%0L4cSgU0vZ3pxHGsQ!K*+#h`YAG^FC z3m(exsi4}~1U?ho4lV)T1giW;0zL{ty5uw92f-VsobOJaX3jZ2@=v_o?}6(7k3o%R z|1Wyn(*ueQBcS^EM(}Ci&ERtIR`40%XTb^Z3*cqo+CO!9{9W)Aj-UQ#UjJ#J=+Xyv zfgb~Vz{kKJfZqe(2!8d?v5PeH>c4P1^nNB)bo@O~a`ct^-X`uM%C*Ylw@A*15jpHwT!^gAcuNepDKLtL-`LBP|{l9m9%lYlkz@?o3 zCU`J-$hVze7Jwp{)KC7{Z^9u)mQ0v-Z>4LlP30eAp-(09-s;8EZ~;Ju*e@d$VZ z_&M-!aNoc2>&JjE=XeG9GVmHu^-Y1Nf`1Q6z83!<*JmrhXK`Es4+UQbrr@>UKH$CJ z3h)8&Snyk*`nm6SeZGzYU&QfZQ2kv8YF;h?#m8l^7rYs~5&Tn7~~uD5_H z{~l0u`waMW@XMh1nr6&*izT+Zr5y!ot=yVCF@z%j3z?;AW!23YW!(*Vz{~35L z_%rY{@SK0}^^&)McXRwn5Rpjw|Ix>H>OXnE*MY}zelvJH_$F{)@OQxdz+K=o!QTa+ z2Ywbb`hZ3sQ2qNExDq@HVSGOL3h*Rw4168@`$157f*1UdzH1Ag zfok_M@VVeMp!$6qcr^Fde&Ttv8~?@a$G?M;!()ExbUhusm*WN~x*qYbPKOggx|ocD z_kkDxU)M7S{F~Er6DT^o7Bv0&*vvV_46;mW5Dl&cY_E2 z%=2asgBs8Ce(rppf~vO{6kUG;XTJ%rSr9PeIAiv*ye(`mYA(a(oeZ z4)~j(>iaCH`Tq_$AN{xDX>&|2F6x?Nay1GEn`#1w?fu9|SdTZ+pfZ(|7lP zOF8~3C^H-Uc%&Ih{>m}7oM zA1J-z!tdXCS2{+#41;O~Q5!Mk4I^my9gbCN&dxDUJ-T=2p< z$vWD(7F78^KEnCrw=pVj;rJ)uO7M!KoQ{u!=W+bk;Mw4Eg3Y&rli(Jx3nA3}y%Llj zdJlLocrPe^{x~Q(_&rd3`)yEsHaDGPdUOSND95LO(jVu6n%AwM=4U&25cqmf^Kd=* zLhz&D5#S$yy6@Yd=>AjiNN~|yKfe}CIlc(o7rYGI3|kue; zdpo!f_bLHjPd5>b~p2XMneZ zvgdb$&jud^MZYINjpHkz`t$vOKLK@rGT-q)Q1g5wD1JT$Tn)Yk>;iXylD7|n>i;w- zee*T28~hhg{aCQT^~VzMK#pGqivAaY8c!1xJ>LNT$Hr>)W>1oC9xkV2rOH5dcWp48zx>!nT4|)Ek$Sl_Fqu}Wjb>?Nq&(1* zZW<|<8f98+RxYKww9-@uxLj|hTg%n*V5M294y8?Moqqzggw`89>HHPPHWI6OqB1g) zRz^q50~M+mndJ7`7>$jjgX7lKx_Vz%J!7TG(Q>s(SLpO)ZM^Qaj*XP6=}4u&T%|8o zU3vRhg+W#rJ?-@TIvQ%Edu>LIG}3MgjStamwKQ7pNqZYjc;<~Qc0y-rG9Jg+vPI(r8Ox;dICF^}UmwS(g$kZEkZ-aicW2dw4wJ}J<& zk)G?Mu-{>7RNssmV&A-m(ojt|i?kJy*qXIEC^uSZ7&A=_m#b+h?H^~7M$7fA6O>P@ z<%u{8)}_IExtuoZmC{Iurc2fSvhz@1g`dVE-lSM|ijE|nc`<7}cTtm(3NTt}_zBh<3hN87=FC&`w12o(V@_2!b(R|`YzUP#qXXHjM}DwkQ5)2%JxTAT^+|8v>hQoUY- zN6UjSM}Mu_wcwcTCoDf@*{Lm;>kznb{l%pdx$d(QF{u*Yt`3MAaq6vQ zgt=B#G1rgTk92;1lHcgjQnh$7Cq}$AJxkJbf$Q|VXb|=ML_Tm+p|Mv+Hb@Z_bz3o~ zY7m%exj9j*Z!45;4ofUdA!Jny_^*la>jHRqRKH(qWuDaFC&$uYGyG6DxKNLSb3|LQo6j?hpQNxl@* zXG$vV$C@>^RmP;lL^q6tY2{L!Jjp?vz!7?0Um{&+oi%X^6WBdcrc%0QnuYEUl_82L zlcX0d?)nrm4xoufYNY|!{XTc}2#U-4H_}B#k6u8g$2&dRfqy2^IY1W2@RB=vTYNN4cxqGCv6;eKhO1({U zuGFo+*?2MG$3&@W{^9_`!b4J%QF>Fn)6%M>ceYy?Vn2PR1T$1l zk@IpLyS`!>H%_{`$&3tF2gj=eNH8VMqmU%$P7NkzxP+Odt=h-{V$WeURt1$Bjat7H z1PmYNB>0+U!Qqk2c=sYx(LW9nBo9XF)H_uhYnBJP5f|~Qbd+wW<0((zVoQkZjJY{! zaOtd`q!$)KfYb9i#OU`i{2>2FdO_!+bXWo z?22y~p(&8y#z)tVh^QdYj7f}Bj`2!7+$g_ToDokLPlmEvU@y*>`?uj5C#wl+lhr82 zdWx5qt}gWtm&5TE4{+1XV!n!^MlM9RvhcGw8zFeZ8F9ThqRP!!$;e2}Tw0N&n!Z2< zU0N9sYXnc+tz~|P$`XByjC42T{W3);-^MVUfD^JB`^2BoG30qGayKqH;6$}rmYG+u zF3HJEk*2CFDMJlm3tJ+=dbt}DQQA6EX$&hWmr`*RGCRkbx--w>QaoAi6+hN%<3nzB zF5Hy5o3y6X7~Wbd)iD?HS@1z3!giK5$d0!_VT#l%Sn^t32e<(e#msRr=~OUNhp=@R zUrtf7T2>uPfGqSi$xbwqHFC$+WD#EBXT~epRS^c+%bZRf2gx$kfqg87jg>|iYvw|s zWri#L!xl|oflx4B!i7|(F!-h}k}VCJ36J{4nhRne>)FQqOH$|((i2r{BG4uFIM&nN zWQ)+Kn{a>|&ddttok9bwDQ_JgO3$qggHZKlTJXeyNx5!LxlDHW;#14&;$M_Zd<1z9Y;}XqU zyDOcnsYyLB4Q}K}pUGA>nr2(75FNFKf`$Kv%1z58&aTj)ug`(7AjBdWf)B#HXZ5J*A2eF!$jUp|xCM2}v+>K6LV`NH;~HDsEU4q7MC>M}r<6O%!V-Ro@F0{I2^u0CJ+|r_I zUr5-7>LLF}?J6-oW||QhR;onlXr{rGyrMjJ>aAd;H?m6$N)z~B>E|-}6slD3RHsFU z^_9t5TNhyThB4$?yPXInU1O=I2G?}t6j1mdGL7B^6w7F-zO8J^NHsH3#oE?XPjVKC z3H^bN$yto2AZ%Ii*qX5#8i&i7CN0H;kQd*YT@__>9mT~X zYmj1>mPRVv5;7XrV@QFwYk%;-6^iIMR$vE8(#dPj56n%-5Bt`f0|A2%Y2lm^+?G)Y zx|Jv-${WE-g=MHrvIn-Ep{$*y0hxl_T3P}L-^r3n-bd?({7&z!9ij-On4G~xEK#;m z8*Gv}5rb;5jo^+aXW`*{pnTSN)%0$1HVG-(jf*BCU2v^n%3^E7FB*7oIKgUJR9+Z-yNk)3*q9m%arY1`V=D+hqdsdAV^V|d6vl=M2GY@TbGSC( zOA;nF#NE97+SqbGg~-N!FFr+bp+XUckCCp_ind%r@4Wfu_nty6{n>Qjx{HmQ(;5 zYjjmifBG_%H?Ok|NS5^zYRyg7jgBVkXpXe0Znny3 z6_<6eR9}^Dme-)zT2*nrJ<4!iFJZ2_$%LVnkh&m|sugJ+CwU%ny^$=dtVPqYYBOr@ zancU!Q%`Ysr^Nt#y#|HrrbL=p2C@d1ksC6ZC8IzX`>;p7vW}2l4{pENLOR%steZ`_z^)at`FY zt+LZlP6s-fUoh`JGvVikaZpX1XP~A?MjT(aHpEsexA|re^6ZgL2-+&F1u2 zUXOZ|IF$?RVU!$N{WRxkkqLY;zqgox$4Qt;I+O|FkP|HiH3*`w%cN#ULrB3Q){z*4`N z5i6iHbg~}fZ`MS=j7m0V%nmaMuc(4mVv|bXHIiFmoMHSWtX1@IB=W8#?RM+UlA=O~ zG3rWt<6a>fX3X6|Ci7bfDe>G!_eBQr~4 zwLwUt)tA=d;s6PkB6rNIyM30q35rz~0G_Cqp^Xw!#%CF~tXw=xeIBA1BbwAmZDk8} zvTxDdW-9wV9czSCIhGYQ6PSaogc7Jo)$NM0k=8I%W_5RI&@9~Rb*l`yx&Wp&$@G(jS9s0Vq?UC>SA zL_YoLIb~K<`^w~9oBGX!Z;_k4ehpzO20`lG!dJB{?UV(YlkgoxA{TLqmYfHaP_qS& zNP%#NCd&-hdUIREFPavVd-AfHW)av9Wqo2{XN3zNf>8x4oe@9+s&f&kBJuHWp>3!L>O{P1}ocT zxn+yORx8Q1tp|iE*pD?L8=%hNMKzfC{PxNy!@87JigQamj;%jo5(fMr$O{LS>S$41 zL7S1!2v?&)mhlWzE119l6BEkWo9vNSja>VkPQS<->nGXRd z`q&h~tP5jORh7XYxaeTXU2BNSuD_uwG(ui+OFVOI5yJcv*|`^eYTFMXDaWUIP<%rFVsbX^jmuy<3-7mRt3~t4`sJF*OGJ$|^I}69-lS-IGdZuu|B;wrNus}? zlV~gcTB4vSHD;O^kB2QXo)X9On=HWG8s#gb9vnEKx9kJvQCgKTV;Nd z)~uRpw!B|hD~PRYx7T=eL@U{9H_n-bxypCPAln_{7=nk9L7(an)?XVV3#++V{?Ci) zdRWhu^=aVIYD1*4fafn&A`by)U-*bl(wNp7u*gbBT3RKfK}Aof>Z+T8S#r;u+Lrmi z4lZ$Z8p!U-I?GnZ+#OD#HaoVa3~kai4eo{*3uIZt&~i`8RuSxm#)Wb6JtBn^K}>E2 zw{gO9191#FAwq2~E4HWHQqXM82@Y3u;aLrdOV?~`r_hI&p%GBS<`kIG9{b+@gv>9BX#R@oB@c=K@qBrcrI%dmM+9 zTP4JHP;fNcDF+{sGG3lja9( zTCMDium3cYO=1{#IXiBxlnh!5m6DU2;LVsKX$kpsQOc{KAB-DyVZKPZrEiUydstG7 zDLAd8Xa&*t(h93oR%K7J2~}f%B*D;8E_Hc3DGJN7uyYH~hUF#8HhESYH(lDuZ}q!S zBf2l{zX>G*mxh8+BBqILB8>S(sjao`&WmJYv~MwEIlOCgm(*Q4=W0!>T&AJ&42OqJ zJ(vtq&6%9KyNtX^gV@eh)IoN42(_dX0^3$`<^ zF;*HyBD8x=!?lMpq=Vs8x$g=aJDko`5_g7G0XB!rJ6#z|4=_wi7b?RQDrT}VkdpSO z%jH8y@s0GeWm^?;BKofCe2;kCexJ28ORZQf-z=f3ED*C@nAvUYTtsPFmeZDgdi&HL>ZoBqNOYyLX#W9?q1lMu*1XidPHPl)$Ds zFi5B(!i_f-p36Y7evAcBhvMSL(khNvaajZ%V*9Uo(8L^}5p!BHb(`y2tHD^YczdQo zO)iOd{Br`xVFKz&8PL72|FxzRYoq1YaNGAzIU5w3hV1!6-eMq@go0NQD z1ROy|6d8*XV+IVHp^UbsqM~5IObdl44eE^gLJ>d*$SqZCK~~X1}bM z{NQ%3*~bzKX`K!ckV(?KXr%3DdBEs1ldz zS+OKf@A1iA zPnH)s9Ud0Z4^~~e4C1Zt->`wVXzNxT9~5YooRtnAgnTSqjU%-^^fXHm|3YngO8u8_OcOeU zg_F|9AZZ;oJddG1X?-JQ$E)pa?Gt+ij#TEE>&-%5u#czvR70BO+GL_R@Qm!zB4ZGv)xHNTuQC+;@^3jD!tPP+zQ=vku$ z#zW|)q%Yqb$wND^fpNaP$$D)>Y@IKxX}RAX+08cED0^54yo#08%Q3u;Y;O;fik$Ep z@l_s@M~%{_2QQ+(w&*M#n%PQy(B!4bbZL4)?`G-d&Dwz{M>m+dWHW^E z?LQDpvK`g~oM0lTuOrM&dr{REKG#raOJJV2$pR>mr7a=tIV7a1GHBa} zd{dkr<3N;*SAy1S@5YQiWDV=p1vZYX-5G%!Tsp$8i#lAZK1IdqdRtkU&>E|U^%|S3 z&?s7ybxM$L$(IE?oRa*JBZk>orZd`^n&zo4?K)FZmMt%OHYy8OB2BVffN$^#d6_~T ziWId}BM>+!!AqCf2egPYjGvYw>#U(apsd583WqZNx&8)Md>y`cXC`HA{?=C&F@v z!KN_0Vj+{!i4y)gwwlea{6Iz1Q7vVM1QdQ74w#;V;gUZ$t6sXf3)>_(-@i@PF?0K6 zB9$Dbd4G;D%dZTS8+ddO3j1nZC2lR*W@)X!M`HL~W$W?w=&e}0#dt?kgbATbtnJy~ zw2!v>hy9=-P!3LJ4N-srCCYJu!)}b1<(LMap;I{x-=icETOG9rNNo!&L^nrRnpckx z*=UW^3l(PRW3@Uz?vJ}PXQ@YTFld%W$CAy8Q~fU+80qJdI2IQN5x}>Y zY*rd6X07rpQro7XrYyI+#L8$vH=T(>WeO-=BoBw3FQ>7?ZgI>U&}+*sTcQpQuu;t0 zKnPT?X@}~(EVPcwpw&-Wnb1LVOJrMl3JZFYjfC9FWaSUCnj1|CC0)erC9>CKH?thj-hvr&Jz{@c=9F|s+d}BbxFH9=wT)udG;}E%iU5P;e`?6iE}-w zYtTYGwZ(Ve7S80sn7X3p=)~`Gi@DEtg@k&-At^g0R@vDKjmTUtftX=WH<>;j^&^`~ zQJe#w(c3a}yQB>zW=82{;L~J_Qm0vfo~f0X;FV9v{IdsAUKCyQRjqp-~3uFd^U&MIPvK>rx zp3cjk*F_n7j?VJnnRvEmx=Gphghnt;*4kuP_Ta$Q3?Z-D3=taI@&Y}L89XRIum!V7 z$>hRi%bLlB{)Uo>D{Wf_G$(xtO`uzNyl=+NOkCLlolX8d%piT?v+L|1=3GH|f$m0S zkD14fhvb9wBveYc%Dx>vdn!%f+AL}$v|D^MJ!nr;3?6ob5FZACH>97!{%Uqks3##E z?>pgESxYTD?QH}tH7O3)ovsvqiF$1rK{do(PCTy4LZxlJ!lci>#VlikupbwBFMGT| zmkO5N2sVn$1_z)yQ7*B6FDNvv=@LlAm?IB1Nx3Q=B+(L$^9cwBkw64yGNqyxE@b68o>w4rpYa>kt5np6vWEpLYXZ&JYq*T7;v6W1|bYbm~FdR zPrch^6k&Lj0+wg!z7tN%3lJcd(I(HrZdzLxqO*$Z>|xunlET1Hn)(&vnKM(^wdYrZ zi1@2iP}({xe=16nFYZGFxYqLbnpo>>%e!bodU+EzA-T|27B%j;XWg9G==G=q4{ui` z7h$#dhcgPd`VW5;KsKEbBo|Rj%qZ~gj}e|N#})HMQAWgo34*NrY2OJZHP73LY*dy&>&HH0vntW}o zaHZBOA1H%D*Kt}}wp_iWhPLAuk{30!MI0ib366ZxwrwDkojp>LVP}OSzUWT$3C@1V z*85(L$gs>VtKDQXOlmQpEQF6nL+`^0_t9#&sc^c*W%rqlFRa5yQRAcbmMN=4yv-D9 zGk@Ia;&GE*r6<0TQ|iQGQ^h)>mC=&sMRR1dTNyo7J#!tqJ=1MbrS7zR3mg4>89YT4 zm0JmpTdw6q^}TGHchkVl1?^>Bk&H!FwX7q3RHNcN)3e>tzlff+jAla_CR;l79j0Pd z=c`|TrK47*<@bAS^R4aXH`k$U4$T|HkAT8f+}E5fPdhV6^Hodb`C{?NSSmX>QerEp z#ZQGtqB#L+I&5!`Pqbu7)@+B%B3rj+cEUFNUwGsD;>-!N4WAQXk3C7JG7qNUjcM7$ z*FME@td{B-Tm*+p@pTc_yx4|K3l=bIh`fLa>n=0NW#d+RV*tfEQly2}*>3raej$uX zH_|P=Rm1DB#CPkdxI<;ej06dQ##z__|7Y5*m=-ML^rD<7_%&N0ouKUb+?3tC#`Zyi z@~k3OqJ$vnDT*-GcufmU#SX^h5s|bpP7;O3r6R|;BxUWk&<7&mT{Dz!Bd*L+?}cmo zHf~sJdO1FlF@pv{54ra-Q%QJ}Mjl)m?Nl*mfl>0jkiSk!JJa3cvL}V&01Kj^9%I^- z?dUP%62d67??jeRi%~{=SO)8Go(qVCT%qiC$mPqT~xB|50Qp_Btc;lhPm<f?mNnZMv?_=XlWrE$1{gWkoHl1!DUpl{zABqZVGI33V9}Gja{VS2s5a3# z^7;1WB;L_lBa^Ks4hgTu$Yqqf&Nr8e=PqncVw~C4>nT8@&RX>qQWkO@@mDi_<4LoN zx?sZ&SJl%Q_wvfDS&ECdyh7c^1~j4)8dPvX?nlew=EcK<_)G4|DurIk-sRA>j!hM~ zKw}B&i+uqwJ)@H}Ls)Xs%koYGhKHRm5Ke`~7 zS4F6|f46q*UvJ#j0dFAjB$)^xJ49j&X^=C$)#?`^U4Mz~OA>4Uwj zB5uNygm!Jx4qPsV%r}du^+~h6_S~%IoUV19S7Sj~PtsY@_$q_}NHtO$Qx-MdhEEtK zqtT`9DHr(6*wkk)PYC*;>)fzkM-6A1soZ6Io?6dE=C^AxBdlKJqNaWFq?EgQ4eZ*u zUU7-Y4R&oD94wxxc5SRCJb|yP_DHYZCoP>PQG$>9Y)L+MX_8swz)!achtfXe^+K-4 zcXcP3Es}`D2eXsHb5Dxu5}fM!jq^5cM!3ZvET!)?n(eS*(~Poj((FkrGLYX)3~SjN z^gWYJ?Ka%LZckqZuCx=`Z;SxH&5=&D7SN^bYvuf> zCM#yXk|;Rg?yd`*wUazh(iX1RHpuU6my!sn^&~8;uR=lO_$VxtWX)X$Qx6@~2QW6j zj@j4aa*3HwlM*wsNzs5O4ZQ7s%O|OOVLubx5k}}+#{E7w(Loh$R>Yf7YAbqQtR8-& zeJw)v!?iqr*@8*T| z;%B|L9!3`%b*~@jzQCtuWnudq%4omy%X+ZCdqZQWGSGeI_)w#Jb8Tfh+-#0DRxVvS zMCdWTwFmKCdRBPqVJYj+1ICfvje%`TEpgk}Z_~!j-K(_#4##${!3wNQIoEwYdqpK? z;nWGsPFUW(?1b)RE7E1BtUT%DB zSl}5fZ?{p!dfP^YO0Wi&TK!8G?}w??qm12J99+qco$bw~7>P>tRNM2+iwmpQkOV&R@Rl zw9}TS3l^mMxoqxf%a^CC(q$>dIX?Zg6XKNYj_zelNT&-jQ{$vlRN%OtOB#hTDBFIzRjR|yE7L8T&w`!*|EE*_ zYd@Vbb=%YfQy-t&GxfyubyK^iubjR<)rsjVr*=<0H1)vrwVb>GZz7$(VrtjaqZE2z z`ieA}x{F&rK7G~HC%E|mUFX+h-1#u~J;9mjtGMA4Q@f_$%;6s0y=VFgYTYw^qssC7 z`b4!-jI$4@5baTCs?!wNP1CzL{RkC4N`voc>(q_b=!4U5Q+4glan}P>w`=-x+E*

    aghxd_dP`km`s=j#t>0HKDC?f+#1Pq^Yq(dpEQKaX^PHF?NZGT z*a)AXLyy`dadH>zvxiP_dj|?k?Sk?;|1rDwD(*AhDG**Yx3CWMwkiI_X~8hwONaJA zifj10lc{-Joy#U!lG4_ex~Ey(r8&&$W>TbaMp^_RQP-H3nSFEad7CDL8Kfrh9{gY; z!AY?IVh1(hJMng^NfnoXk**J@>MJzUI!R6NS>QQl#h84z24Gh-1FB|EkU9x<>+x=< zqqKlX@B~9l=EI;!8GmG+&pbJm{p7Cju%o_v3WL()b{T9Y%qPHZS zzRtKExe%>2w^y`K8x?nz(LyYdX;M=`RCXgbGpXWNu?<2@*B+R9kW*KRYCWmwK)Sni zXUu=Z_js}|@92kpVrS~Ntjj?U(Hr_DW{p1CTknGSk3veDN(sypD9gn1k3?Xl*yu%c=472M}AM*6u=`M_vPG4<;`Ehz8J0c_ZSZDG=A8%W) zTFlMElO~vYe}@_oHsnYI?tB?FX+|X$x`pEh>B~bXFxUNN2#_?*7n%cs zlAKBFaRMD?T+y-Vf_;;&K}E}zxDsjFGxaEvgf?q0(y<=3`WS|UcFox3a%o1>&qKgG zmbI@O(K%*1OZ`K{@XBBFhfI|?aTxASX&)HsfmB}|VD5G@PUOnPh9N@?IxJTTZ6J>0 z)a7l58XVKThoRmj{Xz4P2sep;^4OwlFm*GP$kj3Z)yBz`)oJa+%Get(MKwJnG4-3o z!f&=&-C;LK48-@y2E9Pqp>1x{S|wFfDe^zvy;(H}6`|>&4y;Z%P2;;!&u>en?xxO1 zj8bp*wyYg#ZrNO)BL&)hT~ZghvYGHpsb>#`u`Hlxpa>_@4o(bGZ5V&GV-2KGrhmi zXP^H6=cB5=15gWNScs79R8H0nM5<+6+ltq7VS_d%hqv_Kw9??wz!?rI4lzfEH3F)mt!7&@p3 z3u-kkJV@&+x`zO)ePEJj`Di{>RP=P*goNQ%vU!p^r@EYzB6kP=R&c33yX z3Cc3qft)J2zYlX+>Q1IjI(W}q6(`(sP1+&_%);Y|Ry-);L)K(yZlyZSp_KL0IuDsz z9vaO)2}UyDF$&=b(0B;0&1@tr(Aqb`=)@Ijo~f5PAgHq(jP;`B+hKBsnC^n*ksq%l zhPxsEDW4N-i~YrcvM3()yZx|;!N~}7+{nkqOvIg+aW-IXX&U2vDGI;Wq~tNPU10{8 zr0J_ACZe=_US(U5oM<^^BIO}ckz7)oJ+YWuIh*9R_?ma1ptqMHgCJ%;L zZF`l}!vB=7m$jSh8A6R-cSbMRbr;T^&|J9~ss5QoL`(^{I1@z$(TXW7IlQ9gRVF_< zYiBexK|p4mqT%StK8R+6QH1)4&vaml_C#4**Ocm_uP7nT!Ivr)0s&`g(m zM{9xh)DhaK+gl!7jep zJ}=>)hw~-G#w?$MGo@AVIh9HhTU{S*yv(1oV~eebL5X+Q6Ka;hl-r%TMpJw6Nn4v} zsn;rICYdj@QcSkIWX*v2QgSt9*f@^nn7&1&ldEj#+HO2#j&2vhxnG_H($dnjSa(Lt zoN%X3iEw@RhC`Cu3XLQ}^h8ek+o7?{FgjAhyIou@!y^+RRcn*MFYY8&ANKJ4_x!RQ zy?zMyk`P1$(!8tB8a4gjtwHIs+=&>5LLysEz!Y$s{2$Xi5^QnK<(#yrT2!&@D8#x_ z#@Fb?8H((Nsjin3=Cf)nvJ2uV<)4YJbVOVS$!6O4LsPrMD$*QCAl^rnXa@7O=qnP@ z;oEv-zT`ow{4MB?*=pbW)m9P4;_>yd!dA3m{zjgLwisZnY`a_bCPxxc*;hiWM94nH zO^j@p&<0f=&$8!t$jmJn0o4;%MCl>-!S@ zDcHgYY*U19QJT@@Q}`ZV8)@AyxG|Y}7oNlA##fI83m8R+lji5|k(e1nA$kl>pYpTU zz_;#+gLM90Ja*NvZsbFSQt`pWB60Pum;a0p_)Xmd?^}x2_|td_k|s}ODXvfqu^Vk01`;ik z<*{<5Y-w5<*C4u~ZaFRrtlF$bXoKV&-B5UP4GM?;$sv4T`Wo!?tsG0AyTLBzR8#~i z;YhmK$DJ@;>n2A@Sy>M{P2fZAT5N#jJcx_t)6y+aO&YFX_H5v@rCJ`Cm&(4#Hb}Q< zq9K!9`^+JrOI()*BZ`@oa!%j2?4Y>h7jaKIMBHtebu**GE~#oVwZqhxB4f#5;W(}f zWw}-K34ED=l{?7s-!}aZuvZ>%`_&-5A!Z_%7Fw-E~zprh|tWWAvZoQy{+(3kz&|m z;ip=J+g+}m>y@(-#B-P+=U;+j9jZ&30;4-VCT&+l7K-3O(#2S%M^6-$K@& zyvW*FzliBxITCek+VRVnOr2|eSL%?M5J+4W`~-f=oG=%`hRZP8+;%ka@S|~SOH`+1 zq&W~W$_k*wd)RFzm7khcG4aDew^&xGtyc=#%q_^tLXX5Nw5mc_NCyE-+si$o&kB(g#@co>h9NVm^g=u*UrMWHT(Byn zUE18ZsA06@NHe9iFa=FYwJIAf1w=4O<(N7`Ri~NG=6cxD>qXh z5l~M&Vxf#;BUfBX!H6|N9SDkkMz=$o6l z9x_7`zF&4b;*2}3J`FRtM^qk{@hl%W^=@V^Dx*jP=BFj{j(6m@>WL=EgHDK1hQcnm zOzJu$@|8++3hFN^_i9~Z`wIjlT7t>Nq3pA=jT$pdJa>0UL-F)M%VPmBp0f{rsqGCz z-NwF4B3Zav%?XLCwz;|@i{mNKf`>%&Y>E6>d!{!&*%cr3^>38ez7K|=VTs+5ryE=L z-Pjb~#nl+x(5dL>Fm7Y+jBQkfIc&4W>qYGqrid0?v$k(YMTebT=52odUNC!P|0#Ss z*K+tlK3Pq1C(g}$&Wv1m!2!53l!#um1+pha90n4||*;ClQDZ_m{GRD(=5 ziqhCfI?U_P51Eya`$JQ8`XsOXznK-&wTwwabs@o`I4g`E}=^Nrr(P#2KYf-0qYqVqz#SHyBef@0K)0oNzEc*L!7!`aNhG*Hd>qHG7 zh|Q9*9o-DOD=cri)PexobLo_?#dU2@mIl(zIi)S=Do_FB@C8Pt%w1UA~kr`9@CgfQp1gT_jnF3WJ z)jDf*$<-pSC~l%={U2s%N!_<(?bE5YMfgHPEv7ZhoCOQ>5`XVa{D@Fka0_d9th@1u zJqvKQTQ7SJPUm<1v1s=zer=oZ6AG^XpZB%xse5E^m>by;x4hR&cPas4JEl;#cbPQH zHBeDxzC+~Ieot$)C`oZ@A*Qgec59mvjmlS$Z}%wg^LK5ar>k%%AgI6QPB~tC%j_@r z4ezNPVs@O9L=37i(5koi-}GzVI2EEw_6$rOoy07xlo?c#3*f<%TtrjWVSKqMFmsW+ ze>`iJ%&foF|IFOpR?pE~64UZ|z$Oce8udzB_5z@tZsy{fE&MayfyHc|;ROxP5(RR@ zEeqHo$PERD-22MYqV4Qi(Hnh8Ni! zyJMO$WJxWT8qQI-Jh;6WFdPU%6is34oYF~)h>bM%+@p3)nL`0-c6Hlb9p~BNZqX|w zXl&|@*4!9;VR=$g*Lsjmyv}LKEkbP6m-e?<+qPM;C?PuO46RxiF@pocT!u7KaKT#C zvs1swgiuzCKP_CuiK2qB9LtQxls~3BIHlUPX6qm}7G_s+p+IuRgPQIPM{T0rV|Oy( zOtHCp;&YXMlBxK`^#%6QXepuK*u<1?G@>*de4Xj5aMbP#^Gc88&x@dP^urS84o*ZR zYNm6KJUWd_(<~DwjjQgOdgM;Gk|LH2H3m24NyrLI6heFQebsXAnBGW_p{ZEsF$#A6 znETWY8g!=si=L+^MdLOPk|Bs;-AH?I-2eU6gzZUJf;Pc$&-d6~#mW$sVJj#3OqC zFjl0eM+aIDptJ6wyYO=uJrm|oAyY`5guU>#okSro=l$_q3&C1sgn{5dQG|S_#@Kme zN>H}~2}Q9U$7{}m6%joa3E;T-t`racpxJlEJ(kU$ImXq9QQmi{8E zmbX)}V_WY)<25}Ut1#}T199mSrjCw?H_oESJ!mFznvuKU?Q89VE^tkPli1^!nT?}= z$+=-WRIlFfqc=WfXV$N2EADLQmMLUJ^85WwDyyFSmaKlgJcn#$d33BfiI#Y;1ltrZ zQvu5(-0pj{SV5WOS&|^+7m#Bw4=OnLD0a<0H{CQGv>Ie{r9fGg@xCC#djaGdyg-l%s=s1e}^vHNm9MPiFm6*6?#!1k7U=j zl_uvFzFy=n%I57e+x$Jye0qeF~NS9@8D<%|E}yDF26aGrkA*mz@k0m4XX- z$S8o|-?^Le2_(YZi?C!M6ZSEQ6dHm%8d}p~41;zVeym-Tkz(y!i>!0- ze%n&mw#sX+l00>K>>f15XK7hp%zJ5y(O4eO$QWMIl9?>;ilu!ahEY>vlzWP))Fxf) zJ<~F)Deg2Bvl5-|lwHumLfmp|lu)7r;V^fY5jp{U;^k3l8o9$JlvOGQWWphXHZwkE z)Ej%w=}9*7c&oiEP>HSaq2VU8xt>>#`d9vViy&_?Ebi$Lq;B)!6MX|`Fm=D8pY|B zfP^Af1cj6B2(j=(or6hH0ZgGPXh|^GRhCLDc!x}Sd2P#NrMOSu z(}w!CBHxkn8^ZQ_K7ztGPliftN;kt-@=xXQ*s*_PAv^}43Gy#(Fco_Jt3Ntox8%F? zwQlW8)aD`bAqA$57vAzdX}wXg>|d@X}6RP3mvB&mTI#SLHi1`p95-iHtv5oo$QBPvg=0I~Bu6$JO=tcIxojXI!uR z>w}@?p7c!ngsVo*+tU5**5kZ7zD{JoqX70|VVg8%YWX6HJ|GmwkQ1V~Ty8IBx33F` zy5W7b7j8;>2lz%^{>>+-p-;H=<06fXhYw&{8@$Pw50UYm0DBYxk>N$mJfNUAT8o zJfeU>&a$VhWEmCWm;#qzX#?4e=iCr;WfW!&GZVeOQe@(jnfk=Ry!nlJq`dV>T2?=A zu{@%-dhALz>Z6t6%T)dwGvhzbc`96@zOVpm7a`buS8>c^OWIGm z6lIA0EIb?U>XxZT$+l=*g@19wwsf#`AWe0&8^!K;cwxjKtrogs6zOP})TtJ`ln+O- zrf>0LXHY`6j1FWLFfSHIZI%d&p#U;_`*WZj718 z7}9G)0`7>W>DN)64LbL$Ys?2PY-z%#52J&N%W9dhibzJ zThd~bZ{KPIrQ1@G#>t|7;8~w8O>hS}ut&!8BmwkN28DGGmkvwt`CKwXnkT(lFbQ8s z^3~7W8}au%V;20`JQ*T?Prg0EHydf9(m+(Ddk_npK6!&atwEOJ23$?BuL5{!E=w5s zYdmDcdyqSw&bWEL#G)cLG|ker?s@7RX}c-tyNVQga-TV(!JCBHBz^EdbL@l`F_}eI zlV}%3oQiJ>2`wpm$RFymY2+^VP~|k(qdzwte`DH;Jbe`vOf|iPuMATfQ@B?(OWZET8q`HUIXG zYFuGOGBB!pXSM!*zTs5UcdJf6$*K+Nn-0;aIZN&HLI5a>w8>k z*!@W#7zwpbDk`vqbX2>8wnW>i-;#q{%$neHrGnG^v)d|d@T}3&r_GF=J6&_xXcx;vC-gmDh+AQDgQQWT*gr z^Ded@%V32rYn?Z`o$Z@~r8MJClWYeZ)bP=@w9ryx8!HB<)@#??8|TL-9#|f0?QBa` zd%cJmv`rVY^fH@TP3=#%R_5?#O zwqsoKTgWXMD^h8ez{4EtZhiQLdA?octc(e~vcZaTqfiB7mn|%r6l9A-9EAd|cEbKy z%jJo1aDuUs*1uQ<$~Ohj#oNt&>g3eHsuZoGg~_jM;6q@^%f~Bxsp~AptM3nb*eR>+ zsbCL{Y3#x2w~Dc(Zt$8FeNOFm5tP(R8PJ^k{T5d}pCRATgFW_LFLn>F0@eiao4xn6 z;W<<-TCulN;>Q@#6V|9a%Jijc18q#9ns3j|>Z;#j`y>@3%cJHM-B2oU@XqciXYk5janRQ|+#>jXW z2mF!nxJyD$eBsUx?lXFb0Ma&+)ugX|BG zczl5jEbQ;}*LSh`N{qe(!x?>UDCHXqObMCdmY~nxK-O-F)@|Orsn2wX!d|Fqy~)xO z#trfX{UKcWhI-y0#4=1cl;iar+-UvDzTbjBq_4;E0z`Q_`h*uB#?TsJiLXT2YPKgb zJ?S>Swb5U%_y?3)-JK9L=quKq{v?Ie^7-ni+vytgwb-71$yL80wTv!`&U@|FK|V4n z1aHBTP3@c3qy(IC#bH?%+BSt0QXDiDGAat;vJ`|Vs1ErBitlazPBFD3vN9A2kVFJ5 z2GIyKm!-qP8>w+m3PD@@wlla(vuP_mHeU=WV`Rwms4Ix@bTslVl}Yt5Az@rcg&_5t zZEm2G?LlSI=CK4lB6ni1Yx{kbQGNNWoW`~0@cGLmyI|kXNzuoAIT}B_Z5IU0Nna3D z$v*~J{9u6`0-|j0`ZbPhmS2)yxG7I2Z(6qr2Y9gNms`G~MnDBo^;63TlWyfhHI@NZz)`P_w5}YG z+$X>wE1Z4B*7k<2DK&<-)=G80cUY*bUS@Nux#L0W;nJ7=GioSMa>)6zLr5WOY;EDJ z2heiOb`vh1#h2~2@=Eq4>1LALL?Z-A&Hf(taJ(DEQrN0zn%n#=^r2KV!8CQCQE<`j zkOd4YD$2Wv$?UNySQLq4D~etjX1p))bgqVWW-ln+5;KV8>g3xYDCq`ytA!Uo853LX zrmch2A8JI%6n@LnlXBt)Odb8@vKeBVrzcH$FshibHf5oa%rt8fD$7esb!#-i+2Qea z8!e+(SM03i4Wj{+cD4Kj1q;?eKIwbmDdmIcd*WYRv)3JE7rg%keTChXc{JT&+N!mv zCO`$+mdfldarK8FH>M4?ZPvz8Z>w*7>nNY9GQVBsB6AuR$h;MFxw~z}S1ZWxdOu{p zoN)(tpm_*$7@wh46o|1$T9lgN`6UI` znK3G?in_GeMueF$Ir>yC$E5EX+NdWQoZ)=)S6C3_0}Ci*$@m^TjmHGl~6UF zJu_+PFvs@w6fxSb<^&YpZv1Peh)p`e3XyF698Dqr1u0~JVx>a(gG@jUO%r7x2&4*O zi*t*)75II&frMCT`H8w&ZXv2(i zFCRAMK1un#U)R&xVPU#h6HAV*%qCsea;6~g;y_r;+$V+^w%^M|c7HKrogkz^*txA> z6U1Kj!JoiR9fi6j=F$WxwhwDWg0fz+3{hzlW0XK5d)dOQD^74<%esr)Hw>`-fk?8# zuH@{k8J#v(!Iv*CbelgKPwjId#`&UOZIadgDo&(rqbo>7!2{N4Y|v6K*UFJdrtS$-FTE7< zK1%lRXskOfbI1_JHKHUm{0w8rxE1Rz|g1uE%G?BUv}vJS+VnY72=~D!hxD#iwBty=GJ# zs~A_SPcZ{4SF~^=>?ut^&Vo>>q#p3mY5tbtR?VK`WhIxwUZt7kJSp8q>o}Ek1{%R= z%uW->qN|u)kQo$lc$gHNE#@h)N$5kzoOcrmm-`n2n zYDuSg8zz-f&?m^}sapGEpaBr7>J%sjpO>Ovi}PeFL38 zr9rP)5F9O$v0{f8D{tg2Qh)m{-*IILL7!@(=cL4l`UmNMQ*XO}H#1Q+839Lddvs+8Y{cR#=f?A z5p;gm;!KZXigq2hw_7M}pTz*}siV+`%){Yo0!<0AL}TBw!1Rc+xh}OyZS!`fZ%{WS z{=NjO{j~IK`mK;NbGj`0)9zrGaA_kRuc4$lbZXHS4(wHNvKaD7+*NE@prB*Mr&qrlUe2 zr`#ILV}z`7aIH}7$WZKRW-*k;YYCg_mmE9a)hO|h4$!dNV~9&_nfW);F&wDh^4<^y z6SL2+RE3fzklP~;Ei!3|6QS=(TD7(xVhj8SCPThKOHigx+Mva)sylli8UDS?xG8Md z)7WwySCqxvQBg0%lW3j5I1Gg*)KkrVF?WN8Vv=rcp}F~bYAD+`C=!OH7HuDrrF|ql zsjw{I`~^4Q{z8fHSyi2-aO82{7-e!BJvtZS9w*3*aoOOD_3 z;aW5@gLP5RE7rpIZ@IoU|Mmuw*yOe=Kn_gI|S6QZ7X^-#u7IELr6JCTLUl1QRvQw=TLP4Z7 zYYO2=J6i8E*4LN=9cHK+8?NXg_K087lPMIXEXCrsIb^~_i_D;*2$i{G6w+AcnCnoF zEI9{dYpga%d)>l2Oj7K%6RiWwG{ndJuwLzLOSG|YW@aV*+5#p* z>ji$I8Z%&Qp_7I8d4*}gtmVp5{c}z>-RTTr{?3#3WEE@Y#&0efKGpV~ACxsOgAkE| zZb#uNYn5+jq_biALf_>|Fe3%O=+`-xyk$OFbfJ90@sR;Ne=t%j`Sz-i2*cBDG}!>A zCmTs=ZRN=kTudIlY&3Y}hIh><*VU*D;B1eLXj2!i>ZEUi!`0NL0PO{9T|Bjlvgs%J-eRx4WljhLRCwA%N=c>ApWzr%u(ay0=?9 z^)mvT7=>OGeQ2q0bm8I@QvOKZevy5@CCt~Cu6m^wqdrnfwYoC>|ghFtvYUN*1wigU|fN&nY?b zB^g#o^R@{yfZj;KEM~sb6l_KOx@5+cf4I7bL%RCxn03rlgq*obNLETM^o<)-wsU45 zoKur~&MaT8EJtRkl;ONV_{ZEzph{*hS|~?P$UVs<2v9u7V(`$%3PU!*Eb6A` zl*Tp2(q53TJhia3Zt@fMPjnWN^Qw=Q98BJrJ=9u85nyG^S||2K{cIBq#<4P5APExk zU(ZFH_k0{b1~ppE0N`CCVl@&BYFqx&b#i4?y%)QA4_yaayZP>G>pZ2>8qMIY6o!p} zxa3h!E)otOw%AP7=C3aP@D!s)A~i4VhYlS)x=!U>H!D|luIkZo&YcgS+!2$%PvQf1 z@%m#oC;$&%9AtJ-I9gM#*COewDCGt@qQzlZdSL1)gk-dYKVl4PdKkZ11QdkL7^%(a^hpk*RVD^*?7tU^t`_fzO#Lij zM>{e%qEqjctqn`skd>Apy;bAvLU`N|T8+*kufoC`tzrDMzo!#RA4?p)>mVQpX7PEM zWa#u}i;Z~GMReibC*%X2laqgvg4NZ@gQ}U7+l4nh=sRHa;HGiOO9PXs7G)of_0#0s ziMEQ+yMF1Da0&+x6=l=~Y$DmQY@qQzQH}GffQEiAAhAgO3}=dy&!d|AKEME%VZ@fz z5Uy=lXAhnL_R;y#sig}1NtMIZ>cRODdo32BdJJEdoVJ}tOb1&(33Zy1t{(QP-uG7@ zoPJON;?YBPo8>@f|*vfo6S=`J-ejj!fI*O z5~z-)bOe@Obrx0(L*mci_>}l^>*_Iq=hMgadBue{r8d|u9=Q?Am36gnr^LFcDJee9 z{BMoOMy}gQKMZa~LfN*bof!kPYMmMEdzq|js1T^HTfO0z&!0vV%9gv-Qa6%AZYaZy zjrgFooF~tm(W9jpO^P&2htf=o7JK*7n?4#xq7p|EW($)s{JTgT;e{pDT2?U6RLg%K z3KrNsz^z5K!2Tx3g5P*To7ymYsVF1V6fQ^XH}2DTaWlT>tdJOQldo$zB$c6in$6}J z8^G{JjQd+cSW}%X<yrf+8Fi^^gN_Wpvj0rv)=?kg_YdVQS? z=fdRupWyq^;9^9JH%pDi_H}~P5m|{_vWS{|?`Y+v&pyG#Hm+h^=C6!)5eaGVS8Y&R zBx|JLWw;IyiI%pOZI~U$Mfg#*+_glb#v25~BjbCtWd9+c9mzY4{~zYy)on(BaxrPm zTc(TD^0Qu}EBDdE(v@wRqsB6-j7_Il zK_e5JbN;MHrwpRDGY`iwL|9l_qVBY&&9KEaKyG(}8Rab92`)ryf&u1kkWnBG{qF+_ zlXEOu!-F-Oobjy(b3rWVin%933o=*!inK*sF29WLigG5!K@fecBl#SG=dx7 zyvA{(V0UXgTOo{Ar*pUre8r%wZH z2_96q9-TNGepTrjr9mw5>j?MUwD{7sNNiL3Gm|oJJYJYsk2m>D>1@>|-q4PjV|qAX z_N>=>j<$+wJ-Wb0Jgt7i%L*@zeoIzhaUS-?p9cfeS*y~NP~qbnkQ-jT@RjL^EX&X8 zDK`^Gk`^47U$ZYnp6QEt@PtEt>=Jk76T#$opc~bf+`Hv}J;lNo*EI;^guAFKLoCJ; z1R}8bYz9@YpG^l{{)Xxp1ZTcWTs6xm9=KWPp7}0&a3pybE=?I=vNBH8D|e$q{`Uu|#QTPhjnzvTi(7@`j#DC> z!sQcVeeZ_Jg)u9JEdfQv`4hIWj!sRqgxfb;R}f~$qIg&bJ3FL=Acz}*w=Fe>f-8M< zQQrA~K9B?ZBJQew6sW#Sx|c!NxsB-^9LQu{og#O`Z1YW*YzDm1_bz|?yWgXGF6tZ* z)8C&EyK`5CCwXrqqmKYc^X{}`q0tHIu9vKfXKI4WOdIGT>8 zk8EY$;>i4dwgd1Bh3ary;Ns-z z>e3}bA{AuM>kqhZ*!l$AeHU}$=AB&-iz3pk{T5}I+cANu9J#Im9aRHa^snPxHgL`W za#48FWQ-f|3~|0x{X0I)+#7LJ8IhF2L?L|KSa_1Ge*|S4nL-AlcToyu09Uu1{dUE; z#&#@H?9&K=in*Jy2_mv?guHomY=@x%f*Z3JniEtC*dBX6^6ZWhxAbRUO4UHVm(rC` LKS=A_HEaI`v7!?- diff --git a/freemius/languages/freemius-ta.mo b/freemius/languages/freemius-ta.mo index 5c0c37c9c10713c8689f22ac67024f7d01455249..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 93241 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 diff --git a/freemius/languages/freemius-zh_CN.mo b/freemius/languages/freemius-zh_CN.mo index 4cc40abb8a665966c8f71ddfd6b1e62bad8cddc8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 56773 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 Dg3c$F diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot index 24d1d20..e69de29 100644 --- a/freemius/languages/freemius.pot +++ b/freemius/languages/freemius.pot @@ -1,2556 +0,0 @@ -# Copyright (C) 2021 freemius -# This file is distributed under the same license as the freemius package. -msgid "" -msgstr "" -"Project-Id-Version: freemius\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language-Team: Freemius Team \n" -"Last-Translator: Vova Feldman \n" -"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" -"X-Poedit-Basepath: ..\n" -"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" -"X-Poedit-SearchPath-0: .\n" -"X-Poedit-SearchPathExcluded-0: *.js\n" -"X-Poedit-SourceCharset: UTF-8\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: includes/class-freemius.php:1919, templates/account.php:912 -msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." -msgstr "" - -#: includes/class-freemius.php:1926 -msgid "Would you like to proceed with the update?" -msgstr "" - -#: includes/class-freemius.php:2138 -msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." -msgstr "" - -#: includes/class-freemius.php:2140 -msgid "Error" -msgstr "" - -#: includes/class-freemius.php:2540 -msgid "I found a better %s" -msgstr "" - -#: includes/class-freemius.php:2542 -msgid "What's the %s's name?" -msgstr "" - -#: includes/class-freemius.php:2548 -msgid "It's a temporary %s. I'm just debugging an issue." -msgstr "" - -#: includes/class-freemius.php:2550 -msgid "Deactivation" -msgstr "" - -#: includes/class-freemius.php:2551 -msgid "Theme Switch" -msgstr "" - -#: includes/class-freemius.php:2560, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 -msgid "Other" -msgstr "" - -#: includes/class-freemius.php:2568 -msgid "I no longer need the %s" -msgstr "" - -#: includes/class-freemius.php:2575 -msgid "I only needed the %s for a short period" -msgstr "" - -#: includes/class-freemius.php:2581 -msgid "The %s broke my site" -msgstr "" - -#: includes/class-freemius.php:2588 -msgid "The %s suddenly stopped working" -msgstr "" - -#: includes/class-freemius.php:2598 -msgid "I can't pay for it anymore" -msgstr "" - -#: includes/class-freemius.php:2600 -msgid "What price would you feel comfortable paying?" -msgstr "" - -#: includes/class-freemius.php:2606 -msgid "I don't like to share my information with you" -msgstr "" - -#: includes/class-freemius.php:2627 -msgid "The %s didn't work" -msgstr "" - -#: includes/class-freemius.php:2637 -msgid "I couldn't understand how to make it work" -msgstr "" - -#: includes/class-freemius.php:2645 -msgid "The %s is great, but I need specific feature that you don't support" -msgstr "" - -#: includes/class-freemius.php:2647 -msgid "What feature?" -msgstr "" - -#: includes/class-freemius.php:2651 -msgid "The %s is not working" -msgstr "" - -#: includes/class-freemius.php:2653 -msgid "Kindly share what didn't work so we can fix it for future users..." -msgstr "" - -#: includes/class-freemius.php:2657 -msgid "It's not what I was looking for" -msgstr "" - -#: includes/class-freemius.php:2659 -msgid "What you've been looking for?" -msgstr "" - -#: includes/class-freemius.php:2663 -msgid "The %s didn't work as expected" -msgstr "" - -#: includes/class-freemius.php:2665 -msgid "What did you expect?" -msgstr "" - -#: includes/class-freemius.php:3520, templates/debug.php:20 -msgid "Freemius Debug" -msgstr "" - -#: includes/class-freemius.php:4272 -msgid "I don't know what is cURL or how to install it, help me!" -msgstr "" - -#: includes/class-freemius.php:4274 -msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." -msgstr "" - -#: includes/class-freemius.php:4281 -msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." -msgstr "" - -#: includes/class-freemius.php:4386 -msgid "Yes - do your thing" -msgstr "" - -#: includes/class-freemius.php:4391 -msgid "No - just deactivate" -msgstr "" - -#: includes/class-freemius.php:4436, includes/class-freemius.php:4930, includes/class-freemius.php:6191, includes/class-freemius.php:13368, includes/class-freemius.php:14110, includes/class-freemius.php:17542, includes/class-freemius.php:17647, includes/class-freemius.php:17822, includes/class-freemius.php:20056, includes/class-freemius.php:20414, includes/class-freemius.php:20424, includes/class-freemius.php:21109, includes/class-freemius.php:22015, includes/class-freemius.php:22148, includes/class-freemius.php:22304, templates/add-ons.php:57 -msgctxt "exclamation" -msgid "Oops" -msgstr "" - -#: includes/class-freemius.php:4505 -msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." -msgstr "" - -#: includes/class-freemius.php:4927 -msgctxt "addonX cannot run without pluginY" -msgid "%s cannot run without %s." -msgstr "" - -#: includes/class-freemius.php:4928 -msgctxt "addonX cannot run..." -msgid "%s cannot run without the plugin." -msgstr "" - -#: includes/class-freemius.php:5127, includes/class-freemius.php:5152, includes/class-freemius.php:21180 -msgid "Unexpected API error. Please contact the %s's author with the following error." -msgstr "" - -#: includes/class-freemius.php:5857 -msgid "Premium %s version was successfully activated." -msgstr "" - -#: includes/class-freemius.php:5869, includes/class-freemius.php:7774 -msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." -msgid "W00t" -msgstr "" - -#: includes/class-freemius.php:5884 -msgid "You have a %s license." -msgstr "" - -#: includes/class-freemius.php:5888, includes/class-freemius.php:16947, includes/class-freemius.php:16958, includes/class-freemius.php:20325, includes/class-freemius.php:20689, includes/class-freemius.php:20758, includes/class-freemius.php:20930 -msgctxt "interjection expressing joy or exuberance" -msgid "Yee-haw" -msgstr "" - -#: includes/class-freemius.php:6174 -msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." -msgstr "" - -#: includes/class-freemius.php:6178 -msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." -msgstr "" - -#: includes/class-freemius.php:6187, templates/add-ons.php:186, templates/account/partials/addon.php:381 -msgid "More information about %s" -msgstr "" - -#: includes/class-freemius.php:6188 -msgid "Purchase License" -msgstr "" - -#: includes/class-freemius.php:7125, templates/connect.php:171 -msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." -msgstr "" - -#: includes/class-freemius.php:7129 -msgid "start the trial" -msgstr "" - -#: includes/class-freemius.php:7130, templates/connect.php:175 -msgid "complete the install" -msgstr "" - -#: includes/class-freemius.php:7249 -msgid "You are just one step away - %s" -msgstr "" - -#: includes/class-freemius.php:7252 -msgctxt "%s - plugin name. As complete \"PluginX\" activation now" -msgid "Complete \"%s\" Activation Now" -msgstr "" - -#: includes/class-freemius.php:7334 -msgid "We made a few tweaks to the %s, %s" -msgstr "" - -#: includes/class-freemius.php:7338 -msgid "Opt in to make \"%s\" better!" -msgstr "" - -#: includes/class-freemius.php:7773 -msgid "The upgrade of %s was successfully completed." -msgstr "" - -#: includes/class-freemius.php:10255, includes/class-fs-plugin-updater.php:1087, includes/class-fs-plugin-updater.php:1282, includes/class-fs-plugin-updater.php:1289, templates/auto-installation.php:32 -msgid "Add-On" -msgstr "" - -#: includes/class-freemius.php:10257, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549 -msgid "Plugin" -msgstr "" - -#: includes/class-freemius.php:10258, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71 -msgid "Theme" -msgstr "" - -#: includes/class-freemius.php:13188 -msgid "An unknown error has occurred while trying to toggle the license's white-label mode." -msgstr "" - -#: includes/class-freemius.php:13202 -msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." -msgstr "" - -#: includes/class-freemius.php:13207 -msgid "User Dashboard" -msgstr "" - -#: includes/class-freemius.php:13208 -msgid "revert it now" -msgstr "" - -#: includes/class-freemius.php:13266 -msgid "An unknown error has occurred while trying to set the user's beta mode." -msgstr "" - -#: includes/class-freemius.php:13339 -msgid "Invalid new user ID or email address." -msgstr "" - -#: includes/class-freemius.php:13369, includes/class-freemius.php:22259 -msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." -msgstr "" - -#: includes/class-freemius.php:13370, includes/class-freemius.php:22260 -msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." -msgstr "" - -#: includes/class-freemius.php:13377, includes/class-freemius.php:22267 -msgid "Change Ownership" -msgstr "" - -#: includes/class-freemius.php:13977 -msgid "Invalid site details collection." -msgstr "" - -#: includes/class-freemius.php:14097 -msgid "We couldn't find your email address in the system, are you sure it's the right address?" -msgstr "" - -#: includes/class-freemius.php:14099 -msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" -msgstr "" - -#: includes/class-freemius.php:14373 -msgid "Account is pending activation." -msgstr "" - -#: includes/class-freemius.php:14485, templates/forms/premium-versions-upgrade-handler.php:47 -msgid "Buy a license now" -msgstr "" - -#: includes/class-freemius.php:14497, templates/forms/premium-versions-upgrade-handler.php:46 -msgid "Renew your license now" -msgstr "" - -#: includes/class-freemius.php:14501 -msgid "%s to access version %s security & feature updates, and support." -msgstr "" - -#: includes/class-freemius.php:16929 -msgid "%s activation was successfully completed." -msgstr "" - -#: includes/class-freemius.php:16943 -msgid "Your account was successfully activated with the %s plan." -msgstr "" - -#: includes/class-freemius.php:16954, includes/class-freemius.php:20754 -msgid "Your trial has been successfully started." -msgstr "" - -#: includes/class-freemius.php:17540, includes/class-freemius.php:17645, includes/class-freemius.php:17820 -msgid "Couldn't activate %s." -msgstr "" - -#: includes/class-freemius.php:17541, includes/class-freemius.php:17646, includes/class-freemius.php:17821 -msgid "Please contact us with the following message:" -msgstr "" - -#: includes/class-freemius.php:17642, templates/forms/data-debug-mode.php:162 -msgid "An unknown error has occurred." -msgstr "" - -#: includes/class-freemius.php:18178, includes/class-freemius.php:23340 -msgid "Upgrade" -msgstr "" - -#: includes/class-freemius.php:18184 -msgid "Start Trial" -msgstr "" - -#: includes/class-freemius.php:18186 -msgid "Pricing" -msgstr "" - -#: includes/class-freemius.php:18266, includes/class-freemius.php:18268 -msgid "Affiliation" -msgstr "" - -#: includes/class-freemius.php:18296, includes/class-freemius.php:18298, templates/account.php:242, templates/debug.php:324 -msgid "Account" -msgstr "" - -#: includes/class-freemius.php:18312, includes/class-freemius.php:18314, includes/customizer/class-fs-customizer-support-section.php:60 -msgid "Contact Us" -msgstr "" - -#: includes/class-freemius.php:18325, includes/class-freemius.php:18327, includes/class-freemius.php:23354, templates/account.php:121, templates/account/partials/addon.php:44 -msgid "Add-Ons" -msgstr "" - -#: includes/class-freemius.php:18361 -msgctxt "ASCII arrow left icon" -msgid "←" -msgstr "" - -#: includes/class-freemius.php:18361 -msgctxt "ASCII arrow right icon" -msgid "➤" -msgstr "" - -#: includes/class-freemius.php:18363, templates/pricing.php:109 -msgctxt "noun" -msgid "Pricing" -msgstr "" - -#: includes/class-freemius.php:18576, includes/customizer/class-fs-customizer-support-section.php:67 -msgid "Support Forum" -msgstr "" - -#: includes/class-freemius.php:19550 -msgid "Your email has been successfully verified - you are AWESOME!" -msgstr "" - -#: includes/class-freemius.php:19551 -msgctxt "a positive response" -msgid "Right on" -msgstr "" - -#: includes/class-freemius.php:20057 -msgid "seems like the key you entered doesn't match our records." -msgstr "" - -#: includes/class-freemius.php:20081 -msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." -msgstr "" - -#: includes/class-freemius.php:20316 -msgid "Your %s Add-on plan was successfully upgraded." -msgstr "" - -#: includes/class-freemius.php:20318 -msgid "%s Add-on was successfully purchased." -msgstr "" - -#: includes/class-freemius.php:20321 -msgid "Download the latest version" -msgstr "" - -#: includes/class-freemius.php:20407 -msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" -msgstr "" - -#: includes/class-freemius.php:20413, includes/class-freemius.php:20423, includes/class-freemius.php:20889, includes/class-freemius.php:20978 -msgid "Error received from the server:" -msgstr "" - -#: includes/class-freemius.php:20423 -msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." -msgstr "" - -#: includes/class-freemius.php:20651, includes/class-freemius.php:20894, includes/class-freemius.php:20949, includes/class-freemius.php:21056 -msgctxt "something somebody says when they are thinking about what you have just said." -msgid "Hmm" -msgstr "" - -#: includes/class-freemius.php:20664 -msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." -msgstr "" - -#: includes/class-freemius.php:20665, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46 -msgctxt "trial period" -msgid "Trial" -msgstr "" - -#: includes/class-freemius.php:20670 -msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." -msgstr "" - -#: includes/class-freemius.php:20674, includes/class-freemius.php:20733 -msgid "Please contact us here" -msgstr "" - -#: includes/class-freemius.php:20685 -msgid "Your plan was successfully activated." -msgstr "" - -#: includes/class-freemius.php:20686 -msgid "Your plan was successfully upgraded." -msgstr "" - -#: includes/class-freemius.php:20703 -msgid "Your plan was successfully changed to %s." -msgstr "" - -#: includes/class-freemius.php:20719 -msgid "Your license has expired. You can still continue using the free %s forever." -msgstr "" - -#: includes/class-freemius.php:20721 -msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." -msgstr "" - -#: includes/class-freemius.php:20729 -msgid "Your license has been cancelled. If you think it's a mistake, please contact support." -msgstr "" - -#: includes/class-freemius.php:20742 -msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." -msgstr "" - -#: includes/class-freemius.php:20768 -msgid "Your free trial has expired. You can still continue using all our free features." -msgstr "" - -#: includes/class-freemius.php:20770 -msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." -msgstr "" - -#: includes/class-freemius.php:20885 -msgid "It looks like the license could not be activated." -msgstr "" - -#: includes/class-freemius.php:20927 -msgid "Your license was successfully activated." -msgstr "" - -#: includes/class-freemius.php:20953 -msgid "It looks like your site currently doesn't have an active license." -msgstr "" - -#: includes/class-freemius.php:20977 -msgid "It looks like the license deactivation failed." -msgstr "" - -#: includes/class-freemius.php:21006 -msgid "Your %s license was successfully deactivated." -msgstr "" - -#: includes/class-freemius.php:21007 -msgid "Your license was successfully deactivated, you are back to the %s plan." -msgstr "" - -#: includes/class-freemius.php:21010 -msgid "O.K" -msgstr "" - -#: includes/class-freemius.php:21063 -msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." -msgstr "" - -#: includes/class-freemius.php:21072 -msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." -msgstr "" - -#: includes/class-freemius.php:21114 -msgid "You are already running the %s in a trial mode." -msgstr "" - -#: includes/class-freemius.php:21125 -msgid "You already utilized a trial before." -msgstr "" - -#: includes/class-freemius.php:21139 -msgid "Plan %s do not exist, therefore, can't start a trial." -msgstr "" - -#: includes/class-freemius.php:21150 -msgid "Plan %s does not support a trial period." -msgstr "" - -#: includes/class-freemius.php:21161 -msgid "None of the %s's plans supports a trial period." -msgstr "" - -#: includes/class-freemius.php:21211 -msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" -msgstr "" - -#: includes/class-freemius.php:21247 -msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." -msgstr "" - -#: includes/class-freemius.php:21266 -msgid "Your %s free trial was successfully cancelled." -msgstr "" - -#: includes/class-freemius.php:21582 -msgid "Version %s was released." -msgstr "" - -#: includes/class-freemius.php:21582 -msgid "Please download %s." -msgstr "" - -#: includes/class-freemius.php:21589 -msgid "the latest %s version here" -msgstr "" - -#: includes/class-freemius.php:21594 -msgid "New" -msgstr "" - -#: includes/class-freemius.php:21599 -msgid "Seems like you got the latest release." -msgstr "" - -#: includes/class-freemius.php:21600 -msgid "You are all good!" -msgstr "" - -#: includes/class-freemius.php:21903 -msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." -msgstr "" - -#: includes/class-freemius.php:22043 -msgid "Site successfully opted in." -msgstr "" - -#: includes/class-freemius.php:22044, includes/class-freemius.php:23050 -msgid "Awesome" -msgstr "" - -#: includes/class-freemius.php:22060, templates/forms/optout.php:41 -msgid "We appreciate your help in making the %s better by letting us track some usage data." -msgstr "" - -#: includes/class-freemius.php:22061 -msgid "Thank you!" -msgstr "" - -#: includes/class-freemius.php:22068 -msgid "We will no longer be sending any usage data of %s on %s to %s." -msgstr "" - -#: includes/class-freemius.php:22226 -msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." -msgstr "" - -#: includes/class-freemius.php:22232 -msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." -msgstr "" - -#: includes/class-freemius.php:22237 -msgid "%s is the new owner of the account." -msgstr "" - -#: includes/class-freemius.php:22239 -msgctxt "as congratulations" -msgid "Congrats" -msgstr "" - -#: includes/class-freemius.php:22275 -msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." -msgstr "" - -#: includes/class-freemius.php:22287 -msgid "Please provide your full name." -msgstr "" - -#: includes/class-freemius.php:22292 -msgid "Your name was successfully updated." -msgstr "" - -#: includes/class-freemius.php:22353 -msgid "You have successfully updated your %s." -msgstr "" - -#: includes/class-freemius.php:22412 -msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." -msgstr "" - -#: includes/class-freemius.php:22415 -msgid "Click here" -msgstr "" - -#: includes/class-freemius.php:22513 -msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." -msgstr "" - -#: includes/class-freemius.php:22514 -msgctxt "advance notice of something that will need attention." -msgid "Heads up" -msgstr "" - -#: includes/class-freemius.php:23090 -msgctxt "exclamation" -msgid "Hey" -msgstr "" - -#: includes/class-freemius.php:23090 -msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." -msgstr "" - -#: includes/class-freemius.php:23098 -msgid "No commitment for %s days - cancel anytime!" -msgstr "" - -#: includes/class-freemius.php:23099 -msgid "No credit card required" -msgstr "" - -#: includes/class-freemius.php:23106, templates/forms/trial-start.php:53 -msgctxt "call to action" -msgid "Start free trial" -msgstr "" - -#: includes/class-freemius.php:23183 -msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" -msgstr "" - -#: includes/class-freemius.php:23192 -msgid "Learn more" -msgstr "" - -#: includes/class-freemius.php:23378, templates/account.php:558, templates/account.php:708, templates/connect.php:179, templates/connect.php:461, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321 -msgid "Activate License" -msgstr "" - -#: includes/class-freemius.php:23379, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271 -msgid "Change License" -msgstr "" - -#: includes/class-freemius.php:23500, templates/account/partials/site.php:169 -msgid "Opt Out" -msgstr "" - -#: includes/class-freemius.php:23502, includes/class-freemius.php:23508, templates/account/partials/site.php:49, templates/account/partials/site.php:169 -msgid "Opt In" -msgstr "" - -#: includes/class-freemius.php:23738 -msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" -msgstr "" - -#: includes/class-freemius.php:23746 -msgid "Activate %s features" -msgstr "" - -#: includes/class-freemius.php:23759 -msgid "Please follow these steps to complete the upgrade" -msgstr "" - -#: includes/class-freemius.php:23763 -msgid "Download the latest %s version" -msgstr "" - -#: includes/class-freemius.php:23767 -msgid "Upload and activate the downloaded version" -msgstr "" - -#: includes/class-freemius.php:23769 -msgid "How to upload and activate?" -msgstr "" - -#: includes/class-freemius.php:23903 -msgid "%sClick here%s to choose the sites where you'd like to activate the license on." -msgstr "" - -#: includes/class-freemius.php:24072 -msgid "Auto installation only works for opted-in users." -msgstr "" - -#: includes/class-freemius.php:24082, includes/class-freemius.php:24115, includes/class-fs-plugin-updater.php:1261, includes/class-fs-plugin-updater.php:1275 -msgid "Invalid module ID." -msgstr "" - -#: includes/class-freemius.php:24091, includes/class-fs-plugin-updater.php:1297 -msgid "Premium version already active." -msgstr "" - -#: includes/class-freemius.php:24098 -msgid "You do not have a valid license to access the premium version." -msgstr "" - -#: includes/class-freemius.php:24105 -msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." -msgstr "" - -#: includes/class-freemius.php:24123, includes/class-fs-plugin-updater.php:1296 -msgid "Premium add-on version already installed." -msgstr "" - -#: includes/class-freemius.php:24473 -msgid "View paid features" -msgstr "" - -#: includes/class-freemius.php:24795 -msgid "Thank you so much for using %s and its add-ons!" -msgstr "" - -#: includes/class-freemius.php:24796 -msgid "Thank you so much for using %s!" -msgstr "" - -#: includes/class-freemius.php:24802 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." -msgstr "" - -#: includes/class-freemius.php:24806 -msgid "Thank you so much for using our products!" -msgstr "" - -#: includes/class-freemius.php:24807 -msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." -msgstr "" - -#: includes/class-freemius.php:24826 -msgid "%s and its add-ons" -msgstr "" - -#: includes/class-freemius.php:24835 -msgid "Products" -msgstr "" - -#: includes/class-freemius.php:24842, templates/connect.php:275 -msgid "Yes" -msgstr "" - -#: includes/class-freemius.php:24843, templates/connect.php:276 -msgid "send me security & feature updates, educational content and offers." -msgstr "" - -#: includes/class-freemius.php:24844, templates/connect.php:281 -msgid "No" -msgstr "" - -#: includes/class-freemius.php:24846, templates/connect.php:283 -msgid "do %sNOT%s send me security & feature updates, educational content and offers." -msgstr "" - -#: includes/class-freemius.php:24856 -msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" -msgstr "" - -#: includes/class-freemius.php:24858, templates/connect.php:290 -msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" -msgstr "" - -#: includes/class-freemius.php:25140 -msgid "License key is empty." -msgstr "" - -#: includes/class-fs-plugin-updater.php:206, templates/forms/premium-versions-upgrade-handler.php:57 -msgid "Renew license" -msgstr "" - -#: includes/class-fs-plugin-updater.php:211, templates/forms/premium-versions-upgrade-handler.php:58 -msgid "Buy license" -msgstr "" - -#: includes/class-fs-plugin-updater.php:327, includes/class-fs-plugin-updater.php:360 -msgid "There is a %s of %s available." -msgstr "" - -#: includes/class-fs-plugin-updater.php:329, includes/class-fs-plugin-updater.php:365 -msgid "new Beta version" -msgstr "" - -#: includes/class-fs-plugin-updater.php:330, includes/class-fs-plugin-updater.php:366 -msgid "new version" -msgstr "" - -#: includes/class-fs-plugin-updater.php:389 -msgid "Important Upgrade Notice:" -msgstr "" - -#: includes/class-fs-plugin-updater.php:1326 -msgid "Installing plugin: %s" -msgstr "" - -#: includes/class-fs-plugin-updater.php:1367 -msgid "Unable to connect to the filesystem. Please confirm your credentials." -msgstr "" - -#: includes/class-fs-plugin-updater.php:1549 -msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." -msgstr "" - -#: includes/fs-plugin-info-dialog.php:541 -msgid "Purchase More" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:542, templates/account/partials/addon.php:385 -msgctxt "verb" -msgid "Purchase" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:546 -msgid "Start my free %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:744 -msgid "Install Free Version Update Now" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:745, templates/account.php:641 -msgid "Install Update Now" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:754 -msgid "Install Free Version Now" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:365, templates/account/partials/addon.php:418 -msgid "Install Now" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:771 -msgctxt "as download latest version" -msgid "Download Latest Free Version" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:772, templates/account.php:101, templates/add-ons.php:37, templates/account/partials/addon.php:25 -msgctxt "as download latest version" -msgid "Download Latest" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:787, templates/add-ons.php:329, templates/account/partials/addon.php:356, templates/account/partials/addon.php:412 -msgid "Activate this add-on" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:458 -msgid "Activate Free Version" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:790, templates/account.php:125, templates/add-ons.php:330, templates/account/partials/addon.php:48 -msgid "Activate" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1002 -msgctxt "Plugin installer section title" -msgid "Description" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1003 -msgctxt "Plugin installer section title" -msgid "Installation" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1004 -msgctxt "Plugin installer section title" -msgid "FAQ" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1005, templates/plugin-info/description.php:55 -msgid "Screenshots" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1006 -msgctxt "Plugin installer section title" -msgid "Changelog" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1007 -msgctxt "Plugin installer section title" -msgid "Reviews" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1008 -msgctxt "Plugin installer section title" -msgid "Other Notes" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1023 -msgctxt "Plugin installer section title" -msgid "Features & Pricing" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1033 -msgid "Plugin Install" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1105 -msgctxt "e.g. Professional Plan" -msgid "%s Plan" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1131 -msgctxt "e.g. the best product" -msgid "Best" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1137, includes/fs-plugin-info-dialog.php:1157 -msgctxt "as every month" -msgid "Monthly" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1140 -msgctxt "as once a year" -msgid "Annual" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1143 -msgid "Lifetime" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1157, includes/fs-plugin-info-dialog.php:1159, includes/fs-plugin-info-dialog.php:1161 -msgctxt "e.g. billed monthly" -msgid "Billed %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1159 -msgctxt "as once a year" -msgid "Annually" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1161 -msgctxt "as once a year" -msgid "Once" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1167 -msgid "Single Site License" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1169 -msgid "Unlimited Licenses" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1171 -msgid "Up to %s Sites" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1181, templates/plugin-info/features.php:82 -msgctxt "as monthly period" -msgid "mo" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1188, templates/plugin-info/features.php:80 -msgctxt "as annual period" -msgid "year" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1242 -msgctxt "noun" -msgid "Price" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1290 -msgid "Save %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1300 -msgid "No commitment for %s - cancel anytime" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1303 -msgid "After your free %s, pay as little as %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1314 -msgid "Details" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:112, templates/debug.php:201, templates/debug.php:238, templates/debug.php:455, templates/account/partials/addon.php:36 -msgctxt "product version" -msgid "Version" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1325 -msgctxt "as the plugin author" -msgid "Author" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1332 -msgid "Last Updated" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:527 -msgctxt "x-ago" -msgid "%s ago" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1346 -msgid "Requires WordPress Version" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1347 -msgid "%s or higher" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1354 -msgid "Compatible up to" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1362 -msgid "Downloaded" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1366 -msgid "%s time" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1368 -msgid "%s times" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1379 -msgid "WordPress.org Plugin Page" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1388 -msgid "Plugin Homepage" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1397, includes/fs-plugin-info-dialog.php:1481 -msgid "Donate to this plugin" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1404 -msgid "Average Rating" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1411 -msgid "based on %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1415 -msgid "%s rating" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1417 -msgid "%s ratings" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1432 -msgid "%s star" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1434 -msgid "%s stars" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1446 -msgid "Click to see reviews that provided a rating of %s" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1459 -msgid "Contributors" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1489, includes/fs-plugin-info-dialog.php:1491 -msgid "Warning" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1489 -msgid "This plugin has not been tested with your current version of WordPress." -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1491 -msgid "This plugin has not been marked as compatible with your version of WordPress." -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1510 -msgid "Paid add-on must be deployed to Freemius." -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1511 -msgid "Add-on must be deployed to WordPress.org or Freemius." -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1532 -msgid "Newer Version (%s) Installed" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1533 -msgid "Newer Free Version (%s) Installed" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1540 -msgid "Latest Version Installed" -msgstr "" - -#: includes/fs-plugin-info-dialog.php:1541 -msgid "Latest Free Version Installed" -msgstr "" - -#: templates/account.php:102, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:311 -msgid "Downgrading your plan" -msgstr "" - -#: templates/account.php:103, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:312 -msgid "Cancelling the subscription" -msgstr "" - -#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account.php:105, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 -msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." -msgstr "" - -#: templates/account.php:106, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:315 -msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." -msgstr "" - -#: templates/account.php:107, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 -msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" -msgstr "" - -#: templates/account.php:108, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:316 -msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." -msgstr "" - -#: templates/account.php:109, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:317 -msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." -msgstr "" - -#. translators: %s: Plan title (e.g. "Professional") -#: templates/account.php:111, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 -msgid "Activate %s Plan" -msgstr "" - -#. translators: %s: Time period (e.g. Auto renews in "2 months") -#: templates/account.php:114, templates/account/partials/addon.php:38, templates/account/partials/site.php:291 -msgid "Auto renews in %s" -msgstr "" - -#. translators: %s: Time period (e.g. Expires in "2 months") -#: templates/account.php:116, templates/account/partials/addon.php:40, templates/account/partials/site.php:293 -msgid "Expires in %s" -msgstr "" - -#: templates/account.php:117 -msgctxt "as synchronize license" -msgid "Sync License" -msgstr "" - -#: templates/account.php:118, templates/account/partials/addon.php:41 -msgid "Cancel Trial" -msgstr "" - -#: templates/account.php:119, templates/account/partials/addon.php:42 -msgid "Change Plan" -msgstr "" - -#: templates/account.php:120, templates/account/partials/addon.php:43 -msgctxt "verb" -msgid "Upgrade" -msgstr "" - -#: templates/account.php:122, templates/account/partials/addon.php:45, templates/account/partials/site.php:318 -msgctxt "verb" -msgid "Downgrade" -msgstr "" - -#: templates/account.php:124, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:47, templates/account/partials/site.php:33 -msgid "Free" -msgstr "" - -#: templates/account.php:126, templates/debug.php:371, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:49 -msgctxt "as product pricing plan" -msgid "Plan" -msgstr "" - -#: templates/account.php:127 -msgid "Bundle Plan" -msgstr "" - -#: templates/account.php:250 -msgid "Free Trial" -msgstr "" - -#: templates/account.php:261 -msgid "Account Details" -msgstr "" - -#: templates/account.php:268, templates/forms/data-debug-mode.php:33 -msgid "Start Debug" -msgstr "" - -#: templates/account.php:270 -msgid "Stop Debug" -msgstr "" - -#: templates/account.php:277 -msgid "Billing & Invoices" -msgstr "" - -#: templates/account.php:288 -msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" -msgstr "" - -#: templates/account.php:290 -msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" -msgstr "" - -#: templates/account.php:293 -msgid "Delete Account" -msgstr "" - -#: templates/account.php:305, templates/account/partials/addon.php:231, templates/account/partials/deactivate-license-button.php:35 -msgid "Deactivate License" -msgstr "" - -#: templates/account.php:328, templates/forms/subscription-cancellation.php:125 -msgid "Are you sure you want to proceed?" -msgstr "" - -#: templates/account.php:328, templates/account/partials/addon.php:255 -msgid "Cancel Subscription" -msgstr "" - -#: templates/account.php:357, templates/account/partials/addon.php:340 -msgctxt "as synchronize" -msgid "Sync" -msgstr "" - -#: templates/account.php:372, templates/debug.php:505 -msgid "Name" -msgstr "" - -#: templates/account.php:378, templates/debug.php:506 -msgid "Email" -msgstr "" - -#: templates/account.php:385, templates/debug.php:369, templates/debug.php:555 -msgid "User ID" -msgstr "" - -#: templates/account.php:403, templates/account.php:721, templates/account.php:754, templates/debug.php:236, templates/debug.php:363, templates/debug.php:452, templates/debug.php:504, templates/debug.php:553, templates/debug.php:632, templates/account/payments.php:35, templates/debug/logger.php:21 -msgid "ID" -msgstr "" - -#: templates/account.php:410 -msgid "Site ID" -msgstr "" - -#: templates/account.php:413 -msgid "No ID" -msgstr "" - -#: templates/account.php:418, templates/debug.php:243, templates/debug.php:372, templates/debug.php:456, templates/debug.php:508, templates/account/partials/site.php:227 -msgid "Public Key" -msgstr "" - -#: templates/account.php:424, templates/debug.php:373, templates/debug.php:457, templates/debug.php:509, templates/account/partials/site.php:239 -msgid "Secret Key" -msgstr "" - -#: templates/account.php:427 -msgctxt "as secret encryption key missing" -msgid "No Secret" -msgstr "" - -#: templates/account.php:454, templates/account/partials/site.php:120, templates/account/partials/site.php:122 -msgid "Trial" -msgstr "" - -#: templates/account.php:481, templates/debug.php:561, templates/account/partials/site.php:260 -msgid "License Key" -msgstr "" - -#: templates/account.php:512 -msgid "Join the Beta program" -msgstr "" - -#: templates/account.php:518 -msgid "not verified" -msgstr "" - -#: templates/account.php:527, templates/account/partials/addon.php:190 -msgid "Expired" -msgstr "" - -#: templates/account.php:587 -msgid "Premium version" -msgstr "" - -#: templates/account.php:589 -msgid "Free version" -msgstr "" - -#: templates/account.php:601 -msgid "Verify Email" -msgstr "" - -#: templates/account.php:615 -msgid "Download %s Version" -msgstr "" - -#: templates/account.php:631 -msgid "Download Paid Version" -msgstr "" - -#: templates/account.php:649, templates/account.php:892, templates/account/partials/site.php:248, templates/account/partials/site.php:270 -msgctxt "verb" -msgid "Show" -msgstr "" - -#: templates/account.php:664 -msgid "What is your %s?" -msgstr "" - -#: templates/account.php:672, templates/account/billing.php:21 -msgctxt "verb" -msgid "Edit" -msgstr "" - -#: templates/account.php:676, templates/forms/user-change.php:27 -msgid "Change User" -msgstr "" - -#: templates/account.php:700 -msgid "Sites" -msgstr "" - -#: templates/account.php:713 -msgid "Search by address" -msgstr "" - -#: templates/account.php:722, templates/debug.php:366 -msgid "Address" -msgstr "" - -#: templates/account.php:723 -msgid "License" -msgstr "" - -#: templates/account.php:724 -msgid "Plan" -msgstr "" - -#: templates/account.php:757 -msgctxt "as software license" -msgid "License" -msgstr "" - -#: templates/account.php:886 -msgctxt "verb" -msgid "Hide" -msgstr "" - -#: templates/account.php:908, templates/forms/data-debug-mode.php:31 -msgid "Processing" -msgstr "" - -#: templates/account.php:911 -msgid "Get updates for bleeding edge Beta versions of %s." -msgstr "" - -#: templates/account.php:969 -msgid "Cancelling %s" -msgstr "" - -#: templates/account.php:969, templates/account.php:986, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 -msgid "trial" -msgstr "" - -#: templates/account.php:984, templates/forms/deactivation/form.php:150 -msgid "Cancelling %s..." -msgstr "" - -#: templates/account.php:987, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 -msgid "subscription" -msgstr "" - -#: templates/account.php:1001 -msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" -msgstr "" - -#: templates/account.php:1075 -msgid "Disabling white-label mode" -msgstr "" - -#: templates/account.php:1076 -msgid "Enabling white-label mode" -msgstr "" - -#: templates/add-ons.php:38 -msgid "View details" -msgstr "" - -#: templates/add-ons.php:48 -msgid "Add Ons for %s" -msgstr "" - -#: templates/add-ons.php:58 -msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." -msgstr "" - -#: templates/add-ons.php:229 -msgctxt "active add-on" -msgid "Active" -msgstr "" - -#: templates/add-ons.php:230 -msgctxt "installed add-on" -msgid "Installed" -msgstr "" - -#: templates/admin-notice.php:13, templates/forms/license-activation.php:222, templates/forms/resend-key.php:77 -msgctxt "as close a window" -msgid "Dismiss" -msgstr "" - -#: templates/auto-installation.php:45 -msgid "%s sec" -msgstr "" - -#: templates/auto-installation.php:83 -msgid "Automatic Installation" -msgstr "" - -#: templates/auto-installation.php:93 -msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." -msgstr "" - -#: templates/auto-installation.php:104 -msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." -msgstr "" - -#: templates/auto-installation.php:109 -msgid "Cancel Installation" -msgstr "" - -#: templates/checkout.php:180 -msgid "Checkout" -msgstr "" - -#: templates/checkout.php:180 -msgid "PCI compliant" -msgstr "" - -#. translators: %s: name (e.g. Hey John,) -#: templates/connect.php:112 -msgctxt "greeting" -msgid "Hey %s," -msgstr "" - -#: templates/connect.php:162 -msgid "Allow & Continue" -msgstr "" - -#: templates/connect.php:166 -msgid "Re-send activation email" -msgstr "" - -#: templates/connect.php:170 -msgid "Thanks %s!" -msgstr "" - -#: templates/connect.php:180, templates/forms/license-activation.php:46 -msgid "Agree & Activate License" -msgstr "" - -#: templates/connect.php:184 -msgid "Welcome to %s! To get started, please enter your license key:" -msgstr "" - -#: templates/connect.php:191 -msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." -msgstr "" - -#: templates/connect.php:192 -msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." -msgstr "" - -#: templates/connect.php:198 -msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." -msgstr "" - -#: templates/connect.php:199 -msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." -msgstr "" - -#: templates/connect.php:233 -msgid "We're excited to introduce the Freemius network-level integration." -msgstr "" - -#: templates/connect.php:236 -msgid "During the update process we detected %d site(s) that are still pending license activation." -msgstr "" - -#: templates/connect.php:238 -msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." -msgstr "" - -#: templates/connect.php:240 -msgid "%s's paid features" -msgstr "" - -#: templates/connect.php:245 -msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." -msgstr "" - -#: templates/connect.php:247 -msgid "During the update process we detected %s site(s) in the network that are still pending your attention." -msgstr "" - -#: templates/connect.php:256, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 -msgid "License key" -msgstr "" - -#: templates/connect.php:259, templates/forms/license-activation.php:22 -msgid "Can't find your license key?" -msgstr "" - -#: templates/connect.php:318, templates/connect.php:700, templates/forms/deactivation/retry-skip.php:20 -msgctxt "verb" -msgid "Skip" -msgstr "" - -#: templates/connect.php:321 -msgid "Delegate to Site Admins" -msgstr "" - -#: templates/connect.php:321 -msgid "If you click it, this decision will be delegated to the sites administrators." -msgstr "" - -#: templates/connect.php:346 -msgid "License issues?" -msgstr "" - -#: templates/connect.php:362 -msgid "Your Profile Overview" -msgstr "" - -#: templates/connect.php:363 -msgid "Name and email address" -msgstr "" - -#: templates/connect.php:370 -msgid "So you can manage and control your license remotely from the User Dashboard." -msgstr "" - -#: templates/connect.php:371 -msgid "Your Site Overview" -msgstr "" - -#: templates/connect.php:372 -msgid "Site URL, WP version, PHP info" -msgstr "" - -#: templates/connect.php:379 -msgid "Admin Notices" -msgstr "" - -#: templates/connect.php:380, templates/connect.php:398 -msgid "Updates, announcements, marketing, no spam" -msgstr "" - -#: templates/connect.php:387 -msgid "So you can reuse the license when the %s is no longer active." -msgstr "" - -#: templates/connect.php:388 -msgid "Current %s Status" -msgstr "" - -#: templates/connect.php:389 -msgid "Active, deactivated, or uninstalled" -msgstr "" - -#: templates/connect.php:397 -msgid "Newsletter" -msgstr "" - -#: templates/connect.php:405 -msgid "Plugins & Themes" -msgstr "" - -#: templates/connect.php:405 -msgid "optional" -msgstr "" - -#: templates/connect.php:406 -msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts." -msgstr "" - -#: templates/connect.php:407 -msgid "Title, slug, version, and is active" -msgstr "" - -#: templates/connect.php:424 -msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management." -msgstr "" - -#: templates/connect.php:426 -msgid "diagnostic data" -msgstr "" - -#: templates/connect.php:427 -msgid "Freemius is our licensing and software updates engine" -msgstr "" - -#: templates/connect.php:430 -msgid "What permissions are being granted?" -msgstr "" - -#: templates/connect.php:457 -msgid "Don't have a license key?" -msgstr "" - -#: templates/connect.php:460 -msgid "Have a license key?" -msgstr "" - -#: templates/connect.php:468 -msgid "Privacy Policy" -msgstr "" - -#: templates/connect.php:470 -msgid "License Agreement" -msgstr "" - -#: templates/connect.php:470 -msgid "Terms of Service" -msgstr "" - -#: templates/connect.php:866 -msgctxt "as in the process of sending an email" -msgid "Sending email" -msgstr "" - -#: templates/connect.php:867 -msgctxt "as activating plugin" -msgid "Activating" -msgstr "" - -#: templates/contact.php:78 -msgid "Contact" -msgstr "" - -#: templates/debug.php:17 -msgctxt "as turned off" -msgid "Off" -msgstr "" - -#: templates/debug.php:18 -msgctxt "as turned on" -msgid "On" -msgstr "" - -#: templates/debug.php:20 -msgid "SDK" -msgstr "" - -#: templates/debug.php:24 -msgctxt "as code debugging" -msgid "Debugging" -msgstr "" - -#: templates/debug.php:52, templates/debug.php:248, templates/debug.php:374, templates/debug.php:510 -msgid "Actions" -msgstr "" - -#: templates/debug.php:62 -msgid "Are you sure you want to delete all Freemius data?" -msgstr "" - -#: templates/debug.php:62 -msgid "Delete All Accounts" -msgstr "" - -#: templates/debug.php:69 -msgid "Clear API Cache" -msgstr "" - -#: templates/debug.php:77 -msgid "Clear Updates Transients" -msgstr "" - -#: templates/debug.php:84 -msgid "Sync Data From Server" -msgstr "" - -#: templates/debug.php:93 -msgid "Migrate Options to Network" -msgstr "" - -#: templates/debug.php:98 -msgid "Load DB Option" -msgstr "" - -#: templates/debug.php:101 -msgid "Set DB Option" -msgstr "" - -#: templates/debug.php:180 -msgid "Key" -msgstr "" - -#: templates/debug.php:181 -msgid "Value" -msgstr "" - -#: templates/debug.php:197 -msgctxt "as software development kit versions" -msgid "SDK Versions" -msgstr "" - -#: templates/debug.php:202 -msgid "SDK Path" -msgstr "" - -#: templates/debug.php:203, templates/debug.php:242 -msgid "Module Path" -msgstr "" - -#: templates/debug.php:204 -msgid "Is Active" -msgstr "" - -#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:35 -msgid "Plugins" -msgstr "" - -#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:56 -msgid "Themes" -msgstr "" - -#: templates/debug.php:237, templates/debug.php:368, templates/debug.php:454, templates/debug/scheduled-crons.php:80 -msgid "Slug" -msgstr "" - -#: templates/debug.php:239, templates/debug.php:453 -msgid "Title" -msgstr "" - -#: templates/debug.php:240 -msgctxt "as application program interface" -msgid "API" -msgstr "" - -#: templates/debug.php:241 -msgid "Freemius State" -msgstr "" - -#: templates/debug.php:245 -msgid "Network Blog" -msgstr "" - -#: templates/debug.php:246 -msgid "Network User" -msgstr "" - -#: templates/debug.php:283 -msgctxt "as connection was successful" -msgid "Connected" -msgstr "" - -#: templates/debug.php:284 -msgctxt "as connection blocked" -msgid "Blocked" -msgstr "" - -#: templates/debug.php:320 -msgid "Simulate Trial Promotion" -msgstr "" - -#: templates/debug.php:332 -msgid "Simulate Network Upgrade" -msgstr "" - -#: templates/debug.php:357 -msgid "%s Installs" -msgstr "" - -#: templates/debug.php:359 -msgctxt "like websites" -msgid "Sites" -msgstr "" - -#: templates/debug.php:365, templates/account/partials/site.php:156 -msgid "Blog ID" -msgstr "" - -#: templates/debug.php:370 -msgid "License ID" -msgstr "" - -#: templates/debug.php:434, templates/debug.php:533, templates/account/partials/addon.php:435 -msgctxt "verb" -msgid "Delete" -msgstr "" - -#: templates/debug.php:448 -msgid "Add Ons of module %s" -msgstr "" - -#: templates/debug.php:500 -msgid "Users" -msgstr "" - -#: templates/debug.php:507 -msgid "Verified" -msgstr "" - -#: templates/debug.php:549 -msgid "%s Licenses" -msgstr "" - -#: templates/debug.php:554 -msgid "Plugin ID" -msgstr "" - -#: templates/debug.php:556 -msgid "Plan ID" -msgstr "" - -#: templates/debug.php:557 -msgid "Quota" -msgstr "" - -#: templates/debug.php:558 -msgid "Activated" -msgstr "" - -#: templates/debug.php:559 -msgid "Blocking" -msgstr "" - -#: templates/debug.php:560, templates/debug.php:631, templates/debug/logger.php:22 -msgid "Type" -msgstr "" - -#: templates/debug.php:562 -msgctxt "as expiration date" -msgid "Expiration" -msgstr "" - -#: templates/debug.php:590 -msgid "Debug Log" -msgstr "" - -#: templates/debug.php:594 -msgid "All Types" -msgstr "" - -#: templates/debug.php:601 -msgid "All Requests" -msgstr "" - -#: templates/debug.php:606, templates/debug.php:635, templates/debug/logger.php:25 -msgid "File" -msgstr "" - -#: templates/debug.php:607, templates/debug.php:633, templates/debug/logger.php:23 -msgid "Function" -msgstr "" - -#: templates/debug.php:608 -msgid "Process ID" -msgstr "" - -#: templates/debug.php:609 -msgid "Logger" -msgstr "" - -#: templates/debug.php:610, templates/debug.php:634, templates/debug/logger.php:24 -msgid "Message" -msgstr "" - -#: templates/debug.php:612 -msgid "Filter" -msgstr "" - -#: templates/debug.php:620 -msgid "Download" -msgstr "" - -#: templates/debug.php:636, templates/debug/logger.php:26 -msgid "Timestamp" -msgstr "" - -#: templates/secure-https-header.php:28 -msgid "Secure HTTPS %s page, running from an external domain" -msgstr "" - -#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 -msgid "Support" -msgstr "" - -#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 -msgctxt "milliseconds" -msgid "ms" -msgstr "" - -#: includes/debug/debug-bar-start.php:41 -msgid "Freemius API" -msgstr "" - -#: includes/debug/debug-bar-start.php:42 -msgid "Requests" -msgstr "" - -#: templates/account/billing.php:22 -msgctxt "verb" -msgid "Update" -msgstr "" - -#: templates/account/billing.php:33 -msgid "Billing" -msgstr "" - -#: templates/account/billing.php:38, templates/account/billing.php:38 -msgid "Business name" -msgstr "" - -#: templates/account/billing.php:39, templates/account/billing.php:39 -msgid "Tax / VAT ID" -msgstr "" - -#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 -msgid "Address Line %d" -msgstr "" - -#: templates/account/billing.php:46, templates/account/billing.php:46 -msgid "City" -msgstr "" - -#: templates/account/billing.php:46, templates/account/billing.php:46 -msgid "Town" -msgstr "" - -#: templates/account/billing.php:47, templates/account/billing.php:47 -msgid "ZIP / Postal Code" -msgstr "" - -#: templates/account/billing.php:302 -msgid "Country" -msgstr "" - -#: templates/account/billing.php:304 -msgid "Select Country" -msgstr "" - -#: templates/account/billing.php:311, templates/account/billing.php:312 -msgid "State" -msgstr "" - -#: templates/account/billing.php:311, templates/account/billing.php:312 -msgid "Province" -msgstr "" - -#: templates/account/payments.php:29 -msgid "Payments" -msgstr "" - -#: templates/account/payments.php:36 -msgid "Date" -msgstr "" - -#: templates/account/payments.php:37 -msgid "Amount" -msgstr "" - -#: templates/account/payments.php:38, templates/account/payments.php:50 -msgid "Invoice" -msgstr "" - -#: templates/debug/api-calls.php:56 -msgid "API" -msgstr "" - -#: templates/debug/api-calls.php:68 -msgid "Method" -msgstr "" - -#: templates/debug/api-calls.php:69 -msgid "Code" -msgstr "" - -#: templates/debug/api-calls.php:70 -msgid "Length" -msgstr "" - -#: templates/debug/api-calls.php:71 -msgctxt "as file/folder path" -msgid "Path" -msgstr "" - -#: templates/debug/api-calls.php:73 -msgid "Body" -msgstr "" - -#: templates/debug/api-calls.php:75 -msgid "Result" -msgstr "" - -#: templates/debug/api-calls.php:76 -msgid "Start" -msgstr "" - -#: templates/debug/api-calls.php:77 -msgid "End" -msgstr "" - -#: templates/debug/logger.php:15 -msgid "Log" -msgstr "" - -#. translators: %s: time period (e.g. In "2 hours") -#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 -msgid "In %s" -msgstr "" - -#. translators: %s: time period (e.g. "2 hours" ago) -#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 -msgid "%s ago" -msgstr "" - -#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 -msgctxt "seconds" -msgid "sec" -msgstr "" - -#: templates/debug/plugins-themes-sync.php:23 -msgid "Plugins & Themes Sync" -msgstr "" - -#: templates/debug/plugins-themes-sync.php:28 -msgid "Total" -msgstr "" - -#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 -msgid "Last" -msgstr "" - -#: templates/debug/scheduled-crons.php:76 -msgid "Scheduled Crons" -msgstr "" - -#: templates/debug/scheduled-crons.php:81 -msgid "Module" -msgstr "" - -#: templates/debug/scheduled-crons.php:82 -msgid "Module Type" -msgstr "" - -#: templates/debug/scheduled-crons.php:83 -msgid "Cron Type" -msgstr "" - -#: templates/debug/scheduled-crons.php:85 -msgid "Next" -msgstr "" - -#: templates/forms/affiliation.php:82 -msgid "Non-expiring" -msgstr "" - -#: templates/forms/affiliation.php:85 -msgid "Apply to become an affiliate" -msgstr "" - -#: templates/forms/affiliation.php:107 -msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." -msgstr "" - -#: templates/forms/affiliation.php:122 -msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." -msgstr "" - -#: templates/forms/affiliation.php:125 -msgid "Your affiliation account was temporarily suspended." -msgstr "" - -#: templates/forms/affiliation.php:128 -msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." -msgstr "" - -#: templates/forms/affiliation.php:131 -msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." -msgstr "" - -#: templates/forms/affiliation.php:144 -msgid "Like the %s? Become our ambassador and earn cash ;-)" -msgstr "" - -#: templates/forms/affiliation.php:145 -msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" -msgstr "" - -#: templates/forms/affiliation.php:148 -msgid "Program Summary" -msgstr "" - -#: templates/forms/affiliation.php:150 -msgid "%s commission when a customer purchases a new license." -msgstr "" - -#: templates/forms/affiliation.php:152 -msgid "Get commission for automated subscription renewals." -msgstr "" - -#: templates/forms/affiliation.php:155 -msgid "%s tracking cookie after the first visit to maximize earnings potential." -msgstr "" - -#: templates/forms/affiliation.php:158 -msgid "Unlimited commissions." -msgstr "" - -#: templates/forms/affiliation.php:160 -msgid "%s minimum payout amount." -msgstr "" - -#: templates/forms/affiliation.php:161 -msgid "Payouts are in USD and processed monthly via PayPal." -msgstr "" - -#: templates/forms/affiliation.php:162 -msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." -msgstr "" - -#: templates/forms/affiliation.php:165 -msgid "Affiliate" -msgstr "" - -#: templates/forms/affiliation.php:168, templates/forms/resend-key.php:23 -msgid "Email address" -msgstr "" - -#: templates/forms/affiliation.php:172 -msgid "Full name" -msgstr "" - -#: templates/forms/affiliation.php:176 -msgid "PayPal account email address" -msgstr "" - -#: templates/forms/affiliation.php:180 -msgid "Where are you going to promote the %s?" -msgstr "" - -#: templates/forms/affiliation.php:182 -msgid "Enter the domain of your website or other websites from where you plan to promote the %s." -msgstr "" - -#: templates/forms/affiliation.php:184 -msgid "Add another domain" -msgstr "" - -#: templates/forms/affiliation.php:188 -msgid "Extra Domains" -msgstr "" - -#: templates/forms/affiliation.php:189 -msgid "Extra domains where you will be marketing the product from." -msgstr "" - -#: templates/forms/affiliation.php:199 -msgid "Promotion methods" -msgstr "" - -#: templates/forms/affiliation.php:202 -msgid "Social media (Facebook, Twitter, etc.)" -msgstr "" - -#: templates/forms/affiliation.php:206 -msgid "Mobile apps" -msgstr "" - -#: templates/forms/affiliation.php:210 -msgid "Website, email, and social media statistics (optional)" -msgstr "" - -#: templates/forms/affiliation.php:213 -msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." -msgstr "" - -#: templates/forms/affiliation.php:217 -msgid "How will you promote us?" -msgstr "" - -#: templates/forms/affiliation.php:220 -msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." -msgstr "" - -#: templates/forms/affiliation.php:232, templates/forms/resend-key.php:22 -msgid "Cancel" -msgstr "" - -#: templates/forms/affiliation.php:234 -msgid "Become an affiliate" -msgstr "" - -#: templates/forms/data-debug-mode.php:25 -msgid "Please enter the license key to enable the debug mode:" -msgstr "" - -#: templates/forms/data-debug-mode.php:27 -msgid "To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your \"My Profile\" section of your User Dashboard:" -msgstr "" - -#: templates/forms/data-debug-mode.php:32 -msgid "Submit" -msgstr "" - -#: templates/forms/data-debug-mode.php:36 -msgid "User key" -msgstr "" - -#: templates/forms/license-activation.php:23 -msgid "Please enter the license key that you received in the email right after the purchase:" -msgstr "" - -#: templates/forms/license-activation.php:28 -msgid "Update License" -msgstr "" - -#: templates/forms/license-activation.php:41 -msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." -msgstr "" - -#: templates/forms/license-activation.php:183 -msgid "Associate with the license owner's account." -msgstr "" - -#: templates/forms/optout.php:30 -msgctxt "verb" -msgid "Opt Out" -msgstr "" - -#: templates/forms/optout.php:31 -msgctxt "verb" -msgid "Opt In" -msgstr "" - -#: templates/forms/optout.php:34 -msgid "Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard." -msgstr "" - -#: templates/forms/optout.php:36 -msgid "Warning: Opting out will block automatic updates" -msgstr "" - -#: templates/forms/optout.php:37 -msgid "Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won't receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you'll need to check for %1$s updates and install them manually." -msgstr "" - -#: templates/forms/optout.php:39 -msgid "I'd like to keep automatic updates" -msgstr "" - -#: templates/forms/optout.php:44 -msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." -msgstr "" - -#: templates/forms/optout.php:45 -msgid "On second thought - I want to continue helping" -msgstr "" - -#: templates/forms/optout.php:49 -msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." -msgstr "" - -#: templates/forms/optout.php:74 -msgid "Plugins & themes tracking" -msgstr "" - -#: templates/forms/optout.php:261 -msgid "Saved" -msgstr "" - -#: templates/forms/premium-versions-upgrade-handler.php:40 -msgid "There is a new version of %s available." -msgstr "" - -#: templates/forms/premium-versions-upgrade-handler.php:41 -msgid " %s to access version %s security & feature updates, and support." -msgstr "" - -#: templates/forms/premium-versions-upgrade-handler.php:54 -msgid "New Version Available" -msgstr "" - -#: templates/forms/premium-versions-upgrade-handler.php:75 -msgctxt "close a window" -msgid "Dismiss" -msgstr "" - -#: templates/forms/resend-key.php:21 -msgid "Send License Key" -msgstr "" - -#: templates/forms/resend-key.php:57 -msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." -msgstr "" - -#: templates/forms/subscription-cancellation.php:37 -msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." -msgstr "" - -#: templates/forms/subscription-cancellation.php:47 -msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" -msgstr "" - -#: templates/forms/subscription-cancellation.php:52 -msgid "license" -msgstr "" - -#: templates/forms/subscription-cancellation.php:57 -msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." -msgstr "" - -#: templates/forms/subscription-cancellation.php:68 -msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." -msgstr "" - -#: templates/forms/subscription-cancellation.php:103 -msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." -msgstr "" - -#: templates/forms/subscription-cancellation.php:136 -msgid "Cancel %s?" -msgstr "" - -#: templates/forms/subscription-cancellation.php:143 -msgid "Proceed" -msgstr "" - -#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 -msgid "Cancel %s & Proceed" -msgstr "" - -#: templates/forms/trial-start.php:22 -msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." -msgstr "" - -#: templates/forms/trial-start.php:28 -msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." -msgstr "" - -#: templates/forms/user-change.php:26 -msgid "By changing the user, you agree to transfer the account ownership to:" -msgstr "" - -#: templates/forms/user-change.php:28 -msgid "I Agree - Change User" -msgstr "" - -#: templates/forms/user-change.php:30 -msgid "Enter email address" -msgstr "" - -#: templates/forms/user-change.php:81 -msgctxt "close window" -msgid "Dismiss" -msgstr "" - -#: templates/js/style-premium-theme.php:39 -msgid "Premium" -msgstr "" - -#: templates/js/style-premium-theme.php:42 -msgid "Beta" -msgstr "" - -#: templates/partials/network-activation.php:27 -msgid "Activate license on all sites in the network." -msgstr "" - -#: templates/partials/network-activation.php:28 -msgid "Apply on all sites in the network." -msgstr "" - -#: templates/partials/network-activation.php:31 -msgid "Activate license on all pending sites." -msgstr "" - -#: templates/partials/network-activation.php:32 -msgid "Apply on all pending sites." -msgstr "" - -#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 -msgid "allow" -msgstr "" - -#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 -msgid "delegate" -msgstr "" - -#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 -msgid "skip" -msgstr "" - -#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 -msgid "Click to view full-size screenshot %d" -msgstr "" - -#: templates/plugin-info/features.php:56 -msgid "Unlimited Updates" -msgstr "" - -#: templates/account/partials/activate-license-button.php:46 -msgid "Localhost" -msgstr "" - -#: templates/account/partials/activate-license-button.php:50 -msgctxt "as 5 licenses left" -msgid "%s left" -msgstr "" - -#: templates/account/partials/activate-license-button.php:51 -msgid "Last license" -msgstr "" - -#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account/partials/addon.php:29 -msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." -msgstr "" - -#: templates/account/partials/addon.php:185 -msgid "Cancelled" -msgstr "" - -#: templates/account/partials/addon.php:195 -msgid "No expiration" -msgstr "" - -#: templates/account/partials/site.php:189 -msgid "Owner Name" -msgstr "" - -#: templates/account/partials/site.php:201 -msgid "Owner Email" -msgstr "" - -#: templates/account/partials/site.php:213 -msgid "Owner ID" -msgstr "" - -#: templates/account/partials/site.php:286 -msgid "Subscription" -msgstr "" - -#: templates/forms/deactivation/contact.php:19 -msgid "Sorry for the inconvenience and we are here to help if you give us a chance." -msgstr "" - -#: templates/forms/deactivation/contact.php:22 -msgid "Contact Support" -msgstr "" - -#: templates/forms/deactivation/form.php:64 -msgid "Anonymous feedback" -msgstr "" - -#: templates/forms/deactivation/form.php:70 -msgid "Deactivate" -msgstr "" - -#: templates/forms/deactivation/form.php:72 -msgid "Activate %s" -msgstr "" - -#: templates/forms/deactivation/form.php:87 -msgid "Quick Feedback" -msgstr "" - -#: templates/forms/deactivation/form.php:91 -msgid "If you have a moment, please let us know why you are %s" -msgstr "" - -#: templates/forms/deactivation/form.php:91 -msgid "deactivating" -msgstr "" - -#: templates/forms/deactivation/form.php:91 -msgid "switching" -msgstr "" - -#: templates/forms/deactivation/form.php:369 -msgid "Submit & %s" -msgstr "" - -#: templates/forms/deactivation/form.php:390 -msgid "Kindly tell us the reason so we can improve." -msgstr "" - -#: templates/forms/deactivation/form.php:515 -msgid "Yes - %s" -msgstr "" - -#: templates/forms/deactivation/form.php:522 -msgid "Skip & %s" -msgstr "" - -#: templates/forms/deactivation/retry-skip.php:21 -msgid "Click here to use the plugin anonymously" -msgstr "" - -#: templates/forms/deactivation/retry-skip.php:23 -msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." -msgstr "" diff --git a/freemius/languages/index.php b/freemius/languages/index.php index 0316c6a..e69de29 100644 --- a/freemius/languages/index.php +++ b/freemius/languages/index.php @@ -1,3 +0,0 @@ -plugins ) ) { - $fs_active_plugins->plugins = array(); - } - } - - if ( empty( $fs_active_plugins->abspath ) ) { - /** - * Store the WP install absolute path reference to identify environment change - * while replicating the storage. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - $fs_active_plugins->abspath = ABSPATH; - } else { - if ( ABSPATH !== $fs_active_plugins->abspath ) { - /** - * WordPress path has changed, cleanup the SDK references cache. - * This resolves issues triggered when spinning a staging environments - * while replicating the database. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - $fs_active_plugins->abspath = ABSPATH; - $fs_active_plugins->plugins = array(); - unset( $fs_active_plugins->newest ); - } else { - /** - * Make sure SDK references are still valid. This resolves - * issues when users hard delete modules via FTP. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.7 - */ - $has_changes = false; - foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { - if ( ! file_exists( ( isset( $data->type ) && 'theme' === $data->type ? $themes_directory : WP_PLUGIN_DIR ) . '/' . $sdk_path ) ) { - unset( $fs_active_plugins->plugins[ $sdk_path ] ); - - if ( - ! empty( $fs_active_plugins->newest ) && - $sdk_path === $fs_active_plugins->newest->sdk_path - ) { - unset( $fs_active_plugins->newest ); - } - - $has_changes = true; - } - } - - if ( $has_changes ) { - if ( empty( $fs_active_plugins->plugins ) ) { - unset( $fs_active_plugins->newest ); - } - - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - } - } - - if ( ! function_exists( 'fs_find_direct_caller_plugin_file' ) ) { - require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-1.1.7.1.php'; - } - - if ( ! function_exists( 'fs_get_plugins' ) ) { - require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-2.2.1.php'; - } - - // Update current SDK info based on the SDK path. - if ( ! isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) || - $this_sdk_version != $fs_active_plugins->plugins[ $this_sdk_relative_path ]->version - ) { - if ( $is_theme ) { - $plugin_path = basename( dirname( $this_sdk_relative_path ) ); - } else { - $plugin_path = plugin_basename( fs_find_direct_caller_plugin_file( $file_path ) ); - } - - $fs_active_plugins->plugins[ $this_sdk_relative_path ] = (object) array( - 'version' => $this_sdk_version, - 'type' => ( $is_theme ? 'theme' : 'plugin' ), - 'timestamp' => time(), - 'plugin_path' => $plugin_path, - ); - } - - $is_current_sdk_newest = isset( $fs_active_plugins->newest ) && ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path ); - - if ( ! isset( $fs_active_plugins->newest ) ) { - /** - * This will be executed only once, for the first time a Freemius powered plugin is activated. - */ - fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); - - $is_current_sdk_newest = true; - } else if ( version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '<' ) ) { - /** - * Current SDK is newer than the newest stored SDK. - */ - fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); - - if ( class_exists( 'Freemius' ) ) { - // Older SDK version was already loaded. - - if ( ! $fs_active_plugins->newest->in_activation ) { - // Re-order plugins to load this plugin first. - fs_newest_sdk_plugin_first(); - } - - // Refresh page. - fs_redirect( $_SERVER['REQUEST_URI'] ); - } - } else { - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $fs_newest_sdk = $fs_active_plugins->newest; - $fs_newest_sdk = $fs_active_plugins->plugins[ $fs_newest_sdk->sdk_path ]; - - $is_newest_sdk_type_theme = ( isset( $fs_newest_sdk->type ) && 'theme' === $fs_newest_sdk->type ); - - if ( ! $is_newest_sdk_type_theme ) { - $is_newest_sdk_plugin_active = is_plugin_active( $fs_newest_sdk->plugin_path ); - } else { - $current_theme = wp_get_theme(); - $is_newest_sdk_plugin_active = ( $current_theme->stylesheet === $fs_newest_sdk->plugin_path ); - - $current_theme_parent = $current_theme->parent(); - - /** - * If the current theme is a child of the theme that has the newest SDK, this prevents a redirects loop - * from happening by keeping the SDK info stored in the `fs_active_plugins` option. - */ - if ( ! $is_newest_sdk_plugin_active && $current_theme_parent instanceof WP_Theme ) { - $is_newest_sdk_plugin_active = ( $fs_newest_sdk->plugin_path === $current_theme_parent->stylesheet ); - } - } - - if ( $is_current_sdk_newest && - ! $is_newest_sdk_plugin_active && - ! $fs_active_plugins->newest->in_activation - ) { - // If current SDK is the newest and the plugin is NOT active, it means - // that the current plugin in activation mode. - $fs_active_plugins->newest->in_activation = true; - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - - if ( ! $is_theme ) { - $sdk_starter_path = fs_normalize_path( WP_PLUGIN_DIR . '/' . $this_sdk_relative_path . '/start.php' ); - } else { - $sdk_starter_path = fs_normalize_path( - $themes_directory - . '/' - . str_replace( "../{$themes_directory_name}/", '', $this_sdk_relative_path ) - . '/start.php' ); - } - - $is_newest_sdk_path_valid = ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) && file_exists( $sdk_starter_path ); - - if ( ! $is_newest_sdk_path_valid && ! $is_current_sdk_newest ) { - // Plugin with newest SDK is no longer active, or SDK was moved to a different location. - unset( $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ] ); - } - - if ( ! ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) || - ! $is_newest_sdk_path_valid || - // Is newest SDK downgraded. - ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && - version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '>' ) ) - ) { - /** - * Plugin with newest SDK is no longer active. - * OR - * The newest SDK was in the current plugin. BUT, seems like the version of - * the SDK was downgraded to a lower SDK. - */ - // Find the active plugin with the newest SDK version and update the newest reference. - fs_fallback_to_newest_active_sdk(); - } else { - if ( $is_newest_sdk_plugin_active && - $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && - ( $fs_active_plugins->newest->in_activation || - ( class_exists( 'Freemius' ) && ( ! defined( 'WP_FS__SDK_VERSION' ) || version_compare( WP_FS__SDK_VERSION, $this_sdk_version, '<' ) ) ) - ) - - ) { - if ( $fs_active_plugins->newest->in_activation && ! $is_newest_sdk_type_theme ) { - // Plugin no more in activation. - $fs_active_plugins->newest->in_activation = false; - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - - // Reorder plugins to load plugin with newest SDK first. - if ( fs_newest_sdk_plugin_first() ) { - // Refresh page after re-order to make sure activated plugin loads newest SDK. - if ( class_exists( 'Freemius' ) ) { - fs_redirect( $_SERVER['REQUEST_URI'] ); - } - } - } - } - } - - if ( class_exists( 'Freemius' ) ) { - // SDK was already loaded. - return; - } - - if ( version_compare( $this_sdk_version, $fs_active_plugins->newest->version, '<' ) ) { - $newest_sdk = $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ]; - - $plugins_or_theme_dir_path = ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) ? - WP_PLUGIN_DIR : - $themes_directory; - - $newest_sdk_starter = fs_normalize_path( - $plugins_or_theme_dir_path - . '/' - . str_replace( "../{$themes_directory_name}/", '', $fs_active_plugins->newest->sdk_path ) - . '/start.php' ); - - if ( file_exists( $newest_sdk_starter ) ) { - // Reorder plugins to load plugin with newest SDK first. - fs_newest_sdk_plugin_first(); - - // There's a newer SDK version, load it instead of the current one! - require_once $newest_sdk_starter; - - return; - } - } - - #endregion SDK Selection Logic -------------------------------------------------------------------- - - #region Hooks & Filters Collection -------------------------------------------------------------------- - - /** - * Freemius hooks (actions & filters) tags structure: - * - * fs_{filter/action_name}_{plugin_slug} - * - * -------------------------------------------------------- - * - * Usage with WordPress' add_action() / add_filter(): - * - * add_action('fs_{filter/action_name}_{plugin_slug}', $callable); - * - * -------------------------------------------------------- - * - * Usage with Freemius' instance add_action() / add_filter(): - * - * // No need to add 'fs_' prefix nor '_{plugin_slug}' suffix. - * my_freemius()->add_action('{action_name}', $callable); - * - * -------------------------------------------------------- - * - * Freemius filters collection: - * - * fs_connect_url_{plugin_slug} - * fs_trial_promotion_message_{plugin_slug} - * fs_is_long_term_user_{plugin_slug} - * fs_uninstall_reasons_{plugin_slug} - * fs_is_plugin_update_{plugin_slug} - * fs_api_domains_{plugin_slug} - * fs_email_template_sections_{plugin_slug} - * fs_support_forum_submenu_{plugin_slug} - * fs_support_forum_url_{plugin_slug} - * fs_connect_message_{plugin_slug} - * fs_connect_message_on_update_{plugin_slug} - * fs_uninstall_confirmation_message_{plugin_slug} - * fs_pending_activation_message_{plugin_slug} - * fs_is_submenu_visible_{plugin_slug} - * fs_plugin_icon_{plugin_slug} - * fs_show_trial_{plugin_slug} - * - * -------------------------------------------------------- - * - * Freemius actions collection: - * - * fs_after_license_loaded_{plugin_slug} - * fs_after_license_change_{plugin_slug} - * fs_after_plans_sync_{plugin_slug} - * - * fs_after_account_details_{plugin_slug} - * fs_after_account_user_sync_{plugin_slug} - * fs_after_account_plan_sync_{plugin_slug} - * fs_before_account_load_{plugin_slug} - * fs_after_account_connection_{plugin_slug} - * fs_account_property_edit_{plugin_slug} - * fs_account_email_verified_{plugin_slug} - * fs_account_page_load_before_departure_{plugin_slug} - * fs_before_account_delete_{plugin_slug} - * fs_after_account_delete_{plugin_slug} - * - * fs_sdk_version_update_{plugin_slug} - * fs_plugin_version_update_{plugin_slug} - * - * fs_initiated_{plugin_slug} - * fs_after_init_plugin_registered_{plugin_slug} - * fs_after_init_plugin_anonymous_{plugin_slug} - * fs_after_init_plugin_pending_activations_{plugin_slug} - * fs_after_init_addon_registered_{plugin_slug} - * fs_after_init_addon_anonymous_{plugin_slug} - * fs_after_init_addon_pending_activations_{plugin_slug} - * - * fs_after_premium_version_activation_{plugin_slug} - * fs_after_free_version_reactivation_{plugin_slug} - * - * fs_after_uninstall_{plugin_slug} - * fs_before_admin_menu_init_{plugin_slug} - */ - - #endregion Hooks & Filters Collection -------------------------------------------------------------------- - - if ( ! class_exists( 'Freemius' ) ) { - - if ( ! defined( 'WP_FS__SDK_VERSION' ) ) { - define( 'WP_FS__SDK_VERSION', $this_sdk_version ); - } - - $plugins_or_theme_dir_path = fs_normalize_path( trailingslashit( $is_theme ? - $themes_directory : - WP_PLUGIN_DIR ) ); - - if ( 0 === strpos( $file_path, $plugins_or_theme_dir_path ) ) { - // No symlinks - } else { - /** - * This logic finds the SDK symlink and set WP_FS__DIR to use it. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.5 - */ - $sdk_symlink = null; - - // Try to load SDK's symlink from cache. - if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && - is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && - ! empty( $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink ) - ) { - $sdk_symlink = $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink; - if ( 0 === strpos( $sdk_symlink, $plugins_or_theme_dir_path ) ) { - /** - * Make the symlink path relative. - * - * @author Leo Fajardo (@leorw) - */ - $sdk_symlink = substr( $sdk_symlink, strlen( $plugins_or_theme_dir_path ) ); - - $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - - $realpath = realpath( $plugins_or_theme_dir_path . $sdk_symlink ); - if ( ! is_string( $realpath ) || ! file_exists( $realpath ) ) { - $sdk_symlink = null; - } - } - - if ( empty( $sdk_symlink ) ) // Has symlinks, therefore, we need to configure WP_FS__DIR based on the symlink. - { - $partial_path_right = basename( $file_path ); - $partial_path_left = dirname( $file_path ); - $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); - - while ( '/' !== $partial_path_left && - ( false === $realpath || $file_path !== fs_normalize_path( $realpath ) ) - ) { - $partial_path_right = trailingslashit( basename( $partial_path_left ) ) . $partial_path_right; - $partial_path_left_prev = $partial_path_left; - $partial_path_left = dirname( $partial_path_left_prev ); - - /** - * Avoid infinite loop if for example `$partial_path_left_prev` is `C:/`, in this case, - * `dirname( 'C:/' )` will return `C:/`. - * - * @author Leo Fajardo (@leorw) - */ - if ( $partial_path_left === $partial_path_left_prev ) { - $partial_path_left = ''; - break; - } - - $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); - } - - if ( ! empty( $partial_path_left ) && '/' !== $partial_path_left ) { - $sdk_symlink = fs_normalize_path( dirname( $partial_path_right ) ); - - // Cache value. - if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && - is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) - ) { - $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; - update_option( 'fs_active_plugins', $fs_active_plugins ); - } - } - } - - if ( ! empty( $sdk_symlink ) ) { - // Set SDK dir to the symlink path. - define( 'WP_FS__DIR', $plugins_or_theme_dir_path . $sdk_symlink ); - } - } - - // Load SDK files. - require_once dirname( __FILE__ ) . '/require.php'; - - /** - * Quick shortcut to get Freemius for specified plugin. - * Used by various templates. - * - * @param number $module_id - * - * @return Freemius - */ - function freemius( $module_id ) { - return Freemius::instance( $module_id ); - } - - /** - * @param string $slug - * @param number $plugin_id - * @param string $public_key - * @param bool $is_live Is live or test plugin. - * @param bool $is_premium Hints freemius if running the premium plugin or not. - * - * @return Freemius - * - * @deprecated Please use fs_dynamic_init(). - */ - function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = true ) { - $fs = Freemius::instance( $plugin_id, $slug, true ); - $fs->init( $plugin_id, $public_key, $is_live, $is_premium ); - - return $fs; - } - - /** - * @param array $module Plugin or Theme details. - * - * @return Freemius - * @throws Freemius_Exception - */ - function fs_dynamic_init( $module ) { - $fs = Freemius::instance( $module['id'], $module['slug'], true ); - $fs->dynamic_init( $module ); - - return $fs; - } - - function fs_dump_log() { - FS_Logger::dump(); - } - } diff --git a/freemius/templates/account.php b/freemius/templates/account.php index 0921366..e69de29 100644 --- a/freemius/templates/account.php +++ b/freemius/templates/account.php @@ -1,1098 +0,0 @@ -get_slug(); - - /** - * @var FS_Plugin_Tag $update - */ - $update = $fs->has_release_on_freemius() ? - $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) : - null; - - if ( is_object($update) ) { - /** - * This logic is particularly required for multisite environment. - * If a module is site activated (not network) and not on the main site, - * the module will NOT be executed on the network level, therefore, the - * custom updates logic will not be executed as well, so unless we force - * the injection of the update into the updates transient, premium updates - * will not work. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - $updater = FS_Plugin_Updater::instance( $fs ); - $updater->set_update_data( $update ); - } - - $is_paying = $fs->is_paying(); - $user = $fs->get_user(); - $site = $fs->get_site(); - $name = $user->get_name(); - $license = $fs->_get_license(); - $is_data_debug_mode = $fs->is_data_debug_mode(); - $is_whitelabeled = $fs->is_whitelabeled(); - $subscription = ( is_object( $license ) ? - $fs->_get_subscription( $license->id ) : - null ); - $plan = $fs->get_plan(); - $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); - $is_paid_trial = $fs->is_paid_trial(); - $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() ); - $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial ); - $trial_plan = $fs->get_trial_plan(); - - if ( $has_paid_plan ) { - $fs->_add_license_activation_dialog_box(); - } - - $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ? - $fs->get_installs_ids_with_foreign_licenses() : - array(); - - if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) { - $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses ); - } - - if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) { - $fs->_add_data_debug_mode_dialog_box(); - } - - if ( fs_request_get_bool( 'auto_install' ) ) { - $fs->_add_auto_installation_dialog_box(); - } - - if ( fs_request_get_bool( 'activate_license' ) ) { - // Open the license activation dialog box on the account page. - add_action( 'admin_footer', array( - &$fs, - '_open_license_activation_dialog_box' - ) ); - } - - $payments = $fs->_fetch_payments(); - - $show_billing = ( ! $is_whitelabeled && is_array( $payments ) && 0 < count( $payments ) ); - - - $has_tabs = $fs->_add_tabs_before_content(); - - if ( $has_tabs ) { - $query_params['tabs'] = 'true'; - } - - // Aliases. - $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); - $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); - $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); - /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ - $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); - $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); - $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); - $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); - $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); - /* translators: %s: Plan title (e.g. "Professional") */ - $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); - $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); - /* translators: %s: Time period (e.g. Auto renews in "2 months") */ - $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); - /* translators: %s: Time period (e.g. Expires in "2 months") */ - $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); - $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); - $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); - $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); - $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); - $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); - $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); - $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); - $free_text = fs_text_inline( 'Free', 'free', $slug ); - $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); - $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); - $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); - - $show_plan_row = true; - $show_license_row = is_object( $license ); - - $site_view_params = array(); - - if ( fs_is_network_admin() ) { - $sites = Freemius::get_sites(); - $all_installs_plan_id = null; - $all_installs_license_id = ( $show_license_row ? $license->id : null ); - foreach ( $sites as $s ) { - $site_info = $fs->get_site_info( $s ); - $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); - $view_params = array( - 'freemius' => $fs, - 'license' => $license, - 'site' => $site_info, - 'install' => $install, - ); - - $site_view_params[] = $view_params; - - if ( empty( $install ) ) { - continue; - } - - if ( $show_plan_row ) { - if ( is_null( $all_installs_plan_id ) ) { - $all_installs_plan_id = $install->plan_id; - } else if ( $all_installs_plan_id != $install->plan_id ) { - $show_plan_row = false; - } - } - - if ( $show_license_row && $all_installs_license_id != $install->license_id ) { - $show_license_row = false; - } - } - } - - $has_bundle_license = false; - - if ( is_object( $license ) && - FS_Plugin_License::is_valid_id( $license->parent_license_id ) - ) { - // Context license has a parent license, therefore, the account has a bundle license. - $has_bundle_license = true; - } - - $bundle_subscription = null; - $is_bundle_first_payment_pending = false; - - if ( - $show_plan_row && - is_object( $license ) && - $has_bundle_license - ) { - $bundle_plan_title = strtoupper( $license->parent_plan_title ); - $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); - $is_bundle_first_payment_pending = $license->is_first_payment_pending(); - } - - $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? - get_current_blog_id() : - 0; - - $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); - - $is_premium = $fs->is_premium(); - - $account_addons = $fs->get_updated_account_addons(); - $installed_addons = $fs->get_installed_addons(); - $installed_addons_ids = array(); - - /** - * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not. - * - * @author Leo Fajardo - * - * @since 2.4.0 - */ - foreach ( $installed_addons as $fs_addon ) { - $installed_addons_ids[] = $fs_addon->get_id(); - - if ( $has_bundle_license ) { - // We already have the context bundle license details, skip. - continue; - } - - if ( - $show_plan_row && - $fs_addon->has_active_valid_license() - ) { - $addon_license = $fs_addon->_get_license(); - - if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) { - // Add-on's license is associated with a parent/bundle license. - $has_bundle_license = true; - - $bundle_plan_title = strtoupper( $addon_license->parent_plan_title ); - $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id ); - $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending(); - } - } - } - - $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); - - $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); -?> -

    - apply_filters( 'hide_account_tabs', false ) ) : ?> - - - -
    -
    -
    ' . PHP_EOL; echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . '' . PHP_EOL; echo '' . PHP_EOL; @@ -2958,7 +2961,7 @@ function radio_station_override_show_metabox() { echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . esc_html( __( 'Use Excerpt Editor metabox below.', 'radio-station' ) ) . '' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo ' ' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . PHP_EOL; @@ -3230,7 +3233,7 @@ function radio_station_override_show_metabox() { } } echo '>' . PHP_EOL; - + // --- inside metabox contents --- $widget_title = $metabox['title']; echo '

    ' . esc_html( $metabox['title'] ) . '

    ' . PHP_EOL; @@ -3244,14 +3247,14 @@ function radio_station_override_show_metabox() { $i ++; } echo '' . PHP_EOL; - + // --- input list field styles --- // 2.3.3.9: add styles for table to list conversion $css = '.input-label, .input-field, .input-helper {display: inline-block;} .input-field {max-width: 120px;} .input-field, .input-helper {margin-left: 20px;} ' . PHP_EOL; - + // --- output inside metabox styles --- $css .= '#show-inside-metaboxes .postbox {display: inline-block; min-width: 230px; max-width: 250px; vertical-align: top;} #show-inside-metaboxes .postbox.first {margin-right: 20px;} @@ -3268,7 +3271,7 @@ function radio_station_override_show_metabox() { // --- enqueue inline script --- wp_add_inline_script( 'radio-station-admin', $js ); - + } // ------------------------- @@ -3295,7 +3298,7 @@ function radio_station_override_show_script() { radio_sync_genres(); radio_sync_languages(); } }" . PHP_EOL; - + // --- show/hide linked input field --- $js .= "function radio_check_linked(id) { /* console.log(id); */ @@ -3390,7 +3393,7 @@ function radio_station_schedule_override_metabox() { echo '
    ' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . PHP_EOL; @@ -3419,7 +3422,7 @@ function radio_station_schedule_override_metabox() { jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); }); });" . PHP_EOL; - + // --- enqueue inline script --- // 2.3.0: enqeue instead of echoing wp_add_inline_script( 'radio-station-admin', $js ); @@ -3506,7 +3509,7 @@ function radio_station_overrides_table( $post_id ) { 'end_date' => '', 'disabled' => '', ); - + // --- check and sanitize possibly posted times --- // 2.3.3.9: for adding new override time by querystring if ( isset( $_REQUEST['date'] ) ) { @@ -3727,7 +3730,7 @@ function radio_station_overrides_table( $post_id ) { $list .= '' . PHP_EOL; $list .= '' . PHP_EOL; - + } // --- new overrides div --- @@ -3738,7 +3741,7 @@ function radio_station_overrides_table( $post_id ) { 'list' => $list, // 'conflicts' => $conflicts, ); - + return $table; } @@ -3816,7 +3819,7 @@ function radio_station_override_edit_script() { // --- check disabled selection --- // TODO: ... $js .= "function radio_check_disabled() { - + }" . PHP_EOL; // --- check select change --- @@ -4145,7 +4148,7 @@ function radio_station_override_save_data( $post_id ) { } else { delete_post_meta( $post_id, 'sync_genres' ); } - + // --- sync languages switch --- $sync_languages = false; if ( isset( $_POST['sync_languages'] ) && ( 'yes' == $_POST['sync_languages'] ) ) { @@ -4176,7 +4179,7 @@ function radio_station_override_save_data( $post_id ) { } else { delete_post_meta( $post_id, 'sync_languages' ); } - + // --- update linked show fields --- $linked_show_fields = array(); $show_fields = array( 'title', 'content', 'excerpt', 'avatar', 'image', 'user_list', 'producer_list', 'link', 'email', 'phone', 'file', 'download', 'patreon' ); @@ -4283,7 +4286,7 @@ function radio_station_override_save_data( $post_id ) { } elseif ( ( 'disabled' === $key ) || ( 'multiday' == $key ) ) { if ( 'yes' == $value ) { $isvalid = true; - } + } } elseif ( 'id' == $key ) { // 2.3.3.9: validate unique shift ID if ( preg_match( '/^[a-zA-Z0-9_]+$/', $value ) ) { @@ -4338,7 +4341,7 @@ function radio_station_override_save_data( $post_id ) { $sched_changed = false; } } - + // --- sort schedule overrides by date/time --- if ( count( $new_scheds ) > 0 ) { $date_scheds = $sorted_scheds = array(); @@ -4355,11 +4358,11 @@ function radio_station_override_save_data( $post_id ) { $time = radio_station_convert_hour( $start, 24 ); $timestamp = radio_station_to_time( $date . ' ' . $time ); - // --- deduplicate based on timestamp --- + // --- deduplicate based on timestamp --- if ( isset( $sorted_sheds[$timestamp] ) ) { while( isset( $sorted_scheds[$timestamp] ) ) { $timestamp++; - } + } $sched['disabled'] = 'yes'; } $sorted_scheds[$timestamp] = $sched; @@ -4480,7 +4483,7 @@ function radio_station_override_save_data( $post_id ) { $js .= "parent.jQuery('.override-date').each(function() { parent.jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); });" . PHP_EOL; - + // --- output the scripts --- echo ''; @@ -4493,7 +4496,7 @@ function radio_station_override_save_data( $post_id ) { } // --- return early when adding single override --- - // 2.3.3.9: added for single override action + // 2.3.3.9: added for single override action if ( 'radio_station_add_override_time' == $_REQUEST['action'] ) { return; } @@ -4561,7 +4564,7 @@ function radio_station_override_column_data( $column, $post_id ) { // 2.3.3.9: loop possible multiple overrides foreach ( $overrides as $i => $override ) { - + if ( count( $overrides ) > 1 ) { echo '' . ( $i + 1 ) . ': ' . PHP_EOL; } @@ -4570,7 +4573,7 @@ function radio_station_override_column_data( $column, $post_id ) { if ( isset( $override['disabled'] ) && ( 'yes' == $override['disabled'] ) ) { echo '[Disabled]
    ' . PHP_EOL; } - + // 2.3.2: no need to apply timezone conversions here $datetime = strtotime( $override['date'] ); $month = date( 'F', $datetime ); @@ -4680,7 +4683,7 @@ function radio_station_override_column_data( $column, $post_id ) { // 2.3.0: adjust cell display to two line (to allow for long show titles) // 2.3.2: deduplicate show check (if same show as last show displayed) - // 2.3.3.9: buffer affected show shift output + // 2.3.3.9: buffer affected show shift output $affected = ''; if ( !isset( $last_show ) || ( $last_show != $show_shift['post_id'] ) ) { $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); @@ -4706,7 +4709,7 @@ function radio_station_override_column_data( $column, $post_id ) { $affected .= ' - ' . esc_html( $end_hour ) . ':' . esc_html( $shift['end_min'] ); } $affected .= '
    '; - + // 2.3.3.9: store affected shows by override index $affected_shows[$i] = $affected; @@ -4748,7 +4751,7 @@ function radio_station_override_column_data( $column, $post_id ) { } elseif ( 'linked_show' == $column ) { - // --- get linked Show --- + // --- get linked Show --- $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); if ( !$linked_id || ( '' == $linked_id ) ) { echo '' . esc_html( __( 'None', 'radio-station' ) ) . ''; @@ -4761,7 +4764,7 @@ function radio_station_override_column_data( $column, $post_id ) { $post_title = $wpdb->get_var( $query ); echo esc_html( $post_title ); } - + } elseif ( 'override_image' == $column ) { // --- override/show avatar --- @@ -4831,7 +4834,7 @@ function radio_station_override_date_filter( $post_type, $which ) { $datetime = radio_station_to_time( $override ); $month = radio_station_get_time( 'm', $datetime ); $year = radio_station_get_time( 'Y', $datetime ); - $months[$year . '-' . $month] = array( 'year' => $year, 'month' => $month ); + $months[$year . '-' . $month] = array( 'year' => $year, 'month' => $month ); } } } else { @@ -4947,7 +4950,7 @@ function radio_station_playlist_metabox() { echo '
    ' . PHP_EOL; echo '' . PHP_EOL; echo '
    ' . PHP_EOL; echo '' . PHP_EOL; @@ -5185,7 +5188,7 @@ function radio_station_playlist_metabox() { // 2.3.3.9: added track list style filter $css = apply_filters( 'radio_station_tracks_list_styles', $css ); // phpcs:ignore WordPress.Security.OutputNotEscaped - echo ''; + echo ''; // --- close meta inner --- echo ''; @@ -5288,7 +5291,7 @@ function radio_station_playlist_track_table( $playlist_id ) { echo ' selected="selected"'; } echo '>' . esc_html( $i ) . '' . PHP_EOL; - } + } echo '' . _x( 's', 'seconds unit', 'radio-station' ) . '' . PHP_EOL; // --- track comments --- @@ -5469,7 +5472,7 @@ function radio_station_playlist_save_data( $post_id ) { } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { - $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); + $error = __( 'Failed. No Playlist ID provided.', 'radio-station' ); } else { $post_id = absint( $_POST['playlist_id'] ); $post = get_post( $post_id ); @@ -5557,7 +5560,7 @@ function radio_station_playlist_save_data( $post_id ) { // 2.3.3: remove current show transient // 2.3.4: add previous show transient // 2.3.3.9: just call new clear cache function - if ( $show_changed ) { + if ( $show_changed ) { radio_station_clear_cached_data( $show, RADIO_STATION_PLAYLIST_SLUG ); } } @@ -5668,7 +5671,7 @@ function radio_station_playlist_column_styles() { #tracklist {width: 250px;} .tracklist-table {width: 350px;} .tracklist-table td {padding: 0px 10px;}"; - + // 2.3.3.9: add playlist list styles filter $css = apply_filters( 'radio_station_playlist_list_styles', $css ); echo ''; @@ -5726,7 +5729,7 @@ function radio_station_post_show_metabox() { // 2.3.3.6: store current post global global $post; $stored_post = $post; - + // 2.3.3.9: filter meta key according to post type $post_type = $post->post_type; $metakey = apply_filters( 'radio_station_related_show_meta_key', 'post_showblog_id', $post_type ); @@ -6260,7 +6263,7 @@ function radio_station_posts_list_styles() { $css = ".wp-list-table .posts .column-show {max-width: 100px;} .inline-edit-col .select-show {min-width: 200px; min-height: 100px;} .bulkactions .column-show .title {display: none;}"; - + // 2.3.3.9: added posts list styles filter $css = apply_filters( 'radio_station_posts_list_styles', $css ); echo ''; diff --git a/radio-station-admin.php b/radio-station-admin.php index d415a08..d7a2a7e 100644 --- a/radio-station-admin.php +++ b/radio-station-admin.php @@ -174,7 +174,7 @@ function radio_station_add_admin_menus() { do_action( 'radio_station_admin_submenu_middle' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Hosts', 'radio-station' ), __( 'Hosts', 'radio-station' ), 'edit_hosts', 'hosts' ); // add_submenu_page( 'radio-station', $rs . ' ' . __( 'Producers', 'radio-station' ), __( 'Producers', 'radio-station' ), 'edit_producers', 'producers' ); - + // 2.3.2: as temporarily disabled, allow enabling export playlists via filter $export_playlists = apply_filters( 'radio_station_export_playlists', false ); if ( $export_playlists ) { @@ -186,11 +186,11 @@ function radio_station_add_admin_menus() { if ( file_exists( RADIO_STATION_DIR . '/includes/import-export.php' ) ) { add_submenu_page( 'radio-station', $rs . ' ' . __( 'Import/Export Shows', 'radio-station' ), __( 'Import/Export', 'radio-station' ), 'manage_options', 'import-export-shows', 'radio_station_import_export_show_page' ); } - - add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settingscap, 'radio-station', 'radio_station_settings_page' ); + + add_submenu_page( 'radio-station', $rs . ' ' . __( 'Settings', 'radio-station' ), __( 'Settings', 'radio-station' ), $settingscap, 'radio-station', 'radio_station_settings_page' ); // 2.3.0: rename Help page to Documentation add_submenu_page( 'radio-station', $rs . ' ' . __( 'Documentation', 'radio-station' ), __( 'Help', 'radio-station' ), 'publish_playlists', 'radio-station-docs', 'radio_station_plugin_docs_page' ); - + do_action( 'radio_station_admin_submenu_bottom' ); // --- hack the submenu global to add post type add/edit URLs --- @@ -315,7 +315,7 @@ function radio_station_role_editor() { // --- info regarding Pro role assignment interface --- // 2.4.0.3: change text to reflect inclusion in Pro echo esc_html( __( 'Radio Station Pro includes a Role Assignment Interface so you can easily assign Radio Station roles to any user.', 'radio-station' ) ) . '
    '; - + // --- Pro upgrade link --- // TODO: maybe add picture of role editing interface ? $upgrade_url = radio_station_get_upgrade_url(); @@ -355,7 +355,7 @@ function radio_station_plugin_docs_page() { // --- include help file template --- // include RADIO_STATION_DIR . '/templates/help.php'; - + // --- include markdown reader --- include_once RADIO_STATION_DIR . '/reader.php'; @@ -374,7 +374,7 @@ function radio_station_plugin_docs_page() { echo '' . PHP_EOL; } } - + echo ""; - + } // --------------------- @@ -1651,7 +1651,7 @@ function radio_station_record_subscribe() { echo ""; exit; - + } // ------------------ @@ -1663,7 +1663,7 @@ function radio_station_record_subscribe() { function radio_station_clear_plugin_options() { if ( !current_user_can( 'manage_options' ) ) {return;} - + if ( isset( $_GET['option'] ) && ( 'subscribed' == $_GET['option'] ) ) { // note: there is a typo in this option not worth fixing delete_option( 'radio_station_subcribed' ); @@ -1684,6 +1684,6 @@ function radio_station_clear_plugin_options() { if ( isset( $_GET['option'] ) && ( 'offeraccepted' == $_GET['option'] ) ) { delete_option( 'radio_station_listing_offer_accepted' ); } - + exit; } diff --git a/radio-station.php b/radio-station.php index f7b58a8..327b527 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://netmix.com/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.3.3 +Version: 2.4.0.3.4 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages @@ -520,7 +520,7 @@ ), // === Player Colours === - + // --- [Pro] Playing Highlight Color --- 'player_playing_color' => array( 'type' => 'color', @@ -869,7 +869,7 @@ ), // --- [Pro] Time Spaced Grid View --- - // 2.4.0.3: added grid view time spacing option + // 2.4.0.4: added grid view time spacing option 'schedule_timegrid' => array( 'type' => 'checkbox', 'label' => __( 'Time Spaced Grid', 'radio-station' ), @@ -1320,7 +1320,7 @@ 'section' => 'loading', 'pro' => true, ), - + // --- [Pro] Timezone Switching --- 'timezone_switching' => array( 'type' => 'checkbox', @@ -1349,7 +1349,7 @@ // --- Playlist Editing Role Note --- // 2.4.0.3: added role to playlist assignment note - 'permissions_playlistow_role_note' => array( + 'permissions_playlist_role_note' => array( 'type' => 'note', 'label' => __( 'Playlist Permissions', 'radio-station' ), 'helper' => __( 'Any user with a Host or Producer role can create Playlists.', 'radio-station' ), @@ -1448,7 +1448,7 @@ // ------------------------- // Pro Version Install Check // ------------------------- -// 2.4.0.3: added check active/installed Pro version +// 2.4.0.3: added check active/installed Pro version $plan = 'free'; // --- check for deactivated pro plugin --- @@ -1619,7 +1619,7 @@ function radio_station_plugin_activation() { // 2.3.3: added clear transients on (re)activation // 2.3.3.9: just use clear cached data function radio_station_clear_cached_data( false ); - + // --- set welcome redirect transient --- // TODO: check if handled by Freemius activation // set_transient( 'radio_station_welcome', 1, 7 ); @@ -2276,7 +2276,7 @@ function radio_station_automatic_pages_content_set( $content ) { } } } - + // --- languages archive page --- // 2.3.3.9: added automatic display of language archive page $language_archive_page = radio_station_get_setting( '' ); @@ -2435,7 +2435,7 @@ function radio_station_override_linked_show_data( $post, $post_type ) { } } } - } + } } return $post; } @@ -2493,7 +2493,7 @@ function radio_station_override_content( $content ) { $override = radio_station_get_show_override( $post->ID, 'show_content' ); if ( false !== $override ) { $override = radio_station_override_linked_show_data( $post, RADIO_STATION_OVERRIDE_SLUG ); - $content = $override->post_content; + $content = $override->post_content; } add_filter( 'the_content', 'radio_station_override_content', 0 ); return $content; @@ -2868,7 +2868,7 @@ function radio_station_get_show_post_link( $output, $format, $link, $adjacent_po if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { // 2.3.3.6: get next/previous Show for override date/time // 2.3.3.9: modified to handle multiple override times - // 2.3.3.9: added check that schedule key is set + // 2.3.3.9: added check that schedule key is set $scheds = get_post_meta( $post->ID, 'show_override_sched', true ); if ( $scheds && is_array( $scheds ) ) { if ( array_key_exists( 'date', $scheds ) ) { @@ -3482,13 +3482,10 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { global $post, $wp_roles; - // --- get the current user --- - // 2.3.3.6: get user object from fourth argument instead - // $user = wp_get_current_user(); - // --- check if super admin --- + // 2.3.3.6: get user object from fourth argument instead // ? fix to not revoke edit caps from super admin ? - // (not implemented, as causing a connection reset error) + // (not implemented, as causing a connection reset error!) // if ( function_exists( 'is_super_admin' ) && is_super_admin() ) { // return $allcaps; // } @@ -3499,11 +3496,16 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { echo 'Cap Args: ' . print_r( $args, true ) . ''; } - // --- check for editor + // --- check for editor role --- // 2.3.3.6: check editor roles first separately - $editor_roles = array( 'administrator', 'editor', 'show-editor' ); + // 2.4.0.4: only add WordPress editor role if on in settings + $editor_roles = array( 'administrator', 'show-editor' ); + $editor_role_caps = radio_station_get_setting( 'add_editor_capabilities' ); + if ( 'yes' == $editor_role_caps ) { + $editor_roles[] = 'editor'; + } foreach ( $editor_roles as $role ) { - if ( in_array( $role, $user->roles ) ) { + if ( in_array( $role, $user->roles ) ) { return $allcaps; } } @@ -3535,7 +3537,9 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // 2.3.3.6: preserve if user has edit_others_shows capability foreach ( $edit_others_shows_roles as $role ) { if ( in_array( $role, $user->roles ) ) { - return $allcaps; + // 2.4.0.4: do not automatically assume capability match + // return $allcaps; + $found = true; } } @@ -3553,7 +3557,7 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // --- limit this to published shows --- // 2.3.0: added object and property_exists check to be safe - if ( is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { + if ( isset( $post ) && is_object( $post ) && property_exists( $post, 'post_type' ) && isset( $post->post_type ) ) { // 2.3.0: removed is_admin check (so works with frontend edit show link) // 2.3.0: moved check if show is published inside @@ -3577,11 +3581,24 @@ function radio_station_revoke_show_edit_cap( $allcaps, $caps, $args, $user ) { // --- remove the edit_shows capability --- $allcaps['edit_shows'] = false; + $allcaps['edit_others_shows'] = false; + if ( RADIO_STATION_DEBUG ) { + echo "Removed Edit Show Caps (" . $post->ID . ")"; + } // 2.3.0: move check if show is published inside if ( 'publish' == $post->post_status ) { $allcaps['edit_published_shows'] = false; } + } else { + // 2.4.0.4: add edit others shows capability + // (fix for when not original show author) + $allcaps['edit_shows'] = true; + $allcaps['edit_others_shows'] = true; + if ( RADIO_STATION_DEBUG ) { + echo "Added Edit Show Caps (" . $post->ID . ")"; + } + } } } diff --git a/readme.txt b/readme.txt index bcccd52..c4a90e2 100644 --- a/readme.txt +++ b/readme.txt @@ -219,6 +219,8 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.4 = +* Fixed: DJ / Host can edit own/others Show permissions +* Fixed: Override link to show dropdown query * Fixed: Fallback scripts and fallback stream URLs * Fixed: Radio Clock responsive width display * Fixed: Collapse descriptions for non-show pages From c5e470e2f0ad2fb15224df41cdbbaa48c86bc449 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 30 Sep 2021 08:43:00 +1000 Subject: [PATCH 234/377] dev update #384 --- includes/post-types-admin.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 4061548..ff349c4 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -2847,12 +2847,27 @@ function radio_station_override_show_metabox() { if ( $linked_id == $show_id ) { echo ' selected="selected"'; } - echo '>' . esc_html( $post_id ) . ': ' . esc_html( $title ); + echo '>' . esc_html( $show_id ) . ': '; + if ( 'on' != $active ) { + echo '[Inactive] '; + } + echo esc_html( $title ); if ( 'draft' == $status ) { echo ' (Draft)'; + } elseif ( 'pending' == $status ) { + echo ' (Pending)'; + } elseif ( 'future' == $status ) { + echo ' (Future)'; } - if ( 'on' != $active ) { - echo ' (Inactive)'; + $hosts = get_post_meta( $show_id, 'show_user_list', true ); + if ( $hosts && is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + echo ' : '; + $hostnames = array(); + foreach ( $hosts as $host ) { + $user = get_user_by( 'ID', $host ); + $hostnames[] = $user->display_name; + } + echo implode( ', ', $hostnames ); } echo '' . PHP_EOL; } From 264e0968edecb39b444764820d3c0839d9999562 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 28 Oct 2021 17:24:35 +1000 Subject: [PATCH 235/377] dev updates --- CHANGELOG.md | 2 +- css/rs-shortcodes.css | 13 +- includes/class-radio-clock-widget.php | 3 +- includes/post-types-admin.php | 37 +++- includes/shortcodes.php | 253 ++++++++++++++------------ includes/support-functions.php | 166 +++++++++++------ loader.php | 56 +++--- radio-station-admin.php | 34 ++-- radio-station.php | 70 ++++--- readme.md | 10 +- readme.txt | 20 +- templates/master-schedule-tabs.php | 12 +- templates/single-show-content.php | 8 + 13 files changed, 419 insertions(+), 265 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db2141a..b47a79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -### +### 2.4.0.4 * Fixed: DJ / Host can edit own/others Show permissions * Fixed: Override link to show dropdown query * Fixed: Fallback scripts and fallback stream URLs diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index 007e8e1..a642b29 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -123,6 +123,11 @@ margin-bottom: 10px; } +.show-archive-item-content, .playlist-archive-item-content, .override-archive-item-content, +.show-archive-item-excerpt, .playlist-archive-item-excerpt, .override-archive-item-excerpt { + font-size: 0.8em; +} + /* Show Subarchive Shortcodes */ /* -------------------------- */ .show-post, .show-playlist, .show-host, .show-producer, .show-episode { @@ -132,21 +137,23 @@ .show-post-thumbnail, .show-post-info, .show-playlist-thumbnail, .show-playlist-info, +.show-host-thumbnail, .show-host-info, +.show-producer-thumbnail, .show-producer-info, .show-episode-thumbnail, .show-episode-info { display: table-cell; vertical-align: top; } -.show-post-thumbnail, .show-playlist-thumbnail, .show-episode-thumbnail { +.show-post-thumbnail, .show-playlist-thumbnail, .show-host-thumbnail, .show-producer-thumbnail, .show-episode-thumbnail { padding-right: 30px; margin-top: 10px; } -.show-post-info, .show-playlist-info, .show-episode-info { +.show-post-info, .show-playlist-info, .show-host-thumbnail, .show-producer-thumbnail .show-episode-info { max-width: 600px; } -.show-post-title, .show-playlist-title, .show-episode-title { +.show-post-title, .show-playlist-title, .show-host-title, .show-producer-title, .show-episode-title { margin-bottom: 10px; } diff --git a/includes/class-radio-clock-widget.php b/includes/class-radio-clock-widget.php index cbe1aa3..b88981d 100644 --- a/includes/class-radio-clock-widget.php +++ b/includes/class-radio-clock-widget.php @@ -194,7 +194,8 @@ public function widget( $args, $instance ) { // --- enqueue widget stylesheet in footer --- // (this means it will only load if widget is on page) - radio_station_enqueue_style( 'widgets' ); + // 2.4.0.4: fix to load shortcode stylesheet + radio_station_enqueue_style( 'shortcodes' ); } } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index ff349c4..a3e1463 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -642,9 +642,12 @@ function radio_station_show_hosts_metabox() { $hosts = get_users( $args ); // --- get the Hosts currently assigned to the show --- + // 2.4.0.4: convert possible (old) non-array value $current = get_post_meta( $post->ID, 'show_user_list', true ); if ( !$current ) { $current = array(); + } elseif ( !is_array( $current ) ) { + $current = array( $current ); } // --- move any selected Hosts to the top of the list --- @@ -721,8 +724,11 @@ function radio_station_show_producers_metabox() { // --- get Producers currently assigned to the show --- $current = get_post_meta( $post->ID, 'show_producer_list', true ); + // 2.4.0.4: convert possible (old) non-array values if ( !$current ) { $current = array(); + } elseif ( !is_array( $current ) ) { + $current = array( $current ); } // --- move any selected DJs to the top of the list --- @@ -2701,10 +2707,16 @@ function radio_station_show_column_data( $column, $post_id ) { } elseif ( 'hosts' == $column ) { $hosts = get_post_meta( $post_id, 'show_user_list', true ); - if ( $hosts && ( count( $hosts ) > 0 ) ) { - foreach ( $hosts as $host ) { - $user_info = get_userdata( $host ); - echo esc_html( $user_info->display_name ) . '
    '; + if ( $hosts ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $hosts ) ) { + $hosts = array( $hosts ); + } + if ( is_array( $hosts ) && ( count( $hosts ) > 0 ) ) { + foreach ( $hosts as $host ) { + $user_info = get_userdata( $host ); + echo esc_html( $user_info->display_name ) . '
    '; + } } } @@ -2712,10 +2724,16 @@ function radio_station_show_column_data( $column, $post_id ) { // 2.3.0: added column for Producers $producers = get_post_meta( $post_id, 'show_producer_list', true ); - if ( $producers && ( count( $producers ) > 0 ) ) { - foreach ( $producers as $producer ) { - $user_info = get_userdata( $producer ); - echo esc_html( $user_info->display_name ) . '
    '; + if ( $producers ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $producers ) ) { + $producers = array( $producers ); + } + if ( is_array( $producers ) && ( count( $producers ) > 0 ) ) { + foreach ( $producers as $producer ) { + $user_info = get_userdata( $producer ); + echo esc_html( $user_info->display_name ) . '
    '; + } } } @@ -5576,7 +5594,8 @@ function radio_station_playlist_save_data( $post_id ) { // 2.3.4: add previous show transient // 2.3.3.9: just call new clear cache function if ( $show_changed ) { - radio_station_clear_cached_data( $show, RADIO_STATION_PLAYLIST_SLUG ); + // 2.4.0.4: fix to mismatching post type argument + radio_station_clear_cached_data( $show, RADIO_STATION_SHOW_SLUG ); } } } diff --git a/includes/shortcodes.php b/includes/shortcodes.php index d0c1f52..e87c5b0 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -260,18 +260,23 @@ function radio_station_clock_shortcode( $atts = array() ) { // Archive List Shortcode Abstract // ------------------------------- // (handles Shows, Overrides, Playlists etc.) -function radio_station_archive_list_shortcode( $type, $atts ) { +function radio_station_archive_list_shortcode( $post_type, $atts ) { + + // --- set type from post type --- + $type = str_replace( 'rs-', '', $post_type ); // --- get clock time format --- $time_format = radio_station_get_setting( 'clock_time_format' ); // --- merge defaults with passed attributes --- // 2.3.3.9: add atts for specific posts + // 2.4.0.4: added optional view attribute $defaults = array( // --- shortcode display ---- 'description' => 'excerpt', 'hide_empty' => 0, 'time' => $time_format, + 'view' => 0, // --- taxonomy queries --- 'genre' => '', 'language' => '', @@ -307,7 +312,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $atts['offset'] = (int) $atts['perpage'] * $page; } } - $atts = shortcode_atts( $defaults, $atts, $type . '-archive' ); + $atts = shortcode_atts( $defaults, $atts, $post_type . '-archive' ); if ( RADIO_STATION_DEBUG ) { echo 'Shortcode Atts: ' . print_r( $atts, true ) . ''; } @@ -315,7 +320,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- get published shows --- // 2.3.3.9: ignore offset and limit and reapply later $args = array( - 'post_type' => $type, + 'post_type' => $post_type, 'post_status' => $atts['status'], 'numberposts' => - 1, // 'numberposts' => $atts['perpage'], @@ -325,7 +330,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { ); // --- extra queries for shows --- - if ( RADIO_STATION_SHOW_SLUG == $type ) { + if ( RADIO_STATION_SHOW_SLUG == $post_type ) { if ( $atts['with_shifts'] ) { @@ -357,7 +362,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // --- specific genres taxonomy query --- - if ( !empty( $atts['genre'] ) && in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { + if ( !empty( $atts['genre'] ) && in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { // --- check for provided genre(s) as slug or ID --- if ( strstr( $atts['genre'], ',' ) ) { @@ -380,7 +385,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- specific languages taxonomy query --- // 2.3.0: added language taxonomy support - if ( !empty( $atts['language'] ) && in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { + if ( !empty( $atts['language'] ) && in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { // --- check for provided genre(s) as slug or ID --- if ( strstr( $atts['language'], ',' ) ) { @@ -404,11 +409,11 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // 2.3.3.9: allow for selective post specifications // 2.4.0: fix selective posts for default (false) - if ( ( RADIO_STATION_SHOW_SLUG == $type ) && isset( $atts['show'] ) && $atts['show'] ) { + if ( ( RADIO_STATION_SHOW_SLUG == $post_type ) && isset( $atts['show'] ) && $atts['show'] ) { $args['include'] = explode( ',', $atts['show'] ); - } elseif ( ( RADIO_STATION_OVERRIDE_SLUG == $type ) && isset( $atts['override'] ) && $atts['override'] ) { + } elseif ( ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) && isset( $atts['override'] ) && $atts['override'] ) { $args['include'] = explode( ',', $atts['override'] ); - } elseif ( ( RADIO_STATION_PLAYLIST_SLUG == $type ) && isset( $atts['playlist'] ) && $atts['playlist'] ) { + } elseif ( ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) && isset( $atts['playlist'] ) && $atts['playlist'] ) { $args['include'] = explode( ',', $atts['playlist'] ); } @@ -423,7 +428,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // --- process playlist taxonomy query --- - if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + if ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) { // 2.3.3.9: added missing check for matching archive post results if ( $archive_posts && is_array( $archive_posts ) && ( count( $archive_posts ) > 0 ) ) { @@ -478,7 +483,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- check override dates --- // (overrides without a date set will not be displayed) - if ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { if ( $archive_posts && is_array( $archive_posts ) && ( count( $archive_posts ) > 0 ) ) { foreach( $archive_posts as $i => $archive_post ) { // 2.3.3.9: set singular to false to allow for multiple override times @@ -514,8 +519,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $start_data_format = $end_data_format = 'g:i a'; } $start_data_format = 'j, ' . $start_data_format; - $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $type . '-archive', $atts ); - $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $type . '-archive', $atts ); + $start_data_format = apply_filters( 'radio_station_time_format_start', $start_data_format, $post_type . '-archive', $atts ); + $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $post_type . '-archive', $atts ); // --- check for results --- if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { @@ -548,11 +553,18 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // --- output list or no results message --- - $list = '
    '; + // 2.4.0.4: remove rs- prefix from element classes + // 2.4.0.4: maybe add view class + $classes = array( $type . '-archives' ); + if ( $atts['view'] ) { + $classes[] = $atts['view']; + } + $class_list = implode( ' ', $classes ); + $list = '
    '; if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { // --- no shows messages ---- - if ( RADIO_STATION_SHOW_SLUG == $type ) { + if ( RADIO_STATION_SHOW_SLUG == $post_type ) { // 2.3.3.9: improve messages if genre / language specificed if ( ( !empty( $atts['genre'] ) ) && ( !empty( $atts['genre'] ) ) ) { $message = __( 'No Shows in the requested Genre and Language were found.', 'radio-station' ); @@ -563,14 +575,14 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } else { $message = __( 'No Shows were found to display.', 'radio-station' ); } - } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + } elseif ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) { $message = __( 'No Playlists were found to display.', 'radio-station' ); - } elseif ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + } elseif ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { $message = __( 'No Overrides were found to display.', 'radio-station' ); } // 2.3.3.9: filter message to allow for other possible types - $message = apply_filters( 'radio_station_archive_shortcode_no_records', $message, $type, $atts ); + $message = apply_filters( 'radio_station_archive_shortcode_no_records', $message, $post_type, $atts ); $list .= esc_html( $message ); } else { @@ -585,7 +597,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- set info keys --- // (note: meta is dates for overrides, shifts for shows, tracks for playlists etc.) $infokeys = array( 'avatar', 'title', 'meta', 'genres', 'languages', 'description', 'custom' ); - $infokeys = apply_filters( 'radio_station_archive_shortcode_info_order', $infokeys, $type, $atts ); + $infokeys = apply_filters( 'radio_station_archive_shortcode_info_order', $infokeys, $post_type, $atts ); // --- loop post archive --- foreach ( $archive_posts as $archive_post ) { @@ -601,7 +613,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $post_excerpt = $archive_post->post_excerpt; // --- check linked Show for overrides --- - if ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + if ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) { $linked_show = get_post_meta( $archive_post->ID, 'linked_show_id', true ); if ( $linked_show ) { @@ -627,7 +639,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- show avatar or thumbnail fallback --- $info['avatar'] = '
    '; $show_avatar = false; - if ( $atts['show_avatars'] && in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { + if ( $atts['show_avatars'] && in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { $attr = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); $show_avatar = radio_station_get_show_avatar( $image_post_id, 'thumbnail', $attr ); } @@ -635,8 +647,9 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $info['avatar'] .= $show_avatar; } elseif ( $atts['thumbnails'] ) { if ( has_post_thumbnail( $image_post_id ) ) { - $atts = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); - $thumbnail = get_the_post_thumbnail( $image_post_id, 'thumbnail', $atts ); + // 2.4.0.4: use attr not atts to prevent possible shortcode variable conflict + $attr = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); + $thumbnail = get_the_post_thumbnail( $image_post_id, 'thumbnail', $attr ); $info['avatar'] .= $thumbnail; } } @@ -649,7 +662,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $info['title'] .= '
    '; // --- display Override date(s) --- - if ( ( RADIO_STATION_OVERRIDE_SLUG == $type ) && ( $atts['show_dates'] ) ) { + if ( ( RADIO_STATION_OVERRIDE_SLUG == $post_type ) && ( $atts['show_dates'] ) ) { // 2.3.3.9: set third attribute to false to allow for multiple override times $override_times = get_post_meta( $archive_post->ID, 'show_override_sched', true ); @@ -703,7 +716,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // TODO: display Show shifts meta - // if ( RADIO_STATION_SHOW_SLUG == $type ) { + // if ( RADIO_STATION_SHOW_SLUG == $post_type ) { // if ( $atts['show_shifts'] ) { // $shifts = radio_station_get_show_schedule( $post_id ); // } @@ -711,7 +724,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // } // TODO: playlist tracks / track count meta - // if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + // if ( RADIO_STATION_PLAYLIST_SLUG == $post_type ) { // $tracks = get_post_meta( $post_id, 'playlist', true ); // $track_count = count( $tracks ); // $info['meta'] = ''; @@ -721,10 +734,10 @@ function radio_station_archive_list_shortcode( $type, $atts ) { if ( !isset( $info['meta'] ) ) { $info['meta'] = ''; } - $info['meta'] = apply_filters ( 'radio_station_archive_shortcode_meta', $info['meta'], $post_id, $type, $atts ); + $info['meta'] = apply_filters ( 'radio_station_archive_shortcode_meta', $info['meta'], $post_id, $post_type, $atts ); // TODO: display genre and language terms ? - // if ( in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { + // if ( in_array( $post_type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { // if ( $atts['show_genres'] ) { // $genres = wp_get_post_terms( $post_id, RADIO_STATION_GENRES_SLUG ); // $info['genres'] = ''; @@ -736,7 +749,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // } // --- description --- - if ( 'none' == $atts['description'] ) { + // 2.4.0.4: remove description for grid view + if ( ( 'none' == $atts['description'] ) || ( 'grid' == $atts['view'] ) ) { $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { $info['description'] = '
    '; @@ -757,10 +771,10 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } // 2.3.3.9: add filter for custom HTML info - $info['custom'] = apply_filters( 'radio_station_archive_shortcode_info_custom', '', $post_id, $type, $atts ); + $info['custom'] = apply_filters( 'radio_station_archive_shortcode_info_custom', '', $post_id, $post_type, $atts ); // 2.3.3.9: filter info and loop info keys to add to archive list - $info = apply_filters( 'radio_station_archive_shortcode_info', $info, $post_id, $type, $atts ); + $info = apply_filters( 'radio_station_archive_shortcode_info', $info, $post_id, $post_type, $atts ); foreach ( $infokeys as $infokey ) { if ( isset( $info[$infokey] ) ) { $list .= $info[$infokey]; @@ -776,7 +790,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- add archive_pagination --- if ( $atts['pagination'] && ( $atts['perpage'] > 0 ) && ( $post_count > 0 ) ) { if ( $post_count > $atts['perpage'] ) { - $list .= radio_station_archive_pagination( $type, $atts, $post_count ); + $list .= radio_station_archive_pagination( $post_type, $atts, $post_count ); } } @@ -787,7 +801,8 @@ function radio_station_archive_list_shortcode( $type, $atts ) { radio_station_enqueue_style( 'shortcodes' ); // --- filter and return --- - $list = apply_filters( 'radio_station_' . $type . '_archive_list', $list, $atts ); + // 2.4.0.4: added third argument for post type + $list = apply_filters( 'radio_station_' . $type . '_archive_list', $list, $atts, $post_type ); return $list; } @@ -2203,57 +2218,63 @@ function radio_station_current_show_shortcode( $atts ) { $hosts = ''; $show_hosts = get_post_meta( $show_id, 'show_user_list', true ); - if ( $show_hosts && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { + if ( $show_hosts ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $show_hosts ) ) { + $show_hosts = array( $show_hosts ); + } + if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $hosts = '
    '; + $hosts = '
    '; - $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; - $count = 0; - // 2.3.3.9: fix to host count - $host_count = count( $show_hosts ); - foreach ( $show_hosts as $host ) { + $count = 0; + // 2.3.3.9: fix to host count + $host_count = count( $show_hosts ); + foreach ( $show_hosts as $host ) { - $count ++; + $count ++; - // 2.3.0: maybe get stored user data - // $user = get_userdata( $host ); - if ( isset( $radio_station_data['user-' . $host] ) ) { - $user = $radio_station_data['user-' . $host]; - } else { - $user = get_user_by( 'ID', $host ); - $radio_station_data['user-' . $host] = $user; - } + // 2.3.0: maybe get stored user data + // $user = get_userdata( $host ); + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } - if ( $atts['link_hosts'] ) { - // 2.3.0: use new get host URL function - $host_link = radio_station_get_host_url( $host ); - $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); + if ( $atts['link_hosts'] ) { + // 2.3.0: use new get host URL function + $host_link = radio_station_get_host_url( $host ); + $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); - // 2.3.3.5: only wrap with tags if there is a link - if ( $host_link ) { - $hosts .= ''; - } - $hosts .= esc_html( $user->display_name ); - if ( $host_link ) { - $hosts .= ''; + // 2.3.3.5: only wrap with tags if there is a link + if ( $host_link ) { + $hosts .= ''; + } + $hosts .= esc_html( $user->display_name ); + if ( $host_link ) { + $hosts .= ''; + } + } else { + $hosts .= esc_html( $user->display_name ); } - } else { - $hosts .= esc_html( $user->display_name ); - } - if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) - || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) == $count ) ) ) { - $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $hosts .= ', '; + if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) + || ( ( $host_count > 2 ) && ( ( $host_count - 1 ) == $count ) ) ) { + $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { + $hosts .= ', '; + } } + $hosts .= '
    '; + } + $hosts = apply_filters( 'radio_station_current_show_hosts_display', $hosts, $show_id, $atts ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $html['hosts'] = $hosts; } - $hosts .= '
    '; - } - $hosts = apply_filters( 'radio_station_current_show_hosts_display', $hosts, $show_id, $atts ); - if ( ( '' != $hosts ) && is_string( $hosts ) ) { - $html['hosts'] = $hosts; } } @@ -2865,56 +2886,62 @@ function radio_station_upcoming_shows_shortcode( $atts ) { $hosts = ''; $show_hosts = get_post_meta( $show_id, 'show_user_list', true ); - if ( isset( $show_hosts ) && is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { + if ( $show_hosts ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $show_hosts ) ) { + $show_hosts = array( $show_hosts ); + } + if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - $hosts = '
    '; - $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; + $hosts = '
    '; + $hosts .= esc_html( __( 'with', 'radio-station' ) ) . ' '; - $count = 0; - // 2.3.3.9: fix to host count - $host_count = count( $show_hosts ); - foreach ( $show_hosts as $host ) { + $count = 0; + // 2.3.3.9: fix to host count + $host_count = count( $show_hosts ); + foreach ( $show_hosts as $host ) { - $count ++; + $count ++; - // 2.3.0: maybe get stored user data - // $user = get_userdata( $host ); - if ( isset( $radio_station_data['user-' . $host] ) ) { - $user = $radio_station_data['user-' . $host]; - } else { - $user = get_user_by( 'ID', $host ); - $radio_station_data['user-' . $host] = $user; - } + // 2.3.0: maybe get stored user data + // $user = get_userdata( $host ); + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } - if ( $atts['link_hosts'] ) { - // 2.3.0: use new get host URL function - $host_link = radio_station_get_host_url( $host ); - $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); + if ( $atts['link_hosts'] ) { + // 2.3.0: use new get host URL function + $host_link = radio_station_get_host_url( $host ); + $host_link = apply_filters( 'radio_station_dj_link', $host_link, $host ); - // 2.3.3.5: only wrap with tags if there is a link - if ( $host_link ) { - $hosts .= ''; - } - $hosts .= esc_html( $user->display_name ); - if ( $host_link ) { - $hosts .= ''; + // 2.3.3.5: only wrap with tags if there is a link + if ( $host_link ) { + $hosts .= ''; + } + $hosts .= esc_html( $user->display_name ); + if ( $host_link ) { + $hosts .= ''; + } + } else { + $hosts .= esc_html( $user->display_name ); } - } else { - $hosts .= esc_html( $user->display_name ); - } - if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) - || ( ( $host_count > 2 ) && ( $count == ( $host_count - 1 ) ) ) ) { - $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; - } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { - $hosts .= ', '; + if ( ( ( 1 == $count ) && ( 2 == $host_count ) ) + || ( ( $host_count > 2 ) && ( $count == ( $host_count - 1 ) ) ) ) { + $hosts .= ' ' . esc_html( __( 'and', 'radio-station' ) ) . ' '; + } elseif ( ( $count < $host_count ) && ( $host_count > 2 ) ) { + $hosts .= ', '; + } } + $hosts .= '
    '; + } + $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show_id, $atts ); + if ( ( '' != $hosts ) && is_string( $hosts ) ) { + $html['hosts'] = $hosts; } - $hosts .= '
    '; - } - $hosts = apply_filters( 'radio_station_upcoming_show_hosts_display', $hosts, $show_id, $atts ); - if ( ( '' != $hosts ) && is_string( $hosts ) ) { - $html['hosts'] = $hosts; } } diff --git a/includes/support-functions.php b/includes/support-functions.php index 80f5435..624c37c 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -85,6 +85,10 @@ // - Sanitize Input Value // - Get Meta Input Types // - Sanitize Shortcode Values +// === Transient Caching === +// - Delete Prefixed Transients +// - Clear Cached Data +// - Clear Cache on Status Transitions // === Translations === // - Translate Weekday // - Replace Weekdays @@ -525,7 +529,7 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { $title = $linked_show->post_title; } } - + // 2.3.3.9: get possible array of override shifts $override_shifts = get_post_meta( $override['ID'], 'show_override_sched', true ); // 2.3.3.9: convert possible single override to array @@ -533,7 +537,7 @@ function radio_station_get_overrides( $start_date = false, $end_date = false ) { $override_shifts = array( $override_shifts ); update_post_meta( $override['ID'], 'show_override_sched', $override_shifts ); } - + if ( $override_shifts && is_array( $override_shifts ) && ( count( $override_shifts ) > 0 ) ) { // 2.2.3.9: loop to add unique shift IDs and maybe resave @@ -796,7 +800,7 @@ function radio_station_get_show_data( $datatype, $show_id, $args = array() ) { } } elseif ( ( 'hosts' == $datatype ) || ( 'producers' == $datatype ) ) { - + // 2.3.3.9; added get host/producer profile posts $user_ids = get_post_meta( $show_id, $metakey, true ); if ( !$user_ids ) { @@ -918,37 +922,49 @@ function radio_station_get_show_data_meta( $show, $single = false ) { $show_hosts = get_post_meta( $show->ID, 'show_user_list', true ); $show_producers = get_post_meta( $show->ID, 'show_producer_list', true ); $hosts = $producers = array(); - if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { - foreach ( $show_hosts as $host ) { - if ( isset( $radio_station_data['user-' . $host] ) ) { - $user = $radio_station_data['user-' . $host]; - } else { - $user = get_user_by( 'ID', $host ); - $radio_station_data['user-' . $host] = $user; - } - // 2.3.3.9: added check user still exists - if ( $user ) { - $hosts[] = array( - 'name' => $user->display_name, - 'url' => radio_station_get_host_url( $host ), - ); + if ( $show_hosts ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $show_hosts ) ) { + $show_hosts = array( $show_hosts ); + } + if ( is_array( $show_hosts ) && ( count( $show_hosts ) > 0 ) ) { + foreach ( $show_hosts as $host ) { + if ( isset( $radio_station_data['user-' . $host] ) ) { + $user = $radio_station_data['user-' . $host]; + } else { + $user = get_user_by( 'ID', $host ); + $radio_station_data['user-' . $host] = $user; + } + // 2.3.3.9: added check user still exists + if ( $user ) { + $hosts[] = array( + 'name' => $user->display_name, + 'url' => radio_station_get_host_url( $host ), + ); + } } } } - if ( is_array( $show_producers ) && ( count( $show_producers ) > 0 ) ) { - foreach ( $show_producers as $producer ) { - if ( isset( $radio_station_data['user-' . $producer] ) ) { - $user = $radio_station_data['user-' . $producer]; - } else { - $user = get_user_by( 'ID', $producer ); - $radio_station_data['user-' . $producer] = $user; - } - // 2.3.3.9: added check user still exists - if ( $user ) { - $producers[] = array( - 'name' => $user->display_name, - 'url' => radio_station_get_producer_url( $producer ), - ); + if ( $show_producers ) { + // 2.4.0.4: convert possible (old) non-array value + if ( !is_array( $show_producers ) ) { + $show_producers = array( $show_producers ); + } + if ( is_array( $show_producers ) && ( count( $show_producers ) > 0 ) ) { + foreach ( $show_producers as $producer ) { + if ( isset( $radio_station_data['user-' . $producer] ) ) { + $user = $radio_station_data['user-' . $producer]; + } else { + $user = get_user_by( 'ID', $producer ); + $radio_station_data['user-' . $producer] = $user; + } + // 2.3.3.9: added check user still exists + if ( $user ) { + $producers[] = array( + 'name' => $user->display_name, + 'url' => radio_station_get_producer_url( $producer ), + ); + } } } } @@ -1149,7 +1165,7 @@ function radio_station_get_override_data_meta( $override ) { 'avatar_url' => 'show_avatar', // 'image_url' => 'show_thumbnail', ); - + // --- apply selected show data to override --- foreach ( $fields as $key => $meta_key ) { if ( isset( $show_fields[$meta_key] ) && $show_fields[$meta_key] ) { @@ -1214,7 +1230,7 @@ function radio_station_get_show_override( $override_id, $meta_key ) { // ----------------------------- // Get Linked Overrides for Show // ----------------------------- -// 2.3.3.9: added for getting linked show overrides +// 2.3.3.9: added for getting linked show overrides function radio_station_get_linked_overrides( $post_id ) { // -- get show ID for override or show post --- @@ -1234,7 +1250,7 @@ function radio_station_get_linked_overrides( $post_id ) { if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { $override_id = $result['post_id']; - + // --- check for published post status --- $query = "SELECT post_status FROM " . $wpdb->prefix . "posts WHERE ID = %d"; $query = $wpdb->prepare( $query, $override_id ); @@ -1242,7 +1258,7 @@ function radio_station_get_linked_overrides( $post_id ) { if ( 'publish' == $status ) { $override_ids[] = $override_id; } - } + } } // --- filter and return --- @@ -1272,7 +1288,7 @@ function radio_station_get_linked_override_times( $post_id ) { } } } - + // --- filter and return --- $overrides = apply_filters( 'radio_station_linked_override_times', $overrides, $post_id ); return $overrides; @@ -2293,7 +2309,7 @@ function radio_station_get_current_playlist() { } } } - + $latest = array(); if ( isset( $queued[0] ) ) { $latest = $queued[0]; @@ -4048,7 +4064,7 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $today = date( 'l', $time ); } else { - // 2.3.3.9: fix to use radio_station_get_time + // 2.3.3.9: fix to use radio_station_get_time // $timezone = radio_station_get_timezone(); // $datetime = radio_station_get_date_time( '@' . $time, $timezone ); // $today = $datetime->format( 'l' ); @@ -4069,7 +4085,7 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { if ( defined( 'RADIO_STATION_USE_SERVER_TIMES' ) && RADIO_STATION_USE_SERVER_TIMES ) { $weekdate = date( 'Y-m-d', $weekdate_time ); } else { - // 2.3.3.9: fix to use radio_station_get_time + // 2.3.3.9: fix to use radio_station_get_time // $weekdatetime = radio_station_get_date_time( '@' . $weekdate_time, $timezone ); // $weekdate = $weekdatetime->format( 'Y-m-d' ); $weekdate = radio_station_get_time( 'Y-m-d', $weekdate_time ); @@ -4077,6 +4093,23 @@ function radio_station_get_schedule_weekdates( $weekdays, $time = false ) { $weekdates[$weekday] = $weekdate; } + // 2.4.0.4: check/fix for duplicate date crackliness (daylight saving?) + foreach ( $weekdates as $day => $date ) { + if ( isset( $prevdate ) && ( $prevdate == $date ) ) { + $weekdates[$day] = radio_station_get_next_date( $date ); + $found = false; + foreach ( $weekdates as $k => $v ) { + if ( $found ) { + $weekdates[$k] = radio_station_next_date[$v]; + } + if ( $k == $day ) { + $found = true; + } + } + } + $prevdate = $date; + } + if ( RADIO_STATION_DEBUG ) { echo ''; echo 'Time: ' . $time . PHP_EOL; @@ -4348,7 +4381,7 @@ function radio_station_convert_show_shifts( $show ) { 'day' => $shift['day'], 'start' => $start_hour . ':' . $shift['start_min'], 'end' => $end_hour . ':' . $shift['end_min'], - 'encore' => $encore, + 'encore' => $encore, ); } $show['schedule'] = $schedule; @@ -4710,7 +4743,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['checkbox'] ) ) { - // --- checkbox inputs --- + // --- checkbox inputs --- // 2.2.8: removed strict in_array checking // 2.3.2: fix for unchecked boxes index warning $value = ''; @@ -4742,7 +4775,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } elseif ( in_array( $key, $types['date'] ) ) { - // --- datepicker date field --- + // --- datepicker date field --- $date = $_POST[$postkey]; $parts = explode( '-', $date ); if ( 3 == count( $parts ) ) { @@ -4750,7 +4783,7 @@ function radio_station_sanitize_input( $prefix, $key ) { $value = $date; } } - + } elseif ( in_array( $key, $types['hour'] ) ) { // --- hours (24) --- @@ -4785,7 +4818,7 @@ function radio_station_sanitize_input( $prefix, $key ) { } } - + return $value; } @@ -4844,17 +4877,17 @@ function radio_station_sanitize_playlist_entry( $entry ) { } elseif ( in_array( $key, $numeric_keys ) ) { $value = absint( $value ); if ( ( 'seconds' == $key ) && ( $value < 10 ) ) { - + } } elseif ( 'status' == $key ) { if ( $value != 'played' ) { $value = 'queued'; } - } + } $entry[$entry_key] = $value; - } + } } - + return $entry; } @@ -4940,11 +4973,11 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'for_time' => 'integer', ); } elseif ( 'master-schedule' == $type ) { - + // --- master schedule attribute keys --- // 2.3.3.9: added for AJAX schedule loading $keys = array( - + // --- schedule display options --- 'time' => 'text', 'show_times' => 'boolean', @@ -4965,13 +4998,13 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { 'show_genres' => 'boolean', 'show_encore' => 'boolean', 'show_file' => 'boolean', - + // --- view specific options --- 'divheight' => 'integer', 'gridwidth' => 'integer', 'hide_past_shows' => 'boolean', 'image_position' => 'text', - + ); } @@ -4986,6 +5019,10 @@ function radio_station_sanitize_shortcode_values( $type, $extras = false ) { return $atts; } +// ------------------------- +// === Transient Caching === +// ------------------------- + // -------------------------- // Delete Prefixed Transients // -------------------------- @@ -4995,8 +5032,8 @@ function radio_station_delete_transients_with_prefix( $prefix ) { // 2.3.3.9: add trailing underscore to prefix $prefix = $wpdb->esc_like( '_transient_' . $prefix . '_' ); - - // 2.3.3.9: fix to LIKE match + + // 2.3.3.9: fix to LIKE match $query = "SELECT `option_name` FROM " . $wpdb->prefix . "options WHERE `option_name` LIKE '%" . $prefix . "%'"; $results = $wpdb->get_results( $query, ARRAY_A ); // if ( RADIO_STATION_DEBUG ) { @@ -5008,7 +5045,7 @@ function radio_station_delete_transients_with_prefix( $prefix ) { } foreach ( $results as $option ) { - // 2.3.3.9: fix to replace malgunctioning ltrim + // 2.3.3.9: fix to replace malgunctioning ltrim // $key = ltrim( $option['option_name'], '_transient_' ); $key = substr( $option['option_name'], 11 ); delete_transient( $key ); @@ -5043,7 +5080,9 @@ function radio_station_clear_cached_data( $post_id = false, $post_type = false ) // --- maybe clear show meta data --- if ( $post_id ) { do_action( 'radio_station_clear_data', $post_type, $post_id ); - do_action( 'radio_station_clear_data', $post_type . '_meta', $post_id ); + if ( $post_type ) { + do_action( 'radio_station_clear_data', $post_type . '_meta', $post_id ); + } } // --- maybe send directory ping --- @@ -5057,6 +5096,21 @@ function radio_station_clear_cached_data( $post_id = false, $post_type = false ) } +// --------------------------------- +// Clear Cache on Status Transitions +// --------------------------------- +// 2.4.0.4: clear show and override caches on post status changes +add_action( 'transition_post_status', 'radio_station_clear_cache_on_transitions', 10, 3 ); +function radio_station_clear_cache_on_transitions( $new_status, $old_status, $post ) { + if ( $new_status == $old_status ) { + return; + } + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( in_array( $post->post_type, $post_types ) ) { + radio_station_clear_cached_data( $post->ID, $post->post_type ); + } +} + // -------------------- // === Translations === diff --git a/loader.php b/loader.php index 57ad710..597923e 100644 --- a/loader.php +++ b/loader.php @@ -149,7 +149,7 @@ public function __construct( $args ) { unset( $args['options'] ); // --- set plugin args and namespace --- - // 1.1.9: filter all arguments + // 1.1.9: filter all arguments $args = apply_filters( $args['namespace'] . '_args', $args ); $this->args = $args; $this->namespace = $args['namespace']; @@ -806,13 +806,13 @@ public function update_settings() { $settings = call_user_func( $funcname, $settings ); } - // --- output new settings --- + // --- output new settings --- if ( $this->debug ) { echo "
    All New Settings:
    "; print_r( $settings ); echo "

    "; } - + if ( $settings && is_array( $settings ) ) { // --- loop default keys to remove others --- @@ -843,7 +843,7 @@ public function update_settings() { } } } - + // --- update the plugin settings --- $settings['savetime'] = time(); @@ -1159,11 +1159,11 @@ public function add_actions() { // --- AJAX readme viewer --- add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) ); - + // --- load Freemius (requires PHP 5.4+) --- // 1.2.1: move Freemius loading to plugins_loaded hook if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) { - add_action( 'plugins_loaded', array( $this, 'load_freemius' ) ); + add_action( 'plugins_loaded', array( $this, 'load_freemius' ), 5 ); } } @@ -1345,10 +1345,10 @@ public function readme_viewer() { // ==================== public function load_freemius() { - // 1.2.1: no need to laod if not in admin area + // 1.2.1: no need to load if not in admin area if ( !is_admin() ) { - return; - } + return; + } $args = $this->args; $namespace = $this->namespace; @@ -1357,7 +1357,7 @@ public function load_freemius() { if ( !isset( $args['freemius_id'] ) || !isset( $args['freemius_key'] ) ) { return; } - + // --- check for free / premium plan --- // convert plan string value of 'free' or 'premium' to boolean premium switch // TODO: check for active addons also ? @@ -1451,8 +1451,8 @@ public function load_freemius() { 'support' => $args['support'], 'account' => $args['account'], ), - ); - + ); + // --- maybe add plugin submenu to parent menu --- if ( isset( $args['parentmenu'] ) ) { $settings['menu']['parent'] = array( 'slug' => $args['parentmenu'] ); @@ -1460,12 +1460,18 @@ public function load_freemius() { // --- filter settings before initializing --- $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings ); + if ( $this->debug ) { + echo 'Freemius Settings: ' . print_r( $settings, true ) . ''; + } if ( !$settings || !is_array( $settings ) ) { return; } // --- initialize Freemius now --- $freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings ); + if ( $this->debug ) { + echo 'Freemius Object: ' . print_r( $freemius, true ) . ''; + } // --- set plugin basename --- // 1.0.1: set free / premium plugin basename @@ -1475,7 +1481,7 @@ public function load_freemius() { // --- add Freemius connect message filter --- $this->freemius_connect(); - + // --- fire Freemius loaded action --- do_action( $args['namespace'] . '_loaded' ); } @@ -1588,7 +1594,7 @@ public function plugin_links( $links, $file ) { // --- maybe add Pro upgrade link --- if ( isset( $args['hasplans'] ) && $args['hasplans'] ) { - + // 1.2.1: add check if premium is already installed if ( !isset( $args['plan'] ) || ( 'premium' != $args['plan'] ) ) { @@ -1604,7 +1610,7 @@ public function plugin_links( $links, $file ) { array_unshift( $links, $upgrade_link ); // --- external pro link --- - // 1.2.0: added separate pro details link + // 1.2.0: added separate pro details link if ( isset( $args['pro_link'] ) ) { $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : ''; $pro_link = "" . esc_html( __('Pro Details' ) ) . ""; @@ -1615,16 +1621,14 @@ public function plugin_links( $links, $file ) { // --- maybe add Addons link --- // 1.2.0: activated add-ons link - if ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) { + // 1.2.2: remove duplication of addons link + if ( !isset( $args['hasaddons'] ) || !$args['hasaddons'] ) { if ( isset( $args['addons_link'] ) ) { $addons_url = $args['addons_link']; $addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : ''; - } else { - $addons_url = add_query_arg( 'page', $args['slug'] . '-addons', admin_url( 'admin.php' ) ); - $addons_target = ''; + $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; + array_unshift( $links, $addons_link ); } - $addons_link = "" . esc_html( __( 'Add Ons' ) ) . ""; - array_unshift( $links, $addons_link ); } } @@ -1755,7 +1759,7 @@ public function settings_header() { // 1.1.9: add filter for plugin icon url $icon_url = apply_filters( $namespace . '_settings_page_icon_url', $icon_url ); echo '
    '; - if ( $icon_url ) { + if ( $icon_url ) { echo ''; } echo '
    '; echo '

    ' . esc_html( $subtitle ) . '

    '; echo '
    - apply_filters( 'hide_license_key', false ) ); - - $profile = array(); - - if ( ! $is_whitelabeled ) { - $profile[] = array( - 'id' => 'user_name', - 'title' => fs_text_inline( 'Name', 'name', $slug ), - 'value' => $name - ); - // if (isset($user->email) && false !== strpos($user->email, '@')) - $profile[] = array( - 'id' => 'email', - 'title' => fs_text_inline( 'Email', 'email', $slug ), - 'value' => $user->email - ); - - if ( is_numeric( $user->id ) ) { - $profile[] = array( - 'id' => 'user_id', - 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), - 'value' => $user->id - ); - } - } - - $profile[] = array( - 'id' => 'product', - 'title' => ( $fs->is_plugin() ? - fs_text_inline( 'Plugin', 'plugin', $slug ) : - fs_text_inline( 'Theme', 'theme', $slug ) ), - 'value' => $fs->get_plugin_title() - ); - - $profile[] = array( - 'id' => 'product_id', - 'title' => ( $fs->is_plugin() ? - fs_text_inline( 'Plugin', 'plugin', $slug ) : - fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), - 'value' => $fs->get_id() - ); - - if ( ! fs_is_network_admin()) { - $profile[] = array( - 'id' => 'site_id', - 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), - 'value' => is_string( $site->id ) ? - $site->id : - fs_text_inline( 'No ID', 'no-id', $slug ) - ); - - $profile[] = array( - 'id' => 'site_public_key', - 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), - 'value' => $site->public_key - ); - - $profile[] = array( - 'id' => 'site_secret_key', - 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), - 'value' => ( ( is_string( $site->secret_key ) ) ? - $site->secret_key : - fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) - ) - ); - } - - $profile[] = array( - 'id' => 'version', - 'title' => $version_text, - 'value' => $fs->get_plugin_version() - ); - - if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) { - $profile[] = array( - 'id' => 'beta_program', - 'title' => '', - 'value' => $site->is_beta - ); - } - - if ( $has_paid_plan || $has_bundle_license ) { - if ( $fs->is_trial() ) { - if ( $show_plan_row ) { - $profile[] = array( - 'id' => 'plan', - 'title' => $plan_text, - 'value' => ( is_string( $trial_plan->name ) ? - strtoupper( $trial_plan->title ) : - fs_text_inline( 'Trial', 'trial', $slug ) ) - ); - } - } else { - if ( $show_plan_row ) { - $profile[] = array( - 'id' => 'plan', - 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, - 'value' => strtoupper( is_string( $plan->name ) ? - $plan->title : - strtoupper( $free_text ) - ) - ); - - if ( $has_bundle_license ) { - $profile[] = array( - 'id' => 'bundle_plan', - 'title' => $bundle_plan_text, - 'value' => $bundle_plan_title - ); - } - } - - if ( is_object( $license ) ) { - if ( ! $hide_license_key ) { - $profile[] = array( - 'id' => 'license_key', - 'title' => fs_text_inline( 'License Key', $slug ), - 'value' => $license->secret_key, - ); - } - } - } - } - ?> - - - - - > - - - - - - - - - - - is_verified() ) : ?> - - - - is_trial() ) : ?> - - - is_lifetime() ) : ?> - is_first_payment_pending() ) : ?> - is_expired() ?> - - - is_first_payment_pending() ) : ?> - - - is_trial() ) : ?> - - - -
    - is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?> - - _get_plan_by_id( $available_license->plan_id ) ?> - $fs, - 'slug' => $slug, - 'license' => $available_license, - 'plan' => $premium_plan, - 'is_localhost' => $site->is_localhost(), - 'install_id' => $site->id, - 'class' => 'button-primary', - ); - fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?> - -
    - - - - - - get_unique_affix() . '_sync_license' ) ?> - is_single_plan() ) : ?> - - - - -
    - - - - - - - - - has_premium_version() ) : ?> - - - can_use_premium_code() ) : ?> - - - - - - -
    - - - -
    - - - is_verified() ) : ?> -
    - - - -
    - - - has_release_on_freemius() ) : ?> - - - - - - - - - - secret_key ) && in_array( $p['id'], array( - 'email', - 'user_name' - ) ) ) - ) : ?> -
    - - - - -
    - - - -
    - - - -
    -

    -
    - - - - - - - - -
    -
    -
    -
    - - - - - - - - - - -
    -
    -
    - - -
    -
    -
    -
    -
    - - - -
    -
    - - - - - - - - - - - - - - - - - is_whitelabeled_by_flag() ) { - $hide_all_addons_data = true; - - foreach ( $addons_to_show as $addon_id ) { - $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); - $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed ); - $is_addon_connected = $addon_info['is_connected']; - - $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? - freemius( $addon_id ) : - null; - - $is_whitelabeled = is_object( $fs_addon ) ? - $fs_addon->is_whitelabeled( true ) : - $addon_info['is_whitelabeled']; - - if ( ! $is_whitelabeled ) { - $hide_all_addons_data = false; - } - - if ( $is_data_debug_mode ) { - $is_whitelabeled = false; - } - - $addon_info_by_id[ $addon_id ] = $addon_info; - } - } - - foreach ( $addons_to_show as $addon_id ) { - $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); - - if ( - $hide_all_addons_data && - ! $is_addon_installed && - ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) ) - ) { - continue; - } - - $addon_view_params = array( - 'parent_fs' => $fs, - 'addon_id' => $addon_id, - 'odd' => $odd, - 'fs_blog_id' => $fs_blog_id, - 'active_plugins_directories_map' => &$active_plugins_directories_map, - 'is_addon_installed' => $is_addon_installed, - 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ? - $addon_info_by_id[ $addon_id ] : - $fs->_get_addon_info( $addon_id, $is_addon_installed ), - 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode ) - ); - - fs_require_template( - 'account/partials/addon.php', - $addon_view_params - ); - - $odd = ! $odd; - } ?> - -

    -
    -
    - - - do_action( 'after_account_details' ) ?> - - $VARS['id'] ); - fs_require_once_template( 'account/billing.php', $view_params ); - fs_require_once_template( 'account/payments.php', $view_params ); - } - ?> - - - - - - _get_subscription_cancellation_dialog_box_template_params( true ); - if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { - fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); - } - ?> - -_add_tabs_after_content(); - } - - $params = array( - 'page' => 'account', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/account/billing.php b/freemius/templates/account/billing.php index a4de409..e69de29 100644 --- a/freemius/templates/account/billing.php +++ b/freemius/templates/account/billing.php @@ -1,423 +0,0 @@ -get_slug(); - - $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); - $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); - - $billing = $fs->_fetch_billing(); - $has_billing = ( $billing instanceof FS_Billing ); - if ( ! $has_billing ) { - $billing = new FS_Billing(); - } -?> - -
    -
    -

    - > - - - - - - - - - - - - - - 'Afghanistan', - 'AX' => 'Aland Islands', - 'AL' => 'Albania', - 'DZ' => 'Algeria', - 'AS' => 'American Samoa', - 'AD' => 'Andorra', - 'AO' => 'Angola', - 'AI' => 'Anguilla', - 'AQ' => 'Antarctica', - 'AG' => 'Antigua and Barbuda', - 'AR' => 'Argentina', - 'AM' => 'Armenia', - 'AW' => 'Aruba', - 'AU' => 'Australia', - 'AT' => 'Austria', - 'AZ' => 'Azerbaijan', - 'BS' => 'Bahamas', - 'BH' => 'Bahrain', - 'BD' => 'Bangladesh', - 'BB' => 'Barbados', - 'BY' => 'Belarus', - 'BE' => 'Belgium', - 'BZ' => 'Belize', - 'BJ' => 'Benin', - 'BM' => 'Bermuda', - 'BT' => 'Bhutan', - 'BO' => 'Bolivia', - 'BQ' => 'Bonaire, Saint Eustatius and Saba', - 'BA' => 'Bosnia and Herzegovina', - 'BW' => 'Botswana', - 'BV' => 'Bouvet Island', - 'BR' => 'Brazil', - 'IO' => 'British Indian Ocean Territory', - 'VG' => 'British Virgin Islands', - 'BN' => 'Brunei', - 'BG' => 'Bulgaria', - 'BF' => 'Burkina Faso', - 'BI' => 'Burundi', - 'KH' => 'Cambodia', - 'CM' => 'Cameroon', - 'CA' => 'Canada', - 'CV' => 'Cape Verde', - 'KY' => 'Cayman Islands', - 'CF' => 'Central African Republic', - 'TD' => 'Chad', - 'CL' => 'Chile', - 'CN' => 'China', - 'CX' => 'Christmas Island', - 'CC' => 'Cocos Islands', - 'CO' => 'Colombia', - 'KM' => 'Comoros', - 'CK' => 'Cook Islands', - 'CR' => 'Costa Rica', - 'HR' => 'Croatia', - 'CU' => 'Cuba', - 'CW' => 'Curacao', - 'CY' => 'Cyprus', - 'CZ' => 'Czech Republic', - 'CD' => 'Democratic Republic of the Congo', - 'DK' => 'Denmark', - 'DJ' => 'Djibouti', - 'DM' => 'Dominica', - 'DO' => 'Dominican Republic', - 'TL' => 'East Timor', - 'EC' => 'Ecuador', - 'EG' => 'Egypt', - 'SV' => 'El Salvador', - 'GQ' => 'Equatorial Guinea', - 'ER' => 'Eritrea', - 'EE' => 'Estonia', - 'ET' => 'Ethiopia', - 'FK' => 'Falkland Islands', - 'FO' => 'Faroe Islands', - 'FJ' => 'Fiji', - 'FI' => 'Finland', - 'FR' => 'France', - 'GF' => 'French Guiana', - 'PF' => 'French Polynesia', - 'TF' => 'French Southern Territories', - 'GA' => 'Gabon', - 'GM' => 'Gambia', - 'GE' => 'Georgia', - 'DE' => 'Germany', - 'GH' => 'Ghana', - 'GI' => 'Gibraltar', - 'GR' => 'Greece', - 'GL' => 'Greenland', - 'GD' => 'Grenada', - 'GP' => 'Guadeloupe', - 'GU' => 'Guam', - 'GT' => 'Guatemala', - 'GG' => 'Guernsey', - 'GN' => 'Guinea', - 'GW' => 'Guinea-Bissau', - 'GY' => 'Guyana', - 'HT' => 'Haiti', - 'HM' => 'Heard Island and McDonald Islands', - 'HN' => 'Honduras', - 'HK' => 'Hong Kong', - 'HU' => 'Hungary', - 'IS' => 'Iceland', - 'IN' => 'India', - 'ID' => 'Indonesia', - 'IR' => 'Iran', - 'IQ' => 'Iraq', - 'IE' => 'Ireland', - 'IM' => 'Isle of Man', - 'IL' => 'Israel', - 'IT' => 'Italy', - 'CI' => 'Ivory Coast', - 'JM' => 'Jamaica', - 'JP' => 'Japan', - 'JE' => 'Jersey', - 'JO' => 'Jordan', - 'KZ' => 'Kazakhstan', - 'KE' => 'Kenya', - 'KI' => 'Kiribati', - 'XK' => 'Kosovo', - 'KW' => 'Kuwait', - 'KG' => 'Kyrgyzstan', - 'LA' => 'Laos', - 'LV' => 'Latvia', - 'LB' => 'Lebanon', - 'LS' => 'Lesotho', - 'LR' => 'Liberia', - 'LY' => 'Libya', - 'LI' => 'Liechtenstein', - 'LT' => 'Lithuania', - 'LU' => 'Luxembourg', - 'MO' => 'Macao', - 'MK' => 'Macedonia', - 'MG' => 'Madagascar', - 'MW' => 'Malawi', - 'MY' => 'Malaysia', - 'MV' => 'Maldives', - 'ML' => 'Mali', - 'MT' => 'Malta', - 'MH' => 'Marshall Islands', - 'MQ' => 'Martinique', - 'MR' => 'Mauritania', - 'MU' => 'Mauritius', - 'YT' => 'Mayotte', - 'MX' => 'Mexico', - 'FM' => 'Micronesia', - 'MD' => 'Moldova', - 'MC' => 'Monaco', - 'MN' => 'Mongolia', - 'ME' => 'Montenegro', - 'MS' => 'Montserrat', - 'MA' => 'Morocco', - 'MZ' => 'Mozambique', - 'MM' => 'Myanmar', - 'NA' => 'Namibia', - 'NR' => 'Nauru', - 'NP' => 'Nepal', - 'NL' => 'Netherlands', - 'NC' => 'New Caledonia', - 'NZ' => 'New Zealand', - 'NI' => 'Nicaragua', - 'NE' => 'Niger', - 'NG' => 'Nigeria', - 'NU' => 'Niue', - 'NF' => 'Norfolk Island', - 'KP' => 'North Korea', - 'MP' => 'Northern Mariana Islands', - 'NO' => 'Norway', - 'OM' => 'Oman', - 'PK' => 'Pakistan', - 'PW' => 'Palau', - 'PS' => 'Palestinian Territory', - 'PA' => 'Panama', - 'PG' => 'Papua New Guinea', - 'PY' => 'Paraguay', - 'PE' => 'Peru', - 'PH' => 'Philippines', - 'PN' => 'Pitcairn', - 'PL' => 'Poland', - 'PT' => 'Portugal', - 'PR' => 'Puerto Rico', - 'QA' => 'Qatar', - 'CG' => 'Republic of the Congo', - 'RE' => 'Reunion', - 'RO' => 'Romania', - 'RU' => 'Russia', - 'RW' => 'Rwanda', - 'BL' => 'Saint Barthelemy', - 'SH' => 'Saint Helena', - 'KN' => 'Saint Kitts and Nevis', - 'LC' => 'Saint Lucia', - 'MF' => 'Saint Martin', - 'PM' => 'Saint Pierre and Miquelon', - 'VC' => 'Saint Vincent and the Grenadines', - 'WS' => 'Samoa', - 'SM' => 'San Marino', - 'ST' => 'Sao Tome and Principe', - 'SA' => 'Saudi Arabia', - 'SN' => 'Senegal', - 'RS' => 'Serbia', - 'SC' => 'Seychelles', - 'SL' => 'Sierra Leone', - 'SG' => 'Singapore', - 'SX' => 'Sint Maarten', - 'SK' => 'Slovakia', - 'SI' => 'Slovenia', - 'SB' => 'Solomon Islands', - 'SO' => 'Somalia', - 'ZA' => 'South Africa', - 'GS' => 'South Georgia and the South Sandwich Islands', - 'KR' => 'South Korea', - 'SS' => 'South Sudan', - 'ES' => 'Spain', - 'LK' => 'Sri Lanka', - 'SD' => 'Sudan', - 'SR' => 'Suriname', - 'SJ' => 'Svalbard and Jan Mayen', - 'SZ' => 'Swaziland', - 'SE' => 'Sweden', - 'CH' => 'Switzerland', - 'SY' => 'Syria', - 'TW' => 'Taiwan', - 'TJ' => 'Tajikistan', - 'TZ' => 'Tanzania', - 'TH' => 'Thailand', - 'TG' => 'Togo', - 'TK' => 'Tokelau', - 'TO' => 'Tonga', - 'TT' => 'Trinidad and Tobago', - 'TN' => 'Tunisia', - 'TR' => 'Turkey', - 'TM' => 'Turkmenistan', - 'TC' => 'Turks and Caicos Islands', - 'TV' => 'Tuvalu', - 'VI' => 'U.S. Virgin Islands', - 'UG' => 'Uganda', - 'UA' => 'Ukraine', - 'AE' => 'United Arab Emirates', - 'GB' => 'United Kingdom', - 'US' => 'United States', - 'UM' => 'United States Minor Outlying Islands', - 'UY' => 'Uruguay', - 'UZ' => 'Uzbekistan', - 'VU' => 'Vanuatu', - 'VA' => 'Vatican', - 'VE' => 'Venezuela', - 'VN' => 'Vietnam', - 'WF' => 'Wallis and Futuna', - 'EH' => 'Western Sahara', - 'YE' => 'Yemen', - 'ZM' => 'Zambia', - 'ZW' => 'Zimbabwe', - ) ?> - - - - - - -
    - -
    -
    -
    - - \ No newline at end of file diff --git a/freemius/templates/account/index.php b/freemius/templates/account/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/account/index.php +++ b/freemius/templates/account/index.php @@ -1,3 +0,0 @@ - -
    - - - - - - -
    \ No newline at end of file diff --git a/freemius/templates/account/partials/addon.php b/freemius/templates/account/partials/addon.php index b77cd81..e69de29 100644 --- a/freemius/templates/account/partials/addon.php +++ b/freemius/templates/account/partials/addon.php @@ -1,446 +0,0 @@ -get_slug(); - - $fs_blog_id = $VARS['fs_blog_id']; - - $active_plugins_directories_map = $VARS['active_plugins_directories_map']; - - $addon_info = $VARS['addon_info']; - $is_addon_activated = $fs->is_addon_activated( $addon_id ); - $is_addon_connected = $addon_info['is_connected']; - $is_addon_installed = $VARS['is_addon_installed']; - - $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? - freemius( $addon_id ) : - false; - - // Aliases. - $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); - $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); - $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); - /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ - $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); - $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); - $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); - $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); - $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); - /* translators: %s: Plan title (e.g. "Professional") */ - $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); - $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); - /* translators: %s: Time period (e.g. Auto renews in "2 months") */ - $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); - /* translators: %s: Time period (e.g. Expires in "2 months") */ - $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); - $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); - $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); - $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); - $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); - $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); - $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); - $free_text = fs_text_inline( 'Free', 'free', $slug ); - $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); - $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); - - // Defaults. - $plan = null; - $is_paid_trial = false; - /** - * @var FS_Plugin_License $license - */ - $license = null; - $site = null; - $is_active_subscription = false; - $subscription = null; - $is_paying = false; - $show_upgrade = false; - $is_whitelabeled = $VARS['is_whitelabeled']; - - if ( is_object( $fs_addon ) ) { - $is_paying = $fs_addon->is_paying(); - $user = $fs_addon->get_user(); - $site = $fs_addon->get_site(); - $license = $fs_addon->_get_license(); - $subscription = ( is_object( $license ) ? - $fs_addon->_get_subscription( $license->id ) : - null ); - $plan = $fs_addon->get_plan(); - $plan_name = $plan->name; - $plan_title = $plan->title; - $is_paid_trial = $fs_addon->is_paid_trial(); - $version = $fs_addon->get_plugin_version(); - $is_whitelabeled = ( - $fs_addon->is_whitelabeled( true ) && - ! $fs_addon->get_parent_instance()->is_data_debug_mode() - ); - $show_upgrade = ( - ! $is_whitelabeled && - $fs_addon->has_paid_plan() && - ! $is_paying && - ! $is_paid_trial && - ! $fs_addon->_has_premium_license() - ); - } else if ( $is_addon_connected ) { - if ( - empty( $addon_info ) || - ! isset( $addon_info['site'] ) - ) { - $is_addon_connected = false; - } else { - /** - * @var FS_Site $site - */ - $site = $addon_info['site']; - $version = $addon_info['version']; - - $plan_name = isset( $addon_info['plan_name'] ) ? - $addon_info['plan_name'] : - ''; - - $plan_title = isset( $addon_info['plan_title'] ) ? - $addon_info['plan_title'] : - ''; - - if ( isset( $addon_info['license'] ) ) { - $license = $addon_info['license']; - } - - if ( isset( $addon_info['subscription'] ) ) { - $subscription = $addon_info['subscription']; - } - - $has_valid_and_active_license = ( - is_object( $license ) && - $license->is_active() && - $license->is_valid() - ); - - $is_paid_trial = ( - $site->is_trial() && - $has_valid_and_active_license && - ( $site->trial_plan_id == $license->plan_id ) - ); - - $is_whitelabeled = $addon_info['is_whitelabeled']; - } - } - - $has_feature_enabled_license = ( - is_object( $license ) && - $license->is_features_enabled() - ); - - $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); - - $show_delete_install_button = ( ! $is_paying && WP_FS__DEV_MODE && ! $is_whitelabeled ); -?> -> -
    - - - id ?> - is_trial() || is_object( $license ) ) : ?> - is_trial() ) { - $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); - - $tags[] = array( - 'label' => sprintf( - ( $is_paid_trial ? - $renews_in_text : - $expires_in_text ), - human_time_diff( time(), strtotime( $site->trial_ends ) ) - ), - 'type' => ( $is_paid_trial ? 'success' : 'warn' ) - ); - } else { - if ( is_object( $license ) ) { - if ( $license->is_cancelled ) { - $tags[] = array( - 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), - 'type' => 'error' - ); - } else if ( $license->is_expired() ) { - $tags[] = array( - 'label' => fs_text_inline( 'Expired', 'expired', $slug ), - 'type' => 'error' - ); - } else if ( $license->is_lifetime() ) { - $tags[] = array( - 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), - 'type' => 'success' - ); - } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { - $tags[] = array( - 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), - 'type' => 'warn' - ); - } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { - $tags[] = array( - 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), - 'type' => 'success' - ); - } - } - } - - foreach ( $tags as $t ) { - printf( '' . "\n", $t['type'], $t['label'] ); - } - ?> - - 1 ) : ?> -
    - - 1 ) : ?>
    - - get_addon_basename( $addon_id ) ?> - - - - - is_allowed_to_install() ) : ?> - - - - - - - get_id(), 'account', - 'delete_account', - fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), - '', - array( 'plugin_id' => $addon_id ), - false, - $show_upgrade - ); - } - ?> -
    - id ?> - - -
    - - - - -
    - -
    $fs, - 'slug' => $slug, - 'blog_id' => $blog_id, - 'class' => 'button-small', - ); - - $license = null; - if ( $is_registered ) { - $view_params['install_id'] = $install->id; - $view_params['is_localhost'] = $install->is_localhost(); - - $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); - $license = $has_license ? - $fs->_get_license_by_id( $install->license_id ) : - null; - } else { - $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); - } - - if ( ! $is_whitelabeled ) { - if ( is_object( $license ) ) { - $view_params['license'] = $license; - - // Show license deactivation button. - fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); - } else { - if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { - // Main license is available for activation. - $available_license = $main_license; - } else { - // Try to find any available license for activation. - $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); - } - - if ( is_object( $available_license ) ) { - $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); - - $view_params['license'] = $available_license; - $view_params['class'] .= ' button-primary'; - $view_params['plan'] = $premium_plan; - - fs_require_template( 'account/partials/activate-license-button.php', $view_params ); - } - } - } - } ?> - is_trial() ) { - if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) { - $plan_title = is_string( $trial_plan->name ) ? - strtoupper( $trial_plan->title ) : - fs_text_inline( 'Trial', 'trial', $slug ); - } else { - $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); - } - } else { - $plan = $fs->_get_plan_by_id( $install->plan_id ); - $plan_title = strtoupper( is_string( $plan->title ) ? - $plan->title : - strtoupper( $free_text ) - ); - } - } - ?> - - -
    - - - - - - - - - - - - > - - - - - - - -
    id ?>created ) ) ?>formatted_gross() ?>is_migrated() ) : ?>
    -
    -
    -
    -get_slug(); - - $open_addon_slug = fs_request_get( 'slug' ); - - $open_addon = false; - - $is_data_debug_mode = $fs->is_data_debug_mode(); - $is_whitelabeled = $fs->is_whitelabeled(); - - /** - * @var FS_Plugin[] - */ - $addons = $fs->get_addons(); - - $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); - - $account_addon_ids = $fs->get_updated_account_addons(); - - $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); - $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); - - $has_tabs = $fs->_add_tabs_before_content(); - - $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? - get_current_blog_id() : - 0; -?> -
    - -

    get_plugin_name() ) ) ?>

    - - - do_action( 'addons/after_title' ) ?> - -
    - -

    - -
      - - _get_addons_plans_and_pricing_map_by_id(); - - $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); - ?> - is_whitelabeled_by_flag() ) { - $hide_all_addons_data = true; - - $addon_ids = $fs->get_updated_account_addons(); - $installed_addons = $fs->get_installed_addons(); - foreach ( $installed_addons as $fs_addon ) { - $addon_ids[] = $fs_addon->get_id(); - } - - if ( ! empty( $addon_ids ) ) { - $addon_ids = array_unique( $addon_ids ); - } - - foreach ( $addon_ids as $addon_id ) { - $addon = $fs->get_addon( $addon_id ); - - if ( ! is_object( $addon ) ) { - continue; - } - - $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); - - if ( ! $addon_storage->is_whitelabeled ) { - $hide_all_addons_data = false; - break; - } - - if ( $is_data_debug_mode ) { - $is_whitelabeled = false; - } - } - } - ?> - - get_addon_basename( $addon->id ); - - $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); - - if ( ! $is_addon_installed && $hide_all_addons_data ) { - continue; - } - - $is_addon_activated = $is_addon_installed ? - $fs->is_addon_activated( $addon->id ) : - false; - - $is_plugin_active = ( - $is_addon_activated || - isset( $active_plugins_directories_map[ dirname( $basename ) ] ) - ); - - $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); - - $price = 0; - $has_trial = false; - $has_free_plan = false; - $has_paid_plan = false; - - if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { - $plans = $plans_and_pricing_by_addon_id[$addon->id]; - - if ( is_array( $plans ) && 0 < count( $plans ) ) { - foreach ( $plans as $plan ) { - if ( ! isset( $plan->pricing ) || - ! is_array( $plan->pricing ) || - 0 == count( $plan->pricing ) - ) { - // No pricing means a free plan. - $has_free_plan = true; - continue; - } - - - $has_paid_plan = true; - $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); - - $min_price = 999999; - foreach ( $plan->pricing as $pricing ) { - $pricing = new FS_Pricing( $pricing ); - - if ( ! $pricing->is_usd() ) { - /** - * Skip non-USD pricing. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.1 - */ - continue; - } - - if ( $pricing->has_annual() ) { - $min_price = min( $min_price, $pricing->annual_price ); - } else if ( $pricing->has_monthly() ) { - $min_price = min( $min_price, 12 * $pricing->monthly_price ); - } - } - - if ( $min_price < 999999 ) { - $price = $min_price; - } - - } - } - - if ( ! $has_paid_plan && ! $has_free_plan ) { - continue; - } - } - ?> -
    • - get_id() . '&plugin=' . $addon->slug . - '&TB_iframe=true&width=600&height=550' ) ), - esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), - esc_attr( $addon->title ) - ) . ' class="thickbox%s">%s'; - - echo sprintf( - $view_details_link, - /** - * Additional class. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - ' fs-overlay', - /** - * Set the view details link text to an empty string since it is an overlay that - * doesn't really need a text and whose purpose is to open the details dialog when - * the card is clicked. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.4 - */ - '' - ); - ?> - info ) ) { - $addon->info = new stdClass(); - } - if ( ! isset( $addon->info->card_banner_url ) ) { - $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; - } - if ( ! isset( $addon->info->short_description ) ) { - $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; - } - ?> -
      -
        -
      • %s', - esc_html( $is_plugin_active ? - fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : - fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) - ) - ); - } - ?>
      • - -
      • title ?>
      • -
      • - 0) - $descriptors[] = '$' . number_format( $price, 2 ); - if ($has_trial) - $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); - - echo implode(' - ', $descriptors); - - } ?> -
      • -
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • - is_wp_org_compliant ); - - $is_allowed_to_install = ( - $fs->is_allowed_to_install() || - $is_free_only_wp_org_compliant - ); - - $show_premium_activation_or_installation_action = true; - - if ( ! in_array( $addon->id, $account_addon_ids ) ) { - $show_premium_activation_or_installation_action = false; - } else if ( $is_addon_installed ) { - /** - * If any add-on's version (free or premium) is installed, check if the - * premium version can be activated and show the relevant action. Otherwise, - * show the relevant action for the free version. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.5 - */ - $fs_addon = $is_addon_activated ? - $fs->get_addon_instance( $addon->id ) : - null; - - $premium_plugin_basename = is_object( $fs_addon ) ? - $fs_addon->premium_plugin_basename() : - "{$addon->premium_slug}/{$addon->slug}.php"; - - if ( - ( $is_addon_activated && $fs_addon->is_premium() ) || - file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) - ) { - $basename = $premium_plugin_basename; - } - - $show_premium_activation_or_installation_action = ( - ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && - /** - * This check is needed for cases when an active add-on doesn't have an - * associated Freemius instance. - * - * @author Leo Fajardo (@leorw) - * @since 2.4.5 - */ - ( ! $is_plugin_active ) - ); - } - ?> - -
      • - - _get_latest_download_local_url( $addon->id ); - ?> - -
      • -
        - - %s', - wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), - fs_esc_html_inline( 'Install Now', 'install-now', $slug ) - ); - } else { - echo sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), - fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), - fs_text_inline( 'Activate', 'activate', $addon->slug ) - ); - } - ?> - - - -
        -
        -
      • - -
      -
      -
    • - - -
    -
    - - do_action( 'addons/after_addons' ) ?> -
    - -_add_tabs_after_content(); - } - - $params = array( - 'page' => 'addons', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/add-trial-to-pricing.php b/freemius/templates/add-trial-to-pricing.php index 24fc885..e69de29 100644 --- a/freemius/templates/add-trial-to-pricing.php +++ b/freemius/templates/add-trial-to-pricing.php @@ -1,31 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/admin-notice.php b/freemius/templates/admin-notice.php index 6079e71..e69de29 100644 --- a/freemius/templates/admin-notice.php +++ b/freemius/templates/admin-notice.php @@ -1,76 +0,0 @@ - - data-id="" data-manager-id="" data-slug="" data-type="" - class=" fs-notice"> - - - -
    -
    - -
    - - -
    -
    diff --git a/freemius/templates/ajax-loader.php b/freemius/templates/ajax-loader.php index bc116f8..e69de29 100644 --- a/freemius/templates/ajax-loader.php +++ b/freemius/templates/ajax-loader.php @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/freemius/templates/auto-installation.php b/freemius/templates/auto-installation.php index 6b8183c..e69de29 100644 --- a/freemius/templates/auto-installation.php +++ b/freemius/templates/auto-installation.php @@ -1,249 +0,0 @@ -is_tracking_allowed() ? - 'stop_tracking' : - 'allow_tracking'; - - $title = $fs->get_plugin_title(); - - if ( $plugin_id != $fs->get_id() ) { - $addon = $fs->get_addon( $plugin_id ); - - if ( is_object( $addon ) ) { - $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); - } - } - - $plugin_title = sprintf( - '%s', - esc_html( $title ) - ); - - $sec_countdown = 30; - $countdown_html = sprintf( - esc_js( - /* translators: %s: Number of seconds */ - fs_text_inline( '%s sec', 'x-sec', $slug ) - ), - sprintf( '%s', $sec_countdown ) - ); - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); - fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); - - $params = array(); - $loader_html = fs_get_template( 'ajax-loader.php', $params ); - - // Pass unique auto installation URL if WP_Filesystem is needed. - $install_url = $fs->_get_sync_license_url( - $plugin_id, - true, - array( 'auto_install' => 'true' ) - ); - - - ob_start(); - - $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. - - $credentials = request_filesystem_credentials( - esc_url_raw( $install_url ), - $method, - false, - WP_PLUGIN_DIR, - array() - ); - - $credentials_form = ob_get_clean(); - - $require_credentials = ! empty( $credentials_form ); -?> -
    -
    -
    -

    -
    -
    - - -
    - -
    - -

    %s', - 'https://freemius.com', - 'freemius.com' - ), - $countdown_html - ) ?>

    - - -
    - -
    -
    ' - - diff --git a/freemius/templates/checkout.php b/freemius/templates/checkout.php index a0969cc..e69de29 100644 --- a/freemius/templates/checkout.php +++ b/freemius/templates/checkout.php @@ -1,337 +0,0 @@ -get_slug(); - - $timestamp = time(); - - $context_params = array( - 'plugin_id' => $fs->get_id(), - 'public_key' => $fs->get_public_key(), - 'plugin_version' => $fs->get_plugin_version(), - 'mode' => 'dashboard', - 'trial' => fs_request_get_bool( 'trial' ), - ); - - $plan_id = fs_request_get( 'plan_id' ); - if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { - $context_params['plan_id'] = $plan_id; - } - - $licenses = fs_request_get( 'licenses' ); - if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { - $context_params['licenses'] = $licenses; - } - - $plugin_id = fs_request_get( 'plugin_id' ); - if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { - $plugin_id = $fs->get_id(); - } - - if ( $plugin_id == $fs->get_id() ) { - $is_premium = $fs->is_premium(); - - $bundle_id = $fs->get_bundle_id(); - if ( ! is_null( $bundle_id ) ) { - $context_params['bundle_id'] = $bundle_id; - } - } else { - // Identify the module code version of the checkout context module. - if ( $fs->is_addon_activated( $plugin_id ) ) { - $fs_addon = Freemius::get_instance_by_id( $plugin_id ); - $is_premium = $fs_addon->is_premium(); - } else { - // If add-on isn't activated assume the premium version isn't installed. - $is_premium = false; - } - } - - // Get site context secure params. - if ( $fs->is_registered() ) { - $site = $fs->get_site(); - - if ( $plugin_id != $fs->get_id() ) { - if ( $fs->is_addon_activated( $plugin_id ) ) { - $fs_addon = Freemius::get_instance_by_id( $plugin_id ); - $addon_site = $fs_addon->get_site(); - if ( is_object( $addon_site ) ) { - $site = $addon_site; - } - } - } - - $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( - $site, - $timestamp, - 'checkout' - ) ); - } else { - $current_user = Freemius::_get_current_wp_user(); - - // Add site and user info to the request, this information - // is NOT being stored unless the user complete the purchase - // and agrees to the TOS. - $context_params = array_merge( $context_params, array( - 'user_firstname' => $current_user->user_firstname, - 'user_lastname' => $current_user->user_lastname, - 'user_email' => $current_user->user_email, - 'home_url' => home_url(), - ) ); - - $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); - - if ( is_object( $fs_user ) && $fs_user->is_verified() ) { - $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( - $fs_user, - $timestamp, - 'checkout' - ) ); - } - } - - if ( $fs->is_payments_sandbox() ) { - // Append plugin secure token for sandbox mode authentication. - $context_params['sandbox'] = FS_Security::instance()->get_secure_token( - $fs->get_plugin(), - $timestamp, - 'checkout' - ); - - /** - * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. - */ - if ( empty( $context_params['s_ctx_ts'] ) ) { - $context_params['s_ctx_ts'] = $timestamp; - } - } - - $return_url = $fs->_get_sync_license_url( $plugin_id ); - - $can_user_install = ( - ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || - ( $fs->is_theme() && current_user_can( 'install_themes' ) ) - ); - - $query_params = array_merge( $context_params, $_GET, array( - // Current plugin version. - 'plugin_version' => $fs->get_plugin_version(), - 'sdk_version' => WP_FS__SDK_VERSION, - 'is_premium' => $is_premium ? 'true' : 'false', - 'can_install' => $can_user_install ? 'true' : 'false', - 'return_url' => $return_url, - ) ); - - $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); - if ( false !== $xdebug_session ) { - $query_params['XDEBUG_SESSION'] = $xdebug_session; - } - - $view_params = array( - 'id' => $VARS['id'], - 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), - ); - fs_require_once_template('secure-https-header.php', $view_params); -?> -
    -
    - -
    \ No newline at end of file diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 9cc7cab..e69de29 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -1,1038 +0,0 @@ -get_slug(); - - $is_pending_activation = $fs->is_pending_activation(); - $is_premium_only = $fs->is_only_premium(); - $has_paid_plans = $fs->has_paid_plan(); - $is_premium_code = $fs->is_premium(); - $is_freemium = $fs->is_freemium(); - - $fs->_enqueue_connect_essentials(); - - $current_user = Freemius::_get_current_wp_user(); - - $first_name = $current_user->user_firstname; - if ( empty( $first_name ) ) { - $first_name = $current_user->nickname; - } - - $site_url = get_site_url(); - $protocol_pos = strpos( $site_url, '://' ); - if ( false !== $protocol_pos ) { - $site_url = substr( $site_url, $protocol_pos + 3 ); - } - - $freemius_site_www = 'https://freemius.com'; - - $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url(); - $freemius_plugin_terms_url = $fs->get_eula_url(); - - $freemius_site_url = $fs->is_premium() ? - $freemius_site_www : - $freemius_usage_tracking_url; - - if ( $fs->is_premium() ) { - $freemius_site_url .= '?' . http_build_query( array( - 'id' => $fs->get_id(), - 'slug' => $slug, - ) ); - } - - $freemius_link = 'freemius.com'; - - $error = fs_request_get( 'error' ); - - $require_license_key = $is_premium_only || - ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) ); - - if ( $is_pending_activation ) { - $require_license_key = false; - } - - if ( $require_license_key ) { - $fs->_add_license_activation_dialog_box(); - } - - $is_optin_dialog = ( - $fs->is_theme() && - $fs->is_themes_page() && - $fs->show_opt_in_on_themes_page() - ); - - if ( $is_optin_dialog ) { - $show_close_button = false; - $previous_theme_activation_url = ''; - - if ( ! $is_premium_code ) { - $show_close_button = true; - } else if ( $is_premium_only ) { - $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); - $show_close_button = ( ! empty( $previous_theme_activation_url ) ); - } - } - - $is_network_level_activation = ( - fs_is_network_admin() && - $fs->is_network_active() && - ! $fs->is_network_delegated_connection() - ); - - $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); - - $activate_with_current_user = ( - is_object( $fs_user ) && - ! $is_pending_activation && - // If requires a license for activation, use the user associated with the license for the opt-in. - ! $require_license_key && - ! $is_network_level_activation - ); - - $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); - $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); - - $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); - - /* translators: %s: name (e.g. Hey John,) */ - $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); - - $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ? - FS_GDPR_Manager::instance()->is_required() : - false; - - if ( is_null( $is_gdpr_required ) ) { - $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously(); - } -?> - -
    - - - - do_action( 'connect/before' ); - ?> -
    -
    - - - $fs->get_id() ); - fs_require_once_template( 'plugin-icon.php', $vars ); - ?> - - -
    -
    - -

    - -

    apply_filters( 'pending_activation_message', sprintf( - /* translators: %s: name (e.g. Thanks John!) */ - fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . - fs_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message', $slug ), - $first_name, - '' . $fs->get_plugin_name() . '', - '' . $current_user->user_email . '', - fs_text_inline( 'complete the install', 'complete-the-install', $slug ) - ) ); - } else if ( $require_license_key ) { - $button_label = $is_network_upgrade_mode ? - fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) : - fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); - - $message = $fs->apply_filters( - 'connect-message_on-premium', - sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), - $first_name, - $fs->get_plugin_name() - ); - } else { - $filter = 'connect_message'; - $default_optin_message = $is_gdpr_required ? - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) : - fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug); - - if ( $fs->is_plugin_update() ) { - // If Freemius was added on a plugin update, set different - // opt-in message. - $default_optin_message = $is_gdpr_required ? - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ) : - fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update', $slug ); - - // If user customized the opt-in message on update, use - // that message. Otherwise, fallback to regular opt-in - // custom message if exist. - if ( $fs->has_filter( 'connect_message_on_update' ) ) { - $filter = 'connect_message_on_update'; - } - } - - $message = $fs->apply_filters( - $filter, - ($is_network_upgrade_mode ? - '' : - /* translators: %s: name (e.g. Hey John,) */ - $hey_x_text . '
    ' - ) . - sprintf( - esc_html( $default_optin_message ), - '' . esc_html( $fs->get_plugin_name() ) . '', - '' . $current_user->user_login . '', - '' . $site_url . '', - $freemius_link - ), - $first_name, - $fs->get_plugin_name(), - $current_user->user_login, - '' . $site_url . '', - $freemius_link, - $is_gdpr_required - ); - } - - if ( $is_network_upgrade_mode ) { - $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); - - if ($is_premium_code){ - $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); - - $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( - /* translators: %s: module type (plugin, theme, or add-on) */ - fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), - $fs->get_module_label( true ) - ) ); - - /* translators: %s: module type (plugin, theme, or add-on) */ - $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); - }else { - $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); - } - } - - echo $message; - ?>

    - -
    - - - -
    - - do_action( 'connect/after_license_input' ); - ?> - - - %s', - $fs->get_text_inline( 'Yes', 'yes' ), - $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) - ); - - $do_not_send_updates_text = sprintf( - '%s - %s', - $fs->get_text_inline( 'No', 'no' ), - sprintf( - $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), - '', - '' - ) - ); - ?> -
    - -
    - - -
    -
    - - - $fs->get_id(), - 'sites' => $sites, - 'require_license_key' => $require_license_key - ); - - echo fs_get_template( 'partials/network-activation.php', $vars ); - ?> - -
    -
    - is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> - - - apply_filters( 'show_delegation_option', true ) ) : ?> - - - -
    - - get_public_key() ) ?> - - -
    - -
    - - $value ) : ?> - - - - -
    - - - - -
    'dashicons dashicons-admin-users', - 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ), - 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ), - 'priority' => 5, - ); - } - - $permissions['site'] = array( - 'icon-class' => 'dashicons dashicons-admin-settings', - 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ), - 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), - 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ), - 'priority' => 10, - ); - - if ( ! $require_license_key ) { - $permissions['notices'] = array( - 'icon-class' => 'dashicons dashicons-testimonial', - 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), - 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), - 'priority' => 13, - ); - } - - $permissions['events'] = array( - 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), - 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ), - 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), - 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ), - 'priority' => 20, - ); - - // Add newsletter permissions if enabled. - if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { - $permissions['newsletter'] = array( - 'icon-class' => 'dashicons dashicons-email-alt', - 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), - 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), - 'priority' => 15, - ); - } - - $permissions['extensions'] = array( - 'icon-class' => 'dashicons dashicons-menu', - 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ), - 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ), - 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ), - 'priority' => 25, - 'optional' => true, - 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key ) - ); - - // Allow filtering of the permissions list. - $permissions = $fs->apply_filters( 'permission_list', $permissions ); - - // Sort by priority. - uasort( $permissions, 'fs_sort_by_priority' ); - - if ( ! empty( $permissions ) ) : ?> -
    - -

    get_module_label( true ), - sprintf('%s', fs_esc_html_inline('diagnostic data', 'send-data')), - 'freemius.com ' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '' - ) ?>

    - - - -
      $permission ) : ?> -
    • - - -
      -
      -
      - - -
      - class="fs-tooltip-trigger"> - -

      -
      -
    • - -
    -
    - - -
    -

    - - - - - - - -

    -
    - -
    - -   -   - -
    -
    - do_action( 'connect/after' ); - - if ( $is_optin_dialog ) { ?> -
    - - \ No newline at end of file diff --git a/freemius/templates/contact.php b/freemius/templates/contact.php index bba1018..e69de29 100644 --- a/freemius/templates/contact.php +++ b/freemius/templates/contact.php @@ -1,128 +0,0 @@ -get_slug(); - - $context_params = array( - 'plugin_id' => $fs->get_id(), - 'plugin_public_key' => $fs->get_public_key(), - 'plugin_version' => $fs->get_plugin_version(), - ); - - - // Get site context secure params. - if ( $fs->is_registered() ) { - $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( - $fs->get_site(), - time(), - 'contact' - ) ); - } - - $query_params = array_merge( $_GET, array_merge( $context_params, array( - 'plugin_version' => $fs->get_plugin_version(), - 'wp_login_url' => wp_login_url(), - 'site_url' => get_site_url(), -// 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", - ) ) ); - - $view_params = array( - 'id' => $VARS['id'], - 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), - ); - fs_require_once_template('secure-https-header.php', $view_params); - - $has_tabs = $fs->_add_tabs_before_content(); - - if ( $has_tabs ) { - $query_params['tabs'] = 'true'; - } -?> -
    -
    - -
    -_add_tabs_after_content(); - } - - $params = array( - 'page' => 'contact', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/debug.php b/freemius/templates/debug.php index 8903631..e69de29 100644 --- a/freemius/templates/debug.php +++ b/freemius/templates/debug.php @@ -1,759 +0,0 @@ - -

    newest->version ?>

    -
    - - - - -
    -
    -
    - -
    -

    - - - - - - - - get_option( 'ms_migration_complete', false, true ) ) : ?> - - - - - - -
    - -
    - - - -
    -
    - -
    - - -
    -
    - -
    - - - -
    -
    - -
    - - -
    -
    - -
    - - - -
    -
    - - - -
    - - 'WP_FS__REMOTE_ADDR', - 'val' => WP_FS__REMOTE_ADDR, - ), - array( - 'key' => 'WP_FS__ADDRESS_PRODUCTION', - 'val' => WP_FS__ADDRESS_PRODUCTION, - ), - array( - 'key' => 'FS_API__ADDRESS', - 'val' => FS_API__ADDRESS, - ), - array( - 'key' => 'FS_API__SANDBOX_ADDRESS', - 'val' => FS_API__SANDBOX_ADDRESS, - ), - array( - 'key' => 'WP_FS__DIR', - 'val' => WP_FS__DIR, - ), - ) -?> -
    - - - - - - - - - - > - - - - - - -
    -

    - - - - - - - - - - - plugins as $sdk_path => $data ) : ?> - version ) ?> - > - - - - - - - -
    version ?>plugin_path ?>
    - - - - - get_option( $module_type . 's' ), FS_Plugin::get_class_name() ) ?> - 0 ) : ?> -

    - - - - - - - - - - - - - - - - - - - - $data ) : ?> - file ); - } else { - $current_theme = wp_get_theme(); - $is_active = ( $current_theme->stylesheet === $data->file ); - - if ( ! $is_active && is_child_theme() ) { - $parent_theme = $current_theme->parent(); - - $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); - } - } - ?> - id ) : null ?> - has_api_connectivity() && $fs->is_on() ) { - echo ' style="background: #E6FFE6; font-weight: bold"'; - } else { - echo ' style="background: #ffd0d0; font-weight: bold"'; - } - } ?>> - - - - - has_api_connectivity() ) { - echo ' style="color: red; text-transform: uppercase;"'; - } ?>>has_api_connectivity() ? - fs_text_x_inline( 'Connected', 'as connection was successful' ) : - fs_text_x_inline( 'Blocked', 'as connection blocked' ) - ); - } ?> - is_on() ) { - echo ' style="color: red; text-transform: uppercase;"'; - } ?>>is_on() ? - $on_text : - $off_text - ); - } ?> - - - - get_network_install_blog_id(); - $network_user = $fs->get_network_user(); - } - ?> - - - - - - - -
    id ?>version ?>title ?>file ?>public_key ?>email; - } ?> - - has_trial_plan() ) : ?> -
    - - - - - -
    - - is_registered() ) : ?> - - - is_network_upgrade_mode() ) : ?> -
    - - - - - -
    - - -
    - - - - - 0 ) : ?> -

    /

    - - - - - - - - - - - - - - - - - - - $sites ) : ?> - - - - - - - - - - - - - - - - - - - -
    id ?>blog_id ?>url ) ?>user_id ?>license_id) ? $site->license_id : '' ?>plan_id ) ) { - if ( false === $all_plans ) { - $option_name = 'plans'; - if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { - $option_name = $module_type . '_' . $option_name; - } - - $all_plans = fs_get_entities( $fs_options->get_option( $option_name, array() ), FS_Plugin_Plan::get_class_name() ); - } - - foreach ( $all_plans[ $slug ] as $plan ) { - $plan_id = Freemius::_decrypt( $plan->id ); - - if ( $site->plan_id == $plan_id ) { - $plan_name = Freemius::_decrypt( $plan->name ); - break; - } - } - } - - echo $plan_name; - ?>public_key ?>is_whitelabeled ? - FS_Plugin_License::mask_secret_key_for_html( $site->secret_key ) : - esc_html( $site->secret_key ); - ?> -
    - - - - - - - - - -
    -
    - - - - $plugin_addons ) : ?> -

    - - - - - - - - - - - - - - - - - - - - - - - -
    id ?>title ?>slug ?>version ?>public_key ?>secret_key ) ?>
    - -is_whitelabeled ) { - $users_with_developer_license_by_id[ $license->user_id ] = true; - } - } - } - -?> - -

    - - - - - - - - - - - - - - $user ) : ?> - - - - - - - - - - - - -
    id ?>get_name() ?> - - email ?> - - is_verified ) ?>public_key ?>secret_key) : esc_html( $user->secret_key ) ?> - -
    - - - - -
    - -
    - - - - 0 ) : ?> -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    id ?>plugin_id ?>user_id ?>plan_id ?>is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?>activated ?>is_block_features ? 'Blocking' : 'Flexible' ?>is_whitelabeled ? 'Whitelabeled' : 'Normal' ?>is_whitelabeled ? - $license->get_html_escaped_masked_secret_key() : - esc_html( $license->secret_key ); - ?>expiration ?>
    - - - - -

    - -
    - - - - - - - -
    - - -
    - - -
    - -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    #
    {$log.log_order}.{$log.type}{$log.logger}{$log.function} - - {$log.message_short} - -
    {$log.message}
    -
    {$log.file}:{$log.line}{$log.created}
    -
    - - diff --git a/freemius/templates/debug/api-calls.php b/freemius/templates/debug/api-calls.php index ea4e823..e69de29 100644 --- a/freemius/templates/debug/api-calls.php +++ b/freemius/templates/debug/api-calls.php @@ -1,155 +0,0 @@ - 0, - 'POST' => 0, - 'PUT' => 0, - 'DELETE' => 0 - ); - - $show_body = false; - foreach ( $logger as $log ) { - $counters[ $log['method'] ] ++; - - if ( ! is_null( $log['body'] ) ) { - $show_body = true; - } - } - - $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); - - /** - * This template is used for debugging, therefore, when possible - * we'd like to prettify the output of a JSON encoded variable. - * This will only be executed when $pretty_print is `true`, and - * the var is `true` only for PHP 5.3 and higher. Due to the - * limitations of the current Theme Check, it throws an error - * that using the "options" parameter (the 2nd param) is not - * supported in PHP 5.2 and lower. Thus, we added this alias - * variable to work around that false-positive. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.2.7 - */ - $encode = 'json_encode'; - - $root_path_len = strlen( ABSPATH ); - - $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); -?> -

    - -

    Total Time:

    - -

    Total Requests:

    - $count ) : ?> -

    :

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #
    . - %s', - $log['path'] - ); - ?> - - - - - - - - - - - - - - %s', - substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) - ); - if ( $pretty_print ) { - $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); - } - ?> -
    - -
    - %s', - substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) - ); - } - - if ( $is_not_empty_result && $pretty_print ) { - $decoded = json_decode( $result ); - if ( ! is_null( $decoded ) ) { - $result = $encode( $decoded, JSON_PRETTY_PRINT ); - } - } else { - $result = is_string( $result ) ? $result : json_encode( $result ); - } - ?> - style="display: none"> -
    \ No newline at end of file diff --git a/freemius/templates/debug/index.php b/freemius/templates/debug/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/debug/index.php +++ b/freemius/templates/debug/index.php @@ -1,3 +0,0 @@ - -

    - - - - - - - - - - - - - - - - - - > - - - - - - - - - - -
    #
    .get_id() ?> - %s', - esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) - ); - ?> -
    - -
    -
    get_file() ) . ':' . $log['line']; - } - ?>
    \ No newline at end of file diff --git a/freemius/templates/debug/plugins-themes-sync.php b/freemius/templates/debug/plugins-themes-sync.php index 8508cd1..e69de29 100644 --- a/freemius/templates/debug/plugins-themes-sync.php +++ b/freemius/templates/debug/plugins-themes-sync.php @@ -1,76 +0,0 @@ -get_option( 'all_plugins' ); - $all_themes = $fs_options->get_option( 'all_themes' ); - - /* translators: %s: time period (e.g. In "2 hours") */ - $in_x_text = fs_text_inline( 'In %s', 'in-x' ); - /* translators: %s: time period (e.g. "2 hours" ago) */ - $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); - $sec_text = fs_text_x_inline( 'sec', 'seconds' ); -?> -

    - - - - - - - - - - - - - - - - - - - - - - - - -
    plugins ) ?>timestamp ) && is_numeric( $all_plugins->timestamp ) ) { - $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); - $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? - $diff . ' ' . $sec_text : - human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); - - echo esc_html( sprintf( - ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? - $in_x_text : - $x_ago_text ), - $human_diff - ) ); - } - ?>
    themes ) ?>timestamp ) && is_numeric( $all_themes->timestamp ) ) { - $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); - $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? - $diff . ' ' . $sec_text : - human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); - - echo esc_html( sprintf( - ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? - $in_x_text : - $x_ago_text ), - $human_diff - ) ); - } - ?>
    diff --git a/freemius/templates/debug/scheduled-crons.php b/freemius/templates/debug/scheduled-crons.php index 47a715e..e69de29 100644 --- a/freemius/templates/debug/scheduled-crons.php +++ b/freemius/templates/debug/scheduled-crons.php @@ -1,136 +0,0 @@ -get_option( $module_type . 's' ), FS_Plugin::get_class_name() ); - if ( is_array( $modules ) && count( $modules ) > 0 ) { - foreach ( $modules as $slug => $data ) { - if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { - $current_theme = wp_get_theme(); - $is_active = ( $current_theme->stylesheet === $data->file ); - } else { - $is_active = is_plugin_active( $data->file ); - } - - /** - * @author Vova Feldman - * - * @since 1.2.1 Don't load data from inactive modules. - */ - if ( $is_active ) { - $fs = freemius( $data->id ); - - $next_execution = $fs->next_sync_cron(); - $last_execution = $fs->last_sync_cron(); - - if ( false !== $next_execution ) { - $scheduled_crons[ $slug ][] = array( - 'name' => $fs->get_plugin_name(), - 'slug' => $slug, - 'module_type' => $fs->get_module_type(), - 'type' => 'sync_cron', - 'last' => $last_execution, - 'next' => $next_execution, - ); - } - - $next_install_execution = $fs->next_install_sync(); - $last_install_execution = $fs->last_install_sync(); - - if (false !== $next_install_execution || - false !== $last_install_execution - ) { - $scheduled_crons[ $slug ][] = array( - 'name' => $fs->get_plugin_name(), - 'slug' => $slug, - 'module_type' => $fs->get_module_type(), - 'type' => 'install_sync', - 'last' => $last_install_execution, - 'next' => $next_install_execution, - ); - } - } - } - } - } - - $sec_text = fs_text_x_inline( 'sec', 'seconds' ); -?> -

    - - - - - - - - - - - - - - $crons ) : ?> - - - - - - - - - - - - -
    diff --git a/freemius/templates/email.php b/freemius/templates/email.php index 598c783..e69de29 100644 --- a/freemius/templates/email.php +++ b/freemius/templates/email.php @@ -1,49 +0,0 @@ - - - $section ) { - ?> - - - - - $row ) { - $col_count = count( $row ); - ?> - - - - - - - - - - - -
    :
    \ No newline at end of file diff --git a/freemius/templates/firewall-issues-js.php b/freemius/templates/firewall-issues-js.php index 2abfbc0..e69de29 100644 --- a/freemius/templates/firewall-issues-js.php +++ b/freemius/templates/firewall-issues-js.php @@ -1,59 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/forms/affiliation.php b/freemius/templates/forms/affiliation.php index fe6d694..e69de29 100644 --- a/freemius/templates/forms/affiliation.php +++ b/freemius/templates/forms/affiliation.php @@ -1,509 +0,0 @@ -get_slug(); - - $user = $fs->get_user(); - $affiliate = $fs->get_affiliate(); - $affiliate_terms = $fs->get_affiliate_terms(); - - $plugin_title = $fs->get_plugin_title(); - $module_type = $fs->is_plugin() ? - WP_FS__MODULE_TYPE_PLUGIN : - WP_FS__MODULE_TYPE_THEME; - - $commission = $affiliate_terms->get_formatted_commission(); - - $readonly = false; - $is_affiliate = is_object( $affiliate ); - $is_pending_affiliate = false; - $email_address = ( is_object( $user ) ? - $user->email : - '' ); - $full_name = ( is_object( $user ) ? - $user->get_name() : - '' ); - $paypal_email_address = ''; - $domain = ''; - $extra_domains = array(); - $promotion_method_social_media = false; - $promotion_method_mobile_apps = false; - $statistics_information = false; - $promotion_method_description = false; - $members_dashboard_login_url = 'https://members.freemius.com/login/'; - - $affiliate_application_data = $fs->get_affiliate_application_data(); - - if ( $is_affiliate && $affiliate->is_pending() ) { - $readonly = 'readonly'; - $is_pending_affiliate = true; - - $paypal_email_address = $affiliate->paypal_email; - $domain = $affiliate->domain; - $statistics_information = $affiliate_application_data['stats_description']; - $promotion_method_description = $affiliate_application_data['promotion_method_description']; - - if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { - $extra_domains = $affiliate_application_data['additional_domains']; - } - - if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { - $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); - $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); - $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); - } - } else { - $current_user = Freemius::_get_current_wp_user(); - $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); - $email_address = $current_user->user_email; - $domain = fs_strip_url_protocol( get_site_url() ); - } - - $affiliate_tracking = 30; - - if ( is_object( $affiliate_terms ) ) { - $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? - ( $affiliate_terms->cookie_days . '-day' ) : - fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); - } - - $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); - - $module_id = $fs->get_id(); - $affiliate_program_terms_url = "https://freemius.com/plugin/{$module_id}/{$slug}/legal/affiliate-program/"; -?> -
    -
    -
    -
    -
    -
    - - - - is_active() ) : ?> -
    -

    %s', - $members_dashboard_login_url, - $members_dashboard_login_url - ) - ); - ?>

    -
    - - is_suspended() ) { - $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); - $message_container_class = 'notice notice-warning'; - } else if ( $affiliate->is_rejected() ) { - $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); - $message_container_class = 'error'; - } else if ( $affiliate->is_blocked() ) { - $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); - $message_container_class = 'error'; - } - ?> -
    -

    -
    - - -
    -
    - -
    -

    -

    -
    - -

    -
      -
    • - has_renewals_commission() ) : ?> -
    • - - is_session_cookie() ) ) : ?> -
    • - - has_lifetime_commission() ) : ?> -
    • - -
    • -
    • -
    • -
    -
    > -

    - -
    - - > -
    -
    - - > -
    -
    - - > -
    -
    - - > -

    - - + ... - -
    -
    > - -

    - - -
    - > -
    - - -
    -
    - -
    - /> - -
    -
    - /> - -
    -
    -
    - - - -

    - -
    -
    - - - -

    - -
    - -
    - - -
    - - -
    - - - - - -
    -
    -
    -
    - - -
    - 'affiliation', - 'module_id' => $module_id, - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); -?> \ No newline at end of file diff --git a/freemius/templates/forms/data-debug-mode.php b/freemius/templates/forms/data-debug-mode.php index 44cb442..e69de29 100644 --- a/freemius/templates/forms/data-debug-mode.php +++ b/freemius/templates/forms/data-debug-mode.php @@ -1,213 +0,0 @@ -get_slug(); - $unique_affix = $fs->get_unique_affix(); - $last_license_user_id = $fs->get_last_license_user_id(); - $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id ); - - $message_above_input_field = ( ! $has_last_license_user_id ) ? - fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) : - sprintf( - fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ), - $last_license_user_id - ); - - $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' ); - $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug ); - $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug ); - $license_or_user_key_text = ( ! $has_last_license_user_id ) ? - fs_text_inline( 'License key', 'license-key' , $slug ) : - fs_text_inline( 'User key', 'user-key' , $slug ); - $input_html = ""; - - $modal_content_html = <<< HTML -

    -

    {$message_above_input_field}

    - {$input_html} -HTML; - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - \ No newline at end of file diff --git a/freemius/templates/forms/deactivation/contact.php b/freemius/templates/forms/deactivation/contact.php index 24d67e7..e69de29 100644 --- a/freemius/templates/forms/deactivation/contact.php +++ b/freemius/templates/forms/deactivation/contact.php @@ -1,23 +0,0 @@ -get_slug(); - - echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) - . sprintf(" %s", - $fs->contact_url( 'technical_support' ), - fs_text_inline( 'Contact Support', 'contact-support', $slug ) - ); diff --git a/freemius/templates/forms/deactivation/form.php b/freemius/templates/forms/deactivation/form.php index 0bdcae0..e69de29 100644 --- a/freemius/templates/forms/deactivation/form.php +++ b/freemius/templates/forms/deactivation/form.php @@ -1,543 +0,0 @@ -get_slug(); - - $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; - $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; - $confirmation_message = $VARS['uninstall_confirmation_message']; - - $is_anonymous = ( ! $fs->is_registered() ); - $anonymous_feedback_checkbox_html = ''; - - $reasons_list_items_html = ''; - - if ( $show_deactivation_feedback_form ) { - $reasons = $VARS['reasons']; - - foreach ( $reasons as $reason ) { - $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); - - if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { - $list_item_classes .= ' has-internal-message'; - $reason_internal_message = $reason['internal_message']; - } else { - $reason_internal_message = ''; - } - - $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); - $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); - - $reason_list_item_html = <<< HTML -
  • - -
    {$reason_internal_message}
    -
  • -HTML; - - $reasons_list_items_html .= $reason_list_item_html; - } - - if ( $is_anonymous ) { - $anonymous_feedback_checkbox_html = sprintf( - '', - fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) - ); - } - } - - // Aliases. - $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); - $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); - $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); - - if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { - fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); - } -?> - diff --git a/freemius/templates/forms/deactivation/index.php b/freemius/templates/forms/deactivation/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/forms/deactivation/index.php +++ b/freemius/templates/forms/deactivation/index.php @@ -1,3 +0,0 @@ -get_slug(); - - $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); - $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); - $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); - - echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) - . " {$use_plugin_anonymously_text}"; \ No newline at end of file diff --git a/freemius/templates/forms/index.php b/freemius/templates/forms/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/forms/index.php +++ b/freemius/templates/forms/index.php @@ -1,3 +0,0 @@ -get_slug(); - $unique_affix = $fs->get_unique_affix(); - - $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); - $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); - $message_below_input_field = ''; - - $header_title = $fs->is_free_plan() ? - fs_text_inline( 'Activate License', 'activate-license', $slug ) : - fs_text_inline( 'Update License', 'update-license', $slug ); - - if ( $fs->is_registered() ) { - $activate_button_text = $header_title; - } else { - $freemius_site_url = $fs->has_paid_plan() ? - 'https://freemius.com/' : - // Insights platform information. - $fs->get_usage_tracking_terms_url(); - - $freemius_link = 'freemius.com'; - - $message_below_input_field = sprintf( - fs_text_inline( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), - $fs->get_module_label( true ), - $freemius_link - ); - - $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); - } - - $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); - - $is_network_activation = ( - $fs->is_network_active() && - fs_is_network_admin() && - ! $fs->is_delegated_connection() - ); - $network_activation_html = ''; - - $sites_details = array(); - if ( $is_network_activation ) { - $all_sites = Freemius::get_sites(); - - foreach ( $all_sites as $site ) { - $site_details = $fs->get_site_info( $site ); - - $blog_id = Freemius::get_site_blog_id( $site ); - $install = $fs->get_install_by_blog_id($blog_id); - - if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { - $site_details['license_id'] = $install->license_id; - } - - $sites_details[] = $site_details; - } - - if ( $is_network_activation ) { - $vars = array( - 'id' => $fs->get_id(), - 'sites' => $sites_details, - 'require_license_key' => true - ); - - $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); - } - } - - $premium_licenses = $fs->get_available_premium_licenses(); - $available_licenses = array(); - foreach ( $premium_licenses as $premium_license ) { - $activations_left = $premium_license->left(); - if ( ! ( $activations_left > 0 ) ) { - continue; - } - - $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; - } - - $total_available_licenses = count( $available_licenses ); - if ( $total_available_licenses > 0 ) { - $license_input_html = <<< HTML -
    - - - - - - - - - - - -
    -HTML; - - if ( $total_available_licenses > 1 ) { - // Sort the licenses by number of activations left in descending order. - krsort( $available_licenses ); - - $license_input_html .= ''; - } else { - $available_licenses = array_values( $available_licenses ); - - /** - * @var FS_Plugin_License $available_license - */ - $available_license = $available_licenses[0]; - $value = sprintf( - "%s-Site %s License - %s", - ( 1 == $available_license->quota ? - 'Single' : - ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota ) - ), - $fs->_get_plan_by_id( $available_license->plan_id )->title, - $available_license->get_html_escaped_masked_secret_key() - ); - - $license_input_html .= <<< HTML - -HTML; - } - - $license_input_html .= <<< HTML -
    - -
    - -
    -
    -
    -HTML; - } else { - $license_input_html = ""; - } - - $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug ); - $ownership_change_option_html = ""; - - /** - * IMPORTANT: - * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE - * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) - * FOR MIGRATED MODULES. - */ - $modal_content_html = <<< HTML -

    -

    {$message_above_input_field}

    - {$license_input_html} - {$cant_find_license_key_text} - {$network_activation_html} -

    {$message_below_input_field}

    - {$ownership_change_option_html} -HTML; - - /** - * Handle the ownership change option if not an add-on or if no license yet is activated for the - * parent product in case of an add-on. - * - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - */ - $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() ); - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - \ No newline at end of file diff --git a/freemius/templates/forms/optout.php b/freemius/templates/forms/optout.php index 4867a8a..e69de29 100644 --- a/freemius/templates/forms/optout.php +++ b/freemius/templates/forms/optout.php @@ -1,336 +0,0 @@ -get_slug(); - - $action = $fs->is_tracking_allowed() ? - 'stop_tracking' : - 'allow_tracking'; - - $reconnect_url = $fs->get_activation_url( array( - 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), - 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), - ) ); - - $plugin_title = "{$fs->get_plugin()->title}"; - $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); - $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug ); - - if ( $fs->is_premium() ) { - $opt_in_message_appreciation = fs_text_inline( 'Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard.', 'premium-opt-in-message-appreciation', $slug ); - - $opt_out_message_subtitle = sprintf( fs_text_inline( 'Warning: Opting out will block automatic updates', 'premium-opt-out-message-appreciation', $slug ), $fs->get_module_type() ); - $opt_out_message_usage_tracking = sprintf( fs_text_inline( 'Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won\'t receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you\'ll need to check for %1$s updates and install them manually.', 'premium-opt-out-message-usage-tracking', $slug ), $fs->get_module_type(), $plugin_title ); - - $primary_cta_label = fs_text_inline( 'I\'d like to keep automatic updates', 'premium-opt-out-cancel', $slug ); - } else { - $opt_in_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-in-message-appreciation', $slug ), $fs->get_module_type() ); - - $opt_out_message_subtitle = $opt_in_message_appreciation; - $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title ); - $primary_cta_label = fs_text_inline( 'On second thought - I want to continue helping', 'opt-out-cancel', $slug ); - } - - $opt_out_message_clicking_opt_out = sprintf( - fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ), - $plugin_title, - sprintf( - '%s', - 'https://freemius.com', - 'freemius.com' - ) - ); - - $admin_notice_params = array( - 'id' => '', - 'slug' => $fs->get_id(), - 'type' => 'success', - 'sticky' => false, - 'plugin' => $fs->get_plugin()->title, - 'message' => $opt_in_message_appreciation - ); - - $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params ); - - $modal_content_html = " - is_premium() ? ' style="color: red"' : '' ) . ">{$opt_out_message_subtitle} -

    -

    {$opt_out_message_usage_tracking}

    -

    {$opt_out_message_clicking_opt_out}

    - "; - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); - fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); -?> - diff --git a/freemius/templates/forms/premium-versions-upgrade-handler.php b/freemius/templates/forms/premium-versions-upgrade-handler.php index f30639b..e69de29 100644 --- a/freemius/templates/forms/premium-versions-upgrade-handler.php +++ b/freemius/templates/forms/premium-versions-upgrade-handler.php @@ -1,205 +0,0 @@ -get_slug(); - - $plugin_data = $fs->get_plugin_data(); - $plugin_name = $plugin_data['Name']; - $plugin_basename = $fs->get_plugin_basename(); - - $license = $fs->_get_license(); - - if ( ! is_object( $license ) ) { - $purchase_url = $fs->pricing_url(); - } else { - $subscription = $fs->_get_subscription( $license->id ); - - $purchase_url = $fs->checkout_url( - is_object( $subscription ) ? - ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : - WP_FS__PERIOD_LIFETIME, - false, - array( 'licenses' => $license->quota ) - ); - } - - $message = sprintf( - fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . - fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), - '', - sprintf( - '%s', - is_object( $license ) ? - fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : - fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) - ), - '' - ); - - $modal_content_html = "

    {$message}

    "; - - $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); - - $renew_license_button_text = is_object( $license ) ? - fs_text_inline( 'Renew license', 'renew-license', $slug ) : - fs_text_inline( 'Buy license', 'buy-license', $slug ); - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - \ No newline at end of file diff --git a/freemius/templates/forms/premium-versions-upgrade-metadata.php b/freemius/templates/forms/premium-versions-upgrade-metadata.php index 5f9fddd..e69de29 100644 --- a/freemius/templates/forms/premium-versions-upgrade-metadata.php +++ b/freemius/templates/forms/premium-versions-upgrade-metadata.php @@ -1,47 +0,0 @@ -_get_license(); - - if ( ! is_object( $license ) ) { - $purchase_url = $fs->pricing_url(); - } else { - $subscription = $fs->_get_subscription( $license->id ); - - $purchase_url = $fs->checkout_url( - is_object( $subscription ) ? - ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : - WP_FS__PERIOD_LIFETIME, - false, - array( 'licenses' => $license->quota ) - ); - } - - $plugin_data = $fs->get_plugin_data(); -?> - \ No newline at end of file diff --git a/freemius/templates/forms/resend-key.php b/freemius/templates/forms/resend-key.php index f8cafb9..e69de29 100644 --- a/freemius/templates/forms/resend-key.php +++ b/freemius/templates/forms/resend-key.php @@ -1,247 +0,0 @@ -get_slug(); - - $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); - $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); - $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); - $other_text = fs_text_inline( 'Other', 'other', $slug ); - - $is_freemium = $fs->is_freemium(); - - $send_button_text_html = esc_html($send_button_text); - - $button_html = <<< HTML - -HTML; - - if ( $is_freemium ) { - $current_user = Freemius::_get_current_wp_user(); - $email = $current_user->user_email; - $esc_email = esc_attr( $email ); - $form_html = <<< HTML - -{$button_html} -HTML; - } else { - $email = ''; - $form_html = <<< HTML -{$button_html} - -HTML; - } - - $message_above_input_field = fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); - $modal_content_html = <<< HTML -

    -

    {$message_above_input_field}

    -
    - {$form_html} -
    -HTML; - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - diff --git a/freemius/templates/forms/subscription-cancellation.php b/freemius/templates/forms/subscription-cancellation.php index 2a1d591..e69de29 100644 --- a/freemius/templates/forms/subscription-cancellation.php +++ b/freemius/templates/forms/subscription-cancellation.php @@ -1,277 +0,0 @@ -get_slug(); - -/** - * @var FS_Plugin_License $license - */ -$license = $VARS['license']; - -$has_trial = $VARS['has_trial']; - -$subscription_cancellation_context = $has_trial ? - fs_text_inline( 'trial', 'trial', $slug ) : - fs_text_inline( 'subscription', 'subscription', $slug ); - -$plan = $fs->get_plan(); -$module_label = $fs->get_module_label( true ); - -if ( $VARS['is_license_deactivation'] ) { - $subscription_cancellation_text = ''; -} else { - $subscription_cancellation_text = sprintf( - fs_text_inline( - "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", - 'deactivation-or-uninstall-message', - $slug - ), - $module_label - ) . ' '; -} - - $subscription_cancellation_text .= sprintf( - fs_text_inline( - 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', - 'cancel-subscription-message', - $slug - ), - ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), - $subscription_cancellation_context -); - -$cancel_subscription_action_label = sprintf( - fs_esc_html_inline( - "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", - 'cancel-x', - $slug - ), - esc_html( $subscription_cancellation_context ), - sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), - esc_html( $module_label ) -); - -$keep_subscription_active_action_label = esc_html( sprintf( - fs_text_inline( - "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", - 'dont-cancel-x', - $slug - ), - $subscription_cancellation_context -) ); - -$subscription_cancellation_text = esc_html( $subscription_cancellation_text ); - -$subscription_cancellation_html = <<< HTML -

    {$subscription_cancellation_text}

    -
      -
    • - -
    • -
    • - -
    • -
    -HTML; - -$downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); -$cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); -/* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ -$downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); -$prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); -$after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); -$after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); -$after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); - -$subscription_cancellation_confirmation_message = $has_trial ? - fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : - sprintf( - '%s %s %s %s', - sprintf( - $downgrade_x_confirm_text, - ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), - $plan->title, - human_time_diff( time(), strtotime( $license->expiration ) ) - ), - ( - $license->is_block_features ? - ( - $fs->is_only_premium() ? - sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : - sprintf( $after_downgrade_blocking_text, $plan->title ) - ) : - sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) - ), - $prices_increase_text, - fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) - ); - -fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - \ No newline at end of file diff --git a/freemius/templates/forms/trial-start.php b/freemius/templates/forms/trial-start.php index b66e727..e69de29 100644 --- a/freemius/templates/forms/trial-start.php +++ b/freemius/templates/forms/trial-start.php @@ -1,181 +0,0 @@ -get_slug(); - - $message_header = sprintf( - /* translators: %1$s: Number of trial days; %2$s: Plan name; */ - fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), - '', - '' - ); - $message_content = sprintf( - /* translators: %s: Link to freemius.com */ - fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), - $fs->get_module_type(), - sprintf( - '%s', - 'https://freemius.com', - 'freemius.com' - ) - ); - - $modal_content_html = <<< HTML -

    -

    {$message_header}

    -

    {$message_content}

    -HTML; - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - diff --git a/freemius/templates/forms/user-change.php b/freemius/templates/forms/user-change.php index 3571b83..e69de29 100644 --- a/freemius/templates/forms/user-change.php +++ b/freemius/templates/forms/user-change.php @@ -1,296 +0,0 @@ -get_slug(); - - /** - * @var object[] $license_owners - */ - $license_owners = $VARS['license_owners']; - - $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug ); - $header_title = fs_text_inline( 'Change User', 'change-user', $slug ); - $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug ); - $other_text = fs_text_inline( 'Other', 'other', $slug ); - $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug ); - - $user_change_options_html = <<< HTML -
    - - -HTML; - - foreach ( $license_owners as $license_owner ) { - $user_change_options_html .= <<< HTML - - - - -HTML; - } - - $user_change_options_html .= <<< HTML - - - - - -
    -
    - -
    - -
    -
    -
    -
    -HTML; - - $modal_content_html = <<< HTML -

    -

    {$change_user_message}

    - {$user_change_options_html} -HTML; - - fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); -?> - diff --git a/freemius/templates/gdpr-optin-js.php b/freemius/templates/gdpr-optin-js.php index 4fdc5e3..e69de29 100644 --- a/freemius/templates/gdpr-optin-js.php +++ b/freemius/templates/gdpr-optin-js.php @@ -1,66 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/index.php b/freemius/templates/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/index.php +++ b/freemius/templates/index.php @@ -1,3 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/js/open-license-activation.php b/freemius/templates/js/open-license-activation.php index a88e6f9..e69de29 100644 --- a/freemius/templates/js/open-license-activation.php +++ b/freemius/templates/js/open-license-activation.php @@ -1,37 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/js/style-premium-theme.php b/freemius/templates/js/style-premium-theme.php index 942da64..e69de29 100644 --- a/freemius/templates/js/style-premium-theme.php +++ b/freemius/templates/js/style-premium-theme.php @@ -1,53 +0,0 @@ -get_slug(); - -?> - \ No newline at end of file diff --git a/freemius/templates/partials/index.php b/freemius/templates/partials/index.php index cd6990e..e69de29 100644 --- a/freemius/templates/partials/index.php +++ b/freemius/templates/partials/index.php @@ -1,2 +0,0 @@ -get_slug(); - - $sites = $VARS['sites']; - $require_license_key = $VARS['require_license_key']; - - $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); - $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); -?> -|' ?> - \ No newline at end of file diff --git a/freemius/templates/plugin-icon.php b/freemius/templates/plugin-icon.php index ab0fb54..e69de29 100644 --- a/freemius/templates/plugin-icon.php +++ b/freemius/templates/plugin-icon.php @@ -1,20 +0,0 @@ - -
    - -
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/description.php b/freemius/templates/plugin-info/description.php index 26bc67b..e69de29 100644 --- a/freemius/templates/plugin-info/description.php +++ b/freemius/templates/plugin-info/description.php @@ -1,78 +0,0 @@ -info->selling_point_0 ) || - ! empty( $plugin->info->selling_point_1 ) || - ! empty( $plugin->info->selling_point_2 ) - ) : ?> -
    -
      - - info->{'selling_point_' . $i} ) ) : ?> -
    • - -

      info->{'selling_point_' . $i} ) ?>

    • - - -
    -
    - -
    - info->description, array( - 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), - 'b' => array(), - 'i' => array(), - 'p' => array(), - 'blockquote' => array(), - 'h2' => array(), - 'h3' => array(), - 'ul' => array(), - 'ol' => array(), - 'li' => array() - ) ); - ?> -
    -info->screenshots ) ) : ?> - info->screenshots ?> -
    -

    slug ) ?>

    -
      - $url ) : ?> - -
    • - - -
    • - -
    -
    - \ No newline at end of file diff --git a/freemius/templates/plugin-info/features.php b/freemius/templates/plugin-info/features.php index b3d0fc8..e69de29 100644 --- a/freemius/templates/plugin-info/features.php +++ b/freemius/templates/plugin-info/features.php @@ -1,114 +0,0 @@ -features) && is_array($plan->features)) { - foreach ( $plan->features as $feature ) { - if ( ! isset( $features_plan_map[ $feature->id ] ) ) { - $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); - } - - $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; - } - } - - // Add support as a feature. - if ( ! empty( $plan->support_email ) || - ! empty( $plan->support_skype ) || - ! empty( $plan->support_phone ) || - true === $plan->is_success_manager - ) { - if ( ! isset( $features_plan_map['support'] ) ) { - $support_feature = new stdClass(); - $support_feature->id = 'support'; - $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); - $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); - } else { - $support_feature = $features_plan_map['support']; - } - - $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; - } - } - - // Add updates as a feature for all plans. - $updates_feature = new stdClass(); - $updates_feature->id = 'updates'; - $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); - $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); - foreach ( $plans as $plan ) { - $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; - } -?> -
    - - - - - - - - - - - $data ) : ?> - - - - - - - - -
    - title ?> - pricing ) ) { - fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); - } else { - foreach ( $plan->pricing as $pricing ) { - /** - * @var FS_Pricing $pricing - */ - if ( 1 == $pricing->licenses ) { - if ( $pricing->has_annual() ) { - echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); - } else if ( $pricing->has_monthly() ) { - echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); - } else { - echo "\${$pricing->lifetime_price}"; - } - } - } - } - ?> -
    title ) ) ?> - id ] ) ) : ?> - id ]->value ) ) : ?> - id ]->value ) ?> - - - - -
    -
    \ No newline at end of file diff --git a/freemius/templates/plugin-info/index.php b/freemius/templates/plugin-info/index.php index 0316c6a..e69de29 100644 --- a/freemius/templates/plugin-info/index.php +++ b/freemius/templates/plugin-info/index.php @@ -1,3 +0,0 @@ - -
      - $url ) : ?> - -
    1. - -
    2. - -
    diff --git a/freemius/templates/powered-by.php b/freemius/templates/powered-by.php index bb6e081..e69de29 100644 --- a/freemius/templates/powered-by.php +++ b/freemius/templates/powered-by.php @@ -1,61 +0,0 @@ - -is_whitelabeled() ) : ?> -
    - - \ No newline at end of file diff --git a/freemius/templates/pricing.php b/freemius/templates/pricing.php index 469f300..e69de29 100644 --- a/freemius/templates/pricing.php +++ b/freemius/templates/pricing.php @@ -1,209 +0,0 @@ -get_slug(); - $timestamp = time(); - - $context_params = array( - 'plugin_id' => $fs->get_id(), - 'plugin_public_key' => $fs->get_public_key(), - 'plugin_version' => $fs->get_plugin_version(), - ); - - $bundle_id = $fs->get_bundle_id(); - if ( ! is_null( $bundle_id ) ) { - $context_params['bundle_id'] = $bundle_id; - } - - // Get site context secure params. - if ( $fs->is_registered() ) { - $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( - $fs->get_site(), - $timestamp, - 'upgrade' - ) ); - } else { - $context_params['home_url'] = home_url(); - } - - if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) - { - $context_params['sandbox'] = FS_Security::instance()->get_secure_token( - $fs->get_plugin(), - $timestamp, - 'checkout' - ); - } - - $query_params = array_merge( $context_params, $_GET, array( - 'next' => $fs->_get_sync_license_url( false, false ), - 'plugin_version' => $fs->get_plugin_version(), - // Billing cycle. - 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), - 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', - 'currency' => $fs->apply_filters( 'default_currency', 'usd' ), - ) ); - - $use_external_pricing = $fs->should_use_external_pricing(); - - if ( ! $use_external_pricing ) { - $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); - wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); - } else { - if ( ! $fs->is_registered() ) { - $template_data = array( - 'id' => $fs->get_id(), - ); - fs_require_template( 'forms/trial-start.php', $template_data); - } - - $view_params = array( - 'id' => $VARS['id'], - 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), - ); - fs_require_once_template('secure-https-header.php', $view_params); - } - - $has_tabs = $fs->_add_tabs_before_content(); - - if ( $has_tabs ) { - $query_params['tabs'] = 'true'; - } -?> -
    - -
    - $fs->contact_url(), - 'is_network_admin' => fs_is_network_admin(), - 'is_production' => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ), - 'menu_slug' => $fs->get_menu_slug(), - 'mode' => 'dashboard', - 'fs_wp_endpoint_url' => WP_FS__ADDRESS, - 'request_handler_url' => admin_url( - 'admin-ajax.php?' . http_build_query( array( - 'module_id' => $fs->get_id(), - 'action' => $fs->get_ajax_action( 'pricing_ajax_action' ), - 'security' => $fs->get_ajax_security( 'pricing_ajax_action' ) - ) ) - ), - 'selector' => '#fs_pricing_wrapper', - 'unique_affix' => $fs->get_unique_affix(), - ), $query_params ); - - wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); - ?> - -
    -
    - - - - - - -
    - - - -
    -_add_tabs_after_content(); - } - - $params = array( - 'page' => 'pricing', - 'module_id' => $fs->get_id(), - 'module_type' => $fs->get_module_type(), - 'module_slug' => $slug, - 'module_version' => $fs->get_plugin_version(), - ); - fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/secure-https-header.php b/freemius/templates/secure-https-header.php index 3d0a81e..e69de29 100644 --- a/freemius/templates/secure-https-header.php +++ b/freemius/templates/secure-https-header.php @@ -1,39 +0,0 @@ - -
    - - get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), - $VARS['page'] - ) ) . - ' - ' . - sprintf( - '%s', - 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, - 'Freemius Inc. [US]' - ); - } - ?> -
    \ No newline at end of file diff --git a/freemius/templates/sticky-admin-notice-js.php b/freemius/templates/sticky-admin-notice-js.php index 028a966..e69de29 100644 --- a/freemius/templates/sticky-admin-notice-js.php +++ b/freemius/templates/sticky-admin-notice-js.php @@ -1,39 +0,0 @@ - - \ No newline at end of file diff --git a/freemius/templates/tabs-capture-js.php b/freemius/templates/tabs-capture-js.php index 236be3b..e69de29 100644 --- a/freemius/templates/tabs-capture-js.php +++ b/freemius/templates/tabs-capture-js.php @@ -1,63 +0,0 @@ -get_slug(); -?> - \ No newline at end of file diff --git a/freemius/templates/tabs.php b/freemius/templates/tabs.php index 2a20b70..e69de29 100644 --- a/freemius/templates/tabs.php +++ b/freemius/templates/tabs.php @@ -1,190 +0,0 @@ -get_slug(); - - $menu_items = $fs->get_menu_items(); - - $show_settings_with_tabs = $fs->show_settings_with_tabs(); - - $tabs = array(); - foreach ( $menu_items as $priority => $items ) { - foreach ( $items as $item ) { - if ( ! $item['show_submenu'] ) { - $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? - 'support' : - $item['menu_slug']; - - if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { - continue; - } - - if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { - continue; - } - } - - $url = $fs->_get_admin_page_url( $item['menu_slug'] ); - $title = $item['menu_title']; - - $tab = array( - 'label' => $title, - 'href' => $url, - 'slug' => $item['menu_slug'], - ); - - if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { - $tab['href'] .= '&trial=true'; - } - - $tabs[] = $tab; - } - } -?> - \ No newline at end of file diff --git a/radio-station.php b/radio-station.php index 385857c..756a9d3 100644 --- a/radio-station.php +++ b/radio-station.php @@ -10,7 +10,7 @@ Plugin URI: https://radiostation.pro/radio-station Description: Adds Show pages, DJ role, playlist and on-air programming functionality to your site. Author: Tony Zeoli, Tony Hayes -Version: 2.4.0.5.3 +Version: 2.4.0.5.4 Requires at least: 3.3.1 Text Domain: radio-station Domain Path: /languages diff --git a/readme.txt b/readme.txt index 2912627..3f30c95 100644 --- a/readme.txt +++ b/readme.txt @@ -219,6 +219,7 @@ You can now visit your site to make sure nothing is broken. If you experience is == Changelog == = 2.4.0.6 = +* Update: Freemius SDK (2.4.3) * Updated: documentation links to new demo site address * Fixed: remove duplicate Related Show box in Post Quick Edit * Fixed: multiple attributes for automatic pages shortcodes From 6238db3537d59df9801e01e97d46f5f14a767c8b Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Thu, 3 Mar 2022 18:15:20 +1000 Subject: [PATCH 251/377] dev updates --- CHANGELOG.md | 1 + freemius/LICENSE.txt | 674 + freemius/README.md | 282 + freemius/assets/css/admin/account.css | 1 + freemius/assets/css/admin/add-ons.css | 2 + freemius/assets/css/admin/affiliation.css | 1 + freemius/assets/css/admin/checkout.css | 1 + freemius/assets/css/admin/common.css | 2 + freemius/assets/css/admin/connect.css | 1 + freemius/assets/css/admin/debug.css | 1 + freemius/assets/css/admin/dialog-boxes.css | 2 + .../assets/css/admin/gdpr-optin-notice.css | 1 + freemius/assets/css/admin/index.php | 3 + freemius/assets/css/admin/plugins.css | 1 + freemius/assets/css/customizer.css | 1 + freemius/assets/css/index.php | 3 + freemius/assets/img/index.php | 3 + freemius/assets/img/plugin-icon.png | Bin 0 -> 9380 bytes freemius/assets/img/theme-icon.png | Bin 0 -> 11237 bytes freemius/assets/index.php | 3 + freemius/assets/js/index.php | 3 + freemius/assets/js/nojquery.ba-postmessage.js | 140 + .../assets/js/nojquery.ba-postmessage.min.js | 12 + freemius/assets/js/postmessage.js | 135 + freemius/config.php | 391 + freemius/includes/class-freemius-abstract.php | 597 + freemius/includes/class-freemius.php | 25387 ++++++++++++++++ freemius/includes/class-fs-admin-notices.php | 321 + freemius/includes/class-fs-api.php | 664 + freemius/includes/class-fs-logger.php | 691 + freemius/includes/class-fs-options.php | 431 + freemius/includes/class-fs-plugin-updater.php | 1561 + freemius/includes/class-fs-security.php | 85 + freemius/includes/class-fs-storage.php | 532 + freemius/includes/class-fs-user-lock.php | 126 + .../class-fs-customizer-support-section.php | 102 + .../class-fs-customizer-upsell-control.php | 161 + freemius/includes/customizer/index.php | 3 + .../debug/class-fs-debug-bar-panel.php | 64 + freemius/includes/debug/debug-bar-start.php | 52 + freemius/includes/debug/index.php | 3 + .../entities/class-fs-affiliate-terms.php | 128 + .../includes/entities/class-fs-affiliate.php | 84 + .../includes/entities/class-fs-billing.php | 95 + .../includes/entities/class-fs-entity.php | 159 + .../includes/entities/class-fs-payment.php | 168 + .../entities/class-fs-plugin-info.php | 34 + .../entities/class-fs-plugin-license.php | 330 + .../entities/class-fs-plugin-plan.php | 145 + .../includes/entities/class-fs-plugin-tag.php | 60 + .../includes/entities/class-fs-plugin.php | 159 + .../includes/entities/class-fs-pricing.php | 157 + .../entities/class-fs-scope-entity.php | 29 + freemius/includes/entities/class-fs-site.php | 253 + .../entities/class-fs-subscription.php | 147 + freemius/includes/entities/class-fs-user.php | 62 + freemius/includes/entities/index.php | 3 + freemius/includes/fs-core-functions.php | 1416 + freemius/includes/fs-essential-functions.php | 500 + freemius/includes/fs-plugin-info-dialog.php | 1644 + freemius/includes/i18n.php | 605 + freemius/includes/index.php | 3 + freemius/includes/l10n.php | 48 + .../managers/class-fs-admin-menu-manager.php | 1006 + .../class-fs-admin-notice-manager.php | 477 + .../managers/class-fs-cache-manager.php | 326 + .../managers/class-fs-gdpr-manager.php | 202 + .../managers/class-fs-key-value-storage.php | 392 + .../managers/class-fs-license-manager.php | 104 + .../managers/class-fs-option-manager.php | 521 + .../managers/class-fs-plan-manager.php | 162 + .../managers/class-fs-plugin-manager.php | 220 + freemius/includes/managers/index.php | 3 + .../Exceptions/ArgumentNotExistException.php | 13 + .../sdk/Exceptions/EmptyArgumentException.php | 13 + .../includes/sdk/Exceptions/Exception.php | 78 + .../Exceptions/InvalidArgumentException.php | 12 + .../sdk/Exceptions/OAuthException.php | 16 + freemius/includes/sdk/Exceptions/index.php | 3 + freemius/includes/sdk/FreemiusBase.php | 219 + freemius/includes/sdk/FreemiusWordPress.php | 715 + freemius/includes/sdk/LICENSE.txt | 340 + freemius/includes/sdk/index.php | 3 + .../fs-essential-functions-1.1.7.1.php | 43 + .../fs-essential-functions-2.2.1.php | 45 + freemius/includes/supplements/index.php | 3 + freemius/index.php | 3 + freemius/languages/freemius-cs_CZ.mo | Bin 0 -> 60931 bytes freemius/languages/freemius-da_DK.mo | Bin 0 -> 59840 bytes freemius/languages/freemius-en.mo | Bin 0 -> 59161 bytes freemius/languages/freemius-es_ES.mo | Bin 0 -> 62945 bytes freemius/languages/freemius-fr_FR.mo | Bin 0 -> 63519 bytes freemius/languages/freemius-he_IL.mo | Bin 0 -> 62213 bytes freemius/languages/freemius-hu_HU.mo | Bin 0 -> 60199 bytes freemius/languages/freemius-it_IT.mo | Bin 0 -> 61645 bytes freemius/languages/freemius-ja.mo | Bin 0 -> 67335 bytes freemius/languages/freemius-nl_NL.mo | Bin 0 -> 61628 bytes freemius/languages/freemius-ru_RU.mo | Bin 0 -> 75599 bytes freemius/languages/freemius-ta.mo | Bin 0 -> 93241 bytes freemius/languages/freemius-zh_CN.mo | Bin 0 -> 56773 bytes freemius/languages/freemius.pot | 2556 ++ freemius/languages/index.php | 3 + freemius/package.json | 27 + freemius/require.php | 53 + freemius/start.php | 530 + freemius/templates/account.php | 1098 + freemius/templates/account/billing.php | 423 + freemius/templates/account/index.php | 3 + .../partials/activate-license-button.php | 54 + freemius/templates/account/partials/addon.php | 451 + .../partials/deactivate-license-button.php | 36 + freemius/templates/account/partials/index.php | 3 + freemius/templates/account/partials/site.php | 352 + freemius/templates/account/payments.php | 59 + freemius/templates/add-ons.php | 502 + freemius/templates/add-trial-to-pricing.php | 31 + freemius/templates/admin-notice.php | 76 + freemius/templates/ajax-loader.php | 6 + freemius/templates/auto-installation.php | 249 + freemius/templates/checkout.php | 337 + freemius/templates/connect.php | 1038 + freemius/templates/contact.php | 128 + freemius/templates/debug.php | 765 + freemius/templates/debug/api-calls.php | 155 + freemius/templates/debug/index.php | 3 + freemius/templates/debug/logger.php | 66 + .../templates/debug/plugins-themes-sync.php | 76 + freemius/templates/debug/scheduled-crons.php | 136 + freemius/templates/email.php | 49 + freemius/templates/firewall-issues-js.php | 63 + freemius/templates/forms/affiliation.php | 509 + freemius/templates/forms/data-debug-mode.php | 213 + .../templates/forms/deactivation/contact.php | 23 + .../templates/forms/deactivation/form.php | 543 + .../templates/forms/deactivation/index.php | 3 + .../forms/deactivation/retry-skip.php | 24 + freemius/templates/forms/index.php | 3 + .../templates/forms/license-activation.php | 870 + freemius/templates/forms/optout.php | 336 + .../premium-versions-upgrade-handler.php | 205 + .../premium-versions-upgrade-metadata.php | 47 + freemius/templates/forms/resend-key.php | 247 + .../forms/subscription-cancellation.php | 277 + freemius/templates/forms/trial-start.php | 181 + freemius/templates/forms/user-change.php | 296 + freemius/templates/gdpr-optin-js.php | 66 + freemius/templates/index.php | 3 + freemius/templates/js/index.php | 3 + .../templates/js/jquery.content-change.php | 58 + .../templates/js/open-license-activation.php | 37 + freemius/templates/js/style-premium-theme.php | 53 + freemius/templates/partials/index.php | 2 + .../templates/partials/network-activation.php | 94 + freemius/templates/plugin-icon.php | 20 + .../templates/plugin-info/description.php | 78 + freemius/templates/plugin-info/features.php | 114 + freemius/templates/plugin-info/index.php | 3 + .../templates/plugin-info/screenshots.php | 34 + freemius/templates/powered-by.php | 61 + freemius/templates/pricing.php | 209 + freemius/templates/secure-https-header.php | 39 + freemius/templates/sticky-admin-notice-js.php | 41 + freemius/templates/tabs-capture-js.php | 63 + freemius/templates/tabs.php | 190 + includes/data-feeds.php | 7 +- includes/shortcodes.php | 6 +- includes/support-functions.php | 42 +- js/radio-station-page.js | 4 +- radio-station.php | 7 +- readme.txt | 1 + templates/single-show-content.php | 4 +- 171 files changed, 57922 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c3fec..a495042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed: undefined warning for debugshifts * Fixed: current show in schedule when on exact start second * Added: filters for time and date separators +* Added: description/excerpt to single show data endpoint ### 2.4.0.5 * Fixed: plugin conflicts causing fatal errors diff --git a/freemius/LICENSE.txt b/freemius/LICENSE.txt index e69de29..30ace6a 100644 --- a/freemius/LICENSE.txt +++ b/freemius/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/freemius/README.md b/freemius/README.md index e69de29..8260aff 100644 --- a/freemius/README.md +++ b/freemius/README.md @@ -0,0 +1,282 @@ +Freemius WordPress SDK +====================== + +Welcome to the official repository for the Freemius SDK! Adding the SDK to your WordPress plugin, theme, or add-ons, enables all the benefits that come with using the [Freemius platform](https://freemius.com) such as: + +* [Software Licensing](https://freemius.com/wordpress/software-licensing/) +* [Secure Checkout](https://freemius.com/wordpress/checkout/) +* [Subscriptions](https://freemius.com/wordpress/recurring-payments-subscriptions/) +* [Automatic Updates](https://freemius.com/wordpress/automatic-software-updates/) +* [Seamless EU VAT](https://freemius.com/wordpress/collecting-eu-vat-europe/) +* [Cart Abandonment Recovery](https://freemius.com/wordpress/cart-abandonment-recovery/) +* [Affiliate Platform](https://freemius.com/wordpress/affiliate-platform/) +* [Analytics & Usage Tracking](https://freemius.com/wordpress/insights/) +* [User Dashboard](https://freemius.com/wordpress/user-dashboard/) + +* [Monetization](https://freemius.com/wordpress/) +* [Analytics](https://freemius.com/wordpress/insights/) +* [More...](https://freemius.com/wordpress/features-comparison/) + +Freemius truly empowers developers to create prosperous subscription-based businesses. + +If you're new to Freemius then we recommend taking a look at our [Getting Started](https://freemius.com/help/documentation/getting-started/) guide first. + +If you're a WordPress plugin or theme developer and are interested in monetizing with Freemius then you can [sign-up for a FREE account](https://dashboard.freemius.com/register/): + +https://dashboard.freemius.com/register/ + +Once you have your account setup and are familiar with how it all works you're ready to begin [integrating Freemius](https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/) into your WordPress product + +You can see some of the existing WordPress.org plugins & themes that are already utilizing the power of Freemius here: + +* https://profiles.wordpress.org/freemius/#content-plugins +* https://includewp.com/freemius/#focus + +## Code Documentation + +You can find the SDK's documentation here: +https://freemius.com/help/documentation/wordpress-sdk/ + +## Integrating & Initializing the SDK + +As part of the integration process, you'll need to [add the latest version](https://freemius.com/help/documentation/getting-started/#add_the_latest_wordpress_sdk_into_your_product) of the Freemius SDK into your WordPress project. + +Then, when you've completed the [SDK integration form](https://freemius.com/help/documentation/getting-started/#fill_out_the_sdk_integration_form) a snippet of code is generated which you'll need to copy and paste into the top of your main plugin's PHP file, right after the plugin's header comment. + +Note: For themes, this will be in the root `functions.php` file instead. + +A typical SDK snippet will look similar to the following (your particular snippet may differ slightly depending on your integration): + +```php +if ( ! function_exists( 'my_prefix_fs' ) ) { + // Create a helper function for easy SDK access. + function my_prefix_fs() { + global $my_prefix_fs; + + if ( ! isset( $my_prefix_fs ) ) { + // Include Freemius SDK. + require_once dirname(__FILE__) . '/freemius/start.php'; + + $my_prefix_fs = fs_dynamic_init( array( + 'id' => '1234', + 'slug' => 'my-new-plugin', + 'premium_slug' => 'my-new-plugin-premium', + 'type' => 'plugin', + 'public_key' => 'pk_bAEfta69seKymZzmf2xtqq8QXHz9y', + 'is_premium' => true, + // If your plugin is a serviceware, set this option to false. + 'has_premium_version' => true, + 'has_paid_plans' => true, + 'is_org_compliant' => true, + 'menu' => array( + 'slug' => 'my-new-plugin', + 'parent' => array( + 'slug' => 'options-general.php', + ), + ), + // Set the SDK to work in a sandbox mode (for development & testing). + // IMPORTANT: MAKE SURE TO REMOVE SECRET KEY BEFORE DEPLOYMENT. + 'secret_key' => 'sk_ubb4yN3mzqGR2x8#P7r5&@*xC$utE', + ) ); + } + + return $my_prefix_fs; + } + + // Init Freemius. + my_prefix_fs(); + // Signal that SDK was initiated. + do_action( 'my_prefix_fs_loaded' ); +} + +``` + +## Usage example + +You can call anySDK methods by prefixing them with the shortcode function for your particular plugin/theme (specified when completing the SDK integration form in the Developer Dashboard): + +```php +get_upgrade_url(); ?> +``` + +Or when calling Freemius multiple times in a scope, it's recommended to use it with the global variable: + +```php +get_account_url(); +?> +``` + +There are many other SDK methods available that you can use to enhance the functionality of your WordPress product. Some of the more common use-cases are covered in the [Freemius SDK Gists](https://freemius.com/help/documentation/wordpress-sdk/gists/) documentation. + +## Adding license based logic examples + +Add marketing content to encourage your users to upgrade for your paid version: + +```php +is_not_paying() ) { + echo '

    ' . esc_html__('Awesome Premium Features', 'my-plugin-slug') . '

    '; + echo '' . + esc_html__('Upgrade Now!', 'my-plugin-slug') . + ''; + echo '
    '; + } +?> +``` + +Add logic which will only be available in your premium plugin version: + +```php +is__premium_only() ) { + + // ... premium only logic ... + + } +?> +``` + +To add a function which will only be available in your premium plugin version, simply add __premium_only as the suffix of the function name. Just make sure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: + +```php +is__premium_only() ) { + // Init premium version. + $this->admin_init__premium_only(); + + add_action( 'admin_init', array( &$this, 'admin_init_hook__premium_only' ); + } + + ... + } + + // This method will be only included in the premium version. + function admin_init__premium_only() { + ... + } + + // This method will be only included in the premium version. + function admin_init_hook__premium_only() { + ... + } + } +?> +``` + +Add logic which will only be executed for customers in your 'professional' plan: + +```php +is_plan('professional', true) ) { + // .. logic related to Professional plan only ... + } +?> +``` + +Add logic which will only be executed for customers in your 'professional' plan or higher plans: + +```php +is_plan('professional') ) { + // ... logic related to Professional plan and higher plans ... + } +?> +``` + +Add logic which will only be available in your premium plugin version AND will only be executed for customers in your 'professional' plan (and higher plans): + +```php +is_plan__premium_only('professional') ) { + // ... logic related to Professional plan and higher plans ... + } +?> +``` + +Add logic only for users in trial: + +```php +is_trial() ) { + // ... logic for users in trial ... + } +?> +``` + +Add logic for specified paid plan: + +```php +is__premium_only() ) { + if ( my_prefix_fs()->is_plan( 'professional', true ) ) { + + // ... logic related to Professional plan only ... + + } else if ( my_prefix_fs()->is_plan( 'business' ) ) { + + // ... logic related to Business plan and higher plans ... + + } + } +?> +``` + +## Excluding files and folders from the free plugin version +There are [two ways](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/#excluding_files_and_folders_from_the_free_plugin_version) to exclude files from your free version. + +1. Add `__premium_only` just before the file extension. For example, functions__premium_only.php will be only included in the premium plugin version. This works for all types of files, not only PHP. +2. Add `@fs_premium_only` a special meta tag to the plugin's main PHP file header. Example: +```php + +``` +In the example plugin header above, the file `/lib/functions.php` and the directory `/premium-files/` will be removed from the free plugin version. + +# WordPress.org Compliance +Based on [WordPress.org Guidelines](https://wordpress.org/plugins/about/guidelines/) you are not allowed to submit a plugin that has premium code in it: +> All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers. + +Therefore, if you want to deploy your free plugin's version to WordPress.org, make sure you wrap all your premium code with `if ( my_prefix_fs()->{{ method }}__premium_only() )` or use [some of the other methods](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/) provided by the SDK to exclude premium features & files from the free version. + +## Deployment +Zip your Freemius product’s root folder and [upload it in the Deployment section](https://freemius.com/help/documentation/selling-with-freemius/deployment/) in the *Freemius Developer's Dashboard*. +The plugin/theme will automatically be scanned and processed by a custom-developed *PHP Processor* which will auto-generate two versions of your plugin: + +1. **Premium version**: Identical to your uploaded version, including all code (except your `secret_key`). Will be enabled for download ONLY for your paying or in trial customers. +2. **Free version**: The code stripped from all your paid features (based on the logic added wrapped in `{ method }__premium_only()`). + +The free version is the one that you should give your users to download. Therefore, download the free generated version and upload to your site. Or, if your plugin was WordPress.org compliant and you made sure to exclude all your premium code with the different provided techniques, you can deploy the downloaded free version to the .org repo. + +## License +Copyright (c) Freemius®, Inc. + +Licensed under the GNU general public license (version 3). diff --git a/freemius/assets/css/admin/account.css b/freemius/assets/css/admin/account.css index e69de29..5582a0a 100644 --- a/freemius/assets/css/admin/account.css +++ b/freemius/assets/css/admin/account.css @@ -0,0 +1 @@ +label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id="license_not_whitelabeled"].success,.fs-notice[data-id="license_whitelabeled"].success{color:inherit;border-left-color:#00a0d2}.fs-notice[data-id="license_not_whitelabeled"].success label.fs-plugin-title,.fs-notice[data-id="license_whitelabeled"].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #F1F1F1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:0.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type="text"]{color:#0073AA;font-size:16px;background:none}.fs-key-value-table input[type="text"]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} diff --git a/freemius/assets/css/admin/add-ons.css b/freemius/assets/css/admin/add-ons.css index e69de29..d2391c6 100644 --- a/freemius/assets/css/admin/add-ons.css +++ b/freemius/assets/css/admin/add-ons.css @@ -0,0 +1,2 @@ +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:greenyellow;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.3);box-shadow:1px 1px 1px rgba(0,0,0,0.3);text-transform:uppercase;font-size:0.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} +#TB_window,#TB_window iframe{width:821px !important}#plugin-information .fyi{width:266px !important}#plugin-information #section-holder{margin-right:299px}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description iframe{max-width:100%}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:greenyellow;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid darkgreen;padding:2px;text-align:center;font-size:0.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#F3F3F3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:greenyellow}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;width:auto;top:0;right:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-top:6px solid white;border-right:4px solid transparent;border-left:4px solid transparent;top:12px;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{position:absolute;right:-1px;top:100%;margin-left:auto;padding:3px 0;border:1px solid #bfbfbf;background-color:#fff;z-index:1;width:230px;text-align:left;-moz-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);-webkit-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{-moz-border-radius:3px 0 3px 3px;-webkit-border-radius:3px 0 3px 3px;border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{-moz-border-radius:3px 3px 0 3px;-webkit-border-radius:3px 3px 0 3px;border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:normal;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{top:auto;bottom:100%;margin-bottom:2px}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{text-align:center;display:table}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer>.button,#plugin-information-footer .fs-dropdown{position:relative;top:3px}#plugin-information-footer>.button.left,#plugin-information-footer .fs-dropdown.left{float:left}#plugin-information-footer>.right,#plugin-information-footer .fs-dropdown{float:right}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} diff --git a/freemius/assets/css/admin/affiliation.css b/freemius/assets/css/admin/affiliation.css index e69de29..003ca37 100644 --- a/freemius/assets/css/admin/affiliation.css +++ b/freemius/assets/css/admin/affiliation.css @@ -0,0 +1 @@ +@charset "UTF-8";#fs_affiliation_content_wrapper #messages{margin-top:25px}#fs_affiliation_content_wrapper h3{font-size:24px;padding:0;margin-left:0}#fs_affiliation_content_wrapper ul li{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;list-style-type:none}#fs_affiliation_content_wrapper ul li:before{content:'✓';margin-right:10px;font-weight:bold}#fs_affiliation_content_wrapper p:not(.description),#fs_affiliation_content_wrapper li,#fs_affiliation_content_wrapper label{font-size:16px !important;line-height:26px !important}#fs_affiliation_content_wrapper .button{margin-top:20px;margin-bottom:7px;line-height:35px;height:40px;font-size:16px}#fs_affiliation_content_wrapper .button#cancel_button{margin-right:5px}#fs_affiliation_content_wrapper form .input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form .input-container .input-label{font-weight:bold;display:block;width:100%}#fs_affiliation_content_wrapper form .input-container.input-container-text label,#fs_affiliation_content_wrapper form .input-container.input-container-text input,#fs_affiliation_content_wrapper form .input-container.input-container-text textarea{display:block}#fs_affiliation_content_wrapper form .input-container #add_domain,#fs_affiliation_content_wrapper form .input-container .remove-domain{text-decoration:none;display:inline-block;margin-top:3px}#fs_affiliation_content_wrapper form .input-container #add_domain:focus,#fs_affiliation_content_wrapper form .input-container .remove-domain:focus{box-shadow:none}#fs_affiliation_content_wrapper form .input-container #add_domain.disabled,#fs_affiliation_content_wrapper form .input-container .remove-domain.disabled{color:#aaa;cursor:default}#fs_affiliation_content_wrapper form #extra_domains_container .description{margin-top:0;position:relative;top:-4px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain{display:inline-block;margin-right:5px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain:last-of-type{margin-bottom:0} diff --git a/freemius/assets/css/admin/checkout.css b/freemius/assets/css/admin/checkout.css index e69de29..56515d2 100644 --- a/freemius/assets/css/admin/checkout.css +++ b/freemius/assets/css/admin/checkout.css @@ -0,0 +1 @@ +@media screen and (max-width: 782px){#wpbody-content{padding-bottom:0 !important}} diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css index e69de29..d96aa2f 100644 --- a/freemius/assets/css/admin/common.css +++ b/freemius/assets/css/admin/common.css @@ -0,0 +1,2 @@ +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,0.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,0.1),inset 0 1px 3px 0 rgba(0,0,0,0.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,0.5);z-index:999;-moz-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}.fs-switch-feedback{margin-left:10px}.fs-switch-feedback.success{color:#71ae00}.rtl .fs-switch-feedback{margin-left:0;margin-right:10px}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media (max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}} +.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/assets/css/admin/connect.css b/freemius/assets/css/admin/connect.css index e69de29..dff7c49 100644 --- a/freemius/assets/css/admin/connect.css +++ b/freemius/assets/css/admin/connect.css @@ -0,0 +1 @@ +#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions>.fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li>i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li .fs-switch{float:right}#fs_connect .fs-permissions ul li .fs-permission-description{margin-left:55px}#fs_connect .fs-permissions ul li .fs-permission-description span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff !important;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li .fs-switch{float:left}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;text-align:center;font-size:0.9em;margin-top:10px} diff --git a/freemius/assets/css/admin/debug.css b/freemius/assets/css/admin/debug.css index e69de29..4b30d84 100644 --- a/freemius/assets/css/admin/debug.css +++ b/freemius/assets/css/admin/debug.css @@ -0,0 +1 @@ +.fs-switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:bold}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac !important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be} diff --git a/freemius/assets/css/admin/dialog-boxes.css b/freemius/assets/css/admin/dialog-boxes.css index e69de29..434f346 100644 --- a/freemius/assets/css/admin/dialog-boxes.css +++ b/freemius/assets/css/admin/dialog-boxes.css @@ -0,0 +1,2 @@ +.fs-modal{position:fixed;overflow:auto;height:100%;width:100%;top:0;z-index:100000;display:none;background:rgba(0,0,0,0.6)}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;position:absolute;left:50%;margin-left:-298px;padding-bottom:30px;top:-100%;z-index:100001;width:596px}@media (max-width: 650px){.fs-modal .fs-modal-dialog{margin-left:-50%;box-sizing:border-box;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active{display:block}.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{border:0;background:#fefefe;padding:20px}.fs-modal .fs-modal-header{border-bottom:#eeeeee solid 1px;background:#fbfbfb;padding:15px 20px;position:relative;margin-bottom:-10px}.fs-modal .fs-modal-header h4{margin:0;padding:0;text-transform:uppercase;font-size:1.2em;font-weight:bold;color:#cacaca;text-shadow:1px 1px 1px #fff;letter-spacing:0.6px;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{position:absolute;right:10px;top:12px;cursor:pointer;color:#bbb;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;padding:3px;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;-ms-transition:all 0.2s ease-in-out;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{color:#fff;background:#aaa}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-weight:bold;font-size:20px;margin-top:0}.fs-modal .fs-modal-footer{border-top:#eeeeee solid 1px;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:first-child{margin:0}.fs-modal .fs-modal-panel>.notice.inline{margin:0;display:none}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{right:auto;left:20px}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .reason-input,.fs-modal.fs-modal-deactivation-feedback .internal-message{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea,.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;padding:7px;display:none}@media (max-width: 650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label{float:left}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0 !important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{margin-top:0;line-height:1.5em}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:bold;padding:0 25px;margin-bottom:0}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;top:5px;position:relative}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table,.fs-license-options-container table select,.fs-license-options-container table .fs-available-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{position:relative;top:6px;float:left;margin-right:5px}.fs-license-options-container table .fs-other-license-key-container div{overflow:hidden;width:auto;height:30px;display:block;top:2px;position:relative}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{width:100%;border-collapse:collapse}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type="radio"]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{width:1%;padding-left:3px;padding-right:3px}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key{width:100%}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media (max-width: 650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}} +.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-right:7px;margin-left:0}a.show-license-resend-modal{margin-top:4px;display:inline-block}.fs-ajax-loader{position:relative;width:170px;height:20px;margin:auto}.fs-ajax-loader .fs-ajax-loader-bar{position:absolute;top:0;background-color:#0074a3;width:20px;height:20px;-webkit-animation-name:bounce_ajaxLoader;-moz-animation-name:bounce_ajaxLoader;-ms-animation-name:bounce_ajaxLoader;-o-animation-name:bounce_ajaxLoader;animation-name:bounce_ajaxLoader;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s;animation-duration:1.5s;animation-iteration-count:infinite;-o-animation-iteration-count:infinite;-ms-animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;-webkit-animation-direction:normal;-moz-animation-direction:normal;-ms-animation-direction:normal;-o-animation-direction:normal;animation-direction:normal;-moz-transform:0.3;-o-transform:0.3;-ms-transform:0.3;-webkit-transform:0.3;transform:0.3}.fs-ajax-loader .fs-ajax-loader-bar-1{left:0px;animation-delay:0.6s;-o-animation-delay:0.6s;-ms-animation-delay:0.6s;-webkit-animation-delay:0.6s;-moz-animation-delay:0.6s}.fs-ajax-loader .fs-ajax-loader-bar-2{left:19px;animation-delay:0.75s;-o-animation-delay:0.75s;-ms-animation-delay:0.75s;-webkit-animation-delay:0.75s;-moz-animation-delay:0.75s}.fs-ajax-loader .fs-ajax-loader-bar-3{left:38px;animation-delay:0.9s;-o-animation-delay:0.9s;-ms-animation-delay:0.9s;-webkit-animation-delay:0.9s;-moz-animation-delay:0.9s}.fs-ajax-loader .fs-ajax-loader-bar-4{left:57px;animation-delay:1.05s;-o-animation-delay:1.05s;-ms-animation-delay:1.05s;-webkit-animation-delay:1.05s;-moz-animation-delay:1.05s}.fs-ajax-loader .fs-ajax-loader-bar-5{left:76px;animation-delay:1.2s;-o-animation-delay:1.2s;-ms-animation-delay:1.2s;-webkit-animation-delay:1.2s;-moz-animation-delay:1.2s}.fs-ajax-loader .fs-ajax-loader-bar-6{left:95px;animation-delay:1.35s;-o-animation-delay:1.35s;-ms-animation-delay:1.35s;-webkit-animation-delay:1.35s;-moz-animation-delay:1.35s}.fs-ajax-loader .fs-ajax-loader-bar-7{left:114px;animation-delay:1.5s;-o-animation-delay:1.5s;-ms-animation-delay:1.5s;-webkit-animation-delay:1.5s;-moz-animation-delay:1.5s}.fs-ajax-loader .fs-ajax-loader-bar-8{left:133px;animation-delay:1.65s;-o-animation-delay:1.65s;-ms-animation-delay:1.65s;-webkit-animation-delay:1.65s;-moz-animation-delay:1.65s}@-moz-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-ms-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-o-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-webkit-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}.fs-modal-auto-install #request-filesystem-credentials-form h2,.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;padding:10px 10px 5px 10px;width:300px;max-width:100%}.fs-modal-auto-install #request-filesystem-credentials-form>div,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form fieldset{width:300px;max-width:100%;margin:0 auto;display:block}.button-primary.warn{box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c;background:#f56a48;border-color:#ec6544 #d2593c #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{color:#f5b3a1 !important;background:#e76444 !important;border-color:#d85e40 !important;text-shadow:0 -1px 0 rgba(0,0,0,0.1) !important} diff --git a/freemius/assets/css/admin/gdpr-optin-notice.css b/freemius/assets/css/admin/gdpr-optin-notice.css index e69de29..0da5146 100644 --- a/freemius/assets/css/admin/gdpr-optin-notice.css +++ b/freemius/assets/css/admin/gdpr-optin-notice.css @@ -0,0 +1 @@ +.fs-notice[data-id^="gdpr_optin_actions"] .underlined{text-decoration:underline}.fs-notice[data-id^="gdpr_optin_actions"] ul .button,.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{vertical-align:middle}.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{display:inline-block;margin-left:3px} diff --git a/freemius/assets/css/admin/index.php b/freemius/assets/css/admin/index.php index e69de29..0316c6a 100644 --- a/freemius/assets/css/admin/index.php +++ b/freemius/assets/css/admin/index.php @@ -0,0 +1,3 @@ +4QUc+`00KlZEA}{+QaB(jz&calwp&7Oa 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 TzbJ{mb_A#>Xv){ZEu;Sjxq-&r literal 0 HcmV?d00001 diff --git a/freemius/assets/img/theme-icon.png b/freemius/assets/img/theme-icon.png index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..045d4b25f9727fada65dcfd66cf0a0a3d167ee84 100644 GIT binary patch literal 11237 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

    {^6Retfv z6e)+Y86 z&-OAhn&%B~c$`zOH~YnViV(=PdD(}Fcr6gb z@qCJTX)KfxzL`hqdq$pyR{#-Ot7VpvXTX&Lf;o10Uz!M^YMI*SFN%7P`VNK(D>QTdyCP-Z<%CK(t*T4j;W{3$D2mM!AexlA- zUb<}1Dx;~3vaWm)z9em_tc|P?cssQ$Hj)Y6^M;Y^!#S7DAf(wTVsi4od$#co(U{KZ4KkEm$NX_fw^6flAOA1wX{X>{{THMY+L{U literal 0 HcmV?d00001 diff --git a/freemius/assets/index.php b/freemius/assets/index.php index e69de29..0316c6a 100644 --- a/freemius/assets/index.php +++ b/freemius/assets/index.php @@ -0,0 +1,3 @@ + 0) { + $window.on('scroll', function () { + for (var i = 0; i < iframes.length; i++) { + FS.PostMessage.postScroll(iframes[i]); + } + }); + } + }, + init_child : function () + { + this.init(_parent_subdomain); + + _is_child = true; + + // Post height of a child right after window is loaded. + $(window).bind('load', function () { + FS.PostMessage.postHeight(); + + // Post message that window was loaded. + FS.PostMessage.post('loaded'); + }); + }, + hasParent : function () + { + return _hasParent; + }, + postHeight : function (diff, wrapper) { + diff = diff || 0; + wrapper = wrapper || '#wrap_section'; + this.post('height', { + height: diff + $(wrapper).outerHeight(true) + }); + }, + postScroll : function (iframe) { + this.post('scroll', { + top: $window.scrollTop(), + height: ($window.height() - parseFloat($html.css('paddingTop')) - parseFloat($html.css('marginTop'))) + }, iframe); + }, + post : function (type, data, iframe) + { + console.debug('PostMessage.post', type); + + if (iframe) + { + // Post to iframe. + _postman.postMessage(JSON.stringify({ + type: type, + data: data + }), iframe.src, iframe.contentWindow); + } + else { + // Post to parent. + _postman.postMessage(JSON.stringify({ + type: type, + data: data + }), _parent_url, window.parent); + } + }, + receive: function (type, callback) + { + console.debug('PostMessage.receive', type); + + if (undef === _callbacks[type]) + _callbacks[type] = []; + + _callbacks[type].push(callback); + }, + receiveOnce: function (type, callback) + { + if (this.is_set(type)) + return; + + this.receive(type, callback); + }, + // Check if any callbacks assigned to a specified message type. + is_set: function (type) + { + return (undef != _callbacks[type]); + }, + parent_url: function () + { + return _parent_url; + }, + parent_subdomain: function () + { + return _parent_subdomain; + } + }; + }(); +})(jQuery); \ No newline at end of file diff --git a/freemius/config.php b/freemius/config.php index e69de29..f51eb40 100644 --- a/freemius/config.php +++ b/freemius/config.php @@ -0,0 +1,391 @@ +is_registered() && $fs->is_tracking_allowed()` + * + * @since 1.0.1 + * @return bool + */ + abstract function is_registered(); + + /** + * Check if the user skipped connecting the account with Freemius. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_anonymous(); + + /** + * Check if the user currently in activation mode. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_activation_mode(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Usage Tracking + #---------------------------------------------------------------------------------- + + /** + * Returns TRUE if the user opted-in and didn't disconnect (opt-out). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool + */ + abstract function is_tracking_allowed(); + + /** + * Returns TRUE if the user never opted-in or manually opted-out. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + function is_tracking_prohibited() { + return ! $this->is_registered() || ! $this->is_tracking_allowed(); + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API Result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + abstract function stop_tracking(); + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + abstract function allow_tracking(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Module Type + #---------------------------------------------------------------------------------- + + /** + * Checks if the plugin's type is "plugin". The other type is "theme". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + abstract function is_plugin(); + + /** + * Checks if the module type is "theme". The other type is "plugin". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_theme() { + return ( ! $this->is_plugin() ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Permissions + #---------------------------------------------------------------------------------- + + /** + * Check if plugin must be WordPress.org compliant. + * + * @since 1.0.7 + * + * @return bool + */ + abstract function is_org_repo_compliant(); + + /** + * Check if plugin is allowed to install executable files. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_allowed_to_install() { + return ( $this->is_premium() || ! $this->is_org_repo_compliant() ); + } + + #endregion + + /** + * Check if user in trial or in free plan (not paying). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_not_paying() { + return ( $this->is_trial() || $this->is_free_plan() ); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + */ + abstract function is_paying(); + + /** + * Check if the user is paying or in trial. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying_or_trial() { + return ( $this->is_paying() || $this->is_trial() ); + } + + /** + * Check if user in a trial or have feature enabled license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + abstract function can_use_premium_code(); + + #---------------------------------------------------------------------------------- + #region Premium Only + #---------------------------------------------------------------------------------- + + /** + * All logic wrapped in methods with "__premium_only()" suffix will be only + * included in the premium code. + * + * Example: + * if ( freemius()->is__premium_only() ) { + * ... + * } + */ + + /** + * Returns true when running premium plugin code. + * + * @since 1.0.9 + * + * @return bool + */ + function is__premium_only() { + return $this->is_premium(); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + * + */ + function is_paying__premium_only() { + return ( $this->is__premium_only() && $this->is_paying() ); + } + + /** + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan__premium_only( $plan, $exact = false ) { + return ( $this->is_premium() && $this->is_plan( $plan, $exact ) ); + } + + /** + * Check if plan matches active license' plan or active trial license' plan. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan_or_trial__premium_only( $plan, $exact = false ) { + return ( $this->is_premium() && $this->is_plan_or_trial( $plan, $exact ) ); + } + + /** + * Check if the user is paying or in trial. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying_or_trial__premium_only() { + return $this->is_premium() && $this->is_paying_or_trial(); + } + + /** + * Check if the user has an activated and valid paid license on current plugin's install. + * + * @since 1.0.4 + * + * @return bool + * + * @deprecated Method name is confusing since it's not clear from the name the code will be removed. + * @using Alias to is_paying__premium_only() + */ + function is_paying__fs__() { + return $this->is_paying__premium_only(); + } + + /** + * Check if user in a trial or have feature enabled license. + * + * All code wrapped in this statement will be only included in the premium code. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function can_use_premium_code__premium_only() { + return $this->is_premium() && $this->can_use_premium_code(); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Trial + #---------------------------------------------------------------------------------- + + /** + * Check if the user in a trial. + * + * @since 1.0.3 + * + * @return bool + */ + abstract function is_trial(); + + /** + * Check if trial already utilized. + * + * @since 1.0.9 + * + * @return bool + */ + abstract function is_trial_utilized(); + + #endregion + + #---------------------------------------------------------------------------------- + #region Plans + #---------------------------------------------------------------------------------- + + /** + * Check if the user is on the free plan of the product. + * + * @since 1.0.4 + * + * @return bool + */ + abstract function is_free_plan(); + + /** + * @since 1.0.2 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + abstract function is_plan( $plan, $exact = false ); + + /** + * Check if plan based on trial. If not in trial mode, should return false. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + abstract function is_trial_plan( $plan, $exact = false ); + + /** + * Check if plan matches active license' plan or active trial license' plan. + * + * @since 1.0.9 + * + * @param string $plan Plan name. + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan_or_trial( $plan, $exact = false ) { + return $this->is_plan( $plan, $exact ) || + $this->is_trial_plan( $plan, $exact ); + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + abstract function has_paid_plan(); + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + abstract function has_free_plan(); + + /** + * Check if plugin is premium only (no free plans). + * + * NOTE: is__premium_only() is very different method, don't get confused. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + abstract function is_only_premium(); + + /** + * Check if module has a premium code version. + * + * Serviceware module might be freemium without any + * premium code version, where the paid features + * are all part of the service. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + abstract function has_premium_version(); + + /** + * Check if module has any release on Freemius, + * or all plugin's code is on WordPress.org (Serviceware). + * + * @return bool + */ + function has_release_on_freemius() { + return ! $this->is_org_repo_compliant() || + $this->has_premium_version(); + } + + /** + * Checks if it's a freemium plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_freemium() { + return $this->has_paid_plan() && + $this->has_free_plan(); + } + + /** + * Check if module has only one plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return bool + */ + abstract function is_single_plan(); + + #endregion + + /** + * Check if running payments in sandbox mode. + * + * @since 1.0.4 + * + * @return bool + */ + abstract function is_payments_sandbox(); + + /** + * Check if running test vs. live plugin. + * + * @since 1.0.5 + * + * @return bool + */ + abstract function is_live(); + + /** + * Check if running premium plugin code. + * + * @since 1.0.5 + * + * @return bool + */ + abstract function is_premium(); + + /** + * Get upgrade URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param string $period Billing cycle. + * + * @return string + */ + abstract function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ); + + /** + * Check if Freemius was first added in a plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + function is_plugin_update() { + return ! $this->is_plugin_new_install(); + } + + /** + * Check if Freemius was part of the plugin when the user installed it first. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + abstract function is_plugin_new_install(); + + #---------------------------------------------------------------------------------- + #region Marketing + #---------------------------------------------------------------------------------- + + /** + * Check if current user purchased any other plugins before. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function has_purchased_before(); + + /** + * Check if current user classified as an agency. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_agency(); + + /** + * Check if current user classified as a developer. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_developer(); + + /** + * Check if current user classified as a business. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + abstract function is_business(); + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index e69de29..fba1289 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -0,0 +1,25387 @@ +store_id_slug_type_path_map( $module_id, $slug ); + } + + $this->_module_id = $module_id; + $this->_slug = $this->get_slug(); + $this->_module_type = $this->get_module_type(); + + $this->_blog_id = is_multisite() ? get_current_blog_id() : null; + + $this->_storage = FS_Storage::instance( $this->_module_type, $this->_slug ); + + $this->_cache = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" ); + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init ); + $this->_plugin_dir_path = plugin_dir_path( $this->_plugin_main_file_path ); + $this->_plugin_basename = $this->get_plugin_basename(); + $this->_free_plugin_basename = str_replace( '-premium/', '/', $this->_plugin_basename ); + + $this->_is_multisite_integrated = ( + defined( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) && + ( true === constant( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) ) + ); + + $this->_is_network_active = ( + is_multisite() && + $this->_is_multisite_integrated && + // Themes are always network activated, but the ACTUAL activation is per site. + $this->is_plugin() && + ( + is_plugin_active_for_network( $this->_plugin_basename ) || + // Plugin network level activation or uninstall. + ( fs_is_network_admin() && is_plugin_inactive( $this->_plugin_basename ) ) + ) + ); + + $this->_storage->set_network_active( + $this->_is_network_active, + $this->is_delegated_connection() + ); + + if ( ! isset( $this->_storage->is_network_activated ) ) { + $this->_storage->is_network_activated = $this->_is_network_active; + } + + if ( $this->_storage->is_network_activated != $this->_is_network_active ) { + // Update last activation level. + $this->_storage->is_network_activated = $this->_is_network_active; + + $this->maybe_adjust_storage(); + } + + #region Migration + + if ( is_multisite() ) { + /** + * If the install_timestamp exists on the site level but doesn't exist on the + * network level storage, it means that we need to process the storage with migration. + * + * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, install_timestamp will be already set in the network level storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( false === $this->_storage->get( 'install_timestamp', false, true ) && + false !== $this->_storage->get( 'install_timestamp', false, false ) + ) { + // Initiate storage migration. + $this->_storage->migrate_to_network(); + + // Migrate module cache to network level storage. + $this->_cache->migrate_to_network(); + } + } + + #endregion + + $base_name_split = explode( '/', $this->_plugin_basename ); + $this->_plugin_dir_name = $base_name_split[0]; + + if ( $this->_logger->is_on() ) { + $this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path ); + $this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path ); + $this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename ); + $this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename ); + $this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name ); + } + + // Remember link between file to slug. + $this->store_file_slug_map(); + + // Store plugin's initial install timestamp. + if ( ! isset( $this->_storage->install_timestamp ) ) { + $this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME; + } + + if ( ! is_object( $this->_plugin ) ) { + $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get(); + } + + $this->_admin_notices = FS_Admin_Notices::instance( + $this->_slug . ( $this->is_theme() ? ':theme' : '' ), + /** + * Ensure that the admin notice will always have a title by using the stored plugin title if available and + * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + ( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ), + $this->get_unique_affix() + ); + + if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) || + fs_request_is_action( 'restart_freemius' ) + ) { + FS_Api::clear_cache(); + $this->_cache->clear(); + } + + $this->register_constructor_hooks(); + + /** + * Starting from version 2.0.0, `FS_Site` entities no longer have the `plan` property and have `plan_id` + * instead. This should be called before calling `_load_account()`, otherwise, `$this->_site` will not be + * loaded in `_load_account` for versions of SDK starting from 2.0.0. + * + * @author Leo Fajardo (@leorw) + */ + self::migrate_install_plan_to_plan_id( $this->_storage ); + + $this->_load_account(); + + $this->_version_updates_handler(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + private function maybe_adjust_storage() { + $install_timestamp = null; + $prev_is_premium = null; + + $options_to_update = array(); + + $is_network_admin = fs_is_network_admin(); + + $network_install_timestamp = $this->_storage->get( 'install_timestamp', null, true ); + + if ( ! $is_network_admin ) { + if ( is_null( $network_install_timestamp ) ) { + // Plugin was not network-activated before. + return; + } + + if ( is_null( $this->_storage->get( 'install_timestamp', null, false ) ) ) { + // Set the `install_timestamp` only if it's not yet set. + $install_timestamp = $network_install_timestamp; + } + + $prev_is_premium = $this->_storage->get( 'prev_is_premium', null, true ); + } else { + $current_wp_user = self::_get_current_wp_user(); + $current_fs_user = self::_get_user_by_email( $current_wp_user->user_email ); + $network_user_info = array(); + + $skips_count = 0; + + $sites = self::get_sites(); + $sites_count = count( $sites ); + + $blog_id_2_install_map = array(); + + $is_first_non_ignored_blog = true; + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $blog_install_timestamp = $this->_storage->get( 'install_timestamp', null, $blog_id ); + + if ( is_null( $blog_install_timestamp ) ) { + // Plugin has not been installed on this blog. + continue; + } + + $is_earlier_install = ( + ! is_null( $install_timestamp ) && + $blog_install_timestamp < $install_timestamp + ); + + $install = $this->get_install_by_blog_id( $blog_id ); + + $update_network_user_info = false; + + if ( ! is_object( $install ) ) { + if ( ! $this->_storage->get( 'is_anonymous', false, $blog_id ) ) { + // The opt-in decision (whether to skip or opt in) is yet to be made. + continue; + } + + $skips_count ++; + } else { + $blog_id_2_install_map[ $blog_id ] = $install; + + if ( empty( $network_user_info ) ) { + // Set the network user info for the 1st time. Choose any user information whether or not it is for the current WP user. + $update_network_user_info = true; + } + + if ( ! $update_network_user_info && + is_object( $current_fs_user ) && + $network_user_info['user_id'] != $current_fs_user->id && + $install->user_id == $current_fs_user->id + ) { + // If an install that is owned by the current WP user is found, use its user information instead. + $update_network_user_info = true; + } + + if ( ! $update_network_user_info && + $is_earlier_install && + ( ! is_object( $current_fs_user ) || $current_fs_user->id == $install->user_id ) + ) { + // Update to the earliest install info if there's no install found so far that is owned by the current WP user; OR only if the found install is owned by the current WP user. + $update_network_user_info = true; + } + } + + if ( $update_network_user_info ) { + $network_user_info = array( + 'user_id' => $install->user_id, + 'blog_id' => $blog_id + ); + } + + $site_prev_is_premium = $this->_storage->get( 'prev_is_premium', null, $blog_id ); + + if ( $is_first_non_ignored_blog ) { + $prev_is_premium = $site_prev_is_premium; + + if ( is_null( $network_install_timestamp ) ) { + $install_timestamp = $blog_install_timestamp; + } + + $is_first_non_ignored_blog = false; + + continue; + } + + if ( ! is_null( $prev_is_premium ) && $prev_is_premium !== $site_prev_is_premium ) { + // If a different `$site_prev_is_premium` value is found, do not include the option in the collection of options to update. + $prev_is_premium = null; + } + + if ( $is_earlier_install ) { + // If an earlier install timestamp is found. + $install_timestamp = $blog_install_timestamp; + } + } + + $installs_count = count( $blog_id_2_install_map ); + + if ( $sites_count === ( $installs_count + $skips_count ) ) { + if ( ! empty( $network_user_info ) ) { + $options_to_update['network_user_id'] = $network_user_info['user_id']; + $options_to_update['network_install_blog_id'] = $network_user_info['blog_id']; + + foreach ( $blog_id_2_install_map as $blog_id => $install ) { + if ( $install->user_id == $network_user_info['user_id'] ) { + continue; + } + + $this->_storage->store( 'is_delegated_connection', true, $blog_id ); + } + } + + if ( $sites_count === $skips_count ) { + /** + * Assume network-level skipping as the intended action if all actions identified were only + * skipping of the connection (i.e., no opt-ins and delegated connections so far). + */ + $options_to_update['is_anonymous_ms'] = true; + } else if ( $sites_count === $installs_count ) { + /** + * Assume network-level opt-in as the intended action if all actions identified were only opt-ins + * (i.e., no delegation and skipping of the connections so far). + */ + $options_to_update['is_network_connected'] = true; + } + } + } + + if ( ! is_null( $install_timestamp ) ) { + $options_to_update['install_timestamp'] = $install_timestamp; + } + + if ( ! is_null( $prev_is_premium ) ) { + $options_to_update['prev_is_premium'] = $prev_is_premium; + } + + if ( ! empty( $options_to_update ) ) { + $this->adjust_storage( $options_to_update, $is_network_admin ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param array $options + * @param bool $is_network_admin + */ + private function adjust_storage( $options, $is_network_admin ) { + foreach ( $options as $name => $value ) { + $this->_storage->store( $name, $value, $is_network_admin ? true : null ); + } + } + + /** + * Checks whether this module has a settings menu. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function has_settings_menu() { + return ( $this->_is_network_active && fs_is_network_admin() ) ? + $this->_menu->has_network_menu() : + $this->_menu->has_menu(); + } + + /** + * If `true` the opt-in should be shown as a modal dialog box on the themes.php page. WordPress.org themes guidelines prohibit from redirecting the user from the themes.php page after activating a theme. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.5 + * + * @return bool + */ + function show_opt_in_on_themes_page() { + if ( ! $this->is_free_wp_org_theme() ) { + return false; + } + + if ( ! $this->has_settings_menu() ) { + return true; + } + + return $this->show_settings_with_tabs(); + } + + /** + * If `true` the opt-in should be shown on the product's main setting page. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.5 + * + * @return bool + * + * @uses show_opt_in_on_themes_page(); + */ + function show_opt_in_on_setting_page() { + return ! $this->show_opt_in_on_themes_page(); + } + + /** + * If `true` the settings should be shown using tabs. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.5 + * + * @return bool + */ + function show_settings_with_tabs() { + return ( self::NAVIGATION_TABS === $this->_navigation ); + } + + /** + * Check if the context module is free wp.org theme. + * + * This method is helpful because: + * 1. wp.org themes are limited to a single submenu item, + * and sub-submenu items are most likely not allowed (never verified). + * 2. wp.org themes are not allowed to redirect the user + * after the theme activation, therefore, the agreed UX + * is showing the opt-in as a modal dialog box after + * activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_free_wp_org_theme() { + return ( + $this->is_theme() && + $this->is_org_repo_compliant() && + ! $this->is_premium() + ); + } + + /** + * Checks whether this a submenu item is visible. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.6 + * @since 1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page. + * + * @param string $slug + * @param bool $is_tabs_visibility_check This is used to decide if the associated tab should be shown or hidden. + * + * @return bool + */ + function is_submenu_item_visible( $slug, $is_tabs_visibility_check = false ) { + if ( $this->is_admin_page( $slug ) ) { + /** + * It is the current context page, so show the submenu item + * so the user will have the right context page, even if it + * was set to hidden. + */ + return true; + } + + if ( ! $this->has_settings_menu() ) { + // No menu settings at all. + return false; + } + + if ( + ! $is_tabs_visibility_check && + $this->is_org_repo_compliant() && + $this->show_settings_with_tabs() + ) { + /** + * wp.org themes are limited to a single submenu item, and + * sub-submenu items are most likely not allowed (never verified). + */ + return false; + } + + return $this->_menu->is_submenu_item_visible( $slug ); + } + + /** + * Check if a Freemius page should be accessible via the UI. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $slug + * + * @return bool + */ + function is_page_visible( $slug ) { + if ( $this->is_admin_page( $slug ) ) { + return true; + } + + return $this->_menu->is_submenu_item_visible( $slug, true, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _version_updates_handler() { + if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) { + // Freemius version upgrade mode. + $this->_storage->sdk_last_version = $this->_storage->sdk_version; + $this->_storage->sdk_version = $this->version; + + if ( empty( $this->_storage->sdk_last_version ) || + version_compare( $this->_storage->sdk_last_version, $this->version, '<' ) + ) { + $this->_storage->sdk_upgrade_mode = true; + $this->_storage->sdk_downgrade_mode = false; + } else { + $this->_storage->sdk_downgrade_mode = true; + $this->_storage->sdk_upgrade_mode = false; + + } + + $this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version ); + } + + $plugin_version = $this->get_plugin_version(); + if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) { + // Plugin version upgrade mode. + $this->_storage->plugin_last_version = $this->_storage->plugin_version; + $this->_storage->plugin_version = $plugin_version; + + if ( empty( $this->_storage->plugin_last_version ) || + version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' ) + ) { + $this->_storage->plugin_upgrade_mode = true; + $this->_storage->plugin_downgrade_mode = false; + } else { + $this->_storage->plugin_downgrade_mode = true; + $this->_storage->plugin_upgrade_mode = false; + } + + if ( ! empty( $this->_storage->plugin_last_version ) ) { + // Different version of the plugin was installed before, therefore it's an update. + $this->_storage->is_plugin_new_install = false; + } + + $this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version ); + } + } + + #-------------------------------------------------------------------------------- + #region Data Migration on SDK Update + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @param string $sdk_prev_version + * @param string $sdk_version + */ + function _sdk_version_update( $sdk_prev_version, $sdk_version ) { + /** + * @since 1.1.7.3 Fixed unwanted connectivity test cleanup. + */ + if ( empty( $sdk_prev_version ) ) { + return; + } + + if ( version_compare( $sdk_prev_version, '2.1.0', '<' ) && + version_compare( $sdk_version, '2.1.0', '>=' ) + ) { + $this->_storage->handle_gdpr_admin_notice = true; + } + + if ( version_compare( $sdk_prev_version, '2.0.0', '<' ) && + version_compare( $sdk_version, '2.0.0', '>=' ) + ) { + $this->migrate_to_subscriptions_collection(); + + $this->consolidate_licenses(); + + // Clear trial_plan since it's now loaded from the plans collection when needed. + $this->_storage->remove( 'trial_plan', true, false ); + } + + if ( version_compare( $sdk_prev_version, '1.2.3', '<' ) && + version_compare( $sdk_version, '1.2.3', '>=' ) + ) { + /** + * Starting from version 1.2.3, paths are stored as relative instead of absolute and some of them can be + * invalid. + * + * @author Leo Fajardo (@leorw) + */ + $this->remove_invalid_paths(); + } + + if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) && + version_compare( $sdk_version, '1.1.5', '>=' ) + ) { + // On version 1.1.5 merged connectivity and is_on data. + if ( isset( $this->_storage->connectivity_test ) ) { + if ( ! isset( $this->_storage->is_on ) ) { + unset( $this->_storage->connectivity_test ); + } else { + $connectivity_data = $this->_storage->connectivity_test; + $connectivity_data['is_active'] = $this->_storage->is_on['is_active']; + $connectivity_data['timestamp'] = $this->_storage->is_on['timestamp']; + + // Override. + $this->_storage->connectivity_test = $connectivity_data; + + // Remove previous structure. + unset( $this->_storage->is_on ); + } + + } + } + + if ( + version_compare( $sdk_prev_version, '2.2.1', '<' ) && + version_compare( $sdk_version, '2.2.1', '>=' ) + ) { + /** + * Clear the file cache without storing the previous path since it could be a wrong path. For example, + * in the versions of the SDK lower than 2.2.1, it's possible for the path of an add-on to be the same + * as the parent plugin's when the add-on was auto-installed since the relevant method names were not + * skipped in the logic that determines the right path in the `get_caller_main_file_and_type` method + * (e.g. `try_activate_plugin`). Since it was an auto-installation, the caller was the parent plugin + * and so its path was used. In case the stored path is wrong, clearing the cache will resolve issues + * related to data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $this->clear_module_main_file_cache( false ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param \FS_Storage $storage + * @param bool|int|null $blog_id + */ + private static function migrate_install_plan_to_plan_id( FS_Storage $storage, $blog_id = null ) { + if ( empty( $storage->sdk_version ) ) { + // New installation of the plugin, no need to upgrade. + return; + } + + if ( ! version_compare( $storage->sdk_version, '2.0.0', '<' ) ) { + // Previous version is >= 2.0.0, so no need to migrate. + return; + } + + // Alias. + $module_type = $storage->get_module_type(); + $module_slug = $storage->get_module_slug(); + + $installs = self::get_all_sites( $module_type, $blog_id ); + $install = isset( $installs[ $module_slug ] ) ? $installs[ $module_slug ] : null; + + if ( ! is_object( $install ) ) { + return; + } + + if ( isset( $install->plan ) && is_object( $install->plan ) ) { + if ( isset( $install->plan->id ) && ! empty( $install->plan->id ) ) { + $install->plan_id = self::_decrypt( $install->plan->id ); + } + + unset( $install->plan ); + + $installs[ $module_slug ] = clone $install; + + self::set_account_option_by_module( + $module_type, + 'sites', + $installs, + true, + $blog_id + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function migrate_to_subscriptions_collection() { + if ( ! is_object( $this->_site ) ) { + return; + } + + if ( isset( $this->_storage->subscription ) && is_object( $this->_storage->subscription ) ) { + $this->_storage->subscriptions = array( fs_get_entity( $this->_storage->subscription, FS_Subscription::get_class_name() ) ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function consolidate_licenses() { + $plugin_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_PLUGIN ); + if ( isset( $plugin_licenses[ $this->_slug ] ) ) { + $plugin_licenses = $plugin_licenses[ $this->_slug ]; + } else { + $plugin_licenses = array(); + } + + $theme_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_THEME ); + if ( isset( $theme_licenses[ $this->_slug ] ) ) { + $theme_licenses = $theme_licenses[ $this->_slug ]; + } else { + $theme_licenses = array(); + } + + if ( empty( $plugin_licenses ) && empty( $theme_licenses ) ) { + return; + } + + $all_licenses = array(); + $user_id_license_ids_map = array(); + + foreach ( $plugin_licenses as $user_id => $user_licenses ) { + if ( is_array( $user_licenses ) ) { + if ( ! isset( $user_license_ids[ $user_id ] ) ) { + $user_id_license_ids_map[ $user_id ] = array(); + } + + foreach ( $user_licenses as $user_license ) { + $all_licenses[] = $user_license; + $user_id_license_ids_map[ $user_id ][] = $user_license->id; + } + } + } + + foreach ( $theme_licenses as $user_id => $user_licenses ) { + if ( is_array( $user_licenses ) ) { + if ( ! isset( $user_license_ids[ $user_id ] ) ) { + $user_id_license_ids_map[ $user_id ] = array(); + } + + foreach ( $user_licenses as $user_license ) { + $all_licenses[] = $user_license; + $user_id_license_ids_map[ $user_id ][] = $user_license->id; + } + } + } + + self::store_user_id_license_ids_map( + $user_id_license_ids_map, + $this->_module_id + ); + + $this->_store_licenses( true, $this->_module_id, $all_licenses ); + } + + /** + * Remove invalid paths. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + private function remove_invalid_paths() { + // Remove invalid path that is still associated with the current slug if there's any. + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + foreach ( $file_slug_map as $plugin_basename => $slug ) { + if ( $slug === $this->_slug && + $plugin_basename !== $this->_plugin_basename && + ! file_exists( $this->get_absolute_path( $plugin_basename ) ) + ) { + unset( $file_slug_map[ $plugin_basename ] ); + self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); + + break; + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $plugin_prev_version + * @param string $plugin_version + */ + function _after_version_update( $plugin_prev_version, $plugin_version ) { + if ( $this->is_theme() ) { + // Expire the cache of the previous tabs since the theme may + // have setting updates. + $this->_cache->expire( 'tabs' ); + $this->_cache->expire( 'tabs_stylesheets' ); + } + } + + /** + * A special migration logic for the $_accounts, executed for all the plugins in the system: + * - Moves some data to the network level storage. + * - If the plugin's connection was skipped for all sites, set the plugin as if it was network skipped. + * - If the plugin's connection was ignored for all sites, don't do anything in terms of the network connection. + * - If the plugin was connected to all sites by the same super-admin, set the plugin as if was network opted-in for all sites. + * - If there's at least one site that was connected by a super-admin, find the "main super-admin" (the one that installed the majority of the plugin installs) and set the plugin as if was network activated with the main super-admin, set all the sites that were skipped or opted-in with a different user to delegated mode. Then, prompt the currently logged super-admin to choose what to do with the ignored sites. + * - If there are any sites in the network which the connection decision was not yet taken for, set this plugin into network activation mode so a super-admin can choose what to do with the rest of the sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function migrate_accounts_to_network() { + $sites = self::get_sites(); + $sites_count = count( $sites ); + $connection_status = array(); + $plugin_slugs = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + self::$_accounts->migrate_to_network( $blog_id ); + + /** + * Build a list of all Freemius powered plugins slugs. + */ + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array(), $blog_id ); + foreach ( $id_slug_type_path_map as $module_id => $data ) { + if ( WP_FS__MODULE_TYPE_PLUGIN === $data['type'] ) { + $plugin_slugs[ $data['slug'] ] = true; + } + } + + $installs = self::get_account_option( 'sites', WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); + + if ( is_array( $installs ) ) { + foreach ( $installs as $slug => $install ) { + if ( ! isset( $connection_status[ $slug ] ) ) { + $connection_status[ $slug ] = array(); + } + + if ( is_object( $install ) && + FS_Site::is_valid_id( $install->id ) && + FS_User::is_valid_id( $install->user_id ) + ) { + $connection_status[ $slug ][ $blog_id ] = $install->user_id; + } + } + } + } + + foreach ( $plugin_slugs as $slug => $true ) { + if ( ! isset( $connection_status[ $slug ] ) ) { + $connection_status[ $slug ] = array(); + } + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( isset( $connection_status[ $slug ][ $blog_id ] ) ) { + continue; + } + + $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + $is_anonymous = $storage->get( 'is_anonymous', null, $blog_id ); + + if ( ! is_null( $is_anonymous ) ) { + // Since 1.1.3 is_anonymous is an array. + if ( is_array( $is_anonymous ) && isset( $is_anonymous['is'] ) ) { + $is_anonymous = $is_anonymous['is']; + } + + if ( is_bool( $is_anonymous ) && true === $is_anonymous ) { + $connection_status[ $slug ][ $blog_id ] = 'skipped'; + } + } + + if ( ! isset( $connection_status[ $slug ][ $blog_id ] ) ) { + $connection_status[ $slug ][ $blog_id ] = 'ignored'; + } + } + } + + $super_admins = array(); + + foreach ( $connection_status as $slug => $blogs_status ) { + $skips = 0; + $ignores = 0; + $connections = 0; + $opted_in_users = array(); + $opted_in_super_admins = array(); + + $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + foreach ( $blogs_status as $blog_id => $status_or_user_id ) { + if ( 'skipped' === $status_or_user_id ) { + $skips ++; + } else if ( 'ignored' === $status_or_user_id ) { + $ignores ++; + } else if ( FS_User::is_valid_id( $status_or_user_id ) ) { + $connections ++; + + if ( ! isset( $opted_in_users[ $status_or_user_id ] ) ) { + $opted_in_users[ $status_or_user_id ] = array(); + } + + $opted_in_users[ $status_or_user_id ][] = $blog_id; + + if ( isset( $super_admins[ $status_or_user_id ] ) || + self::is_super_admin( $status_or_user_id ) + ) { + // Cache super-admin data. + $super_admins[ $status_or_user_id ] = true; + + // Remember opted-in super-admins for the plugin. + $opted_in_super_admins[ $status_or_user_id ] = true; + } + } + } + + $main_super_admin_user_id = null; + $all_migrated = false; + if ( $sites_count == $skips ) { + // All sites were skipped -> network skip by copying the anonymous mode from any of the sites. + $storage->is_anonymous_ms = $storage->is_anonymous; + + $all_migrated = true; + } else if ( $sites_count == $ignores ) { + // Don't do anything, still in activation mode. + + $all_migrated = true; + } else if ( 0 < count( $opted_in_super_admins ) ) { + // Find the super-admin with the majority of installs. + $max_installs_by_super_admin = 0; + foreach ( $opted_in_super_admins as $user_id => $true ) { + $installs_count = count( $opted_in_users[ $user_id ] ); + + if ( $installs_count > $max_installs_by_super_admin ) { + $max_installs_by_super_admin = $installs_count; + $main_super_admin_user_id = $user_id; + } + } + + if ( $sites_count == $connections && 1 == count( $opted_in_super_admins ) ) { + // Super-admin opted-in for all sites in the network. + $storage->is_network_connected = true; + + $all_migrated = true; + } + + // Store network user. + $storage->network_user_id = $main_super_admin_user_id; + + $storage->network_install_blog_id = ( $sites_count == $connections ) ? + // Since all sites are opted-in, associating with the main site. + get_current_blog_id() : + // Associating with the 1st found opted-in site. + $opted_in_users[ $main_super_admin_user_id ][0]; + + /** + * Make sure we migrate the plan ID of the network install, otherwise, if after the migration + * the 1st page that will be loaded is the network level WP Admin and $storage->network_install_blog_id + * is different than the main site of the network, the $this->_site will not be set since the plan_id + * will be empty. + */ + $storage->migrate_to_network(); + self::migrate_install_plan_to_plan_id( $storage, $storage->network_install_blog_id ); + } else { + // At least one opt-in. All the opt-in were created by a non-super-admin. + if ( 0 == $ignores ) { + // All sites were opted-in or skipped, all by non-super-admin. So delegate all. + $storage->store( 'is_delegated_connection', true, true ); + + $all_migrated = true; + } + } + + if ( ! $all_migrated ) { + /** + * Delegate all sites that were: + * 1) Opted-in by a user that is NOT the main-super-admin. + * 2) Skipped and non of the sites was opted-in by a super-admin. If any site was opted-in by a super-admin, there will be a main-super-admin, and we consider the skip as if it was done by that user. + */ + foreach ( $blogs_status as $blog_id => $status_or_user_id ) { + if ( $status_or_user_id == $main_super_admin_user_id ) { + continue; + } + + if ( FS_User::is_valid_id( $status_or_user_id ) || + ( 'skipped' === $status_or_user_id && is_null( $main_super_admin_user_id ) ) + ) { + $storage->store( 'is_delegated_connection', true, $blog_id ); + } + } + } + + + if ( ( $connections + $skips > 0 ) ) { + if ( $ignores > 0 ) { + /** + * If admin already opted-in or skipped in any of the network sites, and also + * have sites which the connection decision was not yet taken, set this plugin + * into network activation mode so the super-admin can choose what to do with + * the rest of the sites. + */ + self::set_network_upgrade_mode( $storage ); + } + } + } + } + + /** + * Set a module into network upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Storage $storage + * + * @return bool + */ + private static function set_network_upgrade_mode( FS_Storage $storage ) { + return $storage->is_network_activation = true; + } + + /** + * Will return true after upgrading to the SDK with the network level integration, + * when the super-admin involvement is required regarding the rest of the sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_upgrade_mode() { + return $this->_storage->get( 'is_network_activation' ); + } + + /** + * Clear flag after the upgrade mode completion. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool True if network activation was on and now completed. + */ + private function network_upgrade_mode_completed() { + if ( fs_is_network_admin() && $this->is_network_upgrade_mode() ) { + $this->_storage->remove( 'is_network_activation' ); + + return true; + } + + return false; + } + + #endregion + + /** + * This action is connected to the 'plugins_loaded' hook and helps to determine + * if this is a new plugin installation or a plugin update. + * + * There are 3 different use-cases: + * 1) New plugin installation right with Freemius: + * 1.1 _activate_plugin_event_hook() will be executed first + * 1.2 Since $this->_storage->is_plugin_new_install is not set, + * and $this->_storage->plugin_last_version is not set, + * $this->_storage->is_plugin_new_install will be set to TRUE. + * 1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will + * be already set to TRUE. + * + * 2) Plugin update, didn't have Freemius before, and now have the SDK: + * 2.1 _activate_plugin_event_hook() will not be executed, because + * the activation hook do NOT fires on updates since WP 3.1. + * 2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will + * be empty, therefore, it will be set to FALSE. + * + * 3) Plugin update, had Freemius in prev version as well: + * 3.1 _version_updates_handler() will be executed 1st, since FS was installed + * before, $this->_storage->plugin_last_version will NOT be empty, + * therefore, $this->_storage->is_plugin_new_install will be set to FALSE. + * 3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is + * already set, therefore, it will not be modified. + * + * Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9. + * + * NOTE: + * The only fallback of this mechanism is if an admin updates a plugin based on use-case #2, + * and then, the next immediate PageView is the plugin's main settings page, it will not + * show the opt-in right away. The reason it will happen is because Freemius execution + * will be turned off till the plugin is fully loaded at least once + * (till $this->_storage->was_plugin_loaded is TRUE). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + */ + function _plugins_loaded() { + // Update flag that plugin was loaded with Freemius at least once. + $this->_storage->was_plugin_loaded = true; + + /** + * Bug fix - only set to false when it's a plugin, due to the + * execution sequence of the theme hooks and our methods, if + * this will be set for themes, Freemius will always assume + * it's a theme update. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.2 + */ + if ( $this->is_plugin() && + ! isset( $this->_storage->is_plugin_new_install ) + ) { + $this->_storage->is_plugin_new_install = ( + ! is_plugin_active( $this->_plugin_basename ) && + empty( $this->_storage->plugin_last_version ) + ); + } + } + + /** + * Add special parameter to WP admin AJAX calls so when we + * process AJAX calls we can identify its source properly. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + static function _enrich_ajax_url() { + $admin_param = is_network_admin() ? + '_fs_network_admin' : + '_fs_blog_admin'; + ?> + + + + _logger->entrance(); + + if ( is_admin() ) { + add_action( 'admin_init', array( &$this, '_hook_action_links_and_register_account_hooks' ) ); + + if ( $this->is_plugin() ) { + if ( self::is_plugin_install_page() && true !== fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) { + /** + * Unless the `fs_allow_updater_and_dialog` URL param exists and its value is `true`, make + * Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) + * so that they won't interfere with the .org plugins' functionalities on that page (e.g. + * updating of a .org plugin). + */ + add_filter( 'site_transient_update_plugins', array( 'Freemius', '_remove_fs_updates_from_plugin_install_page' ), 10, 2 ); + } else if ( self::is_plugins_page() || self::is_updates_page() ) { + /** + * On the "Plugins" and "Updates" admin pages, if there are premium or non–org-compliant plugins, modify their details dialog URLs (add a Freemius-specific param) so that the SDK can determine if the plugin information dialog should show information from Freemius. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + add_action( 'admin_footer', array( 'Freemius', '_prepend_fs_allow_updater_and_dialog_flag_url_param' ) ); + } + + $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; + + /** + * @since 1.2.2 + * + * Hook to both free and premium version activations to support + * auto deactivation on the other version activation. + */ + register_activation_hook( + $plugin_dir . $this->_free_plugin_basename, + array( &$this, '_activate_plugin_event_hook' ) + ); + + register_activation_hook( + $plugin_dir . $this->premium_plugin_basename(), + array( &$this, '_activate_plugin_event_hook' ) + ); + } else { + add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 ); + + add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) ); + } + + /** + * Part of the mechanism to identify new plugin install vs. plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + if ( empty( $this->_storage->was_plugin_loaded ) ) { + /** + * During the plugin activation (not theme), 'plugins_loaded' will be already executed + * when the logic gets here since the activation logic first add the activate plugins, + * then triggers 'plugins_loaded', and only then include the code of the plugin that + * is activated. Which means that _plugins_loaded() will NOT be executed during the + * plugin activation, and that IS intentional. + * + * @author Vova Feldman (@svovaf) + */ + if ( $this->is_plugin() && + $this->is_activation_mode( false ) && + 0 == did_action( 'plugins_loaded' ) + ) { + add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) ); + } else { + // If was activated before, then it was already loaded before. + $this->_plugins_loaded(); + } + } + + if ( ! self::is_ajax() ) { + if ( ! $this->is_addon() ) { + add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY ); + } + } + + if ( $this->_storage->handle_gdpr_admin_notice ) { + add_action( 'init', array( &$this, '_maybe_show_gdpr_admin_notice' ) ); + } + + add_action( 'init', array( &$this, '_maybe_add_gdpr_optin_ajax_handler') ); + add_action( 'init', array( &$this, '_maybe_add_pricing_ajax_handler' ) ); + } + + if ( $this->is_plugin() ) { + if ( $this->_is_network_active ) { + add_action( 'wpmu_new_blog', array( $this, '_after_new_blog_callback' ), 10, 6 ); + } + + register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) ); + } + + if ( is_multisite() ) { + add_action( 'deactivate_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'archive_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'make_spam_blog', array( &$this, '_after_site_deactivated_callback' ) ); + add_action( 'deleted_blog', array( &$this, '_after_site_deleted_callback' ), 10, 2 ); + + add_action( 'activate_blog', array( &$this, '_after_site_reactivated_callback' ) ); + add_action( 'unarchive_blog', array( &$this, '_after_site_reactivated_callback' ) ); + add_action( 'make_ham_blog', array( &$this, '_after_site_reactivated_callback' ) ); + } + + if ( $this->is_theme() && + self::is_customizer() && + $this->apply_filters( 'show_customizer_upsell', true ) + ) { + // Register customizer upsell. + add_action( 'customize_register', array( &$this, '_customizer_register' ) ); + } + + add_action( 'admin_init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY ); + + if ( $this->is_theme() && ! $this->is_migration() ) { + add_action( 'admin_init', array( &$this, '_add_tracking_links' ) ); + } + + add_action( 'admin_init', array( &$this, '_add_license_activation' ) ); + add_action( 'admin_init', array( &$this, '_add_premium_version_upgrade_selection' ) ); + add_action( 'admin_init', array( &$this, '_add_beta_mode_update_handler' ) ); + add_action( 'admin_init', array( &$this, '_add_user_change_option' ) ); + + $this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) ); + $this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) ); + $this->add_ajax_action( 'set_data_debug_mode', array( &$this, '_set_data_debug_mode' ) ); + $this->add_ajax_action( 'toggle_whitelabel_mode', array( &$this, '_toggle_whitelabel_mode_ajax_handler' ) ); + + if ( $this->_is_network_active && fs_is_network_admin() ) { + $this->add_ajax_action( 'network_activate', array( &$this, '_network_activate_ajax_action' ) ); + } + + $this->add_ajax_action( 'install_premium_version', array( + &$this, + '_install_premium_version_ajax_action' + ) ); + + $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) ); + + $this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) ); + + $this->add_action( 'sdk_version_update', array( &$this, '_sdk_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 ); + + $this->add_action( + 'plugin_version_update', + array( &$this, '_after_version_update' ), + WP_FS__DEFAULT_PRIORITY, + 2 + ); + $this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) ); + + add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); + add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); + add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); + + /** + * Handle request to reset anonymous mode for `get_reconnect_url()`. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + if ( fs_request_is_action( 'reset_anonymous_mode' ) && + $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) + ) { + add_action( 'admin_init', array( &$this, 'connect_again' ) ); + } + } + + /** + * Register the required hooks right after the settings parse is completed. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + */ + private function register_after_settings_parse_hooks() { + if ( is_admin() && + $this->is_theme() && + $this->is_premium() && + ! $this->has_active_valid_license() + ) { + $this->add_ajax_action( + 'delete_theme_update_data', + array( &$this, '_delete_theme_update_data_action' ) + ); + } + + if ( $this->show_settings_with_tabs() ) { + /** + * Include the required hooks to capture the theme settings' page tabs + * and cache them. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + if ( ! $this->_cache->has_valid( 'tabs' ) ) { + add_action( 'admin_footer', array( &$this, '_tabs_capture' ) ); + // Add license activation AJAX callback. + $this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) ); + + add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 ); + } + + add_action( + 'admin_footer', + array( &$this, '_add_freemius_tabs' ), + /** + * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()). + * That's why the priority is 11 while the tabs capture logic is added + * with priority 10. + * + * @author Vova Feldman (@svovaf) + */ + 11 + ); + } + + if ( ! self::is_ajax() ) { + if ( ! $this->is_addon() || $this->is_only_premium() ) { + add_action( + ( $this->_is_network_active && fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', + array( &$this, '_prepare_admin_menu' ), + WP_FS__LOWEST_PRIORITY + ); + } + } + } + + /** + * Makes Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) so that + * they won't interfere with the .org plugins' functionalities on that page (e.g. updating of a .org plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @param object $updates + * @param string|null $transient + * + * @return object + */ + static function _remove_fs_updates_from_plugin_install_page( $updates, $transient = null ) { + if ( is_object( $updates ) && isset( $updates->response ) ) { + foreach ( $updates->response as $file => $plugin ) { + if ( isset( $plugin->package ) && false !== strpos( $plugin->package, 'api.freemius' ) ) { + unset( $updates->response[ $file ] ); + } + } + } + + return $updates; + } + + /** + * Prepends the `fs_allow_updater_and_dialog` param to the plugin information URLs to tell the SDK to handle + * the information that is shown on the plugin details dialog that is shown when the relevant link is clicked. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return string + */ + static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { + $slug_basename_map = array(); + foreach ( self::$_instances as $instance ) { + if ( ! $instance->is_plugin() ) { + continue; + } + + $slug_basename_map[ $instance->get_slug() ] = $instance->premium_plugin_basename(); + } + ?> + + is_beta() ) { + $has_any_beta_version = true; + break; + } + } + + if ( $has_any_beta_version ) { + fs_enqueue_local_style( 'fs_plugins', '/admin/plugins.css' ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + static function _maybe_add_beta_label_to_plugins_and_handle_confirmation() { + $beta_data = array(); + + foreach ( self::$_instances as $instance ) { + if ( ! $instance->is_premium() ) { + continue; + } + + /** + * If there's an available beta version update, a confirmation message will be shown when the + * "Update now" link on the "Plugins" or "Themes" page is clicked. + */ + $has_beta_update = $instance->has_beta_update(); + + $is_beta = ( + // The "Beta" label is added separately for themes. + $instance->is_plugin() && + $instance->is_beta() + ); + + if ( ! $is_beta && ! $has_beta_update ) { + continue; + } + + $beta_data[ $instance->get_plugin_basename() ] = array( 'is_installed_version_beta' => $is_beta ); + + if ( ! $has_beta_update ) { + continue; + } + + $beta_data[ $instance->get_plugin_basename() ]['beta_version_update_confirmation_message'] = sprintf( + '%s %s', + sprintf( + fs_esc_attr_inline( + 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', + 'beta-version-update-caution', + $instance->get_slug() + ), + $instance->get_plugin_title() + ), + fs_esc_attr_inline( 'Would you like to proceed with the update?', 'update-confirmation', $instance->get_slug() ) + ); + } + + if ( empty( $beta_data ) ) { + return; + } + ?> + + _free_plugin_basename ] ); + unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] ); + + update_option( 'uninstall_plugins', $uninstallable_plugins ); + } + + /** + * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates. + * + * @param bool $store_prev_path + */ + private function clear_module_main_file_cache( $store_prev_path = true ) { + if ( ! isset( $this->_storage->plugin_main_file ) || + empty( $this->_storage->plugin_main_file->path ) + ) { + return; + } + + if ( ! $store_prev_path ) { + /** + * Storing the previous path is not needed when clearing the cache after an SDK version update since + * the main purpose of the cache clearing in that event is to correct a wrong plugin main file path + * which causes data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + unset( $this->_storage->plugin_main_file->path ); + } else { + $plugin_main_file = clone $this->_storage->plugin_main_file; + + // Store cached path (2nd layer cache). + $plugin_main_file->prev_path = $plugin_main_file->path; + + // Clear cached path. + unset( $plugin_main_file->path ); + + $this->_storage->plugin_main_file = $plugin_main_file; + } + + /** + * Clear global cached path. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' ); + unset( $id_slug_type_path_map[ $this->_module_id ]['path'] ); + self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + function _hook_action_links_and_register_account_hooks() { + if ( $this->is_migration() ) { + return; + } + + $this->_add_tracking_links(); + + if ( self::is_plugins_page() && $this->is_plugin() ) { + $this->hook_plugin_action_links(); + } + + $this->_register_account_hooks(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _register_account_hooks() { + if ( ! is_admin() ) { + return; + } + + /** + * Always show the deactivation feedback form since we added + * automatic free version deactivation upon premium code activation. + * + * @since 1.2.1.6 + */ + $this->add_ajax_action( + 'submit_uninstall_reason', + array( &$this, '_submit_uninstall_reason_action' ) + ); + + $this->add_ajax_action( + 'cancel_subscription_or_trial', + array( &$this, 'cancel_subscription_or_trial_ajax_action' ) + ); + + if ( ! $this->is_addon() || $this->is_parent_plugin_installed() ) { + if ( ( $this->is_plugin() && self::is_plugins_page() ) || + ( $this->is_theme() && self::is_themes_page() ) + ) { + add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) ); + } + } + } + + /** + * Leverage backtrace to find caller plugin file path. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool $is_init Is initiation sequence. + * + * @return string + */ + private function _find_caller_plugin_file( $is_init = false ) { + // Try to load the cached value of the file path. + if ( isset( $this->_storage->plugin_main_file ) ) { + $plugin_main_file = $this->_storage->plugin_main_file; + if ( ! empty( $plugin_main_file->path ) ) { + $absolute_path = $this->get_absolute_path( $plugin_main_file->path ); + if ( file_exists( $absolute_path ) ) { + return $absolute_path; + } + } + } + + /** + * @since 1.2.1 + * + * `clear_module_main_file_cache()` is clearing the plugin's cached path on + * deactivation. Therefore, if any plugin/theme was initiating `Freemius` + * with that plugin's slug, it was overriding the empty plugin path with a wrong path. + * + * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path` + * when the class instantiator isn't the module. + */ + if ( ! $is_init ) { + // Fetch prev path cache. + if ( isset( $this->_storage->plugin_main_file ) && + ! empty( $this->_storage->plugin_main_file->prev_path ) + ) { + $absolute_path = $this->get_absolute_path( $this->_storage->plugin_main_file->prev_path ); + if ( file_exists( $absolute_path ) ) { + return $absolute_path; + } + } + + wp_die( + $this->get_text_inline( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', 'failed-finding-main-path' ) . + " Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";", + $this->get_text_inline( 'Error', 'error' ), + array( 'back_link' => true ) + ); + } + + /** + * @since 1.2.1 + * + * Only the original instantiator that calls dynamic_init can modify the module's path. + */ + // Find caller module. + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_storage->plugin_main_file = (object) array( + 'path' => $id_slug_type_path_map[ $this->_module_id ]['path'], + ); + + return $this->get_absolute_path( $id_slug_type_path_map[ $this->_module_id ]['path'] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string $path + * + * @return string + */ + private function get_relative_path( $path ) { + $module_root_dir = $this->get_module_root_dir_path(); + if ( 0 === strpos( $path, $module_root_dir ) ) { + $path = substr( $path, strlen( $module_root_dir ) ); + } + + return $path; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string $path + * @param string|bool $module_type + * + * @return string + */ + private function get_absolute_path( $path, $module_type = false ) { + $module_root_dir = $this->get_module_root_dir_path( $module_type ); + if ( 0 !== strpos( $path, $module_root_dir ) ) { + $path = fs_normalize_path( $module_root_dir . $path ); + } + + return $path; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @param string|bool $module_type + * + * @return string + */ + private function get_module_root_dir_path( $module_type = false ) { + $is_plugin = empty( $module_type ) ? + $this->is_plugin() : + ( WP_FS__MODULE_TYPE_PLUGIN === $module_type ); + + return fs_normalize_path( trailingslashit( $is_plugin ? + WP_PLUGIN_DIR : + get_theme_root( get_stylesheet() ) ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param number $module_id + * @param string $slug + * + * @since 1.2.2 + */ + private function store_id_slug_type_path_map( $module_id, $slug ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + + $store_option = false; + + if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) { + $id_slug_type_path_map[ $module_id ] = array( + 'slug' => $slug + ); + + $store_option = true; + } else if ( + isset( $id_slug_type_path_map[ $module_id ]['slug'] ) && + $slug !== $id_slug_type_path_map[ $module_id ]['slug'] + ) { + $id_slug_type_path_map[ $module_id ]['slug'] = $slug; + $store_option = true; + } + + if ( empty( $id_slug_type_path_map[ $module_id ]['path'] ) || + /** + * This verification is for cases when suddenly the same module + * is installed but with a different folder name. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + */ + ! file_exists( $this->get_absolute_path( + $id_slug_type_path_map[ $module_id ]['path'], + $id_slug_type_path_map[ $module_id ]['type'] + ) ) + ) { + $caller_main_file_and_type = $this->get_caller_main_file_and_type(); + + $id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type; + $id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path; + + $store_option = true; + } + + if ( $store_option ) { + self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); + } + } + + /** + * Identifies the caller type: plugin or theme. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when + * add-ons are relying on loading the SDK from the parent module, and also allows themes including the + * SDK an internal file instead of directly from functions.php. + * @since 1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic. + */ + private function get_caller_main_file_and_type() { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins( true ); + $all_plugins_paths = array(); + + // Get active plugin's main files real full names (might be symlinks). + foreach ( $all_plugins as $relative_path => $data ) { + if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) { + /** + * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they + * can't really include the SDK. + * + * @author Vova Feldman + * @since 1.2.1.7 + */ + continue; + } + + $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); + } + + $caller_file_candidate = false; + $caller_map = array(); + $module_type = WP_FS__MODULE_TYPE_PLUGIN; + $themes_dir = fs_normalize_path( get_theme_root( get_stylesheet() ) ); + $plugin_dir_to_skip = false; + + for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { + if ( empty( $bt[ $i ]['file'] ) ) { + continue; + } + + if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) { + // If file same as the prev file in the stack, skip it. + continue; + } + + if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array( + 'do_action', + 'apply_filter', + // The string split is stupid, but otherwise, theme check + // throws info notices. + 'requir' . 'e_once', + 'requir' . 'e', + 'includ' . 'e_once', + 'includ' . 'e', + 'install_and_activate_plugin', + 'try_activate_plugin', + 'activate_plugin' + ) ) + ) { + if ( 'activate_plugin' === $bt[ $i ]['function'] ) { + /** + * Store the directory of the activator plugin so that any other file that starts with it + * cannot be mistakenly chosen as a candidate caller file. + * + * @author Leo Fajardo + * + * @since 2.3.0 + */ + $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); + + foreach ( $all_plugins_paths as $plugin_path ) { + $plugin_dir = fs_normalize_path( dirname( $plugin_path ) . '/' ); + if ( false !== strpos( $caller_file_path, $plugin_dir ) ) { + $plugin_dir_to_skip = $plugin_dir; + + break; + } + } + } + + // Ignore call stack hooks and files inclusion. + continue; + } + + $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); + + if ( ! empty( $plugin_dir_to_skip ) ) { + /** + * Skip if it's an activator plugin file to avoid mistakenly choosing it as a candidate caller file. + * + * @author Leo Fajardo + * + * @since 2.3.0 + */ + if ( 0 === strpos( $caller_file_path, $plugin_dir_to_skip ) ) { + continue; + } + } + + if ( 'functions.php' === basename( $caller_file_path ) ) { + /** + * 1. Assumes that theme's starting execution file is functions.php. + * 2. This complex logic fixes symlink issues (e.g. with Vargant). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + */ + + if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) { + $module_type = WP_FS__MODULE_TYPE_THEME; + + /** + * Relative path of the theme, e.g.: + * `my-theme/functions.php` + * + * @author Leo Fajardo (@leorw) + */ + $caller_file_candidate = basename( dirname( $caller_file_path ) ) . + '/' . + basename( $caller_file_path ); + + continue; + } + } + + $caller_file_hash = md5( $caller_file_path ); + + if ( ! isset( $caller_map[ $caller_file_hash ] ) ) { + foreach ( $all_plugins_paths as $plugin_path ) { + if ( empty( $plugin_path ) ) { + continue; + } + + if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) { + $caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path ); + break; + } + } + } + + if ( isset( $caller_map[ $caller_file_hash ] ) ) { + $module_type = WP_FS__MODULE_TYPE_PLUGIN; + $caller_file_candidate = plugin_basename( $caller_map[ $caller_file_hash ] ); + } + } + + return (object) array( + 'module_type' => $module_type, + 'path' => $caller_file_candidate + ); + } + + #---------------------------------------------------------------------------------- + #region Deactivation Feedback Form + #---------------------------------------------------------------------------------- + + /** + * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins + * page. + * + * @author Vova Feldman (@svovaf) + * @author Leo Fajardo (@leorw) + * + * @since 1.1.2 + */ + function _add_deactivation_feedback_dialog_box() { + $subscription_cancellation_dialog_box_template_params = $this->apply_filters( 'show_deactivation_subscription_cancellation', true ) ? + $this->_get_subscription_cancellation_dialog_box_template_params() : + array(); + + /** + * @since 2.3.0 Developers can optionally hide the deactivation feedback form using the 'show_deactivation_feedback_form' filter. + */ + $show_deactivation_feedback_form = true; + if ( $this->has_filter( 'show_deactivation_feedback_form' ) ) { + $show_deactivation_feedback_form = $this->apply_filters( 'show_deactivation_feedback_form', true ); + } else if ( $this->is_addon() ) { + /** + * If the add-on's 'show_deactivation_feedback_form' is not set, try to inherit the value from the parent. + */ + $show_deactivation_feedback_form = $this->get_parent_instance()->apply_filters( 'show_deactivation_feedback_form', true ); + } + + $uninstall_confirmation_message = $this->apply_filters( 'uninstall_confirmation_message', '' ); + + if ( + empty( $subscription_cancellation_dialog_box_template_params ) && + ! $show_deactivation_feedback_form && + empty( $uninstall_confirmation_message ) + ) { + return; + } + + $vars = array( 'id' => $this->_module_id ); + + if ( $show_deactivation_feedback_form ) { + /* Check the type of user: + * 1. Long-term (long-term) + * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term). + * 3. Short-term (short-term) + */ + $is_long_term_user = true; + + // Check if the site is at least 2 days old. + $time_installed = $this->_storage->install_timestamp; + + // Difference in seconds. + $date_diff = time() - $time_installed; + + // Convert seconds to days. + $date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) ); + + if ( $date_diff_days < 2 ) { + $is_long_term_user = false; + } + + $is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user ); + + if ( $is_long_term_user ) { + $user_type = 'long-term'; + } else { + if ( ! $this->is_registered() && ! $this->is_anonymous() ) { + $user_type = 'non-registered-and-non-anonymous-short-term'; + } else { + $user_type = 'short-term'; + } + } + + $uninstall_reasons = $this->_get_uninstall_reasons( $user_type ); + + $vars['reasons'] = $uninstall_reasons; + } + + $vars['subscription_cancellation_dialog_box_template_params'] = &$subscription_cancellation_dialog_box_template_params; + $vars['show_deactivation_feedback_form'] = $show_deactivation_feedback_form; + $vars['uninstall_confirmation_message'] = $uninstall_confirmation_message; + + /** + * Load the HTML template for the deactivation feedback dialog box. + * + * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin. + */ + fs_require_template( 'forms/deactivation/form.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @param string $user_type + * + * @return array The uninstall reasons for the specified user type. + */ + function _get_uninstall_reasons( $user_type = 'long-term' ) { + $module_type = $this->_module_type; + + $internal_message_template_var = array( + 'id' => $this->_module_id + ); + + $plan = $this->get_plan(); + + if ( $this->is_registered() && is_object( $plan ) && $plan->has_technical_support() ) { + $contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var ); + } else { + $contact_support_template = ''; + } + + $reason_found_better_plugin = array( + 'id' => self::REASON_FOUND_A_BETTER_PLUGIN, + 'text' => sprintf( $this->get_text_inline( 'I found a better %s', 'reason-found-a-better-plugin' ), $module_type ), + 'input_type' => 'textfield', + 'input_placeholder' => sprintf( $this->get_text_inline( "What's the %s's name?", 'placeholder-plugin-name' ), $module_type ), + ); + + $reason_temporary_deactivation = array( + 'id' => self::REASON_TEMPORARY_DEACTIVATION, + 'text' => sprintf( + $this->get_text_inline( "It's a temporary %s. I'm just debugging an issue.", 'reason-temporary-x' ), + strtolower( $this->is_plugin() ? + $this->get_text_inline( 'Deactivation', 'deactivation' ) : + $this->get_text_inline( 'Theme Switch', 'theme-switch' ) + ) + ), + 'input_type' => '', + 'input_placeholder' => '' + ); + + $reason_other = array( + 'id' => self::REASON_OTHER, + 'text' => $this->get_text_inline( 'Other', 'reason-other' ), + 'input_type' => 'textfield', + 'input_placeholder' => '' + ); + + $long_term_user_reasons = array( + array( + 'id' => self::REASON_NO_LONGER_NEEDED, + 'text' => sprintf( $this->get_text_inline( 'I no longer need the %s', 'reason-no-longer-needed' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + $reason_found_better_plugin, + array( + 'id' => self::REASON_NEEDED_FOR_A_SHORT_PERIOD, + 'text' => sprintf( $this->get_text_inline( 'I only needed the %s for a short period', 'reason-needed-for-a-short-period' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + array( + 'id' => self::REASON_BROKE_MY_SITE, + 'text' => sprintf( $this->get_text_inline( 'The %s broke my site', 'reason-broke-my-site' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ), + array( + 'id' => self::REASON_SUDDENLY_STOPPED_WORKING, + 'text' => sprintf( $this->get_text_inline( 'The %s suddenly stopped working', 'reason-suddenly-stopped-working' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ) + ); + + if ( $this->is_paying() ) { + $long_term_user_reasons[] = array( + 'id' => self::REASON_CANT_PAY_ANYMORE, + 'text' => $this->get_text_inline( "I can't pay for it anymore", 'reason-cant-pay-anymore' ), + 'input_type' => 'textfield', + 'input_placeholder' => $this->get_text_inline( 'What price would you feel comfortable paying?', 'placeholder-comfortable-price' ) + ); + } + + $reason_dont_share_info = array( + 'id' => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION, + 'text' => $this->get_text_inline( "I don't like to share my information with you", 'reason-dont-like-to-share-my-information' ), + 'input_type' => '', + 'input_placeholder' => '' + ); + + /** + * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the + * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in + * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is + * enabled and the user's account is currently not in pending activation state (similar to the way the Skip + * button in the opt-in form is shown/hidden). + */ + if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) { + $reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var ); + } + + $uninstall_reasons = array( + 'long-term' => $long_term_user_reasons, + 'non-registered-and-non-anonymous-short-term' => array( + array( + 'id' => self::REASON_DIDNT_WORK, + 'text' => sprintf( $this->get_text_inline( "The %s didn't work", 'reason-didnt-work' ), $module_type ), + 'input_type' => '', + 'input_placeholder' => '' + ), + $reason_dont_share_info, + $reason_found_better_plugin + ), + 'short-term' => array( + array( + 'id' => self::REASON_COULDNT_MAKE_IT_WORK, + 'text' => $this->get_text_inline( "I couldn't understand how to make it work", 'reason-couldnt-make-it-work' ), + 'input_type' => '', + 'input_placeholder' => '', + 'internal_message' => $contact_support_template + ), + $reason_found_better_plugin, + array( + 'id' => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE, + 'text' => sprintf( $this->get_text_inline( "The %s is great, but I need specific feature that you don't support", 'reason-great-but-need-specific-feature' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( 'What feature?', 'placeholder-feature' ) + ), + array( + 'id' => self::REASON_NOT_WORKING, + 'text' => sprintf( $this->get_text_inline( 'The %s is not working', 'reason-not-working' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( "Kindly share what didn't work so we can fix it for future users...", 'placeholder-share-what-didnt-work' ) + ), + array( + 'id' => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR, + 'text' => $this->get_text_inline( "It's not what I was looking for", 'reason-not-what-i-was-looking-for' ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( "What you've been looking for?", 'placeholder-what-youve-been-looking-for' ) + ), + array( + 'id' => self::REASON_DIDNT_WORK_AS_EXPECTED, + 'text' => sprintf( $this->get_text_inline( "The %s didn't work as expected", 'reason-didnt-work-as-expected' ), $module_type ), + 'input_type' => 'textarea', + 'input_placeholder' => $this->get_text_inline( 'What did you expect?', 'placeholder-what-did-you-expect' ) + ) + ) + ); + + // Randomize the reasons for the current user type. + shuffle( $uninstall_reasons[ $user_type ] ); + + // Keep the following reasons as the last items in the list. + $uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation; + $uninstall_reasons[ $user_type ][] = $reason_other; + + $uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons ); + + return $uninstall_reasons[ $user_type ]; + } + + /** + * Called after the user has submitted his reason for deactivating the plugin. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + */ + function _submit_uninstall_reason_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'submit_uninstall_reason' ); + + $reason_id = fs_request_get( 'reason_id' ); + + // Check if the given reason ID is an unsigned integer. + if ( ! ctype_digit( $reason_id ) ) { + exit; + } + + $reason_info = trim( fs_request_get( 'reason_info', '' ) ); + if ( ! empty( $reason_info ) ) { + $reason_info = substr( $reason_info, 0, 128 ); + } + + $reason = (object) array( + 'id' => $reason_id, + 'info' => $reason_info, + 'is_anonymous' => fs_request_get_bool( 'is_anonymous' ) + ); + + $this->_storage->store( 'uninstall_reason', $reason ); + + /** + * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do + * not support uninstall hook. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( $this->is_theme() ) { + if ( $this->is_premium() && ! $this->has_active_valid_license() ) { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + $this->_uninstall_plugin_event( false ); + $this->remove_sdk_reference(); + } + + // Print '1' for successful operation. + echo 1; + exit; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + */ + function cancel_subscription_or_trial_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'cancel_subscription_or_trial' ); + + $result = $this->cancel_subscription_or_trial( fs_request_get( 'plugin_id', $this->get_id() ), false ); + + if ( $this->is_api_error( $result ) ) { + $this->shoot_ajax_failure( $result->error->message ); + } + + $this->shoot_ajax_success(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param number $plugin_id + * + * @return object + */ + private function cancel_subscription_or_trial( $plugin_id ) { + $fs = null; + if ( $plugin_id == $this->get_id() ) { + $fs = $this; + } else if ( $this->is_addon_activated( $plugin_id ) ) { + $fs = self::get_instance_by_id( $plugin_id ); + } + + $result = null; + + if ( ! is_null( $fs ) ) { + $result = $fs->is_paid_trial() ? + $fs->_cancel_trial() : + $fs->_downgrade_site(); + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _delete_theme_update_data_action() { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Instance + #---------------------------------------------------------------------------------- + + /** + * Main singleton instance. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param number $module_id + * @param string|bool $slug + * @param bool $is_init Is initiation sequence. + * + * @return Freemius|false + */ + static function instance( $module_id, $slug = false, $is_init = false ) { + if ( empty( $module_id ) ) { + return false; + } + + /** + * Load the essential static data prior to initiating FS_Plugin_Manager since there's an essential MS network migration logic that needs to be executed prior to the initiation. + */ + self::_load_required_static(); + + if ( ! is_numeric( $module_id ) ) { + if ( ! $is_init && true === $slug ) { + $is_init = true; + } + + $slug = $module_id; + + $module = FS_Plugin_Manager::instance( $slug )->get(); + + if ( is_object( $module ) ) { + $module_id = $module->id; + } + } + + $key = 'm_' . $module_id; + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init ); + } + + return self::$_instances[ $key ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $addon_id + * + * @return bool + */ + private static function has_instance( $addon_id ) { + return isset( self::$_instances[ 'm_' . $addon_id ] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string|number $id_or_slug + * @param string $module_type + * + * @return number|false + */ + private static function get_module_id( $id_or_slug, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { + if ( is_numeric( $id_or_slug ) ) { + return $id_or_slug; + } + + foreach ( self::$_instances as $instance ) { + // Also check the module type since there can be a plugin and a theme with the same slug. + if ( ( $module_type === $instance->get_module_type() ) && ( $id_or_slug === $instance->get_slug() ) ) { + return $instance->get_id(); + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $id + * + * @return false|Freemius + */ + static function get_instance_by_id( $id ) { + return isset ( self::$_instances[ 'm_' . $id ] ) ? + self::$_instances[ 'm_' . $id ] : + false; + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $plugin_file + * @param string $module_type + * + * @return false|Freemius + */ + static function get_instance_by_file( $plugin_file, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { + $slug = self::find_slug_by_basename( $plugin_file ); + + return ( false !== $slug ) ? + self::instance( self::get_module_id( $slug, $module_type ) ) : + false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return false|Freemius + */ + function get_parent_instance() { + return self::get_instance_by_id( $this->_plugin->parent_plugin_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return false|Freemius + */ + function get_addon_instance( $id_or_slug ) { + $addon_id = self::get_module_id( $id_or_slug ); + + return self::instance( $addon_id ); + } + + #endregion ------------------------------------------------------------------ + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_parent_plugin_installed() { + $is_active = self::has_instance( $this->_plugin->parent_plugin_id ); + + if ( $is_active ) { + return true; + } + + /** + * Parent module might be a theme. If that's the case, the add-on's FS + * instance will be loaded prior to the theme's FS instance, therefore, + * we need to check if it's active with a "look ahead". + * + * @author Vova Feldman + * @since 1.2.2.3 + */ + global $fs_active_plugins; + if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) { + $active_theme = wp_get_theme(); + + foreach ( $fs_active_plugins->plugins as $sdk => $module ) { + if ( WP_FS__MODULE_TYPE_THEME === $module->type ) { + if ( $module->plugin_path == $active_theme->get_stylesheet() ) { + // Parent module is a theme and it's currently active. + return true; + } + } + } + } + + return false; + } + + /** + * Check if add-on parent plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_parent_in_activation() { + $parent_fs = $this->get_parent_instance(); + if ( ! is_object( $parent_fs ) ) { + return false; + } + + return ( $parent_fs->is_activation_mode() ); + } + + /** + * Is plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $and_on + * + * @return bool + */ + function is_activation_mode( $and_on = true ) { + return fs_is_network_admin() ? + $this->is_network_activation_mode( $and_on ) : + $this->is_site_activation_mode( $and_on ); + } + + /** + * Is plugin in activation mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $and_on + * + * @return bool + */ + function is_site_activation_mode( $and_on = true ) { + return ( + ( $this->is_on() || ! $and_on ) && + ( + ( $this->is_premium() && true === $this->_storage->require_license_activation ) || + ( + ( ! $this->is_registered() || + ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) && + ( ! $this->is_enable_anonymous() || + ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) ) + ) + ) + ); + } + + /** + * Checks if the SDK in network activation mode. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $and_on + * + * @return bool + */ + private function is_network_activation_mode( $and_on = true ) { + if ( ! $this->_is_network_active ) { + // Not network activated. + return false; + } + + if ( $this->is_network_upgrade_mode() ) { + // Special flag to enforce network activation mode to decide what to do with the sites that are not yet opted-in nor skipped. + return true; + } + + if ( ! $this->is_site_activation_mode( $and_on ) ) { + // Whether the context is single site or the network, if the plugin is no longer in activation mode then it is not in network activation mode as well. + return false; + } + + if ( $this->is_network_delegated_connection() ) { + // Super-admin delegated the connection to the site admins -> not activation mode. + return false; + } + + if ( $this->is_network_anonymous() && true !== $this->_storage->require_license_activation ) { + // Super-admin skipped the connection network wide -> not activation mode. + return false; + } + + if ( $this->is_network_registered() ) { + // Super-admin connected at least one site -> not activation mode. + return false; + } + + return true; + } + + /** + * Check if current page is the opt-in/pending-activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return bool + */ + function is_activation_page() { + if ( $this->_menu->is_activation_page( $this->show_opt_in_on_themes_page() ) ) { + return true; + } + + if ( ! $this->is_activation_mode() ) { + return false; + } + + // Check if current page is matching the activation page. + return $this->is_matching_url( $this->get_activation_url() ); + } + + /** + * Check if URL path's are matching and that all querystring + * arguments of the $sub_url exist in the $url with the same values. + * + * WARNING: + * 1. This method doesn't check if the sub/domain are matching. + * 2. Ignore case sensitivity. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $sub_url + * @param string $url If argument is not set, check if the sub_url matching the current's page URL. + * + * @return bool + */ + private function is_matching_url( $sub_url, $url = '' ) { + if ( empty( $url ) ) { + $url = $_SERVER['REQUEST_URI']; + } + + $url = strtolower( $url ); + $sub_url = strtolower( $sub_url ); + + if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) { + // Different path - DO NOT OVERRIDE PAGE. + return false; + } + + $url_params = array(); + parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params ); + + $sub_url_params = array(); + parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params ); + + foreach ( $sub_url_params as $key => $val ) { + if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) { + // Not matching query string - DO NOT OVERRIDE PAGE. + return false; + } + } + + return true; + } + + /** + * Get the basenames of all active plugins for specific blog. Including network activated plugins. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return string[] + */ + private static function get_active_plugins_basenames( $blog_id = 0 ) { + if ( is_multisite() && $blog_id > 0 ) { + $active_basenames = get_blog_option( $blog_id, 'active_plugins' ); + } else { + $active_basenames = get_option( 'active_plugins' ); + } + + if ( ! is_array( $active_basenames ) ) { + $active_basenames = array(); + } + + if ( is_multisite() ) { + $network_active_basenames = get_site_option( 'active_sitewide_plugins' ); + + if ( is_array( $network_active_basenames ) && ! empty( $network_active_basenames ) ) { + $active_basenames = array_merge( $active_basenames, array_keys( $network_active_basenames ) ); + } + } + + return $active_basenames; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param int $blog_id + * + * @return array + */ + static function get_active_plugins_directories_map( $blog_id = 0 ) { + $active_basenames = self::get_active_plugins_basenames( $blog_id ); + + $map = array(); + + foreach ( $active_basenames as $active_basename ) { + $active_basename = fs_normalize_path( $active_basename ); + + if ( false === strpos( $active_basename, '/' ) ) { + continue; + } + + $map[ dirname( $active_basename ) ] = true; + } + + return $map; + } + + /** + * Get collection of all active plugins. Including network activated plugins. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param int $blog_id Since 2.0.0 + * + * @return array[string]array + */ + private static function get_active_plugins( $blog_id = 0 ) { + self::require_plugin_essentials(); + + $active_plugin = array(); + $all_plugins = fs_get_plugins(); + $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); + + foreach ( $active_plugins_basenames as $plugin_basename ) { + $active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ]; + } + + return $active_plugin; + } + + /** + * Get collection of all site active plugins for a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return array[string]array + */ + private static function get_site_active_plugins( $blog_id = 0 ) { + $active_basenames = ( is_multisite() && $blog_id > 0 ) ? + get_blog_option( $blog_id, 'active_plugins' ) : + get_option( 'active_plugins' ); + + $active = array(); + + if ( ! is_array( $active_basenames ) ) { + return $active; + } + + foreach ( $active_basenames as $basename ) { + $active[ $basename ] = array( + 'is_active' => true, + 'Version' => '1.0', // Dummy version. + 'slug' => self::get_plugin_slug( $basename ), + ); + } + + return $active; + } + + /** + * Get collection of all plugins with their activation status for a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @param int $blog_id Since 2.0.0 + * + * @return array Key is the plugin file path and the value is an array of the plugin data. + */ + private static function get_all_plugins( $blog_id = 0 ) { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins(); + + $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); + + foreach ( $all_plugins as $basename => &$data ) { + // By default set to inactive (next foreach update the active plugins). + $data['is_active'] = false; + // Enrich with plugin slug. + $data['slug'] = self::get_plugin_slug( $basename ); + } + + // Flag active plugins. + foreach ( $active_plugins_basenames as $basename ) { + if ( isset( $all_plugins[ $basename ] ) ) { + $all_plugins[ $basename ]['is_active'] = true; + } + } + + return $all_plugins; + } + + /** + * Get collection of all plugins and if they are network level activated. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array Key is the plugin basename and the value is an array of the plugin data. + */ + private static function get_network_plugins() { + self::require_plugin_essentials(); + + $all_plugins = fs_get_plugins(); + + $network_active_basenames = is_multisite() ? + get_site_option( 'active_sitewide_plugins' ) : + array(); + + foreach ( $all_plugins as $basename => &$data ) { + // By default set to inactive (next foreach update the active plugins). + $data['is_active'] = false; + // Enrich with plugin slug. + $data['slug'] = self::get_plugin_slug( $basename ); + } + + // Flag active plugins. + foreach ( $network_active_basenames as $basename ) { + if ( isset( $all_plugins[ $basename ] ) ) { + $all_plugins[ $basename ]['is_active'] = true; + } + } + + return $all_plugins; + } + + /** + * Cached result of get_site_transient( 'update_plugins' ) + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @var object + */ + private static $_plugins_info; + + /** + * Helper function to get specified plugin's slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @param $basename + * + * @return string + */ + private static function get_plugin_slug( $basename ) { + if ( ! isset( self::$_plugins_info ) ) { + self::$_plugins_info = get_site_transient( 'update_plugins' ); + } + + $slug = ''; + + if ( is_object( self::$_plugins_info ) ) { + if ( isset( self::$_plugins_info->no_update ) && + isset( self::$_plugins_info->no_update[ $basename ] ) && + ! empty( self::$_plugins_info->no_update[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->no_update[ $basename ]->slug; + } else if ( isset( self::$_plugins_info->response ) && + isset( self::$_plugins_info->response[ $basename ] ) && + ! empty( self::$_plugins_info->response[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->response[ $basename ]->slug; + } + } + + if ( empty( $slug ) ) { + // Try to find slug from FS data. + $slug = self::find_slug_by_basename( $basename ); + } + + if ( empty( $slug ) ) { + // Fallback to plugin's folder name. + $slug = dirname( $basename ); + } + + return $slug; + } + + private static $_statics_loaded = false; + + /** + * Load static resources. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private static function _load_required_static() { + if ( self::$_statics_loaded ) { + return; + } + + self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + self::$_static_logger->entrance(); + + self::$_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); + + if ( is_multisite() ) { + $has_skipped_migration = ( + // 'id_slug_type_path_map' - was never stored on older versions, therefore, not exists on the site level. + null === self::$_accounts->get_option( 'id_slug_type_path_map', null, false ) && + // 'file_slug_map' stored on the site level, so it was running an SDK version before it was integrated with MS-network. + null !== self::$_accounts->get_option( 'file_slug_map', null, false ) + ); + + /** + * If the file_slug_map exists on the site level but doesn't exist on the + * network level storage, it means that we need to process the storage with migration. + * + * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, file_slug_map will be already set in the network level storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( + ( $has_skipped_migration && true !== self::$_accounts->get_option( 'ms_migration_complete', false, true ) ) || + ( null === self::$_accounts->get_option( 'file_slug_map', null, true ) && + null !== self::$_accounts->get_option( 'file_slug_map', null, false ) ) + ) { + self::migrate_options_to_network(); + } + } + + self::$_global_admin_notices = FS_Admin_Notices::instance( 'global' ); + + if ( ! WP_FS__DEMO_MODE ) { + add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( + 'Freemius', + '_add_debug_section' + ) ); + } + + add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) ); + + self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) ); + + self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) ); + + self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) ); + + if ( 0 == did_action( 'plugins_loaded' ) ) { + add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 ); + } + + add_action( 'admin_footer', array( 'Freemius', '_enrich_ajax_url' ) ); + add_action( 'admin_footer', array( 'Freemius', '_open_support_forum_in_new_page' ) ); + + if ( self::is_plugins_page() || self::is_themes_page() ) { + add_action( 'admin_print_footer_scripts', array( 'Freemius', '_maybe_add_beta_label_styles' ), 9 ); + + /** + * Specifically use this hook so that the JS event handlers will work properly on the "Themes" + * page. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + add_action( 'admin_footer-' . self::get_current_page(), array( 'Freemius', '_maybe_add_beta_label_to_plugins_and_handle_confirmation') ); + } + + self::$_statics_loaded = true; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 2.1.3 + */ + private static function migrate_options_to_network() { + self::migrate_accounts_to_network(); + + // Migrate API options from site level to network level. + $api_network_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true ); + $api_network_options->migrate_to_network(); + + // Migrate API cache to network level storage. + FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME )->migrate_to_network(); + + self::$_accounts->set_option( 'ms_migration_complete', true, true ); + } + + #---------------------------------------------------------------------------------- + #region Localization + #---------------------------------------------------------------------------------- + + /** + * Load framework's text domain. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + */ + static function _load_textdomain() { + if ( ! is_admin() ) { + return; + } + + global $fs_active_plugins; + + // Works both for plugins and themes. + load_plugin_textdomain( + 'freemius', + false, + $fs_active_plugins->newest->sdk_path . '/languages/' + ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Debugging + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _add_debug_section() { + if ( ! is_super_admin() ) { + // Add debug page only for super-admins. + return; + } + + self::$_static_logger->entrance(); + + $title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION ); + + if ( WP_FS__DEV_MODE ) { + // Add top-level debug menu item. + $hook = FS_Admin_Menu_Manager::add_page( + $title, + $title, + 'manage_options', + 'freemius', + array( 'Freemius', '_debug_page_render' ) + ); + } else { + // Add hidden debug page. + $hook = FS_Admin_Menu_Manager::add_subpage( + null, + $title, + $title, + 'manage_options', + 'freemius', + array( 'Freemius', '_debug_page_render' ) + ); + } + + if ( ! empty( $hook ) ) { + add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + static function _toggle_debug_mode() { + check_admin_referer( 'fs_toggle_debug_mode' ); + + if ( ! is_super_admin() ) { + return; + } + + $is_on = fs_request_get( 'is_on', false, 'post' ); + + if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) { + update_option( 'fs_debug_mode', $is_on ); + + // Turn on/off storage logging. + FS_Logger::_set_storage_logging( ( 1 == $is_on ) ); + } + + exit; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + static function _get_debug_log() { + check_admin_referer( 'fs_get_debug_log' ); + + if ( ! is_super_admin() ) { + return; + } + + $limit = min( ! empty( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 200, 200 ); + $offset = min( ! empty( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 200, 200 ); + + $logs = FS_Logger::load_db_logs( + fs_request_get( 'filters', false, 'post' ), + $limit, + $offset + ); + + self::shoot_ajax_success( $logs ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + static function _get_db_option() { + check_admin_referer( 'fs_get_db_option' ); + + $option_name = fs_request_get( 'option_name' ); + + if ( ! is_super_admin() || + ! fs_starts_with( $option_name, 'fs_' ) + ) { + self::shoot_ajax_failure(); + } + + $value = get_option( $option_name ); + + $result = array( + 'name' => $option_name, + ); + + if ( false !== $value ) { + if ( ! is_string( $value ) ) { + $value = json_encode( $value ); + } + + $result['value'] = $value; + } + + self::shoot_ajax_success( $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + static function _set_db_option() { + check_admin_referer( 'fs_set_db_option' ); + + $option_name = fs_request_get( 'option_name' ); + + if ( ! is_super_admin() || + ! fs_starts_with( $option_name, 'fs_' ) + ) { + self::shoot_ajax_failure(); + } + + $option_value = fs_request_get( 'option_value' ); + + if ( ! empty( $option_value ) ) { + update_option( $option_name, $option_value ); + } + + self::shoot_ajax_success(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _debug_page_actions() { + self::_clean_admin_content_section(); + + if ( fs_request_is_action( 'restart_freemius' ) ) { + check_admin_referer( 'restart_freemius' ); + + if ( ! is_multisite() ) { + // Clear accounts data. + self::$_accounts->clear( null, true ); + } else { + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + self::$_accounts->clear( $blog_id, true ); + } + + // Clear network level storage. + self::$_accounts->clear( true, true ); + } + + // Clear SDK reference cache. + delete_option( 'fs_active_plugins' ); + } else if ( fs_request_is_action( 'clear_updates_data' ) ) { + check_admin_referer( 'clear_updates_data' ); + + if ( ! is_multisite() ) { + set_site_transient( 'update_plugins', null ); + set_site_transient( 'update_themes', null ); + } else { + $current_blog_id = get_current_blog_id(); + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + switch_to_blog( self::get_site_blog_id( $site ) ); + + set_site_transient( 'update_plugins', null ); + set_site_transient( 'update_themes', null ); + } + + switch_to_blog( $current_blog_id ); + } + } else if ( fs_request_is_action( 'simulate_trial' ) ) { + check_admin_referer( 'simulate_trial' ); + + $fs = freemius( fs_request_get( 'module_id' ) ); + + // Update SDK install to at least 24 hours before. + $fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC ); + // Unset the trial shown timestamp. + unset( $fs->_storage->trial_promotion_shown ); + } else if ( fs_request_is_action( 'simulate_network_upgrade' ) ) { + check_admin_referer( 'simulate_network_upgrade' ); + + $fs = freemius( fs_request_get( 'module_id' ) ); + + self::set_network_upgrade_mode( $fs->_storage ); + } else if ( fs_request_is_action( 'delete_install' ) ) { + check_admin_referer( 'delete_install' ); + + self::_delete_site_by_slug( + fs_request_get( 'slug' ), + fs_request_get( 'module_type' ), + true, + fs_request_get( 'blog_id', null ) + ); + } else if ( fs_request_is_action( 'delete_user' ) ) { + check_admin_referer( 'delete_user' ); + + self::delete_user( fs_request_get( 'user_id' ) ); + } else if ( fs_request_is_action( 'download_logs' ) ) { + check_admin_referer( 'download_logs' ); + + $download_url = FS_Logger::download_db_logs( + fs_request_get( 'filters', false, 'post' ) + ); + + if ( false === $download_url ) { + wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact support@freemius.com.' ); + } + + fs_redirect( $download_url ); + } else if ( fs_request_is_action( 'migrate_options_to_network' ) ) { + check_admin_referer( 'migrate_options_to_network' ); + + self::migrate_options_to_network(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _debug_page_render() { + self::$_static_logger->entrance(); + + if ( ! is_multisite() ) { + $all_plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); + $all_themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME ); + } else { + $sites = self::get_sites(); + + $all_plugins_installs = array(); + $all_themes_installs = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); + + foreach ( $plugins_installs as $slug => $install ) { + if ( ! isset( $all_plugins_installs[ $slug ] ) ) { + $all_plugins_installs[ $slug ] = array(); + } + + $install->blog_id = $blog_id; + + $all_plugins_installs[ $slug ][] = $install; + } + + $themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ); + + foreach ( $themes_installs as $slug => $install ) { + if ( ! isset( $all_themes_installs[ $slug ] ) ) { + $all_themes_installs[ $slug ] = array(); + } + + $install->blog_id = $blog_id; + + $all_themes_installs[ $slug ][] = $install; + } + } + } + + $licenses_by_module_type = self::get_all_licenses_by_module_type(); + + $vars = array( + 'plugin_sites' => $all_plugins_installs, + 'theme_sites' => $all_themes_installs, + 'users' => self::get_all_users(), + 'addons' => self::get_all_addons(), + 'account_addons' => self::get_all_account_addons(), + 'plugin_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_PLUGIN ], + 'theme_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_THEME ] + ); + + fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' ); + fs_require_once_template( 'debug.php', $vars ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Issues + #---------------------------------------------------------------------------------- + + /** + * Check if Freemius should be turned on for the current plugin install. + * + * Note: + * $this->_is_on is updated in has_api_connectivity() + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_on() { + self::$_static_logger->entrance(); + + if ( isset( $this->_is_on ) ) { + return $this->_is_on; + } + + // If already installed or pending then sure it's on :) + if ( $this->is_registered() || $this->is_pending_activation() ) { + $this->_is_on = true; + + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $flush_if_no_connectivity + * + * @return bool + */ + private function should_run_connectivity_test( $flush_if_no_connectivity = false ) { + if ( ! isset( $this->_storage->connectivity_test ) ) { + // Connectivity test was never executed, or cache was cleared. + return true; + } + + if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) { + if ( WP_FS__IS_HTTP_REQUEST ) { + if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) { + // Domain changed. + return true; + } + + if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) { + // Server IP changed. + return true; + } + } + } + + if ( $this->_storage->connectivity_test['is_connected'] && + $this->_storage->connectivity_test['is_active'] + ) { + // API connected and Freemius is active - no need to run connectivity check. + return false; + } + + if ( $flush_if_no_connectivity ) { + /** + * If explicitly asked to flush when no connectivity - do it only + * if at least 10 sec passed from the last API connectivity test. + */ + return ( isset( $this->_storage->connectivity_test['timestamp'] ) && + ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 ); + } + + /** + * @since 1.1.7 Don't check for connectivity on plugin downgrade. + */ + $version = $this->get_plugin_version(); + if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) { + // If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test. + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param int|null $blog_id Since 2.0.0. + * @param bool $is_gdpr_test Since 2.0.2. Perform only the GDPR test. + * + * @return object|false + */ + private function ping( $blog_id = null, $is_gdpr_test = false ) { + if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) { + return false; + } + + $version = $this->get_plugin_version(); + + $is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ); + + return $this->get_api_plugin_scope()->ping( + $this->get_anonymous_id( $blog_id ), + array( + 'is_update' => json_encode( $is_update ), + 'version' => $version, + 'sdk' => $this->version, + 'is_admin' => json_encode( is_admin() ), + 'is_ajax' => json_encode( self::is_ajax() ), + 'is_cron' => json_encode( self::is_cron() ), + 'is_gdpr_test' => $is_gdpr_test, + 'is_http' => json_encode( WP_FS__IS_HTTP_REQUEST ), + ) + ); + } + + /** + * Check if there's any connectivity issue to Freemius API. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $flush_if_no_connectivity + * + * @return bool + */ + function has_api_connectivity( $flush_if_no_connectivity = false ) { + $this->_logger->entrance(); + + if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) { + return $this->_has_api_connection; + } + + if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY && + isset( $this->_storage->connectivity_test ) && + true === $this->_storage->connectivity_test['is_connected'] + ) { + unset( $this->_storage->connectivity_test ); + } + + if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { + $this->_has_api_connection = $this->_storage->connectivity_test['is_connected']; + /** + * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration. + * + * @since 1.2.1.5 If the user running the premium version then ignore the 'is_active' flag and turn Freemius on to enable license key activation. + */ + $this->_is_on = $this->_storage->connectivity_test['is_active'] || + $this->is_premium() || + ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF ); + + return $this->_has_api_connection; + } + + $pong = $this->ping(); + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( ! $is_connected ) { + // API failure. + $this->_add_connectivity_issue_message( $pong ); + } + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + } + + $this->store_connectivity_info( $pong, $is_connected ); + + return $this->_has_api_connection; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param object $pong + * @param bool $is_connected + */ + private function store_connectivity_info( $pong, $is_connected ) { + $this->_logger->entrance(); + + $version = $this->get_plugin_version(); + + if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { + $is_active = false; + } else { + $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); + } + + $is_active = $this->apply_filters( + 'is_on', + $is_active, + $this->is_plugin_update(), + $version + ); + + $this->_storage->connectivity_test = array( + 'is_connected' => $is_connected, + 'host' => $_SERVER['HTTP_HOST'], + 'server_ip' => WP_FS__REMOTE_ADDR, + 'is_active' => $is_active, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + // Last version with connectivity attempt. + 'version' => $version, + ); + + $this->_has_api_connection = $is_connected; + $this->_is_on = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF ); + } + + /** + * Force turning Freemius on. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @return bool TRUE if successfully turned on. + */ + private function turn_on() { + $this->_logger->entrance(); + + if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) { + return false; + } + + $updated_connectivity = $this->_storage->connectivity_test; + $updated_connectivity['is_active'] = true; + $updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME; + $this->_storage->connectivity_test = $updated_connectivity; + + $this->_is_on = true; + + return true; + } + + /** + * Anonymous and unique site identifier (Hash). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param null|int $blog_id Since 2.0.0 + * + * @return string + */ + function get_anonymous_id( $blog_id = null ) { + $unique_id = self::$_accounts->get_option( 'unique_id', null, $blog_id ); + + if ( empty( $unique_id ) || ! is_string( $unique_id ) ) { + $key = fs_strip_url_protocol( get_site_url( $blog_id ) ); + + $secure_auth = SECURE_AUTH_KEY; + if ( empty( $secure_auth ) || + false !== strpos( $secure_auth, ' ' ) || + 'put your unique phrase here' === $secure_auth + ) { + // Protect against default auth key. + $secure_auth = md5( microtime() ); + } + + /** + * Base the unique identifier on the WP secure authentication key. Which + * turns the key into a secret anonymous identifier. This will help us + * to avoid duplicate installs generation on the backend upon opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + */ + $unique_id = md5( $key . $secure_auth ); + + self::$_accounts->set_option( 'unique_id', $unique_id, true, $blog_id ); + } + + $this->_logger->departure( $unique_id ); + + return $unique_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @return \WP_User + */ + static function _get_current_wp_user() { + self::require_pluggable_essentials(); + self::wp_cookie_constants(); + + return wp_get_current_user(); + } + + /** + * Define cookie constants which are required by Freemius::_get_current_wp_user() since + * it uses wp_get_current_user() which needs the cookie constants set. When a plugin + * is network activated the cookie constants are only configured after the network + * plugins activation, therefore, if we don't define those constants WP will throw + * PHP warnings/notices. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.1 + */ + private static function wp_cookie_constants() { + if ( defined( 'LOGGED_IN_COOKIE' ) && + ( defined( 'AUTH_COOKIE' ) || defined( 'SECURE_AUTH_COOKIE' ) ) + ) { + return; + } + + /** + * Used to guarantee unique hash cookies + * + * @since 1.5.0 + */ + if ( ! defined( 'COOKIEHASH' ) ) { + $siteurl = get_site_option( 'siteurl' ); + if ( $siteurl ) { + define( 'COOKIEHASH', md5( $siteurl ) ); + } else { + define( 'COOKIEHASH', '' ); + } + } + + if ( ! defined( 'LOGGED_IN_COOKIE' ) ) { + define( 'LOGGED_IN_COOKIE', 'wordpress_logged_in_' . COOKIEHASH ); + } + + /** + * @since 2.5.0 + */ + if ( ! defined( 'AUTH_COOKIE' ) ) { + define( 'AUTH_COOKIE', 'wordpress_' . COOKIEHASH ); + } + + /** + * @since 2.6.0 + */ + if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) { + define( 'SECURE_AUTH_COOKIE', 'wordpress_sec_' . COOKIEHASH ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return int + */ + static function get_current_wp_user_id() { + $wp_user = self::_get_current_wp_user(); + + return $wp_user->ID; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $email + * + * @return bool + */ + static function is_valid_email( $email ) { + if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { + return false; + } + + $parts = explode( '@', $email ); + + if ( 2 !== count( $parts ) || empty( $parts[1] ) ) { + return false; + } + + $blacklist = array( + 'admin.', + 'webmaster.', + 'localhost.', + 'dev.', + 'development.', + 'test.', + 'stage.', + 'staging.', + ); + + // Make sure domain is not one of the blacklisted. + foreach ( $blacklist as $invalid ) { + if ( 0 === strpos( $parts[1], $invalid ) ) { + return false; + } + } + + // Get the UTF encoded domain name. + $domain = idn_to_ascii( $parts[1] ) . '.'; + + return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); + } + + /** + * Generate API connectivity issue message. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param mixed $api_result + * @param bool $is_first_failure + */ + function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) { + if ( ! $this->is_premium() && $this->_enable_anonymous ) { + // Don't add message if it's the free version and can run anonymously. + return; + } + + if ( ! function_exists( 'wp_nonce_url' ) ) { + require_once ABSPATH . 'wp-includes/functions.php'; + } + + $current_user = self::_get_current_wp_user(); +// $admin_email = get_option( 'admin_email' ); + $admin_email = $current_user->user_email; + + // Aliases. + $deactivate_plugin_title = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' ); + $deactivate_plugin_desc = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' ); + $install_previous_title = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' ); + $install_previous_desc = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' ); + $fix_issue_title = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' ); + $fix_issue_desc = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' ); + /* translators: %s: product title (e.g. "Awesome Plugin" requires an access to...) */ + $x_requires_access_to_api = $this->esc_html_inline( '%s requires an access to our API.', 'x-requires-access-to-api' ); + $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' ); + $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' ); + + $message = false; + if ( is_object( $api_result ) && + isset( $api_result->error ) && + isset( $api_result->error->code ) + ) { + switch ( $api_result->error->code ) { + case 'curl_missing': + $missing_methods = ''; + if ( is_array( $api_result->missing_methods ) && + ! empty( $api_result->missing_methods ) + ) { + foreach ( $api_result->missing_methods as $m ) { + if ( 'curl_version' === $m ) { + continue; + } + + if ( ! empty( $missing_methods ) ) { + $missing_methods .= ', '; + } + + $missing_methods .= sprintf( '%s', $m ); + } + + if ( ! empty( $missing_methods ) ) { + $missing_methods = sprintf( + '

    %s %s', + $this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ), + $missing_methods + ); + } + } + + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' . + $missing_methods . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '

    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ), + ' - ' . sprintf( + $this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ), + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + $sysadmin_title, + esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) ) + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; + case 'cloudflare_ddos_protection': + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' . + $happy_to_resolve_issue_asap . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $fix_issue_title, + ' - ' . sprintf( + $fix_issue_desc, + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), + $install_previous_title, + $install_previous_desc + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; + case 'squid_cache_block': + $message = sprintf( + $x_requires_access_to_api . ' ' . + $this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s - %s', + $this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ), + sprintf( + $this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ), + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + $sysadmin_title, + sprintf( + $this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ), + // We use a filter since the plugin might require additional API connectivity. + '' . implode( ', ', $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com' + ) ) ) . '', + $this->_module_type + ) + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + break; +// default: +// $message = $this->get_text_inline( 'connectivity-test-fails-message' ); +// break; + } + } + + $message_id = 'failed_connect_api'; + $type = 'error'; + + $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' ); + + if ( false === $message ) { + if ( $is_first_failure ) { + // First attempt failed. + $message = sprintf( + $x_requires_access_to_api . ' ' . + $connectivity_test_fails_message . ' ' . + $this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '

    ' . + '%s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    %s %s
    ', + sprintf( + '%s', + $this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' ) + ), + sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $this->get_text_inline( 'No - just deactivate', 'no-deactivate' ) + ) + ) + ); + + $message_id = 'failed_connect_api_first'; + $type = 'promotion'; + } else { + // Second connectivity attempt failed. + $message = sprintf( + $x_requires_access_to_api . ' ' . + $connectivity_test_fails_message . ' ' . + $happy_to_resolve_issue_asap . + ' %s', + '' . $this->get_plugin_name() . '', + sprintf( + '
    1. %s
    2. %s
    3. %s
    ', + sprintf( + '%s%s', + $fix_issue_title, + ' - ' . sprintf( + $fix_issue_desc, + '' . $admin_email . '' + ) + ), + sprintf( + '%s - %s', + sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), + $install_previous_title, + $install_previous_desc + ), + sprintf( + '%s - %s', + wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), + $deactivate_plugin_title, + $deactivate_plugin_desc + ) + ) + ); + } + } + + $this->_admin_notices->add_sticky( + $message, + $message_id, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + $type + ); + } + + /** + * Handle user request to resolve connectivity issue. + * This method will send an email to Freemius API technical staff for resolution. + * The email will contain server's info and installed plugins (might be caching issue). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _email_about_firewall_issue() { + check_admin_referer( 'fs_resolve_firewall_issues' ); + + if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { + return; + } + + $this->_admin_notices->remove_sticky( 'failed_connect_api' ); + + $pong = $this->ping(); + + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + $this->store_connectivity_info( $pong, $is_connected ); + + echo $this->get_after_plugin_activation_redirect_url(); + exit; + } + + $current_user = self::_get_current_wp_user(); + $admin_email = $current_user->user_email; + + $error_type = fs_request_get( 'error_type', 'general' ); + + switch ( $error_type ) { + case 'squid': + $title = 'Squid ACL Blocking Issue'; + break; + case 'cloudflare': + $title = 'CloudFlare Blocking Issue'; + break; + default: + $title = 'API Connectivity Issue'; + break; + } + + $custom_email_sections = array(); + + // Add 'API Error' custom email section. + $custom_email_sections['api_error'] = array( + 'title' => 'API Error', + 'rows' => array( + 'ping' => array( + 'API Error', + is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong ) + ), + ) + ); + + // Send email with technical details to resolve API connectivity issues. + $this->send_email( + 'api@freemius.com', // recipient + $title . ' [' . $this->get_plugin_name() . ']', // subject + $custom_email_sections, + array( "Reply-To: $admin_email <$admin_email>" ) // headers + ); + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ), + '' . $admin_email . '' + ), + 'server_details_sent' + ); + + // Action was taken, tell that API connectivity troubleshooting should be off now. + + echo "1"; + exit; + } + + /** + * Handle connectivity test retry approved by the user. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + */ + function _retry_connectivity_test() { + check_admin_referer( 'fs_retry_connectivity_test' ); + + if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { + return; + } + + $this->_admin_notices->remove_sticky( 'failed_connect_api_first' ); + + $pong = $this->ping(); + + $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + + if ( $is_connected ) { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + $this->store_connectivity_info( $pong, $is_connected ); + + echo $this->get_after_plugin_activation_redirect_url(); + } else { + // Add connectivity issue message after 2nd failed attempt. + $this->_add_connectivity_issue_message( $pong, false ); + + echo "1"; + } + + exit; + } + + static function _add_firewall_issues_javascript() { + $params = array(); + fs_require_once_template( 'firewall-issues-js.php', $params ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Email + #---------------------------------------------------------------------------------- + + /** + * Generates and sends an HTML email with customizable sections. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @param string $to_address + * @param string $subject + * @param array $sections + * @param array $headers + * + * @return bool Whether the email contents were sent successfully. + */ + private function send_email( + $to_address, + $subject, + $sections = array(), + $headers = array() + ) { + $default_sections = $this->get_email_sections(); + + // Insert new sections or replace the default email sections. + if ( is_array( $sections ) && ! empty( $sections ) ) { + foreach ( $sections as $section_id => $custom_section ) { + if ( ! isset( $default_sections[ $section_id ] ) ) { + // If the section does not exist, add it. + $default_sections[ $section_id ] = $custom_section; + } else { + // If the section already exists, override it. + $current_section = $default_sections[ $section_id ]; + + // Replace the current section's title if a custom section title exists. + if ( isset( $custom_section['title'] ) ) { + $current_section['title'] = $custom_section['title']; + } + + // Insert new rows under the current section or replace the default rows. + if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) { + foreach ( $custom_section['rows'] as $row_id => $row ) { + $current_section['rows'][ $row_id ] = $row; + } + } + + $default_sections[ $section_id ] = $current_section; + } + } + } + + $vars = array( 'sections' => $default_sections ); + $message = fs_get_template( 'email.php', $vars ); + + // Set the type of email to HTML. + $headers[] = 'Content-type: text/html; charset=UTF-8'; + + $header_string = implode( "\r\n", $headers ); + + return wp_mail( + $to_address, + $subject, + $message, + $header_string + ); + } + + /** + * Generates the data for the sections of the email content. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.2 + * + * @return array + */ + private function get_email_sections() { + // Retrieve the current user's information so that we can get the user's email, first name, and last name below. + $current_user = self::_get_current_wp_user(); + + // Retrieve the cURL version information so that we can get the version number below. + $curl_version_information = curl_version(); + + $active_plugin = self::get_active_plugins(); + + // Generate the list of active plugins separated by new line. + $active_plugin_string = ''; + foreach ( $active_plugin as $plugin ) { + $active_plugin_string .= sprintf( + '%s [v%s]
    ', + $plugin['PluginURI'], + $plugin['Name'], + $plugin['Version'] + ); + } + + $server_ip = WP_FS__REMOTE_ADDR; + + // Add PHP info for deeper investigation. + ob_start(); + phpinfo(); + $php_info = ob_get_clean(); + + $api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 ); + + // Generate the default email sections. + $sections = array( + 'sdk' => array( + 'title' => 'SDK', + 'rows' => array( + 'fs_version' => array( 'FS Version', $this->version ), + 'curl_version' => array( 'cURL Version', $curl_version_information['version'] ) + ) + ), + 'plugin' => array( + 'title' => ucfirst( $this->get_module_type() ), + 'rows' => array( + 'name' => array( 'Name', $this->get_plugin_name() ), + 'version' => array( 'Version', $this->get_plugin_version() ) + ) + ), + 'api' => array( + 'title' => 'API Subdomain', + 'rows' => array( + 'dns' => array( + 'DNS_CNAME', + function_exists( 'dns_get_record' ) ? + var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) : + 'dns_get_record() disabled/blocked' + ), + 'ip' => array( + 'IP', + function_exists( 'gethostbyname' ) ? + gethostbyname( $api_domain ) : + 'gethostbyname() disabled/blocked' + ), + ), + ), + 'site' => array( + 'title' => 'Site', + 'rows' => array( + 'unique_id' => array( 'Unique ID', $this->get_anonymous_id() ), + 'address' => array( 'Address', site_url() ), + 'host' => array( + 'HTTP_HOST', + ( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) + ), + 'hosting' => array( + 'Hosting Company' => fs_request_has( 'hosting_company' ) ? + fs_request_get( 'hosting_company' ) : + 'Unknown', + ), + 'server_addr' => array( + 'SERVER_ADDR', + '' . $server_ip . '' + ) + ) + ), + 'user' => array( + 'title' => 'User', + 'rows' => array( + 'email' => array( 'Email', $current_user->user_email ), + 'first' => array( 'First', $current_user->user_firstname ), + 'last' => array( 'Last', $current_user->user_lastname ) + ) + ), + 'plugins' => array( + 'title' => 'Plugins', + 'rows' => array( + 'active_plugins' => array( 'Active Plugins', $active_plugin_string ) + ) + ), + 'php_info' => array( + 'title' => 'PHP Info', + 'rows' => array( + 'info' => array( $php_info ) + ), + ) + ); + + // Allow the sections to be modified by other code. + $sections = $this->apply_filters( 'email_template_sections', $sections ); + + return $sections; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Initialization + #---------------------------------------------------------------------------------- + + /** + * Init plugin's Freemius instance. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param number $id + * @param string $public_key + * @param bool $is_live + * @param bool $is_premium + */ + function init( $id, $public_key, $is_live = true, $is_premium = true ) { + $this->_logger->entrance(); + + $this->dynamic_init( array( + 'id' => $id, + 'public_key' => $public_key, + 'is_live' => $is_live, + 'is_premium' => $is_premium, + ) ); + } + + /** + * Dynamic initiator, originally created to support initiation + * with parent_id for add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param array $plugin_info + * + * @throws Freemius_Exception + */ + function dynamic_init( array $plugin_info ) { + $this->_logger->entrance(); + + $this->parse_settings( $plugin_info ); + + $this->register_after_settings_parse_hooks(); + + if ( $this->should_stop_execution() ) { + return; + } + + if ( ! $this->is_registered() ) { + if ( $this->is_anonymous() ) { + // If user skipped, no need to test connectivity. + $this->_has_api_connection = true; + $this->_is_on = true; + } else { + if ( ! $this->has_api_connectivity() ) { + if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) || + $this->_admin_notices->has_sticky( 'failed_connect_api' ) + ) { + if ( ! $this->_enable_anonymous || $this->is_premium() ) { + // If anonymous mode is disabled, add firewall admin-notice message. + add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) ); + + $ajax_action_suffix = $this->_slug . ( $this->is_theme() ? ':theme' : '' ); + add_action( "wp_ajax_fs_resolve_firewall_issues_{$ajax_action_suffix}", array( + &$this, + '_email_about_firewall_issue' + ) ); + + add_action( "wp_ajax_fs_retry_connectivity_test_{$ajax_action_suffix}", array( + &$this, + '_retry_connectivity_test' + ) ); + + /** + * Currently the admin notice manager relies on the module's type and slug. The new AJAX actions manager uses module IDs, hence, consider to replace the if block above with the commented code below after adjusting the admin notices manager to work with module IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + /*$this->add_ajax_action( 'resolve_firewall_issues', array( + &$this, + '_email_about_firewall_issue' + ) ); + + $this->add_ajax_action( 'retry_connectivity_test', array( + &$this, + '_retry_connectivity_test' + ) );*/ + } + } + + return; + } else { + $this->_admin_notices->remove_sticky( array( + 'failed_connect_api_first', + 'failed_connect_api', + ) ); + + if ( $this->_anonymous_mode ) { + // Simulate anonymous mode. + $this->_is_anonymous = true; + } + } + } + } + + /** + * This should be executed even if Freemius is off for the core module, + * otherwise, the add-ons dialogbox won't work properly. This is esepcially + * relevant when the developer decided to turn FS off for existing users. + * + * @author Vova Feldman (@svovaf) + */ + if ( $this->is_user_in_admin() && + 'plugin-information' === fs_request_get( 'tab', false ) && + $this->should_use_freemius_updater_and_dialog() && + ( + ( $this->is_addon() && $this->get_slug() == fs_request_get( 'plugin', false ) ) || + ( $this->has_addons() && $this->get_id() == fs_request_get( 'parent_plugin_id', false ) ) + ) + ) { + require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php'; + + new FS_Plugin_Info_Dialog( $this->is_addon() ? $this->get_parent_instance() : $this ); + } + + // Check if Freemius is on for the current plugin. + // This MUST be executed after all the plugin variables has been loaded. + if ( ! $this->is_registered() && ! $this->is_on() ) { + return; + } + + if ( $this->has_api_connectivity() ) { + if ( self::is_cron() ) { + $this->hook_callback_to_sync_cron(); + } else if ( $this->is_user_in_admin() ) { + /** + * Schedule daily data sync cron if: + * + * 1. User opted-in (for tracking). + * 2. If skipped, but later upgraded (opted-in via upgrade). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + */ + if ( $this->is_registered() ) { + if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) { + $this->schedule_sync_cron(); + } + } + + /** + * Check if requested for manual blocking background sync. + */ + if ( fs_request_has( 'background_sync' ) ) { + $this->run_manual_sync(); + } + } + } + + if ( $this->is_registered() ) { + $this->hook_callback_to_install_sync(); + } + + if ( $this->is_addon() ) { + if ( $this->is_parent_plugin_installed() ) { + // Link to parent FS. + $this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id ); + + // Get parent plugin reference. + $this->_parent_plugin = $this->_parent->get_plugin(); + } + } + + if ( $this->is_user_in_admin() ) { + if ( $this->is_addon() ) { + if ( ! $this->is_parent_plugin_installed() ) { + $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); + + if ( isset( $plugin_info['parent'] ) ) { + $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); + } + + $this->_admin_notices->add( + ( ! empty( $parent_name ) ? + sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) : + sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() ) + ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + + return; + } else { + $is_network_admin = fs_is_network_admin(); + + if ( ! $this->_parent->is_registered() && $this->is_registered() ) { + // If add-on activated and parent not, automatically install parent for the user. + $this->activate_parent_account( $this->_parent ); + } else if ( + $this->_parent->is_registered() && + ! $this->is_registered() && + /** + * If not registered for add-on and the following conditions for the add-on are met, activate add-on account. + * * Network active and in network admin - network activate add-on account. + * * Network active and not in network admin - activate add-on account for the current blog. + * * Not network active and not in network admin - activate add-on account for the current blog. + * + * If not registered for add-on, not network active, and in network admin, do not handle the add-on activation. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + ( $this->is_network_active() || ! $is_network_admin ) + ) { + $premium_license = null; + + if ( + ! $this->has_free_plan() && + $this->is_bundle_license_auto_activation_enabled() && + $this->_parent->is_activated_with_bundle_license() + ) { + /** + * If the add-on has no free plan, try to activate the account only when there's a bundle license. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + */ + $bundle_license = $this->get_active_parent_license( $this->_parent->_get_license()->secret_key, false ); + + if ( + is_object( $bundle_license ) && + ! empty( $bundle_license->products ) && + in_array( $this->get_id(), $bundle_license->products ) + ) { + $premium_license = $bundle_license; + } + } + + if ( $this->has_free_plan() || is_object( $premium_license) ) { + // If parent plugin activated, automatically install add-on for the user. + $this->_activate_addon_account( + $this->_parent, + ( $this->is_network_active() && $is_network_admin ) ? + true : + get_current_blog_id(), + $premium_license + ); + } + } + + // @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic. + if ( $this->is_premium() ) { + // Remove add-on download admin-notice. + $this->_parent->_admin_notices->remove_sticky( array( + 'addon_plan_upgraded_' . $this->_slug, + 'no_addon_license_' . $this->_slug, + ) ); + } + +// $this->deactivate_premium_only_addon_without_license(); + } + } + + add_action( 'admin_init', array( &$this, '_admin_init_action' ) ); + +// if ( $this->is_registered() || +// $this->is_anonymous() || +// $this->is_pending_activation() +// ) { +// $this->_init_admin(); +// } + } + + /** + * Should be called outside `$this->is_user_in_admin()` scope + * because the updater has some logic that needs to be executed + * during AJAX calls. + * + * Currently we need to hook to the `http_request_host_is_external` filter. + * In the future, there might be additional logic added. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + if ( + $this->should_use_freemius_updater_and_dialog() && + ( + $this->is_premium() || + /** + * If not premium but the premium version is installed, also instantiate the updater so that the + * plugin information dialog of the premium version will have the information from the server. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) ) + ) && + $this->has_release_on_freemius() + ) { + FS_Plugin_Updater::instance( $this ); + } + + $this->do_action( 'initiated' ); + + if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) { + if ( isset( $this->_storage->prev_is_premium ) ) { + $this->apply_filters( + 'after_code_type_change', + // New code type. + $this->_plugin->is_premium + ); + } else { + // Set for code type for the first time. + $this->_storage->prev_is_premium = $this->_plugin->is_premium; + } + } + + if ( ! $this->is_addon() ) { + if ( $this->is_registered() ) { + // Fix for upgrade from versions < 1.0.9. + if ( ! isset( $this->_storage->activation_timestamp ) ) { + $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; + } + + $this->do_action( 'after_init_plugin_registered' ); + } else if ( $this->is_anonymous() ) { + $this->do_action( 'after_init_plugin_anonymous' ); + } else if ( $this->is_pending_activation() ) { + $this->do_action( 'after_init_plugin_pending_activations' ); + } + } else { + if ( $this->is_registered() ) { + $this->do_action( 'after_init_addon_registered' ); + } else if ( $this->is_anonymous() ) { + $this->do_action( 'after_init_addon_anonymous' ); + } else if ( $this->is_pending_activation() ) { + $this->do_action( 'after_init_addon_pending_activations' ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return bool + */ + private function should_use_freemius_updater_and_dialog() { + return ( + /** + * Allow updater and dialog when the `fs_allow_updater_and_dialog` URL query param exists and has `true` + * value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action` + * URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts + * with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing + * plugin details from .org). + */ + ( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) || + ( + ! self::is_plugin_install_page() && + // Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected. + ( 'install-plugin' !== fs_request_get( 'action' ) ) + ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.1.5 + */ + function _stop_tracking_callback() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'stop_tracking' ); + + $result = $this->stop_tracking( fs_is_network_admin() ); + + if ( true === $result ) { + self::shoot_ajax_success(); + } + + $this->_logger->api_error( $result ); + + self::shoot_ajax_failure( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . + ( $this->is_api_error( $result ) && isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _allow_tracking_callback() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'allow_tracking' ); + + $result = $this->allow_tracking( fs_is_network_admin() ); + + if ( true === $result ) { + self::shoot_ajax_success(); + } + + $this->_logger->api_error( $result ); + + self::shoot_ajax_failure( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . + ( $this->is_api_error( $result ) && isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) ) + ); + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function stop_site_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + if ( $this->is_tracking_prohibited() ) { + // Already disconnected. + return true; + } + + // Send update to FS. + $result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array( + 'is_disconnected' => true + ) ); + + if ( ! $this->is_api_result_entity( $result ) || + ! isset( $result->is_disconnected ) || + ! $result->is_disconnected + ) { + $this->_logger->api_error( $result ); + + return $result; + } + + $this->_site->is_disconnected = $result->is_disconnected; + $this->_store_site(); + + $this->clear_sync_cron(); + + // Successfully disconnected. + return true; + } + + /** + * Opt-out network from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function stop_network_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + $install_id_2_blog_id = array(); + $installs_map = $this->get_blog_install_map(); + + $opt_out_all = true; + + $params = array(); + foreach ( $installs_map as $blog_id => $install ) { + if ( $install->is_tracking_prohibited() ) { + // Already opted-out. + continue; + } + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + // Opt-out only from non-delegated installs. + $opt_out_all = false; + continue; + } + + $params[] = array( 'id' => $install->id ); + + $install_id_2_blog_id[ $install->id ] = $blog_id; + } + + if ( empty( $install_id_2_blog_id ) ) { + return true; + } + + $params[] = array( 'is_disconnected' => true ); + + // Send update to FS. + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + $this->_logger->api_error( $result ); + + return $result; + } + + foreach ( $result->installs as $r_install ) { + $blog_id = $install_id_2_blog_id[ $r_install->id ]; + $install = $installs_map[ $blog_id ]; + $install->is_disconnected = $r_install->is_disconnected; + $this->_store_site( true, $blog_id, $install ); + } + + $this->clear_sync_cron( $opt_out_all ); + + // Successfully disconnected. + return true; + } + + /** + * Opt-out from usage tracking. + * + * Note: This will not delete the account information but will stop all tracking. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-out. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_network_action + * + * @return bool|object + */ + function stop_tracking( $is_network_action = false ) { + $this->_logger->entrance(); + + return $is_network_action ? + $this->stop_network_tracking() : + $this->stop_site_tracking(); + } + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function allow_site_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + if ( $this->is_tracking_allowed() ) { + // Tracking already allowed. + return true; + } + + $result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array( + 'is_disconnected' => false + ) ); + + if ( ! $this->is_api_result_entity( $result ) || + ! isset( $result->is_disconnected ) || + $result->is_disconnected + ) { + $this->_logger->api_error( $result ); + + return $result; + } + + $this->_site->is_disconnected = $result->is_disconnected; + $this->_store_site(); + + $this->schedule_sync_cron(); + + // Successfully reconnected. + return true; + } + + /** + * Opt-in network back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool|object + */ + function allow_network_tracking() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // User never opted-in. + return false; + } + + $install_id_2_blog_id = array(); + $installs_map = $this->get_blog_install_map(); + + $params = array(); + foreach ( $installs_map as $blog_id => $install ) { + if ( $install->is_tracking_allowed() ) { + continue; + } + + $params[] = array( 'id' => $install->id ); + + $install_id_2_blog_id[ $install->id ] = $blog_id; + } + + if ( empty( $install_id_2_blog_id ) ) { + return true; + } + + $params[] = array( 'is_disconnected' => false ); + + // Send update to FS. + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + $this->_logger->api_error( $result ); + + return $result; + } + + foreach ( $result->installs as $r_install ) { + $blog_id = $install_id_2_blog_id[ $r_install->id ]; + $install = $installs_map[ $blog_id ]; + $install->is_disconnected = $r_install->is_disconnected; + $this->_store_site( true, $blog_id, $install ); + } + + $this->schedule_sync_cron(); + + // Successfully reconnected. + return true; + } + + /** + * Opt-in back into usage tracking. + * + * Note: This will only work if the user opted-in previously. + * + * Returns: + * 1. FALSE - If the user never opted-in. + * 2. TRUE - If successfully opted-in back to usage tracking. + * 3. object - API result on failure. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_network_action + * + * @return bool|object + */ + function allow_tracking( $is_network_action = false ) { + $this->_logger->entrance(); + + return $is_network_action ? + $this->allow_network_tracking() : + $this->allow_site_tracking(); + } + + /** + * If user opted-in and later disabled usage-tracking, + * re-allow tracking for licensing and updates. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @param bool $is_context_single_site + */ + private function reconnect_locally( $is_context_single_site = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return; + } + + if ( ! fs_is_network_admin() || $is_context_single_site ) { + if ( $this->is_tracking_prohibited() ) { + $this->_site->is_disconnected = false; + $this->_store_site(); + } + } else { + $installs_map = $this->get_blog_install_map(); + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_tracking_prohibited() ) { + $install->is_disconnected = false; + $this->_store_site( true, $blog_id, $install ); + } + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.2 + * + * @return bool + */ + function is_extensions_tracking_allowed() { + return ( true === $this->apply_filters( + 'is_extensions_tracking_allowed', + $this->_storage->get( 'is_extensions_tracking_allowed', null ) + ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.2 + */ + function _update_tracking_permission_callback() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'update_tracking_permission' ); + + $is_enabled = fs_request_get_bool( 'is_enabled', null ); + + if ( ! is_bool( $is_enabled ) ) { + self::shoot_ajax_failure(); + } + + $permission = fs_request_get( 'permission' ); + + switch ( $permission ) { + case 'extensions': + $this->update_extensions_tracking_flag( $is_enabled ); + break; + default: + $permission = 'no_match'; + } + + if ( 'no_match' === $permission ) { + self::shoot_ajax_failure(); + } + + self::shoot_ajax_success( array( + 'permissions' => array( + $permission => $is_enabled, + ) + ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @param bool|null $is_enabled + */ + function update_extensions_tracking_flag( $is_enabled ) { + if ( is_bool( $is_enabled ) ) { + $this->_storage->store( 'is_extensions_tracking_allowed', $is_enabled ); + } + } + + /** + * Parse plugin's settings (as defined by the plugin dev). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param array $plugin_info + * + * @throws \Freemius_Exception + */ + private function parse_settings( &$plugin_info ) { + $this->_logger->entrance(); + + $id = $this->get_numeric_option( $plugin_info, 'id', false ); + $public_key = $this->get_option( $plugin_info, 'public_key', false ); + $secret_key = $this->get_option( $plugin_info, 'secret_key', null ); + $parent_id = $this->get_numeric_option( $plugin_info, 'parent_id', null ); + $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 Try to pull secret key from external config. + */ + if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) { + $secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" ); + } + + if ( isset( $plugin_info['parent'] ) ) { + $parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null ); +// $parent_slug = $this->get_option( $plugin_info['parent'], 'slug', null ); +// $parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null ); +// $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); + } + + if ( false === $id ) { + throw new Freemius_Exception( array( + 'error' => array( + 'type' => 'ParameterNotSet', + 'message' => 'Plugin id parameter is not set.', + 'code' => 'plugin_id_not_set', + 'http' => 500, + ) + ) ); + } + if ( false === $public_key ) { + throw new Freemius_Exception( array( + 'error' => array( + 'type' => 'ParameterNotSet', + 'message' => 'Plugin public_key parameter is not set.', + 'code' => 'plugin_public_key_not_set', + 'http' => 500, + ) + ) ); + } + + $plugin = ( $this->_plugin instanceof FS_Plugin ) ? + $this->_plugin : + new FS_Plugin(); + + $premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' ); + + $plugin->update( array( + 'id' => $id, + 'type' => $this->get_option( $plugin_info, 'type', $this->_module_type ), + 'public_key' => $public_key, + 'slug' => $this->_slug, + 'premium_slug' => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ), + 'parent_plugin_id' => $parent_id, + 'version' => $this->get_plugin_version(), + 'title' => $this->get_plugin_name( $premium_suffix ), + 'file' => $this->_plugin_basename, + 'is_premium' => $this->get_bool_option( $plugin_info, 'is_premium', true ), + 'premium_suffix' => $premium_suffix, + 'is_live' => $this->get_bool_option( $plugin_info, 'is_live', true ), + 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ), + 'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ), + 'bundle_public_key' => $this->get_option( $plugin_info, 'bundle_public_key', null ), + ) ); + + if ( $plugin->is_updated() ) { + // Update plugin details. + $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin ); + } + // Set the secret key after storing the plugin, we don't want to store the key in the storage. + $this->_plugin->secret_key = $secret_key; + + /** + * If the product is network integrated and activated and the current view is in the network level Admin dashboard, if the product's network-level menu located differently from the sub-site level, then use the network menu details (when set). + * + * @author Vova Feldman + * @since 2.4.5 + */ + if ( $this->is_network_active() && fs_is_network_admin() ) { + if ( isset( $plugin_info['menu_network'] ) && + is_array( $plugin_info['menu_network'] ) && + ! empty( $plugin_info['menu_network'] ) + ) { + $plugin_info['menu'] = $plugin_info['menu_network']; + } + } + + if ( ! isset( $plugin_info['menu'] ) ) { + $plugin_info['menu'] = array(); + + if ( ! empty( $this->_storage->sdk_last_version ) && + version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' ) + ) { + // Backward compatibility to 1.1.2 + $plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ? + $plugin_info['menu_slug'] : + $this->_slug; + } + } + + $this->_menu = FS_Admin_Menu_Manager::instance( + $this->_module_id, + $this->_module_type, + $this->get_unique_affix() + ); + + $this->_menu->init( $plugin_info['menu'], $this->is_addon() ); + + $this->_has_addons = $this->get_bool_option( $plugin_info, 'has_addons', false ); + $this->_has_paid_plans = $this->get_bool_option( $plugin_info, 'has_paid_plans', true ); + $this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans ); + $this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false ); + $this->_is_org_compliant = $this->get_bool_option( $plugin_info, 'is_org_compliant', true ); + $this->_is_premium_only = $this->get_bool_option( $plugin_info, 'is_premium_only', false ); + if ( $this->_is_premium_only ) { + // If premium only plugin, disable anonymous mode. + $this->_enable_anonymous = false; + $this->_anonymous_mode = false; + } else { + $this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true ); + $this->_anonymous_mode = $this->get_bool_option( $plugin_info, 'anonymous_mode', false ); + } + $this->_permissions = $this->get_option( $plugin_info, 'permissions', array() ); + $this->_is_bundle_license_auto_activation_enabled = $this->get_option( $plugin_info, 'bundle_license_auto_activation', false ); + + if ( ! empty( $plugin_info['trial'] ) ) { + $this->_trial_days = $this->get_numeric_option( + $plugin_info['trial'], + 'days', + // Default to 0 - trial without days specification. + 0 + ); + + $this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false ); + } + + $this->_navigation = $this->get_option( + $plugin_info, + 'navigation', + $this->is_free_wp_org_theme() ? + self::NAVIGATION_TABS : + self::NAVIGATION_MENU + ); + } + + /** + * @param string[] $options + * @param string $key + * @param mixed $default + * + * @return bool + */ + private function get_option( &$options, $key, $default = false ) { + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_bool_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_numeric_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default; + } + + /** + * Gate keeper. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function should_stop_execution() { + if ( empty( $this->_storage->was_plugin_loaded ) ) { + /** + * Don't execute Freemius until plugin was fully loaded at least once, + * to give the opportunity for the activation hook to run before pinging + * the API for connectivity test. This logic is relevant for the + * identification of new plugin install vs. plugin update. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + return true; + } + + if ( $this->is_activation_mode() ) { + if ( ! is_admin() ) { + /** + * If in activation mode, don't execute Freemius outside of the + * admin dashboard. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + + if ( ! WP_FS__IS_HTTP_REQUEST ) { + /** + * If in activation and executed without HTTP context (e.g. CLI, Cronjob), + * then don't start Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6.3 + * + * @link https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli + */ + return true; + } + + if ( self::is_cron() ) { + /** + * If in activation mode, don't execute Freemius during wp crons + * (wp crons have HTTP context - called as HTTP request). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + + if ( self::is_ajax() && + ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) && + ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) + ) { + /** + * During activation, if running in AJAX mode, unless there's a sticky + * connectivity issue notice, don't run Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + return true; + } + } + + return false; + } + + /** + * Triggered after code type has changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9.1 + */ + function _after_code_type_change() { + $this->_logger->entrance(); + + if ( $this->is_theme() ) { + // Expire the cache of the previous tabs since the theme may + // have setting updates after code type has changed. + $this->_cache->expire( 'tabs' ); + $this->_cache->expire( 'tabs_stylesheets' ); + } + + if ( $this->is_registered() ) { + if ( ! $this->is_addon() ) { + add_action( + is_admin() ? 'admin_init' : 'init', + array( &$this, '_plugin_code_type_changed' ) + ); + } + + if ( $this->is_premium() ) { + // Purge cached payments after switching to the premium version. + // @todo This logic doesn't handle purging the cache for serviceware module upgrade. + $this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" ); + } + } + } + + /** + * Handles plugin's code type change (free <--> premium). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _plugin_code_type_changed() { + $this->_logger->entrance(); + + if ( $this->is_premium() ) { + $this->reconnect_locally(); + + // Activated premium code. + $this->do_action( 'after_premium_version_activation' ); + + // Remove all sticky messages related to download of the premium version. + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'plan_upgraded', + 'plan_changed', + 'license_activated', + ) ); + + $notice = ''; + if ( ! $this->is_only_premium() ) { + $notice = sprintf( $this->get_text_inline( 'Premium %s version was successfully activated.', 'premium-activated-message' ), $this->_module_type ); + } + + $license_notice = $this->get_license_network_activation_notice(); + if ( ! empty( $license_notice ) ) { + $notice .= ' ' . $license_notice; + } + + if ( ! empty( $notice ) ) { + $this->_admin_notices->add_sticky( + trim( $notice ), + 'premium_activated', + $this->get_text_x_inline( 'W00t', + 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' + ); + } + } else { + // Remove sticky message related to premium code activation. + $this->_admin_notices->remove_sticky( 'premium_activated' ); + + // Activated free code (after had the premium before). + $this->do_action( 'after_free_version_reactivation' ); + + if ( $this->is_paying() && ! $this->is_premium() ) { + $this->_admin_notices->add_sticky( + sprintf( + /* translators: %s: License type (e.g. you have a professional license) */ + $this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ), + $this->get_plan_title() + ) . $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + + // Schedule code type changes event. + $this->schedule_install_sync(); + + /** + * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid + * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version + * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been + * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since + * the main file of the "free" version will be loaded first before calling the hooked callback. Since the + * free and premium versions are almost identical (same class or have same functions), a fatal error like + * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur. + */ + $this->unregister_uninstall_hook(); + + $this->clear_module_main_file_cache(); + + // Update is_premium of latest version. + $this->_storage->prev_is_premium = $this->_plugin->is_premium; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Add-ons + #---------------------------------------------------------------------------------- + + /** + * Check if add-on installed and activated on site. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * @param bool|null $is_premium Since 1.2.1.7 can check for specified add-on version. + * + * @return bool + */ + function is_addon_activated( $id_or_slug, $is_premium = null ) { + $this->_logger->entrance(); + + $addon_id = self::get_module_id( $id_or_slug ); + $is_activated = self::has_instance( $addon_id ); + + if ( ! $is_activated ) { + return false; + } + + if ( is_bool( $is_premium ) ) { + // Check if the specified code version is activate. + $addon = $this->get_addon_instance( $addon_id ); + $is_activated = ( $is_premium === $addon->is_premium() ); + } + + return $is_activated; + } + + /** + * Check if add-on was connected to install + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param string|number $id_or_slug + * + * @return bool + */ + function is_addon_connected( $id_or_slug ) { + $this->_logger->entrance(); + + $sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); + + $addon_id = self::get_module_id( $id_or_slug ); + $addon = $this->get_addon( $addon_id ); + $slug = $addon->slug; + if ( ! isset( $sites[ $slug ] ) ) { + return false; + } + + $site = $sites[ $slug ]; + + $plugin = FS_Plugin_Manager::instance( $addon_id )->get(); + + if ( $plugin->parent_plugin_id != $this->_plugin->id ) { + // The given slug do NOT belong to any of the plugin's add-ons. + return false; + } + + return ( is_object( $site ) && + is_numeric( $site->id ) && + is_numeric( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ); + } + + /** + * Determines if add-on installed. + * + * NOTE: This is a heuristic and only works if the folder/file named as the slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return bool + */ + function is_addon_installed( $id_or_slug ) { + $this->_logger->entrance(); + + $addon_id = self::get_module_id( $id_or_slug ); + + return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) ); + } + + /** + * Get add-on basename. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string|number $id_or_slug + * + * @return string + */ + function get_addon_basename( $id_or_slug ) { + $addon_id = self::get_module_id( $id_or_slug ); + + if ( $this->is_addon_activated( $addon_id ) ) { + return self::instance( $addon_id )->get_plugin_basename(); + } + + $addon = $this->get_addon( $addon_id ); + $premium_basename = "{$addon->premium_slug}/{$addon->slug}.php"; + + if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) { + return $premium_basename; + } + + $all_plugins = $this->get_all_plugins(); + + foreach ( $all_plugins as $basename => $data ) { + if ( $addon->slug === $data['slug'] || + $addon->premium_slug === $data['slug'] + ) { + return $basename; + } + } + + $free_basename = "{$addon->slug}/{$addon->slug}.php"; + + return $free_basename; + } + + /** + * Get installed add-ons instances. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return Freemius[] + */ + function get_installed_addons() { + if ( $this->is_addon() ) { + // Add-on cannot have add-ons. + return array(); + } + + $installed_addons = array(); + + foreach ( self::$_instances as $instance ) { + if ( $instance->is_addon_of( $this->_plugin->id ) ) { + $installed_addons[] = $instance; + } + } + + return $installed_addons; + } + + /** + * Check if any add-ons of the plugin are installed. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.1 + * + * @return bool + */ + function has_installed_addons() { + if ( ! $this->has_addons() ) { + return false; + } + + foreach ( self::$_instances as $instance ) { + if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) { + if ( $this->_plugin->id == $instance->_parent_plugin->id ) { + return true; + } + } + } + + return false; + } + + /** + * Tell Freemius that the current plugin is an add-on. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $parent_plugin_id The parent plugin ID + */ + function init_addon( $parent_plugin_id ) { + $this->_plugin->parent_plugin_id = $parent_plugin_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_addon() { + return ( + isset( $this->_plugin->parent_plugin_id ) && + is_numeric( $this->_plugin->parent_plugin_id ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.2 + * + * @param number $parent_product_id + * + * @return bool + */ + function is_addon_of( $parent_product_id ) { + return ( + $this->is_addon() && + $parent_product_id == $this->_plugin->parent_plugin_id + ); + } + + /** + * Deactivate add-on if it's premium only and the user does't have a valid license. + * + * @param bool $is_after_trial_cancel + * + * @return bool If add-on was deactivated. + */ + private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) { + if ( ! $this->has_free_plan() && + ! $this->has_features_enabled_license() && + ! $this->_has_premium_license() + ) { + if ( $this->is_registered() ) { + // IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons). + // if (empty($this->_storage->activation_timestamp) || + // (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30 + // ) { + /** + * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation. + * + * Retry syncing the user add-on licenses. + */ + // Sync licenses. + $this->_sync_licenses(); + // } + + // Try to activate premium license. + $this->_activate_license( true ); + } + + if ( ! $this->has_free_plan() && + ! $this->has_features_enabled_license() && + ! $this->_has_premium_license() + ) { + // @todo Check if deactivate plugins also call the deactivation hook. + + $this->_parent->_admin_notices->add_sticky( + sprintf( + ( $is_after_trial_cancel ? + $this->_parent->get_text_inline( + '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.', + 'addon-trial-cancelled-message' + ) : + $this->_parent->get_text_inline( + '%s is a premium only add-on. You have to purchase a license first before activating the plugin.', + 'addon-no-license-message' + ) + ), + '' . $this->_plugin->title . '' + ) . ' ' . sprintf( + '%s  ➜', + $this->_parent->addon_url( $this->_slug ), + esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ), + $this->_parent->get_text_inline( 'Purchase License', 'purchase-license' ) + ), + 'no_addon_license_' . $this->_slug, + ( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ), + ( $is_after_trial_cancel ? 'success' : 'error' ) + ); + + deactivate_plugins( array( $this->_plugin_basename ), true ); + + return true; + } + } + + return false; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Sandbox + #---------------------------------------------------------------------------------- + + /** + * Set Freemius into sandbox mode for debugging. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $secret_key + */ + function init_sandbox( $secret_key ) { + $this->_plugin->secret_key = $secret_key; + + // Update plugin details. + FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true ); + } + + /** + * Check if running payments in sandbox mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_payments_sandbox() { + return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key ); + } + + #endregion + + /** + * Check if running test vs. live plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_live() { + return $this->_plugin->is_live; + } + + /** + * Check if super-admin skipped connection for all sites in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function is_network_anonymous() { + if ( ! $this->_is_network_active ) { + return false; + } + + $is_anonymous_ms = $this->_storage->get( 'is_anonymous_ms' ); + + if ( empty( $is_anonymous_ms ) ) { + return false; + } + + return $is_anonymous_ms['is']; + } + + /** + * Check if super-admin opted-in for all sites in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function is_network_connected() { + if ( ! $this->_is_network_active ) { + return false; + } + + return $this->_storage->get( 'is_network_connected' ); + } + + /** + * Check if the user skipped connecting the account with Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_anonymous() { + if ( ! isset( $this->_is_anonymous ) ) { + if ( $this->is_network_anonymous() ) { + $this->_is_anonymous = true; + } else if ( ! fs_is_network_admin() ) { + if ( ! isset( $this->_storage->is_anonymous ) ) { + // Not skipped. + $this->_is_anonymous = false; + } else if ( is_bool( $this->_storage->is_anonymous ) ) { + // For back compatibility, since the variable was boolean before. + $this->_is_anonymous = $this->_storage->is_anonymous; + + // Upgrade stored data format to 1.1.3 format. + $this->set_anonymous_mode( $this->_storage->is_anonymous ); + } else { + // Version 1.1.3 and later. + $this->_is_anonymous = $this->_storage->is_anonymous['is']; + } + } + } + + return $this->_is_anonymous; + } + + /** + * Check if the user skipped the connection of a specified site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_anonymous_site( $blog_id = 0 ) { + if ( $this->is_network_anonymous() ) { + return true; + } + + $is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id ); + + if ( empty( $is_anonymous ) ) { + return false; + } + + return $is_anonymous['is']; + } + + /** + * Check if user connected his account and install pending email activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function is_pending_activation() { + return $this->_storage->get( 'is_pending_activation', false ); + } + + /** + * Check if plugin must be WordPress.org compliant. + * + * @since 1.0.7 + * + * @return bool + */ + function is_org_repo_compliant() { + return $this->_is_org_compliant; + } + + #-------------------------------------------------------------------------------- + #region WP Cron Common + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return object + */ + private function get_cron_data( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + return $this->_storage->get( "{$name}_cron", null ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + */ + private function clear_cron_data( $name ) { + $this->_logger->entrance( $name ); + + $this->_storage->remove( "{$name}_cron" ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param int $cron_blog_id The cron executing blog ID. + */ + private function set_cron_data( $name, $cron_blog_id = 0 ) { + $this->_logger->entrance( $name ); + + $this->_storage->store( "{$name}_cron", (object) array( + 'version' => $this->get_plugin_version(), + 'blog_id' => $cron_blog_id, + 'sdk_version' => $this->version, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + 'on' => true, + ) ); + } + + /** + * Get the cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return int + */ + private function get_cron_blog_id( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + return ( is_object( $cron_data ) && is_numeric( $cron_data->blog_id ) ) ? + $cron_data->blog_id : + 0; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return bool + */ + private function is_cron_on( $name ) { + $this->_logger->entrance( $name ); + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + return ( ! is_null( $cron_data ) && true === $cron_data->on ); + } + + /** + * Unix timestamp for previous cron execution or false if never executed. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * + * @return int|false + */ + private function cron_last_execution( $name ) { + $this->_logger->entrance( $name ); + + return $this->_storage->get( "{$name}_timestamp" ); + } + + /** + * Set cron execution time to now. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + */ + private function set_cron_execution_timestamp( $name ) { + $this->_logger->entrance( $name ); + + $this->_storage->store( "{$name}_timestamp", time() ); + } + + /** + * Sets the keepalive time to now. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @param bool|null $use_network_level_storage + */ + private function set_keepalive_timestamp( $use_network_level_storage = null ) { + $this->_logger->entrance(); + + $this->_storage->store( 'keepalive_timestamp', time(), $use_network_level_storage ); + } + + /** + * Check if cron was executed in the last $period of seconds. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param int $period In seconds + * + * @return bool + */ + private function is_cron_executed( $name, $period = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( $name ); + + $last_execution = $this->cron_last_execution( $name ); + + if ( ! is_numeric( $last_execution ) ) { + return false; + } + + return ( $last_execution > ( WP_FS__SCRIPT_START_TIME - $period ) ); + } + + /** + * WP Cron is executed on a site level. When running in a multisite network environment + * with the network integration activated, for optimization reasons, we are consolidating + * the installs data sync cron to be executed only from a single site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $except_blog_id Target any except the excluded blog ID. + * + * @return int + */ + private function get_cron_target_blog_id( $except_blog_id = 0 ) { + if ( ! is_multisite() ) { + return 0; + } + + if ( $this->_is_network_active && + is_numeric( $this->_storage->network_install_blog_id ) && + $except_blog_id != $this->_storage->network_install_blog_id && + self::is_site_active( $this->_storage->network_install_blog_id ) + ) { + // Try to run cron from the main network blog. + $install = $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ); + + if ( is_object( $install ) && + ( $this->is_premium() || $install->is_tracking_allowed() ) + ) { + return $this->_storage->network_install_blog_id; + } + } + + // Get first opted-in blog ID with active tracking. + $installs = $this->get_blog_install_map(); + foreach ( $installs as $blog_id => $install ) { + if ( $except_blog_id != $blog_id && + self::is_site_active( $blog_id ) && + ( $this->is_premium() || $install->is_tracking_allowed() ) + ) { + return $blog_id; + } + } + + return 0; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * @param bool $is_network_clear If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_cron( $name, $action_tag = '', $is_network_clear = false ) { + $this->_logger->entrance( $name ); + + if ( ! $this->is_cron_on( $name ) ) { + return; + } + + $clear_cron = true; + if ( ! $is_network_clear && $this->_is_network_active ) { + $installs = $this->get_blog_install_map(); + + foreach ( $installs as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_tracking_allowed() ) { + $clear_cron = false; + break; + } + } + } + + if ( ! $clear_cron ) { + return; + } + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? + $cron_data->blog_id : + 0; + + $this->clear_cron_data( $name ); + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( empty( $action_tag ) ) { + $action_tag = $name; + } + + wp_clear_scheduled_hook( $this->get_action_tag( $action_tag ) ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + } + + /** + * Unix timestamp for next cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * + * @return int|false + */ + private function get_next_scheduled_cron( $name, $action_tag = '' ) { + $this->_logger->entrance( $name ); + + if ( ! $this->is_cron_on( $name ) ) { + return false; + } + + /** + * @var object $cron_data + */ + $cron_data = $this->get_cron_data( $name ); + + $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? + $cron_data->blog_id : + 0; + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( empty( $action_tag ) ) { + $action_tag = $name; + } + + $next_scheduled = wp_next_scheduled( $this->get_action_tag( $action_tag ) ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + + return $next_scheduled; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param string $action_tag Callback action tag. + * @param string $recurrence 'single' or 'daily'. + * @param int $start_at Defaults to now. + * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. + * @param int $except_blog_id Target any except the excluded blog ID. + */ + private function schedule_cron( + $name, + $action_tag = '', + $recurrence = 'single', + $start_at = WP_FS__SCRIPT_START_TIME, + $randomize_start = true, + $except_blog_id = 0 + ) { + $this->_logger->entrance( $name ); + + $this->clear_cron( $name, $action_tag, true ); + + $cron_blog_id = $this->get_cron_target_blog_id( $except_blog_id ); + + if ( is_multisite() && 0 == $cron_blog_id ) { + // Don't schedule cron since couldn't find a target blog. + return; + } + + if ( 0 < $cron_blog_id ) { + switch_to_blog( $cron_blog_id ); + } + + if ( 'daily' === $recurrence ) { + if ( $randomize_start ) { + // Schedule first sync with a random 12 hour time range from now. + $start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) ); + } + + // Schedule daily WP cron. + wp_schedule_event( + $start_at, + 'daily', + $this->get_action_tag( $action_tag ) + ); + } else if ( 'single' === $recurrence ) { + // Schedule single cron. + wp_schedule_single_event( + $start_at, + $this->get_action_tag( $action_tag ) + ); + } + + $this->set_cron_data( $name, $cron_blog_id ); + + if ( 0 < $cron_blog_id ) { + restore_current_blog(); + } + } + + /** + * Consolidated cron execution for performance optimization. The max number of API requests is based on the number of unique opted-in users. + * that doesn't halt page loading. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $name Cron name. + * @param callable $callable The function that should be executed. + */ + private function execute_cron( $name, $callable ) { + $this->_logger->entrance( $name ); + + // Store the last time data sync was executed. + $this->set_cron_execution_timestamp( $name ); + + // Check if API is temporary down. + if ( FS_Api::is_temporary_down() ) { + return; + } + + // @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours. + + $users_2_blog_ids = array(); + + if ( ! is_multisite() ) { + // Add dummy blog. + $users_2_blog_ids[0] = array( 0 ); + } else { + $installs = $this->get_blog_install_map(); + foreach ( $installs as $blog_id => $install ) { + if ( $this->is_premium() || $install->is_tracking_allowed() ) { + if ( ! isset( $users_2_blog_ids[ $install->user_id ] ) ) { + $users_2_blog_ids[ $install->user_id ] = array(); + } + + $users_2_blog_ids[ $install->user_id ][] = $blog_id; + } + } + } + + $current_blog_id = get_current_blog_id(); + + foreach ( $users_2_blog_ids as $user_id => $blog_ids ) { + if ( 0 < $blog_ids[0] ) { + $this->switch_to_blog( $blog_ids[0] ); + } + + call_user_func_array( $callable, array( $blog_ids, ( is_multisite() ? $current_blog_id : null ) ) ); + + foreach ( $blog_ids as $blog_id ) { + $this->do_action( "after_{$name}_cron", $blog_id ); + } + } + + if ( is_multisite() ) { + $this->switch_to_blog( $current_blog_id, fs_is_network_admin() ? $this->get_network_install() : null ); + + $this->do_action( "after_{$name}_cron_multisite" ); + } + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Daily Sync Cron + #---------------------------------------------------------------------------------- + + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function is_sync_cron_scheduled() { + return $this->is_cron_on( 'sync' ); + } + + /** + * Get the sync cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return int + */ + private function get_sync_cron_blog_id() { + return $this->get_cron_blog_id( 'sync' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function run_manual_sync() { + self::require_pluggable_essentials(); + + if ( ! $this->is_user_admin() ) { + return; + } + + // Run manual sync. + $this->_sync_cron(); + + // Reschedule next cron to run 24 hours from now (performance optimization). + $this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false ); + } + + /** + * Data sync cron job. Replaces the background sync non blocking HTTP request + * that doesn't halt page loading. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. + */ + function _sync_cron() { + $this->_logger->entrance(); + + $this->execute_cron( 'sync', array( &$this, '_sync_cron_method' ) ); + } + + /** + * The actual data sync cron logic. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int[] $blog_ids + * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the + * `_sync_plugin_license` method in order to switch to the previous blog when sending + * updates for a single site in case `execute_cron` has switched to a different blog. + */ + function _sync_cron_method( array $blog_ids, $current_blog_id = null ) { + if ( $this->is_registered() ) { + if ( $this->has_paid_plan() ) { + // Initiate background plan sync. + $this->_sync_license( true, false, $current_blog_id ); + + if ( $this->is_paying() ) { + // Check for premium plugin updates. + $this->check_updates( true ); + } + } else { + // Sync install(s) (only if something changed locally). + if ( 1 < count( $blog_ids ) ) { + $this->sync_installs(); + } else { + $this->sync_install(); + } + + $this->maybe_sync_install_user(); + } + } + } + + /** + * Check if sync was executed in the last $period of seconds. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $period In seconds + * + * @return bool + */ + private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) { + return $this->is_cron_executed( 'sync', $period ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function is_sync_cron_on() { + return $this->is_cron_on( 'sync' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $start_at Defaults to now. + * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. + * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. + */ + private function schedule_sync_cron( + $start_at = WP_FS__SCRIPT_START_TIME, + $randomize_start = true, + $except_blog_id = 0 + ) { + $this->schedule_cron( + 'sync', + 'data_sync', + 'daily', + $start_at, + $randomize_start, + $except_blog_id + ); + } + + /** + * Add the actual sync function to the cron job hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function hook_callback_to_sync_cron() { + $this->add_action( 'data_sync', array( &$this, '_sync_cron' ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_sync_cron( $is_network_clear = false ) { + $this->_logger->entrance(); + + $this->clear_cron( 'sync', 'data_sync', $is_network_clear ); + } + + /** + * Unix timestamp for next sync cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function next_sync_cron() { + return $this->get_next_scheduled_cron( 'sync', 'data_sync' ); + } + + /** + * Unix timestamp for previous sync cron execution or false if never executed. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function last_sync_cron() { + return $this->cron_last_execution( 'sync' ); + } + + #endregion Daily Sync Cron ------------------------------------------------------------------ + + #---------------------------------------------------------------------------------- + #region Async Install Sync + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return bool + */ + private function is_install_sync_scheduled() { + return $this->is_cron_on( 'install_sync' ); + } + + /** + * Get the sync cron's executing blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return int + */ + private function get_install_sync_cron_blog_id() { + return $this->get_cron_blog_id( 'install_sync' ); + } + + /** + * Instead of running blocking install sync event, execute non blocking scheduled wp-cron. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. + */ + private function schedule_install_sync( $except_blog_id = 0 ) { + $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id ); + } + + /** + * Unix timestamp for previous install sync cron execution or false if never executed. + * + * @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function last_install_sync() { + return $this->cron_last_execution( 'install_sync' ); + } + + /** + * Unix timestamp for next install sync cron execution or false if not scheduled. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @return int|false + */ + function next_install_sync() { + return $this->get_next_scheduled_cron( 'install_sync', 'install_sync' ); + } + + /** + * Add the actual install sync function to the cron job hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + */ + private function hook_callback_to_install_sync() { + $this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. + */ + private function clear_install_sync_cron( $is_network_clear = false ) { + $this->_logger->entrance(); + + $this->clear_cron( 'install_sync', 'install_sync', $is_network_clear ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. + */ + public function _run_sync_install() { + $this->_logger->entrance(); + + $this->execute_cron( 'sync', array( &$this, '_sync_install_cron_method' ) ); + } + + /** + * The actual install(s) sync cron logic. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int[] $blog_ids + * @param int|null $current_blog_id + */ + function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { + if ( $this->is_registered() ) { + if ( 1 < count( $blog_ids ) ) { + $this->sync_installs( array(), true ); + } else { + $this->sync_install( array(), true ); + } + + $this->maybe_sync_install_user(); + } + } + + #endregion Async Install Sync ------------------------------------------------------------------ + + /** + * Show a notice that activation is currently pending. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool|string $email + * @param bool $is_pending_trial Since 1.2.1.5 + */ + function _add_pending_activation_notice( $email = false, $is_pending_trial = false ) { + if ( ! is_string( $email ) ) { + $current_user = self::_get_current_wp_user(); + $email = $current_user->user_email; + } + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message' ), + '' . $this->get_plugin_name() . '', + '' . $email . '', + ( $is_pending_trial ? + $this->get_text_inline( 'start the trial', 'start-the-trial' ) : + $this->get_text_inline( 'complete the install', 'complete-the-install' ) ) + ), + 'activation_pending', + 'Thanks!' + ); + } + + /** + * Check if currently in plugin activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @return bool + */ + function is_plugin_activation() { + $result = get_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); + + return !empty($result); + } + + /** + * + * NOTE: admin_menu action executed before admin_init. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _admin_init_action() { + $is_migration = $this->is_migration(); + + /** + * Automatically redirect to connect/activation page after plugin activation. + * + * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode. + */ + if ( $this->is_plugin_activation() ) { + delete_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); + + if ( isset( $_GET['activate-multi'] ) ) { + /** + * Don't redirect if activating multiple plugins at once (bulk activation). + */ + } else if ( ! $is_migration ) { + $this->_redirect_on_activation_hook(); + return; + } + } + + if ( $is_migration ) { + return; + } + + if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) { + check_admin_referer( $this->get_unique_affix() . '_skip_activation' ); + + $this->skip_connection( null, fs_is_network_admin() ); + + fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ); + } + + if ( $this->is_network_activation_mode() && + fs_request_is_action( $this->get_unique_affix() . '_delegate_activation' ) + ) { + check_admin_referer( $this->get_unique_affix() . '_delegate_activation' ); + + $this->delegate_connection(); + + fs_redirect( $this->get_after_activation_url( 'after_delegation_url' ) ); + } + + $this->_add_upgrade_action_link(); + + if ( ! ( ! $this->_is_network_active && fs_is_network_admin() ) && + ( + ( true === $this->_storage->require_license_activation ) || + // Not registered nor anonymous. + ( ! $this->is_registered() && ! $this->is_anonymous() ) || + // OR, network level and in network upgrade mode. + ( fs_is_network_admin() && $this->_is_network_active && $this->is_network_upgrade_mode() ) + ) + ) { + if ( ! $this->is_pending_activation() ) { + if ( ! $this->is_activation_page() ) { + /** + * If a user visits any other admin page before activating the premium-only theme with a valid + * license, reactivate the previous theme. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( $this->is_theme() && + ! $this->has_settings_menu() && + ! isset( $_REQUEST['fs_action'] ) && + $this->can_activate_previous_theme() + ) { + if ( $this->is_only_premium() ) { + $this->activate_previous_theme(); + return; + } + + if ( true === $this->_storage->require_license_activation ) { + $this->_storage->require_license_activation = false; + } + } + + if ( ! fs_is_network_admin() && + $this->is_network_activation_mode() && + ! $this->is_delegated_connection() + ) { + return; + } + + if ( $this->is_plugin_new_install() || $this->is_only_premium() ) { + if ( ! $this->_anonymous_mode && + ( ! $this->is_addon() || ! $this->_parent->is_anonymous() ) ) { + // Show notice for new plugin installations. + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'You are just one step away - %s', 'you-are-step-away' ), + sprintf( '%s', + $this->get_activation_url( array(), ! $this->is_delegated_connection() ), + sprintf( $this->get_text_x_inline( 'Complete "%s" Activation Now', + '%s - plugin name. As complete "PluginX" activation now', 'activate-x-now' ), $this->get_plugin_name() ) + ) + ), + '', + 'update-nag' + ); + } + } else { + if ( $this->should_add_sticky_optin_notice() ) { + $this->add_sticky_optin_admin_notice(); + } + + if ( $this->has_filter( 'optin_pointer_element' ) ) { + // Don't show admin nag if plugin update. + wp_enqueue_script( 'wp-pointer' ); + wp_enqueue_style( 'wp-pointer' ); + + $this->_enqueue_connect_essentials(); + + add_action( 'admin_print_footer_scripts', array( + $this, + '_add_connect_pointer_script' + ) ); + } + } + } + } + + if ( $this->show_opt_in_on_themes_page() && + $this->is_activation_page() + ) { + $this->_show_theme_activation_optin_dialog(); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function should_add_sticky_optin_notice() { + if ( $this->is_addon() && $this->_parent->is_anonymous() ) { + return false; + } + + if ( fs_is_network_admin() ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( ! $this->is_network_activation_mode() ) { + return false; + } + + return ! isset( $this->_storage->sticky_optin_added_ms ); + } + + if ( ! $this->is_activation_mode() ) { + return false; + } + + // If running from a blog admin and delegated the connection. + return ! isset( $this->_storage->sticky_optin_added ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + private function add_sticky_optin_admin_notice() { + if ( ! $this->_is_network_active || ! fs_is_network_admin() ) { + $this->_storage->sticky_optin_added = true; + } else { + $this->_storage->sticky_optin_added_ms = true; + } + + // Show notice for new plugin installations. + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'We made a few tweaks to the %s, %s', 'few-plugin-tweaks' ), + $this->_module_type, + sprintf( '%s', + $this->get_activation_url(), + sprintf( $this->get_text_inline( 'Opt in to make "%s" better!', 'optin-x-now' ), $this->get_plugin_name() ) + ) + ), + 'connect_account', + '', + 'update-nag' + ); + } + + /** + * Enqueue connect requires scripts and styles. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + function _enqueue_connect_essentials() { + wp_enqueue_script( 'jquery' ); + wp_enqueue_script( 'json2' ); + + fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); + fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); + + fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); + } + + /** + * Add connect / opt-in pointer. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + function _add_connect_pointer_script() { + $vars = array( 'id' => $this->_module_id ); + $pointer_content = fs_get_template( 'connect.php', $vars ); + ?> + + _menu->get_raw_slug() ) || + fs_is_plugin_page( $this->_slug ); + } + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Delete site install from Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store + * @param int|null $blog_id Since 2.0.0 + * + * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). + */ + function _delete_site( $store = true, $blog_id = null ) { + return self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store, $blog_id ); + } + + /** + * Delete site install from Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $slug + * @param string $module_type + * @param bool $store + * @param int|null $blog_id Since 2.0.0 + * + * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). + */ + static function _delete_site_by_slug( $slug, $module_type, $store = true, $blog_id = null ) { + $sites = self::get_all_sites( $module_type, $blog_id ); + + $install_id = false; + + if ( isset( $sites[ $slug ] ) ) { + if ( is_object( $sites[ $slug ] ) ) { + $install_id = $sites[ $slug ]->id; + } + + unset( $sites[ $slug ] ); + + self::set_account_option_by_module( $module_type, 'sites', $sites, $store, $blog_id ); + } + + return $install_id; + } + + /** + * Delete user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param bool $store + * + * @return false|int The user ID if deleted. Otherwise, FALSE (when install not exist). + */ + private static function delete_user( $user_id, $store = true ) { + $users = self::get_all_users(); + + if ( ! is_array( $users ) || ! isset( $users[ $user_id ] ) ) { + return false; + } + + unset( $users[ $user_id ] ); + + self::$_accounts->set_option( 'users', $users, $store ); + + return $user_id; + } + + /** + * Delete plugin's plans information. + * + * @param bool $store Flush to Database if true. + * @param bool $keep_associated_plans If set to false, delete all plans, even if a plan is associated with an install. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function _delete_plans( $store = true, $keep_associated_plans = true ) { + $this->_logger->entrance(); + + $plans = self::get_all_plans( $this->_module_type ); + + $plans_to_keep = array(); + + if ( $keep_associated_plans ) { + $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); + foreach ( $plans_ids_to_keep as $plan_id ) { + $plan = self::_get_plan_by_id( $plan_id ); + if ( is_object( $plan ) ) { + $plans_to_keep[] = self::_encrypt_entity( $plan ); + } + } + } + + if ( ! empty( $plans_to_keep ) ) { + $plans[ $this->_slug ] = $plans_to_keep; + } else { + unset( $plans[ $this->_slug ] ); + } + + $this->set_account_option( 'plans', $plans, $store ); + } + + /** + * Delete all plugin licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $store + */ + private function _delete_licenses( $store = true ) { + $this->_logger->entrance(); + + $all_licenses = self::get_all_licenses(); + + unset( $all_licenses[ $this->_module_id ] ); + + self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); + } + + /** + * Check if Freemius was added on new plugin installation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.5 + * + * @return bool + */ + function is_plugin_new_install() { + return isset( $this->_storage->is_plugin_new_install ) && + $this->_storage->is_plugin_new_install; + } + + /** + * Check if it's the first plugin release that is running Freemius. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + function is_first_freemius_powered_version() { + return empty( $this->_storage->plugin_last_version ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool|string + */ + private function get_previous_theme_slug() { + return isset( $this->_storage->previous_theme ) ? + $this->_storage->previous_theme : + false; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + private function can_activate_previous_theme() { + $slug = $this->get_previous_theme_slug(); + if ( false !== $slug && current_user_can( 'switch_themes' ) ) { + $theme_instance = wp_get_theme( $slug ); + + return $theme_instance->exists(); + } + + return false; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + private function activate_previous_theme() { + switch_theme( $this->get_previous_theme_slug() ); + unset( $this->_storage->previous_theme ); + + global $pagenow; + if ( 'themes.php' === $pagenow ) { + /** + * Refresh the active theme information. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + fs_redirect( $this->admin_url( $pagenow ) ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_previous_theme_activation_url() { + if ( ! $this->can_activate_previous_theme() ) { + return ''; + } + + /** + * Activation URL + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + return wp_nonce_url( + $this->admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ), + 'switch-theme_' . $this->get_previous_theme_slug() + ); + } + + /** + * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in + * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is + * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous + * theme doesn't exist, then there will be no close button. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string $slug_or_name Old theme's slug or name. + * @param bool|WP_Theme $old_theme WP_Theme instance of the old theme if it still exists. + */ + function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) { + $this->_storage->previous_theme = ( false !== $old_theme ) ? + $old_theme->get_stylesheet() : + $slug_or_name; + + $this->_activate_plugin_event_hook(); + } + + /** + * Plugin activated hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @uses FS_Api + */ + function _activate_plugin_event_hook() { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( ! $this->is_user_admin() ) { + return; + } + + $this->unregister_uninstall_hook(); + + // Clear API cache on activation. + FS_Api::clear_cache(); + + $is_premium_version_activation = $this->is_plugin() ? + ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) ) : + $this->is_premium(); + + $this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' ); + + if ( $this->is_plugin() ) { + // This logic is relevant only to plugins since both the free and premium versions of a plugin can be active at the same time. + // 1. If running in the activation of the FREE module, get the basename of the PREMIUM. + // 2. If running in the activation of the PREMIUM module, get the basename of the FREE. + $other_version_basename = $is_premium_version_activation ? + $this->_free_plugin_basename : + $this->premium_plugin_basename(); + + if ( ! $this->_is_network_active ) { + /** + * Themes are always network activated, but the ACTUAL activation is per site. + * + * During the activation, the plugin isn't yet active, therefore, + * _is_network_active will be set to false even if it's a network level + * activation. So we need to fix that by looking at the is_network_admin() value. + * + * @author Vova Feldman + */ + $this->_is_network_active = ( + $this->_is_multisite_integrated && + fs_is_network_admin() + ); + } + + /** + * If the other module version is active, deactivate it. + * + * is_plugin_active() checks if the plugin is active on the site or the network level and + * deactivate_plugins() deactivates the plugin whether it's activated on the site or network level. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( + is_plugin_active( $other_version_basename ) && + $this->apply_filters( 'deactivate_on_activation', true ) + ) { + deactivate_plugins( $other_version_basename ); + } + } + + if ( $this->is_registered() ) { + if ( $is_premium_version_activation ) { + $this->reconnect_locally(); + } + + + // Schedule re-activation event and sync. +// $this->sync_install( array(), true ); + $this->schedule_install_sync(); + + // If activating the premium module version, add an admin notice to congratulate for an upgrade completion. + if ( $is_premium_version_activation ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'The upgrade of %s was successfully completed.', 'successful-version-upgrade-message' ), sprintf( '%s', $this->_plugin->title ) ), + $this->get_text_x_inline( 'W00t', + 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' + ); + } + } else if ( $this->is_anonymous() ) { + if ( isset( $this->_storage->is_anonymous_ms ) && $this->_storage->is_anonymous_ms['is'] ) { + $plugin_version = $this->_storage->is_anonymous_ms['version']; + $network = true; + } else { + $plugin_version = $this->_storage->is_anonymous['version']; + $network = false; + } + + /** + * Reset "skipped" click cache on the following: + * 1. Freemius DEV mode. + * 2. WordPress DEBUG mode. + * 3. If a plugin and the user skipped the exact same version before. + * + * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE). + * + * @todo 4. If explicitly asked to retry after every activation. + */ + if ( WP_FS__DEV_MODE || + ( + ( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) && + $this->get_plugin_version() == $plugin_version + ) + ) { + $this->reset_anonymous_mode( $network ); + } + } + + $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); + + if ( $this->is_addon() && ! $is_trial_or_has_features_enabled_license ) { + /** + * When activating an add-on, try to also activate a license. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $this->_is_network_active ) { + $this->maybe_activate_addon_license(); + } else { + $this->maybe_network_activate_addon_license(); + } + + /** + * Avoid redirecting to the license activation screen after automatically activating an add-on license. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); + + if ( $is_trial_or_has_features_enabled_license && true === $this->_storage->require_license_activation ) { + $this->_storage->require_license_activation = false; + } + } + + if ( + $is_premium_version_activation && + ( + ( ! $this->is_registered() && $this->is_anonymous() ) || + ( + $this->is_registered() && + ! $is_trial_or_has_features_enabled_license + ) + ) + ) { + $this->_storage->require_license_activation = true; + } + + if ( ! isset( $this->_storage->is_plugin_new_install ) ) { + /** + * If no previous version of plugin's version exist, it means that it's either + * the first time that the plugin installed on the site, or the plugin was installed + * before but didn't have Freemius integrated. + * + * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires + * on manual activation via the dashboard, is_plugin_activation() is TRUE + * only after immediate activation. + * + * @since 1.1.4 + * @link https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/ + */ + $this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version ); + } + + /** + * Also flush when activating the premium version so that even if Freemius was off before, the API + * connectivity test can be run again. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3.1 + */ + $has_api_connectivity = $this->has_api_connectivity( WP_FS__DEV_MODE || $is_premium_version_activation ); + + if ( ! $this->_anonymous_mode && + $has_api_connectivity && + ! $this->_isAutoInstall + ) { + // Store hint that the plugin was just activated to enable auto-redirection to settings. + set_transient( "fs_{$this->_module_type}_{$this->_slug}_activated", true, 60 ); + } + + /** + * Activation hook is executed after the plugin's main file is loaded, therefore, + * after the plugin was loaded. The logic is located at activate_plugin() + * ./wp-admin/includes/plugin.php. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + */ + $this->_storage->was_plugin_loaded = true; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + private function maybe_activate_addon_license() { + $parent_fs = $this->get_parent_instance(); + + if ( + ! is_object( $parent_fs ) || + ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) + ) { + // Try to activate a license only if the parent plugin is active and has a valid `install`. + return; + } + + $license = $this->get_active_parent_license(); + if ( ! is_object( $license ) ) { + return; + } + + if ( + $this->is_bundle_license_auto_activation_enabled() && + ! empty( $license->products ) + ) { + $this->activate_bundle_license( $license ); + + return; + } + + if ( ! $this->is_registered() ) { + // Opt in with a license key. + $this->opt_in( + $parent_fs->get_current_or_network_user()->email, + false, + false, + $license->secret_key + ); + } else { + // Activate the license. + $install = $this->get_api_site_scope()->call( + '/', + 'put', + array( 'license_key' => $this->apply_filters( 'license_key', $license->secret_key ) ) + ); + + if ( ! FS_Api::is_api_error( $install ) ) { + $this->_sync_addon_license( $this->get_id(), true ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Plugin_License $license + */ + private function maybe_network_activate_addon_license( $license = null ) { + $parent_fs = $this->get_parent_instance(); + if ( ! is_object( $parent_fs ) || ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) ) { + // Try to activate a license only if the parent plugin is active and has a valid `install`. + return; + } + + $license = ( ! is_null( $license ) ) ? + $license : + $this->get_active_parent_license(); + + if ( ! is_object( $license ) ) { + return; + } + + if ( + $this->is_bundle_license_auto_activation_enabled() && + ! empty( $license->products ) + ) { + $this->activate_bundle_license( $license ); + + return; + } + + if ( ! $this->is_network_registered() ) { + $sites = $this->get_sites_for_network_level_optin(); + + if ( count( $sites ) > $license->left() ) { + // If the add-on is network active, try to activate the license only if it can be activated on all sites. + return; + } + + // Opt in with a license key. + $this->opt_in( + $parent_fs->get_user()->email, + false, + false, + $license->secret_key, + false, + false, + false, + null, + $sites + ); + } else { + $blog_2_install_map = array(); + $site_ids = array(); + + $all_sites = Freemius::get_sites(); + + foreach ( $all_sites as $site ) { + $blog_id = Freemius::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { + // Skip license activation for installs that are already associated with a license. + continue; + } + + if ( is_object( $install ) ) { + $blog_2_install_map[ $blog_id ] = $install; + } else { + $site_ids[] = $blog_id; + } + } + + if ( ( count( $blog_2_install_map ) + count( $site_ids ) ) > $license->left() ) { + return; + } + + $user = $this->get_current_or_network_user(); + + if ( ! empty( $blog_2_install_map ) ) { + $result = $this->activate_license_on_many_installs( $user, $license->secret_key, $blog_2_install_map ); + + if ( true !== $result ) { + return; + } + } + + if ( ! empty( $site_ids ) ) { + $this->activate_license_on_many_sites( $user, $license->secret_key, $site_ids ); + } + } + } + + /** + * Tries to activate a bundle license for all supported products if the current product is activated with a bundle license. This is called after activating an available license (not via the license activation dialog but by clicking on a license activation button) for a product via its "Account" page. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + * + * @param FS_Plugin_License $license + * @param array $sites + * @param int $blog_id + */ + private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) { + if ( ! is_object( $license ) && $this->has_active_valid_license() ) { + $license = $this->_license; + } + + if ( ! is_object( $license ) ) { + return; + } + + $parent_license = ( ! empty( $license->products ) ) ? + $license : + $this->get_active_parent_license( $license->secret_key ); + + if ( is_object( $parent_license ) ) { + $this->activate_bundle_license( $parent_license, $sites, $blog_id ); + } + } + + /** + * Try to activate a bundle license for all the bundle products installed on the site. + * (1) If a child product install already has a license, the bundle license won't be activated. + * (2) On multi-site networks, if the attempt to activate the bundle license is triggered from the network admin, the bundle license activation will only work for non-delegated sites and only if none of them is associated with a license. Even if one of the sites has the product installed with a license key, skip the bundle license activation for the product. + * (3) On multi-site networks, if the attempt to activate the bundle license is triggered from a site-level admin, only activate the license if the product is site-level activated or delegated, and the product installation is not yet associated with a license. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + * + * @param FS_Plugin_License $license + * @param array $sites + * @param int $current_blog_id + */ + private function activate_bundle_license( $license, $sites = array(), $current_blog_id = 0 ) { + $is_network_admin = fs_is_network_admin(); + + $installs_by_blog_map = array(); + $site_info_by_blog_map = array(); + + /** + * Try to activate the license for all supported products. + * + * @author Leo Fajardo + */ + foreach ( $license->products as $product_id ) { + $fs = self::get_instance_by_id( $product_id ); + + if ( ! is_object( $fs ) ) { + continue; + } + + if ( ! $fs->has_paid_plan() ) { + continue; + } + + if ( + ! $fs->is_addon() && + ! FS_Plan_Manager::instance()->has_paid_plan( $fs->_plans ) + ) { + /** + * The parent product can be free-only but can have its `has_paid_plan` flag set to `true` when + * there is a context bundle. + */ + continue; + } + + if ( $current_blog_id > 0 ) { + $fs->switch_to_blog( $current_blog_id ); + } + + if ( $fs->has_active_valid_license() ) { + continue; + } + + if ( ! $is_network_admin || $current_blog_id > 0 ) { + if ( $fs->is_network_active() && ! $fs->is_delegated_connection( $current_blog_id ) ) { + // Do not try to activate the license in the site level if the product is network active and the connection was not delegated. + continue; + } + } else { + if ( ! $fs->is_network_active() ) { + // Do not try to activate the license in the network level if the product is not network active. + continue; + } + + if ( $fs->is_network_delegated_connection() ) { + // Do not try to activate the license in the network level if the activation has been delegated to site admins. + continue; + } + + $has_install_with_license = false; + + // Collection of sites that have an install entity that is not activated with a license or non-delegated sites that have no install entity, or both types of site. + $filtered_sites = array(); + + if ( empty( $sites ) ) { + $all_sites = self::get_sites(); + + foreach ( $all_sites as $site ) { + $sites[] = array( 'blog_id' => self::get_site_blog_id( $site ) ); + } + } else { + // Populate the map here to avoid calling `$fs->get_site_info( $site );` in the other `for` loop below. + foreach ( $sites as $site ) { + if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { + continue; + } + + $site_info_by_blog_map[ $site['blog_id'] ] = $site; + } + } + + foreach ( $sites as $site ) { + if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { + continue; + } + + $blog_id = $site['blog_id']; + + if ( ! isset( $installs_by_blog_map[ $blog_id ] ) ) { + $installs_by_blog_map[ $blog_id ] = self::get_all_sites( $fs->get_module_type(), $blog_id ); + } + + $installs = $installs_by_blog_map[ $blog_id ]; + $install = null; + + if ( isset( $installs[ $fs->get_slug() ] ) ) { + $install = $installs[ $fs->get_slug() ]; + + if ( + is_object( $install ) && + ( + ! FS_Site::is_valid_id( $install->id ) || + ! FS_User::is_valid_id( $install->user_id ) || + ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ) + ) { + $install = null; + } + } + + if ( + is_object( $install ) && + FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + $has_install_with_license = true; + break; + } + + if ( $fs->is_site_delegated_connection( $blog_id ) ) { + // Site activation delegated, don't activate bundle license on the site in the network admin. + continue; + } + + if ( ! isset( $site_info_by_blog_map[ $blog_id ] ) ) { + $site_info_by_blog_map[ $blog_id ] = $fs->get_site_info( $site ); + } + + $filtered_sites[] = $site_info_by_blog_map[ $blog_id ]; + } + + if ( $has_install_with_license || empty( $filtered_sites ) ) { + // Do not try to activate the license at the network level if there's any install with a license or there's no site to activate the license on. + continue; + } + + $sites = $filtered_sites; + } + + $fs->activate_migrated_license( + $license->secret_key, + null, + null, + $sites, + ( $current_blog_id > 0 ? $current_blog_id : null ) + ); + } + } + + /** + * Returns a parent license that can be activated for the context product. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param string|null $license_key + * @param bool $flush + * + * @return FS_Plugin_License + */ + function get_active_parent_license( $license_key = null, $flush = true ) { + $parent_licenses_endpoint = "/plugins/{$this->get_id()}/parent_licenses.json?filter=activatable"; + + $fs = $this; + + if ( $this->is_addon() ) { + $parent_instance = $this->get_parent_instance(); + + if ( is_object( $parent_instance ) && $parent_instance->is_registered() ) { + $fs = $parent_instance; + } + } + + $foreign_licenses = $fs->get_foreign_licenses_info( + self::get_all_licenses( $this->get_parent_id() ) + ); + + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $parent_licenses_endpoint = add_query_arg( $foreign_licenses, $parent_licenses_endpoint ); + } + + $result = $fs->get_current_or_network_user_api_scope()->get( $parent_licenses_endpoint, $flush ); + + if ( + ! $this->is_api_result_object( $result, 'licenses' ) || + ! is_array( $result->licenses ) || + empty( $result->licenses ) + ) { + return null; + } + + $parent_license = null; + + if ( empty( $license_key ) ) { + $parent_license = $result->licenses[0]; + } else { + foreach ( $result->licenses as $license ) { + if ( $license_key === $license->secret_key ) { + $parent_license = $license; + break; + } + } + } + + if ( ! is_null( $parent_license ) ) { + $parent_license = new FS_Plugin_License( $parent_license ); + } + + return $parent_license; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return array + */ + function get_sites_for_network_level_optin() { + $sites = array(); + $all_sites = self::get_sites(); + + foreach ( $all_sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( ! $this->is_site_delegated_connection( $blog_id ) && + ! $this->is_installed_on_site( $blog_id ) + ) { + $sites[] = $this->get_site_info( $site ); + } + } + + return $sites; + } + + /** + * Delete account. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function delete_account_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! $this->is_user_admin() ) { + return; + } + + $this->do_action( 'before_account_delete' ); + + // Clear all admin notices. + $this->_admin_notices->clear_all_sticky( false ); + + $this->_delete_site( false ); + + $delete_network_common_data = true; + + if ( $this->_is_network_active ) { + $installs = $this->get_blog_install_map(); + + // Don't delete common network data unless no other installs left. + $delete_network_common_data = empty( $installs ); + } + + if ( $delete_network_common_data ) { + $this->_delete_plans( false ); + + $this->_delete_licenses( false ); + + // Delete add-ons related to plugin's account. + $this->_delete_account_addons( false ); + } + + // @todo Delete plans and licenses of add-ons. + + self::$_accounts->store(); + + /** + * IMPORTANT: + * Clear crons must be executed before clearing all storage. + * Otherwise, the cron will not be cleared. + */ + if ( $delete_network_common_data ) { + $this->clear_sync_cron(); + } + + $this->clear_install_sync_cron(); + + // Clear all storage data. + $this->_storage->clear_all( true, array( + 'is_delegated_connection', + 'connectivity_test', + 'is_on', + ), false ); + + // Send delete event. + $this->get_api_site_scope()->call( '/', 'delete' ); + + $this->do_action( 'after_account_delete' ); + } + + /** + * Delete network level account. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function delete_network_account_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! $this->is_user_admin() ) { + return; + } + + $this->do_action( 'before_network_account_delete' ); + + // Clear all admin notices. + $this->_admin_notices->clear_all_sticky(); + + $this->_delete_plans( false, false ); + + $this->_delete_licenses( false ); + + // Delete add-ons related to plugin's account. + $this->_delete_account_addons( false ); + + // @todo Delete plans and licenses of add-ons. + + self::$_accounts->store( true ); + + /** + * IMPORTANT: + * Clear crons must be executed before clearing all storage. + * Otherwise, the cron will not be cleared. + */ + $this->clear_sync_cron( true ); + $this->clear_install_sync_cron( true ); + + $sites = self::get_sites(); + + $install_ids = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + continue; + } + + $install_id = $this->_delete_site( true, $blog_id ); + + // Clear all storage data. + $this->_storage->clear_all( true, array( 'connectivity_test' ), $blog_id ); + + if ( FS_Site::is_valid_id( $install_id ) ) { + $install_ids[] = $install_id; + } + + switch_to_blog( $blog_id ); + + $this->do_action( 'after_account_delete' ); + + restore_current_blog(); + } + + $this->_storage->clear_all( true, array( + 'connectivity_test', + 'is_on', + ), true ); + + // Send delete event. + if ( ! empty( $install_ids ) ) { + $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', $install_ids ), 'delete' ); + } + + $this->do_action( 'after_network_account_delete' ); + } + + /** + * Plugin deactivation hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + function _deactivate_plugin_hook() { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( ! $this->is_user_admin() ) { + return; + } + + $is_network_deactivation = fs_is_network_admin(); + $storage_keys_for_removal = array(); + + $this->_admin_notices->clear_all_sticky(); + + $storage_keys_for_removal[] = 'sticky_optin_added'; + if ( isset( $this->_storage->sticky_optin_added ) ) { + unset( $this->_storage->sticky_optin_added ); + } + + if ( ! isset( $this->_storage->is_plugin_new_install ) ) { + // Remember that plugin was already installed. + $this->_storage->is_plugin_new_install = false; + } + + // Hook to plugin uninstall. + register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) ); + + $this->clear_module_main_file_cache(); + $this->clear_sync_cron( $this->_is_network_active ); + $this->clear_install_sync_cron(); + + if ( $this->is_registered() ) { + if ( $this->is_premium() && ! $this->has_active_valid_license() ) { + FS_Plugin_Updater::instance( $this )->delete_update_data(); + } + + if ( $is_network_deactivation ) { + // Send deactivation event. + $this->sync_installs( array( + 'is_active' => false, + ) ); + } else { + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + } + } else { + if ( ! $this->has_api_connectivity() ) { + // Reset connectivity test cache. + unset( $this->_storage->connectivity_test ); + + $storage_keys_for_removal[] = 'connectivity_test'; + } + } + + if ( $is_network_deactivation ) { + if ( isset( $this->_storage->sticky_optin_added_ms ) ) { + unset( $this->_storage->sticky_optin_added_ms ); + } + + if ( ! empty( $storage_keys_for_removal ) ) { + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + foreach ( $storage_keys_for_removal as $key ) { + $this->_storage->remove( $key, false, $blog_id ); + } + + $this->_storage->save( $blog_id ); + } + } + } + + // Clear API cache on deactivation. + FS_Api::clear_cache(); + + $this->remove_sdk_reference(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + private function remove_sdk_reference() { + global $fs_active_plugins; + + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( $this->_plugin_basename == $data->plugin_path ) { + unset( $fs_active_plugins->plugins[ $sdk_path ] ); + break; + } + } + + fs_fallback_to_newest_active_sdk(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool $is_anonymous + * @param bool|int $network_or_blog_id Since 2.0.0 + */ + private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = 0 ) { + // Store information regarding skip to try and opt-in the user + // again in the future. + $skip_info = array( + 'is' => $is_anonymous, + 'timestamp' => WP_FS__SCRIPT_START_TIME, + 'version' => $this->get_plugin_version(), + ); + + if ( true === $network_or_blog_id ) { + $this->_storage->is_anonymous_ms = $skip_info; + } else { + $this->_storage->store( 'is_anonymous', $skip_info, $network_or_blog_id ); + } + + $this->network_upgrade_mode_completed(); + + // Update anonymous mode cache. + $this->_is_anonymous = $is_anonymous; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id Site ID. + * @param int $user_id User ID. + * @param string $domain Site domain. + * @param string $path Site path. + * @param int $network_id Network ID. Only relevant on multi-network installations. + * @param array $meta Metadata. Used to set initial site options. + * + * @uses Freemius::is_license_network_active() to check if the context license was network activated by the super-admin. + * @uses Freemius::is_network_connected() to check if the super-admin network opted-in. + * @uses Freemius::is_network_anonymous() to check if the super-admin network skipped. + * @uses Freemius::is_network_delegated_connection() to check if the super-admin network delegated the connection to the site admins. + */ + function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_id, $meta ) { + $this->_logger->entrance(); + + if ( $this->is_premium() && + $this->is_network_connected() && + is_object( $this->_license ) && + $this->_license->can_activate( FS_Site::is_localhost_by_address( $domain ) ) && + $this->is_license_network_active( $blog_id ) + ) { + /** + * Running the premium version, the license was network activated, and the license can also be activated on the current site -> so try to opt-in with the license key. + */ + $current_blog_id = get_current_blog_id(); + $license = clone $this->_license; + + $this->switch_to_blog( $blog_id ); + + // Opt-in with network user. + $this->install_with_user( + $this->get_network_user(), + $license->secret_key, + false, + false, + false + ); + + if ( is_object( $this->_site ) ) { + if ( $this->_site->license_id == $license->id ) { + /** + * If the license was activated successfully, sync the license data from the remote server. + */ + $this->_license = $license; + $this->sync_site_license(); + } + } + + $this->switch_to_blog( $current_blog_id ); + + if ( is_object( $this->_site ) ) { + // Already connected (with or without a license), so no need to continue. + return; + } + } + + if ( $this->is_network_anonymous() ) { + /** + * Opt-in was network skipped so automatically skip the opt-in for the new site. + */ + $this->skip_site_connection( $blog_id ); + } else if ( $this->is_network_delegated_connection() ) { + /** + * Opt-in was network delegated so automatically delegate the opt-in for the new site's admin. + */ + $this->delegate_site_connection( $blog_id ); + } else if ( $this->is_network_connected() ) { + /** + * Opt-in was network activated so automatically opt-in with the network user and new site admin. + */ + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $blog_id ); + + // Opt-in with network user. + $this->install_with_user( + $this->get_network_user(), + false, + false, + false, + false + ); + + $this->switch_to_blog( $current_blog_id ); + } else { + /** + * If the super-admin mixed different options (connect, skip, delegated): + * a) If at least one site connection was delegated, then automatically delegate connection. + * b) Otherwise, it means that at least one site was skipped and at least one site was connected. For a simplified UX in the initial release of the multisite network integration, skip the connection for the newly created site. If the super-admin will want to opt-in they can still do that from the network level Account page. + */ + $has_delegated_site = false; + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( $this->is_site_delegated_connection( $blog_id ) ) { + $has_delegated_site = true; + break; + } + } + + if ( $has_delegated_site ) { + $this->delegate_site_connection( $blog_id ); + } else { + $this->skip_site_connection( $blog_id ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool|int $network_or_blog_id Since 2.0.0. + */ + private function reset_anonymous_mode( $network_or_blog_id = 0 ) { + if ( true === $network_or_blog_id ) { + unset( $this->_storage->is_anonymous_ms ); + } else { + $this->_storage->remove( 'is_anonymous', true, $network_or_blog_id ); + } + + /** + * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module + * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click + * on the admin notice that contains the opt-in link in order to trigger the opt-in popup). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( ! $this->_is_network_active || + 0 === $network_or_blog_id || + get_current_blog_id() == $network_or_blog_id || + ( true === $network_or_blog_id && fs_is_network_admin() ) + ) { + $this->_is_anonymous = null; + } + } + + /** + * This is used to ensure that before redirecting to the opt-in page after resetting the anonymous mode or + * deleting the account in the network level, the URL of the page to redirect to is correct. + * + * @author Leo Fajardo (@leorw) + * + * @since 2.1.3 + */ + private function maybe_set_slug_and_network_menu_exists_flag() { + if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + $this->_menu->set_slug_and_network_menu_exists_flag( $this->_menu->has_menu() ? + $this->_menu->get_slug() : + $this->_slug + ); + } + } + + /** + * Clears the anonymous mode and redirects to the opt-in screen. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + */ + function connect_again() { + if ( ! $this->is_anonymous() ) { + return; + } + + $this->reset_anonymous_mode( fs_is_network_admin() ); + + $this->maybe_set_slug_and_network_menu_exists_flag(); + + fs_redirect( $this->get_activation_url() ); + } + + /** + * Skip account connect, and set anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param array|null $sites Since 2.0.0. Specific sites. + * @param bool $skip_all_network Since 2.0.0. If true, skip connection for all sites. + */ + function skip_connection( $sites = null, $skip_all_network = false ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $skip_all_network ) { + $this->set_anonymous_mode( true, true ); + } + + if ( ! $skip_all_network && empty( $sites ) ) { + $this->skip_site_connection(); + } else { + $uids = array(); + + if ( $skip_all_network ) { + $this->set_anonymous_mode( true, true ); + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $this->skip_site_connection( $blog_id, false ); + $uids[] = $this->get_anonymous_id( $blog_id ); + } + } else if ( ! empty( $sites ) ) { + foreach ( $sites as $site ) { + $uids[] = $site['uid']; + $this->skip_site_connection( $site['blog_id'], false ); + } + } + + // Send anonymous skip event. + // No user identified info nor any tracking will be sent after the user skips the opt-in. + $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( + 'uids' => $uids, + ) ); + } + + $this->network_upgrade_mode_completed(); + } + + /** + * Skip connection for specific site in the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * @param bool $send_skip + */ + private function skip_site_connection( $blog_id = null, $send_skip = true ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account', $blog_id ); + + $this->set_anonymous_mode( true, $blog_id ); + + if ( $send_skip ) { + $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( + 'uids' => array( $this->get_anonymous_id( $blog_id ) ), + ) ); + } + } + + /** + * Plugin version update hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + */ + private function update_plugin_version_event() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return; + } + + $this->schedule_install_sync(); +// $this->sync_install( array(), true ); + } + + /** + * Generate an MD5 signature of a plugins collection. + * This helper methods used to identify changes in a plugins collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array [string]array $plugins + * + * @return string + */ + private function get_plugins_thumbprint( $plugins ) { + ksort( $plugins ); + + $thumbprint = ''; + foreach ( $plugins as $basename => $data ) { + $thumbprint .= $data['slug'] . ',' . + $data['Version'] . ',' . + ( $data['is_active'] ? '1' : '0' ) . ';'; + } + + return md5( $thumbprint ); + } + + /** + * Return a list of modified plugins since the last sync. + * + * Note: + * There's no point to store a plugins counter since even if the number of + * plugins didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_plugins_data_for_api() { + // Alias. + $site_active_plugins_option_name = 'active_plugins'; + $network_plugins_option_name = 'all_plugins'; + + /** + * Collection of all site level active plugins. + */ + $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); + + if ( ! is_object( $site_active_plugins_cache ) ) { + $site_active_plugins_cache = (object) array( + 'timestamp' => '', + 'md5' => '', + 'plugins' => array(), + ); + } + + $time = time(); + + if ( ! empty( $site_active_plugins_cache->timestamp ) && + ( $time - $site_active_plugins_cache->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC + ) { + // Don't send plugin updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $site_active_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); + + // Reload options from DB. + self::$_accounts->load( true ); + $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); + + if ( $time != $site_active_plugins_cache->timestamp ) { + // If timestamp is different, then another thread captured the lock. + return false; + } + + /** + * Collection of all plugins (network level). + */ + $network_plugins_cache = self::$_accounts->get_option( $network_plugins_option_name ); + + if ( ! is_object( $network_plugins_cache ) ) { + $network_plugins_cache = (object) array( + 'timestamp' => '', + 'md5' => '', + 'plugins' => array(), + ); + } + + // Check if there's a change in plugins. + $network_plugins = self::get_network_plugins(); + $site_active_plugins = self::get_site_active_plugins(); + + $network_plugins_thumbprint = $this->get_plugins_thumbprint( $network_plugins ); + $site_active_plugins_thumbprint = $this->get_plugins_thumbprint( $site_active_plugins ); + + // Check if plugins status changed (version or active/inactive). + $network_plugins_changed = ( $network_plugins_cache->md5 !== $network_plugins_thumbprint ); + $site_active_plugins_changed = ( $site_active_plugins_cache->md5 !== $site_active_plugins_thumbprint ); + + if ( ! $network_plugins_changed && + ! $site_active_plugins_changed + ) { + // No changes. + return array(); + } + + $plugins_update_data = array(); + + foreach ( $network_plugins_cache->plugins as $basename => $data ) { + if ( ! isset( $network_plugins[ $basename ] ) ) { + // Plugin uninstalled. + $uninstalled_plugin_data = $data; + $uninstalled_plugin_data['is_active'] = false; + $uninstalled_plugin_data['is_uninstalled'] = true; + $plugins_update_data[] = $uninstalled_plugin_data; + + unset( $network_plugins[ $basename ] ); + + unset( $network_plugins_cache->plugins[ $basename ] ); + unset( $site_active_plugins_cache->plugins[ $basename ] ); + + continue; + } + + $was_active = $data['is_active'] || + ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && + true === $site_active_plugins_cache->plugins[ $basename ]['is_active'] ); + $is_active = $network_plugins[ $basename ]['is_active'] || + ( isset( $site_active_plugins[ $basename ] ) && + $site_active_plugins[ $basename ]['is_active'] ); + + if ( ! isset( $site_active_plugins_cache->plugins[ $basename ] ) && + isset( $site_active_plugins[ $basename ] ) + ) { + // Plugin was site level activated. + $site_active_plugins_cache->plugins[ $basename ] = $network_plugins[ $basename ]; + $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; + } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && + ! isset( $site_active_plugins[ $basename ] ) + ) { + // Plugin was site level deactivated. + unset( $site_active_plugins_cache->plugins[ $basename ] ); + } + + $prev_version = $data['version']; + $current_version = $network_plugins[ $basename ]['Version']; + + if ( $was_active !== $is_active || $prev_version !== $current_version ) { + // Plugin activated or deactivated, or version changed. + + if ( $was_active !== $is_active ) { + if ( $data['is_active'] != $network_plugins[ $basename ]['is_active'] ) { + $network_plugins_cache->plugins[ $basename ]['is_active'] = $data['is_active']; + } + } + + if ( $prev_version !== $current_version ) { + $network_plugins_cache->plugins[ $basename ]['Version'] = $current_version; + } + + $updated_plugin_data = $data; + $updated_plugin_data['is_active'] = $is_active; + $updated_plugin_data['version'] = $current_version; + $updated_plugin_data['title'] = $network_plugins[ $basename ]['Name']; + $plugins_update_data[] = $updated_plugin_data; + } + } + + // Find new plugins that weren't yet seen before. + foreach ( $network_plugins as $basename => $data ) { + if ( ! isset( $network_plugins_cache->plugins[ $basename ] ) ) { + // New plugin. + $new_plugin = array( + 'slug' => $data['slug'], + 'version' => $data['Version'], + 'title' => $data['Name'], + 'is_active' => $data['is_active'], + 'is_uninstalled' => false, + ); + + $network_plugins_cache->plugins[ $basename ] = $new_plugin; + + $is_site_level_active = ( + isset( $site_active_plugins[ $basename ] ) && + $site_active_plugins[ $basename ]['is_active'] + ); + + /** + * If not network active, set the activity status based on the site-level plugin status. + */ + if ( ! $new_plugin['is_active'] ) { + $new_plugin['is_active'] = $is_site_level_active; + } + + $plugins_update_data[] = $new_plugin; + + if ( isset( $site_active_plugins[ $basename ] ) ) { + $site_active_plugins_cache->plugins[ $basename ] = $new_plugin; + $site_active_plugins_cache->plugins[ $basename ]['is_active'] = $is_site_level_active; + } + } + } + + $site_active_plugins_cache->md5 = $site_active_plugins_thumbprint; + $site_active_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); + + $network_plugins_cache->md5 = $network_plugins_thumbprint; + $network_plugins_cache->timestamp = $time; + self::$_accounts->set_option( $network_plugins_option_name, $network_plugins_cache, true ); + + return $plugins_update_data; + } + + /** + * Return a list of modified themes since the last sync. + * + * Note: + * There's no point to store a themes counter since even if the number of + * themes didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_themes_data_for_api() { + // Alias. + $option_name = 'all_themes'; + + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ( ! is_object( $all_cached_themes ) ) { + $all_cached_themes = (object) array( + 'timestamp' => '', + 'md5' => '', + 'themes' => array(), + ); + } + + $time = time(); + + if ( ! empty( $all_cached_themes->timestamp ) && + ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC + ) { + // Don't send theme updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $all_cached_themes->timestamp = $time; + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + + // Reload options from DB. + self::$_accounts->load( true ); + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ( $time != $all_cached_themes->timestamp ) { + // If timestamp is different, then another thread captured the lock. + return false; + } + + // Get active theme. + $active_theme = wp_get_theme(); + $active_theme_stylesheet = $active_theme->get_stylesheet(); + + // Check if there's a change in themes. + $all_themes = wp_get_themes(); + + // Check if themes changed. + ksort( $all_themes ); + + $themes_signature = ''; + foreach ( $all_themes as $slug => $data ) { + $is_active = ( $slug === $active_theme_stylesheet ); + $themes_signature .= $slug . ',' . + $data->version . ',' . + ( $is_active ? '1' : '0' ) . ';'; + } + + // Check if themes status changed (version or active/inactive). + $themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) ); + + $themes_update_data = array(); + + if ( $themes_changed ) { + // Change in themes, report changes. + + // Update existing themes info. + foreach ( $all_cached_themes->themes as $slug => $data ) { + $is_active = ( $slug === $active_theme_stylesheet ); + + if ( ! isset( $all_themes[ $slug ] ) ) { + // Plugin uninstalled. + $uninstalled_theme_data = $data; + $uninstalled_theme_data['is_active'] = false; + $uninstalled_theme_data['is_uninstalled'] = true; + $themes_update_data[] = $uninstalled_theme_data; + + unset( $all_themes[ $slug ] ); + unset( $all_cached_themes->themes[ $slug ] ); + } else if ( $data['is_active'] !== $is_active || + $data['version'] !== $all_themes[ $slug ]->version + ) { + // Plugin activated or deactivated, or version changed. + + $all_cached_themes->themes[ $slug ]['is_active'] = $is_active; + $all_cached_themes->themes[ $slug ]['version'] = $all_themes[ $slug ]->version; + + $themes_update_data[] = $all_cached_themes->themes[ $slug ]; + } + } + + // Find new themes that weren't yet seen before. + foreach ( $all_themes as $slug => $data ) { + if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) { + $is_active = ( $slug === $active_theme_stylesheet ); + + // New plugin. + $new_plugin = array( + 'slug' => $slug, + 'version' => $data->version, + 'title' => $data->name, + 'is_active' => $is_active, + 'is_uninstalled' => false, + ); + + $themes_update_data[] = $new_plugin; + $all_cached_themes->themes[ $slug ] = $new_plugin; + } + } + + $all_cached_themes->md5 = md5( $themes_signature ); + $all_cached_themes->timestamp = time(); + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + } + + return $themes_update_data; + } + + /** + * Get site data for API install request. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @param string[] $override + * @param bool $include_plugins Since 1.1.8 by default include plugin changes. + * @param bool $include_themes Since 1.1.8 by default include plugin changes. + * @param bool $include_blog_data Since 2.3.0 by default include the current blog's data (language, charset, title, and URL). + * + * @return array + */ + private function get_install_data_for_api( + array $override, + $include_plugins = true, + $include_themes = true, + $include_blog_data = true + ) { + if ( $this->is_extensions_tracking_allowed() ) { + if ( ! defined( 'WP_FS__TRACK_PLUGINS' ) || false !== WP_FS__TRACK_PLUGINS ) { + /** + * @since 1.1.8 Also send plugin updates. + */ + if ( $include_plugins && ! isset( $override['plugins'] ) ) { + $plugins = $this->get_plugins_data_for_api(); + if ( ! empty( $plugins ) ) { + $override['plugins'] = $plugins; + } + } + } + + if ( ! defined( 'WP_FS__TRACK_THEMES' ) || false !== WP_FS__TRACK_THEMES ) { + /** + * @since 1.1.8 Also send themes updates. + */ + if ( $include_themes && ! isset( $override['themes'] ) ) { + $themes = $this->get_themes_data_for_api(); + if ( ! empty( $themes ) ) { + $override['themes'] = $themes; + } + } + } + } + + $versions = $this->get_versions(); + + $blog_data = $include_blog_data ? + array( + 'language' => get_bloginfo( 'language' ), + 'charset' => get_bloginfo( 'charset' ), + 'title' => get_bloginfo( 'name' ), + 'url' => get_site_url(), + ) : + array(); + + return array_merge( $versions, $blog_data, array( + 'version' => $this->get_plugin_version(), + 'is_premium' => $this->is_premium(), + // Special params. + 'is_active' => true, + 'is_disconnected' => $this->is_tracking_prohibited(), + 'is_uninstalled' => false, + ), $override ); + } + + /** + * Update installs details. + * + * @todo V1 of multiste network support doesn't support plugin and theme data sending. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string[] string $override + * @param bool $only_diff + * @param bool $include_plugins Since 1.1.8 by default include plugin changes. + * @param bool $include_themes Since 1.1.8 by default include plugin changes. + * + * @return array + */ + private function get_installs_data_for_api( + array $override, + $only_diff = false, + $include_plugins = true, + $include_themes = true + ) { + /** + * @since 1.1.8 Also send plugin updates. + */ +// if ( $include_plugins && ! isset( $override['plugins'] ) ) { +// $plugins = $this->get_plugins_data_for_api(); +// if ( ! empty( $plugins ) ) { +// $override['plugins'] = $plugins; +// } +// } + /** + * @since 1.1.8 Also send themes updates. + */ +// if ( $include_themes && ! isset( $override['themes'] ) ) { +// $themes = $this->get_themes_data_for_api(); +// if ( ! empty( $themes ) ) { +// $override['themes'] = $themes; +// } +// } + + // Common properties. + $versions = $this->get_versions(); + $common = array_merge( $versions, array( + 'version' => $this->get_plugin_version(), + 'is_premium' => $this->is_premium(), + ), $override ); + + + $is_common_diff_for_any_site = false; + $common_diff_union = array(); + + $installs_data = array(); + + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + if ( $install->user_id != $this->_user->id ) { + // Install belongs to a different owner. + continue; + } + + if ( ! $this->is_premium() && $install->is_tracking_prohibited() ) { + // Don't send updates regarding opted-out installs. + continue; + } + + $install_data = $this->get_site_info( $site ); + + $uid = $install_data['uid']; + + unset( $install_data['blog_id'] ); + unset( $install_data['uid'] ); + + $install_data['is_disconnected'] = $install->is_disconnected; + $install_data['is_active'] = $this->is_active_for_site( $blog_id ); + $install_data['is_uninstalled'] = $install->is_uninstalled; + + $common_diff = null; + $is_common_diff = false; + if ( $only_diff ) { + $install_data = $this->get_install_diff_for_api( $install_data, $install, $override ); + $common_diff = $this->get_install_diff_for_api( $common, $install, $override ); + + $is_common_diff = ! empty( $common_diff ); + + if ( $is_common_diff ) { + foreach ( $common_diff as $k => $v ) { + if ( ! isset( $common_diff_union[ $k ] ) ) { + $common_diff_union[ $k ] = $v; + } + } + } + + $is_common_diff_for_any_site = $is_common_diff_for_any_site || $is_common_diff; + } + + if ( ! empty( $install_data ) || $is_common_diff ) { + // Add install ID and site unique ID. + $install_data['id'] = $install->id; + $install_data['uid'] = $uid; + + $installs_data[] = $install_data; + } + } + } + + restore_current_blog(); + + if ( 0 < count( $installs_data ) && ( $is_common_diff_for_any_site || ! $only_diff ) ) { + if ( ! $only_diff ) { + $installs_data[] = $common; + } else if ( ! empty( $common_diff_union ) ) { + $installs_data[] = $common_diff_union; + } + } + + foreach ( $installs_data as &$data ) { + $data = (object) $data; + } + + return $installs_data; + } + + /** + * Compare site actual data to the stored install data and return the differences for an API data sync. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array $site + * @param FS_Site $install + * @param string[] string $override + * + * @return array + */ + private function get_install_diff_for_api( $site, $install, $override = array() ) { + $diff = array(); + $special = array(); + $special_override = false; + + foreach ( $site as $p => $v ) { + if ( property_exists( $install, $p ) ) { + if ( ( is_bool( $install->{$p} ) || ! empty( $install->{$p} ) ) && + $install->{$p} != $v + ) { + $install->{$p} = $v; + $diff[ $p ] = $v; + } + } else { + $special[ $p ] = $v; + + if ( isset( $override[ $p ] ) || + 'plugins' === $p || + 'themes' === $p + ) { + $special_override = true; + } + } + } + + if ( $special_override || 0 < count( $diff ) ) { + // Add special params only if has at least one + // standard param, or if explicitly requested to + // override a special param or a param which is not exist + // in the install object. + $diff = array_merge( $diff, $special ); + } + + return $diff; + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + * + * @return false|object|string + */ + private function send_install_update( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $check_properties = $this->get_install_data_for_api( $override ); + + if ( $flush ) { + $params = $check_properties; + } else { + $params = $this->get_install_diff_for_api( $check_properties, $this->_site, $override ); + } + + $keepalive_only_update = false; + if ( empty( $params ) ) { + $keepalive_only_update = $this->should_send_keepalive_update(); + + if ( ! $keepalive_only_update ) { + /** + * There are no updates to send including keepalive. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return false; + } + } + + if ( ! $keepalive_only_update ) { + /** + * Do not update the last install sync timestamp after a keepalive-only call since there were no actual + * updates sent. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( ! is_multisite() ) { + // Update last install sync timestamp. + $this->set_cron_execution_timestamp( 'install_sync' ); + } + + $params['uid'] = $this->get_anonymous_id(); + } + + $this->set_keepalive_timestamp(); + + // Send updated values to FS. + $site = $this->get_api_site_scope()->call( '/', 'put', $params ); + + if ( ! $keepalive_only_update && $this->is_api_result_entity( $site ) ) { + /** + * Do not clear scheduled sync after a keepalive-only call since there were no actual updates sent. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( ! is_multisite() ) { + // I successfully sent install update, clear scheduled sync if exist. + $this->clear_install_sync_cron(); + } + } + + return $site; + } + + /** + * Update installs only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string[] string $override + * @param bool $flush + * + * @return false|object|string + */ + private function send_installs_update( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $installs_data = $this->get_installs_data_for_api( $override, ! $flush ); + + $keepalive_only_update = false; + if ( empty( $installs_data ) ) { + /** + * Pass `true` to use the network level storage since the update is for many installs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + $keepalive_only_update = $this->should_send_keepalive_update( true ); + + if ( ! $keepalive_only_update ) { + /** + * There are no updates to send including keepalive. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return false; + } + } + + if ( ! $keepalive_only_update ) { + // Update last install sync timestamp if there were actual updates sent (i.e., not a keepalive-only call). + $this->set_cron_execution_timestamp( 'install_sync' ); + } + + /** + * Pass `true` to use the network level storage since the update is for many installs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + $this->set_keepalive_timestamp( true ); + + // Send updated values to FS. + $result = $this->get_api_user_scope()->call( "/plugins/{$this->_plugin->id}/installs.json", 'put', $installs_data ); + + if ( ! $keepalive_only_update && $this->is_api_result_object( $result, 'installs' ) ) { + // I successfully sent installs update (there was an actual update sent and it's not just a keepalive-only call), clear scheduled sync if exist. + $this->clear_install_sync_cron(); + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param bool|null $use_network_level_storage + * + * @return bool + */ + private function should_send_keepalive_update( $use_network_level_storage = null ) { + $keepalive_timestamp = $this->_storage->get( 'keepalive_timestamp', 0, $use_network_level_storage ); + + if ( $keepalive_timestamp < ( time() - WP_FS__TIME_WEEK_IN_SEC ) ) { + // If updated more than 7 days ago, trigger a keepalive and update the time it was triggered. + return true; + } else { + // If updated 7 days ago or less, "flip a coin", if the value is 7 trigger a keepalive and update the last time it was triggered. + return ( 7 == rand( 1, 7 ) ); + } + } + + /** + * Syncs the install owner's data if needed (i.e., if the install owner is different from the loaded user). + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + private function maybe_sync_install_user() { + if ( $this->_user->id == $this->_site->user_id ) { + return; + } + + // Fetch user data and store if found. + $this->sync_user_by_current_install(); + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + */ + private function sync_install( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $site = $this->send_install_update( $override, $flush ); + + if ( false === $site ) { + // No sync required. + return; + } + + if ( ! $this->is_api_result_entity( $site ) ) { + // Failed to sync, don't update locally. + return; + } + + $this->_site = new FS_Site( $site ); + + $this->_store_site( true ); + } + + /** + * Update install only if changed. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string[] string $override + * @param bool $flush + */ + private function sync_installs( $override = array(), $flush = false ) { + $this->_logger->entrance(); + + $result = $this->send_installs_update( $override, $flush ); + + if ( false === $result ) { + // No sync required. + return; + } + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + // Failed to sync, don't update locally. + return; + } + + $address_to_blog_map = $this->get_address_to_blog_map(); + + foreach ( $result->installs as $install ) { + $this->_site = new FS_Site( $install ); + + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id ); + } + } + + /** + * Track install's custom event. + * + * IMPORTANT: + * Custom event tracking is currently only supported for specific clients. + * If you are not one of them, please don't use this method. If you will, + * the API will simply ignore your request based on the plugin ID. + * + * Need custom tracking for your plugin or theme? + * If you are interested in custom event tracking please contact yo@freemius.com + * for further details. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $name Event name. + * @param array $properties Associative key/value array with primitive values only + * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. + * @param bool $once If true, event will be tracked only once. IMPORTANT: Still trigger the API call. + * + * @return object|false Event data or FALSE on failure. + * + * @throws \Freemius_InvalidArgumentException + */ + public function track_event( $name, $properties = array(), $process_at = false, $once = false ) { + $this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) ); + + if ( ! $this->is_registered() ) { + return false; + } + + $event = array( 'type' => $name ); + + if ( is_numeric( $process_at ) && $process_at > time() ) { + $event['process_at'] = $process_at; + } + + if ( $once ) { + $event['once'] = true; + } + + if ( ! empty( $properties ) ) { + // Verify associative array values are primitive. + foreach ( $properties as $k => $v ) { + if ( ! is_scalar( $v ) ) { + throw new Freemius_InvalidArgumentException( 'The $properties argument must be an associative key/value array with primitive values only.' ); + } + } + + $event['properties'] = $properties; + } + + $result = $this->get_api_site_scope()->call( 'events.json', 'post', $event ); + + return $this->is_api_error( $result ) ? + false : + $result; + } + + /** + * Track install's custom event only once, but it still triggers the API call. + * + * IMPORTANT: + * Custom event tracking is currently only supported for specific clients. + * If you are not one of them, please don't use this method. If you will, + * the API will simply ignore your request based on the plugin ID. + * + * Need custom tracking for your plugin or theme? + * If you are interested in custom event tracking please contact yo@freemius.com + * for further details. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $name Event name. + * @param array $properties Associative key/value array with primitive values only + * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. + * + * @return object|false Event data or FALSE on failure. + * + * @throws \Freemius_InvalidArgumentException + * + * @user Freemius::track_event() + */ + public function track_event_once( $name, $properties = array(), $process_at = false ) { + return $this->track_event( $name, $properties, $process_at, true ); + } + + /** + * Plugin uninstall hook. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $check_user Enforce checking if user have plugins activation privileges. + */ + function _uninstall_plugin_event( $check_user = true ) { + $this->_logger->entrance( 'slug = ' . $this->_slug ); + + if ( $check_user && ! current_user_can( 'activate_plugins' ) ) { + return; + } + + $params = array(); + $uninstall_reason = null; + if ( isset( $this->_storage->uninstall_reason ) ) { + $uninstall_reason = $this->_storage->uninstall_reason; + $params['reason_id'] = $uninstall_reason->id; + $params['reason_info'] = $uninstall_reason->info; + } + + if ( ! $this->is_registered() ) { + // Send anonymous uninstall event only if user submitted a feedback. + if ( isset( $uninstall_reason ) ) { + if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) { + $this->opt_in( false, false, false, false, true ); + } else { + $params['uid'] = $this->get_anonymous_id(); + $this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params ); + } + } + } else { + $params = array_merge( $params, array( + 'is_active' => false, + 'is_uninstalled' => true, + ) ); + + if ( $this->_is_network_active ) { + // Send uninstall event. + $this->send_installs_update( $params ); + } else { + // Send uninstall event. + $this->send_install_update( $params ); + } + } + + // @todo Decide if we want to delete plugin information from db. + } + + /** + * Set the basename of the current product and hook _activate_plugin_event_hook() to the activation action. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @param string $is_premium + * @param string $caller + * + * @return string + */ + function set_basename( $is_premium, $caller ) { + $basename = plugin_basename( $caller ); + + $current_basename = $is_premium ? + $this->_premium_plugin_basename : + $this->_free_plugin_basename; + + if ( $current_basename == $basename ) { + // Basename value set correctly. + return; + } + + if ( $is_premium ) { + $this->_premium_plugin_basename = $basename; + } else { + $this->_free_plugin_basename = $basename; + } + + $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; + + register_activation_hook( + $plugin_dir . $basename, + array( &$this, '_activate_plugin_event_hook' ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @since 2.2.1 If the context product is in its premium version, use the current module's basename, even if it was renamed. + * + * @return string + */ + function premium_plugin_basename() { + if ( ! isset( $this->_premium_plugin_basename ) ) { + $this->_premium_plugin_basename = $this->is_premium() ? + // The product is premium, so use the current basename. + $this->_plugin_basename : + $this->get_premium_slug() . '/' . basename( $this->_free_plugin_basename ); + } + + return $this->_premium_plugin_basename; + } + + /** + * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + */ + public static function _uninstall_plugin_hook() { + self::_load_required_static(); + + self::$_static_logger->entrance(); + + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + $plugin_file = substr( current_filter(), strlen( 'uninstall_' ) ); + + self::$_static_logger->info( 'plugin = ' . $plugin_file ); + + define( 'WP_FS__UNINSTALL_MODE', true ); + + $fs = self::get_instance_by_file( $plugin_file ); + + if ( is_object( $fs ) ) { + $fs->remove_sdk_reference(); + + self::require_plugin_essentials(); + + if ( is_plugin_active( $fs->_free_plugin_basename ) || + is_plugin_active( $fs->premium_plugin_basename() ) + ) { + // Deleting Free or Premium plugin version while the other version still installed. + return; + } + + $fs->_uninstall_plugin_event(); + + $fs->do_action( 'after_uninstall' ); + } + } + + #---------------------------------------------------------------------------------- + #region Plugin Information + #---------------------------------------------------------------------------------- + + /** + * Load WordPress core plugin.php essential module. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + */ + private static function require_plugin_essentials() { + if ( ! function_exists( 'get_plugins' ) ) { + self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' ); + + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + } + + /** + * Load WordPress core pluggable.php module. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + */ + private static function require_pluggable_essentials() { + if ( ! function_exists( 'wp_get_current_user' ) ) { + require_once ABSPATH . 'wp-includes/pluggable.php'; + } + } + + /** + * Return plugin data. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $reparse_plugin_metadata + * + * @return array + */ + function get_plugin_data( $reparse_plugin_metadata = false ) { + if ( ! isset( $this->_plugin_data ) || $reparse_plugin_metadata ) { + self::require_plugin_essentials(); + + if ( $this->is_plugin() ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 When using get_plugin_data() do NOT translate plugin data. + * + * @link https://github.com/Freemius/wordpress-sdk/issues/77 + */ + $plugin_data = get_plugin_data( + $this->_plugin_main_file_path, + false, + false + ); + } else { + $theme_data = wp_get_theme(); + + if ( $this->_plugin_basename !== $theme_data->get_stylesheet() && is_child_theme() ) { + $parent_theme = $theme_data->parent(); + + if ( ( $parent_theme instanceof WP_Theme ) && $this->_plugin_basename === $parent_theme->get_stylesheet() ) { + $theme_data = $parent_theme; + } + } + + $plugin_data = array( + 'Name' => $theme_data->get( 'Name' ), + 'Version' => $theme_data->get( 'Version' ), + 'Author' => $theme_data->get( 'Author' ), + 'Description' => $theme_data->get( 'Description' ), + 'PluginURI' => $theme_data->get( 'ThemeURI' ), + ); + } + + $this->_plugin_data = $plugin_data; + } + + return $this->_plugin_data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * @since 1.2.2.5 If slug not set load slug by module ID. + * + * @return string Plugin slug. + */ + function get_slug() { + if ( ! isset( $this->_slug ) ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_slug = $id_slug_type_path_map[ $this->_module_id ]['slug']; + } + + return $this->_slug; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @return string + */ + function get_premium_slug() { + return is_object( $this->_plugin ) ? + $this->_plugin->premium_slug : + "{$this->_slug}-premium"; + } + + /** + * Retrieve the desired folder name for the product. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return string Plugin slug. + */ + function get_target_folder_name() { + return $this->can_use_premium_code() ? + $this->_plugin->premium_slug : + $this->_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return number Plugin ID. + */ + function get_id() { + return $this->_plugin->id; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return number|null Bundle ID. + */ + function get_bundle_id() { + return ( isset( $this->_plugin->bundle_id ) && FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) ) ? + $this->_plugin->bundle_id : + null; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return string|null Bundle public key. + */ + function get_bundle_public_key() { + return isset( $this->_plugin->bundle_public_key ) ? + $this->_plugin->bundle_public_key : + null; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return string Freemius SDK version + */ + function get_sdk_version() { + return $this->version; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return number Parent plugin ID (if parent exist). + */ + function get_parent_id() { + return $this->is_addon() ? + $this->get_parent_instance()->get_id() : + $this->_plugin->id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return string + */ + function get_usage_tracking_terms_url() { + return $this->apply_filters( + 'usage_tracking_terms_url', + "https://freemius.com/wordpress/usage-tracking/{$this->_plugin->id}/{$this->_slug}/" + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return string + */ + function get_eula_url() { + return $this->apply_filters( + 'eula_url', + "https://freemius.com/terms/{$this->_plugin->id}/{$this->_slug}/" + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return string Plugin public key. + */ + function get_public_key() { + return $this->_plugin->public_key; + } + + /** + * Will be available only on sandbox mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return mixed Plugin secret key. + */ + function get_secret_key() { + return $this->_plugin->secret_key; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @return bool + */ + function has_secret_key() { + return ! empty( $this->_plugin->secret_key ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string|bool $premium_suffix + * + * @return string + */ + function get_plugin_name( $premium_suffix = false ) { + $this->_logger->entrance(); + + /** + * This `if-else` can be squeezed into a single `if` but I intentionally split it for code readability. + * + * @author Vova Feldman + */ + if ( ! isset( $this->_plugin_name ) ) { + // Name is not yet set. + $this->set_name( $premium_suffix ); + } else if ( + ! empty( $premium_suffix ) && + ( ! is_object( $this->_plugin ) || $this->_plugin->premium_suffix !== $premium_suffix ) + ) { + // Name is already set, but there's a change in the premium suffix. + $this->set_name( $premium_suffix ); + } + + return $this->_plugin_name; + } + + /** + * Calculates and stores the product's name. This helper function was created specifically for get_plugin_name() just to make the code clearer. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @param string $premium_suffix + */ + private function set_name( $premium_suffix = '' ) { + $plugin_data = $this->get_plugin_data(); + + // Get name. + $this->_plugin_name = $plugin_data['Name']; + + if ( is_string( $premium_suffix ) ) { + $premium_suffix = trim( $premium_suffix ); + + if ( ! empty( $premium_suffix ) ) { + // Check if plugin name contains " (premium)" or a custom suffix and remove it. + $suffix = ( ' ' . strtolower( $premium_suffix ) ); + $suffix_len = strlen( $suffix ); + + if ( strlen( $plugin_data['Name'] ) > $suffix_len && + $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len ) + ) { + $this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len ); + } + } + } + + $this->_logger->departure( 'Name = ' . $this->_plugin_name ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param bool $reparse_plugin_metadata + * + * @return string + */ + function get_plugin_version( $reparse_plugin_metadata = false ) { + $this->_logger->entrance(); + + $plugin_data = $this->get_plugin_data( $reparse_plugin_metadata ); + + $this->_logger->departure( 'Version = ' . $plugin_data['Version'] ); + + return $this->apply_filters( 'plugin_version', $plugin_data['Version'] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @return string + */ + function get_plugin_title() { + $this->_logger->entrance(); + + $title = $this->_plugin->title; + + return $this->apply_filters( 'plugin_title', $title ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param bool $lowercase + * + * @return string + */ + function get_module_label( $lowercase = false ) { + $label = $this->is_addon() ? + $this->get_text_inline( 'Add-On', 'addon' ) : + ( $this->is_plugin() ? + $this->get_text_inline( 'Plugin', 'plugin' ) : + $this->get_text_inline( 'Theme', 'theme' ) ); + + if ( $lowercase ) { + $label = strtolower( $label ); + } + + return $label; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return string + */ + function get_plugin_basename() { + if ( ! isset( $this->_plugin_basename ) ) { + if ( $this->is_plugin() ) { + $this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path ); + } else { + $this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) ); + } + } + + return $this->_plugin_basename; + } + + function get_plugin_folder_name() { + $this->_logger->entrance(); + + $plugin_folder = $this->_plugin_basename; + + while ( '.' !== dirname( $plugin_folder ) ) { + $plugin_folder = dirname( $plugin_folder ); + } + + $this->_logger->departure( 'Folder Name = ' . $plugin_folder ); + + return $plugin_folder; + } + + #endregion ------------------------------------------------------------------ + + /* Account + ------------------------------------------------------------------------------------------------------------------*/ + + /** + * Find plugin's slug by plugin's basename. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $plugin_base_name + * + * @return false|string + */ + private static function find_slug_by_basename( $plugin_base_name ) { + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + + if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) { + return false; + } + + return $file_slug_map[ $plugin_base_name ]; + } + + /** + * Store the map between the plugin's basename to the slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + private function store_file_slug_map() { + $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); + + if ( ! array( $file_slug_map ) ) { + $file_slug_map = array(); + } + + if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) || + $file_slug_map[ $this->_plugin_basename ] !== $this->_slug + ) { + $file_slug_map[ $this->_plugin_basename ] = $this->_slug; + self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); + } + } + + /** + * @return array[number]FS_User + */ + static function get_all_users() { + $users = self::maybe_get_entities_account_option( 'users', array() ); + + if ( ! is_array( $users ) ) { + $users = array(); + } + + return $users; + } + + /** + * @param string $module_type + * @param null|int $blog_id Since 2.0.0 + * + * @return array[string]FS_Site + */ + private static function get_all_sites( + $module_type = WP_FS__MODULE_TYPE_PLUGIN, + $blog_id = null + ) { + $sites = self::get_account_option( 'sites', $module_type, $blog_id ); + + if ( ! is_array( $sites ) ) { + $sites = array(); + } + + return $sites; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.2 + * + * @param string $option_name + * @param string $module_type + * @param null|int $network_level_or_blog_id Since 2.0.0 + * + * @return mixed + */ + private static function get_account_option( $option_name, $module_type = null, $network_level_or_blog_id = null ) { + if ( ! is_null( $module_type ) && WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + return self::maybe_get_entities_account_option( $option_name, array(), $network_level_or_blog_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @since 1.2.2 + * + * @param string $option_name + * @param mixed $option_value + * @param bool $store + * @param null|int $network_level_or_blog_id Since 2.0.0 + */ + private function set_account_option( $option_name, $option_value, $store, $network_level_or_blog_id = null ) { + self::set_account_option_by_module( + $this->_module_type, + $option_name, + $option_value, + $store, + $network_level_or_blog_id + ); + } + + /** + * @author Vova Feldman (@svovaf) + * + * @since 1.2.2.7 + * + * @param string $module_type + * @param string $option_name + * @param mixed $option_value + * @param bool $store + * @param null|int $network_level_or_blog_id Since 2.0.0 + */ + private static function set_account_option_by_module( + $module_type, + $option_name, + $option_value, + $store, + $network_level_or_blog_id = null + ) { + if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) { + $option_name = $module_type . '_' . $option_name; + } + + self::$_accounts->set_option( $option_name, $option_value, $store, $network_level_or_blog_id ); + } + + /** + * This method can also return non-entity or non-entities collection option like the `user_id_license_ids_map` option. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @param string $option_name + * @param mixed $default + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + * + * @return mixed|FS_Plugin[]|FS_User[]|FS_Site[]|FS_Plugin_License[]|FS_Plugin_Plan[]|FS_Plugin_Tag[] + */ + private static function maybe_get_entities_account_option( $option_name, $default = null, $network_level_or_blog_id = null ) { + $option = self::$_accounts->get_option( $option_name, $default, $network_level_or_blog_id ); + + $class_name = ''; + + if ( fs_starts_with( $option_name, WP_FS__MODULE_TYPE_THEME . '_' ) ) { + $option_name = str_replace( WP_FS__MODULE_TYPE_THEME . '_', '', $option_name ); + } + + switch ( $option_name ) { + case 'plugins': + case 'themes': + case 'addons': + $class_name = FS_Plugin::get_class_name(); + break; + case 'users': + $class_name = FS_User::get_class_name(); + break; + case 'sites': + $class_name = FS_Site::get_class_name(); + break; + case 'licenses': + case 'all_licenses': + $class_name = FS_Plugin_License::get_class_name(); + break; + case 'plans': + $class_name = FS_Plugin_Plan::get_class_name(); + break; + case 'updates': + $class_name = FS_Plugin_Tag::get_class_name(); + break; + } + + if ( empty( $class_name ) ) { + return $option; + } + + return fs_get_entities( $option, $class_name ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number|null $module_id + * + * @return FS_Plugin_License[] + */ + private static function get_all_licenses( $module_id = null ) { + $licenses = self::get_account_option( 'all_licenses' ); + + if ( ! is_array( $licenses ) ) { + $licenses = array(); + } + + if ( is_null( $module_id ) ) { + return $licenses; + } + + $licenses = isset( $licenses[ $module_id ] ) ? + $licenses[ $module_id ] : + array(); + + return $licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return array + */ + private static function get_all_licenses_by_module_type() { + $licenses = self::get_account_option( 'all_licenses' ); + + $licenses_by_module_type = array( + WP_FS__MODULE_TYPE_PLUGIN => array(), + WP_FS__MODULE_TYPE_THEME => array() + ); + + if ( ! is_array( $licenses ) ) { + return $licenses_by_module_type; + } + + foreach ( $licenses as $module_id => $module_licenses ) { + $fs = self::get_instance_by_id( $module_id ); + if ( false === $fs ) { + continue; + } + + $licenses_by_module_type[ $fs->_module_type ] = array_merge( $licenses_by_module_type[ $fs->_module_type ], $module_licenses ); + } + + return $licenses_by_module_type; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param number $module_id + * @param number|null $user_id + * + * @return array + */ + private static function get_user_id_license_ids_map( $module_id, $user_id = null ) { + $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); + + if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { + $all_modules_user_id_license_ids_map = array(); + } + + $user_id_license_ids_map = isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ? + $all_modules_user_id_license_ids_map[ $module_id ] : + array(); + + if ( FS_User::is_valid_id( $user_id ) ) { + $user_id_license_ids_map = isset( $user_id_license_ids_map[ $user_id ] ) ? + $user_id_license_ids_map[ $user_id ] : + array(); + } + + return $user_id_license_ids_map; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array $new_user_id_license_ids_map + * @param number $module_id + * @param number|null $user_id + */ + private static function store_user_id_license_ids_map( $new_user_id_license_ids_map, $module_id, $user_id = null ) { + $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); + if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { + $all_modules_user_id_license_ids_map = array(); + } + + if ( ! isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ) { + $all_modules_user_id_license_ids_map[ $module_id ] = array(); + } + + if ( FS_User::is_valid_id( $user_id ) ) { + $all_modules_user_id_license_ids_map[ $module_id ][ $user_id ] = $new_user_id_license_ids_map; + } else { + $all_modules_user_id_license_ids_map[ $module_id ] = $new_user_id_license_ids_map; + } + + self::$_accounts->set_option( 'user_id_license_ids_map', $all_modules_user_id_license_ids_map, true ); + } + + /** + * Get a collection of the user's linked license IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return number[] + */ + private function get_user_linked_license_ids( $user_id ) { + return self::get_user_id_license_ids_map( $this->_module_id, $user_id ); + } + + /** + * Override the user's linked license IDs with a new IDs collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param number[] $license_ids + */ + private function set_user_linked_license_ids( $user_id, array $license_ids ) { + self::store_user_id_license_ids_map( $license_ids, $this->_module_id, $user_id ); + } + + /** + * Link a specified license ID to a given user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param number $user_id + */ + private function link_license_2_user( $license_id, $user_id ) { + $license_ids = $this->get_user_linked_license_ids( $user_id ); + + if ( in_array( $license_id, $license_ids ) ) { + // License already linked. + return; + } + + $license_ids[] = $license_id; + + $this->set_user_linked_license_ids( $user_id, $license_ids ); + } + + /** + * @param string|bool $module_type + * + * @return FS_Plugin_Plan[] + */ + private static function get_all_plans( $module_type = false ) { + $plans = self::get_account_option( 'plans', $module_type ); + + if ( ! is_array( $plans ) ) { + $plans = array(); + } + + return $plans; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return FS_Plugin_Tag[] + */ + private static function get_all_updates() { + $updates = self::maybe_get_entities_account_option( 'updates', array() ); + + if ( ! is_array( $updates ) ) { + $updates = array(); + } + + return $updates; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return array|false + */ + private static function get_all_addons() { + $addons = self::maybe_get_entities_account_option( 'addons', array() ); + + if ( ! is_array( $addons ) ) { + $addons = array(); + } + + return $addons; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return number[]|false + */ + private static function get_all_account_addons() { + $addons = self::$_accounts->get_option( 'account_addons', array() ); + + if ( ! is_array( $addons ) ) { + $addons = array(); + } + + return $addons; + } + + /** + * Check if user has connected his account (opted-in). + * + * Note: + * If the user opted-in and opted-out on a later stage, + * this will still return true. If you want to check if the + * user is currently opted-in, use: + * `$fs->is_registered() && $fs->is_tracking_allowed()` + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * @return bool + */ + function is_registered() { + return is_object( $this->_user ); + } + + /** + * Returns TRUE if the user opted-in and didn't disconnect (opt-out). + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + * + * @return bool + */ + function is_tracking_allowed() { + return ( is_object( $this->_site ) && $this->_site->is_tracking_allowed() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + * + * @return bool + */ + function is_bundle_license_auto_activation_enabled() { + return $this->is_addon() ? + ( is_object( $this->_parent ) && $this->_parent->is_bundle_license_auto_activation_enabled() ) : + $this->_is_bundle_license_auto_activation_enabled; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return FS_Plugin + */ + function get_plugin() { + return $this->_plugin; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return FS_User + */ + function get_user() { + return $this->_user; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return FS_Site + */ + function get_site() { + return $this->_site; + } + + /** + * Get plugin add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @since 1.1.7.3 If not yet loaded, fetch data from the API. + * + * @param bool $flush + * + * @return FS_Plugin[]|false + */ + function get_addons( $flush = false ) { + $this->_logger->entrance(); + + if ( ! $this->_has_addons ) { + return false; + } + + $addons = $this->sync_addons( $flush ); + + return ( ! is_array( $addons ) || empty( $addons ) ) ? + false : + $addons; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return number[]|false + */ + function get_account_addons() { + $this->_logger->entrance(); + + $addons = self::get_all_account_addons(); + + if ( ! is_array( $addons ) || + ! isset( $addons[ $this->_plugin->id ] ) || + ! is_array( $addons[ $this->_plugin->id ] ) || + 0 === count( $addons[ $this->_plugin->id ] ) + ) { + return false; + } + + return $addons[ $this->_plugin->id ]; + } + + /** + * Check if user has any + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + function has_account_addons() { + $addons = $this->get_account_addons(); + + return is_array( $addons ) && ( 0 < count( $addons ) ); + } + + + /** + * Get add-on by ID (from local data). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number $id + * + * @return FS_Plugin|false + */ + function get_addon( $id ) { + $this->_logger->entrance(); + + $addons = $this->get_addons(); + + if ( is_array( $addons ) ) { + foreach ( $addons as $addon ) { + if ( $id == $addon->id ) { + return $addon; + } + } + } + + return false; + } + + /** + * Get add-on by slug (from local data). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $slug + * + * @param bool $flush + * + * @return FS_Plugin|false + */ + function get_addon_by_slug( $slug, $flush = false ) { + $this->_logger->entrance(); + + $addons = $this->get_addons( $flush ); + + if ( is_array( $addons ) ) { + foreach ( $addons as $addon ) { + if ( $slug === $addon->slug ) { + return $addon; + } + } + } + + return false; + } + + /** + * @var array { + * @key number Add-on ID. + * @val object[] The add-on's plans and prices object. + * } + */ + private $plans_and_pricing_by_addon_id; + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return array { + * @key number Add-on ID. + * @val object[] The add-on's plans and prices object. + * } + */ + function _get_addons_plans_and_pricing_map_by_id() { + if ( ! isset( $this->plans_and_pricing_by_addon_id ) ) { + $result = $this->get_api_plugin_scope()->get( $this->add_show_pending( "/addons/pricing.json?type=visible" ) ); + + $plans_and_pricing_by_addon_id = array(); + if ( $this->is_api_result_object( $result, 'addons' ) ) { + foreach ( $result->addons as $addon ) { + $plans_and_pricing_by_addon_id[ $addon->id ] = $addon->plans; + } + } + + $this->plans_and_pricing_by_addon_id = $plans_and_pricing_by_addon_id; + } + + return $this->plans_and_pricing_by_addon_id; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param number $addon_id + * @param bool $is_installed + * + * @return array + */ + function _get_addon_info( $addon_id, $is_installed ) { + $addon = $this->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + // Unexpected call. + return array(); + } + + $slug = $addon->slug; + + $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); + + if ( ! fs_is_network_admin() ) { + // Get blog-level activated installations. + $sites = self::maybe_get_entities_account_option( 'sites', array() ); + } else { + $sites = null; + + if ( $this->is_addon_activated( $addon_id ) && + $this->get_addon_instance( $addon_id )->is_network_active() + ) { + if ( FS_Site::is_valid_id( $addon_storage->network_install_blog_id ) ) { + // Get network-level activated installations. + $sites = self::maybe_get_entities_account_option( + 'sites', + array(), + $addon_storage->network_install_blog_id + ); + } + } + } + + $addon_info = array( + 'is_connected' => false, + 'slug' => $slug, + 'title' => $addon->title, + 'is_whitelabeled' => $addon_storage->is_whitelabeled + ); + + if ( ! $is_installed ) { + $plans_and_pricing_by_addon_id = $this->_get_addons_plans_and_pricing_map_by_id(); + + if ( isset( $plans_and_pricing_by_addon_id[ $addon_id ] ) ) { + $has_paid_plan = false; + $plans = $plans_and_pricing_by_addon_id[ $addon_id ]; + + if ( is_array( $plans ) && count( $plans ) > 0 ) { + foreach ( $plans as $plan ) { + if ( isset( $plan->pricing ) && + is_array( $plan->pricing ) && + count( $plan->pricing ) > 0 + ) { + $has_paid_plan = true; + break; + } + } + } + + $addon_info['has_paid_plan'] = $has_paid_plan; + } + } + + if ( ! is_array( $sites ) || ! isset( $sites[ $slug ] ) ) { + return $addon_info; + } + + $site = $sites[ $slug ]; + + $addon_info['is_connected'] = ( + ( $addon->parent_plugin_id == $this->get_id() ) && + is_object( $site ) && + FS_Site::is_valid_id( $site->id ) && + FS_User::is_valid_id( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ); + + if ( $addon_info['is_connected'] && $is_installed ) { + return $addon_info; + } + + $addon_info['site'] = $site; + + $plugins_data = self::maybe_get_entities_account_option( WP_FS__MODULE_TYPE_PLUGIN . 's', array() ); + if ( isset( $plugins_data[ $slug ] ) ) { + $plugin_data = $plugins_data[ $slug ]; + + $addon_info['version'] = $plugin_data->version; + } + + $all_plans = self::maybe_get_entities_account_option( 'plans', array() ); + if ( isset( $all_plans[ $slug ] ) ) { + $plans = $all_plans[ $slug ]; + + foreach ( $plans as $plan ) { + if ( $site->plan_id == Freemius::_decrypt( $plan->id ) ) { + $addon_info['plan_name'] = Freemius::_decrypt( $plan->name ); + $addon_info['plan_title'] = Freemius::_decrypt( $plan->title ); + break; + } + } + } + + $licenses = self::maybe_get_entities_account_option( 'all_licenses', array() ); + if ( is_array( $licenses ) && isset( $licenses[ $addon_id ] ) ) { + foreach ( $licenses[ $addon_id ] as $license ) { + if ( $license->id == $site->license_id ) { + $addon_info['license'] = $license; + break; + } + } + } + + if ( isset( $addon_info['license'] ) ) { + if ( isset( $addon_storage->subscriptions ) && + ! empty( $addon_storage->subscriptions ) + ) { + $addon_subscriptions = fs_get_entities( $addon_storage->subscriptions, FS_Subscription::get_class_name() ); + + foreach ( $addon_subscriptions as $subscription ) { + if ( $subscription->license_id == $site->license_id ) { + $addon_info['subscription'] = $subscription; + break; + } + } + } + } + + return $addon_info; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return FS_User + */ + static function _get_user_by_id( $user_id ) { + self::$_static_logger->entrance( "user_id = {$user_id}" ); + + $users = self::get_all_users(); + + if ( is_array( $users ) ) { + if ( isset( $users[ $user_id ] ) && + $users[ $user_id ] instanceof FS_User && + $user_id == $users[ $user_id ]->id + ) { + return $users[ $user_id ]; + } + + // If user wasn't found by the key, iterate over all the users collection. + foreach ( $users as $user ) { + /** + * @var FS_User $user + */ + if ( $user_id == $user->id ) { + return $user; + } + } + } + + return null; + } + + /** + * Checks if a Freemius user_id is associated with a super-admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return bool + */ + private static function is_super_admin( $user_id ) { + $is_super_admin = false; + + $user = self::_get_user_by_id( $user_id ); + + if ( $user instanceof FS_User && ! empty( $user->email ) ) { + self::require_pluggable_essentials(); + + $wp_user = get_user_by( 'email', $user->email ); + + if ( $wp_user instanceof WP_User ) { + $super_admins = get_super_admins(); + $is_super_admin = ( is_array( $super_admins ) && in_array( $wp_user->user_login, $super_admins ) ); + } + } + + return $is_super_admin; + } + + #---------------------------------------------------------------------------------- + #region Plans & Licensing + #---------------------------------------------------------------------------------- + + /** + * Check if running premium plugin code. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_premium() { + /** + * `$this->_plugin` will be `false` when `is_activation_mode` calls this method directly from the + * `register_constructor_hooks` method. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + return is_object( $this->_plugin ) ? + $this->_plugin->is_premium : + false; + } + + /** + * Get site's plan ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return number + */ + function get_plan_id() { + return $this->_site->plan_id; + } + + /** + * Get site's plan title. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return string + */ + function get_plan_title() { + $plan = $this->get_plan(); + + return is_object( $plan ) ? $plan->title : 'PLAN_TITLE'; + } + + /** + * Get site's plan name. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_plan_name() { + $plan = $this->get_plan(); + + return is_object( $plan ) ? $plan->name : 'PLAN_NAME'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return FS_Plugin_Plan|false + */ + function get_plan() { + if ( ! is_object( $this->_site ) ) { + return false; + } + + return FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) ? + $this->_get_plan_by_id( $this->_site->plan_id ) : + false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() || ! is_object( $this->_site ) ) { + return false; + } + + return $this->_site->is_trial(); + } + + /** + * Check if currently in a trial with payment method (credit card or paypal). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function is_paid_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return false; + } + + if ( ! $this->has_active_valid_license() ) { + return false; + } + + if ( $this->_site->trial_plan_id != $this->_license->plan_id ) { + return false; + } + + /** + * @var FS_Subscription $subscription + */ + $subscription = $this->_get_subscription( $this->_license->id ); + + return ( is_object( $subscription ) && $subscription->is_active() ); + } + + /** + * Check if trial already utilized. + * + * @since 1.0.9 + * + * @return bool + */ + function is_trial_utilized() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + return $this->_site->is_trial_utilized(); + } + + /** + * Get trial plan information (if in trial). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool|FS_Plugin_Plan + */ + function get_trial_plan() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return false; + } + + // Try to load plan from local cache. + $trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id ); + + if ( ! is_object( $trial_plan ) ) { + $trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id ); + + /** + * If managed to fetch the plan, add it to the plans collection. + */ + if ( $trial_plan instanceof FS_Plugin_Plan ) { + if ( ! is_array( $this->_plans ) ) { + $this->_plans = array(); + } + + $this->_plans[] = $trial_plan; + $this->_store_plans(); + } + } + + if ( $trial_plan instanceof FS_Plugin_Plan ) { + return $trial_plan; + } + + /** + * If for some reason failed to get the trial plan, fallback to a dummy name and title. + */ + $trial_plan = new FS_Plugin_Plan(); + $trial_plan->id = $this->_site->trial_plan_id; + $trial_plan->name = 'pro'; + $trial_plan->title = 'Pro'; + + return $trial_plan; + } + + /** + * Check if the user has an activate, non-expired license on current plugin's install. + * + * @since 1.0.9 + * + * @return bool + */ + function is_paying() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + if ( ! $this->has_paid_plan() ) { + return false; + } + + return ( + ! $this->is_trial() && + 'free' !== $this->get_plan_name() && + $this->has_active_valid_license() + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return bool + */ + function is_free_plan() { + if ( ! $this->is_registered() ) { + return true; + } + + if ( ! $this->has_paid_plan() ) { + return true; + } + + return ( + 'free' === $this->get_plan_name() || + ! $this->has_features_enabled_license() + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function _has_premium_license() { + $this->_logger->entrance(); + + $premium_license = $this->_get_available_premium_license(); + + return ( false !== $premium_license ); + } + + /** + * Check if user has any licenses associated with the plugin (including expired or blocking). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param bool $including_foreign + * + * @return bool + */ + function has_any_license( $including_foreign = true ) { + if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) { + return false; + } + + if ( $including_foreign ) { + return true; + } + + foreach ( $this->_licenses as $license ) { + if ( $this->_user->id == $license->user_id ) { + return true; + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|null $is_localhost + * + * @return FS_Plugin_License|false + */ + function _get_available_premium_license( $is_localhost = null ) { + $this->_logger->entrance(); + + $licenses = $this->get_available_premium_licenses( $is_localhost ); + if ( ! empty( $licenses ) ) { + return $licenses[0]; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|null $is_localhost + * + * @return FS_Plugin_License[] + */ + function get_available_premium_licenses( $is_localhost = null ) { + $this->_logger->entrance(); + + $licenses = array(); + if ( ! $this->has_paid_plan() ) { + return $licenses; + } + + if ( is_array( $this->_licenses ) ) { + foreach ( $this->_licenses as $license ) { + if ( ! $license->can_activate( $is_localhost ) ) { + continue; + } + + $licenses[] = $license; + } + } + + return $licenses; + } + + /** + * Sync local plugin plans with remote server. + * + * IMPORTANT: If for some reason a site is associated with deleted plan, we'll preserve the plan's information and append it as the last plan. This means that if plan is deleted, the is_plan() method will ALWAYS return true for any given argument (it becomes the most inclusive plan). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return FS_Plugin_Plan[]|object + */ + function _sync_plans() { + $plans = $this->_fetch_plugin_plans(); + + if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { + $plans_map = array(); + foreach ( $plans as $plan ) { + $plans_map[ $plan->id ] = true; + } + + $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); + + foreach ( $plans_ids_to_keep as $plan_id ) { + if ( isset( $plans_map[ $plan_id ] ) ) { + continue; + } + + $missing_plan = self::_get_plan_by_id( $plan_id ); + + if ( is_object( $missing_plan ) ) { + $plans[] = $missing_plan; + } + } + + $this->_plans = $plans; + $this->_store_plans(); + } + + $this->do_action( 'after_plans_sync', $plans ); + + return $this->_plans; + } + + /** + * Check if specified plan exists locally. If not, fetch it and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $plan_id + * + * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. + */ + private function sync_plan_if_not_exist( $plan_id ) { + $plan = self::_get_plan_by_id( $plan_id ); + + if ( is_object( $plan ) ) { + // Plan already exists. + return $plan; + } + + $plan = $this->fetch_plan_by_id( $plan_id ); + + if ( $plan instanceof FS_Plugin_Plan ) { + $this->_plans[] = $plan; + $this->_store_plans(); + + return $plan; + } + + return $plan; + } + + /** + * Check if specified license exists locally. If not, fetch it and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param string $license_key + * + * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. + */ + private function sync_license_if_not_exist( $license_id, $license_key ) { + $license = $this->_get_license_by_id( $license_id ); + + if ( is_object( $license ) ) { + // License already exists. + return $license; + } + + $license = $this->fetch_license_by_key( $license_id, $license_key ); + + if ( $license instanceof FS_Plugin_License ) { + $this->_licenses[] = $license; + + $this->set_license( $license ); + + $this->_store_licenses(); + + return $license; + } + + return $license; + } + + /** + * Get a collection of unique plan IDs that are associated with any installs in the network. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return number[] + */ + private function get_plans_ids_associated_with_installs() { + if ( ! is_multisite() ) { + if ( ! is_object( $this->_site ) || + ! FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) + ) { + return array(); + } + + return array( $this->_site->plan_id ); + } + + $plan_ids = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ) { + continue; + } + + $plan_ids[ $install->plan_id ] = true; + } + + return array_keys( $plan_ids ); + } + + /** + * Get a collection of unique license IDs that are associated with any installs in the network. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return number[] + */ + private function get_license_ids_associated_with_installs() { + if ( ! $this->_is_network_active ) { + if ( ! is_object( $this->_site ) || + ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) + ) { + return array(); + } + + return array( $this->_site->license_id ); + } + + $license_ids = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + continue; + } + + $license_ids[ $install->license_id ] = true; + } + + return array_keys( $license_ids ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param number $id + * + * @return FS_Plugin_Plan|false + */ + function _get_plan_by_id( $id ) { + $this->_logger->entrance(); + + if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { + $this->_sync_plans(); + } + + foreach ( $this->_plans as $plan ) { + if ( $id == $plan->id ) { + return $plan; + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @param string $name + * + * @return FS_Plugin_Plan|false + */ + private function get_plan_by_name( $name ) { + $this->_logger->entrance(); + + if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { + $this->_sync_plans(); + } + + foreach ( $this->_plans as $plan ) { + if ( $name == $plan->name ) { + return $plan; + } + } + + return false; + } + + /** + * Sync local licenses with remote server. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param number|bool $site_license_id + * @param number|null $blog_id + * + * @return FS_Plugin_License[]|object + */ + function _sync_licenses( $site_license_id = false, $blog_id = null ) { + $this->_logger->entrance(); + + $is_network_admin = fs_is_network_admin(); + + if ( $is_network_admin && is_null( $blog_id ) ) { + $all_licenses = self::get_all_licenses( $this->_module_id ); + } else { + $all_licenses = $this->get_user_licenses( $this->_user->id ); + } + + $foreign_licenses = $this->get_foreign_licenses_info( $all_licenses, $site_license_id ); + + $all_licenses_map = array(); + foreach ( $all_licenses as $license ) { + $all_licenses_map[ $license->id ] = true; + } + + $licenses = $this->_fetch_licenses( false, $site_license_id, $foreign_licenses, $blog_id ); + + if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { + $licenses_map = array(); + foreach ( $licenses as $license ) { + $licenses_map[ $license->id ] = true; + } + +// $license_ids_to_keep = $this->get_license_ids_associated_with_installs(); +// foreach ( $license_ids_to_keep as $license_id ) { +// if ( isset( $licenses_map[ $license_id ] ) ) { +// continue; +// } +// +// $missing_license = self::_get_license_by_id( $license_id, false ); +// if ( is_object( $missing_license ) ) { +// $licenses[] = $missing_license; +// $licenses_map[ $missing_license->id ] = true; +// } +// } + + $user_license_ids = $this->get_user_linked_license_ids( $this->_user->id ); + + foreach ( $user_license_ids as $key => $license_id ) { + if ( ! isset( $licenses_map[ $license_id ] ) ) { + // Remove access to licenses that no longer exist. + unset( $user_license_ids[ $key ] ); + } + } + + if ( ! empty( $user_license_ids ) ) { + foreach ( $licenses_map as $license_id => $value ) { + if ( ! isset( $all_licenses_map[ $license_id ] ) ) { + // Associate new licenses with the user who triggered the license syncing. + $user_license_ids[] = $license_id; + } + } + + $user_license_ids = array_unique( $user_license_ids ); + } else { + $user_license_ids = array_keys( $licenses_map ); + } + + if ( ! $is_network_admin || ! is_null( $blog_id ) ) { + $user_licenses = array(); + foreach ( $licenses as $license ) { + if ( ! in_array( $license->id, $user_license_ids ) ) { + continue; + } + + $user_licenses[] = $license; + } + + $this->_licenses = $user_licenses; + } else { + $this->_licenses = $licenses; + } + + $this->set_user_linked_license_ids( $this->_user->id, $user_license_ids ); + + $this->_store_licenses( true, $this->_module_id, $licenses ); + } + + // Update current license. + if ( is_object( $this->_license ) ) { + $license = $this->_get_license_by_id( $this->_license->id ); + + if ( is_object( $license ) ) { + /** + * `$license` can be `false` in case a user change action has just been completed and this method + * has synced the `$this->_licenses` collection for the new user. In this case, the + * `$this->_licenses` collection may have only the newly activated license that is associated with + * the new user. `set_license` will eventually be called in the same request by the logic that + * follows outside this method which will detect that the install's license has been updated, and + * then `_update_site_license` will be called which in turn will call `set_license`. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + $this->set_license( $license ); + } + } + + return $this->_licenses; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param number $id + * @param bool $sync_licenses + * + * @return FS_Plugin_License|false + */ + function _get_license_by_id( $id, $sync_licenses = true ) { + $this->_logger->entrance(); + + if ( ! FS_Plugin_License::is_valid_id( $id ) ) { + return false; + } + + /** + * When running from the network level admin and opted-in from the network, + * check if the license exists in the network user licenses collection. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( fs_is_network_admin() && + $this->is_network_registered() && + ( ! is_object( $this->_user ) || $this->_storage->network_user_id != $this->_user->id ) + ) { + $licenses = $this->get_user_licenses( $this->_storage->network_user_id ); + + foreach ( $licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + if ( ! $this->has_any_license() && $sync_licenses ) { + $this->_sync_licenses( $id ); + } + + if ( is_array( $this->_licenses ) ) { + foreach ( $this->_licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + return false; + } + + /** + * Get license by ID. Unlike _get_license_by_id(), this method only checks the local storage and return any license, whether it's associated with the current context user/install or not. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $id + * + * @return FS_Plugin_License + */ + private function get_license_by_id( $id ) { + $licenses = self::get_all_licenses( $this->_module_id ); + + if ( is_array( $licenses ) && ! empty( $licenses ) ) { + foreach ( $licenses as $license ) { + if ( $id == $license->id ) { + return $license; + } + } + } + + return null; + } + + /** + * Synchronize the site's context license by fetching the license form the API and updating the local data with it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return \FS_Plugin_License|mixed + */ + private function sync_site_license() { + $api = $this->get_api_user_scope(); + + $result = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . urlencode( $this->_license->secret_key ), true ); + + if ( ! $this->is_api_result_entity( $result ) ) { + return $result; + } + + $license = $this->_update_site_license( new FS_Plugin_License( $result ) ); + $this->_store_licenses(); + + return $license; + } + + /** + * Get all user's available licenses for the current module. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * + * @return FS_Plugin_License[] + */ + private function get_user_licenses( $user_id ) { + $all_licenses = self::get_all_licenses( $this->_module_id ); + if ( empty( $all_licenses ) ) { + return array(); + } + + $user_license_ids = $this->get_user_linked_license_ids( $user_id ); + if ( empty( $user_license_ids ) ) { + return array(); + } + + $licenses = array(); + foreach ( $all_licenses as $license ) { + if ( in_array( $license->id, $user_license_ids ) ) { + $licenses[] = $license; + } + } + + return $licenses; + } + + /** + * Checks if the context license is network activated except on the given blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $except_blog_id + * + * @return bool + */ + private function is_license_network_active( $except_blog_id = 0 ) { + $this->_logger->entrance(); + + if ( ! is_object( $this->_license ) ) { + return false; + } + + $sites = self::get_sites(); + + if ( $this->_license->total_activations() < ( count( $sites ) - 1 ) ) { + // There are more sites than the number of activations, so license cannot be network activated. + return false; + } + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( $except_blog_id == $blog_id ) { + // Skip excluded blog. + continue; + } + + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) && $install->license_id != $this->_license->id ) { + return false; + } + } + + return true; + } + + /** + * Checks if license can be activated on all the network sites (opted-in or skipped) that are not yet associated with a license. If possible, try to make the activation, if not return false. + * + * Notice: On success, this method will also update the license activations counters (without updating the license in the storage). + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param \FS_Plugin_License $license + * + * @return bool + */ + private function try_activate_license_on_network( FS_User $user, FS_Plugin_License $license ) { + $this->_logger->entrance(); + + $result = $this->can_activate_license_on_network( $license ); + + if ( false === $result ) { + return false; + } + + $installs_without_license = $result['installs']; + if ( ! empty( $installs_without_license ) ) { + $this->activate_license_on_many_installs( $user, $license->secret_key, $installs_without_license ); + } + + $disconnected_site_ids = $result['sites']; + if ( ! empty( $disconnected_site_ids ) ) { + $this->activate_license_on_many_sites( $user, $license->secret_key, $disconnected_site_ids ); + } + + $this->link_license_2_user( $license->id, $user->id ); + + // Sync license after activations. + $license->activated += $result['production_count']; + $license->activated_local += $result['localhost_count']; + +// $this->_store_licenses() + + return true; + } + + /** + * Checks if the given license can be activated on the whole network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_License $license + * + * @return false|array { + * @type array[int]FS_Site $installs Blog ID to install map. + * @type int[] $sites Non-connected blog IDs. + * @type int $production_count Production sites count. + * @type int $localhost_count Production sites count. + * } + */ + private function can_activate_license_on_network( FS_Plugin_License $license ) { + $sites = self::get_sites(); + + $production_count = 0; + $localhost_count = 0; + + $installs_without_license = array(); + $disconnected_site_ids = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { + // License already activated on the install. + continue; + } + + $url = $install->url; + + $installs_without_license[ $blog_id ] = $install; + } else { + $url = is_object( $site ) ? + $site->siteurl : + get_site_url( $blog_id ); + + $disconnected_site_ids[] = $blog_id; + } + + if ( FS_Site::is_localhost_by_address( $url ) ) { + $localhost_count ++; + } else { + $production_count ++; + } + } + + if ( ! $license->can_activate_bulk( $production_count, $localhost_count ) ) { + return false; + } + + return array( + 'installs' => $installs_without_license, + 'sites' => $disconnected_site_ids, + 'production_count' => $production_count, + 'localhost_count' => $localhost_count, + ); + } + + /** + * Activate a given license on a collection of installs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string $license_key + * @param array $blog_2_install_map { + * @key int Blog ID. + * @value FS_Site Blog's associated install. + * } + * + * @return mixed|true + */ + private function activate_license_on_many_installs( + FS_User $user, + $license_key, + array $blog_2_install_map + ) { + $params = array( + array( 'license_key' => $this->apply_filters( 'license_key', $license_key ) ) + ); + + $install_2_blog_map = array(); + foreach ( $blog_2_install_map as $blog_id => $install ) { + $params[] = array( 'id' => $install->id ); + + $install_2_blog_map[ $install->id ] = $blog_id; + } + + $result = $this->get_api_user_scope_by_user( $user )->call( + "plugins/{$this->_plugin->id}/installs.json", + 'PUT', + $params + ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + return $result; + } + + foreach ( $result->installs as $r_install ) { + $install = new FS_Site( $r_install ); + $install->is_disconnected = false; + + // Update install. + $this->_store_site( + true, + $install_2_blog_map[ $r_install->id ], + $install + ); + } + + return true; + } + + /** + * Activate a given license on a collection of blogs/sites that are not yet opted-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @param \FS_User $user + * @param string $license_key + * + * @return true|mixed True if successful, otherwise, the API result. + */ + private function activate_license_on_site( FS_User $user, $license_key ) { + return $this->activate_license_on_many_sites( $user, $license_key ); + } + + /** + * Activate a given license on a collection of blogs/sites that are not yet opted-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string $license_key + * @param int[] $site_ids + * + * @return true|mixed True if successful, otherwise, the API result. + */ + private function activate_license_on_many_sites( + FS_User $user, + $license_key, + array $site_ids = array() + ) { + $sites = array(); + foreach ( $site_ids as $site_id ) { + $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); + } + + // Install the plugin. + $result = $this->create_installs_with_user( + $user, + $license_key, + false, + $sites, + false, + true + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + return $result; + } + + $installs = array(); + + if ( $this->is_api_result_entity( $result ) ) { + $install = new FS_Site( $result ); + + $this->_user = $user; + + $this->_store_site( true, null, $install ); + + $this->_site = $install; + + $this->reset_anonymous_mode(); + } else { + foreach ( $result->installs as $install ) { + $installs[] = new FS_Site( $install ); + } + + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + $first_blog_id = null; + + foreach ( $installs as $install ) { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, $install ); + + $this->reset_anonymous_mode( $blog_id ); + + if ( is_null( $first_blog_id ) ) { + $first_blog_id = $blog_id; + } + } + + if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { + $this->_storage->network_install_blog_id = $first_blog_id; + } + } + + return true; + } + + /** + * Sync site's license with user licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin_License|null $new_license + * + * @return FS_Plugin_License|null + */ + function _update_site_license( $new_license ) { + $this->_logger->entrance(); + + /** + * In case this call will be removed in the future, the `_sync_licenses()` method needs to be updated + * accordingly so that it will also handle the case when an ownership change is done via license + * activation. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + $this->set_license( $new_license ); + + if ( ! is_object( $new_license ) ) { + $this->_site->license_id = null; + $this->_sync_site_subscription( null ); + + return $this->_license; + } + + $this->_site->license_id = $this->_license->id; + + if ( ! is_array( $this->_licenses ) ) { + $this->_licenses = array(); + } + + $is_license_found = false; + for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { + if ( $new_license->id == $this->_licenses[ $i ]->id ) { + $this->_licenses[ $i ] = $new_license; + + $is_license_found = true; + break; + } + } + + // If new license just append. + if ( ! $is_license_found ) { + $this->_licenses[] = $new_license; + } + + $this->_sync_site_subscription( $new_license ); + + return $this->_license; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @param \FS_Plugin_License $license + */ + private function set_license( FS_Plugin_License $license = null ) { + $this->_license = $license; + + $this->maybe_update_whitelabel_flag( $license ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @param FS_Plugin_License $license + */ + private function maybe_update_whitelabel_flag( $license ) { + $is_whitelabeled = isset( $this->_storage->is_whitelabeled ) ? + $this->_storage->is_whitelabeled : + false; + + if ( is_object( $license ) ) { + $license_user = self::_get_user_by_id( $license->user_id ); + + if ( ! is_object( $license_user ) ) { + // If foreign license, do not update the `is_whitelabeled` flag. + return; + } + + if ( $this->is_addon() ) { + /** + * Store the last license data to the parent's storage since it's needed only when showing the + * "Start Debug" dialog which is triggered from the "Account" page. This way, there's no need to + * iterate over the add-ons just to get the last license data. + */ + $this->get_parent_instance()->store_last_activated_license_data( $license, $license_user ); + } else { + $this->store_last_activated_license_data( $license ); + } + + if ( $license->is_whitelabeled ) { + // Activated a developer license, data should be hidden. + $is_whitelabeled = true; + } else if ( $this->is_registered() && $this->_user->id == $license->user_id ) { + // The account owner activated a regular license key, no need to hide the data. + $is_whitelabeled = false; + } + } + + $this->_storage->is_whitelabeled = $is_whitelabeled; + + // Reset the whitelabeled status after update. + $this->is_whitelabeled = null; + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + if ( is_object( $parent_fs ) ) { + $parent_fs->is_whitelabeled = null; + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @param FS_Plugin_License $license + * @param FS_User $license_user + */ + private function store_last_activated_license_data( FS_Plugin_License $license, $license_user = null ) { + if ( ! is_object( $license_user ) ) { + $this->_storage->last_license_key = md5( $license->secret_key ); + $this->_storage->last_license_user_id = null; + } else { + $this->_storage->last_license_user_key = md5( $license_user->secret_key ); + $this->_storage->last_license_user_id = $license_user->id; + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @param bool $ignore_data_debug_mode + * + * @return bool + */ + function is_whitelabeled_by_flag( $ignore_data_debug_mode = false ) { + if ( true !== $this->_storage->is_whitelabeled ) { + return false; + } else if ( $ignore_data_debug_mode ) { + return true; + } + + $fs = $this->is_addon() ? + $this->get_parent_instance() : + $this; + + return ! $fs->is_data_debug_mode(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @return number + */ + function get_last_license_user_id() { + return ( FS_User::is_valid_id( $this->_storage->last_license_user_id ) ) ? + $this->_storage->last_license_user_id : + null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @param int $blog_id + * @param bool $ignore_data_debug_mode + * + * @return bool + */ + function is_whitelabeled( $ignore_data_debug_mode = false, $blog_id = null ) { + if ( ! is_null( $blog_id ) ) { + $this->switch_to_blog( $blog_id ); + } + + if ( ! is_null( $this->is_whitelabeled ) ) { + $is_whitelabeled = $this->is_whitelabeled; + } else { + $is_whitelabeled = false; + + $is_whitelabeled_flag = $this->is_whitelabeled_by_flag( true ); + + if ( ! $this->has_addons() ) { + $is_whitelabeled = $is_whitelabeled_flag; + } else if ( $is_whitelabeled_flag ) { + $is_whitelabeled = true; + } else { + $addon_ids = $this->get_updated_account_addons(); + $installed_addons = $this->get_installed_addons(); + foreach ( $installed_addons as $fs_addon ) { + $addon_ids[] = $fs_addon->get_id(); + } + + if ( ! empty( $addon_ids ) ) { + $addon_ids = array_unique( $addon_ids ); + + $is_network_level = ( + fs_is_network_admin() && + $this->is_network_active() + ); + + foreach ( $addon_ids as $addon_id ) { + $addon = $this->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + continue; + } + + $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); + $fs_addon = $this->is_addon_activated( $addon_id ) ? + self::get_addon_instance( $addon_id ) : + null; + + $was_addon_network_activated = false; + + if ( is_object( $fs_addon ) ) { + $was_addon_network_activated = $fs_addon->is_network_active(); + } else if ( $is_network_level ) { + $was_addon_network_activated = $addon_storage->get( 'was_plugin_loaded', false, true ); + } + + $network_delegated_connection = ( + $was_addon_network_activated && + $addon_storage->get( 'is_delegated_connection', false, true ) + ); + + if ( + $is_network_level && + ( ! $was_addon_network_activated || $network_delegated_connection ) + ) { + $sites = self::get_sites(); + + /** + * If in network admin area and the add-on was not network-activated or network-activated + * and network-delegated, find any add-on whose is_whitelabeled flag is true. + */ + foreach ( $sites as $site ) { + $site_info = $this->get_site_info( $site ); + + if ( $addon_storage->get( 'is_whitelabeled', false, $site_info['blog_id'] ) ) { + $is_whitelabeled = true; + break; + } + } + + if ( $is_whitelabeled ) { + break; + } + } else { + /** + * This will be executed when any of the following is met: + * 1. Add-on was network-activated, not network-delegated, and in network admin area. + * 2. Add-on was network-activated, network-delegated, and in site admin area. + * 3. Add-on was not network-activated and in site admin area. + */ + if ( true === $addon_storage->is_whitelabeled ) { + $is_whitelabeled = true; + break; + } + } + } + } + } + + $this->is_whitelabeled = $is_whitelabeled; + + if ( ! $is_whitelabeled || ! $this->is_data_debug_mode() ) { + $this->_admin_notices->remove_sticky( 'data_debug_mode_enabled' ); + } + + if ( ! is_null( $blog_id ) ) { + $this->restore_current_blog(); + } + } + + return ( + $is_whitelabeled && + ( $ignore_data_debug_mode || ! $this->is_data_debug_mode() ) + ); + } + + /** + * Sync site's subscription. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_License|null $license + * + * @return bool|\FS_Subscription + */ + private function _sync_site_subscription( $license ) { + if ( ! is_object( $license ) ) { + $this->delete_unused_subscriptions(); + + return false; + } + + // Load subscription details if not lifetime. + $subscription = $license->is_lifetime() ? + false : + $this->_fetch_site_license_subscription(); + + if ( is_object( $subscription ) && ! isset( $subscription->error ) ) { + $this->store_subscription( $subscription ); + } else { + $this->delete_unused_subscriptions(); + } + + return $subscription; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool|\FS_Plugin_License + */ + function _get_license() { + if ( ! fs_is_network_admin() || is_object( $this->_license ) ) { + return $this->_license; + } + + return $this->_get_available_premium_license(); + } + + /** + * @param number $license_id + * + * @return null|\FS_Subscription + */ + function _get_subscription( $license_id ) { + if ( ! isset( $this->_storage->subscriptions ) || + empty( $this->_storage->subscriptions ) + ) { + return null; + } + + foreach ( fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ) as $subscription ) { + if ( $subscription->license_id == $license_id ) { + return $subscription; + } + } + + return null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param FS_Subscription $subscription + */ + function store_subscription( FS_Subscription $subscription ) { + if ( ! isset( $this->_storage->subscriptions ) ) { + $this->_storage->subscriptions = array(); + } + + if ( empty( $this->_storage->subscriptions ) || ! is_multisite() ) { + $this->_storage->subscriptions = array( $subscription ); + + return; + } + + $subscriptions = fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ); + + $updated_subscription = false; + foreach ( $subscriptions as $key => $existing_subscription ) { + if ( $existing_subscription->id == $subscription->id ) { + $subscriptions[ $key ] = $subscription; + $updated_subscription = true; + break; + } + } + + if ( ! $updated_subscription ) { + $subscriptions[] = $subscription; + } + + $this->_storage->subscriptions = $subscriptions; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + */ + function delete_unused_subscriptions() { + if ( ! isset( $this->_storage->subscriptions ) || + empty( $this->_storage->subscriptions ) || + // Clean up only if there are already at least 3 subscriptions. + ( count( $this->_storage->subscriptions ) < 3 ) + ) { + return; + } + + if ( ! is_multisite() ) { + // If not multisite, there should only be 1 subscription, so just clear the array. + $this->_storage->subscriptions = array(); + + return; + } + + $subscriptions_to_keep_by_license_id_map = array(); + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( ! is_object( $install ) || + ! FS_Plugin_License::is_valid_id( $install->license_id ) + ) { + continue; + } + + $subscriptions_to_keep_by_license_id_map[ $install->license_id ] = true; + } + + if ( empty( $subscriptions_to_keep_by_license_id_map ) ) { + $this->_storage->subscriptions = array(); + + return; + } + + foreach ( $this->_storage->subscriptions as $key => $subscription ) { + if ( ! isset( $subscriptions_to_keep_by_license_id_map[ $subscription->license_id ] ) ) { + unset( $this->_storage->subscriptions[ $key ] ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param string $plan Plan name + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_plan( $plan, $exact = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + $plan = strtolower( $plan ); + + $current_plan_name = $this->get_plan_name(); + + if ( $current_plan_name === $plan ) { + // Exact plan. + return true; + } else if ( $exact ) { + // Required exact, but plans are different. + return false; + } + + $current_plan_order = - 1; + $required_plan_order = PHP_INT_MAX; + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $plan === $this->_plans[ $i ]->name ) { + $required_plan_order = $i; + } else if ( $current_plan_name === $this->_plans[ $i ]->name ) { + $current_plan_order = $i; + } + } + + return ( $current_plan_order > $required_plan_order ); + } + + /** + * Check if module has only one plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param bool $double_check In some cases developers prefer to release their paid offering as premium-only, even though there is a free version. For those cases, looking at the 'is_premium_only' value isn't enough because the result will return false even when the product has only signle paid plan. + * + * @return bool + */ + function is_single_plan( $double_check = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() || + ! is_array( $this->_plans ) || + 0 === count( $this->_plans ) + ) { + return true; + } + + $has_free_plan = $this->has_free_plan(); + + if ( ! $has_free_plan && $double_check ) { + foreach ( $this->_plans as $plan ) { + if ( $plan->is_free() ) { + $has_free_plan = true; + break; + } + } + } + + return ( 1 === ( count( $this->_plans ) - ( $has_free_plan ? 1 : 0 ) ) ); + } + + /** + * Check if plan based on trial. If not in trial mode, should return false. + * + * @since 1.0.9 + * + * @param string $plan Plan name + * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. + * + * @return bool + */ + function is_trial_plan( $plan, $exact = false ) { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + return false; + } + + if ( ! $this->is_trial() ) { + return false; + } + + $trial_plan = $this->get_trial_plan(); + + if ( $trial_plan->name === $plan ) { + // Exact plan. + return true; + } else if ( $exact ) { + // Required exact, but plans are different. + return false; + } + + $current_plan_order = - 1; + $required_plan_order = - 1; + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $plan === $this->_plans[ $i ]->name ) { + $required_plan_order = $i; + } else if ( $trial_plan->name === $this->_plans[ $i ]->name ) { + $current_plan_order = $i; + } + } + + return ( $current_plan_order > $required_plan_order ); + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function has_paid_plan() { + return $this->_has_paid_plans || + FS_Plan_Manager::instance()->has_paid_plan( $this->_plans ); + } + + /** + * Check if plugin has any plan with a trail. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_trial_plan() { + /** + * @author Vova Feldman(@svovaf) + * @since 1.2.1.5 + * + * Allow setting a trial from the SDK without calling the API. + * But, if the user did opt-in, continue using the real data from the API. + */ + if ( $this->_trial_days >= 0 ) { + return true; + } + + return $this->_storage->get( 'has_trial_plan', false ); + } + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool + */ + function has_free_plan() { + return ! $this->is_only_premium(); + } + + /** + * Displays a license activation dialog box when the user clicks on the "Activate License" + * or "Change License" link on the plugins + * page. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + */ + function _add_license_activation_dialog_box() { + $vars = array( + 'id' => $this->_module_id, + ); + + fs_require_template( 'forms/license-activation.php', $vars ); + fs_require_template( 'forms/resend-key.php', $vars ); + } + + /** + * Returns a collection of IDs of installs that are associated with the context product and its add-ons, and activated with foreign licenses. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @return number[] + */ + function get_installs_ids_with_foreign_licenses() { + $installs = array(); + + if ( + is_object( $this->_license ) && + $this->_site->user_id != $this->_license->user_id + ) { + $installs[] = $this->_site->id; + } + + /** + * Also try to get foreign licenses for the context product's add-ons. + */ + $installs_by_slug_map = $this->get_parent_and_addons_installs_info(); + + foreach ( $installs_by_slug_map as $slug => $install_info ) { + if ( $slug == $this->get_slug() ) { + continue; + } + + $install = $install_info['install']; + $license = $install_info['license']; + + if ( + is_object( $license ) && + $install->user_id != $license->user_id + ) { + $installs[] = $install->id; + } + } + + return $installs; + } + + /** + * Displays the "Change User" dialog box when the user clicks on the "Change User" button on the "Account" page. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @param number[] $install_ids + */ + function _add_user_change_dialog_box( $install_ids ) { + $vars = array( + 'id' => $this->_module_id, + 'license_owners' => $this->fetch_installs_licenses_owners_data( $install_ids ) + ); + + fs_require_template( 'forms/user-change.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + function _add_data_debug_mode_dialog_box() { + $vars = array( + 'id' => $this->_module_id, + ); + + fs_require_template( 'forms/data-debug-mode.php', $vars ); + } + + /** + * Displays a subscription cancellation dialog box when the user clicks on the "Deactivate License" + * link on the "Account" page or deactivates a plugin and there's an active subscription that is + * either associated with a non-lifetime single-site license or non-lifetime multisite license that + * is only activated on a single production site. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param bool $is_license_deactivation + * + * @return array + */ + function _get_subscription_cancellation_dialog_box_template_params( $is_license_deactivation = false ) { + if ( fs_is_network_admin() ) { + // Subscription cancellation dialog box is currently not supported for multisite networks. + return array(); + } + + if ( $this->is_whitelabeled() ) { + return array(); + } + + $license = $this->_get_license(); + + /** + * If the installation is associated with a non-lifetime license, which is either a single-site or only activated on a single production site (or zero), and connected to an active subscription, suggest the customer to cancel the subscription upon deactivation. + * + * @author Leo Fajardo (@leorw) (Comment added by Vova Feldman @svovaf) + * @since 2.2.1 + */ + if ( ! is_object( $license ) || + $license->is_lifetime() || + ( ! $license->is_single_site() && $license->activated > 1 ) + ) { + return array(); + } + + /** + * @var FS_Subscription $subscription + */ + $subscription = $this->_get_subscription( $license->id ); + if ( ! is_object( $subscription ) || ! $subscription->is_active() ) { + return array(); + } + + return array( + 'id' => $this->_module_id, + 'license' => $license, + 'has_trial' => $this->is_paid_trial(), + 'is_license_deactivation' => $is_license_deactivation, + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection_dialog_box() { + $modules_update = get_site_transient( $this->is_theme() ? 'update_themes' : 'update_plugins' ); + if ( ! isset( $modules_update->response[ $this->_plugin_basename ] ) ) { + return; + } + + $vars = array( + 'id' => $this->_module_id, + 'new_version' => is_object( $modules_update->response[ $this->_plugin_basename ] ) ? + $modules_update->response[ $this->_plugin_basename ]->new_version : + $modules_update->response[ $this->_plugin_basename ]['new_version'] + ); + + fs_require_template( 'forms/premium-versions-upgrade-metadata.php', $vars ); + fs_require_once_template( 'forms/premium-versions-upgrade-handler.php', $vars ); + } + + /** + * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins" + * page. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _add_optout_dialog() { + if ( $this->is_theme() ) { + $vars = null; + fs_require_once_template( '/js/jquery.content-change.php', $vars ); + } + + $vars = array( 'id' => $this->_module_id ); + fs_require_template( 'forms/optout.php', $vars ); + } + + /** + * Prepare page to include all required UI and logic for the license activation dialog. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + */ + function _add_license_activation() { + if ( $this->is_migration() ) { + return; + } + + if ( ! $this->is_user_admin() ) { + // Only admins can activate a license. + return; + } + + if ( ! $this->has_paid_plan() ) { + // Module doesn't have any paid plans. + return; + } + + if ( + $this->has_premium_version() && + ! $this->is_premium() && + /** + * Also handle the case when an upgrade was made using the free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + ! is_object( $this->_get_license() ) + ) { + // Only add license activation logic to the premium version, or in case of a serviceware plugin, also in the free version. + return; + } + + // Add license activation link and AJAX request handler. + if ( self::is_plugins_page() ) { + $is_network_admin = fs_is_network_admin(); + + if ( + ( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) || + ( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) ) + ) { + /** + * @since 1.2.0 Add license action link only on plugins page. + */ + $this->_add_license_action_link(); + } + } + + // Add license activation AJAX callback. + $this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) ); + + // Add resend license AJAX callback. + $this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) ); + } + + /** + * Prepares page to include all required UI and logic for the "Change User" dialog. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + function _add_user_change_option() { + if ( ! $this->should_handle_user_change() ) { + return; + } + + $installs_ids_with_foreign_licenses = $this->get_installs_ids_with_foreign_licenses(); + + if ( empty( $installs_ids_with_foreign_licenses ) ) { + // Handle user change only when the parent product or one of its add-ons is activated with a foreign license. + return; + } + + // Add user change AJAX handler. + $this->add_ajax_action( 'change_user', array( &$this, '_user_change_ajax_action' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + function should_handle_user_change() { + if ( ! $this->is_user_admin() ) { + // Only admins can change user. + return false; + } + + if ( $this->is_addon() ) { + return false; + } + + if ( ! $this->is_registered() ) { + return false; + } + + if ( + $this->is_network_active() && + ( fs_is_network_admin() || ! $this->is_site_delegated_connection() ) + ) { + // Handle only on site-level "Account" section for now. + return false; + } + + return true; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection() { + if ( ! $this->is_user_admin() ) { + return; + } + + if ( ! $this->is_premium() || $this->has_any_active_valid_license() ) { + // This is relevant only to the free versions and premium versions without an active license. + return; + } + + if ( self::is_updates_page() || ( $this->is_plugin() && self::is_plugins_page() ) ) { + $this->_add_premium_version_upgrade_selection_action(); + } + } + + /** + * @author Edgar Melkonyan + * @since 2.4.1 + * + * @throws Freemius_Exception + */ + function _toggle_whitelabel_mode_ajax_handler() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'toggle_whitelabel_mode' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $license = $this->get_api_user_scope()->call( + "/licenses/{$this->_site->license_id}.json", + 'put', + array( 'is_whitelabeled' => ! $this->_license->is_whitelabeled ) + ); + + if ( ! $this->is_api_result_entity( $license ) ) { + self::shoot_ajax_failure( + FS_Api::is_api_error_object( $license ) ? + $license->error->message : + fs_text_inline( "An unknown error has occurred while trying to toggle the license's white-label mode.", 'unknown-error-occurred', $this->get_slug() ) + ); + } + + $this->_license->is_whitelabeled = $license->is_whitelabeled; + $this->_store_licenses(); + + $this->_sync_license(); + + if ( ! $license->is_whitelabeled ) { + $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); + } else { + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( + 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.', + 'license_whitelabeled' + ), + "{$this->get_plugin_title()}", + sprintf( '%s', $this->get_text_inline( 'User Dashboard', 'user-dashboard' ) ), + sprintf( '%s', $this->get_text_inline( 'revert it now', 'revert-it-now' ) ) + ), + 'license_whitelabeled' + ); + } + + self::shoot_ajax_response( array( 'success' => true ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + function _add_beta_mode_update_handler() { + if ( ! $this->is_user_admin() ) { + return; + } + + if ( ! $this->is_premium() ) { + return; + } + + $this->add_ajax_action( 'set_beta_mode', array( &$this, '_set_beta_mode_ajax_handler' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + function _set_beta_mode_ajax_handler() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'set_beta_mode' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $is_beta = trim( fs_request_get( 'is_beta', '', 'post' ) ); + + if ( empty( $is_beta ) || ! in_array( $is_beta, array( 'true', 'false' ) ) ) { + self::shoot_ajax_failure(); + } + + $site = $this->get_api_site_scope()->call( + '', + 'put', + array( + 'is_beta' => ( 'true' == $is_beta ), + 'fields' => 'is_beta' + ) + ); + + if ( ! $this->is_api_result_entity( $site ) ) { + self::shoot_ajax_failure( + FS_Api::is_api_error_object( $site ) ? + $site->error->message : + fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() ) + ); + } + + $this->_site->is_beta = $site->is_beta; + $this->_store_site(); + + self::shoot_ajax_response( array( 'success' => true ) ); + } + + /** + * License activation WP AJAX handler. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + * + * @uses Freemius::activate_license() + */ + function _activate_license_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'activate_license' ); + + $license_key = trim( fs_request_get( 'license_key' ) ); + + if ( empty( $license_key ) ) { + exit; + } + + $sites = fs_is_network_admin() ? + fs_request_get( 'sites', array(), 'post' ) : + array(); + + $result = $this->activate_license( + $license_key, + $sites, + fs_request_get_bool( 'is_marketing_allowed', null ), + fs_request_get( 'blog_id', null ), + fs_request_get( 'module_id', null, 'post' ), + fs_request_get( 'user_id', null ), + fs_request_get_bool( 'is_extensions_tracking_allowed', null ) + ); + + if ( + $result['success'] && + $this->is_bundle_license_auto_activation_enabled() + ) { + $license = new FS_Plugin_License(); + $license->secret_key = $license_key; + + $this->maybe_activate_bundle_license( $license, $sites ); + } + + echo json_encode( $result ); + + exit; + } + + /** + * User change WP AJAX handler. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + */ + function _user_change_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'change_user' ); + + $new_email_address = trim( fs_request_get( 'email_address', '' ) ); + $new_user_id = fs_request_get( 'user_id' ); + + if ( empty( $new_email_address ) && ! FS_User::is_valid_id( $new_user_id ) ) { + self::shoot_ajax_failure( fs_text_inline( 'Invalid new user ID or email address.', 'invalid-new-user-id-or-email', $this->get_slug() ) ); + } + + $params = array(); + + if ( ! empty( $new_email_address ) ) { + $params['user_email'] = $new_email_address; + } else { + $params['user_id'] = $new_user_id; + } + + $installs_info_by_slug_map = $this->get_parent_and_addons_installs_info(); + $install_ids = array(); + + foreach ( $installs_info_by_slug_map as $slug => $install_info ) { + $install_ids[ $slug ] = $install_info['install']->id; + } + + $params['install_ids'] = implode( ',', array_values( $install_ids ) ); + + $install = $this->get_api_site_scope()->call( $this->add_show_pending( '/' ), 'put', $params ); + + if ( FS_Api::is_api_error( $install ) ) { + $error = ''; + + if ( is_object( $install ) ) { + switch ( $install->error->code ) { + case 'user_exist': + $error = ( + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' . + $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . + sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email_address . '' ) . + sprintf( + '', + $this->get_account_url( 'change_owner', array( + 'state' => 'init', + 'candidate_email' => $new_email_address + ) ), + $this->get_text_inline( 'Change Ownership', 'change-ownership' ) + ) + ); + break; + } + } + + if ( empty( $error ) ) { + $error = FS_Api::is_api_error_object( $install ) ? + $install->error->message : + var_export( $install->error, true ); + } + + self::shoot_ajax_failure( $error ); + } else { + if ( + // If successful ownership change. + $this->get_user()->id != $install->user_id || + ! empty( $new_email_address ) + ) { + $this->complete_ownership_change_by_license( $install->user_id, $install_ids ); + } + } + + self::shoot_ajax_success(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2.14 + */ + function starting_migration() { + if ( ! empty( $this->_storage->license_migration ) ) { + // Do not overwrite the data if already set. + return; + } + + $this->_storage->license_migration = array( + 'is_migrating' => true, + 'start_timestamp' => time() + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2.14 + */ + function is_migration() { + if ( $this->is_addon() ) { + return $this->get_parent_instance()->is_migration(); + } + + if ( empty( $this->_storage->license_migration ) ) { + return false; + } + + if ( ! $this->_storage->license_migration['is_migrating'] ) { + return false; + } + + return ( + // Return `true` if the migration is within 5 minutes from the starting time. + ( time() - $this->_storage->license_migration['start_timestamp'] ) <= WP_FS__TIME_5_MIN_IN_SEC + ); + } + + /** + * + * A helper method to activate migrated licenses. If the product is network activated and integrated, the method will network activate the license. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.0 + * + * @param string $license_key + * @param null|bool $is_marketing_allowed + * @param null|number $plugin_id + * @param array $sites + * @param int $blog_id + * + * @return array { + * @var bool $success + * @var string $error + * @var string $next_page + * } + * + * @uses Freemius::activate_license() + */ + function activate_migrated_license( + $license_key, + $is_marketing_allowed = null, + $plugin_id = null, + $sites = array(), + $blog_id = null + ) { + $this->_logger->entrance(); + + $result = $this->activate_license( + $license_key, + ( empty( $sites ) && is_null( $blog_id ) && $this->is_network_active() ) ? + $this->get_sites_for_network_level_optin() : + $sites, + $is_marketing_allowed, + $blog_id, + $plugin_id + ); + + // No need to show the sticky after license activation notice after migrating a license. + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @return string + */ + function get_pricing_js_path() { + if ( ! isset( $this->_pricing_js_path ) ) { + $pricing_js_path = $this->apply_filters( 'freemius_pricing_js_path', '' ); + + if ( empty( $pricing_js_path ) ) { + global $fs_active_plugins; + + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( $data->plugin_path == $this->get_plugin_basename() ) { + $plugin_or_theme_root_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); + + $pricing_js_path = $plugin_or_theme_root_dir + . '/' + // The basename will be `plugins`, `themes`, or the basename of a custom plugins or themes directory. + . str_replace( '../' . basename( $plugin_or_theme_root_dir ) . '/', '', $sdk_path ) + . '/includes/freemius-pricing/freemius-pricing.js'; + + break; + } + } + } + + $this->_pricing_js_path = $pricing_js_path; + } + + return $this->_pricing_js_path; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @return bool + */ + function should_use_external_pricing() { + if ( is_null( $this->_use_external_pricing ) ) { + $pricing_js_path = $this->get_pricing_js_path(); + + $this->_use_external_pricing = ( empty( $pricing_js_path ) || ! file_exists( $pricing_js_path ) ); + } + + return $this->_use_external_pricing; + } + + /** + * The implementation of this method was previously in `_activate_license_ajax_action()`. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.4 + * @since 2.0.0 When a super-admin that hasn't connected before is network activating a license and excluding some of the sites for the license activation, go over the unselected sites in the network and if a site is not connected, skipped, nor delegated, if it's a freemium product then just skip the connection for the site, if it's a premium only product, delegate the connection and license activation to the site admin (Vova Feldman @svovaf). + * @param string $license_key + * @param array $sites + * @param null|bool $is_marketing_allowed + * @param null|int $blog_id + * @param null|number $plugin_id + * @param null|number $license_owner_id + * + * @return array { + * @var bool $success + * @var string $error + * @var string $next_page + * } + */ + private function activate_license( + $license_key, + $sites = array(), + $is_marketing_allowed = null, + $blog_id = null, + $plugin_id = null, + $license_owner_id = null, + $is_extensions_tracking_allowed = null + ) { + $this->_logger->entrance(); + + $license_key = trim( $license_key ); + + $is_network_activation_or_migration = ( + fs_is_network_admin() || + ( ! empty( $sites ) && $this->is_migration() ) + ); + + if ( ! $is_network_activation_or_migration ) { + // If the license activation is executed outside the context of a network admin, ignore the sites collection. + $sites = array(); + } + + $fs = ( empty($plugin_id) || $plugin_id == $this->_module_id ) ? + $this : + $this->get_addon_instance( $plugin_id ); + + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + + $error = false; + $next_page = false; + + $has_valid_blog_id = is_numeric( $blog_id ); + + $user = null; + + if ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) { + /** + * When activating an add-on's license and the parent is opted-in, activate the license with the parent's opted-in user context. + * + * @author Vova Feldman (@svovaf) + */ + $user = $fs->get_parent_instance()->get_current_or_network_user(); + } else if ( $fs->is_registered() ) { + $user = $fs->get_current_or_network_user(); + } + + if ( $has_valid_blog_id ) { + /** + * If a specific blog ID was provided, activate the license only on the specific blog that is associated with the given blog ID. + * + * @author Leo Fajardo (@leorw) + */ + $fs->switch_to_blog( $blog_id ); + } + + if ( is_object( $user ) ) { + if ( $is_network_activation_or_migration && ! $has_valid_blog_id ) { + // If no specific blog ID was provided, activate the license for all sites in the network. + $blog_2_install_map = array(); + $site_ids = array(); + + foreach ( $sites as $site ) { + if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { + continue; + } + + $install = $fs->get_install_by_blog_id( $site['blog_id'] ); + + if ( is_object( $install ) ) { + $blog_2_install_map[ $site['blog_id'] ] = $install; + } else { + $site_ids[] = $site['blog_id']; + } + } + + if ( ! empty( $blog_2_install_map ) ) { + $result = $fs->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); + + if ( true !== $result ) { + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } + } + + if ( empty( $error ) && ! empty( $site_ids ) ) { + $result = $fs->activate_license_on_many_sites( $user, $license_key, $site_ids ); + + if ( true !== $result ) { + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } + } + } else { + if ( $fs->is_registered() ) { + $params = array( + 'license_key' => $fs->apply_filters( 'license_key', $license_key ) + ); + + $install_ids = array(); + + $change_owner = FS_User::is_valid_id( $license_owner_id ); + + if ( $change_owner ) { + $params['user_id'] = $license_owner_id; + + $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info(); + + foreach ( $installs_info_by_slug_map as $slug => $install_info ) { + $install_ids[ $slug ] = $install_info['install']->id; + } + + $params['install_ids'] = implode( ',', array_values( $install_ids ) ); + } + + $api = $fs->get_api_site_scope(); + + $install = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + + if ( FS_Api::is_api_error( $install ) ) { + $error = FS_Api::is_api_error_object( $install ) ? + $install->error->message : + var_export( $install->error, true ); + } else { + $fs->reconnect_locally( $has_valid_blog_id ); + + if ( + $change_owner && + // If successful ownership change. + $fs->get_user()->id != $install->user_id + ) { + $fs->complete_ownership_change_by_license( $install->user_id, $install_ids ); + } + } + } else /* ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) */ { + $result = $fs->activate_license_on_site( $user, $license_key ); + + if ( true !== $result ) { + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } + } + } + + if ( empty( $error ) ) { + $fs->network_upgrade_mode_completed(); + + $fs->_user = $user; + + if ( fs_is_network_admin() && ! $has_valid_blog_id ) { + $fs->_site = $fs->get_network_install(); + } + + $fs->_sync_license( true, $has_valid_blog_id ); + + $this->maybe_sync_install_user(); + + $next_page = $fs->is_addon() ? + $fs->get_parent_instance()->get_account_url() : + $fs->get_after_activation_url( 'after_connect_url' ); + } + } else { + $next_page = $fs->opt_in( + false, + false, + false, + $license_key, + false, + false, + false, + $is_marketing_allowed, + $sites + ); + + if ( isset( $next_page->error ) ) { + $error = $next_page->error; + } else { + if ( $is_network_activation_or_migration ) { + /** + * Get the list of sites that were just opted-in (and license activated). + * This is an optimization for the next part below saving some DB queries. + */ + $connected_sites = array(); + foreach ( $sites as $site ) { + if ( isset( $site['blog_id'] ) && is_numeric( $site['blog_id'] ) ) { + $connected_sites[ $site['blog_id'] ] = true; + } + } + + $all_sites = self::get_sites(); + $pending_sites = array(); + + /** + * Check if there are any sites that are not connected, skipped, nor delegated. For every site that falls into that category, if the product is freemium, skip the connection. If the product is premium only, delegate the connection to the site administrator. + * + * @author Vova Feldman (@svovaf) + */ + foreach ( $all_sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( isset( $connected_sites[ $blog_id ] ) ) { + // Site was just connected. + continue; + } + + if ( $fs->is_installed_on_site( $blog_id ) ) { + // Site was already connected before. + continue; + } + + if ( $fs->is_site_delegated_connection( $blog_id ) ) { + // Site's connection was delegated. + continue; + } + + if ( $fs->is_anonymous_site( $blog_id ) ) { + // Site connection was already skipped. + continue; + } + + $pending_sites[] = self::get_site_info( $site ); + } + + if ( ! empty( $pending_sites ) ) { + if ( $fs->is_freemium() && $fs->is_enable_anonymous() ) { + $fs->skip_connection( $pending_sites ); + } else { + $fs->delegate_connection( $pending_sites ); + } + } + } + } + } + + if ( false === $error && true === $fs->_storage->require_license_activation ) { + $fs->_storage->require_license_activation = false; + } + + $result = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $result['error'] = $fs->apply_filters( 'opt_in_error_message', $error ); + } else { + if ( $fs->is_addon() || $fs->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $fs->purge_valid_user_licenses_cache(); + } + + $result['next_page'] = $next_page; + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @return array { + * @key string Product slug. + * @value array { + * @property FS_Site $site + * @property FS_Plugin_License $license + * } + * } + */ + private function get_parent_and_addons_installs_info() { + $fs = $this->is_addon() ? + $this->get_parent_instance() : + $this; + + $installed_addons_ids = array(); + + $installed_addons_instances = $fs->get_installed_addons(); + foreach ( $installed_addons_instances as $instance ) { + $installed_addons_ids[] = $instance->get_id(); + } + + $addons_ids = array_unique( array_merge( + $installed_addons_ids, + $fs->get_updated_account_addons() + ) ); + + // Add parent product info. + $installs_info_by_slug_map = array( + $fs->get_slug() => array( + 'install' => $fs->get_site(), + 'license' => $fs->_get_license() + ) + ); + + foreach ( $addons_ids as $addon_id ) { + $is_installed = isset( $installed_addons_ids_map[ $addon_id ] ); + + $addon_info = $fs->_get_addon_info( $addon_id, $is_installed ); + + if ( ! $addon_info['is_connected'] ) { + // Add-on is not associated with an install entity. + continue; + } + + $installs_info_by_slug_map[ $addon_info['slug'] ] = array( + 'install' => $addon_info['site'], + 'license' => isset( $addon_info['license'] ) ? + $addon_info['license'] : + null + ); + } + + return $installs_info_by_slug_map; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3.1 + */ + function _network_activate_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'network_activate' ); + + $plugin_id = fs_request_get( 'module_id', '', 'post' ); + $fs = ( $plugin_id == $this->_module_id ) ? + $this : + $this->get_addon_instance( $plugin_id ); + + $error = false; + + $sites = fs_request_get( 'sites', array(), 'post' ); + if ( is_array( $sites ) && ! empty( $sites ) ) { + $sites_by_action = array( + 'allow' => array(), + 'delegate' => array(), + 'skip' => array() + ); + + foreach ( $sites as $site ) { + $sites_by_action[ $site['action'] ][] = $site; + } + + $total_sites = count( $sites ); + $total_sites_to_delegate = count( $sites_by_action['delegate'] ); + + $next_page = ''; + + $has_any_install = fs_request_get_bool( 'has_any_install' ); + + if ( $total_sites === $total_sites_to_delegate && + ! $this->is_network_upgrade_mode() && + ! $has_any_install + ) { + $this->delegate_connection(); + } else { + if ( ! empty( $sites_by_action['delegate'] ) ) { + $this->delegate_connection( $sites_by_action['delegate'] ); + } + + if ( ! empty( $sites_by_action['skip'] ) ) { + $this->skip_connection( $sites_by_action['skip'] ); + } + + if ( empty( $sites_by_action['allow'] ) ) { + if ( $has_any_install ) { + $first_install = $fs->find_first_install(); + + if ( ! is_null( $first_install ) ) { + $fs->_site = $first_install['install']; + $fs->_storage->network_install_blog_id = $first_install['blog_id']; + + $fs->_user = self::_get_user_by_id( $fs->_site->user_id ); + $fs->_storage->network_user_id = $fs->_user->id; + } + } + } else { + if ( ! $fs->is_registered() || ! $this->_is_network_active ) { + $next_page = $fs->opt_in( + false, + false, + false, + false, + false, + false, + false, + fs_request_get_bool( 'is_marketing_allowed', null ), + $sites_by_action['allow'] + ); + } else { + $next_page = $fs->install_with_user( + $this->get_network_user(), + false, + false, + false, + true, + $sites_by_action['allow'] + ); + } + + if ( is_object( $next_page ) && isset( $next_page->error ) ) { + $error = $next_page->error; + } + } + } + + if ( empty( $next_page ) ) { + $next_page = $this->get_after_activation_url( 'after_network_activation_url' ); + } + } else { + $error = $this->get_text_inline( 'Invalid site details collection.', 'invalid_site_details_collection' ); + } + + $result = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $result['error'] = $error; + } else { + $result['next_page'] = $next_page; + } + + echo json_encode( $result ); + + exit; + } + + /** + * Billing update AJAX callback. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _update_billing_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'update_billing' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $billing = fs_request_get( 'billing' ); + + $api = $this->get_api_user_scope(); + $result = $api->call( '/billing.json', 'put', array_merge( $billing, array( + 'plugin_id' => $this->get_parent_id(), + ) ) ); + + if ( ! $this->is_api_result_entity( $result ) ) { + self::shoot_ajax_failure(); + } + + // Purge cached billing. + $this->get_api_user_scope()->purge_cache( 'billing.json' ); + + self::shoot_ajax_success(); + } + + /** + * Trial start for anonymous users (AJAX callback). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _start_trial_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'start_trial' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $trial_data = fs_request_get( 'trial' ); + + $next_page = $this->opt_in( + false, + false, + false, + false, + false, + $trial_data['plan_id'] + ); + + if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { + self::shoot_ajax_failure( + isset( $next_page->error ) ? + $next_page->error->message : + var_export( $next_page, true ) + ); + } + + $this->shoot_ajax_success( array( + 'next_page' => $next_page, + ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.0 + */ + function _resend_license_key_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'resend_license_key' ); + + $email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) ); + + if ( empty( $email_address ) ) { + exit; + } + + $error = false; + + $api = $this->get_api_plugin_scope(); + $result = $api->call( '/licenses/resend.json', 'post', + array( + 'email' => $email_address, + 'url' => home_url(), + ) + ); + + if ( is_object( $result ) && isset( $result->error ) ) { + $error = $result->error; + + if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) { + $error = $this->get_text_inline( "We couldn't find your email address in the system, are you sure it's the right address?", 'email-not-found' ); + } else if ( 'no_license' === $error->code ) { + $error = $this->get_text_inline( "We can't see any active licenses associated with that email address, are you sure it's the right address?", 'no-active-licenses' ); + } else { + $error = $error->message; + } + } + + $licenses = array( + 'success' => ( false === $error ) + ); + + if ( false !== $error ) { + $licenses['error'] = sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), strtolower( $error ) ); + } + + echo json_encode( $licenses ); + + exit; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @var string + */ + private static $_pagenow; + + /** + * Get current page or the referer if executing a WP AJAX request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @return string + */ + static function get_current_page() { + if ( ! isset( self::$_pagenow ) ) { + global $pagenow; + if ( empty( $pagenow ) && is_admin() && is_multisite() ) { + /** + * It appears that `$pagenow` is not yet initialized in some network admin pages when this method + * is called, so initialize it here using some pieces of code from `wp-includes/vars.php`. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( is_network_admin() ) { + preg_match( '#/wp-admin/network/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } else if ( is_user_admin() ) { + preg_match( '#/wp-admin/user/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } else { + preg_match( '#/wp-admin/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); + } + + $pagenow = $self_matches[1]; + $pagenow = trim( $pagenow, '/' ); + $pagenow = preg_replace( '#\?.*?$#', '', $pagenow ); + if ( '' === $pagenow || 'index' === $pagenow || 'index.php' === $pagenow ) { + $pagenow = 'index.php'; + } else { + preg_match( '#(.*?)(/|$)#', $pagenow, $self_matches ); + $pagenow = strtolower( $self_matches[1] ); + if ( '.php' !== substr($pagenow, -4, 4) ) + $pagenow .= '.php'; // for Options +Multiviews: /wp-admin/themes/index.php (themes.php is queried) + } + } + + self::$_pagenow = $pagenow; + + if ( self::is_ajax() && + 'admin-ajax.php' === $pagenow + ) { + $referer = fs_get_raw_referer(); + + if ( is_string( $referer ) ) { + $parts = explode( '?', $referer ); + + self::$_pagenow = basename( $parts[0] ); + } + } + } + + return self::$_pagenow; + } + + /** + * Helper method to check if user in the plugins page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @return bool + */ + static function is_plugins_page() { + return ( 'plugins.php' === self::get_current_page() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + * + * @return bool + */ + static function is_plugin_install_page() { + return ( 'plugin-install.php' === self::get_current_page() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + * + * @return bool + */ + static function is_updates_page() { + return ( 'update-core.php' === self::get_current_page() ); + } + + /** + * Helper method to check if user in the themes page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.6 + * + * @return bool + */ + static function is_themes_page() { + return ( 'themes.php' === self::get_current_page() ); + } + + #---------------------------------------------------------------------------------- + #region Affiliation + #---------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return bool + */ + function has_affiliate_program() { + if ( ! is_object( $this->_plugin ) ) { + return false; + } + + return $this->_plugin->has_affiliate_program(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.4 + */ + private function fetch_affiliate_terms() { + if ( ! is_object( $this->plugin_affiliate_terms ) ) { + $plugins_api = $this->get_api_plugin_scope(); + $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', false ); + + if ( ! $this->is_api_result_entity( $affiliate_terms ) ) { + return; + } + + $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.4 + */ + private function fetch_affiliate_and_custom_terms() { + if ( ! empty( $this->_storage->affiliate_application_data ) ) { + $application_data = $this->_storage->affiliate_application_data; + $flush = ( ! isset( $application_data['status'] ) || 'pending' === $application_data['status'] ); + + $users_api = $this->get_api_user_scope(); + $result = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush ); + if ( $this->is_api_result_object( $result, 'affiliates' ) ) { + if ( ! empty( $result->affiliates ) ) { + $affiliate = new FS_Affiliate( $result->affiliates[0] ); + + if ( ! isset( $application_data['status'] ) || $application_data['status'] !== $affiliate->status ) { + $application_data['status'] = $affiliate->status; + $this->_storage->affiliate_application_data = $application_data; + } + + if ( $affiliate->is_using_custom_terms ) { + $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", $flush ); + if ( $this->is_api_result_entity( $affiliate_terms ) ) { + $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); + } + } + + $this->affiliate = $affiliate; + } + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + private function fetch_affiliate_and_terms() { + $this->_logger->entrance(); + + $this->fetch_affiliate_terms(); + $this->fetch_affiliate_and_custom_terms(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return FS_Affiliate + */ + function get_affiliate() { + return $this->affiliate; + } + + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return FS_AffiliateTerms + */ + function get_affiliate_terms() { + return is_object( $this->custom_affiliate_terms ) ? + $this->custom_affiliate_terms : + $this->plugin_affiliate_terms; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + function _submit_affiliate_application() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'submit_affiliate_application' ); + + if ( ! $this->is_user_admin() ) { + // Only for admins. + self::shoot_ajax_failure(); + } + + $affiliate = fs_request_get( 'affiliate' ); + + if ( empty( $affiliate['promotion_methods'] ) ) { + unset( $affiliate['promotion_methods'] ); + } + + if ( ! empty( $affiliate['additional_domains'] ) ) { + $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] ); + } + + if ( ! $this->is_registered() ) { + // Opt in but don't track usage. + $next_page = $this->opt_in( + false, + false, + false, + false, + false, + false, + true + ); + + if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { + self::shoot_ajax_failure( + isset( $next_page->error ) ? + $next_page->error->message : + var_export( $next_page, true ) + ); + } else if ( $this->is_pending_activation() ) { + self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation.', 'account-is-pending-activation' ) ); + } + } + + $this->fetch_affiliate_terms(); + + $api = $this->get_api_user_scope(); + $result = $api->call( + ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), + 'post', + $affiliate + ); + + if ( $this->is_api_error( $result ) ) { + self::shoot_ajax_failure( + isset( $result->error ) ? + $result->error->message : + var_export( $result, true ) + ); + } else { + if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { + $this->_admin_notices->remove_sticky( 'affiliate_program' ); + } + + $affiliate_application_data = array( + 'status' => 'pending', + 'stats_description' => $affiliate['stats_description'], + 'promotion_method_description' => $affiliate['promotion_method_description'], + ); + + if ( ! empty( $affiliate['promotion_methods'] ) ) { + $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods']; + } + + if ( ! empty( $affiliate['domain'] ) ) { + $affiliate_application_data['domain'] = $affiliate['domain']; + } + + if ( ! empty( $affiliate['additional_domains'] ) ) { + $affiliate_application_data['additional_domains'] = $affiliate['additional_domains']; + } + + $this->_storage->affiliate_application_data = $affiliate_application_data; + } + + // Purge cached affiliate. + $api->purge_cache( 'affiliate.json' ); + + self::shoot_ajax_success( $result ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return array|null + */ + function get_affiliate_application_data() { + if ( empty( $this->_storage->affiliate_application_data ) ) { + return null; + } + + return $this->_storage->affiliate_application_data; + } + + #endregion Affiliation ------------------------------------------------------------ + + #---------------------------------------------------------------------------------- + #region URL Generators + #---------------------------------------------------------------------------------- + + /** + * Alias to pricing_url(). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @uses pricing_url() + * + * @param string $period Billing cycle + * @param bool $is_trial + * + * @return string + */ + function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { + return $this->pricing_url( $period, $is_trial ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @uses get_upgrade_url() + * + * @return string + */ + function get_trial_url() { + return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param string $new_version + * + * @return string + */ + function version_upgrade_checkout_link( $new_version ) { + if ( ! is_object( $this->_license ) ) { + $url = $this->pricing_url(); + + $purchase_license_text = $this->get_text_inline( 'Buy a license now', 'buy-license-now' ); + } else { + $subscription = $this->_get_subscription( $this->_license->id ); + + $url = $this->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $this->_license->quota ) + ); + + $purchase_license_text = $this->get_text_inline( 'Renew your license now', 'renew-license-now' ); + } + + return sprintf( + $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), + sprintf( '%s', $url, $purchase_license_text ), + $new_version + ); + } + + /** + * Plugin's pricing URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $billing_cycle Billing cycle + * + * @param bool $is_trial + * + * @return string + */ + function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { + $this->_logger->entrance(); + + $params = array( + 'billing_cycle' => $billing_cycle + ); + + if ( $is_trial ) { + $params['trial'] = 'true'; + } + + $url = $this->is_addon() ? + $this->_parent->addon_url( $this->_slug ) : + $this->_get_admin_page_url( 'pricing', $params ); + + return $this->apply_filters( 'pricing_url', $url ); + } + + /** + * Checkout page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $billing_cycle Billing cycle + * @param bool $is_trial + * @param array $extra (optional) Extra parameters, override other query params. + * @param bool|null $network + * + * @return string + */ + function checkout_url( + $billing_cycle = WP_FS__PERIOD_ANNUALLY, + $is_trial = false, + $extra = array(), + $network = null + ) { + $this->_logger->entrance(); + + $params = array( + 'checkout' => 'true', + 'billing_cycle' => $billing_cycle, + ); + + if ( $is_trial ) { + $params['trial'] = 'true'; + } + + /** + * Params in extra override other params. + */ + $params = array_merge( $params, $extra ); + + return $this->_get_admin_page_url( 'pricing', $params, $network ); + } + + /** + * Add-on checkout URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param number $addon_id + * @param number $pricing_id + * @param string $billing_cycle + * @param bool $is_trial + * @param bool|null $network + * + * @return string + */ + function addon_checkout_url( + $addon_id, + $pricing_id, + $billing_cycle = WP_FS__PERIOD_ANNUALLY, + $is_trial = false, + $network = null + ) { + return $this->checkout_url( $billing_cycle, $is_trial, array( + 'plugin_id' => $addon_id, + 'pricing_id' => $pricing_id, + ), $network ); + } + + #endregion + + #endregion ------------------------------------------------------------------ + + /** + * Check if plugin has any add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @since 1.1.7.3 Base logic only on the parameter provided by the developer in the init function. + * + * @return bool + */ + function has_addons() { + $this->_logger->entrance(); + + return $this->_has_addons; + } + + /** + * Check if plugin can work in anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + * + * @deprecated Please use is_enable_anonymous() instead. + */ + function enable_anonymous() { + return $this->_enable_anonymous; + } + + /** + * Check if plugin can work in anonymous mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_enable_anonymous() { + return $this->_enable_anonymous; + } + + /** + * Check if plugin is premium only (no free plans). + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9 + * + * @return bool + */ + function is_only_premium() { + return $this->_is_premium_only; + } + + /** + * Checks if the plugin's type is "plugin". The other type is "theme". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_plugin() { + return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_module_type() { + if ( ! isset( $this->_module_type ) ) { + $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); + $this->_module_type = $id_slug_type_path_map[ $this->_module_id ]['type']; + } + + return $this->_module_type; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + function get_plugin_main_file_path() { + return $this->_plugin_main_file_path; + } + + /** + * Check if module has a premium code version. + * + * Serviceware module might be freemium without any + * premium code version, where the paid features + * are all part of the service. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + function has_premium_version() { + return $this->_has_premium_version; + } + + /** + * Check if feature supported with current site's plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @todo IMPLEMENT + * + * @param number $feature_id + * + * @throws Exception + */ + function is_feature_supported( $feature_id ) { + throw new Exception( 'not implemented' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @return bool Is running in SSL/HTTPS + */ + function is_ssl() { + return WP_FS__IS_HTTPS; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool Is running in AJAX call. + * + * @link http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax + */ + static function is_ajax() { + return ( defined( 'DOING_AJAX' ) && DOING_AJAX ); + } + + /** + * Check if it's an AJAX call targeted for the current module. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param array|string $actions Collection of AJAX actions. + * + * @return bool + */ + function is_ajax_action( $actions ) { + // Verify it's an ajax call. + if ( ! self::is_ajax() ) { + return false; + } + + // Verify the call is relevant for the plugin. + if ( $this->_module_id != fs_request_get( 'module_id' ) ) { + return false; + } + + // Verify it's one of the specified actions. + if ( is_string( $actions ) ) { + $actions = explode( ',', $actions ); + } + + if ( is_array( $actions ) && 0 < count( $actions ) ) { + $ajax_action = fs_request_get( 'action' ); + + foreach ( $actions as $action ) { + if ( $ajax_action === $this->get_action_tag( $action ) ) { + return true; + } + } + } + + return false; + } + + /** + * Check if it's an AJAX call targeted for current request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param array|string $actions Collection of AJAX actions. + * @param number|null $module_id + * + * @return bool + */ + static function is_ajax_action_static( $actions, $module_id = null ) { + // Verify it's an ajax call. + if ( ! self::is_ajax() ) { + return false; + } + + + if ( ! empty( $module_id ) ) { + // Verify the call is relevant for the plugin. + if ( $module_id != fs_request_get( 'module_id' ) ) { + return false; + } + } + + // Verify it's one of the specified actions. + if ( is_string( $actions ) ) { + $actions = explode( ',', $actions ); + } + + if ( is_array( $actions ) && 0 < count( $actions ) ) { + $ajax_action = fs_request_get( 'action' ); + + foreach ( $actions as $action ) { + if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) { + return true; + } + } + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + static function is_cron() { + return ( defined( 'DOING_CRON' ) && DOING_CRON ); + } + + /** + * Check if a real user is visiting the admin dashboard. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function is_user_in_admin() { + return ( + is_admin() && + ! self::is_ajax() && + ! self::is_cron() && + ( 'admin-post.php' !== self::get_current_page() ) + ); + } + + /** + * Check if a real user is in the customizer view. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + static function is_customizer() { + return is_customize_preview(); + } + + /** + * Check if running in HTTPS and if site's plan matching the specified plan. + * + * @param string $plan + * @param bool $exact + * + * @return bool + */ + function is_ssl_and_plan( $plan, $exact = false ) { + return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) ); + } + + /** + * Construct plugin's settings page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $page + * @param array $params + * @param bool|null $network + * + * @return string + */ + function _get_admin_page_url( $page = '', $params = array(), $network = null ) { + if ( is_null( $network ) ) { + $network = ( + $this->_is_network_active && + ( fs_is_network_admin() || ! $this->is_delegated_connection() ) + ); + } + + if ( 0 < count( $params ) ) { + foreach ( $params as $k => $v ) { + $params[ $k ] = urlencode( $v ); + } + } + + $page_param = $this->_menu->get_slug( $page ); + + if ( empty( $page ) && + // Show the opt-in as an overlay for free wp.org themes or themes without any settings page. + $this->show_opt_in_on_themes_page() + ) { + $params[ $this->get_unique_affix() . '_show_optin' ] = 'true'; + + return add_query_arg( + $params, + $this->admin_url( 'themes.php', 'admin', $network ) + ); + } + + if ( ! $this->has_settings_menu() ) { + if ( ! empty( $page ) ) { + // Module doesn't have a setting page, but since the request is for + // a specific Freemius page, use the admin.php path. + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + if ( $this->is_activation_mode() ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * If plugin doesn't have a settings page, create one for the opt-in screen. + */ + return add_query_arg( array_merge( $params, array( + 'page' => $this->_slug, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + // Plugin without a settings page. + return add_query_arg( + $params, + $this->admin_url( 'plugins.php', 'admin', $network ) + ); + } + } + } + + // Module has a submenu settings page. + if ( ! $this->_menu->is_top_level() ) { + $parent_slug = $this->_menu->get_parent_slug(); + $menu_file = ( false !== strpos( $parent_slug, '.php' ) ) ? + $parent_slug : + 'admin.php'; + + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( $menu_file, 'admin', $network ) ); + } + + // Module has a top level CPT settings page. + if ( $this->_menu->is_cpt() ) { + if ( empty( $page ) && $this->is_activation_mode() ) { + return add_query_arg( array_merge( $params, array( + 'page' => $page_param + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } else { + if ( ! empty( $page ) ) { + $params['page'] = $page_param; + } + + return add_query_arg( + $params, + $this->admin_url( $this->_menu->get_raw_slug(), 'admin', $network ) + ); + } + } + + // Module has a custom top level settings page. + return add_query_arg( array_merge( $params, array( + 'page' => $page_param, + ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); + } + + #-------------------------------------------------------------------------------- + #region Multisite + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return bool + */ + function is_network_active() { + return $this->_is_network_active; + } + + /** + * Delegate activation for the given sites in the network (or all sites if `null`) to site admins. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array|null $sites + */ + private function delegate_connection( $sites = null ) { + $this->_logger->entrance(); + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( is_null( $sites ) ) { + // All sites delegation. + $this->_storage->store( 'is_delegated_connection', true, true, true ); + } else { + // Specified sites delegation. + foreach ( $sites as $site ) { + $this->delegate_site_connection( $site['blog_id'] ); + } + } + + $this->network_upgrade_mode_completed(); + } + + /** + * Delegate specific network site conncetion to the site admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + private function delegate_site_connection( $blog_id ) { + $this->_storage->store( 'is_delegated_connection', true, $blog_id, true ); + } + + /** + * Check if super-admin delegated the connection of ALL sites to the site admins. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_delegated_connection() { + if ( ! $this->_is_network_active ) { + return false; + } + + return $this->_storage->get( 'is_delegated_connection', false, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_site_delegated_connection( $blog_id = 0 ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( 0 == $blog_id ) { + $blog_id = get_current_blog_id(); + } + + return $this->_storage->get( 'is_delegated_connection', false, $blog_id ); + } + + /** + * Check if delegated the connection. When running within the the network admin, + * and haven't specified the blog ID, checks if network level delegated. If running + * within a site admin or specified a blog ID, check if delegated the connection for + * the current context site. + * + * If executed outside the the admin, check if delegated the connection + * for the current context site OR the whole network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id If set, checks if network delegated or blog specific delegated. + * + * @return bool + */ + function is_delegated_connection( $blog_id = 0 ) { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( fs_is_network_admin() && 0 == $blog_id ) { + return $this->is_network_delegated_connection(); + } + + return ( + $this->is_network_delegated_connection() || + $this->is_site_delegated_connection( $blog_id ) + ); + } + + /** + * Check if the current module is active for the site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return bool + */ + function is_active_for_site( $blog_id ) { + if ( ! is_multisite() ) { + // Not a multisite and this code is executed, means that the plugin is active. + return true; + } + + if ( $this->is_theme() ) { + // All themes are site level activated. + return true; + } + + if ( $this->_is_network_active ) { + // Plugin was network activated so it's active. + return true; + } + + return in_array( $this->_plugin_basename, (array) get_blog_option( $blog_id, 'active_plugins', array() ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return array Active & public sites collection. + */ + static function get_sites() { + if ( ! is_multisite() ) { + return array(); + } + + /** + * For consistency with get_blog_list() which only return active public sites. + * + * @author Vova Feldman (@svovaf) + */ + $args = array( + /** + * Commented out in order to handle the migration of site options whether the site is public or not. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + // 'public' => 1, + 'archived' => 0, + 'mature' => 0, + 'spam' => 0, + 'deleted' => 0, + ); + + if ( function_exists( 'get_sites' ) ) { + // For WP 4.6 and above. + return get_sites( $args ); + } else if ( function_exists( 'wp_' . 'get_sites' ) ) { + // For WP 3.7 to WP 4.5. + /** + * This is a hack suggested previously proposed by the TRT. Our SDK is compliant with older WP versions and we'd like to keep it that way. + * + * @todo Remove this hack once this false-positive error is removed from the Theme Sniffer. + * + * @since 2.3.3 + * @author Vova Feldman (@svovaf) + */ + $fn = 'wp_' . 'get_sites'; + return $fn( $args ); + } else { + // For WP 3.6 and below. + return get_blog_list( 0, 'all' ); + } + } + + /** + * Checks if a given blog is active. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param $blog_id + * + * @return bool + */ + private static function is_site_active( $blog_id ) { + global $wpdb; + + $blog_info = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d", $blog_id ) ); + + if ( ! is_object( $blog_info ) ) { + return false; + } + + return ( + true == $blog_info->public && + false == $blog_info->archived && + false == $blog_info->mature && + false == $blog_info->spam && + false == $blog_info->deleted + ); + } + + /** + * Get a mapping between the site addresses to their blog IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array { + * @key string Site address without protocol with a trailing slash. + * @value int Site's blog ID. + * } + */ + private function get_address_to_blog_map() { + $sites = self::get_sites(); + + // Map site addresses to their blog IDs. + $address_to_blog_map = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $address = trailingslashit( fs_strip_url_protocol( get_site_url( $blog_id ) ) ); + $address_to_blog_map[ $address ] = $blog_id; + } + + return $address_to_blog_map; + } + + /** + * Get a mapping between the site addresses to their blog IDs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return array { + * @key int Site's blog ID. + * @value FS_Site Associated install. + * } + */ + function get_blog_install_map() { + $sites = self::get_sites(); + + // Map site blog ID to its install. + $install_map = array(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + $install_map[ $blog_id ] = $install; + } + } + + return $install_map; + } + + /** + * Gets a map of module IDs that the given user has opted-in to. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param number $fs_user_id + * + * @return array { + * @key number $plugin_id + * @value bool Always true. + * } + */ + private static function get_user_opted_in_module_ids_map( $fs_user_id ) { + self::$_static_logger->entrance(); + + if ( ! is_multisite() ) { + $installs = array_merge( + self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ), + self::get_all_sites( WP_FS__MODULE_TYPE_THEME ) + ); + } else { + $sites = self::get_sites(); + + $installs = array(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + $installs = array_merge( + $installs, + self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ), + self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ) + ); + } + } + + $module_ids_map = array(); + foreach ( $installs as $install ) { + if ( is_object( $install ) && + FS_Site::is_valid_id( $install->id ) && + FS_User::is_valid_id( $install->user_id ) && + ( $install->user_id == $fs_user_id ) + ) { + $module_ids_map[ $install->plugin_id ] = true; + } + } + + return $module_ids_map; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return null|array { + * 'install' => FS_Site Module's install, + * 'blog_id' => string The associated blog ID. + * } + */ + function find_first_install() { + $sites = self::get_sites(); + + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + $install = $this->get_install_by_blog_id( $blog_id ); + + if ( is_object( $install ) ) { + return array( + 'install' => $install, + 'blog_id' => $blog_id + ); + } + } + + return null; + } + + /** + * Switches the Freemius site level context to a specified blog. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * @param FS_Site $install + * + * @return bool Since 2.3.1 returns if a switch was made. + */ + function switch_to_blog( $blog_id, FS_Site $install = null ) { + if ( ! is_numeric( $blog_id ) || $blog_id == $this->_context_is_network_or_blog_id ) { + return false; + } + + switch_to_blog( $blog_id ); + $this->_context_is_network_or_blog_id = $blog_id; + + self::$_accounts->set_site_blog_context( $blog_id ); + $this->_storage->set_site_blog_context( $blog_id ); + $this->_storage->set_network_active( $this->_is_network_active, $this->is_delegated_connection( $blog_id ) ); + + $this->_site = is_object( $install ) ? + $install : + $this->get_install_by_blog_id( $blog_id ); + + $this->_user = false; + $this->_licenses = false; + $this->_license = null; + $this->is_whitelabeled = null; + + if ( is_object( $this->_site ) ) { + // Try to fetch user from install. + $this->_user = self::_get_user_by_id( $this->_site->user_id ); + + if ( ! is_object( $this->_user ) && + FS_User::is_valid_id( $this->_storage->prev_user_id ) + ) { + // Try to fetch previously saved user. + $this->_user = self::_get_user_by_id( $this->_storage->prev_user_id ); + + if ( ! is_object( $this->_user ) ) { + // Fallback to network's user. + $this->_user = $this->get_network_user(); + } + } + + $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); + + if ( ! empty( $all_plugin_licenses ) ) { + if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { + $this->_license = null; + } else { + $license_found = false; + foreach ( $all_plugin_licenses as $license ) { + if ( $license->id == $this->_site->license_id ) { + // License found. + $this->_license = $license; + $license_found = true; + break; + } + } + + if ( $license_found ) { + $this->link_license_2_user( $this->_license->id, $this->_user->id ); + } + } + + $this->_licenses = $this->get_user_licenses( $this->_user->id ); + } + } + + unset( $this->_site_api ); + unset( $this->_user_api ); + + return false; + } + + /** + * Restore the blog context to the blog that originally loaded the module. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function restore_current_blog() { + $this->switch_to_blog( $this->_blog_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param array|WP_Site $site + * + * @return int + */ + static function get_site_blog_id( &$site ) { + return ( $site instanceof WP_Site ) ? + $site->blog_id : + ( is_object( $site ) && isset( $site->userblog_id ) ? + $site->userblog_id : + $site['blog_id'] ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param array|WP_Site|null $site + * + * @return array + */ + function get_site_info( $site = null ) { + $this->_logger->entrance(); + + $switched = false; + + if ( is_null( $site ) ) { + $url = get_site_url(); + $name = get_bloginfo( 'name' ); + $blog_id = null; + } else { + $blog_id = self::get_site_blog_id( $site ); + + if ( get_current_blog_id() != $blog_id ) { + switch_to_blog( $blog_id ); + $switched = true; + } + + if ( $site instanceof WP_Site ) { + $url = $site->siteurl; + $name = $site->blogname; + } else { + $url = get_site_url( $blog_id ); + $name = get_bloginfo( 'name' ); + } + } + + $info = array( + 'uid' => $this->get_anonymous_id( $blog_id ), + 'url' => $url, + 'title' => $name, + 'language' => get_bloginfo( 'language' ), + 'charset' => get_bloginfo( 'charset' ), + ); + + if ( is_numeric( $blog_id ) ) { + $info['blog_id'] = $blog_id; + } + + if ( $switched ) { + restore_current_blog(); + } + + return $info; + } + + /** + * Load the module's install based on the blog ID. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * + * @return FS_Site + */ + function get_install_by_blog_id( $blog_id = null ) { + $installs = self::get_all_sites( $this->_module_type, $blog_id ); + $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; + + if ( is_object( $install ) && + is_numeric( $install->id ) && + is_numeric( $install->user_id ) && + FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ) { + // Load site. + $install = clone $install; + } + + return $install; + } + + /** + * Check if module is installed on a specified site. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $blog_id + * + * @return bool + */ + function is_installed_on_site( $blog_id = null ) { + $installs = self::get_all_sites( $this->_module_type, $blog_id ); + $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; + + return ( + is_object( $install ) && + is_numeric( $install->id ) && + is_numeric( $install->user_id ) && + FS_Plugin_Plan::is_valid_id( $install->plan_id ) + ); + } + + /** + * Check if super-admin connected at least one site via the network opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_network_registered() { + if ( ! $this->_is_network_active ) { + return false; + } + + return FS_User::is_valid_id( $this->_storage->network_user_id ); + } + + /** + * Returns the main user associated with the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_User + */ + function get_network_user() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_User::is_valid_id( $this->_storage->network_user_id ) ? + self::_get_user_by_id( $this->_storage->network_user_id ) : + null; + } + + /** + * Returns the current context user or the network's main user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_User + */ + function get_current_or_network_user() { + return ( $this->_user instanceof FS_User ) ? + $this->_user : + $this->get_network_user(); + } + + /** + * Returns the main install associated with the network. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_Site + */ + function get_network_install() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? + $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ) : + null; + } + + /** + * Returns the blog ID that is associated with the main install. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @return int|null + */ + function get_network_install_blog_id() { + if ( ! $this->_is_network_active ) { + return null; + } + + return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? + $this->_storage->network_install_blog_id : + null; + } + + /** + * Returns the current context install or the network's main install. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return FS_Site + */ + function get_current_or_network_install() { + return ( $this->_site instanceof FS_Site ) ? + $this->_site : + $this->get_network_install(); + } + + /** + * Check if executing a site level action from the network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return false|int If yes, return the requested blog ID. + */ + private function is_network_level_site_specific_action() { + if ( ! $this->_is_network_active ) { + return false; + } + + if ( ! fs_is_network_admin() ) { + return false; + } + + $blog_id = fs_request_get( 'blog_id', '' ); + + return is_numeric( $blog_id ) ? $blog_id : false; + } + + /** + * Check if executing an action from the network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + private function is_network_level_action() { + return ( $this->_is_network_active && fs_is_network_admin() ); + } + + /** + * Needs to be executed after site deactivation, archive, deletion, or flag as spam. + * The logic updates the network level user and blog, and reschedule the crons if the cron executing site matching the site that is no longer publicly active. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + private function update_multisite_data_after_site_deactivation( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + if ( $this->_is_network_active ) { + if ( $context_blog_id == $this->_storage->network_install_blog_id ) { + $installs_map = $this->get_blog_install_map(); + + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $context_blog_id == $blog_id ) { + continue; + } + + if ( $install->user_id != $this->_storage->network_user_id ) { + continue; + } + + // Switch reference to a blog that is opted-in and belong to the same super-admin. + $this->_storage->network_install_blog_id = $blog_id; + break; + } + } + } + + if ( $this->is_sync_cron_scheduled() && + $context_blog_id == $this->get_sync_cron_blog_id() + ) { + $this->schedule_sync_cron( WP_FS__SCRIPT_START_TIME, true, $context_blog_id ); + } + + if ( $this->is_install_sync_scheduled() && + $context_blog_id == $this->get_install_sync_cron_blog_id() + ) { + $this->schedule_install_sync( $context_blog_id ); + } + } + + /** + * Executed after site deactivation, archive, or flag as spam. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + public function _after_site_deactivated_callback( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + + $this->switch_to_blog( $current_blog_id ); + } + + /** + * Executed after site deletion. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + * @param bool $drop True if site's database tables should be dropped. Default is false. + */ + public function _after_site_deleted_callback( $context_blog_id = 0, $drop = false ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + if ( $drop ) { + // Delete install if dropping site DB. + $this->delete_account_event(); + } else { + // Send deactivation event. + $this->sync_install( array( + 'is_active' => false, + ) ); + } + + $this->switch_to_blog( $current_blog_id ); + } + + /** + * Executed after site re-activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $context_blog_id + */ + public function _after_site_reactivated_callback( $context_blog_id = 0 ) { + $this->_logger->entrance(); + + $install = $this->get_install_by_blog_id( $context_blog_id ); + + if ( ! is_object( $install ) ) { + // Site not connected. + return; + } + + if ( ! self::is_site_active( $context_blog_id ) ) { + // Site not yet active (can be in spam mode, archived, deleted...). + return; + } + + $current_blog_id = get_current_blog_id(); + + $this->switch_to_blog( $context_blog_id ); + + // Send re-activation event. + $this->sync_install( array( + 'is_active' => true, + ) ); + + $this->switch_to_blog( $current_blog_id ); + } + + #endregion Multisite + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $path + * @param string $scheme + * @param bool $network + * + * @return string + */ + private function admin_url( $path = '', $scheme = 'admin', $network = true ) { + return ( $this->_is_network_active && $network ) ? + network_admin_url( $path, $scheme ) : + admin_url( $path, $scheme ); + } + + /** + * Check if currently in a specified admin page. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $page + * + * @return bool + */ + function is_admin_page( $page ) { + return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) ); + } + + /** + * Check if currently in the product's main admin page. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return bool + */ + function is_main_admin_page() { + return $this->is_admin_page( '' ); + } + + /** + * Get module's main admin setting page URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function main_menu_url() { + return $this->_menu->main_menu_url(); + } + + /** + * Check if currently on the theme's setting page or + * on any of the Freemius added pages (via tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + * + * @deprecated Please use is_product_settings_page() instead; + */ + function is_theme_settings_page() { + return $this->is_product_settings_page(); + } + + /** + * Check if currently on the product's main setting page or on any of the Freemius added pages (via tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_product_settings_page() { + return fs_starts_with( + fs_request_get( 'page', '', 'get' ), + $this->_menu->get_slug() + ); + } + + /** + * Plugin's account page + sync license URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.9.1 + * + * @param bool|number $plugin_id + * @param bool $add_action_nonce + * @param array $params + * + * @return string + */ + function _get_sync_license_url( $plugin_id = false, $add_action_nonce = true, $params = array() ) { + if ( is_numeric( $plugin_id ) ) { + $params['plugin_id'] = $plugin_id; + } + + return $this->get_account_url( + $this->get_unique_affix() . '_sync_license', + $params, + $add_action_nonce + ); + } + + /** + * Plugin's account URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|string $action + * @param array $params + * + * @param bool $add_action_nonce + * + * @return string + */ + function get_account_url( $action = false, $params = array(), $add_action_nonce = true ) { + if ( is_string( $action ) ) { + $params['fs_action'] = $action; + } + + self::require_pluggable_essentials(); + + return ( $add_action_nonce && is_string( $action ) ) ? + fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) : + $this->_get_admin_page_url( 'account', $params ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param string $tab + * @param bool $action + * @param array $params + * @param bool $add_action_nonce + * + * @return string + * + * @uses get_account_url() + */ + function get_account_tab_url( $tab, $action = false, $params = array(), $add_action_nonce = true ) { + $params['tab'] = $tab; + + return $this->get_account_url( $action, $params, $add_action_nonce ); + } + + /** + * Plugin's account URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|string $topic + * @param bool|string $message + * + * @return string + */ + function contact_url( $topic = false, $message = false ) { + $params = array(); + if ( is_string( $topic ) ) { + $params['topic'] = $topic; + } + if ( is_string( $message ) ) { + $params['message'] = $message; + } + + if ( $this->is_addon() ) { + $params['addon_id'] = $this->get_id(); + + return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params ); + } else { + return $this->_get_admin_page_url( 'contact', $params ); + } + } + + /** + * Add-on direct info URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $slug + * + * @return string + */ + function addon_url( $slug ) { + return $this->_get_admin_page_url( 'addons', array( + 'slug' => $slug + ) ); + } + + /** + * Add-ons URL. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.5 + * + * @return string + */ + function get_addons_url() { + return $this->_get_admin_page_url( 'addons' ); + } + + /* Logger + ------------------------------------------------------------------------------------------------------------------*/ + /** + * @param string $id + * @param bool $prefix_slug + * + * @return FS_Logger + */ + function get_logger( $id = '', $prefix_slug = true ) { + return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id ); + } + + /** + * Note: This method is used externally so don't delete it. + * + * @param $id + * @param bool $load_options + * @param bool $prefix_slug + * + * @return FS_Option_Manager + */ + function get_options_manager( $id, $load_options = false, $prefix_slug = true ) { + return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options ); + } + + /* Security + ------------------------------------------------------------------------------------------------------------------*/ + private static function _encrypt( $str ) { + if ( is_null( $str ) ) { + return null; + } + + /** + * The encrypt/decrypt functions are used to protect + * the user from messing up with some of the sensitive + * data stored for the module as a JSON in the database. + * + * I used the same suggested hack by the theme review team. + * For more details, look at the function `Base64UrlDecode()` + * in `./sdk/FreemiusBase.php`. + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + $fn = 'base64' . '_encode'; + + return $fn( $str ); + } + + static function _decrypt( $str ) { + if ( is_null( $str ) ) { + return null; + } + + /** + * The encrypt/decrypt functions are used to protect + * the user from messing up with some of the sensitive + * data stored for the module as a JSON in the database. + * + * I used the same suggested hack by the theme review team. + * For more details, look at the function `Base64UrlDecode()` + * in `./sdk/FreemiusBase.php`. + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + $fn = 'base64' . '_decode'; + + return $fn( $str ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Entity $entity + * + * @return FS_Entity Return an encrypted clone entity. + */ + private static function _encrypt_entity( FS_Entity $entity ) { + $clone = clone $entity; + $props = get_object_vars( $entity ); + + foreach ( $props as $key => $val ) { + $clone->{$key} = self::_encrypt( $val ); + } + + return $clone; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Entity $entity + * + * @return FS_Entity Return an decrypted clone entity. + */ + private static function decrypt_entity( FS_Entity $entity ) { + $clone = clone $entity; + $props = get_object_vars( $entity ); + + foreach ( $props as $key => $val ) { + $clone->{$key} = self::_decrypt( $val ); + } + + return $clone; + } + + /** + * Tries to activate account based on POST params. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @deprecated Not in use, outdated. + */ + function _activate_account() { + if ( $this->is_registered() ) { + // Already activated. + return; + } + + self::_clean_admin_content_section(); + + if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) { +// check_admin_referer( 'activate_' . $this->_plugin->public_key ); + + // Verify matching plugin details. + if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) { + return; + } + + $user = new FS_User(); + $user->id = fs_request_get( 'user_id' ); + $user->public_key = fs_request_get( 'user_public_key' ); + $user->secret_key = fs_request_get( 'user_secret_key' ); + $user->email = fs_request_get( 'user_email' ); + $user->first = fs_request_get( 'user_first' ); + $user->last = fs_request_get( 'user_last' ); + $user->is_verified = fs_request_get_bool( 'user_is_verified' ); + + $site = new FS_Site(); + $site->id = fs_request_get( 'install_id' ); + $site->public_key = fs_request_get( 'install_public_key' ); + $site->secret_key = fs_request_get( 'install_secret_key' ); + $site->plan_id = fs_request_get( 'plan_id' ); + + $plans = array(); + $plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) ); + foreach ( $plans_data as $p ) { + $plan = new FS_Plugin_Plan( $p ); + if ( $site->plan_id == $plan->id ) { + $plan->title = fs_request_get( 'plan_title' ); + $plan->name = fs_request_get( 'plan_name' ); + } + + $plans[] = $plan; + } + + $this->_set_account( $user, $site, $plans ); + + // Reload the page with the keys. + fs_redirect( $this->_get_admin_page_url() ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $email + * + * @return FS_User|false + */ + static function _get_user_by_email( $email ) { + self::$_static_logger->entrance(); + + $email = trim( strtolower( $email ) ); + + $users = self::get_all_users(); + + if ( is_array( $users ) ) { + foreach ( $users as $user ) { + if ( $email === trim( strtolower( $user->email ) ) ) { + return $user; + } + } + } + + return false; + } + + #---------------------------------------------------------------------------------- + #region Account (Loading, Updates & Activation) + #---------------------------------------------------------------------------------- + + /*** + * Load account information (user + site). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private function _load_account() { + $this->_logger->entrance(); + + $this->do_action( 'before_account_load' ); + + $users = self::get_all_users(); + $plans = self::get_all_plans( $this->_module_type ); + + if ( $this->_logger->is_on() && is_admin() ) { + $this->_logger->log( 'users = ' . var_export( $users, true ) ); + $this->_logger->log( 'plans = ' . var_export( $plans, true ) ); + } + + $site = fs_is_network_admin() ? + $this->get_network_install() : + $this->get_install_by_blog_id(); + + if ( fs_is_network_admin() && + $this->is_network_active() && + ! is_object( $site ) && + FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) + ) { + $first_install = $this->find_first_install(); + + if ( is_null( $first_install ) ) { + unset( $this->_storage->network_install_blog_id ); + } else { + $site = $first_install['install']; + $this->_storage->network_install_blog_id = $first_install['blog_id']; + } + } + + if ( is_object( $site ) && + is_numeric( $site->id ) && + is_numeric( $site->user_id ) && + FS_Plugin_Plan::is_valid_id( $site->plan_id ) + ) { + // Load site. + $this->_site = $site; + + // Load plans. + $this->_plans = $plans[ $this->_slug ]; + if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) { + $this->_sync_plans(); + } else { + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) { + $this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] ); + } else { + unset( $this->_plans[ $i ] ); + } + } + } + } + + $user = null; + if ( fs_is_network_admin() && $this->_is_network_active ) { + $user = $this->get_network_user(); + } + + if ( is_object( $user ) ) { + $this->_user = clone $user; + } else if ( $this->_site ) { + $user = self::_get_user_by_id( $this->_site->user_id ); + + if ( ! is_object( $user ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) { + /** + * Try to load the previous owner. This recovery is used for the following use-case: + * 1. Opt-in + * 2. Cloning site1 to site2 + * 3. Ownership switch in site1 (same applies for site2) + * 4. Install data sync on site2 + * 5. Now site2's install is associated with the new owner which does not exists locally. + */ + $user = self::_get_user_by_id( $this->_storage->prev_user_id ); + } + + if ( ! is_object( $user ) ) { + /** + * This is a special fault tolerance mechanism to handle a scenario that the user data is missing. + */ + $user = $this->sync_user_by_current_install(); + } + + $this->_user = ( $user instanceof FS_User ) ? + clone $user : + null; + } + + if ( is_object( $this->_user ) ) { + // Load licenses. + $this->_licenses = $this->get_user_licenses( $this->_user->id ); + } + + if ( is_object( $this->_site ) ) { + $this->_license = $this->_get_license_by_id( $this->_site->license_id ); + + if ( $this->_site->version != $this->get_plugin_version() ) { + // If stored install version is different than current installed plugin version, + // then update plugin version event. + $this->update_plugin_version_event(); + } + } + + if ( true === $this->_storage->require_license_activation && + ! fs_request_get_bool( 'require_license', true ) + ) { + $this->_storage->require_license_activation = false; + } + + if ( $this->is_theme() ) { + $this->_register_account_hooks(); + } + } + + /** + * Special user recovery mechanism. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number|null $site_user_id + * + * @return \FS_User|mixed + */ + private function sync_user_by_current_install( $site_user_id = null ) { + $site_user_id = FS_Site::is_valid_id( $site_user_id ) ? + $site_user_id : + $this->_site->user_id; + + $api = $this->get_api_site_scope(); + + $uid = $this->get_anonymous_id(); + $request_path = "/users/{$site_user_id}.json?uid={$uid}"; + + $result = $api->get( $request_path, false, WP_FS__TIME_10_MIN_IN_SEC ); + + if ( $this->is_api_result_entity( $result ) ) { + $user = new FS_User( $result ); + $this->_user = $user; + $this->_store_user(); + + return $user; + } + + $error_code = FS_Api::get_error_code( $result ); + + if ( in_array( $error_code, array( 'invalid_unique_id', 'user_cannot_be_recovered' ) ) ) { + /** + * Those API errors will continue coming and are not recoverable with the + * current site's data. Therefore, extend the API call's cached result to 7 days. + */ + $api->update_cache_expiration( $request_path, WP_FS__TIME_WEEK_IN_SEC ); + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param FS_User $user + * @param FS_Site $site + * @param bool|array $plans + */ + private function _set_account( FS_User $user, FS_Site $site, $plans = false ) { + $site->user_id = $user->id; + + $this->_site = $site; + $this->_user = $user; + if ( false !== $plans ) { + $this->_plans = $plans; + } + + $this->send_install_update(); + + $this->_store_account(); + + } + + /** + * Get a sanitized array with the WordPress version, SDK version, and PHP version. + * Each version is trimmed after the 16th char. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.1 + * + * @return array + */ + private function get_versions() { + $versions = array(); + $versions['platform_version'] = get_bloginfo( 'version' ); + $versions['sdk_version'] = $this->version; + $versions['programming_language_version'] = phpversion(); + + foreach ( $versions as $k => $version ) { + if ( is_string( $versions[ $k ] ) && ! empty( $versions[ $k ] ) ) { + $versions[ $k ] = substr( $versions[ $k ], 0, 16 ); + } + } + + return $versions; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return bool + */ + function has_beta_update() { + return ( + ! empty( $this->_storage->beta_data ) && + ( true === $this->_storage->beta_data['is_beta'] ) && + version_compare( $this->_storage->beta_data['version'], $this->get_plugin_version(), '>' ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return bool + */ + function is_beta() { + return ( + ! empty( $this->_storage->beta_data ) && + ( true === $this->_storage->beta_data['is_beta'] ) && + ( $this->get_plugin_version() === $this->_storage->beta_data['version'] ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param array $override_with + * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network. + * + * @return array + */ + function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) { + $this->_logger->entrance(); + + $current_user = self::_get_current_wp_user(); + + $activation_action = $this->get_unique_affix() . '_activate_new'; + $return_url = $this->is_anonymous() ? + // If skipped already, then return to the account page. + $this->get_account_url( $activation_action, array(), false ) : + // Return to the module's main page. + $this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) ); + + $versions = $this->get_versions(); + + $params = array_merge( $versions, array( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_nickname' => $current_user->user_nicename, + 'user_email' => $current_user->user_email, + 'user_ip' => WP_FS__REMOTE_ADDR, + 'plugin_slug' => $this->_slug, + 'plugin_id' => $this->get_id(), + 'plugin_public_key' => $this->get_public_key(), + 'plugin_version' => $this->get_plugin_version(), + 'return_url' => fs_nonce_url( $return_url, $activation_action ), + 'account_url' => fs_nonce_url( $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ), 'sync_user' ), + 'is_premium' => $this->is_premium(), + 'is_active' => true, + 'is_uninstalled' => false, + ) ); + + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + $params['parent_plugin_slug'] = $parent_fs->_slug; + $params['parent_plugin_id'] = $parent_fs->get_id(); + } + + if ( true === $network_level_or_blog_id ) { + if ( ! isset( $override_with['sites'] ) ) { + $params['sites'] = $this->get_sites_for_network_level_optin(); + } + } else { + $site = is_numeric( $network_level_or_blog_id ) ? + array( 'blog_id' => $network_level_or_blog_id ) : + null; + + $site = $this->get_site_info( $site ); + + $params = array_merge( $params, array( + 'site_uid' => $site['uid'], + 'site_url' => $site['url'], + 'site_name' => $site['title'], + 'language' => $site['language'], + 'charset' => $site['charset'], + ) ); + } + + if ( $this->is_pending_activation() && + ! empty( $this->_storage->pending_license_key ) + ) { + $params['license_key'] = $this->_storage->pending_license_key; + } + + if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) { + // Even though rand() is known for its security issues, + // the timestamp adds another layer of protection. + // It would be very hard for an attacker to get the secret key form here. + // Plus, this should never run in production since the secret should never + // be included in the production version. + $params['ts'] = WP_FS__SCRIPT_START_TIME; + $params['salt'] = md5( uniqid( rand() ) ); + $params['secure'] = md5( + $params['ts'] . + $params['salt'] . + $this->get_secret_key() + ); + } + + return array_merge( $params, $override_with ); + } + + /** + * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to. + * 2. If there was an API error, return the API result. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $email + * @param string|bool $first + * @param string|bool $last + * @param string|bool $license_key + * @param bool $is_uninstall If "true", this means that the module is currently being uninstalled. + * In this case, the user and site info will be sent to the server but no + * data will be saved to the WP installation's database. + * @param number|bool $trial_plan_id + * @param bool $is_disconnected Whether or not to opt in without tracking. + * @param null|bool $is_marketing_allowed + * @param array $sites If network-level opt-in, an array of containing details of sites. + * + * @return string|object + * @use WP_Error + */ + function opt_in( + $email = false, + $first = false, + $last = false, + $license_key = false, + $is_uninstall = false, + $trial_plan_id = false, + $is_disconnected = false, + $is_marketing_allowed = null, + $sites = array() + ) { + $this->_logger->entrance(); + + if ( false === $email ) { + $current_user = self::_get_current_wp_user(); + $email = $current_user->user_email; + } + + /** + * @since 1.2.1 If activating with license key, ignore the context-user + * since the user will be automatically loaded from the license. + */ + if ( empty( $license_key ) ) { + // Clean up pending license if opt-ing in again. + $this->_storage->remove( 'pending_license_key' ); + + if ( ! $is_uninstall ) { + $fs_user = Freemius::_get_user_by_email( $email ); + if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) { + return $this->install_with_user( + $fs_user, + false, + $trial_plan_id, + true, + true, + $sites + ); + } + } + } + + $user_info = array(); + if ( ! empty( $email ) ) { + $user_info['user_email'] = $email; + } + if ( ! empty( $first ) ) { + $user_info['user_firstname'] = $first; + } + if ( ! empty( $last ) ) { + $user_info['user_lastname'] = $last; + } + + if ( ! empty( $sites ) ) { + $is_network = true; + + $user_info['sites'] = $sites; + } else { + $is_network = false; + } + + $params = $this->get_opt_in_params( $user_info, $is_network ); + + $filtered_license_key = false; + if ( is_string( $license_key ) ) { + $filtered_license_key = $this->apply_filters( 'license_key', $license_key ); + $params['license_key'] = $filtered_license_key; + } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { + $params['trial_plan_id'] = $trial_plan_id; + } + + if ( $is_uninstall ) { + $params['uninstall_params'] = array( + 'reason_id' => $this->_storage->uninstall_reason->id, + 'reason_info' => $this->_storage->uninstall_reason->info + ); + } + + if ( isset( $params['license_key'] ) ) { + $fs_user = Freemius::_get_user_by_email( $email ); + + if ( is_object( $fs_user ) ) { + /** + * If opting in with a context license and the context WP Admin user already opted in + * before from the current site, add the user context security params to avoid the + * unnecessary email activation when the context license is owned by the same context user. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + $params = array_merge( $params, FS_Security::instance()->get_context_params( + $fs_user, + false, + 'install_with_existing_user' + ) ); + } + } + + if ( is_bool( $is_marketing_allowed ) ) { + $params['is_marketing_allowed'] = $is_marketing_allowed; + } + + $params['is_disconnected'] = $is_disconnected; + $params['format'] = 'json'; + + $request = array( + 'method' => 'POST', + 'body' => $params, + 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, + ); + + $url = $this->add_show_pending( WP_FS__ADDRESS . '/action/service/user/install/' ); + $response = self::safe_remote_post( $url, $request ); + + if ( is_wp_error( $response ) ) { + /** + * @var WP_Error $response + */ + $result = new stdClass(); + + $error_code = $response->get_error_code(); + $error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) ); + + $result->error = (object) array( + 'type' => $error_type, + 'message' => $response->get_error_message(), + 'code' => $error_code, + 'http' => 402 + ); + + $this->maybe_modify_api_curl_error_message( $result ); + + return $result; + } + + // Module is being uninstalled, don't handle the returned data. + if ( $is_uninstall ) { + return true; + } + + /** + * When json_decode() executed on PHP 5.2 with an invalid JSON, it will throw a PHP warning. Unfortunately, the new Theme Check doesn't allow PHP silencing and the theme review team isn't open to change that, therefore, instead of using `@json_decode()` we had to use the method without the `@` directive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:5 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:9 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:12 + * @link https://themes.trac.wordpress.org/ticket/46134#comment:14 + */ + $decoded = is_string( $response['body'] ) ? + json_decode( $response['body'] ) : + null; + + if ( empty( $decoded ) ) { + return false; + } + + if ( ! $this->is_api_result_object( $decoded ) ) { + if ( ! empty( $params['license_key'] ) ) { + // Pass the fully entered license key to the failure handler. + $params['license_key'] = $license_key; + } + + return $is_uninstall ? + $decoded : + $this->apply_filters( 'after_install_failure', $decoded, $params ); + } else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) { + if ( $is_network ) { + $site_ids = array(); + foreach ( $sites as $site ) { + $site_ids[] = $site['blog_id']; + } + + /** + * Store the sites so that they can be installed once the user has clicked on the activation link + * in the email. + * + * @author Leo Fajardo (@leorw) + */ + $this->_storage->pending_sites_info = array( + 'blog_ids' => $site_ids, + 'license_key' => $license_key, + 'trial_plan_id' => $trial_plan_id + ); + } + + // Pending activation, add message. + return $this->set_pending_confirmation( + ( isset( $decoded->email ) ? + $decoded->email : + true ), + false, + $filtered_license_key, + ! empty( $params['trial_plan_id'] ) + ); + } else if ( isset( $decoded->install_secret_key ) ) { + return $this->install_with_new_user( + $decoded->user_id, + $decoded->user_public_key, + $decoded->user_secret_key, + ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? + $decoded->is_marketing_allowed : + null ), + ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? + $decoded->is_extensions_tracking_allowed : + null ), + $decoded->install_id, + $decoded->install_public_key, + $decoded->install_secret_key, + false + ); + } else if ( is_array( $decoded->installs ) ) { + return $this->install_many_with_new_user( + $decoded->user_id, + $decoded->user_public_key, + $decoded->user_secret_key, + ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? + $decoded->is_marketing_allowed : + null ), + ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? + $decoded->is_extensions_tracking_allowed : + null ), + $decoded->installs, + false + ); + } + + return $decoded; + } + + /** + * Set user and site identities. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_User $user + * @param FS_Site $site + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will + * redirect (or return a URL) to the account page with a special parameter to + * trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + function setup_account( + FS_User $user, + FS_Site $site, + $redirect = true, + $auto_install = false + ) { + return $this->setup_network_account( + $user, + array( $site ), + $redirect, + $auto_install, + false + ); + } + + /** + * Set user and site identities. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param FS_User $user + * @param FS_Site[] $installs + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. + * @param bool $is_network_level_opt_in + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + function setup_network_account( + FS_User $user, + array $installs, + $redirect = true, + $auto_install = false, + $is_network_level_opt_in = true + ) { + $first_install = $installs[0]; + + $this->_user = $user; + $this->_site = $first_install; + + $this->_sync_plans(); + + if ( $this->_storage->handle_gdpr_admin_notice && + $this->should_handle_gdpr_admin_notice() && + FS_GDPR_Manager::instance()->should_show_opt_in_notice() + ) { + /** + * Clear user lock after an opt-in. + */ + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + FS_User_Lock::instance()->unlock(); + } + + if ( 1 < count( $installs ) ) { + // Only network level opt-in can have more than one install. + $is_network_level_opt_in = true; + } +// $is_network_level_opt_in = self::is_ajax_action_static( 'network_activate', $this->_module_id ); + // If Freemius was OFF before, turn it on. + $this->turn_on(); + + $this->handle_account_connection( + $installs, + ( ! $this->_is_network_active || ! $is_network_level_opt_in ) + ); + + if ( is_numeric( $first_install->license_id ) ) { + $this->set_license( $this->_get_license_by_id( $first_install->license_id ) ); + } + + $this->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) { + // Remove pending activation sticky notice (if still exist). + $this->_admin_notices->remove_sticky( 'activation_pending' ); + + // Remove plugin from pending activation mode. + unset( $this->_storage->is_pending_activation ); + + if ( ! $this->is_paying_or_trial() ) { + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( '%s activation was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), + 'activation_complete' + ); + } + } + + if ( $this->is_paying_or_trial() ) { + if ( ! $this->is_premium() || + ! $this->has_premium_version() || + ! $this->has_settings_menu() + ) { + if ( $this->is_paying() ) { + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ), + $this->get_plan_title() + ) . $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } else { + $trial_plan = $this->get_trial_plan(); + + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), + '' . $this->get_plugin_name() . '' + ) . $this->get_complete_upgrade_instructions( $trial_plan->title ), + 'trial_started', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + ) ); + } + + $plugin_id = fs_request_get( 'plugin_id', false ); + + // Store activation time ONLY for plugins & themes (not add-ons). + if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) { + if ( empty( $this->_storage->activation_timestamp ) ) { + $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; + } + } + + $next_page = ''; + + $extra = array(); + if ( $auto_install ) { + $extra['auto_install'] = 'true'; + } + + if ( is_numeric( $plugin_id ) ) { + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1.6 + * + * Also sync the license after an anonymous user subscribes. + */ + if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) { + // Add-on was installed - sync license right after install. + $next_page = $this->_get_sync_license_url( $plugin_id, true, $extra ); + } + } else { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 If site installed with a valid license, sync license. + */ + if ( $this->is_paying() ) { + $this->_sync_plugin_license( + true, + // Installs data is already synced in the beginning of this method directly or via _set_account(). + false + ); + } + + // Reload the page with the keys. + $next_page = $this->is_anonymous() ? + // If user previously skipped, redirect to account page. + $this->get_account_url( false, $extra ) : + $this->get_after_activation_url( 'after_connect_url', array(), $is_network_level_opt_in ); + } + + if ( ! empty( $next_page ) && $redirect ) { + fs_redirect( $next_page ); + } + + return $next_page; + } + + /** + * Install plugin with new user information after approval. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _install_with_new_user() { + $this->_logger->entrance(); + + if ( $this->is_registered() ) { + return; + } + + if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) || + // @todo This logic should be improved because it's executed on every load of a theme. + $this->is_theme() + ) { +// check_admin_referer( $this->_slug . '_activate_new' ); + + if ( fs_request_has( 'user_secret_key' ) ) { + if ( fs_is_network_admin() && isset( $this->_storage->pending_sites_info ) ) { + $pending_sites_info = $this->_storage->pending_sites_info; + + $this->install_many_pending_with_user( + fs_request_get( 'user_id' ), + fs_request_get( 'user_public_key' ), + fs_request_get( 'user_secret_key' ), + fs_request_get_bool( 'is_marketing_allowed', null ), + fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + $pending_sites_info['blog_ids'], + $pending_sites_info['license_key'], + $pending_sites_info['trial_plan_id'] + ); + } else { + $this->install_with_new_user( + fs_request_get( 'user_id' ), + fs_request_get( 'user_public_key' ), + fs_request_get( 'user_secret_key' ), + fs_request_get_bool( 'is_marketing_allowed', null ), + fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + fs_request_get( 'install_id' ), + fs_request_get( 'install_public_key' ), + fs_request_get( 'install_secret_key' ), + true, + fs_request_get_bool( 'auto_install' ) + ); + } + } else if ( fs_request_has( 'pending_activation' ) ) { + $this->set_pending_confirmation( fs_request_get( 'user_email' ), true ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $id + * @param string $public_key + * @param string $secret_key + * + * @return \FS_User + */ + private function setup_user( $id, $public_key, $secret_key ) { + $user = self::_get_user_by_id( $id ); + + if ( is_object( $user ) ) { + $this->_user = $user; + } else { + $user = new FS_User(); + $user->id = $id; + $user->public_key = $public_key; + $user->secret_key = $secret_key; + + $this->_user = $user; + $user_result = $this->get_api_user_scope()->get(); + $user = new FS_User( $user_result ); + + $this->_user = $user; + $this->_store_user(); + } + + return $user; + } + + /** + * Install plugin with new user. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param number $install_id + * @param string $install_public_key + * @param string $install_secret_key + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_with_new_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + $is_extensions_tracking_allowed, + $install_id, + $install_public_key, + $install_secret_key, + $redirect = true, + $auto_install = false + ) { + /** + * This method is also executed after opting in with a license key since the + * license can be potentially associated with a different owner. + * + * @since 2.0.0 + */ + $user = self::_get_user_by_id( $user_id ); + + if ( ! is_object( $user ) ) { + $user = new FS_User(); + $user->id = $user_id; + $user->public_key = $user_public_key; + $user->secret_key = $user_secret_key; + + $this->_user = $user; + $user_result = $this->get_api_user_scope()->get(); + $user = new FS_User( $user_result ); + } + + $this->_user = $user; + + $site = new FS_Site(); + $site->id = $install_id; + $site->public_key = $install_public_key; + $site->secret_key = $install_secret_key; + + $this->_site = $site; + $site_result = $this->get_api_site_scope()->get(); + $site = new FS_Site( $site_result ); + $this->_site = $site; + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + + return $this->setup_account( + $this->_user, + $this->_site, + $redirect, + $auto_install + ); + } + + /** + * Install plugin with user. + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param array $site_ids + * @param bool $license_key + * @param bool $trial_plan_id + * @param bool $redirect + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_many_pending_with_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + $is_extensions_tracking_allowed, + $site_ids, + $license_key = false, + $trial_plan_id = false, + $redirect = true + ) { + $user = $this->setup_user( $user_id, $user_public_key, $user_secret_key ); + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + + $sites = array(); + foreach ( $site_ids as $site_id ) { + $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); + } + + $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); + } + + /** + * Multi-site install with a new user. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $user_id + * @param string $user_public_key + * @param string $user_secret_key + * @param bool|null $is_marketing_allowed + * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param object[] $installs + * @param bool $redirect + * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. + * + * @return string If redirect is `false`, returns the next page the user should be redirected to. + */ + private function install_many_with_new_user( + $user_id, + $user_public_key, + $user_secret_key, + $is_marketing_allowed, + $is_extensions_tracking_allowed, + array $installs, + $redirect = true, + $auto_install = false + ) { + $this->setup_user( $user_id, $user_public_key, $user_secret_key ); + + if ( ! is_null( $is_marketing_allowed ) ) { + $this->disable_opt_in_notice_and_lock_user(); + } + + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + + $install_ids = array(); + + foreach ( $installs as $install ) { + $install_ids[] = $install->id; + } + + $left = count( $install_ids ); + $offset = 0; + + $installs = array(); + while ( $left > 0 ) { + $result = $this->get_api_user_scope()->get( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', array_slice( $install_ids, $offset, 25 ) ) ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + // @todo Handle API error. + } + + $installs = array_merge( $installs, $result->installs ); + + $left -= 25; + } + + foreach ( $installs as &$install ) { + $install = new FS_Site( $install ); + } + + return $this->setup_network_account( + $this->_user, + $installs, + $redirect, + $auto_install + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $email + * @param bool $redirect + * @param string|bool $license_key Since 1.2.1.5 + * @param bool $is_pending_trial Since 1.2.1.5 + * + * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page. + */ + private function set_pending_confirmation( + $email = false, + $redirect = true, + $license_key = false, + $is_pending_trial = false + ) { + if ( $this->_ignore_pending_mode ) { + /** + * If explicitly asked to ignore pending mode, set to anonymous mode + * if require confirmation before finalizing the opt-in. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + $this->skip_connection( null, fs_is_network_admin() ); + } else { + // Install must be activated via email since + // user with the same email already exist. + $this->_storage->is_pending_activation = true; + $this->_add_pending_activation_notice( $email, $is_pending_trial ); + } + + if ( ! empty( $license_key ) ) { + $this->_storage->pending_license_key = $license_key; + } + + // Remove the opt-in sticky notice. + $this->_admin_notices->remove_sticky( array( + 'connect_account', + 'trial_promotion', + ) ); + + $next_page = $this->get_after_activation_url( 'after_pending_connect_url' ); + + // Reload the page with with pending activation message. + if ( $redirect ) { + fs_redirect( $next_page ); + } + + return $next_page; + } + + /** + * Install plugin with current logged WP user info. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _install_with_current_user() { + $this->_logger->entrance(); + + if ( $this->is_registered() ) { + return; + } + + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) { +// check_admin_referer( 'activate_existing_' . $this->_plugin->public_key ); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.9 Add license key if given. + */ + $license_key = fs_request_get( 'license_secret_key' ); + + $this->update_extensions_tracking_flag( fs_request_get_bool( 'is_extensions_tracking_allowed', null ) ); + + $this->install_with_current_user( $license_key ); + } + } + + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @param string|bool $license_key + * @param number|bool $trial_plan_id + * @param array $sites Since 2.0.0 + * @param bool $redirect + * + * @return object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. + */ + private function install_with_current_user( + $license_key = false, + $trial_plan_id = false, + $sites = array(), + $redirect = true + ) { + // Get current logged WP user. + $current_user = self::_get_current_wp_user(); + + // Find the relevant FS user by the email. + $user = self::_get_user_by_email( $current_user->user_email ); + + return $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param string|bool $license_key + * @param number|bool $trial_plan_id + * @param bool $redirect + * @param bool $setup_account Since 2.0.0. When set to FALSE, executes a light installation without setting up the account as if it's the first opt-in. + * @param array $sites Since 2.0.0. If not empty, should be a collection of site details for the bulk install API request. + * + * @return \FS_Site|object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. If $setup_account is set to `false`, return the newly created install. + */ + function install_with_user( + FS_User $user, + $license_key = false, + $trial_plan_id = false, + $redirect = true, + $setup_account = true, + $sites = array() + ) { + // We have to set the user before getting user scope API handler. + $this->_user = $user; + + // Install the plugin. + $result = $this->create_installs_with_user( + $user, + $license_key, + $trial_plan_id, + $sites, + $redirect + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + // @todo Handler potential API error of the $result + } + + if ( empty( $sites ) ) { + $site = new FS_Site( $result ); + $this->_site = $site; + + if ( ! $setup_account ) { + $this->_store_site(); + + $this->sync_plan_if_not_exist( $site->plan_id ); + + if ( ! empty( $license_key ) && FS_Plugin_License::is_valid_id( $site->license_id ) ) { + $this->sync_license_if_not_exist( $site->license_id, $license_key ); + } + + $this->_admin_notices->remove_sticky( 'connect_account', false ); + + return $site; + } + + return $this->setup_account( $this->_user, $this->_site, $redirect ); + } else { + $installs = array(); + foreach ( $result->installs as $install ) { + $installs[] = new FS_Site( $install ); + } + + return $this->setup_network_account( + $user, + $installs, + $redirect + ); + } + } + + /** + * Initiate an API request to create a collection of installs. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * @param bool $license_key + * @param bool $trial_plan_id + * @param array $sites + * @param bool $redirect + * @param bool $silent + * + * @return object|mixed + */ + private function create_installs_with_user( + FS_User $user, + $license_key = false, + $trial_plan_id = false, + $sites = array(), + $redirect = false, + $silent = false + ) { + $extra_install_params = array( + 'uid' => $this->get_anonymous_id(), + 'is_disconnected' => false, + ); + + if ( ! empty( $license_key ) ) { + $extra_install_params['license_key'] = $this->apply_filters( 'license_key', $license_key ); + + if ( $silent ) { + $extra_install_params['ignore_license_owner'] = true; + } + } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { + $extra_install_params['trial_plan_id'] = $trial_plan_id; + } + + if ( ! empty( $sites ) ) { + $extra_install_params['sites'] = $sites; + } + + $args = $this->get_install_data_for_api( $extra_install_params, false, false ); + + // Install the plugin. + $result = $this->get_api_user_scope_by_user( $user )->call( + "/plugins/{$this->get_id()}/installs.json", + 'post', + $args + ); + + if ( ! $this->is_api_result_entity( $result ) && + ! $this->is_api_result_object( $result, 'installs' ) + ) { + if ( ! empty( $args['license_key'] ) ) { + // Pass the fully entered license key to the failure handler. + $args['license_key'] = $license_key; + } + + $result = $this->apply_filters( 'after_install_failure', $result, $args ); + + if ( ! $silent ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $result->error->message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + if ( $redirect ) { + /** + * We set the user before getting the user scope API handler, so the user became temporarily + * registered (`is_registered() = true`). Since the API returned an error and we will redirect, + * we have to set the user to `null`, otherwise, the user will be redirected to the wrong + * activation page based on the return value of `is_registered()`. In addition, in case the + * context plugin doesn't have a settings menu and the default page is the `Plugins` page, + * misleading plugin activation errors will be shown on the `Plugins` page. + * + * @author Leo Fajardo (@leorw) + */ + $this->_user = null; + + fs_redirect( $this->get_activation_url( array( 'error' => $result->error->message ) ) ); + } + } + + return $result; + } + + /** + * Tries to activate add-on account based on parent plugin info. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param Freemius $parent_fs + * @param bool|int|null $network_level_or_blog_id True for network level opt-in and integer for opt-in for specified blog in the network. + * @param FS_Plugin_License $bundle_license Since 2.4.0. If provided, this license will be activated for the add-on. + */ + private function _activate_addon_account( + Freemius $parent_fs, + $network_level_or_blog_id = null, + FS_Plugin_License $bundle_license = null + ) { + if ( $this->is_registered() ) { + // Already activated. + return; + } + + /** + * Do not override the `uid` if network-level opt-in since the call to `get_sites_for_network_level_optin()` + * already returns the data for the current blog. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $uid_param_to_override = ( true === $network_level_or_blog_id ) ? + array() : + array( 'uid' => $this->get_anonymous_id() ); + + $params = $this->get_install_data_for_api( + $uid_param_to_override, + false, + false, + /** + * Do not include the data for the current blog if network-level opt-in since the call to `get_sites_for_network_level_optin` + * already includes the data for it. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + ( true !== $network_level_or_blog_id ) + ); + + if ( true === $network_level_or_blog_id ) { + $params['sites'] = $this->get_sites_for_network_level_optin(); + + if ( empty( $params['sites'] ) ) { + return; + } + } + + if ( is_object( $bundle_license ) ) { + $params['license_key'] = $bundle_license->secret_key; + } + + // Activate add-on with parent plugin credentials. + $result = $parent_fs->get_api_site_scope()->call( + "/addons/{$this->_plugin->id}/installs.json", + 'post', + $params + ); + + if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + if ( is_object( $bundle_license ) ) { + /** + * When a license object is provided, it's an attempt by the SDK to activate a bundle license and not a user-initiated action, therefore, do not show any admin notice to avoid confusion (e.g.: the notice will show up just above the opt-in link). If the license activation fails, the admin will see an opt-in link instead. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + */ + } else { + $error_message = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + $this->get_text_inline( 'An unknown error has occurred.', 'unknown-error' ); + + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $error_message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + return; + } + + $addon_installs = $result->installs; + foreach ( $addon_installs as $key => $addon_install ) { + $addon_installs[ $key ] = new FS_Site( $addon_install ); + } + + $first_install = $addon_installs[0]; + + // Get user information based on parent's plugin. + $user = $parent_fs->get_user(); + + // First of all, set site and user info - otherwise we won't + // be able to invoke API calls. + $this->_site = $first_install; + $this->_user = $user; + + // Sync add-on plans. + $this->_sync_plans(); + + $this->handle_account_connection( $addon_installs, ! fs_is_network_admin() ); + + // Get site's current plan. + //$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id ); + + // Sync licenses. + $this->_sync_licenses(); + + if ( ! fs_is_network_admin() ) { + // Try to activate premium license. + $this->_activate_license( true, $bundle_license ); + + if ( is_object( $bundle_license ) ) { + $this->maybe_activate_bundle_license( $bundle_license ); + } + } else { + if ( is_object( $bundle_license ) ) { + $premium_license = $bundle_license; + } else { + $license_id = fs_request_get( 'license_id' ); + + if ( is_object( $this->_site ) && + FS_Plugin_License::is_valid_id( $license_id ) && + $license_id == $this->_site->license_id + ) { + // License is already activated. + return; + } + + $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? + $this->_get_license_by_id( $license_id ) : + $this->_get_available_premium_license(); + } + + if ( is_object( $premium_license ) ) { + $this->maybe_network_activate_addon_license( $premium_license ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Site[] $installs + * @param bool $is_site_level + */ + private function handle_account_connection( $installs, $is_site_level ) { + $first_install = $installs[0]; + + if ( $is_site_level ) { + $this->_set_account( $this->_user, $first_install ); + + $this->do_action( 'after_account_connection', $this->_user, $first_install ); + } else { + $this->_store_user(); + + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + $first_blog_id = null; + $blog_2_install_map = array(); + foreach ( $installs as $install ) { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, $install ); + + if ( is_null( $first_blog_id ) ) { + $first_blog_id = $blog_id; + } + + $blog_2_install_map[ $blog_id ] = $install; + } + + if ( ! FS_User::is_valid_id( $this->_storage->network_user_id ) || + ! is_object( self::_get_user_by_id( $this->_storage->network_user_id ) ) + ) { + // Store network user. + $this->_storage->network_user_id = $this->_user->id; + } + + if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { + $this->_storage->network_install_blog_id = $first_blog_id; + } + + if ( count( $installs ) === count( $address_to_blog_map ) ) { + // Super admin opted in for all sites in the network. + $this->_storage->is_network_connected = true; + } + + $this->_store_licenses( false ); + + self::$_accounts->store(); + + // Don't sync the installs data on network upgrade + if ( ! $this->network_upgrade_mode_completed() ) { + $this->send_installs_update(); + } + + // Switch install context back to the first install. + $this->_site = $first_install; + + $current_blog = get_current_blog_id(); + + foreach ( $blog_2_install_map as $blog_id => $install ) { + $this->switch_to_blog( $blog_id ); + + $this->do_action( 'after_account_connection', $this->_user, $install ); + } + + $this->switch_to_blog( $current_blog ); + + $this->do_action( 'after_network_account_connection', $this->_user, $blog_2_install_map ); + } + } + + /** + * Tries to activate parent account based on add-on's info. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param Freemius $parent_fs + */ + private function activate_parent_account( Freemius $parent_fs ) { + if ( ! $this->is_addon() ) { + // This is not an add-on. + return; + } + + if ( $parent_fs->is_registered() ) { + // Already activated. + return; + } + + // Activate parent with add-on's user credentials. + $parent_install = $this->get_api_user_scope()->call( + "/plugins/{$parent_fs->_plugin->id}/installs.json", + 'post', + $parent_fs->get_install_data_for_api( array( + 'uid' => $parent_fs->get_anonymous_id(), + ), false, false ) + ); + + if ( isset( $parent_install->error ) ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . + $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $parent_install->error->message . '', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + + return; + } + + $parent_fs->_admin_notices->remove_sticky( 'connect_account' ); + + if ( $parent_fs->is_pending_activation() ) { + $parent_fs->_admin_notices->remove_sticky( 'activation_pending' ); + + unset( $parent_fs->_storage->is_pending_activation ); + } + + // Get user information based on parent's plugin. + $user = $this->get_user(); + + // First of all, set site info - otherwise we won't + // be able to invoke API calls. + $parent_fs->_site = new FS_Site( $parent_install ); + $parent_fs->_user = $user; + + // Sync add-on plans. + $parent_fs->_sync_plans(); + + $parent_fs->_set_account( $user, $parent_fs->_site ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Admin Menu Items + #---------------------------------------------------------------------------------- + + private $_menu_items = array(); + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.8 + * + * @return array + */ + function get_menu_items() { + return $this->_menu_items; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return string + */ + function get_menu_slug() { + return $this->_menu->get_slug(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function _prepare_admin_menu() { +// if ( ! $this->is_on() ) { +// return; +// } + + /** + * When running from a site admin with a network activated module and the connection + * was NOT delegated and the user still haven't skipped or opted-in, then hide the + * site level settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $should_hide_site_admin_settings = ( + $this->_is_network_active && + ! fs_is_network_admin() && + ! $this->is_delegated_connection() && + ! $this->is_anonymous() && + ! $this->is_registered() + ); + + $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); + + if ( ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || + $should_hide_site_admin_settings + ) { + $this->_menu->remove_menu_item( $should_hide_site_admin_settings ); + } else { + $this->do_action( fs_is_network_admin() ? + 'before_network_admin_menu_init' : + 'before_admin_menu_init' + ); + + $this->add_menu_action(); + + $this->add_network_menu_when_missing(); + + $this->add_submenu_items(); + } + } + + /** + * Admin dashboard menu items modifications. + * + * NOTE: admin_menu action executed before admin_init. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + */ + private function add_menu_action() { + if ( $this->is_activation_mode() ) { + if ( $this->show_opt_in_on_setting_page() ) { + $this->override_plugin_menu_with_activation(); + } else { + /** + * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page. + */ + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { + add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) ); + } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) || + fs_request_get_bool( 'pending_activation' ) + ) { + add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) ); + } + } + } else { + if ( ! $this->is_registered() ) { + // If not registered try to install user. + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { + $this->_install_with_new_user(); + } + } else if ( + fs_request_is_action( 'sync_user' ) && + ( ! $this->has_settings_menu() || $this->show_opt_in_on_themes_page() ) + ) { + $this->_handle_account_user_sync(); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + function _redirect_on_clicked_menu_link() { + $this->_logger->entrance(); + + $page = fs_request_get('page'); + $page = is_string($page) ? strtolower($page) : ''; + + $this->_logger->log( 'page = ' . $page ); + + foreach ( $this->_menu_items as $priority => $items ) { + foreach ( $items as $item ) { + if ( isset( $item['url'] ) ) { + if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) { + $this->_logger->log( 'Redirecting to ' . $item['url'] ); + + fs_redirect( $item['url'] ); + } + } + } + } + } + + /** + * Remove plugin's all admin menu items & pages, and replace with activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + */ + private function override_plugin_menu_with_activation() { + $this->_logger->entrance(); + + $hook = false; + + if ( ! $this->has_settings_menu() ) { + // Add the opt-in page without a menu item. + $hook = FS_Admin_Menu_Manager::add_subpage( + null, + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_slug, + array( &$this, '_connect_page_render' ) + ); + } else if ( $this->_menu->is_top_level() ) { + if ( $this->_menu->is_override_exact() ) { + // Make sure the current page is matching the activation page. + if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { + return; + } + } + + $hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) ); + + if ( false === $hook ) { + // Create new menu item just for the opt-in. + $hook = FS_Admin_Menu_Manager::add_page( + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->get_slug(), + array( &$this, '_connect_page_render' ) + ); + } + } else { + $menus = array( $this->_menu->get_parent_slug() ); + + if ( $this->_menu->is_override_exact() ) { + // Make sure the current page is matching the activation page. + if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { + return; + } + } + + foreach ( $menus as $parent_slug ) { + $hook = $this->_menu->override_submenu_action( + $parent_slug, + $this->_menu->get_raw_slug(), + array( &$this, '_connect_page_render' ) + ); + + if ( false !== $hook ) { + // Found plugin's submenu item. + break; + } + } + } + + if ( $this->is_activation_page() ) { + // Clean admin page from distracting content. + self::_clean_admin_content_section(); + } + + if ( false !== $hook ) { + if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { + $this->_install_with_current_user(); + } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { + $this->_install_with_new_user(); + } + } + } + + /** + * If a plugin was network activated and connected but don't have a network + * level settings, then add an artificial menu item for the Account and other + * Freemius settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function add_network_menu_when_missing() { + $this->_logger->entrance(); + + if ( ! $this->_is_network_active ) { + // Plugin wasn't activated on the network level. + return; + } + + if ( ! fs_is_network_admin() ) { + // The context is not the network admin. + return; + } + + if ( $this->_menu->has_network_menu() ) { + // Plugin already has a network level menu. + return; + } + + if ( $this->is_network_activation_mode() ) { + /** + * Do not add during activation mode, otherwise, there will be duplicate menus while the opt-in + * screen is being shown. + * + * @author Leo Fajardo (@leorw) + */ + return; + } + + if ( ! WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED ) { + if ( $this->is_network_delegated_connection() ) { + // Super-admin delegated the connection to the site admins. + return; + } + } + + if ( ! $this->_menu->has_menu() || $this->_menu->is_top_level() ) { + + if ( $this->_menu->has_menu() || + ! $this->is_addon() || + $this->is_activation_mode() + ) { + $this->_dynamically_added_top_level_page_hook_name = $this->_menu->add_page_and_update( + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->has_menu() ? $this->_menu->get_slug() : $this->_slug + ); + } + } else { + $this->_menu->add_subpage_and_update( + $this->_menu->get_parent_slug(), + $this->get_plugin_name(), + $this->get_plugin_name(), + 'manage_options', + $this->_menu->get_slug() + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.1 + * + * return string + */ + function get_top_level_menu_capability() { + global $menu; + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + + foreach ( $menu as $menu_info ) { + /** + * The second element in the menu info array is the capability/role that has access to the menu and the + * third element is the menu slug. + */ + if ( $menu_info[2] === $top_level_menu_slug ) { + return $menu_info[1]; + } + } + + return 'read'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @return string + */ + private function get_top_level_menu_slug() { + return ( $this->is_addon() ? + $this->get_parent_instance()->_menu->get_top_level_menu_slug() : + $this->_menu->get_top_level_menu_slug() ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function get_pricing_cta_label() { + $label = $this->get_text_inline( 'Upgrade', 'upgrade' ); + + if ( $this->is_in_trial_promotion() && + ! $this->is_paying_or_trial() + ) { + // If running a trial promotion, modify the pricing to load the trial. + $label = $this->get_text_inline( 'Start Trial', 'start-trial' ); + } else if ( $this->is_paying() ) { + $label = $this->get_text_inline( 'Pricing', 'pricing' ); + } + + return $label; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_pricing_page_visible() { + return ( + // Has at least one paid plan. + $this->has_paid_plan() && + // Didn't ask to hide the pricing page. + $this->is_page_visible( 'pricing' ) && + // Don't have a valid active license or has more than one plan. + ( ! $this->is_paying() || ! $this->is_single_plan( true ) ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param bool $is_activation_mode + * + * @return bool + */ + private function should_add_submenu_or_action_links( $is_activation_mode ) { + if ( $this->is_addon() ) { + // No submenu items or action links for add-ons. + return false; + } + + if ( $this->show_opt_in_on_themes_page() ) { + if ( ! fs_is_network_admin() ) { + // Also add action links or submenu items when running in a free .org theme so the tabs will be visible. + return true; + } + } else if ( $is_activation_mode ) { + // Don't show submenu-items/tabs in activation mode, unless it's a wp.org theme. + return false; + } + + if ( fs_is_network_admin() ) { + /** + * Add submenu items or action links to network level when plugin was network activated and the super + * admin did NOT delegate the connection of all sites to site admins. + */ + return ( + $this->_is_network_active && + ( WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED || + ! $this->is_network_delegated_connection() ) + ); + } + + return ( ! $this->_is_network_active || $this->is_delegated_connection() ); + } + + /** + * Add default Freemius menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible. + */ + private function add_submenu_items() { + $this->_logger->entrance(); + + $is_activation_mode = $this->is_activation_mode(); + + $add_submenu_items = $this->should_add_submenu_or_action_links( $is_activation_mode ); + + if ( $add_submenu_items ) { + if ( $this->has_affiliate_program() ) { + // Add affiliation page. + $this->add_submenu_item( + $this->get_text_inline( 'Affiliation', 'affiliation' ), + array( &$this, '_affiliation_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Affiliation', 'affiliation' ), + 'manage_options', + 'affiliation', + 'Freemius::_clean_admin_content_section', + WP_FS__DEFAULT_PRIORITY, + $this->is_submenu_item_visible( 'affiliation' ) + ); + } + } + + if ( $add_submenu_items || + ( $is_activation_mode && + $this->is_only_premium() && + $this->is_admin_page( 'account' ) && + fs_request_is_action( $this->get_unique_affix() . '_sync_license' ) + ) + ) { + if ( ! WP_FS__DEMO_MODE && $this->is_registered() ) { + $show_account = ( + $this->is_submenu_item_visible( 'account' ) && + /** + * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans. + */ + ( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() ) + ); + + // Add user account page. + $this->add_submenu_item( + $this->get_text_inline( 'Account', 'account' ), + array( &$this, '_account_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Account', 'account' ), + 'manage_options', + 'account', + array( &$this, '_account_page_load' ), + WP_FS__DEFAULT_PRIORITY, + ( $add_submenu_items && $show_account ) + ); + } + } + + if ( $add_submenu_items ) { + if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { + // Add contact page. + $this->add_submenu_item( + $this->get_text_inline( 'Contact Us', 'contact-us' ), + array( &$this, '_contact_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Contact Us', 'contact-us' ), + 'manage_options', + 'contact', + 'Freemius::_clean_admin_content_section', + WP_FS__DEFAULT_PRIORITY, + $this->is_submenu_item_visible( 'contact' ) + ); + } + + if ( $this->has_addons() ) { + $this->add_submenu_item( + $this->get_text_inline( 'Add-Ons', 'add-ons' ), + array( &$this, '_addons_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Add-Ons', 'add-ons' ), + 'manage_options', + 'addons', + array( &$this, '_addons_page_load' ), + WP_FS__LOWEST_PRIORITY - 1, + $this->is_submenu_item_visible( 'addons' ) + ); + } + } + + if ( $add_submenu_items || + ( $is_activation_mode && $this->is_only_premium() && $this->is_admin_page( 'pricing' ) ) + ) { + if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { + $show_pricing = ( + $this->is_submenu_item_visible( 'pricing' ) && + $this->is_pricing_page_visible() + ); + + $pricing_cta_text = $this->get_pricing_cta_label(); + $pricing_class = 'upgrade-mode'; + if ( $show_pricing ) { + if ( $this->is_in_trial_promotion() && + ! $this->is_paying_or_trial() + ) { + // If running a trial promotion, modify the pricing to load the trial. + $pricing_class = 'trial-mode'; + } else if ( $this->is_paying() ) { + $pricing_class = ''; + } + } + + // Add upgrade/pricing page. + $this->add_submenu_item( + $pricing_cta_text . '  ' . ( is_rtl() ? $this->get_text_x_inline( '←', 'ASCII arrow left icon', 'symbol_arrow-left' ) : $this->get_text_x_inline( '➤', 'ASCII arrow right icon', 'symbol_arrow-right' ) ), + array( &$this, '_pricing_page_render' ), + $this->get_plugin_name() . ' – ' . $this->get_text_x_inline( 'Pricing', 'noun', 'pricing' ), + 'manage_options', + 'pricing', + 'Freemius::_clean_admin_content_section', + WP_FS__LOWEST_PRIORITY, + ( $add_submenu_items && $show_pricing ), + $pricing_class + ); + } + } + + if ( ! $is_activation_mode || ( true !== $this->_storage->require_license_activation ) ) { + /** + * Add the other menu items if there are any when not in activation mode or license activation is not + * required (license activation is required for registered or anonymous users after activating the + * premium version when the site is not in trial mode or there's no active valid license). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + if ( 0 < count( $this->_menu_items ) ) { + if ( ! $this->_menu->is_top_level() ) { + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + + // Append submenu items right after the plugin's submenu item. + $this->order_sub_submenu_items(); + } else { + // Append submenu items. + $this->embed_submenu_items(); + } + } + } + } + + /** + * Moved the actual submenu item additions to a separated function, + * in order to support sub-submenu items when the plugin's settings + * only have a submenu and not top-level menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + private function embed_submenu_items() { + $item_template = $this->_menu->is_top_level() ? + '%s' : + '%s'; + + $top_level_menu_capability = $this->get_top_level_menu_capability(); + + ksort( $this->_menu_items ); + + $is_first_submenu_item = true; + + foreach ( $this->_menu_items as $priority => $items ) { + foreach ( $items as $item ) { + $capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability ); + + $menu_item = sprintf( + $item_template, + $this->get_unique_affix(), + $item['menu_slug'], + ! empty( $item['class'] ) ? $item['class'] : '', + $item['menu_title'] + ); + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + $menu_slug = $this->_menu->get_slug( $item['menu_slug'] ); + + if ( ! isset( $item['url'] ) ) { + $hook = FS_Admin_Menu_Manager::add_subpage( + $item['show_submenu'] ? + $top_level_menu_slug : + null, + $item['page_title'], + $menu_item, + $capability, + $menu_slug, + $item['render_function'] + ); + + if ( false !== $item['before_render_function'] ) { + add_action( "load-$hook", $item['before_render_function'] ); + } + } else { + FS_Admin_Menu_Manager::add_subpage( + $item['show_submenu'] ? + $top_level_menu_slug : + null, + $item['page_title'], + $menu_item, + $capability, + $menu_slug, + array( $this, '' ) + ); + } + + if ( $item['show_submenu'] && $is_first_submenu_item ) { + if ( $this->_is_network_active && ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + /** + * If the top-level menu has been dynamically created, remove the first submenu item that + * WordPress automatically creates when there's no submenu item whose slug matches the + * parent's. In the following example, the `Awesome Plugin` submenu item will be removed. + * + * Awesome Plugin + * - Awesome Plugin <-- we want to remove this since there's no real setting page for the top-level + * + * @author Leo Fajardo (@leorw) + */ + remove_submenu_page( $top_level_menu_slug, $top_level_menu_slug ); + } + + $is_first_submenu_item = false; + } + } + } + } + + /** + * Re-order the submenu items so all Freemius added new submenu items + * are added right after the plugin's settings submenu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + */ + private function order_sub_submenu_items() { + global $submenu; + + $menu_slug = $this->_menu->get_top_level_menu_slug(); + + /** + * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user + * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any + * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us, + * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission. + * + * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu + * of another plugin, only users that have the right role can access its sub-submenus since we will use the + * capability needed to access the parent menu as the capability for the submenus that we will add. + */ + if ( empty( $submenu[ $menu_slug ] ) ) { + return; + } + + $top_level_menu = &$submenu[ $menu_slug ]; + + $all_submenu_items_after = array(); + + $found_submenu_item = false; + + foreach ( $top_level_menu as $submenu_id => $meta ) { + if ( $found_submenu_item ) { + // Remove all submenu items after the plugin's submenu item. + $all_submenu_items_after[] = $meta; + unset( $top_level_menu[ $submenu_id ] ); + } + + if ( $this->_menu->get_raw_slug() === $meta[2] ) { + // Found the submenu item, put all below. + $found_submenu_item = true; + continue; + } + } + + // Embed all plugin's new submenu items. + $this->embed_submenu_items(); + + // Start with specially high number to make sure it's appended. + $i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 ); + foreach ( $all_submenu_items_after as $meta ) { + $top_level_menu[ $i ] = $meta; + $i ++; + } + + // Sort submenu items. + ksort( $top_level_menu ); + } + + /** + * Helper method to return the module's support forum URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function get_support_forum_url() { + return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" ); + } + + /** + * Displays the Support Forum link when enabled. + * + * Can be filtered like so: + * + * function _fs_show_support_menu( $is_visible, $menu_id ) { + * if ( 'support' === $menu_id ) { + * return _fs->is_registered(); + * } + * return $is_visible; + * } + * _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2); + * + */ + function _add_default_submenu_items() { + if ( ! $this->is_on() ) { + return; + } + + if ( ! $this->is_activation_mode() && + ( ( $this->_is_network_active && fs_is_network_admin() ) || + ( ! $this->_is_network_active && is_admin() ) ) + ) { + $this->add_submenu_link_item( + $this->apply_filters( 'support_forum_submenu', $this->get_text_inline( 'Support Forum', 'support-forum' ) ), + $this->get_support_forum_url(), + 'wp-support-forum', + null, + 50, + $this->is_submenu_item_visible( 'support' ) + ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $menu_title + * @param callable $render_function + * @param bool|string $page_title + * @param string $capability + * @param bool|string $menu_slug + * @param bool|callable $before_render_function + * @param int $priority + * @param bool $show_submenu + * @param string $class Since 1.2.1.5 can add custom classes to menu items. + */ + function add_submenu_item( + $menu_title, + $render_function, + $page_title = false, + $capability = 'manage_options', + $menu_slug = false, + $before_render_function = false, + $priority = WP_FS__DEFAULT_PRIORITY, + $show_submenu = true, + $class = '' + ) { + $this->_logger->entrance( 'Title = ' . $menu_title ); + + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + if ( is_object( $parent_fs ) ) { + $parent_fs->add_submenu_item( + $menu_title, + $render_function, + $page_title, + $capability, + $menu_slug, + $before_render_function, + $priority, + $show_submenu, + $class + ); + + return; + } + } + + if ( ! isset( $this->_menu_items[ $priority ] ) ) { + $this->_menu_items[ $priority ] = array(); + } + + $this->_menu_items[ $priority ][] = array( + 'page_title' => is_string( $page_title ) ? $page_title : $menu_title, + 'menu_title' => $menu_title, + 'capability' => $capability, + 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), + 'render_function' => $render_function, + 'before_render_function' => $before_render_function, + 'show_submenu' => $show_submenu, + 'class' => $class, + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $menu_title + * @param string $url + * @param bool $menu_slug + * @param string $capability + * @param int $priority + * @param bool $show_submenu + */ + function add_submenu_link_item( + $menu_title, + $url, + $menu_slug = false, + $capability = 'read', + $priority = WP_FS__DEFAULT_PRIORITY, + $show_submenu = true + ) { + $this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url ); + + if ( $this->is_addon() ) { + $parent_fs = $this->get_parent_instance(); + + if ( is_object( $parent_fs ) ) { + $parent_fs->add_submenu_link_item( + $menu_title, + $url, + $menu_slug, + $capability, + $priority, + $show_submenu + ); + + return; + } + } + + if ( ! isset( $this->_menu_items[ $priority ] ) ) { + $this->_menu_items[ $priority ] = array(); + } + + $this->_menu_items[ $priority ][] = array( + 'menu_title' => $menu_title, + 'capability' => $capability, + 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), + 'url' => $url, + 'page_title' => $menu_title, + 'render_function' => 'fs_dummy', + 'before_render_function' => '', + 'show_submenu' => $show_submenu, + ); + } + + #endregion ------------------------------------------------------------------ + + #-------------------------------------------------------------------------------- + #region Admin Notices + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @param string|string[] $ids + * @param int|null $network_level_or_blog_id + * + * @uses FS_Admin_Notices::remove_sticky() + */ + function remove_sticky( $ids, $network_level_or_blog_id = null ) { + $this->_admin_notices->remove_sticky( $ids, $network_level_or_blog_id ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Actions / Hooks / Filters + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param string $tag + * + * @return string + */ + public function get_action_tag( $tag ) { + return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $tag + * @param string $slug + * @param bool $is_plugin + * + * @return string + */ + static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) { + $action = "fs_{$tag}"; + + if ( ! empty( $slug ) ) { + $action .= '_' . self::get_module_unique_affix( $slug, $is_plugin ); + } + + return $action; + } + + /** + * Returns a string that can be used to generate a unique action name, + * option name, HTML element ID, or HTML element class. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return string + */ + public function get_unique_affix() { + return self::get_module_unique_affix( $this->_slug, $this->is_plugin() ); + } + + /** + * Returns a string that can be used to generate a unique action name, + * option name, HTML element ID, or HTML element class. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + * + * @param string $slug + * @param bool $is_plugin + * + * @return string + */ + static function get_module_unique_affix( $slug, $is_plugin = true ) { + $affix = $slug; + + if ( ! $is_plugin ) { + $affix .= '-' . WP_FS__MODULE_TYPE_THEME; + } + + return $affix; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are + * based on the slug for backward compatibility. + * + * @param string $tag + * + * @return string + */ + function get_ajax_action( $tag ) { + return self::get_ajax_action_static( $tag, $this->_module_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $tag + * + * @return string + */ + function get_ajax_security( $tag ) { + return wp_create_nonce( $this->get_ajax_action( $tag ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $tag + */ + function check_ajax_referer( $tag ) { + check_ajax_referer( $this->get_ajax_action( $tag ), 'security' ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are + * based on the slug for backward compatibility. + * + * @param string $tag + * @param number|null $module_id + * + * @return string + */ + private static function get_ajax_action_static( $tag, $module_id = null ) { + $action = "fs_{$tag}"; + + if ( ! empty( $module_id ) ) { + $action .= "_{$module_id}"; + } + + return $action; + } + + /** + * Do action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $tag The name of the action to be executed. + * @param mixed $arg,... Optional. Additional arguments which are passed on to the + * functions hooked to the action. Default empty. + * + * @uses do_action() + */ + function do_action( $tag, $arg = '' ) { + $this->_logger->entrance( $tag ); + + $args = func_get_args(); + + call_user_func_array( 'do_action', array_merge( + array( $this->get_action_tag( $tag ) ), + array_slice( $args, 1 ) ) + ); + } + + /** + * Add action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param int $accepted_args + * + * @uses add_action() + */ + function add_action( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY, + $accepted_args = 1 + ) { + $this->_logger->entrance( $tag ); + + add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); + } + + /** + * Add AJAX action, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * + * @uses add_action() + * + * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. + */ + function add_ajax_action( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY + ) { + $this->_logger->entrance( $tag ); + + return self::add_ajax_action_static( + $tag, + $function_to_add, + $priority, + $this->_module_id + ); + } + + /** + * Add AJAX action. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param number|null $module_id + * + * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. + * @uses add_action() + * + */ + static function add_ajax_action_static( + $tag, + $function_to_add, + $priority = WP_FS__DEFAULT_PRIORITY, + $module_id = null + ) { + self::$_static_logger->entrance( $tag ); + + if ( ! self::is_ajax_action_static( $tag, $module_id ) ) { + return false; + } + + add_action( + 'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ), + $function_to_add, + $priority, + 0 + ); + + self::$_static_logger->info( "$tag AJAX callback action added." ); + + return true; + } + + /** + * Send a JSON response back to an Ajax request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $response + */ + static function shoot_ajax_response( $response ) { + wp_send_json( $response ); + } + + /** + * Send a JSON response back to an Ajax request, indicating success. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $data Data to encode as JSON, then print and exit. + */ + static function shoot_ajax_success( $data = null ) { + wp_send_json_success( $data ); + } + + /** + * Send a JSON response back to an Ajax request, indicating failure. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $error Optional error message. + */ + static function shoot_ajax_failure( $error = '' ) { + $result = array( 'success' => false ); + if ( ! empty( $error ) ) { + $result['error'] = $error; + } + + wp_send_json( $result ); + } + + /** + * Apply filter, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $tag The name of the filter hook. + * @param mixed $value The value on which the filters hooked to `$tag` are applied on. + * + * @return mixed The filtered value after all hooked functions are applied to it. + * + * @uses apply_filters() + */ + function apply_filters( $tag, $value ) { + $this->_logger->entrance( $tag ); + + $args = func_get_args(); + array_unshift( $args, $this->get_unique_affix() ); + + return call_user_func_array( 'fs_apply_filter', $args ); + } + + /** + * Add filter, specific for the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $tag + * @param callable $function_to_add + * @param int $priority + * @param int $accepted_args + * + * @uses add_filter() + */ + function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) { + $this->_logger->entrance( $tag ); + + add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); + } + + /** + * Check if has filter. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $tag + * @param callable|bool $function_to_check Optional. The callback to check for. Default false. + * + * @return false|int + * + * @uses has_filter() + */ + function has_filter( $tag, $function_to_check = false ) { + $this->_logger->entrance( $tag ); + + return has_filter( $this->get_action_tag( $tag ), $function_to_check ); + } + + #endregion + + /** + * Override default i18n text phrases. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string[] string $key_value + * + * @uses fs_override_i18n() + */ + function override_i18n( $key_value ) { + fs_override_i18n( $key_value, $this->_slug ); + } + + /* Account Page + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Update site information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store Flush to Database if true. + * @param null|int $network_level_or_blog_id Since 2.0.0 + * @param \FS_Site $site Since 2.0.0 + */ + private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null ) { + $this->_logger->entrance(); + + if ( is_null( $site ) ) { + $site = $this->_site; + } + + if ( !isset( $site ) || !is_object($site) || empty( $site->id ) ) { + $this->_logger->error( "Empty install ID, can't store site." ); + + return; + } + + $site_clone = clone $site; + + $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id ); + + if ( is_object( $this->_user ) && $this->_user->id != $site->user_id ) { + $this->sync_user_by_current_install( $site->user_id ); + + $prev_stored_user_id = $this->_storage->get( 'prev_user_id', false, $network_level_or_blog_id ); + + if ( empty( $prev_stored_user_id ) && + is_object($this->_user) && $this->_user->id != $site->user_id + ) { + /** + * Store the current user ID as the previous user ID so that the previous user can be used + * as the install's owner while the new owner's details are not yet available. + * + * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original` + * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica` + * will be using the previous user until it is updated again (e.g.: until the next clone of `original` + * into `replica`. + * + * @author Leo Fajardo (@leorw) + */ + $this->_storage->store( 'prev_user_id', $sites[ $this->_slug ]->user_id, $network_level_or_blog_id ); + } + } + + $sites[ $this->_slug ] = $site_clone; + + $this->set_account_option( 'sites', $sites, $store, $network_level_or_blog_id ); + } + + /** + * Update plugin's plans information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $store Flush to Database if true. + */ + private function _store_plans( $store = true ) { + $this->_logger->entrance(); + + $plans = self::get_all_plans( $this->_module_type ); + + // Copy plans. + $encrypted_plans = array(); + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + $encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] ); + } + + $plans[ $this->_slug ] = $encrypted_plans; + + $this->set_account_option( 'plans', $plans, $store ); + } + + /** + * Update user's plugin licenses. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $store + * @param number|bool $module_id + * @param FS_Plugin_License[] $licenses + */ + private function _store_licenses( $store = true, $module_id = false, $licenses = array() ) { + $this->_logger->entrance(); + + $all_licenses = self::get_all_licenses(); + + if ( ! FS_Plugin::is_valid_id( $module_id ) ) { + $module_id = $this->_module_id; + + $user_licenses = is_array( $this->_licenses ) ? + $this->_licenses : + array(); + + if ( empty( $user_licenses ) ) { + // If the context user doesn't have any license, don't update the licenses collection. + return; + } + + $new_user_licenses_map = array(); + foreach ( $user_licenses as $user_license ) { + $new_user_licenses_map[ $user_license->id ] = $user_license; + } + + self::store_user_id_license_ids_map( array_keys( $new_user_licenses_map ), $this->_module_id, $this->_user->id ); + + // Update user licenses. + $licenses_to_update_count = count( $new_user_licenses_map ); + foreach ( $all_licenses[ $module_id ] as $key => $license ) { + if ( 0 === $licenses_to_update_count ) { + break; + } + + if ( isset( $new_user_licenses_map[ $license->id ] ) ) { + // Update license. + $all_licenses[ $module_id ][ $key ] = $new_user_licenses_map[ $license->id ]; + unset( $new_user_licenses_map[ $license->id ] ); + + $licenses_to_update_count --; + } + } + + if ( ! empty( $new_user_licenses_map ) ) { + // Add new licenses. + $all_licenses[ $module_id ] = array_merge( array_values( $new_user_licenses_map ), $all_licenses[ $module_id ] ); + } + + $licenses = $all_licenses[ $module_id ]; + } + + if ( ! isset( $all_licenses[ $module_id ] ) ) { + $all_licenses[ $module_id ] = array(); + } + + $all_licenses[ $module_id ] = $licenses; + + self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); + } + + /** + * Update user information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param bool $store Flush to Database if true. + */ + private function _store_user( $store = true ) { + $this->_logger->entrance(); + + if ( empty( $this->_user->id ) ) { + $this->_logger->error( "Empty user ID, can't store user." ); + + return; + } + + $users = self::get_all_users(); + $users[ $this->_user->id ] = $this->_user; + self::$_accounts->set_option( 'users', $users, $store ); + } + + /** + * Update new updates information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param FS_Plugin_Tag|null $update + * @param bool $store Flush to Database if true. + * @param bool|number $plugin_id + */ + private function _store_update( $update, $store = true, $plugin_id = false ) { + $this->_logger->entrance(); + + if ( $update instanceof FS_Plugin_Tag ) { + $update->updated = time(); + } + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $updates = self::get_all_updates(); + $updates[ $plugin_id ] = $update; + self::$_accounts->set_option( 'updates', $updates, $store ); + } + + /** + * Update new updates information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin[] $plugin_addons + * @param bool $store Flush to Database if true. + */ + private function _store_addons( $plugin_addons, $store = true ) { + $this->_logger->entrance(); + + $addons = self::get_all_addons(); + $addons[ $this->_plugin->id ] = $plugin_addons; + self::$_accounts->set_option( 'addons', $addons, $store ); + } + + /** + * Delete plugin's associated add-ons. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + * + * @param bool $store + * + * @return bool + */ + private function _delete_account_addons( $store = true ) { + $all_addons = self::get_all_account_addons(); + + if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) { + return false; + } + + unset( $all_addons[ $this->_plugin->id ] ); + + self::$_accounts->set_option( 'account_addons', $all_addons, $store ); + + return true; + } + + /** + * Update account add-ons list. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin[] $addons + * @param bool $store Flush to Database if true. + */ + private function _store_account_addons( $addons, $store = true ) { + $this->_logger->entrance(); + + $all_addons = self::get_all_account_addons(); + $all_addons[ $this->_plugin->id ] = $addons; + self::$_accounts->set_option( 'account_addons', $all_addons, $store ); + } + + /** + * Purges the cache for the valid user licenses API call so that when the `Account` or `Add-Ons` page is loaded, + * the valid user licenses will be fetched again and the account add-ons may be updated. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + private function purge_valid_user_licenses_cache() { + if ( ! $this->is_registered() ) { + return; + } + + $this->get_api_user_scope()->purge_cache( $this->get_valid_user_licenses_endpoint() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param array $all_licenses + * @param number|null $site_license_id + * @param bool $include_parent_licenses + * + * @return array + */ + private function get_foreign_licenses_info( $all_licenses, $site_license_id = null, $include_parent_licenses = false ) { + $foreign_licenses = array( + 'ids' => array(), + 'license_keys' => array() + ); + + $parent_license_ids_map = array(); + + foreach ( $all_licenses as $license ) { + if ( $license->user_id == $this->_user->id || $license->id == $site_license_id ) { + continue; + } + + $foreign_licenses['ids'][] = $license->id; + $foreign_licenses['license_keys'][] = $license->secret_key; + + if ( + $include_parent_licenses && + is_object( $this->_license ) && + FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) && + ! isset( $parent_license_ids_map[ $this->_license->parent_license_id ] ) + ) { + /** + * Include the parent license's info only if it has not been included before since child licenses + * can have the same parent license. + */ + $foreign_licenses['ids'][] = $this->_license->parent_license_id; + $foreign_licenses['license_keys'][] = $license->secret_key; + + $parent_license_ids_map[ $this->_license->parent_license_id ] = true; + } + } + + if ( empty( $foreign_licenses['ids'] ) ) { + $foreign_licenses = array(); + } + + return $foreign_licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + private function get_valid_user_licenses_endpoint() { + $user_licenses_endpoint = '/licenses.json?type=active' . + ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? '&is_enriched=true' : '' ); + + $foreign_licenses = $this->get_foreign_licenses_info( self::get_all_licenses( $this->_module_id ), null, true ); + + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); + } + + return $user_licenses_endpoint; + } + + /** + * Fetches active licenses that are enriched with product type if there's a context `bundle_id` and bundle + * licenses enriched with product IDs if there are any. From the licenses, the `get_updated_account_addons` + * method filters out non–add-on product IDs and stores the add-on IDs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return stdClass[] array + */ + private function fetch_valid_user_licenses() { + $this->_logger->entrance(); + + $result = $this->get_api_user_scope()->get( $this->get_valid_user_licenses_endpoint() ); + + if ( ! $this->is_api_result_object( $result, 'licenses' ) || + ! is_array( $result->licenses ) + ) { + return array(); + } + + return $result->licenses; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + * + * @return number[] Account add-on IDs. + */ + function get_updated_account_addons() { + $addons = $this->get_addons(); + if ( empty( $addons ) ) { + return array(); + } + + $account_addons = $this->get_account_addons(); + if ( ! is_array( $account_addons ) ) { + $account_addons = array(); + } + + $user_licenses = $this->is_registered() ? + $this->fetch_valid_user_licenses() : + array(); + + if ( empty( $user_licenses ) ) { + return $account_addons; + } + + $addon_ids = array(); + foreach ( $addons as $addon ) { + $addon_ids[] = $addon->id; + } + + $license_product_ids = array(); + + foreach ( $user_licenses as $license ) { + if ( isset( $license->plugin_type ) && 'bundle' === $license->plugin_type ) { + $license_product_ids = array_merge( $license_product_ids, $license->products ); + } else { + $license_product_ids[] = $license->plugin_id; + } + } + + // Filter out non–add-on IDs. + $new_account_addons = array_intersect( $addon_ids, $license_product_ids ); + if ( count( $new_account_addons ) !== count( $account_addons ) ) { + $this->_store_account_addons( array_unique( $new_account_addons ) ); + } + + return $new_account_addons; + } + + /** + * Store account params in the Database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.1 + * + * @param null|int $blog_id Since 2.0.0 + */ + private function _store_account( $blog_id = null ) { + $this->_logger->entrance(); + + $this->_store_site( false, $blog_id ); + $this->_store_user( false ); + $this->_store_plans( false ); + $this->_store_licenses( false ); + + self::$_accounts->store( $blog_id ); + } + + /** + * Sync user's information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + */ + private function _handle_account_user_sync() { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + // Get user's information. + $user = $api->get( '/', true ); + + if ( isset( $user->id ) ) { + $this->_user->first = $user->first; + $this->_user->last = $user->last; + $this->_user->email = $user->email; + + $is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' ); + + if ( $user->is_verified && + ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) + ) { + $this->_user->is_verified = true; + + $this->do_action( 'account_email_verified', $user->email ); + + $this->_admin_notices->add( + $this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ), + $this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!', + 'success', + // Make admin sticky if account menu item is invisible, + // since the page will be auto redirected to the plugin's + // main settings page, and the non-sticky message + // will disappear. + ! $is_menu_item_account_visible, + 'email_verified' + ); + } + + // Flush user details to DB. + $this->_store_user(); + + $this->do_action( 'after_account_user_sync', $user ); + + /** + * If account menu item is hidden, redirect to plugin's main settings page. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @link https://github.com/Freemius/wordpress-sdk/issues/6 + */ + if ( ! $is_menu_item_account_visible ) { + fs_redirect( $this->_get_admin_page_url() ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * @uses FS_Api + * + * @param number|bool $license_id + * + * @return FS_Subscription|object|bool + */ + private function _fetch_site_license_subscription( $license_id = false ) { + $this->_logger->entrance(); + $api = $this->get_api_site_scope(); + + if ( ! is_numeric( $license_id ) ) { + $license_id = FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) ? + $this->_license->parent_license_id : + $this->_license->id; + } + + $result = $api->get( "/licenses/{$license_id}/subscriptions.json", true ); + + return ! isset( $result->error ) ? + ( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ? + new FS_Subscription( $result->subscriptions[0] ) : + false + ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * @uses FS_Api + * + * @param number|bool $plan_id + * + * @return FS_Plugin_Plan|object + */ + private function _fetch_site_plan( $plan_id = false ) { + $this->_logger->entrance(); + $api = $this->get_api_site_scope(); + + if ( ! is_numeric( $plan_id ) ) { + $plan_id = $this->_site->plan_id; + } + + $plan = $api->get( "/plans/{$plan_id}.json", true ); + + return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * @uses FS_Api + * + * @return FS_Plugin_Plan[]|object + */ + private function _fetch_plugin_plans() { + $this->_logger->entrance(); + $api = $this->get_current_or_network_user_api_scope(); + + /** + * @since 1.2.3 When running in DEV mode, retrieve pending plans as well. + */ + $result = $api->get( $this->add_show_pending( "/plugins/{$this->_module_id}/plans.json" ), true ); + + if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) { + for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) { + $result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] ); + } + + $result = $result->plans; + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $plan_id + * + * @return \FS_Plugin_Plan|object + */ + private function fetch_plan_by_id( $plan_id ) { + $this->_logger->entrance(); + $api = $this->get_current_or_network_user_api_scope(); + + $result = $api->get( "/plugins/{$this->_module_id}/plans/{$plan_id}.json", true ); + + return $this->is_api_result_entity( $result ) ? + new FS_Plugin_Plan( $result ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * @uses FS_Api + * + * @param number|bool $plugin_id + * @param number|bool $site_license_id + * @param array $foreign_licenses @since 2.0.0. This is used by network-activated plugins. + * @param number|null $blog_id + * + * @return FS_Plugin_License[]|object + */ + private function _fetch_licenses( + $plugin_id = false, + $site_license_id = false, + $foreign_licenses = array(), + $blog_id = null + ) { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $user_licenses_endpoint = "/plugins/{$plugin_id}/licenses.json?is_enriched=true"; + if ( ! empty ( $foreign_licenses ) ) { + $foreign_licenses = array( + // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. + 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), + 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) + ); + + $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); + } + + $result = $api->get( $user_licenses_endpoint, true ); + + $is_site_license_synced = false; + + $api_errors = array(); + + if ( $this->is_api_result_object( $result, 'licenses' ) && + is_array( $result->licenses ) + ) { + for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { + $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); + + if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) { + $is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id ); + } + } + + $result = $result->licenses; + } else { + $api_errors[] = $result; + $result = array(); + } + + if ( ! $is_site_license_synced ) { + if ( ! is_null( $blog_id ) ) { + /** + * If blog ID is not null, the request is for syncing of the license of a single site via the + * network-level "Account" page. + * + * @author Leo Fajardo (@leorw) + */ + $this->switch_to_blog( $blog_id ); + } + + $api = $this->get_api_site_scope(); + + if ( is_numeric( $site_license_id ) ) { + // Try to retrieve a foreign license that is linked to the install. + $api_result = $api->call( '/licenses.json?is_enriched=true' ); + + if ( $this->is_api_result_object( $api_result, 'licenses' ) && + is_array( $api_result->licenses ) + ) { + $licenses = $api_result->licenses; + + if ( ! empty( $licenses ) ) { + $result[] = new FS_Plugin_License( $licenses[0] ); + } + } else { + $api_errors[] = $api_result; + } + } else if ( + is_object( $this->_license ) && + /** + * Sync only if the license belongs to the context plugin. `$plugin_id` can be an add-on ID while + * the FS instance that does the syncing is the parent FS instance. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->_license->plugin_id == $plugin_id + ) { + $is_license_in_result = false; + if ( ! empty( $result ) ) { + foreach ( $result as $license ) { + if ( $license->id == $this->_license->id ) { + $is_license_in_result = true; + break; + } + } + } + + if ( ! $is_license_in_result ) { + // Fetch foreign license by ID and license key. + $license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . + urlencode( $this->_license->secret_key ) . '&is_enriched=true' ); + + if ( $this->is_api_result_entity( $license ) ) { + $result[] = new FS_Plugin_License( $license ); + } else { + $api_errors[] = $license; + } + } + } + + if ( ! is_null( $blog_id ) ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + } + + if ( is_array( $result ) && 0 < count( $result ) ) { + // If found at least one license, return license collection even if there are errors. + return $result; + } + + if ( ! empty( $api_errors ) ) { + // If found any errors and no licenses, return first error. + return $api_errors[0]; + } + + // Fallback to empty licenses list. + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param number $license_id + * @param string $license_key + * + * @return \FS_Plugin_License|object + */ + private function fetch_license_by_key( $license_id, $license_key ) { + $this->_logger->entrance(); + + $api = $this->get_current_or_network_user_api_scope(); + + $result = $api->get( "/licenses/{$license_id}.json?license_key=" . urlencode( $license_key ) ); + + return $this->is_api_result_entity( $result ) ? + new FS_Plugin_License( $result ) : + $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * @uses FS_Api + * + * @param number|bool $plugin_id + * @param bool $flush + * + * @return FS_Payment[]|object + */ + function _fetch_payments( $plugin_id = false, $flush = false ) { + $this->_logger->entrance(); + + $api = $this->get_api_user_scope(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $include_bundles = ( + is_object( $this->_plugin ) && + FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) + ); + + $result = $api->get( + "/plugins/{$plugin_id}/payments.json?include_addons=true" . ($include_bundles ? '&include_bundles=true' : ''), + $flush + ); + + if ( ! isset( $result->error ) ) { + for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) { + $result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] ); + } + $result = $result->payments; + } + + return $result; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * @uses FS_Api + * + * @param bool $flush + * + * @return \FS_Billing|mixed + */ + function _fetch_billing( $flush = false ) { + require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php'; + + $billing = $this->get_api_user_scope()->get( 'billing.json', $flush ); + + if ( $this->is_api_result_entity( $billing ) ) { + $billing = new FS_Billing( $billing ); + } + + return $billing; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param FS_Plugin_License[] $licenses + * @param number $module_id + */ + private function _update_licenses( $licenses, $module_id ) { + $this->_logger->entrance(); + + if ( is_array( $licenses ) ) { + for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) { + $licenses[ $i ]->updated = time(); + } + } + + $this->_store_licenses( true, $module_id, $licenses ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * + * @return object|false New plugin tag info if exist. + */ + private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { + $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than ); + + if ( ! is_object( $latest_tag ) ) { + return false; + } + + $plugin_version = $this->get_plugin_version(); + + // Check if version is actually newer. + $has_new_version = + // If it's an non-installed add-on then always return latest. + ( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) || + // Compare versions. + version_compare( $plugin_version, $latest_tag->version, '<' ); + + $this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' ); + + $is_latest_version_beta = ( 'beta' === $latest_tag->release_mode ); + + $this->_storage->beta_data = array( + 'is_beta' => $is_latest_version_beta, + 'version' => $latest_tag->version + ); + + return $has_new_version ? $latest_tag : false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * + * @return bool|FS_Plugin_Tag + */ + function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { + $this->_logger->entrance(); + + if ( ! is_numeric( $plugin_id ) ) { + $plugin_id = $this->_plugin->id; + } + + $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than ); + $updates = $this->get_all_updates(); + + return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false; + } + + /** + * Check if site assigned with active license. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @deprecated Please use has_active_valid_license() instead because license can be cancelled. + */ + function has_active_license() { + return ( + is_object( $this->_license ) && + is_numeric( $this->_license->id ) && + ! $this->_license->is_expired() + ); + } + + /** + * Check if site assigned with active & valid (not expired) license. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param bool $check_expiration + */ + function has_active_valid_license( $check_expiration = true ) { + return self::is_active_valid_license( $this->_license, $check_expiration ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + function is_data_debug_mode() { + if ( is_null( $this->is_whitelabeled ) || ! $this->is_whitelabeled ) { + return false; + } + + $fs = $this->is_addon() ? + $this->get_parent_instance() : + $this; + + if ( $fs->is_network_active() && fs_is_network_admin() ) { + $is_developer_license_debug_mode = get_site_transient( "fs_{$this->get_id()}_data_debug_mode" ); + } else { + $is_developer_license_debug_mode = get_transient( "fs_{$this->get_id()}_data_debug_mode" ); + } + + return ( 'true' === $is_developer_license_debug_mode ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + function _set_data_debug_mode() { + if ( ! $this->is_whitelabeled( true ) ) { + return; + } + + $license_or_user_key = fs_request_get( 'license_or_user_key' ); + + $transient_value = ( ! empty( $license_or_user_key ) ) ? + 'true' : + 'false'; + + if ( 'true' === $transient_value ) { + $stored_key = $this->_storage->get( ! FS_User::is_valid_id( $this->_storage->last_license_user_id ) ? + 'last_license_key' : + 'last_license_user_key' + ); + + if ( md5( $license_or_user_key ) !== $stored_key ) { + $this->shoot_ajax_failure( sprintf( + '%s... %s', + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), + $this->get_text_inline( + 'seems like the key you entered doesn\'t match our records.', + 'developer-or-license-not-found' + ) + ) ); + } + } + + if ( $this->is_network_active() && fs_is_network_admin() ) { + set_site_transient( + "fs_{$this->get_id()}_data_debug_mode", + $transient_value, + WP_FS__TIME_24_HOURS_IN_SEC / 24 + ); + } else { + set_transient( + "fs_{$this->get_id()}_data_debug_mode", + $transient_value, + WP_FS__TIME_24_HOURS_IN_SEC / 24 + ); + } + + if ( 'true' === $transient_value ) { + $this->_admin_notices->add_sticky( + $this->get_text_inline( + 'Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.', + 'data_debug_mode_enabled' + ), + 'data_debug_mode_enabled' + ); + } + + $this->shoot_ajax_success(); + } + + /** + * Check if a given license is active & valid (not expired). + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + * + * @param FS_Plugin_License $license + * @param bool $check_expiration + * + * @return bool + */ + private static function is_active_valid_license( $license, $check_expiration = true ) { + return ( + is_object( $license ) && + FS_Plugin_License::is_valid_id( $license->id ) && + $license->is_active() && + ( ! $check_expiration || $license->is_valid() ) + ); + } + + /** + * Checks if there's any site that is associated with an active & valid license. + * This logic is used to determine if the admin can download the premium code base from a network level admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + * + * @return bool + */ + function has_any_active_valid_license() { + if ( ! fs_is_network_admin() ) { + return $this->has_active_valid_license(); + } + + $installs = $this->get_blog_install_map(); + $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); + + foreach ( $installs as $blog_id => $install ) { + if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { + continue; + } + + foreach ( $all_plugin_licenses as $license ) { + if ( $license->id == $install->license_id ) { + if ( self::is_active_valid_license( $license ) ) { + return true; + } + } + } + } + + return false; + } + + /** + * Check if site assigned with license with enabled features. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function has_features_enabled_license() { + return ( + is_object( $this->_license ) && + is_numeric( $this->_license->id ) && + $this->_license->is_features_enabled() + ); + } + + /** + * Checks if the product is activated with a bundle license. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + * + * @return bool + */ + function is_activated_with_bundle_license() { + if ( ! $this->has_features_enabled_license() ) { + return false; + } + + return FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ); + } + + /** + * Check if user is a trial or have feature enabled license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @return bool + */ + function can_use_premium_code() { + return $this->is_trial() || $this->has_features_enabled_license(); + } + + /** + * Checks if the current user can activate plugins or switch themes. Note that this method should only be used + * after the `init` action is triggered because it is using `current_user_can()` which is only functional after + * the context user is authenticated. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function is_user_admin() { + /** + * Require a super-admin when network activated, running from the network level OR if + * running from the site level but not delegated the opt-in. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + if ( $this->_is_network_active && + ( fs_is_network_admin() || ! $this->is_delegated_connection() ) + ) { + return is_super_admin(); + } + + return ( $this->is_plugin() && current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) + || ( $this->is_theme() && current_user_can( 'switch_themes' ) ); + } + + /** + * Sync site's plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by + * the admin. + * @param bool $is_context_single_site @since 2.0.0. This is used when syncing a license for a single install from the + * network-level "Account" page. + * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the + * `_sync_plugin_license` method in order to switch to the previous blog when sending + * updates for a single site in case `execute_cron` has switched to a different blog. + */ + private function _sync_license( $background = false, $is_context_single_site = false, $current_blog_id = null ) { + $this->_logger->entrance(); + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + + $is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() ); + + if ( $is_addon_sync ) { + $this->_sync_addon_license( $plugin_id, $background ); + } else { + $this->_sync_plugin_license( $background, true, $is_context_single_site, $current_blog_id ); + } + + $this->do_action( 'after_account_plan_sync', $this->get_plan_name() ); + } + + /** + * Sync plugin's add-on license. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * @uses FS_Api + * + * @param number $addon_id + * @param bool $background + */ + private function _sync_addon_license( $addon_id, $background ) { + $this->_logger->entrance(); + + if ( $this->is_addon_activated( $addon_id ) ) { + // If already installed, use add-on sync. + $fs_addon = self::get_instance_by_id( $addon_id ); + + if ( + // Add-on is network activated and network integrated. + $fs_addon->is_network_active() || + // Background sync cron. + self::is_cron() || + // Add-on is not network activated or not network integrated. + ! fs_is_network_admin() + ) { + $fs_addon->_sync_license( $background ); + + return; + } + } + + // Validate add-on exists. + $addon = $this->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + return; + } + + // Add add-on into account add-ons. + $account_addons = $this->get_account_addons(); + if ( ! is_array( $account_addons ) ) { + $account_addons = array(); + } + $account_addons[] = $addon->id; + $account_addons = array_unique( $account_addons ); + $this->_store_account_addons( $account_addons ); + + // Load add-on licenses. + $licenses = $this->_fetch_licenses( $addon->id ); + + // Sync add-on licenses. + if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { + $this->_update_licenses( $licenses, $addon->id ); + + if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) { + $plans_result = $this->get_api_site_or_plugin_scope()->get( $this->add_show_pending( "/addons/{$addon_id}/plans.json" ) ); + + if ( ! isset( $plans_result->error ) ) { + $plans = array(); + foreach ( $plans_result->plans as $plan ) { + $plans[] = new FS_Plugin_Plan( $plan ); + } + + $this->_admin_notices->add_sticky( + sprintf( + ( FS_Plan_Manager::instance()->has_free_plan( $plans ) ? + $this->get_text_inline( 'Your %s Add-on plan was successfully upgraded.', 'addon-successfully-upgraded-message' ) : + /* translators: %s:product name, e.g. Facebook add-on was successfully... */ + $this->get_text_inline( '%s Add-on was successfully purchased.', 'addon-successfully-purchased-message' ) ), + $addon->title + ) . ' ' . $this->get_latest_download_link( + $this->get_text_inline( 'Download the latest version', 'download-latest-version' ), + $addon_id + ), + 'addon_plan_upgraded_' . $addon->slug, + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + } + } + } + + /** + * Sync site's plugin plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by the admin. + * @param bool $send_installs_update Since 2.0.0 + * @param bool $is_context_single_site Since 2.0.0. This is used when sending an update for a single install and + * syncing its license from the network-level "Account" page (e.g.: after + * activating a license only for the single install). + * @param int|null $current_blog_id Since 2.2.3. This is passed from the `execute_cron` method so that it + * can be used here to switch to the previous blog in case `execute_cron` + * has switched to a different blog. + */ + private function _sync_plugin_license( + $background = false, + $send_installs_update = true, + $is_context_single_site = false, + $current_blog_id = null + ) { + $this->_logger->entrance(); + + $plan_change = 'none'; + + $is_site_level_sync = ( $is_context_single_site || fs_is_blog_admin() || ! $this->_is_network_active ); + + if ( ! $send_installs_update ) { + $site = $this->_site; + } else { + /** + * Sync site info. + * + * @todo This line will execute install sync on a daily basis, even if running the free version (for opted-in users). The reason we want to keep it that way is for cases when the user was a paying customer, then there was a failure in subscription payment, and then after some time the payment was successful. This could be heavily optimized. For example, we can skip the $flush if the current install was never associated with a paid version. + */ + if ( $is_site_level_sync ) { + /** + * Switch to the previous blog since `execute_cron` may have switched to a different blog. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + if ( is_numeric( $current_blog_id ) ) { + $this->switch_to_blog( $current_blog_id ); + } + + $result = $this->send_install_update( array(), true ); + $is_valid = $this->is_api_result_entity( $result ); + } else { + $result = $this->send_installs_update( array(), true ); + $is_valid = $this->is_api_result_object( $result, 'installs' ); + } + + if ( ! $is_valid ) { + if ( $is_context_single_site ) { + // Switch back to the main blog so that the following logic will have the right entities. + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + // Show API messages only if not background sync or if paying customer. + if ( ! $background || $this->is_paying() ) { + // Try to ping API to see if not blocked. + if ( ! FS_Api::test() ) { + /** + * Failed to ping API - blocked! + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages. + */ + $api = $this->get_api_site_scope(); + + if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { + self::$_global_admin_notices->add( + sprintf( + $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', 'server-blocking-access' ), + $this->get_plugin_name(), + '' . implode( ', ', $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com' + ) ) ) . '' + ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error', + $background, + 'api_blocked' + ); + } + } else { + // Authentication params are broken. + $this->_admin_notices->add( + $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + } + + // No reason to continue with license sync while there are API issues. + return; + } + + if ( $is_site_level_sync ) { + $site = new FS_Site( $result ); + } else { + // Map site addresses to their blog IDs. + $address_to_blog_map = $this->get_address_to_blog_map(); + + // Find the current context install. + $site = null; + foreach ( $result->installs as $install ) { + if ( $install->id == $this->_site->id ) { + $site = new FS_Site( $install ); + } else { + $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); + $blog_id = $address_to_blog_map[ $address ]; + + $this->_store_site( true, $blog_id, new FS_Site( $install ) ); + } + } + } + + // Sync plans. + $this->_sync_plans(); + } + + // Remove sticky API connectivity message. + self::$_global_admin_notices->remove_sticky( 'api_blocked' ); + + if ( ! $this->has_paid_plan() ) { + $this->_site = $site; + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } else { + $context_blog_id = 0; + + if ( $is_context_single_site ) { + $context_blog_id = get_current_blog_id(); + + // Switch back to the main blog in order to properly sync the license. + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + /** + * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license + * associated with that ID is not included in the user's licenses collection. + */ + $this->_sync_licenses( + $site->license_id, + ( $is_context_single_site ? + $context_blog_id : + null + ) + ); + + if ( $is_context_single_site ) { + $this->switch_to_blog( $context_blog_id ); + } + + // Check if plan / license changed. + if ( $site->plan_id != $this->_site->plan_id || + // Check if trial started. + $site->trial_plan_id != $this->_site->trial_plan_id || + $site->trial_ends != $this->_site->trial_ends || + // Check if license changed. + $site->license_id != $this->_site->license_id + ) { + if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) { + // New trial started. + $this->_site = $site; + $plan_change = 'trial_started'; + + // For trial with subscription use-case. + $new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id ); + + if ( is_object( $new_license ) && $new_license->is_valid() ) { + $this->_site = $site; + $this->_update_site_license( $new_license ); + $this->_store_licenses(); + + $this->_sync_site_subscription( $this->_license ); + } + } else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) { + // Was in trial, but now trial expired and no license ID. + // New trial started. + $this->_site = $site; + $plan_change = 'trial_expired'; + } else { + $is_free = $this->is_free_plan(); + + // Make sure license exist and not expired. + $new_license = is_null( $site->license_id ) ? + null : + $this->_get_license_by_id( $site->license_id ); + + if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) { + // License cancelled. + $this->_site = $site; + $this->_update_site_license( $new_license ); + $this->_store_licenses(); + + $plan_change = 'cancelled'; + } else if ( $is_free && ( ( ! is_object( $new_license ) || $new_license->is_expired() ) ) ) { + // The license is expired, so ignore upgrade method. + $this->_site = $site; + } else { + // License changed. + $this->_site = $site; + + /** + * IMPORTANT: + * The line below should be executed before trying to activate the license on the rest of the network, otherwise, the license' activation counters may be out of sync + there's no need to activate the license on the context site since it's already activated on it. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $this->_update_site_license( $new_license ); + + if ( ! $is_context_single_site && + fs_is_network_admin() && + $this->_is_network_active && + $new_license->quota > 1 && + get_blog_count() > 1 + ) { + // See if license can activated on all sites. + if ( ! $this->try_activate_license_on_network( $this->_user, $new_license ) ) { + if ( ! fs_request_get_bool( 'auto_install' ) ) { + // Open the license activation dialog box on the account page. + add_action( 'admin_footer', array( + &$this, + '_open_license_activation_dialog_box' + ) ); + } + } + } + + $this->_store_licenses(); + + $plan_change = $is_free ? + ( $this->is_only_premium() ? 'activated' : 'upgraded' ) : + ( is_object( $new_license ) ? + 'changed' : + 'downgraded' ); + } + } + + // Store updated site info. + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } else { + if ( ! is_object( $this->_license ) ) { + $this->maybe_update_whitelabel_flag( + FS_Plugin_License::is_valid_id( $site->license_id ) ? + $this->get_license_by_id( $site->license_id ) : + null + ); + } else { + $this->maybe_update_whitelabel_flag( $this->_license ); + + if ( $this->_license->is_expired() ) { + if ( ! $this->has_features_enabled_license() ) { + $this->_deactivate_license(); + $plan_change = 'downgraded'; + } else { + $last_time_expired_license_notice_was_shown = $this->_storage->get( 'expired_license_notice_shown', 0 ); + + if ( time() - ( 14 * WP_FS__TIME_24_HOURS_IN_SEC ) >= $last_time_expired_license_notice_was_shown ) { + /** + * Show the expired license notice every 14 days. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + $plan_change = 'expired'; + } + } + } + } + + if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) { + $this->_sync_site_subscription( $this->_license ); + } + } + + if ( ! $this->is_addon() && + $this->_site->is_beta() !== $site->is_beta + ) { + // Beta flag updated. + $this->_site = $site; + + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } + + if ( $this->is_addon() || $this->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $this->purge_valid_user_licenses_cache(); + } + } + + $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; + + if ( $this->apply_filters( 'has_paid_plan_account', $this->has_paid_plan() ) ) { + switch ( $plan_change ) { + case 'none': + if ( ! $background && is_admin() ) { + $plan = $this->is_trial() ? + $this->get_trial_plan() : + $this->get_plan(); + + if ( $plan->is_free() ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.', 'plan-did-not-change-message' ), + '' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text_x_inline( 'Trial', 'trial period', 'trial' ) : '' ) . '' + ) . ' ' . sprintf( + '%s', + $this->contact_url( + 'bug', + sprintf( $this->get_text_inline( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.', 'plan-did-not-change-email-message' ), + strtoupper( $plan->name ) + ) + ), + $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) + ), + $hmm_text + ); + } + } + break; + case 'upgraded': + case 'activated': + $this->_admin_notices->add_sticky( + ( 'activated' === $plan_change ) ? + $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ) : + $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) . + $this->get_complete_upgrade_instructions(), + 'plan_upgraded', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'trial_expired', + 'activation_complete', + 'license_expired', + ) ); + break; + case 'changed': + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your plan was successfully changed to %s.', 'plan-changed-to-x-message' ), + $this->get_plan_title() + ), + 'plan_changed' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'trial_expired', + 'activation_complete', + ) ); + break; + case 'downgraded': + $this->_admin_notices->add_sticky( + ($this->has_free_plan() ? + sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using the free %s forever.', 'license-expired-blocking-message' ), $this->_module_type ) : + /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ + sprintf( $this->get_text_inline( 'Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'license-expired-blocking-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true) ) ), + 'license_expired', + $hmm_text + ); + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'cancelled': + $this->_admin_notices->add( + $this->get_text_inline( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.', 'license-cancelled' ) . ' ' . + sprintf( + '%s', + $this->contact_url( 'bug' ), + $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) + ), + $hmm_text, + 'error' + ); + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'expired': + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.', 'license-expired-non-blocking-message' ), $this->get_plan()->title ), + 'license_expired', + $hmm_text + ); + + $this->_storage->expired_license_notice_shown = WP_FS__SCRIPT_START_TIME; + + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + break; + case 'trial_started': + $this->_admin_notices->add_sticky( + sprintf( + $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), + '' . $this->get_plugin_name() . '' + ) . $this->get_complete_upgrade_instructions( $this->get_trial_plan()->title ), + 'trial_started', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + ) ); + break; + case 'trial_expired': + $this->_admin_notices->add_sticky( + ($this->has_free_plan() ? + $this->get_text_inline( 'Your free trial has expired. You can still continue using all our free features.', 'trial-expired-message' ) : + /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ + sprintf( $this->get_text_inline( 'Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'trial-expired-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true))), + 'trial_expired', + $hmm_text + ); + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'plan_upgraded', + ) ); + break; + } + } + + if ( 'none' !== $plan_change ) { + if ( + ! is_object( $this->_license ) || + ! $this->_license->is_whitelabeled + ) { + $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); + } + + $this->do_action( 'after_license_change', $plan_change, $this->get_plan() ); + } + } + + /** + * Include the required JS at the footer of the admin to trigger the license activation dialog box. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + public function _open_license_activation_dialog_box() { + $vars = array( 'license_id' => $this->_site->license_id ); + fs_require_once_template( 'js/open-license-activation.php', $vars ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $background + * @param FS_Plugin_License|null $premium_license + */ + protected function _activate_license( $background = false, $premium_license = null ) { + $this->_logger->entrance(); + + if ( is_null( $premium_license ) ) { + $license_id = fs_request_get( 'license_id' ); + + if ( is_object( $this->_site ) && + FS_Plugin_License::is_valid_id( $license_id ) && + $license_id == $this->_site->license_id + ) { + // License is already activated. + return; + } + + $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? + $this->_get_license_by_id( $license_id ) : + $this->_get_available_premium_license(); + } + + if ( ! is_object( $premium_license ) ) { + return; + } + + if ( ! is_object( $this->_site ) ) { + // Not yet opted-in. + $user = $this->get_current_or_network_user(); + if ( ! is_object( $user ) ) { + $user = self::_get_user_by_id( $premium_license->user_id ); + } + + if ( is_object( $user ) ) { + $this->install_with_user( $user, $premium_license->secret_key, false, false, false ); + } else { + $this->opt_in( + false, + false, + false, + $premium_license->secret_key + ); + + return; + } + } + + + /** + * If the premium license is already associated with the install, just + * update the license reference (activation is not required). + * + * @since 1.1.9 + */ + if ( $premium_license->id == $this->_site->license_id ) { + // License is already activated. + $this->_update_site_license( $premium_license ); + $this->_store_account(); + + return; + } + + if ( $this->_site->user_id != $premium_license->user_id ) { + $api_request_params = array( 'license_key' => $premium_license->secret_key ); + } else { + $api_request_params = array(); + } + + $api = $this->get_api_site_scope(); + $license = $api->call( "/licenses/{$premium_license->id}.json?is_enriched=true", 'put', $api_request_params ); + + if ( ! $this->is_api_result_entity( $license ) ) { + if ( ! $background ) { + $this->_admin_notices->add( sprintf( + '%s %s', + $this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ), + ( is_object( $license ) && isset( $license->error ) ? + $license->error->message : + sprintf( '%s
    %s', + $this->get_text_inline( 'Error received from the server:', 'server-error-message' ), + var_export( $license, true ) + ) + ) + ), + $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...', + 'error' + ); + } + + return; + } + + $premium_license = new FS_Plugin_License( $license ); + + // Updated site plan. + $site = $this->get_api_site_scope()->get( '/', true ); + if ( $this->is_api_result_entity( $site ) ) { + $this->_site = new FS_Site( $site ); + } + $this->_update_site_license( $premium_license ); + + $this->_store_account(); + + if ( $this->is_addon() || $this->has_addons() ) { + /** + * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, + * an updated valid user licenses collection will be fetched from the server which is used to also + * update the account add-ons (add-ons the user has licenses for). + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $this->purge_valid_user_licenses_cache(); + } + + if ( ! $background ) { + $this->_admin_notices->add_sticky( + $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) . + $this->get_complete_upgrade_instructions(), + 'license_activated', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + + $this->_admin_notices->remove_sticky( array( + 'trial_promotion', + 'license_expired', + ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param bool $show_notice + */ + protected function _deactivate_license( $show_notice = true ) { + $this->_logger->entrance(); + + $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; + + if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'It looks like your site currently doesn\'t have an active license.', 'no-active-license-message' ), $this->get_plan_title() ), + $hmm_text + ); + + return; + } + + $api = $this->get_api_site_scope(); + $license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' ); + + $this->handle_license_deactivation_result( $license, $hmm_text, $show_notice ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param FS_Plugin_License $license + * @param bool|string $hmm_text + * @param bool $show_notice + */ + private function handle_license_deactivation_result( $license, $hmm_text = false, $show_notice = true ) { + if ( isset( $license->error ) ) { + $this->_admin_notices->add( + $this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '
    ' . + $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ), + $hmm_text, + 'error' + ); + + return; + } + + // Update license cache. + if ( is_array( $this->_licenses ) ) { + for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { + if ( $license->id == $this->_licenses[ $i ]->id ) { + $this->_licenses[ $i ] = new FS_Plugin_License( $license ); + } + } + } + + // Update site plan to default. + $this->_sync_plans(); + $this->_site->plan_id = $this->_plans[0]->id; + // Unlink license from site. + $this->_update_site_license( null ); + + $this->_store_account(); + + if ( $show_notice ) { + $this->_admin_notices->add( + sprintf( $this->is_only_premium() ? + $this->get_text_inline( 'Your %s license was successfully deactivated.', 'license-deactivation-message_premium-only' ) : + $this->get_text_inline( 'Your license was successfully deactivated, you are back to the %s plan.', 'license-deactivation-message' ), + $this->get_plan_title() + ), + $this->get_text_inline( 'O.K', 'ok' ) + ); + } + + $this->_admin_notices->remove_sticky( array( + 'plan_upgraded', + 'license_activated', + ) ); + } + + /** + * Site plan downgrade. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @return object + * + * @uses FS_Api + */ + private function _downgrade_site() { + $this->_logger->entrance(); + + $deactivate_license = fs_request_get_bool( 'deactivate_license' ); + + $api = $this->get_api_site_scope(); + $site = $api->call( 'downgrade.json', 'put', array( 'deactivate_license' => $deactivate_license ) ); + + $plan_downgraded = false; + $plan = false; + if ( $this->is_api_result_entity( $site ) ) { + $prev_plan_id = $this->_site->plan_id; + + // Update new site plan id. + $this->_site->plan_id = $site->plan_id; + + $plan = $this->get_plan(); + $subscription = $this->_sync_site_subscription( $this->_license ); + + // Plan downgraded if plan was changed or subscription was cancelled. + $plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) || + ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() ); + } else { + // handle different error cases. + $this->handle_license_deactivation_result( + $site, + $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...' + ); + } + + if ( ! $plan_downgraded ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.', 'subscription-cancellation-failure-message' ) + ) + ); + } + + // Remove previous sticky message about upgrade (if exist). + $this->_admin_notices->remove_sticky( 'plan_upgraded' ); + + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Your subscription was successfully cancelled. Your %s plan license will expire in %s.', 'plan-x-downgraded-message' ), + $plan->title, + human_time_diff( time(), strtotime( $this->_license->expiration ) ) + ) + ); + + // Store site updates. + $this->_store_site(); + + if ( $deactivate_license && + ! FS_Plugin_License::is_valid_id( $site->license_id ) + ) { + if ( $this->_site->is_localhost() ) { + $this->_license->activated_local = max( 0, $this->_license->activated_local - 1 ); + } else { + $this->_license->activated = max( 0, $this->_license->activated - 1 ); + } + + // Handle successful license deactivation result. + $this->handle_license_deactivation_result( $this->_license ); + } + + return $site; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @param bool|string $plan_name + * + * @return bool If trial was successfully started. + */ + function start_trial( $plan_name = false ) { + $this->_logger->entrance(); + + // Alias. + $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; + + if ( $this->is_trial() ) { + // Already in trial mode. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'You are already running the %s in a trial mode.', 'in-trial-mode' ), $this->_module_type ), + $oops_text, + 'error' + ); + + return false; + } + + if ( $this->_site->is_trial_utilized() ) { + // Trial was already utilized. + $this->_admin_notices->add( + $this->get_text_inline( 'You already utilized a trial before.', 'trial-utilized' ), + $oops_text, + 'error' + ); + + return false; + } + + if ( false !== $plan_name ) { + $plan = $this->get_plan_by_name( $plan_name ); + + if ( false === $plan ) { + // Plan doesn't exist. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ), + $oops_text, + 'error' + ); + + return false; + } + + if ( ! $plan->has_trial() ) { + // Plan doesn't exist. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ), + $oops_text, + 'error' + ); + + return false; + } + } else { + if ( ! $this->has_trial_plan() ) { + // None of the plans have a trial. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ), + $oops_text, + 'error' + ); + + return false; + } + + $plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); + + $plan = $plans_with_trial[0]; + } + + $api = $this->get_api_site_scope(); + $plan = $api->call( "plans/{$plan->id}/trials.json", 'post' ); + + if ( ! $this->is_api_result_entity( $plan ) ) { + // Some API error while trying to start the trial. + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) + . ' ' . var_export( $plan, true ), + $oops_text, + 'error' + ); + + return false; + } + + // Sync license. + $this->_sync_license(); + + return $this->is_trial(); + } + + /** + * Cancel site trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return object + * + * @uses FS_Api + */ + private function _cancel_trial() { + $this->_logger->entrance(); + + if ( ! $this->is_trial() ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' ) + ) + ); + } + + $trial_plan = $this->get_trial_plan(); + + $api = $this->get_api_site_scope(); + $site = $api->call( 'trials.json', 'delete' ); + + $trial_cancelled = false; + + if ( $this->is_api_result_entity( $site ) ) { + $prev_trial_ends = $this->_site->trial_ends; + + if ( $this->is_paid_trial() ) { + $this->_license->expiration = $site->trial_ends; + $this->_license->is_cancelled = true; + $this->_update_site_license( $this->_license ); + $this->_store_licenses(); + + // Clear subscription reference. + $this->_sync_site_subscription( null ); + } + + // Update site info. + $this->_site = new FS_Site( $site ); + + $trial_cancelled = ( $prev_trial_ends != $site->trial_ends ); + } else { + // @todo handle different error cases. + } + + if ( ! $trial_cancelled ) { + return (object) array( + 'error' => (object) array( + 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' ) + ) + ); + } + + // Remove previous sticky messages about upgrade or trial (if exist). + $this->_admin_notices->remove_sticky( array( + 'trial_started', + 'trial_promotion', + 'plan_upgraded', + ) ); + + // Store site updates. + $this->_store_site(); + + if ( ! $this->is_addon() || + ! $this->deactivate_premium_only_addon_without_license( true ) + ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $trial_plan->title ) + ); + } + + return $site; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|number $plugin_id + * + * @return bool + */ + private function _is_addon_id( $plugin_id ) { + return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id ); + } + + /** + * Check if user eligible to download premium version updates. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + private function _can_download_premium() { + return $this->has_any_active_valid_license() || + ( $this->is_trial() && ! $this->get_trial_plan()->is_free() ); + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|number $addon_id + * @param string $type "json" or "zip" + * + * @return string + */ + private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) { + + $is_addon = $this->_is_addon_id( $addon_id ); + + $is_premium = null; + if ( ! $is_addon ) { + $is_premium = ( $this->is_premium() || $this->_can_download_premium() ); + } else if ( $this->is_addon_activated( $addon_id ) ) { + $fs_addon = self::get_instance_by_id( $addon_id ); + $is_premium = ( $fs_addon->is_premium() || $fs_addon->_can_download_premium() ); + } + + // If add-on, then append add-on ID. + $endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) . + '/updates/latest.' . $type; + + // If add-on and not yet activated, try to fetch based on server licensing. + if ( is_bool( $is_premium ) ) { + $endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint ); + } + + if ( $this->has_secret_key() ) { + $endpoint = add_query_arg( 'type', 'all', $endpoint ); + } else if ( is_object( $this->_site ) && $this->_site->is_beta() ) { + $endpoint = add_query_arg( 'type', 'beta', $endpoint ); + } + + return $endpoint; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool|number $addon_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + * @param bool|string $fetch_readme Since 2.2.1 + * + * @return object|false Plugin latest tag info. + */ + function _fetch_latest_version( + $addon_id = false, + $flush = true, + $expiration = WP_FS__TIME_24_HOURS_IN_SEC, + $newer_than = false, + $fetch_readme = true + ) { + $this->_logger->entrance(); + + $switch_to_blog_id = null; + + /** + * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in. + * @since 1.1.7.4 Also check updates for add-ons. + */ + if ( ! $this->is_registered() && + ! $this->_is_addon_id( $addon_id ) + ) { + if ( ! is_multisite() ) { + return false; + } + + $installs_map = $this->get_blog_install_map(); + + foreach ( $installs_map as $blog_id => $install ) { + /** + * @var FS_Site $install + */ + if ( $install->is_trial() ) { + $switch_to_blog_id = $blog_id; + break; + } + + if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { + $license = $this->get_license_by_id( $install->license_id ); + + if ( is_object( $license ) && $license->is_features_enabled() ) { + $switch_to_blog_id = $blog_id; + break; + } + } + } + + if ( is_null( $switch_to_blog_id ) ) { + return false; + } + } + + $current_blog_id = is_numeric( $switch_to_blog_id ) ? + get_current_blog_id() : + 0; + + if ( is_numeric( $switch_to_blog_id ) ) { + $this->switch_to_blog( $switch_to_blog_id ); + } + + $latest_version_endpoint = $this->_get_latest_version_endpoint( $addon_id, 'json' ); + + if ( ! empty( $newer_than ) ) { + $latest_version_endpoint = add_query_arg( 'newer_than', $newer_than, $latest_version_endpoint ); + } + + if ( true === $fetch_readme ) { + $latest_version_endpoint = add_query_arg( 'readme', 'true', $latest_version_endpoint ); + } + + $tag = $this->get_api_site_or_plugin_scope()->get( + $latest_version_endpoint, + $flush, + $expiration + ); + + if ( is_numeric( $switch_to_blog_id ) ) { + $this->switch_to_blog( $current_blog_id ); + } + + $latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get'; + + $this->_logger->departure( 'Latest version ' . $latest_version ); + + return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false; + } + + #---------------------------------------------------------------------------------- + #region Download Plugin + #---------------------------------------------------------------------------------- + + /** + * Download latest plugin version, based on plan. + * + * Not like _download_latest(), this will redirect the page + * to secure download url to prevent dual download (from FS to WP server, + * and then from WP server to the client / browser). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @uses FS_Api + * @uses wp_redirect() + */ + private function download_latest_directly( $plugin_id = false ) { + $this->_logger->entrance(); + + wp_redirect( $this->get_latest_download_api_url( $plugin_id ) ); + } + + /** + * Get latest plugin FS API download URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @return string + */ + private function get_latest_download_api_url( $plugin_id = false ) { + $this->_logger->entrance(); + + return $this->get_api_site_scope()->get_signed_url( + $this->_get_latest_version_endpoint( $plugin_id, 'zip' ) + ); + } + + /** + * Get payment invoice URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @param bool|number $payment_id + * + * @return string + */ + function _get_invoice_api_url( $payment_id = false ) { + $this->_logger->entrance(); + + $url = $this->get_api_user_scope()->get_signed_url( + "/payments/{$payment_id}/invoice.pdf" + ); + + if ( ! fs_starts_with( $url, 'https://' ) ) { + // Always use HTTPS for invoices. + $url = 'https' . substr( $url, 4 ); + } + + return $url; + } + + /** + * Get latest plugin download link. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $label + * @param bool|number $plugin_id + * + * @return string + */ + private function get_latest_download_link( $label, $plugin_id = false ) { + return sprintf( + '%s', + $this->_get_latest_download_local_url( $plugin_id ), + $label + ); + } + + /** + * Get latest plugin download local URL. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool|number $plugin_id + * + * @return string + */ + function _get_latest_download_local_url( $plugin_id = false ) { + // Add timestamp to protect from caching. + $params = array( 'ts' => WP_FS__SCRIPT_START_TIME ); + + if ( ! empty( $plugin_id ) ) { + $params['plugin_id'] = $plugin_id; + } else if ( $this->is_addon() ) { + $params['plugin_id'] = $this->get_id(); + } + + $fs = $this->is_addon() ? + $this->get_parent_instance() : + $this; + + return $this->apply_filters( 'download_latest_url', $fs->get_account_url( 'download_latest', $params ) ); + } + + #endregion Download Plugin ------------------------------------------------------------------ + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param bool $background Hints the method if it's a background updates check. If false, it means that + * was initiated by the admin. + * @param bool|number $plugin_id + * @param bool $flush Since 1.1.7.3 + * @param int $expiration Since 1.2.2.7 + * @param bool|string $newer_than Since 2.2.1 + */ + private function check_updates( + $background = false, + $plugin_id = false, + $flush = true, + $expiration = WP_FS__TIME_24_HOURS_IN_SEC, + $newer_than = false + ) { + $this->_logger->entrance(); + + // Check if there's a newer version for download. + $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than ); + + $update = null; + if ( is_object( $new_version ) ) { + $update = new FS_Plugin_Tag( $new_version ); + + if ( ! $background ) { + $this->_admin_notices->add( + sprintf( + /* translators: %s: Numeric version number (e.g. '2.1.9' */ + $this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ), + $update->version, + sprintf( + '%s', + $this->get_account_url( 'download_latest' ), + sprintf( + /* translators: %s: plan name (e.g. latest "Professional" version) */ + $this->get_text_inline( 'the latest %s version here', 'latest-x-version' ), + $this->get_plan_title() + ) + ) + ), + $this->get_text_inline( 'New', 'new' ) . '!' + ); + } + } else if ( false === $new_version && ! $background ) { + $this->_admin_notices->add( + $this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ), + $this->get_text_inline( 'You are all good!', 'you-are-good' ) + ); + } + + $this->_store_update( $update, true, $plugin_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool $flush Since 1.1.7.3 add 24 hour cache by default. + * + * @return FS_Plugin[] + * + * @uses FS_Api + */ + private function sync_addons( $flush = false ) { + $this->_logger->entrance(); + + $api = $this->get_api_site_or_plugin_scope(); + + $path = $this->add_show_pending( '/addons.json?enriched=true&count=50' ); + + /** + * @since 1.2.1 + * + * If there's a cached version of the add-ons and not asking + * for a flush, just use the currently stored add-ons. + */ + if ( ! $flush && $api->is_cached( $path ) ) { + $addons = self::get_all_addons(); + + return isset( $addons[ $this->_plugin->id ] ) ? + $addons[ $this->_plugin->id ] : + array(); + } + + $result = $api->get( $path, $flush ); + + $addons = array(); + if ( $this->is_api_result_object( $result, 'plugins' ) && + is_array( $result->plugins ) + ) { + for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) { + $addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] ); + } + + $this->_store_addons( $addons, true ); + } + + return $addons; + } + + /** + * Handle user email update. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + * + * @param string $new_email + * + * @return object + */ + private function update_email( $new_email ) { + $this->_logger->entrance(); + + + $api = $this->get_api_user_scope(); + $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array( + 'email' => $new_email, + 'after_email_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ), + ) ); + + if ( ! isset( $user->error ) ) { + $this->_user->email = $user->email; + $this->_user->is_verified = $user->is_verified; + $this->_store_user(); + } else { + // handle different error cases. + + } + + return $user; + } + + #---------------------------------------------------------------------------------- + #region API Error Handling + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + private function is_api_error( $result ) { + return FS_Api::is_api_error( $result ); + } + + /** + * Checks if given API result is a non-empty and not an error object. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * @param string|null $required_property Optional property we want to verify that is set. + * + * @return bool + */ + function is_api_result_object( $result, $required_property = null ) { + return FS_Api::is_api_result_object( $result, $required_property ); + } + + /** + * Checks if given API result is a non-empty entity object with non-empty ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool + */ + private function is_api_result_entity( $result ) { + return FS_Api::is_api_result_entity( $result ); + } + + #endregion + + /** + * Make sure a given argument is an array of a specific type. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $array + * @param string $class + * + * @return bool + */ + private function is_array_instanceof( $array, $class ) { + return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) ); + } + + /** + * Start install ownership change. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @uses FS_Api + * + * @param string $candidate_email + * + * @return bool Is ownership change successfully initiated. + */ + private function init_change_owner( $candidate_email ) { + $this->_logger->entrance(); + + $api = $this->get_api_site_scope(); + $result = $api->call( "/users/{$this->_user->id}.json", 'put', array( + 'email' => $candidate_email, + 'after_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'change_owner' ) + ), + ) ); + + return ! $this->is_api_error( $result ); + } + + /** + * Handle install ownership change. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * @uses FS_Api + * + * @return bool Was ownership change successfully complete. + */ + private function complete_change_owner() { + $this->_logger->entrance(); + + $site_result = $this->get_api_site_scope( true )->get(); + $site = new FS_Site( $site_result ); + $this->_site = $site; + + $user = new FS_User(); + $user->id = fs_request_get( 'user_id' ); + + // Validate install's user and given user. + if ( $user->id != $this->_site->user_id ) { + return false; + } + + $user->public_key = fs_request_get( 'user_public_key' ); + $user->secret_key = fs_request_get( 'user_secret_key' ); + + // Fetch new user information. + $this->_user = $user; + $user_result = $this->get_api_user_scope( true )->get(); + $user = new FS_User( $user_result ); + $this->_user = $user; + + $this->_set_account( $user, $site ); + + return true; + } + + /** + * Completes ownership change by license. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @param number $user_id + * @param array[string]number $install_ids_by_slug_map + * + */ + private function complete_ownership_change_by_license( $user_id, $install_ids_by_slug_map ) { + $this->_logger->entrance(); + + $this->sync_user_by_current_install( $user_id ); + + $result = $this->get_api_user_scope( true )->get( + "/installs.json?install_ids=" . implode( ',', $install_ids_by_slug_map ) + ); + + if ( $this->is_api_result_object( $result, 'installs' ) ) { + $sites = self::get_all_sites( $this->get_module_type() ); + $install_ids_by_slug_map = array_flip( $install_ids_by_slug_map ); + + foreach ( $result->installs as $install ) { + $site = new FS_Site( $install ); + + $sites[ $install_ids_by_slug_map[ $site->id ] ] = clone $site; + } + + $this->set_account_option( 'sites', $sites, true ); + } + } + + /** + * Handle user name update. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * @uses FS_Api + * + * @return object + */ + private function update_user_name() { + $this->_logger->entrance(); + $name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' ); + + $api = $this->get_api_user_scope(); + $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array( + 'name' => $name, + ) ); + + if ( ! isset( $user->error ) ) { + $this->_user->first = $user->first; + $this->_user->last = $user->last; + $this->_store_user(); + } else { + // handle different error cases. + + } + + return $user; + } + + /** + * Verify user email. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * @uses FS_Api + */ + private function verify_email() { + $this->_handle_account_user_sync(); + + if ( $this->_user->is_verified() ) { + return; + } + + $api = $this->get_api_site_scope(); + $result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array( + 'after_email_confirm_url' => $this->_get_admin_page_url( + 'account', + array( 'fs_action' => 'sync_user' ) + ) + ) ); + + if ( ! isset( $result->error ) ) { + $this->_admin_notices->add( sprintf( + $this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ), + sprintf( '%2$s', esc_url( $this->_user->email ), $this->_user->email ) + ) ); + } else { + // handle different error cases. + + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @param array $params + * @param bool|null $network + * + * @return string + */ + function get_activation_url( $params = array(), $network = null ) { + if ( $this->is_addon() && $this->has_free_plan() ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 Add-on's activation is the parent's module activation. + */ + return $this->get_parent_instance()->get_activation_url( $params ); + } + + return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params, $network ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param array $params + * + * @return string + */ + function get_reconnect_url( $params = array() ) { + $params['fs_action'] = 'reset_anonymous_mode'; + $params['fs_unique_affix'] = $this->get_unique_affix(); + + return $this->get_activation_url( $params ); + } + + /** + * Get the URL of the page that should be loaded after the user connect + * or skip in the opt-in screen. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $filter Filter name. + * @param array $params Since 1.2.2.7 + * @param bool|null $network + * + * @return string + */ + function get_after_activation_url( $filter, $params = array(), $network = null ) { + if ( $this->show_opt_in_on_themes_page() && + ( fs_request_has( 'pending_activation' ) || + // For cases when the first time path is set, even though it's a WP.org theme. + fs_request_get_bool( $this->get_unique_affix() . '_show_optin' ) ) + ) { + $first_time_path = ''; + } else { + $first_time_path = $this->_menu->get_first_time_path( + fs_is_network_admin() && $this->_is_network_active + ); + } + + if ( $this->_is_network_active && + fs_is_network_admin() && + ! $this->_menu->has_network_menu() && + $this->is_network_registered() + ) { + $target_url = $this->get_account_url(); + } else { + // Default plugin's page. + $target_url = $this->_get_admin_page_url( '', array(), $network ); + } + + return add_query_arg( $params, $this->apply_filters( + $filter, + empty( $first_time_path ) ? + $target_url : + $first_time_path + ) ); + } + + /** + * Handle account page updates / edits / actions. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + */ + private function _handle_account_edits() { + if ( ! $this->is_user_admin() ) { + return; + } + + $action = fs_get_action(); + + if ( empty( $action ) ) { + return; + } + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + $install_id = fs_request_get( 'install_id', '' ); + + // Alias. + $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; + + $is_network_action = $this->is_network_level_action(); + $blog_id = $this->is_network_level_site_specific_action(); + $is_parent_plugin_action = ( $plugin_id == $this->get_id() ); + + if ( is_numeric( $blog_id ) ) { + $this->switch_to_blog( $blog_id ); + } else { + $blog_id = ''; + } + + switch ( $action ) { + case 'opt_in': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $is_parent_plugin_action ) { + if ( $is_network_action && ! empty( $blog_id ) ) { + if ( ! $this->is_registered() ) { + $this->install_with_user( + $this->get_network_user(), + false, + false, + false, + false + ); + + $this->_admin_notices->add( + $this->get_text_inline( 'Site successfully opted in.', 'successful-opt-in' ), + $this->get_text_inline( 'Awesome', 'awesome' ) + ); + } + } + } + break; + + case 'toggle_tracking': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $is_parent_plugin_action ) { + if ( $is_network_action && ! empty( $blog_id ) ) { + if ( $this->is_registered() ) { + if ( $this->is_tracking_prohibited() ) { + if ( $this->allow_site_tracking() ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation' ), $this->_module_type ), + $this->get_text_inline( 'Thank you!', 'thank-you' ) + ); + } + } else { + if ( $this->stop_site_tracking() ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( 'We will no longer be sending any usage data of %s on %s to %s.', 'opted-out-successfully' ), + $this->get_plugin_title(), + fs_strip_url_protocol( get_site_url( $blog_id ) ), + sprintf( + '%s', + 'https://freemius.com', + 'freemius.com' + ) + ) + ); + } + } + } + } + } + + break; + + case 'delete_account': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + $is_network_deletion = $is_network_action && empty( $blog_id ); + + if ( $is_parent_plugin_action ) { + // Delete add-on installs if have any. + $installed_addons = $this->get_installed_addons(); + foreach ( $installed_addons as $fs_addon ) { + if ( $is_network_deletion ) { + $fs_addon->delete_network_account_event(); + } else { + $fs_addon->delete_account_event(); + } + } + + if ( $is_network_deletion ) { + $this->delete_network_account_event(); + } else { + $this->delete_account_event(); + } + + // Clear user and site. + $this->_site = null; + $this->_user = null; + + $this->maybe_set_slug_and_network_menu_exists_flag(); + + fs_redirect( $this->get_activation_url() ); + } else { + if ( $this->is_addon_activated( $plugin_id ) ) { + $fs_addon = self::get_instance_by_id( $plugin_id ); + + if ( $is_network_deletion ) { + $fs_addon->delete_network_account_event(); + } else { + $fs_addon->delete_account_event(); + } + + fs_redirect( $this->_get_admin_page_url( 'account' ) ); + } + } + + return; + + case 'downgrade_account': + if ( is_numeric( $blog_id ) ) { + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + } else { + check_admin_referer( $action ); + } + + $switch_to_network_install_blog_after_cancellation = ( + is_numeric( $blog_id ) && + $plugin_id == $this->get_id() && + ! $this->is_trial() + ); + + $result = $this->cancel_subscription_or_trial( $plugin_id ); + if ( $this->is_api_error( $result ) ) { + $this->_admin_notices->add( + $result->error->message, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + if ( $switch_to_network_install_blog_after_cancellation ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + + return; + + case 'activate_license': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + $fs = $this; + if ( $plugin_id != $this->get_id() ) { + $fs = $this->is_addon_activated( $plugin_id ) ? + self::get_instance_by_id( $plugin_id ) : + null; + } + + if ( is_object( $fs ) ) { + $fs->_activate_license(); + + /** + * Remove the product ID from `$_REQUEST` so that the syncing of the license for the other products will work properly. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.0 + */ + unset( $_REQUEST['plugin_id'] ); + + if ( $this->is_bundle_license_auto_activation_enabled() ) { + $fs->maybe_activate_bundle_license( null, array(), is_numeric( $blog_id ) ? $blog_id : 0 ); + } + } + + return; + + case 'deactivate_license': + check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); + + if ( $plugin_id == $this->get_id() ) { + $this->_deactivate_license(); + + if ( $this->is_only_premium() ) { + // Clear user and site. + $this->_site = null; + $this->_user = null; + + if ( ! $is_network_action ) { + fs_redirect( $this->get_activation_url() ); + } else if ( is_numeric( $blog_id ) ) { + $this->switch_to_blog( $this->_storage->network_install_blog_id ); + } + } + } else { + if ( $this->is_addon_activated( $plugin_id ) ) { + $fs_addon = self::get_instance_by_id( $plugin_id ); + $fs_addon->_deactivate_license(); + } + } + + return; + + case 'check_updates': + check_admin_referer( $action ); + $this->check_updates(); + + return; + + case 'change_owner': + $state = fs_request_get( 'state', 'init' ); + switch ( $state ) { + case 'init': + $candidate_email = fs_request_get( 'candidate_email', '' ); + + if ( $this->init_change_owner( $candidate_email ) ) { + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '' . $this->_user->email . '' ) ); + } + break; + case 'owner_confirmed': + $candidate_email = fs_request_get( 'candidate_email', '' ); + + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '' . $candidate_email . '' ) ); + break; + case 'candidate_confirmed': + if ( $this->complete_change_owner() ) { + $this->_admin_notices->add_sticky( + sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '' . $this->_user->email . '' ), + 'ownership_changed', + $this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!' + ); + } else { + // @todo Handle failed ownership change message. + } + break; + } + + return; + + case 'update_email': + check_admin_referer( 'update_email' ); + + $new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' ); + $result = $this->update_email( $new_email ); + + if ( isset( $result->error ) ) { + switch ( $result->error->code ) { + case 'user_exist': + $this->_admin_notices->add( + $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . + sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email . '' ) . + sprintf( + '', + $this->get_account_url( 'change_owner', array( + 'state' => 'init', + 'candidate_email' => $new_email + ) ), + $this->get_text_inline( 'Change Ownership', 'change-ownership' ) + ), + $oops_text, + 'error' + ); + break; + } + } else { + $this->_admin_notices->add( $this->get_text_inline( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.', 'email-updated-message' ) ); + } + + return; + + case 'update_user_name': + check_admin_referer( 'update_user_name' ); + + $result = $this->update_user_name(); + + if ( isset( $result->error ) ) { + $this->_admin_notices->add( + $this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ), + $oops_text, + 'error' + ); + } else { + $this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) ); + } + + return; + + #region Actions that might be called from external links (e.g. email) + + case 'cancel_trial': + $result = $this->cancel_subscription_or_trial( $plugin_id ); + if ( $this->is_api_error( $result ) ) { + $this->_admin_notices->add( + $result->error->message, + $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + 'error' + ); + } + + return; + + case 'verify_email': + $this->verify_email(); + + return; + + case 'sync_user': + $this->_handle_account_user_sync(); + + return; + + case $this->get_unique_affix() . '_sync_license': + $this->_sync_license(); + + return; + + case 'download_latest': + $this->download_latest_directly( $plugin_id ); + + return; + + #endregion + } + + if ( WP_FS__IS_POST_REQUEST ) { + $properties = array( 'site_secret_key', 'site_id', 'site_public_key' ); + foreach ( $properties as $p ) { + if ( 'update_' . $p === $action ) { + check_admin_referer( $action ); + + $this->_logger->log( $action ); + + $site_property = substr( $p, strlen( 'site_' ) ); + $site_property_value = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' ); + $this->get_site()->{$site_property} = $site_property_value; + + // Store account after modification. + $this->_store_site(); + + $this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value ); + + $this->_admin_notices->add( sprintf( + /* translators: %s: User's account property (e.g. email address, name) */ + $this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ), + '' . str_replace( '_', ' ', $p ) . '' + ) ); + + return; + } + } + } + } + + /** + * Account page resources load. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _account_page_load() { + $this->_logger->entrance(); + + $this->_logger->info( var_export( $_REQUEST, true ) ); + + fs_enqueue_local_style( 'fs_account', '/admin/account.css' ); + + if ( $this->has_addons() ) { + wp_enqueue_script( 'plugin-install' ); + add_thickbox(); + + function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + + return $classes; + } + + add_filter( 'admin_body_class', 'fs_addons_body_class' ); + } + + if ( $this->has_paid_plan() && + ! $this->has_any_license() && + ! $this->is_sync_executed() && + $this->is_tracking_allowed() + ) { + /** + * If no licenses found and no sync job was executed during the last 24 hours, + * just execute the sync job right away (blocking execution). + * + * @since 1.1.7.3 + */ + $this->run_manual_sync(); + } + + $this->_handle_account_edits(); + + if ( + is_object( $this->_license ) && + $this->_license->user_id == $this->_user->id && + ! $this->is_whitelabeled( true ) + ) { + $this->_admin_notices->add( + sprintf( + $this->get_text_inline( "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.", 'license_not_whitelabeled' ), + sprintf( + '%s', + $this->get_text_inline( 'Click here', 'click-here' ) + ) + ), + '', + 'success', + false, + 'license_not_whitelabeled' + ); + } + + $this->do_action( 'account_page_load_before_departure' ); + } + + /** + * Renders the "Affiliation" page. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + */ + function _affiliation_page_render() { + $this->_logger->entrance(); + + $this->fetch_affiliate_and_terms(); + + fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' ); + + $vars = array( 'id' => $this->_module_id ); + echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) ); + } + + + /** + * Render account page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _account_page_render() { + $this->_logger->entrance(); + + $template = 'account.php'; + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) ); + } + + /** + * Render account connect page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _connect_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) ); + } + + /** + * Load required resources before add-ons page render. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _addons_page_load() { + $this->_logger->entrance(); + + fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); + + wp_enqueue_script( 'plugin-install' ); + add_thickbox(); + + function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + + return $classes; + } + + add_filter( 'admin_body_class', 'fs_addons_body_class' ); + + if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { + $this->_admin_notices->add( + sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '' . $this->get_plugin_name() . '' ), + $this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ), + 'update-nag' + ); + } + } + + /** + * Render add-ons page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function _addons_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) ); + } + + /* Pricing & Upgrade + ------------------------------------------------------------------------------------------------------------------*/ + /** + * Render pricing page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _pricing_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + if ( 'true' === fs_request_get( 'checkout', false ) ) { + echo $this->apply_filters( 'templates/checkout.php', fs_get_template( 'checkout.php', $vars ) ); + } else { + echo $this->apply_filters( 'templates/pricing.php', fs_get_template( 'pricing.php', $vars ) ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + function _maybe_add_pricing_ajax_handler() { + if ( ! $this->should_use_external_pricing() ) { + $this->add_ajax_action( 'pricing_ajax_action', array( &$this, '_fs_pricing_ajax_action_handler' ) ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + function _fs_pricing_ajax_action_handler() { + $this->check_ajax_referer( 'pricing_ajax_action' ); + + $result = null; + $pricing_action = fs_request_get( 'pricing_action' ); + + switch ( $pricing_action ) { + case 'fetch_pricing_data': + $params = array( + 'is_enriched' => true, + 'trial' => fs_request_get_bool( 'trial' ), + 'sandbox' => fs_request_get( 'sandbox' ), + 's_ctx_type' => fs_request_get( 's_ctx_type' ), + 's_ctx_id' => fs_request_get( 's_ctx_id' ), + 's_ctx_ts' => fs_request_get( 's_ctx_ts' ), + 's_ctx_secure' => fs_request_get( 's_ctx_secure' ), + ); + + $bundle_id = $this->get_bundle_id(); + $bundle_public_key = $this->get_bundle_public_key(); + + $has_bundle_context = ( FS_Plugin::is_valid_id( $bundle_id ) && ! empty( $bundle_public_key ) ); + + if ( ! $has_bundle_context ) { + $api = $this->get_api_plugin_scope(); + } else { + $api = FS_Api::instance( + $bundle_id, + 'plugin', + $bundle_id, + $bundle_public_key, + ! $this->is_live(), + false, + $this->get_sdk_version() + ); + + $params['plugin_id'] = $this->get_id(); + $params['plugin_public_key'] = $this->get_public_key(); + } + + $result = $api->get( 'pricing.json?' . http_build_query( $params ) ); + break; + case 'start_trial': + $result = $this->opt_in( + false, + false, + false, + false, + false, + fs_request_get( 'plan_id' ) + ); + } + + if ( is_object( $result ) && $this->is_api_error( $result ) ) { + $this->_logger->api_error( $result ); + + self::shoot_ajax_failure( + isset( $result->error ) ? + ( is_string( $result->error ) ? $result->error : $result->error->message ) : + var_export( $result, true ) + ); + } + + $this->shoot_ajax_success( $result ); + } + + #---------------------------------------------------------------------------------- + #region Contact Us + #---------------------------------------------------------------------------------- + + /** + * Render contact-us page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function _contact_page_render() { + $this->_logger->entrance(); + + $vars = array( 'id' => $this->_module_id ); + + /** + * Added filter to the template to allow developers wrapping the template + * in custom HTML (e.g. within a wizard/tabs). + * + * @author Vova Feldman (@svovaf) + * @since 2.1.3 + */ + echo $this->apply_filters( 'templates/contact.php', fs_get_template( 'contact.php', $vars ) ); + } + + #endregion ------------------------------------------------------------------------ + + /** + * Hide all admin notices to prevent distractions. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @uses remove_all_actions() + */ + private static function _hide_admin_notices() { + remove_all_actions( 'admin_notices' ); + remove_all_actions( 'network_admin_notices' ); + remove_all_actions( 'all_admin_notices' ); + remove_all_actions( 'user_admin_notices' ); + } + + static function _clean_admin_content_section_hook() { + self::_hide_admin_notices(); + + // Hide footer. + echo ''; + } + + /** + * Attach to admin_head hook to hide all admin notices. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + static function _clean_admin_content_section() { + add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' ); + } + + /* CSS & JavaScript + ------------------------------------------------------------------------------------------------------------------*/ + /* function _enqueue_script($handle, $src) { + $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); + + $this->_logger->entrance( 'script = ' . $url ); + + wp_enqueue_script( $handle, $url ); + }*/ + + /* SDK + ------------------------------------------------------------------------------------------------------------------*/ + private $_user_api; + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_api_user_scope( $flush = false ) { + if ( ! isset( $this->_user_api ) || $flush ) { + $this->_user_api = $this->get_api_user_scope_by_user( $this->_user ); + } + + return $this->_user_api; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_User $user + * + * @return \FS_Api + */ + private function get_api_user_scope_by_user( FS_User $user ) { + return FS_Api::instance( + $this->_module_id, + 'user', + $user->id, + $user->public_key, + ! $this->is_live(), + $user->secret_key, + $this->get_sdk_version() + ); + } + + /** + * + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_current_or_network_user_api_scope( $flush = false ) { + if ( ! $this->_is_network_active || + ( isset( $this->_user ) && $this->_user instanceof FS_User ) + ) { + return $this->get_api_user_scope( $flush ); + } + + $user = $this->get_current_or_network_user(); + + $this->_user_api = FS_Api::instance( + $this->_module_id, + 'user', + $user->id, + $user->public_key, + ! $this->is_live(), + $user->secret_key, + $this->get_sdk_version() + ); + + return $this->_user_api; + } + + private $_site_api; + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @param bool $flush + * + * @return FS_Api + */ + private function get_api_site_scope( $flush = false ) { + if ( ! isset( $this->_site_api ) || $flush ) { + $this->_site_api = FS_Api::instance( + $this->_module_id, + 'install', + $this->_site->id, + $this->_site->public_key, + ! $this->is_live(), + $this->_site->secret_key, + $this->get_sdk_version() + ); + } + + return $this->_site_api; + } + + private $_plugin_api; + + /** + * Get plugin public API scope. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return FS_Api + */ + function get_api_plugin_scope() { + if ( ! isset( $this->_plugin_api ) ) { + $this->_plugin_api = FS_Api::instance( + $this->_module_id, + 'plugin', + $this->_plugin->id, + $this->_plugin->public_key, + ! $this->is_live(), + false, + $this->get_sdk_version() + ); + } + + return $this->_plugin_api; + } + + /** + * Get bundle public API scope. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return FS_Api + */ + function get_api_bundle_scope() { + return FS_Api::instance( + $this->get_bundle_id(), + 'plugin', + $this->get_bundle_id(), + $this->get_bundle_public_key(), + ! $this->is_live(), + false, + $this->get_sdk_version() + ); + } + + /** + * Get site API scope object (fallback to public plugin scope when not registered). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return FS_Api + */ + function get_api_site_or_plugin_scope() { + return $this->is_registered() ? + $this->get_api_site_scope() : + $this->get_api_plugin_scope(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.2.3.1 + * + * @param object $result + */ + private function maybe_modify_api_curl_error_message( $result ) { + if ( + 'cUrlMissing' !== $result->error->type && + ( 'CurlException' !== $result->error->type || CURLE_COULDNT_CONNECT != $result->error->code ) && + ( 'HttpRequestFailed' !== $result->error->type || false === strpos( $result->error->message, 'cURL error ' . CURLE_COULDNT_CONNECT ) ) + ) { + return; + } + + $result->error->message = $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . + ' ' . + $this->esc_html_inline( + sprintf( + 'Please contact your hosting provider and ask them to whitelist %s for external connection.', + implode( + ', ', + $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com' + ) ) + ) + ), + 'connectivity-whitelist' + ) . + ' ' . + sprintf( + $this->esc_html_inline( 'Once you are done, deactivate the %s and activate it again.', 'connectivity-reactivate-module' ), + $this->get_module_type() + ); + } + + /** + * Show trial promotional notice (if any trial exist). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + */ + function _check_for_trial_plans( $plans ) { + /** + * For some reason core's do_action() flattens arrays when it has a single object item. Therefore, we need to restructure the array as expected. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.2 + */ + if ( ! is_array( $plans ) && is_object( $plans ) ) { + $plans = array( $plans ); + } + + if ( ! $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { + $plans = array(); + } + + $this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans ); + } + + /** + * During trial promotion the "upgrade" submenu item turns to + * "start trial" to encourage the trial. Since we want to keep + * the same menu item handler and there's no robust way to + * add new arguments to the menu item link's querystring, + * use JavaScript to find the menu item and update the href of + * the link. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _fix_start_trial_menu_item_url() { + $template_args = array( 'id' => $this->_module_id ); + fs_require_template( 'add-trial-to-pricing.php', $template_args ); + } + + /** + * Check if module is currently in a trial promotion mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + function is_in_trial_promotion() { + return $this->_admin_notices->has_sticky( 'trial_promotion' ); + } + + /** + * Show trial promotional notice (if any trial exist). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool If trial notice added. + */ + function _add_trial_notice() { + if ( ! $this->is_user_admin() ) { + return false; + } + + if ( ! $this->is_user_in_admin() ) { + return false; + } + + if ( $this->_is_network_active ) { + if ( fs_is_network_admin() ) { + // Network level trial is disabled at the moment. + return false; + } + + if ( ! $this->is_delegated_connection() ) { + // Only delegated sites should support trials. + return false; + } + } + + // Check if trial message is already shown. + if ( $this->is_in_trial_promotion() ) { + add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) ); + + $this->_menu->add_counter_to_menu_item( 1, 'fs-trial' ); + + return false; + } + + if ( $this->is_premium() && ! WP_FS__DEV_MODE ) { + // Don't show trial if running the premium code, unless running in DEV mode. + return false; + } + + if ( ! $this->has_trial_plan() ) { + // No plans with trial. + return false; + } + + if ( ! $this->apply_filters( 'show_trial', true ) ) { + // Developer explicitly asked not to show the trial promo. + return false; + } + + if ( $this->is_registered() ) { + // Check if trial already utilized. + if ( $this->_site->is_trial_utilized() ) { + return false; + } + + if ( $this->is_paying_or_trial() ) { + // Don't show trial if paying or already in trial. + return false; + } + } + + if ( $this->is_activation_mode() || $this->is_pending_activation() ) { + // If not yet opted-in/skipped, or pending activation, don't show trial. + return false; + } + + $last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false ); + $was_promotion_shown_before = ( false !== $last_time_trial_promotion_shown ); + + // Show promotion if never shown before and 24 hours after initial activation with FS. + if ( ! $was_promotion_shown_before && + $this->_storage->install_timestamp > ( time() - $this->apply_filters( 'show_first_trial_after_n_sec', WP_FS__TIME_24_HOURS_IN_SEC ) ) + ) { + return false; + } + + // OR if promotion was shown before, try showing it every 30 days. + if ( $was_promotion_shown_before && + $this->apply_filters( 'reshow_trial_after_every_n_sec', 30 * WP_FS__TIME_24_HOURS_IN_SEC ) > time() - $last_time_trial_promotion_shown + ) { + return false; + } + + $trial_period = $this->_trial_days; + $require_payment = $this->_is_trial_require_payment; + $trial_url = $this->get_trial_url(); + $plans_string = strtolower( $this->get_text_inline( 'Awesome', 'awesome' ) ); + + if ( $this->is_registered() ) { + // If opted-in, override trial with up to date data from API. + $trial_plans = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); + $trial_plans_count = count( $trial_plans ); + + if ( 0 === $trial_plans_count ) { + // If there's no plans with a trial just exit. + return false; + } + + /** + * @var FS_Plugin_Plan $paid_plan + */ + $paid_plan = $trial_plans[0]; + $require_payment = $paid_plan->is_require_subscription; + $trial_period = $paid_plan->trial_period; + + $total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 ); + + if ( $total_paid_plans !== $trial_plans_count ) { + // Not all paid plans have a trial - generate a string of those that have it. + for ( $i = 0; $i < $trial_plans_count; $i ++ ) { + $plans_string .= sprintf( + ' %s', + $trial_url, + $trial_plans[ $i ]->title + ); + + if ( $i < $trial_plans_count - 2 ) { + $plans_string .= ', '; + } else if ( $i == $trial_plans_count - 2 ) { + $plans_string .= ' and '; + } + } + } + } + + $message = sprintf( + $this->get_text_x_inline( 'Hey', 'exclamation', 'hey' ) . '! ' . $this->get_text_inline( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.', 'trial-x-promotion-message' ), + sprintf( '%s', $this->get_plugin_name() ), + $plans_string, + $trial_period + ); + + // "No Credit-Card Required" or "No Commitment for N Days". + $cc_string = $require_payment ? + sprintf( $this->get_text_inline( 'No commitment for %s days - cancel anytime!', 'no-commitment-for-x-days' ), $trial_period ) : + $this->get_text_inline( 'No credit card required', 'no-cc-required' ) . '!'; + + + // Start trial button. + $button = ' ' . sprintf( + '', + $trial_url, + $this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' ) + ); + + $this->_admin_notices->add_sticky( + $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ), + 'trial_promotion', + '', + 'promotion' + ); + + $this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME; + + return true; + } + + /** + * Lets users/customers know that the product has an affiliate program. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2.11 + * + * @return bool Returns true if the notice has been added. + */ + function _add_affiliate_program_notice() { + if ( ! $this->is_user_admin() ) { + return false; + } + + if ( ! $this->is_user_in_admin() ) { + return false; + } + + // Check if the notice is already shown. + if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { + return false; + } + + if ( + // Product has no affiliate program. + ! $this->has_affiliate_program() || + // User has applied for an affiliate account. + ! empty( $this->_storage->affiliate_application_data ) + ) { + return false; + } + + if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) { + // Developer explicitly asked not to show the notice about the affiliate program. + return false; + } + + if ( $this->is_activation_mode() || $this->is_pending_activation() ) { + // If not yet opted in/skipped, or pending activation, don't show the notice. + return false; + } + + $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false ); + $was_notice_shown_before = ( false !== $last_time_notice_was_shown ); + + /** + * Do not show the notice if it was already shown before or less than 30 days have passed since the initial + * activation with FS. + */ + if ( $was_notice_shown_before || + $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) ) + ) { + return false; + } + + if ( ! $this->is_paying() && + FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation + ) { + // If the user is not a customer and the affiliate program is only for customers, don't show the notice. + return false; + } + + $message = sprintf( + $this->get_text_inline( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!', 'become-an-ambassador-admin-notice' ), + sprintf( '%s', $this->get_plugin_name() ), + $this->get_module_label( true ) + ); + + // HTML code for the "Learn more..." button. + $button = ' ' . sprintf( + '', + $this->_get_admin_page_url( 'affiliation' ), + $this->get_text_inline( 'Learn more', 'learn-more' ) . '...' + ); + + $this->_admin_notices->add_sticky( + $this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ), + 'affiliate_program', + '', + 'promotion' + ); + + $this->_storage->affiliate_program_notice_shown = WP_FS__SCRIPT_START_TIME; + + return true; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + */ + function _enqueue_common_css() { + if ( $this->has_paid_plan() && ! $this->is_paying() ) { + // Add basic CSS for admin-notices and menu-item colors. + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + function _show_theme_activation_optin_dialog() { + fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); + + add_action( 'admin_footer', array( &$this, '_add_fs_theme_activation_dialog' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + function _add_fs_theme_activation_dialog() { + global $pagenow; + + if ( 'themes.php' !== $pagenow ) { + return; + } + + $vars = array( 'id' => $this->_module_id ); + fs_require_once_template( 'connect.php', $vars ); + } + + /* Action Links + ------------------------------------------------------------------------------------------------------------------*/ + private $_action_links_hooked = false; + private $_action_links = array(); + + /** + * Hook to plugin action links filter. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + private function hook_plugin_action_links() { + $this->_logger->entrance(); + + $this->_action_links_hooked = true; + + $this->_logger->log( 'Adding action links hooks.' ); + + // Add action link to settings page. + add_filter( 'plugin_action_links_' . $this->_plugin_basename, array( + &$this, + '_modify_plugin_action_links_hook' + ), WP_FS__DEFAULT_PRIORITY, 2 ); + add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array( + &$this, + '_modify_plugin_action_links_hook' + ), WP_FS__DEFAULT_PRIORITY, 2 ); + } + + /** + * Add plugin action link. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param $label + * @param $url + * @param bool $external + * @param int $priority + * @param bool $key + */ + function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) { + $this->_logger->entrance(); + + if ( ! isset( $this->_action_links[ $priority ] ) ) { + $this->_action_links[ $priority ] = array(); + } + + if ( false === $key ) { + $key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) ); + } + + $this->_action_links[ $priority ][] = array( + 'label' => $label, + 'href' => $url, + 'key' => $key, + 'external' => $external + ); + } + + /** + * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + */ + function _add_upgrade_action_link() { + $this->_logger->entrance(); + + $is_activation_mode = $this->is_activation_mode(); + + $add_action_links = $this->should_add_submenu_or_action_links( $is_activation_mode ); + + /** + * The following logic is based on the logic in `add_submenu_items()` method that decides when the "Upgrade" + * and "Add-Ons" menus should be added. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $add_upgrade_link = ( + $add_action_links || + ( $is_activation_mode && $this->is_only_premium() ) + ) && ! WP_FS__DEMO_MODE && ( ! $this->is_whitelabeled() ); + + $add_addons_link = ( $add_action_links && $this->has_addons() ); + + if ( ! $add_upgrade_link && ! $add_addons_link ) { + return; + } + + if ( + $add_upgrade_link && + $this->is_pricing_page_visible() && + $this->is_submenu_item_visible( 'pricing' ) + ) { + $this->add_plugin_action_link( + $this->get_text_inline( 'Upgrade', 'upgrade' ), + $this->get_upgrade_url(), + false, + 7, + 'upgrade' + ); + } + + if ( + $add_addons_link && + $this->has_addons() && + $this->is_submenu_item_visible( 'addons' ) + ) { + $this->add_plugin_action_link( + $this->get_text_inline( 'Add-Ons', 'add-ons' ), + $this->_get_admin_page_url( 'addons' ), + false, + 9, + 'addons' + ); + } + } + + /** + * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection. + * + * @author Leo Fajardo (@leorw) + * @since 1.1.9 + */ + function _add_license_action_link() { + $this->_logger->entrance(); + + if ( ! self::is_ajax() ) { + // Inject license activation dialog UI and client side code. + add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) ); + } + + $link_text = $this->is_free_plan() ? + $this->get_text_inline( 'Activate License', 'activate-license' ) : + $this->get_text_inline( 'Change License', 'change-license' ); + + $this->add_plugin_action_link( + $link_text, + '#', + false, + 11, + ( 'activate-license ' . $this->get_unique_affix() ) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function _add_premium_version_upgrade_selection_action() { + $this->_logger->entrance(); + + if ( ! self::is_ajax() ) { + add_action( 'admin_footer', array( &$this, '_add_premium_version_upgrade_selection_dialog_box' ) ); + } + } + + /** + * Adds "Opt In" or "Opt Out" link to the main "Plugins" page link actions collection. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.1.5 + */ + function _add_tracking_links() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + $this->_logger->entrance(); + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.2 Allow opting out from usage-tracking for paid products too by giving the appropriate warning letting the user know the automatic updates mechanism cannot function without an ongoing connection to the licensing and updates engine. + */ + /*if ( $this->is_premium() ) { + // Don't add opt-in/out for premium code base. + return; + }*/ + + if ( $this->is_only_premium() && $this->is_free_plan() ) { + // Don't add tracking links for premium-only products that were opted-in by relation (add-on or a parent product) before activating any license. + return; + } + + if ( + $this->is_addon() && + ! $this->is_only_premium() && + $this->_parent->is_anonymous() + ) { + return; + } + + if ( fs_is_network_admin() ) { + if ( ! $this->_is_network_active ) { + // Don't add tracking links when browsing the network WP Admin and the plugin is not network active. + return; + } else if ( $this->is_network_delegated_connection() ) { + // Don't add tracking links when browsing the network WP Admin and the activation has been delegated to site admins. + return; + } + } else { + if ( $this->_is_network_active && ! $this->is_delegated_connection() ) { + // Don't add tracking links when browsing the sub-site WP Admin, the plugin is network active, and the connection was not delegated. + return; + } + } + + if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) { + if ( ! $this->is_registered() && $this->is_anonymous() ) { + $this->connect_again(); + + return; + } + } + + if ( ( $this->is_plugin() && ! self::is_plugins_page() ) || + ( $this->is_theme() && ! self::is_themes_page() ) + ) { + // Only show tracking links on the plugins and themes pages. + return; + } + + if ( + $this->is_activation_mode() && + $this->is_premium() && + ! $this->is_registered() + ) { + // If not yet registered and running the premium code base, a license activation link will already be shown. + return; + } + + if ( $this->is_registered() && $this->is_tracking_allowed() ) { + if ( ! $this->is_premium() && ! $this->is_enable_anonymous() ) { + // If opted in and tracking is allowed, don't allow to opt out if not premium and anonymous mode is disabled. + return; + } + } + + if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) { + return; + } + + if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) { + return; + } + + if ( $this->add_ajax_action( 'update_tracking_permission', array( &$this, '_update_tracking_permission_callback' ) ) ) { + return; + } + + $link_text_id = ''; + $url = '#'; + + if ( $this->is_registered() ) { + if ( $this->is_tracking_allowed() ) { + $link_text_id = $this->get_text_inline( 'Opt Out', 'opt-out' ); + } else { + $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); + } + } else if ( $this->is_anonymous() || $this->is_activation_mode() ) { + /** + * Show opt-in link only if skipped or in activation mode. + */ + $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); + + $params = ! $this->is_anonymous() ? + array() : + array( + 'nonce' => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ), + 'fs_action' => ( $this->get_unique_affix() . '_reconnect' ), + ); + + $url = $this->get_activation_url( $params ); + } + + add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) ); + + if ( ! empty( $link_text_id ) && $this->is_plugin() && self::is_plugins_page() ) { + $this->add_plugin_action_link( + $link_text_id, + $url, + false, + 13, + "opt-in-or-opt-out {$this->_slug}" + ); + } + } + + /** + * Get the URL of the page that should be loaded right after the plugin activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.4 + * + * @return string + */ + function get_after_plugin_activation_redirect_url() { + $url = false; + + if ( ! $this->is_addon() || ! $this->has_free_plan() ) { + $first_time_path = $this->_menu->get_first_time_path( + fs_is_network_admin() && $this->_is_network_active + ); + + if ( $this->is_activation_mode() ) { + $url = $this->get_activation_url(); + } else if ( ! empty( $first_time_path ) ) { + $url = $first_time_path; + } else { + $page = ''; + if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { + if ( $this->is_network_registered() ) { + $page = 'account'; + } else if ( $this->is_pending_activation() || $this->is_network_anonymous() ) { + $this->maybe_set_slug_and_network_menu_exists_flag(); + } + } + + $url = $this->_get_admin_page_url( $page ); + } + } else { + $plugin_fs = false; + + if ( $this->is_parent_plugin_installed() ) { + $plugin_fs = self::get_parent_instance(); + } + + if ( is_object( $plugin_fs ) ) { + if ( ! $plugin_fs->is_registered() ) { + // Forward to parent plugin connect when parent not registered. + $url = $plugin_fs->get_activation_url(); + } else { + // Forward to account page. + $url = $plugin_fs->_get_admin_page_url( 'account' ); + } + } + } + + return $url; + } + + /** + * Forward page to activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function _redirect_on_activation_hook() { + if ( $this->apply_filters( 'redirect_on_activation', true ) ) { + $url = $this->get_after_plugin_activation_redirect_url(); + + if ( is_string( $url ) ) { + fs_redirect( $url ); + } + } + } + + /** + * Modify plugin's page action links collection. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.0 + * + * @param array $links + * @param $file + * + * @return array + */ + function _modify_plugin_action_links_hook( $links, $file ) { + $this->_logger->entrance(); + + $passed_deactivate = false; + $deactivate_link = ''; + $before_deactivate = array(); + $after_deactivate = array(); + foreach ( $links as $key => $link ) { + if ( 'deactivate' === $key ) { + $deactivate_link = $link; + $passed_deactivate = true; + continue; + } + + if ( ! $passed_deactivate ) { + $before_deactivate[ $key ] = $link; + } else { + $after_deactivate[ $key ] = $link; + } + } + + ksort( $this->_action_links ); + + foreach ( $this->_action_links as $new_links ) { + foreach ( $new_links as $link ) { + $before_deactivate[ $link['key'] ] = '' . $link['label'] . ''; + } + } + + if ( ! empty( $deactivate_link ) ) { + /** + * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link. + * + * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation. + */ + $deactivate_link .= ''; + + // Append deactivation link. + $before_deactivate['deactivate'] = $deactivate_link; + } + + return array_merge( $before_deactivate, $after_deactivate ); + } + + /** + * Adds admin message. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + */ + function add_admin_message( $message, $title = '', $type = 'success' ) { + $this->_admin_notices->add( $message, $title, $type ); + } + + /** + * Adds sticky admin message. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $message + * @param string $id + * @param string $title + * @param string $type + */ + function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' ) { + $this->_admin_notices->add_sticky( $message, $id, $title, $type ); + } + + /** + * Check if the paid version of the module is installed. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.0 + * + * @return bool + */ + private function is_premium_version_installed() { + $premium_plugin_basename = $this->premium_plugin_basename(); + $premium_plugin = get_plugins( '/' . dirname( $premium_plugin_basename ) ); + + return ! empty( $premium_plugin ); + } + + /** + * Helper function that returns the final steps for the upgrade completion. + * + * If the module is already running the premium code, returns an empty string. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $plan_title + * + * @return string + */ + private function get_complete_upgrade_instructions( $plan_title = '' ) { + $this->_logger->entrance(); + + $activate_license_string = $this->get_license_network_activation_notice(); + + if ( ! $this->has_premium_version() || $this->is_premium() ) { + return '' . $activate_license_string; + } + + if ( empty( $plan_title ) ) { + $plan_title = $this->get_plan_title(); + } + + if ( $this->is_premium_version_installed() ) { + /** + * If the premium version is already installed, instead of showing the installation instructions, + * tell the current user to activate it. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $premium_plugin_basename = $this->premium_plugin_basename(); + + return sprintf( + /* translators: %1$s: Product title; %2$s: Plan title */ + $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ), + sprintf( '%s', esc_html( $this->get_plugin_title() ) ), + $plan_title, + sprintf( + '', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_plugin_basename, 'activate-plugin_' . $premium_plugin_basename ), + esc_html( sprintf( + /* translators: %s: Plan title */ + $this->get_text_inline( 'Activate %s features', 'activate-x-features' ), + $plan_title + ) ) + ) + ); + } else { + // @since 1.2.1.5 The free version is auto deactivated. + $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ? + ( '
  • ' . $this->esc_html_inline( 'Deactivate the free version', 'deactivate-free-version' ) . '.
  • ' ) : + ''; + + return sprintf( + ' %s:
    1. %s.
    2. %s
    3. %s (%s).
    ', + $this->get_text_inline( 'Please follow these steps to complete the upgrade', 'follow-steps-to-complete-upgrade' ), + ( empty( $activate_license_string ) ? '' : $activate_license_string . '
  • ' ) . + $this->get_latest_download_link( sprintf( + /* translators: %s: Plan title */ + $this->get_text_inline( 'Download the latest %s version', 'download-latest-x-version' ), + $plan_title + ) ), + $deactivation_step, + $this->get_text_inline( 'Upload and activate the downloaded version', 'upload-and-activate' ), + $this->apply_filters( 'upload_and_install_video_url', '//bit.ly/upload-wp-' . $this->_module_type . 's' ), + $this->get_text_inline( 'How to upload and activate?', 'howto-upload-activate' ) + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $url + * @param array $request + */ + private static function enrich_request_for_debug( &$url, &$request ) { + if ( WP_FS__DEBUG_SDK || isset( $_COOKIE['XDEBUG_SESSION'] ) ) { + $url = add_query_arg( 'XDEBUG_SESSION_START', rand( 0, 9999999 ), $url ); + $url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url ); + + $request['cookies'] = array( + new WP_Http_Cookie( array( + 'name' => 'XDEBUG_SESSION', + 'value' => 'PHPSTORM', + ) ) + ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $url + * @param array $request + * @param int $success_cache_expiration + * @param int $failure_cache_expiration + * @param bool $maybe_enrich_request_for_debug + * + * @return WP_Error|array + */ + static function safe_remote_post( + &$url, + $request, + $success_cache_expiration = 0, + $failure_cache_expiration = 0, + $maybe_enrich_request_for_debug = true + ) { + $should_cache = ($success_cache_expiration + $failure_cache_expiration > 0); + + $cache_key = $should_cache ? md5( fs_strip_url_protocol($url) . json_encode( $request ) ) : false; + + $response = (!WP_FS__DEBUG_SDK && ( false !== $cache_key )) ? + get_transient( $cache_key ) : + false; + + if ( false === $response ) { + if ( $maybe_enrich_request_for_debug ) { + self::enrich_request_for_debug( $url, $request ); + } + + $response = wp_remote_post( $url, $request ); + + if ( $response instanceof WP_Error ) { + if ( 'https://' === substr( $url, 0, 8 ) && + isset( $response->errors ) && + isset( $response->errors['http_request_failed'] ) + ) { + $http_error = strtolower( $response->errors['http_request_failed'][0] ); + + if ( false !== strpos( $http_error, 'ssl' ) || + false !== strpos( $http_error, 'curl error 35' ) + ) { + // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). + $url = 'http://' . substr( $url, 8 ); + + $request['timeout'] = 15; + $response = wp_remote_post( $url, $request ); + } + } + } + + if ( false !== $cache_key ) { + set_transient( + $cache_key, + $response, + ( ( $response instanceof WP_Error ) ? + $failure_cache_expiration : + $success_cache_expiration ) + ); + } + } + + return $response; + } + + /** + * This method is used to enrich the after upgrade notice instructions when the upgraded + * license cannot be activated network wide (license quota isn't large enough). + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_license_network_activation_notice() { + if ( ! $this->_is_network_active ) { + // Module isn't network level activated. + return ''; + } + + if ( ! fs_is_network_admin() ) { + // Not network level admin. + return ''; + } + + if ( get_blog_count() == 1 ) { + // There's only a single site in the network so if there's a context license it was already activated. + return ''; + } + + if ( ! is_object( $this->_license ) ) { + // No context license. + return ''; + } + + if ( $this->_license->is_single_site() && 0 < $this->_license->activated ) { + // License was already utilized (this is not 100% the case if all the network is localhost sites and the license can be utilized on unlimited localhost sites). + return ''; + } + + if ( $this->can_activate_license_on_network( $this->_license ) ) { + // License can be activated on all the network, so probably, the license is already activate on all the network (that's how the after upgrade sync works). + return ''; + } + + return sprintf( + $this->get_text_inline( '%sClick here%s to choose the sites where you\'d like to activate the license on.', 'network-choose-sites-for-license' ), + '', + '' + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * + * @return string + */ + function get_text( $key ) { + return fs_text( $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * + * @return string + */ + function get_text_inline( $text, $key = '' ) { + return _fs_text_inline( $text, $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * + * @return string + */ + function get_text_x_inline( $text, $context, $key ) { + return _fs_text_x_inline( $text, $context, $key, $this->_slug ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * + * @return string + */ + function esc_html_inline( $text, $key ) { + return esc_html( _fs_text_inline( $text, $key, $this->_slug ) ); + } + + #---------------------------------------------------------------------------------- + #region Versioning + #---------------------------------------------------------------------------------- + + /** + * Check if Freemius in SDK upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_sdk_upgrade_mode() { + return isset( $this->_storage->sdk_upgrade_mode ) ? + $this->_storage->sdk_upgrade_mode : + false; + } + + /** + * Turn SDK upgrade mode off. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function set_sdk_upgrade_complete() { + $this->_storage->sdk_upgrade_mode = false; + } + + /** + * Check if plugin upgrade mode. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_plugin_upgrade_mode() { + return isset( $this->_storage->plugin_upgrade_mode ) ? + $this->_storage->plugin_upgrade_mode : + false; + } + + /** + * Turn plugin upgrade mode off. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function set_plugin_upgrade_complete() { + $this->_storage->plugin_upgrade_mode = false; + + $license_migration = ! empty( $this->_storage->license_migration ) ? + $this->_storage->license_migration : + array(); + + $license_migration['is_migrating'] = false; + + $this->_storage->license_migration = $license_migration; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Permissions + #---------------------------------------------------------------------------------- + + /** + * Check if specific permission requested. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $permission + * + * @return bool + */ + function is_permission_requested( $permission ) { + return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Auto Activation + #---------------------------------------------------------------------------------- + + /** + * Hints the SDK if running an auto-installation. + * + * @var bool + */ + private $_isAutoInstall = false; + + /** + * After upgrade callback to install and auto activate a plugin. + * This code will only be executed on explicit request from the user, + * following the practice Jetpack are using with their theme installations. + * + * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + function _install_premium_version_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'install_premium_version' ); + + if ( ! $this->is_registered() ) { + // Not registered. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Auto installation only works for opted-in users.', 'auto-install-error-not-opted-in' ), + 'code' => 'premium_installed', + ) ); + } + + $plugin_id = fs_request_get( 'target_module_id', $this->get_id() ); + + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid ID. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ) ); + } + + if ( $plugin_id == $this->get_id() ) { + if ( $this->is_premium() ) { + // Already using the premium code version. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), + 'code' => 'premium_installed', + ) ); + } + if ( ! $this->can_use_premium_code() ) { + // Don't have access to the premium code. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'You do not have a valid license to access the premium version.', 'auto-install-error-invalid-license' ), + 'code' => 'invalid_license', + ) ); + } + if ( ! $this->has_release_on_freemius() ) { + // Plugin is a serviceware, no premium code version. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Plugin is a "Serviceware" which means it does not have a premium code version.', 'auto-install-error-serviceware' ), + 'code' => 'premium_version_missing', + ) ); + } + } else { + $addon = $this->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ) ); + } + + if ( $this->is_addon_activated( $plugin_id, true ) ) { + // Premium add-on version is already activated. + self::shoot_ajax_failure( array( + 'message' => $this->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ), + 'code' => 'premium_installed', + ) ); + } + } + + $this->_isAutoInstall = true; + + // Try to install and activate. + $updater = FS_Plugin_Updater::instance( $this ); + $result = $updater->install_and_activate_plugin( $plugin_id ); + + if ( is_array( $result ) && ! empty( $result['message'] ) ) { + self::shoot_ajax_failure( array( + 'message' => $result['message'], + 'code' => $result['code'], + ) ); + } + + self::shoot_ajax_success( $result ); + } + + /** + * Displays module activation dialog box after a successful upgrade + * where the user explicitly requested to auto download and install + * the premium version. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + function _add_auto_installation_dialog_box() { + $this->_logger->entrance(); + + if ( ! $this->is_registered() ) { + // Not registered. + return; + } + + $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); + + if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid module ID. + return; + } + + if ( $plugin_id == $this->get_id() ) { + if ( $this->is_premium() ) { + // Already using the premium code version. + return; + } + if ( ! $this->can_use_premium_code() ) { + // Don't have access to the premium code. + return; + } + if ( ! $this->has_release_on_freemius() ) { + // Plugin is a serviceware, no premium code version. + return; + } + } else { + $addon = $this->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + return; + } + + if ( $this->is_addon_activated( $plugin_id, true ) ) { + // Premium add-on version is already activated. + return; + } + } + + $vars = array( + 'id' => $this->_module_id, + 'target_module_id' => $plugin_id, + 'slug' => $this->_slug, + ); + + fs_require_template( 'auto-installation.php', $vars ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Tabs Integration + #-------------------------------------------------------------------------------- + + #region Module's Original Tabs + + /** + * Inject a JavaScript logic to capture the theme tabs HTML. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _tabs_capture() { + $this->_logger->entrance(); + + if ( ! $this->is_product_settings_page() || + ! $this->is_matching_url( $this->main_menu_url() ) + ) { + return; + } + + $params = array( + 'id' => $this->_module_id, + ); + + fs_require_once_template( 'tabs-capture-js.php', $params ); + } + + /** + * Cache theme's tabs HTML for a week. The cache will also be set as expired + * after version and type (free/premium) changes, in addition to the week period. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _store_tabs_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'store_tabs' ); + + // Init filesystem if not yet initiated. + WP_Filesystem(); + + // Get POST body HTML data. + global $wp_filesystem; + $tabs_html = $wp_filesystem->get_contents( "php://input" ); + + if ( is_string( $tabs_html ) ) { + $tabs_html = trim( $tabs_html ); + } + + if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) { + self::shoot_ajax_failure(); + } + + $this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); + + self::shoot_ajax_success(); + } + + /** + * Cache theme's settings page custom styles. The cache will also be set as expired + * after version and type (free/premium) changes, in addition to the week period. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _store_tabs_styles() { + $this->_logger->entrance(); + + if ( ! $this->is_product_settings_page() || + ! $this->is_matching_url( $this->main_menu_url() ) + ) { + return; + } + + $wp_styles = wp_styles(); + + $theme_styles_url = get_template_directory_uri(); + + $stylesheets = array(); + foreach ( $wp_styles->queue as $handler ) { + if ( fs_starts_with( $handler, 'fs_' ) ) { + // Assume that stylesheets that their handler starts with "fs_" belong to the SDK. + continue; + } + + /** + * @var _WP_Dependency $stylesheet + */ + $stylesheet = $wp_styles->registered[ $handler ]; + + if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) { + $stylesheets[] = $stylesheet->src; + } + } + + if ( ! empty( $stylesheets ) ) { + $this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + } + + /** + * Check if module's original settings page has any tabs. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + private function has_tabs() { + return $this->_cache->has( 'tabs' ); + } + + /** + * Get module's settings page HTML content, starting + * from the beginning of the
    element, + * until the tabs HTML (including). + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + private function get_tabs_html() { + $this->_logger->entrance(); + + return $this->_cache->get( 'tabs' ); + } + + /** + * Check if page should include tabs. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool + */ + private function should_page_include_tabs() { + if ( ! $this->has_settings_menu() ) { + // Don't add tabs if no settings at all. + return false; + } + + if ( self::NAVIGATION_TABS !== $this->_navigation ) { + // Only add tabs to themes for now. + return false; + } + + if ( $this->is_theme() && ! $this->has_paid_plan() && ! $this->has_addons() ) { + // Only add tabs to monetizing themes. + return false; + } + + if ( ! $this->is_product_settings_page() ) { + // Only add tabs if browsing one of the product's setting pages. + return false; + } + + if ( $this->is_activation_mode() && $this->is_activation_page() ) { + // Don't include tabs in the activation page. + return false; + } + + if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) { + // Don't add tabs on checkout page, we want to reduce distractions + // as much as possible. + return false; + } + + return true; + } + + /** + * Add the tabs HTML before the setting's page content and + * enqueue any required stylesheets. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool If tabs were included. + */ + function _add_tabs_before_content() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return false; + } + + /** + * Enqueue the original stylesheets that are included in the + * theme settings page. That way, if the theme settings has + * some custom _styled_ content above the tabs UI, this + * will make sure that the styling is preserved. + */ + $stylesheets = $this->_cache->get( 'tabs_stylesheets', array() ); + if ( is_array( $stylesheets ) ) { + for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) { + wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] ); + } + } + + // Cut closing
    tag. + echo substr( trim( $this->get_tabs_html() ), 0, - 6 ); + + return true; + } + + /** + * Add the tabs closing HTML after the setting's page content. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return bool If tabs closing HTML was included. + */ + function _add_tabs_after_content() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return false; + } + + echo '
  • '; + + return true; + } + + #endregion + + /** + * Add in-page JavaScript to inject the Freemius tabs into + * the module's setting tabs section. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _add_freemius_tabs() { + $this->_logger->entrance(); + + if ( ! $this->should_page_include_tabs() ) { + return; + } + + $params = array( 'id' => $this->_module_id ); + fs_require_once_template( 'tabs.php', $params ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Customizer Integration for Themes + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param WP_Customize_Manager $customizer + */ + function _customizer_register( $customizer ) { + $this->_logger->entrance(); + + if ( $this->is_pricing_page_visible() ) { + require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php'; + + $customizer->add_section( 'freemius_upsell', array( + 'title' => '★ ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ), + 'priority' => 1, + ) ); + $customizer->add_setting( 'freemius_upsell', array( + 'sanitize_callback' => 'esc_html', + ) ); + + $customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array( + 'fs' => $this, + 'section' => 'freemius_upsell', + 'priority' => 100, + ) ) ); + } + + if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) { + require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php'; + + // Main Documentation Link In Customizer Root. + $customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array( + 'fs' => $this, + 'priority' => 1000, + ) ) ); + } + } + + #endregion + + /** + * If the theme has a paid version, add some custom + * styling to the theme's premium version (if exists) + * to highlight that it's the premium version of the + * same theme, making it easier for identification + * after the user upgrades and upload it to the site. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + function _style_premium_theme() { + $this->_logger->entrance(); + + if ( ! self::is_themes_page() ) { + // Only include in the themes page. + return; + } + + if ( ! $this->has_paid_plan() ) { + // Only include if has any paid plans. + return; + } + + $params = null; + fs_require_once_template( '/js/jquery.content-change.php', $params ); + + $params = array( + 'slug' => $this->_slug, + 'id' => $this->_module_id, + ); + + fs_require_template( '/js/style-premium-theme.php', $params ); + } + + /** + * This method will return the absolute URL of the module's local icon. + * + * When you are running your plugin or theme on a **localhost** environment, if the icon + * is not found in the local assets folder, try to fetch the icon URL from Freemius. If not set and + * it's a plugin hosted on WordPress.org, try fetching the icon URL from wordpress.org. + * If an icon is found, this method will automatically attempt to download the icon and store it + * in /freemius/assets/img/{slug}.{png|jpg|gif|svg}. + * + * It's important to mention that this method is NOT phoning home since the developer will deploy + * the product with the local icon in the assets folder. The download process just simplifies + * the process for the developer. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_local_icon_url() { + global $fs_active_plugins; + + /** + * @since 1.1.7.5 + */ + $local_path = $this->apply_filters( 'plugin_icon', false ); + + if ( is_string( $local_path ) ) { + $icons = array( $local_path ); + } else { + $img_dir = WP_FS__DIR_IMG; + + // Locate the main assets folder. + if ( 1 < count( $fs_active_plugins->plugins ) ) { + $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); + + foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) { + if ( $data->plugin_path == $this->get_plugin_basename() ) { + $img_dir = $plugin_or_theme_img_dir + . '/' + /** + * The basename will be `themes` or the basename of a custom themes directory. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + . str_replace( '../' . basename( $plugin_or_theme_img_dir ) . '/', '', $sdk_path ) + . '/assets/img'; + + break; + } + } + } + + // Try to locate the icon in the assets folder. + $icons = glob( fs_normalize_path( $img_dir . "/{$this->_slug}.*" ) ); + + if ( ! is_array( $icons ) || 0 === count( $icons ) ) { + if ( ! WP_FS__IS_LOCALHOST && $this->is_theme() ) { + $icons = array( + fs_normalize_path( $img_dir . '/theme-icon.png' ) + ); + } else { + $icon_found = false; + $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.png" ); + + if ( ! function_exists( 'get_filesystem_method' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + $have_write_permissions = ( 'direct' === get_filesystem_method( array(), fs_normalize_path( $img_dir ) ) ); + + /** + * IMPORTANT: THIS CODE WILL NEVER RUN AFTER THE PLUGIN IS IN THE REPO. + * + * This code will only be executed once during the testing + * of the plugin in a local environment. The plugin icon file WILL + * already exist in the assets folder when the plugin is deployed to + * the repository. + */ + if ( WP_FS__IS_LOCALHOST && $have_write_permissions ) { + // Fetch icon from Freemius. + $icon = $this->fetch_remote_icon_url(); + + // Fetch icon from WordPress.org. + if ( empty( $icon ) && $this->is_plugin() && $this->is_org_repo_compliant() ) { + if ( ! function_exists( 'plugins_api' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + } + + $plugin_information = plugins_api( 'plugin_information', array( + 'slug' => $this->_slug, + 'fields' => array( + 'sections' => false, + 'tags' => false, + 'icons' => true + ) + ) ); + + if ( + ! is_wp_error( $plugin_information ) + && isset( $plugin_information->icons ) + && ! empty( $plugin_information->icons ) + ) { + /** + * Get the smallest icon. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $icon = end( $plugin_information->icons ); + } + } + + if ( ! empty( $icon ) ) { + if ( 0 !== strpos( $icon, 'http' ) ) { + $icon = 'http:' . $icon; + } + + /** + * Get a clean file extension, e.g.: "jpg" and not "jpg?rev=1305765". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $ext = pathinfo( strtok( $icon, '?' ), PATHINFO_EXTENSION ); + + $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.{$ext}" ); + + // Try to download the icon. + $icon_found = fs_download_image( $icon, $local_path ); + } + } + + if ( ! $icon_found ) { + // No icons found, fallback to default icon. + if ( $have_write_permissions ) { + // If have write permissions, copy default icon. + copy( fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ), $local_path ); + } else { + // If doesn't have write permissions, use default icon path. + $local_path = fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ); + } + } + + $icons = array( $local_path ); + } + } + } + + $icon_dir = dirname( $icons[0] ); + + return fs_img_url( substr( $icons[0], strlen( $icon_dir ) ), $icon_dir ); + } + + /** + * Fetch module's extended info. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return object|mixed + */ + private function fetch_module_info() { + return $this->get_api_plugin_scope()->get( 'info.json', false, WP_FS__TIME_WEEK_IN_SEC ); + } + + /** + * Fetch module's remote icon URL. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function fetch_remote_icon_url() { + $info = $this->fetch_module_info(); + + return ( $this->is_api_result_object( $info, 'icon' ) && is_string( $info->icon ) ) ? + $info->icon : + ''; + } + + #-------------------------------------------------------------------------------- + #region GDPR + #-------------------------------------------------------------------------------- + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @return bool + */ + function fetch_and_store_current_user_gdpr_anonymously() { + $pong = $this->ping( null, true ); + + if ( ! $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) { + return false; + } else { + FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + + return $pong->is_gdpr_required; + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param array $user_plugins + * + * @return string + */ + private function get_gdpr_admin_notice_string( $user_plugins ) { + $this->_logger->entrance(); + + $addons = self::get_all_addons(); + + foreach ( $user_plugins as $user_plugin ) { + $has_addons = isset( $addons[ $user_plugin->id ] ); + + if ( WP_FS__MODULE_TYPE_PLUGIN === $user_plugin->type && ! $has_addons ) { + if ( $this->_module_id == $user_plugin->id ) { + $addons = $this->get_addons(); + $has_addons = ( ! empty( $addons ) ); + } else { + $plugin_api = FS_Api::instance( + $user_plugin->id, + 'plugin', + $user_plugin->id, + $user_plugin->public_key, + ! $user_plugin->is_live, + false, + $this->get_sdk_version() + ); + + $addons_result = $plugin_api->get( '/addons.json?enriched=true', true ); + + if ( $this->is_api_result_object( $addons_result, 'plugins' ) && + is_array( $addons_result->plugins ) && + ! empty( $addons_result->plugins ) + ) { + $has_addons = true; + } + } + } + + $user_plugin->has_addons = $has_addons; + } + + $is_single_parent_product = ( 1 === count( $user_plugins ) ); + + $multiple_products_text = ''; + + if ( $is_single_parent_product ) { + $single_parent_product = reset( $user_plugins ); + + $thank_you = sprintf( + "%s", + $single_parent_product->id, + sprintf( + $single_parent_product->has_addons ? + $this->get_text_inline( 'Thank you so much for using %s and its add-ons!', 'thank-you-for-using-product-and-its-addons' ) : + $this->get_text_inline( 'Thank you so much for using %s!', 'thank-you-for-using-product' ), + sprintf('%s', $single_parent_product->title) + ) + ); + + $already_opted_in = sprintf( + $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving the %s.", 'already-opted-in-to-product-usage-tracking' ), + ( WP_FS__MODULE_TYPE_THEME === $single_parent_product->type ) ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN + ); + } else { + $thank_you = $this->get_text_inline( 'Thank you so much for using our products!', 'thank-you-for-using-products' ); + $already_opted_in = $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving them.", 'already-opted-in-to-products-usage-tracking' ); + + $products_and_add_ons = ''; + foreach ( $user_plugins as $user_plugin ) { + if ( ! empty( $products_and_add_ons ) ) { + $products_and_add_ons .= ', '; + } + + if ( ! $user_plugin->has_addons ) { + $products_and_add_ons .= sprintf( + "%s", + $user_plugin->id, + $user_plugin->title + ); + } else { + $products_and_add_ons .= sprintf( + "%s", + $user_plugin->id, + sprintf( + $this->get_text_inline( '%s and its add-ons', 'product-and-its-addons' ), + $user_plugin->title + ) + ); + } + } + + $multiple_products_text = sprintf( + "%s: %s", + $this->get_text_inline( 'Products', 'products' ), + $products_and_add_ons + ); + } + + $actions = sprintf( + '
    • %s - %s
    • %s - %s
    ', + sprintf('', $this->get_text_inline( 'Yes', 'yes' ) ), + $this->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ), + sprintf('', $this->get_text_inline( 'No', 'no' ) ), + sprintf( + $this->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), + '', + '' + ) + ); + + return sprintf( + '%s %s %s', + $thank_you, + $already_opted_in, + sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . + '

    ' . + '' . $this->get_text_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) . '' . + $actions . + ( $is_single_parent_product ? '' : $multiple_products_text ) + ); + } + + /** + * This method is called for opted-in users to fetch the is_marketing_allowed flag of the user for all the + * plugins and themes they've opted in to. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param string $user_email + * @param string $license_key + * @param array $plugin_ids + * @param string|null $license_key + * + * @return array|false + */ + private function fetch_user_marketing_flag_status_by_plugins( $user_email, $license_key, $plugin_ids ) { + $request = array( + 'method' => 'POST', + 'body' => array(), + 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, + ); + + if ( is_string( $user_email ) ) { + $request['body']['email'] = $user_email; + } else { + $request['body']['license_key'] = $license_key; + } + + $result = array(); + + $url = WP_FS__ADDRESS . '/action/service/user_plugin/'; + $total_plugin_ids = count( $plugin_ids ); + + $plugin_ids_count_per_request = 10; + for ( $i = 1; $i <= $total_plugin_ids; $i += $plugin_ids_count_per_request ) { + $plugin_ids_set = array_slice( $plugin_ids, $i - 1, $plugin_ids_count_per_request ); + + $request['body']['plugin_ids'] = $plugin_ids_set; + + $response = self::safe_remote_post( + $url, + $request, + WP_FS__TIME_24_HOURS_IN_SEC, + WP_FS__TIME_12_HOURS_IN_SEC + ); + + if ( ! is_wp_error( $response ) ) { + $decoded = is_string( $response['body'] ) ? + json_decode( $response['body'] ) : + null; + + if ( + !is_object($decoded) || + !isset($decoded->success) || + true !== $decoded->success || + !isset( $decoded->data ) || + !is_array( $decoded->data ) + ) { + return false; + } + + $result = array_merge( $result, $decoded->data ); + } + } + + return $result; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _maybe_show_gdpr_admin_notice() { + if ( ! $this->is_user_in_admin() ) { + return; + } + + if ( ! $this->should_handle_gdpr_admin_notice() ) { + return; + } + + if ( ! $this->is_user_admin() ) { + return; + } + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + $lock = FS_User_Lock::instance(); + + /** + * Try to acquire a 60-sec lock based on the WP user and thread/process ID. + */ + if ( ! $lock->try_lock( 60 ) ) { + return; + } + + /** + * @var $current_wp_user WP_User + */ + $current_wp_user = self::_get_current_wp_user(); + + /** + * @var FS_User $current_fs_user + */ + $current_fs_user = Freemius::_get_user_by_email( $current_wp_user->user_email ); + + $ten_years_in_sec = 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC; + + if ( ! is_object( $current_fs_user ) ) { + // 10-year lock. + $lock->lock( $ten_years_in_sec ); + + return; + } + + $gdpr = FS_GDPR_Manager::instance(); + + if ( $gdpr->is_opt_in_notice_shown() ) { + // 30-day lock. + $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); + + return; + } + + if ( ! $gdpr->should_show_opt_in_notice() ) { + // 10-year lock. + $lock->lock( $ten_years_in_sec ); + + return; + } + + $last_time_notice_shown = $gdpr->last_time_notice_was_shown(); + $was_notice_shown_before = ( false !== $last_time_notice_shown ); + + if ( $was_notice_shown_before && + 30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_notice_shown + ) { + // If the notice was shown before, show it again after 30 days from the last time it was shown. + return; + } + + /** + * Find all plugin IDs that were installed by the current admin. + */ + $plugin_ids_map = self::get_user_opted_in_module_ids_map( $current_fs_user->id ); + + if ( empty( $plugin_ids_map )) { + $lock->lock( $ten_years_in_sec ); + + return; + } + + $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( + $current_fs_user->email, + null, + array_keys( $plugin_ids_map ) + ); + + if ( empty( $user_plugins ) ) { + $lock->lock( + is_array($user_plugins) ? + $ten_years_in_sec : + // Lock for 24-hours on errors. + WP_FS__TIME_24_HOURS_IN_SEC + ); + + return; + } + + $has_unset_marketing_optin = false; + + foreach ( $user_plugins as $user_plugin ) { + if ( true == $user_plugin->is_marketing_allowed ) { + unset( $plugin_ids_map[ $user_plugin->plugin_id ] ); + } + + if ( ! $has_unset_marketing_optin && is_null( $user_plugin->is_marketing_allowed ) ) { + $has_unset_marketing_optin = true; + } + } + + if ( empty( $plugin_ids_map ) || + ( $was_notice_shown_before && ! $has_unset_marketing_optin ) + ) { + $lock->lock( $ten_years_in_sec ); + + return; + } + + $modules = array_merge( + array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), + array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) + ); + + foreach ( $modules as $module ) { + if ( ! FS_Plugin::is_valid_id( $module->parent_plugin_id ) && isset( $plugin_ids_map[ $module->id ] ) ) { + $plugin_ids_map[ $module->id ] = $module; + } + } + + $plugin_title = null; + if ( 1 === count( $plugin_ids_map ) ) { + $module = reset( $plugin_ids_map ); + $plugin_title = $module->title; + } + + $gdpr->add_opt_in_sticky_notice( + $this->get_gdpr_admin_notice_string( $plugin_ids_map ), + $plugin_title + ); + + $this->add_gdpr_optin_ajax_handler_and_style(); + + $gdpr->notice_was_just_shown(); + + // 30-day lock. + $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + + /** + * Prevents the GDPR opt-in admin notice from being added if the user has already chosen to allow or not allow + * marketing. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + private function disable_opt_in_notice_and_lock_user() { + FS_GDPR_Manager::instance()->disable_opt_in_notice(); + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + // 10-year lock. + FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _add_gdpr_optin_js() { + $vars = array( 'id' => $this->_module_id ); + + fs_require_once_template( 'gdpr-optin-js.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function enqueue_gdpr_optin_notice_style() { + fs_enqueue_local_style( 'fs_gdpr_optin_notice', '/admin/gdpr-optin-notice.css' ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _maybe_add_gdpr_optin_ajax_handler() { + $this->add_ajax_action( 'fetch_is_marketing_required_flag_value', array( &$this, '_fetch_is_marketing_required_flag_value_ajax_action' ) ); + + if ( FS_GDPR_Manager::instance()->is_opt_in_notice_shown() ) { + $this->add_gdpr_optin_ajax_handler_and_style(); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _fetch_is_marketing_required_flag_value_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); + + $license_key = fs_request_get( 'license_key' ); + + if ( empty($license_key) ) { + self::shoot_ajax_failure( $this->get_text_inline( 'License key is empty.', 'empty-license-key' ) ); + } + + $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( + null, + $license_key, + array( $this->_module_id ) + ); + + if ( ! is_array( $user_plugins ) || + empty($user_plugins) || + !isset($user_plugins[0]->plugin_id) || + $user_plugins[0]->plugin_id != $this->_module_id + ) { + /** + * If faced an error or if the module ID do not match to the current module, ask for GDPR opt-in. + * + * @author Vova Feldman (@svovaf) + */ + self::shoot_ajax_success( array( + 'is_marketing_allowed' => null, + 'license_owner_id' => null + ) ); + } + + self::shoot_ajax_success( array( + 'is_marketing_allowed' => $user_plugins[0]->is_marketing_allowed, + 'license_owner_id' => ( isset( $user_plugins[0]->license_owner_id ) ? $user_plugins[0]->license_owner_id : null ) + ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.2 + * + * @param number[] $install_ids + * + * @return array { + * An array of objects containing the installs' licenses owners data. + * + * @property number $id User ID. + * @property string $email User email (can be masked email). + * } + */ + private function fetch_installs_licenses_owners_data( $install_ids ) { + $this->_logger->entrance(); + + $response = $this->get_api_user_scope()->get( + '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) + ); + + $license_owners = null; + + if ( $this->is_api_result_object( $response, 'owners' ) ) { + $license_owners = $response->owners; + } + + return $license_owners; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + private function add_gdpr_optin_ajax_handler_and_style() { + // Add GDPR action AJAX callback. + $this->add_ajax_action( 'gdpr_optin_action', array( &$this, '_gdpr_optin_ajax_action' ) ); + + add_action( 'admin_footer', array( &$this, '_add_gdpr_optin_js' ) ); + add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_gdpr_optin_notice_style' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + function _gdpr_optin_ajax_action() { + $this->_logger->entrance(); + + $this->check_ajax_referer( 'gdpr_optin_action' ); + + if ( ! fs_request_has( 'is_marketing_allowed' ) || ! fs_request_has( 'plugin_ids' ) ) { + self::shoot_ajax_failure(); + } + + $current_wp_user = self::_get_current_wp_user(); + + $plugin_ids = fs_request_get( 'plugin_ids', array() ); + if ( ! is_array( $plugin_ids ) || empty( $plugin_ids ) ) { + self::shoot_ajax_failure(); + } + + $modules = array_merge( + array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), + array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) + ); + + foreach ( $modules as $key => $module ) { + if ( ! in_array( $module->id, $plugin_ids ) ) { + unset( $modules[ $key ] ); + } + } + + if ( empty( $modules ) ) { + self::shoot_ajax_failure(); + } + + $user_api = $this->get_api_user_scope_by_user( Freemius::_get_user_by_email( $current_wp_user->user_email ) ); + + foreach ( $modules as $module ) { + $user_api->call( "?plugin_id={$module->id}", 'put', array( + 'is_marketing_allowed' => ( true == fs_request_get_bool( 'is_marketing_allowed' ) ) + ) ); + } + + FS_GDPR_Manager::instance()->remove_opt_in_notice(); + + require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; + + // 10-year lock. + FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); + + self::shoot_ajax_success(); + } + + /** + * Checks if the GDPR admin notice should be handled. By default, this logic is off, unless the integrator adds the special 'handle_gdpr_admin_notice' filter. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + private function should_handle_gdpr_admin_notice() { + return $this->apply_filters( + 'handle_gdpr_admin_notice', + // Default to false. + false + ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Marketing + #---------------------------------------------------------------------------------- + + /** + * Check if current user purchased any other plugins before. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_purchased_before() { + // TODO: Implement has_purchased_before() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as an agency. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_agency() { + // TODO: Implement is_agency() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as a developer. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_developer() { + // TODO: Implement is_developer() method. + throw new Exception( 'not implemented' ); + } + + /** + * Check if current user classified as a business. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_business() { + // TODO: Implement is_business() method. + throw new Exception( 'not implemented' ); + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Helper + #---------------------------------------------------------------------------------- + + /** + * If running with a secret key, assume it's the developer and show pending plans as well. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.2 + * + * @param string $path + * + * @return string + */ + function add_show_pending( $path ) { + if ( ! $this->has_secret_key() ) { + return $path; + } + + return $path . ( false !== strpos( $path, '?' ) ? '&' : '?' ) . 'show_pending=true'; + } + + #endregion + } diff --git a/freemius/includes/class-fs-admin-notices.php b/freemius/includes/class-fs-admin-notices.php index e69de29..01b197a 100644 --- a/freemius/includes/class-fs-admin-notices.php +++ b/freemius/includes/class-fs-admin-notices.php @@ -0,0 +1,321 @@ +_id = $id; + $this->_title = $title; + $this->_module_unique_affix = $module_unique_affix; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + + $this->_network_notices = FS_Admin_Notice_Manager::instance( + $id, + $title, + $module_unique_affix, + $is_network_and_blog_admins, + true + ); + } + + $this->_notices = FS_Admin_Notice_Manager::instance( + $id, + $title, + $module_unique_affix, + false, + $this->_blog_id + ); + } + + /** + * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + * @param bool $store_if_sticky + * @param int|null $network_level_or_blog_id + * + * @uses add_action() + */ + function add( + $message, + $title = '', + $type = 'success', + $is_sticky = false, + $id = '', + $store_if_sticky = true, + $network_level_or_blog_id = null + ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + $notices->add( + $message, + $title, + $type, + $is_sticky, + $id, + $store_if_sticky + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string|string[] $ids + * @param int|null $network_level_or_blog_id + */ + function remove_sticky( $ids, $network_level_or_blog_id = null ) { + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + if ( $this->should_use_network_notices( $ids[0], $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + return $notices->remove_sticky( $ids ); + } + + /** + * Check if sticky message exists by id. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $id + * @param int|null $network_level_or_blog_id + * + * @return bool + */ + function has_sticky( $id, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + return $notices->has_sticky( $id ); + } + + /** + * Adds sticky admin notification. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $message + * @param string $id Message ID + * @param string $title + * @param string $type + * @param int|null $network_level_or_blog_id + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and + * blog admin pages. + */ + function add_sticky( + $message, + $id, + $title = '', + $type = 'success', + $network_level_or_blog_id = null, + $wp_user_id = null, + $plugin_title = null, + $is_network_and_blog_admins = false + ) { + if ( $this->should_use_network_notices( $id, $network_level_or_blog_id ) ) { + $notices = $this->_network_notices; + } else { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + } + + $notices->add_sticky( $message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + } + + /** + * Clear all sticky messages. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null $network_level_or_blog_id + */ + function clear_all_sticky( $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + 0 == $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) + ) { + $notices = $this->get_site_notices( $network_level_or_blog_id ); + $notices->clear_all_sticky(); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_notices->clear_all_sticky(); + } + } + + /** + * Add admin message to all admin messages queue, and hook to all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + */ + function add_all( $message, $title = '', $type = 'success', $is_sticky = false, $id = '' ) { + $this->add( $message, $title, $type, $is_sticky, true, $id ); + } + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return FS_Admin_Notice_Manager + */ + private function get_site_notices( $blog_id = 0 ) { + if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { + return $this->_notices; + } + + return FS_Admin_Notice_Manager::instance( + $this->_id, + $this->_title, + $this->_module_unique_affix, + false, + $blog_id + ); + } + + /** + * Check if the network notices should be used. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $id + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite notices (if there's a network). When `false`, use the current context blog notices. When `null`, the decision which notices manager to use (MS vs. Current S) will be handled internally and determined based on the $id and the context admin (blog admin vs. network level admin). + * + * @return bool + */ + private function should_use_network_notices( $id = '', $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + return fs_is_network_admin(); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php index e69de29..ec24ea2 100644 --- a/freemius/includes/class-fs-api.php +++ b/freemius/includes/class-fs-api.php @@ -0,0 +1,664 @@ +get_option( 'api_clock_diff', 0 ); + Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); + + if ( self::$_options->get_option( 'api_force_http', false ) ) { + Freemius_Api_WordPress::SetHttp(); + } + } + + /** + * @param string $slug + * @param string $scope 'app', 'developer', 'user' or 'install'. + * @param number $id Element's id. + * @param string $public_key Public key. + * @param bool|string $secret_key Element's secret key. + * @param bool $is_sandbox + * @param null|string $sdk_version + */ + private function __construct( + $slug, + $scope, + $id, + $public_key, + $secret_key, + $is_sandbox, + $sdk_version + ) { + $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox ); + + $this->_slug = $slug; + $this->_sdk_version = $sdk_version; + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + } + + /** + * Find clock diff between server and API server, and store the diff locally. + * + * @param bool|int $diff + * + * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. + */ + private function _sync_clock_diff( $diff = false ) { + $this->_logger->entrance(); + + // Sync clock and store. + $new_clock_diff = ( false === $diff ) ? + Freemius_Api_WordPress::FindClockDiff() : + $diff; + + if ( $new_clock_diff === self::$_clock_diff ) { + return false; + } + + self::$_clock_diff = $new_clock_diff; + + // Update API clock's diff. + Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); + + // Store new clock diff in storage. + self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); + + return $new_clock_diff; + } + + /** + * Override API call to enable retry with servers' clock auto sync method. + * + * @param string $path + * @param string $method + * @param array $params + * @param bool $retry Is in retry or first call attempt. + * + * @return array|mixed|string|void + */ + private function _call( $path, $method = 'GET', $params = array(), $retry = false ) { + $this->_logger->entrance( $method . ':' . $path ); + + if ( self::is_temporary_down() ) { + $result = $this->get_temporary_unavailable_error(); + } else { + /** + * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet. + */ + if ( ! empty( $this->_sdk_version ) ) { + if ( false === strpos( $path, 'sdk_version=' ) && + ! isset( $params['sdk_version'] ) + ) { + // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY. + $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path ); + } + } + + $result = $this->_api->Api( $path, $method, $params ); + + if ( null !== $result && + isset( $result->error ) && + isset( $result->error->code ) && + 'request_expired' === $result->error->code + ) { + if ( ! $retry ) { + $diff = isset( $result->error->timestamp ) ? + ( time() - strtotime( $result->error->timestamp ) ) : + false; + + // Try to sync clock diff. + if ( false !== $this->_sync_clock_diff( $diff ) ) { + // Retry call with new synced clock. + return $this->_call( $path, $method, $params, true ); + } + } + } + } + + if ( $this->_logger->is_on() && self::is_api_error( $result ) ) { + // Log API errors. + $this->_logger->api_error( $result ); + } + + return $result; + } + + /** + * Override API call to wrap it in servers' clock sync method. + * + * @param string $path + * @param string $method + * @param array $params + * + * @return array|mixed|string|void + * @throws Freemius_Exception + */ + function call( $path, $method = 'GET', $params = array() ) { + return $this->_call( $path, $method, $params ); + } + + /** + * Get API request URL signed via query string. + * + * @param string $path + * + * @return string + */ + function get_signed_url( $path ) { + return $this->_api->GetSignedUrl( $path ); + } + + /** + * @param string $path + * @param bool $flush + * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours + * + * @return stdClass|mixed + */ + function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( $path ); + + $cache_key = $this->get_cache_key( $path ); + + // Always flush during development. + if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { + $flush = true; + } + + $cached_result = self::$_cache->get( $cache_key ); + + if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) { + $result = $this->call( $path ); + + if ( ! is_object( $result ) || isset( $result->error ) ) { + // Api returned an error. + if ( is_object( $cached_result ) && + ! isset( $cached_result->error ) + ) { + // If there was an error during a newer data fetch, + // fallback to older data version. + $result = $cached_result; + + if ( $this->_logger->is_on() ) { + $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) ); + } + } else { + if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) { + /** + * If the response code is 404, cache the result for half of the `$expiration`. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + $expiration /= 2; + } else { + // If no older data version and the response code is not 404, return result without + // caching the error. + return $result; + } + } + } + + self::$_cache->set( $cache_key, $result, $expiration ); + + $cached_result = $result; + } else { + $this->_logger->log( 'Using cached API result.' ); + } + + return $cached_result; + } + + /** + * Check if there's a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @param string $path + * @param string $method + * @param array $params + * + * @return bool + */ + function is_cached( $path, $method = 'GET', $params = array() ) { + $cache_key = $this->get_cache_key( $path, $method, $params ); + + return self::$_cache->has_valid( $cache_key ); + } + + /** + * Invalidate a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param string $path + * @param string $method + * @param array $params + */ + function purge_cache( $path, $method = 'GET', $params = array() ) { + $this->_logger->entrance( "{$method}:{$path}" ); + + $cache_key = $this->get_cache_key( $path, $method, $params ); + + self::$_cache->purge( $cache_key ); + } + + /** + * Invalidate a cached version of the API request. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $path + * @param int $expiration + * @param string $method + * @param array $params + */ + function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) { + $this->_logger->entrance( "{$method}:{$path}:{$expiration}" ); + + $cache_key = $this->get_cache_key( $path, $method, $params ); + + self::$_cache->update_expiration( $cache_key, $expiration ); + } + + /** + * @param string $path + * @param string $method + * @param array $params + * + * @return string + * @throws \Freemius_Exception + */ + private function get_cache_key( $path, $method = 'GET', $params = array() ) { + $canonized = $this->_api->CanonizePath( $path ); +// $exploded = explode('/', $canonized); +// return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); + return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); + } + + /** + * Test API connectivity. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 If fails, try to fallback to HTTP. + * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if + * temporary down. + * + * @return bool True if successful connectivity to the API. + */ + static function test() { + self::_init(); + + $cache_key = 'ping_test'; + + $test = self::$_cache->get_valid( $cache_key, null ); + + if ( is_null( $test ) ) { + $test = Freemius_Api_WordPress::Test(); + + if ( false === $test && Freemius_Api_WordPress::IsHttps() ) { + // Fallback to HTTP, since HTTPS fails. + Freemius_Api_WordPress::SetHttp(); + + self::$_options->set_option( 'api_force_http', true, true ); + + $test = Freemius_Api_WordPress::Test(); + + if ( false === $test ) { + /** + * API connectivity test fail also in HTTP request, therefore, + * fallback to HTTPS to keep connection secure. + * + * @since 1.1.6 + */ + self::$_options->set_option( 'api_force_http', false, true ); + } + } + + self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); + } + + return $test; + } + + /** + * Check if API is temporary down. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + static function is_temporary_down() { + self::_init(); + + $test = self::$_cache->get_valid( 'ping_test', null ); + + return ( false === $test ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return object + */ + private function get_temporary_unavailable_error() { + return (object) array( + 'error' => (object) array( + 'type' => 'TemporaryUnavailable', + 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.', + 'code' => 'temporary_unavailable', + 'http' => 503 + ) + ); + } + + /** + * Ping API for connectivity test, and return result object. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param null|string $unique_anonymous_id + * @param array $params + * + * @return object + */ + function ping( $unique_anonymous_id = null, $params = array() ) { + $this->_logger->entrance(); + + if ( self::is_temporary_down() ) { + return $this->get_temporary_unavailable_error(); + } + + $pong = is_null( $unique_anonymous_id ) ? + Freemius_Api_WordPress::Ping() : + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); + + if ( $this->is_valid_ping( $pong ) ) { + return $pong; + } + + if ( self::should_try_with_http( $pong ) ) { + // Fallback to HTTP, since HTTPS fails. + Freemius_Api_WordPress::SetHttp(); + + self::$_options->set_option( 'api_force_http', true, true ); + + $pong = is_null( $unique_anonymous_id ) ? + Freemius_Api_WordPress::Ping() : + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); + + if ( ! $this->is_valid_ping( $pong ) ) { + self::$_options->set_option( 'api_force_http', false, true ); + } + } + + return $pong; + } + + /** + * Check if based on the API result we should try + * to re-run the same request with HTTP instead of HTTPS. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param $result + * + * @return bool + */ + private static function should_try_with_http( $result ) { + if ( ! Freemius_Api_WordPress::IsHttps() ) { + return false; + } + + return ( ! is_object( $result ) || + ! isset( $result->error ) || + ! isset( $result->error->code ) || + ! in_array( $result->error->code, array( + 'curl_missing', + 'cloudflare_ddos_protection', + 'maintenance_mode', + 'squid_cache_block', + 'too_many_requests', + ) ) ); + + } + + /** + * Check if valid ping request result. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.1 + * + * @param mixed $pong + * + * @return bool + */ + function is_valid_ping( $pong ) { + return Freemius_Api_WordPress::Test( $pong ); + } + + function get_url( $path = '' ) { + return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); + } + + /** + * Clear API cache. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + static function clear_cache() { + self::_init(); + + self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); + self::$_cache->clear(); + } + + #---------------------------------------------------------------------------------- + #region Error Handling + #---------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + static function is_api_error( $result ) { + return ( is_object( $result ) && isset( $result->error ) ) || + is_string( $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param mixed $result + * + * @return bool Is API result contains an error. + */ + static function is_api_error_object( $result ) { + return ( + is_object( $result ) && + isset( $result->error ) && + isset( $result->error->message ) + ); + } + + /** + * Checks if given API result is a non-empty and not an error object. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * @param string|null $required_property Optional property we want to verify that is set. + * + * @return bool + */ + static function is_api_result_object( $result, $required_property = null ) { + return ( + is_object( $result ) && + ! isset( $result->error ) && + ( empty( $required_property ) || isset( $result->{$required_property} ) ) + ); + } + + /** + * Checks if given API result is a non-empty entity object with non-empty ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $result + * + * @return bool + */ + static function is_api_result_entity( $result ) { + return self::is_api_result_object( $result, 'id' ) && + FS_Entity::is_valid_id( $result->id ); + } + + /** + * Get API result error code. If failed to get code, returns an empty string. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param mixed $result + * + * @return string + */ + static function get_error_code( $result ) { + if ( is_object( $result ) && + isset( $result->error ) && + is_object( $result->error ) && + ! empty( $result->error->code ) + ) { + return $result->error->code; + } + + return ''; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-logger.php b/freemius/includes/class-fs-logger.php index e69de29..624c683 100644 --- a/freemius/includes/class-fs-logger.php +++ b/freemius/includes/class-fs-logger.php @@ -0,0 +1,691 @@ +_id = $id; + + $bt = debug_backtrace(); + $caller = $bt[2]; + + if ( false !== strpos( $caller['file'], 'plugins' ) ) { + $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' ); + } else { + $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' ); + } + + if ( $on ) { + $this->on(); + } + if ( $echo ) { + $this->echo_on(); + } + } + + /** + * @param string $id + * @param bool $on + * @param bool $echo + * + * @return FS_Logger + */ + public static function get_logger( $id, $on = false, $echo = false ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_processID ) ) { + self::init(); + } + + if ( ! isset( self::$LOGGERS[ $id ] ) ) { + self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo ); + } + + return self::$LOGGERS[ $id ]; + } + + /** + * Initialize logging global info. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + private static function init() { + self::$_ownerName = function_exists( 'get_current_user' ) ? + get_current_user() : + 'unknown'; + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); + self::$_abspathLength = strlen( ABSPATH ); + self::$_processID = mt_rand( 0, 32000 ); + + // Process ID may be `false` on errors. + if ( ! is_numeric( self::$_processID ) ) { + self::$_processID = 0; + } + } + + private static function hook_footer() { + if ( self::$_HOOKED_FOOTER ) { + return; + } + + if ( is_admin() ) { + add_action( 'admin_footer', 'FS_Logger::dump', 100 ); + } else { + add_action( 'wp_footer', 'FS_Logger::dump', 100 ); + } + } + + function is_on() { + return $this->_on; + } + + function on() { + $this->_on = true; + + if ( ! function_exists( 'dbDelta' ) ) { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + } + + self::hook_footer(); + } + + function echo_on() { + $this->on(); + + $this->_echo = true; + } + + function is_echo_on() { + return $this->_echo; + } + + function get_id() { + return $this->_id; + } + + function get_file() { + return $this->_file_start; + } + + private function _log( &$message, $type, $wrapper = false ) { + if ( ! $this->is_on() ) { + return; + } + + $bt = debug_backtrace(); + $depth = $wrapper ? 3 : 2; + while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) { + $depth ++; + } + + $caller = $bt[ $depth ]; + + /** + * Retrieve the correct call file & line number from backtrace + * when logging from a wrapper method. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + if ( empty( $caller['line'] ) ) { + $depth --; + + while ( $depth >= 0 ) { + if ( ! empty( $bt[ $depth ]['line'] ) ) { + $caller['line'] = $bt[ $depth ]['line']; + $caller['file'] = $bt[ $depth ]['file']; + break; + } + } + } + + $log = array_merge( $caller, array( + 'cnt' => self::$CNT ++, + 'logger' => $this, + 'timestamp' => microtime( true ), + 'log_type' => $type, + 'msg' => $message, + ) ); + + if ( self::$_isStorageLoggingOn ) { + $this->db_log( $type, $message, self::$CNT, $caller ); + } + + self::$LOG[] = $log; + + if ( $this->is_echo_on() && ! Freemius::is_ajax() ) { + echo self::format_html( $log ) . "\n"; + } + } + + function log( $message, $wrapper = false ) { + $this->_log( $message, 'log', $wrapper ); + } + + function info( $message, $wrapper = false ) { + $this->_log( $message, 'info', $wrapper ); + } + + function warn( $message, $wrapper = false ) { + $this->_log( $message, 'warn', $wrapper ); + } + + function error( $message, $wrapper = false ) { + $this->_log( $message, 'error', $wrapper ); + } + + /** + * Log API error. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $api_result + * @param bool $wrapper + */ + function api_error( $api_result, $wrapper = false ) { + $message = ''; + if ( is_object( $api_result ) && + ! empty( $api_result->error ) && + ! empty( $api_result->error->message ) + ) { + $message = $api_result->error->message; + } else if ( is_object( $api_result ) ) { + $message = var_export( $api_result, true ); + } else if ( is_string( $api_result ) ) { + $message = $api_result; + } else if ( empty( $api_result ) ) { + $message = 'Empty API result.'; + } + + $message = 'API Error: ' . $message; + + $this->_log( $message, 'error', $wrapper ); + } + + function entrance( $message = '', $wrapper = false ) { + $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + function departure( $message = '', $wrapper = false ) { + $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + #-------------------------------------------------------------------------------- + #region Log Formatting + #-------------------------------------------------------------------------------- + + private static function format( $log, $show_type = true ) { + return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']'; + } + + private static function format_html( $log ) { + return '
    [' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . esc_html( $log['msg'] ) . '' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']
    '; + } + + #endregion + + static function dump() { + ?> + + + + prefix}fs_logger"; + + if ( $is_on ) { + /** + * Create logging table. + * + * NOTE: + * dbDelta must use KEY and not INDEX for indexes. + * + * @link https://core.trac.wordpress.org/ticket/2695 + */ + $result = $wpdb->query( "CREATE TABLE {$table} ( +`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, +`process_id` INT UNSIGNED NOT NULL, +`user_name` VARCHAR(64) NOT NULL, +`logger` VARCHAR(128) NOT NULL, +`log_order` INT UNSIGNED NOT NULL, +`type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log', +`message` TEXT NOT NULL, +`file` VARCHAR(256) NOT NULL, +`line` INT UNSIGNED NOT NULL, +`function` VARCHAR(256) NOT NULL, +`request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call', +`request_url` VARCHAR(1024) NOT NULL, +`created` DECIMAL(16, 6) NOT NULL, +PRIMARY KEY (`id`), +KEY `process_id` (`process_id` ASC), +KEY `process_logger` (`process_id` ASC, `logger` ASC), +KEY `function` (`function` ASC), +KEY `type` (`type` ASC))" ); + } else { + /** + * Drop logging table. + */ + $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" ); + } + + if ( false !== $result ) { + update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) ); + } + + return ( false !== $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $type + * @param string $message + * @param int $log_order + * @param array $caller + * + * @return false|int + */ + private function db_log( + &$type, + &$message, + &$log_order, + &$caller + ) { + global $wpdb; + + $request_type = 'call'; + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + $request_type = 'cron'; + } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + $request_type = 'ajax'; + } + + $request_url = WP_FS__IS_HTTP_REQUEST ? + $_SERVER['REQUEST_URI'] : + ''; + + return $wpdb->insert( + "{$wpdb->prefix}fs_logger", + array( + 'process_id' => self::$_processID, + 'user_name' => self::$_ownerName, + 'logger' => $this->_id, + 'log_order' => $log_order, + 'type' => $type, + 'request_type' => $request_type, + 'request_url' => $request_url, + 'message' => $message, + 'file' => isset( $caller['file'] ) ? + substr( $caller['file'], self::$_abspathLength ) : + '', + 'line' => $caller['line'], + 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'], + 'created' => microtime( true ), + ) + ); + } + + /** + * Persistent DB logger columns. + * + * @var array + */ + private static $_log_columns = array( + 'id', + 'process_id', + 'user_name', + 'logger', + 'log_order', + 'type', + 'message', + 'file', + 'line', + 'function', + 'request_type', + 'request_url', + 'created', + ); + + /** + * Create DB logs query. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * @param bool $escape_eol + * + * @return string + */ + private static function build_db_logs_query( + $filters = false, + $limit = 200, + $offset = 0, + $order = false, + $escape_eol = false + ) { + global $wpdb; + + $select = '*'; + + if ( $escape_eol ) { + $select = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $select .= ', '; + } + + if ( 'message' !== self::$_log_columns[ $i ] ) { + $select .= self::$_log_columns[ $i ]; + } else { + $select .= 'REPLACE(message , \'\n\', \' \') AS message'; + } + } + } + + $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; + if ( is_array( $filters ) ) { + $criteria = array(); + + if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) { + $filters['type'] = strtolower( $filters['type'] ); + + switch ( $filters['type'] ) { + case 'warn_error': + $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); + break; + case 'error': + case 'warn': + $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); + break; + case 'info': + default: + $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); + break; + } + } + + if ( ! empty( $filters['request_type'] ) ) { + $filters['request_type'] = strtolower( $filters['request_type'] ); + + if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) { + $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); + } + } + + if ( ! empty( $filters['file'] ) ) { + $criteria[] = array( + 'col' => 'file', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['file'] ), + ); + } + + if ( ! empty( $filters['function'] ) ) { + $criteria[] = array( + 'col' => 'function', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['function'] ), + ); + } + + if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) { + $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); + } + + if ( ! empty( $filters['logger'] ) ) { + $criteria[] = array( + 'col' => 'logger', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['logger'] ) . '%', + ); + } + + if ( ! empty( $filters['message'] ) ) { + $criteria[] = array( + 'col' => 'message', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['message'] ) . '%', + ); + } + + if ( 0 < count( $criteria ) ) { + $query .= "\nWHERE\n"; + + $first = true; + foreach ( $criteria as $c ) { + if ( ! $first ) { + $query .= "AND\n"; + } + + if ( is_array( $c['val'] ) ) { + $operator = 'IN'; + + for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) { + $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'"; + } + + $val = '(' . implode( ',', $c['val'] ) . ')'; + } else { + $operator = ! empty( $c['op'] ) ? $c['op'] : '='; + $val = "'" . esc_sql( $c['val'] ) . "'"; + } + + $query .= "`{$c['col']}` {$operator} {$val}\n"; + + $first = false; + } + } + } + + if ( ! is_array( $order ) ) { + $order = array( + 'col' => 'id', + 'order' => 'desc' + ); + } + + $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; + + return $query; + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return object[]|null + */ + public static function load_db_logs( + $filters = false, + $limit = 200, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order + ); + + return $wpdb->get_results( $query ); + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param string $filename + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return false|string File download URL or false on failure. + */ + public static function download_db_logs( + $filters = false, + $filename = '', + $limit = 10000, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order, + true + ); + + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}"; + + $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; + + $columns = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $columns .= ', '; + } + + $columns .= "'" . self::$_log_columns[ $i ] . "'"; + } + + $query = "SELECT {$columns} UNION ALL " . $query; + + $result = $wpdb->query( $query ); + + if ( false === $result ) { + return false; + } + + return rtrim( $upload_dir['url'], '/' ) . '/' . $filename; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $filename + * + * @return string + */ + public static function get_logs_download_url( $filename = '' ) { + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + + return rtrim( $upload_dir['url'], '/' ) . $filename; + } + + #endregion + } diff --git a/freemius/includes/class-fs-options.php b/freemius/includes/class-fs-options.php index e69de29..dcb9409 100644 --- a/freemius/includes/class-fs-options.php +++ b/freemius/includes/class-fs-options.php @@ -0,0 +1,431 @@ +_id = $id; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); + } + + $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); + } + + /** + * Switch the context of the site level options manager. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param $blog_id + */ + function set_site_blog_context( $blog_id ) { + $this->_blog_id = $blog_id; + + $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $option + * @param mixed $default + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + * + * @return mixed + */ + function get_option( $option, $default = null, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + return $this->_network_options->get_option( $option, $default ); + } + + $site_options = $this->get_site_options( $network_level_or_blog_id ); + + return $site_options->get_option( $option, $default ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param string $option + * @param mixed $value + * @param bool $flush + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + */ + function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + $this->_network_options->set_option( $option, $value, $flush ); + } else { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->set_option( $option, $value, $flush ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * @param bool $flush + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + */ + function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { + $this->_network_options->unset_option( $option, $flush ); + } else { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->unset_option( $option, $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param bool $flush + * @param bool $network_level + */ + function load( $flush = false, $network_level = true ) { + if ( $this->_is_multisite && $network_level ) { + $this->_network_options->load( $flush ); + } else { + $this->_options->load( $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.0 + * + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage. + */ + function store( $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + 0 == $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) + ) { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->store(); + } + + if ( $this->_is_multisite && + ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) + ) { + $this->_network_options->store(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int|null|bool $network_level_or_blog_id + * @param bool $flush + */ + function clear( $network_level_or_blog_id = null, $flush = false ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) || + is_numeric( $network_level_or_blog_id ) + ) { + $site_options = $this->get_site_options( $network_level_or_blog_id ); + $site_options->clear( $flush ); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_options->clear( $flush ); + } + } + + /** + * Migration script to the new storage data structure that is network compatible. + * + * IMPORTANT: + * This method should be executed only after it is determined if this is a network + * level compatible product activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + function migrate_to_network( $blog_id = 0 ) { + if ( ! $this->_is_multisite ) { + return; + } + + $updated = false; + + $site_options = $this->get_site_options( $blog_id ); + + $keys = $site_options->get_options_keys(); + + foreach ( $keys as $option ) { + if ( $this->is_site_option( $option ) || + // Don't move admin notices to the network storage. + in_array($option, array( + // Don't move admin notices to the network storage. + 'admin_notices', + // Don't migrate the module specific data, it will be migrated by the FS_Storage. + 'plugin_data', + 'theme_data', + )) + ) { + continue; + } + + $option_updated = false; + + // Migrate option to the network storage. + $site_option = $site_options->get_option( $option ); + + if ( ! $this->_network_options->has_option( $option ) ) { + // Option not set on the network level, so just set it. + $this->_network_options->set_option( $option, $site_option, false ); + + $option_updated = true; + } else { + // Option already set on the network level, so we need to merge it inelegantly. + $network_option = $this->_network_options->get_option( $option ); + + if ( is_array( $network_option ) && is_array( $site_option ) ) { + // Option is an array. + foreach ( $site_option as $key => $value ) { + if ( ! isset( $network_option[ $key ] ) ) { + $network_option[ $key ] = $value; + + $option_updated = true; + } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { + if ( empty( $network_option[ $key ] ) ) { + $network_option[ $key ] = $value; + + $option_updated = true; + } else if ( empty( $value ) ) { + // Do nothing. + } else { + reset($value); + $first_key = key($value); + if ( $value[$first_key] instanceof FS_Entity ) { + // Merge entities by IDs. + $network_entities_ids = array(); + foreach ( $network_option[ $key ] as $entity ) { + $network_entities_ids[ $entity->id ] = true; + } + + foreach ( $value as $entity ) { + if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { + $network_option[ $key ][] = $entity; + + $option_updated = true; + } + } + } + } + } + } + } + + if ( $option_updated ) { + $this->_network_options->set_option( $option, $network_option, false ); + } + } + + /** + * Remove the option from site level storage. + * + * IMPORTANT: + * The line below is intentionally commented since we want to preserve the option + * on the site storage level for "downgrade compatibility". Basically, if the user + * will downgrade to an older version of the plugin with the prev storage structure, + * it will continue working. + * + * @todo After a few releases we can remove this. + */ +// $site_options->unset_option($option, false); + + if ( $option_updated ) { + $updated = true; + } + } + + if ( ! $updated ) { + return; + } + + // Update network level storage. + $this->_network_options->store(); +// $site_options->store(); + } + + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * We don't want to load the map right away since it's not even needed in a non-MS environment. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function load_site_options_map() { + self::$_SITE_OPTIONS_MAP = array( + 'sites' => true, + 'theme_sites' => true, + 'unique_id' => true, + 'active_plugins' => true, + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * + * @return bool + */ + private function is_site_option( $option ) { + if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { + return false; + } + + if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { + self::load_site_options_map(); + } + + return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return FS_Option_Manager + */ + private function get_site_options( $blog_id = 0 ) { + if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { + return $this->_options; + } + + return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); + } + + /** + * Check if an option should be stored on the MS network storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $option + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). + * + * @return bool + */ + private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + // Determine which storage to use based on the option. + return ! $this->is_site_option( $option ); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php index e69de29..8f6b996 100644 --- a/freemius/includes/class-fs-plugin-updater.php +++ b/freemius/includes/class-fs-plugin-updater.php @@ -0,0 +1,1561 @@ +get_id(); + + if ( ! isset( self::$_INSTANCES[ $key ] ) ) { + self::$_INSTANCES[ $key ] = new self( $freemius ); + } + + return self::$_INSTANCES[ $key ]; + } + + #endregion + + private function __construct( Freemius $freemius ) { + $this->_fs = $freemius; + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->filters(); + } + + /** + * Initiate required filters. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + */ + private function filters() { + // Override request for plugin information + add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 ); + + $this->add_transient_filters(); + + /** + * If user has the premium plugin's code but do NOT have an active license, + * encourage him to upgrade by showing that there's a new release, but instead + * of showing an update link, show upgrade link to the pricing page. + * + * @since 1.1.6 + * + */ + // WP 2.9+ + add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( + &$this, + 'catch_plugin_update_row' + ), 9 ); + add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( + &$this, + 'edit_and_echo_plugin_update_row' + ), 11, 2 ); + + if ( ! $this->_fs->has_any_active_valid_license() ) { + add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) ); + } + + if ( ! WP_FS__IS_PRODUCTION_MODE ) { + add_filter( 'http_request_host_is_external', array( + $this, + 'http_request_host_is_external_filter' + ), 10, 3 ); + } + + if ( $this->_fs->is_premium() ) { + if ( ! $this->is_correct_folder_name() ) { + add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 ); + } + + add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 ); + add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); + + if ( ! $this->_fs->has_any_active_valid_license() ) { + add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 ); + } + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + */ + function catch_plugin_information_dialog_contents() { + if ( + 'plugin-information' !== fs_request_get( 'tab', false ) || + $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) + ) { + return; + } + + add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 ); + + ob_start(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.4 + * + * @param string $hook_suffix + */ + function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) { + if ( + 'plugin-information' !== fs_request_get( 'tab', false ) || + $this->_fs->get_slug() !== fs_request_get( 'plugin', false ) + ) { + return; + } + + $license = $this->_fs->_get_license(); + + $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ? + $this->_fs->_get_subscription( $license->id ) : + null; + + $contents = ob_get_clean(); + + $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' ); + + if ( false !== $update_button_id_attribute_pos ) { + $update_button_start_pos = strrpos( + substr( $contents, 0, $update_button_id_attribute_pos ), + '', $update_button_id_attribute_pos ) + strlen( '' ) ); + + /** + * The part of the contents without the update button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents = substr( $contents, 0, $update_button_start_pos ); + + $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) ); + + /** + * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, + * the text will be "Renew license" and will link to the checkout page with the license's billing cycle + * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. + */ + $update_button = preg_replace( + '/(\)(.+)(\<\/a>)/is', + is_object( $license ) ? + sprintf( + '$1$3%s$5%s$7', + $this->_fs->checkout_url( + is_object( $subscription ) ? + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : + WP_FS__PERIOD_LIFETIME, + false, + array( 'licenses' => $license->quota ) + ), + fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) + ) : + sprintf( + '$1$3%s$5%s$7', + $this->_fs->pricing_url(), + fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) + ), + $update_button + ); + + /** + * Append the modified button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents .= $update_button; + + /** + * Append the remaining part of the contents after the update button. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.5 + */ + $modified_contents .= substr( $contents, $update_button_end_pos ); + + $contents = $modified_contents; + } + + echo $contents; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function add_transient_filters() { + if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) { + $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' ); + + return; + } + + add_filter( 'pre_set_site_transient_update_plugins', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + + add_filter( 'pre_set_site_transient_update_themes', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private function remove_transient_filters() { + remove_filter( 'pre_set_site_transient_update_plugins', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + + remove_filter( 'pre_set_site_transient_update_themes', array( + &$this, + 'pre_set_site_transient_update_plugins_filter' + ) ); + } + + /** + * Capture plugin update row by turning output buffering. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + function catch_plugin_update_row() { + ob_start(); + } + + /** + * Overrides default update message format with "renew your license" message. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $file + * @param array $plugin_data + */ + function edit_and_echo_plugin_update_row( $file, $plugin_data ) { + $plugin_update_row = ob_get_clean(); + + $current = get_site_transient( 'update_plugins' ); + if ( ! isset( $current->response[ $file ] ) ) { + echo $plugin_update_row; + + return; + } + + $r = $current->response[ $file ]; + + $has_beta_update = $this->_fs->has_beta_update(); + + if ( $this->_fs->has_any_active_valid_license() ) { + if ( $has_beta_update ) { + /** + * Turn the "new version" text into "new Beta version". + * + * Sample input: + * There is a new version of Awesome Plugin available. update now. + * Output: + * There is a new Beta version of Awesome Plugin available. update now. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $plugin_update_row = preg_replace( + '/(\)(.+)(\.+\)/is', + ( + '$1' . + sprintf( + fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), + $has_beta_update ? + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ), + $this->_fs->get_plugin_title() + ) . + ' ' . + '$3' . + '$6' + ), + $plugin_update_row + ); + } + } else { + /** + * Turn the "new version" text into a link that opens the plugin information dialog when clicked and + * make the "View version x details" text link to the checkout page instead of opening the plugin + * information dialog when clicked. + * + * Sample input: + * There is a new version of Awesome Plugin available. update now. + * Output: + * There is a Buy a license now to access version x.y.z security & feature updates, and support. + * OR + * There is a Buy a license now to access version x.y.z security & feature updates, and support. + * + * @author Leo Fajardo (@leorw) + */ + $plugin_update_row = preg_replace( + '/(\)(.+)(\.+\)/is', + ( + '$1' . + sprintf( + fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), + sprintf( + '%s', + '$5', + $has_beta_update ? + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ) + ), + $this->_fs->get_plugin_title() + ) . + ' ' . + $this->_fs->version_upgrade_checkout_link( $r->new_version ) . + '$6' + ), + $plugin_update_row + ); + } + + if ( + $this->_fs->is_plugin() && + isset( $r->upgrade_notice ) && + strlen( trim( $r->upgrade_notice ) ) > 0 + ) { + $slug = $this->_fs->get_slug(); + + $upgrade_notice_html = sprintf( + '

    %3$s %4$s

    ', + $slug, + $this->_fs->get_module_type(), + fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ), + esc_html( $r->upgrade_notice ) + ); + + $plugin_update_row = str_replace( '
    ' . $newline; $table .= '' . $arrows['left'] . '' . $newline; diff --git a/templates/master-schedule-tabs.php b/templates/master-schedule-tabs.php index 9556b82..87b7313 100644 --- a/templates/master-schedule-tabs.php +++ b/templates/master-schedule-tabs.php @@ -47,6 +47,10 @@ $more = apply_filters( 'radio_station_schedule_tabs_excerpt_more', '[…]' ); } +// --- filter arrows --- +$arrows = array( 'right' => '►', 'left' => '◄' ); +$arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'tabs' ); + // --- set cell info key order --- // 2.3.3.8: added for possible info rearrangement $infokeys = array( 'title', 'hosts', 'times', 'encore', 'file', 'genres', 'custom' ); @@ -143,8 +147,6 @@ // 2.3.0: added left/right arrow responsive controls // 2.3.1: added (negative) return to arrow onclick functions - $arrows = array( 'right' => '►', 'left' => '◄' ); - $arrows = apply_filters( 'radio_station_schedule_arrows', $arrows, 'tabs' ); $tabs .= '
  • ' . $newline; $tabs .= '
    ' . $newline; $tabs .= '' . $arrows['left'] . '' . $newline; From 25797997b1df648511e091dfe4e253ca7b7afb67 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Tue, 9 Mar 2021 16:50:46 +1000 Subject: [PATCH 191/377] dev update #323 --- includes/post-types-admin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index 37c20d4..debb202 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -1189,9 +1189,10 @@ function radio_station_post_show_metabox() { $selected = array( $selected ); } // 2.3.3.6: remove possible saved zero value + // 2.3.3.9: fix to duplicate use of selected variable if ( count( $selected ) > 0 ) { - foreach ( $selected as $i => $selected ) { - if ( 0 == $selected ) { + foreach ( $selected as $i => $value ) { + if ( 0 == $value ) { unset( $selected[$i] ); } } From b28fd4db0c15ece2dd05759d028c1e0bdafa8b57 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Wed, 10 Mar 2021 14:09:57 +1000 Subject: [PATCH 192/377] dev update #322 --- includes/support-functions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/support-functions.php b/includes/support-functions.php index 5f1ae4f..065b65d 100644 --- a/includes/support-functions.php +++ b/includes/support-functions.php @@ -4017,11 +4017,11 @@ function radio_station_convert_show_shifts( $show ) { $start_hour = substr( radio_station_convert_shift_time( $shift['start_hour'] . $shift['start_meridian'], 24 ), 0, 2 ); $end_hour = substr( radio_station_convert_shift_time( $shift['end_hour'] . $shift['end_meridian'], 24 ), 0, 2 ); // 2.3.3.9: added missing shift encore designation - $encore = ( 'on' == $shift['encore'] ) ? 1 : 0; + $encore = ( 'on' == $shift['encore'] ) ? true : false; $schedule[$i] = array( - 'day' => $shift['day'], - 'start' => $start_hour . ':' . $shift['start_min'], - 'end' => $end_hour . ':' . $shift['end_min'], + 'day' => $shift['day'], + 'start' => $start_hour . ':' . $shift['start_min'], + 'end' => $end_hour . ':' . $shift['end_min'], 'encore' => $encore, ); } From e96efcaf908335b97c7d55d3e49c378e8c027c25 Mon Sep 17 00:00:00 2001 From: Tony Hayes Date: Mon, 26 Apr 2021 16:03:59 +1000 Subject: [PATCH 193/377] dev updates including #301 #249 --- CHANGELOG.md | 1 + css/rs-schedule.css | 57 +- css/rs-shortcodes.css | 77 +- css/rs-templates.css | 9 + docs/API.md | 7 + docs/Data.md | 10 +- docs/Display.md | 34 +- docs/Filters.md | 13 +- docs/Manage.md | 5 + docs/Options.md | 192 +- docs/Pro.md | 48 + docs/Roadmap.md | 24 +- docs/Roles.md | 10 +- docs/Shortcodes.md | 53 +- docs/Widgets.md | 24 +- docs/index.md | 11 +- freemius/assets/css/admin/common.css | 2 +- freemius/assets/css/admin/connect.css | 2 +- freemius/includes/class-freemius.php | 120 +- freemius/includes/class-fs-logger.php | 4 +- freemius/includes/entities/class-fs-site.php | 17 + freemius/includes/entities/class-fs-user.php | 17 - freemius/languages/freemius-cs_CZ.mo | Bin 59930 -> 60931 bytes freemius/languages/freemius-da_DK.mo | Bin 58864 -> 59840 bytes freemius/languages/freemius-en.mo | Bin 58177 -> 59161 bytes freemius/languages/freemius-es_ES.mo | Bin 61970 -> 62945 bytes freemius/languages/freemius-fr_FR.mo | Bin 62562 -> 63519 bytes freemius/languages/freemius-he_IL.mo | Bin 61224 -> 62213 bytes freemius/languages/freemius-hu_HU.mo | Bin 59307 -> 60199 bytes freemius/languages/freemius-it_IT.mo | Bin 60682 -> 61645 bytes freemius/languages/freemius-ja.mo | Bin 66410 -> 67335 bytes freemius/languages/freemius-nl_NL.mo | Bin 60674 -> 61628 bytes freemius/languages/freemius-ru_RU.mo | Bin 74731 -> 75599 bytes freemius/languages/freemius-ta.mo | Bin 92468 -> 93241 bytes freemius/languages/freemius-zh_CN.mo | Bin 55922 -> 56773 bytes freemius/languages/freemius.pot | 658 +- freemius/package.json | 2 +- freemius/start.php | 6 +- freemius/templates/account.php | 8 +- freemius/templates/connect.php | 86 +- .../templates/forms/license-activation.php | 4 +- includes/class-current-playlist-widget.php | 28 +- includes/class-current-show-widget.php | 15 +- includes/class-radio-clock-widget.php | 31 +- includes/class-upcoming-shows-widget.php | 20 +- includes/extra-strings.php | 182 + includes/legacy.php | 14 +- includes/master-schedule.php | 499 +- includes/post-types-admin.php | 7924 ++++++++++------- includes/post-types.php | 208 +- includes/shortcodes.php | 1067 ++- includes/support-functions.php | 612 +- js/moment.js | 5670 ++++++++++++ js/moment.min.js | 2 + js/radio-station-admin.js | 39 + js/radio-station-clock.js | 93 +- js/radio-station-countdown.js | 2 +- js/radio-station.js | 152 +- loader.php | 7 +- radio-station-admin.php | 49 +- radio-station.php | 303 +- reader.php | 2 +- readme.md | 2 +- readme.txt | 22 +- templates/master-schedule-list.php | 57 +- templates/master-schedule-table.php | 66 +- templates/master-schedule-tabs.php | 76 +- templates/single-show-content.php | 112 +- 68 files changed, 14252 insertions(+), 4503 deletions(-) create mode 100644 docs/Pro.md create mode 100644 includes/extra-strings.php create mode 100644 js/moment.js create mode 100644 js/moment.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e6ed7..27ce7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 2.3.3.9 * Update: Plugin Panel (1.1.8) with Number Step Min/Max fix +* Update: Freemius SDK (2.4.2) * Fixed: Shows Data Endpoint 24 Hour Shift Format and Encore Switch * Fixed: Multiple host separator display in Current Show Widget diff --git a/css/rs-schedule.css b/css/rs-schedule.css index 1e95ec7..ab3cbeb 100644 --- a/css/rs-schedule.css +++ b/css/rs-schedule.css @@ -7,6 +7,39 @@ clear: both; } +.show-user-time { + display: none; + font-style: italic; +} + +/* Edit / Add Links */ +/* ---------------- */ +.show-title, .show-edit-link { + display: inline-block; +} + +.show-edit-link, .show-add-link, .shift-edit-link { + text-decoration: none; + cursor: pointer; +} + +.show-edit-link span, .show-add-link span { + color: #777777; + opacity: 0.9; +} + +.show-edit-link span { + font-size: 18px; +} + +.show-add-link span { + font-size: 16px; +} + +.show-edit-link:hover span, .show-add-link:hover span { + opacity: 1; +} + /* Schedule Controls */ /* ----------------- */ #master-schedule-controls-wrapper { @@ -89,10 +122,16 @@ #master-program-schedule th .shift-left-arrow { float: left; + font-size: 2em; + font-weight: bold; + line-height: 0.5em; } #master-program-schedule th .shift-right-arrow { float: right; + font-size: 2em; + font-weight: bold; + line-height: 0.5em; } #master-program-schedule th .shift-left-arrow, #master-program-schedule th .shift-right-arrow { @@ -146,7 +185,7 @@ #master-program-schedule .show-title, #master-program-schedule .show-image, #master-program-schedule .show-desc { max-width: 120px; - margin: 0 auto; + text-align: center; } #master-program-schedule .show-info.overflow { @@ -280,6 +319,9 @@ #master-schedule-tabs .first-tab .shift-left-arrow, #master-schedule-tabs .last-tab .shift-right-arrow { display: inline-block; + font-size: 2em; + font-weight: bold; + line-height: 0.5em; padding: 5px; } @@ -291,14 +333,14 @@ cursor: pointer; text-align: center; background-color: #eeeeee; - color: #444444; + color: #444444; font-size: 1em; font-weight: bold; - border-radius: 7px 7px 0 0; - border: 1px solid #000000; - list-style: none; - margin-left: 20px; - border-bottom: 0; + border-radius: 7px 7px 0 0; + border: 1px solid #000000; + list-style: none; + margin-left: 20px; + border-bottom: 0; } #master-schedule-tabs .master-schedule-tabs-day.start-tab, @@ -517,6 +559,7 @@ line-height: 1em; } + #master-schedule-divs .show-image img { width: 100%; height: auto; diff --git a/css/rs-shortcodes.css b/css/rs-shortcodes.css index e55604e..6675a67 100644 --- a/css/rs-shortcodes.css +++ b/css/rs-shortcodes.css @@ -11,9 +11,15 @@ font-size: 0.8em; } +.show-user-time { + display: none; + font-style: italic; +} + + /* Timezone Shortcode */ /* ------------------ */ -.radio-timezone-title, .radio-timezone, .radio-user-timezone-title, .radio-user-timezone { +.radio-timezone-title, .radio-timezone { display: inline-block; } @@ -21,11 +27,15 @@ font-weight: bold; } -.radio-user-timezone-title { +.radio-user-timezone-title, .radio-user-timezone { display: none; - margin-left: 30px; } +.radio-user-timezone { + font-style: italic; +} + + /* Clock Shortcode */ /* --------------- */ .radio-clock-title, .radio-server-time, .radio-server-date, .radio-server-zone, @@ -37,6 +47,10 @@ min-width: 100px; } +.radio-timezone-title, .radio-user-timezone-title { + min-width: 120px; +} + .radio-server-time, .radio-user-time, .radio-server-date, .radio-user-date { margin-left: 10px; } @@ -45,6 +59,10 @@ margin-left: 20px; } +.radio-user-time, .radio-user-date, .radio-user-zone { + font-style: italic; +} + .radio-station-clock-widget .radio-station-server-clock, .radio-station-clock-widget .radio-station-user-clock { font-size: 0.9em; margin-top: 10px; @@ -63,7 +81,7 @@ /* Archive Shortcodes */ /* ------------------ */ -.genre-archive { +.genre-archive, .language-archive { clear: both; } @@ -73,7 +91,7 @@ } .show-archive-item, .playlist-archive-item, .override-archive-item { - list-style: none; + list-style: none; clear: both; padding-top: 10px; padding-bottom: 10px; @@ -84,9 +102,14 @@ padding-right: 20px; } -.genre-archive .show-archive-item-title { - font-size: 1em; - margin-bottom: 5px; +.genre-archive .show-archive-item-title, .language-archive .show-archive-item-title { + font-size: 1em; + margin-bottom: 5px; +} + +.genre-archive .show-archive-item-thumbnail, .language-archive .show-archive-item-thumbnail { + width: 120px; + height: auto; } .show-archive-item-title, .playlist-archive-item-title, .override-archive-item-title { @@ -104,8 +127,8 @@ .show-post-thumbnail, .show-post-info, .show-playlist-thumbnail, .show-playlist-info, .show-episode-thumbnail, .show-episode-info { - display: table-cell; - vertical-align: top; + display: table-cell; + vertical-align: top; } .show-post-thumbnail, .show-playlist-thumbnail, .show-episode-thumbnail { @@ -195,7 +218,7 @@ ul.current-show-list, ul.current-show-list li, ul.upcoming-shows-list, ul.upcomi } .current-show-countdown .rs-label, .upcoming-show-countdown .rs-label { - font-weight: bold; + font-weight: bold; } .current-show-countdown .rs-hours, .current-show-countdown .rs-minutes, .current-show-countdown .rs-seconds, @@ -212,36 +235,40 @@ ul.current-show-list, ul.current-show-list li, ul.upcoming-shows-list, ul.upcomi .current-show-desc, .on-air-dj-desc, .current-show-playlist, .on-air-dj-playlist { - font-size: 0.8em; - margin-top: 5px; - margin-bottom: 5px; + font-size: 0.8em; + margin-top: 5px; + margin-bottom: 5px; } .current-show-schedule, .upcoming-show-schedule, .on-air-dj-schedule { - font-size: 0.85em; - margin-top: 10px; - margin-bottom: 10px; - overflow: hidden; + font-size: 0.85em; + margin-top: 10px; + margin-bottom: 10px; + overflow: hidden; } .current-show-shifts, .upcoming-show-shifts, .on-air-dj-sched { + font-size: 0.85em; + margin-top: 5px; + margin-bottom: 5px; +} + +.current-show .show-user-time, .upcoming-show-schedule .show-user-time { font-size: 0.85em; - margin-top: 5px; - margin-bottom: 5px; } .current-shift-list, .current-shift-list-item { - margin: 0 !important; - padding: 0 !important; + margin: 0 !important; + padding: 0 !important; } .current-shift-list .current-shift-list-item { - list-style: disc; - text-indent: 20px; + list-style: disc; + text-indent: 20px; } .current-show-schedule .current-show-shifts.current-shift { - font-style: italic; + font-style: italic; } .upcoming-shows-list .upcoming-show-title, .on-air-upcoming-list .on-air-dj-title { diff --git a/css/rs-templates.css b/css/rs-templates.css index 7c709a9..cf51141 100644 --- a/css/rs-templates.css +++ b/css/rs-templates.css @@ -7,6 +7,15 @@ clear: both; } +.show-user-time { + display: none; + font-style: italic; +} + +.rs-start-date, .rs-end-date { + margin-right: 0.5em; +} + /* Show Page Styles */ /* ---------------- */ diff --git a/docs/API.md b/docs/API.md index 3130ba9..9706b0b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -119,3 +119,10 @@ Optionally, a single or comma-separated `genre` argument will retrieve specific Optionally, a single or comma-separated `language` argument will retrieve specific Language information. * *languages*: Data array of all Languages with all the `[Shows]` for each Language. + +#### [Pro] Episodes Endpoint + +#### [Pro] Hosts Endpoint + +#### [Pro] Producers Endpoint + diff --git a/docs/Data.md b/docs/Data.md index 0441428..8c5a7f3 100644 --- a/docs/Data.md +++ b/docs/Data.md @@ -35,13 +35,13 @@ For each Track the following can be specified: Standard WordPress Posts are assignable to a Show via a metabox on the Post Edit screen. This is good for allowing listeners to find news and other announcements for a particular Show, for when assigned to a Show they will display in a paginated list on that Show's page. -### [Pro] Episodes +### [Pro] Show Episodes -A future release of Radio Statio Pro will include Show Episodes. These will be assignable to a Show to create and archive of Episodes for specific dates that will display on the Show page. +[Radio Statio Pro](https://radiostation.pro) will include Show Episodes. These will be assignable to a Show to create and archive of Episodes for specific dates that will display on the Show page. -### [Pro] Profiles +### [Pro] Host and Producer Profiles -A future release of Radio Station Pro will include Show Host and Producers Profile pages. These will display a profile template rather than the standard Author template for Hosts and Producers. +[Radio Station Pro](https://radiostation.pro) will include Show Host and Producers Profile pages. These will display a profile template rather than the standard Author template for Hosts and Producers. ## Taxonomies @@ -54,3 +54,5 @@ A flexible taxonomy allowing for the addition of Genre terms that can be assigne A fixed taxonomy allowing for assigning of Language terms to a Show (or Override.) In a Show does not have a Language assigned, it is assumed to be in the main Language selected in the Plugin Settings. Languages are displayed on a Show's page, and discoverable via the plugin's data [API](./API.md) Languages Endpoint, and in future will be displayable in widgets and other shortcodes also. There will also be an addition of a Languages Archive Shortcode that will work similar to the Genre Archive Shortcode. + + diff --git a/docs/Display.md b/docs/Display.md index b44e512..cddbe3d 100644 --- a/docs/Display.md +++ b/docs/Display.md @@ -95,6 +95,36 @@ If your chosen Show template (eg. `page.php`) does not display the Featured Imag Since 2.3.0, image support has been added to Schedule Overrides so that they behave just like Shows in this respect. So the above sections on Show Images also now apply to Schedule Overrides. -### [Pro] Genre Images +### [Pro] Profile Images + +In [Radio Station Pro](https://radiostation.pro), Host and Producer Profiles also support having images added, which then display on thee public profile pages for the host or producer. + +### [Pro] Genre Images and Colors + +In [Radio Station Pro](https://radiostation.pro), images and colors can also be assigned to Genre taxonomy terms via the WordPress Admin Taxonomy Editing interface. Images may then optionally be displayed in the [Genre Archive Shortcode](./Shortcodes.md#genre-archives-shortcode) to provide another visual level to that Show list display. Genre colors are used in the Master Schedule display when the user highlights particular Genres. + + +## Translations + +#### Using WordPress Translations + +Please note that the "Main Broadcast Language" Setting on the plugin page does NOT translate any strings, it is for assigning the default Language for Shows, which is displayed to visitors on the Show page etc. See the [Language Taxonomy](./Data.md) for more details. + +Radio Station is fully translation ready. This means all the text strings within the plugin are wrapped in translation functions so that WordPress can handle the automatic translation of these strings. If your site language is set through WordPress Admin -> Settings page (under Site Language) then you will see the translated string output changed in your selected language - but only for any strings where a translation exists. + +This is because some strings have never been translated into certain languages, but also because some have been translated in the past and some added to newer versions have not been translated yet. You can add to the existing translations in any Language via the [Radio Station Translation Project on WordPress.Org](https://translate.wordpress.org/projects/wp-plugins/radio-station/) By logging in to WordPress.org you can now add these string translations directly, and they will be used by the WordPress to translate the plugin. + +#### Time Related Translations + +Unlike other word strings, those involving dates and times are automatically translated by the plugin into your WordPress language using the `WP_Locale` class. + +This means you do not have to retranslate common strings for days of the week, month names, or am/pm meridian strings. + +#### Using a Translation Plugin + + + + + + -In Radio Station Pro, images can also be assigned to Genre taxonomy terms via the WordPress Admin Taxonomy Editing interface. These may then optionally be displayed in the [Genre Archive Shortcode](./Shortcodes.md#genre-archives-shortcode) to provide another visual level to that Show list display. diff --git a/docs/Filters.md b/docs/Filters.md index b851cab..81be917 100644 --- a/docs/Filters.md +++ b/docs/Filters.md @@ -2,6 +2,10 @@ *** +## Settings Filters + +To programmatically override any of the Plugin Settings available from the Settings Page, see [Options Documentation](./Options.md) + ## Data Filters There are filters throughout the plugin that allow you to override data values and plugin output. We employ the practice of adding as many of these as possible to allow users of the plugin to customize it's behaviour without needing to modify the plugin's code - as these kind of modifications are overwritten with plugin updates. @@ -10,7 +14,7 @@ You can add your own custom filters via a Code Snippets plugin (which has the ad ## Finding Filters -You can find these filters by searching any of PHP plugin files for `apply_filters( 'radio_`. +You can find these filters by searching any of the PHP plugin files for: `apply_filters( 'radio_` ## Filter Values and Arguments @@ -441,3 +445,10 @@ Here is a full list of available filters within the plugin, grouped by file and | |`radio_station_show_latest_posts_label` | ` $label` | `$post_id`| | |`radio_station_show_page_latest_shortcode` | ` $shortcode` | `$post_id`| | |`radio_station_show_page_section_order` | ` $section_order` | `$post_id`| + + +## [Pro] Pro Filter List + +Below is a list of filters that are available within [Radio Station Pro](https://radiostation.pro). + +*TODO* \ No newline at end of file diff --git a/docs/Manage.md b/docs/Manage.md index 30c47e7..bd2716c 100644 --- a/docs/Manage.md +++ b/docs/Manage.md @@ -10,6 +10,11 @@ Shows, Overrides and Playlists each have their own standard list page via the Wo You can add Show Shifts from the Show's Edit page. You can click Add Shift as many times as you like to create new Shift entries. For a Shift to be valid it must have all time fields filled in. The Encore field (repeat airing) is optional. You can disable a Shift and it will be unused but still saved. Each Shift entry also has a Duplicate and Remove icon to copy that entry to a new Shift or remove it. Shifts are not altered until you save them via clicking Update for a Show. +### [Pro] Visual Shift Editor + +[Radio Station Pro](https://radiostation.pro) includes a Visual Shift Editor to allow you to assign Shifts directly to the Schedule. ... + + ## Shift Conflict Checking ### Shift Save Checking diff --git a/docs/Options.md b/docs/Options.md index 1c02228..c8c97f6 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -2,47 +2,111 @@ *** -Plugin Settings are stored in an array under the `radio_station` option key. +Plugin Settings are stored in an array under the `radio_station` key in the WordPress options table. Below is a list of plugin options available via the Plugin Settings Screen. +### Plugin Setting Value Filters + +Note for custom flexibility, all Plugin Settings can also be filtered programmatically via their respective option key. Use `add_filter` to add a filter to `radio_station_{settings_key}`, then check your desired conditions to modify the value before returning it. eg: + +``` +add_filter( 'radio_station_station_phone', 'my_custom_station_phone' ); +function my_custom_station_phone( $number ) { + $current_hour = (int) date( 'G', time() ); + if ( $current_hour > 20 ) {$number = '(123) 456 7890';} + return $number; +} +``` + +The above example will change the display of the Station Phone number after 8pm (server time). + + ## General ### Broadcast #### Streaming URL -Default: None. Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used in the upcoming Radio Player. +Default: None. Key: streaming_format +Enter the Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used by default by the Radio Player. + +#### Stream Format +Default: AAC/M4A. Key: streaming_format +Select the format for your stream. This will be discoverable via Data Feeds and used by default by the Radio Player. + +#### Fallback URL +Default: None. Key: fallback_url +Enter the fallback Streaming URL for your Radio Station. This will be discoverable via Data Feeds and used by default by the Radio Player. + +#### Streaming URL +Default: OGG. Key: fallback_format +Select the format for your fallback stream. This will be discoverable via Data Feeds and used by default by the Radio Player. #### Main Broadcast Language -Default: WordPress Language. Select the main language used on your Radio Station. +Default: WordPress Language. Key: radio_language +Select the main language used on your Radio Station. + +### Station -### Times +#### Station Title +Default: none. Key: station_title + +#### Station Image +Default: none. Key: station_image #### Location Timezone -Default: WordPress Timezone. Select your Broadcast Location for Radio Timezone display. +Default: WordPress Timezone. Key: timezone_location +Select your Broadcast Location for Radio Timezone display. #### Clock Time Format -Default: 12 Hour Format. Default Time Format for display output. Can be overridden in each shortcode or widget. +Default: 12 Hour Format. Key: clock_time_format +Default Time Format for display output. Can be overridden in each shortcode or widget. + +#### Station Phone +Default: none. Key: station_phone +Default Phone Number to use for requests etc. + +#### Shows Phone +Default: On. Key: shows_phone +Use the Station Phone Number on Shows which do not have a phone number specified. + +#### Station Email +Default: none. Key: station_email +Default Email Address to use for requests etc. + +#### Shows Email +Default: On. Key: shows_email +Use the Station Email Address on Shows which do not have an email address specified. ### Feeds #### Enable Data Routes -Default: On. Enables Station Data Routes via WordPress REST API. - +Default: On. Key: enable_data_routes +Enables Station Data Routes via WordPress REST API. #### Enable Data Feeds -Default: On. Enable Station Data Feeds via WordPress Feed links. +Default: On. Key: enable_data_feeds +Enable Station Data Feeds via WordPress Feed links. +#### Ping Netmix Directory +Default: On. Key: ping_netmix_directory + +#### Clear Transients +Default: Off. Key: clear_transients + +#### [Pro] Transient Caching +Default: On. transient_caching +Use Transient Caching to improve Schedule calculation performance. #### [Pro] Show Shift Feeds -Default: On. Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts. +Default: On. Key: show_shift_feeds +Convert RSS Feeds for a single Show to a Show shift feed, allowing a visitor to subscribe to a Show feed to be notified of Show shifts. -#### [Pro] Transient Caching -Default: On. Use Transient Caching to improve Schedule calculation performance. +## Player @@ -51,66 +115,98 @@ Default: On. Use Transient Caching to improve Schedule calculation performance. ### Master Schedule #### Master Schedule Page -Default: None. Select the Page you are displaying the Master Schedule on. - +Default: None. Key: schedule_page +Select the Page you are displaying the Master Schedule on. #### Automatic Schedule Display -Default: Yes. Replaces selected page content with Master Schedule. +Default: On. Key: schedule_auto +Replaces selected page content with Master Schedule. Alternatively customize with the shortcode: `[master-schedule]` See [Master Schedule Shortcode](./Shortcodes.md#master-schedule) for more info. #### Schedule View Default -Default: Table. View type to use for automatic display on Master Schedule Page. +Default: Table. Key: schedule_view +View type to use for automatic display on Master Schedule Page. + +#### Radio Clock +Default: On. Key: schedule_clock +Whether to enable the display of the Radio/User Times clock on the automatic Master Schedule Page. #### [Pro] Schedule View Switcher -Default: On. Enable View Switching on the Master Schedule. +Default: On. Key: schedule_switcher +Enable View Switching on the automatic Master Schedule Page. + +#### [Pro] Available Views +Default: table, tabs. Key: schedule_views +Which Views to enable for switching on the automatic Master Schedule Page. ### Show Pages #### Show Info Blocks Position -Default: Left. Where to position Show info blocks relative to Show Page content. +Default: Left. Key: show_block_position +Where to position Show info blocks relative to Show Page content. #### Show Content Layout -Default: Tabbed. How to display extra sections below Show description. In content tabs or standard layout down the page. - +Default: Tabbed. Key: show_section_layout +How to display extra sections below Show description. In content tabs or standard layout down the page. #### Show Content Header Image -Default: Off. If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead. +Default: Off. Key: show_header_image +If your chosen template does not display the Featured Image, enable this and use the Content Header Image box on the Show edit screen instead. #### Show Posts Per Page -Default: 10. Linked Show Posts per page on the Show Page tab/display. +Default: 10. Key: show_posts_per_page +Linked Show Posts per page on the Show Page tab/display. #### Show Playlists per Page -Default: 10. Playlists per page on the Show Page tab/display. +Default: 10. Key: show_playlists_per_page +Playlists per page on the Show Page tab/display. #### [Pro] Show Episodes per Page -Default: 10. Number of Show Episodes per page on the Show page tab/display. +Default: 10. Key: show_episodes_per_page +Number of Show Episodes per page on the Show page tab/display. ### Archives #### Show Archives Page -Default: None. Select the Page for displaying the Show archive list. +Default: None. Key: show_archive_page +Select the Page for displaying the Show archive list. #### Show Archives Automatic Display -Default: On. Replaces selected page content with default Show Archive. +Default: On. Key: show_archive_auto +Replaces selected page content with default Show Archive. Alternatively customize display using the shortcode: `[shows-archive]` See [Show Archives Shortcode](./Shortcodes.md#show-archives-shortcode) for more info. +#### Override Archives Page +Default: None. Key: override_archive_page +Select the Page for displaying the Override archive list. + +#### Override Archives Automatic Display +Default: On. Key: override_archive_auto +Replaces selected page content with default Override Archive. +Alternatively customize display using the shortcode: `[overrides-archive]` +See [Show Archives Shortcode](./Shortcodes.md#overrides-archives-shortcode) for more info. + #### Playlist Archives Page -Default: None. Select the Page for displaying the Playlist archive list. +Default: None. Key: playlist_archive_page +Select the Page for displaying the Playlist archive list. #### Playlist Archives Automatic Display -Default: On. Replaces selected page content with default Playlist Archive. +Default: On. Key: playlist_archive_auto +Replaces selected page content with default Playlist Archive. Alternatively customize display using the shortcode: `[playlists-archive]` See [Playlist Archives Shortcode](./Shortcodes.md#playlist-archives-shortcode) for more info. #### Genre Archives Page -Default: None. Select the Page for displaying the Genre archive list. +Default: None. Key: genre_archive_page +Select the Page for displaying the Genre archive list. #### Genre Archives Automatic Display -Default: On. Replaces selected page content with default Genre Archive. +Default: On. Key: genre_archive_auto +Replaces selected page content with default Genre Archive. Alternatively customize display using the shortcode: `[genres-archive]` See [Genre Archives Shortcode](./Shortcodes.md#genre-archives-shortcode) for more info. @@ -123,39 +219,51 @@ See [Templates](./Display.md#page-templates) for more info. ### Single Templates #### Show Template -Default: page.php. Which template to use for displaying Show content. +Default: page.php. Key: show_template +Which template to use for displaying Show and Override content. #### Combines Show Template Method -Default: Off. Advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.) +Default: Off. Key: show_template_combined +For advanced usage. Use both a custom template AND content filtering for a Show. (Not compatible with Legacy templates.) #### Playlist Template -Default: page.php. Which template to use for displaying Playlist content. +Default: page.php. Key: playlist_template +Which template to use for displaying Playlist content. #### Combined Playlist Template Method -Default: Off. Advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.) +Default: Off. Key: playlist_template_combined +For advanced usage. Use both a custom template AND content filtering for a Playlist. (Not compatible with Legacy templates.) + +## Widgets + +#### AJAX Loading +Default: On. Key: ajax_widgets +Whether to load Widget contents via AJAX by default. This prevents stale cached displays of Current/Upcoming Shows etc. Note this can be changed on a per widget basis. + +#### [Pro] Dynamic Reloading +Default: On. Key: dynamic_reload +Whether to reload Widgets automatically on change of Current Show. Can also be set on a per widget basis. ## Roles Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types. -You can assign this Role to any user to give them full Station Schedule updating permissions. +You can assign this Role to any user to give them full Station Schedule updating permissions without giving them a WordPress administrator role. See [Roles](./Roles.md#show-editor-role] for more info. ### Permissions #### Add to Author Role Capabilities -Default: On. Allow users with WordPress Author role to publish and edit their own Shows and Playlists. +Default: On. Key: add_author_capabilities +Allow users with the WordPress Author role to publish and edit their own Shows and Playlists. #### Add to Editor Role Capabilities -Default: On. Allow users with WordPress Editor role to edit all Radio Station post types. +Default: On. Key: add_editor_capabilities +Allow users with the WordPress Editor role to edit all Radio Station post types. ### Role Editing #### [Pro] Role Editor Interface -Allows you to assign any of the Radio Station plugin Roles directly to any user. - - -## Setting Value Filters +Allows you to assign any of the Radio Station plugin Roles directly to any user. For more information see [Roles Documentation](./Roles.md#role-editing) -For custom flexibility, all Plugin Settings can also be filtered via their respective option key. Use `add_filter` to add a filter to `radio_station_{settings_key}`, then check your desired conditions to modify the value before returning it. eg: diff --git a/docs/Pro.md b/docs/Pro.md new file mode 100644 index 0000000..56c3e9a --- /dev/null +++ b/docs/Pro.md @@ -0,0 +1,48 @@ +# Radio Station Pro Documentation + +*** + +Pro Feature Documentation is included where relevant within the existing Free version documentation, marked with `[Pro]`. + +But in order to find the Pro feature documentation more easily, an index is provided here linking to each of those sections. + +Note that additional Pro Documentation will be added as new Features are released into the Pro version. + + +#### API +[Episodes Data Endpoint](./API.md#pro-episodes-endpoint) +[Hosts Data Endpoint](./API.md#pro-hosts-endpoint) +[Producers Data Endpoint](./API.md#pro-producers-endpoint) + +#### Data +[Show Episodes](./Data.md#pro-show-episodes) +[Host and Producer Profiles](./Data.md#pro-host-and-producer-profiles) + +#### Display +[Profile Images](./Display.md#pro-profile-images) +[Genre Images and Colors](./Display.md#genre-images-and-colors) + +#### Filters +[Pro Filters List](./Filters.md#pro-pro-filter-list) + +#### Manage +[Visual Shift Editor)(./Manage.md#pro-visual-shift-editor) + +#### Options +[Pro Options](./Options.md) (throughout list) + +#### Roles +[Role Editor Interface](./Roles.md#pro-role-editor-interface) + +#### Shortcodes +[Schedule Grid View](./Shortcodes.md#master-schedule-shortcode) +[Schedule Calendar View](./Shortcodes.md#master-schedule-shortcode) +[Multiple View Switching](./Shortcodes.md#pro-multiple-view-switching) +[Show Episodes Archive Shortcode](./Shortcodes.md#pro-show-episodes-archive-shortcode + +#### Widgets +[Timezone Switcher](./Widgets.md#pro-timezone-switcher) + + + + diff --git a/docs/Roadmap.md b/docs/Roadmap.md index 4428ede..a991859 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -3,9 +3,11 @@ *** -Current Release Version: 2.3.1 (released 14th May 2020) +Current Major Release Version: 2.3.3 -Upcoming Release Version: 2.3.2 +Upcoming Major Release Version: 2.3.4 + +For minor release notes, see the [Changelog](./CHANGELOG.md) Normal = Feature Completed @@ -16,21 +18,21 @@ Normal = Feature Completed | Free | Version | Pro | Version | | --- | --- | --- | --- | | Scheduler with Conflict Checker | 2.3.0 | *with Visual Schedule Editor* | 2.3.5 | -| Master Schedule Views | 2.2.8 | with View Switching | 2.3.2 | -| Show Page Display Template | 2.3.0 | *with Social Icons* | 2.3.4 | +| Master Schedule Views | 2.2.8 | with Grid View and View Switching | 2.3.4 | +| Show Page Display Template | 2.3.0 | *with Social Icons* | 2.3.5 | | Show Genre Assignments | 2.2.8 | with Genre Term Image Support | 2.3.0 | | Host / Producer / Show Editor Roles | 2.3.0 | with Role Assignment Interface | 2.3.0 | -| **Streaming Widget Player** | 2.3.3 | **with Sticky Sitewide Player** | 2.3.3 | +| **Streaming Widget Player** | 2.3.4 | **with Sitewide Persistant Player** | 2.3.3 | | Current and Upcoming Show Widgets | 2.2.8 | - | - | -| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.3 | -| Playlist Widget | 2.2.8 | *with Track Affiliate Links* | 2.3.4 | +| Show Countdown Timer | 2.3.0 | *with Dynamic Reloading* | 2.3.4 | +| Playlist Widget | 2.2.8 | *with Track Affiliate Links* | 2.3.5 | | Show Posts and Playlists | 2.2.8 | *with Show Episodes Post Type* | 2.3.4 | | Post Type Archive Shortcodes | 2.3.0 | *with Grid View* | 2.3.4 | -| Show Hosts and Producers | 2.2.8 | *with Profile Page Templates* | 2.3.4 | +| Show Hosts and Producers | 2.2.8 | *with Guests and Profile Page Templates* | 2.3.4 | | REST/Feed API Endpoints | 2.3.0 | *with Episodes, Hosts and Producers* | 2.3.4 | -| Radio Clock and Widget | 2.3.2 | *with Timezone Switcher* | 2.3.3 | +| Radio Clock and Widget | 2.3.2 | *with Timezone Switcher* | 2.3.4 | | Schedule Caching | 2.3.0 | with Show Meta Caching | 2.3.0 | - +| Standard HTML Markup | 2.3.0 | with Schema.Org Markup | 2.3.4 | [comment]: # (Show and Override Feeds) -Interested in "going pro"? [Find out more about Radio Station Pro](https://netmix.com/radio-station-pro/). +Interested in "going pro"? [Find out more about Radio Station Pro](https://radiostation.pro/). diff --git a/docs/Roles.md b/docs/Roles.md index 2d172ae..72c4ec0 100644 --- a/docs/Roles.md +++ b/docs/Roles.md @@ -9,29 +9,35 @@ For the free version, you can also use the [Multiple Roles Plugin](https://wordp ### Plugin Roles #### Show Host Role + Previously labelled DJ role, users with this Role can be assigned to a Show and are displayed as Show Hosts on the Show page, as well as in other widgets and shortcodes if the option to display them is set. Hosts assigned to a Show are then able to Edit that Show as well. #### Show Producer Role + Similar to the Show Host role, users with this Role can be assigned to a Show and are displayed on the Show page, as well as in other widgets and shortcodes if the option to display them is set. Producers assigned to a Show are then able to Edit that Show as well. #### Show Editor Role + Since 2.3.0, a new Show Editor role has been added with Publish and Edit capabilities for all Radio Station Post Types. You can assign this Role to any user to give them full Station Schedule updating permissions, without them needing to be a site Administrator. ### WordPress Role Capabilities #### Author Role Capabilities + There is a Plugin Setting which if enabled, allows users with the in-built WordPress Author role to publish and edit their own Shows and Playlists. #### Editor Role Capabilities + There is a Plugin Setting which if enabled, allows users with the in-built WordPress Editor role to edit all Radio Station post types. #### Administrator Role Capabilities + Users with Administrator role are automatically given all permissions to edit all Radio Station post types. ### Role Editing #### [Pro] Role Editor Interface -Accessible from the Plugin Settings page under the Roles tab. -Allows you to assign any of the Radio Station plugin Roles directly to any user. + +[Radio Station Pro](https://radiostation.pro) includes a Role Editor Interface that is accessible directly from the Plugin Settings page under the Roles tab. This allows you to assign any of the Radio Station plugin Roles easily to any existing user. diff --git a/docs/Shortcodes.md b/docs/Shortcodes.md index d0bc15d..7c6d3bb 100644 --- a/docs/Shortcodes.md +++ b/docs/Shortcodes.md @@ -14,17 +14,21 @@ Shortcode Output Examples can be seen on the [Radio Station Demo Site](http://ra Use the shortcode `[master-schedule]` on any page. This will generate a full-page schedule in one of five Views: -* Table (default) - responsive program grid in table form -* Tabbed - responsive styled list view with day selection tabs -* List - unstyled plain list view for custom development use -* Divs - (display issues) legacy unstyled div based view -* Legacy - (deprecated) legacy table grid view +* [Table](https://radiostationdemo.com/master-schedule/table-view/) (default) - responsive program in table form +* [Tabbed](https://radiostationdemo.com/master-schedule/tabbed-view/) - responsive styled list view with day selection tabs +* [List](https://radiostationdemo.com/master-schedule/list-view/) - unstyled plain list view for custom development use +* [Divs](https://radiostationdemo.com/master-schedule/divs-view/) - (deprecated - display issues) legacy unstyled div-based view +* [Legacy](https://radiostationdemo.com/master-schedule/legacy-view/) - (deprecated) legacy table view +* [Grid](https://radiostationdemo.com/master-schedule/grid-view/) - [Pro] extra grid style view available in Pro version +* [Calendar](https://radiostationdemo.com/master-schedule/calendar-view/) - [Pro] extra calendar style view available in Pro version -Note that Divs and Legacy do not honour Schedule Overrides, but have been kept for backwards compatibility. The legacy Divs view also has display issues but may be rewritten in future. +The above View names are linked to examples on the Demo Site. + +Note that Divs and Legacy Views are considered deprecated as they do not honour Schedule Overrides, but have been kept for backwards compatibility. The legacy Divs view also has display issues. The following attributes are available for the shortcode: -* *view* : Which View to use for display output. 'table', 'tabbed', 'list', 'divs', 'legacy'. Default 'table'. +* *view* : Which View to use for display output. 'table', 'tabs', 'list', 'divs', 'legacy', 'grid', 'calendar'. Default 'table'. * *time* : Display time format you with to use. 12 and 24. Default is the Plugin Setting. * *clock* : Display Radio Clock above schedule. 0 or 1. Default is the Plugin Setting. * *show_link* : Display the title of the show as a link to its profile page. 0 or 1. Default 1. @@ -44,12 +48,17 @@ The following attributes are available for the shortcode: * *image_position* : Show image position for Tabs view. 'right', 'left' or 'alternate'. Default 'left'. * *hide_past_shows* : Hide shows that are finished in the Schedule for Tabs view. 0 or 1. Default 0. * *divheight* : Set the height, in pixels, of the individual divs. For legacy 'divs' view only. Default 45. +* *gridheight* : Set the width, in pixels, of the grid columns. For Pro 'grid' view only. Default 150. +* *weeks* : Number of weeks to display in calendar. For Pro 'calendar' view only. Default 4. +* *previous_weeks* : Number of past weeks to display in calendar. For Pro 'calendar' view only. Default 1. Example: Display the schedule in 24-hour time format, use `[master-schedule time="24"]`. -##### [Pro] Multi-View Switching +##### [Pro] Multiple View Switching + +In [Radio Station Pro](https://radiostation.pro), you can display multiple views which the user can switch between. This gives your visitors better access to your schedule in the way that is better suited to them. You can set the available views in the plugin settings for the automatic schedule page as well as the default view. Alternatively you can use the Master Schedule Shortcode as normal on a page and provide a comma-separated list of views (the first value provided will be used as the default view.) eg. `[master-schedule view="table,tabs,grid"]` -In Radio Station Pro, you can display multiple views which the user can switch between. This gives your visitors better access to your schedule in the way that is better suited to them. You can set the available views in the plugin settings for the automatic schedule page as well as the default view. Alternatively you can use the Master Schedule Shortcode as normal on a page and provide a comma-separated list of views (the first value provided will be used as the default view.) eg. `[master-schedule view="table,tabs"]` +The default view can also be set via the plugin settings page, or by adding a `default_view` shortcode attribute if you wish to override this setting in the shortcode. Further, if you wish to customize the attributes for each particular view, you can use the `radio_station_pro_multiview_attributes` filter. By adding a view prefix to the attribute you wish to change, this will override the attribute for that view. eg. To display the full day heading in Tabs view, but short day headings in Table view: @@ -60,7 +69,9 @@ function my_custom_multiview_attributes( $atts ) { $atts['tabs_display_day'] = 'long'; return $atts; } -`` +``` + +[Demo Site Example Output](https://radiostationdemo.com/master-schedule/multiple-view-switching/) #### Radio Timezone Shortcode @@ -69,6 +80,7 @@ function my_custom_multiview_attributes( $atts ) { Displays the Radio Station Timezone selected via Plugin Settings. There are no attributes for this shortcode. This is the default display above the Master Schedule when the Radio Clock is turned off in the Plugin Settings. + #### Radio Clock Shortcode `[radio-clock]` @@ -182,7 +194,24 @@ The following attributes are available for this shortcode: `[languages-archive]` (or `[language-archive]`) -This shortcode will be available in a future version, similar to the Genre archive shortcode. +The following attributes are available for this shortcode: + +* *languages* : Genres to display (ID or slug). Separate multiple values with commas. Default empty (all) +* *link_languages* : Link Genre titles to term pages. 0 or 1. Default 1. +* *language_desc' : Display Genre term description. 0 or 1. Default 1. +* *hide_empty' : No output if no records to display for Genre. 0 or 1. Default 1. +* *status* : Query for Show status. Default 'publish'. +* *perpage* : Query for number of Shows. Default -1 (all) +* *offset* : Query for Show offset. Default '' (no offset) +* *orderby* : Query to order Show display by. Default 'title'. +* *order* : Query order for Shows. Default 'ASC'. +* *with_shifts* : Only display Shows with active Shifts. 0 or 1. Default 0. +* *show_avatars* : Display the Show Avatar. 0 or 1. Default 1. +* *thumbnails* : Display Show Featured image if no Show Avatar. 0 or 1. Default 0. +* *avatar_width* : * *avatar_width* : Set a width style in pixels for Show Avatars. Default is 75. +* *show_desc* : Display Show Descriptions. 'none', 'full' or 'excerpt'. Default 'none'. + +[Demo Site Example Output](https://radiostationdemo.com/archive-shortcodes/languages-archive/) ### Show Posts Archive Shortcode @@ -215,7 +244,7 @@ The following attributes are available for this shortcode: `[show-episodes-archive]` (or `[show-episode-archive]`) -This shortcode will be available in a future version of Radio Station Pro. +This shortcode will be available in [Radio Station Pro](https://radiostation.pro) ## Widget Shortcodes diff --git a/docs/Widgets.md b/docs/Widgets.md index bf75c5f..d2f397d 100644 --- a/docs/Widgets.md +++ b/docs/Widgets.md @@ -37,10 +37,10 @@ Example: `[current-show title="Now On-Air" show_avatar="1" show_link="1" show_sc [Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-show-widget/) + ## Upcoming Shows Widget Displays a limited list of Upcoming Shows - if there are Shows scheduled. - ### Upcoming Shows Shortcode `[upcoming-shows]` (legacy supported name: `[dj-coming-up-widget]`) @@ -64,6 +64,7 @@ Example: `[upcoming-shows title="Coming Up On-Air" show_avatar="1" show_link="1" [Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/upcoming-shows-widget/) + ## Current Playlist Widget Displays the Playlist assigned to the currently playing Show - if there is one assigned to it. @@ -85,17 +86,28 @@ Example: `[current-playlist title="Current Song" artist="1" song="1" album="1" l [Demo Site Example Output](https://radiostationdemo.com/extra-shortcodes/current-playlist-widget/) + ## Radio Clock Widget -`[radio-clock]` (see [Radio Clock Shortcode](./Shortcodes.md#radio-clock-widget) +`[radio-clock]` (see [Radio Clock Shortcode](./Shortcodes.md#radio-clock-shortcode)) As of 2.3.2, Radio Station now includes a Radio Clock Widget which will display the current Radio Station time in your selected Radio Station Timezone (via the Plugin Settings page) alongside the site visitor's current time (via browser detection.) ### [Pro] Timezone Switcher -A future version of Radio Station Pro will include a user Timezone Switcher in the which will allow your listener's to select a timezone and display adjusted Schedule and Show times. +[Radio Station Pro](https://radiostation.pro) will include a user Timezone Switcher in the which will allow your listener's to select a timezone and display adjusted Schedule and Show times. + -## Streaming Player Widget +## Radio Player Widget A future version of Radio Station will include a Streaming Player Widget. -### [Pro] Sitewide Player -A future version of Radio Station Pro will include a Sitewide Streaming Player. +### Radio Player Shortcode +`[radio-player]` (see [Radio Player Shortcode](./Shortcodes.md#radio-player-shortcode)) + +The following attributes are available for this shortcode: + + + + + +### [Pro] Sitewide Bar Player +Radio Station Pro includes a Sitewide Bar Streaming Player. It isn't added via the Widgets Page, but uses the Radio Player Shortcode with Settings from the Plugin Settings Page. diff --git a/docs/index.md b/docs/index.md index 4fe6a1a..4d6aa8a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,16 +7,17 @@ | --- | --- | | [FAQ](./FAQ.md) | Frequently Asked Questions | | [Options](./Options.md) | Plugin Option Details and Value Filters | -| [Display](./Display.md) | Automatic Pages, Templates and Images | +| [Display](./Display.md) | Automatic Pages, Templates, Images and Translations | | [Manage](./Manage.md) | Admin Lists, Shift Editing, Conflict Checker | | [Roles](./Roles.md) | Plugin Roles and related Capabilities | | [Widgets](./Widgets.md) | Current Show, Upcoming Shows, Playlist, Clock, Player | | [Shortcodes](./Shortcodes.md) | Data List Archives and Master Schedule Views | -| [Data](./Data.md) | Post Types and Taxonomies | +| [Data](./Data.md) | Post Types, Taxonomies and Translations | | [Filters](./Filters.md) | Custom Development Value Filters | | [API](./API.md) | Data API via REST and Feed Endpoints | [Roadmap](./Roadmap.md) | Feature Roadmap for Free and Pro Versions | | [Changelog](../CHANGELOG.md) | Log of Changes for each Release | +| [Pro](./Pro.md) | Index of Pro Feature Documenation | ### Quickstart Guide @@ -35,7 +36,7 @@ Then there are also a number of other [Shortcodes](./Shortcodes.md) you can use Radio Station has several in-built [Data](./Data.md) types. These include [Custom Post Types](./Data.md#custom-post-types) for Shows, Schedule Overrides and Playlists. There are [Taxonomies](./Data.md#taxonomies) for Genres and Languages. You can override most data values and display output via [Custom Filters](./Filters.md) throughout the plugin. We have also incorporated an [API](./API.md) in the plugin via REST and/or WordPress Feeds, and this data is accessible in JSON format. -This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://netmix.com/radio-station-pro/). Check out the [Roadmap](./Roadmap.md) if you are interested in seeing what is coming up next! +This plugin is under active development and we are continuously working to enhance the Free version available on [WordPress.Org](https://wordpress.org/plugins/radio-station/), as well as creating new feature additions for [Radio Station Pro](https://radiostation.pro/). Check out the [Roadmap](./Roadmap.md) if you are interested in seeing what is coming up next! #### Plugin Support and Contributing @@ -44,3 +45,7 @@ If you are wanting to Submit a Bug or Feature Request, you can do so via the [Wo Similarly, you can Contribute directly to the plugin via submitting an Issue or Pull Request on the [Github Plugin Repository](https://github.com/netmix/radio-station/). Or if you would prefer to get involved in the plugin's development even more substantially, please [Contact Us via Email](mailto:info@netmix.com) and let us know what you would like to do. + +#### [Pro] Professional Version Documentation + +For ease of reference, documentation of features that are included in [Radio Station Pro](https://radiostation.pro) are included within the Free Documentation here, simply marked with `[Pro]` like the heading above. For a list of all these features linked to their revelant sections see the [Pro Feature Index](./Pro.md) \ No newline at end of file diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css index a2dd879..d96aa2f 100644 --- a/freemius/assets/css/admin/common.css +++ b/freemius/assets/css/admin/common.css @@ -1,2 +1,2 @@ .fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,0.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,0.1),inset 0 1px 3px 0 rgba(0,0,0,0.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,0.5);z-index:999;-moz-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}.fs-switch-feedback{margin-left:10px}.fs-switch-feedback.success{color:#71ae00}.rtl .fs-switch-feedback{margin-left:0;margin-right:10px}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media (max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}} -.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 500px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} +.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/assets/css/admin/connect.css b/freemius/assets/css/admin/connect.css index 43ac76c..dff7c49 100644 --- a/freemius/assets/css/admin/connect.css +++ b/freemius/assets/css/admin/connect.css @@ -1 +1 @@ -#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions .fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li .fs-switch{float:right}#fs_connect .fs-permissions ul li .fs-permission-description{margin-left:55px}#fs_connect .fs-permissions ul li .fs-permission-description span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:0;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li .fs-switch{float:left}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa} +#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions>.fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li>i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li .fs-switch{float:right}#fs_connect .fs-permissions ul li .fs-permission-description{margin-left:55px}#fs_connect .fs-permissions ul li .fs-permission-description span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff !important;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li .fs-switch{float:left}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;text-align:center;font-size:0.9em;margin-top:10px} diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index 0536aaf..f3b3428 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -384,6 +384,13 @@ class Freemius extends Freemius_Abstract { * @var boolean|null */ private $_use_external_pricing = null; + /** + * @author Leo Fajardo (@leorw) + * @since 2.4.2 + * + * @var string|null + */ + private $_pricing_js_path = null; #endregion @@ -5484,7 +5491,7 @@ private function reconnect_locally( $is_context_single_site = false ) { function is_extensions_tracking_allowed() { return ( true === $this->apply_filters( 'is_extensions_tracking_allowed', - $this->_storage->get( 'is_extensions_tracking_allowed', true ) + $this->_storage->get( 'is_extensions_tracking_allowed', null ) ) ); } @@ -5528,10 +5535,12 @@ function _update_tracking_permission_callback() { * @author Leo Fajardo (@leorw) * @since 2.3.2 * - * @param bool $is_enabled + * @param bool|null $is_enabled */ - private function update_extensions_tracking_flag( $is_enabled ) { - $this->_storage->store( 'is_extensions_tracking_allowed', $is_enabled ); + function update_extensions_tracking_flag( $is_enabled ) { + if ( is_bool( $is_enabled ) ) { + $this->_storage->store( 'is_extensions_tracking_allowed', $is_enabled ); + } } /** @@ -6860,8 +6869,6 @@ function _sync_cron() { */ function _sync_cron_method( array $blog_ids, $current_blog_id = null ) { if ( $this->is_registered() ) { - $this->sync_user_beta_mode(); - if ( $this->has_paid_plan() ) { // Initiate background plan sync. $this->_sync_license( true, false, $current_blog_id ); @@ -7234,7 +7241,8 @@ function _admin_init_action() { } if ( $this->is_plugin_new_install() || $this->is_only_premium() ) { - if ( ! $this->_anonymous_mode ) { + if ( ! $this->_anonymous_mode && + ( ! $this->is_addon() || ! $this->_parent->is_anonymous() ) ) { // Show notice for new plugin installations. $this->_admin_notices->add( sprintf( @@ -7285,6 +7293,10 @@ function _admin_init_action() { * @return bool */ private function should_add_sticky_optin_notice() { + if ( $this->is_addon() && $this->_parent->is_anonymous() ) { + return false; + } + if ( fs_is_network_admin() ) { if ( ! $this->_is_network_active ) { return false; @@ -13238,26 +13250,25 @@ function _set_beta_mode_ajax_handler() { self::shoot_ajax_failure(); } - $user = $this->get_api_user_scope()->call( + $site = $this->get_api_site_scope()->call( '', 'put', array( - 'plugin_id' => $this->get_id(), 'is_beta' => ( 'true' == $is_beta ), 'fields' => 'is_beta' ) ); - if ( ! $this->is_api_result_entity( $user ) ) { + if ( ! $this->is_api_result_entity( $site ) ) { self::shoot_ajax_failure( - FS_Api::is_api_error_object( $user ) ? - $user->error->message : + FS_Api::is_api_error_object( $site ) ? + $site->error->message : fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() ) ); } - $this->_user->is_beta = $user->is_beta; - $this->_store_user(); + $this->_site->is_beta = $site->is_beta; + $this->_store_site(); self::shoot_ajax_response( array( 'success' => true ) ); } @@ -13292,7 +13303,7 @@ function _activate_license_ajax_action() { fs_request_get( 'blog_id', null ), fs_request_get( 'module_id', null, 'post' ), fs_request_get( 'user_id', null ), - fs_request_get_bool( 'is_extensions_tracking_allowed', true ) + fs_request_get_bool( 'is_extensions_tracking_allowed', null ) ); if ( @@ -13482,7 +13493,31 @@ function activate_migrated_license( * @return string */ function get_pricing_js_path() { - return $this->apply_filters( 'freemius_pricing_js_path', WP_FS__DIR_INCLUDES . '/freemius-pricing/freemius-pricing.js' ); + if ( ! isset( $this->_pricing_js_path ) ) { + $pricing_js_path = $this->apply_filters( 'freemius_pricing_js_path', '' ); + + if ( empty( $pricing_js_path ) ) { + global $fs_active_plugins; + + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( $data->plugin_path == $this->get_plugin_basename() ) { + $plugin_or_theme_root_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); + + $pricing_js_path = $plugin_or_theme_root_dir + . '/' + // The basename will be `plugins`, `themes`, or the basename of a custom plugins or themes directory. + . str_replace( '../' . basename( $plugin_or_theme_root_dir ) . '/', '', $sdk_path ) + . '/includes/freemius-pricing/freemius-pricing.js'; + + break; + } + } + } + + $this->_pricing_js_path = $pricing_js_path; + } + + return $this->_pricing_js_path; } /** @@ -13527,7 +13562,7 @@ private function activate_license( $blog_id = null, $plugin_id = null, $license_owner_id = null, - $is_extensions_tracking_allowed = true + $is_extensions_tracking_allowed = null ) { $this->_logger->entrance(); @@ -16448,19 +16483,6 @@ function is_beta() { ); } - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - */ - private function sync_user_beta_mode() { - $user = $this->get_api_user_scope()->get( '/?plugin_id=' . $this->get_id() . '&fields=is_beta' ); - - if ( $this->is_api_result_entity( $user ) ) { - $this->_user->is_beta = $user->is_beta; - $this->_store_user(); - } - } - /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 @@ -17148,9 +17170,7 @@ private function install_with_new_user( $this->disable_opt_in_notice_and_lock_user(); } - if ( ! is_null( $is_extensions_tracking_allowed ) ) { - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - } + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); return $this->setup_account( $this->_user, @@ -17195,9 +17215,7 @@ private function install_many_pending_with_user( $this->disable_opt_in_notice_and_lock_user(); } - if ( ! is_null( $is_extensions_tracking_allowed ) ) { - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - } + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); $sites = array(); foreach ( $site_ids as $site_id ) { @@ -17240,9 +17258,7 @@ private function install_many_with_new_user( $this->disable_opt_in_notice_and_lock_user(); } - if ( ! is_null( $is_extensions_tracking_allowed ) ) { - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); - } + $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); $install_ids = array(); @@ -17353,7 +17369,7 @@ function _install_with_current_user() { */ $license_key = fs_request_get( 'license_secret_key' ); - $this->update_extensions_tracking_flag( fs_request_get_bool( 'is_extensions_tracking_allowed', true ) ); + $this->update_extensions_tracking_flag( fs_request_get_bool( 'is_extensions_tracking_allowed', null ) ); $this->install_with_current_user( $license_key ); } @@ -20605,6 +20621,20 @@ private function _sync_plugin_license( } } + if ( ! $this->is_addon() && + $this->_site->is_beta() !== $site->is_beta + ) { + // Beta flag updated. + $this->_site = $site; + + $this->_store_site( + true, + $is_site_level_sync ? + null : + $this->get_network_install_blog_id() + ); + } + if ( $this->is_addon() || $this->has_addons() ) { /** * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, @@ -21298,7 +21328,7 @@ private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' if ( $this->has_secret_key() ) { $endpoint = add_query_arg( 'type', 'all', $endpoint ); - } else if ( $this->is_registered() && $this->_user->is_beta() ) { + } else if ( is_object( $this->_site ) && $this->_site->is_beta() ) { $endpoint = add_query_arg( 'type', 'beta', $endpoint ); } @@ -23396,6 +23426,14 @@ function _add_tracking_links() { return; } + if ( + $this->is_addon() && + ! $this->is_only_premium() && + $this->_parent->is_anonymous() + ) { + return; + } + if ( fs_is_network_admin() ) { if ( ! $this->_is_network_active ) { // Don't add tracking links when browsing the network WP Admin and the plugin is not network active. diff --git a/freemius/includes/class-fs-logger.php b/freemius/includes/class-fs-logger.php index 90e918f..624c683 100644 --- a/freemius/includes/class-fs-logger.php +++ b/freemius/includes/class-fs-logger.php @@ -142,7 +142,7 @@ function get_file() { return $this->_file_start; } - private function _log( &$message, $type = 'log', $wrapper ) { + private function _log( &$message, $type, $wrapper = false ) { if ( ! $this->is_on() ) { return; } @@ -688,4 +688,4 @@ public static function get_logs_download_url( $filename = '' ) { } #endregion - } \ No newline at end of file + } diff --git a/freemius/includes/entities/class-fs-site.php b/freemius/includes/entities/class-fs-site.php index fd97476..984d8f9 100644 --- a/freemius/includes/entities/class-fs-site.php +++ b/freemius/includes/entities/class-fs-site.php @@ -102,6 +102,14 @@ class FS_Site extends FS_Scope_Entity { * @var bool */ public $is_uninstalled = false; + /** + * @author Edgar Melkonyan + * + * @since 2.4.2 + * + * @var bool + */ + public $is_beta; /** * @param stdClass|bool $site @@ -233,4 +241,13 @@ function is_tracking_allowed() { function is_tracking_prohibited() { return ! $this->is_tracking_allowed(); } + + /** + * @author Edgar Melkonyan + * + * @return bool + */ + function is_beta() { + return ( isset( $this->is_beta ) && true === $this->is_beta ); + } } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-user.php b/freemius/includes/entities/class-fs-user.php index 6ad4e0e..a329e87 100644 --- a/freemius/includes/entities/class-fs-user.php +++ b/freemius/includes/entities/class-fs-user.php @@ -31,13 +31,6 @@ class FS_User extends FS_Scope_Entity { */ public $is_verified; /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @var bool - */ - public $is_beta; - /** * @var string|null */ public $customer_id; @@ -63,16 +56,6 @@ function is_verified() { return ( isset( $this->is_verified ) && true === $this->is_verified ); } - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.0 - * - * @return bool - */ - function is_beta() { - return ( isset( $this->is_beta ) && true === $this->is_beta ); - } - static function get_type() { return 'user'; } diff --git a/freemius/languages/freemius-cs_CZ.mo b/freemius/languages/freemius-cs_CZ.mo index 8452f9fa9ee5001a711e7cb70df10f39ab310da2..f51ed3b6e97f323cca5034d7b26ec7ebb405c9de 100644 GIT binary patch delta 11209 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 KUV33+o&N-XB&+@a delta 10222 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 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index fd44d1b7108c9ab874f7f74afe5c37f9a09de764..420c0a4d649886c862c39da081ccbd6152dafa52 100644 GIT binary patch delta 11002 zcmd7WcXX9iy2tSsQh<;`5?ZLo5C|;^5JD4yNbe;9q{ag|DVUrSa!!PRdO(^YN{w_x z5R@(i2XLee(gYC(21TwqNK?3|ND<4Z^ZjMN-dXe4UF)tjYhC~O?B^}JJp0-23Envs zaOix1|BI-=5{v(`%Uf0*OpjJ%S@RnG%T$^#6T9&Y=3u9I%gV$zF%3f-TUJAS5-a0u z+eO%fcnt>Q35>!s*v7K_))gANxe&us8Tb)4z{I9p!yy=oc~}XHF$8CzI(!8yU

    l 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 delta 10152 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 delta 10084 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 diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo index dbcaa7ba5ec26a6e0d11cf16f46ea8ad32b2ccbb..e10ef71aa02158461e55b43881f21c6790851396 100644 GIT binary patch delta 11010 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 delta 10167 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 BZHfQ@ delta 10084 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 lS>^x4QS5pD`wB15l^_4@QUfZOXU{TE8qJ1_tM diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index 45f0a636024c3ef84a5b2cf92e064c78bd8e96b3..b4e5ee1af68ff8d0bed9f2da324728fec357a589 100644 GIT binary patch delta 10984 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 delta 10239 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 Wc9ef<|JegdPyJZtn*7sdxBmke5@baH diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index 110d188d7eaa49af8b1f25589f3ef188b2f42e9b..8eadcf8e0fc21d20cab4e551adc8376fcbffad68 100644 GIT binary patch delta 11010 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 delta 10165 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 N%V^t!5fR@<{3kdnNcaE% delta 10137 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 E0W9HPyZ`_I delta 10178 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 J_SiE5{vDC$9N+)| delta 10172 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 SQgC{>^OIk}u}mks$o~PiDiVSK diff --git a/freemius/languages/freemius-zh_CN.mo b/freemius/languages/freemius-zh_CN.mo index 7214b03438e1759ea8387048c8f17ad99085a0ca..4cc40abb8a665966c8f71ddfd6b1e62bad8cddc8 100644 GIT binary patch delta 12957 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 diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot index 0d3d49d..24d1d20 100644 --- a/freemius/languages/freemius.pot +++ b/freemius/languages/freemius.pot @@ -1,4 +1,4 @@ -# Copyright (C) 2020 freemius +# Copyright (C) 2021 freemius # This file is distributed under the same license as the freemius package. msgid "" msgstr "" @@ -16,795 +16,795 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: includes/class-freemius.php:1912, templates/account.php:910 +#: includes/class-freemius.php:1919, templates/account.php:912 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" -#: includes/class-freemius.php:1919 +#: includes/class-freemius.php:1926 msgid "Would you like to proceed with the update?" msgstr "" -#: includes/class-freemius.php:2131 +#: includes/class-freemius.php:2138 msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." msgstr "" -#: includes/class-freemius.php:2133 +#: includes/class-freemius.php:2140 msgid "Error" msgstr "" -#: includes/class-freemius.php:2533 +#: includes/class-freemius.php:2540 msgid "I found a better %s" msgstr "" -#: includes/class-freemius.php:2535 +#: includes/class-freemius.php:2542 msgid "What's the %s's name?" msgstr "" -#: includes/class-freemius.php:2541 +#: includes/class-freemius.php:2548 msgid "It's a temporary %s. I'm just debugging an issue." msgstr "" -#: includes/class-freemius.php:2543 +#: includes/class-freemius.php:2550 msgid "Deactivation" msgstr "" -#: includes/class-freemius.php:2544 +#: includes/class-freemius.php:2551 msgid "Theme Switch" msgstr "" -#: includes/class-freemius.php:2553, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 +#: includes/class-freemius.php:2560, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 msgid "Other" msgstr "" -#: includes/class-freemius.php:2561 +#: includes/class-freemius.php:2568 msgid "I no longer need the %s" msgstr "" -#: includes/class-freemius.php:2568 +#: includes/class-freemius.php:2575 msgid "I only needed the %s for a short period" msgstr "" -#: includes/class-freemius.php:2574 +#: includes/class-freemius.php:2581 msgid "The %s broke my site" msgstr "" -#: includes/class-freemius.php:2581 +#: includes/class-freemius.php:2588 msgid "The %s suddenly stopped working" msgstr "" -#: includes/class-freemius.php:2591 +#: includes/class-freemius.php:2598 msgid "I can't pay for it anymore" msgstr "" -#: includes/class-freemius.php:2593 +#: includes/class-freemius.php:2600 msgid "What price would you feel comfortable paying?" msgstr "" -#: includes/class-freemius.php:2599 +#: includes/class-freemius.php:2606 msgid "I don't like to share my information with you" msgstr "" -#: includes/class-freemius.php:2620 +#: includes/class-freemius.php:2627 msgid "The %s didn't work" msgstr "" -#: includes/class-freemius.php:2630 +#: includes/class-freemius.php:2637 msgid "I couldn't understand how to make it work" msgstr "" -#: includes/class-freemius.php:2638 +#: includes/class-freemius.php:2645 msgid "The %s is great, but I need specific feature that you don't support" msgstr "" -#: includes/class-freemius.php:2640 +#: includes/class-freemius.php:2647 msgid "What feature?" msgstr "" -#: includes/class-freemius.php:2644 +#: includes/class-freemius.php:2651 msgid "The %s is not working" msgstr "" -#: includes/class-freemius.php:2646 +#: includes/class-freemius.php:2653 msgid "Kindly share what didn't work so we can fix it for future users..." msgstr "" -#: includes/class-freemius.php:2650 +#: includes/class-freemius.php:2657 msgid "It's not what I was looking for" msgstr "" -#: includes/class-freemius.php:2652 +#: includes/class-freemius.php:2659 msgid "What you've been looking for?" msgstr "" -#: includes/class-freemius.php:2656 +#: includes/class-freemius.php:2663 msgid "The %s didn't work as expected" msgstr "" -#: includes/class-freemius.php:2658 +#: includes/class-freemius.php:2665 msgid "What did you expect?" msgstr "" -#: includes/class-freemius.php:3513, templates/debug.php:20 +#: includes/class-freemius.php:3520, templates/debug.php:20 msgid "Freemius Debug" msgstr "" -#: includes/class-freemius.php:4265 +#: includes/class-freemius.php:4272 msgid "I don't know what is cURL or how to install it, help me!" msgstr "" -#: includes/class-freemius.php:4267 +#: includes/class-freemius.php:4274 msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." msgstr "" -#: includes/class-freemius.php:4274 +#: includes/class-freemius.php:4281 msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." msgstr "" -#: includes/class-freemius.php:4379 +#: includes/class-freemius.php:4386 msgid "Yes - do your thing" msgstr "" -#: includes/class-freemius.php:4384 +#: includes/class-freemius.php:4391 msgid "No - just deactivate" msgstr "" -#: includes/class-freemius.php:4429, includes/class-freemius.php:4923, includes/class-freemius.php:6182, includes/class-freemius.php:13357, includes/class-freemius.php:14075, includes/class-freemius.php:17526, includes/class-freemius.php:17631, includes/class-freemius.php:17806, includes/class-freemius.php:20040, includes/class-freemius.php:20398, includes/class-freemius.php:20408, includes/class-freemius.php:21079, includes/class-freemius.php:21985, includes/class-freemius.php:22118, includes/class-freemius.php:22274, templates/add-ons.php:57 +#: includes/class-freemius.php:4436, includes/class-freemius.php:4930, includes/class-freemius.php:6191, includes/class-freemius.php:13368, includes/class-freemius.php:14110, includes/class-freemius.php:17542, includes/class-freemius.php:17647, includes/class-freemius.php:17822, includes/class-freemius.php:20056, includes/class-freemius.php:20414, includes/class-freemius.php:20424, includes/class-freemius.php:21109, includes/class-freemius.php:22015, includes/class-freemius.php:22148, includes/class-freemius.php:22304, templates/add-ons.php:57 msgctxt "exclamation" msgid "Oops" msgstr "" -#: includes/class-freemius.php:4498 +#: includes/class-freemius.php:4505 msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." msgstr "" -#: includes/class-freemius.php:4920 +#: includes/class-freemius.php:4927 msgctxt "addonX cannot run without pluginY" msgid "%s cannot run without %s." msgstr "" -#: includes/class-freemius.php:4921 +#: includes/class-freemius.php:4928 msgctxt "addonX cannot run..." msgid "%s cannot run without the plugin." msgstr "" -#: includes/class-freemius.php:5120, includes/class-freemius.php:5145, includes/class-freemius.php:21150 +#: includes/class-freemius.php:5127, includes/class-freemius.php:5152, includes/class-freemius.php:21180 msgid "Unexpected API error. Please contact the %s's author with the following error." msgstr "" -#: includes/class-freemius.php:5848 +#: includes/class-freemius.php:5857 msgid "Premium %s version was successfully activated." msgstr "" -#: includes/class-freemius.php:5860, includes/class-freemius.php:7762 +#: includes/class-freemius.php:5869, includes/class-freemius.php:7774 msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." msgid "W00t" msgstr "" -#: includes/class-freemius.php:5875 +#: includes/class-freemius.php:5884 msgid "You have a %s license." msgstr "" -#: includes/class-freemius.php:5879, includes/class-freemius.php:16925, includes/class-freemius.php:16936, includes/class-freemius.php:20309, includes/class-freemius.php:20659, includes/class-freemius.php:20728, includes/class-freemius.php:20900 +#: includes/class-freemius.php:5888, includes/class-freemius.php:16947, includes/class-freemius.php:16958, includes/class-freemius.php:20325, includes/class-freemius.php:20689, includes/class-freemius.php:20758, includes/class-freemius.php:20930 msgctxt "interjection expressing joy or exuberance" msgid "Yee-haw" msgstr "" -#: includes/class-freemius.php:6165 +#: includes/class-freemius.php:6174 msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." msgstr "" -#: includes/class-freemius.php:6169 +#: includes/class-freemius.php:6178 msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." msgstr "" -#: includes/class-freemius.php:6178, templates/add-ons.php:186, templates/account/partials/addon.php:381 +#: includes/class-freemius.php:6187, templates/add-ons.php:186, templates/account/partials/addon.php:381 msgid "More information about %s" msgstr "" -#: includes/class-freemius.php:6179 +#: includes/class-freemius.php:6188 msgid "Purchase License" msgstr "" -#: includes/class-freemius.php:7118, templates/connect.php:171 +#: includes/class-freemius.php:7125, templates/connect.php:171 msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." msgstr "" -#: includes/class-freemius.php:7122 +#: includes/class-freemius.php:7129 msgid "start the trial" msgstr "" -#: includes/class-freemius.php:7123, templates/connect.php:175 +#: includes/class-freemius.php:7130, templates/connect.php:175 msgid "complete the install" msgstr "" -#: includes/class-freemius.php:7241 +#: includes/class-freemius.php:7249 msgid "You are just one step away - %s" msgstr "" -#: includes/class-freemius.php:7244 +#: includes/class-freemius.php:7252 msgctxt "%s - plugin name. As complete \"PluginX\" activation now" msgid "Complete \"%s\" Activation Now" msgstr "" -#: includes/class-freemius.php:7322 +#: includes/class-freemius.php:7334 msgid "We made a few tweaks to the %s, %s" msgstr "" -#: includes/class-freemius.php:7326 +#: includes/class-freemius.php:7338 msgid "Opt in to make \"%s\" better!" msgstr "" -#: includes/class-freemius.php:7761 +#: includes/class-freemius.php:7773 msgid "The upgrade of %s was successfully completed." msgstr "" -#: includes/class-freemius.php:10243, includes/class-fs-plugin-updater.php:1099, includes/class-fs-plugin-updater.php:1294, includes/class-fs-plugin-updater.php:1301, templates/auto-installation.php:32 +#: includes/class-freemius.php:10255, includes/class-fs-plugin-updater.php:1087, includes/class-fs-plugin-updater.php:1282, includes/class-fs-plugin-updater.php:1289, templates/auto-installation.php:32 msgid "Add-On" msgstr "" -#: includes/class-freemius.php:10245, templates/account.php:392, templates/account.php:400, templates/debug.php:358, templates/debug.php:549 +#: includes/class-freemius.php:10257, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549 msgid "Plugin" msgstr "" -#: includes/class-freemius.php:10246, templates/account.php:393, templates/account.php:401, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71 +#: includes/class-freemius.php:10258, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71 msgid "Theme" msgstr "" -#: includes/class-freemius.php:13176 +#: includes/class-freemius.php:13188 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" -#: includes/class-freemius.php:13190 +#: includes/class-freemius.php:13202 msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." msgstr "" -#: includes/class-freemius.php:13195 +#: includes/class-freemius.php:13207 msgid "User Dashboard" msgstr "" -#: includes/class-freemius.php:13196 +#: includes/class-freemius.php:13208 msgid "revert it now" msgstr "" -#: includes/class-freemius.php:13255 +#: includes/class-freemius.php:13266 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" -#: includes/class-freemius.php:13328 +#: includes/class-freemius.php:13339 msgid "Invalid new user ID or email address." msgstr "" -#: includes/class-freemius.php:13358, includes/class-freemius.php:22229 +#: includes/class-freemius.php:13369, includes/class-freemius.php:22259 msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." msgstr "" -#: includes/class-freemius.php:13359, includes/class-freemius.php:22230 +#: includes/class-freemius.php:13370, includes/class-freemius.php:22260 msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." msgstr "" -#: includes/class-freemius.php:13366, includes/class-freemius.php:22237 +#: includes/class-freemius.php:13377, includes/class-freemius.php:22267 msgid "Change Ownership" msgstr "" -#: includes/class-freemius.php:13942 +#: includes/class-freemius.php:13977 msgid "Invalid site details collection." msgstr "" -#: includes/class-freemius.php:14062 +#: includes/class-freemius.php:14097 msgid "We couldn't find your email address in the system, are you sure it's the right address?" msgstr "" -#: includes/class-freemius.php:14064 +#: includes/class-freemius.php:14099 msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" msgstr "" -#: includes/class-freemius.php:14338 +#: includes/class-freemius.php:14373 msgid "Account is pending activation." msgstr "" -#: includes/class-freemius.php:14450, templates/forms/premium-versions-upgrade-handler.php:47 +#: includes/class-freemius.php:14485, templates/forms/premium-versions-upgrade-handler.php:47 msgid "Buy a license now" msgstr "" -#: includes/class-freemius.php:14462, templates/forms/premium-versions-upgrade-handler.php:46 +#: includes/class-freemius.php:14497, templates/forms/premium-versions-upgrade-handler.php:46 msgid "Renew your license now" msgstr "" -#: includes/class-freemius.php:14466 +#: includes/class-freemius.php:14501 msgid "%s to access version %s security & feature updates, and support." msgstr "" -#: includes/class-freemius.php:16907 +#: includes/class-freemius.php:16929 msgid "%s activation was successfully completed." msgstr "" -#: includes/class-freemius.php:16921 +#: includes/class-freemius.php:16943 msgid "Your account was successfully activated with the %s plan." msgstr "" -#: includes/class-freemius.php:16932, includes/class-freemius.php:20724 +#: includes/class-freemius.php:16954, includes/class-freemius.php:20754 msgid "Your trial has been successfully started." msgstr "" -#: includes/class-freemius.php:17524, includes/class-freemius.php:17629, includes/class-freemius.php:17804 +#: includes/class-freemius.php:17540, includes/class-freemius.php:17645, includes/class-freemius.php:17820 msgid "Couldn't activate %s." msgstr "" -#: includes/class-freemius.php:17525, includes/class-freemius.php:17630, includes/class-freemius.php:17805 +#: includes/class-freemius.php:17541, includes/class-freemius.php:17646, includes/class-freemius.php:17821 msgid "Please contact us with the following message:" msgstr "" -#: includes/class-freemius.php:17626, templates/forms/data-debug-mode.php:162 +#: includes/class-freemius.php:17642, templates/forms/data-debug-mode.php:162 msgid "An unknown error has occurred." msgstr "" -#: includes/class-freemius.php:18162, includes/class-freemius.php:23310 +#: includes/class-freemius.php:18178, includes/class-freemius.php:23340 msgid "Upgrade" msgstr "" -#: includes/class-freemius.php:18168 +#: includes/class-freemius.php:18184 msgid "Start Trial" msgstr "" -#: includes/class-freemius.php:18170 +#: includes/class-freemius.php:18186 msgid "Pricing" msgstr "" -#: includes/class-freemius.php:18250, includes/class-freemius.php:18252 +#: includes/class-freemius.php:18266, includes/class-freemius.php:18268 msgid "Affiliation" msgstr "" -#: includes/class-freemius.php:18280, includes/class-freemius.php:18282, templates/account.php:240, templates/debug.php:324 +#: includes/class-freemius.php:18296, includes/class-freemius.php:18298, templates/account.php:242, templates/debug.php:324 msgid "Account" msgstr "" -#: includes/class-freemius.php:18296, includes/class-freemius.php:18298, includes/customizer/class-fs-customizer-support-section.php:60 +#: includes/class-freemius.php:18312, includes/class-freemius.php:18314, includes/customizer/class-fs-customizer-support-section.php:60 msgid "Contact Us" msgstr "" -#: includes/class-freemius.php:18309, includes/class-freemius.php:18311, includes/class-freemius.php:23324, templates/account.php:119, templates/account/partials/addon.php:44 +#: includes/class-freemius.php:18325, includes/class-freemius.php:18327, includes/class-freemius.php:23354, templates/account.php:121, templates/account/partials/addon.php:44 msgid "Add-Ons" msgstr "" -#: includes/class-freemius.php:18345 +#: includes/class-freemius.php:18361 msgctxt "ASCII arrow left icon" msgid "←" msgstr "" -#: includes/class-freemius.php:18345 +#: includes/class-freemius.php:18361 msgctxt "ASCII arrow right icon" msgid "➤" msgstr "" -#: includes/class-freemius.php:18347, templates/pricing.php:109 +#: includes/class-freemius.php:18363, templates/pricing.php:109 msgctxt "noun" msgid "Pricing" msgstr "" -#: includes/class-freemius.php:18560, includes/customizer/class-fs-customizer-support-section.php:67 +#: includes/class-freemius.php:18576, includes/customizer/class-fs-customizer-support-section.php:67 msgid "Support Forum" msgstr "" -#: includes/class-freemius.php:19534 +#: includes/class-freemius.php:19550 msgid "Your email has been successfully verified - you are AWESOME!" msgstr "" -#: includes/class-freemius.php:19535 +#: includes/class-freemius.php:19551 msgctxt "a positive response" msgid "Right on" msgstr "" -#: includes/class-freemius.php:20041 +#: includes/class-freemius.php:20057 msgid "seems like the key you entered doesn't match our records." msgstr "" -#: includes/class-freemius.php:20065 +#: includes/class-freemius.php:20081 msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." msgstr "" -#: includes/class-freemius.php:20300 +#: includes/class-freemius.php:20316 msgid "Your %s Add-on plan was successfully upgraded." msgstr "" -#: includes/class-freemius.php:20302 +#: includes/class-freemius.php:20318 msgid "%s Add-on was successfully purchased." msgstr "" -#: includes/class-freemius.php:20305 +#: includes/class-freemius.php:20321 msgid "Download the latest version" msgstr "" -#: includes/class-freemius.php:20391 +#: includes/class-freemius.php:20407 msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" msgstr "" -#: includes/class-freemius.php:20397, includes/class-freemius.php:20407, includes/class-freemius.php:20859, includes/class-freemius.php:20948 +#: includes/class-freemius.php:20413, includes/class-freemius.php:20423, includes/class-freemius.php:20889, includes/class-freemius.php:20978 msgid "Error received from the server:" msgstr "" -#: includes/class-freemius.php:20407 +#: includes/class-freemius.php:20423 msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." msgstr "" -#: includes/class-freemius.php:20621, includes/class-freemius.php:20864, includes/class-freemius.php:20919, includes/class-freemius.php:21026 +#: includes/class-freemius.php:20651, includes/class-freemius.php:20894, includes/class-freemius.php:20949, includes/class-freemius.php:21056 msgctxt "something somebody says when they are thinking about what you have just said." msgid "Hmm" msgstr "" -#: includes/class-freemius.php:20634 +#: includes/class-freemius.php:20664 msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." msgstr "" -#: includes/class-freemius.php:20635, templates/account.php:121, templates/add-ons.php:250, templates/account/partials/addon.php:46 +#: includes/class-freemius.php:20665, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46 msgctxt "trial period" msgid "Trial" msgstr "" -#: includes/class-freemius.php:20640 +#: includes/class-freemius.php:20670 msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." msgstr "" -#: includes/class-freemius.php:20644, includes/class-freemius.php:20703 +#: includes/class-freemius.php:20674, includes/class-freemius.php:20733 msgid "Please contact us here" msgstr "" -#: includes/class-freemius.php:20655 +#: includes/class-freemius.php:20685 msgid "Your plan was successfully activated." msgstr "" -#: includes/class-freemius.php:20656 +#: includes/class-freemius.php:20686 msgid "Your plan was successfully upgraded." msgstr "" -#: includes/class-freemius.php:20673 +#: includes/class-freemius.php:20703 msgid "Your plan was successfully changed to %s." msgstr "" -#: includes/class-freemius.php:20689 +#: includes/class-freemius.php:20719 msgid "Your license has expired. You can still continue using the free %s forever." msgstr "" -#: includes/class-freemius.php:20691 +#: includes/class-freemius.php:20721 msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" -#: includes/class-freemius.php:20699 +#: includes/class-freemius.php:20729 msgid "Your license has been cancelled. If you think it's a mistake, please contact support." msgstr "" -#: includes/class-freemius.php:20712 +#: includes/class-freemius.php:20742 msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." msgstr "" -#: includes/class-freemius.php:20738 +#: includes/class-freemius.php:20768 msgid "Your free trial has expired. You can still continue using all our free features." msgstr "" -#: includes/class-freemius.php:20740 +#: includes/class-freemius.php:20770 msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" -#: includes/class-freemius.php:20855 +#: includes/class-freemius.php:20885 msgid "It looks like the license could not be activated." msgstr "" -#: includes/class-freemius.php:20897 +#: includes/class-freemius.php:20927 msgid "Your license was successfully activated." msgstr "" -#: includes/class-freemius.php:20923 +#: includes/class-freemius.php:20953 msgid "It looks like your site currently doesn't have an active license." msgstr "" -#: includes/class-freemius.php:20947 +#: includes/class-freemius.php:20977 msgid "It looks like the license deactivation failed." msgstr "" -#: includes/class-freemius.php:20976 +#: includes/class-freemius.php:21006 msgid "Your %s license was successfully deactivated." msgstr "" -#: includes/class-freemius.php:20977 +#: includes/class-freemius.php:21007 msgid "Your license was successfully deactivated, you are back to the %s plan." msgstr "" -#: includes/class-freemius.php:20980 +#: includes/class-freemius.php:21010 msgid "O.K" msgstr "" -#: includes/class-freemius.php:21033 +#: includes/class-freemius.php:21063 msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." msgstr "" -#: includes/class-freemius.php:21042 +#: includes/class-freemius.php:21072 msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." msgstr "" -#: includes/class-freemius.php:21084 +#: includes/class-freemius.php:21114 msgid "You are already running the %s in a trial mode." msgstr "" -#: includes/class-freemius.php:21095 +#: includes/class-freemius.php:21125 msgid "You already utilized a trial before." msgstr "" -#: includes/class-freemius.php:21109 +#: includes/class-freemius.php:21139 msgid "Plan %s do not exist, therefore, can't start a trial." msgstr "" -#: includes/class-freemius.php:21120 +#: includes/class-freemius.php:21150 msgid "Plan %s does not support a trial period." msgstr "" -#: includes/class-freemius.php:21131 +#: includes/class-freemius.php:21161 msgid "None of the %s's plans supports a trial period." msgstr "" -#: includes/class-freemius.php:21181 +#: includes/class-freemius.php:21211 msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" msgstr "" -#: includes/class-freemius.php:21217 +#: includes/class-freemius.php:21247 msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." msgstr "" -#: includes/class-freemius.php:21236 +#: includes/class-freemius.php:21266 msgid "Your %s free trial was successfully cancelled." msgstr "" -#: includes/class-freemius.php:21552 +#: includes/class-freemius.php:21582 msgid "Version %s was released." msgstr "" -#: includes/class-freemius.php:21552 +#: includes/class-freemius.php:21582 msgid "Please download %s." msgstr "" -#: includes/class-freemius.php:21559 +#: includes/class-freemius.php:21589 msgid "the latest %s version here" msgstr "" -#: includes/class-freemius.php:21564 +#: includes/class-freemius.php:21594 msgid "New" msgstr "" -#: includes/class-freemius.php:21569 +#: includes/class-freemius.php:21599 msgid "Seems like you got the latest release." msgstr "" -#: includes/class-freemius.php:21570 +#: includes/class-freemius.php:21600 msgid "You are all good!" msgstr "" -#: includes/class-freemius.php:21873 +#: includes/class-freemius.php:21903 msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." msgstr "" -#: includes/class-freemius.php:22013 +#: includes/class-freemius.php:22043 msgid "Site successfully opted in." msgstr "" -#: includes/class-freemius.php:22014, includes/class-freemius.php:23020 +#: includes/class-freemius.php:22044, includes/class-freemius.php:23050 msgid "Awesome" msgstr "" -#: includes/class-freemius.php:22030, templates/forms/optout.php:41 +#: includes/class-freemius.php:22060, templates/forms/optout.php:41 msgid "We appreciate your help in making the %s better by letting us track some usage data." msgstr "" -#: includes/class-freemius.php:22031 +#: includes/class-freemius.php:22061 msgid "Thank you!" msgstr "" -#: includes/class-freemius.php:22038 +#: includes/class-freemius.php:22068 msgid "We will no longer be sending any usage data of %s on %s to %s." msgstr "" -#: includes/class-freemius.php:22196 +#: includes/class-freemius.php:22226 msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." msgstr "" -#: includes/class-freemius.php:22202 +#: includes/class-freemius.php:22232 msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." msgstr "" -#: includes/class-freemius.php:22207 +#: includes/class-freemius.php:22237 msgid "%s is the new owner of the account." msgstr "" -#: includes/class-freemius.php:22209 +#: includes/class-freemius.php:22239 msgctxt "as congratulations" msgid "Congrats" msgstr "" -#: includes/class-freemius.php:22245 +#: includes/class-freemius.php:22275 msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." msgstr "" -#: includes/class-freemius.php:22257 +#: includes/class-freemius.php:22287 msgid "Please provide your full name." msgstr "" -#: includes/class-freemius.php:22262 +#: includes/class-freemius.php:22292 msgid "Your name was successfully updated." msgstr "" -#: includes/class-freemius.php:22323 +#: includes/class-freemius.php:22353 msgid "You have successfully updated your %s." msgstr "" -#: includes/class-freemius.php:22382 +#: includes/class-freemius.php:22412 msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." msgstr "" -#: includes/class-freemius.php:22385 +#: includes/class-freemius.php:22415 msgid "Click here" msgstr "" -#: includes/class-freemius.php:22483 +#: includes/class-freemius.php:22513 msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." msgstr "" -#: includes/class-freemius.php:22484 +#: includes/class-freemius.php:22514 msgctxt "advance notice of something that will need attention." msgid "Heads up" msgstr "" -#: includes/class-freemius.php:23060 +#: includes/class-freemius.php:23090 msgctxt "exclamation" msgid "Hey" msgstr "" -#: includes/class-freemius.php:23060 +#: includes/class-freemius.php:23090 msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." msgstr "" -#: includes/class-freemius.php:23068 +#: includes/class-freemius.php:23098 msgid "No commitment for %s days - cancel anytime!" msgstr "" -#: includes/class-freemius.php:23069 +#: includes/class-freemius.php:23099 msgid "No credit card required" msgstr "" -#: includes/class-freemius.php:23076, templates/forms/trial-start.php:53 +#: includes/class-freemius.php:23106, templates/forms/trial-start.php:53 msgctxt "call to action" msgid "Start free trial" msgstr "" -#: includes/class-freemius.php:23153 +#: includes/class-freemius.php:23183 msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" msgstr "" -#: includes/class-freemius.php:23162 +#: includes/class-freemius.php:23192 msgid "Learn more" msgstr "" -#: includes/class-freemius.php:23348, templates/account.php:556, templates/account.php:706, templates/connect.php:179, templates/connect.php:456, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321 +#: includes/class-freemius.php:23378, templates/account.php:558, templates/account.php:708, templates/connect.php:179, templates/connect.php:461, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321 msgid "Activate License" msgstr "" -#: includes/class-freemius.php:23349, templates/account.php:650, templates/account.php:705, templates/account/partials/addon.php:322, templates/account/partials/site.php:271 +#: includes/class-freemius.php:23379, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271 msgid "Change License" msgstr "" -#: includes/class-freemius.php:23462, templates/account/partials/site.php:169 +#: includes/class-freemius.php:23500, templates/account/partials/site.php:169 msgid "Opt Out" msgstr "" -#: includes/class-freemius.php:23464, includes/class-freemius.php:23470, templates/account/partials/site.php:49, templates/account/partials/site.php:169 +#: includes/class-freemius.php:23502, includes/class-freemius.php:23508, templates/account/partials/site.php:49, templates/account/partials/site.php:169 msgid "Opt In" msgstr "" -#: includes/class-freemius.php:23700 +#: includes/class-freemius.php:23738 msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" msgstr "" -#: includes/class-freemius.php:23708 +#: includes/class-freemius.php:23746 msgid "Activate %s features" msgstr "" -#: includes/class-freemius.php:23721 +#: includes/class-freemius.php:23759 msgid "Please follow these steps to complete the upgrade" msgstr "" -#: includes/class-freemius.php:23725 +#: includes/class-freemius.php:23763 msgid "Download the latest %s version" msgstr "" -#: includes/class-freemius.php:23729 +#: includes/class-freemius.php:23767 msgid "Upload and activate the downloaded version" msgstr "" -#: includes/class-freemius.php:23731 +#: includes/class-freemius.php:23769 msgid "How to upload and activate?" msgstr "" -#: includes/class-freemius.php:23865 +#: includes/class-freemius.php:23903 msgid "%sClick here%s to choose the sites where you'd like to activate the license on." msgstr "" -#: includes/class-freemius.php:24034 +#: includes/class-freemius.php:24072 msgid "Auto installation only works for opted-in users." msgstr "" -#: includes/class-freemius.php:24044, includes/class-freemius.php:24077, includes/class-fs-plugin-updater.php:1273, includes/class-fs-plugin-updater.php:1287 +#: includes/class-freemius.php:24082, includes/class-freemius.php:24115, includes/class-fs-plugin-updater.php:1261, includes/class-fs-plugin-updater.php:1275 msgid "Invalid module ID." msgstr "" -#: includes/class-freemius.php:24053, includes/class-fs-plugin-updater.php:1309 +#: includes/class-freemius.php:24091, includes/class-fs-plugin-updater.php:1297 msgid "Premium version already active." msgstr "" -#: includes/class-freemius.php:24060 +#: includes/class-freemius.php:24098 msgid "You do not have a valid license to access the premium version." msgstr "" -#: includes/class-freemius.php:24067 +#: includes/class-freemius.php:24105 msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." msgstr "" -#: includes/class-freemius.php:24085, includes/class-fs-plugin-updater.php:1308 +#: includes/class-freemius.php:24123, includes/class-fs-plugin-updater.php:1296 msgid "Premium add-on version already installed." msgstr "" -#: includes/class-freemius.php:24435 +#: includes/class-freemius.php:24473 msgid "View paid features" msgstr "" -#: includes/class-freemius.php:24757 +#: includes/class-freemius.php:24795 msgid "Thank you so much for using %s and its add-ons!" msgstr "" -#: includes/class-freemius.php:24758 +#: includes/class-freemius.php:24796 msgid "Thank you so much for using %s!" msgstr "" -#: includes/class-freemius.php:24764 +#: includes/class-freemius.php:24802 msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." msgstr "" -#: includes/class-freemius.php:24768 +#: includes/class-freemius.php:24806 msgid "Thank you so much for using our products!" msgstr "" -#: includes/class-freemius.php:24769 +#: includes/class-freemius.php:24807 msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." msgstr "" -#: includes/class-freemius.php:24788 +#: includes/class-freemius.php:24826 msgid "%s and its add-ons" msgstr "" -#: includes/class-freemius.php:24797 +#: includes/class-freemius.php:24835 msgid "Products" msgstr "" -#: includes/class-freemius.php:24804, templates/connect.php:280 +#: includes/class-freemius.php:24842, templates/connect.php:275 msgid "Yes" msgstr "" -#: includes/class-freemius.php:24805, templates/connect.php:281 +#: includes/class-freemius.php:24843, templates/connect.php:276 msgid "send me security & feature updates, educational content and offers." msgstr "" -#: includes/class-freemius.php:24806, templates/connect.php:286 +#: includes/class-freemius.php:24844, templates/connect.php:281 msgid "No" msgstr "" -#: includes/class-freemius.php:24808, templates/connect.php:288 +#: includes/class-freemius.php:24846, templates/connect.php:283 msgid "do %sNOT%s send me security & feature updates, educational content and offers." msgstr "" -#: includes/class-freemius.php:24818 +#: includes/class-freemius.php:24856 msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" msgstr "" -#: includes/class-freemius.php:24820, templates/connect.php:295 +#: includes/class-freemius.php:24858, templates/connect.php:290 msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" msgstr "" -#: includes/class-freemius.php:25102 +#: includes/class-freemius.php:25140 msgid "License key is empty." msgstr "" @@ -832,15 +832,15 @@ msgstr "" msgid "Important Upgrade Notice:" msgstr "" -#: includes/class-fs-plugin-updater.php:1338 +#: includes/class-fs-plugin-updater.php:1326 msgid "Installing plugin: %s" msgstr "" -#: includes/class-fs-plugin-updater.php:1379 +#: includes/class-fs-plugin-updater.php:1367 msgid "Unable to connect to the filesystem. Please confirm your credentials." msgstr "" -#: includes/class-fs-plugin-updater.php:1561 +#: includes/class-fs-plugin-updater.php:1549 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" @@ -861,7 +861,7 @@ msgstr "" msgid "Install Free Version Update Now" msgstr "" -#: includes/fs-plugin-info-dialog.php:745, templates/account.php:639 +#: includes/fs-plugin-info-dialog.php:745, templates/account.php:641 msgid "Install Update Now" msgstr "" @@ -878,7 +878,7 @@ msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:772, templates/account.php:99, templates/add-ons.php:37, templates/account/partials/addon.php:25 +#: includes/fs-plugin-info-dialog.php:772, templates/account.php:101, templates/add-ons.php:37, templates/account/partials/addon.php:25 msgctxt "as download latest version" msgid "Download Latest" msgstr "" @@ -887,11 +887,11 @@ msgstr "" msgid "Activate this add-on" msgstr "" -#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:453 +#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:458 msgid "Activate Free Version" msgstr "" -#: includes/fs-plugin-info-dialog.php:790, templates/account.php:123, templates/add-ons.php:330, templates/account/partials/addon.php:48 +#: includes/fs-plugin-info-dialog.php:790, templates/account.php:125, templates/add-ons.php:330, templates/account/partials/addon.php:48 msgid "Activate" msgstr "" @@ -1020,7 +1020,7 @@ msgstr "" msgid "Details" msgstr "" -#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:110, templates/debug.php:201, templates/debug.php:238, templates/debug.php:455, templates/account/partials/addon.php:36 +#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:112, templates/debug.php:201, templates/debug.php:238, templates/debug.php:455, templates/account/partials/addon.php:36 msgctxt "product version" msgid "Version" msgstr "" @@ -1034,7 +1034,7 @@ msgstr "" msgid "Last Updated" msgstr "" -#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:525 +#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:527 msgctxt "x-ago" msgid "%s ago" msgstr "" @@ -1143,293 +1143,293 @@ msgstr "" msgid "Latest Free Version Installed" msgstr "" -#: templates/account.php:100, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:311 +#: templates/account.php:102, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:311 msgid "Downgrading your plan" msgstr "" -#: templates/account.php:101, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:312 +#: templates/account.php:103, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:312 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' -#: templates/account.php:103, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 +#: templates/account.php:105, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" -#: templates/account.php:104, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:315 +#: templates/account.php:106, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:315 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" -#: templates/account.php:105, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +#: templates/account.php:107, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" -#: templates/account.php:106, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:316 +#: templates/account.php:108, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:316 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" -#: templates/account.php:107, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:317 +#: templates/account.php:109, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:317 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") -#: templates/account.php:109, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +#: templates/account.php:111, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") -#: templates/account.php:112, templates/account/partials/addon.php:38, templates/account/partials/site.php:291 +#: templates/account.php:114, templates/account/partials/addon.php:38, templates/account/partials/site.php:291 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") -#: templates/account.php:114, templates/account/partials/addon.php:40, templates/account/partials/site.php:293 +#: templates/account.php:116, templates/account/partials/addon.php:40, templates/account/partials/site.php:293 msgid "Expires in %s" msgstr "" -#: templates/account.php:115 +#: templates/account.php:117 msgctxt "as synchronize license" msgid "Sync License" msgstr "" -#: templates/account.php:116, templates/account/partials/addon.php:41 +#: templates/account.php:118, templates/account/partials/addon.php:41 msgid "Cancel Trial" msgstr "" -#: templates/account.php:117, templates/account/partials/addon.php:42 +#: templates/account.php:119, templates/account/partials/addon.php:42 msgid "Change Plan" msgstr "" -#: templates/account.php:118, templates/account/partials/addon.php:43 +#: templates/account.php:120, templates/account/partials/addon.php:43 msgctxt "verb" msgid "Upgrade" msgstr "" -#: templates/account.php:120, templates/account/partials/addon.php:45, templates/account/partials/site.php:318 +#: templates/account.php:122, templates/account/partials/addon.php:45, templates/account/partials/site.php:318 msgctxt "verb" msgid "Downgrade" msgstr "" -#: templates/account.php:122, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:47, templates/account/partials/site.php:33 +#: templates/account.php:124, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:47, templates/account/partials/site.php:33 msgid "Free" msgstr "" -#: templates/account.php:124, templates/debug.php:371, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:49 +#: templates/account.php:126, templates/debug.php:371, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:49 msgctxt "as product pricing plan" msgid "Plan" msgstr "" -#: templates/account.php:125 +#: templates/account.php:127 msgid "Bundle Plan" msgstr "" -#: templates/account.php:248 +#: templates/account.php:250 msgid "Free Trial" msgstr "" -#: templates/account.php:259 +#: templates/account.php:261 msgid "Account Details" msgstr "" -#: templates/account.php:266, templates/forms/data-debug-mode.php:33 +#: templates/account.php:268, templates/forms/data-debug-mode.php:33 msgid "Start Debug" msgstr "" -#: templates/account.php:268 +#: templates/account.php:270 msgid "Stop Debug" msgstr "" -#: templates/account.php:275 +#: templates/account.php:277 msgid "Billing & Invoices" msgstr "" -#: templates/account.php:286 +#: templates/account.php:288 msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" msgstr "" -#: templates/account.php:288 +#: templates/account.php:290 msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" msgstr "" -#: templates/account.php:291 +#: templates/account.php:293 msgid "Delete Account" msgstr "" -#: templates/account.php:303, templates/account/partials/addon.php:231, templates/account/partials/deactivate-license-button.php:35 +#: templates/account.php:305, templates/account/partials/addon.php:231, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" -#: templates/account.php:326, templates/forms/subscription-cancellation.php:125 +#: templates/account.php:328, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" -#: templates/account.php:326, templates/account/partials/addon.php:255 +#: templates/account.php:328, templates/account/partials/addon.php:255 msgid "Cancel Subscription" msgstr "" -#: templates/account.php:355, templates/account/partials/addon.php:340 +#: templates/account.php:357, templates/account/partials/addon.php:340 msgctxt "as synchronize" msgid "Sync" msgstr "" -#: templates/account.php:370, templates/debug.php:505 +#: templates/account.php:372, templates/debug.php:505 msgid "Name" msgstr "" -#: templates/account.php:376, templates/debug.php:506 +#: templates/account.php:378, templates/debug.php:506 msgid "Email" msgstr "" -#: templates/account.php:383, templates/debug.php:369, templates/debug.php:555 +#: templates/account.php:385, templates/debug.php:369, templates/debug.php:555 msgid "User ID" msgstr "" -#: templates/account.php:401, templates/account.php:719, templates/account.php:752, templates/debug.php:236, templates/debug.php:363, templates/debug.php:452, templates/debug.php:504, templates/debug.php:553, templates/debug.php:632, templates/account/payments.php:35, templates/debug/logger.php:21 +#: templates/account.php:403, templates/account.php:721, templates/account.php:754, templates/debug.php:236, templates/debug.php:363, templates/debug.php:452, templates/debug.php:504, templates/debug.php:553, templates/debug.php:632, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" -#: templates/account.php:408 +#: templates/account.php:410 msgid "Site ID" msgstr "" -#: templates/account.php:411 +#: templates/account.php:413 msgid "No ID" msgstr "" -#: templates/account.php:416, templates/debug.php:243, templates/debug.php:372, templates/debug.php:456, templates/debug.php:508, templates/account/partials/site.php:227 +#: templates/account.php:418, templates/debug.php:243, templates/debug.php:372, templates/debug.php:456, templates/debug.php:508, templates/account/partials/site.php:227 msgid "Public Key" msgstr "" -#: templates/account.php:422, templates/debug.php:373, templates/debug.php:457, templates/debug.php:509, templates/account/partials/site.php:239 +#: templates/account.php:424, templates/debug.php:373, templates/debug.php:457, templates/debug.php:509, templates/account/partials/site.php:239 msgid "Secret Key" msgstr "" -#: templates/account.php:425 +#: templates/account.php:427 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" -#: templates/account.php:452, templates/account/partials/site.php:120, templates/account/partials/site.php:122 +#: templates/account.php:454, templates/account/partials/site.php:120, templates/account/partials/site.php:122 msgid "Trial" msgstr "" -#: templates/account.php:479, templates/debug.php:561, templates/account/partials/site.php:260 +#: templates/account.php:481, templates/debug.php:561, templates/account/partials/site.php:260 msgid "License Key" msgstr "" -#: templates/account.php:510 +#: templates/account.php:512 msgid "Join the Beta program" msgstr "" -#: templates/account.php:516 +#: templates/account.php:518 msgid "not verified" msgstr "" -#: templates/account.php:525, templates/account/partials/addon.php:190 +#: templates/account.php:527, templates/account/partials/addon.php:190 msgid "Expired" msgstr "" -#: templates/account.php:585 +#: templates/account.php:587 msgid "Premium version" msgstr "" -#: templates/account.php:587 +#: templates/account.php:589 msgid "Free version" msgstr "" -#: templates/account.php:599 +#: templates/account.php:601 msgid "Verify Email" msgstr "" -#: templates/account.php:613 +#: templates/account.php:615 msgid "Download %s Version" msgstr "" -#: templates/account.php:629 +#: templates/account.php:631 msgid "Download Paid Version" msgstr "" -#: templates/account.php:647, templates/account.php:890, templates/account/partials/site.php:248, templates/account/partials/site.php:270 +#: templates/account.php:649, templates/account.php:892, templates/account/partials/site.php:248, templates/account/partials/site.php:270 msgctxt "verb" msgid "Show" msgstr "" -#: templates/account.php:662 +#: templates/account.php:664 msgid "What is your %s?" msgstr "" -#: templates/account.php:670, templates/account/billing.php:21 +#: templates/account.php:672, templates/account/billing.php:21 msgctxt "verb" msgid "Edit" msgstr "" -#: templates/account.php:674, templates/forms/user-change.php:27 +#: templates/account.php:676, templates/forms/user-change.php:27 msgid "Change User" msgstr "" -#: templates/account.php:698 +#: templates/account.php:700 msgid "Sites" msgstr "" -#: templates/account.php:711 +#: templates/account.php:713 msgid "Search by address" msgstr "" -#: templates/account.php:720, templates/debug.php:366 +#: templates/account.php:722, templates/debug.php:366 msgid "Address" msgstr "" -#: templates/account.php:721 +#: templates/account.php:723 msgid "License" msgstr "" -#: templates/account.php:722 +#: templates/account.php:724 msgid "Plan" msgstr "" -#: templates/account.php:755 +#: templates/account.php:757 msgctxt "as software license" msgid "License" msgstr "" -#: templates/account.php:884 +#: templates/account.php:886 msgctxt "verb" msgid "Hide" msgstr "" -#: templates/account.php:906, templates/forms/data-debug-mode.php:31 +#: templates/account.php:908, templates/forms/data-debug-mode.php:31 msgid "Processing" msgstr "" -#: templates/account.php:909 +#: templates/account.php:911 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" -#: templates/account.php:967 +#: templates/account.php:969 msgid "Cancelling %s" msgstr "" -#: templates/account.php:967, templates/account.php:984, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +#: templates/account.php:969, templates/account.php:986, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 msgid "trial" msgstr "" -#: templates/account.php:982, templates/forms/deactivation/form.php:150 +#: templates/account.php:984, templates/forms/deactivation/form.php:150 msgid "Cancelling %s..." msgstr "" -#: templates/account.php:985, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +#: templates/account.php:987, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 msgid "subscription" msgstr "" -#: templates/account.php:999 +#: templates/account.php:1001 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" -#: templates/account.php:1073 +#: templates/account.php:1075 msgid "Disabling white-label mode" msgstr "" -#: templates/account.php:1074 +#: templates/account.php:1076 msgid "Enabling white-label mode" msgstr "" @@ -1455,7 +1455,7 @@ msgctxt "installed add-on" msgid "Installed" msgstr "" -#: templates/admin-notice.php:13, templates/forms/license-activation.php:220, templates/forms/resend-key.php:77 +#: templates/admin-notice.php:13, templates/forms/license-activation.php:222, templates/forms/resend-key.php:77 msgctxt "as close a window" msgid "Dismiss" msgstr "" @@ -1510,84 +1510,92 @@ msgstr "" msgid "Agree & Activate License" msgstr "" -#: templates/connect.php:189 -msgid "Thanks for purchasing %s! To get started, please enter your license key:" +#: templates/connect.php:184 +msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" -#: templates/connect.php:196 +#: templates/connect.php:191 msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." msgstr "" -#: templates/connect.php:197 +#: templates/connect.php:192 msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." msgstr "" -#: templates/connect.php:203 +#: templates/connect.php:198 msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." msgstr "" -#: templates/connect.php:204 +#: templates/connect.php:199 msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." msgstr "" -#: templates/connect.php:238 +#: templates/connect.php:233 msgid "We're excited to introduce the Freemius network-level integration." msgstr "" -#: templates/connect.php:241 +#: templates/connect.php:236 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" -#: templates/connect.php:243 +#: templates/connect.php:238 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" -#: templates/connect.php:245 +#: templates/connect.php:240 msgid "%s's paid features" msgstr "" -#: templates/connect.php:250 +#: templates/connect.php:245 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" -#: templates/connect.php:252 +#: templates/connect.php:247 msgid "During the update process we detected %s site(s) in the network that are still pending your attention." msgstr "" -#: templates/connect.php:261, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 +#: templates/connect.php:256, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 msgid "License key" msgstr "" -#: templates/connect.php:264, templates/forms/license-activation.php:22 +#: templates/connect.php:259, templates/forms/license-activation.php:22 msgid "Can't find your license key?" msgstr "" -#: templates/connect.php:323, templates/connect.php:695, templates/forms/deactivation/retry-skip.php:20 +#: templates/connect.php:318, templates/connect.php:700, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" -#: templates/connect.php:326 +#: templates/connect.php:321 msgid "Delegate to Site Admins" msgstr "" -#: templates/connect.php:326 +#: templates/connect.php:321 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" -#: templates/connect.php:364 +#: templates/connect.php:346 +msgid "License issues?" +msgstr "" + +#: templates/connect.php:362 msgid "Your Profile Overview" msgstr "" -#: templates/connect.php:365 +#: templates/connect.php:363 msgid "Name and email address" msgstr "" -#: templates/connect.php:372 +#: templates/connect.php:370 +msgid "So you can manage and control your license remotely from the User Dashboard." +msgstr "" + +#: templates/connect.php:371 msgid "Your Site Overview" msgstr "" -#: templates/connect.php:373 +#: templates/connect.php:372 msgid "Site URL, WP version, PHP info" msgstr "" @@ -1595,64 +1603,84 @@ msgstr "" msgid "Admin Notices" msgstr "" -#: templates/connect.php:380, templates/connect.php:396 +#: templates/connect.php:380, templates/connect.php:398 msgid "Updates, announcements, marketing, no spam" msgstr "" -#: templates/connect.php:386 -msgid "Current %s Events" +#: templates/connect.php:387 +msgid "So you can reuse the license when the %s is no longer active." +msgstr "" + +#: templates/connect.php:388 +msgid "Current %s Status" msgstr "" -#: templates/connect.php:387 -msgid "Activation, deactivation and uninstall" +#: templates/connect.php:389 +msgid "Active, deactivated, or uninstalled" msgstr "" -#: templates/connect.php:395 +#: templates/connect.php:397 msgid "Newsletter" msgstr "" -#: templates/connect.php:403 +#: templates/connect.php:405 msgid "Plugins & Themes" msgstr "" -#: templates/connect.php:404 +#: templates/connect.php:405 +msgid "optional" +msgstr "" + +#: templates/connect.php:406 +msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts." +msgstr "" + +#: templates/connect.php:407 msgid "Title, slug, version, and is active" msgstr "" -#: templates/connect.php:421, templates/forms/license-activation.php:41 -msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +#: templates/connect.php:424 +msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management." msgstr "" #: templates/connect.php:426 +msgid "diagnostic data" +msgstr "" + +#: templates/connect.php:427 +msgid "Freemius is our licensing and software updates engine" +msgstr "" + +#: templates/connect.php:430 msgid "What permissions are being granted?" msgstr "" -#: templates/connect.php:452 +#: templates/connect.php:457 msgid "Don't have a license key?" msgstr "" -#: templates/connect.php:455 +#: templates/connect.php:460 msgid "Have a license key?" msgstr "" -#: templates/connect.php:463 +#: templates/connect.php:468 msgid "Privacy Policy" msgstr "" -#: templates/connect.php:465 +#: templates/connect.php:470 msgid "License Agreement" msgstr "" -#: templates/connect.php:465 +#: templates/connect.php:470 msgid "Terms of Service" msgstr "" -#: templates/connect.php:854 +#: templates/connect.php:866 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" -#: templates/connect.php:855 +#: templates/connect.php:867 msgctxt "as activating plugin" msgid "Activating" msgstr "" @@ -2241,7 +2269,11 @@ msgstr "" msgid "Update License" msgstr "" -#: templates/forms/license-activation.php:181 +#: templates/forms/license-activation.php:41 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "" + +#: templates/forms/license-activation.php:183 msgid "Associate with the license owner's account." msgstr "" diff --git a/freemius/package.json b/freemius/package.json index 7f72783..4c339ed 100644 --- a/freemius/package.json +++ b/freemius/package.json @@ -4,7 +4,7 @@ "author": "Freemius, Inc.", "license": "GPL-3.0", "homepage": "https://freemius.com", - "version": "2.4.0", + "version": "2.4.2", "main": "gulpfile.js", "dependencies": {}, "scripts": { diff --git a/freemius/start.php b/freemius/start.php index cd70a40..9cb4f4d 100644 --- a/freemius/start.php +++ b/freemius/start.php @@ -15,7 +15,7 @@ * * @var string */ - $this_sdk_version = '2.4.1'; + $this_sdk_version = '2.4.2'; #region SDK Selection Logic -------------------------------------------------------------------- @@ -512,7 +512,7 @@ function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = } /** - * @param array $module Plugin or Theme details. + * @param array $module Plugin or Theme details. * * @return Freemius * @throws Freemius_Exception @@ -527,4 +527,4 @@ function fs_dynamic_init( $module ) { function fs_dump_log() { FS_Logger::dump(); } - } \ No newline at end of file + } diff --git a/freemius/templates/account.php b/freemius/templates/account.php index 13cf519..0921366 100644 --- a/freemius/templates/account.php +++ b/freemius/templates/account.php @@ -21,7 +21,9 @@ /** * @var FS_Plugin_Tag $update */ - $update = $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ); + $update = $fs->has_release_on_freemius() ? + $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) : + null; if ( is_object($update) ) { /** @@ -433,11 +435,11 @@ class="dashicons dashicons-image-rotate"> $fs->get_plugin_version() ); - if ( $is_premium && ! $is_whitelabeled ) { + if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) { $profile[] = array( 'id' => 'beta_program', 'title' => '', - 'value' => $user->is_beta + 'value' => $site->is_beta ); } diff --git a/freemius/templates/connect.php b/freemius/templates/connect.php index 5ce2212..9cc7cab 100644 --- a/freemius/templates/connect.php +++ b/freemius/templates/connect.php @@ -181,12 +181,7 @@ class="wrapis_enable_anonymous() $message = $fs->apply_filters( 'connect-message_on-premium', - ($is_network_upgrade_mode ? - '' : - /* translators: %s: name (e.g. Hey John,) */ - $hey_x_text . '
    ' - ) . - sprintf( fs_text_inline( 'Thanks for purchasing %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), + sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), $first_name, $fs->get_plugin_name() ); @@ -347,6 +342,9 @@ class="button button-secondary" tabindex="2">> + + +

    'dashicons dashicons-admin-settings', - 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), - 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ), - 'priority' => 10, - ); - - $permissions['notices'] = array( - 'icon-class' => 'dashicons dashicons-testimonial', - 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), - 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), - 'priority' => 13, - ); + $permissions['site'] = array( + 'icon-class' => 'dashicons dashicons-admin-settings', + 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ), + 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ), + 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ), + 'priority' => 10, + ); + + if ( ! $require_license_key ) { + $permissions['notices'] = array( + 'icon-class' => 'dashicons dashicons-testimonial', + 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ), + 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), + 'priority' => 13, + ); + } - $permissions['events'] = array( - 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), - 'label' => sprintf( $fs->get_text_inline( 'Current %s Events', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), - 'desc' => $fs->get_text_inline( 'Activation, deactivation and uninstall', 'permissions-events_desc' ), - 'priority' => 20, - ); + $permissions['events'] = array( + 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), + 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ), + 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ), + 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ), + 'priority' => 20, + ); // Add newsletter permissions if enabled. if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) { @@ -398,14 +400,15 @@ class="button button-secondary" tabindex="2"> 'dashicons dashicons-menu', - 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ), + 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ), + 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ), 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ), 'priority' => 25, 'optional' => true, - 'default' => $fs->apply_filters( 'permission_extensions_default', true ) - ); + 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key ) + ); // Allow filtering of the permissions list. $permissions = $fs->apply_filters( 'permission_list', $permissions ); @@ -417,13 +420,15 @@ class="button button-secondary" tabindex="2">

    get_module_label( true ), - $freemius_link + sprintf('%s', fs_esc_html_inline('diagnostic data', 'send-data')), + 'freemius.com ' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '' ) ?>

    - + +
      $permission ) : ?>
    • - + class="fs-tooltip-trigger">

      @@ -702,9 +707,16 @@ function updatePrimaryCtaText( actionType ) { var ajaxOptin = ( requireLicenseKey || isNetworkActive ); $form.on('submit', function () { - var isExtensionsTrackingAllowed = $( '#fs-permission-extensions .fs-switch' ).hasClass( 'fs-on' ); - - $( 'input[name=is_extensions_tracking_allowed]' ).val( isExtensionsTrackingAllowed ? 1 : 0 ); + var $extensionsPermission = $('#fs-permission-extensions .fs-switch'), + isExtensionsTrackingAllowed = ($extensionsPermission.length > 0) ? + $extensionsPermission.hasClass('fs-on') : + null; + + if (null === isExtensionsTrackingAllowed) { + $('input[name=is_extensions_tracking_allowed]').remove(); + } else { + $('input[name=is_extensions_tracking_allowed]').val(isExtensionsTrackingAllowed ? 1 : 0); + } /** * @author Vova Feldman (@svovaf) diff --git a/freemius/templates/forms/license-activation.php b/freemius/templates/forms/license-activation.php index fb1aa2c..facc12a 100644 --- a/freemius/templates/forms/license-activation.php +++ b/freemius/templates/forms/license-activation.php @@ -115,13 +115,15 @@ * @var FS_Plugin_License $license */ foreach ( $available_licenses as $license ) { + $plan = $fs->_get_plan_by_id( $license->plan_id ); + $label = sprintf( "%s-Site %s License - %s", ( 1 == $license->quota ? 'Single' : ( $license->is_unlimited() ? 'Unlimited' : $license->quota ) ), - $fs->_get_plan_by_id( $license->plan_id )->title, + ( is_object( $plan ) ? $plan->title : '' ), $license->get_html_escaped_masked_secret_key() ); diff --git a/includes/class-current-playlist-widget.php b/includes/class-current-playlist-widget.php index 4deb523..b8809b8 100644 --- a/includes/class-current-playlist-widget.php +++ b/includes/class-current-playlist-widget.php @@ -51,7 +51,7 @@ public function form( $instance ) { ' . esc_html( __( 'AJAX Load Widget?', 'radio-station' ) ) . ' -

      +

      -

      '; +

      '; // --- filter and output --- // 2.3.0: filter to allow for extra fields @@ -175,7 +175,6 @@ public function widget( $args, $instance ) { $comments = $instance['comments']; $hide_empty = isset( $instance['hide_empty'] ) ? $instance['hide_empty'] : 1; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $dynamic = isset( $instance['dynamic'] ) ? $instance['dynamic'] : 1; $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; // --- set shortcode attributes for display --- @@ -188,7 +187,6 @@ public function widget( $args, $instance ) { 'label' => $label, 'comments' => $comments, 'countdown' => $countdown, - 'dynamic' => $dynamic, 'widget' => 1, 'id' => $id, ); @@ -198,6 +196,9 @@ public function widget( $args, $instance ) { $atts['ajax'] = $ajax; } + // 2.3.3.9: add filter for default widget attributes + $atts = apply_filters( 'radio_station_current_playlist_widget_atts', $atts, $instance ); + // --- get default display output --- // 2.3.0: use shortcode to generate default widget output $output = radio_station_current_playlist_shortcode( $atts ); @@ -216,8 +217,7 @@ public function widget( $args, $instance ) { // --- open widget container --- // 2.3.0: add unique id to widget // 2.3.2: add class to widget - $id = 'current-playlist-widget-' . $id; - echo '
      '; + echo '
      '; // --- output widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped @@ -228,9 +228,21 @@ public function widget( $args, $instance ) { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['after_title']; + // 2.3.3.9: add div wrapper for widget contents + echo '
      '; + + // --- check for widget output override --- + // 2.3.3.9: added missing override filter + $output = apply_filters( 'radio_station_current_playlist_widget_override', $output, $args, $atts ); + // --- output widget display --- - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $output; + if ( $output ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $output; + } + + // --- close widget contents wrapper --- + echo '
      '; // --- close widget container --- echo '
      '; diff --git a/includes/class-current-show-widget.php b/includes/class-current-show-widget.php index 7204261..13c3b58 100644 --- a/includes/class-current-show-widget.php +++ b/includes/class-current-show-widget.php @@ -260,7 +260,6 @@ public function widget( $args, $instance ) { $width = empty( $instance['avatar_width'] ) ? '' : $instance['avatar_width']; $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $dynamic = isset( $instance['dynamic'] ) ? $instance['dynamic'] : 0; $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; // --- set shortcode attributes --- @@ -284,7 +283,6 @@ public function widget( $args, $instance ) { 'link_djs' => $link_djs, 'show_encore' => $encore, 'countdown' => $countdown, - 'dynamic' => $dynamic, 'widget' => 1, 'id' => $id, ); @@ -294,6 +292,9 @@ public function widget( $args, $instance ) { $atts['ajax'] = $ajax; } + // 2.3.3.9: add filter for default widget attributes + $atts = apply_filters( 'radio_station_current_show_widget_atts', $atts, $instance ); + // --- before widget --- // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['before_widget']; @@ -301,8 +302,7 @@ public function widget( $args, $instance ) { // --- open widget container --- // 2.3.0: add unique id to widget // 2.3.2: add class to widget - $id = 'current-show-widget-' . $id; - echo '
      '; + echo '
      '; // --- widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped @@ -313,6 +313,9 @@ public function widget( $args, $instance ) { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['after_title']; + // 2.3.3.9: add div wrapper for widget contents + echo '
      '; + // --- get default display output --- // 2.3.0: use shortcode to generate default widget output $output = radio_station_current_show_shortcode( $atts ); @@ -327,6 +330,10 @@ public function widget( $args, $instance ) { echo $output; } + // --- close widget contents wrapper --- + echo '
      '; + + // --- close widget container --- echo '
      '; // --- after widget --- diff --git a/includes/class-radio-clock-widget.php b/includes/class-radio-clock-widget.php index d25774d..cbe1aa3 100644 --- a/includes/class-radio-clock-widget.php +++ b/includes/class-radio-clock-widget.php @@ -118,6 +118,16 @@ public function update( $new_instance, $old_instance ) { // --- output widget display --- public function widget( $args, $instance ) { + global $radio_station_data; + + // --- set widget id --- + // 2.3.3.9: added unique widget id + if ( !isset( $radio_station_data['widgets']['clock'] ) ) { + $id = $radio_station_data['widgets']['clock'] = 0; + } else { + $id = $radio_station_data['widgets']['clock']++; + } + // 2.3.0: added hide widget if empty option $title = empty( $instance['title'] ) ? '' : $instance['title']; $title = apply_filters( 'widget_title', $title ); @@ -140,18 +150,16 @@ public function widget( $args, $instance ) { 'widget' => 1, ); - // --- get default display output --- - $output = radio_station_clock_shortcode( $atts ); - - // --- check for widget output override --- - $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); + // 2.3.3.9: add missing filter for clock widget attributes + $atts = apply_filters( 'radio_station_clock_widget_atts', $atts, $instance ); // --- before widget --- // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['before_widget']; // --- open widget container --- - echo '
      '; + // 2.3.0: add instance id and class to widget container + echo '
      '; // --- output widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped @@ -162,10 +170,21 @@ public function widget( $args, $instance ) { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['after_title']; + echo '
      '; + + // --- get default display output --- + $output = radio_station_clock_shortcode( $atts ); + + // --- check for widget output override --- + $output = apply_filters( 'radio_station_radio_clock_widget_override', $output, $args, $atts ); + // --- output widget display --- // phpcs:ignore WordPress.Security.OutputNotEscaped echo $output; + // --- close widget contents --- + echo '
      '; + // --- close widget container --- echo '
      '; diff --git a/includes/class-upcoming-shows-widget.php b/includes/class-upcoming-shows-widget.php index 885cf04..35073f1 100644 --- a/includes/class-upcoming-shows-widget.php +++ b/includes/class-upcoming-shows-widget.php @@ -55,9 +55,9 @@ public function form( $instance ) { ' . esc_html( __( 'AJAX Load Widget?', 'radio-station' ) ) . ' -

      +

      -

      +

      -

      '; +

      '; // --- filter and output --- $fields = apply_filters( 'radio_station_upcoming_shows_widget_fields', $fields, $this, $instance ); @@ -226,7 +226,6 @@ public function widget( $args, $instance ) { $link_djs = isset( $instance['link_djs'] ) ? $instance['link_djs'] : ''; $encore = isset( $instance['encore'] ) ? $instnace['encore'] : 0; $countdown = isset( $instance['countdown'] ) ? $instance['countdown'] : 0; - $dynamic = isset( $instance['dynamic'] ) ? $instance['dynamic'] : 0; $ajax = isset( $instance['ajax'] ) ? $instance['ajax'] : 0; // --- set shortcode attributes --- @@ -250,7 +249,6 @@ public function widget( $args, $instance ) { 'link_djs' => $link_djs, 'show_encore' => $encore, 'countdown' => $countdown, - 'dynamic' => $dynamic, 'widget' => 1, 'id' => $id, ); @@ -260,6 +258,9 @@ public function widget( $args, $instance ) { $atts['ajax'] = $ajax; } + // 2.3.3.9: add filter for default widget attributes + $atts = apply_filters( 'radio_station_upcoming_shows_widget_atts', $atts, $instance ); + // --- before widget --- // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['before_widget']; @@ -267,8 +268,7 @@ public function widget( $args, $instance ) { // --- open widget container --- // 2.3.0: add unique id to widget // 2.3.2: add class to widget - $id = 'upcoming-shows-widget-' . $id; - echo '
      '; + echo '
      '; // --- output widget title --- // phpcs:ignore WordPress.Security.OutputNotEscaped @@ -279,6 +279,9 @@ public function widget( $args, $instance ) { // phpcs:ignore WordPress.Security.OutputNotEscaped echo $args['after_title']; + // 2.3.3.9: add div wrapper for widget contents + echo '
      '; + // --- get default display output --- // 2.3.0: use shortcode to generate default widget output $output = radio_station_upcoming_shows_shortcode( $atts ); @@ -293,6 +296,9 @@ public function widget( $args, $instance ) { echo $output; } + // --- close widget contents wrapper --- + echo '
      '; + // --- close widget container --- echo '
      '; diff --git a/includes/extra-strings.php b/includes/extra-strings.php new file mode 100644 index 0000000..a8c6264 --- /dev/null +++ b/includes/extra-strings.php @@ -0,0 +1,182 @@ +not exported. See help for details on how to include images.', 'radio-station' ); +__( 'YAML import error. See below for details.', 'radio-station' ); +__( 'Failed to create export folder', 'radio-station' ); +__( 'No image exported for', 'radio-station' ); +__( 'Failed to copy file. See below for details', 'radio-station' ); +__( 'Failed to create show_images.zip', 'radio-station' ); +__( 'Each show in the YAML file must define at minimum the following keys: ' , 'radio-station' ); +__( 'show-title: may not be null.', 'radio-station' ); +__( 'show-description: may not be null.', 'radio-station' ); +__('show-image: must be a URL reference to an existing image.', 'radio-station' ); +__( 'show-avatar: must be a URL reference to an existing image.', 'radio-station' ); +__( 'show-header: must be a URL reference to an existing image.', 'radio-station' ); +__( 'upload-images: may not be null.', 'radio-station' ); +__( 'show-url: must be a valid web address.', 'radio-station' ); +__('show-podcast: must be a valid web address.', 'radio-station' ); +__( 'show-user-list: must be a simple array of valid email addresses.', 'radio-station' ); +__( 'show-producer-list: must be a simple array of valid email addresses.', 'radio-station' ); +__( 'show-email: must be a valid email address.', 'radio-station' ); +__( 'show-active: may not be null.', 'radio-station' ); +__( 'YAML data parsed successfully, but contains formatting errors. See below for details.', 'radio-station' ); +__( 'Data file errors noted as follows:', 'radio-station' ); +__( 'show-schedule[] time blocks must be in 24h format and have the form "04:55" (note 0 padding).', 'radio-station' ); +__( 'Error, for show-schedule[], only "disabled" and "encore" flags are allowed', 'radio-station' ); +__( 'show-schedule[] must reference an array of time blocks containing at least one element.', 'radio-station' ); +__( 'Invalid weekday. show-schedule[] must be one of "sun".."sat", or "sunday".."saturday" (case insensitive).', 'radio-station' ); +__(' show-schedule: must define at least one weekday.', 'radio-station' ); +__( 'Import/Export Show Data', 'radio-station' ); +__( 'Import/Export', 'radio-station' ); +__( 'Import', 'radio-station' ); +__( 'Import show data from a YAML file.', 'radio-station' ); +__('Delete existing show data', 'radio-station' ); +__( 'WARNING', 'radio-station' ); +__('Import', 'radio-station' ); +__( 'Export', 'radio-station' ); +__( 'Export show data to a downloadable file. Does not include images by default. Check Advanced for additional options.', 'radio-station' ); +__( 'Advanced', 'radio-station' ); +__( 'YAML file name', 'radio-station' ); +__('Default similar to', 'radio-station') ); +__( 'URL where show images will be staged for import (see help)', 'radio-station') ); +__( 'Title', 'radio-station' ); +__( 'Episode', 'radio-station' ); +__( 'Add Episode', 'radio-station' ); +__( 'Edit Episode', 'radio-station' ); +__( 'New Episode', 'radio-station' ); +__( 'View Episode', 'radio-station' ); +__( 'Search Episodes', 'radio-station' ); +__( 'No Episodes found', 'radio-station' ); +__( 'No Episodes found in Trash', 'radio-station' ); +__( 'All Episodes', 'radio-station' ); +__( 'Post Type for Show Episodes', 'radio-station' ); +__( 'No Episodes were found to display.', 'radio-station' ); +__( 'No Hosts were found to display.', 'radio-station' ); +__( 'No Producers were found to display.', 'radio-station' ); +__( 'Edit', 'radio-station' ); +__( 'DJs / Hosts', 'radio-station' ); +__( 'Show Editors', 'radio-station' ); +__( 'Role Assignment Interface', 'radio-station' ); +__( 'You can assign Radio Station roles to users directly here.', 'radio-station' ); +__( 'They can also be assigned via the WordPress user editor.', 'radio-station' ); +__( 'Grant Role to User', 'radio-station' ); +__( 'Remove Role from User', 'radio-station' ); +__( 'Visual Schedule Editor', 'radio-station' ); +__( 'Table', 'radio-station' ); +__( 'Tabs', 'radio-station' ); +__( 'Grid', 'radio-station' ); +__( 'Calendar', 'radio-station' ); +__( 'You have unsaved changes. Are you sure you want to close this window?', 'radio-station' ); +__( 'Save Shifts', 'radio-station' ); +__( 'Save Shift', 'radio-station' ); +__( 'Edit Show Shifts', 'radio-station' ); +__( 'Edit Show Shift', 'radio-station' ); +__( 'Add Show or Override to Schedule', 'radio-station' ); +__( 'Error. Provided Show ID does not exist.', 'radio-station' ); +__( 'You do not have permission to do that.', 'radio_station' ); +__( 'Warning. Provided Shift ID is not valid.', 'radio-station' ); +__( 'Warning. Provided Shift ID no longer exists.', 'radio-station' ); +__( 'Show', 'radio-station' ); +__( 'Warning! Provided Shift ID no longer exists.', 'radio-station' ); +__( 'Display Shifts', 'radio-station' ); +__( 'Override', 'radio-station' ); +__( 'Error. Provided ID is not a Show or Override.', 'radio-station' ); +__( 'Error! Invalid date value passed.', 'radio-station' ); +__( 'Error! Invalid day value passed.', 'radio-station' ); +__( 'Override Date', 'radio-station' ); +__( 'Shift Day', 'radio-station' ); +__( 'Encore', 'radio-station' ); +__( 'Disabled', 'radio-station' ); +__( 'Start Time', 'radio-station' ); +__( 'End Time', 'radio-station' ); +__( 'Shift Type', 'radio-station' ); +__( 'Show Type', 'radio-station' ); +__( 'Select Show...', 'radio-station' ); +__( 'Open in a New Window', 'radio-station' ); +__( 'Create New Show', 'radio-station' ); +__( 'Override Type', 'radio-station' ); +__( 'Select Override...', 'radio-station' ); +__( 'Create New Override', 'radio-station' ); +__( 'Error! Invalid Show ID passed.', 'radio-station' ); +__( 'Error! That Show ID does not exist.', 'radio-station' ); +__( 'Error! Invalid Override ID passed.', 'radio-station' ); +__( 'Error! That Override ID does not exist.', 'radio-station' ); +__( 'Failed. Please relogin and try again.', 'radio-station' ); +__( 'View', 'radio-station' ); +__( 'List', 'radio-station' ); +__( 'Search Topics', 'radio-station' ); +__( 'All Topics', 'radio-station' ); +__( 'Parent Topic', 'radio-station' ); +__( 'Parent Topic:', 'radio-station' ); +__( 'Edit Topic', 'radio-station' ); +__( 'Update Topic', 'radio-station' ); +__( 'Add New Topic', 'radio-station' ); +__( 'New Topic Name', 'radio-station' ); +__( 'Topic', 'radio-station' ); +__( 'Search Guests', 'radio-station' ); +__( 'All Guests', 'radio-station' ); +__( 'Parent Guest', 'radio-station' ); +__( 'Parent Guest:', 'radio-station' ); +__( 'Edit Guest', 'radio-station' ); +__( 'Update Guest', 'radio-station' ); +__( 'Add New Guest', 'radio-station' ); +__( 'New Guest Name', 'radio-station' ); +__( 'Guest', 'radio-station' ); +__( 'Image', 'radio-station' ); +__( 'Add Genre Image', 'radio-station' ); +__( 'Remove Genre Image', 'radio-station' ); +__( 'Insert', 'radio-station' ); +__( 'Change Timezone', 'radio-station' ); +__( 'Select Region...', 'radio-station' ); +__( 'Select Timezone...', 'radio-station' ); +__( 'Clear', 'radio-station' ); +__( 'Cancel', 'radio-station' ); +__( 'Default', 'radio-station' ); +__( 'On', 'radio-station' ); +__( 'Off', 'radio-station' ); +__( 'Automatic Reloading', 'radio-station' ); +__( 'Load Schedule for Previous Week', 'radio-station' ); +__( 'Load Schedule for Next Week', 'radio-station' ); +__( 'Shows', 'radio-station' ); +__( 'with', 'radio-station' ); +__( 'and', 'radio-station' ); +__( 'to', 'radio-station' ); +__( 'encore airing', 'radio-station' ); +__( 'Audio File', 'radio-station' ); +__( 'Genres', 'radio-station' ); +__( 'No Shows', 'radio-station' ); +__( 'No Shows scheduled for this date.', 'radio-station' ); +__( 'Previous Day', 'radio-station' ); +__( 'Next Day', 'radio-station' ); +__( 'Viewing', 'radio-station' ); +__( 'No Shows scheduled for this day.', 'radio-station' ); diff --git a/includes/legacy.php b/includes/legacy.php index 37ec003..b1fc4b8 100644 --- a/includes/legacy.php +++ b/includes/legacy.php @@ -399,17 +399,21 @@ function radio_station_get_now_playing( $time = false ) { if ( !$current_show ) { return false; } + $show_id = $current_show['show']['id']; + + // TODO: improve handling of playlists for overrides if ( isset( $current_show['override'] ) && $current_show['override'] ) { $playlist = apply_filters( 'radio_station_override_now_playing', false, $current_show['override'] ); return $playlist; } - $show_id = $current_show['show']['id']; + // 2.3.3.9: fix to assign current show shifts to playlist data // TODO: match current playlist to assigned show shift ? - if ( isset( $current_show['shifts'] ) ) { - $shifts = $current_show['shifts']; - } $playlist = array(); + $shifts = get_post_meta( $show_id, 'show_sched', true ); + if ( $shifts ) { + $playlist['shifts'] = $shifts; + } // --- grab the most recent playlist for the current show --- $args = array( @@ -431,7 +435,7 @@ function radio_station_get_now_playing( $time = false ) { $playlist['query'] = $args; $playlist['posts'] = $playlist_posts; - // TODO: check for playlist linked to this shift or date? + // TODO: check for playlist linked to this shift / date? if ( $playlist_posts && is_array( $playlist_posts ) && ( count( $playlist_posts ) > 0 ) ) { diff --git a/includes/master-schedule.php b/includes/master-schedule.php index d25e2d9..1248339 100644 --- a/includes/master-schedule.php +++ b/includes/master-schedule.php @@ -6,6 +6,18 @@ * @Since: 2.1.1 */ +// - Master Schedule Shortcode +// - Schedule Loader Script +// - AJAX Schedule Loader +// - Show Genre Selector +// - Table View Javascript +// - Tabbed View Javascript +// - List View Javascript + + +// ------------------------- +// Master Schedule Shortcode +// ------------------------- add_shortcode( 'master-schedule', 'radio_station_master_schedule' ); function radio_station_master_schedule( $atts ) { @@ -49,6 +61,8 @@ function radio_station_master_schedule( $atts ) { // 2.3.0: set default table display to new table formatting // 2.3.2: added start_day attribute (for use width days) // 2.3.2: added display_day, display_date and display_month attributes + // 2.3.3.9: added start_date attribute for non-now schedules + // 2.3.3.9: added active_date attribute for schedule switching $time_format = (int) radio_station_get_setting( 'clock_time_format' ); $defaults = array( @@ -64,6 +78,8 @@ function radio_station_master_schedule( $atts ) { 'view' => 'table', 'days' => false, 'start_day' => false, + 'start_date' => false, + 'active_date' => false, 'display_day' => 'short', 'display_date' => 'jS', 'display_month' => 'short', @@ -84,11 +100,29 @@ function radio_station_master_schedule( $atts ) { ); // 2.3.0: change some defaults for tabbed and list view // 2.3.2: check for comma separated view list + $view = ''; + $views = array(); if ( isset( $atts['view'] ) ) { + // 2.3.2: view value to lowercase to be case insensitive - $atts['view'] = strtolower( $atts['view'] ); - $views = explode( ',', $atts['view'] ); - if ( ( 'tabs' == $atts['view'] ) || in_array( 'tabs', $views ) ) { + $view = $atts['view'] = strtolower( $atts['view'] ); + if ( strstr( $atts['view'], ',' ) ) { + $views = explode( ',', $atts['view'] ); + foreach ( $views as $i => $aview ) { + if ( 'tabbed' == $aview ) { + $aview = 'tabs'; + } + $views[$i] = trim( $aview ); + } + // 2.3.3.9: set default view for multiviews to table + $defaults['default_view'] = 'table'; + } elseif ( 'tabbed' == $view ) { + // 2.3.3.9: fix for possible misspelt tab view + $view = 'tabs'; + } + + // 2.3.3.9: set prefixed defaults for multiple views + if ( 'tabs' == $atts['view'] ) { // 2.3.2: add show descriptions default for tabbed view // 2.3.2: add display_day and display_date attributes // 2.3.3: revert show description default for tabbed view @@ -101,22 +135,37 @@ function radio_station_master_schedule( $atts ) { $defaults['display_date'] = false; $defaults['image_position'] = 'left'; $defaults['hide_past_shows'] = false; - } elseif ( ( 'list' == $atts['view'] ) || in_array( 'list', $views ) ) { + } elseif ( in_array( 'tabs', $views ) ) { + $defaults['tabs_show_image'] = 1; + $defaults['tabs_show_hosts'] = 1; + $defaults['tabs_show_genres'] = 1; + $defaults['tabs_display_day'] = 'full'; + $defaults['tabs_display_date'] = false; + $defaults['tabs_image_position'] = 'left'; + $defaults['tabs_hide_past_shows'] = false; + } + if ( 'list' == $atts['view'] ) { // 2.3.2: add display date attribute $defaults['show_genres'] = 1; $defaults['display_date'] = false; - } elseif ( ( 'divs' == $atts['view'] ) || in_array( 'divs', $views ) ) { + } elseif ( in_array( 'list', $views ) ) { + $defaults['list_show_genres'] = 1; + $defaults['list_display_date'] = false; + } + if ( ( 'divs' == $atts['view'] ) || ( in_array( 'divs', $views ) ) ) { // 2.3.3.8: moved divs view only default here // 2.3.3.9: added check if divs in views array $defaults['divheight'] = 45; - } elseif ( ( 'grid' == $atts['grid'] ) || in_array( 'grid', $views ) ) { - // 2.3.3.9: add default for grid view width - $defaults['gridwidth'] = 150; } } + // 2.3.3.9: filter defaults according to view(s) + $defaults = apply_filters( 'radio_station_master_schedule_default_atts', $defaults, $view, $views ); // --- merge attributes with defaults --- $atts = shortcode_atts( $defaults, $atts, 'master-schedule' ); + if ( RADIO_STATION_DEBUG ) { + echo 'Master Schedule Shortcode Attributes: ' . print_r( $atts, true ) . ''; + } // --- enqueue schedule stylesheet --- // 2.3.0: use abstracted method for enqueueing widget styles @@ -161,7 +210,7 @@ function radio_station_master_schedule( $atts ) { // --- genre selector --- if ( $atts['selector'] ) { $controls['selector'] = '
      ' . $newline; - $controls['selector'] .= radio_station_master_schedule_selector(); + $controls['selector'] .= radio_station_master_schedule_genre_selector(); $controls['selector'] .= PHP_EOL . '
      ' . $newline; } @@ -182,11 +231,37 @@ function radio_station_master_schedule( $atts ) { } $output .= '

      ' . $newline; + $output = apply_filters( 'radio_station_schedule_controls_output', $output, $atts ); + + // --- hidden inputs for calendar start/active dates --- + // 2.3.3.9: added for schedule week change reloading + if ( isset( $atts['start_date'] ) && $atts['start_date'] ) { + if ( $atts['start_date'] == date( 'Y-m-d', strtotime( $atts['start_date'] ) ) ) { + $start_date = $atts['start_date']; + } + } + if ( !isset( $start_date ) ) { + $now = radio_station_get_now(); + $start_date = radio_station_get_time( 'date', $now ); + } + $output .= ''; + $active_date = $start_date; + if ( isset( $atts['active_date'] ) && $atts['active_date'] ) { + if ( $atts['active_date'] == date( 'Y-m-d', strtotime( $atts['active_date'] ) ) ) { + $active_date = $atts['active_date']; + } + } + $output .= ''; + + // --- enqueue schedule loader script --- + $js = radio_station_master_schedule_loader_js( $atts ); + wp_add_inline_script( 'radio-station', $js ); // --- schedule display override --- // 2.3.1: add full schedule override filter - $override = apply_filters( 'radio_station_schedule_override', '', $atts ); - if ( ( '' != $override ) && strstr( $override, '' ) ) { + // 2.3.3.9: add existing controls output to filter + $override = apply_filters( 'radio_station_schedule_override', $output, $atts ); + if ( strstr( $override, '' ) ) { $override = str_replace( '', '', $override ); return $override; } @@ -197,30 +272,41 @@ function radio_station_master_schedule( $atts ) { // --- load master schedule template --- // 2.2.7: added tabbed master schedule template + // 2.2.7: add tabbed view javascript to footer // 2.3.0: use new data model for table and tabs view // 2.3.0: check for user theme templates + // 2.3.3.9: use output buffering on templates + // 2.3.3.9: get and enqueue scripts inline directly if ( 'table' == $atts['view'] ) { - add_action( 'wp_footer', 'radio_station_master_schedule_table_js' ); + ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-table.php' ); require $template; - - $output = apply_filters( 'master_schedule_table_view', $output, $atts ); - return $output; + $html = ob_get_contents(); + ob_end_clean(); + $html = apply_filters( 'master_schedule_table_view', $html, $atts ); + $js = radio_station_master_schedule_table_js(); + wp_add_inline_script( 'radio-station', $js ); + return $output . $html; } elseif ( 'tabs' == $atts['view'] ) { - // 2.2.7: add tabbed view javascript to footer - add_action( 'wp_footer', 'radio_station_master_schedule_tabs_js' ); + ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-tabs.php' ); require $template; - - $output = apply_filters( 'master_schedule_tabs_view', $output, $atts ); - return $output; + $html = ob_get_contents(); + ob_end_clean(); + $html = apply_filters( 'master_schedule_tabs_view', $html, $atts ); + $js = radio_station_master_schedule_tabs_js(); + wp_add_inline_script( 'radio-station', $js ); + return $output . $html; } elseif ( 'list' == $atts['view'] ) { - add_action( 'wp_footer', 'radio_station_master_schedule_list_js' ); + ob_start(); $template = radio_station_get_template( 'file', 'master-schedule-list.php' ); require $template; - - $output = apply_filters( 'master_schedule_list_view', $output, $atts ); - return $output; + $html = ob_get_contents(); + ob_end_clean(); + $html = apply_filters( 'master_schedule_list_view', $html, $atts ); + $js = radio_station_master_schedule_list_js(); + wp_add_inline_script( 'radio-station', $js ); + return $output . $html; } // ---------------------- @@ -382,9 +468,152 @@ function radio_station_master_schedule( $atts ) { } // ---------------------- -// Show / Genre Selector +// Schedule Loader Script // ---------------------- -function radio_station_master_schedule_selector() { +function radio_station_master_schedule_loader_js( $atts ) { + + // --- set AJAX URL with attribute keys --- + $loader_url = esc_url( add_query_arg( 'action', 'radio_station_schedule', admin_url( 'admin-ajax.php' ) ) ); + $ignore_keys = array( 'start_date', 'view' ); + foreach ( $atts as $key => $value ) { + if ( !in_array( $key, $ignore_keys ) ) { + $loader_url .= '&' . esc_js( $key ) . '=' . esc_js( $value ); + } + } + + // --- schedule loader function --- + $js = "function radio_load_schedule(direction,view) { + startdate = document.getElementById('schedule-start-date').value; + activedate = document.getElementById('schedule-active-date').value; + if (!view) { + if (jQuery('.master-schedule-view-tab.current-view').length) { + view = jQuery('.master-schedule-view-tab.current-view').attr('id').replace('master-schedule-view-tab-',''); + } else { + if (jQuery('#master-program-schedule').length) {view = 'table';} + else if (jQuery('#master-schedule-tabs').length) {view = 'tabs';} + else if (jQuery('#master-schedule-list').length) {view = 'list';} + else if (jQuery('#master-schedule-grid').length) {view = 'grid';} + else if (jQuery('#master-schedule-calendar').length) {view = 'calendar';} + else {return;} + } + } + if (radio.debug) {console.log('Reloading Schedule View: '+view);} + if (!direction) {offset = 0;} + else if (direction == 'previous') {offset = -(7 * 24 * 60 * 60 * 1000);} + else if (direction == 'next') {offset = (7 * 24 * 60 * 60 * 1000);} + newdate = new Date(new Date(startdate).getTime() + offset).toISOString().substr(0,10); + url = '" . $loader_url . "&view='+view; + timestamp = Math.floor((new Date()).getTime() / 1000); + url += '×tamp='+timestamp+'&start_date='+newdate+'&active_date='+activedate; + document.getElementById('schedule-'+view+'-loader').src = url; + }" . PHP_EOL; + + // --- filter and return --- + $js = apply_filters( 'radio_station_master_schedule_loader_js', $js ); + return $js; +} + +// -------------------- +// AJAX Schedule Loader +// -------------------- +add_action( 'wp_ajax_radio_station_schedule', 'radio_station_ajax_schedule_loader' ); +add_action( 'wp_ajax_nopriv_radio_station_schedule', 'radio_station_ajax_schedule_loader' ); +function radio_station_ajax_schedule_loader() { + + // --- sanitize shortcode attributes --- + $atts = radio_station_sanitize_shortcode_values( 'master-schedule' ); + if ( RADIO_STATION_DEBUG ) { + print_r( $_REQUEST ); + echo "Sanitized Master Schedule Shortcode Attributes: " . print_r( $atts, true ); + } + + // --- output schedule contents --- + echo '
      '; + echo radio_station_master_schedule( $atts ); + echo '
      '; + + $js = ''; + if ( strstr( $atts['view'], ',' ) ) { + $views = explode( ',', $atts['view'] ); + } else { + $views = array( $atts['view'] ); + } + foreach ( $views as $view ) { + + $view = trim( $view ); + + // --- set schedule element ID --- + if ( 'table' == $view ) { + $schedule_id = 'master-program-schedule'; + } elseif ( 'tabs' == $view ) { + $schedule_id = 'master-schedule-tabs'; + $panels_id = 'master-schedule-tab-panels'; + } elseif ( 'list' == $view ) { + $schedule_id = 'master-list'; + } elseif ( 'grid' == $view ) { + $schedule_id = 'master-schedule-grid'; + } elseif ( 'calendar' == $view ) { + $schedule_id = 'master-schedule-calendar'; + } + + // --- send new schedule to parent window --- + // $js .= "document.getElementById('schedule-contents').innerHTML = '';" . PHP_EOL; + $js .= "schedule = document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML;" . PHP_EOL; + $js .= "parent.document.getElementById('" . esc_attr( $schedule_id ) . "').innerHTML = schedule;" . PHP_EOL; + if ( isset( $panels_id ) ) { + $js .= "panels = document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML;" . PHP_EOL; + $js .= "parent.document.getElementById('" . esc_attr( $panels_id ) . "').innerHTML = panels;" . PHP_EOL; + } + + // --- copy the new start date value to the parent window --- + $js .= "start_date = document.getElementById('schedule-start-date').value;" . PHP_EOL; + $js .= "parent.document.getElementById('schedule-start-date').value = start_date;" . PHP_EOL; + + // --- maybe retrigger view(s) javascript in parent window --- + // (uses set interval cycle in case script not yet loaded) + $js .= "var genres_highlighted = false;" . PHP_EOL; + $js .= "schedule_loader = setInterval(function() {" . PHP_EOL; + $js .= "if (!genres_highlighted && (typeof parent.radio_genre_highlight == 'function')) {" . PHP_EOL; + $js .= "parent.radio_genre_highlight();" . PHP_EOL; + $js .= "genres_highlighted = true;" . PHP_EOL; + $js .= "}" . PHP_EOL; + if ( 'table' == $view ) { + $js .= "if (typeof parent.radio_table_initialize == 'function') {"; + $js .= "parent.radio_table_initialize();" . PHP_EOL; + $js .= "clearInterval(schedule_loader);" . PHP_EOL; + $js .= "}" . PHP_EOL; + } elseif ( 'tabs' == $view ) { + $js .= "if (typeof parent.radio_tabs_initialize == 'function') {"; + $js .= "parent.radio_tabs_init = false;" . PHP_EOL; + $js .= "parent.radio_tabs_initialize();" . PHP_EOL; + $js .= "clearInterval(schedule_loader);" . PHP_EOL; + $js .= "}" . PHP_EOL; + } elseif ( 'list' == $view ) { + $js .= "if (typeof parent.radio_list_highlight == 'function') {"; + $js .= "parent.radio_list_highlight();" . PHP_EOL; + $js .= "clearInterval(schedule_loader);" . PHP_EOL; + } + $js .= "parent.radio_convert_times();" . PHP_EOL; + $js .= "/* LOADER PLACEHOLDER */"; + $js .= "}, 2000);" . PHP_EOL; + } + + // --- filter load script and output --- + $js = apply_filters( 'radio_station_master_schedule_load_script', $js, $atts ); + if ( '' != $js ) { + echo ""; + } + + exit; + +} + + +// ------------------- +// Show Genre Selector +// ------------------- +// 2.3.3.9: change name from radio_station_master_schedule_selector +function radio_station_master_schedule_genre_selector() { // --- get genres --- $args = array( @@ -404,12 +633,13 @@ function radio_station_master_schedule_selector() { // --- genre highlight links --- // 2.3.0: fix by imploding with genre link spacer + // 2.3.3.9: escape genre slug and assign javascript to onclick $genre_links = array(); foreach ( $genres as $i => $genre ) { $slug = sanitize_title_with_dashes( $genre->name ); - $javascript = 'javascript:radio_genre_highlight(\'' . $slug . '\')'; + $onclick = "radio_genre_highlight('" . esc_attr( $slug ) . "');"; $title = __( 'Click to toggle Highlight of Shows with this Genre.', 'radio-station' ); - $genre_link = ''; + $genre_link = ''; $genre_link .= esc_html( $genre->name ) . ''; $genre_links[] = $genre_link; } @@ -420,31 +650,39 @@ function radio_station_master_schedule_selector() { // --- genre highlighter script --- // 2.3.0: improved to highlight / unhighlight multiple genres // 2.3.0: improved to work with table, tabs or list view - $js = "var highlighted_genres = new Array(); + // 2.3.3.9: added genre class targets for grid and calendar views + // 2.3.3.9: added accepting false to retrigger highlights for AJAX + $js = "var radio_genres_selected = new Array(); function radio_genre_highlight(genre) { - if (jQuery('#genre-highlight-'+genre).hasClass('highlighted')) { - jQuery('#genre-highlight-'+genre).removeClass('highlighted'); - - jQuery('.master-show-entry').each(function() {jQuery(this).removeClass('highlighted');}); - jQuery('.master-schedule-tabs-show').each(function() {jQuery(this).removeClass('highlighted');}); - jQuery('.master-list-day-item').each(function() {jQuery(this).removeClass('highlighted');}); - - j = 0; new_genre_highlights = new Array(); - for (i = 0; i < highlighted_genres.length; i++) { - if (highlighted_genres[i] != genre) { - jQuery('.'+highlighted_genres[i]).addClass('highlighted'); - new_genre_highlights[j] = highlighted_genres[i]; j++; + classes = '.master-show-entry, .master-schedule-tabs-show, .master-list-day-item, .master-schedule-grid-show, .master-schedule-calendar-date, .master-schedule-calendar-show'; + if (genre === false) { + jQuery(classes).removeClass('highlighted'); + for (i = 0; i < radio_genres_selected.length; i++) { + jQuery('.'+radio_genres_selected[i]).addClass('highlighted'); + } + } else { + if (jQuery('#genre-highlight-'+genre).hasClass('highlighted')) { + jQuery('#genre-highlight-'+genre).removeClass('highlighted'); + jQuery(classes).removeClass('highlighted'); + + j = 0; new_genre_highlights = new Array(); + for (i = 0; i < radio_genres_selected.length; i++) { + if (radio_genres_selected[i] != genre) { + jQuery('.'+radio_genres_selected[i]).addClass('highlighted'); + new_genre_highlights[j] = radio_genres_selected[i]; j++; + } } - } - highlighted_genres = new_genre_highlights; + radio_genres_selected = new_genre_highlights; - } else { - jQuery('#genre-highlight-'+genre).addClass('highlighted'); - highlighted_genres[highlighted_genres.length] = genre; - jQuery('.'+genre).each(function () { - jQuery(this).addClass('highlighted'); - }); + } else { + jQuery('#genre-highlight-'+genre).addClass('highlighted'); + radio_genres_selected[radio_genres_selected.length] = genre; + jQuery('.'+genre).each(function () { + jQuery(this).addClass('highlighted'); + }); + } } + /* console.log(jQuery(classes)); */ }"; // --- enqueue script --- @@ -464,19 +702,31 @@ function radio_station_master_schedule_table_js() { // 2.3.2: fix to currenthour substr // 2.3.3.5: change selected day and arrow logic (to single day shifting) // 2.3.3.6: also highlight split shift via matching shift class + // 2.3.3.9: prefix show-info selector with .master-program-hour-row + // 2.3.3.9: use setInterval instead of setTimeout for highlighting + // 2.3.3.9: check for required elements before executing functions + // 2.3.3.9: fix to check before and after current time not show $js = "/* Initialize Table */ + var radio_table_init = false; jQuery(document).ready(function() { - radio_table_responsive(false); - radio_times_highlight(); - setTimeout(radio_times_highlight, 60000); + radio_table_initialize(); + var radio_table_highlighting = setInterval(radio_table_highlight, 60000); }); jQuery(window).resize(function () { radio_resize_debounce(function() {radio_table_responsive(false);}, 500, 'scheduletable'); }); + /* Table Initialize */ + function radio_table_initialize() { + radio_table_responsive(false); + radio_table_highlight(); + radio_table_init = true; + } + /* Current Time Highlighting */ - function radio_times_highlight() { - radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); + function radio_table_highlight() { + if (!jQuery('.master-program-day').length) {return;} + radio.current_time = Math.floor((new Date()).getTime() / 1000); radio.offset_time = radio.current_time + radio.timezone.offset; if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} @@ -496,48 +746,41 @@ function radio_times_highlight() { if (hour == currenthour) {jQuery(this).addClass('current-hour');} else {jQuery(this).removeClass('current-hour');} }); - var radio_active_shift = false; var radio_table_current = false; for (i = 0; i < 7; i++) { jQuery('#master-program-schedule .day-'+i).each(function() { + var radio_table_shift = false; jQuery(this).find('.master-show-entry').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (radio.debug) {console.log(jQuery(this)); console.log(start+' - '+end);} if ( (start < radio.offset_time) && (end > radio.offset_time) ) { if (radio.debug) {console.log('^ Now Playing ^');} - radio_table_current = true; jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); /* also highlight split shift via matching shift class */ if (jQuery(this).hasClass('overnight')) { classes = jQuery(this).attr('class').split(/\s+/); for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_active_shift = classes[i];} + if (classes[i].substr(0,6) == 'split-') {radio_table_shift = classes[i];} } } } else { jQuery(this).removeClass('nowplaying'); - if (radio_table_current) { - jQuery(this).removeClass('before-current').addClass('after-current'); - if (radio.debug) {console.log('^ Adding Before Current Class');} - } else { - jQuery(this).addClass('before-current').removeClass('after-current'); - if (radio.debug) {console.log('^ Adding After Current Class');} - } - } - if (radio_active_shift) { - jQuery('.'+radio_active_shift).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); - if (radio.debug) {console.log('^ Adding Now Playing Class');} + if (start > radio.offset_time) {jQuery(this).addClass('after-current');} + else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} } }); + if (radio_table_shift) { + jQuery('.'+radio_table_shift).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + } }); } - if (!radio_table_current) {jQuery('.master-show-entry').removeClass('before-current');} } /* Make Table Responsive */ function radio_table_responsive(leftright) { + if (!jQuery('.master-program-day').length) {return;} - fallback = -1; selected = -1; foundtab = false; + fallback = -1; selected = -1; foundday = false; if (!leftright || (leftright == 'left')) { if (jQuery('.master-program-day.first-column').length) { start = jQuery('.master-program-day.first-column'); @@ -558,16 +801,16 @@ classes = end.attr('class').split(' '); if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} if (selected < 0) {selected = 0;} else if (selected > 6) {selected = 6;} if (!jQuery('.master-program-day.day-'+selected).length) { - while (!foundtab) { + while (!foundday) { if (leftright == 'left') {selected--;} else if (leftright == 'right') {selected++;} - if (jQuery('.master-program-day.day-'+selected).length) {foundtab = true;} - if ((selected < 0) || (selected > 6)) {selected = fallback; foundtab = true;} + if (jQuery('.master-program-day.day-'+selected).length) {foundday = true;} + if ((selected < 0) || (selected > 6)) {selected = fallback; foundday = true;} } } if (radio.debug) {console.log('Selected Column: '+selected);} totalwidth = jQuery('#master-program-hour-heading').width(); - jQuery('.master-program-day, .show-info').removeClass('first-column').removeClass('last-column').hide(); + jQuery('.master-program-day, .master-program-hour-row .show-info').removeClass('first-column').removeClass('last-column').hide(); jQuery('#master-program-schedule').css('width','100%'); tablewidth = jQuery('#master-program-schedule').width(); jQuery('#master-program-schedule').css('width','auto'); @@ -576,15 +819,16 @@ classes = end.attr('class').split(' '); if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { if ((i > 0) && (i == selected)) {jQuery('.master-program-day.day-'+i).addClass('first-column'); firstcolumn = i;} else if (i < 6) {jQuery('.master-program-day.day-'+i).addClass('last-column');} - jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).show(); + jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); colwidth = jQuery('.master-program-day.day-'+i).width(); totalwidth = totalwidth + colwidth; if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} jQuery('.master-program-day.day-'+i).removeClass('last-column'); if (totalwidth > tablewidth) { if (radio.debug) {console.log('Hiding Column '+i);} - jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).hide(); endtable = true; + jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; } else { + if (radio.debug) {console.log('Showing Column '+i);} jQuery('.master-program-day.day-'+i).removeClass('last-column'); totalwidth = totalwidth - colwidth + jQuery('.master-program-day.day-'+i).width(); lastcolumn = i; columns++; @@ -597,17 +841,17 @@ classes = end.attr('class').split(' '); if (leftright == 'right') { for (i = (selected - 1); i > -1; i--) { if (!endtable && (jQuery('.master-program-day.day-'+i).length)) { - jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).show(); + jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).show(); colwidth = jQuery('.master-program-day.day-'+i).width(); totalwidth = totalwidth + colwidth; if (radio.debug) {console.log('('+colwidth+') : '+totalwidth+' / '+tablewidth);} if (totalwidth > tablewidth) { if (radio.debug) {console.log('Hiding Column '+i);} - jQuery('.master-program-day.day-'+i+', .show-info.day-'+i).hide(); endtable = true; + jQuery('.master-program-day.day-'+i+', .master-program-hour-row .show-info.day-'+i).hide(); endtable = true; } else { + if (radio.debug) {console.log('Showing Column '+i);} jQuery('.master-program-day').removeClass('first-column'); jQuery('.master-program-day.day-'+i).addClass('first-column'); - if (radio.debug) {console.log('Showing Tab '+i);} columns++; } } @@ -619,10 +863,12 @@ classes = end.attr('class').split(' '); /* Shift Day Left / Right */ function radio_shift_day(leftright) { radio_table_responsive(leftright); return false; - }"; + }" . PHP_EOL; - // --- enqueue script inline --- - wp_add_inline_script( 'radio-station', $js ); + // --- filter and return --- + // 2.3.3.9: add filter and return instead of inline enqueue + $js = apply_filters( 'radio_station_master_schedule_table_js', $js ); + return $js; } // ---------------------- @@ -647,7 +893,8 @@ function radio_station_master_schedule_tabs_js() { // 2.3.3.6: allow for clicking on date to change days // 2.3.3.8: make entire heading label div clickable to change tabs - $js = "jQuery(document).ready(function() { + // 2.3.3.9: make into function and add to document ready code block + $js = "function radio_tabs_clicks() { jQuery('.master-schedule-tabs-headings').bind('click', function (event) { headerID = jQuery(event.target).closest('li').attr('id'); panelID = headerID.replace('header', 'day'); @@ -656,27 +903,37 @@ function radio_station_master_schedule_tabs_js() { jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); jQuery('#'+panelID).addClass('active-day-panel'); }); - });"; + }" . PHP_EOL; // --- tabbed view responsiveness --- // 2.3.0: added for tabbed responsiveness // 2.3.2: display selected day message if outside view // 2.3.3.5: change selected day and arrow logic (to single day shifting) // 2.3.3.6: also highlight split shift via matching shift class + // 2.3.3.9: use setInterval instead of setTimeout for highlighting check + // 2.3.3.9: check for required elements before executing functions + // 2.3.3.9: fix to check before and after current time not show $js .= "/* Initialize Tabs */ + var radio_tabs_init = false; jQuery(document).ready(function() { - radio.schedule_tabinit = false; - radio_tabs_responsive(false); - radio_show_highlight(); - setTimeout(radio_show_highlight, 60000); + radio_tabs_initialize(); + var radio_tab_highlighting = setInterval(radio_tabs_show_highlight, 60000); }); jQuery(window).resize(function () { radio_resize_debounce(function() {radio_tabs_responsive(false);}, 500, 'scheduletabs'); }); + function radio_tabs_initialize() { + radio_tabs_clicks(); + radio_tabs_responsive(false); + radio_tabs_show_highlight(); + radio_tabs_init = true; + } + /* Set Day Tab on Load */ - function radio_set_active_tab(day) { - if (radio.schedule_tabinit) {return;} + function radio_tabs_active_tab(day) { + if (radio_tabs_init) {return;} + if (!jQuery('.master-schedule-tabs-headings').length) {return;} jQuery('.master-schedule-tabs-day').removeClass('active-day-tab'); jQuery('.master-schedule-tabs-panel').removeClass('active-day-panel'); if (!day) { @@ -686,11 +943,11 @@ function radio_set_active_tab(day) { jQuery('#master-schedule-tabs-header-'+day).addClass('active-day-tab'); jQuery('#master-schedule-tabs-day-'+day).addClass('active-day-panel'); } - radio.schedule_tabinit = true; } /* Current Show Highlighting */ - function radio_show_highlight() { + function radio_tabs_show_highlight() { + if (!jQuery('.master-schedule-tabs-headings').length) {return;} radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); radio.offset_time = radio.current_time + radio.timezone.offset; if (radio.debug) {console.log(radio.current_time+' - '+radio.offset_time);} @@ -701,40 +958,39 @@ function radio_show_highlight() { if ( (start < radio.offset_time) && (end > radio.offset_time) ) { jQuery(this).addClass('current-day'); day = jQuery(this).attr('id').replace('master-schedule-tabs-header-', ''); - radio_set_active_tab(day); + radio_tabs_active_tab(day); } else {jQuery(this).removeClass('current-day');} }); - radio_set_active_tab(false); /* fallback */ - var radio_active_split = false; var radio_tabs_current = false; + radio_tabs_active_tab(false); /* fallback */ + var radio_tabs_split = false; jQuery('.master-schedule-tabs-show').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); if (radio.debug) {console.log(start+' - '+end);} if ( (start < radio.offset_time) && (end > radio.offset_time) ) { - radio_tabs_current = true; if (radio.debug) {console.log('^ Now Playing ^');} jQuery(this).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); /* also highlight split shift via matching shift class */ if (jQuery(this).hasClass('overnight')) { classes = jQuery(this).attr('class').split(/\s+/); for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_active_split = classes[i];} + if (classes[i].substr(0,6) == 'split-') {radio_tabs_split = classes[i];} } } } else { jQuery(this).removeClass('nowplaying'); - if (radio_tabs_current) {jQuery(this).removeClass('before-current').addClass('after-current');} - else {jQuery(this).addClass('before-current').removeClass('after-current');} + if (start > radio.offset_time) {jQuery(this).addClass('after-current');} + else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} } }); - if (radio_active_split) { - jQuery('.'+radio_active_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + if (radio_tabs_split) { + jQuery('.'+radio_tabs_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } - if (!radio_tabs_current) {jQuery('.master-schedule-tabs-show').removeClass('before-current');} } /* Make Tabs Responsive */ function radio_tabs_responsive(leftright) { + if (!jQuery('.master-schedule-tabs-headings').length) {return;} fallback = -1; selected = -1; foundtab = false; if (!leftright || (leftright == 'left')) { @@ -839,13 +1095,13 @@ classes = end.attr('class').split(' '); /* Shift Day Left / Right */ function radio_shift_tab(leftright) { - radio_tabs_responsive(leftright); - return false; - }"; + radio_tabs_responsive(leftright); return false; + }" . PHP_EOL; - // --- enqueue script inline --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station', $js ); + // --- filter and return --- + // 2.3.3.9: add filter and return instead of inline enqueue + $js = apply_filters( 'radio_station_master_schedule_tabs_js', $js ); + return $js; } // -------------------- @@ -856,13 +1112,17 @@ function radio_station_master_schedule_list_js() { // --- list view javascript --- // 2.3.3.6: also highlight split shift via matching shift class + // 2.3.3.9: use setInterval instead of setTimeout for highlighting + // 2.3.3.9: check for required elements before executing functions + // 2.3.3.9: fix to check before and after current time not show $js = "/* Initialize List */ jQuery(document).ready(function() { radio_list_highlight(); - setTimeout(radio_list_highlight, 60000); + var radio_list_highlighting = setInterval(radio_list_highlight, 60000); }); /* Current Show Highlighting */ function radio_list_highlight() { + if (!jQuery('.master-list-day').length) {return;} radio.current_time = Math.floor( (new Date()).getTime() / 1000 ); radio.offset_time = radio.current_time + radio.timezone.offset; if (radio.timezone.adjusted) {radio.offset_time = radio.current_time;} @@ -873,7 +1133,7 @@ function radio_list_highlight() { jQuery(this).addClass('current-day'); } else {jQuery(this).removeClass('current-day');} }); - var radio_active_list = false; var radio_list_current = false; + var radio_list_split = false; jQuery('.master-list-day-item').each(function() { start = parseInt(jQuery(this).find('.rs-start-time').attr('data')); end = parseInt(jQuery(this).find('.rs-end-time').attr('data')); @@ -885,21 +1145,22 @@ function radio_list_highlight() { if (jQuery(this).hasClass('overnight')) { classes = jQuery(this).attr('class').split(/\s+/); for (i = 0; i < classes.length; i++) { - if (classes[i].substr(0,6) == 'split-') {radio_active_list = classes[i];} + if (classes[i].substr(0,6) == 'split-') {radio_list_split = classes[i];} } } } else { jQuery(this).removeClass('nowplaying'); - if (radio_list_current) {jQuery(this).removeClass('before-current').addClass('after-current');} - else {jQuery(this).addClass('before-current').removeClass('after-current');} + if (start > radio.offset_time) {jQuery(this).addClass('after-current');} + else if (end < radio.offset_time) {jQuery(this).addClass('before-current');} } }); - if (radio_active_list) { - jQuery('.'+radio_active_list).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); + if (radio_list_split) { + jQuery('.'+radio_list_split).removeClass('before-current').removeClass('after-current').addClass('nowplaying'); } - if (!radio_list_current) {jQuery('.master-list-day-item').removeClass('before-current');} - }"; + }" . PHP_EOL; - // --- enqueue script inline --- - wp_add_inline_script( 'radio-station', $js ); + // --- filter and return --- + // 2.3.3.9: add filter and return instead of inline enqueue + $js = apply_filters( 'radio_station_master_schedule_list_js', $js ); + return $js; } diff --git a/includes/post-types-admin.php b/includes/post-types-admin.php index debb202..d58fc78 100644 --- a/includes/post-types-admin.php +++ b/includes/post-types-admin.php @@ -13,28 +13,6 @@ // - Add Language Metabox // - Language Selection Metabox // - Update Language Term on Save -// === Playlists === -// - Add Playlist Data Metabox -// - Playlist Data Metabox -// - Add Assign Playlist to Show Metabox -// - Assign Playlist to Show Metabox -// - Update Playlist Data -// - Add Playlist List Columns -// - Playlist List Column Data -// - Playlist List Column Styles -// === Posts === -// - Add Related Show Metabox -// - Related Show Metabox -// - Update Related Show -// - Related Show Quick Edit Select Input -// - Add Related Show Post List Column -// - Related Show Post List Column Data -// - Related Show Quick Edit Script -// - Add Bulk Edit Posts Action -// - Bulk Edit Posts Script -// - Bulk Edit Posts Handler -// - Bulk Edit Posts Notice -// - Related Show Post List Styles // === Shows === // - Add Show Info Metabox // - Show Info Metabox @@ -51,20 +29,49 @@ // - Add Show Images Metabox // - Show Images Metabox // - Update Show Metadata -// - Relogin AJAX Message // - Add Show List Columns // - Show List Column Data // - Show List Column Styles // === Schedule Overrides === +// - Add Override Show Data Metabox +// - Override Show Data Metabox +// - Override Show Data Script // - Add Schedule Override Metabox // - Schedule Override Metabox +// - Overrides Table List +// - Override List Edit Script // - Update Schedule Override // - Add Schedule Override List Columns // - Schedule Override Column Data // - Schedule Override Column Styles // - Sortable Override Date Column // - Add Schedule Override Month Filter -// === Post Type List Query Filter === +// - Add Schedule Past Future Filter +// === Playlists === +// - Add Playlist Data Metabox +// - Playlist Data Metabox +// - Add Assign Playlist to Show Metabox +// - Assign Playlist to Show Metabox +// - Update Playlist Data +// - Add Playlist List Columns +// - Playlist List Column Data +// - Playlist List Column Styles +// === Posts === +// - Add Related Shows Metabox +// - Related Shows Metabox +// - Update Relateds Show +// - Related Shows Quick Edit Select Input +// - Add Related Shows Post List Column +// - Related Shows Post List Column Data +// - Related Shows Quick Edit Script +// - Add Bulk Edit Posts Action +// - Bulk Edit Posts Script +// - Bulk Edit Posts Handler +// - Bulk Edit Posts Notice +// - Related Show Post List Styles +// === Extra Functions === +// - Post Type List Query Filter +// - Relogin AJAX Message // ------------------------- @@ -370,1425 +377,2559 @@ function radio_station_language_term_filter( $post_id ) { } -// ----------------- -// === Playlists === -// ----------------- +// ------------- +// === Shows === +// ------------- -// ------------------------- -// Add Playlist Data Metabox -// ------------------------- -// --- Add custom repeating meta field for the playlist edit form --- -// (Stores multiple associated values as a serialized string) -// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 -add_action( 'add_meta_boxes', 'radio_station_add_playlist_metabox' ); -function radio_station_add_playlist_metabox() { +// --------------------- +// Add Show Info Metabox +// --------------------- +add_action( 'add_meta_boxes', 'radio_station_add_show_info_metabox' ); +function radio_station_add_show_info_metabox() { // 2.2.2: change context to show at top of edit screen // 2.3.2: filter top metabox position - $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'playlist' ); + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'shows' ); add_meta_box( - 'radio-station-playlist-metabox', - __( 'Playlist Entries', 'radio-station' ), - 'radio_station_playlist_metabox', - RADIO_STATION_PLAYLIST_SLUG, + 'radio-station-show-info-metabox', + __( 'Show Information', 'radio-station' ), + 'radio_station_show_info_metabox', + RADIO_STATION_SHOW_SLUG, $position, 'high' ); } -// --------------------- -// Playlist Data Metabox -// --------------------- -function radio_station_playlist_metabox() { +// ----------------- +// Show Info Metabox +// ----------------- +function radio_station_show_info_metabox() { - global $post, $current_screen; + global $post; + $post_id = $post->ID; - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'playlist_tracks_nonce' ); + // 2.3.0: added missing nonce field + wp_nonce_field( 'radio-station', 'show_meta_nonce' ); - // --- get the saved meta as an array --- - // 2.3.2: set single argument to true - $entries = get_post_meta( $post->ID, 'playlist', true ); + // --- get show meta --- + // 2.3.2: added show download disable switch + $active = get_post_meta( $post_id, 'show_active', true ); + $link = get_post_meta( $post_id, 'show_link', true ); + $email = get_post_meta( $post_id, 'show_email', true ); + $phone = get_post_meta( $post_id, 'show_phone', true ); + $file = get_post_meta( $post_id, 'show_file', true ); + $download = get_post_meta( $post_id, 'show_download', true ); + $patreon_id = get_post_meta( $post_id, 'show_patreon', true ); - // --- set button titles --- - // 2.3.2: added titles for button icons - $move_up_title = __( 'Move Track Up', 'radio-station' ); - $move_down_title = __( 'Move Track Down', 'radio-station' ); - $duplicate_title = __( 'Duplicate Track', 'radio-station' ); - $remove_title = __( 'Remove Track', 'radio-station' ); + // added max-width to prevent metabox overflows + // 2.3.0: removed new lines between labels and fields and changed widths + // 2.3.2: increase label width to 120px for disable download field label + // 2.3.3.9: move meta_inner ID to class + echo '
      '; - echo '
      '; + echo '

      + + ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

      '; - // 2.3.2: separate track list table - echo radio_station_playlist_track_table( $entries ); + echo '

      +

      '; - // --- track save/add buttons --- - // 2.3.2: change track save from button-primary to button-secondary - // 2.3.2: added playlist AJAX save button (for existing posts only) - // 2.3.2: added playlist tracks clear button - // 2.3.2: added table and track saved message - echo ''; - echo '
      '; - echo ''; - echo ''; - if ( 'add' != $current_screen->action ) { - echo ''; - } - echo ''; - echo ''; - echo '
      '; - echo ''; - echo ''; - echo ''; - echo '
      '; + // 2.3.3.6: change text string from DJ / Host email (as maybe multiple hosts) + echo '

      +

      '; - echo '
      '; + // 2.3.3.6: added Show phone number input field + echo '

      +

      '; - // --- move new tracks message --- - // 2.3.2: added new track move message - echo '
      ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
      '; + echo '

      +

      '; - // --- clear all tracks function --- - $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); - $js = "function radio_tracks_clear() { - if (jQuery('#track-table tr').length) { - var agree = confirm('" . esc_js( $confirm_clear ) . "'); - if (!agree) {return false;} - jQuery('#track-table tr').remove(); - trackcount = 1; - } - }" . PHP_EOL; + // 2.3.2: added show download disable field + echo '

      +

      '; - // --- save tracks via AJAX --- - // 2.3.2: added form input cloning to save playlist tracks - $ajaxurl = admin_url( 'admin-ajax.php' ); - $js .= "function radio_tracks_save() { - jQuery('#track-save-form, #track-save-frame').remove(); - form = '
      '; - form += '
      '; - jQuery('#wpbody').append(form); - if (!jQuery('#track-save-frame').length) { - frame = ''; - jQuery('#wpbody').append(frame); - } - /* copy tracklist input fields and nonce */ - jQuery('#track-table input').each(function() {jQuery(this).clone().appendTo('#track-save-form');}); - jQuery('#track-table select').each(function() { - name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); - jQuery('').appendTo('#track-save-form'); - }); - jQuery('#playlist_tracks_nonce').clone().attr('id','').appendTo('#track-save-form'); - jQuery('#post_ID').clone().attr('id','').attr('name','playlist_id').appendTo('#track-save-form'); - jQuery('#tracks-saving-message').show(); - jQuery('#track-save-form').submit(); - }" . PHP_EOL; + // 2.3.0: added patreon page field + echo '

      '; + echo ' https://patreon.com/

      '; - // --- move track up or down --- - // 2.3.2: added move track function - $js .= "function radio_track_move(updown, n) { - /* swap track rows */ - if (updown == 'up') { - m = n - 1; - jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); - jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ - } - if (updown == 'down') { - m = n + 1; - jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); - jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); - /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ + // 2.3.3.5: added action for further custom fields + do_action( 'radio_station_show_fields', $post_id, 'show' ); + + echo '
      '; + + // --- inside show metaboxes --- + // 2.3.0: move metaboxes together inside meta + $inside_metaboxes = array( + 'hosts' => array( + 'title' => __( 'Show DJ(s) / Host(s)', 'radio-station' ), + 'callback' => 'radio_station_show_hosts_metabox', + ), + 'producers' => array( + 'title' => __( 'Show Producer(s)', 'radio-station' ), + 'callback' => 'radio_station_show_producers_metabox', + ), + 'languages' => array( + 'title' => __( 'Show Language(s)', 'radio-station' ), + 'callback' => 'radio_station_show_language_metabox', + ) + ); + + // --- display inside metaboxes --- + echo '
      '; + $i = 1; + foreach ( $inside_metaboxes as $key => $metabox ) { + + $classes = array( 'postbox' ); + if ( 1 == $i ) { + $classes[] = 'first'; + } elseif ( count( $inside_metaboxes ) == $i ) { + $classes[] = 'last'; } - /* reset track classes */ - radio_track_classes(); + $class = implode( ' ', $classes ); - /* swap track count */ - jQuery('#track-'+n+'-rowa .track-count').html(m); - jQuery('#track-'+m+'-rowa .track-count').html(n); + echo '
      ' . PHP_EOL; - /* swap input name keys */ - jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); - }); - jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); - }); + // echo ''; - /* swap button actions */ - jQuery('#track-'+n+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+m+');'); - jQuery('#track-'+n+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+m+');'); - jQuery('#track-'+m+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+n+');'); - jQuery('#track-'+m+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+n+');'); - jQuery('#track-'+n+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+m+');'); - jQuery('#track-'+n+'-rowb .track-remove').attr('onclick','radio_track_remove('+m+');'); - jQuery('#track-'+m+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+n+');'); - jQuery('#track-'+m+'-rowb .track-remove').attr('onclick','radio_track_remove('+n+');'); + // 2.3.2: remove class="hndle" to prevent box sorting + $widget_title = $metabox['title']; + echo '

      ' . esc_html( $metabox['title'] ) . '

      ' . PHP_EOL; + echo '
      ' . PHP_EOL; + call_user_func( $metabox['callback'] ); + echo '
      ' . PHP_EOL; - /* swap row IDs */ - jQuery('#track-'+m+'-rowa').attr('id', 'track-0-rowa'); - jQuery('#track-'+m+'-rowb').attr('id', 'track-0-rowb'); - jQuery('#track-'+n+'-rowa').attr('id', 'track-'+m+'-rowa'); - jQuery('#track-'+n+'-rowb').attr('id', 'track-'+m+'-rowb'); - jQuery('#track-0-rowa').attr('id', 'track-'+n+'-rowa'); - jQuery('#track-0-rowb').attr('id', 'track-'+n+'-rowb'); - }" . PHP_EOL; + echo '
      ' . PHP_EOL; - // --- reset first and last track classes --- - $js .= "function radio_track_classes() { - jQuery('.track-rowa, .track-rowb, .track-rowc').removeClass('first-track').removeClass('last-track'); - jQuery('.track-rowa').first().addClass('first-track'); jQuery('.track-rowa').last().addClass('last-track'); - jQuery('.track-rowb').first().addClass('first-track'); jQuery('.track-rowb').last().addClass('last-track'); - /* jQuery('.track-rowc').first().addClass('first-track'); jQuery('.track-rowc').last().addClass('last-track'); */ - }" . PHP_EOL; + $i++; + } + echo '
      '; - // --- add track function --- - // 2.3.0: set javascript as string to enqueue - // 2.3.2: added missing track-meta cell class - // 2.3.2: added track move arrows - // 2.3.2: added first and last row classes - // 2.3.2: set to standalone onclick function - $js .= "function radio_track_add() { - if (trackcount == 1) {classes = 'first-track last-track';} else {classes = 'last-track';} - output = ''; - output += ''+trackcount+''; - output += ''; - output += ''; - output += ''; - output += ''; - output += ''; - output += ''; - output += '" . esc_js( __( 'Comments', 'radio-station' ) ) . ": '; - output += '
      " . esc_js( __( 'New', 'radio-station' ) ) . ":
      '; - output += '
      '; - output += '
      " . esc_js( __( 'Status', 'radio-station' ) ) . ":
      '; - output += '
      '; - output += ''; - output += '
      " . esc_js( __( 'Move', 'radio-station') ) . "
      : '; - output += '
      '; - output += '
      '; - output += '
      '; - output += '
      '; - output += ''; - output += ''; + // --- output inside metabox styles --- + // 2.3.3.9: remove !important from display inline-block rule + echo ""; +} - /* output += ''; - output += ''; */ +// ------------------------------ +// Add Assign DJs to Show Metabox +// ------------------------------ +// 2.3.0: move inside show meta selection metabox to reduce clutter +// add_action( 'add_meta_boxes', 'radio_station_add_show_hosts_metabox' ); +function radio_station_add_show_hosts_metabox() { + // 2.2.2: add high priority to show at top of edit sidebar + // 2.3.0: change metabox title from DJs to DJs / Hosts + add_meta_box( + 'radio-station-show-hosts-metabox', + __( 'DJs / Hosts', 'radio-station' ), + 'radio_station_show_hosts_metabox', + RADIO_STATION_SHOW_SLUG, + 'side', + 'high' + ); +} + +// ---------------------------- +// Assign Hosts to Show Metabox +// ---------------------------- +function radio_station_show_hosts_metabox() { + + global $post, $wp_roles, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'show_hosts_nonce' ); + + // --- check for DJ / Host roles --- + // 2.3.0: simplified by using role__in argument + $args = array( + 'role__in' => array( 'dj', 'administrator' ), + 'orderby' => 'display_name', + 'order' => 'ASC' + ); + $hosts = get_users( $args ); + + // --- get the Hosts currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_user_list', true ); + if ( !$current ) { + $current = array(); + } + + // --- move any selected Hosts to the top of the list --- + foreach ( $hosts as $i => $host ) { + // 2.2.8: remove strict in_array checking + if ( in_array( $host->ID, $current ) ) { + unset( $hosts[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $hosts, $host ); // prepend the user to the beginning of the array + } + } + + // --- Host Selection Input --- + // 2.2.2: add fix to make DJ multi-select input full metabox width + // 2.3.3.9: move meta_inner ID to class + echo '
      '; + echo ''; + + // --- multiple selection helper text --- + // 2.3.0: added multiple selection helper text + echo '
      ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
      '; + echo '
      '; +} + +// ------------------------------------ +// Add Assign Producers to Show Metabox +// ------------------------------------ +// 2.3.0: move inside show meta selection metabox to reduce clutter +// add_action( 'add_meta_boxes', 'radio_station_add_show_producers_metabox' ); +function radio_station_add_show_producers_metabox() { + add_meta_box( + 'radio-station-show-producers-metabox', + __( 'Show Producer(s)', 'radio-station' ), + 'radio_station_show_producers_metabox', + RADIO_STATION_SHOW_SLUG, + 'side', + 'high' + ); +} + +// -------------------------------- +// Assign Producers to Show Metabox +// -------------------------------- +function radio_station_show_producers_metabox() { + + global $post, $wp_roles, $wpdb; + + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'show_producers_nonce' ); + + // --- check for Producer roles --- + $args = array( + 'role__in' => array( 'producer', 'administrator', 'show-editor' ), + 'orderby' => 'display_name', + 'order' => 'ASC' + ); + $producers = get_users( $args ); + + // --- get Producers currently assigned to the show --- + $current = get_post_meta( $post->ID, 'show_producer_list', true ); + if ( !$current ) { + $current = array(); + } + + // --- move any selected DJs to the top of the list --- + foreach ( $producers as $i => $producer ) { + if ( in_array( $producer->ID, $current ) ) { + unset( $producers[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item + array_unshift( $producers, $producer ); // prepend the user to the beginning of the array + } + } + + // --- Producer Selection Input --- + // 2.3.3.9: move meta_inner ID to class + echo '
      '; + echo ''; + + // --- multiple selection helper text --- + echo '
      ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
      '; + echo '
      '; +} + +// ----------------------- +// Add Show Shifts Metabox +// ----------------------- +// --- Adds schedule box to show edit screens --- +add_action( 'add_meta_boxes', 'radio_station_add_show_shifts_metabox' ); +function radio_station_add_show_shifts_metabox() { + // 2.2.2: change context to show at top of edit screen + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'shifts' ); + add_meta_box( + 'radio-station-show-shifts-metabox', + __( 'Show Schedule', 'radio-station' ), + 'radio_station_show_shifts_metabox', + RADIO_STATION_SHOW_SLUG, + $position, + 'low' + ); +} + +// ------------------- +// Show Shifts Metabox +// ------------------- +function radio_station_show_shifts_metabox() { + + global $post, $current_screen; + + // --- hidden debug fields --- + // 2.3.2: added save debugging field + if ( RADIO_STATION_DEBUG ) { + echo ''; + } + if ( RADIO_STATION_SAVE_DEBUG ) { + echo ''; + } + + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); + + // 2.3.3.9: move meta_inner ID to class + echo '
      '; + + echo '
      '; + + // 2.3.2: added to bypass shift check on add (for debugging) + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + echo ''; + } + + // --- output show shifts table --- + // 2.3.2: separated table function (for AJAX saving) + $table = radio_station_show_shifts_table( $post->ID ); + + // --- show inactive message --- + // 2.3.0: added show inactive reminder message + if ( !$table['active'] ) { + echo '
      '; + echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; + echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); + echo '
      '; + } + + // --- shift conflicts message --- + // 2.3.0: added instructions for fixing shift conflicts + // 2.3.3.9: check conflict count instead of boolean test + if ( count( $table['conflicts'] ) > 0 ) { + radio_station_shifts_conflict_message(); + } + + // --- output shift list --- + if ( '' != $table['list'] ) { + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; + } + echo '
      '; + + // --- shift save/add buttons --- + // 2.3.0: center add shift button + // 2.3.2: added show shifts AJAX save button (for existing posts only) + // 2.3.2: added show shifts clear button + // 2.3.2: added table and shifts saved message + // 2.3.2: fix centering by removing span wrapper + // 2.3.2: change from button-primary to button-secondary + // echo '' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; + echo '
      '; + echo ''; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '
      '; + echo ''; + echo ''; + if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { + echo ''; + } + echo ''; + echo ''; + echo '
      '; + echo ''; + echo ''; + echo ''; + echo '
      '; + echo '
      '; + + // --- get shift edit javascript --- + // 2.3.3.9: moved to separate function + $js = radio_station_shift_edit_script(); + + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + + // --- shift display styles --- + // 2.3.2: added dashed border to new shift + echo ''; + + // --- close meta inner --- + echo '
      '; +} + +// ----------------- +// Shift Edit Script +// ----------------- +function radio_station_shift_edit_script() { + + // --- set days, hours and minutes arrays --- + $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + $hours = $mins = array(); + for ( $i = 1; $i <= 12; $i ++ ) { + $hours[$i] = $i; + } + for ( $i = 0; $i < 60; $i ++ ) { + if ( $i < 10 ) { + $min = '0' . $i; + } else { + $min = $i; + } + $mins[$i] = $min; + } + + // 2.2.7: added meridiem translations + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + + // --- show shifts scripts --- + // 2.3.0: added confirmation to remove shift button + // 2.3.2: removed document ready functions wrapper + $c = 0; + $confirm_remove = __( 'Are you sure you want to remove this shift?', 'radio-station' ); + $confirm_clear = __( 'Are you sure you want to clear the shift list?', 'radio-station' ); + // $js = "var count = " . esc_attr( $c ) . ";"; - jQuery('#track-table').append(output); - trackcount++; - radio_track_classes(); - return false; + // --- clear all shifts function --- + $js = "function radio_shifts_clear() { + if (jQuery('#shifts-list').children().length) { + var agree = confirm('" . esc_js( $confirm_clear ) . "'); + if (!agree) {return false;} + jQuery('#shifts-list').children().remove(); + jQuery('
      ').appendTo('#shifts-list'); + } }" . PHP_EOL; - // --- duplicate track function --- - $js .= "function radio_track_duplicate(id) { - var i; var nextid = id + 1; - /* shift rows down */ - for (i = trackcount; i > id; i--) { - jQuery('#track-'+i+'-rowa, #track-'+i+'-rowb').each(function() { - jQuery(this).attr('id', jQuery(this).attr('id').replace(i, (i+1))); - jQuery(this).find('.track-count').html(i+1); - jQuery(this).find('input, select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+i+']', '['+(i+1)+']')); - }); - jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); - jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); - jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); - jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); - }); + // --- save shifts via AJAX --- + // 2.3.2: added form input cloning to saving show shifts + $ajaxurl = admin_url( 'admin-ajax.php' ); + $js .= "function radio_shifts_save() { + action = 'radio_station_show_save_shifts'; + if (jQuery('#single-shift').length && jQuery('#single-shift').prop('checked')) {action = 'radio_station_show_save_shift';} + jQuery('#shift-save-form, #shift-save-frame').remove(); + form = '
      '; + form += '
      '; + jQuery('#wpbody').append(form); + if (!jQuery('#shift-save-frame').length) { + frame = ''; + jQuery('#wpbody').append(frame); } - /* add duplicate row */ - jQuery('#track-'+id+'-rowa').clone().attr('id','track-'+nextid+'-rowa').insertAfter('#track-'+id+'-rowb'); - jQuery('#track-'+id+'-rowb').clone().attr('id','track-'+nextid+'-rowb').insertAfter('#track-'+nextid+'-rowa'); - jQuery('#track-'+nextid+'-rowa .track-count').html(nextid); - jQuery('#track-'+nextid+'-rowa, #track-'+nextid+'-rowb').each(function() { - jQuery(this).find('input, select').each(function() { - jQuery(this).attr('name', jQuery(this).attr('name').replace('['+id+']', '['+nextid+']')); - }); - jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\", '+nextid+');'); - jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\", '+nextid+');'); - jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+nextid+');'); - jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+nextid+');'); + /* copy shifts input fields and nonce */ + jQuery('#shifts-list input').each(function() {jQuery(this).clone().appendTo('#shift-save-form');}); + jQuery('#shifts-list select').each(function() { + name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); + jQuery('').appendTo('#shift-save-form'); }); - radio_track_classes(); - trackcount++; + jQuery('#show_shifts_nonce').clone().attr('id','').appendTo('#shift-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','show_id').appendTo('#shift-save-form'); + if (jQuery('#shift_ID').length) {jQuery('#shift_ID').attr('id','').attr('name','shift_id').appendTo('#shift-save-form');} + jQuery('#shifts-saving-message').show(); + jQuery('#shift-save-form').submit(); }" . PHP_EOL; - // --- remove track function --- - // 2.3.2: reset first and last classes on remove - // 2.3.2: set to standalone onclick function - $js .= "function radio_track_remove(id) { - jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb, #track-'+id+'-rowc').remove(); - radio_track_classes(); trackcount--; + // --- check select change --- + // 2.3.3: added select change detection + $js .= "function radio_check_select(el) { + val = el.options[el.selectedIndex].value; + if (val == '') {jQuery('#'+el.id).addClass('incomplete');} + else {jQuery('#'+el.id).removeClass('incomplete');} + origid = el.id.replace('shift-',''); + origval = jQuery('#'+origid).val(); + if (val == origval) {jQuery('#'+el.id).removeClass('changed');} + else {jQuery('#'+el.id).addClass('changed');} + uid = origid.substr(0,8); + radio_check_shift(uid); + }" . PHP_EOL; - /* renumber track count */ - var tcount = 1; - jQuery('.track-rowa').each(function() { - jQuery(this).find('.track-count').html(tcount); tcount++; + // --- check checkbox change --- + // 2.3.3: added checkbox change detection + $js .= "function radio_check_checkbox(el) { + val = el.checked ? 'on' : ''; + origid = el.id.replace('shift-',''); + origval = jQuery('#'+origid).val(); + if (val == origval) {jQuery('#'+el.id).removeClass('changed');} + else {jQuery('#'+el.id).addClass('changed');} + uid = origid.substr(0,8); + radio_check_shift(uid); + }" . PHP_EOL; + + // --- check shift change --- + // 2.3.3: added shift change detection + $js .= "function radio_check_shift(id) { + var shiftchanged = false; + jQuery('#shift-'+id).find('select,input').each(function() { + if ( (jQuery(this).attr('id').indexOf('shift-') == 0) && (jQuery(this).hasClass('changed')) ) { + shiftchanged = true; + } }); + if (shiftchanged) {jQuery('#shift-'+id).addClass('changed');} + else {jQuery('#shift-'+id).removeClass('changed');} + radio_check_shifts(); + }" . PHP_EOL; + + // 2.3.3.6: store possible existing onbeforeunload function + // (to help prevent conflicts with other plugins using this event) + $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . PHP_EOL; + $js .= "function radio_check_shifts() { + if (jQuery('.show-shift.changed').length) { + if (!onbeforeunloadset) { + storedonbeforeunload = window.onbeforeunload; + window.onbeforeunload = function() {return true;} + onbeforeunloadset = true; + } + } else { + if (onbeforeunloadset) { + window.onbeforeunload = storedonbeforeunload; + onbeforeunloadset = false; + } + } + }" . PHP_EOL; + + // --- add new shift --- + // 2.3.2: separate function for onclick + $js .= "function radio_shift_new() { + values = {}; + values.day = ''; + values.start_hour = ''; + values.start_min = ''; + values.start_meridian = ''; + values.end_hour = ''; + values.end_min = ''; + values.end_meridian = ''; + values.encore = ''; + values.disabled = ''; + radio_shift_add(values); + }" . PHP_EOL; + + // --- remove shift ---- + // 2.3.2: separate function for onclick + // 2.3.3: fix to jQuery targeting for new shifts + $js .= "function radio_shift_remove(el) { + agree = confirm('" . esc_js( $confirm_remove ) . "'); + if (!agree) {return false;} + shiftid = el.id.replace('shift-','').replace('-remove',''); + jQuery('#'+el.id).closest('.shift-wrapper').remove(); + }" . PHP_EOL; + + // --- duplicate shift --- + // 2.3.2: separate function for onclick + $js .= "function radio_shift_duplicate(el) { + shiftid = el.id.replace('shift-','').replace('-duplicate',''); + values = {}; + values.day = jQuery('#shift-'+shiftid+'-day').val(); + values.start_hour = jQuery('#shift-'+shiftid+'-start-hour').val(); + values.start_min = jQuery('#shift-'+shiftid+'-start-min').val(); + values.start_meridian = jQuery('#shift-'+shiftid+'-start-meridian').val(); + values.end_hour = jQuery('#shift-'+shiftid+'-end-hour').val(); + values.end_min = jQuery('#shift-'+shiftid+'-end-min').val(); + values.end_meridian = jQuery('#shift-'+shiftid+'-end-meridian').val(); + values.encore = ''; + if (jQuery('#shift-'+shiftid+'-encore').prop('checked')) {values.encore = 'on';} + values.disabled = 'yes'; + radio_shift_add(values); }" . PHP_EOL; - // --- set track count --- - // 2.3.2: set count from row count length - // 2.3.2: removed document ready wrapper - $js .= "var trackcount = jQuery('.track-rowa').length + 1;"; + // --- add shift function --- + // 2.3.2: added missing shift wrapper class + // 2.3.2: set new count based on new shift children + // 2.3.3: add input IDs so new shifts can be duplicated + $js .= "function radio_shift_add(values) { + var count = jQuery('#new-shifts').children().length + 1; + output = '
      '; + output += '
        '; + output += '
      • '; + output += '" . esc_js( __( 'Day', 'radio-station' ) ) . ": '; + output += ''; - echo ''; - echo ''; - echo ''; - echo ''; + // --- set empty show shift array --- + $times = array( + 'day' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + 'encore' => '', + 'disabled' => '', + ); + + // --- check and sanitize possibly posted times --- + // 2.3.3.9: for adding new show shift by querystring + if ( isset( $_REQUEST['day'] ) ) { + $times['day'] = $_REQUEST['day']; + if ( !in_array( $times['day'], $days ) ) { + $times['day'] = ''; + } + } + if ( isset( $_REQUEST['start_hour'] ) ) { + $start_hour = absint( $_REQUEST['start_hour'] ); + if ( ( $times['start_hour'] < 1 ) || ( $times['start_hour'] > 12 ) ) { + $times['start_hour'] = 12; + } + } + if ( isset( $_REQUEST['start_min'] ) ) { + $times['start_min'] = absint( $_REQUEST['start_min'] ); + if ( ( $times['start_min'] < 1 ) || ( $times['start_min'] > 60 ) ) { + $times['start_min'] = 0; + } + if ( $times['start_min'] < 10 ) { + $times['start_min'] = '0' . $times['start_min']; + } + } + if ( isset( $_REQUEST['start_meridian'] ) ) { + $times['start_meridian'] = $_REQUEST['start_meridian']; + if ( !in_array( $times['start_meridian'], array( '', 'am', 'pm' ) ) ) { + $times['start_meridian'] = ''; + } + } + if ( isset( $_REQUEST['end_hour'] ) ) { + $times['end_hour'] = absint( $_REQUEST['end_hour'] ); + if ( ( $times['end_hour'] < 1 ) || ( $times['end_hour'] > 12 ) ) { + $times['end_hour'] = 12; + } + } + if ( isset( $_REQUEST['end_min'] ) ) { + $times['end_min'] = absint( $_REQUEST['end_min'] ); + if ( ( $times['end_min'] < 1 ) || ( $times['end_min'] > 60 ) ) { + $times['end_min'] = 0; + } + if ( $times['end_min'] < 10 ) { + $times['end_min'] = '0' . $times['end_min']; + } + } + if ( isset( $_REQUEST['end_meridian'] ) ) { + $times['end_meridian'] = $_REQUEST['end_meridian']; + if ( !in_array( $times['end_meridian'], array( '', 'am', 'pm' ) ) ) { + $times['end_meridian'] = ''; + } + } + if ( isset( $_REQUEST['encore'] ) ) { + $times['encore'] = $_REQUEST['encore']; + if ( !in_array( $times['encore'], array( '', 'on' ) ) ) { + $times['encore'] = ''; + } + } + if ( isset( $_REQUEST['disabled'] ) ) { + $times['disabled'] = $_REQUEST['disabled']; + if ( !in_array( $times['disabled'], array( '', 'yes' ) ) ) { + $times['disabled'] = ''; + } + } - // --- track row b --- - echo ''; + $unique_id = radio_station_unique_shift_id(); + $shifts = array( $unique_id => $times ); + } - // --- track comments --- - echo '' . esc_html__( 'Comments', 'radio-station' ) . ' '; - echo ''; + $conflict_list = array(); + $list = ''; + if ( isset( $shifts ) && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { - // --- track meta --- - echo ''; - echo '
        ' . esc_html( __( 'New', 'radio-station' ) ) . ':
        '; - // 2.3.2: remove new value checking as now used and cleared on save - // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; - // ' . checked( $track['playlist_entry_new'] ) . ' - echo '
        '; - echo '
        ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
        '; - echo '
        '; + // 2.2.7: soft shifts by start day and time for ordered display + foreach ( $shifts as $unique_id => $shift ) { + // 2.3.0: add shift index to prevent start time overwriting + $j = 1; + $shift['unique_id'] = $unique_id; + $shift_start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $shift_start = radio_station_convert_shift_time( $shift_start ); + if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { + // --- group shifts by days of week --- + $starttime = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_start ); + // 2.3.0: simplify by getting day index + $i = array_search( $shift['day'], $days ); + $day_shifts[$i][$starttime . '.' . $j] = $shift; + } else { + // --- to still allow shift time sorting if day is not set --- + $starttime = radio_station_to_time( '1981-04-28 ' . $shift_start ); + $day_shifts[7][$starttime . '.' . $j] = $shift; + } + $j ++; + } - // 2.3.2: added move track arrows - echo ''; - echo '
        ' . esc_html( __( 'Move', 'radio-station') ) . ':
        '; - echo '
        '; - echo '
        '; + // --- sort day shifts by day and time --- + ksort( $day_shifts ); + // 2.3.0: resort order using start of week + $sorted_shifts = array(); + $weekdays = radio_station_get_schedule_weekdays(); + foreach ( $weekdays as $i => $weekday ) { + if ( isset( $day_shifts[$i] ) ) { + $sorted_shifts[$i] = $day_shifts[$i]; + } + } + if ( isset( $day_shifts[7] ) ) { + $sorted_shifts[7] = $day_shifts[7]; + } - // --- remove track button --- - echo '
        '; - echo '
        '; - echo ''; - echo ''; + // --- sort shifts by (unique) start time for each day --- + $show_shifts = array(); + foreach ( $sorted_shifts as $shift_day => $day_shift ) { + ksort( $day_shift ); + foreach ( $day_shift as $shift ) { + $unique_id = $shift['unique_id']; + unset( $shift['unique_id'] ); + $show_shifts[$unique_id] = $shift; + } + } + + // --- loop ordered show shifts --- + foreach ( $show_shifts as $unique_id => $shift ) { + + $classes = array( 'show-shift' ); + + // --- check conflicts with other show shifts --- + // 2.3.0: added shift conflict checking + $conflicts = radio_station_check_shift( $post_id, $shift ); + if ( $conflicts && is_array( $conflicts ) ) { + // 2.3.3.9: store conflicts by shift ID in array list + $conflict_list[$unique_id] = $conflicts; + $classes[] = 'conflicts'; + } + + // --- check if shift disabled --- + // 2.3.0: check shift disabled switch or show inactive + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $classes[] = 'disabled'; + } elseif ( !$active ) { + $classes[] = 'disabled'; + } + $classlist = implode( " ", $classes ); + + $list .= '
        '; + $list .= '
          '; + + // --- shift day selection --- + $list .= '
        • '; + $list .= esc_html( __( 'Day', 'radio-station' ) ) . ': '; + + $class = ( '' == $shift['day'] ) ? 'incomplete' : ''; + $list .= ''; + $list .= ''; + $list .= '
        • '; + + // --- shift start time --- + $list .= '
        • '; + $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ': '; + + // --- start hour selection --- + $class = ( '' == $shift['start_hour'] ) ? 'incomplete' : ''; + $list .= ''; + $list .= ''; + + // --- start minute selection --- + $list .= ''; + $list .= ''; + + // --- start meridiem selection --- + $class = ( '' == $shift['start_meridian'] ) ? 'incomplete' : ''; + $list .= ''; + $list .= ''; + + $list .= '
        • '; + + // --- shift end time --- + $list .= '
        • '; + $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ': '; + + // --- end hour selection --- + $class = ( '' == $shift['end_hour'] ) ? 'incomplete' : ''; + $list .= ''; + $list .= ''; + + // --- end minute selection --- + $list .= ''; + $list .= ''; + + // --- end meridiem selection --- + $class = ( '' == $shift['end_meridian'] ) ? 'incomplete' : ''; + $list .= ''; + $list .= ''; + $list .= '
        • '; + + // --- encore presentation --- + if ( !isset( $shift['encore'] ) ) { + $shift['encore'] = ''; + } + $list .= '
        • '; + $list .= ''; + $list .= ' ' . esc_html( __( 'Encore', 'radio-station' ) ); + $list .= ''; + $list .= '
        • '; + + // --- shift disabled --- + // 2.3.0: added disabled checkbox to shift row + if ( !isset( $shift['disabled'] ) ) { + $shift['disabled'] = ''; + } + $list .= '
        • '; + $list .= ''; + $list .= ' ' . esc_html( __( 'Disabled', 'radio-station' ) ); + $list .= ''; + $list .= '
        • '; + + // --- duplicate shift icon --- + // 2.3.0: added duplicate shift icon + $list .= '
        • '; + $title = __( 'Duplicate Shift', 'radio-station' ); + $list .= ''; + $list .= '
        • '; + + // --- remove shift icon --- + // 2.3.0: change remove button to icon + $list .= '
        • '; + $title = __( 'Remove Shift', 'radio-station' ); + $list .= ''; + $list .= '
        • '; - // --- track row c --- - // TODO: add track time / start / end input fields ? - // echo ''; - // echo ''; + $list .= '
        '; - $c ++; + // --- output any shift conflicts found --- + if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { + $list .= '
        '; + $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': '; + foreach ( $conflicts as $j => $conflict ) { + if ( $j > 0 ) { + $list .= ', '; + } + if ( $conflict['show'] == $post_id ) { + $list .= '' . esc_html( __('This Show', 'radio-station' ) ) . ''; + } else { + $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); + $show_title = get_the_title( $conflict['show'] ); + $list .= '' . esc_html( $show_title ) . ''; + } + $conflict_start = esc_html( $conflict['shift']['start_hour'] ) . ':' . esc_html( $conflict['shift']['start_min'] ) . ' ' . esc_html( $conflict['shift']['start_meridian'] ); + $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); + $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; + } + $list .= '

        '; } + + // --- close shift wrapper --- + $list .= '
        '; + } } - echo ''; + + // 2.3.2: moved into function and changed ID + // 2.3.3.9: change element from span to div + $list .= '
        '; + + // --- set return data --- + // 2.3.2: added for separated function + $table = array( + 'list' => $list, + 'active' => $active, + 'conflicts' => $conflict_list, + ); + + return $table; } // ----------------------------------- -// Add Assign Playlist to Show Metabox +// Add Show Description Helper Metabox // ----------------------------------- -// (add metabox for assigning playlist to show) -add_action( 'add_meta_boxes', 'radio_station_add_playlist_show_metabox' ); -function radio_station_add_playlist_show_metabox() { - // 2.2.2: add high priority to shift above publish box +// 2.3.0: added metabox for show description helper text +add_action( 'add_meta_boxes', 'radio_station_add_show_helper_box' ); +function radio_station_add_show_helper_box() { + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'helper' ); add_meta_box( - 'radio-station-playlist-show-metabox', - __( 'Linked Show', 'radio-station' ), - 'radio_station_playlist_show_metabox', - RADIO_STATION_PLAYLIST_SLUG, - 'side', - 'high' + 'radio-station-show-helper-box', + __( 'Show Description', 'radio-station' ), + 'radio_station_show_helper_box', + RADIO_STATION_SHOW_SLUG, + $position, + 'low' ); } // ------------------------------- -// Assign Playlist to Show Metabox +// Show Description Helper Metabox // ------------------------------- -function radio_station_playlist_show_metabox() { +// 2.3.0: added metabox for show description helper text +function radio_station_show_helper_box() { - global $post, $wpdb; + // --- show description helper text --- + echo '

        '; + echo esc_html( __( "The text field below is for your Show Description. It will display in the About section of your Show page.", 'radio-station' ) ); + echo ' ' . esc_html( __( "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.", 'radio-station' ) ); + echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . "
        "; + echo esc_html( __( "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.", 'radio-station' ) ); + echo ' ' . esc_html( __( "You can then assign them to a relevant Post Category for display on your site also.", 'radio-station' ) ); + echo '

        '; - $user = wp_get_current_user(); + // TODO: upgrade to Pro for upcoming Show Episodes blurb + // echo '
        ' . esc_html( 'In future, Radio Station Pro will include an Episodes post type', 'radio-station' ) ); + // TODO: change this text/link when Pro Episodes become available + // $upgrade_url = radio_station_get_upgrade_url(); + // echo '
        '; + // // echo esc_html( __( "Upgrade to Radio Station Pro', 'radio-station' ) ); + // echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); + // echo ' →.'; - // --- check that we have at least one show --- - // 2.3.0: moved up to check for any shows - $args = array( - 'numberposts' => - 1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', +} + +// ---------------------------------- +// Rename Show Featured Image Metabox +// ---------------------------------- +// 2.3.0: renamed from "Feature Image" to be clearer +// 2.3.0: removed this as now implementing show images separately +// (note this is the Show Logo for backwards compatibility reasons) +// add_action( 'do_meta_boxes', 'radio_station_rename_featured_image_metabox' ); +function radio_station_rename_featured_image_metabox() { + remove_meta_box( 'postimagediv', RADIO_STATION_SHOW_SLUG, 'side' ); + add_meta_box( + 'postimagediv', + __( 'Show Logo', 'radio-station' ), + 'post_thumbnail_meta_box', + RADIO_STATION_SHOW_SLUG, + 'side', + 'low' ); - $shows = get_posts( $args ); - if ( count( $shows ) > 0 ) { - $have_shows = true; - } else { - $have_shows = false; - } +} - // --- maybe restrict show selection to user-assigned shows --- - // 2.2.8: remove strict argument from in_array checking - // 2.3.0: added check for new Show Editor role - // 2.3.0: added check for edit_others_shows capability - if ( !in_array( 'administrator', $user->roles ) - && !in_array( 'show-editor', $user->roles ) - && !current_user_can( 'edit_others_shows' ) ) { +// ----------------------- +// Add Show Images Metabox +// ----------------------- +// 2.3.0: added show images metabox +add_action( 'add_meta_boxes', 'radio_station_add_show_images_metabox' ); +function radio_station_add_show_images_metabox() { + add_meta_box( + 'radio-station-show-images-metabox', + __( 'Show Images', 'radio-station' ), + 'radio_station_show_images_metabox', + array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ), + 'side', + 'low' + ); +} - // --- get the user lists for all shows --- - $allowed_shows = array(); - $query = "SELECT pm.meta_value, pm.post_id FROM " . $wpdb->prefix . "postmeta pm"; - $query .= " WHERE pm.meta_key = 'show_user_list'"; - $show_user_lists = $wpdb->get_results( $query ); +// ------------------- +// Show Images Metabox +// ------------------- +// 2.3.0: added show header and avatar image metabox +// ref: https://codex.wordpress.org/Javascript_Reference/wp.media +function radio_station_show_images_metabox() { - // ---- check each list for the current user --- - foreach ( $show_user_lists as $user_list ) { + global $post; - $user_list->meta_value = maybe_unserialize( $user_list->meta_value ); + if ( isset( $_GET['avatar_refix'] ) && ( 'yes' == $_GET['avatar_refix'] ) ) { + delete_post_meta( $post->ID, '_rs_image_updated', true ); + $show_avatar = radio_station_get_show_avatar_id( $post->ID ); + echo "Transferred ID: " . $show_avatar; + } - // --- if a list has no users, unserialize() will return false instead of an empty array --- - // (fix that to prevent errors in the foreach loop) - if ( !is_array( $user_list->meta_value ) ) { - $user_list->meta_value = array(); - } + wp_nonce_field( 'radio-station', 'show_images_nonce' ); + $upload_link = get_upload_iframe_src( 'image', $post->ID ); - // --- only include shows the user is assigned to --- - foreach ( $user_list->meta_value as $user_id ) { - if ( $user->ID === $user_id ) { - $allowed_shows[] = $user_list->post_id; - } - } - } + // --- get show avatar image info --- + $show_avatar = get_post_meta( $post->ID, 'show_avatar', true ); + $show_avatar_src = wp_get_attachment_image_src( $show_avatar, 'full' ); + $has_show_avatar = is_array( $show_avatar_src ); - $args = array( - 'numberposts' => - 1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'aSC', - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', - 'include' => implode( ',', $allowed_shows ), - ); + // --- show avatar image --- + echo '
        '; - $shows = get_posts( $args ); + // --- image container --- + echo '
        '; + if ( $has_show_avatar ) { + echo ''; } + echo '
        '; - echo '
        '; - if ( !$have_shows ) { - echo esc_html( __( 'No Shows were found.', 'radio-station' ) ); - } else { - if ( count( $shows ) < 1 ) { - echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ); - } else { - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); - - // --- select show to assign playlist to --- - $current = get_post_meta( $post->ID, 'playlist_show_id', true ); - echo ''; - } + // --- add and remove links --- + echo '

        '; + $hidden = ''; + if ( $has_show_avatar ) { + $hidden = ' hidden'; + } + echo ''; + echo esc_html( __( 'Set Show Avatar Image', 'radio-station' ) ); + echo ''; + $hidden = ''; + if ( !$has_show_avatar ) { + $hidden = ' hidden'; } + echo ''; + echo esc_html( __( 'Remove Show Avatar Image', 'radio-station' ) ); + echo ''; + echo '

        '; + + // --- hidden input for image ID --- + echo ''; + echo '
        '; -} -// -------------------- -// Update Playlist Data -// -------------------- -// --- When a playlist is saved, saves our custom data --- -// 2.3.2: added action for AJAX save of tracks -add_action( 'wp_ajax_radio_station_playlist_save_tracks', 'radio_station_playlist_save_data' ); -add_action( 'save_post', 'radio_station_playlist_save_data' ); -function radio_station_playlist_save_data( $post_id ) { + // --- check if show content header image is enabled --- + $header_image = radio_station_get_setting( 'show_header_image' ); + if ( $header_image ) { + + // --- get show header image info + $show_header = get_post_meta( $post->ID, 'show_header', true ); + $show_header_src = wp_get_attachment_image_src( $show_header, 'full' ); + $has_show_header = is_array( $show_header_src ); + + // --- show header image --- + echo '
        '; - // --- verify if this is an auto save routine --- - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return; - } + // --- image container --- + echo '
        '; + if ( $has_show_header ) { + echo ''; + } + echo '
        '; - // --- make sure we have a post ID for AJAX save --- - // 2.3.2: added AJAX track saving checks - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != $_REQUEST['action'] ) ) { - return; + // --- add and remove links --- + echo '

        '; + $hidden = ''; + if ( $has_show_header ) { + $hidden = ' hidden'; } - if ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { - return; + echo ''; + echo esc_html( __( 'Set Show Header Image', 'radio-station' ) ); + echo ''; + $hidden = ''; + if ( !$has_show_header ) { + $hidden = ' hidden'; } - $post_id = absint( $_POST['playlist_id'] ); - $post = get_post( $post_id ); + echo ''; + echo esc_html( __( 'Remove Show Header Image', 'radio-station' ) ); + echo ''; + echo '

        '; - $error = false; - if ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { - $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); - } elseif ( !$post ) { - $error = __( 'Failed. Invalid Playlist ID.', 'radio-station' ); - } elseif ( !current_user_can( 'edit_playlists' ) ) { - $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); - } + // --- hidden input for image ID --- + echo ''; - // --- send error message to parent window --- - if ( $error ) { - echo ""; + echo '
        '; - exit; - } } - // --- save playlist tracks --- - if ( isset( $_POST['playlist'] ) ) { - - // --- verify playlist nonce --- - // 2.3.2: fix OR condition to AND condition - if ( isset( $_POST['playlist_tracks_nonce'] ) - && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + // --- set images autosave nonce and iframe --- + $images_autosave_nonce = wp_create_nonce( 'show-images-autosave' ); + echo ''; + echo ''; - $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); + // --- image selection script --- + $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); + $js = "jQuery(function(){ - // move songs that are still queued to the end of the list so that order is maintained - foreach ( $playlist as $i => $song ) { - // 2.3.2: move songs marked as new to the end instead of queued - // if ( 'queued' === $song['playlist_entry_status'] ) { - if ( $song['playlist_entry_new'] ) { - // 2.3.2: unset before adding to maintain (now ordered) track count - // 2.3.2: unset new flag from track record now it has been moved - unset( $playlist[$i] ); - unset( $song['playlist_entry_new'] ); - $playlist[] = $song; - } - } - update_post_meta( $post_id, 'playlist', $playlist ); - } - } + var mediaframe, parentdiv, + imagesmetabox = jQuery('#radio-station-show-images-metabox'), + addimagelink = imagesmetabox.find('.upload-custom-image'), + deleteimagelink = imagesmetabox.find('.delete-custom-image'); - // --- sanitize and save related show ID --- - // 2.3.0: check for changes in related show ID - if ( isset( $_POST['playlist_show_id'] ) ) { + /* Add Image on Click */ + addimagelink.on( 'click', function( event ) { - // --- verify playlist related to show nonce --- - if ( isset( $_POST['playlist_show_nonce'] ) - && wp_verify_nonce( $_POST['playlist_show_nonce'], 'radio-station' ) ) { + event.preventDefault(); + parentdiv = jQuery(this).parent().parent(); - $changed = false; - $prev_show = get_post_meta( $post_id, 'playlist_show_id', true ); - $show = $_POST['playlist_show_id']; - if ( empty( $show ) ) { - delete_post_meta( $post_id, 'playlist_show_id' ); - if ( $prev_show ) { - $show = $prev_show; - $changed = true; - } - } else { - $show = absint( $show ); - if ( ( $show > 0 ) && ( $show != $prev_show ) ) { - update_post_meta( $post_id, 'playlist_show_id', $show ); - $changed = true; - } - } + if (mediaframe) {mediaframe.open(); return;} + mediaframe = wp.media({ + title: 'Select or Upload Image', + button: {text: 'Use this Image'}, + multiple: false + }); - // 2.3.0: maybe clear cached data to be safe - // 2.3.3: remove current show transient - // 2.3.4: add previous show transient - if ( $changed ) { - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - delete_transient( 'radio_station_previous_show' ); + mediaframe.on( 'select', function() { + var attachment = mediaframe.state().get('selection').first().toJSON(); + image = '\"\"'; + parentdiv.find('.custom-image-container').append(image); + parentdiv.find('.custom-image-id').val(attachment.id); + parentdiv.find('.upload-custom-image').addClass('hidden'); + parentdiv.find('.delete-custom-image').removeClass('hidden'); - // 2.3.4: delete all prefixed transients (for times) - radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); - radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); - radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); + /* auto-save image via AJAX */ + postid = '" . $post->ID . "'; imgid = attachment.id; + if (parentdiv.attr('id') == 'show-avatar-image') {imagetype = 'avatar';} + if (parentdiv.attr('id') == 'show-header-image') {imagetype = 'header';} + imagessavenonce = jQuery('#show-images-save-nonce').val(); + framesrc = ajaxurl+'?action=radio_station_show_images_save'; + framesrc += '&post_id='+postid+'&image_type='+imagetype; + framesrc += '&image_id='+imgid+'&_wpnonce='+imagessavenonce; + jQuery('#show-images-save-frame').attr('src', framesrc); + }); - do_action( 'radio_station_clear_data', 'show_meta', $show ); - } - } - } + mediaframe.open(); + }); - // --- AJAX saving --- - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == $_POST['action'] ) ) { + /* Delete Image on Click */ + deleteimagelink.on( 'click', function( event ) { + event.preventDefault(); + agree = confirm('" . esc_js( $confirm_remove ) . "'); + if (!agree) {return;} + parentdiv = jQuery(this).parent().parent(); + parentdiv.find('.custom-image-container').html(''); + parentdiv.find('.custom-image-id').val(''); + parentdiv.find('.upload-custom-image').removeClass('hidden'); + parentdiv.find('.delete-custom-image').addClass('hidden'); + }); - // --- display tracks saved message --- - $playlist_tracks_nonce = wp_create_nonce( 'radio-station' ); - echo ""; + });"; - // --- refresh track list table --- - $entries = get_post_meta( $post_id, 'playlist', true ); - echo radio_station_playlist_track_table( $entries ); - echo ""; + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); - exit; - } - } } -// -------------------- -// Relogin AJAX Message -// -------------------- -// 2.3.2: added for show shifts and playlist tracks AJAX -add_action( 'wp_ajax_nopriv_radio_station_show_save_shifts', 'radio_station_relogin_message' ); -add_action( 'wp_ajax_nopriv_radio_station_playlist_save_tracks', 'radio_station_relogin_message' ); -function radio_station_relogin_message() { - - if ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { - $type = 'shift'; - } elseif ( 'station_playlist_save_tracks' == $_REQUEST['action'] ) { - $type = 'track'; - } +// --------------------------------- +// AJAX to AutoSave Images on Change +// --------------------------------- +add_action( 'wp_ajax_radio_station_show_images_save', 'radio_station_show_images_save' ); +function radio_station_show_images_save() { - // --- send relogin message - $error = __( 'Failed. Relogin in try again.', 'radio-station' ); - echo ""; + global $post; - exit; -} + // --- sanitize posted values --- + if ( isset( $_GET['post_id'] ) ) { + $post_id = absint( $_GET['post_id'] ); + if ( $post_id < 1 ) { + unset( $post_id ); + } + } -// ------------------------- -// Add Playlist List Columns -// ------------------------- -// 2.2.7: added data columns to playlist list display -add_filter( 'manage_edit-' . RADIO_STATION_PLAYLIST_SLUG . '_columns', 'radio_station_playlist_columns', 6 ); -function radio_station_playlist_columns( $columns ) { - if ( isset( $columns['thumbnail'] ) ) { - unset( $columns['thumbnail'] ); + // 2.3.3.6: get post for checking capability + $post = get_post( $post_id ); + if ( !$post ) { + exit; } - if ( isset( $columns['post_thumb'] ) ) { - unset( $columns['post_thumb'] ); + + // --- check edit capability --- + if ( !current_user_can( 'edit_shows' ) ) { + exit; } - $date = $columns['date']; - unset( $columns['date'] ); - $comments = $columns['comments']; - unset( $columns['comments'] ); - $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); - $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); - $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); - $columns['comments'] = $comments; - $columns['date'] = $date; - return $columns; -} + // --- verify nonce value --- + if ( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( $_GET['_wpnonce'], 'show-images-autosave' ) ) { + exit; + } -// ------------------------- -// Playlist List Column Data -// ------------------------- -// 2.2.7: added data columns for show list display -add_action( 'manage_' . RADIO_STATION_PLAYLIST_SLUG . '_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); -function radio_station_playlist_column_data( $column, $post_id ) { - $tracks = get_post_meta( $post_id, 'playlist', true ); - if ( 'show' == $column ) { - $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); - $post = get_post( $show_id ); - echo "" . esc_html( $post->post_title ) . ""; - } elseif ( 'trackcount' == $column ) { - echo count( $tracks ); - } elseif ( 'tracklist' == $column ) { - echo ''; - echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . "
        "; - echo ''; } -} -// --------------------------- -// Playlist List Column Styles -// --------------------------- -add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); -function radio_station_playlist_column_styles() { - $currentscreen = get_current_screen(); - if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { - return; + if ( isset( $post_id ) && isset( $image_id ) && isset( $image_type ) ) { + update_post_meta( $post_id, 'show_' . $image_type, $image_id ); + } else { + exit; } - // --- playlist list styles --- - echo ""; + // --- add image updated flag --- + // (help prevent duplication on new posts) + $updated = get_post_meta( $post_id, '_rs_image_updated', true ); + if ( !$updated ) { + add_post_meta( $post_id, '_rs_image_updated', true ); + } - // --- expand/collapse tracklist data --- - $js = "function showhidetracklist(postid) { - if (document.getElementById('tracklist-'+postid).style.display == 'none') { - document.getElementById('tracklist-'+postid).style.display = ''; - } else {document.getElementById('tracklist-'+postid).style.display = 'none';} - }"; + // --- refresh parent frame nonce --- + $images_save_nonce = wp_create_nonce( 'show-images-autosave' ); + echo ""; - // --- enqueue script inline --- - // 2.3.0: enqueue instead of echo - wp_add_inline_script( 'radio-station-admin', $js ); + exit; } +// -------------------- +// Update Show Metadata +// -------------------- +// 2.3.2: added AJAX show save shifts action +// 2.3.3.9: added AJAX show save shift action +add_action( 'wp_ajax_radio_station_show_save_shift', 'radio_station_show_save_data' ); +add_action( 'wp_ajax_radio_station_show_save_shifts', 'radio_station_show_save_data' ); +add_action( 'save_post', 'radio_station_show_save_data' ); +function radio_station_show_save_data( $post_id ) { -// ------------- -// === Posts === -// ------------- + // --- verify if this is an auto save routine --- + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } -// ------------------------ -// Add Related Show Metabox -// ------------------------ -// (add metabox for show assignment on blog posts) -add_action( 'add_meta_boxes', 'radio_station_add_post_show_metabox' ); -function radio_station_add_post_show_metabox() { + // --- make sure we have a post ID for AJAX save --- + // 2.3.2: added AJAX shift saving checks + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + // 2.3.3: added double check for AJAX action match + if ( isset( $_REQUEST['action'] ) ) { + // 2.3.3.9: check for single or multiple shift save action + if ( 'radio_station_show_save_shift' == $_REQUEST['action'] ) { + $selection = 'single'; + if ( preg_match( '/^[a-zA-Z0-9_]+$/', $_REQUEST['shift_id'] ) ) { + $shift_id = $_REQUEST['shift_id']; + } + } elseif ( 'radio_station_show_save_shifts' == $_REQUEST['action'] ) { + $selection = 'multiple'; + } else { + return; + } + } else { + return; + } - // 2.3.0: moved check for shows inside metabox + // --- check for errors --- + $error = false; + if ( !current_user_can( 'edit_shows' ) ) { + $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['show_shifts_nonce'] ) || !wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['show_id'] ) || ( '' == $_POST['show_id'] ) ) { + $error = __( 'Error! No Show ID provided.', 'radio-station' ); + } else { + $post_id = absint( $_POST['show_id'] ); + $post = get_post( $post_id ); + if ( !$post ) { + $error = __( 'Failed. Invalid Show.', 'radio-station' ); + } + } - // ---- add a filter for which post types to show metabox on --- - $post_types = array( 'post' ); - $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); + // --- send error to parent frame --- + // 2.3.3.9: fix to remove shift save form not track save form + if ( $error ) { + echo ""; + exit; + } + } - // --- add the metabox to post types --- - add_meta_box( - 'radio-station-post-show-metabox', - __( 'Related to Show', 'radio-station' ), - 'radio_station_post_show_metabox', - $post_types, - 'side' - ); -} + // --- set show meta changed flags --- + $show_meta_changed = $show_shifts_changed = false; -// -------------------- -// Related Show Metabox -// -------------------- -function radio_station_post_show_metabox() { + // --- get posted DJ / host list --- + // 2.2.7: check DJ post value is set + if ( isset( $_POST['show_hosts_nonce'] ) && wp_verify_nonce( $_POST['show_hosts_nonce'], 'radio-station' ) ) { - global $post; + // 2.3.3.9: user check moved to input sanitization function + $hosts = radio_station_sanitize_input( 'show', 'user_list' ); - // 2.3.3.6: store current post global - $stored_post = $post; + // 2.3.3.9: fix to get previous hosts before updating + $prev_hosts = get_post_meta( $post_id, 'show_user_list', true ); + if ( $prev_hosts != $hosts ) { + update_post_meta( $post_id, 'show_user_list', $hosts ); + $show_meta_changed = true; + } + } - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'post_show_nonce' ); + // --- get posted show producers --- + // 2.3.0: added show producer sanitization + if ( isset( $_POST['show_producers_nonce'] ) && wp_verify_nonce( $_POST['show_producers_nonce'], 'radio-station' ) ) { - $args = array( - 'numberposts' => - 1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', // ??? - ); - $shows = get_posts( $args ); + // 2.3.3.9: user check moved to input sanitization function + $producers = radio_station_sanitize_input( 'show', 'producer_list' ); - // --- get current selection --- - $selected = get_post_meta( $post->ID, 'post_showblog_id', true ); - // 2.3.3.4: convert existing selection to array - if ( !$selected ) { - $selected = array(); - } elseif ( !is_array( $selected ) ) { - $selected = array( $selected ); + // 2.3.0: added save of show producers + // 2.3.3.9: fix to get previous producers before updating + $prev_producers = get_post_meta( $post_id, 'show_producer_list', true ); + if ( $prev_producers != $producers ) { + update_post_meta( $post_id, 'show_producer_list', $producers ); + $show_meta_changed = true; + } } - // 2.3.3.6: remove possible saved zero value - // 2.3.3.9: fix to duplicate use of selected variable - if ( count( $selected ) > 0 ) { - foreach ( $selected as $i => $value ) { - if ( 0 == $value ) { - unset( $selected[$i] ); + + // --- save show meta data --- + // 2.3.0: added separate nonce check for show meta + if ( isset( $_POST['show_meta_nonce'] ) && wp_verify_nonce( $_POST['show_meta_nonce'], 'radio-station' ) ) { + + // --- get the meta data to be saved --- + // 2.3.2: added download disable switch + // 2.2.3: added show metadata value sanitization + // 2.3.3.9: use input sanitization function + $file = radio_station_sanitize_input( 'show', 'file' ); + $email = radio_station_sanitize_input( 'show', 'email' ); + $link = radio_station_sanitize_input( 'show' , 'link' ); + $patreon_id = radio_station_sanitize_input( 'show', 'patreon' ); + $phone = radio_station_sanitize_input( 'show', 'phone' ); + $active = radio_station_sanitize_input( 'show', 'active' ); + $download = radio_station_sanitize_input( 'show', 'download' ); + + // --- get existing values and check if changed --- + // 2.3.0: added check against previous values + // 2.3.2: added download disable switch + // 2.3.3.6: added phone number field saving + $prev_file = get_post_meta( $post_id, 'show_file', true ); + $prev_download = get_post_meta( $post_id, 'show_download', true ); + $prev_email = get_post_meta( $post_id, 'show_email', true ); + $prev_phone = get_post_meta( $post_id, 'show_phone', true ); + $prev_active = get_post_meta( $post_id, 'show_active', true ); + $prev_link = get_post_meta( $post_id, 'show_link', true ); + $prev_patreon_id = get_post_meta( $post_id, 'show_patreon', true ); + if ( ( $prev_active != $active ) || ( $prev_link != $link ) + || ( $prev_email != $email ) || ( $prev_phone != $phone ) + || ( $prev_file != $file ) || ( $prev_download != $download ) + || ( $prev_patreon_id != $patreon_id ) ) { + $show_meta_changed = true; + } + + // --- update the show metadata --- + // 2.3.2: added download disable switch + update_post_meta( $post_id, 'show_file', $file ); + update_post_meta( $post_id, 'show_download', $download ); + update_post_meta( $post_id, 'show_email', $email ); + update_post_meta( $post_id, 'show_phone', $phone ); + update_post_meta( $post_id, 'show_active', $active ); + update_post_meta( $post_id, 'show_link', $link ); + update_post_meta( $post_id, 'show_patreon', $patreon_id ); + } + + + // --- update the show images --- + if ( isset( $_POST['show_images_nonce'] ) && wp_verify_nonce( $_POST['show_images_nonce'], 'radio-station' ) ) { + + // --- show header image --- + if ( isset( $_POST['show_header'] ) ) { + $header = absint( $_POST['show_header'] ); + if ( $header > 0 ) { + // $prev_header = get_post_meta( $post_id, 'show_header', true ); + // if ( $header != $prev_header ) {$show_meta_changed = true;} + update_post_meta( $post_id, 'show_header', $header ); } } + + // --- show avatar image --- + $avatar = absint( $_POST['show_avatar'] ); + if ( $avatar > 0 ) { + // $prev_avatar = get_post_meta( $post_id, 'show_avatar', true ); + // if ( $avatar != $prev_avatar ) {$show_meta_changed = true;} + update_post_meta( $post_id, 'show_avatar', $avatar ); + } + + // --- add image updated flag --- + // (to prevent duplication for new posts) + $updated = get_post_meta( $post_id, '_rs_image_updated', true ); + if ( !$updated ) { + add_post_meta( $post_id, '_rs_image_updated', true ); + } } - echo '
        '; + // --- check show shift nonce --- + if ( isset( $_POST['show_shifts_nonce'] ) && wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { - if ( count( $shows ) > 0 ) { + // --- loop posted show shift times --- + // 2.3.1: added check if any shifts are set (fix undefined index warning) + $shifts = $new_shifts = array(); + if ( isset( $_POST['show_sched'] ) ) { + $shifts = $_POST['show_sched']; + } + $prev_shifts = radio_station_get_show_schedule( $post_id ); + $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + foreach ( $shifts as $i => $shift ) { - // --- select related show input --- - // 2.2.3.4: allow for multiple selections - echo ''; - } else { - // --- no shows message --- - echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); - } - echo '
        '; + } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { - // --- related shows post box styles --- - // 2.3.3.6: add style for pre-selected option - echo ""; + // --- check shift start and end minute --- + if ( empty( $value ) ) { + // 2.3.0: auto-set minute value to 00 if empty + $isvalid = true; + $value = '00'; + } elseif ( ( absint( $value ) > - 1 ) && ( absint( $value ) < 61 ) ) { + $isvalid = true; + } else { + $disabled = true; + } - // 2.3.3.6: revert current post global - $post = $stored_post; -} + } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { -// ------------------- -// Update Related Show -// ------------------- -add_action( 'save_post', 'radio_station_post_save_data' ); -function radio_station_post_save_data( $post_id ) { + // --- check shift meridiem --- + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + if ( '' == $value ) { + $disabled = true; + } - // --- do not save when doing autosaves --- - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return; - } + } elseif ( 'encore' === $key ) { - // 2.3.3.6: store post for capability checking - global $post; - $stored_post = $post; + // --- check shift encore switch --- + // 2.2.4: fix to missing encore sanitization saving + $valid = array( '', 'on' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } - // --- check related show field is set --- - // 2.3.0: added check if changed - if ( isset( $_POST['post_showblog_id'] ) ) { + } elseif ( 'disabled' == $key ) { - // --- verify field save nonce --- - if ( !isset( $_POST['post_show_nonce'] ) || !wp_verify_nonce( $_POST['post_show_nonce'], 'radio-station' ) ) { - return; - } + // --- check shift disabled switch --- + // 2.3.0: added shift disable switch + // note: overridden on incomplete data or shift conflict + $valid = array( '', 'yes' ); + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + if ( 'yes' == $value ) { + $disabled = true; + } - // --- get the related show ID --- - $changed = false; - $current_shows = get_post_meta( $post_id, 'post_showblog_id', true ); - $show_ids = $_POST['post_showblog_id']; + } - // 2.3.3.6: maybe add existing (uneditable) Show IDs - $new_show_ids = array(); - if ( $current_shows && is_array( $current_shows ) && ( count( $current_shows ) > 0 ) ) { - foreach ( $current_shows as $current_show ) { - if ( $current_show > 0 ) { - $post = get_post( $current_show ); - if ( $post && !current_user_can( 'edit_shows' ) ) { - $new_show_ids[] = $current_show; + // --- if valid add to new schedule --- + if ( $isvalid ) { + $new_shifts[$i][$key] = $value; + } else { + $new_shifts[$i][$key] = ''; } } - } - } - if ( !empty( $show_ids ) ) { - // --- sanitize to numeric before updating --- - // 2.3.3.4: maybe sanitize multiple array values - if ( !is_array( $show_ids ) ) { - $show_ids = array( $show_ids ); - } - foreach ( $show_ids as $i => $show_id ) { - $show_id = absint( trim( $show_id ) ); - // 2.3.3.6: check show ID value is above zero not -1 - if ( $show_id > 0 ) { - // 2.3.3.6: check edit Show capability before adding - $post = get_post( $show_id ); - if ( $post && current_user_can( 'edit_shows' ) && !in_array( $show_id, $new_show_ids ) ) { - $new_show_ids[] = $show_id; + // --- check for shift conflicts with other shows --- + // 2.3.0: added show shift conflict checking + if ( !$disabled ) { + $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'shows' ); + if ( $conflicts ) { + $disabled = true; + if ( RADIO_STATION_DEBUG ) { + echo "*Conflicting Shift Disabled*"; + } } } - } - } - // --- delete or update Show IDs for post --- - // 2.3.3.6: check existing versus new show ID values - if ( 0 == count( $new_show_ids ) ) { - delete_post_meta( $post_id, 'post_showblog_id' ); - $changed = true; - } elseif ( $new_show_ids != $current_shows ) { - update_post_meta( $post_id, 'post_showblog_id', $new_show_ids ); - $changed = true; - } + // --- disable if incomplete data or shift conflicts --- + if ( $disabled ) { + $new_shifts[$i]['disabled'] = 'yes'; + if ( RADIO_STATION_DEBUG ) { + echo "*Shift Disabled*"; + } + } - // 2.3.0: clear cached data to be safe - // 2.3.3: remove current show transient - // 2.3.4: add previous show transient - if ( $changed ) { - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - delete_transient( 'radio_station_previous_show' ); + } - // 2.3.4: delete all prefixed transients (for times) - radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); - radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); - radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); + // --- recheck for conflicts with other shifts for this show --- + // 2.3.0: added new shift conflict checking + $new_shifts = radio_station_check_new_shifts( $new_shifts ); - do_action( 'radio_station_clear_data', 'show_meta', $show ); + // --- update the schedule meta entry --- + // 2.3.0: check if shift times have changed before saving + if ( $new_shifts != $prev_shifts ) { + $show_shifts_changed = true; + update_post_meta( $post_id, 'show_sched', $new_shifts ); + } + } else { + // 2.3.0: fix to clear data if all shifts removed + delete_post_meta( $post_id, 'show_sched' ); + $show_shifts_changed = true; } } - // 2.3.3.6 restore stored post object - $post = $stored_post; -} - -// ------------------------------------ -// Related Show Quick Edit Select Input -// ------------------------------------ -// 2.3.3.4: added Related Show field to Post List Quick Edit -add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_post', 10, 2 ); -function radio_station_quick_edit_post( $column_name, $post_type ) { - - global $post; - $stored_post = $post; - - // 2.3.3.5: added fix for post type context - if ( $post_type != 'post' ) { - return; + // --- maybe clear transient data --- + // 2.3.0: added to clear transients if any meta has changed + // 2.3.3: remove current show transient + // 2.3.3.9: just call clear cached data function + if ( $show_meta_changed || $show_shifts_changed ) { + radio_station_clear_cached_data( $post_id ); } - // --- get all shows --- - $args = array( - 'numberposts' => - 1, - 'offset' => 0, - 'orderby' => 'post_title', - 'order' => 'ASC', - 'post_type' => RADIO_STATION_SHOW_SLUG, - 'post_status' => 'publish', // ??? - ); - $shows = get_posts( $args ); + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - echo ''; + // 2.3.3.9: removed check of AJAX action as done earlier - // --- related shows post box styles --- - // 2.3.3.6: add style for pre-selected option - echo ""; + // --- (hidden) debug information --- + echo "Posted Shifts: " . print_r( $_POST['show_sched'], true ) . PHP_EOL; + echo "New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; - // 2.3.3.6: restore stored post object - $post = $stored_post; -} + // --- display shifts saved message --- + // 2.3.3.9: fade out shifts saved message + $show_shifts_nonce = wp_create_nonce( 'radio-station' ); + echo ""; -// --------------------------------- -// Add Related Show Post List Column -// --------------------------------- -// 2.3.3.4: added Related Show Column to Post List -add_filter( 'manage_edit-post_columns', 'radio_station_post_columns', 6 ); -function radio_station_post_columns( $columns ) { - $columns['show'] = esc_attr( __( 'Show(s)', 'radio-station' ) ); - return $columns; -} + // 2.3.3.9: added check if show shifts changed + if ( $show_shifts_changed ) { -// ---------------------------------- -// Related Show Post List Column Data -// ---------------------------------- -// 2.3.3.4: added Related Show Column to Post List -add_action( 'manage_post_posts_custom_column', 'radio_station_post_column_data', 5, 2 ); -function radio_station_post_column_data( $column, $post_id ) { - if ( 'show' == $column ) { + // --- output new show shifts list --- + echo '
        '; + if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { + echo ''; + } + $table = radio_station_show_shifts_table( $post_id ); + // 2.3.3.9: check conflict count instead of boolean test + if ( count( $table['conflicts'] ) > 0 ) { + radio_station_shifts_conflict_message(); + } + + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; - // 2.3.3.6: store global post object while capability checking - global $post; - $stored_post = $post; + echo '
        '; + echo '
        '; - // --- get Shows linked to Post --- - $data = ''; - $show_ids = $disabled = array(); - $show_id = get_post_meta( $post_id, 'post_showblog_id', true ); - if ( $show_id ) { - // 2.3.3.6: add check to ignore possible saved zero value - if ( is_array( $show_id ) ) { - $show_ids = $show_id; - foreach ( $show_ids as $i => $show_id ) { - if ( 0 == $show_id ) { - unset( $show_ids[$i] ); - } + // --- refresh show shifts list --- + echo ""; + + // 2.3.3.9: trigger action for single or multiple shift save + if ( isset( $selection ) ) { + if ( 'single' == $selection ) { + do_action( 'radio_station_show_save_shift' ); + } else { + do_action( 'radio_station_show_save_shifts' ); } - // 2.3.3.8: fix to implode show_ids not show_id - $data = implode( ',', $show_ids ); - } elseif ( $show_id > 0 ) { - $show_ids = array( $show_id ); - $data = $show_id; } - } - // --- display Shows linked to post --- - if ( count( $show_ids ) > 0 ) { - foreach ( $show_ids as $show_id ) { - $show = get_post( trim( $show_id ) ); - if ( $show ) { - // 2.3.3.6: only link to Shows user can edit - $post = $show; - if ( current_user_can( 'edit_shows' ) ) { - echo ''; - } else { - // 2.3.3.6: set disabled (uneditable) data - $disabled[] = $show_id; + // 2.3.3.6: clear changes may not have been saved window reload message + echo ""; + + // --- alert on conflicts --- + // 2.3.3.9: check conflict count instead of boolean test + if ( count( $table['conflicts'] ) > 0 ) { + // 2.3.3.9: maybe skip conflict message if single shift save has no conflict + $display_warning = true; + if ( isset( $selection ) && ( 'single' == $selection ) && isset( $shift_id ) ) { + $display_warning = $found_conflict = false; + foreach ( $table['conflicts'] as $unique_id => $conflict ) { + if ( $shift_id == $unique_id ) { + $found_conflict = true; + } } - echo esc_html( $show->post_title ) . '
        '; - if ( current_user_can( 'edit_shows' ) ) { - echo '
        '; + if ( $found_conflict ) { + $display_warning = true; } } + if ( $display_warning ) { + $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); + echo ""; + } } + + // --- reload the current schedule view --- + echo ""; + } - echo ''; - echo ''; - // --- restore global post object --- - $post = $stored_post; + exit; } + } -// ------------------------------ -// Related Show Quick Edit Script -// ------------------------------ -// 2.3.3.4: added Related Show Quick Edit value population script -// ref: https://codex.wordpress.org/Plugin_API/Action_Reference/quick_edit_custom_box -// 2.3.3.6: disable uneditable Show select options -add_action( 'admin_enqueue_scripts', 'radio_station_posts_quick_edit_script' ); -function radio_station_posts_quick_edit_script( $hook ) { +// ----------------------------------- +// Clear Schedule Cache on Delete Show +// ----------------------------------- +// 2.3.3.9: added to update schedule on show/override deletions +add_action( 'delete_post', 'radio_station_show_delete', 10, 2 ); +function radio_station_show_delete( $post_id, $post ) { - if ( 'edit.php' != $hook ) { + if ( !property_exists( $post, 'post_type' ) ) { + $post = get_post( $post_id ); + } + $post_types = array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ); + if ( !in_array( $post->post_type, $post_types ) ) { return; } - // 2.3.3.7: use jQuery instead of \$ for better compatibility - if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { - $js = "(function($) { - var \$wp_inline_edit = inlineEditPost.edit; - inlineEditPost.edit = function( id ) { - \$wp_inline_edit.apply(this, arguments); - var post_id = 0; var disabled_ids; - if (typeof(id) == 'object') {post_id = parseInt(this.getId(id));} - if (post_id > 0) { - var show_ids = jQuery('#post-'+post_id+' .column-show .show-ids').text(); - if (show_ids != '') { - if (show_ids.indexOf(',') > -1) {ids = show_ids.split(',');} - else {ids = new Array(); ids[0] = show_ids;} - for (i = 0; i < ids.length; i++) { - var thisshowid = ids[i]; - jQuery('#edit-'+post_id+' .select-show option').each(function() { - if (jQuery(this).val() == thisshowid) {jQuery(this).attr('selected','selected');} - }); - } - /* disable uneditable options */ - disabled = jQuery('#post-'+post_id+' .column-show .disabled-ids').text(); - if (disabled != '') { - if (disabled.indexOf(',') > -1) {disabled_ids = disabled.split(',');} - else {disabled_ids = new Array(); disabled_ids[0] = disabled;} - jQuery('#edit-'+post_id+' .select-show option').each(function() { - for (j = 0; j < disabled_ids.length; j++) { - if (jQuery(this).val() == disabled_ids[j]) { - jQuery(this).attr('disabled','disabled'); - if (jQuery(this).attr('selected') == 'selected') {jQuery(this).addClass('pre-selected');} - } - } - }); - } - } - } - }; - })(jQuery);"; + // --- clear all cached schedule data --- + radio_station_clear_cached_data( $post_id ); - wp_add_inline_script( 'radio-station-admin', $js ); +} + +// ----------------- +// Save Output Debug +// ----------------- +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 0 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 0 ); +function radio_station_save_debug_start( $post_id ) { + if ( !RADIO_STATION_SAVE_DEBUG ) { + return; + } + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } + ob_start(); +} +add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 9999 ); +add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 9999 ); +function radio_station_save_debug_end( $post_id ) { + if ( !RADIO_STATION_SAVE_DEBUG ) { + return; + } + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } + $contents = ob_get_contents(); + ob_end_clean(); + if ( strlen( $contents ) > 0 ) { + echo "Output Detected During Save (preventing redirect):
        "; + echo ''; + exit; } } +// --------------------- +// Add Show List Columns +// --------------------- +// 2.2.7: added data columns to show list display +add_filter( 'manage_edit-' . RADIO_STATION_SHOW_SLUG . '_columns', 'radio_station_show_columns', 6 ); +function radio_station_show_columns( $columns ) { -// -------------------------- -// Add Bulk Edit Posts Action -// -------------------------- -// 2.3.3.4: add action to Bulk Edit list -// ref: https://dream-encode.com/wordpress-custom-bulk-actions/ -add_filter( 'bulk_actions-edit-post', 'radio_station_show_posts_bulk_edit_action' ); -function radio_station_show_posts_bulk_edit_action( $bulk_actions ) { - $bulk_actions['related_show'] = __( 'Set Related Show(s)', 'radio-station' ); - return $bulk_actions; + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); + } + + $date = $columns['date']; + unset( $columns['date'] ); + $comments = $columns['comments']; + unset( $columns['comments'] ); + $genres = $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] ); + $languages = $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] ); + + $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); + // 2.3.0: added show description indicator column + $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); + $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); + // 2.3.0: change DJs column label to Hosts + $columns['hosts'] = esc_attr( __( 'Hosts', 'radio-station' ) ); + $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] = $genres; + $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] = $languages; + $columns['comments'] = $comments; + $columns['date'] = $date; + $columns['show_image'] = esc_attr( __( 'Show Avatar', 'radio-station' ) ); + + return $columns; } -// ---------------------- -// Bulk Edit Posts Script -// ---------------------- -// 2.3.3.4: add script for Bulk Edit action -add_action( 'admin_enqueue_scripts', 'radio_station_show_posts_bulk_edit_script' ); -function radio_station_show_posts_bulk_edit_script( $hook ) { +// --------------------- +// Show List Column Data +// --------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_' . RADIO_STATION_SHOW_SLUG . '_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); +function radio_station_show_column_data( $column, $post_id ) { - if ( 'edit.php' != $hook ) { - return; - } + if ( 'active' == $column ) { + + $active = get_post_meta( $post_id, 'show_active', true ); + if ( 'on' == $active ) { + echo esc_html( __( 'Yes', 'radio-station' ) ); + } else { + echo esc_html( __( 'No', 'radio-station' ) ); + } + + } elseif ( 'description' == $column ) { + + // 2.3.0: added show description indicator + global $wpdb; + $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $post_id ); + $content = $wpdb->get_var( $query ); + if ( !$content || ( trim( $content ) == '' ) ) { + // 2.3.3.9: change bold emphasis to italics + echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + } else { + echo esc_html( __( 'Yes', 'radio-station' ) ); + } + + } elseif ( 'shifts' == $column ) { + + $active = get_post_meta( $post_id, 'show_active', true ); + if ( 'on' == $active ) { + $active = true; + } + + // 2.3.0: check using dates for reliability + $now = radio_station_get_now(); + $weekdays = radio_station_get_schedule_weekdays(); + $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); + + $shifts = get_post_meta( $post_id, 'show_sched', true ); + if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + + $sorted_shifts = $dayless_shifts = array(); + foreach ( $shifts as $shift ) { + // 2.3.2: added check that shift day is not empty + if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { + // 2.3.2: fix to convert shift time to 24 hour format + $shift_time = $shift['start_hour'] . ":" . $shift['start_min'] . ' ' . $shift['start_meridian']; + $shift_time = radio_station_convert_shift_time( $shift_time ); + $shift_time = $weekdates[$shift['day']] . $shift_time; + $timestamp = radio_station_to_time( $shift_time ); + $sortedshifts[$timestamp] = $shift; + } else { + $dayless_shifts[] = $shift; + } + } + ksort( $sortedshifts ); + + foreach ( $sortedshifts as $shift ) { - // 2.3.3.7: use jQuery instead of \$ for better compatibility - // 2.3.3.7: do not reclone the show field if it already exists - if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { - $js = "jQuery(document).ready(function() { - jQuery('#bulk-action-selector-top, #bulk-action-selector-bottom').on('change', function(e) { - if (jQuery(this).val() == 'related_show') { - /* clone the Quick Edit fieldset to after bulk action selector */ - if (!jQuery(this).parent().find('.related-show-field').length) { - jQuery('.related-show-field').first().clone().insertAfter(jQuery(this)); + // 2.3.0: highlight disabled shifts + $classes = array( 'show-shift' ); + $disabled = false; + $title = ''; + if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { + $disabled = true; + $classes[] = 'disabled'; + $title = __( 'This Shift is Disabled.', 'radio-station' ); + } + + // --- check and highlight conflicts --- + // 2.3.0: added shift conflict checking + $conflicts = radio_station_check_shift( $post_id, $shift ); + if ( $conflicts ) { + $classes[] = 'conflict'; + if ( $disabled ) { + $title = __( 'This Shift has Schedule Conflicts and is Disabled.', 'radio-station' ); + } else { + $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); } - } else { - jQuery(this).find('.related-show-field').remove(); } - }); - });"; - wp_add_inline_script( 'radio-station-admin', $js ); - } -} + // 2.3.0: also highlight if the show is not active + if ( !$active ) { + if ( !in_array( 'disabled', $classes ) ) { + $classes[] = 'disabled'; + } + $title = __( 'This Show is not currently active.', 'radio-station' ); + } + $classlist = implode( ' ', $classes ); -// ----------------------- -// Bulk Edit Posts Handler -// ----------------------- -// 2.3.3.4: add handler for bulk edit action -add_filter( 'handle_bulk_actions-edit-post', 'radio_station_posts_bulk_edit_handler', 10, 3 ); -function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids ) { + echo "
        "; - global $post; - $stored_post = $post; + // --- get shift start and end times --- + // 2.3.2: fix to convert to 24 hour time + $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); + $end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); - if ( 'related_show' !== $action ) { - return $redirect_to; - } elseif ( !isset($_REQUEST['post_showblog_id'] ) || ( '' == $_REQUEST['post_showblog_id'] ) ) { - return $redirect_to; - } + // --- make weekday filter selections bold --- + // 2.3.0: fix to bolding only if weekday isset + $bold = false; + if ( isset( $_GET['weekday'] ) ) { + $weekday = trim( $_GET['weekday'] ); + $nextday = radio_station_get_next_day( $weekday ); + // 2.3.0: handle shifts that go overnight for weekday filter + if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { + echo ""; + $bold = true; + } + } - $show_ids = $_REQUEST['post_showblog_id']; + echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); + echo " " . esc_html( $start ) . " - " . esc_html( $end ); + if ( $bold ) { + echo ""; + } + echo "
        "; + } - // 2.3.3.6: check that user can edit specified Shows - $posted_show_ids = array(); - if ( count( $show_ids ) > 0 ) { - foreach ( $show_ids as $show_id ) { - // 2.3.3.6: added check to ignore zero values - if ( 0 != $show_id ) { - $post = get_post( $show_id ); - if ( current_user_can( 'edit_shows' ) ) { - $posted_show_ids[] = $show_id; + // --- dayless shifts --- + // 2.3.2: added separate display of dayless shifts + if ( count( $dayless_shifts ) > 0 ) { + foreach ( $dayless_shifts as $shift ) { + $title = __( 'This shift is disabled as no day is set.', 'radio-station' ); + echo "
        "; + $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; + $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; + echo esc_html( $start ) . " - " . esc_html( $end ); + echo "
        "; } } } - } - // --- loop post IDs to update --- - $updated_post_ids = $failed_post_ids = array(); - foreach ( $post_ids as $post_id ) { - $post = get_post( $post_id ); - if ( $post ) { + } elseif ( 'hosts' == $column ) { - // 2.3.3.6: keep existing (non-editable) related Shows for post - $existing_show_ids = array(); - $current_ids = get_post_meta( $post_id, 'post_showblog_id', true ); - if ( $current_ids && is_array( $current_ids ) && ( count( $current_ids ) > 0 ) ) { - foreach ( $current_ids as $i => $current_id ) { - // 2.3.3.6: added check to ignore possible zero values - if ( 0 != $current_id ) { - $post = get_post( $current_id ); - if ( !current_user_can( 'edit_shows' ) ) { - $existing_show_ids[] = $current_id; - } - } - } + $hosts = get_post_meta( $post_id, 'show_user_list', true ); + if ( $hosts && ( count( $hosts ) > 0 ) ) { + foreach ( $hosts as $host ) { + $user_info = get_userdata( $host ); + echo esc_html( $user_info->display_name ) . "
        "; } - $new_show_ids = array_merge( $posted_show_ids, $existing_show_ids ); + } - // --- update to new show IDs --- - update_post_meta( $post_id, 'post_showblog_id', $new_show_ids ); - $updated_post_ids[] = $post_id; - } else { - $failed_post_ids[] = $post_id; + } elseif ( 'producers' == $column ) { + + // 2.3.0: added column for Producers + $producers = get_post_meta( $post_id, 'show_producer_list', true ); + if ( $producers && ( count( $producers ) > 0 ) ) { + foreach ( $producers as $producer ) { + $user_info = get_userdata( $producer ); + echo esc_html( $user_info->display_name ) . "
        "; + } } - } - if ( count( $updated_post_ids ) > 0 ) { - $redirect_to = add_query_arg( 'radio_station_related_show_updated', count( $updated_post_ids ), $redirect_to ); - } - if ( count( $failed_post_ids ) > 0 ) { - $redirect_to = add_query_arg( 'radio_station_related_show_failed', count( $failed_post_ids ), $redirect_to ); - } + } elseif ( 'show_image' == $column ) { - // --- restore stored post --- - $post = $stored_post; + // 2.3.0: get show avatar (with fallback to thumbnail) + $image_url = radio_station_get_show_avatar_url( $post_id ); + if ( $image_url ) { + echo "
        " . esc_html( __(
        "; + } - return $redirect_to; + } } -// ---------------------- -// Bulk Edit Posts Notice -// ---------------------- -// 2.3.3.4: add notice for bulk edit result -add_action( 'admin_notices', 'radio_station_posts_bulk_edit_notice' ); -function radio_station_posts_bulk_edit_notice() { - $updated = $failed = false; - if ( isset( $_REQUEST['radio_station_related_show_updated'] ) ) { - $updated_ = intval( $_REQUEST['radio_station_related_show_updated'] ); - } - if ( isset( $_REQUEST['radio_station_related_show_failed'] ) ) { - $failed = intval( $_REQUEST['radio_station_related_show_failed'] ); +// ----------------------- +// Show List Column Styles +// ----------------------- +// 2.2.7: added show column styles +add_action( 'admin_footer', 'radio_station_show_column_styles' ); +function radio_station_show_column_styles() { + $current_screen = get_current_screen(); + if ( 'edit-' . RADIO_STATION_SHOW_SLUG !== $current_screen->id ) { + return; } - if ( $updated || $failed ) { - - echo '
        '; - - if ( $updated > 0 ) { - // --- number of posts updated message --- - echo '

        '; - $message = __( 'Updated Related Shows for %d Posts.', 'radio_station' ); - $message = sprintf( $message, $updated ); - echo esc_html( $message ); - echo '

        '; - } - if ( $failed > 0 ) { - // --- number of posts failed messsage --- - echo '

        '; - $message = __( 'Failed to Update Related Shows for %d Posts.', 'radio-station' ); - $message = sprintf( $message, $failed ); - esc_html( $message ); - echo '

        '; - } - echo '
        '; - } + echo ""; } -// ----------------------------- -// Related Show Post List Styles -// ----------------------------- -// 2.3.3.4: added Related Show Post List styles -add_action( 'admin_footer', 'radio_station_posts_list_styles' ); -function radio_station_posts_list_styles() { - $currentscreen = get_current_screen(); - if ( 'edit-post' !== $currentscreen->id ) { +// ------------------------- +// Add Show Shift Day Filter +// ------------------------- +// 2.2.7: added show day selection filtering +add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); +function radio_station_show_day_filter( $post_type, $which ) { + + if ( RADIO_STATION_SHOW_SLUG !== $post_type ) { return; } - // --- post list styles --- - echo ""; -} + // -- maybe get specified day --- + $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; + // --- show day selector --- + $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); -// ------------- -// === Shows === -// ------------- + echo ''; + echo ''; +} + + +// -------------------------- +// === Schedule Overrides === +// -------------------------- + +// ------------------------------ +// Add Override Show Data Metabox +// ------------------------------ +add_action( 'add_meta_boxes', 'radio_station_add_override_show_metabox' ); +function radio_station_add_override_show_metabox() { + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'overrides' ); add_meta_box( - 'radio-station-show-info-metabox', - __( 'Show Information', 'radio-station' ), - 'radio_station_show_info_metabox', - RADIO_STATION_SHOW_SLUG, + 'radio-station-override-show-metabox', + __( 'Override Show Data', 'radio-station' ), + 'radio_station_override_show_metabox', + RADIO_STATION_OVERRIDE_SLUG, $position, - 'high' + 'low' ); } -// ----------------- -// Show Info Metabox -// ----------------- -function radio_station_show_info_metabox() { +// -------------------------- +// Override Show Data Metabox +// -------------------------- +function radio_station_override_show_metabox() { - global $post; + global $post, $current_screen; + $post_id = $post->ID; - // 2.3.0: added missing nonce field - wp_nonce_field( 'radio-station', 'show_meta_nonce' ); + // --- add nonce field for update verification --- + wp_nonce_field( 'radio-station', 'show_data_nonce' ); - // --- get show meta --- - // 2.3.2: added show download disable switch - $active = get_post_meta( $post->ID, 'show_active', true ); - $link = get_post_meta( $post->ID, 'show_link', true ); - $email = get_post_meta( $post->ID, 'show_email', true ); - $phone = get_post_meta( $post->ID, 'show_phone', true ); - $file = get_post_meta( $post->ID, 'show_file', true ); - $download = get_post_meta( $post->ID, 'show_download', true ); - $patreon_id = get_post_meta( $post->ID, 'show_patreon', true ); + // --- open meta inner wrap --- + echo '
        '; - // added max-width to prevent metabox overflows - // 2.3.0: removed new lines between labels and fields and changed widths - // 2.3.2: increase label width to 120px for disable download field label - echo '
        '; + // --- get all shows --- + $args = array( + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => array( 'publish', 'draft' ), + 'orderby' => 'modified', + ); + $shows = get_posts( $args ); - echo '

        - - ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

        '; + // --- link to Show field --- + $linked_id = get_post_meta( $post->ID, 'linked_show_id', true ); + echo ''; + + // --- sync genre taxonomy --- + $sync_genres = get_post_meta( $post_id, 'sync_genres', true ); + echo '' . PHP_EOL; + + // --- sync language taxonomy --- + $sync_languages = get_post_meta( $post_id, 'sync_languages', true ); + echo '' . PHP_EOL; + + echo '
        ' . PHP_EOL; + echo '' . esc_html( __( 'Link to Show', 'radio-station' ) ) . ': ' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . esc_html( __( 'If selected, Override data will be used from the Linked Show.', 'radio-station' ) ) . '' . PHP_EOL; + echo '
        ' . PHP_EOL; + echo '' . esc_html( 'Sync Genres?', 'radio-station' ) . '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . esc_html( __( 'If checked, assigned Genre terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . PHP_EOL; + echo '
        ' . PHP_EOL; + echo '' . esc_html( 'Sync Languages?', 'radio-station' ) . '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . PHP_EOL; + echo '' . esc_html( __( 'If checked, assigned Language terms are synced from the Linked Show when Updating.', 'radio-station' ) ) . ' ' . PHP_EOL; + echo '

        '; + + // --- get show meta --- + // $active = get_post_meta( $post->ID, 'show_active', true ); + $link = get_post_meta( $post_id, 'show_link', true ); + $email = get_post_meta( $post_id, 'show_email', true ); + $phone = get_post_meta( $post_id, 'show_phone', true ); + $file = get_post_meta( $post_id, 'show_file', true ); + $download = get_post_meta( $post_id, 'show_download', true ); + $patreon_id = get_post_meta( $post_id, 'show_patreon', true ); + + $linked_fields = get_post_meta( $post_id, 'linked_show_fields', true ); + echo '
        '; + + echo '
        '; + echo '' . esc_html( __( 'Usage Note', 'radio-station' ) ) . ''; + echo ': ' . esc_html( __( ' Unchecked boxes use Show data, checked boxes use Override data.', 'radio-station' ) ); + echo '

        '; + + // --- table headings --- + echo ''; + + // TODO: show active ? + // echo '

        + // + // ' . esc_html( __( 'Check this box if show is currently active (Show will not appear on programming schedule if unchecked.)', 'radio-station' ) ) . '

        '; + + // --- title --- + echo '' . PHP_EOL; - echo '

        -

        '; + // --- content --- + echo '' . PHP_EOL; - // 2.3.3.6: change text string from DJ / Host email (as maybe multiple hosts) - echo '

        -

        '; + // --- excerpt --- + echo '' . PHP_EOL; + + // --- avatar --- + echo '' . PHP_EOL; - // 2.3.3.6: added Show phone number input field - echo '

        -

        '; + // --- featured image --- + echo '' . PHP_EOL; - echo '

        -

        '; + // --- hosts --- + echo '' . PHP_EOL; - // 2.3.2: added show download disable field - echo '

        -

        '; + // --- producers --- + echo '' . PHP_EOL; + + // --- website --- + echo '' . PHP_EOL; + + // --- email --- + echo '' . PHP_EOL; - // 2.3.0: added patreon page field - echo '

        '; - echo ' https://patreon.com/

        '; + // --- phone --- + echo '' . PHP_EOL; - // 2.3.3.5: added action for further custom fields - do_action( 'radio_station_show_fields', $post->ID, 'show' ); + // --- audio file --- + echo '' . PHP_EOL; + + // --- disable download --- + echo '' . PHP_EOL; - echo ''; + // --- patreon --- + echo '' . PHP_EOL; + + do_action( 'radio_station_show_fields', $post_id, 'override' ); + + // --- close field table --- + echo '
        '; + echo '' . esc_html( __( 'Override?', 'radio-station' ) ) . ''; + echo ''; + echo '' . esc_html( __( 'Override Data', 'radio-station' ) ) . ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo ' '; + echo '' . PHP_EOL; + echo ''; + echo '
        ' . PHP_EOL; + echo '
        ' . PHP_EOL; + + // --- close meta inner --- + echo '

        '; // --- inside show metaboxes --- - // 2.3.0: move metaboxes together inside meta $inside_metaboxes = array( 'hosts' => array( - 'title' => __( 'Show DJ(s) / Host(s)', 'radio-station' ), + 'title' => __( 'Override DJ(s) / Host(s)', 'radio-station' ), 'callback' => 'radio_station_show_hosts_metabox', ), 'producers' => array( - 'title' => __( 'Show Producer(s)', 'radio-station' ), + 'title' => __( 'Override Producer(s)', 'radio-station' ), 'callback' => 'radio_station_show_producers_metabox', ), - 'languages' => array( - 'title' => __( 'Show Language(s)', 'radio-station' ), - 'callback' => 'radio_station_show_language_metabox', - ) ); // --- display inside metaboxes --- @@ -1804,206 +2945,475 @@ function radio_station_show_info_metabox() { } $class = implode( ' ', $classes ); - echo '
        ' . "\n"; - $widget_title = $metabox['title']; - - // echo ''; - - // 2.3.2: remove class="hndle" to prevent box sorting - echo '

        ' . esc_html( $metabox['title'] ) . '

        '; - echo '
        '; - call_user_func( $metabox['callback'] ); - echo "
        "; - echo "
        "; + echo '' . PHP_EOL; $i ++; } echo '
        '; - + // --- output inside metabox styles --- - echo ""; + + // --- get override show script --- + $js = radio_station_override_show_script(); + + // --- enqueue inline script --- + wp_add_inline_script( 'radio-station-admin', $js ); + } -// ------------------------------ -// Add Assign DJs to Show Metabox -// ------------------------------ -// 2.3.0: move inside show meta selection metabox to reduce clutter -// add_action( 'add_meta_boxes', 'radio_station_add_show_hosts_metabox' ); -function radio_station_add_show_hosts_metabox() { - // 2.2.2: add high priority to show at top of edit sidebar - // 2.3.0: change metabox title from DJs to DJs / Hosts +// ------------------------- +// Override Show Data Script +// ------------------------- +function radio_station_override_show_script() { + + $genres_div = '#' . RADIO_STATION_GENRES_SLUG . 'div'; + $languages_div = '#' . RADIO_STATION_LANGUAGES_SLUG . 'div'; + + // --- check for linked show value --- + // 2.3.3.9: check linked show value + $js = "function radio_link_check() { + linked_id = jQuery('#override-link').val(); + if (linked_id == '') { + jQuery('.override-label, .override-checkbox, #override-genres, #override-languages').hide(); + jQuery('.override-input, " . esc_js( $genres_div ) . ", " . esc_js( $languages_div ) . ", #hosts, #producers').show(); + jQuery('#linked-fields-message').hide(); + } else { + jQuery('.override-label, .override-checkbox, #override-genres, #override-languages').show(); + keys = ['website', 'email', 'phone', 'file', 'download', 'patreon', 'hosts', 'producers']; + for (i = 0; i < keys.length; i++) {radio_check_linked(keys[i]);} + jQuery('#linked-fields-message').show(); + radio_sync_genres(); radio_sync_languages(); + } + }" . PHP_EOL; + + // --- show/hide linked input field --- + $js .= "function radio_check_linked(id) { + /* console.log(id); */ + display = jQuery('#override-show-'+id).prop('checked'); + divid = '#override-input-'+id; dataid = '#override-data-'+id; + if (id == 'content') {divid += ', #wp-content-wrap, #post-status-info';} + else if (id == 'excerpt') {divid += ', #postexcerpt';} + else if (id == 'hosts') {divid += ', #hosts';} + else if (id == 'producers') {divid += ', #producers';} + /* console.log(divid); */ + if (display) {jQuery(dataid).hide(); jQuery(divid).show();} + else {jQuery(divid).hide(); jQuery(dataid).show();} + }" . PHP_EOL; + + // --- show/hide genre taxonomy metaboxs --- + $js .= "function radio_sync_genres() { + checked = jQuery('#override-genres-input').prop('checked'); + if (checked) {jQuery('" . esc_js( $genres_div ) . "').hide();} + else {jQuery('" . esc_js( $genres_div ) . "').show();} + }" . PHP_EOL; + + // --- show/hide language taxonomy metabox --- + $js .= "function radio_sync_languages() { + checked = jQuery('#override-languages-input').prop('checked'); + if (checked) {jQuery('" . esc_js( $languages_div ) . "').hide();} + else {jQuery('" . esc_js( $languages_div ) . "').show();} + }" . PHP_EOL; + + // --- check to hide linked metaboxes on pageload --- + $js .= "jQuery(document).ready(function() {radio_link_check();});" .PHP_EOL; + + $js = apply_filters( 'radio_station_override_show_script', $js ); + return $js; +} + +// ----------------------------- +// Add Schedule Override Metabox +// ----------------------------- +// --- Add schedule override box to override edit screens --- +add_action( 'add_meta_boxes', 'radio_station_add_schedule_override_metabox' ); +function radio_station_add_schedule_override_metabox() { + // 2.2.2: add high priority to show at top of edit screen + // 2.3.0: set position to top to be above editor box + // 2.3.0: update meta box ID for consistency + // 2.3.2: filter top metabox position + // 2.3.3.9: change priority to low to be below show data box + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'overrides' ); add_meta_box( - 'radio-station-show-hosts-metabox', - __( 'DJs / Hosts', 'radio-station' ), - 'radio_station_show_hosts_metabox', - RADIO_STATION_SHOW_SLUG, - 'side', + 'radio-station-override-schedule-metabox', + __( 'Override Schedule', 'radio-station' ), + 'radio_station_schedule_override_metabox', + RADIO_STATION_OVERRIDE_SLUG, + $position, 'high' ); } -// ---------------------------- -// Assign Hosts to Show Metabox -// ---------------------------- -function radio_station_show_hosts_metabox() { +// ------------------------- +// Schedule Override Metabox +// ------------------------- +function radio_station_schedule_override_metabox() { - global $post, $wp_roles, $wpdb; + global $post, $current_screen; - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'show_hosts_nonce' ); + // --- add nonce field for update verification --- + wp_nonce_field( 'radio-station', 'show_override_nonce' ); - // --- check for DJ / Host roles --- - // 2.3.0: simplified by using role__in argument - $args = array( - 'role__in' => array( 'dj', 'administrator' ), - 'orderby' => 'display_name', - 'order' => 'ASC' - ); - $hosts = get_users( $args ); + // 2.2.7: add explicit width to date picker field to ensure date is visible + // 2.3.0: convert template style output to straight php output + // 2.3.3.9: change meta_inner ID to class + echo '
        '; + + // --- override list table --- + echo '
        '; + + $table = radio_station_overrides_table( $post->ID ); + if ( '' != $table['list'] ) { + echo $table['list']; + } + + echo '
        '; + + // --- override save/add buttons --- + // 2.3.3.9: added for AJAX save and multiple overrides + echo ''; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '
        '; + echo ''; + echo ''; + if ( !is_object( $current_screen ) || ( 'add' != $current_screen->action ) ) { + echo ''; + } + echo ''; + echo ''; + echo '
        '; + echo ''; + echo ''; + echo ''; + echo '
        '; + echo ''; + + // --- override list styles --- + // 2.3.0: added datepicker z-index to fix conflict with editor buttons + // 2.3.3.9: apply class styles to override list + echo ""; + + // --- enqueue datepicker script and styles --- + // 2.3.0: enqueue for override post type only + radio_station_enqueue_datepicker(); + + // --- get override edit javascript --- + $js = radio_station_override_edit_script(); + + // --- initialize datepicker fields --- + // 2.3.3.9: also initialize end date field + $js .= "jQuery(document).ready(function() { + jQuery('.override-date').each(function() { + jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); + }); + });" . PHP_EOL; + + // --- enqueue inline script --- + // 2.3.0: enqeue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); + + // --- close meta inner --- + echo '
        '; + +} + +// -------------------- +// Overrides Table List +// -------------------- +// 2.3.3.9: separated out for AJAX saving/display +function radio_station_overrides_table( $post_id ) { + + // 2.2.7: added meridiem translations + $am = radio_station_translate_meridiem( 'am' ); + $pm = radio_station_translate_meridiem( 'pm' ); + + // --- get the saved meta as an array --- + $overrides = get_post_meta( $post_id, 'show_override_sched', true ); + if ( array_key_exists( 'date', $overrides ) ) { + $overrides = array( $overrides ); + } + echo 'Current Overrides: ' . print_r( $overrides, true ) . ''; + + if ( !$overrides ) { + + // --- set empty override time array --- + $times = array( + 'date' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + 'multiday' => '', + 'end_date' => '', + 'disabled' => '', + ); + + // --- check and sanitize possibly posted times --- + // 2.3.3.9: for adding new override time by querystring + if ( isset( $_REQUEST['date'] ) ) { + $times['date'] = $_REQUEST['date']; + $times['date'] = date( 'Y-m-d', strtotime( $times['date'] ) ); + } + if ( isset( $_REQUEST['start_hour'] ) ) { + $start_hour = absint( $_REQUEST['start_hour'] ); + if ( ( $times['start_hour'] < 1 ) || ( $times['start_hour'] > 12 ) ) { + $times['start_hour'] = 1; + } + } + if ( isset( $_REQUEST['start_min'] ) ) { + $times['start_min'] = absint( $_REQUEST['start_min'] ); + if ( ( $times['start_min'] < 1 ) || ( $times['start_min'] > 60 ) ) { + $times['start_min'] = 0; + } + if ( $times['start_min'] < 10 ) { + $times['start_min'] = '0' . $times['start_min']; + } + } + if ( isset( $_REQUEST['start_meridian'] ) ) { + $times['start_meridian'] = $_REQUEST['start_meridian']; + if ( !in_array( $times['start_meridian'], array( '', 'am', 'pm' ) ) ) { + $times['start_meridian'] = ''; + } + } + if ( isset( $_REQUEST['end_hour'] ) ) { + $times['end_hour'] = absint( $_REQUEST['end_hour'] ); + if ( ( $times['end_hour'] < 1 ) || ( $times['end_hour'] > 12 ) ) { + $times['end_hour'] = 1; + } + } + if ( isset( $_REQUEST['end_min'] ) ) { + $times['end_min'] = absint( $_REQUEST['end_min'] ); + if ( ( $times['end_min'] < 1 ) || ( $times['end_min'] > 60 ) ) { + $times['end_min'] = 0; + } + if ( $times['end_min'] < 10 ) { + $times['end_min'] = '0' . $times['end_min']; + } + } + if ( isset( $_REQUEST['end_meridian'] ) ) { + $times['end_meridian'] = $_REQUEST['end_meridian']; + if ( !in_array( $times['end_meridian'], array( '', 'am', 'pm' ) ) ) { + $times['end_meridian'] = ''; + } + } + if ( isset( $_REQUEST['disabled'] ) ) { + $times['disabled'] = $_REQUEST['disabled']; + if ( !in_array( $times['disabled'], array( '', 'yes' ) ) ) { + $times['disabled'] = ''; + } + } - // --- get the Hosts currently assigned to the show --- - $current = get_post_meta( $post->ID, 'show_user_list', true ); - if ( !$current ) { - $current = array(); + // 2.2.8: fix undefined index warnings for new schedule overrides + $overrides = array( $times ); } - // --- move any selected Hosts to the top of the list --- - foreach ( $hosts as $i => $host ) { - // 2.2.8: remove strict in_array checking - if ( in_array( $host->ID, $current ) ) { - unset( $hosts[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $hosts, $host ); // prepend the user to the beginning of the array - } - } + // 2.3.3.9: loop possible multiple overrides + $list = ''; + foreach ( $overrides as $i => $override ) { - // --- Host Selection Input --- - // 2.2.2: add fix to make DJ multi-select input full metabox width - echo '
        '; - echo ''; + $list .= '
        '; + $list .= '
          '; - // --- multiple selection helper text --- - // 2.3.0: added multiple selection helper text - echo '
          ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
          '; - echo '
        '; -} + // --- Override (Start) Date --- + $list .= '
      • '; -// ------------------------------------ -// Add Assign Producers to Show Metabox -// ------------------------------------ -// 2.3.0: move inside show meta selection metabox to reduce clutter -// add_action( 'add_meta_boxes', 'radio_station_add_show_producers_metabox' ); -function radio_station_add_show_producers_metabox() { - add_meta_box( - 'radio-station-show-producers-metabox', - __( 'Show Producer(s)', 'radio-station' ), - 'radio_station_show_producers_metabox', - RADIO_STATION_SHOW_SLUG, - 'side', - 'high' - ); -} + $list .= esc_html( __( 'Start Date', 'radio-station' ) ) . ':'; + $date = ( !empty( $override['date'] ) ) ? trim( $override['date'] ) : ''; + $list .= ''; + $list .= ''; -// -------------------------------- -// Assign Producers to Show Metabox -// -------------------------------- -function radio_station_show_producers_metabox() { + $list .= '
      • '; - global $post, $wp_roles, $wpdb; + // --- Override Start Time --- + $list .= '
      • '; - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'show_producers_nonce' ); + // --- start time label -- + $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ':'; - // --- check for Producer roles --- - $args = array( - 'role__in' => array( 'producer', 'administrator', 'show-editor' ), - 'orderby' => 'display_name', - 'order' => 'ASC' - ); - $producers = get_users( $args ); + // --- start hour --- + $list .= ''; + $list .= ''; + + // --- start minute --- + $list .= ''; + $list .= ''; + + // --- start meridian --- + $list .= ''; + $list .= ''; + + $list .= '
      • '; + + // --- Override End Time --- + // 2.3.4: add common end minutes to top of options + $list .= '
      • '; + + // --- end time label --- + $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ':'; + + // --- end hour --- + $list .= ''; + $list .= ''; + + // --- end minutes --- + $list .= ''; + $list .= ''; + + // --- end meridian --- + $list .= ''; + $list .= ''; + + $list .= '
      • '; + + // - multiday switch - + // 2.3.3.9: added multiday checkbox prototype + $list .= ''; + + // --- disabled --- + // 2.3.3.9: added disabled override checkox + $list .= '
      • '; + $list .= 'ID, 'show_producer_list', true ); - if ( !$current ) { - $current = array(); - } + // --- duplicate shift icon --- + $list .= '
      • '; + $title = __( 'Duplicate Override', 'radio-station' ); + $list .= ''; + $list .= '
      • '; - // --- move any selected DJs to the top of the list --- - foreach ( $producers as $i => $producer ) { - if ( in_array( $producer->ID, $current ) ) { - unset( $producers[$i] ); // unset first, or prepending will change the index numbers and cause you to delete the wrong item - array_unshift( $producers, $producer ); // prepend the user to the beginning of the array - } - } + // --- remove shift icon --- + $list .= '
      • '; + $title = __( 'Remove Override', 'radio-station' ); + $list .= ''; + $list .= '
      • '; - // --- Producer Selection Input --- - echo '
        '; - echo ''; + $list .= '
      '; + $list .= '
      '; + + } - // --- multiple selection helper text --- - echo '
      ' . esc_html( __( 'Ctrl-Click selects multiple.', 'radio-station' ) ) . '
      '; - echo '
      '; -} + // --- new overrides div --- + $list .= '
      '; -// ----------------------- -// Add Show Shifts Metabox -// ----------------------- -// --- Adds schedule box to show edit screens --- -add_action( 'add_meta_boxes', 'radio_station_add_show_shifts_metabox' ); -function radio_station_add_show_shifts_metabox() { - // 2.2.2: change context to show at top of edit screen - // 2.3.2: filter top metabox position - $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'shifts' ); - add_meta_box( - 'radio-station-show-shifts-metabox', - __( 'Show Schedule', 'radio-station' ), - 'radio_station_show_shifts_metabox', - RADIO_STATION_SHOW_SLUG, - $position, - 'low' + // --- set table output to return --- + $table = array( + 'list' => $list, + // 'conflicts' => $conflicts, ); + + return $table; } -// ------------------- -// Show Shifts Metabox -// ------------------- -function radio_station_show_shifts_metabox() { - - global $post, $current_screen; +// ------------------------- +// Override List Edit Script +// ------------------------- +function radio_station_override_edit_script() { // --- set days, hours and minutes arrays --- $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); @@ -2019,128 +3429,66 @@ function radio_station_show_shifts_metabox() { } $mins[$i] = $min; } - - // 2.2.7: added meridiem translations $am = radio_station_translate_meridiem( 'am' ); $pm = radio_station_translate_meridiem( 'pm' ); - // --- hidden debug fields --- - // 2.3.2: added save debugging field - if ( RADIO_STATION_DEBUG ) { - echo ''; - } - if ( RADIO_STATION_SAVE_DEBUG ) { - echo ''; - } - - // --- add nonce field for verification --- - wp_nonce_field( 'radio-station', 'show_shifts_nonce' ); - - echo '
      '; - echo '
      '; - - // 2.3.2: added to bypass shift check on add (for debugging) - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - echo ''; - } - - // --- output show shifts table --- - // 2.3.2: separated table function (for AJAX saving) - $table = radio_station_show_shifts_table( $post->ID ); - - // --- show inactive message --- - // 2.3.0: added show inactive reminder message - if ( !$table['active'] ) { - echo '
      '; - echo '' . esc_html( __( 'This Show is inactive!', 'radio-station' ) ) . ' '; - echo esc_html( __( 'All Shifts are inactive also until Show is activated.', 'radio-station' ) ); - echo '
      '; - } - - // --- shift conflicts message --- - // 2.3.0: added instructions for fixing shift conflicts - if ( $table['conflicts'] ) { - radio_station_shifts_conflict_message(); - } - - // --- output shift list --- - if ( '' != $table['list'] ) { - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $table['list']; - } - echo "
      "; - - // 2.3.0: center add shift button - // 2.3.2: fix centering by removing span wrapper - // 2.3.2: change from button-primary to button-secondary - echo '
      '; - // echo '' . esc_html( __( 'Add Shift', 'radio-station' ) ) . ''; - - // --- shift save/add buttons --- - // 2.3.2: added show shifts AJAX save button (for existing posts only) - // 2.3.2: added show shifts clear button - // 2.3.2: added table and shifts saved message - echo ''; - echo '
      '; - echo ''; - echo ''; - if ( 'add' != $current_screen->action ) { - echo ''; - } - echo ''; - echo ''; - echo '
      '; - echo ''; - echo ''; - echo ''; - echo '
      '; - echo '
      '; - // --- show shifts scripts --- - // 2.3.0: added confirmation to remove shift button - // 2.3.2: removed document ready functions wrapper $c = 0; - $confirm_remove = __( 'Are you sure you want to remove this shift?', 'radio-station' ); - $confirm_clear = __( 'Are you sure you want to clear the shift list?', 'radio-station' ); - // $js = "var count = " . esc_attr( $c ) . ";"; + $confirm_remove = __( 'Are you sure you want to remove this Override?', 'radio-station' ); + $confirm_clear = __( 'Are you sure you want to clear all overrides?', 'radio-station' ); // --- clear all shifts function --- - $js = "function radio_shifts_clear() { - if (jQuery('#shifts-list').children().length) { + $js = "function radio_overrides_clear() { + if (jQuery('#overrides-list').children().length) { var agree = confirm('" . esc_js( $confirm_clear ) . "'); if (!agree) {return false;} - jQuery('#shifts-list').children().remove(); - jQuery('
      ').appendTo('#shifts-list'); + jQuery('#overrides-list').children().remove(); + jQuery('
      ').appendTo('#overrides-list'); } }" . PHP_EOL; // --- save shifts via AJAX --- // 2.3.2: added form input cloning to saving show shifts $ajaxurl = admin_url( 'admin-ajax.php' ); - $js .= "function radio_shifts_save() { - jQuery('#shift-save-form, #shift-save-frame').remove(); - form = '
      '; - form += '
      '; + $js .= "function radio_overrides_save() { + jQuery('#override-save-form, #override-save-frame').remove(); + form = '
      '; + form += '
      '; jQuery('#wpbody').append(form); - if (!jQuery('#shift-save-frame').length) { - frame = ''; + if (!jQuery('#override-save-frame').length) { + frame = ''; jQuery('#wpbody').append(frame); } - /* copy shifts input fields and nonce */ - jQuery('#shifts-list input').each(function() {jQuery(this).clone().appendTo('#shift-save-form');}); - jQuery('#shifts-list select').each(function() { + /* copy override input fields and nonce */ + jQuery('#overrides-list input').each(function() {jQuery(this).clone().appendTo('#override-save-form');}); + jQuery('#overrides-list select').each(function() { name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); - jQuery('').appendTo('#shift-save-form'); + jQuery('').appendTo('#override-save-form'); }); - jQuery('#show_shifts_nonce').clone().attr('id','').appendTo('#shift-save-form'); - jQuery('#post_ID').clone().attr('id','').attr('name','show_id').appendTo('#shift-save-form'); - jQuery('#shifts-saving-message').show(); - jQuery('#shift-save-form').submit(); + jQuery('#show_override_nonce').clone().attr('id','').appendTo('#override-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','override_id').appendTo('#override-save-form'); + jQuery('#override-saving-message').show(); + jQuery('#override-save-form').submit(); + }" . PHP_EOL; + + // --- check multiday selection --- + // 2.3.3.9: added to show hide end date field + $js .= "function radio_check_multiday(id) { + if (jQuery('#override-multiday-'+id).prop('checked')) { + jQuery('#override-'+id+'-end-date').show(); + } else {jQuery('#override-'+id+'-end-date').hide();} + }" . PHP_EOL; + + // TODO: input change highlighting + + // --- check disabled selection --- + // TODO: ... + $js .= "function radio_check_disabled() { + }" . PHP_EOL; // --- check select change --- - // 2.3.3: added select change detection - $js .= "function radio_check_select(el) { + /* $js .= "function radio_check_select(el) { val = el.options[el.selectedIndex].value; if (val == '') {jQuery('#'+el.id).addClass('incomplete');} else {jQuery('#'+el.id).removeClass('incomplete');} @@ -2150,11 +3498,10 @@ function radio_station_show_shifts_metabox() { else {jQuery('#'+el.id).addClass('changed');} uid = origid.substr(0,8); radio_check_shift(uid); - }" . PHP_EOL; + }" . PHP_EOL; */ // --- check checkbox change --- - // 2.3.3: added checkbox change detection - $js .= "function radio_check_checkbox(el) { + /* $js .= "function radio_check_checkbox(el) { val = el.checked ? 'on' : ''; origid = el.id.replace('shift-',''); origval = jQuery('#'+origid).val(); @@ -2162,26 +3509,25 @@ function radio_station_show_shifts_metabox() { else {jQuery('#'+el.id).addClass('changed');} uid = origid.substr(0,8); radio_check_shift(uid); - }" . PHP_EOL; - - // --- check shift change --- - // 2.3.3: added shift change detection - $js .= "function radio_check_shift(id) { - var shiftchanged = false; - jQuery('#shift-'+id).find('select,input').each(function() { - if ( (jQuery(this).attr('id').indexOf('shift-') == 0) && (jQuery(this).hasClass('changed')) ) { - shiftchanged = true; + }" . PHP_EOL; */ + + // --- check override change --- + /* $js .= "function radio_check_override(id) { + var overridechanged = false; + jQuery('#overide-'+id).find('select,input').each(function() { + if ( (jQuery(this).attr('id').indexOf('override-') == 0) && (jQuery(this).hasClass('changed')) ) { + overridechanged = true; } }); - if (shiftchanged) {jQuery('#shift-'+id).addClass('changed');} - else {jQuery('#shift-'+id).removeClass('changed');} - radio_check_shifts(); - }" . PHP_EOL; + if (shiftchanged) {jQuery('#override-'+id).addClass('changed');} + else {jQuery('#override-'+id).removeClass('changed');} + radio_check_overrides(); + }" . PHP_EOL; */ - // 2.3.3.6: store possible existing onbeforeunload function + // --- store possible existing onbeforeunload function --- // (to help prevent conflicts with other plugins using this event) - $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . PHP_EOL; - $js .= "function radio_check_shifts() { + /* $js .= "var storedonbeforeunload = null; var onbeforeunloadset = false;" . PHP_EOL; + $js .= "function radio_check_overrides() { if (jQuery('.show-shift.changed').length) { if (!onbeforeunloadset) { storedonbeforeunload = window.onbeforeunload; @@ -2194,98 +3540,85 @@ function radio_station_show_shifts_metabox() { onbeforeunloadset = false; } } - }" . PHP_EOL; + }" . PHP_EOL; */ - // --- add new shift --- - // 2.3.2: separate function for onclick - $js .= "function radio_shift_new() { - values = {}; - values.day = ''; - values.start_hour = ''; - values.start_min = ''; - values.start_meridian = ''; - values.end_hour = ''; - values.end_min = ''; - values.end_meridian = ''; - values.encore = ''; - values.disabled = ''; - radio_shift_add(values); + // --- add new override --- + $todate = date( 'Y-m-d', time() ); + $js .= "function radio_override_new() { + values = {}; + values.date = '" . $todate . "'; + values.start_hour = ''; + values.start_min = ''; + values.start_meridian = ''; + values.end_hour = ''; + values.end_min = ''; + values.end_meridian = ''; + values.multiday = ''; + values.end_date = ''; + radio_override_add(values); }" . PHP_EOL; - - // --- remove shift ---- - // 2.3.2: separate function for onclick - // 2.3.3: fix to jQuery targeting for new shifts - $js .= "function radio_shift_remove(el) { + + // --- remove override --- + $js .= "function radio_override_remove(el) { agree = confirm('" . esc_js( $confirm_remove ) . "'); if (!agree) {return false;} - shiftid = el.id.replace('shift-','').replace('-remove',''); - jQuery('#'+el.id).closest('.shift-wrapper').remove(); + /* overrideid = el.id.replace('override-','').replace('-remove',''); */ + jQuery('#'+el.id).closest('.override-wrapper').remove(); }" . PHP_EOL; // --- duplicate shift --- - // 2.3.2: separate function for onclick - $js .= "function radio_shift_duplicate(el) { - shiftid = el.id.replace('shift-','').replace('-duplicate',''); + $js .= "function radio_override_duplicate(el) { + overrideid = el.id.replace('override-','').replace('-duplicate',''); values = {}; - values.day = jQuery('#shift-'+shiftid+'-day').val(); - values.start_hour = jQuery('#shift-'+shiftid+'-start-hour').val(); - values.start_min = jQuery('#shift-'+shiftid+'-start-min').val(); - values.start_meridian = jQuery('#shift-'+shiftid+'-start-meridian').val(); - values.end_hour = jQuery('#shift-'+shiftid+'-end-hour').val(); - values.end_min = jQuery('#shift-'+shiftid+'-end-min').val(); - values.end_meridian = jQuery('#shift-'+shiftid+'-end-meridian').val(); - values.encore = ''; - if (jQuery('#shift-'+shiftid+'-encore').prop('checked')) {values.encore = 'on';} + values.date = jQuery('#override-'+overrideid+'-date').val(); + values.start_hour = jQuery('#override-'+overrideid+'-start-hour').val(); + values.start_min = jQuery('#override-'+overrideid+'-start-min').val(); + values.start_meridian = jQuery('#override-'+overrideid+'-start-meridian').val(); + values.end_hour = jQuery('#override-'+overrideid+'-end-hour').val(); + values.end_min = jQuery('#override-'+overrideid+'-end-min').val(); + values.end_meridian = jQuery('#override-'+overrideid+'-end-meridian').val(); + values.multiday = ''; + /* if (jQuery('#override-'+overrideid+'-multiday').prop('checked')) {values.multiday = 'yes';} */ + values.end_date = ''; + /* values.end_date = jQuery('#override-'+overrideid+'-end-date'); */ values.disabled = 'yes'; - radio_shift_add(values); + radio_override_add(values); }" . PHP_EOL; - // --- add shift function --- - // 2.3.2: added missing shift wrapper class - // 2.3.2: set new count based on new shift children - // 2.3.3: add input IDs so new shifts can be duplicated - $js .= "function radio_shift_add(values) { - var count = jQuery('#new-shifts').children().length + 1; - output = '
      '; - output += '
        '; - output += '
      • '; - output += '" . esc_js( __( 'Day', 'radio-station' ) ) . ": '; - output += ''; + output += '
      • ';"; + + // --- start hour --- + $js .= "output += '
      • '; output += '" . esc_js( __( 'Start Time', 'radio-station' ) ) . ": '; - output += '';"; - // - start hour - foreach ( $hours as $hour ) { $js .= "output += '';"; + $js .= "output += ' ';"; // - start minute - + $js .= "output += ''; output += '
      • '; + output += '" . esc_js( __( 'End Time', 'radio-station' ) ) . ": ';"; // - end hour - + $js .= "output += ''; output += '';"; foreach ( $mins as $min ) { $js .= "output += '
      • ';"; + + // - disable override - + $js .= "output += '
      • '; + output += ''; + // - duplicate override time - + $js .= "output += '
      • '; + output += ''; output += '
      • ';"; - // - remove shift - - $js .= "output += '
      • '; - output += ''; + // - remove override time - + $js .= "output += '
      • '; + output += ''; output += '
      • ';"; - // --- append new shift list item --- + // --- append new override list item --- $js .= "output += '
      '; - jQuery('#new-shifts').append(output); + jQuery('#new-overrides').append(output); + jQuery('#override-new' + count + '-date').datepicker({dateFormat : 'yy-mm-dd'}); + jQuery('#override-new' + count + '-end-date').datepicker({dateFormat : 'yy-mm-dd'}); return false; - }"; + }" . PHP_EOL; - // --- enqueue inline script --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + $js = apply_filters( 'radio_station_override_edit_script', $js ); + return $js; +} - // --- shift display styles --- - // 2.3.2: added dashed border to new shift - echo ''; +// ------------------------ +// Update Schedule Override +// ------------------------ +add_action( 'wp_ajax_radio_station_override_save', 'radio_station_override_save_data' ); +add_action( 'save_post', 'radio_station_override_save_data' ); +function radio_station_override_save_data( $post_id ) { - // --- close meta inner --- - echo '
      '; -} + // --- verify if this is an auto save routine --- + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { + return; + } -// ----------------------- -// Shifts Oonflict Message -// ----------------------- -function radio_station_shifts_conflict_message() { - echo '
      '; - echo '' . esc_html( __( 'Warning! Show Shift Conflicts were detected!', 'radio-station' ) ) . '
      '; - echo esc_html( __( 'Please note that Shifts with conflicts are automatically disabled upon saving.', 'radio-station' ) ) . '
      '; - echo esc_html( __( 'Fix the Shift and/or the Shift on the conflicting Show and Update them both.', 'radio-station' ) ) . '
      '; - echo esc_html( __( 'Then you can uncheck the shift Disable box and Update to re-enable the Shift.', 'radio-station' ) ) . '
      '; - // TODO: add more information blog post / documentation link ? - // echo '' . esc_html( __( 'Show Documentation', 'radio-station' ) ) . ''; - echo '

      '; -} + // --- check for AJAX override save --- + // 2.3.2: added AJAX shift saving checks + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { -// ---------------- -// Show Shift Table -// ---------------- -// 2.3.2: separate shift table function (for AJAX saving) -function radio_station_show_shifts_table( $post_id ) { + // 2.3.3: added double check for AJAX action match + if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_override_save' != $_REQUEST['action'] ) ) { + return; + } - global $post; + // --- make sure we have a post ID for AJAX save --- + if ( !isset( $_POST['override_id'] ) || ( '' == $_POST['override_id'] ) ) { + return; + } + $post_id = absint( $_POST['override_id'] ); + $post = get_post( $post_id ); - // --- edit show link --- - $edit_link = add_query_arg( 'action', 'edit', admin_url( 'post.php' ) ); + // --- check for errors --- + $error = false; + if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !$post ) { + $error = __( 'Failed. Invalid Override.', 'radio-station' ); + } elseif ( !current_user_can( 'edit_shows' ) ) { + $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); + } - // 2.2.7: added meridiem translations - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); + // --- send error to parent frame --- + if ( $error ) { + echo ""; - // --- set days, hours and minutes arrays --- - $days = array( '', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); - $hours = $mins = array(); - for ( $i = 1; $i <= 12; $i ++ ) { - $hours[$i] = $i; - } - for ( $i = 0; $i < 60; $i ++ ) { - if ( $i < 10 ) { - $min = '0' . $i; - } else { - $min = $i; + exit; } - $mins[$i] = $min; } - // --- get the saved meta as an array --- - $shifts = radio_station_get_show_schedule( $post_id ); - $active = get_post_meta( $post_id, 'show_active', true ); + $meta_changed = $sched_changed = false; - $has_conflicts = false; - $list = ''; - if ( isset( $shifts ) && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { + // --- verify nonce for show data --- + // 2.3.3.9: added to save show override metabox data + if ( isset( $_POST['show_data_nonce'] ) && wp_verify_nonce( $_POST['show_data_nonce'], 'radio-station' ) ) { - // 2.2.7: soft shifts by start day and time for ordered display - foreach ( $shifts as $unique_id => $shift ) { - // 2.3.0: add shift index to prevent start time overwriting - $j = 1; - $shift['unique_id'] = $unique_id; - $shift_start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - $shift_start = radio_station_convert_shift_time( $shift_start ); - if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { - // --- group shifts by days of week --- - $starttime = radio_station_to_time( 'next ' . $shift['day'] . ' ' . $shift_start ); - // 2.3.0: simplify by getting day index - $i = array_search( $shift['day'], $days ); - $day_shifts[$i][$starttime . '.' . $j] = $shift; - } else { - // --- to still allow shift time sorting if day is not set --- - $starttime = radio_station_to_time( '1981-04-28 ' . $shift_start ); - $day_shifts[7][$starttime . '.' . $j] = $shift; - } - $j ++; + // --- save linked show ID --- + // 2.3.3.9: save linked show ID + // $linked_show_id = get_post_meta( $post_id, 'linked_show_id', true ); + $prev_linked = get_post_meta( $post_id, 'linked_show_id', true ); + $linked_id = absint( $_POST['linked_show_id'] ); + $linked_show = get_post( $linked_id ); + if ( !$linked_show ) { + delete_post_meta( $post_id, 'linked_show_id' ); + } else { + update_post_meta( $post_id, 'linked_show_id', $linked_id ); + } + if ( $linked_id != $prev_linked ) { + $meta_changed = true; } - // --- sort day shifts by day and time --- - ksort( $day_shifts ); - // 2.3.0: resort order using start of week - $sorted_shifts = array(); - $weekdays = radio_station_get_schedule_weekdays(); - foreach ( $weekdays as $i => $weekday ) { - if ( isset( $day_shifts[$i] ) ) { - $sorted_shifts[$i] = $day_shifts[$i]; + // --- sync genres switch --- + $sync_genres = false; + if ( isset( $_POST['sync_genres'] ) && ( 'yes' == $_POST['sync_genres'] ) ) { + + update_post_meta( $post_id, 'sync_genres', 'yes' ); + + if ( $linked_show ) { + + // --- sync genre terms --- + $prev_term_ids = $term_ids = array(); + $prev_terms = wp_get_object_terms( $post_id, RADIO_STATION_GENRES_SLUG ); + if ( count( $prev_terms ) > 0 ) { + foreach( $prev_terms as $prev_term ) { + $prev_term_ids[] = $prev_term->term_id; + } + } + $genre_terms = wp_get_object_terms( $linked_id, RADIO_STATION_GENRES_SLUG ); + print_r( $genre_terms ); + if ( count( $genre_terms ) > 0 ) { + foreach ( $genre_terms as $genre_term ) { + $term_ids[] = $genre_term->term_id; + } + } + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_GENRES_SLUG ); + if ( array_diff( $prev_term_ids, $term_ids ) === array_diff( $term_ids, $prev_term_ids ) ) { + $meta_changed = true; + } } + } else { + delete_post_meta( $post_id, 'sync_genres' ); } - if ( isset( $day_shifts[7] ) ) { - $sorted_shifts[7] = $day_shifts[7]; + + // --- sync languages switch --- + $sync_languages = false; + if ( isset( $_POST['sync_languages'] ) && ( 'yes' == $_POST['sync_languages'] ) ) { + + update_post_meta( $post_id, 'sync_languages', 'yes' ); + + // --- sync language terms --- + $prev_term_ids = $term_ids = array(); + $prev_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( count( $prev_terms ) > 0 ) { + foreach( $prev_terms as $prev_term ) { + $prev_term_ids[] = $prev_term->term_id; + } + } + $language_terms = wp_get_object_terms( $linked_id, RADIO_STATION_LANGUAGES_SLUG ); + print_r( $language_terms ); + if ( count( $language_terms ) > 0 ) { + foreach ( $language_terms as $language_term ) { + $term_ids[] = $language_term->term_id; + } + } + wp_set_post_terms( $post_id, $term_ids, RADIO_STATION_LANGUAGES_SLUG ); + if ( array_diff( $prev_term_ids, $term_ids ) === array_diff( $term_ids, $prev_term_ids ) ) { + $meta_changed = true; + } + + } else { + delete_post_meta( $post_id, 'sync_languages' ); } - $show_shifts = array(); - foreach ( $sorted_shifts as $shift_day => $day_shift ) { - // --- sort shifts by (unique) start time for each day --- - ksort( $day_shift ); - foreach ( $day_shift as $shift ) { - $unique_id = $shift['unique_id']; - unset( $shift['unique_id'] ); - $show_shifts[$unique_id] = $shift; + + // --- update linked show fields --- + $linked_show_fields = array(); + $show_fields = array( 'title', 'content', 'excerpt', 'avatar', 'image', 'user_list', 'producer_list', 'link', 'email', 'phone', 'file', 'download', 'patreon' ); + $non_input_fields = array( 'show_title', 'show_content', 'show_excerpt' ); + foreach ( $show_fields as $field ) { + $show_field = 'show_' . $field; + $linked = false; + if ( isset( $_POST[$show_field . '_link'] ) ) { + $linked = $_POST[$show_field . '_link']; + } + $linked_show_fields[$show_field] = ( 'yes' == $linked ) ? true : false; + if ( !in_array( $show_field, $non_input_fields ) ) { + if ( isset( $_POST[$show_field] ) ) { + $value = radio_station_sanitize_input( 'show', $field ); + update_post_meta( $post_id, $show_field, $value ); + } else { + delete_post_meta( $post_id, $show_field ); + } } } + update_post_meta( $post_id, 'linked_show_fields', $linked_show_fields ); - // --- loop ordered show shifts --- - foreach ( $show_shifts as $unique_id => $shift ) { + } - $classes = array( 'show-shift' ); + // --- verify this came from the our screen and with proper authorization --- + // 2.3.3.9: reverse condition to allow for combined data/schedule processing + if ( isset( $_POST['show_override_nonce'] ) && wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { + + // --- get the show override data --- + $show_sched = $_POST['show_sched']; + if ( is_array( $show_sched ) ) { + + // 2.3.3.9: loop to save possible multiple override dates/times --- + $new_scheds = array(); + foreach ( $show_sched as $sched ) { + + // --- get/set current schedule for merging --- + // 2.2.2: added to set default keys + // 2.3.3.9: just set new schedule array here + $new_sched = array( + 'date' => '', + 'start_hour' => '', + 'start_min' => '', + 'start_meridian' => '', + 'end_hour' => '', + 'end_min' => '', + 'end_meridian' => '', + 'disabled' => '', + // 'multiday' => '', + // 'end_date' => '', + ); - // --- check conflicts with other show shifts --- - // 2.3.0: added shift conflict checking - $conflicts = radio_station_check_shift( $post_id, $shift ); - if ( $conflicts && is_array( $conflicts ) ) { - $has_conflicts = true; - $classes[] = 'conflicts'; - } + // --- sanitize values before saving --- + // 2.2.2: loop and validate schedule override values + $override_dates = array(); + foreach ( $sched as $key => $value ) { + + $isvalid = false; + + // --- validate according to key --- + if ( ( 'date' === $key ) || ( 'end_date' == $key ) ) { + // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) + $parts = explode( '-', $value ); + // 2.3.3.9: added extra check for date parts + if ( 3 == count( $parts ) ) { + if ( checkdate( (int) $parts[1], (int) $parts[2], (int) $parts[0] ) ) { + $isvalid = true; + // 2.2.7: sync separate meta key for override date + // (could be used to improve column sorting efficiency) + // 2.3.3.9: store all override dates for later + $override_dates[] = $value; + } + } + } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { + $isvalid = true; + } + } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { + // 2.2.3: fix to validate 00 minute value + if ( empty( $value ) ) { + $isvalid = true; + } elseif ( absint( $value ) > - 1 && absint( $value ) < 61 ) { + $isvalid = true; + } + } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { + $valid = array( '', 'am', 'pm' ); + // 2.2.8: remove strict in_array checking + if ( in_array( $value, $valid ) ) { + $isvalid = true; + } + } elseif ( ( 'disabled' === $key ) || ( 'multiday' == $key ) ) { + if ( 'yes' == $value ) { + $isvalid = true; + } + } - // --- check if shift disabled --- - // 2.3.0: check shift disabled switch or show inactive - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $classes[] = 'disabled'; - } elseif ( !$active ) { - $classes[] = 'disabled'; + // --- if valid add to new schedule setting --- + if ( $isvalid ) { + $new_sched[$key] = $value; + } + } + + // --- add to new schedule array --- + // 2.3.3.9: to allow for multiple overrides + $new_scheds[] = $new_sched; } - $classlist = implode( " ", $classes ); - $list .= '
      '; - $list .= '
        '; - - // --- shift day selection --- - $list .= '
      • '; - $list .= esc_html( __( 'Day', 'radio-station' ) ) . ': '; - - $class = ''; - if ( '' == $shift['day'] ) { - $class = 'incomplete'; - } - $list .= ''; - $list .= ''; - $list .= '
      • '; - - // --- shift start time --- - $list .= '
      • '; - $list .= esc_html( __( 'Start Time', 'radio-station' ) ) . ': '; - - // --- start hour selection --- - $class = ''; - if ( '' == $shift['start_hour'] ) { - $class = 'incomplete'; - } - $list .= ''; - $list .= ''; - - // --- start minute selection --- - $list .= ''; - $list .= ''; - - // --- start meridiem selection --- - $class = ''; - if ( '' == $shift['start_meridian'] ) { - $class = 'incomplete'; - } - $list .= ''; - $list .= ''; - $list .= '
      • '; - - // --- shift end time --- - $list .= '
      • '; - $list .= esc_html( __( 'End Time', 'radio-station' ) ) . ': '; - - // --- end hour selection --- - $class = ''; - if ( '' == $shift['end_hour'] ) { - $class = 'incomplete'; - } - $list .= ''; - $list .= ''; - - // --- end minute selection --- - $list .= ''; - $list .= ''; - - // --- end meridiem selection --- - $class = ''; - if ( '' == $shift['end_meridian'] ) { - $class = 'incomplete'; - } - $list .= ''; - $list .= ''; - $list .= '
      • '; - - // --- encore presentation --- - if ( !isset( $shift['encore'] ) ) {$shift['encore'] = '';} - $list .= '
      • '; - $list .= ''; - $list .= esc_html( __( 'Encore', 'radio-station' ) ); - $list .= ''; - $list .= '
      • '; - - // --- shift disabled --- - // 2.3.0: added disabled checkbox to shift row - if ( !isset( $shift['disabled'] ) ) {$shift['disabled'] = '';} - $list .= '
      • '; - $list .= ''; - $list .= esc_html( __( 'Disabled', 'radio-station' ) ); - $list .= ''; - $list .= '
      • '; - - // --- duplicate shift icon --- - // 2.3.0: added duplicate shift icon - $list .= '
      • '; - $title = __( 'Duplicate Shift', 'radio-station' ); - $list .= ''; - $list .= '
      • '; - - // --- remove shift icon --- - // 2.3.0: change remove button to icon - $list .= '
      • '; - $title = __( 'Remove Shift', 'radio-station' ); - $list .= ''; - // $list .= ''; - // $list .= esc_html( __( 'Remove', 'radio-station' ) ); - $list .= ''; - $list .= '
      • '; - - $list .= '
      '; - - // --- output any shift conflicts found --- - if ( $conflicts && is_array( $conflicts ) && ( count( $conflicts ) > 0 ) ) { - $list .= '
      '; - $list .= '' . esc_html( __( 'Shift Conflicts', 'radio-station' ) ) . ': '; - foreach ( $conflicts as $j => $conflict ) { - if ( $j > 0 ) { - $list .= ', '; + // --- sort date_scheds by date keys --- + ksort( $date_scheds ); + foreach ( $date_scheds as $date => $scheds ) { + $sorted_scheds = array(); + foreach( $scheds as $i => $sched ) { + // --- sort by start time --- + $start = $sched['start_hour'] . ':' . $sched['start_min'] . $sched['start_meridian']; + $time = radio_station_convert_hour( $start, 24 ); + $timestamp = radio_station_to_time( $date . ' ' . $time ); + + // --- deduplicate based on timestamp --- + if ( isset( $sorted_sheds[$timestamp] ) ) { + while( isset( $sorted_scheds[$timestamp] ) ) { + $timestamp++; + } + $sched['disabled'] = 'yes'; + } + $sorted_scheds[$timestamp] = $sched; } - if ( $conflict['show'] == $post_id ) { - $list .= '' . esc_html( __('This Show', 'radio-station' ) ) . ''; - } else { - $show_edit_link = add_query_arg( 'post', $conflict['show'], $edit_link ); - $show_title = get_the_title( $conflict['show'] ); - $list .= '' . esc_html( $show_title ) . ''; + ksort( $sorted_scheds ); + $date_scheds[$date] = $sorted_scheds; + } + $new_scheds = array(); + foreach ( $date_scheds as $date => $scheds ) { + foreach ( $scheds as $sched ) { + $new_scheds[] = $sched; } - $conflict_start = esc_html( $conflict['shift']['start_hour'] ) . ':' . esc_html( $conflict['shift']['start_min'] ) . ' ' . esc_html( $conflict['shift']['start_meridian'] ); - $conflict_end = esc_html( $conflict['shift']['end_hour'] ) . ':' . esc_html( $conflict['shift']['end_min'] ). ' ' . esc_html( $conflict['shift']['end_meridian'] ); - $list .= ' - ' . esc_html( $conflict['shift']['day'] ) . ' ' . $conflict_start . ' - ' . $conflict_end; } - $list .= '

      '; } - // --- close shift wrapper --- - $list .= '
      '; - + // --- save schedule setting if changed --- + // 2.3.0: check if changed before saving + if ( $sched_changed ) { + update_post_meta( $post_id, 'show_override_sched', $new_scheds ); + } } } - // 2.3.2: moved into function and changed ID - $list .= ''; + // --- clear cached schedule if changed --- + // 2.3.3.9: clear cache on data/schedule change + if ( $meta_changed || $sched_changed ) { + radio_station_clear_cached_data( $post_id ); + } - // --- set return data --- - // 2.3.2: added for separated function - $table = array( - 'list' => $list, - 'active' => $active, - 'conflicts' => $has_conflicts, - ); + // --- update overrides table when AJAX saving --- + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( isset( $_POST['action'] ) && ( 'radio_station_override_save' == $_POST['action'] ) ) { - return $table; -} + // --- (hidden) debug information --- + echo "Previous Overrides: " . print_r( $current_scheds, true ) . PHP_EOL; + echo "New Overrides: " . print_r( $new_scheds, true ) . PHP_EOL; -// ----------------------------------- -// Add Show Description Helper Metabox -// ----------------------------------- -// 2.3.0: added metabox for show description helper text -add_action( 'add_meta_boxes', 'radio_station_add_show_helper_box' ); -function radio_station_add_show_helper_box() { - // 2.3.2: filter top metabox position - $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'helper' ); - add_meta_box( - 'radio-station-show-helper-box', - __( 'Show Description', 'radio-station' ), - 'radio_station_show_helper_box', - RADIO_STATION_SHOW_SLUG, - $position, - 'low' - ); -} + // --- display shifts saved message --- + // 2.3.3.9: fade out overrides saved message + $show_override_nonce = wp_create_nonce( 'radio-station' ); + echo ""; -// ------------------------------- -// Show Description Helper Metabox -// ------------------------------- -// 2.3.0: added metabox for show description helper text -function radio_station_show_helper_box() { + // 2.3.3.9: added check if override schedule changed + if ( $sched_changed ) { - // --- show description helper text --- - echo '

      '; - echo esc_html( __( "The text field below is for your Show Description. It will display in the About section of your Show page.", 'radio-station' ) ); - echo ' ' . esc_html( __( "It is not recommended to include your past show content or archives in this area, as it will affect the Show page layout your visitors see.", 'radio-station' ) ); - echo esc_html( __( "It may also impact SEO, as archived content won't have their own pages and thus their own SEO and Social meta rules.", 'radio-station' ) ) . "
      "; - echo esc_html( __( "We recommend using WordPress Posts to add new posts and assign them to your Show(s) using the Related Show metabox on the Post Edit screen so they display on the Show page.", 'radio-station' ) ); - echo ' ' . esc_html( __( "You can then assign them to a relevant Post Category for display on your site also.", 'radio-station' ) ); - echo '

      '; + // --- output new show shifts list --- + echo '
      '; + $table = radio_station_overrides_table( $post_id ); + // if ( $table['conflicts'] ) { + // radio_station_overrides_conflict_message(); + // } - // TODO: upgrade to Pro for upcoming Show Episodes blurb - // echo '
      ' . esc_html( 'In future, Radio Station Pro will include an Episodes post type', 'radio-station' ) ); - // TODO: change this text/link when Pro Episodes become available - // $upgrade_url = radio_station_get_upgrade_url(); - // echo '
      '; - // // echo esc_html( __( "Upgrade to Radio Station Pro', 'radio-station' ) ); - // echo esc_html( __( 'Find out more about Radio Station Pro', 'radio-station' ) ); - // echo ' →.'; + // phpcs:ignore WordPress.Security.OutputNotEscaped + echo $table['list']; -} + echo '
      '; -// ---------------------------------- -// Rename Show Featured Image Metabox -// ---------------------------------- -// 2.3.0: renamed from "Feature Image" to be clearer -// 2.3.0: removed this as now implementing show images separately -// (note this is the Show Logo for backwards compatibility reasons) -// add_action( 'do_meta_boxes', 'radio_station_rename_featured_image_metabox' ); -function radio_station_rename_featured_image_metabox() { - remove_meta_box( 'postimagediv', RADIO_STATION_SHOW_SLUG, 'side' ); - add_meta_box( - 'postimagediv', - __( 'Show Logo', 'radio-station' ), - 'post_thumbnail_meta_box', - RADIO_STATION_SHOW_SLUG, - 'side', - 'low' - ); -} + echo '
      '; -// ----------------------- -// Add Show Images Metabox -// ----------------------- -// 2.3.0: added show images metabox -add_action( 'add_meta_boxes', 'radio_station_add_show_images_metabox' ); -function radio_station_add_show_images_metabox() { - add_meta_box( - 'radio-station-show-images-metabox', - __( 'Show Images', 'radio-station' ), - 'radio_station_show_images_metabox', - array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ), - 'side', - 'low' - ); -} + // --- refresh show shifts list --- + echo ""; -// ------------------- -// Show Images Metabox -// ------------------- -// 2.3.0: added show header and avatar image metabox -// ref: https://codex.wordpress.org/Javascript_Reference/wp.media -function radio_station_show_images_metabox() { + // echo ""; - global $post; + // --- alert on conflicts --- + // if ( $table['conflicts'] ) { + // $warning = __( 'Warning! Overide conflicts detected.', 'radio-station' ); + // echo ""; + // } - if ( isset( $_GET['avatar_refix'] ) && ( 'yes' == $_GET['avatar_refix'] ) ) { - delete_post_meta( $post->ID, '_rs_image_updated', true ); - $show_avatar = radio_station_get_show_avatar_id( $post->ID ); - echo "Transferred ID: " . $show_avatar; - } + // --- re-initialize datepicker fields in parent window --- + $js .= "parent.jQuery('.override-date').each(function() { + jQuery(this).datepicker({dateFormat : 'yy-mm-dd'}); + });" . PHP_EOL; + + // --- reload the current schedule view --- + echo ""; - wp_nonce_field( 'radio-station', 'show_images_nonce' ); - $upload_link = get_upload_iframe_src( 'image', $post->ID ); + } - // --- get show avatar image info --- - $show_avatar = get_post_meta( $post->ID, 'show_avatar', true ); - $show_avatar_src = wp_get_attachment_image_src( $show_avatar, 'full' ); - $has_show_avatar = is_array( $show_avatar_src ); + exit; + } + } - // --- show avatar image --- - echo '
      '; +} - // --- image container --- - echo '
      '; - if ( $has_show_avatar ) { - echo ''; - } - echo '
      '; +// ---------------------------------- +// Add Schedule Override List Columns +// ---------------------------------- +// 2.2.7: added data columns to override list display +add_filter( 'manage_edit-' . RADIO_STATION_OVERRIDE_SLUG . '_columns', 'radio_station_override_columns', 6 ); +function radio_station_override_columns( $columns ) { - // --- add and remove links --- - echo '

      '; - $hidden = ''; - if ( $has_show_avatar ) { - $hidden = ' hidden'; + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); } - echo ''; - echo esc_html( __( 'Set Show Avatar Image', 'radio-station' ) ); - echo ''; - $hidden = ''; - if ( !$has_show_avatar ) { - $hidden = ' hidden'; + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); } - echo ''; - echo esc_html( __( 'Remove Show Avatar Image', 'radio-station' ) ); - echo ''; - echo '

      '; - - // --- hidden input for image ID --- - echo ''; - - echo '
      '; - - // --- check if show content header image is enabled --- - $header_image = radio_station_get_setting( 'show_header_image' ); - if ( $header_image ) { - - // --- get show header image info - $show_header = get_post_meta( $post->ID, 'show_header', true ); - $show_header_src = wp_get_attachment_image_src( $show_header, 'full' ); - $has_show_header = is_array( $show_header_src ); - - // --- show header image --- - echo '
      '; + $date = $columns['date']; + unset( $columns['date'] ); + $genres = $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] ); + $languages = $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG]; + unset( $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] ); - // --- image container --- - echo '
      '; - if ( $has_show_header ) { - echo ''; - } - echo '
      '; + // 2.3.3.9: list date and times in single column + $columns['override_times'] = esc_attr( __( 'Override Time(s)', 'radio-station' ) ); + $columns['shows_affected'] = esc_attr( __( 'Affected Show(s)', 'radio-station' ) ); + // 2.3.0: added description indicator column + // 2.3.3.9: removed description indicator column + // $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); + // 2.3.3.9: add linked show name column + $columns['linked_show'] = esc_attr( __( 'Linked Show', 'radio-station' ) ); + // 2.3.3.9: move genres and languages columns + $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] = $genres; + $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] = $languages; + // 2.3.2: added missing translation text domain + $columns['override_image'] = esc_attr( __( 'Image', 'radio-station' ) ); + // 2.3.3.9: do not re-add published date column (to reduce confusion) + // $columns['date'] = $date; - // --- add and remove links --- - echo '

      '; - $hidden = ''; - if ( $has_show_header ) { - $hidden = ' hidden'; - } - echo ''; - echo esc_html( __( 'Set Show Header Image', 'radio-station' ) ); - echo ''; - $hidden = ''; - if ( !$has_show_header ) { - $hidden = ' hidden'; - } - echo ''; - echo esc_html( __( 'Remove Show Header Image', 'radio-station' ) ); - echo ''; - echo '

      '; + return $columns; +} - // --- hidden input for image ID --- - echo ''; +// ----------------------------- +// Schedule Override Column Data +// ----------------------------- +// 2.2.7: added data columns for override list display +add_action( 'manage_' . RADIO_STATION_OVERRIDE_SLUG . '_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); +function radio_station_override_column_data( $column, $post_id ) { - echo '
      '; + global $radio_station_show_shifts; + // 2.3.3.9: list override date and times in single column + $overrides = get_post_meta( $post_id, 'show_override_sched', true ); + if ( array_key_exists( 'date', $overrides ) ) { + $overrides = array( $overrides ); } - // --- set images autosave nonce and iframe --- - $images_autosave_nonce = wp_create_nonce( 'show-images-autosave' ); - echo ''; - echo ''; + if ( 'override_times' == $column ) { + + // 2.3.3.9: loop possible multiple overrides + foreach ( $overrides as $i => $override ) { + + if ( count( $overrides ) > 1 ) { + echo '' . ( $i + 1 ) . ': '; + } - // --- image selection script --- - $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' ); - $js = "jQuery(function(){ + // 2.3.3.9: maybe display disabled for this override time + if ( isset( $override['disabled'] ) && ( 'yes' == $override['disabled'] ) ) { + echo '[Disabled]
      '; + } + + // 2.3.2: no need to apply timezone conversions here + $datetime = strtotime( $override['date'] ); + $month = date( 'F', $datetime ); + $month = radio_station_translate_month( $month, true ); + $weekday = date( 'l', $datetime ); + $weekday = radio_station_translate_weekday( $weekday ); + echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' ' . esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); + echo '
      '; + + // 2.3.3.9: merge override times into this columns + // 2.3.3.9: display according to selected time format + $time_format = radio_station_get_setting( 'clock_time_format' ); + if ( 12 == $time_format ) { + echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . esc_html( $override['start_meridian'] ); + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . esc_html( $override['end_meridian'] ); + } elseif ( 24 == $time_format ) { + $start_hour = radio_station_convert_hour( $override['start_hour'] . ' ' . $override['start_meridian'] ); + $end_hour = radio_station_convert_hour( $override['end_hour'] . ' ' . $override['end_meridian'] ); + echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ); + echo ' - ' . esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ); + } + echo '
      '; + } - var mediaframe, parentdiv, - imagesmetabox = jQuery('#radio-station-show-images-metabox'), - addimagelink = imagesmetabox.find('.upload-custom-image'), - deleteimagelink = imagesmetabox.find('.delete-custom-image'); + } elseif ( 'shows_affected' == $column ) { - /* Add Image on Click */ - addimagelink.on( 'click', function( event ) { + // --- maybe get all show shifts --- + if ( isset( $radio_station_show_shifts ) ) { + $show_shifts = $radio_station_show_shifts; + } else { + global $wpdb; + $query = "SELECT posts.post_title, meta.post_id, meta.meta_value FROM " . $wpdb->prefix . "postmeta AS meta + JOIN " . $wpdb->prefix . "posts as posts ON posts.ID = meta.post_id + WHERE meta.meta_key = 'show_sched' AND posts.post_status = 'publish'"; + // 2.3.0: get results as an array + $show_shifts = $wpdb->get_results( $query, ARRAY_A ); + $radio_station_show_shifts = $show_shifts; + } + if ( !$show_shifts || ( count( $show_shifts ) == 0 ) ) { + return; + } - event.preventDefault(); - parentdiv = jQuery(this).parent().parent(); + // 2.3.3.9: loop possible multiple overrides + $affected_shows = array(); + foreach ( $overrides as $i => $override ) { + + // --- get the override weekday --- + // 2.3.2: remove date time and get day from date directly + $weekday = date( 'l', strtotime( $override['date'] ) ); + + // --- get start and end override times --- + // 2.3.2: fix to convert to 24 hour format first + $start = $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; + $end = $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; + $start = radio_station_convert_shift_time( $start ); + $end = radio_station_convert_shift_time( $end ); + $override_start = radio_station_to_time( $override['date'] . ' ' . $start ); + $override_end = radio_station_to_time( $override['date'] . ' ' . $end ); + // (if the end time is less than start time, adjust end to next day) + if ( $override_end <= $override_start ) { + $override_end = $override_end + ( 24 * 60 * 60 ); + } + // 2.3.3.9: clear last shift for looping + if ( isset( $last_shift ) ) { + unset( $last_shift ); + } - if (mediaframe) {mediaframe.open(); return;} - mediaframe = wp.media({ - title: 'Select or Upload Image', - button: {text: 'Use this Image'}, - multiple: false - }); + // --- loop show shifts --- + foreach ( $show_shifts as $show_shift ) { + $shifts = maybe_unserialize( $show_shift['meta_value'] ); + if ( !is_array( $shifts ) ) { + $shifts = array(); + } - mediaframe.on( 'select', function() { - var attachment = mediaframe.state().get('selection').first().toJSON(); - image = '\"\"'; - parentdiv.find('.custom-image-container').append(image); - parentdiv.find('.custom-image-id').val(attachment.id); - parentdiv.find('.upload-custom-image').addClass('hidden'); - parentdiv.find('.delete-custom-image').removeClass('hidden'); + foreach ( $shifts as $shift ) { + if ( isset( $shift['day'] ) && ( $shift['day'] == $weekday ) ) { + + // --- get start and end shift times --- + // 2.3.0: validate shift time to check if complete + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + $time = radio_station_validate_shift( $shift ); + $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; + $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; + $start = radio_station_convert_shift_time( $start ); + $end = radio_station_convert_shift_time( $end ); + $shift_start = radio_station_to_time( $override['date'] . ' ' . $start ); + $shift_end = radio_station_to_time( $override['date'] . ' ' . $end ); + if ( ( $shift_start == $shift_end ) || ( $shift_start > $shift_end ) ) { + $shift_end = $shift_end + ( 24 * 60 * 60 ); + } - /* auto-save image via AJAX */ - postid = '" . $post->ID . "'; imgid = attachment.id; - if (parentdiv.attr('id') == 'show-avatar-image') {imagetype = 'avatar';} - if (parentdiv.attr('id') == 'show-header-image') {imagetype = 'header';} - imagessavenonce = jQuery('#show-images-save-nonce').val(); - framesrc = ajaxurl+'?action=radio_station_show_images_save'; - framesrc += '&post_id='+postid+'&image_type='+imagetype; - framesrc += '&image_id='+imgid+'&_wpnonce='+imagessavenonce; - jQuery('#show-images-save-frame').attr('src', framesrc); - }); + if ( RADIO_STATION_DEBUG ) { + echo $weekday . ': ' . $start . ' to ' . $end . '
      ' . PHP_EOL; + echo $override['date'] . ': ' . $shift_start . ' to ' . $shift_end . '
      ' . PHP_EOL; + echo $override['date'] . ': ' . $override_start . ' to ' . $override_end . '
      ' . PHP_EOL; + echo '
      ' . PHP_EOL; + } - mediaframe.open(); - }); + // --- compare override time overlaps to get affected shows --- + // 2.3.2: fix to override overlap checking logic + if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_start ) ) + || ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) + || ( $override_start == $shift_start ) + || ( ( $override_start > $shift_start ) && ( $override_end < $shift_end ) ) + || ( ( $override_start > $shift_start ) && ( $override_start < $shift_end ) ) ) { + + // 2.3.0: adjust cell display to two line (to allow for long show titles) + // 2.3.2: deduplicate show check (if same show as last show displayed) + // 2.3.3.9: buffer affected show shift output + $affected = ''; + if ( !isset( $last_show ) || ( $last_show != $show_shift['post_id'] ) ) { + $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); + if ( 'on' != $active ) { + $affected .= "[" . esc_html( __( 'Inactive', 'radio-station' ) ) . "] "; + } + $affected .= '' . $show_shift['post_title'] . "
      "; + } - /* Delete Image on Click */ - deleteimagelink.on( 'click', function( event ) { - event.preventDefault(); - agree = confirm('" . esc_js( $confirm_remove ) . "'); - if (!agree) {return;} - parentdiv = jQuery(this).parent().parent(); - parentdiv.find('.custom-image-container').html(''); - parentdiv.find('.custom-image-id').val(''); - parentdiv.find('.upload-custom-image').removeClass('hidden'); - parentdiv.find('.delete-custom-image').addClass('hidden'); - }); + if ( isset( $shift['disabled'] ) && $shift['disabled'] ) { + $affected .= "[" . esc_html( __( 'Disabled', 'radio-station' ) ) . "] "; + } + $affected .= radio_station_translate_weekday( $shift['day'] ) . ' '; + // 2.3.3.9: display according to time format setting + $time_format = radio_station_get_setting( 'clock_time_format' ); + if ( 12 == $time_format ) { + $affected .= esc_html( $shift['start_hour'] ) . ':' . esc_html( $shift['start_min'] ) . esc_html( $shift['start_meridian'] ); + $affected .= ' - ' . esc_html( $shift['end_hour'] ) . ':' . esc_html( $shift['end_min'] ) . esc_html( $shift['end_meridian'] ); + } elseif ( 24 == $time_format ) { + $start_hour = radio_station_convert_hour( $shift['start_hour'] ); + $end_hour = radio_station_convert_hour( $shift['end_hour'] ); + $affected .= esc_html( $start_hour ) . ':' . esc_html( $shift['start_min'] ); + $affected .= ' - ' . esc_html( $end_hour ) . ':' . esc_html( $shift['end_min'] ); + } + $affected .= "
      "; + + // 2.3.3.9: store affected shows by override index + $affected_shows[$i] = $affected; - });"; + // 2.3.2: store last show displayed + $last_show = $show_shift['post_id']; + } + } + } + } + } + // 2.3.3.9: output affected shows with numbered correlations + foreach ( $overrides as $i => $override ) { + if ( isset( $affected_shows[$i] ) ) { + if ( $i == 0 ) { + echo '
      '; + } else { + echo '
      '; + } + if ( count( $overrides ) > 1 ) { + echo '' . ( $i + 1 ). ': '; + } + echo $affected_shows[$i]; + echo '
      '; + } + } - // --- enqueue script inline --- - // 2.3.0: enqueue instead of echoing - wp_add_inline_script( 'radio-station-admin', $js ); + } elseif ( 'description' == $column ) { -} + // 2.3.0: added override description indicator + global $wpdb; + $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $post_id ); + $content = $wpdb->get_var( $query ); + if ( !$content || ( trim( $content ) == '' ) ) { + echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + } else { + echo esc_html( __( 'Yes', 'radio-station' ) ); + } -// --------------------------------- -// AJAX to AutoSave Images on Change -// --------------------------------- -add_action( 'wp_ajax_radio_station_show_images_save', 'radio_station_show_images_save' ); -function radio_station_show_images_save() { + } elseif ( 'linked_show' == $column ) { - global $post; + // --- get linked Show --- + $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); + if ( !$linked_id || ( '' == $linked_id ) ) { + echo '' . esc_html( __( 'None', 'radio-station' ) ) . ''; + } else { + // --- get linked show title --- + // TODO: maybe add edit Show link ? + global $wpdb; + $query = "SELECT post_title FROM " . $wpdb->prefix . "posts WHERE ID = %d"; + $query = $wpdb->prepare( $query, $linked_id ); + $post_title = $wpdb->get_var( $query ); + echo esc_html( $post_title ); + } + + } elseif ( 'override_image' == $column ) { - // --- sanitize posted values --- - if ( isset( $_GET['post_id'] ) ) { - $post_id = absint( $_GET['post_id'] ); - if ( $post_id < 1 ) { - unset( $post_id ); + // --- override/show avatar --- + // 2.3.3.9: apply filters to check for linked show override + $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); + $thumbnail_url = apply_filters( 'radio_station_show_avatar', $thumbnail_url, $post_id ); + if ( $thumbnail_url ) { + echo "
      " . esc_attr( __(
      "; } } +} - // 2.3.3.6: get post for checking capability - $post = get_post( $post_id ); - if ( !$post ) { - exit; - } +// ----------------------------- +// Sortable Override Date Column +// ----------------------------- +// 2.2.7: added to allow override date column sorting +add_filter( 'manage_edit-override_sortable_columns', 'radio_station_override_sortable_columns' ); +function radio_station_override_sortable_columns( $columns ) { + $columns['override_date'] = 'show_override_date'; + return $columns; +} - // --- check edit capability --- - if ( !current_user_can( 'edit_shows' ) ) { - exit; +// ------------------------------- +// Schedule Override Column Styles +// ------------------------------- +add_action( 'admin_footer', 'radio_station_override_column_styles' ); +function radio_station_override_column_styles() { + $currentscreen = get_current_screen(); + if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { + return; } - // --- verify nonce value --- - if ( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( $_GET['_wpnonce'], 'show-images-autosave' ) ) { - exit; + // 2.3.2: set override image column width to override image width + // 2.3.3.9: set override times column width + echo ""; +} + +// ---------------------------------- +// Add Schedule Override Month Filter +// ---------------------------------- +// 2.2.7: added month selection filtering +// add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); +function radio_station_override_date_filter( $post_type, $which ) { + + global $wp_locale; + if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { + return; } - if ( isset( $_GET['image_id'] ) ) { - $image_id = absint( $_GET['image_id'] ); - if ( $image_id < 1 ) { - unset( $image_id ); + // --- get all show override months / years --- + global $wpdb; + $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; + $results = $wpdb->get_results( $overridequery, ARRAY_A ); + $months = array(); + if ( $results && is_array( $results ) && ( count( $results ) > 0 ) ) { + foreach ( $results as $result ) { + $post_id = $result['ID']; + // 2.3.3.9: allow for multiple override dates + $overrides = get_post_meta( $post_id, 'show_override_date', false ); + foreach ( $overrides as $i => $override ) { + $datetime = radio_station_to_time( $override ); + $month = radio_station_get_time( 'm', $datetime ); + $year = radio_station_get_time( 'Y', $datetime ); + $months[$year . '-' . $month] = array( 'year' => $year, 'month' => $month ); + } } + } else { + return; } - if ( isset( $_GET['image_type'] ) ) { - if ( in_array( $_GET['image_type'], array( 'header', 'avatar' ) ) ) { - $image_type = $_GET['image_type']; + + // --- maybe get specified month --- + // TODO: maybe use get_query_var for month ? + $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + + // --- month override selector --- + echo ''; + echo ''; - if ( isset( $post_id ) && isset( $image_id ) && isset( $image_type ) ) { - update_post_meta( $post_id, 'show_' . $image_type, $image_id ); - } else { - exit; - } +} - // --- add image updated flag --- - // (help prevent duplication on new posts) - $updated = get_post_meta( $post_id, '_rs_image_updated', true ); - if ( !$updated ) { - add_post_meta( $post_id, '_rs_image_updated', true ); +// ------------------------------- +// Add Schedule Past Future Filter +// ------------------------------- +// 2.3.3: added past future filter prototype code +add_action( 'restrict_manage_posts', 'radio_station_override_past_future_filter', 10, 2 ); +function radio_station_override_past_future_filter( $post_type, $which ) { + + if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { + return; } - // --- refresh parent frame nonce --- - $images_save_nonce = wp_create_nonce( 'show-images-autosave' ); - echo ""; + // --- set past future selection / default --- + $pastfuture = isset( $_GET['pastfuture'] ) ? $_GET['pastfuture'] : ''; + $pastfuture = apply_filters( 'radio_station_overrides_past_future_default', $pastfuture ); - exit; + // --- past / future override selector --- + // 2.3.3.5: added option for today filtering + echo ''; + echo ''; + +} + + +// ----------------- +// === Playlists === +// ----------------- + +// ------------------------- +// Add Playlist Data Metabox +// ------------------------- +// --- Add custom repeating meta field for the playlist edit form --- +// (Stores multiple associated values as a serialized string) +// Borrowed and adapted from http://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed/19852#19852 +add_action( 'add_meta_boxes', 'radio_station_add_playlist_metabox' ); +function radio_station_add_playlist_metabox() { + // 2.2.2: change context to show at top of edit screen + // 2.3.2: filter top metabox position + $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'playlist' ); + add_meta_box( + 'radio-station-playlist-metabox', + __( 'Playlist Entries', 'radio-station' ), + 'radio_station_playlist_metabox', + RADIO_STATION_PLAYLIST_SLUG, + $position, + 'high' + ); } -// -------------------- -// Update Show Metadata -// -------------------- -// 2.3.2: added AJAX show save action -add_action( 'wp_ajax_radio_station_show_save_shifts', 'radio_station_show_save_data' ); -add_action( 'save_post', 'radio_station_show_save_data' ); -function radio_station_show_save_data( $post_id ) { - - // --- verify if this is an auto save routine --- - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return; - } +// --------------------- +// Playlist Data Metabox +// --------------------- +function radio_station_playlist_metabox() { - // --- make sure we have a post ID for AJAX save --- - // 2.3.2: added AJAX shift saving checks - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - // 2.3.3: added double check for AJAX action match - if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_show_save_shifts' != $_REQUEST['action'] ) ) { - return; - } - if ( !isset( $_POST['show_id'] ) || ( '' == $_POST['show_id'] ) ) { - return; - } - $post_id = absint( $_POST['show_id'] ); - $post = get_post( $post_id ); + global $post, $current_screen; - // --- check for errors --- - $error = false; - if ( !isset( $_POST['show_shifts_nonce'] ) || !wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { - $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); - } elseif ( !$post ) { - $error = __( 'Failed. Invalid Show.', 'radio-station' ); - } elseif ( !current_user_can( 'edit_shows' ) ) { - $error = __( 'Failed. Publish or Update instead.', 'radio-station' ); - } + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'playlist_tracks_nonce' ); - // --- send error to parent frame --- - if ( $error ) { - echo ""; + // --- get the saved meta as an array --- + // 2.3.2: set single argument to true + $entries = get_post_meta( $post->ID, 'playlist', true ); - exit; - } - } + // --- set button titles --- + // 2.3.2: added titles for button icons + $move_up_title = __( 'Move Track Up', 'radio-station' ); + $move_down_title = __( 'Move Track Down', 'radio-station' ); + $duplicate_title = __( 'Duplicate Track', 'radio-station' ); + $remove_title = __( 'Remove Track', 'radio-station' ); - // --- set show meta changed flags --- - $show_meta_changed = $show_shifts_changed = false; + // 2.3.3.9: move meta_inner ID to class + echo '
      '; - // --- get posted DJ / host list --- - // 2.2.7: check DJ post value is set - if ( isset( $_POST['show_hosts_nonce'] ) && wp_verify_nonce( $_POST['show_hosts_nonce'], 'radio-station' ) ) { + // 2.3.2: separate track list table + echo radio_station_playlist_track_table( $entries ); - if ( isset( $_POST['show_user_list'] ) ) { - $hosts = $_POST['show_user_list']; - } - if ( !isset( $hosts ) || !is_array( $hosts ) ) { - $hosts = array(); - } else { - foreach ( $hosts as $i => $host ) { - if ( !empty( $host ) ) { - $userid = get_user_by( 'ID', $host ); - if ( !$userid ) { - unset( $hosts[$i] ); - } - } - } - } - update_post_meta( $post_id, 'show_user_list', $hosts ); - $prev_hosts = get_post_meta( $post_id, 'show_user_list', true ); - if ( $prev_hosts != $hosts ) { - $show_meta_changed = true; - } + // --- track save/add buttons --- + // 2.3.2: change track save from button-primary to button-secondary + // 2.3.2: added playlist AJAX save button (for existing posts only) + // 2.3.2: added playlist tracks clear button + // 2.3.2: added table and track saved message + echo ''; + + // 2.3.3.9: change to single cell spanning 3 columns + echo '
      '; + echo ''; + echo ''; + if ( 'add' != $current_screen->action ) { + echo ''; } + echo ''; + echo ''; + echo '
      '; + echo ''; + echo ''; + echo ''; + echo '
      '; + + echo '
      '; + + // --- move new tracks message --- + // 2.3.2: added new track move message + echo '
      ' . __( 'Tracks marked New are moved to the end of Playlist on update.', 'radio-station' ) . '
      '; - // --- get posted show producers --- - // 2.3.0: added show producer sanitization - if ( isset( $_POST['show_producers_nonce'] ) && wp_verify_nonce( $_POST['show_producers_nonce'], 'radio-station' ) ) { - - if ( isset( $_POST['show_producer_list'] ) ) { - $producers = $_POST['show_producer_list']; - } - if ( !isset( $producers ) || !is_array( $producers ) ) { - $producers = array(); - } else { - foreach ( $producers as $i => $producer ) { - if ( !empty( $producer ) ) { - $userid = get_user_by( 'ID', $producer ); - if ( !$userid ) { - unset( $producers[$i] ); - } - } - } - } - // 2.3.0: added save of show producers - update_post_meta( $post_id, 'show_producer_list', $producers ); - $prev_producers = get_post_meta( $post_id, 'show_producer_list', true ); - if ( $prev_producers != $producers ) { - $show_meta_changed = true; + // --- clear all tracks function --- + $confirm_clear = __( 'Are you sure you want to clear the track list?', 'radio-station' ); + $js = "function radio_tracks_clear() { + if (jQuery('#track-table tr').length) { + var agree = confirm('" . esc_js( $confirm_clear ) . "'); + if (!agree) {return false;} + jQuery('#track-table tr').remove(); + trackcount = 1; } - } - - // --- save show meta data --- - // 2.3.0: added separate nonce check for show meta - if ( isset( $_POST['show_meta_nonce'] ) && wp_verify_nonce( $_POST['show_meta_nonce'], 'radio-station' ) ) { + }" . PHP_EOL; - // --- get the meta data to be saved --- - // 2.2.3: added show metadata value sanitization - $file = wp_strip_all_tags( trim( $_POST['show_file'] ) ); - $email = sanitize_email( trim( $_POST['show_email'] ) ); - $link = filter_var( trim( $_POST['show_link'] ), FILTER_SANITIZE_URL ); - $patreon_id = sanitize_title( $_POST['show_patreon'] ); - - // 2.3.3.6: added phone number with character filter validation - $phone = trim( $_POST['show_phone'] ); - if ( strlen( $phone ) > 0 ) { - $phone = str_split( $phone, 1 ); - $phone = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $phone ); - if ( count( $phone ) > 0 ) { - $phone = implode( '', $phone ); - } else { - $phone = ''; - } + // --- save tracks via AJAX --- + // 2.3.2: added form input cloning to save playlist tracks + $ajaxurl = admin_url( 'admin-ajax.php' ); + $js .= "function radio_tracks_save() { + jQuery('#track-save-form, #track-save-frame').remove(); + form = '
      '; + form += '
      '; + jQuery('#wpbody').append(form); + if (!jQuery('#track-save-frame').length) { + frame = ''; + jQuery('#wpbody').append(frame); } + /* copy tracklist input fields and nonce */ + jQuery('#track-table input').each(function() {jQuery(this).clone().appendTo('#track-save-form');}); + jQuery('#track-table select').each(function() { + name = jQuery(this).attr('name'); value = jQuery(this).children('option:selected').val(); + jQuery('').appendTo('#track-save-form'); + }); + jQuery('#playlist_tracks_nonce').clone().attr('id','').appendTo('#track-save-form'); + jQuery('#post_ID').clone().attr('id','').attr('name','playlist_id').appendTo('#track-save-form'); + jQuery('#tracks-saving-message').show(); + jQuery('#track-save-form').submit(); + }" . PHP_EOL; - // 2.2.8: removed strict in_array checking - // 2.3.2: fix for unchecked boxes index warning - $active = $download = ''; - if ( isset( $_POST['show_active'] ) ) { - $active = $_POST['show_active']; - } - if ( !in_array( $active, array( '', 'on' ) ) ) { - $active = ''; - } - // 2.3.2: added download disable switch - if ( isset( $_POST['show_download'] ) ) { - $download = $_POST['show_download']; + // --- move track up or down --- + // 2.3.2: added move track function + $js .= "function radio_track_move(updown, n) { + /* swap track rows */ + if (updown == 'up') { + m = n - 1; + jQuery('#track-'+n+'-rowa').insertBefore('#track-'+m+'-rowa'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ } - if ( !in_array( $download, array( '', 'on' ) ) ) { - $download = ''; + if (updown == 'down') { + m = n + 1; + jQuery('#track-'+n+'-rowa').insertAfter('#track-'+m+'-rowb'); + jQuery('#track-'+n+'-rowb').insertAfter('#track-'+n+'-rowa'); + /* jQuery('#track-'+n+'-rowc').insertAfter('#track-'+n+'-rowb'); */ } + /* reset track classes */ + radio_track_classes(); - // --- get existing values and check if changed --- - // 2.3.0: added check against previous values - // 2.3.2: added download disable switch - // 2.3.3.6: added phone number field saving - $prev_file = get_post_meta( $post_id, 'show_file', true ); - $prev_download = get_post_meta( $post_id, 'show_download', true ); - $prev_email = get_post_meta( $post_id, 'show_email', true ); - $prev_phone = get_post_meta( $post_id, 'show_phone', true ); - $prev_active = get_post_meta( $post_id, 'show_active', true ); - $prev_link = get_post_meta( $post_id, 'show_link', true ); - $prev_patreon_id = get_post_meta( $post_id, 'show_patreon', true ); - if ( ( $prev_active != $active ) || ( $prev_link != $link ) - || ( $prev_email != $email ) || ( $prev_phone != $phone ) - || ( $prev_file != $file ) || ( $prev_download != $download ) - || ( $prev_patreon_id != $patreon_id ) ) { - $show_meta_changed = true; - } + /* swap track count */ + jQuery('#track-'+n+'-rowa .track-count').html(m); + jQuery('#track-'+m+'-rowa .track-count').html(n); - // --- update the show metadata --- - // 2.3.2: added download disable switch - update_post_meta( $post_id, 'show_file', $file ); - update_post_meta( $post_id, 'show_download', $download ); - update_post_meta( $post_id, 'show_email', $email ); - update_post_meta( $post_id, 'show_phone', $phone ); - update_post_meta( $post_id, 'show_active', $active ); - update_post_meta( $post_id, 'show_link', $link ); - update_post_meta( $post_id, 'show_patreon', $patreon_id ); - } + /* swap input name keys */ + jQuery('#track-'+n+'-rowa input, #track-'+n+'-rowb input, #track-'+n+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+n+']', '['+m+']')); + }); + jQuery('#track-'+m+'-rowa input, #track-'+m+'-rowb input, #track-'+m+'-rowb select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+m+']', '['+n+']')); + }); + /* swap button actions */ + jQuery('#track-'+n+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+m+');'); + jQuery('#track-'+n+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+m+');'); + jQuery('#track-'+m+'-rowb .track-arrow-up').attr('onclick', 'radio_track_move(\"up\", '+n+');'); + jQuery('#track-'+m+'-rowb .track-arrow-down').attr('onclick', 'radio_track_move(\"down\", '+n+');'); + jQuery('#track-'+n+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+m+');'); + jQuery('#track-'+n+'-rowb .track-remove').attr('onclick','radio_track_remove('+m+');'); + jQuery('#track-'+m+'-rowb .track-duplicate').attr('onclick','radio_track_duplicate('+n+');'); + jQuery('#track-'+m+'-rowb .track-remove').attr('onclick','radio_track_remove('+n+');'); - // --- update the show images --- - if ( isset( $_POST['show_images_nonce'] ) && wp_verify_nonce( $_POST['show_images_nonce'], 'radio-station' ) ) { + /* swap row IDs */ + jQuery('#track-'+m+'-rowa').attr('id', 'track-0-rowa'); + jQuery('#track-'+m+'-rowb').attr('id', 'track-0-rowb'); + jQuery('#track-'+n+'-rowa').attr('id', 'track-'+m+'-rowa'); + jQuery('#track-'+n+'-rowb').attr('id', 'track-'+m+'-rowb'); + jQuery('#track-0-rowa').attr('id', 'track-'+n+'-rowa'); + jQuery('#track-0-rowb').attr('id', 'track-'+n+'-rowb'); + }" . PHP_EOL; - // --- show header image --- - if ( isset( $_POST['show_header'] ) ) { - $header = absint( $_POST['show_header'] ); - if ( $header > 0 ) { - // $prev_header = get_post_meta( $post_id, 'show_header', true ); - // if ( $header != $prev_header ) {$show_meta_changed = true;} - update_post_meta( $post_id, 'show_header', $header ); - } - } + // --- reset first and last track classes --- + $js .= "function radio_track_classes() { + jQuery('.track-rowa, .track-rowb, .track-rowc').removeClass('first-track').removeClass('last-track'); + jQuery('.track-rowa').first().addClass('first-track'); jQuery('.track-rowa').last().addClass('last-track'); + jQuery('.track-rowb').first().addClass('first-track'); jQuery('.track-rowb').last().addClass('last-track'); + /* jQuery('.track-rowc').first().addClass('first-track'); jQuery('.track-rowc').last().addClass('last-track'); */ + }" . PHP_EOL; - // --- show avatar image --- - $avatar = absint( $_POST['show_avatar'] ); - if ( $avatar > 0 ) { - // $prev_avatar = get_post_meta( $post_id, 'show_avatar', true ); - // if ( $avatar != $prev_avatar ) {$show_meta_changed = true;} - update_post_meta( $post_id, 'show_avatar', $avatar ); - } + // --- add track function --- + // 2.3.0: set javascript as string to enqueue + // 2.3.2: added missing track-meta cell class + // 2.3.2: added track move arrows + // 2.3.2: added first and last row classes + // 2.3.2: set to standalone onclick function + $js .= "function radio_track_add() { + if (trackcount == 1) {classes = 'first-track last-track';} else {classes = 'last-track';} + output = ''; + output += ''+trackcount+''; + output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += ''; + output += '" . esc_js( __( 'Comments', 'radio-station' ) ) . ": '; + output += '
      " . esc_js( __( 'New', 'radio-station' ) ) . ":
      '; + output += '
      '; + output += '
      " . esc_js( __( 'Status', 'radio-station' ) ) . ":
      '; + output += '
      '; + output += ''; + output += '
      " . esc_js( __( 'Move', 'radio-station') ) . "
      : '; + output += '
      '; + output += '
      '; + output += '
      '; + output += '
      '; + output += ''; + output += ''; - // --- add image updated flag --- - // (to prevent duplication for new posts) - $updated = get_post_meta( $post_id, '_rs_image_updated', true ); - if ( !$updated ) { - add_post_meta( $post_id, '_rs_image_updated', true ); - } - } + /* output += ''; + output += ''; */ - // --- check show shift nonce --- - if ( isset( $_POST['show_shifts_nonce'] ) && wp_verify_nonce( $_POST['show_shifts_nonce'], 'radio-station' ) ) { + jQuery('#track-table').append(output); + trackcount++; + radio_track_classes(); + return false; + }" . PHP_EOL; - // --- loop posted show shift times --- - // 2.3.1: added check if any shifts are set (fix undefined index warning) - $shifts = $new_shifts = array(); - if ( isset( $_POST['show_sched'] ) ) { - $shifts = $_POST['show_sched']; + // --- duplicate track function --- + $js .= "function radio_track_duplicate(id) { + var i; var nextid = id + 1; + /* shift rows down */ + for (i = trackcount; i > id; i--) { + jQuery('#track-'+i+'-rowa, #track-'+i+'-rowb').each(function() { + jQuery(this).attr('id', jQuery(this).attr('id').replace(i, (i+1))); + jQuery(this).find('.track-count').html(i+1); + jQuery(this).find('input, select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+i+']', '['+(i+1)+']')); + }); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\",'+(i+1)+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\",'+(i+1)+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+(i+1)+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+(i+1)+');'); + }); } - $prev_shifts = radio_station_get_show_schedule( $post_id ); - $days = array( '', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ); - if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { - foreach ( $shifts as $i => $shift ) { + /* add duplicate row */ + jQuery('#track-'+id+'-rowa').clone().attr('id','track-'+nextid+'-rowa').insertAfter('#track-'+id+'-rowb'); + jQuery('#track-'+id+'-rowb').clone().attr('id','track-'+nextid+'-rowb').insertAfter('#track-'+nextid+'-rowa'); + jQuery('#track-'+nextid+'-rowa .track-count').html(nextid); + jQuery('#track-'+nextid+'-rowa, #track-'+nextid+'-rowb').each(function() { + jQuery(this).find('input, select').each(function() { + jQuery(this).attr('name', jQuery(this).attr('name').replace('['+id+']', '['+nextid+']')); + }); + jQuery(this).find('.track-arrow-up').attr('onclick','radio_track_move(\"up\", '+nextid+');'); + jQuery(this).find('.track-arrow-down').attr('onclick','radio_track_move(\"down\", '+nextid+');'); + jQuery(this).find('.track-duplicate').attr('onclick','radio_track_duplicate('+nextid+');'); + jQuery(this).find('.track-remove').attr('onclick','radio_track_remove('+nextid+');'); + }); + radio_track_classes(); + trackcount++; + }" . PHP_EOL; - // --- reset shift disabled flag --- - // 2.3.0: added shift disabling logic - $disabled = false; + // --- remove track function --- + // 2.3.2: reset first and last classes on remove + // 2.3.2: set to standalone onclick function + $js .= "function radio_track_remove(id) { + jQuery('#track-'+id+'-rowa, #track-'+id+'-rowb, #track-'+id+'-rowc').remove(); + radio_track_classes(); trackcount--; - // --- maybe generate new unique shift ID --- - if ( 'new-' == substr( $i, 0, 4 ) ) { - $i = radio_station_unique_shift_id(); - } + /* renumber track count */ + var tcount = 1; + jQuery('.track-rowa').each(function() { + jQuery(this).find('.track-count').html(tcount); tcount++; + }); + }" . PHP_EOL; - // --- loop shift keys --- - foreach ( $shift as $key => $value ) { + // --- set track count --- + // 2.3.2: set count from row count length + // 2.3.2: removed document ready wrapper + $js .= "var trackcount = jQuery('.track-rowa').length + 1;"; - // --- validate according to key --- - $isvalid = false; - if ( 'day' === $key ) { + // --- enqueue inline script --- + // 2.3.0: enqueue instead of echoing + wp_add_inline_script( 'radio-station-admin', $js ); - // --- check shift day --- - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $days ) ) { - $isvalid = true; - } - if ( '' == $value ) { - // 2.3.0: auto-disable if no day is set - $disabled = true; - } + // --- track list styles --- + // 2.3.0: added track meta style fix + // 2.3.2: added track meta select font size fix + // 2.3.2: added track move arrow styles + // 2.3.2: added table buttons styling + // 2.3.2: added track save message styling + echo ''; - } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { + // --- close meta inner --- + echo '
      '; - // --- check shift start and end hour --- - if ( empty( $value ) ) { - // 2.3.0: auto-disable shift if not start/end hour - $isvalid = $disabled = true; - } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { - $isvalid = true; - } + // 2.3.2: removed publish button duplication + // (replaced with track save AJAX button) +} - } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { +// ---------------- +// Track List Table +// ---------------- +// 2.3.2: separated tracklist table (for AJAX) +function radio_station_playlist_track_table( $entries ) { - // --- check shift start and end minute --- - if ( empty( $value ) ) { - // 2.3.0: auto-set minute value to 00 if empty - $isvalid = true; - $value = '00'; - } elseif ( ( absint( $value ) > - 1 ) && ( absint( $value ) < 61 ) ) { - $isvalid = true; - } else { - $disabled = true; - } + // --- open track table --- + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; - } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { + // --- set button titles --- + // 2.3.2: added titles for icon buttons + $move_up_title = __( 'Move Track Up', 'radio-station' ); + $move_down_title = __( 'Move Track Down', 'radio-station' ); + $duplicate_title = __( 'Duplicate Track', 'radio-station' ); + $remove_title = __( 'Remove Track', 'radio-station' ); - // --- check shift meridiem --- - $valid = array( '', 'am', 'pm' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; - } - if ( '' == $value ) { - $disabled = true; - } + // 2.3.2: removed [0] array key + $c = 1; + if ( isset( $entries ) && !empty( $entries ) ) { - } elseif ( 'encore' === $key ) { + foreach ( $entries as $track ) { + if ( isset( $track['playlist_entry_artist'] ) || isset( $track['playlist_entry_song'] ) + || isset( $track['playlist_entry_album'] ) || isset( $track['playlist_entry_label'] ) + || isset( $track['playlist_entry_comments'] ) || isset( $track['playlist_entry_new'] ) + || isset( $track['playlist_entry_status'] ) ) { - // --- check shift encore switch --- - // 2.2.4: fix to missing encore sanitization saving - $valid = array( '', 'on' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; - } + // --- track row a --- + $class = ''; + if ( 1 == $c ) { + $class = 'first-track'; + } elseif ( $c == count( $entries ) ) { + $class = 'last-track'; + } + echo ''; - } elseif ( 'disabled' == $key ) { + // --- track count --- + echo ''; - // --- check shift disabled switch --- - // 2.3.0: added shift disable switch - // note: overridden on incomplete data or shift conflict - $valid = array( '', 'yes' ); - if ( in_array( $value, $valid ) ) { - $isvalid = true; - } - if ( 'yes' == $value ) { - $disabled = true; - } + // --- track entry inputs --- + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; - } + // --- track row b --- + echo ''; - // --- if valid add to new schedule --- - if ( $isvalid ) { - $new_shifts[$i][$key] = $value; - } else { - $new_shifts[$i][$key] = ''; - } - } + // --- track comments --- + echo ''; - // --- check for shift conflicts with other shows --- - // 2.3.0: added show shift conflict checking - if ( !$disabled ) { - $conflicts = radio_station_check_shift( $post_id, $new_shifts[$i], 'shows' ); - if ( $conflicts ) { - $disabled = true; - if ( RADIO_STATION_DEBUG ) { - echo "*Conflicting Shift Disabled*"; - } - } - } + // --- track meta --- + echo ''; - // --- disable if incomplete data or shift conflicts --- - if ( $disabled ) { - $new_shifts[$i]['disabled'] = 'yes'; - if ( RADIO_STATION_DEBUG ) { - echo "*Shift Disabled*"; - } - } + // 2.3.2: added move track arrows + echo ''; + echo ''; - // --- recheck for conflicts with other shifts for this show --- - // 2.3.0: added new shift conflict checking - $new_shifts = radio_station_check_new_shifts( $new_shifts ); + // --- track row c --- + // TODO: add track time / start / end input fields ? + // echo ''; + // echo ''; - // --- update the schedule meta entry --- - // 2.3.0: check if shift times have changed before saving - if ( $new_shifts != $prev_shifts ) { - $show_shifts_changed = true; - update_post_meta( $post_id, 'show_sched', $new_shifts ); + $c ++; } - } else { - // 2.3.0: fix to clear data if all shifts removed - delete_post_meta( $post_id, 'show_sched' ); - $show_shifts_changed = true; } } + echo '
      ' . esc_html( __( 'Artist', 'radio-station' ) ) . '' . esc_html( __( 'Song', 'radio-station' ) ) . '' . esc_html( __( 'Album', 'radio-station' ) ) . '' . esc_html( __( 'Record Label', 'radio-station' ) ) . '
      ' . esc_html( $c ) . '
      ' . esc_html__( 'Comments', 'radio-station' ) . ' '; + echo ''; + echo '
      ' . esc_html( __( 'New', 'radio-station' ) ) . ':
      '; + // 2.3.2: remove new value checking as now used and cleared on save + // $track['playlist_entry_new'] = isset( $track['playlist_entry_new'] ) ? $track['playlist_entry_new'] : false; + // ' . checked( $track['playlist_entry_new'] ) . ' + echo '
      '; + echo '
      ' . esc_html( __( 'Status', 'radio-station' ) ) . ':
      '; + echo '
      '; + echo '
      ' . esc_html( __( 'Move', 'radio-station') ) . ':
      '; + echo '
      '; + echo '
      '; - } + // --- remove track button --- + echo '
      '; + echo '
      '; + echo '
      '; +} - // --- maybe clear transient data --- - // 2.3.0: added to clear transients if any meta has changed - // 2.3.3: remove current show transient - if ( $show_meta_changed || $show_shifts_changed ) { - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - delete_transient( 'radio_station_previous_show' ); +// ----------------------------------- +// Add Assign Playlist to Show Metabox +// ----------------------------------- +// (add metabox for assigning playlist to show) +add_action( 'add_meta_boxes', 'radio_station_add_playlist_show_metabox' ); +function radio_station_add_playlist_show_metabox() { + // 2.2.2: add high priority to shift above publish box + add_meta_box( + 'radio-station-playlist-show-metabox', + __( 'Linked Show', 'radio-station' ), + 'radio_station_playlist_show_metabox', + RADIO_STATION_PLAYLIST_SLUG, + 'side', + 'high' + ); +} - // 2.3.4: delete all prefixed transients (for times) - radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); - radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); - radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); +// ------------------------------- +// Assign Playlist to Show Metabox +// ------------------------------- +function radio_station_playlist_show_metabox() { - do_action( 'radio_station_clear_data', 'show', $post_id ); - do_action( 'radio_station_clear_data', 'show_meta', $post_id ); + global $post, $wpdb; - // --- set last updated schedule time --- - // 2.3.2: added for data API use - update_option( 'radio_station_schedule_updated', time() ); + $user = wp_get_current_user(); - // --- maybe send directory ping --- - // 2.3.1: added directory update ping option - // 2.3.2: queue directory ping - radio_station_queue_directory_ping(); + // --- check that we have at least one show --- + // 2.3.0: moved up to check for any shows + $args = array( + 'numberposts' => - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + ); + $shows = get_posts( $args ); + if ( count( $shows ) > 0 ) { + $have_shows = true; + } else { + $have_shows = false; } - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - if ( isset( $_POST['action'] ) && ( 'radio_station_show_save_shifts' == $_POST['action'] ) ) { + // --- maybe restrict show selection to user-assigned shows --- + // 2.2.8: remove strict argument from in_array checking + // 2.3.0: added check for new Show Editor role + // 2.3.0: added check for edit_others_shows capability + if ( !in_array( 'administrator', $user->roles ) + && !in_array( 'show-editor', $user->roles ) + && !current_user_can( 'edit_others_shows' ) ) { - // --- (hidden) debug information --- - echo "Posted Shifsts: " . print_r( $_POST['show_sched'], true ) . PHP_EOL; - echo "New Shifts: " . print_r( $new_shifts, true ) . PHP_EOL; + // --- get the user lists for all shows --- + $allowed_shows = array(); + $query = "SELECT pm.meta_value, pm.post_id FROM " . $wpdb->prefix . "postmeta pm"; + $query .= " WHERE pm.meta_key = 'show_user_list'"; + $show_user_lists = $wpdb->get_results( $query ); - // --- display shifts saved message --- - $show_shifts_nonce = wp_create_nonce( 'radio-station' ); - echo ""; + // ---- check each list for the current user --- + foreach ( $show_user_lists as $user_list ) { - // --- output new show shifts list --- - echo '
      '; - if ( isset( $_REQUEST['check-bypass'] ) && ( '1' == $_REQUEST['check-bypass'] ) ) { - echo ''; - } - $table = radio_station_show_shifts_table( $post_id ); - if ( $table['conflicts'] ) { - radio_station_shifts_conflict_message(); + $user_list->meta_value = maybe_unserialize( $user_list->meta_value ); + + // --- if a list has no users, unserialize() will return false instead of an empty array --- + // (fix that to prevent errors in the foreach loop) + if ( !is_array( $user_list->meta_value ) ) { + $user_list->meta_value = array(); } - // phpcs:ignore WordPress.Security.OutputNotEscaped - echo $table['list']; + // --- only include shows the user is assigned to --- + foreach ( $user_list->meta_value as $user_id ) { + if ( $user->ID === $user_id ) { + $allowed_shows[] = $user_list->post_id; + } + } + } - echo '
      '; - echo '
      '; + $args = array( + 'numberposts' => - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'aSC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', + 'include' => implode( ',', $allowed_shows ), + ); - // --- refresh show shifts list --- - echo ""; + $shows = get_posts( $args ); + } - // 2.3.3.6: clear changes may not have been saved window reload message - echo ""; + // 2.3.3.9: move meta_inner ID to class + echo '
      '; + if ( !$have_shows ) { + echo esc_html( __( 'No Shows were found.', 'radio-station' ) ); + } else { + if ( count( $shows ) < 1 ) { + echo esc_html( __( 'You are not assigned to any Shows.', 'radio-station' ) ); + } else { + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'playlist_show_nonce' ); - // --- alert on conflicts --- - if ( $table['conflicts'] ) { - $warning = __( 'Warning! Shift conflicts detected.', 'radio-station' ); - echo ""; + // --- select show to assign playlist to --- + $current = get_post_meta( $post->ID, 'playlist_show_id', true ); + echo ''; } } - + echo '
      '; } -// ----------------- -// Save Output Debug -// ----------------- -add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 0 ); -add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 0 ); -add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 0 ); -function radio_station_save_debug_start( $post_id ) { - if ( !RADIO_STATION_SAVE_DEBUG ) { - return; - } - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return; - } - ob_start(); -} -add_action( 'save_post_' . RADIO_STATION_SHOW_SLUG, 'radio_station_save_debug_start', 9999 ); -add_action( 'save_post_' . RADIO_STATION_OVERRIDE_SLUG, 'radio_station_save_debug_start', 9999 ); -add_action( 'save_post_' . RADIO_STATION_PLAYLIST_SLUG, 'radio_station_save_debug_start', 9999 ); -function radio_station_save_debug_end( $post_id ) { - if ( !RADIO_STATION_SAVE_DEBUG ) { - return; - } +// -------------------- +// Update Playlist Data +// -------------------- +// 2.3.2: added action for AJAX save of tracks +add_action( 'wp_ajax_radio_station_playlist_save_tracks', 'radio_station_playlist_save_data' ); +add_action( 'save_post', 'radio_station_playlist_save_data' ); +function radio_station_playlist_save_data( $post_id ) { + + // --- verify if this is an auto save routine --- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } - $contents = ob_get_contents(); - ob_end_clean(); - if ( strlen( $contents ) > 0 ) { - echo "Output Detected During Save (preventing redirect):
      "; - echo ''; - exit; - } -} - -// --------------------- -// Add Show List Columns -// --------------------- -// 2.2.7: added data columns to show list display -add_filter( 'manage_edit-' . RADIO_STATION_SHOW_SLUG . '_columns', 'radio_station_show_columns', 6 ); -function radio_station_show_columns( $columns ) { - - if ( isset( $columns['thumbnail'] ) ) { - unset( $columns['thumbnail'] ); - } - if ( isset( $columns['post_thumb'] ) ) { - unset( $columns['post_thumb'] ); - } - - $date = $columns['date']; - unset( $columns['date'] ); - $comments = $columns['comments']; - unset( $columns['comments'] ); - $genres = $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG]; - unset( $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] ); - $languages = $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG]; - unset( $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] ); - - $columns['active'] = esc_attr( __( 'Active?', 'radio-station' ) ); - // 2.3.0: added show description indicator column - $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); - $columns['shifts'] = esc_attr( __( 'Shifts', 'radio-station' ) ); - // 2.3.0: change DJs column label to Hosts - $columns['hosts'] = esc_attr( __( 'Hosts', 'radio-station' ) ); - $columns['taxonomy-' . RADIO_STATION_GENRES_SLUG] = $genres; - $columns['taxonomy-' . RADIO_STATION_LANGUAGES_SLUG] = $languages; - $columns['comments'] = $comments; - $columns['date'] = $date; - $columns['show_image'] = esc_attr( __( 'Show Avatar', 'radio-station' ) ); - - return $columns; -} - -// --------------------- -// Show List Column Data -// --------------------- -// 2.2.7: added data columns for show list display -add_action( 'manage_' . RADIO_STATION_SHOW_SLUG . '_posts_custom_column', 'radio_station_show_column_data', 5, 2 ); -function radio_station_show_column_data( $column, $post_id ) { - if ( 'active' == $column ) { - - $active = get_post_meta( $post_id, 'show_active', true ); - if ( 'on' == $active ) { - echo esc_html( __( 'Yes', 'radio-station' ) ); - } else { - echo esc_html( __( 'No', 'radio-station' ) ); - } + // --- make sure we have a post ID for AJAX save --- + // 2.3.2: added AJAX track saving checks + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - } elseif ( 'description' == $column ) { + // 2.3.3: added double check for AJAX action match + if ( !isset( $_REQUEST['action'] ) || ( 'radio_station_playlist_save_tracks' != $_REQUEST['action'] ) ) { + return; + } - // 2.3.0: added show description indicator - global $wpdb; - $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; - $query = $wpdb->prepare( $query, $post_id ); - $content = $wpdb->get_var( $query ); - if ( !$content || ( trim( $content ) == '' ) ) { - echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + $error = false; + if ( !current_user_can( 'edit_playlists' ) ) { + $error = __( 'Failed. Use manual Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['playlist_tracks_nonce'] ) || !wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { + $error = __( 'Expired. Publish or Update instead.', 'radio-station' ); + } elseif ( !isset( $_POST['playlist_id'] ) || ( '' == $_POST['playlist_id'] ) ) { + $error = __( 'Failed. Playlist ID provided.', 'radio-station' ); } else { - echo esc_html( __( 'Yes', 'radio-station' ) ); + $post_id = absint( $_POST['playlist_id'] ); + $post = get_post( $post_id ); + if ( !$post ) { + $error = __( 'Failed. Invalid Playlist ID.', 'radio-station' ); + } } - } elseif ( 'shifts' == $column ) { + // --- send error message to parent window --- + if ( $error ) { + echo ""; - $active = get_post_meta( $post_id, 'show_active', true ); - if ( 'on' == $active ) { - $active = true; + exit; } + } - // 2.3.0: check using dates for reliability - $now = radio_station_get_now(); - $weekdays = radio_station_get_schedule_weekdays(); - $weekdates = radio_station_get_schedule_weekdates( $weekdays, $now ); - - $shifts = get_post_meta( $post_id, 'show_sched', true ); - if ( $shifts && is_array( $shifts ) && ( count( $shifts ) > 0 ) ) { - - $sorted_shifts = $dayless_shifts = array(); - foreach ( $shifts as $shift ) { - // 2.3.2: added check that shift day is not empty - if ( isset( $shift['day'] ) && ( '' != $shift['day'] ) ) { - // 2.3.2: fix to convert shift time to 24 hour format - $shift_time = $shift['start_hour'] . ":" . $shift['start_min'] . ' ' . $shift['start_meridian']; - $shift_time = radio_station_convert_shift_time( $shift_time ); - $shift_time = $weekdates[$shift['day']] . $shift_time; - $timestamp = radio_station_to_time( $shift_time ); - $sortedshifts[$timestamp] = $shift; - } else { - $dayless_shifts[] = $shift; - } - } - ksort( $sortedshifts ); - - foreach ( $sortedshifts as $shift ) { + // --- save playlist tracks --- + $playlist_changed = false; + if ( isset( $_POST['playlist'] ) ) { - // 2.3.0: highlight disabled shifts - $classes = array( 'show-shift' ); - $disabled = false; - $title = ''; - if ( isset( $shift['disabled'] ) && ( 'yes' == $shift['disabled'] ) ) { - $disabled = true; - $classes[] = 'disabled'; - $title = __( 'This Shift is Disabled.', 'radio-station' ); - } + // --- verify playlist nonce --- + // 2.3.2: fix OR condition to AND condition + if ( isset( $_POST['playlist_tracks_nonce'] ) && wp_verify_nonce( $_POST['playlist_tracks_nonce'], 'radio-station' ) ) { - // --- check and highlight conflicts --- - // 2.3.0: added shift conflict checking - $conflicts = radio_station_check_shift( $post_id, $shift ); - if ( $conflicts ) { - $classes[] = 'conflict'; - if ( $disabled ) { - $title = __( 'This Shift has Schedule Conflicts and is Disabled.', 'radio-station' ); - } else { - $title = __( 'This Shift has Schedule Conflicts.', 'radio-station' ); - } - } + $prev_playlist = get_post_meta( $post_id, 'playlist', true ); + $playlist = isset( $_POST['playlist'] ) ? $_POST['playlist'] : array(); - // 2.3.0: also highlight if the show is not active - if ( !$active ) { - if ( !in_array( 'disabled', $classes ) ) { - $classes[] = 'disabled'; - } - $title = __( 'This Show is not currently active.', 'radio-station' ); + // move songs that are still queued to the end of the list so that order is maintained + foreach ( $playlist as $i => $song ) { + // 2.3.2: move songs marked as new to the end instead of queued + // if ( 'queued' === $song['playlist_entry_status'] ) { + if ( $song['playlist_entry_new'] ) { + // 2.3.2: unset before adding to maintain (now ordered) track count + // 2.3.2: unset new flag from track record now it has been moved + unset( $playlist[$i] ); + unset( $song['playlist_entry_new'] ); + $playlist[] = $song; } - $classlist = implode( ' ', $classes ); + } + if ( $prev_playlist != $playlist ) { + update_post_meta( $post_id, 'playlist', $playlist ); + $playlist_changed = true; + } + } + } - echo "
      "; + // --- sanitize and save related show ID --- + // 2.3.0: check for changes in related show ID + if ( isset( $_POST['playlist_show_id'] ) ) { - // --- get shift start and end times --- - // 2.3.2: fix to convert to 24 hour time - $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; - $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - $start_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $start_time ); - $end_time = radio_station_to_time( $weekdates[$shift['day']] . ' ' . $end_time ); + // --- verify playlist related to show nonce --- + if ( isset( $_POST['playlist_show_nonce'] ) && wp_verify_nonce( $_POST['playlist_show_nonce'], 'radio-station' ) ) { - // --- make weekday filter selections bold --- - // 2.3.0: fix to bolding only if weekday isset - $bold = false; - if ( isset( $_GET['weekday'] ) ) { - $weekday = trim( $_GET['weekday'] ); - $nextday = radio_station_get_next_day( $weekday ); - // 2.3.0: handle shifts that go overnight for weekday filter - if ( ( $weekday == $shift['day'] ) || ( ( $shift['day'] == $nextday ) && ( $end_time < $start_time ) ) ) { - echo ""; - $bold = true; - } + $show_changed = false; + $prev_show = get_post_meta( $post_id, 'playlist_show_id', true ); + $show = $_POST['playlist_show_id']; + if ( empty( $show ) ) { + delete_post_meta( $post_id, 'playlist_show_id' ); + if ( $prev_show ) { + $show = $prev_show; + $show_changed = true; } - - echo esc_html( radio_station_translate_weekday( $shift['day'] ) ); - echo " " . esc_html( $start ) . " - " . esc_html( $end ); - if ( $bold ) { - echo ""; + } else { + $show = absint( $show ); + if ( ( $show > 0 ) && ( $show != $prev_show ) ) { + update_post_meta( $post_id, 'playlist_show_id', $show ); + $show_changed = true; } - echo "
      "; } - // --- dayless shifts --- - // 2.3.2: added separate display of dayless shifts - if ( count( $dayless_shifts ) > 0 ) { - foreach ( $dayless_shifts as $shift ) { - $title = __( 'This shift is disabled as no day is set.', 'radio-station' ); - echo "
      "; - $start = $shift['start_hour'] . ":" . $shift['start_min'] . $shift['start_meridian']; - $end = $shift['end_hour'] . ":" . $shift['end_min'] . $shift['end_meridian']; - echo esc_html( $start ) . " - " . esc_html( $end ); - echo "
      "; - } + // 2.3.0: maybe clear cached data to be safe + // 2.3.3: remove current show transient + // 2.3.4: add previous show transient + // 2.3.3.9: just call new clear cache function + if ( $show_changed ) { + radio_station_clear_cached_data( $show ); } } + } - } elseif ( 'hosts' == $column ) { - - $hosts = get_post_meta( $post_id, 'show_user_list', true ); - if ( $hosts && ( count( $hosts ) > 0 ) ) { - foreach ( $hosts as $host ) { - $user_info = get_userdata( $host ); - echo esc_html( $user_info->display_name ) . "
      "; - } - } + // --- AJAX saving --- + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( isset( $_POST['action'] ) && ( 'radio_station_playlist_save_tracks' == $_POST['action'] ) ) { - } elseif ( 'producers' == $column ) { + // --- display tracks saved message --- + // 2.3.3.9: fadeout tracks saved message + $playlist_tracks_nonce = wp_create_nonce( 'radio-station' ); + echo ""; - // 2.3.0: added column for Producers - $producers = get_post_meta( $post_id, 'show_producer_list', true ); - if ( $producers && ( count( $producers ) > 0 ) ) { - foreach ( $producers as $producer ) { - $user_info = get_userdata( $producer ); - echo esc_html( $user_info->display_name ) . "
      "; + // --- refresh track list table --- + // 2.3.3.9: added check if playlist changed + if ( $playlist_changed ) { + $entries = get_post_meta( $post_id, 'playlist', true ); + echo radio_station_playlist_track_table( $entries ); + echo ""; } - } - - } elseif ( 'show_image' == $column ) { - // 2.3.0: get show avatar (with fallback to thumbnail) - $image_url = radio_station_get_show_avatar_url( $post_id ); - if ( $image_url ) { - echo "
      " . esc_html( __(
      "; + exit; } + } +} +// ------------------------- +// Add Playlist List Columns +// ------------------------- +// 2.2.7: added data columns to playlist list display +add_filter( 'manage_edit-' . RADIO_STATION_PLAYLIST_SLUG . '_columns', 'radio_station_playlist_columns', 6 ); +function radio_station_playlist_columns( $columns ) { + if ( isset( $columns['thumbnail'] ) ) { + unset( $columns['thumbnail'] ); + } + if ( isset( $columns['post_thumb'] ) ) { + unset( $columns['post_thumb'] ); } + $date = $columns['date']; + unset( $columns['date'] ); + $comments = $columns['comments']; + unset( $columns['comments'] ); + $columns['show'] = esc_attr( __( 'Show', 'radio-station' ) ); + $columns['trackcount'] = esc_attr( __( 'Tracks', 'radio-station' ) ); + $columns['tracklist'] = esc_attr( __( 'Track List', 'radio-station' ) ); + $columns['comments'] = $comments; + $columns['date'] = $date; + + return $columns; } -// ----------------------- -// Show List Column Styles -// ----------------------- -// 2.2.7: added show column styles -add_action( 'admin_footer', 'radio_station_show_column_styles' ); -function radio_station_show_column_styles() { - $current_screen = get_current_screen(); - if ( 'edit-' . RADIO_STATION_SHOW_SLUG !== $current_screen->id ) { - return; +// ------------------------- +// Playlist List Column Data +// ------------------------- +// 2.2.7: added data columns for show list display +add_action( 'manage_' . RADIO_STATION_PLAYLIST_SLUG . '_posts_custom_column', 'radio_station_playlist_column_data', 5, 2 ); +function radio_station_playlist_column_data( $column, $post_id ) { + $tracks = get_post_meta( $post_id, 'playlist', true ); + if ( 'show' == $column ) { + $show_id = get_post_meta( $post_id, 'playlist_show_id', true ); + $post = get_post( $show_id ); + echo "" . esc_html( $post->post_title ) . ""; + } elseif ( 'trackcount' == $column ) { + echo count( $tracks ); + } elseif ( 'tracklist' == $column ) { + echo ''; + echo esc_html( __( 'Show/Hide Tracklist', 'radio-station' ) ) . "
      "; + echo ''; } - - echo ""; } -// ------------------------- -// Add Show Shift Day Filter -// ------------------------- -// 2.2.7: added show day selection filtering -add_action( 'restrict_manage_posts', 'radio_station_show_day_filter', 10, 2 ); -function radio_station_show_day_filter( $post_type, $which ) { - - if ( RADIO_STATION_SHOW_SLUG !== $post_type ) { +// --------------------------- +// Playlist List Column Styles +// --------------------------- +add_action( 'admin_footer', 'radio_station_playlist_column_styles' ); +function radio_station_playlist_column_styles() { + $currentscreen = get_current_screen(); + if ( 'edit-' . RADIO_STATION_PLAYLIST_SLUG !== $currentscreen->id ) { return; } - // -- maybe get specified day --- - $d = isset( $_GET['weekday'] ) ? $_GET['weekday'] : 0; - - // --- show day selector --- - $days = array( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ); + // --- playlist list styles --- + echo ""; - echo ''; - echo ''; + // --- enqueue script inline --- + // 2.3.0: enqueue instead of echo + wp_add_inline_script( 'radio-station-admin', $js ); } -// -------------------------- -// === Schedule Overrides === -// -------------------------- +// ------------- +// === Posts === +// ------------- -// ----------------------------- -// Add Schedule Override Metabox -// ----------------------------- -// --- Add schedule override box to override edit screens --- -add_action( 'add_meta_boxes', 'radio_station_add_schedule_override_metabox' ); -function radio_station_add_schedule_override_metabox() { - // 2.2.2: add high priority to show at top of edit screen - // 2.3.0: set position to top to be above editor box - // 2.3.0: update meta box ID for consistency - // 2.3.2: filter top metabox position - $position = apply_filters( 'radio_station_metabox_position', 'rstop', 'overrides' ); +// ------------------------- +// Add Related Shows Metabox +// ------------------------- +// (add metabox for show assignment on blog posts) +add_action( 'add_meta_boxes', 'radio_station_add_post_show_metabox' ); +function radio_station_add_post_show_metabox() { + + // 2.3.0: moved check for shows inside metabox + + // ---- add a filter for which post types to show metabox on --- + $post_types = array( 'post' ); + $post_types = apply_filters( 'radio_station_show_related_post_types', $post_types ); + + // --- add the metabox to post types --- + // 2.3.3.9: added high priority for better visibility + // 2.3.3.9: change ID from radio-station-post-show-metabox add_meta_box( - 'radio-station-override-metabox', - __( 'Override Schedule', 'radio-station' ), - 'radio_station_schedule_override_metabox', - RADIO_STATION_OVERRIDE_SLUG, - $position, + 'radio-station-related-show-metabox', + __( 'Related to Show', 'radio-station' ), + 'radio_station_post_show_metabox', + $post_types, + 'side', 'high' ); } -// ------------------------- -// Schedule Override Metabox -// ------------------------- -function radio_station_schedule_override_metabox() { +// --------------------- +// Related Shows Metabox +// --------------------- +function radio_station_post_show_metabox() { + // 2.3.3.6: store current post global global $post; + $stored_post = $post; + + // 2.3.3.9: filter meta key according to post type + $post_type = $post->post_type; + $metakey = apply_filters( 'radio_station_related_show_meta_key', 'post_showblog_id', $post_type ); - // 2.2.7: added meridiem translations - $am = radio_station_translate_meridiem( 'am' ); - $pm = radio_station_translate_meridiem( 'pm' ); + // --- add nonce field for verification --- + wp_nonce_field( 'radio-station', 'post_show_nonce' ); - // --- add nonce field for update verification --- - wp_nonce_field( 'radio-station', 'show_override_nonce' ); + // 2.3.3.9: allow for post assignment to draft Shows + $args = array( + 'numberposts' => - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => array( 'publish', 'draft' ), + ); + $shows = get_posts( $args ); - // 2.2.7: add explicit width to date picker field to ensure date is visible - // 2.3.0: convert template style output to straight php output - echo '
      '; + // --- get current selection --- + // 2.3.3.4: convert possible existing selection to array + $selected = get_post_meta( $post->ID, $metakey, true ); + if ( !$selected ) { + $selected = array(); + } elseif ( !is_array( $selected ) ) { + $selected = array( $selected ); + } - // --- get the saved meta as an array --- - $override = get_post_meta( $post->ID, 'show_override_sched', false ); - if ( $override ) { - $override = $override[0]; - } else { - // 2.2.8: fix undefined index warnings for new schedule overrides - $override = array( - 'date' => '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '' - ); + // 2.3.3.6: remove possible saved zero value + // 2.3.3.9: fix to duplicate use of selected variable + if ( count( $selected ) > 0 ) { + foreach ( $selected as $i => $value ) { + if ( 0 == $value ) { + unset( $selected[$i] ); + } + } } - echo '
        '; + // 2.3.3.9: move meta_inner ID to class + echo '
        '; - echo '
      • '; - echo esc_html( __( 'Date', 'radio-station' ) ) . ':'; - if ( !empty( $override['date'] ) ) { - $date = trim( $override['date'] ); - } else { - $date = ''; - } - echo ''; - echo '
      • '; + if ( count( $shows ) > 0 ) { - echo '
      • '; - echo esc_html( __( 'Start Time', 'radio-station' ) ) . ':'; - echo ''; + // --- select related show input --- + // 2.2.3.4: allow for multiple selections + // 2.3.3.9: use metakey for post type + echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - for ( $i = 0; $i < 60; $i ++ ) { - $min = $i; - if ( $i < 10 ) { - $min = '0' . $i; + // --- loop shows for selection options --- + // 2.3.3.4: check for multiple selections + foreach ( $shows as $show ) { + + // 2.3.3.6: check capability of user to edit each Show + // (override global post object temporarily to do this) + $post = $show; + echo ''; } - echo ''; + echo ''; + } else { + // --- no shows message --- + echo esc_html( __( 'No Shows to Select.', 'radio-station' ) ); } - echo ''; - echo ''; - echo '
      • '; + echo '
        '; - // 2.3.4: add common end minutes to top of options - echo '
      • '; - echo esc_html( __( 'End Time', 'radio-station' ) ) . ':'; - echo ''; - echo ''; - echo ''; - echo '
      • '; - echo '
      '; - echo '
      '; + // --- get the related show ID --- + $changed = false; + $current_shows = get_post_meta( $post_id, $metakey, true ); + $show_ids = $_POST[$metakey]; + + // 2.3.3.6: maybe add existing (uneditable) Show IDs + $new_show_ids = array(); + if ( $current_shows && is_array( $current_shows ) && ( count( $current_shows ) > 0 ) ) { + foreach ( $current_shows as $current_show ) { + if ( $current_show > 0 ) { + $post = get_post( $current_show ); + if ( $post && !current_user_can( 'edit_shows' ) ) { + $new_show_ids[] = $current_show; + } + } + } + } - // --- datepicker z-index style fix --- - // 2.3.0: added for display conflict with editor buttons - echo ""; + if ( !empty( $show_ids ) ) { + // --- sanitize to numeric before updating --- + // 2.3.3.4: maybe sanitize multiple array values + if ( !is_array( $show_ids ) ) { + $show_ids = array( $show_ids ); + } + foreach ( $show_ids as $i => $show_id ) { + $show_id = absint( trim( $show_id ) ); + // 2.3.3.6: check show ID value is above zero not -1 + if ( $show_id > 0 ) { + // 2.3.3.6: check edit Show capability before adding + $post = get_post( $show_id ); + if ( $post && current_user_can( 'edit_shows' ) && !in_array( $show_id, $new_show_ids ) ) { + $new_show_ids[] = $show_id; + } + } + } + } - // --- enqueue datepicker script and styles --- - // 2.3.0: enqueue for override post type only - radio_station_enqueue_datepicker(); + // --- delete or update Show IDs for post --- + // 2.3.3.6: check existing versus new show ID values + if ( 0 == count( $new_show_ids ) ) { + delete_post_meta( $post_id, $metakey ); + $changed = true; + } elseif ( $new_show_ids != $current_shows ) { + update_post_meta( $post_id, $metakey, $new_show_ids ); + $changed = true; + } - // --- enqueue inline script --- - // 2.3.0: enqeue instead of echoing - $js = "jQuery(document).ready(function() { - jQuery('#OverrideDate').datepicker({dateFormat : 'yy-mm-dd'}); - });"; - wp_add_inline_script( 'radio-station-admin', $js ); + // 2.3.0: clear cached data to be safe + // 2.3.3: remove current show transient + // 2.3.4: add previous show transient + // 2.3.3.9: just call clear cache data function + if ( $changed ) { + radio_station_clear_cached_data( $post_id ); + } + } + // 2.3.3.6 restore stored post object + $post = $stored_post; } -// ------------------------ -// Update Schedule Override -// ------------------------ -add_action( 'save_post', 'radio_station_override_save_data' ); -function radio_station_override_save_data( $post_id ) { +// ------------------------------------- +// Related Shows Quick Edit Select Input +// ------------------------------------- +// 2.3.3.4: added Related Show field to Post List Quick Edit +add_action( 'quick_edit_custom_box', 'radio_station_quick_edit_post', 10, 2 ); +function radio_station_quick_edit_post( $column_name, $post_type ) { - // --- verify if this is an auto save routine --- - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { - return; - } + global $post; + $stored_post = $post; - // --- verify this came from the our screen and with proper authorization --- - if ( !isset( $_POST['show_override_nonce'] ) || !wp_verify_nonce( $_POST['show_override_nonce'], 'radio-station' ) ) { + // 2.3.3.5: added fix for post type context + if ( $post_type != 'post' ) { return; } - // --- get the show override data --- - $sched = $_POST['show_sched']; - if ( !is_array( $sched ) ) { - return; - } + // --- get all shows --- + $args = array( + 'numberposts' => - 1, + 'offset' => 0, + 'orderby' => 'post_title', + 'order' => 'ASC', + 'post_type' => RADIO_STATION_SHOW_SLUG, + 'post_status' => 'publish', // ??? + ); + $shows = get_posts( $args ); - // --- get/set current schedule for merging --- - // 2.2.2: added to set default keys - $current_sched = get_post_meta( $post_id, 'show_override_sched', true ); - if ( !$current_sched || !is_array( $current_sched ) ) { - $current_sched = array( - 'date' => '', - 'start_hour' => '', - 'start_min' => '', - 'start_meridian' => '', - 'end_hour' => '', - 'end_min' => '', - 'end_meridian' => '', - ); - } + echo ''; - // --- sanitize values before saving --- - // 2.2.2: loop and validate schedule override values - $changed = false; - foreach ( $sched as $key => $value ) { - $isvalid = false; - - // --- validate according to key --- - if ( 'date' === $key ) { - // check posted date format (yyyy-mm-dd) with checkdate (month, date, year) - $parts = explode( '-', $value ); - if ( checkdate( $parts[1], $parts[2], $parts[0] ) ) { - $isvalid = true; - } - } elseif ( ( 'start_hour' === $key ) || ( 'end_hour' === $key ) ) { - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( ( absint( $value ) > 0 ) && ( absint( $value ) < 13 ) ) { - $isvalid = true; - } - } elseif ( ( 'start_min' === $key ) || ( 'end_min' === $key ) ) { - // 2.2.3: fix to validate 00 minute value - if ( empty( $value ) ) { - $isvalid = true; - } elseif ( absint( $value ) > - 1 && absint( $value ) < 61 ) { - $isvalid = true; - } - } elseif ( ( 'start_meridian' === $key ) || ( 'end_meridian' === $key ) ) { - $valid = array( '', 'am', 'pm' ); - // 2.2.8: remove strict in_array checking - if ( in_array( $value, $valid ) ) { - $isvalid = true; - } - } + // --- related shows post box styles --- + // 2.3.3.6: add style for pre-selected option + echo ""; - // --- if valid add to current schedule setting --- - if ( $isvalid && ( $value !== $current_sched[$key] ) ) { - $current_sched[$key] = $value; - $changed = true; + // 2.3.3.6: restore stored post object + $post = $stored_post; +} - // 2.2.7: sync separate meta key for override date - // (could be used to improve column sorting efficiency) - if ( 'date' == $key ) { - update_post_meta( $post_id, 'show_override_date', $value ); - } - } - } +// ---------------------------------- +// Add Related Shows Post List Column +// ---------------------------------- +// 2.3.3.4: added Related Show Column to Post List +add_filter( 'manage_edit-post_columns', 'radio_station_post_columns', 6 ); +function radio_station_post_columns( $columns ) { + $columns['show'] = esc_attr( __( 'Show(s)', 'radio-station' ) ); + return $columns; +} - // --- save schedule setting if changed --- - // 2.3.0: check if changed before saving - if ( $changed ) { - update_post_meta( $post_id, 'show_override_sched', $current_sched ); +// ----------------------------------- +// Related Shows Post List Column Data +// ----------------------------------- +// 2.3.3.4: added Related Show Column to Post List +add_action( 'manage_post_posts_custom_column', 'radio_station_post_column_data', 5, 2 ); +function radio_station_post_column_data( $column, $post_id ) { + if ( 'show' == $column ) { - // --- clear cached schedule data if changed --- - // 2.3.3: remove current show transient - // 2.3.4: add previous show transient - delete_transient( 'radio_station_current_schedule' ); - delete_transient( 'radio_station_next_show' ); - delete_transient( 'radio_station_previous_show' ); + // 2.3.3.6: store global post object while capability checking + global $post; + $stored_post = $post; - // 2.3.4: delete all prefixed transients (for times) - radio_station_delete_transients_with_prefix( 'radio_station_current_schedule' ); - radio_station_delete_transients_with_prefix( 'radio_station_next_show' ); - radio_station_delete_transients_with_prefix( 'radio_station_previous_show' ); + // --- get Shows linked to Post --- + $data = ''; + $show_ids = $disabled = array(); + $show_id = get_post_meta( $post_id, 'post_showblog_id', true ); + if ( $show_id ) { + // 2.3.3.6: add check to ignore possible saved zero value + if ( is_array( $show_id ) ) { + $show_ids = $show_id; + foreach ( $show_ids as $i => $show_id ) { + if ( 0 == $show_id ) { + unset( $show_ids[$i] ); + } + } + // 2.3.3.8: fix to implode show_ids not show_id + $data = implode( ',', $show_ids ); + } elseif ( $show_id > 0 ) { + $show_ids = array( $show_id ); + $data = $show_id; + } + } - // --- set last updated schedule time --- - // 2.3.2: added for data API use - update_option( 'radio_station_schedule_updated', time() ); + // --- display Shows linked to post --- + if ( count( $show_ids ) > 0 ) { + foreach ( $show_ids as $show_id ) { + $show = get_post( trim( $show_id ) ); + if ( $show ) { + // 2.3.3.6: only link to Shows user can edit + $post = $show; + if ( current_user_can( 'edit_shows' ) ) { + echo ''; + } else { + // 2.3.3.6: set disabled (uneditable) data + $disabled[] = $show_id; + } + echo esc_html( $show->post_title ) . '
      '; + if ( current_user_can( 'edit_shows' ) ) { + echo '
      '; + } + } + } + } + echo ''; + echo ''; - // --- maybe send directory ping --- - // 2.3.1: added directory update ping option - // 2.3.2: queue directory ping - radio_station_queue_directory_ping(); + // --- restore global post object --- + $post = $stored_post; } } -// ---------------------------------- -// Add Schedule Override List Columns -// ---------------------------------- -// 2.2.7: added data columns to override list display -add_filter( 'manage_edit-' . RADIO_STATION_OVERRIDE_SLUG . '_columns', 'radio_station_override_columns', 6 ); -function radio_station_override_columns( $columns ) { +// ------------------------------- +// Related Shows Quick Edit Script +// ------------------------------- +// 2.3.3.4: added Related Show Quick Edit value population script +// ref: https://codex.wordpress.org/Plugin_API/Action_Reference/quick_edit_custom_box +// 2.3.3.6: disable uneditable Show select options +add_action( 'admin_enqueue_scripts', 'radio_station_posts_quick_edit_script' ); +function radio_station_posts_quick_edit_script( $hook ) { - if ( isset( $columns['thumbnail'] ) ) { - unset( $columns['thumbnail'] ); - } - if ( isset( $columns['post_thumb'] ) ) { - unset( $columns['post_thumb'] ); + if ( 'edit.php' != $hook ) { + return; } - $date = $columns['date']; - unset( $columns['date'] ); - $columns['override_date'] = esc_attr( __( 'Date', 'radio-station' ) ); - $columns['start_time'] = esc_attr( __( 'Start Time', 'radio-station' ) ); - $columns['end_time'] = esc_attr( __( 'End Time', 'radio-station' ) ); - $columns['shows_affected'] = esc_attr( __( 'Affected Show(s) on Date', 'radio-station' ) ); - // 2.3.0: added description indicator column - $columns['description'] = esc_attr( __( 'About?', 'radio-station' ) ); - // 2.3.2: added missing translation text domain - $columns['override_image'] = esc_attr( __( 'Image', 'radio-station' ) ); - $columns['date'] = $date; + // 2.3.3.7: use jQuery instead of \$ for better compatibility + if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { + $js = "(function($) { + var \$wp_inline_edit = inlineEditPost.edit; + inlineEditPost.edit = function( id ) { + \$wp_inline_edit.apply(this, arguments); + var post_id = 0; var disabled_ids; + if (typeof(id) == 'object') {post_id = parseInt(this.getId(id));} + if (post_id > 0) { + var show_ids = jQuery('#post-'+post_id+' .column-show .show-ids').text(); + if (show_ids != '') { + if (show_ids.indexOf(',') > -1) {ids = show_ids.split(',');} + else {ids = new Array(); ids[0] = show_ids;} + for (i = 0; i < ids.length; i++) { + var thisshowid = ids[i]; + jQuery('#edit-'+post_id+' .select-show option').each(function() { + if (jQuery(this).val() == thisshowid) {jQuery(this).attr('selected','selected');} + }); + } + /* disable uneditable options */ + disabled = jQuery('#post-'+post_id+' .column-show .disabled-ids').text(); + if (disabled != '') { + if (disabled.indexOf(',') > -1) {disabled_ids = disabled.split(',');} + else {disabled_ids = new Array(); disabled_ids[0] = disabled;} + jQuery('#edit-'+post_id+' .select-show option').each(function() { + for (j = 0; j < disabled_ids.length; j++) { + if (jQuery(this).val() == disabled_ids[j]) { + jQuery(this).attr('disabled','disabled'); + if (jQuery(this).attr('selected') == 'selected') {jQuery(this).addClass('pre-selected');} + } + } + }); + } + } + } + }; + })(jQuery);"; - return $columns; + wp_add_inline_script( 'radio-station-admin', $js ); + } } -// ----------------------------- -// Schedule Override Column Data -// ----------------------------- -// 2.2.7: added data columns for override list display -add_action( 'manage_' . RADIO_STATION_OVERRIDE_SLUG . '_posts_custom_column', 'radio_station_override_column_data', 5, 2 ); -function radio_station_override_column_data( $column, $post_id ) { - - global $radio_station_show_shifts; - $override = get_post_meta( $post_id, 'show_override_sched', true ); - if ( 'override_date' == $column ) { +// -------------------------- +// Add Bulk Edit Posts Action +// -------------------------- +// 2.3.3.4: add action to Bulk Edit list +// ref: https://dream-encode.com/wordpress-custom-bulk-actions/ +add_filter( 'bulk_actions-edit-post', 'radio_station_show_posts_bulk_edit_action' ); +function radio_station_show_posts_bulk_edit_action( $bulk_actions ) { + $bulk_actions['related_show'] = __( 'Set Related Show(s)', 'radio-station' ); + return $bulk_actions; +} - // 2.3.2: no need to apply timezone conversions here - $datetime = strtotime( $override['date'] ); - $month = date( 'F', $datetime ); - $month = radio_station_translate_month( $month ); - $weekday = date( 'l', $datetime ); - $weekday = radio_station_translate_weekday( $weekday ); - echo esc_html( $weekday ) . ' ' . esc_html( date( 'j', $datetime ) ) . ' '; - echo esc_html( $month ) . ' ' . esc_html( date( 'Y', $datetime ) ); +// ---------------------- +// Bulk Edit Posts Script +// ---------------------- +// 2.3.3.4: add script for Bulk Edit action +add_action( 'admin_enqueue_scripts', 'radio_station_show_posts_bulk_edit_script' ); +function radio_station_show_posts_bulk_edit_script( $hook ) { - } elseif ( 'start_time' == $column ) { + if ( 'edit.php' != $hook ) { + return; + } - echo esc_html( $override['start_hour'] ) . ':' . esc_html( $override['start_min'] ) . ' ' . esc_html( $override['start_meridian'] ); + // 2.3.3.7: use jQuery instead of \$ for better compatibility + // 2.3.3.7: do not reclone the show field if it already exists + if ( !isset( $_GET['post_type'] ) || ( 'post' == $_GET['post_type'] ) ) { + $js = "jQuery(document).ready(function() { + jQuery('#bulk-action-selector-top, #bulk-action-selector-bottom').on('change', function(e) { + if (jQuery(this).val() == 'related_show') { + /* clone the Quick Edit fieldset to after bulk action selector */ + if (!jQuery(this).parent().find('.related-show-field').length) { + jQuery('.related-show-field').first().clone().insertAfter(jQuery(this)); + } + } else { + jQuery(this).find('.related-show-field').remove(); + } + }); + });"; - } elseif ( 'end_time' == $column ) { + wp_add_inline_script( 'radio-station-admin', $js ); + } +} - echo esc_html( $override['end_hour'] ) . ':' . esc_html( $override['end_min'] ) . ' ' . esc_html( $override['end_meridian'] ); +// ----------------------- +// Bulk Edit Posts Handler +// ----------------------- +// 2.3.3.4: add handler for bulk edit action +add_filter( 'handle_bulk_actions-edit-post', 'radio_station_posts_bulk_edit_handler', 10, 3 ); +function radio_station_posts_bulk_edit_handler( $redirect_to, $action, $post_ids ) { - } elseif ( 'shows_affected' == $column ) { + global $post; + $stored_post = $post; - // --- maybe get all show shifts --- - if ( isset( $radio_station_show_shifts ) ) { - $show_shifts = $radio_station_show_shifts; - } else { - global $wpdb; - $query = "SELECT posts.post_title, meta.post_id, meta.meta_value FROM " . $wpdb->prefix . "postmeta AS meta - JOIN " . $wpdb->prefix . "posts as posts ON posts.ID = meta.post_id - WHERE meta.meta_key = 'show_sched' AND posts.post_status = 'publish'"; - // 2.3.0: get results as an array - $show_shifts = $wpdb->get_results( $query, ARRAY_A ); - $radio_station_show_shifts = $show_shifts; - } - if ( !$show_shifts || ( count( $show_shifts ) == 0 ) ) { - return; - } + if ( 'related_show' !== $action ) { + return $redirect_to; + } elseif ( !isset($_REQUEST['post_showblog_id'] ) || ( '' == $_REQUEST['post_showblog_id'] ) ) { + return $redirect_to; + } - // --- get the override weekday --- - // 2.3.2: remove date time and get day from date directly - $weekday = date( 'l', strtotime( $override['date'] ) ); - - // --- get start and end override times --- - // 2.3.2: fix to convert to 24 hour format first - $start = $override['start_hour'] . ':' . $override['start_min'] . ' ' . $override['start_meridian']; - $end = $override['end_hour'] . ':' . $override['end_min'] . ' ' . $override['end_meridian']; - $start = radio_station_convert_shift_time( $start ); - $end = radio_station_convert_shift_time( $end ); - $override_start = radio_station_to_time( $override['date'] . ' ' . $start ); - $override_end = radio_station_to_time( $override['date'] . ' ' . $end ); - // (if the end time is less than start time, adjust end to next day) - if ( $override_end <= $override_start ) { - $override_end = $override_end + ( 24 * 60 * 60 ); - } + $show_ids = $_REQUEST['post_showblog_id']; - // --- loop show shifts --- - foreach ( $show_shifts as $show_shift ) { - $shifts = maybe_unserialize( $show_shift['meta_value'] ); - if ( !is_array( $shifts ) ) { - $shifts = array(); + // 2.3.3.6: check that user can edit specified Shows + $posted_show_ids = array(); + if ( count( $show_ids ) > 0 ) { + foreach ( $show_ids as $show_id ) { + // 2.3.3.6: added check to ignore zero values + if ( 0 != $show_id ) { + $post = get_post( $show_id ); + if ( current_user_can( 'edit_shows' ) ) { + $posted_show_ids[] = $show_id; + } } + } + } - foreach ( $shifts as $shift ) { - if ( isset( $shift['day'] ) && ( $shift['day'] == $weekday ) ) { - - // --- get start and end shift times --- - // 2.3.0: validate shift time to check if complete - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $time = radio_station_validate_shift( $shift ); - $start = $shift['start_hour'] . ':' . $shift['start_min'] . ' ' . $shift['start_meridian']; - $end = $shift['end_hour'] . ':' . $shift['end_min'] . ' ' . $shift['end_meridian']; - $start = radio_station_convert_shift_time( $start ); - $end = radio_station_convert_shift_time( $end ); - $shift_start = radio_station_to_time( $override['date'] . ' ' . $start ); - $shift_end = radio_station_to_time( $override['date'] . ' ' . $end ); - if ( ( $shift_start == $shift_end ) || ( $shift_start > $shift_end ) ) { - $shift_end = $shift_end + ( 24 * 60 * 60 ); - } - - if ( RADIO_STATION_DEBUG ) { - echo $weekday . ': ' . $start . ' to ' . $end . '
      ' . PHP_EOL; - echo $override['date'] . ': ' . $shift_start . ' to ' . $shift_end . '
      ' . PHP_EOL; - echo $override['date'] . ': ' . $override_start . ' to ' . $override_end . '
      ' . PHP_EOL; - echo '
      ' . PHP_EOL; - } - - // --- compare override time overlaps to get affected shows --- - // 2.3.2: fix to override overlap checking logic - if ( ( ( $override_start < $shift_start ) && ( $override_end > $shift_start ) ) - || ( ( $override_start < $shift_start ) && ( $override_end > $shift_end ) ) - || ( $override_start == $shift_start ) - || ( ( $override_start > $shift_start ) && ( $override_end < $shift_end ) ) - || ( ( $override_start > $shift_start ) && ( $override_start < $shift_end ) ) ) { - - // 2.3.0: adjust cell display to two line (to allow for long show titles) - // 2.3.2: deduplicate show check (if same show as last show displayed) - if ( !isset( $last_show ) || ( $last_show != $show_shift['post_id'] ) ) { - $active = get_post_meta( $show_shift['post_id'], 'show_active', true ); - if ( 'on' != $active ) { - echo "[" . esc_html( __( 'Inactive', 'radio-station' ) ) . "] "; - } - echo '' . $show_shift['post_title'] . "
      "; - } + // --- loop post IDs to update --- + $updated_post_ids = $failed_post_ids = array(); + foreach ( $post_ids as $post_id ) { + $post = get_post( $post_id ); + if ( $post ) { - if ( isset( $shift['disabled'] ) && $shift['disabled'] ) { - echo "[" . esc_html( __( 'Disabled', 'radio-station' ) ) . "] "; + // 2.3.3.6: keep existing (non-editable) related Shows for post + $existing_show_ids = array(); + $current_ids = get_post_meta( $post_id, 'post_showblog_id', true ); + if ( $current_ids && is_array( $current_ids ) && ( count( $current_ids ) > 0 ) ) { + foreach ( $current_ids as $i => $current_id ) { + // 2.3.3.6: added check to ignore possible zero values + if ( 0 != $current_id ) { + $post = get_post( $current_id ); + if ( !current_user_can( 'edit_shows' ) ) { + $existing_show_ids[] = $current_id; } - echo radio_station_translate_weekday( $shift['day'] ); - echo " " . esc_html( $shift['start_hour'] ) . ":" . esc_html( $shift['start_min'] ) . esc_html( $shift['start_meridian'] ); - echo " - " . esc_html( $shift['end_hour'] ) . ":" . esc_html( $shift['end_min'] ) . esc_html( $shift['end_meridian'] ); - echo "
      "; - - // 2.3.2: store last show displayed - $last_show = $show_shift['post_id']; } } } - } - - } elseif ( 'description' == $column ) { + $new_show_ids = array_merge( $posted_show_ids, $existing_show_ids ); - // 2.3.0: added override description indicator - global $wpdb; - $query = "SELECT post_content FROM " . $wpdb->prefix . "posts WHERE ID = %d"; - $query = $wpdb->prepare( $query, $post_id ); - $content = $wpdb->get_var( $query ); - if ( !$content || ( trim( $content ) == '' ) ) { - echo '' . esc_html( __( 'No', 'radio-station' ) ) . ''; + // --- update to new show IDs --- + update_post_meta( $post_id, 'post_showblog_id', $new_show_ids ); + $updated_post_ids[] = $post_id; } else { - echo esc_html( __( 'Yes', 'radio-station' ) ); + $failed_post_ids[] = $post_id; } + } - } elseif ( 'override_image' == $column ) { - $thumbnail_url = radio_station_get_show_avatar_url( $post_id ); - if ( $thumbnail_url ) { - echo "
      " . esc_attr( __(
      "; - } + if ( count( $updated_post_ids ) > 0 ) { + $redirect_to = add_query_arg( 'radio_station_related_show_updated', count( $updated_post_ids ), $redirect_to ); } -} - -// ----------------------------- -// Sortable Override Date Column -// ----------------------------- -// 2.2.7: added to allow override date column sorting -add_filter( 'manage_edit-override_sortable_columns', 'radio_station_override_sortable_columns' ); -function radio_station_override_sortable_columns( $columns ) { - $columns['override_date'] = 'show_override_date'; - return $columns; -} - -// ------------------------------- -// Schedule Override Column Styles -// ------------------------------- -add_action( 'admin_footer', 'radio_station_override_column_styles' ); -function radio_station_override_column_styles() { - $currentscreen = get_current_screen(); - if ( 'edit-' . RADIO_STATION_OVERRIDE_SLUG !== $currentscreen->id ) { - return; + if ( count( $failed_post_ids ) > 0 ) { + $redirect_to = add_query_arg( 'radio_station_related_show_failed', count( $failed_post_ids ), $redirect_to ); } - // 2.3.2: set override image column width to override image width - echo ""; -} - -// ---------------------------------- -// Add Schedule Override Month Filter -// ---------------------------------- -// 2.2.7: added month selection filtering -add_action( 'restrict_manage_posts', 'radio_station_override_date_filter', 10, 2 ); -function radio_station_override_date_filter( $post_type, $which ) { + // --- restore stored post --- + $post = $stored_post; - global $wp_locale; - if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { - return; - } + return $redirect_to; +} - // --- get all show override months / years --- - global $wpdb; - $overridequery = "SELECT ID FROM " . $wpdb->posts . " WHERE post_type = '" . RADIO_STATION_OVERRIDE_SLUG . "'"; - $results = $wpdb->get_results( $overridequery, ARRAY_A ); - $months = array(); - if ( $results && ( count( $results ) > 0 ) ) { - foreach ( $results as $result ) { - $post_id = $result['ID']; - $override = get_post_meta( $post_id, 'show_override_date', true ); - $datetime = radio_station_to_time( $override ); - $month = radio_station_get_time( 'm', $datetime ); - $year = radio_station_get_time( 'Y', $datetime ); - $months[$year . '-' . $month] = array( 'year' => $year, 'month' => $month ); - } - } else { - return; +// ---------------------- +// Bulk Edit Posts Notice +// ---------------------- +// 2.3.3.4: add notice for bulk edit result +add_action( 'admin_notices', 'radio_station_posts_bulk_edit_notice' ); +function radio_station_posts_bulk_edit_notice() { + $updated = $failed = false; + if ( isset( $_REQUEST['radio_station_related_show_updated'] ) ) { + $updated_ = intval( $_REQUEST['radio_station_related_show_updated'] ); + } + if ( isset( $_REQUEST['radio_station_related_show_failed'] ) ) { + $failed = intval( $_REQUEST['radio_station_related_show_failed'] ); } + if ( $updated || $failed ) { - // --- maybe get specified month --- - // TODO: maybe use get_query_var for month ? - $m = isset( $_GET['month'] ) ? (int) $_GET['month'] : 0; + echo '
      '; - // --- month override selector --- - echo ''; - echo ''; + echo '
      '; + } } -// ------------------------------- -// Add Schedule Past Future Filter -// ------------------------------- -// 2.3.3: added past future filter prototype code -add_action( 'restrict_manage_posts', 'radio_station_override_past_future_filter', 10, 2 ); -function radio_station_override_past_future_filter( $post_type, $which ) { - - if ( RADIO_STATION_OVERRIDE_SLUG !== $post_type ) { +// ----------------------------- +// Related Show Post List Styles +// ----------------------------- +// 2.3.3.4: added Related Show Post List styles +add_action( 'admin_footer', 'radio_station_posts_list_styles' ); +function radio_station_posts_list_styles() { + $currentscreen = get_current_screen(); + if ( 'edit-post' !== $currentscreen->id ) { return; } - // --- set past future selection / default --- - $pastfuture = isset( $_GET['pastfuture'] ) ? $_GET['pastfuture'] : ''; - $pastfuture = apply_filters( 'radio_station_overrides_past_future_default', $pastfuture ); + // --- post list styles --- + // 2.3.3.9: fix to column-show type (oclumn-show) + echo ""; +} - // --- past / future override selector --- - // 2.3.3.5: added option for today filtering - echo ''; - echo ''; -} +// ----------------------- +// === Extra Functions === +// ----------------------- -// ----------------------------------- -// === Post Type List Query Filter === -// ----------------------------------- +// --------------------------- +// Post Type List Query Filter +// --------------------------- // 2.2.7: added filter for custom column sorting add_action( 'pre_get_posts', 'radio_station_columns_query_filter' ); function radio_station_columns_query_filter( $query ) { @@ -4396,11 +5893,17 @@ function radio_station_columns_query_filter( $query ) { if ( $results && ( count( $results ) > 0 ) ) { foreach ( $results as $result ) { $post_id = $result['ID']; - $override = get_post_meta( $post_id, 'show_override_sched', true ); - if ( $override ) { - update_post_meta( $post_id, 'show_override_date', $override['date'] ); - } else { - delete_post_meta( $post_id, 'show_override_date' ); + $overrides = get_post_meta( $post_id, 'show_override_sched', true ); + + // 2.3.3.9: refresh and loop possible multiple overrides + delete_post_meta( $post_id, 'show_override_date' ); + if ( $overrides && is_array( $overrides ) ) { + if ( array_key_exists( 'date', $overrides ) ) { + $overrides = array( $overrides ); + } + foreach ( $overrides as $i => $override ) { + add_post_meta( $post_id, 'show_override_date', $override['date'] ); + } } } } @@ -4468,3 +5971,38 @@ function radio_station_columns_query_filter( $query ) { } } +// -------------------- +// Relogin AJAX Message +// -------------------- +// 2.3.2: added for show shifts and playlist tracks AJAX +// 2.3.3.9: added action for override times saving +// 2.3.3.9: added action for single shift saving +add_action( 'wp_ajax_nopriv_radio_station_show_save_shift', 'radio_station_relogin_message' ); +add_action( 'wp_ajax_nopriv_radio_station_show_save_shifts', 'radio_station_relogin_message' ); +add_action( 'wp_ajax_nopriv_radio_station_override_save', 'radio_station_relogin_message' ); +add_action( 'wp_ajax_nopriv_radio_station_playlist_save_tracks', 'radio_station_relogin_message' ); +function radio_station_relogin_message() { + + // 2.3.3.9: fix to playlist action name prefix + // 2.3.3.9: added support for override times + // 2.3.3.9: add check for single shift action + $save_shift_actions = array( 'radio_station_show_save_shift', 'radio_station_show_save_shifts' ); + if ( in_array( $_REQUEST['action'], $save_shift_actions ) ) { + $type = 'shift'; + } elseif ( 'radio_station_override_save' == $_REQUEST['action'] ) { + $type = 'override'; + } elseif ( 'radio_station_playlist_save_tracks' == $_REQUEST['action'] ) { + $type = 'track'; + } + + // --- send relogin message --- + $error = __( 'Failed. Please relogin and try again.', 'radio-station' ); + echo ""; + + exit; +} + diff --git a/includes/post-types.php b/includes/post-types.php index f0ced94..fea44af 100644 --- a/includes/post-types.php +++ b/includes/post-types.php @@ -10,8 +10,8 @@ // -- Show // -- Playlist // -- Override -// * DJ / Host -// * Producer +// -- DJ / Host +// -- Producer // - Set CPTs to Classic Editor // - Add Theme Thumbnail Support // - Add Admin Bar Add New Links @@ -20,6 +20,8 @@ // - Register Show Taxonomies // -- Genre Taxonomy // -- Language Taxonomy +// === Schedule Override Filters === +// - Add Override Template Filters // ------------------ @@ -64,7 +66,8 @@ function radio_station_create_post_types() { 'taxonomies' => array( RADIO_STATION_GENRES_SLUG, RADIO_STATION_LANGUAGES_SLUG ), 'hierarchical' => false, // 2.3.0: added custom field and revision support - 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'revisions' ), + // 2.3.3.9: added post excerpt support + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'comments', 'custom-fields', 'revisions' ), 'can_export' => true, // 2.3.0: added show archives support 'has_archive' => 'shows', @@ -158,7 +161,8 @@ function radio_station_create_post_types() { 'hierarchical' => false, // 2.3.0: added editor support for override description // 2.3.0: added custom field and revision support - 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields', 'revisions' ), + // 2.3.3.9: added post excerpt support + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), 'can_export' => true, 'has_archive' => false, 'rewrite' => array( @@ -204,7 +208,10 @@ function radio_station_create_post_types() { 'exclude_from_search' => false, 'public' => true, 'hierarchical' => false, - 'can_export' => false, + // 2.3.3.9: set can_export true + 'can_export' => true, + // 2.3.3.9: added all post type supports + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), 'has_archive' => 'hosts', 'rewrite' => array( 'slug' => 'host', @@ -213,10 +220,10 @@ function radio_station_create_post_types() { ), 'query_var' => true, 'capability_type' => 'host', - 'map_meta_cap' => false, + // 2.3.3.9: set map_meta_cap true + 'map_meta_cap' => true, ); $post_type = apply_filters( 'radio_station_post_type_host', $post_type ); - // TODO: change Host post type slug to rs-host register_post_type( RADIO_STATION_HOST_SLUG, $post_type ); // -------- @@ -250,7 +257,10 @@ function radio_station_create_post_types() { 'exclude_from_search' => false, 'public' => true, 'hierarchical' => false, - 'can_export' => false, + // 2.3.3.9: set can_export true + 'can_export' => true, + // 2.3.3.9: added all post type supports + 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'revisions' ), 'has_archive' => 'producers', 'rewrite' => array( 'slug' => 'producer', @@ -259,10 +269,10 @@ function radio_station_create_post_types() { ), 'query_var' => true, 'capability_type' => 'producer', - 'map_meta_cap' => false, + // 2.3.3.9: set map_meta_cap true + 'map_meta_cap' => true, ); $post_type = apply_filters( 'radio_station_post_type_producer', $post_type ); - // TODO: change Producer post type slug to rs-producer register_post_type( RADIO_STATION_PRODUCER_SLUG, $post_type ); // --- maybe trigger flush of rewrite rules --- @@ -512,3 +522,181 @@ function radio_station_register_show_taxonomies() { } + +// --------------------------------- +// === Schedule Override Filters === +// --------------------------------- +// (to apply linked show overrides in template single-show-content.php) +// note: post object overrides (content, excerpt) are in radio_station_override_linked_show_data + +// -------------------------------------- +// Add Schedule Override Template Filters +// -------------------------------------- +add_action( 'wp', 'radio_station_override_filters' ); +function radio_station_override_filters() { + if ( is_admin() || !is_singular() ) { + return; + } + global $post; + if ( is_object( $post ) && ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) ) { + add_filter( 'the_title', 'radio_station_override_show_title', 10, 2 ); + add_filter( 'radio_station_show_title', 'radio_station_override_show_title', 10, 2 ); + add_filter( 'radio_station_show_avatar', 'radio_station_override_show_avatar', 10, 2 ); + add_filter( 'radio_station_show_avatar_id', 'radio_station_override_show_avatar_id', 10, 2 ); + add_filter( 'radio_station_show_thumbnail', 'radio_station_override_show_thumbnail', 10, 2 ); + add_filter( 'get_post_metadata', 'radio_station_override_thumbnail_id', 11, 4 ); + // add_filter( 'radio_station_show_header', $header_id, $post_id ); + add_filter( 'radio_station_show_hosts', 'radio_station_override_show_hosts', 10, 2 ); + add_filter( 'radio_station_show_producers', 'radio_station_override_show_producers', 10, 2 ); + add_filter( 'radio_station_show_link', 'radio_station_override_show_link', 10, 2 ); + add_filter( 'radio_station_show_email', 'radio_station_override_show_email', 10, 2 ); + add_filter( 'radio_station_show_phone', 'radio_station_override_show_phone', 10, 2 ); + add_filter( 'radio_station_show_download', 'radio_station_override_show_download', 10, 2 ); + add_filter( 'radio_station_show_file', 'radio_station_override_show_file', 10, 2 ); + add_filter( 'radio_station_show_patreon', 'radio_station_override_show_patreon', 10, 2 ); + add_filter( 'radio_station_show_shifts', 'radio_station_override_show_shifts', 10, 2 ); + // add_filter( 'radio_station_show_rss', 'radio_station_override_show_rss', 10, 2 ); + // add_filter( 'radio_station_show_social_icons', 'radio_station_override_show_social_icons', 10, 2 ); + } +} + +// --- Show Title --- +function radio_station_override_show_title( $show_title, $post_id ) { + global $post; + if ( !is_object( $post ) || ( $post->ID != $post_id ) ) { + return $show_title; + } + $override = radio_station_get_show_override( $post_id, 'show_title' ); + if ( false !== $override ) { + return $override; + } + return $show_title; +} + +// --- Show Avatar --- +function radio_station_override_show_avatar( $show_avatar, $post_id ) { + if ( radio_station_doing_template() ) { + $override = radio_station_get_show_override( $post_id, 'show_avatar' ); + if ( false !== $override ) { + return $override; + } + } + return $show_avatar; +} + +// --- Show Avatar ID --- +function radio_station_override_show_avatar_id( $avatar_id, $post_id ) { + if ( radio_station_doing_template() ) { + $override = radio_station_get_show_override( $post_id, 'show_avatar' ); + if ( false !== $override ) { + return $override; + } + } + return $avatar_id; +} + +// --- Show Thumbnail (Featured Image) --- +function radio_station_override_show_thumbnail( $show_thumbnail, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_image' ); + if ( false !== $override ) { + return $override; + } + return $show_thumbnail; +} + +// --- Show Thumbnail ID --- +function radio_station_override_thumbnail_id( $id, $object_id, $meta_key, $single ) { + global $post; + if ( ( '_thumbnail_id' != $meta_key ) || !is_object( $post ) || ( $post->ID != $object_id ) ) { + return $id; + } + if ( RADIO_STATION_OVERRIDE_SLUG == $post->post_type ) { + $override = radio_station_get_show_override( $object_id, 'show_image' ); + if ( false !== $override ) { + return $override; + } + } + return $id; +} + +// --- Show Hosts --- +function radio_station_override_show_hosts( $hosts, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_user_list' ); + if ( false !== $override ) { + return $override; + } + return $hosts; +} + +// --- Show Producers --- +function radio_station_override_show_producers( $producers, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_producer_list' ); + if ( false !== $override ) { + return $override; + } + return $producers; +} + +// --- Show Website Link --- +function radio_station_override_show_link( $show_link, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_link' ); + if ( false !== $override ) { + return $override; + } + return $show_link; +} + +// --- Show Email --- +function radio_station_override_show_email( $show_email, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_email' ); + if ( false !== $override ) { + return $override; + } + return $show_email; +} + +// --- Show Phone --- +function radio_station_override_show_phone( $show_phone, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_phone' ); + if ( false !== $override ) { + return $override; + } + return $show_phone; +} + +// --- Show File --- +function radio_station_override_show_file( $show_file, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_file' ); + if ( false !== $override ) { + return $override; + } + return $show_file; +} + +// --- Disable Download --- +function radio_station_override_show_download( $show_download, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_download' ); + if ( false !== $override ) { + return $override; + } + return $show_download; +} + +// --- Show Patreon --- +function radio_station_override_show_patreon( $show_patreon, $post_id ) { + $override = radio_station_get_show_override( $post_id, 'show_patreon' ); + if ( false !== $override ) { + return $override; + } + return $show_patreon; +} + +// --- Show Shifts --- +function radio_station_override_show_shifts( $show_shifts, $post_id ) { + $linked_id = get_post_meta( $post_id, 'linked_show_id', true ); + if ( $linked_id ) { + $show_shifts = radio_station_get_show_schedule( $linked_id ); + } + return $show_shifts; +} + diff --git a/includes/shortcodes.php b/includes/shortcodes.php index fd1dd0b..7b38b52 100644 --- a/includes/shortcodes.php +++ b/includes/shortcodes.php @@ -15,7 +15,8 @@ // - Playlist Archive Shortcode // - Override Archive Shortcode // - Genre Archive Shortcode -// * Language Archive Shortcode +// - Language Archive Shortcode +// - Archive Pagination Javascript // === Show Related Shortcodes === // - Show List Shortcode Abstract // - Show Posts Archive Shortcode @@ -105,20 +106,21 @@ function radio_station_timezone_shortcode( $atts = array() ) { // --- radio timezone --- $output .= '
      '; $output .= esc_html( __( 'Radio Timezone', 'radio-station' ) ); - $output .= '
      : '; - $output .= '
      ' . esc_html( $timezone_display ) . '
      '; + $output .= ':
      '; + $output .= '
      ' . esc_html( $timezone_display ) . '

      '; // --- user timezone --- - $output .= ''; + // 2.3.3.9: change span elements to divs + $output .= '
      '; $output .= esc_html( __( 'Your Timezone', 'radio-station' ) ); - $output .= ': '; - $output .= ''; + $output .= ':
      '; + $output .= '
      '; // 2.3.2 allow for timezone selector test - $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); - if ( '' != $select ) { - $output .= $select; - } + // $select = apply_filters( 'radio_station_timezone_select', '', 'radio-station-timezone-' . $instance, $atts ); + // if ( '' != $select ) { + // $output .= $select; + // } $output .= '
      '; @@ -227,10 +229,10 @@ function radio_station_clock_shortcode( $atts = array() ) { // 2.3.2 allow for timezone selector test // $select = radio_station_timezone_select( 'radio-station-clock-' + $instance ); - $select = apply_filters( 'radio_station_clock_timezone_select', '', 'radio-station-clock-' . $instance, $atts ); - if ( '' != $select ) { - $clock .= $select; - } + // $select = apply_filters( 'radio_station_clock_timezone_select', '', 'radio-station-clock-' . $instance, $atts ); + // if ( '' != $select ) { + // $clock .= $select; + // } $clock .= '
      '; @@ -246,6 +248,7 @@ function radio_station_clock_shortcode( $atts = array() ) { return $clock; } + // -------------------------- // === Archive Shortcodes === // -------------------------- @@ -253,10 +256,9 @@ function radio_station_clock_shortcode( $atts = array() ) { // ------------------------------- // Archive List Shortcode Abstract // ------------------------------- +// (handles Shows, Overrides, Playlists etc.) function radio_station_archive_list_shortcode( $type, $atts ) { - // TODO: add pagination to Archive list shortcode - // --- merge defaults with passed attributes --- $time_format = radio_station_get_setting( 'clock_time_format' ); $defaults = array( @@ -273,18 +275,18 @@ function radio_station_archive_list_shortcode( $type, $atts ) { 'status' => 'publish', 'perpage' => - 1, 'offset' => 0, - // 'pagination' => 1, + 'pagination' => 1, // --- shows only --- 'with_shifts' => 1, // 'show_shifts' => 0, - // --- overrides only --- + // --- for overrides only --- 'show_dates' => 1, - // --- shows and overrides --- + // --- for shows and overrides --- // 'display_genres' => 0, // 'display_languages' => 0, 'show_avatars' => 1, 'thumbnails' => 0, - // --- playlists --- + // --- for playlists --- // 'track_count' => 0, ); @@ -298,11 +300,13 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $atts = shortcode_atts( $defaults, $atts, $type . '-archive' ); // --- get published shows --- + // 2.3.3.9: ignore offset and limit and reapply later $args = array( 'post_type' => $type, 'post_status' => $atts['status'], - 'numberposts' => $atts['perpage'], - 'offset' => $atts['offset'], + 'numberposts' => - 1, + // 'numberposts' => $atts['perpage'], + // 'offset' => $atts['offset'], 'orderby' => $atts['orderby'], 'order' => $atts['order'], ); @@ -325,6 +329,7 @@ function radio_station_archive_list_shortcode( $type, $atts ) { 'compare' => '=', ), ); + } else { // --- just active shows --- @@ -388,64 +393,84 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $args = apply_filters( 'radio_station_' . $type . '_archive_post_args', $args ); $archive_posts = get_posts( $args ); $archive_posts = apply_filters( 'radio_station_' . $type . '_archive_posts', $archive_posts ); - + // --- process playlist taxonomy query --- if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + // 2.3.3.9: added missing check for matching archive post results + if ( $archive_posts && is_array( $archive_posts ) && ( count( $archive_posts ) > 0 ) ) { - // --- check assigned show has a specified genre term --- - if ( !empty( $atts['genre'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { - if ( is_array( $atts['genre'] ) ) {$genres = $atts['genre'];} - else {$genres = explode( ',', $atts['genre'] );} - foreach ( $archive_posts as $i => $archive_post ) { - $found_genre = false; - $show_id = get_post_meta( $archive_post->ID, 'playlist_show_id', true ); - if ( $show_id ) { - $show_genres = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG ); - if ( $show_genres ) { - foreach ( $show_genres as $show_genre ) { - if ( in_array( $show_genre->term_id, $genres ) || in_array( $show_genre->slug, $genres) ) { - $found_genre = true; + // --- check assigned show has a specified genre term --- + if ( !empty( $atts['genre'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { + if ( is_array( $atts['genre'] ) ) {$genres = $atts['genre'];} + else {$genres = explode( ',', $atts['genre'] );} + foreach ( $archive_posts as $i => $archive_post ) { + $found_genre = false; + $show_id = get_post_meta( $archive_post->ID, 'playlist_show_id', true ); + if ( $show_id ) { + $show_genres = wp_get_post_terms( $show_id, RADIO_STATION_GENRES_SLUG ); + if ( $show_genres ) { + foreach ( $show_genres as $show_genre ) { + if ( in_array( $show_genre->term_id, $genres ) || in_array( $show_genre->slug, $genres) ) { + $found_genre = true; + } } } } - } - if ( !$found_genre ) { - unset( $archive_posts[$i] ); + if ( !$found_genre ) { + unset( $archive_posts[$i] ); + } } } - } - // --- check assigned show has a specified language term --- - if ( !empty( $atts['language'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { - if ( is_array( $atts['language'] ) ) {$languages = $atts['language'];} - else {$languages = explode( ',', $atts['language'] );} - foreach ( $archive_posts as $i => $archive_post ) { - $found_language = false; - $show_id = get_post_meta( $archive_post->ID, 'playlist_show_id', true ); - if ( $show_id ) { - $show_languages = wp_get_post_terms( $show_id, RADIO_STATION_LANGUAGES_SLUG ); - if ( $show_languages ) { - foreach ( $show_languages as $show_language ) { - if ( in_array( $show_language->term_id, $languages ) || in_array( $show_language->slug, $languages ) ) { - $found_language = true; + // --- check assigned show has a specified language term --- + if ( !empty( $atts['language'] ) && $archive_posts && ( count( $archive_posts ) > 0 ) ) { + if ( is_array( $atts['language'] ) ) {$languages = $atts['language'];} + else {$languages = explode( ',', $atts['language'] );} + foreach ( $archive_posts as $i => $archive_post ) { + $found_language = false; + $show_id = get_post_meta( $archive_post->ID, 'playlist_show_id', true ); + if ( $show_id ) { + $show_languages = wp_get_post_terms( $show_id, RADIO_STATION_LANGUAGES_SLUG ); + if ( $show_languages ) { + foreach ( $show_languages as $show_language ) { + if ( in_array( $show_language->term_id, $languages ) || in_array( $show_language->slug, $languages ) ) { + $found_language = true; + } } } } - } - if ( !$found_language ) { - unset( $archive_posts[$i] ); + if ( !$found_language ) { + unset( $archive_posts[$i] ); + } } } + } } // --- check override dates --- + // (overrides without a date set will not be displayed) if ( RADIO_STATION_OVERRIDE_SLUG == $type ) { if ( $archive_posts && is_array( $archive_posts ) && ( count( $archive_posts ) > 0 ) ) { foreach( $archive_posts as $i => $archive_post ) { - $datetime = get_post_meta( $archive_post->ID, 'show_override_sched', true ); - if ( $datetime || !is_array( $datetime ) || ( count( $datetime ) == 0 ) ) { + // 2.3.3.9: set singular to false to allow for multiple override times + $override_times = get_post_meta( $archive_post->ID, 'show_override_sched', true ); + if ( array_key_exists( 'date', $override_times ) ) { + $override_times = array( $override_times ); + } + if ( $override_times || !is_array( $override_times ) || ( count( $override_times ) == 0 ) ) { unset( $archive_posts[$i] ); + } else { + // 2.3.3.9: check if all override times are disabled + $enabled = count( $override_times ); + foreach ( $override_times as $override_time ) { + if ( isset( $override_time['disabled'] ) && ( 'yes' == $override_time['disabled'] ) ) { + $enabled--; + } + } + if ( 0 == count( $enabled ) ) { + unset( $archive_posts[$i] ); + } } } } @@ -464,26 +489,57 @@ function radio_station_archive_list_shortcode( $type, $atts ) { $end_data_format = apply_filters( 'radio_station_time_format_end', $end_data_format, $type . '-archive', $atts ); // --- check for results --- - $list = '
      '; if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { - if ( $atts['hide_empty'] ) { return ''; } + $post_count = 0; + } else { + + // --- count total archive posts --- + $post_count = count( $archive_posts ); + + // --- manually apply offset and perpage limit --- + // 2.3.3.9: added to enable pagination count + if ( $post_count > $atts['offset'] ) { + $offset_posts = array(); + foreach ( $archive_posts as $i => $archive_post ) { + if ( ( $i > $atts['offset'] ) && ( count( $offset_posts ) < $atts['perpage'] ) ) { + $offset_posts[] = $archive_post; + } + } + $archive_posts = $offset_posts; + } else { + $archive_posts = array(); + } + } + + // --- output list or no results message --- + $list = '
      '; + if ( !$archive_posts || !is_array( $archive_posts ) || ( count( $archive_posts ) == 0 ) ) { // --- no shows messages ---- if ( RADIO_STATION_SHOW_SLUG == $type ) { - if ( !empty( $atts['genre'] ) ) { - $list .= esc_html( __( 'No Shows in this Genre were found.', 'radio-station' ) ); + // 2.3.3.9: improve messages if genre / language specificed + if ( ( !empty( $atts['genre'] ) ) && ( !empty( $atts['genre'] ) ) ) { + $message = __( 'No Shows in the requested Genre and Language were found.', 'radio-station' ); + } elseif ( !empty( $atts['genre'] ) ) { + $message = __( 'No Shows in the requested Genre were found.', 'radio-station' ); + } elseif ( !empty( $atts['language'] ) ) { + $message = __( 'No Shows in the requested Language were found.', 'radio-station' ); } else { - $list .= esc_html( __( 'No Shows were found to display.', 'radio-station' ) ); + $message = __( 'No Shows were found to display.', 'radio-station' ); } } elseif ( RADIO_STATION_PLAYLIST_SLUG == $type ) { - $list .= esc_html( __( 'No Playlists were found to display.', 'radio-station' ) ); + $message = __( 'No Playlists were found to display.', 'radio-station' ); } elseif ( RADIO_STATION_OVERRIDE_SLUG == $type ) { - $list .= esc_html( __( 'No Overrides were found to display.', 'radio-station' ) ); + $message = __( 'No Overrides were found to display.', 'radio-station' ); } + // 2.3.3.9: filter message to allow for other possible types + $message = apply_filters( 'radio_station_archive_shortcode_no_records', $message, $type, $atts ); + $list .= esc_html( $message ); + } else { // --- filter excerpt length and more --- @@ -493,128 +549,187 @@ function radio_station_archive_list_shortcode( $type, $atts ) { // --- archive list --- $list .= '
        '; + // --- set info keys --- + // (note: meta is dates for overrides, shifts for shows, tracks for playlists etc.) + $infokeys = array( 'avatar', 'title', 'meta', 'genres', 'languages', 'description', 'custom' ); + $infokeys = apply_filters( 'radio_station_archive_shortcode_info_order', $infokeys, $type, $atts ); + + // --- loop post archive --- foreach ( $archive_posts as $archive_post ) { + $info = array(); + + // --- map archive data to variables --- + // 2.3.3.9: added to allow overriding by override linked show data + $post_id = $image_post_id = $archive_post->ID; + $title = $archive_post->post_title; + $permalink = get_permalink( $archive_post->ID ); + $post_content = $archive_post->post_content; + $post_excerpt = $archive_post->post_excerpt; + + // --- check linked Show for overrides --- + if ( RADIO_STATION_OVERRIDE_SLUG == $type ) { + $linked_show = get_post_meta( $archive_post->ID, 'linked_show_id', true ); + if ( $linked_show ) { + + // --- overridc particular fields with linked show data --- + $show_post = get_post( $linked_show ); + $show_fields = get_post_meta( $post_id, 'linked_show_fields', true ); + if ( !isset( $show_fields['show_title'] ) || !$show_fields['show_title'] ) { + $title = $show_post->post_title; + } + if ( !isset( $show_fields['show_content'] ) || !$show_fields['show_content'] ) { + $post_content = $show_post->post_content; + $post_excerpt = $show_post->post_excerpt; + } + if ( !isset( $show_fields['show_avatar'] ) || !$show_fields['show_avatar'] ) { + $image_post_id = get_post_meta( $linked_show, 'show_avatar', true ); + } + + } + } + $list .= '
      • '; // --- show avatar or thumbnail fallback --- - $list .= '
        '; + $info['avatar'] = '
        '; $show_avatar = false; if ( $atts['show_avatars'] && in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { $attr = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); - $show_avatar = radio_station_get_show_avatar( $archive_post->ID, 'thumbnail', $attr ); + $show_avatar = radio_station_get_show_avatar( $image_post_id, 'thumbnail', $attr ); } if ( $show_avatar ) { - $list .= $show_avatar; + $info['avatar'] .= $show_avatar; } elseif ( $atts['thumbnails'] ) { - if ( has_post_thumbnail( $archive_post->ID ) ) { + if ( has_post_thumbnail( $image_post_id ) ) { $atts = array( 'class' => esc_attr( $type ) . '-thumbnail-image' ); - $thumbnail = get_the_post_thumbnail( $archive_post->ID, 'thumbnail', $atts ); - $list .= $thumbnail; + $thumbnail = get_the_post_thumbnail( $image_post_id, 'thumbnail', $atts ); + $info['avatar'] .= $thumbnail; } } - $list .= '
        '; + $info['avatar'] .= '
        '; // --- title ---- - $list .= ''; + $info['title'] = '
        '; + $info['title'] .= ''; + $info['title'] .= esc_html( $title ) . ''; + $info['title'] .= '
        '; // --- display Override date(s) --- if ( ( RADIO_STATION_OVERRIDE_SLUG == $type ) && ( $atts['show_dates'] ) ) { - $datetime = get_post_meta( $archive_post->ID, 'show_override_sched', true ); + + // 2.3.3.9: set third attribute to false to allow for multiple override times + $override_times = get_post_meta( $archive_post->ID, 'show_override_sched', true ); + if ( array_key_exists( 'date', $override_times ) ) { + $override_times = array( $override_times ); + } // 2.3.1: fix to append not echo override date to archive list - $list .= "
        "; + $info['meta'] = '
        '; + + foreach ( $override_times as $override_time ) { + + // 2.3.3.9: added check if override time/date is disabled + if ( !isset( $override_time['disabled'] ) || ( 'yes' != $override_time['disabled'] ) ) { + + // --- convert date info --- + // 2.3.2: replace strtotime with to_time for timezones + // 2.3.2: fix to convert to 24 hour format first + // $day = radio_station_get_time( 'day', $date_time ); + // $display_day = radio_station_translate_weekday( $day ); + $date_time = radio_station_to_time( $override_time['date'] ); + $start = $override_time['start_hour'] . ':' . $override_time['start_min'] . ' ' . $override_time['start_meridian']; + $end = $override_time['end_hour'] . ':' . $override_time['end_min'] . ' ' . $override_time['end_meridian']; + $start_time = radio_station_convert_shift_time( $start ); + $end_time = radio_station_convert_shift_time( $end ); + $shift_start_time = radio_station_to_time( $override_time['day'] . ' ' . $start_time ); + $shift_end_time = radio_station_to_time( $override_time['day'] . ' ' . $end_time ); + if ( $shift_start_time > $shift_end_time ) { + $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); + } - // --- convert date info --- - // 2.3.2: replace strtotime with to_time for timezones - // 2.3.2: fix to convert to 24 hour format first - $date_time = radio_station_to_time( $datetime['date'] ); - // $day = radio_station_get_time( 'day', $date_time ); - // $display_day = radio_station_translate_weekday( $day ); - $start = $datetime['start_hour'] . ':' . $datetime['start_min'] . ' ' . $datetime['start_meridian']; - $end = $datetime['end_hour'] . ':' . $datetime['end_min'] . ' ' . $datetime['end_meridian']; - $start_time = radio_station_convert_shift_time( $start ); - $end_time = radio_station_convert_shift_time( $end ); - $shift_start_time = radio_station_to_time( $datetime['day'] . ' ' . $start_time ); - $shift_end_time = radio_station_to_time( $datetime['day'] . ' ' . $end_time ); - if ( $shift_start_time > $shift_end_time ) { - $shift_end_time = $shift_end_time + ( 24 * 60 * 60 ); - } + // --- convert shift times --- + // 2.3.2: use time formats with translations + $start = radio_station_get_time( $start_data_format, $shift_start_time ); + $end = radio_station_get_time( $end_data_format, $shift_end_time ); + $start = radio_station_translate_time( $start ); + $end = radio_station_translate_time( $end ); - // --- convert shift times --- - // 2.3.2: use time formats with translations - $start = radio_station_get_time( $start_data_format, $shift_start_time ); - $end = radio_station_get_time( $end_data_format, $shift_end_time ); - $start = radio_station_translate_time( $start ); - $end = radio_station_translate_time( $end ); + // 2.3.1: fix to append not echo override date to archive list + $info['meta'] .= '' . esc_html( $start ) . ''; + $info['meta'] .= ' - '; + $info['meta'] .= '' . esc_html( $end ) . ''; + $info['meta'] .= '
        '; - // 2.3.1: fix to append not echo override date to archive list - $list .= '' . esc_html( $start ) . ''; - $list .= ' - '; - $list .= '' . esc_html( $end ) . ''; - $list .= "
        "; + } + } + + $info['meta'] .= '
        '; } - // TODO: display Show shifts ? + // TODO: display Show shifts meta // if ( RADIO_STATION_SHOW_SLUG == $type ) { // if ( $atts['show_shifts'] ) { - // $shifts = radio_station_get_show_schedule( $archive_post->ID ); + // $shifts = radio_station_get_show_schedule( $post_id ); // } + // $info['meta'] = ''; // } + // TODO: playlist tracks / track count meta + // if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { + // $tracks = get_post_meta( $post_id, 'playlist', true ); + // $track_count = count( $tracks ); + // $info['meta'] = ''; + // } + + // 2.3.3.9: filter meta display for different post types + if ( !isset( $info['meta'] ) ) { + $info['meta'] = ''; + } + $info['meta'] = apply_filters ( 'radio_station_archive_shortcode_meta', $info['meta'], $post_id, $type, $atts ); + // TODO: display genre and language terms ? // if ( in_array( $type, array( RADIO_STATION_SHOW_SLUG, RADIO_STATION_OVERRIDE_SLUG ) ) ) { // if ( $atts['show_genres'] ) { - // $genres = wp_get_post_terms( $archive_post->ID, RADIO_STATION_GENRES_SLUG ); + // $genres = wp_get_post_terms( $post_id, RADIO_STATION_GENRES_SLUG ); + // $info['genres'] = ''; // } // if ( $atts['show_languages'] ) { - // $languages = wp_get_post_terms( $archive_post->ID, RADIO_STATION_LANGUAGES_SLUG ); + // $languages = wp_get_post_terms( $post_id, RADIO_STATION_LANGUAGES_SLUG ); + // $info['languages'] = ''; // } // } - // TODO: playlist tracks / track count ? - // if ( RADIO_STATION_PLAYLIST_SLUG == $type ) { - // $tracks = get_post_meta( $archive_post->ID, 'playlist', true ); - // $track_count = count( $tracks ); - // } - - // TODO: display episode / host / producer meta ? - // if ( defined( 'RADIO_STATION_PRO_EPISODE_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_episode_archive_meta', '' ); - // } - // if ( defined( 'RADIO_STATION_PRO_HOST_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_host_archive_meta', '' ); - // } - // if ( defined( 'RADIO_STATION_PRO_PRODUCER_SLUG' && ( RADIO_STATION_PRO_EPISODE_SLUG == $type ) { - // $list .= apply_filters( 'radio_station_producer_archive_meta', '' ); - // } - - // --- description --- - $post_content = $archive_post->post_content; - $post_id = $archive_post->ID; if ( 'none' == $atts['description'] ) { - $list .= ''; + $info['description'] = ''; } elseif ( 'full' == $atts['description'] ) { - $list .= '
        '; + $info['description'] = '
        '; $content = apply_filters( 'radio_station_' . $type . '_archive_content', $post_content, $post_id ); - $list .= $content; - $list .= '
        '; + $info['description'] .= $content; + $info['description'] .= '
        '; } else { - $list .= '
        '; - $permalink = get_permalink( $post_id ); - if ( !empty( $archive_post->post_excerpt ) ) { - $excerpt = $archive_post->post_excerpt; + $info['description'] = '
        '; + if ( !empty( $post_excerpt ) ) { + $excerpt = $post_excerpt; $excerpt .= ' ' . $more . ''; } else { $excerpt = radio_station_trim_excerpt( $post_content, $length, $more, $permalink ); } $excerpt = apply_filters( 'radio_station_' . $type . '_archive_excerpt', $excerpt, $post_id ); - $list .= $excerpt; - $list .= '
        '; + $info['description'] .= $excerpt; + $info['description'] .= '
        '; + } + + // 2.3.3.9: add filter for custom HTML info + $info['custom'] = apply_filters( 'radio_station_archive_shortcode_info_custom', '', $post_id, $type, $atts ); + + // 2.3.3.9: filter info and loop info keys to add to archive list + $info = apply_filters( 'radio_station_archive_shortcode_info', $info, $post_id, $type, $atts ); + foreach ( $infokeys as $infokey ) { + if ( isset( $info[$infokey] ) ) { + $list .= $info[$infokey]; + } } $list .= '
      • '; @@ -623,6 +738,16 @@ function radio_station_archive_list_shortcode( $type, $atts ) { } $list .= '
      '; + // --- add archive_pagination --- + if ( $atts['pagination'] && ( $archive_count > 0 ) ) { + if ( $archive_count > $atts['perpage'] ) { + $list .= radio_station_archive_pagination( $type, $atts, $post_count ); + } + } + + // --- enqueue pagination javascript --- + add_action( 'wp_footer', 'radio_station_archive_pagination_javascript' ); + // --- enqueue shortcode styles --- radio_station_enqueue_style( 'shortcodes' ); @@ -666,6 +791,7 @@ function radio_station_override_archive_list( $atts ) { add_shortcode( 'genres-archive', 'radio_station_genre_archive_list' ); function radio_station_genre_archive_list( $atts ) { + // 2.3.3.9: default show description display to on $defaults = array( // --- genre display options --- 'genres' => '', @@ -674,6 +800,7 @@ function radio_station_genre_archive_list( $atts ) { 'genre_images' => 1, 'image_width' => 150, 'hide_empty' => 1, + 'pagination' => 1, // --- query args --- 'status' => 'publish', 'perpage' => - 1, @@ -685,9 +812,7 @@ function radio_station_genre_archive_list( $atts ) { 'show_avatars' => 1, 'thumbnails' => 0, 'avatar_width' => 75, - 'show_desc' => 0, - // TODO: genre archive result pagination - // 'pagination' => 1, + 'show_desc' => 1, ); // --- handle possible pagination offset --- @@ -739,6 +864,7 @@ function radio_station_genre_archive_list( $atts ) { foreach ( $genres as $name => $genre ) { // --- get published shows --- + // TODO: also display Overrides in Genre archive list ? $args = array( 'post_type' => RADIO_STATION_SHOW_SLUG, 'numberposts' => $atts['perpage'], @@ -838,6 +964,11 @@ function radio_station_genre_archive_list( $atts ) { } else { + // --- filter excerpt length and more --- + // 2.3.3.9: added for show description excerpts + $length = apply_filters( 'radio_station_genre_archive_excerpt_length', false ); + $more = apply_filters( 'radio_station_genre_archive_excerpt_more', '[…]' ); + // --- show archive list --- $list .= '
        '; @@ -867,16 +998,29 @@ function radio_station_genre_archive_list( $atts ) { $list .= '
      '; // --- show title ---- + $permalink = get_permalink( $post->ID ); $list .= ''; // --- show excerpt --- - // TODO: show description - // if ( $atts['show_desc' ] ) { - // - // } + // 2.2.3.9: display show description + if ( $atts['show_desc' ] ) { + if ( !empty( $post->post_excerpt ) ) { + $excerpt = $post->post_excerpt; + $excerpt .= ' ' . $more . ''; + } else { + $excerpt = radio_station_trim_excerpt( $post->post_content, $length, $more, $permalink ); + } + $excerpt = apply_filters( 'radio_station_genre_archive_excerpt', $excerpt, $post->ID ); + + if ( '' != $excerpt ) { + $list .= '
      '; + $list .= $excerpt; + $list .= '
      '; + } + } $list .= '
    • '; } @@ -888,6 +1032,15 @@ function radio_station_genre_archive_list( $atts ) { $list .= '
  • ', '
    ' . $upgrade_notice_html, $plugin_update_row ); + } + + echo $plugin_update_row; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + * + * @param array $prepared_themes + * + * @return array + */ + function change_theme_update_info_html( $prepared_themes ) { + $theme_basename = $this->_fs->get_plugin_basename(); + + if ( ! isset( $prepared_themes[ $theme_basename ] ) ) { + return $prepared_themes; + } + + $themes_update = get_site_transient( 'update_themes' ); + if ( ! isset( $themes_update->response[ $theme_basename ] ) || + empty( $themes_update->response[ $theme_basename ]['package'] ) + ) { + return $prepared_themes; + } + + $prepared_themes[ $theme_basename ]['update'] = preg_replace( + '/(\)(.+)(\)/is', + '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) . + '$4', + $prepared_themes[ $theme_basename ]['update'] + ); + + // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page. + $prepared_themes[ $theme_basename ]['hasPackage'] = false; + + return $prepared_themes; + } + + /** + * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip. + * During development mode we want to be able updating plugin versions via our localhost repository. This + * filter white-list all domains including "api.freemius". + * + * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/ + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param bool $allow + * @param string $host + * @param string $url + * + * @return bool + */ + function http_request_host_is_external_filter( $allow, $host, $url ) { + return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow; + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update api just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param object $transient_data Update array build by WordPress. + * + * @return object Modified update array with custom plugin data. + */ + function pre_set_site_transient_update_plugins_filter( $transient_data ) { + $this->_logger->entrance(); + + /** + * "plugins" or "themes". + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + $module_type = $this->_fs->get_module_type() . 's'; + + /** + * Ensure that we don't mix plugins update info with themes update info. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) { + return $transient_data; + } + + if ( empty( $transient_data ) || + defined( 'WP_FS__UNINSTALL_MODE' ) + ) { + return $transient_data; + } + + global $wp_current_filter; + + $current_plugin_version = $this->_fs->get_plugin_version(); + + if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) { + if ( + is_null( $this->_update_details ) || + ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version ) + ) { + /** + * After an update, clear the stored update details and reparse the plugin's main file in order to get + * the updated version's information and prevent the previous update information from showing up on the + * updates page. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + $this->_update_details = null; + $current_plugin_version = $this->_fs->get_plugin_version( true ); + } + } + + if ( ! isset( $this->_update_details ) ) { + // Get plugin's newest update. + $new_version = $this->_fs->get_update( + false, + fs_request_get_bool( 'force-check' ), + WP_FS__TIME_24_HOURS_IN_SEC / 24, + $current_plugin_version + ); + + $this->_update_details = false; + + if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) { + $this->_logger->log( 'Found newer plugin version ' . $new_version->version ); + + /** + * Cache plugin details locally since set_site_transient( 'update_plugins' ) + * called multiple times and the non wp.org plugins are filtered after the + * call to .org. + * + * @since 1.1.8.1 + */ + $this->_update_details = $this->get_update_details( $new_version ); + } + } + + // Alias. + $basename = $this->_fs->premium_plugin_basename(); + + if ( is_object( $this->_update_details ) ) { + if ( isset( $transient_data->no_update ) ) { + unset( $transient_data->no_update[ $basename ] ); + } + + if ( ! isset( $transient_data->response ) ) { + $transient_data->response = array(); + } + + // Add plugin to transient data. + $transient_data->response[ $basename ] = $this->_fs->is_plugin() ? + $this->_update_details : + (array) $this->_update_details; + } else { + if ( isset( $transient_data->response ) ) { + /** + * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + unset( $transient_data->response[ $basename ] ); + } + + if ( ! isset( $transient_data->no_update ) ) { + $transient_data->no_update = array(); + } + + /** + * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI. + * + * @since 2.4.1 + * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/ + */ + $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ? + (object) array( + 'id' => $basename, + 'slug' => $this->_fs->get_slug(), + 'plugin' => $basename, + 'new_version' => $this->_fs->get_plugin_version(), + 'url' => '', + 'package' => '', + 'icons' => array(), + 'banners' => array(), + 'banners_rtl' => array(), + 'tested' => '', + 'requires_php' => '', + 'compatibility' => new stdClass(), + ) : + array( + 'theme' => $basename, + 'new_version' => $this->_fs->get_plugin_version(), + 'url' => '', + 'package' => '', + 'requires' => '', + 'requires_php' => '', + ); + } + + $slug = $this->_fs->get_slug(); + + if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) { + if ( ! isset( $this->_translation_updates ) ) { + $this->_translation_updates = array(); + + if ( current_user_can( 'update_languages' ) ) { + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug ); + if ( ! empty( $translation_updates ) ) { + $this->_translation_updates = $translation_updates; + } + } + } + + if ( ! empty( $this->_translation_updates ) ) { + $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ? + $transient_data->translations : + array(); + + $current_plugin_translation_updates_map = array(); + foreach ( $all_translation_updates as $key => $translation_update ) { + if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) { + $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update; + unset( $all_translation_updates[ $key ] ); + } + } + + foreach ( $this->_translation_updates as $translation_update ) { + $lang = $translation_update['language']; + if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) || + version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' ) + ) { + $current_plugin_translation_updates_map[ $lang ] = $translation_update; + } + } + + $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) ); + } + } + + return $transient_data; + } + + /** + * Get module's required data for the updates mechanism. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_Tag $new_version + * + * @return object + */ + function get_update_details( FS_Plugin_Tag $new_version ) { + $update = new stdClass(); + $update->slug = $this->_fs->get_slug(); + $update->new_version = $new_version->version; + $update->url = WP_FS__ADDRESS; + $update->package = $new_version->url; + $update->tested = $new_version->tested_up_to_version; + $update->requires = $new_version->requires_platform_version; + + $icon = $this->_fs->get_local_icon_url(); + + if ( ! empty( $icon ) ) { + $update->icons = array( +// '1x' => $icon, +// '2x' => $icon, + 'default' => $icon, + ); + } + + if ( $this->_fs->is_premium() ) { + $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false ); + + if ( + isset( $latest_tag->readme ) && + isset( $latest_tag->readme->upgrade_notice ) && + ! empty( $latest_tag->readme->upgrade_notice ) + ) { + $update->upgrade_notice = $latest_tag->readme->upgrade_notice; + } + } + + $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename(); + + return $update; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param FS_Plugin_Tag $new_version + * + * @return bool + */ + private function is_new_version_premium( FS_Plugin_Tag $new_version ) { + $query_str = parse_url( $new_version->url, PHP_URL_QUERY ); + if ( empty( $query_str ) ) { + return false; + } + + parse_str( $query_str, $params ); + + return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] ); + } + + /** + * Update the updates transient with the module's update information. + * + * This method is required for multisite environment. + * If a module is site activated (not network) and not on the main site, + * the module will NOT be executed on the network level, therefore, the + * custom updates logic will not be executed as well, so unless we force + * the injection of the update into the updates transient, premium updates + * will not work. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param \FS_Plugin_Tag $new_version + */ + function set_update_data( FS_Plugin_Tag $new_version ) { + $this->_logger->entrance(); + + if ( ! $this->is_new_version_premium( $new_version ) ) { + return; + } + + $transient_key = "update_{$this->_fs->get_module_type()}s"; + + $transient_data = get_site_transient( $transient_key ); + + $transient_data = is_object( $transient_data ) ? + $transient_data : + new stdClass(); + + // Alias. + $basename = $this->_fs->get_plugin_basename(); + $is_plugin = $this->_fs->is_plugin(); + + if ( ! isset( $transient_data->response ) || + ! is_array( $transient_data->response ) + ) { + $transient_data->response = array(); + } else if ( ! empty( $transient_data->response[ $basename ] ) ) { + $version = $is_plugin ? + ( ! empty( $transient_data->response[ $basename ]->new_version ) ? + $transient_data->response[ $basename ]->new_version : + null + ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ? + $transient_data->response[ $basename ]['new_version'] : + null + ); + + if ( $version == $new_version->version ) { + // The update data is already set. + return; + } + } + + // Remove the added filters. + $this->remove_transient_filters(); + + $this->_update_details = $this->get_update_details( $new_version ); + + // Set update data in transient. + $transient_data->response[ $basename ] = $is_plugin ? + $this->_update_details : + (array) $this->_update_details; + + if ( ! isset( $transient_data->checked ) || + ! is_array( $transient_data->checked ) + ) { + $transient_data->checked = array(); + } + + // Flag the module as if it was already checked. + $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version(); + $transient_data->last_checked = time(); + + set_site_transient( $transient_key, $transient_data ); + + $this->add_transient_filters(); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.0.2 + */ + function delete_update_data() { + $this->_logger->entrance(); + + $transient_key = "update_{$this->_fs->get_module_type()}s"; + + $transient_data = get_site_transient( $transient_key ); + + // Alias + $basename = $this->_fs->get_plugin_basename(); + + if ( ! is_object( $transient_data ) || + ! isset( $transient_data->response ) || + ! is_array( $transient_data->response ) || + empty( $transient_data->response[ $basename ] ) + ) { + return; + } + + unset( $transient_data->response[ $basename ] ); + + // Remove the added filters. + $this->remove_transient_filters(); + + set_site_transient( $transient_key, $transient_data ); + + $this->add_transient_filters(); + } + + /** + * Try to fetch plugin's info from .org repository. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @param string $action + * @param object $args + * + * @return bool|mixed + */ + static function _fetch_plugin_info_from_repository( $action, $args ) { + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/'; + if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { + $url = set_url_scheme( $url, 'https' ); + } + + $args = array( + 'timeout' => 15, + 'body' => array( + 'action' => $action, + 'request' => serialize( $args ) + ) + ); + + $request = wp_remote_post( $url, $args ); + + if ( is_wp_error( $request ) ) { + return false; + } + + $res = maybe_unserialize( wp_remote_retrieve_body( $request ) ); + + if ( ! is_object( $res ) && ! is_array( $res ) ) { + return false; + } + + return $res; + } + + /** + * Fetches module translation updates from wordpress.org. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.2 + * + * @param string $module_type + * @param string $slug + * + * @return array|null + */ + private function fetch_wp_org_module_translation_updates( $module_type, $slug ) { + $plugin_data = $this->_fs->get_plugin_data(); + + $locales = array_values( get_available_languages() ); + $locales = apply_filters( "{$module_type}_update_check_locales", $locales ); + $locales = array_unique( $locales ); + + $plugin_basename = $this->_fs->get_plugin_basename(); + if ( 'themes' === $module_type ) { + $plugin_basename = $slug; + } + + global $wp_version; + + $request_args = array( + 'timeout' => 15, + 'body' => array( + "{$module_type}" => json_encode( + array( + "{$module_type}" => array( + $plugin_basename => array( + 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ), + 'Author' => $plugin_data['Author'], + ) + ) + ) + ), + 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ), + 'locale' => json_encode( $locales ) + ), + 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) ) + ); + + $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/"; + if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { + $url = set_url_scheme( $url, 'https' ); + } + + $raw_response = Freemius::safe_remote_post( + $url, + $request_args, + WP_FS__TIME_24_HOURS_IN_SEC, + WP_FS__TIME_12_HOURS_IN_SEC, + false + ); + + if ( is_wp_error( $raw_response ) ) { + return null; + } + + $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); + + if ( ! is_array( $response ) ) { + return null; + } + + if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) { + return null; + } + + return $response['translations']; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.2 + * + * @param string $module_type + * @param string $slug + * + * @return array + */ + private function get_installed_translations( $module_type, $slug ) { + if ( function_exists( 'wp_get_installed_translations' ) ) { + return wp_get_installed_translations( $module_type ); + } + + $dir = "/{$module_type}"; + + if ( ! is_dir( WP_LANG_DIR . $dir ) ) + return array(); + + $files = scandir( WP_LANG_DIR . $dir ); + if ( ! $files ) + return array(); + + $language_data = array(); + + foreach ( $files as $file ) { + if ( 0 !== strpos( $file, $slug ) ) { + continue; + } + + if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) { + continue; + } + + if ( substr( $file, -3 ) !== '.po' ) { + continue; + } + + if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { + continue; + } + + if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { + continue; + } + + list( , $textdomain, $language ) = $match; + + if ( '' === $textdomain ) { + $textdomain = 'default'; + } + + $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" ); + } + + return $language_data; + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @uses FS_Api + * + * @param object $data + * @param string $action + * @param mixed $args + * + * @return object + */ + function plugins_api_filter( $data, $action = '', $args = null ) { + $this->_logger->entrance(); + + if ( ( 'plugin_information' !== $action ) || + ! isset( $args->slug ) + ) { + return $data; + } + + $addon = false; + $is_addon = false; + $addon_version = false; + + if ( $this->_fs->get_slug() !== $args->slug ) { + $addon = $this->_fs->get_addon_by_slug( $args->slug ); + + if ( ! is_object( $addon ) ) { + return $data; + } + + if ( $this->_fs->is_addon_activated( $addon->id ) ) { + $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version(); + } else if ( $this->_fs->is_addon_installed( $addon->id ) ) { + $addon_plugin_data = get_plugin_data( + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ), + false, + false + ); + + if ( ! empty( $addon_plugin_data ) ) { + $addon_version = $addon_plugin_data['Version']; + } + } + + $is_addon = true; + } + + $plugin_in_repo = false; + if ( ! $is_addon ) { + // Try to fetch info from .org repository. + $data = self::_fetch_plugin_info_from_repository( $action, $args ); + + $plugin_in_repo = ( false !== $data ); + } + + if ( ! $plugin_in_repo ) { + $data = $args; + + // Fetch as much as possible info from local files. + $plugin_local_data = $this->_fs->get_plugin_data(); + $data->name = $plugin_local_data['Name']; + $data->author = $plugin_local_data['Author']; + $data->sections = array( + 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.', + ); + + // @todo Store extra plugin info on Freemius or parse readme.txt markup. + /*$info = $this->_fs->get_api_site_scope()->call('/information.json'); + +if ( !isset($info->error) ) { + $data = $info; +}*/ + } + + $plugin_version = $is_addon ? + $addon_version : + $this->_fs->get_plugin_version(); + + // Get plugin's newest update. + $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version ); + + if ( ! is_object( $new_version ) || empty( $new_version->version ) ) { + $data->version = $plugin_version; + } else { + if ( $is_addon ) { + $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); + $data->slug = $addon->slug; + $data->url = WP_FS__ADDRESS; + $data->package = $new_version->url; + } + + if ( ! $plugin_in_repo ) { + $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created; + $data->requires = $new_version->requires_platform_version; + $data->tested = $new_version->tested_up_to_version; + } + + $data->version = $new_version->version; + $data->download_link = $new_version->url; + + if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) { + $new_version_readme_data = $new_version->readme; + if ( isset( $new_version_readme_data->sections ) ) { + $new_version_readme_data->sections = (array) $new_version_readme_data->sections; + } else { + $new_version_readme_data->sections = array(); + } + + if ( isset( $data->sections ) ) { + if ( isset( $data->sections['screenshots'] ) ) { + $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots']; + } + + if ( isset( $data->sections['reviews'] ) ) { + $new_version_readme_data->sections['reviews'] = $data->sections['reviews']; + } + } + + if ( isset( $new_version_readme_data->banners ) ) { + $new_version_readme_data->banners = (array) $new_version_readme_data->banners; + } else if ( isset( $data->banners ) ) { + $new_version_readme_data->banners = $data->banners; + } + + $wp_org_sections = array( + 'author', + 'author_profile', + 'rating', + 'ratings', + 'num_ratings', + 'support_threads', + 'support_threads_resolved', + 'active_installs', + 'added', + 'homepage' + ); + + foreach ( $wp_org_sections as $wp_org_section ) { + if ( isset( $data->{$wp_org_section} ) ) { + $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section}; + } + } + + $data = $new_version_readme_data; + } + } + + return $data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param number|bool $addon_id + * @param bool|string $newer_than Since 2.2.1 + * @param bool|string $fetch_readme Since 2.2.1 + * + * @return object + */ + private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) { + return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme ); + } + + /** + * Checks if a given basename has a matching folder name + * with the current context plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + private function is_correct_folder_name() { + return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) ); + } + + /** + * This is a special after upgrade handler for migrating modules + * that didn't use the '-premium' suffix folder structure before + * the migration. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $response Install response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * @param array $result Installation result data. + * + * @return bool + */ + function _maybe_update_folder_name( $response, $hook_extra, $result ) { + $basename = $this->_fs->get_plugin_basename(); + + if ( true !== $response || + empty( $hook_extra ) || + empty( $hook_extra['plugin'] ) || + $basename !== $hook_extra['plugin'] + ) { + return $response; + } + + $active_plugins_basenames = get_option( 'active_plugins' ); + + foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) { + if ( $basename === $active_plugin_basename ) { + // Get filename including extension. + $filename = basename( $basename ); + + $new_basename = plugin_basename( + trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) . + $filename + ); + + // Verify that the expected correct path exists. + if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) { + // Override active plugin name. + $active_plugins_basenames[ $key ] = $new_basename; + update_option( 'active_plugins', $active_plugins_basenames ); + } + + break; + } + } + + return $response; + } + + #---------------------------------------------------------------------------------- + #region Auto Activation + #---------------------------------------------------------------------------------- + + /** + * Installs and active a plugin when explicitly requested that from a 3rd party service. + * + * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin. + * + * @link http://tgmpluginactivation.com/ + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ + * + * @uses WP_Filesystem + * @uses WP_Error + * @uses WP_Upgrader + * @uses Plugin_Upgrader + * @uses Plugin_Installer_Skin + * @uses Plugin_Upgrader_Skin + * + * @param number|bool $plugin_id + * + * @return array + */ + function install_and_activate_plugin( $plugin_id = false ) { + if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) { + // Invalid plugin ID. + return array( + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ); + } + + $is_addon = false; + if ( FS_Plugin::is_valid_id( $plugin_id ) && + $plugin_id != $this->_fs->get_id() + ) { + $addon = $this->_fs->get_addon( $plugin_id ); + + if ( ! is_object( $addon ) ) { + // Invalid add-on ID. + return array( + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), + 'code' => 'invalid_module_id', + ); + } + + $slug = $addon->slug; + $premium_slug = $addon->premium_slug; + $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); + + $is_addon = true; + } else { + $slug = $this->_fs->get_slug(); + $premium_slug = $this->_fs->get_premium_slug(); + $title = $this->_fs->get_plugin_title() . + ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' ); + } + + if ( $this->is_premium_plugin_active( $plugin_id ) ) { + // Premium version already activated. + return array( + 'message' => $is_addon ? + $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) : + $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), + 'code' => 'premium_installed', + ); + } + + $latest_version = $this->get_latest_download_details( $plugin_id, false, false ); + $target_folder = $premium_slug; + + // Prep variables for Plugin_Installer_Skin class. + $extra = array(); + $extra['slug'] = $target_folder; + $source = $latest_version->url; + $api = null; + + $install_url = add_query_arg( + array( + 'action' => 'install-plugin', + 'plugin' => urlencode( $slug ), + ), + 'update.php' + ); + + if ( ! class_exists( 'Plugin_Upgrader', false ) ) { + // Include required resources for the installation. + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + } + + $skin_args = array( + 'type' => 'web', + 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ), + 'url' => esc_url_raw( $install_url ), + 'nonce' => 'install-plugin_' . $slug, + 'plugin' => '', + 'api' => $api, + 'extra' => $extra, + ); + +// $skin = new Automatic_Upgrader_Skin( $skin_args ); +// $skin = new Plugin_Installer_Skin( $skin_args ); + $skin = new WP_Ajax_Upgrader_Skin( $skin_args ); + + // Create a new instance of Plugin_Upgrader. + $upgrader = new Plugin_Upgrader( $skin ); + + // Perform the action and install the plugin from the $source urldecode(). + add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); + + $install_result = $upgrader->install( $source ); + + remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 ); + + if ( is_wp_error( $install_result ) ) { + return array( + 'message' => $install_result->get_error_message(), + 'code' => $install_result->get_error_code(), + ); + } elseif ( is_wp_error( $skin->result ) ) { + return array( + 'message' => $skin->result->get_error_message(), + 'code' => $skin->result->get_error_code(), + ); + } elseif ( $skin->get_errors()->get_error_code() ) { + return array( + 'message' => $skin->get_error_messages(), + 'code' => 'unknown', + ); + } elseif ( is_null( $install_result ) ) { + global $wp_filesystem; + + $error_code = 'unable_to_connect_to_filesystem'; + $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && + is_wp_error( $wp_filesystem->errors ) && + $wp_filesystem->errors->get_error_code() + ) { + $error_message = $wp_filesystem->errors->get_error_message(); + } + + return array( + 'message' => $error_message, + 'code' => $error_code, + ); + } + + // Grab the full path to the main plugin's file. + $plugin_activate = $upgrader->plugin_info(); + + // Try to activate the plugin. + $activation_result = $this->try_activate_plugin( $plugin_activate ); + + if ( is_wp_error( $activation_result ) ) { + return array( + 'message' => $activation_result->get_error_message(), + 'code' => $activation_result->get_error_code(), + ); + } + + return $skin->get_upgrade_messages(); + } + + /** + * Tries to activate a plugin. If fails, returns the error. + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @param string $file_path Path within wp-plugins/ to main plugin file. + * This determines the styling of the output messages. + * + * @return bool|WP_Error + */ + protected function try_activate_plugin( $file_path ) { + $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() ); + + return is_wp_error( $activate ) ? + $activate : + true; + } + + /** + * Check if a premium module version is already active. + * + * @author Vova Feldman + * @since 1.2.1.7 + * + * @param number|bool $plugin_id + * + * @return bool + */ + private function is_premium_plugin_active( $plugin_id = false ) { + if ( $plugin_id != $this->_fs->get_id() ) { + return $this->_fs->is_addon_activated( $plugin_id, true ); + } + + return is_plugin_active( $this->_fs->premium_plugin_basename() ); + } + + /** + * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + * + * @param bool|WP_Error $response Response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * + * @return bool|WP_Error + */ + static function _store_basename_for_source_adjustment( $response, $hook_extra ) { + if ( isset( $hook_extra['plugin'] ) ) { + self::$_upgrade_basename = $hook_extra['plugin']; + } else if ( isset( $hook_extra['theme'] ) ) { + self::$_upgrade_basename = $hook_extra['theme']; + } else { + self::$_upgrade_basename = null; + } + + return $response; + } + + /** + * Adjust the plugin directory name if necessary. + * Assumes plugin has a folder (not a single file plugin). + * + * The final destination directory of a plugin is based on the subdirectory name found in the + * (un)zipped source. In some cases this subdirectory name is not the same as the expected + * slug and the plugin will not be recognized as installed. This is fixed by adjusting + * the temporary unzipped source subdirectory name to the expected plugin slug. + * + * @author Vova Feldman + * @since 1.2.1.7 + * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated. + * + * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/. + * @param string $remote_source Path to upgrade/zip-file-name.tmp. + * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin. + * + * @return string|WP_Error + */ + static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) { + if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) { + return $source; + } + + $basename = self::$_upgrade_basename; + $is_theme = false; + + // Figure out what the slug is supposed to be. + if ( isset( $upgrader->skin->options['extra'] ) ) { + // Set by the auto-install logic. + $desired_slug = $upgrader->skin->options['extra']['slug']; + } else if ( ! empty( $basename ) ) { + /** + * If it doesn't end with ".php", it's a theme. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + $is_theme = ( ! fs_ends_with( $basename, '.php' ) ); + + $desired_slug = ( ! $is_theme ) ? + dirname( $basename ) : + // Theme slug + $basename; + } else { + // Can't figure out the desired slug, stop the execution. + return $source; + } + + if ( is_multisite() ) { + /** + * If we are running in a multisite environment and the product is not network activated, + * the instance will not exist anyway. Therefore, try to update the source if necessary + * regardless if the Freemius instance of the product exists or not. + * + * @author Vova Feldman + */ + } else if ( ! empty( $basename ) ) { + $fs = Freemius::get_instance_by_file( + $basename, + $is_theme ? + WP_FS__MODULE_TYPE_THEME : + WP_FS__MODULE_TYPE_PLUGIN + ); + + if ( ! is_object( $fs ) ) { + /** + * If the Freemius instance does not exist on a non-multisite network environment, it means that: + * 1. The product is not powered by Freemius; OR + * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.1 + */ + return $source; + } + } + + $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) ); + + if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) { + $from_path = untrailingslashit( $source ); + $to_path = trailingslashit( $remote_source ) . $desired_slug; + + if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) { + return trailingslashit( $to_path ); + } + + return new WP_Error( + 'rename_failed', + fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ), + array( + 'found' => $subdir_name, + 'expected' => $desired_slug + ) + ); + } + + return $source; + } + + #endregion + } diff --git a/freemius/includes/class-fs-security.php b/freemius/includes/class-fs-security.php index e69de29..4535aa2 100644 --- a/freemius/includes/class-fs-security.php +++ b/freemius/includes/class-fs-security.php @@ -0,0 +1,85 @@ +id . + $entity->secret_key . + $entity->public_key . + $action + ); + } + + /** + * @param \FS_Scope_Entity $entity + * @param int|bool $timestamp + * @param string $action + * + * @return array + */ + function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) { + if ( false === $timestamp ) { + $timestamp = time(); + } + + return array( + 's_ctx_type' => $entity->get_type(), + 's_ctx_id' => $entity->id, + 's_ctx_ts' => $timestamp, + 's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ), + ); + } + } diff --git a/freemius/includes/class-fs-storage.php b/freemius/includes/class-fs-storage.php index e69de29..187a011 100644 --- a/freemius/includes/class-fs-storage.php +++ b/freemius/includes/class-fs-storage.php @@ -0,0 +1,532 @@ +_module_type = $module_type; + $this->_module_slug = $slug; + $this->_is_multisite = is_multisite(); + + if ( $this->_is_multisite ) { + $this->_blog_id = get_current_blog_id(); + $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true ); + } + + $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id ); + } + + /** + * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values + * are retrieved/stored from/into the storage. + * + * @author Leo Fajardo (@leorw) + * + * @param bool $is_network_active + * @param bool $is_delegated_connection + */ + function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { + $this->_is_network_active = $is_network_active; + $this->_is_delegated_connection = $is_delegated_connection; + } + + /** + * Switch the context of the site level storage manager. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + */ + function set_site_blog_context( $blog_id ) { + $this->_storage = $this->get_site_storage( $blog_id ); + $this->_blog_id = $blog_id; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param mixed $value + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * @param bool $flush + */ + function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + $this->_network_storage->store( $key, $value, $flush ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->store( $key, $value, $flush ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param bool $store + * @param string[] $exceptions Set of keys to keep and not clear. + * @param int|null|bool $network_level_or_blog_id + */ + function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite || + false === $network_level_or_blog_id || + is_null( $network_level_or_blog_id ) || + is_numeric( $network_level_or_blog_id ) + ) { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->clear_all( $store, $exceptions ); + } + + if ( $this->_is_multisite && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_storage->clear_all( $store, $exceptions ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param bool $store + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + */ + function remove( $key, $store = true, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + $this->_network_storage->remove( $key, $store ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->remove( $key, $store ); + } + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $key + * @param mixed $default + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * + * @return mixed + */ + function get( $key, $default = false, $network_level_or_blog_id = null ) { + if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { + return $this->_network_storage->get( $key, $default ); + } else { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + + return $storage->get( $key, $default ); + } + } + + /** + * Multisite activated: + * true: Save network storage. + * int: Save site specific storage. + * false|0: Save current site storage. + * null: Save network and current site storage. + * Site level activated: + * Save site storage. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool|int|null $network_level_or_blog_id + */ + function save( $network_level_or_blog_id = null ) { + if ( $this->_is_network_active && + ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) + ) { + $this->_network_storage->save(); + } + + if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { + $storage = $this->get_site_storage( $network_level_or_blog_id ); + $storage->save(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_module_slug() { + return $this->_module_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_module_type() { + return $this->_module_type; + } + + /** + * Migration script to the new storage data structure that is network compatible. + * + * IMPORTANT: + * This method should be executed only after it is determined if this is a network + * level compatible product activation. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + if ( ! $this->_is_multisite ) { + return; + } + + $updated = false; + + if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { + self::load_network_options_map(); + } + + foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { + if ( ! $this->is_multisite_option( $option ) ) { + continue; + } + + if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { + // Migrate option to the network storage. + $this->_network_storage->store( $option, $this->_storage->{$option}, false ); + + /** + * Remove the option from site level storage. + * + * IMPORTANT: + * The line below is intentionally commented since we want to preserve the option + * on the site storage level for "downgrade compatibility". Basically, if the user + * will downgrade to an older version of the plugin with the prev storage structure, + * it will continue working. + * + * @todo After a few releases we can remove this. + */ +// $this->_storage->remove($option, false); + + $updated = true; + } + } + + if ( ! $updated ) { + return; + } + + // Update network level storage. + $this->_network_storage->save(); +// $this->_storage->save(); + } + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * We don't want to load the map right away since it's not even needed in a non-MS environment. + * + * Example: + * array( + * 'option1' => 0, // Means that the option should always be stored on the network level. + * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. + * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. + * 'option3' => 3, // Means that the option should always be stored on the site level. + * ) + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + private static function load_network_options_map() { + self::$_NETWORK_OPTIONS_MAP = array( + // Network level options. + 'affiliate_application_data' => 0, + 'beta_data' => 0, + 'connectivity_test' => 0, + 'handle_gdpr_admin_notice' => 0, + 'has_trial_plan' => 0, + 'install_sync_timestamp' => 0, + 'install_sync_cron' => 0, + 'is_anonymous_ms' => 0, + 'is_network_activated' => 0, + 'is_on' => 0, + 'is_plugin_new_install' => 0, + 'network_install_blog_id' => 0, + 'pending_sites_info' => 0, + 'plugin_last_version' => 0, + 'plugin_main_file' => 0, + 'plugin_version' => 0, + 'sdk_downgrade_mode' => 0, + 'sdk_last_version' => 0, + 'sdk_upgrade_mode' => 0, + 'sdk_version' => 0, + 'sticky_optin_added_ms' => 0, + 'subscriptions' => 0, + 'sync_timestamp' => 0, + 'sync_cron' => 0, + 'was_plugin_loaded' => 0, + 'network_user_id' => 0, + 'plugin_upgrade_mode' => 0, + 'plugin_downgrade_mode' => 0, + 'is_network_connected' => 0, + /** + * Special flag that is used when a super-admin upgrades to the new version of the SDK that + * supports network level integration, when the connection decision wasn't made for all of the + * sites in the network. + */ + 'is_network_activation' => 0, + 'license_migration' => 0, + + // When network activated, then network level. + 'install_timestamp' => 1, + 'prev_is_premium' => 1, + 'require_license_activation' => 1, + + // If not network activated OR delegated, then site level. + 'activation_timestamp' => 2, + 'expired_license_notice_shown' => 2, + 'is_whitelabeled' => 2, + 'last_license_key' => 2, + 'last_license_user_id' => 2, + 'prev_user_id' => 2, + 'sticky_optin_added' => 2, + 'uninstall_reason' => 2, + 'is_pending_activation' => 2, + 'pending_license_key' => 2, + 'is_extensions_tracking_allowed' => 2, + + // Site level options. + 'is_anonymous' => 3, + ); + } + + /** + * This method will and should only be executed when is_multisite() is true. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $key + * + * @return bool|mixed + */ + private function is_multisite_option( $key ) { + if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { + self::load_network_options_map(); + } + + if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) { + // Option not found -> use site level storage. + return false; + } + + if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Option found and set to always use the network level storage on a multisite. + return true; + } + + if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Option found and set to always use the site level storage on a multisite. + return false; + } + + if ( ! $this->_is_network_active ) { + return false; + } + + if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) { + // Network activated. + return true; + } + + if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) { + // Network activated and not delegated. + return true; + } + + return false; + } + + /** + * @author Leo Fajardo + * + * @param string $key + * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * + * @return bool + */ + private function should_use_network_storage( $key, $network_level_or_blog_id = null ) { + if ( ! $this->_is_multisite ) { + // Not a multisite environment. + return false; + } + + if ( is_numeric( $network_level_or_blog_id ) ) { + // Explicitly asked to use a specified blog storage. + return false; + } + + if ( is_bool( $network_level_or_blog_id ) ) { + // Explicitly specified whether should use the network or blog level storage. + return $network_level_or_blog_id; + } + + // Determine which storage to use based on the option. + return $this->is_multisite_option( $key ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $blog_id + * + * @return \FS_Key_Value_Storage + */ + private function get_site_storage( $blog_id = 0 ) { + if ( ! is_numeric( $blog_id ) || + $blog_id == $this->_blog_id || + 0 == $blog_id + ) { + return $this->_storage; + } + + return FS_Key_Value_Storage::instance( + $this->_module_type . '_data', + $this->_storage->get_secondary_id(), + $blog_id + ); + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Magic methods + #-------------------------------------------------------------------------------- + + function __set( $k, $v ) { + if ( $this->should_use_network_storage( $k ) ) { + $this->_network_storage->{$k} = $v; + } else { + $this->_storage->{$k} = $v; + } + } + + function __isset( $k ) { + return $this->should_use_network_storage( $k ) ? + isset( $this->_network_storage->{$k} ) : + isset( $this->_storage->{$k} ); + } + + function __unset( $k ) { + if ( $this->should_use_network_storage( $k ) ) { + unset( $this->_network_storage->{$k} ); + } else { + unset( $this->_storage->{$k} ); + } + } + + function __get( $k ) { + return $this->should_use_network_storage( $k ) ? + $this->_network_storage->{$k} : + $this->_storage->{$k}; + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/class-fs-user-lock.php b/freemius/includes/class-fs-user-lock.php index e69de29..842cbba 100644 --- a/freemius/includes/class-fs-user-lock.php +++ b/freemius/includes/class-fs-user-lock.php @@ -0,0 +1,126 @@ +_wp_user_id = Freemius::get_current_wp_user_id(); + $this->_thread_id = mt_rand( 0, 32000 ); + } + + + /** + * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param int $expiration + * + * @return bool TRUE if successfully acquired lock. + */ + function try_lock( $expiration = 0 ) { + if ( $this->is_locked() ) { + // Already locked. + return false; + } + + set_site_transient( "locked_{$this->_wp_user_id}", $this->_thread_id, $expiration ); + + if ( $this->has_lock() ) { + set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); + + return true; + } + + return false; + } + + /** + * Acquire lock regardless if it's already acquired by another locker or not. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param int $expiration + */ + function lock( $expiration = 0 ) { + set_site_transient( "locked_{$this->_wp_user_id}", true, $expiration ); + } + + /** + * Checks if lock is currently acquired. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + function is_locked() { + return ( false !== get_site_transient( "locked_{$this->_wp_user_id}" ) ); + } + + /** + * Unlock the lock. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + function unlock() { + delete_site_transient( "locked_{$this->_wp_user_id}" ); + } + + /** + * Checks if lock is currently acquired by the current locker. + * + * @return bool + */ + private function has_lock() { + return ( $this->_thread_id == get_site_transient( "locked_{$this->_wp_user_id}" ) ); + } + } \ No newline at end of file diff --git a/freemius/includes/customizer/class-fs-customizer-support-section.php b/freemius/includes/customizer/class-fs-customizer-support-section.php index e69de29..3a9300d 100644 --- a/freemius/includes/customizer/class-fs-customizer-support-section.php +++ b/freemius/includes/customizer/class-fs-customizer-support-section.php @@ -0,0 +1,102 @@ +register_section_type( 'FS_Customizer_Support_Section' ); + + parent::__construct( $manager, $id, $args ); + } + + /** + * The type of customize section being rendered. + * + * @since 1.0.0 + * @access public + * @var string + */ + public $type = 'freemius-support-section'; + + /** + * @var Freemius + */ + public $fs = null; + + /** + * Add custom parameters to pass to the JS via JSON. + * + * @since 1.0.0 + */ + public function json() { + $json = parent::json(); + + $is_contact_visible = $this->fs->is_page_visible( 'contact' ); + $is_support_visible = $this->fs->is_page_visible( 'support' ); + + $json['theme_title'] = $this->fs->get_plugin_name(); + + if ( $is_contact_visible && $is_support_visible ) { + $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); + } + + if ( $is_contact_visible ) { + $json['contact'] = array( + 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), + 'url' => $this->fs->contact_url(), + ); + } + + if ( $is_support_visible ) { + $json['support'] = array( + 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), + 'url' => $this->fs->get_support_forum_url() + ); + } + + return $json; + } + + /** + * Outputs the Underscore.js template. + * + * @since 1.0.0 + */ + protected function render_template() { + ?> +
  • +

    + {{ data.theme_title }} + <# if ( data.contact && data.support ) { #> +
    + <# } #> + <# if ( data.contact ) { #> + {{ data.contact.label }} + <# } #> + <# if ( data.support ) { #> + {{ data.support.label }} + <# } #> + <# if ( data.contact && data.support ) { #> +
    + <# } #> +

    +
  • + register_control_type( 'FS_Customizer_Upsell_Control' ); + + parent::__construct( $manager, $id, $args ); + } + + /** + * Enqueue resources for the control. + */ + public function enqueue() { + fs_enqueue_local_style( 'fs_customizer', 'customizer.css' ); + } + + /** + * Json conversion + */ + public function to_json() { + $pricing_cta = esc_html( $this->fs->get_pricing_cta_label() ) . '  ' . ( is_rtl() ? '←' : '➤' ); + + parent::to_json(); + + $this->json['button_text'] = $pricing_cta; + $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? + $this->fs->get_trial_url() : + $this->fs->get_upgrade_url(); + + $api = FS_Plugin::is_valid_id( $this->fs->get_bundle_id() ) ? + $this->fs->get_api_bundle_scope() : + $this->fs->get_api_plugin_scope(); + + // Load features. + $pricing = $api->get( $this->fs->add_show_pending( "pricing.json" ) ); + + if ( $this->fs->is_api_result_object( $pricing, 'plans' ) ) { + // Add support features. + if ( is_array( $pricing->plans ) && 0 < count( $pricing->plans ) ) { + $support_features = array( + 'kb' => 'Help Center', + 'forum' => 'Support Forum', + 'email' => 'Priority Email Support', + 'phone' => 'Phone Support', + 'skype' => 'Skype Support', + 'is_success_manager' => 'Personal Success Manager', + ); + + for ( $i = 0, $len = count( $pricing->plans ); $i < $len; $i ++ ) { + if ( 'free' == $pricing->plans[$i]->name ) { + continue; + } + + if ( ! isset( $pricing->plans[ $i ]->features ) || + ! is_array( $pricing->plans[ $i ]->features ) ) { + $pricing->plans[$i]->features = array(); + } + + foreach ( $support_features as $key => $label ) { + $key = ( 'is_success_manager' !== $key ) ? + "support_{$key}" : + $key; + + if ( ! empty( $pricing->plans[ $i ]->{$key} ) ) { + + $support_feature = new stdClass(); + $support_feature->title = $label; + + $pricing->plans[ $i ]->features[] = $support_feature; + } + } + } + } + } + + $this->json['plans'] = $pricing->plans; + + $this->json['strings'] = array( + 'plan' => $this->fs->get_text_x_inline( 'Plan', 'as product pricing plan', 'plan' ), + ); + } + + /** + * Control content + */ + public function content_template() { + ?> +
    + <# if ( data.plans ) { #> +
      + <# for (i in data.plans) { #> + <# if ( 'free' != data.plans[i].name && (null != data.plans[i].features && 0 < data.plans[i].features.length) ) { #> +
    • +
      + +
      + <# if ( data.plans[i].description ) { #> +

      {{ data.plans[i].description }}

      + <# } #> + <# if ( data.plans[i].features ) { #> +
        + <# for ( j in data.plans[i].features ) { #> +
      • + <# if ( data.plans[i].features[j].value ) { #>{{ data.plans[i].features[j].value }} <# } #>{{ data.plans[i].features[j].title }} + <# if ( data.plans[i].features[j].description ) { #> + {{ data.plans[i].features[j].description }} + <# } #> +
      • + <# } #> +
      + <# } #> + <# if ( 'free' != data.plans[i].name ) { #> + {{{ data.button_text }}} + <# } #> +
      +
      +
    • + <# } #> + <# } #> +
    + <# } #> +
    + title( 'Freemius' ); + } + + static function requests_count() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } + + return number_format( count( $logger ) ); + } + + static function total_time() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } + + $total_time = .0; + foreach ( $logger as $l ) { + $total_time += $l['total']; + } + + return number_format( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); + } + + function render() { + ?> +
    + +
    + +
    + +
    + +
    + commission_type ) ? + ( '$' . $this->commission ) : + ( $this->commission . '%' ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function has_lifetime_commission() { + return ( 0 !== $this->future_payments_days ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function is_session_cookie() { + return ( 0 == $this->cookie_days ); + } + + /** + * @author Leo Fajardo (@leorw) + * + * @return bool + */ + function has_renewals_commission() { + return ( is_null( $this->commission_renewals_days ) || $this->commission_renewals_days > 0 ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-affiliate.php b/freemius/includes/entities/class-fs-affiliate.php index e69de29..cdae366 100644 --- a/freemius/includes/entities/class-fs-affiliate.php +++ b/freemius/includes/entities/class-fs-affiliate.php @@ -0,0 +1,84 @@ +status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_pending() { + return ( 'pending' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_suspended() { + return ( 'suspended' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_rejected() { + return ( 'rejected' === $this->status ); + } + + /** + * @author Leo Fajardo + * + * @return bool + */ + function is_blocked() { + return ( 'blocked' === $this->status ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-billing.php b/freemius/includes/entities/class-fs-billing.php index e69de29..bf68401 100644 --- a/freemius/includes/entities/class-fs-billing.php +++ b/freemius/includes/entities/class-fs-billing.php @@ -0,0 +1,95 @@ + $def_value ) { + $this->{$key} = isset( $entity->{$key} ) ? + $entity->{$key} : + $def_value; + } + } + + static function get_type() { + return 'type'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Entity $entity1 + * @param FS_Entity $entity2 + * + * @return bool + */ + static function equals( $entity1, $entity2 ) { + if ( is_null( $entity1 ) && is_null( $entity2 ) ) { + return true; + } else if ( is_object( $entity1 ) && is_object( $entity2 ) ) { + return ( $entity1->id == $entity2->id ); + } else if ( is_object( $entity1 ) ) { + return is_null( $entity1->id ); + } else { + return is_null( $entity2->id ); + } + } + + private $_is_updated = false; + + /** + * Update object property. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string|array[string]mixed $key + * @param string|bool $val + * + * @return bool + */ + function update( $key, $val = false ) { + if ( ! is_array( $key ) ) { + $key = array( $key => $val ); + } + + $is_updated = false; + + foreach ( $key as $k => $v ) { + if ( $this->{$k} === $v ) { + continue; + } + + if ( ( is_string( $this->{$k} ) && is_numeric( $v ) || + ( is_numeric( $this->{$k} ) && is_string( $v ) ) ) && + $this->{$k} == $v + ) { + continue; + } + + // Update value. + $this->{$k} = $v; + + $is_updated = true; + } + + $this->_is_updated = $is_updated; + + return $is_updated; + } + + /** + * Checks if entity was updated. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_updated() { + return $this->_is_updated; + } + + /** + * @param $id + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @return bool + */ + static function is_valid_id($id){ + return is_numeric($id); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @return string + */ + public static function get_class_name() { + return get_called_class(); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-payment.php b/freemius/includes/entities/class-fs-payment.php index e69de29..8e35766 100644 --- a/freemius/includes/entities/class-fs-payment.php +++ b/freemius/includes/entities/class-fs-payment.php @@ -0,0 +1,168 @@ +bound_payment_id ) && 0 > $this->gross ); + } + + /** + * Checks if the payment was migrated from another platform. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.2 + * + * @return bool + */ + function is_migrated() { + return ( 0 != $this->source ); + } + + /** + * Returns the gross in this format: + * `{symbol}{amount | 2 decimal digits} {currency | uppercase}` + * + * Examples: £9.99 GBP, -£9.99 GBP. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + function formatted_gross() + { + return ( + ( $this->gross < 0 ? '-' : '' ) . + $this->get_symbol() . + number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' . + strtoupper( $this->currency ) + ); + } + + /** + * A map between supported currencies with their symbols. + * + * @var array + */ + static $CURRENCY_2_SYMBOL; + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @return string + */ + private function get_symbol() { + if ( ! isset( self::$CURRENCY_2_SYMBOL ) ) { + // Lazy load. + self::$CURRENCY_2_SYMBOL = array( + self::CURRENCY_USD => '$', + self::CURRENCY_GBP => '£', + self::CURRENCY_EUR => '€', + ); + } + + return self::$CURRENCY_2_SYMBOL[ $this->currency ]; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-info.php b/freemius/includes/entities/class-fs-plugin-info.php index e69de29..f546534 100644 --- a/freemius/includes/entities/class-fs-plugin-info.php +++ b/freemius/includes/entities/class-fs-plugin-info.php @@ -0,0 +1,34 @@ +is_features_enabled() ) { + return 0; + } + + if ( $this->is_unlimited() ) { + return 999; + } + + return ( $this->quota - $this->activated - ( $this->is_free_localhost ? 0 : $this->activated_local ) ); + } + + /** + * Check if single site license. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8.1 + * + * @return bool + */ + function is_single_site() { + return ( is_numeric( $this->quota ) && 1 == $this->quota ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.5 + * + * @return bool + */ + function is_expired() { + return ! $this->is_lifetime() && ( strtotime( $this->expiration ) < WP_FS__SCRIPT_START_TIME ); + } + + /** + * Check if license is not expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @return bool + */ + function is_valid() { + return ! $this->is_expired(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_lifetime() { + return is_null( $this->expiration ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.0 + * + * @return bool + */ + function is_unlimited() { + return is_null( $this->quota ); + } + + /** + * Check if license is fully utilized. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|null $is_localhost + * + * @return bool + */ + function is_utilized( $is_localhost = null ) { + if ( is_null( $is_localhost ) ) { + $is_localhost = WP_FS__IS_LOCALHOST_FOR_SERVER; + } + + if ( $this->is_unlimited() ) { + return false; + } + + return ! ( $this->is_free_localhost && $is_localhost ) && + ( $this->quota <= $this->activated + ( $this->is_free_localhost ? 0 : $this->activated_local ) ); + } + + /** + * Check if license can be activated. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param bool|null $is_localhost + * + * @return bool + */ + function can_activate( $is_localhost = null ) { + return ! $this->is_utilized( $is_localhost ) && $this->is_features_enabled(); + } + + /** + * Check if license can be activated on a given number of production and localhost sites. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param int $production_count + * @param int $localhost_count + * + * @return bool + */ + function can_activate_bulk( $production_count, $localhost_count ) { + if ( $this->is_unlimited() ) { + return true; + } + + /** + * For simplicity, the logic will work as following: when given X sites to activate the license on, if it's + * possible to activate on ALL of them, do the activation. If it's not possible to activate on ALL of them, + * do NOT activate on any of them. + */ + return ( $this->quota >= $this->activated + $production_count + ( $this->is_free_localhost ? 0 : $this->activated_local + $localhost_count ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + * + * @return bool + */ + function is_active() { + return ( ! $this->is_cancelled ); + } + + /** + * Check if license's plan features are enabled. + * + * - Either if plan not expired + * - If expired, based on the configuration to block features or not. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_features_enabled() { + return $this->is_active() && ( ! $this->is_block_features || ! $this->is_expired() ); + } + + /** + * Subscription considered to be new without any payments + * if the license expires in less than 24 hours + * from the license creation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_first_payment_pending() { + return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->expiration ) - strtotime( $this->created ) ); + } + + /** + * @return int + */ + function total_activations() { + return ( $this->activated + $this->activated_local ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return string + */ + function get_html_escaped_masked_secret_key() { + return self::mask_secret_key_for_html( $this->secret_key ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @param string $secret_key + * + * @return string + */ + static function mask_secret_key_for_html( $secret_key ) { + return ( + // Initial 6 chars - sk_ABC + htmlspecialchars( substr( $secret_key, 0, 6 ) ) . + // Masking + str_pad( '', ( strlen( $secret_key ) - 9 ) * 6, '•' ) . + // Last 3 chars. + htmlspecialchars( substr( $secret_key, - 3 ) ) + ); + } + } diff --git a/freemius/includes/entities/class-fs-plugin-plan.php b/freemius/includes/entities/class-fs-plugin-plan.php index e69de29..00a0d74 100644 --- a/freemius/includes/entities/class-fs-plugin-plan.php +++ b/freemius/includes/entities/class-fs-plugin-plan.php @@ -0,0 +1,145 @@ +name = strtolower( $plan->name ); + } + } + + static function get_type() { + return 'plan'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_free() { + return ( 'free' === $this->name ); + } + + /** + * Checks if this plan supports "Technical Support". + * + * @author Leo Fajardo (leorw) + * @since 1.2.0 + * + * @return bool + */ + function has_technical_support() { + return ( ! empty( $this->support_email ) || + ! empty( $this->support_skype ) || + ! empty( $this->support_phone ) || + ! empty( $this->is_success_manager ) + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function has_trial() { + return ! $this->is_free() && + is_numeric( $this->trial_period ) && ( $this->trial_period > 0 ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin-tag.php b/freemius/includes/entities/class-fs-plugin-tag.php index e69de29..739e9c8 100644 --- a/freemius/includes/entities/class-fs-plugin-tag.php +++ b/freemius/includes/entities/class-fs-plugin-tag.php @@ -0,0 +1,60 @@ +release_mode ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-plugin.php b/freemius/includes/entities/class-fs-plugin.php index e69de29..2bc039a 100644 --- a/freemius/includes/entities/class-fs-plugin.php +++ b/freemius/includes/entities/class-fs-plugin.php @@ -0,0 +1,159 @@ +is_premium = false; + $this->is_live = true; + + if ( empty( $this->premium_slug ) && ! empty( $plugin->slug ) ) { + $this->premium_slug = "{$this->slug}-premium"; + } + + if ( empty( $this->premium_suffix ) ) { + $this->premium_suffix = '(Premium)'; + } + + if ( isset( $plugin->info ) && is_object( $plugin->info ) ) { + $this->info = new FS_Plugin_Info( $plugin->info ); + } + } + + /** + * Check if plugin is an add-on (has parent). + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool + */ + function is_addon() { + return isset( $this->parent_plugin_id ) && is_numeric( $this->parent_plugin_id ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.3 + * + * @return bool + */ + function has_affiliate_program() { + return ( ! empty( $this->affiliate_moderation ) ); + } + + static function get_type() { + return 'plugin'; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-pricing.php b/freemius/includes/entities/class-fs-pricing.php index e69de29..5404fe5 100644 --- a/freemius/includes/entities/class-fs-pricing.php +++ b/freemius/includes/entities/class-fs-pricing.php @@ -0,0 +1,157 @@ +monthly_price ) && $this->monthly_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_annual() { + return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_lifetime() { + return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); + } + + /** + * Check if unlimited licenses pricing. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_unlimited() { + return is_null( $this->licenses ); + } + + + /** + * Check if pricing has more than one billing cycle. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_multi_cycle() { + $cycles = 0; + if ( $this->has_monthly() ) { + $cycles ++; + } + if ( $this->has_annual() ) { + $cycles ++; + } + if ( $this->has_lifetime() ) { + $cycles ++; + } + + return $cycles > 1; + } + + /** + * Get annual over monthly discount. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return int + */ + function annual_discount_percentage() { + return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); + } + + /** + * Get annual over monthly savings. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return float + */ + function annual_savings() { + return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + * + * @return bool + */ + function is_usd() { + return ( 'usd' === $this->currency ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-scope-entity.php b/freemius/includes/entities/class-fs-scope-entity.php index e69de29..6b83107 100644 --- a/freemius/includes/entities/class-fs-scope-entity.php +++ b/freemius/includes/entities/class-fs-scope-entity.php @@ -0,0 +1,29 @@ +plan_id = $site->plan_id; + } + + if ( ! is_bool( $this->is_disconnected ) ) { + $this->is_disconnected = false; + } + } + + static function get_type() { + return 'install'; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $url + * + * @return bool + */ + static function is_localhost_by_address( $url ) { + if ( false !== strpos( $url, '127.0.0.1' ) || + false !== strpos( $url, 'localhost' ) + ) { + return true; + } + + if ( ! fs_starts_with( $url, 'http' ) ) { + $url = 'http://' . $url; + } + + $url_parts = parse_url( $url ); + + $subdomain = $url_parts['host']; + + return ( + // Starts with. + fs_starts_with( $subdomain, 'local.' ) || + fs_starts_with( $subdomain, 'dev.' ) || + fs_starts_with( $subdomain, 'test.' ) || + fs_starts_with( $subdomain, 'stage.' ) || + fs_starts_with( $subdomain, 'staging.' ) || + + // Ends with. + fs_ends_with( $subdomain, '.dev' ) || + fs_ends_with( $subdomain, '.test' ) || + fs_ends_with( $subdomain, '.staging' ) || + fs_ends_with( $subdomain, '.local' ) || + fs_ends_with( $subdomain, '.example' ) || + fs_ends_with( $subdomain, '.invalid' ) || + // GoDaddy test/dev. + fs_ends_with( $subdomain, '.myftpupload.com' ) || + // ngrok tunneling. + fs_ends_with( $subdomain, '.ngrok.io' ) || + // wpsandbox. + fs_ends_with( $subdomain, '.wpsandbox.pro' ) || + // SiteGround staging. + fs_starts_with( $subdomain, 'staging' ) || + // WPEngine staging. + fs_ends_with( $subdomain, '.staging.wpengine.com' ) || + fs_ends_with( $subdomain, '.dev.wpengine.com' ) || + fs_ends_with( $subdomain, '.wpengine.com' ) || + // Pantheon + ( fs_ends_with( $subdomain, 'pantheonsite.io' ) && + ( fs_starts_with( $subdomain, 'test-' ) || fs_starts_with( $subdomain, 'dev-' ) ) ) || + // Cloudways + fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || + // Kinsta + ( fs_starts_with( $subdomain, 'staging-' ) && ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) ) || + // DesktopServer + fs_ends_with( $subdomain, '.dev.cc' ) || + // Pressable + fs_ends_with( $subdomain, '.mystagingwebsite.com' ) + ); + } + + function is_localhost() { + return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) ); + } + + /** + * Check if site in trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_trial() { + return is_numeric( $this->trial_plan_id ) && ( strtotime( $this->trial_ends ) > WP_FS__SCRIPT_START_TIME ); + } + + /** + * Check if user already utilized the trial with the current install. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_trial_utilized() { + return is_numeric( $this->trial_plan_id ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_tracking_allowed() { + return ( true !== $this->is_disconnected ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function is_tracking_prohibited() { + return ! $this->is_tracking_allowed(); + } + + /** + * @author Edgar Melkonyan + * + * @return bool + */ + function is_beta() { + return ( isset( $this->is_beta ) && true === $this->is_beta ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-subscription.php b/freemius/includes/entities/class-fs-subscription.php index e69de29..3556fbd 100644 --- a/freemius/includes/entities/class-fs-subscription.php +++ b/freemius/includes/entities/class-fs-subscription.php @@ -0,0 +1,147 @@ +is_canceled() ) { + return false; + } + + return ( + ! empty( $this->next_payment ) && + strtotime( $this->next_payment ) > WP_FS__SCRIPT_START_TIME + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @return bool + */ + function is_canceled() { + return ! is_null( $this->canceled_at ); + } + + /** + * Subscription considered to be new without any payments + * if the next payment should be made within less than 24 hours + * from the subscription creation. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @return bool + */ + function is_first_payment_pending() { + return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->next_payment ) - strtotime( $this->created ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + */ + function has_trial() { + return ! is_null( $this->trial_ends ); + } + } \ No newline at end of file diff --git a/freemius/includes/entities/class-fs-user.php b/freemius/includes/entities/class-fs-user.php index e69de29..a329e87 100644 --- a/freemius/includes/entities/class-fs-user.php +++ b/freemius/includes/entities/class-fs-user.php @@ -0,0 +1,62 @@ +first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) ); + } + + function is_verified() { + return ( isset( $this->is_verified ) && true === $this->is_verified ); + } + + static function get_type() { + return 'user'; + } + } \ No newline at end of file diff --git a/freemius/includes/entities/index.php b/freemius/includes/entities/index.php index e69de29..0316c6a 100644 --- a/freemius/includes/entities/index.php +++ b/freemius/includes/entities/index.php @@ -0,0 +1,3 @@ +
    ' : '' ) . $title; + + if ( is_string( $confirmation ) ) { + return sprintf( '
    %s%s
    ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), + $method, + $action, + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $confirmation, + $title + ); + } else if ( 'GET' !== strtoupper( $method ) ) { + return sprintf( '
    %s%s
    ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), + $method, + $action, + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $title + ); + } else { + return sprintf( '%s', + wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), + $title + ); + } + } + + function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { + ?> $entities_or_entity ) { + if ( is_array( $entities_or_entity ) ) { + $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name ); + } else { + $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name ); + } + } + + return $entities; + } + } + + if ( ! function_exists( 'fs_nonce_url' ) ) { + /** + * Retrieve URL with nonce added to URL query. + * + * Originally was using `wp_nonce_url()` but the new version + * changed the return value to escaped URL, that's not the expected + * behaviour. + * + * @author Vova Feldman (@svovaf) + * @since ~1.1.3 + * + * @param string $actionurl URL to add nonce action. + * @param int|string $action Optional. Nonce action name. Default -1. + * @param string $name Optional. Nonce name. Default '_wpnonce'. + * + * @return string Escaped URL with nonce action added. + */ + function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) { + return add_query_arg( $name, wp_create_nonce( $action ), $actionurl ); + } + } + + if ( ! function_exists( 'fs_starts_with' ) ) { + /** + * Check if string starts with. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $haystack + * @param string $needle + * + * @return bool + */ + function fs_starts_with( $haystack, $needle ) { + $length = strlen( $needle ); + + return ( substr( $haystack, 0, $length ) === $needle ); + } + } + + if ( ! function_exists( 'fs_ends_with' ) ) { + /** + * Check if string ends with. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $haystack + * @param string $needle + * + * @return bool + */ + function fs_ends_with( $haystack, $needle ) { + $length = strlen( $needle ); + $start = $length * - 1; // negative + + return ( substr( $haystack, $start ) === $needle ); + } + } + + if ( ! function_exists( 'fs_strip_url_protocol' ) ) { + function fs_strip_url_protocol( $url ) { + if ( ! fs_starts_with( $url, 'http' ) ) { + return $url; + } + + $protocol_pos = strpos( $url, '://' ); + + if ( $protocol_pos > 5 ) { + return $url; + } + + return substr( $url, $protocol_pos + 3 ); + } + } + + #region Url Canonization ------------------------------------------------------------------ + + if ( ! function_exists( 'fs_canonize_url' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $url + * @param bool $omit_host + * @param array $ignore_params + * + * @return string + */ + function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { + $parsed_url = parse_url( strtolower( $url ) ); + +// if ( ! isset( $parsed_url['host'] ) ) { +// return $url; +// } + + $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path']; + + if ( isset( $parsed_url['query'] ) ) { + parse_str( $parsed_url['query'], $queryString ); + $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params ); + } + + return $canonical; + } + } + + if ( ! function_exists( 'fs_canonize_query_string' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param array $params + * @param array $ignore_params + * @param bool $params_prefix + * + * @return string + */ + function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) { + if ( ! is_array( $params ) || 0 === count( $params ) ) { + return ''; + } + + // Url encode both keys and values + $keys = fs_urlencode_rfc3986( array_keys( $params ) ); + $values = fs_urlencode_rfc3986( array_values( $params ) ); + $params = array_combine( $keys, $values ); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort( $params, 'strcmp' ); + + $pairs = array(); + foreach ( $params as $parameter => $value ) { + $lower_param = strtolower( $parameter ); + + // Skip ignore params. + if ( in_array( $lower_param, $ignore_params ) || + ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) ) + ) { + continue; + } + + if ( is_array( $value ) ) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + natsort( $value ); + foreach ( $value as $duplicate_value ) { + $pairs[] = $lower_param . '=' . $duplicate_value; + } + } else { + $pairs[] = $lower_param . '=' . $value; + } + } + + if ( 0 === count( $pairs ) ) { + return ''; + } + + return implode( "&", $pairs ); + } + } + + if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string|string[] $input + * + * @return array|mixed|string + */ + function fs_urlencode_rfc3986( $input ) { + if ( is_array( $input ) ) { + return array_map( 'fs_urlencode_rfc3986', $input ); + } else if ( is_scalar( $input ) ) { + return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) ); + } + + return ''; + } + } + + #endregion Url Canonization ------------------------------------------------------------------ + + if ( ! function_exists( 'fs_download_image' ) ) { + /** + * @author Vova Feldman (@svovaf) + * + * @since 1.2.2 Changed to usage of WP_Filesystem_Direct. + * + * @param string $from URL + * @param string $to File path. + * + * @return bool Is successfully downloaded. + */ + function fs_download_image( $from, $to ) { + $dir = dirname( $to ); + + if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { + return false; + } + + if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; + } + + $fs = new WP_Filesystem_Direct( '' ); + $tmpfile = download_url( $from ); + + if ( $tmpfile instanceof WP_Error ) { + // Issue downloading the file. + return false; + } + + $fs->copy( $tmpfile, $to ); + $fs->delete( $tmpfile ); + + return true; + } + } + + /* General Utilities + --------------------------------------------------------------------------------------------*/ + + if ( ! function_exists( 'fs_sort_by_priority' ) ) { + /** + * Sorts an array by the value of the priority key. + * + * @author Daniel Iser (@danieliser) + * @since 1.1.7 + * + * @param $a + * @param $b + * + * @return int + */ + function fs_sort_by_priority( $a, $b ) { + + // If b has a priority and a does not, b wins. + if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) { + return 1; + } // If b has a priority and a does not, b wins. + elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { + return - 1; + } // If neither has a priority or both priorities are equal its a tie. + elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) { + return 0; + } + + // If both have priority return the winner. + return ( $a['priority'] < $b['priority'] ) ? - 1 : 1; + } + } + + #-------------------------------------------------------------------------------- + #region Localization + #-------------------------------------------------------------------------------- + + if ( ! function_exists( 'fs_text' ) ) { + /** + * Retrieve a translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * @param string $slug + * + * @return string + * + * @global $fs_text , $fs_text_overrides + */ + function fs_text( $key, $slug = 'freemius' ) { + global $fs_text, + $fs_module_info_text, + $fs_text_overrides; + + if ( isset( $fs_text_overrides[ $slug ] ) ) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + } + + if ( ! isset( $fs_text ) ) { + $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? + WP_FS__DIR_INCLUDES : + dirname( __FILE__ ); + + require_once $dir . '/i18n.php'; + } + + if ( isset( $fs_text[ $key ] ) ) { + return $fs_text[ $key ]; + } + + if ( isset( $fs_module_info_text[ $key ] ) ) { + return $fs_module_info_text[ $key ]; + } + + return $key; + } + + #region Private + + /** + * Retrieve an inline translated text by key with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); + + // Avoid misleading Theme Check warning. + $fn = 'translate_with_gettext_context'; + + return $fn( $text, $context, $text_domain ); + } + + #endregion + + /** + * Retrieve an inline translated text by key with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return _fs_text_x_inline( $text, $context, $key, $slug ); + } + + /** + * Output a translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $key + * @param string $slug + */ + function fs_echo( $key, $slug = 'freemius' ) { + echo fs_text( $key, $slug ); + } + + /** + * Output an inline translated text. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo _fs_text_inline( $text, $key, $slug ); + } + + /** + * Output an inline translated text with a context. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo _fs_text_x_inline( $text, $context, $key, $slug ); + } + } + + if ( ! function_exists( 'fs_text_override' ) ) { + /** + * Get a translatable text override if exists, or `false`. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string|false + */ + function fs_text_override( $text, $key, $slug ) { + global $fs_text_overrides; + + /** + * Check if string is overridden. + */ + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { + return false; + } + + if ( empty( $key ) ) { + $key = strtolower( str_replace( ' ', '-', $text ) ); + } + + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + + return false; + } + } + + if ( ! function_exists( 'fs_text_and_domain' ) ) { + /** + * Get a translatable text and its text domain. + * + * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string[] + */ + function fs_text_and_domain( $text, $key, $slug ) { + $override = fs_text_override( $text, $key, $slug ); + + if ( false === $override ) { + // No override, use FS text domain. + $text_domain = 'freemius'; + } else { + // Found an override. + $text = $override; + // Use the module's text domain. + $text_domain = $slug; + } + + return array( $text, $text_domain ); + } + } + + if ( ! function_exists( '_fs_text_inline' ) ) { + /** + * Retrieve an inline translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); + + // Avoid misleading Theme Check warning. + $fn = 'translate'; + + return $fn( $text, $text_domain ); + } + } + + if ( ! function_exists( 'fs_text_inline' ) ) { + /** + * Retrieve an inline translated text by key. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + * + * @global $fs_text_overrides + */ + function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + return _fs_text_inline( $text, $key, $slug ); + } + } + + if ( ! function_exists( 'fs_esc_attr' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_attr( $key, $slug ) { + return esc_attr( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_attr_echo( $key, $slug ) { + echo esc_attr( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_attr( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_js( $key, $slug ) { + return esc_js( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_js_echo( $key, $slug ) { + echo esc_js( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_json_encode_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_json_encode_echo( $key, $slug ) { + echo json_encode( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo json_encode( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + * + * @return string + */ + function fs_esc_html( $key, $slug ) { + return esc_html( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + * + * @return string + */ + function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $context Context information for the translators. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo' ) ) { + /** + * @author Vova Feldman + * @since 1.2.1.6 + * + * @param string $key + * @param string $slug + */ + function fs_esc_html_echo( $key, $slug ) { + echo esc_html( fs_text( $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + * + * @param string $text Translatable string. + * @param string $key String key for overrides. + * @param string $slug Module slug for overrides. + */ + function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_inline( $text, $key, $slug ) ); + } + } + + if ( ! function_exists( 'fs_override_i18n' ) ) { + /** + * Override default i18n text phrases. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param array[string]string $key_value + * @param string $slug + * + * @global $fs_text_overrides + */ + function fs_override_i18n( array $key_value, $slug = 'freemius' ) { + global $fs_text_overrides; + + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { + $fs_text_overrides[ $slug ] = array(); + } + + foreach ( $key_value as $key => $value ) { + $fs_text_overrides[ $slug ][ $key ] = $value; + } + } + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Multisite Network + #-------------------------------------------------------------------------------- + + if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_plugin_uninstall() { + return ( + defined( 'WP_UNINSTALL_PLUGIN' ) || + ( 0 < did_action( 'update_option_uninstall_plugins' ) ) + ); + } + } + + if ( ! function_exists( 'fs_is_network_admin' ) ) { + /** + * Unlike is_network_admin(), this one will also work properly when + * the context execution is WP AJAX handler, and during plugin + * uninstall. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_network_admin() { + return ( + WP_FS__IS_NETWORK_ADMIN || + ( is_multisite() && fs_is_plugin_uninstall() ) + ); + } + } + + if ( ! function_exists( 'fs_is_blog_admin' ) ) { + /** + * Unlike is_blog_admin(), this one will also work properly when + * the context execution is WP AJAX handler, and during plugin + * uninstall. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function fs_is_blog_admin() { + return ( + WP_FS__IS_BLOG_ADMIN || + ( ! is_multisite() && fs_is_plugin_uninstall() ) + ); + } + } + + #endregion + + if ( ! function_exists( 'fs_apply_filter' ) ) { + /** + * Apply filter for specific plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param string $module_unique_affix Module's unique affix. + * @param string $tag The name of the filter hook. + * @param mixed $value The value on which the filters hooked to `$tag` are applied on. + * + * @return mixed The filtered value after all hooked functions are applied to it. + * + * @uses apply_filters() + */ + function fs_apply_filter( $module_unique_affix, $tag, $value ) { + $args = func_get_args(); + + return call_user_func_array( 'apply_filters', array_merge( + array( "fs_{$tag}_{$module_unique_affix}" ), + array_slice( $args, 2 ) ) + ); + } + } \ No newline at end of file diff --git a/freemius/includes/fs-essential-functions.php b/freemius/includes/fs-essential-functions.php index e69de29..76a3ef8 100644 --- a/freemius/includes/fs-essential-functions.php +++ b/freemius/includes/fs-essential-functions.php @@ -0,0 +1,500 @@ +add( "Freemius failed to redirect the page because the headers have been already sent from line {$line} in file {$file}. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' ); + } + + return false; + } + + if ( defined( 'DOING_AJAX' ) ) { + // Don't redirect on AJAX calls. + return false; + } + + if ( ! $location ) // allows the wp_redirect filter to cancel a redirect + { + return false; + } + + $location = fs_sanitize_redirect( $location ); + + if ( $is_IIS ) { + header( "Refresh: 0;url=$location" ); + } else { + if ( php_sapi_name() != 'cgi-fcgi' ) { + status_header( $status ); + } // This causes problems on IIS and some FastCGI setups + header( "Location: $location" ); + } + + if ( $exit ) { + exit(); + } + + return true; + } + + if ( ! function_exists( 'fs_sanitize_redirect' ) ) { + /** + * Sanitizes a URL for use in a redirect. + * + * @since 2.3 + * + * @param string $location + * + * @return string redirect-sanitized URL + */ + function fs_sanitize_redirect( $location ) { + $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location ); + $location = fs_kses_no_null( $location ); + + // remove %0d and %0a from location + $strip = array( '%0d', '%0a' ); + $found = true; + while ( $found ) { + $found = false; + foreach ( (array) $strip as $val ) { + while ( strpos( $location, $val ) !== false ) { + $found = true; + $location = str_replace( $val, '', $location ); + } + } + } + + return $location; + } + } + + if ( ! function_exists( 'fs_kses_no_null' ) ) { + /** + * Removes any NULL characters in $string. + * + * @since 1.0.0 + * + * @param string $string + * + * @return string + */ + function fs_kses_no_null( $string ) { + $string = preg_replace( '/\0+/', '', $string ); + $string = preg_replace( '/(\\\\0)+/', '', $string ); + + return $string; + } + } + } + + #endregion Core Redirect (copied from BuddyPress) ----------------------------------------- + + if ( ! function_exists( '__fs' ) ) { + global $fs_text_overrides; + + if ( ! isset( $fs_text_overrides ) ) { + $fs_text_overrides = array(); + } + + /** + * Retrieve a translated text by key. + * + * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7. + * @todo Remove this method in the future. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $key + * @param string $slug + * + * @return string + * + * @global $fs_text, $fs_text_overrides + */ + function __fs( $key, $slug = 'freemius' ) { + _deprecated_function( __FUNCTION__, '2.0.0', 'fs_text()' ); + + global $fs_text, + $fs_module_info_text, + $fs_text_overrides; + + if ( isset( $fs_text_overrides[ $slug ] ) ) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { + return $fs_text_overrides[ $slug ][ $key ]; + } + + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { + return $fs_text_overrides[ $slug ][ $lower_key ]; + } + } + + if ( ! isset( $fs_text ) ) { + $dir = defined( 'WP_FS__DIR_INCLUDES' ) ? + WP_FS__DIR_INCLUDES : + dirname( __FILE__ ); + + require_once $dir . '/i18n.php'; + } + + if ( isset( $fs_text[ $key ] ) ) { + return $fs_text[ $key ]; + } + + if ( isset( $fs_module_info_text[ $key ] ) ) { + return $fs_module_info_text[ $key ]; + } + + return $key; + } + + /** + * Output a translated text by key. + * + * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`. + * + * @todo Remove this method in the future. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param string $key + * @param string $slug + */ + function _efs( $key, $slug = 'freemius' ) { + fs_echo( $key, $slug ); + } + } + + if ( ! function_exists( 'fs_get_ip' ) ) { + /** + * Get client IP. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.2 + * + * @return string|null + */ + function fs_get_ip() { + $fields = array( + 'HTTP_CF_CONNECTING_IP', + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR', + ); + + foreach ( $fields as $ip_field ) { + if ( ! empty( $_SERVER[ $ip_field ] ) ) { + return $_SERVER[ $ip_field ]; + } + } + + return null; + } + } + + /** + * Leverage backtrace to find caller plugin main file path. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return string + */ + function fs_find_caller_plugin_file() { + /** + * All the code below will be executed once on activation. + * If the user changes the main plugin's file name, the file_exists() + * will catch it. + */ + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $all_plugins = fs_get_plugins( true ); + $all_plugins_paths = array(); + + // Get active plugin's main files real full names (might be symlinks). + foreach ( $all_plugins as $relative_path => $data ) { + $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); + } + + $plugin_file = null; + for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { + if ( empty( $bt[ $i ]['file'] ) ) { + continue; + } + + if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) { + $plugin_file = $bt[ $i ]['file']; + break; + } + } + + if ( is_null( $plugin_file ) ) { + // Throw an error to the developer in case of some edge case dev environment. + wp_die( + 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', + 'Error', + array( 'back_link' => true ) + ); + } + + return $plugin_file; + } + + require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php'; + + /** + * Update SDK newest version reference. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $sdk_relative_path + * @param string|bool $plugin_file + * + * @global $fs_active_plugins + */ + function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) { + /** + * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()` + * function in the older version will be used instead of this one. But since the older version is using + * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the + * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be + * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine. + * + * Future versions that will call this function will use the proper logic here instead of just relying on the + * `is_plugin_active` function to fail for themes. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + + global $fs_active_plugins; + + $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ]; + + if ( ! is_string( $plugin_file ) ) { + $plugin_file = plugin_basename( fs_find_caller_plugin_file() ); + } + + if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) { + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $in_activation = ( ! is_plugin_active( $plugin_file ) ); + } else { + $theme = wp_get_theme(); + $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet ); + } + + $fs_active_plugins->newest = (object) array( + 'plugin_path' => $plugin_file, + 'sdk_path' => $sdk_relative_path, + 'version' => $newest_sdk->version, + 'in_activation' => $in_activation, + 'timestamp' => time(), + ); + + // Update DB with latest SDK version and path. + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + /** + * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool Was plugin order changed. Return false if plugin was loaded first anyways. + * + * @global $fs_active_plugins + */ + function fs_newest_sdk_plugin_first() { + global $fs_active_plugins; + + /** + * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins). + * + * @link https://github.com/Freemius/wordpress-sdk/issues/26 + */ + + $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path; + + $active_plugins = get_option( 'active_plugins', array() ); + $updated_active_plugins = array( $newest_sdk_plugin_path ); + + $plugin_found = false; + $is_first_path = true; + + foreach ( $active_plugins as $key => $plugin_path ) { + if ( $plugin_path === $newest_sdk_plugin_path ) { + if ( $is_first_path ) { + // if it's the first plugin already, no need to continue + return false; + } + + $plugin_found = true; + + // Skip the plugin (it is already added as the 1st item of $updated_active_plugins). + continue; + } + + $updated_active_plugins[] = $plugin_path; + + if ( $is_first_path ) { + $is_first_path = false; + } + } + + if ( $plugin_found ) { + update_option( 'active_plugins', $updated_active_plugins ); + + return true; + } + + if ( is_multisite() ) { + // Plugin is network active. + $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() ); + + if ( isset( $network_active_plugins[ $newest_sdk_plugin_path ] ) ) { + reset( $network_active_plugins ); + if ( $newest_sdk_plugin_path === key( $network_active_plugins ) ) { + // Plugin is already activated first on the network level. + return false; + } else { + $time = $network_active_plugins[ $newest_sdk_plugin_path ]; + + // Remove plugin from its current position. + unset( $network_active_plugins[ $newest_sdk_plugin_path ] ); + + // Set it to be included first. + $network_active_plugins = array( $newest_sdk_plugin_path => $time ) + $network_active_plugins; + + update_site_option( 'active_sitewide_plugins', $network_active_plugins ); + + return true; + } + } + } + + return false; + } + + /** + * Go over all Freemius SDKs in the system and find and "remember" + * the newest SDK which is associated with an active plugin. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @global $fs_active_plugins + */ + function fs_fallback_to_newest_active_sdk() { + global $fs_active_plugins; + + /** + * @var object $newest_sdk_data + */ + $newest_sdk_data = null; + $newest_sdk_path = null; + + foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) { + if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' ) + ) { + // If plugin inactive or SDK starter file doesn't exist, remove SDK reference. + if ( 'plugin' === $data->type ) { + $is_module_active = is_plugin_active( $data->plugin_path ); + } else { + $active_theme = wp_get_theme(); + $is_module_active = ( $data->plugin_path === $active_theme->get_template() ); + } + + $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) ); + + if ( ! $is_module_active || ! $is_sdk_exists ) { + unset( $fs_active_plugins->plugins[ $sdk_relative_path ] ); + + // No need to store the data since it will be stored in fs_update_sdk_newest_version() + // or explicitly with update_option(). + } else { + $newest_sdk_data = $data; + $newest_sdk_path = $sdk_relative_path; + } + } + } + + if ( is_null( $newest_sdk_data ) ) { + // Couldn't find any SDK reference. + $fs_active_plugins = new stdClass(); + update_option( 'fs_active_plugins', $fs_active_plugins ); + } else { + fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path ); + } + } \ No newline at end of file diff --git a/freemius/includes/fs-plugin-info-dialog.php b/freemius/includes/fs-plugin-info-dialog.php index e69de29..bcfce99 100644 --- a/freemius/includes/fs-plugin-info-dialog.php +++ b/freemius/includes/fs-plugin-info-dialog.php @@ -0,0 +1,1644 @@ +_fs = $fs; + + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + // Remove default plugin information action. + remove_all_actions( 'install_plugins_pre_plugin-information' ); + + // Override action with custom plugins function for add-ons. + add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) ); + + // Override request for plugin information for Add-ons. + add_filter( + 'fs_plugins_api', + array( &$this, '_get_addon_info_filter' ), + WP_FS__DEFAULT_PRIORITY, 3 ); + } + + /** + * Generate add-on plugin information. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param array $data + * @param string $action + * @param object|null $args + * + * @return array|null + */ + function _get_addon_info_filter( $data, $action = '', $args = null ) { + $this->_logger->entrance(); + + $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() ); + + if ( $this->_fs->get_id() != $parent_plugin_id || + ( 'plugin_information' !== $action ) || + ! isset( $args->slug ) + ) { + return $data; + } + + // Find add-on by slug. + $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE ); + + if ( false === $selected_addon ) { + return $data; + } + + if ( ! isset( $selected_addon->info ) ) { + // Setup some default info. + $selected_addon->info = new stdClass(); + $selected_addon->info->selling_point_0 = 'Selling Point 1'; + $selected_addon->info->selling_point_1 = 'Selling Point 2'; + $selected_addon->info->selling_point_2 = 'Selling Point 3'; + $selected_addon->info->description = '

    Tell your users all about your add-on

    '; + } + + fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); + + $data = $args; + + $has_free_plan = false; + $has_paid_plan = false; + + // Load add-on pricing. + $has_pricing = false; + $has_features = false; + $plans = false; + + $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) ); + + if ( ! isset( $result->error ) ) { + $plans = $result->plans; + + if ( is_array( $plans ) ) { + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null; + $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null; + + $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); + $plan = $plans[ $i ]; + + if ( 'free' == $plans[ $i ]->name || + ! is_array( $pricing ) || + 0 == count( $pricing ) + ) { + $has_free_plan = true; + } + + if ( is_array( $pricing ) && 0 < count( $pricing ) ) { + $filtered_pricing = array(); + + foreach ( $pricing as $prices ) { + $prices = new FS_Pricing( $prices ); + + if ( ! $prices->is_usd() ) { + /** + * Skip non-USD pricing. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + continue; + } + + if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) || + ( $prices->has_annual() && $prices->annual_price > 1.0 ) || + ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 ) + ) { + $filtered_pricing[] = $prices; + } + } + + if ( ! empty( $filtered_pricing ) ) { + $has_paid_plan = true; + + $plan->pricing = $filtered_pricing; + + $has_pricing = true; + } + } + + if ( is_array( $features ) && 0 < count( $features ) ) { + $plan->features = $features; + + $has_features = true; + } + } + } + } + + $latest = null; + + if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { + $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( + 'plugin_information', (object) array( + 'slug' => $selected_addon->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => true, + 'reviews' => true, + 'downloaded' => false, + 'active_installs' => true + ) + ) ); + + if ( ! empty( $repo_data ) ) { + $data = $repo_data; + $data->wp_org_missing = false; + } else { + // Couldn't find plugin on .org. + $selected_addon->is_wp_org_compliant = false; + + // Plugin is missing, not on Freemius nor WP.org. + $data->wp_org_missing = true; + } + + $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing ); + } else { + $data->has_purchased_license = false; + $data->wp_org_missing = false; + + $fs_addon = null; + $current_addon_version = false; + if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) { + $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id ); + $current_addon_version = $fs_addon->get_plugin_version(); + } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) { + $addon_plugin_data = get_plugin_data( + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ), + false, + false + ); + + if ( ! empty( $addon_plugin_data ) ) { + $current_addon_version = $addon_plugin_data['Version']; + } + } + + // Fetch latest version from Freemius. + $latest = $this->_fs->_fetch_latest_version( + $selected_addon->id, + true, + WP_FS__TIME_24_HOURS_IN_SEC, + $current_addon_version + ); + + if ( $has_paid_plan ) { + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); + + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); + } + + $data->checkout_link = $this->_fs->checkout_url( + WP_FS__PERIOD_ANNUALLY, + false, + array(), + ( $has_valid_blog_id ? false : null ) + ); + + if ( $has_valid_blog_id ) { + restore_current_blog(); + } + } + + /** + * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.1 + */ + if ( is_object( $fs_addon ) ) { + $data->has_purchased_license = $fs_addon->has_active_valid_license(); + } else { + $account_addons = $this->_fs->get_account_addons(); + if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { + $data->has_purchased_license = true; + } + } + + if ( $has_free_plan || $data->has_purchased_license ) { + $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id ); + } + + $data->fs_missing = ( + false === $latest && + ( + empty( $selected_addon->premium_releases_count ) || + ! ( $selected_addon->premium_releases_count > 0 ) + ) + ); + + // Fetch as much as possible info from local files. + $plugin_local_data = $this->_fs->get_plugin_data(); + $data->author = $plugin_local_data['Author']; + + if ( ! empty( $selected_addon->info->banner_url ) ) { + $data->banners = array( + 'low' => $selected_addon->info->banner_url, + ); + } + + if ( ! empty( $selected_addon->info->screenshots ) ) { + $view_vars = array( + 'screenshots' => $selected_addon->info->screenshots, + 'plugin' => $selected_addon, + ); + $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars ); + } + + if ( is_object( $latest ) ) { + $data->version = $latest->version; + $data->last_updated = $latest->created; + $data->requires = $latest->requires_platform_version; + $data->tested = $latest->tested_up_to_version; + } else if ( ! empty( $current_addon_version ) ) { + $data->version = $current_addon_version; + } else { + // Add dummy version. + $data->version = '1.0.0'; + + // Add message to developer to deploy the plugin through Freemius. + } + } + + $data->name = $selected_addon->title; + $view_vars = array( 'plugin' => $selected_addon ); + + if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { + $latest_version_readme_data = $latest->readme; + if ( isset( $latest_version_readme_data->sections ) ) { + $data->sections = (array) $latest_version_readme_data->sections; + } else { + $data->sections = array(); + } + } + + $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars ); + + if ( $has_pricing ) { + // Add plans to data. + $data->plans = $plans; + + if ( $has_features ) { + $view_vars = array( + 'plans' => $plans, + 'plugin' => $selected_addon, + ); + $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); + } + } + + $data->has_free_plan = $has_free_plan; + $data->has_paid_plan = $has_paid_plan; + $data->is_paid = $has_paid_plan; + $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant; + $data->premium_slug = $selected_addon->premium_slug; + $data->addon_id = $selected_addon->id; + + if ( ! isset( $data->has_purchased_license ) ) { + $data->has_purchased_license = false; + } + + return $data; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_billing_cycle( FS_Plugin_Plan $plan ) { + $billing_cycle = null; + + if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { + $pricing = $plan->pricing[0]; + if ( isset( $pricing->annual_price ) ) { + $billing_cycle = 'annual'; + } else if ( isset( $pricing->monthly_price ) ) { + $billing_cycle = 'monthly'; + } else if ( isset( $pricing->lifetime_price ) ) { + $billing_cycle = 'lifetime'; + } + } else { + foreach ( $plan->pricing as $pricing ) { + if ( isset( $pricing->annual_price ) ) { + $billing_cycle = 'annual'; + } else if ( isset( $pricing->monthly_price ) ) { + $billing_cycle = 'monthly'; + } else if ( isset( $pricing->lifetime_price ) ) { + $billing_cycle = 'lifetime'; + } + + if ( ! is_null( $billing_cycle ) ) { + break; + } + } + } + + return $billing_cycle; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param FS_Plugin_Plan $plan + * @param FS_Pricing $pricing + * + * @return float|null|string + */ + private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) { + $price_tag = ''; + if ( isset( $pricing->annual_price ) ) { + $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' ); + } else if ( isset( $pricing->monthly_price ) ) { + $price_tag = $pricing->monthly_price . ' / mo'; + } else if ( isset( $pricing->lifetime_price ) ) { + $price_tag = $pricing->lifetime_price; + } + + return '$' . $price_tag; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param object $api + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_actions_dropdown( $api, $plan = null ) { + $this->actions = isset( $this->actions ) ? + $this->actions : + $this->get_plugin_actions( $api ); + + $actions = $this->actions; + + $checkout_cta = $this->get_checkout_cta( $api, $plan ); + if ( ! empty( $checkout_cta ) ) { + /** + * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in + * the actions dropdown. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $api->has_purchased_license ) { + array_unshift( $actions, $checkout_cta ); + } else { + $actions[] = $checkout_cta; + } + } + + if ( empty( $actions ) ) { + return ''; + } + + $total_actions = count( $actions ); + if ( 1 === $total_actions ) { + return $actions[0]; + } + + ob_start(); + + ?> +
    +
    + +
    + + +
    +
    +
    + checkout_link ) || + ! isset( $api->plans ) || + ! is_array( $api->plans ) || + 0 == count( $api->plans ) + ) { + return ''; + } + + if ( is_null( $plan ) ) { + foreach ( $api->plans as $p ) { + if ( ! empty( $p->pricing ) ) { + $plan = $p; + break; + } + } + } + + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); + + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); + } + + $addon_checkout_url = $this->_fs->addon_checkout_url( + $plan->plugin_id, + $plan->pricing[0]->id, + $this->get_billing_cycle( $plan ), + $plan->has_trial(), + ( $has_valid_blog_id ? false : null ) + ); + + if ( $has_valid_blog_id ) { + restore_current_blog(); + } + + return '' . + esc_html( ! $plan->has_trial() ? + ( + $api->has_purchased_license ? + fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) + ) : + sprintf( + /* translators: %s: N-days trial */ + fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), + $this->get_trial_period( $plan ) + ) + ) . + ''; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param object $api + * + * @return string[] + */ + private function get_plugin_actions( $api ) { + $this->status = isset( $this->status ) ? + $this->status : + install_plugin_install_status( $api ); + + $is_update_available = ( 'update_available' === $this->status['status'] ); + + if ( $is_update_available && empty( $this->status['url'] ) ) { + return array(); + } + + $blog_id = fs_request_get( 'fs_blog_id' ); + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id ); + + $actions = array(); + + $is_addon_activated = $this->_fs->is_addon_activated( $api->slug ); + $fs_addon = null; + + $is_free_installed = null; + $is_premium_installed = null; + + $has_installed_version = ( 'install' !== $this->status['status'] ); + + if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { + /** + * Free-only add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_free_installed = $has_installed_version; + $is_premium_installed = false; + } else if ( ! $api->has_free_plan ) { + /** + * Premium-only add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $is_free_installed = false; + $is_premium_installed = $has_installed_version; + } else { + /** + * Freemium add-on. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + if ( ! $has_installed_version ) { + $is_free_installed = false; + $is_premium_installed = false; + } else { + $fs_addon = $is_addon_activated ? + $this->_fs->get_addon_instance( $api->slug ) : + null; + + if ( is_object( $fs_addon ) ) { + if ( $fs_addon->is_premium() ) { + $is_premium_installed = true; + } else { + $is_free_installed = true; + } + } + + if ( is_null( $is_free_installed ) ) { + $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) ); + if ( ! $is_free_installed ) { + /** + * Check if there's a plugin installed in a directory named `$api->slug`. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $installed_plugins = get_plugins( '/' . $api->slug ); + $is_free_installed = ( ! empty( $installed_plugins ) ); + } + } + + if ( is_null( $is_premium_installed ) ) { + $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) ); + if ( ! $is_premium_installed ) { + /** + * Check if there's a plugin installed in a directory named `$api->premium_slug`. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $installed_plugins = get_plugins( '/' . $api->premium_slug ); + $is_premium_installed = ( ! empty( $installed_plugins ) ); + } + } + } + + $has_installed_version = ( $is_free_installed || $is_premium_installed ); + } + + $this->status['is_free_installed'] = $is_free_installed; + $this->status['is_premium_installed'] = $is_premium_installed; + + $can_install_free_version = false; + $can_install_free_version_update = false; + $can_download_free_version = false; + $can_activate_free_version = false; + $can_install_premium_version = false; + $can_install_premium_version_update = false; + $can_download_premium_version = false; + $can_activate_premium_version = false; + + if ( ! $api->has_purchased_license ) { + if ( $api->has_free_plan ) { + if ( $has_installed_version ) { + if ( $is_update_available ) { + $can_install_free_version_update = true; + } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { + $can_activate_free_version = true; + } + } else { + if ( + $this->_fs->is_premium() || + ! $this->_fs->is_org_repo_compliant() || + $api->is_wp_org_compliant + ) { + $can_install_free_version = true; + } else { + $can_download_free_version = true; + } + } + } + } else { + if ( ! is_object( $fs_addon ) && $is_addon_activated ) { + $fs_addon = $this->_fs->get_addon_instance( $api->slug ); + } + + $can_download_premium_version = true; + + if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { + if ( $is_premium_installed ) { + $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() ); + } else if ( $is_free_installed ) { + $can_activate_free_version = ( ! $is_addon_activated ); + } + } + + if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) { + if ( $is_update_available ) { + $can_install_premium_version_update = true; + } else if ( ! $is_premium_installed ) { + $can_install_premium_version = true; + } + } + } + + if ( + $can_install_premium_version || + $can_install_premium_version_update + ) { + if ( is_numeric( $blog_id ) ) { + /** + * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded + * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update + * to work. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); + } + + /** + * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be + * installed/updated. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + */ + $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&', $this->status['url'] ); + } + + if ( $can_install_free_version_update || $can_install_premium_version_update ) { + $actions[] = $this->get_cta( + ( $can_install_free_version_update ? + fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) : + fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ), + true, + false, + $this->status['url'], + '_parent' + ); + } else if ( $can_install_free_version || $can_install_premium_version ) { + $actions[] = $this->get_cta( + ( $can_install_free_version ? + fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) : + fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ), + true, + false, + $this->status['url'], + '_parent' + ); + } + + $download_latest_action = ''; + + if ( + ! empty( $api->download_link ) && + ( $can_download_free_version || $can_download_premium_version ) + ) { + $download_latest_action = $this->get_cta( + ( $can_download_free_version ? + fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) : + fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ), + true, + false, + esc_url( $api->download_link ) + ); + } + + if ( ! $can_activate_free_version && ! $can_activate_premium_version ) { + if ( ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + } + } else { + $activate_action = sprintf( + '%s', + wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ), + $can_activate_free_version ? + fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) : + fs_text_inline( 'Activate', 'activate', $api->slug ) + ); + + if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + + $download_latest_action = ''; + } + + if ( $can_install_premium_version || $can_install_premium_version_update ) { + if ( $can_download_premium_version && ! empty( $download_latest_action ) ) { + $actions[] = $download_latest_action; + + $download_latest_action = ''; + } + + $actions[] = $activate_action; + } else { + array_unshift( $actions, $activate_action ); + } + + if ( ! empty ($download_latest_action ) ) { + $actions[] = $download_latest_action; + } + } + + return $actions; + } + + /** + * Rebuilds the status URL based on the admin URL. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.0 + * + * @param int $blog_id + * @param string $network_status_url + * @param string $status + * + * @return string + */ + private static function get_blog_status_url( $blog_id, $network_status_url, $status ) { + if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) { + return $network_status_url; + } + + $action = ( 'install' === $status ) ? + 'install-plugin' : + 'upgrade-plugin'; + + $query = parse_url( $network_status_url, PHP_URL_QUERY ); + if ( empty( $query ) ) { + return $network_status_url; + } + + parse_str( html_entity_decode( $query ), $url_params ); + if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) { + return $network_status_url; + } + + $plugin = $url_params['plugin']; + + return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); + } + + /** + * Helper method to get a CTA button HTML. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $label + * @param bool $is_primary + * @param bool $is_disabled + * @param string $href + * @param string $target + * + * @return string + */ + private function get_cta( + $label, + $is_primary = true, + $is_disabled = false, + $href = '', + $target = '_blank' + ) { + $classes = array(); + + if ( ! $is_primary ) { + $classes[] = 'left'; + } else { + $classes[] = 'button-primary'; + $classes[] = 'right'; + } + + if ( $is_disabled ) { + $classes[] = 'disabled'; + } + + $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : ''; + + return sprintf( + '%s', + empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, + implode( ' ', $classes ), + $label + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.7 + * + * @param FS_Plugin_Plan $plan + * + * @return string + */ + private function get_trial_period( $plan ) { + $trial_period = (int) $plan->trial_period; + + switch ( $trial_period ) { + case 30: + return 'month'; + case 60: + return '2 months'; + default: + return "{$plan->trial_period} days"; + } + } + + /** + * Display plugin information in dialog box form. + * + * Based on core install_plugin_information() function. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function install_plugin_information() { + global $tab; + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + $args = array( + 'slug' => wp_unslash( $_REQUEST['plugin'] ), + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => true, + 'reviews' => true, + 'downloaded' => false, + 'active_installs' => true + ) + ); + + if ( is_array( $args ) ) { + $args = (object) $args; + } + + if ( ! isset( $args->per_page ) ) { + $args->per_page = 24; + } + + if ( ! isset( $args->locale ) ) { + $args->locale = get_locale(); + } + + $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); + + if ( is_wp_error( $api ) ) { + wp_die( $api ); + } + + $plugins_allowedtags = array( + 'a' => array( + 'href' => array(), + 'title' => array(), + 'target' => array(), + // Add image style for screenshots. + 'class' => array() + ), + 'style' => array(), + 'abbr' => array( 'title' => array() ), + 'acronym' => array( 'title' => array() ), + 'code' => array(), + 'pre' => array(), + 'em' => array(), + 'strong' => array(), + 'div' => array( 'class' => array() ), + 'span' => array( 'class' => array() ), + 'p' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array( 'class' => array() ), + 'i' => array( 'class' => array() ), + 'h1' => array(), + 'h2' => array(), + 'h3' => array(), + 'h4' => array(), + 'h5' => array(), + 'h6' => array(), + 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ), +// 'table' => array(), +// 'td' => array(), +// 'tr' => array(), +// 'th' => array(), +// 'thead' => array(), +// 'tbody' => array(), + ); + + $plugins_section_titles = array( + 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ), + 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ), + 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ), + 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ), + 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ), + 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ), + 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ), + ); + + // Sanitize HTML +// foreach ( (array) $api->sections as $section_name => $content ) { +// $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); +// } + + foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { + if ( isset( $api->$key ) ) { + $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); + } + } + + // Add after $api->slug is ready. + $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug ); + + $_tab = esc_attr( $tab ); + + $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English. + if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) { + $section_titles = array_keys( (array) $api->sections ); + $section = array_shift( $section_titles ); + } + + iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) ); + + $_with_banner = ''; + +// var_dump($api->banners); + if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) { + $_with_banner = 'with-banner'; + $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low']; + $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high']; + ?> + + '; + echo "

    {$api->name}

    "; + echo "
    \n"; + + foreach ( (array) $api->sections as $section_name => $content ) { + if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { + continue; + } + + if ( isset( $plugins_section_titles[ $section_name ] ) ) { + $title = $plugins_section_titles[ $section_name ]; + } else { + $title = ucwords( str_replace( '_', ' ', $section_name ) ); + } + + $class = ( $section_name === $section ) ? ' class="current"' : ''; + $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); + $href = esc_url( $href ); + $san_section = esc_attr( $section_name ); + echo "\t" . esc_html( $title ) . "\n"; + } + + echo "
    \n"; + + ?> +
    +
    + is_paid ) : ?> + plans ) ) : ?> +
    + plans as $plan ) : ?> + pricing ) ) { + continue; + } + + /** + * @var FS_Plugin_Plan $plan + */ + ?> + pricing[0] ?> + is_multi_cycle() ?> +
    +

    slug ), $plan->title ) ) ?>

    + has_annual() ?> + has_monthly() ?> + +
    + + pricing[0]->annual_discount_percentage() : 0 ?> + 0 ) : ?> + slug ), $annual_discount . '%' ) ?> + +
      +
    + get_actions_dropdown( $api, $plan ) ?> +
    + has_trial() ) : ?> + get_trial_period( $plan ) ?> +
      +
    • + slug ), $trial_period ) ) ?> +
    • +
    • + slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?> +
    • +
    + +
    +
    +
    + + + +
    +

    slug ) ?>

    +
      + version ) ) { ?> +
    • + slug ); ?> + : version; ?>
    • + author ) ) { + ?> +
    • + slug ); ?> + : author, '_blank' ); ?> +
    • + last_updated ) ) { + ?> +
    • slug ); ?> + : + slug ), + human_time_diff( strtotime( $api->last_updated ) ) + ) ) ?> +
    • + requires ) ) { + ?> +
    • + slug ) ?> + : slug ), $api->requires ) ) ?> +
    • + tested ) ) { + ?> +
    • + slug ); ?> + : tested; ?> +
    • + downloaded ) ) { + ?> +
    • + slug ) ?> + : downloaded ) ? + /* translators: %s: 1 or One (Number of times downloaded) */ + fs_text_inline( '%s time', 'x-time', $api->slug ) : + /* translators: %s: Number of times downloaded */ + fs_text_inline( '%s times', 'x-times', $api->slug ) + ), + number_format_i18n( $api->downloaded ) + ) ); ?> +
    • + slug ) && true == $api->is_wp_org_compliant ) { + ?> +
    • slug ) ?> + » +
    • + homepage ) ) { + ?> +
    • slug ) ?> + » +
    • + donate_link ) && empty( $api->contributors ) ) { + ?> +
    • slug ) ?> + » +
    • + +
    +
    + rating ) ) { ?> +

    slug ); ?>

    + $api->rating, + 'type' => 'percent', + 'number' => $api->num_ratings + ) ); ?> + (slug ), + sprintf( + ( ( 1 == $api->num_ratings ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s rating', 'x-rating', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) + ), + number_format_i18n( $api->num_ratings ) + ) ) ) ?>) + + ratings ) && array_sum( (array) $api->ratings ) > 0 ) { + foreach ( $api->ratings as $key => $ratecount ) { + // Avoid div-by-zero. + $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; + $stars_label = sprintf( + ( ( 1 == $key ) ? + /* translators: %s: 1 or One */ + fs_text_inline( '%s star', 'x-star', $api->slug ) : + /* translators: %s: Number larger than 1 */ + fs_text_inline( '%s stars', 'x-stars', $api->slug ) + ), + number_format_i18n( $key ) + ); + ?> +
    + + + + + +
    + contributors ) ) { + ?> +

    slug ); ?>

    +
      + contributors as $contrib_username => $contrib_profile ) { + if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { + continue; + } + if ( empty( $contrib_username ) ) { + $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); + } + $contrib_username = sanitize_user( $contrib_username ); + if ( empty( $contrib_profile ) ) { + echo "
    • {$contrib_username}
    • "; + } else { + echo "
    • {$contrib_username}
    • "; + } + } + ?> +
    + donate_link ) ) { ?> + slug ) ?> + » + + +
    +
    + tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; + } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { + echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; + } + + foreach ( (array) $api->sections as $section_name => $content ) { + $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); + $content = links_add_target( $content, '_blank' ); + + $san_section = esc_attr( $section_name ); + + $display = ( $section_name === $section ) ? 'block' : 'none'; + + if ( 'description' === $section_name && + ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || + ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) + ) { + $missing_notice = array( + 'type' => 'error', + 'id' => md5( microtime() ), + 'message' => $api->is_paid ? + fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : + fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), + ); + fs_require_template( 'admin-notice.php', $missing_notice ); + } + echo "\t
    \n"; + echo $content; + echo "\t
    \n"; + } + echo "
    \n"; + echo "
    \n"; + echo "
    \n"; // #plugin-information-scrollable + echo "\n"; + ?> + + 'You are just one step away - %s', + * + * We can use the filter: + * fs_override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ), '{plugin_slug}' ); + * + * Or with the Freemius instance: + * + * my_freemius->override_i18n( array( + * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ), + * 'skip' => __( 'Not today', '{your-text_domain}' ), + * ) ); + */ + global $fs_text; + + $fs_text = array( + 'account' => _fs_text( 'Account' ), + 'addon' => _fs_text( 'Add-On' ), + 'contact-us' => _fs_text( 'Contact Us' ), + 'contact-support' => _fs_text( 'Contact Support' ), + 'change-ownership' => _fs_text( 'Change Ownership' ), + 'support' => _fs_text( 'Support' ), + 'support-forum' => _fs_text( 'Support Forum' ), + 'add-ons' => _fs_text( 'Add-Ons' ), + 'upgrade' => _fs_x( 'Upgrade', 'verb' ), + 'awesome' => _fs_text( 'Awesome' ), + 'pricing' => _fs_x( 'Pricing', 'noun' ), + 'price' => _fs_x( 'Price', 'noun' ), + 'unlimited-updates' => _fs_text( 'Unlimited Updates' ), + 'downgrade' => _fs_x( 'Downgrade', 'verb' ), + 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ), + 'cancel-trial' => _fs_text( 'Cancel Trial' ), + 'free-trial' => _fs_text( 'Free Trial' ), + 'start-free-x' => _fs_text( 'Start my free %s' ), + 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ), + 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ), + 'details' => _fs_text( 'Details' ), + 'account-details' => _fs_text( 'Account Details' ), + 'delete' => _fs_x( 'Delete', 'verb' ), + 'show' => _fs_x( 'Show', 'verb' ), + 'hide' => _fs_x( 'Hide', 'verb' ), + 'edit' => _fs_x( 'Edit', 'verb' ), + 'update' => _fs_x( 'Update', 'verb' ), + 'date' => _fs_text( 'Date' ), + 'amount' => _fs_text( 'Amount' ), + 'invoice' => _fs_text( 'Invoice' ), + 'billing' => _fs_text( 'Billing' ), + 'payments' => _fs_text( 'Payments' ), + 'delete-account' => _fs_text( 'Delete Account' ), + 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ), + 'plan' => _fs_x( 'Plan', 'as product pricing plan' ), + 'change-plan' => _fs_text( 'Change Plan' ), + 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ), + 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ), + 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ), + 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ), + 'new' => _fs_text( 'New' ), + 'free' => _fs_text( 'Free' ), + 'trial' => _fs_x( 'Trial', 'as trial plan' ), + 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ), + 'purchase' => _fs_x( 'Purchase', 'verb' ), + 'purchase-license' => _fs_text( 'Purchase License' ), + 'buy' => _fs_x( 'Buy', 'verb' ), + 'buy-license' => _fs_text( 'Buy License' ), + 'license-single-site' => _fs_text( 'Single Site License' ), + 'license-unlimited' => _fs_text( 'Unlimited Licenses' ), + 'license-x-sites' => _fs_text( 'Up to %s Sites' ), + 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ), + 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ), + 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ), + 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ), + 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now', + '%s - plugin name. As complete "Jetpack" activation now' ), + 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ), + 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ), + 'error' => _fs_text( 'Error' ), + 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ), + 'learn-more' => _fs_text( 'Learn more' ), + 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."), + 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'), + + #region Affiliation + 'affiliation' => _fs_text( 'Affiliation' ), + 'affiliate' => _fs_text( 'Affiliate' ), + 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ), + 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ), + 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ), + 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ), + 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ), + 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ), + 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ), + 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ), + 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ), + 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ), + 'program-summary' => _fs_text( 'Program Summary' ), + 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ), + 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ), + 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ), + 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ), + 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ), + 'become-an-affiliate' => _fs_text( 'Become an affiliate' ), + 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ), + 'full-name' => _fs_text( 'Full name' ), + 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ), + 'promotion-methods' => _fs_text( 'Promotion methods' ), + 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ), + 'mobile-apps' => _fs_text( 'Mobile apps' ), + 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ), + 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ), + 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ), + 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ), + 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ), + 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ), + 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ), + 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ), + 'add-another-domain' => _fs_text( 'Add another domain' ), + 'remove' => _fs_x( 'Remove', 'Remove domain' ), + 'email-address-is-required' => _fs_text( 'Email address is required.' ), + 'domain-is-required' => _fs_text( 'Domain is required.' ), + 'invalid-domain' => _fs_text( 'Invalid domain' ), + 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ), + 'processing' => _fs_text( 'Processing...' ), + 'non-expiring' => _fs_text( 'Non-expiring' ), + 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ), + #endregion Affiliation + + #region Account + 'expiration' => _fs_x( 'Expiration', 'as expiration date' ), + 'license' => _fs_x( 'License', 'as software license' ), + 'not-verified' => _fs_text( 'not verified' ), + 'verify-email' => _fs_text( 'Verify Email' ), + 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ), + 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ), + 'no-expiration' => _fs_text( 'No expiration' ), + 'expired' => _fs_text( 'Expired' ), + 'cancelled' => _fs_text( 'Cancelled' ), + 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ), + 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ), + /* translators: %s: Version number (e.g. 4.6 or higher) */ + 'x-or-higher' => _fs_text( '%s or higher' ), + 'version' => _fs_x( 'Version', 'as plugin version' ), + 'name' => _fs_text( 'Name' ), + 'email' => _fs_text( 'Email' ), + 'email-address' => _fs_text( 'Email address' ), + 'verified' => _fs_text( 'Verified' ), + 'module' => _fs_text( 'Module' ), + 'module-type' => _fs_text( 'Module Type' ), + 'plugin' => _fs_text( 'Plugin' ), + 'plugins' => _fs_text( 'Plugins' ), + 'theme' => _fs_text( 'Theme' ), + 'themes' => _fs_text( 'Themes' ), + 'path' => _fs_x( 'Path', 'as file/folder path' ), + 'title' => _fs_text( 'Title' ), + 'free-version' => _fs_text( 'Free version' ), + 'premium-version' => _fs_text( 'Premium version' ), + 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ), + 'id' => _fs_text( 'ID' ), + 'users' => _fs_text( 'Users' ), + 'module-installs' => _fs_text( '%s Installs' ), + 'sites' => _fs_x( 'Sites', 'like websites' ), + 'user-id' => _fs_text( 'User ID' ), + 'site-id' => _fs_text( 'Site ID' ), + 'public-key' => _fs_text( 'Public Key' ), + 'secret-key' => _fs_text( 'Secret Key' ), + 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ), + 'no-id' => _fs_text( 'No ID' ), + 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ), + 'sync' => _fs_x( 'Sync', 'as synchronize' ), + 'activate-license' => _fs_text( 'Activate License' ), + 'activate-free-version' => _fs_text( 'Activate Free Version' ), + 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ), + 'activating-license' => _fs_text( 'Activating license...' ), + 'change-license' => _fs_text( 'Change License' ), + 'update-license' => _fs_text( 'Update License' ), + 'deactivate-license' => _fs_text( 'Deactivate License' ), + 'activate' => _fs_text( 'Activate' ), + 'deactivate' => _fs_text( 'Deactivate' ), + 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ), + 'skip-and-x' => _fs_text( 'Skip & %s' ), + 'no-deactivate' => _fs_text( 'No - just deactivate' ), + 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ), + 'active' => _fs_x( 'Active', 'active mode' ), + 'is-active' => _fs_x( 'Is Active', 'is active mode?' ), + 'install-now' => _fs_text( 'Install Now' ), + 'install-update-now' => _fs_text( 'Install Update Now' ), + 'more-information-about-x' => _fs_text( 'More information about %s' ), + 'localhost' => _fs_text( 'Localhost' ), + 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ), + 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ), + 'last-license' => _fs_text( 'Last license' ), + 'what-is-your-x' => _fs_text( 'What is your %s?' ), + 'activate-this-addon' => _fs_text( 'Activate this add-on' ), + 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ), + 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ), + 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ), + 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ), + 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ), + 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ), + 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ), + 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ), + #endregion Account + + 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ), + 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ), + #region Plugin Deactivation + 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ), + 'quick-feedback' => _fs_text( 'Quick feedback' ), + 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ), + 'deactivating' => _fs_text( 'deactivating' ), + 'deactivation' => _fs_text( 'Deactivation' ), + 'theme-switch' => _fs_text( 'Theme Switch' ), + 'switching' => _fs_text( 'switching' ), + 'switch' => _fs_text( 'Switch' ), + 'activate-x' => _fs_text( 'Activate %s' ), + 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ), + 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ), + 'cancel' => _fs_text( 'Cancel' ), + 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ), + 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ), + 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ), + 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ), + 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ), + 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ), + 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ), + 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ), + 'reason-other' => _fs_x( 'Other', + 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ), + 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ), + 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ), + 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ), + 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ), + 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ), + 'reason-not-working' => _fs_text( 'The %s is not working' ), + 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ), + 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ), + 'placeholder-feature' => _fs_text( 'What feature?' ), + 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ), + 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ), + 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ), + 'reason-didnt-work' => _fs_text( "The %s didn't work" ), + 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ), + 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ), + #endregion Plugin Deactivation + + #region Connect + 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ), + 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ), + 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ), + 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ), + 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ), + 'complete-the-install' => _fs_text( 'complete the install' ), + 'start-the-trial' => _fs_text( 'start the trial' ), + 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ), + 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ), + 'what-permissions' => _fs_text( 'What permissions are being granted?' ), + 'permissions-profile' => _fs_text( 'Your Profile Overview' ), + 'permissions-profile_desc' => _fs_text( 'Name and email address' ), + 'permissions-site' => _fs_text( 'Your Site Overview' ), + 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ), + 'permissions-events' => _fs_text( 'Current %s Events' ), + 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ), + 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ), + 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ), + 'permissions-admin-notices' => _fs_text( 'Admin Notices' ), + 'permissions-newsletter' => _fs_text( 'Newsletter' ), + 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ), + 'privacy-policy' => _fs_text( 'Privacy Policy' ), + 'tos' => _fs_text( 'Terms of Service' ), + 'activating' => _fs_x( 'Activating', 'as activating plugin' ), + 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ), + 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ), + 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ), + 'skip' => _fs_x( 'Skip', 'verb' ), + 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ), + 'resend-activation-email' => _fs_text( 'Re-send activation email' ), + 'license-key' => _fs_text( 'License key' ), + 'send-license-key' => _fs_text( 'Send License Key' ), + 'sending-license-key' => _fs_text( 'Sending license key' ), + 'have-license-key' => _fs_text( 'Have a license key?' ), + 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ), + 'cant-find-license-key' => _fs_text( "Can't find your license key?" ), + 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ), + 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ), + 'opt-in' => _fs_text( 'Opt In' ), + 'opt-out' => _fs_text( 'Opt Out' ), + 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ), + 'opting-out' => _fs_text( 'Opting out...' ), + 'opting-in' => _fs_text( 'Opting in...' ), + 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ), + 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ), + 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ), + 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ), + 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ), + 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ), + 'continue' => _fs_text( 'Continue' ), + 'allow' => _fs_text( 'allow' ), + 'delegate' => _fs_text( 'delegate' ), + #endregion Connect + + #region Screenshots + 'screenshots' => _fs_text( 'Screenshots' ), + 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ), + #endregion Screenshots + + #region Debug + 'freemius-debug' => _fs_text( 'Freemius Debug' ), + 'on' => _fs_x( 'On', 'as turned on' ), + 'off' => _fs_x( 'Off', 'as turned off' ), + 'debugging' => _fs_x( 'Debugging', 'as code debugging' ), + 'freemius-state' => _fs_text( 'Freemius State' ), + 'connected' => _fs_x( 'Connected', 'as connection was successful' ), + 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ), + 'api' => _fs_x( 'API', 'as application program interface' ), + 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ), + 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ), + 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ), + 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ), + 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ), + 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ), + 'actions' => _fs_text( 'Actions' ), + 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ), + 'start-fresh' => _fs_text( 'Start Fresh' ), + 'clear-api-cache' => _fs_text( 'Clear API Cache' ), + 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ), + 'scheduled-crons' => _fs_text( 'Scheduled Crons' ), + 'cron-type' => _fs_text( 'Cron Type' ), + 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ), + 'module-licenses' => _fs_text( '%s Licenses' ), + 'debug-log' => _fs_text( 'Debug Log' ), + 'all' => _fs_text( 'All' ), + 'file' => _fs_text( 'File' ), + 'function' => _fs_text( 'Function' ), + 'process-id' => _fs_text( 'Process ID' ), + 'logger' => _fs_text( 'Logger' ), + 'message' => _fs_text( 'Message' ), + 'download' => _fs_text( 'Download' ), + 'filter' => _fs_text( 'Filter' ), + 'type' => _fs_text( 'Type' ), + 'all-types' => _fs_text( 'All Types' ), + 'all-requests' => _fs_text( 'All Requests' ), + #endregion Debug + + #region Expressions + 'congrats' => _fs_x( 'Congrats', 'as congratulations' ), + 'oops' => _fs_x( 'Oops', 'exclamation' ), + 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ), + 'woot' => _fs_x( 'W00t', + '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ), + 'right-on' => _fs_x( 'Right on', 'a positive response' ), + 'hmm' => _fs_x( 'Hmm', + 'something somebody says when they are thinking about what you have just said. ' ), + 'ok' => _fs_text( 'O.K' ), + 'hey' => _fs_x( 'Hey', 'exclamation' ), + 'heads-up' => _fs_x( 'Heads up', + 'advance notice of something that will need attention.' ), + #endregion Expressions + + #region Admin Notices + 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ), + 'you-are-good' => _fs_text( 'You are all good!' ), + 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ), + 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ), + 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ), + 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ), + 'x-updated' => _fs_text( 'You have successfully updated your %s.' ), + 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ), + 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ), + 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ), + 'no-cc-required' => _fs_text( 'No credit card required' ), + 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ), + 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ), + 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ), + 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ), + 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ), + 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ), + 'download-latest-version' => _fs_text( 'Download the latest version' ), + 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ), + 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ), + 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ), + 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.', + '%s - product name, e.g. Facebook add-on was successfully...' ), + 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ), + 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ), + 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ), + 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ), + 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ), + 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ), + 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ), + 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ), + 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ), + 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ), + 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ), + 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ), + 'server-error-message' => _fs_text( 'Error received from the server:' ), + 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ), + 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ), + 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ), + 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ), + 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ), + 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ), + 'please-download-x' => _fs_text( 'Please download %s.' ), + 'latest-x-version' => _fs_x( 'the latest %s version here', + '%s - plan name, as the latest professional version here' ), + 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ), + 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ), + 'starting-trial' => _fs_text( 'Starting trial' ), + 'please-wait' => _fs_text( 'Please wait' ), + 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ), + 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ), + 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ), + 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ), + 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ), + 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ), + 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ), + 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ), + 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ), + 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ), + 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ), + 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ), + 'contact-us-here' => _fs_text( 'Please contact us here' ), + 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ), + #endregion Admin Notices + #region Connectivity Issues + 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ), + 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ), + 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ), + 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ), + 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ), + 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.', + 'as pluginX requires an access to our API' ), + 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ), + 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ), + 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ), + 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ), + 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ), + 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ), + 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ), + 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ), + 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ), + 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ), + 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ), + 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ), + 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ), + 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ), + 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ), + 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ), + 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', + '%1$s - plugin title, %2$s - API domain' ), + 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ), + #endregion Connectivity Issues + #region Change Owner + 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ), + 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ), + 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ), + #endregion Change Owner + 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.', + 'addonX cannot run without pluginY' ), + 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ), + 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.', + 'pluginX activation was successfully...' ), + 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ), + 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ), + 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ), + #-------------------------------------------------------------------------------- + #region Add-On Licensing + #-------------------------------------------------------------------------------- + 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ), + 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ), + #endregion + #-------------------------------------------------------------------------------- + #region Billing Cycles + #-------------------------------------------------------------------------------- + 'monthly' => _fs_x( 'Monthly', 'as every month' ), + 'mo' => _fs_x( 'mo', 'as monthly period' ), + 'annual' => _fs_x( 'Annual', 'as once a year' ), + 'annually' => _fs_x( 'Annually', 'as once a year' ), + 'once' => _fs_x( 'Once', 'as once a year' ), + 'year' => _fs_x( 'year', 'as annual period' ), + 'lifetime' => _fs_text( 'Lifetime' ), + 'best' => _fs_x( 'Best', 'e.g. the best product' ), + 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ), + 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ), + #endregion Billing Cycles + 'view-details' => _fs_text( 'View details' ), + #-------------------------------------------------------------------------------- + #region Trial + #-------------------------------------------------------------------------------- + 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ), + /* translators: %1$s: Number of trial days; %2$s: Plan name; */ + 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ), + /* translators: %s: Link to freemius.com */ + 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ), + + #endregion + #-------------------------------------------------------------------------------- + #region Billing Details + #-------------------------------------------------------------------------------- + 'business-name' => _fs_text( 'Business name' ), + 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ), + 'address-line-n' => _fs_text( 'Address Line %d' ), + 'country' => _fs_text( 'Country' ), + 'select-country' => _fs_text( 'Select Country' ), + 'city' => _fs_text( 'City' ), + 'town' => _fs_text( 'Town' ), + 'state' => _fs_text( 'State' ), + 'province' => _fs_text( 'Province' ), + 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ), + #endregion + #-------------------------------------------------------------------------------- + #region Module Installation + #-------------------------------------------------------------------------------- + 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ), + 'auto-installation' => _fs_text( 'Automatic Installation' ), + /* translators: %s: Number of seconds */ + 'x-sec' => _fs_text( '%s sec' ), + 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ), + 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ), + 'cancel-installation' => _fs_text( 'Cancel Installation' ), + 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ), + 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ), + 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ), + 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ), + 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ), + 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ), + 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ), + #endregion + + /* translators: %s: Page name */ + 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ), + 'pci-compliant' => _fs_text( 'PCI compliant' ), + 'view-paid-features' => _fs_text( 'View paid features' ), + ); + + /** + * Localization of the strings in the plugin/theme info dialog box. + * + * $fs_module_info_text should ONLY include strings that are not located in $fs_text. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + */ + global $fs_module_info_text; + + $fs_module_info_text = array( + 'description' => _fs_x( 'Description', 'Plugin installer section title' ), + 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ), + 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ), + 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ), + 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ), + 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ), + /* translators: %s: 1 or One */ + 'x-star' => _fs_text( '%s star' ), + /* translators: %s: Number larger than 1 */ + 'x-stars' => _fs_text( '%s stars' ), + /* translators: %s: 1 or One */ + 'x-rating' => _fs_text( '%s rating' ), + /* translators: %s: Number larger than 1 */ + 'x-ratings' => _fs_text( '%s ratings' ), + /* translators: %s: 1 or One (Number of times downloaded) */ + 'x-time' => _fs_text( '%s time' ), + /* translators: %s: Number of times downloaded */ + 'x-times' => _fs_text( '%s times' ), + /* translators: %s: # of stars (e.g. 5 stars) */ + 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ), + 'last-updated:' => _fs_text( 'Last Updated' ), + 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ), + 'author:' => _fs_x( 'Author:', 'as the plugin author' ), + 'compatible-up-to:' => _fs_text( 'Compatible up to:' ), + 'downloaded:' => _fs_text( 'Downloaded:' ), + 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ), + 'plugin-homepage' => _fs_text( 'Plugin Homepage' ), + 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ), + 'average-rating' => _fs_text( 'Average Rating' ), + 'based-on-x' => _fs_text( 'based on %s' ), + 'warning:' => _fs_text( 'Warning:' ), + 'contributors' => _fs_text( 'Contributors' ), + 'plugin-install' => _fs_text( 'Plugin Install' ), + 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ), + 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ), + 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ), + 'latest-installed' => _fs_text( 'Latest Version Installed' ), + ); diff --git a/freemius/includes/index.php b/freemius/includes/index.php index e69de29..0316c6a 100644 --- a/freemius/includes/index.php +++ b/freemius/includes/index.php @@ -0,0 +1,3 @@ + + */ + private $_default_submenu_items; + /** + * @since 1.1.3 + * + * @var string + */ + private $_first_time_path; + /** + * @since 1.2.2 + * + * @var bool + */ + private $_menu_exists; + /** + * @since 2.0.0 + * + * @var bool + */ + private $_network_menu_exists; + + #endregion Properties + + /** + * @var FS_Logger + */ + protected $_logger; + + #region Singleton + + /** + * @var FS_Admin_Menu_Manager[] + */ + private static $_instances = array(); + + /** + * @param number $module_id + * @param string $module_type + * @param string $module_unique_affix + * + * @return FS_Admin_Menu_Manager + */ + static function instance( $module_id, $module_type, $module_unique_affix ) { + $key = 'm_' . $module_id; + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $module_id, $module_type, $module_unique_affix ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_module_id = $module_id; + $this->_module_type = $module_type; + $this->_module_unique_affix = $module_unique_affix; + } + + #endregion Singleton + + #region Helpers + + private function get_option( &$options, $key, $default = false ) { + return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; + } + + private function get_bool_option( &$options, $key, $default = false ) { + return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; + } + + #endregion Helpers + + /** + * @param array $menu + * @param bool $is_addon + */ + function init( $menu, $is_addon = false ) { + $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); + $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); + + $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); + + $this->_default_submenu_items = array(); + // @deprecated + $this->_type = 'page'; + $this->_is_top_level = true; + $this->_is_override_exact = false; + $this->_parent_slug = false; + // @deprecated + $this->_parent_type = 'page'; + + if ( isset( $menu ) ) { + if ( ! $is_addon ) { + $this->_default_submenu_items = array( + 'contact' => $this->get_bool_option( $menu, 'contact', true ), + 'support' => $this->get_bool_option( $menu, 'support', true ), + 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), + 'account' => $this->get_bool_option( $menu, 'account', true ), + 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), + 'addons' => $this->get_bool_option( $menu, 'addons', true ), + ); + + // @deprecated + $this->_type = $this->get_option( $menu, 'type', 'page' ); + } + + $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); + + if ( isset( $menu['parent'] ) ) { + $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); + // @deprecated + $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); + + // If parent's slug is different, then it's NOT a top level menu item. + $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); + } else { + /** + * If no parent then top level if: + * - Has custom admin menu ('page') + * - CPT menu type ('cpt') + */ +// $this->_is_top_level = in_array( $this->_type, array( +// 'cpt', +// 'page' +// ) ); + } + + $first_path = $this->get_option( $menu, 'first-path', false ); + + if ( ! empty( $first_path ) && is_string( $first_path ) ) { + $this->_first_time_path = $first_path; + } + } + } + + /** + * Check if top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_top_level() { + return $this->_is_top_level; + } + + /** + * Check if the page should be override on exact URL match. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool False if submenu item. + */ + function is_override_exact() { + return $this->_is_override_exact; + } + + + /** + * Get the path of the page the user should be forwarded to after first activation. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param bool $is_network Since 2.4.5 + * + * @return string + */ + function get_first_time_path( $is_network = false ) { + if ( empty ( $this->_first_time_path ) ) { + return $this->_first_time_path; + } + + if ( $is_network ) { + return network_admin_url( $this->_first_time_path ); + } else { + return admin_url( $this->_first_time_path ); + } + } + + /** + * Check if plugin's menu item is part of a custom top level menu. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function has_custom_parent() { + return ! $this->_is_top_level && is_string( $this->_parent_slug ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @return bool + */ + function has_menu() { + return $this->_menu_exists; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return bool + */ + function has_network_menu() { + return $this->_network_menu_exists; + } + + /** + * @author Leo Fajardo (@leorw) + * + * @param string $menu_slug + * + * @since 2.1.3 + */ + function set_slug_and_network_menu_exists_flag($menu_slug ) { + $this->_menu_slug = $menu_slug; + $this->_network_menu_exists = false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $id + * @param bool $default + * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. + * + * @return bool + */ + function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { + if ( ! $ignore_menu_existence && ! $this->has_menu() ) { + return false; + } + + return fs_apply_filter( + $this->_module_unique_affix, + 'is_submenu_visible', + $this->get_bool_option( $this->_default_submenu_items, $id, $default ), + $id + ); + } + + /** + * Calculates admin settings menu slug. + * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @param string $page + * + * @return string + */ + function get_slug( $page = '' ) { + return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? + $this->_menu_slug : + $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_slug() { + return $this->_parent_slug; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_type() { + return $this->_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return bool + */ + function is_cpt() { + return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || + // Back compatibility. + 'cpt' === $this->_type + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_parent_type() { + return $this->_parent_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_raw_slug() { + return $this->_menu_slug; + } + + /** + * Get plugin's original menu slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_original_menu_slug() { + if ( 'cpt' === $this->_type ) { + return add_query_arg( array( + 'post_type' => $this->_menu_slug + ), 'edit.php' ); + } + + if ( false === strpos( $this->_menu_slug, '.php?' ) ) { + return $this->_menu_slug; + } else { + return $this->_module_unique_affix; + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.3 + * + * @return string + */ + function get_top_level_menu_slug() { + return $this->has_custom_parent() ? + $this->get_parent_slug() : + $this->get_raw_slug(); + } + + /** + * Is user on plugin's admin activation page. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + * + * @param bool $show_opt_in_on_themes_page Since 2.3.1 + * + * @return bool + * + * @deprecated Please use is_activation_page() instead. + */ + function is_main_settings_page( $show_opt_in_on_themes_page = false ) { + return $this->is_activation_page( $show_opt_in_on_themes_page ); + } + + /** + * Is user on product's admin activation page. + * + * @author Vova Feldman (@svovaf) + * @since 2.3.1 + * + * @param bool $show_opt_in_on_themes_page Since 2.3.1 + * + * @return bool + */ + function is_activation_page( $show_opt_in_on_themes_page = false ) { + if ( $show_opt_in_on_themes_page ) { + /** + * In activation only when show_optin query string param is given. + * + * @since 1.2.2 + */ + return ( + ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && + Freemius::is_themes_page() && + fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ) + ); + } + + if ( $this->_menu_exists && + ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) + ) { + /** + * Module has a settings menu and the context page is the main settings page, so assume it's in + * activation (doesn't really check if already opted-in/skipped or not). + * + * @since 1.2.2 + */ + return true; + } + + return false; + } + + #region Submenu Override + + /** + * Override submenu's action. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.0 + * + * @param string $parent_slug + * @param string $menu_slug + * @param callable $function + * + * @return false|string If submenu exist, will return the hook name. + */ + function override_submenu_action( $parent_slug, $menu_slug, $function ) { + global $submenu; + + $menu_slug = plugin_basename( $menu_slug ); + $parent_slug = plugin_basename( $parent_slug ); + + if ( ! isset( $submenu[ $parent_slug ] ) ) { + // Parent menu not exist. + return false; + } + + $found_submenu_item = false; + foreach ( $submenu[ $parent_slug ] as $submenu_item ) { + if ( $menu_slug === $submenu_item[2] ) { + $found_submenu_item = $submenu_item; + break; + } + } + + if ( false === $found_submenu_item ) { + // Submenu item not found. + return false; + } + + // Remove current function. + $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); + remove_all_actions( $hookname ); + + // Attach new action. + add_action( $hookname, $function ); + + return $hookname; + } + + #endregion Submenu Override + + #region Top level menu Override + + /** + * Find plugin's admin dashboard main menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.2 + * + * @return string[]|false + */ + private function find_top_level_menu() { + global $menu; + + $position = - 1; + $found_menu = false; + + $menu_slug = $this->get_raw_slug(); + + $hook_name = get_plugin_page_hookname( $menu_slug, '' ); + foreach ( $menu as $pos => $m ) { + if ( $menu_slug === $m[2] ) { + $position = $pos; + $found_menu = $m; + break; + } + } + + if ( false === $found_menu ) { + return false; + } + + return array( + 'menu' => $found_menu, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Find plugin's admin dashboard main submenu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return array|false + */ + private function find_main_submenu() { + global $submenu; + + $top_level_menu_slug = $this->get_top_level_menu_slug(); + + if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { + return false; + } + + $submenu_slug = $this->get_raw_slug(); + + $position = - 1; + $found_submenu = false; + + $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); + + foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { + if ( $submenu_slug === $sub[2] ) { + $position = $pos; + $found_submenu = $sub; + } + } + + if ( false === $found_submenu ) { + return false; + } + + return array( + 'menu' => $found_submenu, + 'parent_slug' => $top_level_menu_slug, + 'position' => $position, + 'hook_name' => $hook_name + ); + } + + /** + * Remove all sub-menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @return bool If submenu with plugin's menu slug was found. + */ + private function remove_all_submenu_items() { + global $submenu; + + $menu_slug = $this->get_raw_slug(); + + if ( ! isset( $submenu[ $menu_slug ] ) ) { + return false; + } + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $submenu_ref = &$submenu; + $submenu_ref[ $menu_slug ] = array(); + + return true; + } + + /** + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param bool $remove_top_level_menu + * + * @return false|array[string]mixed + */ + function remove_menu_item( $remove_top_level_menu = false ) { + $this->_logger->entrance(); + + // Find main menu item. + $top_level_menu = $this->find_top_level_menu(); + + if ( false === $top_level_menu ) { + return false; + } + + // Remove it with its actions. + remove_all_actions( $top_level_menu['hook_name'] ); + + // Remove all submenu items. + $this->remove_all_submenu_items(); + + if ( $remove_top_level_menu ) { + global $menu; + unset( $menu[ $top_level_menu['position'] ] ); + } + + return $top_level_menu; + } + + /** + * Get module's main admin setting page URL. + * + * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @return string + */ + function main_menu_url() { + $this->_logger->entrance(); + + if ( $this->_is_top_level ) { + $menu = $this->find_top_level_menu(); + } else { + $menu = $this->find_main_submenu(); + } + + $parent_slug = isset( $menu['parent_slug'] ) ? + $menu['parent_slug'] : + 'admin.php'; + + return admin_url( + $parent_slug . + ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) . + 'page=' . + $menu['menu'][2] + ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.4 + * + * @param callable $function + * + * @return false|array[string]mixed + */ + function override_menu_item( $function ) { + $found_menu = $this->remove_menu_item(); + + if ( false === $found_menu ) { + return false; + } + + if ( ! $this->is_top_level() || ! $this->is_cpt() ) { + $menu_slug = plugin_basename( $this->get_slug() ); + + $hookname = get_plugin_page_hookname( $menu_slug, '' ); + + // Override menu action. + add_action( $hookname, $function ); + } else { + global $menu; + + // Remove original CPT menu. + unset( $menu[ $found_menu['position'] ] ); + + // Create new top-level menu action. + $hookname = self::add_page( + $found_menu['menu'][3], + $found_menu['menu'][0], + 'manage_options', + $this->get_slug(), + $function, + $found_menu['menu'][6], + $found_menu['position'] + ); + } + + return $hookname; + } + + /** + * Adds a counter to the module's top level menu item. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param int $counter + * @param string $class + */ + function add_counter_to_menu_item( $counter = 1, $class = '' ) { + global $menu, $submenu; + + $mask = '%s '; + + /** + * This method is NOT executed for WordPress.org themes. + * Since we maintain only one version of the SDK we added this small + * hack to avoid the error from Theme Check since it's a false-positive. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + */ + $menu_ref = &$menu; + $submenu_ref = &$submenu; + + if ( $this->_is_top_level ) { + // Find main menu item. + $found_menu = $this->find_top_level_menu(); + + if ( false !== $found_menu ) { + // Override menu label. + $menu_ref[ $found_menu['position'] ][0] = sprintf( + $mask, + $found_menu['menu'][0], + $class, + $counter + ); + } + } else { + $found_submenu = $this->find_main_submenu(); + + if ( false !== $found_submenu ) { + // Override menu label. + $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( + $mask, + $found_submenu['menu'][0], + $class, + $counter + ); + } + } + } + + #endregion Top level menu Override + + /** + * Add a top-level menu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_menu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * @param string $icon_url The URL to the icon to be used for this menu. + * * Pass a base64-encoded SVG using a data URI, which will be colored to + * match the color scheme. This should begin with + * 'data:image/svg+xml;base64,'. + * * Pass the name of a Dashicons helper class to use a font icon, + * e.g. 'dashicons-chart-pie'. + * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added + * via CSS. + * @param int $position The position in the menu order this one should appear. + * + * @return string The resulting page's hook_suffix. + */ + static function add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $fn = 'add_menu' . '_page'; + + return $fn( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * @param string $icon_url + * @param int|null $position + * + * @return string + */ + function add_page_and_update( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '', + $icon_url = '', + $position = null + ) { + $this->_menu_slug = $menu_slug; + $this->_is_top_level = true; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_page( + $page_title, + $menu_title, + $capability, + $menu_slug, + $function, + $icon_url, + $position + ); + } + + /** + * Add a submenu page. + * + * Note for WordPress.org Theme/Plugin reviewer: + * + * This is a replication of `add_submenu_page()` to avoid Theme Check warning. + * + * Why? + * ==== + * Freemius is an SDK for plugin and theme developers. Since the core + * of the SDK is relevant both for plugins and themes, for obvious reasons, + * we only develop and maintain one code base. + * + * This method will not run for wp.org themes (only plugins) since theme + * admin settings/options are now only allowed in the customizer. + * + * If you have any questions or need clarifications, please don't hesitate + * pinging me on slack, my username is @svovaf. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2 + * + * @param string $parent_slug The slug name for the parent menu (or the file name of a standard + * WordPress admin page). + * @param string $page_title The text to be displayed in the title tags of the page when the menu is + * selected. + * @param string $menu_title The text to be used for the menu. + * @param string $capability The capability required for this menu to be displayed to the user. + * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). + * @param callable|string $function The function to be called to output the content for this page. + * + * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability + * required. + */ + static function add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $fn = 'add_submenu' . '_page'; + + return $fn( $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + + /** + * Add sub page and update menu instance settings. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $parent_slug + * @param string $page_title + * @param string $menu_title + * @param string $capability + * @param string $menu_slug + * @param callable|string $function + * + * @return string + */ + function add_subpage_and_update( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function = '' + ) { + $this->_menu_slug = $menu_slug; + $this->_parent_slug = $parent_slug; + $this->_is_top_level = false; + $this->_menu_exists = true; + $this->_network_menu_exists = true; + + return self::add_subpage( + $parent_slug, + $page_title, + $menu_title, + $capability, + $menu_slug, + $function + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-admin-notice-manager.php b/freemius/includes/managers/class-fs-admin-notice-manager.php index e69de29..b238d9b 100644 --- a/freemius/includes/managers/class-fs-admin-notice-manager.php +++ b/freemius/includes/managers/class-fs-admin-notice-manager.php @@ -0,0 +1,477 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Admin_Notice_Manager( + $id, + $title, + $module_unique_affix, + $is_network_and_blog_admins, + $network_level_or_blog_id + ); + } + + return self::$_instances[ $key ]; + } + + /** + * @param string $id + * @param string $title + * @param string $module_unique_affix + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and + * blog admin pages. + * @param bool|int $network_level_or_blog_id + */ + protected function __construct( + $id, + $title = '', + $module_unique_affix = '', + $is_network_and_blog_admins = false, + $network_level_or_blog_id = false + ) { + $this->_id = $id; + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_title = ! empty( $title ) ? $title : ''; + $this->_module_unique_affix = $module_unique_affix; + $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); + + if ( is_multisite() ) { + $this->_is_network_notices = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_notices = false; + } + + $is_network_admin = fs_is_network_admin(); + $is_blog_admin = fs_is_blog_admin(); + + if ( ( $this->_is_network_notices && $is_network_admin ) || + ( ! $this->_is_network_notices && $is_blog_admin ) || + ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) + ) { + if ( 0 < count( $this->_sticky_storage ) ) { + $ajax_action_suffix = str_replace( ':', '-', $this->_id ); + + // If there are sticky notices for the current slug, add a callback + // to the AJAX action that handles message dismiss. + add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( + &$this, + 'dismiss_notice_ajax_callback' + ) ); + + foreach ( $this->_sticky_storage as $msg ) { + // Add admin notice. + $this->add( + $msg['message'], + $msg['title'], + $msg['type'], + true, + $msg['id'], + false, + isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, + ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, + $is_network_and_blog_admins + ); + } + } + } + } + + /** + * Remove sticky message by ID. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + */ + function dismiss_notice_ajax_callback() { + check_admin_referer( 'fs_dismiss_notice_action' ); + + if ( ! is_numeric( $_POST['message_id'] ) ) { + $this->_sticky_storage->remove( $_POST['message_id'] ); + } + + wp_die(); + } + + /** + * Rendered sticky message dismiss JavaScript. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + static function _add_sticky_dismiss_javascript() { + $params = array(); + fs_require_once_template( 'sticky-admin-notice-js.php', $params ); + } + + private static $_added_sticky_javascript = false; + + /** + * Hook to the admin_footer to add sticky message dismiss JavaScript handler. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + private static function has_sticky_messages() { + if ( ! self::$_added_sticky_javascript ) { + add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); + } + } + + /** + * Handle admin_notices by printing the admin messages stacked in the queue. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + */ + function _admin_notices_hook() { + if ( function_exists( 'current_user_can' ) && + ! current_user_can( 'manage_options' ) + ) { + // Only show messages to admins. + return; + } + + + $show_admin_notices = ( ! $this->is_gutenberg_page() ); + + foreach ( $this->_notices as $id => $msg ) { + if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { + if ( get_current_user_id() != $msg['wp_user_id'] ) { + continue; + } + } + + /** + * Added a filter to control the visibility of admin notices. + * + * Usage example: + * + * /** + * * @param bool $show + * * @param array $msg { + * * @var string $message The actual message. + * * @var string $title An optional message title. + * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). + * * @var string $id The unique identifier of the message. + * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. + * * @var string $plugin The product's title. + * * @var string $wp_user_id An optional WP user ID that this admin notice is for. + * * } + * * + * * @return bool + * *\/ + * function my_custom_show_admin_notice( $show, $msg ) { + * if ('trial_promotion' != $msg['id']) { + * return false; + * } + * + * return $show; + * } + * + * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); + * + * @author Vova Feldman + * @since 2.2.0 + */ + $show_notice = call_user_func_array( 'fs_apply_filter', array( + $this->_module_unique_affix, + 'show_admin_notice', + $show_admin_notices, + $msg + ) ); + + if ( true !== $show_notice ) { + continue; + } + + fs_require_template( 'admin-notice.php', $msg ); + + if ( $msg['sticky'] ) { + self::has_sticky_messages(); + } + } + } + + /** + * Enqueue common stylesheet to style admin notice. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function _enqueue_styles() { + fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); + } + + /** + * Check if the current page is the Gutenberg block editor. + * + * @author Vova Feldman (@svovaf) + * @since 2.2.3 + * + * @return bool + */ + function is_gutenberg_page() { + if ( function_exists( 'is_gutenberg_page' ) && + is_gutenberg_page() + ) { + // The Gutenberg plugin is on. + return true; + } + + $current_screen = get_current_screen(); + + if ( method_exists( $current_screen, 'is_block_editor' ) && + $current_screen->is_block_editor() + ) { + // Gutenberg page on 5+. + return true; + } + + return false; + } + + /** + * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.4 + * + * @param string $message + * @param string $title + * @param string $type + * @param bool $is_sticky + * @param string $id Message ID + * @param bool $store_if_sticky + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + * + * @uses add_action() + */ + function add( + $message, + $title = '', + $type = 'success', + $is_sticky = false, + $id = '', + $store_if_sticky = true, + $wp_user_id = null, + $plugin_title = null, + $is_network_and_blog_admins = false + ) { + $notices_type = $this->get_notices_type(); + + if ( empty( $this->_notices ) ) { + if ( ! $is_network_and_blog_admins ) { + add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); + } else { + add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); + add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); + } + + add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); + } + + if ( '' === $id ) { + $id = md5( $title . ' ' . $message . ' ' . $type ); + } + + $message_object = array( + 'message' => $message, + 'title' => $title, + 'type' => $type, + 'sticky' => $is_sticky, + 'id' => $id, + 'manager_id' => $this->_id, + 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), + 'wp_user_id' => $wp_user_id, + ); + + if ( $is_sticky && $store_if_sticky ) { + $this->_sticky_storage->{$id} = $message_object; + } + + $this->_notices[ $id ] = $message_object; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string|string[] $ids + */ + function remove_sticky( $ids ) { + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + foreach ( $ids as $id ) { + // Remove from sticky storage. + $this->_sticky_storage->remove( $id ); + + if ( isset( $this->_notices[ $id ] ) ) { + unset( $this->_notices[ $id ] ); + } + } + } + + /** + * Check if sticky message exists by id. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param $id + * + * @return bool + */ + function has_sticky( $id ) { + return isset( $this->_sticky_storage[ $id ] ); + } + + /** + * Adds sticky admin notification. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $message + * @param string $id Message ID + * @param string $title + * @param string $type + * @param number|null $wp_user_id + * @param string|null $plugin_title + * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network + * and blog admin pages. + */ + function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false ) { + if ( ! empty( $this->_module_unique_affix ) ) { + $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); + $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); + } + + $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins ); + } + + /** + * Clear all sticky messages. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + function clear_all_sticky() { + $this->_sticky_storage->clear_all(); + } + + #-------------------------------------------------------------------------------- + #region Helper Method + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_notices_type() { + return $this->_is_network_notices ? + 'network_admin_notices' : + 'admin_notices'; + } + + #endregion + } diff --git a/freemius/includes/managers/class-fs-cache-manager.php b/freemius/includes/managers/class-fs-cache-manager.php index e69de29..7f2d850 100644 --- a/freemius/includes/managers/class-fs-cache-manager.php +++ b/freemius/includes/managers/class-fs-cache-manager.php @@ -0,0 +1,326 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_options = FS_Option_Manager::get_manager( $id, true, true, false ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param $id + * + * @return FS_Cache_Manager + */ + static function get_manager( $id ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_MANAGERS[ $id ] ) ) { + self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); + } + + return self::$_MANAGERS[ $id ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @return bool + */ + function is_empty() { + $this->_logger->entrance(); + + return $this->_options->is_empty(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + */ + function clear() { + $this->_logger->entrance(); + + $this->_options->clear( true ); + } + + /** + * Delete cache manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_options->delete(); + } + + /** + * Check if there's a cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * + * @return bool + */ + function has( $key ) { + $cache_entry = $this->_options->get_option( $key, false ); + + return ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ); + } + + /** + * Check if there's a valid cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param null|int $expiration Since 1.2.2.7 + * + * @return bool + */ + function has_valid( $key, $expiration = null ) { + $cache_entry = $this->_options->get_option( $key, false ); + + $is_valid = ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ); + + if ( $is_valid && + is_numeric( $expiration ) && + isset( $cache_entry->created ) && + is_numeric( $cache_entry->created ) && + $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME + ) { + /** + * Even if the cache is still valid, since we are checking for validity + * with an explicit expiration period, if the period has past, return + * `false` as if the cache is invalid. + * + * @since 1.2.2.7 + */ + $is_valid = false; + } + + return $is_valid; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + function get_valid( $key, $default = null ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->result; + } + + return is_object( $default ) ? clone $default : $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + * @param mixed $value + * @param int $expiration + * @param int $created Since 2.0.0 Cache creation date. + */ + function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = new stdClass(); + + $cache_entry->result = $value; + $cache_entry->created = $created; + $cache_entry->timestamp = $created + $expiration; + $this->_options->set_option( $key, $cache_entry, true ); + } + + /** + * Get cached record expiration, or false if not cached or expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * @param string $key + * + * @return bool|int + */ + function get_record_expiration( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) && + $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME + ) { + return $cache_entry->timestamp; + } + + return false; + } + + /** + * Purge cached item. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.6 + * + * @param string $key + */ + function purge( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $this->_options->unset_option( $key, true ); + } + + /** + * Extend cached item caching period. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @param string $key + * @param int $expiration + * + * @return bool + */ + function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( ! is_object( $cache_entry ) || + ! isset( $cache_entry->timestamp ) || + ! is_numeric( $cache_entry->timestamp ) + ) { + return false; + } + + $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); + + return true; + } + + /** + * Set cached item as expired. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.7 + * + * @param string $key + */ + function expire( $key ) { + $this->_logger->entrance( 'key = ' . $key ); + + $cache_entry = $this->_options->get_option( $key, false ); + + if ( is_object( $cache_entry ) && + isset( $cache_entry->timestamp ) && + is_numeric( $cache_entry->timestamp ) + ) { + // Set to expired. + $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; + $this->_options->set_option( $key, $cache_entry, true ); + } + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $this->_options->migrate_to_network(); + } + + #endregion + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-gdpr-manager.php b/freemius/includes/managers/class-fs-gdpr-manager.php index e69de29..a64abb0 100644 --- a/freemius/includes/managers/class-fs-gdpr-manager.php +++ b/freemius/includes/managers/class-fs-gdpr-manager.php @@ -0,0 +1,202 @@ +_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); + $this->_wp_user_id = Freemius::get_current_wp_user_id(); + $this->_option_name = "u{$this->_wp_user_id}"; + $this->_data = $this->_storage->get_option( $this->_option_name, array() ); + $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); + + if ( ! is_array( $this->_data ) ) { + $this->_data = array(); + } + } + + /** + * Update a GDPR option for the current admin and store it. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param string $name + * @param mixed $value + */ + private function update_option( $name, $value ) { + $this->_data[ $name ] = $value; + + $this->_storage->set_option( $this->_option_name, $this->_data, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @return bool|null + */ + public function is_required() { + return isset( $this->_data['required'] ) ? + $this->_data['required'] : + null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + * + * @param bool $is_required + */ + public function store_is_required( $is_required ) { + $this->update_option( 'required', $is_required ); + } + + /** + * Checks if the GDPR opt-in sticky notice is currently shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function is_opt_in_notice_shown() { + return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + } + + /** + * Remove the GDPR opt-in sticky notice. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function remove_opt_in_notice() { + $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); + + $this->disable_opt_in_notice(); + } + + /** + * Prevents the opt-in message from being added/shown. + * + * @author Leo Fajardo (@leorw) + * @since 2.1.0 + */ + public function disable_opt_in_notice() { + $this->update_option( 'show_opt_in_notice', false ); + } + + /** + * Checks if a GDPR opt-in message needs to be shown to the current admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function should_show_opt_in_notice() { + return ( + ! isset( $this->_data['show_opt_in_notice'] ) || + true === $this->_data['show_opt_in_notice'] + ); + } + + /** + * Get the last time the GDPR opt-in notice was shown. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return false|int + */ + public function last_time_notice_was_shown() { + return isset( $this->_data['notice_shown_at'] ) ? + $this->_data['notice_shown_at'] : + false; + } + + /** + * Update the timestamp of the last time the GDPR opt-in message was shown to now. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function notice_was_just_shown() { + $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); + } + + /** + * @param string $message + * @param string|null $plugin_title + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { + $this->_notices->add_sticky( + $message, + "gdpr_optin_actions_{$this->_wp_user_id}", + '', + 'promotion', + true, + $this->_wp_user_id, + $plugin_title, + true + ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-key-value-storage.php b/freemius/includes/managers/class-fs-key-value-storage.php index e69de29..713df6f 100644 --- a/freemius/includes/managers/class-fs-key-value-storage.php +++ b/freemius/includes/managers/class-fs-key-value-storage.php @@ -0,0 +1,392 @@ + 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_instances[ $key ] ) ) { + self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); + } + + return self::$_instances[ $key ]; + } + + protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_id = $id; + $this->_secondary_id = $secondary_id; + + if ( is_multisite() ) { + $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_multisite_storage = false; + } + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( + WP_FS__ACCOUNTS_OPTION_NAME, + true, + $this->_is_multisite_storage ? + true : + ( $this->_blog_id > 0 ? $this->_blog_id : false ) + ); + } + + protected function get_all_data() { + return $this->get_option_manager()->get_option( $this->_id, array() ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + */ + function load() { + $all_plugins_data = $this->get_all_data(); + $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? + $all_plugins_data[ $this->_secondary_id ] : + array(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $value + * @param bool $flush + */ + function store( $key, $value, $flush = true ) { + if ( $this->_logger->is_on() ) { + $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); + } + + if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { + // No need to store data if the value wasn't changed. + return; + } + + $all_data = $this->get_all_data(); + + $this->_data[ $key ] = $value; + + $all_data[ $this->_secondary_id ] = $this->_data; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, $flush ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function save() { + $this->get_option_manager()->store(); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param bool $store + * @param string[] $exceptions Set of keys to keep and not clear. + */ + function clear_all( $store = true, $exceptions = array() ) { + $new_data = array(); + foreach ( $exceptions as $key ) { + if ( isset( $this->_data[ $key ] ) ) { + $new_data[ $key ] = $this->_data[ $key ]; + } + } + + $this->_data = $new_data; + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * Delete key-value storage. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $this->_data = array(); + + $all_data = $this->get_all_data(); + unset( $all_data[ $this->_secondary_id ] ); + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param bool $store + */ + function remove( $key, $store = true ) { + if ( ! array_key_exists( $key, $this->_data ) ) { + return; + } + + unset( $this->_data[ $key ] ); + + if ( $store ) { + $all_data = $this->get_all_data(); + $all_data[ $this->_secondary_id ] = $this->_data; + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_id, $all_data, true ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param string $key + * @param mixed $default + * + * @return bool|\FS_Plugin + */ + function get( $key, $default = false ) { + return array_key_exists( $key, $this->_data ) ? + $this->_data[ $key ] : + $default; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + function get_secondary_id() { + return $this->_secondary_id; + } + + + /* ArrayAccess + Magic Access (better for refactoring) + -----------------------------------------------------------------------------------*/ + function __set( $k, $v ) { + $this->store( $k, $v ); + } + + function __isset( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function __unset( $k ) { + $this->remove( $k ); + } + + function __get( $k ) { + return $this->get( $k, null ); + } + + function offsetSet( $k, $v ) { + if ( is_null( $k ) ) { + throw new Exception( 'Can\'t append value to request params.' ); + } else { + $this->{$k} = $v; + } + } + + function offsetExists( $k ) { + return array_key_exists( $k, $this->_data ); + } + + function offsetUnset( $k ) { + unset( $this->$k ); + } + + function offsetGet( $k ) { + return $this->get( $k, null ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the current element + * + * @link http://php.net/manual/en/iterator.current.php + * @return mixed Can return any type. + */ + public function current() { + return current( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Move forward to next element + * + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + */ + public function next() { + next( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Return the key of the current element + * + * @link http://php.net/manual/en/iterator.key.php + * @return mixed scalar on success, or null on failure. + */ + public function key() { + return key( $this->_data ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Checks if current position is valid + * + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() { + $key = key( $this->_data ); + + return ( $key !== null && $key !== false ); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Rewind the Iterator to the first element + * + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + */ + public function rewind() { + reset( $this->_data ); + } + + /** + * (PHP 5 >= 5.1.0)
    + * Count elements of an object + * + * @link http://php.net/manual/en/countable.count.php + * @return int The custom count as an integer. + *

    + *

    + * The return value is cast to an integer. + */ + public function count() { + return count( $this->_data ); + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-license-manager.php b/freemius/includes/managers/class-fs-license-manager.php index e69de29..891ecd8 100644 --- a/freemius/includes/managers/class-fs-license-manager.php +++ b/freemius/includes/managers/class-fs-license-manager.php @@ -0,0 +1,104 @@ +get_slug() ); +// +// if ( ! isset( self::$_instances[ $slug ] ) ) { +// self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); +// } +// +// return self::$_instances[ $slug ]; +// } +// +//// private function __construct($slug) { +//// parent::__construct($slug); +//// } +// +// function entry_id() { +// return 'licenses'; +// } +// +// function sync( $id ) { +// +// } +// +// /** +// * @author Vova Feldman (@svovaf) +// * @since 1.0.5 +// * @uses FS_Api +// * +// * @param number|bool $plugin_id +// * +// * @return FS_Plugin_License[]|stdClass Licenses or API error. +// */ +// function api_get_user_plugin_licenses( $plugin_id = false ) { +// $api = $this->_fs->get_api_user_scope(); +// +// if ( ! is_numeric( $plugin_id ) ) { +// $plugin_id = $this->_fs->get_id(); +// } +// +// $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); +// +// if ( ! isset( $result->error ) ) { +// for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { +// $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); +// } +// +// $result = $result->licenses; +// } +// +// return $result; +// } +// +// function api_get_many() { +// +// } +// +// function api_activate( $id ) { +// +// } +// +// function api_deactivate( $id ) { +// +// } + + /** + * @param FS_Plugin_License[] $licenses + * + * @return bool + */ + static function has_premium_license( $licenses ) { + if ( is_array( $licenses ) ) { + foreach ( $licenses as $license ) { + /** + * @var FS_Plugin_License $license + */ + if ( ! $license->is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-option-manager.php b/freemius/includes/managers/class-fs-option-manager.php index e69de29..9d59abf 100644 --- a/freemius/includes/managers/class-fs-option-manager.php +++ b/freemius/includes/managers/class-fs-option-manager.php @@ -0,0 +1,521 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + + $this->_logger->entrance(); + $this->_logger->log( 'id = ' . $id ); + + $this->_id = $id; + + $this->_autoload = $autoload; + + if ( is_multisite() ) { + $this->_is_network_storage = ( true === $network_level_or_blog_id ); + + if ( is_numeric( $network_level_or_blog_id ) ) { + $this->_blog_id = $network_level_or_blog_id; + } + } else { + $this->_is_network_storage = false; + } + + if ( $load ) { + $this->load(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $id + * @param bool $load + * @param bool|int $network_level_or_blog_id Since 2.0.0 + * @param bool|null $autoload + * + * @return \FS_Option_Manager + */ + static function get_manager( + $id, + $load = false, + $network_level_or_blog_id = false, + $autoload = null + ) { + $key = strtolower( $id ); + + if ( is_multisite() ) { + if ( true === $network_level_or_blog_id ) { + $key .= ':ms'; + } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { + $key .= ":{$network_level_or_blog_id}"; + } else { + $network_level_or_blog_id = get_current_blog_id(); + + $key .= ":{$network_level_or_blog_id}"; + } + } + + if ( ! isset( self::$_MANAGERS[ $key ] ) ) { + self::$_MANAGERS[ $key ] = new FS_Option_Manager( + $id, + $load, + $network_level_or_blog_id, + $autoload + ); + } // If load required but not yet loaded, load. + else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { + self::$_MANAGERS[ $key ]->load(); + } + + return self::$_MANAGERS[ $key ]; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param bool $flush + */ + function load( $flush = false ) { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $flush || ! isset( $this->_options ) ) { + if ( isset( $this->_options ) ) { + // Clear prev options. + $this->clear(); + } + + $cache_group = $this->get_cache_group(); + + if ( WP_FS__DEBUG_SDK ) { + + // Don't use cache layer in DEBUG mode. + $load_options = empty( $this->_options ); + + } else { + + $this->_options = wp_cache_get( + $option_name, + $cache_group + ); + + $load_options = ( false === $this->_options ); + } + + $cached = true; + + if ( $load_options ) { + if ( $this->_is_network_storage ) { + $this->_options = get_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + $this->_options = get_blog_option( $this->_blog_id, $option_name ); + } else { + $this->_options = get_option( $option_name ); + } + + if ( is_string( $this->_options ) ) { + $this->_options = json_decode( $this->_options ); + } + +// $this->_logger->info('get_option = ' . var_export($this->_options, true)); + + if ( false === $this->_options ) { + $this->clear(); + } + + $cached = false; + } + + if ( ! WP_FS__DEBUG_SDK && ! $cached ) { + // Set non encoded cache. + wp_cache_set( $option_name, $this->_options, $cache_group ); + } + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_loaded() { + return isset( $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return bool + */ + function is_empty() { + return ( $this->is_loaded() && false === $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool $flush + */ + function clear( $flush = false ) { + $this->_logger->entrance(); + + $this->_options = array(); + + if ( $flush ) { + $this->store(); + } + } + + /** + * Delete options manager from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + */ + function delete() { + $option_name = $this->get_option_manager_name(); + + if ( $this->_is_network_storage ) { + delete_site_option( $option_name ); + } else if ( $this->_blog_id > 0 ) { + delete_blog_option( $this->_blog_id, $option_name ); + } else { + delete_option( $option_name ); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param string $option + * + * @return bool + */ + function has_option( $option ) { + return array_key_exists( $option, $this->_options ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $default + * + * @return mixed + */ + function get_option( $option, $default = null ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->load(); + } + + if ( is_array( $this->_options ) ) { + $value = isset( $this->_options[ $option ] ) ? + $this->_options[ $option ] : + $default; + } else if ( is_object( $this->_options ) ) { + $value = isset( $this->_options->{$option} ) ? + $this->_options->{$option} : + $default; + } else { + $value = $default; + } + + /** + * If it's an object, return a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = $options->get_option( 'object1' ); + * $object1->x = 123; + * + * $object2 = $options->get_option( 'object2' ); + * $object2->y = 'dummy'; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't return a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' which is quite not + * an expected behaviour. + * + * @author Vova Feldman + */ + return is_object( $value ) ? clone $value : $value; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param mixed $value + * @param bool $flush + */ + function set_option( $option, $value, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( ! $this->is_loaded() ) { + $this->clear(); + } + + /** + * If it's an object, store a clone of the object, otherwise, + * external changes of the object will actually change the value + * of the object in the options manager which may lead to an unexpected + * behaviour and data integrity when a store() call is triggered. + * + * Example: + * $object1 = new stdClass(); + * $object1->x = 123; + * + * $options->set_option( 'object1', $object1 ); + * + * $object1->x = 456; + * + * $options->set_option( 'object2', $object2, true ); + * + * If we don't set the option as a clone of option 'object1', setting 'object2' + * will also store the updated value of 'object1' ($object1->x = 456 instead of + * $object1->x = 123) which is quite not an expected behaviour. + * + * @author Vova Feldman + */ + $copy = is_object( $value ) ? clone $value : $value; + + if ( is_array( $this->_options ) ) { + $this->_options[ $option ] = $copy; + } else if ( is_object( $this->_options ) ) { + $this->_options->{$option} = $copy; + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Unset option. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @param string $option + * @param bool $flush + */ + function unset_option( $option, $flush = false ) { + $this->_logger->entrance( 'option = ' . $option ); + + if ( is_array( $this->_options ) ) { + if ( ! isset( $this->_options[ $option ] ) ) { + return; + } + + unset( $this->_options[ $option ] ); + + } else if ( is_object( $this->_options ) ) { + if ( ! isset( $this->_options->{$option} ) ) { + return; + } + + unset( $this->_options->{$option} ); + } + + if ( $flush ) { + $this->store(); + } + } + + /** + * Dump options to database. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + */ + function store() { + $this->_logger->entrance(); + + $option_name = $this->get_option_manager_name(); + + if ( $this->_logger->is_on() ) { + $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); + } + + // Update DB. + if ( $this->_is_network_storage ) { + update_site_option( $option_name, $this->_options ); + } else if ( $this->_blog_id > 0 ) { + update_blog_option( $this->_blog_id, $option_name, $this->_options ); + } else { + update_option( $option_name, $this->_options, $this->_autoload ); + } + + if ( ! WP_FS__DEBUG_SDK ) { + wp_cache_set( $option_name, $this->_options, $this->get_cache_group() ); + } + } + + /** + * Get options keys. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.3 + * + * @return string[] + */ + function get_options_keys() { + if ( is_array( $this->_options ) ) { + return array_keys( $this->_options ); + } else if ( is_object( $this->_options ) ) { + return array_keys( get_object_vars( $this->_options ) ); + } + + return array(); + } + + #-------------------------------------------------------------------------------- + #region Migration + #-------------------------------------------------------------------------------- + + /** + * Migrate options from site level. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + function migrate_to_network() { + $site_options = FS_Option_Manager::get_manager($this->_id, true, false); + + $options = is_object( $site_options->_options ) ? + get_object_vars( $site_options->_options ) : + $site_options->_options; + + if ( ! empty( $options ) ) { + foreach ( $options as $key => $val ) { + $this->set_option( $key, $val, false ); + } + + $this->store(); + } + } + + #endregion + + #-------------------------------------------------------------------------------- + #region Helper Methods + #-------------------------------------------------------------------------------- + + /** + * @return string + */ + private function get_option_manager_name() { + return $this->_id; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + * + * @return string + */ + private function get_cache_group() { + $group = WP_FS__SLUG; + + if ( $this->_is_network_storage ) { + $group .= '_ms'; + } else if ( $this->_blog_id > 0 ) { + $group .= "_s{$this->_blog_id}"; + } + + return $group; + } + + #endregion + } diff --git a/freemius/includes/managers/class-fs-plan-manager.php b/freemius/includes/managers/class-fs-plan-manager.php index e69de29..639de43 100644 --- a/freemius/includes/managers/class-fs-plan-manager.php +++ b/freemius/includes/managers/class-fs-plan-manager.php @@ -0,0 +1,162 @@ +is_utilized() && $license->is_features_enabled() ) { + return true; + } + } + } + + return false; + } + + /** + * Check if plugin has any paid plans. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_paid_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return false; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( ! $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Check if plugin has any free plan, or is it premium only. + * + * Note: If no plans configured, assume plugin is free. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.7 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_free_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->is_free() ) { + return true; + } + } + + return false; + } + + /** + * Find all plans that have trial. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return FS_Plugin_Plan[] + */ + function get_trial_plans( $plans ) { + $trial_plans = array(); + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + $trial_plans[] = $plans[ $i ]; + } + } + } + + return $trial_plans; + } + + /** + * Check if plugin has any trial plan. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.9 + * + * @param FS_Plugin_Plan[] $plans + * + * @return bool + */ + function has_trial_plan( $plans ) { + if ( ! is_array( $plans ) || 0 === count( $plans ) ) { + return true; + } + + /** + * @var FS_Plugin_Plan[] $plans + */ + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + if ( $plans[ $i ]->has_trial() ) { + return true; + } + } + + return false; + } + } \ No newline at end of file diff --git a/freemius/includes/managers/class-fs-plugin-manager.php b/freemius/includes/managers/class-fs-plugin-manager.php index e69de29..bacf160 100644 --- a/freemius/includes/managers/class-fs-plugin-manager.php +++ b/freemius/includes/managers/class-fs-plugin-manager.php @@ -0,0 +1,220 @@ +_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); + $this->_module_id = $module_id; + + $this->load(); + } + + protected function get_option_manager() { + return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + * + * @param string|bool $module_type "plugin", "theme", or "false" for all modules. + * + * @return array + */ + protected function get_all_modules( $module_type = false ) { + $option_manager = $this->get_option_manager(); + + if ( false !== $module_type ) { + return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() ); + } + + return array( + self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ), + self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ), + ); + } + + /** + * Load plugin data from local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + */ + function load() { + $all_modules = $this->get_all_modules(); + + if ( ! is_numeric( $this->_module_id ) ) { + unset( $all_modules[ self::OPTION_NAME_THEMES ] ); + } + + foreach ( $all_modules as $modules ) { + /** + * @since 1.2.2 + * + * @var $modules FS_Plugin[] + */ + foreach ( $modules as $module ) { + $found_module = false; + + /** + * If module ID is not numeric, it must be a plugin's slug. + * + * @author Leo Fajardo (@leorw) + * @since 1.2.2 + */ + if ( ! is_numeric( $this->_module_id ) ) { + if ( $this->_module_id === $module->slug ) { + $this->_module_id = $module->id; + $found_module = true; + } + } else if ( $this->_module_id == $module->id ) { + $found_module = true; + } + + if ( $found_module ) { + $this->_module = $module; + break; + } + } + } + } + + /** + * Store plugin on local DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param bool|FS_Plugin $module + * @param bool $flush + * + * @return bool|\FS_Plugin + */ + function store( $module = false, $flush = true ) { + if ( false !== $module ) { + $this->_module = $module; + } + + $all_modules = $this->get_all_modules( $this->_module->type ); + $all_modules[ $this->_module->slug ] = $this->_module; + + $options_manager = $this->get_option_manager(); + $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); + + return $this->_module; + } + + /** + * Update local plugin data if different. + * + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param \FS_Plugin $plugin + * @param bool $store + * + * @return bool True if plugin was updated. + */ + function update( FS_Plugin $plugin, $store = true ) { + if ( ! ($this->_module instanceof FS_Plugin ) || + $this->_module->slug != $plugin->slug || + $this->_module->public_key != $plugin->public_key || + $this->_module->secret_key != $plugin->secret_key || + $this->_module->parent_plugin_id != $plugin->parent_plugin_id || + $this->_module->title != $plugin->title + ) { + $this->store( $plugin, $store ); + + return true; + } + + return false; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @param FS_Plugin $plugin + * @param bool $store + */ + function set( FS_Plugin $plugin, $store = false ) { + $this->_module = $plugin; + + if ( $store ) { + $this->store(); + } + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.6 + * + * @return bool|\FS_Plugin + */ + function get() { + return isset( $this->_module ) ? + $this->_module : + false; + } + + + } \ No newline at end of file diff --git a/freemius/includes/managers/index.php b/freemius/includes/managers/index.php index e69de29..0316c6a 100644 --- a/freemius/includes/managers/index.php +++ b/freemius/includes/managers/index.php @@ -0,0 +1,3 @@ +_result = $result; + + $code = 0; + $message = 'Unknown error, please check GetResult().'; + $type = ''; + + if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { + if ( isset( $result['error']['code'] ) ) { + $code = $result['error']['code']; + } + if ( isset( $result['error']['message'] ) ) { + $message = $result['error']['message']; + } + if ( isset( $result['error']['type'] ) ) { + $type = $result['error']['type']; + } + } + + $this->_type = $type; + $this->_code = $code; + + parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); + } + + /** + * Return the associated result object returned by the API server. + * + * @return array The result from the API server + */ + public function getResult() { + return $this->_result; + } + + public function getStringCode() { + return $this->_code; + } + + public function getType() { + return $this->_type; + } + + /** + * To make debugging easier. + * + * @return string The string representation of the error + */ + public function __toString() { + $str = $this->getType() . ': '; + + if ( $this->code != 0 ) { + $str .= $this->getStringCode() . ': '; + } + + return $str . $this->getMessage(); + } + } + } diff --git a/freemius/includes/sdk/Exceptions/InvalidArgumentException.php b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php index e69de29..4840d9e 100644 --- a/freemius/includes/sdk/Exceptions/InvalidArgumentException.php +++ b/freemius/includes/sdk/Exceptions/InvalidArgumentException.php @@ -0,0 +1,12 @@ +_id = $pID; + $this->_public = $pPublic; + $this->_secret = $pSecret; + $this->_scope = $pScope; + $this->_isSandbox = $pIsSandbox; + } + + public function IsSandbox() { + return $this->_isSandbox; + } + + function CanonizePath( $pPath ) { + $pPath = trim( $pPath, '/' ); + $query_pos = strpos( $pPath, '?' ); + $query = ''; + + if ( false !== $query_pos ) { + $query = substr( $pPath, $query_pos ); + $pPath = substr( $pPath, 0, $query_pos ); + } + + // Trim '.json' suffix. + $format_length = strlen( '.' . self::FORMAT ); + $start = $format_length * ( - 1 ); //negative + if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { + $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); + } + + switch ( $this->_scope ) { + case 'app': + $base = '/apps/' . $this->_id; + break; + case 'developer': + $base = '/developers/' . $this->_id; + break; + case 'user': + $base = '/users/' . $this->_id; + break; + case 'plugin': + $base = '/plugins/' . $this->_id; + break; + case 'install': + $base = '/installs/' . $this->_id; + break; + default: + throw new Freemius_Exception( 'Scope not implemented.' ); + } + + return '/v' . FS_API__VERSION . $base . + ( ! empty( $pPath ) ? '/' : '' ) . $pPath . + ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; + } + + abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); + + /** + * @param string $pPath + * @param string $pMethod + * @param array $pParams + * + * @return object[]|object|null + */ + private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + $pMethod = strtoupper( $pMethod ); + + try { + $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { + return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); + } + + /** + * Base64 decoding that does not need to be urldecode()-ed. + * + * Exactly the same as PHP base64 encode except it uses + * `-` instead of `+` + * `_` instead of `/` + * No padded = + * + * @param string $input Base64UrlEncoded() string + * + * @return string + */ + protected static function Base64UrlDecode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_decode'; + return $fn( strtr( $input, '-_', '+/' ) ); + } + + /** + * Base64 encoding that does not need to be urlencode()ed. + * + * Exactly the same as base64 encode except it uses + * `-` instead of `+ + * `_` instead of `/` + * + * @param string $input string + * + * @return string Base64 encoded string + */ + protected static function Base64UrlEncode( $input ) { + /** + * IMPORTANT NOTE: + * This is a hack suggested by @otto42 and @greenshady from + * the theme's review team. The usage of base64 for API + * signature encoding was approved in a Slack meeting + * held on Tue (10/25 2016). + * + * @todo Remove this hack once the base64 error is removed from the Theme Check. + * + * @since 1.2.2 + * @author Vova Feldman (@svovaf) + */ + $fn = 'base64' . '_encode'; + $str = strtr( $fn( $input ), '+/', '-_' ); + $str = str_replace( '=', '', $str ); + + return $str; + } + } diff --git a/freemius/includes/sdk/FreemiusWordPress.php b/freemius/includes/sdk/FreemiusWordPress.php index e69de29..354273d 100644 --- a/freemius/includes/sdk/FreemiusWordPress.php +++ b/freemius/includes/sdk/FreemiusWordPress.php @@ -0,0 +1,715 @@ + '7.37' ); + + if ( ! defined( 'FS_API__PROTOCOL' ) ) { + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); + } + + if ( ! defined( 'FS_API__LOGGER_ON' ) ) { + define( 'FS_API__LOGGER_ON', false ); + } + + if ( ! defined( 'FS_API__ADDRESS' ) ) { + define( 'FS_API__ADDRESS', '://api.freemius.com' ); + } + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); + } + + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + return; + } + + class Freemius_Api_WordPress extends Freemius_Api_Base { + private static $_logger = array(); + + /** + * @param string $pScope 'app', 'developer', 'user' or 'install'. + * @param number $pID Element's id. + * @param string $pPublic Public key. + * @param string|bool $pSecret Element's secret key. + * @param bool $pSandbox Whether or not to run API in sandbox mode. + */ + public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { + // If secret key not provided, use public key encryption. + if ( is_bool( $pSecret ) ) { + $pSecret = $pPublic; + } + + parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); + } + + public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { + $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); + + if ( ':' === $address[0] ) { + $address = self::$_protocol . $address; + } + + return $address . $pCanonizedPath; + } + + #---------------------------------------------------------------------------------- + #region Servers Clock Diff + #---------------------------------------------------------------------------------- + + /** + * @var int Clock diff in seconds between current server to API server. + */ + private static $_clock_diff = 0; + + /** + * Set clock diff for all API calls. + * + * @since 1.0.3 + * + * @param $pSeconds + */ + public static function SetClockDiff( $pSeconds ) { + self::$_clock_diff = $pSeconds; + } + + /** + * Find clock diff between current server to API server. + * + * @since 1.0.2 + * @return int Clock diff in seconds. + */ + public static function FindClockDiff() { + $time = time(); + $pong = self::Ping(); + + return ( $time - strtotime( $pong->timestamp ) ); + } + + #endregion + + /** + * @var string http or https + */ + private static $_protocol = FS_API__PROTOCOL; + + /** + * Set API connection protocol. + * + * @since 1.0.4 + */ + public static function SetHttp() { + self::$_protocol = 'http'; + } + + /** + * @since 1.0.4 + * + * @return bool + */ + public static function IsHttps() { + return ( 'https' === self::$_protocol ); + } + + /** + * Sign request with the following HTTP headers: + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @param string $pResourceUrl + * @param array $pWPRemoteArgs + * + * @return array + */ + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { + $auth = $this->GenerateAuthorizationParams( + $pResourceUrl, + $pWPRemoteArgs['method'], + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' + ); + + $pWPRemoteArgs['headers']['Date'] = $auth['date']; + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; + + if ( ! empty( $auth['content_md5'] ) ) { + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; + } + + return $pWPRemoteArgs; + } + + /** + * Generate Authorization request headers: + * + * Content-MD5: MD5(HTTP Request body) + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, + * {scope_entity_secret_key})) + * + * @author Vova Feldman + * + * @param string $pResourceUrl + * @param string $pMethod + * @param string $pPostParams + * + * @return array + * @throws Freemius_Exception + */ + function GenerateAuthorizationParams( + $pResourceUrl, + $pMethod = 'GET', + $pPostParams = '' + ) { + $pMethod = strtoupper( $pMethod ); + + $eol = "\n"; + $content_md5 = ''; + $content_type = ''; + $now = ( time() - self::$_clock_diff ); + $date = date( 'r', $now ); + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $content_type = 'application/json'; + + if ( ! empty( $pPostParams ) ) { + $content_md5 = md5( $pPostParams ); + } + } + + $string_to_sign = implode( $eol, array( + $pMethod, + $content_md5, + $content_type, + $date, + $pResourceUrl + ) ); + + // If secret and public keys are identical, it means that + // the signature uses public key hash encoding. + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; + + $auth = array( + 'date' => $date, + 'authorization' => $auth_type . ' ' . $this->_id . ':' . + $this->_public . ':' . + self::Base64UrlEncode( hash_hmac( + 'sha256', $string_to_sign, $this->_secret + ) ) + ); + + if ( ! empty( $content_md5 ) ) { + $auth['content_md5'] = $content_md5; + } + + return $auth; + } + + /** + * Get API request URL signed via query string. + * + * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). + * + * @param string $pPath + * + * @throws Freemius_Exception + * + * @return string + */ + function GetSignedUrl( $pPath ) { + $resource = explode( '?', $this->CanonizePath( $pPath ) ); + $pResourceUrl = $resource[0]; + + $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); + + return Freemius_Api_WordPress::GetUrl( + $pResourceUrl . '?' . + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . + 'authorization=' . urlencode( $auth['authorization'] ) . + '&auth_date=' . urlencode( $auth['date'] ) + , $this->_isSandbox ); + } + + /** + * @author Vova Feldman + * + * @param string $pUrl + * @param array $pWPRemoteArgs + * + * @return mixed + */ + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { + $start = microtime( true ); + + $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); + + if ( FS_API__LOGGER_ON ) { + $end = microtime( true ); + + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); + $is_http_error = is_wp_error( $response ); + + self::$_logger[] = array( + 'id' => count( self::$_logger ), + 'start' => $start, + 'end' => $end, + 'total' => ( $end - $start ), + 'method' => $pWPRemoteArgs['method'], + 'path' => $pUrl, + 'body' => $has_body ? $pWPRemoteArgs['body'] : null, + 'result' => ! $is_http_error ? + $response['body'] : + json_encode( $response->get_error_messages() ), + 'code' => ! $is_http_error ? $response['response']['code'] : null, + 'backtrace' => debug_backtrace(), + ); + } + + return $response; + } + + /** + * @return array + */ + static function GetLogger() { + return self::$_logger; + } + + /** + * @param string $pCanonizedPath + * @param string $pMethod + * @param array $pParams + * @param null|array $pWPRemoteArgs + * @param bool $pIsSandbox + * @param null|callable $pBeforeExecutionFunction + * + * @return object[]|object|null + * + * @throws \Freemius_Exception + */ + private static function MakeStaticRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null, + $pIsSandbox = false, + $pBeforeExecutionFunction = null + ) { + // Connectivity errors simulation. + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { + self::ThrowCloudFlareDDoSException(); + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { + self::ThrowSquidAclException(); + } + + if ( empty( $pWPRemoteArgs ) ) { + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . + home_url(); + + $pWPRemoteArgs = array( + 'method' => strtoupper( $pMethod ), + 'connect_timeout' => 10, + 'timeout' => 60, + 'follow_redirects' => true, + 'redirection' => 5, + 'user-agent' => $user_agent, + 'blocking' => true, + ); + } + + if ( ! isset( $pWPRemoteArgs['headers'] ) || + ! is_array( $pWPRemoteArgs['headers'] ) + ) { + $pWPRemoteArgs['headers'] = array(); + } + + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { + $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; + + if ( is_array( $pParams ) && 0 < count( $pParams ) ) { + $pWPRemoteArgs['body'] = json_encode( $pParams ); + } + } + + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); + + $resource = explode( '?', $pCanonizedPath ); + + if ( FS_SDK__HAS_CURL ) { + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait + // for 2 seconds if the server does not support this header. + $pWPRemoteArgs['headers']['Expect'] = ''; + } + + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { + $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; + } + + if ( false !== $pBeforeExecutionFunction && + is_callable( $pBeforeExecutionFunction ) + ) { + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); + } + + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + + if ( is_wp_error( $result ) ) { + /** + * @var WP_Error $result + */ + if ( self::IsCurlError( $result ) ) { + /** + * With dual stacked DNS responses, it's possible for a server to + * have IPv6 enabled but not have IPv6 connectivity. If this is + * the case, cURL will try IPv4 first and if that fails, then it will + * fall back to IPv6 and the error EHOSTUNREACH is returned by the + * operating system. + */ + $matches = array(); + $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { + /** + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. + * @author Vova Feldman (@svovaf) + */ + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { +// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); + // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); + + // Re-run request. + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); + } + } + } + } + + if ( is_wp_error( $result ) ) { + self::ThrowWPRemoteException( $result ); + } + } + + $response_body = $result['body']; + + if ( empty( $response_body ) ) { + return null; + } + + $decoded = json_decode( $response_body ); + + if ( is_null( $decoded ) ) { + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && + preg_match( '/text\/javascript/', $response_body ) + ) { + self::ThrowCloudFlareDDoSException( $response_body ); + } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && + preg_match( '/squid/', $response_body ) + ) { + self::ThrowSquidAclException( $response_body ); + } else { + $decoded = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $response_body, + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + } + + return $decoded; + } + + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than wp_remote_request() + * to make the request. + * + * @param string $pCanonizedPath The URL to make the request to + * @param string $pMethod HTTP method + * @param array $pParams The parameters to use for the POST body + * @param null|array $pWPRemoteArgs wp_remote_request options. + * + * @return object[]|object|null + * + * @throws Freemius_Exception + */ + public function MakeRequest( + $pCanonizedPath, + $pMethod = 'GET', + $pParams = array(), + $pWPRemoteArgs = null + ) { + $resource = explode( '?', $pCanonizedPath ); + + // Only sign request if not ping.json connectivity test. + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); + + return self::MakeStaticRequest( + $pCanonizedPath, + $pMethod, + $pParams, + $pWPRemoteArgs, + $this->_isSandbox, + $sign_request ? array( &$this, 'SignRequest' ) : null + ); + } + + /** + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter + * + * @param resource $handle A cURL handle returned by curl_init() + * + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to + * CURL_IPRESOLVE_V4 + * + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e + */ + static function CurlResolveToIPv4( $handle ) { + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); + + return $handle; + } + + #---------------------------------------------------------------------------------- + #region Connectivity Test + #---------------------------------------------------------------------------------- + + /** + * If successful connectivity to the API endpoint using ping.json endpoint. + * + * - OR - + * + * Validate if ping result object is valid. + * + * @param mixed $pPong + * + * @return bool + */ + public static function Test( $pPong = null ) { + $pong = is_null( $pPong ) ? + self::Ping() : + $pPong; + + return ( + is_object( $pong ) && + isset( $pong->api ) && + 'pong' === $pong->api + ); + } + + /** + * Ping API to test connectivity. + * + * @return object + */ + public static function Ping() { + try { + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); + } catch ( Freemius_Exception $e ) { + // Map to error object. + $result = (object) $e->getResult(); + } catch ( Exception $e ) { + // Map to error object. + $result = (object) array( + 'error' => (object) array( + 'type' => 'Unknown', + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', + 'code' => 'unknown', + 'http' => 402 + ) + ); + } + + return $result; + } + + #endregion + + #---------------------------------------------------------------------------------- + #region Connectivity Exceptions + #---------------------------------------------------------------------------------- + + /** + * @param \WP_Error $pError + * + * @return bool + */ + private static function IsCurlError( WP_Error $pError ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + return ( 0 === strpos( $message, 'cURL' ) ); + } + + /** + * @param WP_Error $pError + * + * @throws Freemius_Exception + */ + private static function ThrowWPRemoteException( WP_Error $pError ) { + if ( self::IsCurlError( $pError ) ) { + $message = $pError->get_error_message( 'http_request_failed' ); + + #region Check if there are any missing cURL methods. + + $curl_required_methods = array( + 'curl_version', + 'curl_exec', + 'curl_init', + 'curl_close', + 'curl_setopt', + 'curl_setopt_array', + 'curl_error', + ); + + // Find all missing methods. + $missing_methods = array(); + foreach ( $curl_required_methods as $m ) { + if ( ! function_exists( $m ) ) { + $missing_methods[] = $m; + } + } + + if ( ! empty( $missing_methods ) ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'cUrlMissing', + 'message' => $message, + 'code' => 'curl_missing', + 'http' => 402 + ), + 'missing_methods' => $missing_methods, + ) ); + } + + #endregion + + // cURL error - "cURL error {{errno}}: {{error}}". + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); + + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; + + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $code, + 'message' => $message, + 'type' => 'CurlException', + ), + ) ); + } else { + $e = new Freemius_Exception( array( + 'error' => (object) array( + 'code' => $pError->get_error_code(), + 'message' => $pError->get_error_message(), + 'type' => 'WPRemoteException', + ), + ) ); + } + + throw $e; + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowCloudFlareDDoSException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'CloudFlareDDoSProtection', + 'message' => $pResult, + 'code' => 'cloudflare_ddos_protection', + 'http' => 402 + ) + ) ); + } + + /** + * @param string $pResult + * + * @throws Freemius_Exception + */ + private static function ThrowSquidAclException( $pResult = '' ) { + throw new Freemius_Exception( array( + 'error' => (object) array( + 'type' => 'SquidCacheBlock', + 'message' => $pResult, + 'code' => 'squid_cache_block', + 'http' => 402 + ) + ) ); + } + + #endregion + } diff --git a/freemius/includes/sdk/LICENSE.txt b/freemius/includes/sdk/LICENSE.txt index e69de29..d6a9326 100644 --- a/freemius/includes/sdk/LICENSE.txt +++ b/freemius/includes/sdk/LICENSE.txt @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/freemius/includes/sdk/index.php b/freemius/includes/sdk/index.php index e69de29..0316c6a 100644 --- a/freemius/includes/sdk/index.php +++ b/freemius/includes/sdk/index.php @@ -0,0 +1,3 @@ + $data ) { + if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) . '/' ) ) ) { + if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { + return $relative_path; + } + } + } + + return null; + } diff --git a/freemius/includes/supplements/fs-essential-functions-2.2.1.php b/freemius/includes/supplements/fs-essential-functions-2.2.1.php index e69de29..946a34d 100644 --- a/freemius/includes/supplements/fs-essential-functions-2.2.1.php +++ b/freemius/includes/supplements/fs-essential-functions-2.2.1.php @@ -0,0 +1,45 @@ +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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-da_DK.mo b/freemius/languages/freemius-da_DK.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..420c0a4d649886c862c39da081ccbd6152dafa52 100644 GIT binary patch literal 59840 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-en.mo b/freemius/languages/freemius-en.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..af870a5634b2dc8a67ccfab10c389a38046d0bcb 100644 GIT binary patch literal 59161 zcmeI537lkAb^o8xY$H3!Cc>M+*>Jjt9URu4=^3VBHs~J4L1nD&dfi&n00GoQOca+07!}mSB`TsZn7G7zl22Uz-`_d+zN%hE)aXCS=Tm%` z{?>iVUC%xD+;i`H?;D49-H`C#=pIROEF3&cp(J_rq85Akc`e}zd=QSo^A;yb5xyG^ z!2Opb$wK%X_(b@!!0X^r!f%0l!%xFQ;e+rD_-%LxJerHP!3W?8u;)aM!As%(unhNu zQ*a)9F;sl7f&0K4;J)z9A^ffo{vf=7<9ETe@IRo^?|oX5Y=nbwPxuaa9DENv9)1cc z-tWPK;J=3RzY52XTjuF4f=}f9a(FO28y*hV!6(5&I6eufy5!}s8{P(W-PfVY^%JOa z{~R6&Q!di^zwE`X0g$<;w8 zC&?*r8JrKd!2)~{q)LyWsWV_#5G&gx>}w z2X}JBUF7p1{L4GLB-Q`rsw}4_$0!| zLS1(XRC!iF)mtA_yq7`U_e^*w9D%#w6uch(1gbu+Im_?A9_sj8;F0jur+fJ?hN|Do z;jvFZ*TB~je$@(3|H)@3$uWdafDL#aJR05(mCySFzXMfY{|Ob}9_RS^BcSAU8GJIl z7%JbR@E%x$H|qSklm&hQj==Q1B-smI1J#b#LZy2Xl-{@#DxG`aR(L;@TrWP~?|TK5 zK6x!vJ>CWt&s~9^gk&}OB2;}Ibb-s;;qaG)pA2>Um|oBC@lf@7V&Iuj^3w;m!+{X~ zD~Jdr_d~VYo-4gx4uFVmas))=lZ#;Q6O!ZwQ04g8swBAzei3elm-cymy$2pe_!Cg| z{dK7J_$gcp52W+Zq{&&Z8(s+&-z{(e-VW8?`>b(2bUa)@_(He`+z!{nafrwyAAzIr zr*Hy3^Fpup&%wP2KMJSdV^H}TS?l$DHB|q(1xg;@2bJHy3gIuog@nHcQ+PnXw@VLH zJ)Hq>fEPgZqlY1?FWL7ZA1}^?R};PlQnkrGC=2DIAL{zQfycw2L8beobuJH2gOZnP zAyu9HF5|TMBj%?FA4n&D*yMu`S3w_Ec`K4 zy&QO{r*kxvJRJuQhx4K24aCpC2$v1ySxoP3U7r+z}G#)``^2v`rkuvGyErb20VYO*Y{3%vci{p zd9H-gyFY@`e+N9v^<@c44qgr=Pp^gh!CT;g@K&h&+zn5J-+~L^{?GRKmqXQe0qXp< za1p#4(&WiEp~=@3K0ciR)y@|}$9M?Kv$0(&8%o2-K`gSWzQ zc+!~bk+;A@3EvJ?Pxrx9@Vij?Sz7Y=&xbE1ybY3-~ZQ#xDM8fZb`@n}o_#5zRg!ijptHMX2?ps~;>jvSzgr5bK z?<{Z!RJ~jo&OZ$IC;Uw)`Ft#d_nPqh9SW81LU+^5DE1lPco@Uu{Q@BliG)! z&vpB-3C|&XD^z|Sg>?DkvgakqCU_6r4Cg)H=k3pdZz6mzycj<71wJmk8%mBp4^^K> zT@}V*xRme^RK2_!syrWpiuatW{rq)M_4HAAEPMbS4*waR4U-pod!7T&BUp!$w?Bu+ zz`Nn`@Qd(C@TXAe?e`*Yw;m{cw>Gc{)y^-3s_(Zzwd*J0nea#O2)N{r{JQhuQG}lj zPlY?7^7me-^gafUgbzWb`G7-S-oyeD3)&&&NS<9^qp{crjGIPJ^nabD{d-W~lpy zq2#j)SHW9h7yKc-1pWjny}oNa{z14O;R00q?|^&3mqE$= z*89V$Q1NXI%%J+mm2hu(6IA}+4$pvh!aLw&a2tHvYg}HQ@LHGag;4oFAF6!opz8fH zsQ3zSA26~Zrq7jXQQa4q~>sPqqiozH_7LyDP9Lf!woz?VS9do4T&-T)1~9n-)BSV#|qpEUkO#dN1)FC6e^!x3_{|;P~|xmsvHZU+WACy6kGw7j{&#@ zUI9;r*T9AF9;o}i2^Ie%@MickD7m@rCXeq;Q1QGMD*t!FC&9mly6!7b<@rabdiw!X zyg!G!?+HYz`rjY!g2zKdB6$N;eBE#M`&U36?}JCeuR@jo$58bvVblEWaQIq+C*R`f ze;gh|_%pBpAAv{1t#9>w)&pMwRbRJ2#rGbla(oo3|9$~J8U7e5-v_*nI)X<*nmqXe zd?wufcDMiUgVMX*f9macDpb0?a8LM5sC2f$t*{OY@P4TKmcPTtwbP;MaWj-WJUeg) zJe%+o?1Fbf^}7$lU&4=vMz|E-0ndWpfZg!O z_j-J*;Q-;SQ0@HzxEOvCNv6B-Ht(P~|@j9t1B4;Z0Eac@~r$)Zl6Gc~J86 zXHez;7*sw#1C`GG(CR0IzXsLr-+@Z!m*M!l+dX_p;8URb$x^8Dt%B0$YoW@yEwBtf zO?V0-ipk15+;8?z@KuB#yfgH(zwrETgY!AwgvY`gp~`&+R5~AnlBZ9=!{Mi(8!%Gc#^IUI$mhZ|uJya|rO&qDQ&v+icBA)&jV>giV>_Ild; zBi>&Qgi7~BsCqgHs@~6m%I|8ZbT`4J@CvB%yd3JfcS5z#2cgRK9e6tYK3oEi`>3aP zA$*kZMtB4~<1fAct%rvZejeNmUklHG--W90WBmN%;1AU0)vb zaW7vFlsugdB`2$)IR6GHd3p!j0N)?N{{;^wyystg zJ0A`uKPN(6-v`wW>!9lEc~JRzCp;eB33c5=Q2F|L2tNi@-~Sf)E2#RN_cwn3VNm_& z1gPVuLtTF!RDSxQ+Uatrc*mgHbrPyyz5s54uZOzsU!dCI=TPw-_6hGV$3n@;nQ%{d z9@P1jQ2D+XD&7I8a&3jG&k|Jrno#+=21*XDgLU|6Scd0(((TJzq4N28sQiBuDqlZ> z%I_}&kG#+2<0NSH3y?mt)_+!=dt(LY+Sus$6Hnz2Qoza`!{Y&1R_l4MCM-JA4w{70$l~D!!ZGZSei@ z2KcPcxZmt2a5>>ueAeaU9w_R z;41j%Q2F_HC^_8!e)pR#gGHT(-SEfizW_%3)M{5Poj zKj%Tee+@i=@H+TbH~|arkk9-1I#fGNh457&{1UjD8^SHlr`8pX&Zdbtr z;pKrPcp~9lQ0?%iA^bl0HNp==rFZ)m{Jy)PuKN_+7d{Bp&R+@q22{N~63+L3(d*+< zDEZtL!n@$1gkK63-y7gb@U3tV{v9mAMPKs%_c~Z1{C&6pZh6T4D9?p2CVUTE3D14l z_23Jk$z~@1=_Z#6NcpH?SdKjvnegyY~PyULR z|9E&9;U(~?@EoZ2Ex<$IZE!jKIF$VS7*^pSU-kUI2p&QBUa0iG3@?Yjgs+5K|K984 z5qK%#r~iZN$yWz{1j=vr1E_NRoJLdrPyD*q?*;Jbga@GFy%wGdzYouW$9}`}vlR{z z{tVm%mwnU6l^4Kmgue*i1W)}(pFceSmk>VYTQ0||pz8Bga3Q=6svkZCRWHASD$i5D z?eX3Qb$+k!cs(tLlBZs%esd8#8eRhD!yV!LwNUc*dMLTM8P0?6fRfYO;GXafsPlKj z{ouV&@qacP|6(})5AYz4e-|DLe-_Rk`iRGS3{<=eq2%X8xCcB7DxYUV#k&%A!HWYo zL8ZGHN*`>4%1=3*Z-nDhQ1QGB?g_7jivN${6XEsnVE9(3`#%(T50qSg94h{Yq4Mz^ zsPa4pr3Zcvb>GF`_52LLF2c`-N^dBHN1(3T0hRB12)`ikrEo8fUkjDLKY_~E>!I@b zZm4|R4)=ohLdACM^B_d#8EKU8_Y9QZZ3H{owV zn5fnoJxMw^mZcMgQZc`~I+89t`Kk4^R8I@#T2?4drKL)}Q7D(QVo$oMoE7RBDK<(w zi7qWQlmSlH8tG70$wo?zQe`x45bMHIh$W<6?@1S)`qX-2F;AAt<+LI8|E(~+iSwWi$H6wgFqYCNkn$O@UBsy1sL>qNOwNz0|-tU_Kax@^}(i9(hrJ?ZrP zJ`$>@zcv{qQct_Ns5wfqmBM(|llIor;p+HAIiqS8Y_c=YSP*MVZBwaEcCE^ z_Ig$7wmym!W|YKTSkDNr~D-EgLU2$J1(sf+AGPdZ9^@g+^%@(U?k$Srj_Nuzy6- zK;k=ysG2qr18$-jBzP`G%XGzJglep?(=LkDN5+HbSf(ZC)06jH^`u)#VfQzuBc)nh zLO)VP@M5h8*>|Cc@helRtLkKh8l;2i0G+nltk4B%g7H$NG*0Oe_iD3|7RK{4)ml1M z8Xe1Ox~xWJR7P9BtgYzkI-)KZX~!F-@k|FqKk(mD6pE=<7~Y{?Ib5yoC{cou2F0-& zx2o7#s=GQEFYGFfm!6xYS)o?p2Aa3ppc^9?D(+&!(l}8!aA*}RvLmIKnbN7I!(-Jd zZK}A5GpnbFA+@X)E#|Er<-snC+MrtLNqRT+C%pr!`uo#DtyV=wvk`=2xLWC2bi%Gv zPJa4HXHPp_qk`McpIJDK^WHj-DEhB#S$kJpmLj1|8aa#*NpG>3ZmgjAIj%B?AVNQ0EjG&x3x2RrsnWW& zv`7~(Q5B@MF~Y8p3LHo4(8EY@qFkNIiV}@2)mm{=jq$FhS{qgBswZ>@`j|*`A`-P& zAwBY4q=TVdQ6(8?M@C9zjXe1;nvCBPW(2I3ni^ZkNJ)20(L?1@qd~^`rM!=-Sr(;v z#pVrys<||~vYFKzTt#4TYC@@%%Shm&bX67kuQW-tOhOfU@=k_6Q&Ql zS}3~i_qHQP)VN0fdb*_DB3Md=RL6DLMp_^1Nq!-NdeFm@)n>WXFN;+bWV}#mn&R$G zhspa6>!p-bqkyM1)NC|JlG2jP3R1L#pOrMD&!Y9H?&`4Y0P;IIR-yqKwJG&C*&7*V z2X%tpuc1wb=$sNSmG-xar%@dpRm838B{mXkWZmV$5LNjbMCxgpaiy#k(v`|hw8gB- zYKU)vAZ~ z2@$pWm#b{7nDAq=P%(S4NU^Yxlw|C^Db{J>{G@kxw@}1k^2`;qP?plqvl`?2sV8yc zl&hP3km2k|vr?o7bER1n>d7smh9PFGz%WZ%)pC(;Pp}fBqL%9Q>aY|90w3EX*qSzi zBP03X-AkW}_HpPzvS6f6JyX?*Mpo>myGT~0qjW-&R4-)j}=TeQa&t&N`1sCmR1hx z$m*^>hMH?sS6D1k3yNjI#)(F{vDsK)4Q5@YQccU%$|zBw{_An1tzZTsCb>c)DYQ_s zs!&;s<}AfILR6JIvZ?cR5|tAmEw(7#i#N^jZqbew=FpUa> zo%%pQrfC1-abj!O{6Y4Q^n#8{&yWE+o5oigI+cnKgz8^b6mrW|G2_#P!V8lQG>7WL zwbFzXK`h0f?Cua?suq7Kd4$Ywlz4^#10cEQ`JI4H6u;^#b+J+=mYF)_9F(Yd0(3DAnlg0T;3~rQNEXjxmOeVv%n-MRLXTv)%jgwVOYLitc#afD$m#!)d zk7Xge*(bQ^poFiaStBQ+S=sipy*0w*4P(UhVp);fV5LDuYG!9iJ=OFDDrje^DA5R( zxQ~{tJ9I6xk8-)YF6);jLiyImkOYj7Rg6#k8K0m(57FIWRCR5A_onyjnmJ?pR2BL2UOBCPSUK@4T)im z>g9ShEr!a(yrTz}OH$8_2I?`fObZ6&%&{CTU zng^-La41|7qmBc~T&Wttv2mqSWs zNV&YM&A46QN_K&o9mCcwH1`rL=wtL7trd#d0?%M5%XIzdO+C$)9uCunmMLPk6MFxA zuP_K_Hn3$DSa$iYnc#Fd1LlQUB4;hEX@nifq^W$^mK9dr) z+DubA>8naT&5u6%Aa=BKzx-K9z=pzXsUrq(iv)k(rI=S9b_O7G@UY85lAL8Xr}Uq|e# z6-1jpBIO(IG=sWggGaieduh!1&QdkxiJ6Wj*l~uS9IKdlKaWixPM*8JTkPqPIA-*^}_+$*j!n;FA=p~%c+SVFiEH#ypBNqwo8 zP^EK8jXsvBzoS~02dM8ii<<#G#EqM*#H0JhLEd@Vk zw_9B)CacxG(oFZY-JxSj-rX=VXe_14Y)JXjIO|$69AHi(UqJ4Sb1h4YQGw$~p~a;^ z-&0e}(^6FIig~c<-}GHlkek5&NRrrG(mr!N)j6k-*MO$~!PDqjK(UM$YCAGhMv9q| zYOZZb^(1R>Oz02wn5>~ZZJjNjJWlsmjW&^Tnn_DB!Ffz^-K(PbgSE}rT$j%zbS#HYHtP1OlSoM`Yxsd zgAHlZIjeA6Mq$z|M}>cD2qY!YDL5GnKKNR^8!OvH(oGQ5TM#V}`ZQ?i&e%@Fm z=Ua-VUzKLm+)Z2#{i(aRyVLf`cfSgR*(N8N#Dyvi<{~%b{S|K#llX>8IOb_vE31KV zhH+)l%rL9jNvSpZoJlS*h8vJg@! znxYS3K%ycS);VUzK^SI`PrO^fCuI{ft#J0syKEDbsmic*h;{DrTV?@SfJl`zkj&*C z;%wQ|<}PySrHL{gPjNO1(PNbb5f>2F)d+L2`}f5(|v)HX?fUlME^_n)g) z%uJP`|oW5hV>?kuHCQzy}qXeArOUp8l+pyo?RY$M$qOppz>MzkV9Q>x$< zbx|AT%zLt7mWtbUr|w8T1lyKiqPlO3ChZ43?52^O#>=RV<{uW9Goubq8b+4jlK3NNP(=y8RvzmK-4_#w?W%+%=blQB5W zm}^~SMaFQ}5-A#iWqun%>;k!hi86?RLw}vUX*TVASOD#aF<#$NaEG^y#OU)PpACG!Rb2MDDv!3N z={K8RYl<006?|j6zat-#Bx)m6iB?{wUoHty=hCd5q18=3^V$T(su2L0sAbfRoKYrc zxwNcUx|{fXdSZfTy+&#)U!>F67ENuMa@bw5R*|}nB}KIa!@&^K2vnr}3U%h5M5q2$ zS5tJ8u^JAB+3D*7hTC9o$@@^v>KZk|#LVk1_rX~Vcv#0nl~-|d2IYY!L?dh4hfQfG z3JkT($!fMGX%Z*mXb=6hWqxjIGV2*mFV0v^9mw#xHuReY-z*ckzmF*^1A^4MOuXB>WKDwcK2kGtCw^Vhw~l)L1rf^_$5ec~P^V+>?u{YDILLS#7~QNG?)z z=vA|U)Z{IzLf2x7vBs?_NoyQ4J5M9yP-cA;OB1bbvl?=f?V=hgO#6Ly)00rnrC{1%&wZScJp z$@(f93!#{psPkJpB?53MQ5$~9-RJAI;iz3|3Paj8#DKLyt9Sb2hUR$tTt6g~Y7H=n z0d)}qO!M&evWz2<&j&@HoY3dPE@r8?8`B^jFLh z_Zr*mVBu-(+KcwI?S;@NA%mx&-df#Jn40hV63uXRMQ0LjotDWWqWK3aNXCrcv=Mzz zb55j?)w>Z^IDdyy{vXNa|5Rg0XlCqL{qM>ZH9o0-(J`C$#w9QvMfRs@t0fEZ__Z{X zUlBFd?2~nAcUSUhZxad$XFzbT_Rud+=J zGcTQQF|t7hy*|u}9a`J`yG0$h9kC{~geu(@WK2;YA8LIs9)mv?n6K^Gz-T)-eWKEn zZ0xx>*~mzMR@5*$rWL~mxz;F1DP+UOH44qyl!Q&v&9Dfgf@dbH+pI!^k@sH)ARBC~ z`q4o)snL{7gQfLzS=??wR*kfE>P7cwd$&yf|+B_jM{1K!8o|g)oG}9SJt_wGWzW>3boO3y1URC zUCrQTh>1Y%F$^j9q-+ym+)%j~oP2*sn~NYiHiOwXX}*D2hE_#{*u1aUesXg^vn?kW zT+xJQB_u9ev!$I(OUfo(ltLP~9BQTd4lgN2k@nSMIFTrhJGq$=Wt)m?8BgiBI zr(>80S4&h&7AjBH<#&e(jc~L6?Kr` z9j01R3PEfunDXh#r~49nkSmIl%BbZVB@~rS#B2vaV4v5qbU=cN^yj#iYl(z(DYmb6l=$f z0LoBY_*h=S5Nlr&L5JAhYaTK&L#S*5UD%hkb{;;?rE7XG zdA!)x_`_+wDbr--@$Lxaal`g;GGOCmx&hH;yz=VUU9m3pjehxMvV#{4u*Dc}qSgQx zXfF%PW#gHQW$bOaw`?2=?(}FE4r}jKFsdXVYJ`}&iaE%rpJ%6aARS=cAzsd92pgB> zljLNR?=6ee5ETvP-LIdKzTXxH{&>sm2mA6X!zDArtU}R!cor}iL!1xIRb!oPcq-jg zWqg?8ux-cdyi*iksn>bl)V=a9oREFS8LoqA3zX}S0K=?}oA z>wImdbwDnWXXU)H$II%2G!0**4Ro@^DbO z(uU@e;Ds};O#>>eRL8Qk)~wjd#w>SZct&TS<*sEAC=da%FZFJVnQ_mK9BgtM zo4sKLpQf#+>-eBRbLXsd_z2alYNRI&3$gZ)|BKY25r8Lz@!7j3O z&vdD)Sz%4R+>Y%mGV{;}wo}&2O0QBh_nkv17<4vm+Hzz|xmeZzj- zm>rm|sn(j~iG~#4A!w5ojb>Rb87%Bdm#3HY4oWutHFCheie^wOuXl1dqzwFJBqz@=kFsb$;M*DY1W9-Q0x4y0+CD?E(!yX1;&VA}cP z2A*0pCX1~Ng+6OM&7XKOS=R=DnH6e^ccfV}No|{ov}cQTo$+Sa{o||H^h(9p_8|ED zw#uFB`d@!uwLcMt`?f2?Pi>;ef2y%;niRK_;-Abn2T1UEjFGMvx|qzJY0^f{6$A5T zc`cBXDxNwO7FDAc8ALmNrW?!lTA6=LQYPmTZ24{S36!d3hC|wYNc5)Ch;1G64RLye zgRW$<613jTY|P3-p0Hk9VCBfuoi%YoO3UoJs3En=Q&g<3x8;=ysj+%ktFplgjiNPK zuL=Aut!2SE4ypg)BSzR+rn9;;HO&)U+HoeQEL&dmTvR??iFK0u0(^r{@XNHtp_!tV zYGeV1nCs0Vb7C!9^0dGNv9_NNHoBSkk|Eoft4&yFlA1;{m42MfNR0xe&fM;Vx{G;P~(kjgGn5uOU*- zVUqV}2y_2RF{@+IQBjPqmR07h1=}dC75G>g-d6c~ygha+(VjMV$DxQJge~UxG}sJtU8myvEJ>BEjM~Gaw*8e#H$zyOSC0-^ zX-(4GBFx>#N_97}$&^xi>&Z&HKkm@nO+0#gL8CA}kql~{>VMh1NIsXvvUqn;1^5<| zLAjBl*Q(`3nz3o9p^@87VtKTvHyw$c$`nw#L>3M^U(RKR-O}heAlK$yHb)&2V5L~F zo+(hRs@cq6H9d9qp$v6>mp6>_>r+6#EE@ovcP z=vGK*EOZ|^*LFf&j^w|A**wV9r_A5UO5!$#J$JP^IM>Wuq{q(GT~umiyFNw3jOU#u zDCE#a$g{h!hY)mE3Zn}ZzYy`$xQ3|n$HSIWFBLNc+= zXLWT_h=(@&&fB<v|OqG#wN?=p+I&v%7{ctQ}DogAy|YNd|wTrZtwaZ}Y(m}Z$f7&%d(!oOB;TND zwxnvbW+sj8V5;)&p3-x5qq=Nd>T)^P9toxNY0Jb| zx+JqBK403?wdJIf8j8Mc;^4vlJ|~^ge$9X-34fF_PqDcbfmK)&=V423 z_iZg|?dP-crb(x)o1TP7nFzBZNKdp%$GD!1x(>Y=A9xRX*;I~CXoB+q(L?H_`NFPk z_GBn0!O`z~?9R88rtROi5=@IpGko3Y>gIQd*A^%gL)<^bL$EA?+NLgyh23vn%OxP} z^hGi=pNWvAHXFh!Y#eP6OpC^3R$ymfP?cIOW{MVPGp%VX&RjW~)M-`6tp*4~EmIui z$kdfmkhxEja+5qU9FCgP8(n*3h)D*3wHOu;9kE(wuA;Kr*x(OmnmlO^V3r6gJKgNc z^(wc-+_61tYGrvKu%(5i+*Y>AxBSgd2dg%R3D#ZSOg@HNb;MB_-LN9rq5)B+l*G}8A|y`- zL;njQ3K*bz#yTGtknfY;lRG*D({V8IK@Di^^tJwh=XtO;0pv1e@P>#J$VW1?g2GT@iDn z-Lb9w*aT2kyw=WA%as?DLZRz8EG?T=wpY=1{G#VYCaX+&5D@LeKNU};^}(KUjqM2w{@v5!X+!iy8`Bh_$I+u>=G0dLv(8a%8Y zB|dKNuCh2J+ccpb6Nr0gd`4zx$%$_fl{&FmuVzuIE2~SAR}Sf;-SQPG>e(ygGkxeb zU)7!F$zcnjFR-U{MR{$4N!ZeIs`X#9A>W4vZf?_FmKF7}D5~bqB#%l|J_ggX-O+N1 zHo6AQx_no*DC>JyC9E!2zyC`Y7uYS=FK)okl@v!{Q) z9x4xFygUk)t{o{C*ko!mv$hAVIY8CSv2i{=E0eo#^Sv`mY(<>b2^;)>>n-(5bNkD- zl1_%*`?#|*9(lnK(}Ikzs!HNmht-q4bR15_mrz*p5*xNd*i>9agt(Yh+qo=lk&U`U?}ZbR*f)yE;4$bE!{16!*T&YLU#mD6E?9AJ*efcWRxh-k@S2LBPLw?1Yo2~X0h2a`s7V9Z#clst*# zFR9X=cel9g8KhXiwyvNYGqlT((qmq{FhN86PUfzUmO173!m{_v7coctm!mb+p)FT;1sM z+1Jd>(cD+LGu#w>t`_FLGB_5a9$>yrwa7_fGtW2_M%$!nJD|1wZ?SwjaTMk9VY+PG zscPQzI*UrYHmFj1uFnDDC?5scnqH4VsD^A<()`Ta++O}^tHHDCs;&Fb=r%%H0i(p4 z(dHs6C6d>Io2_{-oL2u~V$qXa-oJ^ZuT5kQ6W89y#G7BM_{jAvBh#x1yd+uIrDjs` zM2EFWoM(3Rx^t0PXSMPSDGQm7*sHm|ad+I-F2rz-v&!kLdwCJpZZ4N>d4#%+t!>Oo zNKlg#yh>VdH!B_)#9nesR?hiC{)ULIwQSeG1e!=tU+g$QC{K?TCZ;BDL{-Y7P6hFk zTzINpT$-4fg3ed;GRLsTvP*lU>}YF2Op_Q)n{n5=BP`x_=}CWX9WLv!%>tRM;6>}= ze|HRGU3b5`u)p_bttxNnqR z*oV#8>C&Y*T?$bTw>*?x?dmz`@s=s3V4KJPc6oKxOCYth%hF$9!tk?}} zhc&MGn1~&ob*;|BIxAP~^01_Uvwf^<6Mu9e)V(!vT1T~=z!MZ1I za`a`VuJNi4wrzCl>a}*A`>5x({c7#&*R`~@y#ga{?^6%$+N8a`oD4p7mS<~IHul;R zxoUH=*0rI^QnQ}XvrFR(7K&6=xjG^LY`O!RFf>NJOB-J<^Omuu&tJ(9^g-9UuoFlL z=b9<&vfWhEk453PbJ5qVT(r8HcHrZz?&>wzxu#y6NtYYx+Bh=Oex%a1v6Apyzs}mj z!g@=!be_5rax`Fz{VkIw*-bz2!!tUE+&=jAg0IK-izoS5q%IO4`A*uNsM4%1!KhwX z->`9z&Mo<1k^a}tX6M)nYJ;+G|LjR@W+1znIjnha(DyVpv0HoFX+3I7H3VVn(hDdF z;&!mJs@@^(p9xaawUR)B+o@b|%MeX9H7&fc$}@0!m87(f&Jx$MB<|cp$=M96tKaK| z?rfHx*6C#$GdRd)t$v`{KeP30SCg%~QlVX2v#fipFqs7LflMvpyKTCB6=9tGv(kbd zDb~Bj>xo?O_FQL>N;{DMW)R@F8PbVX8M?GFE^GZ%V@1zb5<5<~yX!I+?W8qP(&n(3 zHsp7{|49|8l`2M9U*DpV<3qO8B&+!vF!eY?t;k^W`)GYVh?lem8~qyM)uHTo;gzd# zLyuObB&k@rs&s!C(H=xqPR;q?>-9W=(9^Ycd_1uM+4`Lg+ey1gkkql?hs{H!0*Ad< z$Xlp0-`?)``Jf+^+o0yq30Dm-I=i}gvA%kU-mg#BkzjWB7rQU>#$I8@s%))!mu7nZ zK==CkXsOt}vN>As9;~iN#~O`^`ikYtN14YohkDQj%h!a5GnTVVUNrIUt`~PKH!pB~ z-lmO%-K(@tkN9`@F{G?WIo5qCTUym;;~_n`+dZh4V^XQr+KTitHcGHvvRq^bL3++k zLKjG1*;62-dfvQsY`tjWcO<^}vwJWrjIYSe#Z!FFR#8&)ck{cJJ(?Lw&?|h{MQ!vGu-}y=jv+rmQvt+7AvKX1<;+^$*FWlVzt**Xkg5!qda}}Mhl;pND z^)`EPo2TqlpR69Ub&XBFov)POrch1TelYqWqn#dqm`n6*XKdUR%i0ZVN9~U`XuVAS zaeXzV^OX{N8B|!0nDa9v)HUlHT0NNa^vrY2s5@UNVN;Ox>N)o5<$J+=voAJV=PM1j9@Nqqp^R(kgrJNV+k9 z5lQ~h5Zl!sU%r@6P*ktl3CIRQCM=pD^Pni3_}F(6-bP@?VO$^4My}3RO0+v5Ec&Ou zm5R6Z<%hJJ%y%PqzEZ;bL+H+(uatDYQWD=S-}y>O=PMJJ%ZKq}3-)|gd{Yhi&?Udk*KaysDd~Krq|9S(Y&UCruevXP zXm-*0N=d~#RsNc}xJJ?WN{QBH?fKMvrGe+bT5rzh0i@1XN_c>>^OX`l>W3oHDs$&6 zC7rL7~KIYMS@mT&ctza|olxpW=9(6vh)A^W3=VKns z+QXt+=VKmWGh*js9-WVQbUx0&L=l`LbdbBjm{@G;)}2BHQ@dxl4);_4sY)E=la6C zraPb9=zMY`<+Z_`Pj1Y3zcw$hX?x{Xc(Xq5Kh+zpJD=QW>g#+wH{JQwI#<9xVGm^U00;^BTSW zK@B~1H~V86GPdSEqZI{zDy)ZRVyAuMf*b6E78WzxLU{GMeX2rVqKMy}XnkdZ7f<^q zCE~{;I1s)MG4mr3Gs4rq^bkMo(Doe%`)0%RuQBlLh48Tj2VYmP&nei8&01eYnD)Vg zunwKr*9`2D&v+z!p+MgxnDH5cS>GMV3m!ha5nq^VOEWwu%C}I;d``f&f_R<%|N6E7 z&E>Ck%Rd;fQlAFU=K$;z0P)TLVV@5#8JYR|{_w*7-{u|t{(Ann;U#}Fe|#CrtT*xh zZ+HX$@AIAc{+j&ohJ4AjzqH=oi*K*Q4{yVdFT&5?fp4$BAJCic!%OeuH|Kk E0n$6m-~a#s literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-es_ES.mo b/freemius/languages/freemius-es_ES.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e10ef71aa02158461e55b43881f21c6790851396 100644 GIT binary patch literal 62945 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

    |Ux4%+3 zo#y1E3NFY?X;jw9`sUElNrKVNV^=eJD#f)Sv*tdepvjNMeN`8;i{6MWU zyI!I>G9D`hZNxk!t`qkQc!62oqx-(wj@1CxY;iuvUt8!IOKI8bSry;!AN zSV*ce&fXO3w6ZQ4JlrF6ahx`D2P2fF%=4^;Uccyk9-MV?lPejnPIMbpW-xb}MWLBo z7&WMvsS0YAx|;PW)1JddED9>M+s$za2pB$&Nw76dgTo`a@*ZSPMf*6+AXzXHr{1XM zOed@MGhM{1l2Lk~g{6D|6I+wW&X}2#29wUpNd{pdCUClwLzMpDfvgRIll6;N_+^Jv0Op!YODx^@dq0(4^A&Sa<+3Y%9MC1gJ z6gxK^c3vu->rb~Jhhmit7!6LwF}WW%s)3LSZd6cqngbP?qFYu>Q(DLT2iZT83py`3 zLj#y>(yyjERf{V^%`fW;t>v!R@Ls0y&ZHyVvG#bYHX}h0doe1zI~?Gu#b2r(Ci4fy zARG-0#ER^Mn$=!dVty@H^z*&8F>#efSA0VcO@IV5KALuTM5PH$pTsy>^jG5H4%x-x zjCjI$GTgfh_Tqdtz7Nwl*??11TN~ zg7bzk;&QRB!cAF8$w5yg&r)uT{kw!4h|ESv;Y8@jmMH{&)obu|w-(P(5+^K#ZTb8=Orp~^JN5JTv~9#629^`jyxWA$2lN?y5y zii?oLbF7Iwvn=*w$?~lDvDNHOy4KkAfEBRFs zhPjs;ojMLP%R~qIvFtY1+M%zx35ArIs*O*XH-QF1!1x_LNu>&dZR%69r(#p#QNCDp zVH!w#&SU=7DRc?piH21X=n`8T%b8zg&!kZ^;Q&{hxfU!Mg#;MN#=4W~*5+jRC97p2 z(`42LxB0x~bbK*d5e{gim!2=L)D4MoY4v)$nN|ZaG4Ggx^_s*p+CVcV_G#IOf;rZr zWvGw_Ud0gFep2cb{vJWfIioS?ZUqE$H9v%s;Z(RK79ADIVxye1PPB@9gEzEYWK>5a zh-p7GFXV2?k`08$#-{t$XlCSM0V$aw_4?|Pal6cg>@p2IhOJv@9wk^dg!Y?kRjS!C zZ(!(4uYB~O?y;rEgWFJ;B4#@w_t!bWWVGX}N!@g^X@Yu{*6aWtm_ znOr6DF!K;SG))}iH*Aml$V0{@8nyYRbTZVGcwiV@$q_!2u55Qqw=^I+VhsTc|4n8c zizFUipg~_B17T?ri)2i+sGQ+HtJESng;is=0apTFn#1ebfct%{)B~IESno_S&dO)X ze~4_+J&zaDVFfl8oHeOVO_=pO6nulCpwi{}*HGj`U7I<&NkOFL?Tx$At1uv2gxQdr z6`3;~=@2+!XExal-qp&hhK6^pqB`u}8T?(v+*p;V-)z9z%n=FS@T3{k4O1TJ+Wu9s<@;;R&?Y<`IoQ!dP>VIu z+)%G(*mzJH9u-%g&5Xfc zsN$K7iwTz#CP!MWY_IYjs&~$*F~$<*cNFW201f?eWuwqT+3>m#t;JvtSC#j_V^K6! zvyrGt-Tx|1V_}bt67Y*oyN&D;*{JE2W_qslgwCmY55vfyv6>EKV``uDtV_vo0-r{) zfIJ%KQkD^efYWfHl_jO`wW;Ku8ddrV+%`lH@jpt}9o-p|jLcz$N)(P}7);2^%X6dN zZjAJ1?oxxo1pZg@IU}Azk!qdFH1DvrHrZ(F0`%T6x?CF%;-REN7J6!PO-D`vx&I;3 z=uJSdOjla_G80BBnV!nmwyFk_O#~+N2RbI3=uc^~<<4Vo#A>n&morRSiV4AEy6aID zl^?8abaPuVlQ6Y#BPA8BOdBC6$V7rTn6CX$iQ$3*##P)1X=M+Z5>fyvvOD5HgO#;v z&1#6N8->lQ;)-9#rpB_@@C0!b7mut$Zrfj}*LWmEG_1vt0&Ca)V1dgO(Qz!m4iu!5 z)m|K!nNS>#3|$QYgAHl!oDtlXQE<8yD8$RFqou+!R6et-`<|evorM7zf-YliGjZ>=372ql=D!9*-kw%wfQ5IGTpsr$w;zpV^JYA%L4BvdJomr zW~{BC6XNKVt9W>a)L7|;YGL@P7x1q}4l=3T;!rA3oXgeLK(e`_*5@TV_BdG&f)W@d+pnUkhf&Mk^Co0Brz7`I8W)x&

    C(G7IRHyVee5<98S{nr?51lr-l;Ye0_pNlxDhzjd^A9OLW){guk zcbQ`QN`!GIrHGqpS#;|de-&xAEh9=RPD5?$YFS|3ZP(BkJ+evo?Gi1B1 z6Rk~Zch`D{aD;<9n-cc$|d!OouYT z9de>Yr-q5>>oTe7(cn_Bh;?A`3m*OIxn90^;bk@39iFw`PgCk~m2S%rW6|(a@;c1i z*fJ!8u*aY)?y@dpJZqU0iNI37sS&$DVd!KF%HOn!ei@Z)%orU;5MEIM>%=Ctz-t7z z#5hyfOK7WT;b_XckhI6GHLJ@49mZ%W?2UQF+%RSC1~QS~S_p|3hGL9+(asL>-zH)E zT%1RH)8w1E*m5(2W+6Uy_$P9;BvzY%BwBsxy;H`rW?Ks2+qv_^2woOXE}&PpK4Ivq&fB-9y%2a*tptke&4 zYiBB`Tl}(?r6nC8kT^NOd@W4S;Vz!fczSilit0#4+_j_M4EQdY$y0ZPO^qV5%`CTIA|y{# zamZD(gEZuYWubd9$5`itMz|U!TIqsWMhZhETZw#krjnq4Bf4cGm6e~_ zGL-)mA;JUBwHPz;cwgxnzeQa_3>}zwYi$DGjzzDnmPr)o6nCTpnt2EUVahj9*#{yQejr9>0@<5rz$+jA{BeTRGF@J2k=U*h- znn)~|VrHhzZ)HmaAgly6{FH~#w<+nU%r%uUWe%aRwrd4Xf85xdF8lkTqEzdENj%UL z;Q&{Uh%ZYyV)3lwxD#!M1p@sJ|pA#qf{38O{tvP6gZw=T63 zNs>XXkF#or)V3IJRp%`$)|i%~M!y}RQ*@B4TF=Kr`D20k`kpC9%i?rLWgyuxaCNc+ zO@LIC8lBRrVTV>r3(^bOuyLJ2KAWmAN8JpIpcT9^S=+n{DI=f1C?HcdMt)?FIW=<0 zq%3Wo%i?weB428jc7kmAD)SL)vtkeqMMFcaL_b6_P@2yb%PF@IG^shk;EE@Bbe<^(9}(r<(lFRTvV#uQ#}F`ErgU)+X)H~4 z@MtSB2Rhv-B_DnxV*I4p0mB;XbPBJWpag%e=)Jn}k3!!r7>@d6;LnJ*epDs#x zwe^F3BQDGqNq3D5nYxE1wU~m@I*n8iefQT`rLrOil3_%R{m~4DjxwpUgM=t7%0lKA zmJQ2G7H#sVIA*$}kw5BBAx89E%)beD1TGCXLWvkAwt+C_7p2CU2b~v*#wc$wXF05E zGndp{8s}n7t6V0biU^0hO#`S5Le2R+b#s~dCJADhtB8aA>ELQfCMuni-+&HFeiEWk?3Yr!wE=HZF2H7fC!BRs~27mvy=} z-d&}e7A{nTD-_INqaZcgqb!#V9j9-kpDo+!CMTk2RTnwpG5tQT=`f|DwLDouMVTXJ zS(y1_WG*5!Ey`)jPD;sv?KFu=f0a5aEnZn7A8DN0YN9ok4t!P{XN`{| z=+3Jpv2#PCJ3U6HamKtmA&Ad8E;{+_XVw`XNLR)Mt3q~^ZFA#;s>}OTIaR%oAgo#T zo(UWmwvVX$!BvmrP>#=ekR%4mTe7xyUY{Ma@}>CX#L|Qo>Asw{COF z!Eyo6c<0%G%&=<%Oc1S%fDu$i5Wt5 zGg@+STgX}~!C0~UcrHRsTtc|G1tgYg5g{%T86cP!6M#CiGhq?TGL4m)r`3gY7Dv>l zu~`oEklEXsRBY7-rRW$AN0=k>jKzsD0*1~|L|a2qle1u|g_~@Z6ydTm%xRGi%WH;s zE?$#<&otS2!(vduDY0yu`N}Wz-9}Wctw#aU&3LRz`Hsh1B>!x0QZQuWVNgB{5)5MT zl}?QW_r47l4U%gZo;%!)Ls^l1c!XOw4L<2erET&JYMz*BymF)`LVw(_JwgLaPo~>p zUG$Zc<8a;D>B)ZiZL))xjgV$cG*Rn-%aqT;lG$`7V;Or};VsicA)FrV!g1wa1*1v~ zqCtqYtDB=}{k%EFiFAZ@hj=@;!ED@?JITqg=a)s4ii!sF;n&cp-ye&LKR&W>b6pT}Uz;gTC?t|RsjKYGO)R}wcKegMO;V$GW?CV~ z^zqQGsxYC>1tlp<7{&ceX1GF>`5&uec zdPx1(uS^3viH4KVMlJRP}ts`Quz>$hPbG=iF3y!e6PbH*z%+1%D zlKjG``P(qOPXR78v@>Ny@$|^)cEDsZWX*qdisuvN>gsfO^DvZ~X!_-XIWfO+P~Z=; zIqe#Jp}j`u8V@0xl93`gl1)3XfpNa9$yT#2wk{UdwA^o7cJo9VMGs4kSGllO7TxQ} z_O_W+bwuL_quN|X_8yomPN%1vyV86(8LIvs_RutBlgFt=7w2@4QrQK&FPM6+lWnK8=e_uE8~JdaRcW(qHM=cArAVet zM!I9uH3~2YqgPJakIC5q(@o7*cRGGN#)UNzz0HmRgP>-qUUwnC#D0n zB4!O*>X!006zbt(r>o>o?ZG>cDJL!FA7bB{_di2O$GXzW_G+$MttvePxAQEd9?2Cp zB>g(Aq8XTWzU9Dci^gQNNGf!%@nYZgWW25ffcXS9!8_4y8K;(9k#e(G*O~5y3?N^{ zW>%`i(q{0bCE_l2|NUPV^=E>*FWnh_Dv2WhslhTgDW;SXpUjg3#CU9FWa@<}CUd8U z+i1B;z;9O60#0cXsZ(bWHF}vrwBzT8u`J)p;$z}61((1gZj(DuBFnshw8xN`O|=P2 zAM&I)ZR21n8LtGXH@_JR+K^YQR~J}6@_H9IZg6Rxtcw<0t2RZ!>T+9AnNS<6hpi?_ zR!9`B$vP#7w-n2Qi=5K@BSs9fvrK2SGcnDsE@ho5C`-zV_D1FIN~B313-AP=5SJ;H zAx}|DHL?Ih$?a|xpIFP5>=>A#)bjOUqnn8@9UKM>J0R7=?*0EOL#0j8ZWT=IRhYNe~YkS4+T@qN;cx!Erj zso*fx`#!=vzEaKFSac8y{c26cZ>?Cev{v9FF?_7@^?2KQE7tB&-cc2yLTD0ed-gXS zp|0^MKWGaSgOgfgD!_o6%5j0i9*mb|m^cMItqhcJ85k~1I;XvZe{34Y{1KZFrttn+8kXriBWoR9jtAHU}-tXGalz3~9N$8g*V6A)eUkUR|3S z;;CJpeOo$HIAdyx_R)#oWft?CXN81v!XY6$1y;#yg+@fKS3}H@(@mt0t$svPxfSDp zJ$kzqrb}8^Vq}z720l%8DRi1U=($*l0bbdJj6XS$vZ83B2k!^cZGI+CP&8Xo6m6MF zV;M|M-rrNZP7i9z#-%RjbLB|jnlu%V;ts5%JsF`u>t^!9zn@?$&P1U_tqTEto}0CVS35 zzmx3oH1l$&-Hzbm!yxd6&x4|+7w*C72S;Iv$1kZW7$LQ5rwVQa5{n^_PBfXPx9O!7Ti|zyY#0P;+*W zeOL$|?!_@Nv)ZeO%XSC~+d~vy1ER3AQjG|AsR-N9g9EME=~Cn{izDeu;sv$3Iop5$uJ4S5o+5W)~7v*PyldhcODb z`VZdMihAWN1eUpm}0&tN{{F;PO#lXn=k11bmz(e+YF!uN?FWeQCdS> z6L)CdMuBMenEzwxqIqfBcOX5pv~!wLEm96^(H?WtWNT}ME45a6N9h!@ zj?SUX;-0afpOAab%0mO9SEF!+T0n?5uFa7u|_I!PpPcde7yE49n=U z+D$aWW-SVoh4AU9=)F7PIcg0Ll}`7V>^`#bg>~2{s(jksGG%3mw;4hu^T$jV_nYi0 zE%8K7i4*fpPd^Ykm%>j;3-p4v6W!B6;e)A zzn`>uR}DN|l3vyn&9TU;7ImbJs#Kh3a<)I(7txZI(rhcjWJ{->VJc>IzWVJKI&DQ- ze1FiAZ!MeOOow@6Xx1Qh1QfR0ea+e8vlFe&uA7)SyRYXfvm`K`1 z5&9ahX`!jyz_>gjlD4}9QP?gOImTyFUT+V5AOhAkUFkRCN-YiU**LOe+eVYiaZAPm z8U#7y*2h92;Z+)WFlm&jV#Wf!WWSKVPD+{SZgJU8q1eIFR8Wgi?TU5`m~shj6w>!V z9#D&3M%*le_Ee}#Rm;$prP6A3&FVN(6-=*^-pKtIFIfsibHmyf)kqkWI3IHo!{2%r znc58_i)*EB_rdA5H6XBaZsbI(udGpug^}MC8Op8B$$Uf_1f?7)kgJ(;cA95W(CwlV zNTZyf*r{eTI1e$u(LujN*YCG!YL*QRGf`Fxd_8Y)7O-$WjgNF=Nrt%iGP_YbKj~GV zM``vGjl+0^cY7vUoG>nKbo;`4=C^3^yF6rV0zMZDi{BZ-e^CzLbVC+}63p|AsxaCn zP0LeOlHLkMUZNJ|_Hm|c%qO*&cblajUhvbXUFSYP)bf#$ZRoXGK@>8zB>&8U(cb@_ zHQNQWDoq~--87_iFmkLJZO*b1A~_X&RW_f6J@gNb#X$0uEyFBO4bwQ{`S#`{-qG45 zlC2$wxK}g8GP1s%W>T^5!p0=}nSFyE0>tZVHl8eDA=43iHJ3LYG`pq`HeBSYTDss_ zUYT{6+r?X6pdKRu4X=a><(v@v(XzN%@$ew_l3TJ0p;z*EIrMENsR9#dCP93W7XVXU z5-m(jP2OFqnuWR)*iU@nwR(3MVmt+1Z|Jp)ArrDsnM`)JShkWAgK0DEQg?zS);{gY z=h5-HHdj_?WP?~&AFpwdp=wu@(2{S8>a*obZIAM6da+F>ymqABw}H)B9EayT9~wPq z+?-YW*m^R)j~I^X3)#|K?d2|&q0pq?7UOPl4W8+tfKT60%v9WJYx64cm%Ozn*I&;` z63u!#VBa>B6*;SRT6)cOB6fJzw=qA~RRv7fgup)`@u|LH{^){CUNJ>jwGbEN;Rd&u z`;;%BAv6`NXJRU%FAnuhH+8an-+3h1^L!ZAq zA;^Qits!4W73Y#E>$9Av-gA-p?OKcos};pmQ=U8_<-S1!yVlh!E}3!@eLE&5%4ZsV zI~oZ)@O9O;^y+=ml6jg+@X?4Z$rmO~a+rDGr{_%$g?)(Yg;G$i+E|RcmO4*Y%2NLrPWH;l(TJ#2a&tOx!jkjIahFHiD zn5|E*isX>6gIyJQ$CTv~xTbGC2MJ-Pdc`e6B-L!s@s1`t)%4OwNgrLMtkp@(uS3Y0 zht;>m>B4k2OHZrmGL2CVh;rLME$<(1Jv-PX!B!%)@0u*@pQ;>40{eit7V#}KeIDzd zrv0pkAV;e0zUg+NK)LO{3|wg^^4}-{ew!hkXf2>m>1$c>Q-c*FUrC%e;px8J&e}=g zC@F<2)(!GI&r%X0wVs5A^;IZ{9Jj(kN!Hw@FtzERRYlqSHb!6DV(~~%T{sXZ?ETIKQF8oKkL2qFuK^Ne@nH0w-3$Q zQu-XiXvxk@8~gjWwI^%U{`K9-cK>K|Z93KI%(U08Su=_2(H$FL`mWg&c0H_N{kdu! z+25}2TVsLS_LAWpqx~DS01n6Y51|Ftrkv~FNv^1-SvYm!`4?W$fBuF2=UJGg*aNxiIffd$wCbEOpsaWj1P9YJj{54km znpI2CwA#toueHIoB;@AUU$Sk>wvEN0T`+L|k|*~M zH?@JSe?3xKs&s8SFi@PmI-Awdv}K;IRRH>0we#8U%R(G z?*!&oYq3*{JUi8yuFt#GPL*%X8+9<;x%Pt97mnv`TX5E%U3iw3TEiB-LLUxy{_w?F zOBa2t=tIxtg6y6>s^REAp8;9yf^YdqTVXDB<_=+&$(yKDE5tj|H4~NnO%e*JZthk>!T1+y8N0%> z>v*}WmS*)e6I?2)P+@fp5H?tHf7Y72Roh57p-RXaBJz=c-GmiKw0X-a^iQ1NYBI^N zII2zTc>bwtS1PKSF{6J1@o?&(pOG?7Zvu_ zg^t?bb`F$*QZk%K__jfV0=6%$f;m=6HfnFQEFyK;R^_MIcT>aE(P@^~^@Eqcj+4$I zq6~;7+&l>5trKP{LV&go%9Y`sc=4UN+xgW<=WaPz>ofO8^Om zDmNt^))xSpdJSR9@Rg~NVd%}>Ue_{`uP)FX5{0aT^v8BQ&K(kCIc>;jpcr1V%7eb{ z!#h`%*P?=*WArTsET*|b@}@*!y%)FK0h+3(>1=6O=I$aZ(`HC$$v`?v;sH0R)r%EG zlS#s9VKXym#`Zoem~3KEZE&!mW9v$S}hbf zk{)zx17d>LnmwyKcWX)`Tzc^is2WogNupSZpgBggSSK{JNTEqyNP-Z#HO^w2&{5*> zdU`?u`A1Up!@h>H!A6f4&e~_7V#j<;d7noZ-5_V}?U*L0DMYZ>PKiy(j33%uA!GKXR znaBwjs!ji)7t7^uq~oo2XrKJ;)s(WKn3`c6>15AIDT3*_+u7&QG-Aztpu)TPNC9I; zvN3}+Qq!nRPO=wX}Fa+%W{X4 zz}^q&YG66UfWhH24SN34F@I=~P)QBh&qr4D!%wn_ET1K9UO^|ViiV7WRvjhwUYUe# zU1~yXOHZ)*k#_e5~b{X{r(!O#ntqQ=N7T zAg8Sb66BmAlANXtZGJU3CUesBLApvhcUPVFca*GAE2W?FaFVUANcr=X2%{?t6@-dK z`XGrkoQsr#N1*6nH^>XmTa#yXm*XuxtJ6G<@~;gbPLjOvD>IR7iz3{ zGK8j4jB5}Pl{kg({=`PJrp*v{GY9TPB#@VmAmX{A1%$b1CM8EvvMvdao9gZ&y`+_* zjYHSXE#NZ|q}h;OC&H`u+EanX3c^wA07^e56xpmpNErh-25wnlNazV>g1P5RVb!45 zSka7W*-2n5PoH|xh9gM=^*&5hGrL>OnHmfWpXv!NMwOtqU-iz)lHnGb5geAF>;u1h zQ<#OKu6-_^J&*^@)9g~xX@rjS(%qM!!b8Z({I5;rT z$t8!W7TsK*kJN_KE1srjP+q5gkRJrDyD1D#?rgo(q&3lu?af}O(OEs{JE)U8zK_!3E znFVcSmXFy5Cd(ulrF79y=%XMNRF1|BD(ovgs9~9rc+hnMkFx!#iXAu86*+d4s-+Lc z8xt1ew1!)qsQ}%Se@E4ECmc9 z#{J7Z`oVaIIFwzQJH#p8ZpQ)N*nz^l^*L)JGSxqmG3tf99}_*p6+%ggEK`*~j#^mc z<2aiXN5l5@NCE6t{LCd&`JBpPRRvlG`NZK=g|#2tPfec^+1w0dSmO*>ZTfPisQ{MJ z@@OX##T<6>>_3AJfkd1L@LTNK3JoG3vZy64mz3f?0%7ROc z(YTMzwsQTW&~a56k4i&iE`3`QG+1s%9}4KPgyKLi)>FK;$ToTmb4})pOAu)&^%$c% z=q8vf3?Osef)nPRAWfV;5C$tUXabj8@@?eIwU7sVuAqsTH-)^-bv=Cz4omp-PNl`? zb<7R(cvVVdszQ(#GHMM;mua+-K;g@@IKMr6J}lz0Ky1j zs@Lg|S)qCcKUo%kyB4ev>s}GZGbJr*$tGQ(nT(eMK_VwF-k=zg!AIA3u2_)qRG!lJ zDK` zA9JrwT&n0<&(#G!+x*Ap-;h6!*@GMjuCE6@m+R|1e1(T4a}<3ujUVcv=3)CxPd>|7 zON6;s?!0PvNMGqugu-6k$lTy3_cs~NNe#+)7e$M6Wd+(jgoGO|sIgO;t>8V#VpU!g zqA%Rc3VVI-R_gFxoUbb*d-odu@Cr!-bp0bSA6o;(_4bB zG_*5!i?(cglcSfYiE0M7?Wq@mqSVjlifbM<*#wa~>`z-2E*hLkD)mXV9Tft0Dku$1 z2V1?#SxgaUHK*V*KEos7YQ~b~;h2Ig@wE6PwoxDafzZpzaFk%u7k@;P3UP8L4AL7r zTLS>o(o)Fb+99V$6okm;U#4cQ*-)o^62?BSl~JW=;zL%ygms*zGFp1u0O?qbCRj{Q zWR_qpF@N<|k``x>mZh+r+HCgJygLs6#h^{RS87YX%LqZc*yBAnggB(CqG{G8$BtJ2ZYv}IoQkA6sUN^W>C7B@`a z6qd@GlRi_xMxu&)u-3hMEEL4%}bC0zm=a~XFVDc!mFA$TuhNia=Zydh37M#m+oNm#@ zcJc58gPmW>d!{eu;Eq_-*Q3k%r9958-K4%USS%y(WziV2l7wdue|{K$q)K>`58j2{ zil_ku);pINJ&4PTX=*csIK(+&k*rvHE`%5U@ci!=Nn`Sm$5>rzX;w%|Ez%USe~UD3 znE+@?MB(CJFAC9{=)LhZBbOn`jxl7p#d%`rOoFy2K2;7+2SLcJj==&A5$`aS{UGt! zGjd@<=IKVTtA$1x4^n2yNonZ~jT~wEqcm9pgO74SAH12=J{B_zZ{W?$-HL-_CQF&< z{=p;O6;jP)S}FA%OCXmaa=jM)))8%9SPTM#;3Q#d7LDr`rbK<-f?Rf`2@PTL>$uEt z<=;25=}ICCre(|>(5%97Fr6_c*ETv7sAV^g)1I#!?Fzcul}r`wjp**(%JJc-p&6$ewWPaT5L z1Pd2<#Bd#^lC8PBx8kKD>lQ?GoL`KbZXqmOsZaSTl6qP`N=(wdR#C_uAW1EmAY#bCrC{dBgs+0qpl$|o&dp#ZncOq|OIHS^mScxWcF z&G#0V=#AzNzL!KDj$pfHD|g;sP3mhb7@LPfjg1rcm%>T3z2+$`(!{r%g%&1sR9MNj z^6E9qSd059rRWBsbiFMN-jaG$gqW>u7>Z(VeLhJh0sAfRZIRG?Jqd4>7Qh1wy=psc zMh#ujWT0b|NhAFmN(@Rat(dK55bH!3N`g+Z^ze6dTM3W#aWt8vf?MXBz;2=wc%I}kQ%@(MB$pL3}c-=}WfZ zj}YC3q4W5WY#XnTtevoIBw46HkOG)26rnfth9BEU#Nqo9}mm&rPD|;FfeK@bv-E}$=O@$I|ouEBWcezs(ls;9=nN3h~ z06B$tR&}hAnD~X|^!i2%^*6EF;`As4(9GOj`nD5(zekG5wP)E2hl_U?s=%^)A_JCJ z`dQs{bU)*@(W6Yup^^vb(mYBR6uaWSCSeLfnX*<)#)b=tBh_SvBsV5=ujfS@^n|gA z=8~IF;-byQaa4d8mP?_GPG2 z1(;Im8C_{ru9Nm$P^j4w%H^ATy)q$0EYFC@S=YurKON^-ej<0`a?u+MpK4{{J;kMw zir&^Pdn?(>1~jxOADRmDmn1B=+#qCLpEFN>XtXqzjStz=42SOnV6AZeVz7e2&XT2a z!|1W1kJZVPAlrx#2TUo;Jeq0x!qf&#XVwMa4)_Drhb8W&q6WBkCn(AK0Rd8nnxkgVZ@4Zb#xEw@HH4x~^VLEK9$a znaUp});j2@I%2&th|j@{u)?=gDH4Yp@%?sjE+BW`iqJcNOCi?I(6 zllZ5rqMKw{W5{m=jJ;lLrqagvMfxxGw^6DUHJP2!@|XA-LxG~&f8GAtOw_(py@o3U zgz3QjcUT{=!heHQzgf?J{bv6+hq1JOq%{h_s2 z_6L(|yft?V+m@PMg95_JahP|s-DFl_4q>JtX?dD9_@WrzvW&C^aam$%e0OV@AS==T zY7;#z89{f=tCI+l7)dL$B%jDGiBTh28`{7j#4wCH&T?aHJs~Vb=T0H|e4*Nj^~4h@wY)UH(%2A0X3Q!G?GDvT_9@lZ!UKBcpczZ7jmyh$N|`BV z;#a~y?@vrB$lVkvWaM}`*~Aue?NI^_yt|9Br@^rZ$!Px9DvGQ*Sx9160z?Z`LpiLV zBFchwLArwld_y>2H*3+6T}S2eWmGQ6Xd!Z7A##|U;awyFjraYO#>!svU|GWX@KIU5 z2B6(i%m@v+u@?q6tLQQ6PQ*SeW_ZWVyj{?5OmbeS;`yM^PNS#*lj7QX8@mUzAYvGH zcgDFZkkP%xo=9*HAr)}2x5O~nX*y$cg#4RyBTChxZBUi5)*HS;2!N@<>oePv=v=*P zQB6=jv>Uq&u>~6yGn2?td$G)5Zt@8<7REUT%_lL=W0x2XmwIl2op~Ks61^0-OQZW= zv9M9n;t93L+-BIR8Jbv21{8SEYFCZa&1A^(@u7qQsL9=j>>AmfIMqH%7Blo6SKAb} z(}%!VXj)|X556-8CC_6EiJeH#{8XOF*?Z>(`4OJQC7Xwjh%MYiU_31b6Di932_%E# zB1w7cfabv>i-QeD_UnUmQAtR`ga&v+*_I=gpA2y?v12YD@4o? z&hFwvn%k6}+H6nIXBxB;U=vIE^o_Z@Y{MUn z5El5*op5T-x)7wuHIS-d3@ir?wP4>wbMadB%xe>|J^rFGA8(YF>Lm;o$BU9|<$woF z5NJzLV0TM5Wr8wZ&?_nx!Vrj1DK?&TZVKv1^2S)oQc)tE<8upU1wuxKM%lPo;AtrE z$$in$!1eZbZDeiU=q)Bu3>pRFkrqu9GAXTkB1kMntM%%TI16+W*IT4*O4i2mitC!t zqMkKYJ(h2sl0_5@Sc|Nf+9CmM(xi&^78t?u0u;3=sh_~v#ctAa$pq`*2=pz3?oIae z&`G5vM)}*cUCc;A879{$DyHmUF@!w#arPhjh9s#N^|}~43#S(2juC4l$s^G`j2XmJ zGC}DM9AC;3;R|ZTvs8vmH-7`#WCUMugFOglS%~c(4)f(1=10SKGHB8&lUaJhzo-_r zZz+Gp)Tp1%#k$>uZZ?G;V)hAHUu-;Z+3Dig)6nun`b~TbZ3A4;vx8iF2aFlw0))rM zq}45eQL$HSTdKA=)|Hy-m+6YS=>0$$M3CTO3N_D2P1vp;$YM-xAC}KcFm1{c*KwIh&hJukvm;A9|FIR!GyxMS1S}bl2ci~5D zT;e?L=-wT(GDLqLAm_!`C_5?CNkNttG#VwYr18KUvO5ThQF}Sau$FQ48%fPh~Q#_^bnXzlfgNZS;?KXlY zY79PZkkP;i3utm6kwPsP3Uh~M5VPc$hcBCj)SAUq6TR{tJ4@YtXrucA6k{HhlBH}X zp4J!h03-wBtM(cqllF1vyo8&UF!OmHZQGGOOgpFe_s+ zT!__U+QM?~bx0E`(+Z)6&4Tkwexni%;S;taut<|HRA@Rtqq20@fS~@$(tIG@-Ocp2 zam@z`g;9_Kj1_AzXmou3%p>{>Uty&&m zV4CHl8{^>gl{mP6O0CC%m9B$eiX+FX3!^OgGJ$Jf1~0rG!F<`c9nKH<=o$se;AG7? ze0wBRvS(P9o^8jm>86VLBpVfLc^a`|T2$MKk!IzcEq(`r#I_nU_h4EvGv6h%7f3Pu zQrNr{$kMuG()@q*vuxy`k9X@c9(_yWq!5bjnJFD>zm}N0o`0xHFpjVVY_oeGnUg9_S{- z>x}Kb;nEZ)5GhCLa2ZuOhrqM7rT|qls`iI!gg)i#~bU|8sbh!x!OEQZJwXcr{xG3x_y_j z!y4u54osxIWHOVbN7(T?3X|P+^6U<66P!1Uo*W^FGU7}-cf0iZB9a9Lrc)-<2t`(;#wlLxW?!Ql zNG$#bZ})^>eI5`&^vOsW`uLocBFcCcv_gd?7GB`7A`^QYR>Ba7%}is*sF?#`0>pBp z@y*~WI3U_#1Ig$Vs}T9;@zg|K>87`1D%?g2aeROGZ|A#oPzj5lO)!>R=n8eCC9~q7 z#FfyoN|G)(DQ3Z>!Le&0NfHX8k2s;8B&>CcusWj`Q^+0e@v)*0<*buzZCWzJWznEm6{3&^KsTB@sBA6H)(z|&#qA=-kcaqI->T2{Vam%znLrtRl?t;7X@!|doyq7QmXWWK zD5Gu|#)UtyPHZKUkys=cxM)BzCPmGQ`Kw{*<~ZZ$f6Gpxf`yS>+k-@b&ezMfnW=(4 zS*FsYLE;W+F_lbFP6sRM|Gm%JZNo%l5ekE3c)_}9%*BN6Y^+vpLMAK1x2n)+?Yy%0 zWXO{!nO_UgHkM#yG=dNZ&VhJgvTp0LdhFjEqp}s+D$CM8=^hC{dt}#0ZZCM zFRT?!HLZQw0wgTVOSWs{zOc)^YDx@Z+pZ6$SqKJjt9Jik6;|v8Rnej`b+Dzlw4r$C zN$Kon6DHJW0(apXu1Jo`Z6uY*LaZew%d{zdB(KfnvajUjx9b%cmX4Gs(u>vdnSnq- z4lAcxvZ>p4`dE}WY)(X6Xf?|u1y7pun!8RRkBqrKz=zFw?SlylJIhLrRF#cHiT74x z2G)p0TxZFAK{3u#1=5UcBSx)SNepAUlibsJ6y6*q zZ$+$c`^&BM8a;)-y<-c$oM&OAI7Ny)vM^_2##4y%iCV$RU|vNv?sS1-71`42bugrD zStDJ1QHC%8qmW06!x#1>q8D3{(dfo+>`8Dcf)-4OMXa8ln4Y-HbSCRv(tlPAb$cXN z1;HBgat5@-=bzvk#AKK8m<(3wt{qf()h#bRA;#tmjJ4&@Paxp`xyw02qVvLgW zZ-y))%$!fRwhBg*)@F8=hnW~Gu}zrI_MkHE(RC6-4@k1?w<2R+S}thO3)*ND+ys6g zf*7_VWoRo^9lfsHI*Tot`5{Jx{g@(0eBL<+@wQHF(>82zGUSz{rW$?n1GMH(qclsJXyXBynsJA8z-)|bThRH2{EFk4EGxs?aTqp#I7RxHe{)HP= zHo~wjj51;64&m*ajM+!Wi}0t5m(SviDmMkU9D3%HK2VkLl;U?~p3ux>Hb6rD+T7$p zetD$O9Vu?H%mvAj`8t9DB2AffHlqn$*vnUgMHAaJt4&|_WkFjglue?5p=6lltcYz6 zzL(*OMRQr9nIhL+k{~knrm;ie}vQ<;J9Bq2s`;HJC$u^# zF@;)Zqnqy|)6RVMZ<#hxJc1ig7E&wd4Q=IuXOMp49WP9x)9rb##4K~KnH?Xhn)w(k zapNyt8-marXt`2*+%4#nS-_{BTg$!R9dSdd?!-Q)~3rCOE+q55>UxH0+jrZ z|G_NSEl$8hpkO7>Xj4~k?PY|+d{AZXR`rm!1vR4^WiPp0BY17^$cW!5uB5-wL%i&SB)TV!Sj~lks+Vf~cCGJ|Ckc9b(t;oGSQ+mGu?bs;sSj}E%e=-EoRfajJXhngS$(#jqW@qj-*Qnj>{j) z)kw%}b4|7QOS54!U%MNXf9GMZ{CO3P0a(-uIc(PH-2-Oyh~zdn$RszArEVBYq^Gat z&@08R5V5g)UXj~JalfH7pjI&?45dgtKLlYVpVi+dsCpKaR1CE1OSf_?2;zfA>q5qi zNz@+GTrzd;E;z$HUeAk)FM(seEV?I6vjC=d8sHYNc^7+X=YKGyU9?=Koc`O+NALk?Km&d9cw-LuG}#Y$$Jk5Jaaon|f+ zr#u>_0rl(=s7xFq6gGd(BCw?qLT;NPt~}`RV4p$gD>MS*Ti(dsJGo+%#3!eACTx_6 zs0q^PrgMj|j3mP29&moMvgw~VELvH>KBau_$T;V*vp(gW!s_7dS z%=vf9K^Zq8Z8cs?s&r@(bw}@%^DGx?z|`4bbg+GEXiua!dXc=fM5>BWwOFgXZTZ9c zD5AILm#S^P-U@;!6z+;E*v(~cED8eeQqI_Mn8NzAzMFs$)~sdqCoUWrH(9tv2@uwua!tQ0k}Y1xFGHwBGs4*NAsi*oEY+{o?44j!bUZ-9)pszv0nt5v^*n=cdjJh;w z7T$MQ38pu%@$wEb4H#O<7wkW?9H=RnrlB71;`}*nRoO$Xnri#!7V0IH$z=;a+vQSQ z&1ackiq*BSptz!muGdz|5Ge=~EN)}4U|lM(H>=`d zw{F5$l<0vicg43CE6E4hjcse|=N^AF?=+;F@c6=$1&yK{OtyI%ECJ5ArN~?K zGN>pr@BGGxq@x>o4>6%+e({Yvji`wK-zr(tIVQZFfUd(n z8(B07?=zWA?E7@_vveU}lZNjmNiB);5gL=B(@ymLI7>^*=ZtOY?md^l{)O3+3`@E< zBPe#H{DnOCFq!|gxXA=*5EH?mt@DG_h@@*CQK2W)Gu~9LAf!Ubgm4$phLFD^`@^V< zANM;VuWf&9CAoL53B16&Ps*|@G0AOnm(I=4@nUp50Z6lcUXY+?6=&G}ldIh8k8d(% y@-jhLPE9tl=MLCGu91*&JvO_k{OR=G$YAigJo=aq>=2wDuZSuW16`$_s-0f?%X@v zqLCDc>BX30Vp9@2p@`;$@=R|EjT#l<>c)yC=#0!Ld_yl_b}no8usVKA-D4_yuqpytFS# z%HW&8QSi8xNzw~G0X!CbR=^j7tGIp@cr^HF@D%V1;Dz9A;Jd*yc<37N^WZt)zibLZRp5KU%fYMGKr`^wpy>5;@DOcHamZ9l%q$elPf9@U!4D@S2DC{XGHy7(9;qF9t=|SA(kW5O@-J3n==08oV0( z4ygWJ%3$jEZJ^ru_<(yr^=BWbeq0Nl2tEVUINk`V9d8F$g6{<(W%3W8=HsbMGQD>Z zsPZobUjgm_Mb96BD(~l@%ISHixBo=&-dx`w)N|*9>d$&mbQ=Oy?ru=;JqA1loCNoS z2f!P_pMs*tvmfT~-w5jdtHAq!55CCze@qINK=zr zLDBQX%bed%1Aoi)nV|09Z_wL&HYj?Y7x1B=_-6>*1CEC44}-8k@;OlBcGO0v%kdzr zo16~9^2sB>!Mi2NlR@?4Bb$=skHK3(^>61;lI#L^gQDBp!25!q21WOO05vYZ1XqEl zFo_u2%md*7Et4U>=u_t=YT7?z7o7USO>R(PXu9|sN)BEY5=F#gx@#W2++WYZv{S~m6>wf}M@Z{mZ=b-4d9=r)0 z0yUq$1|kBJ;~wF9<6`hyuAc`&;^bI_hT5?W)bpPL&jx=5s@^lUI$x{-Mb{fZNS@pT zt_HsX_JH@G^T&b90^SFF0M`!$)xVA4GVn3r(cmO_3^)U7UUtIo&jyd>`o*B;_p8G7 zJ3zJP{op;pFM^1mt`=5ep@9RO;`wnmw_(4$p z`5vg}j=IXn=QvRG8w4)^w}30bCxIH5H-O&*-wvJ*-uNh=e{Tgf|Gom}1KDZtfKm7@KFYxu?3Ezz$ z`w^(;j=RR&bsDJab3oDiK>;rWMc+YC?>_=O8GLlOUjy}g2bA7?3aD|q0aSlq4QgE9 z0p1sUHwfvI&w+Zb_X$1@4+d4wBS6iUQBZWOgW{_WsNWBOYUk5Hm3tkicD)D`-@FD? z``!ksT^|O;2mc*xfxiQ5V5{VG{T8Tp9y9LkKOI!N`are!f`HpW@y8Ts^aao2`stwf z_jRD!^I=fq{a2v)@blnS@K*2=FrDyv9}nvOGr>vlOQ7DrsO2TCJg#pB zj{(0Hu5Sas&h_zifBuJ{e*YDCPw;LHZ_l!TCxN2J>7ai9Bk(xz8KC&`hH(8B(C7@R zAD;nL?iawd;NO9W+~l6K_y*wPK+V5TfmEHG{X~~PF90v)`Y*w!gFgW`g6*cu!COG_ z@Bad&Css3=jt2+9lfWII===n*AAAP*0Pvll#`}xlx#0Ie$*JSpPNzQbD6V&c>gR6o zRPY+`fnW>V2EGhD8T=l&2K-l0{JFB@e%KYD+W%fqvg}FdHgFGkC3ue~yIy!CC_X+2ik=?_d%^F4tH9&_*y(a1sQx?# zRJq>+_4~uGbvjLg_vd;)cp7*Ucro}E@Ivr=;HBULp5lD>Snz&akAr7}PX_M|z7o_p zybC-7{0ev`_|t$VJk`hfJW%w$0u=u@z=wi=3Z4#r348$f15o^X%+s81DX8|1fvT?x z-UqxE6d$}4R6pJds@zY5>%gCZYDX{3s^!COG}?=Yx# zeH&E$zYcifGkl&s5EMVI1NHuTa69-2@GS7_4_%X z+I=ag`8f-!-TOhkcRi^0UID6|ZwEEs-v=%OKN+t722{Jg0g6uF2Q?3W2kO1!pXK~{ z2DpjqQLqPmDX4m00jk`$f%gL62daIC!Fzz;0LACu0ae~F!4trvp6$<{67c?@em@sf zdFO*_$A<9xwt!cGdaeZOxtV|m!FL~py$eor|KZPbdGrQQ*QZ_Y^6$yu3a)PhmxK3s zzSF4>RJjiW*MdW!`qKe5?$?6i?;F6+f?ov3!B@V(`AAN)_~1dH=zAro@^*vAf#cxO zU?cp#FZ}*g@EGntKj2MZmFri6j|Pu%RP<;Ow zQ1f~gRC}%i)xR6Sjo{0}_1D3Zxc&~P`S=S^^g88F9M1wZKhFd8`!4WQunCHPo&~D? zH-U(%A0wV>wBdqDN`{opC!Cqa$l zS3$M^Hc-#~5Y%%&2G!r+fRYo-Uhd_s0ylGg3Ah7%I;eKt3aXsnf%gTE`O_r%7?^@; z*KOeG;E%!6z@z`n$NfxD?>!XMxNHE`?h#Psl)*aqWKiw?5qKu}yKsHxE4;k}p!%~O z)coHB>iJ>tf#737(dDV&8t{7XeDH(dMc|J?^?%hXz5W4^TggUH?fWLE_J1D~U49E5 z4<7$2Z|9ky-hT+F{$2#03~mYc9|el8j{{Xs72FR#1$-m;D^TUW=G8v`Zw6tZtXTHvVe*p-olS82TKlFO%uPUhbp9hNGZv;ip zTR`#ktza+s18@a+;-7mz&j;Vf^=05kz+Zzc@cnOadwGCPi0-cdMVHrsXMleJ9tC~| z)VO{gEQ7a!PX{l5lh4<`23K?aB~bMK9VmJn^JXvSbWr@Z7S!)g0gncs1CBw57l!-a zVUnr-AA#!MuR)E+v2XGE?+c3Vt3i#=8^8;|TR@G^KZExKe+6C%-ufRsOPER z+1&r?+dN+RD^UG=?%Nq#@cH0!@Sni*z~kTHbbT0j7T4Rswcrdm2)+swAASqeIGlR3 z$G_HqqTfrwb>Lru8jo*-cL$ID3%AGa1zybcqroSFF9Dwe-t#Zr4!Z#qz1|N#75sR( zKlDzwYi2;r$2Wiv)N`QP@l8C_M;k0-8yjc&byglH(z$S=DOP&Ot51#UFpHG{? zU0h!aia-Ar6rDTo!FB`R3a$fp|2Jd=_#E)w;304a_z|!NJnbY@H^;ALC`$SOVb`U%bd?l#o-U*%oz7JG8Zw1c*Zv$1{ z(eL-?mw{^E*`VmN2E51Jkip=WxxVrPw1w*xAH)vfdiXMEwcnf$Tco@7C{52?gT*Tx*3ET~;T~pvgz&TLmybT-x{{j>r zely_oN15v|?!SO4|DwbG{$^18wF{K|_#;r``c&{#@MWOr_BK#-`WX07@QdJC;64A! z$Dt1#;d&jYemxBw1Fr+cXWs+KELpj{`*eweq28Td=&Ui=B0ejfN}u3rYKe?J4&pWlG1z~esa?_UV&_YqKZdK7p8_##l_^+`~4 z`!c9@{sMde_;27c@P423=Xycae*q{y9tOpKJ3)=lG^l#61&;@x4eI%u!u26g<8}+E zas4c)dcFki2Y&+I7p#8X`SfX^>Ukq5KD!wdojwbSK3@h^@7KT+z<&W%Zt?}6|3?SB zFQ|5&2ObYz28w^TgHHpW4?YDv>5D#(Zvao^`Xiv)_XTh*_|Kr`?OA{0?HL4B@8d!B zdj=F8JD~XHMo{g!89Wwz57+~K4BQHS61)UF^-Hc79tl2%>nDK?@GGFkM>a5>oXKU`j%3%-l% z{|VjubO#gZKKH`_s<`)xQsb;-im)s{il6v%znI>euhU5;*Wb z-R^!7D8Bg$D0O2 z*gyLHYeCK5SAj1CKL%b0KJo`{e|{Yt=X&smE}Q>gFC=U@J8?j;BUY=@VfuycHP~7?ALq1w{rhBP<-}+pSb?` zM{tDenDT#;OoJa;NO7PfxiZ?0-yO)zyJ530D7`>6lz^g0&22iNxob^pw8eGaJhtpZO1R|mWV)c9-%_1SbhcD2 zXKyzq)82ItY^Bv!TB3F4H znXI;}^{KQ?slDe@N~paxkoG?KfvvI$JB%(^_?+Ql~9eTxI`kl}=XaJ@pLyAu4L6N32Gbw9P%5>y);u9NC#W#L}O;QR)I7tM(meIt%%gp*wh$4L6lmH?w>l~8?=UnfZ$SyMZC*lYs-c%f+Nwxw z)mj-mH&bmHGtEs`>S-yR=rBk#m1fomo=@wQxi|{eq{(KblD3=GQf-l{OZAD0^U!FO zKaEAaO0n8(vocfd%%qJv9fhgX^iqc^OYQ0etZ^VMS0dBFhQpKM1}fi6L5;Kn8}Jgt zAjWepTCFGgV5;fTK6@yVkA?@ke( z^3^EOsxeoGf=n>oVA3`^b*3OgFjK8pXXrib-srT`(oFVCqnS=ur=}}SJ=TO6^{M`m!rMfEh&iRngzF;&@=S!t!PAyn3g zma|ch{9unoY0#(-B!eTv$>8Xw;o-E@Y&PK0$|TG&(Wv*Fd(Qsz*Il&s;;x^Y5V-LB zLrV|gcORXI=}@Izs@7WBwOF=NFN+#+=&fcZbEB@uTs~%hq`mn~{zi_L>ct;(V#HU| zEt*ZcU8d)chN+(4$UAN#G`7mf1}UPTp7)qjHB6X#r9IbZ?kzmsp4PN5fshqZ_P;8t zi&JrR3Ws(WQAhA|UwsajF#SxU+^L}!{AQ`%V04>lnJHd{6x6jNf;}M- zID^z-hEd^ct#P1I7HeG9XqHEs=(~YNb4s;~PRI`AF@@-dh=|2H_0jG!6AbB!D5>!4 znCdPWvluXAIIaI5*+cccN)b=S2%eiSr*la)$H9IR$>{ME9 zp5kKcz^rPk)!@K$(@hQdzuuwJ8Wq)<$@@@!CZy5{v{`F!byhM=bVEs)R4&EA(>#a+ zSfl05)sl7ASes5^0Q+kd3Z-c#S!n)L1)`WRNd}SPE>9t28A()Yl*%sqee7rv6qojI zr7MdT!72#Sn9*(1w65!^z89q)A>MSJrK%NIl5in2;Snd*`OB3_!biK=V!ZMg_A2njrU^NRx3Ur`Suq zy;J418&gv%I43WqQCYjvUn`A6$~#b~*J;L;npR3Tsx{FTGmT=sp)yfl%qIdKl*=3dTIO04RoSj9vQU+s{iL@v@ioY!(G3*igJzl|RV3JT!KEGUKW5t9&=1O(5 z7t3@D3rSVR*_&dWmNq1VOT9uDCulQIFhZ4-d0uIv*B`u=7Z13&$&?JgPIl^LW-w2h zMWLCT8#Sny=@M#|x*D}I)1J$EED9>MT8#+_2pB$&Nw76dgTo`4@*ZSPMf*6+AXzXH zr{1W>Y`aqKXS#@2C8P906H9px6I+wW?wFaA29wUpNd{pdCUClwOO*cLT%`qplZ}he zS*((5#H!YV0TDUF^?ePKcpGaCQ``xEOr^s^$;L+cK(Y}M!ThF)Hg;ObN_+^JvHXJE z%LERS((VKSlx&2Q&QfYf_-{o`TgME9O)`N*RY;*^Q>osE-~} zBO)h&q}aLXi1Sk6UVl1_9Ew%eVKg`y$K(iZR2?A|+^C@JGzUsDMTh%lD6MV&gX|y4 z1>KjNp#e-b=~q*os>Kzd=9hJa*78(rcsEmcXVTHmcx$3ros}Sny%>|-9WHRy;$NyB zCi54?AY2U$#ER^On$=oZV)k3G=;wQF7$?)th*o*s>iM^P{$tIlIWD`QMnPTOon@SVYm2kbw9o%$G%vY7y$Pdx1 zEc{sL9$CNB^{`>JKJMzF+PTjp=*S-g*0t-mGfmmxy< zwx;0(jF3&}C;pk4Wj>EH?>d?X+^E+pQuA`wHFGjmq@k*4mLZ1FgD2>;u zt!a7X5-Ki2mgZO!cV=1a!;DoaQ5UjVut6fib`~|r zu6IFU9%)w5fiHgtniOI)%SSk#f#x47ytZ!CcKQp=7uf9*IRqMY7l^XRH&g;_l!L zZ5J8U5eZ`256yGATd-sUp|P>)zBQT|xmZ9-W=O5Jx?tR{a3Q-w!;WF=7Me#1Rt%y2 zrkbU4Wra5|^rc%q`cQY-(i6dL$W0Nmosjz*oM1BA@ztbm+LalCdZp%p0X#5^n3$DS z^;k)+-Xb%(O5$baA$n+rIL7bU9`})lj7v0X^G)ews3GydFu0N3{4pI-U}M2ulj_ujSVr{5=0R6!}ocW{w_G5UIDdc~^Ql z24s^k8*;NGbEYjF0w?UOOm%{Hwes?zk)5lk4!d_2e^)WLCjKd5QV*#z2^jlu3)fmE zu_j4SbKZ60s?2U4g{qigZHPXFzYg0s>aaF*M8Y?`X$E!Olt;S0e^qSxzG@@12~S53 zcJvU`VofwR)XS9#)2`Bjfs?CnvCCH*hvIwL1PU&w4=>h~dEk^R|J|LZg$zM#JSYv1 zimT6N#^5iM@k}PfguR5xkya~PtGtKmopWl8u|)YD#kwLuLwBrf6nZEdUiYE37|c>t zdH>rMMN>7KiJH{>U&U!G?6Fw_e$i>SnO!2AHNDb`-YdMJd#c{UFfwSYrn$WyB!h3|wesN$GoSD!HphmEMQjhUg*wN9lT^Gi#EOIjm5L!qE(a z33+*WZq(a@k>1Q*YEYQK|CM~M5Ko~jv3s5dT}h;+fUf+>r>6|9O7$Cw(hcVfwU0gId!G3u_AagmBqBpRo%9Ivn*hX@XG zEku&iiCsIlT7%uf3o9YUC9~7B1FS6sstsetmCG=S_@adchY_rrMdiL=+g(HC#QN0g z!`x$H8Cya41?5?lD3b+z>!n#wWE<^GM^i;2eezMFLXpL|mP7y&Yi2_XfBG_%SFg8aW`=l}sk@o+Qma)e ziz#TF7DeDKf@Kr9TFa9wW@eHrsE)9yUbe!h6_a(c)ZCDc$!d^qt)jR;-^*}bFQKmb ziG(4Rm~~-Bs#Iochv0dL^+vL6uo_Lqs?I3AJA@sEQx9?Xr$q;RSe?RjQy@(&16hN~ z$P3w=C8C5Q-&QZDy>1(64Ke{So-CUwX4IXNXk*?P_flebAu@FpKA*1CX5psg*^6<# zuQjb{Iir!!JT}heN&?H(Dd9{K4Wp`#3%4dRO z+v%=ghHSTWqV);VQXQ{rnwqAw2+H;yV`lVNT#s@TIF$+PZj=mK{b|P2N}KS-_}(fU z9w%Wo)1gdohn#59sbM1ex=d<%G`JKjV$Ci7gGax*elI?|@MksK9iH{xPgCk~m2P;5 zv8ekkc^zhM3=hd5>@w)`r>x5u&sru$BCynNYQ!E;7&;k7`I|P;Uq&SxGe(CIgjZC+ z2C+#s@EXA_G0rsh651+SIGXYAP$&Hs&25Hq!o#sfNIl20qDBIBFpevM zh*WH$h5t!$nqMsqML!K|5@c9dzaBv42AfL}h(@I)tr46vr(G6@vl581&IFP-2z3VG zfh0sCEA_+N+SwB77Qd`%X-RVg5~l{3uek|2(!ukYNUyB0qB>e3?%LMh4EQdY$-_gq zttbSEcXMA=v!qkzXdZy?AQG{NJz8=uE1+fz9FYRy4Goqlu3VkPQgjD}IUgKQlmn^ALI;LgrqysVyHuT0#a-Nwc-Fw{&2+=O&ur>VnQV+$Jp!Bdq!F){zQc zzeywJp8QU@kQKa1RyprNFaMJ?^FL}Z#5D8rR)1f##CD7RY)26{6zto zvN7@_gUqRsOD1J$^IR6U8xYx2v$PXr%T}3hr#365o-OYe)(T?n%J%BiYg)-xy>ZOU z%~iZRI@xXzM;AQw4Eofk(Edt{EUe~c@jrh|zlZf)X`dDrtr8-Q1w4MKCvq2X>4lGI zB+Y8A0gbG1q=i*N7*zCxsxG?em<9LDsO=gL^x$e2r-AG)tg~okjNM@rD%r6+WN3q~ zVQ@3VSRjiUhMEUbQbo`k>KDq%b3_Uuf*9NkX5*a224Wv_LWI(Mu2@dFg`i2z2?kd* z;aLrdOV^~e(`a$otg}+6gOEeB+S=i>6j;4q6D6Cl8Fk zn=wSv%S#q*@~Ajwx}=f6>Te-N^j^%r2~Pwr4G%(z7$&xXFy2Q**tY~nfWFOVwtOmgY50#YDp*r zwyk5zr|T~8W%j^VbX)z(V&B2L4e=RgDC;g`Rvb1dtxA685gW_*-^C3jStE$ z?^k71^+JNMX4!i-a9r3vqV5M*J&r>$KIcJ_7$}p+=1N!$AO@6=m$Bv|yMPuoXC<0Q zx-CfwGyUDV%_t9NN>?L8V|T@?1#OCBQ!h{As)%r%w%l_mDAteB0BTTN{8&@R5GyW= zAVVzwnvEuA2-VDJ$;53gYpn!h#p3Ik2sLpD;o=sMSgJ*Y_>sr}!MvCN)IKm97Qrmj zSebcRT}Th$i25`(%Yhy;dz({=t=gdE9mC-Wb3~r8I59@R&>4zoYbdI67EHD9kgbv; zTvmoTE%IS`%@WVWYtrAd4R+qJ7?g8LEZb(j^2>a;5mjsJQGoOcJXWQA$LnFqKiiuW z4B2=Yln;XhgIIi}T_wT2XOl&P@&ZAofA9;JFZSf6io|tL8a(i!t z{BnQn)5(N|86rMk7#ll`*CWCyPpCC!*0CzO8`j4CmR1|in2ZjPb#v*zSC(oxnO;^RC9v+-EwBqt-DUlvg+DjLj7uc1-D zzZMmLd}ZP0zU;~H$oy+op_o4G2Mop#zlY&!veq_nARTF-9}aNaQnekisKtnG5+Dck zrwmr;;j3t570$52Zm%&p6fg}7h?nJ43rjDS z-Tuf7{&cJ7ZkBY)-?W~FlA;$=L`JjNC_fUXt)y3sjg5?|w-Ozz z(`Kh`D;o>KjbWe8XdbRb36v-R-j{f{1y>Fhr_gW``Y0r=!-oABYLnJCQZimGw{=wP6*y9nXMS%N z;)0{>?o$bA7IX8prX>4e%=~Q_-lqUR)U`8ZRPpr4>2|?nGGxtvb&BT`=IZKny(BM zxX-GzS+<(pmY|X+(WO`3d4y0Fwye^VEJu8C;HOpiHHIS1>i!*Fg!cntrImVL8qt}2B zg!ZZ&*_cGnYqn2J7ivY!8no0c{JW?>8`OQ_$?&I=D6&5_Smq|hlv3i8 zS#p3FkFAVMy)eaO?sRb*EmsNn&5ByUDGefZ>MWv0hZ#gWetsCs@~tdBCN5KO3C!a* znFA%V%nL|+42juPowW2JPm0qv4yKavN|1Wr6peQeLz- zDsxvNO|n>kC-{W8OrZ>Uidw3X1sF~(ZC$*=r@h^C=h$_@c2>^2NA?S$bc-#4pP zy1EN#65OBIEA5z>{Zf$%4pY7FBh2C}ABE>W6HDUG*wSK9kAOLK{Gbb>*#zW=o2qO*3gMgQ>y!J=KGHQByW9bvd6aM*`QRp@0-mU>#jmAry$5)=4SS zf5B1;Q9?5-7#8L4V9bYf6j)C~rBey=v58Ca5LO0mwxr^h!$XeD7W<=>>@L+f)L^&N zV6cnmFH&|96`hCk^8LC9V~^2U96Xzz^CR7a><2<6s3xoJKv?$R!qyBSuj&jQ8q#vN zc4Gz$$}eoeEK)MLYVF!~a+RM@5^=qyWk7Snm(T>dh3$O{GBYt{3v{;m?`{U+3m;u) z|1joq!V7daD!b3zF&>f)GLTRx?kagZ+IuQV;Lz#EcJ zA-|f;3AH4I<2@5@gVofPX>UE~DoK90-gKdG7s|C|1eFl8oY=0)LZzi%q0*O5G0WH> z#IQ>+B^ot63iaJ2xNoC0ev+kb_5$lK{p8L% z$z6h3_<4Z?WNo14%6|4?A$)ii$HdHPuOcqnAt-DQQ8)%fVP~Zp5$;kEwxI_HTC>xo z$P$Yq=}O`Swg9k-*J?(5Yv1y8tYMx;u)(rna!qUGh`JL2u|By*wGaRoTr;% z5{4_(w!N%f@Afl-Fl?oO<>|U-!fAN{0>o#Oh0+s>Bc28O38Up}50 zGli@@|20ezKS~9mt-G?P;!N_zeP{sJTKrxEYmMzXizbAZN6-n$RkpIIe#e}3Gh(CF zqXOK$-H=?3*5W^mQMlFr@T~yS>GUAEno?p!fwMnq>@CL>^F>j5M2B&L?IGH{uHVz0 zOLMjvKns+zn8l*BhPWo~(7cTT(e5$-$I?Z!(zNeDdS-dyj>mvwpwLMnSrJ>L9M-%& z=BCNk)(Tf@t@4i2DP$eDC1or1Jq@HCe=+l-gf@>uB(#YmTXbF;2+uC;R~IyA}zi@Xvw#h&2Of|yfHLu5IX`2+vC3GY;oF!PMWV;D$W-lkBp^f zCu=2ALCt?EY>DOuq*)}rJ??19f~;AF%Sv0fW^_Ut{+&*IUzHhQmhd?ja_k8@RoIw< zHKt_~U;7ltv0AD=ipND*^67R+H*7de0l?JhOR&&I8GVgQdVN|7#FXW8-@ z{el~n?x0yZRm01$z<2kpn4z*@M8XV!#+lmz|7X&z7#1wV^x`~`^J}_7GC|Sv` z#`a+bu*q7OvCx~41rMqH_- z!K*fp?%1~3U6cZCNVKX2-0K+pB`C|XOXGhFtWH->UJ-jZd(HaJ7-2twEBt~C0`i%_aZ~F)ft&@mj*#8 zM+#(WrkI`PnG|$8?*!5)BPe#N84b=u%x`qi9q9TIo2F*j&@dBawZPZ&24?{aXVduh z9xTWZ7hh&KYUd}t3iK$3J>+oWN6%1YAfQ{*LTQ68UQ%Eo+Bi+Q(L`r&||di9|D08z_FMz*2X zW(85m)RO!&3r2hYyVh(M(5f(f7g>p`a{b*U-tax}3d&wttA}G;WT@INN@&S8MfKS7rM5@;Z#vkf9gZDo z^=x8u7T4iD&xb}28aG$UJ#0Oh*h>sY^@VI{e(mNim7&n2KNjO|aSfj7p@2`%P|Q@^ zYHRZ<@eV%PmFutfB#91qI$+N>lodIvc3XPQbs~0nrDt<-}TlOpWXo%>RH-g zhgHNRJk8La5oO@=V~BjSh}t}0y4Uv2YRqX`&-Mlj!rDn^kH(`A%8;tom{k-t-HS~a z2BX!Z^pxE`GB)(t;R!(=^lT0JI;uF6OqCwXdFs9wncsek5n;6=pK8jJC#2jnXkfo} zbi^f7Zn9^`T2Y!3rSV}*lH@ir}rYU7l(i}+4Gmzbk4{OmI zvo^#+hQMq+Ix3P&!VdPU$UCkqm%ueW8@WgbJJm{V86v3;bY1Uguv1Nk zK1%xNSISzQ#QZvhoOxJ1!%i2bvsrptO_ynmazK>Z{%LvtcRRQjh& zb4g$y5Z5A}Let~1{u$cOdI)l)-0GQWB?^?=?#sZHb|d>6CBWZiNGDng=u!GwCI3@{ z6(e6soH*g_p54ycN$w~qg)7z#@^_Y{BtmLE2@UJ3P!Kt8g@ux=xl3Vc(?PS0viU=d zzP8IHraooQ;oAA^)a$l?-{zm$m zhwPV|*?;7JUBMcI|Fy$+GuW&8dk0$@>>}py`PC?Svf(dv!k*Y=t2ps*uV;Th2iA+9 zb#6V3E;i~PF8A;Dp;=!@pFKh{{EPPf~$t@UfxOyPQT z#s`?bYqo@44{KO|E*nSox5|6hSm3s`Y-Gn+|0XSf!?FECXo2-9_xg8|E2?Q0Zk@mO z{B`|n&+lLR;B@Up>o2(QL2LQnGVPG2o`jwK?KS9#YW-qMdFGtuOFHez{)gqysTOh^ z`!|zxB^9sDR7Y}@d* z&H0$E8(6#SQT-zg?O*HPh>(^hU7rpN8{P`L+^O`CPg!RhBldbue zBpo#hzmltb&7Ri08(3e>#cnO~?sR*mHt$ipReUsS)c)|~`gN<%pUB#_;I6&9@GdR2 zhAsLFeOT)L(#2Uz7kw@7L)Xta**$qw!_mK%0a@$^sfG?A3_Wn(z@Ap&83fz}%_=ov zq4nvmu`TdW!rYyeyAHoPTrwB(W#wRm1mBl}UZOx*M`9v;G z|F8S1$tIaJhYu-v!gidwJJn>-TH3qL8cedaFT_qIjX7q6!*h6n0n>53;DClYXnm8M zE!{8+kPthsM=u5|fWhRqR-X(H>Lf|#@FBdAeU%CG%@4mB4LM92DivX<@F-&~j&a*U zht6ilZh28p>?l@&yXPm1s7;68jD^-=5dyuB{YvhNnH5SN+=^p)_>eX?;^EMUR;ikn zJ8DvQ$1uMk%0NonJ#1VGT`t3s)*ZUf2HcdjZz{@ow!(pgeb$=-%ZBBJ0df}7eL9Z4 z)#yxCL7f*Gy$XaL*iF7hn$u_ zn1rFvSp;m8e*%TB#RZMP$femPEGzkBgM9eSG{W|jK?Dgn|2B$-S4|6zF{4Pz;W>1v(+3!q{T=HAofxhPNX5CrjmJy^a#uw$CMl{|7G3vj!v_O-^ z-k|?YW_+d8$={fe`Z1~Cg&aN<3cF*UO0n@%55u=osDd@rNEXzhM6u>MPA}Y4veQ24 zG&SPcIv`Z(lsAH*Xh3sj-r=|C1tb))Gbv87M4q5$-%sge0Y&iJ}wCZkS?W3m49fZnH+wuhyeS0ML~W-?S0f= z>!WkTpvh3ywB%AwDm2qrF60DBDRNDloJlAm?rVs!N)*++b%Cd;5m*C$&cq*iYbkOZ zZ7`-&Qc5b=0qEcutdjzBP67=x3Os`;G z9i1VA!9Lo8;5vLAlda#?#o-T_K`w@B@R*celU!V@CYzGj`Ya>DRyv;8(J@%+0vjmg2?Vt!QH}Wno8n1`MluA#v__Vg6yZI}m0Yog z=oL*5#W7VV_1o+PW7+U^Or*0#0gIW0NR#h7&=R1Z4~dEBvLSx9s5noV!~hNkW41MpRZyOD%a7&Gn~jv z7BQ#7FC0__#S(T?@pVO)&5Hctydy4)ABmCW+)jwlFaD93ou#Uu2c~r*@xbe%=r9E`_0llX2=tcu1%NW^Yx2*UfzMWKb zeG>o8L}svS!>v@Q)SQ-5bvo@<*qw}I&&9@RWFpzQ*zp#Yh6VFX+y#D#5t71(Z1ksf zP=0_}F`4VgP{VUmuv<_kHbBvDuxI%;l#_=2y9hh^+nUMI_Y$`;Jd3=dGa6>?JFVqD zj2c?1qw=w&SqRbBYIFi9{hR;28UoUD4T6wQ{%6i~2~8l2hlcHj|9y zC(f{5Mc~Mm=yfVbAe>eU3x}ICrT&ng#xciWN>WO((86iW;wX6(B0igbqvgd2W826h z&+lc{q5s0(;f0UiQKk@wy4cNKbBsfJEhXd3(vf8)4=In-1^!5>!&{b~ z5IB|itjlSv)35=|zNSM(xQ7%-UxRzHB4~saQns?~BhfqZGn05A2x@ zkWDa~sRMVhff53evV0$D=}+y@NEtGvT$`IcvabT4!7*-Q>*%XY5!@BC1Xq#Xs?#U~ zi#DJL*siCo60nkHrsSm!rpUySTr{{86?J1v4=%8@wwO00l|&kkJEo?$uw7iWCB>2N zX4+(Ip2OXBgT~}Bq+W;^6G_m}`Nw;TMc5ngSH0@-ylmxRVYv&QDn5b%^q==@KBFB( zk<1W{N&ZbA29MKRHPZfN38YqEAptfRl^*kUY6 zf%h0HH?Cm-XE3ITSBdx7L!d8O;F)3wEy;C!nMd&*W*%<2GX1god01Q@jVpH^vbO3& zKr|NXMpD>51QaJu9yp8&%vqEIX zViYgr7HwYgqd;3YI%+fWET#_=JXF{ZkF)F{B8+y*8{HH#uvW9U4&mmL%pyruWak!6 zpklD&H-|<vJ|9Ao9&xnr3aRI*ETMvDoI1W#cIXWQpA|y)R~a`$4tDm}bn}B<&|# zmP&Sv6I*~qHGUd_7-#c&$xY_nQeiEItq?J|OW!~mDK(KQtcC`0AA@s{(LN<(oF!Qj zg7`K?gGn37?O#P2NQcapAdZL~#Ew7yvxNctsj&ZH@y{YfX_yUP_{x&1&U`OL;GICW zWMQPL)DOQ&BPGK%`yP@_;R469?ko>EwGNn8;lLVg`;$OrW=8KS?fNj1q?p!1{k6k~ zSbFMeislCGV{STO)I6y@8!9bk?Uo33NlQ@+?Xe+5zap-B#6u+#9h8b_!td4kx@{Fc z{AR+suwKCiz@*N!H}PYuniox$z%|lhF%(Vs0i7Pt+?ULy(ab77E~7#jR+dnppno>$ zk+i@yjkky=DA&K4M4_@vxQ+zN@<&4KC&-L)h&RX8|1iJx`CE(1ht(Uc#n1?v6FDoy zuxX9F??4pScxq}<=`HnHB?So81#EKj541KGg7a`n;y*LVXfZjCCN?zXL;;BpGm*3I zRrm;nl}8Rkc-FEHdH~OvX&KFe2`nX> z)lx?Ibp=~cqEu4I1DxH_091%%yNSjDEXmA-$iG`*jMQZyu1|)7t!BvV;h5XBark{M zhF~6vp^?E+WXz(`bJ_=?)RGWAXP^9n=(#@d

    p%v>@m~5!siWoW0{3Z!{~4Ybasy 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-he_IL.mo b/freemius/languages/freemius-he_IL.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ad1ff4a138655df268dba88bffa8830ef28e0dd 100644 GIT binary patch literal 62213 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 Cc)wr( literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-hu_HU.mo b/freemius/languages/freemius-hu_HU.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b4e5ee1af68ff8d0bed9f2da324728fec357a589 100644 GIT binary patch literal 60199 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-it_IT.mo b/freemius/languages/freemius-it_IT.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8eadcf8e0fc21d20cab4e551adc8376fcbffad68 100644 GIT binary patch literal 61645 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 PK-&Wu6@Be?EbI!eUC*acU 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 LKS=A_HEaI`v7!?- literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-ta.mo b/freemius/languages/freemius-ta.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5c0c37c9c10713c8689f22ac67024f7d01455249 100644 GIT binary patch literal 93241 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 literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius-zh_CN.mo b/freemius/languages/freemius-zh_CN.mo index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4cc40abb8a665966c8f71ddfd6b1e62bad8cddc8 100644 GIT binary patch literal 56773 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 Dg3c$F literal 0 HcmV?d00001 diff --git a/freemius/languages/freemius.pot b/freemius/languages/freemius.pot index e69de29..24d1d20 100644 --- a/freemius/languages/freemius.pot +++ b/freemius/languages/freemius.pot @@ -0,0 +1,2556 @@ +# Copyright (C) 2021 freemius +# This file is distributed under the same license as the freemius package. +msgid "" +msgstr "" +"Project-Id-Version: freemius\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language-Team: Freemius Team \n" +"Last-Translator: Vova Feldman \n" +"Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: *.js\n" +"X-Poedit-SourceCharset: UTF-8\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: includes/class-freemius.php:1919, templates/account.php:912 +msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." +msgstr "" + +#: includes/class-freemius.php:1926 +msgid "Would you like to proceed with the update?" +msgstr "" + +#: includes/class-freemius.php:2138 +msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." +msgstr "" + +#: includes/class-freemius.php:2140 +msgid "Error" +msgstr "" + +#: includes/class-freemius.php:2540 +msgid "I found a better %s" +msgstr "" + +#: includes/class-freemius.php:2542 +msgid "What's the %s's name?" +msgstr "" + +#: includes/class-freemius.php:2548 +msgid "It's a temporary %s. I'm just debugging an issue." +msgstr "" + +#: includes/class-freemius.php:2550 +msgid "Deactivation" +msgstr "" + +#: includes/class-freemius.php:2551 +msgid "Theme Switch" +msgstr "" + +#: includes/class-freemius.php:2560, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 +msgid "Other" +msgstr "" + +#: includes/class-freemius.php:2568 +msgid "I no longer need the %s" +msgstr "" + +#: includes/class-freemius.php:2575 +msgid "I only needed the %s for a short period" +msgstr "" + +#: includes/class-freemius.php:2581 +msgid "The %s broke my site" +msgstr "" + +#: includes/class-freemius.php:2588 +msgid "The %s suddenly stopped working" +msgstr "" + +#: includes/class-freemius.php:2598 +msgid "I can't pay for it anymore" +msgstr "" + +#: includes/class-freemius.php:2600 +msgid "What price would you feel comfortable paying?" +msgstr "" + +#: includes/class-freemius.php:2606 +msgid "I don't like to share my information with you" +msgstr "" + +#: includes/class-freemius.php:2627 +msgid "The %s didn't work" +msgstr "" + +#: includes/class-freemius.php:2637 +msgid "I couldn't understand how to make it work" +msgstr "" + +#: includes/class-freemius.php:2645 +msgid "The %s is great, but I need specific feature that you don't support" +msgstr "" + +#: includes/class-freemius.php:2647 +msgid "What feature?" +msgstr "" + +#: includes/class-freemius.php:2651 +msgid "The %s is not working" +msgstr "" + +#: includes/class-freemius.php:2653 +msgid "Kindly share what didn't work so we can fix it for future users..." +msgstr "" + +#: includes/class-freemius.php:2657 +msgid "It's not what I was looking for" +msgstr "" + +#: includes/class-freemius.php:2659 +msgid "What you've been looking for?" +msgstr "" + +#: includes/class-freemius.php:2663 +msgid "The %s didn't work as expected" +msgstr "" + +#: includes/class-freemius.php:2665 +msgid "What did you expect?" +msgstr "" + +#: includes/class-freemius.php:3520, templates/debug.php:20 +msgid "Freemius Debug" +msgstr "" + +#: includes/class-freemius.php:4272 +msgid "I don't know what is cURL or how to install it, help me!" +msgstr "" + +#: includes/class-freemius.php:4274 +msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update." +msgstr "" + +#: includes/class-freemius.php:4281 +msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again." +msgstr "" + +#: includes/class-freemius.php:4386 +msgid "Yes - do your thing" +msgstr "" + +#: includes/class-freemius.php:4391 +msgid "No - just deactivate" +msgstr "" + +#: includes/class-freemius.php:4436, includes/class-freemius.php:4930, includes/class-freemius.php:6191, includes/class-freemius.php:13368, includes/class-freemius.php:14110, includes/class-freemius.php:17542, includes/class-freemius.php:17647, includes/class-freemius.php:17822, includes/class-freemius.php:20056, includes/class-freemius.php:20414, includes/class-freemius.php:20424, includes/class-freemius.php:21109, includes/class-freemius.php:22015, includes/class-freemius.php:22148, includes/class-freemius.php:22304, templates/add-ons.php:57 +msgctxt "exclamation" +msgid "Oops" +msgstr "" + +#: includes/class-freemius.php:4505 +msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience." +msgstr "" + +#: includes/class-freemius.php:4927 +msgctxt "addonX cannot run without pluginY" +msgid "%s cannot run without %s." +msgstr "" + +#: includes/class-freemius.php:4928 +msgctxt "addonX cannot run..." +msgid "%s cannot run without the plugin." +msgstr "" + +#: includes/class-freemius.php:5127, includes/class-freemius.php:5152, includes/class-freemius.php:21180 +msgid "Unexpected API error. Please contact the %s's author with the following error." +msgstr "" + +#: includes/class-freemius.php:5857 +msgid "Premium %s version was successfully activated." +msgstr "" + +#: includes/class-freemius.php:5869, includes/class-freemius.php:7774 +msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." +msgid "W00t" +msgstr "" + +#: includes/class-freemius.php:5884 +msgid "You have a %s license." +msgstr "" + +#: includes/class-freemius.php:5888, includes/class-freemius.php:16947, includes/class-freemius.php:16958, includes/class-freemius.php:20325, includes/class-freemius.php:20689, includes/class-freemius.php:20758, includes/class-freemius.php:20930 +msgctxt "interjection expressing joy or exuberance" +msgid "Yee-haw" +msgstr "" + +#: includes/class-freemius.php:6174 +msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." +msgstr "" + +#: includes/class-freemius.php:6178 +msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." +msgstr "" + +#: includes/class-freemius.php:6187, templates/add-ons.php:186, templates/account/partials/addon.php:381 +msgid "More information about %s" +msgstr "" + +#: includes/class-freemius.php:6188 +msgid "Purchase License" +msgstr "" + +#: includes/class-freemius.php:7125, templates/connect.php:171 +msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s." +msgstr "" + +#: includes/class-freemius.php:7129 +msgid "start the trial" +msgstr "" + +#: includes/class-freemius.php:7130, templates/connect.php:175 +msgid "complete the install" +msgstr "" + +#: includes/class-freemius.php:7249 +msgid "You are just one step away - %s" +msgstr "" + +#: includes/class-freemius.php:7252 +msgctxt "%s - plugin name. As complete \"PluginX\" activation now" +msgid "Complete \"%s\" Activation Now" +msgstr "" + +#: includes/class-freemius.php:7334 +msgid "We made a few tweaks to the %s, %s" +msgstr "" + +#: includes/class-freemius.php:7338 +msgid "Opt in to make \"%s\" better!" +msgstr "" + +#: includes/class-freemius.php:7773 +msgid "The upgrade of %s was successfully completed." +msgstr "" + +#: includes/class-freemius.php:10255, includes/class-fs-plugin-updater.php:1087, includes/class-fs-plugin-updater.php:1282, includes/class-fs-plugin-updater.php:1289, templates/auto-installation.php:32 +msgid "Add-On" +msgstr "" + +#: includes/class-freemius.php:10257, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549 +msgid "Plugin" +msgstr "" + +#: includes/class-freemius.php:10258, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71 +msgid "Theme" +msgstr "" + +#: includes/class-freemius.php:13188 +msgid "An unknown error has occurred while trying to toggle the license's white-label mode." +msgstr "" + +#: includes/class-freemius.php:13202 +msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." +msgstr "" + +#: includes/class-freemius.php:13207 +msgid "User Dashboard" +msgstr "" + +#: includes/class-freemius.php:13208 +msgid "revert it now" +msgstr "" + +#: includes/class-freemius.php:13266 +msgid "An unknown error has occurred while trying to set the user's beta mode." +msgstr "" + +#: includes/class-freemius.php:13339 +msgid "Invalid new user ID or email address." +msgstr "" + +#: includes/class-freemius.php:13369, includes/class-freemius.php:22259 +msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." +msgstr "" + +#: includes/class-freemius.php:13370, includes/class-freemius.php:22260 +msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." +msgstr "" + +#: includes/class-freemius.php:13377, includes/class-freemius.php:22267 +msgid "Change Ownership" +msgstr "" + +#: includes/class-freemius.php:13977 +msgid "Invalid site details collection." +msgstr "" + +#: includes/class-freemius.php:14097 +msgid "We couldn't find your email address in the system, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:14099 +msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" +msgstr "" + +#: includes/class-freemius.php:14373 +msgid "Account is pending activation." +msgstr "" + +#: includes/class-freemius.php:14485, templates/forms/premium-versions-upgrade-handler.php:47 +msgid "Buy a license now" +msgstr "" + +#: includes/class-freemius.php:14497, templates/forms/premium-versions-upgrade-handler.php:46 +msgid "Renew your license now" +msgstr "" + +#: includes/class-freemius.php:14501 +msgid "%s to access version %s security & feature updates, and support." +msgstr "" + +#: includes/class-freemius.php:16929 +msgid "%s activation was successfully completed." +msgstr "" + +#: includes/class-freemius.php:16943 +msgid "Your account was successfully activated with the %s plan." +msgstr "" + +#: includes/class-freemius.php:16954, includes/class-freemius.php:20754 +msgid "Your trial has been successfully started." +msgstr "" + +#: includes/class-freemius.php:17540, includes/class-freemius.php:17645, includes/class-freemius.php:17820 +msgid "Couldn't activate %s." +msgstr "" + +#: includes/class-freemius.php:17541, includes/class-freemius.php:17646, includes/class-freemius.php:17821 +msgid "Please contact us with the following message:" +msgstr "" + +#: includes/class-freemius.php:17642, templates/forms/data-debug-mode.php:162 +msgid "An unknown error has occurred." +msgstr "" + +#: includes/class-freemius.php:18178, includes/class-freemius.php:23340 +msgid "Upgrade" +msgstr "" + +#: includes/class-freemius.php:18184 +msgid "Start Trial" +msgstr "" + +#: includes/class-freemius.php:18186 +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:18266, includes/class-freemius.php:18268 +msgid "Affiliation" +msgstr "" + +#: includes/class-freemius.php:18296, includes/class-freemius.php:18298, templates/account.php:242, templates/debug.php:324 +msgid "Account" +msgstr "" + +#: includes/class-freemius.php:18312, includes/class-freemius.php:18314, includes/customizer/class-fs-customizer-support-section.php:60 +msgid "Contact Us" +msgstr "" + +#: includes/class-freemius.php:18325, includes/class-freemius.php:18327, includes/class-freemius.php:23354, templates/account.php:121, templates/account/partials/addon.php:44 +msgid "Add-Ons" +msgstr "" + +#: includes/class-freemius.php:18361 +msgctxt "ASCII arrow left icon" +msgid "←" +msgstr "" + +#: includes/class-freemius.php:18361 +msgctxt "ASCII arrow right icon" +msgid "➤" +msgstr "" + +#: includes/class-freemius.php:18363, templates/pricing.php:109 +msgctxt "noun" +msgid "Pricing" +msgstr "" + +#: includes/class-freemius.php:18576, includes/customizer/class-fs-customizer-support-section.php:67 +msgid "Support Forum" +msgstr "" + +#: includes/class-freemius.php:19550 +msgid "Your email has been successfully verified - you are AWESOME!" +msgstr "" + +#: includes/class-freemius.php:19551 +msgctxt "a positive response" +msgid "Right on" +msgstr "" + +#: includes/class-freemius.php:20057 +msgid "seems like the key you entered doesn't match our records." +msgstr "" + +#: includes/class-freemius.php:20081 +msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." +msgstr "" + +#: includes/class-freemius.php:20316 +msgid "Your %s Add-on plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:20318 +msgid "%s Add-on was successfully purchased." +msgstr "" + +#: includes/class-freemius.php:20321 +msgid "Download the latest version" +msgstr "" + +#: includes/class-freemius.php:20407 +msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s" +msgstr "" + +#: includes/class-freemius.php:20413, includes/class-freemius.php:20423, includes/class-freemius.php:20889, includes/class-freemius.php:20978 +msgid "Error received from the server:" +msgstr "" + +#: includes/class-freemius.php:20423 +msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." +msgstr "" + +#: includes/class-freemius.php:20651, includes/class-freemius.php:20894, includes/class-freemius.php:20949, includes/class-freemius.php:21056 +msgctxt "something somebody says when they are thinking about what you have just said." +msgid "Hmm" +msgstr "" + +#: includes/class-freemius.php:20664 +msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." +msgstr "" + +#: includes/class-freemius.php:20665, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46 +msgctxt "trial period" +msgid "Trial" +msgstr "" + +#: includes/class-freemius.php:20670 +msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." +msgstr "" + +#: includes/class-freemius.php:20674, includes/class-freemius.php:20733 +msgid "Please contact us here" +msgstr "" + +#: includes/class-freemius.php:20685 +msgid "Your plan was successfully activated." +msgstr "" + +#: includes/class-freemius.php:20686 +msgid "Your plan was successfully upgraded." +msgstr "" + +#: includes/class-freemius.php:20703 +msgid "Your plan was successfully changed to %s." +msgstr "" + +#: includes/class-freemius.php:20719 +msgid "Your license has expired. You can still continue using the free %s forever." +msgstr "" + +#: includes/class-freemius.php:20721 +msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:20729 +msgid "Your license has been cancelled. If you think it's a mistake, please contact support." +msgstr "" + +#: includes/class-freemius.php:20742 +msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." +msgstr "" + +#: includes/class-freemius.php:20768 +msgid "Your free trial has expired. You can still continue using all our free features." +msgstr "" + +#: includes/class-freemius.php:20770 +msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." +msgstr "" + +#: includes/class-freemius.php:20885 +msgid "It looks like the license could not be activated." +msgstr "" + +#: includes/class-freemius.php:20927 +msgid "Your license was successfully activated." +msgstr "" + +#: includes/class-freemius.php:20953 +msgid "It looks like your site currently doesn't have an active license." +msgstr "" + +#: includes/class-freemius.php:20977 +msgid "It looks like the license deactivation failed." +msgstr "" + +#: includes/class-freemius.php:21006 +msgid "Your %s license was successfully deactivated." +msgstr "" + +#: includes/class-freemius.php:21007 +msgid "Your license was successfully deactivated, you are back to the %s plan." +msgstr "" + +#: includes/class-freemius.php:21010 +msgid "O.K" +msgstr "" + +#: includes/class-freemius.php:21063 +msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:21072 +msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." +msgstr "" + +#: includes/class-freemius.php:21114 +msgid "You are already running the %s in a trial mode." +msgstr "" + +#: includes/class-freemius.php:21125 +msgid "You already utilized a trial before." +msgstr "" + +#: includes/class-freemius.php:21139 +msgid "Plan %s do not exist, therefore, can't start a trial." +msgstr "" + +#: includes/class-freemius.php:21150 +msgid "Plan %s does not support a trial period." +msgstr "" + +#: includes/class-freemius.php:21161 +msgid "None of the %s's plans supports a trial period." +msgstr "" + +#: includes/class-freemius.php:21211 +msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" +msgstr "" + +#: includes/class-freemius.php:21247 +msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." +msgstr "" + +#: includes/class-freemius.php:21266 +msgid "Your %s free trial was successfully cancelled." +msgstr "" + +#: includes/class-freemius.php:21582 +msgid "Version %s was released." +msgstr "" + +#: includes/class-freemius.php:21582 +msgid "Please download %s." +msgstr "" + +#: includes/class-freemius.php:21589 +msgid "the latest %s version here" +msgstr "" + +#: includes/class-freemius.php:21594 +msgid "New" +msgstr "" + +#: includes/class-freemius.php:21599 +msgid "Seems like you got the latest release." +msgstr "" + +#: includes/class-freemius.php:21600 +msgid "You are all good!" +msgstr "" + +#: includes/class-freemius.php:21903 +msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." +msgstr "" + +#: includes/class-freemius.php:22043 +msgid "Site successfully opted in." +msgstr "" + +#: includes/class-freemius.php:22044, includes/class-freemius.php:23050 +msgid "Awesome" +msgstr "" + +#: includes/class-freemius.php:22060, templates/forms/optout.php:41 +msgid "We appreciate your help in making the %s better by letting us track some usage data." +msgstr "" + +#: includes/class-freemius.php:22061 +msgid "Thank you!" +msgstr "" + +#: includes/class-freemius.php:22068 +msgid "We will no longer be sending any usage data of %s on %s to %s." +msgstr "" + +#: includes/class-freemius.php:22226 +msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder." +msgstr "" + +#: includes/class-freemius.php:22232 +msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." +msgstr "" + +#: includes/class-freemius.php:22237 +msgid "%s is the new owner of the account." +msgstr "" + +#: includes/class-freemius.php:22239 +msgctxt "as congratulations" +msgid "Congrats" +msgstr "" + +#: includes/class-freemius.php:22275 +msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments." +msgstr "" + +#: includes/class-freemius.php:22287 +msgid "Please provide your full name." +msgstr "" + +#: includes/class-freemius.php:22292 +msgid "Your name was successfully updated." +msgstr "" + +#: includes/class-freemius.php:22353 +msgid "You have successfully updated your %s." +msgstr "" + +#: includes/class-freemius.php:22412 +msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." +msgstr "" + +#: includes/class-freemius.php:22415 +msgid "Click here" +msgstr "" + +#: includes/class-freemius.php:22513 +msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." +msgstr "" + +#: includes/class-freemius.php:22514 +msgctxt "advance notice of something that will need attention." +msgid "Heads up" +msgstr "" + +#: includes/class-freemius.php:23090 +msgctxt "exclamation" +msgid "Hey" +msgstr "" + +#: includes/class-freemius.php:23090 +msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." +msgstr "" + +#: includes/class-freemius.php:23098 +msgid "No commitment for %s days - cancel anytime!" +msgstr "" + +#: includes/class-freemius.php:23099 +msgid "No credit card required" +msgstr "" + +#: includes/class-freemius.php:23106, templates/forms/trial-start.php:53 +msgctxt "call to action" +msgid "Start free trial" +msgstr "" + +#: includes/class-freemius.php:23183 +msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" +msgstr "" + +#: includes/class-freemius.php:23192 +msgid "Learn more" +msgstr "" + +#: includes/class-freemius.php:23378, templates/account.php:558, templates/account.php:708, templates/connect.php:179, templates/connect.php:461, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321 +msgid "Activate License" +msgstr "" + +#: includes/class-freemius.php:23379, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271 +msgid "Change License" +msgstr "" + +#: includes/class-freemius.php:23500, templates/account/partials/site.php:169 +msgid "Opt Out" +msgstr "" + +#: includes/class-freemius.php:23502, includes/class-freemius.php:23508, templates/account/partials/site.php:49, templates/account/partials/site.php:169 +msgid "Opt In" +msgstr "" + +#: includes/class-freemius.php:23738 +msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" +msgstr "" + +#: includes/class-freemius.php:23746 +msgid "Activate %s features" +msgstr "" + +#: includes/class-freemius.php:23759 +msgid "Please follow these steps to complete the upgrade" +msgstr "" + +#: includes/class-freemius.php:23763 +msgid "Download the latest %s version" +msgstr "" + +#: includes/class-freemius.php:23767 +msgid "Upload and activate the downloaded version" +msgstr "" + +#: includes/class-freemius.php:23769 +msgid "How to upload and activate?" +msgstr "" + +#: includes/class-freemius.php:23903 +msgid "%sClick here%s to choose the sites where you'd like to activate the license on." +msgstr "" + +#: includes/class-freemius.php:24072 +msgid "Auto installation only works for opted-in users." +msgstr "" + +#: includes/class-freemius.php:24082, includes/class-freemius.php:24115, includes/class-fs-plugin-updater.php:1261, includes/class-fs-plugin-updater.php:1275 +msgid "Invalid module ID." +msgstr "" + +#: includes/class-freemius.php:24091, includes/class-fs-plugin-updater.php:1297 +msgid "Premium version already active." +msgstr "" + +#: includes/class-freemius.php:24098 +msgid "You do not have a valid license to access the premium version." +msgstr "" + +#: includes/class-freemius.php:24105 +msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." +msgstr "" + +#: includes/class-freemius.php:24123, includes/class-fs-plugin-updater.php:1296 +msgid "Premium add-on version already installed." +msgstr "" + +#: includes/class-freemius.php:24473 +msgid "View paid features" +msgstr "" + +#: includes/class-freemius.php:24795 +msgid "Thank you so much for using %s and its add-ons!" +msgstr "" + +#: includes/class-freemius.php:24796 +msgid "Thank you so much for using %s!" +msgstr "" + +#: includes/class-freemius.php:24802 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." +msgstr "" + +#: includes/class-freemius.php:24806 +msgid "Thank you so much for using our products!" +msgstr "" + +#: includes/class-freemius.php:24807 +msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." +msgstr "" + +#: includes/class-freemius.php:24826 +msgid "%s and its add-ons" +msgstr "" + +#: includes/class-freemius.php:24835 +msgid "Products" +msgstr "" + +#: includes/class-freemius.php:24842, templates/connect.php:275 +msgid "Yes" +msgstr "" + +#: includes/class-freemius.php:24843, templates/connect.php:276 +msgid "send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:24844, templates/connect.php:281 +msgid "No" +msgstr "" + +#: includes/class-freemius.php:24846, templates/connect.php:283 +msgid "do %sNOT%s send me security & feature updates, educational content and offers." +msgstr "" + +#: includes/class-freemius.php:24856 +msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)" +msgstr "" + +#: includes/class-freemius.php:24858, templates/connect.php:290 +msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" +msgstr "" + +#: includes/class-freemius.php:25140 +msgid "License key is empty." +msgstr "" + +#: includes/class-fs-plugin-updater.php:206, templates/forms/premium-versions-upgrade-handler.php:57 +msgid "Renew license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:211, templates/forms/premium-versions-upgrade-handler.php:58 +msgid "Buy license" +msgstr "" + +#: includes/class-fs-plugin-updater.php:327, includes/class-fs-plugin-updater.php:360 +msgid "There is a %s of %s available." +msgstr "" + +#: includes/class-fs-plugin-updater.php:329, includes/class-fs-plugin-updater.php:365 +msgid "new Beta version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:330, includes/class-fs-plugin-updater.php:366 +msgid "new version" +msgstr "" + +#: includes/class-fs-plugin-updater.php:389 +msgid "Important Upgrade Notice:" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1326 +msgid "Installing plugin: %s" +msgstr "" + +#: includes/class-fs-plugin-updater.php:1367 +msgid "Unable to connect to the filesystem. Please confirm your credentials." +msgstr "" + +#: includes/class-fs-plugin-updater.php:1549 +msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:541 +msgid "Purchase More" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:542, templates/account/partials/addon.php:385 +msgctxt "verb" +msgid "Purchase" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:546 +msgid "Start my free %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:744 +msgid "Install Free Version Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:745, templates/account.php:641 +msgid "Install Update Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:754 +msgid "Install Free Version Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:755, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:365, templates/account/partials/addon.php:418 +msgid "Install Now" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:771 +msgctxt "as download latest version" +msgid "Download Latest Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:772, templates/account.php:101, templates/add-ons.php:37, templates/account/partials/addon.php:25 +msgctxt "as download latest version" +msgid "Download Latest" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:787, templates/add-ons.php:329, templates/account/partials/addon.php:356, templates/account/partials/addon.php:412 +msgid "Activate this add-on" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:789, templates/connect.php:458 +msgid "Activate Free Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:790, templates/account.php:125, templates/add-ons.php:330, templates/account/partials/addon.php:48 +msgid "Activate" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1002 +msgctxt "Plugin installer section title" +msgid "Description" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1003 +msgctxt "Plugin installer section title" +msgid "Installation" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1004 +msgctxt "Plugin installer section title" +msgid "FAQ" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1005, templates/plugin-info/description.php:55 +msgid "Screenshots" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1006 +msgctxt "Plugin installer section title" +msgid "Changelog" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1007 +msgctxt "Plugin installer section title" +msgid "Reviews" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1008 +msgctxt "Plugin installer section title" +msgid "Other Notes" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1023 +msgctxt "Plugin installer section title" +msgid "Features & Pricing" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1033 +msgid "Plugin Install" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1105 +msgctxt "e.g. Professional Plan" +msgid "%s Plan" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1131 +msgctxt "e.g. the best product" +msgid "Best" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1137, includes/fs-plugin-info-dialog.php:1157 +msgctxt "as every month" +msgid "Monthly" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1140 +msgctxt "as once a year" +msgid "Annual" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1143 +msgid "Lifetime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1157, includes/fs-plugin-info-dialog.php:1159, includes/fs-plugin-info-dialog.php:1161 +msgctxt "e.g. billed monthly" +msgid "Billed %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1159 +msgctxt "as once a year" +msgid "Annually" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1161 +msgctxt "as once a year" +msgid "Once" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1167 +msgid "Single Site License" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1169 +msgid "Unlimited Licenses" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1171 +msgid "Up to %s Sites" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1181, templates/plugin-info/features.php:82 +msgctxt "as monthly period" +msgid "mo" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1188, templates/plugin-info/features.php:80 +msgctxt "as annual period" +msgid "year" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1242 +msgctxt "noun" +msgid "Price" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1290 +msgid "Save %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1300 +msgid "No commitment for %s - cancel anytime" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1303 +msgid "After your free %s, pay as little as %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1314 +msgid "Details" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1318, templates/account.php:112, templates/debug.php:201, templates/debug.php:238, templates/debug.php:455, templates/account/partials/addon.php:36 +msgctxt "product version" +msgid "Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1325 +msgctxt "as the plugin author" +msgid "Author" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1332 +msgid "Last Updated" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1337, templates/account.php:527 +msgctxt "x-ago" +msgid "%s ago" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1346 +msgid "Requires WordPress Version" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1347 +msgid "%s or higher" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1354 +msgid "Compatible up to" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1362 +msgid "Downloaded" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1366 +msgid "%s time" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1368 +msgid "%s times" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1379 +msgid "WordPress.org Plugin Page" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1388 +msgid "Plugin Homepage" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1397, includes/fs-plugin-info-dialog.php:1481 +msgid "Donate to this plugin" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1404 +msgid "Average Rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1411 +msgid "based on %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1415 +msgid "%s rating" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1417 +msgid "%s ratings" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1432 +msgid "%s star" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1434 +msgid "%s stars" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1446 +msgid "Click to see reviews that provided a rating of %s" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1459 +msgid "Contributors" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1489, includes/fs-plugin-info-dialog.php:1491 +msgid "Warning" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1489 +msgid "This plugin has not been tested with your current version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1491 +msgid "This plugin has not been marked as compatible with your version of WordPress." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1510 +msgid "Paid add-on must be deployed to Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1511 +msgid "Add-on must be deployed to WordPress.org or Freemius." +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1532 +msgid "Newer Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1533 +msgid "Newer Free Version (%s) Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1540 +msgid "Latest Version Installed" +msgstr "" + +#: includes/fs-plugin-info-dialog.php:1541 +msgid "Latest Free Version Installed" +msgstr "" + +#: templates/account.php:102, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:26, templates/account/partials/site.php:311 +msgid "Downgrading your plan" +msgstr "" + +#: templates/account.php:103, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:27, templates/account/partials/site.php:312 +msgid "Cancelling the subscription" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account.php:105, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:314 +msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." +msgstr "" + +#: templates/account.php:106, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:30, templates/account/partials/site.php:315 +msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." +msgstr "" + +#: templates/account.php:107, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:31 +msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" +msgstr "" + +#: templates/account.php:108, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:32, templates/account/partials/site.php:316 +msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." +msgstr "" + +#: templates/account.php:109, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:33, templates/account/partials/site.php:317 +msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." +msgstr "" + +#. translators: %s: Plan title (e.g. "Professional") +#: templates/account.php:111, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:35 +msgid "Activate %s Plan" +msgstr "" + +#. translators: %s: Time period (e.g. Auto renews in "2 months") +#: templates/account.php:114, templates/account/partials/addon.php:38, templates/account/partials/site.php:291 +msgid "Auto renews in %s" +msgstr "" + +#. translators: %s: Time period (e.g. Expires in "2 months") +#: templates/account.php:116, templates/account/partials/addon.php:40, templates/account/partials/site.php:293 +msgid "Expires in %s" +msgstr "" + +#: templates/account.php:117 +msgctxt "as synchronize license" +msgid "Sync License" +msgstr "" + +#: templates/account.php:118, templates/account/partials/addon.php:41 +msgid "Cancel Trial" +msgstr "" + +#: templates/account.php:119, templates/account/partials/addon.php:42 +msgid "Change Plan" +msgstr "" + +#: templates/account.php:120, templates/account/partials/addon.php:43 +msgctxt "verb" +msgid "Upgrade" +msgstr "" + +#: templates/account.php:122, templates/account/partials/addon.php:45, templates/account/partials/site.php:318 +msgctxt "verb" +msgid "Downgrade" +msgstr "" + +#: templates/account.php:124, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:47, templates/account/partials/site.php:33 +msgid "Free" +msgstr "" + +#: templates/account.php:126, templates/debug.php:371, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:49 +msgctxt "as product pricing plan" +msgid "Plan" +msgstr "" + +#: templates/account.php:127 +msgid "Bundle Plan" +msgstr "" + +#: templates/account.php:250 +msgid "Free Trial" +msgstr "" + +#: templates/account.php:261 +msgid "Account Details" +msgstr "" + +#: templates/account.php:268, templates/forms/data-debug-mode.php:33 +msgid "Start Debug" +msgstr "" + +#: templates/account.php:270 +msgid "Stop Debug" +msgstr "" + +#: templates/account.php:277 +msgid "Billing & Invoices" +msgstr "" + +#: templates/account.php:288 +msgid "Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the \"Cancel\" button, and first \"Downgrade\" your account. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:290 +msgid "Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?" +msgstr "" + +#: templates/account.php:293 +msgid "Delete Account" +msgstr "" + +#: templates/account.php:305, templates/account/partials/addon.php:231, templates/account/partials/deactivate-license-button.php:35 +msgid "Deactivate License" +msgstr "" + +#: templates/account.php:328, templates/forms/subscription-cancellation.php:125 +msgid "Are you sure you want to proceed?" +msgstr "" + +#: templates/account.php:328, templates/account/partials/addon.php:255 +msgid "Cancel Subscription" +msgstr "" + +#: templates/account.php:357, templates/account/partials/addon.php:340 +msgctxt "as synchronize" +msgid "Sync" +msgstr "" + +#: templates/account.php:372, templates/debug.php:505 +msgid "Name" +msgstr "" + +#: templates/account.php:378, templates/debug.php:506 +msgid "Email" +msgstr "" + +#: templates/account.php:385, templates/debug.php:369, templates/debug.php:555 +msgid "User ID" +msgstr "" + +#: templates/account.php:403, templates/account.php:721, templates/account.php:754, templates/debug.php:236, templates/debug.php:363, templates/debug.php:452, templates/debug.php:504, templates/debug.php:553, templates/debug.php:632, templates/account/payments.php:35, templates/debug/logger.php:21 +msgid "ID" +msgstr "" + +#: templates/account.php:410 +msgid "Site ID" +msgstr "" + +#: templates/account.php:413 +msgid "No ID" +msgstr "" + +#: templates/account.php:418, templates/debug.php:243, templates/debug.php:372, templates/debug.php:456, templates/debug.php:508, templates/account/partials/site.php:227 +msgid "Public Key" +msgstr "" + +#: templates/account.php:424, templates/debug.php:373, templates/debug.php:457, templates/debug.php:509, templates/account/partials/site.php:239 +msgid "Secret Key" +msgstr "" + +#: templates/account.php:427 +msgctxt "as secret encryption key missing" +msgid "No Secret" +msgstr "" + +#: templates/account.php:454, templates/account/partials/site.php:120, templates/account/partials/site.php:122 +msgid "Trial" +msgstr "" + +#: templates/account.php:481, templates/debug.php:561, templates/account/partials/site.php:260 +msgid "License Key" +msgstr "" + +#: templates/account.php:512 +msgid "Join the Beta program" +msgstr "" + +#: templates/account.php:518 +msgid "not verified" +msgstr "" + +#: templates/account.php:527, templates/account/partials/addon.php:190 +msgid "Expired" +msgstr "" + +#: templates/account.php:587 +msgid "Premium version" +msgstr "" + +#: templates/account.php:589 +msgid "Free version" +msgstr "" + +#: templates/account.php:601 +msgid "Verify Email" +msgstr "" + +#: templates/account.php:615 +msgid "Download %s Version" +msgstr "" + +#: templates/account.php:631 +msgid "Download Paid Version" +msgstr "" + +#: templates/account.php:649, templates/account.php:892, templates/account/partials/site.php:248, templates/account/partials/site.php:270 +msgctxt "verb" +msgid "Show" +msgstr "" + +#: templates/account.php:664 +msgid "What is your %s?" +msgstr "" + +#: templates/account.php:672, templates/account/billing.php:21 +msgctxt "verb" +msgid "Edit" +msgstr "" + +#: templates/account.php:676, templates/forms/user-change.php:27 +msgid "Change User" +msgstr "" + +#: templates/account.php:700 +msgid "Sites" +msgstr "" + +#: templates/account.php:713 +msgid "Search by address" +msgstr "" + +#: templates/account.php:722, templates/debug.php:366 +msgid "Address" +msgstr "" + +#: templates/account.php:723 +msgid "License" +msgstr "" + +#: templates/account.php:724 +msgid "Plan" +msgstr "" + +#: templates/account.php:757 +msgctxt "as software license" +msgid "License" +msgstr "" + +#: templates/account.php:886 +msgctxt "verb" +msgid "Hide" +msgstr "" + +#: templates/account.php:908, templates/forms/data-debug-mode.php:31 +msgid "Processing" +msgstr "" + +#: templates/account.php:911 +msgid "Get updates for bleeding edge Beta versions of %s." +msgstr "" + +#: templates/account.php:969 +msgid "Cancelling %s" +msgstr "" + +#: templates/account.php:969, templates/account.php:986, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:133 +msgid "trial" +msgstr "" + +#: templates/account.php:984, templates/forms/deactivation/form.php:150 +msgid "Cancelling %s..." +msgstr "" + +#: templates/account.php:987, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:134 +msgid "subscription" +msgstr "" + +#: templates/account.php:1001 +msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" +msgstr "" + +#: templates/account.php:1075 +msgid "Disabling white-label mode" +msgstr "" + +#: templates/account.php:1076 +msgid "Enabling white-label mode" +msgstr "" + +#: templates/add-ons.php:38 +msgid "View details" +msgstr "" + +#: templates/add-ons.php:48 +msgid "Add Ons for %s" +msgstr "" + +#: templates/add-ons.php:58 +msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." +msgstr "" + +#: templates/add-ons.php:229 +msgctxt "active add-on" +msgid "Active" +msgstr "" + +#: templates/add-ons.php:230 +msgctxt "installed add-on" +msgid "Installed" +msgstr "" + +#: templates/admin-notice.php:13, templates/forms/license-activation.php:222, templates/forms/resend-key.php:77 +msgctxt "as close a window" +msgid "Dismiss" +msgstr "" + +#: templates/auto-installation.php:45 +msgid "%s sec" +msgstr "" + +#: templates/auto-installation.php:83 +msgid "Automatic Installation" +msgstr "" + +#: templates/auto-installation.php:93 +msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." +msgstr "" + +#: templates/auto-installation.php:104 +msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." +msgstr "" + +#: templates/auto-installation.php:109 +msgid "Cancel Installation" +msgstr "" + +#: templates/checkout.php:180 +msgid "Checkout" +msgstr "" + +#: templates/checkout.php:180 +msgid "PCI compliant" +msgstr "" + +#. translators: %s: name (e.g. Hey John,) +#: templates/connect.php:112 +msgctxt "greeting" +msgid "Hey %s," +msgstr "" + +#: templates/connect.php:162 +msgid "Allow & Continue" +msgstr "" + +#: templates/connect.php:166 +msgid "Re-send activation email" +msgstr "" + +#: templates/connect.php:170 +msgid "Thanks %s!" +msgstr "" + +#: templates/connect.php:180, templates/forms/license-activation.php:46 +msgid "Agree & Activate License" +msgstr "" + +#: templates/connect.php:184 +msgid "Welcome to %s! To get started, please enter your license key:" +msgstr "" + +#: templates/connect.php:191 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:192 +msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s." +msgstr "" + +#: templates/connect.php:198 +msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:199 +msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine." +msgstr "" + +#: templates/connect.php:233 +msgid "We're excited to introduce the Freemius network-level integration." +msgstr "" + +#: templates/connect.php:236 +msgid "During the update process we detected %d site(s) that are still pending license activation." +msgstr "" + +#: templates/connect.php:238 +msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." +msgstr "" + +#: templates/connect.php:240 +msgid "%s's paid features" +msgstr "" + +#: templates/connect.php:245 +msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." +msgstr "" + +#: templates/connect.php:247 +msgid "During the update process we detected %s site(s) in the network that are still pending your attention." +msgstr "" + +#: templates/connect.php:256, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49 +msgid "License key" +msgstr "" + +#: templates/connect.php:259, templates/forms/license-activation.php:22 +msgid "Can't find your license key?" +msgstr "" + +#: templates/connect.php:318, templates/connect.php:700, templates/forms/deactivation/retry-skip.php:20 +msgctxt "verb" +msgid "Skip" +msgstr "" + +#: templates/connect.php:321 +msgid "Delegate to Site Admins" +msgstr "" + +#: templates/connect.php:321 +msgid "If you click it, this decision will be delegated to the sites administrators." +msgstr "" + +#: templates/connect.php:346 +msgid "License issues?" +msgstr "" + +#: templates/connect.php:362 +msgid "Your Profile Overview" +msgstr "" + +#: templates/connect.php:363 +msgid "Name and email address" +msgstr "" + +#: templates/connect.php:370 +msgid "So you can manage and control your license remotely from the User Dashboard." +msgstr "" + +#: templates/connect.php:371 +msgid "Your Site Overview" +msgstr "" + +#: templates/connect.php:372 +msgid "Site URL, WP version, PHP info" +msgstr "" + +#: templates/connect.php:379 +msgid "Admin Notices" +msgstr "" + +#: templates/connect.php:380, templates/connect.php:398 +msgid "Updates, announcements, marketing, no spam" +msgstr "" + +#: templates/connect.php:387 +msgid "So you can reuse the license when the %s is no longer active." +msgstr "" + +#: templates/connect.php:388 +msgid "Current %s Status" +msgstr "" + +#: templates/connect.php:389 +msgid "Active, deactivated, or uninstalled" +msgstr "" + +#: templates/connect.php:397 +msgid "Newsletter" +msgstr "" + +#: templates/connect.php:405 +msgid "Plugins & Themes" +msgstr "" + +#: templates/connect.php:405 +msgid "optional" +msgstr "" + +#: templates/connect.php:406 +msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts." +msgstr "" + +#: templates/connect.php:407 +msgid "Title, slug, version, and is active" +msgstr "" + +#: templates/connect.php:424 +msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management." +msgstr "" + +#: templates/connect.php:426 +msgid "diagnostic data" +msgstr "" + +#: templates/connect.php:427 +msgid "Freemius is our licensing and software updates engine" +msgstr "" + +#: templates/connect.php:430 +msgid "What permissions are being granted?" +msgstr "" + +#: templates/connect.php:457 +msgid "Don't have a license key?" +msgstr "" + +#: templates/connect.php:460 +msgid "Have a license key?" +msgstr "" + +#: templates/connect.php:468 +msgid "Privacy Policy" +msgstr "" + +#: templates/connect.php:470 +msgid "License Agreement" +msgstr "" + +#: templates/connect.php:470 +msgid "Terms of Service" +msgstr "" + +#: templates/connect.php:866 +msgctxt "as in the process of sending an email" +msgid "Sending email" +msgstr "" + +#: templates/connect.php:867 +msgctxt "as activating plugin" +msgid "Activating" +msgstr "" + +#: templates/contact.php:78 +msgid "Contact" +msgstr "" + +#: templates/debug.php:17 +msgctxt "as turned off" +msgid "Off" +msgstr "" + +#: templates/debug.php:18 +msgctxt "as turned on" +msgid "On" +msgstr "" + +#: templates/debug.php:20 +msgid "SDK" +msgstr "" + +#: templates/debug.php:24 +msgctxt "as code debugging" +msgid "Debugging" +msgstr "" + +#: templates/debug.php:52, templates/debug.php:248, templates/debug.php:374, templates/debug.php:510 +msgid "Actions" +msgstr "" + +#: templates/debug.php:62 +msgid "Are you sure you want to delete all Freemius data?" +msgstr "" + +#: templates/debug.php:62 +msgid "Delete All Accounts" +msgstr "" + +#: templates/debug.php:69 +msgid "Clear API Cache" +msgstr "" + +#: templates/debug.php:77 +msgid "Clear Updates Transients" +msgstr "" + +#: templates/debug.php:84 +msgid "Sync Data From Server" +msgstr "" + +#: templates/debug.php:93 +msgid "Migrate Options to Network" +msgstr "" + +#: templates/debug.php:98 +msgid "Load DB Option" +msgstr "" + +#: templates/debug.php:101 +msgid "Set DB Option" +msgstr "" + +#: templates/debug.php:180 +msgid "Key" +msgstr "" + +#: templates/debug.php:181 +msgid "Value" +msgstr "" + +#: templates/debug.php:197 +msgctxt "as software development kit versions" +msgid "SDK Versions" +msgstr "" + +#: templates/debug.php:202 +msgid "SDK Path" +msgstr "" + +#: templates/debug.php:203, templates/debug.php:242 +msgid "Module Path" +msgstr "" + +#: templates/debug.php:204 +msgid "Is Active" +msgstr "" + +#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:35 +msgid "Plugins" +msgstr "" + +#: templates/debug.php:232, templates/debug/plugins-themes-sync.php:56 +msgid "Themes" +msgstr "" + +#: templates/debug.php:237, templates/debug.php:368, templates/debug.php:454, templates/debug/scheduled-crons.php:80 +msgid "Slug" +msgstr "" + +#: templates/debug.php:239, templates/debug.php:453 +msgid "Title" +msgstr "" + +#: templates/debug.php:240 +msgctxt "as application program interface" +msgid "API" +msgstr "" + +#: templates/debug.php:241 +msgid "Freemius State" +msgstr "" + +#: templates/debug.php:245 +msgid "Network Blog" +msgstr "" + +#: templates/debug.php:246 +msgid "Network User" +msgstr "" + +#: templates/debug.php:283 +msgctxt "as connection was successful" +msgid "Connected" +msgstr "" + +#: templates/debug.php:284 +msgctxt "as connection blocked" +msgid "Blocked" +msgstr "" + +#: templates/debug.php:320 +msgid "Simulate Trial Promotion" +msgstr "" + +#: templates/debug.php:332 +msgid "Simulate Network Upgrade" +msgstr "" + +#: templates/debug.php:357 +msgid "%s Installs" +msgstr "" + +#: templates/debug.php:359 +msgctxt "like websites" +msgid "Sites" +msgstr "" + +#: templates/debug.php:365, templates/account/partials/site.php:156 +msgid "Blog ID" +msgstr "" + +#: templates/debug.php:370 +msgid "License ID" +msgstr "" + +#: templates/debug.php:434, templates/debug.php:533, templates/account/partials/addon.php:435 +msgctxt "verb" +msgid "Delete" +msgstr "" + +#: templates/debug.php:448 +msgid "Add Ons of module %s" +msgstr "" + +#: templates/debug.php:500 +msgid "Users" +msgstr "" + +#: templates/debug.php:507 +msgid "Verified" +msgstr "" + +#: templates/debug.php:549 +msgid "%s Licenses" +msgstr "" + +#: templates/debug.php:554 +msgid "Plugin ID" +msgstr "" + +#: templates/debug.php:556 +msgid "Plan ID" +msgstr "" + +#: templates/debug.php:557 +msgid "Quota" +msgstr "" + +#: templates/debug.php:558 +msgid "Activated" +msgstr "" + +#: templates/debug.php:559 +msgid "Blocking" +msgstr "" + +#: templates/debug.php:560, templates/debug.php:631, templates/debug/logger.php:22 +msgid "Type" +msgstr "" + +#: templates/debug.php:562 +msgctxt "as expiration date" +msgid "Expiration" +msgstr "" + +#: templates/debug.php:590 +msgid "Debug Log" +msgstr "" + +#: templates/debug.php:594 +msgid "All Types" +msgstr "" + +#: templates/debug.php:601 +msgid "All Requests" +msgstr "" + +#: templates/debug.php:606, templates/debug.php:635, templates/debug/logger.php:25 +msgid "File" +msgstr "" + +#: templates/debug.php:607, templates/debug.php:633, templates/debug/logger.php:23 +msgid "Function" +msgstr "" + +#: templates/debug.php:608 +msgid "Process ID" +msgstr "" + +#: templates/debug.php:609 +msgid "Logger" +msgstr "" + +#: templates/debug.php:610, templates/debug.php:634, templates/debug/logger.php:24 +msgid "Message" +msgstr "" + +#: templates/debug.php:612 +msgid "Filter" +msgstr "" + +#: templates/debug.php:620 +msgid "Download" +msgstr "" + +#: templates/debug.php:636, templates/debug/logger.php:26 +msgid "Timestamp" +msgstr "" + +#: templates/secure-https-header.php:28 +msgid "Secure HTTPS %s page, running from an external domain" +msgstr "" + +#: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 +msgid "Support" +msgstr "" + +#: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 +msgctxt "milliseconds" +msgid "ms" +msgstr "" + +#: includes/debug/debug-bar-start.php:41 +msgid "Freemius API" +msgstr "" + +#: includes/debug/debug-bar-start.php:42 +msgid "Requests" +msgstr "" + +#: templates/account/billing.php:22 +msgctxt "verb" +msgid "Update" +msgstr "" + +#: templates/account/billing.php:33 +msgid "Billing" +msgstr "" + +#: templates/account/billing.php:38, templates/account/billing.php:38 +msgid "Business name" +msgstr "" + +#: templates/account/billing.php:39, templates/account/billing.php:39 +msgid "Tax / VAT ID" +msgstr "" + +#: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 +msgid "Address Line %d" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "City" +msgstr "" + +#: templates/account/billing.php:46, templates/account/billing.php:46 +msgid "Town" +msgstr "" + +#: templates/account/billing.php:47, templates/account/billing.php:47 +msgid "ZIP / Postal Code" +msgstr "" + +#: templates/account/billing.php:302 +msgid "Country" +msgstr "" + +#: templates/account/billing.php:304 +msgid "Select Country" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "State" +msgstr "" + +#: templates/account/billing.php:311, templates/account/billing.php:312 +msgid "Province" +msgstr "" + +#: templates/account/payments.php:29 +msgid "Payments" +msgstr "" + +#: templates/account/payments.php:36 +msgid "Date" +msgstr "" + +#: templates/account/payments.php:37 +msgid "Amount" +msgstr "" + +#: templates/account/payments.php:38, templates/account/payments.php:50 +msgid "Invoice" +msgstr "" + +#: templates/debug/api-calls.php:56 +msgid "API" +msgstr "" + +#: templates/debug/api-calls.php:68 +msgid "Method" +msgstr "" + +#: templates/debug/api-calls.php:69 +msgid "Code" +msgstr "" + +#: templates/debug/api-calls.php:70 +msgid "Length" +msgstr "" + +#: templates/debug/api-calls.php:71 +msgctxt "as file/folder path" +msgid "Path" +msgstr "" + +#: templates/debug/api-calls.php:73 +msgid "Body" +msgstr "" + +#: templates/debug/api-calls.php:75 +msgid "Result" +msgstr "" + +#: templates/debug/api-calls.php:76 +msgid "Start" +msgstr "" + +#: templates/debug/api-calls.php:77 +msgid "End" +msgstr "" + +#: templates/debug/logger.php:15 +msgid "Log" +msgstr "" + +#. translators: %s: time period (e.g. In "2 hours") +#: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 +msgid "In %s" +msgstr "" + +#. translators: %s: time period (e.g. "2 hours" ago) +#: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 +msgid "%s ago" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 +msgctxt "seconds" +msgid "sec" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:23 +msgid "Plugins & Themes Sync" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:28 +msgid "Total" +msgstr "" + +#: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 +msgid "Last" +msgstr "" + +#: templates/debug/scheduled-crons.php:76 +msgid "Scheduled Crons" +msgstr "" + +#: templates/debug/scheduled-crons.php:81 +msgid "Module" +msgstr "" + +#: templates/debug/scheduled-crons.php:82 +msgid "Module Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:83 +msgid "Cron Type" +msgstr "" + +#: templates/debug/scheduled-crons.php:85 +msgid "Next" +msgstr "" + +#: templates/forms/affiliation.php:82 +msgid "Non-expiring" +msgstr "" + +#: templates/forms/affiliation.php:85 +msgid "Apply to become an affiliate" +msgstr "" + +#: templates/forms/affiliation.php:107 +msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." +msgstr "" + +#: templates/forms/affiliation.php:122 +msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." +msgstr "" + +#: templates/forms/affiliation.php:125 +msgid "Your affiliation account was temporarily suspended." +msgstr "" + +#: templates/forms/affiliation.php:128 +msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." +msgstr "" + +#: templates/forms/affiliation.php:131 +msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." +msgstr "" + +#: templates/forms/affiliation.php:144 +msgid "Like the %s? Become our ambassador and earn cash ;-)" +msgstr "" + +#: templates/forms/affiliation.php:145 +msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" +msgstr "" + +#: templates/forms/affiliation.php:148 +msgid "Program Summary" +msgstr "" + +#: templates/forms/affiliation.php:150 +msgid "%s commission when a customer purchases a new license." +msgstr "" + +#: templates/forms/affiliation.php:152 +msgid "Get commission for automated subscription renewals." +msgstr "" + +#: templates/forms/affiliation.php:155 +msgid "%s tracking cookie after the first visit to maximize earnings potential." +msgstr "" + +#: templates/forms/affiliation.php:158 +msgid "Unlimited commissions." +msgstr "" + +#: templates/forms/affiliation.php:160 +msgid "%s minimum payout amount." +msgstr "" + +#: templates/forms/affiliation.php:161 +msgid "Payouts are in USD and processed monthly via PayPal." +msgstr "" + +#: templates/forms/affiliation.php:162 +msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." +msgstr "" + +#: templates/forms/affiliation.php:165 +msgid "Affiliate" +msgstr "" + +#: templates/forms/affiliation.php:168, templates/forms/resend-key.php:23 +msgid "Email address" +msgstr "" + +#: templates/forms/affiliation.php:172 +msgid "Full name" +msgstr "" + +#: templates/forms/affiliation.php:176 +msgid "PayPal account email address" +msgstr "" + +#: templates/forms/affiliation.php:180 +msgid "Where are you going to promote the %s?" +msgstr "" + +#: templates/forms/affiliation.php:182 +msgid "Enter the domain of your website or other websites from where you plan to promote the %s." +msgstr "" + +#: templates/forms/affiliation.php:184 +msgid "Add another domain" +msgstr "" + +#: templates/forms/affiliation.php:188 +msgid "Extra Domains" +msgstr "" + +#: templates/forms/affiliation.php:189 +msgid "Extra domains where you will be marketing the product from." +msgstr "" + +#: templates/forms/affiliation.php:199 +msgid "Promotion methods" +msgstr "" + +#: templates/forms/affiliation.php:202 +msgid "Social media (Facebook, Twitter, etc.)" +msgstr "" + +#: templates/forms/affiliation.php:206 +msgid "Mobile apps" +msgstr "" + +#: templates/forms/affiliation.php:210 +msgid "Website, email, and social media statistics (optional)" +msgstr "" + +#: templates/forms/affiliation.php:213 +msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." +msgstr "" + +#: templates/forms/affiliation.php:217 +msgid "How will you promote us?" +msgstr "" + +#: templates/forms/affiliation.php:220 +msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." +msgstr "" + +#: templates/forms/affiliation.php:232, templates/forms/resend-key.php:22 +msgid "Cancel" +msgstr "" + +#: templates/forms/affiliation.php:234 +msgid "Become an affiliate" +msgstr "" + +#: templates/forms/data-debug-mode.php:25 +msgid "Please enter the license key to enable the debug mode:" +msgstr "" + +#: templates/forms/data-debug-mode.php:27 +msgid "To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your \"My Profile\" section of your User Dashboard:" +msgstr "" + +#: templates/forms/data-debug-mode.php:32 +msgid "Submit" +msgstr "" + +#: templates/forms/data-debug-mode.php:36 +msgid "User key" +msgstr "" + +#: templates/forms/license-activation.php:23 +msgid "Please enter the license key that you received in the email right after the purchase:" +msgstr "" + +#: templates/forms/license-activation.php:28 +msgid "Update License" +msgstr "" + +#: templates/forms/license-activation.php:41 +msgid "The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license." +msgstr "" + +#: templates/forms/license-activation.php:183 +msgid "Associate with the license owner's account." +msgstr "" + +#: templates/forms/optout.php:30 +msgctxt "verb" +msgid "Opt Out" +msgstr "" + +#: templates/forms/optout.php:31 +msgctxt "verb" +msgid "Opt In" +msgstr "" + +#: templates/forms/optout.php:34 +msgid "Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard." +msgstr "" + +#: templates/forms/optout.php:36 +msgid "Warning: Opting out will block automatic updates" +msgstr "" + +#: templates/forms/optout.php:37 +msgid "Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won't receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you'll need to check for %1$s updates and install them manually." +msgstr "" + +#: templates/forms/optout.php:39 +msgid "I'd like to keep automatic updates" +msgstr "" + +#: templates/forms/optout.php:44 +msgid "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." +msgstr "" + +#: templates/forms/optout.php:45 +msgid "On second thought - I want to continue helping" +msgstr "" + +#: templates/forms/optout.php:49 +msgid "By clicking \"Opt Out\", we will no longer be sending any data from %s to %s." +msgstr "" + +#: templates/forms/optout.php:74 +msgid "Plugins & themes tracking" +msgstr "" + +#: templates/forms/optout.php:261 +msgid "Saved" +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:40 +msgid "There is a new version of %s available." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:41 +msgid " %s to access version %s security & feature updates, and support." +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:54 +msgid "New Version Available" +msgstr "" + +#: templates/forms/premium-versions-upgrade-handler.php:75 +msgctxt "close a window" +msgid "Dismiss" +msgstr "" + +#: templates/forms/resend-key.php:21 +msgid "Send License Key" +msgstr "" + +#: templates/forms/resend-key.php:57 +msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." +msgstr "" + +#: templates/forms/subscription-cancellation.php:37 +msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:47 +msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:52 +msgid "license" +msgstr "" + +#: templates/forms/subscription-cancellation.php:57 +msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." +msgstr "" + +#: templates/forms/subscription-cancellation.php:68 +msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." +msgstr "" + +#: templates/forms/subscription-cancellation.php:103 +msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." +msgstr "" + +#: templates/forms/subscription-cancellation.php:136 +msgid "Cancel %s?" +msgstr "" + +#: templates/forms/subscription-cancellation.php:143 +msgid "Proceed" +msgstr "" + +#: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:171 +msgid "Cancel %s & Proceed" +msgstr "" + +#: templates/forms/trial-start.php:22 +msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." +msgstr "" + +#: templates/forms/trial-start.php:28 +msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." +msgstr "" + +#: templates/forms/user-change.php:26 +msgid "By changing the user, you agree to transfer the account ownership to:" +msgstr "" + +#: templates/forms/user-change.php:28 +msgid "I Agree - Change User" +msgstr "" + +#: templates/forms/user-change.php:30 +msgid "Enter email address" +msgstr "" + +#: templates/forms/user-change.php:81 +msgctxt "close window" +msgid "Dismiss" +msgstr "" + +#: templates/js/style-premium-theme.php:39 +msgid "Premium" +msgstr "" + +#: templates/js/style-premium-theme.php:42 +msgid "Beta" +msgstr "" + +#: templates/partials/network-activation.php:27 +msgid "Activate license on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:28 +msgid "Apply on all sites in the network." +msgstr "" + +#: templates/partials/network-activation.php:31 +msgid "Activate license on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:32 +msgid "Apply on all pending sites." +msgstr "" + +#: templates/partials/network-activation.php:40, templates/partials/network-activation.php:74 +msgid "allow" +msgstr "" + +#: templates/partials/network-activation.php:43, templates/partials/network-activation.php:77 +msgid "delegate" +msgstr "" + +#: templates/partials/network-activation.php:47, templates/partials/network-activation.php:81 +msgid "skip" +msgstr "" + +#: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 +msgid "Click to view full-size screenshot %d" +msgstr "" + +#: templates/plugin-info/features.php:56 +msgid "Unlimited Updates" +msgstr "" + +#: templates/account/partials/activate-license-button.php:46 +msgid "Localhost" +msgstr "" + +#: templates/account/partials/activate-license-button.php:50 +msgctxt "as 5 licenses left" +msgid "%s left" +msgstr "" + +#: templates/account/partials/activate-license-button.php:51 +msgid "Last license" +msgstr "" + +#. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' +#: templates/account/partials/addon.php:29 +msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." +msgstr "" + +#: templates/account/partials/addon.php:185 +msgid "Cancelled" +msgstr "" + +#: templates/account/partials/addon.php:195 +msgid "No expiration" +msgstr "" + +#: templates/account/partials/site.php:189 +msgid "Owner Name" +msgstr "" + +#: templates/account/partials/site.php:201 +msgid "Owner Email" +msgstr "" + +#: templates/account/partials/site.php:213 +msgid "Owner ID" +msgstr "" + +#: templates/account/partials/site.php:286 +msgid "Subscription" +msgstr "" + +#: templates/forms/deactivation/contact.php:19 +msgid "Sorry for the inconvenience and we are here to help if you give us a chance." +msgstr "" + +#: templates/forms/deactivation/contact.php:22 +msgid "Contact Support" +msgstr "" + +#: templates/forms/deactivation/form.php:64 +msgid "Anonymous feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:70 +msgid "Deactivate" +msgstr "" + +#: templates/forms/deactivation/form.php:72 +msgid "Activate %s" +msgstr "" + +#: templates/forms/deactivation/form.php:87 +msgid "Quick Feedback" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "If you have a moment, please let us know why you are %s" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "deactivating" +msgstr "" + +#: templates/forms/deactivation/form.php:91 +msgid "switching" +msgstr "" + +#: templates/forms/deactivation/form.php:369 +msgid "Submit & %s" +msgstr "" + +#: templates/forms/deactivation/form.php:390 +msgid "Kindly tell us the reason so we can improve." +msgstr "" + +#: templates/forms/deactivation/form.php:515 +msgid "Yes - %s" +msgstr "" + +#: templates/forms/deactivation/form.php:522 +msgid "Skip & %s" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:21 +msgid "Click here to use the plugin anonymously" +msgstr "" + +#: templates/forms/deactivation/retry-skip.php:23 +msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." +msgstr "" diff --git a/freemius/languages/index.php b/freemius/languages/index.php index e69de29..0316c6a 100644 --- a/freemius/languages/index.php +++ b/freemius/languages/index.php @@ -0,0 +1,3 @@ +plugins ) ) { + $fs_active_plugins->plugins = array(); + } + } + + if ( empty( $fs_active_plugins->abspath ) ) { + /** + * Store the WP install absolute path reference to identify environment change + * while replicating the storage. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $fs_active_plugins->abspath = ABSPATH; + } else { + if ( ABSPATH !== $fs_active_plugins->abspath ) { + /** + * WordPress path has changed, cleanup the SDK references cache. + * This resolves issues triggered when spinning a staging environments + * while replicating the database. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $fs_active_plugins->abspath = ABSPATH; + $fs_active_plugins->plugins = array(); + unset( $fs_active_plugins->newest ); + } else { + /** + * Make sure SDK references are still valid. This resolves + * issues when users hard delete modules via FTP. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.7 + */ + $has_changes = false; + foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { + if ( ! file_exists( ( isset( $data->type ) && 'theme' === $data->type ? $themes_directory : WP_PLUGIN_DIR ) . '/' . $sdk_path ) ) { + unset( $fs_active_plugins->plugins[ $sdk_path ] ); + + if ( + ! empty( $fs_active_plugins->newest ) && + $sdk_path === $fs_active_plugins->newest->sdk_path + ) { + unset( $fs_active_plugins->newest ); + } + + $has_changes = true; + } + } + + if ( $has_changes ) { + if ( empty( $fs_active_plugins->plugins ) ) { + unset( $fs_active_plugins->newest ); + } + + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + } + } + + if ( ! function_exists( 'fs_find_direct_caller_plugin_file' ) ) { + require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-1.1.7.1.php'; + } + + if ( ! function_exists( 'fs_get_plugins' ) ) { + require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-2.2.1.php'; + } + + // Update current SDK info based on the SDK path. + if ( ! isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) || + $this_sdk_version != $fs_active_plugins->plugins[ $this_sdk_relative_path ]->version + ) { + if ( $is_theme ) { + $plugin_path = basename( dirname( $this_sdk_relative_path ) ); + } else { + $plugin_path = plugin_basename( fs_find_direct_caller_plugin_file( $file_path ) ); + } + + $fs_active_plugins->plugins[ $this_sdk_relative_path ] = (object) array( + 'version' => $this_sdk_version, + 'type' => ( $is_theme ? 'theme' : 'plugin' ), + 'timestamp' => time(), + 'plugin_path' => $plugin_path, + ); + } + + $is_current_sdk_newest = isset( $fs_active_plugins->newest ) && ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path ); + + if ( ! isset( $fs_active_plugins->newest ) ) { + /** + * This will be executed only once, for the first time a Freemius powered plugin is activated. + */ + fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); + + $is_current_sdk_newest = true; + } else if ( version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '<' ) ) { + /** + * Current SDK is newer than the newest stored SDK. + */ + fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); + + if ( class_exists( 'Freemius' ) ) { + // Older SDK version was already loaded. + + if ( ! $fs_active_plugins->newest->in_activation ) { + // Re-order plugins to load this plugin first. + fs_newest_sdk_plugin_first(); + } + + // Refresh page. + fs_redirect( $_SERVER['REQUEST_URI'] ); + } + } else { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $fs_newest_sdk = $fs_active_plugins->newest; + $fs_newest_sdk = $fs_active_plugins->plugins[ $fs_newest_sdk->sdk_path ]; + + $is_newest_sdk_type_theme = ( isset( $fs_newest_sdk->type ) && 'theme' === $fs_newest_sdk->type ); + + if ( ! $is_newest_sdk_type_theme ) { + $is_newest_sdk_plugin_active = is_plugin_active( $fs_newest_sdk->plugin_path ); + } else { + $current_theme = wp_get_theme(); + $is_newest_sdk_plugin_active = ( $current_theme->stylesheet === $fs_newest_sdk->plugin_path ); + + $current_theme_parent = $current_theme->parent(); + + /** + * If the current theme is a child of the theme that has the newest SDK, this prevents a redirects loop + * from happening by keeping the SDK info stored in the `fs_active_plugins` option. + */ + if ( ! $is_newest_sdk_plugin_active && $current_theme_parent instanceof WP_Theme ) { + $is_newest_sdk_plugin_active = ( $fs_newest_sdk->plugin_path === $current_theme_parent->stylesheet ); + } + } + + if ( $is_current_sdk_newest && + ! $is_newest_sdk_plugin_active && + ! $fs_active_plugins->newest->in_activation + ) { + // If current SDK is the newest and the plugin is NOT active, it means + // that the current plugin in activation mode. + $fs_active_plugins->newest->in_activation = true; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + if ( ! $is_theme ) { + $sdk_starter_path = fs_normalize_path( WP_PLUGIN_DIR . '/' . $this_sdk_relative_path . '/start.php' ); + } else { + $sdk_starter_path = fs_normalize_path( + $themes_directory + . '/' + . str_replace( "../{$themes_directory_name}/", '', $this_sdk_relative_path ) + . '/start.php' ); + } + + $is_newest_sdk_path_valid = ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) && file_exists( $sdk_starter_path ); + + if ( ! $is_newest_sdk_path_valid && ! $is_current_sdk_newest ) { + // Plugin with newest SDK is no longer active, or SDK was moved to a different location. + unset( $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ] ); + } + + if ( ! ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) || + ! $is_newest_sdk_path_valid || + // Is newest SDK downgraded. + ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && + version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '>' ) ) + ) { + /** + * Plugin with newest SDK is no longer active. + * OR + * The newest SDK was in the current plugin. BUT, seems like the version of + * the SDK was downgraded to a lower SDK. + */ + // Find the active plugin with the newest SDK version and update the newest reference. + fs_fallback_to_newest_active_sdk(); + } else { + if ( $is_newest_sdk_plugin_active && + $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && + ( $fs_active_plugins->newest->in_activation || + ( class_exists( 'Freemius' ) && ( ! defined( 'WP_FS__SDK_VERSION' ) || version_compare( WP_FS__SDK_VERSION, $this_sdk_version, '<' ) ) ) + ) + + ) { + if ( $fs_active_plugins->newest->in_activation && ! $is_newest_sdk_type_theme ) { + // Plugin no more in activation. + $fs_active_plugins->newest->in_activation = false; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + // Reorder plugins to load plugin with newest SDK first. + if ( fs_newest_sdk_plugin_first() ) { + // Refresh page after re-order to make sure activated plugin loads newest SDK. + if ( class_exists( 'Freemius' ) ) { + fs_redirect( $_SERVER['REQUEST_URI'] ); + } + } + } + } + } + + if ( class_exists( 'Freemius' ) ) { + // SDK was already loaded. + return; + } + + if ( version_compare( $this_sdk_version, $fs_active_plugins->newest->version, '<' ) ) { + $newest_sdk = $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ]; + + $plugins_or_theme_dir_path = ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) ? + WP_PLUGIN_DIR : + $themes_directory; + + $newest_sdk_starter = fs_normalize_path( + $plugins_or_theme_dir_path + . '/' + . str_replace( "../{$themes_directory_name}/", '', $fs_active_plugins->newest->sdk_path ) + . '/start.php' ); + + if ( file_exists( $newest_sdk_starter ) ) { + // Reorder plugins to load plugin with newest SDK first. + fs_newest_sdk_plugin_first(); + + // There's a newer SDK version, load it instead of the current one! + require_once $newest_sdk_starter; + + return; + } + } + + #endregion SDK Selection Logic -------------------------------------------------------------------- + + #region Hooks & Filters Collection -------------------------------------------------------------------- + + /** + * Freemius hooks (actions & filters) tags structure: + * + * fs_{filter/action_name}_{plugin_slug} + * + * -------------------------------------------------------- + * + * Usage with WordPress' add_action() / add_filter(): + * + * add_action('fs_{filter/action_name}_{plugin_slug}', $callable); + * + * -------------------------------------------------------- + * + * Usage with Freemius' instance add_action() / add_filter(): + * + * // No need to add 'fs_' prefix nor '_{plugin_slug}' suffix. + * my_freemius()->add_action('{action_name}', $callable); + * + * -------------------------------------------------------- + * + * Freemius filters collection: + * + * fs_connect_url_{plugin_slug} + * fs_trial_promotion_message_{plugin_slug} + * fs_is_long_term_user_{plugin_slug} + * fs_uninstall_reasons_{plugin_slug} + * fs_is_plugin_update_{plugin_slug} + * fs_api_domains_{plugin_slug} + * fs_email_template_sections_{plugin_slug} + * fs_support_forum_submenu_{plugin_slug} + * fs_support_forum_url_{plugin_slug} + * fs_connect_message_{plugin_slug} + * fs_connect_message_on_update_{plugin_slug} + * fs_uninstall_confirmation_message_{plugin_slug} + * fs_pending_activation_message_{plugin_slug} + * fs_is_submenu_visible_{plugin_slug} + * fs_plugin_icon_{plugin_slug} + * fs_show_trial_{plugin_slug} + * + * -------------------------------------------------------- + * + * Freemius actions collection: + * + * fs_after_license_loaded_{plugin_slug} + * fs_after_license_change_{plugin_slug} + * fs_after_plans_sync_{plugin_slug} + * + * fs_after_account_details_{plugin_slug} + * fs_after_account_user_sync_{plugin_slug} + * fs_after_account_plan_sync_{plugin_slug} + * fs_before_account_load_{plugin_slug} + * fs_after_account_connection_{plugin_slug} + * fs_account_property_edit_{plugin_slug} + * fs_account_email_verified_{plugin_slug} + * fs_account_page_load_before_departure_{plugin_slug} + * fs_before_account_delete_{plugin_slug} + * fs_after_account_delete_{plugin_slug} + * + * fs_sdk_version_update_{plugin_slug} + * fs_plugin_version_update_{plugin_slug} + * + * fs_initiated_{plugin_slug} + * fs_after_init_plugin_registered_{plugin_slug} + * fs_after_init_plugin_anonymous_{plugin_slug} + * fs_after_init_plugin_pending_activations_{plugin_slug} + * fs_after_init_addon_registered_{plugin_slug} + * fs_after_init_addon_anonymous_{plugin_slug} + * fs_after_init_addon_pending_activations_{plugin_slug} + * + * fs_after_premium_version_activation_{plugin_slug} + * fs_after_free_version_reactivation_{plugin_slug} + * + * fs_after_uninstall_{plugin_slug} + * fs_before_admin_menu_init_{plugin_slug} + */ + + #endregion Hooks & Filters Collection -------------------------------------------------------------------- + + if ( ! class_exists( 'Freemius' ) ) { + + if ( ! defined( 'WP_FS__SDK_VERSION' ) ) { + define( 'WP_FS__SDK_VERSION', $this_sdk_version ); + } + + $plugins_or_theme_dir_path = fs_normalize_path( trailingslashit( $is_theme ? + $themes_directory : + WP_PLUGIN_DIR ) ); + + if ( 0 === strpos( $file_path, $plugins_or_theme_dir_path ) ) { + // No symlinks + } else { + /** + * This logic finds the SDK symlink and set WP_FS__DIR to use it. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.2.5 + */ + $sdk_symlink = null; + + // Try to load SDK's symlink from cache. + if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + ! empty( $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink ) + ) { + $sdk_symlink = $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink; + if ( 0 === strpos( $sdk_symlink, $plugins_or_theme_dir_path ) ) { + /** + * Make the symlink path relative. + * + * @author Leo Fajardo (@leorw) + */ + $sdk_symlink = substr( $sdk_symlink, strlen( $plugins_or_theme_dir_path ) ); + + $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + + $realpath = realpath( $plugins_or_theme_dir_path . $sdk_symlink ); + if ( ! is_string( $realpath ) || ! file_exists( $realpath ) ) { + $sdk_symlink = null; + } + } + + if ( empty( $sdk_symlink ) ) // Has symlinks, therefore, we need to configure WP_FS__DIR based on the symlink. + { + $partial_path_right = basename( $file_path ); + $partial_path_left = dirname( $file_path ); + $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); + + while ( '/' !== $partial_path_left && + ( false === $realpath || $file_path !== fs_normalize_path( $realpath ) ) + ) { + $partial_path_right = trailingslashit( basename( $partial_path_left ) ) . $partial_path_right; + $partial_path_left_prev = $partial_path_left; + $partial_path_left = dirname( $partial_path_left_prev ); + + /** + * Avoid infinite loop if for example `$partial_path_left_prev` is `C:/`, in this case, + * `dirname( 'C:/' )` will return `C:/`. + * + * @author Leo Fajardo (@leorw) + */ + if ( $partial_path_left === $partial_path_left_prev ) { + $partial_path_left = ''; + break; + } + + $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); + } + + if ( ! empty( $partial_path_left ) && '/' !== $partial_path_left ) { + $sdk_symlink = fs_normalize_path( dirname( $partial_path_right ) ); + + // Cache value. + if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && + is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) + ) { + $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; + update_option( 'fs_active_plugins', $fs_active_plugins ); + } + } + } + + if ( ! empty( $sdk_symlink ) ) { + // Set SDK dir to the symlink path. + define( 'WP_FS__DIR', $plugins_or_theme_dir_path . $sdk_symlink ); + } + } + + // Load SDK files. + require_once dirname( __FILE__ ) . '/require.php'; + + /** + * Quick shortcut to get Freemius for specified plugin. + * Used by various templates. + * + * @param number $module_id + * + * @return Freemius + */ + function freemius( $module_id ) { + return Freemius::instance( $module_id ); + } + + /** + * @param string $slug + * @param number $plugin_id + * @param string $public_key + * @param bool $is_live Is live or test plugin. + * @param bool $is_premium Hints freemius if running the premium plugin or not. + * + * @return Freemius + * + * @deprecated Please use fs_dynamic_init(). + */ + function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = true ) { + $fs = Freemius::instance( $plugin_id, $slug, true ); + $fs->init( $plugin_id, $public_key, $is_live, $is_premium ); + + return $fs; + } + + /** + * @param array $module Plugin or Theme details. + * + * @return Freemius + * @throws Freemius_Exception + */ + function fs_dynamic_init( $module ) { + $fs = Freemius::instance( $module['id'], $module['slug'], true ); + $fs->dynamic_init( $module ); + + return $fs; + } + + function fs_dump_log() { + FS_Logger::dump(); + } + } diff --git a/freemius/templates/account.php b/freemius/templates/account.php index e69de29..0921366 100644 --- a/freemius/templates/account.php +++ b/freemius/templates/account.php @@ -0,0 +1,1098 @@ +get_slug(); + + /** + * @var FS_Plugin_Tag $update + */ + $update = $fs->has_release_on_freemius() ? + $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) : + null; + + if ( is_object($update) ) { + /** + * This logic is particularly required for multisite environment. + * If a module is site activated (not network) and not on the main site, + * the module will NOT be executed on the network level, therefore, the + * custom updates logic will not be executed as well, so unless we force + * the injection of the update into the updates transient, premium updates + * will not work. + * + * @author Vova Feldman (@svovaf) + * @since 2.0.0 + */ + $updater = FS_Plugin_Updater::instance( $fs ); + $updater->set_update_data( $update ); + } + + $is_paying = $fs->is_paying(); + $user = $fs->get_user(); + $site = $fs->get_site(); + $name = $user->get_name(); + $license = $fs->_get_license(); + $is_data_debug_mode = $fs->is_data_debug_mode(); + $is_whitelabeled = $fs->is_whitelabeled(); + $subscription = ( is_object( $license ) ? + $fs->_get_subscription( $license->id ) : + null ); + $plan = $fs->get_plan(); + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); + $is_paid_trial = $fs->is_paid_trial(); + $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() ); + $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial ); + $trial_plan = $fs->get_trial_plan(); + + if ( $has_paid_plan ) { + $fs->_add_license_activation_dialog_box(); + } + + $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ? + $fs->get_installs_ids_with_foreign_licenses() : + array(); + + if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) { + $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses ); + } + + if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) { + $fs->_add_data_debug_mode_dialog_box(); + } + + if ( fs_request_get_bool( 'auto_install' ) ) { + $fs->_add_auto_installation_dialog_box(); + } + + if ( fs_request_get_bool( 'activate_license' ) ) { + // Open the license activation dialog box on the account page. + add_action( 'admin_footer', array( + &$fs, + '_open_license_activation_dialog_box' + ) ); + } + + $payments = $fs->_fetch_payments(); + + $show_billing = ( ! $is_whitelabeled && is_array( $payments ) && 0 < count( $payments ) ); + + + $has_tabs = $fs->_add_tabs_before_content(); + + if ( $has_tabs ) { + $query_params['tabs'] = 'true'; + } + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); + + $show_plan_row = true; + $show_license_row = is_object( $license ); + + $site_view_params = array(); + + if ( fs_is_network_admin() ) { + $sites = Freemius::get_sites(); + $all_installs_plan_id = null; + $all_installs_license_id = ( $show_license_row ? $license->id : null ); + foreach ( $sites as $s ) { + $site_info = $fs->get_site_info( $s ); + $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); + $view_params = array( + 'freemius' => $fs, + 'license' => $license, + 'site' => $site_info, + 'install' => $install, + ); + + $site_view_params[] = $view_params; + + if ( empty( $install ) ) { + continue; + } + + if ( $show_plan_row ) { + if ( is_null( $all_installs_plan_id ) ) { + $all_installs_plan_id = $install->plan_id; + } else if ( $all_installs_plan_id != $install->plan_id ) { + $show_plan_row = false; + } + } + + if ( $show_license_row && $all_installs_license_id != $install->license_id ) { + $show_license_row = false; + } + } + } + + $has_bundle_license = false; + + if ( is_object( $license ) && + FS_Plugin_License::is_valid_id( $license->parent_license_id ) + ) { + // Context license has a parent license, therefore, the account has a bundle license. + $has_bundle_license = true; + } + + $bundle_subscription = null; + $is_bundle_first_payment_pending = false; + + if ( + $show_plan_row && + is_object( $license ) && + $has_bundle_license + ) { + $bundle_plan_title = strtoupper( $license->parent_plan_title ); + $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); + $is_bundle_first_payment_pending = $license->is_first_payment_pending(); + } + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + + $is_premium = $fs->is_premium(); + + $account_addons = $fs->get_updated_account_addons(); + $installed_addons = $fs->get_installed_addons(); + $installed_addons_ids = array(); + + /** + * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not. + * + * @author Leo Fajardo + * + * @since 2.4.0 + */ + foreach ( $installed_addons as $fs_addon ) { + $installed_addons_ids[] = $fs_addon->get_id(); + + if ( $has_bundle_license ) { + // We already have the context bundle license details, skip. + continue; + } + + if ( + $show_plan_row && + $fs_addon->has_active_valid_license() + ) { + $addon_license = $fs_addon->_get_license(); + + if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) { + // Add-on's license is associated with a parent/bundle license. + $has_bundle_license = true; + + $bundle_plan_title = strtoupper( $addon_license->parent_plan_title ); + $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id ); + $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending(); + } + } + } + + $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); + + $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); +?> +

    + apply_filters( 'hide_account_tabs', false ) ) : ?> + + + +
    +
    +
    +
    +
    +

    + +
    + + apply_filters( 'hide_license_key', false ) ); + + $profile = array(); + + if ( ! $is_whitelabeled ) { + $profile[] = array( + 'id' => 'user_name', + 'title' => fs_text_inline( 'Name', 'name', $slug ), + 'value' => $name + ); + // if (isset($user->email) && false !== strpos($user->email, '@')) + $profile[] = array( + 'id' => 'email', + 'title' => fs_text_inline( 'Email', 'email', $slug ), + 'value' => $user->email + ); + + if ( is_numeric( $user->id ) ) { + $profile[] = array( + 'id' => 'user_id', + 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), + 'value' => $user->id + ); + } + } + + $profile[] = array( + 'id' => 'product', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ), + 'value' => $fs->get_plugin_title() + ); + + $profile[] = array( + 'id' => 'product_id', + 'title' => ( $fs->is_plugin() ? + fs_text_inline( 'Plugin', 'plugin', $slug ) : + fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), + 'value' => $fs->get_id() + ); + + if ( ! fs_is_network_admin()) { + $profile[] = array( + 'id' => 'site_id', + 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), + 'value' => is_string( $site->id ) ? + $site->id : + fs_text_inline( 'No ID', 'no-id', $slug ) + ); + + $profile[] = array( + 'id' => 'site_public_key', + 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), + 'value' => $site->public_key + ); + + $profile[] = array( + 'id' => 'site_secret_key', + 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), + 'value' => ( ( is_string( $site->secret_key ) ) ? + $site->secret_key : + fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) + ) + ); + } + + $profile[] = array( + 'id' => 'version', + 'title' => $version_text, + 'value' => $fs->get_plugin_version() + ); + + if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) { + $profile[] = array( + 'id' => 'beta_program', + 'title' => '', + 'value' => $site->is_beta + ); + } + + if ( $has_paid_plan || $has_bundle_license ) { + if ( $fs->is_trial() ) { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => $plan_text, + 'value' => ( is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ) ) + ); + } + } else { + if ( $show_plan_row ) { + $profile[] = array( + 'id' => 'plan', + 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, + 'value' => strtoupper( is_string( $plan->name ) ? + $plan->title : + strtoupper( $free_text ) + ) + ); + + if ( $has_bundle_license ) { + $profile[] = array( + 'id' => 'bundle_plan', + 'title' => $bundle_plan_text, + 'value' => $bundle_plan_title + ); + } + } + + if ( is_object( $license ) ) { + if ( ! $hide_license_key ) { + $profile[] = array( + 'id' => 'license_key', + 'title' => fs_text_inline( 'License Key', $slug ), + 'value' => $license->secret_key, + ); + } + } + } + } + ?> + + + + + > + + + + + + + + + + + is_verified() ) : ?> + + + + is_trial() ) : ?> + + + is_lifetime() ) : ?> + is_first_payment_pending() ) : ?> + is_expired() ?> + + + is_first_payment_pending() ) : ?> + + + is_trial() ) : ?> + + + +
    + is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?> + + _get_plan_by_id( $available_license->plan_id ) ?> + $fs, + 'slug' => $slug, + 'license' => $available_license, + 'plan' => $premium_plan, + 'is_localhost' => $site->is_localhost(), + 'install_id' => $site->id, + 'class' => 'button-primary', + ); + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?> + +
    + + + + + + get_unique_affix() . '_sync_license' ) ?> + is_single_plan() ) : ?> + + + + +
    + + + + + + + + + has_premium_version() ) : ?> + + + can_use_premium_code() ) : ?> + + + + + + +
    + + + +
    + + + is_verified() ) : ?> +
    + + + +
    + + + has_release_on_freemius() ) : ?> + + + + + + + + + + secret_key ) && in_array( $p['id'], array( + 'email', + 'user_name' + ) ) ) + ) : ?> +
    + + + + +
    + + + +
    +
    +
    + +
    +

    +
    + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + + + is_whitelabeled_by_flag() ) { + $hide_all_addons_data = true; + + foreach ( $addons_to_show as $addon_id ) { + $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); + $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed ); + $is_addon_connected = $addon_info['is_connected']; + + $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? + freemius( $addon_id ) : + null; + + $is_whitelabeled = is_object( $fs_addon ) ? + $fs_addon->is_whitelabeled( true ) : + $addon_info['is_whitelabeled']; + + if ( ! $is_whitelabeled ) { + $hide_all_addons_data = false; + } + + if ( $is_data_debug_mode ) { + $is_whitelabeled = false; + } + + $addon_info_by_id[ $addon_id ] = $addon_info; + } + } + + foreach ( $addons_to_show as $addon_id ) { + $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); + + if ( + $hide_all_addons_data && + ! $is_addon_installed && + ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) ) + ) { + continue; + } + + $addon_view_params = array( + 'parent_fs' => $fs, + 'addon_id' => $addon_id, + 'odd' => $odd, + 'fs_blog_id' => $fs_blog_id, + 'active_plugins_directories_map' => &$active_plugins_directories_map, + 'is_addon_installed' => $is_addon_installed, + 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ? + $addon_info_by_id[ $addon_id ] : + $fs->_get_addon_info( $addon_id, $is_addon_installed ), + 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode ) + ); + + fs_require_template( + 'account/partials/addon.php', + $addon_view_params + ); + + $odd = ! $odd; + } ?> + +

    +
    +
    + + + do_action( 'after_account_details' ) ?> + + $VARS['id'] ); + fs_require_once_template( 'account/billing.php', $view_params ); + fs_require_once_template( 'account/payments.php', $view_params ); + } + ?> +
    +
    +
    +
    +
    + _get_subscription_cancellation_dialog_box_template_params( true ); + if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { + fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); + } + ?> + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'account', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/account/billing.php b/freemius/templates/account/billing.php index e69de29..a4de409 100644 --- a/freemius/templates/account/billing.php +++ b/freemius/templates/account/billing.php @@ -0,0 +1,423 @@ +get_slug(); + + $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); + $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); + + $billing = $fs->_fetch_billing(); + $has_billing = ( $billing instanceof FS_Billing ); + if ( ! $has_billing ) { + $billing = new FS_Billing(); + } +?> + +
    +
    +

    + > + + + + + + + + + + + + + + 'Afghanistan', + 'AX' => 'Aland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire, Saint Eustatius and Saba', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'VG' => 'British Virgin Islands', + 'BN' => 'Brunei', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CA' => 'Canada', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CW' => 'Curacao', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'CD' => 'Democratic Republic of the Congo', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'TL' => 'East Timor', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'CI' => 'Ivory Coast', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'XK' => 'Kosovo', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Laos', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macao', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MD' => 'Moldova', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territory', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'CG' => 'Republic of the Congo', + 'RE' => 'Reunion', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthelemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'ST' => 'Sao Tome and Principe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SX' => 'Sint Maarten', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'KR' => 'South Korea', + 'SS' => 'South Sudan', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syria', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'VI' => 'U.S. Virgin Islands', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'GB' => 'United Kingdom', + 'US' => 'United States', + 'UM' => 'United States Minor Outlying Islands', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'WF' => 'Wallis and Futuna', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + ) ?> + + + + + + +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/freemius/templates/account/index.php b/freemius/templates/account/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/account/index.php +++ b/freemius/templates/account/index.php @@ -0,0 +1,3 @@ + +
    + + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/addon.php b/freemius/templates/account/partials/addon.php index e69de29..e6d5657 100644 --- a/freemius/templates/account/partials/addon.php +++ b/freemius/templates/account/partials/addon.php @@ -0,0 +1,451 @@ +get_slug(); + + $fs_blog_id = $VARS['fs_blog_id']; + + $active_plugins_directories_map = $VARS['active_plugins_directories_map']; + + $addon_info = $VARS['addon_info']; + $is_addon_activated = $fs->is_addon_activated( $addon_id ); + $is_addon_connected = $addon_info['is_connected']; + $is_addon_installed = $VARS['is_addon_installed']; + + $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? + freemius( $addon_id ) : + false; + + // Aliases. + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); + $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); + /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ + $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); + $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); + $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); + $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); + $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); + /* translators: %s: Plan title (e.g. "Professional") */ + $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); + $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); + /* translators: %s: Time period (e.g. Auto renews in "2 months") */ + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); + $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); + $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); + $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); + $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); + $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); + $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); + + // Defaults. + $plan = null; + $is_paid_trial = false; + /** + * @var FS_Plugin_License $license + */ + $license = null; + $site = null; + $is_active_subscription = false; + $subscription = null; + $is_paying = false; + $show_upgrade = false; + $is_whitelabeled = $VARS['is_whitelabeled']; + + if ( is_object( $fs_addon ) ) { + $is_paying = $fs_addon->is_paying(); + $user = $fs_addon->get_user(); + $site = $fs_addon->get_site(); + $license = $fs_addon->_get_license(); + $subscription = ( is_object( $license ) ? + $fs_addon->_get_subscription( $license->id ) : + null ); + $plan = $fs_addon->get_plan(); + $plan_name = $plan->name; + $plan_title = $plan->title; + $is_paid_trial = $fs_addon->is_paid_trial(); + $version = $fs_addon->get_plugin_version(); + $is_whitelabeled = ( + $fs_addon->is_whitelabeled( true ) && + ! $fs_addon->get_parent_instance()->is_data_debug_mode() + ); + $show_upgrade = ( + ! $is_whitelabeled && + $fs_addon->has_paid_plan() && + ! $is_paying && + ! $is_paid_trial && + ! $fs_addon->_has_premium_license() + ); + } else if ( $is_addon_connected ) { + if ( + empty( $addon_info ) || + ! isset( $addon_info['site'] ) + ) { + $is_addon_connected = false; + } else { + /** + * @var FS_Site $site + */ + $site = $addon_info['site']; + $version = $addon_info['version']; + + $plan_name = isset( $addon_info['plan_name'] ) ? + $addon_info['plan_name'] : + ''; + + $plan_title = isset( $addon_info['plan_title'] ) ? + $addon_info['plan_title'] : + ''; + + if ( isset( $addon_info['license'] ) ) { + $license = $addon_info['license']; + } + + if ( isset( $addon_info['subscription'] ) ) { + $subscription = $addon_info['subscription']; + } + + $has_valid_and_active_license = ( + is_object( $license ) && + $license->is_active() && + $license->is_valid() + ); + + $is_paid_trial = ( + $site->is_trial() && + $has_valid_and_active_license && + ( $site->trial_plan_id == $license->plan_id ) + ); + + $is_whitelabeled = $addon_info['is_whitelabeled']; + } + } + + $has_feature_enabled_license = ( + is_object( $license ) && + $license->is_features_enabled() + ); + + $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); + + $show_delete_install_button = ( ! $is_paying && WP_FS__DEV_MODE && ! $is_whitelabeled ); +?> +> + + + + + + + id ?> + + + + + + + + + + + + + is_trial() || is_object( $license ) ) : ?> + is_trial() ) { + $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); + + $tags[] = array( + 'label' => sprintf( + ( $is_paid_trial ? + $renews_in_text : + $expires_in_text ), + human_time_diff( time(), strtotime( $site->trial_ends ) ) + ), + 'type' => ( $is_paid_trial ? 'success' : 'warn' ) + ); + } else { + if ( is_object( $license ) ) { + if ( $license->is_cancelled ) { + $tags[] = array( + 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_expired() ) { + $tags[] = array( + 'label' => fs_text_inline( 'Expired', 'expired', $slug ), + 'type' => 'error' + ); + } else if ( $license->is_lifetime() ) { + $tags[] = array( + 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), + 'type' => 'success' + ); + } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), + 'type' => 'warn' + ); + } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { + $tags[] = array( + 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), + 'type' => 'success' + ); + } + } + } + + foreach ( $tags as $t ) { + printf( '' . "\n", $t['type'], $t['label'] ); + } + ?> + + + + + get_id(), + 'account', + 'deactivate_license', + fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false, + true + ); + + $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + + if ( ! $license->is_lifetime() && $is_active_subscription ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'downgrade_account', + esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + false, + ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), + 'POST' + ); + } + } else if ( $is_paid_trial ) { + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'cancel_trial', + esc_html( $cancel_trial_text ), + '', + array( 'plugin_id' => $addon_id ), + false, + false, + 'dashicons dashicons-download', + $cancel_trial_confirm_text, + 'POST' + ); + } else if ( ! $has_feature_enabled_license ) { + $premium_licenses = $fs_addon->get_available_premium_licenses(); + + if ( ! empty( $premium_licenses ) ) { + $premium_license = $premium_licenses[0]; + $has_multiple_premium_licenses = ( 1 < count( $premium_licenses ) ); + + if ( ! $has_multiple_premium_licenses ) { + $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); + $site = $fs_addon->get_site(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), + ($has_multiple_premium_licenses ? + 'activate-license-trigger ' . $fs_addon->get_unique_affix() : + ''), + array( + 'plugin_id' => $addon_id, + 'license_id' => $premium_license->id, + ), + true, + true + ); + + $is_license_activation_added = true; + } + } + } + } + +// if ( 0 == count( $buttons ) ) { + if ( $fs_addon->is_premium() && ! $is_license_activation_added ) { + $fs_addon->_add_license_activation_dialog_box(); + + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + 'activate_license', + ( ! $has_feature_enabled_license ) ? + fs_esc_html_inline( 'Activate License', 'activate-license', $slug ) : + fs_esc_html_inline( 'Change License', 'change-license', $slug ), + 'activate-license-trigger ' . $fs_addon->get_unique_affix(), + array( + 'plugin_id' => $addon_id, + ), + (! $has_feature_enabled_license), + true + ); + + $is_license_activation_added = true; + } + + if ( $fs_addon->has_paid_plan() ) { + // Add sync license only if non of the other CTAs are visible. + $buttons[] = fs_ui_get_action_button( + $fs->get_id(), + 'account', + $fs->get_unique_affix() . '_sync_license', + fs_esc_html_x_inline( 'Sync', 'as synchronize', 'sync', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false, + true + ); + } +// } + } else if ( ! $show_upgrade ) { + if ( $fs->is_addon_installed( $addon_id ) ) { + $addon_file = $fs->get_addon_basename( $addon_id ); + + if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), + $activate_text + ); + } + } else { + if ( $fs->is_allowed_to_install() ) { + $buttons[] = sprintf( + '%s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), + fs_text_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + $buttons[] = sprintf( + '%s', + $fs->_get_latest_download_local_url( $addon_id ), + esc_html( $download_latest_text ) + ); + } + } + } + + if ( $show_upgrade ) { + $buttons[] = sprintf( ' %s', + esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), + esc_attr( $addon_info['title'] ), + ( $fs_addon->has_free_plan() ? + $upgrade_text : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) + ); + } + + $buttons_count = count( $buttons ); + ?> + + + 1 ) : ?> +
    + + 1 ) : ?>
    + + + + is_addon_installed( $addon_id ); + ?> + + + + get_addon_basename( $addon_id ) ?> + + + + + is_allowed_to_install() ) : ?> + + + + + + + + + + + + get_id(), 'account', + 'delete_account', + fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), + '', + array( 'plugin_id' => $addon_id ), + false, + $show_upgrade + ); + } + ?> + + + + diff --git a/freemius/templates/account/partials/deactivate-license-button.php b/freemius/templates/account/partials/deactivate-license-button.php index e69de29..123b092 100644 --- a/freemius/templates/account/partials/deactivate-license-button.php +++ b/freemius/templates/account/partials/deactivate-license-button.php @@ -0,0 +1,36 @@ + +
    + + + + + +
    \ No newline at end of file diff --git a/freemius/templates/account/partials/index.php b/freemius/templates/account/partials/index.php index e69de29..0316c6a 100644 --- a/freemius/templates/account/partials/index.php +++ b/freemius/templates/account/partials/index.php @@ -0,0 +1,3 @@ +get_slug(); + $site = $VARS['site']; + $main_license = $VARS['license']; + $is_data_debug_mode = $fs->is_data_debug_mode(); + $is_whitelabeled = $fs->is_whitelabeled(); + $has_paid_plan = $fs->has_paid_plan(); + $is_premium = $fs->is_premium(); + $main_user = $fs->get_user(); + $blog_id = $site['blog_id']; + + $install = $VARS['install']; + $is_registered = ! empty( $install ); + $license = null; + $trial_plan = $fs->get_trial_plan(); + $free_text = fs_text_inline( 'Free', 'free', $slug ); + + if ( $is_whitelabeled && $fs->is_delegated_connection( $blog_id ) ) { + $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id ); + } +?> + data-install-id="id ?>"> + + + id ?> + + +
    + + + + +
    + + + + + + + + + + $fs, + 'slug' => $slug, + 'blog_id' => $blog_id, + 'class' => 'button-small', + ); + + $license = null; + if ( $is_registered ) { + $view_params['install_id'] = $install->id; + $view_params['is_localhost'] = $install->is_localhost(); + + $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); + $license = $has_license ? + $fs->_get_license_by_id( $install->license_id ) : + null; + } else { + $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); + } + + if ( ! $is_whitelabeled ) { + if ( is_object( $license ) ) { + $view_params['license'] = $license; + + // Show license deactivation button. + fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); + } else { + if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { + // Main license is available for activation. + $available_license = $main_license; + } else { + // Try to find any available license for activation. + $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); + } + + if ( is_object( $available_license ) ) { + $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); + + $view_params['license'] = $available_license; + $view_params['class'] .= ' button-primary'; + $view_params['plan'] = $premium_plan; + + fs_require_template( 'account/partials/activate-license-button.php', $view_params ); + } + } + } + } ?> + + + + + is_trial() ) { + if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) { + $plan_title = is_string( $trial_plan->name ) ? + strtoupper( $trial_plan->title ) : + fs_text_inline( 'Trial', 'trial', $slug ); + } else { + $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); + } + } else { + $plan = $fs->_get_plan_by_id( $install->plan_id ); + $plan_title = strtoupper( is_string( $plan->title ) ? + $plan->title : + strtoupper( $free_text ) + ); + } + } + ?> + + + + + + + + + + + + + + + + + + > + + + + + + + + user_id != $main_user->id ) : ?> + user_id ) ?> + + + > + + + + + + + + > + + + + + + + + > + + + + + + + + + + > + + + + + + + + > + + + + + + + + + + + + + > + + + + + + + + + + + id != $license->id ) : ?> + _get_subscription( $license->id ) ?> + is_lifetime() && is_object( $subscription ) ) : ?> + + > + + is_active(); + + $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); + /* translators: %s: Time period (e.g. Expires in "2 months") */ + $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); + ?> + + + + + + + + + + +
    + : + license_id ) ) : ?> + + +
    + + id}", ':' ) ) ?> + + + +
    + +
    + : + get_name() ) ?>
    + : + email ) ?>
    + : + id ?>
    + : + public_key ) ?>
    + : + + secret_key ) ?> + +
    + : + + get_html_escaped_masked_secret_key() ?> + + + + +
    + : + + id ?> - billing_cycle ? + _fs_text_inline( 'Annual', 'annual', $slug ) : + _fs_text_inline( 'Monthly', 'monthly', $slug ) + ); + ?> + + is_first_payment_pending() ) : ?> + + is_first_payment_pending() ) : ?> + + + + expiration ) ); + $downgrade_confirmation_message = sprintf( + $downgrade_x_confirm_text, + ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), + $plan->title, + $human_readable_license_expiration + ); + + $after_downgrade_message = ! $license->is_block_features ? + sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) : + sprintf( $after_downgrade_blocking_text, $plan->title ); + ?> + +
    + + + + +
    +
    + + + + \ No newline at end of file diff --git a/freemius/templates/account/payments.php b/freemius/templates/account/payments.php index e69de29..fd54c9b 100644 --- a/freemius/templates/account/payments.php +++ b/freemius/templates/account/payments.php @@ -0,0 +1,59 @@ +get_slug(); + + $payments = $fs->_fetch_payments(); + + $show_payments = ( is_array( $payments ) && 0 < count( $payments ) ); + + if ( $show_payments ) : +?> +
    +
    +

    + +
    + + + + + + + + + + + + + > + + + + + + + +
    id ?>created ) ) ?>formatted_gross() ?>is_migrated() ) : ?>
    +
    +
    +
    +get_slug(); + + $open_addon_slug = fs_request_get( 'slug' ); + + $open_addon = false; + + $is_data_debug_mode = $fs->is_data_debug_mode(); + $is_whitelabeled = $fs->is_whitelabeled(); + + /** + * @var FS_Plugin[] + */ + $addons = $fs->get_addons(); + + $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); + + $account_addon_ids = $fs->get_updated_account_addons(); + + $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); + $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); + + $has_tabs = $fs->_add_tabs_before_content(); + + $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? + get_current_blog_id() : + 0; +?> +
    + +

    get_plugin_name() ) ) ?>

    + + + do_action( 'addons/after_title' ) ?> + +
    + +

    + +
      + + _get_addons_plans_and_pricing_map_by_id(); + + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); + ?> + is_whitelabeled_by_flag() ) { + $hide_all_addons_data = true; + + $addon_ids = $fs->get_updated_account_addons(); + $installed_addons = $fs->get_installed_addons(); + foreach ( $installed_addons as $fs_addon ) { + $addon_ids[] = $fs_addon->get_id(); + } + + if ( ! empty( $addon_ids ) ) { + $addon_ids = array_unique( $addon_ids ); + } + + foreach ( $addon_ids as $addon_id ) { + $addon = $fs->get_addon( $addon_id ); + + if ( ! is_object( $addon ) ) { + continue; + } + + $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); + + if ( ! $addon_storage->is_whitelabeled ) { + $hide_all_addons_data = false; + break; + } + + if ( $is_data_debug_mode ) { + $is_whitelabeled = false; + } + } + } + ?> + + get_addon_basename( $addon->id ); + + $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); + + if ( ! $is_addon_installed && $hide_all_addons_data ) { + continue; + } + + $is_addon_activated = $is_addon_installed ? + $fs->is_addon_activated( $addon->id ) : + false; + + $is_plugin_active = ( + $is_addon_activated || + isset( $active_plugins_directories_map[ dirname( $basename ) ] ) + ); + + $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); + + $price = 0; + $has_trial = false; + $has_free_plan = false; + $has_paid_plan = false; + + if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { + $plans = $plans_and_pricing_by_addon_id[$addon->id]; + + if ( is_array( $plans ) && 0 < count( $plans ) ) { + foreach ( $plans as $plan ) { + if ( ! isset( $plan->pricing ) || + ! is_array( $plan->pricing ) || + 0 == count( $plan->pricing ) + ) { + // No pricing means a free plan. + $has_free_plan = true; + continue; + } + + + $has_paid_plan = true; + $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); + + $min_price = 999999; + foreach ( $plan->pricing as $pricing ) { + $pricing = new FS_Pricing( $pricing ); + + if ( ! $pricing->is_usd() ) { + /** + * Skip non-USD pricing. + * + * @author Leo Fajardo (@leorw) + * @since 2.3.1 + */ + continue; + } + + if ( $pricing->has_annual() ) { + $min_price = min( $min_price, $pricing->annual_price ); + } else if ( $pricing->has_monthly() ) { + $min_price = min( $min_price, 12 * $pricing->monthly_price ); + } + } + + if ( $min_price < 999999 ) { + $price = $min_price; + } + + } + } + + if ( ! $has_paid_plan && ! $has_free_plan ) { + continue; + } + } + ?> +
    • + get_id() . '&plugin=' . $addon->slug . + '&TB_iframe=true&width=600&height=550' ) ), + esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), + esc_attr( $addon->title ) + ) . ' class="thickbox%s">%s'; + + echo sprintf( + $view_details_link, + /** + * Additional class. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + ' fs-overlay', + /** + * Set the view details link text to an empty string since it is an overlay that + * doesn't really need a text and whose purpose is to open the details dialog when + * the card is clicked. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.4 + */ + '' + ); + ?> + info ) ) { + $addon->info = new stdClass(); + } + if ( ! isset( $addon->info->card_banner_url ) ) { + $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; + } + if ( ! isset( $addon->info->short_description ) ) { + $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; + } + ?> +
      +
        +
      • %s', + esc_html( $is_plugin_active ? + fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : + fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) + ) + ); + } + ?>
      • + +
      • title ?>
      • +
      • + 0) + $descriptors[] = '$' . number_format( $price, 2 ); + if ($has_trial) + $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); + + echo implode(' - ', $descriptors); + + } ?> +
      • +
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • + is_wp_org_compliant ); + + $is_allowed_to_install = ( + $fs->is_allowed_to_install() || + $is_free_only_wp_org_compliant + ); + + $show_premium_activation_or_installation_action = true; + + if ( ! in_array( $addon->id, $account_addon_ids ) ) { + $show_premium_activation_or_installation_action = false; + } else if ( $is_addon_installed ) { + /** + * If any add-on's version (free or premium) is installed, check if the + * premium version can be activated and show the relevant action. Otherwise, + * show the relevant action for the free version. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + $fs_addon = $is_addon_activated ? + $fs->get_addon_instance( $addon->id ) : + null; + + $premium_plugin_basename = is_object( $fs_addon ) ? + $fs_addon->premium_plugin_basename() : + "{$addon->premium_slug}/{$addon->slug}.php"; + + if ( + ( $is_addon_activated && $fs_addon->is_premium() ) || + file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) + ) { + $basename = $premium_plugin_basename; + } + + $show_premium_activation_or_installation_action = ( + ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && + /** + * This check is needed for cases when an active add-on doesn't have an + * associated Freemius instance. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.5 + */ + ( ! $is_plugin_active ) + ); + } + ?> + +
      • + + _get_latest_download_local_url( $addon->id ); + ?> + +
      • +
        + + %s', + wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), + fs_esc_html_inline( 'Install Now', 'install-now', $slug ) + ); + } else { + echo sprintf( + '%s', + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), + fs_text_inline( 'Activate', 'activate', $addon->slug ) + ); + } + ?> + + + +
        +
        +
      • + +
      +
      +
    • + + +
    +
    + + do_action( 'addons/after_addons' ) ?> +
    + +_add_tabs_after_content(); + } + + $params = array( + 'page' => 'addons', + 'module_id' => $fs->get_id(), + 'module_type' => $fs->get_module_type(), + 'module_slug' => $slug, + 'module_version' => $fs->get_plugin_version(), + ); + fs_require_template( 'powered-by.php', $params ); \ No newline at end of file diff --git a/freemius/templates/add-trial-to-pricing.php b/freemius/templates/add-trial-to-pricing.php index e69de29..24fc885 100644 --- a/freemius/templates/add-trial-to-pricing.php +++ b/freemius/templates/add-trial-to-pricing.php @@ -0,0 +1,31 @@ + + \ No newline at end of file diff --git a/freemius/templates/admin-notice.php b/freemius/templates/admin-notice.php index e69de29..6079e71 100644 --- a/freemius/templates/admin-notice.php +++ b/freemius/templates/admin-notice.php @@ -0,0 +1,76 @@ + + data-id="" data-manager-id="" data-slug="" data-type="" + class=" fs-notice"> + + + +
    +
    + +
    + + +
    +